Index: test/java/org/apache/fop/fonts/TrueTypeAnsiTestCase.java
===================================================================
--- test/java/org/apache/fop/fonts/TrueTypeAnsiTestCase.java	(revision 981406)
+++ test/java/org/apache/fop/fonts/TrueTypeAnsiTestCase.java	(working copy)
@@ -77,7 +77,7 @@
         triplets.add(new FontTriplet(fontFamily, "normal", Font.WEIGHT_NORMAL));
         EmbedFontInfo font = new EmbedFontInfo(
                 metricsFile.toURI().toASCIIString(),
-                true, triplets,
+                true, true, triplets,
                 ttfFile.toURI().toASCIIString(), null);
         fontList.add(font);
         renderer.addFontList(fontList);
Index: test/layoutengine/disabled-testcases.xml
===================================================================
--- test/layoutengine/disabled-testcases.xml	(revision 981406)
+++ test/layoutengine/disabled-testcases.xml	(working copy)
@@ -222,4 +222,10 @@
     <description>The block should cause overflow in the
     last column on the page, rather than be broken.</description>
   </testcase>
+  <testcase>
+    <name>Writing mode problems</name>
+    <file>simple-page-master_writing-mode_rl_region-body_writing-mode-lr.xml</file>
+    <description>Test erroneously depends upon incorrect implementation of writing-mode trait
+    derivation on fo:region-*.</description>
+  </testcase>
 </disabled-testcases>
Index: test/layoutengine/standard-testcases/block_basic_2.xml
===================================================================
--- test/layoutengine/standard-testcases/block_basic_2.xml	(revision 981406)
+++ test/layoutengine/standard-testcases/block_basic_2.xml	(working copy)
@@ -75,7 +75,7 @@
     <eval expected="0" xpath="//flow/block[5]/lineArea/inlineparent/@offset"/>
     <eval expected="8616" xpath="//flow/block[5]/lineArea/inlineparent/text/@baseline"/>
     <eval expected="0" xpath="//flow/block[6]/lineArea/text/@offset"/>
-    <eval expected="0" xpath="//flow/block[6]/lineArea/text/word/@offset"/>
+    <true xpath="not(boolean(//flow/block[6]/lineArea/text/word/@offset))"/>
     <eval expected="8616" xpath="//flow/block[6]/lineArea/text/@baseline"/>
   </checks>
 </testcase>
Index: test/layoutengine/standard-testcases/kerning_1_on.xml
===================================================================
--- test/layoutengine/standard-testcases/kerning_1_on.xml	(revision 981406)
+++ test/layoutengine/standard-testcases/kerning_1_on.xml	(working copy)
@@ -52,12 +52,12 @@
   </fo>
   <checks>
     <eval expected="36420" xpath="//flow/block[1]/block[1]/lineArea/inlineparent/@ipd"/>
-    <eval expected="0" xpath="//flow/block[1]/block[1]/lineArea/inlineparent/text/word/@offset"/>
+    <true xpath="not(boolean(//flow/block[1]/block[1]/lineArea/inlineparent/text/word/@offset))"/>
     <eval expected="0 -960 -840 -960 -840" xpath="//flow/block[1]/block[1]/lineArea/inlineparent/text/word/@letter-adjust"/>
 
     <eval expected="40420" xpath="//flow/block[2]/block[1]/lineArea/inlineparent/@ipd"/>
     <eval expected="1000" xpath="//flow/block[2]/block[1]/lineArea/inlineparent/text/@tlsadjust"/>
-    <eval expected="0" xpath="//flow/block[2]/block[1]/lineArea/inlineparent/text/word/@offset"/>
+    <true xpath="not(boolean(//flow/block[2]/block[1]/lineArea/inlineparent/text/word/@offset))"/>
     <eval expected="0 -960 -840 -960 -840" xpath="//flow/block[2]/block[1]/lineArea/inlineparent/text/word/@letter-adjust"/>
   </checks>
   <if-checks xmlns:if="http://xmlgraphics.apache.org/fop/intermediate">
Index: test/layoutengine/standard-testcases/kerning_1_off.xml
===================================================================
--- test/layoutengine/standard-testcases/kerning_1_off.xml	(revision 981406)
+++ test/layoutengine/standard-testcases/kerning_1_off.xml	(working copy)
@@ -47,12 +47,12 @@
   <checks>
     <eval expected="40020" xpath="//flow/block[1]/block[1]/lineArea/inlineparent/@ipd"/>
     <true xpath="not(boolean(//flow/block[1]/block[1]/lineArea/inlineparent/text/@tlsadjust))"/>
-    <eval expected="0" xpath="//flow/block[1]/block[1]/lineArea/inlineparent/text/word/@offset"/>
+    <true xpath="not(boolean(//flow/block[1]/block[1]/lineArea/inlineparent/text/word/@offset))"/>
     <true xpath="not(boolean(//flow/block[1]/block[1]/lineArea/inlineparent/text/word/@letter-adjust))"/>
 
     <eval expected="44020" xpath="//flow/block[2]/block[1]/lineArea/inlineparent/@ipd"/>
     <eval expected="1000" xpath="//flow/block[2]/block[1]/lineArea/inlineparent/text/@tlsadjust"/>
-    <eval expected="0" xpath="//flow/block[2]/block[1]/lineArea/inlineparent/text/word/@offset"/>
+    <true xpath="not(boolean(//flow/block[2]/block[1]/lineArea/inlineparent/text/word/@offset))"/>
     <true xpath="not(boolean(//flow/block[2]/block[1]/lineArea/inlineparent/text/word/@letter-adjust))"/>
   </checks>
 </testcase>
Index: findbugs-exclude.xml
===================================================================
--- findbugs-exclude.xml	(revision 0)
+++ findbugs-exclude.xml	(revision 0)
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<FindBugsFilter>
+  <!-- use of null is preferred over zero length array -->
+  <Match>
+    <Class name="org.apache.fop.area.inline.WordArea"/>
+    <Method name="getBidiLevels"/>
+    <Bug pattern="PZLA_PREFER_ZERO_LENGTH_ARRAYS"/>
+  </Match>
+  <Match>
+    <Class name="org.apache.fop.fo.FOText"/>
+    <Method name="getBidiLevels"/>
+    <Bug pattern="PZLA_PREFER_ZERO_LENGTH_ARRAYS"/>
+  </Match>
+  <Match>
+    <Class name="org.apache.fop.fonts.ArabicScriptProcessor"/>
+    <Method name="position"/>
+    <Bug pattern="PZLA_PREFER_ZERO_LENGTH_ARRAYS"/>
+  </Match>
+  <Match>
+    <Class name="org.apache.fop.fonts.DefaultScriptProcessor"/>
+    <Method name="position"/>
+    <Bug pattern="PZLA_PREFER_ZERO_LENGTH_ARRAYS"/>
+  </Match>
+  <Match>
+    <Class name="org.apache.fop.fonts.GlyphPositioningSubtable"/>
+    <Method name="position"/>
+    <Bug pattern="PZLA_PREFER_ZERO_LENGTH_ARRAYS"/>
+  </Match>
+  <Match>
+    <Class name="org.apache.fop.fonts.GlyphPositioningTable"/>
+    <Method name="position"/>
+    <Bug pattern="PZLA_PREFER_ZERO_LENGTH_ARRAYS"/>
+  </Match>
+  <Match>
+    <Class name="org.apache.fop.fonts.LazyFont"/>
+    <Method name="performPositioning"/>
+    <Bug pattern="PZLA_PREFER_ZERO_LENGTH_ARRAYS"/>
+  </Match>
+  <Match>
+    <Class name="org.apache.fop.fonts.MultiByteFont"/>
+    <Method name="performPositioning"/>
+    <Bug pattern="PZLA_PREFER_ZERO_LENGTH_ARRAYS"/>
+  </Match>
+  <Match>
+    <Class name="org.apache.fop.layoutmgr.BidiUtil$UnicodeBidiAlgorithm"/>
+    <Method name="resolveLevels"/>
+    <Bug pattern="PZLA_PREFER_ZERO_LENGTH_ARRAYS"/>
+  </Match>
+  <!-- string not exposed to end user -->
+  <Match>
+    <Class name="org.apache.fop.fonts.GlyphPositioningTable"/>
+    <Method name="getLookupTypeFromName"/>
+    <Bug pattern="DM_CONVERT_CASE"/>
+  </Match>
+  <Match>
+    <Class name="org.apache.fop.fonts.GlyphSubstitutionTable"/>
+    <Method name="getLookupTypeFromName"/>
+    <Bug pattern="DM_CONVERT_CASE"/>
+  </Match>
+  <Match>
+    <Class name="org.apache.fop.fonts.GlyphTable"/>
+    <Method name="getTableTypeFromName"/>
+    <Bug pattern="DM_CONVERT_CASE"/>
+  </Match>
+  <!-- performance optimizations -->
+  <Match>
+    <Class name="org.apache.fop.fonts.GlyphSequence"/>
+    <Method name="getAssociations"/>
+    <Bug pattern="EI_EXPOSE_REP"/>
+  </Match>
+  <Match>
+    <Class name="org.apache.fop.fonts.GlyphSubstitutionTable$Ligature"/>
+    <Or>
+        <Method name="getComponents"/>
+        <Method name="getLigatures"/>
+    </Or>
+    <Bug pattern="EI_EXPOSE_REP"/>
+  </Match>
+  <Match>
+    <Class name="org.apache.fop.fonts.GlyphSubstitutionTable$Ligature"/>
+    <Or>
+        <Method name="&lt;init&gt;" params="int, int[]" returns="void"/>
+    </Or>
+    <Bug pattern="EI_EXPOSE_REP2"/>
+  </Match>
+  <Match>
+    <Class name="org.apache.fop.fonts.GlyphSubstitutionTable$LigatureSet"/>
+    <Method name="getLigatures"/>
+    <Bug pattern="EI_EXPOSE_REP"/>
+  </Match>
+  <Match>
+    <Class name="org.apache.fop.fonts.GlyphSubstitutionTable$LigatureSet"/>
+    <Method name="&lt;init&gt;" params="org.apache.fop.fonts.GlyphSubstitutionTable$Ligature[]" returns="void"/>
+    <Bug pattern="EI_EXPOSE_REP2"/>
+  </Match>
+  <Match>
+    <Class name="org.apache.fop.area.inline.WordArea"/>
+    <Method name="getBidiLevels"/>
+    <Bug pattern="EI_EXPOSE_REP"/>
+  </Match>
+  <Match>
+    <Class name="org.apache.fop.fo.FOText"/>
+    <Method name="getBidiLevels"/>
+    <Bug pattern="EI_EXPOSE_REP"/>
+  </Match>
+</FindBugsFilter>
Index: src/java/org/apache/fop/render/AbstractPathOrientedRenderer.java
===================================================================
--- src/java/org/apache/fop/render/AbstractPathOrientedRenderer.java	(revision 981406)
+++ src/java/org/apache/fop/render/AbstractPathOrientedRenderer.java	(working copy)
@@ -467,7 +467,7 @@
         float height = area.getBPD() / 1000f;
         if (height != 0.0f || bpheight != 0.0f && bpwidth != 0.0f) {
             float x = currentIPPosition / 1000f;
-            float y = (currentBPPosition + area.getOffset()) / 1000f;
+            float y = (currentBPPosition + area.getBlockProgressionOffset()) / 1000f;
             float width = area.getIPD() / 1000f;
             drawBackAndBorders(area, x, y - borderPaddingBefore
                                 , width + bpwidth
@@ -688,7 +688,7 @@
     public void renderViewport(Viewport viewport) {
 
         float x = currentIPPosition / 1000f;
-        float y = (currentBPPosition + viewport.getOffset()) / 1000f;
+        float y = (currentBPPosition + viewport.getBlockProgressionOffset()) / 1000f;
         float width = viewport.getIPD() / 1000f;
         float height = viewport.getBPD() / 1000f;
         // TODO: Calculate the border rect correctly.
Index: src/java/org/apache/fop/render/intermediate/IFRenderer.java
===================================================================
--- src/java/org/apache/fop/render/intermediate/IFRenderer.java	(revision 981406)
+++ src/java/org/apache/fop/render/intermediate/IFRenderer.java	(working copy)
@@ -60,6 +60,7 @@
 import org.apache.fop.area.BookmarkData;
 import org.apache.fop.area.CTM;
 import org.apache.fop.area.DestinationData;
+import org.apache.fop.area.LineArea;
 import org.apache.fop.area.OffDocumentExtensionAttachment;
 import org.apache.fop.area.OffDocumentItem;
 import org.apache.fop.area.PageSequence;
@@ -473,7 +474,8 @@
         if (hasDocumentNavigation() && id != null) {
             int extraMarginBefore = 5000; // millipoints
             int ipp = currentIPPosition;
-            int bpp = currentBPPosition + inlineArea.getOffset() - extraMarginBefore;
+            int bpp = currentBPPosition
+                + inlineArea.getBlockProgressionOffset() - extraMarginBefore;
             saveAbsolutePosition(id, ipp, bpp);
         }
     }
@@ -920,7 +922,7 @@
         String ptr = (String) ip.getTrait(Trait.PTR); // used for accessibility
         // make sure the rect is determined *before* calling super!
         int ipp = currentIPPosition;
-        int bpp = currentBPPosition + ip.getOffset();
+        int bpp = currentBPPosition + ip.getBlockProgressionOffset();
         ipRect = new Rectangle(ipp, bpp, ip.getIPD(), ip.getBPD());
         AffineTransform transform = graphicContext.getTransform();
         ipRect = transform.createTransformedShape(ipRect).getBounds();
@@ -1011,7 +1013,7 @@
         }
 
         int rx = currentIPPosition + text.getBorderAndPaddingWidthStart();
-        int bl = currentBPPosition + text.getOffset() + text.getBaselineOffset();
+        int bl = currentBPPosition + text.getBlockProgressionOffset() + text.getBaselineOffset();
         textUtil.flush();
         textUtil.setStartPosition(rx, bl);
         textUtil.setSpacing(text.getTextLetterSpaceAdjust(), text.getTextWordSpaceAdjust());
@@ -1027,7 +1029,7 @@
         Font font = getFontFromArea(word.getParentArea());
         String s = word.getWord();
 
-        renderText(s, word.getLetterAdjustArray(),
+        renderText(s, word.getLetterAdjustArray(), word.isReversed(),
                 font, (AbstractTextArea)word.getParentArea());
 
         super.renderWord(word);
@@ -1039,7 +1041,7 @@
         String s = space.getSpace();
 
         AbstractTextArea textArea = (AbstractTextArea)space.getParentArea();
-        renderText(s, null, font, textArea);
+        renderText(s, null, false, font, textArea);
 
         if (textUtil.combined && space.isAdjustable()) {
             //Used for justified text, for example
@@ -1056,12 +1058,13 @@
      * Does low-level rendering of text.
      * @param s text to render
      * @param letterAdjust an array of widths for letter adjustment (may be null)
+     * @param reversed if true then text has been reversed (from logical order)
      * @param font to font in use
      * @param parentArea the parent text area to retrieve certain traits from
      */
     protected void renderText(String s,
-                           int[] letterAdjust,
-                           Font font, AbstractTextArea parentArea) {
+                              int[] letterAdjust, boolean reversed,
+                              Font font, AbstractTextArea parentArea) {
         int l = s.length();
         if (l == 0) {
             return;
@@ -1200,7 +1203,7 @@
         int style = area.getRuleStyle();
         int ruleThickness = area.getRuleThickness();
         int startx = currentIPPosition + area.getBorderAndPaddingWidthStart();
-        int starty = currentBPPosition + area.getOffset() + (ruleThickness / 2);
+        int starty = currentBPPosition + area.getBlockProgressionOffset() + (ruleThickness / 2);
         int endx = currentIPPosition
                         + area.getBorderAndPaddingWidthStart()
                         + area.getIPD();
Index: src/java/org/apache/fop/render/pcl/PCLRenderer.java
===================================================================
--- src/java/org/apache/fop/render/pcl/PCLRenderer.java	(revision 981406)
+++ src/java/org/apache/fop/render/pcl/PCLRenderer.java	(working copy)
@@ -473,7 +473,7 @@
         //Determine position
         int saveIP = currentIPPosition;
         final int rx = currentIPPosition + text.getBorderAndPaddingWidthStart();
-        int bl = currentBPPosition + text.getOffset() + text.getBaselineOffset();
+        int bl = currentBPPosition + text.getBlockProgressionOffset() + text.getBaselineOffset();
 
         try {
 
@@ -515,7 +515,7 @@
 
                 Graphics2DAdapter g2a = getGraphics2DAdapter();
                 final Rectangle paintRect = new Rectangle(
-                        rx, currentBPPosition + text.getOffset() - additionalBPD,
+                        rx, currentBPPosition + text.getBlockProgressionOffset() - additionalBPD,
                         text.getIPD() + extraWidth, text.getBPD() + additionalBPD);
                 RendererContext rc = createRendererContext(paintRect.x, paintRect.y,
                         paintRect.width, paintRect.height, null);
@@ -728,7 +728,7 @@
     public void renderViewport(Viewport viewport) {
 
         float x = currentIPPosition / 1000f;
-        float y = (currentBPPosition + viewport.getOffset()) / 1000f;
+        float y = (currentBPPosition + viewport.getBlockProgressionOffset()) / 1000f;
         float width = viewport.getIPD() / 1000f;
         float height = viewport.getBPD() / 1000f;
         // TODO: Calculate the border rect correctly.
@@ -1066,7 +1066,7 @@
      */
     protected void renderInlineAreaBackAndBorders(InlineArea area) {
         float x = currentIPPosition / 1000f;
-        float y = (currentBPPosition + area.getOffset()) / 1000f;
+        float y = (currentBPPosition + area.getBlockProgressionOffset()) / 1000f;
         float width = area.getIPD() / 1000f;
         float height = area.getBPD() / 1000f;
         float borderPaddingStart = area.getBorderAndPaddingWidthStart() / 1000f;
@@ -1487,7 +1487,7 @@
         saveGraphicsState();
         int style = area.getRuleStyle();
         float startx = (currentIPPosition + area.getBorderAndPaddingWidthStart()) / 1000f;
-        float starty = (currentBPPosition + area.getOffset()) / 1000f;
+        float starty = (currentBPPosition + area.getBlockProgressionOffset()) / 1000f;
         float endx = (currentIPPosition + area.getBorderAndPaddingWidthStart()
                         + area.getIPD()) / 1000f;
         float ruleThickness = area.getRuleThickness() / 1000f;
Index: src/java/org/apache/fop/render/ps/PSRenderer.java
===================================================================
--- src/java/org/apache/fop/render/ps/PSRenderer.java	(revision 981406)
+++ src/java/org/apache/fop/render/ps/PSRenderer.java	(working copy)
@@ -988,7 +988,7 @@
 
         //Determine position
         int rx = currentIPPosition + area.getBorderAndPaddingWidthStart();
-        int bl = currentBPPosition + area.getOffset() + area.getBaselineOffset();
+        int bl = currentBPPosition + area.getBlockProgressionOffset() + area.getBaselineOffset();
 
         Color ct = (Color)area.getTrait(Trait.COLOR);
         if (ct != null) {
@@ -1212,7 +1212,7 @@
         int style = area.getRuleStyle();
         int ruleThickness = area.getRuleThickness();
         int startx = currentIPPosition + area.getBorderAndPaddingWidthStart();
-        int starty = currentBPPosition + area.getOffset() + (ruleThickness / 2);
+        int starty = currentBPPosition + area.getBlockProgressionOffset() + (ruleThickness / 2);
         int endx = currentIPPosition
                         + area.getBorderAndPaddingWidthStart()
                         + area.getIPD();
Index: src/java/org/apache/fop/render/xml/XMLRenderer.java
===================================================================
--- src/java/org/apache/fop/render/xml/XMLRenderer.java	(revision 981406)
+++ src/java/org/apache/fop/render/xml/XMLRenderer.java	(working copy)
@@ -683,6 +683,7 @@
             break;
         default: //nop
         }
+        maybeAddLevelAttribute(block);
         startElement("block", atts);
         super.renderBlock(block);
         endElement("block");
@@ -695,6 +696,7 @@
         atts.clear();
         addAreaAttributes(line);
         addTraitAttributes(line);
+        maybeAddLevelAttribute(line);
         startElement("lineArea", atts);
         super.renderLineArea(line);
         endElement("lineArea");
@@ -725,7 +727,7 @@
         atts.clear();
         addAreaAttributes(viewport);
         addTraitAttributes(viewport);
-        addAttribute("offset", viewport.getOffset());
+        addAttribute("offset", viewport.getBlockProgressionOffset());
         addAttribute("pos", viewport.getContentPosition());
         if (viewport.getClip()) {
             addAttribute("clip", "true");
@@ -783,7 +785,7 @@
         atts.clear();
         addAreaAttributes(space);
         addTraitAttributes(space);
-        addAttribute("offset", space.getOffset());
+        addAttribute("offset", space.getBlockProgressionOffset());
         startElement("space", atts);
         endElement("space");
     }
@@ -799,10 +801,11 @@
         if (text.getTextLetterSpaceAdjust() != 0) {
             addAttribute("tlsadjust", text.getTextLetterSpaceAdjust());
         }
-        addAttribute("offset", text.getOffset());
+        addAttribute("offset", text.getBlockProgressionOffset());
         addAttribute("baseline", text.getBaselineOffset());
         addAreaAttributes(text);
         addTraitAttributes(text);
+        maybeAddLevelAttribute(text);
         startElement("text", atts);
         super.renderText(text);
         endElement("text");
@@ -813,7 +816,10 @@
      */
     protected void renderWord(WordArea word) {
         atts.clear();
-        addAttribute("offset", word.getOffset());
+        int offset = word.getBlockProgressionOffset();
+        if ( offset != 0 ) {
+            addAttribute("offset", offset);
+        }
         int[] letterAdjust = word.getLetterAdjustArray();
         if (letterAdjust != null) {
             StringBuffer sb = new StringBuffer(64);
@@ -829,6 +835,10 @@
                 addAttribute("letter-adjust", sb.toString());
             }
         }
+        maybeAddLevelAttribute(word);
+        if ( word.isReversed() ) {
+            addAttribute("reversed", "true");
+        }
         startElement("word", atts);
         characters(word.getWord());
         endElement("word");
@@ -840,7 +850,11 @@
      */
     protected void renderSpace(SpaceArea space) {
         atts.clear();
-        addAttribute("offset", space.getOffset());
+        int offset = space.getBlockProgressionOffset();
+        if ( offset != 0 ) {
+            addAttribute("offset", offset);
+        }
+        maybeAddLevelAttribute(space);
         if (!space.isAdjustable()) {
             addAttribute("adj", "false"); //default is true
         }
@@ -857,7 +871,8 @@
         atts.clear();
         addAreaAttributes(ip);
         addTraitAttributes(ip);
-        addAttribute("offset", ip.getOffset());
+        addAttribute("offset", ip.getBlockProgressionOffset());
+        maybeAddLevelAttribute(ip);
         startElement("inlineparent", atts);
         super.renderInlineParent(ip);
         endElement("inlineparent");
@@ -870,7 +885,8 @@
         atts.clear();
         addAreaAttributes(ibp);
         addTraitAttributes(ibp);
-        addAttribute("offset", ibp.getOffset());
+        addAttribute("offset", ibp.getBlockProgressionOffset());
+        maybeAddLevelAttribute(ibp);
         startElement("inlineblockparent", atts);
         super.renderInlineBlockParent(ibp);
         endElement("inlineblockparent");
@@ -883,7 +899,7 @@
         atts.clear();
         addAreaAttributes(area);
         addTraitAttributes(area);
-        addAttribute("offset", area.getOffset());
+        addAttribute("offset", area.getBlockProgressionOffset());
         addAttribute("ruleStyle", area.getRuleStyleAsString());
         addAttribute("ruleThickness", area.getRuleThickness());
         startElement("leader", atts);
@@ -896,5 +912,11 @@
         return XML_MIME_TYPE;
     }
 
+    private void maybeAddLevelAttribute ( Area a ) {
+        int level = a.getBidiLevel();
+        if ( level >= 0 ) {
+            addAttribute ( "level", level );
+        }
+    }
+
 }
-
Index: src/java/org/apache/fop/render/afp/AFPRenderer.java
===================================================================
--- src/java/org/apache/fop/render/afp/AFPRenderer.java	(revision 981406)
+++ src/java/org/apache/fop/render/afp/AFPRenderer.java	(working copy)
@@ -622,7 +622,7 @@
         textDataInfo.setFontReference(fontReference);
 
         int x = (currentIPPosition + text.getBorderAndPaddingWidthStart());
-        int y = (currentBPPosition + text.getOffset() + text.getBaselineOffset());
+        int y = (currentBPPosition + text.getBlockProgressionOffset() + text.getBaselineOffset());
 
         int[] coords = unitConv.mpts2units(new float[] {x, y} );
         textDataInfo.setX(coords[X]);
@@ -660,7 +660,7 @@
                 = AFPEventProducer.Provider.get(userAgent.getEventBroadcaster());
             eventProducer.characterSetEncodingError(this, charSet.getName(), encoding);
         }
-        // word.getOffset() = only height of text itself
+        // word.getBlockProgressionOffset() = only height of text itself
         // currentBlockIPPosition: 0 for beginning of line; nonzero
         // where previous line area failed to take up entire allocated space
 
@@ -682,7 +682,7 @@
         int style = area.getRuleStyle();
         float startx = (currentIPPosition + area
                 .getBorderAndPaddingWidthStart()) / 1000f;
-        float starty = (currentBPPosition + area.getOffset()) / 1000f;
+        float starty = (currentBPPosition + area.getBlockProgressionOffset()) / 1000f;
         float endx = (currentIPPosition + area.getBorderAndPaddingWidthStart() + area
                 .getIPD()) / 1000f;
         float ruleThickness = area.getRuleThickness() / 1000f;
Index: src/java/org/apache/fop/render/java2d/ConfiguredFontCollection.java
===================================================================
--- src/java/org/apache/fop/render/java2d/ConfiguredFontCollection.java	(revision 981406)
+++ src/java/org/apache/fop/render/java2d/ConfiguredFontCollection.java	(working copy)
@@ -89,7 +89,8 @@
                 } else {
                     CustomFont fontMetrics = FontLoader.loadFont(
                             fontFile, null, true, EncodingMode.AUTO,
-                            configFontInfo.getKerning(), fontResolver);
+                            configFontInfo.getKerning(),
+                            configFontInfo.getAdvanced(), fontResolver);
                     font = new CustomFontMetricsMapper(fontMetrics);
                 }
 
Index: src/java/org/apache/fop/render/java2d/Java2DRenderer.java
===================================================================
--- src/java/org/apache/fop/render/java2d/Java2DRenderer.java	(revision 981406)
+++ src/java/org/apache/fop/render/java2d/Java2DRenderer.java	(working copy)
@@ -711,7 +711,7 @@
         renderInlineAreaBackAndBorders(text);
 
         int rx = currentIPPosition + text.getBorderAndPaddingWidthStart();
-        int bl = currentBPPosition + text.getOffset() + text.getBaselineOffset();
+        int bl = currentBPPosition + text.getBlockProgressionOffset() + text.getBaselineOffset();
         int saveIP = currentIPPosition;
 
         Font font = getFontFromArea(text);
@@ -823,7 +823,7 @@
         // TODO Colors do not work on Leaders yet
 
         float startx = (currentIPPosition + area.getBorderAndPaddingWidthStart()) / 1000f;
-        float starty = ((currentBPPosition + area.getOffset()) / 1000f);
+        float starty = ((currentBPPosition + area.getBlockProgressionOffset()) / 1000f);
         float endx = (currentIPPosition + area.getBorderAndPaddingWidthStart()
                 + area.getIPD()) / 1000f;
 
Index: src/java/org/apache/fop/render/pdf/PDFRenderer.java
===================================================================
--- src/java/org/apache/fop/render/pdf/PDFRenderer.java	(revision 981406)
+++ src/java/org/apache/fop/render/pdf/PDFRenderer.java	(working copy)
@@ -841,7 +841,8 @@
         if (id != null) {
             int extraMarginBefore = 5000; // millipoints
             int ipp = currentIPPosition;
-            int bpp = currentBPPosition + inlineArea.getOffset() - extraMarginBefore;
+            int bpp = currentBPPosition
+                + inlineArea.getBlockProgressionOffset() - extraMarginBefore;
             saveAbsolutePosition(id, ipp, bpp);
         }
     }
@@ -884,7 +885,7 @@
         if (annotsAllowed) {
             // make sure the rect is determined *before* calling super!
             int ipp = currentIPPosition;
-            int bpp = currentBPPosition + ip.getOffset();
+            int bpp = currentBPPosition + ip.getBlockProgressionOffset();
             ipRect = new Rectangle2D.Float(ipp / 1000f, bpp / 1000f,
                                            ip.getIPD() / 1000f, ip.getBPD() / 1000f);
             AffineTransform transform = getState().getTransform();
@@ -987,11 +988,11 @@
         textutil.updateTf(fontName, size / 1000f, tf.isMultiByte());
 
 
-        // word.getOffset() = only height of text itself
+        // word.getBlockProgressionOffset() = only height of text itself
         // currentBlockIPPosition: 0 for beginning of line; nonzero
         //  where previous line area failed to take up entire allocated space
         int rx = currentIPPosition + text.getBorderAndPaddingWidthStart();
-        int bl = currentBPPosition + text.getOffset() + text.getBaselineOffset();
+        int bl = currentBPPosition + text.getBlockProgressionOffset() + text.getBaselineOffset();
 
         textutil.writeTextMatrix(new AffineTransform(1, 0, 0, -1, rx / 1000f, bl / 1000f));
 
@@ -1288,7 +1289,7 @@
         int style = area.getRuleStyle();
         int ruleThickness = area.getRuleThickness();
         int startx = currentIPPosition + area.getBorderAndPaddingWidthStart();
-        int starty = currentBPPosition + area.getOffset() + (ruleThickness / 2);
+        int starty = currentBPPosition + area.getBlockProgressionOffset() + (ruleThickness / 2);
         int endx = currentIPPosition
                         + area.getBorderAndPaddingWidthStart()
                         + area.getIPD();
Index: src/java/org/apache/fop/render/AbstractRenderer.java
===================================================================
--- src/java/org/apache/fop/render/AbstractRenderer.java	(revision 981406)
+++ src/java/org/apache/fop/render/AbstractRenderer.java	(working copy)
@@ -534,13 +534,11 @@
                 renderBlock((Block) obj);
                 containingBPPosition = contBP;
                 containingIPPosition = contIP;
-            } else {
+            } else if (obj instanceof LineArea) {
                 // a line area is rendered from the top left position
                 // of the line, each inline object is offset from there
                 LineArea line = (LineArea) obj;
-                currentIPPosition = contIP
-                        + parent.getStartIndent()
-                        + line.getStartIndent();
+                currentIPPosition = contIP + parent.getStartIndent();
                 renderLineArea(line);
                 //InlineArea child = (InlineArea) line.getInlineAreas().get(0);
                 currentBPPosition += line.getAllocBPD();
@@ -604,8 +602,9 @@
         List children = line.getInlineAreas();
         int saveBP = currentBPPosition;
         currentBPPosition += line.getSpaceBefore();
-        for (int count = 0; count < children.size(); count++) {
-            InlineArea inline = (InlineArea) children.get(count);
+        currentIPPosition += line.getStartIndent(); 
+        for (int i = 0, l = children.size(); i < l; i++) {
+            InlineArea inline = (InlineArea) children.get(i);
             renderInlineArea(inline);
         }
         currentBPPosition = saveBP;
@@ -671,11 +670,12 @@
      * @param text the text to render
      */
     protected void renderText(TextArea text) {
+        List children = text.getChildAreas();
         int saveIP = currentIPPosition;
         int saveBP = currentBPPosition;
-        Iterator iter = text.getChildAreas().iterator();
-        while (iter.hasNext()) {
-            renderInlineArea((InlineArea) iter.next());
+        for (int i = 0, l = children.size(); i < l; i++) {
+            InlineArea inline = (InlineArea) children.get(i);
+            renderInlineArea(inline);
         }
         currentIPPosition = saveIP + text.getAllocIPD();
     }
@@ -701,14 +701,15 @@
      * @param ip the inline parent to render
      */
     protected void renderInlineParent(InlineParent ip) {
+        List children = ip.getChildAreas();
         renderInlineAreaBackAndBorders(ip);
         int saveIP = currentIPPosition;
         int saveBP = currentBPPosition;
         currentIPPosition += ip.getBorderAndPaddingWidthStart();
-        currentBPPosition += ip.getOffset();
-        Iterator iter = ip.getChildAreas().iterator();
-        while (iter.hasNext()) {
-            renderInlineArea((InlineArea) iter.next());
+        currentBPPosition += ip.getBlockProgressionOffset();
+        for (int i = 0, l = children.size(); i < l; i++) {
+            InlineArea inline = (InlineArea) children.get(i);
+            renderInlineArea(inline);
         }
         currentIPPosition = saveIP + ip.getAllocIPD();
         currentBPPosition = saveBP;
@@ -723,7 +724,7 @@
         currentIPPosition += ibp.getBorderAndPaddingWidthStart();
         // For inline content the BP position is updated by the enclosing line area
         int saveBP = currentBPPosition;
-        currentBPPosition += ibp.getOffset();
+        currentBPPosition += ibp.getBlockProgressionOffset();
         renderBlock(ibp.getChildArea());
         currentBPPosition = saveBP;
     }
@@ -735,7 +736,7 @@
     protected void renderViewport(Viewport viewport) {
         Area content = viewport.getContent();
         int saveBP = currentBPPosition;
-        currentBPPosition += viewport.getOffset();
+        currentBPPosition += viewport.getBlockProgressionOffset();
         Rectangle2D contpos = viewport.getContentPosition();
         if (content instanceof Image) {
             renderImage((Image) content, contpos);
Index: src/java/org/apache/fop/layoutmgr/LayoutManagerMapping.java
===================================================================
--- src/java/org/apache/fop/layoutmgr/LayoutManagerMapping.java	(revision 981406)
+++ src/java/org/apache/fop/layoutmgr/LayoutManagerMapping.java	(working copy)
@@ -239,27 +239,9 @@
     }
 
     public static class BidiOverrideLayoutManagerMaker extends Maker {
-        // public static class BidiOverrideLayoutManagerMaker extends FObjMixedLayoutManagerMaker {
-        public void make(BidiOverride node, List lms) {
-            if (false) {
-                // this is broken; it does nothing
-                // it should make something like an InlineStackingLM
-                super.make(node, lms);
-            } else {
-                ArrayList childList = new ArrayList();
-                // this is broken; it does nothing
-                // it should make something like an InlineStackingLM
-                super.make(node, childList);
-                for (int count = childList.size() - 1; count >= 0; count--) {
-                    LayoutManager lm = (LayoutManager) childList.get(count);
-                    if (lm instanceof InlineLevelLayoutManager) {
-                        LayoutManager blm = new BidiLayoutManager
-                            (node, (InlineLayoutManager) lm);
-                        lms.add(blm);
-                    } else {
-                        lms.add(lm);
-                    }
-                }
+        public void make(FONode node, List lms) {
+            if ( node instanceof BidiOverride ) {
+                lms.add(new BidiLayoutManager((BidiOverride) node));
             }
         }
     }
Index: src/java/org/apache/fop/layoutmgr/PageSequenceLayoutManager.java
===================================================================
--- src/java/org/apache/fop/layoutmgr/PageSequenceLayoutManager.java	(revision 981406)
+++ src/java/org/apache/fop/layoutmgr/PageSequenceLayoutManager.java	(working copy)
@@ -77,8 +77,10 @@
     public void activateLayout() {
         initialize();
 
+        // perform step 5.8 of refinement process (Unicode BIDI Processing)
+        BidiUtil.resolveInlineDirectionality(getPageSequence());
+
         LineArea title = null;
-
         if (getPageSequence().getTitleFO() != null) {
             try {
                 ContentLayoutManager clm = getLayoutManagerMaker().
Index: src/java/org/apache/fop/layoutmgr/inline/BidiLayoutManager.java
===================================================================
--- src/java/org/apache/fop/layoutmgr/inline/BidiLayoutManager.java	(revision 981406)
+++ src/java/org/apache/fop/layoutmgr/inline/BidiLayoutManager.java	(working copy)
@@ -19,46 +19,18 @@
 
 package org.apache.fop.layoutmgr.inline;
 
-import java.util.ArrayList;
-import java.util.List;
-
-import org.apache.fop.area.inline.InlineArea;
 import org.apache.fop.fo.flow.BidiOverride;
 
-
 /**
- * If this bidi has a different writing mode direction
- * ltr or rtl than its parent writing mode then this
- * reverses the inline areas (at the character level).
+ * Layout manager for fo:bidi-override.
  */
-public class BidiLayoutManager extends LeafNodeLayoutManager {
+public class BidiLayoutManager extends InlineLayoutManager {
 
-    private List children;
-
-    public BidiLayoutManager(BidiOverride node, InlineLayoutManager cLM) {
+    /**
+     * Instantiate bidi override layout manager.
+     * @param node an BidiOverride FONode
+     */
+    public BidiLayoutManager(BidiOverride node) {
         super(node);
-        setParent(cLM);
-        children = new ArrayList();
-/*
-        for (int count = cLM.size() - 1; count >= 0; count--) {
-            InlineArea ia = cLM.get(count);
-            if (ia instanceof Word) {
-                // reverse word
-                Word word = (Word) ia;
-                StringBuffer sb = new StringBuffer(word.getWord());
-                word.setWord(sb.reverse().toString());
-            }
-            children.add(ia);
-        }
-*/
     }
-
-    public int size() {
-        return children.size();
-    }
-
-    public InlineArea get(int index) {
-        return (InlineArea) children.get(index);
-    }
-
 }
Index: src/java/org/apache/fop/layoutmgr/inline/ScaledBaselineTable.java
===================================================================
--- src/java/org/apache/fop/layoutmgr/inline/ScaledBaselineTable.java	(revision 981406)
+++ src/java/org/apache/fop/layoutmgr/inline/ScaledBaselineTable.java	(working copy)
@@ -20,6 +20,7 @@
 package org.apache.fop.layoutmgr.inline;
 
 import org.apache.fop.datatypes.Length;
+import org.apache.fop.traits.WritingMode;
 
 /**
  * The FOP specific incarnation of the XSL-FO scaled baseline table.
@@ -40,7 +41,7 @@
      * Return the writing mode for this aligment context.
      * @return the writing mode
      */
-    int getWritingMode();
+    WritingMode getWritingMode();
 
     /**
      * Return the offset measured from the dominant
Index: src/java/org/apache/fop/layoutmgr/inline/InlineLayoutManager.java
===================================================================
--- src/java/org/apache/fop/layoutmgr/inline/InlineLayoutManager.java	(revision 981406)
+++ src/java/org/apache/fop/layoutmgr/inline/InlineLayoutManager.java	(working copy)
@@ -201,7 +201,7 @@
         InlineArea area;
         if (hasInlineParent) {
             area = new InlineParent();
-            area.setOffset(0);
+            area.setBlockProgressionOffset(0);
         } else {
             area = new InlineBlockParent();
         }
@@ -457,12 +457,12 @@
                                         || lastLM instanceof InlineLevelLayoutManager);
         parent.setBPD(alignmentContext.getHeight());
         if (parent instanceof InlineParent) {
-            parent.setOffset(alignmentContext.getOffset());
+            parent.setBlockProgressionOffset(alignmentContext.getOffset());
         } else if (parent instanceof InlineBlockParent) {
             // All inline elements are positioned by the renderers relative to
             // the before edge of their content rectangle
             if (borderProps != null) {
-                parent.setOffset(borderProps.getPaddingBefore(false, this)
+                parent.setBlockProgressionOffset(borderProps.getPaddingBefore(false, this)
                                 + borderProps.getBorderBeforeWidth(false));
             }
         }
Index: src/java/org/apache/fop/layoutmgr/inline/PageNumberLayoutManager.java
===================================================================
--- src/java/org/apache/fop/layoutmgr/inline/PageNumberLayoutManager.java	(revision 981406)
+++ src/java/org/apache/fop/layoutmgr/inline/PageNumberLayoutManager.java	(working copy)
@@ -101,7 +101,7 @@
         TraitSetter.setProducerID(ta, fobj.getId());
         ta.setIPD(baseArea.getIPD());
         ta.setBPD(baseArea.getBPD());
-        ta.setOffset(baseArea.getOffset());
+        ta.setBlockProgressionOffset(baseArea.getBlockProgressionOffset());
         ta.setBaselineOffset(baseArea.getBaselineOffset());
         ta.addTrait(Trait.COLOR, fobj.getColor()); //only to initialize the trait map
         ta.getTraits().putAll(baseArea.getTraits());
Index: src/java/org/apache/fop/layoutmgr/inline/LineLayoutManager.java
===================================================================
--- src/java/org/apache/fop/layoutmgr/inline/LineLayoutManager.java	(revision 981406)
+++ src/java/org/apache/fop/layoutmgr/inline/LineLayoutManager.java	(working copy)
@@ -44,6 +44,7 @@
 import org.apache.fop.hyphenation.Hyphenation;
 import org.apache.fop.hyphenation.Hyphenator;
 import org.apache.fop.layoutmgr.Adjustment;
+import org.apache.fop.layoutmgr.BidiUtil;
 import org.apache.fop.layoutmgr.BlockLevelLayoutManager;
 import org.apache.fop.layoutmgr.BreakElement;
 import org.apache.fop.layoutmgr.BreakingAlgorithm;
@@ -91,6 +92,7 @@
 
     /** {@inheritDoc} */
     public void initialize() {
+        bidiLevel = fobj.getBidiLevel();
         textAlignment = fobj.getTextAlign();
         textAlignmentLast = fobj.getTextAlignLast();
         textIndent = fobj.getTextIndent();
@@ -155,6 +157,7 @@
     }
 
 
+    private int bidiLevel = -1;
     private int textAlignment = EN_JUSTIFY;
     private int textAlignmentLast;
     private int effectiveAlignment;
@@ -1464,6 +1467,7 @@
         }
         lineArea.setBPD(lbp.lineHeight);
         lineArea.setIPD(lbp.lineWidth);
+        lineArea.setBidiLevel(bidiLevel);
         lineArea.addTrait(Trait.SPACE_BEFORE, new Integer(lbp.spaceBefore));
         lineArea.addTrait(Trait.SPACE_AFTER, new Integer(lbp.spaceAfter));
         alignmentContext.resizeLine(lbp.lineHeight, lbp.baseline);
@@ -1568,7 +1572,10 @@
                 && (!context.isLastArea() || !isLastPosition)) {
             lineArea.setBPD(lineArea.getBPD() + context.getSpaceAfter());
         }
-        lineArea.finalise();
+        lineArea.finish();
+        if ( lineArea.getBidiLevel() >= 0 ) {
+            BidiUtil.reorder ( lineArea );
+        }
         parentLayoutManager.addChildArea(lineArea);
     }
 
@@ -1618,6 +1625,9 @@
             blocklc.setTrailingSpace(new SpaceSpecifier(false));
         }
         lineArea.updateExtentsFromChildren();
+        if ( lineArea.getBidiLevel() >= 0 ) {
+            BidiUtil.reorder ( lineArea );
+        }
         parentLayoutManager.addChildArea(lineArea);
     }
 
@@ -1652,4 +1662,3 @@
     }
 
 }
-
Index: src/java/org/apache/fop/layoutmgr/inline/AlignmentContext.java
===================================================================
--- src/java/org/apache/fop/layoutmgr/inline/AlignmentContext.java	(revision 981406)
+++ src/java/org/apache/fop/layoutmgr/inline/AlignmentContext.java	(working copy)
@@ -24,6 +24,7 @@
 import org.apache.fop.datatypes.SimplePercentBaseContext;
 import org.apache.fop.fo.Constants;
 import org.apache.fop.fonts.Font;
+import org.apache.fop.traits.WritingMode;
 
 /**
  * The alignment context is carried within a LayoutContext and as
@@ -171,13 +172,13 @@
      * @param lineHeight the computed value of the lineHeight property
      * @param writingMode the current writing mode
      */
-    public AlignmentContext(Font font, int lineHeight, int writingMode) {
+    public AlignmentContext(Font font, int lineHeight, WritingMode writingMode) {
         this.areaHeight = font.getAscender() - font.getDescender();
         this.lineHeight = lineHeight;
         this.xHeight = font.getXHeight();
         this.parentAlignmentContext = null;
         this.scaledBaselineTable
-                    = ScaledBaselineTableFactory.makeFontScaledBaselineTable(font, writingMode);
+            = ScaledBaselineTableFactory.makeFontScaledBaselineTable(font, writingMode);
         this.actualBaselineTable = scaledBaselineTable;
         this.alignmentBaselineIdentifier = getDominantBaselineIdentifier();
         this.alignmentPoint = font.getAscender();
@@ -299,7 +300,7 @@
      * Return the writing mode.
      * @return the writing mode
      */
-    public int getWritingMode() {
+    public WritingMode getWritingMode() {
         return scaledBaselineTable.getWritingMode();
     }
 
@@ -508,7 +509,7 @@
     }
 
     private boolean isHorizontalWritingMode() {
-        return (getWritingMode() == EN_LR_TB || getWritingMode() == EN_RL_TB);
+        return (getWritingMode() == WritingMode.LR_TB || getWritingMode() == WritingMode.RL_TB);
     }
 
     /** {@inheritDoc} */
Index: src/java/org/apache/fop/layoutmgr/inline/CharacterLayoutManager.java
===================================================================
--- src/java/org/apache/fop/layoutmgr/inline/CharacterLayoutManager.java	(revision 981406)
+++ src/java/org/apache/fop/layoutmgr/inline/CharacterLayoutManager.java	(working copy)
@@ -76,13 +76,17 @@
     private TextArea getCharacterInlineArea(Character node) {
         TextArea text = new TextArea();
         char ch = node.getCharacter();
+        int blockProgressionOffset = 0;
+        int level = node.bidiLevelAt(0);
         if (CharUtilities.isAnySpace(ch)) {
             // add space unless it's zero-width:
             if (!CharUtilities.isZeroWidthSpace(ch)) {
-                text.addSpace(ch, 0, CharUtilities.isAdjustableSpace(ch));
+                text.addSpace(ch, 0, CharUtilities.isAdjustableSpace(ch),
+                              blockProgressionOffset, level);
             }
         } else {
-            text.addWord(String.valueOf(ch), 0);
+            int[] levels = ( level >= 0 ) ? new int[] {level} : null;
+            text.addWord(String.valueOf(ch), 0, null, levels, blockProgressionOffset);
         }
         TraitSetter.setProducerID(text, node.getId());
         TraitSetter.addTextDecoration(text, node.getTextDecoration());
@@ -207,7 +211,8 @@
                     new LeafPosition(this, -1), true));
             returnList.add(new KnuthGlue(letterSpaceIPD.mult(areaInfo.iLScount),
                     new LeafPosition(this, -1), true));
-            returnList.add(new KnuthInlineBox(0, null, notifyPos(new LeafPosition(this, -1)), true));
+            returnList.add(new KnuthInlineBox(0, null,
+                                              notifyPos(new LeafPosition(this, -1)), true));
             if (areaInfo.bHyphenated) {
                 returnList.add(new KnuthPenalty(hyphIPD, KnuthPenalty.FLAGGED_PENALTY, true,
                         new LeafPosition(this, -1), false));
@@ -221,4 +226,3 @@
     }
 
 }
-
Index: src/java/org/apache/fop/layoutmgr/inline/ScaledBaselineTableFactory.java
===================================================================
--- src/java/org/apache/fop/layoutmgr/inline/ScaledBaselineTableFactory.java	(revision 981406)
+++ src/java/org/apache/fop/layoutmgr/inline/ScaledBaselineTableFactory.java	(working copy)
@@ -21,8 +21,8 @@
 
 import org.apache.fop.fo.Constants;
 import org.apache.fop.fonts.Font;
+import org.apache.fop.traits.WritingMode;
 
-
 /**
  * A factory class for making alignment contexts.
  * Currently supports alignment contexts for basic fonts
@@ -35,12 +35,12 @@
      * font, baseline and writingmode.
      * @param font the font for which a baseline table is requested
      * @param dominantBaselineIdentifier the dominant baseline given as an integer constant
-     * @param writingMode the writing mode given as an integer constant
+     * @param writingMode the writing mode
      * @return a scaled baseline table for the given font
      */
     public static ScaledBaselineTable makeFontScaledBaselineTable(Font font
                                                                   , int dominantBaselineIdentifier
-                                                                  , int writingMode) {
+                                                                  , WritingMode writingMode) {
         return new BasicScaledBaselineTable(font.getAscender(), font.getDescender()
                                     , font.getXHeight(), dominantBaselineIdentifier, writingMode);
     }
@@ -49,10 +49,11 @@
      * Creates a new instance of BasicScaledBaselineTable for the given
      * font and writingmode. It assumes an alphabetic baseline.
      * @param font the font for which a baseline table is requested
-     * @param writingMode the writing mode given as an integer constant
+     * @param writingMode the writing mode
      * @return a scaled baseline table for the given font
      */
-    public static ScaledBaselineTable makeFontScaledBaselineTable(Font font, int writingMode) {
+    public static ScaledBaselineTable makeFontScaledBaselineTable
+        ( Font font, WritingMode writingMode ) {
         return makeFontScaledBaselineTable(font, EN_ALPHABETIC, writingMode);
     }
 
@@ -62,12 +63,12 @@
      * external graphic or inline foreign object.
      * @param height the height for which a baseline table is requested
      * @param dominantBaselineIdentifier the dominant baseline given as an integer constant
-     * @param writingMode the writing mode given as an integer constant
+     * @param writingMode the writing mode
      * @return a scaled baseline table for the given dimensions
      */
     public static ScaledBaselineTable makeGraphicsScaledBaselineTable(int height
                                                                 , int dominantBaselineIdentifier
-                                                                , int writingMode) {
+                                                                , WritingMode writingMode) {
         return new BasicScaledBaselineTable(height, 0, height
                                             , dominantBaselineIdentifier, writingMode);
     }
Index: src/java/org/apache/fop/layoutmgr/inline/LeafNodeLayoutManager.java
===================================================================
--- src/java/org/apache/fop/layoutmgr/inline/LeafNodeLayoutManager.java	(revision 981406)
+++ src/java/org/apache/fop/layoutmgr/inline/LeafNodeLayoutManager.java	(working copy)
@@ -213,7 +213,7 @@
      * @param context the layout context used for adding the area
      */
     protected void offsetArea(InlineArea area, LayoutContext context) {
-        area.setOffset(alignmentContext.getOffset());
+        area.setBlockProgressionOffset(alignmentContext.getOffset());
     }
 
     /**
Index: src/java/org/apache/fop/layoutmgr/inline/BasicScaledBaselineTable.java
===================================================================
--- src/java/org/apache/fop/layoutmgr/inline/BasicScaledBaselineTable.java	(revision 981406)
+++ src/java/org/apache/fop/layoutmgr/inline/BasicScaledBaselineTable.java	(working copy)
@@ -25,6 +25,7 @@
 import org.apache.fop.datatypes.LengthBase;
 import org.apache.fop.datatypes.SimplePercentBaseContext;
 import org.apache.fop.fo.Constants;
+import org.apache.fop.traits.WritingMode;
 
 
 /**
@@ -40,7 +41,7 @@
     private int depth;
     private int xHeight;
     private int dominantBaselineIdentifier;
-    private int writingMode;
+    private WritingMode writingMode;
     private int dominantBaselineOffset;
     private int beforeEdgeOffset;
     private int afterEdgeOffset;
@@ -62,7 +63,7 @@
                                     , int depth
                                     , int xHeight
                                     , int dominantBaselineIdentifier
-                                    , int writingMode) {
+                                    , WritingMode writingMode) {
         this.altitude = altitude;
         this.depth = depth;
         this.xHeight = xHeight;
@@ -85,7 +86,7 @@
      * Return the writing mode for this baseline table.
      * @return the writing mode
      */
-    public int getWritingMode() {
+    public WritingMode getWritingMode() {
         return this.writingMode;
     }
 
@@ -134,7 +135,7 @@
     }
 
     private boolean isHorizontalWritingMode() {
-        return writingMode == EN_LR_TB || writingMode == EN_RL_TB;
+        return writingMode == WritingMode.LR_TB || writingMode == WritingMode.RL_TB;
     }
 
     /**
Index: src/java/org/apache/fop/layoutmgr/inline/AbstractGraphicsLayoutManager.java
===================================================================
--- src/java/org/apache/fop/layoutmgr/inline/AbstractGraphicsLayoutManager.java	(revision 981406)
+++ src/java/org/apache/fop/layoutmgr/inline/AbstractGraphicsLayoutManager.java	(working copy)
@@ -91,7 +91,7 @@
         vp.setBPD(imageLayout.getViewportSize().height);
         vp.setContentPosition(placement);
         vp.setClip(imageLayout.isClipped());
-        vp.setOffset(0);
+        vp.setBlockProgressionOffset(0);
 
         // Common Border, Padding, and Background Properties
         TraitSetter.addBorders(vp, fobj.getCommonBorderPaddingBackground()
Index: src/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java
===================================================================
--- src/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java	(revision 981406)
+++ src/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java	(working copy)
@@ -32,6 +32,7 @@
 import org.apache.fop.fo.Constants;
 import org.apache.fop.fo.FOText;
 import org.apache.fop.fo.FObj;
+import org.apache.fop.fo.flow.Character;
 import org.apache.fop.fo.properties.StructurePointerPropertySet;
 import org.apache.fop.fonts.Font;
 import org.apache.fop.fonts.FontSelector;
@@ -80,6 +81,7 @@
         private final boolean isSpace;
         private boolean breakOppAfter;
         private final Font font;
+        private final int level;
 
         AreaInfo(final int startIndex,
                  final int breakIndex,
@@ -89,7 +91,8 @@
                  final boolean isHyphenated,
                  final boolean isSpace,
                  final boolean breakOppAfter,
-                 final Font font) {
+                 final Font font,
+                 final int level) {
             assert startIndex <= breakIndex;
             this.startIndex = startIndex;
             this.breakIndex = breakIndex;
@@ -100,6 +103,7 @@
             this.isSpace = isSpace;
             this.breakOppAfter = breakOppAfter;
             this.font = font;
+            this.level = level;
         }
 
         private int getCharLength() {
@@ -111,16 +115,16 @@
         }
 
         public String toString() {
-            return "AreaInfo["
-                    + "letterSpaceCount = " + letterSpaceCount
+            return super.toString() + "{"
+                    + "interval = [" + startIndex + "," + breakIndex + "]"
+                    + ", isSpace = " + isSpace
+                    + ", level = " + level
+                    + ", areaIPD = " + areaIPD
+                    + ", letterSpaceCount = " + letterSpaceCount
                     + ", wordSpaceCount = " + wordSpaceCount
-                    + ", areaIPD = " + areaIPD
-                    + ", startIndex = " + startIndex
-                    + ", breakIndex = " + breakIndex
                     + ", isHyphenated = " + isHyphenated
-                    + ", isSpace = " + isSpace
                     + ", font = " + font
-                    + "]";
+                    + "}";
         }
     }
 
@@ -275,7 +279,9 @@
             }
             if (tbpNext.getLeafPos() != -1) {
                 areaInfo = (AreaInfo) areaInfos.get(tbpNext.getLeafPos());
-                if (lastAreaInfo == null || areaInfo.font != lastAreaInfo.font) {
+                if (lastAreaInfo == null
+                    || ( areaInfo.font != lastAreaInfo.font )
+                    || ( areaInfo.level != lastAreaInfo.level ) ) {
                     if (lastAreaInfo != null) {
                         addAreaInfoAreas(lastAreaInfo, wordSpaceCount,
                                 letterSpaceCount, firstAreaInfoIndex,
@@ -297,6 +303,7 @@
             addAreaInfoAreas(lastAreaInfo, wordSpaceCount, letterSpaceCount, firstAreaInfoIndex,
                     lastAreaInfoIndex, realWidth, context);
         }
+
     }
 
     private void addAreaInfoAreas(AreaInfo areaInfo, int wordSpaceCount, int letterSpaceCount,
@@ -406,6 +413,9 @@
         private StringBuffer wordChars;
         private int[] letterAdjust;
         private int letterAdjustIndex;
+        private int[] wordLevels;
+        private int wordLevelsCount;
+        private int wordIPD;
 
         private TextArea textArea;
 
@@ -438,7 +448,7 @@
             calcBlockProgressionDimension();
             setBlockProgressionDimension();
             setBaselineOffset();
-            setOffset();
+            setBlockProgressionOffset();
             setText();
             TraitSetter.addFontTraits(textArea, font);
             textArea.addTrait(Trait.COLOR, foText.getColor());
@@ -477,11 +487,11 @@
             textArea.setBaselineOffset(font.getAscender());
         }
 
-        private void setOffset() {
+        private void setBlockProgressionOffset() {
             if (blockProgressionDimension == alignmentContext.getHeight()) {
-                textArea.setOffset(0);
+                textArea.setBlockProgressionOffset(0);
             } else {
-                textArea.setOffset(alignmentContext.getOffset());
+                textArea.setBlockProgressionOffset(alignmentContext.getOffset());
             }
         }
 
@@ -516,6 +526,7 @@
         }
 
         private void addWord(int startIndex, int endIndex, int charLength) {
+            int blockProgressionOffset = 0;
             if (isHyphenated(endIndex)) {
                 charLength++;
             }
@@ -528,13 +539,17 @@
             if (isHyphenated(endIndex)) {
                 addHyphenationChar();
             }
-            textArea.addWord(wordChars.toString(), 0, letterAdjust);
+            textArea.addWord(wordChars.toString(), wordIPD, letterAdjust, wordLevels,
+                             blockProgressionOffset);
         }
 
         private void initWord(int charLength) {
             wordChars = new StringBuffer(charLength);
             letterAdjust = new int[charLength];
             letterAdjustIndex = 0;
+            wordLevels = new int[charLength];
+            wordLevelsCount = 0;
+            wordIPD = 0;
         }
 
         private boolean isHyphenated(int endIndex) {
@@ -546,11 +561,34 @@
         }
 
         private void addWordChars(AreaInfo wordAreaInfo) {
-            for (int i = wordAreaInfo.startIndex; i < wordAreaInfo.breakIndex; i++) {
-                wordChars.append(foText.charAt(i));
+            int s = wordAreaInfo.startIndex;
+            int e = wordAreaInfo.breakIndex;
+            if ( foText.hasMapping ( s, e ) ) {
+                wordChars.append ( foText.getMapping ( s, e ) );
+                addWordLevels ( foText.getMappingBidiLevels ( s, e ) );
+            } else {
+                for (int i = s; i < e; i++) {
+                    wordChars.append(foText.charAt(i));
+                }
+                addWordLevels ( foText.getBidiLevels ( s, e ) );
             }
+            wordIPD += wordAreaInfo.areaIPD.getOpt();
         }
 
+        private void addWordLevels ( int[] levels ) {
+            if ( levels != null ) {
+                int n = levels.length;
+                int need = wordLevelsCount + n;
+                if ( need > wordLevels.length ) {
+                    int[] wordLevelsNew = new int [ need * 2 ];
+                    System.arraycopy ( wordLevels, 0, wordLevelsNew, 0, wordLevelsCount );
+                    wordLevels = wordLevelsNew;
+                }
+                System.arraycopy ( levels, 0, wordLevels, wordLevelsCount, n );
+                wordLevelsCount += n;
+            }
+        }
+
         private void addLetterAdjust(AreaInfo wordAreaInfo) {
             int letterSpaceCount = wordAreaInfo.letterSpaceCount;
             for (int i = wordAreaInfo.startIndex; i < wordAreaInfo.breakIndex; i++) {
@@ -572,13 +610,32 @@
          * Add the spaces - except zero-width spaces - to the TextArea.
          */
         private void addSpaces() {
+            int blockProgressionOffset = 0;
+            // [TBD] need to better handling of spaceIPD assignment, for now,
+            // divide the area info's allocated IPD evenly among the
+            // non-zero-width space characters
+            int numZeroWidthSpaces = 0;
             for (int i = areaInfo.startIndex; i < areaInfo.breakIndex; i++) {
                 char spaceChar = foText.charAt(i);
+                if (CharUtilities.isZeroWidthSpace(spaceChar)) {
+                    numZeroWidthSpaces++;
+                }
+            }
+            int numSpaces = areaInfo.breakIndex - areaInfo.startIndex - numZeroWidthSpaces;
+            int spaceIPD = areaInfo.areaIPD.getOpt() / ( ( numSpaces > 0 ) ? numSpaces : 1 );
+            // add space area children, one for each non-zero-width space character
+            for (int i = areaInfo.startIndex; i < areaInfo.breakIndex; i++) {
+                char spaceChar = foText.charAt(i);
+                int level = foText.bidiLevelAt(i);
                 if (!CharUtilities.isZeroWidthSpace(spaceChar)) {
-                    textArea.addSpace(spaceChar, 0, CharUtilities.isAdjustableSpace(spaceChar));
+                    textArea.addSpace
+                        ( spaceChar, spaceIPD,
+                          CharUtilities.isAdjustableSpace(spaceChar),
+                          blockProgressionOffset, level );
                 }
             }
         }
+
     }
 
     /**
@@ -595,6 +652,18 @@
         }
     }
 
+    private void addAreaInfo ( AreaInfo ai ) {
+        addAreaInfo ( areaInfos.size(), ai );
+    }
+
+    private void addAreaInfo ( int index, AreaInfo ai ) {
+        areaInfos.add ( index, ai );
+    }
+
+    private void removeAreaInfo ( int index ) {
+        areaInfos.remove ( index );
+    }
+
     private AreaInfo getAreaInfo(int index) {
         return (AreaInfo) areaInfos.get(index);
     }
@@ -620,6 +689,7 @@
 
     /** {@inheritDoc} */
     public List getNextKnuthElements(final LayoutContext context, final int alignment) {
+
         lineStartBAP = context.getLineStartBorderAndPaddingWidth();
         lineEndBAP = context.getLineEndBorderAndPaddingWidth();
         alignmentContext = context.getAlignmentContext();
@@ -635,8 +705,11 @@
         boolean inWord = false;
         boolean inWhitespace = false;
         char ch = 0;
+        int level = -1;
+        int prevLevel = -1;
         while (nextStart < foText.length()) {
             ch = foText.charAt(nextStart);
+            level = foText.bidiLevelAt(nextStart);
             boolean breakOpportunity = false;
             byte breakAction = keepTogether
                     ? LineBreakStatus.PROHIBITED_BREAK
@@ -656,12 +729,13 @@
                     TextLayoutManager.LOG.error("Unexpected breakAction: " + breakAction);
             }
             if (inWord) {
-                if (breakOpportunity
-                        || TextLayoutManager.isSpace(ch)
-                        || CharUtilities.isExplicitBreak(ch)) {
+                if ( breakOpportunity
+                     || TextLayoutManager.isSpace(ch)
+                     || CharUtilities.isExplicitBreak(ch)
+                     || ( ( prevLevel != -1 ) && ( level != prevLevel ) ) ) {
                     // this.foText.charAt(lastIndex) == CharUtilities.SOFT_HYPHEN
                     prevAreaInfo = processWord(alignment, sequence, prevAreaInfo, ch,
-                            breakOpportunity, true);
+                            breakOpportunity, true, level);
                 }
             } else if (inWhitespace) {
                 if (ch != CharUtilities.SPACE || breakOpportunity) {
@@ -685,14 +759,14 @@
                 // preserved space or non-breaking space:
                 // create the AreaInfo object
                 areaInfo = new AreaInfo(nextStart, nextStart + 1, 1, 0, wordSpaceIPD, false, true,
-                        breakOpportunity, spaceFont);
+                        breakOpportunity, spaceFont, level);
                 thisStart = nextStart + 1;
             } else if (CharUtilities.isFixedWidthSpace(ch) || CharUtilities.isZeroWidthSpace(ch)) {
                 // create the AreaInfo object
                 Font font = FontSelector.selectFontForCharacterInText(ch, foText, this);
                 MinOptMax ipd = MinOptMax.getInstance(font.getCharWidth(ch));
                 areaInfo = new AreaInfo(nextStart, nextStart + 1, 0, 0, ipd, false, true,
-                        breakOpportunity, font);
+                        breakOpportunity, font, level);
                 thisStart = nextStart + 1;
             } else if (CharUtilities.isExplicitBreak(ch)) {
                 //mandatory break-character: only advance index
@@ -702,12 +776,13 @@
             inWord = !TextLayoutManager.isSpace(ch) && !CharUtilities.isExplicitBreak(ch);
             inWhitespace = ch == CharUtilities.SPACE
                     && foText.getWhitespaceTreatment() != Constants.EN_PRESERVE;
+            prevLevel = level;
             nextStart++;
         }
 
         // Process any last elements
         if (inWord) {
-            processWord(alignment, sequence, prevAreaInfo, ch, false, false);
+            processWord(alignment, sequence, prevAreaInfo, ch, false, false, prevLevel);
         } else if (inWhitespace) {
             processWhitespace(alignment, sequence, true);
         } else if (areaInfo != null) {
@@ -728,6 +803,8 @@
         } else {
             return returnList;
         }
+
+
     }
 
     private KnuthSequence processLinebreak(List returnList, KnuthSequence sequence) {
@@ -743,20 +820,23 @@
     private void processLeftoverAreaInfo(int alignment,
                                          KnuthSequence sequence, AreaInfo areaInfo,
                                          boolean breakOpportunityAfter) {
-        areaInfos.add(areaInfo);
+        addAreaInfo(areaInfo);
         areaInfo.breakOppAfter = breakOpportunityAfter;
         addElementsForASpace(sequence, alignment, areaInfo, areaInfos.size() - 1);
     }
 
     private AreaInfo processWhitespace(final int alignment,
             final KnuthSequence sequence, final boolean breakOpportunity) {
+
         // End of whitespace
         // create the AreaInfo object
         assert nextStart >= thisStart;
-        AreaInfo areaInfo = new AreaInfo(thisStart, nextStart, nextStart - thisStart, 0,
-                wordSpaceIPD.mult(nextStart - thisStart), false, true, breakOpportunity, spaceFont);
+        AreaInfo areaInfo = new AreaInfo
+            ( thisStart, nextStart, nextStart - thisStart, 0,
+              wordSpaceIPD.mult(nextStart - thisStart),
+              false, true, breakOpportunity, spaceFont, -1 );
 
-        areaInfos.add(areaInfo);
+        addAreaInfo(areaInfo);
 
         // create the elements
         addElementsForASpace(sequence, alignment, areaInfo, areaInfos.size() - 1);
@@ -765,24 +845,54 @@
         return areaInfo;
     }
 
-    private AreaInfo processWord(final int alignment, final KnuthSequence sequence,
-            AreaInfo prevAreaInfo, final char ch, final boolean breakOpportunity,
-            final boolean checkEndsWithHyphen) {
+    private AreaInfo processWordMapping
+        ( int lastIndex, final Font font, AreaInfo prevAreaInfo, final char breakOpportunityChar,
+          final boolean endsWithHyphen, int level ) {
+        int s = this.thisStart; // start index of word in FOText character buffer
+        int e = lastIndex;      // end index of word in FOText character buffer
+        int nLS = 0;            // # of letter spaces
+        String script = foText.getScript();
+        String language = foText.getLanguage();
 
-        //Word boundary found, process widths and kerning
-        int lastIndex = nextStart;
-        while (lastIndex > 0 && foText.charAt(lastIndex - 1) == CharUtilities.SOFT_HYPHEN) {
-            lastIndex--;
+        // extract unmapped character sequence
+        CharSequence ics = foText.subSequence ( s, e );
+
+        // if script is not specified (by FO property) or it is specified as 'auto',
+        // then compute dominant script
+        if ( ( script == null ) || "auto".equals(script) ) {
+            script = CharUtilities.scriptTagFromCode ( CharUtilities.dominantScript ( ics ) );
         }
-        final boolean endsWithHyphen = checkEndsWithHyphen
-                && foText.charAt(lastIndex) == CharUtilities.SOFT_HYPHEN;
-        Font font = FontSelector.selectFontForCharactersInText(foText, thisStart, lastIndex, foText, this);
+        if ( ( language == null ) || "none".equals(language) ) {
+            language = "dflt";
+        }
+
+        // perform mapping (of chars to glyphs ... to glyphs ... to chars)
+        CharSequence mcs = font.performSubstitution ( ics, script, language );
+
+        foText.addMapping ( s, e, mcs );
+
+        MinOptMax ipd = MinOptMax.ZERO;
+        for ( int i = 0, n = mcs.length(); i < n; i++ ) {
+            char c = mcs.charAt ( i );
+            int  w = font.getCharWidth ( c );
+            ipd = ipd.plus ( w );
+        }
+
+        // [TBD] - handle kerning
+        // [TBD] - handle letter spacing
+
+        return new AreaInfo
+            ( s, e, 0, nLS, ipd, endsWithHyphen, false, breakOpportunityChar != 0, font, level );
+    }
+
+    private AreaInfo processWordNoMapping(int lastIndex, final Font font, AreaInfo prevAreaInfo,
+            final char breakOpportunityChar, final boolean endsWithHyphen, int level) {
         int wordLength = lastIndex - thisStart;
         boolean kerning = font.hasKerning();
         MinOptMax wordIPD = MinOptMax.ZERO;
         for (int i = thisStart; i < lastIndex; i++) {
             char currentChar = foText.charAt(i);
-
+            
             //character width
             int charWidth = font.getCharWidth(currentChar);
             wordIPD = wordIPD.plus(charWidth);
@@ -793,7 +903,9 @@
                 if (i > thisStart) {
                     char previousChar = foText.charAt(i - 1);
                     kern = font.getKernValue(previousChar, currentChar);
-                } else if (prevAreaInfo != null && !prevAreaInfo.isSpace && prevAreaInfo.breakIndex > 0) {
+                } else if ( ( prevAreaInfo != null )
+                            && !prevAreaInfo.isSpace
+                            && ( prevAreaInfo.breakIndex > 0 ) ) {
                     char previousChar = foText.charAt(prevAreaInfo.breakIndex - 1);
                     kern = font.getKernValue(previousChar, currentChar);
                 }
@@ -804,33 +916,56 @@
             }
         }
         if (kerning
-                && breakOpportunity
-                && !TextLayoutManager.isSpace(ch)
+                && ( breakOpportunityChar != 0 )
+                && !TextLayoutManager.isSpace(breakOpportunityChar)
                 && lastIndex > 0
                 && endsWithHyphen) {
-            final int kern = font.getKernValue(foText.charAt(lastIndex - 1), ch);
+            final int kern = font.getKernValue(foText.charAt(lastIndex - 1), breakOpportunityChar);
             if (kern != 0) {
                 addToLetterAdjust(lastIndex, kern);
                 //TODO: add kern to wordIPD?
             }
         }
         int iLetterSpaces = wordLength - 1;
-        // if there is a break opportunity and the next one
+        // if there is a break opportunity and the next one (break character)
         // is not a space, it could be used as a line end;
         // add one more letter space, in case other text follows
-        if (breakOpportunity && !TextLayoutManager.isSpace(ch)) {
+        if (( breakOpportunityChar != 0 ) && !TextLayoutManager.isSpace(breakOpportunityChar)) {
             iLetterSpaces++;
         }
         assert iLetterSpaces >= 0;
         wordIPD = wordIPD.plus(letterSpaceIPD.mult(iLetterSpaces));
 
-        // create the AreaInfo object
-        AreaInfo areaInfo = new AreaInfo(thisStart, lastIndex, 0,
+        // create and return the AreaInfo object
+        return new AreaInfo(thisStart, lastIndex, 0,
                 iLetterSpaces, wordIPD,
                 endsWithHyphen,
-                false, breakOpportunity, font);
+                false, breakOpportunityChar != 0, font, level);
+    }
+
+    private AreaInfo processWord(final int alignment, final KnuthSequence sequence,
+            AreaInfo prevAreaInfo, final char ch, final boolean breakOpportunity,
+            final boolean checkEndsWithHyphen, int level) {
+
+        //Word boundary found, process widths and kerning
+        int lastIndex = nextStart;
+        while (lastIndex > 0 && foText.charAt(lastIndex - 1) == CharUtilities.SOFT_HYPHEN) {
+            lastIndex--;
+        }
+        final boolean endsWithHyphen = checkEndsWithHyphen
+                && foText.charAt(lastIndex) == CharUtilities.SOFT_HYPHEN;
+        Font font = FontSelector.selectFontForCharactersInText
+            ( foText, thisStart, lastIndex, foText, this );
+        AreaInfo areaInfo;
+        if ( font.performsGlyphSubstitution() ) {
+            areaInfo = processWordMapping
+                ( lastIndex, font, prevAreaInfo, breakOpportunity ? ch : 0, endsWithHyphen, level );
+        } else {
+            areaInfo = processWordNoMapping
+                ( lastIndex, font, prevAreaInfo, breakOpportunity ? ch : 0, endsWithHyphen, level );
+        }
         prevAreaInfo = areaInfo;
-        areaInfos.add(areaInfo);
+        addAreaInfo(areaInfo);
         tempStart = nextStart;
 
         //add the elements
@@ -900,7 +1035,7 @@
         int leafValue = ((LeafPosition) knuthElement.getPosition()).getLeafPos();
         // only the last word space can be a trailing space!
         if (leafValue == areaInfos.size() - 1) {
-            areaInfos.remove(leafValue);
+            removeAreaInfo(leafValue);
         } else {
             TextLayoutManager.LOG.error("trying to remove a non-trailing word space");
         }
@@ -953,7 +1088,9 @@
             }
 
             // add letter spaces
-            boolean isWordEnd = stopIndex == areaInfo.breakIndex && areaInfo.letterSpaceCount < areaInfo.getCharLength();
+            boolean isWordEnd
+                = ( stopIndex == areaInfo.breakIndex )
+                && ( areaInfo.letterSpaceCount < areaInfo.getCharLength() );
             int letterSpaceCount = isWordEnd ? stopIndex - startIndex - 1 : stopIndex - startIndex;
 
             assert letterSpaceCount >= 0;
@@ -962,7 +1099,7 @@
             if (!(nothingChanged && stopIndex == areaInfo.breakIndex && !hyphenFollows)) {
                 // the new AreaInfo object is not equal to the old one
                 changeList.add(new PendingChange(new AreaInfo(startIndex, stopIndex, 0,
-                        letterSpaceCount, newIPD, hyphenFollows, false, false, font),
+                        letterSpaceCount, newIPD, hyphenFollows, false, false, font, -1),
                         ((LeafPosition) pos).getLeafPos()));
                 nothingChanged = false;
             }
@@ -991,9 +1128,9 @@
                     areaInfosAdded++;
                     oldIndex = currChange.index;
                     changeIndex = currChange.index + areaInfosAdded - areaInfosRemoved;
-                    areaInfos.remove(changeIndex);
+                    removeAreaInfo(changeIndex);
                 }
-                areaInfos.add(changeIndex, currChange.areaInfo);
+                addAreaInfo(changeIndex, currChange.areaInfo);
             }
             changeList.clear();
         }
@@ -1065,8 +1202,11 @@
             if (foText.charAt(areaInfo.startIndex) != CharUtilities.SPACE
                     || foText.getWhitespaceTreatment() == Constants.EN_PRESERVE) {
                 // a breaking space that needs to be preserved
-                baseList.addAll(getElementsForBreakingSpace(alignment, areaInfo, auxiliaryPosition, 0,
-                        mainPosition, areaInfo.areaIPD.getOpt(), true));
+                baseList.addAll
+                    ( getElementsForBreakingSpace
+                      ( alignment, areaInfo,
+                        auxiliaryPosition, 0,
+                        mainPosition, areaInfo.areaIPD.getOpt(), true ) );
             } else {
                 // a (possible block) of breaking spaces
                 baseList.addAll(getElementsForBreakingSpace(alignment, areaInfo, mainPosition,
@@ -1104,16 +1244,22 @@
             // add a constant amount of stretch at the end of a line, otherwise
             // they don't add any stretch
             if (skipZeroCheck || lineStartBAP != 0 || lineEndBAP != 0) {
-                elements.add(new KnuthGlue(lineEndBAP, 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0, auxiliaryPosition, false));
+                elements.add(new KnuthGlue(lineEndBAP, 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH,
+                                           0, auxiliaryPosition, false));
                 elements.add(makeZeroWidthPenalty(0));
-                elements.add(new KnuthGlue(p2WidthOffset - (lineStartBAP + lineEndBAP), -3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0, pos2, false));
+                elements.add(new KnuthGlue(p2WidthOffset - (lineStartBAP + lineEndBAP),
+                                           -3 * LineLayoutManager.DEFAULT_SPACE_WIDTH,
+                                           0, pos2, false));
                 elements.add(makeAuxiliaryZeroWidthBox());
                 elements.add(makeZeroWidthPenalty(KnuthElement.INFINITE));
                 elements.add(new KnuthGlue(lineStartBAP + p3WidthOffset, 0, 0, pos3, false));
             } else {
-                elements.add(new KnuthGlue(0, 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0, auxiliaryPosition, false));
+                elements.add(new KnuthGlue(0, 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
+                                           auxiliaryPosition, false));
                 elements.add(makeZeroWidthPenalty(0));
-                elements.add(new KnuthGlue(areaInfo.areaIPD.getOpt(), -3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0, pos2, false));
+                elements.add(new KnuthGlue(areaInfo.areaIPD.getOpt(),
+                                           -3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
+                                           pos2, false));
             }
             break;
 
@@ -1133,9 +1279,10 @@
         return elements;
     }
 
-    private List getElementsForJustifiedText(AreaInfo areaInfo, Position pos2, int p2WidthOffset,
-                                             Position pos3, int p3WidthOffset, boolean skipZeroCheck,
-                                             int shrinkability) {
+    private List getElementsForJustifiedText
+        ( AreaInfo areaInfo, Position pos2, int p2WidthOffset,
+          Position pos3, int p3WidthOffset, boolean skipZeroCheck,
+          int shrinkability ) {
 
         int stretchability = areaInfo.areaIPD.getStretch();
 
@@ -1306,4 +1453,14 @@
 
     }
 
+    /** {@inheritDoc} */
+    public String toString() {
+        return super.toString() + "{"
+            + "chars = \'"
+            + CharUtilities.toNCRefs ( new String ( foText.getCharArray(), 0, foText.length() ) )
+            + "\'"
+            + ", len = " + foText.length()
+            + "}";
+    }
+
 }
Index: src/java/org/apache/fop/layoutmgr/LayoutContext.java
===================================================================
--- src/java/org/apache/fop/layoutmgr/LayoutContext.java	(revision 981406)
+++ src/java/org/apache/fop/layoutmgr/LayoutContext.java	(working copy)
@@ -26,6 +26,7 @@
 import org.apache.fop.layoutmgr.inline.AlignmentContext;
 import org.apache.fop.layoutmgr.inline.HyphContext;
 import org.apache.fop.traits.MinOptMax;
+import org.apache.fop.traits.WritingMode;
 
 
 /**
@@ -89,7 +90,7 @@
     //overlap with refIPD. Need to investigate how best to refactor that.
 
     /** the writing mode established by the nearest ancestor reference area */
-    private int writingMode = Constants.EN_LR_TB;
+    private WritingMode writingMode = WritingMode.LR_TB;
 
     /** Current pending space-after or space-end from preceding area */
     private SpaceSpecifier trailingSpace;
@@ -531,7 +532,7 @@
      * Get the writing mode of the relevant reference area.
      * @return the applicable writing mode
      */
-    public int getWritingMode() {
+    public WritingMode getWritingMode() {
         return writingMode;
     }
 
@@ -539,7 +540,7 @@
      * Set the writing mode.
      * @param writingMode the writing mode
      */
-    public void setWritingMode(int writingMode) {
+    public void setWritingMode(WritingMode writingMode) {
         this.writingMode = writingMode;
     }
 
Index: src/java/org/apache/fop/layoutmgr/ExternalDocumentLayoutManager.java
===================================================================
--- src/java/org/apache/fop/layoutmgr/ExternalDocumentLayoutManager.java	(revision 981406)
+++ src/java/org/apache/fop/layoutmgr/ExternalDocumentLayoutManager.java	(working copy)
@@ -51,6 +51,7 @@
 import org.apache.fop.fo.Constants;
 import org.apache.fop.fo.extensions.ExternalDocument;
 import org.apache.fop.layoutmgr.inline.ImageLayout;
+import org.apache.fop.traits.WritingMode;
 
 /**
  * LayoutManager for an external-document extension element.  This class is instantiated by
@@ -190,7 +191,7 @@
         vp.setIPD(imageSize.width);
         vp.setBPD(imageSize.height);
         vp.setContentPosition(imageLayout.getPlacement());
-        vp.setOffset(0);
+        vp.setBlockProgressionOffset(0);
 
         //Link them all together...
         lineArea.addInlineArea(vp);
@@ -231,8 +232,9 @@
             referenceRect = new Rectangle(0, 0, imageSize.height, imageSize.width);
         }
         FODimension reldims = new FODimension(0, 0);
+        // [TBD] BIDI ALERT
         CTM pageCTM = CTM.getCTMandRelDims(pageSeq.getReferenceOrientation(),
-            Constants.EN_LR_TB, referenceRect, reldims);
+                                           WritingMode.LR_TB, referenceRect, reldims);
 
         Page page = new Page(referenceRect, pageNumber, pageNumberString, isBlank);
 
Index: src/java/org/apache/fop/layoutmgr/BlockLayoutManager.java
===================================================================
--- src/java/org/apache/fop/layoutmgr/BlockLayoutManager.java	(revision 981406)
+++ src/java/org/apache/fop/layoutmgr/BlockLayoutManager.java	(working copy)
@@ -421,6 +421,8 @@
 
             curBlockArea.setIPD(super.getContentAreaIPD());
 
+            curBlockArea.setBidiLevel ( getBlockFO().getBidiLevel() );
+
             TraitSetter.addBreaks(curBlockArea,
                     getBlockFO().getBreakBefore(), getBlockFO().getBreakAfter());
 
@@ -563,4 +565,3 @@
     }
 
 }
-
Index: src/java/org/apache/fop/layoutmgr/AbstractLayoutManager.java
===================================================================
--- src/java/org/apache/fop/layoutmgr/AbstractLayoutManager.java	(revision 981406)
+++ src/java/org/apache/fop/layoutmgr/AbstractLayoutManager.java	(working copy)
@@ -456,7 +456,7 @@
 
     /** {@inheritDoc} */
     public String toString() {
-        return (super.toString() + (fobj != null ? "[fobj=" + fobj.toString() + "]" : ""));
+        return (super.toString() + (fobj != null ? "{fobj = " + fobj.toString() + "}" : ""));
     }
 
     /** {@inheritDoc} */
Index: src/java/org/apache/fop/layoutmgr/BidiUtil.java
===================================================================
--- src/java/org/apache/fop/layoutmgr/BidiUtil.java	(revision 0)
+++ src/java/org/apache/fop/layoutmgr/BidiUtil.java	(revision 0)
@@ -0,0 +1,1542 @@
+/*
+ * 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.layoutmgr;
+
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Stack;
+import java.util.Vector;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.fop.area.LineArea;
+import org.apache.fop.area.inline.Anchor;
+import org.apache.fop.area.inline.InlineArea;
+import org.apache.fop.area.inline.InlineBlockParent;
+import org.apache.fop.area.inline.InlineParent;
+import org.apache.fop.area.inline.Leader;
+import org.apache.fop.area.inline.Space;
+import org.apache.fop.area.inline.SpaceArea;
+import org.apache.fop.area.inline.TextArea;
+import org.apache.fop.area.inline.WordArea;
+import org.apache.fop.area.inline.Viewport;
+import org.apache.fop.fo.CharIterator;
+import org.apache.fop.fo.Constants;
+import org.apache.fop.fo.FObj;
+import org.apache.fop.fo.FONode;
+import org.apache.fop.fo.FOText;
+import org.apache.fop.fo.flow.AbstractPageNumberCitation;
+import org.apache.fop.fo.flow.BidiOverride;
+import org.apache.fop.fo.flow.Block;
+import org.apache.fop.fo.flow.BlockContainer;
+import org.apache.fop.fo.flow.Character;
+import org.apache.fop.fo.flow.ExternalGraphic;
+import org.apache.fop.fo.flow.Inline;
+import org.apache.fop.fo.flow.InlineContainer;
+import org.apache.fop.fo.flow.InlineLevel;
+import org.apache.fop.fo.flow.PageNumber;
+import org.apache.fop.fo.pagination.Flow;
+import org.apache.fop.fo.pagination.PageSequence;
+import org.apache.fop.traits.Direction;
+import org.apache.fop.traits.WritingModeTraitsGetter;
+import org.apache.fop.text.bidi.BidiClassUtils;
+import org.apache.fop.util.CharUtilities;
+import org.apache.fop.util.BidiConstants;
+
+// CSOFF: AvoidNestedBlocksCheck
+// CSOFF: EmptyForIteratorPadCheck
+// CSOFF: NoWhitespaceAfterCheck
+// CSOFF: InnerAssignmentCheck
+// CSOFF: SimplifyBooleanReturnCheck
+// CSOFF: LineLengthCheck
+// CSOFF: ParameterNumberCheck
+
+/**
+ * <p>A utility class for performing bidirectional processing.</p>
+ * @author Glenn Adams
+ */
+public final class BidiUtil {
+
+    /**
+     * logging instance
+     */
+    private static final Log log = LogFactory.getLog(BidiUtil.class);                                                   // CSOK: ConstantNameCheck
+
+    private BidiUtil() {
+    }
+
+    /**
+     * Resolve inline directionality.
+     * @param ps a page sequence FO instance
+     */
+    public static void resolveInlineDirectionality ( PageSequence ps ) {
+        if (log.isDebugEnabled()) {
+            log.debug ( "BD: RESOLVE: " + ps );
+        }
+        List ranges = pruneEmptyRanges ( collectRanges ( ps, new Stack() ) );
+        resolveInlineDirectionality ( ranges );
+    }
+
+    /**
+     * Reorder line area.
+     * @param la a line area instance
+     */
+    public static void reorder ( LineArea la ) {
+
+        // 1. collect inline levels
+        List runs = collectRuns ( la.getInlineAreas(), new Vector() );
+        if (log.isDebugEnabled()) {
+            dumpRuns ( "BD: REORDER: INPUT:", runs );
+        }
+        
+        // 2. split heterogeneous inlines
+        runs = splitRuns ( runs );
+        if (log.isDebugEnabled()) {
+            dumpRuns ( "BD: REORDER: SPLIT INLINES:", runs );
+        }
+
+        // 3. determine minimum and maximum levels
+        int[] mm = computeMinMaxLevel ( runs, null );
+        if (log.isDebugEnabled()) {
+            log.debug( "BD: REORDER: { min = " + mm[0] + ", max = " + mm[1] + "}" );
+        }
+
+        // 4. reorder from maximum level to minimum odd level
+        int mn = mm[0];
+        int mx = mm[1];
+        for ( int l1 = mx, l2 = ( ( mn & 1 ) == 0 ) ? ( mn + 1 ) : mn; l1 >= l2; l1-- ) {
+            runs = reorderRuns ( runs, l1 );
+        }
+        if (log.isDebugEnabled()) {
+            dumpRuns ( "BD: REORDER: REORDERED RUNS:", runs );
+        }
+
+        // 5. reverse word consituents (characters and glyphs) while mirroring
+        boolean mirror = true;
+        reverseWords ( runs, mirror );
+        if (log.isDebugEnabled()) {
+            dumpRuns ( "BD: REORDER: REORDERED WORDS:", runs );
+        }
+
+        // 6. replace line area's inline areas with reordered runs' inline areas
+        replaceInlines ( la, replicateSplitWords ( runs ) );
+    }
+
+    private static void resolveInlineDirectionality ( List ranges ) {
+        for ( Iterator it = ranges.iterator(); it.hasNext(); ) {
+            DelimitedTextRange r = (DelimitedTextRange) it.next();
+            r.resolve();
+            if (log.isDebugEnabled()) {
+                log.debug ( r );
+            }
+        }
+    }
+
+    /**
+     * Collect the sequence of delimited text ranges of node FO, where each new
+     * range is pushed onto RANGES.
+     */
+    private static Stack collectRanges ( FONode fn, Stack ranges ) {
+        // return existing ranges if passed null node
+        if ( fn == null ) {
+            return ranges;
+        }
+        // if boundary before, then push new range
+        if ( isRangeBoundaryBefore ( fn ) ) {
+            maybeNewRange ( ranges, fn );
+        }
+        // get current range, if one exists
+        DelimitedTextRange r;
+        if ( ranges.size() > 0 ) {
+            r = (DelimitedTextRange) ranges.peek();
+        } else {
+            r = null;
+        }
+        // proceses this node
+        if ( fn instanceof FOText ) {
+            if ( r != null ) {
+                r.append ( ( (FOText) fn ) .charIterator(), fn );
+            }
+        } else if ( fn instanceof Character ) {
+            if ( r != null ) {
+                r.append ( ( (Character) fn ) .charIterator(), fn );
+            }
+        } else if ( fn instanceof BidiOverride ) {
+            if ( r != null ) {
+                ranges = collectBidiOverrideRanges ( (BidiOverride) fn, r, ranges );
+            }
+        } else if ( fn instanceof PageSequence ) {
+            ranges = collectRanges ( ( (PageSequence) fn ) .getMainFlow(), ranges );
+        } else {
+            for ( Iterator it = fn.getChildNodes(); ( it != null ) && it.hasNext(); ) {
+                ranges = collectRanges ( (FONode) it.next(), ranges );
+            }
+        }
+        // if boundary after, then push new range
+        if ( isRangeBoundaryAfter ( fn ) ) {
+            maybeNewRange ( ranges, fn );
+        }
+        return ranges;
+    }
+
+    private static Stack collectBidiOverrideRanges ( BidiOverride bo, DelimitedTextRange r, Stack ranges ) {
+        char pfx = 0;
+        char sfx = 0;
+        int unicodeBidi = bo.getUnicodeBidi();
+        int direction = bo.getDirection();
+        if ( unicodeBidi == Constants.EN_BIDI_OVERRIDE ) {
+            pfx = ( direction == Constants.EN_RTL ) ? CharUtilities.RLO : CharUtilities.LRO;
+            sfx = CharUtilities.PDF;
+        } else if ( unicodeBidi == Constants.EN_EMBED ) {
+            pfx = ( direction == Constants.EN_RTL ) ? CharUtilities.RLE : CharUtilities.LRE;
+            sfx = CharUtilities.PDF;
+        }
+        if ( pfx != 0 ) {
+            r.append ( pfx, bo );
+        }
+        for ( Iterator it = bo.getChildNodes(); ( it != null ) && it.hasNext(); ) {
+            ranges = collectRanges ( (FONode) it.next(), ranges );
+        }
+        if ( sfx != 0 ) {
+            r.append ( sfx, bo );
+        }
+        return ranges;
+    }
+
+    private static List collectRuns ( List inlines, List runs ) {
+        for ( Iterator it = inlines.iterator(); it.hasNext(); ) {
+            InlineArea ia = (InlineArea) it.next();
+            if ( ia instanceof WordArea ) {
+                runs = collectRuns ( (WordArea) ia, runs );
+            } else if ( ia instanceof SpaceArea ) {
+                runs = collectRuns ( (SpaceArea) ia, runs );
+            } else if ( ia instanceof InlineParent ) {
+                runs = collectRuns ( (InlineParent) ia, runs );
+            } else if ( ia instanceof Viewport ) {
+                runs = collectRuns ( (Viewport) ia, runs );
+            } else if ( ia instanceof Leader ) {
+                runs = collectRuns ( (Leader) ia, runs );
+            } else if ( ia instanceof Space ) {
+                runs = collectRuns ( (Space) ia, runs );
+            } else if ( ia instanceof Anchor ) {
+                runs = collectRuns ( (Anchor) ia, runs );
+            } else if ( ia instanceof InlineBlockParent ) {
+                runs = collectRuns ( (InlineBlockParent) ia, runs );
+            }
+        }
+        return runs;
+    }
+
+    private static List collectRuns ( Anchor a, List runs ) {
+        return runs;
+    }
+
+    private static List collectRuns ( InlineBlockParent a, List runs ) {
+        return runs;
+    }
+
+    private static List collectRuns ( InlineParent a, List runs ) {
+        return collectRuns ( a.getChildAreas(), runs );
+    }
+
+    private static List collectRuns ( Leader a, List runs ) {
+        return runs;
+    }
+
+    private static List collectRuns ( Space a, List runs ) {
+        return runs;
+    }
+
+    private static List collectRuns ( SpaceArea a, List runs ) {
+        runs.add ( new InlineRun ( a, new int[] {a.getBidiLevel()}) );
+        return runs;
+    }
+
+    private static List collectRuns ( Viewport a, List runs ) {
+        return runs;
+    }
+
+    private static List collectRuns ( WordArea a, List runs ) {
+        runs.add ( new InlineRun ( a, a.getBidiLevels() ) );
+        return runs;
+    }
+
+    private static List splitRuns ( List runs ) {
+        List runsNew = new Vector();
+        for ( Iterator it = runs.iterator(); it.hasNext(); ) {
+            InlineRun ir = (InlineRun) it.next();
+            if ( ir.isHomogenous() ) {
+                runsNew.add ( ir );
+            } else {
+                runsNew.addAll ( ir.split() );
+            }
+        }
+        if ( ! runsNew.equals ( runs ) ) {
+            runs = runsNew;
+        }
+        return runs;
+    }
+
+    private static int[] computeMinMaxLevel ( List runs, int[] mm ) {
+        if ( mm == null ) {
+            mm = new int[] {Integer.MAX_VALUE, Integer.MIN_VALUE};
+        }
+        for ( Iterator it = runs.iterator(); it.hasNext(); ) {
+            InlineRun ir = (InlineRun) it.next();
+            ir.updateMinMax ( mm );
+        }
+        return mm;
+    }
+    private static List reorderRuns ( List runs, int level ) {
+        List runsNew = new Vector();
+        for ( int i = 0, n = runs.size(); i < n; i++ ) {
+            InlineRun iri = (InlineRun) runs.get(i);
+            if ( iri.getMinLevel() < level ) {
+                runsNew.add ( iri );
+            } else {
+                int s = i;
+                int e = s;
+                while ( e < n ) {
+                    InlineRun ire = (InlineRun) runs.get(e);
+                    if ( ire.getMinLevel() < level ) {
+                        break;
+                    } else {
+                        e++;
+                    }
+                }
+                if ( s < e ) {
+                    runsNew.addAll ( reverseRuns ( runs, s, e ) );
+                }
+                i = e - 1;
+            }
+        }
+        if ( ! runsNew.equals ( runs ) ) {
+            runs = runsNew;
+        }
+        return runs;
+    }
+    private static List reverseRuns ( List runs, int s, int e ) {
+        int n = e - s;
+        Vector runsNew = new Vector ( n );
+        if ( n > 0 ) {
+            for ( int i = 0; i < n; i++ ) {
+                int k = ( n - i - 1 );
+                InlineRun ir = (InlineRun) runs.get(s + k);
+                ir.reverse();
+                runsNew.add ( ir );
+            }
+        }
+        return runsNew;
+    }
+    private static void reverseWords ( List runs, boolean mirror ) {
+        for ( Iterator it = runs.iterator(); it.hasNext(); ) {
+            InlineRun ir = (InlineRun) it.next();
+            ir.maybeReverseWord ( mirror );
+        }
+    }
+    private static List replicateSplitWords ( List runs ) {
+        // [TBD] for each run which inline word area appears multiple times in
+        // runs, replicate that word
+        return runs;
+    }
+    private static void replaceInlines ( LineArea la, List runs ) {
+        List inlines = new ArrayList();
+        for ( Iterator it = runs.iterator(); it.hasNext(); ) {
+            InlineRun ir = (InlineRun) it.next();
+            inlines.add ( ir.getInline() );
+        }
+        inlines = unflattenInlines ( inlines );
+        la.setInlineAreas ( inlines );
+    }
+    private static List unflattenInlines ( List inlines ) {
+        List inlinesNew = new ArrayList();                              // unflattened inlines being consed
+        TextArea tLast = null;                                          // last text area parent
+        TextArea tNew = null;                                           // new text area being consed
+        int lLast = -1;                                                 // last bidi level
+        for ( Iterator it = inlines.iterator(); it.hasNext(); ) {
+            InlineArea ia = (InlineArea) it.next();
+            if ( ( ia instanceof WordArea ) || ( ia instanceof SpaceArea ) ) {
+                TextArea t = (TextArea) ia.getParentArea();
+                int l = ia.getBidiLevel();
+                if ( isEndOfTextArea ( t, tLast, l, lLast ) ) {
+                    if ( tNew != null ) {
+                        inlinesNew.add ( tNew );
+                        tNew = null;
+                    }
+                }
+                if ( tNew == null ) {
+                    tNew = createUnflattenedText ( t );
+                }
+                tNew.addChildArea ( ia );
+                tLast = t;
+                lLast = l;
+            } else {
+                inlinesNew.add ( ia );
+            }
+        }
+        if ( tNew != null ) {
+            inlinesNew.add ( tNew );
+        }
+        return inlinesNew;
+    }
+    private static boolean isEndOfTextArea ( TextArea t, TextArea tLast, int level, int levelLast ) {
+        if ( ( tLast != null ) && ( t != tLast ) ) {
+            return true;
+        } else if ( ( levelLast != -1 ) && ( level != levelLast ) ) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+    private static TextArea createUnflattenedText ( TextArea t ) {
+        TextArea tNew = new TextArea();
+        if ( t != null ) {
+            tNew.setBPD ( t.getBPD() );
+            tNew.setTraits ( t.getTraits() );
+            tNew.setBlockProgressionOffset ( t.getBlockProgressionOffset() );
+            tNew.setBaselineOffset ( t.getBaselineOffset() );
+        }
+        return tNew;
+    }
+    private static void dumpRuns ( String header, List runs ) {
+        log.debug ( header );
+        for ( Iterator it = runs.iterator(); it.hasNext(); ) {
+            InlineRun ir = (InlineRun) it.next();
+            log.debug ( ir );
+        }
+    }
+
+    /**
+     * <p>Conditionally add a new delimited text range to RANGES, where new range is
+     * associated with node FN. A new text range is added unless all of the following are true:</p>
+     * <ul>
+     * <li>there exists a current range RCUR in RANGES</li>
+     * <li>RCUR is empty</li>
+     * <li>the node of the RCUR is the same node as FN or a descendent node of FN</li>
+     * </ul>
+     */
+    private static DelimitedTextRange maybeNewRange ( Stack ranges, FONode fn ) {
+        DelimitedTextRange rCur = null;
+        DelimitedTextRange rNew = null;
+        if ( ranges.empty() ) {
+            if ( fn instanceof Block ) {
+                rNew = new DelimitedTextRange(fn);
+            }
+        } else if ( ( rCur = (DelimitedTextRange) ranges.peek() ) != null ) {
+            if ( ! rCur.isEmpty() || ! isSelfOrDescendent ( rCur.getNode(), fn ) ) {
+                rNew = new DelimitedTextRange(fn);
+            }
+        }
+        if ( rNew != null ) {
+            ranges.push ( rNew );
+        } else {
+            rNew = rCur;
+        }
+        return rNew;
+    }
+
+    /**
+     * Determine if node N2 is the same or a descendent of node N1.
+     */
+    private static boolean isSelfOrDescendent ( FONode n1, FONode n2 ) {
+        for ( FONode n = n2; n != null; n = n.getParent() ) {
+            if ( n == n1 ) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static boolean isRangeBoundary ( FONode fn ) {
+        if ( fn instanceof Block ) {                                    // fo:block
+            return true;
+        } else if ( fn instanceof InlineLevel ) {                       // fo:inline, fo:leader, fo:bidi-override, fo:title
+            return false;
+        } else if ( fn instanceof InlineContainer ) {                   // fo:inline-container
+            return false;
+        } else if ( fn instanceof BlockContainer ) {                    // fo:block-container
+            return true;
+        } else if ( fn instanceof AbstractPageNumberCitation ) {        // fo:page-number-citation, fo:page-number-citation-last
+            return false;
+        } else if ( fn instanceof PageNumber ) {                        // fo:page-number
+            return false;
+        } else if ( fn instanceof ExternalGraphic ) {                   // fo:external-graphic
+            return false;
+        } else if ( fn instanceof FOText ) {                            // #PCDATA
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+    private static boolean isRangeBoundaryBefore ( FONode fn ) {
+        return isRangeBoundary ( fn );
+    }
+
+    private static boolean isRangeBoundaryAfter ( FONode fn ) {
+        return isRangeBoundary ( fn );
+    }
+
+    private static List pruneEmptyRanges ( Stack ranges ) {
+        Vector rv = new Vector();
+        for ( Iterator it = ranges.iterator(); it.hasNext(); ) {
+            DelimitedTextRange r = (DelimitedTextRange) it.next();
+            if ( ! r.isEmpty() ) {
+                rv.add ( r );
+            }
+        }
+        return rv;
+    }
+
+    private static String padLeft ( int n, int width ) {
+        return padLeft ( Integer.toString ( n ), width );
+    }
+
+    private static String padLeft ( String s, int width ) {
+        StringBuffer sb = new StringBuffer();
+        for ( int i = s.length(); i < width; i++ ) {
+            sb.append(' ');
+        }
+        sb.append ( s );
+        return sb.toString();
+    }
+
+    /* not used yet
+    private static String padRight ( int n, int width ) {
+        return padRight ( Integer.toString ( n ), width );
+    }
+    */
+
+    private static String padRight ( String s, int width ) {
+        StringBuffer sb = new StringBuffer ( s );
+        for ( int i = sb.length(); i < width; i++ ) {
+            sb.append(' ');
+        }
+        return sb.toString();
+    }
+
+    private static class DelimitedTextRange {
+        private FONode fn;                              // node that generates this text range
+        private StringBuffer buffer;                    // flattened character sequence of generating FO nodes
+        private List intervals;                         // list of intervals over buffer of generating FO nodes
+        DelimitedTextRange ( FONode fn ) {
+            this.fn = fn;
+            this.buffer = new StringBuffer();
+            this.intervals = new Vector();
+        }
+        FONode getNode() {
+            return fn;
+        }
+        void append ( CharIterator it, FONode fn ) {
+            if ( it != null ) {
+                int s = buffer.length();
+                int e = s;
+                while ( it.hasNext() ) {
+                    char c = it.nextChar();
+                    buffer.append ( c );
+                    e++;
+                }
+                intervals.add ( new TextInterval ( fn, s, e ) );
+            }
+        }
+        void append ( char c, FONode fn ) {
+            if ( c != 0 ) {
+                int s = buffer.length();
+                int e = s + 1;
+                buffer.append ( c );
+                intervals.add ( new TextInterval ( fn, s, e ) );
+            }
+        }
+        boolean isEmpty() {
+            return buffer.length() == 0;
+        }
+        void resolve() {
+            WritingModeTraitsGetter tg;
+            if ( ( tg = getWritingModeTraitsGetter ( getNode() ) ) != null ) {
+                resolve ( tg.getInlineProgressionDirection() );
+            }
+        }
+        public String toString() {
+            StringBuffer sb = new StringBuffer ( "DR: " + fn.getLocalName() + "{ <" + CharUtilities.toNCRefs ( buffer.toString() ) + ">" );
+            sb.append ( ", intervals <" );
+            boolean first = true;
+            for ( Iterator it = intervals.iterator(); it.hasNext(); ) {
+                TextInterval ti = (TextInterval) it.next();
+                if ( first ) {
+                    first = false;
+                } else {
+                    sb.append(',');
+                }
+                sb.append ( ti.toString() );
+            }
+            sb.append(">}");
+            return sb.toString();
+        }
+        private void resolve ( Direction paragraphEmbeddingLevel ) {
+            int [] levels;
+            if ( ( levels = UnicodeBidiAlgorithm.resolveLevels ( buffer, paragraphEmbeddingLevel ) ) != null ) {
+                assignLevels ( levels );
+                assignTextLevels();
+                assignBlockLevel(paragraphEmbeddingLevel);
+            }
+        }
+        /**
+         * <p>Assign resolved levels to all text intervals of this delimited text range.</p>
+         * <p>Has a possible side effect of replacing the intervals array with a new array
+         * containing new text intervals, such that each text interval is associated with
+         * a single level run.</p>
+         * @param levels array of levels each corresponding to each index of the delimited
+         * text range
+         */
+        private void assignLevels ( int[] levels ) {
+            Vector intervalsNew = new Vector ( intervals.size() );
+            for ( Iterator it = intervals.iterator(); it.hasNext(); ) {
+                TextInterval ti = (TextInterval) it.next();
+                intervalsNew.addAll ( assignLevels ( ti, levels ) );
+            }
+            if ( ! intervalsNew.equals ( intervals ) ) {
+                intervals = intervalsNew;
+            }
+        }
+        /**
+         * <p>Assign resolved levels to a specified text interval over this delimited text
+         * range.</p>
+         * <p>Returns a list of text intervals containing either (1) the single, input text
+         * interval or (2) two or more new text intervals obtained from sub-dividing the input
+         * text range into level runs, i.e., runs of text assigned to a single level.</p>
+         * @param ti a text interval to which levels are to be assigned
+         * @param levels array of levels each corresponding to each index of the delimited
+         * text range
+         * @returns a list of text intervals as described above
+         */
+        private List assignLevels ( TextInterval ti, int[] levels ) {
+            Vector tiv = new Vector();
+            FONode fn = ti.getNode();
+            int fnStart = ti.getStart();                                     // start of node's text in delimited text range
+            for ( int i = fnStart, n = ti.getEnd(); i < n; ) {
+                int s = i;                                              // inclusive start index of interval in delimited text range
+                int e = s;                                              // exclusive end index of interval in delimited text range
+                int l = levels [ s ];                                   // current run level
+                while ( e < n ) {                                       // skip to end of run level or end of interval
+                    if ( levels [ e ] != l ) {
+                        break;
+                    } else {
+                        e++;
+                    }
+                }
+                if ( ( ti.getStart() == s ) && ( ti.getEnd() == e ) ) {
+                    ti.setLevel ( l );                                       // reuse interval, assigning it single level
+                } else {
+                    ti = new TextInterval ( fn, fnStart, s, e, l );     // subdivide interval
+                }
+                if (log.isDebugEnabled()) {
+                    log.debug ( "AL(" + l + "): " + ti );
+                }
+                tiv.add ( ti );
+                i = e;
+            }
+            return tiv;
+        }
+        /**
+         * <p>Assign resolved levels for each interval to source #PCDATA in the associated FOText.</p>
+         */
+        private void assignTextLevels() {
+            for ( Iterator it = intervals.iterator(); it.hasNext(); ) {
+                TextInterval ti = (TextInterval) it.next();
+                ti.assignTextLevels();
+            }
+        }
+        private void assignBlockLevel ( Direction paragraphEmbeddingLevel ) {
+            int defaultLevel = ( paragraphEmbeddingLevel == Direction.RL ) ? 1 : 0;
+            for ( Iterator it = intervals.iterator(); it.hasNext(); ) {
+                TextInterval ti = (TextInterval) it.next();
+                assignBlockLevel ( ti.getNode(), defaultLevel );
+            }
+        }
+        private void assignBlockLevel ( FONode node, int defaultLevel ) {
+            for ( FONode fn = node; fn != null; fn = fn.getParent() ) {
+                if ( fn instanceof Block ) {
+                    Block bn = (Block) fn;
+                    if ( bn.getBidiLevel() < 0 ) {
+                        bn.setBidiLevel ( defaultLevel );
+                    }
+                    break;
+                }
+            }
+        }
+        private WritingModeTraitsGetter getWritingModeTraitsGetter ( FONode fn ) {
+            for ( FONode n = fn; n != null; n = n.getParent() ) {
+                if ( n instanceof WritingModeTraitsGetter ) {
+                    return (WritingModeTraitsGetter) n;
+                }
+            }
+            return null;
+        }
+    }
+
+    private static class TextInterval {
+        private FONode fn;              // associated node
+        private int textStart;          // starting index within delimited text range of associated node's text
+        private int start;              // starting index within delimited text range
+        private int end;                // ending index within delimited text range
+        private int level;              // resolved level or default (-1)
+        TextInterval ( FONode fn, int start, int end ) {
+            this ( fn, start, start, end, -1 );
+        }
+        TextInterval ( FONode fn, int textStart, int start, int end, int level ) {
+            this.fn = fn;
+            this.textStart = textStart;
+            this.start = start;
+            this.end = end;
+            this.level = level;
+        }
+        FONode getNode() {
+            return fn;
+        }
+        int getTextStart() {
+            return textStart;
+        }
+        int getStart() {
+            return start;
+        }
+        int getEnd() {
+            return end;
+        }
+        int getLevel() {
+            return level;
+        }
+        void setLevel ( int level ) {
+            this.level = level;
+        }
+        public int length() {
+            return end - start;
+        }
+        public String getText() {
+            if ( fn instanceof FOText ) {
+                return new String ( ( (FOText) fn ) .getCharArray() );
+            } else if ( fn instanceof Character ) {
+                return new String ( new char[] {( (Character) fn ) .getCharacter()} );
+            } else {
+                return null;
+            }
+        }
+        public void assignTextLevels() {
+            if ( fn instanceof FOText ) {
+                ( (FOText) fn ) .setBidiLevel ( level, start - textStart, end - textStart );
+            } else if ( fn instanceof Character ) {
+                ( (Character) fn ) .setBidiLevel ( level );
+            }
+        }
+        public boolean equals ( Object o ) {
+            if ( o instanceof TextInterval ) {
+                TextInterval ti = (TextInterval) o;
+                if ( ti.getNode() != fn ) {
+                    return false;
+                } else if ( ti.getStart() != start ) {
+                    return false;
+                } else if ( ti.getEnd() != end ) {
+                    return false;
+                } else {
+                    return true;
+                }
+            } else {
+                return false;
+            }
+        }
+        public int hashCode() {
+            int l = ( fn != null ) ? fn.hashCode() : 0;
+            l = ( l ^ start ) + ( l << 19 );
+            l = ( l ^ end )   + ( l << 11 );
+            return l;
+        }
+        public String toString() {
+            StringBuffer sb = new StringBuffer();
+            char c;
+            if ( fn instanceof FOText ) {
+                c = 'T';
+            } else if ( fn instanceof Character ) {
+                c = 'C';
+            } else if ( fn instanceof BidiOverride ) {
+                c = 'B';
+            } else {
+                c = '?';
+            }
+            sb.append ( c );
+            sb.append ( "[" + start + "," + end + "][" + textStart + "](" + level + ")" );
+            return sb.toString();
+        }
+    }
+
+    /**
+     * The <code>InlineRun</code> class is a utility class used to capture a sequence of
+     * reordering levels associated with an inline area.
+     */
+    private static class InlineRun {
+        private InlineArea inline;
+        private int[] levels;
+        private int minLevel;
+        private int maxLevel;
+        private int reversals;
+        InlineRun ( InlineArea inline, int[] levels ) {
+            this.inline = inline;
+            this.levels = levels;
+            setMinMax ( levels );
+        }
+        private InlineRun ( InlineArea inline, int level, int count ) {
+            this ( inline, makeLevels ( level, count ) );
+        }
+        InlineArea getInline() {
+            return inline;
+        }
+        int getMinLevel() {
+            return minLevel;
+        }
+        private void setMinMax ( int[] levels ) {
+            int mn = Integer.MAX_VALUE;
+            int mx = Integer.MIN_VALUE;
+            if ( ( levels != null ) && ( levels.length > 0 ) ) {
+                for ( int i = 0, n = levels.length; i < n; i++ ) {
+                    int l = levels [ i ];
+                    if ( l < mn ) {
+                        mn = l;
+                    }
+                    if ( l > mx ) {
+                        mx = l;
+                    }
+                }
+            } else {
+                mn = mx = -1;
+            }
+            this.minLevel = mn;
+            this.maxLevel = mx;
+        }
+        public boolean isHomogenous() {
+            return minLevel == maxLevel;
+        }
+        public List split() {
+            List runs = new Vector();
+            for ( int i = 0, n = levels.length; i < n; ) {
+                int l = levels [ i ];
+                int s = i;
+                int e = s;
+                while ( e < n ) {
+                    if ( levels [ e ] != l ) {
+                        break;
+                    } else {
+                        e++;
+                    }
+                }
+                if ( s < e ) {
+                    runs.add ( new InlineRun ( inline, l, e - s ) );
+                }
+                i = e;
+            }
+            return runs;
+        }
+        public void updateMinMax ( int[] mm ) {
+            if ( minLevel < mm[0] ) {
+                mm[0] = minLevel;
+            }
+            if ( maxLevel > mm[1] ) {
+                mm[1] = maxLevel;
+            }
+        }
+        public void reverse() {
+            reversals++;
+        }
+        public void maybeReverseWord ( boolean mirror ) {
+            if ( ( inline instanceof WordArea ) && ( ( reversals & 1 ) != 0 ) ) {
+                ( (WordArea) inline ) .reverse ( mirror );
+            }
+        }
+        public boolean equals ( Object o ) {
+            if ( o instanceof InlineRun ) {
+                InlineRun ir = (InlineRun) o;
+                if ( ir.inline != inline ) {
+                    return false;
+                } else if ( ir.minLevel != minLevel ) {
+                    return false;
+                } else if ( ir.maxLevel != maxLevel ) {
+                    return false;
+                } else if ( ( ir.levels != null ) && ( levels != null ) ) {
+                    if ( ir.levels.length != levels.length ) {
+                        return false;
+                    } else {
+                        for ( int i = 0, n = levels.length; i < n; i++ ) {
+                            if ( ir.levels[i] != levels[i] ) {
+                                return false;
+                            }
+                        }
+                        return true;
+                    }
+                } else if ( ( ir.levels == null ) && ( levels == null ) ) {
+                    return true;
+                } else {
+                    return false;
+                }
+            } else {
+                return false;
+            }
+        }
+        public int hashCode() {
+            int l = ( inline != null ) ? inline.hashCode() : 0;
+            l = ( l ^ minLevel ) + ( l << 19 );
+            l = ( l ^ maxLevel )   + ( l << 11 );
+            return l;
+        }
+        public String toString() {
+            StringBuffer sb = new StringBuffer( "RR: { type = \'" );
+            char c;
+            String content = null;
+            if ( inline instanceof WordArea ) {
+                c = 'W';
+                content = ( (WordArea) inline ) .getWord();
+            } else if ( inline instanceof SpaceArea ) {
+                c = 'S';
+                content = ( (SpaceArea) inline ) .getSpace();
+            } else if ( inline instanceof InlineParent ) {
+                c = 'I';
+            } else if ( inline instanceof InlineBlockParent ) {
+                c = 'B';
+            } else if ( inline instanceof Viewport ) {
+                c = 'V';
+            } else if ( inline instanceof Leader ) {
+                c = 'L';
+            } else if ( inline instanceof Anchor ) {
+                c = 'A';
+            } else if ( inline instanceof Space ) {
+                c = 'G'; // 'G' => glue
+            } else {
+                c = '?';
+            }
+            sb.append ( c );
+            sb.append ( "\', levels = \'" );
+            sb.append ( generateLevels ( levels ) );
+            sb.append ( "\', min = " );
+            sb.append ( minLevel );
+            sb.append ( ", max = " );
+            sb.append ( maxLevel );
+            sb.append ( ", reversals = " );
+            sb.append ( reversals );
+            sb.append ( ", content = <" );
+            sb.append ( CharUtilities.toNCRefs ( content ) );
+            sb.append ( "> }" );
+            return sb.toString();
+        }
+        private String generateLevels ( int[] levels ) {
+            StringBuffer lb = new StringBuffer();
+            int maxLevel = -1;
+            int numLevels = levels.length;
+            for ( int i = 0; i < numLevels; i++ ) {
+                int l = levels [ i ];
+                if ( l > maxLevel ) {
+                    maxLevel = l;
+                }
+            }
+            if ( maxLevel < 0 ) {
+                // leave level buffer empty
+            } else if ( maxLevel < 10 ) {
+                // use string of decimal digits
+                for ( int i = 0; i < numLevels; i++ ) {
+                    lb.append ( (char) ( '0' + levels [ i ] ) );
+                }
+            } else {
+                // use comma separated list
+                boolean first = true;
+                for ( int i = 0; i < numLevels; i++ ) {
+                    if ( first ) {
+                        first = false;
+                    } else {
+                        lb.append(',');
+                    }
+                    lb.append ( levels [ i ] );
+                }
+            }
+            return lb.toString();
+        }
+        private static int[] makeLevels ( int level, int count ) {
+            int[] levels = new int [ count ];
+            Arrays.fill ( levels, level );
+            return levels;
+        }
+    }
+
+    /**
+     * The <code>UnicodeBidiAlgorithm</code> class implements functionality prescribed by
+     * the Unicode Bidirectional Algorithm, Unicode Standard Annex #9.
+     */
+    private static final class UnicodeBidiAlgorithm implements BidiConstants {
+
+        private UnicodeBidiAlgorithm() {
+        }
+
+        /**
+         * Resolve the directionality levels of each character in a character seqeunce.
+         * If some character is encoded in the character sequence as a Unicode Surrogate Pair,
+         * then the directionality level of each of the two members of the  pair will be identical.
+         * @returns null if bidirectional processing is not required; otherwise, returns an array
+         * of integers, where each integer corresponds to exactly one UTF-16
+         * encoding element present in the input character sequence, and where each integer denotes
+         * the directionality level of the corresponding encoding element
+         * @param cs input character sequence representing a UTF-16 encoded string
+         * @param defaultLevel the default paragraph level, which must be zero (LR) or one (RL)
+         */
+        public static int[] resolveLevels ( CharSequence cs, Direction defaultLevel ) {
+            int[] chars = new int [ cs.length() ];
+            if ( convertToScalar ( cs, chars ) || ( defaultLevel == Direction.RL ) ) {
+                return resolveLevels ( chars, ( defaultLevel == Direction.RL ) ? 1 : 0, new int [ chars.length ] );
+            } else {
+                return null;
+            }
+        }
+
+        private static int[] resolveLevels ( int[] chars, int defaultLevel, int[] levels ) {
+            return resolveLevels ( chars, getClasses ( chars ), defaultLevel, levels );
+        }
+
+        private static int[] resolveLevels ( int[] chars, int[] classes, int defaultLevel, int[] levels ) {
+            resolveExplicit ( classes, defaultLevel, levels );
+            resolveRuns ( classes, defaultLevel, levels );
+            dump ( "RL: CC(" + chars.length + ")", chars, classes, defaultLevel, levels );
+            return levels;
+        }
+
+        private static void resolveExplicit ( int[] classes, int defaultLevel, int[] levels ) {
+            int[] es = new int [ MAX_LEVELS ];          /* embeddings stack */
+            int ei = 0;                                 /* embeddings stack index */
+            int ec = defaultLevel;                      /* current embedding level */
+            for ( int i = 0, n = classes.length; i < n; i++ ) {
+                int bc = classes [ i ];                 /* bidi class of current char */
+                int el;                                 /* embedding level to assign to current char */
+                switch ( bc ) {
+                case LRE:                               // start left-to-right embedding
+                case RLE:                               // start right-to-left embedding
+                case LRO:                               // start left-to-right override
+                case RLO:                               // start right-to-left override
+                    {
+                        int en;                         /* new embedding level */
+                        if ( ( bc == RLE ) || ( bc == RLO ) ) {
+                            en = ( ec + 1 ) | 1;
+                        } else {
+                            en = ( ec + 2 ) & ~1;
+                        }
+                        if ( en < ( MAX_LEVELS + 1 ) ) {
+                            es [ ei++ ] = ec;
+                            if ( ( bc == LRO ) || ( bc == RLO ) ) {
+                                ec = en | OVERRIDE;
+                            } else {
+                                ec = en & ~OVERRIDE;
+                            }
+                        } else {
+                            throw new IllegalStateException ( "maximum bidi levels exceeded" );
+                        }
+                        el = ec;
+                        break;
+                    }
+                case PDF:                               // pop directional formatting
+                    {
+                        el = ec;
+                        if ( ei > 0 ) {
+                            ec = es [ --ei ];
+                        } else {
+                            throw new IllegalStateException ( "isolated pop directional formatting character" );
+                        }
+                        break;
+                    }
+                case B:                                 // paragraph separator
+                    {
+                        el = ec = defaultLevel;
+                        ei = 0;
+                        break;
+                    }
+                default:
+                    {
+                        el = ec;
+                        break;
+                    }
+                }
+                switch ( bc ) {
+                case LRE: case RLE: case LRO: case RLO: case PDF:
+                    classes [ i ] = BN;
+                    break;
+                default:
+                    if ( ( el & OVERRIDE ) != 0 ) {
+                        classes [ i ] = ( ( el & 1 ) != 0 ) ? R : L;
+                    }
+                    break;
+                }
+                levels [ i ] = el & ~OVERRIDE;
+            }
+        }
+
+        private static void resolveRuns ( int[] classes, int defaultLevel, int[] levels ) {
+            if ( levels.length != classes.length ) {
+                throw new IllegalArgumentException ( "levels sequence length must match classes sequence length" );
+            }
+            for ( int i = 0, n = levels.length; i < n; ) {
+                int s = i;
+                int e = s;
+                int l = levels [ s ];
+                while ( e < n ) {
+                    if ( levels [ e ] != l ) {
+                        break;
+                    } else {
+                        e++;
+                    }
+                }
+                resolveRun ( classes, defaultLevel, levels, s, e, l );
+                i = e;
+            }
+        }
+
+        private static void resolveRun ( int[] classes, int defaultLevel, int[] levels, int start, int end, int level ) {
+
+            // determine start of run direction
+            int ls;
+            if ( start == 0 ) {
+                ls = max ( defaultLevel, level );
+            } else {
+                ls = max ( levels [ start - 1 ], level );
+            }
+            int sor = ( ( ls & 1 ) != 0 ) ? R : L;
+
+            // determine end of run direction
+            int le;
+            if ( end == levels.length ) {
+                le = max ( level, defaultLevel );
+            } else {
+                le = max ( level, levels [ end + 1 ] );
+            }
+            int eor = ( ( le & 1 ) != 0 ) ? R : L;
+
+            if (log.isDebugEnabled()) {
+                log.debug ( "BR[" + padLeft ( start, 3 ) + "," + padLeft ( end, 3 ) + "] :" + padLeft ( level, 2 ) + ": SOR(" + getClassName(sor) + "), EOR(" + getClassName(eor) + ")" );
+            }
+
+            resolveWeak ( classes, defaultLevel, levels, start, end, level, sor, eor );
+            resolveNeutrals ( classes, defaultLevel, levels, start, end, level, sor, eor );
+            resolveImplicit ( classes, defaultLevel, levels, start, end, level, sor, eor );
+
+        }
+
+        private static void resolveWeak ( int[] classes, int defaultLevel, int[] levels, int start, int end, int level, int sor, int eor ) {
+
+            // W1 - X BN* NSM -> X BN* X
+            for ( int i = start, n = end, bcPrev = sor; i < n; i++ ) {
+                int bc = classes [ i ];
+                if ( bc == NSM ) {
+                    classes [ i ] = bcPrev;
+                } else if ( bc != BN ) {
+                    bcPrev = bc;
+                }
+            }
+
+            // W2 - AL ... EN -> AL ... AN
+            for ( int i = start, n = end, bcPrev = sor; i < n; i++ ) {
+                int bc = classes [ i ];
+                if ( bc == EN ) {
+                    if ( bcPrev == AL ) {
+                        classes [ i ] = AN;
+                    }
+                } else if ( isStrong ( bc ) ) {
+                    bcPrev = bc;
+                }
+            }
+
+            // W3 - AL -> R
+            for ( int i = start, n = end; i < n; i++ ) {
+                int bc = classes [ i ];
+                if ( bc == AL ) {
+                    classes [ i ] = R;
+                }
+            }
+
+            // W4 - EN BN* ES BN* EN -> EN BN* EN BN* EN; XN BN* CS BN* XN -> XN BN* XN BN* XN
+            for ( int i = start, n = end, bcPrev = sor; i < n; i++ ) {
+                int bc = classes [ i ];
+                if ( bc == ES ) {
+                    int bcNext = eor;
+                    for ( int j = i + 1; j < n; j++ ) {
+                        if ( ( bc = classes [ j ] ) != BN ) {
+                            bcNext = bc;
+                            break;
+                        }
+                    }
+                    if ( ( bcPrev == EN ) && ( bcNext == EN ) ) {
+                        classes [ i ] = EN;
+                    }
+                } else if ( bc == CS ) {
+                    int bcNext = eor;
+                    for ( int j = i + 1; j < n; j++ ) {
+                        if ( ( bc = classes [ j ] ) != BN ) {
+                            bcNext = bc;
+                            break;
+                        }
+                    }
+                    if ( ( bcPrev == EN ) && ( bcNext == EN ) ) {
+                        classes [ i ] = EN;
+                    } else if ( ( bcPrev == AN ) && ( bcNext == AN ) ) {
+                        classes [ i ] = AN;
+                    }
+                } else if ( bc != BN ) {
+                    bcPrev = bc;
+                }
+            }
+
+            // W5 - EN (ET|BN)* -> EN (EN|BN)*; (ET|BN)* EN -> (EN|BN)* EN
+            for ( int i = start, n = end, bcPrev = sor; i < n; i++ ) {
+                int bc = classes [ i ];
+                if ( bc == ET ) {
+                    int bcNext = eor;
+                    for ( int j = i + 1; j < n; j++ ) {
+                        bc = classes [ j ];
+                        if ( ( bc != BN ) && ( bc != ET ) ) {
+                            bcNext = bc;
+                            break;
+                        }
+                    }
+                    if ( ( bcPrev == EN ) || ( bcNext == EN ) ) {
+                        classes [ i ] = EN;
+                    }
+                } else if ( ( bc != BN ) && ( bc != ET ) ) {
+                    bcPrev = bc;
+                }
+            }
+
+            // W6 - BN* (ET|ES|CS) BN* -> ON* ON ON*
+            for ( int i = start, n = end; i < n; i++ ) {
+                int bc = classes [ i ];
+                if ( ( bc == ET ) || ( bc == ES ) || ( bc == CS ) ) {
+                    classes [ i ] = ON;
+                    resolveAdjacentBoundaryNeutrals ( classes, start, end, i, ON );
+                }
+            }
+
+            // W7 - L ... EN -> L ... L
+            for ( int i = start, n = end, bcPrev = sor; i < n; i++ ) {
+                int bc = classes [ i ];
+                if ( bc == EN ) {
+                    if ( bcPrev == L ) {
+                        classes [ i ] = L;
+                    }
+                } else if ( ( bc == L ) || ( bc == R ) ) {
+                    bcPrev = bc;
+                }
+            }
+
+        }
+
+        private static void resolveNeutrals ( int[] classes, int defaultLevel, int[] levels, int start, int end, int level, int sor, int eor ) {
+
+            // N1 - (L|R) N+ (L|R) -> L L+ L | R R+ R
+            for ( int i = start, n = end, bcPrev = sor; i < n; i++ ) {
+                int bc = classes [ i ];
+                if ( isNeutral ( bc ) ) {
+                    int bcNext = eor;
+                    for ( int j = i + 1; j < n; j++ ) {
+                        bc = classes [ j ];
+                        if ( ( bc == L ) || ( bc == R ) ) {
+                            bcNext = bc;
+                            break;
+                        }
+                    }
+                    if ( bcPrev == bcNext ) {
+                        classes [ i ] = bcPrev;
+                        resolveAdjacentBoundaryNeutrals ( classes, start, end, i, bcPrev );
+                    }
+                } else if ( ( bc == L ) || ( bc == R ) ) {
+                    bcPrev = bc;
+                }
+            }
+
+            // N2 - N -> default level
+            for ( int i = start, n = end, bcDefault = ( ( defaultLevel & 1 ) != 0 ) ? R : L; i < n; i++ ) {
+                int bc = classes [ i ];
+                if ( isNeutral ( bc ) ) {
+                    classes [ i ] = bcDefault;
+                    resolveAdjacentBoundaryNeutrals ( classes, start, end, i, bcDefault );
+                }
+            }
+
+        }
+
+        private static void resolveAdjacentBoundaryNeutrals ( int[] classes, int start, int end, int index, int bcNew ) {
+            if ( ( index < start ) || ( index >= end ) ) {
+                throw new IllegalArgumentException();
+            } else {
+                for ( int i = index - 1; i >= start; i-- ) {
+                    int bc = classes [ i ];
+                    if ( bc == BN ) {
+                        classes [ i ] = bcNew;
+                    } else {
+                        break;
+                    }
+                }
+                for ( int i = index + 1; i < end; i++ ) {
+                    int bc = classes [ i ];
+                    if ( bc == BN ) {
+                        classes [ i ] = bcNew;
+                    } else {
+                        break;
+                    }
+                }
+            }
+        }
+
+        private static void resolveImplicit ( int[] classes, int defaultLevel, int[] levels, int start, int end, int level, int sor, int eor ) {
+
+            for ( int i = start, n = end; i < n; i++ ) {
+                int bc = classes [ i ];                 // bidi class
+                int el = levels [ i ];                  // embedding level
+                int ed = 0;                             // embedding level delta
+                if ( ( el & 1 ) == 0 ) {                // even
+                    if ( bc == R ) {
+                        ed = 1;
+                    } else if ( bc == AN ) {
+                        ed = 2;
+                    } else if ( bc == EN ) {
+                        ed = 2;
+                    }
+                } else {                                // odd
+                    if ( bc == L ) {
+                        ed = 1;
+                    } else if ( bc == EN ) {
+                        ed = 1;
+                    } else if ( bc == AN ) {
+                        ed = 1;
+                    }
+                }
+                levels [ i ] = el + ed;
+            }
+
+        }
+
+        private static boolean isStrong ( int bc ) {
+            switch ( bc ) {
+            case L:
+            case R:
+            case AL:
+                return true;
+            default:
+                return false;
+            }
+        }
+
+        private static boolean isNeutral ( int bc ) {
+            switch ( bc ) {
+            case WS:
+            case ON:
+            case S:
+            case B:
+                return true;
+            default:
+                return false;
+            }
+        }
+
+        private static int max ( int x, int y ) {
+            if ( x > y ) {
+                return x;
+            } else {
+                return y;
+            }
+        }
+
+        private static int[] getClasses ( int[] chars ) {
+            int[] classes = new int [ chars.length ];
+            int bc;
+            for ( int i = 0, n = chars.length; i < n; i++ ) {
+                int ch = chars [ i ];
+                if ( ch >= 0 ) {
+                    bc = BidiClassUtils.getBidiClass ( chars [ i ] );
+                } else {
+                    bc = SURROGATE;
+                }
+                classes [ i ] = bc;
+            }
+            return classes;
+        }
+
+        /**
+         * Convert character sequence (a UTF-16 encoded string) to an array of unicode scalar values
+         * expressed as integers. If a valid UTF-16 surrogate pair is encountered, it is converted to
+         * two integers, the first being the equivalent unicode scalar  value, and the second being
+         * negative one (-1). This special mechanism is used to track the use of surrogate pairs while
+         * working with unicode scalar values, and permits maintaining indices that apply both to the
+         * input UTF-16 and out scalar value sequences.
+         * @returns a boolean indicating that content is present that triggers bidirectional processing
+         * @param cs a UTF-16 encoded character sequence
+         * @param chars an integer array to accept the converted scalar values, where the length of the
+         * array must be the same as the length of the input character sequence
+         * @throws IllegalArgumentException if the input sequence is not a valid UTF-16 string, e.g.,
+         * if it contains an isolated UTF-16 surrogate
+         */
+        private static boolean convertToScalar ( CharSequence cs, int[] chars ) throws IllegalArgumentException {
+            boolean triggered = false;
+            if ( chars.length != cs.length() ) {
+                throw new IllegalArgumentException ( "characters array length must match input sequence length" );
+            }
+            for ( int i = 0, n = chars.length; i < n; ) {
+                int chIn = cs.charAt ( i );
+                int chOut;
+                if ( chIn < 0xD800 ) {
+                    chOut = chIn;
+                } else if ( chIn < 0xDC00 ) {
+                    int chHi = chIn;
+                    int chLo;
+                    if ( ( i + 1 ) < n ) {
+                        chLo = cs.charAt ( i + 1 );
+                        if ( ( chLo >= 0xDC00 ) && ( chLo <= 0xDFFF ) ) {
+                            chOut = convertToScalar ( chHi, chLo );
+                        } else {
+                            throw new IllegalArgumentException ( "isolated high surrogate" );
+                        }
+                    } else {
+                        throw new IllegalArgumentException ( "truncated surrogate pair" );
+                    }
+                } else if ( chIn < 0xE000 ) {
+                    throw new IllegalArgumentException ( "isolated low surrogate" );
+                } else {
+                    chOut = chIn;
+                }
+                if ( ! triggered && triggersBidi ( chOut ) ) {
+                    triggered = true;
+                }
+                if ( ( chOut & 0xFF0000 ) == 0 ) {
+                    chars [ i++ ] = chOut;
+                } else {
+                    chars [ i++ ] = chOut;
+                    chars [ i++ ] = -1;
+                }
+            }
+            return triggered;
+        }
+
+        /**
+         * Convert UTF-16 surrogate pair to unicode scalar valuee.
+         * @returns a unicode scalar value
+         * @param chHi high (most significant or first) surrogate
+         * @param chLo low (least significant or second) surrogate
+         * @throws IllegalArgumentException if one of the input surrogates is not valid
+         */
+        private static int convertToScalar ( int chHi, int chLo ) {
+            if ( ( chHi < 0xD800 ) || ( chHi > 0xDBFF ) ) {
+                throw new IllegalArgumentException ( "bad high surrogate" );
+            } else if ( ( chLo < 0xDC00 ) || ( chLo > 0xDFFF ) ) {
+                throw new IllegalArgumentException ( "bad low surrogate" );
+            } else {
+                return ( ( ( chHi & 0x03FF ) << 10 ) | ( chLo & 0x03FF ) ) + 0x10000;
+            }
+        }
+
+        /**
+         * Determine of character CH triggers bidirectional processing. Bidirectional
+         * processing is deemed triggerable if CH is a strong right-to-left character,
+         * an arabic letter or number, or is a right-to-left embedding or override
+         * character.
+         * @returns true if character triggers bidirectional processing
+         * @param ch a unicode scalar value
+         */
+        private static boolean triggersBidi ( int ch ) {
+            switch ( BidiClassUtils.getBidiClass ( ch ) ) {
+            case R:
+            case AL:
+            case AN:
+            case RLE:
+            case RLO:
+                return true;
+            default:
+                return false;
+            }
+        }
+
+        private static void dump ( String header, int[] chars, int[] classes, int defaultLevel, int[] levels ) {
+            log.debug ( header );
+            log.debug ( "BD: default level(" + defaultLevel + ")" );
+            StringBuffer sb = new StringBuffer();
+            for ( int i = 0, n = chars.length; i < n; i++ ) {
+                int ch = chars [ i ];
+                sb.setLength(0);
+                if ( ( ch > 0x20 ) && ( ch < 0x7F ) ) {
+                    sb.append ( (char) ch );
+                } else {
+                    sb.append ( CharUtilities.charToNCRef ( ch ) );
+                }
+                for ( int k = sb.length(); k < 12; k++ ) {
+                    sb.append ( ' ' );
+                }
+                sb.append ( ": " + padRight ( getClassName ( classes[i] ), 4 ) + " " + levels[i] );
+                log.debug ( sb );
+            }
+        }
+
+        private static String getClassName ( int bc ) {
+            switch ( bc ) {
+            case L:                                     // left-to-right
+                return "L";
+            case LRE:                                   // left-to-right embedding
+                return "LRE";
+            case LRO:                                   // left-to-right override
+                return "LRO";
+            case R:                                     // right-to-left 
+                return "R";
+            case AL:                                    // right-to-left arabic
+                return "AL";
+            case RLE:                                   // right-to-left embedding
+                return "RLE";
+            case RLO:                                   // right-to-left override
+                return "RLO";
+            case PDF:                                   // pop directional formatting
+                return "PDF";
+            case EN:                                    // european number
+                return "EN";
+            case ES:                                    // european number separator
+                return "ES";
+            case ET:                                    // european number terminator
+                return "ET";
+            case AN:                                    // arabic number
+                return "AN";
+            case CS:                                    // common number separator
+                return "CS";
+            case NSM:                                   // non-spacing mark
+                return "NSM";
+            case BN:                                    // boundary neutral
+                return "BN";
+            case B:                                     // paragraph separator
+                return "B";
+            case S:                                     // segment separator
+                return "S";
+            case WS:                                    // whitespace
+                return "WS";
+            case ON:                                    // other neutrals
+                return "ON";
+            case SURROGATE:                             // placeholder for low surrogate
+                return "SUR";
+            default:
+                return "?";
+            }
+        }
+
+    }
+
+}
Index: src/java/org/apache/fop/text/bidi/BidiClassUtils.java
===================================================================
--- src/java/org/apache/fop/text/bidi/BidiClassUtils.java	(revision 0)
+++ src/java/org/apache/fop/text/bidi/BidiClassUtils.java	(revision 0)
@@ -0,0 +1,264 @@
+/*
+ * 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.text.bidi;
+
+import java.util.Arrays;
+import org.apache.fop.util.BidiConstants;
+
+// CSOFF: WhitespaceAfterCheck
+// CSOFF: LineLengthCheck
+
+/*
+ * !!! THIS IS A GENERATED FILE !!!
+ * If updates to the source are needed, then:
+ * - apply the necessary modifications to
+ *   'src/codegen/unicode/java/org/apache/fop/text/bidi/GenerateBidiClassUtils.java'
+ * - run 'ant codegen-unicode', which will generate a new BidiClassUtils.java
+ *   in 'src/java/org/apache/fop/text/bidi'
+ * - commit BOTH changed files
+ */
+
+/** Bidirectional class utilities. */
+public final class BidiClassUtils {
+
+private BidiClassUtils() {
+}
+
+private static byte[] bcL1 = {
+15,15,15,15,15,15,15,15,15,17,16,17,18,16,15,15,15,15,15,15,15,15,15,15,15,15,15,15,16,16,16,17,18,19,19,11,11,11,19,19,19,
+19,19,10,13,10,13,13,9,9,9,9,9,9,9,9,9,9,13,19,19,19,19,19,19,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,19,19,19,
+19,19,19,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,19,19,19,19,15,15,15,15,15,15,16,15,15,15,15,15,15,15,15,15,15,
+15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,13,19,11,11,11,11,19,19,19,19,1,19,19,15,19,19,11,11,9,9,19,1,19,19,19,9,1,
+19,19,19,19,19,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,19,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+1,1,19,1,1,1,1,1,1,1,1
+};
+
+private static byte[] bcR1 = {
+4,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,
+14,14,14,14,14,4,14,4,14,14,4,14,14,4,14,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
+4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,12,12,12,12,5,5,19,19,5,11,11,5,13,5,19,19,14,14,14,14,14,14,14,14,14,14,14,5,5,5,5,5,5,5,5,
+5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,14,14,14,14,14,14,14,14,14,14,14,14,14,14,
+14,14,14,14,14,14,5,12,12,12,12,12,12,12,12,12,12,11,12,12,5,5,5,14,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
+5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
+5,5,5,5,5,5,5,5,5,5,5,5,5,14,14,14,14,14,14,14,12,14,14,14,14,14,14,14,5,5,14,14,19,14,14,14,14,5,5,9,9,9,9,9,9,9,9,9,9,5,
+5,5,5,5,5
+};
+
+private static int[] bcS1 = {
+256,443,444,448,452,660,661,688,697,699,706,710,720,722,736,741,748,749,750,751,768,880,884,885,886,890,891,894,900,902,903,
+904,908,910,931,1014,1015,1154,1155,1160,1162,1329,1369,1370,1377,1417,1418,1792,1806,1807,1808,1809,1810,1840,1867,1869,1958,
+1969,1970,1984,1994,2027,2036,2038,2039,2042,2043,2048,2070,2074,2075,2084,2085,2088,2089,2094,2096,2111,2304,2307,2308,2364,
+2365,2366,2369,2377,2381,2382,2384,2385,2392,2402,2404,2406,2416,2417,2418,2425,2433,2434,2437,2447,2451,2474,2482,2486,2492,
+2493,2494,2497,2503,2507,2509,2510,2519,2524,2527,2530,2534,2544,2546,2548,2554,2555,2561,2563,2565,2575,2579,2602,2610,2613,
+2616,2620,2622,2625,2631,2635,2641,2649,2654,2662,2672,2674,2677,2689,2691,2693,2703,2707,2730,2738,2741,2748,2749,2750,2753,
+2759,2761,2763,2765,2768,2784,2786,2790,2801,2817,2818,2821,2831,2835,2858,2866,2869,2876,2877,2878,2879,2880,2881,2887,2891,
+2893,2902,2903,2908,2911,2914,2918,2928,2929,2946,2947,2949,2958,2962,2969,2972,2974,2979,2984,2990,3006,3008,3009,3014,3018,
+3021,3024,3031,3046,3056,3059,3065,3066,3073,3077,3086,3090,3114,3125,3133,3134,3137,3142,3146,3157,3160,3168,3170,3174,3192,
+3199,3202,3205,3214,3218,3242,3253,3260,3261,3262,3263,3264,3270,3271,3274,3276,3285,3294,3296,3298,3302,3313,3330,3333,3342,
+3346,3370,3389,3390,3393,3398,3402,3405,3415,3424,3426,3430,3440,3449,3450,3458,3461,3482,3507,3517,3520,3530,3535,3538,3542,
+3544,3570,3572,3585,3633,3634,3636,3647,3648,3654,3655,3663,3664,3674,3713,3716,3719,3722,3725,3732,3737,3745,3749,3751,3754,
+3757,3761,3762,3764,3771,3773,3776,3782,3784,3792,3804,3840,3841,3844,3859,3864,3866,3872,3882,3892,3893,3894,3895,3896,3897,
+3898,3899,3900,3901,3902,3904,3913,3953,3967,3968,3973,3974,3976,3984,3993,4030,4038,4039,4046,4048,4053,4096,4139,4141,4145,
+4146,4152,4153,4155,4157,4159,4160,4170,4176,4182,4184,4186,4190,4193,4194,4197,4199,4206,4209,4213,4226,4227,4229,4231,4237,
+4238,4239,4240,4250,4253,4254,4256,4304,4347,4348,4352,4682,4688,4696,4698,4704,4746,4752,4786,4792,4800,4802,4808,4824,4882,
+4888,4959,4960,4961,4969,4992,5008,5024,5120,5121,5741,5743,5760,5761,5787,5788,5792,5867,5870,5888,5902,5906,5920,5938,5941,
+5952,5970,5984,5998,6002,6016,6068,6070,6071,6078,6086,6087,6089,6100,6103,6104,6107,6108,6109,6112,6128,6144,6150,6151,6155,
+6158,6160,6176,6211,6212,6272,6313,6314,6320,6400,6432,6435,6439,6441,6448,6450,6451,6457,6464,6468,6470,6480,6512,6528,6576,
+6593,6600,6608,6622,6624,6656,6679,6681,6686,6688,6741,6742,6743,6744,6752,6753,6754,6755,6757,6765,6771,6783,6784,6800,6816,
+6823,6824,6912,6916,6917,6964,6965,6966,6971,6972,6973,6978,6979,6981,6992,7002,7009,7019,7028,7040,7042,7043,7073,7074,7078,
+7080,7082,7086,7088,7168,7204,7212,7220,7222,7227,7232,7245,7248,7258,7288,7294,7376,7379,7380,7393,7394,7401,7405,7406,7410,
+7424,7468,7522,7544,7545,7579,7616,7677,7680,7960,7968,8008,8016,8025,8027,8029,8031,8064,8118,8125,8126,8127,8130,8134,8141,
+8144,8150,8157,8160,8173,8178,8182,8189,8192,8203,8206,8207,8208,8214,8216,8217,8218,8219,8221,8222,8223,8224,8232,8233,8234,
+8235,8236,8237,8238,8239,8240,8245,8249,8250,8251,8255,8257,8260,8261,8262,8263,8274,8275,8276,8277,8287,8288,8293,8298,8304,
+8305,8308,8314,8316,8317,8318,8319,8320,8330,8332,8333,8334,8336,8352,8400,8413,8417,8418,8421,8448,8450,8451,8455,8456,8458,
+8468,8469,8470,8473,8478,8484,8485,8486,8487,8488,8489,8490,8494,8495,8501,8505,8506,8508,8512,8517,8522,8523,8524,8526,8527,
+8528,8544,8579,8581,8585,8592,8597,8602,8604,8608,8609,8611,8612,8614,8615,8622,8623,8654,8656,8658,8659,8660,8661,8692,8722,
+8723,8724,8960,8968,8972,8992,8994,9001,9002,9003,9014,9083,9084,9085,9109,9110,9115,9140,9180,9186,9216,9280,9312,9352,9372,
+9450,9472,9655,9656,9665,9666,9720,9728,9839,9840,9900,9901,9935,9955,9960,9985,9990,9996,10025,10061,10063,10070,10081,10088,
+10089,10090,10091,10092,10093,10094,10095,10096,10097,10098,10099,10100,10101,10102,10132,10136,10161,10176,10181,10182,10183,
+10188,10192,10214,10215,10216,10217,10218,10219,10220,10221,10222,10223,10224,10240,10496,10627,10628,10629,10630,10631,10632,
+10633,10634,10635,10636,10637,10638,10639,10640,10641,10642,10643,10644,10645,10646,10647,10648,10649,10712,10713,10714,10715,
+10716,10748,10749,10750,11008,11056,11077,11079,11088,11264,11312,11360,11389,11390,11493,11499,11503,11513,11517,11518,11520,
+11568,11631,11648,11680,11688,11696,11704,11712,11720,11728,11736,11744,11776,11778,11779,11780,11781,11782,11785,11786,11787,
+11788,11789,11790,11799,11800,11802,11803,11804,11805,11806,11808,11809,11810,11811,11812,11813,11814,11815,11816,11817,11818,
+11823,11824,11904,11931,12032,12272,12288,12289,12292,12293,12294,12295,12296,12297,12298,12299,12300,12301,12302,12303,12304,
+12305,12306,12308,12309,12310,12311,12312,12313,12314,12315,12316,12317,12318,12320,12321,12330,12336,12337,12342,12344,12347,
+12348,12349,12350,12353,12441,12443,12445,12447,12448,12449,12539,12540,12543,12549,12593,12688,12690,12694,12704,12736,12784,
+12800,12829,12832,12842,12880,12881,12896,12924,12927,12928,12938,12977,12992,13004,13008,13056,13175,13179,13278,13280,13311,
+13312,19904,19968,40960,40981,40982,42128,42192,42232,42238,42240,42508,42509,42512,42528,42538,42560,42594,42606,42607,42608,
+42611,42620,42622,42623,42624,42656,42726,42736,42738,42752,42775,42784,42786,42864,42865,42888,42889,42891,43003,43010,43011,
+43014,43015,43019,43020,43043,43045,43047,43048,43056,43062,43064,43065,43072,43124,43136,43138,43188,43204,43214,43216,43232,
+43250,43256,43259,43264,43274,43302,43310,43312,43335,43346,43359,43360,43392,43395,43396,43443,43444,43446,43450,43452,43453,
+43457,43471,43472,43486,43520,43561,43567,43569,43571,43573,43584,43587,43588,43596,43597,43600,43612,43616,43632,43633,43639,
+43642,43643,43648,43696,43697,43698,43701,43703,43705,43710,43712,43713,43714,43739,43741,43742,43968,44003,44005,44006,44008,
+44009,44011,44012,44013,44016,44032,55216,55243,57344,63744,64048,64112,64256,64275,64285,64286,64287,64297,64298,64311,64312,
+64317,64318,64319,64320,64322,64323,64325,64326,64336,64434,64467,64830,64831,64832,64848,64912,64914,64968,64976,65008,65020,
+65021,65022,65024,65040,65047,65048,65049,65056,65072,65073,65075,65077,65078,65079,65080,65081,65082,65083,65084,65085,65086,
+65087,65088,65089,65090,65091,65092,65093,65095,65096,65097,65101,65104,65105,65106,65108,65109,65110,65112,65113,65114,65115,
+65116,65117,65118,65119,65120,65122,65123,65124,65128,65129,65130,65131,65136,65141,65142,65277,65279,65281,65283,65284,65285,
+65286,65288,65289,65290,65291,65292,65293,65294,65296,65306,65307,65308,65311,65313,65339,65340,65341,65342,65343,65344,65345,
+65371,65372,65373,65374,65375,65376,65377,65378,65379,65380,65382,65392,65393,65438,65440,65474,65482,65490,65498,65504,65506,
+65507,65508,65509,65512,65513,65517,65520,65529,65532,65534,65536,65549,65576,65596,65599,65616,65664,65792,65793,65794,65799,
+65847,65856,65909,65913,65930,65936,66000,66045,66176,66208,66304,66336,66352,66369,66370,66378,66432,66463,66464,66504,66512,
+66513,66560,66640,66720,67584,67590,67592,67593,67594,67638,67639,67641,67644,67645,67647,67670,67671,67672,67680,67840,67862,
+67868,67871,67872,67898,67903,67904,68096,68097,68100,68101,68103,68108,68112,68116,68117,68120,68121,68148,68152,68155,68159,
+68160,68168,68176,68185,68192,68221,68223,68224,68352,68406,68409,68416,68438,68440,68448,68467,68472,68480,68608,68681,69216,
+69247,69760,69762,69763,69808,69811,69815,69817,69819,69821,69822,73728,74752,74864,77824,118784,119040,119081,119141,119143,
+119146,119149,119155,119163,119171,119173,119180,119210,119214,119296,119362,119365,119552,119648,119808,119894,119966,119970,
+119973,119977,119982,119995,119997,120005,120071,120077,120086,120094,120123,120128,120134,120138,120146,120488,120513,120514,
+120539,120540,120571,120572,120597,120598,120629,120630,120655,120656,120687,120688,120713,120714,120745,120746,120771,120772,
+120782,124928,126976,127024,127232,127248,127281,127293,127295,127298,127302,127306,127319,127327,127353,127355,127359,127370,
+127376,127488,127504,127552,131070,131072,173824,194560,196606,262142,327678,393214,458750,524286,589822,655358,720894,786430,
+851966,917502,917505,917506,917536,917632,917760,918000,983038,983040,1048574,1048576,1114110
+};
+
+private static int[] bcE1 = {
+442,443,447,451,659,660,687,696,698,705,709,719,721,735,740,747,748,749,750,767,879,883,884,885,887,890,893,894,901,902,903,
+906,908,929,1013,1014,1153,1154,1159,1161,1317,1366,1369,1375,1415,1417,1418,1805,1806,1807,1808,1809,1839,1866,1868,1957,
+1968,1969,1983,1993,2026,2035,2037,2038,2041,2042,2047,2069,2073,2074,2083,2084,2087,2088,2093,2095,2110,2303,2306,2307,2361,
+2364,2365,2368,2376,2380,2381,2382,2384,2389,2401,2403,2405,2415,2416,2417,2418,2431,2433,2435,2444,2448,2472,2480,2482,2489,
+2492,2493,2496,2500,2504,2508,2509,2510,2519,2525,2529,2531,2543,2545,2547,2553,2554,2555,2562,2563,2570,2576,2600,2608,2611,
+2614,2617,2620,2624,2626,2632,2637,2641,2652,2654,2671,2673,2676,2677,2690,2691,2701,2705,2728,2736,2739,2745,2748,2749,2752,
+2757,2760,2761,2764,2765,2768,2785,2787,2799,2801,2817,2819,2828,2832,2856,2864,2867,2873,2876,2877,2878,2879,2880,2884,2888,
+2892,2893,2902,2903,2909,2913,2915,2927,2928,2929,2946,2947,2954,2960,2965,2970,2972,2975,2980,2986,3001,3007,3008,3010,3016,
+3020,3021,3024,3031,3055,3058,3064,3065,3066,3075,3084,3088,3112,3123,3129,3133,3136,3140,3144,3149,3158,3161,3169,3171,3183,
+3198,3199,3203,3212,3216,3240,3251,3257,3260,3261,3262,3263,3268,3270,3272,3275,3277,3286,3294,3297,3299,3311,3314,3331,3340,
+3344,3368,3385,3389,3392,3396,3400,3404,3405,3415,3425,3427,3439,3445,3449,3455,3459,3478,3505,3515,3517,3526,3530,3537,3540,
+3542,3551,3571,3572,3632,3633,3635,3642,3647,3653,3654,3662,3663,3673,3675,3714,3716,3720,3722,3725,3735,3743,3747,3749,3751,
+3755,3760,3761,3763,3769,3772,3773,3780,3782,3789,3801,3805,3840,3843,3858,3863,3865,3871,3881,3891,3892,3893,3894,3895,3896,
+3897,3898,3899,3900,3901,3903,3911,3948,3966,3967,3972,3973,3975,3979,3991,4028,4037,4038,4044,4047,4052,4056,4138,4140,4144,
+4145,4151,4152,4154,4156,4158,4159,4169,4175,4181,4183,4185,4189,4192,4193,4196,4198,4205,4208,4212,4225,4226,4228,4230,4236,
+4237,4238,4239,4249,4252,4253,4255,4293,4346,4347,4348,4680,4685,4694,4696,4701,4744,4749,4784,4789,4798,4800,4805,4822,4880,
+4885,4954,4959,4960,4968,4988,5007,5017,5108,5120,5740,5742,5759,5760,5786,5787,5788,5866,5869,5872,5900,5905,5908,5937,5940,
+5942,5969,5971,5996,6000,6003,6067,6069,6070,6077,6085,6086,6088,6099,6102,6103,6106,6107,6108,6109,6121,6137,6149,6150,6154,
+6157,6158,6169,6210,6211,6263,6312,6313,6314,6389,6428,6434,6438,6440,6443,6449,6450,6456,6459,6464,6469,6479,6509,6516,6571,
+6592,6599,6601,6618,6623,6655,6678,6680,6683,6687,6740,6741,6742,6743,6750,6752,6753,6754,6756,6764,6770,6780,6783,6793,6809,
+6822,6823,6829,6915,6916,6963,6964,6965,6970,6971,6972,6977,6978,6980,6987,7001,7008,7018,7027,7036,7041,7042,7072,7073,7077,
+7079,7081,7082,7087,7097,7203,7211,7219,7221,7223,7231,7241,7247,7257,7287,7293,7295,7378,7379,7392,7393,7400,7404,7405,7409,
+7410,7467,7521,7543,7544,7578,7615,7654,7679,7957,7965,8005,8013,8023,8025,8027,8029,8061,8116,8124,8125,8126,8129,8132,8140,
+8143,8147,8155,8159,8172,8175,8180,8188,8190,8202,8205,8206,8207,8213,8215,8216,8217,8218,8220,8221,8222,8223,8231,8232,8233,
+8234,8235,8236,8237,8238,8239,8244,8248,8249,8250,8254,8256,8259,8260,8261,8262,8273,8274,8275,8276,8286,8287,8292,8297,8303,
+8304,8305,8313,8315,8316,8317,8318,8319,8329,8331,8332,8333,8334,8340,8376,8412,8416,8417,8420,8432,8449,8450,8454,8455,8457,
+8467,8468,8469,8472,8477,8483,8484,8485,8486,8487,8488,8489,8493,8494,8500,8504,8505,8507,8511,8516,8521,8522,8523,8525,8526,
+8527,8543,8578,8580,8584,8585,8596,8601,8603,8607,8608,8610,8611,8613,8614,8621,8622,8653,8655,8657,8658,8659,8660,8691,8721,
+8722,8723,8959,8967,8971,8991,8993,9000,9001,9002,9013,9082,9083,9084,9108,9109,9114,9139,9179,9185,9192,9254,9290,9351,9371,
+9449,9471,9654,9655,9664,9665,9719,9727,9838,9839,9899,9900,9933,9953,9955,9983,9988,9993,10023,10059,10061,10066,10078,10087,
+10088,10089,10090,10091,10092,10093,10094,10095,10096,10097,10098,10099,10100,10101,10131,10132,10159,10174,10180,10181,10182,
+10186,10188,10213,10214,10215,10216,10217,10218,10219,10220,10221,10222,10223,10239,10495,10626,10627,10628,10629,10630,10631,
+10632,10633,10634,10635,10636,10637,10638,10639,10640,10641,10642,10643,10644,10645,10646,10647,10648,10711,10712,10713,10714,
+10715,10747,10748,10749,11007,11055,11076,11078,11084,11097,11310,11358,11388,11389,11492,11498,11502,11505,11516,11517,11519,
+11557,11621,11631,11670,11686,11694,11702,11710,11718,11726,11734,11742,11775,11777,11778,11779,11780,11781,11784,11785,11786,
+11787,11788,11789,11798,11799,11801,11802,11803,11804,11805,11807,11808,11809,11810,11811,11812,11813,11814,11815,11816,11817,
+11822,11823,11825,11929,12019,12245,12283,12288,12291,12292,12293,12294,12295,12296,12297,12298,12299,12300,12301,12302,12303,
+12304,12305,12307,12308,12309,12310,12311,12312,12313,12314,12315,12316,12317,12319,12320,12329,12335,12336,12341,12343,12346,
+12347,12348,12349,12351,12438,12442,12444,12446,12447,12448,12538,12539,12542,12543,12589,12686,12689,12693,12703,12727,12771,
+12799,12828,12830,12841,12879,12880,12895,12923,12926,12927,12937,12976,12991,13003,13007,13054,13174,13178,13277,13279,13310,
+13311,19893,19967,40907,40980,40981,42124,42182,42231,42237,42239,42507,42508,42511,42527,42537,42539,42591,42605,42606,42607,
+42610,42611,42621,42622,42623,42647,42725,42735,42737,42743,42774,42783,42785,42863,42864,42887,42888,42890,42892,43009,43010,
+43013,43014,43018,43019,43042,43044,43046,43047,43051,43061,43063,43064,43065,43123,43127,43137,43187,43203,43204,43215,43225,
+43249,43255,43258,43259,43273,43301,43309,43311,43334,43345,43347,43359,43388,43394,43395,43442,43443,43445,43449,43451,43452,
+43456,43469,43471,43481,43487,43560,43566,43568,43570,43572,43574,43586,43587,43595,43596,43597,43609,43615,43631,43632,43638,
+43641,43642,43643,43695,43696,43697,43700,43702,43704,43709,43711,43712,43713,43714,43740,43741,43743,44002,44004,44005,44007,
+44008,44010,44011,44012,44013,44025,55203,55238,55291,63743,64045,64109,64217,64262,64279,64285,64286,64296,64297,64310,64311,
+64316,64317,64318,64319,64321,64322,64324,64325,64335,64433,64466,64829,64830,64831,64847,64911,64913,64967,64975,65007,65019,
+65020,65021,65023,65039,65046,65047,65048,65049,65062,65072,65074,65076,65077,65078,65079,65080,65081,65082,65083,65084,65085,
+65086,65087,65088,65089,65090,65091,65092,65094,65095,65096,65100,65103,65104,65105,65106,65108,65109,65111,65112,65113,65114,
+65115,65116,65117,65118,65119,65121,65122,65123,65126,65128,65129,65130,65131,65140,65141,65276,65278,65279,65282,65283,65284,
+65285,65287,65288,65289,65290,65291,65292,65293,65295,65305,65306,65307,65310,65312,65338,65339,65340,65341,65342,65343,65344,
+65370,65371,65372,65373,65374,65375,65376,65377,65378,65379,65381,65391,65392,65437,65439,65470,65479,65487,65495,65500,65505,
+65506,65507,65508,65510,65512,65516,65518,65528,65531,65533,65535,65547,65574,65594,65597,65613,65629,65786,65792,65793,65794,
+65843,65855,65908,65912,65929,65930,65947,66044,66045,66204,66256,66334,66339,66368,66369,66377,66378,66461,66463,66499,66511,
+66512,66517,66639,66717,66729,67589,67591,67592,67593,67637,67638,67640,67643,67644,67646,67669,67670,67671,67679,67839,67861,
+67867,67870,67871,67897,67902,67903,68095,68096,68099,68100,68102,68107,68111,68115,68116,68119,68120,68147,68151,68154,68158,
+68159,68167,68175,68184,68191,68220,68222,68223,68351,68405,68408,68415,68437,68439,68447,68466,68471,68479,68607,68680,69215,
+69246,69631,69761,69762,69807,69810,69814,69816,69818,69820,69821,69825,74606,74850,74867,78894,119029,119078,119140,119142,
+119145,119148,119154,119162,119170,119172,119179,119209,119213,119261,119361,119364,119365,119638,119665,119892,119964,119967,
+119970,119974,119980,119993,119995,120003,120069,120074,120084,120092,120121,120126,120132,120134,120144,120485,120512,120513,
+120538,120539,120570,120571,120596,120597,120628,120629,120654,120655,120686,120687,120712,120713,120744,120745,120770,120771,
+120779,120831,126975,127019,127123,127242,127278,127281,127293,127295,127298,127302,127310,127319,127327,127353,127356,127359,
+127373,127376,127488,127537,127560,131071,173782,177972,195101,196607,262143,327679,393215,458751,524287,589823,655359,720895,
+786431,851967,917504,917505,917535,917631,917759,917999,921599,983039,1048573,1048575,1114109,1114111
+};
+
+private static byte[] bcC1 = {
+1,1,1,1,1,1,1,1,19,1,19,19,1,19,1,19,19,19,1,19,14,1,19,19,1,1,1,19,19,1,19,1,1,1,1,19,1,1,14,14,1,1,1,1,1,1,19,5,5,15,5,14,
+5,14,5,5,14,5,5,4,4,14,4,19,19,4,4,4,14,4,14,4,14,4,14,4,4,4,14,1,1,14,1,1,14,1,14,1,1,14,1,14,1,1,1,1,1,1,14,1,1,1,1,1,1,
+1,14,1,1,14,1,1,14,1,1,1,1,14,1,1,11,1,1,11,14,1,1,1,1,1,1,1,1,14,1,14,14,14,14,1,1,1,14,1,14,14,1,1,1,1,1,1,1,14,1,1,14,14,
+1,1,14,1,1,14,1,11,14,1,1,1,1,1,1,1,14,1,1,14,1,14,1,1,14,14,1,1,1,14,1,1,1,14,1,1,1,1,1,1,1,1,1,1,1,14,1,1,1,14,1,1,1,1,19,
+11,19,1,1,1,1,1,1,1,14,1,14,14,14,1,1,14,1,19,1,1,1,1,1,1,1,14,1,1,1,1,1,1,1,14,1,1,1,14,1,19,1,1,1,1,1,1,1,14,1,1,14,1,1,
+14,1,1,1,1,1,1,1,1,1,1,14,1,14,14,1,1,1,1,14,1,14,11,1,1,14,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,14,1,14,14,1,1,1,14,1,1,1,1,1,1,
+14,1,1,1,1,14,1,14,1,14,19,19,19,19,1,1,1,14,1,14,1,14,1,14,14,1,14,1,1,1,1,1,1,14,1,14,1,14,1,14,1,1,1,1,1,14,1,14,1,1,1,
+1,1,14,1,14,1,14,1,14,1,1,1,1,14,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,14,1,1,1,1,19,1,19,1,1,1,18,1,19,19,1,1,1,1,1,14,
+1,14,1,1,14,1,1,14,1,1,1,14,1,14,1,14,1,1,1,11,1,14,1,19,19,19,19,14,18,1,1,1,1,1,14,1,1,1,14,1,14,1,1,14,1,14,19,19,1,1,1,
+1,1,1,1,1,19,19,1,14,1,1,1,1,14,1,14,14,1,14,1,14,1,14,14,1,1,1,1,1,14,1,1,14,1,14,1,14,1,14,1,1,1,1,1,14,1,14,1,1,1,14,1,
+14,1,1,1,1,1,14,1,14,1,1,1,1,1,1,1,14,1,14,1,14,1,14,1,1,1,1,1,1,1,1,14,14,1,1,1,1,1,1,1,1,1,1,1,19,1,19,1,1,19,1,1,19,1,19,
+1,1,19,18,15,1,4,19,19,19,19,19,19,19,19,19,19,18,16,2,6,8,3,7,13,11,19,19,19,19,19,19,13,19,19,19,19,19,19,19,18,15,15,15,
+9,1,9,10,19,19,19,1,9,10,19,19,19,1,11,14,14,14,14,14,19,1,19,1,19,1,19,1,19,1,19,1,19,1,19,1,19,1,11,1,1,1,19,1,19,1,19,19,
+19,1,1,19,1,1,1,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,10,11,19,19,19,19,19,19,19,19,19,1,19,19,19,1,
+19,19,19,19,19,19,19,19,9,1,19,19,19,19,19,19,19,19,19,19,1,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,
+19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,1,19,19,19,19,19,19,19,19,19,19,19,19,19,19,
+19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,1,1,1,1,1,19,1,14,19,19,19,1,1,1,1,1,1,1,1,1,1,1,1,14,
+19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,18,19,19,1,1,1,
+19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,1,14,19,1,19,1,1,1,19,19,1,14,19,1,1,19,1,19,1,1,1,1,
+1,1,1,1,19,1,1,19,1,1,19,19,1,19,1,1,1,19,1,19,1,1,19,1,19,1,19,1,19,1,1,1,1,19,1,1,1,1,1,19,1,1,1,1,1,1,14,14,19,14,19,19,
+1,1,1,14,1,19,19,19,1,1,1,19,1,1,1,14,1,14,1,14,1,1,14,1,19,1,1,11,11,1,19,1,1,1,14,1,1,14,1,1,1,1,1,14,1,1,14,1,1,1,14,1,
+1,14,1,14,1,14,1,1,1,1,1,1,14,1,14,1,14,1,14,1,14,1,1,1,1,1,1,1,1,1,1,14,1,14,1,14,1,14,1,14,1,1,1,1,1,1,14,1,14,1,1,1,14,
+1,1,1,1,1,1,1,1,1,1,4,14,4,10,4,4,4,4,4,4,4,4,4,4,4,5,5,5,19,19,5,5,5,5,5,15,5,5,19,5,14,19,19,19,19,14,19,19,19,19,19,19,
+19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,13,19,13,19,13,19,19,19,19,19,19,19,19,11,19,10,10,19,19,11,11,19,5,
+5,5,5,15,19,11,11,11,19,19,19,19,10,13,10,13,9,13,19,19,19,1,19,19,19,19,19,19,1,19,19,19,19,19,19,19,19,19,19,1,1,1,1,1,1,
+1,1,1,11,19,19,19,11,19,19,19,15,19,19,15,1,1,1,1,1,1,1,1,19,1,1,1,19,19,19,19,19,1,14,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,4,
+4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,19,4,4,4,4,4,14,4,14,4,14,4,4,4,4,4,4,14,4,14,4,4,4,4,4,4,4,4,4,4,19,4,4,4,4,4,4,4,4,4,12,
+4,14,1,1,1,14,1,14,1,1,1,1,1,1,1,1,1,1,1,14,1,1,15,14,1,14,1,14,1,19,14,19,19,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+1,19,1,1,1,19,1,1,1,19,1,1,1,19,1,1,1,19,1,9,4,19,19,9,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,15,1,1,1,15,15,15,15,15,15,15,15,
+15,15,15,15,15,15,15,15,14,15,15,1,15,1,15
+};
+
+/**
+ * Lookup bidi class for character expressed as unicode scalar value.
+ * @param ch a unicode scalar value
+ * @return bidi class
+ */
+public static int getBidiClass ( int ch ) {
+  if ( ch <= 0x00FF ) {
+    return bcL1 [ ch - 0x0000 ];
+  } else if ( ( ch >= 0x0590 ) && ( ch <= 0x06FF ) ) {
+    return bcR1 [ ch - 0x0590 ];
+  } else {
+    return getBidiClass ( ch, bcS1, bcE1, bcC1 );
+  }
+}
+
+private static int getBidiClass ( int ch, int[] sa, int[] ea, byte[] ca ) {
+  int k = Arrays.binarySearch ( sa, ch );
+  if ( k >= 0 ) {
+    return ca [ k ];
+  } else {
+    k = - ( k + 1 );
+    if ( k == 0 ) {
+      return BidiConstants.L;
+    } else if ( ch <= ea [ k - 1 ] ) {
+      return ca [ k - 1 ];
+    } else {
+      return BidiConstants.L;
+    }
+  }
+}
+
+}
Index: src/java/org/apache/fop/text/linebreak/LineBreakUtils.java
===================================================================
--- src/java/org/apache/fop/text/linebreak/LineBreakUtils.java	(revision 981406)
+++ src/java/org/apache/fop/text/linebreak/LineBreakUtils.java	(working copy)
@@ -29,8 +29,15 @@
  * - commit BOTH changed files
  */
 
+// CSOFF: WhitespaceAfterCheck
+// CSOFF: LineLengthCheck
+
+/** Line breaking utilities. */
 public final class LineBreakUtils {
 
+    private LineBreakUtils() {
+    }
+
     /** Break class constant */
     public static final byte DIRECT_BREAK = 0;
     /** Break class constant */
@@ -44,179 +51,180 @@
     /** Break class constant */
     public static final byte EXPLICIT_BREAK = 5;
 
-    private static final byte PAIR_TABLE[][] = {
-        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
-        {0, 1, 0, 1, 0, 0, 0, 4, 2, 0, 4, 1, 0, 0, 1, 0, 1, 4, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 4, 4, 0, 4},
-        {0, 0, 4, 1, 0, 0, 0, 4, 2, 0, 4, 1, 0, 0, 1, 0, 0, 4, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 4, 4, 0, 4},
-        {0, 0, 0, 1, 0, 0, 0, 4, 2, 0, 4, 1, 0, 0, 1, 0, 0, 4, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 4, 4, 0, 4},
-        {0, 1, 1, 1, 1, 0, 0, 4, 2, 0, 4, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 4, 4, 0, 4},
-        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
-        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
-        {0, 1, 0, 1, 0, 0, 0, 4, 2, 0, 4, 1, 0, 0, 1, 0, 0, 4, 0, 0, 0, 0, 0, 4, 1, 0, 1, 1, 1, 0, 0, 0, 4, 4, 0, 4},
-        {0, 1, 0, 1, 0, 0, 0, 4, 2, 0, 4, 1, 0, 0, 1, 0, 1, 4, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 4, 4, 0, 4},
-        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
-        {0, 0, 0, 1, 0, 0, 0, 4, 2, 0, 4, 1, 0, 0, 1, 0, 0, 4, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 4, 4, 0, 4},
-        {0, 1, 1, 1, 1, 0, 0, 4, 2, 0, 4, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 4, 4, 0, 4},
-        {0, 0, 0, 1, 0, 0, 0, 4, 2, 0, 4, 1, 0, 0, 1, 0, 1, 4, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 4, 4, 0, 4},
-        {0, 0, 0, 1, 0, 0, 0, 4, 2, 0, 4, 1, 0, 0, 1, 0, 1, 4, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 4, 4, 0, 4},
-        {0, 0, 0, 1, 0, 0, 0, 4, 2, 0, 4, 1, 0, 0, 1, 0, 0, 4, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 4, 4, 0, 4},
-        {0, 0, 0, 1, 0, 0, 0, 4, 2, 0, 4, 1, 0, 0, 1, 0, 1, 4, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 4, 4, 0, 4},
-        {0, 0, 0, 1, 0, 0, 0, 4, 2, 0, 4, 1, 0, 0, 1, 0, 1, 4, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 4, 4, 0, 4},
-        {0, 1, 0, 1, 0, 0, 0, 4, 2, 0, 4, 1, 0, 0, 1, 0, 0, 4, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 4, 4, 0, 4},
-        {0, 0, 0, 1, 0, 0, 0, 4, 2, 0, 4, 1, 1, 1, 1, 0, 1, 4, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 4, 4, 0, 4},
-        {0, 0, 0, 1, 0, 0, 0, 4, 2, 0, 4, 1, 0, 0, 1, 0, 1, 4, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 4, 4, 0, 4},
-        {0, 0, 0, 1, 0, 0, 0, 4, 2, 0, 4, 1, 0, 0, 1, 0, 1, 4, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 4, 4, 0, 4},
-        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
-        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
-        {0, 0, 0, 1, 0, 0, 0, 4, 2, 0, 4, 1, 0, 0, 1, 0, 0, 4, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 4, 4, 0, 4},
-        {0, 1, 0, 1, 0, 0, 0, 4, 2, 0, 4, 1, 0, 0, 1, 0, 1, 4, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 4, 4, 0, 4},
-        {0, 4, 4, 4, 4, 0, 0, 4, 3, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 4, 4, 4, 4, 4, 4, 0, 0, 0, 4, 4, 0, 4},
-        {0, 1, 0, 1, 0, 0, 0, 4, 2, 0, 4, 1, 0, 0, 1, 0, 0, 4, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 4, 4, 0, 4},
-        {0, 1, 0, 1, 0, 0, 0, 4, 2, 0, 4, 1, 1, 1, 1, 1, 0, 4, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 4, 4, 0, 4},
-        {0, 1, 1, 1, 1, 0, 0, 4, 2, 0, 4, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 0, 0, 1, 1, 4, 1, 1, 1, 0, 0, 0, 4, 4, 0, 4},
-        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
-        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
-        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
-        {0, 0, 0, 1, 0, 0, 0, 4, 2, 0, 4, 1, 0, 0, 1, 0, 0, 4, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 4, 4, 0, 4},
-        {0, 1, 1, 1, 1, 0, 0, 4, 2, 0, 4, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 4, 4, 0, 4},
-        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
-        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4}};
+    private static final byte[][] PAIR_TABLE = {
+        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+        {0, 1, 0, 1, 0, 0, 0, 4, 2, 4, 0, 4, 1, 0, 0, 1, 0, 1, 4, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 4, 4, 0, 4},
+        {0, 0, 4, 1, 0, 0, 0, 4, 2, 4, 0, 4, 1, 0, 0, 1, 0, 0, 4, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 4, 4, 0, 4},
+        {0, 0, 0, 1, 0, 0, 0, 4, 2, 4, 0, 4, 1, 0, 0, 1, 0, 0, 4, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 4, 4, 0, 4},
+        {0, 1, 1, 1, 1, 0, 0, 4, 2, 4, 0, 4, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 4, 4, 0, 4},
+        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+        {0, 1, 0, 1, 0, 0, 0, 4, 2, 4, 0, 4, 1, 0, 0, 1, 0, 0, 4, 0, 0, 0, 0, 0, 4, 1, 0, 1, 1, 1, 0, 0, 0, 4, 4, 0, 4},
+        {0, 1, 0, 1, 0, 0, 0, 4, 2, 4, 0, 4, 1, 0, 0, 1, 0, 1, 4, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 4, 4, 0, 4},
+        {0, 1, 0, 1, 0, 0, 0, 4, 2, 4, 0, 4, 1, 0, 0, 1, 0, 0, 4, 0, 0, 0, 0, 0, 4, 1, 0, 1, 1, 1, 0, 0, 0, 4, 4, 0, 4},
+        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+        {0, 0, 0, 1, 0, 0, 0, 4, 2, 4, 0, 4, 1, 0, 0, 1, 0, 0, 4, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 4, 4, 0, 4},
+        {0, 1, 1, 1, 1, 0, 0, 4, 2, 4, 0, 4, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 4, 4, 0, 4},
+        {0, 0, 0, 1, 0, 0, 0, 4, 2, 4, 0, 4, 1, 0, 0, 1, 0, 1, 4, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 4, 4, 0, 4},
+        {0, 0, 0, 1, 0, 0, 0, 4, 2, 4, 0, 4, 1, 0, 0, 1, 0, 1, 4, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 4, 4, 0, 4},
+        {0, 0, 0, 1, 0, 0, 0, 4, 2, 4, 0, 4, 1, 0, 0, 1, 0, 0, 4, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 4, 4, 0, 4},
+        {0, 0, 0, 1, 0, 0, 0, 4, 2, 4, 0, 4, 1, 0, 0, 1, 0, 1, 4, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 4, 4, 0, 4},
+        {0, 0, 0, 1, 0, 0, 0, 4, 2, 4, 0, 4, 1, 0, 0, 1, 0, 1, 4, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 4, 4, 0, 4},
+        {0, 1, 0, 1, 0, 0, 0, 4, 2, 4, 0, 4, 1, 0, 0, 1, 0, 0, 4, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 4, 4, 0, 4},
+        {0, 0, 0, 1, 0, 0, 0, 4, 2, 4, 0, 4, 1, 1, 1, 1, 0, 1, 4, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 4, 4, 0, 4},
+        {0, 0, 0, 1, 0, 0, 0, 4, 2, 4, 0, 4, 1, 0, 0, 1, 0, 1, 4, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 4, 4, 0, 4},
+        {0, 0, 0, 1, 0, 0, 0, 4, 2, 4, 0, 4, 1, 0, 0, 1, 0, 1, 4, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 4, 4, 0, 4},
+        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+        {0, 0, 0, 1, 0, 0, 0, 4, 2, 4, 0, 4, 1, 0, 0, 1, 0, 0, 4, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 4, 4, 0, 4},
+        {0, 1, 0, 1, 0, 0, 0, 4, 2, 4, 0, 4, 1, 0, 0, 1, 0, 1, 4, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 4, 4, 0, 4},
+        {0, 4, 4, 4, 4, 0, 0, 4, 3, 4, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 4, 4, 4, 4, 4, 4, 0, 0, 0, 4, 4, 0, 4},
+        {0, 1, 0, 1, 0, 0, 0, 4, 2, 4, 0, 4, 1, 0, 0, 1, 0, 0, 4, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 4, 4, 0, 4},
+        {0, 1, 0, 1, 0, 0, 0, 4, 2, 4, 0, 4, 1, 1, 1, 1, 1, 0, 4, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 4, 4, 0, 4},
+        {0, 1, 1, 1, 1, 0, 0, 4, 2, 4, 0, 4, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 0, 0, 1, 1, 4, 1, 1, 1, 0, 0, 0, 4, 4, 0, 4},
+        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+        {0, 0, 0, 1, 0, 0, 0, 4, 2, 4, 0, 4, 1, 0, 0, 1, 0, 0, 4, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 4, 4, 0, 4},
+        {0, 1, 1, 1, 1, 0, 0, 4, 2, 4, 0, 4, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 4, 4, 0, 4},
+        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4}};
 
-    private static byte lineBreakProperties[][] = new byte[512][];
+    private static byte[][] lineBreakProperties = new byte[512][];
 
-    private static void init_0() {
-        lineBreakProperties[0] = new byte[] { 9, 9, 9, 9, 9, 9, 9, 9, 9, 4, 22, 6, 6, 10, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 32, 11, 29, 2, 28, 27, 2, 29, 26, 8, 2, 28, 18, 15, 18, 33, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 18, 18, 2, 2, 2, 11, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 26, 28, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 26, 4, 8, 2, 9};
-        lineBreakProperties[1] = new byte[] { 9, 9, 9, 9, 9, 23, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 12, 26, 27, 28, 28, 28, 2, 1, 1, 2, 1, 29, 2, 4, 2, 2, 27, 28, 1, 1, 5, 2, 1, 1, 1, 1, 1, 29, 1, 1, 1, 26, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2};
+    private static void init0() {
+        lineBreakProperties[0] = new byte[] { 9, 9, 9, 9, 9, 9, 9, 9, 9, 4, 23, 6, 6, 11, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 33, 12, 30, 2, 29, 28, 2, 30, 27, 10, 2, 29, 19, 16, 19, 34, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 19, 19, 2, 2, 2, 12, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 27, 29, 10, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 27, 4, 8, 2, 9};
+        lineBreakProperties[1] = new byte[] { 9, 9, 9, 9, 9, 24, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 13, 27, 28, 29, 29, 29, 2, 1, 1, 2, 1, 30, 2, 4, 2, 2, 28, 29, 1, 1, 5, 2, 1, 1, 1, 1, 1, 30, 1, 1, 1, 27, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2};
         lineBreakProperties[2] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
         lineBreakProperties[5] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 5, 1, 1, 1, 5, 1, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 2, 1, 2, 5, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
-        lineBreakProperties[6] = new byte[] { 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 12, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 12, 12, 12, 12, 12, 12, 12, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 18, 0};
+        lineBreakProperties[6] = new byte[] { 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 13, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 13, 13, 13, 13, 13, 13, 13, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 19, 0};
         lineBreakProperties[7] = new byte[] { 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 0, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
         lineBreakProperties[9] = new byte[] { 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
-        lineBreakProperties[10] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
-        lineBreakProperties[11] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 0, 18, 4, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 4, 9, 2, 9, 9, 2, 9, 9, 11, 9, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
-        lineBreakProperties[12] = new byte[] { 2, 2, 2, 2, 0, 0, 2, 2, 2, 27, 27, 27, 18, 18, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 11, 0, 0, 11, 11, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 27, 25, 25, 2, 2, 2, 9, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
-        lineBreakProperties[13] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 11, 2, 9, 9, 9, 9, 9, 9, 9, 2, 9, 9, 9, 9, 9, 9, 9, 2, 2, 9, 9, 2, 9, 9, 9, 9, 2, 2, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 2, 2, 2, 2, 2, 2};
+        lineBreakProperties[10] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
+        lineBreakProperties[11] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 0, 19, 4, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 4, 9, 2, 9, 9, 2, 9, 9, 12, 9, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+        lineBreakProperties[12] = new byte[] { 2, 2, 2, 2, 0, 0, 2, 2, 2, 28, 28, 28, 19, 19, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 12, 0, 0, 12, 12, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 28, 26, 26, 2, 2, 2, 9, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
+        lineBreakProperties[13] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 12, 2, 9, 9, 9, 9, 9, 9, 9, 2, 9, 9, 9, 9, 9, 9, 9, 2, 2, 9, 9, 2, 9, 9, 9, 9, 2, 2, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 2, 2, 2, 2, 2, 2};
         lineBreakProperties[14] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 9, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
-        lineBreakProperties[15] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 2, 2, 2, 2, 18, 11, 2, 0, 0, 0, 0, 0};
-        lineBreakProperties[16] = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
-        lineBreakProperties[18] = new byte[] { 0, 9, 9, 9, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 9, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 2, 9, 9, 9, 9, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 4, 4, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2};
-        lineBreakProperties[19] = new byte[] { 0, 9, 9, 9, 0, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 0, 2, 0, 0, 0, 2, 2, 2, 2, 0, 0, 9, 2, 9, 9, 9, 9, 9, 9, 9, 0, 0, 9, 9, 0, 0, 9, 9, 9, 2, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 2, 2, 0, 2, 2, 2, 9, 9, 0, 0, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 2, 2, 28, 28, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0};
-        lineBreakProperties[20] = new byte[] { 0, 9, 9, 9, 0, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 0, 2, 2, 0, 2, 2, 0, 0, 9, 0, 9, 9, 9, 9, 9, 0, 0, 0, 0, 9, 9, 0, 0, 9, 9, 9, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 9, 9, 2, 2, 2, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
-        lineBreakProperties[21] = new byte[] { 0, 9, 9, 9, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 0, 2, 2, 2, 2, 2, 0, 0, 9, 2, 9, 9, 9, 9, 9, 9, 9, 9, 0, 9, 9, 9, 0, 9, 9, 9, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 9, 9, 0, 0, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 0, 28, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
-        lineBreakProperties[22] = new byte[] { 0, 9, 9, 9, 0, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 0, 2, 2, 2, 2, 2, 0, 0, 9, 2, 9, 9, 9, 9, 9, 9, 9, 0, 0, 9, 9, 0, 0, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 0, 0, 0, 0, 2, 2, 0, 2, 2, 2, 9, 9, 0, 0, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
-        lineBreakProperties[23] = new byte[] { 0, 0, 9, 2, 0, 2, 2, 2, 2, 2, 2, 0, 0, 0, 2, 2, 2, 0, 2, 2, 2, 2, 0, 0, 0, 2, 2, 0, 2, 0, 2, 2, 0, 0, 0, 2, 2, 0, 0, 0, 2, 2, 2, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 9, 9, 9, 9, 9, 0, 0, 0, 9, 9, 9, 0, 9, 9, 9, 9, 0, 0, 2, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 2, 2, 2, 2, 2, 2, 2, 2, 2, 28, 2, 0, 0, 0, 0, 0};
-        lineBreakProperties[24] = new byte[] { 0, 9, 9, 9, 0, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 0, 0, 0, 2, 9, 9, 9, 9, 9, 9, 9, 0, 9, 9, 9, 0, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 9, 9, 0, 2, 2, 0, 0, 0, 0, 0, 0, 2, 2, 9, 9, 0, 0, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2};
-        lineBreakProperties[25] = new byte[] { 0, 0, 9, 9, 0, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 0, 0, 9, 2, 9, 9, 9, 9, 9, 9, 9, 0, 9, 9, 9, 0, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 9, 9, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 2, 9, 9, 0, 0, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
-        lineBreakProperties[26] = new byte[] { 0, 0, 9, 9, 0, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 2, 9, 9, 9, 9, 9, 9, 9, 0, 9, 9, 9, 0, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 9, 9, 0, 0, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 2, 2, 2, 2, 2, 2, 0, 0, 0, 27, 2, 2, 2, 2, 2, 2};
+        lineBreakProperties[15] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 2, 2, 2, 2, 19, 12, 2, 0, 0, 0, 0, 0};
+        lineBreakProperties[16] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 2, 9, 9, 9, 2, 9, 9, 9, 9, 9, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+        lineBreakProperties[17] = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+        lineBreakProperties[18] = new byte[] { 9, 9, 9, 9, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 9, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 2, 9, 9, 9, 9, 9, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 4, 4, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 2, 2, 2, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2};
+        lineBreakProperties[19] = new byte[] { 0, 9, 9, 9, 0, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 0, 2, 0, 0, 0, 2, 2, 2, 2, 0, 0, 9, 2, 9, 9, 9, 9, 9, 9, 9, 0, 0, 9, 9, 0, 0, 9, 9, 9, 2, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 2, 2, 0, 2, 2, 2, 9, 9, 0, 0, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 2, 2, 28, 28, 2, 2, 2, 2, 2, 28, 2, 29, 0, 0, 0, 0};
+        lineBreakProperties[20] = new byte[] { 0, 9, 9, 9, 0, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 0, 2, 2, 0, 2, 2, 0, 0, 9, 0, 9, 9, 9, 9, 9, 0, 0, 0, 0, 9, 9, 0, 0, 9, 9, 9, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 9, 9, 2, 2, 2, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+        lineBreakProperties[21] = new byte[] { 0, 9, 9, 9, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 0, 2, 2, 2, 2, 2, 0, 0, 9, 2, 9, 9, 9, 9, 9, 9, 9, 9, 0, 9, 9, 9, 0, 9, 9, 9, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 9, 9, 0, 0, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 0, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+        lineBreakProperties[22] = new byte[] { 0, 9, 9, 9, 0, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 0, 2, 2, 2, 2, 2, 0, 0, 9, 2, 9, 9, 9, 9, 9, 9, 9, 0, 0, 9, 9, 0, 0, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 0, 0, 0, 0, 2, 2, 0, 2, 2, 2, 9, 9, 0, 0, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+        lineBreakProperties[23] = new byte[] { 0, 0, 9, 2, 0, 2, 2, 2, 2, 2, 2, 0, 0, 0, 2, 2, 2, 0, 2, 2, 2, 2, 0, 0, 0, 2, 2, 0, 2, 0, 2, 2, 0, 0, 0, 2, 2, 0, 0, 0, 2, 2, 2, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 9, 9, 9, 9, 9, 0, 0, 0, 9, 9, 9, 0, 9, 9, 9, 9, 0, 0, 2, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 2, 2, 2, 2, 2, 2, 2, 2, 2, 29, 2, 0, 0, 0, 0, 0};
+        lineBreakProperties[24] = new byte[] { 0, 9, 9, 9, 0, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 0, 0, 0, 2, 9, 9, 9, 9, 9, 9, 9, 0, 9, 9, 9, 0, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 9, 9, 0, 2, 2, 0, 0, 0, 0, 0, 0, 2, 2, 9, 9, 0, 0, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2};
+        lineBreakProperties[25] = new byte[] { 0, 0, 9, 9, 0, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 0, 0, 9, 2, 9, 9, 9, 9, 9, 9, 9, 0, 9, 9, 9, 0, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 9, 9, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 2, 9, 9, 0, 0, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+        lineBreakProperties[26] = new byte[] { 0, 0, 9, 9, 0, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 2, 9, 9, 9, 9, 9, 9, 9, 0, 9, 9, 9, 0, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 9, 9, 0, 0, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 2, 2, 2, 2, 2, 2, 0, 0, 0, 28, 2, 2, 2, 2, 2, 2};
         lineBreakProperties[27] = new byte[] { 0, 0, 9, 9, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 9, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 0, 9, 0, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
-        lineBreakProperties[28] = new byte[] { 0, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 0, 0, 0, 0, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 2, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
-        lineBreakProperties[29] = new byte[] { 0, 30, 30, 0, 30, 0, 0, 30, 30, 0, 30, 0, 0, 30, 0, 0, 0, 0, 0, 0, 30, 30, 30, 30, 0, 30, 30, 30, 30, 30, 30, 30, 0, 30, 30, 30, 0, 30, 0, 30, 0, 0, 30, 30, 0, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 0, 30, 30, 30, 0, 0, 30, 30, 30, 30, 30, 0, 30, 0, 30, 30, 30, 30, 30, 30, 0, 0, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 0, 0, 30, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
-        lineBreakProperties[30] = new byte[] { 2, 5, 5, 5, 5, 2, 5, 5, 12, 5, 5, 4, 12, 11, 11, 11, 11, 11, 12, 2, 11, 2, 2, 2, 9, 9, 2, 2, 2, 2, 2, 2, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 9, 2, 9, 2, 9, 26, 8, 26, 8, 9, 9, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 4};
-        lineBreakProperties[31] = new byte[] { 9, 9, 9, 9, 9, 4, 9, 9, 2, 2, 2, 2, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 4, 4, 2, 2, 2, 2, 2, 2, 9, 2, 2, 2, 2, 2, 2, 0, 2, 2, 5, 5, 4, 5, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
-        lineBreakProperties[32] = new byte[] { 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 4, 4, 2, 2, 2, 2, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30};
-        lineBreakProperties[33] = new byte[] { 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 0, 0, 0, 0, 30, 30, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0};
-        lineBreakProperties[34] = new byte[] { 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 0, 0, 0, 0, 0, 19, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21};
-        lineBreakProperties[35] = new byte[] { 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 0, 0, 0, 0, 0, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 0, 0, 0, 0, 0, 0};
+        lineBreakProperties[28] = new byte[] { 0, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 0, 0, 0, 0, 29, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 2, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+        lineBreakProperties[29] = new byte[] { 0, 31, 31, 0, 31, 0, 0, 31, 31, 0, 31, 0, 0, 31, 0, 0, 0, 0, 0, 0, 31, 31, 31, 31, 0, 31, 31, 31, 31, 31, 31, 31, 0, 31, 31, 31, 0, 31, 0, 31, 0, 0, 31, 31, 0, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 0, 31, 31, 31, 0, 0, 31, 31, 31, 31, 31, 0, 31, 0, 31, 31, 31, 31, 31, 31, 0, 0, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 0, 0, 31, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+        lineBreakProperties[30] = new byte[] { 2, 5, 5, 5, 5, 2, 5, 5, 13, 5, 5, 4, 13, 12, 12, 12, 12, 12, 13, 2, 12, 2, 2, 2, 9, 9, 2, 2, 2, 2, 2, 2, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 9, 2, 9, 2, 9, 27, 8, 27, 8, 9, 9, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 4};
+        lineBreakProperties[31] = new byte[] { 9, 9, 9, 9, 9, 4, 9, 9, 2, 2, 2, 2, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 4, 4, 2, 2, 2, 2, 2, 2, 9, 2, 2, 2, 2, 2, 2, 0, 2, 2, 5, 5, 4, 5, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+        lineBreakProperties[32] = new byte[] { 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 4, 4, 2, 2, 2, 2, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31};
+        lineBreakProperties[33] = new byte[] { 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 31, 31, 31, 31, 31, 31, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0};
+        lineBreakProperties[34] = new byte[] { 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22};
+        lineBreakProperties[35] = new byte[] { 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21};
         lineBreakProperties[36] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 0, 2, 0, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
         lineBreakProperties[37] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 0, 2, 0, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
         lineBreakProperties[38] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 9, 2, 4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0};
         lineBreakProperties[39] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
-        lineBreakProperties[40] = new byte[] { 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
-        lineBreakProperties[44] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0};
-        lineBreakProperties[45] = new byte[] { 4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 26, 8, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 4, 4, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+        lineBreakProperties[40] = new byte[] { 4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
+        lineBreakProperties[45] = new byte[] { 4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 27, 8, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 4, 4, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
         lineBreakProperties[46] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 0, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
-        lineBreakProperties[47] = new byte[] { 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 4, 4, 24, 30, 4, 2, 4, 28, 30, 30, 0, 0, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0};
-        lineBreakProperties[48] = new byte[] { 2, 2, 11, 11, 4, 4, 5, 2, 11, 11, 2, 9, 9, 9, 12, 0, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0};
-        lineBreakProperties[49] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
-        lineBreakProperties[50] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 2, 0, 0, 0, 11, 11, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 0, 0, 30, 30, 30, 30, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
-        lineBreakProperties[51] = new byte[] { 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 0, 0, 0, 0, 0, 0, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 0, 0, 0, 0, 0, 0, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 0, 0, 0, 0, 30, 30, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
-        lineBreakProperties[52] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
-        lineBreakProperties[54] = new byte[] { 9, 9, 9, 9, 9, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 4, 4, 4, 4, 4, 4, 4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0};
-        lineBreakProperties[55] = new byte[] { 9, 9, 9, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 2, 2, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
-        lineBreakProperties[56] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 4, 4, 4, 4, 4, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 0, 0, 0, 2, 2, 2, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 4};
-        lineBreakProperties[59] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9};
+        lineBreakProperties[47] = new byte[] { 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 4, 4, 25, 31, 4, 2, 4, 29, 31, 31, 0, 0, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0};
+        lineBreakProperties[48] = new byte[] { 2, 2, 12, 12, 4, 4, 5, 2, 12, 12, 2, 9, 9, 9, 13, 0, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0};
+        lineBreakProperties[49] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 2, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+        lineBreakProperties[50] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 2, 0, 0, 0, 12, 12, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 0, 0, 31, 31, 31, 31, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+        lineBreakProperties[51] = new byte[] { 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 0, 0, 0, 0, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 0, 0, 0, 0, 0, 0, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 0, 0, 0, 31, 31, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
+        lineBreakProperties[52] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 0, 0, 2, 2, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 0, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 0, 0, 9};
+        lineBreakProperties[53] = new byte[] { 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 0, 0, 0, 0, 0, 0, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 0, 0, 0, 0, 0, 0, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+        lineBreakProperties[54] = new byte[] { 9, 9, 9, 9, 9, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 4, 4, 2, 4, 4, 4, 4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0};
+        lineBreakProperties[55] = new byte[] { 9, 9, 9, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 2, 2, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+        lineBreakProperties[56] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 4, 4, 4, 4, 4, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 0, 0, 0, 2, 2, 2, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 4};
+        lineBreakProperties[57] = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 2, 2, 2, 2, 9, 2, 2, 2, 2, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+        lineBreakProperties[59] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9};
         lineBreakProperties[62] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 0, 2, 0, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0};
         lineBreakProperties[63] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 5, 2, 0};
-        lineBreakProperties[64] = new byte[] { 4, 4, 4, 4, 4, 4, 4, 12, 4, 4, 4, 36, 9, 9, 9, 9, 4, 12, 4, 4, 3, 1, 1, 2, 29, 29, 26, 29, 29, 29, 26, 29, 1, 1, 2, 2, 17, 17, 17, 4, 6, 6, 9, 9, 9, 9, 9, 12, 27, 27, 27, 27, 27, 27, 27, 27, 2, 29, 29, 1, 24, 24, 2, 2, 2, 2, 2, 2, 18, 26, 8, 24, 24, 24, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 2, 4, 4, 4, 4, 2, 4, 4, 4, 34, 2, 2, 2, 2, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 2, 2, 0, 0, 1, 2, 2, 2, 2, 2, 2, 2, 2, 26, 8, 1};
-        lineBreakProperties[65] = new byte[] { 2, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 26, 8, 0, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28, 28, 28, 28, 28, 28, 28, 27, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
-        lineBreakProperties[66] = new byte[] { 2, 2, 2, 27, 2, 1, 2, 2, 2, 27, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 28, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 2, 1, 1, 2, 2, 2, 2, 2, 1, 2, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2};
-        lineBreakProperties[67] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
-        lineBreakProperties[68] = new byte[] { 1, 2, 1, 1, 2, 2, 2, 1, 1, 2, 2, 1, 2, 2, 2, 1, 2, 1, 28, 28, 2, 1, 2, 2, 2, 2, 1, 2, 2, 1, 1, 1, 1, 2, 2, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 2, 1, 2, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 1, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 2, 2, 1, 1, 1, 1, 2, 2, 1, 1, 2, 2, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
+        lineBreakProperties[64] = new byte[] { 4, 4, 4, 4, 4, 4, 4, 13, 4, 4, 4, 37, 9, 9, 9, 9, 4, 13, 4, 4, 3, 1, 1, 2, 30, 30, 27, 30, 30, 30, 27, 30, 1, 1, 2, 2, 18, 18, 18, 4, 6, 6, 9, 9, 9, 9, 9, 13, 28, 28, 28, 28, 28, 28, 28, 28, 2, 30, 30, 1, 25, 25, 2, 2, 2, 2, 2, 2, 19, 27, 8, 25, 25, 25, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 2, 4, 4, 4, 4, 2, 4, 4, 4, 35, 2, 2, 2, 2, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 2, 2, 0, 0, 1, 2, 2, 2, 2, 2, 2, 2, 2, 27, 8, 1};
+        lineBreakProperties[65] = new byte[] { 2, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 27, 8, 0, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+        lineBreakProperties[66] = new byte[] { 2, 2, 2, 28, 2, 1, 2, 2, 2, 28, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 29, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 2, 1, 2, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2};
+        lineBreakProperties[67] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
+        lineBreakProperties[68] = new byte[] { 1, 2, 1, 1, 2, 2, 2, 1, 1, 2, 2, 1, 2, 2, 2, 1, 2, 1, 29, 29, 2, 1, 2, 2, 2, 2, 1, 2, 2, 1, 1, 1, 1, 2, 2, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 2, 1, 2, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 1, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 2, 2, 1, 1, 1, 1, 2, 2, 1, 1, 2, 2, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
         lineBreakProperties[69] = new byte[] { 2, 2, 1, 1, 2, 2, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
-        lineBreakProperties[70] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 26, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
-        lineBreakProperties[71] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+        lineBreakProperties[70] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 27, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
+        lineBreakProperties[71] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
         lineBreakProperties[72] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
         lineBreakProperties[73] = new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2};
+    }
+
+    private static void init1() {
         lineBreakProperties[74] = new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
         lineBreakProperties[75] = new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 2, 2, 1, 1, 2, 2, 2, 2, 1, 1, 2, 2, 1, 1, 2, 2, 2, 2, 1, 1, 1, 2, 2, 1, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
-    }
-
-    private static void init_1() {
         lineBreakProperties[76] = new byte[] { 2, 2, 2, 2, 2, 1, 1, 2, 2, 1, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 1, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
-        lineBreakProperties[77] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
-        lineBreakProperties[78] = new byte[] { 0, 2, 2, 2, 2, 0, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 0, 2, 2, 2, 2, 0, 0, 0, 2, 0, 2, 2, 2, 29, 29, 29, 29, 0, 0, 2, 11, 11, 2, 2, 2, 2, 26, 8, 26, 8, 26, 8, 26, 8, 26, 8, 26, 8, 26, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
-        lineBreakProperties[79] = new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 26, 8, 2, 2, 2, 2, 0, 2, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 26, 8, 26, 8, 26, 8, 26, 8, 26, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
-        lineBreakProperties[83] = new byte[] { 2, 2, 2, 26, 8, 26, 8, 26, 8, 26, 8, 26, 8, 26, 8, 26, 8, 26, 8, 26, 8, 26, 8, 26, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 26, 8, 26, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 26, 8, 2, 2};
-        lineBreakProperties[86] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
-        lineBreakProperties[88] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0};
-        lineBreakProperties[89] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 4, 4, 4, 2, 11, 4};
+        lineBreakProperties[77] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
+        lineBreakProperties[78] = new byte[] { 0, 2, 2, 2, 2, 0, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 0, 2, 2, 2, 2, 0, 0, 0, 2, 1, 2, 2, 2, 30, 30, 30, 30, 0, 0, 2, 12, 12, 2, 2, 2, 2, 27, 8, 27, 8, 27, 8, 27, 8, 27, 8, 27, 8, 27, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
+        lineBreakProperties[79] = new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 27, 8, 2, 2, 2, 2, 0, 2, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 27, 8, 27, 8, 27, 8, 27, 8, 27, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
+        lineBreakProperties[83] = new byte[] { 2, 2, 2, 27, 8, 27, 8, 27, 8, 27, 8, 27, 8, 27, 8, 27, 8, 27, 8, 27, 8, 27, 8, 27, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 27, 8, 27, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 27, 8, 2, 2};
+        lineBreakProperties[86] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+        lineBreakProperties[88] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
+        lineBreakProperties[89] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 12, 4, 4, 4, 2, 12, 4};
         lineBreakProperties[90] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
         lineBreakProperties[91] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9};
-        lineBreakProperties[92] = new byte[] { 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 4, 4, 4, 4, 4, 4, 4, 4, 2, 4, 26, 4, 2, 2, 29, 29, 2, 2, 29, 29, 26, 8, 26, 8, 26, 8, 26, 8, 4, 4, 4, 4, 11, 2, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
-        lineBreakProperties[93] = new byte[] { 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 0, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
-        lineBreakProperties[94] = new byte[] { 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16};
-        lineBreakProperties[95] = new byte[] { 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 0, 0, 0, 0};
-        lineBreakProperties[96] = new byte[] { 16, 8, 8, 16, 16, 24, 16, 16, 26, 8, 26, 8, 26, 8, 26, 8, 26, 8, 16, 16, 26, 8, 26, 8, 26, 8, 26, 8, 24, 26, 8, 8, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 9, 9, 9, 9, 9, 9, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 24, 24, 16, 16, 16, 0, 24, 16, 24, 16, 24, 16, 24, 16, 24, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 24, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16};
-        lineBreakProperties[97] = new byte[] { 16, 16, 16, 24, 16, 24, 16, 24, 16, 16, 16, 16, 16, 16, 24, 16, 16, 16, 16, 16, 16, 24, 24, 0, 0, 9, 9, 24, 24, 24, 24, 16, 24, 24, 16, 24, 16, 24, 16, 24, 16, 24, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 24, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 24, 16, 24, 16, 24, 16, 16, 16, 16, 16, 16, 24, 16, 16, 16, 16, 16, 16, 24, 24, 16, 16, 16, 16, 24, 24, 24, 24, 16};
-        lineBreakProperties[98] = new byte[] { 0, 0, 0, 0, 0, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 0, 0, 0, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16};
-        lineBreakProperties[99] = new byte[] { 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 0, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 0, 0, 0, 0, 0, 0, 0, 0, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24};
-        lineBreakProperties[100] = new byte[] { 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 0, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16};
-        lineBreakProperties[101] = new byte[] { 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 0};
-        lineBreakProperties[155] = new byte[] { 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
-        lineBreakProperties[319] = new byte[] { 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
-        lineBreakProperties[320] = new byte[] { 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 24, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16};
-        lineBreakProperties[329] = new byte[] { 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 0, 0, 0, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
-        lineBreakProperties[332] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 11, 4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 2, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 2, 2};
-        lineBreakProperties[333] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+        lineBreakProperties[92] = new byte[] { 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 4, 4, 4, 4, 4, 4, 4, 4, 2, 4, 27, 4, 2, 2, 30, 30, 2, 2, 30, 30, 27, 8, 27, 8, 27, 8, 27, 8, 4, 4, 4, 4, 12, 2, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+        lineBreakProperties[93] = new byte[] { 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 0, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+        lineBreakProperties[94] = new byte[] { 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17};
+        lineBreakProperties[95] = new byte[] { 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 0, 0, 0, 0};
+        lineBreakProperties[96] = new byte[] { 17, 8, 8, 17, 17, 25, 17, 17, 27, 8, 27, 8, 27, 8, 27, 8, 27, 8, 17, 17, 27, 8, 27, 8, 27, 8, 27, 8, 25, 27, 8, 8, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 9, 9, 9, 9, 9, 9, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 25, 25, 17, 17, 17, 0, 25, 17, 25, 17, 25, 17, 25, 17, 25, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 25, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17};
+        lineBreakProperties[97] = new byte[] { 17, 17, 17, 25, 17, 25, 17, 25, 17, 17, 17, 17, 17, 17, 25, 17, 17, 17, 17, 17, 17, 25, 25, 0, 0, 9, 9, 25, 25, 25, 25, 17, 25, 25, 17, 25, 17, 25, 17, 25, 17, 25, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 25, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 25, 17, 25, 17, 25, 17, 17, 17, 17, 17, 17, 25, 17, 17, 17, 17, 17, 17, 25, 25, 17, 17, 17, 17, 25, 25, 25, 25, 17};
+        lineBreakProperties[98] = new byte[] { 0, 0, 0, 0, 0, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 0, 0, 0, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17};
+        lineBreakProperties[99] = new byte[] { 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 0, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25};
+        lineBreakProperties[100] = new byte[] { 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 0, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 1, 1, 1, 1, 1, 1, 1, 1, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17};
+        lineBreakProperties[101] = new byte[] { 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 0};
+        lineBreakProperties[155] = new byte[] { 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
+        lineBreakProperties[320] = new byte[] { 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 25, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17};
+        lineBreakProperties[329] = new byte[] { 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 0, 0, 0, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 4};
+        lineBreakProperties[332] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 12, 4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 2, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 2, 2};
+        lineBreakProperties[333] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 2, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0};
         lineBreakProperties[335] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2};
-        lineBreakProperties[336] = new byte[] { 2, 2, 9, 2, 2, 2, 9, 2, 2, 2, 2, 9, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 5, 5, 11, 11, 0, 0, 0, 0, 0, 0, 0, 0};
-        lineBreakProperties[337] = new byte[] { 9, 9, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
-        lineBreakProperties[338] = new byte[] { 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 4, 4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
-        lineBreakProperties[340] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 9, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 0, 0, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 0, 0, 2, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
-        lineBreakProperties[344] = new byte[] { 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14};
-        lineBreakProperties[345] = new byte[] { 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 13, 14, 14, 14};
-        lineBreakProperties[346] = new byte[] { 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14};
-        lineBreakProperties[347] = new byte[] { 14, 14, 14, 14, 14, 14, 14, 14, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 13, 14, 14, 14, 14, 14, 14, 14};
-        lineBreakProperties[348] = new byte[] { 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14};
-        lineBreakProperties[349] = new byte[] { 14, 14, 14, 14, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14};
-        lineBreakProperties[350] = new byte[] { 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14};
-        lineBreakProperties[431] = new byte[] { 14, 14, 14, 14, 14, 14, 14, 14, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
-        lineBreakProperties[432] = new byte[] { 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31};
-        lineBreakProperties[448] = new byte[] { 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35};
-        lineBreakProperties[500] = new byte[] { 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 0, 0, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 0, 0, 0, 0, 0, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16};
-        lineBreakProperties[501] = new byte[] { 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+        lineBreakProperties[336] = new byte[] { 2, 2, 9, 2, 2, 2, 9, 2, 2, 2, 2, 9, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 2, 2, 2, 2, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 28, 2, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 5, 5, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0};
+        lineBreakProperties[337] = new byte[] { 9, 9, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0};
+        lineBreakProperties[338] = new byte[] { 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 4, 4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 0, 0, 0};
+        lineBreakProperties[339] = new byte[] { 9, 9, 9, 9, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 2, 2, 2, 2, 2, 2, 4, 4, 4, 2, 2, 2, 2, 0, 2, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+        lineBreakProperties[340] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 9, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 0, 0, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 0, 0, 2, 4, 4, 4, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 0, 0, 0, 0};
+        lineBreakProperties[341] = new byte[] { 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31, 31, 31, 31, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+        lineBreakProperties[343] = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 4, 9, 9, 0, 0, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 0, 0, 0, 0, 0, 0};
+        lineBreakProperties[344] = new byte[] { 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15};
+        lineBreakProperties[345] = new byte[] { 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 14, 15, 15, 15};
+        lineBreakProperties[346] = new byte[] { 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15};
+        lineBreakProperties[347] = new byte[] { 15, 15, 15, 15, 15, 15, 15, 15, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 14, 15, 15, 15, 15, 15, 15, 15};
+        lineBreakProperties[348] = new byte[] { 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15};
+        lineBreakProperties[349] = new byte[] { 15, 15, 15, 15, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15};
+        lineBreakProperties[350] = new byte[] { 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15};
+        lineBreakProperties[431] = new byte[] { 15, 15, 15, 15, 15, 15, 15, 15, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 0, 0, 0, 0, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 0, 0, 0, 0};
+        lineBreakProperties[432] = new byte[] { 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32};
+        lineBreakProperties[448] = new byte[] { 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36};
         lineBreakProperties[502] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 2, 9, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 0, 2, 0, 2, 2, 0, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
         lineBreakProperties[503] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
-        lineBreakProperties[506] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 26, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
-        lineBreakProperties[507] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 27, 2, 0, 0};
-        lineBreakProperties[508] = new byte[] { 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 18, 8, 8, 18, 18, 11, 11, 26, 8, 17, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 16, 16, 16, 16, 26, 8, 26, 8, 26, 8, 26, 8, 26, 8, 26, 8, 26, 8, 26, 8, 16, 16, 26, 8, 16, 16, 16, 16, 16, 16, 16, 8, 16, 8, 0, 24, 24, 11, 11, 16, 26, 8, 26, 8, 26, 8, 16, 16, 16, 16, 16, 16, 16, 16, 0, 16, 28, 27, 16, 0, 0, 0, 0, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
-        lineBreakProperties[509] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 34};
-        lineBreakProperties[510] = new byte[] { 0, 11, 16, 16, 28, 27, 16, 16, 26, 8, 16, 16, 8, 16, 8, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 24, 24, 16, 16, 16, 11, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 26, 16, 8, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 26, 16, 8, 16, 26, 8, 8, 26, 8, 8, 24, 2, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
-        lineBreakProperties[511] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 24, 24, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 0, 0, 0, 27, 28, 16, 16, 16, 28, 28, 0, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 7, 1, 0, 0};
+        lineBreakProperties[506] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 27, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
+        lineBreakProperties[507] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 28, 2, 0, 0};
+        lineBreakProperties[508] = new byte[] { 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 19, 8, 8, 19, 19, 12, 12, 27, 8, 18, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 17, 17, 17, 27, 8, 27, 8, 27, 8, 27, 8, 27, 8, 27, 8, 27, 8, 27, 8, 17, 17, 27, 8, 17, 17, 17, 17, 17, 17, 17, 8, 17, 8, 0, 25, 25, 12, 12, 17, 27, 8, 27, 8, 27, 8, 17, 17, 17, 17, 17, 17, 17, 17, 0, 17, 29, 28, 17, 0, 0, 0, 0, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
+        lineBreakProperties[509] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 35};
+        lineBreakProperties[510] = new byte[] { 0, 12, 17, 17, 29, 28, 17, 17, 27, 8, 17, 17, 8, 17, 8, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 25, 25, 17, 17, 17, 12, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 27, 17, 8, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 27, 17, 8, 17, 27, 8, 8, 27, 8, 8, 25, 2, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
+        lineBreakProperties[511] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 25, 25, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 0, 0, 0, 28, 29, 17, 17, 17, 29, 29, 0, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 7, 1, 0, 0};
     }
 
     static {
-        init_0();
-        init_1();
+        init0();
+        init1();
         lineBreakProperties[3] = lineBreakProperties[2];
         lineBreakProperties[4] = lineBreakProperties[2];
         lineBreakProperties[8] = lineBreakProperties[2];
-        lineBreakProperties[17] = lineBreakProperties[16];
         lineBreakProperties[41] = lineBreakProperties[2];
         lineBreakProperties[42] = lineBreakProperties[2];
         lineBreakProperties[43] = lineBreakProperties[2];
-        lineBreakProperties[53] = lineBreakProperties[16];
-        lineBreakProperties[57] = lineBreakProperties[16];
+        lineBreakProperties[44] = lineBreakProperties[2];
         lineBreakProperties[58] = lineBreakProperties[2];
         lineBreakProperties[60] = lineBreakProperties[2];
         lineBreakProperties[61] = lineBreakProperties[2];
@@ -225,7 +233,7 @@
         lineBreakProperties[82] = lineBreakProperties[2];
         lineBreakProperties[84] = lineBreakProperties[2];
         lineBreakProperties[85] = lineBreakProperties[2];
-        lineBreakProperties[87] = lineBreakProperties[16];
+        lineBreakProperties[87] = lineBreakProperties[17];
         lineBreakProperties[102] = lineBreakProperties[94];
         lineBreakProperties[103] = lineBreakProperties[94];
         lineBreakProperties[104] = lineBreakProperties[94];
@@ -442,6 +450,7 @@
         lineBreakProperties[316] = lineBreakProperties[94];
         lineBreakProperties[317] = lineBreakProperties[94];
         lineBreakProperties[318] = lineBreakProperties[94];
+        lineBreakProperties[319] = lineBreakProperties[94];
         lineBreakProperties[321] = lineBreakProperties[94];
         lineBreakProperties[322] = lineBreakProperties[94];
         lineBreakProperties[323] = lineBreakProperties[94];
@@ -453,10 +462,7 @@
         lineBreakProperties[330] = lineBreakProperties[2];
         lineBreakProperties[331] = lineBreakProperties[2];
         lineBreakProperties[334] = lineBreakProperties[2];
-        lineBreakProperties[339] = lineBreakProperties[16];
-        lineBreakProperties[341] = lineBreakProperties[16];
-        lineBreakProperties[342] = lineBreakProperties[16];
-        lineBreakProperties[343] = lineBreakProperties[16];
+        lineBreakProperties[342] = lineBreakProperties[17];
         lineBreakProperties[351] = lineBreakProperties[344];
         lineBreakProperties[352] = lineBreakProperties[345];
         lineBreakProperties[353] = lineBreakProperties[346];
@@ -603,6 +609,8 @@
         lineBreakProperties[497] = lineBreakProperties[448];
         lineBreakProperties[498] = lineBreakProperties[94];
         lineBreakProperties[499] = lineBreakProperties[94];
+        lineBreakProperties[500] = lineBreakProperties[94];
+        lineBreakProperties[501] = lineBreakProperties[94];
         lineBreakProperties[504] = lineBreakProperties[2];
         lineBreakProperties[505] = lineBreakProperties[2];
     }
@@ -626,68 +634,70 @@
     /** Linebreak property constant */
     public static final byte LINE_BREAK_PROPERTY_CM = 9;
     /** Linebreak property constant */
-    public static final byte LINE_BREAK_PROPERTY_CR = 10;
+    public static final byte LINE_BREAK_PROPERTY_CP = 10;
     /** Linebreak property constant */
-    public static final byte LINE_BREAK_PROPERTY_EX = 11;
+    public static final byte LINE_BREAK_PROPERTY_CR = 11;
     /** Linebreak property constant */
-    public static final byte LINE_BREAK_PROPERTY_GL = 12;
+    public static final byte LINE_BREAK_PROPERTY_EX = 12;
     /** Linebreak property constant */
-    public static final byte LINE_BREAK_PROPERTY_H2 = 13;
+    public static final byte LINE_BREAK_PROPERTY_GL = 13;
     /** Linebreak property constant */
-    public static final byte LINE_BREAK_PROPERTY_H3 = 14;
+    public static final byte LINE_BREAK_PROPERTY_H2 = 14;
     /** Linebreak property constant */
-    public static final byte LINE_BREAK_PROPERTY_HY = 15;
+    public static final byte LINE_BREAK_PROPERTY_H3 = 15;
     /** Linebreak property constant */
-    public static final byte LINE_BREAK_PROPERTY_ID = 16;
+    public static final byte LINE_BREAK_PROPERTY_HY = 16;
     /** Linebreak property constant */
-    public static final byte LINE_BREAK_PROPERTY_IN = 17;
+    public static final byte LINE_BREAK_PROPERTY_ID = 17;
     /** Linebreak property constant */
-    public static final byte LINE_BREAK_PROPERTY_IS = 18;
+    public static final byte LINE_BREAK_PROPERTY_IN = 18;
     /** Linebreak property constant */
-    public static final byte LINE_BREAK_PROPERTY_JL = 19;
+    public static final byte LINE_BREAK_PROPERTY_IS = 19;
     /** Linebreak property constant */
-    public static final byte LINE_BREAK_PROPERTY_JT = 20;
+    public static final byte LINE_BREAK_PROPERTY_JL = 20;
     /** Linebreak property constant */
-    public static final byte LINE_BREAK_PROPERTY_JV = 21;
+    public static final byte LINE_BREAK_PROPERTY_JT = 21;
     /** Linebreak property constant */
-    public static final byte LINE_BREAK_PROPERTY_LF = 22;
+    public static final byte LINE_BREAK_PROPERTY_JV = 22;
     /** Linebreak property constant */
-    public static final byte LINE_BREAK_PROPERTY_NL = 23;
+    public static final byte LINE_BREAK_PROPERTY_LF = 23;
     /** Linebreak property constant */
-    public static final byte LINE_BREAK_PROPERTY_NS = 24;
+    public static final byte LINE_BREAK_PROPERTY_NL = 24;
     /** Linebreak property constant */
-    public static final byte LINE_BREAK_PROPERTY_NU = 25;
+    public static final byte LINE_BREAK_PROPERTY_NS = 25;
     /** Linebreak property constant */
-    public static final byte LINE_BREAK_PROPERTY_OP = 26;
+    public static final byte LINE_BREAK_PROPERTY_NU = 26;
     /** Linebreak property constant */
-    public static final byte LINE_BREAK_PROPERTY_PO = 27;
+    public static final byte LINE_BREAK_PROPERTY_OP = 27;
     /** Linebreak property constant */
-    public static final byte LINE_BREAK_PROPERTY_PR = 28;
+    public static final byte LINE_BREAK_PROPERTY_PO = 28;
     /** Linebreak property constant */
-    public static final byte LINE_BREAK_PROPERTY_QU = 29;
+    public static final byte LINE_BREAK_PROPERTY_PR = 29;
     /** Linebreak property constant */
-    public static final byte LINE_BREAK_PROPERTY_SA = 30;
+    public static final byte LINE_BREAK_PROPERTY_QU = 30;
     /** Linebreak property constant */
-    public static final byte LINE_BREAK_PROPERTY_SG = 31;
+    public static final byte LINE_BREAK_PROPERTY_SA = 31;
     /** Linebreak property constant */
-    public static final byte LINE_BREAK_PROPERTY_SP = 32;
+    public static final byte LINE_BREAK_PROPERTY_SG = 32;
     /** Linebreak property constant */
-    public static final byte LINE_BREAK_PROPERTY_SY = 33;
+    public static final byte LINE_BREAK_PROPERTY_SP = 33;
     /** Linebreak property constant */
-    public static final byte LINE_BREAK_PROPERTY_WJ = 34;
+    public static final byte LINE_BREAK_PROPERTY_SY = 34;
     /** Linebreak property constant */
-    public static final byte LINE_BREAK_PROPERTY_XX = 35;
+    public static final byte LINE_BREAK_PROPERTY_WJ = 35;
     /** Linebreak property constant */
-    public static final byte LINE_BREAK_PROPERTY_ZW = 36;
+    public static final byte LINE_BREAK_PROPERTY_XX = 36;
+    /** Linebreak property constant */
+    public static final byte LINE_BREAK_PROPERTY_ZW = 37;
 
-    private static String lineBreakPropertyShortNames[] = {"AI", "AL", "B2", "BA", "BB", "BK", "CB", "CL", "CM", "CR", "EX",
-        "GL", "H2", "H3", "HY", "ID", "IN", "IS", "JL", "JT", "JV", "LF", "NL", "NS", "NU", "OP", "PO", "PR", "QU", "SA", "SG", "SP",
-        "SY", "WJ", "XX", "ZW"};
+    private static String[] lineBreakPropertyShortNames = {"AI", "AL", "B2", "BA", "BB", "BK", "CB", "CL", "CM",
+        "CP", "CR", "EX", "GL", "H2", "H3", "HY", "ID", "IN", "IS", "JL", "JT", "JV", "LF", "NL", "NS", "NU", "OP",
+        "PO", "PR", "QU", "SA", "SG", "SP", "SY", "WJ", "XX", "ZW"};
 
-    private static String lineBreakPropertyLongNames[] = {"Ambiguous","Alphabetic","Break_Both","Break_After","Break_Before",
-        "Mandatory_Break","Contingent_Break","Close_Punctuation","Combining_Mark","Carriage_Return","Exclamation",
-        "Glue","H2","H3","Hyphen","Ideographic","Inseparable","Infix_Numeric","JL","JT","JV","Line_Feed","Next_Line",
-        "Nonstarter","Numeric","Open_Punctuation","Postfix_Numeric","Prefix_Numeric","Quotation","Complex_Context",
+    private static String[] lineBreakPropertyLongNames = {"Ambiguous","Alphabetic","Break_Both","Break_After","Break_Before",
+        "Mandatory_Break","Contingent_Break","Close_Punctuation","Combining_Mark","Close_Parenthesis","Carriage_Return",
+        "Exclamation","Glue","H2","H3","Hyphen","Ideographic","Inseparable","Infix_Numeric","JL","JT","JV","Line_Feed",
+        "Next_Line","Nonstarter","Numeric","Open_Punctuation","Postfix_Numeric","Prefix_Numeric","Quotation","Complex_Context",
         "Surrogate","Space","Break_Symbols","Word_Joiner","Unknown","ZWSpace"};
 
     /**
Index: src/java/org/apache/fop/fonts/Typeface.java
===================================================================
--- src/java/org/apache/fop/fonts/Typeface.java	(revision 981406)
+++ src/java/org/apache/fop/fonts/Typeface.java	(working copy)
@@ -142,6 +142,10 @@
     
     /** {@inheritDoc} */
     public String toString() {
-        return getFullName();
+        StringBuffer sbuf = new StringBuffer(super.toString());
+        sbuf.append('{');
+        sbuf.append(getFullName());
+        sbuf.append('}');
+        return sbuf.toString();
     }   
 }
Index: src/java/org/apache/fop/fonts/CustomFont.java
===================================================================
--- src/java/org/apache/fop/fonts/CustomFont.java	(revision 981406)
+++ src/java/org/apache/fop/fonts/CustomFont.java	(working copy)
@@ -58,6 +58,7 @@
     private Map kerning;
 
     private boolean useKerning = true;
+    private boolean useAdvanced = true;
 
     /** {@inheritDoc} */
     public String getFontName() {
@@ -283,6 +284,15 @@
         }
     }
 
+    /**
+     * Used to determine if advanced typographic features are enabled.
+     * By default, this is false, but may be overridden by subclasses.
+     * @return true if enabled.
+     */
+    public boolean isAdvancedEnabled() {
+        return useAdvanced;
+    }
+
     /* ---- MutableFont interface ---- */
 
     /** {@inheritDoc} */
@@ -426,6 +436,13 @@
     }
 
     /**
+     * {@inheritDoc}
+     */
+    public void setAdvancedEnabled(boolean enabled) {
+        this.useAdvanced = enabled;
+    }
+
+    /**
      * Sets the font resolver. Needed for URI resolution.
      * @param resolver the font resolver
      */
Index: src/java/org/apache/fop/fonts/GlyphSubstitutionSubtable.java
===================================================================
--- src/java/org/apache/fop/fonts/GlyphSubstitutionSubtable.java	(revision 0)
+++ src/java/org/apache/fop/fonts/GlyphSubstitutionSubtable.java	(revision 0)
@@ -0,0 +1,62 @@
+/*
+ * 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.fonts;
+
+// CSOFF: LineLengthCheck
+
+/**
+ * The <code>GlyphSubstitutionSubtable</code> implements an abstract base of a glyph substitution subtable,
+ * providing a default implementation of the <code>GlyphSubstitution</code> interface.
+ * @author Glenn Adams
+ */
+public abstract class GlyphSubstitutionSubtable extends GlyphSubtable implements GlyphSubstitution {
+
+    /**
+     * Instantiate a <code>GlyphSubstitutionSubtable</code>.
+     * @param id subtable identifier
+     * @param sequence subtable sequence
+     * @param flags subtable flags
+     * @param format subtable format
+     * @param coverage subtable coverage table
+     */
+    protected GlyphSubstitutionSubtable ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage ) {
+        super ( id, sequence, flags, format, coverage );
+    }
+
+    /** {@inheritDoc} */
+    public int getTableType() {
+        return GlyphTable.GLYPH_TABLE_TYPE_SUBSTITUTION;
+    }
+
+    /** {@inheritDoc} */
+    public String getTypeName() {
+        return GlyphSubstitutionTable.getLookupTypeName ( getType() );
+    }
+
+    /** {@inheritDoc} */
+    public GlyphSequence substitute ( GlyphSequence gs, String script, String language ) {
+        if ( gs == null ) {
+            throw new IllegalArgumentException ( "invalid glyph sequence: must not be null" );
+        } else {
+            return gs;
+        }
+    }
+
+}
Index: src/java/org/apache/fop/fonts/ScriptProcessor.java
===================================================================
--- src/java/org/apache/fop/fonts/ScriptProcessor.java	(revision 0)
+++ src/java/org/apache/fop/fonts/ScriptProcessor.java	(revision 0)
@@ -0,0 +1,101 @@
+/*
+ * 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.fonts;
+
+import java.util.HashMap;
+import java.util.Map;
+
+// CSOFF: InnerAssignmentCheck
+// CSOFF: LineLengthCheck
+
+/**
+ * Abstract script processor base class for which an implementation of the substitution and positioning methods
+ * must be supplied.
+ * @author Glenn Adams
+ */
+public abstract class ScriptProcessor {
+
+    private final String script;
+
+    private static Map processors = new HashMap();
+
+    /**
+     * Instantiate a script processor.
+     * @param script a script identifier
+     */
+    protected ScriptProcessor ( String script ) {
+        if ( ( script == null ) || ( script.length() == 0 ) ) {
+            throw new IllegalArgumentException ( "script must be non-empty string" );
+        } else {
+            this.script = script;
+        }
+    }
+
+    /** @return script identifier */
+    public String getScript() {
+        return script;
+    }
+
+    /**
+     * Perform substitution processing using a specific set of lookup tables.
+     * @param gs an input glyph sequence
+     * @param script a script identifier
+     * @param language a language identifier
+     * @param lookups a mapping from lookup specifications to glyph subtables to use for substitution processing
+     * @return the substituted (output) glyph sequence
+     */
+    public abstract GlyphSequence substitute ( GlyphSequence gs, String script, String language, Map/*<LookupSpec,GlyphSubtable[]>*/ lookups );
+
+    /**
+     * Perform positioning processing using a specific set of lookup tables.
+     * @param gs an input glyph sequence
+     * @param script a script identifier
+     * @param language a language identifier
+     * @param lookups a mapping from lookup specifications to glyph subtables to use for positioning processing
+     * @return the substituted (output) glyph sequence
+     */
+    public abstract int[] position ( GlyphSequence gs, String script, String language, Map/*<LookupSpec,GlyphSubtable[]>*/ lookups );
+
+    /**
+     * Obtain script processor instance associated with specified script.
+     * @param script a script identifier
+     * @return a script processor instance or null if none found
+     */
+    public static synchronized ScriptProcessor getInstance ( String script ) {
+        ScriptProcessor sp = null;
+        assert processors != null;
+        if ( ( sp = (ScriptProcessor) processors.get ( script ) ) == null ) {
+            processors.put ( script, sp = createProcessor ( script ) );
+        }
+        return sp;
+    }
+
+    // [TBD] - rework to provide more configurable binding between script name and script processor constructor
+    private static ScriptProcessor createProcessor ( String script ) {
+        ScriptProcessor sp = null;
+        if ( "arab".equals ( script ) ) {
+            sp = new ArabicScriptProcessor ( script );
+        } else {
+            sp = new DefaultScriptProcessor ( script );
+        }
+        return sp;
+    }
+
+}
Index: src/java/org/apache/fop/fonts/IncompatibleSubtableException.java
===================================================================
--- src/java/org/apache/fop/fonts/IncompatibleSubtableException.java	(revision 0)
+++ src/java/org/apache/fop/fonts/IncompatibleSubtableException.java	(revision 0)
@@ -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.fonts;
+
+/**
+ * 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 IncompatibleSubtableException extends RuntimeException {
+    /**
+     * Instantiate incompatible subtable exception
+     */
+    public IncompatibleSubtableException() {
+        super();
+    }
+    /**
+     * Instantiate incompatible subtable exception
+     * @param message a message string
+     */
+    public IncompatibleSubtableException(String message) {
+        super(message);
+    }
+}
Index: src/java/org/apache/fop/fonts/DefaultScriptProcessor.java
===================================================================
--- src/java/org/apache/fop/fonts/DefaultScriptProcessor.java	(revision 0)
+++ src/java/org/apache/fop/fonts/DefaultScriptProcessor.java	(revision 0)
@@ -0,0 +1,46 @@
+/*
+ * 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.fonts;
+
+import java.util.Map;
+
+// CSOFF: LineLengthCheck
+
+/**
+ * Default script processor, which performs the identity substitution and produces no positioning information.
+ * @author Glenn Adams
+ */
+public class DefaultScriptProcessor extends ScriptProcessor {
+
+    DefaultScriptProcessor ( String script ) {
+        super ( script );
+    }
+
+    /** {@inheritDoc} */
+    public GlyphSequence substitute ( GlyphSequence gs, String script, String language, Map/*<LookupSpec,GlyphSubtable[]>*/ lookups ) {
+        return gs;
+    }
+
+    /** {@inheritDoc} */
+    public int[] position ( GlyphSequence cs, String script, String language, Map/*<LookupSpec,GlyphSubtable[]>*/ lookups ) {
+        return null;
+    }
+
+}
Index: src/java/org/apache/fop/fonts/GlyphPositioningTable.java
===================================================================
--- src/java/org/apache/fop/fonts/GlyphPositioningTable.java	(revision 0)
+++ src/java/org/apache/fop/fonts/GlyphPositioningTable.java	(revision 0)
@@ -0,0 +1,170 @@
+/*
+ * 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.fonts;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+// CSOFF: LineLengthCheck
+
+/**
+ * The <code>GlyphPositioningTable</code> class is a glyph table that implements
+ * <code>GlyphPositioning</code> functionality.
+ * @author Glenn Adams
+ */
+public class GlyphPositioningTable extends GlyphTable implements GlyphPositioning {
+
+    /** single positioning subtable type */
+    public static final int GPOS_LOOKUP_TYPE_SINGLE = 1;
+    /** multiple positioning subtable type */
+    public static final int GPOS_LOOKUP_TYPE_PAIR = 2;
+    /** cursive positioning subtable type */
+    public static final int GPOS_LOOKUP_TYPE_CURSIVE = 3;
+    /** mark to base positioning subtable type */
+    public static final int GPOS_LOOKUP_TYPE_MARK_TO_BASE = 4;
+    /** mark to ligature positioning subtable type */
+    public static final int GPOS_LOOKUP_TYPE_MARK_TO_LIGATURE = 5;
+    /** mark to mark positioning subtable type */
+    public static final int GPOS_LOOKUP_TYPE_MARK_TO_MARK = 6;
+    /** context positioning subtable type */
+    public static final int GPOS_LOOKUP_TYPE_CONTEXT = 7;
+    /** chained context positioning subtable type */
+    public static final int GPOS_LOOKUP_TYPE_CHAINED_CONTEXT = 8;
+    /** extension positioning subtable type */
+    public static final int GPOS_LOOKUP_TYPE_EXTENSION_POSITIONING = 9;
+
+    /**
+     * Instantiate a <code>GlyphPositioningTable</code> object using the specified lookups
+     * and subtables.
+     * @param lookups a map of lookup specifications to subtable identifier strings
+     * @param subtables a list of identified subtables
+     */
+    public GlyphPositioningTable ( Map lookups, List subtables ) {
+        super ( lookups );
+        if ( ( subtables == null ) || ( subtables.size() == 0 ) ) {
+            throw new IllegalArgumentException ( "subtables must be non-empty" );
+        } else {
+            for ( Iterator it = subtables.iterator(); it.hasNext();) {
+                Object o = it.next();
+                if ( o instanceof GlyphPositioningSubtable ) {
+                    addSubtable ( (GlyphSubtable) o );
+                } else {
+                    throw new IllegalArgumentException ( "subtable must be a glyph positioning subtable" );
+                }
+            }
+        }
+    }
+
+    /**
+     * Map a lookup type name to its constant (integer) value.
+     * @param name lookup type name
+     * @return lookup type
+     */
+    public static int getLookupTypeFromName ( String name ) {
+        int t;
+        String s = name.toLowerCase();
+        if ( "single".equals ( s ) ) {
+            t = GPOS_LOOKUP_TYPE_SINGLE;
+        } else if ( "pair".equals ( s ) ) {
+            t = GPOS_LOOKUP_TYPE_PAIR;
+        } else if ( "cursive".equals ( s ) ) {
+            t = GPOS_LOOKUP_TYPE_CURSIVE;
+        } else if ( "marktobase".equals ( s ) ) {
+            t = GPOS_LOOKUP_TYPE_MARK_TO_BASE;
+        } else if ( "marktoligature".equals ( s ) ) {
+            t = GPOS_LOOKUP_TYPE_MARK_TO_LIGATURE;
+        } else if ( "marktomark".equals ( s ) ) {
+            t = GPOS_LOOKUP_TYPE_MARK_TO_MARK;
+        } else if ( "context".equals ( s ) ) {
+            t = GPOS_LOOKUP_TYPE_CONTEXT;
+        } else if ( "chainedcontext".equals ( s ) ) {
+            t = GPOS_LOOKUP_TYPE_CHAINED_CONTEXT;
+        } else if ( "extensionpositioning".equals ( s ) ) {
+            t = GPOS_LOOKUP_TYPE_EXTENSION_POSITIONING;
+        } else {
+            t = -1;
+        }
+        return t;
+    }
+
+    /**
+     * Map a lookup type constant (integer) value to its name.
+     * @param type lookup type
+     * @return lookup type name
+     */
+    public static String getLookupTypeName ( int type ) {
+        String tn;
+        switch ( type ) {
+        case GPOS_LOOKUP_TYPE_SINGLE:
+            tn = "single";
+            break;
+        case GPOS_LOOKUP_TYPE_PAIR:
+            tn = "pair";
+            break;
+        case GPOS_LOOKUP_TYPE_CURSIVE:
+            tn = "cursive";
+            break;
+        case GPOS_LOOKUP_TYPE_MARK_TO_BASE:
+            tn = "marktobase";
+            break;
+        case GPOS_LOOKUP_TYPE_MARK_TO_LIGATURE:
+            tn = "marktoligature";
+            break;
+        case GPOS_LOOKUP_TYPE_MARK_TO_MARK:
+            tn = "marktomark";
+            break;
+        case GPOS_LOOKUP_TYPE_CONTEXT:
+            tn = "context";
+            break;
+        case GPOS_LOOKUP_TYPE_CHAINED_CONTEXT:
+            tn = "chainedcontext";
+            break;
+        case GPOS_LOOKUP_TYPE_EXTENSION_POSITIONING:
+            tn = "extensionpositioning";
+            break;
+        default:
+            tn = "unknown";
+            break;
+        }
+        return tn;
+    }
+
+    /**
+     * Create a positioning subtable according to the specified arguments.
+     * @param type subtable type
+     * @param id subtable identifier
+     * @param sequence subtable sequence
+     * @param flags subtable flags
+     * @param format subtable format
+     * @param coverage subtable coverage table
+     * @param entries subtable entries
+     * @return a glyph subtable instance
+     */
+    public static GlyphSubtable createSubtable ( int type, String id, int sequence, int flags, int format, List coverage, List entries ) {
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    public int[] position ( GlyphSequence gs, String script, String language ) {
+        return null;
+    }
+
+}
Index: src/java/org/apache/fop/fonts/GlyphContextTester.java
===================================================================
--- src/java/org/apache/fop/fonts/GlyphContextTester.java	(revision 0)
+++ src/java/org/apache/fop/fonts/GlyphContextTester.java	(revision 0)
@@ -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.fonts;
+
+/**
+ * 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 gs glyph sequence to test
+     * @param ca character association defining the context of test
+     * @return true if test is satisfied
+     */
+    boolean test ( GlyphSequence gs, GlyphSequence.CharAssociation ca );
+
+}
Index: src/java/org/apache/fop/fonts/MutableFont.java
===================================================================
--- src/java/org/apache/fop/fonts/MutableFont.java	(revision 981406)
+++ src/java/org/apache/fop/fonts/MutableFont.java	(working copy)
@@ -133,6 +133,12 @@
     void setKerningEnabled(boolean enabled);
 
     /**
+     * Enables/disabled advanced typographic features.
+     * @param enabled true if advanced typographic features should be enabled if available
+     */
+    void setAdvancedEnabled(boolean enabled);
+
+    /**
      * Adds an entry to the kerning table.
      * @param key Kerning key
      * @param value Kerning value
Index: src/java/org/apache/fop/fonts/GlyphSubstitution.java
===================================================================
--- src/java/org/apache/fop/fonts/GlyphSubstitution.java	(revision 0)
+++ src/java/org/apache/fop/fonts/GlyphSubstitution.java	(revision 0)
@@ -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.fonts;
+
+// CSOFF: LineLengthCheck
+
+/**
+ * The <code>GlyphSubstitution</code> interface is implemented by a font related object
+ * that supports the determination of glyph substitution information based on script and
+ * language of the corresponding character content.
+ * @author Glenn Adams
+ */
+public interface GlyphSubstitution {
+
+    /**
+     * Perform glyph substitutions. If no substitution applies, then returns the unmodified input sequence.
+     * @param gs sequence to map to output glyph sequence
+     * @param script the script associated with the characters corresponding to the glyph sequence
+     * @param language the language associated with the characters corresponding to the glyph sequence
+     * @return resulting glyph sequence, where each 'glyph' in the returned sequence has been mapped
+     * (or not) by substitution
+     */
+    GlyphSequence substitute ( GlyphSequence gs, String script, String language );
+
+}
Index: src/java/org/apache/fop/fonts/DiscontinuousAssociationException.java
===================================================================
--- src/java/org/apache/fop/fonts/DiscontinuousAssociationException.java	(revision 0)
+++ src/java/org/apache/fop/fonts/DiscontinuousAssociationException.java	(revision 0)
@@ -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.fonts;
+
+/**
+ * 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);
+    }
+}
Index: src/java/org/apache/fop/fonts/apps/TTFReader.java
===================================================================
--- src/java/org/apache/fop/fonts/apps/TTFReader.java	(revision 981406)
+++ src/java/org/apache/fop/fonts/apps/TTFReader.java	(working copy)
@@ -21,6 +21,7 @@
 
 import java.io.IOException;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -29,6 +30,11 @@
 import org.apache.commons.logging.LogFactory;
 import org.apache.fop.Version;
 import org.apache.fop.fonts.FontUtil;
+import org.apache.fop.fonts.GlyphCoverageTable;
+import org.apache.fop.fonts.GlyphPositioningTable;
+import org.apache.fop.fonts.GlyphSubstitutionTable;
+import org.apache.fop.fonts.GlyphSubtable;
+import org.apache.fop.fonts.GlyphTable;
 import org.apache.fop.fonts.truetype.FontFileReader;
 import org.apache.fop.fonts.truetype.TTFCmapEntry;
 import org.apache.fop.fonts.truetype.TTFFile;
@@ -38,6 +44,9 @@
 import org.xml.sax.Attributes;
 import org.xml.sax.SAXException;
 
+// CSOFF: InnerAssignmentCheck
+// CSOFF: LineLengthCheck
+
 /**
  * A tool which reads TTF files and generates
  * XML font metrics file for use in FOP.
@@ -350,6 +359,8 @@
 
         generateDOM4Kerning(root, ttf, isCid);
 
+        generateDOM4ScriptExtensions(root, ttf, isCid);
+
         return doc;
     }
 
@@ -463,7 +474,288 @@
         }
     }
 
+    private void generateDOM4LookupReferences ( Element parent, GlyphSubstitutionTable gsub, GlyphTable.LookupSpec[] lookups ) {
+        boolean usedLookup = false;
+        Document d = parent.getOwnerDocument();
+        for ( int i = 0, m = lookups.length; i < m; i++ ) {
+            GlyphTable.LookupSpec ls = lookups [ i ];
+            GlyphSubtable[] sta = gsub.findSubtables ( ls );
+            for ( int j = 0, n = sta.length; j < n; j++ ) {
+                GlyphSubtable st = sta [ j ];
+                Element e = d.createElement("use-lookup");
+                e.setAttribute ( "ref", st.getID() );
+                parent.appendChild(e);
+            }
+        }
+    }
 
+    private void generateDOM4Features ( Element parent, GlyphSubstitutionTable gsub, GlyphTable.LookupSpec[] lookups, String scriptTag, String languageTag ) {
+        Document d = parent.getOwnerDocument();
+        Set features = new java.util.LinkedHashSet();
+        for ( int i = 0, n = lookups.length; i < n; i++ ) {
+            GlyphTable.LookupSpec ls = lookups [ i ];
+            features.add ( ls.getFeature() );
+        }
+        for ( Iterator it = features.iterator(); it.hasNext();) {
+            String featureTag = (String) it.next();
+            Element e = d.createElement("feature");
+            e.setAttribute ( "tag", featureTag );
+            generateDOM4LookupReferences ( e, gsub, gsub.matchLookupSpecs ( scriptTag, languageTag, featureTag ) );
+            if ( e.hasChildNodes() ) {
+                parent.appendChild(e);
+            }
+        }
+    }
+
+    private void generateDOM4Languages ( Element parent, GlyphSubstitutionTable gsub, GlyphTable.LookupSpec[] lookups, String scriptTag ) {
+        Document d = parent.getOwnerDocument();
+        Set languages = new java.util.LinkedHashSet();
+        for ( int i = 0, n = lookups.length; i < n; i++ ) {
+            GlyphTable.LookupSpec ls = lookups [ i ];
+            languages.add ( ls.getLanguage() );
+        }
+        for ( Iterator it = languages.iterator(); it.hasNext();) {
+            String languageTag = (String) it.next();
+            Element e = d.createElement("lang");
+            e.setAttribute ( "tag", languageTag );
+            generateDOM4Features ( e, gsub, gsub.matchLookupSpecs ( scriptTag, languageTag, "*" ), scriptTag, languageTag );
+            parent.appendChild(e);
+        }
+    }
+
+    private void generateDOM4Scripts ( Element parent, GlyphSubstitutionTable gsub, GlyphTable.LookupSpec[] lookups ) {
+        Document d = parent.getOwnerDocument();
+        Set scripts = new java.util.LinkedHashSet();
+        for ( int i = 0, n = lookups.length; i < n; i++ ) {
+            GlyphTable.LookupSpec ls = lookups [ i ];
+            scripts.add ( ls.getScript() );
+        }
+        for ( Iterator it = scripts.iterator(); it.hasNext();) {
+            String scriptTag = (String) it.next();
+            Element e = d.createElement("script");
+            e.setAttribute ( "tag", scriptTag );
+            generateDOM4Languages ( e, gsub, gsub.matchLookupSpecs ( scriptTag, "*", "*" ), scriptTag );
+            parent.appendChild(e);
+        }
+    }
+
+    private void generateDOM4Coverage ( Element parent, GlyphCoverageTable coverage ) {
+        Document d = parent.getOwnerDocument();
+        Element e = d.createElement("coverage");
+        int type = coverage.getType();
+        e.setAttribute ( "format", Integer.toString ( type ) );
+        List entries = coverage.getEntries();
+        if ( type == GlyphCoverageTable.GLYPH_COVERAGE_TYPE_MAPPED ) {
+            for ( Iterator it = entries.iterator(); it.hasNext();) {
+                Integer gid = (Integer) it.next();
+                if ( gid != null ) {
+                    Element g = d.createElement("gid");
+                    g.appendChild(d.createTextNode(gid.toString()));
+                    e.appendChild(g);
+                }
+            }
+        } else if ( type == GlyphCoverageTable.GLYPH_COVERAGE_TYPE_RANGE ) {
+            for ( Iterator it = entries.iterator(); it.hasNext();) {
+                GlyphCoverageTable.CoverageRange cr = (GlyphCoverageTable.CoverageRange) it.next();
+                if ( cr != null ) {
+                    Element r = d.createElement("range");
+                    r.setAttribute ( "gs", Integer.toString(cr.getStart()) );
+                    r.setAttribute ( "ge", Integer.toString(cr.getEnd()) );
+                    r.setAttribute ( "ci", Integer.toString(cr.getIndex()) );
+                    e.appendChild(r);
+                }
+            }
+        }
+        parent.appendChild(e);
+    }
+
+    private void generateDOM4GSUBSingleEntries ( Element parent, List entries, int type, int format ) {
+        if ( entries.size() > 0 ) {
+            Document d = parent.getOwnerDocument();
+            for ( Iterator it = entries.iterator(); it.hasNext();) {
+                Integer gid = (Integer) it.next();
+                if ( gid != null ) {
+                    Element e = d.createElement("gid");
+                    e.appendChild(d.createTextNode(gid.toString()));
+                    parent.appendChild(e);
+                }
+            }
+        }
+    }
+
+    private void generateDOM4GSUBMultipleEntries ( Element parent, List entries, int type, int format ) {
+        // [TBD] - implement me
+    }
+
+    private void generateDOM4GSUBAlternateEntries ( Element parent, List entries, int type, int format ) {
+        // [TBD] - implement me
+    }
+
+    private void generateDOM4LigatureSet ( Element parent, GlyphSubstitutionTable.LigatureSet lset ) {
+        Document d = parent.getOwnerDocument();
+        Element e = d.createElement("ligs");
+        GlyphSubstitutionTable.Ligature[] la = lset.getLigatures();
+        for ( int i = 0, m = la.length; i < m; i++ ) {
+            GlyphSubstitutionTable.Ligature l = la [ i ];
+            Element le = d.createElement("lig");
+            le.setAttribute ( "gid", Integer.toString( l.getLigature() ) );
+            int[] ca = l.getComponents();
+            StringBuffer sb = new StringBuffer();
+            for ( int j = 0, n = ca.length; j < n; j++ ) {
+                if ( j > 0 ) {
+                    sb.append ( ' ' );
+                }
+                sb.append ( Integer.toString ( ca [ j ] ) );
+            }
+            if ( sb.length() > 0 ) {
+                le.appendChild ( d.createTextNode ( sb.toString() ) );
+                e.appendChild ( le );
+            }
+        }
+        parent.appendChild(e);
+    }
+
+    private void generateDOM4GSUBLigatureEntries ( Element parent, List entries, int type, int format ) {
+        if ( entries.size() > 0 ) {
+            for ( Iterator it = entries.iterator(); it.hasNext();) {
+                GlyphSubstitutionTable.LigatureSet lset = (GlyphSubstitutionTable.LigatureSet) it.next();
+                if ( lset != null ) {
+                    generateDOM4LigatureSet ( parent, lset );
+                }
+            }
+        }
+    }
+
+    private void generateDOM4GSUBContextEntries ( Element parent, List entries, int type, int format ) {
+        // [TBD] - implement me
+    }
+
+    private void generateDOM4GSUBChainingContextEntries ( Element parent, List entries, int type, int format ) {
+        // [TBD] - implement me
+    }
+
+    private void generateDOM4GSUBExtensionEntries ( Element parent, List entries, int type, int format ) {
+        // [TBD] - implement me
+    }
+
+    private void generateDOM4GSUBReverseChainingSingleEntries ( Element parent, List entries, int type, int format ) {
+        // [TBD] - implement me
+    }
+
+    private void generateDOM4GSUBEntries ( Element parent, List entries, int type, int format ) {
+        Document d = parent.getOwnerDocument();
+        Element e = d.createElement("entries");
+        switch ( type ) {
+        case GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_SINGLE:
+            generateDOM4GSUBSingleEntries ( e, entries, type, format );
+            break;
+        case GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_MULTIPLE:
+            generateDOM4GSUBMultipleEntries ( e, entries, type, format );
+            break;
+        case GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_ALTERNATE:
+            generateDOM4GSUBAlternateEntries ( e, entries, type, format );
+            break;
+        case GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_LIGATURE:
+            generateDOM4GSUBLigatureEntries ( e, entries, type, format );
+            break;
+        case GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_CONTEXT:
+            generateDOM4GSUBContextEntries ( e, entries, type, format );
+            break;
+        case GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_CHAINING_CONTEXT:
+            generateDOM4GSUBChainingContextEntries ( e, entries, type, format );
+            break;
+        case GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_EXTENSION_SUBSTITUTION:
+            generateDOM4GSUBExtensionEntries ( e, entries, type, format );
+            break;
+        case GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_REVERSE_CHAINING_CONTEXT_SINGLE:
+            generateDOM4GSUBReverseChainingSingleEntries ( e, entries, type, format );
+            break;
+        default:
+            break;
+        }
+        parent.appendChild(e);
+    }
+
+    private void generateDOM4GSUBSubtable ( Element parent, GlyphSubstitutionTable gsub, GlyphSubtable st ) {
+        Document d = parent.getOwnerDocument();
+        Element e = d.createElement("lst");
+        e.setAttribute ( "format", Integer.toString ( st.getFormat() ) );
+        generateDOM4Coverage ( e, st.getCoverage() );
+        generateDOM4GSUBEntries ( e, st.getEntries(), st.getType(), st.getFormat() );
+        parent.appendChild(e);
+    }
+
+    private void generateDOM4GSUBSubtables ( Element parent, GlyphSubstitutionTable gsub, GlyphSubtable[] subtables ) {
+        if ( subtables.length > 0 ) {
+            Document d = parent.getOwnerDocument();
+            Element e = d.createElement("lookup");
+            GlyphSubtable st0 = subtables[0];
+            int st0Type = st0.getType();
+            e.setAttribute ( "id", st0.getID() );
+            e.setAttribute ( "type", st0.getTypeName() );
+            for ( int i = 0, n = subtables.length; i < n; i++ ) {
+                GlyphSubtable st = subtables[i];
+                if ( st.getType() == st0Type ) {
+                    generateDOM4GSUBSubtable ( e, gsub, st );
+                }
+            }
+            parent.appendChild(e);
+        }
+    }
+
+    private GlyphSubtable[] matchSubtables ( GlyphSubtable[] subtables, String id ) {
+        List matches = new java.util.ArrayList();
+        for ( int i = 0, n = subtables.length; i < n; i++ ) {
+            GlyphSubtable st =  subtables [ i ];
+            if ( st.getID().equals ( id ) ) {
+                matches.add ( st );
+            }
+        }
+        return (GlyphSubtable[]) matches.toArray ( new GlyphSubtable[matches.size()] );
+    }
+
+    private void generateDOM4GSUBLookups ( Element parent, GlyphSubstitutionTable gsub, GlyphSubtable[] subtables ) {
+        Set lus = new java.util.LinkedHashSet();
+        for ( int i = 0, n = subtables.length; i < n; i++ ) {
+            GlyphSubtable st =  subtables [ i ];
+            lus.add ( st.getID() );
+        }
+        for ( Iterator it = lus.iterator(); it.hasNext();) {
+            String id = (String) it.next();
+            generateDOM4GSUBSubtables ( parent, gsub, matchSubtables ( subtables, id ) );
+        }
+    }
+
+    private void generateDOM4GSUB ( Element parent, GlyphSubstitutionTable gsub ) {
+        Document d = parent.getOwnerDocument();
+        Element e = d.createElement("gsub");
+        parent.appendChild(e);
+        generateDOM4Scripts ( e, gsub, gsub.getLookups() );
+        generateDOM4GSUBLookups ( e, gsub, gsub.getSubtables() );
+    }
+
+    private void generateDOM4GPOS ( Element parent, GlyphPositioningTable gpos ) {
+        Document d = parent.getOwnerDocument();
+        Element te = d.createElement("gpos");
+        parent.appendChild(te);
+    }
+
+    private void generateDOM4ScriptExtensions(Element parent, TTFFile ttf, boolean isCid) {
+        if ( ttf.hasScriptExtension() ) {
+            Document d = parent.getOwnerDocument();
+            Element se = d.createElement("script-extras");
+            parent.appendChild(se);
+            GlyphSubstitutionTable st;
+            if ( ( st = ttf.getGSUB() ) != null ) {
+                generateDOM4GSUB ( se, st );
+            }
+            GlyphPositioningTable pt;
+            if ( ( pt = ttf.getGPOS() ) != null ) {
+                generateDOM4GPOS ( se, pt );
+            }
+        }
+    }
+
     /**
      * Bugzilla 40739, check that attr has a metrics-version attribute
      * compatible with ours.
Index: src/java/org/apache/fop/fonts/CIDFontType.java
===================================================================
--- src/java/org/apache/fop/fonts/CIDFontType.java	(revision 981406)
+++ src/java/org/apache/fop/fonts/CIDFontType.java	(working copy)
@@ -38,6 +38,8 @@
 
 
     /**
+     * @param name a CID font type identifier
+     * @param value a value
      * @see org.apache.avalon.framework.Enum#Enum(String)
      */
     protected CIDFontType(String name, int value) {
Index: src/java/org/apache/fop/fonts/GlyphTable.java
===================================================================
--- src/java/org/apache/fop/fonts/GlyphTable.java	(revision 0)
+++ src/java/org/apache/fop/fonts/GlyphTable.java	(revision 0)
@@ -0,0 +1,289 @@
+/*
+ * 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.fonts;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+// CSOFF: NoWhitespaceAfterCheck
+// CSOFF: InnerAssignmentCheck
+// CSOFF: SimplifyBooleanReturnCheck
+// CSOFF: LineLengthCheck
+
+/**
+ * Base class for all advanced typographic glyph tables.
+ * @author Glenn Adams
+ */
+public class GlyphTable {
+
+    /** substitution glyph table type */
+    public static final int GLYPH_TABLE_TYPE_SUBSTITUTION = 1;
+    /** positioning glyph table type */
+    public static final int GLYPH_TABLE_TYPE_POSITIONING = 2;
+    /** justification glyph table type */
+    public static final int GLYPH_TABLE_TYPE_JUSTIFICATION = 3;
+    /** baseline glyph table type */
+    public static final int GLYPH_TABLE_TYPE_BASELINE = 4;
+    /** definition glyph table type */
+    public static final int GLYPH_TABLE_TYPE_DEFINITION = 5;
+
+    // map from lookup specs to lists of strings, each naming a subtable
+    private Map /*<LookupSpec,List>*/ lookups;
+
+    // map from subtable names to glyph subtables
+    private Map /*<String,GlyphSubtable>*/ subtables;
+
+    /**
+     * Instantiate glyph table with specified lookups.
+     * @param lookups map from lookup specs to lookup tables
+     */
+    public GlyphTable ( Map /*<LookupSpec,List>*/ lookups ) {
+        if ( ( lookups == null ) || ( lookups.size() == 0 ) ) {
+            throw new IllegalArgumentException ( "lookups must be non-empty map" );
+        } else {
+            this.lookups = lookups;
+            this.subtables = new LinkedHashMap();
+        }
+    }
+
+    /**
+     * Obain array of lookup specifications.
+     * @return (possibly empty) array of all lookup specifications
+     */
+    public LookupSpec[] getLookups() {
+        return matchLookupSpecs ( "*", "*", "*" );
+    }
+
+    /**
+     * Obain array of lookup subtables.
+     * @return (possibly empty) array of all lookup subtables
+     */
+    public GlyphSubtable[] getSubtables() {
+        Collection values = subtables.values();
+        return (GlyphSubtable[]) values.toArray ( new GlyphSubtable [ values.size() ] );
+    }
+
+    /**
+     * Add a subtable.
+     * @param subtable a (non-null) glyph subtable
+     */
+    public void addSubtable ( GlyphSubtable subtable ) {
+        subtables.put ( subtable.getID(), subtable );
+    }
+
+    /**
+     * Match lookup specifications according to <script,language,feature> tuple, where
+     * '*' is a wildcard for a tuple component.
+     * @param script a script identifier
+     * @param language a language identifier
+     * @param feature a feature identifier
+     * @return a (possibly empty) array of matching lookup specifications
+     */
+    public LookupSpec[] matchLookupSpecs ( String script, String language, String feature ) {
+        Set/*<LookupSpec>*/ keys = lookups.keySet();
+        List matches = new ArrayList();
+        for ( Iterator it = keys.iterator(); it.hasNext();) {
+            LookupSpec ls = (LookupSpec) it.next();
+            if ( ! "*".equals(script) ) {
+                if ( ! ls.getScript().equals ( script ) ) {
+                    continue;
+                }
+            }
+            if ( ! "*".equals(language) ) {
+                if ( ! ls.getLanguage().equals ( language ) ) {
+                    continue;
+                }
+            }
+            if ( ! "*".equals(feature) ) {
+                if ( ! ls.getFeature().equals ( feature ) ) {
+                    continue;
+                }
+            }
+            matches.add ( ls );
+        }
+        return (LookupSpec[]) matches.toArray ( new LookupSpec [ matches.size() ] );
+    }
+
+    /**
+     * Match lookup specifications according to <script,language,feature> tuple, where
+     * '*' is a wildcard for a tuple component.
+     * @param script a script identifier
+     * @param language a language identifier
+     * @param feature a feature identifier
+     * @return a (possibly empty) map of matching lookup specifications and their corresponding subtables
+     */
+    public Map/*<LookupSpec,GlyphSubtable[]>*/ matchLookups ( String script, String language, String feature ) {
+        LookupSpec[] lsa = matchLookupSpecs ( script, language, feature );
+        Map lm = new LinkedHashMap();
+        for ( int i = 0, n = lsa.length; i < n; i++ ) {
+            lm.put ( lsa [ i ], findSubtables ( lsa [ i ] ) );
+        }
+        return lm;
+    }
+
+    /**
+     * Find glyph subtables that match a secific lookup specification.
+     * @param ls a (non-null) lookup specification
+     * @return a (possibly empty) array of subtables whose lookup specification matches the specified lookup spec
+     */
+    public GlyphSubtable[] findSubtables ( LookupSpec ls ) {
+        GlyphSubtable[] staEmpty = new GlyphSubtable [ 0 ];
+        List ids;
+        if ( ( ids = (List) lookups.get ( ls ) ) != null ) {
+            List stl = new ArrayList();
+            for ( Iterator it = ids.iterator(); it.hasNext();) {
+                String id = (String) it.next();
+                GlyphSubtable st;
+                if ( ( st = (GlyphSubtable) subtables.get ( id ) ) != null ) {
+                    stl.add ( st );
+                }
+            }
+            return (GlyphSubtable[]) stl.toArray ( staEmpty );
+        } else {
+            return staEmpty;
+        }
+    }
+
+    /**
+     * Obtain glyph table type from name.
+     * @param name of table type to map to type value
+     * @return glyph table type (as an integer constant)
+     */
+    public static int getTableTypeFromName ( String name ) {
+        int t;
+        String s = name.toLowerCase();
+        if ( "gsub".equals ( s ) ) {
+            t = GLYPH_TABLE_TYPE_SUBSTITUTION;
+        } else if ( "gpos".equals ( s ) ) {
+            t = GLYPH_TABLE_TYPE_POSITIONING;
+        } else if ( "jstf".equals ( s ) ) {
+            t = GLYPH_TABLE_TYPE_JUSTIFICATION;
+        } else if ( "base".equals ( s ) ) {
+            t = GLYPH_TABLE_TYPE_BASELINE;
+        } else if ( "gdef".equals ( s ) ) {
+            t = GLYPH_TABLE_TYPE_DEFINITION;
+        } else {
+            t = -1;
+        }
+        return t;
+    }
+
+    /** {@inheritDoc} */
+    public String toString() {
+        StringBuffer sb = new StringBuffer(super.toString());
+        sb.append("{");
+        sb.append("lookups={");
+        sb.append(lookups.toString());
+        sb.append("},subtables={");
+        sb.append(subtables.toString());
+        sb.append("}}");
+        return sb.toString();
+    }
+
+    /**
+     * A structure class encapsulating a lookup specification as a <script,language,feature> tuple.
+     */
+    public static class LookupSpec {
+
+        private final String script;
+        private final String language;
+        private final String feature;
+
+        /**
+         * Instantiate lookup spec.
+         * @param script a script identifier
+         * @param language a language identifier
+         * @param feature a feature identifier
+         */
+        public LookupSpec ( String script, String language, String feature ) {
+            if ( ( script == null ) || ( script.length() == 0 ) ) {
+                throw new IllegalArgumentException ( "script must be non-empty string" );
+            } else if ( ( language == null ) || ( language.length() == 0 ) ) {
+                throw new IllegalArgumentException ( "language must be non-empty string" );
+            } else if ( ( feature == null ) || ( feature.length() == 0 ) ) {
+                throw new IllegalArgumentException ( "feature must be non-empty string" );
+            } else {
+                this.script = script;
+                this.language = language;
+                this.feature = feature;
+            }
+        }
+
+        /** @return script identifier */
+        public String getScript() {
+            return script;
+        }
+
+        /** @return language identifier */
+        public String getLanguage() {
+            return language;
+        }
+
+        /** @return feature identifier  */
+        public String getFeature() {
+            return feature;
+        }
+
+        /** {@inheritDoc} */
+        public int hashCode() {
+            int h = 0;
+            h = 31 * h + script.hashCode();
+            h = 31 * h + language.hashCode();
+            h = 31 * h + feature.hashCode();
+            return h;
+        }
+
+        /** {@inheritDoc} */
+        public boolean equals ( Object o ) {
+            if ( o instanceof LookupSpec ) {
+                LookupSpec l = (LookupSpec) o;
+                if ( ! l.script.equals ( script ) ) {
+                    return false;
+                } else if ( ! l.language.equals ( language ) ) {
+                    return false;
+                } else if ( ! l.feature.equals ( feature ) ) {
+                    return false;
+                } else {
+                    return true;
+                }
+            } else {
+                return false;
+            }
+        }
+
+        /** {@inheritDoc} */
+        public String toString() {
+            StringBuffer sb = new StringBuffer(super.toString());
+            sb.append("{");
+            sb.append("<'" + script + "'");
+            sb.append(",'" + language + "'");
+            sb.append(",'" + feature + "'");
+            sb.append(">}");
+            return sb.toString();
+        }
+
+    }
+
+}
Index: src/java/org/apache/fop/fonts/FontUtil.java
===================================================================
--- src/java/org/apache/fop/fonts/FontUtil.java	(revision 981406)
+++ src/java/org/apache/fop/fonts/FontUtil.java	(working copy)
@@ -23,8 +23,11 @@
 /**
  * Font utilities.
  */
-public class FontUtil {
+public final class FontUtil {
 
+    private FontUtil() {
+    }
+
     /**
      * Parses an CSS2 (SVG and XSL-FO) font weight (normal, bold, 100-900) to
      * an integer.
Index: src/java/org/apache/fop/fonts/MultiByteFont.java
===================================================================
--- src/java/org/apache/fop/fonts/MultiByteFont.java	(revision 981406)
+++ src/java/org/apache/fop/fonts/MultiByteFont.java	(working copy)
@@ -20,6 +20,7 @@
 package org.apache.fop.fonts;
 
 //Java
+import java.nio.CharBuffer;
 import java.text.DecimalFormat;
 import java.util.Map;
 
@@ -27,7 +28,7 @@
 /**
  * Generic MultiByte (CID) font
  */
-public class MultiByteFont extends CIDFont {
+public class MultiByteFont extends CIDFont implements Substitutable, Positionable {
 
     private static int uniqueCounter = -1;
 
@@ -44,6 +45,10 @@
     /** A map from Unicode indices to glyph indices */
     private BFEntry[] bfentries = null;
 
+    /* advanced typographic support */
+    private GlyphSubstitutionTable gsub;
+    private GlyphPositioningTable gpos;
+
     /**
      * Default constructor
      */
@@ -157,8 +162,9 @@
      * @param c the Unicode character index
      * @return the glyph index (or 0 if the glyph is not available)
      */
-    private int findGlyphIndex(char c) {
-        int idx = (int)c;
+    // [TBD] - needs optimization
+    private int findGlyphIndex(int c) {
+        int idx = c;
         int retIdx = SingleByteEncoding.NOT_FOUND_CODE_POINT;
 
         for (int i = 0; (i < bfentries.length) && retIdx == 0; i++) {
@@ -173,6 +179,29 @@
         return retIdx;
     }
 
+    /**
+     * Returns the Unicode scalar value that corresponds to the glyph index. If more than
+     * one correspondence exists, then the first one is returned (ordered by bfentries[]).
+     * If the glyph index is Typeface.NOT_FOUND, then returns the Unicode replacement
+     * character (0x00FFFD).
+     * @param gi glyph index
+     * @returns unicode scalar value
+     */
+    // [TBD] - needs optimization
+    private int findCharacterFromGlyphIndex ( int gi ) {
+        int cc = 0;
+        for ( int i = 0, n = bfentries.length; i < n; i++ ) {
+            BFEntry be = bfentries [ i ];
+            int s = be.getGlyphStartIndex();
+            int e = s + ( be.getUnicodeEnd() - be.getUnicodeStart() );
+            if ( ( gi >= s ) && ( gi <= e ) ) {
+                cc = be.getUnicodeStart() + ( gi - s );
+                break;
+            }
+        }
+        return cc;
+    }
+
     /** {@inheritDoc} */
     public char mapChar(char c) {
         notifyMapOperation();
@@ -248,5 +277,153 @@
         }
         return subset.getSubsetChars();
     }
+
+    /**
+     * Establishes the glyph substitution table.
+     * @param gsub the glyph substitution table to be used by this font
+     */
+    public void setGSUB ( GlyphSubstitutionTable gsub ) {
+        if ( ( this.gsub == null ) || ( gsub == null ) ) {
+            this.gsub = gsub;
+        } else {
+            throw new IllegalStateException ( "font already associated with GSUB table" );
+        }
+    }
+
+    /**
+     * Obtain glyph substitution table.
+     * @return glyph substitution table or null if none is associated with font
+     */
+    public GlyphSubstitutionTable getGSUB() {
+        return gsub;
+    }
+
+    /**
+     * Establishes the glyph positioning table.
+     * @param gpos the glyph positioning table to be used by this font
+     */
+    public void setGPOS ( GlyphPositioningTable gpos ) {
+        if ( ( this.gpos == null ) || ( gpos == null ) ) {
+            this.gpos = gpos;
+        } else {
+            throw new IllegalStateException ( "font already associated with GPOS table" );
+        }
+    }
+
+    /**
+     * Obtain glyph positioning table.
+     * @return glyph positioning table or null if none is associated with font
+     */
+    public GlyphPositioningTable getGPOS() {
+        return gpos;
+    }
+
+    /** {@inheritDoc} */
+    public boolean performsSubstitution() {
+        return gsub != null;
+    }
+
+    /** {@inheritDoc} */
+    public CharSequence performSubstitution ( CharSequence cs, String script, String language ) {
+        if ( gsub != null ) {
+            GlyphSequence igs = mapCharsToGlyphs ( cs );
+            GlyphSequence ogs = gsub.substitute ( igs, script, language );
+            CharSequence ocs = mapGlyphsToChars ( ogs );
+            return ocs;
+        } else {
+            return cs;
+        }
+    }
+
+    /** {@inheritDoc} */
+    public boolean performsPositioning() {
+        return gpos != null;
+    }
+
+    /** {@inheritDoc} */
+    public int[] performPositioning ( CharSequence cs, String script, String language ) {
+        if ( gpos != null ) {
+            return gpos.position ( mapCharsToGlyphs ( cs ), script, language );
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Map sequence CS, comprising a sequence of UTF-16 encoded Unicode Code Points, to
+     * an output character sequence GS, comprising a sequence of Glyph Indices. N.B. Unlike
+     * mapChar(), this method does not make use of embedded subset encodings.
+     * @param cs a CharSequence containing UTF-16 encoded Unicode characters
+     * @returns a CharSequence containing glyph indices
+     */
+    private GlyphSequence mapCharsToGlyphs ( CharSequence cs ) {
+        CharBuffer cb = CharBuffer.allocate ( cs.length() );
+        int gi, giMissing = findGlyphIndex ( Typeface.NOT_FOUND );
+        for ( int i = 0, n = cs.length(); i < n; i++ ) {
+            int cc = cs.charAt ( i );
+            if ( ( cc >= 0xD800 ) && ( cc < 0xDC00 ) ) {
+                if ( ( i + 1 ) < n ) {
+                    int sh = cc;
+                    int sl = cs.charAt ( ++i );
+                    if ( ( sl >= 0xDC00 ) && ( sl < 0xE000 ) ) {
+                        cc = 0x10000 + ( ( sh - 0xD800 ) << 10 ) + ( ( sl - 0xDC00 ) << 0 );
+                    } else {
+                        throw new IllegalArgumentException
+                            (  "ill-formed UTF-16 sequence, "
+                               + "contains isolated high surrogate at index " + i );
+                    }
+                } else {
+                    throw new IllegalArgumentException
+                        ( "ill-formed UTF-16 sequence, "
+                          + "contains isolated high surrogate at end of sequence" );
+                }
+            } else if ( ( cc >= 0xDC00 ) && ( cc < 0xE000 ) ) {
+                throw new IllegalArgumentException
+                    ( "ill-formed UTF-16 sequence, "
+                      + "contains isolated low surrogate at index " + i );
+            }
+            gi = findGlyphIndex ( cc );
+            if ( gi == SingleByteEncoding.NOT_FOUND_CODE_POINT ) {
+                gi = giMissing;
+            }
+            cb.put ( (char) gi );
+        }
+        cb.rewind();
+        return new GlyphSequence ( cs, (CharSequence) cb, null );
+    }
+
+    /**
+     * Map sequence GS, comprising a sequence of Glyph Indices, to output sequence CS,
+     * comprising a sequence of UTF-16 encoded Unicode Code Points.
+     * @param gs a CharSequence containing glyph indices
+     * @returns a CharSequence containing UTF-16 encoded Unicode characters
+     */
+    private CharSequence mapGlyphsToChars ( GlyphSequence gs ) {
+        CharBuffer cb = CharBuffer.allocate ( gs.length() );
+        int cc, ccMissing = Typeface.NOT_FOUND;
+        for ( int i = 0, n = gs.length(); i < n; i++ ) {
+            int gi = gs.charAt ( i );
+            cc = findCharacterFromGlyphIndex ( gi );
+            if ( cc == 0 ) {
+                cc = ccMissing;
+            }
+            if ( cc > 0x10FFFF ) {
+                cc = ccMissing;
+            }
+            if ( cc > 0x00FFFF ) {
+                int sh, sl;
+                cc -= 0x10000;
+                sh = ( ( cc >> 10 ) & 0x3FF ) + 0xD800;
+                sl = ( ( cc >>  0 ) & 0x3FF ) + 0xDC00;
+                cb.put ( (char) sh );
+                cb.put ( (char) sl );
+            } else {
+                cb.put ( (char) cc );
+            }
+        }
+        cb.rewind();
+        return (CharSequence) cb;
+    }
+
 }
 
Index: src/java/org/apache/fop/fonts/Glyphs.java
===================================================================
--- src/java/org/apache/fop/fonts/Glyphs.java	(revision 981406)
+++ src/java/org/apache/fop/fonts/Glyphs.java	(working copy)
@@ -23,7 +23,7 @@
  * This class provides a number of constants for glyph management.
  * @deprecated Use the Glyphs class from XML Graphics Commons instead!
  */
-public class Glyphs {
+public final class Glyphs {
 
     /**
      * Glyph name for the "notdef" glyph
@@ -33,7 +33,7 @@
     /**
      * Glyph names for Mac encoding
      */
-    public static final String MAC_GLYPH_NAMES[] = {
+    public static final String[] MAC_GLYPH_NAMES = {
         /* 0x00 */
         NOTDEF, ".null", "CR", "space", "exclam", "quotedbl", "numbersign",
                 "dollar", "percent", "ampersand", "quotesingle", "parenleft",
@@ -1266,6 +1266,9 @@
         "\u03B6", "zeta"
     };
 
+    private Glyphs() {
+    }
+
     /**
      * Return the glyphname from a character,
      * eg, charToGlyphName('\\') returns "backslash"
@@ -1273,7 +1276,7 @@
      * @param ch glyph to evaluate
      * @return the name of the glyph
      */
-    public static final String charToGlyphName(char ch) {
+    public static String charToGlyphName(char ch) {
         return stringToGlyph(new Character(ch).toString());
     }
 
@@ -1286,7 +1289,7 @@
      * TODO: javadocs for glyphToString and stringToGlyph are confused
      * TODO: Improve method names
      */
-    public static final String glyphToString(String name) {
+    public static String glyphToString(String name) {
         for (int i = 0; i < UNICODE_GLYPHS.length; i += 2) {
             if (UNICODE_GLYPHS[i + 1].equals(name)) {
                 return UNICODE_GLYPHS[i];
Index: src/java/org/apache/fop/fonts/autodetect/FontInfoFinder.java
===================================================================
--- src/java/org/apache/fop/fonts/autodetect/FontInfoFinder.java	(revision 981406)
+++ src/java/org/apache/fop/fonts/autodetect/FontInfoFinder.java	(working copy)
@@ -151,7 +151,7 @@
             subFontName = ((MultiByteFont)customFont).getTTCName();
         }
         EmbedFontInfo fontInfo = new EmbedFontInfo(null, customFont.isKerningEnabled(),
-                fontTripletList, embedUrl, subFontName);
+                customFont.isAdvancedEnabled(), fontTripletList, embedUrl, subFontName);
         fontInfo.setPostScriptName(customFont.getFontName());
         if (fontCache != null) {
             fontCache.addFont(fontInfo);
@@ -226,7 +226,7 @@
                 }
                 try {
                     TTFFontLoader ttfLoader = new TTFFontLoader(
-                            fontFileURI, fontName, true, EncodingMode.AUTO, true, resolver);
+                            fontFileURI, fontName, true, EncodingMode.AUTO, true, true, resolver);
                     customFont = ttfLoader.getFont();
                     if (this.eventListener != null) {
                         customFont.setEventListener(this.eventListener);
Index: src/java/org/apache/fop/fonts/Positionable.java
===================================================================
--- src/java/org/apache/fop/fonts/Positionable.java	(revision 0)
+++ src/java/org/apache/fop/fonts/Positionable.java	(revision 0)
@@ -0,0 +1,46 @@
+/*
+ * 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.fonts;
+
+// CSOFF: LineLengthCheck
+
+/**
+ * Optional interface which indicates that glyph positioning is supported and, if supported,
+ * can perform positioning.
+ * @author Glenn Adams
+ */
+public interface Positionable {
+
+    /**
+     * Determines if font performs glyph positioning.
+     * @return true if performs positioning
+     */
+    boolean performsPositioning();
+
+    /**
+     * Perform glyph positioning.
+     * @param cs character sequence to map to position offsets (advancement adjustments)
+     * @param script a script identifier
+     * @param language a language identifier
+     * @return array (sequence) of pairs of position offsets, one pair for each element of character sequence
+     */
+    int[] performPositioning ( CharSequence cs, String script, String language );
+
+}
Index: src/java/org/apache/fop/fonts/FontLoader.java
===================================================================
--- src/java/org/apache/fop/fonts/FontLoader.java	(revision 981406)
+++ src/java/org/apache/fop/fonts/FontLoader.java	(working copy)
@@ -43,31 +43,36 @@
     protected static Log log = LogFactory.getLog(FontLoader.class);
 
     /** URI representing the font file */
-    protected String fontFileURI = null;
+    protected String fontFileURI;
     /** the FontResolver to use for font URI resolution */
-    protected FontResolver resolver = null;
+    protected FontResolver resolver;
     /** the loaded font */
-    protected CustomFont returnFont = null;
+    protected CustomFont returnFont;
 
     /** true if the font has been loaded */
-    protected boolean loaded = false;
+    protected boolean loaded;
     /** true if the font will be embedded, false if it will be referenced only. */
-    protected boolean embedded = true;
-    /** true if kerning information shall be loaded if available. */
-    protected boolean useKerning = true;
+    protected boolean embedded;
+    /** true if kerning information false be loaded if available. */
+    protected boolean useKerning;
+    /** true if advanced typographic information shall be loaded if available. */
+    protected boolean useAdvanced;
 
     /**
      * Default constructor.
      * @param fontFileURI the URI to the PFB file of a Type 1 font
      * @param embedded indicates whether the font is embedded or referenced
      * @param useKerning indicates whether kerning information shall be loaded if available
+     * @param useAdvanced indicates whether advanced typographic information shall be loaded if
+     * available
      * @param resolver the font resolver used to resolve URIs
      */
     public FontLoader(String fontFileURI, boolean embedded, boolean useKerning,
-            FontResolver resolver) {
+            boolean useAdvanced, FontResolver resolver) {
         this.fontFileURI = fontFileURI;
         this.embedded = embedded;
         this.useKerning = useKerning;
+        this.useAdvanced = useAdvanced;
         this.resolver = resolver;
     }
 
@@ -105,7 +110,7 @@
             boolean embedded, EncodingMode encodingMode,
             FontResolver resolver) throws IOException {
         return loadFont(fontUrl.toExternalForm(), subFontName,
-                embedded, encodingMode, true,
+                embedded, encodingMode, true, true,
                 resolver);
     }
 
@@ -116,13 +121,15 @@
      * @param embedded indicates whether the font is embedded or referenced
      * @param encodingMode the requested encoding mode
      * @param useKerning indicates whether kerning information should be loaded if available
+     * @param useAdvanced indicates whether advanced typographic information shall be loaded if
+     * available
      * @param resolver the font resolver to use when resolving URIs
      * @return the newly loaded font
      * @throws IOException In case of an I/O error
      */
     public static CustomFont loadFont(String fontFileURI, String subFontName,
             boolean embedded, EncodingMode encodingMode, boolean useKerning,
-            FontResolver resolver) throws IOException {
+            boolean useAdvanced, FontResolver resolver) throws IOException {
         fontFileURI = fontFileURI.trim();
         boolean type1 = isType1(fontFileURI);
         FontLoader loader;
@@ -134,7 +141,7 @@
             loader = new Type1FontLoader(fontFileURI, embedded, useKerning, resolver);
         } else {
             loader = new TTFFontLoader(fontFileURI, subFontName,
-                    embedded, encodingMode, useKerning, resolver);
+                    embedded, encodingMode, useKerning, useAdvanced, resolver);
         }
         return loader.getFont();
     }
Index: src/java/org/apache/fop/fonts/GlyphUtils.java
===================================================================
--- src/java/org/apache/fop/fonts/GlyphUtils.java	(revision 0)
+++ src/java/org/apache/fop/fonts/GlyphUtils.java	(revision 0)
@@ -0,0 +1,51 @@
+/*
+ * 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.fonts;
+
+/**
+ * A utility class for glyphs and glyph sequences.
+ * @author Glenn Adams
+ */
+public final class GlyphUtils {
+
+    private GlyphUtils() {
+    }
+
+    /**
+     * Map a glyph (or character) code sequence to  a string, used only
+     * for debugging and logging purposes.
+     * @param cs character (glyph) id sequence
+     * @return a string representation of code sequence
+     */
+    public static String toString ( CharSequence cs ) {
+        StringBuffer sb = new StringBuffer();
+        sb.append ( '[' );
+        for ( int i = 0, n = cs.length(); i < n; i++ ) {
+            int c = cs.charAt ( i );
+            if ( i > 0 ) {
+                sb.append ( ',' );
+            }
+            sb.append ( Integer.toString ( c ) );
+        }
+        sb.append ( ']' );
+        return sb.toString();
+    }
+
+}
Index: src/java/org/apache/fop/fonts/GlyphSubtable.java
===================================================================
--- src/java/org/apache/fop/fonts/GlyphSubtable.java	(revision 0)
+++ src/java/org/apache/fop/fonts/GlyphSubtable.java	(revision 0)
@@ -0,0 +1,108 @@
+/*
+ * 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.fonts;
+
+import java.util.List;
+
+// CSOFF: LineLengthCheck
+
+/**
+ * The <code>GlyphSubtable</code> implements an abstract glyph subtable that
+ * encapsulates identification, type, format, and coverage information.
+ * @author Glenn Adams
+ */
+public abstract class GlyphSubtable {
+
+    private String id;
+    private int sequence;
+    private int flags;
+    private int format;
+    private GlyphCoverageTable coverage;
+
+    /**
+     * Instantiate this glyph subtable.
+     * @param id subtable identifier
+     * @param sequence subtable sequence
+     * @param flags subtable flags
+     * @param format subtable format
+     * @param coverage subtable coverage table
+     */
+    protected GlyphSubtable ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage )
+    {
+        if ( ( id == null ) || ( id.length() == 0 ) ) {
+            throw new IllegalArgumentException ( "invalid lookup identifier, must be non-empty string" );
+        } else if ( coverage == null ) {
+            throw new IllegalArgumentException ( "invalid coverage table, must not be null" );
+        } else {
+            this.id = id;
+            this.sequence = sequence;
+            this.flags = flags;
+            this.format = format;
+            this.coverage = coverage;
+        }
+    }
+
+    /** @return this subtable's identifer */
+    public String getID() {
+        return id;
+    }
+
+    /** @return this subtable's table type */
+    public abstract int getTableType();
+
+    /** @return this subtable's type */
+    public abstract int getType();
+
+    /** @return this subtable's type name */
+    public abstract String getTypeName();
+
+    /** @return this subtable's sequence */
+    public int getSequence() {
+        return sequence;
+    }
+
+    /** @return this subtable's flags */
+    public int getFlags() {
+        return flags;
+    }
+
+    /** @return this subtable's format */
+    public int getFormat() {
+        return format;
+    }
+
+    /** @return this subtable's coverage table */
+    public GlyphCoverageTable getCoverage() {
+        return coverage;
+    }
+
+    /** @return this subtable's lookup entries */
+    public abstract List getEntries();
+
+    /**
+     * Map glyph id to coverage index.
+     * @param gid glyph id
+     * @return the corresponding coverage index of the specified glyph id
+     */
+    public int getCoverageIndex ( int gid ) {
+        return coverage.getCoverageIndex ( gid );
+    }
+
+}
Index: src/java/org/apache/fop/fonts/Font.java
===================================================================
--- src/java/org/apache/fop/fonts/Font.java	(revision 981406)
+++ src/java/org/apache/fop/fonts/Font.java	(working copy)
@@ -25,6 +25,8 @@
 import org.apache.commons.logging.LogFactory;
 import org.apache.fop.fonts.CodePointMapping;
 
+// CSOFF: LineLengthCheck
+
 /**
  * This class holds font state information and provides access to the font
  * metrics.
@@ -240,8 +242,8 @@
      * {@inheritDoc}
      */
     public String toString() {
-        StringBuffer sbuf = new StringBuffer();
-        sbuf.append('(');
+        StringBuffer sbuf = new StringBuffer(super.toString());
+        sbuf.append('{');
         /*
         sbuf.append(fontFamily);
         sbuf.append(',');*/
@@ -253,7 +255,7 @@
         sbuf.append(fontStyle);
         sbuf.append(',');
         sbuf.append(fontWeight);*/
-        sbuf.append(')');
+        sbuf.append('}');
         return sbuf.toString();
     }
 
@@ -346,6 +348,44 @@
         return width;
     }
 
-}
+    /** {@inheritDoc} */
+    public boolean performsGlyphSubstitution() {
+        if ( metric instanceof Substitutable ) {
+            Substitutable s = (Substitutable) metric;
+            return s.performsSubstitution();
+        } else {
+            return false;
+        }
+    }
 
+    /** {@inheritDoc} */
+    public CharSequence performSubstitution ( CharSequence cs, String script, String language ) {
+        if ( metric instanceof Substitutable ) {
+            Substitutable s = (Substitutable) metric;
+            return s.performSubstitution ( cs, script, language );
+        } else {
+            throw new UnsupportedOperationException();
+        }
+    }
 
+    /** {@inheritDoc} */
+    public boolean performsGlyphPositioning() {
+        if ( metric instanceof Positionable ) {
+            Positionable p = (Positionable) metric;
+            return p.performsPositioning();
+        } else {
+            return false;
+        }
+    }
+
+    /** {@inheritDoc} */
+    public int[] performPositioning ( CharSequence cs, String script, String language ) {
+        if ( metric instanceof Positionable ) {
+            Positionable p = (Positionable) metric;
+            return p.performPositioning ( cs, script, language );
+        } else {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+}
Index: src/java/org/apache/fop/fonts/GlyphCoverageTable.java
===================================================================
--- src/java/org/apache/fop/fonts/GlyphCoverageTable.java	(revision 0)
+++ src/java/org/apache/fop/fonts/GlyphCoverageTable.java	(revision 0)
@@ -0,0 +1,357 @@
+/*
+ * 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.fonts;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Iterator;
+
+// CSOFF: NoWhitespaceAfterCheck
+// CSOFF: InnerAssignmentCheck
+// CSOFF: LineLengthCheck
+
+/**
+ * Abstract base class implementation of glyph coverage table.
+ * @author Glenn Adams
+ */
+public abstract class GlyphCoverageTable {
+
+    /** empty coverage table */
+    public static final int GLYPH_COVERAGE_TYPE_EMPTY = 0;
+
+    /** mapped coverage table */
+    public static final int GLYPH_COVERAGE_TYPE_MAPPED = 1;
+
+    /** range based coverage table */
+    public static final int GLYPH_COVERAGE_TYPE_RANGE = 2;
+
+    /**
+     * Obtain coverage type.
+     * @return coverage format type
+     */
+    public abstract int getType();
+
+    /**
+     * Obtain coverage entries.
+     * @return list of coverage entries
+     */
+    public abstract List getEntries();
+
+    /**
+     * Map glyph identifier (code) to coverge index. Returns -1 if glyph identifier is not in the domain of
+     * the coverage table.
+     * @param gid glyph identifier (code)
+     * @return non-negative glyph coverage index or -1 if glyph identifiers is not mapped by table
+     */
+    public abstract int getCoverageIndex ( int gid );
+
+    /**
+     * Create glyph coverage table.
+     * @param coverage list of mapped or ranged coverage entries, or null or empty list
+     * @return a new covera table instance
+     */
+    public static GlyphCoverageTable createCoverageTable ( List coverage ) {
+        GlyphCoverageTable ct;
+        if ( ( coverage == null ) || ( coverage.size() == 0 ) ) {
+            ct = new EmptyCoverageTable ( coverage );
+        } else if ( isMappedCoverage ( coverage ) ) {
+            ct = new MappedCoverageTable ( coverage );
+        } else if ( isRangeCoverage ( coverage ) ) {
+            ct = new RangeCoverageTable ( coverage );
+        } else {
+            ct = null;
+        }
+        assert ct != null : "unknown coverage type";
+        return ct;
+    }
+
+    private static boolean isMappedCoverage ( List coverage ) {
+        if ( ( coverage == null ) || ( coverage.size() == 0 ) ) {
+            return false;
+        } else {
+            for ( Iterator it = coverage.iterator(); it.hasNext();) {
+                Object o = it.next();
+                if ( ! ( o instanceof Integer ) ) {
+                    return false;
+                }
+            }
+            return true;
+        }
+    }
+
+    private static boolean isRangeCoverage ( List coverage ) {
+        if ( ( coverage == null ) || ( coverage.size() == 0 ) ) {
+            return false;
+        } else {
+            for ( Iterator it = coverage.iterator(); it.hasNext();) {
+                Object o = it.next();
+                if ( ! ( o instanceof CoverageRange ) ) {
+                    return false;
+                }
+            }
+            return true;
+        }
+    }
+
+    private static class EmptyCoverageTable extends GlyphCoverageTable {
+        public EmptyCoverageTable ( List coverage ) {
+        }
+        public int getType() {
+            return GLYPH_COVERAGE_TYPE_EMPTY;
+        }
+        public List getEntries() {
+            return new java.util.ArrayList();
+        }
+        public int getCoverageIndex ( int gid ) {
+            return -1;
+        }
+    }
+
+    private static class MappedCoverageTable extends GlyphCoverageTable {
+        private int[] map = null;
+        public MappedCoverageTable ( List coverage ) {
+            populate ( coverage );
+        }
+        public int getType() {
+            return GLYPH_COVERAGE_TYPE_MAPPED;
+        }
+        public List getEntries() {
+            List entries = new java.util.ArrayList();
+            if ( map != null ) {
+                for ( int i = 0, n = map.length; i < n; i++ ) {
+                    entries.add ( Integer.valueOf ( map [ i ] ) );
+                }
+            }
+            return entries;
+        }
+        public int getCoverageIndex ( int gid ) {
+            int i;
+            if ( ( i = Arrays.binarySearch ( map, gid ) ) >= 0 ) {
+                return i;
+            } else {
+                return -1;
+            }
+        }
+        private void populate ( List coverage ) {
+            int i = 0, n = coverage.size(), gidMax = -1;
+            int[] map = new int [ n ];
+            for ( Iterator it = coverage.iterator(); it.hasNext();) {
+                Object o = it.next();
+                if ( o instanceof Integer ) {
+                    int gid = ( (Integer) o ) . intValue();
+                    if ( ( gid >= 0 ) && ( gid < 65536 ) ) {
+                        if ( gid > gidMax ) {
+                            map [ i++ ] = gidMax = gid;
+                        } else {
+                            throw new IllegalArgumentException ( "out of order or duplicate glyph index: " + gid );
+                        }
+                    } else {
+                        throw new IllegalArgumentException ( "illegal glyph index: " + gid );
+                    }
+                } else {
+                    throw new IllegalArgumentException ( "illegal coverage entry, must be Integer: " + o );
+                }
+            }
+            assert i == n;
+            assert this.map == null;
+            this.map = map;
+        }
+        public String toString() {
+            StringBuffer sb = new StringBuffer();
+            sb.append('{');
+            for ( int i = 0, n = map.length; i < n; i++ ) {
+                if ( i > 0 ) {
+                    sb.append(',');
+                }
+                sb.append ( Integer.toString ( map [ i ] ) );
+            }
+            sb.append('}');
+            return sb.toString();
+        }
+    }
+
+    private static class RangeCoverageTable extends GlyphCoverageTable {
+        private int[] sa = null;                                                // array of ranges starts
+        private int[] ea = null;                                                // array of range ends
+        private int[] ca = null;                                                // array of range coverage (start) indices
+        public RangeCoverageTable ( List coverage ) {
+            populate ( coverage );
+        }
+        public int getType() {
+            return GLYPH_COVERAGE_TYPE_RANGE;
+        }
+        public List getEntries() {
+            List entries = new java.util.ArrayList();
+            if ( sa != null ) {
+                for ( int i = 0, n = sa.length; i < n; i++ ) {
+                    entries.add ( new CoverageRange ( sa [ i ], ea [ i ], ca [ i ] ) );
+                }
+            }
+            return entries;
+        }
+        public int getCoverageIndex ( int gid ) {
+            int i, ci;
+            if ( ( i = Arrays.binarySearch ( sa, gid ) ) >= 0 ) {
+                ci = ca [ i ] + gid - sa [ i ];                         // matches start of (some) range
+            } else if ( ( i = - ( i + 1 ) ) == 0 ) {
+                ci = -1;                                                // precedes first range 
+            } else if ( gid > ea [ --i ] ) {
+                ci = -1;                                                // follows preceding (or last) range
+            } else {
+                ci = ca [ i ] + gid - sa [ i ];                         // intersects (some) range
+            }
+            return ci;
+        }
+        private void populate ( List coverage ) {
+            int i = 0, n = coverage.size(), gidMax = -1;
+            int[] sa = new int [ n ];
+            int[] ea = new int [ n ];
+            int[] ca = new int [ n ];
+            for ( Iterator it = coverage.iterator(); it.hasNext();) {
+                Object o = it.next();
+                if ( o instanceof CoverageRange ) {
+                    CoverageRange r = (CoverageRange) o;
+                    int gs = r.getStart();
+                    int ge = r.getEnd();
+                    int ci = r.getIndex();
+                    if ( ( gs < 0 ) || ( gs > 65535 ) ) {
+                        throw new IllegalArgumentException ( "illegal glyph range: [" + gs + "," + ge + "]: bad start index" );
+                    } else if ( ( ge < 0 ) || ( ge > 65535 ) ) {
+                        throw new IllegalArgumentException ( "illegal glyph range: [" + gs + "," + ge + "]: bad end index" );
+                    } else if ( gs > ge ) {
+                        throw new IllegalArgumentException ( "illegal glyph range: [" + gs + "," + ge + "]: start index exceeds end index" );
+                    } else if ( gs < gidMax ) {
+                        throw new IllegalArgumentException ( "out of order glyph range: [" + gs + "," + ge + "]" );
+                    } else if ( ci < 0 ) {
+                        throw new IllegalArgumentException ( "illegal coverage index: " + ci );
+                    } else {
+                        sa [ i ] = gs;
+                        ea [ i ] = gidMax = ge;
+                        ca [ i ] = ci;
+                        i++;
+                    }
+                } else {
+                    throw new IllegalArgumentException ( "illegal coverage entry, must be Integer: " + o );
+                }
+            }
+            assert i == n;
+            assert this.sa == null;
+            assert this.ea == null;
+            assert this.ca == null;
+            this.sa = sa;
+            this.ea = ea;
+            this.ca = ca;
+        }
+        public String toString() {
+            StringBuffer sb = new StringBuffer();
+            sb.append('{');
+            for ( int i = 0, n = sa.length; i < n; i++ ) {
+                if ( i > 0 ) {
+                    sb.append(',');
+                }
+                sb.append ( '[' );
+                sb.append ( Integer.toString ( sa [ i ] ) );
+                sb.append ( Integer.toString ( ea [ i ] ) );
+                sb.append ( "]:" );
+                sb.append ( Integer.toString ( ca [ i ] ) );
+            }
+            sb.append('}');
+            return sb.toString();
+        }
+    }
+
+    /**
+     * The <code>CoverageRange</code> class encapsulates a glyph [start,end] range and
+     * a coverage index.
+     */
+    public static class CoverageRange {
+
+        private final int gidStart;                     // first glyph in range (inclusive)
+        private final int gidEnd;                       // last glyph in range (inclusive)
+        private final int index;                        // coverage index;
+
+        /**
+         * Instantiate a coverage range.
+         */
+        public CoverageRange() {
+            this ( 0, 0, 0 );
+        }
+
+        /**
+         * Instantiate a specific coverage range.
+         * @param gidStart start of range
+         * @param gidEnd end of range
+         * @param index coverage index
+         */
+        public CoverageRange ( int gidStart, int gidEnd, int index ) {
+            if ( ( gidStart < 0 ) || ( gidEnd < 0 ) || ( index < 0 ) ) {
+                throw new IllegalArgumentException();
+            } else if ( gidStart > gidEnd ) {
+                throw new IllegalArgumentException();
+            } else {
+                this.gidStart = gidStart;
+                this.gidEnd = gidEnd;
+                this.index = index;
+            }
+        }
+
+        /** @return start of range */
+        public int getStart() {
+            return gidStart;
+        }
+
+        /** @return end of range */
+        public int getEnd() {
+            return gidEnd;
+        }
+
+        /** @return coverage index */
+        public int getIndex() {
+            return index;
+        }
+
+        /** @return interval as a pair of integers */
+        public int[] getInterval() {
+            return new int[] { gidStart, gidEnd };
+        }
+
+        /**
+         * Obtain interval, filled into first two elements of specified array, or returning new array.
+         * @param interval an array of length two or greater or null
+         * @return interval as a pair of integers, filled into specified array
+         */
+        public int[] getInterval ( int[] interval ) {
+            if ( ( interval == null ) || ( interval.length != 2 ) ) {
+                throw new IllegalArgumentException();
+            } else {
+                interval[0] = gidStart;
+                interval[1] = gidEnd;
+            }
+            return interval;
+        }
+
+        /** @return length of interval */
+        public int getLength() {
+            return gidStart - gidEnd;
+        }
+
+    }
+
+}
Index: src/java/org/apache/fop/fonts/FontSetup.java
===================================================================
--- src/java/org/apache/fop/fonts/FontSetup.java	(revision 981406)
+++ src/java/org/apache/fop/fonts/FontSetup.java	(working copy)
@@ -51,13 +51,16 @@
  * Assigns the font (with metrics) to internal names like "F1" and
  * assigns family-style-weight triplets to the fonts
  */
-public class FontSetup {
+public final class FontSetup {
 
     /**
      * logging instance
      */
-    protected static Log log = LogFactory.getLog(FontSetup.class);
+    private static final Log log = LogFactory.getLog(FontSetup.class); // CSOK: ConstantNameCheck
 
+    private FontSetup() {
+    }
+
     /**
      * Sets up a font info
      * @param fontInfo font info
Index: src/java/org/apache/fop/fonts/GlyphPositioningSubtable.java
===================================================================
--- src/java/org/apache/fop/fonts/GlyphPositioningSubtable.java	(revision 0)
+++ src/java/org/apache/fop/fonts/GlyphPositioningSubtable.java	(revision 0)
@@ -0,0 +1,62 @@
+/*
+ * 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.fonts;
+
+// CSOFF: LineLengthCheck
+
+/**
+ * The <code>GlyphPositioningSubtable</code> implements an abstract base of a glyph subtable,
+ * providing a default implementation of the <code>GlyphPositioning</code> interface.
+ * @author Glenn Adams
+ */
+public abstract class GlyphPositioningSubtable extends GlyphSubtable implements GlyphPositioning {
+
+    /**
+     * Instantiate a <code>GlyphPositioningSubtable</code>.
+     * @param id subtable identifier
+     * @param sequence subtable sequence
+     * @param flags subtable flags
+     * @param format subtable format
+     * @param coverage subtable coverage table
+     */
+    protected GlyphPositioningSubtable ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage ) {
+        super ( id, sequence, flags, format, coverage );
+    }
+
+    /** {@inheritDoc} */
+    public int getTableType() {
+        return GlyphTable.GLYPH_TABLE_TYPE_POSITIONING;
+    }
+
+    /** {@inheritDoc} */
+    public String getTypeName() {
+        return GlyphPositioningTable.getLookupTypeName ( getType() );
+    }
+
+    /** {@inheritDoc} */
+    public int[] position ( GlyphSequence gs, String script, String language ) {
+        if ( gs == null ) {
+            throw new IllegalArgumentException ( "invalid glyph sequence: must not be null" );
+        } else {
+            return null;
+        }
+    }
+
+}
Index: src/java/org/apache/fop/fonts/truetype/TTFCmapEntry.java
===================================================================
--- src/java/org/apache/fop/fonts/truetype/TTFCmapEntry.java	(revision 981406)
+++ src/java/org/apache/fop/fonts/truetype/TTFCmapEntry.java	(working copy)
@@ -44,6 +44,17 @@
     /**
      * {@inheritDoc}
      */
+    public int hashCode() {
+        int hc = super.hashCode();
+        hc ^= ( hc * 11 ) + unicodeStart;
+        hc ^= ( hc * 19 ) + unicodeEnd;
+        hc ^= ( hc * 23 ) + glyphStartIndex;
+        return hc;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
     public boolean equals(Object o) {
         if (o instanceof TTFCmapEntry) {
             TTFCmapEntry ce = (TTFCmapEntry)o;
Index: src/java/org/apache/fop/fonts/truetype/FontFileReader.java
===================================================================
--- src/java/org/apache/fop/fonts/truetype/FontFileReader.java	(revision 981406)
+++ src/java/org/apache/fop/fonts/truetype/FontFileReader.java	(working copy)
@@ -317,6 +317,7 @@
      * Read an ISO-8859-1 string of len bytes.
      *
      * @param len The length of the string to read
+     * @param encodingID a string encoding identifier
      * @return A String
      * @throws IOException If EOF is reached
      */
Index: src/java/org/apache/fop/fonts/truetype/TTFFontLoader.java
===================================================================
--- src/java/org/apache/fop/fonts/truetype/TTFFontLoader.java	(revision 981406)
+++ src/java/org/apache/fop/fonts/truetype/TTFFontLoader.java	(working copy)
@@ -55,7 +55,7 @@
      * @param resolver the FontResolver for font URI resolution
      */
     public TTFFontLoader(String fontFileURI, FontResolver resolver) {
-        this(fontFileURI, null, true, EncodingMode.AUTO, true, resolver);
+        this(fontFileURI, null, true, EncodingMode.AUTO, true, true, resolver);
     }
 
     /**
@@ -66,12 +66,13 @@
      * @param embedded indicates whether the font is embedded or referenced
      * @param encodingMode the requested encoding mode
      * @param useKerning true to enable loading kerning info if available, false to disable
+     * @param useAdvanced true to enable loading advanced info if available, false to disable
      * @param resolver the FontResolver for font URI resolution
      */
     public TTFFontLoader(String fontFileURI, String subFontName,
                 boolean embedded, EncodingMode encodingMode, boolean useKerning,
-                FontResolver resolver) {
-        super(fontFileURI, embedded, true, resolver);
+                boolean useAdvanced, FontResolver resolver) {
+        super(fontFileURI, embedded, useKerning, useAdvanced, resolver);
         this.subFontName = subFontName;
         this.encodingMode = encodingMode;
         if (this.encodingMode == EncodingMode.AUTO) {
@@ -169,6 +170,9 @@
         if (useKerning) {
             copyKerning(ttf, isCid);
         }
+        if (useAdvanced) {
+            copyAdvanced(ttf);
+        }
         if (this.embedded && ttf.isEmbeddable()) {
             returnFont.setEmbedFileName(this.fontFileURI);
         }
@@ -224,4 +228,16 @@
             returnFont.putKerningEntry(kpx1, h2);
         }
     }
+
+    /**
+     * Copy advanced typographic information.
+     */
+    private void copyAdvanced ( TTFFile ttf ) {
+        if ( returnFont instanceof MultiByteFont ) {
+            MultiByteFont mbf = (MultiByteFont) returnFont;
+            mbf.setGSUB ( ttf.getGSUB() );
+            mbf.setGPOS ( ttf.getGPOS() );
+        }
+    }
+
 }
Index: src/java/org/apache/fop/fonts/truetype/TTFFile.java
===================================================================
--- src/java/org/apache/fop/fonts/truetype/TTFFile.java	(revision 981406)
+++ src/java/org/apache/fop/fonts/truetype/TTFFile.java	(working copy)
@@ -31,8 +31,22 @@
 
 import org.apache.xmlgraphics.fonts.Glyphs;
 
+
 import org.apache.fop.fonts.FontUtil;
+import org.apache.fop.fonts.GlyphCoverageTable;
+import org.apache.fop.fonts.GlyphPositioningSubtable;
+import org.apache.fop.fonts.GlyphPositioningTable;
+import org.apache.fop.fonts.GlyphSubstitutionSubtable;
+import org.apache.fop.fonts.GlyphSubstitutionTable;
+import org.apache.fop.fonts.GlyphSubtable;
+import org.apache.fop.fonts.GlyphTable;
 
+// CSOFF: AvoidNestedBlocksCheck
+// CSOFF: NoWhitespaceAfterCheck
+// CSOFF: InnerAssignmentCheck
+// CSOFF: SimplifyBooleanReturnCheck
+// CSOFF: LineLengthCheck
+
 /**
  * Reads a TrueType file or a TrueType Collection.
  * The TrueType spec can be found at the Microsoft.
@@ -122,6 +136,16 @@
 
     private boolean isCFF;
 
+    /* advanced typographic support */
+    private Map/*<String,Object[3]>*/ seScripts;
+    private Map/*<String,Object[2]>*/ seLanguages;
+    private Map/*<String,List<String>>*/ seFeatures;
+    private List seCoverage;
+    private List seEntries;
+    private List seSubtables;
+    private GlyphSubstitutionTable gsub;
+    private GlyphPositioningTable gpos;
+
     /**
      * logging instance
      */
@@ -245,7 +269,7 @@
         }
     }
 
-    private boolean readUnicodeCmap(FontFileReader in, long cmapUniOffset, int encodingID)
+    private boolean readUnicodeCmap(FontFileReader in, long cmapUniOffset, int encodingID)                              // CSOK: MethodLengthCheck
             throws IOException {
         //Read CMAP table and correct mtxTab.index
         int mtxPtr = 0;
@@ -558,6 +582,8 @@
         // print_max_min();
 
         readKerning(in);
+        readGSUB(in);
+        readGPOS(in);
         guessVerticalMetricsFromGlyphBBox();
         return true;
     }
@@ -961,7 +987,7 @@
      * Read the "post" table
      * containing the PostScript names of the glyphs.
      */
-    private final void readPostScript(FontFileReader in) throws IOException {
+    private void readPostScript(FontFileReader in) throws IOException {
         seekTab(in, "post", 0);
         postFormat = in.readTTFLong();
         italicAngle = in.readTTFULong();
@@ -1133,7 +1159,7 @@
      * @param in FontFileReader to read from
      * @throws IOException In case of a I/O problem
      */
-    private final void readGlyf(FontFileReader in) throws IOException {
+    private void readGlyf(FontFileReader in) throws IOException {
         TTFDirTabEntry dirTab = (TTFDirTabEntry)dirTabs.get("glyf");
         if (dirTab == null) {
             throw new IOException("glyf table not found, cannot continue");
@@ -1188,7 +1214,7 @@
      * @param in FontFileReader to read from
      * @throws IOException In case of a I/O problem
      */
-    private final void readName(FontFileReader in) throws IOException {
+    private void readName(FontFileReader in) throws IOException {
         seekTab(in, "name", 2);
         int i = in.getCurrentPos();
         int n = in.readTTFUShort();
@@ -1259,7 +1285,7 @@
      * @param in FontFileReader to read from
      * @throws IOException In case of a I/O problem
      */
-    private final boolean readPCLT(FontFileReader in) throws IOException {
+    private boolean readPCLT(FontFileReader in) throws IOException {
         TTFDirTabEntry dirTab = (TTFDirTabEntry)dirTabs.get("PCLT");
         if (dirTab != null) {
             in.seekSet(dirTab.getOffset() + 4 + 4 + 2);
@@ -1403,7 +1429,7 @@
      * @param in FontFileReader to read from
      * @throws IOException In case of a I/O problem
      */
-    private final void readKerning(FontFileReader in) throws IOException {
+    private void readKerning(FontFileReader in) throws IOException {
         // Read kerning
         kerningTab = new java.util.HashMap();
         ansiKerningTab = new java.util.HashMap();
@@ -1490,7 +1516,1512 @@
         }
     }
 
+    private String toString ( int[] ia ) {
+        StringBuffer sb = new StringBuffer();
+        if ( ( ia == null ) || ( ia.length == 0 ) ) {
+            sb.append ( '-' );
+        } else {
+            boolean first = true;
+            for ( int i = 0; i < ia.length; i++ ) {
+                if ( ! first ) {
+                    sb.append ( ' ' );
+                } else {
+                    first = false;
+                }
+                sb.append ( ia[i] );
+            }
+        }
+        return sb.toString();
+    }
+
+    private void readLangSysTable(FontFileReader in, String tableTag, long langSysTable, String langSysTag) throws IOException {
+        in.seekSet(langSysTable);
+        if (log.isDebugEnabled()) {
+            log.debug(tableTag + " lang sys table: " + langSysTag );
+        }
+        // read lookup order (reorder) table offset
+        int lo = in.readTTFUShort();
+        // read required feature index
+        int rf = in.readTTFUShort();
+        String rfi;
+        if ( rf != 65535 ) {
+            rfi = "f" + rf;
+        } else {
+            rfi = null;
+        }
+        // read (non-required) feature count
+        int nf = in.readTTFUShort();
+        // dump info if debugging
+        if (log.isDebugEnabled()) {
+            log.debug(tableTag + " lang sys table reorder table: " + lo );
+            log.debug(tableTag + " lang sys table required feature index: " + rf );
+            log.debug(tableTag + " lang sys table non-required feature count: " + nf );
+        }
+        // read (non-required) feature indices
+        int[] fia = new int[nf];
+        List fl = new java.util.ArrayList();
+        for ( int i = 0; i < nf; i++ ) {
+            int fi = in.readTTFUShort();
+            if (log.isDebugEnabled()) {
+                log.debug(tableTag + " lang sys table non-required feature index: " + fi );
+            }
+            fia[i] = fi;
+            fl.add ( "f" + fi );
+        }
+        if ( seLanguages == null ) {
+            seLanguages = new java.util.LinkedHashMap();
+        }
+        seLanguages.put ( langSysTag, new Object[] { rfi, fl } );
+    }
+
+    private static String defaultTag = "dflt";
+
+    private void readScriptTable(FontFileReader in, String tableTag, long scriptTable, String scriptTag) throws IOException {
+        in.seekSet(scriptTable);
+        if (log.isDebugEnabled()) {
+            log.debug(tableTag + " script table: " + scriptTag );
+        }
+        // read default language system table offset
+        int dl = in.readTTFUShort();
+        String dt = defaultTag;
+        if ( dl > 0 ) {
+            if (log.isDebugEnabled()) {
+                log.debug(tableTag + " default lang sys tag: " + dt );
+                log.debug(tableTag + " default lang sys table offset: " + dl );
+            }
+        }
+        // read language system record count
+        int nl = in.readTTFUShort();
+        List ll = new java.util.ArrayList();
+        if ( nl > 0 ) {
+            String[] lta = new String[nl];
+            int[] loa = new int[nl];
+            // read language system records
+            for ( int i = 0, n = nl; i < n; i++ ) {
+                String lt = in.readTTFString(4);
+                int lo = in.readTTFUShort();
+                if (log.isDebugEnabled()) {
+                    log.debug(tableTag + " lang sys tag: " + lt );
+                    log.debug(tableTag + " lang sys table offset: " + lo );
+                }
+                lta[i] = lt;
+                loa[i] = lo;
+                if ( dl == lo ) {
+                    dl = 0;
+                    dt = lt;
+                }
+                ll.add ( lt );
+            }
+            // read non-default language system tables
+            for ( int i = 0, n = nl; i < n; i++ ) {
+                readLangSysTable ( in, tableTag, scriptTable + loa [ i ], lta [ i ] );
+            }
+        }
+        // read default language system table (if specified)
+        if ( dl > 0 ) {
+            readLangSysTable ( in, tableTag, scriptTable + dl, dt );
+        } else if ( dt != null ) {
+            if (log.isDebugEnabled()) {
+                log.debug(tableTag + " lang sys default: " + dt );
+            }
+        }
+        seScripts.put ( scriptTag, new Object[] { dt, ll, seLanguages } );
+        seLanguages = null;
+    }
+
+    private void readScriptList(FontFileReader in, String tableTag, long scriptList) throws IOException {
+        in.seekSet(scriptList);
+        // read script record count
+        int ns = in.readTTFUShort();
+        if (log.isDebugEnabled()) {
+            log.debug(tableTag + " script list record count: " + ns );
+        }
+        if ( ns > 0 ) {
+            String[] sta = new String[ns];
+            int[] soa = new int[ns];
+            // read script records
+            for ( int i = 0, n = ns; i < n; i++ ) {
+                String st = in.readTTFString(4);
+                int so = in.readTTFUShort();
+                if (log.isDebugEnabled()) {
+                    log.debug(tableTag + " script tag: " + st );
+                    log.debug(tableTag + " script table offset: " + so );
+                }
+                sta[i] = st;
+                soa[i] = so;
+            }
+            // read script tables
+            for ( int i = 0, n = ns; i < n; i++ ) {
+                seLanguages = null;
+                readScriptTable ( in, tableTag, scriptList + soa [ i ], sta [ i ] );
+            }
+        }
+    }
+
+    private void readFeatureTable(FontFileReader in, String tableTag, long featureTable, String featureTag, int featureIndex) throws IOException {
+        in.seekSet(featureTable);
+        if (log.isDebugEnabled()) {
+            log.debug(tableTag + " feature table: " + featureTag );
+        }
+        // read feature params offset
+        int po = in.readTTFUShort();
+        // read lookup list indices count
+        int nl = in.readTTFUShort();
+        // dump info if debugging
+        if (log.isDebugEnabled()) {
+            log.debug(tableTag + " feature table parameters offset: " + po );
+            log.debug(tableTag + " feature table lookup list index count: " + nl );
+        }
+        // read lookup table indices
+        int[] lia = new int[nl];
+        List lul = new java.util.ArrayList();
+        for ( int i = 0; i < nl; i++ ) {
+            int li = in.readTTFUShort();
+            if (log.isDebugEnabled()) {
+                log.debug(tableTag + " feature table lookup index: " + li );
+            }
+            lia[i] = li;
+            lul.add ( "lu" + li );
+        }
+        seFeatures.put ( "f" + featureIndex, new Object[] { featureTag, lul } );
+    }
+
+    private void readFeatureList(FontFileReader in, String tableTag, long featureList) throws IOException {
+        in.seekSet(featureList);
+        // read feature record count
+        int nf = in.readTTFUShort();
+        if (log.isDebugEnabled()) {
+            log.debug(tableTag + " feature list record count: " + nf );
+        }
+        if ( nf > 0 ) {
+            String[] fta = new String[nf];
+            int[] foa = new int[nf];
+            // read feature records
+            for ( int i = 0, n = nf; i < n; i++ ) {
+                String ft = in.readTTFString(4);
+                int fo = in.readTTFUShort();
+                if (log.isDebugEnabled()) {
+                    log.debug(tableTag + " feature tag: " + ft );
+                    log.debug(tableTag + " feature table offset: " + fo );
+                }
+                fta[i] = ft;
+                foa[i] = fo;
+            }
+            // read feature tables
+            for ( int i = 0, n = nf; i < n; i++ ) {
+                if (log.isDebugEnabled()) {
+                    log.debug(tableTag + " feature index: " + i );
+                }
+                readFeatureTable ( in, tableTag, featureList + foa [ i ], fta [ i ], i );
+            }
+        }
+    }
+
     /**
+     * Determine if script extension is present.
+     * @return true if script extension is present
+     */
+    public boolean hasScriptExtension() {
+        return ( gsub != null ) || ( gpos != null );
+    }
+
+    /**
+     * Returns the GSUB table or null if none present.
+     * @return the GSUB table
+     */
+    public GlyphSubstitutionTable getGSUB() {
+        return gsub;
+    }
+
+    /**
+     * Returns the GPOS table or null if none present.
+     * @return the GPOS table
+     */
+    public GlyphPositioningTable getGPOS() {
+        return gpos;
+    }
+
+    static final class GSUBLookupType {
+        static final int SINGLE                         = 1;
+        static final int MULTIPLE                       = 2;
+        static final int ALTERNATE                      = 3;
+        static final int LIGATURE                       = 4;
+        static final int CONTEXT                        = 5;
+        static final int CHAINED_CONTEXT                = 6;
+        static final int EXTENSION                      = 7;
+        static final int REVERSE_CHAINED_SINGLE         = 8;
+        private GSUBLookupType() {
+        }
+        public static int getSubtableType ( int lt ) {
+            int st;
+            switch ( lt ) {
+            case GSUBLookupType.SINGLE:
+                st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_SINGLE;
+                break;
+            case GSUBLookupType.MULTIPLE:
+                st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_MULTIPLE;
+                break;
+            case GSUBLookupType.ALTERNATE:
+                st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_ALTERNATE;
+                break;
+            case GSUBLookupType.LIGATURE:
+                st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_LIGATURE;
+                break;
+            case GSUBLookupType.CONTEXT:
+                st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_CONTEXT;
+                break;
+            case GSUBLookupType.CHAINED_CONTEXT:
+                st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_CHAINING_CONTEXT;
+                break;
+            case GSUBLookupType.EXTENSION:
+                st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_EXTENSION_SUBSTITUTION;
+                break;
+            case GSUBLookupType.REVERSE_CHAINED_SINGLE:
+                st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_REVERSE_CHAINING_CONTEXT_SINGLE;
+                break;
+            default:
+                st = -1;
+                break;
+            }
+            return st;
+        }
+        public static String toString(int type) {
+            String s;
+            switch ( type ) {
+            case SINGLE:
+                s = "Single";
+                break;
+            case MULTIPLE:
+                s = "Multiple";
+                break;
+            case ALTERNATE:
+                s = "Alternate";
+                break;
+            case LIGATURE:
+                s = "Ligature";
+                break;
+            case CONTEXT:
+                s = "Context";
+                break;
+            case CHAINED_CONTEXT:
+                s = "ChainedContext";
+                break;
+            case EXTENSION:
+                s = "Extension";
+                break;
+            case REVERSE_CHAINED_SINGLE:
+                s = "ReverseChainedSingle";
+                break;
+            default:
+                s = "?";
+                break;
+            }
+            return s;
+        }
+    }
+
+    static final class GPOSLookupType {
+        static final int SINGLE                         = 1;
+        static final int PAIR                           = 2;
+        static final int CURSIVE                        = 3;
+        static final int MARK_TO_BASE                   = 4;
+        static final int MARK_TO_LIGATURE               = 5;
+        static final int MARK_TO_MARK                   = 6;
+        static final int CONTEXT                        = 7;
+        static final int CHAINED_CONTEXT                = 8;
+        static final int EXTENSION                      = 9;
+        private GPOSLookupType() {
+        }
+        public static String toString(int type) {
+            String s;
+            switch ( type ) {
+            case SINGLE:
+                s = "Single";
+                break;
+            case PAIR:
+                s = "Pair";
+                break;
+            case CURSIVE:
+                s = "Cursive";
+                break;
+            case MARK_TO_BASE:
+                s = "MarkToBase";
+                break;
+            case MARK_TO_LIGATURE:
+                s = "MarkToLigature";
+                break;
+            case MARK_TO_MARK:
+                s = "MarkToMark";
+                break;
+            case CONTEXT:
+                s = "Context";
+                break;
+            case CHAINED_CONTEXT:
+                s = "ChainedContext";
+                break;
+            case EXTENSION:
+                s = "Extension";
+                break;
+            default:
+                s = "?";
+                break;
+            }
+            return s;
+        }
+    }
+
+    static final class LookupFlag {
+        static final int RIGHT_TO_LEFT                  = 0x0001;
+        static final int IGNORE_BASE_GLYPHS             = 0x0002;
+        static final int IGNORE_LIGATURE                = 0x0004;
+        static final int IGNORE_MARKS                   = 0x0008;
+        static final int USE_MARK_FILTERING_SET         = 0x0010;
+        static final int MARK_ATTACHMENT_TYPE           = 0xFF00;
+        private LookupFlag() {
+        }
+        public static String toString(int flags) {
+            StringBuffer sb = new StringBuffer();
+            boolean first = true;
+            if ( ( flags & RIGHT_TO_LEFT ) != 0 ) {
+                if ( first ) {
+                    first = false;
+                } else {
+                    sb.append ( '|' );
+                }
+                sb.append ( "RightToLeft" );
+            }
+            if ( ( flags & IGNORE_BASE_GLYPHS ) != 0 ) {
+                if ( first ) {
+                    first = false;
+                } else {
+                    sb.append ( '|' );
+                }
+                sb.append ( "IgnoreBaseGlyphs" );
+            }
+            if ( ( flags & IGNORE_LIGATURE ) != 0 ) {
+                if ( first ) {
+                    first = false;
+                } else {
+                    sb.append ( '|' );
+                }
+                sb.append ( "IgnoreLigature" );
+            }
+            if ( ( flags & IGNORE_MARKS ) != 0 ) {
+                if ( first ) {
+                    first = false;
+                } else {
+                    sb.append ( '|' );
+                }
+                sb.append ( "IgnoreMarks" );
+            }
+            if ( ( flags & USE_MARK_FILTERING_SET ) != 0 ) {
+                if ( first ) {
+                    first = false;
+                } else {
+                    sb.append ( '|' );
+                }
+                sb.append ( "UseMarkFilteringSet" );
+            }
+            if ( sb.length() == 0 ) {
+                sb.append ( '-' );
+            }
+            return sb.toString();
+        }
+    }
+
+    private void readCoverageTableFormat1(FontFileReader in, String label, long tableOffset, int coverageFormat) throws IOException {
+        in.seekSet(tableOffset);
+        // skip over format (already known)
+        in.skip ( 2 );
+        // read glyph count
+        int ng = in.readTTFUShort();
+        int[] ga = new int[ng];
+        for ( int i = 0, n = ng; i < n; i++ ) {
+            int g = in.readTTFUShort();
+            ga[i] = g;
+            seCoverage.add ( Integer.valueOf(g) );
+        }
+        // dump info if debugging
+        if (log.isDebugEnabled()) {
+            log.debug(label + " glyphs: " + toString(ga) );
+        }
+    }
+
+    private void readCoverageTableFormat2(FontFileReader in, String label, long tableOffset, int coverageFormat) throws IOException {
+        in.seekSet(tableOffset);
+        // skip over format (already known)
+        in.skip ( 2 );
+        // read range record count
+        int nr = in.readTTFUShort();
+        int[] rsa = new int[nr];
+        int[] rea = new int[nr];
+        int[] rxa = new int[nr];
+        for ( int i = 0, n = nr; i < n; i++ ) {
+            // read range start
+            int s = in.readTTFUShort();
+            // read range end
+            int e = in.readTTFUShort();
+            // read range coverage index
+            int x = in.readTTFUShort();
+            // dump info if debugging
+            if (log.isDebugEnabled()) {
+                log.debug(label + " range[" + i + "]: [" + s + "," + e + "]: " + x );
+            }
+            rsa[i] = s;
+            rea[i] = e;
+            rxa[i] = x;
+            seCoverage.add ( new GlyphCoverageTable.CoverageRange ( s, e, x ) );
+        }
+    }
+
+    private void readCoverageTable(FontFileReader in, String label, long tableOffset) throws IOException {
+        long cp = in.getCurrentPos();
+        in.seekSet(tableOffset);
+        // read coverage table format
+        int cf = in.readTTFUShort();
+        if ( cf == 1 ) {
+            readCoverageTableFormat1 ( in, label, tableOffset, cf );
+        } else if ( cf == 2 ) {
+            readCoverageTableFormat2 ( in, label, tableOffset, cf );
+        }
+        in.seekSet ( cp );
+    }
+
+    /* not used yet
+    private void readClassDefTableFormat1(FontFileReader in, long tableOffset, int classFormat) throws IOException {
+        in.seekSet(tableOffset);
+        // skip over format (already known)
+        in.skip ( 2 );
+    }
+
+    private void readClassDefTableFormat2(FontFileReader in, long tableOffset, int classFormat) throws IOException {
+        in.seekSet(tableOffset);
+        // skip over format (already known)
+        in.skip ( 2 );
+    }
+
+    private void readClassDefTable(FontFileReader in, long tableOffset) throws IOException {
+        long cp = in.getCurrentPos();
+        in.seekSet(tableOffset);
+        // read class table format
+        int cf = in.readTTFUShort();
+        if ( cf == 1 ) {
+            readClassDefTableFormat1 ( in, tableOffset, cf );
+        } else if ( cf == 2 ) {
+            readClassDefTableFormat2 ( in, tableOffset, cf );
+        }
+        in.seekSet ( cp );
+    }
+    */
+
+    private void readSingleSubTableFormat1(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+        String tableTag = "GSUB";
+        in.seekSet(subtableOffset);
+        // skip over format (already known)
+        in.skip ( 2 );
+        // read coverage offset
+        int co = in.readTTFUShort();
+        // read delta glyph
+        int dg = in.readTTFShort();
+        // dump info if debugging
+        if (log.isDebugEnabled()) {
+            log.debug(tableTag + " single substitution format: " + subtableFormat + " (delta)" );
+            log.debug(tableTag + " single substitution coverage table offset: " + co );
+            log.debug(tableTag + " single substitution delta: " + dg );
+        }
+        // read coverage table
+        readCoverageTable ( in, tableTag + " single substitution coverage", subtableOffset + co );
+        seEntries.add ( Integer.valueOf ( dg ) );
+    }
+
+    private void readSingleSubTableFormat2(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+        String tableTag = "GSUB";
+        in.seekSet(subtableOffset);
+        // skip over format (already known)
+        in.skip ( 2 );
+        // read coverage offset
+        int co = in.readTTFUShort();
+        // read glyph count
+        int ng = in.readTTFUShort();
+        // dump info if debugging
+        if (log.isDebugEnabled()) {
+            log.debug(tableTag + " single substitution format: " + subtableFormat + " (mapped)" );
+            log.debug(tableTag + " single substitution coverage table offset: " + co );
+            log.debug(tableTag + " single substitution glyph count: " + ng );
+        }
+        // read coverage table
+        readCoverageTable ( in, tableTag + " single substitution coverage", subtableOffset + co );
+        // read glyph substitutions
+        int[] gsa = new int[ng];
+        for ( int i = 0, n = ng; i < n; i++ ) {
+            int gs = in.readTTFUShort();
+            if (log.isDebugEnabled()) {
+                log.debug(tableTag + " single substitution glyph[" + i + "]: " + gs );
+            }
+            gsa[i] = gs;
+            seEntries.add ( Integer.valueOf ( gs ) );
+        }
+    }
+
+    private int readSingleSubTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+        in.seekSet(subtableOffset);
+        // read substitution format
+        int sf = in.readTTFUShort();
+        if ( sf == 1 ) {
+            readSingleSubTableFormat1 ( in, lookupType, lookupFlags, subtableOffset, sf );
+        } else if ( sf == 2 ) {
+            readSingleSubTableFormat2 ( in, lookupType, lookupFlags, subtableOffset, sf );
+        }
+        return sf;
+    }
+
+    private void readMultipleSubTableFormat1(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+        String tableTag = "GSUB";
+        in.seekSet(subtableOffset);
+        // skip over format (already known)
+        in.skip ( 2 );
+        // read coverage offset
+        int co = in.readTTFUShort();
+        // read sequence count
+        int ns = in.readTTFUShort();
+        // dump info if debugging
+        if (log.isDebugEnabled()) {
+            log.debug(tableTag + " multiple substitution format: " + subtableFormat + " (mapped)" );
+            log.debug(tableTag + " multiple substitution coverage table offset: " + co );
+            log.debug(tableTag + " multiple substitution sequence count: " + ns );
+        }
+        // read coverage table
+        readCoverageTable ( in, tableTag + " multiple substitution coverage", subtableOffset + co );
+        // read sequence table offsets
+        int[] soa = new int[ns];
+        for ( int i = 0, n = ns; i < n; i++ ) {
+            soa[i] = in.readTTFUShort();
+        }
+        // read sequence tables
+        for ( int i = 0, n = ns; i < n; i++ ) {
+            int so = soa[i];
+            in.seekSet(subtableOffset + so);
+            // read glyph count
+            int ng = in.readTTFUShort();
+            int[] ga = new int[ng];
+            for ( int j = 0; j < ng; j++ ) {
+                int gs = in.readTTFUShort();
+                ga[j] = gs;
+            }
+            if (log.isDebugEnabled()) {
+                log.debug(tableTag + " multiple substitution sequence[" + i + "]: " + toString ( ga ) );
+            }
+        }
+    }
+
+    private int readMultipleSubTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+        in.seekSet(subtableOffset);
+        // read substitution format
+        int sf = in.readTTFUShort();
+        if ( sf == 1 ) {
+            readMultipleSubTableFormat1 ( in, lookupType, lookupFlags, subtableOffset, sf );
+        }
+        return sf;
+    }
+
+    private void readAlternateSubTableFormat1(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+        String tableTag = "GSUB";
+        in.seekSet(subtableOffset);
+        // skip over format (already known)
+        in.skip ( 2 );
+        // read coverage offset
+        int co = in.readTTFUShort();
+        // read alternate set count
+        int ns = in.readTTFUShort();
+        // dump info if debugging
+        if (log.isDebugEnabled()) {
+            log.debug(tableTag + " alternate substitution format: " + subtableFormat + " (mapped)" );
+            log.debug(tableTag + " alternate substitution coverage table offset: " + co );
+            log.debug(tableTag + " alternate substitution alternate set count: " + ns );
+        }
+        // read coverage table
+        readCoverageTable ( in, tableTag + " alternate substitution coverage", subtableOffset + co );
+        // read alternate set table offsets
+        int[] soa = new int[ns];
+        for ( int i = 0, n = ns; i < n; i++ ) {
+            soa[i] = in.readTTFUShort();
+        }
+        // read alternate set tables
+        for ( int i = 0, n = ns; i < n; i++ ) {
+            int so = soa[i];
+            in.seekSet(subtableOffset + so);
+            // read glyph count
+            int ng = in.readTTFUShort();
+            int[] ga = new int[ng];
+            for ( int j = 0; j < ng; j++ ) {
+                int gs = in.readTTFUShort();
+                ga[j] = gs;
+            }
+            if (log.isDebugEnabled()) {
+                log.debug(tableTag + " alternate substitution alternate set[" + i + "]: " + toString ( ga ) );
+            }
+        }
+    }
+
+    private int readAlternateSubTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+        in.seekSet(subtableOffset);
+        // read substitution format
+        int sf = in.readTTFUShort();
+        if ( sf == 1 ) {
+            readAlternateSubTableFormat1 ( in, lookupType, lookupFlags, subtableOffset, sf );
+        }
+        return sf;
+    }
+
+    private void readLigatureSubTableFormat1(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+        String tableTag = "GSUB";
+        in.seekSet(subtableOffset);
+        // skip over format (already known)
+        in.skip ( 2 );
+        // read coverage offset
+        int co = in.readTTFUShort();
+        // read ligature set count
+        int ns = in.readTTFUShort();
+        // dump info if debugging
+        if (log.isDebugEnabled()) {
+            log.debug(tableTag + " ligature substitution format: " + subtableFormat + " (mapped)" );
+            log.debug(tableTag + " ligature substitution coverage table offset: " + co );
+            log.debug(tableTag + " ligature substitution ligature set count: " + ns );
+        }
+        // read coverage table
+        readCoverageTable ( in, tableTag + " ligature substitution coverage", subtableOffset + co );
+        // read ligature set table offsets
+        int[] soa = new int[ns];
+        for ( int i = 0, n = ns; i < n; i++ ) {
+            soa[i] = in.readTTFUShort();
+        }
+        // read ligature set tables
+        for ( int i = 0, n = ns; i < n; i++ ) {
+            int so = soa[i];
+            in.seekSet(subtableOffset + so);
+            // read ligature table count
+            int nl = in.readTTFUShort();
+            int[] loa = new int[nl];
+            for ( int j = 0; j < nl; j++ ) {
+                loa[j] = in.readTTFUShort();
+            }
+            List ligs = new java.util.ArrayList();
+            for ( int j = 0; j < nl; j++ ) {
+                int lo = loa[j];
+                in.seekSet(subtableOffset + so + lo);
+                // read ligature glyph id
+                int lg = in.readTTFUShort();
+                // read ligature (input) component count
+                int nc = in.readTTFUShort();
+                int[] ca = new int [ nc - 1 ];
+                // read ligature (input) component glyph ids
+                for ( int k = 0; k < nc - 1; k++ ) {
+                    ca[k] = in.readTTFUShort();
+                }
+                if (log.isDebugEnabled()) {
+                    log.debug(tableTag + " ligature substitution ligature set[" + i + "]: ligature(" + lg + "), components: " + toString ( ca ) );
+                }
+                ligs.add ( new GlyphSubstitutionTable.Ligature ( lg, ca ) );
+            }
+            seEntries.add ( new GlyphSubstitutionTable.LigatureSet ( ligs ) );
+        }
+    }
+
+    private int readLigatureSubTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+        in.seekSet(subtableOffset);
+        // read substitution format
+        int sf = in.readTTFUShort();
+        if ( sf == 1 ) {
+            readLigatureSubTableFormat1 ( in, lookupType, lookupFlags, subtableOffset, sf );
+        }
+        return sf;
+    }
+
+    private int readContextSubTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+        in.seekSet(subtableOffset);
+        // read substitution format
+        int sf = in.readTTFUShort();
+        // [TBD] - implement me
+        return sf;
+    }
+
+    private void readChainedContextSubTableFormat1(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+        String tableTag = "GSUB";
+        in.seekSet(subtableOffset);
+        // skip over format (already known)
+        in.skip ( 2 );
+        // read coverage offset
+        int co = in.readTTFUShort();
+        // read subrule set count
+        int ns = in.readTTFUShort();
+        // dump info if debugging
+        if (log.isDebugEnabled()) {
+            log.debug(tableTag + " chained context substitution format: " + subtableFormat + " (simple)" );
+            log.debug(tableTag + " chained context substitution coverage table offset: " + co );
+            log.debug(tableTag + " chained context substitution subrule set count: " + ns );
+        }
+        // read coverage table
+        readCoverageTable ( in, tableTag + " chained context substitution coverage", subtableOffset + co );
+        // read subrule set table offsets
+        int[] soa = new int[ns];
+        for ( int i = 0, n = ns; i < n; i++ ) {
+            soa[i] = in.readTTFUShort();
+        }
+        // read subrule set tables
+        for ( int i = 0, n = ns; i < n; i++ ) {
+            int so = soa[i];
+            in.seekSet(subtableOffset + so);
+            // read subrule table count
+            int nst = in.readTTFUShort();
+            int[] stoa = new int[nst];
+            for ( int j = 0; j < nst; j++ ) {
+                stoa[j] = in.readTTFUShort();
+            }
+            for ( int j = 0; j < nst; j++ ) {
+                int sto = stoa[j];
+                in.seekSet(subtableOffset + so + sto);
+                // read backtrack glyph count
+                int nbg = in.readTTFUShort();
+                int[] bga = new int[nbg];
+                // read backtrack glyphs
+                for ( int k = 0; k < nbg; k++ ) {
+                    bga[k] = in.readTTFUShort();
+                }
+                // read input glyph count
+                int nig = in.readTTFUShort();
+                int[] iga = new int [ nig - 1 ];
+                // read input glyphs
+                for ( int k = 0; k < nig - 1; k++ ) {
+                    iga[k] = in.readTTFUShort();
+                }
+                // read lookahead glyph count
+                int nlg = in.readTTFUShort();
+                int[] lga = new int[nlg];
+                // read lookahead glyphs
+                for ( int k = 0; k < nlg; k++ ) {
+                    lga[k] = in.readTTFUShort();
+                }
+                // read substitution lookup record count
+                int nsl = in.readTTFUShort();
+                int[] sia = new int[nsl];
+                int[] lia = new int[nsl];
+                // read substitution lookup records
+                for ( int k = 0; k < nsl; k++ ) {
+                    // read sequence index
+                    sia[k] = in.readTTFUShort();
+                    // read lookup list index
+                    lia[k] = in.readTTFUShort();
+                }
+                if (log.isDebugEnabled()) {
+                    log.debug(tableTag + " chained context substitution subrule set[" + i + "]: backtrack [" + toString(bga) + "]" );
+                    log.debug(tableTag + " chained context substitution subrule set[" + i + "]: input     [" + toString(iga) + "]" );
+                    log.debug(tableTag + " chained context substitution subrule set[" + i + "]: lookahead [" + toString(lga) + "]" );
+                    log.debug(tableTag + " chained context substitution lookup count: " + nsl );
+                    for ( int k = 0; k < nsl; k++ ) {
+                        log.debug(tableTag + " chained context substitution lookup[" + i + "]: [" + sia[k] + "," + lia[k] + "]" );
+                    }
+                }
+            }
+        }
+    }
+
+    private void readChainedContextSubTableFormat2(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+        String tableTag = "GSUB";
+        in.seekSet(subtableOffset);
+        // skip over format (already known)
+        in.skip ( 2 );
+        // read coverage offset
+        int co = in.readTTFUShort();
+        // read backtrack classdef table offset
+        int bo = in.readTTFUShort();
+        // read input classdef table offset
+        int io = in.readTTFUShort();
+        // read lookahead classdef table offset
+        int lo = in.readTTFUShort();
+        // read subclass set count
+        int ns = in.readTTFUShort();
+        // dump info if debugging
+        if (log.isDebugEnabled()) {
+            log.debug(tableTag + " chained context substitution format: " + subtableFormat + " (class based)" );
+            log.debug(tableTag + " chained context substitution coverage table offset: " + co );
+            log.debug(tableTag + " chained context substitution backtrack classdef table offset: " + bo );
+            log.debug(tableTag + " chained context substitution input classdef table offset: " + io );
+            log.debug(tableTag + " chained context substitution lookahead classdef table offset: " + lo );
+            log.debug(tableTag + " chained context substitution subclass set count: " + ns );
+        }
+        // read coverage table
+        readCoverageTable ( in, tableTag + " chained context substitution coverage", subtableOffset + co );
+        // read subclass set table offsets
+        int[] soa = new int[ns];
+        for ( int i = 0, n = ns; i < n; i++ ) {
+            soa[i] = in.readTTFUShort();
+        }
+        // read subclass set tables
+        for ( int i = 0, n = ns; i < n; i++ ) {
+            int so = soa[i];
+            if ( so == 0 ) {
+                continue;
+            }
+            in.seekSet(subtableOffset + so);
+            // read subclass rule table count
+            int nst = in.readTTFUShort();
+            int[] stoa = new int[nst];
+            for ( int j = 0; j < nst; j++ ) {
+                stoa[j] = in.readTTFUShort();
+            }
+            for ( int j = 0; j < nst; j++ ) {
+                int sto = stoa[j];
+                in.seekSet(subtableOffset + so + sto);
+                // read backtrack class count
+                int nbc = in.readTTFUShort();
+                int[] bca = new int[nbc];
+                // read backtrack classes
+                for ( int k = 0; k < nbc; k++ ) {
+                    bca[k] = in.readTTFUShort();
+                }
+                // read input class count
+                int nic = in.readTTFUShort();
+                int[] ica = new int [ nic - 1 ];
+                // read inpput classes
+                for ( int k = 0; k < nic - 1; k++ ) {
+                    ica[k] = in.readTTFUShort();
+                }
+                // read lookahead class count
+                int nlc = in.readTTFUShort();
+                int[] lca = new int[nlc];
+                // read lookahead classes
+                for ( int k = 0; k < nlc; k++ ) {
+                    lca[k] = in.readTTFUShort();
+                }
+                // read substitution lookup record count
+                int nsl = in.readTTFUShort();
+                int[] sia = new int[nsl];
+                int[] lia = new int[nsl];
+                // read substitution lookup records
+                for ( int k = 0; k < nsl; k++ ) {
+                    // read sequence index
+                    sia[k] = in.readTTFUShort();
+                    // read lookup list index
+                    lia[k] = in.readTTFUShort();
+                }
+                if (log.isDebugEnabled()) {
+                    log.debug(tableTag + " chained context substitution subclass set[" + i + "]: backtrack [" + toString(bca) + "]" );
+                    log.debug(tableTag + " chained context substitution subclass set[" + i + "]: input     [" + toString(ica) + "]" );
+                    log.debug(tableTag + " chained context substitution subclass set[" + i + "]: lookahead [" + toString(lca) + "]" );
+                    log.debug(tableTag + " chained context substitution lookup count: " + nsl );
+                    for ( int k = 0; k < nsl; k++ ) {
+                        log.debug(tableTag + " chained context substitution lookup[" + i + "]: [" + sia[k] + "," + lia[k] + "]" );
+                    }
+                }
+            }
+        }
+    }
+
+    private void readChainedContextSubTableFormat3(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+        String tableTag = "GSUB";
+        in.seekSet(subtableOffset);
+        // skip over format (already known)
+        in.skip ( 2 );
+        // read backtrack glyph count
+        int nbg = in.readTTFUShort();
+        // read backtrack glyph coverage table offsets
+        int[] boa = new int[nbg];
+        for ( int i = 0; i < nbg; i++ ) {
+            boa[i] = in.readTTFUShort();
+        }
+        // read input glyph count
+        int nig = in.readTTFUShort();
+        // read input glyph coverage table offsets
+        int[] ioa = new int[nig];
+        for ( int i = 0; i < nig; i++ ) {
+            ioa[i] = in.readTTFUShort();
+        }
+        // read lookahead glyph count
+        int nlg = in.readTTFUShort();
+        // read lookahead glyph coverage table offsets
+        int[] loa = new int[nlg];
+        for ( int i = 0; i < nlg; i++ ) {
+            loa[i] = in.readTTFUShort();
+        }
+        // read substitution lookup record count
+        int nsl = in.readTTFUShort();
+        int[] sia = new int[nsl];
+        int[] lia = new int[nsl];
+        // read substitution lookup records
+        for ( int i = 0; i < nsl; i++ ) {
+            // read sequence index
+            sia[i] = in.readTTFUShort();
+            // read lookup list index
+            lia[i] = in.readTTFUShort();
+        }
+        // dump info if debugging
+        if (log.isDebugEnabled()) {
+            log.debug(tableTag + " chained context substitution format: " + subtableFormat + " (coverage based)" );
+            log.debug(tableTag + " chained context substitution backtrack coverage table offsets: " + toString(boa) );
+            log.debug(tableTag + " chained context substitution input coverage table offsets: " + toString(ioa) );
+            log.debug(tableTag + " chained context substitution lookahead coverage table offsets: " + toString(loa) );
+            log.debug(tableTag + " chained context substitution lookup count: " + nsl );
+            for ( int i = 0; i < nsl; i++ ) {
+                log.debug(tableTag + " chained context substitution lookup[" + i + "]: [" + sia[i] + "," + lia[i] + "]" );
+            }
+        }
+        // read backtrack coverage tables
+        for ( int i = 0; i < boa.length; i++ ) {
+            if (log.isDebugEnabled()) {
+                log.debug(tableTag + " chained context substitution backtrack coverage table[" + i + "]" );
+            }
+            readCoverageTable ( in, tableTag + " chained context substitution coverage", subtableOffset + boa [ i ] );
+        }
+        // read input coverage tables
+        for ( int i = 0; i < ioa.length; i++ ) {
+            if (log.isDebugEnabled()) {
+                log.debug(tableTag + " chained context substitution input coverage table[" + i + "]" );
+            }
+            readCoverageTable ( in, tableTag + " chained context substitution coverage", subtableOffset + ioa [ i ] );
+        }
+        // read lookahead coverage tables
+        for ( int i = 0; i < loa.length; i++ ) {
+            if (log.isDebugEnabled()) {
+                log.debug(tableTag + " chained context substitution lookahead coverage table[" + i + "]" );
+            }
+            readCoverageTable ( in, tableTag + " chained context substitution coverage", subtableOffset + loa [ i ] );
+        }
+    }
+
+    private int readChainedContextSubTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+        in.seekSet(subtableOffset);
+        // read substitution format
+        int sf = in.readTTFUShort();
+        if ( sf == 1 ) {
+            readChainedContextSubTableFormat1 ( in, lookupType, lookupFlags, subtableOffset, sf );
+        } else if ( sf == 2 ) {
+            readChainedContextSubTableFormat2 ( in, lookupType, lookupFlags, subtableOffset, sf );
+        } else if ( sf == 3 ) {
+            readChainedContextSubTableFormat3 ( in, lookupType, lookupFlags, subtableOffset, sf );
+        }
+        return sf;
+    }
+
+    private int readExtensionSubTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+        in.seekSet(subtableOffset);
+        // read substitution format
+        int sf = in.readTTFUShort();
+        // [TBD] - implement me
+        return sf;
+    }
+
+    private int readReverseChainedSingleSubTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+        in.seekSet(subtableOffset);
+        // read substitution format
+        int sf = in.readTTFUShort();
+        // [TBD] - implement me
+        return sf;
+    }
+
+    private void readGSUBSubtable(FontFileReader in, int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset) throws IOException {
+        initSESubState();
+        int subtableFormat = -1;
+        switch ( lookupType ) {
+        case GSUBLookupType.SINGLE:
+            subtableFormat = readSingleSubTable ( in, lookupType, lookupFlags, subtableOffset );
+            break;
+        case GSUBLookupType.MULTIPLE:
+            subtableFormat = readMultipleSubTable ( in, lookupType, lookupFlags, subtableOffset );
+            break;
+        case GSUBLookupType.ALTERNATE:
+            subtableFormat = readAlternateSubTable ( in, lookupType, lookupFlags, subtableOffset );
+            break;
+        case GSUBLookupType.LIGATURE:
+            subtableFormat = readLigatureSubTable ( in, lookupType, lookupFlags, subtableOffset );
+            break;
+        case GSUBLookupType.CONTEXT:
+            subtableFormat = readContextSubTable ( in, lookupType, lookupFlags, subtableOffset );
+            break;
+        case GSUBLookupType.CHAINED_CONTEXT:
+            subtableFormat = readChainedContextSubTable ( in, lookupType, lookupFlags, subtableOffset );
+            break;
+        case GSUBLookupType.REVERSE_CHAINED_SINGLE:
+            subtableFormat = readReverseChainedSingleSubTable ( in, lookupType, lookupFlags, subtableOffset );
+            break;
+        case GSUBLookupType.EXTENSION:
+            subtableFormat = readExtensionSubTable ( in, lookupType, lookupFlags, subtableOffset );
+            break;
+        default:
+            break;
+        }
+        extractSESubState ( GlyphTable.GLYPH_TABLE_TYPE_SUBSTITUTION, lookupType, lookupFlags, lookupSequence, subtableSequence, subtableFormat );
+        resetSESubState();
+    }
+
+    private int readSinglePosTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+        in.seekSet(subtableOffset);
+        // read substitution format
+        int sf = in.readTTFUShort();
+        // [TBD] - implement me
+        return sf;
+    }
+
+    private int readPairPosTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+        in.seekSet(subtableOffset);
+        // read substitution format
+        int sf = in.readTTFUShort();
+        // [TBD] - implement me
+        return sf;
+    }
+
+    private int readCursivePosTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+        in.seekSet(subtableOffset);
+        // read substitution format
+        int sf = in.readTTFUShort();
+        // [TBD] - implement me
+        return sf;
+    }
+
+    private int readMarkToBasePosTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+        in.seekSet(subtableOffset);
+        // read substitution format
+        int sf = in.readTTFUShort();
+        // [TBD] - implement me
+        return sf;
+    }
+
+    private int readMarkToLigaturePosTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+        in.seekSet(subtableOffset);
+        // read substitution format
+        int sf = in.readTTFUShort();
+        // [TBD] - implement me
+        return sf;
+    }
+
+    private int readMarkToMarkPosTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+        in.seekSet(subtableOffset);
+        // read substitution format
+        int sf = in.readTTFUShort();
+        // [TBD] - implement me
+        return sf;
+    }
+
+    private int readContextPosTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+        in.seekSet(subtableOffset);
+        // read substitution format
+        int sf = in.readTTFUShort();
+        // [TBD] - implement me
+        return sf;
+    }
+
+    private int readChainedContextPosTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+        in.seekSet(subtableOffset);
+        // read substitution format
+        int sf = in.readTTFUShort();
+        // [TBD] - implement me
+        return sf;
+    }
+
+    private int readExtensionPosTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+        in.seekSet(subtableOffset);
+        // read substitution format
+        int sf = in.readTTFUShort();
+        // [TBD] - implement me
+        return sf;
+    }
+
+    private void readGPOSSubtable(FontFileReader in, int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset) throws IOException {
+        initSESubState();
+        int subtableFormat = -1;
+        switch ( lookupType ) {
+        case GPOSLookupType.SINGLE:
+            subtableFormat = readSinglePosTable ( in, lookupType, lookupFlags, subtableOffset );
+            break;
+        case GPOSLookupType.PAIR:           
+            subtableFormat = readPairPosTable ( in, lookupType, lookupFlags, subtableOffset );
+            break;
+        case GPOSLookupType.CURSIVE:
+            subtableFormat = readCursivePosTable ( in, lookupType, lookupFlags, subtableOffset );
+            break;
+        case GPOSLookupType.MARK_TO_BASE:
+            subtableFormat = readMarkToBasePosTable ( in, lookupType, lookupFlags, subtableOffset );
+            break;
+        case GPOSLookupType.MARK_TO_LIGATURE:
+            subtableFormat = readMarkToLigaturePosTable ( in, lookupType, lookupFlags, subtableOffset );
+            break;
+        case GPOSLookupType.MARK_TO_MARK:
+            subtableFormat = readMarkToMarkPosTable ( in, lookupType, lookupFlags, subtableOffset );
+            break;
+        case GPOSLookupType.CONTEXT:
+            subtableFormat = readContextPosTable ( in, lookupType, lookupFlags, subtableOffset );
+            break;
+        case GPOSLookupType.CHAINED_CONTEXT:
+            subtableFormat = readChainedContextPosTable ( in, lookupType, lookupFlags, subtableOffset );
+            break;
+        case GPOSLookupType.EXTENSION:
+            subtableFormat = readExtensionPosTable ( in, lookupType, lookupFlags, subtableOffset );
+            break;
+        default:
+            break;
+        }
+        extractSESubState ( GlyphTable.GLYPH_TABLE_TYPE_POSITIONING, lookupType, lookupFlags, lookupSequence, subtableSequence, subtableFormat );
+        resetSESubState();
+    }
+
+    private void readLookupTable(FontFileReader in, String tableTag, int lookupSequence, long lookupTable) throws IOException {
+        boolean isGSUB = tableTag.equals ( "GSUB" );
+        boolean isGPOS = tableTag.equals ( "GPOS" );
+        in.seekSet(lookupTable);
+        // read lookup type
+        int lt = in.readTTFUShort();
+        // read lookup flags
+        int lf = in.readTTFUShort();
+        // read sub-table count
+        int ns = in.readTTFUShort();
+        // dump info if debugging
+        if (log.isDebugEnabled()) {
+            String lts;
+            if ( isGSUB ) {
+                lts = GSUBLookupType.toString ( lt );
+            } else if ( isGPOS ) {
+                lts = GPOSLookupType.toString ( lt );
+            } else {
+                lts = "?";
+            }
+            log.debug(tableTag + " lookup table type: " + lt + " (" + lts + ")" );
+            log.debug(tableTag + " lookup table flags: " + lf + " (" + LookupFlag.toString ( lf ) + ")" );
+            log.debug(tableTag + " lookup table subtable count: " + ns );
+        }
+        // read subtable offsets
+        int[] soa = new int[ns];
+        for ( int i = 0; i < ns; i++ ) {
+            int so = in.readTTFUShort();
+            if (log.isDebugEnabled()) {
+                log.debug(tableTag + " lookup table subtable offset: " + so );
+            }
+            soa[i] = so;
+        }
+        // read mark filtering set
+        if ( ( lf & LookupFlag.USE_MARK_FILTERING_SET ) != 0 ) {
+            // read mark filtering set
+            int fs = in.readTTFUShort();
+            // dump info if debugging
+            if (log.isDebugEnabled()) {
+                log.debug(tableTag + " lookup table mark filter set: " + fs );
+            }
+        }
+        // read subtables
+        for ( int i = 0; i < ns; i++ ) {
+            int so = soa[i];
+            if ( isGSUB ) {
+                readGSUBSubtable ( in, lt, lf, lookupSequence, i, lookupTable + so );
+            } else if ( isGPOS ) {
+                readGPOSSubtable ( in, lt, lf, lookupSequence, i, lookupTable + so );
+            }
+        }
+    }
+
+    private void readLookupList(FontFileReader in, String tableTag, long lookupList) throws IOException {
+        in.seekSet(lookupList);
+        // read lookup record count
+        int nl = in.readTTFUShort();
+        if (log.isDebugEnabled()) {
+            log.debug(tableTag + " lookup list record count: " + nl );
+        }
+        if ( nl > 0 ) {
+            int[] loa = new int[nl];
+            // read lookup records
+            for ( int i = 0, n = nl; i < n; i++ ) {
+                int lo = in.readTTFUShort();
+                if (log.isDebugEnabled()) {
+                    log.debug(tableTag + " lookup table offset: " + lo );
+                }
+                loa[i] = lo;
+            }
+            // read lookup tables
+            for ( int i = 0, n = nl; i < n; i++ ) {
+                if (log.isDebugEnabled()) {
+                    log.debug(tableTag + " lookup index: " + i );
+                }
+                readLookupTable ( in, tableTag, i, lookupList + loa [ i ] );
+            }
+        }
+    }
+
+    /**
+     * Read the common layout tables (used by GSUB and GPOS).
+     * @param in FontFileReader to read from
+     * @param scriptList offset to script list from beginning of font file
+     * @param featureList offset to feature list from beginning of font file
+     * @param lookupList offset to lookup list from beginning of font file
+     * @throws IOException In case of a I/O problem
+     */
+    private void readCommonLayoutTables(FontFileReader in, String tableTag, long scriptList, long featureList, long lookupList) throws IOException {
+        if ( scriptList > 0 ) {
+            readScriptList ( in, tableTag, scriptList );
+        }
+        if ( featureList > 0 ) {
+            readFeatureList ( in, tableTag, featureList );
+        }
+        if ( lookupList > 0 ) {
+            readLookupList ( in, tableTag, lookupList );
+        }
+    }
+
+    /**
+     * Read the GSUB table.
+     * @param in FontFileReader to read from
+     * @throws IOException In case of a I/O problem
+     */
+    private void readGSUB(FontFileReader in) throws IOException {
+        String tableTag = "GSUB";
+        // Initialize temporary state
+        initSEState();
+        // Read glyph substitution (GSUB) table
+        TTFDirTabEntry dirTab = (TTFDirTabEntry)dirTabs.get(tableTag);
+        if ( gpos != null ) {
+            if (log.isDebugEnabled()) {
+                log.debug(tableTag + ": ignoring duplicate table");
+            }
+        } else if (dirTab != null) {
+            seekTab(in, tableTag, 0);
+            int version = in.readTTFLong();
+            if (log.isDebugEnabled()) {
+                log.debug(tableTag + " version: " + ( version / 65536 ) + "." + ( version % 65536 ));
+            }
+            int slo = in.readTTFUShort();
+            int flo = in.readTTFUShort();
+            int llo = in.readTTFUShort();
+            if (log.isDebugEnabled()) {
+                log.debug(tableTag + " script list offset: " + slo );
+                log.debug(tableTag + " feature list offset: " + flo );
+                log.debug(tableTag + " lookup list offset: " + llo );
+            }
+            long to = dirTab.getOffset();
+            readCommonLayoutTables ( in, tableTag, to + slo, to + flo, to + llo );
+            GlyphSubstitutionTable gsub;
+            if ( ( gsub = constructGSUB() ) != null ) {
+                this.gsub = gsub;
+            }
+        }
+    }
+
+    /**
+     * Read the GPOS table.
+     * @param in FontFileReader to read from
+     * @throws IOException In case of a I/O problem
+     */
+    private void readGPOS(FontFileReader in) throws IOException {
+        String tableTag = "GPOS";
+        // Initialize temporary state
+        initSEState();
+        // Read glyph positioning (GPOS) table
+        TTFDirTabEntry dirTab = (TTFDirTabEntry)dirTabs.get(tableTag);
+        if ( gpos != null ) {
+            if (log.isDebugEnabled()) {
+                log.debug(tableTag + ": ignoring duplicate table");
+            }
+        } else if (dirTab != null) {
+            seekTab(in, tableTag, 0);
+            int version = in.readTTFLong();
+            if (log.isDebugEnabled()) {
+                log.debug(tableTag + " version: " + ( version / 65536 ) + "." + ( version % 65536 ));
+            }
+            int slo = in.readTTFUShort();
+            int flo = in.readTTFUShort();
+            int llo = in.readTTFUShort();
+            if (log.isDebugEnabled()) {
+                log.debug(tableTag + " script list offset: " + slo );
+                log.debug(tableTag + " feature list offset: " + flo );
+                log.debug(tableTag + " lookup list offset: " + llo );
+            }
+            long to = dirTab.getOffset();
+            readCommonLayoutTables ( in, tableTag, to + slo, to + flo, to + llo );
+            GlyphPositioningTable gpos;
+            if ( ( gpos = constructGPOS() ) != null ) {
+                this.gpos = gpos;
+            }
+        }
+    }
+
+    /**
+     * Construct the (internal representation of the) GSUB table based on previously
+     * parsed state.
+     * @returns glyph substitution table or null if insufficient or invalid state
+     */
+    private GlyphSubstitutionTable constructGSUB() {
+        GlyphSubstitutionTable gsub = null;
+        Map lookups;
+        if ( ( lookups = constructLookups() ) != null ) {
+            List subtables;
+            if ( ( subtables = constructGSUBSubtables() ) != null ) {
+                if ( ( lookups.size() > 0 ) && ( subtables.size() > 0 ) ) {
+                    gsub = new GlyphSubstitutionTable ( lookups, subtables );
+                }
+            }
+        }
+        resetSEState();
+        return gsub;
+    }
+
+    /**
+     * Construct the (internal representation of the) GPOS table based on previously
+     * parsed state.
+     * @returns glyph positioning table or null if insufficient or invalid state
+     */
+    private GlyphPositioningTable constructGPOS() {
+        GlyphPositioningTable gpos = null;
+        Map lookups;
+        if ( ( lookups = constructLookups() ) != null ) {
+            List subtables;
+            if ( ( subtables = constructGPOSSubtables() ) != null ) {
+                if ( ( lookups.size() > 0 ) && ( subtables.size() > 0 ) ) {
+                    gpos = new GlyphPositioningTable ( lookups, subtables );
+                }
+            }
+        }
+        resetSEState();
+        return gpos;
+    }
+
+    private void constructLookupsFeature ( Map lookups, String st, String lt, String fid ) {
+        Object[] fp = (Object[]) seFeatures.get ( fid );
+        if ( fp != null ) {
+            assert fp.length == 2;
+            String ft = (String) fp[0];                 // feature tag
+            List/*<String>*/ lul = (List) fp[1];        // list of lookup table ids
+            if ( ( ft != null ) && ( lul != null ) && ( lul.size() > 0 ) ) {
+                GlyphTable.LookupSpec ls = new GlyphTable.LookupSpec ( st, lt, ft );
+                lookups.put ( ls, lul );
+            }
+        }
+    }
+
+    private void constructLookupsFeatures ( Map lookups, String st, String lt, List/*<String>*/ fids ) {
+        for ( Iterator fit = fids.iterator(); fit.hasNext();) {
+            String fid = (String) fit.next();
+            constructLookupsFeature ( lookups, st, lt, fid );
+        }
+    }
+
+    private void constructLookupsLanguage ( Map lookups, String st, String lt, Map/*<String,Object[2]>*/ languages ) {
+        Object[] lp = (Object[]) languages.get ( lt );
+        if ( lp != null ) {
+            assert lp.length == 2;
+            if ( lp[0] != null ) {                      // required feature id
+                constructLookupsFeature ( lookups, st, lt, (String) lp[0] );
+            }
+            if ( lp[1] != null ) {                      // non-required features ids
+                constructLookupsFeatures ( lookups, st, lt, (List) lp[1] );
+            }
+        }
+    }
+
+    private void constructLookupsLanguages ( Map lookups, String st, List/*<String>*/ ll, Map/*<String,Object[2]>*/ languages ) {
+        for ( Iterator lit = ll.iterator(); lit.hasNext();) {
+            String lt = (String) lit.next();
+            constructLookupsLanguage ( lookups, st, lt, languages );
+        }
+    }
+
+    private Map constructLookups() {
+        Map/*<GlyphTable.LookupSpec,List<String>>*/ lookups = new java.util.LinkedHashMap();
+        for ( Iterator sit = seScripts.keySet().iterator(); sit.hasNext();) {
+            String st = (String) sit.next();
+            Object[] sp = (Object[]) seScripts.get ( st );
+            if ( sp != null ) {
+                assert sp.length == 3;
+                Map/*<String,Object[2]>*/ languages = (Map) sp[2];
+                if ( sp[0] != null ) {                  // default language
+                    constructLookupsLanguage ( lookups, st, (String) sp[0], languages );
+                }
+                if ( sp[1] != null ) {                  // non-default languages
+                    constructLookupsLanguages ( lookups, st, (List) sp[1], languages );
+                }
+            }
+        }
+        return lookups;
+    }
+
+    private List constructGSUBSubtables() {
+        List/*<GlyphSubtable>*/ subtables = new java.util.ArrayList();
+        if ( seSubtables != null ) {
+            for ( Iterator it = seSubtables.iterator(); it.hasNext();) {
+                Object[] stp = (Object[]) it.next();
+                GlyphSubtable st;
+                if ( ( st = constructGSUBSubtable ( stp ) ) != null ) {
+                    subtables.add ( st );
+                }
+            }
+        }
+        return subtables;
+    }
+
+    private GlyphSubtable constructGSUBSubtable ( Object[] stp ) {
+        GlyphSubtable st = null;
+        assert ( stp != null ) && ( stp.length == 8 );
+        Integer tt = (Integer) stp[0];
+        Integer lt = (Integer) stp[1];
+        Integer ln = (Integer) stp[2];
+        Integer lf = (Integer) stp[3];
+        // Integer sn = (Integer) stp[4]; // not used yet
+        Integer sf = (Integer) stp[5];
+        List coverage = (List) stp[6];
+        List entries = (List) stp[7];
+        if ( tt.intValue() == GlyphTable.GLYPH_TABLE_TYPE_SUBSTITUTION ) {
+            int type = GSUBLookupType.getSubtableType ( lt.intValue() );
+            String id = "lu" + ln.intValue();
+            int sequence = ln.intValue();
+            int flags = lf.intValue();
+            int format = sf.intValue();
+            st = GlyphSubstitutionTable.createSubtable ( type, id, sequence, flags, format, coverage, entries );
+        }
+        return st;
+    }
+
+    private List constructGPOSSubtables() {
+        List/*<GlyphSubtable>*/ subtables = new java.util.ArrayList();
+        return subtables;
+    }
+
+    private void initSEState() {
+        seScripts = new java.util.LinkedHashMap();
+        seLanguages = new java.util.LinkedHashMap();
+        seFeatures = new java.util.LinkedHashMap();
+        seSubtables = new java.util.ArrayList();
+        resetSESubState();
+    }
+
+    private void resetSEState() {
+        seScripts = null;
+        seLanguages = null;
+        seFeatures = null;
+        seSubtables = null;
+        resetSESubState();
+    }
+
+    private void initSESubState() {
+        seCoverage = new java.util.ArrayList();
+        seEntries = new java.util.ArrayList();
+    }
+
+    private void extractSESubState ( int tableType, int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, int subtableFormat ) {
+        if ( ( seCoverage != null ) && ( seCoverage.size() > 0 ) ) {
+            if ( ( seEntries != null ) && ( seEntries.size() > 0 ) ) {
+                if ( seSubtables != null ) {
+                    Integer tt = Integer.valueOf ( tableType );
+                    Integer lt = Integer.valueOf ( lookupType );
+                    Integer ln = Integer.valueOf ( lookupSequence );
+                    Integer lf = Integer.valueOf ( lookupFlags );
+                    Integer sn = Integer.valueOf ( subtableSequence );
+                    Integer sf = Integer.valueOf ( subtableFormat );
+                    seSubtables.add ( new Object[] { tt, lt, ln, lf, sn, sf, seCoverage, seEntries } );
+                }
+            }
+        }
+    }
+
+    private void resetSESubState() {
+        seCoverage = null;
+        seEntries = null;
+    }
+
+    /**
      * Return a List with TTFCmapEntry.
      * @return A list of TTFCmapEntry objects
      */
@@ -1714,4 +3245,4 @@
             ioe.printStackTrace(System.err);
         }
     }
-}
\ No newline at end of file
+}
Index: src/java/org/apache/fop/fonts/FontInfoConfigurator.java
===================================================================
--- src/java/org/apache/fop/fonts/FontInfoConfigurator.java	(revision 981406)
+++ src/java/org/apache/fop/fonts/FontInfoConfigurator.java	(working copy)
@@ -257,20 +257,25 @@
         }
 
         boolean useKerning = fontCfg.getAttributeAsBoolean("kerning", true);
+        boolean useAdvanced = fontCfg.getAttributeAsBoolean("advanced", true);
         EncodingMode encodingMode = EncodingMode.valueOf(
                 fontCfg.getAttribute("encoding-mode", EncodingMode.AUTO.getName()));
         EmbedFontInfo embedFontInfo
-                = new EmbedFontInfo(metricsUrl, useKerning, tripletList, embedUrl, subFont);
+            = new EmbedFontInfo(metricsUrl, useKerning, useAdvanced, tripletList, embedUrl,
+                                subFont);
         embedFontInfo.setEncodingMode(encodingMode);
+        boolean skipCachedFont = false;
         if (fontCache != null) {
             if (!fontCache.containsFont(embedFontInfo)) {
                 fontCache.addFont(embedFontInfo);
-            }
+            } else
+                skipCachedFont = true;
         }
 
         if (log.isDebugEnabled()) {
             String embedFile = embedFontInfo.getEmbedFile();
-            log.debug("Adding font " + (embedFile != null ? embedFile + ", " : "")
+            log.debug( ( skipCachedFont ? "Skipping (cached) font " : "Adding font " )
+                    + (embedFile != null ? embedFile + ", " : "")
                     + "metric file " + embedFontInfo.getMetricsFile());
             for (int j = 0; j < tripletList.size(); ++j) {
                 FontTriplet triplet = (FontTriplet) tripletList.get(j);
Index: src/java/org/apache/fop/fonts/ArabicScriptProcessor.java
===================================================================
--- src/java/org/apache/fop/fonts/ArabicScriptProcessor.java	(revision 0)
+++ src/java/org/apache/fop/fonts/ArabicScriptProcessor.java	(revision 0)
@@ -0,0 +1,525 @@
+/*
+ * 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.fonts;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.fop.text.bidi.BidiClassUtils;
+import org.apache.fop.util.BidiConstants;
+
+// CSOFF: AvoidNestedBlocksCheck
+// CSOFF: NoWhitespaceAfterCheck
+// CSOFF: InnerAssignmentCheck
+// CSOFF: SimplifyBooleanReturnCheck
+// CSOFF: LineLengthCheck
+
+/**
+ * <p>The <code>ArabicScriptProcessor</code> class implements script processor for
+ * performing glypph substitution and positioning operations on content associated with the Arabic script.</p>
+ * @author Glenn Adams
+ */
+public class ArabicScriptProcessor extends ScriptProcessor {
+
+    /**
+     * logging instance
+     */
+    protected static final Log log = LogFactory.getLog(ArabicScriptProcessor.class);                                    // CSOK: ConstantNameCheck
+
+    ArabicScriptProcessor ( String script ) {
+        super ( script );
+    }
+
+    /** {@inheritDoc} */
+    public GlyphSequence substitute ( GlyphSequence gs, String script, String language, Map/*<LookupSpec,GlyphSubtable[]>*/ lookups ) {
+        // finals
+        gs = subFina ( gs, script, language, (GlyphSubtable[]) lookups.get ( new GlyphTable.LookupSpec ( script, language, "fina" ) ) );
+
+        // medials
+        gs = subMedi ( gs, script, language, (GlyphSubtable[]) lookups.get ( new GlyphTable.LookupSpec ( script, language, "medi" ) ) );
+
+        // initials
+        gs = subInit ( gs, script, language, (GlyphSubtable[]) lookups.get ( new GlyphTable.LookupSpec ( script, language, "init" ) ) );
+
+        // isolates
+        gs = subIsol ( gs, script, language, (GlyphSubtable[]) lookups.get ( new GlyphTable.LookupSpec ( script, language, "isol" ) ) );
+
+        // required ligatures
+        gs = subLiga ( gs, script, language, (GlyphSubtable[]) lookups.get ( new GlyphTable.LookupSpec ( script, language, "rlig" ) ) );
+
+        // standard ligatures
+        gs = subLiga ( gs, script, language, (GlyphSubtable[]) lookups.get ( new GlyphTable.LookupSpec ( script, language, "liga" ) ) );
+
+        return gs;
+    }
+
+    /** {@inheritDoc} */
+    public int[] position ( GlyphSequence gs, String script, String language, Map/*<LookupSpec,GlyphSubtable[]>*/ lookups ) {
+        return null;
+    }
+
+    private static GlyphContextTester finalContextTester
+        = new GlyphContextTester() { public boolean test ( GlyphSequence gs, GlyphSequence.CharAssociation ca ) { return inFinalContext ( gs, ca ); } };
+
+    private GlyphSequence subFina ( GlyphSequence gs, String script, String language, GlyphSubtable[] sta ) {
+        return substituteSingle ( gs, script, language, "fina", sta, finalContextTester, false );
+    }
+
+    private static GlyphContextTester medialContextTester
+        = new GlyphContextTester() { public boolean test ( GlyphSequence gs, GlyphSequence.CharAssociation ca ) { return inMedialContext ( gs, ca ); } };
+
+    private GlyphSequence subMedi ( GlyphSequence gs, String script, String language, GlyphSubtable[] sta ) {
+        return substituteSingle ( gs, script, language, "medi", sta, medialContextTester, false );
+    }
+    
+    private static GlyphContextTester initialContextTester
+        = new GlyphContextTester() { public boolean test ( GlyphSequence gs, GlyphSequence.CharAssociation ca ) { return inInitialContext ( gs, ca ); } };
+
+    private GlyphSequence subInit ( GlyphSequence gs, String script, String language, GlyphSubtable[] sta ) {
+        return substituteSingle ( gs, script, language, "init", sta, initialContextTester, false );
+    }
+    
+    private static GlyphContextTester isolateContextTester
+        = new GlyphContextTester() { public boolean test ( GlyphSequence gs, GlyphSequence.CharAssociation ca ) { return inIsolateContext ( gs, ca ); } };
+
+    private GlyphSequence subIsol ( GlyphSequence gs, String script, String language, GlyphSubtable[] sta ) {
+        return substituteSingle ( gs, script, language, "isol", sta, isolateContextTester, false );
+    }
+    
+    private static GlyphContextTester ligatureContextTester
+        = new GlyphContextTester() { public boolean test ( GlyphSequence gs, GlyphSequence.CharAssociation ca ) { return inLigatureContext ( gs, ca ); } };
+
+    private GlyphSequence subLiga ( GlyphSequence gs, String script, String language, GlyphSubtable[] sta ) {
+        return substituteMultiple ( gs, script, language, "liga", sta, ligatureContextTester, false );
+    }
+
+    private GlyphSequence substituteSingle ( GlyphSequence gs, String script, String language, String feature, GlyphSubtable[] sta, GlyphContextTester tester, boolean reverse ) {
+        if ( ( sta != null ) && ( sta.length > 0 ) ) {
+            // enforce subtable type constraints
+            for ( int i = 0, n = sta.length; i < n; i++ ) {
+                GlyphSubtable st = sta [ i ];
+                if ( ! ( st instanceof GlyphSubstitutionSubtable ) ) {
+                    throw new IncompatibleSubtableException ( "'" + feature + "' feature requires glyph substitution subtable" );
+                }
+            }
+            CharSequence ga = gs.getGlyphs();
+            GlyphSequence.CharAssociation[] aa = gs.getAssociations();
+            List gsl = new ArrayList();
+            List cal = new ArrayList();
+            for ( int i = 0, n = ga.length(); i < n; i++ ) {
+                int k = reverse ? ( n - i - 1 ) : i;
+                GlyphSequence.CharAssociation a = aa [ k ];
+                GlyphSequence iss = gs.getGlyphSubsequence ( k, k + 1 );
+                GlyphSequence oss;
+                if ( tester.test ( iss, a ) ) {
+                    oss = doSubstitutions ( iss, script, language, sta );
+                } else {
+                    oss = iss;
+                }
+                gsl.add ( oss );
+                cal.add ( a );
+            }
+            gs = new GlyphSequence ( gs.getCharacters(), gsl, cal, reverse );
+        }
+        return gs;
+    }
+
+    private GlyphSequence substituteMultiple ( GlyphSequence gs, String script, String language, String feature, GlyphSubtable[] sta, GlyphContextTester tester, boolean reverse ) {
+        if ( ( sta != null ) && ( sta.length > 0 ) ) {
+            gs = doSubstitutions ( gs, script, language, sta );
+        }
+        return gs;
+    }
+
+    private GlyphSequence doSubstitutions ( GlyphSequence gs, String script, String language, GlyphSubtable[] sta ) {
+        for ( int i = 0, n = sta.length; i < n; i++ ) {
+            GlyphSubtable st = sta [ i ];
+            assert st instanceof GlyphSubstitutionSubtable;
+            gs = ( (GlyphSubstitutionSubtable) st ) . substitute ( gs, script, language );
+        }
+        return gs;
+    }
+
+    private static boolean inFinalContext ( GlyphSequence gs, GlyphSequence.CharAssociation a ) {
+        CharSequence cs = gs.getCharacters();
+        if ( cs.length() == 0 ) {
+            return false;
+        } else {
+            int s = a.getStart();
+            int e = a.getEnd();
+            if ( ! hasFinalPrecedingContext ( cs, s, e ) ) {
+                return false;
+            } else if ( forcesFinalThisContext ( cs, s, e ) ) {
+                if (log.isDebugEnabled()) {
+                    log.debug ( "+FIN: [" + a.getStart() + "," + a.getEnd() + "]: " + GlyphUtils.toString ( (CharSequence) gs ) );
+                }
+                return true;
+            } else if ( ! hasFinalFollowingContext ( cs, s, e ) ) {
+                return false;
+            } else {
+                if (log.isDebugEnabled()) {
+                    log.debug ( "+FIN: [" + a.getStart() + "," + a.getEnd() + "]: " + GlyphUtils.toString ( (CharSequence) gs ) );
+                }
+                return true;
+            }
+        }
+    }
+
+    private static boolean inMedialContext ( GlyphSequence gs, GlyphSequence.CharAssociation a ) {
+        CharSequence cs = gs.getCharacters();
+        if ( cs.length() == 0 ) {
+            return false;
+        } else {
+            int s = a.getStart();
+            int e = a.getEnd();
+            if ( ! hasMedialPrecedingContext ( cs, s, e ) ) {
+                return false;
+            } else if ( ! hasMedialThisContext ( cs, s, e ) ) {
+                return false;
+            } else if ( ! hasMedialFollowingContext ( cs, s, e ) ) {
+                return false;
+            } else {
+                if (log.isDebugEnabled()) {
+                    log.debug ( "+MED: [" + a.getStart() + "," + a.getEnd() + "]: " + GlyphUtils.toString ( (CharSequence) gs ) );
+                }
+                return true;
+            }
+        }
+    }
+
+    private static boolean inInitialContext ( GlyphSequence gs, GlyphSequence.CharAssociation a ) {
+        CharSequence cs = gs.getCharacters();
+        if ( cs.length() == 0 ) {
+            return false;
+        } else {
+            int s = a.getStart();
+            int e = a.getEnd();
+            if ( ! hasInitialPrecedingContext ( cs, s, e ) ) {
+                return false;
+            } else if ( ! hasInitialFollowingContext ( cs, s, e ) ) {
+                return false;
+            } else {
+                if (log.isDebugEnabled()) {
+                    log.debug ( "+INI: [" + a.getStart() + "," + a.getEnd() + "]: " + GlyphUtils.toString ( (CharSequence) gs ) );
+                }
+                return true;
+            }
+        }
+    }
+
+    private static boolean inIsolateContext ( GlyphSequence gs, GlyphSequence.CharAssociation a ) {
+        CharSequence cs = gs.getCharacters();
+        int n;
+        if ( ( n = cs.length() ) == 0 ) {
+            return false;
+        } else if ( ( a.getStart() == 0 ) && ( a.getEnd() == n ) ) {
+            if (log.isDebugEnabled()) {
+                log.debug ( "+ISO: [" + a.getStart() + "," + a.getEnd() + "]: " + GlyphUtils.toString ( (CharSequence) gs ) );
+            }
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    private static boolean inLigatureContext ( GlyphSequence gs, GlyphSequence.CharAssociation a ) {
+        CharSequence cs = gs.getCharacters();
+        if ( cs.length() == 0 ) {
+            return false;
+        } else {
+            int s = a.getStart();
+            int e = a.getEnd();
+            if ( ! hasLigaturePrecedingContext ( cs, s, e ) ) {
+                return false;
+            } else if ( ! hasLigatureFollowingContext ( cs, s, e ) ) {
+                return false;
+            } else {
+                if (log.isDebugEnabled()) {
+                    log.debug ( "+LIG: [" + a.getStart() + "," + a.getEnd() + "]: " + GlyphUtils.toString ( (CharSequence) gs ) );
+                }
+                return true;
+            }
+        }
+    }
+
+    private static boolean hasFinalPrecedingContext ( CharSequence cs, int s, int e ) {
+        int chp = 0;
+        int clp = 0;
+        for ( int i = s; i > 0; i-- ) {
+            chp = cs.charAt ( i - 1 );
+            clp = BidiClassUtils.getBidiClass ( chp );
+            if ( clp != BidiConstants.NSM ) {
+                break;
+            }
+        }
+        if ( clp != BidiConstants.AL ) {
+            return false;
+        } else if ( hasIsolateInitial ( chp ) ) {
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+    private static boolean forcesFinalThisContext ( CharSequence cs, int s, int e ) {
+        int chl = 0;
+        int cll = 0;
+        for ( int i = 0, n = e - s; i < n; i++ ) {
+            int k = n - i - 1;
+            chl = cs.charAt ( s + k );
+            cll = BidiClassUtils.getBidiClass ( chl );
+            if ( cll != BidiConstants.NSM ) {
+                break;
+            }
+        }
+        if ( cll != BidiConstants.AL ) {
+            return false;
+        }
+        if ( hasIsolateInitial ( chl ) ) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    private static boolean hasFinalFollowingContext ( CharSequence cs, int s, int e ) {
+        int chf = 0;
+        int clf = 0;
+        for ( int i = e, n = cs.length(); i < n; i++ ) {
+            chf = cs.charAt ( i );
+            clf = BidiClassUtils.getBidiClass ( chf );
+            if ( clf != BidiConstants.NSM ) {
+                break;
+            }
+        }
+        if ( clf != BidiConstants.AL ) {
+            return true;
+        } else if ( hasIsolateFinal ( chf ) ) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    private static boolean hasInitialPrecedingContext ( CharSequence cs, int s, int e ) {
+        int chp = 0;
+        int clp = 0;
+        for ( int i = s; i > 0; i-- ) {
+            chp = cs.charAt ( i - 1 );
+            clp = BidiClassUtils.getBidiClass ( chp );
+            if ( clp != BidiConstants.NSM ) {
+                break;
+            }
+        }
+        if ( clp != BidiConstants.AL ) {
+            return true;
+        } else if ( hasIsolateInitial ( chp ) ) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    private static boolean hasInitialFollowingContext ( CharSequence cs, int s, int e ) {
+        int chf = 0;
+        int clf = 0;
+        for ( int i = e, n = cs.length(); i < n; i++ ) {
+            chf = cs.charAt ( i );
+            clf = BidiClassUtils.getBidiClass ( chf );
+            if ( clf != BidiConstants.NSM ) {
+                break;
+            }
+        }
+        if ( clf != BidiConstants.AL ) {
+            return false;
+        } else if ( hasIsolateFinal ( chf ) ) {
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+    private static boolean hasMedialPrecedingContext ( CharSequence cs, int s, int e ) {
+        int chp = 0;
+        int clp = 0;
+        for ( int i = s; i > 0; i-- ) {
+            chp = cs.charAt ( i - 1 );
+            clp = BidiClassUtils.getBidiClass ( chp );
+            if ( clp != BidiConstants.NSM ) {
+                break;
+            }
+        }
+        if ( clp != BidiConstants.AL ) {
+            return false;
+        } else if ( hasIsolateInitial ( chp ) ) {
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+    private static boolean hasMedialThisContext ( CharSequence cs, int s, int e ) {
+        int chf = 0;
+        int clf = 0;
+        for ( int i = 0, n = e - s; i < n; i++ ) {
+            chf = cs.charAt ( s + i );
+            clf = BidiClassUtils.getBidiClass ( chf );
+            if ( clf != BidiConstants.NSM ) {
+                break;
+            }
+        }
+        if ( clf != BidiConstants.AL ) {
+            return false;
+        }
+        int chl = 0;
+        int cll = 0;
+        for ( int i = 0, n = e - s; i < n; i++ ) {
+            int k = n - i - 1;
+            chl = cs.charAt ( s + k );
+            cll = BidiClassUtils.getBidiClass ( chl );
+            if ( cll != BidiConstants.NSM ) {
+                break;
+            }
+        }
+        if ( cll != BidiConstants.AL ) {
+            return false;
+        }
+        if ( hasIsolateFinal ( chf ) ) {
+            return false;
+        } else if ( hasIsolateInitial ( chl ) ) {
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+    private static boolean hasMedialFollowingContext ( CharSequence cs, int s, int e ) {
+        int chf = 0;
+        int clf = 0;
+        for ( int i = e, n = cs.length(); i < n; i++ ) {
+            chf = cs.charAt ( i );
+            clf = BidiClassUtils.getBidiClass ( chf );
+            if ( clf != BidiConstants.NSM ) {
+                break;
+            }
+        }
+        if ( clf != BidiConstants.AL ) {
+            return false;
+        } else if ( hasIsolateFinal ( chf ) ) {
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+    private static boolean hasLigaturePrecedingContext ( CharSequence cs, int s, int e ) {
+        return true;
+    }
+
+    private static boolean hasLigatureFollowingContext ( CharSequence cs, int s, int e ) {
+        int chf = 0;
+        int clf = 0;
+        for ( int i = e, n = cs.length(); i < n; i++ ) {
+            chf = cs.charAt ( i );
+            clf = BidiClassUtils.getBidiClass ( chf );
+            if ( clf != BidiConstants.NSM ) {
+                break;
+            }
+        }
+        if ( clf == BidiConstants.AL ) {
+            return true;
+        } else  {
+            return false;
+        }
+    }
+
+    /**
+     * Ordered array of Unicode scalars designating those Arabic (Script) Letters
+     * which exhibit an isolated form in word initial position.
+     */
+    private static int[] isolatedInitials = {
+        0x0622, // ALEF WITH MADDA ABOVE
+        0x0623, // ALEF WITH HAMZA ABOVE
+        0x0624, // WAW WITH HAMZA ABOVE
+        0x0625, // ALEF WITH HAMZA BELOWW
+        0x0627, // ALEF
+        0x062F, // DAL
+        0x0630, // THAL
+        0x0631, // REH
+        0x0632, // ZAIN
+        0x0648, // WAW
+        0x0671, // ALEF WASLA
+        0x0672, // ALEF WITH WAVY HAMZA ABOVE
+        0x0673, // ALEF WITH WAVY HAMZA BELOW
+        0x0675, // HIGH HAMZA ALEF
+        0x0676, // HIGH HAMZA WAW
+        0x0677, // U WITH HAMZA ABOVE
+        0x0688, // DDAL
+        0x0689, // DAL WITH RING
+        0x068A, // DAL WITH DOT BELOW
+        0x068B, // DAL WITH DOT BELOW AND SMALL TAH
+        0x068C, // DAHAL
+        0x068D, // DDAHAL
+        0x068E, // DUL
+        0x068F, // DUL WITH THREE DOTS ABOVE DOWNWARDS
+        0x0690, // DUL WITH FOUR DOTS ABOVE
+        0x0691, // RREH
+        0x0692, // REH WITH SMALL V
+        0x0693, // REH WITH RING
+        0x0694, // REH WITH DOT BELOW
+        0x0695, // REH WITH SMALL V BELOW
+        0x0696, // REH WITH DOT BELOW AND DOT ABOVE
+        0x0697, // REH WITH TWO DOTS ABOVE
+        0x0698, // JEH
+        0x0699, // REH WITH FOUR DOTS ABOVE
+        0x06C4, // WAW WITH RING
+        0x06C5, // KIRGHIZ OE
+        0x06C6, // OE
+        0x06C7, // U
+        0x06C8, // YU
+        0x06C9, // KIRGHIZ YU
+        0x06CA, // WAW WITH TWO DOTS ABOVE
+        0x06CB, // VE
+        0x06CF, // WAW WITH DOT ABOVE
+        0x06EE, // DAL WITH INVERTED V
+        0x06EF  // REH WITH INVERTED V
+    };
+
+    private static boolean hasIsolateInitial ( int ch ) {
+        return Arrays.binarySearch ( isolatedInitials, ch ) >= 0;
+    }
+
+    /**
+     * Ordered array of Unicode scalars designating those Arabic (Script) Letters
+     * which exhibit an isolated form in word final position.
+     */
+    private static int[] isolatedFinals = {
+    };
+
+    private static boolean hasIsolateFinal ( int ch ) {
+        return Arrays.binarySearch ( isolatedFinals, ch ) >= 0;
+    }
+
+}
Index: src/java/org/apache/fop/fonts/LazyFont.java
===================================================================
--- src/java/org/apache/fop/fonts/LazyFont.java	(revision 981406)
+++ src/java/org/apache/fop/fonts/LazyFont.java	(working copy)
@@ -37,22 +37,23 @@
 /**
  * This class is used to defer the loading of a font until it is really used.
  */
-public class LazyFont extends Typeface implements FontDescriptor {
+public class LazyFont extends Typeface implements FontDescriptor, Substitutable, Positionable {
 
     private static Log log = LogFactory.getLog(LazyFont.class);
 
-    private String metricsFileName = null;
-    private String fontEmbedPath = null;
-    private boolean useKerning = false;
+    private String metricsFileName;
+    private String fontEmbedPath;
+    private boolean useKerning;
+    private boolean useAdvanced;
     private EncodingMode encodingMode = EncodingMode.AUTO;
-    private boolean embedded = true;
-    private String subFontName = null;
+    private boolean embedded;
+    private String subFontName;
 
-    private boolean isMetricsLoaded = false;
-    private Typeface realFont = null;
-    private FontDescriptor realFontDescriptor = null;
+    private boolean isMetricsLoaded;
+    private Typeface realFont;
+    private FontDescriptor realFontDescriptor;
 
-    private FontResolver resolver = null;
+    private FontResolver resolver;
 
     /**
      * Main constructor
@@ -64,6 +65,7 @@
         this.metricsFileName = fontInfo.getMetricsFile();
         this.fontEmbedPath = fontInfo.getEmbedFile();
         this.useKerning = fontInfo.getKerning();
+        this.useAdvanced = fontInfo.getAdvanced();
         this.encodingMode = fontInfo.getEncodingMode();
         this.subFontName = fontInfo.getSubFontName();
         this.embedded = fontInfo.isEmbedded();
@@ -72,9 +74,15 @@
 
     /** {@inheritDoc} */
     public String toString() {
-        return ( "metrics-url=" + metricsFileName + ", embed-url=" + fontEmbedPath
-                + ", kerning=" + useKerning );
-    }
+        StringBuffer sbuf = new StringBuffer(super.toString());
+        sbuf.append('{');
+        sbuf.append("metrics-url=" + metricsFileName);
+        sbuf.append(",embed-url=" + fontEmbedPath);
+        sbuf.append(",kerning=" + useKerning);
+        sbuf.append(",advanced=" + useAdvanced);
+        sbuf.append('}');
+        return sbuf.toString();
+    }   
 
     private void load(boolean fail) {
         if (!isMetricsLoaded) {
@@ -122,6 +130,7 @@
                                     new URL(metricsFileName).openStream()));
                     }
                     reader.setKerningEnabled(useKerning);
+                    reader.setAdvancedEnabled(useAdvanced);
                     if (this.embedded) {
                         reader.setFontEmbedPath(fontEmbedPath);
                     }
@@ -132,7 +141,7 @@
                         throw new RuntimeException("Cannot load font. No font URIs available.");
                     }
                     realFont = FontLoader.loadFont(fontEmbedPath, this.subFontName,
-                            this.embedded, this.encodingMode, useKerning, resolver);
+                            this.embedded, this.encodingMode, useKerning, useAdvanced, resolver);
                 }
                 if (realFont instanceof FontDescriptor) {
                     realFontDescriptor = (FontDescriptor) realFont;
@@ -375,5 +384,53 @@
         return realFontDescriptor.isEmbeddable();
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    public boolean performsSubstitution() {
+        load(true);
+        if ( realFontDescriptor instanceof Substitutable ) {
+            return ((Substitutable)realFontDescriptor).performsSubstitution();
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public CharSequence performSubstitution ( CharSequence cs, String script, String language ) {
+        load(true);
+        if ( realFontDescriptor instanceof Substitutable ) {
+            return ((Substitutable)realFontDescriptor).performSubstitution(cs, script, language);
+        } else {
+            return cs;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean performsPositioning() {
+        load(true);
+        if ( realFontDescriptor instanceof Substitutable ) {
+            return ((Positionable)realFontDescriptor).performsPositioning();
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int[] performPositioning ( CharSequence cs, String script, String language ) {
+        load(true);
+        if ( realFontDescriptor instanceof Substitutable ) {
+            return ((Positionable)realFontDescriptor).performPositioning(cs, script, language);
+        } else {
+            return null;
+        }
+    }
+
 }
 
Index: src/java/org/apache/fop/fonts/GlyphPositioning.java
===================================================================
--- src/java/org/apache/fop/fonts/GlyphPositioning.java	(revision 0)
+++ src/java/org/apache/fop/fonts/GlyphPositioning.java	(revision 0)
@@ -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.fonts;
+
+// CSOFF: LineLengthCheck
+
+/**
+ * The <code>GlyphPositioning</code> interface is implemented by a font related object
+ * that supports the determination of glyph positioning information based on script and
+ * language of the corresponding character content.
+ * @author Glenn Adams
+ */
+public interface GlyphPositioning {
+
+    /**
+     * Perform glyph positioning.
+     * @param gs sequence to map to output glyph sequence
+     * @param script the script associated with the characters corresponding to the glyph sequence
+     * @param language the language associated with the characters corresponding to the glyph sequence
+     * @return array (sequence) of pairs of position [DX,DY] offsets, one pair for each element of
+     * glyph sequence, or null if no non-zero offset applies
+     */
+    int[] position ( GlyphSequence gs, String script, String language );
+
+}
Index: src/java/org/apache/fop/fonts/FontType.java
===================================================================
--- src/java/org/apache/fop/fonts/FontType.java	(revision 981406)
+++ src/java/org/apache/fop/fonts/FontType.java	(working copy)
@@ -54,6 +54,8 @@
 
 
     /**
+     * @param name a font type name
+     * @param value a font type value
      * @see org.apache.avalon.framework.Enum#Enum(String)
      */
     protected FontType(String name, int value) {
Index: src/java/org/apache/fop/fonts/GlyphSequence.java
===================================================================
--- src/java/org/apache/fop/fonts/GlyphSequence.java	(revision 0)
+++ src/java/org/apache/fop/fonts/GlyphSequence.java	(revision 0)
@@ -0,0 +1,254 @@
+/*
+ * 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.fonts;
+
+import java.nio.CharBuffer;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+
+// CSOFF: NoWhitespaceAfterCheck
+// CSOFF: LineLengthCheck
+
+/**
+ * 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 CharSequence {
+
+    private CharSequence characters;
+    private CharSequence glyphs;
+    private CharAssociation[] associations;
+
+    /**
+     * Instantiate a glyph sequence.
+     * @param characters a (possibly empty) sequence of associated (originating) characters
+     * @param sequences a (possibly empty) list of glyph sequences
+     * @param associations a (possibly empty) list of glyph to character associations, one for each glyph in the concatenated glyph sequences
+     * @param reverse a boolean indicating if the glyphs are in reverse order with respect to the nominal inline progression direction
+     */
+    public GlyphSequence ( CharSequence characters, List/*<GlyphSequence>*/ sequences, List/*<CharAssociation>*/ associations, boolean reverse ) {
+        this ( characters, concatenateSequences ( sequences, reverse ), concatenateAssociations ( associations, reverse ) );
+    }
+
+    /**
+     * Instantiate a glyph sequence.
+     * @param characters a (possibly empty) sequence of associated (originating) characters
+     * @param glyphs a (possibly empty) list of glyphs
+     * @param associations a (possibly empty) list of glyph to character associations, one for each glyph in the concatenated glyph sequences
+     */
+    public GlyphSequence ( CharSequence characters, CharSequence glyphs, CharAssociation[] associations ) {
+        if ( ( characters == null ) || ( glyphs == null ) ) {
+            throw new IllegalArgumentException ( "characters and glyphs must be non-null" );
+        } else if ( ( associations != null ) && ( associations.length != glyphs.length() ) ) {
+            throw new IllegalArgumentException ( "number of associations must match number of glyphs" );
+        } else {
+            this.characters = characters;
+            this.glyphs = glyphs;
+            if ( associations == null ) {
+                associations = makeIdentityAssociations ( characters, glyphs );
+            }
+            this.associations = associations;
+        }
+    }
+
+    /** @return sequence of corresponding (originating) characters */
+    public CharSequence getCharacters() {
+        return characters;
+    }
+
+    /** @return sequence of glyphs in glyph sequence */
+    public CharSequence getGlyphs() {
+        return glyphs;
+    }
+
+    /** @return glyph to character associations, one for each glyph */
+    public CharAssociation[] getAssociations() {
+        return associations;
+    }
+
+    /**
+     * Obtain the sequence of characters that corresponds to the glyph sequence at interval
+     * [offset,offset+count).
+     * @param offset to first glyph
+     * @param count of glyphs
+     * @return corresponding character sequence
+     */
+    public CharSequence getCharsForGlyphs ( int offset, int count ) throws DiscontinuousAssociationException {          // CSOK: JavadocMethodCheck
+        int sFirst = -1, eLast = -1;
+        for ( int i = 0, n = count; i < n; i++ ) {
+            CharAssociation ca = associations [ offset + i ];
+            int s = ca.getStart();
+            int e = ca.getEnd();
+            if ( sFirst < 0 ) {
+                sFirst = s;
+            }
+            if ( eLast < 0 ) {
+                eLast = e;
+            } else if ( s == eLast ) {
+                eLast = e;
+            } else {
+                throw new DiscontinuousAssociationException();
+            }
+        }
+        return characters.subSequence ( sFirst, eLast );
+    }
+
+    /**
+     * Obtain the glyph subsequence corresponding to the half-open interval [start,end).
+     * @param start of subsequence
+     * @param end of subsequence
+     * @return a subsequence of this sequence
+     */
+    public GlyphSequence getGlyphSubsequence ( int start, int end ) {
+        CharAssociation[] subset = new CharAssociation[end - start];
+        System.arraycopy(associations, start, subset, 0, end - start);
+        return new GlyphSequence ( characters, glyphs.subSequence ( start, end ), subset );
+    }
+
+    /** @return the number of glyphs in this glyph sequence */
+    public int length() {
+        return glyphs.length();
+    }
+
+    /**
+     * Obtain glyph id at specified index.
+     * @param index to obtain glyph
+     * @return the glyph identifier of glyph at specified index
+     */
+    public char charAt ( int index ) {
+        return glyphs.charAt ( index );
+    }
+
+    /**
+     * Obtain glyph code subsequence over interval [start,end).
+     * @param start of subsequence
+     * @param end of subsequence
+     * @return the glyph code subsequence
+     */
+    public CharSequence subSequence ( int start, int end ) {
+        return glyphs.subSequence ( start, end );
+    }
+
+    /** {@inheritDoc} */
+    public String toString() {
+        return glyphs.toString();
+    }
+
+    private CharAssociation[] makeIdentityAssociations ( CharSequence characters, CharSequence glyphs ) {
+        int nc = characters.length();
+        int ng = glyphs.length();
+        CharAssociation[] ca = new CharAssociation [ ng ];
+        for ( int i = 0, n = ng; i < n; i++ ) {
+            int k = ( i > nc ) ? nc : i;
+            ca [ i ] = new CharAssociation ( i, ( k == nc ) ? 0 : 1 );
+        }
+        return ca;
+    }
+
+    private static CharSequence concatenateSequences ( List/*<GlyphSequence>*/ sequences, boolean reverse ) {
+        int ng = 0;
+        for ( Iterator it = sequences.iterator(); it.hasNext();) {
+            GlyphSequence gs = (GlyphSequence) it.next();
+            ng += gs.length();
+        }
+        CharBuffer cb = CharBuffer.allocate ( ng );
+        if ( ! reverse ) {
+            for ( ListIterator it = sequences.listIterator(); it.hasNext();) {
+                GlyphSequence gs = (GlyphSequence) it.next();
+                cb.append ( (CharSequence) gs );
+            }
+        } else {
+            for ( ListIterator it = sequences.listIterator ( sequences.size() ); it.hasPrevious();) {
+                GlyphSequence gs = (GlyphSequence) it.previous();
+                cb.append ( (CharSequence) gs );
+            }
+        }
+        cb.rewind();
+        return cb;
+    }
+
+    private static CharAssociation[] concatenateAssociations ( List/*<CharAssociation>*/ associations, boolean reverse ) {
+        int na = 0;
+        CharAssociation[] ca = new CharAssociation [ associations.size() ];
+        if ( ! reverse ) {
+            for ( ListIterator it = associations.listIterator(); it.hasNext();) {
+                CharAssociation a = (CharAssociation) it.next();
+                ca [ na++ ] = a;
+            }
+        } else {
+            for ( ListIterator it = associations.listIterator ( associations.size() ); it.hasPrevious();) {
+                CharAssociation a = (CharAssociation) it.previous();
+                ca [ na++ ] = a;
+            }
+        }
+        return ca;
+    }
+
+    /**
+     * A structure class encapsulating an interval of character codes (in a CharSequence)
+     * expressed as an offset and count (of code elements in a CharSequence, i.e., numbere of
+     * UTF-16 code elements. N.B. count does not necessarily designate the number of Unicode
+     * scalar values expressed by the CharSequence; in particular, it does not do so if there
+     * is one or more UTF-16 surrogate pairs present in the CharSequence.)
+     */
+    public static class CharAssociation {
+
+        private final int offset;
+        private final int count;
+
+        /**
+         * Instantiate a 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 = offset;
+            this.count = count;
+        }
+
+        /** @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();
+        }
+
+    }
+}
Index: src/java/org/apache/fop/fonts/EmbedFontInfo.java
===================================================================
--- src/java/org/apache/fop/fonts/EmbedFontInfo.java	(revision 981406)
+++ src/java/org/apache/fop/fonts/EmbedFontInfo.java	(working copy)
@@ -37,6 +37,8 @@
     protected String embedFile;
     /** false, to disable kerning */
     protected boolean kerning;
+    /** false, to disable advanced typographic features */
+    protected boolean advanced;
     /** the requested encoding mode for the font */
     protected EncodingMode encodingMode = EncodingMode.AUTO;
 
@@ -52,17 +54,19 @@
 
     /**
      * Main constructor
-     * @param metricsFile Path to the xml file containing font metrics
-     * @param kerning True if kerning should be enabled
-     * @param fontTriplets List of font triplets to associate with this font
-     * @param embedFile Path to the embeddable font file (may be null)
+     * @param metricsFile path to the xml file containing font metrics
+     * @param kerning true if kerning should be enabled
+     * @param advanced true if advanced typography features should be enabled
+     * @param fontTriplets list of font triplets to associate with this font
+     * @param embedFile path to the embeddable font file (may be null)
      * @param subFontName the sub-fontname used for TrueType Collections (null otherwise)
      */
-    public EmbedFontInfo(String metricsFile, boolean kerning,
+    public EmbedFontInfo(String metricsFile, boolean kerning, boolean advanced,
                     List/*<FontTriplet>*/ fontTriplets, String embedFile, String subFontName) {
         this.metricsFile = metricsFile;
         this.embedFile = embedFile;
         this.kerning = kerning;
+        this.advanced = advanced;
         this.fontTriplets = fontTriplets;
         this.subFontName = subFontName;
     }
@@ -85,13 +89,21 @@
 
     /**
      * Determines if kerning is enabled
-     * @return True if enabled
+     * @return true if enabled
      */
     public boolean getKerning() {
         return kerning;
     }
 
     /**
+     * Determines if advanced typographic features are enabled
+     * @return true if enabled
+     */
+    public boolean getAdvanced() {
+        return advanced;
+    }
+
+    /**
      * Returns the sub-font name of the font. This is primarily used for TrueType Collections
      * to select one of the sub-fonts. For all other fonts, this is always null.
      * @return the sub-font name (or null)
@@ -173,6 +185,7 @@
     public String toString() {
         return "metrics-url=" + metricsFile + ", embed-url=" + embedFile
             + ", kerning=" + kerning
+            + ", advanced=" + advanced
             + ", enc-mode=" + encodingMode
             + ", font-triplet=" + fontTriplets
             + (getSubFontName() != null ? ", sub-font=" + getSubFontName() : "")
Index: src/java/org/apache/fop/fonts/type1/Type1FontLoader.java
===================================================================
--- src/java/org/apache/fop/fonts/type1/Type1FontLoader.java	(revision 981406)
+++ src/java/org/apache/fop/fonts/type1/Type1FontLoader.java	(working copy)
@@ -52,7 +52,7 @@
      */
     public Type1FontLoader(String fontFileURI, boolean embedded, boolean useKerning,
             FontResolver resolver) throws IOException {
-        super(fontFileURI, embedded, useKerning, resolver);
+        super(fontFileURI, embedded, useKerning, true, resolver);
     }
 
     private String getPFMURI(String pfbURI) {
Index: src/java/org/apache/fop/fonts/type1/PFBParser.java
===================================================================
--- src/java/org/apache/fop/fonts/type1/PFBParser.java	(revision 981406)
+++ src/java/org/apache/fop/fonts/type1/PFBParser.java	(working copy)
@@ -161,7 +161,7 @@
     }
 
 
-    private static final boolean byteCmp(byte[] src, int srcOffset, byte[] cmp) {
+    private static boolean byteCmp(byte[] src, int srcOffset, byte[] cmp) {
         for (int i = 0; i < cmp.length; i++) {
             // System.out.println("Compare: " + src[srcOffset + i] + " " + cmp[i]);
             if (src[srcOffset + i] != cmp[i]) {
Index: src/java/org/apache/fop/fonts/CIDSubset.java
===================================================================
--- src/java/org/apache/fop/fonts/CIDSubset.java	(revision 981406)
+++ src/java/org/apache/fop/fonts/CIDSubset.java	(working copy)
@@ -55,6 +55,7 @@
      */
     private Map/*<Integer, Character>*/ usedCharsIndex = new java.util.HashMap();
 
+    /** default constructor */
     public CIDSubset() {
     }
 
Index: src/java/org/apache/fop/fonts/GlyphSubstitutionTable.java
===================================================================
--- src/java/org/apache/fop/fonts/GlyphSubstitutionTable.java	(revision 0)
+++ src/java/org/apache/fop/fonts/GlyphSubstitutionTable.java	(revision 0)
@@ -0,0 +1,609 @@
+/*
+ * 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.fonts;
+
+import java.nio.CharBuffer;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+// CSOFF: InnerAssignmentCheck
+// CSOFF: LineLengthCheck
+
+/**
+ * The <code>GlyphSubstitutionTable</code> class is a glyph table that implements
+ * <code>GlyphSubstitution</code> functionality.
+ * @author Glenn Adams
+ */
+public class GlyphSubstitutionTable extends GlyphTable implements GlyphSubstitution {
+
+    /** single substitution subtable type */
+    public static final int GSUB_LOOKUP_TYPE_SINGLE = 1;
+    /** multiple substitution subtable type */
+    public static final int GSUB_LOOKUP_TYPE_MULTIPLE = 2;
+    /** alternate substitution subtable type */
+    public static final int GSUB_LOOKUP_TYPE_ALTERNATE = 3;
+    /** ligature substitution subtable type */
+    public static final int GSUB_LOOKUP_TYPE_LIGATURE = 4;
+    /** context substitution subtable type */
+    public static final int GSUB_LOOKUP_TYPE_CONTEXT = 5;
+    /** chaining context substitution subtable type */
+    public static final int GSUB_LOOKUP_TYPE_CHAINING_CONTEXT = 6;
+    /** extension substitution substitution subtable type */
+    public static final int GSUB_LOOKUP_TYPE_EXTENSION_SUBSTITUTION = 7;
+    /** reverse chaining context single substitution subtable type */
+    public static final int GSUB_LOOKUP_TYPE_REVERSE_CHAINING_CONTEXT_SINGLE = 8;
+
+    /**
+     * Instantiate a <code>GlyphSubstitutionTable</code> object using the specified lookups
+     * and subtables.
+     * @param lookups a map of lookup specifications to subtable identifier strings
+     * @param subtables a list of identified subtables
+     */
+    public GlyphSubstitutionTable ( Map lookups, List subtables ) {
+        super ( lookups );
+        if ( ( subtables == null ) || ( subtables.size() == 0 ) ) {
+            throw new IllegalArgumentException ( "subtables must be non-empty" );
+        } else {
+            for ( Iterator it = subtables.iterator(); it.hasNext();) {
+                Object o = it.next();
+                if ( o instanceof GlyphSubstitutionSubtable ) {
+                    addSubtable ( (GlyphSubtable) o );
+                } else {
+                    throw new IllegalArgumentException ( "subtable must be a glyph substitution subtable" );
+                }
+            }
+        }
+    }
+
+    /**
+     * Map a lookup type name to its constant (integer) value.
+     * @param name lookup type name
+     * @return lookup type
+     */
+    public static int getLookupTypeFromName ( String name ) {
+        int t;
+        String s = name.toLowerCase();
+        if ( "single".equals ( s ) ) {
+            t = GSUB_LOOKUP_TYPE_SINGLE;
+        } else if ( "multiple".equals ( s ) ) {
+            t = GSUB_LOOKUP_TYPE_MULTIPLE;
+        } else if ( "alternate".equals ( s ) ) {
+            t = GSUB_LOOKUP_TYPE_ALTERNATE;
+        } else if ( "ligature".equals ( s ) ) {
+            t = GSUB_LOOKUP_TYPE_LIGATURE;
+        } else if ( "context".equals ( s ) ) {
+            t = GSUB_LOOKUP_TYPE_CONTEXT;
+        } else if ( "chainingcontext".equals ( s ) ) {
+            t = GSUB_LOOKUP_TYPE_CHAINING_CONTEXT;
+        } else if ( "extensionsubstitution".equals ( s ) ) {
+            t = GSUB_LOOKUP_TYPE_EXTENSION_SUBSTITUTION;
+        } else if ( "reversechainiingcontextsingle".equals ( s ) ) {
+            t = GSUB_LOOKUP_TYPE_REVERSE_CHAINING_CONTEXT_SINGLE;
+        } else {
+            t = -1;
+        }
+        return t;
+    }
+
+    /**
+     * Map a lookup type constant (integer) value to its name.
+     * @param type lookup type
+     * @return lookup type name
+     */
+    public static String getLookupTypeName ( int type ) {
+        String tn = null;
+        switch ( type ) {
+        case GSUB_LOOKUP_TYPE_SINGLE:
+            tn = "single";
+            break;
+        case GSUB_LOOKUP_TYPE_MULTIPLE:
+            tn = "multiple";
+            break;
+        case GSUB_LOOKUP_TYPE_ALTERNATE:
+            tn = "alternate";
+            break;
+        case GSUB_LOOKUP_TYPE_LIGATURE:
+            tn = "ligature";
+            break;
+        case GSUB_LOOKUP_TYPE_CONTEXT:
+            tn = "context";
+            break;
+        case GSUB_LOOKUP_TYPE_CHAINING_CONTEXT:
+            tn = "chainingcontext";
+            break;
+        case GSUB_LOOKUP_TYPE_EXTENSION_SUBSTITUTION:
+            tn = "extensionsubstitution";
+            break;
+        case GSUB_LOOKUP_TYPE_REVERSE_CHAINING_CONTEXT_SINGLE:
+            tn = "reversechainiingcontextsingle";
+            break;
+        default:
+            tn = "unknown";
+            break;
+        }
+        return tn;
+    }
+
+    /**
+     * Create a substitution subtable according to the specified arguments.
+     * @param type subtable type
+     * @param id subtable identifier
+     * @param sequence subtable sequence
+     * @param flags subtable flags
+     * @param format subtable format
+     * @param coverage subtable coverage table
+     * @param entries subtable entries
+     * @return a glyph subtable instance
+     */
+    public static GlyphSubtable createSubtable ( int type, String id, int sequence, int flags, int format, List coverage, List entries ) {
+        GlyphSubtable st = null;
+        switch ( type ) {
+        case GSUB_LOOKUP_TYPE_SINGLE:
+            st = new SimpleSubtable ( id, sequence, flags, format, coverage, entries );
+            break;
+        case GSUB_LOOKUP_TYPE_MULTIPLE:
+            st = new MultipleSubtable ( id, sequence, flags, format, coverage, entries );
+            break;
+        case GSUB_LOOKUP_TYPE_ALTERNATE:
+            st = new AlternateSubtable ( id, sequence, flags, format, coverage, entries );
+            break;
+        case GSUB_LOOKUP_TYPE_LIGATURE:
+            st = new LigatureSubtable ( id, sequence, flags, format, coverage, entries );
+            break;
+        case GSUB_LOOKUP_TYPE_CONTEXT:
+            st = new ContextSubtable ( id, sequence, flags, format, coverage, entries );
+            break;
+        case GSUB_LOOKUP_TYPE_CHAINING_CONTEXT:
+            st = new ChainingContextSubtable ( id, sequence, flags, format, coverage, entries );
+            break;
+        case GSUB_LOOKUP_TYPE_EXTENSION_SUBSTITUTION:
+            st = new ExtensionSubtable ( id, sequence, flags, format, coverage, entries );
+            break;
+        case GSUB_LOOKUP_TYPE_REVERSE_CHAINING_CONTEXT_SINGLE:
+            st = new ReverseChainingSingleSubtable ( id, sequence, flags, format, coverage, entries );
+            break;
+        default:
+            break;
+        }
+        return st;
+    }
+
+    /** {@inheritDoc} */
+    public GlyphSequence substitute ( GlyphSequence gs, String script, String language ) {
+        GlyphSequence ogs;
+        Map/*<LookupSpec,GlyphSubtable[]>*/ lookups = matchLookups ( script, language, "*" );
+        if ( ( lookups != null ) && ( lookups.size() > 0 ) ) {
+            ScriptProcessor sp = ScriptProcessor.getInstance ( script );
+            ogs = sp.substitute ( gs, script, language, lookups );
+        } else {
+            ogs = gs;
+        }
+        return ogs;
+    }
+
+    static class SimpleSubtable extends GlyphSubstitutionSubtable {
+        private int[] map;
+        public SimpleSubtable ( String id, int sequence, int flags, int format, List coverage, List entries ) {
+            super ( id, sequence, flags, format, GlyphCoverageTable.createCoverageTable ( coverage ) );
+            populate ( entries );
+        }
+        /** {@inheritDoc} */
+        public int getType() {
+            return GSUB_LOOKUP_TYPE_SINGLE;
+        }
+        /** {@inheritDoc} */
+        public List getEntries() {
+            List entries = new ArrayList ( map.length );
+            for ( int i = 0, n = map.length; i < n; i++ ) {
+                entries.add ( Integer.valueOf ( map[i] ) );
+            }
+            return entries;
+        }
+        /** {@inheritDoc} */
+        public GlyphSequence substitute ( GlyphSequence gs, String script, String language ) {
+            CharBuffer cb = CharBuffer.allocate ( gs.length() );
+            int ng = 0;
+            for ( int i = 0; i < gs.length(); i++ ) {
+                int gi = gs.charAt ( i );
+                int ci, go = gi;
+                if ( ( ci = getCoverageIndex ( gi ) ) >= 0 ) {
+                    assert ci < map.length : "coverage index out of range";
+                    if ( ci < map.length ) {
+                        go = map [ ci ];
+                    }
+                }
+                if ( ( go < 0 ) || ( go > 65535 ) ) {
+                    go = 65535;
+                }
+                cb.put ( (char) go );
+                ng++;
+            }
+            cb.limit(ng);
+            cb.rewind();
+            return new GlyphSequence ( gs.getCharacters(), (CharSequence) cb, null );
+        }
+        private void populate ( List entries ) {
+            int i = 0, n = entries.size();
+            int[] map = new int [ n ];
+            for ( Iterator it = entries.iterator(); it.hasNext();) {
+                Object o = it.next();
+                if ( o instanceof Integer ) {
+                    int gid = ( (Integer) o ) .intValue();
+                    if ( ( gid >= 0 ) && ( gid < 65536 ) ) {
+                        map [ i++ ] = gid;
+                    } else {
+                        throw new IllegalArgumentException ( "illegal glyph index: " + gid );
+                    }
+                } else {
+                    throw new IllegalArgumentException ( "illegal entries entry, must be Integer: " + o );
+                }
+            }
+            assert i == n;
+            assert this.map == null;
+            this.map = map;
+        }
+        /** {@inheritDoc} */
+        public String toString() {
+            StringBuffer sb = new StringBuffer(super.toString());
+            sb.append('{');
+            sb.append("coverage=");
+            sb.append(getCoverage().toString());
+            sb.append(",entries={");
+            for ( int i = 0, n = map.length; i < n; i++ ) {
+                if ( i > 0 ) {
+                    sb.append(',');
+                }
+                sb.append(Integer.toString(map[i]));
+            }
+            sb.append('}');
+            sb.append('}');
+            return sb.toString();
+        }
+    }
+
+    static class MultipleSubtable extends GlyphSubstitutionSubtable {
+        public MultipleSubtable ( String id, int sequence, int flags, int format, List coverage, List entries ) {
+            super ( id, sequence, flags, format, GlyphCoverageTable.createCoverageTable ( coverage ) );
+        }
+        /** {@inheritDoc} */
+        public int getType() {
+            return GSUB_LOOKUP_TYPE_MULTIPLE;
+        }
+        /** {@inheritDoc} */
+        public List getEntries() {
+            return null; // [TBD] - implement me
+        }
+    }
+
+    static class AlternateSubtable extends GlyphSubstitutionSubtable {
+        public AlternateSubtable ( String id, int sequence, int flags, int format, List coverage, List entries ) {
+            super ( id, sequence, flags, format, GlyphCoverageTable.createCoverageTable ( coverage ) );
+        }
+        /** {@inheritDoc} */
+        public int getType() {
+            return GSUB_LOOKUP_TYPE_ALTERNATE;
+        }
+        /** {@inheritDoc} */
+        public List getEntries() {
+            return null; // [TBD] - implement me
+        }
+    }
+
+    static class LigatureSubtable extends GlyphSubstitutionSubtable {
+        private LigatureSet[] map;
+        public LigatureSubtable ( String id, int sequence, int flags, int format, List coverage, List entries ) {
+            super ( id, sequence, flags, format, GlyphCoverageTable.createCoverageTable ( coverage ) );
+            populate ( entries );
+        }
+        /** {@inheritDoc} */
+        public int getType() {
+            return GSUB_LOOKUP_TYPE_LIGATURE;
+        }
+        /** {@inheritDoc} */
+        public List getEntries() {
+            List entries = new ArrayList ( map.length );
+            for ( int i = 0, n = map.length; i < n; i++ ) {
+                entries.add ( map[i] );
+            }
+            return entries;
+        }
+        /** {@inheritDoc} */
+        public GlyphSequence substitute ( GlyphSequence gs, String script, String language ) {
+            CharBuffer cb = CharBuffer.allocate ( gs.length() );
+            int ng = 0;
+            for ( int i = 0, n = gs.length(); i < n; i++ ) {
+                int gi = gs.charAt ( i );
+                int ci, go = gi;
+                LigatureSet ls = null;
+                if ( ( ci = getCoverageIndex ( gi ) ) >= 0 ) {
+                    assert ci < map.length : "coverage index out of range";
+                    if ( ci < map.length ) {
+                        ls = map [ ci ];
+                    }
+                }
+                if ( ls != null ) {
+                    Ligature l;
+                    if ( ( l = findLigature ( ls, gs, i ) ) != null ) {
+                        go = l.getLigature();
+                        i += l.getNumComponents();
+                    }
+                }
+                if ( ( go < 0 ) || ( go > 65535 ) ) {
+                    go = 65535;
+                }
+                cb.put ( (char) go );
+                ng++;
+            }
+            cb.limit(ng);
+            cb.rewind();
+            return new GlyphSequence ( gs.getCharacters(), (CharSequence) cb, null );
+        }
+        private void populate ( List entries ) {
+            int i = 0, n = entries.size();
+            LigatureSet[] map = new LigatureSet [ n ];
+            for ( Iterator it = entries.iterator(); it.hasNext();) {
+                Object o = it.next();
+                if ( o instanceof LigatureSet ) {
+                    map [ i++ ] = (LigatureSet) o;
+                } else {
+                    throw new IllegalArgumentException ( "illegal ligatures entry, must be LigatureSet: " + o );
+                }
+            }
+            assert i == n;
+            assert this.map == null;
+            this.map = map;
+        }
+        private Ligature findLigature ( LigatureSet ls, CharSequence cs, int offset ) {
+            Ligature[] la = ls.getLigatures();
+            int k = -1;
+            int maxComponents = -1;
+            for ( int i = 0, n = la.length; i < n; i++ ) {
+                Ligature l = la [ i ];
+                if ( l.matchesComponents ( cs, offset + 1 ) ) {
+                    int nc = l.getNumComponents();
+                    if ( nc > maxComponents ) {
+                        maxComponents = nc;
+                        k = i;
+                    }
+                }
+            }
+            if ( k >= 0 ) {
+                return la [ k ];
+            } else {
+                return null;
+            }
+        }
+        /** {@inheritDoc} */
+        public String toString() {
+            StringBuffer sb = new StringBuffer(super.toString());
+            sb.append('{');
+            sb.append("coverage=");
+            sb.append(getCoverage().toString());
+            sb.append(",entries={");
+            for ( int i = 0, n = map.length; i < n; i++ ) {
+                if ( i > 0 ) {
+                    sb.append(',');
+                }
+                sb.append(map[i]);
+            }
+            sb.append('}');
+            sb.append('}');
+            return sb.toString();
+        }
+    }
+
+    static class ContextSubtable extends GlyphSubstitutionSubtable {
+        public ContextSubtable ( String id, int sequence, int flags, int format, List coverage, List entries ) {
+            super ( id, sequence, flags, format, GlyphCoverageTable.createCoverageTable ( coverage ) );
+        }
+        /** {@inheritDoc} */
+        public int getType() {
+            return GSUB_LOOKUP_TYPE_CONTEXT;
+        }
+        /** {@inheritDoc} */
+        public List getEntries() {
+            return null; // [TBD] - implement me
+        }
+    }
+
+    static class ChainingContextSubtable extends GlyphSubstitutionSubtable {
+        public ChainingContextSubtable ( String id, int sequence, int flags, int format, List coverage, List entries ) {
+            super ( id, sequence, flags, format, GlyphCoverageTable.createCoverageTable ( coverage ) );
+        }
+        /** {@inheritDoc} */
+        public int getType() {
+            return GSUB_LOOKUP_TYPE_CHAINING_CONTEXT;
+        }
+        /** {@inheritDoc} */
+        public List getEntries() {
+            return null; // [TBD] - implement me
+        }
+    }
+
+    static class ExtensionSubtable extends GlyphSubstitutionSubtable {
+        public ExtensionSubtable ( String id, int sequence, int flags, int format, List coverage, List entries ) {
+            super ( id, sequence, flags, format, GlyphCoverageTable.createCoverageTable ( coverage ) );
+        }
+        /** {@inheritDoc} */
+        public int getType() {
+            return GSUB_LOOKUP_TYPE_EXTENSION_SUBSTITUTION;
+        }
+        /** {@inheritDoc} */
+        public List getEntries() {
+            return null; // [TBD] - implement me
+        }
+    }
+
+    static class ReverseChainingSingleSubtable extends GlyphSubstitutionSubtable {
+        public ReverseChainingSingleSubtable ( String id, int sequence, int flags, int format, List coverage, List entries ) {
+            super ( id, sequence, flags, format, GlyphCoverageTable.createCoverageTable ( coverage ) );
+        }
+        /** {@inheritDoc} */
+        public int getType() {
+            return GSUB_LOOKUP_TYPE_REVERSE_CHAINING_CONTEXT_SINGLE;
+        }
+        /** {@inheritDoc} */
+        public List getEntries() {
+            return null; // [TBD] - implement me
+        }
+    }
+
+    /**
+     * The <code>Ligature</code> class implements a ligature lookup result in terms of
+     * a ligature glyph (code) and the <emph>N+1...</emph> components that comprise the ligature,
+     * where the <emph>Nth</emph> component was consumed in the coverage table lookup mapping to
+     * this ligature instance.
+     */
+    public static class Ligature {
+
+        private final int ligature;                     // (resulting) ligature glyph 
+        private final int[] components;                 // component glyph codes (note that first component is implied)
+
+        /**
+         * Instantiate a ligature.
+         * @param ligature glyph id
+         * @param components sequence of <emph>N+1...</emph> component glyph (or character) identifiers
+         */
+        public Ligature ( int ligature, int[] components ) {
+            if ( ( ligature < 0 ) || ( ligature > 65535 ) ) {
+                throw new IllegalArgumentException ( "invalid ligature glyph index: " + ligature );
+            } else if ( ( components == null ) || ( components.length == 0 ) ) {
+                throw new IllegalArgumentException ( "invalid ligature components, must be non-empty array" );
+            } else {
+                for ( int i = 0, n = components.length; i < n; i++ ) {
+                    int c = components [ i ];
+                    if ( ( c < 0 ) || ( c > 65535 ) ) {
+                        throw new IllegalArgumentException ( "invalid component glyph index: " + c );
+                    }
+                }
+                this.ligature = ligature;
+                this.components = components;
+            }
+        }
+
+        /** @return ligature glyph id */
+        public int getLigature() {
+            return ligature;
+        }
+
+        /** @return array of <emph>N+1...</emph> components */
+        public int[] getComponents() {
+            return components;
+        }
+
+        /** @return components count */
+        public int getNumComponents() {
+            return components.length;
+        }
+
+        /**
+         * Determine of input sequence at offset matches ligature's components.
+         * @param cs glyph (or character) sequence to match this ligature against
+         * @param offset index at which to start matching the components of this ligature
+         * @return true if matches
+         */
+        public boolean matchesComponents ( CharSequence cs, int offset ) {
+            if ( ( offset + components.length ) > cs.length() ) {
+                return false;
+            } else {
+                for ( int i = 0, n = components.length; i < n; i++ ) {
+                    if ( (int) cs.charAt ( offset + i ) != components [ i ] ) {
+                        return false;
+                    }
+                }
+                return true;
+            }
+        }
+
+        /** {@inheritDoc} */
+        public String toString() {
+            StringBuffer sb = new StringBuffer();
+            sb.append("{components={");
+            for ( int i = 0, n = components.length; i < n; i++ ) {
+                if ( i > 0 ) {
+                    sb.append(',');
+                }
+                sb.append(Integer.toString(components[i]));
+            }
+            sb.append("},ligature=");
+            sb.append(Integer.toString(ligature));
+            sb.append("}");
+            return sb.toString();
+        }
+
+    }
+
+    /**
+     * The <code>LigatureSet</code> class implements a set of  ligatures.
+     */
+    public static class LigatureSet {
+
+        private final Ligature[] ligatures;                     // set of ligatures all of which share the first (implied) component
+
+        /**
+         * Instantiate a set of ligatures.
+         * @param ligatures collection of ligatures
+         */
+        public LigatureSet ( List ligatures ) {
+            this ( (Ligature[]) ligatures.toArray ( new Ligature [ ligatures.size() ] ) );
+        }
+
+        /**
+         * Instantiate a set of ligatures.
+         * @param ligatures array of ligatures
+         */
+        public LigatureSet ( Ligature[] ligatures ) {
+            if ( ( ligatures == null ) || ( ligatures.length == 0 ) ) {
+                throw new IllegalArgumentException ( "invalid ligatures, must be non-empty array" );
+            } else {
+                this.ligatures = ligatures;
+            }
+        }
+
+        /** @return array of ligatures in this ligature set */
+        public Ligature[] getLigatures() {
+            return ligatures;
+        }
+
+        /** @return count of ligatures in this ligature set */
+        public int getNumLigatures() {
+            return ligatures.length;
+        }
+
+        /** {@inheritDoc} */
+        public String toString() {
+            StringBuffer sb = new StringBuffer();
+            sb.append("{ligs={");
+            for ( int i = 0, n = ligatures.length; i < n; i++ ) {
+                if ( i > 0 ) {
+                    sb.append(',');
+                }
+                sb.append(ligatures[i]);
+            }
+            sb.append("}}");
+            return sb.toString();
+        }
+
+    }
+
+}
Index: src/java/org/apache/fop/fonts/FontReader.java
===================================================================
--- src/java/org/apache/fop/fonts/FontReader.java	(revision 981406)
+++ src/java/org/apache/fop/fonts/FontReader.java	(working copy)
@@ -31,12 +31,15 @@
 import org.xml.sax.InputSource;
 import org.xml.sax.Locator;
 import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
 import org.xml.sax.XMLReader;
 import org.xml.sax.helpers.DefaultHandler;
 
 import org.apache.fop.apps.FOPException;
 import org.apache.fop.fonts.apps.TTFReader;
 
+// CSOFF: LineLengthCheck
+
 /**
  * Class for reading a metric.xml file and creating a font object.
  * Typical usage:
@@ -63,6 +66,31 @@
 
     private List bfranges = null;
 
+    /* advanced typographic (script extras) support */
+    private boolean inScriptExtras = false;
+    private int seTable = -1;
+    private Map seLookups = null;
+    private String seScript = null;
+    private String seLanguage = null;
+    private String seFeature = null;
+    private String seUseLookup = null;
+    private List seUseLookups = null;
+    private String luID = null;
+    private int luType = -1;
+    private List ltSubtables = null;
+    private int luSequence = -1;
+    private int luFlags = 0;
+    private int lstSequence = -1;
+    private int lstFormat = -1;
+    private List lstCoverage = null;
+    private List lstGIDs = null;
+    private List lstRanges = null;
+    private List lstEntries = null;
+    private List lstLIGSets = null;
+    private List lstLIGs = null;
+    private int ligGID = -1;
+    /* end of script extras parse state */
+
     private void createFont(InputSource source) throws FOPException {
         XMLReader parser = null;
 
@@ -114,6 +142,14 @@
     }
 
     /**
+     * Enable/disable use of advanced typographic features for the font
+     * @param enabled true to enable, false to disable
+     */
+    public void setAdvancedEnabled(boolean enabled) {
+        returnFont.setAdvancedEnabled(enabled);
+    }
+
+    /**
      * Sets the font resolver. Needed for URI resolution.
      * @param resolver the font resolver
      */
@@ -158,7 +194,9 @@
      */
     public void startElement(String uri, String localName, String qName,
                              Attributes attributes) throws SAXException {
-        if (localName.equals("font-metrics")) {
+        if ( inScriptExtras ) {
+            startElementScriptExtras ( uri, localName, qName, attributes );
+        } else if (localName.equals("font-metrics")) {
             if ("TYPE0".equals(attributes.getValue("type"))) {
                 multiFont = new MultiByteFont();
                 returnFont = multiFont;
@@ -208,7 +246,10 @@
         } else if ("pair".equals(localName)) {
             currentKerning.put(new Integer(attributes.getValue("kpx2")),
                                new Integer(attributes.getValue("kern")));
+        } else if ("script-extras".equals(localName)) {
+            inScriptExtras = true;
         }
+
     }
 
     private int getInt(String str) throws SAXException {
@@ -226,7 +267,9 @@
      */
     public void endElement(String uri, String localName, String qName) throws SAXException {
         String content = text.toString().trim();
-        if ("font-name".equals(localName)) {
+        if ( inScriptExtras ) {
+            endElementScriptExtras ( uri, localName, qName, content );
+        } else if ("font-name".equals(localName)) {
             returnFont.setFontName(content);
         } else if ("full-name".equals(localName)) {
             returnFont.setFullName(content);
@@ -303,6 +346,390 @@
     public void characters(char[] ch, int start, int length) {
         text.append(ch, start, length);
     }
+
+    private void validateScriptTag ( String tag )
+        throws SAXException
+    {
+    }
+
+    private void validateLanguageTag ( String tag, String script )
+        throws SAXException
+    {
+    }
+
+    private void validateFeatureTag ( String tag, int tableType, String script, String language )
+        throws SAXException
+    {
+    }
+
+    private int mapLookupType ( String type, int tableType )
+    {
+        int t = -1;
+        if ( tableType == GlyphTable.GLYPH_TABLE_TYPE_SUBSTITUTION ) {
+            t = GlyphSubstitutionTable.getLookupTypeFromName ( type );
+        } else if ( tableType == GlyphTable.GLYPH_TABLE_TYPE_POSITIONING ) {
+            t = GlyphPositioningTable.getLookupTypeFromName ( type );
+        }
+        return t;
+    }
+
+    private void validateLookupType ( String type, int tableType )
+        throws SAXException
+    {
+        if ( mapLookupType ( type, tableType ) == -1 )
+            throw new SAXParseException ( "invalid lookup type \'" + type + "\'", locator );
+    }
+
+    private void startElementScriptExtras ( String uri, String localName, String qName, Attributes attributes )
+        throws SAXException
+    {
+        if ( "gsub".equals(localName) ) {
+            assert seLookups == null;
+            seLookups = new java.util.HashMap();
+            seTable = GlyphTable.GLYPH_TABLE_TYPE_SUBSTITUTION;
+        } else if ( "gpos".equals(localName) ) {
+            assert seLookups == null;
+            seLookups = new java.util.HashMap();
+            seTable = GlyphTable.GLYPH_TABLE_TYPE_POSITIONING;
+        } else if ( "script".equals(localName) ) {
+            String tag = attributes.getValue("tag");
+            if ( tag != null ) {
+                assert seScript == null;
+                validateScriptTag ( tag );
+                seScript = tag;
+            } else {
+                throw new SAXParseException ( "missing tag attribute on <script/> element", locator );
+            }
+        } else if ( "lang".equals(localName) ) {
+            String tag = attributes.getValue("tag");
+            if ( tag != null ) {
+                assert seLanguage == null;
+                validateLanguageTag ( tag, seScript );
+                seLanguage = tag;
+            } else {
+                throw new SAXParseException ( "missing tag attribute on <lang/> element", locator );
+            }
+        } else if ( "feature".equals(localName) ) {
+            String tag = attributes.getValue("tag");
+            if ( tag != null ) {
+                validateFeatureTag ( tag, seTable, seScript, seLanguage );
+                assert seFeature == null;
+                seFeature = tag;
+            } else {
+                throw new SAXParseException ( "missing tag attribute on <feature/> element", locator );
+            }
+        } else if ( "use-lookup".equals(localName) ) {
+            String ref = attributes.getValue("ref");
+            if ( ref != null ) {
+                assert seUseLookup == null;
+                seUseLookup = ref;
+            } else {
+                throw new SAXParseException ( "missing ref attribute on <use-lookup/> element", locator );
+            }
+        } else if ( "lookup".equals(localName) ) {
+            String id = attributes.getValue("id");
+            if ( id != null ) {
+                assert luID == null;
+                luID = id; luSequence++; lstSequence = -1;
+            } else {
+                throw new SAXParseException ( "missing id attribute on <lookup/> element", locator );
+            }
+            String flags = attributes.getValue("flags");
+            if ( flags != null ) {
+                try {
+                    luFlags = Integer.parseInt ( flags );
+                } catch ( NumberFormatException e ) {
+                    throw new SAXParseException ( "invalid flags attribute on <lookup/> element, must be integer", locator );
+                }
+            }
+            String type = attributes.getValue("type");
+            if ( type != null ) {
+                validateLookupType ( type, seTable );
+                assert luType == -1;
+                luType = mapLookupType ( type, seTable );
+            } else {
+                throw new SAXParseException ( "missing type attribute on <lookup/> element", locator );
+            }
+        } else if ( "lst".equals(localName) ) {
+            String format = attributes.getValue("format");
+            if ( format != null ) {
+                try {
+                    lstSequence++;
+                    lstFormat = Integer.parseInt ( format );
+                } catch ( NumberFormatException e ) {
+                    throw new SAXParseException ( "invalid format attribute on <lst/> element, must be integer", locator );
+                }
+                assert lstCoverage == null;
+                assert lstEntries == null;
+            } else {
+                throw new SAXParseException ( "missing format attribute on <lst/> element", locator );
+            }
+        } else if ( "coverage".equals(localName) ) {
+            assert lstGIDs == null;
+            assert lstRanges == null;
+            lstGIDs = new java.util.ArrayList();
+            lstRanges = new java.util.ArrayList();
+        } else if ( "range".equals(localName) ) {
+            String gs = attributes.getValue("gs");
+            String ge = attributes.getValue("ge");
+            String ci = attributes.getValue("ci");
+            if ( ( gs != null ) && ( ge != null ) && ( ci != null ) ) {
+                try {
+                    int s = Integer.parseInt ( gs );
+                    int e = Integer.parseInt ( ge );
+                    int i = Integer.parseInt ( ci );
+                    lstRanges.add ( new GlyphCoverageTable.CoverageRange ( s, e, i ) );
+                } catch ( NumberFormatException e ) {
+                    throw new SAXParseException ( "invalid format attribute on <lst/> element, must be integer", locator );
+                } catch ( IllegalArgumentException e ) {
+                    throw new SAXParseException ( "bad gs, ge, or ci attribute on <range/> element, must be non-negative integers, with gs <= ge", locator );
+                }
+            } else {
+                throw new SAXParseException ( "missing gs, ge, or ci attribute on <range/> element", locator );
+            }
+        } else if ( "entries".equals(localName) ) {
+            initEntriesState ( seTable, luType, lstFormat );
+        } else if ( "ligs".equals(localName) ) {
+            assert lstLIGs == null;
+            lstLIGs = new java.util.ArrayList();
+        } else if ( "lig".equals(localName) ) {
+            if ( lstLIGs == null ) 
+                throw new SAXParseException ( "missing container <ligs/> element for <lig/> element", locator );
+            else {
+                String gid = attributes.getValue("gid");
+                if ( gid != null ) {
+                    try {
+                        ligGID = Integer.parseInt ( gid );
+                    } catch ( NumberFormatException e ) {
+                        throw new SAXParseException ( "invalid gid attribute on <lig/> element, must be integer", locator );
+                    }
+                } else {
+                    throw new SAXParseException ( "missing gid attribute on <lig/> element", locator );
+                }
+            }
+        }
+    }
+
+    private void endElementScriptExtras ( String uri, String localName, String qName, String content )
+        throws SAXException
+    {
+        if ( "script-extras".equals(localName) ) {
+            inScriptExtras = false;
+        } else if ( "gsub".equals(localName) ) {
+            if ( ( ltSubtables != null ) && ( ltSubtables.size() > 0 ) ) {
+                if ( multiFont.getGSUB() == null ) {
+                    multiFont.setGSUB ( new GlyphSubstitutionTable ( seLookups, ltSubtables ) );
+                }
+            }
+            ltSubtables = null; seTable = -1; seLookups = null;
+        } else if ( "gpos".equals(localName) ) {
+            if ( ( ltSubtables != null ) && ( ltSubtables.size() > 0 ) ) {
+                if ( multiFont.getGPOS() == null ) {
+                    multiFont.setGPOS ( new GlyphPositioningTable ( seLookups, ltSubtables ) );
+                }
+            }
+            ltSubtables = null; seTable = -1;; seLookups = null;
+        } else if ( "script".equals(localName) ) {
+            assert seUseLookups == null;
+            assert seUseLookup == null;
+            assert seFeature == null;
+            assert seLanguage == null;
+            seScript = null;
+        } else if ( "lang".equals(localName) ) {
+            assert seUseLookups == null;
+            assert seUseLookup == null;
+            assert seFeature == null;
+            seLanguage = null;
+        } else if ( "feature".equals(localName) ) {
+            if ( ( seScript != null ) && ( seLanguage != null ) && ( seFeature != null ) ) {
+                if ( ( seUseLookups != null ) && ( seUseLookups.size() > 0 ) ) {
+                    seLookups.put ( new GlyphTable.LookupSpec ( seScript, seLanguage, seFeature ), seUseLookups );
+                }
+            }
+            seUseLookups = null; seFeature = null;
+        } else if ( "use-lookup".equals(localName) ) {
+            if ( seUseLookup != null ) {
+                if ( seUseLookups == null )
+                    seUseLookups = new java.util.ArrayList();
+                seUseLookups.add ( seUseLookup );
+            }
+            seUseLookup = null;
+        } else if ( "lookup".equals(localName) ) {
+            luType = -1;
+            luFlags = 0;
+        } else if ( "lst".equals(localName) ) {
+            assert lstCoverage != null;
+            assert lstEntries != null;
+            addLookupSubtable ( seTable, luType, luID, luSequence, luFlags, lstFormat, lstCoverage, lstEntries );
+            lstFormat = -1;
+            lstCoverage = null;
+            lstEntries = null;
+        } else if ( "coverage".equals(localName) ) {
+            assert lstGIDs != null;
+            assert lstRanges != null;
+            assert lstCoverage == null;
+            if ( lstGIDs.size() > 0 )
+                lstCoverage = lstGIDs;
+            else if ( lstRanges.size() > 0 )
+                lstCoverage = lstRanges;
+            lstGIDs = null; lstRanges = null;
+        } else if ( "gid".equals(localName) ) {
+            if ( lstGIDs != null ) {
+                try {
+                    lstGIDs.add ( Integer.decode ( content ) );
+                } catch ( NumberFormatException e ) {
+                    throw new SAXParseException ( "invalid <gid/> element content, must be integer", locator );
+                }
+            }
+        } else if ( "entries".equals(localName) ) {
+            finishEntriesState ( seTable, luType, lstFormat );
+        } else if ( "ligs".equals(localName) ) {
+            assert lstLIGSets != null;
+            assert lstLIGs != null;
+            lstLIGSets.add ( new GlyphSubstitutionTable.LigatureSet ( lstLIGs ) );
+            lstLIGs = null;
+        } else if ( "lig".equals(localName) ) {
+            assert lstLIGs != null;
+            assert ligGID >= 0;
+            int[] ligComponents;
+            if ( ( ligComponents = parseLigatureComponents ( content ) ) != null ) {
+                lstLIGs.add ( new GlyphSubstitutionTable.Ligature ( ligGID, ligComponents ) );
+            }
+            ligGID = -1;
+        }
+    }
+
+    private void initEntriesState ( int tableType, int lookupType, int subtableFormat )
+    {
+        if ( tableType == GlyphTable.GLYPH_TABLE_TYPE_SUBSTITUTION ) {
+            switch ( lookupType ) {
+            case GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_SINGLE:
+                {
+                    assert lstGIDs == null;
+                    lstGIDs = new java.util.ArrayList();
+                    break;
+                }
+            case GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_LIGATURE:
+                {
+                    assert lstLIGSets == null;
+                    lstLIGSets = new java.util.ArrayList();
+                    break;
+                }
+            case GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_MULTIPLE:
+            case GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_ALTERNATE:
+            case GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_CONTEXT:
+            case GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_CHAINING_CONTEXT:
+            case GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_EXTENSION_SUBSTITUTION:
+            case GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_REVERSE_CHAINING_CONTEXT_SINGLE:
+                throw new UnsupportedOperationException();
+            default:
+                break;
+            }
+        } else if ( tableType == GlyphTable.GLYPH_TABLE_TYPE_POSITIONING ) {
+            switch ( lookupType ) {
+            case GlyphPositioningTable.GPOS_LOOKUP_TYPE_SINGLE:
+            case GlyphPositioningTable.GPOS_LOOKUP_TYPE_PAIR:
+            case GlyphPositioningTable.GPOS_LOOKUP_TYPE_CURSIVE:
+            case GlyphPositioningTable.GPOS_LOOKUP_TYPE_MARK_TO_BASE:
+            case GlyphPositioningTable.GPOS_LOOKUP_TYPE_MARK_TO_LIGATURE:
+            case GlyphPositioningTable.GPOS_LOOKUP_TYPE_MARK_TO_MARK:
+            case GlyphPositioningTable.GPOS_LOOKUP_TYPE_CONTEXT:
+            case GlyphPositioningTable.GPOS_LOOKUP_TYPE_CHAINED_CONTEXT:
+            case GlyphPositioningTable.GPOS_LOOKUP_TYPE_EXTENSION_POSITIONING:
+                throw new UnsupportedOperationException();
+            default:
+                break;
+            }
+        }
+    }
+
+    private void finishEntriesState ( int tableType, int lookupType, int subtableFormat )
+    {
+        if ( tableType == GlyphTable.GLYPH_TABLE_TYPE_SUBSTITUTION ) {
+            switch ( lookupType ) {
+            case GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_SINGLE:
+                {
+                    assert lstGIDs != null;
+                    lstEntries = lstGIDs; lstGIDs = null;
+                    break;
+                }
+            case GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_LIGATURE:
+                {
+                    assert lstLIGSets != null;
+                    lstEntries = lstLIGSets; lstLIGSets = null;
+                    break;
+                }
+            case GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_MULTIPLE:
+            case GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_ALTERNATE:
+            case GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_CONTEXT:
+            case GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_CHAINING_CONTEXT:
+            case GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_EXTENSION_SUBSTITUTION:
+            case GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_REVERSE_CHAINING_CONTEXT_SINGLE:
+                throw new UnsupportedOperationException();
+            default:
+                break;
+            }
+        } else if ( tableType == GlyphTable.GLYPH_TABLE_TYPE_POSITIONING ) {
+            switch ( lookupType ) {
+            case GlyphPositioningTable.GPOS_LOOKUP_TYPE_SINGLE:
+            case GlyphPositioningTable.GPOS_LOOKUP_TYPE_PAIR:
+            case GlyphPositioningTable.GPOS_LOOKUP_TYPE_CURSIVE:
+            case GlyphPositioningTable.GPOS_LOOKUP_TYPE_MARK_TO_BASE:
+            case GlyphPositioningTable.GPOS_LOOKUP_TYPE_MARK_TO_LIGATURE:
+            case GlyphPositioningTable.GPOS_LOOKUP_TYPE_MARK_TO_MARK:
+            case GlyphPositioningTable.GPOS_LOOKUP_TYPE_CONTEXT:
+            case GlyphPositioningTable.GPOS_LOOKUP_TYPE_CHAINED_CONTEXT:
+            case GlyphPositioningTable.GPOS_LOOKUP_TYPE_EXTENSION_POSITIONING:
+                throw new UnsupportedOperationException();
+            default:
+                break;
+            }
+        }
+    }
+
+    private void addLookupSubtable ( int tableType, int lookupType, String lookupID, int lookupSequence, int lookupFlags, int subtableFormat, List coverage, List entries )
+    {
+        GlyphSubtable st = null;
+        if ( tableType == GlyphTable.GLYPH_TABLE_TYPE_SUBSTITUTION ) {
+            st = GlyphSubstitutionTable.createSubtable ( lookupType, lookupID, lookupSequence, lookupFlags, subtableFormat, coverage, entries );
+        } else if ( tableType == GlyphTable.GLYPH_TABLE_TYPE_POSITIONING ) {
+            st = GlyphPositioningTable.createSubtable ( lookupType, lookupID, lookupSequence, lookupFlags, subtableFormat, coverage, entries );
+        }
+        if ( st != null ) {
+            if ( ltSubtables == null )
+                ltSubtables = new java.util.ArrayList();
+            ltSubtables.add ( st );
+        }
+    }
+
+    private int[] parseLigatureComponents ( String s )
+        throws SAXParseException
+    {
+        String[] csa = s.split ( "\\s" );
+        int nc;
+        if ( ( csa == null ) || ( ( nc = csa.length ) == 0 ) )
+            throw new SAXParseException ( "invalid <lig/> element, must specify at least one component", locator );
+        else {
+            int[] components = new int [ nc ];
+            for ( int i = 0, n = nc; i < n; i++ ) {
+                String cs = csa [ i ];
+                int c;
+                try {
+                    c = Integer.parseInt ( cs );
+                    if ( ( c < 0 ) || ( c > 65535 ) ) {
+                        throw new SAXParseException ( "invalid component value (" + c + ") in <lig/> element, out of range", locator );
+                    } else {
+                        components [ i ] = c;
+                    }
+                } catch ( NumberFormatException e ) {
+                    throw new SAXParseException ( "invalid component \"" + cs + "\" in <lig/> element, must be integer", locator );
+                }
+                
+            }
+            return components;
+        }
+    }
+
 }
 
 
Index: src/java/org/apache/fop/fonts/Substitutable.java
===================================================================
--- src/java/org/apache/fop/fonts/Substitutable.java	(revision 0)
+++ src/java/org/apache/fop/fonts/Substitutable.java	(revision 0)
@@ -0,0 +1,50 @@
+/*
+ * 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.fonts;
+
+// CSOFF: LineLengthCheck
+
+/**
+ * Optional interface which indicates that glyph substitution is supported and, if supported,
+ * can perform substitution.
+ * @author Glenn Adams
+ */
+public interface Substitutable {
+
+    /**
+     * Determines if font performs glyph substitution.
+     * @return true if performs substitution.
+     */
+    boolean performsSubstitution();
+
+    /**
+     * Perform substitutions on characters to effect glyph substitution. If some substitution is performed, it
+     * entails mapping from one or more input characters denoting textual character information to one or more
+     * output character codes denoting glyphs in this font, where the output character codes may make use of
+     * private character code values that have significance only for this font.
+     * @param cs character sequence to map to output font encoding character sequence
+     * @param script a script identifier
+     * @param language a language identifier
+     * @return output sequence (represented as a character sequence, where each character in the returned sequence
+     * denotes "font characters", i.e., character codes that map directly (1-1) to their associated glyphs
+     */
+    CharSequence performSubstitution ( CharSequence cs, String script, String language );
+
+}
Index: src/java/org/apache/fop/fo/FOText.java
===================================================================
--- src/java/org/apache/fop/fo/FOText.java	(revision 981406)
+++ src/java/org/apache/fop/fo/FOText.java	(working copy)
@@ -21,6 +21,7 @@
 
 import java.awt.Color;
 import java.nio.CharBuffer;
+import java.util.Map;
 import java.util.NoSuchElementException;
 
 import org.xml.sax.Locator;
@@ -44,7 +45,8 @@
     /** the <code>CharBuffer</code> containing the text */
     private CharBuffer charBuffer;
 
-    /** properties relevant for #PCDATA */
+    // The value of FO traits (refined properties) that apply to #PCDATA
+    // (aka implicit sequence of fo:character)
     private CommonFont commonFont;
     private CommonHyphenation commonHyphenation;
     private Color color;
@@ -57,6 +59,10 @@
     private Property wordSpacing;
     private int wrapOption;
     private Length baselineShift;
+    private String country;
+    private String language;
+    private String script;
+    // End of trait values
 
     /**
      * Points to the previous FOText object created within the current
@@ -79,6 +85,12 @@
     /** Holds the text decoration values. May be null */
     private CommonTextDecoration textDecoration;
 
+    /* bidi levels */
+    private int[] bidiLevels;
+
+    /* advanced script processing state */
+    private Map/*<MapRange,String>*/ mappings;
+
     private static final int IS_WORD_CHAR_FALSE = 0;
     private static final int IS_WORD_CHAR_TRUE = 1;
     private static final int IS_WORD_CHAR_MAYBE = 2;
@@ -171,6 +183,9 @@
         this.wrapOption = pList.get(Constants.PR_WRAP_OPTION).getEnum();
         this.textDecoration = pList.getTextDecorationProps();
         this.baselineShift = pList.get(Constants.PR_BASELINE_SHIFT).getLength();
+        this.country = pList.get(Constants.PR_COUNTRY).getString();
+        this.language = pList.get(Constants.PR_LANGUAGE).getString();
+        this.script = pList.get(Constants.PR_SCRIPT).getString();
     }
 
     /** {@inheritDoc} */
@@ -383,25 +398,25 @@
      * @return The previous FOText node in this Block; null, if this is the
      * first FOText in this Block.
      */
-    public FOText getPrevFOTextThisBlock () {
-        return prevFOTextThisBlock;
-    }
+    //public FOText getPrevFOTextThisBlock () {
+    //    return prevFOTextThisBlock;
+    //}
 
     /**
      * @return The next FOText node in this Block; null if this is the last
      * FOText in this Block; null if subsequent FOText nodes have not yet been
      * processed.
      */
-    public FOText getNextFOTextThisBlock () {
-        return nextFOTextThisBlock;
-    }
+    //public FOText getNextFOTextThisBlock () {
+    //    return nextFOTextThisBlock;
+    //}
 
     /**
      * @return The nearest ancestor block object which contains this FOText.
      */
-    public Block getAncestorBlock () {
-        return ancestorBlock;
-    }
+    //public Block getAncestorBlock () {
+    //    return ancestorBlock;
+    //}
 
     /**
      * Determines whether the input char should be considered part of a
@@ -494,6 +509,9 @@
         boolean canRemove = false;
         boolean canReplace = false;
 
+        public TextCharIterator() {
+        }
+
         /** {@inheritDoc} */
         public boolean hasNext() {
            return (this.currentPosition < charBuffer.limit());
@@ -565,67 +583,88 @@
     }
 
     /**
-     * @return the "color" property.
+     * @return the "color" trait.
      */
     public Color getColor() {
         return color;
     }
 
     /**
-     * @return the "keep-together" property.
+     * @return the "keep-together" trait.
      */
     public KeepProperty getKeepTogether() {
         return keepTogether;
     }
 
     /**
-     * @return the "letter-spacing" property.
+     * @return the "letter-spacing" trait.
      */
     public Property getLetterSpacing() {
         return letterSpacing;
     }
 
     /**
-     * @return the "line-height" property.
+     * @return the "line-height" trait.
      */
     public SpaceProperty getLineHeight() {
         return lineHeight;
     }
 
     /**
-     * @return the "white-space-treatment" property
+     * @return the "white-space-treatment" trait
      */
     public int getWhitespaceTreatment() {
         return whiteSpaceTreatment;
     }
 
     /**
-     * @return the "word-spacing" property.
+     * @return the "word-spacing" trait.
      */
     public Property getWordSpacing() {
         return wordSpacing;
     }
 
     /**
-     * @return the "wrap-option" property.
+     * @return the "wrap-option" trait.
      */
     public int getWrapOption() {
         return wrapOption;
     }
 
-    /** @return the "text-decoration" property. */
+    /** @return the "text-decoration" trait. */
     public CommonTextDecoration getTextDecoration() {
         return textDecoration;
     }
 
-    /** @return the baseline-shift property */
+    /** @return the baseline-shift trait */
     public Length getBaseLineShift() {
         return baselineShift;
     }
 
+    /** @return the country trait */
+    public String getCountry() {
+        return country;
+    }
+
+    /** @return the language trait */
+    public String getLanguage() {
+        return language;
+    }
+
+    /** @return the script trait */
+    public String getScript() {
+        return script;
+    }
+
     /** {@inheritDoc} */
     public String toString() {
-        return (this.charBuffer == null) ? "" : this.charBuffer.toString();
+        if ( charBuffer == null ) {
+            return "";
+        } else {
+            CharBuffer cb = charBuffer.duplicate();
+            cb.rewind();
+            return cb.toString();
+        }
     }
 
     /** {@inheritDoc} */
@@ -670,4 +709,149 @@
             this.charBuffer.rewind();
         }
     }
+
+    /**
+     * Set bidirectional level over interval [start,end).
+     * @param level the resolved level
+     * @param start the starting index of interval
+     * @param end the ending index of interval
+     */
+    public void setBidiLevel ( int level, int start, int end ) {
+        if ( start < end ) {
+            if ( bidiLevels == null ) {
+                bidiLevels = new int [ length() ];
+            }
+            for ( int i = start, n = end; i < n; i++ ) {
+                bidiLevels [ i ] = level;
+            }
+        } else {
+            assert start < end;
+        }
+    }
+
+    /**
+     * Obtain bidirectional level of each character
+     * represented by this FOText.
+     * @return a (possibly empty) array of bidi levels or null
+     * in case no bidi levels have been assigned
+     */
+    public int[] getBidiLevels() {
+        return bidiLevels;
+    }
+
+    /**
+     * Obtain bidirectional level of each character over
+     * interval [start,end).
+     * @param start the starting index of interval
+     * @param end the ending index of interval
+     * @return a (possibly empty) array of bidi levels or null
+     * in case no bidi levels have been assigned
+     */
+    public int[] getBidiLevels ( int start, int end ) {
+        if ( this.bidiLevels != null ) {
+            assert start <= end;
+            int n = end - start;
+            int[] bidiLevels = new int [ n ];
+            for ( int i = 0; i < n; i++ ) {
+                bidiLevels[i] = this.bidiLevels [ start + i ];
+            }
+            return bidiLevels;
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Obtain bidirectional level of character at
+     * specified position, which must be a non-negative integer
+     * less than the length of this FO.
+     * @param position an offset position into FO's characters
+     * @return a resolved bidi level or -1 if default
+     * @throws IndexOutOfBoundsException if position is not non-negative integer
+     * or is greater than or equal to length
+     */
+    public int bidiLevelAt ( int position ) throws IndexOutOfBoundsException {
+        if ( ( position < 0 ) || ( position >= length() ) ) {
+            throw new IndexOutOfBoundsException();
+        } else if ( bidiLevels != null ) {
+            return bidiLevels [ position ];
+        } else {
+            return -1;
+        }
+    }
+
+    /**
+     * Add characters mapped by script substitution processing.
+     * @param start index in character buffer
+     * @param end index in character buffer
+     * @param mappedChars sequence of character codes denoting substituted characters
+     */
+    public void addMapping ( int start, int end, CharSequence mappedChars ) {
+        if ( mappings == null ) {
+            mappings = new java.util.HashMap();
+        }
+        mappings.put ( new MapRange ( start, end ), mappedChars.toString() );
+    }
+
+    /**
+     * Determine if characters over specific interval  have a mapping.
+     * @param start index in character buffer
+     * @param end index in character buffer
+     * @return true if a mapping exist such that the mapping's interval is coincident to
+     * [start,end)
+     */
+    public boolean hasMapping ( int start, int end ) {
+        return ( mappings != null ) && ( mappings.containsKey ( new MapRange ( start, end ) ) );
+    }
+
+    /**
+     * Obtain mapping of characters over specific interval.
+     * @param start index in character buffer
+     * @param end index in character buffer
+     * @return a string of characters representing the mapping over the interval
+     * [start,end)
+     */
+    public String getMapping ( int start, int end ) {
+        if ( mappings != null ) {
+            return (String) mappings.get ( new MapRange ( start, end ) );
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Obtain bidirectional levels of mapping of characters over specific interval.
+     * @param start index in character buffer
+     * @param end index in character buffer
+     * @return a (possibly empty) array of bidi levels or null
+     * in case no bidi levels have been assigned
+     */
+    public int[] getMappingBidiLevels ( int start, int end ) {
+        if ( mappings != null ) {
+            return getBidiLevels ( start, end ); // [TBD] FIX ME
+        } else {
+            return getBidiLevels ( start, end );
+        }
+    }
+
+    private static class MapRange {
+        private int start;
+        private int end;
+        MapRange(int start, int end) {
+            this.start = start;
+            this.end = end;
+        }
+        public int hashCode() {
+            return ( start * 31 ) + end;
+        }
+        public boolean equals ( Object o ) {
+            if ( o instanceof MapRange ) {
+                MapRange r = (MapRange) o;
+                return ( r.start == start ) && ( r.end == end );
+            } else {
+                return false;
+            }
+        }
+    }
+
 }
Index: src/java/org/apache/fop/fo/Constants.java
===================================================================
--- src/java/org/apache/fop/fo/Constants.java	(revision 981406)
+++ src/java/org/apache/fop/fo/Constants.java	(working copy)
@@ -26,7 +26,7 @@
  * <li>Input and output formats</li>
  * <li>Formatting objects (<em>FO_XXX</em>)</li>
  * <li>Formatting properties (<em>PR_XXX</em>)</li>
- * <li>Enumerated values used in formatting properties (<em>EN_XXX</em>)</li>
+ * <li>Enumerated values used in formatting properties and traits (<em>EN_XXX</em>)</li>
  * </ul>
  */
 public interface Constants {
@@ -1208,6 +1208,16 @@
     int EN_NO_LINK = 199;
     /** Enumeration constant -- XSL 1.1 */
     int EN_ALTERNATE = 200;
+    /** Enumeration constant -- for *-direction traits */
+    int EN_LR = 201; // left to right
+    /** Enumeration constant -- for *-direction traits */
+    int EN_RL = 202; // right to left
+    /** Enumeration constant -- for *-direction traits */
+    int EN_TB = 203; // top to bottom
+    /** Enumeration constant -- for *-direction traits */
+    int EN_BT = 204; // bottom to top
+    /** Enumeration constant */
+    int EN_TB_LR = 205; // for top-to-bottom, left-to-right writing mode
     /** Number of enumeration constants defined */
-    int ENUM_COUNT = 200;
+    int ENUM_COUNT = 205;
 }
Index: src/java/org/apache/fop/fo/extensions/svg/SVGDOMContentHandlerFactory.java
===================================================================
--- src/java/org/apache/fop/fo/extensions/svg/SVGDOMContentHandlerFactory.java	(revision 981406)
+++ src/java/org/apache/fop/fo/extensions/svg/SVGDOMContentHandlerFactory.java	(working copy)
@@ -105,7 +105,7 @@
                     Class clazz = Class.forName(
                             "org.apache.batik.dom.svg12.SVG12DOMImplementation");
                     return (DOMImplementation)clazz.getMethod(
-                            "getDOMImplementation", null).invoke(null, null);
+                            "getDOMImplementation", (Class[]) null).invoke(null, (Object[]) null);
                 } catch (Exception e) {
                     return SVGDOMImplementation.getDOMImplementation();
                 }
Index: src/java/org/apache/fop/fo/PropertyList.java
===================================================================
--- src/java/org/apache/fop/fo/PropertyList.java	(revision 981406)
+++ src/java/org/apache/fop/fo/PropertyList.java	(working copy)
@@ -47,8 +47,6 @@
  */
 public abstract class PropertyList {
 
-    // writing-mode index
-    private int writingMode;
 
     private static boolean[] inheritableProperty;
 
@@ -221,51 +219,49 @@
     }
 
     /**
-     * Set writing mode for this FO.
-     * Use that from the nearest ancestor, including self, which generates
-     * reference areas, or from root FO if no ancestor found.
-     * @throws PropertyException ...
-     */
-    public void setWritingMode() throws PropertyException {
-        FObj p = fobj.findNearestAncestorFObj();
-        // If this is a reference area or the root, use the property value.
-        if (fobj.generatesReferenceAreas() || p == null) {
-            writingMode = get(Constants.PR_WRITING_MODE).getEnum();
-        } else {
-            // Otherwise get the writing mode value from the parent.
-            writingMode = getParentPropertyList().getWritingMode();
-        }
-    }
-
-    /**
-     * Return the "writing-mode" property value.
-     * @return the "writing-mode" property value.
-     */
-    public int getWritingMode() {
-        return writingMode;
-    }
-
-
-    /**
-     * Uses the stored writingMode.
+     * Select a writing mode dependent property ID based on value of writing mode property.
      * @param lrtb the property ID to return under lrtb writingmode.
      * @param rltb the property ID to return under rltb writingmode.
      * @param tbrl the property ID to return under tbrl writingmode.
+     * @param tblr the property ID to return under tblr writingmode.
      * @return one of the property IDs, depending on the writing mode.
      */
-    public int getWritingMode(int lrtb, int rltb, int tbrl) {
-        switch (writingMode) {
-            case Constants.EN_LR_TB: return lrtb;
-            case Constants.EN_RL_TB: return rltb;
-            case Constants.EN_TB_RL: return tbrl;
+    public int selectFromWritingMode(int lrtb, int rltb, int tbrl, int tblr) {
+        int propID = -1;
+        try {
+            switch (get(Constants.PR_WRITING_MODE).getEnum()) {
+            case Constants.EN_LR_TB:
+                propID = lrtb;
+                break;
+            case Constants.EN_RL_TB:
+                propID = rltb;
+                break;
+            case Constants.EN_TB_RL:
+                propID = tbrl;
+                break;
+            case Constants.EN_TB_LR:
+                propID = tblr;
+                break;
             default:
-                //nop
+                break;
+            }
+        } catch ( PropertyException e ) {
         }
-        return -1;
+        return propID;
     }
 
     /**
-     * Adds the attributes, passed in by the parser to the PropertyList
+     * <p>Adds the attributes, passed in by the parser to the PropertyList.</p>
+     * <p>Note that certain attributes are given priority in terms of order of
+     * processing due to conversion dependencies, where the order is as follows:</p>
+     * <ol>
+     * <li>writing-mode</li>
+     * <li>column-number</li>
+     * <li>number-columns-spanned</li>
+     * <li>font</li>
+     * <li>font-size</li>
+     * <li><emph>all others in order of appearance</emph></li>
+     * </ol>
      *
      * @param attributes Collection of attributes passed to us from the parser.
      * @throws ValidationException if there is an attribute that does not
@@ -274,12 +270,21 @@
     public void addAttributesToList(Attributes attributes)
                     throws ValidationException {
         /*
+         * Give writing-mode highest conversion priority.
+         */
+        String attributeName = "writing-mode";
+        String attributeValue = attributes.getValue(attributeName);
+        if ( attributeValue != null ) {
+            convertAttributeToProperty(attributes, attributeName, attributeValue);
+        }
+
+        /*
          * If column-number/number-columns-spanned are specified, then we
          * need them before all others (possible from-table-column() on any
          * other property further in the list...
          */
-        String attributeName = "column-number";
-        String attributeValue = attributes.getValue(attributeName);
+        attributeName = "column-number";
+        attributeValue = attributes.getValue(attributeName);
         convertAttributeToProperty(attributes, attributeName,
             attributeValue);
         attributeName = "number-columns-spanned";
@@ -659,4 +664,3 @@
         return CommonTextDecoration.createFromPropertyList(this);
     }
 }
-
Index: src/java/org/apache/fop/fo/FObj.java
===================================================================
--- src/java/org/apache/fop/fo/FObj.java	(revision 981406)
+++ src/java/org/apache/fop/fo/FObj.java	(working copy)
@@ -117,9 +117,7 @@
                     throws FOPException {
         setLocator(locator);
         pList.addAttributesToList(attlist);
-        if (!inMarker()
-                || "marker".equals(elementName)) {
-            pList.setWritingMode();
+        if (!inMarker() || "marker".equals(elementName)) {
             bind(pList);
         }
     }
Index: src/java/org/apache/fop/fo/flow/InlineLevel.java
===================================================================
--- src/java/org/apache/fop/fo/flow/InlineLevel.java	(revision 981406)
+++ src/java/org/apache/fop/fo/flow/InlineLevel.java	(working copy)
@@ -37,7 +37,7 @@
  */
 public abstract class InlineLevel extends FObjMixed {
 
-    // The value of properties relevant for inline-level FOs.
+    // The value of FO traits (refined properties) that apply to inline level FOs.
     private CommonBorderPaddingBackground commonBorderPaddingBackground;
     private CommonMarginInline commonMarginInline;
     private CommonFont commonFont;
@@ -45,7 +45,7 @@
     private KeepProperty keepWithNext;
     private KeepProperty keepWithPrevious;
     private SpaceProperty lineHeight;
-    // End of property values
+    // End of trait values
 
     /**
      * Base constructor
@@ -83,22 +83,22 @@
         return commonFont;
     }
 
-    /** @return the "color" property */
+    /** @return the "color" trait */
     public Color getColor() {
         return color;
     }
 
-    /** @return the "line-height" property */
+    /** @return the "line-height" trait */
     public SpaceProperty getLineHeight() {
         return lineHeight;
     }
 
-    /** @return the "keep-with-next" property */
+    /** @return the "keep-with-next" trait */
     public KeepProperty getKeepWithNext() {
         return keepWithNext;
     }
 
-    /** @return the "keep-with-previous" property */
+    /** @return the "keep-with-previous" trait */
     public KeepProperty getKeepWithPrevious() {
         return keepWithPrevious;
     }
Index: src/java/org/apache/fop/fo/flow/BidiOverride.java
===================================================================
--- src/java/org/apache/fop/fo/flow/BidiOverride.java	(revision 981406)
+++ src/java/org/apache/fop/fo/flow/BidiOverride.java	(working copy)
@@ -22,36 +22,30 @@
 import org.xml.sax.Locator;
 
 import org.apache.fop.apps.FOPException;
+import org.apache.fop.datatypes.Length;
+import org.apache.fop.fo.Constants;
 import org.apache.fop.fo.FONode;
 import org.apache.fop.fo.FObjMixed;
 import org.apache.fop.fo.PropertyList;
 import org.apache.fop.fo.ValidationException;
+import org.apache.fop.fo.properties.Property;
 import org.apache.fop.fo.properties.SpaceProperty;
 
 /**
  * Class modelling the <a href="http://www.w3.org/TR/xsl/#fo_bidi-override">
  * <code>fo:bidi-override</code></a> object.
  */
-public class BidiOverride extends FObjMixed {
+public class BidiOverride extends Inline {
 
-    // used for FO validation
-    private boolean blockOrInlineItemFound = false;
-    private boolean canHaveBlockLevelChildren = true;
-    // The value of properties relevant for fo:bidi-override.
-    // private ToBeImplementedProperty prDirection;
-    // private ToBeImplementedProperty prLetterSpacing;
-    private SpaceProperty lineHeight;
-    // private ToBeImplementedProperty prScoreSpaces;
-    // private ToBeImplementedProperty prUnicodeBidi;
+    // The value of FO traits (refined properties) that apply to fo:bidi-override
+    // (that are not implemented by InlineLevel).
+    private Property letterSpacing;
+    private Property wordSpacing;
+    private int direction;
+    private int unicodeBidi;
+    // private int scoreSpaces;
+    // End of trait values
 
-    // Unused but valid items, commented out for performance:
-    //     private CommonAural commonAural;
-    //     private CommonFont commonFont;
-    //     private CommonRelativePosition commonRelativePosition;
-    //     private Color prColor;
-    //     private SpaceProperty prWordSpacing;
-    // End of property values
-
     /**
      * Base constructor
      *
@@ -59,68 +53,37 @@
      */
     public BidiOverride(FONode parent) {
         super(parent);
-
-       /* Check to see if this node can have block-level children.
-        * See validateChildNode() below.
-        */
-       int lvlLeader = findAncestor(FO_LEADER);
-       int lvlInCntr = findAncestor(FO_INLINE_CONTAINER);
-       int lvlInline = findAncestor(FO_INLINE);
-       int lvlFootnote = findAncestor(FO_FOOTNOTE);
-
-       if (lvlLeader > 0) {
-           if (lvlInCntr < 0 || (lvlInCntr > 0 && lvlInCntr > lvlLeader)) {
-               canHaveBlockLevelChildren = false;
-           }
-       } else if (lvlInline > 0 && lvlFootnote == (lvlInline + 1)) {
-           if (lvlInCntr < 0 || (lvlInCntr > 0 && lvlInCntr > lvlInline)) {
-               canHaveBlockLevelChildren = false;
-           }
-       }
-
     }
 
     /** {@inheritDoc} */
     public void bind(PropertyList pList) throws FOPException {
-        // prDirection = pList.get(PR_DIRECTION);
-        // prLetterSpacing = pList.get(PR_LETTER_SPACING);
-        lineHeight = pList.get(PR_LINE_HEIGHT).getSpace();
-        // prScoreSpaces = pList.get(PR_SCORE_SPACES);
-        // prUnicodeBidi = pList.get(PR_UNICODE_BIDI);
+        super.bind(pList);
+        letterSpacing = pList.get(PR_LETTER_SPACING);
+        wordSpacing = pList.get(PR_WORD_SPACING);
+        direction = pList.get(PR_DIRECTION).getEnum();
+        unicodeBidi = pList.get(PR_UNICODE_BIDI).getEnum();
     }
 
-    /**
-     * {@inheritDoc}
-     * <br>XSL Content Model: marker* (#PCDATA|%inline;|%block;)*
-     * <br><i>Additionally: "An fo:bidi-override that is a descendant of an fo:leader
-     *  or of the fo:inline child of an fo:footnote may not have block-level
-     *  children, unless it has a nearer ancestor that is an
-     *  fo:inline-container."</i>
-     */
-    protected void validateChildNode(Locator loc, String nsURI, String localName)
-                throws ValidationException {
-        if (FO_URI.equals(nsURI)) {
-            if (localName.equals("marker")) {
-                if (blockOrInlineItemFound) {
-                   nodesOutOfOrderError(loc, "fo:marker",
-                        "(#PCDATA|%inline;|%block;)");
-                }
-            } else if (!isBlockOrInlineItem(nsURI, localName)) {
-                invalidChildError(loc, nsURI, localName);
-            } else if (!canHaveBlockLevelChildren && isBlockItem(nsURI, localName)) {
-                invalidChildError(loc, getParent().getName(), nsURI, getName(),
-                        "rule.bidiOverrideContent");
-            } else {
-                blockOrInlineItemFound = true;
-            }
-        }
+    /** @return the "letter-spacing" trait */
+    public Property getLetterSpacing() {
+        return letterSpacing;
     }
 
-    /** @return the "line-height" property */
-    public SpaceProperty getLineHeight() {
-        return lineHeight;
+    /** @return the "word-spacing" trait */
+    public Property getWordSpacing() {
+        return wordSpacing;
     }
 
+    /** @return the "direction" trait */
+    public int getDirection() {
+        return direction;
+    }
+
+    /** @return the "unicodeBidi" trait */
+    public int getUnicodeBidi() {
+        return unicodeBidi;
+    }
+
     /** {@inheritDoc} */
     public String getLocalName() {
         return "bidi-override";
Index: src/java/org/apache/fop/fo/flow/table/Table.java
===================================================================
--- src/java/org/apache/fop/fo/flow/table/Table.java	(revision 981406)
+++ src/java/org/apache/fop/fo/flow/table/Table.java	(working copy)
@@ -45,7 +45,7 @@
  */
 public class Table extends TableFObj implements ColumnNumberManagerHolder, BreakPropertySet {
 
-    /** properties */
+    // The value of FO traits (refined properties) that apply to fo:table.
     private CommonBorderPaddingBackground commonBorderPaddingBackground;
     private CommonMarginBlock commonMarginBlock;
     private LengthRangeProperty blockProgressionDimension;
@@ -60,12 +60,13 @@
     private int tableLayout;
     private int tableOmitFooterAtBreak;
     private int tableOmitHeaderAtBreak;
+    private int writingMode;
     // Unused but valid items, commented out for performance:
     //     private CommonAccessibility commonAccessibility;
     //     private CommonAural commonAural;
     //     private CommonRelativePosition commonRelativePosition;
     //     private int intrusionDisplace;
-    //     private int writingMode;
+    // End of FO trait values
 
     /** extension properties */
     private Length widowContentLimit;
@@ -126,6 +127,7 @@
         tableLayout = pList.get(PR_TABLE_LAYOUT).getEnum();
         tableOmitFooterAtBreak = pList.get(PR_TABLE_OMIT_FOOTER_AT_BREAK).getEnum();
         tableOmitHeaderAtBreak = pList.get(PR_TABLE_OMIT_HEADER_AT_BREAK).getEnum();
+        writingMode = pList.get(PR_WRITING_MODE).getEnum();
 
         //Bind extension properties
         widowContentLimit = pList.get(PR_X_WIDOW_CONTENT_LIMIT).getLength();
@@ -327,7 +329,6 @@
         TableColumn implicitColumn = new TableColumn(this, true);
         PropertyList pList = new StaticPropertyList(
                                 implicitColumn, this.propList);
-        pList.setWritingMode();
         implicitColumn.bind(pList);
         implicitColumn.setColumnWidth(new TableColLength(1.0, implicitColumn));
         implicitColumn.setColumnNumber(colNumber);
@@ -424,14 +425,14 @@
     }
 
     /**
-     * @return the "inline-progression-dimension" property.
+     * @return the "inline-progression-dimension" FO trait.
      */
     public LengthRangeProperty getInlineProgressionDimension() {
         return inlineProgressionDimension;
     }
 
     /**
-     * @return the "block-progression-dimension" property.
+     * @return the "block-progression-dimension" FO trait.
      */
     public LengthRangeProperty getBlockProgressionDimension() {
         return blockProgressionDimension;
@@ -451,27 +452,27 @@
         return commonBorderPaddingBackground;
     }
 
-    /** @return the "break-after" property. */
+    /** @return the "break-after" FO trait. */
     public int getBreakAfter() {
         return breakAfter;
     }
 
-    /** @return the "break-before" property. */
+    /** @return the "break-before" FO trait. */
     public int getBreakBefore() {
         return breakBefore;
     }
 
-    /** @return the "keep-with-next" property.  */
+    /** @return the "keep-with-next" FO trait.  */
     public KeepProperty getKeepWithNext() {
         return keepWithNext;
     }
 
-    /** @return the "keep-with-previous" property.  */
+    /** @return the "keep-with-previous" FO trait.  */
     public KeepProperty getKeepWithPrevious() {
         return keepWithPrevious;
     }
 
-    /** @return the "keep-together" property.  */
+    /** @return the "keep-together" FO trait.  */
     public KeepProperty getKeepTogether() {
         return keepTogether;
     }
@@ -485,7 +486,7 @@
                 || !getKeepTogether().getWithinColumn().isAuto();
     }
 
-    /** @return the "border-collapse" property. */
+    /** @return the "border-collapse" FO trait. */
     public int getBorderCollapse() {
         return borderCollapse;
     }
@@ -495,17 +496,22 @@
         return (getBorderCollapse() == EN_SEPARATE);
     }
 
-    /** @return the "border-separation" property. */
+    /** @return the "border-separation" FO trait. */
     public LengthPairProperty getBorderSeparation() {
         return borderSeparation;
     }
 
-    /** @return the "fox:widow-content-limit" extension property */
+    /** @return the "writing-mode" FO trait */
+    public int getWritingMode() {
+        return writingMode;
+    }
+
+    /** @return the "fox:widow-content-limit" extension FO trait */
     public Length getWidowContentLimit() {
         return widowContentLimit;
     }
 
-    /** @return the "fox:orphan-content-limit" extension property */
+    /** @return the "fox:orphan-content-limit" extension FO trait */
     public Length getOrphanContentLimit() {
         return orphanContentLimit;
     }
Index: src/java/org/apache/fop/fo/flow/Inline.java
===================================================================
--- src/java/org/apache/fop/fo/flow/Inline.java	(revision 981406)
+++ src/java/org/apache/fop/fo/flow/Inline.java	(working copy)
@@ -120,7 +120,8 @@
             } else if (!isBlockOrInlineItem(nsURI, localName)) {
                 invalidChildError(loc, nsURI, localName);
             } else if (!canHaveBlockLevelChildren && isBlockItem(nsURI, localName)) {
-                invalidChildError(loc, getParent().getName(), nsURI, getName(), "rule.inlineContent");
+                invalidChildError(loc, getParent().getName(), nsURI, getName(),
+                                  "rule.inlineContent");
             } else {
                 blockOrInlineItemFound = true;
             }
Index: src/java/org/apache/fop/fo/flow/InlineContainer.java
===================================================================
--- src/java/org/apache/fop/fo/flow/InlineContainer.java	(revision 981406)
+++ src/java/org/apache/fop/fo/flow/InlineContainer.java	(working copy)
@@ -33,6 +33,10 @@
 import org.apache.fop.fo.properties.KeepProperty;
 import org.apache.fop.fo.properties.LengthRangeProperty;
 import org.apache.fop.fo.properties.SpaceProperty;
+import org.apache.fop.traits.Direction;
+import org.apache.fop.traits.WritingMode;
+import org.apache.fop.traits.WritingModeTraits;
+import org.apache.fop.traits.WritingModeTraitsGetter;
 
 /**
  * Class modelling the <a href="http://www.w3.org/TR/xsl/#fo_inline-container">
@@ -40,7 +44,7 @@
  */
 public class InlineContainer extends FObj {
 
-    // The value of properties relevant for fo:inline-container.
+    // The value of FO traits (refined properties) that apply to fo:inline-container.
     private Length alignmentAdjust;
     private int alignmentBaseline;
     private Length baselineShift;
@@ -54,7 +58,7 @@
     private SpaceProperty lineHeight;
     private int overflow;
     private Numeric referenceOrientation;
-    private int writingMode;
+    private WritingModeTraits writingModeTraits;
     // Unused but valid items, commented out for performance:
     //     private CommonRelativePosition commonRelativePosition;
     //     private int displayAlign;
@@ -62,7 +66,7 @@
     //     private KeepProperty keepWithNext;
     //     private KeepProperty keepWithPrevious;
     //     private Length width;
-    // End of property values
+    // End of FO trait values
 
     /** used for FO validation */
     private boolean blockItemFound = false;
@@ -92,7 +96,8 @@
         lineHeight = pList.get(PR_LINE_HEIGHT).getSpace();
         overflow = pList.get(PR_OVERFLOW).getEnum();
         referenceOrientation = pList.get(PR_REFERENCE_ORIENTATION).getNumeric();
-        writingMode = pList.get(PR_WRITING_MODE).getEnum();
+        writingModeTraits = new WritingModeTraits
+            ( WritingMode.valueOf(pList.get(PR_WRITING_MODE).getEnum()) );
     }
 
     /**
@@ -121,27 +126,27 @@
         }
     }
 
-    /** @return the "alignment-adjust" property */
+    /** @return the "alignment-adjust" FO trait */
     public Length getAlignmentAdjust() {
         return alignmentAdjust;
     }
 
-    /** @return the "alignment-baseline" property */
+    /** @return the "alignment-baseline" FO trait */
     public int getAlignmentBaseline() {
         return alignmentBaseline;
     }
 
-    /** @return the "baseline-shift" property */
+    /** @return the "baseline-shift" FO trait */
     public Length getBaselineShift() {
         return baselineShift;
     }
 
-    /** @return the "block-progression-dimension" property */
+    /** @return the "block-progression-dimension" FO trait */
     public LengthRangeProperty getBlockProgressionDimension() {
         return blockProgressionDimension;
     }
 
-    /** @return the "clip" property */
+    /** @return the "clip" FO trait */
     public int getClip() {
         return clip;
     }
@@ -156,41 +161,79 @@
         return this.commonMarginInline;
     }
 
-    /** @return the "dominant-baseline" property */
+    /** @return the "dominant-baseline" FO trait */
     public int getDominantBaseline() {
         return dominantBaseline;
     }
 
-    /** @return the "keep-together" property */
+    /** @return the "keep-together" FO trait */
     public KeepProperty getKeepTogether() {
         return keepTogether;
     }
 
-    /** @return the "inline-progression-dimension" property */
+    /** @return the "inline-progression-dimension" FO trait */
     public LengthRangeProperty getInlineProgressionDimension() {
         return inlineProgressionDimension;
     }
 
-    /** @return the "line-height" property */
+    /** @return the "line-height" FO trait */
     public SpaceProperty getLineHeight() {
         return lineHeight;
     }
 
-    /** @return the "overflow" property */
+    /** @return the "overflow" FO trait */
     public int getOverflow() {
         return overflow;
     }
 
-    /** @return the "reference-orientation" property */
+    /** @return the "reference-orientation" FO trait */
     public int getReferenceOrientation() {
         return referenceOrientation.getValue();
     }
 
-    /** @return the "writing-mode" property */
-    public int getWritingMode() {
-        return writingMode;
+    /**
+     * {@inheritDoc}
+     */
+    public Direction getInlineProgressionDirection() {
+        return writingModeTraits.getInlineProgressionDirection();
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    public Direction getBlockProgressionDirection() {
+        return writingModeTraits.getBlockProgressionDirection();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Direction getColumnProgressionDirection() {
+        return writingModeTraits.getColumnProgressionDirection();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+
+    public Direction getRowProgressionDirection() {
+        return writingModeTraits.getRowProgressionDirection();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Direction getShiftDirection() {
+        return writingModeTraits.getShiftDirection();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public WritingMode getWritingMode() {
+        return writingModeTraits.getWritingMode();
+    }
+
     /** {@inheritDoc} */
     public String getLocalName() {
         return "inline-container";
Index: src/java/org/apache/fop/fo/flow/Block.java
===================================================================
--- src/java/org/apache/fop/fo/flow/Block.java	(revision 981406)
+++ src/java/org/apache/fop/fo/flow/Block.java	(working copy)
@@ -52,7 +52,7 @@
     private boolean blockOrInlineItemFound = false;
     private boolean initialPropertySetFound = false;
 
-    // The value of properties relevant for fo:block.
+    // The value of FO traits (refined properties) that apply to fo:block.
     private CommonBorderPaddingBackground commonBorderPaddingBackground;
     private CommonFont commonFont;
     private CommonHyphenation commonHyphenation;
@@ -89,8 +89,11 @@
     //     private Length textDepth;
     //     private Length textAltitude;
     //     private int visibility;
-    // End of property values
+    // End of FO trait values
 
+    /* default paragraph bidi level */
+    private int bidiLevel = -1;
+
     /**
      * Base constructor
      *
@@ -170,7 +173,7 @@
         return commonHyphenation;
     }
 
-    /** @return the "break-after" property. */
+    /** @return the "break-after" trait. */
     public int getBreakAfter() {
         return breakAfter;
     }
@@ -180,82 +183,82 @@
         return ptr;
     }
 
-    /** @return the "break-before" property. */
+    /** @return the "break-before" trait. */
     public int getBreakBefore() {
         return breakBefore;
     }
 
-    /** @return the "hyphenation-ladder-count" property.  */
+    /** @return the "hyphenation-ladder-count" trait.  */
     public Numeric getHyphenationLadderCount() {
         return hyphenationLadderCount;
     }
 
-    /** @return the "keep-with-next" property.  */
+    /** @return the "keep-with-next" trait.  */
     public KeepProperty getKeepWithNext() {
         return keepWithNext;
     }
 
-    /** @return the "keep-with-previous" property.  */
+    /** @return the "keep-with-previous" trait.  */
     public KeepProperty getKeepWithPrevious() {
         return keepWithPrevious;
     }
 
-    /** @return the "keep-together" property.  */
+    /** @return the "keep-together" trait.  */
     public KeepProperty getKeepTogether() {
         return keepTogether;
     }
 
-    /** @return the "orphans" property.  */
+    /** @return the "orphans" trait.  */
     public int getOrphans() {
         return orphans.getValue();
     }
 
-    /** @return the "widows" property.  */
+    /** @return the "widows" trait.  */
     public int getWidows() {
         return widows.getValue();
     }
 
-    /** @return the "line-stacking-strategy" property.  */
+    /** @return the "line-stacking-strategy" trait.  */
     public int getLineStackingStrategy() {
         return lineStackingStrategy;
     }
 
-    /** @return the "color" property */
+    /** @return the "color" trait */
     public Color getColor() {
         return color;
     }
 
-    /** @return the "line-height" property */
+    /** @return the "line-height" trait */
     public SpaceProperty getLineHeight() {
         return lineHeight;
     }
 
-    /** @return the "span" property */
+    /** @return the "span" trait */
     public int getSpan() {
         return this.span;
     }
 
-    /** @return the "text-align" property */
+    /** @return the "text-align" trait */
     public int getTextAlign() {
         return textAlign;
     }
 
-    /** @return the "text-align-last" property */
+    /** @return the "text-align-last" trait */
     public int getTextAlignLast() {
         return textAlignLast;
     }
 
-    /** @return the "text-indent" property */
+    /** @return the "text-indent" trait */
     public Length getTextIndent() {
         return textIndent;
     }
 
-    /** @return the "last-line-end-indent" property */
+    /** @return the "last-line-end-indent" trait */
     public Length getLastLineEndIndent() {
         return lastLineEndIndent;
     }
 
-    /** @return the "wrap-option" property */
+    /** @return the "wrap-option" trait */
     public int getWrapOption() {
         return wrapOption;
     }
@@ -293,17 +296,17 @@
         }
     }
 
-    /** @return the "linefeed-treatment" property */
+    /** @return the "linefeed-treatment" trait */
     public int getLinefeedTreatment() {
         return linefeedTreatment;
     }
 
-    /** @return the "white-space-treatment" property */
+    /** @return the "white-space-treatment" trait */
     public int getWhitespaceTreatment() {
         return whiteSpaceTreatment;
     }
 
-    /** @return the "white-space-collapse" property */
+    /** @return the "white-space-collapse" trait */
     public int getWhitespaceCollapse() {
         return whiteSpaceCollapse;
     }
@@ -313,28 +316,28 @@
         return this.commonRelativePosition;
     }
 
-    /** @return the "hyphenation-keep" property */
+    /** @return the "hyphenation-keep" trait */
     public int getHyphenationKeep() {
         return this.hyphenationKeep;
     }
 
-    /** @return the "intrusion-displace" property */
+    /** @return the "intrusion-displace" trait */
     public int getIntrusionDisplace() {
         return this.intrusionDisplace;
     }
 
-    /** @return the "line-height-shift-adjustment" property */
+    /** @return the "line-height-shift-adjustment" trait */
     public int getLineHeightShiftAdjustment() {
         return this.lineHeightShiftAdjustment;
     }
 
-     /**
-     * @return the "fox:disable-column-balancing" property, one of
+    /**
+     * @return the "fox:disable-column-balancing" trait, one of
      * {@link Constants#EN_TRUE}, {@link Constants#EN_FALSE}
      */
-     public int getDisableColumnBalancing() {
-         return disableColumnBalancing;
-     }
+    public int getDisableColumnBalancing() {
+        return disableColumnBalancing;
+    }
 
 
     /** {@inheritDoc} */
@@ -342,15 +345,33 @@
         return NullCharIterator.getInstance();
     }
 
+    /**
+     * Get the default paragraph bidirectional embedding level.
+     *
+     * @return the default paragraph bidirectional embedding level
+     */
+    public int getBidiLevel() {
+        return bidiLevel;
+    }
+
+    /**
+     * Set the default paragraph bidirectional embedding level.
+     *
+     * @param bidiLevel the default paragraph bidirectional embedding level
+     */
+    public void setBidiLevel ( int bidiLevel ) {
+        this.bidiLevel = bidiLevel;
+    }
+
     /** {@inheritDoc} */
     public String getLocalName() {
         return "block";
     }
 
-     /**
-      * {@inheritDoc}
-      * @return {@link org.apache.fop.fo.Constants#FO_BLOCK}
-      */
+    /**
+     * {@inheritDoc}
+     * @return {@link org.apache.fop.fo.Constants#FO_BLOCK}
+     */
     public int getNameId() {
         return FO_BLOCK;
     }
Index: src/java/org/apache/fop/fo/flow/Leader.java
===================================================================
--- src/java/org/apache/fop/fo/flow/Leader.java	(revision 981406)
+++ src/java/org/apache/fop/fo/flow/Leader.java	(working copy)
@@ -30,8 +30,8 @@
  * <code>fo:leader</code></a> object.
  * The main property of <code>fo:leader</code> is leader-pattern.
  * The following patterns are treated: rule, space, dots and use-content.
- * @todo implement validateChildNode()
  */
+// [TBD] implement validateChildNode()
 public class Leader extends InlineLevel {
     // The value of properties relevant for fo:leader.
     // See also superclass InlineLevel
Index: src/java/org/apache/fop/fo/flow/Character.java
===================================================================
--- src/java/org/apache/fop/fo/flow/Character.java	(revision 981406)
+++ src/java/org/apache/fop/fo/flow/Character.java	(working copy)
@@ -79,6 +79,9 @@
     //     private int visibility;
     // End of property values
 
+    /* bidi level */
+    private int bidiLevel;
+
     /** constant indicating that the character is OK */
     public static final int OK = 0;
     /** constant indicating that the character does not fit */
@@ -228,6 +231,39 @@
         return FO_CHARACTER;
     }
 
+    /**
+     * Set bidirectional level for character FO as a whole.
+     * @param level the resolved level
+     */
+    public void setBidiLevel ( int level ) {
+        this.bidiLevel = level;
+    }
+
+    /**
+     * Obtain bidirectional level of each character
+     * represented by this FO.
+     * @return a non-empty array of bidi levels
+     */
+    public int[] getBidiLevels() {
+        return new int[] {bidiLevel};
+    }
+
+    /**
+     * Obtain bidirectional level of character at
+     * specified position, which must be zero for this
+     * FO.
+     * @param position an offset position into FO's characters
+     * @return a resolved bidi level or -1 if default
+     * @throws IndexOutOfBoundsException if position is not zero
+     */
+    public int bidiLevelAt ( int position ) throws IndexOutOfBoundsException {
+        if ( position != 0 ) {
+            throw new IndexOutOfBoundsException();
+        } else {
+            return bidiLevel;
+        }
+    }
+
     private class FOCharIterator extends CharIterator {
 
         private boolean bFirst = true;
Index: src/java/org/apache/fop/fo/flow/BlockContainer.java
===================================================================
--- src/java/org/apache/fop/fo/flow/BlockContainer.java	(revision 981406)
+++ src/java/org/apache/fop/fo/flow/BlockContainer.java	(working copy)
@@ -31,14 +31,18 @@
 import org.apache.fop.fo.properties.CommonMarginBlock;
 import org.apache.fop.fo.properties.KeepProperty;
 import org.apache.fop.fo.properties.LengthRangeProperty;
+import org.apache.fop.traits.Direction;
+import org.apache.fop.traits.WritingMode;
+import org.apache.fop.traits.WritingModeTraits;
+import org.apache.fop.traits.WritingModeTraitsGetter;
 import org.xml.sax.Locator;
 
 /**
  * Class modelling the <a href="http://www.w3.org/TR/xsl/#fo_block-container">
  * <code>fo:block-container</code></a> object.
  */
-public class BlockContainer extends FObj implements BreakPropertySet {
-    // The value of properties relevant for fo:block-container.
+public class BlockContainer extends FObj implements BreakPropertySet, WritingModeTraitsGetter {
+    // The value of FO traits (refined properties) that apply to fo:block-container.
     private CommonAbsolutePosition commonAbsolutePosition;
     private CommonBorderPaddingBackground commonBorderPaddingBackground;
     private CommonMarginBlock commonMarginBlock;
@@ -55,11 +59,11 @@
     private Numeric referenceOrientation;
     private int span;
     private int disableColumnBalancing;
-    private int writingMode;
+    private WritingModeTraits writingModeTraits;
     // Unused but valid items, commented out for performance:
     //     private int intrusionDisplace;
     //     private Numeric zIndex;
-    // End of property values
+    // End of FO trait values
 
     /** used for FO validation */
     private boolean blockItemFound = false;
@@ -92,7 +96,8 @@
         overflow = pList.get(PR_OVERFLOW).getEnum();
         referenceOrientation = pList.get(PR_REFERENCE_ORIENTATION).getNumeric();
         span = pList.get(PR_SPAN).getEnum();
-        writingMode = pList.get(PR_WRITING_MODE).getEnum();
+        writingModeTraits = new WritingModeTraits
+            ( WritingMode.valueOf(pList.get(PR_WRITING_MODE).getEnum()) );
         disableColumnBalancing = pList.get(PR_X_DISABLE_COLUMN_BALANCING).getEnum();
     }
 
@@ -161,64 +166,64 @@
     }
 
     /**
-     * @return the "block-progression-dimension" property.
+     * @return the "block-progression-dimension" FO trait.
      */
     public LengthRangeProperty getBlockProgressionDimension() {
         return blockProgressionDimension;
     }
 
-    /** @return the "display-align" property. */
+    /** @return the "display-align" FO trait. */
     public int getDisplayAlign() {
         return displayAlign;
     }
 
-    /** @return the "break-after" property. */
+    /** @return the "break-after" FO trait. */
     public int getBreakAfter() {
         return breakAfter;
     }
 
-    /** @return the "break-before" property. */
+    /** @return the "break-before" FO trait. */
     public int getBreakBefore() {
         return breakBefore;
     }
 
-    /** @return the "keep-with-next" property.  */
+    /** @return the "keep-with-next" FO trait.  */
     public KeepProperty getKeepWithNext() {
         return keepWithNext;
     }
 
-    /** @return the "keep-with-previous" property.  */
+    /** @return the "keep-with-previous" FO trait.  */
     public KeepProperty getKeepWithPrevious() {
         return keepWithPrevious;
     }
 
-    /** @return the "keep-together" property.  */
+    /** @return the "keep-together" FO trait.  */
     public KeepProperty getKeepTogether() {
         return keepTogether;
     }
 
-    /** @return the "inline-progression-dimension" property */
+    /** @return the "inline-progression-dimension" FO trait */
     public LengthRangeProperty getInlineProgressionDimension() {
         return inlineProgressionDimension;
     }
 
-    /** @return the "overflow" property */
+    /** @return the "overflow" FO trait */
     public int getOverflow() {
         return overflow;
     }
 
-    /** @return the "reference-orientation" property */
+    /** @return the "reference-orientation" FO trait */
     public int getReferenceOrientation() {
         return referenceOrientation.getValue();
     }
 
-    /** @return the "span" property */
+    /** @return the "span" FO trait */
     public int getSpan() {
         return this.span;
     }
 
     /**
-     * @return the "fox:disable-column-balancing" property, one of
+     * @return the "fox:disable-column-balancing" FO trait, one of
      * {@link Constants#EN_TRUE}, {@link Constants#EN_FALSE}
      */
     public int getDisableColumnBalancing() {
@@ -226,11 +231,49 @@
     }
 
 
-    /** @return the "writing-mode" property */
-    public int getWritingMode() {
-        return writingMode;
+    /**
+     * {@inheritDoc}
+     */
+    public Direction getInlineProgressionDirection() {
+        return writingModeTraits.getInlineProgressionDirection();
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    public Direction getBlockProgressionDirection() {
+        return writingModeTraits.getBlockProgressionDirection();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Direction getColumnProgressionDirection() {
+        return writingModeTraits.getColumnProgressionDirection();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+
+    public Direction getRowProgressionDirection() {
+        return writingModeTraits.getRowProgressionDirection();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Direction getShiftDirection() {
+        return writingModeTraits.getShiftDirection();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public WritingMode getWritingMode() {
+        return writingModeTraits.getWritingMode();
+    }
+
     /** {@inheritDoc} */
     public String getLocalName() {
         return "block-container";
Index: src/java/org/apache/fop/fo/pagination/RegionStart.java
===================================================================
--- src/java/org/apache/fop/fo/pagination/RegionStart.java	(revision 981406)
+++ src/java/org/apache/fop/fo/pagination/RegionStart.java	(working copy)
@@ -23,6 +23,7 @@
 import java.awt.Rectangle;
 
 // FOP
+import org.apache.fop.fo.Constants;
 import org.apache.fop.fo.FONode;
 import org.apache.fop.datatypes.FODimension;
 import org.apache.fop.datatypes.LengthBase;
@@ -52,12 +53,19 @@
         PercentBaseContext pageHeightContext = getPageHeightContext(LengthBase.CUSTOM_BASE);
         PercentBaseContext neighbourContext;
         Rectangle vpRect;
-        if (spm.getWritingMode() == EN_LR_TB || spm.getWritingMode() == EN_RL_TB) {
+        // [TBD] WRITING MODE ALERT
+        switch ( getWritingMode().getEnumValue() ) {
+        default:
+        case Constants.EN_LR_TB:
+        case Constants.EN_RL_TB:
             neighbourContext = pageHeightContext;
             vpRect = new Rectangle(0, 0, getExtent().getValue(pageWidthContext), reldims.bpd);
-        } else {
+            break;
+        case Constants.EN_TB_LR:
+        case Constants.EN_TB_RL:
             neighbourContext = pageWidthContext;
             vpRect = new Rectangle(0, 0, reldims.bpd, getExtent().getValue(pageHeightContext));
+            break;
         }
         adjustIPD(vpRect, spm.getWritingMode(), neighbourContext);
         return vpRect;
Index: src/java/org/apache/fop/fo/pagination/RegionBody.java
===================================================================
--- src/java/org/apache/fop/fo/pagination/RegionBody.java	(revision 981406)
+++ src/java/org/apache/fop/fo/pagination/RegionBody.java	(working copy)
@@ -28,9 +28,11 @@
 import org.apache.fop.datatypes.LengthBase;
 import org.apache.fop.datatypes.Numeric;
 import org.apache.fop.datatypes.PercentBaseContext;
+import org.apache.fop.fo.Constants;
 import org.apache.fop.fo.FONode;
 import org.apache.fop.fo.PropertyList;
 import org.apache.fop.fo.properties.CommonMarginBlock;
+import org.apache.fop.traits.WritingMode;
 
 /**
  * Class modelling the <a href="http://www.w3.org/TR/xsl/#fo_region-body">
@@ -106,17 +108,29 @@
          * Also the values are resolved relative to the page size
          * and reference orientation.
          */
-        PercentBaseContext pageWidthContext = getPageWidthContext(LengthBase.CONTAINING_BLOCK_WIDTH);
-        PercentBaseContext pageHeightContext = getPageHeightContext(LengthBase.CONTAINING_BLOCK_WIDTH);
+        PercentBaseContext pageWidthContext
+            = getPageWidthContext(LengthBase.CONTAINING_BLOCK_WIDTH);
+        PercentBaseContext pageHeightContext
+            = getPageHeightContext(LengthBase.CONTAINING_BLOCK_WIDTH);
 
         int start;
         int end;
-        if (spm.getWritingMode() == EN_LR_TB) { // Left-to-right
+        // [TBD] WRITING MODE ALERT
+        switch ( getWritingMode().getEnumValue() ) {
+        default:
+        case Constants.EN_LR_TB:
             start = commonMarginBlock.marginLeft.getValue(pageWidthContext);
             end = commonMarginBlock.marginRight.getValue(pageWidthContext);
-        } else { // all other supported modes are right-to-left
+            break;
+        case Constants.EN_RL_TB:
             start = commonMarginBlock.marginRight.getValue(pageWidthContext);
             end = commonMarginBlock.marginLeft.getValue(pageWidthContext);
+            break;
+        case Constants.EN_TB_LR:
+        case Constants.EN_TB_RL:
+            start = commonMarginBlock.marginTop.getValue(pageWidthContext);
+            end = commonMarginBlock.marginBottom.getValue(pageWidthContext);
+            break;
         }
         int before = commonMarginBlock.spaceBefore.getOptimum(pageHeightContext)
                         .getLength().getValue(pageHeightContext);
Index: src/java/org/apache/fop/fo/pagination/SimplePageMaster.java
===================================================================
--- src/java/org/apache/fop/fo/pagination/SimplePageMaster.java	(revision 981406)
+++ src/java/org/apache/fop/fo/pagination/SimplePageMaster.java	(working copy)
@@ -36,6 +36,7 @@
 import org.apache.fop.fo.PropertyList;
 import org.apache.fop.fo.ValidationException;
 import org.apache.fop.fo.properties.CommonMarginBlock;
+import org.apache.fop.traits.WritingMode;
 
 /**
  * Class modelling the <a href="http://www.w3.org/TR/xsl/#fo_simple-page-master">
@@ -44,14 +45,14 @@
  * and attributes.
  */
 public class SimplePageMaster extends FObj {
-    // The value of properties relevant for fo:simple-page-master.
+    // The value of FO traits (refined properties) that apply to fo:simple-page-master.
     private CommonMarginBlock commonMarginBlock;
     private String masterName;
     private Length pageHeight;
     private Length pageWidth;
     private Numeric referenceOrientation;
-    private int writingMode;
-    // End of property values
+    private WritingMode writingMode;
+    // End of FO trait values
 
     /**
      * Page regions (regionClass, Region)
@@ -81,7 +82,7 @@
         pageHeight = pList.get(PR_PAGE_HEIGHT).getLength();
         pageWidth = pList.get(PR_PAGE_WIDTH).getLength();
         referenceOrientation = pList.get(PR_REFERENCE_ORIENTATION).getNumeric();
-        writingMode = pList.getWritingMode();
+        writingMode = WritingMode.valueOf(pList.get(PR_WRITING_MODE).getEnum());
 
         if (masterName == null || masterName.equals("")) {
             missingPropertyError("master-name");
@@ -273,31 +274,31 @@
         return commonMarginBlock;
     }
 
-    /** @return "master-name" property. */
+    /** @return "master-name" FO trait. */
     public String getMasterName() {
         return masterName;
     }
 
-    /** @return the "page-width" property. */
+    /** @return the "page-width" FO trait. */
     public Length getPageWidth() {
         return pageWidth;
     }
 
-    /** @return the "page-height" property. */
+    /** @return the "page-height" FO trait. */
     public Length getPageHeight() {
         return pageHeight;
     }
 
-    /** @return the "writing-mode" property. */
-    public int getWritingMode() {
-        return writingMode;
-    }
-
-    /** @return the "reference-orientation" property. */
+    /** @return the "reference-orientation" FO trait. */
     public int getReferenceOrientation() {
         return referenceOrientation.getValue();
     }
 
+    /** @return the "writing-mode" FO trait. */
+    public WritingMode getWritingMode() {
+        return writingMode;
+    }
+
     /** {@inheritDoc} */
     public String getLocalName() {
         return "simple-page-master";
Index: src/java/org/apache/fop/fo/pagination/PageSequence.java
===================================================================
--- src/java/org/apache/fop/fo/pagination/PageSequence.java	(revision 981406)
+++ src/java/org/apache/fop/fo/pagination/PageSequence.java	(working copy)
@@ -25,22 +25,28 @@
 import org.xml.sax.Locator;
 
 import org.apache.fop.apps.FOPException;
+import org.apache.fop.datatypes.Numeric;
 import org.apache.fop.fo.FONode;
 import org.apache.fop.fo.PropertyList;
 import org.apache.fop.fo.ValidationException;
+import org.apache.fop.traits.Direction;
+import org.apache.fop.traits.WritingMode;
+import org.apache.fop.traits.WritingModeTraits;
+import org.apache.fop.traits.WritingModeTraitsGetter;
 
 /**
  * Class modelling the <a href="http://www.w3.org/TR/xsl/#fo_page-sequence">
  * <code>fo:page-sequence</code></a> object.
  */
-public class PageSequence extends AbstractPageSequence {
+public class PageSequence extends AbstractPageSequence implements WritingModeTraitsGetter {
 
-    // The value of properties relevant for fo:page-sequence.
+    // The value of FO traits (refined properties) that apply to fo:page-sequence.
     private String country;
     private String language;
     private String masterReference;
-    //private int writingMode; //XSL 1.1
-    // End of property values
+    private Numeric referenceOrientation;
+    private WritingModeTraits writingModeTraits;
+    // End of trait values
 
     // There doesn't seem to be anything in the spec requiring flows
     // to be in the order given, only that they map to the regions
@@ -86,8 +92,9 @@
         country = pList.get(PR_COUNTRY).getString();
         language = pList.get(PR_LANGUAGE).getString();
         masterReference = pList.get(PR_MASTER_REFERENCE).getString();
-        //writingMode = pList.getWritingMode();
-
+        referenceOrientation = pList.get(PR_REFERENCE_ORIENTATION).getNumeric();
+        writingModeTraits = new WritingModeTraits
+            ( WritingMode.valueOf(pList.get(PR_WRITING_MODE).getEnum()) );
         if (masterReference == null || masterReference.equals("")) {
             missingPropertyError("master-reference");
         }
@@ -254,10 +261,9 @@
      * @return the SimplePageMaster to use for this page
      * @throws PageProductionException if there's a problem determining the page master
      */
-    public SimplePageMaster getNextSimplePageMaster(int page,
-                                                    boolean isFirstPage,
-                                                    boolean isLastPage,
-                                                    boolean isBlank) throws PageProductionException {
+    public SimplePageMaster getNextSimplePageMaster
+        ( int page, boolean isFirstPage, boolean isLastPage, boolean isBlank )
+        throws PageProductionException {
 
         if (pageSequenceMaster == null) {
             return simplePageMaster;
@@ -293,8 +299,8 @@
     }
 
     /**
-     * Get the value of the <code>master-reference</code> property.
-     * @return the "master-reference" property
+     * Get the value of the <code>master-reference</code> trait.
+     * @return the "master-reference" trait
      */
     public String getMasterReference() {
         return masterReference;
@@ -314,22 +320,100 @@
     }
 
     /**
-     * Get the value of the <code>country</code> property.
-     * @return the country property value
+     * Get the value of the <code>country</code> trait.
+     * @return the country trait value
      */
     public String getCountry() {
         return this.country;
     }
 
     /**
-     * Get the value of the <code>language</code> property.
-     * @return the language property value
+     * Get the value of the <code>language</code> trait.
+     * @return the language trait value
      */
     public String getLanguage() {
         return this.language;
     }
 
     /**
+     * Get the value of the <code>reference-orientation</code> trait.
+     * @return the reference orientation trait value
+     */
+    public int getReferenceOrientation() {
+        if ( referenceOrientation != null ) {
+            return referenceOrientation.getValue();
+        } else {
+            return 0;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Direction getInlineProgressionDirection() {
+        if ( writingModeTraits != null ) {
+            return writingModeTraits.getInlineProgressionDirection();
+        } else {
+            return Direction.LR;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Direction getBlockProgressionDirection() {
+        if ( writingModeTraits != null ) {
+            return writingModeTraits.getBlockProgressionDirection();
+        } else {
+            return Direction.TB;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Direction getColumnProgressionDirection() {
+        if ( writingModeTraits != null ) {
+            return writingModeTraits.getColumnProgressionDirection();
+        } else {
+            return Direction.LR;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Direction getRowProgressionDirection() {
+        if ( writingModeTraits != null ) {
+            return writingModeTraits.getRowProgressionDirection();
+        } else {
+            return Direction.TB;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Direction getShiftDirection() {
+        if ( writingModeTraits != null ) {
+            return writingModeTraits.getShiftDirection();
+        } else {
+            return Direction.TB;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public WritingMode getWritingMode() {
+        if ( writingModeTraits != null ) {
+            return writingModeTraits.getWritingMode();
+        } else {
+            return WritingMode.LR_TB;
+        }
+    }
+
+    /**
      * Releases a page-sequence's children after the page-sequence has been fully processed.
      */
     public void releasePageSequence() {
Index: src/java/org/apache/fop/fo/pagination/RegionAfter.java
===================================================================
--- src/java/org/apache/fop/fo/pagination/RegionAfter.java	(revision 981406)
+++ src/java/org/apache/fop/fo/pagination/RegionAfter.java	(working copy)
@@ -23,6 +23,7 @@
 import java.awt.Rectangle;
 
 // FOP
+import org.apache.fop.fo.Constants;
 import org.apache.fop.fo.FONode;
 import org.apache.fop.datatypes.FODimension;
 import org.apache.fop.datatypes.LengthBase;
@@ -52,14 +53,22 @@
         PercentBaseContext pageHeightContext = getPageHeightContext(LengthBase.CUSTOM_BASE);
         PercentBaseContext neighbourContext;
         Rectangle vpRect;
-        if (spm.getWritingMode() == EN_LR_TB || spm.getWritingMode() == EN_RL_TB) {
+
+        // [TBD] WRITING MODE ALERT
+        switch ( getWritingMode().getEnumValue() ) {
+        default:
+        case Constants.EN_LR_TB:
+        case Constants.EN_RL_TB:
             neighbourContext = pageWidthContext;
             vpRect = new Rectangle(0, reldims.bpd - getExtent().getValue(pageHeightContext)
                                    , reldims.ipd, getExtent().getValue(pageHeightContext));
-        } else {
+            break;
+        case Constants.EN_TB_LR:
+        case Constants.EN_TB_RL:
             neighbourContext = pageHeightContext;
             vpRect = new Rectangle(0, reldims.bpd - getExtent().getValue(pageWidthContext)
                                    , getExtent().getValue(pageWidthContext), reldims.ipd);
+            break;
         }
         if (getPrecedence() == EN_FALSE) {
             adjustIPD(vpRect, spm.getWritingMode(), neighbourContext);
Index: src/java/org/apache/fop/fo/pagination/RegionBefore.java
===================================================================
--- src/java/org/apache/fop/fo/pagination/RegionBefore.java	(revision 981406)
+++ src/java/org/apache/fop/fo/pagination/RegionBefore.java	(working copy)
@@ -23,6 +23,7 @@
 import java.awt.Rectangle;
 
 // FOP
+import org.apache.fop.fo.Constants;
 import org.apache.fop.datatypes.FODimension;
 import org.apache.fop.datatypes.LengthBase;
 import org.apache.fop.datatypes.SimplePercentBaseContext;
@@ -73,12 +74,19 @@
         }
         SimplePercentBaseContext neighbourContext;
         Rectangle vpRect;
-        if (spm.getWritingMode() == EN_LR_TB || spm.getWritingMode() == EN_RL_TB) {
+        // [TBD] WRITING MODE ALERT
+        switch ( getWritingMode().getEnumValue() ) {
+        default:
+        case Constants.EN_LR_TB:
+        case Constants.EN_RL_TB:
             neighbourContext = pageWidthContext;
             vpRect = new Rectangle(0, 0, reldims.ipd, getExtent().getValue(pageHeightContext));
-        } else {
+            break;
+        case Constants.EN_TB_LR:
+        case Constants.EN_TB_RL:
             neighbourContext = pageHeightContext;
             vpRect = new Rectangle(0, 0, getExtent().getValue(pageWidthContext), reldims.ipd);
+            break;
         }
         if (getPrecedence() == EN_FALSE) {
             adjustIPD(vpRect, spm.getWritingMode(), neighbourContext);
Index: src/java/org/apache/fop/fo/pagination/RegionEnd.java
===================================================================
--- src/java/org/apache/fop/fo/pagination/RegionEnd.java	(revision 981406)
+++ src/java/org/apache/fop/fo/pagination/RegionEnd.java	(working copy)
@@ -23,6 +23,7 @@
 import java.awt.Rectangle;
 
 // FOP
+import org.apache.fop.fo.Constants;
 import org.apache.fop.fo.FONode;
 import org.apache.fop.datatypes.FODimension;
 import org.apache.fop.datatypes.LengthBase;
@@ -52,17 +53,24 @@
         PercentBaseContext pageHeightContext = getPageHeightContext(LengthBase.CUSTOM_BASE);
         PercentBaseContext neighbourContext;
         Rectangle vpRect;
-        if (spm.getWritingMode() == EN_LR_TB || spm.getWritingMode() == EN_RL_TB) {
+        // [TBD] WRITING MODE ALERT
+        switch ( getWritingMode().getEnumValue() ) {
+        default:
+        case Constants.EN_LR_TB:
+        case Constants.EN_RL_TB:
             neighbourContext = pageHeightContext;
             vpRect = new Rectangle(reldims.ipd - getExtent().getValue(pageWidthContext), 0,
                     getExtent().getValue(pageWidthContext), reldims.bpd);
-        } else {
+            break;
+        case Constants.EN_TB_LR:
+        case Constants.EN_TB_RL:
             // Rectangle:  x , y (of top left point), width, height
             neighbourContext = pageWidthContext;
             vpRect = new Rectangle(reldims.ipd - getExtent().getValue(pageHeightContext), 0,
                     reldims.bpd, getExtent().getValue(pageHeightContext));
+            break;
         }
-        adjustIPD(vpRect, spm.getWritingMode(), neighbourContext);
+        adjustIPD(vpRect, getWritingMode(), neighbourContext);
         return vpRect;
     }
 
Index: src/java/org/apache/fop/fo/pagination/RegionSE.java
===================================================================
--- src/java/org/apache/fop/fo/pagination/RegionSE.java	(revision 981406)
+++ src/java/org/apache/fop/fo/pagination/RegionSE.java	(working copy)
@@ -26,6 +26,7 @@
 import org.apache.fop.datatypes.PercentBaseContext;
 import org.apache.fop.fo.FONode;
 import org.apache.fop.fo.PropertyList;
+import org.apache.fop.traits.WritingMode;
 
 /**
  * Abstract base class for <a href="http://www.w3.org/TR/xsl/#fo_region-start">
@@ -61,7 +62,8 @@
      * @param wm writing mode
      * @param siblingContext the context to use to resolve extent on siblings
      */
-    protected void adjustIPD(Rectangle vpRefRect, int wm, PercentBaseContext siblingContext) {
+    protected void adjustIPD
+        ( Rectangle vpRefRect, WritingMode wm, PercentBaseContext siblingContext ) {
         int offset = 0;
         RegionBefore before = (RegionBefore) getSiblingRegion(FO_REGION_BEFORE);
         if (before != null && before.getPrecedence() == EN_TRUE) {
@@ -72,8 +74,9 @@
         if (after != null && after.getPrecedence() == EN_TRUE) {
             offset += after.getExtent().getValue(siblingContext);
         }
+        // [TBD] WRITING MODE ALERT
         if (offset > 0) {
-            if (wm == EN_LR_TB || wm == EN_RL_TB) {
+            if (wm == WritingMode.LR_TB || wm == WritingMode.RL_TB) {
                 vpRefRect.height -= offset;
             } else {
                 vpRefRect.width -= offset;
Index: src/java/org/apache/fop/fo/pagination/Region.java
===================================================================
--- src/java/org/apache/fop/fo/pagination/Region.java	(revision 981406)
+++ src/java/org/apache/fop/fo/pagination/Region.java	(working copy)
@@ -33,20 +33,21 @@
 import org.apache.fop.fo.ValidationException;
 import org.apache.fop.fo.expr.PropertyException;
 import org.apache.fop.fo.properties.CommonBorderPaddingBackground;
+import org.apache.fop.traits.WritingMode;
 
 /**
  * This is an abstract base class for pagination regions.
  */
 public abstract class Region extends FObj {
-    // The value of properties relevant for fo:region
+    // The value of FO traits (refined properties) that apply to fo:region
     private CommonBorderPaddingBackground commonBorderPaddingBackground;
     // private ToBeImplementedProperty clip
     private int displayAlign;
     private int overflow;
     private String regionName;
     private Numeric referenceOrientation;
-    private int writingMode;
-    // End of property values
+    private WritingMode writingMode;
+    // End of FO trait values
 
     private SimplePageMaster layoutMaster;
 
@@ -68,7 +69,7 @@
         overflow = pList.get(PR_OVERFLOW).getEnum();
         regionName = pList.get(PR_REGION_NAME).getString();
         referenceOrientation = pList.get(PR_REFERENCE_ORIENTATION).getNumeric();
-        writingMode = pList.getWritingMode();
+        writingMode = WritingMode.valueOf(pList.get(PR_WRITING_MODE).getEnum());
 
         // regions may have name, or default
         if (regionName.equals("")) {
@@ -171,28 +172,28 @@
         return commonBorderPaddingBackground;
     }
 
-    /** @return the "region-name" property. */
+    /** @return the "region-name" FO trait. */
     public String getRegionName() {
         return regionName;
     }
 
-    /** @return the "writing-mode" property. */
-    public int getWritingMode() {
-        return writingMode;
-    }
-
-    /** @return the "overflow" property. */
+    /** @return the "overflow" FO trait. */
     public int getOverflow() {
         return overflow;
     }
 
-    /** @return the display-align property. */
+    /** @return the display-align FO trait. */
     public int getDisplayAlign() {
         return displayAlign;
     }
 
-    /** @return the "reference-orientation" property. */
+    /** @return the "reference-orientation" FO trait. */
     public int getReferenceOrientation() {
         return referenceOrientation.getValue();
     }
+
+    /** @return the "writing-mode" FO trait. */
+    public WritingMode getWritingMode() {
+        return writingMode;
+    }
 }
Index: src/java/org/apache/fop/fo/pagination/RegionBA.java
===================================================================
--- src/java/org/apache/fop/fo/pagination/RegionBA.java	(revision 981406)
+++ src/java/org/apache/fop/fo/pagination/RegionBA.java	(working copy)
@@ -26,6 +26,7 @@
 import org.apache.fop.datatypes.PercentBaseContext;
 import org.apache.fop.fo.FONode;
 import org.apache.fop.fo.PropertyList;
+import org.apache.fop.traits.WritingMode;
 
 /**
  * Abstract base class for <a href="http://www.w3.org/TR/xsl/#fo_region-before">
@@ -70,7 +71,8 @@
      * @param wm writing mode
      * @param siblingContext the context to use to resolve extent on siblings
      */
-    protected void adjustIPD(Rectangle vpRefRect, int wm, PercentBaseContext siblingContext) {
+    protected void adjustIPD
+        ( Rectangle vpRefRect, WritingMode wm, PercentBaseContext siblingContext ) {
         int offset = 0;
         RegionStart start = (RegionStart) getSiblingRegion(FO_REGION_START);
         if (start != null) {
@@ -81,8 +83,9 @@
         if (end != null) {
             offset += end.getExtent().getValue(siblingContext);
         }
+        // [TBD] WRITING MODE ALERT
         if (offset > 0) {
-            if (wm == EN_LR_TB || wm == EN_RL_TB) {
+            if (wm == WritingMode.LR_TB || wm == WritingMode.RL_TB) {
                 vpRefRect.width -= offset;
             } else {
                 vpRefRect.height -= offset;
Index: src/java/org/apache/fop/fo/properties/DimensionPropertyMaker.java
===================================================================
--- src/java/org/apache/fop/fo/properties/DimensionPropertyMaker.java	(revision 981406)
+++ src/java/org/apache/fop/fo/properties/DimensionPropertyMaker.java	(working copy)
@@ -30,27 +30,49 @@
  * Window - Preferences - Java - Code Generation - Code and Comments
  */
 public class DimensionPropertyMaker extends CorrespondingPropertyMaker {
-    int[][] extraCorresponding = null;
 
+    private int[][] extraCorresponding;
+
+    /**
+     * Instantiate a dimension property maker.
+     * @param baseMaker the base property maker
+     */
     public DimensionPropertyMaker(PropertyMaker baseMaker) {
         super(baseMaker);
     }
 
+    /**
+     * Set extra correspondences.
+     * @param extraCorresponding an array of four element integer arrays
+     */
     public void setExtraCorresponding(int[][] extraCorresponding) {
+        if ( extraCorresponding == null ) {
+            throw new NullPointerException();
+        }
+        for ( int i = 0; i < extraCorresponding.length; i++ ) {
+            int[] eca = extraCorresponding[i];
+            if ( ( eca == null ) || ( eca.length != 4 ) ) {
+                throw new IllegalArgumentException ( "bad sub-array @ [" + i + "]" );
+            }
+        }
         this.extraCorresponding = extraCorresponding;
     }
 
+    /** {@inheritDoc} */
     public boolean isCorrespondingForced(PropertyList propertyList) {
-        if (super.isCorrespondingForced(propertyList))
+        if ( super.isCorrespondingForced(propertyList) ) {
             return true;
+        }
         for (int i = 0; i < extraCorresponding.length; i++) {
             int wmcorr = extraCorresponding[i][0]; //propertyList.getWritingMode()];
-            if (propertyList.getExplicit(wmcorr) != null)
+            if (propertyList.getExplicit(wmcorr) != null) {
                 return true;
+            }
         }
         return false;
     }
 
+    /** {@inheritDoc} */
     public Property compute(PropertyList propertyList) throws PropertyException {
         // Based on [width|height]
         Property p = super.compute(propertyList);
@@ -59,18 +81,20 @@
         }
 
         // Based on min-[width|height]
-        int wmcorr = propertyList.getWritingMode(extraCorresponding[0][0],
+        int wmcorr = propertyList.selectFromWritingMode(extraCorresponding[0][0],
                                         extraCorresponding[0][1],
-                                        extraCorresponding[0][2]);
+                                        extraCorresponding[0][2],
+                                        extraCorresponding[0][3]);
         Property subprop = propertyList.getExplicitOrShorthand(wmcorr);
         if (subprop != null) {
             baseMaker.setSubprop(p, Constants.CP_MINIMUM, subprop);
         }
 
         // Based on max-[width|height]
-        wmcorr = propertyList.getWritingMode(extraCorresponding[1][0],
+        wmcorr = propertyList.selectFromWritingMode(extraCorresponding[1][0],
                                     extraCorresponding[1][1],
-                                    extraCorresponding[1][2]);
+                                    extraCorresponding[1][2],
+                                    extraCorresponding[1][3]);
         subprop = propertyList.getExplicitOrShorthand(wmcorr);
         // TODO: Don't set when NONE.
         if (subprop != null) {
Index: src/java/org/apache/fop/fo/properties/CorrespondingPropertyMaker.java
===================================================================
--- src/java/org/apache/fop/fo/properties/CorrespondingPropertyMaker.java	(revision 981406)
+++ src/java/org/apache/fop/fo/properties/CorrespondingPropertyMaker.java	(working copy)
@@ -27,23 +27,43 @@
 /**
  */
 public class CorrespondingPropertyMaker {
+
+    /** baseMaker */
     protected PropertyMaker baseMaker;
-    protected int lr_tb;
-    protected int rl_tb;
-    protected int tb_rl;
-    protected boolean useParent;
+    /** lrtb */
+    protected int lrtb;
+    /** rltb */
+    protected int rltb;
+    /** tbrl */
+    protected int tbrl;
+    /** tblr */
+    protected int tblr;
+
+    private boolean useParent;
     private boolean relative;
 
+    /**
+     * Instantiate corresponding property maker.
+     * @param baseMaker a base property maker
+     */
     public CorrespondingPropertyMaker(PropertyMaker baseMaker) {
         this.baseMaker = baseMaker;
         baseMaker.setCorresponding(this);
     }
 
+    /**
+     * Set corresponding property values.
+     * @param lrtb a corresponding value
+     * @param rltb a corresponding value
+     * @param tbrl a corresponding value
+     * @param tblr a corresponding value
+     */
 
-    public void setCorresponding(int lr_tb, int rl_tb, int tb_rl) {
-        this.lr_tb = lr_tb;
-        this.rl_tb = rl_tb;
-        this.tb_rl = tb_rl;
+    public void setCorresponding(int lrtb, int rltb, int tbrl, int tblr) {
+        this.lrtb = lrtb;
+        this.rltb = rltb;
+        this.tbrl = tbrl;
+        this.tblr = tblr;
     }
 
     /**
@@ -55,6 +75,10 @@
         this.useParent = useParent;
     }
 
+    /**
+     * Set relative flag.
+     * @param relative true if properties operate on a relative direction
+     */
     public void setRelative(boolean relative) {
         this.relative = relative;
     }
@@ -83,7 +107,7 @@
 
         PropertyList pList = getWMPropertyList(propertyList);
         if (pList != null) {
-            int correspondingId = pList.getWritingMode(lr_tb, rl_tb, tb_rl);
+            int correspondingId = pList.selectFromWritingMode(lrtb, rltb, tbrl, tblr);
 
             if (pList.getExplicit(correspondingId) != null) {
                 return true;
@@ -107,7 +131,7 @@
         if (pList == null) {
             return null;
         }
-        int correspondingId = pList.getWritingMode(lr_tb, rl_tb, tb_rl);
+        int correspondingId = pList.selectFromWritingMode(lrtb, rltb, tbrl, tblr);
 
         Property p = propertyList.getExplicitOrShorthand(correspondingId);
         if (p != null) {
Index: src/java/org/apache/fop/fo/properties/IndentPropertyMaker.java
===================================================================
--- src/java/org/apache/fop/fo/properties/IndentPropertyMaker.java	(revision 981406)
+++ src/java/org/apache/fop/fo/properties/IndentPropertyMaker.java	(working copy)
@@ -55,6 +55,9 @@
      * @param paddingCorresponding the corresping propids.
      */
     public void setPaddingCorresponding(int[] paddingCorresponding) {
+        if ( ( paddingCorresponding == null ) || ( paddingCorresponding.length != 4 ) ) {
+            throw new IllegalArgumentException();
+        }
         this.paddingCorresponding = paddingCorresponding;
     }
 
@@ -63,6 +66,9 @@
      * @param borderWidthCorresponding the corresping propids.
      */
     public void setBorderWidthCorresponding(int[] borderWidthCorresponding) {
+        if ( ( borderWidthCorresponding == null ) || ( borderWidthCorresponding.length != 4 ) ) {
+            throw new IllegalArgumentException();
+        }
         this.borderWidthCorresponding = borderWidthCorresponding;
     }
 
@@ -93,7 +99,7 @@
         Numeric padding = getCorresponding(paddingCorresponding, propertyList).getNumeric();
         Numeric border = getCorresponding(borderWidthCorresponding, propertyList).getNumeric();
 
-        int marginProp = pList.getWritingMode(lr_tb, rl_tb, tb_rl);
+        int marginProp = pList.selectFromWritingMode(lrtb, rltb, tbrl, tblr);
         // Calculate the absolute margin.
         if (propertyList.getExplicitOrShorthand(marginProp) == null) {
             Property indent = propertyList.getExplicit(baseMaker.propId);
@@ -149,7 +155,7 @@
         Numeric padding = getCorresponding(paddingCorresponding, propertyList).getNumeric();
         Numeric border = getCorresponding(borderWidthCorresponding, propertyList).getNumeric();
 
-        int marginProp = pList.getWritingMode(lr_tb, rl_tb, tb_rl);
+        int marginProp = pList.selectFromWritingMode(lrtb, rltb, tbrl, tblr);
 
         //Determine whether the nearest anscestor indent was specified through
         //start-indent|end-indent or through a margin property.
@@ -199,7 +205,8 @@
                 throws PropertyException {
         PropertyList pList = getWMPropertyList(propertyList);
         if (pList != null) {
-            int wmcorr = pList.getWritingMode(corresponding[0], corresponding[1], corresponding[2]);
+            int wmcorr = pList.selectFromWritingMode
+                ( corresponding[0], corresponding[1], corresponding[2], corresponding[3] );
             return propertyList.get(wmcorr);
         } else {
             return null;
Index: src/java/org/apache/fop/fo/FOPropertyMapping.java
===================================================================
--- src/java/org/apache/fop/fo/FOPropertyMapping.java	(revision 981406)
+++ src/java/org/apache/fop/fo/FOPropertyMapping.java	(working copy)
@@ -627,7 +627,7 @@
         m.setDefault("black");
         corr = new CorrespondingPropertyMaker(m);
         corr.setCorresponding(PR_BORDER_TOP_COLOR, PR_BORDER_TOP_COLOR,
-                PR_BORDER_RIGHT_COLOR);
+                              PR_BORDER_RIGHT_COLOR, PR_BORDER_LEFT_COLOR);
         corr.setRelative(true);
         addPropertyMaker("border-before-color", m);
 
@@ -636,7 +636,7 @@
         m.useGeneric(genericBorderStyle);
         corr = new CorrespondingPropertyMaker(m);
         corr.setCorresponding(PR_BORDER_TOP_STYLE, PR_BORDER_TOP_STYLE,
-                PR_BORDER_RIGHT_STYLE);
+                              PR_BORDER_RIGHT_STYLE, PR_BORDER_LEFT_STYLE);
         corr.setRelative(true);
         addPropertyMaker("border-before-style", m);
 
@@ -646,7 +646,7 @@
         m.getSubpropMaker(CP_CONDITIONALITY).setDefault("discard");
         corr = new CorrespondingPropertyMaker(m);
         corr.setCorresponding(PR_BORDER_TOP_WIDTH, PR_BORDER_TOP_WIDTH,
-                PR_BORDER_RIGHT_WIDTH);
+                              PR_BORDER_RIGHT_WIDTH, PR_BORDER_LEFT_WIDTH);
         corr.setRelative(true);
         addPropertyMaker("border-before-width", m);
 
@@ -657,7 +657,7 @@
         m.setDefault("black");
         corr = new CorrespondingPropertyMaker(m);
         corr.setCorresponding(PR_BORDER_BOTTOM_COLOR, PR_BORDER_BOTTOM_COLOR,
-                PR_BORDER_LEFT_COLOR);
+                              PR_BORDER_LEFT_COLOR, PR_BORDER_RIGHT_COLOR);
         corr.setRelative(true);
         addPropertyMaker("border-after-color", m);
 
@@ -666,7 +666,7 @@
         m.useGeneric(genericBorderStyle);
         corr = new CorrespondingPropertyMaker(m);
         corr.setCorresponding(PR_BORDER_BOTTOM_STYLE, PR_BORDER_BOTTOM_STYLE,
-                PR_BORDER_LEFT_STYLE);
+                              PR_BORDER_LEFT_STYLE, PR_BORDER_RIGHT_STYLE);
         corr.setRelative(true);
         addPropertyMaker("border-after-style", m);
 
@@ -676,7 +676,7 @@
         m.getSubpropMaker(CP_CONDITIONALITY).setDefault("discard");
         corr = new CorrespondingPropertyMaker(m);
         corr.setCorresponding(PR_BORDER_BOTTOM_WIDTH, PR_BORDER_BOTTOM_WIDTH,
-                PR_BORDER_LEFT_WIDTH);
+                              PR_BORDER_LEFT_WIDTH, PR_BORDER_LEFT_WIDTH);
         corr.setRelative(true);
         addPropertyMaker("border-after-width", m);
 
@@ -687,7 +687,7 @@
         m.setDefault("black");
         corr = new CorrespondingPropertyMaker(m);
         corr.setCorresponding(PR_BORDER_LEFT_COLOR, PR_BORDER_RIGHT_COLOR,
-                PR_BORDER_TOP_COLOR);
+                              PR_BORDER_TOP_COLOR, PR_BORDER_TOP_COLOR);
         corr.setRelative(true);
         addPropertyMaker("border-start-color", m);
 
@@ -696,7 +696,7 @@
         m.useGeneric(genericBorderStyle);
         corr = new CorrespondingPropertyMaker(m);
         corr.setCorresponding(PR_BORDER_LEFT_STYLE, PR_BORDER_RIGHT_STYLE,
-                PR_BORDER_TOP_STYLE);
+                              PR_BORDER_TOP_STYLE, PR_BORDER_TOP_STYLE);
         corr.setRelative(true);
         addPropertyMaker("border-start-style", m);
 
@@ -706,7 +706,7 @@
         m.getSubpropMaker(CP_CONDITIONALITY).setDefault("discard");
         corr = new CorrespondingPropertyMaker(m);
         corr.setCorresponding(PR_BORDER_LEFT_WIDTH, PR_BORDER_RIGHT_WIDTH,
-                PR_BORDER_TOP_WIDTH);
+                              PR_BORDER_TOP_WIDTH, PR_BORDER_TOP_WIDTH);
         corr.setRelative(true);
         addPropertyMaker("border-start-width", m);
 
@@ -717,7 +717,7 @@
         m.setDefault("black");
         corr = new CorrespondingPropertyMaker(m);
         corr.setCorresponding(PR_BORDER_RIGHT_COLOR, PR_BORDER_LEFT_COLOR,
-                PR_BORDER_BOTTOM_COLOR);
+                              PR_BORDER_BOTTOM_COLOR, PR_BORDER_BOTTOM_COLOR);
         corr.setRelative(true);
         addPropertyMaker("border-end-color", m);
 
@@ -726,7 +726,7 @@
         m.useGeneric(genericBorderStyle);
         corr = new CorrespondingPropertyMaker(m);
         corr.setCorresponding(PR_BORDER_RIGHT_STYLE, PR_BORDER_LEFT_STYLE,
-                PR_BORDER_BOTTOM_STYLE);
+                              PR_BORDER_BOTTOM_STYLE, PR_BORDER_BOTTOM_STYLE);
         corr.setRelative(true);
         addPropertyMaker("border-end-style", m);
 
@@ -736,7 +736,7 @@
         m.getSubpropMaker(CP_CONDITIONALITY).setDefault("discard");
         corr = new CorrespondingPropertyMaker(m);
         corr.setCorresponding(PR_BORDER_RIGHT_WIDTH, PR_BORDER_LEFT_WIDTH,
-                PR_BORDER_BOTTOM_WIDTH);
+                              PR_BORDER_BOTTOM_WIDTH, PR_BORDER_BOTTOM_WIDTH);
         corr.setRelative(true);
         addPropertyMaker("border-end-width", m);
 
@@ -750,7 +750,7 @@
         m.addShorthand(s_generics[PR_BORDER]);
         corr = new CorrespondingPropertyMaker(m);
         corr.setCorresponding(PR_BORDER_BEFORE_COLOR, PR_BORDER_BEFORE_COLOR,
-                PR_BORDER_START_COLOR);
+                              PR_BORDER_START_COLOR, PR_BORDER_START_COLOR);
         addPropertyMaker("border-top-color", m);
 
         // border-top-style
@@ -761,7 +761,7 @@
         m.addShorthand(s_generics[PR_BORDER]);
         corr = new CorrespondingPropertyMaker(m);
         corr.setCorresponding(PR_BORDER_BEFORE_STYLE, PR_BORDER_BEFORE_STYLE,
-                PR_BORDER_START_STYLE);
+                              PR_BORDER_START_STYLE, PR_BORDER_START_STYLE);
         addPropertyMaker("border-top-style", m);
 
         // border-top-width
@@ -773,7 +773,7 @@
         bwm.addShorthand(s_generics[PR_BORDER]);
         corr = new CorrespondingPropertyMaker(bwm);
         corr.setCorresponding(PR_BORDER_BEFORE_WIDTH, PR_BORDER_BEFORE_WIDTH,
-                PR_BORDER_START_WIDTH);
+                              PR_BORDER_START_WIDTH, PR_BORDER_START_WIDTH);
         addPropertyMaker("border-top-width", bwm);
 
         // border-bottom-color
@@ -786,7 +786,7 @@
         m.addShorthand(s_generics[PR_BORDER]);
         corr = new CorrespondingPropertyMaker(m);
         corr.setCorresponding(PR_BORDER_AFTER_COLOR, PR_BORDER_AFTER_COLOR,
-                PR_BORDER_END_COLOR);
+                              PR_BORDER_END_COLOR, PR_BORDER_END_COLOR);
         addPropertyMaker("border-bottom-color", m);
 
         // border-bottom-style
@@ -797,7 +797,7 @@
         m.addShorthand(s_generics[PR_BORDER]);
         corr = new CorrespondingPropertyMaker(m);
         corr.setCorresponding(PR_BORDER_AFTER_STYLE, PR_BORDER_AFTER_STYLE,
-                PR_BORDER_END_STYLE);
+                              PR_BORDER_END_STYLE, PR_BORDER_END_STYLE);
         addPropertyMaker("border-bottom-style", m);
 
         // border-bottom-width
@@ -809,7 +809,7 @@
         bwm.addShorthand(s_generics[PR_BORDER]);
         corr = new CorrespondingPropertyMaker(bwm);
         corr.setCorresponding(PR_BORDER_AFTER_WIDTH, PR_BORDER_AFTER_WIDTH,
-                PR_BORDER_END_WIDTH);
+                              PR_BORDER_END_WIDTH, PR_BORDER_END_WIDTH);
         addPropertyMaker("border-bottom-width", bwm);
 
         // border-left-color
@@ -822,7 +822,7 @@
         m.addShorthand(s_generics[PR_BORDER]);
         corr = new CorrespondingPropertyMaker(m);
         corr.setCorresponding(PR_BORDER_START_COLOR, PR_BORDER_END_COLOR,
-                PR_BORDER_AFTER_COLOR);
+                              PR_BORDER_AFTER_COLOR, PR_BORDER_BEFORE_COLOR);
         addPropertyMaker("border-left-color", m);
 
         // border-left-style
@@ -833,7 +833,7 @@
         m.addShorthand(s_generics[PR_BORDER]);
         corr = new CorrespondingPropertyMaker(m);
         corr.setCorresponding(PR_BORDER_START_STYLE, PR_BORDER_END_STYLE,
-                PR_BORDER_AFTER_STYLE);
+                              PR_BORDER_AFTER_STYLE, PR_BORDER_BEFORE_STYLE);
         addPropertyMaker("border-left-style", m);
 
         // border-left-width
@@ -845,7 +845,7 @@
         bwm.addShorthand(s_generics[PR_BORDER]);
         corr = new CorrespondingPropertyMaker(bwm);
         corr.setCorresponding(PR_BORDER_START_WIDTH, PR_BORDER_END_WIDTH,
-                PR_BORDER_AFTER_WIDTH);
+                              PR_BORDER_AFTER_WIDTH, PR_BORDER_BEFORE_WIDTH);
         addPropertyMaker("border-left-width", bwm);
 
         // border-right-color
@@ -858,7 +858,7 @@
         m.addShorthand(s_generics[PR_BORDER]);
         corr = new CorrespondingPropertyMaker(m);
         corr.setCorresponding(PR_BORDER_END_COLOR, PR_BORDER_START_COLOR,
-                PR_BORDER_BEFORE_COLOR);
+                              PR_BORDER_BEFORE_COLOR, PR_BORDER_AFTER_COLOR);
         addPropertyMaker("border-right-color", m);
 
         // border-right-style
@@ -869,7 +869,7 @@
         m.addShorthand(s_generics[PR_BORDER]);
         corr = new CorrespondingPropertyMaker(m);
         corr.setCorresponding(PR_BORDER_END_STYLE, PR_BORDER_START_STYLE,
-                PR_BORDER_BEFORE_STYLE);
+                              PR_BORDER_BEFORE_STYLE, PR_BORDER_AFTER_STYLE);
         addPropertyMaker("border-right-style", m);
 
         // border-right-width
@@ -881,7 +881,7 @@
         bwm.addShorthand(s_generics[PR_BORDER]);
         corr = new CorrespondingPropertyMaker(bwm);
         corr.setCorresponding(PR_BORDER_END_WIDTH, PR_BORDER_START_WIDTH,
-                PR_BORDER_BEFORE_WIDTH);
+                              PR_BORDER_BEFORE_WIDTH, PR_BORDER_AFTER_WIDTH);
         addPropertyMaker("border-right-width", bwm);
 
         // padding-before
@@ -890,7 +890,7 @@
         m.getSubpropMaker(CP_CONDITIONALITY).setDefault("discard");
         corr = new CorrespondingPropertyMaker(m);
         corr.setCorresponding(PR_PADDING_TOP, PR_PADDING_TOP,
-                PR_PADDING_RIGHT);
+                              PR_PADDING_RIGHT, PR_PADDING_LEFT);
         corr.setRelative(true);
         addPropertyMaker("padding-before", m);
 
@@ -900,7 +900,7 @@
         m.getSubpropMaker(CP_CONDITIONALITY).setDefault("discard");
         corr = new CorrespondingPropertyMaker(m);
         corr.setCorresponding(PR_PADDING_BOTTOM, PR_PADDING_BOTTOM,
-                PR_PADDING_LEFT);
+                              PR_PADDING_LEFT, PR_PADDING_RIGHT);
         corr.setRelative(true);
         addPropertyMaker("padding-after", m);
 
@@ -910,7 +910,7 @@
         m.getSubpropMaker(CP_CONDITIONALITY).setDefault("discard");
         corr = new CorrespondingPropertyMaker(m);
         corr.setCorresponding(PR_PADDING_LEFT, PR_PADDING_RIGHT,
-                PR_PADDING_TOP);
+                              PR_PADDING_TOP, PR_PADDING_TOP);
         corr.setRelative(true);
         addPropertyMaker("padding-start", m);
 
@@ -920,7 +920,7 @@
         m.getSubpropMaker(CP_CONDITIONALITY).setDefault("discard");
         corr = new CorrespondingPropertyMaker(m);
         corr.setCorresponding(PR_PADDING_RIGHT, PR_PADDING_LEFT,
-                PR_PADDING_BOTTOM);
+                              PR_PADDING_BOTTOM, PR_PADDING_BOTTOM);
         corr.setRelative(true);
         addPropertyMaker("padding-end", m);
 
@@ -929,7 +929,7 @@
         m.useGeneric(genericPadding);
         corr = new CorrespondingPropertyMaker(m);
         corr.setCorresponding(PR_PADDING_BEFORE, PR_PADDING_BEFORE,
-                PR_PADDING_START);
+                              PR_PADDING_START, PR_PADDING_START);
         addPropertyMaker("padding-top", m);
 
         // padding-bottom
@@ -937,7 +937,7 @@
         m.useGeneric(genericPadding);
         corr = new CorrespondingPropertyMaker(m);
         corr.setCorresponding(PR_PADDING_AFTER, PR_PADDING_AFTER,
-                PR_PADDING_END);
+                              PR_PADDING_END, PR_PADDING_END);
         addPropertyMaker("padding-bottom", m);
 
         // padding-left
@@ -945,7 +945,7 @@
         m.useGeneric(genericPadding);
         corr = new CorrespondingPropertyMaker(m);
         corr.setCorresponding(PR_PADDING_START, PR_PADDING_END,
-                PR_PADDING_AFTER);
+                              PR_PADDING_AFTER, PR_PADDING_BEFORE);
         addPropertyMaker("padding-left", m);
 
         // padding-right
@@ -953,7 +953,7 @@
         m.useGeneric(genericPadding);
         corr = new CorrespondingPropertyMaker(m);
         corr.setCorresponding(PR_PADDING_END, PR_PADDING_START,
-                PR_PADDING_BEFORE);
+                              PR_PADDING_BEFORE, PR_PADDING_AFTER);
         addPropertyMaker("padding-right", m);
     }
 
@@ -1146,7 +1146,7 @@
         m  = new SpaceProperty.Maker(PR_SPACE_BEFORE);
         m.useGeneric(genericSpace);
         corr = new SpacePropertyMaker(m);
-        corr.setCorresponding(PR_MARGIN_TOP, PR_MARGIN_TOP, PR_MARGIN_RIGHT);
+        corr.setCorresponding(PR_MARGIN_TOP, PR_MARGIN_TOP, PR_MARGIN_RIGHT, PR_MARGIN_LEFT);
         corr.setUseParent(false);
         corr.setRelative(true);
         addPropertyMaker("space-before", m);
@@ -1155,7 +1155,7 @@
         m  = new SpaceProperty.Maker(PR_SPACE_AFTER);
         m.useGeneric(genericSpace);
         corr = new SpacePropertyMaker(m);
-        corr.setCorresponding(PR_MARGIN_BOTTOM, PR_MARGIN_BOTTOM, PR_MARGIN_LEFT);
+        corr.setCorresponding(PR_MARGIN_BOTTOM, PR_MARGIN_BOTTOM, PR_MARGIN_LEFT, PR_MARGIN_RIGHT);
         corr.setUseParent(false);
         corr.setRelative(true);
         addPropertyMaker("space-after", m);
@@ -1166,14 +1166,14 @@
         m.setDefault("0pt");
         m.setPercentBase(LengthBase.CONTAINING_REFAREA_WIDTH);
         IndentPropertyMaker sCorr = new IndentPropertyMaker(m);
-        sCorr.setCorresponding(PR_MARGIN_LEFT, PR_MARGIN_RIGHT, PR_MARGIN_TOP);
+        sCorr.setCorresponding(PR_MARGIN_LEFT, PR_MARGIN_RIGHT, PR_MARGIN_TOP, PR_MARGIN_TOP);
         sCorr.setUseParent(false);
         sCorr.setRelative(true);
         sCorr.setPaddingCorresponding(new int[] {
-             PR_PADDING_LEFT, PR_PADDING_RIGHT, PR_PADDING_TOP
+             PR_PADDING_LEFT, PR_PADDING_RIGHT, PR_PADDING_TOP, PR_PADDING_TOP
         });
         sCorr.setBorderWidthCorresponding(new int[] {
-            PR_BORDER_LEFT_WIDTH, PR_BORDER_RIGHT_WIDTH, PR_BORDER_TOP_WIDTH
+            PR_BORDER_LEFT_WIDTH, PR_BORDER_RIGHT_WIDTH, PR_BORDER_TOP_WIDTH, PR_BORDER_TOP_WIDTH
         });
         addPropertyMaker("start-indent", m);
 
@@ -1183,14 +1183,16 @@
         m.setDefault("0pt");
         m.setPercentBase(LengthBase.CONTAINING_REFAREA_WIDTH);
         IndentPropertyMaker eCorr = new IndentPropertyMaker(m);
-        eCorr.setCorresponding(PR_MARGIN_RIGHT, PR_MARGIN_LEFT, PR_MARGIN_BOTTOM);
+        eCorr.setCorresponding(PR_MARGIN_RIGHT, PR_MARGIN_LEFT,
+                               PR_MARGIN_BOTTOM, PR_MARGIN_BOTTOM);
         eCorr.setUseParent(false);
         eCorr.setRelative(true);
         eCorr.setPaddingCorresponding(new int[] {
-            PR_PADDING_RIGHT, PR_PADDING_LEFT, PR_PADDING_BOTTOM
+            PR_PADDING_RIGHT, PR_PADDING_LEFT, PR_PADDING_BOTTOM, PR_PADDING_BOTTOM
         });
         eCorr.setBorderWidthCorresponding(new int[] {
-            PR_BORDER_RIGHT_WIDTH, PR_BORDER_LEFT_WIDTH, PR_BORDER_BOTTOM_WIDTH
+            PR_BORDER_RIGHT_WIDTH, PR_BORDER_LEFT_WIDTH,
+            PR_BORDER_BOTTOM_WIDTH, PR_BORDER_BOTTOM_WIDTH
         });
         addPropertyMaker("end-indent", m);
     }
@@ -1347,10 +1349,10 @@
         m.addSubpropMaker(l);
 
         pdim = new DimensionPropertyMaker(m);
-        pdim.setCorresponding(PR_HEIGHT, PR_HEIGHT, PR_WIDTH);
+        pdim.setCorresponding(PR_HEIGHT, PR_HEIGHT, PR_WIDTH, PR_WIDTH);
         pdim.setExtraCorresponding(new int[][] {
-             {PR_MIN_HEIGHT, PR_MIN_HEIGHT, PR_MIN_WIDTH, },
-             {PR_MAX_HEIGHT, PR_MAX_HEIGHT, PR_MAX_WIDTH, }
+             {PR_MIN_HEIGHT, PR_MIN_HEIGHT, PR_MIN_WIDTH, PR_MIN_WIDTH},
+             {PR_MAX_HEIGHT, PR_MAX_HEIGHT, PR_MAX_WIDTH, PR_MAX_WIDTH}
         });
         pdim.setRelative(true);
         m.setCorresponding(pdim);
@@ -1414,10 +1416,10 @@
 
         pdim = new DimensionPropertyMaker(m);
         pdim.setRelative(true);
-        pdim.setCorresponding(PR_WIDTH, PR_WIDTH, PR_HEIGHT);
+        pdim.setCorresponding(PR_WIDTH, PR_WIDTH, PR_HEIGHT, PR_HEIGHT);
         pdim.setExtraCorresponding(new int[][] {
-            {PR_MIN_WIDTH, PR_MIN_WIDTH, PR_MIN_HEIGHT, },
-            {PR_MAX_WIDTH, PR_MAX_WIDTH, PR_MAX_HEIGHT, }
+            {PR_MIN_WIDTH, PR_MIN_WIDTH, PR_MIN_HEIGHT, PR_MIN_HEIGHT },
+            {PR_MAX_WIDTH, PR_MAX_WIDTH, PR_MAX_HEIGHT, PR_MIN_HEIGHT }
         });
         m.setCorresponding(pdim);
         addPropertyMaker("inline-progression-dimension", m);
@@ -2494,6 +2496,7 @@
         m.addEnum("lr-tb", getEnumProperty(EN_LR_TB, "LR_TB"));
         m.addEnum("rl-tb", getEnumProperty(EN_RL_TB, "RL_TB"));
         m.addEnum("tb-rl", getEnumProperty(EN_TB_RL, "TB_RL"));
+        m.addEnum("tb-lr", getEnumProperty(EN_TB_LR, "TB_LR"));
         m.addKeyword("lr", "lr-tb");
         m.addKeyword("rl", "rl-tb");
         m.addKeyword("tb", "tb-rl");
Index: src/java/org/apache/fop/traits/WritingModeTraits.java
===================================================================
--- src/java/org/apache/fop/traits/WritingModeTraits.java	(revision 0)
+++ src/java/org/apache/fop/traits/WritingModeTraits.java	(revision 0)
@@ -0,0 +1,141 @@
+/*
+ * 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.traits;
+
+/**
+ * This class provides a reusable implementation of the WritingModeTraitsSetter
+ * interface.
+ */
+public class WritingModeTraits implements WritingModeTraitsSetter {
+
+    private Direction inlineProgressionDirection;
+    private Direction blockProgressionDirection;
+    private Direction columnProgressionDirection;
+    private Direction rowProgressionDirection;
+    private Direction shiftDirection;
+    private WritingMode writingMode;
+
+    /**
+     * Default writing mode traits constructor.
+     */
+    public WritingModeTraits() {
+        this ( WritingMode.LR_TB );
+    }
+
+    /**
+     * Construct writing mode traits using the specified writing mode.
+     * @param writingMode a writing mode traits object
+     */
+    public WritingModeTraits ( WritingMode writingMode ) {
+        assignWritingModeTraits ( writingMode );
+    }
+
+    /**
+     * @return the "inline-progression-direction" trait.
+     */
+    public Direction getInlineProgressionDirection() {
+        return inlineProgressionDirection;
+    }
+
+    /**
+     * @param direction the "inline-progression-direction" trait.
+     */
+    public void setInlineProgressionDirection ( Direction direction ) {
+        this.inlineProgressionDirection = direction;
+    }
+
+    /**
+     * @return the "block-progression-direction" trait.
+     */
+    public Direction getBlockProgressionDirection() {
+        return blockProgressionDirection;
+    }
+
+    /**
+     * @param direction the "block-progression-direction" trait.
+     */
+    public void setBlockProgressionDirection ( Direction direction ) {
+        this.blockProgressionDirection = direction;
+    }
+
+    /**
+     * @return the "column-progression-direction" trait.
+     */
+    public Direction getColumnProgressionDirection() {
+        return columnProgressionDirection;
+    }
+
+    /**
+     * @param direction the "column-progression-direction" trait.
+     */
+    public void setColumnProgressionDirection ( Direction direction ) {
+        this.columnProgressionDirection = direction;
+    }
+
+    /**
+     * @return the "row-progression-direction" trait.
+     */
+    public Direction getRowProgressionDirection() {
+        return rowProgressionDirection;
+    }
+
+    /**
+     * @param direction the "row-progression-direction" trait.
+     */
+    public void setRowProgressionDirection ( Direction direction ) {
+        this.rowProgressionDirection = direction;
+    }
+
+    /**
+     * @return the "shift-direction" trait.
+     */
+    public Direction getShiftDirection() {
+        return shiftDirection;
+    }
+
+    /**
+     * @param direction the "shift-direction" trait.
+     */
+    public void setShiftDirection ( Direction direction ) {
+        this.shiftDirection = direction;
+    }
+
+    /**
+     * @return the "writing-mode" trait.
+     */
+    public WritingMode getWritingMode() {
+        return writingMode;
+    }
+
+    /**
+     * @param writingMode the "writing-mode" trait.
+     */
+    public void setWritingMode ( WritingMode writingMode ) {
+        this.writingMode = writingMode;
+    }
+
+    /**
+     * @param writingMode the "writing-mode" trait.
+     */
+    public void assignWritingModeTraits ( WritingMode writingMode ) {
+        writingMode.assignWritingModeTraits ( this );
+    }
+
+}
Index: src/java/org/apache/fop/traits/Direction.java
===================================================================
--- src/java/org/apache/fop/traits/Direction.java	(revision 0)
+++ src/java/org/apache/fop/traits/Direction.java	(revision 0)
@@ -0,0 +1,92 @@
+/*
+ * 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.traits;
+
+import java.io.ObjectStreamException;
+
+import org.apache.fop.fo.Constants;
+
+/**
+ * Enumeration class for direction traits, namely {inline,block}-progression-direction
+ * and shift-direction.
+ */
+public final class Direction extends TraitEnum {
+
+    private static final long serialVersionUID = 1L;
+
+    private static final String[] DIRECTION_NAMES = new String[]
+        {"lr", "rl", "tb", "bt"};
+
+    private static final int[] DIRECTION_VALUES = new int[]
+        {Constants.EN_LR, Constants.EN_RL, Constants.EN_TB, Constants.EN_BT};
+
+    /** direction: left-to-right */
+    public static final Direction LR = new Direction(0);
+    /** direction: right-to-left */
+    public static final Direction RL = new Direction(1);
+    /** direction: top-to-bottom */
+    public static final Direction TB = new Direction(2);
+    /** direction: bottom-to-top */
+    public static final Direction BT = new Direction(3);
+
+    private static final Direction[] DIRECTIONS = new Direction[] {LR, RL, TB, BT};
+
+    private Direction(int index) {
+        super(DIRECTION_NAMES[index], DIRECTION_VALUES[index]);
+    }
+
+    /**
+     * Returns the enumeration/singleton object based on its name.
+     * @param name the name of the enumeration value
+     * @return the enumeration object
+     */
+    public static Direction valueOf(String name) {
+        for (int i = 0; i < DIRECTIONS.length; i++) {
+            if (DIRECTIONS[i].getName().equalsIgnoreCase(name)) {
+                return DIRECTIONS[i];
+            }
+        }
+        throw new IllegalArgumentException("Illegal direction: " + name);
+    }
+
+    /**
+     * Returns the enumeration/singleton object based on its name.
+     * @param enumValue the enumeration value
+     * @return the enumeration object
+     */
+    public static Direction valueOf(int enumValue) {
+        for (int i = 0; i < DIRECTIONS.length; i++) {
+            if (DIRECTIONS[i].getEnumValue() == enumValue) {
+                return DIRECTIONS[i];
+            }
+        }
+        throw new IllegalArgumentException("Illegal direction: " + enumValue);
+    }
+
+    private Object readResolve() throws ObjectStreamException {
+        return valueOf(getName());
+    }
+
+    /** {@inheritDoc} */
+    public String toString() {
+        return getName();
+    }
+
+}
Index: src/java/org/apache/fop/traits/WritingModeTraitsSetter.java
===================================================================
--- src/java/org/apache/fop/traits/WritingModeTraitsSetter.java	(revision 0)
+++ src/java/org/apache/fop/traits/WritingModeTraitsSetter.java	(revision 0)
@@ -0,0 +1,70 @@
+/*
+ * 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.traits;
+
+/**
+ * This interface provides read and assignment access to FO traits related to writing mode.
+ */
+public interface WritingModeTraitsSetter extends WritingModeTraitsGetter {
+
+    /**
+     * Set value of inline-progression-direction trait.
+     * @param direction the "inline-progression-direction" trait
+     */
+    void setInlineProgressionDirection ( Direction direction );
+
+    /**
+     * Set value of block-progression-direction trait.
+     * @param direction the "block-progression-direction" trait
+     */
+    void setBlockProgressionDirection ( Direction direction );
+
+    /**
+     * Set value of column-progression-direction trait.
+     * @param direction the "column-progression-direction" trait
+     */
+    void setColumnProgressionDirection ( Direction direction );
+
+    /**
+     * Set value of row-progression-direction trait.
+     * @param direction the "row-progression-direction" trait
+     */
+    void setRowProgressionDirection ( Direction direction );
+
+    /**
+     * Set value of shift-direction trait.
+     * @param direction the "shift-direction" trait
+     */
+    void setShiftDirection ( Direction direction );
+
+    /**
+     * Set value of writing-mode trait.
+     * @param writingMode the "writing-mode" trait
+     */
+    void setWritingMode ( WritingMode writingMode );
+
+    /**
+     * Collectivelly assign values to all writing mode traits based upon a specific
+     * writing mode.
+     * @param writingMode the "writing-mode" trait
+     */
+    void assignWritingModeTraits ( WritingMode writingMode );
+
+}
Index: src/java/org/apache/fop/traits/WritingMode.java
===================================================================
--- src/java/org/apache/fop/traits/WritingMode.java	(revision 0)
+++ src/java/org/apache/fop/traits/WritingMode.java	(revision 0)
@@ -0,0 +1,140 @@
+/*
+ * 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.traits;
+
+import java.io.ObjectStreamException;
+
+import org.apache.fop.fo.Constants;
+
+/** Enumeration class for writing mode trait. */
+public final class WritingMode extends TraitEnum {
+
+    private static final long serialVersionUID = 1L;
+
+    private static final String[] WRITING_MODE_NAMES = new String[]
+        {"lr-tb", "rl-tb", "tb-lr", "tb-rl"};
+
+    private static final int[] WRITING_MODE_VALUES = new int[]
+        {Constants.EN_LR_TB, Constants.EN_RL_TB, Constants.EN_TB_LR, Constants.EN_TB_RL};
+
+    /** writing mode: lr-tb */
+    public static final WritingMode LR_TB = new WritingMode(0);
+    /** writing mode: rl-tb */
+    public static final WritingMode RL_TB = new WritingMode(1);
+    /** writing mode: tb-lr */
+    public static final WritingMode TB_LR = new WritingMode(2);
+    /** writing mode: tb-rl */
+    public static final WritingMode TB_RL = new WritingMode(3);
+
+    private static final WritingMode[] WRITING_MODES
+        = new WritingMode[] {LR_TB, RL_TB, TB_LR, TB_RL};
+
+    private WritingMode(int index) {
+        super(WRITING_MODE_NAMES[index], WRITING_MODE_VALUES[index]);
+    }
+
+    /**
+     * Assign writing mode traits from this trait to the specified
+     * writing mode traits setter.
+     * @param wms a writing mode traits setter
+     */
+    public void assignWritingModeTraits ( WritingModeTraitsSetter wms ) {
+        Direction inlineProgressionDirection;
+        Direction blockProgressionDirection;
+        Direction columnProgressionDirection;
+        Direction rowProgressionDirection;
+        Direction shiftDirection;
+        switch ( getEnumValue() ) {
+        default:
+        case Constants.EN_LR_TB:
+            inlineProgressionDirection = Direction.LR;
+            blockProgressionDirection = Direction.TB;
+            columnProgressionDirection = Direction.LR;
+            rowProgressionDirection = Direction.TB;
+            shiftDirection = Direction.BT;
+            break;
+        case Constants.EN_RL_TB:
+            inlineProgressionDirection = Direction.RL;
+            blockProgressionDirection = Direction.TB;
+            columnProgressionDirection = Direction.RL;
+            rowProgressionDirection = Direction.TB;
+            shiftDirection = Direction.BT;
+            break;
+        case Constants.EN_TB_LR:
+            inlineProgressionDirection = Direction.TB;
+            blockProgressionDirection = Direction.LR;
+            columnProgressionDirection = Direction.TB;
+            rowProgressionDirection = Direction.LR;
+            shiftDirection = Direction.RL;
+            break;
+        case Constants.EN_TB_RL:
+            inlineProgressionDirection = Direction.TB;
+            blockProgressionDirection = Direction.RL;
+            columnProgressionDirection = Direction.TB;
+            rowProgressionDirection = Direction.RL;
+            shiftDirection = Direction.LR;
+            break;
+        }
+        wms.setInlineProgressionDirection ( inlineProgressionDirection );
+        wms.setBlockProgressionDirection ( blockProgressionDirection );
+        wms.setColumnProgressionDirection ( columnProgressionDirection );
+        wms.setRowProgressionDirection ( rowProgressionDirection );
+        wms.setShiftDirection ( shiftDirection );
+        wms.setWritingMode ( this );
+    }
+
+    /**
+     * Returns the enumeration/singleton object based on its name.
+     * @param name the name of the enumeration value
+     * @return the enumeration object
+     */
+    public static WritingMode valueOf(String name) {
+        for (int i = 0; i < WRITING_MODES.length; i++) {
+            if (WRITING_MODES[i].getName().equalsIgnoreCase(name)) {
+                return WRITING_MODES[i];
+            }
+        }
+        throw new IllegalArgumentException("Illegal writing mode: " + name);
+    }
+
+    /**
+     * Returns the enumeration/singleton object based on its name.
+     * @param enumValue the enumeration value
+     * @return the enumeration object
+     */
+    public static WritingMode valueOf(int enumValue) {
+        for (int i = 0; i < WRITING_MODES.length; i++) {
+            if (WRITING_MODES[i].getEnumValue() == enumValue) {
+                return WRITING_MODES[i];
+            }
+        }
+        throw new IllegalArgumentException("Illegal writing mode: " + enumValue);
+    }
+
+    private Object readResolve() throws ObjectStreamException {
+        return valueOf(getName());
+    }
+
+    /** {@inheritDoc} */
+    public String toString() {
+        return getName();
+    }
+
+}
Index: src/java/org/apache/fop/traits/WritingModeTraitsGetter.java
===================================================================
--- src/java/org/apache/fop/traits/WritingModeTraitsGetter.java	(revision 0)
+++ src/java/org/apache/fop/traits/WritingModeTraitsGetter.java	(revision 0)
@@ -0,0 +1,58 @@
+/*
+ * 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.traits;
+
+/**
+ * This interface provides read access to FO traits related to writing mode.
+ */
+public interface WritingModeTraitsGetter {
+
+    /**
+     * @return the "inline-progression-direction" trait
+     */
+    Direction getInlineProgressionDirection();
+
+    /**
+     * @return the "block-progression-direction" trait
+     */
+    Direction getBlockProgressionDirection();
+
+    /**
+     * @return the "column-progression-direction" trait
+     */
+    Direction getColumnProgressionDirection();
+
+    /**
+     * @return the "row-progression-direction" trait
+     */
+
+    Direction getRowProgressionDirection();
+
+    /**
+     * @return the "shift-direction" trait
+     */
+    Direction getShiftDirection();
+
+    /**
+     * @return the "writing-mode" trait
+     */
+    WritingMode getWritingMode();
+
+}
Index: src/java/org/apache/fop/area/AreaTreeParser.java
===================================================================
--- src/java/org/apache/fop/area/AreaTreeParser.java	(revision 981406)
+++ src/java/org/apache/fop/area/AreaTreeParser.java	(working copy)
@@ -737,7 +737,7 @@
             public void startElement(Attributes attributes) {
                 InlineArea inl = new InlineArea();
                 transferForeignObjects(attributes, inl);
-                inl.setOffset(XMLUtil.getAttributeAsInt(attributes, "offset", 0));
+                inl.setBlockProgressionOffset(XMLUtil.getAttributeAsInt(attributes, "offset", 0));
                 setAreaAttributes(attributes, inl);
                 setTraits(attributes, inl, SUBSET_COMMON);
                 setTraits(attributes, inl, SUBSET_BOX);
@@ -757,7 +757,7 @@
             public void startElement(Attributes attributes) {
                 InlineParent ip = new InlineParent();
                 transferForeignObjects(attributes, ip);
-                ip.setOffset(XMLUtil.getAttributeAsInt(attributes, "offset", 0));
+                ip.setBlockProgressionOffset(XMLUtil.getAttributeAsInt(attributes, "offset", 0));
                 setAreaAttributes(attributes, ip);
                 setTraits(attributes, ip, SUBSET_COMMON);
                 setTraits(attributes, ip, SUBSET_BOX);
@@ -779,7 +779,7 @@
             public void startElement(Attributes attributes) {
                 InlineBlockParent ibp = new InlineBlockParent();
                 transferForeignObjects(attributes, ibp);
-                ibp.setOffset(XMLUtil.getAttributeAsInt(attributes, "offset", 0));
+                ibp.setBlockProgressionOffset(XMLUtil.getAttributeAsInt(attributes, "offset", 0));
                 setAreaAttributes(attributes, ibp);
                 setTraits(attributes, ibp, SUBSET_COMMON);
                 setTraits(attributes, ibp, SUBSET_BOX);
@@ -807,7 +807,7 @@
                 setTraits(attributes, text, SUBSET_COLOR);
                 setTraits(attributes, text, SUBSET_FONT);
                 text.setBaselineOffset(XMLUtil.getAttributeAsInt(attributes, "baseline", 0));
-                text.setOffset(XMLUtil.getAttributeAsInt(attributes, "offset", 0));
+                text.setBlockProgressionOffset(XMLUtil.getAttributeAsInt(attributes, "offset", 0));
                 text.setTextLetterSpaceAdjust(XMLUtil.getAttributeAsInt(attributes,
                         "tlsadjust", 0));
                 text.setTextWordSpaceAdjust(XMLUtil.getAttributeAsInt(attributes,
@@ -830,8 +830,10 @@
                 int[] letterAdjust
                         = ConversionUtils.toIntArray(
                             lastAttributes.getValue("letter-adjust"), "\\s");
+                int level = XMLUtil.getAttributeAsInt(lastAttributes, "level", -1);
                 content.flip();
-                WordArea word = new WordArea(content.toString().trim(), offset, letterAdjust);
+                WordArea word = new WordArea
+                    ( offset, level, content.toString().trim(), letterAdjust, null );
                 AbstractTextArea text = getCurrentText();
                 word.setParentArea(text);
                 text.addChildArea(word);
@@ -850,7 +852,8 @@
                 if (content.position() > 0) {
                     content.flip();
                     boolean adjustable = XMLUtil.getAttributeAsBoolean(lastAttributes, "adj", true);
-                    SpaceArea space = new SpaceArea(content.charAt(0), offset, adjustable);
+                    int level = XMLUtil.getAttributeAsInt(lastAttributes, "level", -1);
+                    SpaceArea space = new SpaceArea(offset, level, content.charAt(0), adjustable);
                     AbstractTextArea text = getCurrentText();
                     space.setParentArea(text);
                     text.addChildArea(space);
@@ -860,7 +863,7 @@
                     setTraits(lastAttributes, space, SUBSET_COMMON);
                     setTraits(lastAttributes, space, SUBSET_BOX);
                     setTraits(lastAttributes, space, SUBSET_COLOR);
-                    space.setOffset(offset);
+                    space.setBlockProgressionOffset(offset);
                     Area parent = (Area)areaStack.peek();
                     parent.addChildArea(space);
                 }
@@ -881,7 +884,8 @@
                 setTraits(attributes, leader, SUBSET_BOX);
                 setTraits(attributes, leader, SUBSET_COLOR);
                 setTraits(attributes, leader, SUBSET_FONT);
-                leader.setOffset(XMLUtil.getAttributeAsInt(attributes, "offset", 0));
+                leader.setBlockProgressionOffset
+                    ( XMLUtil.getAttributeAsInt(attributes, "offset", 0) );
                 String ruleStyle = attributes.getValue("ruleStyle");
                 if (ruleStyle != null) {
                     leader.setRuleStyle(ruleStyle);
@@ -904,7 +908,8 @@
                 setTraits(attributes, viewport, SUBSET_COLOR);
                 viewport.setContentPosition(XMLUtil.getAttributeAsRectangle2D(attributes, "pos"));
                 viewport.setClip(XMLUtil.getAttributeAsBoolean(attributes, "clip", false));
-                viewport.setOffset(XMLUtil.getAttributeAsInt(attributes, "offset", 0));
+                viewport.setBlockProgressionOffset
+                    ( XMLUtil.getAttributeAsInt(attributes, "offset", 0) );
                 setPtr(viewport, attributes);
                 Area parent = (Area)areaStack.peek();
                 parent.addChildArea(viewport);
@@ -1066,6 +1071,7 @@
         private void setAreaAttributes(Attributes attributes, Area area) {
             area.setIPD(Integer.parseInt(attributes.getValue("ipd")));
             area.setBPD(Integer.parseInt(attributes.getValue("bpd")));
+            area.setBidiLevel(XMLUtil.getAttributeAsInt(attributes, "level", -1));
         }
 
         private static final Object[] SUBSET_COMMON = new Object[] {
Index: src/java/org/apache/fop/area/inline/WordArea.java
===================================================================
--- src/java/org/apache/fop/area/inline/WordArea.java	(revision 981406)
+++ src/java/org/apache/fop/area/inline/WordArea.java	(working copy)
@@ -18,6 +18,10 @@
 /* $Id$ */
 package org.apache.fop.area.inline;
 
+import java.util.Arrays;
+
+import org.apache.fop.util.CharUtilities;
+
 /**
  * A string of characters without spaces
  */
@@ -26,22 +30,38 @@
     /** The text for this word area */
     protected String word;
 
-    /** The correction offset for the next area */
-    protected int offset = 0;
-
     /** An array of width for adjusting the individual letters (optional) */
     protected int[] letterAdjust;
 
     /**
+     * An array of resolved bidirectional levels corresponding to each character
+     * in word (optional)
+     */
+    protected int[] levels;
+
+    /**
+     * A flag indicating whether the content of word is reversed in relation to
+     * its original logical order.
+     */
+    protected boolean reversed;
+
+    /**
      * Create a word area
-     * @param w the word string
-     * @param o the offset for the next area
-     * @param la the letter adjust array (may be null)
+     * @param blockProgressionOffset the offset for this area
+     * @param level the bidirectional embedding level (or -1 if not defined) for word as a group
+     * @param word the word string
+     * @param letterAdjust the letter adjust array (may be null)
+     * @param levels array of per-character (glyph) bidirectional levels,
+     * in case word area is heterogenously leveled
      */
-    public WordArea(String w, int o, int[] la) {
-        word = w;
-        offset = o;
-        this.letterAdjust = la;
+    public WordArea
+        ( int blockProgressionOffset, int level, String word, int[] letterAdjust, int[] levels ) {
+        super ( blockProgressionOffset, level );
+        assert word != null;
+        this.word = word;
+        this.letterAdjust = letterAdjust;
+        this.levels = maybePopulateLevels ( levels, level, word.length() );
+        this.reversed = false;
     }
 
     /**
@@ -51,22 +71,103 @@
         return word;
     }
 
+    /** @return the array of letter adjust widths */
+    public int[] getLetterAdjustArray() {
+        return this.letterAdjust;
+    }
+
     /**
-     * @return Returns the offset.
+     * Obtain per-character (glyph) bidi levels.
+     * @return a (possibly empty) array of levels or null (if none resolved)
      */
-    public int getOffset() {
-        return offset;
+    public int[] getBidiLevels() {
+        return levels;
     }
+
     /**
-     * @param o The offset to set.
+     * <p>Obtain per-character (glyph) bidi levels over a specified subsequence.</p>
+     * <p>If word has been reversed, then the subsequence is over the reversed word.</p>
+     * @param start starting (inclusive) index of subsequence
+     * @param end ending (exclusive) index of subsequence
+     * @return a (possibly null) array of per-character (glyph) levels over the specified
+     * sequence
      */
-    public void setOffset(int o) {
-        offset = o;
+    public int[] getBidiLevels ( int start, int end ) {
+        assert start <= end;
+        if ( levels != null ) {
+            int n = end - start;
+            int[] levels = new int [ n ];
+            for ( int i = 0; i < n; i++ ) {
+                levels[i] = this.levels [ start + i ];
+            }
+            return levels;
+        } else {
+            return null;
+        }
     }
 
-    /** @return the array of letter adjust widths */
-    public int[] getLetterAdjustArray() {
-        return this.letterAdjust;
+    /**
+     * <p>Obtain per-character (glyph) level at a specified index position.</p>
+     * <p>If word has been reversed, then the position is relative to the reversed word.</p>
+     * @param position the index of the (possibly reversed) character from which to obtain the
+     * level
+     * @return a resolved bidirectional level or, if not specified, then -1
+     */
+    public int bidiLevelAt ( int position ) {
+        if ( position > word.length() ) {
+            throw new IndexOutOfBoundsException();
+        } else if ( levels != null ) {
+            return levels [ position ];
+        } else {
+            return -1;
+        }
     }
 
+    /**
+     * <p>Reverse characters and corresponding per-character levels if word's length is greater
+     * than one.</p>
+     * @param mirror if true, then perform mirroring if mirrorred characters
+     */
+    public void reverse ( boolean mirror ) {
+        if ( word.length() > 1 ) {
+            word = ( ( new StringBuffer ( word ) ) .reverse() ) .toString();
+            if ( levels != null ) {
+                reverse ( levels );
+            }
+            reversed = !reversed;
+            if ( mirror ) {
+                word = CharUtilities.mirror ( word );
+            }
+        }
+    }
+
+    /**
+     * <p>Determined if word has been reversed (in relation to original logical order).</p>
+     * <p>If a word is reversed, then both its characters (glyphs) and corresponding per-character
+     * levels are in reverse order.</p>
+     * <p>Note: this information is used in order to process non-spacing marks during rendering as
+     * well as provide hints for caret direction.</p>
+     * @return true if word is reversed
+     */
+    public boolean isReversed() {
+        return reversed;
+    }
+
+    private static int[] maybePopulateLevels ( int[] levels, int level, int count ) {
+        if ( ( levels == null ) && ( level >= 0 ) ) {
+            levels = new int[count];
+            Arrays.fill ( levels, level );
+        }
+        return levels;
+    }
+
+    private static void reverse ( int[] a ) {
+        for ( int i = 0, n = a.length, m = n / 2; i < m; i++ ) {
+            int k = n - i - 1;
+            int t = a [ k ];
+            a [ k ] = a [ i ];
+            a [ i ] = t;
+        }
+    }
+
 }
Index: src/java/org/apache/fop/area/inline/InlineArea.java
===================================================================
--- src/java/org/apache/fop/area/inline/InlineArea.java	(revision 981406)
+++ src/java/org/apache/fop/area/inline/InlineArea.java	(working copy)
@@ -74,7 +74,7 @@
     /**
      * offset position from before edge of parent area
      */
-    protected int offset = 0;
+    protected int blockProgressionOffset = 0;
 
     /**
      * parent area
@@ -96,6 +96,23 @@
     protected InlineAdjustingInfo adjustingInfo = null;
 
     /**
+     * Default constructor for inline area.
+     */
+    public InlineArea() {
+        this (  0, -1 );
+    }
+
+    /**
+     * Instantiate inline area.
+     * @param blockProgressionOffset a block progression offset or zero
+     * @param bidiLevel a resolved bidi level or -1
+     */
+    protected InlineArea ( int blockProgressionOffset, int bidiLevel ) {
+        this.blockProgressionOffset = blockProgressionOffset;
+        setBidiLevel(bidiLevel);
+    }
+
+    /**
      * @return the adjustment information object
      */
     public InlineAdjustingInfo getAdjustingInfo() {
@@ -133,25 +150,25 @@
     }
 
     /**
-     * Set the offset of this inline area.
+     * Set the block progression offset of this inline area.
      * This is used to set the offset of the inline area
      * which is relative to the before edge of the parent area.
      *
-     * @param offset the offset
+     * @param blockProgressionOffset the offset
      */
-    public void setOffset(int offset) {
-        this.offset = offset;
+    public void setBlockProgressionOffset(int blockProgressionOffset) {
+        this.blockProgressionOffset = blockProgressionOffset;
     }
 
     /**
-     * Get the offset of this inline area.
+     * Get the block progression offset of this inline area.
      * This returns the offset of the inline area
-     * which is relative to the before edge of the parent area.
+     * relative to the before edge of the parent area.
      *
-     * @return the offset
+     * @return the blockProgressionOffset
      */
-    public int getOffset() {
-        return offset;
+    public int getBlockProgressionOffset() {
+        return blockProgressionOffset;
     }
 
     /**
@@ -239,4 +256,3 @@
         }
     }
 }
-
Index: src/java/org/apache/fop/area/inline/TextArea.java
===================================================================
--- src/java/org/apache/fop/area/inline/TextArea.java	(revision 981406)
+++ src/java/org/apache/fop/area/inline/TextArea.java	(working copy)
@@ -19,6 +19,8 @@
 
 package org.apache.fop.area.inline;
 
+import org.apache.fop.util.CharUtilities;
+
 /**
  * A text inline area.
  */
@@ -55,33 +57,46 @@
      * @param offset the offset for the next area
      */
     public void addWord(String word, int offset) {
-        addWord(word, offset, null);
+        addWord(word, 0, null, null, offset);
     }
 
     /**
      * Create and add a WordArea child to this TextArea.
      *
-     * @param word   the word string
-     * @param offset the offset for the next area
+     * @param word the word string
+     * @param ipd the word's ipd
      * @param letterAdjust the letter adjustment array (may be null)
+     * @param levels array of resolved bidirection levels of word characters,
+     * or null if default level
+     * @param blockProgressionOffset the offset for the next area
      */
-    public void addWord(String word, int offset, int[] letterAdjust) {
-        WordArea wordArea = new WordArea(word, offset, letterAdjust);
+    public void addWord
+        ( String word, int ipd, int[] letterAdjust, int[] levels, int blockProgressionOffset ) {
+        int minWordLevel = findMinLevel ( levels );
+        WordArea wordArea = new WordArea
+            ( blockProgressionOffset, minWordLevel, word, letterAdjust, levels );
+        wordArea.setIPD ( ipd );
         addChildArea(wordArea);
         wordArea.setParentArea(this);
+        updateLevel(minWordLevel);
     }
 
     /**
      * Create and add a SpaceArea child to this TextArea
      *
-     * @param space      the space character
-     * @param offset     the offset for the next area
+     * @param space the space character
+     * @param ipd the space's ipd
+     * @param blockProgressionOffset     the offset for the next area
      * @param adjustable is this space adjustable?
+     * @param level resolved bidirection level of space character
      */
-    public void addSpace(char space, int offset, boolean adjustable) {
-        SpaceArea spaceArea = new SpaceArea(space, offset, adjustable);
+    public void addSpace
+        ( char space, int ipd, boolean adjustable, int blockProgressionOffset, int level ) {
+        SpaceArea spaceArea = new SpaceArea(blockProgressionOffset, level, space, adjustable);
+        spaceArea.setIPD ( ipd );
         addChildArea(spaceArea);
         spaceArea.setParentArea(this);
+        updateLevel(level);
     }
 
     /**
@@ -112,7 +127,45 @@
 
     /** {@inheritDoc} */
     public String toString() {
-        return "TextArea{text=" + getText() + "}";
+        StringBuffer sb = new StringBuffer(super.toString());
+        sb.append(" {text=\"");
+        sb.append(CharUtilities.toNCRefs(getText()));
+        sb.append("\"");
+        sb.append("}");
+        return sb.toString();
     }
+
+    private void updateLevel ( int newLevel ) {
+        if ( newLevel >= 0 ) {
+            int curLevel = getBidiLevel();
+            if ( curLevel >= 0 ) {
+                if ( newLevel < curLevel ) {
+                    setBidiLevel ( newLevel );
+                }
+            } else {
+                setBidiLevel ( newLevel );
+            }
+        }
+    }
+
+    private static int findMinLevel ( int[] levels ) {
+        if ( levels != null ) {
+            int lMin = Integer.MAX_VALUE;
+            for ( int i = 0, n = levels.length; i < n; i++ ) {
+                int l = levels [ i ];
+                if ( ( l >= 0 ) && ( l < lMin ) ) {
+                    lMin = l;
+                }
+            }
+            if ( lMin == Integer.MAX_VALUE ) {
+                return -1;
+            } else {
+                return lMin;
+            }
+        } else {
+            return -1;
+        }
+    }
+
 }
 
Index: src/java/org/apache/fop/area/inline/Viewport.java
===================================================================
--- src/java/org/apache/fop/area/inline/Viewport.java	(revision 981406)
+++ src/java/org/apache/fop/area/inline/Viewport.java	(working copy)
@@ -111,7 +111,7 @@
             out.writeFloat((float) contentPosition.getHeight());
         }
         out.writeBoolean(clip);
-        out.writeObject(props);
+        out.writeObject(traits);
         out.writeObject(content);
     }
 
@@ -124,7 +124,7 @@
                                                     in.readFloat());
         }
         this.clip = in.readBoolean();
-        this.props = (HashMap) in.readObject();
+        this.traits = (HashMap) in.readObject();
         this.content = (Area) in.readObject();
     }
 
Index: src/java/org/apache/fop/area/inline/SpaceArea.java
===================================================================
--- src/java/org/apache/fop/area/inline/SpaceArea.java	(revision 981406)
+++ src/java/org/apache/fop/area/inline/SpaceArea.java	(working copy)
@@ -35,25 +35,27 @@
 
     /**
      * Create a space area
-     * @param s the space character
-     * @param o the offset for the next area
-     * @param a is this space adjustable?
+     * @param space the space character
+     * @param blockProgressionOffset the offset for the next area
+     * @param adjustable is this space adjustable?
+     * @param bidiLevel the bidirectional embedding level (or -1 if not defined)
      */
-    public SpaceArea(char s, int o, boolean a) {
-        space = new String() + s;
-        offset = o;
-        isAdjustable = a;
+    public SpaceArea(int blockProgressionOffset, int bidiLevel, char space, boolean adjustable) {
+        super ( blockProgressionOffset, bidiLevel );
+        this.space = new String ( new char[] {space} );
+        this.isAdjustable = adjustable;
     }
 
     /**
      * @return Returns the space.
      */
     public String getSpace() {
-        return new String(space);
+        return space;
     }
 
     /** @return true if the space is adjustable (WRT word-space processing) */
     public boolean isAdjustable() {
         return this.isAdjustable;
     }
+
 }
Index: src/java/org/apache/fop/area/inline/FilledArea.java
===================================================================
--- src/java/org/apache/fop/area/inline/FilledArea.java	(revision 981406)
+++ src/java/org/apache/fop/area/inline/FilledArea.java	(working copy)
@@ -49,7 +49,7 @@
      * @param v the offset
      */
     /*
-    public void setOffset(int v) {
+    public void setBlockProgressionOffset(int v) {
         setChildOffset(inlines.listIterator(), v);
     }
     */
@@ -62,7 +62,7 @@
             } else if (child instanceof org.apache.fop.area.inline.Viewport) {
                 // nothing
             } else {
-                child.setOffset(v);
+                child.setBlockProgressionOffset(v);
             }
         }
     }
Index: src/java/org/apache/fop/area/inline/InlineParent.java
===================================================================
--- src/java/org/apache/fop/area/inline/InlineParent.java	(revision 981406)
+++ src/java/org/apache/fop/area/inline/InlineParent.java	(working copy)
@@ -21,8 +21,9 @@
 
 import org.apache.fop.area.Area;
 
+import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.List;
-import java.util.ArrayList;
 
 /**
  * Inline parent area.
@@ -60,6 +61,7 @@
             if (autoSize) {
                 increaseIPD(inlineChildArea.getAllocIPD());
             }
+            updateLevel ( childArea.getBidiLevel() );
         }
     }
 
@@ -89,5 +91,28 @@
         }
         return bUnresolvedAreasPresent;
     }
+
+    /**
+     * Reset bidirectionality level of all children to default (-1),
+     * signalling that they will inherit the level of their parent text area.
+     */
+    public void resetChildrenLevel() {
+        for ( Iterator it = inlines.iterator(); it.hasNext();) {
+            ( (InlineArea) it.next() ) .resetBidiLevel();
+        }
+    }
+    private void updateLevel ( int newLevel ) {
+        if ( newLevel >= 0 ) {
+            int curLevel = getBidiLevel();
+            if ( curLevel >= 0 ) {
+                if ( newLevel < curLevel ) {
+                    setBidiLevel ( newLevel );
+                }
+            } else {
+                setBidiLevel ( newLevel );
+            }
+        }
+    }
+
+
 }
-
Index: src/java/org/apache/fop/area/Area.java
===================================================================
--- src/java/org/apache/fop/area/Area.java	(revision 981406)
+++ src/java/org/apache/fop/area/Area.java	(working copy)
@@ -38,27 +38,7 @@
  * Base object for all areas.
  */
 public class Area extends AreaTreeObject implements Serializable {
-    // stacking directions
-    /**
-     * Stacking left to right
-     */
-    public static final int LR = 0;
 
-    /**
-     * Stacking right to left
-     */
-    public static final int RL = 1;
-
-    /**
-     * Stacking top to bottom
-     */
-    public static final int TB = 2;
-
-    /**
-     * Stacking bottom to top
-     */
-    public static final int BT = 3;
-
     // orientations for reference areas
     /**
      * Normal orientation
@@ -127,16 +107,20 @@
     protected int bpd;
 
     /**
+     * Resolved bidirectional level for area.
+     */
+    protected int bidiLevel = -1;
+
+    /**
      * Traits for this area stored in a HashMap
      */
-    protected Map props = null;
+    protected Map traits = null;
 
     /**
      * logging instance
      */
     protected static Log log = LogFactory.getLog(Area.class);
 
-
     /**
      * Get the area class of this area.
      *
@@ -223,6 +207,32 @@
     }
 
     /**
+     * Set the bidirectional embedding level.
+     *
+     * @param bidiLevel the bidirectional embedding level
+     */
+    public void setBidiLevel ( int bidiLevel ) {
+        this.bidiLevel = bidiLevel;
+    }
+
+    /**
+     * Reset the bidirectional embedding level to default
+     * value (-1).
+     */
+    public void resetBidiLevel() {
+        setBidiLevel(-1);
+    }
+
+    /**
+     * Get the bidirectional embedding level.
+     *
+     * @return the bidirectional embedding level
+     */
+    public int getBidiLevel() {
+        return bidiLevel;
+    }
+
+    /**
      * Return the sum of region border- and padding-before
      *
      * @return width in millipoints
@@ -376,24 +386,37 @@
      * @param prop the value of the trait
      */
     public void addTrait(Object traitCode, Object prop) {
-        if (props == null) {
-            props = new java.util.HashMap(20);
+        if (traits == null) {
+            traits = new java.util.HashMap(20);
         }
-        props.put(traitCode, prop);
+        traits.put(traitCode, prop);
     }
 
     /**
+     * Set traits on this area, copying from an existing traits map.
+     *
+     * @param traits the map of traits
+     */
+    public void setTraits ( Map traits ) {
+        if ( traits != null ) {
+            this.traits = new java.util.HashMap ( traits );
+        } else {
+            this.traits = null;
+        }
+    }
+
+    /**
      * Get the map of all traits on this area.
      *
      * @return the map of traits
      */
     public Map getTraits() {
-        return this.props;
+        return this.traits;
     }
 
     /** @return true if the area has traits */
     public boolean hasTraits() {
-        return (this.props != null);
+        return (this.traits != null);
     }
 
     /**
@@ -403,7 +426,7 @@
      * @return the trait value
      */
     public Object getTrait(Object oTraitCode) {
-        return (props != null ? props.get(oTraitCode) : null);
+        return (traits != null ? traits.get(oTraitCode) : null);
     }
 
     /**
@@ -453,4 +476,3 @@
         return sb.toString();
     }
 }
-
Index: src/java/org/apache/fop/area/LineArea.java
===================================================================
--- src/java/org/apache/fop/area/LineArea.java	(revision 981406)
+++ src/java/org/apache/fop/area/LineArea.java	(working copy)
@@ -37,7 +37,7 @@
      * that can be used in order to re-compute adjustement and / or indents when a
      * page-number or a page-number-citation is resolved
      */
-    private class LineAdjustingInfo implements Serializable {
+    private final class LineAdjustingInfo implements Serializable {
         private int lineAlignment;
         private int difference;
         private int availableStretch;
@@ -106,6 +106,15 @@
     }
 
     /**
+     * <p>Set (en masse) the inline child areas of this line area.</p>
+     * <p> Used by bidirectional processing after line area consituent reordering.</p>
+     * @param inlineAreas the list of inline areas
+     */
+    public void setInlineAreas ( List inlineAreas ) {
+        this.inlineAreas = inlineAreas;
+    }
+
+    /**
      * Get the inline child areas of this line area.
      *
      * @return the list of inline areas
@@ -179,7 +188,7 @@
                 // if the LineArea has already been added to the area tree,
                 // call finalize(); otherwise, wait for the LineLM to call it
                 if (adjustingInfo.bAddedToAreaTree) {
-                    finalise();
+                    finish();
                 }
                 break;
             default:
@@ -192,7 +201,7 @@
      * and destroy the AdjustingInfo object if there are
      * no UnresolvedAreas left
      */
-    public void finalise() {
+    public void finish() {
         if (adjustingInfo.lineAlignment == Constants.EN_JUSTIFY) {
             // justified line: apply the variation factor
             boolean bUnresolvedAreasPresent = false;
Index: src/java/org/apache/fop/area/Trait.java
===================================================================
--- src/java/org/apache/fop/area/Trait.java	(revision 981406)
+++ src/java/org/apache/fop/area/Trait.java	(working copy)
@@ -27,6 +27,8 @@
 import org.apache.fop.fo.Constants;
 import org.apache.fop.fonts.FontTriplet;
 import org.apache.fop.traits.BorderProps;
+import org.apache.fop.traits.Direction;
+import org.apache.fop.traits.WritingMode;
 import org.apache.fop.util.ColorUtil;
 
 // properties should be serialized by the holder
@@ -34,8 +36,11 @@
  * Area traits used for rendering.
  * This class represents an area trait that specifies a value for rendering.
  */
-public class Trait implements Serializable {
+public final class Trait {
 
+    private Trait() {
+    }
+
     /**
      * Id reference line, not resolved.
      * not sure if this is needed.
@@ -198,8 +203,17 @@
     /** The ptr trait. Used for accessibility   */
     public static final Integer PTR = new Integer(37);
 
+    /** writing mode trait */
+    public static final Integer WRITING_MODE = new Integer(38);
+    /** inline progression direction trait */
+    public static final Integer INLINE_PROGRESSION_DIRECTION = new Integer(39);
+    /** block progression direction trait */
+    public static final Integer BLOCK_PROGRESSION_DIRECTION = new Integer(40);
+    /** shift direction trait */
+    public static final Integer SHIFT_DIRECTION = new Integer(41);
+
     /** Maximum value used by trait keys */
-    public static final int MAX_TRAIT_KEY = 37;
+    public static final int MAX_TRAIT_KEY = 41;
 
     private static final TraitInfo[] TRAIT_INFO = new TraitInfo[MAX_TRAIT_KEY + 1];
 
@@ -281,6 +295,14 @@
                 new TraitInfo("is-reference-area", Boolean.class));
         put(IS_VIEWPORT_AREA,
                 new TraitInfo("is-viewport-area", Boolean.class));
+        put(WRITING_MODE,
+                new TraitInfo("writing-mode", WritingMode.class));
+        put(INLINE_PROGRESSION_DIRECTION,
+                new TraitInfo("inline-progression-direction", Direction.class));
+        put(BLOCK_PROGRESSION_DIRECTION,
+                new TraitInfo("block-progression-direction", Direction.class));
+        put(SHIFT_DIRECTION,
+                new TraitInfo("shift-direction", Direction.class));
 
     }
 
Index: src/java/org/apache/fop/area/CTM.java
===================================================================
--- src/java/org/apache/fop/area/CTM.java	(revision 981406)
+++ src/java/org/apache/fop/area/CTM.java	(working copy)
@@ -26,6 +26,7 @@
 
 import org.apache.fop.datatypes.FODimension;
 import org.apache.fop.fo.Constants;
+import org.apache.fop.traits.WritingMode;
 
 /**
  * Describe a PDF or PostScript style coordinate transformation matrix (CTM).
@@ -121,16 +122,16 @@
      * Return a CTM which will transform coordinates for a particular writing-mode
      * into normalized first quandrant coordinates.
      * @param wm A writing mode constant from fo.properties.WritingMode, ie.
-     * one of LR_TB, RL_TB, TB_RL.
+     * one of LR_TB, RL_TB, TB_RL, TB_LR.
      * @param ipd The inline-progression dimension of the reference area whose
      * CTM is being set..
      * @param bpd The block-progression dimension of the reference area whose
      * CTM is being set.
      * @return a new CTM with the required transform
      */
-    public static CTM getWMctm(int wm, int ipd, int bpd) {
+    public static CTM getWMctm(WritingMode wm, int ipd, int bpd) {
         CTM wmctm;
-        switch (wm) {
+        switch (wm.getEnumValue()) {
             case Constants.EN_LR_TB:
                 return new CTM(CTM_LRTB);
             case Constants.EN_RL_TB:
@@ -139,6 +140,7 @@
                 return wmctm;
                 //return  CTM_RLTB.translate(ipd, 0);
             case Constants.EN_TB_RL:  // CJK
+            case Constants.EN_TB_LR:  // CJK
                 wmctm = new CTM(CTM_TBRL);
                 wmctm.e = bpd;
                 return wmctm;
@@ -279,7 +281,7 @@
      * @return CTM the coordinate transformation matrix (CTM)
      */
     public static CTM getCTMandRelDims(int absRefOrient,
-                                       int writingMode,
+                                       WritingMode writingMode,
                                        Rectangle2D absVPrect,
                                        FODimension reldims) {
         int width, height;
@@ -330,12 +332,18 @@
          * can set ipd and bpd appropriately based on the writing mode.
          */
 
-        if (writingMode == Constants.EN_LR_TB || writingMode == Constants.EN_RL_TB) {
+        switch ( writingMode.getEnumValue() ) {
+        default:
+        case Constants.EN_LR_TB:
+        case Constants.EN_RL_TB:
             reldims.ipd = width;
             reldims.bpd = height;
-        } else {
+            break;
+        case Constants.EN_TB_LR:
+        case Constants.EN_TB_RL:
             reldims.ipd = height;
             reldims.bpd = width;
+            break;
         }
         // Set a rectangle to be the writing-mode relative version???
         // Now transform for writing mode
Index: src/java/org/apache/fop/area/RegionViewport.java
===================================================================
--- src/java/org/apache/fop/area/RegionViewport.java	(revision 981406)
+++ src/java/org/apache/fop/area/RegionViewport.java	(working copy)
@@ -93,7 +93,7 @@
         out.writeFloat((float) viewArea.getWidth());
         out.writeFloat((float) viewArea.getHeight());
         out.writeBoolean(clip);
-        out.writeObject(props);
+        out.writeObject(traits);
         out.writeObject(regionReference);
     }
 
@@ -102,7 +102,7 @@
         viewArea = new Rectangle2D.Float(in.readFloat(), in.readFloat(),
                                          in.readFloat(), in.readFloat());
         clip = in.readBoolean();
-        props = (HashMap)in.readObject();
+        traits = (HashMap)in.readObject();
         setRegionReference((RegionReference) in.readObject());
     }
 
@@ -115,8 +115,8 @@
     public Object clone() {
         RegionViewport rv = new RegionViewport((Rectangle2D)viewArea.clone());
         rv.regionReference = (RegionReference)regionReference.clone();
-        if (props != null) {
-            rv.props = new HashMap(props);
+        if (traits != null) {
+            rv.traits = new HashMap(traits);
         }
         if (foreignAttributes != null) {
             rv.foreignAttributes = new HashMap(foreignAttributes);
Index: src/java/org/apache/fop/area/Block.java
===================================================================
--- src/java/org/apache/fop/area/Block.java	(revision 981406)
+++ src/java/org/apache/fop/area/Block.java	(working copy)
@@ -55,7 +55,6 @@
      */
     public static final int FIXED = 3;
 
-    private int stacking = TB;
     private int positioning = STACK;
 
     protected transient boolean allowBPDUpdate = true;
Index: src/java/org/apache/fop/util/BidiConstants.java
===================================================================
--- src/java/org/apache/fop/util/BidiConstants.java	(revision 0)
+++ src/java/org/apache/fop/util/BidiConstants.java	(revision 0)
@@ -0,0 +1,84 @@
+/*
+ * 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.util;
+
+
+/**
+ * Constants used for bidirectional processing.
+ * @author Glenn Adams
+ */
+public interface BidiConstants {
+
+    // bidi character class
+
+    // strong category
+    /** left-to-right class */
+    int L           = 1;
+    /** left-to-right embedding class */
+    int LRE         = 2;
+    /** left-to-right override class */
+    int LRO         = 3;
+    /** right-to-left  class */
+    int R           = 4;
+    /** right-to-left arabic class */
+    int AL          = 5;
+    /** right-to-left embedding class */
+    int RLE         = 6;
+    /** right-to-left override class */
+    int RLO         = 7;
+
+    // weak category
+    /** pop directional formatting class */
+    int PDF         = 8;
+    /** european number class */
+    int EN          = 9;
+    /** european number separator class */
+    int ES          = 10;
+    /** european number terminator class */
+    int ET          = 11;
+    /** arabic number class */
+    int AN          = 12;
+    /** common number separator class */
+    int CS          = 13;
+    /** non-spacing mark class */
+    int NSM         = 14;
+    /** boundary neutral class */
+    int BN          = 15;
+
+    // neutral category
+    /** paragraph separator class */
+    int B           = 16;
+    /** segment separator class */
+    int S           = 17;
+    /** whitespace class */
+    int WS          = 18;
+    /** other neutrals class */
+    int ON          = 19;
+
+    // implementation specific categories
+    /** placeholder for low surrogate */
+    int SURROGATE   = 20;
+
+    // other constants
+    /** maximum bidirectional levels */
+    int MAX_LEVELS  = 61;
+    /** override flag */
+    int OVERRIDE    = 128;
+}
Index: src/java/org/apache/fop/util/CharUtilities.java
===================================================================
--- src/java/org/apache/fop/util/CharUtilities.java	(revision 981406)
+++ src/java/org/apache/fop/util/CharUtilities.java	(working copy)
@@ -19,6 +19,17 @@
 
 package org.apache.fop.util;
 
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+// CSOFF: AvoidNestedBlocksCheck
+// CSOFF: InnerAssignmentCheck
+// CSOFF: SimplifyBooleanReturnCheck
+
 /**
  * This class provides utilities to distinguish various kinds of Unicode
  * whitespace and to get character widths in a given FontState.
@@ -74,6 +85,20 @@
     public static final char WORD_JOINER = '\u2060';
     /** zero-width joiner */
     public static final char ZERO_WIDTH_JOINER = '\u200D';
+    /** left-to-right mark */
+    public static final char LRM = '\u200E';
+    /** right-to-left mark */
+    public static final char RLM = '\u202F';
+    /** left-to-right embedding */
+    public static final char LRE = '\u202A';
+    /** right-to-left embedding */
+    public static final char RLE = '\u202B';
+    /** pop directional formatting */
+    public static final char PDF = '\u202C';
+    /** left-to-right override */
+    public static final char LRO = '\u202D';
+    /** right-to-left override */
+    public static final char RLO = '\u202E';
     /** zero-width no-break space (= byte order mark) */
     public static final char ZERO_WIDTH_NOBREAK_SPACE = '\uFEFF';
     /** soft hyphen */
@@ -103,7 +128,7 @@
      * @param c character to inspect
      * @return the determined character class
      */
-    public static int classOf(char c) {
+    public static int classOf ( int c ) {
         switch (c) {
             case CODE_EOT:
                 return EOT;
@@ -126,7 +151,7 @@
      * @param c character to inspect
      * @return True if the character is a normal space
      */
-    public static boolean isBreakableSpace(char c) {
+    public static boolean isBreakableSpace ( int c ) {
         return (c == SPACE || isFixedWidthSpace(c));
     }
 
@@ -135,7 +160,7 @@
      * @param c the character to check
      * @return true if the character is a zero-width space
      */
-    public static boolean isZeroWidthSpace(char c) {
+    public static boolean isZeroWidthSpace ( int c ) {
         return c == ZERO_WIDTH_SPACE           // 200Bh
             || c == WORD_JOINER                // 2060h
             || c == ZERO_WIDTH_NOBREAK_SPACE;  // FEFFh (also used as BOM)
@@ -146,7 +171,7 @@
      * @param c the character to check
      * @return true if the character has a fixed-width
      */
-    public static boolean isFixedWidthSpace(char c) {
+    public static boolean isFixedWidthSpace ( int c ) {
         return (c >= '\u2000' && c <= '\u200B')
                 || c == '\u3000';
 //      c == '\u2000'                   // en quad
@@ -170,7 +195,7 @@
      * @param c character to check
      * @return True if the character is a nbsp
      */
-    public static boolean isNonBreakableSpace(char c) {
+    public static boolean isNonBreakableSpace ( int c ) {
         return
             (c == NBSPACE       // no-break space
             || c == '\u202F'    // narrow no-break space
@@ -185,7 +210,7 @@
      * @param c character to check
      * @return True if the character is adjustable
      */
-    public static boolean isAdjustableSpace(char c) {
+    public static boolean isAdjustableSpace ( int c ) {
         //TODO: are there other kinds of adjustable spaces?
         return
             (c == '\u0020'    // normal space
@@ -197,19 +222,19 @@
      * @param c character to check
      * @return True if the character represents any kind of space
      */
-    public static boolean isAnySpace(char c) {
+    public static boolean isAnySpace ( int c ) {
         return (isBreakableSpace(c) || isNonBreakableSpace(c));
     }
 
     /**
      * Indicates whether a character is classified as "Alphabetic" by the Unicode standard.
-     * @param ch the character
+     * @param c the character
      * @return true if the character is "Alphabetic"
      */
-    public static boolean isAlphabetic(char ch) {
+    public static boolean isAlphabetic ( int c ) {
         //http://www.unicode.org/Public/UNIDATA/UCD.html#Alphabetic
         //Generated from: Other_Alphabetic + Lu + Ll + Lt + Lm + Lo + Nl
-        int generalCategory = Character.getType(ch);
+        int generalCategory = Character.getType((char)c);
         switch (generalCategory) {
             case Character.UPPERCASE_LETTER: //Lu
             case Character.LOWERCASE_LETTER: //Ll
@@ -227,15 +252,1362 @@
 
     /**
      * Indicates whether the given character is an explicit break-character
-     * @param ch    the character to check
+     * @param c    the character to check
      * @return  true if the character represents an explicit break
      */
-    public static boolean isExplicitBreak(char ch) {
-        return (ch == LINEFEED_CHAR
-            || ch == CARRIAGE_RETURN
-            || ch == NEXT_LINE
-            || ch == LINE_SEPARATOR
-            || ch == PARAGRAPH_SEPARATOR);
+    public static boolean isExplicitBreak ( int c ) {
+        return (c == LINEFEED_CHAR
+            || c == CARRIAGE_RETURN
+            || c == NEXT_LINE
+            || c == LINE_SEPARATOR
+            || c == PARAGRAPH_SEPARATOR);
     }
+
+
+    /** 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'
+    /** devanagari script constant */
+    public static final int SCRIPT_DEVANAGARI                           = 315;  // 'deva'
+    /** gujarati script constant */
+    public static final int SCRIPT_GUJARATI                             = 320;  // 'gujr'
+    /** bengali script constant */
+    public static final int SCRIPT_BENGALI                              = 326;  // 'beng'
+    /** oriya script constant */
+    public static final int SCRIPT_ORIYA                                = 327;  // 'orya'
+    /** tibetan script constant */
+    public static final int SCRIPT_TIBETAN                              = 330;  // 'tibt'
+    /** telugu script constant */
+    public static final int SCRIPT_TELUGU                               = 340;  // 'telu'
+    /** tamil script constant */
+    public static final int SCRIPT_TAMIL                                = 346;  // 'taml'
+    /** malayalam script constant */
+    public static final int SCRIPT_MALAYALAM                            = 347;  // 'mlym'
+    /** 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'
+
+    /**
+     * 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 ) {
+        return false; // [TBD] - implement me
+    }
+
+    /**
+     * 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 ) {
+        return false; // [TBD] - implement me
+    }
+
+    /**
+     * 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 ) {
+        return false; // [TBD] - implement me
+    }
+
+    /**
+     * 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 ) {
+        return false; // [TBD] - implement me
+    }
+
+    /**
+     * 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 ) {
+        return false; // [TBD] - implement me
+    }
+
+    /**
+     * 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 ) {
+        return false; // [TBD] - implement me
+    }
+
+    /**
+     * 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 ) {
+        return false; // [TBD] - implement me
+    }
+
+    /**
+     * 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 ) {
+        return false; // [TBD] - implement me
+    }
+
+    /**
+     * 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 ) {
+        return false; // [TBD] - implement me
+    }
+
+    /**
+     * 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 ) {
+        return false; // [TBD] - implement me
+    }
+
+    /**
+     * 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 ) {
+        return false; // [TBD] - implement me
+    }
+
+    /**
+     * 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 ) {
+        return false; // [TBD] - implement me
+    }
+
+    /**
+     * 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 ) {
+        return false; // [TBD] - implement me
+    }
+
+    /**
+     * 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 ) {
+        return false; // [TBD] - implement me
+    }
+
+    /**
+     * 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 ) {
+        return false; // [TBD] - implement me
+    }
+
+    /**
+     * 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 ) {
+        return false; // [TBD] - implement me
+    }
+
+    /**
+     * 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 ) {
+        return false; // [TBD] - implement me
+    }
+
+    /**
+     * 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 ) {
+        return false; // [TBD] - implement me
+    }
+
+    /**
+     * 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 ) {
+        return false; // [TBD] - implement me
+    }
+
+    /**
+     * 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 ) {
+        return false; // [TBD] - implement me
+    }
+
+    /**
+     * 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 ( 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 SCRIPT_GURMUKHI;
+        } else if ( isDevanagari ( c ) ) {
+            return SCRIPT_DEVANAGARI;
+        } else if ( isGujarati ( c ) ) {
+            return SCRIPT_GUJARATI;
+        } else if ( isBengali ( c ) ) {
+            return SCRIPT_BENGALI;
+        } else if ( isOriya ( c ) ) {
+            return SCRIPT_ORIYA;
+        } else if ( isTibetan ( c ) ) {
+            return SCRIPT_TIBETAN;
+        } else if ( isTelugu ( c ) ) {
+            return SCRIPT_TELUGU;
+        } else if ( isTamil ( c ) ) {
+            return SCRIPT_TAMIL;
+        } else if ( isMalayalam ( c ) ) {
+            return 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  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 the ISO script tag associated with an internal
+     * script code.
+     * @param code the script code
+     * @return an ISO script tag
+     */
+    public static String scriptTagFromCode ( int code ) {
+        String tag;
+        if ( scriptTagsMap == null ) {
+            scriptTagsMap = makeScriptTagsMap();
+        }
+        if ( ( tag = (String) scriptTagsMap.get ( Integer.valueOf ( code ) ) ) == null ) {
+            tag = scriptTagFromCode ( SCRIPT_UNDETERMINED );
+        }
+        return tag;
+    }
+
+    /**
+     * Convert a single unicode scalar value to an XML numeric character
+     * reference. If in the BMP, four digits are used, otherwise 6 digits are used.
+     * @param c a unicode scalar value
+     * @return a string representing a numeric character reference
+     */
+    public static String charToNCRef ( int c ) {
+        StringBuffer sb = new StringBuffer();
+        for ( int i = 0, nDigits = ( c > 0xFFFF ) ? 6 : 4; i < nDigits; i++, c >>= 4 ) {
+            int d = c & 0xF;
+            char hd;
+            if ( d < 10 ) {
+                hd = (char) ( (int) '0' + d );
+            } else {
+                hd = (char) ( (int) 'A' + ( d - 10 ) );
+            }
+            sb.append ( hd );
+        }
+        return "&#x" + sb.reverse() + ";";
+    }
+
+    /**
+     * Convert a string to a sequence of ASCII or XML numeric character references.
+     * @param s a java string (encoded in UTF-16)
+     * @return a string representing a sequence of numeric character reference or
+     * ASCII characters
+     */
+    public static String toNCRefs ( String s ) {
+        StringBuffer sb = new StringBuffer();
+        if ( s != null ) {
+            for ( int i = 0; i < s.length(); i++ ) {
+                char c = s.charAt(i);
+                if ( ( c >= 32 ) && ( c < 127 ) ) {
+                    sb.append ( c );
+                } else {
+                    sb.append ( charToNCRef ( c ) );
+                }
+            }
+        }
+        return sb.toString();
+    }
+
+    private static Map scriptTagsMap = null;
+
+    private static void putScriptTag ( Map m, int code, String tag ) {
+        m.put ( Integer.valueOf ( code ), tag );
+    }
+
+    private static Map makeScriptTagsMap() {
+        HashMap m = new HashMap();
+        putScriptTag ( m, SCRIPT_HEBREW, "hebr" );
+        putScriptTag ( m, SCRIPT_MONGOLIAN, "mong" );
+        putScriptTag ( m, SCRIPT_ARABIC, "arab" );
+        putScriptTag ( m, SCRIPT_GREEK, "grek" );
+        putScriptTag ( m, SCRIPT_LATIN, "latn" );
+        putScriptTag ( m, SCRIPT_CYRILLIC, "cyrl" );
+        putScriptTag ( m, SCRIPT_GEORGIAN, "geor" );
+        putScriptTag ( m, SCRIPT_BOPOMOFO, "bopo" );
+        putScriptTag ( m, SCRIPT_HANGUL, "hang" );
+        putScriptTag ( m, SCRIPT_GURMUKHI, "guru" );
+        putScriptTag ( m, SCRIPT_DEVANAGARI, "deva" );
+        putScriptTag ( m, SCRIPT_GUJARATI, "gujr" );
+        putScriptTag ( m, SCRIPT_BENGALI, "beng" );
+        putScriptTag ( m, SCRIPT_ORIYA, "orya" );
+        putScriptTag ( m, SCRIPT_TIBETAN, "tibt" );
+        putScriptTag ( m, SCRIPT_TELUGU, "telu" );
+        putScriptTag ( m, SCRIPT_TAMIL, "taml" );
+        putScriptTag ( m, SCRIPT_MALAYALAM, "mlym" );
+        putScriptTag ( m, SCRIPT_SINHALESE, "sinh" );
+        putScriptTag ( m, SCRIPT_BURMESE, "mymr" );
+        putScriptTag ( m, SCRIPT_THAI, "thai" );
+        putScriptTag ( m, SCRIPT_KHMER, "khmr" );
+        putScriptTag ( m, SCRIPT_LAO, "laoo" );
+        putScriptTag ( m, SCRIPT_HIRAGANA, "hira" );
+        putScriptTag ( m, SCRIPT_ETHIOPIC, "ethi" );
+        putScriptTag ( m, SCRIPT_HAN, "hani" );
+        putScriptTag ( m, SCRIPT_KATAKANA, "kana" );
+        putScriptTag ( m, SCRIPT_MATH, "zmth" );
+        putScriptTag ( m, SCRIPT_SYMBOL, "zsym" );
+        putScriptTag ( m, SCRIPT_UNDETERMINED, "zyyy" );
+        putScriptTag ( m, SCRIPT_UNCODED, "zzzz" );
+        return m;
+    }
+
+    /**
+     * Mirror characters that are designated as having the bidi mirrorred property.
+     * @param s a string whose characters are to be mirrored
+     * @return the resulting string
+     */
+    public static String mirror ( String s ) {
+        StringBuffer sb = new StringBuffer ( s );
+        for ( int i = 0, n = sb.length(); i < n; i++ ) {
+            sb.setCharAt ( i, (char) mirror ( sb.charAt ( i ) ) );
+        }
+        return sb.toString();
+    }
+
+    private static int[] mirroredCharacters = {
+        0x0028,
+        0x0029,
+        0x003C,
+        0x003E,
+        0x005B,
+        0x005D,
+        0x007B,
+        0x007D,
+        0x00AB,
+        0x00BB,
+        0x0F3A,
+        0x0F3B,
+        0x0F3C,
+        0x0F3D,
+        0x169B,
+        0x169C,
+        0x2039,
+        0x203A,
+        0x2045,
+        0x2046,
+        0x207D,
+        0x207E,
+        0x208D,
+        0x208E,
+        0x2208,
+        0x2209,
+        0x220A,
+        0x220B,
+        0x220C,
+        0x220D,
+        0x2215,
+        0x223C,
+        0x223D,
+        0x2243,
+        0x2252,
+        0x2253,
+        0x2254,
+        0x2255,
+        0x2264,
+        0x2265,
+        0x2266,
+        0x2267,
+        0x2268,
+        0x2269,
+        0x226A,
+        0x226B,
+        0x226E,
+        0x226F,
+        0x2270,
+        0x2271,
+        0x2272,
+        0x2273,
+        0x2274,
+        0x2275,
+        0x2276,
+        0x2277,
+        0x2278,
+        0x2279,
+        0x227A,
+        0x227B,
+        0x227C,
+        0x227D,
+        0x227E,
+        0x227F,
+        0x2280,
+        0x2281,
+        0x2282,
+        0x2283,
+        0x2284,
+        0x2285,
+        0x2286,
+        0x2287,
+        0x2288,
+        0x2289,
+        0x228A,
+        0x228B,
+        0x228F,
+        0x2290,
+        0x2291,
+        0x2292,
+        0x2298,
+        0x22A2,
+        0x22A3,
+        0x22A6,
+        0x22A8,
+        0x22A9,
+        0x22AB,
+        0x22B0,
+        0x22B1,
+        0x22B2,
+        0x22B3,
+        0x22B4,
+        0x22B5,
+        0x22B6,
+        0x22B7,
+        0x22C9,
+        0x22CA,
+        0x22CB,
+        0x22CC,
+        0x22CD,
+        0x22D0,
+        0x22D1,
+        0x22D6,
+        0x22D7,
+        0x22D8,
+        0x22D9,
+        0x22DA,
+        0x22DB,
+        0x22DC,
+        0x22DD,
+        0x22DE,
+        0x22DF,
+        0x22E0,
+        0x22E1,
+        0x22E2,
+        0x22E3,
+        0x22E4,
+        0x22E5,
+        0x22E6,
+        0x22E7,
+        0x22E8,
+        0x22E9,
+        0x22EA,
+        0x22EB,
+        0x22EC,
+        0x22ED,
+        0x22F0,
+        0x22F1,
+        0x22F2,
+        0x22F3,
+        0x22F4,
+        0x22F6,
+        0x22F7,
+        0x22FA,
+        0x22FB,
+        0x22FC,
+        0x22FD,
+        0x22FE,
+        0x2308,
+        0x2309,
+        0x230A,
+        0x230B,
+        0x2329,
+        0x232A,
+        0x2768,
+        0x2769,
+        0x276A,
+        0x276B,
+        0x276C,
+        0x276D,
+        0x276E,
+        0x276F,
+        0x2770,
+        0x2771,
+        0x2772,
+        0x2773,
+        0x2774,
+        0x2775,
+        0x27C3,
+        0x27C4,
+        0x27C5,
+        0x27C6,
+        0x27C8,
+        0x27C9,
+        0x27D5,
+        0x27D6,
+        0x27DD,
+        0x27DE,
+        0x27E2,
+        0x27E3,
+        0x27E4,
+        0x27E5,
+        0x27E6,
+        0x27E7,
+        0x27E8,
+        0x27E9,
+        0x27EA,
+        0x27EB,
+        0x27EC,
+        0x27ED,
+        0x27EE,
+        0x27EF,
+        0x2983,
+        0x2984,
+        0x2985,
+        0x2986,
+        0x2987,
+        0x2988,
+        0x2989,
+        0x298A,
+        0x298B,
+        0x298C,
+        0x298D,
+        0x298E,
+        0x298F,
+        0x2990,
+        0x2991,
+        0x2992,
+        0x2993,
+        0x2994,
+        0x2995,
+        0x2996,
+        0x2997,
+        0x2998,
+        0x29B8,
+        0x29C0,
+        0x29C1,
+        0x29C4,
+        0x29C5,
+        0x29CF,
+        0x29D0,
+        0x29D1,
+        0x29D2,
+        0x29D4,
+        0x29D5,
+        0x29D8,
+        0x29D9,
+        0x29DA,
+        0x29DB,
+        0x29F5,
+        0x29F8,
+        0x29F9,
+        0x29FC,
+        0x29FD,
+        0x2A2B,
+        0x2A2C,
+        0x2A2D,
+        0x2A2E,
+        0x2A34,
+        0x2A35,
+        0x2A3C,
+        0x2A3D,
+        0x2A64,
+        0x2A65,
+        0x2A79,
+        0x2A7A,
+        0x2A7D,
+        0x2A7E,
+        0x2A7F,
+        0x2A80,
+        0x2A81,
+        0x2A82,
+        0x2A83,
+        0x2A84,
+        0x2A8B,
+        0x2A8C,
+        0x2A91,
+        0x2A92,
+        0x2A93,
+        0x2A94,
+        0x2A95,
+        0x2A96,
+        0x2A97,
+        0x2A98,
+        0x2A99,
+        0x2A9A,
+        0x2A9B,
+        0x2A9C,
+        0x2AA1,
+        0x2AA2,
+        0x2AA6,
+        0x2AA7,
+        0x2AA8,
+        0x2AA9,
+        0x2AAA,
+        0x2AAB,
+        0x2AAC,
+        0x2AAD,
+        0x2AAF,
+        0x2AB0,
+        0x2AB3,
+        0x2AB4,
+        0x2AC3,
+        0x2AC4,
+        0x2AC5,
+        0x2AC6,
+        0x2ACD,
+        0x2ACE,
+        0x2ACF,
+        0x2AD0,
+        0x2AD1,
+        0x2AD2,
+        0x2AD3,
+        0x2AD4,
+        0x2AD5,
+        0x2AD6,
+        0x2ADE,
+        0x2AE3,
+        0x2E02,
+        0x2E03,
+        0x2E04,
+        0x2E05,
+        0x2E09,
+        0x2E0A,
+        0x2E0C,
+        0x2E0D,
+        0x2E1C,
+        0x2E1D,
+        0x2E20,
+        0x2E21,
+        0x2E22,
+        0x2E23,
+        0x2E24,
+        0x2E25,
+        0x2E26,
+        0x300E,
+        0x300F,
+        0x3010,
+        0x3011,
+        0x3014,
+        0x3015,
+        0x3016,
+        0x3017,
+        0x3018,
+        0x3019,
+        0x301A,
+        0x301B,
+        0xFE59,
+        0xFE5A,
+        0xFF3B,
+        0xFF3D,
+        0xFF5B,
+        0xFF5D,
+        0xFF5F,
+        0xFF60,
+        0xFF62,
+        0xFF63
+    };
+
+    private static int[] mirroredCharactersMapping = {
+        0x0029,
+        0x0028,
+        0x003E,
+        0x003C,
+        0x005D,
+        0x005B,
+        0x007D,
+        0x007B,
+        0x00BB,
+        0x00AB,
+        0x0F3B,
+        0x0F3A,
+        0x0F3D,
+        0x0F3C,
+        0x169C,
+        0x169B,
+        0x203A,
+        0x2039,
+        0x2046,
+        0x2045,
+        0x207E,
+        0x207D,
+        0x208E,
+        0x208D,
+        0x220B,
+        0x220C,
+        0x220D,
+        0x2208,
+        0x2209,
+        0x220A,
+        0x29F5,
+        0x223D,
+        0x223C,
+        0x22CD,
+        0x2253,
+        0x2252,
+        0x2255,
+        0x2254,
+        0x2265,
+        0x2264,
+        0x2267,
+        0x2266,
+        0x2269,
+        0x2268,
+        0x226B,
+        0x226A,
+        0x226F,
+        0x226E,
+        0x2271,
+        0x2270,
+        0x2273,
+        0x2272,
+        0x2275,
+        0x2274,
+        0x2277,
+        0x2276,
+        0x2279,
+        0x2278,
+        0x227B,
+        0x227A,
+        0x227D,
+        0x227C,
+        0x227F,
+        0x227E,
+        0x2281,
+        0x2280,
+        0x2283,
+        0x2282,
+        0x2285,
+        0x2284,
+        0x2287,
+        0x2286,
+        0x2289,
+        0x2288,
+        0x228B,
+        0x228A,
+        0x2290,
+        0x228F,
+        0x2292,
+        0x2291,
+        0x29B8,
+        0x22A3,
+        0x22A2,
+        0x2ADE,
+        0x2AE4,
+        0x2AE3,
+        0x2AE5,
+        0x22B1,
+        0x22B0,
+        0x22B3,
+        0x22B2,
+        0x22B5,
+        0x22B4,
+        0x22B7,
+        0x22B6,
+        0x22CA,
+        0x22C9,
+        0x22CC,
+        0x22CB,
+        0x2243,
+        0x22D1,
+        0x22D0,
+        0x22D7,
+        0x22D6,
+        0x22D9,
+        0x22D8,
+        0x22DB,
+        0x22DA,
+        0x22DD,
+        0x22DC,
+        0x22DF,
+        0x22DE,
+        0x22E1,
+        0x22E0,
+        0x22E3,
+        0x22E2,
+        0x22E5,
+        0x22E4,
+        0x22E7,
+        0x22E6,
+        0x22E9,
+        0x22E8,
+        0x22EB,
+        0x22EA,
+        0x22ED,
+        0x22EC,
+        0x22F1,
+        0x22F0,
+        0x22FA,
+        0x22FB,
+        0x22FC,
+        0x22FD,
+        0x22FE,
+        0x22F2,
+        0x22F3,
+        0x22F4,
+        0x22F6,
+        0x22F7,
+        0x2309,
+        0x2308,
+        0x230B,
+        0x230A,
+        0x232A,
+        0x2329,
+        0x2769,
+        0x2768,
+        0x276B,
+        0x276A,
+        0x276D,
+        0x276C,
+        0x276F,
+        0x276E,
+        0x2771,
+        0x2770,
+        0x2773,
+        0x2772,
+        0x2775,
+        0x2774,
+        0x27C4,
+        0x27C3,
+        0x27C6,
+        0x27C5,
+        0x27C9,
+        0x27C8,
+        0x27D6,
+        0x27D5,
+        0x27DE,
+        0x27DD,
+        0x27E3,
+        0x27E2,
+        0x27E5,
+        0x27E4,
+        0x27E7,
+        0x27E6,
+        0x27E9,
+        0x27E8,
+        0x27EB,
+        0x27EA,
+        0x27ED,
+        0x27EC,
+        0x27EF,
+        0x27EE,
+        0x2984,
+        0x2983,
+        0x2986,
+        0x2985,
+        0x2988,
+        0x2987,
+        0x298A,
+        0x2989,
+        0x298C,
+        0x298B,
+        0x2990,
+        0x298F,
+        0x298E,
+        0x298D,
+        0x2992,
+        0x2991,
+        0x2994,
+        0x2993,
+        0x2996,
+        0x2995,
+        0x2998,
+        0x2997,
+        0x2298,
+        0x29C1,
+        0x29C0,
+        0x29C5,
+        0x29C4,
+        0x29D0,
+        0x29CF,
+        0x29D2,
+        0x29D1,
+        0x29D5,
+        0x29D4,
+        0x29D9,
+        0x29D8,
+        0x29DB,
+        0x29DA,
+        0x2215,
+        0x29F9,
+        0x29F8,
+        0x29FD,
+        0x29FC,
+        0x2A2C,
+        0x2A2B,
+        0x2A2E,
+        0x2A2D,
+        0x2A35,
+        0x2A34,
+        0x2A3D,
+        0x2A3C,
+        0x2A65,
+        0x2A64,
+        0x2A7A,
+        0x2A79,
+        0x2A7E,
+        0x2A7D,
+        0x2A80,
+        0x2A7F,
+        0x2A82,
+        0x2A81,
+        0x2A84,
+        0x2A83,
+        0x2A8C,
+        0x2A8B,
+        0x2A92,
+        0x2A91,
+        0x2A94,
+        0x2A93,
+        0x2A96,
+        0x2A95,
+        0x2A98,
+        0x2A97,
+        0x2A9A,
+        0x2A99,
+        0x2A9C,
+        0x2A9B,
+        0x2AA2,
+        0x2AA1,
+        0x2AA7,
+        0x2AA6,
+        0x2AA9,
+        0x2AA8,
+        0x2AAB,
+        0x2AAA,
+        0x2AAD,
+        0x2AAC,
+        0x2AB0,
+        0x2AAF,
+        0x2AB4,
+        0x2AB3,
+        0x2AC4,
+        0x2AC3,
+        0x2AC6,
+        0x2AC5,
+        0x2ACE,
+        0x2ACD,
+        0x2AD0,
+        0x2ACF,
+        0x2AD2,
+        0x2AD1,
+        0x2AD4,
+        0x2AD3,
+        0x2AD6,
+        0x2AD5,
+        0x22A6,
+        0x22A9,
+        0x2E03,
+        0x2E02,
+        0x2E05,
+        0x2E04,
+        0x2E0A,
+        0x2E09,
+        0x2E0D,
+        0x2E0C,
+        0x2E1D,
+        0x2E1C,
+        0x2E21,
+        0x2E20,
+        0x2E23,
+        0x2E22,
+        0x2E25,
+        0x2E24,
+        0x2E27,
+        0x300F,
+        0x300E,
+        0x3011,
+        0x3010,
+        0x3015,
+        0x3014,
+        0x3017,
+        0x3016,
+        0x3019,
+        0x3018,
+        0x301B,
+        0x301A,
+        0xFE5A,
+        0xFE59,
+        0xFF3D,
+        0xFF3B,
+        0xFF5D,
+        0xFF5B,
+        0xFF60,
+        0xFF5F,
+        0xFF63,
+        0xFF62
+    };
+
+    private static int mirror ( int c ) {
+        int i = Arrays.binarySearch ( mirroredCharacters, c );
+        if ( i < 0 ) {
+            return c;
+        } else {
+            return mirroredCharactersMapping [ i ];
+        }
+    }
+
 }
-
Index: src/codegen/unicode/java/org/apache/fop/text/bidi/GenerateBidiClassUtils.java
===================================================================
--- src/codegen/unicode/java/org/apache/fop/text/bidi/GenerateBidiClassUtils.java	(revision 0)
+++ src/codegen/unicode/java/org/apache/fop/text/bidi/GenerateBidiClassUtils.java	(revision 0)
@@ -0,0 +1,548 @@
+/*
+ * 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.text.bidi;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import org.apache.fop.util.BidiConstants;
+import org.apache.fop.util.License;
+
+/**
+ * <p>Utility for generating a Java class representing bidirectional
+ * class properties from the Unicode property files.</p>
+ *
+ * <p>This code is derived in part from GenerateLineBreakUtils.java.</p>
+ *
+ * @author Glenn Adams
+ */
+public class GenerateBidiClassUtils {
+
+    private static byte bcL1[] = new byte[256];	// ascii and basic latin blocks ( 0x0000 - 0x00FF )
+    private static byte bcR1[] = new byte[368];	// hebrew and arabic blocks	( 0x0590 - 0x06FF )
+    private static int  bcS1[];			// interval start indices
+    private static int  bcE1[];		        // interval end indices
+    private static byte bcC1[];			// interval bid classes
+
+    /**
+     * Generate a class managing bidi class properties for Unicode characters.
+     *
+     * @param bidiFileName name (as URL) of file containing bidi type data
+     * @param outFileName name of the output file
+     * @throws Exception
+     */
+    private static void convertBidiClassProperties(String bidiFileName, String outFileName) throws Exception {
+
+        readBidiClassProperties(bidiFileName);
+
+        // generate class
+        PrintWriter out = new PrintWriter(new FileWriter(outFileName));
+        License.writeJavaLicenseId(out);
+        out.println();
+        out.println("package org.apache.fop.text.bidi;");
+        out.println();
+        out.println("import java.util.Arrays;");
+        out.println("import org.apache.fop.util.BidiConstants;");
+        out.println();
+        out.println("// CSOFF: WhitespaceAfterCheck");
+        out.println("// CSOFF: LineLengthCheck");
+        out.println();
+        out.println("/*");
+        out.println(" * !!! THIS IS A GENERATED FILE !!!");
+        out.println(" * If updates to the source are needed, then:");
+        out.println(" * - apply the necessary modifications to");
+        out.println(" *   'src/codegen/unicode/java/org/apache/fop/text/bidi/GenerateBidiClassUtils.java'");
+        out.println(" * - run 'ant codegen-unicode', which will generate a new BidiClassUtils.java");
+        out.println(" *   in 'src/java/org/apache/fop/text/bidi'");
+        out.println(" * - commit BOTH changed files");
+        out.println(" */");
+        out.println();
+        out.println("/** Bidirectional class utilities. */");
+        out.println("public final class BidiClassUtils {");
+        out.println();
+        out.println("private BidiClassUtils() {");
+        out.println("}");
+        out.println();
+	dumpData(out);
+        out.println ("/**");
+        out.println (" * Lookup bidi class for character expressed as unicode scalar value.");
+        out.println (" * @param ch a unicode scalar value");
+        out.println (" * @return bidi class");
+        out.println (" */");
+	out.println("public static int getBidiClass ( int ch ) {");
+	out.println("  if ( ch <= 0x00FF ) {");
+	out.println("    return bcL1 [ ch - 0x0000 ];");
+	out.println("  } else if ( ( ch >= 0x0590 ) && ( ch <= 0x06FF ) ) {");
+	out.println("    return bcR1 [ ch - 0x0590 ];");
+	out.println("  } else {");
+	out.println("    return getBidiClass ( ch, bcS1, bcE1, bcC1 );");
+	out.println("  }");
+	out.println("}");
+        out.println();
+	out.println("private static int getBidiClass ( int ch, int[] sa, int[] ea, byte[] ca ) {");
+	out.println("  int k = Arrays.binarySearch ( sa, ch );");
+	out.println("  if ( k >= 0 ) {");
+	out.println("    return ca [ k ];");
+	out.println("  } else {");
+	out.println("    k = - ( k + 1 );");
+	out.println("    if ( k == 0 ) {");
+	out.println("      return BidiConstants.L;");
+	out.println("    } else if ( ch <= ea [ k - 1 ] ) {");
+	out.println("      return ca [ k - 1 ];");
+	out.println("    } else {");
+	out.println("      return BidiConstants.L;");
+	out.println("    }");
+	out.println("  }");
+	out.println("}");
+        out.println();
+        out.println("}");
+        out.flush();
+        out.close();
+    }
+
+    /**
+     * Read bidi class property data.
+     *
+     * @param bidiFileName name (as URL) of bidi type data
+     */
+    private static void readBidiClassProperties(String bidiFileName) throws Exception {
+        // read property names
+        BufferedReader b = new BufferedReader(new InputStreamReader(new URL(bidiFileName).openStream()));
+        String line;
+        int lineNumber = 0;
+	TreeSet intervals = new TreeSet();
+	while ( ( line = b.readLine() ) != null ) {
+	    lineNumber++;
+	    if ( line.startsWith("#") )
+		continue;
+	    else if ( line.length() == 0 )
+		continue;
+	    else {
+		if ( line.indexOf ( "#" ) != -1 ) {
+		    line = ( line.split ( "#" ) ) [ 0 ];
+		}
+		String[] fa = line.split ( ";" );
+		if ( fa.length == 2 ) {
+		    int[] interval = parseInterval ( fa[0].trim() );
+		    byte bidiClass = (byte) parseBidiClass ( fa[1].trim() );
+		    if ( interval[1] == interval[0] ) {	// singleton
+			int c = interval[0];
+			if ( c <= 0x00FF ) {
+			    if ( bcL1 [ c - 0x0000 ] == 0 ) {
+				bcL1 [ c - 0x0000 ] = bidiClass;
+			    } else {
+				throw new Exception ( "duplicate singleton entry: " + c );
+			    }
+			} else if ( ( c >= 0x0590 ) && ( c <= 0x06FF ) ) {
+			    if ( bcR1 [ c - 0x0590 ] == 0 ) {
+				bcR1 [ c - 0x0590 ] = bidiClass;
+			    } else {
+				throw new Exception ( "duplicate singleton entry: " + c );
+			    }
+			} else {
+			    addInterval ( intervals, c, c, bidiClass );
+			}
+		    } else {				// non-singleton
+			int s = interval[0];
+			int e = interval[1];		// inclusive
+			if ( s <= 0x00FF ) {
+			    for ( int i = s; i <= e; i++ ) {
+				if ( i <= 0x00FF ) {
+				    if ( bcL1 [ i - 0x0000 ] == 0 ) {
+					bcL1 [ i - 0x0000 ] = bidiClass;
+				    } else {
+					throw new Exception ( "duplicate singleton entry: " + i );
+				    }
+				} else {
+				    addInterval ( intervals, i, e, bidiClass );
+				    break;
+				}
+			    }
+			} else if ( ( s >= 0x0590 ) && ( s <= 0x06FF ) ) {
+			    for ( int i = s; i <= e; i++ ) {
+				if ( i <= 0x06FF )
+				    if ( bcR1 [ i - 0x0590 ] == 0 ) {
+					bcR1 [ i - 0x0590 ] = bidiClass;
+				    } else {
+					throw new Exception ( "duplicate singleton entry: " + i );
+				    }
+				else {
+				    addInterval ( intervals, i, e, bidiClass );
+				    break;
+				}
+			    }
+			} else {
+			    addInterval ( intervals, s, e, bidiClass );
+			}
+		    }
+		} else {
+		    throw new Exception ( "bad syntax, line(" + lineNumber + "): " + line );
+		}
+            }
+        }
+	// compile interval search data
+	int ivIndex = 0, niv = intervals.size();
+	bcS1 = new int [ niv ];
+	bcE1 = new int [ niv ];
+	bcC1 = new byte [ niv ];
+	for ( Iterator it = intervals.iterator(); it.hasNext(); ivIndex++ ) {
+	    Interval iv = (Interval) it.next();
+	    bcS1[ivIndex] = iv.start;
+	    bcE1[ivIndex] = iv.end;
+	    bcC1[ivIndex] = (byte) iv.bidiClass;
+	}
+	// test data
+	test();
+    }
+
+    private static int[] parseInterval ( String interval ) throws Exception {
+	int s, e;
+	String[] fa = interval.split("\\.\\.");
+	if ( fa.length == 1 ) {
+	    s = Integer.parseInt ( fa[0], 16 );
+	    e = s;
+	} else if ( fa.length == 2 ) {
+	    s = Integer.parseInt ( fa[0], 16 );
+	    e = Integer.parseInt ( fa[1], 16 );
+	} else {
+	    throw new Exception ( "bad interval syntax: " + interval );
+	}
+	if ( e < s ) {
+	    throw new Exception ( "bad interval, start must be less than or equal to end: " + interval );
+	}
+	return new int[] { s, e };
+    }
+
+    public static int parseBidiClass ( String bidiClass ) {
+	int bc = 0;
+	if ( "L".equals ( bidiClass ) ) {
+	    bc = BidiConstants.L;
+	} else if ( "LRE".equals ( bidiClass ) ) {
+	    bc = BidiConstants.LRE;
+	} else if ( "LRO".equals ( bidiClass ) ) {
+	    bc = BidiConstants.LRO;
+	} else if ( "R".equals ( bidiClass ) ) {
+	    bc = BidiConstants.R;
+	} else if ( "AL".equals ( bidiClass ) ) {
+	    bc = BidiConstants.AL;
+	} else if ( "RLE".equals ( bidiClass ) ) {
+	    bc = BidiConstants.RLE;
+	} else if ( "RLO".equals ( bidiClass ) ) {
+	    bc = BidiConstants.RLO;
+	} else if ( "PDF".equals ( bidiClass ) ) {
+	    bc = BidiConstants.PDF;
+	} else if ( "EN".equals ( bidiClass ) ) {
+	    bc = BidiConstants.EN;
+	} else if ( "ES".equals ( bidiClass ) ) {
+	    bc = BidiConstants.ES;
+	} else if ( "ET".equals ( bidiClass ) ) {
+	    bc = BidiConstants.ET;
+	} else if ( "AN".equals ( bidiClass ) ) {
+	    bc = BidiConstants.AN;
+	} else if ( "CS".equals ( bidiClass ) ) {
+	    bc = BidiConstants.CS;
+	} else if ( "NSM".equals ( bidiClass ) ) {
+	    bc = BidiConstants.NSM;
+	} else if ( "BN".equals ( bidiClass ) ) {
+	    bc = BidiConstants.BN;
+	} else if ( "B".equals ( bidiClass ) ) {
+	    bc = BidiConstants.B;
+	} else if ( "S".equals ( bidiClass ) ) {
+	    bc = BidiConstants.S;
+	} else if ( "WS".equals ( bidiClass ) ) {
+	    bc = BidiConstants.WS;
+	} else if ( "ON".equals ( bidiClass ) ) {
+	    bc = BidiConstants.ON;
+	} else {
+	    throw new IllegalArgumentException ( "unknown bidi class: " + bidiClass );
+	}
+	return bc;
+    }
+
+    private static void addInterval ( SortedSet intervals, int start, int end, int bidiClass ) {
+	intervals.add ( new Interval ( start, end, bidiClass ) );
+    }
+
+    private static void dumpData ( PrintWriter out ) {
+	boolean first;
+	StringBuffer sb = new StringBuffer();
+
+	// bcL1
+	first = true;
+	sb.setLength(0);
+	out.println ( "private static byte[] bcL1 = {" );
+	for ( int i = 0; i < bcL1.length; i++ ) {
+	    if ( ! first )
+		sb.append ( "," );
+	    else
+		first = false;
+	    sb.append ( bcL1[i] );
+	    if ( sb.length() > 120 ) {
+		sb.append(',');
+		out.println(sb);
+		first = true;
+		sb.setLength(0);
+	    }
+	}
+	if ( sb.length() > 0 )
+		out.println(sb);
+	out.println ( "};" );
+	out.println();
+
+	// bcR1
+	first = true;
+	sb.setLength(0);
+	out.println ( "private static byte[] bcR1 = {" );
+	for ( int i = 0; i < bcR1.length; i++ ) {
+	    if ( ! first )
+		sb.append ( "," );
+	    else
+		first = false;
+	    sb.append ( bcR1[i] );
+	    if ( sb.length() > 120 ) {
+		sb.append(',');
+		out.println(sb);
+		first = true;
+		sb.setLength(0);
+	    }
+	}
+	if ( sb.length() > 0 )
+		out.println(sb);
+	out.println ( "};" );
+	out.println();
+
+	// bcS1
+	first = true;
+	sb.setLength(0);
+	out.println ( "private static int[] bcS1 = {" );
+	for ( int i = 0; i < bcS1.length; i++ ) {
+	    if ( ! first )
+		sb.append ( "," );
+	    else
+		first = false;
+	    sb.append ( bcS1[i] );
+	    if ( sb.length() > 120 ) {
+		sb.append(',');
+		out.println(sb);
+		first = true;
+		sb.setLength(0);
+	    }
+	}
+	if ( sb.length() > 0 )
+		out.println(sb);
+	out.println ( "};" );
+	out.println();
+
+	// bcE1
+	first = true;
+	sb.setLength(0);
+	out.println ( "private static int[] bcE1 = {" );
+	for ( int i = 0; i < bcE1.length; i++ ) {
+	    if ( ! first )
+		sb.append ( "," );
+	    else
+		first = false;
+	    sb.append ( bcE1[i] );
+	    if ( sb.length() > 120 ) {
+		sb.append(',');
+		out.println(sb);
+		first = true;
+		sb.setLength(0);
+	    }
+	}
+	if ( sb.length() > 0 )
+		out.println(sb);
+	out.println ( "};" );
+	out.println();
+
+	// bcC1
+	first = true;
+	sb.setLength(0);
+	out.println ( "private static byte[] bcC1 = {" );
+	for ( int i = 0; i < bcC1.length; i++ ) {
+	    if ( ! first )
+		sb.append ( "," );
+	    else
+		first = false;
+	    sb.append ( bcC1[i] );
+	    if ( sb.length() > 120 ) {
+		sb.append(',');
+		out.println(sb);
+		first = true;
+		sb.setLength(0);
+	    }
+	}
+	if ( sb.length() > 0 )
+		out.println(sb);
+	out.println ( "};" );
+	out.println();
+    }
+
+    private static int getBidiClass ( int ch ) {
+	if ( ch <= 0x00FF )
+	    return bcL1 [ ch - 0x0000 ];
+	else if ( ( ch >= 0x0590 ) && ( ch <= 0x06FF ) )
+	    return bcR1 [ ch - 0x0590 ];
+	else
+	    return getBidiClass ( ch, bcS1, bcE1, bcC1 );
+    }
+
+    private static int getBidiClass ( int ch, int[] sa, int[] ea, byte[] ca ) {
+	int k = Arrays.binarySearch ( sa, ch );
+	if ( k >= 0 ) {
+	    return ca [ k ];
+	} else {
+	    k = - ( k + 1 );
+	    if ( k == 0 ) {
+		return BidiConstants.L;
+	    } else if ( ch <= ea [ k - 1 ] ) {
+		return ca [ k - 1 ];
+	    } else {
+		return BidiConstants.L;
+	    }
+	}
+    }
+
+    private static final int[] testData = {
+	0x000000, BidiConstants.BN,
+	0x000009, BidiConstants.S,
+	0x00000A, BidiConstants.B,
+	0x00000C, BidiConstants.WS,
+	0x000020, BidiConstants.WS,
+	0x000023, BidiConstants.ET,
+	0x000028, BidiConstants.ON,
+	0x00002B, BidiConstants.ES,
+	0x00002C, BidiConstants.CS,
+	0x000031, BidiConstants.EN,
+	0x00003A, BidiConstants.CS,
+	0x000041, BidiConstants.L,
+	0x000300, BidiConstants.NSM,
+	0x000374, BidiConstants.ON,
+	0x0005BE, BidiConstants.R,
+	0x000601, BidiConstants.AN,
+	0x000608, BidiConstants.AL,
+	0x000670, BidiConstants.NSM,
+	0x000710, BidiConstants.AL,
+	0x0007FA, BidiConstants.R,
+	0x000970, BidiConstants.L,
+	0x001392, BidiConstants.ON,
+	0x002000, BidiConstants.WS,
+	0x00200E, BidiConstants.L,
+	0x00200F, BidiConstants.R,
+	0x00202A, BidiConstants.LRE,
+	0x00202B, BidiConstants.RLE,
+	0x00202C, BidiConstants.PDF,
+	0x00202D, BidiConstants.LRO,
+	0x00202E, BidiConstants.RLO,
+	0x0020E1, BidiConstants.NSM,
+	0x002212, BidiConstants.ES,
+	0x002070, BidiConstants.EN,
+	0x003000, BidiConstants.WS,
+	0x003009, BidiConstants.ON,
+	0x00FBD4, BidiConstants.AL,
+	0x00FE69, BidiConstants.ET,
+	0x00FF0C, BidiConstants.CS,
+	0x00FEFF, BidiConstants.BN,
+	0x01034A, BidiConstants.L,
+	0x010E60, BidiConstants.AN,
+	0x01F100, BidiConstants.EN,
+	0x0E0001, BidiConstants.BN,
+	0x0E0100, BidiConstants.NSM,
+	0x10FFFF, BidiConstants.BN
+    };
+
+    private static void test() throws Exception {
+	for ( int i = 0, n = testData.length / 2; i < n; i++ ) {
+	    int ch = testData [ i * 2 + 0 ];
+	    int tc = testData [ i * 2 + 1 ];
+	    int bc = getBidiClass ( ch );
+	    if ( bc != tc ) {
+		throw new Exception ( "test mapping failed for character (0x" + Integer.toHexString(ch) + "): expected " + tc + ", got " + bc );
+	    }
+	}
+    }
+
+    public static void main(String[] args) {
+        String bidiFileName = "http://www.unicode.org/Public/UNIDATA/extracted/DerivedBidiClass.txt";
+        String outFileName = "BidiClassUtils.java";
+        boolean ok = true;
+        for (int i = 0; i < args.length; i = i + 2) {
+            if (i + 1 == args.length) {
+                ok = false;
+            } else {
+                String opt = args[i];
+                if ("-b".equals(opt)) {
+                    bidiFileName = args[i+1];
+                } else if("-o".equals(opt)) {
+                    outFileName = args[i+1];
+                } else {
+                    ok = false;
+                }
+            }
+        }
+        if (!ok) {
+            System.out.println("Usage: GenerateBidiClassUtils [-b <bidiFile>] [-o <outputFile>]");
+            System.out.println("  defaults:");
+            System.out.println("    <bidiFile>:     " + bidiFileName);
+            System.out.println("    <outputFile>:        " + outFileName);
+        } else {
+            try {
+                convertBidiClassProperties(bidiFileName, outFileName);
+                System.out.println("Generated " + outFileName + " from");
+                System.out.println("  <bidiFile>:     " + bidiFileName);
+            } catch (Exception e) {
+                System.out.println("An unexpected error occured");
+                e.printStackTrace();
+            }
+        }
+    }
+
+    private static class Interval implements Comparable {
+	int start;
+	int end;
+	int bidiClass;
+	Interval ( int start, int end, int bidiClass ) {
+	    this.start = start;
+	    this.end = end;
+	    this.bidiClass = bidiClass;
+	}
+	public int compareTo ( Object o ) {
+	    Interval iv = (Interval) o;
+	    if ( start < iv.start )
+		return -1;
+	    else if ( start > iv.start )
+		return 1;
+	    else if ( end < iv.end )
+		return -1;
+	    else if ( end > iv.end )
+		return 1;
+	    else
+		return 0;
+	}
+    }
+}
Index: src/codegen/unicode/java/org/apache/fop/text/linebreak/GenerateLineBreakUtils.java
===================================================================
--- src/codegen/unicode/java/org/apache/fop/text/linebreak/GenerateLineBreakUtils.java	(revision 981406)
+++ src/codegen/unicode/java/org/apache/fop/text/linebreak/GenerateLineBreakUtils.java	(working copy)
@@ -242,8 +242,15 @@
         out.println(" * - commit BOTH changed files");
         out.println(" */");
         out.println();
+        out.println("// CSOFF: WhitespaceAfterCheck");
+        out.println("// CSOFF: LineLengthCheck");
+        out.println();
+        out.println("/** Line breaking utilities. */");
         out.println("public final class LineBreakUtils {");
         out.println();
+        out.println("    private LineBreakUtils() {");
+        out.println("    }");
+        out.println();
         out.println("    /** Break class constant */");
         out.println("    public static final byte DIRECT_BREAK = " + DIRECT_BREAK + ';');
         out.println("    /** Break class constant */");
@@ -257,7 +264,7 @@
         out.println("    /** Break class constant */");
         out.println("    public static final byte EXPLICIT_BREAK = " + EXPLICIT_BREAK + ';');
         out.println();
-        out.println("    private static final byte PAIR_TABLE[][] = {");
+        out.println("    private static final byte[][] PAIR_TABLE = {");
         boolean printComma = false;
         for (int i = 1; i <= lineBreakPropertyValueCount; i++) {
             if (printComma) {
@@ -283,9 +290,9 @@
         }
         out.println("};");
         out.println();
-        out.println("    private static byte lineBreakProperties[][] = new byte[" + rowsize + "][];");
+        out.println("    private static byte[][] lineBreakProperties = new byte[" + rowsize + "][];");
         out.println();
-        out.println("    private static void init_0() {");
+        out.println("    private static void init0() {");
         int rowsPrinted = 0;
         int initSections = 0;
         for (int i = 0; i < rowsize; i++) {
@@ -315,7 +322,7 @@
                     out.println("    }");
                     out.println();
                     initSections++;
-                    out.println("    private static void init_" + initSections + "() {");
+                    out.println("    private static void init" + initSections + "() {");
                     rowsPrinted = 0;
                 }
                 row[i] = new byte[blocksize];
@@ -339,7 +346,7 @@
         out.println();
         out.println("    static {");
         for (int i = 0; i <= initSections; i++) {
-            out.println("        init_" + i + "();");
+            out.println("        init" + i + "();");
         }
         out.print(doStaticLinkCode);
         out.println("    }");
@@ -354,7 +361,7 @@
             out.println(';');
         }
         out.println();
-        final String shortNamePrefix = "    private static String lineBreakPropertyShortNames[] = {";
+        final String shortNamePrefix = "    private static String[] lineBreakPropertyShortNames = {";
         out.print(shortNamePrefix);
         int lineLength = shortNamePrefix.length();
         printComma = false;
@@ -383,7 +390,7 @@
         }
         out.println("};");
         out.println();
-        final String longNamePrefix = "    private static String lineBreakPropertyLongNames[] = {";
+        final String longNamePrefix = "    private static String[] lineBreakPropertyLongNames = {";
         out.print(longNamePrefix);
         lineLength = longNamePrefix.length();
         printComma = false;
Index: src/codegen/unicode/data/LineBreakPairTable.txt
===================================================================
--- src/codegen/unicode/data/LineBreakPairTable.txt	(revision 981406)
+++ src/codegen/unicode/data/LineBreakPairTable.txt	(working copy)
@@ -1,28 +1,29 @@
-    OP  CL  QU  GL  NS  EX  SY  IS  PR  PO  NU  AL  ID  IN  HY  BA  BB  B2  ZW  CM  WJ  H2  H3  JL  JV  JT
-OP  ^   ^   ^   ^   ^   ^   ^   ^   ^   ^   ^   ^   ^   ^   ^   ^   ^   ^   ^   @   ^   ^   ^   ^   ^   ^
-CL  _   ^   %   %   ^   ^   ^   ^   %   %   %   %   _   _   %   %   _   _   ^   #   ^   _   _   _   _   _
-QU  ^   ^   %   %   %   ^   ^   ^   %   %   %   %   %   %   %   %   %   %   ^   #   ^   %   %   %   %   %
-GL  %   ^   %   %   %   ^   ^   ^   %   %   %   %   %   %   %   %   %   %   ^   #   ^   %   %   %   %   %
-NS  _   ^   %   %   %   ^   ^   ^   _   _   _   _   _   _   %   %   _   _   ^   #   ^   _   _   _   _   _
-EX  _   ^   %   %   %   ^   ^   ^   _   _   _   _   _   _   %   %   _   _   ^   #   ^   _   _   _   _   _
-SY  _   ^   %   %   %   ^   ^   ^   _   _   %   _   _   _   %   %   _   _   ^   #   ^   _   _   _   _   _
-IS  _   ^   %   %   %   ^   ^   ^   _   _   %   %   _   _   %   %   _   _   ^   #   ^   _   _   _   _   _
-PR  %   ^   %   %   %   ^   ^   ^   _   _   %   %   %   _   %   %   _   _   ^   #   ^   %   %   %   %   %
-PO  %   ^   %   %   %   ^   ^   ^   _   _   %   %   _   _   %   %   _   _   ^   #   ^   _   _   _   _   _
-NU  %   ^   %   %   %   ^   ^   ^   %   %   %   %   _   %   %   %   _   _   ^   #   ^   _   _   _   _   _
-AL  %   ^   %   %   %   ^   ^   ^   _   _   %   %   _   %   %   %   _   _   ^   #   ^   _   _   _   _   _
-ID  _   ^   %   %   %   ^   ^   ^   _   %   _   _   _   %   %   %   _   _   ^   #   ^   _   _   _   _   _
-IN  _   ^   %   %   %   ^   ^   ^   _   _   _   _   _   %   %   %   _   _   ^   #   ^   _   _   _   _   _
-HY  _   ^   %   %   %   ^   ^   ^   _   _   %   _   _   _   %   %   _   _   ^   #   ^   _   _   _   _   _
-BA  _   ^   %   %   %   ^   ^   ^   _   _   _   _   _   _   %   %   _   _   ^   #   ^   _   _   _   _   _
-BB  %   ^   %   %   %   ^   ^   ^   %   %   %   %   %   %   %   %   %   %   ^   #   ^   %   %   %   %   %
-B2  _   ^   %   %   %   ^   ^   ^   _   _   _   _   _   _   %   %   _   ^   ^   #   ^   _   _   _   _   _
-ZW  _   _   _   _   _   _   _   _   _   _   _   _   _   _   _   _   _   _   ^   _   _   _   _   _   _   _
-CM  _   ^   %   %   %   ^   ^   ^   _   _   %   %   _   %   %   %   _   _   ^   #   ^   _   _   _   _   _
-WJ  %   ^   %   %   %   ^   ^   ^   %   %   %   %   %   %   %   %   %   %   ^   #   ^   %   %   %   %   %
-H2  _   ^   %   %   %   ^   ^   ^   _   %   _   _   _   %   %   %   _   _   ^   #   ^   _   _   _   %   %
-H3  _   ^   %   %   %   ^   ^   ^   _   %   _   _   _   %   %   %   _   _   ^   #   ^   _   _   _   _   %
-JL  _   ^   %   %   %   ^   ^   ^   _   %   _   _   _   %   %   %   _   _   ^   #   ^   %   %   %   %   _
-JV  _   ^   %   %   %   ^   ^   ^   _   %   _   _   _   %   %   %   _   _   ^   #   ^   _   _   _   %   %
-JT  _   ^   %   %   %   ^   ^   ^   _   %   _   _   _   %   %   %   _   _   ^   #   ^   _   _   _   _   %
+    OP  CP  CL  QU  GL  NS  EX  SY  IS  PR  PO  NU  AL  ID  IN  HY  BA  BB  B2  ZW  CM  WJ  H2  H3  JL  JV  JT
+OP  ^   ^   ^   ^   ^   ^   ^   ^   ^   ^   ^   ^   ^   ^   ^   ^   ^   ^   ^   ^   @   ^   ^   ^   ^   ^   ^
+CP  _   ^   ^   %   %   ^   ^   ^   ^   %   %   %   %   _   _   %   %   _   _   ^   #   ^   _   _   _   _   _
+CL  _   ^   ^   %   %   ^   ^   ^   ^   %   %   %   %   _   _   %   %   _   _   ^   #   ^   _   _   _   _   _
+QU  ^   ^   ^   %   %   %   ^   ^   ^   %   %   %   %   %   %   %   %   %   %   ^   #   ^   %   %   %   %   %
+GL  %   ^   ^   %   %   %   ^   ^   ^   %   %   %   %   %   %   %   %   %   %   ^   #   ^   %   %   %   %   %
+NS  _   ^   ^   %   %   %   ^   ^   ^   _   _   _   _   _   _   %   %   _   _   ^   #   ^   _   _   _   _   _
+EX  _   ^   ^   %   %   %   ^   ^   ^   _   _   _   _   _   _   %   %   _   _   ^   #   ^   _   _   _   _   _
+SY  _   ^   ^   %   %   %   ^   ^   ^   _   _   %   _   _   _   %   %   _   _   ^   #   ^   _   _   _   _   _
+IS  _   ^   ^   %   %   %   ^   ^   ^   _   _   %   %   _   _   %   %   _   _   ^   #   ^   _   _   _   _   _
+PR  %   ^   ^   %   %   %   ^   ^   ^   _   _   %   %   %   _   %   %   _   _   ^   #   ^   %   %   %   %   %
+PO  %   ^   ^   %   %   %   ^   ^   ^   _   _   %   %   _   _   %   %   _   _   ^   #   ^   _   _   _   _   _
+NU  %   ^   ^   %   %   %   ^   ^   ^   %   %   %   %   _   %   %   %   _   _   ^   #   ^   _   _   _   _   _
+AL  %   ^   ^   %   %   %   ^   ^   ^   _   _   %   %   _   %   %   %   _   _   ^   #   ^   _   _   _   _   _
+ID  _   ^   ^   %   %   %   ^   ^   ^   _   %   _   _   _   %   %   %   _   _   ^   #   ^   _   _   _   _   _
+IN  _   ^   ^   %   %   %   ^   ^   ^   _   _   _   _   _   %   %   %   _   _   ^   #   ^   _   _   _   _   _
+HY  _   ^   ^   %   %   %   ^   ^   ^   _   _   %   _   _   _   %   %   _   _   ^   #   ^   _   _   _   _   _
+BA  _   ^   ^   %   %   %   ^   ^   ^   _   _   _   _   _   _   %   %   _   _   ^   #   ^   _   _   _   _   _
+BB  %   ^   ^   %   %   %   ^   ^   ^   %   %   %   %   %   %   %   %   %   %   ^   #   ^   %   %   %   %   %
+B2  _   ^   ^   %   %   %   ^   ^   ^   _   _   _   _   _   _   %   %   _   ^   ^   #   ^   _   _   _   _   _
+ZW  _   _   _   _   _   _   _   _   _   _   _   _   _   _   _   _   _   _   _   ^   _   _   _   _   _   _   _
+CM  _   ^   ^   %   %   %   ^   ^   ^   _   _   %   %   _   %   %   %   _   _   ^   #   ^   _   _   _   _   _
+WJ  %   ^   ^   %   %   %   ^   ^   ^   %   %   %   %   %   %   %   %   %   %   ^   #   ^   %   %   %   %   %
+H2  _   ^   ^   %   %   %   ^   ^   ^   _   %   _   _   _   %   %   %   _   _   ^   #   ^   _   _   _   %   %
+H3  _   ^   ^   %   %   %   ^   ^   ^   _   %   _   _   _   %   %   %   _   _   ^   #   ^   _   _   _   _   %
+JL  _   ^   ^   %   %   %   ^   ^   ^   _   %   _   _   _   %   %   %   _   _   ^   #   ^   %   %   %   %   _
+JV  _   ^   ^   %   %   %   ^   ^   ^   _   %   _   _   _   %   %   %   _   _   ^   #   ^   _   _   _   %   %
+JT  _   ^   ^   %   %   %   ^   ^   ^   _   %   _   _   _   %   %   %   _   _   ^   #   ^   _   _   _   _   %
 
Index: checkstyle-5.0.xml
===================================================================
--- checkstyle-5.0.xml	(revision 981406)
+++ checkstyle-5.0.xml	(working copy)
@@ -175,6 +175,7 @@
       <property name="severity" value="warning"/>
       <property name="tokens" value="ASSIGN, BAND, BAND_ASSIGN, BOR, BOR_ASSIGN, BSR, BSR_ASSIGN, BXOR, BXOR_ASSIGN, COLON, DIV, DIV_ASSIGN, EQUAL, GE, GT, LAND, LCURLY, LE, LITERAL_ASSERT, LITERAL_CATCH, LITERAL_DO, LITERAL_ELSE, LITERAL_FINALLY, LITERAL_FOR, LITERAL_IF, LITERAL_RETURN, LITERAL_SYNCHRONIZED, LITERAL_TRY, LITERAL_WHILE, LOR, LT, MINUS, MINUS_ASSIGN, MOD, MOD_ASSIGN, NOT_EQUAL, PLUS, PLUS_ASSIGN, QUESTION, RCURLY, SL, SLIST, SL_ASSIGN, SR, SR_ASSIGN, STAR, STAR_ASSIGN"/>
     </module>
+    <module name="FileContentsHolder"/>
   </module>
   <module name="RegexpHeader">
     <property name="headerFile" value="${samedir}/checkstyle.header"/>
@@ -187,4 +188,17 @@
   <module name="FileTabCharacter">
     <property name="severity" value="error"/>
   </module>
+  <module name="SuppressionFilter">
+    <property name="file" value="${samedir}/checkstyle-suppressions.xml"/>
+  </module>
+  <module name="SuppressionCommentFilter">
+    <property name="offCommentFormat" value="CSOFF\: ([\w\|]+)"/>
+    <property name="onCommentFormat" value="CSON\: ([\w\|]+)"/>
+    <property name="checkFormat" value="$1"/>
+  </module>
+  <module name="SuppressWithNearbyCommentFilter">
+    <property name="commentFormat" value="CSOK\: ([\w\|]+)"/>
+    <property name="checkFormat" value="$1"/>
+    <property name="influenceFormat" value="0"/>
+  </module>
 </module>
Index: build.xml
===================================================================
--- build.xml	(revision 981406)
+++ build.xml	(working copy)
@@ -54,9 +54,9 @@
 <project default="all" basedir="." name="fop">
 <!-- See build.properties and build-local.properties for overriding build settings. -->
 <!-- build-local.properties is not stored in SVN and overrides values from build.properties -->
+  <property environment="env"/>
   <property file="${basedir}/build-local.properties"/>
   <property file="${basedir}/build.properties"/>
-  <property environment="env"/>
   <fileset dir="${basedir}" id="dist.bin">
     <include name="conf/**"/>
     <include name="examples/**"/>
@@ -807,6 +807,7 @@
   <target name="junit-userconfig" depends="junit-compile" if="junit.present" description="Runs FOP's user config JUnit tests">
     <echo message="Running user config tests"/>
     <junit dir="${basedir}" haltonfailure="${junit.haltonfailure}" fork="${junit.fork}" errorproperty="fop.junit.error" failureproperty="fop.junit.failure">
+      <jvmarg value="-Xmx2048m"/>
       <sysproperty key="basedir" value="${basedir}"/>
       <sysproperty key="jawa.awt.headless" value="true"/>
       <sysproperty key="fop.layoutengine.disabled" value="${layoutengine.disabled}"/>
@@ -1194,7 +1195,7 @@
   </target>
   <target name="findbugs" depends="init, findbugs-avail, compile-java" if="findbugs.home.dir">
     <taskdef name="findbugs" classname="edu.umd.cs.findbugs.anttask.FindBugsTask" classpathref="libs-findbugs"/>
-    <findbugs home="${findbugs.home.dir}" output="html" reportLevel="low" effort="max" outputFile="${build.dir}/report_findbugs.html" jvmargs="-Xmx1024m">
+    <findbugs home="${findbugs.home.dir}" output="html" reportLevel="low" effort="max" outputFile="${build.dir}/report_findbugs.html" excludeFilter="findbugs-exclude.xml" jvmargs="-Xmx1024m">
       <sourcePath path="${src.java.dir}"/>
       <class location="${build.classes.dir}"/>
     </findbugs>
@@ -1352,14 +1353,22 @@
   <!-- be generated by below. This target should never be part of the      -->
   <!-- normal build process.                                               -->
   <!-- =================================================================== -->
-  <target name="codegen-unicode" >
+  <target name="codegen-unicode" depends="compile-java">
     <mkdir dir="${build.codegen-classes.dir}"/>
     <javac destdir="${build.codegen-classes.dir}" fork="${javac.fork}" debug="${javac.debug}" deprecation="${javac.deprecation}" optimize="${javac.optimize}" source="${javac.source}" target="${javac.target}">
       <src path="${src.codegen.dir}/unicode/java"/>
+      <classpath>
+        <path refid="libs-build-classpath"/>
+        <pathelement location="${build.classes.dir}"/>
+        <pathelement location="${build.codegen-classes.dir}"/>
+      </classpath>
     </javac>
     <java classname="org.apache.fop.text.linebreak.GenerateLineBreakUtils" classpath="${build.codegen-classes.dir}">
       <arg line="-o ${src.dir}/java/org/apache/fop/text/linebreak/LineBreakUtils.java"/>
     </java>
+    <java classname="org.apache.fop.text.bidi.GenerateBidiClassUtils" classpath="${build.codegen-classes.dir}">
+      <arg line="-o ${src.dir}/java/org/apache/fop/text/bidi/BidiClassUtils.java"/>
+    </java>
   </target>
 <!-- =================================================================== -->
 <!-- Special target for Gump                                             -->
@@ -1368,6 +1377,11 @@
 <!-- =================================================================== -->
 <!-- Clean targets                                                       -->
 <!-- =================================================================== -->
+  <target name="clean-code" description="Cleans the build directory for all class file directories">
+    <delete dir="${build.classes.dir}"/>
+    <delete dir="${build.codegen-classes.dir}"/>
+    <delete dir="${build.sandbox-classes.dir}"/>
+  </target>
   <target name="clean" description="Cleans the build directory">
     <delete dir="${build.dir}"/>
   </target>
Index: checkstyle-suppressions.xml
===================================================================
--- checkstyle-suppressions.xml	(revision 0)
+++ checkstyle-suppressions.xml	(revision 0)
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE suppressions PUBLIC "-//Puppy Crawl//DTD Suppressions 1.1//EN" "http://www.puppycrawl.com/dtds/suppressions_1_1.dtd">
+<suppressions>
+    <suppress files="TTFFile.java" checks="FileLengthCheck"/>
+</suppressions>
