aboutsummaryrefslogtreecommitdiffstats
path: root/src/java/org/apache/fop/render
diff options
context:
space:
mode:
Diffstat (limited to 'src/java/org/apache/fop/render')
-rw-r--r--src/java/org/apache/fop/render/ps/AbstractPSTranscoder.java46
-rw-r--r--src/java/org/apache/fop/render/ps/FontResourceCache.java93
-rw-r--r--src/java/org/apache/fop/render/ps/PSBatikFlowTextElementBridge.java86
-rw-r--r--src/java/org/apache/fop/render/ps/PSBridgeContext.java113
-rw-r--r--src/java/org/apache/fop/render/ps/PSDocumentHandler.java43
-rw-r--r--src/java/org/apache/fop/render/ps/PSImageHandlerSVG.java14
-rw-r--r--src/java/org/apache/fop/render/ps/PSPainter.java5
-rw-r--r--src/java/org/apache/fop/render/ps/PSSVGFlowRootElementBridge.java86
-rw-r--r--src/java/org/apache/fop/render/ps/PSSVGHandler.java15
-rw-r--r--src/java/org/apache/fop/render/ps/PSTextElementBridge.java63
-rw-r--r--src/java/org/apache/fop/render/ps/PSTextPainter.java869
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 &lt;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 &lt;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;
}
}