aboutsummaryrefslogtreecommitdiffstats
path: root/src/java
diff options
context:
space:
mode:
Diffstat (limited to 'src/java')
-rw-r--r--src/java/org/apache/fop/afp/AFPEventProducer.java8
-rw-r--r--src/java/org/apache/fop/afp/fonts/AFPFont.java32
-rw-r--r--src/java/org/apache/fop/afp/fonts/AbstractOutlineFont.java60
-rw-r--r--src/java/org/apache/fop/afp/fonts/CharacterSet.java72
-rw-r--r--src/java/org/apache/fop/afp/fonts/CharacterSetBuilder.java95
-rw-r--r--src/java/org/apache/fop/afp/fonts/CharacterSetOrientation.java124
-rw-r--r--src/java/org/apache/fop/afp/fonts/DoubleByteFont.java34
-rw-r--r--src/java/org/apache/fop/afp/fonts/FopCharacterSet.java50
-rw-r--r--src/java/org/apache/fop/afp/fonts/IntegerKeyStore.java71
-rw-r--r--src/java/org/apache/fop/afp/fonts/OutlineFont.java16
-rw-r--r--src/java/org/apache/fop/afp/fonts/RasterFont.java88
-rw-r--r--src/java/org/apache/fop/afp/goca/GraphicsCharacterString.java6
-rw-r--r--src/java/org/apache/fop/afp/svg/AFPBridgeContext.java42
-rw-r--r--src/java/org/apache/fop/afp/svg/AFPFontFamilyResolver.java84
-rw-r--r--src/java/org/apache/fop/afp/svg/AFPTextHandler.java31
-rw-r--r--src/java/org/apache/fop/afp/svg/AFPTextPainter.java21
-rw-r--r--src/java/org/apache/fop/fo/FOText.java94
-rw-r--r--src/java/org/apache/fop/fonts/Base14Font.java11
-rw-r--r--src/java/org/apache/fop/fonts/CIDFont.java1
-rw-r--r--src/java/org/apache/fop/fonts/CustomFont.java47
-rw-r--r--src/java/org/apache/fop/fonts/FontInfo.java37
-rw-r--r--src/java/org/apache/fop/fonts/FontMetrics.java44
-rw-r--r--src/java/org/apache/fop/fonts/GlyphMapping.java327
-rw-r--r--src/java/org/apache/fop/fonts/LazyFont.java26
-rw-r--r--src/java/org/apache/fop/fonts/MultiByteFont.java18
-rw-r--r--src/java/org/apache/fop/fonts/SingleByteFont.java40
-rw-r--r--src/java/org/apache/fop/fonts/TextFragment.java31
-rw-r--r--src/java/org/apache/fop/fonts/truetype/TTFFile.java38
-rw-r--r--src/java/org/apache/fop/fonts/truetype/TTFFontLoader.java20
-rw-r--r--src/java/org/apache/fop/fonts/type1/AFMCharMetrics.java8
-rw-r--r--src/java/org/apache/fop/fonts/type1/Type1FontLoader.java17
-rw-r--r--src/java/org/apache/fop/image/loader/batik/ImageConverterSVG2G2D.java7
-rw-r--r--src/java/org/apache/fop/image/loader/batik/PreloaderSVG.java3
-rw-r--r--src/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java719
-rw-r--r--src/java/org/apache/fop/render/AbstractGenericSVGHandler.java4
-rw-r--r--src/java/org/apache/fop/render/afp/AFPSVGHandler.java15
-rw-r--r--src/java/org/apache/fop/render/java2d/CustomFontMetricsMapper.java21
-rw-r--r--src/java/org/apache/fop/render/java2d/Java2DFontMetrics.java20
-rw-r--r--src/java/org/apache/fop/render/java2d/Java2DSVGHandler.java5
-rw-r--r--src/java/org/apache/fop/render/java2d/SystemFontMetricsMapper.java21
-rw-r--r--src/java/org/apache/fop/render/pdf/PDFImageHandlerSVG.java4
-rw-r--r--src/java/org/apache/fop/render/ps/AbstractPSTranscoder.java3
-rw-r--r--src/java/org/apache/fop/render/ps/PSImageHandlerSVG.java5
-rw-r--r--src/java/org/apache/fop/render/ps/PSSVGHandler.java3
-rw-r--r--src/java/org/apache/fop/render/ps/PSTextPainter.java361
-rw-r--r--src/java/org/apache/fop/svg/ACIUtils.java68
-rw-r--r--src/java/org/apache/fop/svg/AbstractFOPTextPainter.java331
-rw-r--r--src/java/org/apache/fop/svg/AbstractFOPTranscoder.java22
-rw-r--r--src/java/org/apache/fop/svg/NativeTextPainter.java147
-rw-r--r--src/java/org/apache/fop/svg/PDFTextPainter.java229
-rw-r--r--src/java/org/apache/fop/svg/PDFTextUtil.java41
-rw-r--r--src/java/org/apache/fop/svg/PDFTranscoder.java6
-rw-r--r--src/java/org/apache/fop/svg/SVGUserAgent.java11
-rw-r--r--src/java/org/apache/fop/svg/SimpleSVGUserAgent.java12
-rw-r--r--src/java/org/apache/fop/svg/font/AggregatingFontFamilyResolver.java70
-rw-r--r--src/java/org/apache/fop/svg/font/FOPFontFamilyResolver.java31
-rw-r--r--src/java/org/apache/fop/svg/font/FOPFontFamilyResolverImpl.java67
-rw-r--r--src/java/org/apache/fop/svg/font/FOPGVTFont.java159
-rw-r--r--src/java/org/apache/fop/svg/font/FOPGVTFontFamily.java73
-rw-r--r--src/java/org/apache/fop/svg/font/FOPGVTGlyphVector.java339
-rw-r--r--src/java/org/apache/fop/svg/font/FilteringFontFamilyResolver.java47
61 files changed, 2720 insertions, 1717 deletions
diff --git a/src/java/org/apache/fop/afp/AFPEventProducer.java b/src/java/org/apache/fop/afp/AFPEventProducer.java
index 01d5c4ad7..1b43400c5 100644
--- a/src/java/org/apache/fop/afp/AFPEventProducer.java
+++ b/src/java/org/apache/fop/afp/AFPEventProducer.java
@@ -122,4 +122,12 @@ public interface AFPEventProducer extends EventProducer {
* @event.severity WARN
*/
void charactersetMissingMetrics(Object source, char character, String charSet);
+
+ /**
+ * Double-byte fonts are not currently supported in SVG.
+ * @param source the event source
+ * @param fontFamily name of DB font
+ * @event.severity WARN
+ */
+ void invalidDBFontInSVG(Object source, String fontFamily);
}
diff --git a/src/java/org/apache/fop/afp/fonts/AFPFont.java b/src/java/org/apache/fop/afp/fonts/AFPFont.java
index 06f484f37..99e15a46b 100644
--- a/src/java/org/apache/fop/afp/fonts/AFPFont.java
+++ b/src/java/org/apache/fop/afp/fonts/AFPFont.java
@@ -19,6 +19,7 @@
package org.apache.fop.afp.fonts;
+import java.awt.Rectangle;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
@@ -34,6 +35,8 @@ import org.apache.fop.fonts.Typeface;
*/
public abstract class AFPFont extends Typeface {
+ private static final double STRIKEOUT_POSITION_FACTOR = 0.45;
+
/** The font name */
protected final String name;
@@ -117,7 +120,34 @@ public abstract class AFPFont extends Typeface {
*/
protected static final char toUnicodeCodepoint(int character) {
//AFP fonts use Unicode directly as their mapped code points, so we can simply cast to char
- return (char)character;
+ return (char) character;
+ }
+
+ /** {@inheritDoc} */
+ public int getUnderlineThickness(int size) {
+ // This is the FOCA recommendation in the absence of the Underline Thickness parameter
+ return getBoundingBox('-', size).height;
+ }
+
+ /** {@inheritDoc} */
+ public int getStrikeoutPosition(int size) {
+ //TODO This conflicts with the FOCA recommendation of 0 in the absence of the Throughscore Position
+ // parameter
+ return (int) (STRIKEOUT_POSITION_FACTOR * getCapHeight(size));
+ }
+
+ /** {@inheritDoc} */
+ public int getStrikeoutThickness(int size) {
+ // This is the FOCA recommendation in the absence of the Throughscore Thickness parameter
+ return getBoundingBox('-', size).height;
+ }
+
+ /** {@inheritDoc} */
+ public abstract Rectangle getBoundingBox(int glyphIndex, int size);
+
+ /** {@inheritDoc} */
+ public int[] getWidths() {
+ throw new UnsupportedOperationException();
}
/** {@inheritDoc} */
diff --git a/src/java/org/apache/fop/afp/fonts/AbstractOutlineFont.java b/src/java/org/apache/fop/afp/fonts/AbstractOutlineFont.java
index 7b57a2b8c..edbdf5e95 100644
--- a/src/java/org/apache/fop/afp/fonts/AbstractOutlineFont.java
+++ b/src/java/org/apache/fop/afp/fonts/AbstractOutlineFont.java
@@ -71,22 +71,6 @@ public abstract class AbstractOutlineFont extends AFPFont {
}
/**
- * Get the first character in this font.
- * @return the first character in this font
- */
- public int getFirstChar() {
- return charSet.getFirstChar();
- }
-
- /**
- * Get the last character in this font.
- * @return the last character in this font
- */
- public int getLastChar() {
- return charSet.getLastChar();
- }
-
- /**
* The ascender is the part of a lowercase letter that extends above the
* "x-height" (the height of the letter "x"), such as "d", "t", or "h". Also
* used to denote the part of the letter extending above the x-height.
@@ -98,6 +82,17 @@ public abstract class AbstractOutlineFont extends AFPFont {
return charSet.getAscender() * size;
}
+ /** {@inheritDoc} */
+ public int getUnderlinePosition(int size) {
+ return charSet.getUnderscorePosition() * size;
+ }
+
+ @Override
+ public int getUnderlineThickness(int size) {
+ int underscoreWidth = charSet.getUnderscoreWidth();
+ return underscoreWidth == 0 ? super.getUnderlineThickness(size) : underscoreWidth * size;
+ }
+
/**
* Obtains the height of capital letters for the specified point size.
*
@@ -130,40 +125,7 @@ public abstract class AbstractOutlineFont extends AFPFont {
return charSet.getXHeight() * size;
}
- /**
- * Obtain the width of the character for the specified point size.
- * @param character the character
- * @param size the font size (in mpt)
- * @return the width of the character for the specified point size
- */
- public int getWidth(int character, int size) {
- return charSet.getWidth(toUnicodeCodepoint(character)) * size;
- }
- /**
- * Get the getWidth (in 1/1000ths of a point size) of all characters in this
- * character set.
- *
- * @param size the font size (in mpt)
- * @return the widths of all characters
- */
- public int[] getWidths(int size) {
- int[] widths = charSet.getWidths();
- for (int i = 0; i < widths.length; i++) {
- widths[i] = widths[i] * size;
- }
- return widths;
- }
-
- /**
- * Get the getWidth (in 1/1000ths of a point size) of all characters in this
- * character set.
- *
- * @return the widths of all characters
- */
- public int[] getWidths() {
- return getWidths(1000);
- }
/** {@inheritDoc} */
public boolean hasChar(char c) {
diff --git a/src/java/org/apache/fop/afp/fonts/CharacterSet.java b/src/java/org/apache/fop/afp/fonts/CharacterSet.java
index e0c3b9c9a..3df8ba4c4 100644
--- a/src/java/org/apache/fop/afp/fonts/CharacterSet.java
+++ b/src/java/org/apache/fop/afp/fonts/CharacterSet.java
@@ -19,10 +19,9 @@
package org.apache.fop.afp.fonts;
+import java.awt.Rectangle;
import java.io.UnsupportedEncodingException;
import java.nio.charset.CharacterCodingException;
-import java.util.HashMap;
-import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -63,6 +62,8 @@ public class CharacterSet {
private static final int MAX_NAME_LEN = 8;
+ /** The current orientation (currently only 0 is supported by FOP) */
+ public static final int SUPPORTED_ORIENTATION = 0;
/** The code page to which the character set relates */
protected final String codePage;
@@ -79,11 +80,8 @@ public class CharacterSet {
/** The path to the installed fonts */
private final AFPResourceAccessor accessor;
- /** The current orientation (currently only 0 is supported by FOP) */
- private final String currentOrientation = "0";
-
/** The collection of objects for each orientation */
- private final Map<String, CharacterSetOrientation> characterSetOrientations;
+ private CharacterSetOrientation characterSetOrientation;
/** The nominal vertical size (in millipoints) for bitmap fonts. 0 for outline fonts. */
private int nominalVerticalSize;
@@ -116,8 +114,6 @@ public class CharacterSet {
this.encoding = encoding;
this.encoder = charsetType.getEncoder(encoding);
this.accessor = accessor;
-
- this.characterSetOrientations = new HashMap<String, CharacterSetOrientation>(4);
}
// right pad short names with space
@@ -131,7 +127,9 @@ public class CharacterSet {
* @param cso the metrics for the orientation
*/
public void addCharacterSetOrientation(CharacterSetOrientation cso) {
- characterSetOrientations.put(String.valueOf(cso.getOrientation()), cso);
+ if (cso.getOrientation() == SUPPORTED_ORIENTATION) {
+ characterSetOrientation = cso;
+ }
}
/**
@@ -165,11 +163,24 @@ public class CharacterSet {
* @return the ascender value in millipoints
*/
public int getAscender() {
-
return getCharacterSetOrientation().getAscender();
}
/**
+ * TODO
+ */
+ public int getUnderscoreWidth() {
+ return getCharacterSetOrientation().getUnderscoreWidth();
+ }
+
+ /**
+ * TODO
+ */
+ public int getUnderscorePosition() {
+ return getCharacterSetOrientation().getUnderscorePosition();
+ }
+
+ /**
* Cap height is the average height of the uppercase characters in
* a font. This value is specified by the designer of a font and is
* usually the height of the uppercase M.
@@ -177,7 +188,6 @@ public class CharacterSet {
* @return the cap height value in millipoints
*/
public int getCapHeight() {
-
return getCharacterSetOrientation().getCapHeight();
}
@@ -194,24 +204,6 @@ public class CharacterSet {
}
/**
- * Returns the first character in the character set
- *
- * @return the first character in the character set (Unicode codepoint)
- */
- public char getFirstChar() {
- return getCharacterSetOrientation().getFirstChar();
- }
-
- /**
- * Returns the last character in the character set
- *
- * @return the last character in the character set (Unicode codepoint)
- */
- public char getLastChar() {
- return getCharacterSetOrientation().getLastChar();
- }
-
- /**
* Returns the resource accessor to load the font resources with.
* @return the resource accessor to load the font resources with
*/
@@ -220,16 +212,6 @@ public class CharacterSet {
}
/**
- * Get the width (in 1/1000ths of a point size) of all characters
- *
- * @return the widths of all characters
- */
- public int[] getWidths() {
-
- return getCharacterSetOrientation().getWidths();
- }
-
- /**
* XHeight refers to the height of the lower case letters above the baseline.
*
* @return the typical height of characters
@@ -246,11 +228,13 @@ public class CharacterSet {
* @param character the Unicode character from which the width will be calculated
* @return the width of the character
*/
- public int getWidth(char character) {
- return getCharacterSetOrientation().getWidth(character);
+ public int getWidth(char character, int size) {
+ return getCharacterSetOrientation().getWidth(character, size);
}
-
+ public Rectangle getCharacterBox(char character, int size) {
+ return getCharacterSetOrientation().getCharacterBox(character, size);
+ }
/**
* Returns the AFP character set identifier
@@ -309,9 +293,7 @@ public class CharacterSet {
* @return characterSetOrentation The current orientation metrics.
*/
private CharacterSetOrientation getCharacterSetOrientation() {
- CharacterSetOrientation c
- = characterSetOrientations.get(currentOrientation);
- return c;
+ return characterSetOrientation;
}
/**
diff --git a/src/java/org/apache/fop/afp/fonts/CharacterSetBuilder.java b/src/java/org/apache/fop/afp/fonts/CharacterSetBuilder.java
index 2565942b5..a3b2ab8ec 100644
--- a/src/java/org/apache/fop/afp/fonts/CharacterSetBuilder.java
+++ b/src/java/org/apache/fop/afp/fonts/CharacterSetBuilder.java
@@ -19,6 +19,7 @@
package org.apache.fop.afp.fonts;
+import java.awt.Rectangle;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
@@ -292,16 +293,14 @@ public abstract class CharacterSetBuilder {
metricNormalizationFactor = 1000.0d * 72000.0d
/ fontDescriptor.getNominalFontSizeInMillipoints() / dpi;
}
-
+ ValueNormalizer normalizer = new ValueNormalizer(metricNormalizationFactor);
//process D3AC89 Font Position
- processFontPosition(structuredFieldReader, characterSetOrientations,
- metricNormalizationFactor);
-
+ processFontPosition(structuredFieldReader, characterSetOrientations, normalizer);
//process D38C89 Font Index (per orientation)
for (int i = 0; i < characterSetOrientations.length; i++) {
- processFontIndex(structuredFieldReader,
- characterSetOrientations[i], codePage, metricNormalizationFactor);
- characterSet.addCharacterSetOrientation(characterSetOrientations[i]);
+ CharacterSetOrientation characterSetOrientation = characterSetOrientations[i];
+ processFontIndex(structuredFieldReader, characterSetOrientation, codePage, normalizer);
+ characterSet.addCharacterSetOrientation(characterSetOrientation);
}
} else {
throw new IOException("Missing D3AE89 Font Control structured field.");
@@ -314,6 +313,19 @@ public abstract class CharacterSetBuilder {
return characterSet;
}
+ private static class ValueNormalizer {
+
+ private final double factor;
+
+ public ValueNormalizer(double factor) {
+ this.factor = factor;
+ }
+
+ public int normalize(int value) {
+ return (int) Math.round(value * factor);
+ }
+ }
+
/**
* Load the code page information from the appropriate file. The file name
* to load is determined by the code page name and the file extension 'CDP'.
@@ -475,7 +487,7 @@ public abstract class CharacterSetBuilder {
* @throws IOException if an I/O exception of some sort has occurred.
*/
private void processFontPosition(StructuredFieldReader structuredFieldReader,
- CharacterSetOrientation[] characterSetOrientations, double metricNormalizationFactor)
+ CharacterSetOrientation[] characterSetOrientations, ValueNormalizer normalizer)
throws IOException {
byte[] data = structuredFieldReader.getNext(FONT_POSITION_SF);
@@ -493,48 +505,34 @@ public abstract class CharacterSetBuilder {
if (position == 9) {
CharacterSetOrientation characterSetOrientation
= characterSetOrientations[characterSetOrientationIndex];
-
int xHeight = getSBIN(fpData, 2);
int capHeight = getSBIN(fpData, 4);
int ascHeight = getSBIN(fpData, 6);
int dscHeight = getSBIN(fpData, 8);
-
dscHeight = dscHeight * -1;
-
- characterSetOrientation.setXHeight(
- (int)Math.round(xHeight * metricNormalizationFactor));
- characterSetOrientation.setCapHeight(
- (int)Math.round(capHeight * metricNormalizationFactor));
- characterSetOrientation.setAscender(
- (int)Math.round(ascHeight * metricNormalizationFactor));
- characterSetOrientation.setDescender(
- (int)Math.round(dscHeight * metricNormalizationFactor));
+ int underscoreWidth = getUBIN(fpData, 17);
+ int underscorePosition = getSBIN(fpData, 20);
+ characterSetOrientation.setXHeight(normalizer.normalize(xHeight));
+ characterSetOrientation.setCapHeight(normalizer.normalize(capHeight));
+ characterSetOrientation.setAscender(normalizer.normalize(ascHeight));
+ characterSetOrientation.setDescender(normalizer.normalize(dscHeight));
+ characterSetOrientation.setUnderscoreWidth(normalizer.normalize(underscoreWidth));
+ characterSetOrientation.setUnderscorePosition(normalizer.normalize(underscorePosition));
}
} else if (position == 22) {
position = 0;
characterSetOrientationIndex++;
fpData[position] = data[index];
}
-
position++;
}
}
- /**
- * Process the font index details for the character set orientation.
- *
- * @param structuredFieldReader the structured field reader
- * @param cso the CharacterSetOrientation object to populate
- * @param codepage the map of code pages
- * @param metricNormalizationFactor factor to apply to the metrics to get normalized
- * font metric values
- * @throws IOException if an I/O exception of some sort has occurred.
- */
- private void processFontIndex(StructuredFieldReader structuredFieldReader,
- CharacterSetOrientation cso, Map<String, String> codepage,
- double metricNormalizationFactor)
- throws IOException {
+
+ private void processFontIndex(StructuredFieldReader structuredFieldReader, CharacterSetOrientation cso,
+ Map<String, String> codepage, ValueNormalizer normalizer)
+ throws IOException {
byte[] data = structuredFieldReader.getNext(FONT_INDEX_SF);
@@ -543,8 +541,6 @@ public abstract class CharacterSetBuilder {
byte[] gcgid = new byte[8];
byte[] fiData = new byte[20];
- char lowest = 255;
- char highest = 0;
String firstABCMismatch = null;
// Read data, ignoring bytes 0 - 2
@@ -569,13 +565,15 @@ public abstract class CharacterSetBuilder {
char cidx = idx.charAt(0);
int width = getUBIN(fiData, 0);
+ int ascendHt = getSBIN(fiData, 2);
+ int descendDp = getSBIN(fiData, 4);
int a = getSBIN(fiData, 10);
int b = getUBIN(fiData, 12);
int c = getSBIN(fiData, 14);
int abc = a + b + c;
int diff = Math.abs(abc - width);
if (diff != 0 && width != 0) {
- double diffPercent = 100 * diff / (double)width;
+ double diffPercent = 100 * diff / (double) width;
if (diffPercent > 2) {
if (LOG.isTraceEnabled()) {
LOG.trace(gcgiString + ": "
@@ -587,27 +585,16 @@ public abstract class CharacterSetBuilder {
}
}
}
-
- if (cidx < lowest) {
- lowest = cidx;
- }
-
- if (cidx > highest) {
- highest = cidx;
- }
-
- int normalizedWidth = (int)Math.round(width * metricNormalizationFactor);
-
- cso.setWidth(cidx, normalizedWidth);
-
+ int normalizedWidth = normalizer.normalize(width);
+ int x0 = normalizer.normalize(a);
+ int y0 = normalizer.normalize(-descendDp);
+ int dx = normalizer.normalize(b);
+ int dy = normalizer.normalize(ascendHt + descendDp);
+ cso.setCharacterMetrics(cidx, normalizedWidth, new Rectangle(x0, y0, dx, dy));
}
-
}
}
- cso.setFirstChar(lowest);
- cso.setLastChar(highest);
-
if (LOG.isDebugEnabled() && firstABCMismatch != null) {
//Debug level because it usually is no problem.
LOG.debug("Font has metrics inconsitencies where A+B+C doesn't equal the"
diff --git a/src/java/org/apache/fop/afp/fonts/CharacterSetOrientation.java b/src/java/org/apache/fop/afp/fonts/CharacterSetOrientation.java
index a730525d2..5fe524536 100644
--- a/src/java/org/apache/fop/afp/fonts/CharacterSetOrientation.java
+++ b/src/java/org/apache/fop/afp/fonts/CharacterSetOrientation.java
@@ -19,7 +19,7 @@
package org.apache.fop.afp.fonts;
-import java.util.Arrays;
+import java.awt.Rectangle;
/**
* The IBM Font Object Content Architecture (FOCA) supports presentation
@@ -60,23 +60,13 @@ public class CharacterSetOrientation {
/**
* The character widths in the character set (indexed using Unicode codepoints)
*/
- private int[] charsWidths;
+ private IntegerKeyStore<CharacterMetrics> characterMetrics;
/**
* The height of lowercase letters
*/
private int xHeight;
- /**
- * The first character (Unicode codepoint)
- */
- private char firstChar;
-
- /**
- * The last character (Unicode codepoint)
- */
- private char lastChar;
-
/** The character set orientation */
private final int orientation;
/** space increment */
@@ -86,6 +76,10 @@ public class CharacterSetOrientation {
/** Nominal Character Increment */
private final int nomCharIncrement;
+ private int underscoreWidth;
+
+ private int underscorePosition;
+
/**
* Constructor for the CharacterSetOrientation, the orientation is
* expressed as the degrees rotation (i.e 0, 90, 180, 270)
@@ -97,8 +91,7 @@ public class CharacterSetOrientation {
this.spaceIncrement = spaceIncrement;
this.emSpaceIncrement = emSpaceIncrement;
this.nomCharIncrement = nomCharIncrement;
- charsWidths = new int[256];
- Arrays.fill(charsWidths, -1);
+ this.characterMetrics = new IntegerKeyStore<CharacterMetrics>();
}
/**
@@ -138,19 +131,17 @@ public class CharacterSetOrientation {
}
/**
- * The first character in the character set
- * @return the first character (Unicode codepoint)
+ * TODO
*/
- public char getFirstChar() {
- return firstChar;
+ public int getUnderscoreWidth() {
+ return underscoreWidth;
}
/**
- * The last character in the character set
- * @return the last character (Unicode codepoint)
+ * TODO
*/
- public char getLastChar() {
- return lastChar;
+ public int getUnderscorePosition() {
+ return underscorePosition;
}
/**
@@ -162,17 +153,6 @@ public class CharacterSetOrientation {
}
/**
- * Get the width (in 1/1000ths of a point size) of all characters
- * in this character set.
- * @return the widths of all characters
- */
- public int[] getWidths() {
- int[] arr = new int[(getLastChar() - getFirstChar()) + 1];
- System.arraycopy(charsWidths, getFirstChar(), arr, 0, (getLastChar() - getFirstChar()) + 1);
- return arr;
- }
-
- /**
* XHeight refers to the height of the lower case letters above
* the baseline.
* @return heightX the typical height of characters
@@ -187,13 +167,38 @@ public class CharacterSetOrientation {
* @param character the Unicode character to evaluate
* @return the widths of the character
*/
- public int getWidth(char character) {
- if (character >= charsWidths.length) {
- throw new IllegalArgumentException("Invalid character: "
- + character + " (" + Integer.toString(character)
- + "), maximum is " + (charsWidths.length - 1));
+ public int getWidth(char character, int size) {
+ CharacterMetrics cm = getCharacterMetrics(character);
+ return cm == null ? -1 : size * cm.width;
+ }
+
+ private CharacterMetrics getCharacterMetrics(char character) {
+ return characterMetrics.get((int) character);
+ }
+
+ /**
+ * Get the character box (rectangle with dimensions in 1/1000ths of a point size) of the character
+ * identified by the parameter passed.
+ * @param character the Unicode character to evaluate
+ * @return the character box
+ */
+ public Rectangle getCharacterBox(char character, int size) {
+ CharacterMetrics cm = getCharacterMetrics(character);
+ return scale(cm == null ? getFallbackCharacterBox() : cm.characterBox, size);
+ }
+
+ private static Rectangle scale(Rectangle rectangle, int size) {
+ if (rectangle == null) {
+ return null;
+ } else {
+ return new Rectangle((int) (size * rectangle.getX()), (int) (size * rectangle.getY()),
+ (int) (size * rectangle.getWidth()), (int) (size * rectangle.getHeight()));
}
- return charsWidths[character];
+ }
+
+ private Rectangle getFallbackCharacterBox() {
+ // TODO replace with something sensible
+ return new Rectangle(0, 0, 0, 0);
}
/**
@@ -233,19 +238,19 @@ public class CharacterSetOrientation {
}
/**
- * The first character in the character set
- * @param firstChar the first character
+ * TODO
+ * @param underscoreWidth the underscore width value in millipoints
*/
- public void setFirstChar(char firstChar) {
- this.firstChar = firstChar;
+ public void setUnderscoreWidth(int underscoreWidth) {
+ this.underscoreWidth = underscoreWidth;
}
/**
- * The last character in the character set
- * @param lastChar the last character
+ * TODO
+ * @param underscorePosition the underscore position value in millipoints
*/
- public void setLastChar(char lastChar) {
- this.lastChar = lastChar;
+ public void setUnderscorePosition(int underscorePosition) {
+ this.underscorePosition = underscorePosition;
}
/**
@@ -254,17 +259,8 @@ public class CharacterSetOrientation {
* @param character the Unicode character for which the width is being set
* @param width the widths of the character
*/
- public void setWidth(char character, int width) {
- if (character >= charsWidths.length) {
- // Increase the size of the array if necessary
- // TODO Can we remove firstChar? surely firstChar==0 at this stage?
- int[] arr = new int[(character - firstChar) + 1];
- System.arraycopy(charsWidths, 0, arr, 0, charsWidths.length);
- Arrays.fill(arr, charsWidths.length, character - firstChar, -1);
- charsWidths = arr;
- }
- charsWidths[character] = width;
-
+ public void setCharacterMetrics(char character, int width, Rectangle characterBox) {
+ characterMetrics.put((int) character, new CharacterMetrics(width, characterBox));
}
/**
@@ -299,4 +295,16 @@ public class CharacterSetOrientation {
public int getNominalCharIncrement() {
return this.nomCharIncrement;
}
+
+ private static class CharacterMetrics {
+
+ public final int width;
+
+ public final Rectangle characterBox;
+
+ public CharacterMetrics(int width, Rectangle characterBox) {
+ this.width = width;
+ this.characterBox = characterBox;
+ }
+ }
}
diff --git a/src/java/org/apache/fop/afp/fonts/DoubleByteFont.java b/src/java/org/apache/fop/afp/fonts/DoubleByteFont.java
index 78da6ea82..5b9bf6101 100644
--- a/src/java/org/apache/fop/afp/fonts/DoubleByteFont.java
+++ b/src/java/org/apache/fop/afp/fonts/DoubleByteFont.java
@@ -19,6 +19,7 @@
package org.apache.fop.afp.fonts;
+import java.awt.Rectangle;
import java.lang.Character.UnicodeBlock;
import java.util.HashSet;
import java.util.Set;
@@ -68,7 +69,7 @@ public class DoubleByteFont extends AbstractOutlineFont {
public int getWidth(int character, int size) {
int charWidth;
try {
- charWidth = charSet.getWidth(toUnicodeCodepoint(character));
+ charWidth = charSet.getWidth(toUnicodeCodepoint(character), size);
} catch (IllegalArgumentException e) {
if (!charsProcessed.contains(character)) {
charsProcessed.add(character);
@@ -80,9 +81,9 @@ public class DoubleByteFont extends AbstractOutlineFont {
}
if (charWidth == -1) {
- charWidth = getDefaultCharacterWidth(character);
+ charWidth = getDefaultCharacterWidth(character) * size;
}
- return charWidth * size;
+ return charWidth;
}
private int getDefaultCharacterWidth(int character) {
@@ -94,6 +95,33 @@ public class DoubleByteFont extends AbstractOutlineFont {
}
}
+ @Override
+ public Rectangle getBoundingBox(int character, int size) {
+ Rectangle characterBox = getBoundingBoxOrNull(character, size);
+ if (characterBox == null) {
+ characterBox = getDefaultCharacterBox(character, size);
+ }
+ return characterBox;
+ }
+
+ private Rectangle getBoundingBoxOrNull(int character, int size) {
+ Rectangle characterBox = null;
+ try {
+ characterBox = charSet.getCharacterBox(toUnicodeCodepoint(character), size);
+ } catch (IllegalArgumentException e) {
+ if (!charsProcessed.contains(character)) {
+ charsProcessed.add(character);
+ getAFPEventProducer().charactersetMissingMetrics(this, (char) character,
+ charSet.getName().trim());
+ }
+ }
+ return characterBox;
+ }
+
+ private Rectangle getDefaultCharacterBox(int character, int size) {
+ return getBoundingBoxOrNull('-', size);
+ }
+
private int inferCharWidth(int character) {
//Is this character an ideograph?
diff --git a/src/java/org/apache/fop/afp/fonts/FopCharacterSet.java b/src/java/org/apache/fop/afp/fonts/FopCharacterSet.java
index 7c2b68506..b729a8995 100644
--- a/src/java/org/apache/fop/afp/fonts/FopCharacterSet.java
+++ b/src/java/org/apache/fop/afp/fonts/FopCharacterSet.java
@@ -19,6 +19,8 @@
package org.apache.fop.afp.fonts;
+import java.awt.Rectangle;
+
import org.apache.fop.afp.AFPEventProducer;
import org.apache.fop.afp.util.AFPResourceAccessor;
import org.apache.fop.fonts.Typeface;
@@ -84,45 +86,31 @@ public class FopCharacterSet extends CharacterSet {
}
/**
- * The first character in the character set
- * @return the first character
+ * XHeight refers to the height of the lower case letters above the baseline.
+ * @return the typical height of characters
*/
- public char getFirstChar() {
- return 0;
+ public int getXHeight() {
+ return charSet.getXHeight(1);
}
- /**
- * The last character in the character set
- * @return the last character
- */
- public char getLastChar() {
- return 0;
+ @Override
+ public int getWidth(char character, int size) {
+ return charSet.getWidth(character, size);
}
- /**
- * Get the width (in 1/1000ths of a point size) of all characters
- * @return the widths of all characters
- */
- public int[] getWidths() {
- return charSet.getWidths();
- }
+ @Override
+ public Rectangle getCharacterBox(char character, int size) {
+ return charSet.getBoundingBox(character, size);
+ };
- /**
- * XHeight refers to the height of the lower case letters above the baseline.
- * @return the typical height of characters
- */
- public int getXHeight() {
- return charSet.getXHeight(1);
+ @Override
+ public int getUnderscoreWidth() {
+ return charSet.getUnderlineThickness(1);
}
- /**
- * Get the width (in 1/1000ths of a point size) of the character
- * identified by the parameter passed.
- * @param character the character from which the width will be calculated
- * @return the width of the character
- */
- public int getWidth(char character) {
- return charSet.getWidth(character, 1);
+ @Override
+ public int getUnderscorePosition() {
+ return charSet.getUnderlinePosition(1);
}
/**
diff --git a/src/java/org/apache/fop/afp/fonts/IntegerKeyStore.java b/src/java/org/apache/fop/afp/fonts/IntegerKeyStore.java
new file mode 100644
index 000000000..7e73b5b9f
--- /dev/null
+++ b/src/java/org/apache/fop/afp/fonts/IntegerKeyStore.java
@@ -0,0 +1,71 @@
+/*
+ * 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.fonts;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A simple compact data structure to model a sparse array
+ */
+class IntegerKeyStore<T> {
+
+ private static final int RANGE_BIT_SIZE = 8;
+
+ private static final int RANGE_SIZE = 1 << RANGE_BIT_SIZE;
+
+ private final Map<Integer, ArrayList<T>> arrays = new HashMap<Integer, ArrayList<T>>();
+
+ /**
+ *
+ * @param index a positive integer
+ * @param value value to store
+ */
+ public void put(Integer index, T value) {
+ if (index < 0) {
+ throw new IndexOutOfBoundsException();
+ }
+ int rangeKey = index >> RANGE_BIT_SIZE;
+ int rangeIndex = index % RANGE_SIZE;
+ ArrayList<T> range = arrays.get(rangeKey);
+ if (range == null) {
+ range = new ArrayList<T>(Collections.<T>nCopies(RANGE_SIZE, null));
+ arrays.put(rangeKey, range);
+ }
+ range.set(rangeIndex, value);
+ }
+
+ /**
+ *
+ * @param index a positive integer
+ * @return value the value associated with the index or null
+ */
+ public T get(Integer index) {
+ if (index < 0) {
+ throw new IndexOutOfBoundsException();
+ }
+ int rangeKey = index >> RANGE_BIT_SIZE;
+ int rangeIndex = index % RANGE_SIZE;
+ ArrayList<T> range = arrays.get(rangeKey);
+ return range == null ? null : range.get(rangeIndex);
+ }
+}
diff --git a/src/java/org/apache/fop/afp/fonts/OutlineFont.java b/src/java/org/apache/fop/afp/fonts/OutlineFont.java
index e9cdf5ba4..fc2332ce9 100644
--- a/src/java/org/apache/fop/afp/fonts/OutlineFont.java
+++ b/src/java/org/apache/fop/afp/fonts/OutlineFont.java
@@ -19,6 +19,8 @@
package org.apache.fop.afp.fonts;
+import java.awt.Rectangle;
+
import org.apache.fop.afp.AFPEventProducer;
/**
@@ -38,4 +40,18 @@ public class OutlineFont extends AbstractOutlineFont {
super(name, embeddable, charSet, eventProducer);
}
+ /**
+ * Obtain the width of the character for the specified point size.
+ * @param character the character
+ * @param size the font size (in mpt)
+ * @return the width of the character for the specified point size
+ */
+ public int getWidth(int character, int size) {
+ return charSet.getWidth(toUnicodeCodepoint(character), size);
+ }
+
+ @Override
+ public Rectangle getBoundingBox(int character, int size) {
+ return charSet.getCharacterBox(toUnicodeCodepoint(character), size);
+ }
}
diff --git a/src/java/org/apache/fop/afp/fonts/RasterFont.java b/src/java/org/apache/fop/afp/fonts/RasterFont.java
index 5c4c38dc5..1fd30e0ba 100644
--- a/src/java/org/apache/fop/afp/fonts/RasterFont.java
+++ b/src/java/org/apache/fop/afp/fonts/RasterFont.java
@@ -19,8 +19,8 @@
package org.apache.fop.afp.fonts;
+import java.awt.Rectangle;
import java.util.HashMap;
-import java.util.Iterator;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
@@ -135,46 +135,21 @@ public class RasterFont extends AFPFont {
}
- /**
- * Get the first character in this font.
- * @return the first character in this font.
- */
- public int getFirstChar() {
- Iterator<CharacterSet> it = charSets.values().iterator();
- if (it.hasNext()) {
- CharacterSet csm = it.next();
- return csm.getFirstChar();
- } else {
- String msg = "getFirstChar() - No character set found for font:" + getFontName();
- LOG.error(msg);
- throw new FontRuntimeException(msg);
- }
- }
-
- /**
- * Get the last character in this font.
- * @return the last character in this font.
- */
- public int getLastChar() {
-
- Iterator<CharacterSet> it = charSets.values().iterator();
- if (it.hasNext()) {
- CharacterSet csm = it.next();
- return csm.getLastChar();
+ private int metricsToAbsoluteSize(CharacterSet cs, int value, int givenSize) {
+ int nominalVerticalSize = cs.getNominalVerticalSize();
+ if (nominalVerticalSize != 0) {
+ return value * nominalVerticalSize;
} else {
- String msg = "getLastChar() - No character set found for font:" + getFontName();
- LOG.error(msg);
- throw new FontRuntimeException(msg);
+ return value * givenSize;
}
-
}
- private int metricsToAbsoluteSize(CharacterSet cs, int value, int givenSize) {
+ private int metricsToAbsoluteSize(CharacterSet cs, double value, int givenSize) {
int nominalVerticalSize = cs.getNominalVerticalSize();
if (nominalVerticalSize != 0) {
- return value * nominalVerticalSize;
+ return (int) (value * nominalVerticalSize);
} else {
- return value * givenSize;
+ return (int) (value * givenSize);
}
}
@@ -191,6 +166,20 @@ public class RasterFont extends AFPFont {
return metricsToAbsoluteSize(cs, cs.getAscender(), size);
}
+ /** {@inheritDoc} */
+ public int getUnderlinePosition(int size) {
+ CharacterSet cs = getCharacterSet(size);
+ return metricsToAbsoluteSize(cs, cs.getUnderscorePosition(), size);
+ }
+
+ @Override
+ public int getUnderlineThickness(int size) {
+ CharacterSet cs = getCharacterSet(size);
+ int underscoreWidth = cs.getUnderscoreWidth();
+ return underscoreWidth == 0 ? super.getUnderlineThickness(size)
+ : metricsToAbsoluteSize(cs, underscoreWidth, size);
+ }
+
/**
* Obtains the height of capital letters for the specified point size.
*
@@ -234,33 +223,20 @@ public class RasterFont extends AFPFont {
*/
public int getWidth(int character, int size) {
CharacterSet cs = getCharacterSet(size);
- return metricsToAbsoluteSize(cs, cs.getWidth(toUnicodeCodepoint(character)), size);
+ return metricsToAbsoluteSize(cs, cs.getWidth(toUnicodeCodepoint(character), 1), size);
}
/**
- * Get the getWidth (in 1/1000ths of a point size) of all characters in this
- * character set.
- *
- * @param size the font size (in mpt)
- * @return the widths of all characters
+ * TODO
*/
- public int[] getWidths(int size) {
+ public Rectangle getBoundingBox(int character, int size) {
CharacterSet cs = getCharacterSet(size);
- int[] widths = cs.getWidths();
- for (int i = 0, c = widths.length; i < c; i++) {
- widths[i] = metricsToAbsoluteSize(cs, widths[i], size);
- }
- return widths;
- }
-
- /**
- * Get the getWidth (in 1/1000ths of a point size) of all characters in this
- * character set.
- *
- * @return the widths of all characters
- */
- public int[] getWidths() {
- return getWidths(1000);
+ Rectangle characterBox = cs.getCharacterBox(toUnicodeCodepoint(character), 1);
+ int x = metricsToAbsoluteSize(cs, characterBox.getX(), size);
+ int y = metricsToAbsoluteSize(cs, characterBox.getY(), size);
+ int w = metricsToAbsoluteSize(cs, characterBox.getWidth(), size);
+ int h = metricsToAbsoluteSize(cs, characterBox.getHeight(), size);
+ return new Rectangle(x, y, w, h);
}
/** {@inheritDoc} */
diff --git a/src/java/org/apache/fop/afp/goca/GraphicsCharacterString.java b/src/java/org/apache/fop/afp/goca/GraphicsCharacterString.java
index 77ad7e806..d41adf867 100644
--- a/src/java/org/apache/fop/afp/goca/GraphicsCharacterString.java
+++ b/src/java/org/apache/fop/afp/goca/GraphicsCharacterString.java
@@ -67,7 +67,11 @@ public class GraphicsCharacterString extends AbstractGraphicsCoord {
/** {@inheritDoc} */
public int getDataLength() {
- return super.getDataLength() + str.length();
+ try {
+ return super.getDataLength() + getStringAsBytes().length;
+ } catch (IOException ioe) {
+ throw new RuntimeException(ioe);
+ }
}
/** {@inheritDoc} */
diff --git a/src/java/org/apache/fop/afp/svg/AFPBridgeContext.java b/src/java/org/apache/fop/afp/svg/AFPBridgeContext.java
index 2c6668454..5c13b9d92 100644
--- a/src/java/org/apache/fop/afp/svg/AFPBridgeContext.java
+++ b/src/java/org/apache/fop/afp/svg/AFPBridgeContext.java
@@ -25,13 +25,17 @@ import org.apache.batik.bridge.BridgeContext;
import org.apache.batik.bridge.DocumentLoader;
import org.apache.batik.bridge.UserAgent;
import org.apache.batik.gvt.TextPainter;
+import org.apache.batik.gvt.font.DefaultFontFamilyResolver;
+import org.apache.batik.gvt.font.FontFamilyResolver;
import org.apache.xmlgraphics.image.loader.ImageManager;
import org.apache.xmlgraphics.image.loader.ImageSessionContext;
import org.apache.fop.afp.AFPGraphics2D;
+import org.apache.fop.events.EventBroadcaster;
import org.apache.fop.fonts.FontInfo;
import org.apache.fop.svg.AbstractFOPBridgeContext;
+import org.apache.fop.svg.font.AggregatingFontFamilyResolver;
/**
* An AFP specific implementation of a Batik BridgeContext
@@ -40,6 +44,8 @@ public class AFPBridgeContext extends AbstractFOPBridgeContext {
private final AFPGraphics2D g2d;
+ private final EventBroadcaster eventBroadCaster;
+
/**
* Constructs a new bridge context.
*
@@ -54,47 +60,35 @@ public class AFPBridgeContext extends AbstractFOPBridgeContext {
*/
public AFPBridgeContext(UserAgent userAgent, FontInfo fontInfo,
ImageManager imageManager, ImageSessionContext imageSessionContext,
- AffineTransform linkTransform, AFPGraphics2D g2d) {
+ AffineTransform linkTransform, AFPGraphics2D g2d, EventBroadcaster eventBroadCaster) {
super(userAgent, fontInfo, imageManager, imageSessionContext, linkTransform);
this.g2d = g2d;
+ this.eventBroadCaster = eventBroadCaster;
}
- /**
- * Constructs a new bridge context.
- * @param userAgent the user agent
- * @param documentLoader the Document Loader to use for referenced documents.
- * @param fontInfo the font list for the text painter, may be null
- * in which case text is painted as shapes
- * @param imageManager an image manager
- * @param imageSessionContext an image session context
- * @param linkTransform AffineTransform to properly place links,
- * may be null
- * @param g2d an AFPGraphics 2D implementation
- */
- public AFPBridgeContext(UserAgent userAgent, DocumentLoader documentLoader,
+ private AFPBridgeContext(UserAgent userAgent, DocumentLoader documentLoader,
FontInfo fontInfo, ImageManager imageManager,
ImageSessionContext imageSessionContext,
- AffineTransform linkTransform, AFPGraphics2D g2d) {
- super(userAgent, documentLoader, fontInfo, imageManager,
- imageSessionContext, linkTransform);
+ AffineTransform linkTransform, AFPGraphics2D g2d, EventBroadcaster eventBroadCaster) {
+ super(userAgent, documentLoader, fontInfo, imageManager, imageSessionContext, linkTransform);
this.g2d = g2d;
+ this.eventBroadCaster = eventBroadCaster;
}
/** {@inheritDoc} */
@Override
public void registerSVGBridges() {
super.registerSVGBridges();
-
if (fontInfo != null) {
AFPTextHandler textHandler = new AFPTextHandler(fontInfo, g2d.getResourceManager());
g2d.setCustomTextHandler(textHandler);
-
- TextPainter textPainter = new AFPTextPainter(textHandler);
- setTextPainter(textPainter);
-
+ //TODO
+ FontFamilyResolver fontFamilyResolver = new AggregatingFontFamilyResolver(
+ new AFPFontFamilyResolver(fontInfo, eventBroadCaster), DefaultFontFamilyResolver.SINGLETON);
+ TextPainter textPainter = new AFPTextPainter(textHandler, fontFamilyResolver);
+ setTextPainter(new AFPTextPainter(textHandler, fontFamilyResolver));
putBridge(new AFPTextElementBridge(textPainter));
}
-
putBridge(new AFPImageElementBridge());
}
@@ -105,7 +99,7 @@ public class AFPBridgeContext extends AbstractFOPBridgeContext {
fontInfo,
getImageManager(),
getImageSessionContext(),
- linkTransform, g2d);
+ linkTransform, g2d, eventBroadCaster);
}
}
diff --git a/src/java/org/apache/fop/afp/svg/AFPFontFamilyResolver.java b/src/java/org/apache/fop/afp/svg/AFPFontFamilyResolver.java
new file mode 100644
index 000000000..27026f4f3
--- /dev/null
+++ b/src/java/org/apache/fop/afp/svg/AFPFontFamilyResolver.java
@@ -0,0 +1,84 @@
+/*
+ * 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.svg;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.fop.afp.AFPEventProducer;
+import org.apache.fop.afp.fonts.DoubleByteFont;
+import org.apache.fop.events.EventBroadcaster;
+import org.apache.fop.fonts.Font;
+import org.apache.fop.fonts.FontInfo;
+import org.apache.fop.fonts.FontTriplet;
+import org.apache.fop.fonts.Typeface;
+import org.apache.fop.svg.font.FOPFontFamilyResolverImpl;
+import org.apache.fop.svg.font.FOPGVTFontFamily;
+import org.apache.fop.svg.font.FilteringFontFamilyResolver;
+
+public class AFPFontFamilyResolver extends FilteringFontFamilyResolver {
+
+ private final FontInfo fontInfo;
+
+ private final AFPEventProducer eventProducer;
+
+
+ public AFPFontFamilyResolver(FontInfo fontInfo, EventBroadcaster eventBroadCaster) {
+ super(new FOPFontFamilyResolverImpl(fontInfo));
+ this.fontInfo = fontInfo;
+ this.eventProducer = AFPEventProducer.Provider.get(eventBroadCaster);
+ }
+
+ @Override
+ public FOPGVTFontFamily resolve(String familyName) {
+ FOPGVTFontFamily fopGVTFontFamily = super.resolve(familyName);
+ // TODO why don't DB fonts work with GOCA?!?
+ if (fopGVTFontFamily != null && fopGVTFontFamily.deriveFont(1, new HashMap())
+ .getFont().getFontMetrics() instanceof DoubleByteFont) {
+ notifyDBFontRejection(fopGVTFontFamily.getFamilyName());
+ fopGVTFontFamily = null;
+ }
+ return fopGVTFontFamily;
+ }
+
+ @Override
+ public FOPGVTFontFamily getFamilyThatCanDisplay(char c) {
+ Map<String, Typeface> fonts = fontInfo.getFonts();
+ for (Typeface font : fonts.values()) {
+ // TODO why don't DB fonts work with GOCA?!?
+ if (font.hasChar(c) && !(font instanceof DoubleByteFont)) {
+ String fontFamily = font.getFamilyNames().iterator().next();
+ if (font instanceof DoubleByteFont) {
+ notifyDBFontRejection(font.getFontName());
+ } else {
+ return new FOPGVTFontFamily(fontInfo, fontFamily,
+ new FontTriplet(fontFamily, Font.STYLE_NORMAL, Font.WEIGHT_NORMAL));
+ }
+
+ }
+ }
+ return null;
+ }
+
+ private void notifyDBFontRejection(String fontFamily) {
+ eventProducer.invalidDBFontInSVG(this, fontFamily);
+ }
+
+}
diff --git a/src/java/org/apache/fop/afp/svg/AFPTextHandler.java b/src/java/org/apache/fop/afp/svg/AFPTextHandler.java
index 2bb4cb60e..3e987648e 100644
--- a/src/java/org/apache/fop/afp/svg/AFPTextHandler.java
+++ b/src/java/org/apache/fop/afp/svg/AFPTextHandler.java
@@ -135,27 +135,18 @@ public class AFPTextHandler extends FOPTextHandlerAdapter {
if (log.isDebugEnabled()) {
log.debug(" with overriding font: " + internalFontName + ", " + fontSize);
}
- } else {
- java.awt.Font awtFont = g2d.getFont();
- Font fopFont = fontInfo.getFontInstanceForAWTFont(awtFont);
- if (log.isDebugEnabled()) {
- log.debug(" with font: " + fopFont);
- }
- internalFontName = fopFont.getFontName();
- fontSize = fopFont.getFontSize();
+ fontSize = (int) Math.round(g2d.convertToAbsoluteLength(fontSize));
+ fontReference = registerPageFont(pageFonts, internalFontName, fontSize);
+ // TODO: re-think above registerPageFont code...
+ AFPFont afpFont = (AFPFont) fontInfo.getFonts().get(internalFontName);
+ final CharacterSet charSet = afpFont.getCharacterSet(fontSize);
+ // Work-around for InfoPrint's AFP which loses character set state
+ // over Graphics Data
+ // boundaries.
+ graphicsObj.setCharacterSet(fontReference);
+ // add the character string
+ graphicsObj.addString(str, Math.round(x), Math.round(y), charSet);
}
- fontSize = (int)Math.round(
- g2d.convertToAbsoluteLength(fontSize));
- fontReference = registerPageFont(pageFonts, internalFontName, fontSize);
- // TODO: re-think above registerPageFont code...
- AFPFont afpFont = (AFPFont) fontInfo.getFonts().get(internalFontName);
- final CharacterSet charSet = afpFont.getCharacterSet(fontSize);
- // Work-around for InfoPrint's AFP which loses character set state
- // over Graphics Data
- // boundaries.
- graphicsObj.setCharacterSet(fontReference);
- // add the character string
- graphicsObj.addString(str, Math.round(x), Math.round(y), charSet);
} else {
//Inside Batik's SVG filter operations, you won't get an AFPGraphics2D
g.drawString(str, x, y);
diff --git a/src/java/org/apache/fop/afp/svg/AFPTextPainter.java b/src/java/org/apache/fop/afp/svg/AFPTextPainter.java
index 3815c9eae..996ae8691 100644
--- a/src/java/org/apache/fop/afp/svg/AFPTextPainter.java
+++ b/src/java/org/apache/fop/afp/svg/AFPTextPainter.java
@@ -21,6 +21,9 @@ package org.apache.fop.afp.svg;
import java.awt.Graphics2D;
+import org.apache.batik.gvt.font.FontFamilyResolver;
+import org.apache.batik.gvt.renderer.StrokingTextPainter;
+
import org.apache.fop.afp.AFPGraphics2D;
import org.apache.fop.svg.AbstractFOPTextPainter;
import org.apache.fop.svg.FOPTextHandler;
@@ -39,8 +42,8 @@ public class AFPTextPainter extends AbstractFOPTextPainter {
* Create a new text painter with the given font information.
* @param nativeTextHandler the NativeTextHandler instance used for text painting
*/
- public AFPTextPainter(FOPTextHandler nativeTextHandler) {
- super(nativeTextHandler);
+ public AFPTextPainter(FOPTextHandler nativeTextHandler, FontFamilyResolver fopFontFamilyResolver) {
+ super(nativeTextHandler, new FOPStrokingTextPainter(fopFontFamilyResolver));
}
/** {@inheritDoc} */
@@ -48,4 +51,18 @@ public class AFPTextPainter extends AbstractFOPTextPainter {
return g2d instanceof AFPGraphics2D;
}
+ private static class FOPStrokingTextPainter extends StrokingTextPainter {
+
+ private final FontFamilyResolver fopFontFontFamily;
+
+ FOPStrokingTextPainter(FontFamilyResolver fopFontFontFamily) {
+ this.fopFontFontFamily = fopFontFontFamily;
+ }
+
+ @Override
+ protected FontFamilyResolver getFontFamilyResolver() {
+ return fopFontFontFamily;
+ }
+ }
+
}
diff --git a/src/java/org/apache/fop/fo/FOText.java b/src/java/org/apache/fop/fo/FOText.java
index 2fc998c63..0b7dde212 100644
--- a/src/java/org/apache/fop/fo/FOText.java
+++ b/src/java/org/apache/fop/fo/FOText.java
@@ -21,7 +21,6 @@ package org.apache.fop.fo;
import java.awt.Color;
import java.nio.CharBuffer;
-import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Stack;
@@ -38,12 +37,13 @@ import org.apache.fop.fo.properties.CommonTextDecoration;
import org.apache.fop.fo.properties.KeepProperty;
import org.apache.fop.fo.properties.Property;
import org.apache.fop.fo.properties.SpaceProperty;
+import org.apache.fop.fonts.TextFragment;
import org.apache.fop.util.CharUtilities;
/**
* A text node (PCDATA) in the formatting object tree.
*/
-public class FOText extends FONode implements CharSequence {
+public class FOText extends FONode implements CharSequence, TextFragment {
/** the <code>CharBuffer</code> containing the text */
private CharBuffer charBuffer;
@@ -93,9 +93,6 @@ public class FOText extends FONode implements CharSequence {
/* bidi levels */
private int[] bidiLevels;
- /* advanced script processing state */
- private Map/*<MapRange,String>*/ mappings;
-
private static final int IS_WORD_CHAR_FALSE = 0;
private static final int IS_WORD_CHAR_TRUE = 1;
private static final int IS_WORD_CHAR_MAYBE = 2;
@@ -804,93 +801,6 @@ public class FOText extends FONode implements CharSequence {
}
}
- /**
- * Add characters mapped by script substitution processing.
- * @param start index in character buffer
- * @param end index in character buffer
- * @param mappedChars sequence of character codes denoting substituted characters
- */
- public void addMapping(int start, int end, CharSequence mappedChars) {
- if (mappings == null) {
- mappings = new java.util.HashMap();
- }
- mappings.put(new MapRange(start, end), mappedChars.toString());
- }
-
- /**
- * Determine if characters over specific interval have a mapping.
- * @param start index in character buffer
- * @param end index in character buffer
- * @return true if a mapping exist such that the mapping's interval is coincident to
- * [start,end)
- */
- public boolean hasMapping(int start, int end) {
- return (mappings != null) && (mappings.containsKey(new MapRange(start, end)));
- }
-
- /**
- * Obtain mapping of characters over specific interval.
- * @param start index in character buffer
- * @param end index in character buffer
- * @return a string of characters representing the mapping over the interval
- * [start,end)
- */
- public String getMapping(int start, int end) {
- if (mappings != null) {
- return (String) mappings.get(new MapRange(start, end));
- } else {
- return null;
- }
- }
-
- /**
- * Obtain length of mapping of characters over specific interval.
- * @param start index in character buffer
- * @param end index in character buffer
- * @return the length of the mapping (if present) or zero
- */
- public int getMappingLength(int start, int end) {
- if (mappings != null) {
- return ((String) mappings.get(new MapRange(start, end))) .length();
- } else {
- return 0;
- }
- }
-
- /**
- * Obtain bidirectional levels of mapping of characters over specific interval.
- * @param start index in character buffer
- * @param end index in character buffer
- * @return a (possibly empty) array of bidi levels or null
- * in case no bidi levels have been assigned
- */
- public int[] getMappingBidiLevels(int start, int end) {
- if (hasMapping(start, end)) {
- int nc = end - start;
- int nm = getMappingLength(start, end);
- int[] la = getBidiLevels(start, end);
- if (la == null) {
- return null;
- } else if (nm == nc) { // mapping is same length as mapped range
- return la;
- } else if (nm > nc) { // mapping is longer than mapped range
- int[] ma = new int [ nm ];
- System.arraycopy(la, 0, ma, 0, la.length);
- for (int i = la.length,
- n = ma.length, l = (i > 0) ? la [ i - 1 ] : 0; i < n; i++) {
- ma [ i ] = l;
- }
- return ma;
- } else { // mapping is shorter than mapped range
- int[] ma = new int [ nm ];
- System.arraycopy(la, 0, ma, 0, ma.length);
- return ma;
- }
- } else {
- return getBidiLevels(start, end);
- }
- }
-
@Override
protected Stack collectDelimitedTextRanges(Stack ranges, DelimitedTextRange currentRange) {
if (currentRange != null) {
diff --git a/src/java/org/apache/fop/fonts/Base14Font.java b/src/java/org/apache/fop/fonts/Base14Font.java
index 9b2e95bc7..fdefd0cdd 100644
--- a/src/java/org/apache/fop/fonts/Base14Font.java
+++ b/src/java/org/apache/fop/fonts/Base14Font.java
@@ -25,4 +25,15 @@ package org.apache.fop.fonts;
*/
public abstract class Base14Font extends Typeface {
+ /** Thickness for underline and strikeout. */
+ private static final int LINE_THICKNESS = 50;
+
+ public int getStrikeoutPosition(int size) {
+ return getXHeight(size) / 2;
+ }
+
+ public int getStrikeoutThickness(int size) {
+ return size * LINE_THICKNESS;
+ }
+
}
diff --git a/src/java/org/apache/fop/fonts/CIDFont.java b/src/java/org/apache/fop/fonts/CIDFont.java
index dc398263e..26212ea56 100644
--- a/src/java/org/apache/fop/fonts/CIDFont.java
+++ b/src/java/org/apache/fop/fonts/CIDFont.java
@@ -19,6 +19,7 @@
package org.apache.fop.fonts;
+
import org.apache.fop.apps.io.InternalResourceResolver;
//Java
diff --git a/src/java/org/apache/fop/fonts/CustomFont.java b/src/java/org/apache/fop/fonts/CustomFont.java
index 70961a55c..96b70feea 100644
--- a/src/java/org/apache/fop/fonts/CustomFont.java
+++ b/src/java/org/apache/fop/fonts/CustomFont.java
@@ -37,6 +37,9 @@ import org.apache.fop.apps.io.InternalResourceResolver;
public abstract class CustomFont extends Typeface
implements FontDescriptor, MutableFont {
+ /** Fallback thickness for underline and strikeout when not provided by the font. */
+ private static final int DEFAULT_LINE_THICKNESS = 50;
+
private String fontName;
private String fullName;
private Set<String> familyNames;
@@ -60,6 +63,14 @@ public abstract class CustomFont extends Typeface
private int firstChar;
private int lastChar = 255;
+ private int underlinePosition;
+
+ private int underlineThickness;
+
+ private int strikeoutPosition;
+
+ private int strikeoutThickness;
+
private Map<Integer, Map<Integer, Integer>> kerning;
private boolean useKerning = true;
@@ -507,4 +518,40 @@ public abstract class CustomFont extends Typeface
return copy;
}
+ public int getUnderlinePosition(int size) {
+ return (underlinePosition == 0)
+ ? getDescender(size) / 2
+ : size * underlinePosition;
+ }
+
+ public void setUnderlinePosition(int underlinePosition) {
+ this.underlinePosition = underlinePosition;
+ }
+
+ public int getUnderlineThickness(int size) {
+ return size * ((underlineThickness == 0) ? DEFAULT_LINE_THICKNESS : underlineThickness);
+ }
+
+ public void setUnderlineThickness(int underlineThickness) {
+ this.underlineThickness = underlineThickness;
+ }
+
+ public int getStrikeoutPosition(int size) {
+ return (strikeoutPosition == 0)
+ ? getXHeight(size) / 2
+ : size * strikeoutPosition;
+ }
+
+ public void setStrikeoutPosition(int strikeoutPosition) {
+ this.strikeoutPosition = strikeoutPosition;
+ }
+
+ public int getStrikeoutThickness(int size) {
+ return (strikeoutThickness == 0) ? getUnderlineThickness(size) : size * strikeoutThickness;
+ }
+
+ public void setStrikeoutThickness(int strikeoutThickness) {
+ this.strikeoutThickness = strikeoutThickness;
+ }
+
}
diff --git a/src/java/org/apache/fop/fonts/FontInfo.java b/src/java/org/apache/fop/fonts/FontInfo.java
index 472567eec..720531205 100644
--- a/src/java/org/apache/fop/fonts/FontInfo.java
+++ b/src/java/org/apache/fop/fonts/FontInfo.java
@@ -30,6 +30,7 @@ import java.util.TreeSet;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+
/**
* The FontInfo holds font information for the layout and rendering of a fo document.
* This stores the list of available fonts that are setup by
@@ -135,12 +136,10 @@ public class FontInfo {
if (oldName != null) {
int oldPriority = tripletPriorities.get(triplet).intValue();
if (oldPriority < newPriority) {
- logDuplicateFont(triplet, false, oldName, oldPriority,
- internalFontKey, newPriority);
+ logDuplicateFont(triplet, false, oldName, oldPriority, internalFontKey, newPriority);
return;
} else {
- logDuplicateFont(triplet, true, oldName, oldPriority,
- internalFontKey, newPriority);
+ logDuplicateFont(triplet, true, oldName, oldPriority, internalFontKey, newPriority);
}
}
this.triplets.put(triplet, internalFontKey);
@@ -157,9 +156,8 @@ public class FontInfo {
* @param newKey the new internal font name
* @param newPriority the priority of the duplicate font mapping
*/
- private void logDuplicateFont(FontTriplet triplet, boolean replacing,
- String oldKey, int oldPriority,
- String newKey, int newPriority) {
+ private void logDuplicateFont(FontTriplet triplet, boolean replacing, String oldKey, int oldPriority,
+ String newKey, int newPriority) {
if (log.isDebugEnabled()) {
log.debug(triplet
+ (replacing ? ": Replacing " : ": Not replacing ")
@@ -198,8 +196,7 @@ public class FontInfo {
* default font if not found
* @return internal font triplet key
*/
- private FontTriplet fontLookup(String family, String style,
- int weight, boolean substitutable) {
+ private FontTriplet fontLookup(String family, String style, int weight, boolean substitutable) {
if (log.isTraceEnabled()) {
log.trace("Font lookup: " + family + " " + style + " " + weight
+ (substitutable ? " substitutable" : ""));
@@ -302,8 +299,7 @@ public class FontInfo {
* @return the requested Font instance
*/
public Font getFontInstance(FontTriplet triplet, int fontSize) {
- Map<Integer, Font> sizes
- = getFontInstanceCache().get(triplet);
+ Map<Integer, Font> sizes = getFontInstanceCache().get(triplet);
if (sizes == null) {
sizes = new HashMap<Integer, Font>();
getFontInstanceCache().put(triplet, sizes);
@@ -379,13 +375,11 @@ public class FontInfo {
* @param weight font weight
* @return the font triplet of the font chosen
*/
- public FontTriplet fontLookup(String family, String style,
- int weight) {
+ public FontTriplet fontLookup(String family, String style, int weight) {
return fontLookup(family, style, weight, true);
}
- private List<FontTriplet> fontLookup(String[] families, String style,
- int weight, boolean substitutable) {
+ private List<FontTriplet> fontLookup(String[] families, String style, int weight, boolean substitutable) {
List<FontTriplet> matchingTriplets = new ArrayList<FontTriplet>();
FontTriplet triplet = null;
for (int i = 0; i < families.length; i++) {
@@ -410,8 +404,7 @@ public class FontInfo {
* @return the set of font triplets of all supported and chosen font-families
* in the specified style and weight.
*/
- public FontTriplet[] fontLookup(String[] families, String style,
- int weight) {
+ public FontTriplet[] fontLookup(String[] families, String style, int weight) {
if (families.length == 0) {
throw new IllegalArgumentException("Specify at least one font family");
}
@@ -434,8 +427,8 @@ public class FontInfo {
sb.append(families[i]);
}
throw new IllegalStateException(
- "fontLookup must return an array with at least one "
- + "FontTriplet on the last call. Lookup: " + sb.toString());
+ "fontLookup must return an array with at least one "
+ + "FontTriplet on the last call. Lookup: " + sb.toString());
}
FontTriplet[] fontTriplets = new FontTriplet[matchedTriplets.size()];
@@ -469,8 +462,7 @@ public class FontInfo {
* @param weight font weight
* @return internal key
*/
- public FontTriplet findAdjustWeight(String family, String style,
- int weight) {
+ public FontTriplet findAdjustWeight(String family, String style, int weight) {
FontTriplet key = null;
String f = null;
int newWeight = weight;
@@ -542,8 +534,7 @@ public class FontInfo {
* @param weight font weight
* @return internal key
*/
- public static FontTriplet createFontKey(String family, String style,
- int weight) {
+ public static FontTriplet createFontKey(String family, String style, int weight) {
return new FontTriplet(family, style, weight);
}
diff --git a/src/java/org/apache/fop/fonts/FontMetrics.java b/src/java/org/apache/fop/fonts/FontMetrics.java
index ff32d7305..159d321f7 100644
--- a/src/java/org/apache/fop/fonts/FontMetrics.java
+++ b/src/java/org/apache/fop/fonts/FontMetrics.java
@@ -19,6 +19,7 @@
package org.apache.fop.fonts;
+import java.awt.Rectangle;
import java.util.Map;
import java.util.Set;
@@ -120,6 +121,15 @@ public interface FontMetrics {
int[] getWidths();
/**
+ * Returns the bounding box of the glyph at the given index, for the given font size.
+ *
+ * @param glyphIndex glyph index
+ * @param size font size
+ * @return the scaled bounding box scaled in 1/1000ths of the given size
+ */
+ Rectangle getBoundingBox(int glyphIndex, int size);
+
+ /**
* Indicates if the font has kering information.
* @return True, if kerning is available.
*/
@@ -131,4 +141,38 @@ public interface FontMetrics {
*/
Map<Integer, Map<Integer, Integer>> getKerningInfo();
+ /**
+ * Returns the distance from the baseline to the center of the underline (negative
+ * value indicates below baseline).
+ *
+ * @param size font size
+ * @return the position in 1/1000ths of the font size
+ */
+ int getUnderlinePosition(int size);
+
+ /**
+ * Returns the thickness of the underline.
+ *
+ * @param size font size
+ * @return the thickness in 1/1000ths of the font size
+ */
+ int getUnderlineThickness(int size);
+
+ /**
+ * Returns the distance from the baseline to the center of the strikeout line
+ * (negative value indicates below baseline).
+ *
+ * @param size font size
+ * @return the position in 1/1000ths of the font size
+ */
+ int getStrikeoutPosition(int size);
+
+ /**
+ * Returns the thickness of the strikeout line.
+ *
+ * @param size font size
+ * @return the thickness in 1/1000ths of the font size
+ */
+ int getStrikeoutThickness(int size);
+
}
diff --git a/src/java/org/apache/fop/fonts/GlyphMapping.java b/src/java/org/apache/fop/fonts/GlyphMapping.java
new file mode 100644
index 000000000..a8a82085e
--- /dev/null
+++ b/src/java/org/apache/fop/fonts/GlyphMapping.java
@@ -0,0 +1,327 @@
+/*
+ * 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.fonts;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.fop.complexscripts.fonts.GlyphPositioningTable;
+import org.apache.fop.complexscripts.util.CharScript;
+import org.apache.fop.traits.MinOptMax;
+import org.apache.fop.util.CharUtilities;
+
+/**
+ * Stores the mapping of a text fragment to glyphs, along with various information.
+ */
+public class GlyphMapping {
+
+ private static final Log LOG = LogFactory.getLog(GlyphMapping.class);
+ /** Inclusive. */
+ public final int startIndex;
+ /** Exclusive. */
+ public final int endIndex;
+ private int wordCharLength;
+ public final int wordSpaceCount;
+ public int letterSpaceCount;
+ public MinOptMax areaIPD;
+ public final boolean isHyphenated;
+ public final boolean isSpace;
+ public boolean breakOppAfter;
+ public final Font font;
+ public final int level;
+ public final int[][] gposAdjustments;
+ public final String mapping;
+
+ public GlyphMapping(int startIndex, int endIndex, int wordSpaceCount, int letterSpaceCount,
+ MinOptMax areaIPD, boolean isHyphenated, boolean isSpace, boolean breakOppAfter,
+ Font font, int level, int[][] gposAdjustments) {
+ this(startIndex, endIndex, wordSpaceCount, letterSpaceCount, areaIPD, isHyphenated,
+ isSpace, breakOppAfter, font, level, gposAdjustments, null);
+ }
+
+ public GlyphMapping(int startIndex, int endIndex, int wordSpaceCount, int letterSpaceCount,
+ MinOptMax areaIPD, boolean isHyphenated, boolean isSpace, boolean breakOppAfter,
+ Font font, int level, int[][] gposAdjustments, String mapping) {
+ assert startIndex <= endIndex;
+ this.startIndex = startIndex;
+ this.endIndex = endIndex;
+ this.wordCharLength = -1;
+ this.wordSpaceCount = wordSpaceCount;
+ this.letterSpaceCount = letterSpaceCount;
+ this.areaIPD = areaIPD;
+ this.isHyphenated = isHyphenated;
+ this.isSpace = isSpace;
+ this.breakOppAfter = breakOppAfter;
+ this.font = font;
+ this.level = level;
+ this.gposAdjustments = gposAdjustments;
+ this.mapping = mapping;
+ }
+
+ public static GlyphMapping doGlyphMapping(TextFragment text, int startIndex, int endIndex,
+ Font font, MinOptMax letterSpaceIPD, MinOptMax[] letterSpaceAdjustArray,
+ char precedingChar, char breakOpportunityChar, final boolean endsWithHyphen, int level) {
+ GlyphMapping mapping;
+ if (font.performsSubstitution() || font.performsPositioning()) {
+ mapping = processWordMapping(text, startIndex, endIndex, font,
+ breakOpportunityChar, endsWithHyphen, level);
+ } else {
+ mapping = processWordNoMapping(text, startIndex, endIndex, font,
+ letterSpaceIPD, letterSpaceAdjustArray, precedingChar, breakOpportunityChar, endsWithHyphen,
+ level);
+ }
+ return mapping;
+ }
+
+ private static GlyphMapping processWordMapping(TextFragment text, int startIndex,
+ int endIndex, final Font font, final char breakOpportunityChar,
+ final boolean endsWithHyphen, int level) {
+ int e = endIndex; // end index of word in FOText character buffer
+ int nLS = 0; // # of letter spaces
+ String script = text.getScript();
+ String language = text.getLanguage();
+
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("PW: [" + startIndex + "," + endIndex + "]: {"
+ + " +M"
+ + ", level = " + level
+ + " }");
+ }
+
+ // 1. extract unmapped character sequence
+ CharSequence ics = text.subSequence(startIndex, e);
+
+ // 2. if script is not specified (by FO property) or it is specified as 'auto',
+ // then compute dominant script
+ if ((script == null) || "auto".equals(script)) {
+ script = CharScript.scriptTagFromCode(CharScript.dominantScript(ics));
+ }
+ if ((language == null) || "none".equals(language)) {
+ language = "dflt";
+ }
+
+ // 3. perform mapping of chars to glyphs ... to glyphs ... to chars
+ CharSequence mcs = font.performSubstitution(ics, script, language);
+
+ // 4. compute glyph position adjustments on (substituted) characters
+ int[][] gpa;
+ if (font.performsPositioning()) {
+ // handle GPOS adjustments
+ gpa = font.performPositioning(mcs, script, language);
+ } else if (font.hasKerning()) {
+ // handle standard (non-GPOS) kerning adjustments
+ gpa = getKerningAdjustments(mcs, font);
+ } else {
+ gpa = null;
+ }
+
+ // 5. reorder combining marks so that they precede (within the mapped char sequence) the
+ // base to which they are applied; N.B. position adjustments (gpa) are reordered in place
+ mcs = font.reorderCombiningMarks(mcs, gpa, script, language);
+
+ // 6. compute word ipd based on final position adjustments
+ MinOptMax ipd = MinOptMax.ZERO;
+ for (int i = 0, n = mcs.length(); i < n; i++) {
+ int c = mcs.charAt(i);
+ // TODO !BMP
+ int w = font.getCharWidth(c);
+ if (w < 0) {
+ w = 0;
+ }
+ if (gpa != null) {
+ w += gpa[i][GlyphPositioningTable.Value.IDX_X_ADVANCE];
+ }
+ ipd = ipd.plus(w);
+ }
+
+ // [TBD] - handle letter spacing
+
+ return new GlyphMapping(startIndex, e, 0, nLS, ipd, endsWithHyphen, false,
+ breakOpportunityChar != 0, font, level, gpa,
+ CharUtilities.isSameSequence(mcs, ics) ? null : mcs.toString());
+ }
+
+ /**
+ * Given a mapped character sequence MCS, obtain glyph position adjustments from the
+ * font's kerning data.
+ *
+ * @param mcs mapped character sequence
+ * @param font applicable font
+ * @return glyph position adjustments (or null if no kerning)
+ */
+ private static int[][] getKerningAdjustments(CharSequence mcs, final Font font) {
+ int nc = mcs.length();
+ // extract kerning array
+ int[] ka = new int[nc]; // kerning array
+ for (int i = 0, n = nc, cPrev = -1; i < n; i++) {
+ int c = mcs.charAt(i);
+ // TODO !BMP
+ if (cPrev >= 0) {
+ ka[i] = font.getKernValue(cPrev, c);
+ }
+ cPrev = c;
+ }
+ // was there a non-zero kerning?
+ boolean hasKerning = false;
+ for (int i = 0, n = nc; i < n; i++) {
+ if (ka[i] != 0) {
+ hasKerning = true;
+ break;
+ }
+ }
+ // if non-zero kerning, then create and return glyph position adjustment array
+ if (hasKerning) {
+ int[][] gpa = new int[nc][4];
+ for (int i = 0, n = nc; i < n; i++) {
+ if (i > 0) {
+ gpa [i - 1][GlyphPositioningTable.Value.IDX_X_ADVANCE] = ka[i];
+ }
+ }
+ return gpa;
+ } else {
+ return null;
+ }
+ }
+
+ private static GlyphMapping processWordNoMapping(TextFragment text, int startIndex, int endIndex,
+ final Font font, MinOptMax letterSpaceIPD, MinOptMax[] letterSpaceAdjustArray,
+ char precedingChar, final char breakOpportunityChar, final boolean endsWithHyphen, int level) {
+ boolean kerning = font.hasKerning();
+ MinOptMax wordIPD = MinOptMax.ZERO;
+
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("PW: [" + startIndex + "," + endIndex + "]: {"
+ + " -M"
+ + ", level = " + level
+ + " }");
+ }
+
+ for (int i = startIndex; i < endIndex; i++) {
+ char currentChar = text.charAt(i);
+
+ // character width
+ int charWidth = font.getCharWidth(currentChar);
+ wordIPD = wordIPD.plus(charWidth);
+
+ // kerning
+ if (kerning) {
+ int kern = 0;
+ if (i > startIndex) {
+ char previousChar = text.charAt(i - 1);
+ kern = font.getKernValue(previousChar, currentChar);
+ } else if (precedingChar != 0) {
+ kern = font.getKernValue(precedingChar, currentChar);
+ }
+ if (kern != 0) {
+ addToLetterAdjust(letterSpaceAdjustArray, i, kern);
+ wordIPD = wordIPD.plus(kern);
+ }
+ }
+ }
+ if (kerning
+ && (breakOpportunityChar != 0)
+ && !isSpace(breakOpportunityChar)
+ && endIndex > 0
+ && endsWithHyphen) {
+ int kern = font.getKernValue(text.charAt(endIndex - 1), breakOpportunityChar);
+ if (kern != 0) {
+ addToLetterAdjust(letterSpaceAdjustArray, endIndex, kern);
+ // TODO: add kern to wordIPD?
+ }
+ }
+ // shy+chars at start of word: wordLength == 0 && breakOpportunity
+ // shy only characters in word: wordLength == 0 && !breakOpportunity
+ int wordLength = endIndex - startIndex;
+ int letterSpaces = 0;
+ if (wordLength != 0) {
+ letterSpaces = wordLength - 1;
+ // if there is a break opportunity and the next one (break character)
+ // is not a space, it could be used as a line end;
+ // add one more letter space, in case other text follows
+ if ((breakOpportunityChar != 0) && !isSpace(breakOpportunityChar)) {
+ letterSpaces++;
+ }
+ }
+ assert letterSpaces >= 0;
+ wordIPD = wordIPD.plus(letterSpaceIPD.mult(letterSpaces));
+
+ // create and return the AreaInfo object
+ return new GlyphMapping(startIndex, endIndex, 0,
+ letterSpaces, wordIPD,
+ endsWithHyphen,
+ false, breakOpportunityChar != 0, font, level, null);
+ }
+
+ private static void addToLetterAdjust(MinOptMax[] letterSpaceAdjustArray, int index, int width) {
+ if (letterSpaceAdjustArray[index] == null) {
+ letterSpaceAdjustArray[index] = MinOptMax.getInstance(width);
+ } else {
+ letterSpaceAdjustArray[index] = letterSpaceAdjustArray[index].plus(width);
+ }
+ }
+
+ /**
+ * Indicates whether a character is a space in terms of this layout manager.
+ *
+ * @param ch the character
+ * @return true if it's a space
+ */
+ public static boolean isSpace(final char ch) {
+ return ch == CharUtilities.SPACE
+ || CharUtilities.isNonBreakableSpace(ch)
+ || CharUtilities.isFixedWidthSpace(ch);
+ }
+
+ /**
+ * Obtain number of 'characters' contained in word. If word is mapped, then this
+ * number may be less than or greater than the original length (breakIndex -
+ * startIndex). We compute and memoize thius length upon first invocation of this
+ * method.
+ */
+ public int getWordLength() {
+ if (wordCharLength == -1) {
+ if (mapping != null) {
+ wordCharLength = mapping.length();
+ } else {
+ assert endIndex >= startIndex;
+ wordCharLength = endIndex - startIndex;
+ }
+ }
+ return wordCharLength;
+ }
+
+ public void addToAreaIPD(MinOptMax idp) {
+ areaIPD = areaIPD.plus(idp);
+ }
+
+ public String toString() {
+ return super.toString() + "{"
+ + "interval = [" + startIndex + "," + endIndex + "]"
+ + ", isSpace = " + isSpace
+ + ", level = " + level
+ + ", areaIPD = " + areaIPD
+ + ", letterSpaceCount = " + letterSpaceCount
+ + ", wordSpaceCount = " + wordSpaceCount
+ + ", isHyphenated = " + isHyphenated
+ + ", font = " + font
+ + "}";
+ }
+
+}
diff --git a/src/java/org/apache/fop/fonts/LazyFont.java b/src/java/org/apache/fop/fonts/LazyFont.java
index 49b2e0457..1877a895e 100644
--- a/src/java/org/apache/fop/fonts/LazyFont.java
+++ b/src/java/org/apache/fop/fonts/LazyFont.java
@@ -18,6 +18,7 @@
/* $Id$ */
package org.apache.fop.fonts;
+import java.awt.Rectangle;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
@@ -250,6 +251,26 @@ public class LazyFont extends Typeface implements FontDescriptor, Substitutable,
return realFont.getXHeight(size);
}
+ public int getUnderlinePosition(int size) {
+ load(true);
+ return realFont.getUnderlinePosition(size);
+ }
+
+ public int getUnderlineThickness(int size) {
+ load(true);
+ return realFont.getUnderlineThickness(size);
+ }
+
+ public int getStrikeoutPosition(int size) {
+ load(true);
+ return realFont.getStrikeoutPosition(size);
+ }
+
+ public int getStrikeoutThickness(int size) {
+ load(true);
+ return realFont.getStrikeoutThickness(size);
+ }
+
/**
* {@inheritDoc}
*/
@@ -268,6 +289,11 @@ public class LazyFont extends Typeface implements FontDescriptor, Substitutable,
return realFont.getWidths();
}
+ public Rectangle getBoundingBox(int glyphIndex, int size) {
+ load(true);
+ return realFont.getBoundingBox(glyphIndex, size);
+ }
+
/**
* {@inheritDoc}
*/
diff --git a/src/java/org/apache/fop/fonts/MultiByteFont.java b/src/java/org/apache/fop/fonts/MultiByteFont.java
index 83f6491f3..1d1186dcb 100644
--- a/src/java/org/apache/fop/fonts/MultiByteFont.java
+++ b/src/java/org/apache/fop/fonts/MultiByteFont.java
@@ -19,6 +19,7 @@
package org.apache.fop.fonts;
+import java.awt.Rectangle;
import java.nio.CharBuffer;
import java.nio.IntBuffer;
import java.util.BitSet;
@@ -67,6 +68,9 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl
private int firstUnmapped;
private int lastUnmapped;
+ /** Contains the character bounding boxes for all characters in the font */
+ protected Rectangle[] boundingBoxes;
+
/**
* Default constructor
*/
@@ -170,6 +174,12 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl
return arr;
}
+ public Rectangle getBoundingBox(int glyphIndex, int size) {
+ int index = isEmbeddable() ? cidSet.getOriginalGlyphIndex(glyphIndex) : glyphIndex;
+ Rectangle bbox = boundingBoxes[index];
+ return new Rectangle(bbox.x * size, bbox.y * size, bbox.width * size, bbox.height * size);
+ }
+
/**
* Returns the glyph index for a Unicode character. The method returns 0 if there's no
* such glyph in the character map.
@@ -366,6 +376,14 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl
}
/**
+ * Sets the bounding boxes array.
+ * @param boundingBoxes array of bounding boxes.
+ */
+ public void setBBoxArray(Rectangle[] boundingBoxes) {
+ this.boundingBoxes = boundingBoxes;
+ }
+
+ /**
* Returns a Map of used Glyphs.
* @return Map Map of used Glyphs
*/
diff --git a/src/java/org/apache/fop/fonts/SingleByteFont.java b/src/java/org/apache/fop/fonts/SingleByteFont.java
index cd11c7849..d412609d5 100644
--- a/src/java/org/apache/fop/fonts/SingleByteFont.java
+++ b/src/java/org/apache/fop/fonts/SingleByteFont.java
@@ -19,6 +19,7 @@
package org.apache.fop.fonts;
+import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -47,6 +48,8 @@ public class SingleByteFont extends CustomFont {
private int[] width = null;
+ private Rectangle[] boundingBoxes;
+
private Map<Character, UnencodedCharacter> unencodedCharacters;
private List<SimpleSingleByteEncoding> additionalEncodings;
private Map<Character, Character> alternativeCodes;
@@ -111,6 +114,24 @@ public class SingleByteFont extends CustomFont {
return arr;
}
+ public Rectangle getBoundingBox(int glyphIndex, int size) {
+ Rectangle bbox = null;
+ if (glyphIndex < 256) {
+ int idx = glyphIndex - getFirstChar();
+ if (idx >= 0 && idx < boundingBoxes.length) {
+ bbox = boundingBoxes[idx];
+ }
+ } else if (this.additionalEncodings != null) {
+ int encodingIndex = (glyphIndex / 256) - 1;
+ SimpleSingleByteEncoding encoding = getAdditionalEncoding(encodingIndex);
+ int codePoint = glyphIndex % 256;
+ NamedCharacter nc = encoding.getCharacterForIndex(codePoint);
+ UnencodedCharacter uc = this.unencodedCharacters.get(Character.valueOf(nc.getSingleUnicodeValue()));
+ bbox = uc.getBBox();
+ }
+ return bbox == null ? null : new Rectangle(bbox.x * size, bbox.y * size, bbox.width * size, bbox.height * size);
+ }
+
/**
* Lookup a character using its alternative names. If found, cache it so we
* can speed up lookups.
@@ -292,17 +313,24 @@ public class SingleByteFont extends CustomFont {
this.width[index - getFirstChar()] = w;
}
+ public void setBoundingBox(int index, Rectangle bbox) {
+ if (this.boundingBoxes == null) {
+ this.boundingBoxes = new Rectangle[getLastChar() - getFirstChar() + 1];
+ }
+ this.boundingBoxes[index - getFirstChar()] = bbox;
+ }
+
/**
* Adds an unencoded character (one that is not supported by the primary encoding).
* @param ch the named character
* @param width the width of the character
*/
- public void addUnencodedCharacter(NamedCharacter ch, int width) {
+ public void addUnencodedCharacter(NamedCharacter ch, int width, Rectangle bbox) {
if (this.unencodedCharacters == null) {
this.unencodedCharacters = new HashMap<Character, UnencodedCharacter>();
}
if (ch.hasSingleUnicodeValue()) {
- UnencodedCharacter uc = new UnencodedCharacter(ch, width);
+ UnencodedCharacter uc = new UnencodedCharacter(ch, width, bbox);
this.unencodedCharacters.put(Character.valueOf(ch.getSingleUnicodeValue()), uc);
} else {
//Cannot deal with unicode sequences, so ignore this character
@@ -381,10 +409,12 @@ public class SingleByteFont extends CustomFont {
private final NamedCharacter character;
private final int width;
+ private final Rectangle bbox;
- public UnencodedCharacter(NamedCharacter character, int width) {
+ public UnencodedCharacter(NamedCharacter character, int width, Rectangle bbox) {
this.character = character;
this.width = width;
+ this.bbox = bbox;
}
public NamedCharacter getCharacter() {
@@ -395,6 +425,10 @@ public class SingleByteFont extends CustomFont {
return this.width;
}
+ public Rectangle getBBox() {
+ return bbox;
+ }
+
/** {@inheritDoc} */
@Override
public String toString() {
diff --git a/src/java/org/apache/fop/fonts/TextFragment.java b/src/java/org/apache/fop/fonts/TextFragment.java
new file mode 100644
index 000000000..ad72db8e0
--- /dev/null
+++ b/src/java/org/apache/fop/fonts/TextFragment.java
@@ -0,0 +1,31 @@
+/*
+ * 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.fonts;
+
+public interface TextFragment {
+
+ String getScript();
+
+ String getLanguage();
+
+ char charAt(int index);
+
+ CharSequence subSequence(int startIndex, int endIndex);
+}
diff --git a/src/java/org/apache/fop/fonts/truetype/TTFFile.java b/src/java/org/apache/fop/fonts/truetype/TTFFile.java
index 2e78ef7f0..62686bbd4 100644
--- a/src/java/org/apache/fop/fonts/truetype/TTFFile.java
+++ b/src/java/org/apache/fop/fonts/truetype/TTFFile.java
@@ -19,6 +19,7 @@
package org.apache.fop.fonts.truetype;
+import java.awt.Rectangle;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -199,6 +200,8 @@ public class TTFFile {
private int os2CapHeight = 0;
private int underlinePosition = 0;
private int underlineThickness = 0;
+ private int strikeoutPosition;
+ private int strikeoutThickness;
private int xHeight = 0;
private int os2xHeight = 0;
//Effective ascender/descender
@@ -976,10 +979,22 @@ public class TTFFile {
for (int i = 0; i < wx.length; i++) {
wx[i] = convertTTFUnit2PDFUnit(mtxTab[i].getWx());
}
-
return wx;
}
+ public Rectangle[] getBoundingBoxes() {
+ Rectangle[] boundingBoxes = new Rectangle[mtxTab.length];
+ for (int i = 0; i < boundingBoxes.length; i++) {
+ int[] boundingBox = mtxTab[i].getBoundingBox();
+ boundingBoxes[i] = new Rectangle(
+ convertTTFUnit2PDFUnit(boundingBox[0]),
+ convertTTFUnit2PDFUnit(boundingBox[1]),
+ convertTTFUnit2PDFUnit(boundingBox[2] - boundingBox[0]),
+ convertTTFUnit2PDFUnit(boundingBox[3] - boundingBox[1]));
+ }
+ return boundingBoxes;
+ }
+
/**
* Returns an array (xMin, yMin, xMax, yMax) for a glyph.
*
@@ -1020,6 +1035,22 @@ public class TTFFile {
return ansiKerningTab;
}
+ public int getUnderlinePosition() {
+ return convertTTFUnit2PDFUnit(underlinePosition);
+ }
+
+ public int getUnderlineThickness() {
+ return convertTTFUnit2PDFUnit(underlineThickness);
+ }
+
+ public int getStrikeoutPosition() {
+ return convertTTFUnit2PDFUnit(strikeoutPosition);
+ }
+
+ public int getStrikeoutThickness() {
+ return convertTTFUnit2PDFUnit(strikeoutThickness);
+ }
+
/**
* Indicates if the font may be embedded.
* @return boolean True if it may be embedded
@@ -1301,7 +1332,10 @@ public class TTFFile {
} else {
isEmbeddable = true;
}
- fontFile.skip(11 * 2);
+ fontFile.skip(8 * 2);
+ strikeoutThickness = fontFile.readTTFShort();
+ strikeoutPosition = fontFile.readTTFShort();
+ fontFile.skip(2);
fontFile.skip(10); //panose array
fontFile.skip(4 * 4); //unicode ranges
fontFile.skip(4);
diff --git a/src/java/org/apache/fop/fonts/truetype/TTFFontLoader.java b/src/java/org/apache/fop/fonts/truetype/TTFFontLoader.java
index b7adbd4c9..98f81ab3e 100644
--- a/src/java/org/apache/fop/fonts/truetype/TTFFontLoader.java
+++ b/src/java/org/apache/fop/fonts/truetype/TTFFontLoader.java
@@ -19,6 +19,7 @@
package org.apache.fop.fonts.truetype;
+import java.awt.Rectangle;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
@@ -144,6 +145,10 @@ public class TTFFontLoader extends FontLoader {
returnFont.setAscender(ttf.getLowerCaseAscent());
returnFont.setDescender(ttf.getLowerCaseDescent());
returnFont.setFontBBox(ttf.getFontBBox());
+ returnFont.setUnderlinePosition(ttf.getUnderlinePosition() - ttf.getUnderlineThickness() / 2);
+ returnFont.setUnderlineThickness(ttf.getUnderlineThickness());
+ returnFont.setStrikeoutPosition(ttf.getStrikeoutPosition() - ttf.getStrikeoutThickness() / 2);
+ returnFont.setStrikeoutThickness(ttf.getStrikeoutThickness());
returnFont.setFlags(ttf.getFlags());
returnFont.setStemV(Integer.parseInt(ttf.getStemV())); //not used for TTF
returnFont.setItalicAngle(Integer.parseInt(ttf.getItalicAngle()));
@@ -152,15 +157,15 @@ public class TTFFontLoader extends FontLoader {
returnFont.setEmbeddingMode(this.embeddingMode);
if (isCid) {
multiFont.setCIDType(CIDFontType.CIDTYPE2);
- int[] wx = ttf.getWidths();
- multiFont.setWidthArray(wx);
+ multiFont.setWidthArray(ttf.getWidths());
+ multiFont.setBBoxArray(ttf.getBoundingBoxes());
} else {
singleFont.setFontType(FontType.TRUETYPE);
singleFont.setEncoding(ttf.getCharSetName());
returnFont.setFirstChar(ttf.getFirstChar());
returnFont.setLastChar(ttf.getLastChar());
singleFont.setTrueTypePostScriptVersion(ttf.getPostScriptVersion());
- copyWidthsSingleByte(ttf);
+ copyGlyphMetricsSingleByte(ttf);
}
returnFont.setCMap(getCMap(ttf));
@@ -186,12 +191,15 @@ public class TTFFontLoader extends FontLoader {
return ttf.getCMaps().toArray(array);
}
- private void copyWidthsSingleByte(TTFFile ttf) {
+ private void copyGlyphMetricsSingleByte(TTFFile ttf) {
int[] wx = ttf.getWidths();
+ Rectangle[] bboxes = ttf.getBoundingBoxes();
for (int i = singleFont.getFirstChar(); i <= singleFont.getLastChar(); i++) {
singleFont.setWidth(i, ttf.getCharWidth(i));
+ int[] bbox = ttf.getBBox(i);
+ singleFont.setBoundingBox(i,
+ new Rectangle(bbox[0], bbox[1], bbox[2] - bbox[0], bbox[3] - bbox[1]));
}
-
for (CMapSegment segment : ttf.getCMaps()) {
if (segment.getUnicodeStart() < 0xFFFE) {
for (char u = (char)segment.getUnicodeStart(); u <= segment.getUnicodeEnd(); u++) {
@@ -205,7 +213,7 @@ public class TTFFontLoader extends FontLoader {
if (glyphName.length() > 0) {
String unicode = Character.toString(u);
NamedCharacter nc = new NamedCharacter(glyphName, unicode);
- singleFont.addUnencodedCharacter(nc, wx[glyphIndex]);
+ singleFont.addUnencodedCharacter(nc, wx[glyphIndex], bboxes[glyphIndex]);
}
}
}
diff --git a/src/java/org/apache/fop/fonts/type1/AFMCharMetrics.java b/src/java/org/apache/fop/fonts/type1/AFMCharMetrics.java
index 4906f2c31..8d9f4238b 100644
--- a/src/java/org/apache/fop/fonts/type1/AFMCharMetrics.java
+++ b/src/java/org/apache/fop/fonts/type1/AFMCharMetrics.java
@@ -19,7 +19,7 @@
package org.apache.fop.fonts.type1;
-import java.awt.geom.RectangularShape;
+import java.awt.Rectangle;
import org.apache.fop.fonts.NamedCharacter;
@@ -33,7 +33,7 @@ public class AFMCharMetrics {
private NamedCharacter character;
private double widthX;
private double widthY;
- private RectangularShape bBox;
+ private Rectangle bBox;
/**
* Returns the character code.
@@ -137,7 +137,7 @@ public class AFMCharMetrics {
* Returns the character's bounding box.
* @return the bounding box (or null if it isn't available)
*/
- public RectangularShape getBBox() {
+ public Rectangle getBBox() {
return bBox;
}
@@ -145,7 +145,7 @@ public class AFMCharMetrics {
* Sets the character's bounding box.
* @param box the bounding box
*/
- public void setBBox(RectangularShape box) {
+ public void setBBox(Rectangle box) {
bBox = box;
}
diff --git a/src/java/org/apache/fop/fonts/type1/Type1FontLoader.java b/src/java/org/apache/fop/fonts/type1/Type1FontLoader.java
index 853e23eb5..7922ff6fd 100644
--- a/src/java/org/apache/fop/fonts/type1/Type1FontLoader.java
+++ b/src/java/org/apache/fop/fonts/type1/Type1FontLoader.java
@@ -203,12 +203,16 @@ public class Type1FontLoader extends FontLoader {
for (AFMCharMetrics metrics : charMetrics) {
String charName = metrics.getCharName();
if (charName != null && !glyphNames.contains(charName)) {
- singleFont.addUnencodedCharacter(metrics.getCharacter(),
- (int)Math.round(metrics.getWidthX()));
+ addUnencodedCharacter(singleFont, metrics);
}
}
}
+ private static void addUnencodedCharacter(SingleByteFont font, AFMCharMetrics metrics) {
+ font.addUnencodedCharacter(metrics.getCharacter(),
+ (int) Math.round(metrics.getWidthX()), metrics.getBBox());
+ }
+
/**
* Adds characters not encoded in the font's primary encoding. This method is used when
* the primary encoding is built based on the character codes in the AFM rather than
@@ -220,8 +224,7 @@ public class Type1FontLoader extends FontLoader {
for (int i = 0, c = afm.getCharCount(); i < c; i++) {
AFMCharMetrics metrics = (AFMCharMetrics)charMetrics.get(i);
if (!metrics.hasCharCode() && metrics.getCharacter() != null) {
- singleFont.addUnencodedCharacter(metrics.getCharacter(),
- (int)Math.round(metrics.getWidthX()));
+ addUnencodedCharacter(singleFont, metrics);
}
}
}
@@ -267,7 +270,10 @@ public class Type1FontLoader extends FontLoader {
} else {
returnFont.setStemV(80); // Arbitrary value
}
- returnFont.setItalicAngle((int) afm.getWritingDirectionMetrics(0).getItalicAngle());
+ AFMWritingDirectionMetrics metrics = afm.getWritingDirectionMetrics(0);
+ returnFont.setItalicAngle((int) metrics.getItalicAngle());
+ returnFont.setUnderlinePosition(metrics.getUnderlinePosition().intValue());
+ returnFont.setUnderlineThickness(metrics.getUnderlineThickness().intValue());
} else {
returnFont.setFontBBox(pfm.getFontBBox());
returnFont.setStemV(pfm.getStemV());
@@ -369,6 +375,7 @@ public class Type1FontLoader extends FontLoader {
for (AFMCharMetrics chm : afm.getCharMetrics()) {
if (chm.hasCharCode()) {
singleFont.setWidth(chm.getCharCode(), (int) Math.round(chm.getWidthX()));
+ singleFont.setBoundingBox(chm.getCharCode(), chm.getBBox());
}
}
if (useKerning) {
diff --git a/src/java/org/apache/fop/image/loader/batik/ImageConverterSVG2G2D.java b/src/java/org/apache/fop/image/loader/batik/ImageConverterSVG2G2D.java
index 79592efd1..ec187b8d0 100644
--- a/src/java/org/apache/fop/image/loader/batik/ImageConverterSVG2G2D.java
+++ b/src/java/org/apache/fop/image/loader/batik/ImageConverterSVG2G2D.java
@@ -33,6 +33,7 @@ import org.apache.batik.bridge.GVTBuilder;
import org.apache.batik.bridge.UserAgent;
import org.apache.batik.dom.svg.SVGDOMImplementation;
import org.apache.batik.gvt.GraphicsNode;
+import org.apache.batik.gvt.font.DefaultFontFamilyResolver;
import org.apache.xmlgraphics.image.GraphicsConstants;
import org.apache.xmlgraphics.image.loader.Image;
@@ -123,10 +124,8 @@ public class ImageConverterSVG2G2D extends AbstractImageConverter {
* @return the newly created user agent
*/
protected SimpleSVGUserAgent createBatikUserAgent(float pxToMillimeter) {
- return new SimpleSVGUserAgent(
- pxToMillimeter,
- new AffineTransform()) {
-
+ return new SimpleSVGUserAgent(pxToMillimeter, new AffineTransform(),
+ DefaultFontFamilyResolver.SINGLETON) {
/** {@inheritDoc} */
public void displayMessage(String message) {
//TODO Refine and pipe through to caller
diff --git a/src/java/org/apache/fop/image/loader/batik/PreloaderSVG.java b/src/java/org/apache/fop/image/loader/batik/PreloaderSVG.java
index 3aa340a4a..58daadc52 100644
--- a/src/java/org/apache/fop/image/loader/batik/PreloaderSVG.java
+++ b/src/java/org/apache/fop/image/loader/batik/PreloaderSVG.java
@@ -38,6 +38,7 @@ import org.apache.batik.bridge.UnitProcessor;
import org.apache.batik.bridge.UserAgent;
import org.apache.batik.dom.svg.SAXSVGDocumentFactory;
import org.apache.batik.dom.svg.SVGOMDocument;
+import org.apache.batik.gvt.font.DefaultFontFamilyResolver;
import org.apache.xmlgraphics.image.loader.ImageContext;
import org.apache.xmlgraphics.image.loader.ImageInfo;
@@ -162,7 +163,7 @@ public class PreloaderSVG extends AbstractImagePreloader {
Element e = doc.getRootElement();
float pxUnitToMillimeter = UnitConv.IN2MM / context.getSourceResolution();
UserAgent userAg = new SimpleSVGUserAgent(pxUnitToMillimeter,
- new AffineTransform()) {
+ new AffineTransform(), DefaultFontFamilyResolver.SINGLETON) {
/** {@inheritDoc} */
public void displayMessage(String message) {
diff --git a/src/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java b/src/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java
index 53f51cd32..625b43ee5 100644
--- a/src/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java
+++ b/src/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java
@@ -30,12 +30,11 @@ import org.apache.commons.logging.LogFactory;
import org.apache.fop.area.Trait;
import org.apache.fop.area.inline.TextArea;
-import org.apache.fop.complexscripts.fonts.GlyphPositioningTable;
-import org.apache.fop.complexscripts.util.CharScript;
import org.apache.fop.fo.Constants;
import org.apache.fop.fo.FOText;
import org.apache.fop.fonts.Font;
import org.apache.fop.fonts.FontSelector;
+import org.apache.fop.fonts.GlyphMapping;
import org.apache.fop.layoutmgr.InlineKnuthSequence;
import org.apache.fop.layoutmgr.KnuthBox;
import org.apache.fop.layoutmgr.KnuthElement;
@@ -65,91 +64,15 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
private static final int SOFT_HYPHEN_PENALTY = 1;
/**
- * Store information about each potential text area.
- * Index of character which ends the area, IPD of area, including
- * any word-space and letter-space.
- * Number of word-spaces?
- */
- private class AreaInfo {
-
- private final int startIndex;
- private final int breakIndex;
- private int wordCharLength;
- private final int wordSpaceCount;
- private int letterSpaceCount;
- private MinOptMax areaIPD;
- private final boolean isHyphenated;
- private final boolean isSpace;
- private boolean breakOppAfter;
- private final Font font;
- private final int level;
- private final int[][] gposAdjustments;
-
- AreaInfo(
- int startIndex, int breakIndex, int wordSpaceCount, int letterSpaceCount,
- MinOptMax areaIPD, boolean isHyphenated, boolean isSpace, boolean breakOppAfter,
- Font font, int level, int[][] gposAdjustments) {
- assert startIndex <= breakIndex;
- this.startIndex = startIndex;
- this.breakIndex = breakIndex;
- this.wordCharLength = -1;
- this.wordSpaceCount = wordSpaceCount;
- this.letterSpaceCount = letterSpaceCount;
- this.areaIPD = areaIPD;
- this.isHyphenated = isHyphenated;
- this.isSpace = isSpace;
- this.breakOppAfter = breakOppAfter;
- this.font = font;
- this.level = level;
- this.gposAdjustments = gposAdjustments;
- }
-
- /**
- * Obtain number of 'characters' contained in word. If word
- * is mapped, then this number may be less than or greater than the
- * original length (breakIndex - startIndex). We compute and
- * memoize thius length upon first invocation of this method.
- */
- private int getWordLength() {
- if (wordCharLength == -1) {
- if (foText.hasMapping(startIndex, breakIndex)) {
- wordCharLength = foText.getMapping(startIndex, breakIndex).length();
- } else {
- assert breakIndex >= startIndex;
- wordCharLength = breakIndex - startIndex;
- }
- }
- return wordCharLength;
- }
-
- private void addToAreaIPD(MinOptMax idp) {
- areaIPD = areaIPD.plus(idp);
- }
-
- public String toString() {
- return super.toString() + "{"
- + "interval = [" + startIndex + "," + breakIndex + "]"
- + ", isSpace = " + isSpace
- + ", level = " + level
- + ", areaIPD = " + areaIPD
- + ", letterSpaceCount = " + letterSpaceCount
- + ", wordSpaceCount = " + wordSpaceCount
- + ", isHyphenated = " + isHyphenated
- + ", font = " + font
- + "}";
- }
- }
-
- /**
* this class stores information about changes in vecAreaInfo which are not yet applied
*/
private final class PendingChange {
- private final AreaInfo areaInfo;
+ private final GlyphMapping mapping;
private final int index;
- private PendingChange(final AreaInfo areaInfo, final int index) {
- this.areaInfo = areaInfo;
+ private PendingChange(final GlyphMapping mapping, final int index) {
+ this.mapping = mapping;
this.index = index;
}
}
@@ -160,7 +83,7 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
private static final Log LOG = LogFactory.getLog(TextLayoutManager.class);
// Hold all possible breaks for the text in this LM's FO.
- private final List areaInfos;
+ private final List<GlyphMapping> mappings;
/** Non-space characters on which we can end a line. */
private static final String BREAK_CHARS = "-/";
@@ -216,7 +139,7 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
public TextLayoutManager(FOText node) {
foText = node;
letterSpaceAdjustArray = new MinOptMax[node.length() + 1];
- areaInfos = new ArrayList();
+ mappings = new ArrayList<GlyphMapping>();
}
private KnuthPenalty makeZeroWidthPenalty(int penaltyValue) {
@@ -274,61 +197,61 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
public void addAreas(final PositionIterator posIter, final LayoutContext context) {
// Add word areas
- AreaInfo areaInfo;
+ GlyphMapping mapping;
int wordSpaceCount = 0;
int letterSpaceCount = 0;
- int firstAreaInfoIndex = -1;
- int lastAreaInfoIndex = 0;
+ int firstMappingIndex = -1;
+ int lastMappingIndex = 0;
MinOptMax realWidth = MinOptMax.ZERO;
/* On first area created, add any leading space.
* Calculate word-space stretch value.
*/
- AreaInfo lastAreaInfo = null;
+ GlyphMapping lastMapping = null;
while (posIter.hasNext()) {
final LeafPosition tbpNext = (LeafPosition) posIter.next();
if (tbpNext == null) {
continue; //Ignore elements without Positions
}
if (tbpNext.getLeafPos() != -1) {
- areaInfo = (AreaInfo) areaInfos.get(tbpNext.getLeafPos());
- if (lastAreaInfo == null
- || (areaInfo.font != lastAreaInfo.font)
- || (areaInfo.level != lastAreaInfo.level)) {
- if (lastAreaInfo != null) {
- addAreaInfoAreas(lastAreaInfo, wordSpaceCount,
- letterSpaceCount, firstAreaInfoIndex,
- lastAreaInfoIndex, realWidth, context);
+ mapping = mappings.get(tbpNext.getLeafPos());
+ if (lastMapping == null
+ || (mapping.font != lastMapping.font)
+ || (mapping.level != lastMapping.level)) {
+ if (lastMapping != null) {
+ addMappingAreas(lastMapping, wordSpaceCount,
+ letterSpaceCount, firstMappingIndex,
+ lastMappingIndex, realWidth, context);
}
- firstAreaInfoIndex = tbpNext.getLeafPos();
+ firstMappingIndex = tbpNext.getLeafPos();
wordSpaceCount = 0;
letterSpaceCount = 0;
realWidth = MinOptMax.ZERO;
}
- wordSpaceCount += areaInfo.wordSpaceCount;
- letterSpaceCount += areaInfo.letterSpaceCount;
- realWidth = realWidth.plus(areaInfo.areaIPD);
- lastAreaInfoIndex = tbpNext.getLeafPos();
- lastAreaInfo = areaInfo;
+ wordSpaceCount += mapping.wordSpaceCount;
+ letterSpaceCount += mapping.letterSpaceCount;
+ realWidth = realWidth.plus(mapping.areaIPD);
+ lastMappingIndex = tbpNext.getLeafPos();
+ lastMapping = mapping;
}
}
- if (lastAreaInfo != null) {
- addAreaInfoAreas(lastAreaInfo, wordSpaceCount, letterSpaceCount, firstAreaInfoIndex,
- lastAreaInfoIndex, realWidth, context);
+ if (lastMapping != null) {
+ addMappingAreas(lastMapping, wordSpaceCount, letterSpaceCount, firstMappingIndex,
+ lastMappingIndex, realWidth, context);
}
}
- private void addAreaInfoAreas(AreaInfo areaInfo, int wordSpaceCount, int letterSpaceCount,
- int firstAreaInfoIndex, int lastAreaInfoIndex,
+ private void addMappingAreas(GlyphMapping mapping, int wordSpaceCount, int letterSpaceCount,
+ int firstMappingIndex, int lastMappingIndex,
MinOptMax realWidth, LayoutContext context) {
// TODO: These two statements (if, for) were like this before my recent
- // changes. However, it seems as if they should use the AreaInfo from
- // firstAreaInfoIndex.. lastAreaInfoIndex rather than just the last areaInfo.
+ // changes. However, it seems as if they should use the GlyphMapping from
+ // firstMappingIndex.. lastMappingIndex rather than just the last mapping.
// This needs to be checked.
- int textLength = areaInfo.getWordLength();
- if (areaInfo.letterSpaceCount == textLength && !areaInfo.isHyphenated
+ int textLength = mapping.getWordLength();
+ if (mapping.letterSpaceCount == textLength && !mapping.isHyphenated
&& context.isLastArea()) {
// the line ends at a character like "/" or "-";
// remove the letter space after the last character
@@ -336,7 +259,7 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
letterSpaceCount--;
}
- for (int i = areaInfo.startIndex; i < areaInfo.breakIndex; i++) {
+ for (int i = mapping.startIndex; i < mapping.endIndex; i++) {
MinOptMax letterSpaceAdjustment = letterSpaceAdjustArray[i + 1];
if (letterSpaceAdjustment != null && letterSpaceAdjustment.isElastic()) {
letterSpaceCount++;
@@ -344,7 +267,7 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
}
// add hyphenation character if the last word is hyphenated
- if (context.isLastArea() && areaInfo.isHyphenated) {
+ if (context.isLastArea() && mapping.isHyphenated) {
realWidth = realWidth.plus(hyphIPD);
}
@@ -385,8 +308,8 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
totalAdjust = difference;
}
- TextArea textArea = new TextAreaBuilder(realWidth, totalAdjust, context, firstAreaInfoIndex,
- lastAreaInfoIndex, context.isLastArea(), areaInfo.font).build();
+ TextArea textArea = new TextAreaBuilder(realWidth, totalAdjust, context, firstMappingIndex,
+ lastMappingIndex, context.isLastArea(), mapping.font).build();
// wordSpaceDim is computed in relation to wordSpaceIPD.opt
// but the renderer needs to know the adjustment in relation
@@ -417,15 +340,15 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
private final MinOptMax width; // content ipd
private final int adjust; // content ipd adjustment
private final LayoutContext context; // layout context
- private final int firstIndex; // index of first AreaInfo
- private final int lastIndex; // index of last AreaInfo
+ private final int firstIndex; // index of first GlyphMapping
+ private final int lastIndex; // index of last GlyphMapping
private final boolean isLastArea; // true if last inline area in line area
private final Font font; // applicable font
// other, non-constructor state
private TextArea textArea; // text area being constructed
private int blockProgressionDimension; // calculated bpd
- private AreaInfo areaInfo; // current area info when iterating over words
+ private GlyphMapping mapping; // current mapping when iterating over words
private StringBuffer wordChars; // current word's character buffer
private int[] letterSpaceAdjust; // current word's letter space adjustments
private int letterSpaceAdjustIndex; // last written letter space adjustment index
@@ -442,8 +365,8 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
* @param width the MinOptMax width of the content
* @param adjust the total ipd adjustment with respect to the optimal width
* @param context the layout context
- * @param firstIndex the index of the first AreaInfo used for the TextArea
- * @param lastIndex the index of the last AreaInfo used for the TextArea
+ * @param firstIndex the index of the first GlyphMapping used for the TextArea
+ * @param lastIndex the index of the last GlyphMapping used for the TextArea
* @param isLastArea is this TextArea the last in a line?
* @param font Font to be used in this particular TextArea
*/
@@ -516,30 +439,30 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
* Sets the text of the TextArea, split into words and spaces.
*/
private void setText() {
- int areaInfoIndex = -1;
+ int mappingIndex = -1;
int wordCharLength = 0;
for (int wordIndex = firstIndex; wordIndex <= lastIndex; wordIndex++) {
- areaInfo = getAreaInfo(wordIndex);
- if (areaInfo.isSpace) {
+ mapping = getGlyphMapping(wordIndex);
+ if (mapping.isSpace) {
addSpaces();
} else {
- // areaInfo stores information about a word fragment
- if (areaInfoIndex == -1) {
+ // mapping stores information about a word fragment
+ if (mappingIndex == -1) {
// here starts a new word
- areaInfoIndex = wordIndex;
+ mappingIndex = wordIndex;
wordCharLength = 0;
}
- wordCharLength += areaInfo.getWordLength();
+ wordCharLength += mapping.getWordLength();
if (isWordEnd(wordIndex)) {
- addWord(areaInfoIndex, wordIndex, wordCharLength);
- areaInfoIndex = -1;
+ addWord(mappingIndex, wordIndex, wordCharLength);
+ mappingIndex = -1;
}
}
}
}
- private boolean isWordEnd(int areaInfoIndex) {
- return areaInfoIndex == lastIndex || getAreaInfo(areaInfoIndex + 1).isSpace;
+ private boolean isWordEnd(int mappingIndex) {
+ return mappingIndex == lastIndex || getGlyphMapping(mappingIndex + 1).isSpace;
}
/**
@@ -564,10 +487,10 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
// iterate over word's fragments, adding word chars (with bidi
// levels), letter space adjustments, and glyph position adjustments
for (int i = startIndex; i <= endIndex; i++) {
- AreaInfo wordAreaInfo = getAreaInfo(i);
- addWordChars(wordAreaInfo);
- addLetterAdjust(wordAreaInfo);
- if (addGlyphPositionAdjustments(wordAreaInfo)) {
+ GlyphMapping wordMapping = getGlyphMapping(i);
+ addWordChars(wordMapping);
+ addLetterAdjust(wordMapping);
+ if (addGlyphPositionAdjustments(wordMapping)) {
gposAdjusted = true;
}
}
@@ -617,7 +540,7 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
}
private boolean isHyphenated(int endIndex) {
- return isLastArea && endIndex == lastIndex && areaInfo.isHyphenated;
+ return isLastArea && endIndex == lastIndex && mapping.isHyphenated;
}
private void addHyphenationChar() {
@@ -632,21 +555,54 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
* (1) concatenate (possibly mapped) word characters to word character buffer;
* (2) concatenante (possibly mapped) word bidi levels to levels buffer;
* (3) update word's IPD with optimal IPD of fragment.
- * @param wordAreaInfo fragment info
+ * @param wordMapping fragment info
*/
- private void addWordChars(AreaInfo wordAreaInfo) {
- int s = wordAreaInfo.startIndex;
- int e = wordAreaInfo.breakIndex;
- if (foText.hasMapping(s, e)) {
- wordChars.append(foText.getMapping(s, e));
- addWordLevels(foText.getMappingBidiLevels(s, e));
+ private void addWordChars(GlyphMapping wordMapping) {
+ int s = wordMapping.startIndex;
+ int e = wordMapping.endIndex;
+ if (wordMapping.mapping != null) {
+ wordChars.append(wordMapping.mapping);
+ addWordLevels(getMappingBidiLevels(wordMapping));
} else {
for (int i = s; i < e; i++) {
wordChars.append(foText.charAt(i));
}
addWordLevels(foText.getBidiLevels(s, e));
}
- wordIPD += wordAreaInfo.areaIPD.getOpt();
+ wordIPD += wordMapping.areaIPD.getOpt();
+ }
+
+ /**
+ * Obtain bidirectional levels of mapping of characters over specific interval.
+ * @param start index in character buffer
+ * @param end index in character buffer
+ * @return a (possibly empty) array of bidi levels or null
+ * in case no bidi levels have been assigned
+ */
+ private int[] getMappingBidiLevels(GlyphMapping mapping) {
+ if (mapping.mapping != null) {
+ int nc = mapping.endIndex - mapping.startIndex;
+ int nm = mapping.mapping.length();
+ int[] la = foText.getBidiLevels(mapping.startIndex, mapping.endIndex);
+ if (la == null) {
+ return null;
+ } else if (nm == nc) { // mapping is same length as mapped range
+ return la;
+ } else if (nm > nc) { // mapping is longer than mapped range
+ int[] ma = new int[nm];
+ System.arraycopy(la, 0, ma, 0, la.length);
+ for (int i = la.length, n = ma.length, l = (i > 0) ? la[i - 1] : 0; i < n; i++) {
+ ma[i] = l;
+ }
+ return ma;
+ } else { // mapping is shorter than mapped range
+ int[] ma = new int[nm];
+ System.arraycopy(la, 0, ma, 0, ma.length);
+ return ma;
+ }
+ } else {
+ return foText.getBidiLevels(mapping.startIndex, mapping.endIndex);
+ }
}
/**
@@ -672,16 +628,16 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
/**
* Given a word area info associated with a word fragment,
* concatenate letter space adjustments for each (possibly mapped) character.
- * @param wordAreaInfo fragment info
+ * @param wordMapping fragment info
*/
- private void addLetterAdjust(AreaInfo wordAreaInfo) {
- int letterSpaceCount = wordAreaInfo.letterSpaceCount;
- int wordLength = wordAreaInfo.getWordLength();
+ private void addLetterAdjust(GlyphMapping wordMapping) {
+ int letterSpaceCount = wordMapping.letterSpaceCount;
+ int wordLength = wordMapping.getWordLength();
int taAdjust = textArea.getTextLetterSpaceAdjust();
for (int i = 0, n = wordLength; i < n; i++) {
int j = letterSpaceAdjustIndex + i;
if (j > 0) {
- int k = wordAreaInfo.startIndex + i;
+ int k = wordMapping.startIndex + i;
MinOptMax adj = (k < letterSpaceAdjustArray.length)
? letterSpaceAdjustArray [ k ] : null;
letterSpaceAdjust [ j ] = (adj == null) ? 0 : adj.getOpt();
@@ -697,14 +653,14 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
/**
* Given a word area info associated with a word fragment,
* concatenate glyph position adjustments for each (possibly mapped) character.
- * @param wordAreaInfo fragment info
+ * @param wordMapping fragment info
* @return true if an adjustment was non-zero
*/
- private boolean addGlyphPositionAdjustments(AreaInfo wordAreaInfo) {
+ private boolean addGlyphPositionAdjustments(GlyphMapping wordMapping) {
boolean adjusted = false;
- int[][] gpa = wordAreaInfo.gposAdjustments;
+ int[][] gpa = wordMapping.gposAdjustments;
int numAdjusts = (gpa != null) ? gpa.length : 0;
- int wordLength = wordAreaInfo.getWordLength();
+ int wordLength = wordMapping.getWordLength();
if (numAdjusts > 0) {
int need = gposAdjustmentsIndex + numAdjusts;
if (need <= gposAdjustments.length) {
@@ -733,7 +689,7 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
}
/**
- * The <code>AreaInfo</code> stores information about spaces.
+ * The <code>GlyphMapping</code> stores information about spaces.
* <p/>
* Add the spaces - except zero-width spaces - to the TextArea.
*/
@@ -743,16 +699,16 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
// divide the area info's allocated IPD evenly among the
// non-zero-width space characters
int numZeroWidthSpaces = 0;
- for (int i = areaInfo.startIndex; i < areaInfo.breakIndex; i++) {
+ for (int i = mapping.startIndex; i < mapping.endIndex; i++) {
char spaceChar = foText.charAt(i);
if (CharUtilities.isZeroWidthSpace(spaceChar)) {
numZeroWidthSpaces++;
}
}
- int numSpaces = areaInfo.breakIndex - areaInfo.startIndex - numZeroWidthSpaces;
- int spaceIPD = areaInfo.areaIPD.getOpt() / ((numSpaces > 0) ? numSpaces : 1);
+ int numSpaces = mapping.endIndex - mapping.startIndex - numZeroWidthSpaces;
+ int spaceIPD = mapping.areaIPD.getOpt() / ((numSpaces > 0) ? numSpaces : 1);
// add space area children, one for each non-zero-width space character
- for (int i = areaInfo.startIndex; i < areaInfo.breakIndex; i++) {
+ for (int i = mapping.startIndex; i < mapping.endIndex; i++) {
char spaceChar = foText.charAt(i);
int level = foText.bidiLevelAt(i);
if (!CharUtilities.isZeroWidthSpace(spaceChar)) {
@@ -766,39 +722,20 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
}
- private void addAreaInfo(AreaInfo ai) {
- addAreaInfo(areaInfos.size(), ai);
+ private void addGlyphMapping(GlyphMapping mapping) {
+ addGlyphMapping(mappings.size(), mapping);
}
- private void addAreaInfo(int index, AreaInfo ai) {
- areaInfos.add(index, ai);
+ private void addGlyphMapping(int index, GlyphMapping mapping) {
+ mappings.add(index, mapping);
}
- private void removeAreaInfo(int index) {
- areaInfos.remove(index);
+ private void removeGlyphMapping(int index) {
+ mappings.remove(index);
}
- private AreaInfo getAreaInfo(int index) {
- return (AreaInfo) areaInfos.get(index);
- }
-
- private void addToLetterAdjust(int index, int width) {
- if (letterSpaceAdjustArray[index] == null) {
- letterSpaceAdjustArray[index] = MinOptMax.getInstance(width);
- } else {
- letterSpaceAdjustArray[index] = letterSpaceAdjustArray[index].plus(width);
- }
- }
-
- /**
- * Indicates whether a character is a space in terms of this layout manager.
- * @param ch the character
- * @return true if it's a space
- */
- private static boolean isSpace(final char ch) {
- return ch == CharUtilities.SPACE
- || CharUtilities.isNonBreakableSpace(ch)
- || CharUtilities.isFixedWidthSpace(ch);
+ private GlyphMapping getGlyphMapping(int index) {
+ return mappings.get(index);
}
/** {@inheritDoc} */
@@ -810,8 +747,8 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
final List returnList = new LinkedList();
KnuthSequence sequence = new InlineKnuthSequence();
- AreaInfo areaInfo = null;
- AreaInfo prevAreaInfo = null;
+ GlyphMapping mapping = null;
+ GlyphMapping prevMapping = null;
returnList.add(sequence);
if (LOG.isDebugEnabled()) {
@@ -857,24 +794,24 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
}
if (inWord) {
if (breakOpportunity
- || TextLayoutManager.isSpace(ch)
+ || GlyphMapping.isSpace(ch)
|| CharUtilities.isExplicitBreak(ch)
|| ((prevLevel != -1) && (level != prevLevel))) {
// this.foText.charAt(lastIndex) == CharUtilities.SOFT_HYPHEN
- prevAreaInfo = processWord(alignment, sequence, prevAreaInfo, ch,
+ prevMapping = processWord(alignment, sequence, prevMapping, ch,
breakOpportunity, true, prevLevel);
}
} else if (inWhitespace) {
if (ch != CharUtilities.SPACE || breakOpportunity) {
- prevAreaInfo = processWhitespace(alignment, sequence,
+ prevMapping = processWhitespace(alignment, sequence,
breakOpportunity, prevLevel);
}
} else {
- if (areaInfo != null) {
- prevAreaInfo = areaInfo;
- processLeftoverAreaInfo(alignment, sequence, areaInfo,
+ if (mapping != null) {
+ prevMapping = mapping;
+ processLeftoverGlyphMapping(alignment, sequence, mapping,
ch == CharUtilities.SPACE || breakOpportunity);
- areaInfo = null;
+ mapping = null;
}
if (breakAction == LineBreakStatus.EXPLICIT_BREAK) {
sequence = processLinebreak(returnList, sequence);
@@ -888,15 +825,15 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
this.foText, this);
font.mapChar(ch);
// preserved space or non-breaking space:
- // create the AreaInfo object
- areaInfo = new AreaInfo(nextStart, nextStart + 1, 1, 0, wordSpaceIPD, false, true,
+ // create the GlyphMapping object
+ mapping = new GlyphMapping(nextStart, nextStart + 1, 1, 0, wordSpaceIPD, false, true,
breakOpportunity, spaceFont, level, null);
thisStart = nextStart + 1;
} else if (CharUtilities.isFixedWidthSpace(ch) || CharUtilities.isZeroWidthSpace(ch)) {
- // create the AreaInfo object
+ // create the GlyphMapping object
Font font = FontSelector.selectFontForCharacterInText(ch, foText, this);
MinOptMax ipd = MinOptMax.getInstance(font.getCharWidth(ch));
- areaInfo = new AreaInfo(nextStart, nextStart + 1, 0, 0, ipd, false, true,
+ mapping = new GlyphMapping(nextStart, nextStart + 1, 0, 0, ipd, false, true,
breakOpportunity, font, level, null);
thisStart = nextStart + 1;
} else if (CharUtilities.isExplicitBreak(ch)) {
@@ -904,7 +841,7 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
thisStart = nextStart + 1;
}
- inWord = !TextLayoutManager.isSpace(ch) && !CharUtilities.isExplicitBreak(ch);
+ inWord = !GlyphMapping.isSpace(ch) && !CharUtilities.isExplicitBreak(ch);
inWhitespace = ch == CharUtilities.SPACE
&& foText.getWhitespaceTreatment() != Constants.EN_PRESERVE;
prevLevel = level;
@@ -913,11 +850,11 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
// Process any last elements
if (inWord) {
- processWord(alignment, sequence, prevAreaInfo, ch, false, false, prevLevel);
+ processWord(alignment, sequence, prevMapping, ch, false, false, prevLevel);
} else if (inWhitespace) {
processWhitespace(alignment, sequence, !keepTogether, prevLevel);
- } else if (areaInfo != null) {
- processLeftoverAreaInfo(alignment, sequence, areaInfo,
+ } else if (mapping != null) {
+ processLeftoverGlyphMapping(alignment, sequence, mapping,
ch == CharUtilities.ZERO_WIDTH_SPACE);
} else if (CharUtilities.isExplicitBreak(ch)) {
this.processLinebreak(returnList, sequence);
@@ -948,15 +885,14 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
return sequence;
}
- private void processLeftoverAreaInfo(int alignment,
- KnuthSequence sequence, AreaInfo areaInfo,
- boolean breakOpportunityAfter) {
- addAreaInfo(areaInfo);
- areaInfo.breakOppAfter = breakOpportunityAfter;
- addElementsForASpace(sequence, alignment, areaInfo, areaInfos.size() - 1);
+ private void processLeftoverGlyphMapping(int alignment, KnuthSequence sequence,
+ GlyphMapping mapping, boolean breakOpportunityAfter) {
+ addGlyphMapping(mapping);
+ mapping.breakOppAfter = breakOpportunityAfter;
+ addElementsForASpace(sequence, alignment, mapping, mappings.size() - 1);
}
- private AreaInfo processWhitespace(final int alignment,
+ private GlyphMapping processWhitespace(final int alignment,
final KnuthSequence sequence, final boolean breakOpportunity, int level) {
if (LOG.isDebugEnabled()) {
@@ -964,209 +900,24 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
}
// End of whitespace
- // create the AreaInfo object
+ // create the GlyphMapping object
assert nextStart >= thisStart;
- AreaInfo areaInfo = new AreaInfo(
- thisStart, nextStart, nextStart - thisStart, 0,
+ GlyphMapping mapping = new GlyphMapping(
+ thisStart, nextStart, nextStart - thisStart, 0,
wordSpaceIPD.mult(nextStart - thisStart),
false, true, breakOpportunity, spaceFont, level, null);
- addAreaInfo(areaInfo);
+ addGlyphMapping(mapping);
// create the elements
- addElementsForASpace(sequence, alignment, areaInfo, areaInfos.size() - 1);
+ addElementsForASpace(sequence, alignment, mapping, mappings.size() - 1);
thisStart = nextStart;
- return areaInfo;
- }
-
- private AreaInfo processWordMapping(
- int lastIndex, final Font font, AreaInfo prevAreaInfo, final char breakOpportunityChar,
- final boolean endsWithHyphen, int level) {
- int s = this.thisStart; // start index of word in FOText character buffer
- int e = lastIndex; // end index of word in FOText character buffer
- int nLS = 0; // # of letter spaces
- String script = foText.getScript();
- String language = foText.getLanguage();
-
- if (LOG.isDebugEnabled()) {
- LOG.debug("PW: [" + thisStart + "," + lastIndex + "]: {"
- + " +M"
- + ", level = " + level
- + " }");
- }
-
- // 1. extract unmapped character sequence
- CharSequence ics = foText.subSequence(s, e);
-
- // 2. if script is not specified (by FO property) or it is specified as 'auto',
- // then compute dominant script
- if ((script == null) || "auto".equals(script)) {
- script = CharScript.scriptTagFromCode(CharScript.dominantScript(ics));
- }
- if ((language == null) || "none".equals(language)) {
- language = "dflt";
- }
-
- // 3. perform mapping of chars to glyphs ... to glyphs ... to chars
- CharSequence mcs = font.performSubstitution(ics, script, language);
-
- // 4. compute glyph position adjustments on (substituted) characters
- int[][] gpa;
- if (font.performsPositioning()) {
- // handle GPOS adjustments
- gpa = font.performPositioning(mcs, script, language);
- } else if (font.hasKerning()) {
- // handle standard (non-GPOS) kerning adjustments
- gpa = getKerningAdjustments(mcs, font);
- } else {
- gpa = null;
- }
-
- // 5. reorder combining marks so that they precede (within the mapped char sequence) the
- // base to which they are applied; N.B. position adjustments (gpa) are reordered in place
- mcs = font.reorderCombiningMarks(mcs, gpa, script, language);
-
- // 6. if mapped sequence differs from input sequence, then memoize mapped sequence
- if (!CharUtilities.isSameSequence(mcs, ics)) {
- foText.addMapping(s, e, mcs);
- }
-
- // 7. compute word ipd based on final position adjustments
- MinOptMax ipd = MinOptMax.ZERO;
- for (int i = 0, n = mcs.length(); i < n; i++) {
- int c = mcs.charAt(i);
- // TODO !BMP
- int w = font.getCharWidth(c);
- if (w < 0) {
- w = 0;
- }
- if (gpa != null) {
- w += gpa [ i ] [ GlyphPositioningTable.Value.IDX_X_ADVANCE ];
- }
- ipd = ipd.plus(w);
- }
-
- // [TBD] - handle letter spacing
-
- return new AreaInfo(
- s, e, 0, nLS, ipd, endsWithHyphen, false,
- breakOpportunityChar != 0, font, level, gpa);
- }
-
- /**
- * Given a mapped character sequence MCS, obtain glyph position adjustments
- * from the font's kerning data.
- * @param mcs mapped character sequence
- * @param font applicable font
- * @return glyph position adjustments (or null if no kerning)
- */
- private int[][] getKerningAdjustments(CharSequence mcs, final Font font) {
- int nc = mcs.length();
- // extract kerning array
- int[] ka = new int [ nc ]; // kerning array
- for (int i = 0, n = nc, cPrev = -1; i < n; i++) {
- int c = mcs.charAt(i);
- // TODO !BMP
- if (cPrev >= 0) {
- ka[i] = font.getKernValue(cPrev, c);
- }
- cPrev = c;
- }
- // was there a non-zero kerning?
- boolean hasKerning = false;
- for (int i = 0, n = nc; i < n; i++) {
- if (ka[i] != 0) {
- hasKerning = true;
- break;
- }
- }
- // if non-zero kerning, then create and return glyph position adjustment array
- if (hasKerning) {
- int[][] gpa = new int [ nc ] [ 4 ];
- for (int i = 0, n = nc; i < n; i++) {
- if (i > 0) {
- gpa [ i - 1 ] [ GlyphPositioningTable.Value.IDX_X_ADVANCE ] = ka [ i ];
- }
- }
- return gpa;
- } else {
- return null;
- }
+ return mapping;
}
- private AreaInfo processWordNoMapping(int lastIndex, final Font font, AreaInfo prevAreaInfo,
- final char breakOpportunityChar, final boolean endsWithHyphen, int level) {
- boolean kerning = font.hasKerning();
- MinOptMax wordIPD = MinOptMax.ZERO;
-
- if (LOG.isDebugEnabled()) {
- LOG.debug("PW: [" + thisStart + "," + lastIndex + "]: {"
- + " -M"
- + ", level = " + level
- + " }");
- }
-
- for (int i = thisStart; i < lastIndex; i++) {
- char currentChar = foText.charAt(i);
-
- //character width
- int charWidth = font.getCharWidth(currentChar);
- wordIPD = wordIPD.plus(charWidth);
-
- //kerning
- if (kerning) {
- int kern = 0;
- if (i > thisStart) {
- char previousChar = foText.charAt(i - 1);
- kern = font.getKernValue(previousChar, currentChar);
- } else if (prevAreaInfo != null
- && !prevAreaInfo.isSpace && prevAreaInfo.breakIndex > 0) {
- char previousChar = foText.charAt(prevAreaInfo.breakIndex - 1);
- kern = font.getKernValue(previousChar, currentChar);
- }
- if (kern != 0) {
- addToLetterAdjust(i, kern);
- wordIPD = wordIPD.plus(kern);
- }
- }
- }
- if (kerning
- && (breakOpportunityChar != 0)
- && !TextLayoutManager.isSpace(breakOpportunityChar)
- && lastIndex > 0
- && endsWithHyphen) {
- int kern = font.getKernValue(foText.charAt(lastIndex - 1), breakOpportunityChar);
- if (kern != 0) {
- addToLetterAdjust(lastIndex, kern);
- //TODO: add kern to wordIPD?
- }
- }
- // shy+chars at start of word: wordLength == 0 && breakOpportunity
- // shy only characters in word: wordLength == 0 && !breakOpportunity
- int wordLength = lastIndex - thisStart;
- int letterSpaces = 0;
- if (wordLength != 0) {
- letterSpaces = wordLength - 1;
- // if there is a break opportunity and the next one (break character)
- // is not a space, it could be used as a line end;
- // add one more letter space, in case other text follows
- if ((breakOpportunityChar != 0) && !TextLayoutManager.isSpace(breakOpportunityChar)) {
- letterSpaces++;
- }
- }
- assert letterSpaces >= 0;
- wordIPD = wordIPD.plus(letterSpaceIPD.mult(letterSpaces));
-
- // create and return the AreaInfo object
- return new AreaInfo(thisStart, lastIndex, 0,
- letterSpaces, wordIPD,
- endsWithHyphen,
- false, breakOpportunityChar != 0, font, level, null);
- }
-
- private AreaInfo processWord(final int alignment, final KnuthSequence sequence,
- AreaInfo prevAreaInfo, final char ch, final boolean breakOpportunity,
+ private GlyphMapping processWord(final int alignment, final KnuthSequence sequence,
+ GlyphMapping prevMapping, final char ch, final boolean breakOpportunity,
final boolean checkEndsWithHyphen, int level) {
//Word boundary found, process widths and kerning
@@ -1178,23 +929,20 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
&& foText.charAt(lastIndex) == CharUtilities.SOFT_HYPHEN;
Font font = FontSelector.selectFontForCharactersInText(
foText, thisStart, lastIndex, foText, this);
- AreaInfo areaInfo;
- if (font.performsSubstitution() || font.performsPositioning()) {
- areaInfo = processWordMapping(
- lastIndex, font, prevAreaInfo, breakOpportunity ? ch : 0, endsWithHyphen, level);
- } else {
- areaInfo = processWordNoMapping(
- lastIndex, font, prevAreaInfo, breakOpportunity ? ch : 0, endsWithHyphen, level);
- }
- prevAreaInfo = areaInfo;
- addAreaInfo(areaInfo);
+ char breakOpportunityChar = breakOpportunity ? ch : 0;
+ char precedingChar = prevMapping != null && !prevMapping.isSpace
+ && prevMapping.endIndex > 0 ? foText.charAt(prevMapping.endIndex - 1) : 0;
+ GlyphMapping mapping = GlyphMapping.doGlyphMapping(foText, thisStart, lastIndex, font,
+ letterSpaceIPD, letterSpaceAdjustArray, precedingChar, breakOpportunityChar, endsWithHyphen, level);
+ prevMapping = mapping;
+ addGlyphMapping(mapping);
tempStart = nextStart;
//add the elements
- addElementsForAWordFragment(sequence, alignment, areaInfo, areaInfos.size() - 1);
+ addElementsForAWordFragment(sequence, alignment, mapping, mappings.size() - 1);
thisStart = nextStart;
- return prevAreaInfo;
+ return prevMapping;
}
/** {@inheritDoc} */
@@ -1214,9 +962,9 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
int index = leafPos.getLeafPos();
//element could refer to '-1' position, for non-collapsed spaces (?)
if (index > -1) {
- AreaInfo areaInfo = getAreaInfo(index);
- areaInfo.letterSpaceCount++;
- areaInfo.addToAreaIPD(letterSpaceIPD);
+ GlyphMapping mapping = getGlyphMapping(index);
+ mapping.letterSpaceCount++;
+ mapping.addToAreaIPD(letterSpaceIPD);
if (TextLayoutManager.BREAK_CHARS.indexOf(foText.charAt(tempStart - 1)) >= 0) {
// the last character could be used as a line break
// append new elements to oldList
@@ -1227,13 +975,13 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
} else if (letterSpaceIPD.isStiff()) {
// constant letter space: replace the box
// give it the unwrapped position of the replaced element
- oldListIterator.set(new KnuthInlineBox(areaInfo.areaIPD.getOpt(),
+ oldListIterator.set(new KnuthInlineBox(mapping.areaIPD.getOpt(),
alignmentContext, pos, false));
} else {
// adjustable letter space: replace the glue
oldListIterator.next(); // this would return the penalty element
oldListIterator.next(); // this would return the glue element
- oldListIterator.set(new KnuthGlue(letterSpaceIPD.mult(areaInfo.letterSpaceCount),
+ oldListIterator.set(new KnuthGlue(letterSpaceIPD.mult(mapping.letterSpaceCount),
auxiliaryPosition, true));
}
}
@@ -1242,26 +990,26 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
/** {@inheritDoc} */
public void hyphenate(Position pos, HyphContext hyphContext) {
- AreaInfo areaInfo = getAreaInfo(((LeafPosition) pos).getLeafPos() + changeOffset);
- int startIndex = areaInfo.startIndex;
+ GlyphMapping mapping = getGlyphMapping(((LeafPosition) pos).getLeafPos() + changeOffset);
+ int startIndex = mapping.startIndex;
int stopIndex;
boolean nothingChanged = true;
- Font font = areaInfo.font;
+ Font font = mapping.font;
- while (startIndex < areaInfo.breakIndex) {
+ while (startIndex < mapping.endIndex) {
MinOptMax newIPD = MinOptMax.ZERO;
boolean hyphenFollows;
stopIndex = startIndex + hyphContext.getNextHyphPoint();
- if (hyphContext.hasMoreHyphPoints() && stopIndex <= areaInfo.breakIndex) {
+ if (hyphContext.hasMoreHyphPoints() && stopIndex <= mapping.endIndex) {
// stopIndex is the index of the first character
// after a hyphenation point
hyphenFollows = true;
} else {
// there are no more hyphenation points,
- // or the next one is after areaInfo.breakIndex
+ // or the next one is after mapping.breakIndex
hyphenFollows = false;
- stopIndex = areaInfo.breakIndex;
+ stopIndex = mapping.endIndex;
}
hyphContext.updateOffset(stopIndex - startIndex);
@@ -1286,18 +1034,18 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
// add letter spaces
boolean isWordEnd
- = (stopIndex == areaInfo.breakIndex)
- && (areaInfo.letterSpaceCount < areaInfo.getWordLength());
+ = (stopIndex == mapping.endIndex)
+ && (mapping.letterSpaceCount < mapping.getWordLength());
int letterSpaceCount = isWordEnd ? stopIndex - startIndex - 1 : stopIndex - startIndex;
assert letterSpaceCount >= 0;
newIPD = newIPD.plus(letterSpaceIPD.mult(letterSpaceCount));
- if (!(nothingChanged && stopIndex == areaInfo.breakIndex && !hyphenFollows)) {
- // the new AreaInfo object is not equal to the old one
+ if (!(nothingChanged && stopIndex == mapping.endIndex && !hyphenFollows)) {
+ // the new GlyphMapping object is not equal to the old one
changeList.add(
new PendingChange(
- new AreaInfo(startIndex, stopIndex, 0,
+ new GlyphMapping(startIndex, stopIndex, 0,
letterSpaceCount, newIPD, hyphenFollows,
false, false, font, -1, null),
((LeafPosition) pos).getLeafPos() + changeOffset));
@@ -1324,7 +1072,7 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
return false;
}
- // Find the first and last positions in oldList that point to an AreaInfo
+ // Find the first and last positions in oldList that point to a GlyphMapping
// (i.e. getLeafPos() != -1)
LeafPosition startPos = null;
LeafPosition endPos = null;
@@ -1349,8 +1097,8 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
returnedIndices[0] = (startPos != null ? startPos.getLeafPos() : -1) + changeOffset;
returnedIndices[1] = (endPos != null ? endPos.getLeafPos() : -1) + changeOffset;
- int areaInfosAdded = 0;
- int areaInfosRemoved = 0;
+ int mappingsAdded = 0;
+ int mappingsRemoved = 0;
if (!changeList.isEmpty()) {
int oldIndex = -1;
@@ -1360,24 +1108,24 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
while (changeListIterator.hasNext()) {
currChange = (PendingChange) changeListIterator.next();
if (currChange.index == oldIndex) {
- areaInfosAdded++;
- changeIndex = currChange.index + areaInfosAdded - areaInfosRemoved;
+ mappingsAdded++;
+ changeIndex = currChange.index + mappingsAdded - mappingsRemoved;
} else {
- areaInfosRemoved++;
- areaInfosAdded++;
+ mappingsRemoved++;
+ mappingsAdded++;
oldIndex = currChange.index;
- changeIndex = currChange.index + areaInfosAdded - areaInfosRemoved;
- removeAreaInfo(changeIndex);
+ changeIndex = currChange.index + mappingsAdded - mappingsRemoved;
+ removeGlyphMapping(changeIndex);
}
- addAreaInfo(changeIndex, currChange.areaInfo);
+ addGlyphMapping(changeIndex, currChange.mapping);
}
changeList.clear();
}
// increase the end index for getChangedKnuthElements()
- returnedIndices[1] += (areaInfosAdded - areaInfosRemoved);
+ returnedIndices[1] += (mappingsAdded - mappingsRemoved);
// increase offset to use for subsequent paragraphs
- changeOffset += (areaInfosAdded - areaInfosRemoved);
+ changeOffset += (mappingsAdded - mappingsRemoved);
return hasChanged;
}
@@ -1391,16 +1139,16 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
final LinkedList returnList = new LinkedList();
for (; returnedIndices[0] <= returnedIndices[1]; returnedIndices[0]++) {
- AreaInfo areaInfo = getAreaInfo(returnedIndices[0]);
- if (areaInfo.wordSpaceCount == 0) {
- // areaInfo refers either to a word or a word fragment
- addElementsForAWordFragment(returnList, alignment, areaInfo, returnedIndices[0]);
+ GlyphMapping mapping = getGlyphMapping(returnedIndices[0]);
+ if (mapping.wordSpaceCount == 0) {
+ // mapping refers either to a word or a word fragment
+ addElementsForAWordFragment(returnList, alignment, mapping, returnedIndices[0]);
} else {
- // areaInfo refers to a space
- addElementsForASpace(returnList, alignment, areaInfo, returnedIndices[0]);
+ // mapping refers to a space
+ addElementsForASpace(returnList, alignment, mapping, returnedIndices[0]);
}
}
- setFinished(returnedIndices[0] == areaInfos.size() - 1);
+ setFinished(returnedIndices[0] == mappings.size() - 1);
//ElementListObserver.observe(returnList, "text-changed", null);
return returnList;
}
@@ -1409,9 +1157,9 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
public String getWordChars(Position pos) {
int leafValue = ((LeafPosition) pos).getLeafPos() + changeOffset;
if (leafValue != -1) {
- AreaInfo areaInfo = getAreaInfo(leafValue);
- StringBuffer buffer = new StringBuffer(areaInfo.getWordLength());
- for (int i = areaInfo.startIndex; i < areaInfo.breakIndex; i++) {
+ GlyphMapping mapping = getGlyphMapping(leafValue);
+ StringBuffer buffer = new StringBuffer(mapping.getWordLength());
+ for (int i = mapping.startIndex; i < mapping.endIndex; i++) {
buffer.append(foText.charAt(i));
}
return buffer.toString();
@@ -1420,41 +1168,39 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
}
}
- private void addElementsForASpace(List baseList, int alignment, AreaInfo areaInfo,
+ private void addElementsForASpace(List baseList, int alignment, GlyphMapping mapping,
int leafValue) {
LeafPosition mainPosition = new LeafPosition(this, leafValue);
- if (!areaInfo.breakOppAfter) {
+ if (!mapping.breakOppAfter) {
// a non-breaking space
if (alignment == Constants.EN_JUSTIFY) {
// the space can stretch and shrink, and must be preserved
// when starting a line
baseList.add(makeAuxiliaryZeroWidthBox());
baseList.add(makeZeroWidthPenalty(KnuthElement.INFINITE));
- baseList.add(new KnuthGlue(areaInfo.areaIPD, mainPosition, false));
+ baseList.add(new KnuthGlue(mapping.areaIPD, mainPosition, false));
} else {
// the space does not need to stretch or shrink, and must be
// preserved when starting a line
- baseList.add(new KnuthInlineBox(areaInfo.areaIPD.getOpt(), null, mainPosition,
+ baseList.add(new KnuthInlineBox(mapping.areaIPD.getOpt(), null, mainPosition,
true));
}
} else {
- if (foText.charAt(areaInfo.startIndex) != CharUtilities.SPACE
+ if (foText.charAt(mapping.startIndex) != CharUtilities.SPACE
|| foText.getWhitespaceTreatment() == Constants.EN_PRESERVE) {
// a breaking space that needs to be preserved
- baseList
- .addAll(getElementsForBreakingSpace(alignment, areaInfo, auxiliaryPosition, 0,
- mainPosition, areaInfo.areaIPD.getOpt(), true));
+ baseList.addAll(getElementsForBreakingSpace(alignment, mapping, auxiliaryPosition, 0,
+ mainPosition, mapping.areaIPD.getOpt(), true));
} else {
// a (possible block) of breaking spaces
- baseList
- .addAll(getElementsForBreakingSpace(alignment, areaInfo, mainPosition,
- areaInfo.areaIPD.getOpt(), auxiliaryPosition, 0, false));
+ baseList.addAll(getElementsForBreakingSpace(alignment, mapping, mainPosition,
+ mapping.areaIPD.getOpt(), auxiliaryPosition, 0, false));
}
}
}
- private List getElementsForBreakingSpace(int alignment, AreaInfo areaInfo, Position pos2,
+ private List getElementsForBreakingSpace(int alignment, GlyphMapping mapping, Position pos2,
int p2WidthOffset, Position pos3,
int p3WidthOffset, boolean skipZeroCheck) {
List elements = new ArrayList();
@@ -1504,7 +1250,7 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
elements.add(g);
elements.add(makeZeroWidthPenalty(0));
g = new KnuthGlue(
- areaInfo.areaIPD.getOpt(),
+ mapping.areaIPD.getOpt(),
-3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0, pos2, false);
elements.add(g);
}
@@ -1513,25 +1259,24 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
case EN_JUSTIFY:
// justified text:
// the stretch and shrink depends on the space width
- elements.addAll(getElementsForJustifiedText(areaInfo, pos2, p2WidthOffset, pos3,
- p3WidthOffset, skipZeroCheck, areaInfo.areaIPD.getShrink()));
+ elements.addAll(getElementsForJustifiedText(mapping, pos2, p2WidthOffset, pos3,
+ p3WidthOffset, skipZeroCheck, mapping.areaIPD.getShrink()));
break;
default:
// last line justified, the other lines unjustified:
// use only the space stretch
- elements.addAll(getElementsForJustifiedText(areaInfo, pos2, p2WidthOffset, pos3,
+ elements.addAll(getElementsForJustifiedText(mapping, pos2, p2WidthOffset, pos3,
p3WidthOffset, skipZeroCheck, 0));
}
return elements;
}
- private List getElementsForJustifiedText(
- AreaInfo areaInfo, Position pos2, int p2WidthOffset,
- Position pos3, int p3WidthOffset, boolean skipZeroCheck,
- int shrinkability) {
+ private List getElementsForJustifiedText(GlyphMapping mapping, Position pos2, int p2WidthOffset,
+ Position pos3, int p3WidthOffset, boolean skipZeroCheck,
+ int shrinkability) {
- int stretchability = areaInfo.areaIPD.getStretch();
+ int stretchability = mapping.areaIPD.getStretch();
List elements = new ArrayList();
if (skipZeroCheck || lineStartBAP != 0 || lineEndBAP != 0) {
@@ -1543,34 +1288,34 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
elements.add(makeZeroWidthPenalty(KnuthElement.INFINITE));
elements.add(new KnuthGlue(lineStartBAP + p3WidthOffset, 0, 0, pos3, false));
} else {
- elements.add(new KnuthGlue(areaInfo.areaIPD.getOpt(), stretchability, shrinkability,
+ elements.add(new KnuthGlue(mapping.areaIPD.getOpt(), stretchability, shrinkability,
pos2, false));
}
return elements;
}
- private void addElementsForAWordFragment(List baseList, int alignment, AreaInfo areaInfo,
+ private void addElementsForAWordFragment(List baseList, int alignment, GlyphMapping mapping,
int leafValue) {
LeafPosition mainPosition = new LeafPosition(this, leafValue);
// if the last character of the word fragment is '-' or '/',
// the fragment could end a line; in this case, it loses one
// of its letter spaces;
- boolean suppressibleLetterSpace = areaInfo.breakOppAfter && !areaInfo.isHyphenated;
+ boolean suppressibleLetterSpace = mapping.breakOppAfter && !mapping.isHyphenated;
if (letterSpaceIPD.isStiff()) {
// constant letter spacing
baseList.add(new KnuthInlineBox(suppressibleLetterSpace
- ? areaInfo.areaIPD.getOpt() - letterSpaceIPD.getOpt()
- : areaInfo.areaIPD.getOpt(),
+ ? mapping.areaIPD.getOpt() - letterSpaceIPD.getOpt()
+ : mapping.areaIPD.getOpt(),
alignmentContext, notifyPos(mainPosition), false));
} else {
// adjustable letter spacing
int unsuppressibleLetterSpaces = suppressibleLetterSpace
- ? areaInfo.letterSpaceCount - 1
- : areaInfo.letterSpaceCount;
- baseList.add(new KnuthInlineBox(areaInfo.areaIPD.getOpt()
- - areaInfo.letterSpaceCount * letterSpaceIPD.getOpt(),
+ ? mapping.letterSpaceCount - 1
+ : mapping.letterSpaceCount;
+ baseList.add(new KnuthInlineBox(mapping.areaIPD.getOpt()
+ - mapping.letterSpaceCount * letterSpaceIPD.getOpt(),
alignmentContext, notifyPos(mainPosition), false));
baseList.add(makeZeroWidthPenalty(KnuthElement.INFINITE));
baseList.add(new KnuthGlue(letterSpaceIPD.mult(unsuppressibleLetterSpaces),
@@ -1580,19 +1325,19 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
// extra-elements if the word fragment is the end of a syllable,
// or it ends with a character that can be used as a line break
- if (areaInfo.isHyphenated) {
+ if (mapping.isHyphenated) {
MinOptMax widthIfNoBreakOccurs = null;
- if (areaInfo.breakIndex < foText.length()) {
+ if (mapping.endIndex < foText.length()) {
//Add in kerning in no-break condition
- widthIfNoBreakOccurs = letterSpaceAdjustArray[areaInfo.breakIndex];
+ widthIfNoBreakOccurs = letterSpaceAdjustArray[mapping.endIndex];
}
- //if (areaInfo.breakIndex)
+ //if (mapping.breakIndex)
// the word fragment ends at the end of a syllable:
// if a break occurs the content width increases,
// otherwise nothing happens
addElementsForAHyphen(baseList, alignment, hyphIPD, widthIfNoBreakOccurs,
- areaInfo.breakOppAfter && areaInfo.isHyphenated);
+ mapping.breakOppAfter && mapping.isHyphenated);
} else if (suppressibleLetterSpace) {
// the word fragment ends with a character that acts as a hyphen
// if a break occurs the width does not increase,
diff --git a/src/java/org/apache/fop/render/AbstractGenericSVGHandler.java b/src/java/org/apache/fop/render/AbstractGenericSVGHandler.java
index 392eb875e..983a5ad90 100644
--- a/src/java/org/apache/fop/render/AbstractGenericSVGHandler.java
+++ b/src/java/org/apache/fop/render/AbstractGenericSVGHandler.java
@@ -31,6 +31,7 @@ import org.apache.batik.bridge.GVTBuilder;
import org.apache.batik.dom.AbstractDocument;
import org.apache.batik.dom.svg.SVGDOMImplementation;
import org.apache.batik.gvt.GraphicsNode;
+import org.apache.batik.gvt.font.DefaultFontFamilyResolver;
import org.apache.xmlgraphics.java2d.Graphics2DImagePainter;
@@ -122,7 +123,8 @@ public abstract class AbstractGenericSVGHandler implements XMLHandler, RendererC
//Prepare
FOUserAgent userAgent = rendererContext.getUserAgent();
- SVGUserAgent svgUserAgent = new SVGUserAgent(userAgent, new AffineTransform());
+ SVGUserAgent svgUserAgent = new SVGUserAgent(userAgent, DefaultFontFamilyResolver.SINGLETON,
+ new AffineTransform());
//Create Batik BridgeContext
final BridgeContext bridgeContext = new BridgeContext(svgUserAgent);
diff --git a/src/java/org/apache/fop/render/afp/AFPSVGHandler.java b/src/java/org/apache/fop/render/afp/AFPSVGHandler.java
index 4549a26af..ccb4cc678 100644
--- a/src/java/org/apache/fop/render/afp/AFPSVGHandler.java
+++ b/src/java/org/apache/fop/render/afp/AFPSVGHandler.java
@@ -29,6 +29,7 @@ import org.w3c.dom.Document;
import org.apache.batik.bridge.BridgeContext;
import org.apache.batik.dom.svg.SVGDOMImplementation;
import org.apache.batik.gvt.GraphicsNode;
+import org.apache.batik.gvt.font.DefaultFontFamilyResolver;
import org.apache.xmlgraphics.image.loader.ImageManager;
import org.apache.xmlgraphics.image.loader.ImageSessionContext;
@@ -43,6 +44,7 @@ import org.apache.fop.afp.AFPResourceInfo;
import org.apache.fop.afp.AFPResourceManager;
import org.apache.fop.afp.AFPUnitConverter;
import org.apache.fop.afp.svg.AFPBridgeContext;
+import org.apache.fop.afp.svg.AFPFontFamilyResolver;
import org.apache.fop.apps.FOUserAgent;
import org.apache.fop.fonts.FontInfo;
import org.apache.fop.image.loader.batik.BatikUtil;
@@ -53,6 +55,7 @@ import org.apache.fop.render.RendererContext;
import org.apache.fop.render.RendererContext.RendererContextWrapper;
import org.apache.fop.svg.SVGEventProducer;
import org.apache.fop.svg.SVGUserAgent;
+import org.apache.fop.svg.font.AggregatingFontFamilyResolver;
/**
* AFP XML handler for SVG. Uses Apache Batik for SVG processing.
@@ -196,15 +199,13 @@ public class AFPSVGHandler extends AbstractGenericSVGHandler {
*/
public static BridgeContext createBridgeContext(FOUserAgent userAgent, AFPGraphics2D g2d) {
ImageManager imageManager = userAgent.getImageManager();
-
- SVGUserAgent svgUserAgent
- = new SVGUserAgent(userAgent, new AffineTransform());
-
- ImageSessionContext imageSessionContext = userAgent.getImageSessionContext();
-
FontInfo fontInfo = g2d.getFontInfo();
+ SVGUserAgent svgUserAgent = new SVGUserAgent(userAgent, new AggregatingFontFamilyResolver(
+ new AFPFontFamilyResolver(fontInfo, userAgent.getEventBroadcaster()), DefaultFontFamilyResolver.SINGLETON),
+ new AffineTransform());
+ ImageSessionContext imageSessionContext = userAgent.getImageSessionContext();
return new AFPBridgeContext(svgUserAgent, fontInfo, imageManager, imageSessionContext,
- new AffineTransform(), g2d);
+ new AffineTransform(), g2d, userAgent.getEventBroadcaster());
}
/** {@inheritDoc} */
diff --git a/src/java/org/apache/fop/render/java2d/CustomFontMetricsMapper.java b/src/java/org/apache/fop/render/java2d/CustomFontMetricsMapper.java
index 2f8865b14..5e9372a75 100644
--- a/src/java/org/apache/fop/render/java2d/CustomFontMetricsMapper.java
+++ b/src/java/org/apache/fop/render/java2d/CustomFontMetricsMapper.java
@@ -21,6 +21,7 @@ package org.apache.fop.render.java2d;
import java.awt.Font;
import java.awt.FontFormatException;
+import java.awt.Rectangle;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
@@ -183,11 +184,31 @@ public class CustomFontMetricsMapper extends Typeface implements FontMetricsMapp
return typeface.getWidths();
}
+ public Rectangle getBoundingBox(int glyphIndex, int size) {
+ return typeface.getBoundingBox(glyphIndex, size);
+ }
+
/** {@inheritDoc} */
public final int getXHeight(final int size) {
return typeface.getXHeight(size);
}
+ public int getUnderlinePosition(int size) {
+ return typeface.getUnderlinePosition(size);
+ }
+
+ public int getUnderlineThickness(int size) {
+ return typeface.getUnderlineThickness(size);
+ }
+
+ public int getStrikeoutPosition(int size) {
+ return typeface.getStrikeoutPosition(size);
+ }
+
+ public int getStrikeoutThickness(int size) {
+ return typeface.getStrikeoutThickness(size);
+ }
+
/** {@inheritDoc} */
public final boolean hasKerningInfo() {
return typeface.hasKerningInfo();
diff --git a/src/java/org/apache/fop/render/java2d/Java2DFontMetrics.java b/src/java/org/apache/fop/render/java2d/Java2DFontMetrics.java
index fc4af3574..5e681e9e2 100644
--- a/src/java/org/apache/fop/render/java2d/Java2DFontMetrics.java
+++ b/src/java/org/apache/fop/render/java2d/Java2DFontMetrics.java
@@ -223,6 +223,26 @@ public class Java2DFontMetrics {
return xHeight * 1000;
}
+ public int getUnderlinePosition(String family, int style, int size) {
+ setFont(family, style, size);
+ return -Math.round(lineMetrics.getUnderlineOffset());
+ }
+
+ public int getUnderlineThickness(String family, int style, int size) {
+ setFont(family, style, size);
+ return Math.round(lineMetrics.getUnderlineThickness());
+ }
+
+ public int getStrikeoutPosition(String family, int style, int size) {
+ setFont(family, style, size);
+ return -Math.round(lineMetrics.getStrikethroughOffset());
+ }
+
+ public int getStrikeoutThickness(String family, int style, int size) {
+ setFont(family, style, size);
+ return Math.round(lineMetrics.getStrikethroughThickness());
+ }
+
/**
* Returns width (in 1/1000ths of point size) of character at
* code point i
diff --git a/src/java/org/apache/fop/render/java2d/Java2DSVGHandler.java b/src/java/org/apache/fop/render/java2d/Java2DSVGHandler.java
index 110581f27..f3128c600 100644
--- a/src/java/org/apache/fop/render/java2d/Java2DSVGHandler.java
+++ b/src/java/org/apache/fop/render/java2d/Java2DSVGHandler.java
@@ -31,6 +31,7 @@ import org.apache.commons.logging.LogFactory;
import org.apache.batik.bridge.BridgeContext;
import org.apache.batik.bridge.GVTBuilder;
import org.apache.batik.gvt.GraphicsNode;
+import org.apache.batik.gvt.font.DefaultFontFamilyResolver;
import org.apache.fop.image.loader.batik.BatikUtil;
import org.apache.fop.render.AbstractGenericSVGHandler;
@@ -128,8 +129,8 @@ public class Java2DSVGHandler extends AbstractGenericSVGHandler
int x = info.currentXPosition;
int y = info.currentYPosition;
-
- SVGUserAgent ua = new SVGUserAgent(context.getUserAgent(), new AffineTransform());
+ SVGUserAgent ua = new SVGUserAgent(context.getUserAgent(), DefaultFontFamilyResolver.SINGLETON,
+ new AffineTransform());
BridgeContext ctx = new BridgeContext(ua);
diff --git a/src/java/org/apache/fop/render/java2d/SystemFontMetricsMapper.java b/src/java/org/apache/fop/render/java2d/SystemFontMetricsMapper.java
index 06c975b86..f922e3f05 100644
--- a/src/java/org/apache/fop/render/java2d/SystemFontMetricsMapper.java
+++ b/src/java/org/apache/fop/render/java2d/SystemFontMetricsMapper.java
@@ -20,6 +20,7 @@
package org.apache.fop.render.java2d;
// Java
+import java.awt.Rectangle;
import java.util.Map;
import java.util.Set;
@@ -133,6 +134,22 @@ public class SystemFontMetricsMapper extends Typeface implements FontMetricsMapp
return java2DFontMetrics.getXHeight(family, style, size);
}
+ public int getUnderlinePosition(int size) {
+ return java2DFontMetrics.getUnderlinePosition(family, style, size);
+ }
+
+ public int getUnderlineThickness(int size) {
+ return java2DFontMetrics.getUnderlineThickness(family, style, size);
+ }
+
+ public int getStrikeoutPosition(int size) {
+ return java2DFontMetrics.getStrikeoutPosition(family, style, size);
+ }
+
+ public int getStrikeoutThickness(int size) {
+ return java2DFontMetrics.getStrikeoutThickness(family, style, size);
+ }
+
/**
* {@inheritDoc}
*/
@@ -148,6 +165,10 @@ public class SystemFontMetricsMapper extends Typeface implements FontMetricsMapp
return java2DFontMetrics.getWidths(family, style, Java2DFontMetrics.FONT_SIZE);
}
+ public Rectangle getBoundingBox(int glyphIndex, int size) {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
/**
* {@inheritDoc}
*/
diff --git a/src/java/org/apache/fop/render/pdf/PDFImageHandlerSVG.java b/src/java/org/apache/fop/render/pdf/PDFImageHandlerSVG.java
index 45d8dff2d..3b7084a65 100644
--- a/src/java/org/apache/fop/render/pdf/PDFImageHandlerSVG.java
+++ b/src/java/org/apache/fop/render/pdf/PDFImageHandlerSVG.java
@@ -52,6 +52,7 @@ import org.apache.fop.svg.PDFBridgeContext;
import org.apache.fop.svg.PDFGraphics2D;
import org.apache.fop.svg.SVGEventProducer;
import org.apache.fop.svg.SVGUserAgent;
+import org.apache.fop.svg.font.FOPFontFamilyResolverImpl;
/**
* Image Handler implementation which handles SVG images.
@@ -76,7 +77,8 @@ public class PDFImageHandlerSVG implements ImageHandler {
}
final float uaResolution = userAgent.getSourceResolution();
- SVGUserAgent ua = new SVGUserAgent(userAgent, new AffineTransform());
+ SVGUserAgent ua = new SVGUserAgent(userAgent, new FOPFontFamilyResolverImpl(pdfContext.getFontInfo()),
+ new AffineTransform());
GVTBuilder builder = new GVTBuilder();
diff --git a/src/java/org/apache/fop/render/ps/AbstractPSTranscoder.java b/src/java/org/apache/fop/render/ps/AbstractPSTranscoder.java
index 1d464cae6..96886910e 100644
--- a/src/java/org/apache/fop/render/ps/AbstractPSTranscoder.java
+++ b/src/java/org/apache/fop/render/ps/AbstractPSTranscoder.java
@@ -40,6 +40,7 @@ import org.apache.fop.apps.FOPException;
import org.apache.fop.fonts.FontInfo;
import org.apache.fop.svg.AbstractFOPTranscoder;
import org.apache.fop.svg.PDFDocumentGraphics2DConfigurator;
+import org.apache.fop.svg.font.FOPFontFamilyResolverImpl;
/**
* <p>This class enables to transcode an input to a PostScript document.</p>
@@ -115,6 +116,8 @@ public abstract class AbstractPSTranscoder extends AbstractFOPTranscoder {
this.fontInfo = PDFDocumentGraphics2DConfigurator.createFontInfo(
getEffectiveConfiguration(), useComplexScriptFeatures);
graphics.setCustomTextHandler(new NativeTextHandler(graphics, fontInfo));
+ ((FOPTranscoderUserAgent) userAgent).setFontFamilyResolver(
+ new FOPFontFamilyResolverImpl(fontInfo));
} catch (FOPException fe) {
throw new TranscoderException(fe);
}
diff --git a/src/java/org/apache/fop/render/ps/PSImageHandlerSVG.java b/src/java/org/apache/fop/render/ps/PSImageHandlerSVG.java
index 3ade34522..bd8e97063 100644
--- a/src/java/org/apache/fop/render/ps/PSImageHandlerSVG.java
+++ b/src/java/org/apache/fop/render/ps/PSImageHandlerSVG.java
@@ -41,6 +41,7 @@ import org.apache.fop.render.ImageHandler;
import org.apache.fop.render.RenderingContext;
import org.apache.fop.svg.SVGEventProducer;
import org.apache.fop.svg.SVGUserAgent;
+import org.apache.fop.svg.font.FOPFontFamilyResolverImpl;
/**
* Image handler implementation which handles SVG images for PostScript output.
@@ -62,8 +63,8 @@ public class PSImageHandlerSVG implements ImageHandler {
boolean strokeText = false;
//TODO Configure text stroking
- SVGUserAgent ua
- = new SVGUserAgent(context.getUserAgent(), new AffineTransform());
+ SVGUserAgent ua = new SVGUserAgent(context.getUserAgent(),
+ new FOPFontFamilyResolverImpl(psContext.getFontInfo()), new AffineTransform());
PSGraphics2D graphics = new PSGraphics2D(strokeText, gen);
graphics.setGraphicContext(new org.apache.xmlgraphics.java2d.GraphicContext());
diff --git a/src/java/org/apache/fop/render/ps/PSSVGHandler.java b/src/java/org/apache/fop/render/ps/PSSVGHandler.java
index e30f6391b..dfbaf60b7 100644
--- a/src/java/org/apache/fop/render/ps/PSSVGHandler.java
+++ b/src/java/org/apache/fop/render/ps/PSSVGHandler.java
@@ -249,8 +249,7 @@ public class PSSVGHandler extends AbstractGenericSVGHandler
strokeText = cfg.getChild("stroke-text", true).getValueAsBoolean(strokeText);
}
- SVGUserAgent ua
- = new SVGUserAgent(context.getUserAgent(), new AffineTransform());
+ SVGUserAgent ua = new SVGUserAgent(context.getUserAgent(), null /* TODO */, new AffineTransform());
PSGraphics2D graphics = new PSGraphics2D(strokeText, gen);
graphics.setGraphicContext(new org.apache.xmlgraphics.java2d.GraphicContext());
diff --git a/src/java/org/apache/fop/render/ps/PSTextPainter.java b/src/java/org/apache/fop/render/ps/PSTextPainter.java
index eb2188026..44b7b08a3 100644
--- a/src/java/org/apache/fop/render/ps/PSTextPainter.java
+++ b/src/java/org/apache/fop/render/ps/PSTextPainter.java
@@ -19,25 +19,21 @@
package org.apache.fop.render.ps;
-import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.AffineTransform;
-import java.awt.geom.Ellipse2D;
-import java.awt.geom.GeneralPath;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
+import java.awt.geom.Point2D.Double;
import java.io.IOException;
-import java.text.AttributedCharacterIterator;
import java.util.Iterator;
+import java.util.LinkedList;
import java.util.List;
-import org.apache.batik.gvt.font.GVTGlyphVector;
import org.apache.batik.gvt.text.TextPaintInfo;
-import org.apache.batik.gvt.text.TextSpanLayout;
import org.apache.xmlgraphics.java2d.ps.PSGraphics2D;
import org.apache.xmlgraphics.ps.PSGenerator;
@@ -48,7 +44,6 @@ import org.apache.fop.fonts.FontMetrics;
import org.apache.fop.fonts.LazyFont;
import org.apache.fop.fonts.MultiByteFont;
import org.apache.fop.svg.NativeTextPainter;
-import org.apache.fop.util.CharUtilities;
import org.apache.fop.util.HexEncoder;
/**
@@ -62,10 +57,20 @@ import org.apache.fop.util.HexEncoder;
*/
public class PSTextPainter extends NativeTextPainter {
- private static final boolean DEBUG = false;
-
private FontResourceCache fontResources;
+ private PSGraphics2D ps;
+
+ private PSGenerator gen;
+
+ private TextUtil textUtil;
+
+ private boolean flushCurrentRun;
+
+ private PSTextRun psRun;
+
+ private Double relPos;
+
private static final AffineTransform IDENTITY_TRANSFORM = new AffineTransform();
/**
@@ -82,165 +87,26 @@ public class PSTextPainter extends NativeTextPainter {
return g2d instanceof PSGraphics2D;
}
- /** {@inheritDoc} */
- protected void paintTextRun(TextRun textRun, Graphics2D g2d) throws IOException {
- AttributedCharacterIterator runaci = textRun.getACI();
- runaci.first();
-
- TextPaintInfo tpi = (TextPaintInfo)runaci.getAttribute(PAINT_INFO);
- if (tpi == null || !tpi.visible) {
- return;
- }
- if ((tpi != null) && (tpi.composite != null)) {
- g2d.setComposite(tpi.composite);
- }
-
- //------------------------------------
- TextSpanLayout layout = textRun.getLayout();
- logTextRun(runaci, layout);
- CharSequence chars = collectCharacters(runaci);
- runaci.first(); //Reset ACI
-
- final PSGraphics2D ps = (PSGraphics2D)g2d;
- final PSGenerator gen = ps.getPSGenerator();
+ @Override
+ protected void preparePainting(Graphics2D g2d) {
+ ps = (PSGraphics2D) g2d;
+ gen = ps.getPSGenerator();
ps.preparePainting();
+ }
- if (DEBUG) {
- log.debug("Text: " + chars);
- gen.commentln("%Text: " + chars);
- }
-
- GeneralPath debugShapes = null;
- if (DEBUG) {
- debugShapes = new GeneralPath();
- }
-
- TextUtil textUtil = new TextUtil(gen);
- textUtil.setupFonts(runaci);
- if (!textUtil.hasFonts()) {
- //Draw using Java2D when no native fonts are available
- textRun.getLayout().draw(g2d);
- return;
- }
-
+ @Override
+ protected void saveGraphicsState() throws IOException {
gen.saveGraphicsState();
- gen.concatMatrix(g2d.getTransform());
- Shape imclip = g2d.getClip();
- clip(ps, imclip);
-
- gen.writeln("BT"); //beginTextObject()
-
- AffineTransform localTransform = new AffineTransform();
- Point2D prevPos = null;
- GVTGlyphVector gv = layout.getGlyphVector();
- PSTextRun psRun = new PSTextRun(); //Used to split a text run into smaller runs
- for (int index = 0, c = gv.getNumGlyphs(); index < c; index++) {
- char ch = chars.charAt(index);
- boolean visibleChar = gv.isGlyphVisible(index)
- || (CharUtilities.isAnySpace(ch) && !CharUtilities.isZeroWidthSpace(ch));
- logCharacter(ch, layout, index, visibleChar);
- if (!visibleChar) {
- continue;
- }
- Point2D glyphPos = gv.getGlyphPosition(index);
-
- AffineTransform glyphTransform = gv.getGlyphTransform(index);
- if (log.isTraceEnabled()) {
- log.trace("pos " + glyphPos + ", transform " + glyphTransform);
- }
- if (DEBUG) {
- Shape sh = gv.getGlyphLogicalBounds(index);
- if (sh == null) {
- sh = new Ellipse2D.Double(glyphPos.getX(), glyphPos.getY(), 2, 2);
- }
- debugShapes.append(sh, false);
- }
-
- //Exact position of the glyph
- localTransform.setToIdentity();
- localTransform.translate(glyphPos.getX(), glyphPos.getY());
- if (glyphTransform != null) {
- localTransform.concatenate(glyphTransform);
- }
- localTransform.scale(1, -1);
-
- boolean flushCurrentRun = false;
- //Try to optimize by combining characters using the same font and on the same line.
- if (glyphTransform != null) {
- //Happens for text-on-a-path
- flushCurrentRun = true;
- }
- if (psRun.getRunLength() >= 128) {
- //Don't let a run get too long
- flushCurrentRun = true;
- }
-
- //Note the position of the glyph relative to the previous one
- Point2D relPos;
- if (prevPos == null) {
- relPos = new Point2D.Double(0, 0);
- } else {
- relPos = new Point2D.Double(
- glyphPos.getX() - prevPos.getX(),
- glyphPos.getY() - prevPos.getY());
- }
- if (psRun.vertChanges == 0
- && psRun.getHorizRunLength() > 2
- && relPos.getY() != 0) {
- //new line
- flushCurrentRun = true;
- }
-
- //Select the actual character to paint
- char paintChar = (CharUtilities.isAnySpace(ch) ? ' ' : ch);
-
- //Select (sub)font for character
- Font f = textUtil.selectFontForChar(paintChar);
- char mapped = f.mapChar(ch);
- boolean fontChanging = textUtil.isFontChanging(f, mapped);
- if (fontChanging) {
- flushCurrentRun = true;
- }
-
- if (flushCurrentRun) {
- //Paint the current run and reset for the next run
- psRun.paint(ps, textUtil, tpi);
- psRun.reset();
- }
-
- //Track current run
- psRun.addCharacter(paintChar, relPos);
- psRun.noteStartingTransformation(localTransform);
-
- //Change font if necessary
- if (fontChanging) {
- textUtil.setCurrentFont(f, mapped);
- }
+ }
- //Update last position
- prevPos = glyphPos;
- }
- psRun.paint(ps, textUtil, tpi);
- gen.writeln("ET"); //endTextObject()
+ @Override
+ protected void restoreGraphicsState() throws IOException {
gen.restoreGraphicsState();
-
- if (DEBUG) {
- //Paint debug shapes
- g2d.setStroke(new BasicStroke(0));
- g2d.setColor(Color.LIGHT_GRAY);
- g2d.draw(debugShapes);
- }
}
- private void applyColor(Paint paint, final PSGenerator gen) throws IOException {
- if (paint == null) {
- return;
- } else if (paint instanceof Color) {
- Color col = (Color)paint;
- gen.useColor(col);
- } else {
- log.warn("Paint not supported: " + paint.toString());
- }
+ @Override
+ protected void setInitialTransform(AffineTransform transform) throws IOException {
+ gen.concatMatrix(transform);
}
private PSFontResource getResourceForFont(Font f, String postfix) {
@@ -248,7 +114,8 @@ public class PSTextPainter extends NativeTextPainter {
return this.fontResources.getFontResourceForFontKey(key);
}
- private void clip(PSGraphics2D ps, Shape shape) throws IOException {
+ @Override
+ protected void clip(Shape shape) throws IOException {
if (shape == null) {
return;
}
@@ -258,17 +125,76 @@ public class PSTextPainter extends NativeTextPainter {
ps.getPSGenerator().writeln("clip");
}
+ @Override
+ protected void beginTextObject() throws IOException {
+ gen.writeln("BT");
+ textUtil = new TextUtil();
+ psRun = new PSTextRun(); //Used to split a text run into smaller runs
+ }
+
+ @Override
+ protected void endTextObject() throws IOException {
+ psRun.paint(ps, textUtil, tpi);
+ gen.writeln("ET");
+ }
+
+ @Override
+ protected void positionGlyph(Point2D prevPos, Point2D glyphPos, boolean reposition) {
+ flushCurrentRun = false;
+ //Try to optimize by combining characters using the same font and on the same line.
+ if (reposition) {
+ //Happens for text-on-a-path
+ flushCurrentRun = true;
+ }
+ if (psRun.getRunLength() >= 128) {
+ //Don't let a run get too long
+ flushCurrentRun = true;
+ }
+
+ //Note the position of the glyph relative to the previous one
+ if (prevPos == null) {
+ relPos = new Point2D.Double(0, 0);
+ } else {
+ relPos = new Point2D.Double(
+ glyphPos.getX() - prevPos.getX(),
+ glyphPos.getY() - prevPos.getY());
+ }
+ if (psRun.vertChanges == 0
+ && psRun.getHorizRunLength() > 2
+ && relPos.getY() != 0) {
+ //new line
+ flushCurrentRun = true;
+ }
+ }
+
+ @Override
+ protected void writeGlyph(char glyph, AffineTransform localTransform) throws IOException {
+ boolean fontChanging = textUtil.isFontChanging(font, glyph);
+ if (fontChanging) {
+ flushCurrentRun = true;
+ }
+
+ if (flushCurrentRun) {
+ //Paint the current run and reset for the next run
+ psRun.paint(ps, textUtil, tpi);
+ psRun.reset();
+ }
+
+ //Track current run
+ psRun.addGlyph(glyph, relPos);
+ psRun.noteStartingTransformation(localTransform);
+
+ //Change font if necessary
+ if (fontChanging) {
+ textUtil.setCurrentFont(font, glyph);
+ }
+ }
+
private class TextUtil {
- private PSGenerator gen;
- private Font[] fonts;
private Font currentFont;
private int currentEncoding = -1;
- public TextUtil(PSGenerator gen) {
- this.gen = gen;
- }
-
public boolean isMultiByte(Font f) {
FontMetrics metrics = f.getFontMetrics();
boolean multiByte = metrics instanceof MultiByteFont || metrics instanceof LazyFont
@@ -276,15 +202,6 @@ public class PSTextPainter extends NativeTextPainter {
return multiByte;
}
- public Font selectFontForChar(char ch) {
- for (int i = 0, c = fonts.length; i < c; i++) {
- if (fonts[i].hasChar(ch)) {
- return fonts[i];
- }
- }
- return fonts[0]; //TODO Maybe fall back to painting with shapes
- }
-
public void writeTextMatrix(AffineTransform transform) throws IOException {
double[] matrix = new double[6];
transform.getMatrix(matrix);
@@ -335,27 +252,19 @@ public class PSTextPainter extends NativeTextPainter {
setCurrentFont(font, encoding);
}
- public void setupFonts(AttributedCharacterIterator runaci) {
- this.fonts = findFonts(runaci);
- }
-
- public boolean hasFonts() {
- return (fonts != null) && (fonts.length > 0);
- }
-
}
private class PSTextRun {
private AffineTransform textTransform;
- private List relativePositions = new java.util.LinkedList();
- private StringBuffer currentChars = new StringBuffer();
+ private List<Point2D> relativePositions = new LinkedList<Point2D>();
+ private StringBuffer currentGlyphs = new StringBuffer();
private int horizChanges = 0;
private int vertChanges = 0;
public void reset() {
textTransform = null;
- currentChars.setLength(0);
+ currentGlyphs.setLength(0);
horizChanges = 0;
vertChanges = 0;
relativePositions.clear();
@@ -369,9 +278,9 @@ public class PSTextPainter extends NativeTextPainter {
return 0;
}
- public void addCharacter(char paintChar, Point2D relPos) {
+ public void addGlyph(char glyph, Point2D relPos) {
addRelativePosition(relPos);
- currentChars.append(paintChar);
+ currentGlyphs.append(glyph);
}
private void addRelativePosition(Point2D relPos) {
@@ -393,7 +302,7 @@ public class PSTextPainter extends NativeTextPainter {
}
public int getRunLength() {
- return currentChars.length();
+ return currentGlyphs.length();
}
private boolean isXShow() {
@@ -407,9 +316,6 @@ public class PSTextPainter extends NativeTextPainter {
public void paint(PSGraphics2D g2d, TextUtil textUtil, TextPaintInfo tpi)
throws IOException {
if (getRunLength() > 0) {
- if (log.isDebugEnabled()) {
- log.debug("Text run: " + currentChars);
- }
textUtil.writeTextMatrix(this.textTransform);
if (isXShow()) {
log.debug("Horizontal text: xshow");
@@ -431,25 +337,20 @@ public class PSTextPainter extends NativeTextPainter {
private void paintXYShow(PSGraphics2D g2d, TextUtil textUtil, Paint paint,
boolean x, boolean y) throws IOException {
- PSGenerator gen = textUtil.gen;
- char firstChar = this.currentChars.charAt(0);
- //Font only has to be setup up before the first character
- Font f = textUtil.selectFontForChar(firstChar);
- char mapped = f.mapChar(firstChar);
- textUtil.selectFont(f, mapped);
- textUtil.setCurrentFont(f, mapped);
- applyColor(paint, gen);
-
- boolean multiByte = textUtil.isMultiByte(f);
+ char glyph = currentGlyphs.charAt(0);
+ textUtil.selectFont(font, glyph);
+ textUtil.setCurrentFont(font, glyph);
+ applyColor(paint);
+
+ boolean multiByte = textUtil.isMultiByte(font);
StringBuffer sb = new StringBuffer();
sb.append(multiByte ? '<' : '(');
- for (int i = 0, c = this.currentChars.length(); i < c; i++) {
- char ch = this.currentChars.charAt(i);
- mapped = f.mapChar(ch);
+ for (int i = 0, c = this.currentGlyphs.length(); i < c; i++) {
+ glyph = this.currentGlyphs.charAt(i);
if (multiByte) {
- sb.append(HexEncoder.encode(mapped));
+ sb.append(HexEncoder.encode(glyph));
} else {
- char codepoint = (char) (mapped % 256);
+ char codepoint = (char) (glyph % 256);
PSGenerator.escapeChar(codepoint, sb);
}
}
@@ -457,9 +358,9 @@ public class PSTextPainter extends NativeTextPainter {
if (x || y) {
sb.append("\n[");
int idx = 0;
- Iterator iter = this.relativePositions.iterator();
+ Iterator<Point2D> iter = this.relativePositions.iterator();
while (iter.hasNext()) {
- Point2D pt = (Point2D)iter.next();
+ Point2D pt = iter.next();
if (idx > 0) {
if (x) {
sb.append(format(gen, pt.getX()));
@@ -500,6 +401,17 @@ public class PSTextPainter extends NativeTextPainter {
gen.writeln(sb.toString());
}
+ private void applyColor(Paint paint) throws IOException {
+ if (paint == null) {
+ return;
+ } else if (paint instanceof Color) {
+ Color col = (Color) paint;
+ gen.useColor(col);
+ } else {
+ log.warn("Paint not supported: " + paint.toString());
+ }
+ }
+
private String format(PSGenerator gen, double coord) {
if (Math.abs(coord) < 0.00001) {
return "0";
@@ -510,30 +422,21 @@ public class PSTextPainter extends NativeTextPainter {
private void paintStrokedGlyphs(PSGraphics2D g2d, TextUtil textUtil,
Paint strokePaint, Stroke stroke) throws IOException {
- PSGenerator gen = textUtil.gen;
-
- applyColor(strokePaint, gen);
+ applyColor(strokePaint);
PSGraphics2D.applyStroke(stroke, gen);
- Font f = null;
- Iterator iter = this.relativePositions.iterator();
+ Iterator<Point2D> iter = this.relativePositions.iterator();
iter.next();
Point2D pos = new Point2D.Double(0, 0);
gen.writeln("0 0 M");
- for (int i = 0, c = this.currentChars.length(); i < c; i++) {
- char ch = this.currentChars.charAt(0);
+ for (int i = 0, c = this.currentGlyphs.length(); i < c; i++) {
+ char mapped = this.currentGlyphs.charAt(i);
if (i == 0) {
- //Font only has to be setup up before the first character
- f = textUtil.selectFontForChar(ch);
- }
- char mapped = f.mapChar(ch);
- if (i == 0) {
- textUtil.selectFont(f, mapped);
- textUtil.setCurrentFont(f, mapped);
+ textUtil.selectFont(font, mapped);
+ textUtil.setCurrentFont(font, mapped);
}
//add glyph outlines to current path
- mapped = f.mapChar(this.currentChars.charAt(i));
- FontMetrics metrics = f.getFontMetrics();
+ FontMetrics metrics = font.getFontMetrics();
boolean multiByte = metrics instanceof MultiByteFont
|| metrics instanceof LazyFont
&& ((LazyFont) metrics).getRealFont() instanceof MultiByteFont;
@@ -542,14 +445,14 @@ public class PSTextPainter extends NativeTextPainter {
gen.write(HexEncoder.encode(mapped));
gen.write(">");
} else {
- char codepoint = (char)(mapped % 256);
+ char codepoint = (char) (mapped % 256);
gen.write("(" + codepoint + ")");
}
gen.writeln(" false charpath");
if (iter.hasNext()) {
//Position for the next character
- Point2D pt = (Point2D)iter.next();
+ Point2D pt = iter.next();
pos.setLocation(pos.getX() + pt.getX(), pos.getY() - pt.getY());
gen.writeln(gen.formatDouble5(pos.getX()) + " "
+ gen.formatDouble5(pos.getY()) + " M");
diff --git a/src/java/org/apache/fop/svg/ACIUtils.java b/src/java/org/apache/fop/svg/ACIUtils.java
index f54a026e1..deba98f21 100644
--- a/src/java/org/apache/fop/svg/ACIUtils.java
+++ b/src/java/org/apache/fop/svg/ACIUtils.java
@@ -38,7 +38,8 @@ import org.apache.batik.gvt.text.GVTAttributedCharacterIterator;
import org.apache.fop.fonts.Font;
import org.apache.fop.fonts.FontInfo;
-import org.apache.fop.fonts.FontTriplet;
+import org.apache.fop.svg.font.FOPGVTFont;
+import org.apache.fop.svg.font.FOPGVTFontFamily;
/**
* Utilities for java.text.AttributedCharacterIterator.
@@ -64,57 +65,43 @@ public final class ACIUtils {
@SuppressWarnings("unchecked")
List<GVTFontFamily> gvtFonts = (List<GVTFontFamily>) aci.getAttribute(
GVTAttributedCharacterIterator.TextAttribute.GVT_FONT_FAMILIES);
- Float posture = (Float) aci.getAttribute(TextAttribute.POSTURE);
- Float taWeight = (Float) aci.getAttribute(TextAttribute.WEIGHT);
- Float fontSize = (Float) aci.getAttribute(TextAttribute.SIZE);
-
- String style = toStyle(posture);
- int weight = toCSSWeight(taWeight);
- int fsize = (int)(fontSize.floatValue() * 1000);
+ String style = toStyle((Float) aci.getAttribute(TextAttribute.POSTURE));
+ int weight = toCSSWeight((Float) aci.getAttribute(TextAttribute.WEIGHT));
+ float fontSize = ((Float) aci.getAttribute(TextAttribute.SIZE)).floatValue();
String firstFontFamily = null;
-
//GVT_FONT can sometimes be different from the fonts in GVT_FONT_FAMILIES
//or GVT_FONT_FAMILIES can even be empty and only GVT_FONT is set
- /* The following code section is not available until Batik 1.7 is released. */
- GVTFont gvtFont = (GVTFont)aci.getAttribute(
- GVTAttributedCharacterIterator.TextAttribute.GVT_FONT);
+ GVTFont gvtFont = (GVTFont) aci.getAttribute(GVTAttributedCharacterIterator.TextAttribute.GVT_FONT);
if (gvtFont != null) {
String gvtFontFamily = gvtFont.getFamilyName();
- if (fontInfo.hasFont(gvtFontFamily, style, weight)) {
- FontTriplet triplet = fontInfo.fontLookup(gvtFontFamily, style,
- weight);
- Font f = fontInfo.getFontInstance(triplet, fsize);
+ if (gvtFont instanceof FOPGVTFont) {
+ Font font = ((FOPGVTFont) gvtFont).getFont();
if (LOG.isDebugEnabled()) {
LOG.debug("Found a font that matches the GVT font: "
+ gvtFontFamily + ", " + weight + ", " + style
- + " -> " + f);
+ + " -> " + font);
}
- fonts.add(f);
+ fonts.add(font);
}
firstFontFamily = gvtFontFamily;
}
if (gvtFonts != null) {
boolean haveInstanceOfSVGFontFamily = false;
- for (GVTFontFamily fam : gvtFonts) {
- if (fam instanceof SVGFontFamily) {
+ for (GVTFontFamily fontFamily : gvtFonts) {
+ if (fontFamily instanceof SVGFontFamily) {
haveInstanceOfSVGFontFamily = true;
- }
- String fontFamily = fam.getFamilyName();
- if (fontInfo.hasFont(fontFamily, style, weight)) {
- FontTriplet triplet = fontInfo.fontLookup(fontFamily, style,
- weight);
- Font f = fontInfo.getFontInstance(triplet, fsize);
+ } else if (fontFamily instanceof FOPGVTFontFamily) {
+ Font font = ((FOPGVTFontFamily) fontFamily).deriveFont(fontSize, aci).getFont();
if (LOG.isDebugEnabled()) {
LOG.debug("Found a font that matches the GVT font family: "
- + fontFamily + ", " + weight + ", " + style
- + " -> " + f);
+ + fontFamily.getFamilyName() + ", " + weight + ", " + style + " -> " + font);
}
- fonts.add(f);
+ fonts.add(font);
}
if (firstFontFamily == null) {
- firstFontFamily = fontFamily;
+ firstFontFamily = fontFamily.getFamilyName();
}
}
// SVG fonts are embedded fonts in the SVG document and are rarely used; however if they
@@ -126,25 +113,10 @@ public final class ACIUtils {
return null; // Let Batik paint this text!
}
}
- if (fonts.isEmpty()) {
- if (firstFontFamily == null) {
- //This will probably never happen. Just to be on the safe side.
- firstFontFamily = "any";
- }
- //lookup with fallback possibility (incl. substitution notification)
- FontTriplet triplet = fontInfo.fontLookup(firstFontFamily, style, weight);
- Font f = fontInfo.getFontInstance(triplet, fsize);
- if (LOG.isDebugEnabled()) {
- LOG.debug("Falling back to adjustable font lookup up for: "
- + firstFontFamily + ", " + weight + ", " + style
- + " -> " + f);
- }
- fonts.add(f);
- }
- return fonts.toArray(new Font[fonts.size()]);
+ return fonts.isEmpty() ? null : fonts.toArray(new Font[fonts.size()]);
}
- private static int toCSSWeight(Float weight) {
+ public static int toCSSWeight(Float weight) {
if (weight == null) {
return 400;
} else if (weight <= TextAttribute.WEIGHT_EXTRA_LIGHT.floatValue()) {
@@ -170,7 +142,7 @@ public final class ACIUtils {
}
}
- private static String toStyle(Float posture) {
+ public static String toStyle(Float posture) {
return ((posture != null) && (posture.floatValue() > 0.0))
? Font.STYLE_ITALIC
: Font.STYLE_NORMAL;
diff --git a/src/java/org/apache/fop/svg/AbstractFOPTextPainter.java b/src/java/org/apache/fop/svg/AbstractFOPTextPainter.java
index 3e16799f4..7df127d3a 100644
--- a/src/java/org/apache/fop/svg/AbstractFOPTextPainter.java
+++ b/src/java/org/apache/fop/svg/AbstractFOPTextPainter.java
@@ -23,20 +23,15 @@ import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Shape;
-import java.awt.font.TextAttribute;
-import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.text.AttributedCharacterIterator;
import java.text.CharacterIterator;
-import java.util.Iterator;
-import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-import org.apache.batik.dom.svg.SVGOMTextElement;
import org.apache.batik.gvt.TextNode;
import org.apache.batik.gvt.TextPainter;
import org.apache.batik.gvt.renderer.StrokingTextPainter;
@@ -66,15 +61,15 @@ public abstract class AbstractFOPTextPainter implements TextPainter {
* Use the stroking text painter to get the bounds and shape.
* Also used as a fallback to draw the string with strokes.
*/
- protected static final TextPainter
- PROXY_PAINTER = StrokingTextPainter.getInstance();
+ private final TextPainter proxyTextPainter;
/**
* Create a new PS text painter with the given font information.
* @param nativeTextHandler the NativeTextHandler instance used for text painting
*/
- public AbstractFOPTextPainter(FOPTextHandler nativeTextHandler) {
+ public AbstractFOPTextPainter(FOPTextHandler nativeTextHandler, TextPainter proxyTextPainter) {
this.nativeTextHandler = nativeTextHandler;
+ this.proxyTextPainter = proxyTextPainter;
}
/**
@@ -85,19 +80,10 @@ public abstract class AbstractFOPTextPainter implements TextPainter {
* @param g2d the Graphics2D to use
*/
public void paint(TextNode node, Graphics2D g2d) {
- Point2D loc = node.getLocation();
- if (!isSupportedGraphics2D(g2d) || hasUnsupportedAttributes(node)) {
- if (log.isDebugEnabled()) {
- log.debug("painting text node " + node
- + " by stroking due to unsupported attributes or an incompatible Graphics2D");
- }
- PROXY_PAINTER.paint(node, g2d);
- } else {
- if (log.isDebugEnabled()) {
- log.debug("painting text node " + node + " normally.");
- }
- paintTextRuns(node.getTextRuns(), g2d, loc);
+ if (isSupportedGraphics2D(g2d)) {
+ new TextRunPainter().paintTextRuns(node.getTextRuns(), g2d, node.getLocation());
}
+ proxyTextPainter.paint(node, g2d);
}
/**
@@ -109,190 +95,99 @@ public abstract class AbstractFOPTextPainter implements TextPainter {
*/
protected abstract boolean isSupportedGraphics2D(Graphics2D g2d);
- private boolean hasUnsupportedAttributes(TextNode node) {
- Iterator iter = node.getTextRuns().iterator();
- while (iter.hasNext()) {
- StrokingTextPainter.TextRun
- run = (StrokingTextPainter.TextRun)iter.next();
- AttributedCharacterIterator aci = run.getACI();
- boolean hasUnsupported = hasUnsupportedAttributes(aci);
- if (hasUnsupported) {
- return true;
- }
- }
- return false;
- }
-
- private boolean hasUnsupportedAttributes(AttributedCharacterIterator aci) {
- boolean hasUnsupported = false;
-
- Font font = getFont(aci);
- String text = getText(aci);
- if (hasUnsupportedGlyphs(text, font)) {
- log.trace("-> Unsupported glyphs found");
- hasUnsupported = true;
- }
+ private class TextRunPainter {
- TextPaintInfo tpi = (TextPaintInfo) aci.getAttribute(
- GVTAttributedCharacterIterator.TextAttribute.PAINT_INFO);
- if ((tpi != null)
- && ((tpi.strokeStroke != null && tpi.strokePaint != null)
- || (tpi.strikethroughStroke != null)
- || (tpi.underlineStroke != null)
- || (tpi.overlineStroke != null))) {
- log.trace("-> under/overlines etc. found");
- hasUnsupported = true;
- }
+ private Point2D currentLocation;
- //Alpha is not supported
- Paint foreground = (Paint) aci.getAttribute(TextAttribute.FOREGROUND);
- if (foreground instanceof Color) {
- Color col = (Color)foreground;
- if (col.getAlpha() != 255) {
- log.trace("-> transparency found");
- hasUnsupported = true;
+ public void paintTextRuns(Iterable<StrokingTextPainter.TextRun> textRuns, Graphics2D g2d,
+ Point2D nodeLocation) {
+ currentLocation = new Point2D.Double(nodeLocation.getX(), nodeLocation.getY());
+ for (StrokingTextPainter.TextRun run : textRuns) {
+ paintTextRun(run, g2d);
}
}
- Object letSpace = aci.getAttribute(
- GVTAttributedCharacterIterator.TextAttribute.LETTER_SPACING);
- if (letSpace != null) {
- log.trace("-> letter spacing found");
- hasUnsupported = true;
- }
-
- Object wordSpace = aci.getAttribute(
- GVTAttributedCharacterIterator.TextAttribute.WORD_SPACING);
- if (wordSpace != null) {
- log.trace("-> word spacing found");
- hasUnsupported = true;
- }
-
- Object lengthAdjust = aci.getAttribute(
- GVTAttributedCharacterIterator.TextAttribute.LENGTH_ADJUST);
- if (lengthAdjust != null) {
- log.trace("-> length adjustments found");
- hasUnsupported = true;
- }
-
- Object writeMod = aci.getAttribute(
- GVTAttributedCharacterIterator.TextAttribute.WRITING_MODE);
- if (writeMod != null
- && !GVTAttributedCharacterIterator.TextAttribute.WRITING_MODE_LTR.equals(
- writeMod)) {
- log.trace("-> Unsupported writing modes found");
- hasUnsupported = true;
- }
-
- Object vertOr = aci.getAttribute(
- GVTAttributedCharacterIterator.TextAttribute.VERTICAL_ORIENTATION);
- if (GVTAttributedCharacterIterator.TextAttribute.ORIENTATION_ANGLE.equals(
- vertOr)) {
- log.trace("-> vertical orientation found");
- hasUnsupported = true;
- }
-
- Object rcDel = aci.getAttribute(
- GVTAttributedCharacterIterator.TextAttribute.TEXT_COMPOUND_DELIMITER);
- //Batik 1.6 returns null here which makes it impossible to determine whether this can
- //be painted or not, i.e. fall back to stroking. :-(
- if (rcDel != null && !(rcDel instanceof SVGOMTextElement)) {
- log.trace("-> spans found");
- hasUnsupported = true; //Filter spans
- }
-
- if (hasUnsupported) {
- log.trace("Unsupported attributes found in ACI, using StrokingTextPainter");
- }
- return hasUnsupported;
- }
-
- /**
- * Paint a list of text runs on the Graphics2D at a given location.
- * @param textRuns the list of text runs
- * @param g2d the Graphics2D to paint to
- * @param loc the current location of the "cursor"
- */
- protected void paintTextRuns(List textRuns, Graphics2D g2d, Point2D loc) {
- Point2D currentloc = loc;
- Iterator i = textRuns.iterator();
- while (i.hasNext()) {
- StrokingTextPainter.TextRun
- run = (StrokingTextPainter.TextRun)i.next();
- currentloc = paintTextRun(run, g2d, currentloc);
- }
- }
-
- /**
- * Paint a single text run on the Graphics2D at a given location.
- * @param run the text run to paint
- * @param g2d the Graphics2D to paint to
- * @param loc the current location of the "cursor"
- * @return the new location of the "cursor" after painting the text run
- */
- protected Point2D paintTextRun(StrokingTextPainter.TextRun run, Graphics2D g2d, Point2D loc) {
- AttributedCharacterIterator aci = run.getACI();
- aci.first();
-
- updateLocationFromACI(aci, loc);
- AffineTransform at = g2d.getTransform();
- loc = at.transform(loc, null);
-
- // font
- Font font = getFont(aci);
- if (font != null) {
- nativeTextHandler.setOverrideFont(font);
- }
-
- // color
- TextPaintInfo tpi = (TextPaintInfo) aci.getAttribute(
- GVTAttributedCharacterIterator.TextAttribute.PAINT_INFO);
- if (tpi == null) {
- return loc;
- }
- Paint foreground = tpi.fillPaint;
- if (foreground instanceof Color) {
- Color col = (Color)foreground;
- g2d.setColor(col);
- }
- g2d.setPaint(foreground);
-
- // text anchor
- TextNode.Anchor anchor = (TextNode.Anchor)aci.getAttribute(
- GVTAttributedCharacterIterator.TextAttribute.ANCHOR_TYPE);
-
- // text
- String txt = getText(aci);
- float advance = getStringWidth(txt, font);
- float tx = 0;
- if (anchor != null) {
- switch (anchor.getType()) {
- case TextNode.Anchor.ANCHOR_MIDDLE:
- tx = -advance / 2;
- break;
- case TextNode.Anchor.ANCHOR_END:
- tx = -advance;
- break;
- default: //nop
+ private void paintTextRun(StrokingTextPainter.TextRun run, Graphics2D g2d) {
+ AttributedCharacterIterator aci = run.getACI();
+ aci.first();
+ updateLocationFromACI(aci, currentLocation);
+ // font
+ Font font = getFont(aci);
+ if (font != null) {
+ nativeTextHandler.setOverrideFont(font);
}
- }
-
- // draw string
- double x = loc.getX();
- double y = loc.getY();
- try {
+ // color
+ TextPaintInfo tpi = (TextPaintInfo) aci.getAttribute(
+ GVTAttributedCharacterIterator.TextAttribute.PAINT_INFO);
+ if (tpi == null) {
+ return;
+ }
+ Paint foreground = tpi.fillPaint;
+ if (foreground instanceof Color) {
+ Color col = (Color) foreground;
+ g2d.setColor(col);
+ }
+ g2d.setPaint(foreground);
+ // text anchor
+ TextNode.Anchor anchor = (TextNode.Anchor) aci.getAttribute(
+ GVTAttributedCharacterIterator.TextAttribute.ANCHOR_TYPE);
+ // text
+ String txt = getText(aci);
+ double advance = font == null ? run.getLayout().getAdvance2D().getX() : getStringWidth(txt, font);
+ double tx = 0;
+ if (anchor != null) {
+ switch (anchor.getType()) {
+ case TextNode.Anchor.ANCHOR_MIDDLE:
+ tx = -advance / 2;
+ break;
+ case TextNode.Anchor.ANCHOR_END:
+ tx = -advance;
+ break;
+ default: //nop
+ }
+ }
+ // draw string
+ Point2D outputLocation = g2d.getTransform().transform(currentLocation, null);
+ double x = outputLocation.getX();
+ double y = outputLocation.getY();
try {
- nativeTextHandler.drawString(g2d, txt, (float)x + tx, (float)y);
- } catch (IOException ioe) {
- if (g2d instanceof AFPGraphics2D) {
- ((AFPGraphics2D)g2d).handleIOException(ioe);
+ try {
+ //TODO draw underline and overline if set
+ nativeTextHandler.drawString(g2d, txt, (float) (x + tx), (float) y);
+ //TODO draw strikethrough if set
+ } catch (IOException ioe) {
+ if (g2d instanceof AFPGraphics2D) {
+ ((AFPGraphics2D) g2d).handleIOException(ioe);
+ }
}
+ } finally {
+ nativeTextHandler.setOverrideFont(null);
+ }
+ currentLocation.setLocation(currentLocation.getX() + advance, currentLocation.getY());
+ }
+ private void updateLocationFromACI(AttributedCharacterIterator aci, Point2D loc) {
+ //Adjust position of span
+ Float xpos = (Float) aci.getAttribute(
+ GVTAttributedCharacterIterator.TextAttribute.X);
+ Float ypos = (Float) aci.getAttribute(
+ GVTAttributedCharacterIterator.TextAttribute.Y);
+ Float dxpos = (Float) aci.getAttribute(
+ GVTAttributedCharacterIterator.TextAttribute.DX);
+ Float dypos = (Float) aci.getAttribute(
+ GVTAttributedCharacterIterator.TextAttribute.DY);
+ if (xpos != null) {
+ loc.setLocation(xpos.doubleValue(), loc.getY());
+ }
+ if (ypos != null) {
+ loc.setLocation(loc.getX(), ypos.doubleValue());
+ }
+ if (dxpos != null) {
+ loc.setLocation(loc.getX() + dxpos.doubleValue(), loc.getY());
+ }
+ if (dypos != null) {
+ loc.setLocation(loc.getX(), loc.getY() + dypos.doubleValue());
}
- } finally {
- nativeTextHandler.setOverrideFont(null);
}
- loc.setLocation(loc.getX() + advance, loc.getY());
- return loc;
}
/**
@@ -305,36 +200,9 @@ public abstract class AbstractFOPTextPainter implements TextPainter {
for (char c = aci.first(); c != CharacterIterator.DONE; c = aci.next()) {
sb.append(c);
}
- aci.first();
return sb.toString();
}
- private void updateLocationFromACI(
- AttributedCharacterIterator aci,
- Point2D loc) {
- //Adjust position of span
- Float xpos = (Float)aci.getAttribute(
- GVTAttributedCharacterIterator.TextAttribute.X);
- Float ypos = (Float)aci.getAttribute(
- GVTAttributedCharacterIterator.TextAttribute.Y);
- Float dxpos = (Float)aci.getAttribute(
- GVTAttributedCharacterIterator.TextAttribute.DX);
- Float dypos = (Float)aci.getAttribute(
- GVTAttributedCharacterIterator.TextAttribute.DY);
- if (xpos != null) {
- loc.setLocation(xpos.doubleValue(), loc.getY());
- }
- if (ypos != null) {
- loc.setLocation(loc.getX(), ypos.doubleValue());
- }
- if (dxpos != null) {
- loc.setLocation(loc.getX() + dxpos.doubleValue(), loc.getY());
- }
- if (dypos != null) {
- loc.setLocation(loc.getX(), loc.getY() + dypos.doubleValue());
- }
- }
-
private Font getFont(AttributedCharacterIterator aci) {
Font[] fonts = ACIUtils.findFontsForBatikACI(aci, nativeTextHandler.getFontInfo());
return fonts == null ? null : fonts[0];
@@ -360,21 +228,6 @@ public abstract class AbstractFOPTextPainter implements TextPainter {
return wordWidth / 1000f;
}
- private boolean hasUnsupportedGlyphs(String str, Font font) {
- if (font == null) {
- return true;
- }
- for (int i = 0; i < str.length(); i++) {
- char c = str.charAt(i);
- if (!((c == ' ') || (c == '\n') || (c == '\r') || (c == '\t'))) {
- if (!font.hasChar(c)) {
- return true;
- }
- }
- }
- return false;
- }
-
/**
* Get the outline shape of the text characters.
* This uses the StrokingTextPainter to get the outline
@@ -384,7 +237,7 @@ public abstract class AbstractFOPTextPainter implements TextPainter {
* @return the outline shape of the text characters
*/
public Shape getOutline(TextNode node) {
- return PROXY_PAINTER.getOutline(node);
+ return proxyTextPainter.getOutline(node);
}
/**
@@ -399,7 +252,7 @@ public abstract class AbstractFOPTextPainter implements TextPainter {
/* (todo) getBounds2D() is too slow
* because it uses the StrokingTextPainter. We should implement this
* method ourselves. */
- return PROXY_PAINTER.getBounds2D(node);
+ return proxyTextPainter.getBounds2D(node);
}
/**
@@ -411,7 +264,7 @@ public abstract class AbstractFOPTextPainter implements TextPainter {
* @return the bounds of the text
*/
public Rectangle2D getGeometryBounds(TextNode node) {
- return PROXY_PAINTER.getGeometryBounds(node);
+ return proxyTextPainter.getGeometryBounds(node);
}
// Methods that have no purpose for PS
diff --git a/src/java/org/apache/fop/svg/AbstractFOPTranscoder.java b/src/java/org/apache/fop/svg/AbstractFOPTranscoder.java
index e04bf0d35..9b1606953 100644
--- a/src/java/org/apache/fop/svg/AbstractFOPTranscoder.java
+++ b/src/java/org/apache/fop/svg/AbstractFOPTranscoder.java
@@ -36,9 +36,9 @@ import org.apache.avalon.framework.configuration.DefaultConfiguration;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.impl.SimpleLog;
-import org.apache.batik.bridge.UserAgent;
import org.apache.batik.dom.svg.SVGDOMImplementation;
import org.apache.batik.dom.util.DocumentFactory;
+import org.apache.batik.gvt.font.FontFamilyResolver;
import org.apache.batik.transcoder.ErrorHandler;
import org.apache.batik.transcoder.SVGAbstractTranscoder;
import org.apache.batik.transcoder.TranscoderException;
@@ -56,6 +56,8 @@ import org.apache.xmlgraphics.image.loader.ImageSessionContext;
import org.apache.xmlgraphics.image.loader.impl.AbstractImageSessionContext;
import org.apache.xmlgraphics.util.UnitConv;
+import org.apache.fop.svg.font.FOPFontFamilyResolver;
+
/**
* This is the common base class of all of FOP's transcoders.
*/
@@ -86,11 +88,6 @@ public abstract class AbstractFOPTranscoder extends SVGAbstractTranscoder implem
/** The value to turn off text stroking. */
public static final Boolean VALUE_FORMAT_OFF = Boolean.FALSE;
- /**
- * The user agent dedicated to this Transcoder.
- */
- protected UserAgent userAgent = createUserAgent();
-
private Log logger;
private EntityResolver resolver;
private Configuration cfg = null;
@@ -113,7 +110,7 @@ public abstract class AbstractFOPTranscoder extends SVGAbstractTranscoder implem
* this method if you need non-default behaviour.
* @return UserAgent the newly created user agent
*/
- protected UserAgent createUserAgent() {
+ protected FOPTranscoderUserAgent createUserAgent() {
return new FOPTranscoderUserAgent();
}
@@ -331,6 +328,8 @@ public abstract class AbstractFOPTranscoder extends SVGAbstractTranscoder implem
*/
protected class FOPTranscoderUserAgent extends SVGAbstractTranscoderUserAgent {
+ private FOPFontFamilyResolver fontFamilyResolver;
+
/**
* Displays the specified error message using the {@link ErrorHandler}.
* @param message the message to display
@@ -386,6 +385,15 @@ public abstract class AbstractFOPTranscoder extends SVGAbstractTranscoder implem
return "print";
}
+ public void setFontFamilyResolver(FOPFontFamilyResolver resolver) {
+ fontFamilyResolver = resolver;
+ }
+
+ @Override
+ public FontFamilyResolver getFontFamilyResolver() {
+ return fontFamilyResolver;
+ }
+
}
}
diff --git a/src/java/org/apache/fop/svg/NativeTextPainter.java b/src/java/org/apache/fop/svg/NativeTextPainter.java
index 4513e0101..200f6558b 100644
--- a/src/java/org/apache/fop/svg/NativeTextPainter.java
+++ b/src/java/org/apache/fop/svg/NativeTextPainter.java
@@ -19,7 +19,14 @@
package org.apache.fop.svg;
+import java.awt.BasicStroke;
+import java.awt.Color;
import java.awt.Graphics2D;
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.Point2D;
import java.io.IOException;
import java.text.AttributedCharacterIterator;
import java.util.List;
@@ -27,11 +34,17 @@ import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+import org.apache.batik.bridge.SVGGVTFont;
+import org.apache.batik.gvt.font.FontFamilyResolver;
+import org.apache.batik.gvt.font.GVTGlyphVector;
import org.apache.batik.gvt.renderer.StrokingTextPainter;
+import org.apache.batik.gvt.text.TextPaintInfo;
import org.apache.batik.gvt.text.TextSpanLayout;
import org.apache.fop.fonts.Font;
import org.apache.fop.fonts.FontInfo;
+import org.apache.fop.svg.font.FOPFontFamilyResolverImpl;
+import org.apache.fop.svg.font.FOPGVTFont;
import org.apache.fop.util.CharUtilities;
/**
@@ -41,17 +54,26 @@ import org.apache.fop.util.CharUtilities;
public abstract class NativeTextPainter extends StrokingTextPainter {
/** the logger for this class */
- protected Log log = LogFactory.getLog(NativeTextPainter.class);
+ protected static final Log log = LogFactory.getLog(NativeTextPainter.class);
+
+ private static final boolean DEBUG = false;
/** the font collection */
protected final FontInfo fontInfo;
+ protected final FontFamilyResolver fontFamilyResolver;
+
+ protected Font font;
+
+ protected TextPaintInfo tpi;
+
/**
* Creates a new instance.
* @param fontInfo the font collection
*/
public NativeTextPainter(FontInfo fontInfo) {
this.fontInfo = fontInfo;
+ this.fontFamilyResolver = new FOPFontFamilyResolverImpl(fontInfo);
}
/**
@@ -68,11 +90,93 @@ public abstract class NativeTextPainter extends StrokingTextPainter {
* @param g2d the target Graphics2D instance
* @throws IOException if an I/O error occurs while rendering the text
*/
- protected abstract void paintTextRun(TextRun textRun, Graphics2D g2d) throws IOException;
+ protected final void paintTextRun(TextRun textRun, Graphics2D g2d) throws IOException {
+ AttributedCharacterIterator runaci = textRun.getACI();
+ runaci.first();
+
+ tpi = (TextPaintInfo) runaci.getAttribute(PAINT_INFO);
+ if (tpi == null || !tpi.visible) {
+ return;
+ }
+ if (tpi.composite != null) {
+ g2d.setComposite(tpi.composite);
+ }
+
+ //------------------------------------
+ TextSpanLayout layout = textRun.getLayout();
+ logTextRun(runaci, layout);
+ runaci.first(); //Reset ACI
+
+ GeneralPath debugShapes = null;
+ if (DEBUG) {
+ debugShapes = new GeneralPath();
+ }
+
+ preparePainting(g2d);
+
+ GVTGlyphVector gv = layout.getGlyphVector();
+ if (!(gv.getFont() instanceof FOPGVTFont)) {
+ assert gv.getFont() == null || gv.getFont() instanceof SVGGVTFont;
+ //Draw using Java2D when no native fonts are available
+ textRun.getLayout().draw(g2d);
+ return;
+ }
+ font = ((FOPGVTFont) gv.getFont()).getFont();
+
+ saveGraphicsState();
+ setInitialTransform(g2d.getTransform());
+ clip(g2d.getClip());
+ beginTextObject();
+
+ AffineTransform localTransform = new AffineTransform();
+ Point2D prevPos = null;
+ AffineTransform prevGlyphTransform = null;
+ for (int index = 0, c = gv.getNumGlyphs(); index < c; index++) {
+ if (!gv.isGlyphVisible(index)) {
+ continue;
+ }
+ Point2D glyphPos = gv.getGlyphPosition(index);
+
+ AffineTransform glyphTransform = gv.getGlyphTransform(index);
+ if (log.isTraceEnabled()) {
+ log.trace("pos " + glyphPos + ", transform " + glyphTransform);
+ }
+ if (DEBUG) {
+ Shape sh = gv.getGlyphLogicalBounds(index);
+ if (sh == null) {
+ sh = new Ellipse2D.Double(glyphPos.getX(), glyphPos.getY(), 2, 2);
+ }
+ debugShapes.append(sh, false);
+ }
+
+ //Exact position of the glyph
+ localTransform.setToIdentity();
+ localTransform.translate(glyphPos.getX(), glyphPos.getY());
+ if (glyphTransform != null) {
+ localTransform.concatenate(glyphTransform);
+ }
+ localTransform.scale(1, -1);
+
+ positionGlyph(prevPos, glyphPos, glyphTransform != null || prevGlyphTransform != null);
+ char glyph = (char) gv.getGlyphCode(index);
+ //Update last position
+ prevPos = glyphPos;
+ prevGlyphTransform = glyphTransform;
+
+ writeGlyph(glyph, localTransform);
+ }
+ endTextObject();
+ restoreGraphicsState();
+ if (DEBUG) {
+ //Paint debug shapes
+ g2d.setStroke(new BasicStroke(0));
+ g2d.setColor(Color.LIGHT_GRAY);
+ g2d.draw(debugShapes);
+ }
+ }
- /** {@inheritDoc} */
@Override
- protected void paintTextRuns(List textRuns, Graphics2D g2d) {
+ protected void paintTextRuns(@SuppressWarnings("rawtypes") List textRuns, Graphics2D g2d) {
if (log.isTraceEnabled()) {
log.trace("paintTextRuns: count = " + textRuns.size());
}
@@ -81,7 +185,7 @@ public abstract class NativeTextPainter extends StrokingTextPainter {
return;
}
for (int i = 0; i < textRuns.size(); i++) {
- TextRun textRun = (TextRun)textRuns.get(i);
+ TextRun textRun = (TextRun) textRuns.get(i);
try {
paintTextRun(textRun, g2d);
} catch (IOException ioe) {
@@ -92,16 +196,6 @@ public abstract class NativeTextPainter extends StrokingTextPainter {
}
/**
- * Finds an array of suitable fonts for a given AttributedCharacterIterator.
- * @param aci the character iterator
- * @return the array of fonts
- */
- protected Font[] findFonts(AttributedCharacterIterator aci) {
- Font[] fonts = ACIUtils.findFontsForBatikACI(aci, fontInfo);
- return fonts;
- }
-
- /**
* Collects all characters from an {@link AttributedCharacterIterator}.
* @param runaci the character iterator
* @return the characters
@@ -115,6 +209,25 @@ public abstract class NativeTextPainter extends StrokingTextPainter {
return chars;
}
+ protected abstract void preparePainting(Graphics2D g2d);
+
+ protected abstract void saveGraphicsState() throws IOException;
+
+ protected abstract void restoreGraphicsState() throws IOException;
+
+ protected abstract void setInitialTransform(AffineTransform transform) throws IOException;
+
+ protected abstract void clip(Shape clip) throws IOException;
+
+ protected abstract void beginTextObject() throws IOException;
+
+ protected abstract void endTextObject() throws IOException;
+
+ protected abstract void positionGlyph(Point2D prevPos, Point2D glyphPos, boolean reposition);
+
+ protected abstract void writeGlyph(char glyph, AffineTransform transform) throws IOException;
+
+
/**
* @param runaci an attributed character iterator
* @param layout a text span layout
@@ -155,5 +268,9 @@ public abstract class NativeTextPainter extends StrokingTextPainter {
}
}
+ @Override
+ protected FontFamilyResolver getFontFamilyResolver() {
+ return this.fontFamilyResolver;
+ }
}
diff --git a/src/java/org/apache/fop/svg/PDFTextPainter.java b/src/java/org/apache/fop/svg/PDFTextPainter.java
index ef376663f..c5fa9f04e 100644
--- a/src/java/org/apache/fop/svg/PDFTextPainter.java
+++ b/src/java/org/apache/fop/svg/PDFTextPainter.java
@@ -19,25 +19,17 @@
package org.apache.fop.svg;
-import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.AffineTransform;
-import java.awt.geom.Ellipse2D;
-import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
-import java.text.AttributedCharacterIterator;
-import org.apache.batik.gvt.font.GVTGlyphVector;
import org.apache.batik.gvt.text.TextPaintInfo;
-import org.apache.batik.gvt.text.TextSpanLayout;
-import org.apache.fop.fonts.Font;
import org.apache.fop.fonts.FontInfo;
-import org.apache.fop.util.CharUtilities;
/**
* Renders the attributed character iterator of a {@link org.apache.batik.gvt.TextNode}.
@@ -51,11 +43,19 @@ import org.apache.fop.util.CharUtilities;
*/
class PDFTextPainter extends NativeTextPainter {
- private static final boolean DEBUG = false;
+ private PDFGraphics2D pdf;
+
+ private PDFTextUtil textUtil;
+
+ private double prevVisibleGlyphWidth;
+
+ private boolean repositionNextGlyph;
/**
* Create a new PDF text painter with the given font information.
+ *
* @param fi the font info
+ * @param fontFamilyResolver the Font Family Resolver
*/
public PDFTextPainter(FontInfo fi) {
super(fi);
@@ -67,29 +67,29 @@ class PDFTextPainter extends NativeTextPainter {
return g2d instanceof PDFGraphics2D;
}
- /** {@inheritDoc} */
@Override
- protected void paintTextRun(TextRun textRun, Graphics2D g2d) {
- AttributedCharacterIterator runaci = textRun.getACI();
- runaci.first();
+ protected void preparePainting(Graphics2D g2d) {
+ pdf = (PDFGraphics2D) g2d;
+ }
- TextPaintInfo tpi = (TextPaintInfo)runaci.getAttribute(PAINT_INFO);
- if (tpi == null || !tpi.visible) {
- return;
- }
- if ((tpi != null) && (tpi.composite != null)) {
- g2d.setComposite(tpi.composite);
- }
+ @Override
+ protected void saveGraphicsState() {
+ pdf.saveGraphicsState();
+ }
- //------------------------------------
- TextSpanLayout layout = textRun.getLayout();
- logTextRun(runaci, layout);
- CharSequence chars = collectCharacters(runaci);
- runaci.first(); //Reset ACI
+ @Override
+ protected void restoreGraphicsState() {
+ pdf.restoreGraphicsState();
+ }
- final PDFGraphics2D pdf = (PDFGraphics2D)g2d;
+ @Override
+ protected void setInitialTransform(AffineTransform transform) {
+ createTextUtil();
+ textUtil.concatMatrix(transform);
+ }
- PDFTextUtil textUtil = new PDFTextUtil(pdf.fontInfo) {
+ private void createTextUtil() {
+ textUtil = new PDFTextUtil(pdf.fontInfo) {
protected void write(String code) {
pdf.currentStream.write(code);
}
@@ -97,142 +97,39 @@ class PDFTextPainter extends NativeTextPainter {
pdf.currentStream.append(code);
}
};
+ }
- if (DEBUG) {
- log.debug("Text: " + chars);
- pdf.currentStream.write("%Text: " + chars + "\n");
- }
-
- GeneralPath debugShapes = null;
- if (DEBUG) {
- debugShapes = new GeneralPath();
- }
-
- Font[] fonts = findFonts(runaci);
- if (fonts == null || fonts.length == 0) {
- //Draw using Java2D when no native fonts are available
- textRun.getLayout().draw(g2d);
- return;
- }
-
- pdf.saveGraphicsState();
- textUtil.concatMatrix(g2d.getTransform());
- Shape imclip = g2d.getClip();
- pdf.writeClip(imclip);
-
- applyColorAndPaint(tpi, pdf);
+ @Override
+ protected void clip(Shape clip) {
+ pdf.writeClip(clip);
+ }
+ @Override
+ protected void beginTextObject() {
+ applyColorAndPaint(tpi);
textUtil.beginTextObject();
- textUtil.setFonts(fonts);
- boolean stroke = (tpi.strokePaint != null)
- && (tpi.strokeStroke != null);
+ boolean stroke = (tpi.strokePaint != null) && (tpi.strokeStroke != null);
textUtil.setTextRenderingMode(tpi.fillPaint != null, stroke, false);
+ }
- AffineTransform localTransform = new AffineTransform();
- Point2D prevPos = null;
- double prevVisibleCharWidth = 0.0;
- GVTGlyphVector gv = layout.getGlyphVector();
- for (int index = 0, c = gv.getNumGlyphs(); index < c; index++) {
- char ch = chars.charAt(index);
- boolean visibleChar = gv.isGlyphVisible(index)
- || (CharUtilities.isAnySpace(ch) && !CharUtilities.isZeroWidthSpace(ch));
- logCharacter(ch, layout, index, visibleChar);
- if (!visibleChar) {
- continue;
- }
- Point2D glyphPos = gv.getGlyphPosition(index);
-
- AffineTransform glyphTransform = gv.getGlyphTransform(index);
- //TODO Glyph transforms could be refined so not every char has to be painted
- //with its own TJ command (stretch/squeeze case could be optimized)
- if (log.isTraceEnabled()) {
- log.trace("pos " + glyphPos + ", transform " + glyphTransform);
- }
- if (DEBUG) {
- Shape sh = gv.getGlyphLogicalBounds(index);
- if (sh == null) {
- sh = new Ellipse2D.Double(glyphPos.getX(), glyphPos.getY(), 2, 2);
- }
- debugShapes.append(sh, false);
- }
-
- //Exact position of the glyph
- localTransform.setToIdentity();
- localTransform.translate(glyphPos.getX(), glyphPos.getY());
- if (glyphTransform != null) {
- localTransform.concatenate(glyphTransform);
- }
- localTransform.scale(1, -1);
-
- boolean yPosChanged = (prevPos == null
- || prevPos.getY() != glyphPos.getY()
- || glyphTransform != null);
- if (yPosChanged) {
- if (index > 0) {
- textUtil.writeTJ();
- textUtil.writeTextMatrix(localTransform);
- }
- } else {
- double xdiff = glyphPos.getX() - prevPos.getX();
- //Width of previous character
- Font font = textUtil.getCurrentFont();
- double cw = prevVisibleCharWidth;
- double effxdiff = (1000 * xdiff) - cw;
- if (effxdiff != 0) {
- double adjust = (-effxdiff / font.getFontSize());
- textUtil.adjustGlyphTJ(adjust * 1000);
- }
- if (log.isTraceEnabled()) {
- log.trace("==> x diff: " + xdiff + ", " + effxdiff
- + ", charWidth: " + cw);
- }
- }
- Font f = textUtil.selectFontForChar(ch);
- char paintChar = (CharUtilities.isAnySpace(ch) ? ' ' : ch);
- char mappedChar = f.mapChar(paintChar);
- boolean encodingChanging = false; // used for single byte
- if (!textUtil.isMultiByteFont(f.getFontName())) {
- int encoding = mappedChar / 256;
- mappedChar = (char) (mappedChar % 256);
- if (textUtil.getCurrentEncoding() != encoding) {
- textUtil.setCurrentEncoding(encoding);
- encodingChanging = true;
- }
- }
- if (f != textUtil.getCurrentFont() || encodingChanging) {
- textUtil.writeTJ();
- textUtil.setCurrentFont(f);
- textUtil.writeTf(f);
- textUtil.writeTextMatrix(localTransform);
- }
- textUtil.writeTJMappedChar(mappedChar);
-
- //Update last position
- prevPos = glyphPos;
- prevVisibleCharWidth = textUtil.getCurrentFont().getCharWidth(chars.charAt(index));
- }
+ @Override
+ protected void endTextObject() {
textUtil.writeTJ();
textUtil.endTextObject();
- pdf.restoreGraphicsState();
- if (DEBUG) {
- g2d.setStroke(new BasicStroke(0));
- g2d.setColor(Color.LIGHT_GRAY);
- g2d.draw(debugShapes);
- }
}
- private void applyColorAndPaint(TextPaintInfo tpi, PDFGraphics2D pdf) {
+ private void applyColorAndPaint(TextPaintInfo tpi) {
Paint fillPaint = tpi.fillPaint;
Paint strokePaint = tpi.strokePaint;
Stroke stroke = tpi.strokeStroke;
int fillAlpha = PDFGraphics2D.OPAQUE;
if (fillPaint instanceof Color) {
- Color col = (Color)fillPaint;
+ Color col = (Color) fillPaint;
pdf.applyColor(col, true);
fillAlpha = col.getAlpha();
}
if (strokePaint instanceof Color) {
- Color col = (Color)strokePaint;
+ Color col = (Color) strokePaint;
pdf.applyColor(col, false);
}
pdf.applyPaint(fillPaint, true);
@@ -243,4 +140,46 @@ class PDFTextPainter extends NativeTextPainter {
pdf.applyAlpha(fillAlpha, PDFGraphics2D.OPAQUE);
}
+ @Override
+ protected void positionGlyph(Point2D prevPos, Point2D glyphPos, boolean reposition) {
+ // TODO Glyph transforms could be refined so not every char has to be painted
+ // with its own TJ command (stretch/squeeze case could be optimized)
+ repositionNextGlyph = (prevPos == null
+ || prevPos.getY() != glyphPos.getY()
+ || reposition);
+ if (!repositionNextGlyph) {
+ double xdiff = glyphPos.getX() - prevPos.getX();
+ //Width of previous character
+ double cw = prevVisibleGlyphWidth;
+ double effxdiff = (1000 * xdiff) - cw;
+ if (effxdiff != 0) {
+ double adjust = (-effxdiff / font.getFontSize());
+ textUtil.adjustGlyphTJ(adjust * 1000);
+ }
+ }
+ }
+
+ @Override
+ protected void writeGlyph(char glyph, AffineTransform transform) {
+ prevVisibleGlyphWidth = font.getWidth(glyph);
+ boolean encodingChanging = false; // used for single byte
+ if (!textUtil.isMultiByteFont(font.getFontName())) {
+ int encoding = glyph / 256;
+ glyph = (char) (glyph % 256);
+ if (textUtil.getCurrentEncoding() != encoding) {
+ textUtil.setCurrentEncoding(encoding);
+ encodingChanging = true;
+ }
+ }
+ if (repositionNextGlyph || encodingChanging) {
+ textUtil.writeTJ();
+ if (font != textUtil.getCurrentFont() || encodingChanging) {
+ textUtil.setCurrentFont(font);
+ textUtil.writeTf(font);
+ }
+ textUtil.writeTextMatrix(transform);
+ }
+ textUtil.writeTJMappedChar(glyph);
+ }
+
}
diff --git a/src/java/org/apache/fop/svg/PDFTextUtil.java b/src/java/org/apache/fop/svg/PDFTextUtil.java
index 8325dce1f..d525ecefc 100644
--- a/src/java/org/apache/fop/svg/PDFTextUtil.java
+++ b/src/java/org/apache/fop/svg/PDFTextUtil.java
@@ -30,7 +30,6 @@ import org.apache.fop.fonts.Typeface;
public abstract class PDFTextUtil extends org.apache.fop.pdf.PDFTextUtil {
private FontInfo fontInfo;
- private Font[] fonts;
private Font font;
private int encoding;
@@ -50,23 +49,6 @@ public abstract class PDFTextUtil extends org.apache.fop.pdf.PDFTextUtil {
}
/**
- * Sets the current fonts for the text object. For every character, the suitable font will
- * be selected.
- * @param fonts the new fonts
- */
- public void setFonts(Font[] fonts) {
- this.fonts = fonts;
- }
-
- /**
- * Sets the current font for the text object.
- * @param font the new font
- */
- public void setFont(Font font) {
- setFonts(new Font[] {font});
- }
-
- /**
* Returns the current font in use.
* @return the current font or null if no font is currently active.
*/
@@ -123,27 +105,4 @@ public abstract class PDFTextUtil extends org.apache.fop.pdf.PDFTextUtil {
}
}
- /**
- * Selects a font from the font list suitable to display the given character.
- * @param ch the character
- * @return the recommended Font to use
- */
- public Font selectFontForChar(char ch) {
- for (int i = 0, c = fonts.length; i < c; i++) {
- if (fonts[i].hasChar(ch)) {
- return fonts[i];
- }
- }
- return fonts[0]; //TODO Maybe fall back to painting with shapes
- }
-
- /**
- * Writes a char to the "TJ-Buffer".
- * @param ch the unmapped character
- */
- public void writeTJChar(char ch) {
- char mappedChar = font.mapChar(ch);
- writeTJMappedChar(mappedChar);
- }
-
}
diff --git a/src/java/org/apache/fop/svg/PDFTranscoder.java b/src/java/org/apache/fop/svg/PDFTranscoder.java
index 9f0345657..1a3f154cb 100644
--- a/src/java/org/apache/fop/svg/PDFTranscoder.java
+++ b/src/java/org/apache/fop/svg/PDFTranscoder.java
@@ -31,7 +31,6 @@ import org.apache.avalon.framework.configuration.Configuration;
import org.apache.batik.bridge.BridgeContext;
import org.apache.batik.bridge.UnitProcessor;
-import org.apache.batik.bridge.UserAgent;
import org.apache.batik.ext.awt.RenderingHintsKeyExt;
import org.apache.batik.transcoder.TranscoderException;
import org.apache.batik.transcoder.TranscoderOutput;
@@ -39,6 +38,7 @@ import org.apache.batik.transcoder.image.ImageTranscoder;
import org.apache.fop.Version;
import org.apache.fop.fonts.FontInfo;
+import org.apache.fop.svg.font.FOPFontFamilyResolverImpl;
/**
* <p>This class enables to transcode an input to a PDF document.</p>
@@ -88,7 +88,7 @@ public class PDFTranscoder extends AbstractFOPTranscoder {
/**
* {@inheritDoc}
*/
- protected UserAgent createUserAgent() {
+ protected FOPTranscoderUserAgent createUserAgent() {
return new AbstractFOPTranscoder.FOPTranscoderUserAgent() {
// The PDF stuff wants everything at 72dpi
public float getPixelUnitToMillimeter() {
@@ -131,6 +131,8 @@ public class PDFTranscoder extends AbstractFOPTranscoder {
} else {
graphics.setupDefaultFontInfo();
}
+ ((FOPTranscoderUserAgent) userAgent).setFontFamilyResolver(
+ new FOPFontFamilyResolverImpl(graphics.getFontInfo()));
} catch (Exception e) {
throw new TranscoderException(
"Error while setting up PDFDocumentGraphics2D", e);
diff --git a/src/java/org/apache/fop/svg/SVGUserAgent.java b/src/java/org/apache/fop/svg/SVGUserAgent.java
index d43552289..a265b4fef 100644
--- a/src/java/org/apache/fop/svg/SVGUserAgent.java
+++ b/src/java/org/apache/fop/svg/SVGUserAgent.java
@@ -21,6 +21,8 @@ package org.apache.fop.svg;
import java.awt.geom.AffineTransform;
+import org.apache.batik.gvt.font.FontFamilyResolver;
+
import org.apache.fop.apps.FOUserAgent;
/**
@@ -34,10 +36,11 @@ public class SVGUserAgent extends SimpleSVGUserAgent {
/**
* Creates a new SVGUserAgent.
* @param foUserAgent the FO user agent to associate with this SVG user agent
+ * @param fontFamilyResolver the font family resolver
* @param at the current transform
*/
- public SVGUserAgent(FOUserAgent foUserAgent, AffineTransform at) {
- super(foUserAgent.getSourcePixelUnitToMillimeter(), at);
+ public SVGUserAgent(FOUserAgent foUserAgent, FontFamilyResolver fontFamilyResolver, AffineTransform at) {
+ super(foUserAgent.getSourcePixelUnitToMillimeter(), at, fontFamilyResolver);
this.eventProducer = SVGEventProducer.Provider.get(foUserAgent.getEventBroadcaster());
}
@@ -45,8 +48,8 @@ public class SVGUserAgent extends SimpleSVGUserAgent {
* Creates a new SVGUserAgent.
* @param foUserAgent the FO user agent to associate with this SVG user agent
*/
- public SVGUserAgent(FOUserAgent foUserAgent) {
- this(foUserAgent, new AffineTransform());
+ public SVGUserAgent(FOUserAgent foUserAgent, FontFamilyResolver fontFamilyResolver) {
+ this(foUserAgent, fontFamilyResolver, new AffineTransform());
}
/**
diff --git a/src/java/org/apache/fop/svg/SimpleSVGUserAgent.java b/src/java/org/apache/fop/svg/SimpleSVGUserAgent.java
index 2b27945a4..42a18b17c 100644
--- a/src/java/org/apache/fop/svg/SimpleSVGUserAgent.java
+++ b/src/java/org/apache/fop/svg/SimpleSVGUserAgent.java
@@ -26,6 +26,7 @@ import java.awt.geom.Dimension2D;
import javax.xml.parsers.SAXParserFactory;
import org.apache.batik.bridge.UserAgentAdapter;
+import org.apache.batik.gvt.font.FontFamilyResolver;
/**
* A simple SVG user agent.
@@ -35,14 +36,18 @@ import org.apache.batik.bridge.UserAgentAdapter;
public class SimpleSVGUserAgent extends UserAgentAdapter {
private AffineTransform currentTransform = null;
+
private float pixelUnitToMillimeter = 0.0f;
+ private final FontFamilyResolver fontFamilyResolver;
+
/**
* Creates a new user agent.
* @param pixelUnitToMM the pixel to millimeter conversion factor currently in effect
* @param at the current transform
*/
- public SimpleSVGUserAgent(float pixelUnitToMM, AffineTransform at) {
+ public SimpleSVGUserAgent(float pixelUnitToMM, AffineTransform at, FontFamilyResolver fontFamilyResolver) {
+ this.fontFamilyResolver = fontFamilyResolver;
pixelUnitToMillimeter = pixelUnitToMM;
currentTransform = at;
}
@@ -122,5 +127,10 @@ public class SimpleSVGUserAgent extends UserAgentAdapter {
return new Dimension(100, 100);
}
+ @Override
+ public FontFamilyResolver getFontFamilyResolver() {
+ return fontFamilyResolver;
+ }
+
}
diff --git a/src/java/org/apache/fop/svg/font/AggregatingFontFamilyResolver.java b/src/java/org/apache/fop/svg/font/AggregatingFontFamilyResolver.java
new file mode 100644
index 000000000..313a6a74b
--- /dev/null
+++ b/src/java/org/apache/fop/svg/font/AggregatingFontFamilyResolver.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.svg.font;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.batik.gvt.font.FontFamilyResolver;
+import org.apache.batik.gvt.font.GVTFontFamily;
+
+public class AggregatingFontFamilyResolver implements FontFamilyResolver {
+
+ private final List<FontFamilyResolver> resolvers;
+
+ public AggregatingFontFamilyResolver(FontFamilyResolver... resolvers) {
+ this.resolvers = Arrays.<FontFamilyResolver>asList(resolvers);
+ }
+
+ public String lookup(String familyName) {
+ for (FontFamilyResolver resolver : resolvers) {
+ String lookup = resolver.lookup(familyName);
+ if (lookup != null) {
+ return lookup;
+ }
+ }
+ return null;
+ }
+
+ public GVTFontFamily resolve(String familyName) {
+ for (FontFamilyResolver resolver : resolvers) {
+ GVTFontFamily family = resolver.resolve(familyName);
+ if (family != null) {
+ return family;
+ }
+ }
+ return null;
+ }
+
+ public GVTFontFamily getDefault() {
+ return resolve("any");
+ }
+
+ public GVTFontFamily getFamilyThatCanDisplay(char c) {
+ for (FontFamilyResolver resolver : resolvers) {
+ GVTFontFamily family = resolver.getFamilyThatCanDisplay(c);
+ if (family != null) {
+ return family;
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/src/java/org/apache/fop/svg/font/FOPFontFamilyResolver.java b/src/java/org/apache/fop/svg/font/FOPFontFamilyResolver.java
new file mode 100644
index 000000000..7af5f0b4f
--- /dev/null
+++ b/src/java/org/apache/fop/svg/font/FOPFontFamilyResolver.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.svg.font;
+
+import org.apache.batik.gvt.font.FontFamilyResolver;
+
+public interface FOPFontFamilyResolver extends FontFamilyResolver {
+
+ FOPGVTFontFamily resolve(String familyName);
+
+ FOPGVTFontFamily getDefault();
+
+ FOPGVTFontFamily getFamilyThatCanDisplay(char c);
+}
diff --git a/src/java/org/apache/fop/svg/font/FOPFontFamilyResolverImpl.java b/src/java/org/apache/fop/svg/font/FOPFontFamilyResolverImpl.java
new file mode 100644
index 000000000..a9a631691
--- /dev/null
+++ b/src/java/org/apache/fop/svg/font/FOPFontFamilyResolverImpl.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.svg.font;
+
+import java.util.Map;
+
+import org.apache.fop.fonts.Font;
+import org.apache.fop.fonts.FontInfo;
+import org.apache.fop.fonts.FontTriplet;
+import org.apache.fop.fonts.Typeface;
+
+public class FOPFontFamilyResolverImpl implements FOPFontFamilyResolver {
+
+ private final FontInfo fontInfo;
+
+ public FOPFontFamilyResolverImpl(FontInfo fontInfo) {
+ this.fontInfo = fontInfo;
+ }
+
+ public String lookup(String familyName) {
+ // TODO Auto-generated method stub
+ throw new UnsupportedOperationException();
+ }
+
+ public FOPGVTFontFamily resolve(String familyName) {
+ FOPGVTFontFamily gvtFontFamily = null;
+ FontTriplet triplet = fontInfo.fontLookup(familyName, Font.STYLE_NORMAL, Font.WEIGHT_NORMAL);
+ if (fontInfo.hasFont(familyName, Font.STYLE_NORMAL, Font.WEIGHT_NORMAL)) {
+ gvtFontFamily = new FOPGVTFontFamily(fontInfo, familyName, triplet);
+ }
+ return gvtFontFamily;
+ }
+
+ public FOPGVTFontFamily getDefault() {
+ return resolve("any");
+ }
+
+ public FOPGVTFontFamily getFamilyThatCanDisplay(char c) {
+ Map<String, Typeface> fonts = fontInfo.getFonts();
+ for (Typeface font : fonts.values()) {
+ if (font.hasChar(c)) {
+ String fontFamily = font.getFamilyNames().iterator().next();
+ return new FOPGVTFontFamily(fontInfo, fontFamily,
+ new FontTriplet(fontFamily, Font.STYLE_NORMAL, Font.WEIGHT_NORMAL));
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/src/java/org/apache/fop/svg/font/FOPGVTFont.java b/src/java/org/apache/fop/svg/font/FOPGVTFont.java
new file mode 100644
index 000000000..c55e2fa8b
--- /dev/null
+++ b/src/java/org/apache/fop/svg/font/FOPGVTFont.java
@@ -0,0 +1,159 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.svg.font;
+
+import java.awt.font.FontRenderContext;
+import java.text.CharacterIterator;
+import java.text.StringCharacterIterator;
+
+import org.apache.batik.gvt.font.GVTFont;
+import org.apache.batik.gvt.font.GVTFontFamily;
+import org.apache.batik.gvt.font.GVTGlyphVector;
+import org.apache.batik.gvt.font.GVTLineMetrics;
+
+import org.apache.fop.fonts.Font;
+import org.apache.fop.fonts.FontMetrics;
+
+public class FOPGVTFont implements GVTFont {
+
+ private final Font font;
+
+ private final GVTFontFamily fontFamily;
+
+ public FOPGVTFont(Font font, GVTFontFamily fontFamily) {
+ this.font = font;
+ this.fontFamily = fontFamily;
+ }
+
+ public Font getFont() {
+ return font;
+ }
+
+ public boolean canDisplay(char c) {
+ return font.hasChar(c);
+ }
+
+ public int canDisplayUpTo(char[] text, int start, int limit) {
+ for (int i = start; i < limit; i++) {
+ if (!canDisplay(text[i])) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+
+ public int canDisplayUpTo(CharacterIterator iter, int start, int limit) {
+ for (char c = iter.setIndex(start); iter.getIndex() < limit; c = iter.next()) {
+ if (!canDisplay(c)) {
+ return iter.getIndex();
+ }
+ }
+ return -1;
+ }
+
+ public int canDisplayUpTo(String str) {
+ for (int i = 0; i < str.length(); i++) {
+ if (!canDisplay(str.charAt(i))) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public GVTGlyphVector createGlyphVector(FontRenderContext frc, char[] chars) {
+ return createGlyphVector(frc, new String(chars));
+ }
+
+ public GVTGlyphVector createGlyphVector(FontRenderContext frc, CharacterIterator ci) {
+ // TODO Batik does manual glyph shaping for Arabic. Replace with complex scripts implementation
+ return new FOPGVTGlyphVector(this, ci, frc);
+ }
+
+ public GVTGlyphVector createGlyphVector(FontRenderContext frc,
+ int[] glyphCodes,
+ CharacterIterator ci) {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ public GVTGlyphVector createGlyphVector(FontRenderContext frc, String str) {
+ StringCharacterIterator sci = new StringCharacterIterator(str);
+ return createGlyphVector(frc, sci);
+ }
+
+ public FOPGVTFont deriveFont(float size) {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ public String getFamilyName() {
+ return fontFamily.getFamilyName();
+ }
+
+ public GVTLineMetrics getLineMetrics(char[] chars, int beginIndex, int limit, FontRenderContext frc) {
+ return getLineMetrics(limit - beginIndex);
+ }
+
+ GVTLineMetrics getLineMetrics(int numChars) {
+ numChars = numChars < 0 ? 0 : numChars;
+ FontMetrics metrics = font.getFontMetrics();
+ int size = font.getFontSize();
+ return new GVTLineMetrics(
+ metrics.getCapHeight(size) / 1000000f,
+ java.awt.Font.ROMAN_BASELINE, // Not actually used by Batik
+ null, // Not actually used by Batik
+ -metrics.getDescender(size) / 1000000f,
+ 0, // Not actually used by Batik
+ 0, // Not actually used by Batik
+ numChars,
+ -metrics.getStrikeoutPosition(size) / 1000000f,
+ metrics.getStrikeoutThickness(size) / 1000000f,
+ -metrics.getUnderlinePosition(size) / 1000000f,
+ metrics.getUnderlineThickness(size) / 1000000f,
+ -metrics.getCapHeight(size) / 1000000f, // Because this is what Batik does in GVTLineMetrics
+ metrics.getUnderlineThickness(size) / 1000000f);
+ }
+
+ public GVTLineMetrics getLineMetrics(CharacterIterator ci, int beginIndex, int limit,
+ FontRenderContext frc) {
+ return getLineMetrics(limit - beginIndex);
+ }
+
+ public GVTLineMetrics getLineMetrics(String str, FontRenderContext frc) {
+ return getLineMetrics(str.length());
+ }
+
+ public GVTLineMetrics getLineMetrics(String str, int beginIndex, int limit, FontRenderContext frc) {
+ return getLineMetrics(limit - beginIndex);
+ }
+
+ public float getSize() {
+ return font.getFontSize() / 1000f;
+ }
+
+ public float getVKern(int glyphCode1, int glyphCode2) {
+ return 0;
+ }
+
+ public float getHKern(int glyphCode1, int glyphCode2) {
+ // TODO Cannot be implemented until getKernValue takes glyph indices instead of character codes
+ return 0;
+ }
+
+}
diff --git a/src/java/org/apache/fop/svg/font/FOPGVTFontFamily.java b/src/java/org/apache/fop/svg/font/FOPGVTFontFamily.java
new file mode 100644
index 000000000..036b560a0
--- /dev/null
+++ b/src/java/org/apache/fop/svg/font/FOPGVTFontFamily.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.svg.font;
+
+import java.awt.font.TextAttribute;
+import java.text.AttributedCharacterIterator;
+import java.util.Map;
+
+import org.apache.batik.gvt.font.GVTFontFace;
+import org.apache.batik.gvt.font.GVTFontFamily;
+
+import org.apache.fop.fonts.FontInfo;
+import org.apache.fop.fonts.FontTriplet;
+import org.apache.fop.svg.ACIUtils;
+
+public class FOPGVTFontFamily implements GVTFontFamily {
+
+ private final FontInfo fontInfo;
+
+ private final FontTriplet fontTriplet;
+
+ private final String familyName;
+
+ public FOPGVTFontFamily(FontInfo fontInfo, String familyName, FontTriplet triplet) {
+ this.fontInfo = fontInfo;
+ this.fontTriplet = triplet;
+ this.familyName = familyName;
+ }
+
+ public String getFamilyName() {
+ return familyName;
+ }
+
+ public GVTFontFace getFontFace() {
+ // TODO Auto-generated method stub
+ throw new UnsupportedOperationException();
+ }
+
+ public FOPGVTFont deriveFont(float size, AttributedCharacterIterator aci) {
+ return deriveFont(size, aci.getAttributes());
+ }
+
+ public FOPGVTFont deriveFont(float size, @SuppressWarnings("rawtypes") Map attrs) {
+ Float fontWeight = (Float) attrs.get(TextAttribute.WEIGHT);
+ int weight = fontWeight == null ? fontTriplet.getWeight() : ACIUtils.toCSSWeight(fontWeight);
+ Float fontStyle = (Float) attrs.get(TextAttribute.POSTURE);
+ String style = fontStyle == null ? fontTriplet.getStyle() : ACIUtils.toStyle(fontStyle);
+ FontTriplet triplet = fontInfo.fontLookup(fontTriplet.getName(), style, weight);
+ return new FOPGVTFont(fontInfo.getFontInstance(triplet, (int) (size * 1000)), this);
+ }
+
+ public boolean isComplex() {
+ return false;
+ }
+
+}
diff --git a/src/java/org/apache/fop/svg/font/FOPGVTGlyphVector.java b/src/java/org/apache/fop/svg/font/FOPGVTGlyphVector.java
new file mode 100644
index 000000000..3567bb508
--- /dev/null
+++ b/src/java/org/apache/fop/svg/font/FOPGVTGlyphVector.java
@@ -0,0 +1,339 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.svg.font;
+
+import java.awt.Graphics2D;
+import java.awt.Rectangle;
+import java.awt.Shape;
+import java.awt.font.FontRenderContext;
+import java.awt.font.GlyphJustificationInfo;
+import java.awt.font.GlyphMetrics;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.text.AttributedCharacterIterator;
+import java.text.CharacterIterator;
+import java.text.StringCharacterIterator;
+import java.util.Arrays;
+
+import org.apache.batik.gvt.font.GVTFont;
+import org.apache.batik.gvt.font.GVTGlyphMetrics;
+import org.apache.batik.gvt.font.GVTGlyphVector;
+import org.apache.batik.gvt.font.GVTLineMetrics;
+
+import org.apache.fop.fonts.Font;
+import org.apache.fop.fonts.FontMetrics;
+import org.apache.fop.fonts.GlyphMapping;
+import org.apache.fop.fonts.TextFragment;
+import org.apache.fop.traits.MinOptMax;
+
+class FOPGVTGlyphVector implements GVTGlyphVector {
+
+ private final CharacterIterator charIter;
+
+ private final FOPGVTFont font;
+
+ private final int fontSize;
+
+ private final FontMetrics fontMetrics;
+
+ private final FontRenderContext frc;
+
+ private int[] glyphs;
+
+ private float[] positions;
+
+ private Rectangle2D[] boundingBoxes;
+
+ private GeneralPath outline;
+
+ private AffineTransform[] glyphTransforms;
+
+ private boolean[] glyphVisibilities;
+
+ private Rectangle2D logicalBounds;
+
+ FOPGVTGlyphVector(FOPGVTFont font, final CharacterIterator iter, FontRenderContext frc) {
+ this.charIter = iter;
+ this.font = font;
+ Font f = font.getFont();
+ this.fontSize = f.getFontSize();
+ this.fontMetrics = f.getFontMetrics();
+ this.frc = frc;
+ }
+
+ public void performDefaultLayout() {
+ Font f = font.getFont();
+ TextFragment text = new SVGTextFragment(charIter);
+ MinOptMax letterSpaceIPD = MinOptMax.ZERO;
+ MinOptMax[] letterSpaceAdjustments = new MinOptMax[charIter.getEndIndex() - charIter.getBeginIndex()];
+ GlyphMapping mapping = GlyphMapping.doGlyphMapping(text, charIter.getBeginIndex(), charIter.getEndIndex(),
+ f, letterSpaceIPD, letterSpaceAdjustments, '\0', '\0', false, 0 /* TODO */);
+ glyphs = buildGlyphs(f, mapping.mapping != null ? new StringCharacterIterator(mapping.mapping) : charIter);
+ buildGlyphPositions(mapping, letterSpaceAdjustments);
+ this.glyphVisibilities = new boolean[this.glyphs.length];
+ Arrays.fill(glyphVisibilities, true);
+ this.glyphTransforms = new AffineTransform[this.glyphs.length];
+ }
+
+ private static class SVGTextFragment implements TextFragment {
+
+ private final CharacterIterator charIter;
+
+ SVGTextFragment(CharacterIterator charIter) {
+ this.charIter = charIter;
+ }
+
+ public CharSequence subSequence(int startIndex, int endIndex) {
+ StringBuilder sb = new StringBuilder();
+ for (char c = charIter.first(); c != CharacterIterator.DONE; c = charIter.next()) {
+ sb.append(c);
+ }
+ return sb.toString();
+ }
+
+ public String getScript() {
+ return "DFLT"; // TODO pass on script value from SVG
+ }
+
+ public String getLanguage() {
+ return "dflt"; // TODO pass on language value from SVG
+ }
+
+ public char charAt(int index) {
+ return charIter.setIndex(index - charIter.getBeginIndex());
+ }
+ }
+
+ private int[] buildGlyphs(Font font, final CharacterIterator charIter) {
+ int[] glyphs = new int[charIter.getEndIndex() - charIter.getBeginIndex()];
+ int index = 0;
+ for (char c = charIter.first(); c != CharacterIterator.DONE; c = charIter.next()) {
+ glyphs[index] = font.mapChar(c);
+ index++;
+ }
+ return glyphs;
+ }
+
+ private void buildGlyphPositions(GlyphMapping ai, MinOptMax[] letterSpaceAdjustments) {
+ positions = new float[2 * glyphs.length + 2];
+ if (ai.gposAdjustments != null) {
+ assert ai.gposAdjustments.length == glyphs.length;
+ for (int glyphIndex = 0; glyphIndex < glyphs.length; glyphIndex++) {
+ int n = 2 * glyphIndex;
+ if (ai.gposAdjustments[glyphIndex] != null) {
+ for (int p = 0; p < 4; p++) {
+ positions[n + p] += ai.gposAdjustments[glyphIndex][p] / 1000f;
+ }
+ }
+ positions[n + 2] += positions[n] + getGlyphWidth(glyphIndex);
+ }
+ } else {
+ for (int i = 0, n = 2; i < glyphs.length; i++, n += 2) {
+ int kern = i < glyphs.length - 1 && letterSpaceAdjustments[i + 1] != null
+ ? letterSpaceAdjustments[i + 1].getOpt()
+ : 0;
+ positions[n] = positions[n - 2] + getGlyphWidth(i) + kern / 1000f;
+ positions[n + 1] = 0;
+ }
+ }
+ }
+
+ private float getGlyphWidth(int index) {
+ return fontMetrics.getWidth(glyphs[index], fontSize) / 1000000f;
+ }
+
+ public GVTFont getFont() {
+ return font;
+ }
+
+ public FontRenderContext getFontRenderContext() {
+ return frc;
+ }
+
+ public int getGlyphCode(int glyphIndex) {
+ return glyphs[glyphIndex];
+ }
+
+ public int[] getGlyphCodes(int beginGlyphIndex, int numEntries,
+ int[] codeReturn) {
+ if (codeReturn == null) {
+ codeReturn = new int[numEntries];
+ }
+ System.arraycopy(glyphs, beginGlyphIndex, codeReturn, 0, numEntries);
+ return codeReturn;
+ }
+
+ public GlyphJustificationInfo getGlyphJustificationInfo(int glyphIndex) {
+ // TODO Auto-generated method stub
+ throw new UnsupportedOperationException();
+ }
+
+ public Shape getGlyphLogicalBounds(int glyphIndex) {
+ GVTGlyphMetrics metrics = getGlyphMetrics(glyphIndex);
+ Point2D pos = getGlyphPosition(glyphIndex);
+ GVTLineMetrics fontMetrics = font.getLineMetrics(0);
+ Rectangle2D bounds = new Rectangle2D.Float(0, -fontMetrics.getDescent(), metrics.getHorizontalAdvance(),
+ fontMetrics.getAscent() + fontMetrics.getDescent());
+ AffineTransform t = AffineTransform.getTranslateInstance(pos.getX(), pos.getY());
+ AffineTransform transf = getGlyphTransform(glyphIndex);
+ if (transf != null) {
+ t.concatenate(transf);
+ }
+ t.scale(1, -1); // Translate from glyph coordinate system to user
+ return t.createTransformedShape(bounds);
+ }
+
+ public GVTGlyphMetrics getGlyphMetrics(int glyphIndex) {
+ Rectangle2D bbox = getBoundingBoxes()[glyphIndex];
+ return new GVTGlyphMetrics(positions[2 * (glyphIndex + 1)] - positions[2 * glyphIndex],
+ (fontMetrics.getAscender(fontSize) - fontMetrics.getDescender(fontSize)) / 1000000f,
+ bbox, GlyphMetrics.STANDARD);
+ }
+
+ public Shape getGlyphOutline(int glyphIndex) {
+ Shape glyphBox = getBoundingBoxes()[glyphIndex];
+ AffineTransform tr = AffineTransform.getTranslateInstance(positions[glyphIndex * 2],
+ positions[glyphIndex * 2 + 1]);
+ AffineTransform glyphTransform = getGlyphTransform(glyphIndex);
+ if (glyphTransform != null) {
+ tr.concatenate(glyphTransform);
+ }
+ return tr.createTransformedShape(glyphBox);
+ }
+
+ public Rectangle2D getGlyphCellBounds(int glyphIndex) {
+ // TODO Auto-generated method stub
+ throw new UnsupportedOperationException();
+ }
+
+ public Point2D getGlyphPosition(int glyphIndex) {
+ int positionIndex = glyphIndex * 2;
+ return new Point2D.Float(positions[positionIndex], positions[positionIndex + 1]);
+ }
+
+ public float[] getGlyphPositions(int beginGlyphIndex, int numEntries, float[] positionReturn) {
+ if (positionReturn == null) {
+ positionReturn = new float[numEntries * 2];
+ }
+ System.arraycopy(positions, beginGlyphIndex * 2, positionReturn, 0, numEntries * 2);
+ return positionReturn;
+ }
+
+ public AffineTransform getGlyphTransform(int glyphIndex) {
+ return glyphTransforms[glyphIndex];
+ }
+
+ public Shape getGlyphVisualBounds(int glyphIndex) {
+ Rectangle2D bbox = getBoundingBoxes()[glyphIndex];
+ Point2D pos = getGlyphPosition(glyphIndex);
+ AffineTransform t = AffineTransform.getTranslateInstance(pos.getX(), pos.getY());
+ AffineTransform transf = getGlyphTransform(glyphIndex);
+ if (transf != null) {
+ t.concatenate(transf);
+ }
+ return t.createTransformedShape(bbox);
+ }
+
+ public Rectangle2D getLogicalBounds() {
+ if (logicalBounds == null) {
+ GeneralPath logicalBoundsPath = new GeneralPath();
+ for (int i = 0; i < getNumGlyphs(); i++) {
+ Shape glyphLogicalBounds = getGlyphLogicalBounds(i);
+ logicalBoundsPath.append(glyphLogicalBounds, false);
+ }
+ logicalBounds = logicalBoundsPath.getBounds2D();
+ }
+ return logicalBounds;
+ }
+
+ public int getNumGlyphs() {
+ return glyphs.length;
+ }
+
+ public Shape getOutline() {
+ if (outline == null) {
+ outline = new GeneralPath();
+ for (int i = 0; i < glyphs.length; i++) {
+ outline.append(getGlyphOutline(i), false);
+ }
+ }
+ return outline;
+ }
+
+ public Shape getOutline(float x, float y) {
+ // TODO Auto-generated method stub
+ throw new UnsupportedOperationException();
+ }
+
+ public Rectangle2D getGeometricBounds() {
+ // TODO Auto-generated method stub
+ throw new UnsupportedOperationException();
+ }
+
+ public Rectangle2D getBounds2D(AttributedCharacterIterator aci) {
+ return getOutline().getBounds2D();
+ }
+
+ public void setGlyphPosition(int glyphIndex, Point2D newPos) {
+ int idx = glyphIndex * 2;
+ positions[idx] = (float) newPos.getX();
+ positions[idx + 1] = (float) newPos.getY();
+ }
+
+ public void setGlyphTransform(int glyphIndex, AffineTransform newTX) {
+ glyphTransforms[glyphIndex] = newTX;
+ }
+
+ public void setGlyphVisible(int glyphIndex, boolean visible) {
+ glyphVisibilities[glyphIndex] = visible;
+ }
+
+ public boolean isGlyphVisible(int glyphIndex) {
+ return glyphVisibilities[glyphIndex];
+ }
+
+ public int getCharacterCount(int startGlyphIndex, int endGlyphIndex) {
+ // TODO Not that simple if complex scripts are involved
+ return endGlyphIndex - startGlyphIndex + 1;
+ }
+
+ public void draw(Graphics2D graphics2d, AttributedCharacterIterator aci) {
+ // NOP
+ }
+
+ private Rectangle2D[] getBoundingBoxes() {
+ if (boundingBoxes == null) {
+ buildBoundingBoxes();
+ }
+ return boundingBoxes;
+ }
+
+ private void buildBoundingBoxes() {
+ boundingBoxes = new Rectangle2D[glyphs.length];
+ for (int i = 0; i < glyphs.length; i++) {
+ Rectangle bbox = fontMetrics.getBoundingBox(glyphs[i], fontSize);
+ boundingBoxes[i] = new Rectangle2D.Float(bbox.x / 1000000f, -(bbox.y + bbox.height) / 1000000f,
+ bbox.width / 1000000f, bbox.height / 1000000f);
+ }
+ }
+
+}
diff --git a/src/java/org/apache/fop/svg/font/FilteringFontFamilyResolver.java b/src/java/org/apache/fop/svg/font/FilteringFontFamilyResolver.java
new file mode 100644
index 000000000..319d6b2b8
--- /dev/null
+++ b/src/java/org/apache/fop/svg/font/FilteringFontFamilyResolver.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.svg.font;
+
+
+public class FilteringFontFamilyResolver implements FOPFontFamilyResolver {
+
+ private final FOPFontFamilyResolver delegate;
+
+ public FilteringFontFamilyResolver(FOPFontFamilyResolver delegate) {
+ this.delegate = delegate;
+ }
+
+ public String lookup(String familyName) {
+ return delegate.lookup(familyName);
+ }
+
+ public FOPGVTFontFamily resolve(String familyName) {
+ return delegate.resolve(familyName);
+ }
+
+ public FOPGVTFontFamily getDefault() {
+ return delegate.getDefault();
+ }
+
+ public FOPGVTFontFamily getFamilyThatCanDisplay(char c) {
+ return delegate.getFamilyThatCanDisplay(c);
+ }
+
+}