Browse Source

FOP-2391: preliminary (but incomplete) support for complex script text nodes in svg foreign object



git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1615385 13f79535-47bb-0310-9956-ffa450edef68
tags/fop-2_0
Glenn Adams 10 years ago
parent
commit
7320ab1c52
27 changed files with 920 additions and 575 deletions
  1. 11
    1
      findbugs-exclude.xml
  2. BIN
      lib/batik-all-trunk.jar
  3. 10
    9
      src/java/org/apache/fop/complexscripts/fonts/GlyphProcessingState.java
  4. 3
    2
      src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitutionState.java
  5. 4
    3
      src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitutionTable.java
  6. 6
    2
      src/java/org/apache/fop/complexscripts/fonts/Substitutable.java
  7. 6
    5
      src/java/org/apache/fop/complexscripts/scripts/ArabicScriptProcessor.java
  8. 5
    4
      src/java/org/apache/fop/complexscripts/scripts/DefaultScriptProcessor.java
  9. 5
    4
      src/java/org/apache/fop/complexscripts/scripts/DevanagariScriptProcessor.java
  10. 5
    4
      src/java/org/apache/fop/complexscripts/scripts/GujaratiScriptProcessor.java
  11. 5
    4
      src/java/org/apache/fop/complexscripts/scripts/GurmukhiScriptProcessor.java
  12. 5
    4
      src/java/org/apache/fop/complexscripts/scripts/IndicScriptProcessor.java
  13. 490
    0
      src/java/org/apache/fop/complexscripts/util/CharAssociation.java
  14. 2
    462
      src/java/org/apache/fop/complexscripts/util/GlyphSequence.java
  15. 7
    4
      src/java/org/apache/fop/fonts/Font.java
  16. 44
    15
      src/java/org/apache/fop/fonts/GlyphMapping.java
  17. 5
    4
      src/java/org/apache/fop/fonts/LazyFont.java
  18. 21
    7
      src/java/org/apache/fop/fonts/MultiByteFont.java
  19. 2
    1
      src/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java
  20. 6
    4
      src/java/org/apache/fop/render/java2d/CustomFontMetricsMapper.java
  21. 21
    0
      src/java/org/apache/fop/svg/NativeTextPainter.java
  22. 11
    0
      src/java/org/apache/fop/svg/PDFTextPainter.java
  23. 56
    0
      src/java/org/apache/fop/svg/font/ComplexGlyphVector.java
  24. 24
    4
      src/java/org/apache/fop/svg/font/FOPGVTFont.java
  25. 87
    31
      src/java/org/apache/fop/svg/font/FOPGVTGlyphVector.java
  26. 78
    0
      src/java/org/apache/fop/svg/text/ComplexGlyphLayout.java
  27. 1
    1
      test/java/org/apache/fop/svg/font/GlyphLayoutTestCase.java

+ 11
- 1
findbugs-exclude.xml View File

@@ -141,6 +141,16 @@
</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>
@@ -5463,7 +5473,7 @@
<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>

BIN
lib/batik-all-trunk.jar View File


+ 10
- 9
src/java/org/apache/fop/complexscripts/fonts/GlyphProcessingState.java View 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);
}


+ 3
- 2
src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitutionState.java View 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;

+ 4
- 3
src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitutionTable.java View 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);

+ 6
- 2
src/java/org/apache/fop/complexscripts/fonts/Substitutable.java View 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);

}

+ 6
- 5
src/java/org/apache/fop/complexscripts/scripts/ArabicScriptProcessor.java View 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) {

+ 5
- 4
src/java/org/apache/fop/complexscripts/scripts/DefaultScriptProcessor.java View 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;

+ 5
- 4
src/java/org/apache/fop/complexscripts/scripts/DevanagariScriptProcessor.java View 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 ])) {

+ 5
- 4
src/java/org/apache/fop/complexscripts/scripts/GujaratiScriptProcessor.java View 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 ])) {

+ 5
- 4
src/java/org/apache/fop/complexscripts/scripts/GurmukhiScriptProcessor.java View 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 ])) {

+ 5
- 4
src/java/org/apache/fop/complexscripts/scripts/IndicScriptProcessor.java View 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) {

+ 490
- 0
src/java/org/apache/fop/complexscripts/util/CharAssociation.java View File

@@ -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();
}

}

+ 2
- 462
src/java/org/apache/fop/complexscripts/util/GlyphSequence.java View 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;
}

}

}

+ 7
- 4
src/java/org/apache/fop/fonts/Font.java View 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();
}

+ 44
- 15
src/java/org/apache/fop/fonts/GlyphMapping.java View 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 + "]"

+ 5
- 4
src/java/org/apache/fop/fonts/LazyFont.java View 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;
}

+ 21
- 7
src/java/org/apache/fop/fonts/MultiByteFont.java View 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);
}

/**

+ 2
- 1
src/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java View 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;

+ 6
- 4
src/java/org/apache/fop/render/java2d/CustomFontMetricsMapper.java View 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;
}

+ 21
- 0
src/java/org/apache/fop/svg/NativeTextPainter.java View 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;
}

}

+ 11
- 0
src/java/org/apache/fop/svg/PDFTextPainter.java View 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);
}

}

+ 56
- 0
src/java/org/apache/fop/svg/font/ComplexGlyphVector.java View File

@@ -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();
}
}
}

}

+ 24
- 4
src/java/org/apache/fop/svg/font/FOPGVTFont.java View 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");
}

+ 87
- 31
src/java/org/apache/fop/svg/font/FOPGVTGlyphVector.java View 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() {

+ 78
- 0
src/java/org/apache/fop/svg/text/ComplexGlyphLayout.java View File

@@ -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;
}

}

+ 1
- 1
test/java/org/apache/fop/svg/font/GlyphLayoutTestCase.java View 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 = {

Loading…
Cancel
Save