]> source.dussan.org Git - xmlgraphics-fop.git/commitdiff
Bugzilla #49687:
authorSimon Pepping <spepping@apache.org>
Thu, 19 Aug 2010 19:46:41 +0000 (19:46 +0000)
committerSimon Pepping <spepping@apache.org>
Thu, 19 Aug 2010 19:46:41 +0000 (19:46 +0000)
Complex Script Support, patch of 2010-08-19
Submitted by: Glenn Adams <glenn.at.skynav.com>

git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/branches/Temp_ComplexScripts@987282 13f79535-47bb-0310-9956-ffa450edef68

127 files changed:
build.xml
findbugs-exclude.xml [new file with mode: 0644]
src/codegen/unicode/java/org/apache/fop/hyphenation/UnicodeClasses.java
src/codegen/unicode/java/org/apache/fop/text/bidi/GenerateBidiClassUtils.java [new file with mode: 0644]
src/java/org/apache/fop/area/Area.java
src/java/org/apache/fop/area/AreaTreeParser.java
src/java/org/apache/fop/area/Block.java
src/java/org/apache/fop/area/CTM.java
src/java/org/apache/fop/area/LineArea.java
src/java/org/apache/fop/area/RegionViewport.java
src/java/org/apache/fop/area/Trait.java
src/java/org/apache/fop/area/inline/FilledArea.java
src/java/org/apache/fop/area/inline/InlineArea.java
src/java/org/apache/fop/area/inline/InlineParent.java
src/java/org/apache/fop/area/inline/SpaceArea.java
src/java/org/apache/fop/area/inline/TextArea.java
src/java/org/apache/fop/area/inline/Viewport.java
src/java/org/apache/fop/area/inline/WordArea.java
src/java/org/apache/fop/fo/Constants.java
src/java/org/apache/fop/fo/FOPropertyMapping.java
src/java/org/apache/fop/fo/FOText.java
src/java/org/apache/fop/fo/FObj.java
src/java/org/apache/fop/fo/PropertyList.java
src/java/org/apache/fop/fo/flow/BidiOverride.java
src/java/org/apache/fop/fo/flow/Block.java
src/java/org/apache/fop/fo/flow/BlockContainer.java
src/java/org/apache/fop/fo/flow/Character.java
src/java/org/apache/fop/fo/flow/InlineContainer.java
src/java/org/apache/fop/fo/flow/InlineLevel.java
src/java/org/apache/fop/fo/flow/Leader.java
src/java/org/apache/fop/fo/flow/table/Table.java
src/java/org/apache/fop/fo/pagination/PageSequence.java
src/java/org/apache/fop/fo/pagination/Region.java
src/java/org/apache/fop/fo/pagination/RegionAfter.java
src/java/org/apache/fop/fo/pagination/RegionBA.java
src/java/org/apache/fop/fo/pagination/RegionBefore.java
src/java/org/apache/fop/fo/pagination/RegionBody.java
src/java/org/apache/fop/fo/pagination/RegionEnd.java
src/java/org/apache/fop/fo/pagination/RegionSE.java
src/java/org/apache/fop/fo/pagination/RegionStart.java
src/java/org/apache/fop/fo/pagination/SimplePageMaster.java
src/java/org/apache/fop/fo/properties/CorrespondingPropertyMaker.java
src/java/org/apache/fop/fo/properties/DimensionPropertyMaker.java
src/java/org/apache/fop/fo/properties/IndentPropertyMaker.java
src/java/org/apache/fop/fonts/ArabicScriptProcessor.java [new file with mode: 0644]
src/java/org/apache/fop/fonts/CustomFont.java
src/java/org/apache/fop/fonts/DefaultScriptProcessor.java [new file with mode: 0644]
src/java/org/apache/fop/fonts/DiscontinuousAssociationException.java [new file with mode: 0644]
src/java/org/apache/fop/fonts/EmbedFontInfo.java
src/java/org/apache/fop/fonts/Font.java
src/java/org/apache/fop/fonts/FontCache.java
src/java/org/apache/fop/fonts/FontInfoConfigurator.java
src/java/org/apache/fop/fonts/FontLoader.java
src/java/org/apache/fop/fonts/FontReader.java
src/java/org/apache/fop/fonts/GlyphContextTester.java [new file with mode: 0644]
src/java/org/apache/fop/fonts/GlyphCoverageTable.java [new file with mode: 0644]
src/java/org/apache/fop/fonts/GlyphPositioning.java [new file with mode: 0644]
src/java/org/apache/fop/fonts/GlyphPositioningSubtable.java [new file with mode: 0644]
src/java/org/apache/fop/fonts/GlyphPositioningTable.java [new file with mode: 0644]
src/java/org/apache/fop/fonts/GlyphSequence.java [new file with mode: 0644]
src/java/org/apache/fop/fonts/GlyphSubstitution.java [new file with mode: 0644]
src/java/org/apache/fop/fonts/GlyphSubstitutionSubtable.java [new file with mode: 0644]
src/java/org/apache/fop/fonts/GlyphSubstitutionTable.java [new file with mode: 0644]
src/java/org/apache/fop/fonts/GlyphSubtable.java [new file with mode: 0644]
src/java/org/apache/fop/fonts/GlyphTable.java [new file with mode: 0644]
src/java/org/apache/fop/fonts/GlyphUtils.java [new file with mode: 0644]
src/java/org/apache/fop/fonts/IncompatibleSubtableException.java [new file with mode: 0644]
src/java/org/apache/fop/fonts/LazyFont.java
src/java/org/apache/fop/fonts/MultiByteFont.java
src/java/org/apache/fop/fonts/MutableFont.java
src/java/org/apache/fop/fonts/Positionable.java [new file with mode: 0644]
src/java/org/apache/fop/fonts/ScriptProcessor.java [new file with mode: 0644]
src/java/org/apache/fop/fonts/Substitutable.java [new file with mode: 0644]
src/java/org/apache/fop/fonts/Typeface.java
src/java/org/apache/fop/fonts/apps/TTFReader.java
src/java/org/apache/fop/fonts/autodetect/FontInfoFinder.java
src/java/org/apache/fop/fonts/truetype/TTFFile.java
src/java/org/apache/fop/fonts/truetype/TTFFontLoader.java
src/java/org/apache/fop/fonts/type1/Type1FontLoader.java
src/java/org/apache/fop/layoutmgr/AbstractLayoutManager.java
src/java/org/apache/fop/layoutmgr/BidiUtil.java [new file with mode: 0644]
src/java/org/apache/fop/layoutmgr/BlockLayoutManager.java
src/java/org/apache/fop/layoutmgr/ExternalDocumentLayoutManager.java
src/java/org/apache/fop/layoutmgr/LayoutContext.java
src/java/org/apache/fop/layoutmgr/LayoutManagerMapping.java
src/java/org/apache/fop/layoutmgr/PageSequenceLayoutManager.java
src/java/org/apache/fop/layoutmgr/inline/AbstractGraphicsLayoutManager.java
src/java/org/apache/fop/layoutmgr/inline/AlignmentContext.java
src/java/org/apache/fop/layoutmgr/inline/BasicScaledBaselineTable.java
src/java/org/apache/fop/layoutmgr/inline/BidiLayoutManager.java
src/java/org/apache/fop/layoutmgr/inline/CharacterLayoutManager.java
src/java/org/apache/fop/layoutmgr/inline/InlineLayoutManager.java
src/java/org/apache/fop/layoutmgr/inline/LeaderLayoutManager.java
src/java/org/apache/fop/layoutmgr/inline/LeafNodeLayoutManager.java
src/java/org/apache/fop/layoutmgr/inline/LineLayoutManager.java
src/java/org/apache/fop/layoutmgr/inline/PageNumberLayoutManager.java
src/java/org/apache/fop/layoutmgr/inline/ScaledBaselineTable.java
src/java/org/apache/fop/layoutmgr/inline/ScaledBaselineTableFactory.java
src/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java
src/java/org/apache/fop/pdf/PDFDocument.java
src/java/org/apache/fop/pdf/PDFText.java
src/java/org/apache/fop/render/AbstractPathOrientedRenderer.java
src/java/org/apache/fop/render/AbstractRenderer.java
src/java/org/apache/fop/render/afp/AFPRenderer.java
src/java/org/apache/fop/render/intermediate/IFRenderer.java
src/java/org/apache/fop/render/java2d/ConfiguredFontCollection.java
src/java/org/apache/fop/render/java2d/Java2DRenderer.java
src/java/org/apache/fop/render/pcl/PCLRenderer.java
src/java/org/apache/fop/render/pdf/PDFRenderer.java
src/java/org/apache/fop/render/ps/PSRenderer.java
src/java/org/apache/fop/render/rtf/TextAttributesConverter.java
src/java/org/apache/fop/render/rtf/rtflib/rtfdoc/RtfFootnote.java
src/java/org/apache/fop/render/xml/XMLRenderer.java
src/java/org/apache/fop/text/bidi/BidiClassUtils.java [new file with mode: 0644]
src/java/org/apache/fop/tools/anttasks/FileCompare.java
src/java/org/apache/fop/traits/Direction.java [new file with mode: 0644]
src/java/org/apache/fop/traits/WritingMode.java [new file with mode: 0644]
src/java/org/apache/fop/traits/WritingModeTraits.java [new file with mode: 0644]
src/java/org/apache/fop/traits/WritingModeTraitsGetter.java [new file with mode: 0644]
src/java/org/apache/fop/traits/WritingModeTraitsSetter.java [new file with mode: 0644]
src/java/org/apache/fop/util/BidiConstants.java [new file with mode: 0644]
src/java/org/apache/fop/util/CharUtilities.java
test/java/org/apache/fop/fonts/TrueTypeAnsiTestCase.java
test/layoutengine/disabled-testcases.xml
test/layoutengine/standard-testcases/block_basic_2.xml
test/layoutengine/standard-testcases/kerning_1_off.xml
test/layoutengine/standard-testcases/kerning_1_on.xml

index a54b6485691501608f1cb0d74ec21b5949c40132..105775e7c980244a5c3c56af23ed94e3a479b9da 100644 (file)
--- a/build.xml
+++ b/build.xml
@@ -154,6 +154,8 @@ list of possible build targets.
   <property name="javac.fork" value="no"/>
   <property name="junit.fork" value="on"/>
   <property name="junit.haltonfailure" value="off"/>
+  <property name="junit.printsummary" value="off"/>
+  <property name="junit.formatter.brief" value="on"/>
   <property name="javadoc.packages" value="org.apache.fop.*"/>
   <property name="src.dir" value="${basedir}/src"/>
   <property name="src.codegen.dir" value="${src.dir}/codegen"/>
@@ -712,10 +714,15 @@ list of possible build targets.
 <!-- =================================================================== -->
 <!-- Testing                                                             -->
 <!-- =================================================================== -->
-  <target name="junit-with-xmlunit" depends="init-avail" if="xmlunit.present">
+  <target name="junit-init" depends="init-avail" if="xmlunit.present">
+    <condition property="junit.formatter.brief.use">
+      <istrue value="${junit.formatter.brief}"/>
+    </condition>
+  </target>
+  <target name="junit-with-xmlunit" depends="junit-init" if="xmlunit.present">
     <patternset id="test-sources"/>
   </target>
-  <target name="junit-without-xmlunit" depends="init-avail" unless="xmlunit.present">
+  <target name="junit-without-xmlunit" depends="junit-init" unless="xmlunit.present">
     <patternset id="test-sources">
       <exclude name="**/intermediate/*"/>
     </patternset>
@@ -760,10 +767,10 @@ list of possible build targets.
   <target name="junit-compile" depends="junit-compile-java, junit-compile-copy-resources" description="Compiles FOP's JUnit tests" if="junit.present"/>
   <target name="junit-transcoder" depends="junit-compile" description="Runs FOP's JUnit transcoder tests" if="junit.present">
     <echo message="Running basic functionality tests for fop-transcoder.jar"/>
-    <junit dir="${basedir}" haltonfailure="${junit.haltonfailure}" fork="${junit.fork}">
+    <junit dir="${basedir}" haltonfailure="${junit.haltonfailure}" fork="${junit.fork}" printsummary="${junit.printsummary}">
       <sysproperty key="basedir" value="${basedir}"/>
       <sysproperty key="jawa.awt.headless" value="true"/>
-      <formatter type="brief" usefile="false"/>
+      <formatter type="brief" usefile="false" if="junit.formatter.brief.use"/>
       <formatter type="plain" usefile="true"/>
       <formatter type="xml" usefile="true"/>
       <classpath>
@@ -782,10 +789,10 @@ list of possible build targets.
          previous test block succeeded it indicates that the packaging of the allinone
          JAR needs to be updated.
     -->
-    <junit dir="${basedir}" haltonfailure="${junit.haltonfailure}" fork="${junit.fork}" errorproperty="fop.junit.error" failureproperty="fop.junit.failure">
+    <junit dir="${basedir}" haltonfailure="${junit.haltonfailure}" fork="${junit.fork}" errorproperty="fop.junit.error" failureproperty="fop.junit.failure" printsummary="${junit.printsummary}">
       <sysproperty key="basedir" value="${basedir}"/>
       <sysproperty key="jawa.awt.headless" value="true"/>
-      <formatter type="brief" usefile="false"/>
+      <formatter type="brief" usefile="false" if="junit.formatter.brief.use"/>
       <formatter type="plain" usefile="true"/>
       <formatter type="xml" usefile="true"/>
       <classpath>
@@ -805,13 +812,13 @@ list of possible build targets.
   </target>
   <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">
+    <junit dir="${basedir}" haltonfailure="${junit.haltonfailure}" fork="${junit.fork}" errorproperty="fop.junit.error" failureproperty="fop.junit.failure" printsummary="${junit.printsummary}">
       <jvmarg value="-Xmx1024m"/>
       <sysproperty key="basedir" value="${basedir}"/>
       <sysproperty key="jawa.awt.headless" value="true"/>
       <sysproperty key="fop.layoutengine.disabled" value="${layoutengine.disabled}"/>
       <sysproperty key="fop.layoutengine.testset" value="standard"/>
-      <formatter type="brief" usefile="false"/>
+      <formatter type="brief" usefile="false" if="junit.formatter.brief.use"/>
       <formatter type="plain" usefile="true"/>
       <formatter type="xml" usefile="true"/>
       <classpath>
@@ -823,10 +830,10 @@ list of possible build targets.
   </target>
   <target name="junit-basic" depends="junit-compile" description="Runs FOP's JUnit basic tests" if="junit.present">
     <echo message="Running basic functionality tests for fop.jar"/>
-    <junit dir="${basedir}" haltonfailure="${junit.haltonfailure}" fork="${junit.fork}" errorproperty="fop.junit.error" failureproperty="fop.junit.failure">
+    <junit dir="${basedir}" haltonfailure="${junit.haltonfailure}" fork="${junit.fork}" errorproperty="fop.junit.error" failureproperty="fop.junit.failure" printsummary="${junit.printsummary}">
       <sysproperty key="basedir" value="${basedir}"/>
       <sysproperty key="jawa.awt.headless" value="true"/>
-      <formatter type="brief" usefile="false"/>
+      <formatter type="brief" usefile="false" if="junit.formatter.brief.use"/>
       <formatter type="plain" usefile="true"/>
       <formatter type="xml" usefile="true"/>
       <classpath>
@@ -859,12 +866,12 @@ list of possible build targets.
   </target>
   <target name="junit-layout-standard" depends="junit-compile, junit-fotree" if="junit.present" description="Runs FOP's standard JUnit layout tests">
     <echo message="Running standard layout engine tests"/>
-    <junit dir="${basedir}" haltonfailure="${junit.haltonfailure}" fork="${junit.fork}" errorproperty="fop.junit.error" failureproperty="fop.junit.failure">
+    <junit dir="${basedir}" haltonfailure="${junit.haltonfailure}" fork="${junit.fork}" errorproperty="fop.junit.error" failureproperty="fop.junit.failure" printsummary="${junit.printsummary}">
       <sysproperty key="basedir" value="${basedir}"/>
       <sysproperty key="jawa.awt.headless" value="true"/>
       <sysproperty key="fop.layoutengine.disabled" value="${layoutengine.disabled}"/>
       <sysproperty key="fop.layoutengine.testset" value="standard"/>
-      <formatter type="brief" usefile="false"/>
+      <formatter type="brief" usefile="false" if="junit.formatter.brief.use"/>
       <formatter type="plain" usefile="true"/>
       <formatter type="xml" usefile="true"/>
       <classpath>
@@ -876,12 +883,12 @@ list of possible build targets.
   </target>
   <target name="junit-layout-hyphenation" depends="hyphenation-present, junit-compile" if="hyphenation.present" description="Runs FOP's JUnit hyphenation layout tests">
     <echo message="Running hyphenation layout engine tests"/>
-    <junit dir="${basedir}" haltonfailure="${junit.haltonfailure}" fork="${junit.fork}" errorproperty="fop.junit.error" failureproperty="fop.junit.failure">
+    <junit dir="${basedir}" haltonfailure="${junit.haltonfailure}" fork="${junit.fork}" errorproperty="fop.junit.error" failureproperty="fop.junit.failure" printsummary="${junit.printsummary}">
       <sysproperty key="basedir" value="${basedir}"/>
       <sysproperty key="jawa.awt.headless" value="true"/>
       <sysproperty key="fop.layoutengine.disabled" value="${layoutengine.disabled}"/>
       <sysproperty key="fop.layoutengine.testset" value="hyphenation"/>
-      <formatter type="brief" usefile="false"/>
+      <formatter type="brief" usefile="false" if="junit.formatter.brief.use"/>
       <formatter type="plain" usefile="true"/>
       <formatter type="xml" usefile="true"/>
       <classpath>
@@ -894,11 +901,11 @@ list of possible build targets.
   <target name="junit-layout" depends="junit-layout-standard, junit-layout-hyphenation" description="Runs all FOP's JUnit layout tests"/>
   <target name="junit-fotree" depends="junit-compile" description="Runs FOP's FO tree JUnit tests" if="junit.present">
     <echo message="Running fo tree tests"/>
-    <junit dir="${basedir}" haltonfailure="${junit.haltonfailure}" fork="${junit.fork}" errorproperty="fop.junit.error" failureproperty="fop.junit.failure">
+    <junit dir="${basedir}" haltonfailure="${junit.haltonfailure}" fork="${junit.fork}" errorproperty="fop.junit.error" failureproperty="fop.junit.failure" printsummary="${junit.printsummary}">
       <sysproperty key="basedir" value="${basedir}"/>
       <sysproperty key="jawa.awt.headless" value="true"/>
       <sysproperty key="fop.layoutengine.disabled" value="${fotree.disabled}"/>
-      <formatter type="brief" usefile="false"/>
+      <formatter type="brief" usefile="false" if="junit.formatter.brief.use"/>
       <formatter type="plain" usefile="true"/>
       <formatter type="xml" usefile="true"/>
       <classpath>
@@ -918,12 +925,12 @@ list of possible build targets.
     <attribute name="outfile"/>
     <sequential>
       <echo message="Running @{title} tests..."/>
-      <junit dir="${basedir}" haltonfailure="${junit.haltonfailure}" fork="${junit.fork}" errorproperty="fop.junit.error" failureproperty="fop.junit.failure">
+      <junit dir="${basedir}" haltonfailure="${junit.haltonfailure}" fork="${junit.fork}" errorproperty="fop.junit.error" failureproperty="fop.junit.failure" printsummary="${junit.printsummary}">
         <sysproperty key="basedir" value="${basedir}/@{basedir}"/>
         <sysproperty key="jawa.awt.headless" value="true"/>
         <sysproperty key="fop.layoutengine.disabled" value="${layoutengine.disabled}"/>
         <sysproperty key="fop.layoutengine.testset" value="standard"/>
-        <formatter type="brief" usefile="false"/>
+        <formatter type="brief" usefile="false" if="junit.formatter.brief.use"/>
         <formatter type="plain" usefile="true"/>
         <formatter type="xml" usefile="true"/>
         <classpath>
@@ -955,10 +962,10 @@ list of possible build targets.
   </target>
   <target name="junit-text-linebreak" depends="junit-compile" description="Runs FOP's JUnit unicode linebreak tests" if="junit.present">
     <echo message="Running tests for Unicode UAX#14 support"/>
-    <junit dir="${basedir}" haltonfailure="${junit.haltonfailure}" fork="${junit.fork}" errorproperty="fop.junit.error" failureproperty="fop.junit.failure">
+    <junit dir="${basedir}" haltonfailure="${junit.haltonfailure}" fork="${junit.fork}" errorproperty="fop.junit.error" failureproperty="fop.junit.failure" printsummary="${junit.printsummary}">
       <sysproperty key="basedir" value="${basedir}"/>
       <sysproperty key="jawa.awt.headless" value="true"/>
-      <formatter type="brief" usefile="false"/>
+      <formatter type="brief" usefile="false" if="junit.formatter.brief.use"/>
       <formatter type="plain" usefile="true"/>
       <formatter type="xml" usefile="true"/>
       <classpath>
@@ -1190,22 +1197,62 @@ NOTE:
 <!-- =================================================================== -->
 <!-- Findbugs                                                            -->
 <!-- =================================================================== -->
-  <property name="findbugs.lib" value="${findbugs.home.dir}/lib"/>
-  <path id="libs-findbugs">
-    <fileset dir="${findbugs.lib}">
-      <include name="*.jar"/>
-    </fileset>
-  </path>
-  <target name="findbugs-avail" unless="findbugs.home.dir">
-    <echo message="Findbugs Support NOT Present. Please download it from http://findbugs.sf.net/ and set findbugs.home.dir in build-local.properties"/>
+  <target name="findbugs-maybe-describe-install" unless="findbugs.present">
+    <echo message="Please download FINDBUGS from http://findbugs.sf.net/ and set property findbugs.home.dir"/>
+    <echo message="in build-local.properties to the top-level directory of the binary distribution."/>
+   </target>
+  <target name="findbugs-avail">
+    <condition property="findbugs.present">
+      <and>
+        <isset property="findbugs.home.dir"/>
+        <available file="${findbugs.home.dir}" type="dir"/>
+      </and>
+    </condition>
+    <condition property="findbugs.message" value="FINDBUGS Support PRESENT">
+      <equals arg1="${findbugs.present}" arg2="true"/>
+    </condition>
+    <condition property="findbugs.message" value="FINDBUGS Support NOT Present">
+      <not>
+        <equals arg1="${findbugs.present}" arg2="true"/>
+      </not>
+    </condition>
+    <echo message="${findbugs.message}"/>
+    <antcall target="findbugs-maybe-describe-install"/>
   </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">
+  <target name="findbugs-exec" depends="compile-java" if="findbugs.present">
+    <taskdef name="findbugs" classname="edu.umd.cs.findbugs.anttask.FindBugsTask">
+      <classpath>
+        <fileset dir="${findbugs.home.dir}/lib">
+          <include name="*.jar"/>
+        </fileset>
+      </classpath>
+    </taskdef>
+    <findbugs home="${findbugs.home.dir}" output="${findbugs.output.format}" reportLevel="low" effort="max"
+              outputFile="${build.dir}/report_findbugs.${findbugs.output.extension}" excludeFilter="findbugs-exclude.xml" jvmargs="-Xmx1024m">
       <sourcePath path="${src.java.dir}"/>
       <class location="${build.classes.dir}"/>
+      <auxClasspath>
+        <path refid="libs-build-classpath"/>
+        <path>
+          <fileset dir="${ant.library.dir}">
+            <include name="ant.jar"/>
+            <include name="ant-launcher.jar"/>
+          </fileset>
+        </path>
+      </auxClasspath>
     </findbugs>
   </target>
+  <target name="findbugs-xml" depends="findbugs-avail" if="findbugs.present" description="Runs findbugs for a code quality report in XML">
+    <property name="findbugs.output.format" value="xml"/>
+    <property name="findbugs.output.extension" value="xml"/>
+    <antcall target="findbugs-exec"/> 
+  </target>
+  <target name="findbugs-html" depends="findbugs-avail" if="findbugs.present" description="Runs findbugs for a code quality report in HTML">
+    <property name="findbugs.output.format" value="html"/>
+    <property name="findbugs.output.extension" value="html"/>
+    <antcall target="findbugs-exec"/> 
+  </target>
+  <target name="findbugs" depends="findbugs-html" description="Runs findbugs for a code quality report in HTML"/>
 <!-- =================================================================== -->
 <!-- Creates the reports                                                 -->
 <!-- =================================================================== -->
@@ -1359,14 +1406,22 @@ NOTE:
   <!-- 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                                             -->
diff --git a/findbugs-exclude.xml b/findbugs-exclude.xml
new file mode 100644 (file)
index 0000000..8885c49
--- /dev/null
@@ -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 9ea35581d16d3525bf51a7d2054a6820c28bf2b0..263a1c6945fdee7e60b7f42dfcc2f5ed620aaf30 100644 (file)
@@ -160,7 +160,7 @@ public final class UnicodeClasses {
      * @param unidataPath path to the directory with UCD files  
      * @param outfilePath output file
      * @throws IOException if the input files are not found
-     * @throws URISyntaxException 
+     * @throws URISyntaxException if {@code unidataPath} cannot be converted to a URI
      */
     public static void fromUCD(boolean hexcode, String unidataPath, String outfilePath)
     throws IOException, URISyntaxException {
diff --git a/src/codegen/unicode/java/org/apache/fop/text/bidi/GenerateBidiClassUtils.java b/src/codegen/unicode/java/org/apache/fop/text/bidi/GenerateBidiClassUtils.java
new file mode 100644 (file)
index 0000000..3f76d81
--- /dev/null
@@ -0,0 +1,571 @@
+/*
+ * 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;
+
+// CSOFF: LineLength
+// CSOFF: NoWhitespaceAfter
+
+/**
+ * <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 final class GenerateBidiClassUtils {
+
+    private 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};
+    }
+
+    private 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 =                       // CSOK: ConstantName
+    {
+        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 );
+            }
+        }
+    }
+
+    /**
+     * Main entry point for generator.
+     * @param args array of command line arguments
+     */
+    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;                                              // CSOK: VisibilityModifier
+        int end;                                                // CSOK: VisibilityModifier
+        int bidiClass;                                          // CSOK: VisibilityModifier
+        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 ddf2f5198053af5b75ac247cb99818d4456b2091..089117759e5e951d39031aae3d5197e765942a22 100644 (file)
@@ -38,26 +38,6 @@ import org.apache.fop.traits.BorderProps;
  * 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
     /**
@@ -126,17 +106,21 @@ public class Area extends AreaTreeObject implements Serializable {
     /** the area's block-progression-dimension */
     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.
      *
@@ -222,6 +206,32 @@ public class Area extends AreaTreeObject implements Serializable {
                 + getBorderAndPaddingWidthAfter() + getSpaceAfter();
     }
 
+    /**
+     * 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
      *
@@ -376,10 +386,23 @@ public class Area extends AreaTreeObject implements Serializable {
      * @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);
+        }
+        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;
         }
-        props.put(traitCode, prop);
     }
 
     /**
@@ -388,12 +411,12 @@ public class Area extends AreaTreeObject implements Serializable {
      * @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 @@ public class Area extends AreaTreeObject implements Serializable {
      * @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 @@ public class Area extends AreaTreeObject implements Serializable {
         return sb.toString();
     }
 }
-
index 31c0e04fcf2df3b1e6a936062cb07f0c1746b6cc..184020b1a999ac393164252896bf936a3d406830 100644 (file)
@@ -742,7 +742,7 @@ public class AreaTreeParser {
             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);
@@ -762,7 +762,7 @@ public class AreaTreeParser {
             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);
@@ -784,7 +784,7 @@ public class AreaTreeParser {
             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);
@@ -812,7 +812,7 @@ public class AreaTreeParser {
                 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,
@@ -835,8 +835,10 @@ public class AreaTreeParser {
                 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);
@@ -855,7 +857,8 @@ public class AreaTreeParser {
                 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);
@@ -865,7 +868,7 @@ public class AreaTreeParser {
                     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);
                 }
@@ -886,7 +889,8 @@ public class AreaTreeParser {
                 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);
@@ -909,7 +913,8 @@ public class AreaTreeParser {
                 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);
@@ -1071,6 +1076,7 @@ public class AreaTreeParser {
         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 ecb9c49909de1f9b7cc526621a9d70ae4e3545d1..702d66279293bcecc99b634b0c8e8cdfdfc961e2 100644 (file)
@@ -55,7 +55,6 @@ public class Block extends BlockParent {
      */
     public static final int FIXED = 3;
 
-    private int stacking = TB;
     private int positioning = STACK;
 
     /** if true, allow BPD update */
index 099ab38fae8c1137ef23d903bb07b2aa4ceaae89..552edaa959f9b438a9da84ed2bb223fdc1fcfd5f 100644 (file)
@@ -26,6 +26,7 @@ import java.io.Serializable;
 
 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 @@ public class CTM implements Serializable {
      * 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 @@ public class CTM implements Serializable {
                 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 @@ public class CTM implements Serializable {
      * @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 @@ public class CTM implements Serializable {
          * 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 0e6e6ac309e449b46c56bf1aa9c021006e8c787d..43efc0a838d2fdf531b141cdeccbf48634654e1e 100644 (file)
@@ -105,6 +105,15 @@ public class LineArea extends Area {
         inlineAreas.add(area);
     }
 
+    /**
+     * <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.
      *
@@ -179,7 +188,7 @@ public class LineArea extends Area {
                 // 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 @@ public class LineArea extends Area {
      * 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 d55ecefbb3936c3c22e680926898479d8ff9d2c5..d368c6b801d81d335bfde2c63bd5162a3e72d12a 100644 (file)
@@ -93,7 +93,7 @@ public class RegionViewport extends Area implements Cloneable {
         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 @@ public class RegionViewport extends Area implements Cloneable {
         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 class RegionViewport extends Area implements Cloneable {
     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 42c5462e445dd4fefe8a37125f423800a5d749ae..311ced8091028134f855b51650e7ec6af2ea55f8 100644 (file)
@@ -27,6 +27,8 @@ import org.apache.xmlgraphics.image.loader.ImageInfo;
 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
@@ -201,8 +203,17 @@ public final class Trait implements Serializable {
     /** 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];
 
@@ -284,6 +295,14 @@ public final class Trait implements Serializable {
                 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 7df62e79bce3692b7c48b7636ccc4241fb2c0c7b..261e6b29996526482ebe3ee87418875e78b7b755 100644 (file)
@@ -49,7 +49,7 @@ public class FilledArea extends InlineParent {
      * @param v the offset
      */
     /*
-    public void setOffset(int v) {
+    public void setBlockProgressionOffset(int v) {
         setChildOffset(inlines.listIterator(), v);
     }
     */
@@ -62,7 +62,7 @@ public class FilledArea extends InlineParent {
             } else if (child instanceof org.apache.fop.area.inline.Viewport) {
                 // nothing
             } else {
-                child.setOffset(v);
+                child.setBlockProgressionOffset(v);
             }
         }
     }
index 30afc149b327a49e5acc9339cfade476517490f1..59f516190aaefd6cc47a3a9802c860733e73ae10 100644 (file)
@@ -74,7 +74,7 @@ public class InlineArea extends Area {
     /**
      * offset position from before edge of parent area
      */
-    protected int offset = 0;
+    protected int blockProgressionOffset = 0;
 
     /**
      * parent area
@@ -95,6 +95,23 @@ public class InlineArea extends Area {
      */
     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
      */
@@ -133,25 +150,25 @@ public class InlineArea extends Area {
     }
 
     /**
-     * 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;
     }
 
     /**
@@ -243,4 +260,3 @@ public class InlineArea extends Area {
         }
     }
 }
-
index cbc5b578c27ab7067ac10b93de87c9273cc81253..cdf3b8a8e63455529eea3aeadaf1d1f5d6158532 100644 (file)
@@ -21,8 +21,9 @@ package org.apache.fop.area.inline;
 
 import org.apache.fop.area.Area;
 
-import java.util.List;
 import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
 
 /**
  * Inline parent area.
@@ -60,6 +61,7 @@ public class InlineParent extends InlineArea {
             if (autoSize) {
                 increaseIPD(inlineChildArea.getAllocIPD());
             }
+            updateLevel ( childArea.getBidiLevel() );
         }
     }
 
@@ -89,5 +91,28 @@ public class InlineParent extends InlineArea {
         }
         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 202408debac6de47737d7f0a3298a6e6d7cfff9e..489b1c88038ba2774c8d699eb6564eb5acb9ae1f 100644 (file)
@@ -36,25 +36,27 @@ public class SpaceArea extends InlineArea {
 
     /**
      * 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 f736b894c6ef1dc2d9442678361fc76a3925a910..9d18b8d44809b6454d0ced90c6bd36e0d7cad0d3 100644 (file)
@@ -19,6 +19,8 @@
 
 package org.apache.fop.area.inline;
 
+import org.apache.fop.util.CharUtilities;
+
 /**
  * A text inline area.
  */
@@ -55,33 +57,46 @@ public class TextArea extends AbstractTextArea {
      * @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 @@ public class TextArea extends AbstractTextArea {
 
     /** {@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 489f5afda1ed4e416a17534bde2037226f385ddb..7878d049293d94f1950b9cb7b68ddc0f91faeb7f 100644 (file)
@@ -111,7 +111,7 @@ public class Viewport extends InlineArea {
             out.writeFloat((float) contentPosition.getHeight());
         }
         out.writeBoolean(clip);
-        out.writeObject(props);
+        out.writeObject(traits);
         out.writeObject(content);
     }
 
@@ -124,7 +124,7 @@ public class Viewport extends InlineArea {
                                                     in.readFloat());
         }
         this.clip = in.readBoolean();
-        this.props = (HashMap) in.readObject();
+        this.traits = (HashMap) in.readObject();
         this.content = (Area) in.readObject();
     }
 
index 4737d81d33775f0f3a328c539dcb1337641c5c0a..690a32e17ab0d0e9f253ec1c711ce7f65af0c148 100644 (file)
 
 package org.apache.fop.area.inline;
 
+import java.util.Arrays;
+
+import org.apache.fop.util.CharUtilities;
+
 /**
  * A string of characters without spaces
  */
@@ -27,22 +31,38 @@ public class WordArea extends InlineArea {
     /** 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;
     }
 
     /**
@@ -52,22 +72,112 @@ public class WordArea extends InlineArea {
         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() > 0 ) {
+            word = ( ( new StringBuffer ( word ) ) .reverse() ) .toString();
+            if ( levels != null ) {
+                reverse ( levels );
+            }
+            reversed = !reversed;
+            if ( mirror ) {
+                word = CharUtilities.mirror ( word );
+            }
+        }
+    }
+
+    /**
+     * <p>Perform mirroring on mirrorable characters.</p>
+     */
+    public void mirror() {
+        if ( word.length() > 0 ) {
+            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 5f23502f376faa78117d0e48e84e847750318bd7..60fed8e80d3c7b6e87bfcc957d54d2e0b7b37cb9 100644 (file)
@@ -26,7 +26,7 @@ package org.apache.fop.fo;
  * <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 @@ public interface Constants {
     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 8293180cfd38cda36f2764187c460d74c60e1f8f..cc72c9e8da945e362f25da2185b098667af2be84 100644 (file)
@@ -631,7 +631,7 @@ public final class FOPropertyMapping implements Constants {
         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);
 
@@ -640,7 +640,7 @@ public final class FOPropertyMapping implements Constants {
         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);
 
@@ -650,7 +650,7 @@ public final class FOPropertyMapping implements Constants {
         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);
 
@@ -661,7 +661,7 @@ public final class FOPropertyMapping implements Constants {
         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);
 
@@ -670,7 +670,7 @@ public final class FOPropertyMapping implements Constants {
         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);
 
@@ -680,7 +680,7 @@ public final class FOPropertyMapping implements Constants {
         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);
 
@@ -691,7 +691,7 @@ public final class FOPropertyMapping implements Constants {
         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);
 
@@ -700,7 +700,7 @@ public final class FOPropertyMapping implements Constants {
         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);
 
@@ -710,7 +710,7 @@ public final class FOPropertyMapping implements Constants {
         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);
 
@@ -721,7 +721,7 @@ public final class FOPropertyMapping implements Constants {
         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);
 
@@ -730,7 +730,7 @@ public final class FOPropertyMapping implements Constants {
         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);
 
@@ -740,7 +740,7 @@ public final class FOPropertyMapping implements Constants {
         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);
 
@@ -754,7 +754,7 @@ public final class FOPropertyMapping implements Constants {
         m.addShorthand(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
@@ -765,7 +765,7 @@ public final class FOPropertyMapping implements Constants {
         m.addShorthand(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
@@ -777,7 +777,7 @@ public final class FOPropertyMapping implements Constants {
         bwm.addShorthand(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
@@ -790,7 +790,7 @@ public final class FOPropertyMapping implements Constants {
         m.addShorthand(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
@@ -801,7 +801,7 @@ public final class FOPropertyMapping implements Constants {
         m.addShorthand(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
@@ -813,7 +813,7 @@ public final class FOPropertyMapping implements Constants {
         bwm.addShorthand(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
@@ -826,7 +826,7 @@ public final class FOPropertyMapping implements Constants {
         m.addShorthand(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
@@ -837,7 +837,7 @@ public final class FOPropertyMapping implements Constants {
         m.addShorthand(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
@@ -849,7 +849,7 @@ public final class FOPropertyMapping implements Constants {
         bwm.addShorthand(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
@@ -862,7 +862,7 @@ public final class FOPropertyMapping implements Constants {
         m.addShorthand(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
@@ -873,7 +873,7 @@ public final class FOPropertyMapping implements Constants {
         m.addShorthand(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
@@ -885,7 +885,7 @@ public final class FOPropertyMapping implements Constants {
         bwm.addShorthand(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
@@ -894,7 +894,7 @@ public final class FOPropertyMapping implements Constants {
         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);
 
@@ -904,7 +904,7 @@ public final class FOPropertyMapping implements Constants {
         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);
 
@@ -914,7 +914,7 @@ public final class FOPropertyMapping implements Constants {
         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);
 
@@ -924,7 +924,7 @@ public final class FOPropertyMapping implements Constants {
         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);
 
@@ -933,7 +933,7 @@ public final class FOPropertyMapping implements Constants {
         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
@@ -941,7 +941,7 @@ public final class FOPropertyMapping implements Constants {
         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
@@ -949,7 +949,7 @@ public final class FOPropertyMapping implements Constants {
         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
@@ -957,7 +957,7 @@ public final class FOPropertyMapping implements Constants {
         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);
     }
 
@@ -1150,7 +1150,7 @@ public final class FOPropertyMapping implements Constants {
         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);
@@ -1159,7 +1159,7 @@ public final class FOPropertyMapping implements Constants {
         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);
@@ -1170,14 +1170,14 @@ public final class FOPropertyMapping implements Constants {
         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);
 
@@ -1187,14 +1187,16 @@ public final class FOPropertyMapping implements Constants {
         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);
     }
@@ -1351,10 +1353,10 @@ public final class FOPropertyMapping implements Constants {
         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);
@@ -1418,10 +1420,10 @@ public final class FOPropertyMapping implements Constants {
 
         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);
@@ -2511,6 +2513,7 @@ public final class FOPropertyMapping implements Constants {
         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 5db11f731b66957ffcb493cf6883132696888bb7..bae359e63830169aecaa6fda21dbc95cdf2b1191 100644 (file)
@@ -21,6 +21,7 @@ package org.apache.fop.fo;
 
 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 @@ public class FOText extends FONode implements CharSequence {
     /** 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 @@ public class FOText extends FONode implements CharSequence {
     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 @@ public class FOText extends FONode implements CharSequence {
     /** 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 @@ public class FOText extends FONode implements CharSequence {
         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 @@ public class FOText extends FONode implements CharSequence {
      * @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 @@ public class FOText extends FONode implements CharSequence {
         private boolean canRemove = false;
         private boolean canReplace = false;
 
+        public TextCharIterator() {
+        }
+
         /** {@inheritDoc} */
         public boolean hasNext() {
            return (this.currentPosition < charBuffer.limit());
@@ -565,67 +583,88 @@ public class FOText extends FONode implements CharSequence {
     }
 
     /**
-     * @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 @@ public class FOText extends FONode implements CharSequence {
             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 cb265b00807e17a093ae44e54e3254647cae73a6..03b45db52d556dfda5b229b1d568035061605631 100644 (file)
@@ -117,9 +117,7 @@ public abstract class FObj extends FONode implements Constants {
                     throws FOPException {
         setLocator(locator);
         pList.addAttributesToList(attlist);
-        if (!inMarker()
-                || "marker".equals(elementName)) {
-            pList.setWritingMode();
+        if (!inMarker() || "marker".equals(elementName)) {
             bind(pList);
         }
     }
index 709c4303bfd8fe5606c5b6f09ccf3fe515236141..74037aef867f7f3b1a39cb489e4e31c92115e2a5 100644 (file)
@@ -47,8 +47,6 @@ import org.apache.fop.fo.properties.PropertyMaker;
  */
 public abstract class PropertyList {
 
-    // writing-mode index
-    private int writingMode;
 
     private static boolean[] inheritableProperty;
 
@@ -221,51 +219,51 @@ public abstract class PropertyList {
     }
 
     /**
-     * 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;
+        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
+            propID = -1;
+                break;
+            }
+        } catch ( PropertyException e ) {
+            propID = -1;
         }
-        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
@@ -273,13 +271,22 @@ public abstract class PropertyList {
      */
     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 +666,3 @@ public abstract class PropertyList {
         return CommonTextDecoration.createFromPropertyList(this);
     }
 }
-
index 6e4a743794b3e12413d0862fd9d598b6caa9e0d0..4bd6f46cb5719991919d6e9223f64fe93651cca1 100644 (file)
@@ -22,35 +22,29 @@ package org.apache.fop.fo.flow;
 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;
-
-    // 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
+    // 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
 
     /**
      * Base constructor
@@ -59,66 +53,35 @@ public class BidiOverride extends FObjMixed {
      */
     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 "word-spacing" trait */
+    public Property getWordSpacing() {
+        return wordSpacing;
+    }
+
+    /** @return the "direction" trait */
+    public int getDirection() {
+        return direction;
     }
 
-    /** @return the "line-height" property */
-    public SpaceProperty getLineHeight() {
-        return lineHeight;
+    /** @return the "unicodeBidi" trait */
+    public int getUnicodeBidi() {
+        return unicodeBidi;
     }
 
     /** {@inheritDoc} */
index 66e06db83b2f4b2ea242bfc5e2e7d027adb64970..d5fc901a3b785362f6e79c25a0961176359fe426 100644 (file)
@@ -52,7 +52,7 @@ public class Block extends FObjMixed implements BreakPropertySet, StructurePoint
     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,7 +89,10 @@ public class Block extends FObjMixed implements BreakPropertySet, StructurePoint
     //     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 @@ public class Block extends FObjMixed implements BreakPropertySet, StructurePoint
         return commonHyphenation;
     }
 
-    /** @return the "break-after" property. */
+    /** @return the "break-after" trait. */
     public int getBreakAfter() {
         return breakAfter;
     }
@@ -180,82 +183,82 @@ public class Block extends FObjMixed implements BreakPropertySet, StructurePoint
         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 @@ public class Block extends FObjMixed implements BreakPropertySet, StructurePoint
         }
     }
 
-    /** @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,17 +316,17 @@ public class Block extends FObjMixed implements BreakPropertySet, StructurePoint
         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;
     }
@@ -333,9 +336,9 @@ public class Block extends FObjMixed implements BreakPropertySet, StructurePoint
      * {@link org.apache.fop.fo.Constants#EN_TRUE},
      * {@link org.apache.fop.fo.Constants#EN_FALSE}
      */
-     public int getDisableColumnBalancing() {
-         return disableColumnBalancing;
-     }
+    public int getDisableColumnBalancing() {
+        return disableColumnBalancing;
+    }
 
 
     /** {@inheritDoc} */
@@ -343,15 +346,33 @@ public class Block extends FObjMixed implements BreakPropertySet, StructurePoint
         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 57cb6e5c6ad1029c9c7f5672c94c115ff3067ab8..1dc43fd3615a52e663ab8d193466e0748df6bb1e 100644 (file)
@@ -31,14 +31,18 @@ import org.apache.fop.fo.properties.CommonBorderPaddingBackground;
 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 @@ public class BlockContainer extends FObj implements BreakPropertySet {
     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 @@ public class BlockContainer extends FObj implements BreakPropertySet {
         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,58 +166,58 @@ public class BlockContainer extends FObj implements BreakPropertySet {
     }
 
     /**
-     * @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;
     }
@@ -226,10 +231,52 @@ public class BlockContainer extends FObj implements BreakPropertySet {
         return disableColumnBalancing;
     }
 
+    /**
+     * Obtain inline progression direction.
+     * @return the inline progression direction
+     */
+    public Direction getInlineProgressionDirection() {
+        return writingModeTraits.getInlineProgressionDirection();
+    }
+
+    /**
+     * Obtain block progression direction.
+     * @return the block progression direction
+     */
+    public Direction getBlockProgressionDirection() {
+        return writingModeTraits.getBlockProgressionDirection();
+    }
+
+    /**
+     * Obtain column progression direction.
+     * @return the column progression direction
+     */
+    public Direction getColumnProgressionDirection() {
+        return writingModeTraits.getColumnProgressionDirection();
+    }
 
-    /** @return the "writing-mode" property */
-    public int getWritingMode() {
-        return writingMode;
+    /**
+     * Obtain row progression direction.
+     * @return the row progression direction
+     */
+    public Direction getRowProgressionDirection() {
+        return writingModeTraits.getRowProgressionDirection();
+    }
+
+    /**
+     * Obtain (baseline) shift direction.
+     * @return the (baseline) shift direction
+     */
+    public Direction getShiftDirection() {
+        return writingModeTraits.getShiftDirection();
+    }
+
+    /**
+     * Obtain writing mode.
+     * @return the writing mode
+     */
+    public WritingMode getWritingMode() {
+        return writingModeTraits.getWritingMode();
     }
 
     /** {@inheritDoc} */
index 7328b564405d554a01ff93585cdd6552474fd76b..d01162a45493fec4580cfa0f42b8ce470838caa5 100644 (file)
@@ -79,6 +79,9 @@ public class Character extends FObj implements StructurePointerPropertySet {
     //     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 @@ public class Character extends FObj implements StructurePointerPropertySet {
         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 d2422a24b9514199d3791316d740c4200ac1fd4e..4d25960d920b1254636a0c87f57117382146f001 100644 (file)
@@ -33,6 +33,10 @@ import org.apache.fop.fo.properties.CommonMarginInline;
 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 @@ import org.apache.fop.fo.properties.SpaceProperty;
  */
 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 @@ public class InlineContainer extends FObj {
     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 @@ public class InlineContainer extends FObj {
     //     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 @@ public class InlineContainer extends FObj {
         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 @@ public class InlineContainer extends FObj {
         }
     }
 
-    /** @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,39 +161,82 @@ public class InlineContainer extends FObj {
         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;
+    /**
+     * Obtain inline progression direction.
+     * @return the inline progression direction
+     */
+    public Direction getInlineProgressionDirection() {
+        return writingModeTraits.getInlineProgressionDirection();
+    }
+
+    /**
+     * Obtain block progression direction.
+     * @return the block progression direction
+     */
+    public Direction getBlockProgressionDirection() {
+        return writingModeTraits.getBlockProgressionDirection();
+    }
+
+    /**
+     * Obtain column progression direction.
+     * @return the column progression direction
+     */
+    public Direction getColumnProgressionDirection() {
+        return writingModeTraits.getColumnProgressionDirection();
+    }
+
+    /**
+     * Obtain row progression direction.
+     * @return the row progression direction
+     */
+    public Direction getRowProgressionDirection() {
+        return writingModeTraits.getRowProgressionDirection();
+    }
+
+    /**
+     * Obtain (baseline) shift direction.
+     * @return the (baseline) shift direction
+     */
+    public Direction getShiftDirection() {
+        return writingModeTraits.getShiftDirection();
+    }
+
+    /**
+     * Obtain writing mode.
+     * @return the writing mode
+     */
+    public WritingMode getWritingMode() {
+        return writingModeTraits.getWritingMode();
     }
 
     /** {@inheritDoc} */
index 5410e1ca7ae0c9f0a7568feeaee23780845a5927..e71200d93641f64140167b842e2e94b71f5b41af 100644 (file)
@@ -37,7 +37,7 @@ import org.apache.fop.fo.properties.SpaceProperty;
  */
 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 @@ public abstract class InlineLevel extends FObjMixed {
     private KeepProperty keepWithNext;
     private KeepProperty keepWithPrevious;
     private SpaceProperty lineHeight;
-    // End of property values
+    // End of trait values
 
     /**
      * Base constructor
@@ -83,22 +83,22 @@ public abstract class InlineLevel extends FObjMixed {
         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 1b32e3f1e7c4dee64f7d21605874d3f51a4baa1a..6aad36ba98aa30a3eeb3050c8d911fca236f0d2a 100644 (file)
@@ -32,6 +32,7 @@ import org.apache.fop.fo.properties.LengthRangeProperty;
  * The following patterns are treated: rule, space, dots and use-content.
  * @asf.todo implement validateChildNode()
  */
+// [TBD] implement validateChildNode()
 public class Leader extends InlineLevel {
     // The value of properties relevant for fo:leader.
     // See also superclass InlineLevel
index 86196cb29ef22788ea4fc19db1c6ebfbc0c7d8e3..466a77d0c01dfc1113efa919ba99370b6af6f966 100644 (file)
@@ -45,7 +45,7 @@ import org.apache.fop.fo.properties.TableColLength;
  */
 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 @@ public class Table extends TableFObj implements ColumnNumberManagerHolder, Break
     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 @@ public class Table extends TableFObj implements ColumnNumberManagerHolder, Break
         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 @@ public class Table extends TableFObj implements ColumnNumberManagerHolder, Break
         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 @@ public class Table extends TableFObj implements ColumnNumberManagerHolder, Break
     }
 
     /**
-     * @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 @@ public class Table extends TableFObj implements ColumnNumberManagerHolder, Break
         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 @@ public class Table extends TableFObj implements ColumnNumberManagerHolder, Break
                 || !getKeepTogether().getWithinColumn().isAuto();
     }
 
-    /** @return the "border-collapse" property. */
+    /** @return the "border-collapse" FO trait. */
     public int getBorderCollapse() {
         return borderCollapse;
     }
@@ -495,17 +496,22 @@ public class Table extends TableFObj implements ColumnNumberManagerHolder, Break
         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 01ca5863e053a247a26855b12fd084a9b5747fa6..0ba147e7f253e602df4f7cad8a1368712bbcd846 100644 (file)
@@ -25,22 +25,28 @@ import java.util.Map;
 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 @@ public class PageSequence extends AbstractPageSequence {
         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");
         }
@@ -292,8 +299,8 @@ public class PageSequence extends AbstractPageSequence {
     }
 
     /**
-     * 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;
@@ -313,21 +320,99 @@ public class PageSequence extends AbstractPageSequence {
     }
 
     /**
-     * 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.
      */
index ea449cdff0927c29109162f6d4d0ed1e38dead1e..f3d7dcc66646bd21d010fe2d571a0fcb9d2a451c 100644 (file)
@@ -32,20 +32,21 @@ import org.apache.fop.fo.FObj;
 import org.apache.fop.fo.PropertyList;
 import org.apache.fop.fo.ValidationException;
 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;
 
@@ -67,7 +68,7 @@ public abstract class Region extends FObj {
         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("")) {
@@ -170,28 +171,28 @@ public abstract class Region extends FObj {
         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 841bff0d6f6a5fd5921274313eed99b892da8e29..3b9be033351414950482824fa43096d9b116926d 100644 (file)
@@ -23,6 +23,7 @@ package org.apache.fop.fo.pagination;
 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 @@ public class RegionAfter extends RegionBA {
         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 149e470cfb1abdd1cfa2497ddbd9bc7910017eb7..eac7723f5550241880d69d905b495c041f61430e 100644 (file)
@@ -26,6 +26,7 @@ import org.apache.fop.apps.FOPException;
 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 @@ public abstract class RegionBA extends SideRegion {
      * @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 @@ public abstract class RegionBA extends SideRegion {
         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 71ea26818d9eb74dbba58037b97d939eae9ac01a..2b0fb43f401620cceb7f295ecbf4d1f36cda8e42 100644 (file)
@@ -23,6 +23,7 @@ package org.apache.fop.fo.pagination;
 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 @@ public class RegionBefore extends RegionBA {
         }
         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 cb265706da356cdd5bedb2d4f2054aa834f33f18..a3d556f3cf206ea2d9c1cb7d68e12759ee14af6e 100644 (file)
@@ -28,9 +28,11 @@ import org.apache.fop.datatypes.Length;
 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">
@@ -113,12 +115,22 @@ public class RegionBody extends Region {
 
         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 8b348ed5d86def4c3efa1110c07a6d7e566ce75e..afeccb182da99f11126c7b6083379386ae2091bb 100644 (file)
@@ -23,6 +23,7 @@ package org.apache.fop.fo.pagination;
 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 @@ public class RegionEnd extends RegionSE {
         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 f106e8a366a580cf341e6796d59d4595edd65ff4..9fc2bdc2708da6f1b5e0bce04aece0429f52ad0d 100644 (file)
@@ -26,6 +26,7 @@ import org.apache.fop.apps.FOPException;
 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 @@ public abstract class RegionSE extends SideRegion {
      * @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 @@ public abstract class RegionSE extends SideRegion {
         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 afe9ddfe1f32891c5c2a0991f23bdd891d3225e6..a62744bae3982aaac281d6941317346a512c8fa0 100644 (file)
@@ -23,6 +23,7 @@ package org.apache.fop.fo.pagination;
 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 @@ public class RegionStart extends RegionSE {
         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 8c95e1b8a803829725c16871c82728bb552ea4e4..a144fa33fba054c5f446f6c41ac37ddef66b2d5b 100644 (file)
@@ -36,6 +36,7 @@ import org.apache.fop.fo.FObj;
 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 @@ import org.apache.fop.fo.properties.CommonMarginBlock;
  * 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 @@ public class SimplePageMaster extends FObj {
         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 @@ public class SimplePageMaster extends FObj {
         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 6183b9e56c9850307bb41998dac4a76b2232d631..b74c9dcfb9826a094d5824eac7c87eb2815c7b99 100644 (file)
@@ -35,6 +35,8 @@ public class CorrespondingPropertyMaker {
     protected int rltb;
     /** corresponding property for tb-rl writing mode */
     protected int tbrl;
+    /** corresponding property for tb-lr writing mode */
+    protected int tblr;
     /** user parent property list */
     protected boolean useParent;
     private boolean relative;
@@ -48,17 +50,26 @@ public class CorrespondingPropertyMaker {
         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
+     */
 
     /**
      * Set corresponding property identifiers.
      * @param lrtb the property that corresponds with lr-tb writing mode
      * @param rltb the property that corresponds with rl-tb writing mode
      * @param tbrl the property that corresponds with tb-lr writing mode
+     * @param tblr the property that corresponds with tb-lr writing mode
      */
-    public void setCorresponding(int lrtb, int rltb, int tbrl) {
+    public void setCorresponding(int lrtb, int rltb, int tbrl, int tblr) {
         this.lrtb = lrtb;
         this.rltb = rltb;
         this.tbrl = tbrl;
+        this.tblr = tblr;
     }
 
     /**
@@ -72,7 +83,7 @@ public class CorrespondingPropertyMaker {
 
     /**
      * Set relative flag.
-     * @param relative true if relative direction
+     * @param relative true if properties operate on a relative direction
      */
     public void setRelative(boolean relative) {
         this.relative = relative;
@@ -102,7 +113,7 @@ public class CorrespondingPropertyMaker {
 
         PropertyList pList = getWMPropertyList(propertyList);
         if (pList != null) {
-            int correspondingId = pList.getWritingMode(lrtb, rltb, tbrl);
+            int correspondingId = pList.selectFromWritingMode(lrtb, rltb, tbrl, tblr);
 
             if (pList.getExplicit(correspondingId) != null) {
                 return true;
@@ -126,7 +137,7 @@ public class CorrespondingPropertyMaker {
         if (pList == null) {
             return null;
         }
-        int correspondingId = pList.getWritingMode(lrtb, rltb, tbrl);
+        int correspondingId = pList.selectFromWritingMode(lrtb, rltb, tbrl, tblr);
 
         Property p = propertyList.getExplicitOrShorthand(correspondingId);
         if (p != null) {
index df8583089b2d255daee85db00bad24cca0b417bc..e0f02523a57036704c547684f3de8628446d0d41 100644 (file)
@@ -30,11 +30,11 @@ import org.apache.fop.fo.expr.PropertyException;
  * Window - Preferences - Java - Code Generation - Code and Comments
  */
 public class DimensionPropertyMaker extends CorrespondingPropertyMaker {
-    
-    private int[][] extraCorresponding = null;
+
+    private int[][] extraCorresponding;
 
     /**
-     * Construct a dimension property maker.
+     * Instantiate a dimension property maker.
      * @param baseMaker the base property maker
      */
     public DimensionPropertyMaker(PropertyMaker baseMaker) {
@@ -43,9 +43,18 @@ public class DimensionPropertyMaker extends CorrespondingPropertyMaker {
 
     /**
      * Set extra correspondences.
-     * @param extraCorresponding the 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;
     }
 
@@ -76,18 +85,20 @@ public class DimensionPropertyMaker extends CorrespondingPropertyMaker {
         }
 
         // 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 19c4675ed1482d06c4cf7c36f91a963c918f6603..6b7c58a62f54f504a40278081d1cc0a3a48289ae 100644 (file)
@@ -55,6 +55,9 @@ public class IndentPropertyMaker extends CorrespondingPropertyMaker {
      * @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 @@ public class IndentPropertyMaker extends CorrespondingPropertyMaker {
      * @param borderWidthCorresponding the corresping propids.
      */
     public void setBorderWidthCorresponding(int[] borderWidthCorresponding) {
+        if ( ( borderWidthCorresponding == null ) || ( borderWidthCorresponding.length != 4 ) ) {
+            throw new IllegalArgumentException();
+        }
         this.borderWidthCorresponding = borderWidthCorresponding;
     }
 
@@ -99,7 +105,7 @@ public class IndentPropertyMaker extends CorrespondingPropertyMaker {
         Numeric padding = getCorresponding(paddingCorresponding, propertyList).getNumeric();
         Numeric border = getCorresponding(borderWidthCorresponding, propertyList).getNumeric();
 
-        int marginProp = pList.getWritingMode(lrtb, rltb, tbrl);
+        int marginProp = pList.selectFromWritingMode(lrtb, rltb, tbrl, tblr);
         // Calculate the absolute margin.
         if (propertyList.getExplicitOrShorthand(marginProp) == null) {
             Property indent = propertyList.getExplicit(baseMaker.propId);
@@ -158,7 +164,7 @@ public class IndentPropertyMaker extends CorrespondingPropertyMaker {
         Numeric padding = getCorresponding(paddingCorresponding, propertyList).getNumeric();
         Numeric border = getCorresponding(borderWidthCorresponding, propertyList).getNumeric();
 
-        int marginProp = pList.getWritingMode(lrtb, rltb, tbrl);
+        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.
@@ -208,7 +214,8 @@ public class IndentPropertyMaker extends CorrespondingPropertyMaker {
                 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;
diff --git a/src/java/org/apache/fop/fonts/ArabicScriptProcessor.java b/src/java/org/apache/fop/fonts/ArabicScriptProcessor.java
new file mode 100644 (file)
index 0000000..1e10147
--- /dev/null
@@ -0,0 +1,527 @@
+/*
+ * 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 = {
+        0x0621, // HAMZA
+        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 = {
+        0x0621  // HAMZA
+    };
+
+    private static boolean hasIsolateFinal ( int ch ) {
+        return Arrays.binarySearch ( isolatedFinals, ch ) >= 0;
+    }
+
+}
index 4cf24ae1639a3dd288b4bb834424d0db7d0a0a64..a018bcbbbdba41c8d3b9734b7dd9b5268d586678 100644 (file)
@@ -58,6 +58,7 @@ public abstract class CustomFont extends Typeface
     private Map kerning;
 
     private boolean useKerning = true;
+    private boolean useAdvanced = true;
 
     /** {@inheritDoc} */
     public String getFontName() {
@@ -283,6 +284,15 @@ public abstract class CustomFont extends Typeface
         }
     }
 
+    /**
+     * 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} */
@@ -425,6 +435,13 @@ public abstract class CustomFont extends Typeface
         this.useKerning = enabled;
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    public void setAdvancedEnabled(boolean enabled) {
+        this.useAdvanced = enabled;
+    }
+
     /**
      * Sets the font resolver. Needed for URI resolution.
      * @param resolver the font resolver
diff --git a/src/java/org/apache/fop/fonts/DefaultScriptProcessor.java b/src/java/org/apache/fop/fonts/DefaultScriptProcessor.java
new file mode 100644 (file)
index 0000000..d3ef159
--- /dev/null
@@ -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;
+    }
+
+}
diff --git a/src/java/org/apache/fop/fonts/DiscontinuousAssociationException.java b/src/java/org/apache/fop/fonts/DiscontinuousAssociationException.java
new file mode 100644 (file)
index 0000000..e13ecdf
--- /dev/null
@@ -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 aa464c21d37af2c02dd7caf6c814b9eb54a2568a..37b348e880aa3a09898d48dac8b03d1242115c0d 100644 (file)
@@ -37,6 +37,8 @@ public class EmbedFontInfo implements Serializable {
     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 @@ public class EmbedFontInfo implements Serializable {
 
     /**
      * 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,12 +89,20 @@ public class EmbedFontInfo implements Serializable {
 
     /**
      * 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.
@@ -173,6 +185,7 @@ public class EmbedFontInfo implements Serializable {
     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 d0a87efbfb6756ef3324d3f43982bd09d4cce57c..988a9b14b5a1939e109aff8f879c8b85a049c1e9 100644 (file)
@@ -25,11 +25,13 @@ import org.apache.commons.logging.Log;
 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.
  */
-public class Font {
+public class Font implements Substitutable, Positionable {
 
     /** Extra Bold font weight */
     public static final int WEIGHT_EXTRA_BOLD = 800;
@@ -240,8 +242,8 @@ public class Font {
      * {@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 @@ public class Font {
         sbuf.append(fontStyle);
         sbuf.append(',');
         sbuf.append(fontWeight);*/
-        sbuf.append(')');
+        sbuf.append('}');
         return sbuf.toString();
     }
 
@@ -346,6 +348,44 @@ public class Font {
         return width;
     }
 
-}
+    /** {@inheritDoc} */
+    public boolean performsSubstitution() {
+        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 performsPositioning() {
+        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 0b71e629440f6fcebad5bc349764f986b85249a4..29470a2a98c329525eb78a3f773153e15fd0d4f5 100644 (file)
@@ -77,7 +77,7 @@ public final class FontCache implements Serializable {
      * mapping of font url -> file modified date (for all fonts that have failed
      * to load)
      */
-    private Map failedFontMap/* <String, Long> */= null;
+    private Map failedFontMap/* <String, Long>*/ = null;
 
     /**
      * Default constructor
index f8399b11074a49015f8e350c7cf0c9db2bb4748e..42aedd26b7cb12f48d54c3d370f184a03ea57275 100644 (file)
@@ -254,20 +254,26 @@ public class FontInfoConfigurator {
         }
 
         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 93d4ae03f280217b9093bd8b1d526525027e8ebd..0256fc319a340c642867106e96653fef3516c746 100644 (file)
@@ -43,31 +43,36 @@ public abstract class FontLoader {
     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 @@ public abstract class FontLoader {
             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 @@ public abstract class FontLoader {
      * @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 @@ public abstract class FontLoader {
             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 16da99baaba5a240119e63b46ab9ae5d18a0550a..73eefaa5824a666e7aa82dac6fb63fa53d08145b 100644 (file)
@@ -31,12 +31,15 @@ import org.xml.sax.Attributes;
 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 @@ public class FontReader extends DefaultHandler {
 
     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;
 
@@ -113,6 +141,14 @@ public class FontReader extends DefaultHandler {
         returnFont.setKerningEnabled(enabled);
     }
 
+    /**
+     * 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 class FontReader extends DefaultHandler {
      */
     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 @@ public class FontReader extends DefaultHandler {
         } 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 class FontReader extends DefaultHandler {
      */
     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,376 @@ public class FontReader extends DefaultHandler {
     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 = parseLigatureComponents ( content );
+            if ( ligComponents != 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                              // CSOK: ParameterNumber
+        ( 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" );
+        if ( ( csa == null ) || ( csa.length == 0 ) ) {
+            throw new SAXParseException ( "invalid <lig/> element, must specify at least one component", locator );
+        } else {
+            int nc = csa.length;
+            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;
+        }
+    }
+
 }
 
 
diff --git a/src/java/org/apache/fop/fonts/GlyphContextTester.java b/src/java/org/apache/fop/fonts/GlyphContextTester.java
new file mode 100644 (file)
index 0000000..ab69894
--- /dev/null
@@ -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 );
+
+}
diff --git a/src/java/org/apache/fop/fonts/GlyphCoverageTable.java b/src/java/org/apache/fop/fonts/GlyphCoverageTable.java
new file mode 100644 (file)
index 0000000..88c3db8
--- /dev/null
@@ -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;
+        }
+
+    }
+
+}
diff --git a/src/java/org/apache/fop/fonts/GlyphPositioning.java b/src/java/org/apache/fop/fonts/GlyphPositioning.java
new file mode 100644 (file)
index 0000000..1896b21
--- /dev/null
@@ -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 );
+
+}
diff --git a/src/java/org/apache/fop/fonts/GlyphPositioningSubtable.java b/src/java/org/apache/fop/fonts/GlyphPositioningSubtable.java
new file mode 100644 (file)
index 0000000..c8656a3
--- /dev/null
@@ -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;
+        }
+    }
+
+}
diff --git a/src/java/org/apache/fop/fonts/GlyphPositioningTable.java b/src/java/org/apache/fop/fonts/GlyphPositioningTable.java
new file mode 100644 (file)
index 0000000..85b311c
--- /dev/null
@@ -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;
+    }
+
+}
diff --git a/src/java/org/apache/fop/fonts/GlyphSequence.java b/src/java/org/apache/fop/fonts/GlyphSequence.java
new file mode 100644 (file)
index 0000000..e8430f2
--- /dev/null
@@ -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();
+        }
+
+    }
+}
diff --git a/src/java/org/apache/fop/fonts/GlyphSubstitution.java b/src/java/org/apache/fop/fonts/GlyphSubstitution.java
new file mode 100644 (file)
index 0000000..652367c
--- /dev/null
@@ -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 );
+
+}
diff --git a/src/java/org/apache/fop/fonts/GlyphSubstitutionSubtable.java b/src/java/org/apache/fop/fonts/GlyphSubstitutionSubtable.java
new file mode 100644 (file)
index 0000000..dcdafed
--- /dev/null
@@ -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;
+        }
+    }
+
+}
diff --git a/src/java/org/apache/fop/fonts/GlyphSubstitutionTable.java b/src/java/org/apache/fop/fonts/GlyphSubstitutionTable.java
new file mode 100644 (file)
index 0000000..6573679
--- /dev/null
@@ -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();
+        }
+
+    }
+
+}
diff --git a/src/java/org/apache/fop/fonts/GlyphSubtable.java b/src/java/org/apache/fop/fonts/GlyphSubtable.java
new file mode 100644 (file)
index 0000000..830bad7
--- /dev/null
@@ -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 );
+    }
+
+}
diff --git a/src/java/org/apache/fop/fonts/GlyphTable.java b/src/java/org/apache/fop/fonts/GlyphTable.java
new file mode 100644 (file)
index 0000000..cd656f0
--- /dev/null
@@ -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();
+        }
+
+    }
+
+}
diff --git a/src/java/org/apache/fop/fonts/GlyphUtils.java b/src/java/org/apache/fop/fonts/GlyphUtils.java
new file mode 100644 (file)
index 0000000..e4e22d6
--- /dev/null
@@ -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();
+    }
+
+}
diff --git a/src/java/org/apache/fop/fonts/IncompatibleSubtableException.java b/src/java/org/apache/fop/fonts/IncompatibleSubtableException.java
new file mode 100644 (file)
index 0000000..ebc35ce
--- /dev/null
@@ -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 e5d111d382e86ec8680af80a87b66b80ce6d5067..e58f919b0daa9fa50a489cace0a9086879f66747 100644 (file)
@@ -37,22 +37,23 @@ import org.apache.fop.apps.FOPException;
 /**
  * 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 @@ public class LazyFont extends Typeface implements FontDescriptor {
         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 @@ public class LazyFont extends Typeface implements FontDescriptor {
 
     /** {@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 @@ public class LazyFont extends Typeface implements FontDescriptor {
                                     new URL(metricsFileName).openStream()));
                     }
                     reader.setKerningEnabled(useKerning);
+                    reader.setAdvancedEnabled(useAdvanced);
                     if (this.embedded) {
                         reader.setFontEmbedPath(fontEmbedPath);
                     }
@@ -132,7 +141,7 @@ public class LazyFont extends Typeface implements FontDescriptor {
                         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 @@ public class LazyFont extends Typeface implements FontDescriptor {
         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 b3b5d8639a44023d03a1cadfd21dc7789665f704..0f2488dc40a1ad95a51e8fcc38b2a55bf7265a86 100644 (file)
@@ -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 @@ import java.util.Map;
 /**
  * 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 @@ public class MultiByteFont extends CIDFont {
     /** 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 @@ public class MultiByteFont extends CIDFont {
      * @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 @@ public class MultiByteFont extends CIDFont {
         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 @@ public class MultiByteFont extends CIDFont {
         }
         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 a5acf51b3f9cbfe7db5b9f30d5b4984e0341cf08..7517932e9a10a69dcb74cd011cc70342e4f273e9 100644 (file)
@@ -132,6 +132,12 @@ public interface MutableFont {
      */
     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
diff --git a/src/java/org/apache/fop/fonts/Positionable.java b/src/java/org/apache/fop/fonts/Positionable.java
new file mode 100644 (file)
index 0000000..7d76c15
--- /dev/null
@@ -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 );
+
+}
diff --git a/src/java/org/apache/fop/fonts/ScriptProcessor.java b/src/java/org/apache/fop/fonts/ScriptProcessor.java
new file mode 100644 (file)
index 0000000..c5978f1
--- /dev/null
@@ -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;
+    }
+
+}
diff --git a/src/java/org/apache/fop/fonts/Substitutable.java b/src/java/org/apache/fop/fonts/Substitutable.java
new file mode 100644 (file)
index 0000000..7303085
--- /dev/null
@@ -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 f4e317de3592f0dec26941988d9d5c0bca068bfc..cf625ebb1d202c671a954362272cd8328e014adf 100644 (file)
@@ -142,6 +142,10 @@ public abstract class Typeface implements FontMetrics {
     
     /** {@inheritDoc} */
     public String toString() {
-        return getFullName();
+        StringBuffer sbuf = new StringBuffer(super.toString());
+        sbuf.append('{');
+        sbuf.append(getFullName());
+        sbuf.append('}');
+        return sbuf.toString();
     }   
 }
index 6e64f914464d16e166b9819e3de0e9fa30a8b991..7ff435392efa5a2edfcf1282ea59710a85a66c4e 100644 (file)
@@ -21,6 +21,7 @@ package org.apache.fop.fonts.apps;
 
 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 javax.xml.parsers.DocumentBuilderFactory;
 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.w3c.dom.Element;
 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 @@ public class TTFReader extends AbstractFontReader {
 
         generateDOM4Kerning(root, ttf, isCid);
 
+        generateDOM4ScriptExtensions(root, ttf, isCid);
+
         return doc;
     }
 
@@ -463,6 +474,287 @@ public class TTFReader extends AbstractFontReader {
         }
     }
 
+    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
index 9f0fa9fd6c9b435a7e67c4531a3f089d48aecf29..90f3880435bfd4528a3a007264e153d354f80867 100644 (file)
@@ -151,7 +151,7 @@ public class FontInfoFinder {
             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 @@ public class FontInfoFinder {
                 }
                 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 a049aaf04676acfa7a46c4d150f0dfaba9c765ce..641ad5a679f0501744707e4336b128bc5d2ddbee 100644 (file)
@@ -31,7 +31,21 @@ import org.apache.commons.logging.LogFactory;
 
 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.
@@ -122,6 +136,16 @@ public class TTFFile {
 
     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
      */
@@ -559,6 +583,8 @@ public class TTFFile {
         // print_max_min();
 
         readKerning(in);
+        readGSUB(in);
+        readGPOS(in);
         guessVerticalMetricsFromGlyphBBox();
         return true;
     }
@@ -1491,6 +1517,1511 @@ public class TTFFile {
         }
     }
 
+    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
@@ -1715,4 +3246,4 @@ public class TTFFile {
             ioe.printStackTrace(System.err);
         }
     }
-}
\ No newline at end of file
+}
index 405a25f9e88cf9726e9e86245993fb0a2c736645..c11a0e4cfff09c75cd7f565b8b1a9dd6f29f6467 100644 (file)
@@ -55,7 +55,7 @@ public class TTFFontLoader extends FontLoader {
      * @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 @@ public class TTFFontLoader extends FontLoader {
      * @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 @@ public class TTFFontLoader extends FontLoader {
         if (useKerning) {
             copyKerning(ttf, isCid);
         }
+        if (useAdvanced) {
+            copyAdvanced(ttf);
+        }
         if (this.embedded && ttf.isEmbeddable()) {
             returnFont.setEmbedFileName(this.fontFileURI);
         }
@@ -224,4 +228,16 @@ public class TTFFontLoader extends FontLoader {
             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 d5036de7ba4c7962a4f893d32a25ca391adddce2..1b0b2b218897d6af8ff8f5e66bebf4bd111b9b24 100644 (file)
@@ -52,7 +52,7 @@ public class Type1FontLoader extends FontLoader {
      */
     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 ae230e261a01d0af32c225e0ec64e738f954f38c..7af7f3e73b22b47cfff3cd48c91def1d10991133 100644 (file)
@@ -460,7 +460,7 @@ public abstract class AbstractLayoutManager extends AbstractBaseLayoutManager
 
     /** {@inheritDoc} */
     public String toString() {
-        return (super.toString() + (fobj != null ? "[fobj=" + fobj.toString() + "]" : ""));
+        return (super.toString() + (fobj != null ? "{fobj = " + fobj.toString() + "}" : ""));
     }
 
     /** {@inheritDoc} */
diff --git a/src/java/org/apache/fop/layoutmgr/BidiUtil.java b/src/java/org/apache/fop/layoutmgr/BidiUtil.java
new file mode 100644 (file)
index 0000000..9114ce1
--- /dev/null
@@ -0,0 +1,1551 @@
+/*
+ * 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;
+            }
+            assert runs.size() < 2 : "heterogeneous inlines not yet supported!!";
+            return runs;
+        }
+        public void updateMinMax ( int[] mm ) {
+            if ( minLevel < mm[0] ) {
+                mm[0] = minLevel;
+            }
+            if ( maxLevel > mm[1] ) {
+                mm[1] = maxLevel;
+            }
+        }
+        public boolean maybeNeedsMirroring() {
+            return ( minLevel == maxLevel ) && ( ( minLevel & 1 ) != 0 );
+        }
+        public void reverse() {
+            reversals++;
+        }
+        public void maybeReverseWord ( boolean mirror ) {
+            if ( inline instanceof WordArea ) {
+                WordArea w = (WordArea) inline;
+                if ( ( reversals & 1 ) != 0 ) {
+                    w.reverse ( mirror );
+                } else if ( mirror && maybeNeedsMirroring() ) {
+                    w.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 62e89bdb0af7b1d80330699894668d30f21a01d1..47af209584db7840afd7f902b53a67564d3acc4e 100644 (file)
@@ -421,6 +421,8 @@ public class BlockLayoutManager extends BlockStackingLayoutManager
 
             curBlockArea.setIPD(super.getContentAreaIPD());
 
+            curBlockArea.setBidiLevel ( getBlockFO().getBidiLevel() );
+
             TraitSetter.addBreaks(curBlockArea,
                     getBlockFO().getBreakBefore(), getBlockFO().getBreakAfter());
 
@@ -563,4 +565,3 @@ public class BlockLayoutManager extends BlockStackingLayoutManager
     }
 
 }
-
index b38874606348aa50527df269f556bdbf5e6ff79d..c6842dbf04ebd05dff5005eab9f7292b0bdf8477 100644 (file)
@@ -51,6 +51,7 @@ import org.apache.fop.datatypes.URISpecification;
 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 @@ public class ExternalDocumentLayoutManager extends AbstractPageSequenceLayoutMan
         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 @@ public class ExternalDocumentLayoutManager extends AbstractPageSequenceLayoutMan
             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 18059c5caa85a4ee2e3f740a83e20b1aa1e0d563..a2512c50ba0a391b0b5485c949cc6877345ba161 100644 (file)
@@ -26,6 +26,7 @@ import org.apache.fop.fo.Constants;
 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;
 
 
 /**
@@ -92,7 +93,7 @@ public class LayoutContext {
     //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;
@@ -564,7 +565,7 @@ public class LayoutContext {
      * Get the writing mode of the relevant reference area.
      * @return the applicable writing mode
      */
-    public int getWritingMode() {
+    public WritingMode getWritingMode() {
         return writingMode;
     }
 
@@ -572,7 +573,7 @@ public class LayoutContext {
      * Set the writing mode.
      * @param writingMode the writing mode
      */
-    public void setWritingMode(int writingMode) {
+    public void setWritingMode(WritingMode writingMode) {
         this.writingMode = writingMode;
     }
 
index 2acf67c1f72012942f8b9016b4429e4f1cfba57a..76a1cb9e45837f8857c9156fbca795ee32406e43 100644 (file)
@@ -67,6 +67,7 @@ import org.apache.fop.fo.pagination.SideRegion;
 import org.apache.fop.fo.pagination.StaticContent;
 import org.apache.fop.fo.pagination.Title;
 import org.apache.fop.layoutmgr.inline.BasicLinkLayoutManager;
+import org.apache.fop.layoutmgr.inline.BidiLayoutManager;
 import org.apache.fop.layoutmgr.inline.CharacterLayoutManager;
 import org.apache.fop.layoutmgr.inline.ContentLayoutManager;
 import org.apache.fop.layoutmgr.inline.ExternalGraphicLayoutManager;
@@ -246,28 +247,9 @@ public class LayoutManagerMapping implements LayoutManagerMaker {
     public static class BidiOverrideLayoutManagerMaker extends Maker {
         /** {@inheritDoc} */
         public void make(FONode node, List lms) {
-            /* [GA] remove broken code
-            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
-                            ((BidiOverride) node, (InlineLayoutManager) lm);
-                        lms.add(blm);
-                    } else {
-                        lms.add(lm);
-                    }
-                }
+            if ( node instanceof BidiOverride ) {
+                lms.add(new BidiLayoutManager((BidiOverride) node));
             }
-            */
         }
     }
 
index 67c41ccf03b37b44286fc68c1729bbe4780e32ba..6689ee1495f79ef7ae2e2df433e8594ed10c23f9 100644 (file)
@@ -77,8 +77,10 @@ public class PageSequenceLayoutManager extends AbstractPageSequenceLayoutManager
     public void activateLayout() {
         initialize();
 
-        LineArea title = null;
+        // 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 fd4d803af29e36a93e513b4c9f21535cf6f4af6a..969c4a25b55b7d97cdfc286745b65cbd33c07e6f 100644 (file)
@@ -91,7 +91,7 @@ public abstract class AbstractGraphicsLayoutManager extends LeafNodeLayoutManage
         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 07d153eed48477cbec22f61e5d7f41ddf8a9d32d..c2f5f8e62b931b901ea263fd2804ca5ed1d9bf18 100644 (file)
@@ -24,6 +24,7 @@ import org.apache.fop.datatypes.LengthBase;
 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 @@ public class AlignmentContext implements Constants {
      * @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();
@@ -301,7 +302,7 @@ public class AlignmentContext implements Constants {
      * Return the writing mode.
      * @return the writing mode
      */
-    public int getWritingMode() {
+    public WritingMode getWritingMode() {
         return scaledBaselineTable.getWritingMode();
     }
 
@@ -514,7 +515,7 @@ public class AlignmentContext implements Constants {
     }
 
     private boolean isHorizontalWritingMode() {
-        return (getWritingMode() == EN_LR_TB || getWritingMode() == EN_RL_TB);
+        return (getWritingMode() == WritingMode.LR_TB || getWritingMode() == WritingMode.RL_TB);
     }
 
     /** {@inheritDoc} */
index 467f2e154977dddf143faa1b2c07573ae40cdf2a..1a40398f5e6f1c9f0931c71cb45ffbc176d439e4 100644 (file)
@@ -23,6 +23,7 @@ import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
 import org.apache.fop.fo.Constants;
+import org.apache.fop.traits.WritingMode;
 
 
 /**
@@ -38,7 +39,7 @@ public class BasicScaledBaselineTable implements ScaledBaselineTable, Constants
     private int depth;
     private int xHeight;
     private int dominantBaselineIdentifier;
-    private int writingMode;
+    private WritingMode writingMode;
     private int dominantBaselineOffset;
     private int beforeEdgeOffset;
     private int afterEdgeOffset;
@@ -60,7 +61,7 @@ public class BasicScaledBaselineTable implements ScaledBaselineTable, Constants
                                     , int depth
                                     , int xHeight
                                     , int dominantBaselineIdentifier
-                                    , int writingMode) {
+                                    , WritingMode writingMode) {
         this.altitude = altitude;
         this.depth = depth;
         this.xHeight = xHeight;
@@ -83,7 +84,7 @@ public class BasicScaledBaselineTable implements ScaledBaselineTable, Constants
      * Return the writing mode for this baseline table.
      * @return the writing mode
      */
-    public int getWritingMode() {
+    public WritingMode getWritingMode() {
         return this.writingMode;
     }
 
@@ -137,7 +138,7 @@ public class BasicScaledBaselineTable implements ScaledBaselineTable, Constants
     }
 
     private boolean isHorizontalWritingMode() {
-        return writingMode == EN_LR_TB || writingMode == EN_RL_TB;
+        return writingMode == WritingMode.LR_TB || writingMode == WritingMode.RL_TB;
     }
 
     /**
index f342399fcb5db0572b22ef419a2edd58573c1717..b6145f02521e80157e1a100b61044b987754d8f5 100644 (file)
 
 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 {
-
-    private List children;
+public class BidiLayoutManager extends InlineLayoutManager {
 
     /**
      * Construct bidi layout manager.
-     * @param node bidi override FO
-     * @param cLM parent layout manager
+     * @param node an BidiOverride FONode
      */
-    public BidiLayoutManager(BidiOverride node, InlineLayoutManager cLM) {
+    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);
-        }
-*/
-    }
-
-    /** @return number of children */
-    public int size() {
-        return children.size();
-    }
-
-    /**
-     * @param index of child inline area
-     * @return a child inline area
-     */
-    public InlineArea get(int index) {
-        return (InlineArea) children.get(index);
     }
 
 }
index ed22886a4100a0c48cdc9424e0aaa493a5f921ef..7e47a712f0d07f2483a29813e8d7e45fd4c1a1c3 100644 (file)
@@ -76,13 +76,17 @@ public class CharacterLayoutManager extends LeafNodeLayoutManager {
     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());
@@ -222,4 +226,3 @@ public class CharacterLayoutManager extends LeafNodeLayoutManager {
     }
 
 }
-
index 82285d1062a78b35efe6e807c40bab042d400d9d..f0bc2d817ed270743c3ea6fa0d8424cda549136d 100644 (file)
@@ -201,7 +201,7 @@ public class InlineLayoutManager extends InlineStackingLayoutManager {
         InlineArea area;
         if (hasInlineParent) {
             area = new InlineParent();
-            area.setOffset(0);
+            area.setBlockProgressionOffset(0);
         } else {
             area = new InlineBlockParent();
         }
@@ -458,12 +458,12 @@ public class InlineLayoutManager extends InlineStackingLayoutManager {
                                         || 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 c5f38134bb4f47c25bf42d01c9b1360574e26964..2a58b20186bb24fb0b830672beba0db6fbb243ca 100644 (file)
@@ -344,4 +344,10 @@ public class LeaderLayoutManager extends LeafNodeLayoutManager {
         this.contentAreaIPD = contentAreaIPD;
     }
 
+    /** {@inheritDoc} */
+    public void reset() {
+        childLMs.clear();
+        super.reset();
+    }
+
 }
index f5a0a51f89b25d8cc0412edf043d48e7b98caafa..67a7b6c86244642ee74f6f4984ae2c7901653141 100644 (file)
@@ -225,7 +225,7 @@ public abstract class LeafNodeLayoutManager extends AbstractLayoutManager
      * @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 7e87de24163f057198c2c7735ee5f8ab21117cef..80d7f32e5834e05b47f852a57a30514665a461ea 100644 (file)
@@ -44,6 +44,7 @@ import org.apache.fop.fonts.FontTriplet;
 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 @@ public class LineLayoutManager extends InlineStackingLayoutManager
 
     /** {@inheritDoc} */
     public void initialize() {
+        bidiLevel = fobj.getBidiLevel();
         textAlignment = fobj.getTextAlign();
         textAlignmentLast = fobj.getTextAlignLast();
         textIndent = fobj.getTextIndent();
@@ -155,6 +157,7 @@ public class LineLayoutManager extends InlineStackingLayoutManager
     }
 
 
+    private int bidiLevel = -1;
     private int textAlignment = EN_JUSTIFY;
     private int textAlignmentLast;
     private int effectiveAlignment;
@@ -1487,6 +1490,7 @@ public class LineLayoutManager extends InlineStackingLayoutManager
         }
         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);
@@ -1595,7 +1599,10 @@ public class LineLayoutManager extends InlineStackingLayoutManager
                 && (!context.isLastArea() || !isLastPosition)) {
             lineArea.setBPD(lineArea.getBPD() + context.getSpaceAfter());
         }
-        lineArea.finalise();
+        lineArea.finish();
+        if ( lineArea.getBidiLevel() >= 0 ) {
+            BidiUtil.reorder ( lineArea );
+        }
         parentLayoutManager.addChildArea(lineArea);
     }
 
@@ -1645,6 +1652,9 @@ public class LineLayoutManager extends InlineStackingLayoutManager
             blocklc.setTrailingSpace(new SpaceSpecifier(false));
         }
         lineArea.updateExtentsFromChildren();
+        if ( lineArea.getBidiLevel() >= 0 ) {
+            BidiUtil.reorder ( lineArea );
+        }
         parentLayoutManager.addChildArea(lineArea);
     }
 
@@ -1679,4 +1689,3 @@ public class LineLayoutManager extends InlineStackingLayoutManager
     }
 
 }
-
index d8cfe6cda229cc0e000f95e91f64e7c2067fc08d..4fd9d7ca71f6cce3fa31f218fb428f6a84a1d627 100644 (file)
@@ -101,7 +101,7 @@ public class PageNumberLayoutManager extends LeafNodeLayoutManager {
         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 19a1a1f8a2c8cde7ba88278521511f3b3fc1de9f..21daee907ecb4a4507c8fc2c1dc920e46245974f 100644 (file)
@@ -19,6 +19,8 @@
 
 package org.apache.fop.layoutmgr.inline;
 
+import org.apache.fop.traits.WritingMode;
+
 /**
  * The FOP specific incarnation of the XSL-FO scaled baseline table.
  * All baseline tables are scaled to the font size of the font they
@@ -38,7 +40,7 @@ public interface ScaledBaselineTable {
      * Return the writing mode for this aligment context.
      * @return the writing mode
      */
-    int getWritingMode();
+    WritingMode getWritingMode();
 
     /**
      * Return the offset measured from the dominant
index 0d48ccbfe253c06899e4e7109301debdb83c1702..defa1ff49372002dee07713d043117d8b42ad41e 100644 (file)
@@ -21,7 +21,7 @@ package org.apache.fop.layoutmgr.inline;
 
 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.
@@ -38,12 +38,12 @@ public final class ScaledBaselineTableFactory implements Constants {
      * 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);
     }
@@ -52,10 +52,11 @@ public final class ScaledBaselineTableFactory implements Constants {
      * 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);
     }
 
@@ -65,12 +66,12 @@ public final class ScaledBaselineTableFactory implements Constants {
      * 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 4c45fbe660ff33105a63988542f2f23efc23a5a5..35a944e17714d1b8a84cfed7dc03fd9ec3a5fa0d 100644 (file)
@@ -32,6 +32,7 @@ import org.apache.fop.area.inline.TextArea;
 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,11 +81,12 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
         private final boolean isSpace;
         private boolean breakOppAfter;
         private final Font font;
+        private final int level;
 
         AreaInfo                                                // CSOK: ParameterNumber
             (int startIndex, int breakIndex, int wordSpaceCount, int letterSpaceCount,
              MinOptMax areaIPD, boolean isHyphenated, boolean isSpace, boolean breakOppAfter,
-             Font font) {
+             Font font, int level) {
             assert startIndex <= breakIndex;
             this.startIndex = startIndex;
             this.breakIndex = breakIndex;
@@ -95,6 +97,7 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
             this.isSpace = isSpace;
             this.breakOppAfter = breakOppAfter;
             this.font = font;
+            this.level = level;
         }
 
         private int getCharLength() {
@@ -106,16 +109,16 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
         }
 
         public String toString() {
-            return "AreaInfo["
-                    + "letterSpaceCount = " + letterSpaceCount
-                    + ", wordSpaceCount = " + wordSpaceCount
+            return super.toString() + "{"
+                    + "interval = [" + startIndex + "," + breakIndex + "]"
+                    + ", isSpace = " + isSpace
+                    + ", level = " + level
                     + ", areaIPD = " + areaIPD
-                    + ", startIndex = " + startIndex
-                    + ", breakIndex = " + breakIndex
+                    + ", letterSpaceCount = " + letterSpaceCount
+                    + ", wordSpaceCount = " + wordSpaceCount
                     + ", isHyphenated = " + isHyphenated
-                    + ", isSpace = " + isSpace
                     + ", font = " + font
-                    + "]";
+                    + "}";
         }
     }
 
@@ -270,7 +273,9 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
             }
             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,
@@ -292,6 +297,7 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
             addAreaInfoAreas(lastAreaInfo, wordSpaceCount, letterSpaceCount, firstAreaInfoIndex,
                     lastAreaInfoIndex, realWidth, context);
         }
+
     }
 
     private void addAreaInfoAreas(AreaInfo areaInfo, int wordSpaceCount, int letterSpaceCount,
@@ -401,6 +407,9 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
         private StringBuffer wordChars;
         private int[] letterAdjust;
         private int letterAdjustIndex;
+        private int[] wordLevels;
+        private int wordLevelsCount;
+        private int wordIPD;
 
         private TextArea textArea;
 
@@ -433,7 +442,7 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
             calcBlockProgressionDimension();
             setBlockProgressionDimension();
             setBaselineOffset();
-            setOffset();
+            setBlockProgressionOffset();
             setText();
             TraitSetter.addFontTraits(textArea, font);
             textArea.addTrait(Trait.COLOR, foText.getColor());
@@ -472,11 +481,11 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
             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());
             }
         }
 
@@ -511,6 +520,7 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
         }
 
         private void addWord(int startIndex, int endIndex, int charLength) {
+            int blockProgressionOffset = 0;
             if (isHyphenated(endIndex)) {
                 charLength++;
             }
@@ -523,13 +533,17 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
             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) {
@@ -541,8 +555,31 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
         }
 
         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;
             }
         }
 
@@ -567,13 +604,32 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
          * 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 );
                 }
             }
         }
+
     }
 
     /**
@@ -590,6 +646,18 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
         }
     }
 
+    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);
     }
@@ -615,6 +683,7 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
 
     /** {@inheritDoc} */
     public List getNextKnuthElements(final LayoutContext context, final int alignment) {
+
         lineStartBAP = context.getLineStartBorderAndPaddingWidth();
         lineEndBAP = context.getLineEndBorderAndPaddingWidth();
         alignmentContext = context.getAlignmentContext();
@@ -625,13 +694,19 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
         AreaInfo prevAreaInfo = null;
         returnList.add(sequence);
 
+        if (LOG.isDebugEnabled()) {
+            LOG.debug ( "GK: [" + nextStart + "," + foText.length() + "]" );
+        }
         LineBreakStatus lineBreakStatus = new LineBreakStatus();
         thisStart = nextStart;
         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
@@ -650,13 +725,24 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
                 default:
                     TextLayoutManager.LOG.error("Unexpected breakAction: " + breakAction);
             }
+            if (LOG.isDebugEnabled()) {
+                LOG.debug ( "GK: {"
+                            + " index = " + nextStart
+                            + ", char = " + CharUtilities.charToNCRef ( ch )
+                            + ", level = " + level
+                            + ", levelPrev = " + prevLevel
+                            + ", inWord = " + inWord
+                            + ", inSpace = " + inWhitespace
+                            + "}" );
+            }
             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, prevLevel);
                 }
             } else if (inWhitespace) {
                 if (ch != CharUtilities.SPACE || breakOpportunity) {
@@ -680,14 +766,14 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
                 // 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
@@ -697,12 +783,13 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
             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) {
@@ -723,6 +810,8 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
         } else {
             return returnList;
         }
+
+
     }
 
     private KnuthSequence processLinebreak(List returnList, KnuthSequence sequence) {
@@ -738,20 +827,27 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
     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) {
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug ( "PS: [" + thisStart + "," + nextStart + "]" );
+        }
+
         // 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);
@@ -760,25 +856,69 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
         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();
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug ( "PW: [" + thisStart + "," + lastIndex + "]: {"
+                        + " +M"
+                        + ", level = " + level
+                        + " }" );
+        }
 
-        //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;
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug ( "PW: [" + thisStart + "," + lastIndex + "]: {"
+                        + " -M"
+                        + ", level = " + level
+                        + " }" );
+        }
+
         for (int i = thisStart; i < lastIndex; i++) {
             char currentChar = foText.charAt(i);
-
+            
             //character width
             int charWidth = font.getCharWidth(currentChar);
             wordIPD = wordIPD.plus(charWidth);
@@ -801,33 +941,56 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
             }
         }
         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.performsSubstitution() ) {
+            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
@@ -897,7 +1060,7 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
         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");
         }
@@ -951,8 +1114,8 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
 
             // add letter spaces
             boolean isWordEnd
-                = stopIndex == areaInfo.breakIndex
-                && areaInfo.letterSpaceCount < areaInfo.getCharLength();
+                = (stopIndex == areaInfo.breakIndex)
+                && (areaInfo.letterSpaceCount < areaInfo.getCharLength());
             int letterSpaceCount = isWordEnd ? stopIndex - startIndex - 1 : stopIndex - startIndex;
 
             assert letterSpaceCount >= 0;
@@ -961,7 +1124,7 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
             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;
             }
@@ -990,9 +1153,9 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
                     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();
         }
@@ -1322,4 +1485,14 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
 
     }
 
+    /** {@inheritDoc} */
+    public String toString() {
+        return super.toString() + "{"
+            + "chars = \'"
+            + CharUtilities.toNCRefs ( new String ( foText.getCharArray(), 0, foText.length() ) )
+            + "\'"
+            + ", len = " + foText.length()
+            + "}";
+    }
+
 }
index ae160ef786189bbbda7364585086057c23522fea..a17c8c4df0a0922ac6f4b712113ee26ac03faf84 100644 (file)
@@ -1017,9 +1017,7 @@ public class PDFDocument {
         output(stream);
         for (int count = 0; count < this.trailerObjects.size(); count++) {
             PDFObject o = (PDFObject)this.trailerObjects.get(count);
-            this.location.set(
-                o.getObjectNumber() - 1,
-                new Integer(this.position));
+            setLocation(o.getObjectNumber() - 1, this.position);
             this.position += o.output(stream);
         }
         /* output the xref table and increment the character position
index d7d80fbe07a2e4c6958411b212499b5e5de2af3f..9566f60da50387e9dc3c4f99db60406c00501ee9 100644 (file)
@@ -323,7 +323,7 @@ public class PDFText extends PDFObject {
     /**
      * Converts a text to PDF's "string" data type. Unsupported characters get converted to '?'
      * characters (similar to what the Java "US-ASCII" encoding does).
-     * @see {@link #toPDFString(CharSequence, char)}
+     * @see #toPDFString(CharSequence, char)
      * @param text the text to convert
      * @return the converted string
      */
index ac4936a43438c99f3a5fa0bf0341f4efe1b4fae8..a71e048f82f65ef904ff1ded9b29c62c30bc666e 100644 (file)
@@ -467,7 +467,7 @@ public abstract class AbstractPathOrientedRenderer extends PrintRenderer {
         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 abstract class AbstractPathOrientedRenderer extends PrintRenderer {
     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 72be1b0b8b15fada8a05c99cb9346b4be5738c59..2aa7a599de1ee8c47a7251cb46dfab0fc77caf20 100644 (file)
@@ -535,13 +535,11 @@ public abstract class AbstractRenderer
                 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();
@@ -605,8 +603,9 @@ public abstract class AbstractRenderer
         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;
@@ -672,11 +671,12 @@ public abstract class AbstractRenderer
      * @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();
     }
@@ -702,14 +702,15 @@ public abstract class AbstractRenderer
      * @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;
@@ -724,7 +725,7 @@ public abstract class AbstractRenderer
         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;
     }
@@ -736,7 +737,7 @@ public abstract class AbstractRenderer
     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 df6cf2c5f790738713408ae22aa01ef776ed9439..c8d65df5646a736d611bd6fe0ffda166dc86a8a1 100644 (file)
@@ -624,7 +624,7 @@ public class AFPRenderer extends AbstractPathOrientedRenderer implements AFPCust
         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]);
@@ -663,7 +663,7 @@ public class AFPRenderer extends AbstractPathOrientedRenderer implements AFPCust
                 = 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
 
@@ -685,7 +685,7 @@ public class AFPRenderer extends AbstractPathOrientedRenderer implements AFPCust
         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 23a5da3e3315c69a96c8c1e6011548c149eb60ff..3c7bef60343f3c0200cba8bd6dd73425e48f5db8 100644 (file)
@@ -60,6 +60,7 @@ import org.apache.fop.area.BlockViewport;
 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 @@ public class IFRenderer extends AbstractPathOrientedRenderer {
         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 @@ public class IFRenderer extends AbstractPathOrientedRenderer {
         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 @@ public class IFRenderer extends AbstractPathOrientedRenderer {
         }
 
         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 @@ public class IFRenderer extends AbstractPathOrientedRenderer {
         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 @@ public class IFRenderer extends AbstractPathOrientedRenderer {
         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 @@ public class IFRenderer extends AbstractPathOrientedRenderer {
      * 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 @@ public class IFRenderer extends AbstractPathOrientedRenderer {
         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 29570e69f057e9525b4d194c771ed11e7dc5bba2..b60f3d4430657724dfae286e6963df9c7e1f4993 100644 (file)
@@ -89,7 +89,8 @@ public class ConfiguredFontCollection implements FontCollection {
                 } else {
                     CustomFont fontMetrics = FontLoader.loadFont(
                             fontFile, null, true, EncodingMode.AUTO,
-                            configFontInfo.getKerning(), fontResolver);
+                            configFontInfo.getKerning(),
+                            configFontInfo.getAdvanced(), fontResolver);
                     font = new CustomFontMetricsMapper(fontMetrics);
                 }
 
index cbd50c954868533735a93599d3bd513e736027b7..a947ef6caf34cda2a1d2d0f0fe3543d6b5c95d82 100644 (file)
@@ -712,7 +712,7 @@ public abstract class Java2DRenderer extends AbstractPathOrientedRenderer implem
         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);
@@ -824,7 +824,7 @@ public abstract class Java2DRenderer extends AbstractPathOrientedRenderer implem
         // 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 659997e8f5e128cbb4bec15be8d958d2935e7cc1..aa63058f602057a4dc220c8636adb2c60df3bd4b 100644 (file)
@@ -473,7 +473,7 @@ public class PCLRenderer extends PrintRenderer implements PCLConstants {
         //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 @@ public class PCLRenderer extends PrintRenderer implements PCLConstants {
 
                 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 class PCLRenderer extends PrintRenderer implements PCLConstants {
     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 @@ public class PCLRenderer extends PrintRenderer implements PCLConstants {
      */
     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;
@@ -1489,7 +1489,7 @@ public class PCLRenderer extends PrintRenderer implements PCLConstants {
         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 1aa5ac74e285df0acd19dc3dca560d0a555ffdd8..8a8df35f27b2d83cd5af72730ccfbdec38afa392 100644 (file)
@@ -850,7 +850,8 @@ public class PDFRenderer extends AbstractPathOrientedRenderer implements PDFConf
         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);
         }
     }
@@ -893,7 +894,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer implements PDFConf
         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();
@@ -996,11 +997,11 @@ public class PDFRenderer extends AbstractPathOrientedRenderer implements PDFConf
         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));
 
@@ -1297,7 +1298,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer implements PDFConf
         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 0703912ce9aa7cfcf24893cdcfa19de1f5d16d53..9d3d75e6820478956e34cb736147cc085e4bae23 100644 (file)
@@ -989,7 +989,7 @@ public class PSRenderer extends AbstractPathOrientedRenderer
 
         //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) {
@@ -1213,7 +1213,7 @@ public class PSRenderer extends AbstractPathOrientedRenderer
         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 ee840b4aace859aeaf2f969ce7ed74f16db03c92..1b7e21bd3732f145dd4e53a6a9c274ca30055a9e 100644 (file)
@@ -442,7 +442,7 @@ final class TextAttributesConverter {
     /**
      * Reads background-color from bl and writes it to rtfAttr.
      *
-     * @param bph the CommonBorderPaddingBackground from which the properties are read
+     * @param bpb the CommonBorderPaddingBackground from which the properties are read
      * @param rtfAttr the RtfAttributes object the attributes are written to
      */
     private static void attrBackgroundColor(CommonBorderPaddingBackground bpb,
index ed4aa64a9dae143453fd228fc636879aa75bf723..1a71ed6703b3144363a436bf7c6a82087f29267e 100644 (file)
@@ -41,8 +41,7 @@ public class RtfFootnote extends RtfContainer
      * Create an RTF list item as a child of given container with default attributes.
      * @param parent a container
      * @param w a writer
-     * @return a text run
-     * @throw IOException if not caught
+     * @throws IOException if not caught
      */
     RtfFootnote(RtfContainer parent, Writer w) throws IOException {
         super(parent, w);
index c283dbd98d3b028d7b86dbdeae930ef11a38f1e7..09e2f57ef1e38934637e7b93419dc67217a0ab84 100644 (file)
@@ -683,6 +683,7 @@ public class XMLRenderer extends AbstractXMLRenderer {
             break;
         default: //nop
         }
+        maybeAddLevelAttribute(block);
         startElement("block", atts);
         super.renderBlock(block);
         endElement("block");
@@ -695,6 +696,7 @@ public class XMLRenderer extends AbstractXMLRenderer {
         atts.clear();
         addAreaAttributes(line);
         addTraitAttributes(line);
+        maybeAddLevelAttribute(line);
         startElement("lineArea", atts);
         super.renderLineArea(line);
         endElement("lineArea");
@@ -725,7 +727,7 @@ public class XMLRenderer extends AbstractXMLRenderer {
         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 @@ public class XMLRenderer extends AbstractXMLRenderer {
         atts.clear();
         addAreaAttributes(space);
         addTraitAttributes(space);
-        addAttribute("offset", space.getOffset());
+        addAttribute("offset", space.getBlockProgressionOffset());
         startElement("space", atts);
         endElement("space");
     }
@@ -799,10 +801,11 @@ public class XMLRenderer extends AbstractXMLRenderer {
         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 @@ public class XMLRenderer extends AbstractXMLRenderer {
      */
     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 @@ public class XMLRenderer extends AbstractXMLRenderer {
                 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 @@ public class XMLRenderer extends AbstractXMLRenderer {
      */
     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 @@ public class XMLRenderer extends AbstractXMLRenderer {
         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 @@ public class XMLRenderer extends AbstractXMLRenderer {
         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 @@ public class XMLRenderer extends AbstractXMLRenderer {
         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 @@ public class XMLRenderer extends AbstractXMLRenderer {
         return XML_MIME_TYPE;
     }
 
-}
+    private void maybeAddLevelAttribute ( Area a ) {
+        int level = a.getBidiLevel();
+        if ( level >= 0 ) {
+            addAttribute ( "level", level );
+        }
+    }
 
+}
diff --git a/src/java/org/apache/fop/text/bidi/BidiClassUtils.java b/src/java/org/apache/fop/text/bidi/BidiClassUtils.java
new file mode 100644 (file)
index 0000000..3a15758
--- /dev/null
@@ -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 44ed56f24b4512535a238204adcf20fc1e6dbe43..83b0aa8969579dad98f03109b7dc051ca2fc5a19 100644 (file)
@@ -119,8 +119,8 @@ public class FileCompare {
 
     /**
      * Does a file size compare of two files
-     * @param file1 the first file to compare
-     * @param file2 the second file to compare
+     * @param oldFile the first file to compare
+     * @param newFile the second file to compare
      * @return true if files are same length, false otherwise
      */
     private static boolean compareFileSize(File oldFile, File newFile) {
diff --git a/src/java/org/apache/fop/traits/Direction.java b/src/java/org/apache/fop/traits/Direction.java
new file mode 100644 (file)
index 0000000..e3fd78b
--- /dev/null
@@ -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();
+    }
+
+}
diff --git a/src/java/org/apache/fop/traits/WritingMode.java b/src/java/org/apache/fop/traits/WritingMode.java
new file mode 100644 (file)
index 0000000..1bfd97e
--- /dev/null
@@ -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();
+    }
+
+}
diff --git a/src/java/org/apache/fop/traits/WritingModeTraits.java b/src/java/org/apache/fop/traits/WritingModeTraits.java
new file mode 100644 (file)
index 0000000..2c6b8a7
--- /dev/null
@@ -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 );
+    }
+
+}
diff --git a/src/java/org/apache/fop/traits/WritingModeTraitsGetter.java b/src/java/org/apache/fop/traits/WritingModeTraitsGetter.java
new file mode 100644 (file)
index 0000000..a67e437
--- /dev/null
@@ -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();
+
+}
diff --git a/src/java/org/apache/fop/traits/WritingModeTraitsSetter.java b/src/java/org/apache/fop/traits/WritingModeTraitsSetter.java
new file mode 100644 (file)
index 0000000..1b94e22
--- /dev/null
@@ -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 );
+
+}
diff --git a/src/java/org/apache/fop/util/BidiConstants.java b/src/java/org/apache/fop/util/BidiConstants.java
new file mode 100644 (file)
index 0000000..be3f89f
--- /dev/null
@@ -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 7786552ff280d796897601aeec0facf4b9dc9395..05a0e0d0e13679cbb7d7016289a0fd25d4a4b327 100644 (file)
 
 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 class CharUtilities {
     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 @@ public class CharUtilities {
      * @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 @@ public class CharUtilities {
      * @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 @@ public class CharUtilities {
      * @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 @@ public class CharUtilities {
      * @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 @@ public class CharUtilities {
      * @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 @@ public class CharUtilities {
      * @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 @@ public class CharUtilities {
      * @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 @@ public class CharUtilities {
 
     /**
      * 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 3f98c93f4a3a708b5263986c36a0f8e17d29ccab..741c8a20893bc225113440be487e7f1663ed1fec 100644 (file)
@@ -77,7 +77,7 @@ public class TrueTypeAnsiTestCase extends TestCase {
         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 1fb4f2926528ea22797c57f53e2d4b5a6c3aaabe..9b3851f5bc7df092444bd457f4e069a28d80e1cf 100644 (file)
     <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 25e30c731fb1e905c0cd6f4d54012ec357235e87..813be6c9541fb2f705c5950e33c0cb973b684f9d 100644 (file)
@@ -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 569d982388332b785fa01408beb2311ed7858e4f..a4fa3712b06e39679648f8ac23d2ddf117851420 100644 (file)
   <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 b7410bab145acfc002a5727ab240ca2d2465e344..17852c127e7366bd916e9af045a1f4782c5e9e17 100644 (file)
   </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">