diff options
author | Jeremias Maerki <jeremias@apache.org> | 2009-04-20 06:50:59 +0000 |
---|---|---|
committer | Jeremias Maerki <jeremias@apache.org> | 2009-04-20 06:50:59 +0000 |
commit | e471816c05743e7e1517710cedb5cc256901d3e6 (patch) | |
tree | ee9d62bf08460fcca71b28b24c5de80cf3a25e96 | |
parent | 5deedc814cedf4e1c2ffa746a22bdba34b031d5d (diff) | |
download | xmlgraphics-fop-e471816c05743e7e1517710cedb5cc256901d3e6.tar.gz xmlgraphics-fop-e471816c05743e7e1517710cedb5cc256901d3e6.zip |
Bugzilla #47000:
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.
git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@766594 13f79535-47bb-0310-9956-ffa450edef68
21 files changed, 1350 insertions, 989 deletions
diff --git a/lib/xmlgraphics-commons-1.4svn.jar b/lib/xmlgraphics-commons-1.4svn.jar Binary files differindex 11a3d97b3..c04cb18af 100644 --- a/lib/xmlgraphics-commons-1.4svn.jar +++ b/lib/xmlgraphics-commons-1.4svn.jar diff --git a/src/java/org/apache/fop/render/ps/AbstractPSTranscoder.java b/src/java/org/apache/fop/render/ps/AbstractPSTranscoder.java index 705515311..d6f5fe2ed 100644 --- a/src/java/org/apache/fop/render/ps/AbstractPSTranscoder.java +++ b/src/java/org/apache/fop/render/ps/AbstractPSTranscoder.java @@ -28,20 +28,18 @@ import java.io.OutputStream; 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 @@ import org.apache.fop.svg.AbstractFOPTranscoder; */ 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 @@ public abstract class AbstractPSTranscoder extends AbstractFOPTranscoder { super(); } + /** + * Creates the root Graphics2D instance for generating PostScript. + * @return the root Graphics2D + */ protected abstract AbstractPSDocumentGraphics2D createDocumentGraphics2D(); /** @@ -98,11 +102,13 @@ public abstract class AbstractPSTranscoder extends AbstractFOPTranscoder { 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 @@ public abstract class AbstractPSTranscoder extends AbstractFOPTranscoder { /** {@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; } - } diff --git a/src/java/org/apache/fop/render/ps/FontResourceCache.java b/src/java/org/apache/fop/render/ps/FontResourceCache.java new file mode 100644 index 000000000..7d6f076a7 --- /dev/null +++ b/src/java/org/apache/fop/render/ps/FontResourceCache.java @@ -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); + } + +} diff --git a/src/java/org/apache/fop/render/ps/PSBatikFlowTextElementBridge.java b/src/java/org/apache/fop/render/ps/PSBatikFlowTextElementBridge.java new file mode 100644 index 000000000..31571f987 --- /dev/null +++ b/src/java/org/apache/fop/render/ps/PSBatikFlowTextElementBridge.java @@ -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); + } + + } + +} diff --git a/src/java/org/apache/fop/render/ps/PSBridgeContext.java b/src/java/org/apache/fop/render/ps/PSBridgeContext.java new file mode 100644 index 000000000..1ec6acadf --- /dev/null +++ b/src/java/org/apache/fop/render/ps/PSBridgeContext.java @@ -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); + } + +} diff --git a/src/java/org/apache/fop/render/ps/PSDocumentHandler.java b/src/java/org/apache/fop/render/ps/PSDocumentHandler.java index 1379651c8..b30d5f248 100644 --- a/src/java/org/apache/fop/render/ps/PSDocumentHandler.java +++ b/src/java/org/apache/fop/render/ps/PSDocumentHandler.java @@ -50,8 +50,6 @@ import org.apache.xmlgraphics.ps.dsc.events.DSCCommentBoundingBox; 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 @@ public class PSDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { /** 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 @@ public class PSDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { /** {@inheritDoc} */ public void startDocument() throws IFException { super.startDocument(); + this.fontResources = new FontResourceCache(getFontInfo()); try { OutputStream out; if (psUtil.isOptimizeResources()) { @@ -200,7 +199,7 @@ public class PSDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { 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 @@ public class PSDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { } } - 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); } /** diff --git a/src/java/org/apache/fop/render/ps/PSImageHandlerSVG.java b/src/java/org/apache/fop/render/ps/PSImageHandlerSVG.java index f6679e8da..41cba7563 100644 --- a/src/java/org/apache/fop/render/ps/PSImageHandlerSVG.java +++ b/src/java/org/apache/fop/render/ps/PSImageHandlerSVG.java @@ -65,16 +65,10 @@ public class PSImageHandlerSVG implements ImageHandler { 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 { diff --git a/src/java/org/apache/fop/render/ps/PSPainter.java b/src/java/org/apache/fop/render/ps/PSPainter.java index cb88f4670..051013a63 100644 --- a/src/java/org/apache/fop/render/ps/PSPainter.java +++ b/src/java/org/apache/fop/render/ps/PSPainter.java @@ -350,7 +350,6 @@ public class PSPainter extends AbstractIFPainter { //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 @@ public class PSPainter extends AbstractIFPainter { } 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"); diff --git a/src/java/org/apache/fop/render/ps/PSSVGFlowRootElementBridge.java b/src/java/org/apache/fop/render/ps/PSSVGFlowRootElementBridge.java new file mode 100644 index 000000000..56b1f91bd --- /dev/null +++ b/src/java/org/apache/fop/render/ps/PSSVGFlowRootElementBridge.java @@ -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); + } + + } + +} diff --git a/src/java/org/apache/fop/render/ps/PSSVGHandler.java b/src/java/org/apache/fop/render/ps/PSSVGHandler.java index 75182682d..5cc1a1b01 100644 --- a/src/java/org/apache/fop/render/ps/PSSVGHandler.java +++ b/src/java/org/apache/fop/render/ps/PSSVGHandler.java @@ -259,17 +259,10 @@ public class PSSVGHandler extends AbstractGenericSVGHandler 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. diff --git a/src/java/org/apache/fop/render/ps/PSTextElementBridge.java b/src/java/org/apache/fop/render/ps/PSTextElementBridge.java index ab0c2d723..524fbdad8 100644 --- a/src/java/org/apache/fop/render/ps/PSTextElementBridge.java +++ b/src/java/org/apache/fop/render/ps/PSTextElementBridge.java @@ -19,13 +19,13 @@ 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.w3c.dom.Element; -import org.w3c.dom.Node; +import org.apache.batik.gvt.TextPainter; /** * Bridge class for the <text> element. @@ -37,13 +37,13 @@ import org.w3c.dom.Node; */ 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 class PSTextElementBridge extends SVGTextElementBridge { */ 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; - } } diff --git a/src/java/org/apache/fop/render/ps/PSTextPainter.java b/src/java/org/apache/fop/render/ps/PSTextPainter.java index a318c6465..018b6f9b7 100644 --- a/src/java/org/apache/fop/render/ps/PSTextPainter.java +++ b/src/java/org/apache/fop/render/ps/PSTextPainter.java @@ -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.fop.fonts.Font; -import org.apache.fop.fonts.FontInfo; -import org.apache.fop.fonts.FontTriplet; +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.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; - } - - private boolean hasUnsupportedAttributes(AttributedCharacterIterator aci) { - boolean hasunsupported = false; - - String text = getText(aci); - Font font = makeFont(aci); - if (hasUnsupportedGlyphs(text, font)) { - log.trace("-> Unsupported glyphs found"); - hasunsupported = true; - } - - 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; - } - - //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; - } + if ((tpi != null) && (tpi.composite != null)) { + g2d.setComposite(tpi.composite); } - Object letSpace = aci.getAttribute( - GVTAttributedCharacterIterator.TextAttribute.LETTER_SPACING); - if (letSpace != null) { - log.trace("-> letter spacing found"); - hasunsupported = true; - } + //------------------------------------ + TextSpanLayout layout = textRun.getLayout(); + logTextRun(runaci, layout); + CharSequence chars = collectCharacters(runaci); + runaci.first(); //Reset ACI - Object wordSpace = aci.getAttribute( - GVTAttributedCharacterIterator.TextAttribute.WORD_SPACING); - if (wordSpace != null) { - log.trace("-> word spacing found"); - hasunsupported = true; - } + final PSGraphics2D ps = (PSGraphics2D)g2d; + final PSGenerator gen = ps.getPSGenerator(); + ps.preparePainting(); - Object lengthAdjust = aci.getAttribute( - GVTAttributedCharacterIterator.TextAttribute.LENGTH_ADJUST); - if (lengthAdjust != null) { - log.trace("-> length adjustments found"); - hasunsupported = true; + if (DEBUG) { + log.debug("Text: " + chars); + gen.commentln("%Text: " + chars); } - 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; + GeneralPath debugShapes = null; + if (DEBUG) { + debugShapes = new GeneralPath(); } - Object vertOr = aci.getAttribute( - GVTAttributedCharacterIterator.TextAttribute.VERTICAL_ORIENTATION); - if (GVTAttributedCharacterIterator.TextAttribute.ORIENTATION_ANGLE.equals( - vertOr)) { - log.trace("-> vertical orientation 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 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 - } + gen.saveGraphicsState(); + gen.concatMatrix(g2d.getTransform()); + Shape imclip = g2d.getClip(); + clip(ps, imclip); + + gen.writeln("BT"); //beginTextObject() + + AffineTransform localTransform = new AffineTransform(); + Point2D prevPos = null; + GVTGlyphVector gv = layout.getGlyphVector(); + PSTextRun psRun = new PSTextRun(); //Used to split a text run into smaller runs + for (int index = 0, c = gv.getNumGlyphs(); index < c; index++) { + char ch = chars.charAt(index); + boolean visibleChar = gv.isGlyphVisible(index) + || (CharUtilities.isAnySpace(ch) && !CharUtilities.isZeroWidthSpace(ch)); + logCharacter(ch, layout, index, visibleChar); + if (!visibleChar) { + continue; + } + Point2D glyphPos = gv.getGlyphPosition(index); + + AffineTransform glyphTransform = gv.getGlyphTransform(index); + if (log.isTraceEnabled()) { + log.trace("pos " + glyphPos + ", transform " + glyphTransform); + } + if (DEBUG) { + Shape sh = gv.getGlyphLogicalBounds(index); + if (sh == null) { + sh = new Ellipse2D.Double(glyphPos.getX(), glyphPos.getY(), 2, 2); + } + debugShapes.append(sh, false); + } + + //Exact position of the glyph + localTransform.setToIdentity(); + localTransform.translate(glyphPos.getX(), glyphPos.getY()); + if (glyphTransform != null) { + localTransform.concatenate(glyphTransform); + } + localTransform.scale(1, -1); + + boolean flushCurrentRun = false; + //Try to optimize by combining characters using the same font and on the same line. + if (glyphTransform != null) { + //Happens for text-on-a-path + flushCurrentRun = true; + } + if (psRun.getRunLength() >= 128) { + //Don't let a run get too long + flushCurrentRun = true; + } + + //Note the position of the glyph relative to the previous one + Point2D relPos; + if (prevPos == null) { + relPos = new Point2D.Double(0, 0); + } else { + relPos = new Point2D.Double( + glyphPos.getX() - prevPos.getX(), + glyphPos.getY() - prevPos.getY()); + } + if (psRun.vertChanges == 0 + && psRun.getHorizRunLength() > 2 + && relPos.getY() != 0) { + //new line + flushCurrentRun = true; + } - if (hasunsupported) { - log.trace("Unsupported attributes found in ACI, using StrokingTextPainter"); + //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 (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); + 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 + } - //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; + 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"); + } - Float fontSize = (Float)aci.getAttribute(TextAttribute.SIZE); - if (fontSize == null) { - return loc; + 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 } - 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 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); } - g2d.setPaint(foreground); - g2d.setStroke(stroke); - Font font = makeFont(aci); - java.awt.Font awtFont = makeAWTFont(aci, font); + public Font getCurrentFont() { + return this.currentFont; + } - g2d.setFont(awtFont); + public int getCurrentFontEncoding() { + return this.currentEncoding; + } - 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 void setCurrentFont(Font font, int encoding) { + this.currentFont = font; + this.currentEncoding = encoding; } - drawPrimitiveString(g2d, loc, font, txt, tx); - loc.setLocation(loc.getX() + advance, loc.getY()); - return loc; - } + public void setCurrentFont(Font font, char mapped) { + int encoding = mapped / 256; + setCurrentFont(font, encoding); + } - 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 void setupFonts(AttributedCharacterIterator runaci) { + this.fonts = findFonts(runaci); } - } - 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()); - } - if (ypos != null) { - loc.setLocation(loc.getX(), ypos.doubleValue()); - } - if (dxpos != null) { - loc.setLocation(loc.getX() + dxpos.doubleValue(), loc.getY()); - } - if (dypos != null) { - loc.setLocation(loc.getX(), loc.getY() + dypos.doubleValue()); + public boolean hasFonts() { + return (fonts != null) && (fonts.length > 0); } - } - private String getStyle(AttributedCharacterIterator aci) { - Float posture = (Float)aci.getAttribute(TextAttribute.POSTURE); - return ((posture != null) && (posture.floatValue() > 0.0)) - ? "italic" - : "normal"; } - 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 class PSTextRun { - private Font makeFont(AttributedCharacterIterator aci) { - Float fontSize = (Float)aci.getAttribute(TextAttribute.SIZE); - if (fontSize == null) { - fontSize = new Float(10.0f); - } - 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); - } - } + 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(); } - 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 int getHorizRunLength() { + if (this.vertChanges == 0 + && getRunLength() > 0) { + return getRunLength(); + } + return 0; } - if ("italic".equals(style)) { - fStyle |= java.awt.Font.ITALIC; + + public void addCharacter(char paintChar, Point2D relPos) { + addRelativePosition(relPos); + currentChars.append(paintChar); } - 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++; + } + if (relPos.getY() != 0) { + vertChanges++; } - } else { - charWidth = whitespaceWidth; } - 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); - } - - /** - * 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); - } - - /** - * 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); - } - - // Methods that have no purpose for PS + public int getRunLength() { + return currentChars.length(); + } - /** - * 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 boolean isXShow() { + return vertChanges == 0; + } - /** - * 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; - } + private boolean isYShow() { + return horizChanges == 0; + } - /** - * 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; - } + 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); + } + } + } - /** - * 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 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); + + 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 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; - } + private String format(PSGenerator gen, double coord) { + if (Math.abs(coord) < 0.00001) { + return "0"; + } else { + return gen.formatDouble5(coord); + } + } - /** - * 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; - } + private void paintStrokedGlyphs(PSGraphics2D g2d, TextUtil textUtil, + Paint strokePaint, Stroke stroke) throws IOException { + PSGenerator gen = textUtil.gen; + + applyColor(strokePaint, gen); + PSGraphics2D.applyStroke(stroke, gen); + + 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"); + + 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 + } - /** - * 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; } } diff --git a/src/java/org/apache/fop/svg/AbstractFOPBridgeContext.java b/src/java/org/apache/fop/svg/AbstractFOPBridgeContext.java index ae4d67516..acb59ed7d 100644 --- a/src/java/org/apache/fop/svg/AbstractFOPBridgeContext.java +++ b/src/java/org/apache/fop/svg/AbstractFOPBridgeContext.java @@ -26,10 +26,12 @@ import org.apache.batik.bridge.Bridge; 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 @@ public abstract class AbstractFOPBridgeContext extends BridgeContext { * @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, diff --git a/src/java/org/apache/fop/svg/AbstractFOPTextElementBridge.java b/src/java/org/apache/fop/svg/AbstractFOPTextElementBridge.java index 53b8e2ad5..aec4126b4 100644 --- a/src/java/org/apache/fop/svg/AbstractFOPTextElementBridge.java +++ b/src/java/org/apache/fop/svg/AbstractFOPTextElementBridge.java @@ -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 @@ public abstract class AbstractFOPTextElementBridge extends SVGTextElementBridge 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; - } - } diff --git a/src/java/org/apache/fop/svg/AbstractFOPTranscoder.java b/src/java/org/apache/fop/svg/AbstractFOPTranscoder.java index 83cfa8021..caae32cf0 100644 --- a/src/java/org/apache/fop/svg/AbstractFOPTranscoder.java +++ b/src/java/org/apache/fop/svg/AbstractFOPTranscoder.java @@ -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,11 +41,16 @@ import org.apache.batik.transcoder.TranscoderException; 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. @@ -40,11 +58,24 @@ import org.xml.sax.EntityResolver; 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 @@ public abstract class AbstractFOPTranscoder extends SVGAbstractTranscoder { 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 @@ public abstract class AbstractFOPTranscoder extends SVGAbstractTranscoder { } /** - * @param logger + * Sets the logger. + * @param logger the logger */ public void setLogger(Log logger) { this.logger = logger; @@ -94,6 +129,35 @@ public abstract class AbstractFOPTranscoder extends SVGAbstractTranscoder { 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. @@ -142,6 +206,71 @@ public abstract class AbstractFOPTranscoder extends SVGAbstractTranscoder { 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) // -------------------------------------------------------------------- diff --git a/src/java/org/apache/fop/svg/NativeTextPainter.java b/src/java/org/apache/fop/svg/NativeTextPainter.java new file mode 100644 index 000000000..7da7269c2 --- /dev/null +++ b/src/java/org/apache/fop/svg/NativeTextPainter.java @@ -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)); + } + } + } + + +} diff --git a/src/java/org/apache/fop/svg/PDFBridgeContext.java b/src/java/org/apache/fop/svg/PDFBridgeContext.java index 364c7a6f3..e8569f881 100644 --- a/src/java/org/apache/fop/svg/PDFBridgeContext.java +++ b/src/java/org/apache/fop/svg/PDFBridgeContext.java @@ -26,10 +26,12 @@ 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.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 @@ public class PDFBridgeContext extends AbstractFOPBridgeContext { /** * 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 @@ public class PDFBridgeContext extends AbstractFOPBridgeContext { FontInfo fontInfo, ImageManager imageManager, ImageSessionContext imageSessionContext, AffineTransform linkTransform) { - super(userAgent, documentLoader, fontInfo, imageManager, imageSessionContext, linkTransform); + super(userAgent, documentLoader, fontInfo, + imageManager, imageSessionContext, linkTransform); } /** diff --git a/src/java/org/apache/fop/svg/PDFDocumentGraphics2DConfigurator.java b/src/java/org/apache/fop/svg/PDFDocumentGraphics2DConfigurator.java index e101a9573..b77518ab0 100644 --- a/src/java/org/apache/fop/svg/PDFDocumentGraphics2DConfigurator.java +++ b/src/java/org/apache/fop/svg/PDFDocumentGraphics2DConfigurator.java @@ -55,6 +55,22 @@ public class PDFDocumentGraphics2DConfigurator { //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 @@ public class PDFDocumentGraphics2DConfigurator { 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; } } diff --git a/src/java/org/apache/fop/svg/PDFTextPainter.java b/src/java/org/apache/fop/svg/PDFTextPainter.java index 85447a4f9..dddf61a6e 100644 --- a/src/java/org/apache/fop/svg/PDFTextPainter.java +++ b/src/java/org/apache/fop/svg/PDFTextPainter.java @@ -25,28 +25,18 @@ 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.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,193 +49,159 @@ import org.apache.fop.util.CharUtilities; * * @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); + textUtil.saveGraphicsState(); + textUtil.concatMatrix(g2d.getTransform()); + Shape imclip = g2d.getClip(); + pdf.writeClip(imclip); + + applyColorAndPaint(tpi, pdf); + + 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); - textUtil.saveGraphicsState(); - textUtil.concatMatrix(g2d.getTransform()); - Shape imclip = g2d.getClip(); - pdf.writeClip(imclip); - - applyColorAndPaint(tpi, pdf); - - 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)); - 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)); - } - } - if (!visibleChar) { - continue; - } - Point2D p = gv.getGlyphPosition(index); - - 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); + 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); } + debugShapes.append(sh, false); + } - //Exact position of the glyph - localTransform.setToIdentity(); - localTransform.translate(p.getX(), p.getY()); - if (glyphTransform != null) { - localTransform.concatenate(glyphTransform); - } - localTransform.scale(1, -1); + //Exact position of the glyph + localTransform.setToIdentity(); + localTransform.translate(glyphPos.getX(), glyphPos.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); } } @@ -271,85 +227,4 @@ public class PDFTextPainter extends StrokingTextPainter { 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 diff --git a/src/java/org/apache/fop/svg/PDFTranscoder.java b/src/java/org/apache/fop/svg/PDFTranscoder.java index 333cd5e4c..062270f6b 100644 --- a/src/java/org/apache/fop/svg/PDFTranscoder.java +++ b/src/java/org/apache/fop/svg/PDFTranscoder.java @@ -23,34 +23,19 @@ import java.awt.Color; 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 @@ import org.apache.fop.fonts.FontInfo; 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 @@ public class PDFTranscoder extends AbstractFOPTranscoder }; } - /** {@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 @@ public class PDFTranscoder extends AbstractFOPTranscoder + 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 @@ public class PDFTranscoder extends AbstractFOPTranscoder } } - 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 @@ public class PDFTranscoder extends AbstractFOPTranscoder fontInfo = null; } BridgeContext ctx = new PDFBridgeContext(userAgent, fontInfo, - this.imageManager, this.imageSessionContext); + getImageManager(), getImageSessionContext()); return ctx; } diff --git a/status.xml b/status.xml index cbac0a971..fb6fc05ca 100644 --- a/status.xml +++ b/status.xml @@ -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". |