]> source.dussan.org Git - xmlgraphics-fop.git/commitdiff
FOP-2391: preliminary (but incomplete) support for complex script text nodes in svg...
authorGlenn Adams <gadams@apache.org>
Sun, 3 Aug 2014 04:40:34 +0000 (04:40 +0000)
committerGlenn Adams <gadams@apache.org>
Sun, 3 Aug 2014 04:40:34 +0000 (04:40 +0000)
git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1615385 13f79535-47bb-0310-9956-ffa450edef68

27 files changed:
findbugs-exclude.xml
lib/batik-all-trunk.jar
src/java/org/apache/fop/complexscripts/fonts/GlyphProcessingState.java
src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitutionState.java
src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitutionTable.java
src/java/org/apache/fop/complexscripts/fonts/Substitutable.java
src/java/org/apache/fop/complexscripts/scripts/ArabicScriptProcessor.java
src/java/org/apache/fop/complexscripts/scripts/DefaultScriptProcessor.java
src/java/org/apache/fop/complexscripts/scripts/DevanagariScriptProcessor.java
src/java/org/apache/fop/complexscripts/scripts/GujaratiScriptProcessor.java
src/java/org/apache/fop/complexscripts/scripts/GurmukhiScriptProcessor.java
src/java/org/apache/fop/complexscripts/scripts/IndicScriptProcessor.java
src/java/org/apache/fop/complexscripts/util/CharAssociation.java [new file with mode: 0644]
src/java/org/apache/fop/complexscripts/util/GlyphSequence.java
src/java/org/apache/fop/fonts/Font.java
src/java/org/apache/fop/fonts/GlyphMapping.java
src/java/org/apache/fop/fonts/LazyFont.java
src/java/org/apache/fop/fonts/MultiByteFont.java
src/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java
src/java/org/apache/fop/render/java2d/CustomFontMetricsMapper.java
src/java/org/apache/fop/svg/NativeTextPainter.java
src/java/org/apache/fop/svg/PDFTextPainter.java
src/java/org/apache/fop/svg/font/ComplexGlyphVector.java [new file with mode: 0644]
src/java/org/apache/fop/svg/font/FOPGVTFont.java
src/java/org/apache/fop/svg/font/FOPGVTGlyphVector.java
src/java/org/apache/fop/svg/text/ComplexGlyphLayout.java [new file with mode: 0644]
test/java/org/apache/fop/svg/font/GlyphLayoutTestCase.java

index c3a1b76fbfb081489f9ae733831f79a38539e654..5cc531a6bbdb2ec6e5829a951e20d9faab3de0d2 100644 (file)
       </And>
     </Or>
   </Match>
+  <Match>
+    <Bug pattern="UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR"/>
+    <And>
+      <Class name="org.apache.fop.svg.font.FOPGVTGlyphVector"/>
+      <Or>
+        <Field name="glyphTransforms"/>
+        <Field name="glyphVisibilities"/>
+      </Or>
+    </And>
+  </Match>
   <Match>
     <Bug pattern="DM_DEFAULT_ENCODING"/>
     <Or>
      <Bug pattern="EI_EXPOSE_REP"/>
    </Match>
    <Match>
-     <Class name="org.apache.fop.complexscripts.util.GlyphSequence$CharAssociation"/>
+     <Class name="org.apache.fop.complexscripts.util.CharAssociation"/>
      <Method name="getSubIntervals"/>
      <Bug pattern="EI_EXPOSE_REP"/>
    </Match>
index e458266b924f33755551f26ca7cf7c6a890c2f27..347757f32562110761f4f660736f0060a3018df3 100644 (file)
Binary files a/lib/batik-all-trunk.jar and b/lib/batik-all-trunk.jar differ
index 4f6e4181cae67ee262db99ab492e5a710162d97b..82a188abc886b1c9f6d93a197ebc3fb65f571770 100644 (file)
@@ -23,6 +23,7 @@ import java.nio.IntBuffer;
 import java.util.ArrayList;
 import java.util.List;
 
+import org.apache.fop.complexscripts.util.CharAssociation;
 import org.apache.fop.complexscripts.util.GlyphContextTester;
 import org.apache.fop.complexscripts.util.GlyphSequence;
 import org.apache.fop.complexscripts.util.GlyphTester;
@@ -417,7 +418,7 @@ public class GlyphProcessingState {
      * @return character association of glyph at current position
      * @throws IndexOutOfBoundsException if offset results in an invalid index into input glyph sequence
      */
-    public GlyphSequence.CharAssociation getAssociation(int offset) throws IndexOutOfBoundsException {
+    public CharAssociation getAssociation(int offset) throws IndexOutOfBoundsException {
         int i = index + offset;
         if ((i >= 0) && (i < indexLast)) {
             return igs.getAssociation(i);
@@ -431,7 +432,7 @@ public class GlyphProcessingState {
      * @return character association of glyph at current position
      * @throws IndexOutOfBoundsException if no glyph available
      */
-    public GlyphSequence.CharAssociation getAssociation() throws IndexOutOfBoundsException {
+    public CharAssociation getAssociation() throws IndexOutOfBoundsException {
         return getAssociation(0);
     }
 
@@ -722,7 +723,7 @@ public class GlyphProcessingState {
      * @throws IndexOutOfBoundsException if offset or count results in an
      * invalid index into input glyph sequence
      */
-    public GlyphSequence.CharAssociation[] getAssociations(int offset, int count, boolean reverseOrder, GlyphTester ignoreTester, GlyphSequence.CharAssociation[] associations, int[] counts)
+    public CharAssociation[] getAssociations(int offset, int count, boolean reverseOrder, GlyphTester ignoreTester, CharAssociation[] associations, int[] counts)
         throws IndexOutOfBoundsException {
         if (count < 0) {
             count = getGlyphsAvailable(offset, reverseOrder, ignoreTester) [ 0 ];
@@ -736,7 +737,7 @@ public class GlyphProcessingState {
             throw new IndexOutOfBoundsException("will attempt index at " + (start - count));
         }
         if (associations == null) {
-            associations = new GlyphSequence.CharAssociation [ count ];
+            associations = new CharAssociation [ count ];
         } else if (associations.length != count) {
             throw new IllegalArgumentException("associations array is non-null, but its length (" + associations.length + "), is not equal to count (" + count + ")");
         }
@@ -747,7 +748,7 @@ public class GlyphProcessingState {
         }
     }
 
-    private GlyphSequence.CharAssociation[] getAssociationsForward(int start, int count, GlyphTester ignoreTester, GlyphSequence.CharAssociation[] associations, int[] counts)
+    private CharAssociation[] getAssociationsForward(int start, int count, GlyphTester ignoreTester, CharAssociation[] associations, int[] counts)
         throws IndexOutOfBoundsException {
         int counted = 0;
         int ignored = 0;
@@ -775,7 +776,7 @@ public class GlyphProcessingState {
         return associations;
     }
 
-    private GlyphSequence.CharAssociation[] getAssociationsReverse(int start, int count, GlyphTester ignoreTester, GlyphSequence.CharAssociation[] associations, int[] counts)
+    private CharAssociation[] getAssociationsReverse(int start, int count, GlyphTester ignoreTester, CharAssociation[] associations, int[] counts)
         throws IndexOutOfBoundsException {
         int counted = 0;
         int ignored = 0;
@@ -813,7 +814,7 @@ public class GlyphProcessingState {
      * @throws IndexOutOfBoundsException if offset or count results in an
      * invalid index into input glyph sequence
      */
-    public GlyphSequence.CharAssociation[] getAssociations(int offset, int count) throws IndexOutOfBoundsException {
+    public CharAssociation[] getAssociations(int offset, int count) throws IndexOutOfBoundsException {
         return getAssociations(offset, count, offset < 0, ignoreDefault, null, null);
     }
 
@@ -833,7 +834,7 @@ public class GlyphProcessingState {
      * @throws IndexOutOfBoundsException if offset or count results in an
      * invalid index into input glyph sequence
      */
-    public GlyphSequence.CharAssociation[] getIgnoredAssociations(int offset, int count, boolean reverseOrder, GlyphTester ignoreTester, GlyphSequence.CharAssociation[] associations, int[] counts)
+    public CharAssociation[] getIgnoredAssociations(int offset, int count, boolean reverseOrder, GlyphTester ignoreTester, CharAssociation[] associations, int[] counts)
         throws IndexOutOfBoundsException {
         return getAssociations(offset, count, reverseOrder, new NotGlyphTester(ignoreTester), associations, counts);
     }
@@ -848,7 +849,7 @@ public class GlyphProcessingState {
      * @throws IndexOutOfBoundsException if offset or count results in an
      * invalid index into input glyph sequence
      */
-    public GlyphSequence.CharAssociation[] getIgnoredAssociations(int offset, int count) throws IndexOutOfBoundsException {
+    public CharAssociation[] getIgnoredAssociations(int offset, int count) throws IndexOutOfBoundsException {
         return getIgnoredAssociations(offset, count, offset < 0, ignoreDefault, null, null);
     }
 
index 108f26c829067633c30a1434d92180db4000b910..da40bbd873743d450cc8779b34c93f92073396c2 100644 (file)
@@ -23,6 +23,7 @@ import java.nio.IntBuffer;
 import java.util.ArrayList;
 import java.util.List;
 
+import org.apache.fop.complexscripts.util.CharAssociation;
 import org.apache.fop.complexscripts.util.GlyphSequence;
 import org.apache.fop.complexscripts.util.ScriptContextTester;
 
@@ -128,7 +129,7 @@ public class GlyphSubstitutionState extends GlyphProcessingState {
      * @param a character association that applies to glyph
      * @param predication a predication value to add to association A if predications enabled
      */
-    public void putGlyph(int glyph, GlyphSequence.CharAssociation a, Object predication) {
+    public void putGlyph(int glyph, CharAssociation a, Object predication) {
         if (!ogb.hasRemaining()) {
             ogb = growBuffer(ogb);
         }
@@ -145,7 +146,7 @@ public class GlyphSubstitutionState extends GlyphProcessingState {
      * @param associations array of character associations that apply to glyphs
      * @param predication optional predicaion object to be associated with glyphs' associations
      */
-    public void putGlyphs(int[] glyphs, GlyphSequence.CharAssociation[] associations, Object predication) {
+    public void putGlyphs(int[] glyphs, CharAssociation[] associations, Object predication) {
         assert glyphs != null;
         assert associations != null;
         assert associations.length >= glyphs.length;
index da708bf450d8fbedab1b514868f4b6fb2e501e23..7bb5a23e1e7cf046ecf8c94e025e8860094e2c8e 100644 (file)
@@ -28,6 +28,7 @@ import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
 import org.apache.fop.complexscripts.scripts.ScriptProcessor;
+import org.apache.fop.complexscripts.util.CharAssociation;
 import org.apache.fop.complexscripts.util.GlyphSequence;
 import org.apache.fop.complexscripts.util.GlyphTester;
 
@@ -382,7 +383,7 @@ public class GlyphSubstitutionTable extends GlyphTable {
             } else {
                 int[] ga = getGlyphsForCoverageIndex(ci, gi);
                 if (ga != null) {
-                    ss.putGlyphs(ga, GlyphSequence.CharAssociation.replicate(ss.getAssociation(), ga.length), Boolean.TRUE);
+                    ss.putGlyphs(ga, CharAssociation.replicate(ss.getAssociation(), ga.length), Boolean.TRUE);
                     ss.consume(1);
                 }
                 return true;
@@ -581,9 +582,9 @@ public class GlyphSubstitutionTable extends GlyphTable {
                             nga = counts[0];
                             ngi = counts[1];
                             // fetch associations of matched component glyphs
-                            GlyphSequence.CharAssociation[] laa = ss.getAssociations(0, nga);
+                            CharAssociation[] laa = ss.getAssociations(0, nga);
                             // output ligature glyph and its association
-                            ss.putGlyph(go, GlyphSequence.CharAssociation.join(laa), Boolean.TRUE);
+                            ss.putGlyph(go, CharAssociation.join(laa), Boolean.TRUE);
                             // fetch and output ignored glyphs (if necessary)
                             if (ngi > 0) {
                                 ss.putGlyphs(ss.getIgnoredGlyphs(0, ngi), ss.getIgnoredAssociations(0, ngi), null);
index 984af8deac306f6861a0db6861b374f4e86e17bc..ef349240a887db4da9ae3eea4d0971c294e8744a 100644 (file)
@@ -19,6 +19,8 @@
 
 package org.apache.fop.complexscripts.fonts;
 
+import java.util.List;
+
 // CSOFF: LineLengthCheck
 
 /**
@@ -43,10 +45,11 @@ public interface Substitutable {
      * @param cs character sequence to map to output font encoding character sequence
      * @param script a script identifier
      * @param language a language identifier
+     * @param associations optional list to receive list of character associations
      * @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);
+    CharSequence performSubstitution(CharSequence cs, String script, String language, List associations);
 
     /**
      * Reorder combining marks in character sequence so that they precede (within the sequence) the base
@@ -57,8 +60,9 @@ public interface Substitutable {
      * @param gpa associated glyph position adjustments (also reordered)
      * @param script a script identifier
      * @param language a language identifier
+     * @param associations optional list of associations to be reordered
      * @return output sequence containing reordered "font characters"
      */
-    CharSequence reorderCombiningMarks(CharSequence cs, int[][] gpa, String script, String language);
+    CharSequence reorderCombiningMarks(CharSequence cs, int[][] gpa, String script, String language, List associations);
 
 }
index 5e68c8763f5c197d6d8f1608a7502ecfbab8a1f7..2753ca9451e6c14d6cd7027d3cd26449f7d61ca4 100644 (file)
@@ -29,6 +29,7 @@ import org.apache.commons.logging.LogFactory;
 import org.apache.fop.complexscripts.bidi.BidiClass;
 import org.apache.fop.complexscripts.bidi.BidiConstants;
 import org.apache.fop.complexscripts.fonts.GlyphDefinitionTable;
+import org.apache.fop.complexscripts.util.CharAssociation;
 import org.apache.fop.complexscripts.util.GlyphContextTester;
 import org.apache.fop.complexscripts.util.GlyphSequence;
 import org.apache.fop.complexscripts.util.ScriptContextTester;
@@ -147,7 +148,7 @@ public class ArabicScriptProcessor extends DefaultScriptProcessor {
     }
 
     private static boolean inFinalContext(String script, String language, String feature, GlyphSequence gs, int index, int flags) {
-        GlyphSequence.CharAssociation a = gs.getAssociation(index);
+        CharAssociation a = gs.getAssociation(index);
         int[] ca = gs.getCharacterArray(false);
         int   nc = gs.getCharacterCount();
         if (nc == 0) {
@@ -168,7 +169,7 @@ public class ArabicScriptProcessor extends DefaultScriptProcessor {
     }
 
     private static boolean inInitialContext(String script, String language, String feature, GlyphSequence gs, int index, int flags) {
-        GlyphSequence.CharAssociation a = gs.getAssociation(index);
+        CharAssociation a = gs.getAssociation(index);
         int[] ca = gs.getCharacterArray(false);
         int   nc = gs.getCharacterCount();
         if (nc == 0) {
@@ -187,7 +188,7 @@ public class ArabicScriptProcessor extends DefaultScriptProcessor {
     }
 
     private static boolean inIsolateContext(String script, String language, String feature, GlyphSequence gs, int index, int flags) {
-        GlyphSequence.CharAssociation a = gs.getAssociation(index);
+        CharAssociation a = gs.getAssociation(index);
         int   nc = gs.getCharacterCount();
         if (nc == 0) {
             return false;
@@ -199,7 +200,7 @@ public class ArabicScriptProcessor extends DefaultScriptProcessor {
     }
 
     private static boolean inLigatureContext(String script, String language, String feature, GlyphSequence gs, int index, int flags) {
-        GlyphSequence.CharAssociation a = gs.getAssociation(index);
+        CharAssociation a = gs.getAssociation(index);
         int[] ca = gs.getCharacterArray(false);
         int   nc = gs.getCharacterCount();
         if (nc == 0) {
@@ -218,7 +219,7 @@ public class ArabicScriptProcessor extends DefaultScriptProcessor {
     }
 
     private static boolean inMedialContext(String script, String language, String feature, GlyphSequence gs, int index, int flags) {
-        GlyphSequence.CharAssociation a = gs.getAssociation(index);
+        CharAssociation a = gs.getAssociation(index);
         int[] ca = gs.getCharacterArray(false);
         int   nc = gs.getCharacterCount();
         if (nc == 0) {
index 7aa532838bdab1107d1d241916198d60451575a5..ced4d4041f34e0d5ab83868de118cacb87349317 100644 (file)
@@ -20,6 +20,7 @@
 package org.apache.fop.complexscripts.scripts;
 
 import org.apache.fop.complexscripts.fonts.GlyphDefinitionTable;
+import org.apache.fop.complexscripts.util.CharAssociation;
 import org.apache.fop.complexscripts.util.GlyphSequence;
 import org.apache.fop.complexscripts.util.ScriptContextTester;
 
@@ -92,18 +93,18 @@ public class DefaultScriptProcessor extends ScriptProcessor {
         }
         // only reorder if there is at least one mark and at least one non-mark glyph
         if ((nm > 0) && ((ng - nm) > 0)) {
-            GlyphSequence.CharAssociation[] aa = gs.getAssociations(0, -1);
+            CharAssociation[] aa = gs.getAssociations(0, -1);
             int[] nga = new int [ ng ];
             int[][] npa = (gpa != null) ? new int [ ng ][] : null;
-            GlyphSequence.CharAssociation[] naa = new GlyphSequence.CharAssociation [ ng ];
+            CharAssociation[] naa = new CharAssociation [ ng ];
             int k = 0;
-            GlyphSequence.CharAssociation ba = null;
+            CharAssociation ba = null;
             int bg = -1;
             int[] bpa = null;
             for (int i = 0; i < ng; i++) {
                 int gid = ga [ i ];
                 int[] pa = (gpa != null) ? gpa [ i ] : null;
-                GlyphSequence.CharAssociation ca = aa [ i ];
+                CharAssociation ca = aa [ i ];
                 if (gdef.isGlyphClass(gid, GlyphDefinitionTable.GLYPH_CLASS_MARK)) {
                     nga [ k ] = gid;
                     naa [ k ] = ca;
index e3843f4442d885ca92748dd122c61d06098f3505..8d312b846b0034a5114dd0ba18a2ad4e1816af93 100644 (file)
@@ -22,6 +22,7 @@ package org.apache.fop.complexscripts.scripts;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
+import org.apache.fop.complexscripts.util.CharAssociation;
 import org.apache.fop.complexscripts.util.GlyphSequence;
 
 // CSOFF: LineLengthCheck
@@ -82,7 +83,7 @@ public class DevanagariScriptProcessor extends IndicScriptProcessor {
     }
 
     private static boolean containsPreBaseMatra(GlyphSequence gs, int k) {
-        GlyphSequence.CharAssociation a = gs.getAssociation(k);
+        CharAssociation a = gs.getAssociation(k);
         int[] ca = gs.getCharacterArray(false);
         for (int i = a.getStart(), e = a.getEnd(); i < e; i++) {
             if (isPreM(ca [ i ])) {
@@ -93,7 +94,7 @@ public class DevanagariScriptProcessor extends IndicScriptProcessor {
     }
 
     private static boolean containsConsonant(GlyphSequence gs, int k) {
-        GlyphSequence.CharAssociation a = gs.getAssociation(k);
+        CharAssociation a = gs.getAssociation(k);
         int[] ca = gs.getCharacterArray(false);
         for (int i = a.getStart(), e = a.getEnd(); i < e; i++) {
             if (isC(ca [ i ])) {
@@ -159,7 +160,7 @@ public class DevanagariScriptProcessor extends IndicScriptProcessor {
     }
 
     private static boolean containsMatra(GlyphSequence gs, int k) {
-        GlyphSequence.CharAssociation a = gs.getAssociation(k);
+        CharAssociation a = gs.getAssociation(k);
         int[] ca = gs.getCharacterArray(false);
         for (int i = a.getStart(), e = a.getEnd(); i < e; i++) {
             if (isM(ca [ i ])) {
@@ -170,7 +171,7 @@ public class DevanagariScriptProcessor extends IndicScriptProcessor {
     }
 
     private static boolean containsOtherMark(GlyphSequence gs, int k) {
-        GlyphSequence.CharAssociation a = gs.getAssociation(k);
+        CharAssociation a = gs.getAssociation(k);
         int[] ca = gs.getCharacterArray(false);
         for (int i = a.getStart(), e = a.getEnd(); i < e; i++) {
             switch (typeOf(ca [ i ])) {
index e4519623e0ae7acf43d40b86578e12cc5ceef538..1f96455bc572253b520ec7860447f07f38c4ee35 100644 (file)
@@ -22,6 +22,7 @@ package org.apache.fop.complexscripts.scripts;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
+import org.apache.fop.complexscripts.util.CharAssociation;
 import org.apache.fop.complexscripts.util.GlyphSequence;
 
 // CSOFF: LineLengthCheck
@@ -82,7 +83,7 @@ public class GujaratiScriptProcessor extends IndicScriptProcessor {
     }
 
     private static boolean containsPreBaseMatra(GlyphSequence gs, int k) {
-        GlyphSequence.CharAssociation a = gs.getAssociation(k);
+        CharAssociation a = gs.getAssociation(k);
         int[] ca = gs.getCharacterArray(false);
         for (int i = a.getStart(), e = a.getEnd(); i < e; i++) {
             if (isPreM(ca [ i ])) {
@@ -93,7 +94,7 @@ public class GujaratiScriptProcessor extends IndicScriptProcessor {
     }
 
     private static boolean containsConsonant(GlyphSequence gs, int k) {
-        GlyphSequence.CharAssociation a = gs.getAssociation(k);
+        CharAssociation a = gs.getAssociation(k);
         int[] ca = gs.getCharacterArray(false);
         for (int i = a.getStart(), e = a.getEnd(); i < e; i++) {
             if (isC(ca [ i ])) {
@@ -159,7 +160,7 @@ public class GujaratiScriptProcessor extends IndicScriptProcessor {
     }
 
     private static boolean containsMatra(GlyphSequence gs, int k) {
-        GlyphSequence.CharAssociation a = gs.getAssociation(k);
+        CharAssociation a = gs.getAssociation(k);
         int[] ca = gs.getCharacterArray(false);
         for (int i = a.getStart(), e = a.getEnd(); i < e; i++) {
             if (isM(ca [ i ])) {
@@ -170,7 +171,7 @@ public class GujaratiScriptProcessor extends IndicScriptProcessor {
     }
 
     private static boolean containsOtherMark(GlyphSequence gs, int k) {
-        GlyphSequence.CharAssociation a = gs.getAssociation(k);
+        CharAssociation a = gs.getAssociation(k);
         int[] ca = gs.getCharacterArray(false);
         for (int i = a.getStart(), e = a.getEnd(); i < e; i++) {
             switch (typeOf(ca [ i ])) {
index 9c4d49f59e993936e62acb11cb28d90c91fdb244..94f5893a9e8e02cf693cd1de01447766023e4467 100644 (file)
@@ -23,6 +23,7 @@ import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
 import org.apache.fop.complexscripts.fonts.GlyphDefinitionTable;
+import org.apache.fop.complexscripts.util.CharAssociation;
 import org.apache.fop.complexscripts.util.GlyphSequence;
 
 // CSOFF: LineLengthCheck
@@ -83,7 +84,7 @@ public class GurmukhiScriptProcessor extends IndicScriptProcessor {
     }
 
     private static boolean containsPreBaseMatra(GlyphSequence gs, int k) {
-        GlyphSequence.CharAssociation a = gs.getAssociation(k);
+        CharAssociation a = gs.getAssociation(k);
         int[] ca = gs.getCharacterArray(false);
         for (int i = a.getStart(), e = a.getEnd(); i < e; i++) {
             if (isPreM(ca [ i ])) {
@@ -94,7 +95,7 @@ public class GurmukhiScriptProcessor extends IndicScriptProcessor {
     }
 
     private static boolean containsConsonant(GlyphSequence gs, int k) {
-        GlyphSequence.CharAssociation a = gs.getAssociation(k);
+        CharAssociation a = gs.getAssociation(k);
         int[] ca = gs.getCharacterArray(false);
         for (int i = a.getStart(), e = a.getEnd(); i < e; i++) {
             if (isC(ca [ i ])) {
@@ -160,7 +161,7 @@ public class GurmukhiScriptProcessor extends IndicScriptProcessor {
     }
 
     private static boolean containsMatra(GlyphSequence gs, int k) {
-        GlyphSequence.CharAssociation a = gs.getAssociation(k);
+        CharAssociation a = gs.getAssociation(k);
         int[] ca = gs.getCharacterArray(false);
         for (int i = a.getStart(), e = a.getEnd(); i < e; i++) {
             if (isM(ca [ i ])) {
@@ -171,7 +172,7 @@ public class GurmukhiScriptProcessor extends IndicScriptProcessor {
     }
 
     private static boolean containsOtherMark(GlyphSequence gs, int k) {
-        GlyphSequence.CharAssociation a = gs.getAssociation(k);
+        CharAssociation a = gs.getAssociation(k);
         int[] ca = gs.getCharacterArray(false);
         for (int i = a.getStart(), e = a.getEnd(); i < e; i++) {
             switch (typeOf(ca [ i ])) {
index fcabad3964cdc646712a2e36d158ff995e3d8ce9..a341ef12398fc55d65488fa8ca7138a0b2cec5ce 100644 (file)
@@ -31,6 +31,7 @@ import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
 import org.apache.fop.complexscripts.fonts.GlyphTable;
+import org.apache.fop.complexscripts.util.CharAssociation;
 import org.apache.fop.complexscripts.util.CharScript;
 import org.apache.fop.complexscripts.util.GlyphContextTester;
 import org.apache.fop.complexscripts.util.GlyphSequence;
@@ -488,21 +489,21 @@ public class IndicScriptProcessor extends DefaultScriptProcessor {
         protected GlyphSequence[] segmentize(GlyphSequence gs, Segment[] sa) {
             int   ng = gs.getGlyphCount();
             int[] ga = gs.getGlyphArray(false);
-            GlyphSequence.CharAssociation[] aa = gs.getAssociations(0, -1);
+            CharAssociation[] aa = gs.getAssociations(0, -1);
             Vector<GlyphSequence> nsv = new Vector<GlyphSequence>();
             for (int i = 0, ns = sa.length; i < ns; i++) {
                 Segment s = sa [ i ];
                 Vector<Integer> ngv = new Vector<Integer>(ng);
-                Vector<GlyphSequence.CharAssociation> nav = new Vector<GlyphSequence.CharAssociation>(ng);
+                Vector<CharAssociation> nav = new Vector<CharAssociation>(ng);
                 for (int j = 0; j < ng; j++) {
-                    GlyphSequence.CharAssociation ca = aa [ j ];
+                    CharAssociation ca = aa [ j ];
                     if (ca.contained(s.getOffset(), s.getCount())) {
                         ngv.add(ga [ j ]);
                         nav.add(ca);
                     }
                 }
                 if (ngv.size() > 0) {
-                    nsv.add(new GlyphSequence(gs, null, toIntArray(ngv), null, null, nav.toArray(new GlyphSequence.CharAssociation [ nav.size() ]), null));
+                    nsv.add(new GlyphSequence(gs, null, toIntArray(ngv), null, null, nav.toArray(new CharAssociation [ nav.size() ]), null));
                 }
             }
             if (nsv.size() > 0) {
diff --git a/src/java/org/apache/fop/complexscripts/util/CharAssociation.java b/src/java/org/apache/fop/complexscripts/util/CharAssociation.java
new file mode 100644 (file)
index 0000000..23aebbb
--- /dev/null
@@ -0,0 +1,490 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.util;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A structure class encapsulating an interval of characters expressed as an offset and count of
+ * Unicode scalar values (in an IntBuffer). A <code>CharAssociation</code> is used to maintain a
+ * backpointer from a glyph to one or more character intervals from which the glyph was derived.
+ *
+ * Each glyph in a glyph sequence is associated with a single <code>CharAssociation</code> instance.
+ *
+ * A <code>CharAssociation</code> instance is additionally (and optionally) used to record
+ * predication information about the glyph, such as whether the glyph was produced by the
+ * application of a specific substitution table or whether its position was adjusted by a specific
+ * poisitioning table.
+ *
+ * <p>This work was originally authored by Glenn Adams (gadams@apache.org).</p>
+ */
+public class CharAssociation implements Cloneable {
+
+    // instance state
+    private final int offset;
+    private final int count;
+    private final int[] subIntervals;
+    private Map<String, Object> predications;
+
+    // class state
+    private static volatile Map<String, PredicationMerger> predicationMergers;
+
+    interface PredicationMerger {
+        Object merge(String key, Object v1, Object v2);
+    }
+
+    /**
+     * Instantiate a character association.
+     * @param offset into array of Unicode scalar values (in associated IntBuffer)
+     * @param count of Unicode scalar values (in associated IntBuffer)
+     * @param subIntervals if disjoint, then array of sub-intervals, otherwise null; even
+     * members of array are sub-interval starts, and odd members are sub-interval
+     * ends (exclusive)
+     */
+    public CharAssociation(int offset, int count, int[] subIntervals) {
+        this.offset = offset;
+        this.count = count;
+        this.subIntervals = ((subIntervals != null) && (subIntervals.length > 2)) ? subIntervals : null;
+    }
+
+    /**
+     * Instantiate a non-disjoint character association.
+     * @param offset into array of UTF-16 code elements (in associated CharSequence)
+     * @param count of UTF-16 character code elements (in associated CharSequence)
+     */
+    public CharAssociation(int offset, int count) {
+        this (offset, count, null);
+    }
+
+    /**
+     * Instantiate a non-disjoint character association.
+     * @param subIntervals if disjoint, then array of sub-intervals, otherwise null; even
+     * members of array are sub-interval starts, and odd members are sub-interval
+     * ends (exclusive)
+     */
+    public CharAssociation(int[] subIntervals) {
+        this (getSubIntervalsStart(subIntervals), getSubIntervalsLength(subIntervals), subIntervals);
+    }
+
+    /** @return offset (start of association interval) */
+    public int getOffset() {
+        return offset;
+    }
+
+    /** @return count (number of characer codes in association) */
+    public int getCount() {
+        return count;
+    }
+
+    /** @return start of association interval */
+    public int getStart() {
+        return getOffset();
+    }
+
+    /** @return end of association interval */
+    public int getEnd() {
+        return getOffset() + getCount();
+    }
+
+    /** @return true if association is disjoint */
+    public boolean isDisjoint() {
+        return subIntervals != null;
+    }
+
+    /** @return subintervals of disjoint association */
+    public int[] getSubIntervals() {
+        return subIntervals;
+    }
+
+    /** @return count of subintervals of disjoint association */
+    public int getSubIntervalCount() {
+        return (subIntervals != null) ? (subIntervals.length / 2) : 0;
+    }
+
+    /**
+     * @param offset of interval in sequence
+     * @param count length of interval
+     * @return true if this association is contained within [offset,offset+count)
+     */
+    public boolean contained(int offset, int count) {
+        int s = offset;
+        int e = offset + count;
+        if (!isDisjoint()) {
+            int s0 = getStart();
+            int e0 = getEnd();
+            return (s0 >= s) && (e0 <= e);
+        } else {
+            int ns = getSubIntervalCount();
+            for (int i = 0; i < ns; i++) {
+                int s0 = subIntervals [ 2 * i + 0 ];
+                int e0 = subIntervals [ 2 * i + 1 ];
+                if ((s0 >= s) && (e0 <= e)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Set predication <KEY,VALUE>.
+     * @param key predication key
+     * @param value predication value
+     */
+    public void setPredication(String key, Object value) {
+        if (predications == null) {
+            predications = new HashMap<String, Object>();
+        }
+        if (predications != null) {
+            predications.put(key, value);
+        }
+    }
+
+    /**
+     * Get predication KEY.
+     * @param key predication key
+     * @return predication KEY at OFFSET or null if none exists
+     */
+    public Object getPredication(String key) {
+        if (predications != null) {
+            return predications.get(key);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Merge predication <KEY,VALUE>.
+     * @param key predication key
+     * @param value predication value
+     */
+    public void mergePredication(String key, Object value) {
+        if (predications == null) {
+            predications = new HashMap<String, Object>();
+        }
+        if (predications != null) {
+            if (predications.containsKey(key)) {
+                Object v1 = predications.get(key);
+                Object v2 = value;
+                predications.put(key, mergePredicationValues(key, v1, v2));
+            } else {
+                predications.put(key, value);
+            }
+        }
+    }
+
+    /**
+     * Merge predication values V1 and V2 on KEY. Uses registered <code>PredicationMerger</code>
+     * if one exists, otherwise uses V2 if non-null, otherwise uses V1.
+     * @param key predication key
+     * @param v1 first (original) predication value
+     * @param v2 second (to be merged) predication value
+     * @return merged value
+     */
+    public static Object mergePredicationValues(String key, Object v1, Object v2) {
+        PredicationMerger pm = getPredicationMerger(key);
+        if (pm != null) {
+            return pm.merge(key, v1, v2);
+        } else if (v2 != null) {
+            return v2;
+        } else {
+            return v1;
+        }
+    }
+
+    /**
+     * Merge predications from another CA.
+     * @param ca from which to merge
+     */
+    public void mergePredications(CharAssociation ca) {
+        if (ca.predications != null) {
+            for (Map.Entry<String, Object> e : ca.predications.entrySet()) {
+                mergePredication(e.getKey(), e.getValue());
+            }
+        }
+    }
+
+    /** {@inheritDoc} */
+    public Object clone() {
+        try {
+            CharAssociation ca = (CharAssociation) super.clone();
+            if (predications != null) {
+                ca.predications = new HashMap<String, Object>(predications);
+            }
+            return ca;
+        } catch (CloneNotSupportedException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Register predication merger PM for KEY.
+     * @param key for predication merger
+     * @param pm predication merger
+     */
+    public static void setPredicationMerger(String key, PredicationMerger pm) {
+        if (predicationMergers == null) {
+            predicationMergers = new HashMap<String, PredicationMerger>();
+        }
+        if (predicationMergers != null) {
+            predicationMergers.put(key, pm);
+        }
+    }
+
+    /**
+     * Obtain predication merger for KEY.
+     * @param key for predication merger
+     * @return predication merger or null if none exists
+     */
+    public static PredicationMerger getPredicationMerger(String key) {
+        if (predicationMergers != null) {
+            return predicationMergers.get(key);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Replicate association to form <code>repeat</code> new associations.
+     * @param a association to replicate
+     * @param repeat count
+     * @return array of replicated associations
+     */
+    public static CharAssociation[] replicate(CharAssociation a, int repeat) {
+        CharAssociation[] aa = new CharAssociation [ repeat ];
+        for (int i = 0, n = aa.length; i < n; i++) {
+            aa [ i ] = (CharAssociation) a.clone();
+        }
+        return aa;
+    }
+
+    /**
+     * Join (merge) multiple associations into a single, potentially disjoint
+     * association.
+     * @param aa array of associations to join
+     * @return (possibly disjoint) association containing joined associations
+     */
+    public static CharAssociation join(CharAssociation[] aa) {
+        CharAssociation ca;
+        // extract sorted intervals
+        int[] ia = extractIntervals(aa);
+        if ((ia == null) || (ia.length == 0)) {
+            ca = new CharAssociation(0, 0);
+        } else if (ia.length == 2) {
+            int s = ia[0];
+            int e = ia[1];
+            ca = new CharAssociation(s, e - s);
+        } else {
+            ca = new CharAssociation(mergeIntervals(ia));
+        }
+        return mergePredicates(ca, aa);
+    }
+
+    private static CharAssociation mergePredicates(CharAssociation ca, CharAssociation[] aa) {
+        for (CharAssociation a : aa) {
+            ca.mergePredications(a);
+        }
+        return ca;
+    }
+
+    private static int getSubIntervalsStart(int[] ia) {
+        int us = Integer.MAX_VALUE;
+        int ue = Integer.MIN_VALUE;
+        if (ia != null) {
+            for (int i = 0, n = ia.length; i < n; i += 2) {
+                int s = ia [ i + 0 ];
+                int e = ia [ i + 1 ];
+                if (s < us) {
+                    us = s;
+                }
+                if (e > ue) {
+                    ue = e;
+                }
+            }
+            if (ue < 0) {
+                ue = 0;
+            }
+            if (us > ue) {
+                us = ue;
+            }
+        }
+        return us;
+    }
+
+    private static int getSubIntervalsLength(int[] ia) {
+        int us = Integer.MAX_VALUE;
+        int ue = Integer.MIN_VALUE;
+        if (ia != null) {
+            for (int i = 0, n = ia.length; i < n; i += 2) {
+                int s = ia [ i + 0 ];
+                int e = ia [ i + 1 ];
+                if (s < us) {
+                    us = s;
+                }
+                if (e > ue) {
+                    ue = e;
+                }
+            }
+            if (ue < 0) {
+                ue = 0;
+            }
+            if (us > ue) {
+                us = ue;
+            }
+        }
+        return ue - us;
+    }
+
+    /**
+     * Extract sorted sub-intervals.
+     */
+    private static int[] extractIntervals(CharAssociation[] aa) {
+        int ni = 0;
+        for (int i = 0, n = aa.length; i < n; i++) {
+            CharAssociation a = aa [ i ];
+            if (a.isDisjoint()) {
+                ni += a.getSubIntervalCount();
+            } else {
+                ni += 1;
+            }
+        }
+        int[] sa = new int [ ni ];
+        int[] ea = new int [ ni ];
+        for (int i = 0, k = 0; i < aa.length; i++) {
+            CharAssociation a = aa [ i ];
+            if (a.isDisjoint()) {
+                int[] da = a.getSubIntervals();
+                for (int j = 0; j < da.length; j += 2) {
+                    sa [ k ] = da [ j + 0 ];
+                    ea [ k ] = da [ j + 1 ];
+                    k++;
+                }
+            } else {
+                sa [ k ] = a.getStart();
+                ea [ k ] = a.getEnd();
+                k++;
+            }
+        }
+        return sortIntervals(sa, ea);
+    }
+
+    private static final int[] SORT_INCREMENTS_16
+        = { 1391376, 463792, 198768, 86961, 33936, 13776, 4592, 1968, 861, 336, 112, 48, 21, 7, 3, 1 };
+
+    private static final int[] SORT_INCREMENTS_03
+        = { 7, 3, 1 };
+
+    /**
+     * Sort sub-intervals using modified Shell Sort.
+     */
+    private static int[] sortIntervals(int[] sa, int[] ea) {
+        assert sa != null;
+        assert ea != null;
+        assert sa.length == ea.length;
+        int ni = sa.length;
+        int[] incr = (ni < 21) ? SORT_INCREMENTS_03 : SORT_INCREMENTS_16;
+        for (int k = 0; k < incr.length; k++) {
+            for (int h = incr [ k ], i = h, n = ni, j; i < n; i++) {
+                int s1 = sa [ i ];
+                int e1 = ea [ i ];
+                for (j = i; j >= h; j -= h) {
+                    int s2 = sa [ j - h ];
+                    int e2 = ea [ j - h ];
+                    if (s2 > s1) {
+                        sa [ j ] = s2;
+                        ea [ j ] = e2;
+                    } else if ((s2 == s1) && (e2 > e1)) {
+                        sa [ j ] = s2;
+                        ea [ j ] = e2;
+                    } else {
+                        break;
+                    }
+                }
+                sa [ j ] = s1;
+                ea [ j ] = e1;
+            }
+        }
+        int[] ia = new int [ ni * 2 ];
+        for (int i = 0; i < ni; i++) {
+            ia [ (i * 2) + 0 ] = sa [ i ];
+            ia [ (i * 2) + 1 ] = ea [ i ];
+        }
+        return ia;
+    }
+
+    /**
+     * Merge overlapping and abutting sub-intervals.
+     */
+    private static int[] mergeIntervals(int[] ia) {
+        int ni = ia.length;
+        int i;
+        int n;
+        int nm;
+        int is;
+        int ie;
+        // count merged sub-intervals
+        for (i = 0, n = ni, nm = 0, is = ie = -1; i < n; i += 2) {
+            int s = ia [ i + 0 ];
+            int e = ia [ i + 1 ];
+            if ((ie < 0) || (s > ie)) {
+                is = s;
+                ie = e;
+                nm++;
+            } else if (s >= is) {
+                if (e > ie) {
+                    ie = e;
+                }
+            }
+        }
+        int[] mi = new int [ nm * 2 ];
+        // populate merged sub-intervals
+        for (i = 0, n = ni, nm = 0, is = ie = -1; i < n; i += 2) {
+            int s = ia [ i + 0 ];
+            int e = ia [ i + 1 ];
+            int k = nm * 2;
+            if ((ie < 0) || (s > ie)) {
+                is = s;
+                ie = e;
+                mi [ k + 0 ] = is;
+                mi [ k + 1 ] = ie;
+                nm++;
+            } else if (s >= is) {
+                if (e > ie) {
+                    ie = e;
+                }
+                mi [ k - 1 ] = ie;
+            }
+        }
+        return mi;
+    }
+
+    @Override
+    public String toString() {
+        StringBuffer sb = new StringBuffer();
+        sb.append('[');
+        sb.append(offset);
+        sb.append(',');
+        sb.append(count);
+        sb.append(']');
+        return sb.toString();
+    }
+
+}
index e59dc9b32b6b2031e8c1cdbe7fca9debe538be90..8db7f109e940aeb44ad42d35ca74669e31e165f7 100644 (file)
@@ -22,9 +22,7 @@ package org.apache.fop.complexscripts.util;
 import java.nio.IntBuffer;
 
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 
 // CSOFF: LineLengthCheck
 
@@ -527,8 +525,8 @@ public class GlyphSequence implements Cloneable {
             int   ng  = gs.getGlyphCount();
             int[] ga  = gs.getGlyphArray(false);
             int[] nga = new int [ ng ];
-            GlyphSequence.CharAssociation[] aa  = gs.getAssociations(0, ng);
-            GlyphSequence.CharAssociation[] naa = new GlyphSequence.CharAssociation [ ng ];
+            CharAssociation[] aa  = gs.getAssociations(0, ng);
+            CharAssociation[] naa = new CharAssociation [ ng ];
             if (source < target) {
                 int t = 0;
                 for (int s = 0, e = source; s < e; s++, t++) {
@@ -614,462 +612,4 @@ public class GlyphSequence implements Cloneable {
         }
     }
 
-    /**
-     * A structure class encapsulating an interval of characters
-     * expressed as an offset and count of Unicode scalar values (in
-     * an IntBuffer). A <code>CharAssociation</code> is used to
-     * maintain a backpointer from a glyph to one or more character
-     * intervals from which the glyph was derived.
-     *
-     * Each glyph in a glyph sequence is associated with a single
-     * <code>CharAssociation</code> instance.
-     *
-     * A <code>CharAssociation</code> instance is additionally (and
-     * optionally) used to record predication information about the
-     * glyph, such as whether the glyph was produced by the
-     * application of a specific substitution table or whether its
-     * position was adjusted by a specific poisitioning table.
-     */
-    public static class CharAssociation implements Cloneable {
-
-        // instance state
-        private final int offset;
-        private final int count;
-        private final int[] subIntervals;
-        private Map<String, Object> predications;
-
-        // class state
-        private static volatile Map<String, PredicationMerger> predicationMergers;
-
-        interface PredicationMerger {
-            Object merge(String key, Object v1, Object v2);
-        }
-
-        /**
-         * Instantiate a character association.
-         * @param offset into array of Unicode scalar values (in associated IntBuffer)
-         * @param count of Unicode scalar values (in associated IntBuffer)
-         * @param subIntervals if disjoint, then array of sub-intervals, otherwise null; even
-         * members of array are sub-interval starts, and odd members are sub-interval
-         * ends (exclusive)
-         */
-        public CharAssociation(int offset, int count, int[] subIntervals) {
-            this.offset = offset;
-            this.count = count;
-            this.subIntervals = ((subIntervals != null) && (subIntervals.length > 2)) ? subIntervals : null;
-        }
-
-        /**
-         * Instantiate a non-disjoint character association.
-         * @param offset into array of UTF-16 code elements (in associated CharSequence)
-         * @param count of UTF-16 character code elements (in associated CharSequence)
-         */
-        public CharAssociation(int offset, int count) {
-            this (offset, count, null);
-        }
-
-        /**
-         * Instantiate a non-disjoint character association.
-         * @param subIntervals if disjoint, then array of sub-intervals, otherwise null; even
-         * members of array are sub-interval starts, and odd members are sub-interval
-         * ends (exclusive)
-         */
-        public CharAssociation(int[] subIntervals) {
-            this (getSubIntervalsStart(subIntervals), getSubIntervalsLength(subIntervals), subIntervals);
-        }
-
-        /** @return offset (start of association interval) */
-        public int getOffset() {
-            return offset;
-        }
-
-        /** @return count (number of characer codes in association) */
-        public int getCount() {
-            return count;
-        }
-
-        /** @return start of association interval */
-        public int getStart() {
-            return getOffset();
-        }
-
-        /** @return end of association interval */
-        public int getEnd() {
-            return getOffset() + getCount();
-        }
-
-        /** @return true if association is disjoint */
-        public boolean isDisjoint() {
-            return subIntervals != null;
-        }
-
-        /** @return subintervals of disjoint association */
-        public int[] getSubIntervals() {
-            return subIntervals;
-        }
-
-        /** @return count of subintervals of disjoint association */
-        public int getSubIntervalCount() {
-            return (subIntervals != null) ? (subIntervals.length / 2) : 0;
-        }
-
-        /**
-         * @param offset of interval in sequence
-         * @param count length of interval
-         * @return true if this association is contained within [offset,offset+count)
-         */
-        public boolean contained(int offset, int count) {
-            int s = offset;
-            int e = offset + count;
-            if (!isDisjoint()) {
-                int s0 = getStart();
-                int e0 = getEnd();
-                return (s0 >= s) && (e0 <= e);
-            } else {
-                int ns = getSubIntervalCount();
-                for (int i = 0; i < ns; i++) {
-                    int s0 = subIntervals [ 2 * i + 0 ];
-                    int e0 = subIntervals [ 2 * i + 1 ];
-                    if ((s0 >= s) && (e0 <= e)) {
-                        return true;
-                    }
-                }
-                return false;
-            }
-        }
-
-        /**
-         * Set predication <KEY,VALUE>.
-         * @param key predication key
-         * @param value predication value
-         */
-        public void setPredication(String key, Object value) {
-            if (predications == null) {
-                predications = new HashMap<String, Object>();
-            }
-            if (predications != null) {
-                predications.put(key, value);
-            }
-        }
-
-        /**
-         * Get predication KEY.
-         * @param key predication key
-         * @return predication KEY at OFFSET or null if none exists
-         */
-        public Object getPredication(String key) {
-            if (predications != null) {
-                return predications.get(key);
-            } else {
-                return null;
-            }
-        }
-
-        /**
-         * Merge predication <KEY,VALUE>.
-         * @param key predication key
-         * @param value predication value
-         */
-        public void mergePredication(String key, Object value) {
-            if (predications == null) {
-                predications = new HashMap<String, Object>();
-            }
-            if (predications != null) {
-                if (predications.containsKey(key)) {
-                    Object v1 = predications.get(key);
-                    Object v2 = value;
-                    predications.put(key, mergePredicationValues(key, v1, v2));
-                } else {
-                    predications.put(key, value);
-                }
-            }
-        }
-
-        /**
-         * Merge predication values V1 and V2 on KEY. Uses registered <code>PredicationMerger</code>
-         * if one exists, otherwise uses V2 if non-null, otherwise uses V1.
-         * @param key predication key
-         * @param v1 first (original) predication value
-         * @param v2 second (to be merged) predication value
-         * @return merged value
-         */
-        public static Object mergePredicationValues(String key, Object v1, Object v2) {
-            PredicationMerger pm = getPredicationMerger(key);
-            if (pm != null) {
-                return pm.merge(key, v1, v2);
-            } else if (v2 != null) {
-                return v2;
-            } else {
-                return v1;
-            }
-        }
-
-        /**
-         * Merge predications from another CA.
-         * @param ca from which to merge
-         */
-        public void mergePredications(CharAssociation ca) {
-            if (ca.predications != null) {
-                for (Map.Entry<String, Object> e : ca.predications.entrySet()) {
-                    mergePredication(e.getKey(), e.getValue());
-                }
-            }
-        }
-
-        /** {@inheritDoc} */
-        public Object clone() {
-            try {
-                CharAssociation ca = (CharAssociation) super.clone();
-                if (predications != null) {
-                    ca.predications = new HashMap<String, Object>(predications);
-                }
-                return ca;
-            } catch (CloneNotSupportedException e) {
-                return null;
-            }
-        }
-
-        /**
-         * Register predication merger PM for KEY.
-         * @param key for predication merger
-         * @param pm predication merger
-         */
-        public static void setPredicationMerger(String key, PredicationMerger pm) {
-            if (predicationMergers == null) {
-                predicationMergers = new HashMap<String, PredicationMerger>();
-            }
-            if (predicationMergers != null) {
-                predicationMergers.put(key, pm);
-            }
-        }
-
-        /**
-         * Obtain predication merger for KEY.
-         * @param key for predication merger
-         * @return predication merger or null if none exists
-         */
-        public static PredicationMerger getPredicationMerger(String key) {
-            if (predicationMergers != null) {
-                return predicationMergers.get(key);
-            } else {
-                return null;
-            }
-        }
-
-        /**
-         * Replicate association to form <code>repeat</code> new associations.
-         * @param a association to replicate
-         * @param repeat count
-         * @return array of replicated associations
-         */
-        public static CharAssociation[] replicate(CharAssociation a, int repeat) {
-            CharAssociation[] aa = new CharAssociation [ repeat ];
-            for (int i = 0, n = aa.length; i < n; i++) {
-                aa [ i ] = (CharAssociation) a.clone();
-            }
-            return aa;
-        }
-
-        /**
-         * Join (merge) multiple associations into a single, potentially disjoint
-         * association.
-         * @param aa array of associations to join
-         * @return (possibly disjoint) association containing joined associations
-         */
-        public static CharAssociation join(CharAssociation[] aa) {
-            CharAssociation ca;
-            // extract sorted intervals
-            int[] ia = extractIntervals(aa);
-            if ((ia == null) || (ia.length == 0)) {
-                ca = new CharAssociation(0, 0);
-            } else if (ia.length == 2) {
-                int s = ia[0];
-                int e = ia[1];
-                ca = new CharAssociation(s, e - s);
-            } else {
-                ca = new CharAssociation(mergeIntervals(ia));
-            }
-            return mergePredicates(ca, aa);
-        }
-
-        private static CharAssociation mergePredicates(CharAssociation ca, CharAssociation[] aa) {
-            for (CharAssociation a : aa) {
-                ca.mergePredications(a);
-            }
-            return ca;
-        }
-
-        private static int getSubIntervalsStart(int[] ia) {
-            int us = Integer.MAX_VALUE;
-            int ue = Integer.MIN_VALUE;
-            if (ia != null) {
-                for (int i = 0, n = ia.length; i < n; i += 2) {
-                    int s = ia [ i + 0 ];
-                    int e = ia [ i + 1 ];
-                    if (s < us) {
-                        us = s;
-                    }
-                    if (e > ue) {
-                        ue = e;
-                    }
-                }
-                if (ue < 0) {
-                    ue = 0;
-                }
-                if (us > ue) {
-                    us = ue;
-                }
-            }
-            return us;
-        }
-
-        private static int getSubIntervalsLength(int[] ia) {
-            int us = Integer.MAX_VALUE;
-            int ue = Integer.MIN_VALUE;
-            if (ia != null) {
-                for (int i = 0, n = ia.length; i < n; i += 2) {
-                    int s = ia [ i + 0 ];
-                    int e = ia [ i + 1 ];
-                    if (s < us) {
-                        us = s;
-                    }
-                    if (e > ue) {
-                        ue = e;
-                    }
-                }
-                if (ue < 0) {
-                    ue = 0;
-                }
-                if (us > ue) {
-                    us = ue;
-                }
-            }
-            return ue - us;
-        }
-
-        /**
-         * Extract sorted sub-intervals.
-         */
-        private static int[] extractIntervals(CharAssociation[] aa) {
-            int ni = 0;
-            for (int i = 0, n = aa.length; i < n; i++) {
-                CharAssociation a = aa [ i ];
-                if (a.isDisjoint()) {
-                    ni += a.getSubIntervalCount();
-                } else {
-                    ni += 1;
-                }
-            }
-            int[] sa = new int [ ni ];
-            int[] ea = new int [ ni ];
-            for (int i = 0, k = 0; i < aa.length; i++) {
-                CharAssociation a = aa [ i ];
-                if (a.isDisjoint()) {
-                    int[] da = a.getSubIntervals();
-                    for (int j = 0; j < da.length; j += 2) {
-                        sa [ k ] = da [ j + 0 ];
-                        ea [ k ] = da [ j + 1 ];
-                        k++;
-                    }
-                } else {
-                    sa [ k ] = a.getStart();
-                    ea [ k ] = a.getEnd();
-                    k++;
-                }
-            }
-            return sortIntervals(sa, ea);
-        }
-
-        private static final int[] SORT_INCREMENTS_16
-            = { 1391376, 463792, 198768, 86961, 33936, 13776, 4592, 1968, 861, 336, 112, 48, 21, 7, 3, 1 };
-
-        private static final int[] SORT_INCREMENTS_03
-            = { 7, 3, 1 };
-
-        /**
-         * Sort sub-intervals using modified Shell Sort.
-         */
-        private static int[] sortIntervals(int[] sa, int[] ea) {
-            assert sa != null;
-            assert ea != null;
-            assert sa.length == ea.length;
-            int ni = sa.length;
-            int[] incr = (ni < 21) ? SORT_INCREMENTS_03 : SORT_INCREMENTS_16;
-            for (int k = 0; k < incr.length; k++) {
-                for (int h = incr [ k ], i = h, n = ni, j; i < n; i++) {
-                    int s1 = sa [ i ];
-                    int e1 = ea [ i ];
-                    for (j = i; j >= h; j -= h) {
-                        int s2 = sa [ j - h ];
-                        int e2 = ea [ j - h ];
-                        if (s2 > s1) {
-                            sa [ j ] = s2;
-                            ea [ j ] = e2;
-                        } else if ((s2 == s1) && (e2 > e1)) {
-                            sa [ j ] = s2;
-                            ea [ j ] = e2;
-                        } else {
-                            break;
-                        }
-                    }
-                    sa [ j ] = s1;
-                    ea [ j ] = e1;
-                }
-            }
-            int[] ia = new int [ ni * 2 ];
-            for (int i = 0; i < ni; i++) {
-                ia [ (i * 2) + 0 ] = sa [ i ];
-                ia [ (i * 2) + 1 ] = ea [ i ];
-            }
-            return ia;
-        }
-
-        /**
-         * Merge overlapping and abutting sub-intervals.
-         */
-        private static int[] mergeIntervals(int[] ia) {
-            int ni = ia.length;
-            int i;
-            int n;
-            int nm;
-            int is;
-            int ie;
-            // count merged sub-intervals
-            for (i = 0, n = ni, nm = 0, is = ie = -1; i < n; i += 2) {
-                int s = ia [ i + 0 ];
-                int e = ia [ i + 1 ];
-                if ((ie < 0) || (s > ie)) {
-                    is = s;
-                    ie = e;
-                    nm++;
-                } else if (s >= is) {
-                    if (e > ie) {
-                        ie = e;
-                    }
-                }
-            }
-            int[] mi = new int [ nm * 2 ];
-            // populate merged sub-intervals
-            for (i = 0, n = ni, nm = 0, is = ie = -1; i < n; i += 2) {
-                int s = ia [ i + 0 ];
-                int e = ia [ i + 1 ];
-                int k = nm * 2;
-                if ((ie < 0) || (s > ie)) {
-                    is = s;
-                    ie = e;
-                    mi [ k + 0 ] = is;
-                    mi [ k + 1 ] = ie;
-                    nm++;
-                } else if (s >= is) {
-                    if (e > ie) {
-                        ie = e;
-                    }
-                    mi [ k - 1 ] = ie;
-                }
-            }
-            return mi;
-        }
-
-    }
-
 }
index 3006d08734a2854783be319f272ed279d4dc0de5..296d2ef8e9821015f6ce835afc764417ee0687c7 100644 (file)
@@ -20,6 +20,7 @@
 package org.apache.fop.fonts;
 
 import java.util.Collections;
+import java.util.List;
 import java.util.Map;
 
 import org.apache.commons.logging.Log;
@@ -402,20 +403,22 @@ public class Font implements Substitutable, Positionable {
     }
 
     /** {@inheritDoc} */
-    public CharSequence performSubstitution(CharSequence cs, String script, String language) {
+    public CharSequence performSubstitution(CharSequence cs,
+        String script, String language, List associations) {
         if (metric instanceof Substitutable) {
             Substitutable s = (Substitutable) metric;
-            return s.performSubstitution(cs, script, language);
+            return s.performSubstitution(cs, script, language, associations);
         } else {
             throw new UnsupportedOperationException();
         }
     }
 
     /** {@inheritDoc} */
-    public CharSequence reorderCombiningMarks(CharSequence cs, int[][] gpa, String script, String language) {
+    public CharSequence reorderCombiningMarks(CharSequence cs, int[][] gpa,
+        String script, String language, List associations) {
         if (metric instanceof Substitutable) {
             Substitutable s = (Substitutable) metric;
-            return s.reorderCombiningMarks(cs, gpa, script, language);
+            return s.reorderCombiningMarks(cs, gpa, script, language, associations);
         } else {
             throw new UnsupportedOperationException();
         }
index a8a82085e4129462526f9fb886260f5f8ce93c98..69943242de9cbf1b3e0e21b8fbd0dc6750bfc37a 100644 (file)
@@ -19,6 +19,9 @@
 
 package org.apache.fop.fonts;
 
+import java.util.Collections;
+import java.util.List;
+
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
@@ -47,18 +50,19 @@ public class GlyphMapping {
     public final Font font;
     public final int level;
     public final int[][] gposAdjustments;
-    public final String mapping;
+    public String mapping;
+    public List associations;
 
     public GlyphMapping(int startIndex, int endIndex, int wordSpaceCount, int letterSpaceCount,
             MinOptMax areaIPD, boolean isHyphenated, boolean isSpace, boolean breakOppAfter,
             Font font, int level, int[][] gposAdjustments) {
         this(startIndex, endIndex, wordSpaceCount, letterSpaceCount, areaIPD, isHyphenated,
-                isSpace, breakOppAfter, font, level, gposAdjustments, null);
+             isSpace, breakOppAfter, font, level, gposAdjustments, null, null);
     }
 
     public GlyphMapping(int startIndex, int endIndex, int wordSpaceCount, int letterSpaceCount,
             MinOptMax areaIPD, boolean isHyphenated, boolean isSpace, boolean breakOppAfter,
-            Font font, int level, int[][] gposAdjustments, String mapping) {
+            Font font, int level, int[][] gposAdjustments, String mapping, List associations) {
         assert startIndex <= endIndex;
         this.startIndex = startIndex;
         this.endIndex = endIndex;
@@ -73,15 +77,17 @@ public class GlyphMapping {
         this.level = level;
         this.gposAdjustments = gposAdjustments;
         this.mapping = mapping;
+        this.associations = associations;
     }
 
     public static GlyphMapping doGlyphMapping(TextFragment text, int startIndex, int endIndex,
             Font font, MinOptMax letterSpaceIPD, MinOptMax[] letterSpaceAdjustArray,
-            char precedingChar, char breakOpportunityChar, final boolean endsWithHyphen, int level) {
+            char precedingChar, char breakOpportunityChar, final boolean endsWithHyphen, int level,
+            boolean retainAssociations) {
         GlyphMapping mapping;
         if (font.performsSubstitution() || font.performsPositioning()) {
             mapping = processWordMapping(text, startIndex, endIndex, font,
-                    breakOpportunityChar, endsWithHyphen, level);
+                    breakOpportunityChar, endsWithHyphen, level, retainAssociations);
         } else {
             mapping = processWordNoMapping(text, startIndex, endIndex, font,
                     letterSpaceIPD, letterSpaceAdjustArray, precedingChar, breakOpportunityChar, endsWithHyphen,
@@ -92,7 +98,7 @@ public class GlyphMapping {
 
     private static GlyphMapping processWordMapping(TextFragment text, int startIndex,
             int endIndex, final Font font, final char breakOpportunityChar,
-            final boolean endsWithHyphen, int level) {
+            final boolean endsWithHyphen, int level, boolean retainAssociations) {
         int e = endIndex; // end index of word in FOText character buffer
         int nLS = 0; // # of letter spaces
         String script = text.getScript();
@@ -105,11 +111,11 @@ public class GlyphMapping {
                         + " }");
         }
 
-        // 1. extract unmapped character sequence
+        // 1. extract unmapped character sequence.
         CharSequence ics = text.subSequence(startIndex, e);
 
         // 2. if script is not specified (by FO property) or it is specified as 'auto',
-        // then compute dominant script
+        // then compute dominant script.
         if ((script == null) || "auto".equals(script)) {
             script = CharScript.scriptTagFromCode(CharScript.dominantScript(ics));
         }
@@ -117,10 +123,12 @@ public class GlyphMapping {
             language = "dflt";
         }
 
-        // 3. perform mapping of chars to glyphs ... to glyphs ... to chars
-        CharSequence mcs = font.performSubstitution(ics, script, language);
+        // 3. perform mapping of chars to glyphs ... to glyphs ... to chars, retaining
+        // associations if requested.
+        List associations = retainAssociations ? new java.util.ArrayList() : null;
+        CharSequence mcs = font.performSubstitution(ics, script, language, associations);
 
-        // 4. compute glyph position adjustments on (substituted) characters
+        // 4. compute glyph position adjustments on (substituted) characters.
         int[][] gpa;
         if (font.performsPositioning()) {
             // handle GPOS adjustments
@@ -133,10 +141,10 @@ public class GlyphMapping {
         }
 
         // 5. reorder combining marks so that they precede (within the mapped char sequence) the
-        // base to which they are applied; N.B. position adjustments (gpa) are reordered in place
-        mcs = font.reorderCombiningMarks(mcs, gpa, script, language);
+        // base to which they are applied; N.B. position adjustments (gpa) are reordered in place.
+        mcs = font.reorderCombiningMarks(mcs, gpa, script, language, associations);
 
-        // 6. compute word ipd based on final position adjustments
+        // 6. compute word ipd based on final position adjustments.
         MinOptMax ipd = MinOptMax.ZERO;
         for (int i = 0, n = mcs.length(); i < n; i++) {
             int c = mcs.charAt(i);
@@ -155,7 +163,7 @@ public class GlyphMapping {
 
         return new GlyphMapping(startIndex, e, 0, nLS, ipd, endsWithHyphen, false,
                 breakOpportunityChar != 0, font, level, gpa,
-                CharUtilities.isSameSequence(mcs, ics) ? null : mcs.toString());
+                CharUtilities.isSameSequence(mcs, ics) ? null : mcs.toString(), associations);
     }
 
     /**
@@ -311,6 +319,27 @@ public class GlyphMapping {
         areaIPD = areaIPD.plus(idp);
     }
 
+    public void reverse() {
+        if (mapping.length() > 0) {
+            mapping = new StringBuffer(mapping).reverse().toString();
+        }
+        if (associations != null) {
+            Collections.reverse(associations);
+        }
+        if (gposAdjustments != null) {
+            reverse(gposAdjustments);
+        }
+    }
+
+    private static void reverse(int[][] aa) {
+        for (int i = 0, n = aa.length, m = n / 2; i < m; i++) {
+            int k = n - i - 1;
+            int[] t = aa [ k ];
+            aa [ k ] = aa [ i ];
+            aa [ i ] = t;
+        }
+    }
+
     public String toString() {
         return super.toString() + "{"
                 + "interval = [" + startIndex + "," + endIndex + "]"
index 1877a895e9dfb8594e9d00282b5281df9a74ec36..a392ae6f49ac90206cebdb9d26047962619c6b88 100644 (file)
@@ -22,6 +22,7 @@ import java.awt.Rectangle;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URI;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -402,10 +403,10 @@ public class LazyFont extends Typeface implements FontDescriptor, Substitutable,
     /**
      * {@inheritDoc}
      */
-    public CharSequence performSubstitution(CharSequence cs, String script, String language) {
+    public CharSequence performSubstitution(CharSequence cs, String script, String language, List associations) {
         load(true);
         if (realFontDescriptor instanceof Substitutable) {
-            return ((Substitutable)realFontDescriptor).performSubstitution(cs, script, language);
+            return ((Substitutable)realFontDescriptor).performSubstitution(cs, script, language, associations);
         } else {
             return cs;
         }
@@ -415,13 +416,13 @@ public class LazyFont extends Typeface implements FontDescriptor, Substitutable,
      * {@inheritDoc}
      */
     public CharSequence reorderCombiningMarks(
-        CharSequence cs, int[][] gpa, String script, String language) {
+        CharSequence cs, int[][] gpa, String script, String language, List associations) {
         if (!isMetricsLoaded) {
             load(true);
         }
         if (realFontDescriptor instanceof Substitutable) {
             return ((Substitutable)realFontDescriptor)
-                .reorderCombiningMarks(cs, gpa, script, language);
+                .reorderCombiningMarks(cs, gpa, script, language, associations);
         } else {
             return cs;
         }
index 84dc429dc37bff385a395972fd7f878f85a22715..718a14fb24447118ae580dda875f8d9ee9a27a41 100644 (file)
@@ -25,6 +25,7 @@ import java.nio.CharBuffer;
 import java.nio.IntBuffer;
 import java.util.BitSet;
 import java.util.LinkedHashMap;
+import java.util.List;
 import java.util.Map;
 
 import org.apache.commons.logging.Log;
@@ -487,10 +488,14 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl
     }
 
     /** {@inheritDoc} */
-    public CharSequence performSubstitution(CharSequence cs, String script, String language) {
+    public CharSequence performSubstitution(CharSequence cs, String script, String language, List associations) {
         if (gsub != null) {
-            GlyphSequence igs = mapCharsToGlyphs(cs);
+            GlyphSequence igs = mapCharsToGlyphs(cs, associations);
             GlyphSequence ogs = gsub.substitute(igs, script, language);
+            if (associations != null) {
+                associations.clear();
+                associations.addAll(ogs.getAssociations());
+            }
             CharSequence ocs = mapGlyphsToChars(ogs);
             return ocs;
         } else {
@@ -500,10 +505,14 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl
 
     /** {@inheritDoc} */
     public CharSequence reorderCombiningMarks(
-        CharSequence cs, int[][] gpa, String script, String language) {
+        CharSequence cs, int[][] gpa, String script, String language, List associations) {
         if (gdef != null) {
-            GlyphSequence igs = mapCharsToGlyphs(cs);
+            GlyphSequence igs = mapCharsToGlyphs(cs, associations);
             GlyphSequence ogs = gdef.reorderCombiningMarks(igs, gpa, script, language);
+            if (associations != null) {
+                associations.clear();
+                associations.addAll(ogs.getAssociations());
+            }
             CharSequence ocs = mapGlyphsToChars(ogs);
             return ocs;
         } else {
@@ -520,7 +529,7 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl
     public int[][]
         performPositioning(CharSequence cs, String script, String language, int fontSize) {
         if (gpos != null) {
-            GlyphSequence gs = mapCharsToGlyphs(cs);
+            GlyphSequence gs = mapCharsToGlyphs(cs, null);
             int[][] adjustments = new int [ gs.getGlyphCount() ] [ 4 ];
             if (gpos.position(gs, script, language, fontSize, this.width, adjustments)) {
                 return scaleAdjustments(adjustments, fontSize);
@@ -559,7 +568,7 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl
      * @param cs a CharSequence containing UTF-16 encoded Unicode characters
      * @returns a CharSequence containing glyph indices
      */
-    private GlyphSequence mapCharsToGlyphs(CharSequence cs) {
+    private GlyphSequence mapCharsToGlyphs(CharSequence cs, List associations) {
         IntBuffer cb = IntBuffer.allocate(cs.length());
         IntBuffer gb = IntBuffer.allocate(cs.length());
         int gi;
@@ -598,7 +607,12 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl
         }
         cb.flip();
         gb.flip();
-        return new GlyphSequence(cb, gb, null);
+        if ((associations != null) && (associations.size() == cs.length())) {
+            associations = new java.util.ArrayList(associations);
+        } else {
+            associations = null;
+        }
+        return new GlyphSequence(cb, gb, associations);
     }
 
     /**
index 9d12348adcd226d81845152d09a413c4878d7246..24c1fcbbbe13e505f4cc877d3323430e0d9d1526 100644 (file)
@@ -933,7 +933,8 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
         char precedingChar = prevMapping != null && !prevMapping.isSpace
                 && prevMapping.endIndex > 0 ? foText.charAt(prevMapping.endIndex - 1) : 0;
         GlyphMapping mapping = GlyphMapping.doGlyphMapping(foText, thisStart, lastIndex, font,
-                letterSpaceIPD, letterSpaceAdjustArray, precedingChar, breakOpportunityChar, endsWithHyphen, level);
+                letterSpaceIPD, letterSpaceAdjustArray, precedingChar, breakOpportunityChar,
+                endsWithHyphen, level, false);
         prevMapping = mapping;
         addGlyphMapping(mapping);
         tempStart = nextStart;
index 5e9372a75a9508a607eec57d5301370f2027e4cc..8a5f4b117f04488545ff92d1bb93a46eea7ccf7a 100644 (file)
@@ -24,6 +24,7 @@ import java.awt.FontFormatException;
 import java.awt.Rectangle;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -261,9 +262,9 @@ public class CustomFontMetricsMapper extends Typeface implements FontMetricsMapp
     /**
      * {@inheritDoc}
      */
-    public CharSequence performSubstitution(CharSequence cs, String script, String language) {
+    public CharSequence performSubstitution(CharSequence cs, String script, String language, List associations) {
         if (typeface instanceof Substitutable) {
-            return ((Substitutable) typeface).performSubstitution(cs, script, language);
+            return ((Substitutable) typeface).performSubstitution(cs, script, language, associations);
         } else {
             return cs;
         }
@@ -272,9 +273,10 @@ public class CustomFontMetricsMapper extends Typeface implements FontMetricsMapp
     /**
      * {@inheritDoc}
      */
-    public CharSequence reorderCombiningMarks(CharSequence cs, int[][] gpa, String script, String language) {
+    public CharSequence reorderCombiningMarks(CharSequence cs, int[][] gpa,
+        String script, String language, List associations) {
         if (typeface instanceof Substitutable) {
-            return ((Substitutable) typeface).reorderCombiningMarks(cs, gpa, script, language);
+            return ((Substitutable) typeface).reorderCombiningMarks(cs, gpa, script, language, associations);
         } else {
             return cs;
         }
index 200f6558bff0475f40dfc0fb1e8712b1c46b3360..b5c7d7882de9971a97b2be2a6f3df14995f2751c 100644 (file)
@@ -23,6 +23,7 @@ import java.awt.BasicStroke;
 import java.awt.Color;
 import java.awt.Graphics2D;
 import java.awt.Shape;
+import java.awt.font.FontRenderContext;
 import java.awt.geom.AffineTransform;
 import java.awt.geom.Ellipse2D;
 import java.awt.geom.GeneralPath;
@@ -38,6 +39,8 @@ import org.apache.batik.bridge.SVGGVTFont;
 import org.apache.batik.gvt.font.FontFamilyResolver;
 import org.apache.batik.gvt.font.GVTGlyphVector;
 import org.apache.batik.gvt.renderer.StrokingTextPainter;
+import org.apache.batik.gvt.text.GlyphLayout;
+import org.apache.batik.gvt.text.TextLayoutFactory;
 import org.apache.batik.gvt.text.TextPaintInfo;
 import org.apache.batik.gvt.text.TextSpanLayout;
 
@@ -45,6 +48,7 @@ import org.apache.fop.fonts.Font;
 import org.apache.fop.fonts.FontInfo;
 import org.apache.fop.svg.font.FOPFontFamilyResolverImpl;
 import org.apache.fop.svg.font.FOPGVTFont;
+import org.apache.fop.svg.text.ComplexGlyphLayout;
 import org.apache.fop.util.CharUtilities;
 
 /**
@@ -273,4 +277,21 @@ public abstract class NativeTextPainter extends StrokingTextPainter {
         return this.fontFamilyResolver;
     }
 
+    private static final TextLayoutFactory COMPLEX_SCRIPT_TEXT_LAYOUT_FACTORY =
+        new TextLayoutFactory() {
+            public TextSpanLayout createTextLayout(AttributedCharacterIterator aci,
+                int [] charMap, Point2D offset, FontRenderContext frc) {
+                if (ComplexGlyphLayout.mayRequireComplexLayout(aci)) {
+                    return new ComplexGlyphLayout(aci, charMap, offset, frc);
+                } else {
+                    return new GlyphLayout(aci, charMap, offset, frc);
+                }
+            }
+        };
+
+    @Override
+    protected TextLayoutFactory getTextLayoutFactory() {
+        return COMPLEX_SCRIPT_TEXT_LAYOUT_FACTORY;
+    }
+
 }
index c5fa9f04e20ba0c66d683aa58daf232b2627aa26..c3ff744d5f0df0ce1c74d37992fb201e6f832770 100644 (file)
@@ -26,7 +26,10 @@ import java.awt.Shape;
 import java.awt.Stroke;
 import java.awt.geom.AffineTransform;
 import java.awt.geom.Point2D;
+import java.text.AttributedCharacterIterator;
+import java.util.List;
 
+import org.apache.batik.gvt.TextNode;
 import org.apache.batik.gvt.text.TextPaintInfo;
 
 import org.apache.fop.fonts.FontInfo;
@@ -182,4 +185,12 @@ class PDFTextPainter extends NativeTextPainter {
         textUtil.writeTJMappedChar(glyph);
     }
 
+    @Override
+    public List computeTextRuns(TextNode node,
+                                AttributedCharacterIterator nodeACI,
+                                AttributedCharacterIterator [] chunkACIs) {
+        // skip Batik's bidi reordering and use identity character index maps
+        return super.computeTextRuns(node, nodeACI, chunkACIs, (int[][]) null);
+    }
+
 }
diff --git a/src/java/org/apache/fop/svg/font/ComplexGlyphVector.java b/src/java/org/apache/fop/svg/font/ComplexGlyphVector.java
new file mode 100644 (file)
index 0000000..55f2eb3
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * 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.svg.font;
+
+import java.awt.font.FontRenderContext;
+import java.text.AttributedCharacterIterator;
+import java.text.CharacterIterator;
+
+import org.apache.batik.gvt.text.GVTAttributedCharacterIterator;
+
+import org.apache.fop.fonts.GlyphMapping;
+
+class ComplexGlyphVector extends FOPGVTGlyphVector {
+
+    public static final AttributedCharacterIterator.Attribute WRITING_MODE
+        = GVTAttributedCharacterIterator.TextAttribute.WRITING_MODE;
+
+    public static final Integer WRITING_MODE_RTL
+        = GVTAttributedCharacterIterator.TextAttribute.WRITING_MODE_RTL;
+
+    ComplexGlyphVector(FOPGVTFont font, final CharacterIterator iter, FontRenderContext frc) {
+        super(font, iter, frc);
+    }
+
+    public void performDefaultLayout() {
+        super.performDefaultLayout();
+    }
+
+    protected void maybeReverse(GlyphMapping mapping) {
+        if (charIter instanceof AttributedCharacterIterator) {
+            AttributedCharacterIterator aci = (AttributedCharacterIterator) charIter;
+            aci.first();
+            if (aci.getAttribute(WRITING_MODE) == WRITING_MODE_RTL) {
+                mapping.reverse();
+            }
+        }
+    }
+
+}
index c55e2fa8b4a261c71238937d23e14eb5f8d4a4b0..76f77d367101c697c4782f1a435fbe2067687ee7 100644 (file)
@@ -20,6 +20,7 @@
 package org.apache.fop.svg.font;
 
 import java.awt.font.FontRenderContext;
+import java.text.AttributedString;
 import java.text.CharacterIterator;
 import java.text.StringCharacterIterator;
 
@@ -27,6 +28,7 @@ import org.apache.batik.gvt.font.GVTFont;
 import org.apache.batik.gvt.font.GVTFontFamily;
 import org.apache.batik.gvt.font.GVTGlyphVector;
 import org.apache.batik.gvt.font.GVTLineMetrics;
+import org.apache.batik.gvt.text.GVTAttributedCharacterIterator;
 
 import org.apache.fop.fonts.Font;
 import org.apache.fop.fonts.FontMetrics;
@@ -83,8 +85,11 @@ public class FOPGVTFont implements GVTFont {
     }
 
     public GVTGlyphVector createGlyphVector(FontRenderContext frc, CharacterIterator ci) {
-        // TODO Batik does manual glyph shaping for Arabic. Replace with complex scripts implementation
-        return new FOPGVTGlyphVector(this, ci, frc);
+        if (!font.performsSubstitution() && !font.performsPositioning()) {
+            return new FOPGVTGlyphVector(this, ci, frc);
+        } else {
+            return new ComplexGlyphVector(this, ci, frc);
+        }
     }
 
     public GVTGlyphVector createGlyphVector(FontRenderContext frc,
@@ -93,11 +98,26 @@ public class FOPGVTFont implements GVTFont {
         throw new UnsupportedOperationException("Not implemented");
     }
 
-    public GVTGlyphVector createGlyphVector(FontRenderContext frc, String str) {
-        StringCharacterIterator sci = new StringCharacterIterator(str);
+    public GVTGlyphVector createGlyphVector(FontRenderContext frc, String text) {
+        StringCharacterIterator sci = new StringCharacterIterator(text);
         return createGlyphVector(frc, sci);
     }
 
+    public GVTGlyphVector createGlyphVector(FontRenderContext frc, String text, String script, String language) {
+        if ((script != null) || (language != null)) {
+            AttributedString as = new AttributedString(text);
+            if (script != null) {
+                as.addAttribute(GVTAttributedCharacterIterator.TextAttribute.SCRIPT, script);
+            }
+            if (language != null) {
+                as.addAttribute(GVTAttributedCharacterIterator.TextAttribute.LANGUAGE, language);
+            }
+            return createGlyphVector(frc, as.getIterator());
+        } else {
+            return createGlyphVector(frc, text);
+        }
+    }
+
     public FOPGVTFont deriveFont(float size) {
         throw new UnsupportedOperationException("Not implemented");
     }
index 3567bb50811705c4063ee812d3bfea10018f9205..0abffc52c433519445e972bd071fcb3ea0bb7beb 100644 (file)
@@ -33,11 +33,13 @@ import java.text.AttributedCharacterIterator;
 import java.text.CharacterIterator;
 import java.text.StringCharacterIterator;
 import java.util.Arrays;
+import java.util.List;
 
 import org.apache.batik.gvt.font.GVTFont;
 import org.apache.batik.gvt.font.GVTGlyphMetrics;
 import org.apache.batik.gvt.font.GVTGlyphVector;
 import org.apache.batik.gvt.font.GVTLineMetrics;
+import org.apache.batik.gvt.text.GVTAttributedCharacterIterator;
 
 import org.apache.fop.fonts.Font;
 import org.apache.fop.fonts.FontMetrics;
@@ -47,7 +49,7 @@ import org.apache.fop.traits.MinOptMax;
 
 class FOPGVTGlyphVector implements GVTGlyphVector {
 
-    private final CharacterIterator charIter;
+    protected final CharacterIterator charIter;
 
     private final FOPGVTFont font;
 
@@ -57,9 +59,11 @@ class FOPGVTGlyphVector implements GVTGlyphVector {
 
     private final FontRenderContext frc;
 
-    private int[] glyphs;
+    protected int[] glyphs;
 
-    private float[] positions;
+    protected List associations;
+
+    protected float[] positions;
 
     private Rectangle2D[] boundingBoxes;
 
@@ -86,20 +90,37 @@ class FOPGVTGlyphVector implements GVTGlyphVector {
         MinOptMax letterSpaceIPD = MinOptMax.ZERO;
         MinOptMax[] letterSpaceAdjustments = new MinOptMax[charIter.getEndIndex() - charIter.getBeginIndex()];
         GlyphMapping mapping = GlyphMapping.doGlyphMapping(text, charIter.getBeginIndex(), charIter.getEndIndex(),
-                f, letterSpaceIPD, letterSpaceAdjustments, '\0', '\0', false, 0 /* TODO */);
-        glyphs = buildGlyphs(f, mapping.mapping != null ? new StringCharacterIterator(mapping.mapping) : charIter);
-        buildGlyphPositions(mapping, letterSpaceAdjustments);
+            f, letterSpaceIPD, letterSpaceAdjustments, '\0', '\0', false, 0, true);
+        maybeReverse(mapping);
+        CharacterIterator glyphAsCharIter =
+            mapping.mapping != null ? new StringCharacterIterator(mapping.mapping) : charIter;
+        this.glyphs = buildGlyphs(f, glyphAsCharIter);
+        this.associations = mapping.associations;
+        this.positions = buildGlyphPositions(glyphAsCharIter, mapping.gposAdjustments, letterSpaceAdjustments);
         this.glyphVisibilities = new boolean[this.glyphs.length];
         Arrays.fill(glyphVisibilities, true);
         this.glyphTransforms = new AffineTransform[this.glyphs.length];
     }
 
+    protected void maybeReverse(GlyphMapping mapping) {
+    }
+
     private static class SVGTextFragment implements TextFragment {
 
         private final CharacterIterator charIter;
 
+        private String script;
+
+        private String language;
+
         SVGTextFragment(CharacterIterator charIter) {
             this.charIter = charIter;
+            if (charIter instanceof AttributedCharacterIterator) {
+                AttributedCharacterIterator aci = (AttributedCharacterIterator) charIter;
+                aci.first();
+                this.script = (String) aci.getAttribute(GVTAttributedCharacterIterator.TextAttribute.SCRIPT);
+                this.language = (String) aci.getAttribute(GVTAttributedCharacterIterator.TextAttribute.LANGUAGE);
+            }
         }
 
         public CharSequence subSequence(int startIndex, int endIndex) {
@@ -111,11 +132,19 @@ class FOPGVTGlyphVector implements GVTGlyphVector {
         }
 
         public String getScript() {
-            return "DFLT"; // TODO pass on script value from SVG
+            if (script != null) {
+                return script;
+            } else {
+                return "auto";
+            }
         }
 
         public String getLanguage() {
-            return "dflt"; // TODO pass on language value from SVG
+            if (language != null) {
+                return language;
+            } else {
+                return "none";
+            }
         }
 
         public char charAt(int index) {
@@ -123,42 +152,69 @@ class FOPGVTGlyphVector implements GVTGlyphVector {
         }
     }
 
-    private int[] buildGlyphs(Font font, final CharacterIterator charIter) {
-        int[] glyphs = new int[charIter.getEndIndex() - charIter.getBeginIndex()];
+    private int[] buildGlyphs(Font font, final CharacterIterator glyphAsCharIter) {
+        int[] glyphs = new int[glyphAsCharIter.getEndIndex() - glyphAsCharIter.getBeginIndex()];
         int index = 0;
-        for (char c = charIter.first();  c != CharacterIterator.DONE; c = charIter.next()) {
+        for (char c = glyphAsCharIter.first();  c != CharacterIterator.DONE; c = glyphAsCharIter.next()) {
             glyphs[index] = font.mapChar(c);
             index++;
         }
         return glyphs;
     }
 
-    private void buildGlyphPositions(GlyphMapping ai, MinOptMax[] letterSpaceAdjustments) {
-        positions = new float[2 * glyphs.length + 2];
-        if (ai.gposAdjustments != null) {
-            assert ai.gposAdjustments.length == glyphs.length;
-            for (int glyphIndex = 0; glyphIndex < glyphs.length; glyphIndex++) {
-                int n = 2 * glyphIndex;
-                if (ai.gposAdjustments[glyphIndex] != null) {
-                    for (int p = 0; p < 4; p++) {
-                        positions[n + p] += ai.gposAdjustments[glyphIndex][p] / 1000f;
-                    }
-                }
-                positions[n + 2] += positions[n] + getGlyphWidth(glyphIndex);
+    private static final int[] PA_ZERO = new int[4];
+
+    /**
+     * Build glyph position array.
+     * @param glyphAsCharIter iterator for mapped glyphs as char codes (not glyph codes)
+     * @param dp optionally null glyph position adjustments array
+     * @param lsa optionally null letter space adjustments array
+     * @return array of floats that denote [X,Y] position pairs for each glyph including
+     * including an implied subsequent glyph; i.e., returned array contains one more pair
+     * than the numbers of glyphs, where the position denoted by this last pair represents
+     * the position after the last glyph has incurred advancement
+     */
+    private float[] buildGlyphPositions(final CharacterIterator glyphAsCharIter, int[][] dp, MinOptMax[] lsa) {
+        int numGlyphs = glyphAsCharIter.getEndIndex() - glyphAsCharIter.getBeginIndex();
+        float[] positions = new float[2 * (numGlyphs + 1)];
+        float xc = 0f;
+        float yc = 0f;
+        if (dp != null) {
+            for (int i = 0; i < numGlyphs + 1; ++i) {
+                int[] pa = ((i >= dp.length) || (dp[i] == null)) ? PA_ZERO : dp[i];
+                float xo = xc + ((float) pa[0]) / 1000f;
+                float yo = yc - ((float) pa[1]) / 1000f;
+                float xa = getGlyphWidth(i) + ((float) pa[2]) / 1000f;
+                float ya = ((float) pa[3]) / 1000f;
+                int k = 2 * i;
+                positions[k + 0] = xo;
+                positions[k + 1] = yo;
+                xc += xa;
+                yc += ya;
             }
-        } else {
-            for (int i = 0, n = 2; i < glyphs.length; i++, n += 2) {
-                int kern = i < glyphs.length - 1 && letterSpaceAdjustments[i + 1] != null
-                        ? letterSpaceAdjustments[i + 1].getOpt()
-                        : 0;
-                positions[n] = positions[n - 2] + getGlyphWidth(i) + kern / 1000f;
-                positions[n + 1] = 0;
+        } else if (lsa != null) {
+            for (int i = 0; i < numGlyphs + 1; ++i) {
+                MinOptMax sa = (((i + 1) >= lsa.length) || (lsa[i + 1] == null)) ? MinOptMax.ZERO : lsa[i + 1];
+                float xo = xc;
+                float yo = yc;
+                float xa = getGlyphWidth(i) + sa.getOpt() / 1000f;
+                float ya = 0;
+                int k = 2 * i;
+                positions[k + 0] = xo;
+                positions[k + 1] = yo;
+                xc += xa;
+                yc += ya;
             }
         }
+        return positions;
     }
 
     private float getGlyphWidth(int index) {
-        return fontMetrics.getWidth(glyphs[index], fontSize) / 1000000f;
+        if (index < glyphs.length) {
+            return fontMetrics.getWidth(glyphs[index], fontSize) / 1000000f;
+        } else {
+            return 0f;
+        }
     }
 
     public GVTFont getFont() {
diff --git a/src/java/org/apache/fop/svg/text/ComplexGlyphLayout.java b/src/java/org/apache/fop/svg/text/ComplexGlyphLayout.java
new file mode 100644 (file)
index 0000000..06a7188
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * 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.svg.text;
+
+import java.awt.font.FontRenderContext;
+import java.awt.geom.Point2D;
+import java.text.AttributedCharacterIterator;
+
+import org.apache.batik.gvt.font.GVTFont;
+import org.apache.batik.gvt.font.GVTGlyphVector;
+import org.apache.batik.gvt.text.GVTAttributedCharacterIterator;
+import org.apache.batik.gvt.text.GlyphLayout;
+
+import org.apache.fop.fonts.Font;
+import org.apache.fop.svg.font.FOPGVTFont;
+
+public class ComplexGlyphLayout extends GlyphLayout {
+
+    public ComplexGlyphLayout(AttributedCharacterIterator aci,
+        int [] charMap, Point2D offset, FontRenderContext frc) {
+        super(aci, charMap, offset, frc);
+    }
+
+    @Override
+    protected void doExplicitGlyphLayout() {
+        GVTGlyphVector gv = this.gv;
+        gv.performDefaultLayout();
+        int ng = gv.getNumGlyphs();
+        if (ng > 0) {
+            this.advance = gv.getGlyphPosition(ng);
+        } else {
+            this.advance = new Point2D.Float(0, 0);
+        }
+        this.layoutApplied  = true;
+    }
+
+    public static final boolean mayRequireComplexLayout(AttributedCharacterIterator aci) {
+        boolean rv = false;
+        GVTAttributedCharacterIterator.TextAttribute attrFont = GVTAttributedCharacterIterator.TextAttribute.GVT_FONT;
+        int indexSave = aci.getIndex();
+        aci.first();
+        do {
+            GVTFont gvtFont = (GVTFont) aci.getAttribute(attrFont);
+            if (gvtFont == null) {
+                continue;
+            } else {
+                if (gvtFont instanceof FOPGVTFont) {
+                    Font f = ((FOPGVTFont) gvtFont).getFont();
+                    if (f.performsSubstitution() || f.performsPositioning()) {
+                        rv = true;
+                        break;
+                    }
+                }
+                aci.setIndex(aci.getRunLimit(attrFont));
+            }
+        } while (aci.next() != AttributedCharacterIterator.DONE);
+        aci.setIndex(indexSave);
+        return rv;
+    }
+
+}
index 5c1fb2c862af83b6619b3f2c1d1d599b81f84e3c..04af70544b861e256c77645db5246444b893f484 100644 (file)
@@ -50,7 +50,7 @@ public class GlyphLayoutTestCase extends FOPGVTGlyphVectorTest {
 
     private void testGlyphLayout(boolean useAdvanced) {
         FOPGVTFont font = loadFont(useAdvanced);
-        glyphVector = (FOPGVTGlyphVector) font.createGlyphVector(null, "L\u201DP,V.F,A\u2019LT.");
+        glyphVector = (FOPGVTGlyphVector) font.createGlyphVector(null, "L\u201DP,V.F,A\u2019LT.", "DFLT", "dflt");
         glyphVector.performDefaultLayout();
         // Values in font units (unitsPerEm = 2048), glyph width - kern
         int[] widths = {