sl@0
|
1 |
// Copyright (c) 2000-2009 Nokia Corporation and/or its subsidiary(-ies).
|
sl@0
|
2 |
// All rights reserved.
|
sl@0
|
3 |
// This component and the accompanying materials are made available
|
sl@0
|
4 |
// under the terms of "Eclipse Public License v1.0"
|
sl@0
|
5 |
// which accompanies this distribution, and is available
|
sl@0
|
6 |
// at the URL "http://www.eclipse.org/legal/epl-v10.html".
|
sl@0
|
7 |
//
|
sl@0
|
8 |
// Initial Contributors:
|
sl@0
|
9 |
// Nokia Corporation - initial contribution.
|
sl@0
|
10 |
//
|
sl@0
|
11 |
// Contributors:
|
sl@0
|
12 |
//
|
sl@0
|
13 |
// Description:
|
sl@0
|
14 |
// Bidirectional text reordering; based on the Unicode Bidirectional Reordering Algorithm.
|
sl@0
|
15 |
//
|
sl@0
|
16 |
//
|
sl@0
|
17 |
|
sl@0
|
18 |
#include <bidi.h>
|
sl@0
|
19 |
#include "BidiCopy.h"
|
sl@0
|
20 |
#include <s32std.h>
|
sl@0
|
21 |
|
sl@0
|
22 |
const TInt KBidirectionalStateOverrideStreamValueNone = 0;
|
sl@0
|
23 |
const TInt KBidirectionalStateOverrideStreamValueLeftToRight = 1;
|
sl@0
|
24 |
const TInt KBidirectionalStateOverrideStreamValueRightToLeft = 2;
|
sl@0
|
25 |
|
sl@0
|
26 |
inline TBool IsSupplementary(TUint aChar)
|
sl@0
|
27 |
/**
|
sl@0
|
28 |
@param aChar The 32-bit code point value of a Unicode character.
|
sl@0
|
29 |
|
sl@0
|
30 |
@return True, if aChar is supplementary character; false, otherwise.
|
sl@0
|
31 |
*/
|
sl@0
|
32 |
{
|
sl@0
|
33 |
return (aChar > 0xFFFF);
|
sl@0
|
34 |
}
|
sl@0
|
35 |
|
sl@0
|
36 |
inline TBool IsHighSurrogate(TText16 aInt16)
|
sl@0
|
37 |
/**
|
sl@0
|
38 |
@return True, if aText16 is high surrogate; false, otherwise.
|
sl@0
|
39 |
*/
|
sl@0
|
40 |
{
|
sl@0
|
41 |
return (aInt16 & 0xFC00) == 0xD800;
|
sl@0
|
42 |
}
|
sl@0
|
43 |
|
sl@0
|
44 |
inline TBool IsLowSurrogate(TText16 aInt16)
|
sl@0
|
45 |
/**
|
sl@0
|
46 |
@return True, if aText16 is low surrogate; false, otherwise.
|
sl@0
|
47 |
*/
|
sl@0
|
48 |
{
|
sl@0
|
49 |
return (aInt16 & 0xFC00) == 0xDC00;
|
sl@0
|
50 |
}
|
sl@0
|
51 |
|
sl@0
|
52 |
inline TUint JoinSurrogate(TText16 aHighSurrogate, TText16 aLowSurrogate)
|
sl@0
|
53 |
/**
|
sl@0
|
54 |
Combine a high surrogate and a low surrogate into a supplementary character.
|
sl@0
|
55 |
|
sl@0
|
56 |
@return The 32-bit code point value of the generated Unicode supplementary
|
sl@0
|
57 |
character.
|
sl@0
|
58 |
*/
|
sl@0
|
59 |
{
|
sl@0
|
60 |
return ((aHighSurrogate - 0xD7F7) << 10) + aLowSurrogate;
|
sl@0
|
61 |
}
|
sl@0
|
62 |
|
sl@0
|
63 |
TBool TextDefaultsToRightToLeft(const TDesC& aText, TBool* aFound);
|
sl@0
|
64 |
|
sl@0
|
65 |
TBidirectionalState::TCategory TBidirectionalState::CharToBdCat(TChar::TBdCategory aCat)
|
sl@0
|
66 |
{
|
sl@0
|
67 |
return static_cast<TBidirectionalState::TCategory>(
|
sl@0
|
68 |
1 << static_cast<TInt>(aCat));
|
sl@0
|
69 |
}
|
sl@0
|
70 |
|
sl@0
|
71 |
TBidirectionalState::TCategory TBidirectionalState::UintToBdCat(TUint aCat)
|
sl@0
|
72 |
{
|
sl@0
|
73 |
return static_cast<TBidirectionalState::TCategory>(1 << aCat);
|
sl@0
|
74 |
}
|
sl@0
|
75 |
|
sl@0
|
76 |
void TBidirectionalState::TReorderContext::SetNextCategory(
|
sl@0
|
77 |
TChar::TBdCategory aCat)
|
sl@0
|
78 |
{
|
sl@0
|
79 |
iNextCategory = CharToBdCat(aCat);
|
sl@0
|
80 |
}
|
sl@0
|
81 |
|
sl@0
|
82 |
void TBidirectionalState::TReorderContext::SetNextStrongCategory(
|
sl@0
|
83 |
TChar::TBdCategory aCat)
|
sl@0
|
84 |
{
|
sl@0
|
85 |
iNextStrongCategory = CharToBdCat(aCat);
|
sl@0
|
86 |
}
|
sl@0
|
87 |
|
sl@0
|
88 |
|
sl@0
|
89 |
EXPORT_C void TBidirectionalState::ReverseGroups(TText* aStart,TInt aLength)
|
sl@0
|
90 |
/** A utility to reverse text apart from combining characters, which remains after
|
sl@0
|
91 |
their base characters. This is what is needed when drawing right-to-left text.
|
sl@0
|
92 |
|
sl@0
|
93 |
@param aStart Start position of text to be reversed.
|
sl@0
|
94 |
@param aLength Length of text to be reversed. */
|
sl@0
|
95 |
{
|
sl@0
|
96 |
BidiCopy::ReverseCodes(aStart, aLength);
|
sl@0
|
97 |
BidiCopy::DeleteUnreversedSurrogates(aStart, aLength);
|
sl@0
|
98 |
BidiCopy::SubstituteMirrorImages(aStart, aLength);
|
sl@0
|
99 |
BidiCopy::CorrectGroups(aStart, aLength);
|
sl@0
|
100 |
BidiCopy::CorrectSurrogatePairs(aStart, aLength);
|
sl@0
|
101 |
}
|
sl@0
|
102 |
|
sl@0
|
103 |
|
sl@0
|
104 |
// A local helper function. Get the next character from a buffer. This
|
sl@0
|
105 |
// function won't check buffer length.
|
sl@0
|
106 |
//
|
sl@0
|
107 |
// @param aText The text buffer to read character from.
|
sl@0
|
108 |
// @param aCharacterIndex Count of characters to skip in aText.
|
sl@0
|
109 |
// @return The character.
|
sl@0
|
110 |
TUint GetOneCharacter(const TText16 *aText, TInt aCharacterIndex)
|
sl@0
|
111 |
{
|
sl@0
|
112 |
const TText16 *p = aText;
|
sl@0
|
113 |
TUint c = 0xFFFF;
|
sl@0
|
114 |
while (aCharacterIndex >= 0)
|
sl@0
|
115 |
{
|
sl@0
|
116 |
c = *p++;
|
sl@0
|
117 |
ASSERT(!IsLowSurrogate(c));
|
sl@0
|
118 |
if (IsHighSurrogate(c))
|
sl@0
|
119 |
{
|
sl@0
|
120 |
ASSERT(IsLowSurrogate(*p));
|
sl@0
|
121 |
c = JoinSurrogate(c, *p++);
|
sl@0
|
122 |
}
|
sl@0
|
123 |
--aCharacterIndex;
|
sl@0
|
124 |
}
|
sl@0
|
125 |
return c;
|
sl@0
|
126 |
}
|
sl@0
|
127 |
|
sl@0
|
128 |
|
sl@0
|
129 |
TInt TBidirectionalState::GenerateBdRunArray(const TText* aText, TInt aLength,
|
sl@0
|
130 |
TBidirectionalState::TRunInfo* aRun, TInt aMaxRuns)
|
sl@0
|
131 |
/** Analyse the input text for runs of characters that share the same
|
sl@0
|
132 |
bidirectional class. Categories TChar::EEuropeanNumberSeparator and
|
sl@0
|
133 |
TChar::ECommonNumberSeparator are kept as singletons due to a limitation in
|
sl@0
|
134 |
the reordering logic.
|
sl@0
|
135 |
@param aText The text to be analysed.
|
sl@0
|
136 |
@param aLength The length of the text to be analysed.
|
sl@0
|
137 |
@param aRun Output buffer for the runs after analysis. May be null if there
|
sl@0
|
138 |
is to be no output.
|
sl@0
|
139 |
@param aMaxRuns The size of the aRun array. No more than this number of runs
|
sl@0
|
140 |
will be output.
|
sl@0
|
141 |
@return The number of runs that are required for the full results of the
|
sl@0
|
142 |
analysis.
|
sl@0
|
143 |
@internalTechnology */
|
sl@0
|
144 |
{
|
sl@0
|
145 |
if (aLength == 0)
|
sl@0
|
146 |
{
|
sl@0
|
147 |
if (aRun && 0 < aMaxRuns)
|
sl@0
|
148 |
{
|
sl@0
|
149 |
aRun[0].iCategory = TChar::EOtherNeutral;
|
sl@0
|
150 |
aRun[0].iStart = 0;
|
sl@0
|
151 |
aRun[0].iLength = 0;
|
sl@0
|
152 |
}
|
sl@0
|
153 |
return 1;
|
sl@0
|
154 |
}
|
sl@0
|
155 |
int runs = 0;
|
sl@0
|
156 |
int run_start = 0;
|
sl@0
|
157 |
int run_end = 1;
|
sl@0
|
158 |
const TText* p = aText;
|
sl@0
|
159 |
const TText* q = p + aLength;
|
sl@0
|
160 |
|
sl@0
|
161 |
// get the character pointed by 'p', then move 'p' to next character, and adjust 'run_end' if need
|
sl@0
|
162 |
TChar pc = ::GetOneCharacter(p, 0);
|
sl@0
|
163 |
TChar::TBdCategory cur_cat = pc.GetBdCategory();
|
sl@0
|
164 |
++p;
|
sl@0
|
165 |
if (IsSupplementary(pc))
|
sl@0
|
166 |
{
|
sl@0
|
167 |
++p;
|
sl@0
|
168 |
run_end = 2; // run_end points to "end" of current character
|
sl@0
|
169 |
}
|
sl@0
|
170 |
|
sl@0
|
171 |
while (p < q)
|
sl@0
|
172 |
{
|
sl@0
|
173 |
// get the character pointed by 'p'
|
sl@0
|
174 |
pc = ::GetOneCharacter(p, 0);
|
sl@0
|
175 |
|
sl@0
|
176 |
TChar::TBdCategory new_cat = pc.GetBdCategory();
|
sl@0
|
177 |
if (new_cat != cur_cat)
|
sl@0
|
178 |
{
|
sl@0
|
179 |
if (new_cat == TChar::ENonSpacingMark &&
|
sl@0
|
180 |
cur_cat != TChar::ELeftToRightEmbedding &&
|
sl@0
|
181 |
cur_cat != TChar::ELeftToRightOverride &&
|
sl@0
|
182 |
cur_cat != TChar::ERightToLeftEmbedding &&
|
sl@0
|
183 |
cur_cat != TChar::ERightToLeftOverride &&
|
sl@0
|
184 |
cur_cat != TChar::EPopDirectionalFormat)
|
sl@0
|
185 |
new_cat = cur_cat;
|
sl@0
|
186 |
else if (p < q - 1 &&
|
sl@0
|
187 |
(new_cat == TChar::EWhitespace ||
|
sl@0
|
188 |
new_cat == TChar::EEuropeanNumberSeparator ||
|
sl@0
|
189 |
new_cat == TChar::ECommonNumberSeparator))
|
sl@0
|
190 |
{
|
sl@0
|
191 |
TChar nextChar = ::GetOneCharacter(p, 1);
|
sl@0
|
192 |
TChar::TBdCategory next_cat = nextChar.GetBdCategory();
|
sl@0
|
193 |
if (new_cat == TChar::EWhitespace)
|
sl@0
|
194 |
{
|
sl@0
|
195 |
if ((cur_cat == TChar::ELeftToRight ||
|
sl@0
|
196 |
cur_cat == TChar::ERightToLeft ||
|
sl@0
|
197 |
cur_cat == TChar::ERightToLeftArabic) && cur_cat == next_cat)
|
sl@0
|
198 |
new_cat = cur_cat;
|
sl@0
|
199 |
}
|
sl@0
|
200 |
else if (cur_cat == TChar::EEuropeanNumber && next_cat == TChar::EEuropeanNumber)
|
sl@0
|
201 |
new_cat = TChar::EEuropeanNumber;
|
sl@0
|
202 |
}
|
sl@0
|
203 |
}
|
sl@0
|
204 |
|
sl@0
|
205 |
if (new_cat != cur_cat ||
|
sl@0
|
206 |
cur_cat == TChar::EEuropeanNumberSeparator ||
|
sl@0
|
207 |
cur_cat == TChar::ECommonNumberSeparator)
|
sl@0
|
208 |
{
|
sl@0
|
209 |
if (aRun && runs < aMaxRuns)
|
sl@0
|
210 |
{
|
sl@0
|
211 |
aRun[runs].iCategory = cur_cat;
|
sl@0
|
212 |
aRun[runs].iStart = run_start;
|
sl@0
|
213 |
aRun[runs].iLength = run_end - run_start;
|
sl@0
|
214 |
}
|
sl@0
|
215 |
|
sl@0
|
216 |
runs++;
|
sl@0
|
217 |
run_start = run_end;
|
sl@0
|
218 |
}
|
sl@0
|
219 |
|
sl@0
|
220 |
p++;
|
sl@0
|
221 |
run_end++;
|
sl@0
|
222 |
|
sl@0
|
223 |
// adjust 'p' and 'run_end'
|
sl@0
|
224 |
if (IsSupplementary(pc))
|
sl@0
|
225 |
{
|
sl@0
|
226 |
p++;
|
sl@0
|
227 |
run_end++;
|
sl@0
|
228 |
}
|
sl@0
|
229 |
|
sl@0
|
230 |
cur_cat = new_cat;
|
sl@0
|
231 |
}
|
sl@0
|
232 |
|
sl@0
|
233 |
if (aRun && runs < aMaxRuns)
|
sl@0
|
234 |
{
|
sl@0
|
235 |
aRun[runs].iCategory = cur_cat;
|
sl@0
|
236 |
aRun[runs].iStart = run_start;
|
sl@0
|
237 |
aRun[runs].iLength = run_end - run_start;
|
sl@0
|
238 |
}
|
sl@0
|
239 |
|
sl@0
|
240 |
return runs + 1;
|
sl@0
|
241 |
}
|
sl@0
|
242 |
|
sl@0
|
243 |
|
sl@0
|
244 |
EXPORT_C TInt TBidirectionalState::ReorderText(const TText* aText,TInt aLength,TBool aParRightToLeft,
|
sl@0
|
245 |
TText*& aNewText)
|
sl@0
|
246 |
/** Reorders text according to the Unicode Bidirectional Reordering algorithm.
|
sl@0
|
247 |
|
sl@0
|
248 |
Reorders the input text from logical order (which may be bidirectional) to
|
sl@0
|
249 |
display order (strictly left to right).
|
sl@0
|
250 |
|
sl@0
|
251 |
@param aText The input text in logical order.
|
sl@0
|
252 |
@param aLength The length of the input text.
|
sl@0
|
253 |
@param aParRightToLeft ETrue if the default directionality of the text to be
|
sl@0
|
254 |
re-ordered is right-to-left.
|
sl@0
|
255 |
@param aNewText Returns the re-ordered text. If the text did not need re-ordering,
|
sl@0
|
256 |
or if there was an error, aText will be returned. Otherwise, ownership of
|
sl@0
|
257 |
a newly allocated buffer will be returned to the caller. This buffer must
|
sl@0
|
258 |
be deleted with delete[] (or CleanupArrayDeletePushL()) and not delete (or
|
sl@0
|
259 |
CleanupStack::PushL()).
|
sl@0
|
260 |
@return A system-wide error value if there has been an error; KErrNone if there
|
sl@0
|
261 |
has not. */
|
sl@0
|
262 |
{
|
sl@0
|
263 |
aNewText = const_cast<TText*>(aText);
|
sl@0
|
264 |
if (aLength < 2)
|
sl@0
|
265 |
return KErrNone;
|
sl@0
|
266 |
|
sl@0
|
267 |
int error = KErrNone;
|
sl@0
|
268 |
TBidirectionalState::TRunInfo run_info;
|
sl@0
|
269 |
run_info.iDirection = 0;
|
sl@0
|
270 |
run_info.iIndex = 0;
|
sl@0
|
271 |
run_info.iStart = 0;
|
sl@0
|
272 |
run_info.iLength = aLength;
|
sl@0
|
273 |
TBidirectionalState::TRunInfo* run_info_array = &run_info;
|
sl@0
|
274 |
TBidirectionalState::TRunInfo* allocated_run_info_array = 0;
|
sl@0
|
275 |
int runs = GenerateBdRunArray(aText, aLength, run_info_array, 1);
|
sl@0
|
276 |
if (runs > 1)
|
sl@0
|
277 |
{
|
sl@0
|
278 |
allocated_run_info_array = new TBidirectionalState::TRunInfo[runs];
|
sl@0
|
279 |
if (allocated_run_info_array)
|
sl@0
|
280 |
{
|
sl@0
|
281 |
run_info_array = allocated_run_info_array;
|
sl@0
|
282 |
GenerateBdRunArray(aText, aLength, run_info_array, runs);
|
sl@0
|
283 |
}
|
sl@0
|
284 |
else
|
sl@0
|
285 |
{
|
sl@0
|
286 |
// the run cannot be allocated: stick with our single l-to-r run
|
sl@0
|
287 |
error = KErrNoMemory;
|
sl@0
|
288 |
runs = 1;
|
sl@0
|
289 |
}
|
sl@0
|
290 |
}
|
sl@0
|
291 |
if (error == KErrNone)
|
sl@0
|
292 |
{
|
sl@0
|
293 |
TBidirectionalState state;
|
sl@0
|
294 |
state.ReorderLine(run_info_array, runs, ETrue, ETrue, aParRightToLeft,
|
sl@0
|
295 |
TChar::EOtherNeutral, TChar::EOtherNeutral);
|
sl@0
|
296 |
}
|
sl@0
|
297 |
|
sl@0
|
298 |
// If there was only one run and it's left-to-right, we've finished.
|
sl@0
|
299 |
if (!allocated_run_info_array && run_info.iDirection == 0)
|
sl@0
|
300 |
return error;
|
sl@0
|
301 |
|
sl@0
|
302 |
// Reorder the text into a new buffer.
|
sl@0
|
303 |
TText* buffer = new TText[aLength];
|
sl@0
|
304 |
if (!buffer)
|
sl@0
|
305 |
{
|
sl@0
|
306 |
delete [] allocated_run_info_array;
|
sl@0
|
307 |
return KErrNoMemory;
|
sl@0
|
308 |
}
|
sl@0
|
309 |
const TBidirectionalState::TRunInfo* r = run_info_array;
|
sl@0
|
310 |
TText* dest = buffer;
|
sl@0
|
311 |
for (int i = 0; i < runs; i++, r++)
|
sl@0
|
312 |
{
|
sl@0
|
313 |
const TText* source = &aText[r->iStart];
|
sl@0
|
314 |
int length = r->iLength;
|
sl@0
|
315 |
Mem::Copy(dest,source,length * sizeof(TText));
|
sl@0
|
316 |
if (r->iDirection)
|
sl@0
|
317 |
ReverseGroups(dest,length);
|
sl@0
|
318 |
dest += length;
|
sl@0
|
319 |
}
|
sl@0
|
320 |
|
sl@0
|
321 |
delete [] allocated_run_info_array;
|
sl@0
|
322 |
aNewText = buffer;
|
sl@0
|
323 |
return KErrNone;
|
sl@0
|
324 |
}
|
sl@0
|
325 |
|
sl@0
|
326 |
|
sl@0
|
327 |
EXPORT_C TBidirectionalState::TBidirectionalState()
|
sl@0
|
328 |
/** Standard constructor. */
|
sl@0
|
329 |
{
|
sl@0
|
330 |
Reset();
|
sl@0
|
331 |
}
|
sl@0
|
332 |
|
sl@0
|
333 |
|
sl@0
|
334 |
/** Reorders a line of text and updates the bidirectional state for the next line.
|
sl@0
|
335 |
|
sl@0
|
336 |
@param aRunInfo An array of objects representing runs of characters with the
|
sl@0
|
337 |
same bidirectional category. Any number of characters can be combined into
|
sl@0
|
338 |
a run if they have the same category, except for the categories TChar::EEuropeanNumberSeparator
|
sl@0
|
339 |
and TChar::ECommonNumberSeparator, which should be put into single-character
|
sl@0
|
340 |
runs because the reordering logic depends on this.
|
sl@0
|
341 |
@param aRuns Number of 'run info' objects.
|
sl@0
|
342 |
@param aParStart Tells the function whether the line is the first line of a
|
sl@0
|
343 |
paragraph.
|
sl@0
|
344 |
@param aParEnd Tells the function whether the line is the last line of a paragraph.
|
sl@0
|
345 |
@param aParRightToLeft ETrue if the default directionality of the text to be
|
sl@0
|
346 |
re-ordered is right-to-left.
|
sl@0
|
347 |
@param aNextCategory The category of the character immediately after the end
|
sl@0
|
348 |
of the line. This is ignored if aParEnd is ETrue.
|
sl@0
|
349 |
@param aNextStrongCategory The category of the first strong character (one
|
sl@0
|
350 |
of the categories ELeftToRight, ELeftToRightEmbedding, ELeftToRightOverride,
|
sl@0
|
351 |
ERightToLeft, ERightToLeftArabic, ERightToLeftEmbedding or ERightToLeftOverride)
|
sl@0
|
352 |
after the end of the line. This is ignored if aParEnd is ETrue.
|
sl@0
|
353 |
@param aVisualEndIsAmbiguous EFalse if the logical end of this line is at the
|
sl@0
|
354 |
visual end and the logical beginning of the next line is at the visual beginning.
|
sl@0
|
355 |
*/
|
sl@0
|
356 |
EXPORT_C void TBidirectionalState::ReorderLine(TRunInfo* aRunInfo, TInt aRuns,
|
sl@0
|
357 |
TBool aParStart, TBool aParEnd, TBool aParRightToLeft,
|
sl@0
|
358 |
TChar::TBdCategory aNextCategory, TChar::TBdCategory aNextStrongCategory,
|
sl@0
|
359 |
TBool& aVisualEndIsAmbiguous)
|
sl@0
|
360 |
{
|
sl@0
|
361 |
ReorderLine(aRunInfo, aRuns, aParStart, aParEnd, aParRightToLeft,
|
sl@0
|
362 |
aNextCategory, aNextStrongCategory);
|
sl@0
|
363 |
if (iStackLevel != 0)
|
sl@0
|
364 |
{
|
sl@0
|
365 |
aVisualEndIsAmbiguous = ETrue;
|
sl@0
|
366 |
return;
|
sl@0
|
367 |
}
|
sl@0
|
368 |
TCategory nextCat = CharToBdCat(aNextCategory);
|
sl@0
|
369 |
TCategory nextStrong = CharToBdCat(aNextStrongCategory);
|
sl@0
|
370 |
const TUint KAllStrongLeftToRight =
|
sl@0
|
371 |
ELeftToRight | ELeftToRightEmbedding | ELeftToRightOverride;
|
sl@0
|
372 |
const TUint KAllStrongRightToLeft =
|
sl@0
|
373 |
ERightToLeft | ERightToLeftArabic | ERightToLeftEmbedding | ERightToLeftOverride;
|
sl@0
|
374 |
if (aParRightToLeft)
|
sl@0
|
375 |
{
|
sl@0
|
376 |
// Ambiguous if any of the surrounding categories are strongly left-to-right
|
sl@0
|
377 |
aVisualEndIsAmbiguous =
|
sl@0
|
378 |
(iPreviousStrongCategory | iPreviousCategory | nextCat | nextStrong)
|
sl@0
|
379 |
& KAllStrongLeftToRight;
|
sl@0
|
380 |
}
|
sl@0
|
381 |
else
|
sl@0
|
382 |
{
|
sl@0
|
383 |
// Ambiguous if any of the surrounding categories are strongly right-to-left
|
sl@0
|
384 |
aVisualEndIsAmbiguous =
|
sl@0
|
385 |
(iPreviousStrongCategory | iPreviousCategory | nextCat | nextStrong)
|
sl@0
|
386 |
& KAllStrongRightToLeft;
|
sl@0
|
387 |
}
|
sl@0
|
388 |
}
|
sl@0
|
389 |
/** Reorders a line of text and updates the bidirectional state for the next line.
|
sl@0
|
390 |
|
sl@0
|
391 |
@param aRunInfo An array of objects representing runs of characters with the
|
sl@0
|
392 |
same bidirectional category. Any number of characters can be combined into
|
sl@0
|
393 |
a run if they have the same category, except for the categories TChar::EEuropeanNumberSeparator
|
sl@0
|
394 |
and TChar::ECommonNumberSeparator, which should be put into single-character
|
sl@0
|
395 |
runs because the reordering logic depends on this.
|
sl@0
|
396 |
@param aRuns Number of 'run info' objects.
|
sl@0
|
397 |
@param aParStart Tells the function whether the line is the first line of a
|
sl@0
|
398 |
paragraph.
|
sl@0
|
399 |
@param aParEnd Tells the function whether the line is the last line of a paragraph.
|
sl@0
|
400 |
@param aParRightToLeft ETrue if the default directionality of the text to be
|
sl@0
|
401 |
re-ordered is right-to-left.
|
sl@0
|
402 |
@param aNextCategory The category of the character immediately after the end
|
sl@0
|
403 |
of the line. This is ignored if aParEnd is ETrue.
|
sl@0
|
404 |
@param aNextStrongCategory The category of the first strong character (one
|
sl@0
|
405 |
of the categories ELeftToRight, ELeftToRightEmbedding, ELeftToRightOverride,
|
sl@0
|
406 |
ERightToLeft, ERightToLeftArabic, ERightToLeftEmbedding or ERightToLeftOverride)
|
sl@0
|
407 |
after the end of the line. This is ignored if aParEnd is ETrue. */
|
sl@0
|
408 |
EXPORT_C void TBidirectionalState::ReorderLine(TRunInfo* aRunInfo, TInt aRuns,
|
sl@0
|
409 |
TBool aParStart, TBool aParEnd, TBool aParRightToLeft,
|
sl@0
|
410 |
TChar::TBdCategory aNextCategory, TChar::TBdCategory aNextStrongCategory)
|
sl@0
|
411 |
{
|
sl@0
|
412 |
// Reset if this is a new paragraph.
|
sl@0
|
413 |
if (aParStart)
|
sl@0
|
414 |
{
|
sl@0
|
415 |
Reset();
|
sl@0
|
416 |
iPreviousCategory = EOtherNeutral;
|
sl@0
|
417 |
if (aParRightToLeft)
|
sl@0
|
418 |
{
|
sl@0
|
419 |
iStack[0].iEmbeddingLevel = 1;
|
sl@0
|
420 |
iPreviousStrongCategory = ERightToLeft;
|
sl@0
|
421 |
}
|
sl@0
|
422 |
}
|
sl@0
|
423 |
|
sl@0
|
424 |
// Initialise the context object.
|
sl@0
|
425 |
TReorderContext context;
|
sl@0
|
426 |
context.iRunInfo = aRunInfo;
|
sl@0
|
427 |
context.iRuns = aRuns;
|
sl@0
|
428 |
context.iLastStrongCategory = iPreviousStrongCategory;
|
sl@0
|
429 |
if (aParEnd)
|
sl@0
|
430 |
context.iNextCategory = context.iNextStrongCategory = EOtherNeutral;
|
sl@0
|
431 |
else
|
sl@0
|
432 |
{
|
sl@0
|
433 |
context.iNextCategory = CharToBdCat(aNextCategory);
|
sl@0
|
434 |
context.iNextStrongCategory = CharToBdCat(aNextStrongCategory);
|
sl@0
|
435 |
}
|
sl@0
|
436 |
|
sl@0
|
437 |
// Initialise output data and find out what categories are present so that unnecessary steps can be skipped.
|
sl@0
|
438 |
context.iCategories = iPreviousCategory | context.iNextCategory | context.iNextStrongCategory;
|
sl@0
|
439 |
for (TInt i = 0; i != aRuns; ++i)
|
sl@0
|
440 |
{
|
sl@0
|
441 |
aRunInfo[i].iEmbeddingLevel = iStack[0].iEmbeddingLevel;
|
sl@0
|
442 |
aRunInfo[i].iDirection = 0;
|
sl@0
|
443 |
aRunInfo[i].iIndex = i;
|
sl@0
|
444 |
aRunInfo[i].iCategory = UintToBdCat(aRunInfo[i].iCategory);
|
sl@0
|
445 |
context.iCategories |= aRunInfo[i].iCategory;
|
sl@0
|
446 |
}
|
sl@0
|
447 |
|
sl@0
|
448 |
// Do nothing if no right-to-left material is present.
|
sl@0
|
449 |
if (aRuns == 0 ||
|
sl@0
|
450 |
(iStackLevel == 0 && iStack[0].iEmbeddingLevel == 0 &&
|
sl@0
|
451 |
!(context.iCategories & (ERightToLeftGroup | EBdControlsGroup))))
|
sl@0
|
452 |
return;
|
sl@0
|
453 |
|
sl@0
|
454 |
// Perform the bidirectional algorithm.
|
sl@0
|
455 |
if ((context.iCategories & EBdControlsGroup) ||
|
sl@0
|
456 |
State().iOverrideState != ENoOverrideState)
|
sl@0
|
457 |
HandleBdControls(context);
|
sl@0
|
458 |
ResolveWeakTypesW1W2W3(context);
|
sl@0
|
459 |
ResolveWeakTypesW4W5W6(context);
|
sl@0
|
460 |
ResolveWeakTypesW7(context);
|
sl@0
|
461 |
if (context.iCategories & EOtherNeutral)
|
sl@0
|
462 |
ResolveNeutralTypes(context);
|
sl@0
|
463 |
ResolveImplicitLevels(context);
|
sl@0
|
464 |
PrepareForNextLine(context);
|
sl@0
|
465 |
ReorderRuns(context);
|
sl@0
|
466 |
}
|
sl@0
|
467 |
|
sl@0
|
468 |
|
sl@0
|
469 |
void TBidirectionalState::PrepareForNextLine(const TReorderContext& aContext)
|
sl@0
|
470 |
/**
|
sl@0
|
471 |
Fold context information back into TBidirectionalState.
|
sl@0
|
472 |
@internalComponent
|
sl@0
|
473 |
*/
|
sl@0
|
474 |
{
|
sl@0
|
475 |
if (aContext.iRuns != 0)
|
sl@0
|
476 |
{
|
sl@0
|
477 |
iPreviousCategory = static_cast<TCategory>(
|
sl@0
|
478 |
aContext.iRunInfo[aContext.iRuns - 1].iCategory);
|
sl@0
|
479 |
iPreviousStrongCategory = aContext.iLastStrongCategory;
|
sl@0
|
480 |
}
|
sl@0
|
481 |
}
|
sl@0
|
482 |
|
sl@0
|
483 |
|
sl@0
|
484 |
void TBidirectionalState::HandleBdControls(TReorderContext& aContext)
|
sl@0
|
485 |
/**
|
sl@0
|
486 |
Handle LRO, RLO, LRE, RLE and PDF. After this phase, these categories will no
|
sl@0
|
487 |
longer be found.
|
sl@0
|
488 |
|
sl@0
|
489 |
This corresponds to Unicode(3.2) Bidirectional Algorithm phases X1-X7.
|
sl@0
|
490 |
Phase X8 is not required as the run is assumed to be all in one paragraph.
|
sl@0
|
491 |
Phases X9-X10 are implicit in other functions.
|
sl@0
|
492 |
|
sl@0
|
493 |
@internalComponent
|
sl@0
|
494 |
*/
|
sl@0
|
495 |
{
|
sl@0
|
496 |
aContext.iCategories = iPreviousCategory | aContext.iNextCategory;
|
sl@0
|
497 |
for (TInt i = 0; i != aContext.iRuns; ++i)
|
sl@0
|
498 |
{
|
sl@0
|
499 |
TRunInfo* r = aContext.iRunInfo + i;
|
sl@0
|
500 |
TCategory nextCatInLine = i < aContext.iRuns - 1?
|
sl@0
|
501 |
(TCategory)(r[1].iCategory) : ENoCategory;
|
sl@0
|
502 |
|
sl@0
|
503 |
TBool was_pdf = FALSE;
|
sl@0
|
504 |
if (r->iCategory & EBdControlsGroup)
|
sl@0
|
505 |
{
|
sl@0
|
506 |
if (r->iCategory == EPopDirectionalFormat)
|
sl@0
|
507 |
{
|
sl@0
|
508 |
if (iStackLevel > 0)
|
sl@0
|
509 |
{
|
sl@0
|
510 |
was_pdf = TRUE;
|
sl@0
|
511 |
r->iEmbeddingLevel = State().iEmbeddingLevel;
|
sl@0
|
512 |
if (nextCatInLine == State().iStartCategory)
|
sl@0
|
513 |
// Ignore POP-PUSH pair with nothing between.
|
sl@0
|
514 |
// This is surely wrong? Perhaps it is a hack to
|
sl@0
|
515 |
// help other parts of the algorithm. Must investigate.
|
sl@0
|
516 |
// TPB.
|
sl@0
|
517 |
r->iCategory = r[1].iCategory = EBoundaryNeutral;
|
sl@0
|
518 |
else
|
sl@0
|
519 |
{
|
sl@0
|
520 |
r->iCategory = Pop();
|
sl@0
|
521 |
}
|
sl@0
|
522 |
}
|
sl@0
|
523 |
else
|
sl@0
|
524 |
r->iCategory = EBoundaryNeutral;
|
sl@0
|
525 |
}
|
sl@0
|
526 |
else
|
sl@0
|
527 |
{
|
sl@0
|
528 |
// Category is LRE, RLE, LRO or RLO.
|
sl@0
|
529 |
if (nextCatInLine == EPopDirectionalFormat)
|
sl@0
|
530 |
// Ignore PUSH-POP pair with nothing between.
|
sl@0
|
531 |
r->iCategory = r[1].iCategory = EBoundaryNeutral;
|
sl@0
|
532 |
else
|
sl@0
|
533 |
r->iCategory = Push(static_cast<TCategory>(r->iCategory));
|
sl@0
|
534 |
}
|
sl@0
|
535 |
}
|
sl@0
|
536 |
|
sl@0
|
537 |
if (!was_pdf)
|
sl@0
|
538 |
{
|
sl@0
|
539 |
switch (State().iOverrideState)
|
sl@0
|
540 |
{
|
sl@0
|
541 |
case ELeftToRightOverrideState:
|
sl@0
|
542 |
r->iCategory = ELeftToRight;
|
sl@0
|
543 |
break;
|
sl@0
|
544 |
case ERightToLeftOverrideState:
|
sl@0
|
545 |
r->iCategory = ERightToLeft;
|
sl@0
|
546 |
break;
|
sl@0
|
547 |
default:
|
sl@0
|
548 |
break;
|
sl@0
|
549 |
}
|
sl@0
|
550 |
r->iEmbeddingLevel = State().iEmbeddingLevel;
|
sl@0
|
551 |
}
|
sl@0
|
552 |
if (r->iCategory & EStrongGroup)
|
sl@0
|
553 |
aContext.iLastStrongCategory = static_cast<TCategory>(r->iCategory);
|
sl@0
|
554 |
aContext.iCategories |= r->iCategory;
|
sl@0
|
555 |
}
|
sl@0
|
556 |
}
|
sl@0
|
557 |
|
sl@0
|
558 |
|
sl@0
|
559 |
void TBidirectionalState::ResolveWeakTypesW1W2W3(TReorderContext& aContext)
|
sl@0
|
560 |
/**
|
sl@0
|
561 |
Unicode(3.2) Bidirectional Algorithm phases W1, W2 and W3.
|
sl@0
|
562 |
@internalComponent
|
sl@0
|
563 |
*/
|
sl@0
|
564 |
{
|
sl@0
|
565 |
if (!(aContext.iCategories
|
sl@0
|
566 |
& (ENonSpacingMark | ERightToLeftArabic | EEuropeanNumber)))
|
sl@0
|
567 |
return;
|
sl@0
|
568 |
|
sl@0
|
569 |
TRunInfo* endOfRuns = aContext.iRunInfo + aContext.iRuns;
|
sl@0
|
570 |
TCategory prev_cat = iPreviousCategory;
|
sl@0
|
571 |
TBool european_to_arabic_number = iPreviousStrongCategory == ERightToLeftArabic;
|
sl@0
|
572 |
|
sl@0
|
573 |
aContext.iCategories = iPreviousCategory | aContext.iNextCategory;
|
sl@0
|
574 |
for (TRunInfo* r = aContext.iRunInfo; r != endOfRuns; r++)
|
sl@0
|
575 |
{
|
sl@0
|
576 |
switch (r->iCategory)
|
sl@0
|
577 |
{
|
sl@0
|
578 |
case ENonSpacingMark: // non-spacing marks change to the previous category
|
sl@0
|
579 |
r->iCategory = prev_cat;
|
sl@0
|
580 |
break;
|
sl@0
|
581 |
case ELeftToRight:
|
sl@0
|
582 |
european_to_arabic_number = EFalse;
|
sl@0
|
583 |
break;
|
sl@0
|
584 |
case ERightToLeft:
|
sl@0
|
585 |
european_to_arabic_number = EFalse;
|
sl@0
|
586 |
break;
|
sl@0
|
587 |
case ERightToLeftArabic: // Arabic letters change to R
|
sl@0
|
588 |
european_to_arabic_number = ETrue;
|
sl@0
|
589 |
r->iCategory = ERightToLeft;
|
sl@0
|
590 |
break;
|
sl@0
|
591 |
case EEuropeanNumber: // European numbers change to Arabic if last strong category was R
|
sl@0
|
592 |
if (european_to_arabic_number)
|
sl@0
|
593 |
r->iCategory = EArabicNumber;
|
sl@0
|
594 |
break;
|
sl@0
|
595 |
default:
|
sl@0
|
596 |
break;
|
sl@0
|
597 |
}
|
sl@0
|
598 |
aContext.iCategories |= r->iCategory;
|
sl@0
|
599 |
prev_cat = static_cast<TCategory>(r->iCategory);
|
sl@0
|
600 |
}
|
sl@0
|
601 |
}
|
sl@0
|
602 |
/**
|
sl@0
|
603 |
This phase removes categories NSM, AL, ES, ET, CS, BS, S, WS and BN, leaving
|
sl@0
|
604 |
only L, R, EN, AN and ON.
|
sl@0
|
605 |
@internalComponent
|
sl@0
|
606 |
*/
|
sl@0
|
607 |
void TBidirectionalState::ResolveWeakTypesW4W5W6(TReorderContext& aContext)
|
sl@0
|
608 |
{
|
sl@0
|
609 |
int i;
|
sl@0
|
610 |
TRunInfo* r;
|
sl@0
|
611 |
TCategory prev_cat = iPreviousCategory;
|
sl@0
|
612 |
TCategory next_cat = EOtherNeutral;
|
sl@0
|
613 |
|
sl@0
|
614 |
// Phase P0b.
|
sl@0
|
615 |
prev_cat = iPreviousCategory;
|
sl@0
|
616 |
if (aContext.iCategories & EBoundaryNeutral)
|
sl@0
|
617 |
{
|
sl@0
|
618 |
for (i = 0, aContext.iCategories = iPreviousCategory | aContext.iNextCategory, r = aContext.iRunInfo;
|
sl@0
|
619 |
i < aContext.iRuns;
|
sl@0
|
620 |
i++, aContext.iCategories |= r->iCategory, r++)
|
sl@0
|
621 |
{
|
sl@0
|
622 |
if (r->iCategory == EBoundaryNeutral) // runs of boundary neutrals change to EN, ET or AN if adjacent to
|
sl@0
|
623 |
{ // one of these, otherwise to ON
|
sl@0
|
624 |
int end = i + 1;
|
sl@0
|
625 |
while (end < aContext.iRuns && aContext.iRunInfo[end].iCategory == EBoundaryNeutral)
|
sl@0
|
626 |
end++;
|
sl@0
|
627 |
next_cat = end < aContext.iRuns ? (TCategory)(aContext.iRunInfo[end].iCategory) : aContext.iNextCategory;
|
sl@0
|
628 |
TCategory c = EOtherNeutral;
|
sl@0
|
629 |
if (prev_cat == EEuropeanNumber || next_cat == EEuropeanNumber)
|
sl@0
|
630 |
c = EEuropeanNumber;
|
sl@0
|
631 |
else if (prev_cat == EEuropeanNumberTerminator || next_cat == EEuropeanNumberTerminator)
|
sl@0
|
632 |
c = EEuropeanNumberTerminator;
|
sl@0
|
633 |
else if (prev_cat == EArabicNumber || next_cat == EArabicNumber)
|
sl@0
|
634 |
c = EArabicNumber;
|
sl@0
|
635 |
for (int j = i; j < end; j++)
|
sl@0
|
636 |
aContext.iRunInfo[j].iCategory = c;
|
sl@0
|
637 |
i = end - 1;
|
sl@0
|
638 |
r = &aContext.iRunInfo[i];
|
sl@0
|
639 |
}
|
sl@0
|
640 |
prev_cat = (TCategory)r->iCategory;
|
sl@0
|
641 |
}
|
sl@0
|
642 |
}
|
sl@0
|
643 |
|
sl@0
|
644 |
// Phase P1.
|
sl@0
|
645 |
prev_cat = iPreviousCategory;
|
sl@0
|
646 |
if (aContext.iCategories & (EEuropeanNumberSeparator | ECommonNumberSeparator))
|
sl@0
|
647 |
{
|
sl@0
|
648 |
for (i = 0, aContext.iCategories = iPreviousCategory | aContext.iNextCategory, r = aContext.iRunInfo;
|
sl@0
|
649 |
i < aContext.iRuns;
|
sl@0
|
650 |
i++, aContext.iCategories |= r->iCategory, r++)
|
sl@0
|
651 |
{
|
sl@0
|
652 |
next_cat = i < aContext.iRuns - 1 ? (TCategory)(r[1].iCategory) : aContext.iNextCategory;
|
sl@0
|
653 |
switch (r->iCategory)
|
sl@0
|
654 |
{
|
sl@0
|
655 |
case EEuropeanNumberSeparator: // European separators change to EN if between two ENs, else to ON
|
sl@0
|
656 |
if (prev_cat == EEuropeanNumber && next_cat == EEuropeanNumber)
|
sl@0
|
657 |
r->iCategory = EEuropeanNumber;
|
sl@0
|
658 |
else
|
sl@0
|
659 |
r->iCategory = EOtherNeutral;
|
sl@0
|
660 |
break;
|
sl@0
|
661 |
case ECommonNumberSeparator: // CSs change to EN or AN if between two of the same, else to ON
|
sl@0
|
662 |
if (prev_cat == EEuropeanNumber && next_cat == EEuropeanNumber)
|
sl@0
|
663 |
r->iCategory = EEuropeanNumber;
|
sl@0
|
664 |
else if (prev_cat == EArabicNumber && next_cat == EArabicNumber)
|
sl@0
|
665 |
r->iCategory = EArabicNumber;
|
sl@0
|
666 |
else
|
sl@0
|
667 |
r->iCategory = EOtherNeutral;
|
sl@0
|
668 |
break;
|
sl@0
|
669 |
default:
|
sl@0
|
670 |
break;
|
sl@0
|
671 |
}
|
sl@0
|
672 |
prev_cat = (TCategory)r->iCategory;
|
sl@0
|
673 |
}
|
sl@0
|
674 |
}
|
sl@0
|
675 |
|
sl@0
|
676 |
/*
|
sl@0
|
677 |
Phase L1: tabs, whitespace before tabs, and trailing whitespace, is set to the base embedding level.
|
sl@0
|
678 |
We ought to do this just before the final reordering, but the whitespace and segment separator
|
sl@0
|
679 |
categories have disappeared by then so we use the sentinel value 255 which tells
|
sl@0
|
680 |
ResolveImplicitLevels what to do.
|
sl@0
|
681 |
*/
|
sl@0
|
682 |
TBool demote_whitespace = TRUE;
|
sl@0
|
683 |
for (i = aContext.iRuns - 1, r = &aContext.iRunInfo[i]; i >= 0; i--, r--)
|
sl@0
|
684 |
{
|
sl@0
|
685 |
switch (r->iCategory)
|
sl@0
|
686 |
{
|
sl@0
|
687 |
case EWhitespace:
|
sl@0
|
688 |
break;
|
sl@0
|
689 |
case ESegmentSeparator:
|
sl@0
|
690 |
demote_whitespace = TRUE;
|
sl@0
|
691 |
break;
|
sl@0
|
692 |
default:
|
sl@0
|
693 |
demote_whitespace = FALSE;
|
sl@0
|
694 |
break;
|
sl@0
|
695 |
}
|
sl@0
|
696 |
if (demote_whitespace)
|
sl@0
|
697 |
r->iEmbeddingLevel = 255;
|
sl@0
|
698 |
}
|
sl@0
|
699 |
|
sl@0
|
700 |
// Phases P2 and P3.
|
sl@0
|
701 |
prev_cat = iPreviousCategory;
|
sl@0
|
702 |
if (aContext.iCategories & (EEuropeanNumberTerminator | ESegmentSeparator | EWhitespace))
|
sl@0
|
703 |
{
|
sl@0
|
704 |
for (i = 0, aContext.iCategories = iPreviousCategory | aContext.iNextCategory, r = aContext.iRunInfo;
|
sl@0
|
705 |
i < aContext.iRuns;
|
sl@0
|
706 |
i++, aContext.iCategories |= r->iCategory, r++)
|
sl@0
|
707 |
{
|
sl@0
|
708 |
next_cat = i < aContext.iRuns - 1 ? (TCategory)(r[1].iCategory) : aContext.iNextCategory;
|
sl@0
|
709 |
switch (r->iCategory)
|
sl@0
|
710 |
{
|
sl@0
|
711 |
case EEuropeanNumberTerminator: // runs of ETs change to ENs if next to an EN, else to ON
|
sl@0
|
712 |
{
|
sl@0
|
713 |
int end = i + 1;
|
sl@0
|
714 |
while (end < aContext.iRuns && aContext.iRunInfo[end].iCategory == EEuropeanNumberTerminator)
|
sl@0
|
715 |
end++;
|
sl@0
|
716 |
next_cat = end < aContext.iRuns ? (TCategory)(aContext.iRunInfo[end].iCategory) : aContext.iNextCategory;
|
sl@0
|
717 |
TCategory c = EOtherNeutral;
|
sl@0
|
718 |
if (prev_cat == EEuropeanNumber || next_cat == EEuropeanNumber)
|
sl@0
|
719 |
c = EEuropeanNumber;
|
sl@0
|
720 |
for (int j = i; j < end; j++)
|
sl@0
|
721 |
aContext.iRunInfo[j].iCategory = c;
|
sl@0
|
722 |
i = end - 1;
|
sl@0
|
723 |
r = &aContext.iRunInfo[i];
|
sl@0
|
724 |
}
|
sl@0
|
725 |
break;
|
sl@0
|
726 |
case ESegmentSeparator: // S and WS change to ON
|
sl@0
|
727 |
case EWhitespace:
|
sl@0
|
728 |
r->iCategory = EOtherNeutral;
|
sl@0
|
729 |
break;
|
sl@0
|
730 |
default:
|
sl@0
|
731 |
break;
|
sl@0
|
732 |
}
|
sl@0
|
733 |
prev_cat = (TCategory)r->iCategory;
|
sl@0
|
734 |
}
|
sl@0
|
735 |
}
|
sl@0
|
736 |
}
|
sl@0
|
737 |
|
sl@0
|
738 |
void TBidirectionalState::ResolveWeakTypesW7(TReorderContext& aContext)
|
sl@0
|
739 |
{
|
sl@0
|
740 |
if (!(aContext.iCategories & EEuropeanNumber))
|
sl@0
|
741 |
return;
|
sl@0
|
742 |
|
sl@0
|
743 |
TCategory prev_strong_cat = iPreviousStrongCategory;
|
sl@0
|
744 |
|
sl@0
|
745 |
aContext.iCategories = iPreviousCategory | aContext.iNextCategory;
|
sl@0
|
746 |
TRunInfo* endOfRuns = aContext.iRunInfo + aContext.iRuns;
|
sl@0
|
747 |
for (TRunInfo* r = aContext.iRunInfo; r != endOfRuns; r++)
|
sl@0
|
748 |
{
|
sl@0
|
749 |
switch (r->iCategory)
|
sl@0
|
750 |
{
|
sl@0
|
751 |
case ELeftToRight:
|
sl@0
|
752 |
prev_strong_cat = ELeftToRight;
|
sl@0
|
753 |
break;
|
sl@0
|
754 |
case ERightToLeft:
|
sl@0
|
755 |
prev_strong_cat = ERightToLeft;
|
sl@0
|
756 |
break;
|
sl@0
|
757 |
case EEuropeanNumber:
|
sl@0
|
758 |
if (prev_strong_cat == ELeftToRight)
|
sl@0
|
759 |
r->iCategory = ELeftToRight;
|
sl@0
|
760 |
break;
|
sl@0
|
761 |
default:
|
sl@0
|
762 |
break;
|
sl@0
|
763 |
}
|
sl@0
|
764 |
aContext.iCategories |= r->iCategory;
|
sl@0
|
765 |
}
|
sl@0
|
766 |
}
|
sl@0
|
767 |
|
sl@0
|
768 |
|
sl@0
|
769 |
|
sl@0
|
770 |
void TBidirectionalState::DeneutralizeRuns(TRunInfo* aStart, TRunInfo* aEnd,
|
sl@0
|
771 |
TCategory aStartCategory, TCategory aEndCategory)
|
sl@0
|
772 |
/**
|
sl@0
|
773 |
Turn all ON (Other Neutral) into non-neutrals according to the rules N1 and N2.
|
sl@0
|
774 |
@param aStart The start of the run array to be altered.
|
sl@0
|
775 |
@param aEnd One past the end of the run array to be altered.
|
sl@0
|
776 |
@param aStartCategory
|
sl@0
|
777 |
The last non-neutral before the run, must be ELeftToRight or ERightToLeft.
|
sl@0
|
778 |
@param aEndCategory
|
sl@0
|
779 |
The first non-neutral after the run, must be ELeftToRight or ERightToLeft.
|
sl@0
|
780 |
@internalComponent
|
sl@0
|
781 |
*/ {
|
sl@0
|
782 |
// if sandwiched by the same category, neutrals become that.
|
sl@0
|
783 |
if (aStartCategory == aEndCategory)
|
sl@0
|
784 |
{
|
sl@0
|
785 |
for (; aStart != aEnd; ++aStart)
|
sl@0
|
786 |
aStart->iCategory = aStartCategory;
|
sl@0
|
787 |
return;
|
sl@0
|
788 |
}
|
sl@0
|
789 |
// otherwise look at the embedding level in each case
|
sl@0
|
790 |
for (; aStart != aEnd; ++aStart)
|
sl@0
|
791 |
{
|
sl@0
|
792 |
aStart->iCategory = aStart->iEmbeddingLevel & 1?
|
sl@0
|
793 |
ERightToLeft : ELeftToRight;
|
sl@0
|
794 |
}
|
sl@0
|
795 |
}
|
sl@0
|
796 |
|
sl@0
|
797 |
|
sl@0
|
798 |
void TBidirectionalState::ResolveNeutralTypes(TReorderContext& aContext)
|
sl@0
|
799 |
/**
|
sl@0
|
800 |
This phase removes the ON (Other Neutral) category, leaving only L, R, EN, and
|
sl@0
|
801 |
AN; no need to update aContext.iCategories.
|
sl@0
|
802 |
@internalComponent
|
sl@0
|
803 |
*/
|
sl@0
|
804 |
{
|
sl@0
|
805 |
// Really we should find if any number intervenes, but this would require
|
sl@0
|
806 |
// a BC break.
|
sl@0
|
807 |
TCategory prevNonNeutral = iPreviousStrongCategory;
|
sl@0
|
808 |
if (prevNonNeutral & ELeftToRightGroup)
|
sl@0
|
809 |
prevNonNeutral = ELeftToRight;
|
sl@0
|
810 |
else if (prevNonNeutral & ERightToLeftGroup)
|
sl@0
|
811 |
prevNonNeutral = ERightToLeft;
|
sl@0
|
812 |
TRunInfo *prevNonNeutralRun = aContext.iRunInfo; // one past the last non-neutral found
|
sl@0
|
813 |
TRunInfo *endOfRuns = aContext.iRunInfo + aContext.iRuns;
|
sl@0
|
814 |
|
sl@0
|
815 |
// All neutrals have now been changed to ON; change them to L or R depending on context.
|
sl@0
|
816 |
for (TRunInfo *p = aContext.iRunInfo; p != endOfRuns; ++p)
|
sl@0
|
817 |
{
|
sl@0
|
818 |
TCategory nonNeutral = EOtherNeutral;
|
sl@0
|
819 |
switch (p->iCategory)
|
sl@0
|
820 |
{
|
sl@0
|
821 |
case ELeftToRight:
|
sl@0
|
822 |
nonNeutral = ELeftToRight;
|
sl@0
|
823 |
break;
|
sl@0
|
824 |
case ERightToLeft:
|
sl@0
|
825 |
nonNeutral = ERightToLeft;
|
sl@0
|
826 |
break;
|
sl@0
|
827 |
case EArabicNumber:
|
sl@0
|
828 |
case EEuropeanNumber:
|
sl@0
|
829 |
nonNeutral = ERightToLeft;
|
sl@0
|
830 |
break;
|
sl@0
|
831 |
default:
|
sl@0
|
832 |
break;
|
sl@0
|
833 |
}
|
sl@0
|
834 |
if (nonNeutral != EOtherNeutral)
|
sl@0
|
835 |
{
|
sl@0
|
836 |
if (p != prevNonNeutralRun)
|
sl@0
|
837 |
DeneutralizeRuns(prevNonNeutralRun, p,
|
sl@0
|
838 |
prevNonNeutral, nonNeutral);
|
sl@0
|
839 |
prevNonNeutral = nonNeutral;
|
sl@0
|
840 |
prevNonNeutralRun = p + 1;
|
sl@0
|
841 |
}
|
sl@0
|
842 |
}
|
sl@0
|
843 |
DeneutralizeRuns(prevNonNeutralRun, endOfRuns, prevNonNeutral,
|
sl@0
|
844 |
aContext.iNextStrongCategory);
|
sl@0
|
845 |
}
|
sl@0
|
846 |
|
sl@0
|
847 |
|
sl@0
|
848 |
void TBidirectionalState::ResolveImplicitLevels(TReorderContext& aContext)
|
sl@0
|
849 |
/**
|
sl@0
|
850 |
Phases I1 and I2.
|
sl@0
|
851 |
@internalComponent
|
sl@0
|
852 |
*/ {
|
sl@0
|
853 |
int i;
|
sl@0
|
854 |
TRunInfo* r;
|
sl@0
|
855 |
for (i = 0, r = aContext.iRunInfo; i < aContext.iRuns; i++, r++)
|
sl@0
|
856 |
{
|
sl@0
|
857 |
if (r->iEmbeddingLevel == 255) // sentinel indicating this is a tab or segment-final whitespace
|
sl@0
|
858 |
r->iEmbeddingLevel = iStack[0].iEmbeddingLevel;
|
sl@0
|
859 |
else switch (r->iCategory)
|
sl@0
|
860 |
{
|
sl@0
|
861 |
case ELeftToRight:
|
sl@0
|
862 |
if (r->iEmbeddingLevel & 1)
|
sl@0
|
863 |
r->iEmbeddingLevel++;
|
sl@0
|
864 |
break;
|
sl@0
|
865 |
case ERightToLeft:
|
sl@0
|
866 |
if (!(r->iEmbeddingLevel & 1))
|
sl@0
|
867 |
r->iEmbeddingLevel++;
|
sl@0
|
868 |
break;
|
sl@0
|
869 |
case EEuropeanNumber: case EArabicNumber:
|
sl@0
|
870 |
if (r->iEmbeddingLevel & 1)
|
sl@0
|
871 |
r->iEmbeddingLevel++;
|
sl@0
|
872 |
else
|
sl@0
|
873 |
r->iEmbeddingLevel += 2;
|
sl@0
|
874 |
break;
|
sl@0
|
875 |
default:
|
sl@0
|
876 |
break;
|
sl@0
|
877 |
}
|
sl@0
|
878 |
}
|
sl@0
|
879 |
}
|
sl@0
|
880 |
|
sl@0
|
881 |
|
sl@0
|
882 |
void TBidirectionalState::ReorderRuns(TReorderContext& aContext)
|
sl@0
|
883 |
/**
|
sl@0
|
884 |
Phase L2.
|
sl@0
|
885 |
@internalComponent
|
sl@0
|
886 |
*/ {
|
sl@0
|
887 |
// Find the highest level and lowest odd level.
|
sl@0
|
888 |
int i;
|
sl@0
|
889 |
TRunInfo* r;
|
sl@0
|
890 |
int highest = 0;
|
sl@0
|
891 |
int lowest_odd = EMaxLevel;
|
sl@0
|
892 |
int level = 0;
|
sl@0
|
893 |
for (i = 0, r = aContext.iRunInfo; i < aContext.iRuns; i++, r++)
|
sl@0
|
894 |
{
|
sl@0
|
895 |
level = r->iEmbeddingLevel;
|
sl@0
|
896 |
if (level > highest)
|
sl@0
|
897 |
highest = level;
|
sl@0
|
898 |
if ((level & 1) && level < lowest_odd)
|
sl@0
|
899 |
lowest_odd = level;
|
sl@0
|
900 |
}
|
sl@0
|
901 |
|
sl@0
|
902 |
// From the highest level to the lowest odd level, reverse any run at that level or higher.
|
sl@0
|
903 |
for (level = highest; level >= lowest_odd; level--)
|
sl@0
|
904 |
{
|
sl@0
|
905 |
int run_start = 0;
|
sl@0
|
906 |
r = aContext.iRunInfo;
|
sl@0
|
907 |
while (run_start < aContext.iRuns)
|
sl@0
|
908 |
{
|
sl@0
|
909 |
while (run_start < aContext.iRuns && r->iEmbeddingLevel < level)
|
sl@0
|
910 |
{
|
sl@0
|
911 |
run_start++;
|
sl@0
|
912 |
r++;
|
sl@0
|
913 |
}
|
sl@0
|
914 |
int run_end = run_start;
|
sl@0
|
915 |
while (run_end < aContext.iRuns && r->iEmbeddingLevel >= level)
|
sl@0
|
916 |
{
|
sl@0
|
917 |
r->iDirection = !r->iDirection;
|
sl@0
|
918 |
run_end++;
|
sl@0
|
919 |
r++;
|
sl@0
|
920 |
}
|
sl@0
|
921 |
TRunInfo* p = &aContext.iRunInfo[run_start];
|
sl@0
|
922 |
TRunInfo* q = &aContext.iRunInfo[run_end - 1];
|
sl@0
|
923 |
while (p < q)
|
sl@0
|
924 |
{
|
sl@0
|
925 |
TRunInfo temp = *p;
|
sl@0
|
926 |
*p = *q;
|
sl@0
|
927 |
*q = temp;
|
sl@0
|
928 |
p++;
|
sl@0
|
929 |
q--;
|
sl@0
|
930 |
}
|
sl@0
|
931 |
run_start = run_end;
|
sl@0
|
932 |
}
|
sl@0
|
933 |
}
|
sl@0
|
934 |
}
|
sl@0
|
935 |
|
sl@0
|
936 |
|
sl@0
|
937 |
TBidirectionalState::TCategory TBidirectionalState::Push(TCategory aStartCategory)
|
sl@0
|
938 |
/** @internalComponent */
|
sl@0
|
939 |
{
|
sl@0
|
940 |
TInt rightToLeftFlag = (static_cast<TInt>(aStartCategory)
|
sl@0
|
941 |
& ERightToLeftGroup)? 1 : 0;
|
sl@0
|
942 |
TInt oldLevel = State().iEmbeddingLevel;
|
sl@0
|
943 |
TInt newLevel = oldLevel + 1;
|
sl@0
|
944 |
// And add an extra one if the bottom bit is not correct.
|
sl@0
|
945 |
newLevel += (newLevel & 1) ^ rightToLeftFlag;
|
sl@0
|
946 |
|
sl@0
|
947 |
if (EMaxExplicitLevel < newLevel)
|
sl@0
|
948 |
{
|
sl@0
|
949 |
if (oldLevel == 60)
|
sl@0
|
950 |
++iPushesBeyond60;
|
sl@0
|
951 |
else
|
sl@0
|
952 |
++iPushesBeyond61;
|
sl@0
|
953 |
return EBoundaryNeutral;
|
sl@0
|
954 |
}
|
sl@0
|
955 |
|
sl@0
|
956 |
++iStackLevel;
|
sl@0
|
957 |
TStackItem& state = iStack[iStackLevel];
|
sl@0
|
958 |
state.iEmbeddingLevel = static_cast<TUint8>(newLevel);
|
sl@0
|
959 |
state.iOverrideState = static_cast<TOverrideState>(aStartCategory
|
sl@0
|
960 |
& (ELeftToRightOverride | ERightToLeftOverride));
|
sl@0
|
961 |
state.iStartCategory = aStartCategory;
|
sl@0
|
962 |
|
sl@0
|
963 |
return rightToLeftFlag? ERightToLeft : ELeftToRight;
|
sl@0
|
964 |
}
|
sl@0
|
965 |
|
sl@0
|
966 |
|
sl@0
|
967 |
TBidirectionalState::TCategory TBidirectionalState::Pop()
|
sl@0
|
968 |
/** @internalComponent */
|
sl@0
|
969 |
{
|
sl@0
|
970 |
__ASSERT_DEBUG(0 < iStackLevel, User::Invariant());
|
sl@0
|
971 |
TInt level = State().iEmbeddingLevel;
|
sl@0
|
972 |
if (level < 60)
|
sl@0
|
973 |
--iStackLevel;
|
sl@0
|
974 |
else if (iPushesBeyond61 != 0)
|
sl@0
|
975 |
--iPushesBeyond61;
|
sl@0
|
976 |
else if (level == 61)
|
sl@0
|
977 |
--iStackLevel;
|
sl@0
|
978 |
else if (iPushesBeyond60)
|
sl@0
|
979 |
--iPushesBeyond60;
|
sl@0
|
980 |
else
|
sl@0
|
981 |
--iStackLevel;
|
sl@0
|
982 |
return (level & 1)? ERightToLeft : ELeftToRight;
|
sl@0
|
983 |
}
|
sl@0
|
984 |
|
sl@0
|
985 |
|
sl@0
|
986 |
EXPORT_C void TBidirectionalState::Reset()
|
sl@0
|
987 |
/** Sets the object to its default 'start of paragraph' state. */
|
sl@0
|
988 |
{
|
sl@0
|
989 |
iStackLevel = 0;
|
sl@0
|
990 |
iPushesBeyond60 = 0;
|
sl@0
|
991 |
iPushesBeyond61 = 0;
|
sl@0
|
992 |
iStack[0].iEmbeddingLevel = 0;
|
sl@0
|
993 |
iStack[0].iOverrideState = ENoOverrideState;
|
sl@0
|
994 |
iStack[0].iStartCategory = EOtherNeutral;
|
sl@0
|
995 |
iPreviousCategory = ELeftToRight;
|
sl@0
|
996 |
iPreviousStrongCategory = ELeftToRight;
|
sl@0
|
997 |
}
|
sl@0
|
998 |
|
sl@0
|
999 |
|
sl@0
|
1000 |
EXPORT_C TBool TBidirectionalState::IsDefault() const
|
sl@0
|
1001 |
/** Returns Gets the default 'start of paragraph' state.
|
sl@0
|
1002 |
|
sl@0
|
1003 |
@return ETrue if the object is in its default 'start of paragraph' state. */
|
sl@0
|
1004 |
{
|
sl@0
|
1005 |
return iStackLevel == 0 &&
|
sl@0
|
1006 |
iStack[0].iEmbeddingLevel == 0 &&
|
sl@0
|
1007 |
iStack[0].iOverrideState == ENoOverrideState &&
|
sl@0
|
1008 |
iStack[0].iStartCategory == EOtherNeutral &&
|
sl@0
|
1009 |
iPreviousCategory == ELeftToRight &&
|
sl@0
|
1010 |
iPreviousStrongCategory == ELeftToRight;
|
sl@0
|
1011 |
}
|
sl@0
|
1012 |
|
sl@0
|
1013 |
|
sl@0
|
1014 |
EXPORT_C TBool TBidirectionalState::operator==(const TBidirectionalState& aState) const
|
sl@0
|
1015 |
/** Return ETrue if two bidirectional states are identical.
|
sl@0
|
1016 |
|
sl@0
|
1017 |
@param aState A bidirectional state.
|
sl@0
|
1018 |
@return ETrue if two bidirectional states are identical. */
|
sl@0
|
1019 |
{
|
sl@0
|
1020 |
if (iPreviousCategory != aState.iPreviousCategory ||
|
sl@0
|
1021 |
iPreviousStrongCategory != aState.iPreviousStrongCategory ||
|
sl@0
|
1022 |
iStackLevel != aState.iStackLevel)
|
sl@0
|
1023 |
return FALSE;
|
sl@0
|
1024 |
const TStackItem* p = iStack;
|
sl@0
|
1025 |
const TStackItem* q = aState.iStack;
|
sl@0
|
1026 |
for (int i = 0; i <= iStackLevel; i++, p++, q++)
|
sl@0
|
1027 |
{
|
sl@0
|
1028 |
if (p->iStartCategory != q->iStartCategory ||
|
sl@0
|
1029 |
p->iOverrideState != q->iOverrideState ||
|
sl@0
|
1030 |
p->iEmbeddingLevel != q->iEmbeddingLevel)
|
sl@0
|
1031 |
return FALSE;
|
sl@0
|
1032 |
}
|
sl@0
|
1033 |
return TRUE;
|
sl@0
|
1034 |
}
|
sl@0
|
1035 |
|
sl@0
|
1036 |
|
sl@0
|
1037 |
TInt TBidirectionalState::CatToNumber(TInt aCat)
|
sl@0
|
1038 |
/**
|
sl@0
|
1039 |
Finds the highest bit set in the input. Used to convert
|
sl@0
|
1040 |
TBidirectionalState::TCategory into TChar::TBdCategory.
|
sl@0
|
1041 |
@param aCat a TBidirectionalState::TCategory.
|
sl@0
|
1042 |
@return The equivalent TChar::TBdCategory.
|
sl@0
|
1043 |
@internalComponent
|
sl@0
|
1044 |
*/ {
|
sl@0
|
1045 |
TInt shifts = 0;
|
sl@0
|
1046 |
TInt bits = 32;
|
sl@0
|
1047 |
TInt mask = ~0L;
|
sl@0
|
1048 |
while (bits != 0)
|
sl@0
|
1049 |
{
|
sl@0
|
1050 |
bits >>= 1;
|
sl@0
|
1051 |
mask <<= bits;
|
sl@0
|
1052 |
if ((aCat & mask) == 0)
|
sl@0
|
1053 |
{
|
sl@0
|
1054 |
aCat <<= bits;
|
sl@0
|
1055 |
shifts += bits;
|
sl@0
|
1056 |
}
|
sl@0
|
1057 |
}
|
sl@0
|
1058 |
return 31 - shifts;
|
sl@0
|
1059 |
}
|
sl@0
|
1060 |
|
sl@0
|
1061 |
|
sl@0
|
1062 |
EXPORT_C void TBidirectionalState::ExternalizeL(RWriteStream& aDest)
|
sl@0
|
1063 |
/** Serializes a bidirectional state to an output stream.
|
sl@0
|
1064 |
|
sl@0
|
1065 |
@param aDest An output stream. */
|
sl@0
|
1066 |
{
|
sl@0
|
1067 |
//+ put the prev cat, prev strong cat and stack levels in one number?
|
sl@0
|
1068 |
// Write the previous category and previous strong category.
|
sl@0
|
1069 |
aDest.WriteInt8L(CatToNumber(iPreviousCategory));
|
sl@0
|
1070 |
aDest.WriteInt8L(CatToNumber(iPreviousStrongCategory));
|
sl@0
|
1071 |
|
sl@0
|
1072 |
// Write the number of stack levels
|
sl@0
|
1073 |
aDest.WriteInt8L(iStackLevel);
|
sl@0
|
1074 |
|
sl@0
|
1075 |
/*
|
sl@0
|
1076 |
Write each stack level as a single number: 5 bits for the start category, 2 for the override state,
|
sl@0
|
1077 |
6 for the embedding level.
|
sl@0
|
1078 |
*/
|
sl@0
|
1079 |
for (int i = 0; i <= iStackLevel; i++)
|
sl@0
|
1080 |
{
|
sl@0
|
1081 |
TInt x = CatToNumber(iStack[i].iStartCategory);
|
sl@0
|
1082 |
if (iStack[i].iOverrideState == ELeftToRightOverrideState)
|
sl@0
|
1083 |
{
|
sl@0
|
1084 |
x |= (KBidirectionalStateOverrideStreamValueLeftToRight << 5);
|
sl@0
|
1085 |
}
|
sl@0
|
1086 |
else if (iStack[i].iOverrideState == ERightToLeftOverrideState)
|
sl@0
|
1087 |
{
|
sl@0
|
1088 |
x |= (KBidirectionalStateOverrideStreamValueRightToLeft << 5);
|
sl@0
|
1089 |
}
|
sl@0
|
1090 |
x |= ((TInt)iStack[i].iEmbeddingLevel << 7);
|
sl@0
|
1091 |
aDest.WriteInt16L(x);
|
sl@0
|
1092 |
}
|
sl@0
|
1093 |
|
sl@0
|
1094 |
TInt level = State().iEmbeddingLevel;
|
sl@0
|
1095 |
if (60 <= level)
|
sl@0
|
1096 |
{
|
sl@0
|
1097 |
aDest.WriteInt8L(iPushesBeyond60);
|
sl@0
|
1098 |
aDest.WriteInt8L(iPushesBeyond61);
|
sl@0
|
1099 |
}
|
sl@0
|
1100 |
}
|
sl@0
|
1101 |
|
sl@0
|
1102 |
|
sl@0
|
1103 |
EXPORT_C void TBidirectionalState::InternalizeL(RReadStream& aSource)
|
sl@0
|
1104 |
/** Reads a bidirectional state from an input stream, translating it from its serialized
|
sl@0
|
1105 |
form.
|
sl@0
|
1106 |
|
sl@0
|
1107 |
@param aSource A source stream. */
|
sl@0
|
1108 |
{
|
sl@0
|
1109 |
// Read the previous category and the previous strong category.
|
sl@0
|
1110 |
TInt x = aSource.ReadInt8L();
|
sl@0
|
1111 |
iPreviousCategory = (TCategory)(1 << x);
|
sl@0
|
1112 |
x = aSource.ReadInt8L();
|
sl@0
|
1113 |
iPreviousStrongCategory = (TCategory)(1 << x);
|
sl@0
|
1114 |
|
sl@0
|
1115 |
// Read the number of stack levels.
|
sl@0
|
1116 |
iStackLevel = aSource.ReadInt8L();
|
sl@0
|
1117 |
|
sl@0
|
1118 |
// Read the stack levels.
|
sl@0
|
1119 |
for (int i = 0; i <= iStackLevel; i++)
|
sl@0
|
1120 |
{
|
sl@0
|
1121 |
x = aSource.ReadInt16L();
|
sl@0
|
1122 |
iStack[i].iStartCategory = (TCategory)(1 << (x & 0x1F));
|
sl@0
|
1123 |
switch ((x >> 5) & 3)
|
sl@0
|
1124 |
{
|
sl@0
|
1125 |
case KBidirectionalStateOverrideStreamValueLeftToRight:
|
sl@0
|
1126 |
iStack[i].iOverrideState = ELeftToRightOverrideState;
|
sl@0
|
1127 |
break;
|
sl@0
|
1128 |
case KBidirectionalStateOverrideStreamValueRightToLeft:
|
sl@0
|
1129 |
iStack[i].iOverrideState = ERightToLeftOverrideState;
|
sl@0
|
1130 |
break;
|
sl@0
|
1131 |
case KBidirectionalStateOverrideStreamValueNone:
|
sl@0
|
1132 |
default: iStack[i].iOverrideState = ENoOverrideState; break;
|
sl@0
|
1133 |
};
|
sl@0
|
1134 |
iStack[i].iEmbeddingLevel = (TUint8)(x >> 7);
|
sl@0
|
1135 |
}
|
sl@0
|
1136 |
|
sl@0
|
1137 |
TInt level = State().iEmbeddingLevel;
|
sl@0
|
1138 |
if (60 <= level)
|
sl@0
|
1139 |
{
|
sl@0
|
1140 |
iPushesBeyond60 = aSource.ReadInt8L();
|
sl@0
|
1141 |
iPushesBeyond61 = aSource.ReadInt8L();
|
sl@0
|
1142 |
}
|
sl@0
|
1143 |
else
|
sl@0
|
1144 |
{
|
sl@0
|
1145 |
iPushesBeyond60 = 0;
|
sl@0
|
1146 |
iPushesBeyond61 = 0;
|
sl@0
|
1147 |
}
|
sl@0
|
1148 |
}
|
sl@0
|
1149 |
|
sl@0
|
1150 |
|
sl@0
|
1151 |
TBidirectionalState::TBidirectionalState(TChar::TBdCategory aPrevCat,
|
sl@0
|
1152 |
TChar::TBdCategory aPrevStrongCat,
|
sl@0
|
1153 |
TBool aParRightToLeft)
|
sl@0
|
1154 |
/**
|
sl@0
|
1155 |
Constructor suitable for test code.
|
sl@0
|
1156 |
@internalComponent
|
sl@0
|
1157 |
*/
|
sl@0
|
1158 |
{
|
sl@0
|
1159 |
Reset();
|
sl@0
|
1160 |
iPreviousCategory = CharToBdCat(aPrevCat);
|
sl@0
|
1161 |
iPreviousStrongCategory = CharToBdCat(aPrevStrongCat);
|
sl@0
|
1162 |
iStack[0].iEmbeddingLevel = (TUint8) (aParRightToLeft? 1 : 0);
|
sl@0
|
1163 |
}
|