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-ffa450edef68tags/fop-1_0
@@ -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"/> |
@@ -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; | |||
} | |||
} | |||
} |
@@ -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; |
@@ -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); | |||
} | |||
} | |||
} |
@@ -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; | |||
} | |||
} | |||
} |
@@ -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; | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -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(); | |||
} |
@@ -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; |
@@ -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); |
@@ -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); | |||
} |
@@ -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()); | |||
} | |||
} | |||
} | |||
@@ -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; | |||
} |
@@ -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); | |||
} | |||
} | |||
@@ -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, |
@@ -411,5 +411,4 @@ public abstract class AbstractIFPainter implements IFPainter { | |||
return new AffineTransform(matrix); | |||
} | |||
} |
@@ -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. |
@@ -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() { |
@@ -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); | |||
} |
@@ -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); |
@@ -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); | |||
} | |||
@@ -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]; | |||
} |
@@ -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); |
@@ -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 |
@@ -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); |
@@ -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); | |||
} | |||
} |
@@ -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 |
@@ -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()); | |||
} | |||
@@ -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); |
@@ -71,11 +71,23 @@ | |||
<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> |
@@ -69,37 +69,51 @@ | |||
<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> |