1 /*
2  * Copyright (C) 2018-2019 Muhammad Tayyab Akram
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <stddef.h>
18 #include <stdlib.h>
19 
20 #include "GeneralCategoryLookup.h"
21 #include "PairingLookup.h"
22 #include "SBBase.h"
23 #include "SBCodepointSequence.h"
24 #include "ScriptLookup.h"
25 #include "ScriptStack.h"
26 #include "SBScriptLocator.h"
27 
IsSimilarScript(SBScript lhs,SBScript rhs)28 static SBBoolean IsSimilarScript(SBScript lhs, SBScript rhs)
29 {
30     return SBScriptIsCommonOrInherited(lhs)
31         || SBScriptIsCommonOrInherited(rhs)
32         || lhs == rhs;
33 }
34 
SBScriptLocatorCreate(void)35 SBScriptLocatorRef SBScriptLocatorCreate(void)
36 {
37     SBScriptLocatorRef locator;
38 
39     locator = malloc(sizeof(SBScriptLocator));
40     locator->_codepointSequence.stringEncoding = SBStringEncodingUTF8;
41     locator->_codepointSequence.stringBuffer = NULL;
42     locator->_codepointSequence.stringLength = 0;
43     locator->retainCount = 1;
44     SBScriptLocatorReset(locator);
45 
46     return locator;
47 }
48 
SBScriptLocatorLoadCodepoints(SBScriptLocatorRef locator,const SBCodepointSequence * codepointSequence)49 void SBScriptLocatorLoadCodepoints(SBScriptLocatorRef locator, const SBCodepointSequence *codepointSequence)
50 {
51     locator->_codepointSequence = *codepointSequence;
52     SBScriptLocatorReset(locator);
53 }
54 
SBScriptLocatorGetAgent(SBScriptLocatorRef locator)55 const SBScriptAgent *SBScriptLocatorGetAgent(SBScriptLocatorRef locator)
56 {
57     return &locator->agent;
58 }
59 
ResolveScriptRun(SBScriptLocatorRef locator,SBUInteger offset)60 static void ResolveScriptRun(SBScriptLocatorRef locator, SBUInteger offset)
61 {
62     const SBCodepointSequence *sequence = &locator->_codepointSequence;
63     ScriptStackRef stack = &locator->_scriptStack;
64     SBScript result = SBScriptZYYY;
65     SBUInteger current = offset;
66     SBUInteger next = offset;
67     SBCodepoint codepoint;
68 
69     /* Iterate over the code points of specified string buffer. */
70     while ((codepoint = SBCodepointSequenceGetCodepointAt(sequence, &next)) != SBCodepointInvalid) {
71         SBBoolean isStacked = SBFalse;
72         SBScript script;
73 
74         script = LookupScript(codepoint);
75 
76         /* Handle paired punctuations in case of a common script. */
77         if (script == SBScriptZYYY) {
78             SBGeneralCategory generalCategory = LookupGeneralCategory(codepoint);
79 
80             /* Check if current code point is an open punctuation. */
81             if (generalCategory == SBGeneralCategoryPS) {
82                 SBCodepoint mirror = LookupMirror(codepoint);
83                 if (mirror) {
84                     /* A closing pair exists for this punctuation, so push it onto the stack. */
85                     ScriptStackPush(stack, result, mirror);
86                 }
87             }
88             /* Check if current code point is a close punctuation. */
89             else if (generalCategory == SBGeneralCategoryPE) {
90                 SBBoolean isMirrored = (LookupMirror(codepoint) != 0);
91                 if (isMirrored) {
92                     /* Find the matching entry in the stack, while popping the unmatched ones. */
93                     while (!ScriptStackIsEmpty(stack)) {
94                         SBCodepoint mirror = ScriptStackGetMirror(stack);
95                         if (mirror != codepoint) {
96                             ScriptStackPop(stack);
97                         } else {
98                             break;
99                         }
100                     }
101 
102                     if (!ScriptStackIsEmpty(stack)) {
103                         isStacked = SBTrue;
104                         /* Paired punctuation match the script of enclosing text. */
105                         script = ScriptStackGetScript(stack);
106                     }
107                 }
108             }
109         }
110 
111         if (IsSimilarScript(result, script)) {
112             if (SBScriptIsCommonOrInherited(result) && !SBScriptIsCommonOrInherited(script)) {
113                 /* Set the concrete script of this code point as the result. */
114                 result = script;
115                 /* Seal the pending punctuations with the result. */
116                 ScriptStackSealPairs(stack, result);
117             }
118 
119             if (isStacked) {
120                 /* Pop the paired punctuation from the stack. */
121                 ScriptStackPop(stack);
122             }
123         } else {
124             /* The current code point has a different script, so finish the run. */
125             break;
126         }
127 
128         current = next;
129     }
130 
131     ScriptStackLeavePairs(stack);
132 
133     /* Set the run info in agent. */
134     locator->agent.offset = offset;
135     locator->agent.length = current - offset;
136     locator->agent.script = result;
137 }
138 
SBScriptLocatorMoveNext(SBScriptLocatorRef locator)139 SBBoolean SBScriptLocatorMoveNext(SBScriptLocatorRef locator)
140 {
141     SBUInteger offset = locator->agent.offset + locator->agent.length;
142 
143     if (offset < locator->_codepointSequence.stringLength) {
144         ResolveScriptRun(locator, offset);
145         return SBTrue;
146     }
147 
148     SBScriptLocatorReset(locator);
149     return SBFalse;
150 }
151 
SBScriptLocatorReset(SBScriptLocatorRef locator)152 void SBScriptLocatorReset(SBScriptLocatorRef locator)
153 {
154     ScriptStackReset(&locator->_scriptStack);
155     locator->agent.offset = 0;
156     locator->agent.length = 0;
157     locator->agent.script = SBScriptNil;
158 }
159 
SBScriptLocatorRetain(SBScriptLocatorRef locator)160 SBScriptLocatorRef SBScriptLocatorRetain(SBScriptLocatorRef locator)
161 {
162     if (locator) {
163         locator->retainCount += 1;
164     }
165 
166     return locator;
167 }
168 
SBScriptLocatorRelease(SBScriptLocatorRef locator)169 void SBScriptLocatorRelease(SBScriptLocatorRef locator)
170 {
171     if (locator && --locator->retainCount == 0) {
172         free(locator);
173     }
174 }
175