< prev index next >

src/java.desktop/share/classes/java/awt/font/NumericShaper.java

Print this page




  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 {


< prev index next >