<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"/>
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;
/** 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();
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);
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
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) {
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.
public AFPResourceLevelDefaults getResourceLevelDefaults() {
return this.resourceLevelDefaults;
}
-}
\ No newline at end of file
+
+}
*/
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;
--- /dev/null
+/*
+ * 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);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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;
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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;
+
+}
--- /dev/null
+/*
+ * 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();
+ }
+
+}
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;
}
*/
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);
}
/**
* @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);
}
/**
* @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;
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();
}
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;
}
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);
* @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);
}
/**
* 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) {
copyWidthsSingleByte(ttf);
}
- copyKerning(ttf, isCid);
+ if (useKerning) {
+ copyKerning(ttf, isCid);
+ }
if (this.embedded && ttf.isEmbeddable()) {
returnFont.setEmbedFileName(this.fontFileURI);
}
* 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) {
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());
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());
+ }
}
}
* @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;
* @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;
* @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.
*
newState.addValues(state);
}
}
- if (((PDFData)getData()).gstate != null) {
- newState.addValues(((PDFData)getData()).gstate);
+ if (getPDFData().gstate != null) {
+ newState.addValues(getPDFData().gstate);
}
return newState;
}
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;
}
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;
}
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;
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;
*/
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;
//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;
}
/** {@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);
//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
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 {
builder.absoluteMoveBaseline(p.y);
builder.absoluteMoveInline(p.x);
- builder.setVariableSpaceCharacterIncrement(0);
- builder.setInterCharacterAdjustment(0);
builder.setExtendedTextColor(state.getTextColor());
builder.setCodedFont((byte)fontReference);
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);
}
}
LogUtil.handleException(log, e,
userAgent.getFactory().validateUserConfigStrictly());
}
-
+ } else {
+ fontCollections.add(new AFPFontCollection(userAgent.getEventBroadcaster(), null));
}
fontManager.setup(fontInfo,
return new AffineTransform(matrix);
}
-
}
*/
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;
/**
/**
* 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.
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() {
int bl = currentBPPosition + text.getOffset() + text.getBaselineOffset();
textUtil.flush();
textUtil.setStartPosition(rx, bl);
+ textUtil.setSpacing(text.getTextLetterSpaceAdjust(), text.getTextWordSpaceAdjust());
super.renderText(text);
textUtil.flush();
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);
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;
}
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);
this.starty = y;
}
+ void setSpacing(int tls, int tws) {
+ this.tls = tls;
+ this.tws = tws;
+ }
+
void flush() {
if (text.length() > 0) {
try {
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);
}
}
/** {@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);
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);
}
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.
* @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();
}
/** {@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());
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];
}
}
/** {@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());
? 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) {
}
}
- 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);
}
sb.append(ch);
+ if ((wordSpacing != 0) && CharUtilities.isAdjustableSpace(orgChar)) {
+ glyphAdjust += wordSpacing;
+ }
+ glyphAdjust += letterSpacing;
if (dx != null && i < dxl - 1) {
glyphAdjust += dx[i + 1];
}
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
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];
}
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());
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() {
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);
}
}
+ /**
+ * 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
}
/** {@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(
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);
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
glyphAdjust = -spaceDiff;
} else {
ch = font.mapChar(orgChar);
+ if ((wordSpacing != 0) && CharUtilities.isAdjustableSpace(orgChar)) {
+ glyphAdjust += wordSpacing;
+ }
}
}
textutil.writeTJMappedChar(ch);
--- /dev/null
+/*
+ * 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);
+ }
+
+}
gen.writeDSCComment(DSCConstants.BEGIN_PROLOG);
PSProcSets.writeStdProcSet(gen);
PSProcSets.writeEPSProcSet(gen);
+ FOPProcSet.INSTANCE.writeTo(gen);
gen.writeDSCComment(DSCConstants.END_PROLOG);
//Setup
}
}
+ 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();
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()) {
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);
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());
}
}
/** {@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);
<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>
<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>