• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* GENERATED SOURCE. DO NOT MODIFY. */
2 // © 2016 and later: Unicode, Inc. and others.
3 // License & terms of use: http://www.unicode.org/copyright.html#License
4 /*
5 *******************************************************************************
6 *   Copyright (C) 2001-2010, International Business Machines
7 *   Corporation and others.  All Rights Reserved.
8 *******************************************************************************
9 */
10 /* Written by Simon Montagu, Matitiahu Allouche
11  * (ported from C code written by Markus W. Scherer)
12  */
13 
14 package android.icu.text;
15 
16 import android.icu.lang.UCharacter;
17 
18 final class BidiWriter {
19 
20     /** Bidi control code points */
21     static final char LRM_CHAR = 0x200e;
22     static final char RLM_CHAR = 0x200f;
23     static final int MASK_R_AL = (1 << UCharacter.RIGHT_TO_LEFT |
24                                   1 << UCharacter.RIGHT_TO_LEFT_ARABIC);
25 
IsCombining(int type)26     private static boolean IsCombining(int type)
27     {
28         return ((1<<type &
29                 (1<<UCharacter.NON_SPACING_MARK |
30                  1<<UCharacter.COMBINING_SPACING_MARK |
31                  1<<UCharacter.ENCLOSING_MARK)) != 0);
32     }
33 
34     /*
35      * When we have OUTPUT_REVERSE set on writeReordered(), then we
36      * semantically write RTL runs in reverse and later reverse them again.
37      * Instead, we actually write them in forward order to begin with.
38      * However, if the RTL run was to be mirrored, we need to mirror here now
39      * since the implicit second reversal must not do it.
40      * It looks strange to do mirroring in LTR output, but it is only because
41      * we are writing RTL output in reverse.
42      */
doWriteForward(String src, int options)43     private static String doWriteForward(String src, int options) {
44         /* optimize for several combinations of options */
45         switch(options&(Bidi.REMOVE_BIDI_CONTROLS|Bidi.DO_MIRRORING)) {
46         case 0: {
47             /* simply return the LTR run */
48             return src;
49         }
50         case Bidi.DO_MIRRORING: {
51             StringBuffer dest = new StringBuffer(src.length());
52 
53             /* do mirroring */
54             int i=0;
55             int c;
56 
57             do {
58                 c = UTF16.charAt(src, i);
59                 i += UTF16.getCharCount(c);
60                 UTF16.append(dest, UCharacter.getMirror(c));
61             } while(i < src.length());
62             return dest.toString();
63         }
64         case Bidi.REMOVE_BIDI_CONTROLS: {
65             StringBuilder dest = new StringBuilder(src.length());
66 
67             /* copy the LTR run and remove any Bidi control characters */
68             int i = 0;
69             char c;
70             do {
71                 c = src.charAt(i++);
72                 if(!Bidi.IsBidiControlChar(c)) {
73                     dest.append(c);
74                 }
75             } while(i < src.length());
76             return dest.toString();
77         }
78         default: {
79             StringBuffer dest = new StringBuffer(src.length());
80 
81             /* remove Bidi control characters and do mirroring */
82             int i = 0;
83             int c;
84             do {
85                 c = UTF16.charAt(src, i);
86                 i += UTF16.getCharCount(c);
87                 if(!Bidi.IsBidiControlChar(c)) {
88                     UTF16.append(dest, UCharacter.getMirror(c));
89                 }
90             } while(i < src.length());
91             return dest.toString();
92         }
93         } /* end of switch */
94     }
95 
doWriteForward(char[] text, int start, int limit, int options)96     private static String doWriteForward(char[] text, int start, int limit,
97                                          int options)
98     {
99         return doWriteForward(new String(text, start, limit - start), options);
100     }
101 
writeReverse(String src, int options)102     static String writeReverse(String src, int options) {
103         /*
104          * RTL run -
105          *
106          * RTL runs need to be copied to the destination in reverse order
107          * of code points, not code units, to keep Unicode characters intact.
108          *
109          * The general strategy for this is to read the source text
110          * in backward order, collect all code units for a code point
111          * (and optionally following combining characters, see below),
112          * and copy all these code units in ascending order
113          * to the destination for this run.
114          *
115          * Several options request whether combining characters
116          * should be kept after their base characters,
117          * whether Bidi control characters should be removed, and
118          * whether characters should be replaced by their mirror-image
119          * equivalent Unicode characters.
120          */
121         StringBuffer dest = new StringBuffer(src.length());
122 
123         /* optimize for several combinations of options */
124         switch (options &
125                 (Bidi.REMOVE_BIDI_CONTROLS |
126                  Bidi.DO_MIRRORING |
127                  Bidi.KEEP_BASE_COMBINING)) {
128 
129         case 0:
130             /*
131              * With none of the "complicated" options set, the destination
132              * run will have the same length as the source run,
133              * and there is no mirroring and no keeping combining characters
134              * with their base characters.
135              *
136              * XXX: or dest = UTF16.reverse(new StringBuffer(src));
137              */
138 
139             int srcLength = src.length();
140 
141             /* preserve character integrity */
142             do {
143                 /* i is always after the last code unit known to need to be kept
144                  *  in this segment */
145                 int i = srcLength;
146 
147                 /* collect code units for one base character */
148                 srcLength -= UTF16.getCharCount(UTF16.charAt(src,
149                                                              srcLength - 1));
150 
151                 /* copy this base character */
152                 dest.append(src.substring(srcLength, i));
153             } while(srcLength > 0);
154             break;
155 
156         case Bidi.KEEP_BASE_COMBINING:
157             /*
158              * Here, too, the destination
159              * run will have the same length as the source run,
160              * and there is no mirroring.
161              * We do need to keep combining characters with their base
162              * characters.
163              */
164             srcLength = src.length();
165 
166             /* preserve character integrity */
167             do {
168                 /* i is always after the last code unit known to need to be kept
169                  *  in this segment */
170                 int c;
171                 int i = srcLength;
172 
173                 /* collect code units and modifier letters for one base
174                  * character */
175                 do {
176                     c = UTF16.charAt(src, srcLength - 1);
177                     srcLength -= UTF16.getCharCount(c);
178                 } while(srcLength > 0 && IsCombining(UCharacter.getType(c)));
179 
180                 /* copy this "user character" */
181                 dest.append(src.substring(srcLength, i));
182             } while(srcLength > 0);
183             break;
184 
185         default:
186             /*
187              * With several "complicated" options set, this is the most
188              * general and the slowest copying of an RTL run.
189              * We will do mirroring, remove Bidi controls, and
190              * keep combining characters with their base characters
191              * as requested.
192              */
193             srcLength = src.length();
194 
195             /* preserve character integrity */
196             do {
197                 /* i is always after the last code unit known to need to be kept
198                  *  in this segment */
199                 int i = srcLength;
200 
201                 /* collect code units for one base character */
202                 int c = UTF16.charAt(src, srcLength - 1);
203                 srcLength -= UTF16.getCharCount(c);
204                 if ((options & Bidi.KEEP_BASE_COMBINING) != 0) {
205                     /* collect modifier letters for this base character */
206                     while(srcLength > 0 && IsCombining(UCharacter.getType(c))) {
207                         c = UTF16.charAt(src, srcLength - 1);
208                         srcLength -= UTF16.getCharCount(c);
209                     }
210                 }
211 
212                 if ((options & Bidi.REMOVE_BIDI_CONTROLS) != 0 &&
213                     Bidi.IsBidiControlChar(c)) {
214                     /* do not copy this Bidi control character */
215                     continue;
216                 }
217 
218                 /* copy this "user character" */
219                 int j = srcLength;
220                 if((options & Bidi.DO_MIRRORING) != 0) {
221                     /* mirror only the base character */
222                     c = UCharacter.getMirror(c);
223                     UTF16.append(dest, c);
224                     j += UTF16.getCharCount(c);
225                 }
226                 dest.append(src.substring(j, i));
227             } while(srcLength > 0);
228             break;
229         } /* end of switch */
230 
231         return dest.toString();
232     }
233 
doWriteReverse(char[] text, int start, int limit, int options)234     static String doWriteReverse(char[] text, int start, int limit, int options)
235     {
236         return writeReverse(new String(text, start, limit - start), options);
237     }
238 
writeReordered(Bidi bidi, int options)239     static String writeReordered(Bidi bidi, int options)
240     {
241         int run, runCount;
242         StringBuilder dest;
243         char[] text = bidi.text;
244         runCount = bidi.countRuns();
245 
246         /*
247          * Option "insert marks" implies Bidi.INSERT_LRM_FOR_NUMERIC if the
248          * reordering mode (checked below) is appropriate.
249          */
250         if ((bidi.reorderingOptions & Bidi.OPTION_INSERT_MARKS) != 0) {
251             options |= Bidi.INSERT_LRM_FOR_NUMERIC;
252             options &= ~Bidi.REMOVE_BIDI_CONTROLS;
253         }
254         /*
255          * Option "remove controls" implies Bidi.REMOVE_BIDI_CONTROLS
256          * and cancels Bidi.INSERT_LRM_FOR_NUMERIC.
257          */
258         if ((bidi.reorderingOptions & Bidi.OPTION_REMOVE_CONTROLS) != 0) {
259             options |= Bidi.REMOVE_BIDI_CONTROLS;
260             options &= ~Bidi.INSERT_LRM_FOR_NUMERIC;
261         }
262         /*
263          * If we do not perform the "inverse Bidi" algorithm, then we
264          * don't need to insert any LRMs, and don't need to test for it.
265          */
266         if ((bidi.reorderingMode != Bidi.REORDER_INVERSE_NUMBERS_AS_L) &&
267             (bidi.reorderingMode != Bidi.REORDER_INVERSE_LIKE_DIRECT)  &&
268             (bidi.reorderingMode != Bidi.REORDER_INVERSE_FOR_NUMBERS_SPECIAL) &&
269             (bidi.reorderingMode != Bidi.REORDER_RUNS_ONLY)) {
270             options &= ~Bidi.INSERT_LRM_FOR_NUMERIC;
271         }
272         dest = new StringBuilder((options & Bidi.INSERT_LRM_FOR_NUMERIC) != 0 ?
273                                  bidi.length * 2 : bidi.length);
274         /*
275          * Iterate through all visual runs and copy the run text segments to
276          * the destination, according to the options.
277          *
278          * The tests for where to insert LRMs ignore the fact that there may be
279          * BN codes or non-BMP code points at the beginning and end of a run;
280          * they may insert LRMs unnecessarily but the tests are faster this way
281          * (this would have to be improved for UTF-8).
282          */
283         if ((options & Bidi.OUTPUT_REVERSE) == 0) {
284             /* forward output */
285             if ((options & Bidi.INSERT_LRM_FOR_NUMERIC) == 0) {
286                 /* do not insert Bidi controls */
287                 for (run = 0; run < runCount; ++run) {
288                     BidiRun bidiRun = bidi.getVisualRun(run);
289                     if (bidiRun.isEvenRun()) {
290                         dest.append(doWriteForward(text, bidiRun.start,
291                                                    bidiRun.limit,
292                                                    options & ~Bidi.DO_MIRRORING));
293                      } else {
294                         dest.append(doWriteReverse(text, bidiRun.start,
295                                                    bidiRun.limit, options));
296                      }
297                 }
298             } else {
299                 /* insert Bidi controls for "inverse Bidi" */
300                 byte[] dirProps = bidi.dirProps;
301                 char uc;
302                 int markFlag;
303 
304                 for (run = 0; run < runCount; ++run) {
305                     BidiRun bidiRun = bidi.getVisualRun(run);
306                     markFlag=0;
307                     /* check if something relevant in insertPoints */
308                     markFlag = bidi.runs[run].insertRemove;
309                     if (markFlag < 0) { /* bidi controls count */
310                         markFlag = 0;
311                     }
312                     if (bidiRun.isEvenRun()) {
313                         if (bidi.isInverse() &&
314                                 dirProps[bidiRun.start] != Bidi.L) {
315                             markFlag |= Bidi.LRM_BEFORE;
316                         }
317                         if ((markFlag & Bidi.LRM_BEFORE) != 0) {
318                             uc = LRM_CHAR;
319                         } else if ((markFlag & Bidi.RLM_BEFORE) != 0) {
320                             uc = RLM_CHAR;
321                         } else {
322                             uc = 0;
323                         }
324                         if (uc != 0) {
325                             dest.append(uc);
326                         }
327                         dest.append(doWriteForward(text,
328                                                    bidiRun.start, bidiRun.limit,
329                                                    options & ~Bidi.DO_MIRRORING));
330 
331                         if (bidi.isInverse() &&
332                              dirProps[bidiRun.limit - 1] != Bidi.L) {
333                             markFlag |= Bidi.LRM_AFTER;
334                         }
335                         if ((markFlag & Bidi.LRM_AFTER) != 0) {
336                             uc = LRM_CHAR;
337                         } else if ((markFlag & Bidi.RLM_AFTER) != 0) {
338                             uc = RLM_CHAR;
339                         } else {
340                             uc = 0;
341                         }
342                         if (uc != 0) {
343                             dest.append(uc);
344                         }
345                     } else { /* RTL run */
346                         if (bidi.isInverse() &&
347                             !bidi.testDirPropFlagAt(MASK_R_AL,
348                                                     bidiRun.limit - 1)) {
349                             markFlag |= Bidi.RLM_BEFORE;
350                         }
351                         if ((markFlag & Bidi.LRM_BEFORE) != 0) {
352                             uc = LRM_CHAR;
353                         } else if ((markFlag & Bidi.RLM_BEFORE) != 0) {
354                             uc = RLM_CHAR;
355                         } else {
356                             uc = 0;
357                         }
358                         if (uc != 0) {
359                             dest.append(uc);
360                         }
361                         dest.append(doWriteReverse(text, bidiRun.start,
362                                                    bidiRun.limit, options));
363 
364                         if(bidi.isInverse() &&
365                                 (MASK_R_AL & Bidi.DirPropFlag(dirProps[bidiRun.start])) == 0) {
366                             markFlag |= Bidi.RLM_AFTER;
367                         }
368                         if ((markFlag & Bidi.LRM_AFTER) != 0) {
369                             uc = LRM_CHAR;
370                         } else if ((markFlag & Bidi.RLM_AFTER) != 0) {
371                             uc = RLM_CHAR;
372                         } else {
373                             uc = 0;
374                         }
375                         if (uc != 0) {
376                             dest.append(uc);
377                         }
378                     }
379                 }
380             }
381         } else {
382             /* reverse output */
383             if((options & Bidi.INSERT_LRM_FOR_NUMERIC) == 0) {
384                 /* do not insert Bidi controls */
385                 for(run = runCount; --run >= 0; ) {
386                     BidiRun bidiRun = bidi.getVisualRun(run);
387                     if (bidiRun.isEvenRun()) {
388                         dest.append(doWriteReverse(text,
389                                                    bidiRun.start, bidiRun.limit,
390                                                    options & ~Bidi.DO_MIRRORING));
391                     } else {
392                         dest.append(doWriteForward(text, bidiRun.start,
393                                                    bidiRun.limit, options));
394                     }
395                 }
396             } else {
397                 /* insert Bidi controls for "inverse Bidi" */
398 
399                 byte[] dirProps = bidi.dirProps;
400 
401                 for (run = runCount; --run >= 0; ) {
402                     /* reverse output */
403                     BidiRun bidiRun = bidi.getVisualRun(run);
404                     if (bidiRun.isEvenRun()) {
405                         if (dirProps[bidiRun.limit - 1] != Bidi.L) {
406                             dest.append(LRM_CHAR);
407                         }
408 
409                         dest.append(doWriteReverse(text, bidiRun.start,
410                                 bidiRun.limit, options & ~Bidi.DO_MIRRORING));
411 
412                         if (dirProps[bidiRun.start] != Bidi.L) {
413                             dest.append(LRM_CHAR);
414                         }
415                     } else {
416                         if ((MASK_R_AL & Bidi.DirPropFlag(dirProps[bidiRun.start])) == 0) {
417                             dest.append(RLM_CHAR);
418                         }
419 
420                         dest.append(doWriteForward(text, bidiRun.start,
421                                                    bidiRun.limit, options));
422 
423                         if ((MASK_R_AL & Bidi.DirPropFlag(dirProps[bidiRun.limit - 1])) == 0) {
424                             dest.append(RLM_CHAR);
425                         }
426                     }
427                 }
428             }
429         }
430 
431         return dest.toString();
432     }
433 }
434