diff options
Diffstat (limited to 'src/java/org/apache/fop')
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); + } + +} |