diff options
Diffstat (limited to 'src/java/org/apache/fop/render')
11 files changed, 838 insertions, 595 deletions
diff --git a/src/java/org/apache/fop/render/ps/AbstractPSTranscoder.java b/src/java/org/apache/fop/render/ps/AbstractPSTranscoder.java index 705515311..d6f5fe2ed 100644 --- a/src/java/org/apache/fop/render/ps/AbstractPSTranscoder.java +++ b/src/java/org/apache/fop/render/ps/AbstractPSTranscoder.java @@ -28,20 +28,18 @@ import java.io.OutputStream; import org.w3c.dom.Document; import org.w3c.dom.svg.SVGLength; -import org.apache.avalon.framework.configuration.Configuration; import org.apache.batik.bridge.BridgeContext; import org.apache.batik.bridge.UnitProcessor; import org.apache.batik.transcoder.TranscoderException; import org.apache.batik.transcoder.TranscoderOutput; import org.apache.batik.transcoder.image.ImageTranscoder; -import org.apache.xmlgraphics.java2d.TextHandler; import org.apache.xmlgraphics.java2d.ps.AbstractPSDocumentGraphics2D; -import org.apache.xmlgraphics.ps.PSGenerator; +import org.apache.fop.apps.FOPException; import org.apache.fop.fonts.FontInfo; -import org.apache.fop.fonts.FontSetup; import org.apache.fop.svg.AbstractFOPTranscoder; +import org.apache.fop.svg.PDFDocumentGraphics2DConfigurator; /** * This class enables to transcode an input to a PostScript document. @@ -72,9 +70,11 @@ import org.apache.fop.svg.AbstractFOPTranscoder; */ public abstract class AbstractPSTranscoder extends AbstractFOPTranscoder { - private final Configuration cfg = null; + /** the root Graphics2D instance for generating PostScript */ protected AbstractPSDocumentGraphics2D graphics = null; + private FontInfo fontInfo; + /** * Constructs a new <tt>AbstractPSTranscoder</tt>. */ @@ -82,6 +82,10 @@ public abstract class AbstractPSTranscoder extends AbstractFOPTranscoder { super(); } + /** + * Creates the root Graphics2D instance for generating PostScript. + * @return the root Graphics2D + */ protected abstract AbstractPSDocumentGraphics2D createDocumentGraphics2D(); /** @@ -98,11 +102,13 @@ public abstract class AbstractPSTranscoder extends AbstractFOPTranscoder { graphics = createDocumentGraphics2D(); if (!isTextStroked()) { - FontInfo fontInfo = new FontInfo(); - //TODO Do custom font configuration here somewhere/somehow - FontSetup.setup(fontInfo); - PSGenerator generator = graphics.getPSGenerator(); - graphics.setCustomTextHandler(new NativeTextHandler(graphics, fontInfo)); + try { + this.fontInfo = PDFDocumentGraphics2DConfigurator.createFontInfo( + getEffectiveConfiguration()); + graphics.setCustomTextHandler(new NativeTextHandler(graphics, fontInfo)); + } catch (FOPException fe) { + throw new TranscoderException(fe); + } } super.transcode(document, uri, output); @@ -146,21 +152,15 @@ public abstract class AbstractPSTranscoder extends AbstractFOPTranscoder { /** {@inheritDoc} */ protected BridgeContext createBridgeContext() { + //For compatibility with Batik 1.6 + return createBridgeContext("1.x"); + } - BridgeContext ctx = new BridgeContext(userAgent); - if (!isTextStroked()) { - TextHandler handler = graphics.getCustomTextHandler(); - if (handler instanceof NativeTextHandler) { - NativeTextHandler nativeTextHandler = (NativeTextHandler)handler; - PSTextPainter textPainter = new PSTextPainter(nativeTextHandler); - ctx.setTextPainter(textPainter); - ctx.putBridge(new PSTextElementBridge(textPainter)); - } - } - - //ctx.putBridge(new PSImageElementBridge()); + /** {@inheritDoc} */ + public BridgeContext createBridgeContext(String version) { + BridgeContext ctx = new PSBridgeContext(userAgent, (isTextStroked() ? null : fontInfo), + getImageManager(), getImageSessionContext()); return ctx; } - } diff --git a/src/java/org/apache/fop/render/ps/FontResourceCache.java b/src/java/org/apache/fop/render/ps/FontResourceCache.java new file mode 100644 index 000000000..7d6f076a7 --- /dev/null +++ b/src/java/org/apache/fop/render/ps/FontResourceCache.java @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.ps; + +import java.util.Map; + +import org.apache.xmlgraphics.ps.PSResource; + +import org.apache.fop.fonts.FontInfo; +import org.apache.fop.fonts.LazyFont; +import org.apache.fop.fonts.Typeface; + +/** + * A cache for font resource objects. + */ +class FontResourceCache { + + private FontInfo fontInfo; + + /** This is a map of PSResource instances of all fonts defined (key: font key) */ + private Map fontResources = new java.util.HashMap(); + + public FontResourceCache(FontInfo fontInfo) { + this.fontInfo = fontInfo; + } + + /** + * Returns the PSResource for the given font key. + * @param key the font key ("F*") + * @return the matching PSResource + */ + public PSResource getPSResourceForFontKey(String key) { + PSResource res = null; + if (this.fontResources != null) { + res = (PSResource)this.fontResources.get(key); + } else { + this.fontResources = new java.util.HashMap(); + } + if (res == null) { + res = new PSResource(PSResource.TYPE_FONT, getPostScriptNameForFontKey(key)); + this.fontResources.put(key, res); + } + return res; + } + + private String getPostScriptNameForFontKey(String key) { + int pos = key.indexOf('_'); + String postFix = null; + if (pos > 0) { + postFix = key.substring(pos); + key = key.substring(0, pos); + } + Map fonts = fontInfo.getFonts(); + Typeface tf = (Typeface)fonts.get(key); + if (tf instanceof LazyFont) { + tf = ((LazyFont)tf).getRealFont(); + } + if (tf == null) { + throw new IllegalStateException("Font not available: " + key); + } + if (postFix == null) { + return tf.getFontName(); + } else { + return tf.getFontName() + postFix; + } + } + + /** + * Adds a number of fonts to the cache. + * @param fontMap the font map + */ + public void addAll(Map fontMap) { + this.fontResources.putAll(fontMap); + } + +} diff --git a/src/java/org/apache/fop/render/ps/PSBatikFlowTextElementBridge.java b/src/java/org/apache/fop/render/ps/PSBatikFlowTextElementBridge.java new file mode 100644 index 000000000..31571f987 --- /dev/null +++ b/src/java/org/apache/fop/render/ps/PSBatikFlowTextElementBridge.java @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.ps; + +import java.text.AttributedCharacterIterator; +import java.util.List; + +import org.apache.batik.extension.svg.BatikFlowTextElementBridge; +import org.apache.batik.extension.svg.FlowExtTextPainter; +import org.apache.batik.gvt.GraphicsNode; +import org.apache.batik.gvt.TextNode; +import org.apache.batik.gvt.TextPainter; + +import org.apache.fop.fonts.FontInfo; + +/** + * Element Bridge for Batik's flow text extension, so those texts can be painted using + * PostScript primitives. + */ +public class PSBatikFlowTextElementBridge extends BatikFlowTextElementBridge { + + private PSTextPainter textPainter; + + /** + * Main Constructor. + * @param fontInfo the font directory + */ + public PSBatikFlowTextElementBridge(FontInfo fontInfo) { + this.textPainter = new PSFlowExtTextPainter(fontInfo); + } + + /** {@inheritDoc} */ + protected GraphicsNode instantiateGraphicsNode() { + GraphicsNode node = super.instantiateGraphicsNode(); + if (node != null) { + //Set our own text painter + ((TextNode)node).setTextPainter(getTextPainter()); + } + return node; + } + + /** + * Returns the text painter used by this bridge. + * @return the text painter + */ + public TextPainter getTextPainter() { + return this.textPainter; + } + + private class PSFlowExtTextPainter extends PSTextPainter { + + /** + * Main constructor + * @param fontInfo the font directory + */ + public PSFlowExtTextPainter(FontInfo fontInfo) { + super(fontInfo); + } + + /** {@inheritDoc} */ + public List getTextRuns(TextNode node, AttributedCharacterIterator aci) { + //Text runs are delegated to the normal FlowExtTextPainter, we just paint the text. + FlowExtTextPainter delegate = (FlowExtTextPainter)FlowExtTextPainter.getInstance(); + return delegate.getTextRuns(node, aci); + } + + } + +} diff --git a/src/java/org/apache/fop/render/ps/PSBridgeContext.java b/src/java/org/apache/fop/render/ps/PSBridgeContext.java new file mode 100644 index 000000000..1ec6acadf --- /dev/null +++ b/src/java/org/apache/fop/render/ps/PSBridgeContext.java @@ -0,0 +1,113 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.ps; + +import java.awt.geom.AffineTransform; + +import org.apache.batik.bridge.BridgeContext; +import org.apache.batik.bridge.DocumentLoader; +import org.apache.batik.bridge.SVGTextElementBridge; +import org.apache.batik.bridge.UserAgent; +import org.apache.batik.gvt.TextPainter; + +import org.apache.xmlgraphics.image.loader.ImageManager; +import org.apache.xmlgraphics.image.loader.ImageSessionContext; + +import org.apache.fop.fonts.FontInfo; +import org.apache.fop.svg.AbstractFOPBridgeContext; + +/** + * BridgeContext which registers the custom bridges for PostScript output. + */ +public class PSBridgeContext extends AbstractFOPBridgeContext { + + /** + * Constructs a new bridge context. + * @param userAgent the user agent + * @param documentLoader the Document Loader to use for referenced documents. + * @param fontInfo the font list for the text painter, may be null + * in which case text is painted as shapes + * @param imageManager an image manager + * @param imageSessionContext an image session context + * @param linkTransform AffineTransform to properly place links, + * may be null + */ + public PSBridgeContext(UserAgent userAgent, DocumentLoader documentLoader, + FontInfo fontInfo, ImageManager imageManager, + ImageSessionContext imageSessionContext, + AffineTransform linkTransform) { + super(userAgent, documentLoader, fontInfo, + imageManager, imageSessionContext, linkTransform); + } + + /** + * Constructs a new bridge context. + * @param userAgent the user agent + * @param fontInfo the font list for the text painter, may be null + * in which case text is painted as shapes + * @param imageManager an image manager + * @param imageSessionContext an image session context + */ + public PSBridgeContext(UserAgent userAgent, FontInfo fontInfo, + ImageManager imageManager, ImageSessionContext imageSessionContext) { + super(userAgent, fontInfo, imageManager, imageSessionContext); + } + + /** {@inheritDoc} */ + public void registerSVGBridges() { + super.registerSVGBridges(); + + if (fontInfo != null) { + TextPainter textPainter = new PSTextPainter(fontInfo); + SVGTextElementBridge textElementBridge = new PSTextElementBridge(textPainter); + putBridge(textElementBridge); + + //Batik flow text extension (may not always be available) + //putBridge(new PDFBatikFlowTextElementBridge(fontInfo); + putElementBridgeConditional( + "org.apache.fop.render.ps.PSBatikFlowTextElementBridge", + "org.apache.batik.extension.svg.BatikFlowTextElementBridge"); + + //SVG 1.2 flow text support + //putBridge(new PDFSVG12TextElementBridge(fontInfo)); //-->Batik 1.7 + putElementBridgeConditional( + "org.apache.fop.render.ps.PSSVG12TextElementBridge", + "org.apache.batik.bridge.svg12.SVG12TextElementBridge"); + + //putBridge(new PDFSVGFlowRootElementBridge(fontInfo)); + putElementBridgeConditional( + "org.apache.fop.render.ps.PSSVGFlowRootElementBridge", + "org.apache.batik.bridge.svg12.SVGFlowRootElementBridge"); + } + + //putBridge(new PSImageElementBridge()); //TODO uncomment when implemented + } + + // Make sure any 'sub bridge contexts' also have our bridges. + //TODO There's no matching method in the super-class here + public BridgeContext createBridgeContext() { + return new PSBridgeContext(getUserAgent(), getDocumentLoader(), + fontInfo, + getImageManager(), + getImageSessionContext(), + linkTransform); + } + +} diff --git a/src/java/org/apache/fop/render/ps/PSDocumentHandler.java b/src/java/org/apache/fop/render/ps/PSDocumentHandler.java index 1379651c8..b30d5f248 100644 --- a/src/java/org/apache/fop/render/ps/PSDocumentHandler.java +++ b/src/java/org/apache/fop/render/ps/PSDocumentHandler.java @@ -50,8 +50,6 @@ import org.apache.xmlgraphics.ps.dsc.events.DSCCommentBoundingBox; import org.apache.xmlgraphics.ps.dsc.events.DSCCommentHiResBoundingBox; import org.apache.fop.apps.MimeConstants; -import org.apache.fop.fonts.LazyFont; -import org.apache.fop.fonts.Typeface; import org.apache.fop.render.intermediate.AbstractBinaryWritingIFDocumentHandler; import org.apache.fop.render.intermediate.IFContext; import org.apache.fop.render.intermediate.IFDocumentHandlerConfigurator; @@ -91,8 +89,8 @@ public class PSDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { /** Used to temporarily store PSSetupCode instance until they can be written. */ private List setupCodeList; - /** This is a map of PSResource instances of all fonts defined (key: font key) */ - private Map fontResources; + /** This is a cache of PSResource instances of all fonts defined */ + private FontResourceCache fontResources; /** This is a map of PSResource instances of all forms (key: uri) */ private Map formResources; @@ -139,6 +137,7 @@ public class PSDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { /** {@inheritDoc} */ public void startDocument() throws IFException { super.startDocument(); + this.fontResources = new FontResourceCache(getFontInfo()); try { OutputStream out; if (psUtil.isOptimizeResources()) { @@ -200,7 +199,7 @@ public class PSDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { gen.writeDSCComment(DSCConstants.BEGIN_SETUP); PSRenderingUtil.writeSetupCodeList(gen, setupCodeList, "SetupCode"); if (!psUtil.isOptimizeResources()) { - this.fontResources = PSFontUtils.writeFontDict(gen, fontInfo); + this.fontResources.addAll(PSFontUtils.writeFontDict(gen, fontInfo)); } else { gen.commentln("%FOPFontSetup"); //Place-holder, will be replaced in the second pass } @@ -534,45 +533,13 @@ public class PSDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { } } - private String getPostScriptNameForFontKey(String key) { - int pos = key.indexOf('_'); - String postFix = null; - if (pos > 0) { - postFix = key.substring(pos); - key = key.substring(0, pos); - } - Map fonts = fontInfo.getFonts(); - Typeface tf = (Typeface)fonts.get(key); - if (tf instanceof LazyFont) { - tf = ((LazyFont)tf).getRealFont(); - } - if (tf == null) { - throw new IllegalStateException("Font not available: " + key); - } - if (postFix == null) { - return tf.getFontName(); - } else { - return tf.getFontName() + postFix; - } - } - /** * Returns the PSResource for the given font key. * @param key the font key ("F*") * @return the matching PSResource */ protected PSResource getPSResourceForFontKey(String key) { - PSResource res = null; - if (this.fontResources != null) { - res = (PSResource)this.fontResources.get(key); - } else { - this.fontResources = new java.util.HashMap(); - } - if (res == null) { - res = new PSResource(PSResource.TYPE_FONT, getPostScriptNameForFontKey(key)); - this.fontResources.put(key, res); - } - return res; + return this.fontResources.getPSResourceForFontKey(key); } /** diff --git a/src/java/org/apache/fop/render/ps/PSImageHandlerSVG.java b/src/java/org/apache/fop/render/ps/PSImageHandlerSVG.java index f6679e8da..41cba7563 100644 --- a/src/java/org/apache/fop/render/ps/PSImageHandlerSVG.java +++ b/src/java/org/apache/fop/render/ps/PSImageHandlerSVG.java @@ -65,16 +65,10 @@ public class PSImageHandlerSVG implements ImageHandler { PSGraphics2D graphics = new PSGraphics2D(strokeText, gen); graphics.setGraphicContext(new org.apache.xmlgraphics.java2d.GraphicContext()); - NativeTextHandler nativeTextHandler = null; - BridgeContext ctx = new BridgeContext(ua); - if (!strokeText) { - nativeTextHandler = new NativeTextHandler(graphics, psContext.getFontInfo()); - graphics.setCustomTextHandler(nativeTextHandler); - PSTextPainter textPainter = new PSTextPainter(nativeTextHandler); - ctx.setTextPainter(textPainter); - PSTextElementBridge tBridge = new PSTextElementBridge(textPainter); - ctx.putBridge(tBridge); - } + BridgeContext ctx = new PSBridgeContext(ua, + (strokeText ? null : psContext.getFontInfo()), + context.getUserAgent().getFactory().getImageManager(), + context.getUserAgent().getImageSessionContext()); GraphicsNode root; try { diff --git a/src/java/org/apache/fop/render/ps/PSPainter.java b/src/java/org/apache/fop/render/ps/PSPainter.java index cb88f4670..051013a63 100644 --- a/src/java/org/apache/fop/render/ps/PSPainter.java +++ b/src/java/org/apache/fop/render/ps/PSPainter.java @@ -350,7 +350,6 @@ public class PSPainter extends AbstractIFPainter { //TODO Opportunity for font caching if font state is more heavily used String fontKey = getFontInfo().getInternalFontKey(triplet); int sizeMillipoints = state.getFontSize(); - float fontSize = sizeMillipoints / 1000f; // This assumes that *all* CIDFonts use a /ToUnicode mapping Typeface tf = getTypeface(fontKey); @@ -360,9 +359,7 @@ public class PSPainter extends AbstractIFPainter { } Font font = getFontInfo().getFontInstance(triplet, sizeMillipoints); - PSResource res = this.documentHandler.getPSResourceForFontKey(fontKey); - generator.useFont("/" + res.getName(), fontSize); - generator.getResourceTracker().notifyResourceUsageOnPage(res); + useFont(fontKey, sizeMillipoints); generator.writeln("1 0 0 -1 " + formatMptAsPt(generator, x) + " " + formatMptAsPt(generator, y) + " Tm"); diff --git a/src/java/org/apache/fop/render/ps/PSSVGFlowRootElementBridge.java b/src/java/org/apache/fop/render/ps/PSSVGFlowRootElementBridge.java new file mode 100644 index 000000000..56b1f91bd --- /dev/null +++ b/src/java/org/apache/fop/render/ps/PSSVGFlowRootElementBridge.java @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.ps; + +import java.text.AttributedCharacterIterator; +import java.util.List; + +import org.apache.batik.bridge.svg12.SVGFlowRootElementBridge; +import org.apache.batik.gvt.GraphicsNode; +import org.apache.batik.gvt.TextNode; +import org.apache.batik.gvt.TextPainter; +import org.apache.batik.gvt.flow.FlowTextPainter; + +import org.apache.fop.fonts.FontInfo; + +/** + * Element Bridge for SVG 1.2 flow text, so those texts can be painted using + * PDF primitives. + */ +public class PSSVGFlowRootElementBridge extends SVGFlowRootElementBridge { + + private PSTextPainter textPainter; + + /** + * Main Constructor. + * @param fontInfo the font directory + */ + public PSSVGFlowRootElementBridge(FontInfo fontInfo) { + this.textPainter = new PSFlowTextPainter(fontInfo); + } + + /** {@inheritDoc} */ + protected GraphicsNode instantiateGraphicsNode() { + GraphicsNode node = super.instantiateGraphicsNode(); + if (node != null) { + //Set our own text painter + ((TextNode)node).setTextPainter(getTextPainter()); + } + return node; + } + + /** + * Returns the text painter used by this bridge. + * @return the text painter + */ + public TextPainter getTextPainter() { + return this.textPainter; + } + + private class PSFlowTextPainter extends PSTextPainter { + + /** + * Main constructor + * @param fontInfo the font directory + */ + public PSFlowTextPainter(FontInfo fontInfo) { + super(fontInfo); + } + + /** {@inheritDoc} */ + public List getTextRuns(TextNode node, AttributedCharacterIterator aci) { + //Text runs are delegated to the normal FlowTextPainter, we just paint the text. + FlowTextPainter delegate = (FlowTextPainter)FlowTextPainter.getInstance(); + return delegate.getTextRuns(node, aci); + } + + } + +} diff --git a/src/java/org/apache/fop/render/ps/PSSVGHandler.java b/src/java/org/apache/fop/render/ps/PSSVGHandler.java index 75182682d..5cc1a1b01 100644 --- a/src/java/org/apache/fop/render/ps/PSSVGHandler.java +++ b/src/java/org/apache/fop/render/ps/PSSVGHandler.java @@ -259,17 +259,10 @@ public class PSSVGHandler extends AbstractGenericSVGHandler PSGraphics2D graphics = new PSGraphics2D(strokeText, gen); graphics.setGraphicContext(new org.apache.xmlgraphics.java2d.GraphicContext()); - NativeTextHandler nativeTextHandler = null; - BridgeContext ctx = new BridgeContext(ua); - if (!strokeText) { - FontInfo fontInfo = psInfo.getFontInfo(); - nativeTextHandler = new NativeTextHandler(graphics, fontInfo); - graphics.setCustomTextHandler(nativeTextHandler); - PSTextPainter textPainter = new PSTextPainter(nativeTextHandler); - ctx.setTextPainter(textPainter); - PSTextElementBridge tBridge = new PSTextElementBridge(textPainter); - ctx.putBridge(tBridge); - } + BridgeContext ctx = new PSBridgeContext(ua, + (strokeText ? null : psInfo.fontInfo), + context.getUserAgent().getFactory().getImageManager(), + context.getUserAgent().getImageSessionContext()); //Cloning SVG DOM as Batik attaches non-thread-safe facilities (like the CSS engine) //to it. diff --git a/src/java/org/apache/fop/render/ps/PSTextElementBridge.java b/src/java/org/apache/fop/render/ps/PSTextElementBridge.java index ab0c2d723..524fbdad8 100644 --- a/src/java/org/apache/fop/render/ps/PSTextElementBridge.java +++ b/src/java/org/apache/fop/render/ps/PSTextElementBridge.java @@ -19,13 +19,13 @@ package org.apache.fop.render.ps; -import org.apache.batik.bridge.SVGTextElementBridge; +import org.w3c.dom.Element; + import org.apache.batik.bridge.BridgeContext; +import org.apache.batik.bridge.SVGTextElementBridge; import org.apache.batik.gvt.GraphicsNode; import org.apache.batik.gvt.TextNode; - -import org.w3c.dom.Element; -import org.w3c.dom.Node; +import org.apache.batik.gvt.TextPainter; /** * Bridge class for the <text> element. @@ -37,13 +37,13 @@ import org.w3c.dom.Node; */ public class PSTextElementBridge extends SVGTextElementBridge { - private PSTextPainter textPainter; + private TextPainter textPainter; /** * Constructs a new bridge for the <text> element. * @param textPainter the text painter to use */ - public PSTextElementBridge(PSTextPainter textPainter) { + public PSTextElementBridge(TextPainter textPainter) { this.textPainter = textPainter; } @@ -56,60 +56,13 @@ public class PSTextElementBridge extends SVGTextElementBridge { */ public GraphicsNode createGraphicsNode(BridgeContext ctx, Element e) { GraphicsNode node = super.createGraphicsNode(ctx, e); - /* this code is worthless I think. PSTextPainter does a much better job - * at determining whether to stroke or not. */ - if (true/*node != null && isSimple(ctx, e, node)*/) { - ((TextNode)node).setTextPainter(getTextPainter()); - } + ((TextNode)node).setTextPainter(getTextPainter()); return node; } - private PSTextPainter getTextPainter() { + private TextPainter getTextPainter() { return this.textPainter; } - /** - * Check if text element contains simple text. - * This checks the children of the text element to determine - * if the text is simple. The text is simple if it can be rendered - * with basic text drawing algorithms. This means there are no - * alternate characters, the font is known and there are no effects - * applied to the text. - * - * @param ctx the bridge context - * @param element the svg text element - * @param node the graphics node - * @return true if this text is simple of false if it cannot be - * easily rendered using normal drawString on the PDFGraphics2D - */ - private boolean isSimple(BridgeContext ctx, Element element, GraphicsNode node) { - for (Node n = element.getFirstChild(); - n != null; - n = n.getNextSibling()) { - - switch (n.getNodeType()) { - case Node.ELEMENT_NODE: - - if (n.getLocalName().equals(SVG_TSPAN_TAG) - || n.getLocalName().equals(SVG_ALT_GLYPH_TAG)) { - return false; - } else if (n.getLocalName().equals(SVG_TEXT_PATH_TAG)) { - return false; - } else if (n.getLocalName().equals(SVG_TREF_TAG)) { - return false; - } - break; - case Node.TEXT_NODE: - case Node.CDATA_SECTION_NODE: - default: - } - } - - /*if (CSSUtilities.convertFilter(element, node, ctx) != null) { - return false; - }*/ - - return true; - } } diff --git a/src/java/org/apache/fop/render/ps/PSTextPainter.java b/src/java/org/apache/fop/render/ps/PSTextPainter.java index a318c6465..018b6f9b7 100644 --- a/src/java/org/apache/fop/render/ps/PSTextPainter.java +++ b/src/java/org/apache/fop/render/ps/PSTextPainter.java @@ -19,555 +19,516 @@ package org.apache.fop.render.ps; +import java.awt.BasicStroke; import java.awt.Color; import java.awt.Graphics2D; import java.awt.Paint; import java.awt.Shape; import java.awt.Stroke; -import java.awt.font.TextAttribute; +import java.awt.geom.AffineTransform; +import java.awt.geom.Ellipse2D; +import java.awt.geom.GeneralPath; +import java.awt.geom.PathIterator; import java.awt.geom.Point2D; -import java.awt.geom.Rectangle2D; import java.io.IOException; import java.text.AttributedCharacterIterator; -import java.text.CharacterIterator; import java.util.Iterator; import java.util.List; -import org.apache.batik.dom.svg.SVGOMTextElement; -import org.apache.batik.gvt.TextNode; -import org.apache.batik.gvt.TextPainter; -import org.apache.batik.gvt.font.GVTFontFamily; -import org.apache.batik.gvt.renderer.StrokingTextPainter; -import org.apache.batik.gvt.text.GVTAttributedCharacterIterator; -import org.apache.batik.gvt.text.Mark; +import org.apache.batik.gvt.font.GVTGlyphVector; import org.apache.batik.gvt.text.TextPaintInfo; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.fop.fonts.Font; -import org.apache.fop.fonts.FontInfo; -import org.apache.fop.fonts.FontTriplet; +import org.apache.batik.gvt.text.TextSpanLayout; + import org.apache.xmlgraphics.java2d.ps.PSGraphics2D; +import org.apache.xmlgraphics.ps.PSGenerator; +import org.apache.xmlgraphics.ps.PSResource; +import org.apache.fop.fonts.Font; +import org.apache.fop.fonts.FontInfo; +import org.apache.fop.svg.NativeTextPainter; +import org.apache.fop.util.CharUtilities; /** * Renders the attributed character iterator of a <tt>TextNode</tt>. - * This class draws the text directly into the PSGraphics2D so that + * This class draws the text directly using PostScript text operators so * the text is not drawn using shapes which makes the PS files larger. - * If the text is simple enough to draw then it sets the font and calls - * drawString. If the text is complex or the cannot be translated - * into a simple drawString the StrokingTextPainter is used instead. - * - * (todo) handle underline, overline and strikethrough - * (todo) use drawString(AttributedCharacterIterator iterator...) for some - * - * @author <a href="mailto:keiron@aftexsw.com">Keiron Liddle</a> - * @version $Id$ + * <p> + * The text runs are split into smaller text runs that can be bundles in single + * calls of the xshow, yshow or xyshow operators. For outline text, the charpath + * operator is used. */ -public class PSTextPainter implements TextPainter { +public class PSTextPainter extends NativeTextPainter { - /** the logger for this class */ - protected Log log = LogFactory.getLog(PSTextPainter.class); + private static final boolean DEBUG = false; - private final NativeTextHandler nativeTextHandler; - private final FontInfo fontInfo; + private FontResourceCache fontResources; - /** - * Use the stroking text painter to get the bounds and shape. - * Also used as a fallback to draw the string with strokes. - */ - protected static final TextPainter - PROXY_PAINTER = StrokingTextPainter.getInstance(); + private static final AffineTransform IDENTITY_TRANSFORM = new AffineTransform(); /** * Create a new PS text painter with the given font information. - * @param nativeTextHandler the NativeTextHandler instance used for text painting + * @param fontInfo the font collection */ - public PSTextPainter(NativeTextHandler nativeTextHandler) { - this.nativeTextHandler = nativeTextHandler; - this.fontInfo = nativeTextHandler.getFontInfo(); + public PSTextPainter(FontInfo fontInfo) { + super(fontInfo); + this.fontResources = new FontResourceCache(fontInfo); } - /** - * Paints the specified attributed character iterator using the - * specified Graphics2D and context and font context. - * @param node the TextNode to paint - * @param g2d the Graphics2D to use - */ - public void paint(TextNode node, Graphics2D g2d) { - String txt = node.getText(); - Point2D loc = node.getLocation(); - - if (hasUnsupportedAttributes(node)) { - PROXY_PAINTER.paint(node, g2d); - } else { - paintTextRuns(node.getTextRuns(), g2d, loc); - } + /** {@inheritDoc} */ + protected boolean isSupported(Graphics2D g2d) { + return g2d instanceof PSGraphics2D; } + /** {@inheritDoc} */ + protected void paintTextRun(TextRun textRun, Graphics2D g2d) throws IOException { + AttributedCharacterIterator runaci = textRun.getACI(); + runaci.first(); - private boolean hasUnsupportedAttributes(TextNode node) { - Iterator i = node.getTextRuns().iterator(); - while (i.hasNext()) { - StrokingTextPainter.TextRun - run = (StrokingTextPainter.TextRun)i.next(); - AttributedCharacterIterator aci = run.getACI(); - boolean hasUnsupported = hasUnsupportedAttributes(aci); - if (hasUnsupported) { - return true; - } + TextPaintInfo tpi = (TextPaintInfo)runaci.getAttribute(PAINT_INFO); + if (tpi == null || !tpi.visible) { + return; } - return false; - } - - private boolean hasUnsupportedAttributes(AttributedCharacterIterator aci) { - boolean hasunsupported = false; - - String text = getText(aci); - Font font = makeFont(aci); - if (hasUnsupportedGlyphs(text, font)) { - log.trace("-> Unsupported glyphs found"); - hasunsupported = true; - } - - TextPaintInfo tpi = (TextPaintInfo) aci.getAttribute( - GVTAttributedCharacterIterator.TextAttribute.PAINT_INFO); - if ((tpi != null) - && ((tpi.strokeStroke != null && tpi.strokePaint != null) - || (tpi.strikethroughStroke != null) - || (tpi.underlineStroke != null) - || (tpi.overlineStroke != null))) { - log.trace("-> under/overlines etc. found"); - hasunsupported = true; - } - - //Alpha is not supported - Paint foreground = (Paint) aci.getAttribute(TextAttribute.FOREGROUND); - if (foreground instanceof Color) { - Color col = (Color)foreground; - if (col.getAlpha() != 255) { - log.trace("-> transparency found"); - hasunsupported = true; - } + if ((tpi != null) && (tpi.composite != null)) { + g2d.setComposite(tpi.composite); } - Object letSpace = aci.getAttribute( - GVTAttributedCharacterIterator.TextAttribute.LETTER_SPACING); - if (letSpace != null) { - log.trace("-> letter spacing found"); - hasunsupported = true; - } + //------------------------------------ + TextSpanLayout layout = textRun.getLayout(); + logTextRun(runaci, layout); + CharSequence chars = collectCharacters(runaci); + runaci.first(); //Reset ACI - Object wordSpace = aci.getAttribute( - GVTAttributedCharacterIterator.TextAttribute.WORD_SPACING); - if (wordSpace != null) { - log.trace("-> word spacing found"); - hasunsupported = true; - } + final PSGraphics2D ps = (PSGraphics2D)g2d; + final PSGenerator gen = ps.getPSGenerator(); + ps.preparePainting(); - Object lengthAdjust = aci.getAttribute( - GVTAttributedCharacterIterator.TextAttribute.LENGTH_ADJUST); - if (lengthAdjust != null) { - log.trace("-> length adjustments found"); - hasunsupported = true; + if (DEBUG) { + log.debug("Text: " + chars); + gen.commentln("%Text: " + chars); } - Object writeMod = aci.getAttribute( - GVTAttributedCharacterIterator.TextAttribute.WRITING_MODE); - if (writeMod != null - && !GVTAttributedCharacterIterator.TextAttribute.WRITING_MODE_LTR.equals( - writeMod)) { - log.trace("-> Unsupported writing modes found"); - hasunsupported = true; + GeneralPath debugShapes = null; + if (DEBUG) { + debugShapes = new GeneralPath(); } - Object vertOr = aci.getAttribute( - GVTAttributedCharacterIterator.TextAttribute.VERTICAL_ORIENTATION); - if (GVTAttributedCharacterIterator.TextAttribute.ORIENTATION_ANGLE.equals( - vertOr)) { - log.trace("-> vertical orientation found"); - hasunsupported = true; + TextUtil textUtil = new TextUtil(gen); + textUtil.setupFonts(runaci); + if (!textUtil.hasFonts()) { + //Draw using Java2D when no native fonts are available + textRun.getLayout().draw(g2d); + return; } - Object rcDel = aci.getAttribute( - GVTAttributedCharacterIterator.TextAttribute.TEXT_COMPOUND_DELIMITER); - //Batik 1.6 returns null here which makes it impossible to determine whether this can - //be painted or not, i.e. fall back to stroking. :-( - if (/*rcDel != null &&*/ !(rcDel instanceof SVGOMTextElement)) { - log.trace("-> spans found"); - hasunsupported = true; //Filter spans - } + gen.saveGraphicsState(); + gen.concatMatrix(g2d.getTransform()); + Shape imclip = g2d.getClip(); + clip(ps, imclip); + + gen.writeln("BT"); //beginTextObject() + + AffineTransform localTransform = new AffineTransform(); + Point2D prevPos = null; + GVTGlyphVector gv = layout.getGlyphVector(); + PSTextRun psRun = new PSTextRun(); //Used to split a text run into smaller runs + for (int index = 0, c = gv.getNumGlyphs(); index < c; index++) { + char ch = chars.charAt(index); + boolean visibleChar = gv.isGlyphVisible(index) + || (CharUtilities.isAnySpace(ch) && !CharUtilities.isZeroWidthSpace(ch)); + logCharacter(ch, layout, index, visibleChar); + if (!visibleChar) { + continue; + } + Point2D glyphPos = gv.getGlyphPosition(index); + + AffineTransform glyphTransform = gv.getGlyphTransform(index); + if (log.isTraceEnabled()) { + log.trace("pos " + glyphPos + ", transform " + glyphTransform); + } + if (DEBUG) { + Shape sh = gv.getGlyphLogicalBounds(index); + if (sh == null) { + sh = new Ellipse2D.Double(glyphPos.getX(), glyphPos.getY(), 2, 2); + } + debugShapes.append(sh, false); + } + + //Exact position of the glyph + localTransform.setToIdentity(); + localTransform.translate(glyphPos.getX(), glyphPos.getY()); + if (glyphTransform != null) { + localTransform.concatenate(glyphTransform); + } + localTransform.scale(1, -1); + + boolean flushCurrentRun = false; + //Try to optimize by combining characters using the same font and on the same line. + if (glyphTransform != null) { + //Happens for text-on-a-path + flushCurrentRun = true; + } + if (psRun.getRunLength() >= 128) { + //Don't let a run get too long + flushCurrentRun = true; + } + + //Note the position of the glyph relative to the previous one + Point2D relPos; + if (prevPos == null) { + relPos = new Point2D.Double(0, 0); + } else { + relPos = new Point2D.Double( + glyphPos.getX() - prevPos.getX(), + glyphPos.getY() - prevPos.getY()); + } + if (psRun.vertChanges == 0 + && psRun.getHorizRunLength() > 2 + && relPos.getY() != 0) { + //new line + flushCurrentRun = true; + } - if (hasunsupported) { - log.trace("Unsupported attributes found in ACI, using StrokingTextPainter"); + //Select the actual character to paint + char paintChar = (CharUtilities.isAnySpace(ch) ? ' ' : ch); + + //Select (sub)font for character + Font f = textUtil.selectFontForChar(paintChar); + char mapped = f.mapChar(ch); + boolean fontChanging = textUtil.isFontChanging(f, mapped); + if (fontChanging) { + flushCurrentRun = true; + } + + if (flushCurrentRun) { + //Paint the current run and reset for the next run + psRun.paint(ps, textUtil, tpi); + psRun.reset(); + } + + //Track current run + psRun.addCharacter(paintChar, relPos); + psRun.noteStartingTransformation(localTransform); + + //Change font if necessary + if (fontChanging) { + textUtil.setCurrentFont(f, mapped); + } + + //Update last position + prevPos = glyphPos; + } + psRun.paint(ps, textUtil, tpi); + gen.writeln("ET"); //endTextObject() + gen.restoreGraphicsState(); + + if (DEBUG) { + //Paint debug shapes + g2d.setStroke(new BasicStroke(0)); + g2d.setColor(Color.LIGHT_GRAY); + g2d.draw(debugShapes); } - return hasunsupported; } - /** - * Paint a list of text runs on the Graphics2D at a given location. - * @param textRuns the list of text runs - * @param g2d the Graphics2D to paint to - * @param loc the current location of the "cursor" - */ - protected void paintTextRuns(List textRuns, Graphics2D g2d, Point2D loc) { - Point2D currentloc = loc; - Iterator i = textRuns.iterator(); - while (i.hasNext()) { - StrokingTextPainter.TextRun - run = (StrokingTextPainter.TextRun)i.next(); - currentloc = paintTextRun(run, g2d, currentloc); + private void applyColor(Paint paint, final PSGenerator gen) throws IOException { + if (paint == null) { + return; + } else if (paint instanceof Color) { + Color col = (Color)paint; + gen.useColor(col); + } else { + log.warn("Paint not supported: " + paint.toString()); } } - /** - * Paint a single text run on the Graphics2D at a given location. - * @param run the text run to paint - * @param g2d the Graphics2D to paint to - * @param loc the current location of the "cursor" - * @return the new location of the "cursor" after painting the text run - */ - protected Point2D paintTextRun(StrokingTextPainter.TextRun run, Graphics2D g2d, Point2D loc) { - AttributedCharacterIterator aci = run.getACI(); - return paintACI(aci, g2d, loc); + private PSResource getResourceForFont(Font f, String postfix) { + String key = (postfix != null ? f.getFontName() + '_' + postfix : f.getFontName()); + return this.fontResources.getPSResourceForFontKey(key); } - /** - * Extract the raw text from an ACI. - * @param aci ACI to inspect - * @return the extracted text - */ - protected String getText(AttributedCharacterIterator aci) { - StringBuffer sb = new StringBuffer(aci.getEndIndex() - aci.getBeginIndex()); - for (char c = aci.first(); c != CharacterIterator.DONE; c = aci.next()) { - sb.append(c); + private void clip(PSGraphics2D ps, Shape shape) throws IOException { + if (shape == null) { + return; } - return sb.toString(); + ps.getPSGenerator().writeln("newpath"); + PathIterator iter = shape.getPathIterator(IDENTITY_TRANSFORM); + ps.processPathIterator(iter); + ps.getPSGenerator().writeln("clip"); } - /** - * Paint an ACI on a Graphics2D at a given location. The method has to - * update the location after painting. - * @param aci ACI to paint - * @param g2d Graphics2D to paint on - * @param loc start location - * @return new current location - */ - protected Point2D paintACI(AttributedCharacterIterator aci, Graphics2D g2d, Point2D loc) { - //ACIUtils.dumpAttrs(aci); + private class TextUtil { - aci.first(); + private PSGenerator gen; + private Font[] fonts; + private Font currentFont; + private int currentEncoding = -1; - updateLocationFromACI(aci, loc); - - TextPaintInfo tpi = (TextPaintInfo) aci.getAttribute( - GVTAttributedCharacterIterator.TextAttribute.PAINT_INFO); - - if (tpi == null) { - return loc; + public TextUtil(PSGenerator gen) { + this.gen = gen; } - TextNode.Anchor anchor = (TextNode.Anchor)aci.getAttribute( - GVTAttributedCharacterIterator.TextAttribute.ANCHOR_TYPE); + public Font selectFontForChar(char ch) { + for (int i = 0, c = fonts.length; i < c; i++) { + if (fonts[i].hasChar(ch)) { + return fonts[i]; + } + } + return fonts[0]; //TODO Maybe fall back to painting with shapes + } - //Set up font - List gvtFonts = (List)aci.getAttribute( - GVTAttributedCharacterIterator.TextAttribute.GVT_FONT_FAMILIES); - Paint foreground = tpi.fillPaint; - Paint strokePaint = tpi.strokePaint; - Stroke stroke = tpi.strokeStroke; + public void writeTextMatrix(AffineTransform transform) throws IOException { + double[] matrix = new double[6]; + transform.getMatrix(matrix); + gen.writeln(gen.formatDouble5(matrix[0]) + " " + + gen.formatDouble5(matrix[1]) + " " + + gen.formatDouble5(matrix[2]) + " " + + gen.formatDouble5(matrix[3]) + " " + + gen.formatDouble5(matrix[4]) + " " + + gen.formatDouble5(matrix[5]) + " Tm"); + } - Float fontSize = (Float)aci.getAttribute(TextAttribute.SIZE); - if (fontSize == null) { - return loc; + public boolean isFontChanging(Font f, char mapped) { + if (f != getCurrentFont()) { + int encoding = mapped / 256; + if (encoding != getCurrentFontEncoding()) { + return true; //Font is changing + } + } + return false; //Font is the same } - Float posture = (Float)aci.getAttribute(TextAttribute.POSTURE); - Float taWeight = (Float)aci.getAttribute(TextAttribute.WEIGHT); - if (foreground instanceof Color) { - Color col = (Color)foreground; - g2d.setColor(col); + public void selectFont(Font f, char mapped) throws IOException { + int encoding = mapped / 256; + String postfix = (encoding == 0 ? null : Integer.toString(encoding)); + PSResource res = getResourceForFont(f, postfix); + gen.useFont("/" + res.getName(), f.getFontSize() / 1000f); + gen.getResourceTracker().notifyResourceUsageOnPage(res); } - g2d.setPaint(foreground); - g2d.setStroke(stroke); - Font font = makeFont(aci); - java.awt.Font awtFont = makeAWTFont(aci, font); + public Font getCurrentFont() { + return this.currentFont; + } - g2d.setFont(awtFont); + public int getCurrentFontEncoding() { + return this.currentEncoding; + } - String txt = getText(aci); - float advance = getStringWidth(txt, font); - float tx = 0; - if (anchor != null) { - switch (anchor.getType()) { - case TextNode.Anchor.ANCHOR_MIDDLE: - tx = -advance / 2; - break; - case TextNode.Anchor.ANCHOR_END: - tx = -advance; - break; - default: //nop - } + public void setCurrentFont(Font font, int encoding) { + this.currentFont = font; + this.currentEncoding = encoding; } - drawPrimitiveString(g2d, loc, font, txt, tx); - loc.setLocation(loc.getX() + advance, loc.getY()); - return loc; - } + public void setCurrentFont(Font font, char mapped) { + int encoding = mapped / 256; + setCurrentFont(font, encoding); + } - protected void drawPrimitiveString(Graphics2D g2d, Point2D loc, Font font, String txt, float tx) { - //Finally draw text - nativeTextHandler.setOverrideFont(font); - try { - try { - nativeTextHandler.drawString(txt, (float)(loc.getX() + tx), (float)(loc.getY())); - } catch (IOException ioe) { - if (g2d instanceof PSGraphics2D) { - ((PSGraphics2D)g2d).handleIOException(ioe); - } - } - } finally { - nativeTextHandler.setOverrideFont(null); + public void setupFonts(AttributedCharacterIterator runaci) { + this.fonts = findFonts(runaci); } - } - private void updateLocationFromACI( - AttributedCharacterIterator aci, - Point2D loc) { - //Adjust position of span - Float xpos = (Float)aci.getAttribute( - GVTAttributedCharacterIterator.TextAttribute.X); - Float ypos = (Float)aci.getAttribute( - GVTAttributedCharacterIterator.TextAttribute.Y); - Float dxpos = (Float)aci.getAttribute( - GVTAttributedCharacterIterator.TextAttribute.DX); - Float dypos = (Float)aci.getAttribute( - GVTAttributedCharacterIterator.TextAttribute.DY); - if (xpos != null) { - loc.setLocation(xpos.doubleValue(), loc.getY()); - } - if (ypos != null) { - loc.setLocation(loc.getX(), ypos.doubleValue()); - } - if (dxpos != null) { - loc.setLocation(loc.getX() + dxpos.doubleValue(), loc.getY()); - } - if (dypos != null) { - loc.setLocation(loc.getX(), loc.getY() + dypos.doubleValue()); + public boolean hasFonts() { + return (fonts != null) && (fonts.length > 0); } - } - private String getStyle(AttributedCharacterIterator aci) { - Float posture = (Float)aci.getAttribute(TextAttribute.POSTURE); - return ((posture != null) && (posture.floatValue() > 0.0)) - ? "italic" - : "normal"; } - private int getWeight(AttributedCharacterIterator aci) { - Float taWeight = (Float)aci.getAttribute(TextAttribute.WEIGHT); - return ((taWeight != null) && (taWeight.floatValue() > 1.0)) - ? Font.WEIGHT_BOLD - : Font.WEIGHT_NORMAL; - } + private class PSTextRun { - private Font makeFont(AttributedCharacterIterator aci) { - Float fontSize = (Float)aci.getAttribute(TextAttribute.SIZE); - if (fontSize == null) { - fontSize = new Float(10.0f); - } - String style = getStyle(aci); - int weight = getWeight(aci); - - String fontFamily = null; - List gvtFonts = (List) aci.getAttribute( - GVTAttributedCharacterIterator.TextAttribute.GVT_FONT_FAMILIES); - if (gvtFonts != null) { - Iterator i = gvtFonts.iterator(); - while (i.hasNext()) { - GVTFontFamily fam = (GVTFontFamily) i.next(); - /* (todo) Enable SVG Font painting - if (fam instanceof SVGFontFamily) { - PROXY_PAINTER.paint(node, g2d); - return; - }*/ - fontFamily = fam.getFamilyName(); - if (fontInfo.hasFont(fontFamily, style, weight)) { - FontTriplet triplet = fontInfo.fontLookup( - fontFamily, style, weight); - int fsize = (int)(fontSize.floatValue() * 1000); - return fontInfo.getFontInstance(triplet, fsize); - } - } + private AffineTransform textTransform; + private List relativePositions = new java.util.LinkedList(); + private StringBuffer currentChars = new StringBuffer(); + private int horizChanges = 0; + private int vertChanges = 0; + + public void reset() { + textTransform = null; + currentChars.setLength(0); + horizChanges = 0; + vertChanges = 0; + relativePositions.clear(); } - FontTriplet triplet = fontInfo.fontLookup("any", style, Font.WEIGHT_NORMAL); - int fsize = (int)(fontSize.floatValue() * 1000); - return fontInfo.getFontInstance(triplet, fsize); - } - private java.awt.Font makeAWTFont(AttributedCharacterIterator aci, Font font) { - final String style = getStyle(aci); - final int weight = getWeight(aci); - int fStyle = java.awt.Font.PLAIN; - if (weight == Font.WEIGHT_BOLD) { - fStyle |= java.awt.Font.BOLD; + public int getHorizRunLength() { + if (this.vertChanges == 0 + && getRunLength() > 0) { + return getRunLength(); + } + return 0; } - if ("italic".equals(style)) { - fStyle |= java.awt.Font.ITALIC; + + public void addCharacter(char paintChar, Point2D relPos) { + addRelativePosition(relPos); + currentChars.append(paintChar); } - return new java.awt.Font(font.getFontName(), fStyle, - (font.getFontSize() / 1000)); - } - private float getStringWidth(String str, Font font) { - float wordWidth = 0; - float whitespaceWidth = font.getWidth(font.mapChar(' ')); - - for (int i = 0; i < str.length(); i++) { - float charWidth; - char c = str.charAt(i); - if (!((c == ' ') || (c == '\n') || (c == '\r') || (c == '\t'))) { - charWidth = font.getWidth(font.mapChar(c)); - if (charWidth <= 0) { - charWidth = whitespaceWidth; + private void addRelativePosition(Point2D relPos) { + if (getRunLength() > 0) { + if (relPos.getX() != 0) { + horizChanges++; + } + if (relPos.getY() != 0) { + vertChanges++; } - } else { - charWidth = whitespaceWidth; } - wordWidth += charWidth; + relativePositions.add(relPos); } - return wordWidth / 1000f; - } - private boolean hasUnsupportedGlyphs(String str, Font font) { - for (int i = 0; i < str.length(); i++) { - float charWidth; - char c = str.charAt(i); - if (!((c == ' ') || (c == '\n') || (c == '\r') || (c == '\t'))) { - if (!font.hasChar(c)) { - return true; - } + public void noteStartingTransformation(AffineTransform transform) { + if (textTransform == null) { + this.textTransform = new AffineTransform(transform); } } - return false; - } - /** - * Get the outline shape of the text characters. - * This uses the StrokingTextPainter to get the outline - * shape since in theory it should be the same. - * - * @param node the text node - * @return the outline shape of the text characters - */ - public Shape getOutline(TextNode node) { - return PROXY_PAINTER.getOutline(node); - } - - /** - * Get the bounds. - * This uses the StrokingTextPainter to get the bounds - * since in theory it should be the same. - * - * @param node the text node - * @return the bounds of the text - */ - public Rectangle2D getBounds2D(TextNode node) { - /* (todo) getBounds2D() is too slow - * because it uses the StrokingTextPainter. We should implement this - * method ourselves. */ - return PROXY_PAINTER.getBounds2D(node); - } - - /** - * Get the geometry bounds. - * This uses the StrokingTextPainter to get the bounds - * since in theory it should be the same. - * @param node the text node - * @return the bounds of the text - */ - public Rectangle2D getGeometryBounds(TextNode node) { - return PROXY_PAINTER.getGeometryBounds(node); - } - - // Methods that have no purpose for PS + public int getRunLength() { + return currentChars.length(); + } - /** - * Get the mark. - * This does nothing since the output is pdf and not interactive. - * @param node the text node - * @param pos the position - * @param all select all - * @return null - */ - public Mark getMark(TextNode node, int pos, boolean all) { - return null; - } + private boolean isXShow() { + return vertChanges == 0; + } - /** - * Select at. - * This does nothing since the output is pdf and not interactive. - * @param x the x position - * @param y the y position - * @param node the text node - * @return null - */ - public Mark selectAt(double x, double y, TextNode node) { - return null; - } + private boolean isYShow() { + return horizChanges == 0; + } - /** - * Select to. - * This does nothing since the output is pdf and not interactive. - * @param x the x position - * @param y the y position - * @param beginMark the start mark - * @return null - */ - public Mark selectTo(double x, double y, Mark beginMark) { - return null; - } + public void paint(PSGraphics2D g2d, TextUtil textUtil, TextPaintInfo tpi) + throws IOException { + if (getRunLength() > 0) { + if (log.isDebugEnabled()) { + log.debug("Text run: " + currentChars); + } + textUtil.writeTextMatrix(this.textTransform); + if (isXShow()) { + log.debug("Horizontal text: xshow"); + paintXYShow(g2d, textUtil, tpi.fillPaint, true, false); + } else if (isYShow()) { + log.debug("Vertical text: yshow"); + paintXYShow(g2d, textUtil, tpi.fillPaint, false, true); + } else { + log.debug("Arbitrary text: xyshow"); + paintXYShow(g2d, textUtil, tpi.fillPaint, true, true); + } + boolean stroke = (tpi.strokePaint != null) && (tpi.strokeStroke != null); + if (stroke) { + log.debug("Stroked glyph outlines"); + paintStrokedGlyphs(g2d, textUtil, tpi.strokePaint, tpi.strokeStroke); + } + } + } - /** - * Selec first. - * This does nothing since the output is pdf and not interactive. - * @param node the text node - * @return null - */ - public Mark selectFirst(TextNode node) { - return null; - } + private void paintXYShow(PSGraphics2D g2d, TextUtil textUtil, Paint paint, + boolean x, boolean y) throws IOException { + PSGenerator gen = textUtil.gen; + char firstChar = this.currentChars.charAt(0); + //Font only has to be setup up before the first character + Font f = textUtil.selectFontForChar(firstChar); + char mapped = f.mapChar(firstChar); + textUtil.selectFont(f, mapped); + textUtil.setCurrentFont(f, mapped); + applyColor(paint, gen); + + StringBuffer sb = new StringBuffer(); + sb.append('('); + for (int i = 0, c = this.currentChars.length(); i < c; i++) { + char ch = this.currentChars.charAt(i); + mapped = f.mapChar(ch); + PSGenerator.escapeChar(mapped, sb); + } + sb.append(')'); + if (x || y) { + sb.append("\n["); + int idx = 0; + Iterator iter = this.relativePositions.iterator(); + while (iter.hasNext()) { + Point2D pt = (Point2D)iter.next(); + if (idx > 0) { + if (x) { + sb.append(format(gen, pt.getX())); + } + if (y) { + if (x) { + sb.append(' '); + } + sb.append(format(gen, -pt.getY())); + } + if (idx % 8 == 0) { + sb.append('\n'); + } else { + sb.append(' '); + } + } + idx++; + } + if (x) { + sb.append('0'); + } + if (y) { + if (x) { + sb.append(' '); + } + sb.append('0'); + } + sb.append(']'); + } + sb.append(' '); + if (x) { + sb.append('x'); + } + if (y) { + sb.append('y'); + } + sb.append("show"); // --> xshow, yshow or xyshow + gen.writeln(sb.toString()); + } - /** - * Select last. - * This does nothing since the output is pdf and not interactive. - * @param node the text node - * @return null - */ - public Mark selectLast(TextNode node) { - return null; - } + private String format(PSGenerator gen, double coord) { + if (Math.abs(coord) < 0.00001) { + return "0"; + } else { + return gen.formatDouble5(coord); + } + } - /** - * Get selected. - * This does nothing since the output is pdf and not interactive. - * @param start the start mark - * @param finish the finish mark - * @return null - */ - public int[] getSelected(Mark start, Mark finish) { - return null; - } + private void paintStrokedGlyphs(PSGraphics2D g2d, TextUtil textUtil, + Paint strokePaint, Stroke stroke) throws IOException { + PSGenerator gen = textUtil.gen; + + applyColor(strokePaint, gen); + PSGraphics2D.applyStroke(stroke, gen); + + Font f = null; + Iterator iter = this.relativePositions.iterator(); + iter.next(); + Point2D pos = new Point2D.Double(0, 0); + gen.writeln("0 0 M"); + for (int i = 0, c = this.currentChars.length(); i < c; i++) { + char ch = this.currentChars.charAt(0); + if (i == 0) { + //Font only has to be setup up before the first character + f = textUtil.selectFontForChar(ch); + } + char mapped = f.mapChar(ch); + if (i == 0) { + textUtil.selectFont(f, mapped); + textUtil.setCurrentFont(f, mapped); + } + mapped = f.mapChar(this.currentChars.charAt(i)); + //add glyph outlines to current path + char codepoint = (char)(mapped % 256); + gen.write("(" + codepoint + ")"); + gen.writeln(" false charpath"); + + if (iter.hasNext()) { + //Position for the next character + Point2D pt = (Point2D)iter.next(); + pos.setLocation(pos.getX() + pt.getX(), pos.getY() - pt.getY()); + gen.writeln(gen.formatDouble5(pos.getX()) + " " + + gen.formatDouble5(pos.getY()) + " M"); + } + } + gen.writeln("stroke"); //paints all accumulated glyph outlines + } - /** - * Get the highlighted shape. - * This does nothing since the output is pdf and not interactive. - * @param beginMark the start mark - * @param endMark the end mark - * @return null - */ - public Shape getHighlightShape(Mark beginMark, Mark endMark) { - return null; } } |