mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-09 23:34:20 +00:00
712 lines
22 KiB
C
712 lines
22 KiB
C
/*
|
|
* 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 <stddef.h>
|
|
|
|
#include <juce_graphics/unicode/sheenbidi/Headers/SheenBidi/SBConfig.h>
|
|
|
|
#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);
|
|
}
|
|
}
|