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</code> 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</code> are typically applied 45 * as attributes to text with the 46 * {@link TextAttribute#NUMERIC_SHAPING NUMERIC_SHAPING} attribute 47 * of the <code>TextAttribute</code> class. 48 * For example, this code snippet causes a <code>TextLayout</code> 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</code>, 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 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 border=1 cellspacing=3 cellpadding=0 summary="NumericShaper constants precedence."> 110 * <tr> 111 * <th class="TableHeadingColor">Unicode Range</th> 112 * <th class="TableHeadingColor"><code>NumericShaper</code> Constants</th> 113 * <th class="TableHeadingColor">Precedence</th> 114 * </tr> 115 * <tr> 116 * <td rowspan="2">Arabic</td> 117 * <td>{@link NumericShaper#ARABIC NumericShaper.ARABIC}<br> 118 * {@link NumericShaper#EASTERN_ARABIC NumericShaper.EASTERN_ARABIC}</td> 119 * <td>{@link NumericShaper#EASTERN_ARABIC NumericShaper.EASTERN_ARABIC}</td> 120 * </tr> 121 * <tr> 122 * <td>{@link NumericShaper.Range#ARABIC}<br> 123 * {@link NumericShaper.Range#EASTERN_ARABIC}</td> 124 * <td>{@link NumericShaper.Range#EASTERN_ARABIC}</td> 125 * </tr> 126 * <tr> 127 * <td>Tai Tham</td> 128 * <td>{@link NumericShaper.Range#TAI_THAM_HORA}<br> 129 * {@link NumericShaper.Range#TAI_THAM_THAM}</td> 130 * <td>{@link NumericShaper.Range#TAI_THAM_THAM}</td> 131 * </tr> 132 * </table> 133 * 134 * @since 1.4 135 */ 136 137 public final class NumericShaper implements java.io.Serializable { 138 139 // For access from java.text.Bidi 140 static { 141 if (SharedSecrets.getJavaAWTFontAccess() == null) { 142 SharedSecrets.setJavaAWTFontAccess(new JavaAWTFontAccessImpl()); 143 } 144 } 145 146 /** 147 * A {@code NumericShaper.Range} represents a Unicode range of a 148 * script having its own decimal digits. For example, the {@link 149 * NumericShaper.Range#THAI} range has the Thai digits, THAI DIGIT 150 * ZERO (U+0E50) to THAI DIGIT NINE (U+0E59). 151 * 152 * <p>The <code>Range</code> enum replaces the traditional bit 153 * mask-based values (e.g., {@link NumericShaper#ARABIC}), and 154 * supports more Unicode ranges than the bit mask-based ones. For 155 * example, the following code using the bit mask: 156 * <blockquote><pre> 157 * NumericShaper.getContextualShaper(NumericShaper.ARABIC | 158 * NumericShaper.TAMIL, 159 * NumericShaper.EUROPEAN); 160 * </pre></blockquote> 161 * can be written using this enum as: 162 * <blockquote><pre> 163 * NumericShaper.getContextualShaper(EnumSet.of( 164 * NumericShaper.Range.ARABIC, 165 * NumericShaper.Range.TAMIL), 166 * NumericShaper.Range.EUROPEAN); 167 * </pre></blockquote> 168 * 169 * @since 1.7 170 */ 171 public static enum Range { 172 // The order of EUROPEAN to MOGOLIAN must be consistent 1323 * Returns a shaper for the provided Unicode 1324 * range. All Latin-1 (EUROPEAN) digits are converted to the 1325 * corresponding decimal digits of the specified Unicode range. 1326 * 1327 * @param singleRange the Unicode range given by a {@link 1328 * NumericShaper.Range} constant. 1329 * @return a non-contextual {@code NumericShaper}. 1330 * @throws NullPointerException if {@code singleRange} is {@code null} 1331 * @since 1.7 1332 */ 1333 public static NumericShaper getShaper(Range singleRange) { 1334 return new NumericShaper(singleRange, EnumSet.of(singleRange)); 1335 } 1336 1337 /** 1338 * Returns a contextual shaper for the provided unicode range(s). 1339 * Latin-1 (EUROPEAN) digits are converted to the decimal digits 1340 * corresponding to the range of the preceding text, if the 1341 * range is one of the provided ranges. Multiple ranges are 1342 * represented by or-ing the values together, such as, 1343 * <code>NumericShaper.ARABIC | NumericShaper.THAI</code>. The 1344 * shaper assumes EUROPEAN as the starting context, that is, if 1345 * EUROPEAN digits are encountered before any strong directional 1346 * text in the string, the context is presumed to be EUROPEAN, and 1347 * so the digits will not shape. 1348 * @param ranges the specified Unicode ranges 1349 * @return a shaper for the specified ranges 1350 */ 1351 public static NumericShaper getContextualShaper(int ranges) { 1352 ranges |= CONTEXTUAL_MASK; 1353 return new NumericShaper(EUROPEAN_KEY, ranges); 1354 } 1355 1356 /** 1357 * Returns a contextual shaper for the provided Unicode 1358 * range(s). The Latin-1 (EUROPEAN) digits are converted to the 1359 * decimal digits corresponding to the range of the preceding 1360 * text, if the range is one of the provided ranges. 1361 * 1362 * <p>The shaper assumes EUROPEAN as the starting context, that 1363 * is, if EUROPEAN digits are encountered before any strong 1364 * directional text in the string, the context is presumed to be 1365 * EUROPEAN, and so the digits will not shape. 1366 * 1367 * @param ranges the specified Unicode ranges 1368 * @return a contextual shaper for the specified ranges 1369 * @throws NullPointerException if {@code ranges} is {@code null}. 1370 * @since 1.7 1371 */ 1372 public static NumericShaper getContextualShaper(Set<Range> ranges) { 1373 NumericShaper shaper = new NumericShaper(Range.EUROPEAN, ranges); 1374 shaper.mask = CONTEXTUAL_MASK; 1375 return shaper; 1376 } 1377 1378 /** 1379 * Returns a contextual shaper for the provided unicode range(s). 1380 * Latin-1 (EUROPEAN) digits will be converted to the decimal digits 1381 * corresponding to the range of the preceding text, if the 1382 * range is one of the provided ranges. Multiple ranges are 1383 * represented by or-ing the values together, for example, 1384 * <code>NumericShaper.ARABIC | NumericShaper.THAI</code>. The 1385 * shaper uses defaultContext as the starting context. 1386 * @param ranges the specified Unicode ranges 1387 * @param defaultContext the starting context, such as 1388 * <code>NumericShaper.EUROPEAN</code> 1389 * @return a shaper for the specified Unicode ranges. 1390 * @throws IllegalArgumentException if the specified 1391 * <code>defaultContext</code> is not a single valid range. 1392 */ 1393 public static NumericShaper getContextualShaper(int ranges, int defaultContext) { 1394 int key = getKeyFromMask(defaultContext); 1395 ranges |= CONTEXTUAL_MASK; 1396 return new NumericShaper(key, ranges); 1397 } 1398 1399 /** 1400 * Returns a contextual shaper for the provided Unicode range(s). 1401 * The Latin-1 (EUROPEAN) digits will be converted to the decimal 1402 * digits corresponding to the range of the preceding text, if the 1403 * range is one of the provided ranges. The shaper uses {@code 1404 * defaultContext} as the starting context. 1405 * 1406 * @param ranges the specified Unicode ranges 1407 * @param defaultContext the starting context, such as 1408 * {@code NumericShaper.Range.EUROPEAN} 1409 * @return a contextual shaper for the specified Unicode ranges. 1410 * @throws NullPointerException 1411 * if {@code ranges} or {@code defaultContext} is {@code null} 1446 && rangeSet.contains(Range.TAI_THAM_HORA)) { 1447 rangeSet.remove(Range.TAI_THAM_HORA); 1448 } 1449 1450 rangeArray = rangeSet.toArray(new Range[rangeSet.size()]); 1451 if (rangeArray.length > BSEARCH_THRESHOLD) { 1452 // sort rangeArray for binary search 1453 Arrays.sort(rangeArray, 1454 new Comparator<Range>() { 1455 public int compare(Range s1, Range s2) { 1456 return s1.base > s2.base ? 1 : s1.base == s2.base ? 0 : -1; 1457 } 1458 }); 1459 } 1460 } 1461 1462 /** 1463 * Converts the digits in the text that occur between start and 1464 * start + count. 1465 * @param text an array of characters to convert 1466 * @param start the index into <code>text</code> to start 1467 * converting 1468 * @param count the number of characters in <code>text</code> 1469 * to convert 1470 * @throws IndexOutOfBoundsException if start or start + count is 1471 * out of bounds 1472 * @throws NullPointerException if text is null 1473 */ 1474 public void shape(char[] text, int start, int count) { 1475 checkParams(text, start, count); 1476 if (isContextual()) { 1477 if (rangeSet == null) { 1478 shapeContextually(text, start, count, key); 1479 } else { 1480 shapeContextually(text, start, count, shapingRange); 1481 } 1482 } else { 1483 shapeNonContextually(text, start, count); 1484 } 1485 } 1486 1487 /** 1488 * Converts the digits in the text that occur between start and 1489 * start + count, using the provided context. 1490 * Context is ignored if the shaper is not a contextual shaper. 1491 * @param text an array of characters 1492 * @param start the index into <code>text</code> to start 1493 * converting 1494 * @param count the number of characters in <code>text</code> 1495 * to convert 1496 * @param context the context to which to convert the 1497 * characters, such as <code>NumericShaper.EUROPEAN</code> 1498 * @throws IndexOutOfBoundsException if start or start + count is 1499 * out of bounds 1500 * @throws NullPointerException if text is null 1501 * @throws IllegalArgumentException if this is a contextual shaper 1502 * and the specified <code>context</code> is not a single valid 1503 * range. 1504 */ 1505 public void shape(char[] text, int start, int count, int context) { 1506 checkParams(text, start, count); 1507 if (isContextual()) { 1508 int ctxKey = getKeyFromMask(context); 1509 if (rangeSet == null) { 1510 shapeContextually(text, start, count, ctxKey); 1511 } else { 1512 shapeContextually(text, start, count, Range.values()[ctxKey]); 1513 } 1514 } else { 1515 shapeNonContextually(text, start, count); 1516 } 1517 } 1518 1519 /** 1520 * Converts the digits in the text that occur between {@code 1521 * start} and {@code start + count}, using the provided {@code 1522 * context}. {@code Context} is ignored if the shaper is not a 1553 } 1554 } else { 1555 shapeNonContextually(text, start, count); 1556 } 1557 } 1558 1559 private void checkParams(char[] text, int start, int count) { 1560 if (text == null) { 1561 throw new NullPointerException("text is null"); 1562 } 1563 if ((start < 0) 1564 || (start > text.length) 1565 || ((start + count) < 0) 1566 || ((start + count) > text.length)) { 1567 throw new IndexOutOfBoundsException( 1568 "bad start or count for text of length " + text.length); 1569 } 1570 } 1571 1572 /** 1573 * Returns a <code>boolean</code> indicating whether or not 1574 * this shaper shapes contextually. 1575 * @return <code>true</code> if this shaper is contextual; 1576 * <code>false</code> otherwise. 1577 */ 1578 public boolean isContextual() { 1579 return (mask & CONTEXTUAL_MASK) != 0; 1580 } 1581 1582 /** 1583 * Returns an <code>int</code> that ORs together the values for 1584 * all the ranges that will be shaped. 1585 * <p> 1586 * For example, to check if a shaper shapes to Arabic, you would use the 1587 * following: 1588 * <blockquote> 1589 * {@code if ((shaper.getRanges() & shaper.ARABIC) != 0) { ... } 1590 * </blockquote> 1591 * 1592 * <p>Note that this method supports only the bit mask-based 1593 * ranges. Call {@link #getRangeSet()} for the enum-based ranges. 1594 * 1595 * @return the values for all the ranges to be shaped. 1596 */ 1597 public int getRanges() { 1598 return mask & ~CONTEXTUAL_MASK; 1599 } 1600 1601 /** 1602 * Returns a {@code Set} representing all the Unicode ranges in 1603 * this {@code NumericShaper} that will be shaped. 1713 1714 /** 1715 * Returns a hash code for this shaper. 1716 * @return this shaper's hash code. 1717 * @see java.lang.Object#hashCode 1718 */ 1719 public int hashCode() { 1720 int hash = mask; 1721 if (rangeSet != null) { 1722 // Use the CONTEXTUAL_MASK bit only for the enum-based 1723 // NumericShaper. A deserialized NumericShaper might have 1724 // bit masks. 1725 hash &= CONTEXTUAL_MASK; 1726 hash ^= rangeSet.hashCode(); 1727 } 1728 return hash; 1729 } 1730 1731 /** 1732 * Returns {@code true} if the specified object is an instance of 1733 * <code>NumericShaper</code> and shapes identically to this one, 1734 * regardless of the range representations, the bit mask or the 1735 * enum. For example, the following code produces {@code "true"}. 1736 * <blockquote><pre> 1737 * NumericShaper ns1 = NumericShaper.getShaper(NumericShaper.ARABIC); 1738 * NumericShaper ns2 = NumericShaper.getShaper(NumericShaper.Range.ARABIC); 1739 * System.out.println(ns1.equals(ns2)); 1740 * </pre></blockquote> 1741 * 1742 * @param o the specified object to compare to this 1743 * <code>NumericShaper</code> 1744 * @return <code>true</code> if <code>o</code> is an instance 1745 * of <code>NumericShaper</code> and shapes in the same way; 1746 * <code>false</code> otherwise. 1747 * @see java.lang.Object#equals(java.lang.Object) 1748 */ 1749 public boolean equals(Object o) { 1750 if (o != null) { 1751 try { 1752 NumericShaper rhs = (NumericShaper)o; 1753 if (rangeSet != null) { 1754 if (rhs.rangeSet != null) { 1755 return isContextual() == rhs.isContextual() 1756 && rangeSet.equals(rhs.rangeSet) 1757 && shapingRange == rhs.shapingRange; 1758 } 1759 return isContextual() == rhs.isContextual() 1760 && rangeSet.equals(Range.maskToRangeSet(rhs.mask)) 1761 && shapingRange == Range.indexToRange(rhs.key); 1762 } else if (rhs.rangeSet != null) { 1763 Set<Range> rset = Range.maskToRangeSet(mask); 1764 Range srange = Range.indexToRange(key); 1765 return isContextual() == rhs.isContextual() 1766 && rset.equals(rhs.rangeSet) 1767 && srange == rhs.shapingRange; 1768 } 1769 return rhs.mask == mask && rhs.key == key; 1770 } 1771 catch (ClassCastException e) { 1772 } 1773 } 1774 return false; 1775 } 1776 1777 /** 1778 * Returns a <code>String</code> that describes this shaper. This method 1779 * is used for debugging purposes only. 1780 * @return a <code>String</code> describing this shaper. 1781 */ 1782 public String toString() { 1783 StringBuilder buf = new StringBuilder(super.toString()); 1784 1785 buf.append("[contextual:").append(isContextual()); 1786 1787 String[] keyNames = null; 1788 if (isContextual()) { 1789 buf.append(", context:"); 1790 buf.append(shapingRange == null ? Range.values()[key] : shapingRange); 1791 } 1792 1793 if (rangeSet == null) { 1794 buf.append(", range(s): "); 1795 boolean first = true; 1796 for (int i = 0; i < NUM_KEYS; ++i) { 1797 if ((mask & (1 << i)) != 0) { 1798 if (first) { 1799 first = false; 1800 } else { | 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 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 border=1 cellspacing=3 cellpadding=0 summary="NumericShaper constants precedence."> 110 * <tr> 111 * <th class="TableHeadingColor">Unicode Range</th> 112 * <th class="TableHeadingColor">{@code NumericShaper} Constants</th> 113 * <th class="TableHeadingColor">Precedence</th> 114 * </tr> 115 * <tr> 116 * <td rowspan="2">Arabic</td> 117 * <td>{@link NumericShaper#ARABIC NumericShaper.ARABIC}<br> 118 * {@link NumericShaper#EASTERN_ARABIC NumericShaper.EASTERN_ARABIC}</td> 119 * <td>{@link NumericShaper#EASTERN_ARABIC NumericShaper.EASTERN_ARABIC}</td> 120 * </tr> 121 * <tr> 122 * <td>{@link NumericShaper.Range#ARABIC}<br> 123 * {@link NumericShaper.Range#EASTERN_ARABIC}</td> 124 * <td>{@link NumericShaper.Range#EASTERN_ARABIC}</td> 125 * </tr> 126 * <tr> 127 * <td>Tai Tham</td> 128 * <td>{@link NumericShaper.Range#TAI_THAM_HORA}<br> 129 * {@link NumericShaper.Range#TAI_THAM_THAM}</td> 130 * <td>{@link NumericShaper.Range#TAI_THAM_THAM}</td> 131 * </tr> 132 * </table> 133 * 134 * @since 1.4 135 */ 136 137 public final class NumericShaper implements java.io.Serializable { 138 139 // For access from java.text.Bidi 140 static { 141 if (SharedSecrets.getJavaAWTFontAccess() == null) { 142 SharedSecrets.setJavaAWTFontAccess(new JavaAWTFontAccessImpl()); 143 } 144 } 145 146 /** 147 * A {@code NumericShaper.Range} represents a Unicode range of a 148 * script having its own decimal digits. For example, the {@link 149 * NumericShaper.Range#THAI} range has the Thai digits, THAI DIGIT 150 * ZERO (U+0E50) to THAI DIGIT NINE (U+0E59). 151 * 152 * <p>The {@code Range} enum replaces the traditional bit 153 * mask-based values (e.g., {@link NumericShaper#ARABIC}), and 154 * supports more Unicode ranges than the bit mask-based ones. For 155 * example, the following code using the bit mask: 156 * <blockquote><pre> 157 * NumericShaper.getContextualShaper(NumericShaper.ARABIC | 158 * NumericShaper.TAMIL, 159 * NumericShaper.EUROPEAN); 160 * </pre></blockquote> 161 * can be written using this enum as: 162 * <blockquote><pre> 163 * NumericShaper.getContextualShaper(EnumSet.of( 164 * NumericShaper.Range.ARABIC, 165 * NumericShaper.Range.TAMIL), 166 * NumericShaper.Range.EUROPEAN); 167 * </pre></blockquote> 168 * 169 * @since 1.7 170 */ 171 public static enum Range { 172 // The order of EUROPEAN to MOGOLIAN must be consistent 1323 * Returns a shaper for the provided Unicode 1324 * range. All Latin-1 (EUROPEAN) digits are converted to the 1325 * corresponding decimal digits of the specified Unicode range. 1326 * 1327 * @param singleRange the Unicode range given by a {@link 1328 * NumericShaper.Range} constant. 1329 * @return a non-contextual {@code NumericShaper}. 1330 * @throws NullPointerException if {@code singleRange} is {@code null} 1331 * @since 1.7 1332 */ 1333 public static NumericShaper getShaper(Range singleRange) { 1334 return new NumericShaper(singleRange, EnumSet.of(singleRange)); 1335 } 1336 1337 /** 1338 * Returns a contextual shaper for the provided unicode range(s). 1339 * Latin-1 (EUROPEAN) digits are converted to the decimal digits 1340 * corresponding to the range of the preceding text, if the 1341 * range is one of the provided ranges. Multiple ranges are 1342 * represented by or-ing the values together, such as, 1343 * {@code NumericShaper.ARABIC | NumericShaper.THAI}. The 1344 * shaper assumes EUROPEAN as the starting context, that is, if 1345 * EUROPEAN digits are encountered before any strong directional 1346 * text in the string, the context is presumed to be EUROPEAN, and 1347 * so the digits will not shape. 1348 * @param ranges the specified Unicode ranges 1349 * @return a shaper for the specified ranges 1350 */ 1351 public static NumericShaper getContextualShaper(int ranges) { 1352 ranges |= CONTEXTUAL_MASK; 1353 return new NumericShaper(EUROPEAN_KEY, ranges); 1354 } 1355 1356 /** 1357 * Returns a contextual shaper for the provided Unicode 1358 * range(s). The Latin-1 (EUROPEAN) digits are converted to the 1359 * decimal digits corresponding to the range of the preceding 1360 * text, if the range is one of the provided ranges. 1361 * 1362 * <p>The shaper assumes EUROPEAN as the starting context, that 1363 * is, if EUROPEAN digits are encountered before any strong 1364 * directional text in the string, the context is presumed to be 1365 * EUROPEAN, and so the digits will not shape. 1366 * 1367 * @param ranges the specified Unicode ranges 1368 * @return a contextual shaper for the specified ranges 1369 * @throws NullPointerException if {@code ranges} is {@code null}. 1370 * @since 1.7 1371 */ 1372 public static NumericShaper getContextualShaper(Set<Range> ranges) { 1373 NumericShaper shaper = new NumericShaper(Range.EUROPEAN, ranges); 1374 shaper.mask = CONTEXTUAL_MASK; 1375 return shaper; 1376 } 1377 1378 /** 1379 * Returns a contextual shaper for the provided unicode range(s). 1380 * Latin-1 (EUROPEAN) digits will be converted to the decimal digits 1381 * corresponding to the range of the preceding text, if the 1382 * range is one of the provided ranges. Multiple ranges are 1383 * represented by or-ing the values together, for example, 1384 * {@code NumericShaper.ARABIC | NumericShaper.THAI}. The 1385 * shaper uses defaultContext as the starting context. 1386 * @param ranges the specified Unicode ranges 1387 * @param defaultContext the starting context, such as 1388 * {@code NumericShaper.EUROPEAN} 1389 * @return a shaper for the specified Unicode ranges. 1390 * @throws IllegalArgumentException if the specified 1391 * {@code defaultContext} is not a single valid range. 1392 */ 1393 public static NumericShaper getContextualShaper(int ranges, int defaultContext) { 1394 int key = getKeyFromMask(defaultContext); 1395 ranges |= CONTEXTUAL_MASK; 1396 return new NumericShaper(key, ranges); 1397 } 1398 1399 /** 1400 * Returns a contextual shaper for the provided Unicode range(s). 1401 * The Latin-1 (EUROPEAN) digits will be converted to the decimal 1402 * digits corresponding to the range of the preceding text, if the 1403 * range is one of the provided ranges. The shaper uses {@code 1404 * defaultContext} as the starting context. 1405 * 1406 * @param ranges the specified Unicode ranges 1407 * @param defaultContext the starting context, such as 1408 * {@code NumericShaper.Range.EUROPEAN} 1409 * @return a contextual shaper for the specified Unicode ranges. 1410 * @throws NullPointerException 1411 * if {@code ranges} or {@code defaultContext} is {@code null} 1446 && rangeSet.contains(Range.TAI_THAM_HORA)) { 1447 rangeSet.remove(Range.TAI_THAM_HORA); 1448 } 1449 1450 rangeArray = rangeSet.toArray(new Range[rangeSet.size()]); 1451 if (rangeArray.length > BSEARCH_THRESHOLD) { 1452 // sort rangeArray for binary search 1453 Arrays.sort(rangeArray, 1454 new Comparator<Range>() { 1455 public int compare(Range s1, Range s2) { 1456 return s1.base > s2.base ? 1 : s1.base == s2.base ? 0 : -1; 1457 } 1458 }); 1459 } 1460 } 1461 1462 /** 1463 * Converts the digits in the text that occur between start and 1464 * start + count. 1465 * @param text an array of characters to convert 1466 * @param start the index into {@code text} to start 1467 * converting 1468 * @param count the number of characters in {@code text} 1469 * to convert 1470 * @throws IndexOutOfBoundsException if start or start + count is 1471 * out of bounds 1472 * @throws NullPointerException if text is null 1473 */ 1474 public void shape(char[] text, int start, int count) { 1475 checkParams(text, start, count); 1476 if (isContextual()) { 1477 if (rangeSet == null) { 1478 shapeContextually(text, start, count, key); 1479 } else { 1480 shapeContextually(text, start, count, shapingRange); 1481 } 1482 } else { 1483 shapeNonContextually(text, start, count); 1484 } 1485 } 1486 1487 /** 1488 * Converts the digits in the text that occur between start and 1489 * start + count, using the provided context. 1490 * Context is ignored if the shaper is not a contextual shaper. 1491 * @param text an array of characters 1492 * @param start the index into {@code text} to start 1493 * converting 1494 * @param count the number of characters in {@code text} 1495 * to convert 1496 * @param context the context to which to convert the 1497 * characters, such as {@code NumericShaper.EUROPEAN} 1498 * @throws IndexOutOfBoundsException if start or start + count is 1499 * out of bounds 1500 * @throws NullPointerException if text is null 1501 * @throws IllegalArgumentException if this is a contextual shaper 1502 * and the specified {@code context} is not a single valid 1503 * range. 1504 */ 1505 public void shape(char[] text, int start, int count, int context) { 1506 checkParams(text, start, count); 1507 if (isContextual()) { 1508 int ctxKey = getKeyFromMask(context); 1509 if (rangeSet == null) { 1510 shapeContextually(text, start, count, ctxKey); 1511 } else { 1512 shapeContextually(text, start, count, Range.values()[ctxKey]); 1513 } 1514 } else { 1515 shapeNonContextually(text, start, count); 1516 } 1517 } 1518 1519 /** 1520 * Converts the digits in the text that occur between {@code 1521 * start} and {@code start + count}, using the provided {@code 1522 * context}. {@code Context} is ignored if the shaper is not a 1553 } 1554 } else { 1555 shapeNonContextually(text, start, count); 1556 } 1557 } 1558 1559 private void checkParams(char[] text, int start, int count) { 1560 if (text == null) { 1561 throw new NullPointerException("text is null"); 1562 } 1563 if ((start < 0) 1564 || (start > text.length) 1565 || ((start + count) < 0) 1566 || ((start + count) > text.length)) { 1567 throw new IndexOutOfBoundsException( 1568 "bad start or count for text of length " + text.length); 1569 } 1570 } 1571 1572 /** 1573 * Returns a {@code boolean} indicating whether or not 1574 * this shaper shapes contextually. 1575 * @return {@code true} if this shaper is contextual; 1576 * {@code false} otherwise. 1577 */ 1578 public boolean isContextual() { 1579 return (mask & CONTEXTUAL_MASK) != 0; 1580 } 1581 1582 /** 1583 * Returns an {@code int} that ORs together the values for 1584 * all the ranges that will be shaped. 1585 * <p> 1586 * For example, to check if a shaper shapes to Arabic, you would use the 1587 * following: 1588 * <blockquote> 1589 * {@code if ((shaper.getRanges() & shaper.ARABIC) != 0) { ... } 1590 * </blockquote> 1591 * 1592 * <p>Note that this method supports only the bit mask-based 1593 * ranges. Call {@link #getRangeSet()} for the enum-based ranges. 1594 * 1595 * @return the values for all the ranges to be shaped. 1596 */ 1597 public int getRanges() { 1598 return mask & ~CONTEXTUAL_MASK; 1599 } 1600 1601 /** 1602 * Returns a {@code Set} representing all the Unicode ranges in 1603 * this {@code NumericShaper} that will be shaped. 1713 1714 /** 1715 * Returns a hash code for this shaper. 1716 * @return this shaper's hash code. 1717 * @see java.lang.Object#hashCode 1718 */ 1719 public int hashCode() { 1720 int hash = mask; 1721 if (rangeSet != null) { 1722 // Use the CONTEXTUAL_MASK bit only for the enum-based 1723 // NumericShaper. A deserialized NumericShaper might have 1724 // bit masks. 1725 hash &= CONTEXTUAL_MASK; 1726 hash ^= rangeSet.hashCode(); 1727 } 1728 return hash; 1729 } 1730 1731 /** 1732 * Returns {@code true} if the specified object is an instance of 1733 * {@code NumericShaper} and shapes identically to this one, 1734 * regardless of the range representations, the bit mask or the 1735 * enum. For example, the following code produces {@code "true"}. 1736 * <blockquote><pre> 1737 * NumericShaper ns1 = NumericShaper.getShaper(NumericShaper.ARABIC); 1738 * NumericShaper ns2 = NumericShaper.getShaper(NumericShaper.Range.ARABIC); 1739 * System.out.println(ns1.equals(ns2)); 1740 * </pre></blockquote> 1741 * 1742 * @param o the specified object to compare to this 1743 * {@code NumericShaper} 1744 * @return {@code true} if {@code o} is an instance 1745 * of {@code NumericShaper} and shapes in the same way; 1746 * {@code false} otherwise. 1747 * @see java.lang.Object#equals(java.lang.Object) 1748 */ 1749 public boolean equals(Object o) { 1750 if (o != null) { 1751 try { 1752 NumericShaper rhs = (NumericShaper)o; 1753 if (rangeSet != null) { 1754 if (rhs.rangeSet != null) { 1755 return isContextual() == rhs.isContextual() 1756 && rangeSet.equals(rhs.rangeSet) 1757 && shapingRange == rhs.shapingRange; 1758 } 1759 return isContextual() == rhs.isContextual() 1760 && rangeSet.equals(Range.maskToRangeSet(rhs.mask)) 1761 && shapingRange == Range.indexToRange(rhs.key); 1762 } else if (rhs.rangeSet != null) { 1763 Set<Range> rset = Range.maskToRangeSet(mask); 1764 Range srange = Range.indexToRange(key); 1765 return isContextual() == rhs.isContextual() 1766 && rset.equals(rhs.rangeSet) 1767 && srange == rhs.shapingRange; 1768 } 1769 return rhs.mask == mask && rhs.key == key; 1770 } 1771 catch (ClassCastException e) { 1772 } 1773 } 1774 return false; 1775 } 1776 1777 /** 1778 * Returns a {@code String} that describes this shaper. This method 1779 * is used for debugging purposes only. 1780 * @return a {@code String} describing this shaper. 1781 */ 1782 public String toString() { 1783 StringBuilder buf = new StringBuilder(super.toString()); 1784 1785 buf.append("[contextual:").append(isContextual()); 1786 1787 String[] keyNames = null; 1788 if (isContextual()) { 1789 buf.append(", context:"); 1790 buf.append(shapingRange == null ? Range.values()[key] : shapingRange); 1791 } 1792 1793 if (rangeSet == null) { 1794 buf.append(", range(s): "); 1795 boolean first = true; 1796 for (int i = 0; i < NUM_KEYS; ++i) { 1797 if ((mask & (1 << i)) != 0) { 1798 if (first) { 1799 first = false; 1800 } else { |