Переглянути джерело

Performance improvements and file-size reductions by introducing letter-spacing and word-spacing attributes in new IF (as mentioned on fop-dev).

Allow to control whether kerning information is loaded from fonts.
Started support for AFP font embedding (incomplete and currently disabled)

git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/branches/Temp_AreaTreeNewDesign@741165 13f79535-47bb-0310-9956-ffa450edef68
tags/fop-1_0
Jeremias Maerki 15 роки тому
джерело
коміт
4e27a1c9dd
31 змінених файлів з 879 додано та 182 видалено
  1. 2
    0
      src/documentation/intermediate-format-ng/fop-intermediate-format-ng-content.xsd
  2. 94
    33
      src/java/org/apache/fop/afp/AFPResourceManager.java
  3. 8
    0
      src/java/org/apache/fop/afp/fonts/AFPFont.java
  4. 63
    0
      src/java/org/apache/fop/afp/modca/IncludedResourceObject.java
  5. 60
    0
      src/java/org/apache/fop/afp/util/DefaultFOPResourceAccessor.java
  6. 40
    0
      src/java/org/apache/fop/afp/util/ResourceAccessor.java
  7. 58
    0
      src/java/org/apache/fop/afp/util/SimpleResourceAccessor.java
  8. 19
    7
      src/java/org/apache/fop/fonts/FontLoader.java
  9. 1
    1
      src/java/org/apache/fop/fonts/LazyFont.java
  10. 1
    1
      src/java/org/apache/fop/fonts/autodetect/FontInfoFinder.java
  11. 8
    4
      src/java/org/apache/fop/fonts/truetype/TTFFontLoader.java
  12. 10
    5
      src/java/org/apache/fop/fonts/type1/Type1FontLoader.java
  13. 52
    23
      src/java/org/apache/fop/pdf/PDFPaintingState.java
  14. 116
    30
      src/java/org/apache/fop/render/afp/AFPPainter.java
  15. 2
    1
      src/java/org/apache/fop/render/afp/AFPRendererConfigurator.java
  16. 0
    1
      src/java/org/apache/fop/render/intermediate/AbstractIFPainter.java
  17. 49
    7
      src/java/org/apache/fop/render/intermediate/IFPainter.java
  18. 5
    2
      src/java/org/apache/fop/render/intermediate/IFParser.java
  19. 16
    4
      src/java/org/apache/fop/render/intermediate/IFRenderer.java
  20. 8
    4
      src/java/org/apache/fop/render/intermediate/IFSerializer.java
  21. 2
    1
      src/java/org/apache/fop/render/java2d/ConfiguredFontCollection.java
  22. 8
    2
      src/java/org/apache/fop/render/java2d/Java2DPainter.java
  23. 23
    11
      src/java/org/apache/fop/render/pcl/PCLPainter.java
  24. 10
    0
      src/java/org/apache/fop/render/pdf/PDFContentGenerator.java
  25. 10
    2
      src/java/org/apache/fop/render/pdf/PDFPainter.java
  26. 81
    0
      src/java/org/apache/fop/render/ps/FOPProcSet.java
  27. 1
    0
      src/java/org/apache/fop/render/ps/PSDocumentHandler.java
  28. 89
    30
      src/java/org/apache/fop/render/ps/PSPainter.java
  29. 8
    4
      src/sandbox/org/apache/fop/render/svg/SVGPainter.java
  30. 14
    2
      test/layoutengine/standard-testcases/block_letter-spacing.xml
  31. 21
    7
      test/layoutengine/standard-testcases/kerning_1_on.xml

+ 2
- 0
src/documentation/intermediate-format-ng/fop-intermediate-format-ng-content.xsd Переглянути файл

@@ -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"/>

+ 94
- 33
src/java/org/apache/fop/afp/AFPResourceManager.java Переглянути файл

@@ -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;
}
}

}

+ 8
- 0
src/java/org/apache/fop/afp/fonts/AFPFont.java Переглянути файл

@@ -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;

+ 63
- 0
src/java/org/apache/fop/afp/modca/IncludedResourceObject.java Переглянути файл

@@ -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);
}
}

}

+ 60
- 0
src/java/org/apache/fop/afp/util/DefaultFOPResourceAccessor.java Переглянути файл

@@ -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;
}
}

}

+ 40
- 0
src/java/org/apache/fop/afp/util/ResourceAccessor.java Переглянути файл

@@ -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;

}

+ 58
- 0
src/java/org/apache/fop/afp/util/SimpleResourceAccessor.java Переглянути файл

@@ -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();
}

}

+ 19
- 7
src/java/org/apache/fop/fonts/FontLoader.java Переглянути файл

@@ -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();
}

+ 1
- 1
src/java/org/apache/fop/fonts/LazyFont.java Переглянути файл

@@ -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;

+ 1
- 1
src/java/org/apache/fop/fonts/autodetect/FontInfoFinder.java Переглянути файл

@@ -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);

+ 8
- 4
src/java/org/apache/fop/fonts/truetype/TTFFontLoader.java Переглянути файл

@@ -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);
}

+ 10
- 5
src/java/org/apache/fop/fonts/type1/Type1FontLoader.java Переглянути файл

@@ -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());
}
}
}


+ 52
- 23
src/java/org/apache/fop/pdf/PDFPaintingState.java Переглянути файл

@@ -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;
}

+ 116
- 30
src/java/org/apache/fop/render/afp/AFPPainter.java Переглянути файл

@@ -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);
}
}


+ 2
- 1
src/java/org/apache/fop/render/afp/AFPRendererConfigurator.java Переглянути файл

@@ -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,

+ 0
- 1
src/java/org/apache/fop/render/intermediate/AbstractIFPainter.java Переглянути файл

@@ -411,5 +411,4 @@ public abstract class AbstractIFPainter implements IFPainter {
return new AffineTransform(matrix);
}


}

+ 49
- 7
src/java/org/apache/fop/render/intermediate/IFPainter.java Переглянути файл

@@ -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.

+ 5
- 2
src/java/org/apache/fop/render/intermediate/IFParser.java Переглянути файл

@@ -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() {

+ 16
- 4
src/java/org/apache/fop/render/intermediate/IFRenderer.java Переглянути файл

@@ -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);
}

+ 8
- 4
src/java/org/apache/fop/render/intermediate/IFSerializer.java Переглянути файл

@@ -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);

+ 2
- 1
src/java/org/apache/fop/render/java2d/ConfiguredFontCollection.java Переглянути файл

@@ -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);
}


+ 8
- 2
src/java/org/apache/fop/render/java2d/Java2DPainter.java Переглянути файл

@@ -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];
}

+ 23
- 11
src/java/org/apache/fop/render/pcl/PCLPainter.java Переглянути файл

@@ -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);

+ 10
- 0
src/java/org/apache/fop/render/pdf/PDFContentGenerator.java Переглянути файл

@@ -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

+ 10
- 2
src/java/org/apache/fop/render/pdf/PDFPainter.java Переглянути файл

@@ -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);

+ 81
- 0
src/java/org/apache/fop/render/ps/FOPProcSet.java Переглянути файл

@@ -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);
}

}

+ 1
- 0
src/java/org/apache/fop/render/ps/PSDocumentHandler.java Переглянути файл

@@ -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

+ 89
- 30
src/java/org/apache/fop/render/ps/PSPainter.java Переглянути файл

@@ -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());
}


+ 8
- 4
src/sandbox/org/apache/fop/render/svg/SVGPainter.java Переглянути файл

@@ -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);

+ 14
- 2
test/layoutengine/standard-testcases/block_letter-spacing.xml Переглянути файл

@@ -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>

+ 21
- 7
test/layoutengine/standard-testcases/kerning_1_on.xml Переглянути файл

@@ -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>

Завантаження…
Відмінити
Зберегти