git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1619960 13f79535-47bb-0310-9956-ffa450edef68pull/28/head
@@ -564,6 +564,9 @@ list of possible build targets. | |||
<include name="org/apache/fop/apps/FOPException.class"/> | |||
<include name="org/apache/fop/apps/io/**"/> | |||
<include name="org/apache/fop/area/AreaTreeControl*"/> | |||
<include name="org/apache/fop/complexscripts/bidi/BidiClass.class"/> | |||
<include name="org/apache/fop/complexscripts/bidi/BidiConstants.class"/> | |||
<include name="org/apache/fop/complexscripts/bidi/UnicodeBidiAlgorithm.class"/> | |||
<include name="org/apache/fop/complexscripts/fonts/*.class"/> | |||
<include name="org/apache/fop/complexscripts/util/GlyphTester.class"/> | |||
<include name="org/apache/fop/events/EventProducer.class"/> | |||
@@ -573,7 +576,9 @@ list of possible build targets. | |||
<include name="org/apache/fop/svg/**"/> | |||
<include name="org/apache/fop/fonts/**"/> | |||
<include name="org/apache/fop/render/shading/**"/> | |||
<include name="org/apache/fop/traits/Direction.class"/> | |||
<include name="org/apache/fop/traits/MinOptMax.class"/> | |||
<include name="org/apache/fop/traits/TraitEnum.class"/> | |||
<include name="org/apache/fop/util/CMYKColorSpace*.class"/> | |||
<include name="org/apache/fop/util/Color*.class"/> | |||
<include name="org/apache/fop/util/ASCII*.class"/> |
@@ -54,6 +54,10 @@ | |||
<Class name="org.apache.fop.render.intermediate.IFGraphicContext"/> | |||
<Method name="clone"/> | |||
</And> | |||
<And> | |||
<Class name="org.apache.fop.svg.text.BidiAttributedCharacterIterator"/> | |||
<Method name="clone"/> | |||
</And> | |||
</Or> | |||
</Match> | |||
<Match> |
@@ -38,12 +38,27 @@ public final class CharMirror { | |||
*/ | |||
public static String mirror(String s) { | |||
StringBuffer sb = new StringBuffer(s); | |||
for (int i = 0, n = sb.length(); i < n; i++) { | |||
for (int i = 0, n = sb.length(); i < n; ++i) { | |||
sb.setCharAt(i, (char) mirror(sb.charAt(i))); | |||
} | |||
return sb.toString(); | |||
} | |||
/** | |||
* Determine if string has a mirrorable character. | |||
* @param s a string whose characters are to be tested for mirrorability | |||
* @return true if some character can be mirrored | |||
*/ | |||
public static boolean hasMirrorable(String s) { | |||
for (int i = 0, n = s.length(); i < n; ++i) { | |||
char c = s.charAt(i); | |||
if (Arrays.binarySearch(mirroredCharacters, c) >= 0) { | |||
return true; | |||
} | |||
} | |||
return false; | |||
} | |||
private static int[] mirroredCharacters = { | |||
0x0028, | |||
0x0029, |
@@ -21,6 +21,8 @@ package org.apache.fop.fo; | |||
import java.awt.Color; | |||
import java.nio.CharBuffer; | |||
import java.text.CharacterIterator; | |||
import java.text.StringCharacterIterator; | |||
import java.util.NoSuchElementException; | |||
import java.util.Stack; | |||
@@ -48,6 +50,9 @@ public class FOText extends FONode implements CharSequence, TextFragment { | |||
/** the <code>CharBuffer</code> containing the text */ | |||
private CharBuffer charBuffer; | |||
// cached iterator | |||
private CharacterIterator charIterator; | |||
// The value of FO traits (refined properties) that apply to #PCDATA | |||
// (aka implicit sequence of fo:character) | |||
private CommonFont commonFont; | |||
@@ -649,16 +654,39 @@ public class FOText extends FONode implements CharSequence, TextFragment { | |||
return country; | |||
} | |||
/** @return the language trait */ | |||
@Override | |||
public synchronized CharacterIterator getIterator() { | |||
if (charIterator != null) { | |||
charIterator = new StringCharacterIterator(toString()); | |||
} | |||
return charIterator; | |||
} | |||
@Override | |||
public int getBeginIndex() { | |||
return 0; | |||
} | |||
@Override | |||
public int getEndIndex() { | |||
return length(); | |||
} | |||
@Override | |||
public String getLanguage() { | |||
return language; | |||
} | |||
/** @return the script trait */ | |||
@Override | |||
public String getScript() { | |||
return script; | |||
} | |||
@Override | |||
public int getBidiLevel() { | |||
return length() > 0 ? bidiLevelAt(0) : -1; | |||
} | |||
/** {@inheritDoc} */ | |||
public String toString() { | |||
if (charBuffer == null) { |
@@ -19,7 +19,6 @@ | |||
package org.apache.fop.fonts; | |||
import java.util.Collections; | |||
import java.util.List; | |||
import org.apache.commons.logging.Log; | |||
@@ -320,30 +319,6 @@ public class GlyphMapping { | |||
areaIPD = areaIPD.plus(idp); | |||
} | |||
public void reverse() { | |||
if (mapping == null) { | |||
return; | |||
} | |||
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 + "]" |
@@ -19,13 +19,53 @@ | |||
package org.apache.fop.fonts; | |||
import java.text.CharacterIterator; | |||
/** | |||
* Encapsulates a sub-sequence (fragement) of a text iterator (or other text source), | |||
* where begin index and end index are indices into larger text iterator that denote | |||
* [begin,end) of sub-sequence range. Additionally associated with a designated script | |||
* (or "auto"), a designated language (or "none"), and a (single) bidi level (or -1 | |||
* if not known). | |||
*/ | |||
public interface TextFragment { | |||
/** | |||
* Obtain reference to underlying iterator. | |||
*/ | |||
CharacterIterator getIterator(); | |||
/** | |||
* Obtain beginning index (inclusive) of sub-sequence of fragment in overall text source. | |||
*/ | |||
int getBeginIndex(); | |||
/** | |||
* Obtain ending index (exclusive) of sub-sequence of fragment in overall text source. | |||
*/ | |||
int getEndIndex(); | |||
/** | |||
* Obtain associated script (if designated) or "auto" if not. | |||
*/ | |||
String getScript(); | |||
/** | |||
* Obtain associated language (if designated) or "none" if not. | |||
*/ | |||
String getLanguage(); | |||
char charAt(int index); | |||
/** | |||
* Obtain associated bidi level (if known) or -1 if not. | |||
*/ | |||
int getBidiLevel(); | |||
/** | |||
* Obtain character at specified index within this fragment's sub-sequence, | |||
* where index 0 corresponds to beginning index in overal text source, and | |||
* subSequenceIndex must be less than ending index - beginning index. | |||
*/ | |||
char charAt(int subSequenceIndex); | |||
CharSequence subSequence(int startIndex, int endIndex); | |||
} |
@@ -31,14 +31,17 @@ import java.awt.geom.Point2D; | |||
import java.io.IOException; | |||
import java.text.AttributedCharacterIterator; | |||
import java.util.List; | |||
import java.util.Set; | |||
import org.apache.commons.logging.Log; | |||
import org.apache.commons.logging.LogFactory; | |||
import org.apache.batik.bridge.SVGGVTFont; | |||
import org.apache.batik.gvt.TextNode; | |||
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.renderer.StrokingTextPainter.TextChunk; | |||
import org.apache.batik.gvt.text.GlyphLayout; | |||
import org.apache.batik.gvt.text.TextLayoutFactory; | |||
import org.apache.batik.gvt.text.TextPaintInfo; | |||
@@ -48,6 +51,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.BidiAttributedCharacterIterator; | |||
import org.apache.fop.svg.text.ComplexGlyphLayout; | |||
import org.apache.fop.util.CharUtilities; | |||
@@ -213,6 +217,114 @@ public abstract class NativeTextPainter extends StrokingTextPainter { | |||
return chars; | |||
} | |||
// Use FOP's bidi algorithm implementation and sub-divide each chunk into runs | |||
// that respect bidi level boundaries. N.B. batik does not sub-divide chunks at | |||
// bidi level boundaries because it performs eager reordering. In FOP, we need | |||
// to perform lazy reordering after character to glyph mapping occurs since | |||
// that mapping process requires logical (not visual) ordered input. | |||
@Override | |||
public List computeTextRuns(TextNode node, AttributedCharacterIterator nodeACI, | |||
AttributedCharacterIterator [] chunkACIs) { | |||
nodeACI.first(); | |||
int defaultBidiLevel = (nodeACI.getAttribute(WRITING_MODE) == WRITING_MODE_RTL) ? 1 : 0; | |||
for (int i = 0, n = chunkACIs.length; i < n; ++i) { | |||
chunkACIs[i] = new BidiAttributedCharacterIterator(chunkACIs[i], defaultBidiLevel); | |||
} | |||
return super.computeTextRuns(node, nodeACI, chunkACIs, (int[][]) null); | |||
} | |||
// We want to sub-divide text chunks into distinct runs at bidi level boundaries. | |||
@Override | |||
protected Set getTextRunBoundaryAttributes() { | |||
Set textRunBoundaryAttributes = super.getTextRunBoundaryAttributes(); | |||
if (!textRunBoundaryAttributes.contains(BIDI_LEVEL)) { | |||
textRunBoundaryAttributes.add(BIDI_LEVEL); | |||
} | |||
return textRunBoundaryAttributes; | |||
} | |||
// Perform reordering of runs. | |||
@Override | |||
protected List reorderTextRuns(TextChunk chunk, List runs) { | |||
// 1. determine min/max bidi levels for runs | |||
int mn = -1; | |||
int mx = -1; | |||
for (TextRun r : (List<TextRun>) runs) { | |||
int level = r.getBidiLevel(); | |||
if (level >= 0) { | |||
if ((mn < 0) || (level < mn)) { | |||
mn = level; | |||
} | |||
if ((mx < 0) || (level > mx)) { | |||
mx = level; | |||
} | |||
} | |||
} | |||
// 2. reorder from maximum level to minimum odd level | |||
if (mx > 0) { | |||
for (int l1 = mx, l2 = ((mn & 1) == 0) ? (mn + 1) : mn; l1 >= l2; l1--) { | |||
runs = reorderRuns(runs, l1); | |||
} | |||
} | |||
// 3. reverse glyphs (and perform mirroring) in runs as needed | |||
boolean mirror = true; | |||
reverseGlyphs(runs, mirror); | |||
return runs; | |||
} | |||
private List reorderRuns(List runs, int level) { | |||
assert level >= 0; | |||
List runsNew = new java.util.ArrayList(); | |||
for (int i = 0, n = runs.size(); i < n; i++) { | |||
TextRun tri = (TextRun) runs.get(i); | |||
if (tri.getBidiLevel() < level) { | |||
runsNew.add(tri); | |||
} else { | |||
int s = i; | |||
int e = s; | |||
while (e < n) { | |||
TextRun tre = (TextRun) runs.get(e); | |||
if (tre.getBidiLevel() < level) { | |||
break; | |||
} else { | |||
e++; | |||
} | |||
} | |||
if (s < e) { | |||
runsNew.addAll(reverseRuns(runs, s, e)); | |||
} | |||
i = e - 1; | |||
} | |||
} | |||
if (!runsNew.equals(runs)) { | |||
runs = runsNew; | |||
} | |||
return runs; | |||
} | |||
private List reverseRuns(List runs, int s, int e) { | |||
int n = e - s; | |||
List runsNew = new java.util.ArrayList(n); | |||
if (n > 0) { | |||
for (int i = 0; i < n; i++) { | |||
int k = (n - i - 1); | |||
TextRun tr = (TextRun) runs.get(s + k); | |||
tr.reverse(); | |||
runsNew.add(tr); | |||
} | |||
} | |||
return runsNew; | |||
} | |||
private void reverseGlyphs(List runs, boolean mirror) { | |||
for (TextRun r : (List<TextRun>) runs) { | |||
r.maybeReverseGlyphs(mirror); | |||
} | |||
} | |||
protected abstract void preparePainting(Graphics2D g2d); | |||
protected abstract void saveGraphicsState() throws IOException; |
@@ -26,10 +26,7 @@ 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; | |||
@@ -185,12 +182,4 @@ 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); | |||
} | |||
} |
@@ -20,12 +20,18 @@ | |||
package org.apache.fop.svg.font; | |||
import java.awt.font.FontRenderContext; | |||
import java.awt.geom.AffineTransform; | |||
import java.awt.geom.Rectangle2D; | |||
import java.text.AttributedCharacterIterator; | |||
import java.text.CharacterIterator; | |||
import java.util.Collections; | |||
import java.util.List; | |||
import org.apache.batik.gvt.text.GVTAttributedCharacterIterator; | |||
import org.apache.fop.fonts.GlyphMapping; | |||
import org.apache.fop.complexscripts.util.CharAssociation; | |||
import org.apache.fop.complexscripts.util.CharMirror; | |||
import org.apache.fop.fonts.Font; | |||
class ComplexGlyphVector extends FOPGVTGlyphVector { | |||
@@ -35,6 +41,9 @@ class ComplexGlyphVector extends FOPGVTGlyphVector { | |||
public static final Integer WRITING_MODE_RTL | |||
= GVTAttributedCharacterIterator.TextAttribute.WRITING_MODE_RTL; | |||
private boolean reversed; // true if this GV was reversed | |||
private boolean mirrored; // true if this GV required some mirroring | |||
ComplexGlyphVector(FOPGVTFont font, final CharacterIterator iter, FontRenderContext frc) { | |||
super(font, iter, frc); | |||
} | |||
@@ -43,14 +52,129 @@ class ComplexGlyphVector extends FOPGVTGlyphVector { | |||
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(); | |||
public boolean isReversed() { | |||
return reversed; | |||
} | |||
public void maybeReverse(boolean mirror) { | |||
if (!reversed) { | |||
if (glyphs != null) { | |||
if (glyphs.length > 1) { | |||
reverse(glyphs); | |||
if (associations != null) { | |||
Collections.reverse(associations); | |||
} | |||
if (positions != null) { | |||
reverse(positions); | |||
} | |||
if (boundingBoxes != null) { | |||
reverse(boundingBoxes); | |||
} | |||
if (glyphTransforms != null) { | |||
reverse(glyphTransforms); | |||
} | |||
if (glyphVisibilities != null) { | |||
reverse(glyphVisibilities); | |||
} | |||
} | |||
if (maybeMirror()) { | |||
mirrored = true; | |||
} | |||
} | |||
reversed = true; | |||
} | |||
} | |||
// For each mirrorable character in source text, perform substitution of | |||
// associated glyph with a mirrored glyph. N.B. The source text is NOT | |||
// modified, only the mapped glyphs. | |||
private boolean maybeMirror() { | |||
boolean mirrored = false; | |||
String s = text.subSequence(text.getBeginIndex(), text.getEndIndex()).toString(); | |||
if (CharMirror.hasMirrorable(s)) { | |||
String m = CharMirror.mirror(s); | |||
assert m.length() == s.length(); | |||
for (int i = 0, n = m.length(); i < n; ++i) { | |||
char cs = s.charAt(i); | |||
char cm = m.charAt(i); | |||
if (cm != cs) { | |||
if (substituteMirroredGlyph(i, cm)) { | |||
mirrored = true; | |||
} | |||
} | |||
} | |||
} | |||
return mirrored; | |||
} | |||
private boolean substituteMirroredGlyph(int index, char mirror) { | |||
Font f = font.getFont(); | |||
int gi = 0; | |||
for (CharAssociation ca : (List<CharAssociation>) associations) { | |||
if (ca.contained(index, 1)) { | |||
setGlyphCode(gi, f.mapChar(mirror)); | |||
return true; | |||
} else { | |||
++gi; | |||
} | |||
} | |||
return false; | |||
} | |||
private static void reverse(boolean[] ba) { | |||
for (int i = 0, n = ba.length, m = n / 2; i < m; i++) { | |||
int k = n - i - 1; | |||
boolean t = ba [ k ]; | |||
ba [ k ] = ba [ i ]; | |||
ba [ i ] = t; | |||
} | |||
} | |||
private static void reverse(int[] ia) { | |||
for (int i = 0, n = ia.length, m = n / 2; i < m; i++) { | |||
int k = n - i - 1; | |||
int t = ia [ k ]; | |||
ia [ k ] = ia [ i ]; | |||
ia [ i ] = t; | |||
} | |||
} | |||
private static void reverse(float[] fa) { | |||
int skip = 2; | |||
int numPositions = fa.length / skip; | |||
for (int i = 0, n = numPositions, m = n / 2; i < m; ++i) { | |||
int j = n - i - 1; | |||
for (int k = 0; k < skip; ++k) { | |||
int l1 = i * skip + k; | |||
int l2 = j * skip + k; | |||
float t = fa [ l2 ]; | |||
fa [ l2 ] = fa [ l1 ]; | |||
fa [ l1 ] = t; | |||
} | |||
} | |||
float runAdvanceX = fa [ 0 ]; | |||
for (int i = 0, n = fa.length; i < n; i += 2) { | |||
fa [ i ] = runAdvanceX - fa [ i ]; | |||
} | |||
} | |||
private static void reverse(Rectangle2D[] ra) { | |||
for (int i = 0, n = ra.length, m = n / 2; i < m; i++) { | |||
int k = n - i - 1; | |||
Rectangle2D t = ra [ k ]; | |||
ra [ k ] = ra [ i ]; | |||
ra [ i ] = t; | |||
} | |||
} | |||
private static void reverse(AffineTransform[] ta) { | |||
for (int i = 0, n = ta.length, m = n / 2; i < m; i++) { | |||
int k = n - i - 1; | |||
AffineTransform t = ta [ k ]; | |||
ta [ k ] = ta [ i ]; | |||
ta [ i ] = t; | |||
} | |||
} | |||
} |
@@ -49,9 +49,9 @@ import org.apache.fop.traits.MinOptMax; | |||
class FOPGVTGlyphVector implements GVTGlyphVector { | |||
protected final CharacterIterator charIter; | |||
protected final TextFragment text; | |||
private final FOPGVTFont font; | |||
protected final FOPGVTFont font; | |||
private final int fontSize; | |||
@@ -65,18 +65,18 @@ class FOPGVTGlyphVector implements GVTGlyphVector { | |||
protected float[] positions; | |||
private Rectangle2D[] boundingBoxes; | |||
protected Rectangle2D[] boundingBoxes; | |||
private GeneralPath outline; | |||
protected GeneralPath outline; | |||
private AffineTransform[] glyphTransforms; | |||
protected AffineTransform[] glyphTransforms; | |||
private boolean[] glyphVisibilities; | |||
protected boolean[] glyphVisibilities; | |||
private Rectangle2D logicalBounds; | |||
protected Rectangle2D logicalBounds; | |||
FOPGVTGlyphVector(FOPGVTFont font, final CharacterIterator iter, FontRenderContext frc) { | |||
this.charIter = iter; | |||
this.text = new SVGTextFragment(iter); | |||
this.font = font; | |||
Font f = font.getFont(); | |||
this.fontSize = f.getFontSize(); | |||
@@ -86,14 +86,12 @@ class FOPGVTGlyphVector implements GVTGlyphVector { | |||
public void performDefaultLayout() { | |||
Font f = font.getFont(); | |||
TextFragment text = new SVGTextFragment(charIter); | |||
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, true, true); | |||
maybeReverse(mapping); | |||
MinOptMax[] letterSpaceAdjustments = new MinOptMax[text.getEndIndex() - text.getBeginIndex()]; | |||
GlyphMapping mapping = GlyphMapping.doGlyphMapping(text, text.getBeginIndex(), text.getEndIndex(), | |||
f, letterSpaceIPD, letterSpaceAdjustments, '\0', '\0', false, text.getBidiLevel(), true, true); | |||
CharacterIterator glyphAsCharIter = | |||
mapping.mapping != null ? new StringCharacterIterator(mapping.mapping) : charIter; | |||
mapping.mapping != null ? new StringCharacterIterator(mapping.mapping) : text.getIterator(); | |||
this.glyphs = buildGlyphs(f, glyphAsCharIter); | |||
this.associations = mapping.associations; | |||
this.positions = buildGlyphPositions(glyphAsCharIter, mapping.gposAdjustments, letterSpaceAdjustments); | |||
@@ -102,9 +100,6 @@ class FOPGVTGlyphVector implements GVTGlyphVector { | |||
this.glyphTransforms = new AffineTransform[this.glyphs.length]; | |||
} | |||
protected void maybeReverse(GlyphMapping mapping) { | |||
} | |||
private static class SVGTextFragment implements TextFragment { | |||
private final CharacterIterator charIter; | |||
@@ -113,6 +108,8 @@ class FOPGVTGlyphVector implements GVTGlyphVector { | |||
private String language; | |||
private int level = -1; | |||
SVGTextFragment(CharacterIterator charIter) { | |||
this.charIter = charIter; | |||
if (charIter instanceof AttributedCharacterIterator) { | |||
@@ -120,9 +117,27 @@ class FOPGVTGlyphVector implements GVTGlyphVector { | |||
aci.first(); | |||
this.script = (String) aci.getAttribute(GVTAttributedCharacterIterator.TextAttribute.SCRIPT); | |||
this.language = (String) aci.getAttribute(GVTAttributedCharacterIterator.TextAttribute.LANGUAGE); | |||
Integer level = (Integer) aci.getAttribute(GVTAttributedCharacterIterator.TextAttribute.BIDI_LEVEL); | |||
if (level != null) { | |||
this.level = level.intValue(); | |||
} | |||
} | |||
} | |||
public CharacterIterator getIterator() { | |||
return charIter; | |||
} | |||
public int getBeginIndex() { | |||
return charIter.getBeginIndex(); | |||
} | |||
public int getEndIndex() { | |||
return charIter.getEndIndex(); | |||
} | |||
// TODO - [GA] the following appears to be broken because it ignores | |||
// sttart and end index arguments | |||
public CharSequence subSequence(int startIndex, int endIndex) { | |||
StringBuilder sb = new StringBuilder(); | |||
for (char c = charIter.first(); c != CharacterIterator.DONE; c = charIter.next()) { | |||
@@ -147,6 +162,10 @@ class FOPGVTGlyphVector implements GVTGlyphVector { | |||
} | |||
} | |||
public int getBidiLevel() { | |||
return level; | |||
} | |||
public char charAt(int index) { | |||
return charIter.setIndex(index - charIter.getBeginIndex()); | |||
} | |||
@@ -225,6 +244,10 @@ class FOPGVTGlyphVector implements GVTGlyphVector { | |||
return frc; | |||
} | |||
public void setGlyphCode(int glyphIndex, int glyphCode) { | |||
glyphs[glyphIndex] = glyphCode; | |||
} | |||
public int getGlyphCode(int glyphIndex) { | |||
return glyphs[glyphIndex]; | |||
} | |||
@@ -372,6 +395,13 @@ class FOPGVTGlyphVector implements GVTGlyphVector { | |||
return endGlyphIndex - startGlyphIndex + 1; | |||
} | |||
public boolean isReversed() { | |||
return false; | |||
} | |||
public void maybeReverse(boolean mirror) { | |||
} | |||
public void draw(Graphics2D graphics2d, AttributedCharacterIterator aci) { | |||
// NOP | |||
} |