diff options
author | Vincent Hennebert <vhennebert@apache.org> | 2013-07-29 21:45:20 +0000 |
---|---|---|
committer | Vincent Hennebert <vhennebert@apache.org> | 2013-07-29 21:45:20 +0000 |
commit | f8e822efe1de8bd8192dbb8ff035b9a79f876614 (patch) | |
tree | 8d38f873dd101e6ed0c2ba3b4dfa29e1eb0fdc60 /src/java/org/apache/fop/render/ps/PSTextPainter.java | |
parent | c0b99ad44d0e1409008886e2f687c46f4ac05d9d (diff) | |
download | xmlgraphics-fop-f8e822efe1de8bd8192dbb8ff035b9a79f876614.tar.gz xmlgraphics-fop-f8e822efe1de8bd8192dbb8ff035b9a79f876614.zip |
Directly use FOP fonts to lay out SVG images for PDF, PS and AFP outputs.
The metrics are now taken from FOP configured fonts and no longer from AWT equivalents. That avoids discrepancies in case AWT and FOP use slightly different fonts, or if the font is not installed on the system. That actually also avoids having to install the font on the system.
FOP is also used for the primary layout of text (prior to SVG-specific transforms like translation or rotation) for consistency between SVG and XSL-FO.
This is a joint work from Peter Hancock and myself.
git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/branches/Temp_FopFontsForSVG@1508208 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'src/java/org/apache/fop/render/ps/PSTextPainter.java')
-rw-r--r-- | src/java/org/apache/fop/render/ps/PSTextPainter.java | 361 |
1 files changed, 132 insertions, 229 deletions
diff --git a/src/java/org/apache/fop/render/ps/PSTextPainter.java b/src/java/org/apache/fop/render/ps/PSTextPainter.java index eb2188026..44b7b08a3 100644 --- a/src/java/org/apache/fop/render/ps/PSTextPainter.java +++ b/src/java/org/apache/fop/render/ps/PSTextPainter.java @@ -19,25 +19,21 @@ package org.apache.fop.render.ps; -import java.awt.BasicStroke; import java.awt.Color; import java.awt.Graphics2D; import java.awt.Paint; import java.awt.Shape; import java.awt.Stroke; import java.awt.geom.AffineTransform; -import java.awt.geom.Ellipse2D; -import java.awt.geom.GeneralPath; import java.awt.geom.PathIterator; import java.awt.geom.Point2D; +import java.awt.geom.Point2D.Double; import java.io.IOException; -import java.text.AttributedCharacterIterator; import java.util.Iterator; +import java.util.LinkedList; import java.util.List; -import org.apache.batik.gvt.font.GVTGlyphVector; import org.apache.batik.gvt.text.TextPaintInfo; -import org.apache.batik.gvt.text.TextSpanLayout; import org.apache.xmlgraphics.java2d.ps.PSGraphics2D; import org.apache.xmlgraphics.ps.PSGenerator; @@ -48,7 +44,6 @@ import org.apache.fop.fonts.FontMetrics; import org.apache.fop.fonts.LazyFont; import org.apache.fop.fonts.MultiByteFont; import org.apache.fop.svg.NativeTextPainter; -import org.apache.fop.util.CharUtilities; import org.apache.fop.util.HexEncoder; /** @@ -62,10 +57,20 @@ import org.apache.fop.util.HexEncoder; */ public class PSTextPainter extends NativeTextPainter { - private static final boolean DEBUG = false; - private FontResourceCache fontResources; + private PSGraphics2D ps; + + private PSGenerator gen; + + private TextUtil textUtil; + + private boolean flushCurrentRun; + + private PSTextRun psRun; + + private Double relPos; + private static final AffineTransform IDENTITY_TRANSFORM = new AffineTransform(); /** @@ -82,165 +87,26 @@ public class PSTextPainter extends NativeTextPainter { return g2d instanceof PSGraphics2D; } - /** {@inheritDoc} */ - protected void paintTextRun(TextRun textRun, Graphics2D g2d) throws IOException { - AttributedCharacterIterator runaci = textRun.getACI(); - runaci.first(); - - TextPaintInfo tpi = (TextPaintInfo)runaci.getAttribute(PAINT_INFO); - if (tpi == null || !tpi.visible) { - return; - } - if ((tpi != null) && (tpi.composite != null)) { - g2d.setComposite(tpi.composite); - } - - //------------------------------------ - TextSpanLayout layout = textRun.getLayout(); - logTextRun(runaci, layout); - CharSequence chars = collectCharacters(runaci); - runaci.first(); //Reset ACI - - final PSGraphics2D ps = (PSGraphics2D)g2d; - final PSGenerator gen = ps.getPSGenerator(); + @Override + protected void preparePainting(Graphics2D g2d) { + ps = (PSGraphics2D) g2d; + gen = ps.getPSGenerator(); ps.preparePainting(); + } - if (DEBUG) { - log.debug("Text: " + chars); - gen.commentln("%Text: " + chars); - } - - GeneralPath debugShapes = null; - if (DEBUG) { - debugShapes = new GeneralPath(); - } - - TextUtil textUtil = new TextUtil(gen); - textUtil.setupFonts(runaci); - if (!textUtil.hasFonts()) { - //Draw using Java2D when no native fonts are available - textRun.getLayout().draw(g2d); - return; - } - + @Override + protected void saveGraphicsState() throws IOException { gen.saveGraphicsState(); - gen.concatMatrix(g2d.getTransform()); - Shape imclip = g2d.getClip(); - clip(ps, imclip); - - gen.writeln("BT"); //beginTextObject() - - AffineTransform localTransform = new AffineTransform(); - Point2D prevPos = null; - GVTGlyphVector gv = layout.getGlyphVector(); - PSTextRun psRun = new PSTextRun(); //Used to split a text run into smaller runs - for (int index = 0, c = gv.getNumGlyphs(); index < c; index++) { - char ch = chars.charAt(index); - boolean visibleChar = gv.isGlyphVisible(index) - || (CharUtilities.isAnySpace(ch) && !CharUtilities.isZeroWidthSpace(ch)); - logCharacter(ch, layout, index, visibleChar); - if (!visibleChar) { - continue; - } - Point2D glyphPos = gv.getGlyphPosition(index); - - AffineTransform glyphTransform = gv.getGlyphTransform(index); - if (log.isTraceEnabled()) { - log.trace("pos " + glyphPos + ", transform " + glyphTransform); - } - if (DEBUG) { - Shape sh = gv.getGlyphLogicalBounds(index); - if (sh == null) { - sh = new Ellipse2D.Double(glyphPos.getX(), glyphPos.getY(), 2, 2); - } - debugShapes.append(sh, false); - } - - //Exact position of the glyph - localTransform.setToIdentity(); - localTransform.translate(glyphPos.getX(), glyphPos.getY()); - if (glyphTransform != null) { - localTransform.concatenate(glyphTransform); - } - localTransform.scale(1, -1); - - boolean flushCurrentRun = false; - //Try to optimize by combining characters using the same font and on the same line. - if (glyphTransform != null) { - //Happens for text-on-a-path - flushCurrentRun = true; - } - if (psRun.getRunLength() >= 128) { - //Don't let a run get too long - flushCurrentRun = true; - } - - //Note the position of the glyph relative to the previous one - Point2D relPos; - if (prevPos == null) { - relPos = new Point2D.Double(0, 0); - } else { - relPos = new Point2D.Double( - glyphPos.getX() - prevPos.getX(), - glyphPos.getY() - prevPos.getY()); - } - if (psRun.vertChanges == 0 - && psRun.getHorizRunLength() > 2 - && relPos.getY() != 0) { - //new line - flushCurrentRun = true; - } - - //Select the actual character to paint - char paintChar = (CharUtilities.isAnySpace(ch) ? ' ' : ch); - - //Select (sub)font for character - Font f = textUtil.selectFontForChar(paintChar); - char mapped = f.mapChar(ch); - boolean fontChanging = textUtil.isFontChanging(f, mapped); - if (fontChanging) { - flushCurrentRun = true; - } - - if (flushCurrentRun) { - //Paint the current run and reset for the next run - psRun.paint(ps, textUtil, tpi); - psRun.reset(); - } - - //Track current run - psRun.addCharacter(paintChar, relPos); - psRun.noteStartingTransformation(localTransform); - - //Change font if necessary - if (fontChanging) { - textUtil.setCurrentFont(f, mapped); - } + } - //Update last position - prevPos = glyphPos; - } - psRun.paint(ps, textUtil, tpi); - gen.writeln("ET"); //endTextObject() + @Override + protected void restoreGraphicsState() throws IOException { gen.restoreGraphicsState(); - - if (DEBUG) { - //Paint debug shapes - g2d.setStroke(new BasicStroke(0)); - g2d.setColor(Color.LIGHT_GRAY); - g2d.draw(debugShapes); - } } - private void applyColor(Paint paint, final PSGenerator gen) throws IOException { - if (paint == null) { - return; - } else if (paint instanceof Color) { - Color col = (Color)paint; - gen.useColor(col); - } else { - log.warn("Paint not supported: " + paint.toString()); - } + @Override + protected void setInitialTransform(AffineTransform transform) throws IOException { + gen.concatMatrix(transform); } private PSFontResource getResourceForFont(Font f, String postfix) { @@ -248,7 +114,8 @@ public class PSTextPainter extends NativeTextPainter { return this.fontResources.getFontResourceForFontKey(key); } - private void clip(PSGraphics2D ps, Shape shape) throws IOException { + @Override + protected void clip(Shape shape) throws IOException { if (shape == null) { return; } @@ -258,17 +125,76 @@ public class PSTextPainter extends NativeTextPainter { ps.getPSGenerator().writeln("clip"); } + @Override + protected void beginTextObject() throws IOException { + gen.writeln("BT"); + textUtil = new TextUtil(); + psRun = new PSTextRun(); //Used to split a text run into smaller runs + } + + @Override + protected void endTextObject() throws IOException { + psRun.paint(ps, textUtil, tpi); + gen.writeln("ET"); + } + + @Override + protected void positionGlyph(Point2D prevPos, Point2D glyphPos, boolean reposition) { + flushCurrentRun = false; + //Try to optimize by combining characters using the same font and on the same line. + if (reposition) { + //Happens for text-on-a-path + flushCurrentRun = true; + } + if (psRun.getRunLength() >= 128) { + //Don't let a run get too long + flushCurrentRun = true; + } + + //Note the position of the glyph relative to the previous one + if (prevPos == null) { + relPos = new Point2D.Double(0, 0); + } else { + relPos = new Point2D.Double( + glyphPos.getX() - prevPos.getX(), + glyphPos.getY() - prevPos.getY()); + } + if (psRun.vertChanges == 0 + && psRun.getHorizRunLength() > 2 + && relPos.getY() != 0) { + //new line + flushCurrentRun = true; + } + } + + @Override + protected void writeGlyph(char glyph, AffineTransform localTransform) throws IOException { + boolean fontChanging = textUtil.isFontChanging(font, glyph); + if (fontChanging) { + flushCurrentRun = true; + } + + if (flushCurrentRun) { + //Paint the current run and reset for the next run + psRun.paint(ps, textUtil, tpi); + psRun.reset(); + } + + //Track current run + psRun.addGlyph(glyph, relPos); + psRun.noteStartingTransformation(localTransform); + + //Change font if necessary + if (fontChanging) { + textUtil.setCurrentFont(font, glyph); + } + } + private class TextUtil { - private PSGenerator gen; - private Font[] fonts; private Font currentFont; private int currentEncoding = -1; - public TextUtil(PSGenerator gen) { - this.gen = gen; - } - public boolean isMultiByte(Font f) { FontMetrics metrics = f.getFontMetrics(); boolean multiByte = metrics instanceof MultiByteFont || metrics instanceof LazyFont @@ -276,15 +202,6 @@ public class PSTextPainter extends NativeTextPainter { return multiByte; } - public Font selectFontForChar(char ch) { - for (int i = 0, c = fonts.length; i < c; i++) { - if (fonts[i].hasChar(ch)) { - return fonts[i]; - } - } - return fonts[0]; //TODO Maybe fall back to painting with shapes - } - public void writeTextMatrix(AffineTransform transform) throws IOException { double[] matrix = new double[6]; transform.getMatrix(matrix); @@ -335,27 +252,19 @@ public class PSTextPainter extends NativeTextPainter { setCurrentFont(font, encoding); } - public void setupFonts(AttributedCharacterIterator runaci) { - this.fonts = findFonts(runaci); - } - - public boolean hasFonts() { - return (fonts != null) && (fonts.length > 0); - } - } private class PSTextRun { private AffineTransform textTransform; - private List relativePositions = new java.util.LinkedList(); - private StringBuffer currentChars = new StringBuffer(); + private List<Point2D> relativePositions = new LinkedList<Point2D>(); + private StringBuffer currentGlyphs = new StringBuffer(); private int horizChanges = 0; private int vertChanges = 0; public void reset() { textTransform = null; - currentChars.setLength(0); + currentGlyphs.setLength(0); horizChanges = 0; vertChanges = 0; relativePositions.clear(); @@ -369,9 +278,9 @@ public class PSTextPainter extends NativeTextPainter { return 0; } - public void addCharacter(char paintChar, Point2D relPos) { + public void addGlyph(char glyph, Point2D relPos) { addRelativePosition(relPos); - currentChars.append(paintChar); + currentGlyphs.append(glyph); } private void addRelativePosition(Point2D relPos) { @@ -393,7 +302,7 @@ public class PSTextPainter extends NativeTextPainter { } public int getRunLength() { - return currentChars.length(); + return currentGlyphs.length(); } private boolean isXShow() { @@ -407,9 +316,6 @@ public class PSTextPainter extends NativeTextPainter { public void paint(PSGraphics2D g2d, TextUtil textUtil, TextPaintInfo tpi) throws IOException { if (getRunLength() > 0) { - if (log.isDebugEnabled()) { - log.debug("Text run: " + currentChars); - } textUtil.writeTextMatrix(this.textTransform); if (isXShow()) { log.debug("Horizontal text: xshow"); @@ -431,25 +337,20 @@ public class PSTextPainter extends NativeTextPainter { private void paintXYShow(PSGraphics2D g2d, TextUtil textUtil, Paint paint, boolean x, boolean y) throws IOException { - PSGenerator gen = textUtil.gen; - char firstChar = this.currentChars.charAt(0); - //Font only has to be setup up before the first character - Font f = textUtil.selectFontForChar(firstChar); - char mapped = f.mapChar(firstChar); - textUtil.selectFont(f, mapped); - textUtil.setCurrentFont(f, mapped); - applyColor(paint, gen); - - boolean multiByte = textUtil.isMultiByte(f); + char glyph = currentGlyphs.charAt(0); + textUtil.selectFont(font, glyph); + textUtil.setCurrentFont(font, glyph); + applyColor(paint); + + boolean multiByte = textUtil.isMultiByte(font); StringBuffer sb = new StringBuffer(); sb.append(multiByte ? '<' : '('); - for (int i = 0, c = this.currentChars.length(); i < c; i++) { - char ch = this.currentChars.charAt(i); - mapped = f.mapChar(ch); + for (int i = 0, c = this.currentGlyphs.length(); i < c; i++) { + glyph = this.currentGlyphs.charAt(i); if (multiByte) { - sb.append(HexEncoder.encode(mapped)); + sb.append(HexEncoder.encode(glyph)); } else { - char codepoint = (char) (mapped % 256); + char codepoint = (char) (glyph % 256); PSGenerator.escapeChar(codepoint, sb); } } @@ -457,9 +358,9 @@ public class PSTextPainter extends NativeTextPainter { if (x || y) { sb.append("\n["); int idx = 0; - Iterator iter = this.relativePositions.iterator(); + Iterator<Point2D> iter = this.relativePositions.iterator(); while (iter.hasNext()) { - Point2D pt = (Point2D)iter.next(); + Point2D pt = iter.next(); if (idx > 0) { if (x) { sb.append(format(gen, pt.getX())); @@ -500,6 +401,17 @@ public class PSTextPainter extends NativeTextPainter { gen.writeln(sb.toString()); } + private void applyColor(Paint paint) throws IOException { + if (paint == null) { + return; + } else if (paint instanceof Color) { + Color col = (Color) paint; + gen.useColor(col); + } else { + log.warn("Paint not supported: " + paint.toString()); + } + } + private String format(PSGenerator gen, double coord) { if (Math.abs(coord) < 0.00001) { return "0"; @@ -510,30 +422,21 @@ public class PSTextPainter extends NativeTextPainter { private void paintStrokedGlyphs(PSGraphics2D g2d, TextUtil textUtil, Paint strokePaint, Stroke stroke) throws IOException { - PSGenerator gen = textUtil.gen; - - applyColor(strokePaint, gen); + applyColor(strokePaint); PSGraphics2D.applyStroke(stroke, gen); - Font f = null; - Iterator iter = this.relativePositions.iterator(); + Iterator<Point2D> iter = this.relativePositions.iterator(); iter.next(); Point2D pos = new Point2D.Double(0, 0); gen.writeln("0 0 M"); - for (int i = 0, c = this.currentChars.length(); i < c; i++) { - char ch = this.currentChars.charAt(0); + for (int i = 0, c = this.currentGlyphs.length(); i < c; i++) { + char mapped = this.currentGlyphs.charAt(i); if (i == 0) { - //Font only has to be setup up before the first character - f = textUtil.selectFontForChar(ch); - } - char mapped = f.mapChar(ch); - if (i == 0) { - textUtil.selectFont(f, mapped); - textUtil.setCurrentFont(f, mapped); + textUtil.selectFont(font, mapped); + textUtil.setCurrentFont(font, mapped); } //add glyph outlines to current path - mapped = f.mapChar(this.currentChars.charAt(i)); - FontMetrics metrics = f.getFontMetrics(); + FontMetrics metrics = font.getFontMetrics(); boolean multiByte = metrics instanceof MultiByteFont || metrics instanceof LazyFont && ((LazyFont) metrics).getRealFont() instanceof MultiByteFont; @@ -542,14 +445,14 @@ public class PSTextPainter extends NativeTextPainter { gen.write(HexEncoder.encode(mapped)); gen.write(">"); } else { - char codepoint = (char)(mapped % 256); + char codepoint = (char) (mapped % 256); gen.write("(" + codepoint + ")"); } gen.writeln(" false charpath"); if (iter.hasNext()) { //Position for the next character - Point2D pt = (Point2D)iter.next(); + Point2D pt = iter.next(); pos.setLocation(pos.getX() + pt.getX(), pos.getY() - pt.getY()); gen.writeln(gen.formatDouble5(pos.getX()) + " " + gen.formatDouble5(pos.getY()) + " M"); |