/* * Copyright (C) 2014-2025 Muhammad Tayyab Akram * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include "BidiChain.h" #include "BidiTypeLookup.h" #include "IsolatingRun.h" #include "LevelRun.h" #include "Object.h" #include "RunQueue.h" #include "SBAlgorithm.h" #include "SBAssert.h" #include "SBBase.h" #include "SBCodepointSequence.h" #include "SBLine.h" #include "SBLog.h" #include "StatusStack.h" #include "SBParagraph.h" typedef struct _ParagraphContext { Object object; BidiChain bidiChain; StatusStack statusStack; RunQueue runQueue; IsolatingRun isolatingRun; } ParagraphContext, *ParagraphContextRef; static void PopulateBidiChain(BidiChainRef chain, const SBBidiType *types, SBUInteger length); static SBBoolean ProcessRun(ParagraphContextRef context, const LevelRunRef levelRun, SBBoolean forceFinish); #define BIDI_LINKS 0 #define BIDI_TYPES 1 #define BIDI_FLAGS 2 #define COUNT 3 static SBBoolean InitializeParagraphContext(ParagraphContextRef context, const SBBidiType *types, SBLevel *levels, SBUInteger length) { SBBoolean isInitialized = SBFalse; void *pointers[COUNT] = { NULL }; SBUInteger sizes[COUNT]; sizes[BIDI_LINKS] = sizeof(BidiLink) * (length + 2); sizes[BIDI_TYPES] = sizeof(SBBidiType) * (length + 2); sizes[BIDI_FLAGS] = sizeof(BidiFlag) * (length + 2); ObjectInitialize(&context->object); if (ObjectAddMemoryWithChunks(&context->object, sizes, COUNT, pointers)) { BidiLink *fixedLinks = pointers[BIDI_LINKS]; SBBidiType *fixedTypes = pointers[BIDI_TYPES]; BidiFlag *fixedFlags = pointers[BIDI_FLAGS]; BidiChainInitialize(&context->bidiChain, fixedTypes, levels, fixedFlags, fixedLinks); StatusStackInitialize(&context->statusStack); RunQueueInitialize(&context->runQueue); IsolatingRunInitialize(&context->isolatingRun); PopulateBidiChain(&context->bidiChain, types, length); isInitialized = SBTrue; } return isInitialized; } #undef BIDI_LINKS #undef BIDI_TYPES #undef BIDI_FLAGS #undef COUNT static void FinalizeParagraphContext(ParagraphContextRef context) { StatusStackFinalize(&context->statusStack); RunQueueFinalize(&context->runQueue); IsolatingRunFinalize(&context->isolatingRun); ObjectFinalize(&context->object); } #define PARAGRAPH 0 #define LEVELS 1 #define COUNT 2 static SBParagraphRef AllocateParagraph(SBUInteger length) { void *pointers[COUNT] = { NULL }; SBUInteger sizes[COUNT]; sizes[PARAGRAPH] = sizeof(SBParagraph); sizes[LEVELS] = sizeof(SBLevel) * (length + 2); if (ObjectCreate(sizes, COUNT, pointers)) { SBParagraphRef paragraph = pointers[PARAGRAPH]; SBLevel *levels = pointers[LEVELS]; paragraph->fixedLevels = levels; } return pointers[PARAGRAPH]; } #undef PARAGRAPH #undef LEVELS #undef COUNT static void DisposeParagraph(SBParagraphRef paragraph) { ObjectDispose(¶graph->_object); } static SBUInteger DetermineBoundary(SBAlgorithmRef algorithm, SBUInteger paragraphOffset, SBUInteger suggestedLength) { SBBidiType *bidiTypes = algorithm->fixedTypes; SBUInteger suggestedLimit = paragraphOffset + suggestedLength; SBUInteger stringIndex; for (stringIndex = paragraphOffset; stringIndex < suggestedLimit; stringIndex++) { if (bidiTypes[stringIndex] == SBBidiTypeB) { stringIndex += SBAlgorithmGetSeparatorLength(algorithm, stringIndex); goto Return; } } Return: return (stringIndex - paragraphOffset); } static void PopulateBidiChain(BidiChainRef chain, const SBBidiType *types, SBUInteger length) { SBBidiType type = SBBidiTypeNil; SBUInteger priorIndex = SBInvalidIndex; SBUInteger index; for (index = 0; index < length; index++) { SBBidiType priorType = type; type = types[index]; switch (type) { case SBBidiTypeB: case SBBidiTypeON: case SBBidiTypeLRE: case SBBidiTypeRLE: case SBBidiTypeLRO: case SBBidiTypeRLO: case SBBidiTypePDF: case SBBidiTypeLRI: case SBBidiTypeRLI: case SBBidiTypeFSI: case SBBidiTypePDI: BidiChainAdd(chain, type, index - priorIndex); priorIndex = index; if (type == SBBidiTypeB) { index = length; goto AddLast; } break; default: if (type != priorType) { BidiChainAdd(chain, type, index - priorIndex); priorIndex = index; } break; } } AddLast: BidiChainAdd(chain, SBBidiTypeNil, index - priorIndex); } static BidiLink SkipIsolatingRun(BidiChainRef chain, BidiLink skipLink, BidiLink breakLink) { BidiLink link = skipLink; SBUInteger depth = 1; while ((link = BidiChainGetNext(chain, link)) != breakLink) { SBBidiType type = BidiChainGetType(chain, link); switch (type) { case SBBidiTypeLRI: case SBBidiTypeRLI: case SBBidiTypeFSI: depth += 1; break; case SBBidiTypePDI: if (--depth == 0) { return link; } break; } } return BidiLinkNone; } static SBLevel DetermineBaseLevel(BidiChainRef chain, BidiLink skipLink, BidiLink breakLink, SBLevel defaultLevel, SBBoolean isIsolate) { BidiLink link = skipLink; /* Rules P2, P3 */ while ((link = BidiChainGetNext(chain, link)) != breakLink) { SBBidiType type = BidiChainGetType(chain, link); switch (type) { case SBBidiTypeL: return 0; case SBBidiTypeAL: case SBBidiTypeR: return 1; case SBBidiTypeLRI: case SBBidiTypeRLI: case SBBidiTypeFSI: link = SkipIsolatingRun(chain, link, breakLink); if (link == BidiLinkNone) { goto Default; } break; case SBBidiTypePDI: if (isIsolate) { /* * In case of isolating run, the PDI will be the last code point. * NOTE: * The inner isolating runs will be skipped by the case above this one. */ goto Default; } break; } } Default: return defaultLevel; } static SBLevel DetermineParagraphLevel(BidiChainRef chain, SBLevel baseLevel) { if (baseLevel >= SBLevelMax) { return DetermineBaseLevel(chain, chain->roller, chain->roller, (baseLevel != SBLevelDefaultRTL ? 0 : 1), SBFalse); } return baseLevel; } static SBBoolean DetermineLevels(ParagraphContextRef context, SBLevel baseLevel) { BidiChainRef chain = &context->bidiChain; StatusStackRef stack = &context->statusStack; BidiLink roller = chain->roller; BidiLink link; BidiLink priorLink; BidiLink firstLink; BidiLink lastLink; SBLevel priorLevel; SBBidiType sor; SBBidiType eor; SBUInteger overIsolate; SBUInteger overEmbedding; SBUInteger validIsolate; priorLink = chain->roller; firstLink = BidiLinkNone; lastLink = BidiLinkNone; priorLevel = baseLevel; sor = SBBidiTypeNil; /* Rule X1 */ overIsolate = 0; overEmbedding = 0; validIsolate = 0; StatusStackPush(stack, baseLevel, SBBidiTypeON, SBFalse); BidiChainForEach(chain, roller, link) { SBBoolean forceFinish = SBFalse; SBBoolean bnEquivalent = SBFalse; SBBidiType type; type = BidiChainGetType(chain, link); #define LeastGreaterOddLevel() \ ( \ (StatusStackGetEmbeddingLevel(stack) + 1) | 1 \ ) #define LeastGreaterEvenLevel() \ ( \ (StatusStackGetEmbeddingLevel(stack) + 2) & ~1 \ ) #define MergeLinkIfNeeded() \ { \ if (BidiChainMergeNext(chain, priorLink)) { \ continue; \ } \ } #define PushEmbedding(l, o) \ { \ SBLevel newLevel = l; \ \ bnEquivalent = SBTrue; \ \ if (newLevel <= SBLevelMax && !overIsolate && !overEmbedding) { \ if (!StatusStackPush(stack, newLevel, o, SBFalse)) { \ return SBFalse; \ } \ } else { \ if (!overIsolate) { \ overEmbedding += 1; \ } \ } \ } #define PushIsolate(l, o) \ { \ SBBidiType priorStatus = StatusStackGetOverrideStatus(stack); \ SBLevel newLevel = l; \ \ BidiChainSetLevel(chain, link, \ StatusStackGetEmbeddingLevel(stack)); \ \ if (newLevel <= SBLevelMax && !overIsolate && !overEmbedding) { \ validIsolate += 1; \ \ if (!StatusStackPush(stack, newLevel, o, SBTrue)) { \ return SBFalse; \ } \ } else { \ overIsolate += 1; \ } \ \ if (priorStatus != SBBidiTypeON) { \ BidiChainSetType(chain, link, priorStatus); \ MergeLinkIfNeeded(); \ } \ } switch (type) { /* Rule X2 */ case SBBidiTypeRLE: PushEmbedding(LeastGreaterOddLevel(), SBBidiTypeON); break; /* Rule X3 */ case SBBidiTypeLRE: PushEmbedding(LeastGreaterEvenLevel(), SBBidiTypeON); break; /* Rule X4 */ case SBBidiTypeRLO: PushEmbedding(LeastGreaterOddLevel(), SBBidiTypeR); break; /* Rule X5 */ case SBBidiTypeLRO: PushEmbedding(LeastGreaterEvenLevel(), SBBidiTypeL); break; /* Rule X5a */ case SBBidiTypeRLI: PushIsolate(LeastGreaterOddLevel(), SBBidiTypeON); break; /* Rule X5b */ case SBBidiTypeLRI: PushIsolate(LeastGreaterEvenLevel(), SBBidiTypeON); break; /* Rule X5c */ case SBBidiTypeFSI: { SBBoolean isRTL = (DetermineBaseLevel(chain, link, roller, 0, SBTrue) == 1); PushIsolate(isRTL ? LeastGreaterOddLevel() : LeastGreaterEvenLevel(), SBBidiTypeON); break; } /* Rule X6 */ default: BidiChainSetLevel(chain, link, StatusStackGetEmbeddingLevel(stack)); if (StatusStackGetOverrideStatus(stack) != SBBidiTypeON) { BidiChainSetType(chain, link, StatusStackGetOverrideStatus(stack)); MergeLinkIfNeeded(); } break; /* Rule X6a */ case SBBidiTypePDI: { SBBidiType overrideStatus; if (overIsolate != 0) { overIsolate -= 1; } else if (validIsolate == 0) { /* Do nothing */ } else { overEmbedding = 0; while (!StatusStackGetIsolateStatus(stack)) { StatusStackPop(stack); } StatusStackPop(stack); validIsolate -= 1; } BidiChainSetLevel(chain, link, StatusStackGetEmbeddingLevel(stack)); overrideStatus = StatusStackGetOverrideStatus(stack); if (overrideStatus != SBBidiTypeON) { BidiChainSetType(chain, link, overrideStatus); MergeLinkIfNeeded(); } break; } /* Rule X7 */ case SBBidiTypePDF: bnEquivalent = SBTrue; if (overIsolate != 0) { /* Do nothing */ } else if (overEmbedding != 0) { overEmbedding -= 1; } else if (!StatusStackGetIsolateStatus(stack) && stack->count >= 2) { StatusStackPop(stack); } break; /* Rule X8 */ case SBBidiTypeB: /* * These values are reset for clarity, in this implementation B can only occur as the * last code in the array. */ StatusStackSetEmpty(stack); StatusStackPush(stack, baseLevel, SBBidiTypeON, SBFalse); overIsolate = 0; overEmbedding = 0; validIsolate = 0; BidiChainSetLevel(chain, link, baseLevel); break; case SBBidiTypeBN: bnEquivalent = SBTrue; break; case SBBidiTypeNil: forceFinish = SBTrue; BidiChainSetLevel(chain, link, baseLevel); break; } /* Rule X9 */ if (bnEquivalent) { /* The type of this link is BN equivalent, so abandon it and continue the loop. */ BidiChainAbandonNext(chain, priorLink); continue; } if (sor == SBBidiTypeNil) { sor = SBLevelAsNormalBidiType(SBNumberGetMax(baseLevel, BidiChainGetLevel(chain, link))); firstLink = link; priorLevel = BidiChainGetLevel(chain, link); } else if (priorLevel != BidiChainGetLevel(chain, link) || forceFinish) { LevelRun levelRun; SBLevel currentLevel; /* Since the level has changed at this link, therefore the run must end at prior link. */ lastLink = priorLink; /* Save the current level i.e. level of the next run. */ currentLevel = BidiChainGetLevel(chain, link); /* * Now we have both the prior level and the current level i.e. unchanged levels of both * the current run and the next run. So, identify eor of the current run. * NOTE: * sor of the run has been already determined at this stage. */ eor = SBLevelAsNormalBidiType(SBNumberGetMax(priorLevel, currentLevel)); LevelRunInitialize(&levelRun, chain, firstLink, lastLink, sor, eor); if (!ProcessRun(context, &levelRun, forceFinish)) { return SBFalse; } /* The sor of next run (if any) should be technically equal to eor of this run. */ sor = eor; /* The next run (if any) will start from this index. */ firstLink = link; priorLevel = currentLevel; } priorLink = link; } return SBTrue; } static SBBoolean ProcessRun(ParagraphContextRef context, const LevelRunRef levelRun, SBBoolean forceFinish) { RunQueueRef queue = &context->runQueue; if (!RunQueueEnqueue(queue, levelRun)) { return SBFalse; } if (queue->shouldDequeue || forceFinish) { IsolatingRunRef isolatingRun = &context->isolatingRun; /* Rule X10 */ for (; queue->count != 0; RunQueueDequeue(queue)) { LevelRunRef front = RunQueueGetFront(queue); if (RunKindIsAttachedTerminating(front->kind)) { continue; } isolatingRun->baseLevelRun = front; if (!IsolatingRunResolve(isolatingRun)) { return SBFalse; } } } return SBTrue; } static void SaveLevels(BidiChainRef chain, SBLevel *levels, SBLevel baseLevel) { BidiLink roller = chain->roller; BidiLink link; SBUInteger index = 0; SBLevel level = baseLevel; BidiChainForEach(chain, roller, link) { SBUInteger offset = BidiChainGetOffset(chain, link); for (; index < offset; index++) { levels[index] = level; } level = BidiChainGetLevel(chain, link); } } static SBBoolean ResolveParagraph(SBParagraphRef paragraph, SBAlgorithmRef algorithm, SBUInteger offset, SBUInteger length, SBLevel baseLevel) { const SBBidiType *bidiTypes = algorithm->fixedTypes + offset; SBBoolean isSucceeded = SBFalse; ParagraphContext context; if (InitializeParagraphContext(&context, bidiTypes, paragraph->fixedLevels, length)) { SBLevel resolvedLevel = DetermineParagraphLevel(&context.bidiChain, baseLevel); SB_LOG_BLOCK_OPENER("Determined Paragraph Level"); SB_LOG_STATEMENT("Base Level", 1, SB_LOG_LEVEL(resolvedLevel)); SB_LOG_BLOCK_CLOSER(); context.isolatingRun.codepointSequence = &algorithm->codepointSequence; context.isolatingRun.bidiTypes = bidiTypes; context.isolatingRun.bidiChain = &context.bidiChain; context.isolatingRun.paragraphOffset = offset; context.isolatingRun.paragraphLevel = resolvedLevel; if (DetermineLevels(&context, resolvedLevel)) { SaveLevels(&context.bidiChain, ++paragraph->fixedLevels, resolvedLevel); SB_LOG_BLOCK_OPENER("Determined Embedding Levels"); SB_LOG_STATEMENT("Levels", 1, SB_LOG_LEVELS_ARRAY(paragraph->fixedLevels, length)); SB_LOG_BLOCK_CLOSER(); paragraph->algorithm = SBAlgorithmRetain(algorithm); paragraph->refTypes = bidiTypes; paragraph->offset = offset; paragraph->length = length; paragraph->baseLevel = resolvedLevel; paragraph->retainCount = 1; isSucceeded = SBTrue; } FinalizeParagraphContext(&context); } return isSucceeded; } SB_INTERNAL SBParagraphRef SBParagraphCreate(SBAlgorithmRef algorithm, SBUInteger paragraphOffset, SBUInteger suggestedLength, SBLevel baseLevel) { const SBCodepointSequence *codepointSequence = &algorithm->codepointSequence; SBUInteger stringLength = codepointSequence->stringLength; SBUInteger actualLength; SBParagraphRef paragraph; /* The given range MUST be valid. */ SBAssert(SBUIntegerVerifyRange(stringLength, paragraphOffset, suggestedLength) && suggestedLength > 0); SB_LOG_BLOCK_OPENER("Paragraph Input"); SB_LOG_STATEMENT("Paragraph Offset", 1, SB_LOG_NUMBER(paragraphOffset)); SB_LOG_STATEMENT("Suggested Length", 1, SB_LOG_NUMBER(suggestedLength)); SB_LOG_STATEMENT("Base Direction", 1, SB_LOG_BASE_LEVEL(baseLevel)); SB_LOG_BLOCK_CLOSER(); actualLength = DetermineBoundary(algorithm, paragraphOffset, suggestedLength); SB_LOG_BLOCK_OPENER("Determined Paragraph Boundary"); SB_LOG_STATEMENT("Actual Length", 1, SB_LOG_NUMBER(actualLength)); SB_LOG_BLOCK_CLOSER(); paragraph = AllocateParagraph(actualLength); if (paragraph) { if (ResolveParagraph(paragraph, algorithm, paragraphOffset, actualLength, baseLevel)) { return paragraph; } DisposeParagraph(paragraph); } SB_LOG_BREAKER(); return NULL; } SBUInteger SBParagraphGetOffset(SBParagraphRef paragraph) { return paragraph->offset; } SBUInteger SBParagraphGetLength(SBParagraphRef paragraph) { return paragraph->length; } SBLevel SBParagraphGetBaseLevel(SBParagraphRef paragraph) { return paragraph->baseLevel; } const SBLevel *SBParagraphGetLevelsPtr(SBParagraphRef paragraph) { return paragraph->fixedLevels; } SBLineRef SBParagraphCreateLine(SBParagraphRef paragraph, SBUInteger lineOffset, SBUInteger lineLength) { SBUInteger paragraphOffset = paragraph->offset; SBUInteger paragraphLength = paragraph->length; SBUInteger paragraphLimit = paragraphOffset + paragraphLength; SBUInteger lineLimit = lineOffset + lineLength; if (lineOffset < lineLimit && lineOffset >= paragraphOffset && lineLimit <= paragraphLimit) { return SBLineCreate(paragraph, lineOffset, lineLength); } return NULL; } SBParagraphRef SBParagraphRetain(SBParagraphRef paragraph) { if (paragraph) { paragraph->retainCount += 1; } return paragraph; } void SBParagraphRelease(SBParagraphRef paragraph) { if (paragraph && --paragraph->retainCount == 0) { SBAlgorithmRelease(paragraph->algorithm); DisposeParagraph(paragraph); } }