aboutsummaryrefslogtreecommitdiffstats
path: root/src/java/org/apache/fop/svg
diff options
context:
space:
mode:
Diffstat (limited to 'src/java/org/apache/fop/svg')
-rw-r--r--src/java/org/apache/fop/svg/AbstractFOPTranscoder.java31
-rw-r--r--src/java/org/apache/fop/svg/PDFBatikFlowTextElementBridge.java62
-rw-r--r--src/java/org/apache/fop/svg/PDFBridgeContext.java42
-rw-r--r--src/java/org/apache/fop/svg/PDFDocumentGraphics2DConfigurator.java9
-rw-r--r--src/java/org/apache/fop/svg/PDFFlowExtTextPainter.java49
-rw-r--r--src/java/org/apache/fop/svg/PDFFlowTextPainter.java49
-rw-r--r--src/java/org/apache/fop/svg/PDFGraphics2D.java193
-rw-r--r--src/java/org/apache/fop/svg/PDFSVGFlowRootElementBridge.java62
-rw-r--r--src/java/org/apache/fop/svg/PDFTextElementBridge.java76
-rw-r--r--src/java/org/apache/fop/svg/PDFTextPainter.java568
-rw-r--r--src/java/org/apache/fop/svg/PDFTextUtil.java308
-rw-r--r--src/java/org/apache/fop/svg/PDFTranscoder.java61
12 files changed, 1028 insertions, 482 deletions
diff --git a/src/java/org/apache/fop/svg/AbstractFOPTranscoder.java b/src/java/org/apache/fop/svg/AbstractFOPTranscoder.java
index 3a03db023..b7cee29ef 100644
--- a/src/java/org/apache/fop/svg/AbstractFOPTranscoder.java
+++ b/src/java/org/apache/fop/svg/AbstractFOPTranscoder.java
@@ -19,27 +19,25 @@
package org.apache.fop.svg;
-import org.xml.sax.EntityResolver;
-
-import org.apache.commons.logging.impl.SimpleLog;
-import org.apache.commons.logging.Log;
import org.apache.batik.bridge.UserAgent;
import org.apache.batik.dom.svg.SVGDOMImplementation;
import org.apache.batik.dom.util.DocumentFactory;
import org.apache.batik.transcoder.ErrorHandler;
+import org.apache.batik.transcoder.SVGAbstractTranscoder;
import org.apache.batik.transcoder.TranscoderException;
import org.apache.batik.transcoder.TranscodingHints;
-import org.apache.batik.transcoder.SVGAbstractTranscoder;
import org.apache.batik.transcoder.image.ImageTranscoder;
import org.apache.batik.transcoder.keys.BooleanKey;
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;
/**
* This is the common base class of all of FOP's transcoders.
*/
-public abstract class AbstractFOPTranscoder extends SVGAbstractTranscoder
- {
+public abstract class AbstractFOPTranscoder extends SVGAbstractTranscoder {
/**
* The key to specify whether to stroke text instead of using text
@@ -81,6 +79,9 @@ public abstract class AbstractFOPTranscoder extends SVGAbstractTranscoder
return new FOPTranscoderUserAgent();
}
+ /**
+ * @param logger
+ */
public void setLogger(Log logger) {
this.logger = logger;
}
@@ -125,6 +126,22 @@ public abstract class AbstractFOPTranscoder extends SVGAbstractTranscoder
return factory;
}
+ /**
+ * Indicates whether text should be stroked rather than painted using text operators. Stroking
+ * text (also referred to as "painting as shapes") can used in situations where the quality of
+ * text output is not satisfying. The downside of the work-around: The generated file will
+ * likely become bigger and you will lose copy/paste functionality for certain output formats
+ * such as PDF.
+ * @return true if text should be stroked rather than painted using text operators
+ */
+ protected boolean isTextStroked() {
+ boolean stroke = false;
+ if (hints.containsKey(KEY_STROKE_TEXT)) {
+ stroke = ((Boolean)hints.get(KEY_STROKE_TEXT)).booleanValue();
+ }
+ return stroke;
+ }
+
// --------------------------------------------------------------------
// FOP's default error handler (for transcoders)
// --------------------------------------------------------------------
diff --git a/src/java/org/apache/fop/svg/PDFBatikFlowTextElementBridge.java b/src/java/org/apache/fop/svg/PDFBatikFlowTextElementBridge.java
new file mode 100644
index 000000000..748e216a7
--- /dev/null
+++ b/src/java/org/apache/fop/svg/PDFBatikFlowTextElementBridge.java
@@ -0,0 +1,62 @@
+/*
+ * 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 org.apache.batik.extension.svg.BatikFlowTextElementBridge;
+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
+ * PDF primitives.
+ */
+public class PDFBatikFlowTextElementBridge extends BatikFlowTextElementBridge {
+
+ private PDFTextPainter textPainter;
+
+ /**
+ * Main Constructor.
+ * @param fontInfo the font directory
+ */
+ public PDFBatikFlowTextElementBridge(FontInfo fontInfo) {
+ this.textPainter = new PDFFlowExtTextPainter(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;
+ }
+
+}
diff --git a/src/java/org/apache/fop/svg/PDFBridgeContext.java b/src/java/org/apache/fop/svg/PDFBridgeContext.java
index 3353bc0f2..3ffad3335 100644
--- a/src/java/org/apache/fop/svg/PDFBridgeContext.java
+++ b/src/java/org/apache/fop/svg/PDFBridgeContext.java
@@ -20,7 +20,9 @@
package org.apache.fop.svg;
import java.awt.geom.AffineTransform;
+import java.lang.reflect.Constructor;
+import org.apache.batik.bridge.Bridge;
import org.apache.batik.bridge.BridgeContext;
import org.apache.batik.bridge.DocumentLoader;
import org.apache.batik.bridge.UserAgent;
@@ -62,8 +64,9 @@ public class PDFBridgeContext extends BridgeContext {
* @param linkTransform AffineTransform to properly place links,
* may be null
*/
- public PDFBridgeContext(UserAgent userAgent, FontInfo fontInfo,
- AffineTransform linkTransform) {
+ public PDFBridgeContext(UserAgent userAgent,
+ FontInfo fontInfo,
+ AffineTransform linkTransform) {
super(userAgent);
this.fontInfo = fontInfo;
this.linkTransform = linkTransform;
@@ -79,12 +82,43 @@ public class PDFBridgeContext extends BridgeContext {
this(userAgent, fontInfo, null);
}
+ private void putPDFElementBridgeConditional(String className, String testFor) {
+ try {
+ Class.forName(testFor);
+ //if we get here the test class is available
+
+ Class clazz = Class.forName(className);
+ Constructor constructor = clazz.getConstructor(new Class[] {FontInfo.class});
+ putBridge((Bridge)constructor.newInstance(new Object[] {fontInfo}));
+ } catch (Throwable t) {
+ //simply ignore (bridges instantiated over this method are optional)
+ }
+ }
+
/** {@inheritDoc} */
public void registerSVGBridges() {
super.registerSVGBridges();
if (fontInfo != null) {
- putBridge(new PDFTextElementBridge(fontInfo));
+ PDFTextElementBridge textElementBridge = new PDFTextElementBridge(fontInfo);
+ putBridge(textElementBridge);
+
+ //Batik flow text extension (may not always be available)
+ //putBridge(new PDFBatikFlowTextElementBridge(fontInfo);
+ putPDFElementBridgeConditional(
+ "org.apache.fop.svg.PDFBatikFlowTextElementBridge",
+ "org.apache.batik.extension.svg.BatikFlowTextElementBridge");
+
+ //SVG 1.2 flow text support
+ //putBridge(new PDFSVG12TextElementBridge(fontInfo)); //-->Batik 1.7
+ putPDFElementBridgeConditional(
+ "org.apache.fop.svg.PDFSVG12TextElementBridge",
+ "org.apache.batik.bridge.svg12.SVG12TextElementBridge");
+
+ //putBridge(new PDFSVGFlowRootElementBridge(fontInfo));
+ putPDFElementBridgeConditional(
+ "org.apache.fop.svg.PDFSVGFlowRootElementBridge",
+ "org.apache.batik.bridge.svg12.SVGFlowRootElementBridge");
}
PDFAElementBridge pdfAElementBridge = new PDFAElementBridge();
@@ -99,8 +133,10 @@ public class PDFBridgeContext extends BridgeContext {
}
// 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 PDFBridgeContext(getUserAgent(), getDocumentLoader(),
fontInfo, linkTransform);
}
+
}
diff --git a/src/java/org/apache/fop/svg/PDFDocumentGraphics2DConfigurator.java b/src/java/org/apache/fop/svg/PDFDocumentGraphics2DConfigurator.java
index fe4d50a4a..f57e8cc58 100644
--- a/src/java/org/apache/fop/svg/PDFDocumentGraphics2DConfigurator.java
+++ b/src/java/org/apache/fop/svg/PDFDocumentGraphics2DConfigurator.java
@@ -24,6 +24,7 @@ import java.util.List;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.fop.apps.FOPException;
+import org.apache.fop.fonts.FontCache;
import org.apache.fop.fonts.FontInfo;
import org.apache.fop.fonts.FontResolver;
import org.apache.fop.fonts.FontSetup;
@@ -53,8 +54,14 @@ public class PDFDocumentGraphics2DConfigurator {
//Fonts
try {
FontResolver fontResolver = FontSetup.createMinimalFontResolver();
+ //TODO The following could be optimized by retaining the FontCache somewhere
+ FontCache fontCache = FontCache.load();
+ if (fontCache == null) {
+ fontCache = new FontCache();
+ }
List fontList = PrintRendererConfigurator.buildFontListFromConfiguration(
- cfg, null, fontResolver, false, null);
+ cfg, null, fontResolver, false, fontCache);
+ fontCache.save();
FontInfo fontInfo = new FontInfo();
FontSetup.setup(fontInfo, fontList, fontResolver);
graphics.setFontInfo(fontInfo);
diff --git a/src/java/org/apache/fop/svg/PDFFlowExtTextPainter.java b/src/java/org/apache/fop/svg/PDFFlowExtTextPainter.java
new file mode 100644
index 000000000..0e8f47cfe
--- /dev/null
+++ b/src/java/org/apache/fop/svg/PDFFlowExtTextPainter.java
@@ -0,0 +1,49 @@
+/*
+ * 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.text.AttributedCharacterIterator;
+import java.util.List;
+
+import org.apache.batik.extension.svg.FlowExtTextPainter;
+import org.apache.batik.gvt.TextNode;
+import org.apache.fop.fonts.FontInfo;
+
+/**
+ * Text Painter for Batik's flow text extension.
+ */
+public class PDFFlowExtTextPainter extends PDFTextPainter {
+
+ /**
+ * Main constructor
+ * @param fontInfo the font directory
+ */
+ public PDFFlowExtTextPainter(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/svg/PDFFlowTextPainter.java b/src/java/org/apache/fop/svg/PDFFlowTextPainter.java
new file mode 100644
index 000000000..eeef40da1
--- /dev/null
+++ b/src/java/org/apache/fop/svg/PDFFlowTextPainter.java
@@ -0,0 +1,49 @@
+/*
+ * 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.text.AttributedCharacterIterator;
+import java.util.List;
+
+import org.apache.batik.gvt.TextNode;
+import org.apache.batik.gvt.flow.FlowTextPainter;
+import org.apache.fop.fonts.FontInfo;
+
+/**
+ * Text Painter for SVG 1.2 (flow) text.
+ */
+public class PDFFlowTextPainter extends PDFTextPainter {
+
+ /**
+ * Main constructor
+ * @param fontInfo the font directory
+ */
+ public PDFFlowTextPainter(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/svg/PDFGraphics2D.java b/src/java/org/apache/fop/svg/PDFGraphics2D.java
index dc93c3371..2436e1a10 100644
--- a/src/java/org/apache/fop/svg/PDFGraphics2D.java
+++ b/src/java/org/apache/fop/svg/PDFGraphics2D.java
@@ -106,6 +106,9 @@ public class PDFGraphics2D extends AbstractGraphics2D {
/** The number of decimal places. */
private static final int DEC = 8;
+
+ /** Convenience constant for full opacity */
+ static final int OPAQUE = 255;
/**
* the PDF Document being created
@@ -619,7 +622,7 @@ public class PDFGraphics2D extends AbstractGraphics2D {
Shape imclip = getClip();
writeClip(imclip);
currentStream.write("" + width + " 0 0 " + (-height) + " " + x
- + " " + (y + height) + " cm\n" + "/Im"
+ + " " + (y + height) + " cm\n"
+ imageInfo.getName() + " Do\nQ\n");
return true;
}
@@ -704,16 +707,7 @@ public class PDFGraphics2D extends AbstractGraphics2D {
}
}
- if (c.getAlpha() != 255) {
- checkTransparencyAllowed();
- Map vals = new java.util.HashMap();
- vals.put(PDFGState.GSTATE_ALPHA_STROKE,
- new Float(c.getAlpha() / 255f));
- PDFGState gstate = pdfDoc.getFactory().makeGState(
- vals, graphicsState.getGState());
- resourceContext.addGState(gstate);
- currentStream.write("/" + gstate.getName() + " gs\n");
- }
+ applyAlpha(OPAQUE, c.getAlpha());
c = getColor();
applyColor(c, false);
@@ -1054,12 +1048,12 @@ public class PDFGraphics2D extends AbstractGraphics2D {
private boolean createPattern(PatternPaint pp, boolean fill) {
preparePainting();
- FontInfo fontInfo = new FontInfo();
- FontSetup.setup(fontInfo, null, null);
+ FontInfo specialFontInfo = new FontInfo();
+ FontSetup.setup(specialFontInfo, null, null);
PDFResources res = pdfDoc.getFactory().makeResources();
PDFResourceContext context = new PDFResourceContext(res);
- PDFGraphics2D pattGraphic = new PDFGraphics2D(textAsShapes, fontInfo,
+ PDFGraphics2D pattGraphic = new PDFGraphics2D(textAsShapes, specialFontInfo,
pdfDoc, context, pageRef,
"", 0);
pattGraphic.setGraphicContext(new GraphicContext());
@@ -1125,7 +1119,7 @@ public class PDFGraphics2D extends AbstractGraphics2D {
/** @todo see if pdfDoc and res can be linked here,
(currently res <> PDFDocument's resources) so addFonts()
can be moved to PDFDocument class */
- res.addFonts(pdfDoc, fontInfo);
+ res.addFonts(pdfDoc, specialFontInfo);
PDFPattern myPat = pdfDoc.getFactory().makePattern(
resourceContext, 1, res, 1, 1, bbox,
@@ -1156,11 +1150,13 @@ public class PDFGraphics2D extends AbstractGraphics2D {
Shape clip = getClip();
Rectangle2D usrClipBounds, usrBounds;
usrBounds = shape.getBounds2D();
- usrClipBounds = clip.getBounds2D();
- if (!usrClipBounds.intersects(usrBounds)) {
- return true;
+ if (clip != null) {
+ usrClipBounds = clip.getBounds2D();
+ if (!usrClipBounds.intersects(usrBounds)) {
+ return true;
+ }
+ Rectangle2D.intersect(usrBounds, usrClipBounds, usrBounds);
}
- Rectangle2D.intersect(usrBounds, usrClipBounds, usrBounds);
double usrX = usrBounds.getX();
double usrY = usrBounds.getY();
double usrW = usrBounds.getWidth();
@@ -1169,11 +1165,15 @@ public class PDFGraphics2D extends AbstractGraphics2D {
Rectangle devShapeBounds, devClipBounds, devBounds;
AffineTransform at = getTransform();
devShapeBounds = at.createTransformedShape(shape).getBounds();
- devClipBounds = at.createTransformedShape(clip).getBounds();
- if (!devClipBounds.intersects(devShapeBounds)) {
- return true;
+ if (clip != null) {
+ devClipBounds = at.createTransformedShape(clip).getBounds();
+ if (!devClipBounds.intersects(devShapeBounds)) {
+ return true;
+ }
+ devBounds = devShapeBounds.intersection(devClipBounds);
+ } else {
+ devBounds = devShapeBounds;
}
- devBounds = devShapeBounds.intersection(devClipBounds);
int devX = devBounds.x;
int devY = devBounds.y;
int devW = devBounds.width;
@@ -1416,69 +1416,25 @@ public class PDFGraphics2D extends AbstractGraphics2D {
if (ovFontState == null) {
java.awt.Font gFont = getFont();
fontTransform = gFont.getTransform();
- String n = gFont.getFamily();
- if (n.equals("sanserif")) {
- n = "sans-serif";
- }
- float siz = gFont.getSize2D();
- String style = gFont.isItalic() ? "italic" : "normal";
- int weight = gFont.isBold() ? Font.WEIGHT_BOLD : Font.WEIGHT_NORMAL;
- FontTriplet triplet = fontInfo.fontLookup(n, style, weight);
- fontState = fontInfo.getFontInstance(triplet, (int)(siz * 1000 + 0.5));
+ fontState = getInternalFontForAWTFont(gFont);
} else {
fontState = fontInfo.getFontInstance(
ovFontState.getFontTriplet(), ovFontState.getFontSize());
ovFontState = null;
}
- String name;
- float size;
- name = fontState.getFontName();
- size = (float)fontState.getFontSize() / 1000f;
-
- if ((!name.equals(this.currentFontName))
- || (size != this.currentFontSize)) {
- this.currentFontName = name;
- this.currentFontSize = size;
- currentStream.write("/" + name + " " + size + " Tf\n");
-
- }
+ updateCurrentFont(fontState);
currentStream.write("q\n");
Color c = getColor();
applyColor(c, true);
applyPaint(getPaint(), true);
- int salpha = c.getAlpha();
+ applyAlpha(c.getAlpha(), OPAQUE);
- if (salpha != 255) {
- checkTransparencyAllowed();
- Map vals = new java.util.HashMap();
- vals.put(PDFGState.GSTATE_ALPHA_NONSTROKE, new Float(salpha / 255f));
- PDFGState gstate = pdfDoc.getFactory().makeGState(
- vals, graphicsState.getGState());
- resourceContext.addGState(gstate);
- currentStream.write("/" + gstate.getName() + " gs\n");
- }
-
- Map kerning = null;
- boolean kerningAvailable = false;
+ Map kerning = fontState.getKerning();
+ boolean kerningAvailable = (kerning != null && !kerning.isEmpty());
- kerning = fontState.getKerning();
- if (kerning != null && !kerning.isEmpty()) {
- kerningAvailable = true;
- }
-
- // This assumes that *all* CIDFonts use a /ToUnicode mapping
- boolean useMultiByte = false;
- org.apache.fop.fonts.Typeface f =
- (org.apache.fop.fonts.Typeface)fontInfo.getFonts().get(name);
- if (f instanceof LazyFont) {
- if (((LazyFont) f).getRealFont() instanceof CIDFont) {
- useMultiByte = true;
- }
- } else if (f instanceof CIDFont) {
- useMultiByte = true;
- }
+ boolean useMultiByte = isMultiByteFont(currentFontName);
// String startText = useMultiByte ? "<FEFF" : "(";
String startText = useMultiByte ? "<" : "(";
@@ -1524,6 +1480,7 @@ public class PDFGraphics2D extends AbstractGraphics2D {
case '\\':
currentStream.write("\\");
break;
+ default:
}
currentStream.write(ch);
}
@@ -1540,13 +1497,88 @@ public class PDFGraphics2D extends AbstractGraphics2D {
}
currentStream.write(endText);
-
currentStream.write("] TJ\n");
-
currentStream.write("ET\n");
currentStream.write("Q\n");
}
+ /**
+ * Applies the given alpha values for filling and stroking.
+ * @param fillAlpha A value between 0 and 255 (=OPAQUE) for filling
+ * @param strokeAlpha A value between 0 and 255 (=OPAQUE) for stroking
+ */
+ protected void applyAlpha(int fillAlpha, int strokeAlpha) {
+ if (fillAlpha != OPAQUE || strokeAlpha != OPAQUE) {
+ checkTransparencyAllowed();
+ Map vals = new java.util.HashMap();
+ if (fillAlpha != OPAQUE) {
+ vals.put(PDFGState.GSTATE_ALPHA_NONSTROKE, new Float(fillAlpha / 255f));
+ }
+ if (strokeAlpha != OPAQUE) {
+ vals.put(PDFGState.GSTATE_ALPHA_STROKE, new Float(strokeAlpha / 255f));
+ }
+ PDFGState gstate = pdfDoc.getFactory().makeGState(
+ vals, graphicsState.getGState());
+ resourceContext.addGState(gstate);
+ currentStream.write("/" + gstate.getName() + " gs\n");
+ }
+ }
+
+ /**
+ * Updates the currently selected font.
+ * @param font the new font to use
+ */
+ protected void updateCurrentFont(Font font) {
+ String name = font.getFontName();
+ float size = (float)font.getFontSize() / 1000f;
+
+ //Only update if necessary
+ if ((!name.equals(this.currentFontName))
+ || (size != this.currentFontSize)) {
+ this.currentFontName = name;
+ this.currentFontSize = size;
+ currentStream.write("/" + name + " " + size + " Tf\n");
+ }
+ }
+
+ /**
+ * Returns a suitable internal font given an AWT Font instance.
+ * @param awtFont the AWT font
+ * @return the internal Font
+ */
+ protected Font getInternalFontForAWTFont(java.awt.Font awtFont) {
+ Font fontState;
+ String n = awtFont.getFamily();
+ if (n.equals("sanserif")) {
+ n = "sans-serif";
+ }
+ float siz = awtFont.getSize2D();
+ String style = awtFont.isItalic() ? "italic" : "normal";
+ int weight = awtFont.isBold() ? Font.WEIGHT_BOLD : Font.WEIGHT_NORMAL;
+ FontTriplet triplet = fontInfo.fontLookup(n, style, weight);
+ fontState = fontInfo.getFontInstance(triplet, (int)(siz * 1000 + 0.5));
+ return fontState;
+ }
+
+ /**
+ * Determines whether the font with the given name is a multi-byte font.
+ * @param name the name of the font
+ * @return true if it's a multi-byte font
+ */
+ protected boolean isMultiByteFont(String name) {
+ // This assumes that *all* CIDFonts use a /ToUnicode mapping
+ org.apache.fop.fonts.Typeface f
+ = (org.apache.fop.fonts.Typeface)fontInfo.getFonts().get(name);
+ if (f instanceof LazyFont) {
+ if (((LazyFont) f).getRealFont() instanceof CIDFont) {
+ return true;
+ }
+ } else if (f instanceof CIDFont) {
+ return true;
+ }
+ return false;
+ }
+
private void addKerning(StringWriter buf, Integer ch1, Integer ch2,
Map kerning, String startText,
String endText) {
@@ -1699,16 +1731,7 @@ public class PDFGraphics2D extends AbstractGraphics2D {
}
}
- if (c.getAlpha() != 255) {
- checkTransparencyAllowed();
- Map vals = new java.util.HashMap();
- vals.put(PDFGState.GSTATE_ALPHA_NONSTROKE,
- new Float(c.getAlpha() / 255f));
- PDFGState gstate = pdfDoc.getFactory().makeGState(
- vals, graphicsState.getGState());
- resourceContext.addGState(gstate);
- currentStream.write("/" + gstate.getName() + " gs\n");
- }
+ applyAlpha(c.getAlpha(), OPAQUE);
c = getColor();
applyColor(c, true);
diff --git a/src/java/org/apache/fop/svg/PDFSVGFlowRootElementBridge.java b/src/java/org/apache/fop/svg/PDFSVGFlowRootElementBridge.java
new file mode 100644
index 000000000..ec6996389
--- /dev/null
+++ b/src/java/org/apache/fop/svg/PDFSVGFlowRootElementBridge.java
@@ -0,0 +1,62 @@
+/*
+ * 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 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.fop.fonts.FontInfo;
+
+/**
+ * Element Bridge for SVG 1.2 flow text, so those texts can be painted using
+ * PDF primitives.
+ */
+public class PDFSVGFlowRootElementBridge extends SVGFlowRootElementBridge {
+
+ private PDFTextPainter textPainter;
+
+ /**
+ * Main Constructor.
+ * @param fontInfo the font directory
+ */
+ public PDFSVGFlowRootElementBridge(FontInfo fontInfo) {
+ this.textPainter = new PDFFlowTextPainter(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;
+ }
+
+}
diff --git a/src/java/org/apache/fop/svg/PDFTextElementBridge.java b/src/java/org/apache/fop/svg/PDFTextElementBridge.java
index 100711eb9..47e794dda 100644
--- a/src/java/org/apache/fop/svg/PDFTextElementBridge.java
+++ b/src/java/org/apache/fop/svg/PDFTextElementBridge.java
@@ -19,15 +19,13 @@
package org.apache.fop.svg;
-import org.apache.batik.gvt.TextNode;
-import org.apache.batik.bridge.SVGTextElementBridge;
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.apache.fop.fonts.FontInfo;
-
import org.w3c.dom.Element;
-import org.w3c.dom.Node;
/**
* Bridge class for the &lt;text> element.
@@ -37,11 +35,12 @@ import org.w3c.dom.Node;
* @author <a href="mailto:keiron@aftexsw.com">Keiron Liddle</a>
*/
public class PDFTextElementBridge extends SVGTextElementBridge {
+
private PDFTextPainter pdfTextPainter;
/**
* Constructs a new bridge for the &lt;text> element.
- * @param fi the font infomration
+ * @param fi the font information
*/
public PDFTextElementBridge(FontInfo fi) {
pdfTextPainter = new PDFTextPainter(fi);
@@ -56,71 +55,20 @@ public class PDFTextElementBridge extends SVGTextElementBridge {
*/
public GraphicsNode createGraphicsNode(BridgeContext ctx, Element e) {
GraphicsNode node = super.createGraphicsNode(ctx, e);
- if (node != null && isSimple(ctx, e, node)) {
+ if (node != null) {
+ //Set our own text painter
((TextNode)node).setTextPainter(getTextPainter());
}
return node;
}
- private PDFTextPainter getTextPainter() {
- return pdfTextPainter;
- }
-
/**
- * 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
+ * Returns the TextPainter instance used by this bridge.
+ * @return the text painter
*/
- private boolean isSimple(BridgeContext ctx, Element element, GraphicsNode node) {
- /* I cannot find any reference that 36pt is the maximum font size in PDF. Tests show
- * no such restriction (jeremias, 28.5.2007)
- *
- // Font size, in user space units.
- float fs = TextUtilities.convertFontSize(element).floatValue();
- // PDF cannot display fonts over 36pt
- if (fs > 36) {
- return false;
- }
- */
-
- Element nodeElement;
- for (Node n = element.getFirstChild();
- n != null;
- n = n.getNextSibling()) {
-
- switch (n.getNodeType()) {
- case Node.ELEMENT_NODE:
-
- nodeElement = (Element)n;
-
- 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:
- }
- }
-
- /*if (CSSUtilities.convertFilter(element, node, ctx) != null) {
- return false;
- }*/
-
- return true;
+ public TextPainter getTextPainter() {
+ return pdfTextPainter;
}
+
}
diff --git a/src/java/org/apache/fop/svg/PDFTextPainter.java b/src/java/org/apache/fop/svg/PDFTextPainter.java
index f05d5ae4f..81fc107d4 100644
--- a/src/java/org/apache/fop/svg/PDFTextPainter.java
+++ b/src/java/org/apache/fop/svg/PDFTextPainter.java
@@ -19,32 +19,34 @@
package org.apache.fop.svg;
+import java.awt.BasicStroke;
+import java.awt.Color;
import java.awt.Graphics2D;
-import java.awt.geom.Point2D;
-import java.awt.geom.Rectangle2D;
-/* java.awt.Font is not imported to avoid confusion with
- org.apache.fop.fonts.Font */
-import java.text.AttributedCharacterIterator;
-import java.awt.font.TextAttribute;
-import java.awt.Shape;
import java.awt.Paint;
+import java.awt.Shape;
import java.awt.Stroke;
-import java.awt.Color;
-import java.util.List;
+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.gvt.text.Mark;
-import org.apache.batik.gvt.TextPainter;
-import org.apache.batik.gvt.TextNode;
-import org.apache.batik.gvt.text.GVTAttributedCharacterIterator;
-import org.apache.batik.gvt.text.TextPaintInfo;
-import org.apache.batik.gvt.font.GVTFontFamily;
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;
/**
* Renders the attributed character iterator of a <tt>TextNode</tt>.
@@ -54,108 +56,221 @@ import org.apache.fop.fonts.FontTriplet;
* 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$
*/
-public class PDFTextPainter implements TextPainter {
- private FontInfo fontInfo;
+public class PDFTextPainter extends StrokingTextPainter {
- /**
- * 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 boolean DEBUG = true;
+
+ private boolean strokeText = false;
+ private FontInfo fontInfo;
/**
* Create a new PDF text painter with the given font information.
- * @param fi the fint info
+ * @param fi the font info
*/
public PDFTextPainter(FontInfo fi) {
fontInfo = fi;
}
- /**
- * 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();
-
- AttributedCharacterIterator aci =
- node.getAttributedCharacterIterator();
- // reset position to start of char iterator
- if (aci.getBeginIndex() == aci.getEndIndex()) {
- return;
- }
- char ch = aci.first();
- if (ch == AttributedCharacterIterator.DONE) {
- return;
+ /** {@inheritDoc} */
+ protected void paintTextRuns(List textRuns, Graphics2D g2d) {
+ if (DEBUG) {
+ System.out.println("paintTextRuns: count = " + textRuns.size());
+ //fontInfo.dumpAllTripletsToSystemOut();
}
- TextNode.Anchor anchor;
- anchor = (TextNode.Anchor) aci.getAttribute(
- GVTAttributedCharacterIterator.TextAttribute.ANCHOR_TYPE);
-
- List gvtFonts;
- gvtFonts = (List) aci.getAttribute(
- GVTAttributedCharacterIterator.TextAttribute.GVT_FONT_FAMILIES);
-
- TextPaintInfo tpi = (TextPaintInfo) aci.getAttribute(
- GVTAttributedCharacterIterator.TextAttribute.PAINT_INFO);
-
- if (tpi == null) {
- return;
- }
-
- Paint forg = tpi.fillPaint;
- Paint strokePaint = tpi.strokePaint;
- Float size = (Float) aci.getAttribute(TextAttribute.SIZE);
- if (size == null) {
+ if (!(g2d instanceof PDFGraphics2D) || strokeText) {
+ super.paintTextRuns(textRuns, g2d);
return;
}
- Stroke stroke = tpi.strokeStroke;
- /*
- Float xpos = (Float) aci.getAttribute(
- GVTAttributedCharacterIterator.TextAttribute.X);
- Float ypos = (Float) aci.getAttribute(
- GVTAttributedCharacterIterator.TextAttribute.Y);
- */
-
- Float posture = (Float) aci.getAttribute(TextAttribute.POSTURE);
- Float taWeight = (Float) aci.getAttribute(TextAttribute.WEIGHT);
-
- boolean useStrokePainter = false;
+ PDFGraphics2D pdf = (PDFGraphics2D)g2d;
+ PDFTextUtil textUtil = new PDFTextUtil(pdf);
+ 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);
+ }
+
+ //------------------------------------
+ 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();
+ }
+
+ Font[] fonts = findFonts(runaci);
+ if (fonts == null || fonts.length == 0) {
+ //Draw using Java2D
+ textRun.getLayout().draw(g2d);
+ continue;
+ }
+
+ textUtil.saveGraphicsState();
+ textUtil.concatMatrixCurrentTransform();
+ Shape imclip = g2d.getClip();
+ pdf.writeClip(imclip);
+
+ applyColorAndPaint(tpi, pdf);
+
+ textUtil.beginTextObject();
+ textUtil.setFonts(fonts);
+ textUtil.setTextRenderingMode(tpi.fillPaint != null, tpi.strokePaint != null, 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);
+ }
- if (forg instanceof Color) {
- Color col = (Color) forg;
- if (col.getAlpha() != 255) {
- useStrokePainter = true;
+ //Exact position of the glyph
+ localTransform.setToIdentity();
+ localTransform.translate(p.getX(), p.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()) {
+ 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));
+ }
+ textUtil.writeTJ();
+ textUtil.endTextObject();
+ textUtil.restoreGraphicsState();
+ if (DEBUG) {
+ g2d.setStroke(new BasicStroke(0));
+ g2d.setColor(Color.LIGHT_GRAY);
+ g2d.draw(debugShapes);
}
- g2d.setColor(col);
}
- g2d.setPaint(forg);
- g2d.setStroke(stroke);
+ }
- if (strokePaint != null) {
- // need to draw using AttributedCharacterIterator
- useStrokePainter = true;
+ private void applyColorAndPaint(TextPaintInfo tpi, PDFGraphics2D pdf) {
+ Paint fillPaint = tpi.fillPaint;
+ Paint strokePaint = tpi.strokePaint;
+ Stroke stroke = tpi.strokeStroke;
+ int fillAlpha = PDFGraphics2D.OPAQUE;
+ if (fillPaint instanceof Color) {
+ Color col = (Color)fillPaint;
+ pdf.applyColor(col, true);
+ fillAlpha = col.getAlpha();
}
-
- if (hasUnsupportedAttributes(aci)) {
- useStrokePainter = true;
+ if (strokePaint instanceof Color) {
+ Color col = (Color)strokePaint;
+ pdf.applyColor(col, false);
}
-
- // text contains unsupported information
- if (useStrokePainter) {
- PROXY_PAINTER.paint(node, g2d);
- return;
+ pdf.applyPaint(fillPaint, true);
+ pdf.applyStroke(stroke);
+ if (strokePaint != null) {
+ pdf.applyPaint(strokePaint, false);
}
+ 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))
? "italic" : "normal";
@@ -163,236 +278,65 @@ public class PDFTextPainter implements TextPainter {
&& (taWeight.floatValue() > 1.0)) ? Font.WEIGHT_BOLD
: Font.WEIGHT_NORMAL;
- Font fontState = null;
- FontInfo fi = fontInfo;
- boolean found = false;
String fontFamily = 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));
+ }
+ } 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) {
- PROXY_PAINTER.paint(node, g2d);
- return;
+ return null; //Let Batik paint this text!
}
fontFamily = fam.getFamilyName();
- if (fi.hasFont(fontFamily, style, weight)) {
+ if (DEBUG) {
+ System.out.print(fontFamily + ", ");
+ }
+ if (fontInfo.hasFont(fontFamily, style, weight)) {
FontTriplet triplet = fontInfo.fontLookup(fontFamily, style,
weight);
- int fsize = (int)(size.floatValue() * 1000);
- fontState = fontInfo.getFontInstance(triplet, fsize);
- found = true;
- break;
+ int fsize = (int)(fontSize.floatValue() * 1000);
+ fonts.add(fontInfo.getFontInstance(triplet, fsize));
}
}
}
- if (!found) {
+ if (fonts.size() == 0) {
FontTriplet triplet = fontInfo.fontLookup("any", style, Font.WEIGHT_NORMAL);
- int fsize = (int)(size.floatValue() * 1000);
- fontState = fontInfo.getFontInstance(triplet, fsize);
- } else {
- if (g2d instanceof PDFGraphics2D) {
- ((PDFGraphics2D) g2d).setOverrideFontState(fontState);
- }
- }
- int fStyle = java.awt.Font.PLAIN;
- if (weight == Font.WEIGHT_BOLD) {
- if (style.equals("italic")) {
- fStyle = java.awt.Font.BOLD | java.awt.Font.ITALIC;
- } else {
- fStyle = java.awt.Font.BOLD;
+ int fsize = (int)(fontSize.floatValue() * 1000);
+ fonts.add(fontInfo.getFontInstance(triplet, fsize));
+ if (DEBUG) {
+ System.out.print("fallback to 'any' font");
}
- } else {
- if (style.equals("italic")) {
- fStyle = java.awt.Font.ITALIC;
- } else {
- fStyle = java.awt.Font.PLAIN;
- }
- }
- java.awt.Font font = new java.awt.Font(fontFamily, fStyle,
- (int)(fontState.getFontSize() / 1000));
-
- g2d.setFont(font);
-
- float advance = getStringWidth(txt, fontState);
- 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;
- }
- }
- g2d.drawString(txt, (float)(loc.getX() + tx), (float)(loc.getY()));
- }
-
- private boolean hasUnsupportedAttributes(AttributedCharacterIterator aci) {
- boolean hasunsupported = false;
- Object letSpace = aci.getAttribute(
- GVTAttributedCharacterIterator.TextAttribute.LETTER_SPACING);
- if (letSpace != null) {
- hasunsupported = true;
- }
-
- Object wordSpace = aci.getAttribute(
- GVTAttributedCharacterIterator.TextAttribute.WORD_SPACING);
- if (wordSpace != null) {
- hasunsupported = true;
- }
-
- AttributedCharacterIterator.Attribute key;
- key = GVTAttributedCharacterIterator.TextAttribute.WRITING_MODE;
- Object writeMod = aci.getAttribute(key);
- if (!GVTAttributedCharacterIterator.TextAttribute.WRITING_MODE_LTR.equals(
- writeMod)) {
- hasunsupported = true;
}
-
- Object vertOr = aci.getAttribute(
- GVTAttributedCharacterIterator.TextAttribute.VERTICAL_ORIENTATION);
- if (GVTAttributedCharacterIterator.TextAttribute.ORIENTATION_ANGLE.equals(
- vertOr)) {
- hasunsupported = true;
+ if (DEBUG) {
+ System.out.println();
}
- return hasunsupported;
+ return (Font[])fonts.toArray(new Font[fonts.size()]);
}
-
- private float getStringWidth(String str, Font fontState) {
- float wordWidth = 0;
- float whitespaceWidth = fontState.getWidth(fontState.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 = fontState.getWidth(fontState.mapChar(c));
- if (charWidth <= 0) {
- charWidth = whitespaceWidth;
- }
- } else {
- charWidth = whitespaceWidth;
- }
- wordWidth += charWidth;
- }
- return wordWidth / 1000f;
- }
-
- /**
- * 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) {
- 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 PDF
-
- /**
- * 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;
- }
-
- /**
- * 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;
- }
-
- /**
- * 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;
- }
-
- /**
- * 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;
- }
-
- /**
- * 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;
- }
-
- /**
- * 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;
- }
-
- /**
- * 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;
- }
-
-}
-
+
+} \ No newline at end of file
diff --git a/src/java/org/apache/fop/svg/PDFTextUtil.java b/src/java/org/apache/fop/svg/PDFTextUtil.java
new file mode 100644
index 000000000..0fb552026
--- /dev/null
+++ b/src/java/org/apache/fop/svg/PDFTextUtil.java
@@ -0,0 +1,308 @@
+/*
+ * 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.geom.AffineTransform;
+
+import org.apache.fop.fonts.Font;
+import org.apache.fop.pdf.PDFNumber;
+import org.apache.fop.pdf.PDFText;
+
+/**
+ * Utility class for generating PDF text objects.
+ */
+public class PDFTextUtil {
+
+ /** The number of decimal places. */
+ private static final int DEC = 8;
+
+ /** PDF text rendering mode: Fill text */
+ public static final int TR_FILL = 0;
+ /** PDF text rendering mode: Stroke text */
+ public static final int TR_STROKE = 1;
+ /** PDF text rendering mode: Fill, then stroke text */
+ public static final int TR_FILL_STROKE = 2;
+ /** PDF text rendering mode: Neither fill nor stroke text (invisible) */
+ public static final int TR_INVISIBLE = 3;
+ /** PDF text rendering mode: Fill text and add to path for clipping */
+ public static final int TR_FILL_CLIP = 4;
+ /** PDF text rendering mode: Stroke text and add to path for clipping */
+ public static final int TR_STROKE_CLIP = 5;
+ /** PDF text rendering mode: Fill, then stroke text and add to path for clipping */
+ public static final int TR_FILL_STROKE_CLIP = 6;
+ /** PDF text rendering mode: Add text to path for clipping */
+ public static final int TR_CLIP = 7;
+
+
+ private PDFGraphics2D g2d;
+ private boolean inTextObject = false;
+ private Font[] fonts;
+ private Font font;
+ private String startText;
+ private String endText;
+ private boolean useMultiByte;
+ private StringBuffer bufTJ;
+ private int textRenderingMode = 0;
+
+ /**
+ * Main constructor.
+ * @param g2d the PDFGraphics2D instance to work with
+ */
+ public PDFTextUtil(PDFGraphics2D g2d) {
+ this.g2d = g2d;
+ }
+
+ private void writeAffineTransform(AffineTransform at, StringBuffer sb) {
+ double[] lt = new double[6];
+ at.getMatrix(lt);
+ sb.append(PDFNumber.doubleOut(lt[0], DEC)).append(" ");
+ sb.append(PDFNumber.doubleOut(lt[1], DEC)).append(" ");
+ sb.append(PDFNumber.doubleOut(lt[2], DEC)).append(" ");
+ sb.append(PDFNumber.doubleOut(lt[3], DEC)).append(" ");
+ sb.append(PDFNumber.doubleOut(lt[4], DEC)).append(" ");
+ sb.append(PDFNumber.doubleOut(lt[5], DEC));
+ }
+
+ private void writeChar(char ch, StringBuffer sb) {
+ if (!useMultiByte) {
+ if (ch > 127) {
+ sb.append("\\").append(Integer.toOctalString((int)ch));
+ } else {
+ switch (ch) {
+ case '(':
+ case ')':
+ case '\\':
+ sb.append("\\");
+ break;
+ default:
+ }
+ sb.append(ch);
+ }
+ } else {
+ sb.append(PDFText.toUnicodeHex(ch));
+ }
+ }
+
+ private void checkInTextObject() {
+ if (!inTextObject) {
+ throw new IllegalStateException("Not in text object");
+ }
+ }
+
+ /**
+ * Called when a new text object should be started. Be sure to call setFont() before
+ * issuing any text painting commands.
+ */
+ public void beginTextObject() {
+ if (inTextObject) {
+ throw new IllegalStateException("Already in text object");
+ }
+ g2d.currentStream.write("BT\n");
+ this.inTextObject = true;
+ }
+
+ /**
+ * Called when a text object should be ended.
+ */
+ public void endTextObject() {
+ checkInTextObject();
+ g2d.currentStream.write("ET\n");
+ this.inTextObject = false;
+ initValues();
+ }
+
+ private void initValues() {
+ this.font = null;
+ this.textRenderingMode = TR_FILL;
+ }
+
+ /**
+ * Creates a "q" command, pushing a copy of the entire graphics state onto the stack.
+ */
+ public void saveGraphicsState() {
+ g2d.currentStream.write("q\n");
+ }
+
+ /**
+ * Creates a "Q" command, restoring the entire graphics state to its former value by popping
+ * it from the stack.
+ */
+ public void restoreGraphicsState() {
+ g2d.currentStream.write("Q\n");
+ }
+
+ /**
+ * Creates a "cm" command using the current transformation as the matrix.
+ */
+ public void concatMatrixCurrentTransform() {
+ StringBuffer sb = new StringBuffer();
+ if (!g2d.getTransform().isIdentity()) {
+ writeAffineTransform(g2d.getTransform(), sb);
+ sb.append(" cm\n");
+ }
+ g2d.currentStream.write(sb.toString());
+ }
+
+ /**
+ * Sets the current fonts for the text object. For every character, the suitable font will
+ * be selected.
+ * @param fonts the new fonts
+ */
+ public void setFonts(Font[] fonts) {
+ this.fonts = fonts;
+ }
+
+ /**
+ * Sets the current font for the text object.
+ * @param font the new font
+ */
+ public void setFont(Font font) {
+ setFonts(new Font[] {font});
+ }
+
+ /**
+ * Returns the current font in use.
+ * @return the current font or null if no font is currently active.
+ */
+ public Font getCurrentFont() {
+ return this.font;
+ }
+
+ /**
+ * Sets the current font.
+ * @param f the new font to use
+ */
+ public void setCurrentFont(Font f) {
+ this.font = f;
+ }
+
+ /**
+ * Writes a "Tf" command, setting a new current font.
+ * @param f the font to select
+ */
+ public void writeTf(Font f) {
+ checkInTextObject();
+ String fontName = f.getFontName();
+ float fontSize = (float)f.getFontSize() / 1000f;
+ g2d.currentStream.write("/" + fontName + " " + PDFNumber.doubleOut(fontSize) + " Tf\n");
+
+ this.useMultiByte = g2d.isMultiByteFont(fontName);
+ this.startText = useMultiByte ? "<" : "(";
+ this.endText = useMultiByte ? ">" : ")";
+ }
+
+ /**
+ * Sets the text rendering mode.
+ * @param mode the rendering mode (value 0 to 7, see PDF Spec, constants: TR_*)
+ */
+ public void setTextRenderingMode(int mode) {
+ if (mode < 0 || mode > 7) {
+ throw new IllegalArgumentException(
+ "Illegal value for text rendering mode. Expected: 0-7");
+ }
+ if (mode != this.textRenderingMode) {
+ this.textRenderingMode = mode;
+ g2d.currentStream.write(this.textRenderingMode + " Tr\n");
+ }
+ }
+
+ /**
+ * Sets the text rendering mode.
+ * @param fill true if the text should be filled
+ * @param stroke true if the text should be stroked
+ * @param addToClip true if the path should be added for clipping
+ */
+ public void setTextRenderingMode(boolean fill, boolean stroke, boolean addToClip) {
+ int mode;
+ if (fill) {
+ mode = (stroke ? 2 : 0);
+ } else {
+ mode = (stroke ? 1 : 3);
+ }
+ if (addToClip) {
+ mode += 4;
+ }
+ setTextRenderingMode(mode);
+ }
+
+ /**
+ * Writes a "Tm" command, setting a new text transformation matrix.
+ * @param localTransform the new text transformation matrix
+ */
+ public void writeTextMatrix(AffineTransform localTransform) {
+ StringBuffer sb = new StringBuffer();
+ writeAffineTransform(localTransform, sb);
+ sb.append(" Tm\n");
+ g2d.currentStream.write(sb.toString());
+ }
+
+ /**
+ * Selects a font from the font list suitable to display the given character.
+ * @param ch the character
+ * @return the recommended Font to use
+ */
+ 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
+ }
+
+ /**
+ * Writes a char to the "TJ-Buffer".
+ * @param ch the unmapped character
+ */
+ public void writeTJChar(char ch) {
+ if (bufTJ == null) {
+ bufTJ = new StringBuffer();
+ }
+ if (bufTJ.length() == 0) {
+ bufTJ.append("[").append(startText);
+ }
+ char mappedChar = font.mapChar(ch);
+ writeChar(mappedChar, bufTJ);
+ }
+
+ /**
+ * Writes a glyph adjust value to the "TJ-Buffer".
+ * @param adjust the glyph adjust value in thousands of text unit space.
+ */
+ public void adjustGlyphTJ(double adjust) {
+ bufTJ.append(endText).append(" ");
+ bufTJ.append(PDFNumber.doubleOut(adjust, DEC - 4));
+ bufTJ.append(" ");
+ bufTJ.append(startText);
+ }
+
+ /**
+ * Writes a "TJ" command, writing out the accumulated buffer with the characters and glyph
+ * positioning values. The buffer is reset afterwards.
+ */
+ public void writeTJ() {
+ if (bufTJ != null && bufTJ.length() > 0) {
+ bufTJ.append(endText).append("] TJ\n");
+ g2d.currentStream.write(bufTJ.toString());
+ bufTJ.setLength(0);
+ }
+ }
+
+}
diff --git a/src/java/org/apache/fop/svg/PDFTranscoder.java b/src/java/org/apache/fop/svg/PDFTranscoder.java
index e213379cb..f0d40b574 100644
--- a/src/java/org/apache/fop/svg/PDFTranscoder.java
+++ b/src/java/org/apache/fop/svg/PDFTranscoder.java
@@ -25,6 +25,7 @@ import java.io.IOException;
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;
@@ -33,10 +34,10 @@ 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.fop.Version;
import org.apache.fop.fonts.FontInfo;
-import org.apache.fop.fonts.FontSetup;
import org.w3c.dom.Document;
import org.w3c.dom.svg.SVGLength;
@@ -63,6 +64,12 @@ import org.w3c.dom.svg.SVGLength;
* <tt>KEY_USER_STYLESHEET_URI</tt> to fix the URI of a user
* stylesheet, and <tt>KEY_PIXEL_TO_MM</tt> to specify the pixel to
* millimeter conversion factor.
+ *
+ * <p><tt>KEY_AUTO_FONTS</tt> to disable the auto-detection of fonts installed in the system.
+ * The PDF Transcoder cannot use AWT's font subsystem and that's why the fonts have to be
+ * configured differently. By default, font auto-detection is enabled to match the behaviour
+ * of the other transcoders, but this may be associated with a price in the form of a small
+ * performance penalty. If font auto-detection is not desired, it can be disable using this key.
*
* @author <a href="mailto:keiron@aftexsw.com">Keiron Liddle</a>
* @version $Id$
@@ -76,13 +83,20 @@ public class PDFTranscoder extends AbstractFOPTranscoder
*/
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;
/**
- * Constructs a new <tt>ImageTranscoder</tt>.
+ * Constructs a new <tt>PDFTranscoder</tt>.
*/
public PDFTranscoder() {
super();
@@ -102,9 +116,7 @@ public class PDFTranscoder extends AbstractFOPTranscoder
};
}
- /**
- * {@inheritDoc}
- */
+ /** {@inheritDoc} */
public void configure(Configuration cfg) throws ConfigurationException {
this.cfg = cfg;
}
@@ -121,16 +133,35 @@ public class PDFTranscoder extends AbstractFOPTranscoder
TranscoderOutput output)
throws TranscoderException {
- graphics = new PDFDocumentGraphics2D();
+ graphics = new PDFDocumentGraphics2D(isTextStroked());
graphics.getPDFDocument().getInfo().setProducer("Apache FOP Version "
+ Version.getVersion()
+ ": PDF Transcoder for Batik");
try {
- if (this.cfg != null) {
+ 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;
+ }
+ }
+
+ if (effCfg != null) {
PDFDocumentGraphics2DConfigurator configurator
= new PDFDocumentGraphics2DConfigurator();
- configurator.configure(graphics, this.cfg);
+ configurator.configure(graphics, effCfg);
+ } else {
+ graphics.setupDefaultFontInfo();
}
} catch (Exception e) {
throw new TranscoderException(
@@ -190,8 +221,18 @@ public class PDFTranscoder extends AbstractFOPTranscoder
/** {@inheritDoc} */
protected BridgeContext createBridgeContext() {
- BridgeContext ctx = new PDFBridgeContext(userAgent, graphics.getFontInfo());
- return ctx;
+ //For compatibility with Batik 1.6
+ return createBridgeContext("1.x");
}
+ /** {@inheritDoc} */
+ public BridgeContext createBridgeContext(String version) {
+ FontInfo fontInfo = graphics.getFontInfo();
+ if (isTextStroked()) {
+ fontInfo = null;
+ }
+ BridgeContext ctx = new PDFBridgeContext(userAgent, fontInfo);
+ return ctx;
+ }
+
}