From e471816c05743e7e1517710cedb5cc256901d3e6 Mon Sep 17 00:00:00 2001 From: Jeremias Maerki Date: Mon, 20 Apr 2009 06:50:59 +0000 Subject: [PATCH] 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 --- lib/xmlgraphics-commons-1.4svn.jar | Bin 540172 -> 549055 bytes .../fop/render/ps/AbstractPSTranscoder.java | 46 +- .../fop/render/ps/FontResourceCache.java | 93 ++ .../ps/PSBatikFlowTextElementBridge.java | 86 ++ .../apache/fop/render/ps/PSBridgeContext.java | 113 +++ .../fop/render/ps/PSDocumentHandler.java | 43 +- .../fop/render/ps/PSImageHandlerSVG.java | 14 +- .../org/apache/fop/render/ps/PSPainter.java | 5 +- .../render/ps/PSSVGFlowRootElementBridge.java | 86 ++ .../apache/fop/render/ps/PSSVGHandler.java | 15 +- .../fop/render/ps/PSTextElementBridge.java | 63 +- .../apache/fop/render/ps/PSTextPainter.java | 869 +++++++++--------- .../fop/svg/AbstractFOPBridgeContext.java | 6 +- .../fop/svg/AbstractFOPTextElementBridge.java | 48 +- .../apache/fop/svg/AbstractFOPTranscoder.java | 135 ++- .../org/apache/fop/svg/NativeTextPainter.java | 224 +++++ .../org/apache/fop/svg/PDFBridgeContext.java | 11 +- .../PDFDocumentGraphics2DConfigurator.java | 23 +- .../org/apache/fop/svg/PDFTextPainter.java | 363 +++----- .../org/apache/fop/svg/PDFTranscoder.java | 92 +- status.xml | 4 + 21 files changed, 1350 insertions(+), 989 deletions(-) create mode 100644 src/java/org/apache/fop/render/ps/FontResourceCache.java create mode 100644 src/java/org/apache/fop/render/ps/PSBatikFlowTextElementBridge.java create mode 100644 src/java/org/apache/fop/render/ps/PSBridgeContext.java create mode 100644 src/java/org/apache/fop/render/ps/PSSVGFlowRootElementBridge.java create mode 100644 src/java/org/apache/fop/svg/NativeTextPainter.java diff --git a/lib/xmlgraphics-commons-1.4svn.jar b/lib/xmlgraphics-commons-1.4svn.jar index 11a3d97b3eac1d0d6fd36b82154cb1bd6b4f51b2..c04cb18afe380d28a6d12f273d3d3aa5694c9b1d 100644 GIT binary patch delta 56913 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 delta 48738 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 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 AbstractPSTranscoder. */ @@ -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 TextNode. - * 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 Keiron Liddle - * @version $Id$ + *

+ * 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,23 +41,41 @@ 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. */ 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 PDFTranscoder. */ @@ -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. --> + + 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. + Fixed a bug that left the PrintRenderer unconfigured even if a configuration was specified for "application/X-fop-print". -- 2.39.5