• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // © 2016 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html#License
3 /*
4 *******************************************************************************
5 *   Copyright (C) 2001-2014, International Business Machines
6 *   Corporation and others.  All Rights Reserved.
7 *******************************************************************************
8 */
9 /* Written by Simon Montagu, Matitiahu Allouche
10  * (ported from C code written by Markus W. Scherer)
11  */
12 
13 package com.ibm.icu.text;
14 
15 
16 import java.util.Arrays;
17 
18 final class BidiLine {
19 
20     /*
21      * General remarks about the functions in this file:
22      *
23      * These functions deal with the aspects of potentially mixed-directional
24      * text in a single paragraph or in a line of a single paragraph
25      * which has already been processed according to
26      * the Unicode 3.0 Bidi algorithm as defined in
27      * http://www.unicode.org/unicode/reports/tr9/ , version 13,
28      * also described in The Unicode Standard, Version 4.0.1 .
29      *
30      * This means that there is a Bidi object with a levels
31      * and a dirProps array.
32      * paraLevel and direction are also set.
33      * Only if the length of the text is zero, then levels==dirProps==NULL.
34      *
35      * The overall directionality of the paragraph
36      * or line is used to bypass the reordering steps if possible.
37      * Even purely RTL text does not need reordering there because
38      * the getLogical/VisualIndex() methods can compute the
39      * index on the fly in such a case.
40      *
41      * The implementation of the access to same-level-runs and of the reordering
42      * do attempt to provide better performance and less memory usage compared to
43      * a direct implementation of especially rule (L2) with an array of
44      * one (32-bit) integer per text character.
45      *
46      * Here, the levels array is scanned as soon as necessary, and a vector of
47      * same-level-runs is created. Reordering then is done on this vector.
48      * For each run of text positions that were resolved to the same level,
49      * only 8 bytes are stored: the first text position of the run and the visual
50      * position behind the run after reordering.
51      * One sign bit is used to hold the directionality of the run.
52      * This is inefficient if there are many very short runs. If the average run
53      * length is <2, then this uses more memory.
54      *
55      * In a further attempt to save memory, the levels array is never changed
56      * after all the resolution rules (Xn, Wn, Nn, In).
57      * Many methods have to consider the field trailingWSStart:
58      * if it is less than length, then there is an implicit trailing run
59      * at the paraLevel,
60      * which is not reflected in the levels array.
61      * This allows a line Bidi object to use the same levels array as
62      * its paragraph parent object.
63      *
64      * When a Bidi object is created for a line of a paragraph, then the
65      * paragraph's levels and dirProps arrays are reused by way of setting
66      * a pointer into them, not by copying. This again saves memory and forbids to
67      * change the now shared levels for (L1).
68      */
69 
70     /* handle trailing WS (L1) -------------------------------------------------- */
71 
72     /*
73      * setTrailingWSStart() sets the start index for a trailing
74      * run of WS in the line. This is necessary because we do not modify
75      * the paragraph's levels array that we just point into.
76      * Using trailingWSStart is another form of performing (L1).
77      *
78      * To make subsequent operations easier, we also include the run
79      * before the WS if it is at the paraLevel - we merge the two here.
80      *
81      * This method is called only from setLine(), so paraLevel is
82      * set correctly for the line even when contextual multiple paragraphs.
83      */
84 
setTrailingWSStart(Bidi bidi)85     static void setTrailingWSStart(Bidi bidi)
86     {
87         byte[] dirProps = bidi.dirProps;
88         byte[] levels = bidi.levels;
89         int start = bidi.length;
90         byte paraLevel = bidi.paraLevel;
91 
92         /* If the line is terminated by a block separator, all preceding WS etc...
93            are already set to paragraph level.
94            Setting trailingWSStart to pBidi->length will avoid changing the
95            level of B chars from 0 to paraLevel in getLevels when
96            orderParagraphsLTR==TRUE
97         */
98         if (dirProps[start - 1] == Bidi.B) {
99             bidi.trailingWSStart = start;   /* currently == bidi.length */
100             return;
101         }
102         /* go backwards across all WS, BN, explicit codes */
103         while (start > 0 &&
104                 (Bidi.DirPropFlag(dirProps[start - 1]) & Bidi.MASK_WS) != 0) {
105             --start;
106         }
107 
108         /* if the WS run can be merged with the previous run then do so here */
109         while (start > 0 && levels[start - 1] == paraLevel) {
110             --start;
111         }
112 
113         bidi.trailingWSStart=start;
114     }
115 
setLine(Bidi paraBidi, int start, int limit)116     static Bidi setLine(Bidi paraBidi, int start, int limit) {
117         int length;
118 
119         Bidi lineBidi = new Bidi();
120 
121         /* set the values in lineBidi from its paraBidi parent */
122         /* class members are already initialized to 0 */
123         // lineBidi.paraBidi = null;        /* mark unfinished setLine */
124         // lineBidi.flags = 0;
125         // lineBidi.controlCount = 0;
126 
127         length = lineBidi.length = lineBidi.originalLength =
128                 lineBidi.resultLength = limit - start;
129 
130         lineBidi.text = new char[length];
131         System.arraycopy(paraBidi.text, start, lineBidi.text, 0, length);
132         lineBidi.paraLevel = paraBidi.GetParaLevelAt(start);
133         lineBidi.paraCount = paraBidi.paraCount;
134         lineBidi.runs = new BidiRun[0];
135         lineBidi.reorderingMode = paraBidi.reorderingMode;
136         lineBidi.reorderingOptions = paraBidi.reorderingOptions;
137         if (paraBidi.controlCount > 0) {
138             int j;
139             for (j = start; j < limit; j++) {
140                 if (Bidi.IsBidiControlChar(paraBidi.text[j])) {
141                     lineBidi.controlCount++;
142                 }
143             }
144             lineBidi.resultLength -= lineBidi.controlCount;
145         }
146         /* copy proper subset of DirProps */
147         lineBidi.getDirPropsMemory(length);
148         lineBidi.dirProps = lineBidi.dirPropsMemory;
149         System.arraycopy(paraBidi.dirProps, start, lineBidi.dirProps, 0,
150                          length);
151         /* copy proper subset of Levels */
152         lineBidi.getLevelsMemory(length);
153         lineBidi.levels = lineBidi.levelsMemory;
154         System.arraycopy(paraBidi.levels, start, lineBidi.levels, 0,
155                          length);
156         lineBidi.runCount = -1;
157 
158         if (paraBidi.direction != Bidi.MIXED) {
159             /* the parent is already trivial */
160             lineBidi.direction = paraBidi.direction;
161 
162             /*
163              * The parent's levels are all either
164              * implicitly or explicitly ==paraLevel;
165              * do the same here.
166              */
167             if (paraBidi.trailingWSStart <= start) {
168                 lineBidi.trailingWSStart = 0;
169             } else if (paraBidi.trailingWSStart < limit) {
170                 lineBidi.trailingWSStart = paraBidi.trailingWSStart - start;
171             } else {
172                 lineBidi.trailingWSStart = length;
173             }
174         } else {
175             byte[] levels = lineBidi.levels;
176             int i, trailingWSStart;
177             byte level;
178 
179             setTrailingWSStart(lineBidi);
180             trailingWSStart = lineBidi.trailingWSStart;
181 
182             /* recalculate lineBidi.direction */
183             if (trailingWSStart == 0) {
184                 /* all levels are at paraLevel */
185                 lineBidi.direction = (byte)(lineBidi.paraLevel & 1);
186             } else {
187                 /* get the level of the first character */
188                 level = (byte)(levels[0] & 1);
189 
190                 /* if there is anything of a different level, then the line
191                    is mixed */
192                 if (trailingWSStart < length &&
193                     (lineBidi.paraLevel & 1) != level) {
194                     /* the trailing WS is at paraLevel, which differs from
195                        levels[0] */
196                     lineBidi.direction = Bidi.MIXED;
197                 } else {
198                     /* see if levels[1..trailingWSStart-1] have the same
199                        direction as levels[0] and paraLevel */
200                     for (i = 1; ; i++) {
201                         if (i == trailingWSStart) {
202                             /* the direction values match those in level */
203                             lineBidi.direction = level;
204                             break;
205                         } else if ((levels[i] & 1) != level) {
206                             lineBidi.direction = Bidi.MIXED;
207                             break;
208                         }
209                     }
210                 }
211             }
212 
213             switch(lineBidi.direction) {
214                 case Bidi.DIRECTION_LEFT_TO_RIGHT:
215                     /* make sure paraLevel is even */
216                     lineBidi.paraLevel = (byte)
217                         ((lineBidi.paraLevel + 1) & ~1);
218 
219                     /* all levels are implicitly at paraLevel (important for
220                        getLevels()) */
221                     lineBidi.trailingWSStart = 0;
222                     break;
223                 case Bidi.DIRECTION_RIGHT_TO_LEFT:
224                     /* make sure paraLevel is odd */
225                     lineBidi.paraLevel |= 1;
226 
227                     /* all levels are implicitly at paraLevel (important for
228                        getLevels()) */
229                     lineBidi.trailingWSStart = 0;
230                     break;
231                 default:
232                     break;
233             }
234         }
235         lineBidi.paraBidi = paraBidi;     /* mark successful setLine */
236         return lineBidi;
237     }
238 
getLevelAt(Bidi bidi, int charIndex)239     static byte getLevelAt(Bidi bidi, int charIndex)
240     {
241         /* return paraLevel if in the trailing WS run, otherwise the real level */
242         if (bidi.direction != Bidi.MIXED || charIndex >= bidi.trailingWSStart) {
243             return bidi.GetParaLevelAt(charIndex);
244         } else {
245             return bidi.levels[charIndex];
246         }
247     }
248 
getLevels(Bidi bidi)249     static byte[] getLevels(Bidi bidi)
250     {
251         int start = bidi.trailingWSStart;
252         int length = bidi.length;
253 
254         if (start != length) {
255             /* the current levels array does not reflect the WS run */
256             /*
257              * After the previous if(), we know that the levels array
258              * has an implicit trailing WS run and therefore does not fully
259              * reflect itself all the levels.
260              * This must be a Bidi object for a line, and
261              * we need to create a new levels array.
262              */
263             /* bidi.paraLevel is ok even if contextual multiple paragraphs,
264                since bidi is a line object                                     */
265             Arrays.fill(bidi.levels, start, length, bidi.paraLevel);
266 
267             /* this new levels array is set for the line and reflects the WS run */
268             bidi.trailingWSStart = length;
269         }
270         if (length < bidi.levels.length) {
271             byte[] levels = new byte[length];
272             System.arraycopy(bidi.levels, 0, levels, 0, length);
273             return levels;
274         }
275         return bidi.levels;
276     }
277 
getLogicalRun(Bidi bidi, int logicalPosition)278     static BidiRun getLogicalRun(Bidi bidi, int logicalPosition)
279     {
280         /* this is done based on runs rather than on levels since levels have
281            a special interpretation when REORDER_RUNS_ONLY
282          */
283         BidiRun newRun = new BidiRun(), iRun;
284         getRuns(bidi);
285         int runCount = bidi.runCount;
286         int visualStart = 0, logicalLimit = 0;
287         iRun = bidi.runs[0];
288 
289         for (int i = 0; i < runCount; i++) {
290             iRun = bidi.runs[i];
291             logicalLimit = iRun.start + iRun.limit - visualStart;
292             if ((logicalPosition >= iRun.start) &&
293                 (logicalPosition < logicalLimit)) {
294                 break;
295             }
296             visualStart = iRun.limit;
297         }
298         newRun.start = iRun.start;
299         newRun.limit = logicalLimit;
300         newRun.level = iRun.level;
301         return newRun;
302     }
303 
getVisualRun(Bidi bidi, int runIndex)304     static BidiRun getVisualRun(Bidi bidi, int runIndex)
305     {
306         int start = bidi.runs[runIndex].start;
307         int limit;
308         byte level = bidi.runs[runIndex].level;
309 
310         if (runIndex > 0) {
311             limit = start +
312                     bidi.runs[runIndex].limit -
313                     bidi.runs[runIndex - 1].limit;
314         } else {
315             limit = start + bidi.runs[0].limit;
316         }
317         return new BidiRun(start, limit, level);
318     }
319 
320     /* in trivial cases there is only one trivial run; called by getRuns() */
getSingleRun(Bidi bidi, byte level)321     static void getSingleRun(Bidi bidi, byte level) {
322         /* simple, single-run case */
323         bidi.runs = bidi.simpleRuns;
324         bidi.runCount = 1;
325 
326         /* fill and reorder the single run */
327         bidi.runs[0] = new BidiRun(0, bidi.length, level);
328     }
329 
330     /* reorder the runs array (L2) ---------------------------------------------- */
331 
332     /*
333      * Reorder the same-level runs in the runs array.
334      * Here, runCount>1 and maxLevel>=minLevel>=paraLevel.
335      * All the visualStart fields=logical start before reordering.
336      * The "odd" bits are not set yet.
337      *
338      * Reordering with this data structure lends itself to some handy shortcuts:
339      *
340      * Since each run is moved but not modified, and since at the initial maxLevel
341      * each sequence of same-level runs consists of only one run each, we
342      * don't need to do anything there and can predecrement maxLevel.
343      * In many simple cases, the reordering is thus done entirely in the
344      * index mapping.
345      * Also, reordering occurs only down to the lowest odd level that occurs,
346      * which is minLevel|1. However, if the lowest level itself is odd, then
347      * in the last reordering the sequence of the runs at this level or higher
348      * will be all runs, and we don't need the elaborate loop to search for them.
349      * This is covered by ++minLevel instead of minLevel|=1 followed
350      * by an extra reorder-all after the reorder-some loop.
351      * About a trailing WS run:
352      * Such a run would need special treatment because its level is not
353      * reflected in levels[] if this is not a paragraph object.
354      * Instead, all characters from trailingWSStart on are implicitly at
355      * paraLevel.
356      * However, for all maxLevel>paraLevel, this run will never be reordered
357      * and does not need to be taken into account. maxLevel==paraLevel is only reordered
358      * if minLevel==paraLevel is odd, which is done in the extra segment.
359      * This means that for the main reordering loop we don't need to consider
360      * this run and can --runCount. If it is later part of the all-runs
361      * reordering, then runCount is adjusted accordingly.
362      */
reorderLine(Bidi bidi, byte minLevel, byte maxLevel)363     private static void reorderLine(Bidi bidi, byte minLevel, byte maxLevel) {
364 
365         /* nothing to do? */
366         if (maxLevel<=(minLevel|1)) {
367             return;
368         }
369 
370         BidiRun[] runs;
371         BidiRun tempRun;
372         byte[] levels;
373         int firstRun, endRun, limitRun, runCount;
374 
375         /*
376          * Reorder only down to the lowest odd level
377          * and reorder at an odd minLevel in a separate, simpler loop.
378          * See comments above for why minLevel is always incremented.
379          */
380         ++minLevel;
381 
382         runs = bidi.runs;
383         levels = bidi.levels;
384         runCount = bidi.runCount;
385 
386         /* do not include the WS run at paraLevel<=old minLevel except in the simple loop */
387         if (bidi.trailingWSStart < bidi.length) {
388             --runCount;
389         }
390 
391         while (--maxLevel >= minLevel) {
392             firstRun = 0;
393 
394             /* loop for all sequences of runs */
395             for ( ; ; ) {
396                 /* look for a sequence of runs that are all at >=maxLevel */
397                 /* look for the first run of such a sequence */
398                 while (firstRun < runCount && levels[runs[firstRun].start] < maxLevel) {
399                     ++firstRun;
400                 }
401                 if (firstRun >= runCount) {
402                     break;  /* no more such runs */
403                 }
404 
405                 /* look for the limit run of such a sequence (the run behind it) */
406                 for (limitRun = firstRun; ++limitRun < runCount &&
407                       levels[runs[limitRun].start]>=maxLevel; ) {}
408 
409                 /* Swap the entire sequence of runs from firstRun to limitRun-1. */
410                 endRun = limitRun - 1;
411                 while (firstRun < endRun) {
412                     tempRun = runs[firstRun];
413                     runs[firstRun] = runs[endRun];
414                     runs[endRun] = tempRun;
415                     ++firstRun;
416                     --endRun;
417                 }
418 
419                 if (limitRun == runCount) {
420                     break;  /* no more such runs */
421                 } else {
422                     firstRun = limitRun + 1;
423                 }
424             }
425         }
426 
427         /* now do maxLevel==old minLevel (==odd!), see above */
428         if ((minLevel & 1) == 0) {
429             firstRun = 0;
430 
431             /* include the trailing WS run in this complete reordering */
432             if (bidi.trailingWSStart == bidi.length) {
433                 --runCount;
434             }
435 
436             /* Swap the entire sequence of all runs. (endRun==runCount) */
437             while (firstRun < runCount) {
438                 tempRun = runs[firstRun];
439                 runs[firstRun] = runs[runCount];
440                 runs[runCount] = tempRun;
441                 ++firstRun;
442                 --runCount;
443             }
444         }
445     }
446 
447     /* compute the runs array --------------------------------------------------- */
448 
getRunFromLogicalIndex(Bidi bidi, int logicalIndex)449     static int getRunFromLogicalIndex(Bidi bidi, int logicalIndex) {
450         BidiRun[] runs = bidi.runs;
451         int runCount = bidi.runCount, visualStart = 0, i, length, logicalStart;
452 
453         for (i = 0; i < runCount; i++) {
454             length = runs[i].limit - visualStart;
455             logicalStart = runs[i].start;
456             if ((logicalIndex >= logicalStart) && (logicalIndex < (logicalStart+length))) {
457                 return i;
458             }
459             visualStart += length;
460         }
461         ///CLOVER:OFF
462         /* we should never get here */
463         throw new IllegalStateException("Internal ICU error in getRunFromLogicalIndex");
464         ///CLOVER:ON
465     }
466 
467     /*
468      * Compute the runs array from the levels array.
469      * After getRuns() returns true, runCount is guaranteed to be >0
470      * and the runs are reordered.
471      * Odd-level runs have visualStart on their visual right edge and
472      * they progress visually to the left.
473      * If option OPTION_INSERT_MARKS is set, insertRemove will contain the
474      * sum of appropriate LRM/RLM_BEFORE/AFTER flags.
475      * If option OPTION_REMOVE_CONTROLS is set, insertRemove will contain the
476      * negative number of BiDi control characters within this run.
477      */
getRuns(Bidi bidi)478     static void getRuns(Bidi bidi) {
479         /*
480          * This method returns immediately if the runs are already set. This
481          * includes the case of length==0 (handled in setPara)..
482          */
483         if (bidi.runCount >= 0) {
484             return;
485         }
486         if (bidi.direction != Bidi.MIXED) {
487             /* simple, single-run case - this covers length==0 */
488             /* bidi.paraLevel is ok even for contextual multiple paragraphs */
489             getSingleRun(bidi, bidi.paraLevel);
490         } else /* Bidi.MIXED, length>0 */ {
491             /* mixed directionality */
492             int length = bidi.length, limit;
493             byte[] levels = bidi.levels;
494             int i, runCount;
495             byte level = -1;    /* initialize with no valid level */
496             /*
497              * If there are WS characters at the end of the line
498              * and the run preceding them has a level different from
499              * paraLevel, then they will form their own run at paraLevel (L1).
500              * Count them separately.
501              * We need some special treatment for this in order to not
502              * modify the levels array which a line Bidi object shares
503              * with its paragraph parent and its other line siblings.
504              * In other words, for the trailing WS, it may be
505              * levels[]!=paraLevel but we have to treat it like it were so.
506              */
507             limit = bidi.trailingWSStart;
508             /* count the runs, there is at least one non-WS run, and limit>0 */
509             runCount = 0;
510             for (i = 0; i < limit; ++i) {
511                 /* increment runCount at the start of each run */
512                 if (levels[i] != level) {
513                     ++runCount;
514                     level = levels[i];
515                 }
516             }
517 
518             /*
519              * We don't need to see if the last run can be merged with a trailing
520              * WS run because setTrailingWSStart() would have done that.
521              */
522             if (runCount == 1 && limit == length) {
523                 /* There is only one non-WS run and no trailing WS-run. */
524                 getSingleRun(bidi, levels[0]);
525             } else /* runCount>1 || limit<length */ {
526                 /* allocate and set the runs */
527                 BidiRun[] runs;
528                 int runIndex, start;
529                 byte minLevel = Bidi.MAX_EXPLICIT_LEVEL + 1;
530                 byte maxLevel=0;
531 
532                 /* now, count a (non-mergeable) WS run */
533                 if (limit < length) {
534                     ++runCount;
535                 }
536 
537                 /* runCount > 1 */
538                 bidi.getRunsMemory(runCount);
539                 runs = bidi.runsMemory;
540 
541                 /* set the runs */
542                 /* FOOD FOR THOUGHT: this could be optimized, e.g.:
543                  * 464->444, 484->444, 575->555, 595->555
544                  * However, that would take longer. Check also how it would
545                  * interact with BiDi control removal and inserting Marks.
546                  */
547                 runIndex = 0;
548 
549                 /* search for the run limits and initialize visualLimit values with the run lengths */
550                 i = 0;
551                 do {
552                     /* prepare this run */
553                     start = i;
554                     level = levels[i];
555                     if (level < minLevel) {
556                         minLevel = level;
557                     }
558                     if (level > maxLevel) {
559                         maxLevel = level;
560                     }
561 
562                     /* look for the run limit */
563                     while (++i < limit && levels[i] == level) {}
564 
565                     /* i is another run limit */
566                     runs[runIndex] = new BidiRun(start, i - start, level);
567                     ++runIndex;
568                 } while (i < limit);
569 
570                 if (limit < length) {
571                     /* there is a separate WS run */
572                     runs[runIndex] = new BidiRun(limit, length - limit, bidi.paraLevel);
573                     /* For the trailing WS run, bidi.paraLevel is ok even
574                        if contextual multiple paragraphs.                   */
575                     if (bidi.paraLevel < minLevel) {
576                         minLevel = bidi.paraLevel;
577                     }
578                 }
579 
580                 /* set the object fields */
581                 bidi.runs = runs;
582                 bidi.runCount = runCount;
583 
584                 reorderLine(bidi, minLevel, maxLevel);
585 
586                 /* now add the direction flags and adjust the visualLimit's to be just that */
587                 /* this loop will also handle the trailing WS run */
588                 limit = 0;
589                 for (i = 0; i < runCount; ++i) {
590                     runs[i].level = levels[runs[i].start];
591                     limit = (runs[i].limit += limit);
592                 }
593 
594                 /* Set the embedding level for the trailing WS run. */
595                 /* For a RTL paragraph, it will be the *first* run in visual order. */
596                 /* For the trailing WS run, bidi.paraLevel is ok even if
597                    contextual multiple paragraphs.                          */
598                 if (runIndex < runCount) {
599                     int trailingRun = ((bidi.paraLevel & 1) != 0)? 0 : runIndex;
600                     runs[trailingRun].level = bidi.paraLevel;
601                 }
602             }
603         }
604 
605         /* handle insert LRM/RLM BEFORE/AFTER run */
606         if (bidi.insertPoints.size > 0) {
607             Bidi.Point point;
608             int runIndex, ip;
609             for (ip = 0; ip < bidi.insertPoints.size; ip++) {
610                 point = bidi.insertPoints.points[ip];
611                 runIndex = getRunFromLogicalIndex(bidi, point.pos);
612                 bidi.runs[runIndex].insertRemove |= point.flag;
613             }
614         }
615 
616         /* handle remove BiDi control characters */
617         if (bidi.controlCount > 0) {
618             int runIndex, ic;
619             char c;
620             for (ic = 0; ic < bidi.length; ic++) {
621                 c = bidi.text[ic];
622                 if (Bidi.IsBidiControlChar(c)) {
623                     runIndex = getRunFromLogicalIndex(bidi, ic);
624                     bidi.runs[runIndex].insertRemove--;
625                 }
626             }
627         }
628     }
629 
prepareReorder(byte[] levels, byte[] pMinLevel, byte[] pMaxLevel)630     static int[] prepareReorder(byte[] levels, byte[] pMinLevel, byte[] pMaxLevel)
631     {
632         int start;
633         byte level, minLevel, maxLevel;
634 
635         if (levels == null || levels.length <= 0) {
636             return null;
637         }
638 
639         /* determine minLevel and maxLevel */
640         minLevel = Bidi.MAX_EXPLICIT_LEVEL + 1;
641         maxLevel = 0;
642         for (start = levels.length; start>0; ) {
643             level = levels[--start];
644             if (level < 0) {
645                 return null;
646             }
647            if (level > (Bidi.MAX_EXPLICIT_LEVEL + 1)) {
648                 return null;
649             }
650             if (level < minLevel) {
651                 minLevel = level;
652             }
653             if (level > maxLevel) {
654                 maxLevel = level;
655             }
656         }
657         pMinLevel[0] = minLevel;
658         pMaxLevel[0] = maxLevel;
659 
660         /* initialize the index map */
661         int[] indexMap = new int[levels.length];
662         for (start = levels.length; start > 0; ) {
663             --start;
664             indexMap[start] = start;
665         }
666 
667         return indexMap;
668     }
669 
reorderLogical(byte[] levels)670     static int[] reorderLogical(byte[] levels)
671     {
672         byte[] aMinLevel = new byte[1];
673         byte[] aMaxLevel = new byte[1];
674         int start, limit, sumOfSosEos;
675         byte minLevel, maxLevel;
676         int[] indexMap = prepareReorder(levels, aMinLevel, aMaxLevel);
677         if (indexMap == null) {
678             return null;
679         }
680 
681         minLevel = aMinLevel[0];
682         maxLevel = aMaxLevel[0];
683 
684         /* nothing to do? */
685         if (minLevel == maxLevel && (minLevel & 1) == 0) {
686             return indexMap;
687         }
688 
689         /* reorder only down to the lowest odd level */
690         minLevel |= 1;
691 
692         /* loop maxLevel..minLevel */
693         do {
694             start = 0;
695 
696             /* loop for all sequences of levels to reorder at the current maxLevel */
697             for ( ; ; ) {
698                 /* look for a sequence of levels that are all at >=maxLevel */
699                 /* look for the first index of such a sequence */
700                 while (start < levels.length && levels[start] < maxLevel) {
701                     ++start;
702                 }
703                 if (start >= levels.length) {
704                     break;  /* no more such sequences */
705                 }
706 
707                 /* look for the limit of such a sequence (the index behind it) */
708                 for (limit = start; ++limit < levels.length && levels[limit] >= maxLevel; ) {}
709 
710                 /*
711                  * sos=start of sequence, eos=end of sequence
712                  *
713                  * The closed (inclusive) interval from sos to eos includes all the logical
714                  * and visual indexes within this sequence. They are logically and
715                  * visually contiguous and in the same range.
716                  *
717                  * For each run, the new visual index=sos+eos-old visual index;
718                  * we pre-add sos+eos into sumOfSosEos ->
719                  * new visual index=sumOfSosEos-old visual index;
720                  */
721                 sumOfSosEos = start + limit - 1;
722 
723                 /* reorder each index in the sequence */
724                 do {
725                     indexMap[start] = sumOfSosEos - indexMap[start];
726                 } while (++start < limit);
727 
728                 /* start==limit */
729                 if (limit == levels.length) {
730                     break;  /* no more such sequences */
731                 } else {
732                     start = limit + 1;
733                 }
734             }
735         } while (--maxLevel >= minLevel);
736         return indexMap;
737     }
738 
reorderVisual(byte[] levels)739     static int[] reorderVisual(byte[] levels)
740     {
741         byte[] aMinLevel = new byte[1];
742         byte[] aMaxLevel = new byte[1];
743         int start, end, limit, temp;
744         byte minLevel, maxLevel;
745 
746         int[] indexMap = prepareReorder(levels, aMinLevel, aMaxLevel);
747         if (indexMap == null) {
748             return null;
749         }
750 
751         minLevel = aMinLevel[0];
752         maxLevel = aMaxLevel[0];
753 
754         /* nothing to do? */
755         if (minLevel == maxLevel && (minLevel & 1) == 0) {
756             return indexMap;
757         }
758 
759         /* reorder only down to the lowest odd level */
760         minLevel |= 1;
761 
762         /* loop maxLevel..minLevel */
763         do {
764             start = 0;
765 
766             /* loop for all sequences of levels to reorder at the current maxLevel */
767             for ( ; ; ) {
768                 /* look for a sequence of levels that are all at >=maxLevel */
769                 /* look for the first index of such a sequence */
770                 while (start < levels.length && levels[start] < maxLevel) {
771                     ++start;
772                 }
773                 if (start >= levels.length) {
774                     break;  /* no more such runs */
775                 }
776 
777                 /* look for the limit of such a sequence (the index behind it) */
778                 for (limit = start; ++limit < levels.length && levels[limit] >= maxLevel; ) {}
779 
780                 /*
781                  * Swap the entire interval of indexes from start to limit-1.
782                  * We don't need to swap the levels for the purpose of this
783                  * algorithm: the sequence of levels that we look at does not
784                  * move anyway.
785                  */
786                 end = limit - 1;
787                 while (start < end) {
788                     temp = indexMap[start];
789                     indexMap[start] = indexMap[end];
790                     indexMap[end] = temp;
791 
792                     ++start;
793                     --end;
794                 }
795 
796                 if (limit == levels.length) {
797                     break;  /* no more such sequences */
798                 } else {
799                     start = limit + 1;
800                 }
801             }
802         } while (--maxLevel >= minLevel);
803 
804         return indexMap;
805     }
806 
getVisualIndex(Bidi bidi, int logicalIndex)807     static int getVisualIndex(Bidi bidi, int logicalIndex)
808     {
809         int visualIndex = Bidi.MAP_NOWHERE;
810 
811         /* we can do the trivial cases without the runs array */
812         switch(bidi.direction) {
813         case Bidi.LTR:
814             visualIndex = logicalIndex;
815             break;
816         case Bidi.RTL:
817             visualIndex = bidi.length - logicalIndex - 1;
818             break;
819         default:
820             getRuns(bidi);
821             BidiRun[] runs = bidi.runs;
822             int i, visualStart = 0, offset, length;
823 
824             /* linear search for the run, search on the visual runs */
825             for (i = 0; i < bidi.runCount; ++i) {
826                 length = runs[i].limit - visualStart;
827                 offset = logicalIndex - runs[i].start;
828                 if (offset >= 0 && offset < length) {
829                     if (runs[i].isEvenRun()) {
830                         /* LTR */
831                         visualIndex = visualStart + offset;
832                     } else {
833                         /* RTL */
834                         visualIndex = visualStart + length - offset - 1;
835                     }
836                     break;                  /* exit for loop */
837                 }
838                 visualStart += length;
839             }
840             if (i >= bidi.runCount) {
841                 return Bidi.MAP_NOWHERE;
842             }
843         }
844 
845         if (bidi.insertPoints.size > 0) {
846             /* add the number of added marks until the calculated visual index */
847             BidiRun runs[] = bidi.runs;
848             int i, length, insertRemove;
849             int visualStart = 0, markFound = 0;
850             for (i = 0; ; i++, visualStart += length) {
851                 length = runs[i].limit - visualStart;
852                 insertRemove = runs[i].insertRemove;
853                 if ((insertRemove & (Bidi.LRM_BEFORE|Bidi.RLM_BEFORE)) > 0) {
854                     markFound++;
855                 }
856                 /* is it the run containing the visual index? */
857                 if (visualIndex < runs[i].limit) {
858                     return visualIndex + markFound;
859                 }
860                 if ((insertRemove & (Bidi.LRM_AFTER|Bidi.RLM_AFTER)) > 0) {
861                     markFound++;
862                 }
863             }
864         }
865         else if (bidi.controlCount > 0) {
866             /* subtract the number of controls until the calculated visual index */
867             BidiRun[] runs = bidi.runs;
868             int i, j, start, limit, length, insertRemove;
869             int visualStart = 0, controlFound = 0;
870             char uchar = bidi.text[logicalIndex];
871             /* is the logical index pointing to a control ? */
872             if (Bidi.IsBidiControlChar(uchar)) {
873                 return Bidi.MAP_NOWHERE;
874             }
875             /* loop on runs */
876             for (i = 0; ; i++, visualStart += length) {
877                 length = runs[i].limit - visualStart;
878                 insertRemove = runs[i].insertRemove;
879                 /* calculated visual index is beyond this run? */
880                 if (visualIndex >= runs[i].limit) {
881                     controlFound -= insertRemove;
882                     continue;
883                 }
884                 /* calculated visual index must be within current run */
885                 if (insertRemove == 0) {
886                     return visualIndex - controlFound;
887                 }
888                 if (runs[i].isEvenRun()) {
889                     /* LTR: check from run start to logical index */
890                     start = runs[i].start;
891                     limit = logicalIndex;
892                 } else {
893                     /* RTL: check from logical index to run end */
894                     start = logicalIndex + 1;
895                     limit = runs[i].start + length;
896                 }
897                 for (j = start; j < limit; j++) {
898                     uchar = bidi.text[j];
899                     if (Bidi.IsBidiControlChar(uchar)) {
900                         controlFound++;
901                     }
902                 }
903                 return visualIndex - controlFound;
904             }
905         }
906 
907         return visualIndex;
908     }
909 
getLogicalIndex(Bidi bidi, int visualIndex)910     static int getLogicalIndex(Bidi bidi, int visualIndex)
911     {
912         BidiRun[] runs;
913         int i, runCount, start;
914 
915         runs = bidi.runs;
916         runCount = bidi.runCount;
917         if (bidi.insertPoints.size > 0) {
918             /* handle inserted LRM/RLM */
919             int markFound = 0, insertRemove;
920             int visualStart = 0, length;
921             /* subtract number of marks until visual index */
922             for (i = 0; ; i++, visualStart += length) {
923                 length = runs[i].limit - visualStart;
924                 insertRemove = runs[i].insertRemove;
925                 if ((insertRemove & (Bidi.LRM_BEFORE|Bidi.RLM_BEFORE)) > 0) {
926                     if (visualIndex <= (visualStart+markFound)) {
927                         return Bidi.MAP_NOWHERE;
928                     }
929                     markFound++;
930                 }
931                 /* is adjusted visual index within this run? */
932                 if (visualIndex < (runs[i].limit + markFound)) {
933                     visualIndex -= markFound;
934                     break;
935                 }
936                 if ((insertRemove & (Bidi.LRM_AFTER|Bidi.RLM_AFTER)) > 0) {
937                     if (visualIndex == (visualStart + length + markFound)) {
938                         return Bidi.MAP_NOWHERE;
939                     }
940                     markFound++;
941                 }
942             }
943         }
944         else if (bidi.controlCount > 0) {
945             /* handle removed BiDi control characters */
946             int controlFound = 0, insertRemove, length;
947             int logicalStart, logicalEnd, visualStart = 0, j, k;
948             char uchar;
949             boolean evenRun;
950             /* add number of controls until visual index */
951             for (i = 0; ; i++, visualStart += length) {
952                 length = runs[i].limit - visualStart;
953                 insertRemove = runs[i].insertRemove;
954                 /* is adjusted visual index beyond current run? */
955                 if (visualIndex >= (runs[i].limit - controlFound + insertRemove)) {
956                     controlFound -= insertRemove;
957                     continue;
958                 }
959                 /* adjusted visual index is within current run */
960                 if (insertRemove == 0) {
961                     visualIndex += controlFound;
962                     break;
963                 }
964                 /* count non-control chars until visualIndex */
965                 logicalStart = runs[i].start;
966                 evenRun = runs[i].isEvenRun();
967                 logicalEnd = logicalStart + length - 1;
968                 for (j = 0; j < length; j++) {
969                     k= evenRun ? logicalStart+j : logicalEnd-j;
970                     uchar = bidi.text[k];
971                     if (Bidi.IsBidiControlChar(uchar)) {
972                         controlFound++;
973                     }
974                     if ((visualIndex + controlFound) == (visualStart + j)) {
975                         break;
976                     }
977                 }
978                 visualIndex += controlFound;
979                 break;
980             }
981         }
982         /* handle all cases */
983         if (runCount <= 10) {
984             /* linear search for the run */
985             for (i = 0; visualIndex >= runs[i].limit; ++i) {}
986         } else {
987             /* binary search for the run */
988             int begin = 0, limit = runCount;
989 
990             /* the middle if() is guaranteed to find the run, we don't need a loop limit */
991             for ( ; ; ) {
992                 i = (begin + limit) >>> 1;
993                 if (visualIndex >= runs[i].limit) {
994                     begin = i + 1;
995                 } else if (i==0 || visualIndex >= runs[i-1].limit) {
996                     break;
997                 } else {
998                     limit = i;
999                 }
1000             }
1001         }
1002 
1003         start= runs[i].start;
1004         if (runs[i].isEvenRun()) {
1005             /* LTR */
1006             /* the offset in runs[i] is visualIndex-runs[i-1].visualLimit */
1007             if (i > 0) {
1008                 visualIndex -= runs[i - 1].limit;
1009             }
1010             return start + visualIndex;
1011         } else {
1012             /* RTL */
1013             return start + runs[i].limit - visualIndex - 1;
1014         }
1015     }
1016 
getLogicalMap(Bidi bidi)1017     static int[] getLogicalMap(Bidi bidi)
1018     {
1019         /* fill a logical-to-visual index map using the runs[] */
1020         BidiRun[] runs = bidi.runs;
1021         int logicalStart, visualStart, logicalLimit, visualLimit;
1022         int[] indexMap = new int[bidi.length];
1023         if (bidi.length > bidi.resultLength) {
1024             Arrays.fill(indexMap, Bidi.MAP_NOWHERE);
1025         }
1026 
1027         visualStart = 0;
1028         for (int j = 0; j < bidi.runCount; ++j) {
1029             logicalStart = runs[j].start;
1030             visualLimit = runs[j].limit;
1031             if (runs[j].isEvenRun()) {
1032                 do { /* LTR */
1033                     indexMap[logicalStart++] = visualStart++;
1034                 } while (visualStart < visualLimit);
1035             } else {
1036                 logicalStart += visualLimit - visualStart;  /* logicalLimit */
1037                 do { /* RTL */
1038                     indexMap[--logicalStart] = visualStart++;
1039                 } while (visualStart < visualLimit);
1040             }
1041             /* visualStart==visualLimit; */
1042         }
1043 
1044         if (bidi.insertPoints.size > 0) {
1045             int markFound = 0, runCount = bidi.runCount;
1046             int length, insertRemove, i, j;
1047             runs = bidi.runs;
1048             visualStart = 0;
1049             /* add number of marks found until each index */
1050             for (i = 0; i < runCount; i++, visualStart += length) {
1051                 length = runs[i].limit - visualStart;
1052                 insertRemove = runs[i].insertRemove;
1053                 if ((insertRemove & (Bidi.LRM_BEFORE|Bidi.RLM_BEFORE)) > 0) {
1054                     markFound++;
1055                 }
1056                 if (markFound > 0) {
1057                     logicalStart = runs[i].start;
1058                     logicalLimit = logicalStart + length;
1059                     for (j = logicalStart; j < logicalLimit; j++) {
1060                         indexMap[j] += markFound;
1061                     }
1062                 }
1063                 if ((insertRemove & (Bidi.LRM_AFTER|Bidi.RLM_AFTER)) > 0) {
1064                     markFound++;
1065                 }
1066             }
1067         }
1068         else if (bidi.controlCount > 0) {
1069             int controlFound = 0, runCount = bidi.runCount;
1070             int length, insertRemove, i, j, k;
1071             boolean evenRun;
1072             char uchar;
1073             runs = bidi.runs;
1074             visualStart = 0;
1075             /* subtract number of controls found until each index */
1076             for (i = 0; i < runCount; i++, visualStart += length) {
1077                 length = runs[i].limit - visualStart;
1078                 insertRemove = runs[i].insertRemove;
1079                 /* no control found within previous runs nor within this run */
1080                 if ((controlFound - insertRemove) == 0) {
1081                     continue;
1082                 }
1083                 logicalStart = runs[i].start;
1084                 evenRun = runs[i].isEvenRun();
1085                 logicalLimit = logicalStart + length;
1086                 /* if no control within this run */
1087                 if (insertRemove == 0) {
1088                     for (j = logicalStart; j < logicalLimit; j++) {
1089                         indexMap[j] -= controlFound;
1090                     }
1091                     continue;
1092                 }
1093                 for (j = 0; j < length; j++) {
1094                     k = evenRun ? logicalStart + j : logicalLimit - j - 1;
1095                     uchar = bidi.text[k];
1096                     if (Bidi.IsBidiControlChar(uchar)) {
1097                         controlFound++;
1098                         indexMap[k] = Bidi.MAP_NOWHERE;
1099                         continue;
1100                     }
1101                     indexMap[k] -= controlFound;
1102                 }
1103             }
1104         }
1105         return indexMap;
1106     }
1107 
getVisualMap(Bidi bidi)1108     static int[] getVisualMap(Bidi bidi)
1109     {
1110         /* fill a visual-to-logical index map using the runs[] */
1111         BidiRun[] runs = bidi.runs;
1112         int logicalStart, visualStart, visualLimit;
1113         int allocLength = bidi.length > bidi.resultLength ? bidi.length
1114                                                           : bidi.resultLength;
1115         int[] indexMap = new int[allocLength];
1116 
1117         visualStart = 0;
1118         int idx = 0;
1119         for (int j = 0; j < bidi.runCount; ++j) {
1120             logicalStart = runs[j].start;
1121             visualLimit = runs[j].limit;
1122             if (runs[j].isEvenRun()) {
1123                 do { /* LTR */
1124                     indexMap[idx++] = logicalStart++;
1125                 } while (++visualStart < visualLimit);
1126             } else {
1127                 logicalStart += visualLimit - visualStart;  /* logicalLimit */
1128                 do { /* RTL */
1129                     indexMap[idx++] = --logicalStart;
1130                 } while (++visualStart < visualLimit);
1131             }
1132             /* visualStart==visualLimit; */
1133         }
1134 
1135         if (bidi.insertPoints.size > 0) {
1136             int markFound = 0, runCount = bidi.runCount;
1137             int insertRemove, i, j, k;
1138             runs = bidi.runs;
1139             /* count all inserted marks */
1140             for (i = 0; i < runCount; i++) {
1141                 insertRemove = runs[i].insertRemove;
1142                 if ((insertRemove & (Bidi.LRM_BEFORE|Bidi.RLM_BEFORE)) > 0) {
1143                     markFound++;
1144                 }
1145                 if ((insertRemove & (Bidi.LRM_AFTER|Bidi.RLM_AFTER)) > 0) {
1146                     markFound++;
1147                 }
1148             }
1149             /* move back indexes by number of preceding marks */
1150             k = bidi.resultLength;
1151             for (i = runCount - 1; i >= 0 && markFound > 0; i--) {
1152                 insertRemove = runs[i].insertRemove;
1153                 if ((insertRemove & (Bidi.LRM_AFTER|Bidi.RLM_AFTER)) > 0) {
1154                     indexMap[--k] = Bidi.MAP_NOWHERE;
1155                     markFound--;
1156                 }
1157                 visualStart = i > 0 ? runs[i-1].limit : 0;
1158                 for (j = runs[i].limit - 1; j >= visualStart && markFound > 0; j--) {
1159                     indexMap[--k] = indexMap[j];
1160                 }
1161                 if ((insertRemove & (Bidi.LRM_BEFORE|Bidi.RLM_BEFORE)) > 0) {
1162                     indexMap[--k] = Bidi.MAP_NOWHERE;
1163                     markFound--;
1164                 }
1165             }
1166         }
1167         else if (bidi.controlCount > 0) {
1168             int runCount = bidi.runCount, logicalEnd;
1169             int insertRemove, length, i, j, k, m;
1170             char uchar;
1171             boolean evenRun;
1172             runs = bidi.runs;
1173             visualStart = 0;
1174             /* move forward indexes by number of preceding controls */
1175             k = 0;
1176             for (i = 0; i < runCount; i++, visualStart += length) {
1177                 length = runs[i].limit - visualStart;
1178                 insertRemove = runs[i].insertRemove;
1179                 /* if no control found yet, nothing to do in this run */
1180                 if ((insertRemove == 0) && (k == visualStart)) {
1181                     k += length;
1182                     continue;
1183                 }
1184                 /* if no control in this run */
1185                 if (insertRemove == 0) {
1186                     visualLimit = runs[i].limit;
1187                     for (j = visualStart; j < visualLimit; j++) {
1188                         indexMap[k++] = indexMap[j];
1189                     }
1190                     continue;
1191                 }
1192                 logicalStart = runs[i].start;
1193                 evenRun = runs[i].isEvenRun();
1194                 logicalEnd = logicalStart + length - 1;
1195                 for (j = 0; j < length; j++) {
1196                     m = evenRun ? logicalStart + j : logicalEnd - j;
1197                     uchar = bidi.text[m];
1198                     if (!Bidi.IsBidiControlChar(uchar)) {
1199                         indexMap[k++] = m;
1200                     }
1201                 }
1202             }
1203         }
1204         if (allocLength == bidi.resultLength) {
1205             return indexMap;
1206         }
1207         int[] newMap = new int[bidi.resultLength];
1208         System.arraycopy(indexMap, 0, newMap, 0, bidi.resultLength);
1209         return newMap;
1210     }
1211 
invertMap(int[] srcMap)1212     static int[] invertMap(int[] srcMap)
1213     {
1214         int srcLength = srcMap.length;
1215         int destLength = -1, count = 0, i, srcEntry;
1216 
1217         /* find highest value and count positive indexes in srcMap */
1218         for (i = 0; i < srcLength; i++) {
1219             srcEntry = srcMap[i];
1220             if (srcEntry > destLength) {
1221                 destLength = srcEntry;
1222             }
1223             if (srcEntry >= 0) {
1224                 count++;
1225             }
1226         }
1227         destLength++;           /* add 1 for origin 0 */
1228         int[] destMap = new int[destLength];
1229         if (count < destLength) {
1230             /* we must fill unmatched destMap entries with -1 */
1231             Arrays.fill(destMap, Bidi.MAP_NOWHERE);
1232         }
1233         for (i = 0; i < srcLength; i++) {
1234             srcEntry = srcMap[i];
1235             if (srcEntry >= 0) {
1236                 destMap[srcEntry] = i;
1237             }
1238         }
1239         return destMap;
1240     }
1241 }
1242