ASF Bugzilla – Attachment 23467 Details for
Bug 47000
[PATCH] Full-featured TextPainter for PS output
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
Remember
[x]
|
Forgot Password
Login:
[x]
[patch]
PSTextPainter patch
PSTextPainter.diff (text/plain), 114.82 KB, created by
Jeremias Maerki
on 2009-04-08 12:39:12 UTC
(
hide
)
Description:
PSTextPainter patch
Filename:
MIME Type:
Creator:
Jeremias Maerki
Created:
2009-04-08 12:39:12 UTC
Size:
114.82 KB
patch
obsolete
>Index: status.xml >=================================================================== >--- status.xml (revision 761554) >+++ status.xml (working copy) >@@ -58,6 +58,10 @@ > documents. Example: the fix of marks layering will be such a case when it's done. > --> > <release version="FOP Trunk" date="TBD"> >+ <action context="Renderers" dev="JM" type="add"> >+ Added a custom text painter for rendering SVG text using text operators when rendering >+ to PostScript or EPS. Text is no longer painted as shapes, thus creating much smaller files. >+ </action> > <action context="Renderers" dev="JM" type="fix"> > Fixed a bug that left the PrintRenderer unconfigured even if a configuration was > specified for "application/X-fop-print". >Index: src/java/org/apache/fop/svg/PDFTranscoder.java >=================================================================== >--- src/java/org/apache/fop/svg/PDFTranscoder.java (revision 761552) >+++ src/java/org/apache/fop/svg/PDFTranscoder.java (working copy) >@@ -23,35 +23,20 @@ > import java.io.BufferedOutputStream; > import java.io.IOException; > import java.io.OutputStream; >-import java.io.InputStream; > >-import javax.xml.transform.Source; >-import javax.xml.transform.stream.StreamSource; >- > import org.w3c.dom.Document; > import org.w3c.dom.svg.SVGLength; > > import org.apache.avalon.framework.configuration.Configurable; > import org.apache.avalon.framework.configuration.Configuration; >-import org.apache.avalon.framework.configuration.ConfigurationException; >-import org.apache.avalon.framework.configuration.DefaultConfiguration; > import org.apache.batik.bridge.BridgeContext; > import org.apache.batik.bridge.UnitProcessor; > import org.apache.batik.bridge.UserAgent; > import org.apache.batik.ext.awt.RenderingHintsKeyExt; > import org.apache.batik.transcoder.TranscoderException; > import org.apache.batik.transcoder.TranscoderOutput; >-import org.apache.batik.transcoder.TranscodingHints; > import org.apache.batik.transcoder.image.ImageTranscoder; >-import org.apache.batik.transcoder.keys.BooleanKey; >-import org.apache.batik.transcoder.keys.FloatKey; >-import org.apache.batik.util.ParsedURL; > >-import org.apache.xmlgraphics.image.loader.ImageContext; >-import org.apache.xmlgraphics.image.loader.ImageManager; >-import org.apache.xmlgraphics.image.loader.ImageSessionContext; >-import org.apache.xmlgraphics.image.loader.impl.AbstractImageSessionContext; >- > import org.apache.fop.Version; > import org.apache.fop.fonts.FontInfo; > >@@ -91,27 +76,9 @@ > public class PDFTranscoder extends AbstractFOPTranscoder > implements Configurable { > >- /** >- * The key is used to specify the resolution for on-the-fly images generated >- * due to complex effects like gradients and filters. >- */ >- public static final TranscodingHints.Key KEY_DEVICE_RESOLUTION = new FloatKey(); >- >- /** >- * The key is used to specify whether the available fonts should be automatically >- * detected. The alternative is to configure the transcoder manually using a configuration >- * file. >- */ >- public static final TranscodingHints.Key KEY_AUTO_FONTS = new BooleanKey(); >- >- private Configuration cfg = null; >- > /** Graphics2D instance that is used to paint to */ > protected PDFDocumentGraphics2D graphics = null; > >- private ImageManager imageManager; >- private ImageSessionContext imageSessionContext; >- > /** > * Constructs a new <tt>PDFTranscoder</tt>. > */ >@@ -133,11 +100,6 @@ > }; > } > >- /** {@inheritDoc} */ >- public void configure(Configuration cfg) throws ConfigurationException { >- this.cfg = cfg; >- } >- > /** > * Transcodes the specified Document as an image in the specified output. > * >@@ -155,28 +117,13 @@ > + Version.getVersion() > + ": PDF Transcoder for Batik"); > if (hints.containsKey(KEY_DEVICE_RESOLUTION)) { >- graphics.setDeviceDPI(((Float)hints.get(KEY_DEVICE_RESOLUTION)).floatValue()); >+ graphics.setDeviceDPI(getDeviceResolution()); > } > > setupImageInfrastructure(uri); > > try { >- Configuration effCfg = this.cfg; >- if (effCfg == null) { >- //By default, enable font auto-detection if no cfg is given >- boolean autoFonts = true; >- if (hints.containsKey(KEY_AUTO_FONTS)) { >- autoFonts = ((Boolean)hints.get(KEY_AUTO_FONTS)).booleanValue(); >- } >- if (autoFonts) { >- DefaultConfiguration c = new DefaultConfiguration("pdf-transcoder"); >- DefaultConfiguration fonts = new DefaultConfiguration("fonts"); >- c.addChild(fonts); >- DefaultConfiguration autodetect = new DefaultConfiguration("auto-detect"); >- fonts.addChild(autodetect); >- effCfg = c; >- } >- } >+ Configuration effCfg = getEffectiveConfiguration(); > > if (effCfg != null) { > PDFDocumentGraphics2DConfigurator configurator >@@ -242,39 +189,6 @@ > } > } > >- private void setupImageInfrastructure(final String baseURI) { >- final ImageContext imageContext = new ImageContext() { >- public float getSourceResolution() { >- return 25.4f / userAgent.getPixelUnitToMillimeter(); >- } >- }; >- this.imageManager = new ImageManager(imageContext); >- this.imageSessionContext = new AbstractImageSessionContext() { >- >- public ImageContext getParentContext() { >- return imageContext; >- } >- >- public float getTargetResolution() { >- return graphics.getDeviceDPI(); >- } >- >- public Source resolveURI(String uri) { >- System.out.println("resolve " + uri); >- try { >- ParsedURL url = new ParsedURL(baseURI, uri); >- InputStream in = url.openStream(); >- StreamSource source = new StreamSource(in, url.toString()); >- return source; >- } catch (IOException ioe) { >- userAgent.displayError(ioe); >- return null; >- } >- } >- >- }; >- } >- > /** {@inheritDoc} */ > protected BridgeContext createBridgeContext() { > //For compatibility with Batik 1.6 >@@ -288,7 +202,7 @@ > fontInfo = null; > } > BridgeContext ctx = new PDFBridgeContext(userAgent, fontInfo, >- this.imageManager, this.imageSessionContext); >+ getImageManager(), getImageSessionContext()); > return ctx; > } > >Index: src/java/org/apache/fop/svg/PDFTextPainter.java >=================================================================== >--- src/java/org/apache/fop/svg/PDFTextPainter.java (revision 761552) >+++ src/java/org/apache/fop/svg/PDFTextPainter.java (working copy) >@@ -25,28 +25,18 @@ > import java.awt.Paint; > import java.awt.Shape; > import java.awt.Stroke; >-import java.awt.font.TextAttribute; > import java.awt.geom.AffineTransform; > import java.awt.geom.Ellipse2D; > import java.awt.geom.GeneralPath; > import java.awt.geom.Point2D; >-import java.lang.reflect.Method; > import java.text.AttributedCharacterIterator; >-import java.util.Iterator; >-import java.util.List; > >-import org.apache.batik.bridge.SVGFontFamily; >-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.renderer.StrokingTextPainter; >-import org.apache.batik.gvt.text.GVTAttributedCharacterIterator; > import org.apache.batik.gvt.text.TextPaintInfo; > import org.apache.batik.gvt.text.TextSpanLayout; > > import org.apache.fop.fonts.Font; > import org.apache.fop.fonts.FontInfo; >-import org.apache.fop.fonts.FontTriplet; > import org.apache.fop.util.CharUtilities; > > /** >@@ -59,194 +49,160 @@ > * > * @version $Id$ > */ >-public class PDFTextPainter extends StrokingTextPainter { >+class PDFTextPainter extends NativeTextPainter { > > private static final boolean DEBUG = false; > >- private final boolean strokeText = false; >- private final FontInfo fontInfo; >- > /** > * Create a new PDF text painter with the given font information. > * @param fi the font info > */ > public PDFTextPainter(FontInfo fi) { >- fontInfo = fi; >+ super(fi); > } > > /** {@inheritDoc} */ >- protected void paintTextRuns(List textRuns, Graphics2D g2d) { >- if (DEBUG) { >- System.out.println("paintTextRuns: count = " + textRuns.size()); >- //fontInfo.dumpAllTripletsToSystemOut(); >- } >- if (!(g2d instanceof PDFGraphics2D) || strokeText) { >- super.paintTextRuns(textRuns, g2d); >+ protected boolean isSupported(Graphics2D g2d) { >+ return g2d instanceof PDFGraphics2D; >+ } >+ >+ /** {@inheritDoc} */ >+ protected void paintTextRun(TextRun textRun, Graphics2D g2d) { >+ 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 PDFGraphics2D pdf = (PDFGraphics2D)g2d; > PDFTextUtil textUtil = new PDFTextUtil(pdf.fontInfo) { > protected void write(String code) { > pdf.currentStream.write(code); > } > }; >- for (int i = 0; i < textRuns.size(); i++) { >- TextRun textRun = (TextRun)textRuns.get(i); >- AttributedCharacterIterator runaci = textRun.getACI(); >- runaci.first(); > >- TextPaintInfo tpi = (TextPaintInfo)runaci.getAttribute(PAINT_INFO); >- if (tpi == null || !tpi.visible) { >- continue; >- } >- if ((tpi != null) && (tpi.composite != null)) { >- g2d.setComposite(tpi.composite); >- } >+ if (DEBUG) { >+ log.debug("Text: " + chars); >+ pdf.currentStream.write("%Text: " + chars + "\n"); >+ } > >- //------------------------------------ >- TextSpanLayout layout = textRun.getLayout(); >- if (DEBUG) { >- int charCount = runaci.getEndIndex() - runaci.getBeginIndex(); >- System.out.println("================================================"); >- System.out.println("New text run:"); >- System.out.println("char count: " + charCount); >- System.out.println("range: " >- + runaci.getBeginIndex() + " - " + runaci.getEndIndex()); >- System.out.println("glyph count: " + layout.getGlyphCount()); //=getNumGlyphs() >- } >- //Gather all characters of the run >- StringBuffer chars = new StringBuffer(); >- for (runaci.first(); runaci.getIndex() < runaci.getEndIndex();) { >- chars.append(runaci.current()); >- runaci.next(); >- } >- runaci.first(); >- if (DEBUG) { >- System.out.println("Text: " + chars); >- pdf.currentStream.write("%Text: " + chars + "\n"); >- } >+ GeneralPath debugShapes = null; >+ if (DEBUG) { >+ debugShapes = new GeneralPath(); >+ } > >- GeneralPath debugShapes = null; >- if (DEBUG) { >- debugShapes = new GeneralPath(); >- } >+ Font[] fonts = findFonts(runaci); >+ if (fonts == null || fonts.length == 0) { >+ //Draw using Java2D when no native fonts are available >+ textRun.getLayout().draw(g2d); >+ return; >+ } > >- Font[] fonts = findFonts(runaci); >- if (fonts == null || fonts.length == 0) { >- //Draw using Java2D >- textRun.getLayout().draw(g2d); >- continue; >- } >+ textUtil.saveGraphicsState(); >+ textUtil.concatMatrix(g2d.getTransform()); >+ Shape imclip = g2d.getClip(); >+ pdf.writeClip(imclip); > >- textUtil.saveGraphicsState(); >- textUtil.concatMatrix(g2d.getTransform()); >- Shape imclip = g2d.getClip(); >- pdf.writeClip(imclip); >+ applyColorAndPaint(tpi, pdf); > >- applyColorAndPaint(tpi, pdf); >+ textUtil.beginTextObject(); >+ textUtil.setFonts(fonts); >+ boolean stroke = (tpi.strokePaint != null) >+ && (tpi.strokeStroke != null); >+ textUtil.setTextRenderingMode(tpi.fillPaint != null, stroke, false); > >- textUtil.beginTextObject(); >- textUtil.setFonts(fonts); >- boolean stroke = (tpi.strokePaint != null) >- && (tpi.strokeStroke != null); >- textUtil.setTextRenderingMode(tpi.fillPaint != null, stroke, false); >+ AffineTransform localTransform = new AffineTransform(); >+ Point2D prevPos = null; >+ double prevVisibleCharWidth = 0.0; >+ GVTGlyphVector gv = layout.getGlyphVector(); >+ 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 localTransform = new AffineTransform(); >- Point2D prevPos = null; >- double prevVisibleCharWidth = 0.0; >- GVTGlyphVector gv = layout.getGlyphVector(); >- 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)); >- if (DEBUG) { >- System.out.println("glyph " + index >- + " -> " + layout.getGlyphIndex(index) + " => " + ch); >- if (CharUtilities.isAnySpace(ch) && ch != 32) { >- System.out.println("Space found: " + Integer.toHexString(ch)); >- } >- if (ch == CharUtilities.ZERO_WIDTH_JOINER) { >- System.out.println("ZWJ found: " + Integer.toHexString(ch)); >- } >- if (ch == CharUtilities.SOFT_HYPHEN) { >- System.out.println("Soft hyphen found: " + Integer.toHexString(ch)); >- } >- if (!visibleChar) { >- System.out.println("Invisible glyph found: " + Integer.toHexString(ch)); >- } >+ AffineTransform glyphTransform = gv.getGlyphTransform(index); >+ //TODO Glyph transforms could be refined so not every char has to be painted >+ //with its own TJ command (stretch/squeeze case could be optimized) >+ 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); > } >- if (!visibleChar) { >- continue; >- } >- Point2D p = gv.getGlyphPosition(index); >+ debugShapes.append(sh, false); >+ } > >- AffineTransform glyphTransform = gv.getGlyphTransform(index); >- //TODO Glyph transforms could be refined so not every char has to be painted >- //with its own TJ command (stretch/squeeze case could be optimized) >- if (DEBUG) { >- System.out.println("pos " + p + ", transform " + glyphTransform); >- Shape sh; >- sh = gv.getGlyphLogicalBounds(index); >- if (sh == null) { >- sh = new Ellipse2D.Double(p.getX(), p.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); > >- //Exact position of the glyph >- localTransform.setToIdentity(); >- localTransform.translate(p.getX(), p.getY()); >- if (glyphTransform != null) { >- localTransform.concatenate(glyphTransform); >- } >- localTransform.scale(1, -1); >- >- boolean yPosChanged = (prevPos == null >- || prevPos.getY() != p.getY() >- || glyphTransform != null); >- if (yPosChanged) { >- if (index > 0) { >- textUtil.writeTJ(); >- textUtil.writeTextMatrix(localTransform); >- } >- } else { >- double xdiff = p.getX() - prevPos.getX(); >- //Width of previous character >- Font font = textUtil.getCurrentFont(); >- double cw = prevVisibleCharWidth; >- double effxdiff = (1000 * xdiff) - cw; >- if (effxdiff != 0) { >- double adjust = (-effxdiff / font.getFontSize()); >- textUtil.adjustGlyphTJ(adjust * 1000); >- } >- if (DEBUG) { >- System.out.println("==> x diff: " + xdiff + ", " + effxdiff >- + ", charWidth: " + cw); >- } >- } >- Font f = textUtil.selectFontForChar(ch); >- if (f != textUtil.getCurrentFont()) { >+ boolean yPosChanged = (prevPos == null >+ || prevPos.getY() != glyphPos.getY() >+ || glyphTransform != null); >+ if (yPosChanged) { >+ if (index > 0) { > textUtil.writeTJ(); >- textUtil.setCurrentFont(f); >- textUtil.writeTf(f); > textUtil.writeTextMatrix(localTransform); > } >- char paintChar = (CharUtilities.isAnySpace(ch) ? ' ' : ch); >- textUtil.writeTJChar(paintChar); >- >- //Update last position >- prevPos = p; >- prevVisibleCharWidth = textUtil.getCurrentFont().getCharWidth(chars.charAt(index)); >+ } else { >+ double xdiff = glyphPos.getX() - prevPos.getX(); >+ //Width of previous character >+ Font font = textUtil.getCurrentFont(); >+ double cw = prevVisibleCharWidth; >+ double effxdiff = (1000 * xdiff) - cw; >+ if (effxdiff != 0) { >+ double adjust = (-effxdiff / font.getFontSize()); >+ textUtil.adjustGlyphTJ(adjust * 1000); >+ } >+ if (log.isTraceEnabled()) { >+ log.trace("==> x diff: " + xdiff + ", " + effxdiff >+ + ", charWidth: " + cw); >+ } > } >- textUtil.writeTJ(); >- textUtil.endTextObject(); >- textUtil.restoreGraphicsState(); >- if (DEBUG) { >- g2d.setStroke(new BasicStroke(0)); >- g2d.setColor(Color.LIGHT_GRAY); >- g2d.draw(debugShapes); >+ Font f = textUtil.selectFontForChar(ch); >+ if (f != textUtil.getCurrentFont()) { >+ textUtil.writeTJ(); >+ textUtil.setCurrentFont(f); >+ textUtil.writeTf(f); >+ textUtil.writeTextMatrix(localTransform); > } >+ char paintChar = (CharUtilities.isAnySpace(ch) ? ' ' : ch); >+ textUtil.writeTJChar(paintChar); >+ >+ //Update last position >+ prevPos = glyphPos; >+ prevVisibleCharWidth = textUtil.getCurrentFont().getCharWidth(chars.charAt(index)); > } >+ textUtil.writeTJ(); >+ textUtil.endTextObject(); >+ textUtil.restoreGraphicsState(); >+ if (DEBUG) { >+ g2d.setStroke(new BasicStroke(0)); >+ g2d.setColor(Color.LIGHT_GRAY); >+ g2d.draw(debugShapes); >+ } > } > > private void applyColorAndPaint(TextPaintInfo tpi, PDFGraphics2D pdf) { >@@ -271,85 +227,4 @@ > pdf.applyAlpha(fillAlpha, PDFGraphics2D.OPAQUE); > } > >- private Font[] findFonts(AttributedCharacterIterator aci) { >- List fonts = new java.util.ArrayList(); >- List gvtFonts = (List) aci.getAttribute( >- GVTAttributedCharacterIterator.TextAttribute.GVT_FONT_FAMILIES); >- Float posture = (Float) aci.getAttribute(TextAttribute.POSTURE); >- Float taWeight = (Float) aci.getAttribute(TextAttribute.WEIGHT); >- Float fontSize = (Float) aci.getAttribute(TextAttribute.SIZE); >- >- String style = ((posture != null) && (posture.floatValue() > 0.0)) >- ? Font.STYLE_ITALIC : Font.STYLE_NORMAL; >- int weight = ((taWeight != null) >- && (taWeight.floatValue() > 1.0)) ? Font.WEIGHT_BOLD >- : Font.WEIGHT_NORMAL; >- >- String firstFontFamily = null; >- >- //GVT_FONT can sometimes be different from the fonts in GVT_FONT_FAMILIES >- //or GVT_FONT_FAMILIES can even be empty and only GVT_FONT is set >- /* The following code section is not available until Batik 1.7 is released. */ >- GVTFont gvtFont = (GVTFont)aci.getAttribute( >- GVTAttributedCharacterIterator.TextAttribute.GVT_FONT); >- if (gvtFont != null) { >- try { >- Method method = gvtFont.getClass().getMethod("getFamilyName", null); >- String gvtFontFamily = (String)method.invoke(gvtFont, null); >- //TODO Uncomment the following line when Batik 1.7 is shipped with FOP >- //String gvtFontFamily = gvtFont.getFamilyName(); //Not available in Batik 1.6 >- if (DEBUG) { >- System.out.print(gvtFontFamily + ", "); >- } >- if (fontInfo.hasFont(gvtFontFamily, style, weight)) { >- FontTriplet triplet = fontInfo.fontLookup(gvtFontFamily, style, >- weight); >- int fsize = (int)(fontSize.floatValue() * 1000); >- fonts.add(fontInfo.getFontInstance(triplet, fsize)); >- } >- firstFontFamily = gvtFontFamily; >- } catch (Exception e) { >- //Most likely NoSuchMethodError here when using Batik 1.6 >- //Just skip this section in this case >- } >- } >- >- if (gvtFonts != null) { >- Iterator i = gvtFonts.iterator(); >- while (i.hasNext()) { >- GVTFontFamily fam = (GVTFontFamily) i.next(); >- if (fam instanceof SVGFontFamily) { >- return null; //Let Batik paint this text! >- } >- String fontFamily = fam.getFamilyName(); >- if (DEBUG) { >- System.out.print(fontFamily + ", "); >- } >- if (fontInfo.hasFont(fontFamily, style, weight)) { >- FontTriplet triplet = fontInfo.fontLookup(fontFamily, style, >- weight); >- int fsize = (int)(fontSize.floatValue() * 1000); >- fonts.add(fontInfo.getFontInstance(triplet, fsize)); >- } >- if (firstFontFamily == null) { >- firstFontFamily = fontFamily; >- } >- } >- } >- if (fonts.size() == 0) { >- if (firstFontFamily == null) { >- //This will probably never happen. Just to be on the safe side. >- firstFontFamily = "any"; >- } >- //lookup with fallback possibility (incl. substitution notification) >- FontTriplet triplet = fontInfo.fontLookup(firstFontFamily, style, weight); >- int fsize = (int)(fontSize.floatValue() * 1000); >- fonts.add(fontInfo.getFontInstance(triplet, fsize)); >- } >- if (DEBUG) { >- System.out.println(); >- } >- return (Font[])fonts.toArray(new Font[fonts.size()]); >- } >- > } >\ No newline at end of file >Index: src/java/org/apache/fop/svg/PDFDocumentGraphics2DConfigurator.java >=================================================================== >--- src/java/org/apache/fop/svg/PDFDocumentGraphics2DConfigurator.java (revision 761552) >+++ src/java/org/apache/fop/svg/PDFDocumentGraphics2DConfigurator.java (working copy) >@@ -55,6 +55,22 @@ > > //Fonts > try { >+ FontInfo fontInfo = createFontInfo(cfg); >+ graphics.setFontInfo(fontInfo); >+ } catch (FOPException e) { >+ throw new ConfigurationException("Error while setting up fonts", e); >+ } >+ } >+ >+ /** >+ * Creates the {@link FontInfo} instance for the given configuration. >+ * @param cfg the configuration >+ * @return the font collection >+ * @throws FOPException if an error occurs while setting up the fonts >+ */ >+ public static FontInfo createFontInfo(Configuration cfg) throws FOPException { >+ FontInfo fontInfo = new FontInfo(); >+ if (cfg != null) { > FontResolver fontResolver = FontManager.createMinimalFontResolver(); > //TODO The following could be optimized by retaining the FontManager somewhere > FontManager fontManager = new FontManager(); >@@ -73,12 +89,11 @@ > if (fontManager.useCache()) { > fontManager.getFontCache().save(); > } >- FontInfo fontInfo = new FontInfo(); > FontSetup.setup(fontInfo, fontInfoList, fontResolver); >- graphics.setFontInfo(fontInfo); >- } catch (FOPException e) { >- throw new ConfigurationException("Error while setting up fonts", e); >+ } else { >+ FontSetup.setup(fontInfo); > } >+ return fontInfo; > } > > } >Index: src/java/org/apache/fop/svg/PDFBridgeContext.java >=================================================================== >--- src/java/org/apache/fop/svg/PDFBridgeContext.java (revision 761552) >+++ src/java/org/apache/fop/svg/PDFBridgeContext.java (working copy) >@@ -26,10 +26,12 @@ > import org.apache.batik.bridge.SVGTextElementBridge; > import org.apache.batik.bridge.UserAgent; > import org.apache.batik.gvt.TextPainter; >-import org.apache.fop.fonts.FontInfo; >+ > import org.apache.xmlgraphics.image.loader.ImageManager; > import org.apache.xmlgraphics.image.loader.ImageSessionContext; > >+import org.apache.fop.fonts.FontInfo; >+ > /** > * BridgeContext which registers the custom bridges for PDF output. > */ >@@ -38,11 +40,9 @@ > /** > * Constructs a new bridge context. > * @param userAgent the user agent >- * @param loader the Document Loader to use for referenced documents. >+ * @param documentLoader the Document Loader to use for referenced documents. > * @param fontInfo the font list for the text painter, may be null > * in which case text is painted as shapes >- * @param linkTransform AffineTransform to properly place links, >- * may be null > * @param imageManager an image manager > * @param imageSessionContext an image session context > * @param linkTransform AffineTransform to properly place links, >@@ -52,7 +52,8 @@ > FontInfo fontInfo, ImageManager imageManager, > ImageSessionContext imageSessionContext, > AffineTransform linkTransform) { >- super(userAgent, documentLoader, fontInfo, imageManager, imageSessionContext, linkTransform); >+ super(userAgent, documentLoader, fontInfo, >+ imageManager, imageSessionContext, linkTransform); > } > > /** >Index: src/java/org/apache/fop/svg/NativeTextPainter.java >=================================================================== >--- src/java/org/apache/fop/svg/NativeTextPainter.java (revision 0) >+++ src/java/org/apache/fop/svg/NativeTextPainter.java (revision 0) >@@ -0,0 +1,224 @@ >+/* >+ * 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; >+ >+import java.awt.Graphics2D; >+import java.awt.font.TextAttribute; >+import java.io.IOException; >+import java.text.AttributedCharacterIterator; >+import java.util.Iterator; >+import java.util.List; >+ >+import org.apache.batik.bridge.SVGFontFamily; >+import org.apache.batik.gvt.font.GVTFont; >+import org.apache.batik.gvt.font.GVTFontFamily; >+import org.apache.batik.gvt.renderer.StrokingTextPainter; >+import org.apache.batik.gvt.text.GVTAttributedCharacterIterator; >+import org.apache.batik.gvt.text.TextSpanLayout; >+import org.apache.commons.logging.Log; >+import org.apache.commons.logging.LogFactory; >+ >+import org.apache.fop.fonts.Font; >+import org.apache.fop.fonts.FontInfo; >+import org.apache.fop.fonts.FontTriplet; >+import org.apache.fop.util.CharUtilities; >+ >+/** >+ * Abstract base class for text painters that use specialized text commands native to an output >+ * format to render text. >+ */ >+public abstract class NativeTextPainter extends StrokingTextPainter { >+ >+ /** the logger for this class */ >+ protected Log log = LogFactory.getLog(NativeTextPainter.class); >+ >+ /** the font collection */ >+ protected final FontInfo fontInfo; >+ >+ /** >+ * Creates a new instance. >+ * @param fontInfo the font collection >+ */ >+ public NativeTextPainter(FontInfo fontInfo) { >+ this.fontInfo = fontInfo; >+ } >+ >+ /** >+ * Indicates whether the given {@link Graphics2D} instance if compatible with this text painter >+ * implementation. >+ * @param g2d the instance to check >+ * @return true if the instance is compatible. >+ */ >+ protected abstract boolean isSupported(Graphics2D g2d); >+ >+ /** >+ * Paints a single text run. >+ * @param textRun the text run >+ * @param g2d the target Graphics2D instance >+ * @throws IOException if an I/O error occurs while rendering the text >+ */ >+ protected abstract void paintTextRun(TextRun textRun, Graphics2D g2d) throws IOException; >+ >+ /** {@inheritDoc} */ >+ protected void paintTextRuns(List textRuns, Graphics2D g2d) { >+ if (log.isTraceEnabled()) { >+ log.trace("paintTextRuns: count = " + textRuns.size()); >+ } >+ if (!isSupported(g2d)) { >+ super.paintTextRuns(textRuns, g2d); >+ return; >+ } >+ for (int i = 0; i < textRuns.size(); i++) { >+ TextRun textRun = (TextRun)textRuns.get(i); >+ try { >+ paintTextRun(textRun, g2d); >+ } catch (IOException ioe) { >+ //No other possibility than to use a RuntimeException >+ throw new RuntimeException(ioe); >+ } >+ } >+ } >+ >+ /** >+ * Finds an array of suitable fonts for a given AttributedCharacterIterator. >+ * @param aci the character iterator >+ * @return the array of fonts >+ */ >+ protected Font[] findFonts(AttributedCharacterIterator aci) { >+ List fonts = new java.util.ArrayList(); >+ List gvtFonts = (List) aci.getAttribute( >+ GVTAttributedCharacterIterator.TextAttribute.GVT_FONT_FAMILIES); >+ Float posture = (Float) aci.getAttribute(TextAttribute.POSTURE); >+ Float taWeight = (Float) aci.getAttribute(TextAttribute.WEIGHT); >+ Float fontSize = (Float) aci.getAttribute(TextAttribute.SIZE); >+ >+ String style = ((posture != null) && (posture.floatValue() > 0.0)) >+ ? Font.STYLE_ITALIC : Font.STYLE_NORMAL; >+ int weight = ((taWeight != null) >+ && (taWeight.floatValue() > 1.0)) ? Font.WEIGHT_BOLD >+ : Font.WEIGHT_NORMAL; >+ >+ String firstFontFamily = null; >+ >+ //GVT_FONT can sometimes be different from the fonts in GVT_FONT_FAMILIES >+ //or GVT_FONT_FAMILIES can even be empty and only GVT_FONT is set >+ /* The following code section is not available until Batik 1.7 is released. */ >+ GVTFont gvtFont = (GVTFont)aci.getAttribute( >+ GVTAttributedCharacterIterator.TextAttribute.GVT_FONT); >+ if (gvtFont != null) { >+ try { >+ String gvtFontFamily = gvtFont.getFamilyName(); //Not available in Batik 1.6! >+ if (log.isDebugEnabled()) { >+ log.debug("Matching font family: " + gvtFontFamily); >+ } >+ if (fontInfo.hasFont(gvtFontFamily, style, weight)) { >+ FontTriplet triplet = fontInfo.fontLookup(gvtFontFamily, style, >+ weight); >+ int fsize = (int)(fontSize.floatValue() * 1000); >+ fonts.add(fontInfo.getFontInstance(triplet, fsize)); >+ } >+ firstFontFamily = gvtFontFamily; >+ } catch (Exception e) { >+ //Most likely NoSuchMethodError here when using Batik 1.6 >+ //Just skip this section in this case >+ } >+ } >+ >+ if (gvtFonts != null) { >+ Iterator i = gvtFonts.iterator(); >+ while (i.hasNext()) { >+ GVTFontFamily fam = (GVTFontFamily) i.next(); >+ if (fam instanceof SVGFontFamily) { >+ return null; //Let Batik paint this text! >+ } >+ String fontFamily = fam.getFamilyName(); >+ if (log.isDebugEnabled()) { >+ log.debug("Matching font family: " + fontFamily); >+ } >+ if (fontInfo.hasFont(fontFamily, style, weight)) { >+ FontTriplet triplet = fontInfo.fontLookup(fontFamily, style, >+ weight); >+ int fsize = (int)(fontSize.floatValue() * 1000); >+ fonts.add(fontInfo.getFontInstance(triplet, fsize)); >+ } >+ if (firstFontFamily == null) { >+ firstFontFamily = fontFamily; >+ } >+ } >+ } >+ if (fonts.size() == 0) { >+ if (firstFontFamily == null) { >+ //This will probably never happen. Just to be on the safe side. >+ firstFontFamily = "any"; >+ } >+ //lookup with fallback possibility (incl. substitution notification) >+ FontTriplet triplet = fontInfo.fontLookup(firstFontFamily, style, weight); >+ int fsize = (int)(fontSize.floatValue() * 1000); >+ fonts.add(fontInfo.getFontInstance(triplet, fsize)); >+ } >+ return (Font[])fonts.toArray(new Font[fonts.size()]); >+ } >+ >+ /** >+ * Collects all characters from an {@link AttributedCharacterIterator}. >+ * @param runaci the character iterator >+ * @return the characters >+ */ >+ protected CharSequence collectCharacters(AttributedCharacterIterator runaci) { >+ StringBuffer chars = new StringBuffer(); >+ for (runaci.first(); runaci.getIndex() < runaci.getEndIndex();) { >+ chars.append(runaci.current()); >+ runaci.next(); >+ } >+ return chars; >+ } >+ >+ protected final void logTextRun(AttributedCharacterIterator runaci, TextSpanLayout layout) { >+ if (log.isTraceEnabled()) { >+ int charCount = runaci.getEndIndex() - runaci.getBeginIndex(); >+ log.trace("================================================"); >+ log.trace("New text run:"); >+ log.trace("char count: " + charCount); >+ log.trace("range: " >+ + runaci.getBeginIndex() + " - " + runaci.getEndIndex()); >+ log.trace("glyph count: " + layout.getGlyphCount()); //=getNumGlyphs() >+ } >+ } >+ >+ protected final void logCharacter(char ch, TextSpanLayout layout, int index, >+ boolean visibleChar) { >+ if (log.isTraceEnabled()) { >+ log.trace("glyph " + index >+ + " -> " + layout.getGlyphIndex(index) + " => " + ch); >+ if (CharUtilities.isAnySpace(ch) && ch != 32) { >+ log.trace("Space found: " + Integer.toHexString(ch)); >+ } else if (ch == CharUtilities.ZERO_WIDTH_JOINER) { >+ log.trace("ZWJ found: " + Integer.toHexString(ch)); >+ } else if (ch == CharUtilities.SOFT_HYPHEN) { >+ log.trace("Soft hyphen found: " + Integer.toHexString(ch)); >+ } >+ if (!visibleChar) { >+ log.trace("Invisible glyph found: " + Integer.toHexString(ch)); >+ } >+ } >+ } >+ >+ >+} > >Property changes on: src\java\org\apache\fop\svg\NativeTextPainter.java >___________________________________________________________________ >Added: svn:keywords > + Id >Added: svn:eol-style > + native > >Index: src/java/org/apache/fop/svg/AbstractFOPTranscoder.java >=================================================================== >--- src/java/org/apache/fop/svg/AbstractFOPTranscoder.java (revision 761552) >+++ src/java/org/apache/fop/svg/AbstractFOPTranscoder.java (working copy) >@@ -19,6 +19,19 @@ > > package org.apache.fop.svg; > >+import java.io.IOException; >+import java.io.InputStream; >+ >+import javax.xml.transform.Source; >+import javax.xml.transform.stream.StreamSource; >+ >+import org.w3c.dom.DOMImplementation; >+ >+import org.xml.sax.EntityResolver; >+ >+import org.apache.avalon.framework.configuration.Configuration; >+import org.apache.avalon.framework.configuration.ConfigurationException; >+import org.apache.avalon.framework.configuration.DefaultConfiguration; > import org.apache.batik.bridge.UserAgent; > import org.apache.batik.dom.svg.SVGDOMImplementation; > import org.apache.batik.dom.util.DocumentFactory; >@@ -28,23 +41,41 @@ > import org.apache.batik.transcoder.TranscodingHints; > import org.apache.batik.transcoder.image.ImageTranscoder; > import org.apache.batik.transcoder.keys.BooleanKey; >+import org.apache.batik.transcoder.keys.FloatKey; >+import org.apache.batik.util.ParsedURL; > import org.apache.batik.util.SVGConstants; > import org.apache.commons.logging.Log; > import org.apache.commons.logging.impl.SimpleLog; >-import org.w3c.dom.DOMImplementation; >-import org.xml.sax.EntityResolver; > >+import org.apache.xmlgraphics.image.loader.ImageContext; >+import org.apache.xmlgraphics.image.loader.ImageManager; >+import org.apache.xmlgraphics.image.loader.ImageSessionContext; >+import org.apache.xmlgraphics.image.loader.impl.AbstractImageSessionContext; >+ > /** > * This is the common base class of all of FOP's transcoders. > */ > public abstract class AbstractFOPTranscoder extends SVGAbstractTranscoder { > > /** >+ * The key is used to specify the resolution for on-the-fly images generated >+ * due to complex effects like gradients and filters. >+ */ >+ public static final TranscodingHints.Key KEY_DEVICE_RESOLUTION = new FloatKey(); >+ >+ /** > * The key to specify whether to stroke text instead of using text > * operations. > */ > public static final TranscodingHints.Key KEY_STROKE_TEXT = new BooleanKey(); > >+ /** >+ * The key is used to specify whether the available fonts should be automatically >+ * detected. The alternative is to configure the transcoder manually using a configuration >+ * file. >+ */ >+ public static final TranscodingHints.Key KEY_AUTO_FONTS = new BooleanKey(); >+ > /** The value to turn on text stroking. */ > public static final Boolean VALUE_FORMAT_ON = Boolean.TRUE; > >@@ -58,6 +89,9 @@ > > private Log logger; > private EntityResolver resolver; >+ private Configuration cfg = null; >+ private ImageManager imageManager; >+ private ImageSessionContext imageSessionContext; > > /** > * Constructs a new FOP-style transcoder. >@@ -80,7 +114,8 @@ > } > > /** >- * @param logger >+ * Sets the logger. >+ * @param logger the logger > */ > public void setLogger(Log logger) { > this.logger = logger; >@@ -94,7 +129,36 @@ > this.resolver = resolver; > } > >+ /** {@inheritDoc} */ >+ public void configure(Configuration cfg) throws ConfigurationException { >+ this.cfg = cfg; >+ } >+ > /** >+ * Returns the effective configuration for the transcoder. >+ * @return the effective configuration >+ */ >+ protected Configuration getEffectiveConfiguration() { >+ Configuration effCfg = this.cfg; >+ if (effCfg == null) { >+ //By default, enable font auto-detection if no cfg is given >+ boolean autoFonts = true; >+ if (hints.containsKey(KEY_AUTO_FONTS)) { >+ autoFonts = ((Boolean)hints.get(KEY_AUTO_FONTS)).booleanValue(); >+ } >+ if (autoFonts) { >+ DefaultConfiguration c = new DefaultConfiguration("cfg"); >+ DefaultConfiguration fonts = new DefaultConfiguration("fonts"); >+ c.addChild(fonts); >+ DefaultConfiguration autodetect = new DefaultConfiguration("auto-detect"); >+ fonts.addChild(autodetect); >+ effCfg = c; >+ } >+ } >+ return effCfg; >+ } >+ >+ /** > * Returns the logger associated with this transcoder. It returns a > * SimpleLog if no logger has been explicitly set. > * @return Logger the logger for the transcoder. >@@ -142,6 +206,71 @@ > return stroke; > } > >+ /** >+ * Returns the device resolution that has been set up. >+ * @return the device resolution (in dpi) >+ */ >+ protected float getDeviceResolution() { >+ if (hints.containsKey(KEY_DEVICE_RESOLUTION)) { >+ return ((Float)hints.get(KEY_DEVICE_RESOLUTION)).floatValue(); >+ } else { >+ return 72; >+ } >+ } >+ >+ /** >+ * Returns the ImageManager to be used by the transcoder. >+ * @return the image manager >+ */ >+ protected ImageManager getImageManager() { >+ return this.imageManager; >+ } >+ >+ /** >+ * Returns the ImageSessionContext to be used by the transcoder. >+ * @return the image session context >+ */ >+ protected ImageSessionContext getImageSessionContext() { >+ return this.imageSessionContext; >+ } >+ >+ /** >+ * Sets up the image infrastructure (the image loading framework). >+ * @param baseURI the base URI of the current document >+ */ >+ protected void setupImageInfrastructure(final String baseURI) { >+ final ImageContext imageContext = new ImageContext() { >+ public float getSourceResolution() { >+ return 25.4f / userAgent.getPixelUnitToMillimeter(); >+ } >+ }; >+ this.imageManager = new ImageManager(imageContext); >+ this.imageSessionContext = new AbstractImageSessionContext() { >+ >+ public ImageContext getParentContext() { >+ return imageContext; >+ } >+ >+ public float getTargetResolution() { >+ return getDeviceResolution(); >+ } >+ >+ public Source resolveURI(String uri) { >+ System.out.println("resolve " + uri); >+ try { >+ ParsedURL url = new ParsedURL(baseURI, uri); >+ InputStream in = url.openStream(); >+ StreamSource source = new StreamSource(in, url.toString()); >+ return source; >+ } catch (IOException ioe) { >+ userAgent.displayError(ioe); >+ return null; >+ } >+ } >+ >+ }; >+ } >+ > // -------------------------------------------------------------------- > // FOP's default error handler (for transcoders) > // -------------------------------------------------------------------- >Index: src/java/org/apache/fop/svg/AbstractFOPTextElementBridge.java >=================================================================== >--- src/java/org/apache/fop/svg/AbstractFOPTextElementBridge.java (revision 761552) >+++ src/java/org/apache/fop/svg/AbstractFOPTextElementBridge.java (working copy) >@@ -19,13 +19,13 @@ > > package org.apache.fop.svg; > >+import org.w3c.dom.Element; >+ > import org.apache.batik.bridge.BridgeContext; > import org.apache.batik.bridge.SVGTextElementBridge; > import org.apache.batik.gvt.GraphicsNode; > import org.apache.batik.gvt.TextNode; > import org.apache.batik.gvt.TextPainter; >-import org.w3c.dom.Element; >-import org.w3c.dom.Node; > > /** > * Bridge class for the <text> element. >@@ -65,49 +65,5 @@ > return node; > } > >- /** >- * Check if text element contains simple text. >- * This checks the children of the text element to determine >- * if the text is simple. The text is simple if it can be rendered >- * with basic text drawing algorithms. This means there are no >- * alternate characters, the font is known and there are no effects >- * applied to the text. >- * >- * @param ctx the bridge context >- * @param element the svg text element >- * @param node the graphics node >- * @return true if this text is simple of false if it cannot be >- * easily rendered using normal drawString on the Graphics2D >- */ >- protected boolean isSimple(BridgeContext ctx, Element element, GraphicsNode node) { >- for (Node n = element.getFirstChild(); >- n != null; >- n = n.getNextSibling()) { >- >- switch (n.getNodeType()) { >- case Node.ELEMENT_NODE: >- >- if (n.getLocalName().equals(SVG_TSPAN_TAG) >- || n.getLocalName().equals(SVG_ALT_GLYPH_TAG)) { >- return false; >- } else if (n.getLocalName().equals(SVG_TEXT_PATH_TAG)) { >- return false; >- } else if (n.getLocalName().equals(SVG_TREF_TAG)) { >- return false; >- } >- break; >- case Node.TEXT_NODE: >- case Node.CDATA_SECTION_NODE: >- default: >- } >- } >- >- /*if (CSSUtilities.convertFilter(element, node, ctx) != null) { >- return false; >- }*/ >- >- return true; >- } >- > } > >Index: src/java/org/apache/fop/svg/AbstractFOPBridgeContext.java >=================================================================== >--- src/java/org/apache/fop/svg/AbstractFOPBridgeContext.java (revision 761552) >+++ src/java/org/apache/fop/svg/AbstractFOPBridgeContext.java (working copy) >@@ -26,10 +26,12 @@ > import org.apache.batik.bridge.BridgeContext; > import org.apache.batik.bridge.DocumentLoader; > import org.apache.batik.bridge.UserAgent; >-import org.apache.fop.fonts.FontInfo; >+ > import org.apache.xmlgraphics.image.loader.ImageManager; > import org.apache.xmlgraphics.image.loader.ImageSessionContext; > >+import org.apache.fop.fonts.FontInfo; >+ > /** > * A FOP base implementation of a Batik BridgeContext. > */ >@@ -49,8 +51,6 @@ > * @param loader the Document Loader to use for referenced documents. > * @param fontInfo the font list for the text painter, may be null > * in which case text is painted as shapes >- * @param linkTransform AffineTransform to properly place links, >- * may be null > * @param imageManager an image manager > * @param imageSessionContext an image session context > * @param linkTransform AffineTransform to properly place links, >Index: src/java/org/apache/fop/render/ps/PSTextPainter.java >=================================================================== >--- src/java/org/apache/fop/render/ps/PSTextPainter.java (revision 761552) >+++ src/java/org/apache/fop/render/ps/PSTextPainter.java (working copy) >@@ -19,555 +19,516 @@ > > 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.font.TextAttribute; >+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.Rectangle2D; > import java.io.IOException; > import java.text.AttributedCharacterIterator; >-import java.text.CharacterIterator; > import java.util.Iterator; > import java.util.List; > >-import org.apache.batik.dom.svg.SVGOMTextElement; >-import org.apache.batik.gvt.TextNode; >-import org.apache.batik.gvt.TextPainter; >-import org.apache.batik.gvt.font.GVTFontFamily; >-import org.apache.batik.gvt.renderer.StrokingTextPainter; >-import org.apache.batik.gvt.text.GVTAttributedCharacterIterator; >-import org.apache.batik.gvt.text.Mark; >+import org.apache.batik.gvt.font.GVTGlyphVector; > import org.apache.batik.gvt.text.TextPaintInfo; >-import org.apache.commons.logging.Log; >-import org.apache.commons.logging.LogFactory; >+import org.apache.batik.gvt.text.TextSpanLayout; >+ >+import org.apache.xmlgraphics.java2d.ps.PSGraphics2D; >+import org.apache.xmlgraphics.ps.PSGenerator; >+import org.apache.xmlgraphics.ps.PSResource; >+ > import org.apache.fop.fonts.Font; > import org.apache.fop.fonts.FontInfo; >-import org.apache.fop.fonts.FontTriplet; >-import org.apache.xmlgraphics.java2d.ps.PSGraphics2D; >+import org.apache.fop.svg.NativeTextPainter; >+import org.apache.fop.util.CharUtilities; > >- > /** > * Renders the attributed character iterator of a <tt>TextNode</tt>. >- * This class draws the text directly into the PSGraphics2D so that >+ * This class draws the text directly using PostScript text operators so > * the text is not drawn using shapes which makes the PS files larger. >- * If the text is simple enough to draw then it sets the font and calls >- * drawString. If the text is complex or the cannot be translated >- * into a simple drawString the StrokingTextPainter is used instead. >- * >- * (todo) handle underline, overline and strikethrough >- * (todo) use drawString(AttributedCharacterIterator iterator...) for some >- * >- * @author <a href="mailto:keiron@aftexsw.com">Keiron Liddle</a> >- * @version $Id$ >+ * <p> >+ * The text runs are split into smaller text runs that can be bundles in single >+ * calls of the xshow, yshow or xyshow operators. For outline text, the charpath >+ * operator is used. > */ >-public class PSTextPainter implements TextPainter { >+public class PSTextPainter extends NativeTextPainter { > >- /** the logger for this class */ >- protected Log log = LogFactory.getLog(PSTextPainter.class); >+ private static final boolean DEBUG = false; > >- private final NativeTextHandler nativeTextHandler; >- private final FontInfo fontInfo; >+ private FontResourceCache fontResources; > >- /** >- * Use the stroking text painter to get the bounds and shape. >- * Also used as a fallback to draw the string with strokes. >- */ >- protected static final TextPainter >- PROXY_PAINTER = StrokingTextPainter.getInstance(); >+ private static final AffineTransform IDENTITY_TRANSFORM = new AffineTransform(); > > /** > * Create a new PS text painter with the given font information. >- * @param nativeTextHandler the NativeTextHandler instance used for text painting >+ * @param fontInfo the font collection > */ >- public PSTextPainter(NativeTextHandler nativeTextHandler) { >- this.nativeTextHandler = nativeTextHandler; >- this.fontInfo = nativeTextHandler.getFontInfo(); >+ public PSTextPainter(FontInfo fontInfo) { >+ super(fontInfo); >+ this.fontResources = new FontResourceCache(fontInfo); > } > >- /** >- * Paints the specified attributed character iterator using the >- * specified Graphics2D and context and font context. >- * @param node the TextNode to paint >- * @param g2d the Graphics2D to use >- */ >- public void paint(TextNode node, Graphics2D g2d) { >- String txt = node.getText(); >- Point2D loc = node.getLocation(); >- >- if (hasUnsupportedAttributes(node)) { >- PROXY_PAINTER.paint(node, g2d); >- } else { >- paintTextRuns(node.getTextRuns(), g2d, loc); >- } >+ /** {@inheritDoc} */ >+ protected boolean isSupported(Graphics2D g2d) { >+ return g2d instanceof PSGraphics2D; > } > >+ /** {@inheritDoc} */ >+ protected void paintTextRun(TextRun textRun, Graphics2D g2d) throws IOException { >+ AttributedCharacterIterator runaci = textRun.getACI(); >+ runaci.first(); > >- private boolean hasUnsupportedAttributes(TextNode node) { >- Iterator i = node.getTextRuns().iterator(); >- while (i.hasNext()) { >- StrokingTextPainter.TextRun >- run = (StrokingTextPainter.TextRun)i.next(); >- AttributedCharacterIterator aci = run.getACI(); >- boolean hasUnsupported = hasUnsupportedAttributes(aci); >- if (hasUnsupported) { >- return true; >- } >+ TextPaintInfo tpi = (TextPaintInfo)runaci.getAttribute(PAINT_INFO); >+ if (tpi == null || !tpi.visible) { >+ return; > } >- return false; >- } >+ if ((tpi != null) && (tpi.composite != null)) { >+ g2d.setComposite(tpi.composite); >+ } > >- private boolean hasUnsupportedAttributes(AttributedCharacterIterator aci) { >- boolean hasunsupported = false; >+ //------------------------------------ >+ TextSpanLayout layout = textRun.getLayout(); >+ logTextRun(runaci, layout); >+ CharSequence chars = collectCharacters(runaci); >+ runaci.first(); //Reset ACI > >- String text = getText(aci); >- Font font = makeFont(aci); >- if (hasUnsupportedGlyphs(text, font)) { >- log.trace("-> Unsupported glyphs found"); >- hasunsupported = true; >- } >+ final PSGraphics2D ps = (PSGraphics2D)g2d; >+ final PSGenerator gen = ps.getPSGenerator(); >+ ps.preparePainting(); > >- TextPaintInfo tpi = (TextPaintInfo) aci.getAttribute( >- GVTAttributedCharacterIterator.TextAttribute.PAINT_INFO); >- if ((tpi != null) >- && ((tpi.strokeStroke != null && tpi.strokePaint != null) >- || (tpi.strikethroughStroke != null) >- || (tpi.underlineStroke != null) >- || (tpi.overlineStroke != null))) { >- log.trace("-> under/overlines etc. found"); >- hasunsupported = true; >+ if (DEBUG) { >+ log.debug("Text: " + chars); >+ gen.commentln("%Text: " + chars); > } > >- //Alpha is not supported >- Paint foreground = (Paint) aci.getAttribute(TextAttribute.FOREGROUND); >- if (foreground instanceof Color) { >- Color col = (Color)foreground; >- if (col.getAlpha() != 255) { >- log.trace("-> transparency found"); >- hasunsupported = true; >- } >+ GeneralPath debugShapes = null; >+ if (DEBUG) { >+ debugShapes = new GeneralPath(); > } > >- Object letSpace = aci.getAttribute( >- GVTAttributedCharacterIterator.TextAttribute.LETTER_SPACING); >- if (letSpace != null) { >- log.trace("-> letter spacing found"); >- hasunsupported = true; >+ 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; > } > >- Object wordSpace = aci.getAttribute( >- GVTAttributedCharacterIterator.TextAttribute.WORD_SPACING); >- if (wordSpace != null) { >- log.trace("-> word spacing found"); >- hasunsupported = true; >- } >+ gen.saveGraphicsState(); >+ gen.concatMatrix(g2d.getTransform()); >+ Shape imclip = g2d.getClip(); >+ clip(ps, imclip); > >- Object lengthAdjust = aci.getAttribute( >- GVTAttributedCharacterIterator.TextAttribute.LENGTH_ADJUST); >- if (lengthAdjust != null) { >- log.trace("-> length adjustments found"); >- hasunsupported = true; >- } >+ gen.writeln("BT"); //beginTextObject() > >- Object writeMod = aci.getAttribute( >- GVTAttributedCharacterIterator.TextAttribute.WRITING_MODE); >- if (writeMod != null >- && !GVTAttributedCharacterIterator.TextAttribute.WRITING_MODE_LTR.equals( >- writeMod)) { >- log.trace("-> Unsupported writing modes found"); >- hasunsupported = true; >- } >+ 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); > >- Object vertOr = aci.getAttribute( >- GVTAttributedCharacterIterator.TextAttribute.VERTICAL_ORIENTATION); >- if (GVTAttributedCharacterIterator.TextAttribute.ORIENTATION_ANGLE.equals( >- vertOr)) { >- log.trace("-> vertical orientation found"); >- hasunsupported = true; >- } >+ 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); >+ } > >- Object rcDel = aci.getAttribute( >- GVTAttributedCharacterIterator.TextAttribute.TEXT_COMPOUND_DELIMITER); >- //Batik 1.6 returns null here which makes it impossible to determine whether this can >- //be painted or not, i.e. fall back to stroking. :-( >- if (/*rcDel != null &&*/ !(rcDel instanceof SVGOMTextElement)) { >- log.trace("-> spans found"); >- hasunsupported = true; //Filter spans >+ //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() >+ gen.restoreGraphicsState(); > >- if (hasunsupported) { >- log.trace("Unsupported attributes found in ACI, using StrokingTextPainter"); >+ if (DEBUG) { >+ //Paint debug shapes >+ g2d.setStroke(new BasicStroke(0)); >+ g2d.setColor(Color.LIGHT_GRAY); >+ g2d.draw(debugShapes); > } >- return hasunsupported; > } > >- /** >- * Paint a list of text runs on the Graphics2D at a given location. >- * @param textRuns the list of text runs >- * @param g2d the Graphics2D to paint to >- * @param loc the current location of the "cursor" >- */ >- protected void paintTextRuns(List textRuns, Graphics2D g2d, Point2D loc) { >- Point2D currentloc = loc; >- Iterator i = textRuns.iterator(); >- while (i.hasNext()) { >- StrokingTextPainter.TextRun >- run = (StrokingTextPainter.TextRun)i.next(); >- currentloc = paintTextRun(run, g2d, currentloc); >+ 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()); > } > } > >- /** >- * Paint a single text run on the Graphics2D at a given location. >- * @param run the text run to paint >- * @param g2d the Graphics2D to paint to >- * @param loc the current location of the "cursor" >- * @return the new location of the "cursor" after painting the text run >- */ >- protected Point2D paintTextRun(StrokingTextPainter.TextRun run, Graphics2D g2d, Point2D loc) { >- AttributedCharacterIterator aci = run.getACI(); >- return paintACI(aci, g2d, loc); >+ private PSResource getResourceForFont(Font f, String postfix) { >+ String key = (postfix != null ? f.getFontName() + '_' + postfix : f.getFontName()); >+ return this.fontResources.getPSResourceForFontKey(key); > } > >- /** >- * Extract the raw text from an ACI. >- * @param aci ACI to inspect >- * @return the extracted text >- */ >- protected String getText(AttributedCharacterIterator aci) { >- StringBuffer sb = new StringBuffer(aci.getEndIndex() - aci.getBeginIndex()); >- for (char c = aci.first(); c != CharacterIterator.DONE; c = aci.next()) { >- sb.append(c); >+ private void clip(PSGraphics2D ps, Shape shape) throws IOException { >+ if (shape == null) { >+ return; > } >- return sb.toString(); >+ ps.getPSGenerator().writeln("newpath"); >+ PathIterator iter = shape.getPathIterator(IDENTITY_TRANSFORM); >+ ps.processPathIterator(iter); >+ ps.getPSGenerator().writeln("clip"); > } > >- /** >- * Paint an ACI on a Graphics2D at a given location. The method has to >- * update the location after painting. >- * @param aci ACI to paint >- * @param g2d Graphics2D to paint on >- * @param loc start location >- * @return new current location >- */ >- protected Point2D paintACI(AttributedCharacterIterator aci, Graphics2D g2d, Point2D loc) { >- //ACIUtils.dumpAttrs(aci); >+ private class TextUtil { > >- aci.first(); >+ private PSGenerator gen; >+ private Font[] fonts; >+ private Font currentFont; >+ private int currentEncoding = -1; > >- updateLocationFromACI(aci, loc); >- >- TextPaintInfo tpi = (TextPaintInfo) aci.getAttribute( >- GVTAttributedCharacterIterator.TextAttribute.PAINT_INFO); >- >- if (tpi == null) { >- return loc; >+ public TextUtil(PSGenerator gen) { >+ this.gen = gen; > } > >- TextNode.Anchor anchor = (TextNode.Anchor)aci.getAttribute( >- GVTAttributedCharacterIterator.TextAttribute.ANCHOR_TYPE); >- >- //Set up font >- List gvtFonts = (List)aci.getAttribute( >- GVTAttributedCharacterIterator.TextAttribute.GVT_FONT_FAMILIES); >- Paint foreground = tpi.fillPaint; >- Paint strokePaint = tpi.strokePaint; >- Stroke stroke = tpi.strokeStroke; >- >- Float fontSize = (Float)aci.getAttribute(TextAttribute.SIZE); >- if (fontSize == null) { >- return loc; >+ 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 > } >- Float posture = (Float)aci.getAttribute(TextAttribute.POSTURE); >- Float taWeight = (Float)aci.getAttribute(TextAttribute.WEIGHT); > >- if (foreground instanceof Color) { >- Color col = (Color)foreground; >- g2d.setColor(col); >+ public void writeTextMatrix(AffineTransform transform) throws IOException { >+ double[] matrix = new double[6]; >+ transform.getMatrix(matrix); >+ gen.writeln(gen.formatDouble5(matrix[0]) + " " >+ + gen.formatDouble5(matrix[1]) + " " >+ + gen.formatDouble5(matrix[2]) + " " >+ + gen.formatDouble5(matrix[3]) + " " >+ + gen.formatDouble5(matrix[4]) + " " >+ + gen.formatDouble5(matrix[5]) + " Tm"); > } >- g2d.setPaint(foreground); >- g2d.setStroke(stroke); > >- Font font = makeFont(aci); >- java.awt.Font awtFont = makeAWTFont(aci, font); >- >- g2d.setFont(awtFont); >- >- String txt = getText(aci); >- float advance = getStringWidth(txt, font); >- float tx = 0; >- if (anchor != null) { >- switch (anchor.getType()) { >- case TextNode.Anchor.ANCHOR_MIDDLE: >- tx = -advance / 2; >- break; >- case TextNode.Anchor.ANCHOR_END: >- tx = -advance; >- break; >- default: //nop >+ public boolean isFontChanging(Font f, char mapped) { >+ if (f != getCurrentFont()) { >+ int encoding = mapped / 256; >+ if (encoding != getCurrentFontEncoding()) { >+ return true; //Font is changing >+ } > } >+ return false; //Font is the same > } > >- drawPrimitiveString(g2d, loc, font, txt, tx); >- loc.setLocation(loc.getX() + advance, loc.getY()); >- return loc; >- } >+ public void selectFont(Font f, char mapped) throws IOException { >+ int encoding = mapped / 256; >+ String postfix = (encoding == 0 ? null : Integer.toString(encoding)); >+ PSResource res = getResourceForFont(f, postfix); >+ gen.useFont("/" + res.getName(), f.getFontSize() / 1000f); >+ gen.getResourceTracker().notifyResourceUsageOnPage(res); >+ } > >- protected void drawPrimitiveString(Graphics2D g2d, Point2D loc, Font font, String txt, float tx) { >- //Finally draw text >- nativeTextHandler.setOverrideFont(font); >- try { >- try { >- nativeTextHandler.drawString(txt, (float)(loc.getX() + tx), (float)(loc.getY())); >- } catch (IOException ioe) { >- if (g2d instanceof PSGraphics2D) { >- ((PSGraphics2D)g2d).handleIOException(ioe); >- } >- } >- } finally { >- nativeTextHandler.setOverrideFont(null); >+ public Font getCurrentFont() { >+ return this.currentFont; > } >- } > >- private void updateLocationFromACI( >- AttributedCharacterIterator aci, >- Point2D loc) { >- //Adjust position of span >- Float xpos = (Float)aci.getAttribute( >- GVTAttributedCharacterIterator.TextAttribute.X); >- Float ypos = (Float)aci.getAttribute( >- GVTAttributedCharacterIterator.TextAttribute.Y); >- Float dxpos = (Float)aci.getAttribute( >- GVTAttributedCharacterIterator.TextAttribute.DX); >- Float dypos = (Float)aci.getAttribute( >- GVTAttributedCharacterIterator.TextAttribute.DY); >- if (xpos != null) { >- loc.setLocation(xpos.doubleValue(), loc.getY()); >+ public int getCurrentFontEncoding() { >+ return this.currentEncoding; > } >- if (ypos != null) { >- loc.setLocation(loc.getX(), ypos.doubleValue()); >+ >+ public void setCurrentFont(Font font, int encoding) { >+ this.currentFont = font; >+ this.currentEncoding = encoding; > } >- if (dxpos != null) { >- loc.setLocation(loc.getX() + dxpos.doubleValue(), loc.getY()); >+ >+ public void setCurrentFont(Font font, char mapped) { >+ int encoding = mapped / 256; >+ setCurrentFont(font, encoding); > } >- if (dypos != null) { >- loc.setLocation(loc.getX(), loc.getY() + dypos.doubleValue()); >+ >+ public void setupFonts(AttributedCharacterIterator runaci) { >+ this.fonts = findFonts(runaci); > } >- } > >- private String getStyle(AttributedCharacterIterator aci) { >- Float posture = (Float)aci.getAttribute(TextAttribute.POSTURE); >- return ((posture != null) && (posture.floatValue() > 0.0)) >- ? "italic" >- : "normal"; >- } >+ public boolean hasFonts() { >+ return (fonts != null) && (fonts.length > 0); >+ } > >- private int getWeight(AttributedCharacterIterator aci) { >- Float taWeight = (Float)aci.getAttribute(TextAttribute.WEIGHT); >- return ((taWeight != null) && (taWeight.floatValue() > 1.0)) >- ? Font.WEIGHT_BOLD >- : Font.WEIGHT_NORMAL; > } > >- private Font makeFont(AttributedCharacterIterator aci) { >- Float fontSize = (Float)aci.getAttribute(TextAttribute.SIZE); >- if (fontSize == null) { >- fontSize = new Float(10.0f); >+ private class PSTextRun { >+ >+ private AffineTransform textTransform; >+ private List relativePositions = new java.util.LinkedList(); >+ private StringBuffer currentChars = new StringBuffer(); >+ private int horizChanges = 0; >+ private int vertChanges = 0; >+ >+ public void reset() { >+ textTransform = null; >+ currentChars.setLength(0); >+ horizChanges = 0; >+ vertChanges = 0; >+ relativePositions.clear(); > } >- String style = getStyle(aci); >- int weight = getWeight(aci); > >- String fontFamily = null; >- List gvtFonts = (List) aci.getAttribute( >- GVTAttributedCharacterIterator.TextAttribute.GVT_FONT_FAMILIES); >- if (gvtFonts != null) { >- Iterator i = gvtFonts.iterator(); >- while (i.hasNext()) { >- GVTFontFamily fam = (GVTFontFamily) i.next(); >- /* (todo) Enable SVG Font painting >- if (fam instanceof SVGFontFamily) { >- PROXY_PAINTER.paint(node, g2d); >- return; >- }*/ >- fontFamily = fam.getFamilyName(); >- if (fontInfo.hasFont(fontFamily, style, weight)) { >- FontTriplet triplet = fontInfo.fontLookup( >- fontFamily, style, weight); >- int fsize = (int)(fontSize.floatValue() * 1000); >- return fontInfo.getFontInstance(triplet, fsize); >- } >+ public int getHorizRunLength() { >+ if (this.vertChanges == 0 >+ && getRunLength() > 0) { >+ return getRunLength(); > } >+ return 0; > } >- FontTriplet triplet = fontInfo.fontLookup("any", style, Font.WEIGHT_NORMAL); >- int fsize = (int)(fontSize.floatValue() * 1000); >- return fontInfo.getFontInstance(triplet, fsize); >- } > >- private java.awt.Font makeAWTFont(AttributedCharacterIterator aci, Font font) { >- final String style = getStyle(aci); >- final int weight = getWeight(aci); >- int fStyle = java.awt.Font.PLAIN; >- if (weight == Font.WEIGHT_BOLD) { >- fStyle |= java.awt.Font.BOLD; >+ public void addCharacter(char paintChar, Point2D relPos) { >+ addRelativePosition(relPos); >+ currentChars.append(paintChar); > } >- if ("italic".equals(style)) { >- fStyle |= java.awt.Font.ITALIC; >- } >- return new java.awt.Font(font.getFontName(), fStyle, >- (font.getFontSize() / 1000)); >- } > >- private float getStringWidth(String str, Font font) { >- float wordWidth = 0; >- float whitespaceWidth = font.getWidth(font.mapChar(' ')); >- >- for (int i = 0; i < str.length(); i++) { >- float charWidth; >- char c = str.charAt(i); >- if (!((c == ' ') || (c == '\n') || (c == '\r') || (c == '\t'))) { >- charWidth = font.getWidth(font.mapChar(c)); >- if (charWidth <= 0) { >- charWidth = whitespaceWidth; >+ private void addRelativePosition(Point2D relPos) { >+ if (getRunLength() > 0) { >+ if (relPos.getX() != 0) { >+ horizChanges++; > } >- } else { >- charWidth = whitespaceWidth; >+ if (relPos.getY() != 0) { >+ vertChanges++; >+ } > } >- wordWidth += charWidth; >+ relativePositions.add(relPos); > } >- return wordWidth / 1000f; >- } > >- private boolean hasUnsupportedGlyphs(String str, Font font) { >- for (int i = 0; i < str.length(); i++) { >- float charWidth; >- char c = str.charAt(i); >- if (!((c == ' ') || (c == '\n') || (c == '\r') || (c == '\t'))) { >- if (!font.hasChar(c)) { >- return true; >- } >+ public void noteStartingTransformation(AffineTransform transform) { >+ if (textTransform == null) { >+ this.textTransform = new AffineTransform(transform); > } > } >- return false; >- } > >- /** >- * Get the outline shape of the text characters. >- * This uses the StrokingTextPainter to get the outline >- * shape since in theory it should be the same. >- * >- * @param node the text node >- * @return the outline shape of the text characters >- */ >- public Shape getOutline(TextNode node) { >- return PROXY_PAINTER.getOutline(node); >- } >+ public int getRunLength() { >+ return currentChars.length(); >+ } > >- /** >- * Get the bounds. >- * This uses the StrokingTextPainter to get the bounds >- * since in theory it should be the same. >- * >- * @param node the text node >- * @return the bounds of the text >- */ >- public Rectangle2D getBounds2D(TextNode node) { >- /* (todo) getBounds2D() is too slow >- * because it uses the StrokingTextPainter. We should implement this >- * method ourselves. */ >- return PROXY_PAINTER.getBounds2D(node); >- } >+ private boolean isXShow() { >+ return vertChanges == 0; >+ } > >- /** >- * Get the geometry bounds. >- * This uses the StrokingTextPainter to get the bounds >- * since in theory it should be the same. >- * @param node the text node >- * @return the bounds of the text >- */ >- public Rectangle2D getGeometryBounds(TextNode node) { >- return PROXY_PAINTER.getGeometryBounds(node); >- } >+ private boolean isYShow() { >+ return horizChanges == 0; >+ } > >- // Methods that have no purpose for PS >+ 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"); >+ paintXYShow(g2d, textUtil, tpi.fillPaint, true, false); >+ } else if (isYShow()) { >+ log.debug("Vertical text: yshow"); >+ paintXYShow(g2d, textUtil, tpi.fillPaint, false, true); >+ } else { >+ log.debug("Arbitrary text: xyshow"); >+ paintXYShow(g2d, textUtil, tpi.fillPaint, true, true); >+ } >+ boolean stroke = (tpi.strokePaint != null) && (tpi.strokeStroke != null); >+ if (stroke) { >+ log.debug("Stroked glyph outlines"); >+ paintStrokedGlyphs(g2d, textUtil, tpi.strokePaint, tpi.strokeStroke); >+ } >+ } >+ } > >- /** >- * Get the mark. >- * This does nothing since the output is pdf and not interactive. >- * @param node the text node >- * @param pos the position >- * @param all select all >- * @return null >- */ >- public Mark getMark(TextNode node, int pos, boolean all) { >- return null; >- } >+ 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); > >- /** >- * Select at. >- * This does nothing since the output is pdf and not interactive. >- * @param x the x position >- * @param y the y position >- * @param node the text node >- * @return null >- */ >- public Mark selectAt(double x, double y, TextNode node) { >- return null; >- } >+ StringBuffer sb = new StringBuffer(); >+ sb.append('('); >+ for (int i = 0, c = this.currentChars.length(); i < c; i++) { >+ char ch = this.currentChars.charAt(i); >+ mapped = f.mapChar(ch); >+ PSGenerator.escapeChar(mapped, sb); >+ } >+ sb.append(')'); >+ if (x || y) { >+ sb.append("\n["); >+ int idx = 0; >+ Iterator iter = this.relativePositions.iterator(); >+ while (iter.hasNext()) { >+ Point2D pt = (Point2D)iter.next(); >+ if (idx > 0) { >+ if (x) { >+ sb.append(format(gen, pt.getX())); >+ } >+ if (y) { >+ if (x) { >+ sb.append(' '); >+ } >+ sb.append(format(gen, -pt.getY())); >+ } >+ if (idx % 8 == 0) { >+ sb.append('\n'); >+ } else { >+ sb.append(' '); >+ } >+ } >+ idx++; >+ } >+ if (x) { >+ sb.append('0'); >+ } >+ if (y) { >+ if (x) { >+ sb.append(' '); >+ } >+ sb.append('0'); >+ } >+ sb.append(']'); >+ } >+ sb.append(' '); >+ if (x) { >+ sb.append('x'); >+ } >+ if (y) { >+ sb.append('y'); >+ } >+ sb.append("show"); // --> xshow, yshow or xyshow >+ gen.writeln(sb.toString()); >+ } > >- /** >- * Select to. >- * This does nothing since the output is pdf and not interactive. >- * @param x the x position >- * @param y the y position >- * @param beginMark the start mark >- * @return null >- */ >- public Mark selectTo(double x, double y, Mark beginMark) { >- return null; >- } >+ private String format(PSGenerator gen, double coord) { >+ if (Math.abs(coord) < 0.00001) { >+ return "0"; >+ } else { >+ return gen.formatDouble5(coord); >+ } >+ } > >- /** >- * Selec first. >- * This does nothing since the output is pdf and not interactive. >- * @param node the text node >- * @return null >- */ >- public Mark selectFirst(TextNode node) { >- return null; >- } >+ private void paintStrokedGlyphs(PSGraphics2D g2d, TextUtil textUtil, >+ Paint strokePaint, Stroke stroke) throws IOException { >+ PSGenerator gen = textUtil.gen; > >- /** >- * Select last. >- * This does nothing since the output is pdf and not interactive. >- * @param node the text node >- * @return null >- */ >- public Mark selectLast(TextNode node) { >- return null; >- } >+ applyColor(strokePaint, gen); >+ PSGraphics2D.applyStroke(stroke, gen); > >- /** >- * Get selected. >- * This does nothing since the output is pdf and not interactive. >- * @param start the start mark >- * @param finish the finish mark >- * @return null >- */ >- public int[] getSelected(Mark start, Mark finish) { >- return null; >- } >+ Font f = null; >+ Iterator 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); >+ 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); >+ } >+ mapped = f.mapChar(this.currentChars.charAt(i)); >+ //add glyph outlines to current path >+ char codepoint = (char)(mapped % 256); >+ gen.write("(" + codepoint + ")"); >+ gen.writeln(" false charpath"); > >- /** >- * Get the highlighted shape. >- * This does nothing since the output is pdf and not interactive. >- * @param beginMark the start mark >- * @param endMark the end mark >- * @return null >- */ >- public Shape getHighlightShape(Mark beginMark, Mark endMark) { >- return null; >+ if (iter.hasNext()) { >+ //Position for the next character >+ Point2D pt = (Point2D)iter.next(); >+ pos.setLocation(pos.getX() + pt.getX(), pos.getY() - pt.getY()); >+ gen.writeln(gen.formatDouble5(pos.getX()) + " " >+ + gen.formatDouble5(pos.getY()) + " M"); >+ } >+ } >+ gen.writeln("stroke"); //paints all accumulated glyph outlines >+ } >+ > } > > } >Index: src/java/org/apache/fop/render/ps/PSTextElementBridge.java >=================================================================== >--- src/java/org/apache/fop/render/ps/PSTextElementBridge.java (revision 761552) >+++ src/java/org/apache/fop/render/ps/PSTextElementBridge.java (working copy) >@@ -19,14 +19,14 @@ > > package org.apache.fop.render.ps; > >-import org.apache.batik.bridge.SVGTextElementBridge; >+import org.w3c.dom.Element; >+ > import org.apache.batik.bridge.BridgeContext; >+import org.apache.batik.bridge.SVGTextElementBridge; > import org.apache.batik.gvt.GraphicsNode; > import org.apache.batik.gvt.TextNode; >+import org.apache.batik.gvt.TextPainter; > >-import org.w3c.dom.Element; >-import org.w3c.dom.Node; >- > /** > * Bridge class for the <text> element. > * This bridge will use the direct text painter if the text >@@ -37,13 +37,13 @@ > */ > public class PSTextElementBridge extends SVGTextElementBridge { > >- private PSTextPainter textPainter; >+ private TextPainter textPainter; > > /** > * Constructs a new bridge for the <text> element. > * @param textPainter the text painter to use > */ >- public PSTextElementBridge(PSTextPainter textPainter) { >+ public PSTextElementBridge(TextPainter textPainter) { > this.textPainter = textPainter; > } > >@@ -56,60 +56,13 @@ > */ > public GraphicsNode createGraphicsNode(BridgeContext ctx, Element e) { > GraphicsNode node = super.createGraphicsNode(ctx, e); >- /* this code is worthless I think. PSTextPainter does a much better job >- * at determining whether to stroke or not. */ >- if (true/*node != null && isSimple(ctx, e, node)*/) { >- ((TextNode)node).setTextPainter(getTextPainter()); >- } >+ ((TextNode)node).setTextPainter(getTextPainter()); > return node; > } > >- private PSTextPainter getTextPainter() { >+ private TextPainter getTextPainter() { > return this.textPainter; > } > >- /** >- * Check if text element contains simple text. >- * This checks the children of the text element to determine >- * if the text is simple. The text is simple if it can be rendered >- * with basic text drawing algorithms. This means there are no >- * alternate characters, the font is known and there are no effects >- * applied to the text. >- * >- * @param ctx the bridge context >- * @param element the svg text element >- * @param node the graphics node >- * @return true if this text is simple of false if it cannot be >- * easily rendered using normal drawString on the PDFGraphics2D >- */ >- private boolean isSimple(BridgeContext ctx, Element element, GraphicsNode node) { >- for (Node n = element.getFirstChild(); >- n != null; >- n = n.getNextSibling()) { >- >- switch (n.getNodeType()) { >- case Node.ELEMENT_NODE: >- >- if (n.getLocalName().equals(SVG_TSPAN_TAG) >- || n.getLocalName().equals(SVG_ALT_GLYPH_TAG)) { >- return false; >- } else if (n.getLocalName().equals(SVG_TEXT_PATH_TAG)) { >- return false; >- } else if (n.getLocalName().equals(SVG_TREF_TAG)) { >- return false; >- } >- break; >- case Node.TEXT_NODE: >- case Node.CDATA_SECTION_NODE: >- default: >- } >- } >- >- /*if (CSSUtilities.convertFilter(element, node, ctx) != null) { >- return false; >- }*/ >- >- return true; >- } > } > >Index: src/java/org/apache/fop/render/ps/PSSVGHandler.java >=================================================================== >--- src/java/org/apache/fop/render/ps/PSSVGHandler.java (revision 761552) >+++ src/java/org/apache/fop/render/ps/PSSVGHandler.java (working copy) >@@ -259,17 +259,10 @@ > PSGraphics2D graphics = new PSGraphics2D(strokeText, gen); > graphics.setGraphicContext(new org.apache.xmlgraphics.java2d.GraphicContext()); > >- NativeTextHandler nativeTextHandler = null; >- BridgeContext ctx = new BridgeContext(ua); >- if (!strokeText) { >- FontInfo fontInfo = psInfo.getFontInfo(); >- nativeTextHandler = new NativeTextHandler(graphics, fontInfo); >- graphics.setCustomTextHandler(nativeTextHandler); >- PSTextPainter textPainter = new PSTextPainter(nativeTextHandler); >- ctx.setTextPainter(textPainter); >- PSTextElementBridge tBridge = new PSTextElementBridge(textPainter); >- ctx.putBridge(tBridge); >- } >+ BridgeContext ctx = new PSBridgeContext(ua, >+ (strokeText ? null : psInfo.fontInfo), >+ context.getUserAgent().getFactory().getImageManager(), >+ context.getUserAgent().getImageSessionContext()); > > //Cloning SVG DOM as Batik attaches non-thread-safe facilities (like the CSS engine) > //to it. >Index: src/java/org/apache/fop/render/ps/PSSVGFlowRootElementBridge.java >=================================================================== >--- src/java/org/apache/fop/render/ps/PSSVGFlowRootElementBridge.java (revision 0) >+++ src/java/org/apache/fop/render/ps/PSSVGFlowRootElementBridge.java (revision 0) >@@ -0,0 +1,86 @@ >+/* >+ * 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.render.ps; >+ >+import java.text.AttributedCharacterIterator; >+import java.util.List; >+ >+import org.apache.batik.bridge.svg12.SVGFlowRootElementBridge; >+import org.apache.batik.gvt.GraphicsNode; >+import org.apache.batik.gvt.TextNode; >+import org.apache.batik.gvt.TextPainter; >+import org.apache.batik.gvt.flow.FlowTextPainter; >+ >+import org.apache.fop.fonts.FontInfo; >+ >+/** >+ * Element Bridge for SVG 1.2 flow text, so those texts can be painted using >+ * PDF primitives. >+ */ >+public class PSSVGFlowRootElementBridge extends SVGFlowRootElementBridge { >+ >+ private PSTextPainter textPainter; >+ >+ /** >+ * Main Constructor. >+ * @param fontInfo the font directory >+ */ >+ public PSSVGFlowRootElementBridge(FontInfo fontInfo) { >+ this.textPainter = new PSFlowTextPainter(fontInfo); >+ } >+ >+ /** {@inheritDoc} */ >+ protected GraphicsNode instantiateGraphicsNode() { >+ GraphicsNode node = super.instantiateGraphicsNode(); >+ if (node != null) { >+ //Set our own text painter >+ ((TextNode)node).setTextPainter(getTextPainter()); >+ } >+ return node; >+ } >+ >+ /** >+ * Returns the text painter used by this bridge. >+ * @return the text painter >+ */ >+ public TextPainter getTextPainter() { >+ return this.textPainter; >+ } >+ >+ private class PSFlowTextPainter extends PSTextPainter { >+ >+ /** >+ * Main constructor >+ * @param fontInfo the font directory >+ */ >+ public PSFlowTextPainter(FontInfo fontInfo) { >+ super(fontInfo); >+ } >+ >+ /** {@inheritDoc} */ >+ public List getTextRuns(TextNode node, AttributedCharacterIterator aci) { >+ //Text runs are delegated to the normal FlowTextPainter, we just paint the text. >+ FlowTextPainter delegate = (FlowTextPainter)FlowTextPainter.getInstance(); >+ return delegate.getTextRuns(node, aci); >+ } >+ >+ } >+ >+} > >Property changes on: src\java\org\apache\fop\render\ps\PSSVGFlowRootElementBridge.java >___________________________________________________________________ >Added: svn:keywords > + Id >Added: svn:eol-style > + native > >Index: src/java/org/apache/fop/render/ps/PSPainter.java >=================================================================== >--- src/java/org/apache/fop/render/ps/PSPainter.java (revision 761552) >+++ src/java/org/apache/fop/render/ps/PSPainter.java (working copy) >@@ -350,7 +350,6 @@ > //TODO Opportunity for font caching if font state is more heavily used > String fontKey = getFontInfo().getInternalFontKey(triplet); > int sizeMillipoints = state.getFontSize(); >- float fontSize = sizeMillipoints / 1000f; > > // This assumes that *all* CIDFonts use a /ToUnicode mapping > Typeface tf = getTypeface(fontKey); >@@ -360,9 +359,7 @@ > } > Font font = getFontInfo().getFontInstance(triplet, sizeMillipoints); > >- PSResource res = this.documentHandler.getPSResourceForFontKey(fontKey); >- generator.useFont("/" + res.getName(), fontSize); >- generator.getResourceTracker().notifyResourceUsageOnPage(res); >+ useFont(fontKey, sizeMillipoints); > > generator.writeln("1 0 0 -1 " + formatMptAsPt(generator, x) > + " " + formatMptAsPt(generator, y) + " Tm"); >Index: src/java/org/apache/fop/render/ps/PSImageHandlerSVG.java >=================================================================== >--- src/java/org/apache/fop/render/ps/PSImageHandlerSVG.java (revision 761552) >+++ src/java/org/apache/fop/render/ps/PSImageHandlerSVG.java (working copy) >@@ -65,16 +65,10 @@ > PSGraphics2D graphics = new PSGraphics2D(strokeText, gen); > graphics.setGraphicContext(new org.apache.xmlgraphics.java2d.GraphicContext()); > >- NativeTextHandler nativeTextHandler = null; >- BridgeContext ctx = new BridgeContext(ua); >- if (!strokeText) { >- nativeTextHandler = new NativeTextHandler(graphics, psContext.getFontInfo()); >- graphics.setCustomTextHandler(nativeTextHandler); >- PSTextPainter textPainter = new PSTextPainter(nativeTextHandler); >- ctx.setTextPainter(textPainter); >- PSTextElementBridge tBridge = new PSTextElementBridge(textPainter); >- ctx.putBridge(tBridge); >- } >+ BridgeContext ctx = new PSBridgeContext(ua, >+ (strokeText ? null : psContext.getFontInfo()), >+ context.getUserAgent().getFactory().getImageManager(), >+ context.getUserAgent().getImageSessionContext()); > > GraphicsNode root; > try { >Index: src/java/org/apache/fop/render/ps/PSDocumentHandler.java >=================================================================== >--- src/java/org/apache/fop/render/ps/PSDocumentHandler.java (revision 761552) >+++ src/java/org/apache/fop/render/ps/PSDocumentHandler.java (working copy) >@@ -50,8 +50,6 @@ > import org.apache.xmlgraphics.ps.dsc.events.DSCCommentHiResBoundingBox; > > import org.apache.fop.apps.MimeConstants; >-import org.apache.fop.fonts.LazyFont; >-import org.apache.fop.fonts.Typeface; > import org.apache.fop.render.intermediate.AbstractBinaryWritingIFDocumentHandler; > import org.apache.fop.render.intermediate.IFContext; > import org.apache.fop.render.intermediate.IFDocumentHandlerConfigurator; >@@ -91,8 +89,8 @@ > /** Used to temporarily store PSSetupCode instance until they can be written. */ > private List setupCodeList; > >- /** This is a map of PSResource instances of all fonts defined (key: font key) */ >- private Map fontResources; >+ /** This is a cache of PSResource instances of all fonts defined */ >+ private FontResourceCache fontResources; > /** This is a map of PSResource instances of all forms (key: uri) */ > private Map formResources; > >@@ -139,6 +137,7 @@ > /** {@inheritDoc} */ > public void startDocument() throws IFException { > super.startDocument(); >+ this.fontResources = new FontResourceCache(getFontInfo()); > try { > OutputStream out; > if (psUtil.isOptimizeResources()) { >@@ -200,7 +199,7 @@ > gen.writeDSCComment(DSCConstants.BEGIN_SETUP); > PSRenderingUtil.writeSetupCodeList(gen, setupCodeList, "SetupCode"); > if (!psUtil.isOptimizeResources()) { >- this.fontResources = PSFontUtils.writeFontDict(gen, fontInfo); >+ this.fontResources.addAll(PSFontUtils.writeFontDict(gen, fontInfo)); > } else { > gen.commentln("%FOPFontSetup"); //Place-holder, will be replaced in the second pass > } >@@ -534,45 +533,13 @@ > } > } > >- private String getPostScriptNameForFontKey(String key) { >- int pos = key.indexOf('_'); >- String postFix = null; >- if (pos > 0) { >- postFix = key.substring(pos); >- key = key.substring(0, pos); >- } >- Map fonts = fontInfo.getFonts(); >- Typeface tf = (Typeface)fonts.get(key); >- if (tf instanceof LazyFont) { >- tf = ((LazyFont)tf).getRealFont(); >- } >- if (tf == null) { >- throw new IllegalStateException("Font not available: " + key); >- } >- if (postFix == null) { >- return tf.getFontName(); >- } else { >- return tf.getFontName() + postFix; >- } >- } >- > /** > * Returns the PSResource for the given font key. > * @param key the font key ("F*") > * @return the matching PSResource > */ > protected PSResource getPSResourceForFontKey(String key) { >- PSResource res = null; >- if (this.fontResources != null) { >- res = (PSResource)this.fontResources.get(key); >- } else { >- this.fontResources = new java.util.HashMap(); >- } >- if (res == null) { >- res = new PSResource(PSResource.TYPE_FONT, getPostScriptNameForFontKey(key)); >- this.fontResources.put(key, res); >- } >- return res; >+ return this.fontResources.getPSResourceForFontKey(key); > } > > /** >Index: src/java/org/apache/fop/render/ps/PSBridgeContext.java >=================================================================== >--- src/java/org/apache/fop/render/ps/PSBridgeContext.java (revision 0) >+++ src/java/org/apache/fop/render/ps/PSBridgeContext.java (revision 0) >@@ -0,0 +1,113 @@ >+/* >+ * 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.render.ps; >+ >+import java.awt.geom.AffineTransform; >+ >+import org.apache.batik.bridge.BridgeContext; >+import org.apache.batik.bridge.DocumentLoader; >+import org.apache.batik.bridge.SVGTextElementBridge; >+import org.apache.batik.bridge.UserAgent; >+import org.apache.batik.gvt.TextPainter; >+ >+import org.apache.xmlgraphics.image.loader.ImageManager; >+import org.apache.xmlgraphics.image.loader.ImageSessionContext; >+ >+import org.apache.fop.fonts.FontInfo; >+import org.apache.fop.svg.AbstractFOPBridgeContext; >+ >+/** >+ * BridgeContext which registers the custom bridges for PostScript output. >+ */ >+public class PSBridgeContext extends AbstractFOPBridgeContext { >+ >+ /** >+ * Constructs a new bridge context. >+ * @param userAgent the user agent >+ * @param documentLoader the Document Loader to use for referenced documents. >+ * @param fontInfo the font list for the text painter, may be null >+ * in which case text is painted as shapes >+ * @param imageManager an image manager >+ * @param imageSessionContext an image session context >+ * @param linkTransform AffineTransform to properly place links, >+ * may be null >+ */ >+ public PSBridgeContext(UserAgent userAgent, DocumentLoader documentLoader, >+ FontInfo fontInfo, ImageManager imageManager, >+ ImageSessionContext imageSessionContext, >+ AffineTransform linkTransform) { >+ super(userAgent, documentLoader, fontInfo, >+ imageManager, imageSessionContext, linkTransform); >+ } >+ >+ /** >+ * Constructs a new bridge context. >+ * @param userAgent the user agent >+ * @param fontInfo the font list for the text painter, may be null >+ * in which case text is painted as shapes >+ * @param imageManager an image manager >+ * @param imageSessionContext an image session context >+ */ >+ public PSBridgeContext(UserAgent userAgent, FontInfo fontInfo, >+ ImageManager imageManager, ImageSessionContext imageSessionContext) { >+ super(userAgent, fontInfo, imageManager, imageSessionContext); >+ } >+ >+ /** {@inheritDoc} */ >+ public void registerSVGBridges() { >+ super.registerSVGBridges(); >+ >+ if (fontInfo != null) { >+ TextPainter textPainter = new PSTextPainter(fontInfo); >+ SVGTextElementBridge textElementBridge = new PSTextElementBridge(textPainter); >+ putBridge(textElementBridge); >+ >+ //Batik flow text extension (may not always be available) >+ //putBridge(new PDFBatikFlowTextElementBridge(fontInfo); >+ putElementBridgeConditional( >+ "org.apache.fop.render.ps.PSBatikFlowTextElementBridge", >+ "org.apache.batik.extension.svg.BatikFlowTextElementBridge"); >+ >+ //SVG 1.2 flow text support >+ //putBridge(new PDFSVG12TextElementBridge(fontInfo)); //-->Batik 1.7 >+ putElementBridgeConditional( >+ "org.apache.fop.render.ps.PSSVG12TextElementBridge", >+ "org.apache.batik.bridge.svg12.SVG12TextElementBridge"); >+ >+ //putBridge(new PDFSVGFlowRootElementBridge(fontInfo)); >+ putElementBridgeConditional( >+ "org.apache.fop.render.ps.PSSVGFlowRootElementBridge", >+ "org.apache.batik.bridge.svg12.SVGFlowRootElementBridge"); >+ } >+ >+ //putBridge(new PSImageElementBridge()); //TODO uncomment when implemented >+ } >+ >+ // Make sure any 'sub bridge contexts' also have our bridges. >+ //TODO There's no matching method in the super-class here >+ public BridgeContext createBridgeContext() { >+ return new PSBridgeContext(getUserAgent(), getDocumentLoader(), >+ fontInfo, >+ getImageManager(), >+ getImageSessionContext(), >+ linkTransform); >+ } >+ >+} > >Property changes on: src\java\org\apache\fop\render\ps\PSBridgeContext.java >___________________________________________________________________ >Added: svn:keywords > + Id >Added: svn:eol-style > + native > >Index: src/java/org/apache/fop/render/ps/PSBatikFlowTextElementBridge.java >=================================================================== >--- src/java/org/apache/fop/render/ps/PSBatikFlowTextElementBridge.java (revision 0) >+++ src/java/org/apache/fop/render/ps/PSBatikFlowTextElementBridge.java (revision 0) >@@ -0,0 +1,86 @@ >+/* >+ * 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.render.ps; >+ >+import java.text.AttributedCharacterIterator; >+import java.util.List; >+ >+import org.apache.batik.extension.svg.BatikFlowTextElementBridge; >+import org.apache.batik.extension.svg.FlowExtTextPainter; >+import org.apache.batik.gvt.GraphicsNode; >+import org.apache.batik.gvt.TextNode; >+import org.apache.batik.gvt.TextPainter; >+ >+import org.apache.fop.fonts.FontInfo; >+ >+/** >+ * Element Bridge for Batik's flow text extension, so those texts can be painted using >+ * PostScript primitives. >+ */ >+public class PSBatikFlowTextElementBridge extends BatikFlowTextElementBridge { >+ >+ private PSTextPainter textPainter; >+ >+ /** >+ * Main Constructor. >+ * @param fontInfo the font directory >+ */ >+ public PSBatikFlowTextElementBridge(FontInfo fontInfo) { >+ this.textPainter = new PSFlowExtTextPainter(fontInfo); >+ } >+ >+ /** {@inheritDoc} */ >+ protected GraphicsNode instantiateGraphicsNode() { >+ GraphicsNode node = super.instantiateGraphicsNode(); >+ if (node != null) { >+ //Set our own text painter >+ ((TextNode)node).setTextPainter(getTextPainter()); >+ } >+ return node; >+ } >+ >+ /** >+ * Returns the text painter used by this bridge. >+ * @return the text painter >+ */ >+ public TextPainter getTextPainter() { >+ return this.textPainter; >+ } >+ >+ private class PSFlowExtTextPainter extends PSTextPainter { >+ >+ /** >+ * Main constructor >+ * @param fontInfo the font directory >+ */ >+ public PSFlowExtTextPainter(FontInfo fontInfo) { >+ super(fontInfo); >+ } >+ >+ /** {@inheritDoc} */ >+ public List getTextRuns(TextNode node, AttributedCharacterIterator aci) { >+ //Text runs are delegated to the normal FlowExtTextPainter, we just paint the text. >+ FlowExtTextPainter delegate = (FlowExtTextPainter)FlowExtTextPainter.getInstance(); >+ return delegate.getTextRuns(node, aci); >+ } >+ >+ } >+ >+} > >Property changes on: src\java\org\apache\fop\render\ps\PSBatikFlowTextElementBridge.java >___________________________________________________________________ >Added: svn:keywords > + Id >Added: svn:eol-style > + native > >Index: src/java/org/apache/fop/render/ps/FontResourceCache.java >=================================================================== >--- src/java/org/apache/fop/render/ps/FontResourceCache.java (revision 0) >+++ src/java/org/apache/fop/render/ps/FontResourceCache.java (revision 0) >@@ -0,0 +1,93 @@ >+/* >+ * 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.render.ps; >+ >+import java.util.Map; >+ >+import org.apache.xmlgraphics.ps.PSResource; >+ >+import org.apache.fop.fonts.FontInfo; >+import org.apache.fop.fonts.LazyFont; >+import org.apache.fop.fonts.Typeface; >+ >+/** >+ * A cache for font resource objects. >+ */ >+class FontResourceCache { >+ >+ private FontInfo fontInfo; >+ >+ /** This is a map of PSResource instances of all fonts defined (key: font key) */ >+ private Map fontResources = new java.util.HashMap(); >+ >+ public FontResourceCache(FontInfo fontInfo) { >+ this.fontInfo = fontInfo; >+ } >+ >+ /** >+ * Returns the PSResource for the given font key. >+ * @param key the font key ("F*") >+ * @return the matching PSResource >+ */ >+ public PSResource getPSResourceForFontKey(String key) { >+ PSResource res = null; >+ if (this.fontResources != null) { >+ res = (PSResource)this.fontResources.get(key); >+ } else { >+ this.fontResources = new java.util.HashMap(); >+ } >+ if (res == null) { >+ res = new PSResource(PSResource.TYPE_FONT, getPostScriptNameForFontKey(key)); >+ this.fontResources.put(key, res); >+ } >+ return res; >+ } >+ >+ private String getPostScriptNameForFontKey(String key) { >+ int pos = key.indexOf('_'); >+ String postFix = null; >+ if (pos > 0) { >+ postFix = key.substring(pos); >+ key = key.substring(0, pos); >+ } >+ Map fonts = fontInfo.getFonts(); >+ Typeface tf = (Typeface)fonts.get(key); >+ if (tf instanceof LazyFont) { >+ tf = ((LazyFont)tf).getRealFont(); >+ } >+ if (tf == null) { >+ throw new IllegalStateException("Font not available: " + key); >+ } >+ if (postFix == null) { >+ return tf.getFontName(); >+ } else { >+ return tf.getFontName() + postFix; >+ } >+ } >+ >+ /** >+ * Adds a number of fonts to the cache. >+ * @param fontMap the font map >+ */ >+ public void addAll(Map fontMap) { >+ this.fontResources.putAll(fontMap); >+ } >+ >+} > >Property changes on: src\java\org\apache\fop\render\ps\FontResourceCache.java >___________________________________________________________________ >Added: svn:keywords > + Id >Added: svn:eol-style > + native > >Index: src/java/org/apache/fop/render/ps/AbstractPSTranscoder.java >=================================================================== >--- src/java/org/apache/fop/render/ps/AbstractPSTranscoder.java (revision 761552) >+++ src/java/org/apache/fop/render/ps/AbstractPSTranscoder.java (working copy) >@@ -28,20 +28,18 @@ > import org.w3c.dom.Document; > import org.w3c.dom.svg.SVGLength; > >-import org.apache.avalon.framework.configuration.Configuration; > import org.apache.batik.bridge.BridgeContext; > import org.apache.batik.bridge.UnitProcessor; > import org.apache.batik.transcoder.TranscoderException; > import org.apache.batik.transcoder.TranscoderOutput; > import org.apache.batik.transcoder.image.ImageTranscoder; > >-import org.apache.xmlgraphics.java2d.TextHandler; > import org.apache.xmlgraphics.java2d.ps.AbstractPSDocumentGraphics2D; >-import org.apache.xmlgraphics.ps.PSGenerator; > >+import org.apache.fop.apps.FOPException; > import org.apache.fop.fonts.FontInfo; >-import org.apache.fop.fonts.FontSetup; > import org.apache.fop.svg.AbstractFOPTranscoder; >+import org.apache.fop.svg.PDFDocumentGraphics2DConfigurator; > > /** > * This class enables to transcode an input to a PostScript document. >@@ -72,9 +70,11 @@ > */ > public abstract class AbstractPSTranscoder extends AbstractFOPTranscoder { > >- private final Configuration cfg = null; >+ /** the root Graphics2D instance for generating PostScript */ > protected AbstractPSDocumentGraphics2D graphics = null; > >+ private FontInfo fontInfo; >+ > /** > * Constructs a new <tt>AbstractPSTranscoder</tt>. > */ >@@ -82,6 +82,10 @@ > super(); > } > >+ /** >+ * Creates the root Graphics2D instance for generating PostScript. >+ * @return the root Graphics2D >+ */ > protected abstract AbstractPSDocumentGraphics2D createDocumentGraphics2D(); > > /** >@@ -98,11 +102,13 @@ > > graphics = createDocumentGraphics2D(); > if (!isTextStroked()) { >- FontInfo fontInfo = new FontInfo(); >- //TODO Do custom font configuration here somewhere/somehow >- FontSetup.setup(fontInfo); >- PSGenerator generator = graphics.getPSGenerator(); >- graphics.setCustomTextHandler(new NativeTextHandler(graphics, fontInfo)); >+ try { >+ this.fontInfo = PDFDocumentGraphics2DConfigurator.createFontInfo( >+ getEffectiveConfiguration()); >+ graphics.setCustomTextHandler(new NativeTextHandler(graphics, fontInfo)); >+ } catch (FOPException fe) { >+ throw new TranscoderException(fe); >+ } > } > > super.transcode(document, uri, output); >@@ -146,21 +152,15 @@ > > /** {@inheritDoc} */ > protected BridgeContext createBridgeContext() { >+ //For compatibility with Batik 1.6 >+ return createBridgeContext("1.x"); >+ } > >- BridgeContext ctx = new BridgeContext(userAgent); >- if (!isTextStroked()) { >- TextHandler handler = graphics.getCustomTextHandler(); >- if (handler instanceof NativeTextHandler) { >- NativeTextHandler nativeTextHandler = (NativeTextHandler)handler; >- PSTextPainter textPainter = new PSTextPainter(nativeTextHandler); >- ctx.setTextPainter(textPainter); >- ctx.putBridge(new PSTextElementBridge(textPainter)); >- } >- } >- >- //ctx.putBridge(new PSImageElementBridge()); >+ /** {@inheritDoc} */ >+ public BridgeContext createBridgeContext(String version) { >+ BridgeContext ctx = new PSBridgeContext(userAgent, (isTextStroked() ? null : fontInfo), >+ getImageManager(), getImageSessionContext()); > return ctx; > } > >- > }
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
Attachments on
bug 47000
: 23467