aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/xmlgraphics-commons-1.4svn.jarbin540172 -> 549055 bytes
-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
-rw-r--r--src/java/org/apache/fop/svg/AbstractFOPBridgeContext.java6
-rw-r--r--src/java/org/apache/fop/svg/AbstractFOPTextElementBridge.java48
-rw-r--r--src/java/org/apache/fop/svg/AbstractFOPTranscoder.java135
-rw-r--r--src/java/org/apache/fop/svg/NativeTextPainter.java224
-rw-r--r--src/java/org/apache/fop/svg/PDFBridgeContext.java11
-rw-r--r--src/java/org/apache/fop/svg/PDFDocumentGraphics2DConfigurator.java23
-rw-r--r--src/java/org/apache/fop/svg/PDFTextPainter.java363
-rw-r--r--src/java/org/apache/fop/svg/PDFTranscoder.java92
-rw-r--r--status.xml4
21 files changed, 1350 insertions, 989 deletions
diff --git a/lib/xmlgraphics-commons-1.4svn.jar b/lib/xmlgraphics-commons-1.4svn.jar
index 11a3d97b3..c04cb18af 100644
--- a/lib/xmlgraphics-commons-1.4svn.jar
+++ b/lib/xmlgraphics-commons-1.4svn.jar
Binary files differ
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;
}
}
diff --git a/src/java/org/apache/fop/svg/AbstractFOPBridgeContext.java b/src/java/org/apache/fop/svg/AbstractFOPBridgeContext.java
index ae4d67516..acb59ed7d 100644
--- a/src/java/org/apache/fop/svg/AbstractFOPBridgeContext.java
+++ b/src/java/org/apache/fop/svg/AbstractFOPBridgeContext.java
@@ -26,10 +26,12 @@ import org.apache.batik.bridge.Bridge;
import org.apache.batik.bridge.BridgeContext;
import org.apache.batik.bridge.DocumentLoader;
import org.apache.batik.bridge.UserAgent;
-import org.apache.fop.fonts.FontInfo;
+
import org.apache.xmlgraphics.image.loader.ImageManager;
import org.apache.xmlgraphics.image.loader.ImageSessionContext;
+import org.apache.fop.fonts.FontInfo;
+
/**
* A FOP base implementation of a Batik BridgeContext.
*/
@@ -49,8 +51,6 @@ public abstract class AbstractFOPBridgeContext extends BridgeContext {
* @param loader the Document Loader to use for referenced documents.
* @param fontInfo the font list for the text painter, may be null
* in which case text is painted as shapes
- * @param linkTransform AffineTransform to properly place links,
- * may be null
* @param imageManager an image manager
* @param imageSessionContext an image session context
* @param linkTransform AffineTransform to properly place links,
diff --git a/src/java/org/apache/fop/svg/AbstractFOPTextElementBridge.java b/src/java/org/apache/fop/svg/AbstractFOPTextElementBridge.java
index 53b8e2ad5..aec4126b4 100644
--- a/src/java/org/apache/fop/svg/AbstractFOPTextElementBridge.java
+++ b/src/java/org/apache/fop/svg/AbstractFOPTextElementBridge.java
@@ -19,13 +19,13 @@
package org.apache.fop.svg;
+import org.w3c.dom.Element;
+
import org.apache.batik.bridge.BridgeContext;
import org.apache.batik.bridge.SVGTextElementBridge;
import org.apache.batik.gvt.GraphicsNode;
import org.apache.batik.gvt.TextNode;
import org.apache.batik.gvt.TextPainter;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
/**
* Bridge class for the &lt;text> element.
@@ -65,49 +65,5 @@ public abstract class AbstractFOPTextElementBridge extends SVGTextElementBridge
return node;
}
- /**
- * Check if text element contains simple text.
- * This checks the children of the text element to determine
- * if the text is simple. The text is simple if it can be rendered
- * with basic text drawing algorithms. This means there are no
- * alternate characters, the font is known and there are no effects
- * applied to the text.
- *
- * @param ctx the bridge context
- * @param element the svg text element
- * @param node the graphics node
- * @return true if this text is simple of false if it cannot be
- * easily rendered using normal drawString on the Graphics2D
- */
- protected boolean isSimple(BridgeContext ctx, Element element, GraphicsNode node) {
- for (Node n = element.getFirstChild();
- n != null;
- n = n.getNextSibling()) {
-
- switch (n.getNodeType()) {
- case Node.ELEMENT_NODE:
-
- if (n.getLocalName().equals(SVG_TSPAN_TAG)
- || n.getLocalName().equals(SVG_ALT_GLYPH_TAG)) {
- return false;
- } else if (n.getLocalName().equals(SVG_TEXT_PATH_TAG)) {
- return false;
- } else if (n.getLocalName().equals(SVG_TREF_TAG)) {
- return false;
- }
- break;
- case Node.TEXT_NODE:
- case Node.CDATA_SECTION_NODE:
- default:
- }
- }
-
- /*if (CSSUtilities.convertFilter(element, node, ctx) != null) {
- return false;
- }*/
-
- return true;
- }
-
}
diff --git a/src/java/org/apache/fop/svg/AbstractFOPTranscoder.java b/src/java/org/apache/fop/svg/AbstractFOPTranscoder.java
index 83cfa8021..caae32cf0 100644
--- a/src/java/org/apache/fop/svg/AbstractFOPTranscoder.java
+++ b/src/java/org/apache/fop/svg/AbstractFOPTranscoder.java
@@ -19,6 +19,19 @@
package org.apache.fop.svg;
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.xml.transform.Source;
+import javax.xml.transform.stream.StreamSource;
+
+import org.w3c.dom.DOMImplementation;
+
+import org.xml.sax.EntityResolver;
+
+import org.apache.avalon.framework.configuration.Configuration;
+import org.apache.avalon.framework.configuration.ConfigurationException;
+import org.apache.avalon.framework.configuration.DefaultConfiguration;
import org.apache.batik.bridge.UserAgent;
import org.apache.batik.dom.svg.SVGDOMImplementation;
import org.apache.batik.dom.util.DocumentFactory;
@@ -28,11 +41,16 @@ import org.apache.batik.transcoder.TranscoderException;
import org.apache.batik.transcoder.TranscodingHints;
import org.apache.batik.transcoder.image.ImageTranscoder;
import org.apache.batik.transcoder.keys.BooleanKey;
+import org.apache.batik.transcoder.keys.FloatKey;
+import org.apache.batik.util.ParsedURL;
import org.apache.batik.util.SVGConstants;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.impl.SimpleLog;
-import org.w3c.dom.DOMImplementation;
-import org.xml.sax.EntityResolver;
+
+import org.apache.xmlgraphics.image.loader.ImageContext;
+import org.apache.xmlgraphics.image.loader.ImageManager;
+import org.apache.xmlgraphics.image.loader.ImageSessionContext;
+import org.apache.xmlgraphics.image.loader.impl.AbstractImageSessionContext;
/**
* This is the common base class of all of FOP's transcoders.
@@ -40,11 +58,24 @@ import org.xml.sax.EntityResolver;
public abstract class AbstractFOPTranscoder extends SVGAbstractTranscoder {
/**
+ * The key is used to specify the resolution for on-the-fly images generated
+ * due to complex effects like gradients and filters.
+ */
+ public static final TranscodingHints.Key KEY_DEVICE_RESOLUTION = new FloatKey();
+
+ /**
* The key to specify whether to stroke text instead of using text
* operations.
*/
public static final TranscodingHints.Key KEY_STROKE_TEXT = new BooleanKey();
+ /**
+ * The key is used to specify whether the available fonts should be automatically
+ * detected. The alternative is to configure the transcoder manually using a configuration
+ * file.
+ */
+ public static final TranscodingHints.Key KEY_AUTO_FONTS = new BooleanKey();
+
/** The value to turn on text stroking. */
public static final Boolean VALUE_FORMAT_ON = Boolean.TRUE;
@@ -58,6 +89,9 @@ public abstract class AbstractFOPTranscoder extends SVGAbstractTranscoder {
private Log logger;
private EntityResolver resolver;
+ private Configuration cfg = null;
+ private ImageManager imageManager;
+ private ImageSessionContext imageSessionContext;
/**
* Constructs a new FOP-style transcoder.
@@ -80,7 +114,8 @@ public abstract class AbstractFOPTranscoder extends SVGAbstractTranscoder {
}
/**
- * @param logger
+ * Sets the logger.
+ * @param logger the logger
*/
public void setLogger(Log logger) {
this.logger = logger;
@@ -94,6 +129,35 @@ public abstract class AbstractFOPTranscoder extends SVGAbstractTranscoder {
this.resolver = resolver;
}
+ /** {@inheritDoc} */
+ public void configure(Configuration cfg) throws ConfigurationException {
+ this.cfg = cfg;
+ }
+
+ /**
+ * Returns the effective configuration for the transcoder.
+ * @return the effective configuration
+ */
+ protected Configuration getEffectiveConfiguration() {
+ Configuration effCfg = this.cfg;
+ if (effCfg == null) {
+ //By default, enable font auto-detection if no cfg is given
+ boolean autoFonts = true;
+ if (hints.containsKey(KEY_AUTO_FONTS)) {
+ autoFonts = ((Boolean)hints.get(KEY_AUTO_FONTS)).booleanValue();
+ }
+ if (autoFonts) {
+ DefaultConfiguration c = new DefaultConfiguration("cfg");
+ DefaultConfiguration fonts = new DefaultConfiguration("fonts");
+ c.addChild(fonts);
+ DefaultConfiguration autodetect = new DefaultConfiguration("auto-detect");
+ fonts.addChild(autodetect);
+ effCfg = c;
+ }
+ }
+ return effCfg;
+ }
+
/**
* Returns the logger associated with this transcoder. It returns a
* SimpleLog if no logger has been explicitly set.
@@ -142,6 +206,71 @@ public abstract class AbstractFOPTranscoder extends SVGAbstractTranscoder {
return stroke;
}
+ /**
+ * Returns the device resolution that has been set up.
+ * @return the device resolution (in dpi)
+ */
+ protected float getDeviceResolution() {
+ if (hints.containsKey(KEY_DEVICE_RESOLUTION)) {
+ return ((Float)hints.get(KEY_DEVICE_RESOLUTION)).floatValue();
+ } else {
+ return 72;
+ }
+ }
+
+ /**
+ * Returns the ImageManager to be used by the transcoder.
+ * @return the image manager
+ */
+ protected ImageManager getImageManager() {
+ return this.imageManager;
+ }
+
+ /**
+ * Returns the ImageSessionContext to be used by the transcoder.
+ * @return the image session context
+ */
+ protected ImageSessionContext getImageSessionContext() {
+ return this.imageSessionContext;
+ }
+
+ /**
+ * Sets up the image infrastructure (the image loading framework).
+ * @param baseURI the base URI of the current document
+ */
+ protected void setupImageInfrastructure(final String baseURI) {
+ final ImageContext imageContext = new ImageContext() {
+ public float getSourceResolution() {
+ return 25.4f / userAgent.getPixelUnitToMillimeter();
+ }
+ };
+ this.imageManager = new ImageManager(imageContext);
+ this.imageSessionContext = new AbstractImageSessionContext() {
+
+ public ImageContext getParentContext() {
+ return imageContext;
+ }
+
+ public float getTargetResolution() {
+ return getDeviceResolution();
+ }
+
+ public Source resolveURI(String uri) {
+ System.out.println("resolve " + uri);
+ try {
+ ParsedURL url = new ParsedURL(baseURI, uri);
+ InputStream in = url.openStream();
+ StreamSource source = new StreamSource(in, url.toString());
+ return source;
+ } catch (IOException ioe) {
+ userAgent.displayError(ioe);
+ return null;
+ }
+ }
+
+ };
+ }
+
// --------------------------------------------------------------------
// FOP's default error handler (for transcoders)
// --------------------------------------------------------------------
diff --git a/src/java/org/apache/fop/svg/NativeTextPainter.java b/src/java/org/apache/fop/svg/NativeTextPainter.java
new file mode 100644
index 000000000..7da7269c2
--- /dev/null
+++ b/src/java/org/apache/fop/svg/NativeTextPainter.java
@@ -0,0 +1,224 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.svg;
+
+import java.awt.Graphics2D;
+import java.awt.font.TextAttribute;
+import java.io.IOException;
+import java.text.AttributedCharacterIterator;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.batik.bridge.SVGFontFamily;
+import org.apache.batik.gvt.font.GVTFont;
+import org.apache.batik.gvt.font.GVTFontFamily;
+import org.apache.batik.gvt.renderer.StrokingTextPainter;
+import org.apache.batik.gvt.text.GVTAttributedCharacterIterator;
+import org.apache.batik.gvt.text.TextSpanLayout;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.fop.fonts.Font;
+import org.apache.fop.fonts.FontInfo;
+import org.apache.fop.fonts.FontTriplet;
+import org.apache.fop.util.CharUtilities;
+
+/**
+ * Abstract base class for text painters that use specialized text commands native to an output
+ * format to render text.
+ */
+public abstract class NativeTextPainter extends StrokingTextPainter {
+
+ /** the logger for this class */
+ protected Log log = LogFactory.getLog(NativeTextPainter.class);
+
+ /** the font collection */
+ protected final FontInfo fontInfo;
+
+ /**
+ * Creates a new instance.
+ * @param fontInfo the font collection
+ */
+ public NativeTextPainter(FontInfo fontInfo) {
+ this.fontInfo = fontInfo;
+ }
+
+ /**
+ * Indicates whether the given {@link Graphics2D} instance if compatible with this text painter
+ * implementation.
+ * @param g2d the instance to check
+ * @return true if the instance is compatible.
+ */
+ protected abstract boolean isSupported(Graphics2D g2d);
+
+ /**
+ * Paints a single text run.
+ * @param textRun the text run
+ * @param g2d the target Graphics2D instance
+ * @throws IOException if an I/O error occurs while rendering the text
+ */
+ protected abstract void paintTextRun(TextRun textRun, Graphics2D g2d) throws IOException;
+
+ /** {@inheritDoc} */
+ protected void paintTextRuns(List textRuns, Graphics2D g2d) {
+ if (log.isTraceEnabled()) {
+ log.trace("paintTextRuns: count = " + textRuns.size());
+ }
+ if (!isSupported(g2d)) {
+ super.paintTextRuns(textRuns, g2d);
+ return;
+ }
+ for (int i = 0; i < textRuns.size(); i++) {
+ TextRun textRun = (TextRun)textRuns.get(i);
+ try {
+ paintTextRun(textRun, g2d);
+ } catch (IOException ioe) {
+ //No other possibility than to use a RuntimeException
+ throw new RuntimeException(ioe);
+ }
+ }
+ }
+
+ /**
+ * Finds an array of suitable fonts for a given AttributedCharacterIterator.
+ * @param aci the character iterator
+ * @return the array of fonts
+ */
+ protected Font[] findFonts(AttributedCharacterIterator aci) {
+ List fonts = new java.util.ArrayList();
+ List gvtFonts = (List) aci.getAttribute(
+ GVTAttributedCharacterIterator.TextAttribute.GVT_FONT_FAMILIES);
+ Float posture = (Float) aci.getAttribute(TextAttribute.POSTURE);
+ Float taWeight = (Float) aci.getAttribute(TextAttribute.WEIGHT);
+ Float fontSize = (Float) aci.getAttribute(TextAttribute.SIZE);
+
+ String style = ((posture != null) && (posture.floatValue() > 0.0))
+ ? Font.STYLE_ITALIC : Font.STYLE_NORMAL;
+ int weight = ((taWeight != null)
+ && (taWeight.floatValue() > 1.0)) ? Font.WEIGHT_BOLD
+ : Font.WEIGHT_NORMAL;
+
+ String firstFontFamily = null;
+
+ //GVT_FONT can sometimes be different from the fonts in GVT_FONT_FAMILIES
+ //or GVT_FONT_FAMILIES can even be empty and only GVT_FONT is set
+ /* The following code section is not available until Batik 1.7 is released. */
+ GVTFont gvtFont = (GVTFont)aci.getAttribute(
+ GVTAttributedCharacterIterator.TextAttribute.GVT_FONT);
+ if (gvtFont != null) {
+ try {
+ String gvtFontFamily = gvtFont.getFamilyName(); //Not available in Batik 1.6!
+ if (log.isDebugEnabled()) {
+ log.debug("Matching font family: " + gvtFontFamily);
+ }
+ if (fontInfo.hasFont(gvtFontFamily, style, weight)) {
+ FontTriplet triplet = fontInfo.fontLookup(gvtFontFamily, style,
+ weight);
+ int fsize = (int)(fontSize.floatValue() * 1000);
+ fonts.add(fontInfo.getFontInstance(triplet, fsize));
+ }
+ firstFontFamily = gvtFontFamily;
+ } catch (Exception e) {
+ //Most likely NoSuchMethodError here when using Batik 1.6
+ //Just skip this section in this case
+ }
+ }
+
+ if (gvtFonts != null) {
+ Iterator i = gvtFonts.iterator();
+ while (i.hasNext()) {
+ GVTFontFamily fam = (GVTFontFamily) i.next();
+ if (fam instanceof SVGFontFamily) {
+ return null; //Let Batik paint this text!
+ }
+ String fontFamily = fam.getFamilyName();
+ if (log.isDebugEnabled()) {
+ log.debug("Matching font family: " + fontFamily);
+ }
+ if (fontInfo.hasFont(fontFamily, style, weight)) {
+ FontTriplet triplet = fontInfo.fontLookup(fontFamily, style,
+ weight);
+ int fsize = (int)(fontSize.floatValue() * 1000);
+ fonts.add(fontInfo.getFontInstance(triplet, fsize));
+ }
+ if (firstFontFamily == null) {
+ firstFontFamily = fontFamily;
+ }
+ }
+ }
+ if (fonts.size() == 0) {
+ if (firstFontFamily == null) {
+ //This will probably never happen. Just to be on the safe side.
+ firstFontFamily = "any";
+ }
+ //lookup with fallback possibility (incl. substitution notification)
+ FontTriplet triplet = fontInfo.fontLookup(firstFontFamily, style, weight);
+ int fsize = (int)(fontSize.floatValue() * 1000);
+ fonts.add(fontInfo.getFontInstance(triplet, fsize));
+ }
+ return (Font[])fonts.toArray(new Font[fonts.size()]);
+ }
+
+ /**
+ * Collects all characters from an {@link AttributedCharacterIterator}.
+ * @param runaci the character iterator
+ * @return the characters
+ */
+ protected CharSequence collectCharacters(AttributedCharacterIterator runaci) {
+ StringBuffer chars = new StringBuffer();
+ for (runaci.first(); runaci.getIndex() < runaci.getEndIndex();) {
+ chars.append(runaci.current());
+ runaci.next();
+ }
+ return chars;
+ }
+
+ protected final void logTextRun(AttributedCharacterIterator runaci, TextSpanLayout layout) {
+ if (log.isTraceEnabled()) {
+ int charCount = runaci.getEndIndex() - runaci.getBeginIndex();
+ log.trace("================================================");
+ log.trace("New text run:");
+ log.trace("char count: " + charCount);
+ log.trace("range: "
+ + runaci.getBeginIndex() + " - " + runaci.getEndIndex());
+ log.trace("glyph count: " + layout.getGlyphCount()); //=getNumGlyphs()
+ }
+ }
+
+ protected final void logCharacter(char ch, TextSpanLayout layout, int index,
+ boolean visibleChar) {
+ if (log.isTraceEnabled()) {
+ log.trace("glyph " + index
+ + " -> " + layout.getGlyphIndex(index) + " => " + ch);
+ if (CharUtilities.isAnySpace(ch) && ch != 32) {
+ log.trace("Space found: " + Integer.toHexString(ch));
+ } else if (ch == CharUtilities.ZERO_WIDTH_JOINER) {
+ log.trace("ZWJ found: " + Integer.toHexString(ch));
+ } else if (ch == CharUtilities.SOFT_HYPHEN) {
+ log.trace("Soft hyphen found: " + Integer.toHexString(ch));
+ }
+ if (!visibleChar) {
+ log.trace("Invisible glyph found: " + Integer.toHexString(ch));
+ }
+ }
+ }
+
+
+}
diff --git a/src/java/org/apache/fop/svg/PDFBridgeContext.java b/src/java/org/apache/fop/svg/PDFBridgeContext.java
index 364c7a6f3..e8569f881 100644
--- a/src/java/org/apache/fop/svg/PDFBridgeContext.java
+++ b/src/java/org/apache/fop/svg/PDFBridgeContext.java
@@ -26,10 +26,12 @@ import org.apache.batik.bridge.DocumentLoader;
import org.apache.batik.bridge.SVGTextElementBridge;
import org.apache.batik.bridge.UserAgent;
import org.apache.batik.gvt.TextPainter;
-import org.apache.fop.fonts.FontInfo;
+
import org.apache.xmlgraphics.image.loader.ImageManager;
import org.apache.xmlgraphics.image.loader.ImageSessionContext;
+import org.apache.fop.fonts.FontInfo;
+
/**
* BridgeContext which registers the custom bridges for PDF output.
*/
@@ -38,11 +40,9 @@ public class PDFBridgeContext extends AbstractFOPBridgeContext {
/**
* Constructs a new bridge context.
* @param userAgent the user agent
- * @param loader the Document Loader to use for referenced documents.
+ * @param documentLoader the Document Loader to use for referenced documents.
* @param fontInfo the font list for the text painter, may be null
* in which case text is painted as shapes
- * @param linkTransform AffineTransform to properly place links,
- * may be null
* @param imageManager an image manager
* @param imageSessionContext an image session context
* @param linkTransform AffineTransform to properly place links,
@@ -52,7 +52,8 @@ public class PDFBridgeContext extends AbstractFOPBridgeContext {
FontInfo fontInfo, ImageManager imageManager,
ImageSessionContext imageSessionContext,
AffineTransform linkTransform) {
- super(userAgent, documentLoader, fontInfo, imageManager, imageSessionContext, linkTransform);
+ super(userAgent, documentLoader, fontInfo,
+ imageManager, imageSessionContext, linkTransform);
}
/**
diff --git a/src/java/org/apache/fop/svg/PDFDocumentGraphics2DConfigurator.java b/src/java/org/apache/fop/svg/PDFDocumentGraphics2DConfigurator.java
index e101a9573..b77518ab0 100644
--- a/src/java/org/apache/fop/svg/PDFDocumentGraphics2DConfigurator.java
+++ b/src/java/org/apache/fop/svg/PDFDocumentGraphics2DConfigurator.java
@@ -55,6 +55,22 @@ public class PDFDocumentGraphics2DConfigurator {
//Fonts
try {
+ FontInfo fontInfo = createFontInfo(cfg);
+ graphics.setFontInfo(fontInfo);
+ } catch (FOPException e) {
+ throw new ConfigurationException("Error while setting up fonts", e);
+ }
+ }
+
+ /**
+ * Creates the {@link FontInfo} instance for the given configuration.
+ * @param cfg the configuration
+ * @return the font collection
+ * @throws FOPException if an error occurs while setting up the fonts
+ */
+ public static FontInfo createFontInfo(Configuration cfg) throws FOPException {
+ FontInfo fontInfo = new FontInfo();
+ if (cfg != null) {
FontResolver fontResolver = FontManager.createMinimalFontResolver();
//TODO The following could be optimized by retaining the FontManager somewhere
FontManager fontManager = new FontManager();
@@ -73,12 +89,11 @@ public class PDFDocumentGraphics2DConfigurator {
if (fontManager.useCache()) {
fontManager.getFontCache().save();
}
- FontInfo fontInfo = new FontInfo();
FontSetup.setup(fontInfo, fontInfoList, fontResolver);
- graphics.setFontInfo(fontInfo);
- } catch (FOPException e) {
- throw new ConfigurationException("Error while setting up fonts", e);
+ } else {
+ FontSetup.setup(fontInfo);
}
+ return fontInfo;
}
}
diff --git a/src/java/org/apache/fop/svg/PDFTextPainter.java b/src/java/org/apache/fop/svg/PDFTextPainter.java
index 85447a4f9..dddf61a6e 100644
--- a/src/java/org/apache/fop/svg/PDFTextPainter.java
+++ b/src/java/org/apache/fop/svg/PDFTextPainter.java
@@ -25,28 +25,18 @@ import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Shape;
import java.awt.Stroke;
-import java.awt.font.TextAttribute;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
-import java.lang.reflect.Method;
import java.text.AttributedCharacterIterator;
-import java.util.Iterator;
-import java.util.List;
-import org.apache.batik.bridge.SVGFontFamily;
-import org.apache.batik.gvt.font.GVTFont;
-import org.apache.batik.gvt.font.GVTFontFamily;
import org.apache.batik.gvt.font.GVTGlyphVector;
-import org.apache.batik.gvt.renderer.StrokingTextPainter;
-import org.apache.batik.gvt.text.GVTAttributedCharacterIterator;
import org.apache.batik.gvt.text.TextPaintInfo;
import org.apache.batik.gvt.text.TextSpanLayout;
import org.apache.fop.fonts.Font;
import org.apache.fop.fonts.FontInfo;
-import org.apache.fop.fonts.FontTriplet;
import org.apache.fop.util.CharUtilities;
/**
@@ -59,193 +49,159 @@ import org.apache.fop.util.CharUtilities;
*
* @version $Id$
*/
-public class PDFTextPainter extends StrokingTextPainter {
+class PDFTextPainter extends NativeTextPainter {
private static final boolean DEBUG = false;
- private final boolean strokeText = false;
- private final FontInfo fontInfo;
-
/**
* Create a new PDF text painter with the given font information.
* @param fi the font info
*/
public PDFTextPainter(FontInfo fi) {
- fontInfo = fi;
+ super(fi);
}
/** {@inheritDoc} */
- protected void paintTextRuns(List textRuns, Graphics2D g2d) {
- if (DEBUG) {
- System.out.println("paintTextRuns: count = " + textRuns.size());
- //fontInfo.dumpAllTripletsToSystemOut();
- }
- if (!(g2d instanceof PDFGraphics2D) || strokeText) {
- super.paintTextRuns(textRuns, g2d);
+ protected boolean isSupported(Graphics2D g2d) {
+ return g2d instanceof PDFGraphics2D;
+ }
+
+ /** {@inheritDoc} */
+ protected void paintTextRun(TextRun textRun, Graphics2D g2d) {
+ AttributedCharacterIterator runaci = textRun.getACI();
+ runaci.first();
+
+ TextPaintInfo tpi = (TextPaintInfo)runaci.getAttribute(PAINT_INFO);
+ if (tpi == null || !tpi.visible) {
return;
}
+ if ((tpi != null) && (tpi.composite != null)) {
+ g2d.setComposite(tpi.composite);
+ }
+
+ //------------------------------------
+ TextSpanLayout layout = textRun.getLayout();
+ logTextRun(runaci, layout);
+ CharSequence chars = collectCharacters(runaci);
+ runaci.first(); //Reset ACI
+
final PDFGraphics2D pdf = (PDFGraphics2D)g2d;
PDFTextUtil textUtil = new PDFTextUtil(pdf.fontInfo) {
protected void write(String code) {
pdf.currentStream.write(code);
}
};
- for (int i = 0; i < textRuns.size(); i++) {
- TextRun textRun = (TextRun)textRuns.get(i);
- AttributedCharacterIterator runaci = textRun.getACI();
- runaci.first();
- TextPaintInfo tpi = (TextPaintInfo)runaci.getAttribute(PAINT_INFO);
- if (tpi == null || !tpi.visible) {
- continue;
- }
- if ((tpi != null) && (tpi.composite != null)) {
- g2d.setComposite(tpi.composite);
- }
+ if (DEBUG) {
+ log.debug("Text: " + chars);
+ pdf.currentStream.write("%Text: " + chars + "\n");
+ }
- //------------------------------------
- TextSpanLayout layout = textRun.getLayout();
- if (DEBUG) {
- int charCount = runaci.getEndIndex() - runaci.getBeginIndex();
- System.out.println("================================================");
- System.out.println("New text run:");
- System.out.println("char count: " + charCount);
- System.out.println("range: "
- + runaci.getBeginIndex() + " - " + runaci.getEndIndex());
- System.out.println("glyph count: " + layout.getGlyphCount()); //=getNumGlyphs()
- }
- //Gather all characters of the run
- StringBuffer chars = new StringBuffer();
- for (runaci.first(); runaci.getIndex() < runaci.getEndIndex();) {
- chars.append(runaci.current());
- runaci.next();
- }
- runaci.first();
- if (DEBUG) {
- System.out.println("Text: " + chars);
- pdf.currentStream.write("%Text: " + chars + "\n");
- }
+ GeneralPath debugShapes = null;
+ if (DEBUG) {
+ debugShapes = new GeneralPath();
+ }
- GeneralPath debugShapes = null;
- if (DEBUG) {
- debugShapes = new GeneralPath();
- }
+ Font[] fonts = findFonts(runaci);
+ if (fonts == null || fonts.length == 0) {
+ //Draw using Java2D when no native fonts are available
+ textRun.getLayout().draw(g2d);
+ return;
+ }
- Font[] fonts = findFonts(runaci);
- if (fonts == null || fonts.length == 0) {
- //Draw using Java2D
- textRun.getLayout().draw(g2d);
+ textUtil.saveGraphicsState();
+ textUtil.concatMatrix(g2d.getTransform());
+ Shape imclip = g2d.getClip();
+ pdf.writeClip(imclip);
+
+ applyColorAndPaint(tpi, pdf);
+
+ textUtil.beginTextObject();
+ textUtil.setFonts(fonts);
+ boolean stroke = (tpi.strokePaint != null)
+ && (tpi.strokeStroke != null);
+ textUtil.setTextRenderingMode(tpi.fillPaint != null, stroke, false);
+
+ AffineTransform localTransform = new AffineTransform();
+ Point2D prevPos = null;
+ double prevVisibleCharWidth = 0.0;
+ GVTGlyphVector gv = layout.getGlyphVector();
+ for (int index = 0, c = gv.getNumGlyphs(); index < c; index++) {
+ char ch = chars.charAt(index);
+ boolean visibleChar = gv.isGlyphVisible(index)
+ || (CharUtilities.isAnySpace(ch) && !CharUtilities.isZeroWidthSpace(ch));
+ logCharacter(ch, layout, index, visibleChar);
+ if (!visibleChar) {
continue;
}
+ Point2D glyphPos = gv.getGlyphPosition(index);
- textUtil.saveGraphicsState();
- textUtil.concatMatrix(g2d.getTransform());
- Shape imclip = g2d.getClip();
- pdf.writeClip(imclip);
-
- applyColorAndPaint(tpi, pdf);
-
- textUtil.beginTextObject();
- textUtil.setFonts(fonts);
- boolean stroke = (tpi.strokePaint != null)
- && (tpi.strokeStroke != null);
- textUtil.setTextRenderingMode(tpi.fillPaint != null, stroke, false);
-
- AffineTransform localTransform = new AffineTransform();
- Point2D prevPos = null;
- double prevVisibleCharWidth = 0.0;
- GVTGlyphVector gv = layout.getGlyphVector();
- for (int index = 0, c = gv.getNumGlyphs(); index < c; index++) {
- char ch = chars.charAt(index);
- boolean visibleChar = gv.isGlyphVisible(index)
- || (CharUtilities.isAnySpace(ch) && !CharUtilities.isZeroWidthSpace(ch));
- if (DEBUG) {
- System.out.println("glyph " + index
- + " -> " + layout.getGlyphIndex(index) + " => " + ch);
- if (CharUtilities.isAnySpace(ch) && ch != 32) {
- System.out.println("Space found: " + Integer.toHexString(ch));
- }
- if (ch == CharUtilities.ZERO_WIDTH_JOINER) {
- System.out.println("ZWJ found: " + Integer.toHexString(ch));
- }
- if (ch == CharUtilities.SOFT_HYPHEN) {
- System.out.println("Soft hyphen found: " + Integer.toHexString(ch));
- }
- if (!visibleChar) {
- System.out.println("Invisible glyph found: " + Integer.toHexString(ch));
- }
- }
- if (!visibleChar) {
- continue;
- }
- Point2D p = gv.getGlyphPosition(index);
-
- AffineTransform glyphTransform = gv.getGlyphTransform(index);
- //TODO Glyph transforms could be refined so not every char has to be painted
- //with its own TJ command (stretch/squeeze case could be optimized)
- if (DEBUG) {
- System.out.println("pos " + p + ", transform " + glyphTransform);
- Shape sh;
- sh = gv.getGlyphLogicalBounds(index);
- if (sh == null) {
- sh = new Ellipse2D.Double(p.getX(), p.getY(), 2, 2);
- }
- debugShapes.append(sh, false);
+ AffineTransform glyphTransform = gv.getGlyphTransform(index);
+ //TODO Glyph transforms could be refined so not every char has to be painted
+ //with its own TJ command (stretch/squeeze case could be optimized)
+ if (log.isTraceEnabled()) {
+ log.trace("pos " + glyphPos + ", transform " + glyphTransform);
+ }
+ if (DEBUG) {
+ Shape sh = gv.getGlyphLogicalBounds(index);
+ if (sh == null) {
+ sh = new Ellipse2D.Double(glyphPos.getX(), glyphPos.getY(), 2, 2);
}
+ debugShapes.append(sh, false);
+ }
- //Exact position of the glyph
- localTransform.setToIdentity();
- localTransform.translate(p.getX(), p.getY());
- if (glyphTransform != null) {
- localTransform.concatenate(glyphTransform);
- }
- localTransform.scale(1, -1);
+ //Exact position of the glyph
+ localTransform.setToIdentity();
+ localTransform.translate(glyphPos.getX(), glyphPos.getY());
+ if (glyphTransform != null) {
+ localTransform.concatenate(glyphTransform);
+ }
+ localTransform.scale(1, -1);
- boolean yPosChanged = (prevPos == null
- || prevPos.getY() != p.getY()
- || glyphTransform != null);
- if (yPosChanged) {
- if (index > 0) {
- textUtil.writeTJ();
- textUtil.writeTextMatrix(localTransform);
- }
- } else {
- double xdiff = p.getX() - prevPos.getX();
- //Width of previous character
- Font font = textUtil.getCurrentFont();
- double cw = prevVisibleCharWidth;
- double effxdiff = (1000 * xdiff) - cw;
- if (effxdiff != 0) {
- double adjust = (-effxdiff / font.getFontSize());
- textUtil.adjustGlyphTJ(adjust * 1000);
- }
- if (DEBUG) {
- System.out.println("==> x diff: " + xdiff + ", " + effxdiff
- + ", charWidth: " + cw);
- }
- }
- Font f = textUtil.selectFontForChar(ch);
- if (f != textUtil.getCurrentFont()) {
+ boolean yPosChanged = (prevPos == null
+ || prevPos.getY() != glyphPos.getY()
+ || glyphTransform != null);
+ if (yPosChanged) {
+ if (index > 0) {
textUtil.writeTJ();
- textUtil.setCurrentFont(f);
- textUtil.writeTf(f);
textUtil.writeTextMatrix(localTransform);
}
- char paintChar = (CharUtilities.isAnySpace(ch) ? ' ' : ch);
- textUtil.writeTJChar(paintChar);
-
- //Update last position
- prevPos = p;
- prevVisibleCharWidth = textUtil.getCurrentFont().getCharWidth(chars.charAt(index));
+ } else {
+ double xdiff = glyphPos.getX() - prevPos.getX();
+ //Width of previous character
+ Font font = textUtil.getCurrentFont();
+ double cw = prevVisibleCharWidth;
+ double effxdiff = (1000 * xdiff) - cw;
+ if (effxdiff != 0) {
+ double adjust = (-effxdiff / font.getFontSize());
+ textUtil.adjustGlyphTJ(adjust * 1000);
+ }
+ if (log.isTraceEnabled()) {
+ log.trace("==> x diff: " + xdiff + ", " + effxdiff
+ + ", charWidth: " + cw);
+ }
}
- textUtil.writeTJ();
- textUtil.endTextObject();
- textUtil.restoreGraphicsState();
- if (DEBUG) {
- g2d.setStroke(new BasicStroke(0));
- g2d.setColor(Color.LIGHT_GRAY);
- g2d.draw(debugShapes);
+ Font f = textUtil.selectFontForChar(ch);
+ if (f != textUtil.getCurrentFont()) {
+ textUtil.writeTJ();
+ textUtil.setCurrentFont(f);
+ textUtil.writeTf(f);
+ textUtil.writeTextMatrix(localTransform);
}
+ char paintChar = (CharUtilities.isAnySpace(ch) ? ' ' : ch);
+ textUtil.writeTJChar(paintChar);
+
+ //Update last position
+ prevPos = glyphPos;
+ prevVisibleCharWidth = textUtil.getCurrentFont().getCharWidth(chars.charAt(index));
+ }
+ textUtil.writeTJ();
+ textUtil.endTextObject();
+ textUtil.restoreGraphicsState();
+ if (DEBUG) {
+ g2d.setStroke(new BasicStroke(0));
+ g2d.setColor(Color.LIGHT_GRAY);
+ g2d.draw(debugShapes);
}
}
@@ -271,85 +227,4 @@ public class PDFTextPainter extends StrokingTextPainter {
pdf.applyAlpha(fillAlpha, PDFGraphics2D.OPAQUE);
}
- private Font[] findFonts(AttributedCharacterIterator aci) {
- List fonts = new java.util.ArrayList();
- List gvtFonts = (List) aci.getAttribute(
- GVTAttributedCharacterIterator.TextAttribute.GVT_FONT_FAMILIES);
- Float posture = (Float) aci.getAttribute(TextAttribute.POSTURE);
- Float taWeight = (Float) aci.getAttribute(TextAttribute.WEIGHT);
- Float fontSize = (Float) aci.getAttribute(TextAttribute.SIZE);
-
- String style = ((posture != null) && (posture.floatValue() > 0.0))
- ? Font.STYLE_ITALIC : Font.STYLE_NORMAL;
- int weight = ((taWeight != null)
- && (taWeight.floatValue() > 1.0)) ? Font.WEIGHT_BOLD
- : Font.WEIGHT_NORMAL;
-
- String firstFontFamily = null;
-
- //GVT_FONT can sometimes be different from the fonts in GVT_FONT_FAMILIES
- //or GVT_FONT_FAMILIES can even be empty and only GVT_FONT is set
- /* The following code section is not available until Batik 1.7 is released. */
- GVTFont gvtFont = (GVTFont)aci.getAttribute(
- GVTAttributedCharacterIterator.TextAttribute.GVT_FONT);
- if (gvtFont != null) {
- try {
- Method method = gvtFont.getClass().getMethod("getFamilyName", null);
- String gvtFontFamily = (String)method.invoke(gvtFont, null);
- //TODO Uncomment the following line when Batik 1.7 is shipped with FOP
- //String gvtFontFamily = gvtFont.getFamilyName(); //Not available in Batik 1.6
- if (DEBUG) {
- System.out.print(gvtFontFamily + ", ");
- }
- if (fontInfo.hasFont(gvtFontFamily, style, weight)) {
- FontTriplet triplet = fontInfo.fontLookup(gvtFontFamily, style,
- weight);
- int fsize = (int)(fontSize.floatValue() * 1000);
- fonts.add(fontInfo.getFontInstance(triplet, fsize));
- }
- firstFontFamily = gvtFontFamily;
- } catch (Exception e) {
- //Most likely NoSuchMethodError here when using Batik 1.6
- //Just skip this section in this case
- }
- }
-
- if (gvtFonts != null) {
- Iterator i = gvtFonts.iterator();
- while (i.hasNext()) {
- GVTFontFamily fam = (GVTFontFamily) i.next();
- if (fam instanceof SVGFontFamily) {
- return null; //Let Batik paint this text!
- }
- String fontFamily = fam.getFamilyName();
- if (DEBUG) {
- System.out.print(fontFamily + ", ");
- }
- if (fontInfo.hasFont(fontFamily, style, weight)) {
- FontTriplet triplet = fontInfo.fontLookup(fontFamily, style,
- weight);
- int fsize = (int)(fontSize.floatValue() * 1000);
- fonts.add(fontInfo.getFontInstance(triplet, fsize));
- }
- if (firstFontFamily == null) {
- firstFontFamily = fontFamily;
- }
- }
- }
- if (fonts.size() == 0) {
- if (firstFontFamily == null) {
- //This will probably never happen. Just to be on the safe side.
- firstFontFamily = "any";
- }
- //lookup with fallback possibility (incl. substitution notification)
- FontTriplet triplet = fontInfo.fontLookup(firstFontFamily, style, weight);
- int fsize = (int)(fontSize.floatValue() * 1000);
- fonts.add(fontInfo.getFontInstance(triplet, fsize));
- }
- if (DEBUG) {
- System.out.println();
- }
- return (Font[])fonts.toArray(new Font[fonts.size()]);
- }
-
} \ No newline at end of file
diff --git a/src/java/org/apache/fop/svg/PDFTranscoder.java b/src/java/org/apache/fop/svg/PDFTranscoder.java
index 333cd5e4c..062270f6b 100644
--- a/src/java/org/apache/fop/svg/PDFTranscoder.java
+++ b/src/java/org/apache/fop/svg/PDFTranscoder.java
@@ -23,34 +23,19 @@ import java.awt.Color;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.OutputStream;
-import java.io.InputStream;
-
-import javax.xml.transform.Source;
-import javax.xml.transform.stream.StreamSource;
import org.w3c.dom.Document;
import org.w3c.dom.svg.SVGLength;
import org.apache.avalon.framework.configuration.Configurable;
import org.apache.avalon.framework.configuration.Configuration;
-import org.apache.avalon.framework.configuration.ConfigurationException;
-import org.apache.avalon.framework.configuration.DefaultConfiguration;
import org.apache.batik.bridge.BridgeContext;
import org.apache.batik.bridge.UnitProcessor;
import org.apache.batik.bridge.UserAgent;
import org.apache.batik.ext.awt.RenderingHintsKeyExt;
import org.apache.batik.transcoder.TranscoderException;
import org.apache.batik.transcoder.TranscoderOutput;
-import org.apache.batik.transcoder.TranscodingHints;
import org.apache.batik.transcoder.image.ImageTranscoder;
-import org.apache.batik.transcoder.keys.BooleanKey;
-import org.apache.batik.transcoder.keys.FloatKey;
-import org.apache.batik.util.ParsedURL;
-
-import org.apache.xmlgraphics.image.loader.ImageContext;
-import org.apache.xmlgraphics.image.loader.ImageManager;
-import org.apache.xmlgraphics.image.loader.ImageSessionContext;
-import org.apache.xmlgraphics.image.loader.impl.AbstractImageSessionContext;
import org.apache.fop.Version;
import org.apache.fop.fonts.FontInfo;
@@ -91,27 +76,9 @@ import org.apache.fop.fonts.FontInfo;
public class PDFTranscoder extends AbstractFOPTranscoder
implements Configurable {
- /**
- * The key is used to specify the resolution for on-the-fly images generated
- * due to complex effects like gradients and filters.
- */
- public static final TranscodingHints.Key KEY_DEVICE_RESOLUTION = new FloatKey();
-
- /**
- * The key is used to specify whether the available fonts should be automatically
- * detected. The alternative is to configure the transcoder manually using a configuration
- * file.
- */
- public static final TranscodingHints.Key KEY_AUTO_FONTS = new BooleanKey();
-
- private Configuration cfg = null;
-
/** Graphics2D instance that is used to paint to */
protected PDFDocumentGraphics2D graphics = null;
- private ImageManager imageManager;
- private ImageSessionContext imageSessionContext;
-
/**
* Constructs a new <tt>PDFTranscoder</tt>.
*/
@@ -133,11 +100,6 @@ public class PDFTranscoder extends AbstractFOPTranscoder
};
}
- /** {@inheritDoc} */
- public void configure(Configuration cfg) throws ConfigurationException {
- this.cfg = cfg;
- }
-
/**
* Transcodes the specified Document as an image in the specified output.
*
@@ -155,28 +117,13 @@ public class PDFTranscoder extends AbstractFOPTranscoder
+ Version.getVersion()
+ ": PDF Transcoder for Batik");
if (hints.containsKey(KEY_DEVICE_RESOLUTION)) {
- graphics.setDeviceDPI(((Float)hints.get(KEY_DEVICE_RESOLUTION)).floatValue());
+ graphics.setDeviceDPI(getDeviceResolution());
}
setupImageInfrastructure(uri);
try {
- Configuration effCfg = this.cfg;
- if (effCfg == null) {
- //By default, enable font auto-detection if no cfg is given
- boolean autoFonts = true;
- if (hints.containsKey(KEY_AUTO_FONTS)) {
- autoFonts = ((Boolean)hints.get(KEY_AUTO_FONTS)).booleanValue();
- }
- if (autoFonts) {
- DefaultConfiguration c = new DefaultConfiguration("pdf-transcoder");
- DefaultConfiguration fonts = new DefaultConfiguration("fonts");
- c.addChild(fonts);
- DefaultConfiguration autodetect = new DefaultConfiguration("auto-detect");
- fonts.addChild(autodetect);
- effCfg = c;
- }
- }
+ Configuration effCfg = getEffectiveConfiguration();
if (effCfg != null) {
PDFDocumentGraphics2DConfigurator configurator
@@ -242,39 +189,6 @@ public class PDFTranscoder extends AbstractFOPTranscoder
}
}
- private void setupImageInfrastructure(final String baseURI) {
- final ImageContext imageContext = new ImageContext() {
- public float getSourceResolution() {
- return 25.4f / userAgent.getPixelUnitToMillimeter();
- }
- };
- this.imageManager = new ImageManager(imageContext);
- this.imageSessionContext = new AbstractImageSessionContext() {
-
- public ImageContext getParentContext() {
- return imageContext;
- }
-
- public float getTargetResolution() {
- return graphics.getDeviceDPI();
- }
-
- public Source resolveURI(String uri) {
- System.out.println("resolve " + uri);
- try {
- ParsedURL url = new ParsedURL(baseURI, uri);
- InputStream in = url.openStream();
- StreamSource source = new StreamSource(in, url.toString());
- return source;
- } catch (IOException ioe) {
- userAgent.displayError(ioe);
- return null;
- }
- }
-
- };
- }
-
/** {@inheritDoc} */
protected BridgeContext createBridgeContext() {
//For compatibility with Batik 1.6
@@ -288,7 +202,7 @@ public class PDFTranscoder extends AbstractFOPTranscoder
fontInfo = null;
}
BridgeContext ctx = new PDFBridgeContext(userAgent, fontInfo,
- this.imageManager, this.imageSessionContext);
+ getImageManager(), getImageSessionContext());
return ctx;
}
diff --git a/status.xml b/status.xml
index cbac0a971..fb6fc05ca 100644
--- a/status.xml
+++ b/status.xml
@@ -58,6 +58,10 @@
documents. Example: the fix of marks layering will be such a case when it's done.
-->
<release version="FOP Trunk" date="TBD">
+ <action context="Renderers" dev="JM" type="add">
+ Added a custom text painter for rendering SVG text using text operators when rendering
+ to PostScript or EPS. Text is no longer painted as shapes, thus creating much smaller files.
+ </action>
<action context="Renderers" dev="JM" type="fix">
Fixed a bug that left the PrintRenderer unconfigured even if a configuration was
specified for "application/X-fop-print".