<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"/>
<!-- =================================================================== -->
<!-- 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>
<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>
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>
</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>
</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>
</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>
</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>
<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>
<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>
</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>
<!-- =================================================================== -->
<!-- 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 -->
<!-- =================================================================== -->
<!-- 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 -->
--- /dev/null
+<?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="<init>" 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="<init>" 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>
* @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 {
--- /dev/null
+/*
+ * 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;
+ }
+ }
+ }
+}
* 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
/**
/** 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.
*
+ 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
*
* @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);
}
/**
* @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);
}
/**
* @return the trait value
*/
public Object getTrait(Object oTraitCode) {
- return (props != null ? props.get(oTraitCode) : null);
+ return (traits != null ? traits.get(oTraitCode) : null);
}
/**
return sb.toString();
}
}
-
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);
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);
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);
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,
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);
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);
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);
}
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);
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);
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[] {
*/
public static final int FIXED = 3;
- private int stacking = TB;
private int positioning = STACK;
/** if true, allow BPD update */
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).
* 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:
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;
* @return CTM the coordinate transformation matrix (CTM)
*/
public static CTM getCTMandRelDims(int absRefOrient,
- int writingMode,
+ WritingMode writingMode,
Rectangle2D absVPrect,
FODimension reldims) {
int width, height;
* 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
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.
*
// 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:
* 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;
out.writeFloat((float) viewArea.getWidth());
out.writeFloat((float) viewArea.getHeight());
out.writeBoolean(clip);
- out.writeObject(props);
+ out.writeObject(traits);
out.writeObject(regionReference);
}
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());
}
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);
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
/** 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];
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));
}
* @param v the offset
*/
/*
- public void setOffset(int v) {
+ public void setBlockProgressionOffset(int v) {
setChildOffset(inlines.listIterator(), v);
}
*/
} else if (child instanceof org.apache.fop.area.inline.Viewport) {
// nothing
} else {
- child.setOffset(v);
+ child.setBlockProgressionOffset(v);
}
}
}
/**
* offset position from before edge of parent area
*/
- protected int offset = 0;
+ protected int blockProgressionOffset = 0;
/**
* parent 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
*/
}
/**
- * 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;
}
/**
}
}
}
-
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.
if (autoSize) {
increaseIPD(inlineChildArea.getAllocIPD());
}
+ updateLevel ( childArea.getBidiLevel() );
}
}
}
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 );
+ }
+ }
+ }
+
+
+}
/**
* 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;
}
+
}
package org.apache.fop.area.inline;
+import org.apache.fop.util.CharUtilities;
+
/**
* A text inline area.
*/
* @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);
}
/**
/** {@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;
+ }
+ }
+
}
out.writeFloat((float) contentPosition.getHeight());
}
out.writeBoolean(clip);
- out.writeObject(props);
+ out.writeObject(traits);
out.writeObject(content);
}
in.readFloat());
}
this.clip = in.readBoolean();
- this.props = (HashMap) in.readObject();
+ this.traits = (HashMap) in.readObject();
this.content = (Area) in.readObject();
}
package org.apache.fop.area.inline;
+import java.util.Arrays;
+
+import org.apache.fop.util.CharUtilities;
+
/**
* A string of characters without spaces
*/
/** 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;
}
/**
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;
+ }
}
}
* <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 {
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;
}
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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
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
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
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
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
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
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
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
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
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
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
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
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);
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);
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);
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);
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
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
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
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);
}
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);
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);
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);
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);
}
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);
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);
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");
import java.awt.Color;
import java.nio.CharBuffer;
+import java.util.Map;
import java.util.NoSuchElementException;
import org.xml.sax.Locator;
/** 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;
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
/** 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;
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} */
* @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
private boolean canRemove = false;
private boolean canReplace = false;
+ public TextCharIterator() {
+ }
+
/** {@inheritDoc} */
public boolean hasNext() {
return (this.currentPosition < charBuffer.limit());
}
/**
- * @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} */
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;
+ }
+ }
+ }
+
}
throws FOPException {
setLocator(locator);
pList.addAttributesToList(attlist);
- if (!inMarker()
- || "marker".equals(elementName)) {
- pList.setWritingMode();
+ if (!inMarker() || "marker".equals(elementName)) {
bind(pList);
}
}
*/
public abstract class PropertyList {
- // writing-mode index
- private int writingMode;
private static boolean[] inheritableProperty;
}
/**
- * 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
*/
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";
return CommonTextDecoration.createFromPropertyList(this);
}
}
-
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
*/
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} */
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;
// 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
return commonHyphenation;
}
- /** @return the "break-after" property. */
+ /** @return the "break-after" trait. */
public int getBreakAfter() {
return breakAfter;
}
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;
}
}
}
- /** @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;
}
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;
}
* {@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} */
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;
}
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;
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;
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();
}
}
/**
- * @return the "block-progression-dimension" property.
+ * @return the "block-progression-dimension" FO trait.
*/
public LengthRangeProperty getBlockProgressionDimension() {
return blockProgressionDimension;
}
- /** @return the "display-align" property. */
+ /** @return the "display-align" FO trait. */
public int getDisplayAlign() {
return displayAlign;
}
- /** @return the "break-after" property. */
+ /** @return the "break-after" FO trait. */
public int getBreakAfter() {
return breakAfter;
}
- /** @return the "break-before" property. */
+ /** @return the "break-before" FO trait. */
public int getBreakBefore() {
return breakBefore;
}
- /** @return the "keep-with-next" property. */
+ /** @return the "keep-with-next" FO trait. */
public KeepProperty getKeepWithNext() {
return keepWithNext;
}
- /** @return the "keep-with-previous" property. */
+ /** @return the "keep-with-previous" FO trait. */
public KeepProperty getKeepWithPrevious() {
return keepWithPrevious;
}
- /** @return the "keep-together" property. */
+ /** @return the "keep-together" FO trait. */
public KeepProperty getKeepTogether() {
return keepTogether;
}
- /** @return the "inline-progression-dimension" property */
+ /** @return the "inline-progression-dimension" FO trait */
public LengthRangeProperty getInlineProgressionDimension() {
return inlineProgressionDimension;
}
- /** @return the "overflow" property */
+ /** @return the "overflow" FO trait */
public int getOverflow() {
return overflow;
}
- /** @return the "reference-orientation" property */
+ /** @return the "reference-orientation" FO trait */
public int getReferenceOrientation() {
return referenceOrientation.getValue();
}
- /** @return the "span" property */
+ /** @return the "span" FO trait */
public int getSpan() {
return this.span;
}
return 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} */
// 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 */
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;
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">
*/
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;
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;
// 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;
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()) );
}
/**
}
}
- /** @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;
}
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} */
*/
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;
private KeepProperty keepWithNext;
private KeepProperty keepWithPrevious;
private SpaceProperty lineHeight;
- // End of property values
+ // End of trait values
/**
* Base constructor
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;
}
* 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
*/
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;
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;
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();
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);
}
/**
- * @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;
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;
}
|| !getKeepTogether().getWithinColumn().isAuto();
}
- /** @return the "border-collapse" property. */
+ /** @return the "border-collapse" FO trait. */
public int getBorderCollapse() {
return borderCollapse;
}
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;
}
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
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");
}
}
/**
- * 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;
}
/**
- * 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.
*/
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;
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("")) {
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;
+ }
}
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;
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);
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">
* @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) {
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;
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;
}
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);
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">
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);
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;
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;
}
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">
* @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) {
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;
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;
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;
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">
* 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)
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");
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";
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;
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;
}
/**
/**
* 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;
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;
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) {
* 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) {
/**
* 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;
}
}
// 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) {
* @param paddingCorresponding the corresping propids.
*/
public void setPaddingCorresponding(int[] paddingCorresponding) {
+ if ( ( paddingCorresponding == null ) || ( paddingCorresponding.length != 4 ) ) {
+ throw new IllegalArgumentException();
+ }
this.paddingCorresponding = paddingCorresponding;
}
* @param borderWidthCorresponding the corresping propids.
*/
public void setBorderWidthCorresponding(int[] borderWidthCorresponding) {
+ if ( ( borderWidthCorresponding == null ) || ( borderWidthCorresponding.length != 4 ) ) {
+ throw new IllegalArgumentException();
+ }
this.borderWidthCorresponding = borderWidthCorresponding;
}
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);
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.
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;
--- /dev/null
+/*
+ * 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;
+ }
+
+}
private Map kerning;
private boolean useKerning = true;
+ private boolean useAdvanced = true;
/** {@inheritDoc} */
public String getFontName() {
}
}
+ /**
+ * 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} */
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
--- /dev/null
+/*
+ * 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;
+ }
+
+}
--- /dev/null
+/*
+ * 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);
+ }
+}
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;
/**
* 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;
}
/**
* 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.
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() : "")
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;
* {@inheritDoc}
*/
public String toString() {
- StringBuffer sbuf = new StringBuffer();
- sbuf.append('(');
+ StringBuffer sbuf = new StringBuffer(super.toString());
+ sbuf.append('{');
/*
sbuf.append(fontFamily);
sbuf.append(',');*/
sbuf.append(fontStyle);
sbuf.append(',');
sbuf.append(fontWeight);*/
- sbuf.append(')');
+ sbuf.append('}');
return sbuf.toString();
}
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();
+ }
+ }
+
+}
* 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
}
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);
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;
}
boolean embedded, EncodingMode encodingMode,
FontResolver resolver) throws IOException {
return loadFont(fontUrl.toExternalForm(), subFontName,
- embedded, encodingMode, true,
+ embedded, encodingMode, true, true,
resolver);
}
* @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;
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();
}
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:
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;
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
*/
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;
} 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 {
*/
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);
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;
+ }
+ }
+
}
--- /dev/null
+/*
+ * 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 );
+
+}
--- /dev/null
+/*
+ * 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;
+ }
+
+ }
+
+}
--- /dev/null
+/*
+ * 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 );
+
+}
--- /dev/null
+/*
+ * 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;
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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;
+ }
+
+}
--- /dev/null
+/*
+ * 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();
+ }
+
+ }
+}
--- /dev/null
+/*
+ * 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 );
+
+}
--- /dev/null
+/*
+ * 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;
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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();
+ }
+
+ }
+
+}
--- /dev/null
+/*
+ * 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 );
+ }
+
+}
--- /dev/null
+/*
+ * 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();
+ }
+
+ }
+
+}
--- /dev/null
+/*
+ * 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();
+ }
+
+}
--- /dev/null
+/*
+ * 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);
+ }
+}
/**
* 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
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();
/** {@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) {
new URL(metricsFileName).openStream()));
}
reader.setKerningEnabled(useKerning);
+ reader.setAdvancedEnabled(useAdvanced);
if (this.embedded) {
reader.setFontEmbedPath(fontEmbedPath);
}
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;
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;
+ }
+ }
+
}
package org.apache.fop.fonts;
//Java
+import java.nio.CharBuffer;
import java.text.DecimalFormat;
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;
/** A map from Unicode indices to glyph indices */
private BFEntry[] bfentries = null;
+ /* advanced typographic support */
+ private GlyphSubstitutionTable gsub;
+ private GlyphPositioningTable gpos;
+
/**
* Default constructor
*/
* @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++) {
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();
}
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;
+ }
+
}
*/
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
--- /dev/null
+/*
+ * 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 );
+
+}
--- /dev/null
+/*
+ * 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;
+ }
+
+}
--- /dev/null
+/*
+ * 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 );
+
+}
/** {@inheritDoc} */
public String toString() {
- return getFullName();
+ StringBuffer sbuf = new StringBuffer(super.toString());
+ sbuf.append('{');
+ sbuf.append(getFullName());
+ sbuf.append('}');
+ return sbuf.toString();
}
}
import java.io.IOException;
import java.util.Iterator;
+import java.util.List;
import java.util.Map;
import java.util.Set;
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;
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.
generateDOM4Kerning(root, ttf, isCid);
+ generateDOM4ScriptExtensions(root, ttf, isCid);
+
return doc;
}
}
}
+ 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
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);
}
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);
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.
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
*/
// print_max_min();
readKerning(in);
+ readGSUB(in);
+ readGPOS(in);
guessVerticalMetricsFromGlyphBBox();
return true;
}
}
}
+ 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
ioe.printStackTrace(System.err);
}
}
-}
\ No newline at end of file
+}
* @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);
}
/**
* @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) {
if (useKerning) {
copyKerning(ttf, isCid);
}
+ if (useAdvanced) {
+ copyAdvanced(ttf);
+ }
if (this.embedded && ttf.isEmbeddable()) {
returnFont.setEmbedFileName(this.fontFileURI);
}
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() );
+ }
+ }
+
}
*/
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) {
/** {@inheritDoc} */
public String toString() {
- return (super.toString() + (fobj != null ? "[fobj=" + fobj.toString() + "]" : ""));
+ return (super.toString() + (fobj != null ? "{fobj = " + fobj.toString() + "}" : ""));
}
/** {@inheritDoc} */
--- /dev/null
+/*
+ * 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 "?";
+ }
+ }
+
+ }
+
+}
curBlockArea.setIPD(super.getContentAreaIPD());
+ curBlockArea.setBidiLevel ( getBlockFO().getBidiLevel() );
+
TraitSetter.addBreaks(curBlockArea,
getBlockFO().getBreakBefore(), getBlockFO().getBreakAfter());
}
}
-
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
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);
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);
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;
/**
//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;
* Get the writing mode of the relevant reference area.
* @return the applicable writing mode
*/
- public int getWritingMode() {
+ public WritingMode getWritingMode() {
return writingMode;
}
* Set the writing mode.
* @param writingMode the writing mode
*/
- public void setWritingMode(int writingMode) {
+ public void setWritingMode(WritingMode writingMode) {
this.writingMode = writingMode;
}
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;
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));
}
- */
}
}
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().
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()
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
* @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();
* Return the writing mode.
* @return the writing mode
*/
- public int getWritingMode() {
+ public WritingMode getWritingMode() {
return scaledBaselineTable.getWritingMode();
}
}
private boolean isHorizontalWritingMode() {
- return (getWritingMode() == EN_LR_TB || getWritingMode() == EN_RL_TB);
+ return (getWritingMode() == WritingMode.LR_TB || getWritingMode() == WritingMode.RL_TB);
}
/** {@inheritDoc} */
import org.apache.commons.logging.LogFactory;
import org.apache.fop.fo.Constants;
+import org.apache.fop.traits.WritingMode;
/**
private int depth;
private int xHeight;
private int dominantBaselineIdentifier;
- private int writingMode;
+ private WritingMode writingMode;
private int dominantBaselineOffset;
private int beforeEdgeOffset;
private int afterEdgeOffset;
, int depth
, int xHeight
, int dominantBaselineIdentifier
- , int writingMode) {
+ , WritingMode writingMode) {
this.altitude = altitude;
this.depth = depth;
this.xHeight = xHeight;
* Return the writing mode for this baseline table.
* @return the writing mode
*/
- public int getWritingMode() {
+ public WritingMode getWritingMode() {
return this.writingMode;
}
}
private boolean isHorizontalWritingMode() {
- return writingMode == EN_LR_TB || writingMode == EN_RL_TB;
+ return writingMode == WritingMode.LR_TB || writingMode == WritingMode.RL_TB;
}
/**
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);
}
}
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());
}
}
-
InlineArea area;
if (hasInlineParent) {
area = new InlineParent();
- area.setOffset(0);
+ area.setBlockProgressionOffset(0);
} else {
area = new InlineBlockParent();
}
|| 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));
}
}
this.contentAreaIPD = contentAreaIPD;
}
+ /** {@inheritDoc} */
+ public void reset() {
+ childLMs.clear();
+ super.reset();
+ }
+
}
* @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());
}
/**
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;
/** {@inheritDoc} */
public void initialize() {
+ bidiLevel = fobj.getBidiLevel();
textAlignment = fobj.getTextAlign();
textAlignmentLast = fobj.getTextAlignLast();
textIndent = fobj.getTextIndent();
}
+ private int bidiLevel = -1;
private int textAlignment = EN_JUSTIFY;
private int textAlignmentLast;
private int effectiveAlignment;
}
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);
&& (!context.isLastArea() || !isLastPosition)) {
lineArea.setBPD(lineArea.getBPD() + context.getSpaceAfter());
}
- lineArea.finalise();
+ lineArea.finish();
+ if ( lineArea.getBidiLevel() >= 0 ) {
+ BidiUtil.reorder ( lineArea );
+ }
parentLayoutManager.addChildArea(lineArea);
}
blocklc.setTrailingSpace(new SpaceSpecifier(false));
}
lineArea.updateExtentsFromChildren();
+ if ( lineArea.getBidiLevel() >= 0 ) {
+ BidiUtil.reorder ( lineArea );
+ }
parentLayoutManager.addChildArea(lineArea);
}
}
}
-
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());
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
* Return the writing mode for this aligment context.
* @return the writing mode
*/
- int getWritingMode();
+ WritingMode getWritingMode();
/**
* Return the offset measured from the dominant
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.
* 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);
}
* 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);
}
* 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);
}
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;
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;
this.isSpace = isSpace;
this.breakOppAfter = breakOppAfter;
this.font = font;
+ this.level = level;
}
private int getCharLength() {
}
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
- + "]";
+ + "}";
}
}
}
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,
addAreaInfoAreas(lastAreaInfo, wordSpaceCount, letterSpaceCount, firstAreaInfoIndex,
lastAreaInfoIndex, realWidth, context);
}
+
}
private void addAreaInfoAreas(AreaInfo areaInfo, int wordSpaceCount, int letterSpaceCount,
private StringBuffer wordChars;
private int[] letterAdjust;
private int letterAdjustIndex;
+ private int[] wordLevels;
+ private int wordLevelsCount;
+ private int wordIPD;
private TextArea textArea;
calcBlockProgressionDimension();
setBlockProgressionDimension();
setBaselineOffset();
- setOffset();
+ setBlockProgressionOffset();
setText();
TraitSetter.addFontTraits(textArea, font);
textArea.addTrait(Trait.COLOR, foText.getColor());
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());
}
}
}
private void addWord(int startIndex, int endIndex, int charLength) {
+ int blockProgressionOffset = 0;
if (isHyphenated(endIndex)) {
charLength++;
}
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) {
}
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;
}
}
* 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 );
}
}
}
+
}
/**
}
}
+ 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);
}
/** {@inheritDoc} */
public List getNextKnuthElements(final LayoutContext context, final int alignment) {
+
lineStartBAP = context.getLineStartBorderAndPaddingWidth();
lineEndBAP = context.getLineEndBorderAndPaddingWidth();
alignmentContext = context.getAlignmentContext();
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
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) {
// 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
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) {
} else {
return returnList;
}
+
+
}
private KnuthSequence processLinebreak(List returnList, KnuthSequence sequence) {
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);
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);
}
}
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
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");
}
// 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;
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;
}
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();
}
}
+ /** {@inheritDoc} */
+ public String toString() {
+ return super.toString() + "{"
+ + "chars = \'"
+ + CharUtilities.toNCRefs ( new String ( foText.getCharArray(), 0, foText.length() ) )
+ + "\'"
+ + ", len = " + foText.length()
+ + "}";
+ }
+
}
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
/**
* 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
*/
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
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.
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();
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;
* @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();
}
* @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;
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;
}
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);
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]);
= 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
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;
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;
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);
}
}
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();
}
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());
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);
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
* 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;
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();
} else {
CustomFont fontMetrics = FontLoader.loadFont(
fontFile, null, true, EncodingMode.AUTO,
- configFontInfo.getKerning(), fontResolver);
+ configFontInfo.getKerning(),
+ configFontInfo.getAdvanced(), fontResolver);
font = new CustomFontMetricsMapper(fontMetrics);
}
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);
// 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;
//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 {
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);
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.
*/
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;
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;
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);
}
}
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();
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));
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();
//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) {
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();
/**
* 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,
* 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);
break;
default: //nop
}
+ maybeAddLevelAttribute(block);
startElement("block", atts);
super.renderBlock(block);
endElement("block");
atts.clear();
addAreaAttributes(line);
addTraitAttributes(line);
+ maybeAddLevelAttribute(line);
startElement("lineArea", atts);
super.renderLineArea(line);
endElement("lineArea");
atts.clear();
addAreaAttributes(viewport);
addTraitAttributes(viewport);
- addAttribute("offset", viewport.getOffset());
+ addAttribute("offset", viewport.getBlockProgressionOffset());
addAttribute("pos", viewport.getContentPosition());
if (viewport.getClip()) {
addAttribute("clip", "true");
atts.clear();
addAreaAttributes(space);
addTraitAttributes(space);
- addAttribute("offset", space.getOffset());
+ addAttribute("offset", space.getBlockProgressionOffset());
startElement("space", atts);
endElement("space");
}
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");
*/
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);
addAttribute("letter-adjust", sb.toString());
}
}
+ maybeAddLevelAttribute(word);
+ if ( word.isReversed() ) {
+ addAttribute("reversed", "true");
+ }
startElement("word", atts);
characters(word.getWord());
endElement("word");
*/
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
}
atts.clear();
addAreaAttributes(ip);
addTraitAttributes(ip);
- addAttribute("offset", ip.getOffset());
+ addAttribute("offset", ip.getBlockProgressionOffset());
+ maybeAddLevelAttribute(ip);
startElement("inlineparent", atts);
super.renderInlineParent(ip);
endElement("inlineparent");
atts.clear();
addAreaAttributes(ibp);
addTraitAttributes(ibp);
- addAttribute("offset", ibp.getOffset());
+ addAttribute("offset", ibp.getBlockProgressionOffset());
+ maybeAddLevelAttribute(ibp);
startElement("inlineblockparent", atts);
super.renderInlineBlockParent(ibp);
endElement("inlineblockparent");
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);
return XML_MIME_TYPE;
}
-}
+ private void maybeAddLevelAttribute ( Area a ) {
+ int level = a.getBidiLevel();
+ if ( level >= 0 ) {
+ addAttribute ( "level", level );
+ }
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+ }
+}
+
+}
/**
* 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) {
--- /dev/null
+/*
+ * 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();
+ }
+
+}
--- /dev/null
+/*
+ * 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();
+ }
+
+}
--- /dev/null
+/*
+ * 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 );
+ }
+
+}
--- /dev/null
+/*
+ * 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();
+
+}
--- /dev/null
+/*
+ * 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 );
+
+}
--- /dev/null
+/*
+ * 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;
+}
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.
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 */
* @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;
* @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));
}
* @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)
* @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
* @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
* @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
* @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
/**
* 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 ];
+ }
+ }
+
+}
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);
<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>
<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>
<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>
</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">