You are viewing a plain text version of this content. The canonical link for it is here.
Posted to fop-commits@xmlgraphics.apache.org by ga...@apache.org on 2012/02/26 03:29:29 UTC

svn commit: r1293736 [14/38] - in /xmlgraphics/fop/trunk: ./ src/codegen/java/org/apache/fop/tools/ src/codegen/unicode/java/org/apache/fop/complexscripts/ src/codegen/unicode/java/org/apache/fop/complexscripts/bidi/ src/documentation/content/xdocs/tru...

Added: xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/util/CharScript.java
URL: http://svn.apache.org/viewvc/xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/util/CharScript.java?rev=1293736&view=auto
==============================================================================
--- xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/util/CharScript.java (added)
+++ xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/util/CharScript.java Sun Feb 26 02:29:01 2012
@@ -0,0 +1,930 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.util;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.fop.util.CharUtilities;
+
+// CSOFF: AvoidNestedBlocksCheck
+// CSOFF: InnerAssignmentCheck
+// CSOFF: LineLengthCheck
+// CSOFF: SimplifyBooleanReturnCheck
+// CSOFF: WhitespaceAfterCheck
+
+/**
+ * Script related utilities.
+ * @author Glenn Adams
+ */
+public final class CharScript {
+
+    //
+    // The following script codes are based on ISO 15924. Codes less than 1000 are
+    // official assignments from 15924; those equal to or greater than 1000 are FOP
+    // implementation specific.
+    // 
+    /** hebrew script constant */
+    public static final int SCRIPT_HEBREW                               = 125;  // 'hebr'
+    /** mongolian script constant */
+    public static final int SCRIPT_MONGOLIAN                            = 145;  // 'mong'
+    /** arabic script constant */
+    public static final int SCRIPT_ARABIC                               = 160;  // 'arab'
+    /** greek script constant */
+    public static final int SCRIPT_GREEK                                = 200;  // 'grek'
+    /** latin script constant */
+    public static final int SCRIPT_LATIN                                = 215;  // 'latn'
+    /** cyrillic script constant */
+    public static final int SCRIPT_CYRILLIC                             = 220;  // 'cyrl'
+    /** georgian script constant */
+    public static final int SCRIPT_GEORGIAN                             = 240;  // 'geor'
+    /** bopomofo script constant */
+    public static final int SCRIPT_BOPOMOFO                             = 285;  // 'bopo'
+    /** hangul script constant */
+    public static final int SCRIPT_HANGUL                               = 286;  // 'hang'
+    /** gurmukhi script constant */
+    public static final int SCRIPT_GURMUKHI                             = 310;  // 'guru'
+    /** gurmukhi 2 script constant */
+    public static final int SCRIPT_GURMUKHI_2                           = 1310; // 'gur2'       -- MSFT (pseudo) script tag for variant shaping semantics
+    /** devanagari script constant */
+    public static final int SCRIPT_DEVANAGARI                           = 315;  // 'deva'
+    /** devanagari 2 script constant */
+    public static final int SCRIPT_DEVANAGARI_2                         = 1315; // 'dev2'       -- MSFT (pseudo) script tag for variant shaping semantics
+    /** gujarati script constant */
+    public static final int SCRIPT_GUJARATI                             = 320;  // 'gujr'
+    /** gujarati 2 script constant */
+    public static final int SCRIPT_GUJARATI_2                           = 1320; // 'gjr2'       -- MSFT (pseudo) script tag for variant shaping semantics
+    /** bengali script constant */
+    public static final int SCRIPT_BENGALI                              = 326;  // 'beng'
+    /** bengali 2 script constant */
+    public static final int SCRIPT_BENGALI_2                            = 1326; // 'bng2'       -- MSFT (pseudo) script tag for variant shaping semantics
+    /** oriya script constant */
+    public static final int SCRIPT_ORIYA                                = 327;  // 'orya'
+    /** oriya 2 script constant */
+    public static final int SCRIPT_ORIYA_2                              = 1327; // 'ory2'       -- MSFT (pseudo) script tag for variant shaping semantics
+    /** tibetan script constant */
+    public static final int SCRIPT_TIBETAN                              = 330;  // 'tibt'
+    /** telugu script constant */
+    public static final int SCRIPT_TELUGU                               = 340;  // 'telu'
+    /** telugu 2 script constant */
+    public static final int SCRIPT_TELUGU_2                             = 1340; // 'tel2'       -- MSFT (pseudo) script tag for variant shaping semantics
+    /** kannada script constant */
+    public static final int SCRIPT_KANNADA                              = 345;  // 'knda'
+    /** kannada 2 script constant */
+    public static final int SCRIPT_KANNADA_2                            = 1345; // 'knd2'       -- MSFT (pseudo) script tag for variant shaping semantics
+    /** tamil script constant */
+    public static final int SCRIPT_TAMIL                                = 346;  // 'taml'
+    /** tamil 2 script constant */
+    public static final int SCRIPT_TAMIL_2                              = 1346; // 'tml2'       -- MSFT (pseudo) script tag for variant shaping semantics
+    /** malayalam script constant */
+    public static final int SCRIPT_MALAYALAM                            = 347;  // 'mlym'
+    /** malayalam 2 script constant */
+    public static final int SCRIPT_MALAYALAM_2                          = 1347; // 'mlm2'       -- MSFT (pseudo) script tag for variant shaping semantics
+    /** sinhalese script constant */
+    public static final int SCRIPT_SINHALESE                            = 348;  // 'sinh'
+    /** burmese script constant */
+    public static final int SCRIPT_BURMESE                              = 350;  // 'mymr'
+    /** thai script constant */
+    public static final int SCRIPT_THAI                                 = 352;  // 'thai'
+    /** khmer script constant */
+    public static final int SCRIPT_KHMER                                = 355;  // 'khmr'
+    /** lao script constant */
+    public static final int SCRIPT_LAO                                  = 356;  // 'laoo'
+    /** hiragana script constant */
+    public static final int SCRIPT_HIRAGANA                             = 410;  // 'hira'
+    /** ethiopic script constant */
+    public static final int SCRIPT_ETHIOPIC                             = 430;  // 'ethi'
+    /** han script constant */
+    public static final int SCRIPT_HAN                                  = 500;  // 'hani'
+    /** katakana script constant */
+    public static final int SCRIPT_KATAKANA                             = 410;  // 'kana'
+    /** math script constant */
+    public static final int SCRIPT_MATH                                 = 995;  // 'zmth'
+    /** symbol script constant */
+    public static final int SCRIPT_SYMBOL                               = 996;  // 'zsym'
+    /** undetermined script constant */
+    public static final int SCRIPT_UNDETERMINED                         = 998;  // 'zyyy'
+    /** uncoded script constant */
+    public static final int SCRIPT_UNCODED                              = 999;  // 'zzzz'
+
+    /**
+      * A static (class) parameter indicating whether V2 indic shaping
+      * rules apply or not, with default being <code>true</code>.
+      */
+    private static final boolean useV2Indic = true; // CSOK: ConstantNameCheck
+
+    private CharScript() {
+    }
+
+    /**
+     * Determine if character c is punctuation.
+     * @param c a character represented as a unicode scalar value
+     * @return true if character is punctuation
+     */
+    public static boolean isPunctuation ( int c ) {
+        if ( ( c >= 0x0021 ) && ( c <= 0x002F ) ) {             // basic latin punctuation
+            return true;
+        } else if ( ( c >= 0x003A ) && ( c <= 0x0040 ) ) {      // basic latin punctuation
+            return true;
+        } else if ( ( c >= 0x005F ) && ( c <= 0x0060 ) ) {      // basic latin punctuation
+            return true;
+        } else if ( ( c >= 0x007E ) && ( c <= 0x007E ) ) {      // basic latin punctuation
+            return true;
+        } else if ( ( c >= 0x007E ) && ( c <= 0x007E ) ) {      // basic latin punctuation
+            return true;
+        } else if ( ( c >= 0x00A1 ) && ( c <= 0x00BF ) ) {      // latin supplement punctuation
+            return true;
+        } else if ( ( c >= 0x00D7 ) && ( c <= 0x00D7 ) ) {      // latin supplement punctuation
+            return true;
+        } else if ( ( c >= 0x00F7 ) && ( c <= 0x00F7 ) ) {      // latin supplement punctuation
+            return true;
+        } else if ( ( c >= 0x2000 ) && ( c <= 0x206F ) ) {      // general punctuation
+            return true;
+        } else {                                                // [TBD] - not complete
+            return false;
+        }
+    }
+
+    /**
+     * Determine if character c is a digit.
+     * @param c a character represented as a unicode scalar value
+     * @return true if character is a digit
+     */
+    public static boolean isDigit ( int c ) {
+        if ( ( c >= 0x0030 ) && ( c <= 0x0039 ) ) {             // basic latin digits
+            return true;
+        } else {                                                // [TBD] - not complete
+            return false;
+        }
+    }
+
+    /**
+     * Determine if character c belong to the hebrew script.
+     * @param c a character represented as a unicode scalar value
+     * @return true if character belongs to hebrew script
+     */
+    public static boolean isHebrew ( int c ) {
+        if ( ( c >= 0x0590 ) && ( c <= 0x05FF ) ) {             // hebrew block
+            return true;
+        } else if ( ( c >= 0xFB00 ) && ( c <= 0xFB4F ) ) {      // hebrew presentation forms block
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Determine if character c belong to the mongolian script.
+     * @param c a character represented as a unicode scalar value
+     * @return true if character belongs to mongolian script
+     */
+    public static boolean isMongolian ( int c ) {
+        if ( ( c >= 0x1800 ) && ( c <= 0x18AF ) ) {             // mongolian block
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Determine if character c belong to the arabic script.
+     * @param c a character represented as a unicode scalar value
+     * @return true if character belongs to arabic script
+     */
+    public static boolean isArabic ( int c ) {
+        if ( ( c >= 0x0600 ) && ( c <= 0x06FF ) ) {             // arabic block
+            return true;
+        } else if ( ( c >= 0x0750 ) && ( c <= 0x077F ) ) {      // arabic supplement block
+            return true;
+        } else if ( ( c >= 0xFB50 ) && ( c <= 0xFDFF ) ) {      // arabic presentation forms a block
+            return true;
+        } else if ( ( c >= 0xFE70 ) && ( c <= 0xFEFF ) ) {      // arabic presentation forms b block
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Determine if character c belong to the greek script.
+     * @param c a character represented as a unicode scalar value
+     * @return true if character belongs to greek script
+     */
+    public static boolean isGreek ( int c ) {
+        if ( ( c >= 0x0370 ) && ( c <= 0x03FF ) ) {             // greek (and coptic) block
+            return true;
+        } else if ( ( c >= 0x1F00 ) && ( c <= 0x1FFF ) ) {      // greek extended block
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Determine if character c belong to the latin script.
+     * @param c a character represented as a unicode scalar value
+     * @return true if character belongs to latin script
+     */
+    public static boolean isLatin ( int c ) {
+        if ( ( c >= 0x0041 ) && ( c <= 0x005A ) ) {             // basic latin upper case
+            return true;
+        } else if ( ( c >= 0x0061 ) && ( c <= 0x007A ) ) {      // basic latin lower case
+            return true;
+        } else if ( ( c >= 0x00C0 ) && ( c <= 0x00D6 ) ) {      // latin supplement upper case
+            return true;
+        } else if ( ( c >= 0x00D8 ) && ( c <= 0x00DF ) ) {      // latin supplement upper case
+            return true;
+        } else if ( ( c >= 0x00E0 ) && ( c <= 0x00F6 ) ) {      // latin supplement lower case
+            return true;
+        } else if ( ( c >= 0x00F8 ) && ( c <= 0x00FF ) ) {      // latin supplement lower case
+            return true;
+        } else if ( ( c >= 0x0100 ) && ( c <= 0x017F ) ) {      // latin extended a
+            return true;
+        } else if ( ( c >= 0x0180 ) && ( c <= 0x024F ) ) {      // latin extended b
+            return true;
+        } else if ( ( c >= 0x1E00 ) && ( c <= 0x1EFF ) ) {      // latin extended additional
+            return true;
+        } else if ( ( c >= 0x2C60 ) && ( c <= 0x2C7F ) ) {      // latin extended c
+            return true;
+        } else if ( ( c >= 0xA720 ) && ( c <= 0xA7FF ) ) {      // latin extended d
+            return true;
+        } else if ( ( c >= 0xFB00 ) && ( c <= 0xFB0F ) ) {      // latin ligatures
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Determine if character c belong to the cyrillic script.
+     * @param c a character represented as a unicode scalar value
+     * @return true if character belongs to cyrillic script
+     */
+    public static boolean isCyrillic ( int c ) {
+        if ( ( c >= 0x0400 ) && ( c <= 0x04FF ) ) {             // cyrillic block
+            return true;
+        } else if ( ( c >= 0x0500 ) && ( c <= 0x052F ) ) {      // cyrillic supplement block
+            return true;
+        } else if ( ( c >= 0x2DE0 ) && ( c <= 0x2DFF ) ) {      // cyrillic extended-a block
+            return true;
+        } else if ( ( c >= 0xA640 ) && ( c <= 0xA69F ) ) {      // cyrillic extended-b block
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Determine if character c belong to the georgian script.
+     * @param c a character represented as a unicode scalar value
+     * @return true if character belongs to georgian script
+     */
+    public static boolean isGeorgian ( int c ) {
+        if ( ( c >= 0x10A0 ) && ( c <= 0x10FF ) ) {             // georgian block
+            return true;
+        } else if ( ( c >= 0x2D00 ) && ( c <= 0x2D2F ) ) {      // georgian supplement block
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Determine if character c belong to the hangul script.
+     * @param c a character represented as a unicode scalar value
+     * @return true if character belongs to hangul script
+     */
+    public static boolean isHangul ( int c ) {
+        if ( ( c >= 0x1100 ) && ( c <= 0x11FF ) ) {             // hangul jamo
+            return true;
+        } else if ( ( c >= 0x3130 ) && ( c <= 0x318F ) ) {      // hangul compatibility jamo
+            return true;
+        } else if ( ( c >= 0xA960 ) && ( c <= 0xA97F ) ) {      // hangul jamo extended a
+            return true;
+        } else if ( ( c >= 0xAC00 ) && ( c <= 0xD7A3 ) ) {      // hangul syllables
+            return true;
+        } else if ( ( c >= 0xD7B0 ) && ( c <= 0xD7FF ) ) {      // hangul jamo extended a
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Determine if character c belong to the gurmukhi script.
+     * @param c a character represented as a unicode scalar value
+     * @return true if character belongs to gurmukhi script
+     */
+    public static boolean isGurmukhi ( int c ) {
+        if ( ( c >= 0x0A00 ) && ( c <= 0x0A7F ) ) {             // gurmukhi block
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Determine if character c belong to the devanagari script.
+     * @param c a character represented as a unicode scalar value
+     * @return true if character belongs to devanagari script
+     */
+    public static boolean isDevanagari ( int c ) {
+        if ( ( c >= 0x0900 ) && ( c <= 0x097F ) ) {             // devangari block
+            return true;
+        } else if ( ( c >= 0xA8E0 ) && ( c <= 0xA8FF ) ) {      // devangari extended block
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Determine if character c belong to the gujarati script.
+     * @param c a character represented as a unicode scalar value
+     * @return true if character belongs to gujarati script
+     */
+    public static boolean isGujarati ( int c ) {
+        if ( ( c >= 0x0A80 ) && ( c <= 0x0AFF ) ) {             // gujarati block
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Determine if character c belong to the bengali script.
+     * @param c a character represented as a unicode scalar value
+     * @return true if character belongs to bengali script
+     */
+    public static boolean isBengali ( int c ) {
+        if ( ( c >= 0x0980 ) && ( c <= 0x09FF ) ) {             // bengali block
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Determine if character c belong to the oriya script.
+     * @param c a character represented as a unicode scalar value
+     * @return true if character belongs to oriya script
+     */
+    public static boolean isOriya ( int c ) {
+        if ( ( c >= 0x0B00 ) && ( c <= 0x0B7F ) ) {             // oriya block
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Determine if character c belong to the tibetan script.
+     * @param c a character represented as a unicode scalar value
+     * @return true if character belongs to tibetan script
+     */
+    public static boolean isTibetan ( int c ) {
+        if ( ( c >= 0x0F00 ) && ( c <= 0x0FFF ) ) {             // tibetan block
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Determine if character c belong to the telugu script.
+     * @param c a character represented as a unicode scalar value
+     * @return true if character belongs to telugu script
+     */
+    public static boolean isTelugu ( int c ) {
+        if ( ( c >= 0x0C00 ) && ( c <= 0x0C7F ) ) {             // telugu block
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Determine if character c belong to the kannada script.
+     * @param c a character represented as a unicode scalar value
+     * @return true if character belongs to kannada script
+     */
+    public static boolean isKannada ( int c ) {
+        if ( ( c >= 0x0C00 ) && ( c <= 0x0C7F ) ) {             // kannada block
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Determine if character c belong to the tamil script.
+     * @param c a character represented as a unicode scalar value
+     * @return true if character belongs to tamil script
+     */
+    public static boolean isTamil ( int c ) {
+        if ( ( c >= 0x0B80 ) && ( c <= 0x0BFF ) ) {             // tamil block
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Determine if character c belong to the malayalam script.
+     * @param c a character represented as a unicode scalar value
+     * @return true if character belongs to malayalam script
+     */
+    public static boolean isMalayalam ( int c ) {
+        if ( ( c >= 0x0D00 ) && ( c <= 0x0D7F ) ) {             // malayalam block
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Determine if character c belong to the sinhalese script.
+     * @param c a character represented as a unicode scalar value
+     * @return true if character belongs to sinhalese script
+     */
+    public static boolean isSinhalese ( int c ) {
+        if ( ( c >= 0x0D80 ) && ( c <= 0x0DFF ) ) {             // sinhala block
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Determine if character c belong to the burmese script.
+     * @param c a character represented as a unicode scalar value
+     * @return true if character belongs to burmese script
+     */
+    public static boolean isBurmese ( int c ) {
+        if ( ( c >= 0x1000 ) && ( c <= 0x109F ) ) {             // burmese (myanmar) block
+            return true;
+        } else if ( ( c >= 0xAA60 ) && ( c <= 0xAA7F ) ) {      // burmese (myanmar) extended block
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Determine if character c belong to the thai script.
+     * @param c a character represented as a unicode scalar value
+     * @return true if character belongs to thai script
+     */
+    public static boolean isThai ( int c ) {
+        if ( ( c >= 0x0E00 ) && ( c <= 0x0E7F ) ) {             // thai block
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Determine if character c belong to the khmer script.
+     * @param c a character represented as a unicode scalar value
+     * @return true if character belongs to khmer script
+     */
+    public static boolean isKhmer ( int c ) {
+        if ( ( c >= 0x1780 ) && ( c <= 0x17FF ) ) {             // khmer block
+            return true;
+        } else if ( ( c >= 0x19E0 ) && ( c <= 0x19FF ) ) {      // khmer symbols block
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Determine if character c belong to the lao script.
+     * @param c a character represented as a unicode scalar value
+     * @return true if character belongs to lao script
+     */
+    public static boolean isLao ( int c ) {
+        if ( ( c >= 0x0E80 ) && ( c <= 0x0EFF ) ) {             // lao block
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Determine if character c belong to the ethiopic (amharic) script.
+     * @param c a character represented as a unicode scalar value
+     * @return true if character belongs to ethiopic (amharic) script
+     */
+    public static boolean isEthiopic ( int c ) {
+        if ( ( c >= 0x1200 ) && ( c <= 0x137F ) ) {             // ethiopic block
+            return true;
+        } else if ( ( c >= 0x1380 ) && ( c <= 0x139F ) ) {      // ethoipic supplement block
+            return true;
+        } else if ( ( c >= 0x2D80 ) && ( c <= 0x2DDF ) ) {      // ethoipic extended block
+            return true;
+        } else if ( ( c >= 0xAB00 ) && ( c <= 0xAB2F ) ) {      // ethoipic extended-a block
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Determine if character c belong to the han (unified cjk) script.
+     * @param c a character represented as a unicode scalar value
+     * @return true if character belongs to han (unified cjk) script
+     */
+    public static boolean isHan ( int c ) {
+        if ( ( c >= 0x3400 ) && ( c <= 0x4DBF ) ) {             
+            return true; // cjk unified ideographs extension a
+        } else if ( ( c >= 0x4E00 ) && ( c <= 0x9FFF ) ) {      
+            return true; // cjk unified ideographs
+        } else if ( ( c >= 0xF900 ) && ( c <= 0xFAFF ) ) {      
+            return true; // cjk compatibility ideographs
+        } else if ( ( c >= 0x20000 ) && ( c <= 0x2A6DF ) ) {    
+            return true; // cjk unified ideographs extension b
+        } else if ( ( c >= 0x2A700 ) && ( c <= 0x2B73F ) ) {    
+            return true; // cjk unified ideographs extension c
+        } else if ( ( c >= 0x2F800 ) && ( c <= 0x2FA1F ) ) {    
+            return true; // cjk compatibility ideographs supplement
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Determine if character c belong to the bopomofo script.
+     * @param c a character represented as a unicode scalar value
+     * @return true if character belongs to bopomofo script
+     */
+    public static boolean isBopomofo ( int c ) {
+        if ( ( c >= 0x3100 ) && ( c <= 0x312F ) ) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Determine if character c belong to the hiragana script.
+     * @param c a character represented as a unicode scalar value
+     * @return true if character belongs to hiragana script
+     */
+    public static boolean isHiragana ( int c ) {
+        if ( ( c >= 0x3040 ) && ( c <= 0x309F ) ) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Determine if character c belong to the katakana script.
+     * @param c a character represented as a unicode scalar value
+     * @return true if character belongs to katakana script
+     */
+    public static boolean isKatakana ( int c ) {
+        if ( ( c >= 0x30A0 ) && ( c <= 0x30FF ) ) {
+            return true;
+        } else if ( ( c >= 0x31F0 ) && ( c <= 0x31FF ) ) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Obtain ISO15924 numeric script code of character. If script is not or cannot be determined,
+     * then the script code 998 ('zyyy') is returned.
+     * @param c the character to obtain script
+     * @return an ISO15924 script code
+     */
+    public static int scriptOf ( int c ) { // [TBD] - needs optimization!!!
+        if ( CharUtilities.isAnySpace ( c ) ) {
+            return SCRIPT_UNDETERMINED;
+        } else if ( isPunctuation ( c ) ) {
+            return SCRIPT_UNDETERMINED;
+        } else if ( isDigit ( c ) ) {
+            return SCRIPT_UNDETERMINED;
+        } else if ( isLatin ( c ) ) {
+            return SCRIPT_LATIN;
+        } else if ( isCyrillic ( c ) ) {
+            return SCRIPT_CYRILLIC;
+        } else if ( isGreek ( c ) ) {
+            return SCRIPT_GREEK;
+        } else if ( isHan ( c ) ) {
+            return SCRIPT_HAN;
+        } else if ( isBopomofo ( c ) ) {
+            return SCRIPT_BOPOMOFO;
+        } else if ( isKatakana ( c ) ) {
+            return SCRIPT_KATAKANA;
+        } else if ( isHiragana ( c ) ) {
+            return SCRIPT_HIRAGANA;
+        } else if ( isHangul ( c ) ) {
+            return SCRIPT_HANGUL;
+        } else if ( isArabic ( c ) ) {
+            return SCRIPT_ARABIC;
+        } else if ( isHebrew ( c ) ) {
+            return SCRIPT_HEBREW;
+        } else if ( isMongolian ( c ) ) {
+            return SCRIPT_MONGOLIAN;
+        } else if ( isGeorgian ( c ) ) {
+            return SCRIPT_GEORGIAN;
+        } else if ( isGurmukhi ( c ) ) {
+            return useV2IndicRules ( SCRIPT_GURMUKHI );
+        } else if ( isDevanagari ( c ) ) {
+            return useV2IndicRules ( SCRIPT_DEVANAGARI );
+        } else if ( isGujarati ( c ) ) {
+            return useV2IndicRules ( SCRIPT_GUJARATI );
+        } else if ( isBengali ( c ) ) {
+            return useV2IndicRules ( SCRIPT_BENGALI );
+        } else if ( isOriya ( c ) ) {
+            return useV2IndicRules ( SCRIPT_ORIYA );
+        } else if ( isTibetan ( c ) ) {
+            return SCRIPT_TIBETAN;
+        } else if ( isTelugu ( c ) ) {
+            return useV2IndicRules ( SCRIPT_TELUGU );
+        } else if ( isKannada ( c ) ) {
+            return useV2IndicRules ( SCRIPT_KANNADA );
+        } else if ( isTamil ( c ) ) {
+            return useV2IndicRules ( SCRIPT_TAMIL );
+        } else if ( isMalayalam ( c ) ) {
+            return useV2IndicRules ( SCRIPT_MALAYALAM );
+        } else if ( isSinhalese ( c ) ) {
+            return SCRIPT_SINHALESE;
+        } else if ( isBurmese ( c ) ) {
+            return SCRIPT_BURMESE;
+        } else if ( isThai ( c ) ) {
+            return SCRIPT_THAI;
+        } else if ( isKhmer ( c ) ) {
+            return SCRIPT_KHMER;
+        } else if ( isLao ( c ) ) {
+            return SCRIPT_LAO;
+        } else if ( isEthiopic ( c ) ) {
+            return SCRIPT_ETHIOPIC;
+        } else {
+            return SCRIPT_UNDETERMINED;
+        }
+    }
+
+    /**
+     * Obtain the V2 indic script code corresponding to V1 indic script code SC if
+     * and only iff V2 indic rules apply; otherwise return SC.
+     * @param sc a V1 indic script code
+     * @return either SC or the V2 flavor of SC if V2 indic rules apply
+     */
+    public static int useV2IndicRules ( int sc ) {
+        if ( useV2Indic ) {
+            return ( sc < 1000 ) ? ( sc + 1000 ) : sc;
+        } else {
+            return sc;
+        }
+    }
+
+    /**
+     * Obtain the  script codes of each character in a character sequence. If script
+     * is not or cannot be determined for some character, then the script code 998
+     * ('zyyy') is returned.
+     * @param cs the character sequence
+     * @return a (possibly empty) array of script codes
+     */
+    public static int[] scriptsOf ( CharSequence cs ) {
+        Set s = new HashSet();
+        for ( int i = 0, n = cs.length(); i < n; i++ ) {
+            s.add ( Integer.valueOf ( scriptOf ( cs.charAt ( i ) ) ) );
+        }
+        int[] sa = new int [ s.size() ];
+        int ns = 0;
+        for ( Iterator it = s.iterator(); it.hasNext();) {
+            sa [ ns++ ] = ( (Integer) it.next() ) .intValue();
+        }
+        Arrays.sort ( sa );
+        return sa;
+    }
+
+    /**
+     * Determine the dominant script of a character sequence.
+     * @param cs the character sequence
+     * @return the dominant script or SCRIPT_UNDETERMINED
+     */
+    public static int dominantScript ( CharSequence cs ) {
+        Map m = new HashMap();
+        for ( int i = 0, n = cs.length(); i < n; i++ ) {
+            int c = cs.charAt ( i );
+            int s = scriptOf ( c );
+            Integer k = Integer.valueOf ( s );
+            Integer v = (Integer) m.get ( k );
+            if ( v != null ) {
+                m.put ( k, Integer.valueOf ( v.intValue() + 1 ) );
+            } else {
+                m.put ( k, Integer.valueOf ( 0 ) );
+            }
+        }
+        int sMax = -1;
+        int cMax = -1;
+        for ( Iterator it = m.entrySet().iterator(); it.hasNext();) {
+            Map.Entry e = (Map.Entry) it.next();
+            Integer k = (Integer) e.getKey();
+            int s = k.intValue();
+            switch ( s ) {
+            case SCRIPT_UNDETERMINED:
+            case SCRIPT_UNCODED:
+                break;
+            default:
+                {
+                    Integer v = (Integer) e.getValue();
+                    assert v != null;
+                    int c = v.intValue();
+                    if ( c > cMax ) {
+                        cMax = c; sMax = s;
+                    }
+                    break;
+                }
+            }
+        }
+        if ( sMax < 0 ) {
+            sMax = SCRIPT_UNDETERMINED;
+        }
+        return sMax;
+    }
+
+    /**
+     * Determine if script tag denotes an 'Indic' script, where a
+     * script is an 'Indic' script if it is intended to be processed by
+     * the generic 'Indic' Script Processor.
+     * @param script a script tag
+     * @return true if script tag is a designated 'Indic' script
+     */
+    public static boolean isIndicScript ( String script ) {
+        return isIndicScript ( scriptCodeFromTag ( script ) );
+    }
+
+    /**
+     * Determine if script tag denotes an 'Indic' script, where a
+     * script is an 'Indic' script if it is intended to be processed by
+     * the generic 'Indic' Script Processor.
+     * @param script a script code
+     * @return true if script code is a designated 'Indic' script
+     */
+    public static boolean isIndicScript ( int script ) {
+        switch ( script ) {
+        case SCRIPT_BENGALI:
+        case SCRIPT_BENGALI_2:
+        case SCRIPT_BURMESE:
+        case SCRIPT_DEVANAGARI:
+        case SCRIPT_DEVANAGARI_2:
+        case SCRIPT_GUJARATI:
+        case SCRIPT_GUJARATI_2:
+        case SCRIPT_GURMUKHI:
+        case SCRIPT_GURMUKHI_2:
+        case SCRIPT_KANNADA:
+        case SCRIPT_KANNADA_2:
+        case SCRIPT_MALAYALAM:
+        case SCRIPT_MALAYALAM_2:
+        case SCRIPT_ORIYA:
+        case SCRIPT_ORIYA_2:
+        case SCRIPT_TAMIL:
+        case SCRIPT_TAMIL_2:
+        case SCRIPT_TELUGU:
+        case SCRIPT_TELUGU_2:
+            return true;
+        default:
+            return false;
+        }
+    }
+
+    /**
+     * Determine the script tag associated with an internal script code.
+     * @param code the script code
+     * @return a  script tag
+     */
+    public static String scriptTagFromCode ( int code ) {
+        Map<Integer,String> m = getScriptTagsMap();
+        if ( m != null ) {
+            String tag;
+            if ( ( tag = m.get ( Integer.valueOf ( code ) ) ) != null ) {
+                return tag;
+            } else {
+                return "";
+            }
+        } else {
+            return "";
+        }
+    }
+
+    /**
+     * Determine the internal script code associated with a script tag.
+     * @param tag the script tag
+     * @return a script code
+     */
+    public static int scriptCodeFromTag ( String tag ) {
+        Map<String,Integer> m = getScriptCodeMap();
+        if ( m != null ) {
+            Integer c;
+            if ( ( c = m.get ( tag ) ) != null ) {
+                return (int) c;
+            } else {
+                return SCRIPT_UNDETERMINED;
+            }
+        } else {
+            return SCRIPT_UNDETERMINED;
+        }
+    }
+
+    private static Map<Integer,String> scriptTagsMap = null;
+    private static Map<String,Integer> scriptCodeMap = null;
+
+    private static void putScriptTag ( Map tm, Map cm, int code, String tag ) {
+        assert tag != null;
+        assert tag.length() != 0;
+        assert code >= 0;
+        assert code <  2000;
+        tm.put ( Integer.valueOf ( code ), tag );
+        cm.put ( tag, Integer.valueOf ( code ) );
+    }
+
+    private static void makeScriptMaps() {
+        HashMap<Integer,String> tm = new HashMap<Integer,String>();
+        HashMap<String,Integer> cm = new HashMap<String,Integer>();
+        putScriptTag ( tm, cm, SCRIPT_HEBREW, "hebr" );
+        putScriptTag ( tm, cm, SCRIPT_MONGOLIAN, "mong" );
+        putScriptTag ( tm, cm, SCRIPT_ARABIC, "arab" );
+        putScriptTag ( tm, cm, SCRIPT_GREEK, "grek" );
+        putScriptTag ( tm, cm, SCRIPT_LATIN, "latn" );
+        putScriptTag ( tm, cm, SCRIPT_CYRILLIC, "cyrl" );
+        putScriptTag ( tm, cm, SCRIPT_GEORGIAN, "geor" );
+        putScriptTag ( tm, cm, SCRIPT_BOPOMOFO, "bopo" );
+        putScriptTag ( tm, cm, SCRIPT_HANGUL, "hang" );
+        putScriptTag ( tm, cm, SCRIPT_GURMUKHI, "guru" );
+        putScriptTag ( tm, cm, SCRIPT_GURMUKHI_2, "gur2" );
+        putScriptTag ( tm, cm, SCRIPT_DEVANAGARI, "deva" );
+        putScriptTag ( tm, cm, SCRIPT_DEVANAGARI_2, "dev2" );
+        putScriptTag ( tm, cm, SCRIPT_GUJARATI, "gujr" );
+        putScriptTag ( tm, cm, SCRIPT_GUJARATI_2, "gjr2" );
+        putScriptTag ( tm, cm, SCRIPT_BENGALI, "beng" );
+        putScriptTag ( tm, cm, SCRIPT_BENGALI_2, "bng2" );
+        putScriptTag ( tm, cm, SCRIPT_ORIYA, "orya" );
+        putScriptTag ( tm, cm, SCRIPT_ORIYA_2, "ory2" );
+        putScriptTag ( tm, cm, SCRIPT_TIBETAN, "tibt" );
+        putScriptTag ( tm, cm, SCRIPT_TELUGU, "telu" );
+        putScriptTag ( tm, cm, SCRIPT_TELUGU_2, "tel2" );
+        putScriptTag ( tm, cm, SCRIPT_KANNADA, "knda" );
+        putScriptTag ( tm, cm, SCRIPT_KANNADA_2, "knd2" );
+        putScriptTag ( tm, cm, SCRIPT_TAMIL, "taml" );
+        putScriptTag ( tm, cm, SCRIPT_TAMIL_2, "tml2" );
+        putScriptTag ( tm, cm, SCRIPT_MALAYALAM, "mlym" );
+        putScriptTag ( tm, cm, SCRIPT_MALAYALAM_2, "mlm2" );
+        putScriptTag ( tm, cm, SCRIPT_SINHALESE, "sinh" );
+        putScriptTag ( tm, cm, SCRIPT_BURMESE, "mymr" );
+        putScriptTag ( tm, cm, SCRIPT_THAI, "thai" );
+        putScriptTag ( tm, cm, SCRIPT_KHMER, "khmr" );
+        putScriptTag ( tm, cm, SCRIPT_LAO, "laoo" );
+        putScriptTag ( tm, cm, SCRIPT_HIRAGANA, "hira" );
+        putScriptTag ( tm, cm, SCRIPT_ETHIOPIC, "ethi" );
+        putScriptTag ( tm, cm, SCRIPT_HAN, "hani" );
+        putScriptTag ( tm, cm, SCRIPT_KATAKANA, "kana" );
+        putScriptTag ( tm, cm, SCRIPT_MATH, "zmth" );
+        putScriptTag ( tm, cm, SCRIPT_SYMBOL, "zsym" );
+        putScriptTag ( tm, cm, SCRIPT_UNDETERMINED, "zyyy" );
+        putScriptTag ( tm, cm, SCRIPT_UNCODED, "zzzz" );
+        scriptTagsMap = tm;
+        scriptCodeMap = cm;
+    }
+
+    private static Map<Integer,String> getScriptTagsMap() {
+        if ( scriptTagsMap == null ) {
+            makeScriptMaps();
+        }
+        return scriptTagsMap;
+    }
+
+    private static Map<String,Integer> getScriptCodeMap() {
+        if ( scriptCodeMap == null ) {
+            makeScriptMaps();
+        }
+        return scriptCodeMap;
+    }
+
+}

Added: xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/util/DiscontinuousAssociationException.java
URL: http://svn.apache.org/viewvc/xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/util/DiscontinuousAssociationException.java?rev=1293736&view=auto
==============================================================================
--- xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/util/DiscontinuousAssociationException.java (added)
+++ xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/util/DiscontinuousAssociationException.java Sun Feb 26 02:29:01 2012
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.util;
+
+/**
+ * Exception thrown during when attempting to map glyphs to associated characters
+ * in the case that the associated characters do not represent a compact interval.
+ * @author Glenn Adams
+ */
+public class DiscontinuousAssociationException extends RuntimeException {
+    /**
+     * Instantiate discontinuous association exception
+     */
+    public DiscontinuousAssociationException() {
+        super();
+    }
+    /**
+     * Instantiate discontinuous association exception
+     * @param message a message string
+     */
+    public DiscontinuousAssociationException(String message) {
+        super(message);
+    }
+}

Added: xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/util/GlyphContextTester.java
URL: http://svn.apache.org/viewvc/xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/util/GlyphContextTester.java?rev=1293736&view=auto
==============================================================================
--- xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/util/GlyphContextTester.java (added)
+++ xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/util/GlyphContextTester.java Sun Feb 26 02:29:01 2012
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.util;
+
+// CSOFF: LineLengthCheck
+
+/**
+ * Interface for testing the originating (source) character context of a glyph sequence.
+ * @author Glenn Adams
+ */
+public interface GlyphContextTester {
+
+    /**
+     * Perform a test on a glyph sequence in a specific (originating) character context.
+     * @param script governing script
+     * @param language governing language
+     * @param feature governing feature
+     * @param gs glyph sequence to test
+     * @param index index into glyph sequence to test
+     * @param flags that apply to lookup in scope
+     * @return true if test is satisfied
+     */
+    boolean test ( String script, String language, String feature, GlyphSequence gs, int index, int flags );
+
+}

Added: xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/util/GlyphSequence.java
URL: http://svn.apache.org/viewvc/xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/util/GlyphSequence.java?rev=1293736&view=auto
==============================================================================
--- xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/util/GlyphSequence.java (added)
+++ xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/util/GlyphSequence.java Sun Feb 26 02:29:01 2012
@@ -0,0 +1,1075 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.util;
+
+import java.nio.IntBuffer;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.fop.util.CharUtilities;
+
+// CSOFF: InnerAssignmentCheck
+// CSOFF: LineLengthCheck
+// CSOFF: WhitespaceAfterCheck
+// CSOFF: NoWhitespaceAfterCheck
+
+/**
+ * A GlyphSequence encapsulates a sequence of character codes, a sequence of glyph codes,
+ * and a sequence of character associations, where, for each glyph in the sequence of glyph
+ * codes, there is a corresponding character association. Character associations server to
+ * relate the glyph codes in a glyph sequence to the specific characters in an original
+ * character code sequence with which the glyph codes are associated.
+ * @author Glenn Adams
+ */
+public class GlyphSequence implements Cloneable {
+
+    /** default character buffer capacity in case new character buffer is created */
+    private static final int DEFAULT_CHARS_CAPACITY = 8;
+
+    /** character buffer */
+    private IntBuffer characters;
+    /** glyph buffer */
+    private IntBuffer glyphs;
+    /** association list */
+    private List associations;
+    /** predications flag */
+    private boolean predications;
+
+    /**
+     * Instantiate a glyph sequence, reusing (i.e., not copying) the referenced
+     * character and glyph buffers and associations. If characters is null, then
+     * an empty character buffer is created. If glyphs is null, then a glyph buffer
+     * is created whose capacity is that of the character buffer. If associations is
+     * null, then identity associations are created.
+     * @param characters a (possibly null) buffer of associated (originating) characters
+     * @param glyphs a (possibly null) buffer of glyphs
+     * @param associations a (possibly null) array of glyph to character associations
+     * @param predications true if predications are enabled
+     */
+    public GlyphSequence ( IntBuffer characters, IntBuffer glyphs, List associations, boolean predications ) {
+        if ( characters == null ) {
+            characters = IntBuffer.allocate ( DEFAULT_CHARS_CAPACITY );
+        }
+        if ( glyphs == null ) {
+            glyphs = IntBuffer.allocate ( characters.capacity() );
+        }
+        if ( associations == null ) {
+            associations = makeIdentityAssociations ( characters.limit(), glyphs.limit() );
+        }
+        this.characters = characters;
+        this.glyphs = glyphs;
+        this.associations = associations;
+        this.predications = predications;
+    }
+
+    /**
+     * Instantiate a glyph sequence, reusing (i.e., not copying) the referenced
+     * character and glyph buffers and associations. If characters is null, then
+     * an empty character buffer is created. If glyphs is null, then a glyph buffer
+     * is created whose capacity is that of the character buffer. If associations is
+     * null, then identity associations are created.
+     * @param characters a (possibly null) buffer of associated (originating) characters
+     * @param glyphs a (possibly null) buffer of glyphs
+     * @param associations a (possibly null) array of glyph to character associations
+     */
+    public GlyphSequence ( IntBuffer characters, IntBuffer glyphs, List associations ) {
+        this ( characters, glyphs, associations, false );
+    }
+
+    /**
+     * Instantiate a glyph sequence using an existing glyph sequence, where the new glyph sequence shares
+     * the character array of the existing sequence (but not the buffer object), and creates new copies
+     * of glyphs buffer and association list.
+     * @param gs an existing glyph sequence
+     */
+    public GlyphSequence ( GlyphSequence gs ) {
+        this ( gs.characters.duplicate(), copyBuffer ( gs.glyphs ), copyAssociations ( gs.associations ), gs.predications );
+    }
+
+    /**
+     * Instantiate a glyph sequence using an existing glyph sequence, where the new glyph sequence shares
+     * the character array of the existing sequence (but not the buffer object), but uses the specified
+     * backtrack, input, and lookahead glyph arrays to populate the glyphs, and uses the specified
+     * of glyphs buffer and association list.
+     * backtrack, input, and lookahead association arrays to populate the associations.
+     * @param gs an existing glyph sequence
+     * @param bga backtrack glyph array
+     * @param iga input glyph array
+     * @param lga lookahead glyph array
+     * @param bal backtrack association list
+     * @param ial input association list
+     * @param lal lookahead association list
+     */
+    public GlyphSequence ( GlyphSequence gs, int[] bga, int[] iga, int[] lga, CharAssociation[] bal, CharAssociation[] ial, CharAssociation[] lal ) {
+        this ( gs.characters.duplicate(), concatGlyphs ( bga, iga, lga ), concatAssociations ( bal, ial, lal ), gs.predications );
+    }
+
+    /**
+     * Obtain reference to underlying character buffer.
+     * @return character buffer reference
+     */
+    public IntBuffer getCharacters() {
+        return characters;
+    }
+
+    /**
+     * Obtain array of characters. If <code>copy</code> is true, then
+     * a newly instantiated array is returned, otherwise a reference to
+     * the underlying buffer's array is returned. N.B. in case a reference
+     * to the undelying buffer's array is returned, the length
+     * of the array is not necessarily the number of characters in array.
+     * To determine the number of characters, use {@link #getCharacterCount}.
+     * @param copy true if to return a newly instantiated array of characters
+     * @return array of characters
+     */
+    public int[] getCharacterArray ( boolean copy ) {
+        if ( copy ) {
+            return toArray ( characters );
+        } else {
+            return characters.array();
+        }
+    }
+
+    /**
+     * Obtain the number of characters in character array, where
+     * each character constitutes a unicode scalar value.
+     * @return number of characters available in character array
+     */
+    public int getCharacterCount() {
+        return characters.limit();
+    }
+
+    /**
+     * Obtain glyph id at specified index.
+     * @param index to obtain glyph
+     * @return the glyph identifier of glyph at specified index
+     * @throws IndexOutOfBoundsException if index is less than zero
+     * or exceeds last valid position
+     */
+    public int getGlyph ( int index ) throws IndexOutOfBoundsException {
+        return glyphs.get ( index );
+    }
+
+    /**
+     * Set glyph id at specified index.
+     * @param index to set glyph
+     * @param gi glyph index
+     * @throws IndexOutOfBoundsException if index is greater or equal to
+     * the limit of the underlying glyph buffer
+     */
+    public void setGlyph ( int index, int gi ) throws IndexOutOfBoundsException {
+        if ( gi > 65535 ) {
+            gi = 65535;
+        }
+        glyphs.put ( index, gi );
+    }
+
+    /**
+     * Obtain reference to underlying glyph buffer.
+     * @return glyph buffer reference
+     */
+    public IntBuffer getGlyphs() {
+        return glyphs;
+    }
+
+    /**
+     * Obtain count glyphs starting at offset. If <code>count</code> is
+     * negative, then it is treated as if the number of available glyphs
+     * were specified.
+     * @param offset into glyph sequence
+     * @param count of glyphs to obtain starting at offset, or negative,
+     * indicating all avaialble glyphs starting at offset
+     * @return glyph array
+     */
+    public int[] getGlyphs ( int offset, int count ) {
+        int ng = getGlyphCount();
+        if ( offset < 0 ) {
+            offset = 0;
+        } else if ( offset > ng ) {
+            offset = ng;
+        }
+        if ( count < 0 ) {
+            count = ng - offset;
+        }
+        int[] ga = new int [ count ];
+        for ( int i = offset, n = offset + count, k = 0; i < n; i++ ) {
+            if ( k < ga.length ) {
+                ga [ k++ ] = glyphs.get ( i );
+            }
+        }
+        return ga;
+    }
+
+    /**
+     * Obtain array of glyphs. If <code>copy</code> is true, then
+     * a newly instantiated array is returned, otherwise a reference to
+     * the underlying buffer's array is returned. N.B. in case a reference
+     * to the undelying buffer's array is returned, the length
+     * of the array is not necessarily the number of glyphs in array.
+     * To determine the number of glyphs, use {@link #getGlyphCount}.
+     * @param copy true if to return a newly instantiated array of glyphs
+     * @return array of glyphs
+     */
+    public int[] getGlyphArray ( boolean copy ) {
+        if ( copy ) {
+            return toArray ( glyphs );
+        } else {
+            return glyphs.array();
+        }
+    }
+
+    /**
+     * Obtain the number of glyphs in glyphs array, where
+     * each glyph constitutes a font specific glyph index.
+     * @return number of glyphs available in character array
+     */
+    public int getGlyphCount() {
+        return glyphs.limit();
+    }
+
+    /**
+     * Obtain association at specified index.
+     * @param index into associations array
+     * @return glyph to character associations at specified index
+     * @throws IndexOutOfBoundsException if index is less than zero
+     * or exceeds last valid position
+     */
+    public CharAssociation getAssociation ( int index ) throws IndexOutOfBoundsException {
+        return (CharAssociation) associations.get ( index );
+    }
+
+    /**
+     * Obtain reference to underlying associations list.
+     * @return associations list
+     */
+    public List getAssociations() {
+        return associations;
+    }
+
+    /**
+     * Obtain count associations starting at offset.
+     * @param offset into glyph sequence
+     * @param count of associations to obtain starting at offset, or negative,
+     * indicating all avaialble associations starting at offset
+     * @return associations
+     */
+    public CharAssociation[] getAssociations ( int offset, int count ) {
+        int ng = getGlyphCount();
+        if ( offset < 0 ) {
+            offset = 0;
+        } else if ( offset > ng ) {
+            offset = ng;
+        }
+        if ( count < 0 ) {
+            count = ng - offset;
+        }
+        CharAssociation[] aa = new CharAssociation [ count ];
+        for ( int i = offset, n = offset + count, k = 0; i < n; i++ ) {
+            if ( k < aa.length ) {
+                aa [ k++ ] = (CharAssociation) associations.get ( i );
+            }
+        }
+        return aa;
+    }
+
+    /**
+     * Enable or disable predications.
+     * @param enable true if predications are to be enabled; otherwise false to disable
+     */
+    public void setPredications ( boolean enable ) {
+        this.predications = enable;
+    }
+
+    /**
+     * Obtain predications state.
+     * @return true if predications are enabled
+     */
+    public boolean getPredications() {
+        return this.predications;
+    }
+
+    /**
+     * Set predication <KEY,VALUE> at glyph sequence OFFSET.
+     * @param offset offset (index) into glyph sequence
+     * @param key predication key
+     * @param value predication value
+     */
+    public void setPredication ( int offset, String key, Object value ) {
+        if ( predications ) {
+            CharAssociation[] aa = getAssociations ( offset, 1 );
+            CharAssociation   ca = aa[0];
+            ca.setPredication ( key, value );
+        }
+    }
+
+    /**
+     * Get predication KEY at glyph sequence OFFSET.
+     * @param offset offset (index) into glyph sequence
+     * @param key predication key
+     * @return predication KEY at OFFSET or null if none exists
+     */
+    public Object getPredication ( int offset, String key ) {
+        if ( predications ) {
+            CharAssociation[] aa = getAssociations ( offset, 1 );
+            CharAssociation   ca = aa[0];
+            return ca.getPredication ( key );
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Compare glyphs.
+     * @param gb buffer containing glyph indices with which this glyph sequence's glyphs are to be compared
+     * @return zero if glyphs are the same, otherwise returns 1 or -1 according to whether this glyph sequence's
+     * glyphs are lexicographically greater or lesser than the glyphs in the specified string buffer
+     */
+    public int compareGlyphs ( IntBuffer gb ) {
+        int ng = getGlyphCount();
+        for ( int i = 0, n = gb.limit(); i < n; i++ ) {
+            if ( i < ng ) {
+                int g1 = glyphs.get ( i );
+                int g2 = gb.get ( i );
+                if ( g1 > g2 ) {
+                    return 1;
+                } else if ( g1 < g2 ) {
+                    return -1;
+                }
+            } else {
+                return -1;              // this gb is a proper prefix of specified gb
+            }
+        }
+        return 0;                       // same lengths with no difference
+    }
+
+    /** {@inheritDoc} */
+    public Object clone() {
+        try {
+            GlyphSequence gs = (GlyphSequence) super.clone();
+            gs.characters = copyBuffer ( characters );
+            gs.glyphs = copyBuffer ( glyphs );
+            gs.associations = copyAssociations ( associations );
+            return gs;
+        } catch ( CloneNotSupportedException e ) {
+            return null;
+        }
+    }
+
+    /** {@inheritDoc} */
+    public String toString() {
+        StringBuffer sb = new StringBuffer();
+        sb.append ( '{' );
+        sb.append ( "chars = [" );
+        sb.append ( characters );
+        sb.append ( "], glyphs = [" );
+        sb.append ( glyphs );
+        sb.append ( "], associations = [" );
+        sb.append ( associations );
+        sb.append ( "]" );
+        sb.append ( '}' );
+        return sb.toString();
+    }
+
+    /**
+     * Determine if two arrays of glyphs are identical.
+     * @param ga1 first glyph array
+     * @param ga2 second glyph array
+     * @return true if arrays are botth null or both non-null and have identical elements
+     */
+    public static boolean sameGlyphs ( int[] ga1, int[] ga2 ) {
+        if ( ga1 == ga2 ) {
+            return true;
+        } else if ( ( ga1 == null ) || ( ga2 == null ) ) {
+            return false;
+        } else if ( ga1.length != ga2.length ) {
+            return false;
+        } else {
+            for ( int i = 0, n = ga1.length; i < n; i++ ) {
+                if ( ga1[i] != ga2[i] ) {
+                    return false;
+                }
+            }
+            return true;
+        }
+    }
+
+    /**
+     * Concatenante glyph arrays.
+     * @param bga backtrack glyph array
+     * @param iga input glyph array
+     * @param lga lookahead glyph array
+     * @return new integer buffer containing concatenated glyphs
+     */
+    public static IntBuffer concatGlyphs ( int[] bga, int[] iga, int[] lga ) {
+        int ng = 0;
+        if ( bga != null ) {
+            ng += bga.length;
+        }
+        if ( iga != null ) {
+            ng += iga.length;
+        }
+        if ( lga != null ) {
+            ng += lga.length;
+        }
+        IntBuffer gb = IntBuffer.allocate ( ng );
+        if ( bga != null ) {
+            gb.put ( bga );
+        }
+        if ( iga != null ) {
+            gb.put ( iga );
+        }
+        if ( lga != null ) {
+            gb.put ( lga );
+        }
+        gb.flip();
+        return gb;
+    }
+
+    /**
+     * Concatenante association arrays.
+     * @param baa backtrack association array
+     * @param iaa input association array
+     * @param laa lookahead association array
+     * @return new list containing concatenated associations
+     */
+    public static List concatAssociations ( CharAssociation[] baa, CharAssociation[] iaa, CharAssociation[] laa ) {
+        int na = 0;
+        if ( baa != null ) {
+            na += baa.length;
+        }
+        if ( iaa != null ) {
+            na += iaa.length;
+        }
+        if ( laa != null ) {
+            na += laa.length;
+        }
+        if ( na > 0 ) {
+            List gl = new ArrayList ( na );
+            if ( baa != null ) {
+                for ( int i = 0; i < baa.length; i++ ) {
+                    gl.add ( baa[i] );
+                }
+            }
+            if ( iaa != null ) {
+                for ( int i = 0; i < iaa.length; i++ ) {
+                    gl.add ( iaa[i] );
+                }
+            }
+            if ( laa != null ) {
+                for ( int i = 0; i < laa.length; i++ ) {
+                    gl.add ( laa[i] );
+                }
+            }
+            return gl;
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Join (concatenate) glyph sequences.
+     * @param gs original glyph sequence from which to reuse character array reference
+     * @param sa array of glyph sequences, whose glyph arrays and association lists are to be concatenated
+     * @return new glyph sequence referring to character array of GS and concatenated glyphs and associations of SA
+     */
+    public static GlyphSequence join ( GlyphSequence gs, GlyphSequence[] sa ) {
+        assert sa != null;
+        int tg = 0;
+        int ta = 0;
+        for ( int i = 0, n = sa.length; i < n; i++ ) {
+            GlyphSequence s = sa [ i ];
+            IntBuffer ga = s.getGlyphs();
+            assert ga != null;
+            int ng = ga.limit();
+            List al = s.getAssociations();
+            assert al != null;
+            int na = al.size();
+            assert na == ng;
+            tg += ng;
+            ta += na;
+        }
+        IntBuffer uga = IntBuffer.allocate ( tg );
+        ArrayList ual = new ArrayList ( ta );
+        for ( int i = 0, n = sa.length; i < n; i++ ) {
+            GlyphSequence s = sa [ i ];
+            uga.put ( s.getGlyphs() );
+            ual.addAll ( s.getAssociations() );
+        }
+        return new GlyphSequence ( gs.getCharacters(), uga, ual, gs.getPredications() );
+    }
+
+    /**
+     * Reorder sequence such that [SOURCE,SOURCE+COUNT) is moved just prior to TARGET.
+     * @param gs input sequence
+     * @param source index of sub-sequence to reorder
+     * @param count length of sub-sequence to reorder
+     * @param target index to which source sub-sequence is to be moved
+     * @return reordered sequence (or original if no reordering performed)
+     */
+    public static GlyphSequence reorder ( GlyphSequence gs, int source, int count, int target ) {
+        if ( source != target ) {
+            int   ng  = gs.getGlyphCount();
+            int[] ga  = gs.getGlyphArray ( false );
+            int[] nga = new int [ ng ];
+            GlyphSequence.CharAssociation[] aa  = gs.getAssociations ( 0, ng );
+            GlyphSequence.CharAssociation[] naa = new GlyphSequence.CharAssociation [ ng ];
+            if ( source < target ) {
+                int t = 0;
+                for ( int s = 0, e = source; s < e; s++, t++ ) {
+                    nga[t] = ga[s];
+                    naa[t] = aa[s];
+                }
+                for ( int s = source + count, e = target; s < e; s++, t++ ) {
+                    nga[t] = ga[s];
+                    naa[t] = aa[s];
+                }
+                for ( int s = source, e = source + count; s < e; s++, t++ ) {
+                    nga[t] = ga[s];
+                    naa[t] = aa[s];
+                }
+                for ( int s = target, e = ng; s < e; s++, t++ ) {
+                    nga[t] = ga[s];
+                    naa[t] = aa[s];
+                }
+            } else {
+                int t = 0;
+                for ( int s = 0, e = target; s < e; s++, t++ ) {
+                    nga[t] = ga[s];
+                    naa[t] = aa[s];
+                }
+                for ( int s = source, e = source + count; s < e; s++, t++ ) {
+                    nga[t] = ga[s];
+                    naa[t] = aa[s];
+                }
+                for ( int s = target, e = source; s < e; s++, t++ ) {
+                    nga[t] = ga[s];
+                    naa[t] = aa[s];
+                }
+                for ( int s = source + count, e = ng; s < e; s++, t++ ) {
+                    nga[t] = ga[s];
+                    naa[t] = aa[s];
+                }
+            }
+            return new GlyphSequence ( gs, null, nga, null, null, naa, null );
+        } else {
+            return gs;
+        }
+    }
+
+    private static int[] toArray ( IntBuffer ib ) {
+        if ( ib != null ) {
+            int n = ib.limit();
+            int[] ia = new int[n];
+            ib.get ( ia, 0, n );
+            return ia;
+        } else {
+            return new int[0];
+        }
+    }
+
+    private static List makeIdentityAssociations ( int numChars, int numGlyphs ) {
+        int nc = numChars;
+        int ng = numGlyphs;
+        List av = new ArrayList ( ng );
+        for ( int i = 0, n = ng; i < n; i++ ) {
+            int k = ( i > nc ) ? nc : i;
+            av.add ( new CharAssociation ( i, ( k == nc ) ? 0 : 1 ) );
+        }
+        return av;
+    }
+
+    private static IntBuffer copyBuffer ( IntBuffer ib ) {
+        if ( ib != null ) {
+            int[] ia = new int [ ib.capacity() ];
+            int   p  = ib.position();
+            int   l  = ib.limit();
+            System.arraycopy ( ib.array(), 0, ia, 0, ia.length );
+            return IntBuffer.wrap ( ia, p, l - p );
+        } else {
+            return null;
+        }
+    }
+
+    private static List copyAssociations ( List ca ) {
+        if ( ca != null ) {
+            return new ArrayList ( ca );
+        } else {
+            return ca;
+        }
+    }
+
+    /**
+     * A structure class encapsulating an interval of characters
+     * expressed as an offset and count of Unicode scalar values (in
+     * an IntBuffer). A <code>CharAssociation</code> is used to
+     * maintain a backpointer from a glyph to one or more character
+     * intervals from which the glyph was derived.
+     *
+     * Each glyph in a glyph sequence is associated with a single
+     * <code>CharAssociation</code> instance.
+     *
+     * A <code>CharAssociation</code> instance is additionally (and
+     * optionally) used to record predication information about the
+     * glyph, such as whether the glyph was produced by the
+     * application of a specific substitution table or whether its
+     * position was adjusted by a specific poisitioning table.
+     */
+    public static class CharAssociation implements Cloneable {
+
+        // instance state
+        private final int offset;
+        private final int count;
+        private final int[] subIntervals;
+        private Map<String,Object> predications;
+
+        // class state
+        private static volatile Map<String,PredicationMerger> predicationMergers;
+
+        interface PredicationMerger {
+            Object merge ( String key, Object v1, Object v2 );
+        }
+
+        /**
+         * Instantiate a character association.
+         * @param offset into array of Unicode scalar values (in associated IntBuffer)
+         * @param count of Unicode scalar values (in associated IntBuffer)
+         * @param subIntervals if disjoint, then array of sub-intervals, otherwise null; even
+         * members of array are sub-interval starts, and odd members are sub-interval
+         * ends (exclusive)
+         */
+        public CharAssociation ( int offset, int count, int[] subIntervals ) {
+            this.offset = offset;
+            this.count = count;
+            this.subIntervals = ( ( subIntervals != null ) && ( subIntervals.length > 2 ) ) ? subIntervals : null;
+        }
+
+        /**
+         * Instantiate a non-disjoint character association.
+         * @param offset into array of UTF-16 code elements (in associated CharSequence)
+         * @param count of UTF-16 character code elements (in associated CharSequence)
+         */
+        public CharAssociation ( int offset, int count ) {
+            this ( offset, count, null );
+        }
+
+        /**
+         * Instantiate a non-disjoint character association.
+         * @param subIntervals if disjoint, then array of sub-intervals, otherwise null; even
+         * members of array are sub-interval starts, and odd members are sub-interval
+         * ends (exclusive)
+         */
+        public CharAssociation ( int[] subIntervals ) {
+            this ( getSubIntervalsStart ( subIntervals ), getSubIntervalsLength ( subIntervals ), subIntervals );
+        }
+
+        /** @return offset (start of association interval) */
+        public int getOffset() {
+            return offset;
+        }
+
+        /** @return count (number of characer codes in association) */
+        public int getCount() {
+            return count;
+        }
+
+        /** @return start of association interval */
+        public int getStart() {
+            return getOffset();
+        }
+
+        /** @return end of association interval */
+        public int getEnd() {
+            return getOffset() + getCount();
+        }
+
+        /** @return true if association is disjoint */
+        public boolean isDisjoint() {
+            return subIntervals != null;
+        }
+
+        /** @return subintervals of disjoint association */
+        public int[] getSubIntervals() {
+            return subIntervals;
+        }
+
+        /** @return count of subintervals of disjoint association */
+        public int getSubIntervalCount() {
+            return ( subIntervals != null ) ? ( subIntervals.length / 2 ) : 0;
+        }
+
+        /**
+         * @param offset of interval in sequence
+         * @param count length of interval
+         * @return true if this association is contained within [offset,offset+count)
+         */
+        public boolean contained ( int offset, int count ) {
+            int s = offset;
+            int e = offset + count;
+            if ( ! isDisjoint() ) {
+                int s0 = getStart();
+                int e0 = getEnd();
+                return ( s0 >= s ) && ( e0 <= e );
+            } else {
+                int ns = getSubIntervalCount();
+                for ( int i = 0; i < ns; i++ ) {
+                    int s0 = subIntervals [ 2 * i + 0 ];
+                    int e0 = subIntervals [ 2 * i + 1 ];
+                    if ( ( s0 >= s ) && ( e0 <= e ) ) {
+                        return true;
+                    }
+                }
+                return false;
+            }
+        }
+
+        /**
+         * Set predication <KEY,VALUE>.
+         * @param key predication key
+         * @param value predication value
+         */
+        public void setPredication ( String key, Object value ) {
+            if ( predications == null ) {
+                predications = new HashMap<String,Object>();
+            }
+            if ( predications != null ) {
+                predications.put ( key, value );
+            }
+        }
+
+        /**
+         * Get predication KEY.
+         * @param key predication key
+         * @return predication KEY at OFFSET or null if none exists
+         */
+        public Object getPredication ( String key ) {
+            if ( predications != null ) {
+                return predications.get ( key );
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Merge predication <KEY,VALUE>.
+         * @param key predication key
+         * @param value predication value
+         */
+        public void mergePredication ( String key, Object value ) {
+            if ( predications == null ) {
+                predications = new HashMap<String,Object>();
+            }
+            if ( predications != null ) {
+                if ( predications.containsKey ( key ) ) {
+                    Object v1 = predications.get ( key );
+                    Object v2 = value;
+                    predications.put ( key, mergePredicationValues ( key, v1, v2 ) );
+                } else {
+                    predications.put ( key, value );
+                }
+            }
+        }
+
+        /**
+         * Merge predication values V1 and V2 on KEY. Uses registered <code>PredicationMerger</code>
+         * if one exists, otherwise uses V2 if non-null, otherwise uses V1.
+         * @param key predication key
+         * @param v1 first (original) predication value
+         * @param v2 second (to be merged) predication value
+         * @return merged value
+         */
+        public static Object mergePredicationValues ( String key, Object v1, Object v2 ) {
+            PredicationMerger pm = getPredicationMerger ( key );
+            if ( pm != null ) {
+                return pm.merge ( key, v1, v2 );
+            } else if ( v2 != null ) {
+                return v2;
+            } else {
+                return v1;
+            }
+        }
+
+        /**
+         * Merge predications from another CA.
+         * @param ca from which to merge
+         */
+        public void mergePredications ( CharAssociation ca ) {
+            if ( ca.predications != null ) {
+                for ( Map.Entry<String,Object> e : ca.predications.entrySet() ) {
+                    mergePredication ( e.getKey(), e.getValue() );
+                }
+            }
+        }
+
+        /** {@inheritDoc} */
+        public Object clone() {
+            try {
+                CharAssociation ca = (CharAssociation) super.clone();
+                if ( predications != null ) {
+                    ca.predications = new HashMap<String,Object> ( predications );
+                }
+                return ca;
+            } catch ( CloneNotSupportedException e ) {
+                return null;
+            }
+        }
+
+        /**
+         * Register predication merger PM for KEY.
+         * @param key for predication merger
+         * @param pm predication merger
+         */
+        public static void setPredicationMerger ( String key, PredicationMerger pm ) {
+            if ( predicationMergers == null ) {
+                predicationMergers = new HashMap<String,PredicationMerger>();
+            }
+            if ( predicationMergers != null ) {
+                predicationMergers.put ( key, pm );
+            }            
+        }
+
+        /**
+         * Obtain predication merger for KEY.
+         * @param key for predication merger
+         * @return predication merger or null if none exists
+         */
+        public static PredicationMerger getPredicationMerger ( String key ) {
+            if ( predicationMergers != null ) {
+                return predicationMergers.get ( key );
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Replicate association to form <code>repeat</code> new associations.
+         * @param a association to replicate
+         * @param repeat count
+         * @return array of replicated associations
+         */
+        public static CharAssociation[] replicate ( CharAssociation a, int repeat ) {
+            CharAssociation[] aa = new CharAssociation [ repeat ];
+            for ( int i = 0, n = aa.length; i < n; i++ ) {
+                aa [ i ] = (CharAssociation) a.clone();
+            }
+            return aa;
+        }
+
+        /**
+         * Join (merge) multiple associations into a single, potentially disjoint
+         * association.
+         * @param aa array of associations to join
+         * @return (possibly disjoint) association containing joined associations
+         */
+        public static CharAssociation join ( CharAssociation[] aa ) {
+            CharAssociation ca;
+            // extract sorted intervals
+            int[] ia = extractIntervals ( aa );
+            if ( ( ia == null ) || ( ia.length == 0 ) ) {
+                ca = new CharAssociation ( 0, 0 );
+            } else if ( ia.length == 2 ) {
+                int s = ia[0];
+                int e = ia[1];
+                ca = new CharAssociation ( s, e - s );
+            } else {
+                ca = new CharAssociation ( mergeIntervals ( ia ) );
+            }
+            return mergePredicates ( ca, aa );
+        }
+
+        private static CharAssociation mergePredicates ( CharAssociation ca, CharAssociation[] aa ) {
+            for ( CharAssociation a : aa ) {
+                ca.mergePredications ( a );
+            }
+            return ca;
+        }
+
+        private static int getSubIntervalsStart ( int[] ia ) {
+            int us = Integer.MAX_VALUE;
+            int ue = Integer.MIN_VALUE;
+            if ( ia != null ) {
+                for ( int i = 0, n = ia.length; i < n; i += 2 ) {
+                    int s = ia [ i + 0 ];
+                    int e = ia [ i + 1 ];
+                    if ( s < us ) {
+                        us = s;
+                    }
+                    if ( e > ue ) {
+                        ue = e;
+                    }
+                }
+                if ( ue < 0 ) {
+                    ue = 0;
+                }
+                if ( us > ue ) {
+                    us = ue;
+                }
+            }
+            return us;
+        }
+
+        private static int getSubIntervalsLength ( int[] ia ) {
+            int us = Integer.MAX_VALUE;
+            int ue = Integer.MIN_VALUE;
+            if ( ia != null ) {
+                for ( int i = 0, n = ia.length; i < n; i += 2 ) {
+                    int s = ia [ i + 0 ];
+                    int e = ia [ i + 1 ];
+                    if ( s < us ) {
+                        us = s;
+                    }
+                    if ( e > ue ) {
+                        ue = e;
+                    }
+                }
+                if ( ue < 0 ) {
+                    ue = 0;
+                }
+                if ( us > ue ) {
+                    us = ue;
+                }
+            }
+            return ue - us;
+        }
+
+        /**
+         * Extract sorted sub-intervals.
+         */
+        private static int[] extractIntervals ( CharAssociation[] aa ) {
+            int ni = 0;
+            for ( int i = 0, n = aa.length; i < n; i++ ) {
+                CharAssociation a = aa [ i ];
+                if ( a.isDisjoint() ) {
+                    ni += a.getSubIntervalCount();
+                } else {
+                    ni += 1;
+                }
+            }
+            int[] sa = new int [ ni ];
+            int[] ea = new int [ ni ];
+            for ( int i = 0, k = 0; i < aa.length; i++ ) {
+                CharAssociation a = aa [ i ];
+                if ( a.isDisjoint() ) {
+                    int[] da = a.getSubIntervals();
+                    for ( int j = 0; j < da.length; j += 2 ) {
+                        sa [ k ] = da [ j + 0 ];
+                        ea [ k ] = da [ j + 1 ];
+                        k++;
+                    }
+                } else {
+                    sa [ k ] = a.getStart();
+                    ea [ k ] = a.getEnd();
+                    k++;
+                }
+            }
+            return sortIntervals ( sa, ea );
+        }
+
+        private static final int[] sortIncrements16                                                             // CSOK: ConstantNameCheck
+            = { 1391376, 463792, 198768, 86961, 33936, 13776, 4592, 1968, 861, 336, 112, 48, 21, 7, 3, 1 };
+
+        private static final int[] sortIncrements03                                                             // CSOK: ConstantNameCheck
+            = { 7, 3, 1 };
+
+        /**
+         * Sort sub-intervals using modified Shell Sort.
+         */
+        private static int[] sortIntervals ( int[] sa, int[] ea ) {
+            assert sa != null;
+            assert ea != null;
+            assert sa.length == ea.length;
+            int ni = sa.length;
+            int[] incr = ( ni < 21 ) ? sortIncrements03 : sortIncrements16;
+            for ( int k = 0; k < incr.length; k++ ) {
+                for ( int h = incr [ k ], i = h, n = ni, j; i < n; i++ ) {
+                    int s1 = sa [ i ];
+                    int e1 = ea [ i ];
+                    for ( j = i; j >= h; j -= h) {
+                        int s2 = sa [ j - h ];
+                        int e2 = ea [ j - h ];
+                        if ( s2 > s1 ) {
+                            sa [ j ] = s2;
+                            ea [ j ] = e2;
+                        } else if ( ( s2 == s1 ) && ( e2 > e1 ) ) {
+                            sa [ j ] = s2;
+                            ea [ j ] = e2;
+                        } else {
+                            break;
+                        }
+                    }
+                    sa [ j ] = s1;
+                    ea [ j ] = e1;
+                }
+            }
+            int[] ia = new int [ ni * 2 ];
+            for ( int i = 0; i < ni; i++ ) {
+                ia [ ( i * 2 ) + 0 ] = sa [ i ];
+                ia [ ( i * 2 ) + 1 ] = ea [ i ];
+            }
+            return ia;
+        }
+
+        /**
+         * Merge overlapping and abutting sub-intervals.
+         */
+        private static int[] mergeIntervals ( int[] ia ) {
+            int ni = ia.length;
+            int i, n, nm, is, ie;
+            // count merged sub-intervals
+            for ( i = 0, n = ni, nm = 0, is = ie = -1; i < n; i += 2 ) {
+                int s = ia [ i + 0 ];
+                int e = ia [ i + 1 ];
+                if ( ( ie < 0 ) || ( s > ie ) ) {
+                    is = s;
+                    ie = e;
+                    nm++;
+                } else if ( s >= is ) {
+                    if ( e > ie ) {
+                        ie = e;
+                    }
+                }
+            }
+            int[] mi = new int [ nm * 2 ];
+            // populate merged sub-intervals
+            for ( i = 0, n = ni, nm = 0, is = ie = -1; i < n; i += 2 ) {
+                int s = ia [ i + 0 ];
+                int e = ia [ i + 1 ];
+                int k = nm * 2;
+                if ( ( ie < 0 ) || ( s > ie ) ) {
+                    is = s;
+                    ie = e;
+                    mi [ k + 0 ] = is;
+                    mi [ k + 1 ] = ie;
+                    nm++;
+                } else if ( s >= is ) {
+                    if ( e > ie ) {
+                        ie = e;
+                    }
+                    mi [ k - 1 ] = ie;
+                }
+            }
+            return mi;
+        }
+
+    }
+
+}

Added: xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/util/GlyphTester.java
URL: http://svn.apache.org/viewvc/xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/util/GlyphTester.java?rev=1293736&view=auto
==============================================================================
--- xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/util/GlyphTester.java (added)
+++ xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/util/GlyphTester.java Sun Feb 26 02:29:01 2012
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.util;
+
+/**
+ * Interface for testing glyph properties according to glyph identifier.
+ * @author Glenn Adams
+ */
+public interface GlyphTester {
+
+    /**
+     * Perform a test on a glyph identifier.
+     * @param gi glyph identififer
+     * @param flags that apply to lookup in scope
+     * @return true if test is satisfied
+     */
+    boolean test ( int gi, int flags );
+
+}



---------------------------------------------------------------------
To unsubscribe, e-mail: fop-commits-unsubscribe@xmlgraphics.apache.org
For additional commands, e-mail: fop-commits-help@xmlgraphics.apache.org