1 /* 2 * Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package java.awt.font; 27 28 import java.io.IOException; 29 import java.io.ObjectOutputStream; 30 import java.util.Arrays; 31 import java.util.Comparator; 32 import java.util.EnumSet; 33 import java.util.Set; 34 import jdk.internal.misc.SharedSecrets; 35 36 /** 37 * The {@code NumericShaper} class is used to convert Latin-1 (European) 38 * digits to other Unicode decimal digits. Users of this class will 39 * primarily be people who wish to present data using 40 * national digit shapes, but find it more convenient to represent the 41 * data internally using Latin-1 (European) digits. This does not 42 * interpret the deprecated numeric shape selector character (U+206E). 43 * <p> 44 * Instances of {@code NumericShaper} are typically applied 45 * as attributes to text with the 46 * {@link TextAttribute#NUMERIC_SHAPING NUMERIC_SHAPING} attribute 47 * of the {@code TextAttribute} class. 48 * For example, this code snippet causes a {@code TextLayout} to 49 * shape European digits to Arabic in an Arabic context:<br> 50 * <blockquote><pre> 51 * Map map = new HashMap(); 52 * map.put(TextAttribute.NUMERIC_SHAPING, 53 * NumericShaper.getContextualShaper(NumericShaper.ARABIC)); 54 * FontRenderContext frc = ...; 55 * TextLayout layout = new TextLayout(text, map, frc); 56 * layout.draw(g2d, x, y); 57 * </pre></blockquote> 58 * <br> 59 * It is also possible to perform numeric shaping explicitly using instances 60 * of {@code NumericShaper}, as this code snippet demonstrates:<br> 61 * <blockquote><pre> 62 * char[] text = ...; 63 * // shape all EUROPEAN digits (except zero) to ARABIC digits 64 * NumericShaper shaper = NumericShaper.getShaper(NumericShaper.ARABIC); 65 * shaper.shape(text, start, count); 66 * 67 * // shape European digits to ARABIC digits if preceding text is Arabic, or 68 * // shape European digits to TAMIL digits if preceding text is Tamil, or 69 * // leave European digits alone if there is no preceding text, or 70 * // preceding text is neither Arabic nor Tamil 71 * NumericShaper shaper = 72 * NumericShaper.getContextualShaper(NumericShaper.ARABIC | 73 * NumericShaper.TAMIL, 74 * NumericShaper.EUROPEAN); 75 * shaper.shape(text, start, count); 76 * </pre></blockquote> 77 * 78 * <p><b>Bit mask- and enum-based Unicode ranges</b></p> 79 * 80 * <p>This class supports two different programming interfaces to 81 * represent Unicode ranges for script-specific digits: bit 82 * mask-based ones, such as {@link #ARABIC NumericShaper.ARABIC}, and 83 * enum-based ones, such as {@link NumericShaper.Range#ARABIC}. 84 * Multiple ranges can be specified by ORing bit mask-based constants, 85 * such as: 86 * <blockquote><pre> 87 * NumericShaper.ARABIC | NumericShaper.TAMIL 88 * </pre></blockquote> 89 * or creating a {@code Set} with the {@link NumericShaper.Range} 90 * constants, such as: 91 * <blockquote><pre> 92 * EnumSet.of(NumericShaper.Range.ARABIC, NumericShaper.Range.TAMIL) 93 * </pre></blockquote> 94 * The enum-based ranges are a super set of the bit mask-based ones. 95 * 96 * <p>If the two interfaces are mixed (including serialization), 97 * Unicode range values are mapped to their counterparts where such 98 * mapping is possible, such as {@code NumericShaper.Range.ARABIC} 99 * from/to {@code NumericShaper.ARABIC}. If any unmappable range 100 * values are specified, such as {@code NumericShaper.Range.BALINESE}, 101 * those ranges are ignored. 102 * 103 * <p><b>Decimal Digits Precedence</b></p> 104 * 105 * <p>A Unicode range may have more than one set of decimal digits. If 106 * multiple decimal digits sets are specified for the same Unicode 107 * range, one of the sets will take precedence as follows. 108 * 109 * <table class="plain"> 110 * <caption style="display:none">NumericShaper constants precedence</caption> 111 * <thead> 112 * <tr> 113 * <th class="TableHeadingColor">Unicode Range</th> 114 * <th class="TableHeadingColor">{@code NumericShaper} Constants</th> 115 * <th class="TableHeadingColor">Precedence</th> 116 * </tr> 117 * </thead> 118 * <tbody> 119 * <tr> 120 * <td rowspan="2">Arabic</td> 121 * <td>{@link NumericShaper#ARABIC NumericShaper.ARABIC}<br> 122 * {@link NumericShaper#EASTERN_ARABIC NumericShaper.EASTERN_ARABIC}</td> 123 * <td>{@link NumericShaper#EASTERN_ARABIC NumericShaper.EASTERN_ARABIC}</td> 124 * </tr> 125 * <tr> 126 * <td>{@link NumericShaper.Range#ARABIC}<br> 127 * {@link NumericShaper.Range#EASTERN_ARABIC}</td> 128 * <td>{@link NumericShaper.Range#EASTERN_ARABIC}</td> 129 * </tr> 130 * </tbody> 131 * <tbody> 132 * <tr> 133 * <td>Tai Tham</td> 134 * <td>{@link NumericShaper.Range#TAI_THAM_HORA}<br> 135 * {@link NumericShaper.Range#TAI_THAM_THAM}</td> 136 * <td>{@link NumericShaper.Range#TAI_THAM_THAM}</td> 137 * </tr> 138 * </tbody> 139 * </table> 140 * 141 * @since 1.4 142 */ 143 144 public final class NumericShaper implements java.io.Serializable { 145 146 // For access from java.text.Bidi 147 static { 148 if (SharedSecrets.getJavaAWTFontAccess() == null) { 149 SharedSecrets.setJavaAWTFontAccess(new JavaAWTFontAccessImpl()); 150 } 151 } 152 153 /** 154 * A {@code NumericShaper.Range} represents a Unicode range of a 155 * script having its own decimal digits. For example, the {@link 156 * NumericShaper.Range#THAI} range has the Thai digits, THAI DIGIT 157 * ZERO (U+0E50) to THAI DIGIT NINE (U+0E59). 158 * 159 * <p>The {@code Range} enum replaces the traditional bit 160 * mask-based values (e.g., {@link NumericShaper#ARABIC}), and 161 * supports more Unicode ranges than the bit mask-based ones. For 162 * example, the following code using the bit mask: 163 * <blockquote><pre> 164 * NumericShaper.getContextualShaper(NumericShaper.ARABIC | 165 * NumericShaper.TAMIL, 166 * NumericShaper.EUROPEAN); 167 * </pre></blockquote> 168 * can be written using this enum as: 169 * <blockquote><pre> 170 * NumericShaper.getContextualShaper(EnumSet.of( 171 * NumericShaper.Range.ARABIC, 172 * NumericShaper.Range.TAMIL), 173 * NumericShaper.Range.EUROPEAN); 174 * </pre></blockquote> 175 * 176 * @since 1.7 177 */ 178 public static enum Range { 179 // The order of EUROPEAN to MOGOLIAN must be consistent 180 // with the bitmask-based constants. 181 /** 182 * The Latin (European) range with the Latin (ASCII) digits. 183 */ 184 EUROPEAN ('\u0030', '\u0000', '\u0300'), 185 /** 186 * The Arabic range with the Arabic-Indic digits. 187 */ 188 ARABIC ('\u0660', '\u0600', '\u0780'), 189 /** 190 * The Arabic range with the Eastern Arabic-Indic digits. 191 */ 192 EASTERN_ARABIC ('\u06f0', '\u0600', '\u0780'), 193 /** 194 * The Devanagari range with the Devanagari digits. 195 */ 196 DEVANAGARI ('\u0966', '\u0900', '\u0980'), 197 /** 198 * The Bengali range with the Bengali digits. 199 */ 200 BENGALI ('\u09e6', '\u0980', '\u0a00'), 201 /** 202 * The Gurmukhi range with the Gurmukhi digits. 203 */ 204 GURMUKHI ('\u0a66', '\u0a00', '\u0a80'), 205 /** 206 * The Gujarati range with the Gujarati digits. 207 */ 208 GUJARATI ('\u0ae6', '\u0b00', '\u0b80'), 209 /** 210 * The Oriya range with the Oriya digits. 211 */ 212 ORIYA ('\u0b66', '\u0b00', '\u0b80'), 213 /** 214 * The Tamil range with the Tamil digits. 215 */ 216 TAMIL ('\u0be6', '\u0b80', '\u0c00'), 217 /** 218 * The Telugu range with the Telugu digits. 219 */ 220 TELUGU ('\u0c66', '\u0c00', '\u0c80'), 221 /** 222 * The Kannada range with the Kannada digits. 223 */ 224 KANNADA ('\u0ce6', '\u0c80', '\u0d00'), 225 /** 226 * The Malayalam range with the Malayalam digits. 227 */ 228 MALAYALAM ('\u0d66', '\u0d00', '\u0d80'), 229 /** 230 * The Thai range with the Thai digits. 231 */ 232 THAI ('\u0e50', '\u0e00', '\u0e80'), 233 /** 234 * The Lao range with the Lao digits. 235 */ 236 LAO ('\u0ed0', '\u0e80', '\u0f00'), 237 /** 238 * The Tibetan range with the Tibetan digits. 239 */ 240 TIBETAN ('\u0f20', '\u0f00', '\u1000'), 241 /** 242 * The Myanmar range with the Myanmar digits. 243 */ 244 MYANMAR ('\u1040', '\u1000', '\u1080'), 245 /** 246 * The Ethiopic range with the Ethiopic digits. Ethiopic 247 * does not have a decimal digit 0 so Latin (European) 0 is 248 * used. 249 */ 250 ETHIOPIC ('\u1369', '\u1200', '\u1380') { 251 @Override 252 char getNumericBase() { return 1; } 253 }, 254 /** 255 * The Khmer range with the Khmer digits. 256 */ 257 KHMER ('\u17e0', '\u1780', '\u1800'), 258 /** 259 * The Mongolian range with the Mongolian digits. 260 */ 261 MONGOLIAN ('\u1810', '\u1800', '\u1900'), 262 // The order of EUROPEAN to MOGOLIAN must be consistent 263 // with the bitmask-based constants. 264 265 /** 266 * The N'Ko range with the N'Ko digits. 267 */ 268 NKO ('\u07c0', '\u07c0', '\u0800'), 269 /** 270 * The Myanmar range with the Myanmar Shan digits. 271 */ 272 MYANMAR_SHAN ('\u1090', '\u1000', '\u10a0'), 273 /** 274 * The Limbu range with the Limbu digits. 275 */ 276 LIMBU ('\u1946', '\u1900', '\u1950'), 277 /** 278 * The New Tai Lue range with the New Tai Lue digits. 279 */ 280 NEW_TAI_LUE ('\u19d0', '\u1980', '\u19e0'), 281 /** 282 * The Balinese range with the Balinese digits. 283 */ 284 BALINESE ('\u1b50', '\u1b00', '\u1b80'), 285 /** 286 * The Sundanese range with the Sundanese digits. 287 */ 288 SUNDANESE ('\u1bb0', '\u1b80', '\u1bc0'), 289 /** 290 * The Lepcha range with the Lepcha digits. 291 */ 292 LEPCHA ('\u1c40', '\u1c00', '\u1c50'), 293 /** 294 * The Ol Chiki range with the Ol Chiki digits. 295 */ 296 OL_CHIKI ('\u1c50', '\u1c50', '\u1c80'), 297 /** 298 * The Vai range with the Vai digits. 299 */ 300 VAI ('\ua620', '\ua500', '\ua640'), 301 /** 302 * The Saurashtra range with the Saurashtra digits. 303 */ 304 SAURASHTRA ('\ua8d0', '\ua880', '\ua8e0'), 305 /** 306 * The Kayah Li range with the Kayah Li digits. 307 */ 308 KAYAH_LI ('\ua900', '\ua900', '\ua930'), 309 /** 310 * The Cham range with the Cham digits. 311 */ 312 CHAM ('\uaa50', '\uaa00', '\uaa60'), 313 /** 314 * The Tai Tham Hora range with the Tai Tham Hora digits. 315 */ 316 TAI_THAM_HORA ('\u1a80', '\u1a20', '\u1ab0'), 317 /** 318 * The Tai Tham Tham range with the Tai Tham Tham digits. 319 */ 320 TAI_THAM_THAM ('\u1a90', '\u1a20', '\u1ab0'), 321 /** 322 * The Javanese range with the Javanese digits. 323 */ 324 JAVANESE ('\ua9d0', '\ua980', '\ua9e0'), 325 /** 326 * The Meetei Mayek range with the Meetei Mayek digits. 327 */ 328 MEETEI_MAYEK ('\uabf0', '\uabc0', '\uac00'), 329 /** 330 * The Sinhala range with the Sinhala digits. 331 * @since 9 332 */ 333 SINHALA ('\u0de6', '\u0d80', '\u0e00'), 334 /** 335 * The Myanmar Extended-B range with the Myanmar Tai Laing digits. 336 * @since 9 337 */ 338 MYANMAR_TAI_LAING ('\ua9f0', '\ua9e0', '\uaa00'); 339 340 private static int toRangeIndex(Range script) { 341 int index = script.ordinal(); 342 return index < NUM_KEYS ? index : -1; 343 } 344 345 private static Range indexToRange(int index) { 346 return index < NUM_KEYS ? Range.values()[index] : null; 347 } 348 349 private static int toRangeMask(Set<Range> ranges) { 350 int m = 0; 351 for (Range range : ranges) { 352 int index = range.ordinal(); 353 if (index < NUM_KEYS) { 354 m |= 1 << index; 355 } 356 } 357 return m; 358 } 359 360 private static Set<Range> maskToRangeSet(int mask) { 361 Set<Range> set = EnumSet.noneOf(Range.class); 362 Range[] a = Range.values(); 363 for (int i = 0; i < NUM_KEYS; i++) { 364 if ((mask & (1 << i)) != 0) { 365 set.add(a[i]); 366 } 367 } 368 return set; 369 } 370 371 // base character of range digits 372 private final int base; 373 // Unicode range 374 private final int start, // inclusive 375 end; // exclusive 376 377 private Range(int base, int start, int end) { 378 this.base = base - ('0' + getNumericBase()); 379 this.start = start; 380 this.end = end; 381 } 382 383 private int getDigitBase() { 384 return base; 385 } 386 387 char getNumericBase() { 388 return 0; 389 } 390 391 private boolean inRange(int c) { 392 return start <= c && c < end; 393 } 394 } 395 396 /** index of context for contextual shaping - values range from 0 to 18 */ 397 private int key; 398 399 /** flag indicating whether to shape contextually (high bit) and which 400 * digit ranges to shape (bits 0-18) 401 */ 402 private int mask; 403 404 /** 405 * The context {@code Range} for contextual shaping or the {@code 406 * Range} for non-contextual shaping. {@code null} for the bit 407 * mask-based API. 408 * 409 * @since 1.7 410 */ 411 private Range shapingRange; 412 413 /** 414 * {@code Set<Range>} indicating which Unicode ranges to 415 * shape. {@code null} for the bit mask-based API. 416 */ 417 private transient Set<Range> rangeSet; 418 419 /** 420 * rangeSet.toArray() value. Sorted by Range.base when the number 421 * of elements is greater than BSEARCH_THRESHOLD. 422 */ 423 private transient Range[] rangeArray; 424 425 /** 426 * If more than BSEARCH_THRESHOLD ranges are specified, binary search is used. 427 */ 428 private static final int BSEARCH_THRESHOLD = 3; 429 430 private static final long serialVersionUID = -8022764705923730308L; 431 432 /** Identifies the Latin-1 (European) and extended range, and 433 * Latin-1 (European) decimal base. 434 */ 435 public static final int EUROPEAN = 1<<0; 436 437 /** Identifies the ARABIC range and decimal base. */ 438 public static final int ARABIC = 1<<1; 439 440 /** Identifies the ARABIC range and ARABIC_EXTENDED decimal base. */ 441 public static final int EASTERN_ARABIC = 1<<2; 442 443 /** Identifies the DEVANAGARI range and decimal base. */ 444 public static final int DEVANAGARI = 1<<3; 445 446 /** Identifies the BENGALI range and decimal base. */ 447 public static final int BENGALI = 1<<4; 448 449 /** Identifies the GURMUKHI range and decimal base. */ 450 public static final int GURMUKHI = 1<<5; 451 452 /** Identifies the GUJARATI range and decimal base. */ 453 public static final int GUJARATI = 1<<6; 454 455 /** Identifies the ORIYA range and decimal base. */ 456 public static final int ORIYA = 1<<7; 457 458 /** Identifies the TAMIL range and decimal base. */ 459 // TAMIL DIGIT ZERO was added in Unicode 4.1 460 public static final int TAMIL = 1<<8; 461 462 /** Identifies the TELUGU range and decimal base. */ 463 public static final int TELUGU = 1<<9; 464 465 /** Identifies the KANNADA range and decimal base. */ 466 public static final int KANNADA = 1<<10; 467 468 /** Identifies the MALAYALAM range and decimal base. */ 469 public static final int MALAYALAM = 1<<11; 470 471 /** Identifies the THAI range and decimal base. */ 472 public static final int THAI = 1<<12; 473 474 /** Identifies the LAO range and decimal base. */ 475 public static final int LAO = 1<<13; 476 477 /** Identifies the TIBETAN range and decimal base. */ 478 public static final int TIBETAN = 1<<14; 479 480 /** Identifies the MYANMAR range and decimal base. */ 481 public static final int MYANMAR = 1<<15; 482 483 /** Identifies the ETHIOPIC range and decimal base. */ 484 public static final int ETHIOPIC = 1<<16; 485 486 /** Identifies the KHMER range and decimal base. */ 487 public static final int KHMER = 1<<17; 488 489 /** Identifies the MONGOLIAN range and decimal base. */ 490 public static final int MONGOLIAN = 1<<18; 491 492 /** Identifies all ranges, for full contextual shaping. 493 * 494 * <p>This constant specifies all of the bit mask-based 495 * ranges. Use {@code EnumSet.allOf(NumericShaper.Range.class)} to 496 * specify all of the enum-based ranges. 497 */ 498 public static final int ALL_RANGES = 0x0007ffff; 499 500 private static final int EUROPEAN_KEY = 0; 501 private static final int ARABIC_KEY = 1; 502 private static final int EASTERN_ARABIC_KEY = 2; 503 private static final int DEVANAGARI_KEY = 3; 504 private static final int BENGALI_KEY = 4; 505 private static final int GURMUKHI_KEY = 5; 506 private static final int GUJARATI_KEY = 6; 507 private static final int ORIYA_KEY = 7; 508 private static final int TAMIL_KEY = 8; 509 private static final int TELUGU_KEY = 9; 510 private static final int KANNADA_KEY = 10; 511 private static final int MALAYALAM_KEY = 11; 512 private static final int THAI_KEY = 12; 513 private static final int LAO_KEY = 13; 514 private static final int TIBETAN_KEY = 14; 515 private static final int MYANMAR_KEY = 15; 516 private static final int ETHIOPIC_KEY = 16; 517 private static final int KHMER_KEY = 17; 518 private static final int MONGOLIAN_KEY = 18; 519 520 private static final int NUM_KEYS = MONGOLIAN_KEY + 1; // fixed 521 522 private static final int CONTEXTUAL_MASK = 1<<31; 523 524 private static final char[] bases = { 525 '\u0030' - '\u0030', // EUROPEAN 526 '\u0660' - '\u0030', // ARABIC-INDIC 527 '\u06f0' - '\u0030', // EXTENDED ARABIC-INDIC (EASTERN_ARABIC) 528 '\u0966' - '\u0030', // DEVANAGARI 529 '\u09e6' - '\u0030', // BENGALI 530 '\u0a66' - '\u0030', // GURMUKHI 531 '\u0ae6' - '\u0030', // GUJARATI 532 '\u0b66' - '\u0030', // ORIYA 533 '\u0be6' - '\u0030', // TAMIL - zero was added in Unicode 4.1 534 '\u0c66' - '\u0030', // TELUGU 535 '\u0ce6' - '\u0030', // KANNADA 536 '\u0d66' - '\u0030', // MALAYALAM 537 '\u0e50' - '\u0030', // THAI 538 '\u0ed0' - '\u0030', // LAO 539 '\u0f20' - '\u0030', // TIBETAN 540 '\u1040' - '\u0030', // MYANMAR 541 '\u1369' - '\u0031', // ETHIOPIC - no zero 542 '\u17e0' - '\u0030', // KHMER 543 '\u1810' - '\u0030', // MONGOLIAN 544 }; 545 546 // some ranges adjoin or overlap, rethink if we want to do a binary search on this 547 548 private static final char[] contexts = { 549 '\u0000', '\u0300', // 'EUROPEAN' (really latin-1 and extended) 550 '\u0600', '\u0780', // ARABIC 551 '\u0600', '\u0780', // EASTERN_ARABIC -- note overlap with arabic 552 '\u0900', '\u0980', // DEVANAGARI 553 '\u0980', '\u0a00', // BENGALI 554 '\u0a00', '\u0a80', // GURMUKHI 555 '\u0a80', '\u0b00', // GUJARATI 556 '\u0b00', '\u0b80', // ORIYA 557 '\u0b80', '\u0c00', // TAMIL 558 '\u0c00', '\u0c80', // TELUGU 559 '\u0c80', '\u0d00', // KANNADA 560 '\u0d00', '\u0d80', // MALAYALAM 561 '\u0e00', '\u0e80', // THAI 562 '\u0e80', '\u0f00', // LAO 563 '\u0f00', '\u1000', // TIBETAN 564 '\u1000', '\u1080', // MYANMAR 565 '\u1200', '\u1380', // ETHIOPIC - note missing zero 566 '\u1780', '\u1800', // KHMER 567 '\u1800', '\u1900', // MONGOLIAN 568 '\uffff', 569 }; 570 571 // assume most characters are near each other so probing the cache is infrequent, 572 // and a linear probe is ok. 573 574 private static int ctCache = 0; 575 private static int ctCacheLimit = contexts.length - 2; 576 577 // warning, synchronize access to this as it modifies state 578 private static int getContextKey(char c) { 579 if (c < contexts[ctCache]) { 580 while (ctCache > 0 && c < contexts[ctCache]) --ctCache; 581 } else if (c >= contexts[ctCache + 1]) { 582 while (ctCache < ctCacheLimit && c >= contexts[ctCache + 1]) ++ctCache; 583 } 584 585 // if we're not in a known range, then return EUROPEAN as the range key 586 return (ctCache & 0x1) == 0 ? (ctCache / 2) : EUROPEAN_KEY; 587 } 588 589 // cache for the NumericShaper.Range version 590 private transient volatile Range currentRange = Range.EUROPEAN; 591 592 private Range rangeForCodePoint(final int codepoint) { 593 if (currentRange.inRange(codepoint)) { 594 return currentRange; 595 } 596 597 final Range[] ranges = rangeArray; 598 if (ranges.length > BSEARCH_THRESHOLD) { 599 int lo = 0; 600 int hi = ranges.length - 1; 601 while (lo <= hi) { 602 int mid = (lo + hi) / 2; 603 Range range = ranges[mid]; 604 if (codepoint < range.start) { 605 hi = mid - 1; 606 } else if (codepoint >= range.end) { 607 lo = mid + 1; 608 } else { 609 currentRange = range; 610 return range; 611 } 612 } 613 } else { 614 for (int i = 0; i < ranges.length; i++) { 615 if (ranges[i].inRange(codepoint)) { 616 return ranges[i]; 617 } 618 } 619 } 620 return Range.EUROPEAN; 621 } 622 623 /* 624 * A range table of strong directional characters (types L, R, AL). 625 * Even (left) indexes are starts of ranges of non-strong-directional (or undefined) 626 * characters, odd (right) indexes are starts of ranges of strong directional 627 * characters. 628 */ 629 private static int[] strongTable = { 630 0x0000, 0x0041, 631 0x005b, 0x0061, 632 0x007b, 0x00aa, 633 0x00ab, 0x00b5, 634 0x00b6, 0x00ba, 635 0x00bb, 0x00c0, 636 0x00d7, 0x00d8, 637 0x00f7, 0x00f8, 638 0x02b9, 0x02bb, 639 0x02c2, 0x02d0, 640 0x02d2, 0x02e0, 641 0x02e5, 0x02ee, 642 0x02ef, 0x0370, 643 0x0374, 0x0376, 644 0x0378, 0x037a, 645 0x037e, 0x037f, 646 0x0380, 0x0386, 647 0x0387, 0x0388, 648 0x038b, 0x038c, 649 0x038d, 0x038e, 650 0x03a2, 0x03a3, 651 0x03f6, 0x03f7, 652 0x0483, 0x048a, 653 0x0530, 0x0531, 654 0x0557, 0x0559, 655 0x0560, 0x0561, 656 0x0588, 0x0589, 657 0x058a, 0x0590, 658 0x0591, 0x05be, 659 0x05bf, 0x05c0, 660 0x05c1, 0x05c3, 661 0x05c4, 0x05c6, 662 0x05c7, 0x05c8, 663 0x0600, 0x0608, 664 0x0609, 0x060b, 665 0x060c, 0x060d, 666 0x060e, 0x061b, 667 0x064b, 0x066d, 668 0x0670, 0x0671, 669 0x06d6, 0x06e5, 670 0x06e7, 0x06ee, 671 0x06f0, 0x06fa, 672 0x0711, 0x0712, 673 0x0730, 0x074b, 674 0x07a6, 0x07b1, 675 0x07eb, 0x07f4, 676 0x07f6, 0x07fa, 677 0x0816, 0x081a, 678 0x081b, 0x0824, 679 0x0825, 0x0828, 680 0x0829, 0x082e, 681 0x0859, 0x085c, 682 0x08e3, 0x0903, 683 0x093a, 0x093b, 684 0x093c, 0x093d, 685 0x0941, 0x0949, 686 0x094d, 0x094e, 687 0x0951, 0x0958, 688 0x0962, 0x0964, 689 0x0981, 0x0982, 690 0x0984, 0x0985, 691 0x098d, 0x098f, 692 0x0991, 0x0993, 693 0x09a9, 0x09aa, 694 0x09b1, 0x09b2, 695 0x09b3, 0x09b6, 696 0x09ba, 0x09bd, 697 0x09c1, 0x09c7, 698 0x09c9, 0x09cb, 699 0x09cd, 0x09ce, 700 0x09cf, 0x09d7, 701 0x09d8, 0x09dc, 702 0x09de, 0x09df, 703 0x09e2, 0x09e6, 704 0x09f2, 0x09f4, 705 0x09fb, 0x0a03, 706 0x0a04, 0x0a05, 707 0x0a0b, 0x0a0f, 708 0x0a11, 0x0a13, 709 0x0a29, 0x0a2a, 710 0x0a31, 0x0a32, 711 0x0a34, 0x0a35, 712 0x0a37, 0x0a38, 713 0x0a3a, 0x0a3e, 714 0x0a41, 0x0a59, 715 0x0a5d, 0x0a5e, 716 0x0a5f, 0x0a66, 717 0x0a70, 0x0a72, 718 0x0a75, 0x0a83, 719 0x0a84, 0x0a85, 720 0x0a8e, 0x0a8f, 721 0x0a92, 0x0a93, 722 0x0aa9, 0x0aaa, 723 0x0ab1, 0x0ab2, 724 0x0ab4, 0x0ab5, 725 0x0aba, 0x0abd, 726 0x0ac1, 0x0ac9, 727 0x0aca, 0x0acb, 728 0x0acd, 0x0ad0, 729 0x0ad1, 0x0ae0, 730 0x0ae2, 0x0ae6, 731 0x0af1, 0x0af9, 732 0x0afa, 0x0b02, 733 0x0b04, 0x0b05, 734 0x0b0d, 0x0b0f, 735 0x0b11, 0x0b13, 736 0x0b29, 0x0b2a, 737 0x0b31, 0x0b32, 738 0x0b34, 0x0b35, 739 0x0b3a, 0x0b3d, 740 0x0b3f, 0x0b40, 741 0x0b41, 0x0b47, 742 0x0b49, 0x0b4b, 743 0x0b4d, 0x0b57, 744 0x0b58, 0x0b5c, 745 0x0b5e, 0x0b5f, 746 0x0b62, 0x0b66, 747 0x0b78, 0x0b83, 748 0x0b84, 0x0b85, 749 0x0b8b, 0x0b8e, 750 0x0b91, 0x0b92, 751 0x0b96, 0x0b99, 752 0x0b9b, 0x0b9c, 753 0x0b9d, 0x0b9e, 754 0x0ba0, 0x0ba3, 755 0x0ba5, 0x0ba8, 756 0x0bab, 0x0bae, 757 0x0bba, 0x0bbe, 758 0x0bc0, 0x0bc1, 759 0x0bc3, 0x0bc6, 760 0x0bc9, 0x0bca, 761 0x0bcd, 0x0bd0, 762 0x0bd1, 0x0bd7, 763 0x0bd8, 0x0be6, 764 0x0bf3, 0x0c01, 765 0x0c04, 0x0c05, 766 0x0c0d, 0x0c0e, 767 0x0c11, 0x0c12, 768 0x0c29, 0x0c2a, 769 0x0c3a, 0x0c3d, 770 0x0c3e, 0x0c41, 771 0x0c45, 0x0c58, 772 0x0c5b, 0x0c60, 773 0x0c62, 0x0c66, 774 0x0c70, 0x0c7f, 775 0x0c80, 0x0c82, 776 0x0c84, 0x0c85, 777 0x0c8d, 0x0c8e, 778 0x0c91, 0x0c92, 779 0x0ca9, 0x0caa, 780 0x0cb4, 0x0cb5, 781 0x0cba, 0x0cbd, 782 0x0cc5, 0x0cc6, 783 0x0cc9, 0x0cca, 784 0x0ccc, 0x0cd5, 785 0x0cd7, 0x0cde, 786 0x0cdf, 0x0ce0, 787 0x0ce2, 0x0ce6, 788 0x0cf0, 0x0cf1, 789 0x0cf3, 0x0d02, 790 0x0d04, 0x0d05, 791 0x0d0d, 0x0d0e, 792 0x0d11, 0x0d12, 793 0x0d3b, 0x0d3d, 794 0x0d41, 0x0d46, 795 0x0d49, 0x0d4a, 796 0x0d4d, 0x0d4e, 797 0x0d4f, 0x0d57, 798 0x0d58, 0x0d5f, 799 0x0d62, 0x0d66, 800 0x0d76, 0x0d79, 801 0x0d80, 0x0d82, 802 0x0d84, 0x0d85, 803 0x0d97, 0x0d9a, 804 0x0db2, 0x0db3, 805 0x0dbc, 0x0dbd, 806 0x0dbe, 0x0dc0, 807 0x0dc7, 0x0dcf, 808 0x0dd2, 0x0dd8, 809 0x0de0, 0x0de6, 810 0x0df0, 0x0df2, 811 0x0df5, 0x0e01, 812 0x0e31, 0x0e32, 813 0x0e34, 0x0e40, 814 0x0e47, 0x0e4f, 815 0x0e5c, 0x0e81, 816 0x0e83, 0x0e84, 817 0x0e85, 0x0e87, 818 0x0e89, 0x0e8a, 819 0x0e8b, 0x0e8d, 820 0x0e8e, 0x0e94, 821 0x0e98, 0x0e99, 822 0x0ea0, 0x0ea1, 823 0x0ea4, 0x0ea5, 824 0x0ea6, 0x0ea7, 825 0x0ea8, 0x0eaa, 826 0x0eac, 0x0ead, 827 0x0eb1, 0x0eb2, 828 0x0eb4, 0x0ebd, 829 0x0ebe, 0x0ec0, 830 0x0ec5, 0x0ec6, 831 0x0ec7, 0x0ed0, 832 0x0eda, 0x0edc, 833 0x0ee0, 0x0f00, 834 0x0f18, 0x0f1a, 835 0x0f35, 0x0f36, 836 0x0f37, 0x0f38, 837 0x0f39, 0x0f3e, 838 0x0f48, 0x0f49, 839 0x0f6d, 0x0f7f, 840 0x0f80, 0x0f85, 841 0x0f86, 0x0f88, 842 0x0f8d, 0x0fbe, 843 0x0fc6, 0x0fc7, 844 0x0fcd, 0x0fce, 845 0x0fdb, 0x1000, 846 0x102d, 0x1031, 847 0x1032, 0x1038, 848 0x1039, 0x103b, 849 0x103d, 0x103f, 850 0x1058, 0x105a, 851 0x105e, 0x1061, 852 0x1071, 0x1075, 853 0x1082, 0x1083, 854 0x1085, 0x1087, 855 0x108d, 0x108e, 856 0x109d, 0x109e, 857 0x10c6, 0x10c7, 858 0x10c8, 0x10cd, 859 0x10ce, 0x10d0, 860 0x1249, 0x124a, 861 0x124e, 0x1250, 862 0x1257, 0x1258, 863 0x1259, 0x125a, 864 0x125e, 0x1260, 865 0x1289, 0x128a, 866 0x128e, 0x1290, 867 0x12b1, 0x12b2, 868 0x12b6, 0x12b8, 869 0x12bf, 0x12c0, 870 0x12c1, 0x12c2, 871 0x12c6, 0x12c8, 872 0x12d7, 0x12d8, 873 0x1311, 0x1312, 874 0x1316, 0x1318, 875 0x135b, 0x1360, 876 0x137d, 0x1380, 877 0x1390, 0x13a0, 878 0x13f6, 0x13f8, 879 0x13fe, 0x1401, 880 0x1680, 0x1681, 881 0x169b, 0x16a0, 882 0x16f9, 0x1700, 883 0x170d, 0x170e, 884 0x1712, 0x1720, 885 0x1732, 0x1735, 886 0x1737, 0x1740, 887 0x1752, 0x1760, 888 0x176d, 0x176e, 889 0x1771, 0x1780, 890 0x17b4, 0x17b6, 891 0x17b7, 0x17be, 892 0x17c6, 0x17c7, 893 0x17c9, 0x17d4, 894 0x17db, 0x17dc, 895 0x17dd, 0x17e0, 896 0x17ea, 0x1810, 897 0x181a, 0x1820, 898 0x1878, 0x1880, 899 0x18a9, 0x18aa, 900 0x18ab, 0x18b0, 901 0x18f6, 0x1900, 902 0x191f, 0x1923, 903 0x1927, 0x1929, 904 0x192c, 0x1930, 905 0x1932, 0x1933, 906 0x1939, 0x1946, 907 0x196e, 0x1970, 908 0x1975, 0x1980, 909 0x19ac, 0x19b0, 910 0x19ca, 0x19d0, 911 0x19db, 0x1a00, 912 0x1a17, 0x1a19, 913 0x1a1b, 0x1a1e, 914 0x1a56, 0x1a57, 915 0x1a58, 0x1a61, 916 0x1a62, 0x1a63, 917 0x1a65, 0x1a6d, 918 0x1a73, 0x1a80, 919 0x1a8a, 0x1a90, 920 0x1a9a, 0x1aa0, 921 0x1aae, 0x1b04, 922 0x1b34, 0x1b35, 923 0x1b36, 0x1b3b, 924 0x1b3c, 0x1b3d, 925 0x1b42, 0x1b43, 926 0x1b4c, 0x1b50, 927 0x1b6b, 0x1b74, 928 0x1b7d, 0x1b82, 929 0x1ba2, 0x1ba6, 930 0x1ba8, 0x1baa, 931 0x1bab, 0x1bae, 932 0x1be6, 0x1be7, 933 0x1be8, 0x1bea, 934 0x1bed, 0x1bee, 935 0x1bef, 0x1bf2, 936 0x1bf4, 0x1bfc, 937 0x1c2c, 0x1c34, 938 0x1c36, 0x1c3b, 939 0x1c4a, 0x1c4d, 940 0x1c80, 0x1cc0, 941 0x1cc8, 0x1cd3, 942 0x1cd4, 0x1ce1, 943 0x1ce2, 0x1ce9, 944 0x1ced, 0x1cee, 945 0x1cf4, 0x1cf5, 946 0x1cf7, 0x1d00, 947 0x1dc0, 0x1e00, 948 0x1f16, 0x1f18, 949 0x1f1e, 0x1f20, 950 0x1f46, 0x1f48, 951 0x1f4e, 0x1f50, 952 0x1f58, 0x1f59, 953 0x1f5a, 0x1f5b, 954 0x1f5c, 0x1f5d, 955 0x1f5e, 0x1f5f, 956 0x1f7e, 0x1f80, 957 0x1fb5, 0x1fb6, 958 0x1fbd, 0x1fbe, 959 0x1fbf, 0x1fc2, 960 0x1fc5, 0x1fc6, 961 0x1fcd, 0x1fd0, 962 0x1fd4, 0x1fd6, 963 0x1fdc, 0x1fe0, 964 0x1fed, 0x1ff2, 965 0x1ff5, 0x1ff6, 966 0x1ffd, 0x200e, 967 0x2010, 0x2071, 968 0x2072, 0x207f, 969 0x2080, 0x2090, 970 0x209d, 0x2102, 971 0x2103, 0x2107, 972 0x2108, 0x210a, 973 0x2114, 0x2115, 974 0x2116, 0x2119, 975 0x211e, 0x2124, 976 0x2125, 0x2126, 977 0x2127, 0x2128, 978 0x2129, 0x212a, 979 0x212e, 0x212f, 980 0x213a, 0x213c, 981 0x2140, 0x2145, 982 0x214a, 0x214e, 983 0x2150, 0x2160, 984 0x2189, 0x2336, 985 0x237b, 0x2395, 986 0x2396, 0x249c, 987 0x24ea, 0x26ac, 988 0x26ad, 0x2800, 989 0x2900, 0x2c00, 990 0x2c2f, 0x2c30, 991 0x2c5f, 0x2c60, 992 0x2ce5, 0x2ceb, 993 0x2cef, 0x2cf2, 994 0x2cf4, 0x2d00, 995 0x2d26, 0x2d27, 996 0x2d28, 0x2d2d, 997 0x2d2e, 0x2d30, 998 0x2d68, 0x2d6f, 999 0x2d71, 0x2d80, 1000 0x2d97, 0x2da0, 1001 0x2da7, 0x2da8, 1002 0x2daf, 0x2db0, 1003 0x2db7, 0x2db8, 1004 0x2dbf, 0x2dc0, 1005 0x2dc7, 0x2dc8, 1006 0x2dcf, 0x2dd0, 1007 0x2dd7, 0x2dd8, 1008 0x2ddf, 0x3005, 1009 0x3008, 0x3021, 1010 0x302a, 0x302e, 1011 0x3030, 0x3031, 1012 0x3036, 0x3038, 1013 0x303d, 0x3041, 1014 0x3097, 0x309d, 1015 0x30a0, 0x30a1, 1016 0x30fb, 0x30fc, 1017 0x3100, 0x3105, 1018 0x312e, 0x3131, 1019 0x318f, 0x3190, 1020 0x31bb, 0x31f0, 1021 0x321d, 0x3220, 1022 0x3250, 0x3260, 1023 0x327c, 0x327f, 1024 0x32b1, 0x32c0, 1025 0x32cc, 0x32d0, 1026 0x32ff, 0x3300, 1027 0x3377, 0x337b, 1028 0x33de, 0x33e0, 1029 0x33ff, 0x3400, 1030 0x4db6, 0x4e00, 1031 0x9fd6, 0xa000, 1032 0xa48d, 0xa4d0, 1033 0xa60d, 0xa610, 1034 0xa62c, 0xa640, 1035 0xa66f, 0xa680, 1036 0xa69e, 0xa6a0, 1037 0xa6f0, 0xa6f2, 1038 0xa6f8, 0xa722, 1039 0xa788, 0xa789, 1040 0xa7ae, 0xa7b0, 1041 0xa7b8, 0xa7f7, 1042 0xa802, 0xa803, 1043 0xa806, 0xa807, 1044 0xa80b, 0xa80c, 1045 0xa825, 0xa827, 1046 0xa828, 0xa830, 1047 0xa838, 0xa840, 1048 0xa874, 0xa880, 1049 0xa8c4, 0xa8ce, 1050 0xa8da, 0xa8f2, 1051 0xa8fe, 0xa900, 1052 0xa926, 0xa92e, 1053 0xa947, 0xa952, 1054 0xa954, 0xa95f, 1055 0xa97d, 0xa983, 1056 0xa9b3, 0xa9b4, 1057 0xa9b6, 0xa9ba, 1058 0xa9bc, 0xa9bd, 1059 0xa9ce, 0xa9cf, 1060 0xa9da, 0xa9de, 1061 0xa9e5, 0xa9e6, 1062 0xa9ff, 0xaa00, 1063 0xaa29, 0xaa2f, 1064 0xaa31, 0xaa33, 1065 0xaa35, 0xaa40, 1066 0xaa43, 0xaa44, 1067 0xaa4c, 0xaa4d, 1068 0xaa4e, 0xaa50, 1069 0xaa5a, 0xaa5c, 1070 0xaa7c, 0xaa7d, 1071 0xaab0, 0xaab1, 1072 0xaab2, 0xaab5, 1073 0xaab7, 0xaab9, 1074 0xaabe, 0xaac0, 1075 0xaac1, 0xaac2, 1076 0xaac3, 0xaadb, 1077 0xaaec, 0xaaee, 1078 0xaaf6, 0xab01, 1079 0xab07, 0xab09, 1080 0xab0f, 0xab11, 1081 0xab17, 0xab20, 1082 0xab27, 0xab28, 1083 0xab2f, 0xab30, 1084 0xab66, 0xab70, 1085 0xabe5, 0xabe6, 1086 0xabe8, 0xabe9, 1087 0xabed, 0xabf0, 1088 0xabfa, 0xac00, 1089 0xd7a4, 0xd7b0, 1090 0xd7c7, 0xd7cb, 1091 0xd7fc, 0xe000, 1092 0xfa6e, 0xfa70, 1093 0xfada, 0xfb00, 1094 0xfb07, 0xfb13, 1095 0xfb18, 0xfb1d, 1096 0xfb1e, 0xfb1f, 1097 0xfb29, 0xfb2a, 1098 0xfd3e, 0xfd40, 1099 0xfdd0, 0xfdf0, 1100 0xfdfd, 0xfdfe, 1101 0xfe00, 0xfe70, 1102 0xfeff, 0xff21, 1103 0xff3b, 0xff41, 1104 0xff5b, 0xff66, 1105 0xffbf, 0xffc2, 1106 0xffc8, 0xffca, 1107 0xffd0, 0xffd2, 1108 0xffd8, 0xffda, 1109 0xffdd, 0x10000, 1110 0x1000c, 0x1000d, 1111 0x10027, 0x10028, 1112 0x1003b, 0x1003c, 1113 0x1003e, 0x1003f, 1114 0x1004e, 0x10050, 1115 0x1005e, 0x10080, 1116 0x100fb, 0x10100, 1117 0x10101, 0x10102, 1118 0x10103, 0x10107, 1119 0x10134, 0x10137, 1120 0x10140, 0x101d0, 1121 0x101fd, 0x10280, 1122 0x1029d, 0x102a0, 1123 0x102d1, 0x10300, 1124 0x10324, 0x10330, 1125 0x1034b, 0x10350, 1126 0x10376, 0x10380, 1127 0x1039e, 0x1039f, 1128 0x103c4, 0x103c8, 1129 0x103d6, 0x10400, 1130 0x1049e, 0x104a0, 1131 0x104aa, 0x10500, 1132 0x10528, 0x10530, 1133 0x10564, 0x1056f, 1134 0x10570, 0x10600, 1135 0x10737, 0x10740, 1136 0x10756, 0x10760, 1137 0x10768, 0x10800, 1138 0x1091f, 0x10920, 1139 0x10a01, 0x10a04, 1140 0x10a05, 0x10a07, 1141 0x10a0c, 0x10a10, 1142 0x10a38, 0x10a3b, 1143 0x10a3f, 0x10a40, 1144 0x10ae5, 0x10ae7, 1145 0x10b39, 0x10b40, 1146 0x10e60, 0x10e7f, 1147 0x11001, 0x11002, 1148 0x11038, 0x11047, 1149 0x1104e, 0x11066, 1150 0x11070, 0x11082, 1151 0x110b3, 0x110b7, 1152 0x110b9, 0x110bb, 1153 0x110c2, 0x110d0, 1154 0x110e9, 0x110f0, 1155 0x110fa, 0x11103, 1156 0x11127, 0x1112c, 1157 0x1112d, 0x11136, 1158 0x11144, 0x11150, 1159 0x11173, 0x11174, 1160 0x11177, 0x11182, 1161 0x111b6, 0x111bf, 1162 0x111ca, 0x111cd, 1163 0x111ce, 0x111d0, 1164 0x111e0, 0x111e1, 1165 0x111f5, 0x11200, 1166 0x11212, 0x11213, 1167 0x1122f, 0x11232, 1168 0x11234, 0x11235, 1169 0x11236, 0x11238, 1170 0x1123e, 0x11280, 1171 0x11287, 0x11288, 1172 0x11289, 0x1128a, 1173 0x1128e, 0x1128f, 1174 0x1129e, 0x1129f, 1175 0x112aa, 0x112b0, 1176 0x112df, 0x112e0, 1177 0x112e3, 0x112f0, 1178 0x112fa, 0x11302, 1179 0x11304, 0x11305, 1180 0x1130d, 0x1130f, 1181 0x11311, 0x11313, 1182 0x11329, 0x1132a, 1183 0x11331, 0x11332, 1184 0x11334, 0x11335, 1185 0x1133a, 0x1133d, 1186 0x11340, 0x11341, 1187 0x11345, 0x11347, 1188 0x11349, 0x1134b, 1189 0x1134e, 0x11350, 1190 0x11351, 0x11357, 1191 0x11358, 0x1135d, 1192 0x11364, 0x11480, 1193 0x114b3, 0x114b9, 1194 0x114ba, 0x114bb, 1195 0x114bf, 0x114c1, 1196 0x114c2, 0x114c4, 1197 0x114c8, 0x114d0, 1198 0x114da, 0x11580, 1199 0x115b2, 0x115b8, 1200 0x115bc, 0x115be, 1201 0x115bf, 0x115c1, 1202 0x115dc, 0x11600, 1203 0x11633, 0x1163b, 1204 0x1163d, 0x1163e, 1205 0x1163f, 0x11641, 1206 0x11645, 0x11650, 1207 0x1165a, 0x11680, 1208 0x116ab, 0x116ac, 1209 0x116ad, 0x116ae, 1210 0x116b0, 0x116b6, 1211 0x116b7, 0x116c0, 1212 0x116ca, 0x11700, 1213 0x1171a, 0x11720, 1214 0x11722, 0x11726, 1215 0x11727, 0x11730, 1216 0x11740, 0x118a0, 1217 0x118f3, 0x118ff, 1218 0x11900, 0x11ac0, 1219 0x11af9, 0x12000, 1220 0x1239a, 0x12400, 1221 0x1246f, 0x12470, 1222 0x12475, 0x12480, 1223 0x12544, 0x13000, 1224 0x1342f, 0x14400, 1225 0x14647, 0x16800, 1226 0x16a39, 0x16a40, 1227 0x16a5f, 0x16a60, 1228 0x16a6a, 0x16a6e, 1229 0x16a70, 0x16ad0, 1230 0x16aee, 0x16af5, 1231 0x16af6, 0x16b00, 1232 0x16b30, 0x16b37, 1233 0x16b46, 0x16b50, 1234 0x16b5a, 0x16b5b, 1235 0x16b62, 0x16b63, 1236 0x16b78, 0x16b7d, 1237 0x16b90, 0x16f00, 1238 0x16f45, 0x16f50, 1239 0x16f7f, 0x16f93, 1240 0x16fa0, 0x1b000, 1241 0x1b002, 0x1bc00, 1242 0x1bc6b, 0x1bc70, 1243 0x1bc7d, 0x1bc80, 1244 0x1bc89, 0x1bc90, 1245 0x1bc9a, 0x1bc9c, 1246 0x1bc9d, 0x1bc9f, 1247 0x1bca0, 0x1d000, 1248 0x1d0f6, 0x1d100, 1249 0x1d127, 0x1d129, 1250 0x1d167, 0x1d16a, 1251 0x1d173, 0x1d183, 1252 0x1d185, 0x1d18c, 1253 0x1d1aa, 0x1d1ae, 1254 0x1d1e9, 0x1d360, 1255 0x1d372, 0x1d400, 1256 0x1d455, 0x1d456, 1257 0x1d49d, 0x1d49e, 1258 0x1d4a0, 0x1d4a2, 1259 0x1d4a3, 0x1d4a5, 1260 0x1d4a7, 0x1d4a9, 1261 0x1d4ad, 0x1d4ae, 1262 0x1d4ba, 0x1d4bb, 1263 0x1d4bc, 0x1d4bd, 1264 0x1d4c4, 0x1d4c5, 1265 0x1d506, 0x1d507, 1266 0x1d50b, 0x1d50d, 1267 0x1d515, 0x1d516, 1268 0x1d51d, 0x1d51e, 1269 0x1d53a, 0x1d53b, 1270 0x1d53f, 0x1d540, 1271 0x1d545, 0x1d546, 1272 0x1d547, 0x1d54a, 1273 0x1d551, 0x1d552, 1274 0x1d6a6, 0x1d6a8, 1275 0x1d6db, 0x1d6dc, 1276 0x1d715, 0x1d716, 1277 0x1d74f, 0x1d750, 1278 0x1d789, 0x1d78a, 1279 0x1d7c3, 0x1d7c4, 1280 0x1d7cc, 0x1d800, 1281 0x1da00, 0x1da37, 1282 0x1da3b, 0x1da6d, 1283 0x1da75, 0x1da76, 1284 0x1da84, 0x1da85, 1285 0x1da8c, 0x1e800, 1286 0x1e8d0, 0x1e8d7, 1287 0x1eef0, 0x1eef2, 1288 0x1f000, 0x1f110, 1289 0x1f12f, 0x1f130, 1290 0x1f16a, 0x1f170, 1291 0x1f19b, 0x1f1e6, 1292 0x1f203, 0x1f210, 1293 0x1f23b, 0x1f240, 1294 0x1f249, 0x1f250, 1295 0x1f252, 0x20000, 1296 0x2a6d7, 0x2a700, 1297 0x2b735, 0x2b740, 1298 0x2b81e, 0x2b820, 1299 0x2cea2, 0x2f800, 1300 0x2fa1e, 0xf0000, 1301 0xffffe, 0x100000, 1302 0x10fffe, 0x10ffff // sentinel 1303 }; 1304 1305 1306 // use a binary search with a cache 1307 1308 private transient volatile int stCache = 0; 1309 1310 private boolean isStrongDirectional(char c) { 1311 int cachedIndex = stCache; 1312 if (c < strongTable[cachedIndex]) { 1313 cachedIndex = search(c, strongTable, 0, cachedIndex); 1314 } else if (c >= strongTable[cachedIndex + 1]) { 1315 cachedIndex = search(c, strongTable, cachedIndex + 1, 1316 strongTable.length - cachedIndex - 1); 1317 } 1318 boolean val = (cachedIndex & 0x1) == 1; 1319 stCache = cachedIndex; 1320 return val; 1321 } 1322 1323 private static int getKeyFromMask(int mask) { 1324 int key = 0; 1325 while (key < NUM_KEYS && ((mask & (1<<key)) == 0)) { 1326 ++key; 1327 } 1328 if (key == NUM_KEYS || ((mask & ~(1<<key)) != 0)) { 1329 throw new IllegalArgumentException("invalid shaper: " + Integer.toHexString(mask)); 1330 } 1331 return key; 1332 } 1333 1334 /** 1335 * Returns a shaper for the provided unicode range. All 1336 * Latin-1 (EUROPEAN) digits are converted 1337 * to the corresponding decimal unicode digits. 1338 * @param singleRange the specified Unicode range 1339 * @return a non-contextual numeric shaper 1340 * @throws IllegalArgumentException if the range is not a single range 1341 */ 1342 public static NumericShaper getShaper(int singleRange) { 1343 int key = getKeyFromMask(singleRange); 1344 return new NumericShaper(key, singleRange); 1345 } 1346 1347 /** 1348 * Returns a shaper for the provided Unicode 1349 * range. All Latin-1 (EUROPEAN) digits are converted to the 1350 * corresponding decimal digits of the specified Unicode range. 1351 * 1352 * @param singleRange the Unicode range given by a {@link 1353 * NumericShaper.Range} constant. 1354 * @return a non-contextual {@code NumericShaper}. 1355 * @throws NullPointerException if {@code singleRange} is {@code null} 1356 * @since 1.7 1357 */ 1358 public static NumericShaper getShaper(Range singleRange) { 1359 return new NumericShaper(singleRange, EnumSet.of(singleRange)); 1360 } 1361 1362 /** 1363 * Returns a contextual shaper for the provided unicode range(s). 1364 * Latin-1 (EUROPEAN) digits are converted to the decimal digits 1365 * corresponding to the range of the preceding text, if the 1366 * range is one of the provided ranges. Multiple ranges are 1367 * represented by or-ing the values together, such as, 1368 * {@code NumericShaper.ARABIC | NumericShaper.THAI}. The 1369 * shaper assumes EUROPEAN as the starting context, that is, if 1370 * EUROPEAN digits are encountered before any strong directional 1371 * text in the string, the context is presumed to be EUROPEAN, and 1372 * so the digits will not shape. 1373 * @param ranges the specified Unicode ranges 1374 * @return a shaper for the specified ranges 1375 */ 1376 public static NumericShaper getContextualShaper(int ranges) { 1377 ranges |= CONTEXTUAL_MASK; 1378 return new NumericShaper(EUROPEAN_KEY, ranges); 1379 } 1380 1381 /** 1382 * Returns a contextual shaper for the provided Unicode 1383 * range(s). The Latin-1 (EUROPEAN) digits are converted to the 1384 * decimal digits corresponding to the range of the preceding 1385 * text, if the range is one of the provided ranges. 1386 * 1387 * <p>The shaper assumes EUROPEAN as the starting context, that 1388 * is, if EUROPEAN digits are encountered before any strong 1389 * directional text in the string, the context is presumed to be 1390 * EUROPEAN, and so the digits will not shape. 1391 * 1392 * @param ranges the specified Unicode ranges 1393 * @return a contextual shaper for the specified ranges 1394 * @throws NullPointerException if {@code ranges} is {@code null}. 1395 * @since 1.7 1396 */ 1397 public static NumericShaper getContextualShaper(Set<Range> ranges) { 1398 NumericShaper shaper = new NumericShaper(Range.EUROPEAN, ranges); 1399 shaper.mask = CONTEXTUAL_MASK; 1400 return shaper; 1401 } 1402 1403 /** 1404 * Returns a contextual shaper for the provided unicode range(s). 1405 * Latin-1 (EUROPEAN) digits will be converted to the decimal digits 1406 * corresponding to the range of the preceding text, if the 1407 * range is one of the provided ranges. Multiple ranges are 1408 * represented by or-ing the values together, for example, 1409 * {@code NumericShaper.ARABIC | NumericShaper.THAI}. The 1410 * shaper uses defaultContext as the starting context. 1411 * @param ranges the specified Unicode ranges 1412 * @param defaultContext the starting context, such as 1413 * {@code NumericShaper.EUROPEAN} 1414 * @return a shaper for the specified Unicode ranges. 1415 * @throws IllegalArgumentException if the specified 1416 * {@code defaultContext} is not a single valid range. 1417 */ 1418 public static NumericShaper getContextualShaper(int ranges, int defaultContext) { 1419 int key = getKeyFromMask(defaultContext); 1420 ranges |= CONTEXTUAL_MASK; 1421 return new NumericShaper(key, ranges); 1422 } 1423 1424 /** 1425 * Returns a contextual shaper for the provided Unicode range(s). 1426 * The Latin-1 (EUROPEAN) digits will be converted to the decimal 1427 * digits corresponding to the range of the preceding text, if the 1428 * range is one of the provided ranges. The shaper uses {@code 1429 * defaultContext} as the starting context. 1430 * 1431 * @param ranges the specified Unicode ranges 1432 * @param defaultContext the starting context, such as 1433 * {@code NumericShaper.Range.EUROPEAN} 1434 * @return a contextual shaper for the specified Unicode ranges. 1435 * @throws NullPointerException 1436 * if {@code ranges} or {@code defaultContext} is {@code null} 1437 * @since 1.7 1438 */ 1439 public static NumericShaper getContextualShaper(Set<Range> ranges, 1440 Range defaultContext) { 1441 if (defaultContext == null) { 1442 throw new NullPointerException(); 1443 } 1444 NumericShaper shaper = new NumericShaper(defaultContext, ranges); 1445 shaper.mask = CONTEXTUAL_MASK; 1446 return shaper; 1447 } 1448 1449 /** 1450 * Private constructor. 1451 */ 1452 private NumericShaper(int key, int mask) { 1453 this.key = key; 1454 this.mask = mask; 1455 } 1456 1457 private NumericShaper(Range defaultContext, Set<Range> ranges) { 1458 shapingRange = defaultContext; 1459 rangeSet = EnumSet.copyOf(ranges); // throws NPE if ranges is null. 1460 1461 // Give precedence to EASTERN_ARABIC if both ARABIC and 1462 // EASTERN_ARABIC are specified. 1463 if (rangeSet.contains(Range.EASTERN_ARABIC) 1464 && rangeSet.contains(Range.ARABIC)) { 1465 rangeSet.remove(Range.ARABIC); 1466 } 1467 1468 // As well as the above case, give precedence to TAI_THAM_THAM if both 1469 // TAI_THAM_HORA and TAI_THAM_THAM are specified. 1470 if (rangeSet.contains(Range.TAI_THAM_THAM) 1471 && rangeSet.contains(Range.TAI_THAM_HORA)) { 1472 rangeSet.remove(Range.TAI_THAM_HORA); 1473 } 1474 1475 rangeArray = rangeSet.toArray(new Range[rangeSet.size()]); 1476 if (rangeArray.length > BSEARCH_THRESHOLD) { 1477 // sort rangeArray for binary search 1478 Arrays.sort(rangeArray, 1479 new Comparator<Range>() { 1480 public int compare(Range s1, Range s2) { 1481 return s1.base > s2.base ? 1 : s1.base == s2.base ? 0 : -1; 1482 } 1483 }); 1484 } 1485 } 1486 1487 /** 1488 * Converts the digits in the text that occur between start and 1489 * start + count. 1490 * @param text an array of characters to convert 1491 * @param start the index into {@code text} to start 1492 * converting 1493 * @param count the number of characters in {@code text} 1494 * to convert 1495 * @throws IndexOutOfBoundsException if start or start + count is 1496 * out of bounds 1497 * @throws NullPointerException if text is null 1498 */ 1499 public void shape(char[] text, int start, int count) { 1500 checkParams(text, start, count); 1501 if (isContextual()) { 1502 if (rangeSet == null) { 1503 shapeContextually(text, start, count, key); 1504 } else { 1505 shapeContextually(text, start, count, shapingRange); 1506 } 1507 } else { 1508 shapeNonContextually(text, start, count); 1509 } 1510 } 1511 1512 /** 1513 * Converts the digits in the text that occur between start and 1514 * start + count, using the provided context. 1515 * Context is ignored if the shaper is not a contextual shaper. 1516 * @param text an array of characters 1517 * @param start the index into {@code text} to start 1518 * converting 1519 * @param count the number of characters in {@code text} 1520 * to convert 1521 * @param context the context to which to convert the 1522 * characters, such as {@code NumericShaper.EUROPEAN} 1523 * @throws IndexOutOfBoundsException if start or start + count is 1524 * out of bounds 1525 * @throws NullPointerException if text is null 1526 * @throws IllegalArgumentException if this is a contextual shaper 1527 * and the specified {@code context} is not a single valid 1528 * range. 1529 */ 1530 public void shape(char[] text, int start, int count, int context) { 1531 checkParams(text, start, count); 1532 if (isContextual()) { 1533 int ctxKey = getKeyFromMask(context); 1534 if (rangeSet == null) { 1535 shapeContextually(text, start, count, ctxKey); 1536 } else { 1537 shapeContextually(text, start, count, Range.values()[ctxKey]); 1538 } 1539 } else { 1540 shapeNonContextually(text, start, count); 1541 } 1542 } 1543 1544 /** 1545 * Converts the digits in the text that occur between {@code 1546 * start} and {@code start + count}, using the provided {@code 1547 * context}. {@code Context} is ignored if the shaper is not a 1548 * contextual shaper. 1549 * 1550 * @param text a {@code char} array 1551 * @param start the index into {@code text} to start converting 1552 * @param count the number of {@code char}s in {@code text} 1553 * to convert 1554 * @param context the context to which to convert the characters, 1555 * such as {@code NumericShaper.Range.EUROPEAN} 1556 * @throws IndexOutOfBoundsException 1557 * if {@code start} or {@code start + count} is out of bounds 1558 * @throws NullPointerException 1559 * if {@code text} or {@code context} is null 1560 * @since 1.7 1561 */ 1562 public void shape(char[] text, int start, int count, Range context) { 1563 checkParams(text, start, count); 1564 if (context == null) { 1565 throw new NullPointerException("context is null"); 1566 } 1567 1568 if (isContextual()) { 1569 if (rangeSet != null) { 1570 shapeContextually(text, start, count, context); 1571 } else { 1572 int key = Range.toRangeIndex(context); 1573 if (key >= 0) { 1574 shapeContextually(text, start, count, key); 1575 } else { 1576 shapeContextually(text, start, count, shapingRange); 1577 } 1578 } 1579 } else { 1580 shapeNonContextually(text, start, count); 1581 } 1582 } 1583 1584 private void checkParams(char[] text, int start, int count) { 1585 if (text == null) { 1586 throw new NullPointerException("text is null"); 1587 } 1588 if ((start < 0) 1589 || (start > text.length) 1590 || ((start + count) < 0) 1591 || ((start + count) > text.length)) { 1592 throw new IndexOutOfBoundsException( 1593 "bad start or count for text of length " + text.length); 1594 } 1595 } 1596 1597 /** 1598 * Returns a {@code boolean} indicating whether or not 1599 * this shaper shapes contextually. 1600 * @return {@code true} if this shaper is contextual; 1601 * {@code false} otherwise. 1602 */ 1603 public boolean isContextual() { 1604 return (mask & CONTEXTUAL_MASK) != 0; 1605 } 1606 1607 /** 1608 * Returns an {@code int} that ORs together the values for 1609 * all the ranges that will be shaped. 1610 * <p> 1611 * For example, to check if a shaper shapes to Arabic, you would use the 1612 * following: 1613 * <blockquote> 1614 * {@code if ((shaper.getRanges() & shaper.ARABIC) != 0) { ... } 1615 * </blockquote> 1616 * 1617 * <p>Note that this method supports only the bit mask-based 1618 * ranges. Call {@link #getRangeSet()} for the enum-based ranges. 1619 * 1620 * @return the values for all the ranges to be shaped. 1621 */ 1622 public int getRanges() { 1623 return mask & ~CONTEXTUAL_MASK; 1624 } 1625 1626 /** 1627 * Returns a {@code Set} representing all the Unicode ranges in 1628 * this {@code NumericShaper} that will be shaped. 1629 * 1630 * @return all the Unicode ranges to be shaped. 1631 * @since 1.7 1632 */ 1633 public Set<Range> getRangeSet() { 1634 if (rangeSet != null) { 1635 return EnumSet.copyOf(rangeSet); 1636 } 1637 return Range.maskToRangeSet(mask); 1638 } 1639 1640 /** 1641 * Perform non-contextual shaping. 1642 */ 1643 private void shapeNonContextually(char[] text, int start, int count) { 1644 int base; 1645 char minDigit = '0'; 1646 if (shapingRange != null) { 1647 base = shapingRange.getDigitBase(); 1648 minDigit += shapingRange.getNumericBase(); 1649 } else { 1650 base = bases[key]; 1651 if (key == ETHIOPIC_KEY) { 1652 minDigit++; // Ethiopic doesn't use decimal zero 1653 } 1654 } 1655 for (int i = start, e = start + count; i < e; ++i) { 1656 char c = text[i]; 1657 if (c >= minDigit && c <= '\u0039') { 1658 text[i] = (char)(c + base); 1659 } 1660 } 1661 } 1662 1663 /** 1664 * Perform contextual shaping. 1665 * Synchronized to protect caches used in getContextKey. 1666 */ 1667 private synchronized void shapeContextually(char[] text, int start, int count, int ctxKey) { 1668 1669 // if we don't support this context, then don't shape 1670 if ((mask & (1<<ctxKey)) == 0) { 1671 ctxKey = EUROPEAN_KEY; 1672 } 1673 int lastkey = ctxKey; 1674 1675 int base = bases[ctxKey]; 1676 char minDigit = ctxKey == ETHIOPIC_KEY ? '1' : '0'; // Ethiopic doesn't use decimal zero 1677 1678 synchronized (NumericShaper.class) { 1679 for (int i = start, e = start + count; i < e; ++i) { 1680 char c = text[i]; 1681 if (c >= minDigit && c <= '\u0039') { 1682 text[i] = (char)(c + base); 1683 } 1684 1685 if (isStrongDirectional(c)) { 1686 int newkey = getContextKey(c); 1687 if (newkey != lastkey) { 1688 lastkey = newkey; 1689 1690 ctxKey = newkey; 1691 if (((mask & EASTERN_ARABIC) != 0) && 1692 (ctxKey == ARABIC_KEY || 1693 ctxKey == EASTERN_ARABIC_KEY)) { 1694 ctxKey = EASTERN_ARABIC_KEY; 1695 } else if (((mask & ARABIC) != 0) && 1696 (ctxKey == ARABIC_KEY || 1697 ctxKey == EASTERN_ARABIC_KEY)) { 1698 ctxKey = ARABIC_KEY; 1699 } else if ((mask & (1<<ctxKey)) == 0) { 1700 ctxKey = EUROPEAN_KEY; 1701 } 1702 1703 base = bases[ctxKey]; 1704 1705 minDigit = ctxKey == ETHIOPIC_KEY ? '1' : '0'; // Ethiopic doesn't use decimal zero 1706 } 1707 } 1708 } 1709 } 1710 } 1711 1712 private void shapeContextually(char[] text, int start, int count, Range ctxKey) { 1713 // if we don't support the specified context, then don't shape. 1714 if (ctxKey == null || !rangeSet.contains(ctxKey)) { 1715 ctxKey = Range.EUROPEAN; 1716 } 1717 1718 Range lastKey = ctxKey; 1719 int base = ctxKey.getDigitBase(); 1720 char minDigit = (char)('0' + ctxKey.getNumericBase()); 1721 final int end = start + count; 1722 for (int i = start; i < end; ++i) { 1723 char c = text[i]; 1724 if (c >= minDigit && c <= '9') { 1725 text[i] = (char)(c + base); 1726 continue; 1727 } 1728 if (isStrongDirectional(c)) { 1729 ctxKey = rangeForCodePoint(c); 1730 if (ctxKey != lastKey) { 1731 lastKey = ctxKey; 1732 base = ctxKey.getDigitBase(); 1733 minDigit = (char)('0' + ctxKey.getNumericBase()); 1734 } 1735 } 1736 } 1737 } 1738 1739 /** 1740 * Returns a hash code for this shaper. 1741 * @return this shaper's hash code. 1742 * @see java.lang.Object#hashCode 1743 */ 1744 public int hashCode() { 1745 int hash = mask; 1746 if (rangeSet != null) { 1747 // Use the CONTEXTUAL_MASK bit only for the enum-based 1748 // NumericShaper. A deserialized NumericShaper might have 1749 // bit masks. 1750 hash &= CONTEXTUAL_MASK; 1751 hash ^= rangeSet.hashCode(); 1752 } 1753 return hash; 1754 } 1755 1756 /** 1757 * Returns {@code true} if the specified object is an instance of 1758 * {@code NumericShaper} and shapes identically to this one, 1759 * regardless of the range representations, the bit mask or the 1760 * enum. For example, the following code produces {@code "true"}. 1761 * <blockquote><pre> 1762 * NumericShaper ns1 = NumericShaper.getShaper(NumericShaper.ARABIC); 1763 * NumericShaper ns2 = NumericShaper.getShaper(NumericShaper.Range.ARABIC); 1764 * System.out.println(ns1.equals(ns2)); 1765 * </pre></blockquote> 1766 * 1767 * @param o the specified object to compare to this 1768 * {@code NumericShaper} 1769 * @return {@code true} if {@code o} is an instance 1770 * of {@code NumericShaper} and shapes in the same way; 1771 * {@code false} otherwise. 1772 * @see java.lang.Object#equals(java.lang.Object) 1773 */ 1774 public boolean equals(Object o) { 1775 if (o != null) { 1776 try { 1777 NumericShaper rhs = (NumericShaper)o; 1778 if (rangeSet != null) { 1779 if (rhs.rangeSet != null) { 1780 return isContextual() == rhs.isContextual() 1781 && rangeSet.equals(rhs.rangeSet) 1782 && shapingRange == rhs.shapingRange; 1783 } 1784 return isContextual() == rhs.isContextual() 1785 && rangeSet.equals(Range.maskToRangeSet(rhs.mask)) 1786 && shapingRange == Range.indexToRange(rhs.key); 1787 } else if (rhs.rangeSet != null) { 1788 Set<Range> rset = Range.maskToRangeSet(mask); 1789 Range srange = Range.indexToRange(key); 1790 return isContextual() == rhs.isContextual() 1791 && rset.equals(rhs.rangeSet) 1792 && srange == rhs.shapingRange; 1793 } 1794 return rhs.mask == mask && rhs.key == key; 1795 } 1796 catch (ClassCastException e) { 1797 } 1798 } 1799 return false; 1800 } 1801 1802 /** 1803 * Returns a {@code String} that describes this shaper. This method 1804 * is used for debugging purposes only. 1805 * @return a {@code String} describing this shaper. 1806 */ 1807 public String toString() { 1808 StringBuilder buf = new StringBuilder(super.toString()); 1809 1810 buf.append("[contextual:").append(isContextual()); 1811 1812 String[] keyNames = null; 1813 if (isContextual()) { 1814 buf.append(", context:"); 1815 buf.append(shapingRange == null ? Range.values()[key] : shapingRange); 1816 } 1817 1818 if (rangeSet == null) { 1819 buf.append(", range(s): "); 1820 boolean first = true; 1821 for (int i = 0; i < NUM_KEYS; ++i) { 1822 if ((mask & (1 << i)) != 0) { 1823 if (first) { 1824 first = false; 1825 } else { 1826 buf.append(", "); 1827 } 1828 buf.append(Range.values()[i]); 1829 } 1830 } 1831 } else { 1832 buf.append(", range set: ").append(rangeSet); 1833 } 1834 buf.append(']'); 1835 1836 return buf.toString(); 1837 } 1838 1839 /** 1840 * Returns the index of the high bit in value (assuming le, actually 1841 * power of 2 >= value). value must be positive. 1842 */ 1843 private static int getHighBit(int value) { 1844 if (value <= 0) { 1845 return -32; 1846 } 1847 1848 int bit = 0; 1849 1850 if (value >= 1 << 16) { 1851 value >>= 16; 1852 bit += 16; 1853 } 1854 1855 if (value >= 1 << 8) { 1856 value >>= 8; 1857 bit += 8; 1858 } 1859 1860 if (value >= 1 << 4) { 1861 value >>= 4; 1862 bit += 4; 1863 } 1864 1865 if (value >= 1 << 2) { 1866 value >>= 2; 1867 bit += 2; 1868 } 1869 1870 if (value >= 1 << 1) { 1871 bit += 1; 1872 } 1873 1874 return bit; 1875 } 1876 1877 /** 1878 * fast binary search over subrange of array. 1879 */ 1880 private static int search(int value, int[] array, int start, int length) 1881 { 1882 int power = 1 << getHighBit(length); 1883 int extra = length - power; 1884 int probe = power; 1885 int index = start; 1886 1887 if (value >= array[index + extra]) { 1888 index += extra; 1889 } 1890 1891 while (probe > 1) { 1892 probe >>= 1; 1893 1894 if (value >= array[index + probe]) { 1895 index += probe; 1896 } 1897 } 1898 1899 return index; 1900 } 1901 1902 /** 1903 * Converts the {@code NumericShaper.Range} enum-based parameters, 1904 * if any, to the bit mask-based counterparts and writes this 1905 * object to the {@code stream}. Any enum constants that have no 1906 * bit mask-based counterparts are ignored in the conversion. 1907 * 1908 * @param stream the output stream to write to 1909 * @throws IOException if an I/O error occurs while writing to {@code stream} 1910 * @since 1.7 1911 */ 1912 private void writeObject(ObjectOutputStream stream) throws IOException { 1913 if (shapingRange != null) { 1914 int index = Range.toRangeIndex(shapingRange); 1915 if (index >= 0) { 1916 key = index; 1917 } 1918 } 1919 if (rangeSet != null) { 1920 mask |= Range.toRangeMask(rangeSet); 1921 } 1922 stream.defaultWriteObject(); 1923 } 1924 }