]> source.dussan.org Git - xmlgraphics-fop.git/commitdiff
Performance improvements and file-size reductions by introducing letter-spacing and...
authorJeremias Maerki <jeremias@apache.org>
Thu, 5 Feb 2009 16:27:08 +0000 (16:27 +0000)
committerJeremias Maerki <jeremias@apache.org>
Thu, 5 Feb 2009 16:27:08 +0000 (16:27 +0000)
Allow to control whether kerning information is loaded from fonts.
Started support for AFP font embedding (incomplete and currently disabled)

git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/branches/Temp_AreaTreeNewDesign@741165 13f79535-47bb-0310-9956-ffa450edef68

31 files changed:
src/documentation/intermediate-format-ng/fop-intermediate-format-ng-content.xsd
src/java/org/apache/fop/afp/AFPResourceManager.java
src/java/org/apache/fop/afp/fonts/AFPFont.java
src/java/org/apache/fop/afp/modca/IncludedResourceObject.java [new file with mode: 0644]
src/java/org/apache/fop/afp/util/DefaultFOPResourceAccessor.java [new file with mode: 0644]
src/java/org/apache/fop/afp/util/ResourceAccessor.java [new file with mode: 0644]
src/java/org/apache/fop/afp/util/SimpleResourceAccessor.java [new file with mode: 0644]
src/java/org/apache/fop/fonts/FontLoader.java
src/java/org/apache/fop/fonts/LazyFont.java
src/java/org/apache/fop/fonts/autodetect/FontInfoFinder.java
src/java/org/apache/fop/fonts/truetype/TTFFontLoader.java
src/java/org/apache/fop/fonts/type1/Type1FontLoader.java
src/java/org/apache/fop/pdf/PDFPaintingState.java
src/java/org/apache/fop/render/afp/AFPPainter.java
src/java/org/apache/fop/render/afp/AFPRendererConfigurator.java
src/java/org/apache/fop/render/intermediate/AbstractIFPainter.java
src/java/org/apache/fop/render/intermediate/IFPainter.java
src/java/org/apache/fop/render/intermediate/IFParser.java
src/java/org/apache/fop/render/intermediate/IFRenderer.java
src/java/org/apache/fop/render/intermediate/IFSerializer.java
src/java/org/apache/fop/render/java2d/ConfiguredFontCollection.java
src/java/org/apache/fop/render/java2d/Java2DPainter.java
src/java/org/apache/fop/render/pcl/PCLPainter.java
src/java/org/apache/fop/render/pdf/PDFContentGenerator.java
src/java/org/apache/fop/render/pdf/PDFPainter.java
src/java/org/apache/fop/render/ps/FOPProcSet.java [new file with mode: 0644]
src/java/org/apache/fop/render/ps/PSDocumentHandler.java
src/java/org/apache/fop/render/ps/PSPainter.java
src/sandbox/org/apache/fop/render/svg/SVGPainter.java
test/layoutengine/standard-testcases/block_letter-spacing.xml
test/layoutengine/standard-testcases/kerning_1_on.xml

index daedf92725cc1c573bfb49cdff0a6578ca07a129..c0bdd281b4288e8b91e16f0e7fb9977429a9199e 100644 (file)
@@ -61,6 +61,8 @@
             <xs:extension base="xs:string">
               <xs:attribute name="x" use="required" type="mf:lengthType"/>
               <xs:attribute name="y" use="required" type="mf:lengthType"/>
+              <xs:attribute name="letter-spacing" type="mf:lengthType"/>
+              <xs:attribute name="word-spacing" type="mf:lengthType"/>
               <xs:attribute name="dx">
                 <xs:simpleType>
                   <xs:list itemType="mf:lengthType"/>
index 9218ea8e442f5114813397a9081ab947f2ccaaf8..78c06bedf04c20f04dc015c46fc7147eccecb117 100644 (file)
@@ -21,19 +21,32 @@ package org.apache.fop.afp;
 
 import java.io.IOException;
 import java.io.OutputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
 import java.util.Map;
 
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
 import org.apache.fop.afp.modca.AbstractNamedAFPObject;
 import org.apache.fop.afp.modca.AbstractPageObject;
 import org.apache.fop.afp.modca.IncludeObject;
+import org.apache.fop.afp.modca.IncludedResourceObject;
 import org.apache.fop.afp.modca.PageSegment;
 import org.apache.fop.afp.modca.Registry;
 import org.apache.fop.afp.modca.ResourceGroup;
+import org.apache.fop.afp.modca.ResourceObject;
+import org.apache.fop.afp.util.ResourceAccessor;
+import org.apache.fop.afp.util.SimpleResourceAccessor;
 
 /**
  * Manages the creation and storage of document resources
  */
 public class AFPResourceManager {
+
+    /** logging instance */
+    private static Log log = LogFactory.getLog(AFPResourceManager.class);
+
     /** The AFP datastream (document tree) */
     private DataStream dataStream;
 
@@ -47,8 +60,8 @@ public class AFPResourceManager {
     /** Maintain a reference count of instream objects for referencing purposes */
     private int instreamObjectCount = 0;
 
-    /** a mapping of resourceInfo --> names of includable objects */
-    private final Map/*<AFPResourceInfo,String>*/ includableObjectsMap
+    /** a mapping of resourceInfo --> include name */
+    private final Map/*<AFPResourceInfo,String>*/ includeNameMap
         = new java.util.HashMap()/*<AFPResourceInfo,String>*/;
 
     private Map pageSegmentMap = new java.util.HashMap();
@@ -120,7 +133,7 @@ public class AFPResourceManager {
         AFPResourceInfo resourceInfo = dataObjectInfo.getResourceInfo();
         updateResourceInfoUri(resourceInfo);
 
-        String objectName = (String)includableObjectsMap.get(resourceInfo);
+        String objectName = (String)includeNameMap.get(resourceInfo);
         if (objectName != null) {
             // an existing data resource so reference it by adding an include to the current page
             includeObject(dataObjectInfo, objectName);
@@ -156,35 +169,35 @@ public class AFPResourceManager {
         useInclude &= resourceGroup != null;
         if (useInclude) {
 
-            boolean usePageSegment = dataObjectInfo.isCreatePageSegment();
-
-            // if it is to reside within a resource group at print-file or external level
-            if (resourceLevel.isPrintFile() || resourceLevel.isExternal()) {
-                if (usePageSegment) {
-                    String pageSegmentName = "S10" + namedObj.getName().substring(3);
-                    namedObj.setName(pageSegmentName);
-                    PageSegment seg = new PageSegment(pageSegmentName);
-                    seg.addObject(namedObj);
-                    namedObj = seg;
-                }
+        boolean usePageSegment = dataObjectInfo.isCreatePageSegment();
 
-                // wrap newly created data object in a resource object
-                namedObj = dataObjectFactory.createResource(namedObj, resourceInfo, objectType);
+        // if it is to reside within a resource group at print-file or external level
+        if (resourceLevel.isPrintFile() || resourceLevel.isExternal()) {
+            if (usePageSegment) {
+                String pageSegmentName = "S10" + namedObj.getName().substring(3);
+                namedObj.setName(pageSegmentName);
+                PageSegment seg = new PageSegment(pageSegmentName);
+                seg.addObject(namedObj);
+                namedObj = seg;
             }
 
-            // add data object into its resource group destination
-            resourceGroup.addObject(namedObj);
+            // wrap newly created data object in a resource object
+            namedObj = dataObjectFactory.createResource(namedObj, resourceInfo, objectType);
+        }
 
-            // create the include object
-            objectName = namedObj.getName();
-            if (usePageSegment) {
-                includePageSegment(dataObjectInfo, objectName);
-                pageSegmentMap.put(resourceInfo, objectName);
-            } else {
-                includeObject(dataObjectInfo, objectName);
-                // record mapping of resource info to data object resource name
-                includableObjectsMap.put(resourceInfo, objectName);
-            }
+        // add data object into its resource group destination
+        resourceGroup.addObject(namedObj);
+
+        // create the include object
+        objectName = namedObj.getName();
+        if (usePageSegment) {
+            includePageSegment(dataObjectInfo, objectName);
+            pageSegmentMap.put(resourceInfo, objectName);
+        } else {
+            includeObject(dataObjectInfo, objectName);
+            // record mapping of resource info to data object resource name
+            includeNameMap.put(resourceInfo, objectName);
+        }
 
         } else {
             // not to be included so inline data object directly into the current page
@@ -206,10 +219,10 @@ public class AFPResourceManager {
 
     private void includeObject(AFPDataObjectInfo dataObjectInfo,
             String objectName) {
-        IncludeObject includeObject
-            = dataObjectFactory.createInclude(objectName, dataObjectInfo);
-        dataStream.getCurrentPage().addObject(includeObject);
-    }
+            IncludeObject includeObject
+                = dataObjectFactory.createInclude(objectName, dataObjectInfo);
+            dataStream.getCurrentPage().addObject(includeObject);
+        }
 
     private void includePageSegment(AFPDataObjectInfo dataObjectInfo,
             String pageSegmentName) {
@@ -220,6 +233,53 @@ public class AFPResourceManager {
         currentPage.createIncludePageSegment(pageSegmentName, x, y, createHardPageSegments);
     }
 
+    /**
+     * Creates an included resource object by loading the contained object from a file.
+     * @param resourceName the name of the resource
+     * @param basePath the base path in which to look for the resource files
+     * @param resourceObjectType the resource object type ({@link ResourceObject}.*)
+     * @throws IOException if an I/O error occurs while loading the resource
+     */
+    public void createIncludedResource(String resourceName, String basePath,
+                byte resourceObjectType) throws IOException {
+        AFPResourceLevel resourceLevel = new AFPResourceLevel(AFPResourceLevel.PRINT_FILE);
+        URI uri;
+        try {
+            uri = new URI(resourceName.trim());
+        } catch (URISyntaxException e) {
+            throw new IOException("Could not create URI from resource name: " + resourceName
+                    + " (" + e.getMessage() + ")");
+        }
+
+        AFPResourceInfo resourceInfo = new AFPResourceInfo();
+        resourceInfo.setLevel(resourceLevel);
+        resourceInfo.setName(resourceName);
+        resourceInfo.setUri(uri.toASCIIString());
+
+        String objectName = (String)includeNameMap.get(resourceInfo);
+        if (objectName == null) {
+            if (log.isDebugEnabled()) {
+                log.debug("Adding included resource: " + resourceName);
+            }
+            //TODO This works with local filenames only. In the long term, this
+            //should work through FOP's URI resolver.
+            ResourceAccessor accessor = new SimpleResourceAccessor(basePath);
+            IncludedResourceObject resourceContent = new IncludedResourceObject(
+                        resourceName, accessor, uri);
+
+            ResourceObject resourceObject = factory.createResource(resourceName);
+            resourceObject.setDataObject(resourceContent);
+            resourceObject.setType(resourceObjectType);
+
+            ResourceGroup resourceGroup = streamer.getResourceGroup(resourceLevel);
+            resourceGroup.addObject(resourceObject);
+            // record mapping of resource info to data object resource name
+            includeNameMap.put(resourceInfo, resourceName);
+        } else {
+            //skip, already created
+        }
+    }
+
     /**
      * Sets resource level defaults. The existing defaults over merged with the ones passed in
      * as parameter.
@@ -236,4 +296,5 @@ public class AFPResourceManager {
     public AFPResourceLevelDefaults getResourceLevelDefaults() {
         return this.resourceLevelDefaults;
     }
-}
\ No newline at end of file
+
+}
index 4801737b33dc59c07c20f97fb158de0aa05de1fc..f5661108713da48767eeefea7112eebb3e81a066 100644 (file)
@@ -97,6 +97,14 @@ public abstract class AFPFont extends Typeface {
      */
     public abstract CharacterSet getCharacterSet(int size);
 
+    /**
+     * Indicates if this font may be embedded.
+     * @return True, if embedding is possible/permitted
+     */
+    public boolean isEmbeddable() {
+        return false; //TODO Complete AFP font embedding
+    }
+
     /** {@inheritDoc} */
     public String toString() {
         return "name=" + name;
diff --git a/src/java/org/apache/fop/afp/modca/IncludedResourceObject.java b/src/java/org/apache/fop/afp/modca/IncludedResourceObject.java
new file mode 100644 (file)
index 0000000..296ab2d
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * 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.afp.modca;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+
+import org.apache.commons.io.IOUtils;
+
+import org.apache.fop.afp.util.ResourceAccessor;
+
+
+/**
+ * Encapsulates an included resource object that is loaded from an external file.
+ */
+public class IncludedResourceObject extends AbstractNamedAFPObject {
+
+    private ResourceAccessor resourceAccessor;
+    private URI uri;
+
+    /**
+     * Main constructor.
+     * @param name the name of the included resource
+     * @param resourceAccessor the resource accessor to load the external file with
+     * @param uri the URI of the external file
+     */
+    public IncludedResourceObject(String name,
+            ResourceAccessor resourceAccessor, URI uri) {
+        super(name);
+        this.resourceAccessor = resourceAccessor;
+        this.uri = uri;
+    }
+
+    /** {@inheritDoc} */
+    public void writeToStream(OutputStream os) throws IOException {
+        InputStream in = resourceAccessor.createInputStream(this.uri);
+        try {
+            IOUtils.copy(in, os);
+        } finally {
+            IOUtils.closeQuietly(in);
+        }
+    }
+
+}
diff --git a/src/java/org/apache/fop/afp/util/DefaultFOPResourceAccessor.java b/src/java/org/apache/fop/afp/util/DefaultFOPResourceAccessor.java
new file mode 100644 (file)
index 0000000..55c8eab
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * 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.afp.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+
+import javax.xml.transform.Source;
+import javax.xml.transform.stream.StreamSource;
+
+import org.apache.fop.apps.FOUserAgent;
+
+/**
+ * Default implementation of the {@link ResourceAccessor} interface for use inside FOP.
+ */
+public class DefaultFOPResourceAccessor implements ResourceAccessor {
+
+    private FOUserAgent userAgent;
+
+    /**
+     * Main constructor.
+     * @param userAgent the FO user agent
+     */
+    public DefaultFOPResourceAccessor(FOUserAgent userAgent) {
+        this.userAgent = userAgent;
+    }
+
+    /** {@inheritDoc} */
+    public InputStream createInputStream(URI uri) throws IOException {
+        Source src = userAgent.resolveURI(uri.toASCIIString());
+        if (src == null) {
+            return null;
+        } else if (src instanceof StreamSource) {
+            StreamSource ss = (StreamSource)src;
+            InputStream in = ss.getInputStream();
+            return in;
+        } else {
+            return null;
+        }
+    }
+
+}
diff --git a/src/java/org/apache/fop/afp/util/ResourceAccessor.java b/src/java/org/apache/fop/afp/util/ResourceAccessor.java
new file mode 100644 (file)
index 0000000..6b9995c
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * 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.afp.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+
+/**
+ * Defines an interface through which external resource objects can be accessed.
+ */
+public interface ResourceAccessor {
+
+    /**
+     * Creates a new {@link InputStream} for the given URI that allows read access to an external
+     * resource.
+     * @param uri the URI of an external resource.
+     * @return the new input stream
+     * @throws IOException if an I/O error occurs while opening the resource
+     */
+    InputStream createInputStream(URI uri) throws IOException;
+
+}
diff --git a/src/java/org/apache/fop/afp/util/SimpleResourceAccessor.java b/src/java/org/apache/fop/afp/util/SimpleResourceAccessor.java
new file mode 100644 (file)
index 0000000..51772c2
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * 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.afp.util;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URL;
+
+/**
+ * Simple implementation of the {@link ResourceAccessor} interface for access via files.
+ */
+public class SimpleResourceAccessor implements ResourceAccessor {
+
+    private URI baseURI;
+
+    /**
+     * Creates a new simple resource accessor.
+     * @param basePath the base path to resolve relative URIs to
+     */
+    public SimpleResourceAccessor(File basePath) {
+        this.baseURI = basePath.toURI();
+    }
+
+    /**
+     * Creates a new simple resource accessor.
+     * @param basePath the base path to resolve relative URIs to
+     */
+    public SimpleResourceAccessor(String basePath) {
+        this(new File(basePath));
+    }
+
+    /** {@inheritDoc} */
+    public InputStream createInputStream(URI uri) throws IOException {
+        URI resolved = this.baseURI.resolve(uri);
+        URL url = resolved.toURL();
+        return url.openStream();
+    }
+
+}
index b3d32e38d63a9006348b9de62450a3eabf8ec5c0..aaca5b3fd9a0043db59dd8cc3af2926581567b06 100644 (file)
@@ -53,16 +53,21 @@ public abstract class FontLoader {
     protected boolean loaded = false;
     /** true if the font will be embedded, false if it will be referenced only. */
     protected boolean embedded = true;
+    /** true if kerning information shall be loaded if available. */
+    protected boolean useKerning = true;
 
     /**
      * Default constructor.
      * @param fontFileURI the URI to the PFB file of a Type 1 font
      * @param embedded indicates whether the font is embedded or referenced
+     * @param useKerning indicates whether kerning information shall be loaded if available
      * @param resolver the font resolver used to resolve URIs
      */
-    public FontLoader(String fontFileURI, boolean embedded, FontResolver resolver) {
+    public FontLoader(String fontFileURI, boolean embedded, boolean useKerning,
+            FontResolver resolver) {
         this.fontFileURI = fontFileURI;
         this.embedded = embedded;
+        this.useKerning = useKerning;
         this.resolver = resolver;
     }
 
@@ -82,7 +87,8 @@ public abstract class FontLoader {
      */
     public static CustomFont loadFont(File fontFile, String subFontName,
             boolean embedded, EncodingMode encodingMode, FontResolver resolver) throws IOException {
-        return loadFont(fontFile.getAbsolutePath(), subFontName, embedded, encodingMode, resolver);
+        return loadFont(fontFile.getAbsolutePath(), subFontName,
+                embedded, encodingMode, true, resolver);
     }
 
     /**
@@ -96,8 +102,11 @@ public abstract class FontLoader {
      * @throws IOException In case of an I/O error
      */
     public static CustomFont loadFont(URL fontUrl, String subFontName,
-            boolean embedded, EncodingMode encodingMode, FontResolver resolver) throws IOException {
-        return loadFont(fontUrl.toExternalForm(), subFontName, embedded, encodingMode, resolver);
+            boolean embedded, EncodingMode encodingMode,
+            FontResolver resolver) throws IOException {
+        return loadFont(fontUrl.toExternalForm(), subFontName,
+                embedded, encodingMode, true,
+                resolver);
     }
 
     /**
@@ -106,12 +115,14 @@ public abstract class FontLoader {
      * @param subFontName the sub-fontname of a font (for TrueType Collections, null otherwise)
      * @param embedded indicates whether the font is embedded or referenced
      * @param encodingMode the requested encoding mode
+     * @param useKerning indicates whether kerning information should be loaded if available
      * @param resolver the font resolver to use when resolving URIs
      * @return the newly loaded font
      * @throws IOException In case of an I/O error
      */
     public static CustomFont loadFont(String fontFileURI, String subFontName,
-            boolean embedded, EncodingMode encodingMode, FontResolver resolver) throws IOException {
+            boolean embedded, EncodingMode encodingMode, boolean useKerning,
+            FontResolver resolver) throws IOException {
         fontFileURI = fontFileURI.trim();
         boolean type1 = isType1(fontFileURI);
         FontLoader loader;
@@ -120,9 +131,10 @@ public abstract class FontLoader {
                 throw new IllegalArgumentException(
                         "CID encoding mode not supported for Type 1 fonts");
             }
-            loader = new Type1FontLoader(fontFileURI, embedded, resolver);
+            loader = new Type1FontLoader(fontFileURI, embedded, useKerning, resolver);
         } else {
-            loader = new TTFFontLoader(fontFileURI, subFontName, embedded, encodingMode, resolver);
+            loader = new TTFFontLoader(fontFileURI, subFontName,
+                    embedded, encodingMode, useKerning, resolver);
         }
         return loader.getFont();
     }
index c18ed6965ca635e3d1d758403ce85450e7a663dc..e5d111d382e86ec8680af80a87b66b80ce6d5067 100644 (file)
@@ -132,7 +132,7 @@ public class LazyFont extends Typeface implements FontDescriptor {
                         throw new RuntimeException("Cannot load font. No font URIs available.");
                     }
                     realFont = FontLoader.loadFont(fontEmbedPath, this.subFontName,
-                            this.embedded, this.encodingMode, resolver);
+                            this.embedded, this.encodingMode, useKerning, resolver);
                 }
                 if (realFont instanceof FontDescriptor) {
                     realFontDescriptor = (FontDescriptor) realFont;
index 951ebdcffd8b8402e8f046df8a390b2db51c55e4..03a3e1018e10350f35ef02a07a37002838d136d6 100644 (file)
@@ -225,7 +225,7 @@ public class FontInfoFinder {
                 }
                 try {
                     TTFFontLoader ttfLoader = new TTFFontLoader(
-                            fontFileURI, fontName, true, EncodingMode.AUTO, resolver);
+                            fontFileURI, fontName, true, EncodingMode.AUTO, true, resolver);
                     customFont = ttfLoader.getFont();
                     if (this.eventListener != null) {
                         customFont.setEventListener(this.eventListener);
index cbf9c9d1755af75e8826701e573f34390a85e9ca..405a25f9e88cf9726e9e86245993fb0a2c736645 100644 (file)
@@ -55,7 +55,7 @@ public class TTFFontLoader extends FontLoader {
      * @param resolver the FontResolver for font URI resolution
      */
     public TTFFontLoader(String fontFileURI, FontResolver resolver) {
-        this(fontFileURI, null, true, EncodingMode.AUTO, resolver);
+        this(fontFileURI, null, true, EncodingMode.AUTO, true, resolver);
     }
 
     /**
@@ -65,11 +65,13 @@ public class TTFFontLoader extends FontLoader {
      *          TrueType fonts)
      * @param embedded indicates whether the font is embedded or referenced
      * @param encodingMode the requested encoding mode
+     * @param useKerning true to enable loading kerning info if available, false to disable
      * @param resolver the FontResolver for font URI resolution
      */
     public TTFFontLoader(String fontFileURI, String subFontName,
-                boolean embedded, EncodingMode encodingMode, FontResolver resolver) {
-        super(fontFileURI, embedded, resolver);
+                boolean embedded, EncodingMode encodingMode, boolean useKerning,
+                FontResolver resolver) {
+        super(fontFileURI, embedded, true, resolver);
         this.subFontName = subFontName;
         this.encodingMode = encodingMode;
         if (this.encodingMode == EncodingMode.AUTO) {
@@ -164,7 +166,9 @@ public class TTFFontLoader extends FontLoader {
             copyWidthsSingleByte(ttf);
         }
 
-        copyKerning(ttf, isCid);
+        if (useKerning) {
+            copyKerning(ttf, isCid);
+        }
         if (this.embedded && ttf.isEmbeddable()) {
             returnFont.setEmbedFileName(this.fontFileURI);
         }
index 981f3ad6941fbe5d97a6c28f1c803801ac282bf8..1d0c75605dafae72254b9ee0fb9e62ab204fb156 100644 (file)
@@ -45,12 +45,13 @@ public class Type1FontLoader extends FontLoader {
      * Constructs a new Type 1 font loader.
      * @param fontFileURI the URI to the PFB file of a Type 1 font
      * @param embedded indicates whether the font is embedded or referenced
+     * @param useKerning indicates whether to load kerning information if available
      * @param resolver the font resolver used to resolve URIs
      * @throws IOException In case of an I/O error
      */
-    public Type1FontLoader(String fontFileURI, boolean embedded, FontResolver resolver)
-                throws IOException {
-        super(fontFileURI, embedded, resolver);
+    public Type1FontLoader(String fontFileURI, boolean embedded, boolean useKerning,
+            FontResolver resolver) throws IOException {
+        super(fontFileURI, embedded, useKerning, resolver);
     }
 
     private String getPFMURI(String pfbURI) {
@@ -321,7 +322,9 @@ public class Type1FontLoader extends FontLoader {
                     singleFont.setWidth(chm.getCharCode(), (int)Math.round(chm.getWidthX()));
                 }
             }
-            returnFont.replaceKerningMap(afm.createXKerningMapEncoded());
+            if (useKerning) {
+                returnFont.replaceKerningMap(afm.createXKerningMapEncoded());
+            }
         } else {
             returnFont.setFlags(pfm.getFlags());
             returnFont.setFirstChar(pfm.getFirstChar());
@@ -329,7 +332,9 @@ public class Type1FontLoader extends FontLoader {
             for (short i = pfm.getFirstChar(); i <= pfm.getLastChar(); i++) {
                 singleFont.setWidth(i, pfm.getCharWidth(i));
             }
-            returnFont.replaceKerningMap(pfm.getKerning());
+            if (useKerning) {
+                returnFont.replaceKerningMap(pfm.getKerning());
+            }
         }
     }
 
index 11dfc635a812dc3358da0a6808511eede71ff996..ebe2b383bb75ebf6e4b48d0f7416404eb3cc4547 100644 (file)
@@ -62,14 +62,15 @@ public class PDFPaintingState extends org.apache.fop.util.AbstractPaintingState
      * @return true if the new paint changes the current paint
      */
     public boolean setPaint(Paint p) {
-        Paint paint = ((PDFData)getData()).paint;
+        PDFData data = getPDFData();
+        Paint paint = data.paint;
         if (paint == null) {
             if (p != null) {
-                ((PDFData)getData()).paint = p;
+                data.paint = p;
                 return true;
             }
         } else if (!paint.equals(p)) {
-            ((PDFData)getData()).paint = p;
+            data.paint = p;
             return true;
         }
         return false;
@@ -88,7 +89,7 @@ public class PDFPaintingState extends org.apache.fop.util.AbstractPaintingState
      * @return true if the clip will change the current clip.
      */
     public boolean checkClip(Shape cl) {
-        Shape clip = ((PDFData)getData()).clip;
+        Shape clip = getPDFData().clip;
         if (clip == null) {
             if (cl != null) {
                 return true;
@@ -108,16 +109,39 @@ public class PDFPaintingState extends org.apache.fop.util.AbstractPaintingState
      * @param cl the new clip in the current state
      */
     public void setClip(Shape cl) {
-        Shape clip = ((PDFData)getData()).clip;
+        PDFData data = getPDFData();
+        Shape clip = data.clip;
         if (clip != null) {
             Area newClip = new Area(clip);
             newClip.intersect(new Area(cl));
-            ((PDFData)getData()).clip = new GeneralPath(newClip);
+            data.clip = new GeneralPath(newClip);
         } else {
-            ((PDFData)getData()).clip = cl;
+            data.clip = cl;
         }
     }
 
+    /**
+     * Sets the character spacing (Tc).
+     * @param value the new value
+     * @return true if the value was changed with respect to the previous value
+     */
+    public boolean setCharacterSpacing(float value) {
+        PDFData data = getPDFData();
+        if (value != data.characterSpacing) {
+            data.characterSpacing = value;
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Returns the current character spacing (Tc) value.
+     * @return the Tc value
+     */
+    public float getCharacterSpacing() {
+        return getPDFData().characterSpacing;
+    }
+
     /**
      * Get the current stack level.
      *
@@ -149,8 +173,8 @@ public class PDFPaintingState extends org.apache.fop.util.AbstractPaintingState
                 newState.addValues(state);
             }
         }
-        if (((PDFData)getData()).gstate != null) {
-            newState.addValues(((PDFData)getData()).gstate);
+        if (getPDFData().gstate != null) {
+            newState.addValues(getPDFData().gstate);
         }
         return newState;
     }
@@ -177,32 +201,38 @@ public class PDFPaintingState extends org.apache.fop.util.AbstractPaintingState
         getStateStack().add(copy);
     }
 
+    private PDFData getPDFData() {
+        return (PDFData)getData();
+    }
+
     private class PDFData extends org.apache.fop.util.AbstractPaintingState.AbstractData {
 
         private static final long serialVersionUID = 3527950647293177764L;
 
         private Paint paint = null;
         private Paint backPaint = null;
-        private int lineCap = 0;
-        private int lineJoin = 0;
-        private float miterLimit = 0;
-        private boolean text = false;
-        private int dashOffset = 0;
+        //private int lineCap = 0; //Disabled the ones that are not used, yet
+        //private int lineJoin = 0;
+        //private float miterLimit = 0;
+        //private int dashOffset = 0;
         private Shape clip = null;
         private PDFGState gstate = null;
 
+        //text state
+        private float characterSpacing = 0f;
+
         /** {@inheritDoc} */
         public Object clone() {
             PDFData obj = (PDFData)super.clone();
             obj.paint = this.paint;
             obj.backPaint = this.paint;
-            obj.lineCap = this.lineCap;
-            obj.lineJoin = this.lineJoin;
-            obj.miterLimit = this.miterLimit;
-            obj.text = this.text;
-            obj.dashOffset = this.dashOffset;
+            //obj.lineCap = this.lineCap;
+            //obj.lineJoin = this.lineJoin;
+            //obj.miterLimit = this.miterLimit;
+            //obj.dashOffset = this.dashOffset;
             obj.clip = this.clip;
             obj.gstate = this.gstate;
+            obj.characterSpacing = this.characterSpacing;
             return obj;
         }
 
@@ -211,10 +241,9 @@ public class PDFPaintingState extends org.apache.fop.util.AbstractPaintingState
             return super.toString()
                 + ", paint=" + paint
                 + ", backPaint=" + backPaint
-                + ", lineCap=" + lineCap
-                + ", miterLimit=" + miterLimit
-                + ", text=" + text
-                + ", dashOffset=" + dashOffset
+                //+ ", lineCap=" + lineCap
+                //+ ", miterLimit=" + miterLimit
+                //+ ", dashOffset=" + dashOffset
                 + ", clip=" + clip
                 + ", gstate=" + gstate;
         }
index d22ea83269b24f695bed559f5663a34b0bab1514..0c2501b870c5940e87a2d26ca9dc45b408696d46 100644 (file)
@@ -30,9 +30,6 @@ import java.util.Map;
 
 import org.w3c.dom.Document;
 
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
 import org.apache.xmlgraphics.image.loader.ImageProcessingHints;
 import org.apache.xmlgraphics.image.loader.ImageSessionContext;
 
@@ -49,6 +46,7 @@ import org.apache.fop.afp.fonts.AFPPageFonts;
 import org.apache.fop.afp.fonts.CharacterSet;
 import org.apache.fop.afp.modca.AbstractPageObject;
 import org.apache.fop.afp.modca.PresentationTextObject;
+import org.apache.fop.afp.modca.ResourceObject;
 import org.apache.fop.afp.ptoca.PtocaBuilder;
 import org.apache.fop.afp.ptoca.PtocaProducer;
 import org.apache.fop.fonts.Font;
@@ -69,8 +67,8 @@ import org.apache.fop.util.CharUtilities;
  */
 public class AFPPainter extends AbstractIFPainter {
 
-    /** logging instance */
-    private static Log log = LogFactory.getLog(AFPPainter.class);
+    //** logging instance */
+    //private static Log log = LogFactory.getLog(AFPPainter.class);
 
     private static final int X = 0;
     private static final int Y = 1;
@@ -241,7 +239,7 @@ public class AFPPainter extends AbstractIFPainter {
 
     //TODO Try to resolve the name-clash between the AFPBorderPainter in the afp package
     //and this one. Not done for now to avoid a lot of re-implementation and code duplication.
-    private class AFPBorderPainterAdapter extends BorderPainter {
+    private static class AFPBorderPainterAdapter extends BorderPainter {
 
         private AFPBorderPainter delegate;
 
@@ -312,8 +310,9 @@ public class AFPPainter extends AbstractIFPainter {
     }
 
     /** {@inheritDoc} */
-    public void drawText(int x, int y, final int[] dx, int[] dy, final String text)
-            throws IFException {
+    public void drawText(int x, int y,
+            final int letterSpacing, final int wordSpacing, final int[] dx,
+            final String text) throws IFException {
         final int fontSize = this.state.getFontSize();
         getPaintingState().setFontSize(fontSize);
 
@@ -322,8 +321,8 @@ public class AFPPainter extends AbstractIFPainter {
         //TODO Ignored: state.getFontVariant()
         String fontKey = getFontInfo().getInternalFontKey(triplet);
         if (fontKey == null) {
-            fontKey = getFontInfo().getInternalFontKey(
-                    new FontTriplet("any", Font.STYLE_NORMAL, Font.WEIGHT_NORMAL));
+            triplet = new FontTriplet("any", Font.STYLE_NORMAL, Font.WEIGHT_NORMAL);
+            fontKey = getFontInfo().getInternalFontKey(triplet);
         }
 
         // register font as necessary
@@ -339,6 +338,23 @@ public class AFPPainter extends AbstractIFPainter {
 
         final CharacterSet charSet = afpFont.getCharacterSet(fontSize);
 
+        if (afpFont.isEmbeddable()) {
+            try {
+                //Embed fonts (char sets and code pages)
+                //TODO This should be moved to a place where it has less performance impact
+                if (charSet.getPath() != null) {
+                    documentHandler.getResourceManager().createIncludedResource(
+                            charSet.getName(), charSet.getPath(),
+                            ResourceObject.TYPE_FONT_CHARACTER_SET);
+                    documentHandler.getResourceManager().createIncludedResource(
+                            charSet.getCodePage(), charSet.getPath(),
+                            ResourceObject.TYPE_CODE_PAGE);
+                }
+            } catch (IOException ioe) {
+                throw new IFException("Error while embedding font resources", ioe);
+            }
+        }
+
         AbstractPageObject page = getDataStream().getCurrentPage();
         PresentationTextObject pto = page.getPresentationTextObject();
         try {
@@ -350,8 +366,6 @@ public class AFPPainter extends AbstractIFPainter {
                     builder.absoluteMoveBaseline(p.y);
                     builder.absoluteMoveInline(p.x);
 
-                    builder.setVariableSpaceCharacterIncrement(0);
-                    builder.setInterCharacterAdjustment(0);
                     builder.setExtendedTextColor(state.getTextColor());
                     builder.setCodedFont((byte)fontReference);
 
@@ -363,33 +377,105 @@ public class AFPPainter extends AbstractIFPainter {
                         int dxu = Math.round(unitConv.mpt2units(dx[0]));
                         builder.relativeMoveInline(-dxu);
                     }
-                    for (int i = 0; i < l; i++) {
-                        char orgChar = text.charAt(i);
-                        float glyphAdjust = 0;
-                        if (CharUtilities.isFixedWidthSpace(orgChar)) {
-                            sb.append(CharUtilities.SPACE);
-                            int spaceWidth = font.getCharWidth(CharUtilities.SPACE);
-                            int charWidth = font.getCharWidth(orgChar);
-                            glyphAdjust += (charWidth - spaceWidth);
-                        } else {
-                            sb.append(orgChar);
+
+                    //Following are two variants for glyph placement.
+                    //SVI does not seem to be implemented in the same way everywhere, so
+                    //a fallback alternative is preserved here.
+                    final boolean usePTOCAWordSpacing = true;
+                    if (usePTOCAWordSpacing) {
+
+                        int interCharacterAdjustment = 0;
+                        if (letterSpacing != 0) {
+                            interCharacterAdjustment = Math.round(unitConv.mpt2units(
+                                    letterSpacing));
+                        }
+                        builder.setInterCharacterAdjustment(interCharacterAdjustment);
+
+                        int spaceWidth = font.getCharWidth(CharUtilities.SPACE);
+                        int fixedSpaceCharacterIncrement = Math.round(unitConv.mpt2units(
+                                spaceWidth + letterSpacing));
+                        int varSpaceCharacterIncrement = fixedSpaceCharacterIncrement;
+                        if (wordSpacing != 0) {
+                            varSpaceCharacterIncrement = Math.round(unitConv.mpt2units(
+                                    spaceWidth + wordSpacing + letterSpacing));
                         }
+                        builder.setVariableSpaceCharacterIncrement(varSpaceCharacterIncrement);
+
+                        boolean fixedSpaceMode = false;
+
+                        for (int i = 0; i < l; i++) {
+                            char orgChar = text.charAt(i);
+                            float glyphAdjust = 0;
+                            if (CharUtilities.isFixedWidthSpace(orgChar)) {
+                                flushText(builder, sb, charSet);
+                                builder.setVariableSpaceCharacterIncrement(
+                                        fixedSpaceCharacterIncrement);
+                                fixedSpaceMode = true;
+                                sb.append(CharUtilities.SPACE);
+                                int charWidth = font.getCharWidth(orgChar);
+                                glyphAdjust += (charWidth - spaceWidth);
+                            } else {
+                                if (fixedSpaceMode) {
+                                    flushText(builder, sb, charSet);
+                                    builder.setVariableSpaceCharacterIncrement(
+                                            varSpaceCharacterIncrement);
+                                    fixedSpaceMode = false;
+                                }
+                                char ch;
+                                if (orgChar == CharUtilities.NBSPACE) {
+                                    ch = ' '; //converted to normal space to allow word spacing
+                                } else {
+                                    ch = orgChar;
+                                }
+                                sb.append(ch);
+                            }
+
+                            if (i < dxl - 1) {
+                                glyphAdjust += dx[i + 1];
+                            }
 
-                        if (i < dxl - 1) {
-                            glyphAdjust += dx[i + 1];
+                            if (glyphAdjust != 0) {
+                                flushText(builder, sb, charSet);
+                                int increment = Math.round(unitConv.mpt2units(glyphAdjust));
+                                builder.relativeMoveInline(increment);
+                            }
                         }
+                    } else {
+                        for (int i = 0; i < l; i++) {
+                            char orgChar = text.charAt(i);
+                            float glyphAdjust = 0;
+                            if (CharUtilities.isFixedWidthSpace(orgChar)) {
+                                sb.append(CharUtilities.SPACE);
+                                int spaceWidth = font.getCharWidth(CharUtilities.SPACE);
+                                int charWidth = font.getCharWidth(orgChar);
+                                glyphAdjust += (charWidth - spaceWidth);
+                            } else {
+                                sb.append(orgChar);
+                            }
 
-                        if (glyphAdjust != 0) {
-                            if (sb.length() > 0) {
-                                builder.addTransparentData(charSet.encodeChars(sb));
-                                sb.setLength(0);
+                            if ((wordSpacing != 0) && CharUtilities.isAdjustableSpace(orgChar)) {
+                                glyphAdjust += wordSpacing;
+                            }
+                            glyphAdjust += letterSpacing;
+                            if (i < dxl - 1) {
+                                glyphAdjust += dx[i + 1];
+                            }
+
+                            if (glyphAdjust != 0) {
+                                flushText(builder, sb, charSet);
+                                int increment = Math.round(unitConv.mpt2units(glyphAdjust));
+                                builder.relativeMoveInline(increment);
                             }
-                            int increment = Math.round(unitConv.mpt2units(glyphAdjust));
-                            builder.relativeMoveInline(increment);
                         }
                     }
+                    flushText(builder, sb, charSet);
+                }
+
+                private void flushText(PtocaBuilder builder, StringBuffer sb,
+                        final CharacterSet charSet) throws IOException {
                     if (sb.length() > 0) {
                         builder.addTransparentData(charSet.encodeChars(sb));
+                        sb.setLength(0);
                     }
                 }
 
index fffba85a18823abe2ff8d09a890859b0f6a42777..bd6df613679226f625bfc10ddf7cdbe1acdc0778 100644 (file)
@@ -345,7 +345,8 @@ public class AFPRendererConfigurator extends PrintRendererConfigurator
                 LogUtil.handleException(log, e,
                         userAgent.getFactory().validateUserConfigStrictly());
             }
-
+        } else {
+            fontCollections.add(new AFPFontCollection(userAgent.getEventBroadcaster(), null));
         }
 
         fontManager.setup(fontInfo,
index 82ea18d00d1d8c164d565968764cae93727e5de4..7f6cce05a73ad408cad7d6eb3b6eb4029b14e73e 100644 (file)
@@ -411,5 +411,4 @@ public abstract class AbstractIFPainter implements IFPainter {
         return new AffineTransform(matrix);
     }
 
-
 }
index d60cd24d227ca48b425aa08c689f18feba1e9b45..6369b02510520f4bf9adc6bc6a25c5d0e5c6041e 100644 (file)
@@ -81,13 +81,53 @@ import org.apache.fop.traits.RuleStyle;
  */
 public interface IFPainter {
 
-    void startViewport(AffineTransform transform, Dimension size, Rectangle clipRect) throws IFException;
-    void startViewport(AffineTransform[] transforms, Dimension size, Rectangle clipRect) throws IFException;
+    /**
+     * Starts a new viewport, establishing a new coordinate system. A viewport has a size and
+     * can optionally be clipped. Corresponds to SVG's svg element.
+     * @param transform the transformation matrix establishing the new coordinate system
+     * @param size the size of the viewport
+     * @param clipRect the clipping rectangle (may be null)
+     * @throws IFException if an error occurs while handling this element
+     */
+    void startViewport(AffineTransform transform, Dimension size, Rectangle clipRect)
+        throws IFException;
+
+    /**
+     * Starts a new viewport, establishing a new coordinate system. A viewport has a size and
+     * can optionally be clipped. Corresponds to SVG's svg element.
+     * @param transforms a series of transformation matrices establishing the new coordinate system
+     * @param size the size of the viewport
+     * @param clipRect the clipping rectangle (may be null)
+     * @throws IFException if an error occurs while handling this element
+     */
+    void startViewport(AffineTransform[] transforms, Dimension size, Rectangle clipRect)
+        throws IFException;
     //For transform, Batik's org.apache.batik.parser.TransformListHandler/Parser can be used
+
+    /**
+     * Ends the current viewport and restores the previous coordinate system.
+     * @throws IFException if an error occurs while handling this element
+     */
     void endViewport() throws IFException;
 
+    /**
+     * Starts a new group of graphical elements. Corresponds to SVG's g element.
+     * @param transforms a series of transformation matrices establishing the new coordinate system
+     * @throws IFException if an error occurs while handling this element
+     */
     void startGroup(AffineTransform[] transforms) throws IFException;
+
+    /**
+     * Starts a new group of graphical elements. Corresponds to SVG's g element.
+     * @param transform the transformation matrix establishing the new coordinate system
+     * @throws IFException if an error occurs while handling this element
+     */
     void startGroup(AffineTransform transform) throws IFException;
+
+    /**
+     * Ends the current group and restores the previous coordinate system.
+     * @throws IFException if an error occurs while handling this element
+     */
     void endGroup() throws IFException;
 
     /**
@@ -105,16 +145,18 @@ public interface IFPainter {
 
     /**
      * Draws text. The initial coordinates (x and y) point to the starting point at the normal
-     * baseline of the font. The arrays (dx and dy) are optional and can be used to achieve
-     * effects like kerning.
+     * baseline of the font. The parameters letterSpacing, wordSpacing and the array dx are
+     * optional and can be used to influence character positioning (for example, for kerning).
      * @param x X-coordinate of the starting point of the text
      * @param y Y-coordinate of the starting point of the text
-     * @param dx an array of adjustment values for each character in X-direction
-     * @param dy an array of adjustment values for each character in Y-direction
+     * @param letterSpacing additional spacing between characters (may be 0)
+     * @param wordSpacing additional spacing between words (may be 0)
+     * @param dx an array of adjustment values for each character in X-direction (may be null)
      * @param text the text
      * @throws IFException if an error occurs while handling this event
      */
-    void drawText(int x, int y, int[] dx, int[] dy, String text) throws IFException;
+    void drawText(int x, int y, int letterSpacing, int wordSpacing,
+            int[] dx, String text) throws IFException;
 
     /**
      * Restricts the current clipping region with the given rectangle.
index c5c88ca783281151ff4d56f25174f11fadd57734..61d1838bca91a66232f0546855981c3ffb5acbbd 100644 (file)
@@ -462,9 +462,12 @@ public class IFParser implements IFConstants {
             public void endElement() throws IFException {
                 int x = Integer.parseInt(lastAttributes.getValue("x"));
                 int y = Integer.parseInt(lastAttributes.getValue("y"));
+                String s = lastAttributes.getValue("letter-spacing");
+                int letterSpacing = (s != null ? Integer.parseInt(s) : 0);
+                s = lastAttributes.getValue("word-spacing");
+                int wordSpacing = (s != null ? Integer.parseInt(s) : 0);
                 int[] dx = XMLUtil.getAttributeAsIntArray(lastAttributes, "dx");
-                int[] dy = XMLUtil.getAttributeAsIntArray(lastAttributes, "dy");
-                painter.drawText(x, y, dx, dy, content.toString());
+                painter.drawText(x, y, letterSpacing, wordSpacing, dx, content.toString());
             }
 
             public boolean ignoreCharacters() {
index abef34e227c9ad36e62058f7447b743f52ff9ecf..03ec6a4c05286cd6eb0d1dba7f2de49bfac5000c 100644 (file)
@@ -984,6 +984,7 @@ public class IFRenderer extends AbstractPathOrientedRenderer {
         int bl = currentBPPosition + text.getOffset() + text.getBaselineOffset();
         textUtil.flush();
         textUtil.setStartPosition(rx, bl);
+        textUtil.setSpacing(text.getTextLetterSpaceAdjust(), text.getTextWordSpaceAdjust());
         super.renderText(text);
 
         textUtil.flush();
@@ -1009,9 +1010,9 @@ public class IFRenderer extends AbstractPathOrientedRenderer {
         AbstractTextArea textArea = (AbstractTextArea)space.getParentArea();
         renderText(s, null, font, textArea);
 
-        if (space.isAdjustable()) {
+        if (textUtil.combined && space.isAdjustable()) {
             //Used for justified text, for example
-            int tws = ((TextArea) space.getParentArea()).getTextWordSpaceAdjust()
+            int tws = textArea.getTextWordSpaceAdjust()
                          + 2 * textArea.getTextLetterSpaceAdjust();
             if (tws != 0) {
                 textUtil.adjust(tws);
@@ -1042,7 +1043,7 @@ public class IFRenderer extends AbstractPathOrientedRenderer {
             char ch = s.charAt(i);
             textUtil.addChar(ch);
             int glyphAdjust = 0;
-            if (font.hasChar(ch)) {
+            if (textUtil.combined && font.hasChar(ch)) {
                 int tls = (i < l - 1 ? parentArea.getTextLetterSpaceAdjust() : 0);
                 glyphAdjust += tls;
             }
@@ -1060,6 +1061,8 @@ public class IFRenderer extends AbstractPathOrientedRenderer {
         private int lastDXPos = 0;
         private StringBuffer text = new StringBuffer();
         private int startx, starty;
+        private int tls, tws;
+        private boolean combined = false;
 
         void addChar(char ch) {
             text.append(ch);
@@ -1092,6 +1095,11 @@ public class IFRenderer extends AbstractPathOrientedRenderer {
             this.starty = y;
         }
 
+        void setSpacing(int tls, int tws) {
+            this.tls = tls;
+            this.tws = tws;
+        }
+
         void flush() {
             if (text.length() > 0) {
                 try {
@@ -1101,7 +1109,11 @@ public class IFRenderer extends AbstractPathOrientedRenderer {
                         effDX = new int[size];
                         System.arraycopy(dx, 0, effDX, 0, size);
                     }
-                    painter.drawText(startx, starty, effDX, null, text.toString());
+                    if (combined) {
+                        painter.drawText(startx, starty, 0, 0, effDX, text.toString());
+                    } else {
+                        painter.drawText(startx, starty, tls, tws, effDX, text.toString());
+                    }
                 } catch (IFException e) {
                     handleIFException(e);
                 }
index 50bc5f7de32bf154675a59accf64094f9392b221..e4aa86e9e660e2ed00d149b87b3e53fcf0881492 100644 (file)
@@ -508,18 +508,22 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler
     }
 
     /** {@inheritDoc} */
-    public void drawText(int x, int y, int[] dx, int[] dy, String text) throws IFException {
+    public void drawText(int x, int y, int letterSpacing, int wordSpacing,
+            int[] dx, String text) throws IFException {
         try {
             AttributesImpl atts = new AttributesImpl();
             XMLUtil.addAttribute(atts, XMLConstants.XML_SPACE, "preserve");
             addAttribute(atts, "x", Integer.toString(x));
             addAttribute(atts, "y", Integer.toString(y));
+            if (letterSpacing != 0) {
+                addAttribute(atts, "letter-spacing", Integer.toString(letterSpacing));
+            }
+            if (wordSpacing != 0) {
+                addAttribute(atts, "word-spacing", Integer.toString(wordSpacing));
+            }
             if (dx != null) {
                 addAttribute(atts, "dx", IFUtil.toString(dx));
             }
-            if (dy != null) {
-                addAttribute(atts, "dy", IFUtil.toString(dy));
-            }
             handler.startElement(EL_TEXT, atts);
             char[] chars = text.toCharArray();
             handler.characters(chars, 0, chars.length);
index 9f96087b956dcbea3ad6b6cdfa65e3fed64eeaeb..29570e69f057e9525b4d194c771ed11e7dc5bba2 100644 (file)
@@ -88,7 +88,8 @@ public class ConfiguredFontCollection implements FontCollection {
                     font = new CustomFontMetricsMapper(fontMetrics, fontSource);
                 } else {
                     CustomFont fontMetrics = FontLoader.loadFont(
-                            fontFile, null, true, EncodingMode.AUTO, fontResolver);
+                            fontFile, null, true, EncodingMode.AUTO,
+                            configFontInfo.getKerning(), fontResolver);
                     font = new CustomFontMetricsMapper(fontMetrics);
                 }
 
index c32df4f385981d8bfdcb83507c55ad1fb8c4828c..55c5b80151dc2a5f82b4fe5ae1e6843eef969836 100644 (file)
@@ -46,6 +46,7 @@ import org.apache.fop.render.intermediate.IFException;
 import org.apache.fop.render.intermediate.IFState;
 import org.apache.fop.traits.BorderProps;
 import org.apache.fop.traits.RuleStyle;
+import org.apache.fop.util.CharUtilities;
 
 /**
  * {@code IFPainter} implementation that paints on a Graphics2D instance.
@@ -83,6 +84,7 @@ public class Java2DPainter extends AbstractIFPainter {
      * @param g2d the target Graphics2D instance
      * @param context the IF context
      * @param fontInfo the font information
+     * @param state the IF state object
      */
     public Java2DPainter(Graphics2D g2d, IFContext context, FontInfo fontInfo, IFState state) {
         super();
@@ -206,8 +208,8 @@ public class Java2DPainter extends AbstractIFPainter {
     }
 
     /** {@inheritDoc} */
-    public void drawText(int x, int y, int[] dx, int[] dy, String text) throws IFException {
-        //Note: dy is currently ignored
+    public void drawText(int x, int y, int letterSpacing, int wordSpacing, int[] dx, String text)
+            throws IFException {
         g2dState.updateColor(state.getTextColor());
         FontTriplet triplet = new FontTriplet(
                 state.getFontFamily(), state.getFontStyle(), state.getFontWeight());
@@ -234,6 +236,10 @@ public class Java2DPainter extends AbstractIFPainter {
             float glyphAdjust = 0;
             int cw = font.getCharWidth(orgChar);
 
+            if ((wordSpacing != 0) && CharUtilities.isAdjustableSpace(orgChar)) {
+                glyphAdjust += wordSpacing;
+            }
+            glyphAdjust += letterSpacing;
             if (dx != null && i < dxl - 1) {
                 glyphAdjust += dx[i + 1];
             }
index 03e40d54971474e23ff03c69dcc2c944991830b9..da4f6a656bb57ad3c1702867f8f617c5ac8e4c75 100644 (file)
@@ -312,8 +312,8 @@ public class PCLPainter extends AbstractIFPainter implements PCLConstants {
     }
 
     /** {@inheritDoc} */
-    public void drawText(int x, int y, int[] dx, int[] dy, String text) throws IFException {
-        //Note: dy is currently ignored
+    public void drawText(int x, int y, int letterSpacing, int wordSpacing, int[] dx, String text)
+                throws IFException {
         try {
             FontTriplet triplet = new FontTriplet(
                     state.getFontFamily(), state.getFontStyle(), state.getFontWeight());
@@ -324,13 +324,13 @@ public class PCLPainter extends AbstractIFPainter implements PCLConstants {
                         ? false
                         : HardcodedFonts.setFont(gen, fontKey, state.getFontSize(), text);
             if (pclFont) {
-                drawTextNative(x, y, dx, text, triplet);
+                drawTextNative(x, y, letterSpacing, wordSpacing, dx, text, triplet);
             } else {
-                drawTextAsBitmap(x, y, dx, dy, text, triplet);
+                drawTextAsBitmap(x, y, letterSpacing, wordSpacing, dx, text, triplet);
                 if (DEBUG) {
                     state.setTextColor(Color.GRAY);
                     HardcodedFonts.setFont(gen, "F1", state.getFontSize(), text);
-                    drawTextNative(x, y, dx, text, triplet);
+                    drawTextNative(x, y, letterSpacing, wordSpacing, dx, text, triplet);
                 }
             }
         } catch (IOException ioe) {
@@ -338,8 +338,8 @@ public class PCLPainter extends AbstractIFPainter implements PCLConstants {
         }
     }
 
-    private void drawTextNative(int x, int y, int[] dx, String text, FontTriplet triplet)
-                throws IOException {
+    private void drawTextNative(int x, int y, int letterSpacing, int wordSpacing, int[] dx,
+            String text, FontTriplet triplet) throws IOException {
         Color textColor = state.getTextColor();
         if (textColor != null) {
             gen.setTransparencyMode(true, false);
@@ -376,6 +376,10 @@ public class PCLPainter extends AbstractIFPainter implements PCLConstants {
             }
             sb.append(ch);
 
+            if ((wordSpacing != 0) && CharUtilities.isAdjustableSpace(orgChar)) {
+                glyphAdjust += wordSpacing;
+            }
+            glyphAdjust += letterSpacing;
             if (dx != null && i < dxl - 1) {
                 glyphAdjust += dx[i + 1];
             }
@@ -391,7 +395,9 @@ public class PCLPainter extends AbstractIFPainter implements PCLConstants {
 
     private static final double SAFETY_MARGIN_FACTOR = 0.05;
 
-    private Rectangle getTextBoundingBox(int x, int y, int[] dx, int[] dy, String text,
+    private Rectangle getTextBoundingBox(int x, int y,
+            int letterSpacing, int wordSpacing, int[] dx,
+            String text,
             Font font, FontMetricsMapper metrics) {
         int maxAscent = metrics.getMaxAscent(font.getFontSize()) / 1000;
         int descent = metrics.getDescender(font.getFontSize()) / 1000; //is negative
@@ -412,6 +418,10 @@ public class PCLPainter extends AbstractIFPainter implements PCLConstants {
             float glyphAdjust = 0;
             int cw = font.getCharWidth(orgChar);
 
+            if ((wordSpacing != 0) && CharUtilities.isAdjustableSpace(orgChar)) {
+                glyphAdjust += wordSpacing;
+            }
+            glyphAdjust += letterSpacing;
             if (dx != null && i < dxl - 1) {
                 glyphAdjust += dx[i + 1];
             }
@@ -425,7 +435,8 @@ public class PCLPainter extends AbstractIFPainter implements PCLConstants {
         return boundingRect;
     }
 
-    private void drawTextAsBitmap(final int x, final int y, final int[] dx, final int[] dy,
+    private void drawTextAsBitmap(final int x, final int y,
+            final int letterSpacing, final int wordSpacing, final int[] dx,
             final String text, FontTriplet triplet) throws IFException {
         //Use Java2D to paint different fonts via bitmap
         final Font font = parent.getFontInfo().getFontInstance(triplet, state.getFontSize());
@@ -439,7 +450,8 @@ public class PCLPainter extends AbstractIFPainter implements PCLConstants {
         int safetyMargin = (int)(SAFETY_MARGIN_FACTOR * font.getFontSize());
         final int baselineOffset = maxAscent + safetyMargin;
 
-        final Rectangle boundingBox = getTextBoundingBox(x, y, dx, dy, text, font, mapper);
+        final Rectangle boundingBox = getTextBoundingBox(x, y,
+                letterSpacing, wordSpacing, dx, text, font, mapper);
         final Dimension dim = boundingBox.getSize();
 
         Graphics2DImagePainter painter = new Graphics2DImagePainter() {
@@ -462,7 +474,7 @@ public class PCLPainter extends AbstractIFPainter implements PCLConstants {
                 Java2DPainter painter = new Java2DPainter(g2d,
                         getContext(), parent.getFontInfo(), state);
                 try {
-                    painter.drawText(x, y, dx, dy, text);
+                    painter.drawText(x, y, letterSpacing, wordSpacing, dx, text);
                 } catch (IFException e) {
                     //This should never happen with the Java2DPainter
                     throw new RuntimeException("Unexpected error while painting text", e);
index 567ef050765a3fc9380d6ac9ec35d29e176adf36..45fb7ec5111820e129dda1d8bada682c02fa4ccd 100644 (file)
@@ -237,6 +237,16 @@ public class PDFContentGenerator {
         }
     }
 
+    /**
+     * Sets the current character spacing (Tc) value.
+     * @param value the Tc value (in unscaled text units)
+     */
+    public void updateCharacterSpacing(float value) {
+        if (getState().setCharacterSpacing(value)) {
+            currentStream.add(format(value) + " Tc\n");
+        }
+    }
+
     /**
      * Establishes a new foreground or fill color.
      * @param col the color to apply
index 6386b8df6b77f2983a967b2aeba37ce2f5fea640..fa00fd7b41b5f9f4e3784336bf380fb4812ae29d 100644 (file)
@@ -253,8 +253,8 @@ public class PDFPainter extends AbstractIFPainter {
     }
 
     /** {@inheritDoc} */
-    public void drawText(int x, int y, int[] dx, int[] dy, String text) throws IFException {
-        //Note: dy is currently ignored
+    public void drawText(int x, int y, int letterSpacing, int wordSpacing, int[] dx, String text)
+            throws IFException {
         generator.updateColor(state.getTextColor(), true, null);
         generator.beginTextObject();
         FontTriplet triplet = new FontTriplet(
@@ -277,6 +277,8 @@ public class PDFPainter extends AbstractIFPainter {
         PDFTextUtil textutil = generator.getTextUtil();
         textutil.updateTf(fontKey, fontSize, tf.isMultiByte());
 
+        generator.updateCharacterSpacing((float)letterSpacing / 1000f);
+
         textutil.writeTextMatrix(new AffineTransform(1, 0, 0, -1, x / 1000f, y / 1000f));
         int l = text.length();
         int dxl = (dx != null ? dx.length : 0);
@@ -300,6 +302,9 @@ public class PDFPainter extends AbstractIFPainter {
                         ch = (char)(ch % 256);
                     }
                 }
+                if ((wordSpacing != 0) && CharUtilities.isAdjustableSpace(orgChar)) {
+                    glyphAdjust += wordSpacing;
+                }
             } else {
                 if (CharUtilities.isFixedWidthSpace(orgChar)) {
                     //Fixed width space are rendered as spaces so copy/paste works in a reader
@@ -308,6 +313,9 @@ public class PDFPainter extends AbstractIFPainter {
                     glyphAdjust = -spaceDiff;
                 } else {
                     ch = font.mapChar(orgChar);
+                    if ((wordSpacing != 0) && CharUtilities.isAdjustableSpace(orgChar)) {
+                        glyphAdjust += wordSpacing;
+                    }
                 }
             }
             textutil.writeTJMappedChar(ch);
diff --git a/src/java/org/apache/fop/render/ps/FOPProcSet.java b/src/java/org/apache/fop/render/ps/FOPProcSet.java
new file mode 100644 (file)
index 0000000..99b62a6
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * 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.io.IOException;
+
+import org.apache.xmlgraphics.ps.DSCConstants;
+import org.apache.xmlgraphics.ps.PSGenerator;
+import org.apache.xmlgraphics.ps.PSProcSet;
+
+/**
+ * Proc Set with FOP-specific procs.
+ */
+public final class FOPProcSet extends PSProcSet {
+
+    /** Singleton instance of the FOP procset */
+    public static final FOPProcSet INSTANCE = new FOPProcSet();
+
+    private FOPProcSet() {
+        super("Apache FOP Std ProcSet", 1.0f, 0);
+    }
+
+    /**
+     * Writes the procset to the PostScript file.
+     * @param gen the PS generator
+     * @throws IOException if an I/O error occurs
+     */
+    public void writeTo(PSGenerator gen) throws IOException {
+        gen.writeDSCComment(DSCConstants.BEGIN_RESOURCE,
+                new Object[] {TYPE_PROCSET, getName(),
+                    Float.toString(getVersion()), Integer.toString(getRevision())});
+        gen.writeDSCComment(DSCConstants.VERSION,
+                new Object[] {Float.toString(getVersion()), Integer.toString(getRevision())});
+        gen.writeDSCComment(DSCConstants.COPYRIGHT, "Copyright 2009 "
+                    + "The Apache Software Foundation. "
+                    + "License terms: http://www.apache.org/licenses/LICENSE-2.0");
+        gen.writeDSCComment(DSCConstants.TITLE,
+                "Basic set of procedures used by Apache FOP");
+
+
+        gen.writeln("/TJ { % Similar but not equal to PDF's TJ operator");
+        gen.writeln("  {");
+        gen.writeln("    dup type /stringtype eq");
+        gen.writeln("    { show }"); //normal text show
+        gen.writeln("    { neg 1000 div 0 rmoveto }"); //negative X movement
+        gen.writeln("    ifelse");
+        gen.writeln("  } forall");
+        gen.writeln("} bd");
+
+        gen.writeln("/ATJ { % As TJ but adds letter-spacing");
+        gen.writeln("  /ATJls exch def");
+        gen.writeln("  {");
+        gen.writeln("    dup type /stringtype eq");
+        gen.writeln("    { ATJls 0 3 2 roll ashow }"); //normal text show
+        gen.writeln("    { neg 1000 div 0 rmoveto }"); //negative X movement
+        gen.writeln("    ifelse");
+        gen.writeln("  } forall");
+        gen.writeln("} bd");
+
+        gen.writeDSCComment(DSCConstants.END_RESOURCE);
+        gen.getResourceTracker().registerSuppliedResource(this);
+    }
+
+}
index 7f86017c1cb2b6e6fcb2d9feb21aacfbb9114eda..c3aac8cd84a4034f70976e8e46518186bf0306df 100644 (file)
@@ -199,6 +199,7 @@ public class PSDocumentHandler extends AbstractBinaryWritingIFDocumentHandler {
         gen.writeDSCComment(DSCConstants.BEGIN_PROLOG);
         PSProcSets.writeStdProcSet(gen);
         PSProcSets.writeEPSProcSet(gen);
+        FOPProcSet.INSTANCE.writeTo(gen);
         gen.writeDSCComment(DSCConstants.END_PROLOG);
 
         //Setup
index 02fb7292cac0d560d0b9c6f3a92225d3c4450549..cb88f467084ca4ccb92ebe8eff299b2799bc7aac 100644 (file)
@@ -312,8 +312,33 @@ public class PSPainter extends AbstractIFPainter {
         }
     }
 
+    private String formatMptAsPt(PSGenerator gen, int value) {
+        return gen.formatDouble(value / 1000.0);
+    }
+
+    /* Disabled: performance experiment (incomplete)
+
+    private static final String ZEROS = "0.00";
+
+    private String formatMptAsPt1(int value) {
+        String s = Integer.toString(value);
+        int len = s.length();
+        StringBuffer sb = new StringBuffer();
+        if (len < 4) {
+            sb.append(ZEROS.substring(0, 5 - len));
+            sb.append(s);
+        } else {
+            int dec = len - 3;
+            sb.append(s.substring(0, dec));
+            sb.append('.');
+            sb.append(s.substring(dec));
+        }
+        return sb.toString();
+    }*/
+
     /** {@inheritDoc} */
-    public void drawText(int x, int y, int[] dx, int[] dy, String text) throws IFException {
+    public void drawText(int x, int y, int letterSpacing, int wordSpacing,
+            int[] dx, String text) throws IFException {
         try {
             //Note: dy is currently ignored
             PSGenerator generator = getGenerator();
@@ -334,16 +359,13 @@ public class PSPainter extends AbstractIFPainter {
                 singleByteFont = (SingleByteFont)tf;
             }
             Font font = getFontInfo().getFontInstance(triplet, sizeMillipoints);
-            //String fontName = font.getFontName();
 
             PSResource res = this.documentHandler.getPSResourceForFontKey(fontKey);
             generator.useFont("/" + res.getName(), fontSize);
             generator.getResourceTracker().notifyResourceUsageOnPage(res);
-            //textutil.updateTf(fontKey, fontSize, tf.isMultiByte());
 
-            generator.writeln("1 0 0 -1 " + generator.formatDouble(x / 1000.0)
-                    + " " + generator.formatDouble(y / 1000.0) + " Tm");
-            //textutil.writeTextMatrix(new AffineTransform(1, 0, 0, -1, x / 1000f, y / 1000f));
+            generator.writeln("1 0 0 -1 " + formatMptAsPt(generator, x)
+                    + " " + formatMptAsPt(generator, y) + " Tm");
 
             int textLen = text.length();
             if (singleByteFont != null && singleByteFont.hasAdditionalEncodings()) {
@@ -356,7 +378,8 @@ public class PSPainter extends AbstractIFPainter {
                     int encoding = mapped / 256;
                     if (currentEncoding != encoding) {
                         if (i > 0) {
-                            writeText(text, start, i - start, dx, dy, font, tf);
+                            writeText(text, start, i - start,
+                                    letterSpacing, wordSpacing, dx, font, tf);
                         }
                         if (encoding == 0) {
                             useFont(fontKey, sizeMillipoints);
@@ -367,62 +390,98 @@ public class PSPainter extends AbstractIFPainter {
                         start = i;
                     }
                 }
-                writeText(text, start, textLen - start, dx, dy, font, tf);
+                writeText(text, start, textLen - start,
+                        letterSpacing, wordSpacing, dx, font, tf);
             } else {
                 //Simple single-font painting
                 useFont(fontKey, sizeMillipoints);
-                writeText(text, 0, textLen, dx, dy, font, tf);
+                writeText(text, 0, textLen,
+                        letterSpacing, wordSpacing, dx, font, tf);
             }
         } catch (IOException ioe) {
             throw new IFException("I/O error in drawText()", ioe);
         }
     }
 
-    private void writeText(String text, int start, int len, int[] dx, int[] dy,
+    private void writeText(String text, int start, int len,
+            int letterSpacing, int wordSpacing, int[] dx,
             Font font, Typeface tf) throws IOException {
         PSGenerator generator = getGenerator();
         int end = start + len;
         int initialSize = len;
         initialSize += initialSize / 2;
+
+        boolean hasLetterSpacing = (letterSpacing != 0);
+        boolean needTJ = false;
+
+        int lineStart = 0;
+        StringBuffer accText = new StringBuffer(initialSize);
         StringBuffer sb = new StringBuffer(initialSize);
-        sb.append("(");
-        int[] offsets = new int[len];
         int dxl = (dx != null ? dx.length : 0);
         for (int i = start; i < end; i++) {
             char orgChar = text.charAt(i);
             char ch;
             int cw;
+            int glyphAdjust = 0;
             if (CharUtilities.isFixedWidthSpace(orgChar)) {
                 //Fixed width space are rendered as spaces so copy/paste works in a reader
                 ch = font.mapChar(CharUtilities.SPACE);
-                //int spaceDiff = font.getCharWidth(ch) - font.getCharWidth(orgChar);
-                //glyphAdjust = -(spaceDiff);
+                cw = font.getCharWidth(orgChar);
+                glyphAdjust = font.getCharWidth(ch) - cw;
             } else {
+                if ((wordSpacing != 0) && CharUtilities.isAdjustableSpace(orgChar)) {
+                    glyphAdjust -= wordSpacing;
+                }
                 ch = font.mapChar(orgChar);
-                //cw = tf.getWidth(ch, font.getFontSize()) / 1000;
+                cw = font.getCharWidth(orgChar);
             }
 
-            cw = font.getCharWidth(orgChar);
-            int glyphAdjust = 0;
             if (dx != null && i < dxl - 1) {
-                glyphAdjust += dx[i + 1];
+                glyphAdjust -= dx[i + 1];
             }
-            offsets[i - start] = cw + glyphAdjust;
             char codepoint = (char)(ch % 256);
-            PSGenerator.escapeChar(codepoint, sb);
-        }
-        sb.append(")" + PSGenerator.LF + "[");
-        for (int i = 0; i < len; i++) {
-            if (i > 0) {
-                if (i % 8 == 0) {
-                    sb.append(PSGenerator.LF);
-                } else {
-                    sb.append(" ");
+            PSGenerator.escapeChar(codepoint, accText); //add character to accumulated text
+            if (glyphAdjust != 0) {
+                needTJ = true;
+                if (sb.length() == 0) {
+                    sb.append('['); //Need to start TJ
+                }
+                if (accText.length() > 0) {
+                    if ((sb.length() - lineStart + accText.length()) > 200) {
+                        sb.append(PSGenerator.LF);
+                        lineStart = sb.length();
+                    }
+                    sb.append('(');
+                    sb.append(accText);
+                    sb.append(") ");
+                    accText.setLength(0); //reset accumulated text
                 }
+                sb.append(Integer.toString(glyphAdjust)).append(' ');
+            }
+        }
+        if (needTJ) {
+            if (accText.length() > 0) {
+                sb.append('(');
+                sb.append(accText);
+                sb.append(')');
+            }
+            if (hasLetterSpacing) {
+                sb.append("] " + formatMptAsPt(generator, letterSpacing) + " ATJ");
+            } else {
+                sb.append("] TJ");
+            }
+        } else {
+            sb.append('(').append(accText).append(")");
+            if (hasLetterSpacing) {
+                StringBuffer spb = new StringBuffer();
+                spb.append(formatMptAsPt(generator, letterSpacing))
+                    .append(" 0 ");
+                sb.insert(0, spb.toString());
+                sb.append(" ashow");
+            } else {
+                sb.append(" show");
             }
-            sb.append(generator.formatDouble(offsets[i] / 1000f));
         }
-        sb.append("]" + PSGenerator.LF + "xshow");
         generator.writeln(sb.toString());
     }
 
index d427096be94b442fbaa5a97713cdcdbe341cff72..0ee13c021a6299b8abd4ab87fca5f7beae8739dd 100644 (file)
@@ -325,19 +325,23 @@ public class SVGPainter extends AbstractIFPainter implements SVGConstants {
     }
 
     /** {@inheritDoc} */
-    public void drawText(int x, int y, int[] dx, int[] dy, String text) throws IFException {
+    public void drawText(int x, int y, int letterSpacing, int wordSpacing, int[] dx, String text)
+                throws IFException {
         try {
             establish(MODE_TEXT);
             AttributesImpl atts = new AttributesImpl();
             XMLUtil.addAttribute(atts, XMLConstants.XML_SPACE, "preserve");
             XMLUtil.addAttribute(atts, "x", Integer.toString(x));
             XMLUtil.addAttribute(atts, "y", Integer.toString(y));
+            if (letterSpacing != 0) {
+                XMLUtil.addAttribute(atts, "letter-spacing", Integer.toString(letterSpacing));
+            }
+            if (wordSpacing != 0) {
+                XMLUtil.addAttribute(atts, "word-spacing", Integer.toString(wordSpacing));
+            }
             if (dx != null) {
                 XMLUtil.addAttribute(atts, "dx", IFUtil.toString(dx));
             }
-            if (dy != null) {
-                XMLUtil.addAttribute(atts, "dy", IFUtil.toString(dy));
-            }
             handler.startElement("text", atts);
             char[] chars = text.toCharArray();
             handler.characters(chars, 0, chars.length);
index 73f9b8e3af264409a57a768fccfdcf196c8433d2..092cac755ec430eacf2dce6c10eafe74810c11a6 100644 (file)
     <eval expected="Default space between characters is defined to" xpath="//if:text[14]"/>
     <eval expected="0" xpath="//if:text[14]/@x"/>
     <eval expected="197466" xpath="//if:text[14]/@y"/>
-    <eval expected="0 2000 2000 2000 2000 2000 2000 0 4000 2000 2000 2000 2000 0 4000 2000 2000 2000 2000 2000 2000 0 4000 2000 2000 2000 2000 2000 2000 2000 2000 2000 0 4000 2000 0 4000 2000 2000 2000 2000 2000 2000 0 4000 2000" xpath="//if:text[14]/@dx"/>
+    <eval expected="2000" xpath="//if:text[14]/@letter-spacing"/>
+    <true xpath="not(//if:text[14]/@word-spacing)"/>
+    <true xpath="not(//if:text[14]/@dx)"/>
 
     <eval expected="Default space between characters is defined to be" xpath="//if:text[20]"/>
     <eval expected="0" xpath="//if:text[20]/@x"/>
     <eval expected="283866" xpath="//if:text[20]/@y"/>
-    <eval expected="0 1938 1938 1938 1938 1938 1938 0 3836 1938 1938 1938 1938 0 3836 1938 1938 1938 1938 1938 1938 0 3836 1938 1938 1938 1938 1938 1938 1938 1938 1938 0 3836 1938 0 3836 1938 1938 1938 1938 1938 1938 0 3836 1938 0 3836 1938" xpath="//if:text[20]/@dx"/>
+    <eval expected="1938" xpath="//if:text[20]/@letter-spacing"/>
+    <eval expected="-40" xpath="//if:text[20]/@word-spacing"/>
+    <true xpath="not(//if:text[20]/@dx)"/>
+
+    <eval expected="2000" xpath="//if:text[21]/@letter-spacing"/>
+    <true xpath="not(//if:text[21]/@word-spacing)"/>
+    <true xpath="not(//if:text[21]/@dx)"/>
+
+    <eval expected="1364" xpath="//if:text[22]/@letter-spacing"/>
+    <eval expected="-362" xpath="//if:text[22]/@word-spacing"/>
+    <true xpath="not(//if:text[22]/@dx)"/>
   </if-checks>
 </testcase>
index df74f09c5f73d7f72b14b3650932093f5a7a6baa..b7410bab145acfc002a5727ab240ca2d2465e344 100644 (file)
     <eval expected=" text-text Hello World." xpath="//if:text[2]"/>
     <eval expected="36420" xpath="//if:text[2]/@x"/>
     <eval expected="10266" xpath="//if:text[2]/@y"/>
+    <true xpath="not(//if:text[2]/@letter-spacing)"/>
+    <true xpath="not(//if:text[2]/@word-spacing)"/>
     <eval expected="0 0 0 -360 0 0 0 0 -360 0 0 0 0 0 0 0 0 0 -360 0 180" xpath="//if:text[2]/@dx"/>
 
     <eval expected="VAVAV" xpath="//if:text[3]"/>
     <eval expected="0" xpath="//if:text[3]/@x"/>
     <eval expected="24666" xpath="//if:text[3]/@y"/>
-    <eval expected="0 40 160 40 160" xpath="//if:text[3]/@dx"/>
+    <eval expected="1000" xpath="//if:text[3]/@letter-spacing"/>
+    <true xpath="not(//if:text[3]/@word-spacing)"/>
+    <eval expected="0 -960 -840 -960 -840" xpath="//if:text[3]/@dx"/>
     
     <eval expected=" text-text Hello World." xpath="//if:text[4]"/>
     <eval expected="40420" xpath="//if:text[4]/@x"/>
     <eval expected="24666" xpath="//if:text[4]/@y"/>
-    <eval expected="0 2000 1000 640 1000 1000 1000 1000 640 1000 0 2000 1000 1000 1000 1000 0 2000 640 1000 1180 1000 1000" xpath="//if:text[4]/@dx"/>
-
+    <eval expected="1000" xpath="//if:text[4]/@letter-spacing"/>
+    <true xpath="not(//if:text[3]/@word-spacing)"/>
+    <eval expected="0 0 0 -360 0 0 0 0 -360 0 0 0 0 0 0 0 0 0 -360 0 180" xpath="//if:text[4]/@dx"/>
+    
     <eval expected="VAVAV" xpath="//if:text[5]"/>
     <eval expected="0" xpath="//if:text[5]/@x"/>
     <eval expected="39066" xpath="//if:text[5]/@y"/>
+    <true xpath="not(//if:text[5]/@letter-spacing)"/>
+    <eval expected="5000" xpath="//if:text[5]/@word-spacing"/>
     <eval expected="0 -960 -840 -960 -840" xpath="//if:text[5]/@dx"/>
     
     <eval expected=" text-text Hello World." xpath="//if:text[6]"/>
     <eval expected="36420" xpath="//if:text[6]/@x"/>
     <eval expected="39066" xpath="//if:text[6]/@y"/>
-    <eval expected="0 5000 0 -360 0 0 0 0 -360 0 0 5000 0 0 0 0 0 5000 -360 0 180" xpath="//if:text[6]/@dx"/>
-
+    <true xpath="not(//if:text[5]/@letter-spacing)"/>
+    <eval expected="5000" xpath="//if:text[6]/@word-spacing"/>
+    <eval expected="0 0 0 -360 0 0 0 0 -360 0 0 0 0 0 0 0 0 0 -360 0 180" xpath="//if:text[4]/@dx"/>
+    
     <eval expected="VAVAV" xpath="//if:text[7]"/>
     <eval expected="0" xpath="//if:text[7]/@x"/>
     <eval expected="53466" xpath="//if:text[7]/@y"/>
-    <eval expected="0 40 160 40 160" xpath="//if:text[7]/@dx"/>
+    <eval expected="1000" xpath="//if:text[7]/@letter-spacing"/>
+    <eval expected="3000" xpath="//if:text[7]/@word-spacing"/>  <!-- TODO Not sure that's correct! -->
+    <eval expected="0 -960 -840 -960 -840" xpath="//if:text[7]/@dx"/>
     
     <eval expected=" text-text Hello World." xpath="//if:text[8]"/>
     <eval expected="40420" xpath="//if:text[8]/@x"/>
     <eval expected="53466" xpath="//if:text[8]/@y"/>
-    <eval expected="0 5000 1000 640 1000 1000 1000 1000 640 1000 0 5000 1000 1000 1000 1000 0 5000 640 1000 1180 1000 1000" xpath="//if:text[8]/@dx"/>
+    <eval expected="1000" xpath="//if:text[8]/@letter-spacing"/>
+    <eval expected="3000" xpath="//if:text[8]/@word-spacing"/> <!-- TODO Not sure that's correct! -->
+    <eval expected="0 0 0 -360 0 0 0 0 -360 0 0 0 0 0 0 0 0 0 -360 0 180" xpath="//if:text[8]/@dx"/>
     
   </if-checks>
 </testcase>