瀏覽代碼

FOP-1969: Support for unicode Surrogate pairs

git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1827168 13f79535-47bb-0310-9956-ffa450edef68
tags/fop-2_3
Simon Steiner 6 年之前
父節點
當前提交
88751079b0
共有 47 個檔案被更改,包括 1677 行新增252 行删除
  1. 7
    0
      fop-core/pom.xml
  2. 17
    0
      fop-core/src/main/java/org/apache/fop/complexscripts/util/GlyphSequence.java
  3. 14
    0
      fop-core/src/main/java/org/apache/fop/fonts/CIDFont.java
  4. 8
    3
      fop-core/src/main/java/org/apache/fop/fonts/CIDFull.java
  5. 11
    1
      fop-core/src/main/java/org/apache/fop/fonts/CIDSet.java
  6. 22
    11
      fop-core/src/main/java/org/apache/fop/fonts/CIDSubset.java
  7. 79
    31
      fop-core/src/main/java/org/apache/fop/fonts/Font.java
  8. 9
    4
      fop-core/src/main/java/org/apache/fop/fonts/FontSelector.java
  9. 61
    35
      fop-core/src/main/java/org/apache/fop/fonts/GlyphMapping.java
  10. 56
    7
      fop-core/src/main/java/org/apache/fop/fonts/MultiByteFont.java
  11. 3
    2
      fop-core/src/main/java/org/apache/fop/fonts/truetype/OFMtxEntry.java
  12. 105
    3
      fop-core/src/main/java/org/apache/fop/fonts/truetype/OpenFont.java
  13. 4
    2
      fop-core/src/main/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java
  14. 14
    4
      fop-core/src/main/java/org/apache/fop/pdf/PDFText.java
  15. 19
    11
      fop-core/src/main/java/org/apache/fop/pdf/PDFTextUtil.java
  16. 11
    2
      fop-core/src/main/java/org/apache/fop/pdf/PDFToUnicodeCMap.java
  17. 5
    0
      fop-core/src/main/java/org/apache/fop/render/java2d/CustomFontMetricsMapper.java
  18. 12
    5
      fop-core/src/main/java/org/apache/fop/render/java2d/Java2DPainter.java
  19. 17
    7
      fop-core/src/main/java/org/apache/fop/render/java2d/Java2DRenderer.java
  20. 88
    0
      fop-core/src/main/java/org/apache/fop/render/java2d/Java2DUtil.java
  21. 2
    2
      fop-core/src/main/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFFontReader.java
  22. 15
    11
      fop-core/src/main/java/org/apache/fop/render/pdf/PDFPainter.java
  23. 9
    4
      fop-core/src/main/java/org/apache/fop/render/ps/PSPainter.java
  24. 133
    0
      fop-core/src/main/java/org/apache/fop/util/CharUtilities.java
  25. 11
    4
      fop-core/src/main/java/org/apache/fop/util/HexEncoder.java
  26. 2
    3
      fop-core/src/test/java/org/apache/fop/complexscripts/bidi/BidiTestData.java
  27. 8
    10
      fop-core/src/test/java/org/apache/fop/complexscripts/scripts/arabic/ArabicWordFormsTestCase.java
  28. 17
    23
      fop-core/src/test/java/org/apache/fop/complexscripts/scripts/arabic/GenerateArabicTestData.java
  29. 7
    0
      fop-core/src/test/java/org/apache/fop/fonts/CIDFullTestCase.java
  30. 198
    0
      fop-core/src/test/java/org/apache/fop/fonts/CIDSubsetTestCase.java
  31. 139
    0
      fop-core/src/test/java/org/apache/fop/fonts/FontSelectorTestCase.java
  32. 121
    9
      fop-core/src/test/java/org/apache/fop/fonts/truetype/TTFFileTestCase.java
  33. 119
    0
      fop-core/src/test/java/org/apache/fop/render/java2d/Java2DUtilTestCase.java
  34. 55
    31
      fop-core/src/test/java/org/apache/fop/render/pdf/PDFEncodingTestCase.java
  35. 57
    17
      fop-core/src/test/java/org/apache/fop/render/pdf/PDFPainterTestCase.java
  36. 12
    4
      fop-core/src/test/java/org/apache/fop/render/ps/PSPainterTestCase.java
  37. 73
    0
      fop-core/src/test/java/org/apache/fop/util/CharUtilitiesTestCase.java
  38. 14
    1
      fop-core/src/test/java/org/apache/fop/util/HexEncoderTestCase.java
  39. 1
    1
      fop/build.xml
  40. 57
    0
      fop/test/layoutengine/hyphenation-testcases/block_hyphenation_kerning_non_bmp.xml
  41. 3
    0
      fop/test/resources/fonts/ttf/Aegean600.LICENSE
  42. 二進制
      fop/test/resources/fonts/ttf/Aegean600.ttf
  43. 18
    0
      fop/test/resources/fonts/ttf/AndroidEmoji.LICENSE
  44. 二進制
      fop/test/resources/fonts/ttf/AndroidEmoji.ttf
  45. 6
    3
      fop/test/xml/pdf-encoding/pdf-encoding-test.xconf
  46. 1
    1
      fop/test/xml/pdf-encoding/test-custom-font.fo
  47. 37
    0
      fop/test/xml/pdf-encoding/test-custom-non-bmp-font.fo

+ 7
- 0
fop-core/pom.xml 查看文件

<version>${xmlunit.version}</version> <version>${xmlunit.version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>2.0.3</version>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>


<build> <build>
<headerLocation>${project.baseUri}src/tools/resources/checkstyle/LICENSE.txt</headerLocation> <headerLocation>${project.baseUri}src/tools/resources/checkstyle/LICENSE.txt</headerLocation>
<includeResources>false</includeResources> <includeResources>false</includeResources>
<includeTestResources>false</includeTestResources> <includeTestResources>false</includeTestResources>
<includeTestSourceDirectory>true</includeTestSourceDirectory>
<linkXRef>false</linkXRef> <linkXRef>false</linkXRef>
<logViolationsToConsole>true</logViolationsToConsole> <logViolationsToConsole>true</logViolationsToConsole>
<suppressionsLocation>${project.baseUri}src/tools/resources/checkstyle/suppressions.xml</suppressionsLocation> <suppressionsLocation>${project.baseUri}src/tools/resources/checkstyle/suppressions.xml</suppressionsLocation>

+ 17
- 0
fop-core/src/main/java/org/apache/fop/complexscripts/util/GlyphSequence.java 查看文件

/** /**
* Obtain the number of characters in character array, where * Obtain the number of characters in character array, where
* each character constitutes a unicode scalar value. * each character constitutes a unicode scalar value.
* NB: Supplementary characters (non-BMP code points) count as 1
* character, not as two UTF-16 code units.
* @return number of characters available in character array * @return number of characters available in character array
*/ */
public int getCharacterCount() { public int getCharacterCount() {
return characters.limit(); return characters.limit();
} }


/**
* Obtain the number of characters in character array, where
* each character constitutes a UTF-16 character. This means
* that every non-BMP character is counted as 2 characters.
* @return number of chars (UTF-16 code units) available in
* character array
*/
public int getUTF16CharacterCount() {
int count = 0;
for (int ch : characters.array()) {
count += Character.charCount(ch);
}
return count;
}

/** /**
* Obtain glyph id at specified index. * Obtain glyph id at specified index.
* @param index to obtain glyph * @param index to obtain glyph

+ 14
- 0
fop-core/src/main/java/org/apache/fop/fonts/CIDFont.java 查看文件

*/ */
public abstract CIDSet getCIDSet(); public abstract CIDSet getCIDSet();


/**
* Determines whether this font contains a particular code point/glyph.
* @param cp character to check
* @return True if the character is supported, False otherwise
*/
public abstract boolean hasCodePoint(int cp);

/**
* Map a Unicode code point to a code point in the font.
* @param cp code point to map
* @return the mapped code point
*/
public abstract int mapCodePoint(int cp);

// ---- Optional ---- // ---- Optional ----
/** /**
* Returns the default width for this font. * Returns the default width for this font.

+ 8
- 3
fop-core/src/main/java/org/apache/fop/fonts/CIDFull.java 查看文件

} }


/** {@inheritDoc} */ /** {@inheritDoc} */
public char getUnicode(int index) {
public int getUnicode(int index) {
initGlyphIndices(); initGlyphIndices();
if (glyphIndices.get(index)) { if (glyphIndices.get(index)) {
return (char) index;
return index;
} else { } else {
return CharUtilities.NOT_A_CHARACTER; return CharUtilities.NOT_A_CHARACTER;
} }


/** {@inheritDoc} */ /** {@inheritDoc} */
public int mapChar(int glyphIndex, char unicode) { public int mapChar(int glyphIndex, char unicode) {
return (char) glyphIndex;
return glyphIndex;
}

/** {@inheritDoc} */
public int mapCodePoint(int glyphIndex, int codePoint) {
return glyphIndex;
} }


/** {@inheritDoc} */ /** {@inheritDoc} */

+ 11
- 1
fop-core/src/main/java/org/apache/fop/fonts/CIDSet.java 查看文件

* @param index the subset index (character selector) * @param index the subset index (character selector)
* @return the Unicode value or "NOT A CHARACTER" (0xFFFF) * @return the Unicode value or "NOT A CHARACTER" (0xFFFF)
*/ */
char getUnicode(int index);
int getUnicode(int index);


/** /**
* Gets the unicode character from the original font glyph index * Gets the unicode character from the original font glyph index
*/ */
int mapChar(int glyphIndex, char unicode); int mapChar(int glyphIndex, char unicode);


/**
* Maps a character to a character selector for a font subset. If the character isn't in the
* subset yet, it is added and a new character selector returned. Otherwise, the already
* allocated character selector is returned from the existing map/subset.
* @param glyphIndex the glyph index of the character
* @param codePoint the Unicode index of the character
* @return the subset index
*/
int mapCodePoint(int glyphIndex, int codePoint);

/** /**
* Returns an unmodifiable Map of the font subset. It maps from glyph index to * Returns an unmodifiable Map of the font subset. It maps from glyph index to
* character selector (i.e. the subset index in this case). * character selector (i.e. the subset index in this case).

+ 22
- 11
fop-core/src/main/java/org/apache/fop/fonts/CIDSubset.java 查看文件

/** /**
* usedCharsIndex contains new glyph, original char (char selector -> Unicode) * usedCharsIndex contains new glyph, original char (char selector -> Unicode)
*/ */
private Map<Integer, Character> usedCharsIndex = new HashMap<Integer, Character>();
private Map<Integer, Integer> usedCharsIndex = new HashMap<Integer, Integer>();


/** /**
* A map between the original character and it's GID in the original font. * A map between the original character and it's GID in the original font.
*/ */
private Map<Character, Integer> charToGIDs = new HashMap<Character, Integer>();
private Map<Integer, Integer> charToGIDs = new HashMap<Integer, Integer>();




private final MultiByteFont font; private final MultiByteFont font;
} }


/** {@inheritDoc} */ /** {@inheritDoc} */
public char getUnicode(int index) {
Character mapValue = usedCharsIndex.get(index);
public int getUnicode(int index) {
Integer mapValue = usedCharsIndex.get(index);
if (mapValue != null) { if (mapValue != null) {
return mapValue; return mapValue;
} else { } else {


/** {@inheritDoc} */ /** {@inheritDoc} */
public int mapChar(int glyphIndex, char unicode) { public int mapChar(int glyphIndex, char unicode) {
return mapCodePoint(glyphIndex, unicode);
}

/** {@inheritDoc} */
public int mapCodePoint(int glyphIndex, int codePoint) {
// Reencode to a new subset font or get the reencoded value // Reencode to a new subset font or get the reencoded value
// IOW, accumulate the accessed characters and build a character map for them // IOW, accumulate the accessed characters and build a character map for them
Integer subsetCharSelector = usedGlyphs.get(glyphIndex); Integer subsetCharSelector = usedGlyphs.get(glyphIndex);
int selector = usedGlyphsCount; int selector = usedGlyphsCount;
usedGlyphs.put(glyphIndex, selector); usedGlyphs.put(glyphIndex, selector);
usedGlyphsIndex.put(selector, glyphIndex); usedGlyphsIndex.put(selector, glyphIndex);
usedCharsIndex.put(selector, unicode);
charToGIDs.put(unicode, glyphIndex);
usedCharsIndex.put(selector, codePoint);
charToGIDs.put(codePoint, glyphIndex);
usedGlyphsCount++; usedGlyphsCount++;
return selector; return selector;
} else { } else {


/** {@inheritDoc} */ /** {@inheritDoc} */
public char getUnicodeFromGID(int glyphIndex) { public char getUnicodeFromGID(int glyphIndex) {
// TODO this method is never called in the MultiByte font path.
// This is why we can safely cast the value of usedCharsIndex.get(selector)
// to int . BTW is a question if it should be changed to int as getUnicode
// or left like this.
int selector = usedGlyphs.get(glyphIndex); int selector = usedGlyphs.get(glyphIndex);
return usedCharsIndex.get(selector);
return (char) usedCharsIndex.get(selector).intValue();
} }


/** {@inheritDoc} */ /** {@inheritDoc} */
public int getGIDFromChar(char ch) { public int getGIDFromChar(char ch) {
return charToGIDs.get(ch);
return charToGIDs.get((int) ch);
} }


/** {@inheritDoc} */ /** {@inheritDoc} */
public char[] getChars() { public char[] getChars() {
char[] charArray = new char[usedGlyphsCount];
StringBuilder buf = new StringBuilder();

for (int i = 0; i < usedGlyphsCount; i++) { for (int i = 0; i < usedGlyphsCount; i++) {
charArray[i] = getUnicode(i);
buf.appendCodePoint(getUnicode(i));
} }
return charArray;

return buf.toString().toCharArray();
} }


/** {@inheritDoc} */ /** {@inheritDoc} */

+ 79
- 31
fop-core/src/main/java/org/apache/fop/fonts/Font.java 查看文件



import org.apache.fop.complexscripts.fonts.Positionable; import org.apache.fop.complexscripts.fonts.Positionable;
import org.apache.fop.complexscripts.fonts.Substitutable; import org.apache.fop.complexscripts.fonts.Substitutable;
import org.apache.fop.render.java2d.CustomFontMetricsMapper;
import org.apache.fop.util.CharUtilities;


/** /**
* This class holds font state information and provides access to the font * This class holds font state information and provides access to the font
* @param ch2 second character * @param ch2 second character
* @return the distance to adjust for kerning, 0 if there's no kerning * @return the distance to adjust for kerning, 0 if there's no kerning
*/ */
public int getKernValue(char ch1, char ch2) {
Map<Integer, Integer> kernPair = getKerning().get((int) ch1);
public int getKernValue(int ch1, int ch2) {
// Isolate surrogate pair
if ((ch1 >= 0xD800) && (ch1 <= 0xE000)) {
return 0;
} else if ((ch2 >= 0xD800) && (ch2 <= 0xE000)) {
return 0;
}

Map<Integer, Integer> kernPair = getKerning().get(ch1);
if (kernPair != null) { if (kernPair != null) {
Integer width = kernPair.get((int) ch2);
Integer width = kernPair.get(ch2);
if (width != null) { if (width != null) {
return width * getFontSize() / 1000; return width * getFontSize() / 1000;
} }
return 0; return 0;
} }


/**
* Returns the amount of kerning between two characters.
*
* The value returned measures in pt. So it is already adjusted for font size.
*
* @param ch1 first character
* @param ch2 second character
* @return the distance to adjust for kerning, 0 if there's no kerning
*/
public int getKernValue(int ch1, int ch2) {
// TODO !BMP
if (ch1 > 0x10000) {
return 0;
} else if ((ch1 >= 0xD800) && (ch1 <= 0xE000)) {
return 0;
} else if (ch2 > 0x10000) {
return 0;
} else if ((ch2 >= 0xD800) && (ch2 <= 0xE000)) {
return 0;
} else {
return getKernValue((char) ch1, (char) ch2);
}
}

/** /**
* Returns the width of a character * Returns the width of a character
* @param charnum character to look up * @param charnum character to look up
return c; return c;
} }


/**
* Map a unicode code point to a font character.
* Default uses CodePointMapping.
* @param cp code point to map
* @return the mapped character
*/
public int mapCodePoint(int cp) {
FontMetrics fontMetrics = getRealFontMetrics();

if (fontMetrics instanceof CIDFont) {
return ((CIDFont) fontMetrics).mapCodePoint(cp);
}

if (CharUtilities.isBmpCodePoint(cp)) {
return mapChar((char) cp);
}

return Typeface.NOT_FOUND;
}

/** /**
* Determines whether this font contains a particular character/glyph. * Determines whether this font contains a particular character/glyph.
* @param c character to check * @param c character to check
* @return True if the character is supported, Falso otherwise
* @return True if the character is supported, False otherwise
*/ */
public boolean hasChar(char c) { public boolean hasChar(char c) {
if (metric instanceof org.apache.fop.fonts.Typeface) { if (metric instanceof org.apache.fop.fonts.Typeface) {
} }
} }


/**
* Determines whether this font contains a particular code point/glyph.
* @param cp code point to check
* @return True if the code point is supported, False otherwise
*/
public boolean hasCodePoint(int cp) {
FontMetrics realFont = getRealFontMetrics();

if (realFont instanceof CIDFont) {
return ((CIDFont) realFont).hasCodePoint(cp);
}

if (CharUtilities.isBmpCodePoint(cp)) {
return hasChar((char) cp);
}

return false;
}

/**
* Get the real underlying font if it is wrapped inside some container such as a {@link LazyFont} or a
* {@link CustomFontMetricsMapper}.
*
* @return instance of the font
*/
private FontMetrics getRealFontMetrics() {
FontMetrics realFontMetrics = metric;

if (realFontMetrics instanceof CustomFontMetricsMapper) {
realFontMetrics = ((CustomFontMetricsMapper) realFontMetrics).getRealFont();
}

if (realFontMetrics instanceof LazyFont) {
return ((LazyFont) realFontMetrics).getRealFont();
}

return realFontMetrics;
}

/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public int getCharWidth(int c) { public int getCharWidth(int c) {
if (c < 0x10000) { if (c < 0x10000) {
return getCharWidth((char) c); return getCharWidth((char) c);
} else {
// TODO !BMP
return -1;
} }

if (hasCodePoint(c)) {
int mappedChar = mapCodePoint(c);
return getWidth(mappedChar);
}

return -1;
} }


/** /**

+ 9
- 4
fop-core/src/main/java/org/apache/fop/fonts/FontSelector.java 查看文件

import org.apache.fop.fo.FOText; import org.apache.fop.fo.FOText;
import org.apache.fop.fo.flow.Character; import org.apache.fop.fo.flow.Character;
import org.apache.fop.fo.properties.CommonFont; import org.apache.fop.fo.properties.CommonFont;
import org.apache.fop.util.CharUtilities;


/** /**
* Helper class for automatic font selection. * Helper class for automatic font selection.
final Font font = fi.getFontInstance(fontkeys[fontnum], final Font font = fi.getFontInstance(fontkeys[fontnum],
commonFont.fontSize.getValue(context)); commonFont.fontSize.getValue(context));
fonts[fontnum] = font; fonts[fontnum] = font;
for (int pos = firstIndex; pos < breakIndex; pos++) {
if (font.hasChar(charSeq.charAt(pos))) {

int numCodePoints = 0;
for (int cp : CharUtilities.codepointsIter(charSeq, firstIndex, breakIndex)) {
numCodePoints++;

if (font.hasCodePoint(cp)) {
fontCount[fontnum]++; fontCount[fontnum]++;
} }
} }


// quick fall through if all characters can be displayed
if (fontCount[fontnum] == (breakIndex - firstIndex)) {
// quick fall through if all codepoints can be displayed
if (fontCount[fontnum] == numCodePoints) {
return font; return font;
} }
} }

+ 61
- 35
fop-core/src/main/java/org/apache/fop/fonts/GlyphMapping.java 查看文件



package org.apache.fop.fonts; package org.apache.fop.fonts;


import java.util.ArrayList;
import java.util.List; import java.util.List;


import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.fop.traits.MinOptMax; import org.apache.fop.traits.MinOptMax;
import org.apache.fop.util.CharUtilities; import org.apache.fop.util.CharUtilities;


import static org.apache.fop.fonts.type1.AdobeStandardEncoding.i;

/** /**
* Stores the mapping of a text fragment to glyphs, along with various information. * Stores the mapping of a text fragment to glyphs, along with various information.
*/ */
MinOptMax areaIPD, boolean isHyphenated, boolean isSpace, boolean breakOppAfter, MinOptMax areaIPD, boolean isHyphenated, boolean isSpace, boolean breakOppAfter,
Font font, int level, int[][] gposAdjustments) { Font font, int level, int[][] gposAdjustments) {
this(startIndex, endIndex, wordSpaceCount, letterSpaceCount, areaIPD, isHyphenated, this(startIndex, endIndex, wordSpaceCount, letterSpaceCount, areaIPD, isHyphenated,
isSpace, breakOppAfter, font, level, gposAdjustments, null, null);
isSpace, breakOppAfter, font, level, gposAdjustments, null, null);
} }


public GlyphMapping(int startIndex, int endIndex, int wordSpaceCount, int letterSpaceCount, public GlyphMapping(int startIndex, int endIndex, int wordSpaceCount, int letterSpaceCount,
GlyphMapping mapping; GlyphMapping mapping;
if (font.performsSubstitution() || font.performsPositioning()) { if (font.performsSubstitution() || font.performsPositioning()) {
mapping = processWordMapping(text, startIndex, endIndex, font, mapping = processWordMapping(text, startIndex, endIndex, font,
breakOpportunityChar, endsWithHyphen, level,
dontOptimizeForIdentityMapping, retainAssociations, retainControls);
breakOpportunityChar, endsWithHyphen, level,
dontOptimizeForIdentityMapping, retainAssociations, retainControls);
} else { } else {
mapping = processWordNoMapping(text, startIndex, endIndex, font, mapping = processWordNoMapping(text, startIndex, endIndex, font,
letterSpaceIPD, letterSpaceAdjustArray, precedingChar, breakOpportunityChar, endsWithHyphen, level);
letterSpaceIPD, letterSpaceAdjustArray, precedingChar, breakOpportunityChar, endsWithHyphen, level);
} }
return mapping; return mapping;
} }
private static GlyphMapping processWordMapping(TextFragment text, int startIndex, private static GlyphMapping processWordMapping(TextFragment text, int startIndex,
int endIndex, final Font font, final char breakOpportunityChar, int endIndex, final Font font, final char breakOpportunityChar,
final boolean endsWithHyphen, int level, final boolean endsWithHyphen, int level,
boolean dontOptimizeForIdentityMapping, boolean retainAssociations, boolean retainControls) {
int e = endIndex; // end index of word in FOText character buffer
boolean dontOptimizeForIdentityMapping, boolean retainAssociations, boolean retainControls) {
int nLS = 0; // # of letter spaces int nLS = 0; // # of letter spaces
String script = text.getScript(); String script = text.getScript();
String language = text.getLanguage(); String language = text.getLanguage();


if (LOG.isDebugEnabled()) { if (LOG.isDebugEnabled()) {
LOG.debug("PW: [" + startIndex + "," + endIndex + "]: {" LOG.debug("PW: [" + startIndex + "," + endIndex + "]: {"
+ " +M"
+ ", level = " + level
+ " }");
+ " +M"
+ ", level = " + level
+ " }");
} }


// 1. extract unmapped character sequence. // 1. extract unmapped character sequence.
CharSequence ics = text.subSequence(startIndex, e);
CharSequence ics = text.subSequence(startIndex, endIndex);


// 2. if script is not specified (by FO property) or it is specified as 'auto', // 2. if script is not specified (by FO property) or it is specified as 'auto',
// then compute dominant script. // then compute dominant script.


// 3. perform mapping of chars to glyphs ... to glyphs ... to chars, retaining // 3. perform mapping of chars to glyphs ... to glyphs ... to chars, retaining
// associations if requested. // associations if requested.
List associations = retainAssociations ? new java.util.ArrayList() : null;
List associations = retainAssociations ? new ArrayList() : null;

// This is a workaround to read the ligature from the font even if the script
// does not match the one defined for the table.
// More info here: https://issues.apache.org/jira/browse/FOP-2638
// zyyy == SCRIPT_UNDEFINED
if ("zyyy".equals(script) || "auto".equals(script)) {
script = "*";
}

CharSequence mcs = font.performSubstitution(ics, script, language, associations, retainControls); CharSequence mcs = font.performSubstitution(ics, script, language, associations, retainControls);


// 4. compute glyph position adjustments on (substituted) characters. // 4. compute glyph position adjustments on (substituted) characters.
MinOptMax ipd = MinOptMax.ZERO; MinOptMax ipd = MinOptMax.ZERO;
for (int i = 0, n = mcs.length(); i < n; i++) { for (int i = 0, n = mcs.length(); i < n; i++) {
int c = mcs.charAt(i); int c = mcs.charAt(i);
// TODO !BMP

if (CharUtilities.containsSurrogatePairAt(mcs, i)) {
c = Character.toCodePoint((char) c, mcs.charAt(++i));
}

int w = font.getCharWidth(c); int w = font.getCharWidth(c);
if (w < 0) { if (w < 0) {
w = 0; w = 0;


// [TBD] - handle letter spacing // [TBD] - handle letter spacing


return new GlyphMapping(startIndex, e, 0, nLS, ipd, endsWithHyphen, false,
return new GlyphMapping(startIndex, endIndex, 0, nLS, ipd, endsWithHyphen, false,
breakOpportunityChar != 0, font, level, gpa, breakOpportunityChar != 0, font, level, gpa,
!dontOptimizeForIdentityMapping && CharUtilities.isSameSequence(mcs, ics) ? null : mcs.toString(), !dontOptimizeForIdentityMapping && CharUtilities.isSameSequence(mcs, ics) ? null : mcs.toString(),
associations); associations);
* @return glyph position adjustments (or null if no kerning) * @return glyph position adjustments (or null if no kerning)
*/ */
private static int[][] getKerningAdjustments(CharSequence mcs, final Font font, int[][] gpa) { private static int[][] getKerningAdjustments(CharSequence mcs, final Font font, int[][] gpa) {
int nc = mcs.length();
int numCodepoints = Character.codePointCount(mcs, 0, mcs.length());
// extract kerning array // 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);
int[] kernings = new int[numCodepoints]; // kerning array

int prevCp = -1;
int i = 0;
for (int cp : CharUtilities.codepointsIter(mcs)) {
if (prevCp >= 0) {
kernings[i] = font.getKernValue(prevCp, cp);
} }
cPrev = c;
prevCp = cp;
i++;
} }
// was there a non-zero kerning? // was there a non-zero kerning?
boolean hasKerning = false; boolean hasKerning = false;
for (int i = 0, n = nc; i < n; i++) {
if (ka[i] != 0) {
for (int kerningValue : kernings) {
if (kerningValue != 0) {
hasKerning = true; hasKerning = true;
break; break;
} }
// if non-zero kerning, then create and return glyph position adjustment array // if non-zero kerning, then create and return glyph position adjustment array
if (hasKerning) { if (hasKerning) {
if (gpa == null) { if (gpa == null) {
gpa = new int[nc][4];
gpa = new int[numCodepoints][4];
} }
for (int i = 0, n = nc; i < n; i++) {
for (i = 0; i < numCodepoints; i++) {
if (i > 0) { if (i > 0) {
gpa [i - 1][GlyphPositioningTable.Value.IDX_X_ADVANCE] += ka[i];
gpa [i - 1][GlyphPositioningTable.Value.IDX_X_ADVANCE] += kernings[i];
} }
} }
return gpa; return gpa;


if (LOG.isDebugEnabled()) { if (LOG.isDebugEnabled()) {
LOG.debug("PW: [" + startIndex + "," + endIndex + "]: {" LOG.debug("PW: [" + startIndex + "," + endIndex + "]: {"
+ " -M"
+ ", level = " + level
+ " }");
+ " -M"
+ ", level = " + level
+ " }");
} }


for (int i = startIndex; i < endIndex; i++) {
char currentChar = text.charAt(i);
CharSequence ics = text.subSequence(startIndex, endIndex);
int offset = 0;
for (int currentChar : CharUtilities.codepointsIter(ics)) {


// character width // character width
int charWidth = font.getCharWidth(currentChar); int charWidth = font.getCharWidth(currentChar);
// kerning // kerning
if (kerning) { if (kerning) {
int kern = 0; int kern = 0;
if (i > startIndex) {
char previousChar = text.charAt(i - 1);
if (offset > 0) {
int previousChar = java.lang.Character.codePointAt(ics, offset - 1);
kern = font.getKernValue(previousChar, currentChar); kern = font.getKernValue(previousChar, currentChar);
} else if (precedingChar != 0) { } else if (precedingChar != 0) {
kern = font.getKernValue(precedingChar, currentChar); kern = font.getKernValue(precedingChar, currentChar);
} }
if (kern != 0) { if (kern != 0) {
addToLetterAdjust(letterSpaceAdjustArray, i, kern);
addToLetterAdjust(letterSpaceAdjustArray, startIndex + offset, kern);
wordIPD = wordIPD.plus(kern); wordIPD = wordIPD.plus(kern);
} }
} }
offset++;
} }
if (kerning if (kerning
&& (breakOpportunityChar != 0) && (breakOpportunityChar != 0)
&& !isSpace(breakOpportunityChar) && !isSpace(breakOpportunityChar)
&& endIndex > 0 && endIndex > 0
&& endsWithHyphen) { && endsWithHyphen) {
int kern = font.getKernValue(text.charAt(endIndex - 1), breakOpportunityChar);
int endChar = text.charAt(endIndex - 1);

if (java.lang.Character.isLowSurrogate((char) endChar)) {
char highSurrogate = text.charAt(endIndex - 2);
endChar = java.lang.Character.toCodePoint(highSurrogate, (char) endChar);
}

int kern = font.getKernValue(endChar, (int) breakOpportunityChar);
if (kern != 0) { if (kern != 0) {
addToLetterAdjust(letterSpaceAdjustArray, endIndex, kern); addToLetterAdjust(letterSpaceAdjustArray, endIndex, kern);
// TODO: add kern to wordIPD? // TODO: add kern to wordIPD?

+ 56
- 7
fop-core/src/main/java/org/apache/fop/fonts/MultiByteFont.java 查看文件

import java.io.InputStream; import java.io.InputStream;
import java.nio.CharBuffer; import java.nio.CharBuffer;
import java.nio.IntBuffer; import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.BitSet; import java.util.BitSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
return (char) glyphIndex; return (char) glyphIndex;
} }


/** {@inheritDoc} */
@Override
public int mapCodePoint(int cp) {
notifyMapOperation();
int glyphIndex = findGlyphIndex(cp);
if (glyphIndex == SingleByteEncoding.NOT_FOUND_CODE_POINT) {

for (char ch : Character.toChars(cp)) {
//TODO better handling for non BMP
warnMissingGlyph(ch);
}

if (!isOTFFile) {
glyphIndex = findGlyphIndex(Typeface.NOT_FOUND);
}
}
if (isEmbeddable()) {
glyphIndex = cidSet.mapCodePoint(glyphIndex, cp);
}
return (char) glyphIndex;
}

/** {@inheritDoc} */ /** {@inheritDoc} */
@Override @Override
public boolean hasChar(char c) { public boolean hasChar(char c) {
return (findGlyphIndex(c) != SingleByteEncoding.NOT_FOUND_CODE_POINT);
return hasCodePoint(c);
}

/** {@inheritDoc} */
@Override
public boolean hasCodePoint(int cp) {
return (findGlyphIndex(cp) != SingleByteEncoding.NOT_FOUND_CODE_POINT);
} }


/** /**
if (!retainControls) { if (!retainControls) {
ogs = elideControls(ogs); ogs = elideControls(ogs);
} }
// ocs may not contains all the characters that were in cs.
// see: #createPrivateUseMapping(int gi)
CharSequence ocs = mapGlyphsToChars(ogs); CharSequence ocs = mapGlyphsToChars(ogs);
return ocs; return ocs;
} else { } else {
*/ */
private CharSequence mapGlyphsToChars(GlyphSequence gs) { private CharSequence mapGlyphsToChars(GlyphSequence gs) {
int ng = gs.getGlyphCount(); int ng = gs.getGlyphCount();
CharBuffer cb = CharBuffer.allocate(ng);
int ccMissing = Typeface.NOT_FOUND; int ccMissing = Typeface.NOT_FOUND;
List<Character> chars = new ArrayList<Character>(gs.getUTF16CharacterCount());

for (int i = 0, n = ng; i < n; i++) { for (int i = 0, n = ng; i < n; i++) {
int gi = gs.getGlyph(i); int gi = gs.getGlyph(i);
int cc = findCharacterFromGlyphIndex(gi); int cc = findCharacterFromGlyphIndex(gi);
cc -= 0x10000; cc -= 0x10000;
sh = ((cc >> 10) & 0x3FF) + 0xD800; sh = ((cc >> 10) & 0x3FF) + 0xD800;
sl = ((cc >> 0) & 0x3FF) + 0xDC00; sl = ((cc >> 0) & 0x3FF) + 0xDC00;
cb.put((char) sh);
cb.put((char) sl);
chars.add((char) sh);
chars.add((char) sl);
} else { } else {
cb.put((char) cc);
chars.add((char) cc);
} }
} }

CharBuffer cb = CharBuffer.allocate(chars.size());

for (char c : chars) {
cb.put(c);
}

cb.flip(); cb.flip();
return cb; return cb;
} }
return sb; return sb;
} }


/**
* Removes the glyphs associated with elidable control characters.
* All the characters in an association must be elidable in order
* to remove the corresponding glyph.
*
* @param gs GlyphSequence that may contains the elidable glyphs
* @return GlyphSequence without the elidable glyphs
*/
private static GlyphSequence elideControls(GlyphSequence gs) { private static GlyphSequence elideControls(GlyphSequence gs) {
if (hasElidableControl(gs)) { if (hasElidableControl(gs)) {
int[] ca = gs.getCharacterArray(false); int[] ca = gs.getCharacterArray(false);
int e = a.getEnd(); int e = a.getEnd();
while (s < e) { while (s < e) {
int ch = ca [ s ]; int ch = ca [ s ];
if (isElidableControl(ch)) {
if (!isElidableControl(ch)) {
break; break;
} else { } else {
++s; ++s;
} }
} }
if (s == e) {
// If there is at least one non-elidable character in the char
// sequence then the glyph/association is kept.
if (s != e) {
ngb.put(gs.getGlyph(i)); ngb.put(gs.getGlyph(i));
nal.add(a); nal.add(a);
} }

+ 3
- 2
fop-core/src/main/java/org/apache/fop/fonts/truetype/OFMtxEntry.java 查看文件



package org.apache.fop.fonts.truetype; package org.apache.fop.fonts.truetype;


import java.util.ArrayList;
import java.util.List; import java.util.List;


/** /**
private int lsb; private int lsb;
private String name = ""; private String name = "";
private int index; private int index;
private List unicodeIndex = new java.util.ArrayList();
private List<Integer> unicodeIndex = new ArrayList<Integer>();
private int[] boundingBox = new int[4]; private int[] boundingBox = new int[4];
private long offset; private long offset;
private byte found; private byte found;
* Returns the unicodeIndex. * Returns the unicodeIndex.
* @return List * @return List
*/ */
public List getUnicodeIndex() {
public List<Integer> getUnicodeIndex() {
return unicodeIndex; return unicodeIndex;
} }



+ 105
- 3
fop-core/src/main/java/org/apache/fop/fonts/truetype/OpenFont.java 查看文件

* tables are present. Currently only unicode cmaps are supported. * tables are present. Currently only unicode cmaps are supported.
* Set the unicodeIndex in the TTFMtxEntries and fills in the * Set the unicodeIndex in the TTFMtxEntries and fills in the
* cmaps vector. * cmaps vector.
*
* @see <a href="https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6cmap.html">
* TrueType-Reference-Manual
* </a>
*/ */
protected boolean readCMAP() throws IOException { protected boolean readCMAP() throws IOException {


int numCMap = fontFile.readTTFUShort(); // Number of cmap subtables int numCMap = fontFile.readTTFUShort(); // Number of cmap subtables
long cmapUniOffset = 0; long cmapUniOffset = 0;
long symbolMapOffset = 0; long symbolMapOffset = 0;
long surrogateMapOffset = 0;


if (log.isDebugEnabled()) { if (log.isDebugEnabled()) {
log.debug(numCMap + " cmap tables"); log.debug(numCMap + " cmap tables");
if (cmapPID == 3 && cmapEID == 0) { if (cmapPID == 3 && cmapEID == 0) {
symbolMapOffset = cmapOffset; symbolMapOffset = cmapOffset;
} }
if (cmapPID == 3 && cmapEID == 10) {
surrogateMapOffset = cmapOffset;
}
} }


if (cmapUniOffset > 0) {
if (surrogateMapOffset > 0) {
// TODO maybe for SingleByte fonts instances we should not reach this branch
return readUnicodeCmap(surrogateMapOffset, 10);
} else if (cmapUniOffset > 0) {
return readUnicodeCmap(cmapUniOffset, 1); return readUnicodeCmap(cmapUniOffset, 1);
} else if (symbolMapOffset > 0) { } else if (symbolMapOffset > 0) {
return readUnicodeCmap(symbolMapOffset, 0); return readUnicodeCmap(symbolMapOffset, 0);
// Read unicode cmap // Read unicode cmap
seekTab(fontFile, OFTableName.CMAP, cmapUniOffset); seekTab(fontFile, OFTableName.CMAP, cmapUniOffset);
int cmapFormat = fontFile.readTTFUShort(); int cmapFormat = fontFile.readTTFUShort();
/*int cmap_length =*/ fontFile.readTTFUShort(); //skip cmap length

if (cmapFormat < 8) {
fontFile.readTTFUShort(); //skip cmap length
fontFile.readTTFUShort(); //skip cmap version
} else {
fontFile.readTTFUShort(); //skip 2 bytes to read a Fixed32
fontFile.readTTFULong(); //skip cmap length
fontFile.readTTFULong(); //skip cmap version
}


if (log.isDebugEnabled()) { if (log.isDebugEnabled()) {
log.debug("CMAP format: " + cmapFormat); log.debug("CMAP format: " + cmapFormat);
} }


if (cmapFormat == 4) { if (cmapFormat == 4) {
fontFile.skip(2); // Skip version number
int cmapSegCountX2 = fontFile.readTTFUShort(); int cmapSegCountX2 = fontFile.readTTFUShort();
int cmapSearchRange = fontFile.readTTFUShort(); int cmapSearchRange = fontFile.readTTFUShort();
int cmapEntrySelector = fontFile.readTTFUShort(); int cmapEntrySelector = fontFile.readTTFUShort();
} }
} }
} }
} else if (cmapFormat == 12) {
long nGroups = fontFile.readTTFULong();

for (long i = 0; i < nGroups; ++i) {
long startCharCode = fontFile.readTTFULong();
long endCharCode = fontFile.readTTFULong();
long startGlyphCode = fontFile.readTTFULong();

if (startCharCode < 0 || startCharCode > 0x10FFFFL) {
log.warn("startCharCode outside Unicode range");
continue;
}

if (startCharCode >= 0xD800 && startCharCode <= 0xDFFF) {
log.warn("startCharCode is a surrogate pair: " + startCharCode);
}

//endCharCode outside unicode range or is surrogate pair.
if (endCharCode > 0 && endCharCode < startCharCode || endCharCode > 0x10FFFFL) {
log.warn("startCharCode outside Unicode range");
continue;
}

if (endCharCode >= 0xD800 && endCharCode <= 0xDFFF) {
log.warn("endCharCode is a surrogate pair: " + startCharCode);
}

for (long offset = 0; offset <= endCharCode - startCharCode; ++offset) {
long glyphIndexL = startGlyphCode + offset;
long charCodeL = startCharCode + offset;

if (glyphIndexL >= numberOfGlyphs) {
log.warn("Format 12 cmap contains an invalid glyph index");
break;
}

if (charCodeL > 0x10FFFFL) {
log.warn("Format 12 cmap contains character beyond UCS-4");
}

if (glyphIndexL > Integer.MAX_VALUE) {
log.error("glyphIndex > Integer.MAX_VALUE");
continue;
}

if (charCodeL > Integer.MAX_VALUE) {
log.error("startCharCode + j > Integer.MAX_VALUE");
continue;
}

// Update lastChar
if (charCodeL < 0xFF && charCodeL > lastChar) {
lastChar = (short) charCodeL;
}

int charCode = (int) charCodeL;
int glyphIndex = (int) glyphIndexL;

// Also add winAnsiWidth.
List<Integer> ansiIndexes = null;

if (charCodeL <= java.lang.Character.MAX_VALUE) {
ansiIndexes = ansiIndex.get((int) charCodeL);
}

unicodeMappings.add(new UnicodeMapping(this, glyphIndex, charCode));
mtxTab[glyphIndex].getUnicodeIndex().add(charCode);

if (ansiIndexes == null) {
continue;
}

for (Integer aIdx : ansiIndexes) {
ansiWidth[aIdx] = mtxTab[glyphIndex].getWx();

if (log.isTraceEnabled()) {
log.trace("Added width "
+ mtxTab[glyphIndex].getWx()
+ " uni: " + offset
+ " ansi: " + aIdx);
}
}
}
}
} else { } else {
log.error("Cmap format not supported: " + cmapFormat); log.error("Cmap format not supported: " + cmapFormat);
return false; return false;

+ 4
- 2
fop-core/src/main/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java 查看文件



//log.info("Word: " + new String(textArray, startIndex, stopIndex - startIndex)); //log.info("Word: " + new String(textArray, startIndex, stopIndex - startIndex));
for (int i = startIndex; i < stopIndex; i++) { for (int i = startIndex; i < stopIndex; i++) {
char ch = foText.charAt(i);
newIPD = newIPD.plus(font.getCharWidth(ch));
int cp = Character.codePointAt(foText, i);
i += Character.charCount(cp) - 1;

newIPD = newIPD.plus(font.getCharWidth(cp));
//if (i > startIndex) { //if (i > startIndex) {
if (i < stopIndex) { if (i < stopIndex) {
MinOptMax letterSpaceAdjust = letterSpaceAdjustArray[i + 1]; MinOptMax letterSpaceAdjust = letterSpaceAdjustArray[i + 1];

+ 14
- 4
fop-core/src/main/java/org/apache/fop/pdf/PDFText.java 查看文件



import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;


import java.util.Locale;

import org.apache.fop.util.CharUtilities;

/** /**
* This class represents a simple number object. It also contains contains some * This class represents a simple number object. It also contains contains some
* utility methods for outputting numbers to PDF. * utility methods for outputting numbers to PDF.


/** /**
* Convert a char to a multibyte hex representation appending to string buffer. * Convert a char to a multibyte hex representation appending to string buffer.
* Since Java always stores strings in UTF-16, we don't have to do any conversion.
* The created string will be:
* <ul>
* <li>4-character string in case of non-BMP character</li>
* <li>6-character string in case of BMP character</li>
* </ul>
* @param c character to encode * @param c character to encode
* @param sb the string buffer to append output * @param sb the string buffer to append output
*/ */
public static final void toUnicodeHex(char c, StringBuffer sb) {
for (int i = 0; i < 4; ++i) {
sb.append(DIGITS[(c >> (12 - 4 * i)) & 0x0F]);
public static final void toUnicodeHex(int c, StringBuffer sb) {
if (CharUtilities.isBmpCodePoint(c)) {
sb.append(Integer.toHexString(c + 0x10000).substring(1).toUpperCase(Locale.US));
} else {
sb.append(Integer.toHexString(c + 0x1000000).substring(1).toUpperCase(Locale.US));
} }
} }



+ 19
- 11
fop-core/src/main/java/org/apache/fop/pdf/PDFTextUtil.java 查看文件

PDFNumber.doubleOut(lt[5], DEC, sb); PDFNumber.doubleOut(lt[5], DEC, sb);
} }


private static void writeChar(char ch, StringBuffer sb, boolean multibyte, boolean cid) {
private static void writeChar(int codePoint, StringBuffer sb, boolean multibyte, boolean cid) {
if (!multibyte) { if (!multibyte) {
if (cid || ch < 32 || ch > 127) {
sb.append("\\").append(Integer.toOctalString(ch));
if (cid || codePoint < 32 || codePoint > 127) {
sb.append("\\").append(Integer.toOctalString(codePoint));
} else { } else {
switch (ch) {
switch (codePoint) {
case '(': case '(':
case ')': case ')':
case '\\': case '\\':
break; break;
default: default:
} }
sb.append(ch);
sb.appendCodePoint(codePoint);
} }
} else { } else {
PDFText.toUnicodeHex(ch, sb);
PDFText.toUnicodeHex(codePoint, sb);
} }
} }


private void writeChar(char ch, StringBuffer sb) {
writeChar(ch, sb, useMultiByte, useCid);
private void writeChar(int codePoint, StringBuffer sb) {
writeChar(codePoint, sb, useMultiByte, useCid);
} }


private void checkInTextObject() { private void checkInTextObject() {


/** /**
* Writes a char to the "TJ-Buffer". * Writes a char to the "TJ-Buffer".
* @param codepoint the mapped character (code point/character code)
* @param ch the mapped character (code point/character code)
*/ */
public void writeTJMappedChar(char codepoint) {
public void writeTJMappedChar(char ch) {
writeTJMappedCodePoint((int) ch);
}

/**
* Writes a codepoint to the "TJ-Buffer".
* @param codePoint the mapped character (code point/character code)
*/
public void writeTJMappedCodePoint(int codePoint) {
if (bufTJ == null) { if (bufTJ == null) {
bufTJ = new StringBuffer(); bufTJ = new StringBuffer();
} }
bufTJ.append('['); bufTJ.append('[');
bufTJ.append(startText); bufTJ.append(startText);
} }
writeChar(codepoint, bufTJ);
writeChar(codePoint, bufTJ);
} }


/** /**

+ 11
- 2
fop-core/src/main/java/org/apache/fop/pdf/PDFToUnicodeCMap.java 查看文件

charIndex++; charIndex++;
} }
writer.write("<" + padCharIndex(charIndex) + "> "); writer.write("<" + padCharIndex(charIndex) + "> ");
writer.write("<" + padHexString(Integer.toHexString(charArray[charIndex]), 4)
+ ">\n");

if (Character.codePointAt(charArray, charIndex) > 0xFFFF) {
// Handle UTF-16 surrogate pairs
String pairs = Integer.toHexString(charArray[charIndex])
+ Integer.toHexString(charArray[++charIndex]);
writer.write("<" + pairs + ">\n");
i++;
} else {
writer.write("<" + padHexString(Integer.toHexString(charArray[charIndex]), 4)
+ ">\n");
}
charIndex++; charIndex++;
} }
remainingEntries -= entriesThisSection; remainingEntries -= entriesThisSection;

+ 5
- 0
fop-core/src/main/java/org/apache/fop/render/java2d/CustomFontMetricsMapper.java 查看文件

return typeface.hasKerningInfo(); return typeface.hasKerningInfo();
} }


/** {@inheritDoc} */
public boolean isMultiByte() {
return typeface.isMultiByte();
}

/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */

+ 12
- 5
fop-core/src/main/java/org/apache/fop/render/java2d/Java2DPainter.java 查看文件

g2dState.updateFont(font.getFontName(), state.getFontSize() * 1000); g2dState.updateFont(font.getFontName(), state.getFontSize() * 1000);


Graphics2D g2d = this.g2dState.getGraph(); Graphics2D g2d = this.g2dState.getGraph();
GlyphVector gv = g2d.getFont().createGlyphVector(g2d.getFontRenderContext(), text);
GlyphVector gv = Java2DUtil.createGlyphVector(text, g2d, font, fontInfo);
Point2D cursor = new Point2D.Float(0, 0); Point2D cursor = new Point2D.Float(0, 0);


int l = text.length(); int l = text.length();
cursor.setLocation(cursor.getX() + dp[0][0], cursor.getY() - dp[0][1]); cursor.setLocation(cursor.getX() + dp[0][0], cursor.getY() - dp[0][1]);
gv.setGlyphPosition(0, cursor); gv.setGlyphPosition(0, cursor);
} }

int currentIdx = 0;
for (int i = 0; i < l; i++) { for (int i = 0; i < l; i++) {
char orgChar = text.charAt(i);
int orgChar = text.codePointAt(i);
// The dp (GPOS/kerning adjustment) is performed over glyphs and not
// characters (GlyphMapping.processWordMapping). The length of dp is
// adjusted later to fit the length of the String adding trailing 0.
// This means that it's probably ok to consume one of the 2 surrogate
// pairs.
i += CharUtilities.incrementIfNonBMP(orgChar);

float xGlyphAdjust = 0; float xGlyphAdjust = 0;
float yGlyphAdjust = 0; float yGlyphAdjust = 0;
int cw = font.getCharWidth(orgChar); int cw = font.getCharWidth(orgChar);
} }


cursor.setLocation(cursor.getX() + cw + xGlyphAdjust, cursor.getY() - yGlyphAdjust); cursor.setLocation(cursor.getX() + cw + xGlyphAdjust, cursor.getY() - yGlyphAdjust);
gv.setGlyphPosition(i + 1, cursor);
gv.setGlyphPosition(++currentIdx, cursor);
} }
g2d.drawGlyphVector(gv, x, y); g2d.drawGlyphVector(gv, x, y);
} }
g2dState.transform(transform); g2dState.transform(transform);
} }




} }

+ 17
- 7
fop-core/src/main/java/org/apache/fop/render/java2d/Java2DRenderer.java 查看文件

AffineTransform at = new AffineTransform(); AffineTransform at = new AffineTransform();
at.translate(rx / 1000f, bl / 1000f); at.translate(rx / 1000f, bl / 1000f);
state.transform(at); state.transform(at);
renderText(text, state.getGraph(), font);
renderText(text, state.getGraph(), font, fontInfo);
restoreGraphicsState(); restoreGraphicsState();


currentIPPosition = saveIP + text.getAllocIPD(); currentIPPosition = saveIP + text.getAllocIPD();
* @param text the TextArea * @param text the TextArea
* @param g2d the Graphics2D to render to * @param g2d the Graphics2D to render to
* @param font the font to paint with * @param font the font to paint with
* @param fontInfo the font information
*/ */
public static void renderText(TextArea text, Graphics2D g2d, Font font) {
public static void renderText(TextArea text, Graphics2D g2d, Font font, FontInfo fontInfo) {


Color col = (Color) text.getTrait(Trait.COLOR); Color col = (Color) text.getTrait(Trait.COLOR);
g2d.setColor(col); g2d.setColor(col);
WordArea word = (WordArea) child; WordArea word = (WordArea) child;
String s = word.getWord(); String s = word.getWord();
int[] letterAdjust = word.getLetterAdjustArray(); int[] letterAdjust = word.getLetterAdjustArray();
GlyphVector gv = g2d.getFont().createGlyphVector(g2d.getFontRenderContext(), s);
GlyphVector gv = Java2DUtil.createGlyphVector(s, g2d, font, fontInfo);
double additionalWidth = 0.0; double additionalWidth = 0.0;
if (letterAdjust == null if (letterAdjust == null
&& text.getTextLetterSpaceAdjust() == 0 && text.getTextLetterSpaceAdjust() == 0
} else { } else {
int[] offsets = getGlyphOffsets(s, font, text, letterAdjust); int[] offsets = getGlyphOffsets(s, font, text, letterAdjust);
float cursor = 0.0f; float cursor = 0.0f;
for (int i = 0; i < offsets.length; i++) {

if (offsets.length != gv.getNumGlyphs()) {
log.error(String.format("offsets length different from glyphNumber: %d != %d",
offsets.length, gv.getNumGlyphs()));
}

// If for any reason offsets.length != gv.getNumGlyphs() then we have to choose the minimum to avoid
// ArrayIndexOutOfBoundsException. This might happen when surrogate pairs are not correctly handled.
for (int i = 0; i < Math.min(offsets.length, gv.getNumGlyphs()); i++) {
Point2D pt = gv.getGlyphPosition(i); Point2D pt = gv.getGlyphPosition(i);
pt.setLocation(cursor, pt.getY()); pt.setLocation(cursor, pt.getY());
gv.setGlyphPosition(i, pt); gv.setGlyphPosition(i, pt);
cursor += offsets[i] / 1000f; cursor += offsets[i] / 1000f;
} }

additionalWidth = cursor - gv.getLogicalBounds().getWidth(); additionalWidth = cursor - gv.getLogicalBounds().getWidth();
} }
g2d.drawGlyphVector(gv, textCursor, 0); g2d.drawGlyphVector(gv, textCursor, 0);


private static int[] getGlyphOffsets(String s, Font font, TextArea text, private static int[] getGlyphOffsets(String s, Font font, TextArea text,
int[] letterAdjust) { int[] letterAdjust) {
int textLen = s.length();
int textLen = s.codePointCount(0, s.length());
int[] offsets = new int[textLen]; int[] offsets = new int[textLen];
for (int i = 0; i < textLen; i++) { for (int i = 0; i < textLen; i++) {
final char c = s.charAt(i);
final char mapped = font.mapChar(c);
int c = s.codePointAt(i);
final int mapped = font.mapCodePoint(c);
int wordSpace; int wordSpace;


if (CharUtilities.isAdjustableSpace(mapped)) { if (CharUtilities.isAdjustableSpace(mapped)) {

+ 88
- 0
fop-core/src/main/java/org/apache/fop/render/java2d/Java2DUtil.java 查看文件



package org.apache.fop.render.java2d; package org.apache.fop.render.java2d;


import java.awt.Graphics2D;
import java.awt.font.GlyphVector;
import java.util.Arrays;

import org.apache.fop.apps.FOUserAgent; import org.apache.fop.apps.FOUserAgent;
import org.apache.fop.fonts.Font;
import org.apache.fop.fonts.FontCollection; import org.apache.fop.fonts.FontCollection;
import org.apache.fop.fonts.FontEventAdapter; import org.apache.fop.fonts.FontEventAdapter;
import org.apache.fop.fonts.FontInfo; import org.apache.fop.fonts.FontInfo;
import org.apache.fop.fonts.FontManager; import org.apache.fop.fonts.FontManager;
import org.apache.fop.fonts.LazyFont;
import org.apache.fop.fonts.MultiByteFont;
import org.apache.fop.fonts.Typeface;
import org.apache.fop.util.CharUtilities;


/** /**
* Rendering-related utilities for Java2D. * Rendering-related utilities for Java2D.
return fi; return fi;
} }


/**
* Creates an instance of {@link GlyphVector} that correctly handle surrogate pairs and advanced font features such
* as GSUB/GPOS/GDEF.
*
* @param text Text to render
* @param g2d the target Graphics2D instance
* @param font the font instance
* @param fontInfo the font information
* @return an instance of {@link GlyphVector}
*/
public static GlyphVector createGlyphVector(String text, Graphics2D g2d, Font font, FontInfo fontInfo) {
MultiByteFont multiByteFont = getMultiByteFont(font.getFontName(), fontInfo);

if (multiByteFont == null) {
return createGlyphVector(text, g2d);
}

return createGlyphVectorMultiByteFont(text, g2d, multiByteFont);
}

/**
* Creates a {@link GlyphVector} using characters. Filters out non-bmp characters.
*/
private static GlyphVector createGlyphVector(String text, Graphics2D g2d) {
StringBuilder sb = new StringBuilder(text.length());
for (int cp : CharUtilities.codepointsIter(text)) {
// If we are here we probably do not support non-BMP codepoints
sb.appendCodePoint(cp <= 0xFFFF ? cp : Typeface.NOT_FOUND);
}
return g2d.getFont().createGlyphVector(g2d.getFontRenderContext(), sb.toString());
}

/**
* Creates a {@link GlyphVector} using glyph indexes instead of characters. To correctly support the advanced font
* features we have to build the GlyphVector passing the glyph indexes instead of the characters. This because some
* of the chars in text might have been replaced by an internal font representation during
* GlyphMapping.processWordMapping. Eg 'fi' replaced with the corresponding character in the font ligatures table
* (GSUB).
*/
private static GlyphVector createGlyphVectorMultiByteFont(String text, Graphics2D g2d,
MultiByteFont multiByteFont) {
int[] glyphCodes = new int[text.length()];
int currentIdx = 0;

for (int cp : CharUtilities.codepointsIter(text)) {
// mapChar is not working here because MultiByteFont.mapChar replaces the glyph index with
// CIDSet.mapChar when isEmbeddable == true.
glyphCodes[currentIdx++] = multiByteFont.findGlyphIndex(cp);
}

// Trims glyphCodes
if (currentIdx != text.length()) {
glyphCodes = Arrays.copyOf(glyphCodes, currentIdx);
}

return g2d.getFont().createGlyphVector(g2d.getFontRenderContext(), glyphCodes);
}

/**
* Returns an instance of {@link MultiByteFont} for the given font name. This method will try to unwrap containers
* such as {@link CustomFontMetricsMapper} and {@link LazyFont}
*
* @param fontName font key
* @param fontInfo font information
* @return An instance of {@link MultiByteFont} or null if it
*/
private static MultiByteFont getMultiByteFont(String fontName, FontInfo fontInfo) {
Typeface tf = fontInfo.getFonts().get(fontName);

if (tf instanceof CustomFontMetricsMapper) {
tf = ((CustomFontMetricsMapper) tf).getRealFont();
}

if (tf instanceof LazyFont) {
tf = ((LazyFont) tf).getRealFont();
}

return (tf instanceof MultiByteFont) ? (MultiByteFont) tf : null;
}


} }

+ 2
- 2
fop-core/src/main/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFFontReader.java 查看文件

int nextOffset = 0; int nextOffset = 0;
int charCode = 0; int charCode = 0;
if (entry.getUnicodeIndex().size() > 0) { if (entry.getUnicodeIndex().size() > 0) {
charCode = (Integer) entry.getUnicodeIndex().get(0);
charCode = entry.getUnicodeIndex().get(0);
} else { } else {
charCode = entry.getIndex(); charCode = entry.getIndex();
} }
OFMtxEntry entry = mtx.get(i); OFMtxEntry entry = mtx.get(i);
int charCode = 0; int charCode = 0;
if (entry.getUnicodeIndex().size() > 0) { if (entry.getUnicodeIndex().size() > 0) {
charCode = (Integer) entry.getUnicodeIndex().get(0);
charCode = entry.getUnicodeIndex().get(0);
} else { } else {
charCode = entry.getIndex(); charCode = entry.getIndex();
} }

+ 15
- 11
fop-core/src/main/java/org/apache/fop/render/pdf/PDFPainter.java 查看文件

textutil.adjustGlyphTJ(-dx[0] / fontSize); textutil.adjustGlyphTJ(-dx[0] / fontSize);
} }
for (int i = 0; i < l; i++) { for (int i = 0; i < l; i++) {
char orgChar = text.charAt(i);
char ch;
int orgChar = text.charAt(i);
int ch;

// surrogate pairs have to be merged in a single code point
if (CharUtilities.containsSurrogatePairAt(text, i)) {
orgChar = Character.toCodePoint((char) orgChar, text.charAt(++i));
}

float glyphAdjust = 0; float glyphAdjust = 0;
if (font.hasChar(orgChar)) {
ch = font.mapChar(orgChar);
if (font.hasCodePoint(orgChar)) {
ch = font.mapCodePoint(orgChar);
ch = selectAndMapSingleByteFont(tf, fontName, fontSize, textutil, ch); ch = selectAndMapSingleByteFont(tf, fontName, fontSize, textutil, ch);
if ((wordSpacing != 0) && CharUtilities.isAdjustableSpace(orgChar)) { if ((wordSpacing != 0) && CharUtilities.isAdjustableSpace(orgChar)) {
glyphAdjust += wordSpacing; glyphAdjust += wordSpacing;
int spaceDiff = font.getCharWidth(CharUtilities.SPACE) - font.getCharWidth(orgChar); int spaceDiff = font.getCharWidth(CharUtilities.SPACE) - font.getCharWidth(orgChar);
glyphAdjust = -spaceDiff; glyphAdjust = -spaceDiff;
} else { } else {
ch = font.mapChar(orgChar);
ch = font.mapCodePoint(orgChar);
if ((wordSpacing != 0) && CharUtilities.isAdjustableSpace(orgChar)) { if ((wordSpacing != 0) && CharUtilities.isAdjustableSpace(orgChar)) {
glyphAdjust += wordSpacing; glyphAdjust += wordSpacing;
} }
} }
ch = selectAndMapSingleByteFont(tf, fontName, fontSize, textutil, ch); ch = selectAndMapSingleByteFont(tf, fontName, fontSize, textutil, ch);
} }
textutil.writeTJMappedChar(ch);
textutil.writeTJMappedCodePoint(ch);


if (dx != null && i < dxl - 1) { if (dx != null && i < dxl - 1) {
glyphAdjust += dx[i + 1]; glyphAdjust += dx[i + 1];
double xd = (xo - xoLast) / 1000f; double xd = (xo - xoLast) / 1000f;
double yd = (yo - yoLast) / 1000f; double yd = (yo - yoLast) / 1000f;
tu.writeTd(xd, yd); tu.writeTd(xd, yd);
ch = f.mapChar(ch);
ch = selectAndMapSingleByteFont(tf, f.getFontName(), fsPoints, tu, ch);
tu.writeTj(ch, tf.isMultiByte(), true);
tu.writeTj(f.mapChar(ch), tf.isMultiByte(), true);
xc += xa + pa[2]; xc += xa + pa[2];
yc += ya + pa[3]; yc += ya + pa[3];
xoLast = xo; xoLast = xo;
} }
*/ */


private char selectAndMapSingleByteFont(Typeface tf, String fontName, float fontSize, PDFTextUtil textutil,
char ch) {
private int selectAndMapSingleByteFont(Typeface tf, String fontName, float fontSize, PDFTextUtil textutil,
int ch) {
if ((tf instanceof SingleByteFont && ((SingleByteFont)tf).hasAdditionalEncodings()) || tf.isCID()) { if ((tf instanceof SingleByteFont && ((SingleByteFont)tf).hasAdditionalEncodings()) || tf.isCID()) {
int encoding = ch / 256; int encoding = ch / 256;
if (encoding == 0) { if (encoding == 0) {

+ 9
- 4
fop-core/src/main/java/org/apache/fop/render/ps/PSPainter.java 查看文件

StringBuffer sb = new StringBuffer(initialSize); StringBuffer sb = new StringBuffer(initialSize);
boolean isOTF = multiByte && ((MultiByteFont)tf).isOTFFile(); boolean isOTF = multiByte && ((MultiByteFont)tf).isOTFFile();
for (int i = start; i < end; i++) { for (int i = start; i < end; i++) {
char orgChar = text.charAt(i);
char ch;
int orgChar = text.charAt(i);
int ch;
int cw; int cw;
int xGlyphAdjust = 0; int xGlyphAdjust = 0;
int yGlyphAdjust = 0; int yGlyphAdjust = 0;
if ((wordSpacing != 0) && CharUtilities.isAdjustableSpace(orgChar)) { if ((wordSpacing != 0) && CharUtilities.isAdjustableSpace(orgChar)) {
xGlyphAdjust -= wordSpacing; xGlyphAdjust -= wordSpacing;
} }
ch = font.mapChar(orgChar);
cw = font.getCharWidth(orgChar); // this is never used?

// surrogate pairs have to be merged in a single code point
if (CharUtilities.containsSurrogatePairAt(text, i)) {
orgChar = Character.toCodePoint((char) orgChar, text.charAt(++i));
}

ch = font.mapCodePoint(orgChar);
} }


if (dp != null && i < dp.length && dp[i] != null) { if (dp != null && i < dp.length && dp[i] != null) {

+ 133
- 0
fop-core/src/main/java/org/apache/fop/util/CharUtilities.java 查看文件



package org.apache.fop.util; package org.apache.fop.util;


import java.util.Iterator;
import java.util.NoSuchElementException;

/** /**
* This class provides utilities to distinguish various kinds of Unicode * This class provides utilities to distinguish various kinds of Unicode
* whitespace and to get character widths in a given FontState. * whitespace and to get character widths in a given FontState.
} }
} }


/**
* Determine whether the specified character (Unicode code point) is in then Basic
* Multilingual Plane (BMP). Such code points can be represented using a single {@code char}.
*
* @see Character#isBmpCodePoint(int) from Java 1.7
* @param codePoint the character (Unicode code point) to be tested
* @return {@code true} if the specified code point is between Character#MIN_VALUE and
* Character#MAX_VALUE} inclusive; {@code false} otherwise
*/
public static boolean isBmpCodePoint(int codePoint) {
return codePoint >>> 16 == 0;
}

/**
* Returns 1 if codePoint not in the BMP. This function is particularly useful in for
* loops over strings where, in presence of surrogate pairs, you need to skip one loop.
*
* @param codePoint 1 if codePoint &gt; 0xFFFF, 0 otherwise
* @return 1 if codePoint &gt; 0xFFFF, 0 otherwise
*/
public static int incrementIfNonBMP(int codePoint) {
return isBmpCodePoint(codePoint) ? 0 : 1;
}

/**
* Determine if the given characters is part of a surrogate pair.
*
* @param ch character to be checked
* @return true if ch is an high surrogate or a low surrogate
*/
public static boolean isSurrogatePair(char ch) {
return Character.isHighSurrogate(ch) || Character.isLowSurrogate(ch);
}

/**
* Tells whether there is a surrogate pair starting from the given index in the {@link CharSequence}. If the
* character at index is an high surrogate then the character at index+1 is checked to be a low surrogate. If a
* malformed surrogate pair is encountered then an {@link IllegalArgumentException} is thrown.
* <pre>
* high surrogate [0xD800 - 0xDC00]
* low surrogate [0xDC00 - 0xE000]
* </pre>
*
* @param chars CharSequence to check
* @param index index in the CharSequqnce where to start the check
* @throws IllegalArgumentException if there wrong usage of surrogate pairs
* @return true if there is a well-formed surrogate pair at index
*/
public static boolean containsSurrogatePairAt(CharSequence chars, int index) {
char ch = chars.charAt(index);

if (Character.isHighSurrogate(ch)) {
if ((index + 1) > chars.length()) {
throw new IllegalArgumentException(
"ill-formed UTF-16 sequence, contains isolated high surrogate at end of sequence");
}

if (Character.isLowSurrogate(chars.charAt(index + 1))) {
return true;
}

throw new IllegalArgumentException(
"ill-formed UTF-16 sequence, contains isolated high surrogate at index " + index);

} else if (Character.isLowSurrogate(ch)) {
throw new IllegalArgumentException(
"ill-formed UTF-16 sequence, contains isolated low surrogate at index " + index);
}

return false;
}

/**
* Creates an iterator to iter a {@link CharSequence} codepoints.
*
* @see #codepointsIter(CharSequence, int, int)
* @param s {@link CharSequence} to iter
* @return codepoint iterator for the given {@link CharSequence}.
*/
public static Iterable<Integer> codepointsIter(final CharSequence s) {
return codepointsIter(s, 0, s.length());
}

/**
* Creates an iterator to iter a sub-CharSequence codepoints.
*
* @see <a href="http://bugs.java.com/bugdatabase/view_bug.do?bug_id=5003547">Bug JDK-5003547</a>
* @param s {@link CharSequence} to iter
* @param beginIndex lower range
* @param endIndex upper range
* @return codepoint iterator for the given sub-CharSequence.
*/
public static Iterable<Integer> codepointsIter(final CharSequence s, final int beginIndex, final int endIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > s.length()) {
throw new StringIndexOutOfBoundsException(endIndex);
}
int subLen = endIndex - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}

return new Iterable<Integer>() {
public Iterator<Integer> iterator() {
return new Iterator<Integer>() {
int nextIndex = beginIndex;

public boolean hasNext() {
return nextIndex < endIndex;
}

public Integer next() {
if (!hasNext()) {
// Findbugs wants this: IT_NO_SUCH_ELEMENT
throw new NoSuchElementException();
}
int result = Character.codePointAt(s, nextIndex);
nextIndex += Character.charCount(result);
return result;
}

public void remove() {
throw new UnsupportedOperationException();
}
};
}
};
}
} }

+ 11
- 4
fop-core/src/main/java/org/apache/fop/util/HexEncoder.java 查看文件

} }


/** /**
* Returns an hex encoding of the given character as a four-character string.
* Returns an hex encoding of the given character as:
* <ul>
* <li>4-character string in case of non-BMP character</li>
* <li>6-character string in case of BMP character</li>
* </ul>
* *
* @param c a character * @param c a character
* @return an hex-encoded representation of the character * @return an hex-encoded representation of the character
*/ */
public static String encode(char c) {
return encode(c, 4);
public static String encode(int c) {
if (CharUtilities.isBmpCodePoint(c)) {
return encode(c, 4);
} else {
return encode(c, 6);
}
} }

} }

+ 2
- 3
fop-core/src/test/java/org/apache/fop/complexscripts/bidi/BidiTestData.java 查看文件

import java.io.InputStream; import java.io.InputStream;
import java.io.ObjectInputStream; import java.io.ObjectInputStream;


import org.apache.commons.io.IOUtils;


/* /*
* !!! THIS IS A GENERATED FILE !!! * !!! THIS IS A GENERATED FILE !!!
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
data = null; data = null;
} finally { } finally {
if (is != null) {
try { is.close(); } catch (Exception e) { /* NOP */ }
}
IOUtils.closeQuietly(is);
} }
return data; return data;
} }

+ 8
- 10
fop-core/src/test/java/org/apache/fop/complexscripts/scripts/arabic/ArabicWordFormsTestCase.java 查看文件

import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;


import org.apache.commons.io.IOUtils;

import org.apache.fop.complexscripts.fonts.GlyphPositioningTable; import org.apache.fop.complexscripts.fonts.GlyphPositioningTable;
import org.apache.fop.complexscripts.fonts.GlyphSubstitutionTable; import org.apache.fop.complexscripts.fonts.GlyphSubstitutionTable;
import org.apache.fop.complexscripts.fonts.ttx.TTXFile; import org.apache.fop.complexscripts.fonts.ttx.TTXFile;
FileInputStream fis = null; FileInputStream fis = null;
try { try {
fis = new FileInputStream(dpn); fis = new FileInputStream(dpn);
if (fis != null) {
ObjectInputStream ois = new ObjectInputStream(fis);
List<Object[]> data = (List<Object[]>) ois.readObject();
if (data != null) {
processWordForms(data);
}
ois.close();
ObjectInputStream ois = new ObjectInputStream(fis);
List<Object[]> data = (List<Object[]>) ois.readObject();
if (data != null) {
processWordForms(data);
} }
ois.close();
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
throw new RuntimeException(e.getMessage(), e); throw new RuntimeException(e.getMessage(), e);
} catch (IOException e) { } catch (IOException e) {
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(e.getMessage(), e); throw new RuntimeException(e.getMessage(), e);
} finally { } finally {
if (fis != null) {
try { fis.close(); } catch (Exception e) { /* NOP */ }
}
IOUtils.closeQuietly(fis);
} }
} }



+ 17
- 23
fop-core/src/test/java/org/apache/fop/complexscripts/scripts/arabic/GenerateArabicTestData.java 查看文件

import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;


import org.apache.commons.io.IOUtils;

import org.apache.fop.complexscripts.fonts.GlyphPositioningTable; import org.apache.fop.complexscripts.fonts.GlyphPositioningTable;
import org.apache.fop.complexscripts.fonts.GlyphSubstitutionTable; import org.apache.fop.complexscripts.fonts.GlyphSubstitutionTable;
import org.apache.fop.complexscripts.fonts.ttx.TTXFile; import org.apache.fop.complexscripts.fonts.ttx.TTXFile;
FileInputStream fis = null; FileInputStream fis = null;
try { try {
fis = new FileInputStream(spn); fis = new FileInputStream(spn);
if (fis != null) {
LineNumberReader lr = new LineNumberReader(new InputStreamReader(fis, Charset.forName("UTF-8")));
String wf;
while ((wf = lr.readLine()) != null) {
GlyphSequence igs = tf.mapCharsToGlyphs(wf);
GlyphSequence ogs = gsub.substitute(igs, script, language);
int[][] paa = new int [ ogs.getGlyphCount() ] [ 4 ];
if (!gpos.position(ogs, script, language, 1000, widths, paa)) {
paa = null;
}
data.add(new Object[] { wf, getGlyphs(igs), getGlyphs(ogs), paa });
LineNumberReader lr = new LineNumberReader(new InputStreamReader(fis, Charset.forName("UTF-8")));
String wf;
while ((wf = lr.readLine()) != null) {
GlyphSequence igs = tf.mapCharsToGlyphs(wf);
GlyphSequence ogs = gsub.substitute(igs, script, language);
int[][] paa = new int [ ogs.getGlyphCount() ] [ 4 ];
if (!gpos.position(ogs, script, language, 1000, widths, paa)) {
paa = null;
} }
lr.close();
data.add(new Object[] { wf, getGlyphs(igs), getGlyphs(ogs), paa });
} }
lr.close();
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
throw new RuntimeException(e.getMessage(), e); throw new RuntimeException(e.getMessage(), e);
} catch (IOException e) { } catch (IOException e) {
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(e.getMessage(), e); throw new RuntimeException(e.getMessage(), e);
} finally { } finally {
if (fis != null) {
try { fis.close(); } catch (Exception e) { /* NOP */ }
}
IOUtils.closeQuietly(fis);
} }
} else { } else {
assert gsub != null; assert gsub != null;
FileOutputStream fos = null; FileOutputStream fos = null;
try { try {
fos = new FileOutputStream(dpn); fos = new FileOutputStream(dpn);
if (fos != null) {
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(data);
oos.close();
}
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(data);
oos.close();
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
throw new RuntimeException(e.getMessage(), e); throw new RuntimeException(e.getMessage(), e);
} catch (IOException e) { } catch (IOException e) {
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(e.getMessage(), e); throw new RuntimeException(e.getMessage(), e);
} finally { } finally {
if (fos != null) {
try { fos.close(); } catch (Exception e) { /* NOP */ }
}
IOUtils.closeQuietly(fos);
} }
} }



+ 7
- 0
fop-core/src/test/java/org/apache/fop/fonts/CIDFullTestCase.java 查看文件

assertEquals(cidFull.mapChar(9, c), (char) 9); assertEquals(cidFull.mapChar(9, c), (char) 9);
} }


@Test
public void testMapCodePoint() {
// index 9 exists
char c = 'a';
assertEquals(cidFull.mapCodePoint(9, c), (char) 9);
}

@Test @Test
public void testGetGlyphs() { public void testGetGlyphs() {
Map<Integer, Integer> fontGlyphs = cidFull.getGlyphs(); Map<Integer, Integer> fontGlyphs = cidFull.getGlyphs();

+ 198
- 0
fop-core/src/test/java/org/apache/fop/fonts/CIDSubsetTestCase.java 查看文件

/*
* 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 java.util.Arrays;
import java.util.BitSet;
import java.util.HashMap;
import java.util.Map;

import org.junit.Before;
import org.junit.Test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import org.apache.fop.util.CharUtilities;

public class CIDSubsetTestCase {

/** The surrogate pair is expected to be in the end of the string. Change it carefully. */
private static final String TEXT = "Hello CIDSubset \uD83D\uDCA9";

private CIDSubset cidSub;
private BitSet bs;
private int[] codepoints;
private int[] widths;
private Map<Integer, Integer> glyphToSelector;
private Map<Integer, Integer> charToSelector;
private HashMap<Integer, Integer> charToGlyph;

@Before
public void setup() {
bs = new BitSet();
glyphToSelector = new HashMap<Integer, Integer>();
charToSelector = new HashMap<Integer, Integer>();
charToGlyph = new HashMap<Integer, Integer>();

codepoints = new int[TEXT.length() - 1]; // skip one char because of surrogate pair
bs.set(0); // .notdef

int glyphIdx = 0;
for (int i = 0; i < TEXT.length(); i++) {
int cp = TEXT.codePointAt(i);
i += CharUtilities.incrementIfNonBMP(cp);

codepoints[glyphIdx] = cp;

glyphIdx++;

// Assign glyphIdx for each character
// glyphIndex 0 is reserved for .notdef
if (!charToGlyph.containsKey(cp)) {
charToGlyph.put(cp, glyphIdx);
bs.set(glyphIdx);
}
}

// fill widths up to max glyph index + 1 for .notdef
widths = new int[glyphIdx + 1];
for (int i = 0; i < widths.length; i++) {
widths[i] = 100 * i;
}

MultiByteFont mbFont = mock(MultiByteFont.class);
when(mbFont.getGlyphIndices()).thenReturn(bs);
when(mbFont.getWidths()).thenReturn(widths);
cidSub = new CIDSubset(mbFont);

for (int i = 0; i < codepoints.length; i++) {
int codepoint = codepoints[i];
int glyphIndex = charToGlyph.get(codepoint);
int subsetCharSelector = cidSub.mapCodePoint(glyphIndex, codepoint);
glyphToSelector.put(glyphIndex, subsetCharSelector);
charToSelector.put(codepoint, subsetCharSelector);
}
}

@Test
public void testGetOriginalGlyphIndex() {
// index 5 exists
int codepoint = (int) TEXT.charAt(0);
int subsetCharSelector = charToSelector.get(codepoint);
int originalIdx = charToGlyph.get(codepoint);
assertEquals(originalIdx, cidSub.getOriginalGlyphIndex(subsetCharSelector));
}

@Test
public void testGetUnicode() {
int bmpCodepoint = codepoints[5];
int nonBmpCodepoint = codepoints[codepoints.length - 1];

assertEquals(bmpCodepoint, cidSub.getUnicode(charToSelector.get(bmpCodepoint)));
assertEquals(nonBmpCodepoint, cidSub.getUnicode(charToSelector.get(nonBmpCodepoint)));

// not exist
assertEquals(CharUtilities.NOT_A_CHARACTER, cidSub.getUnicode(-1));
}

@Test
public void testMapChar() {
for (Map.Entry<Integer, Integer> entry : glyphToSelector.entrySet()) {
int glyphIndex = entry.getKey();
int subsetCharSelector = entry.getValue();
// the value of codepoint is not relevant for the purpose of this test: safe to take a random value.
int codepoint = 'a';
assertEquals(subsetCharSelector, cidSub.mapChar(glyphIndex, (char) codepoint));
}
}

@Test
public void testMapCodePoint() {
for (Map.Entry<Integer, Integer> entry : glyphToSelector.entrySet()) {
int glyphIndex = entry.getKey();
int subsetCharSelector = entry.getValue();
// the value of codepoint is not relevant for the purpose of this test: safe to take a random value.
int codepoint = 'a';
assertEquals(subsetCharSelector, cidSub.mapCodePoint(glyphIndex, codepoint));
}
}

@Test
public void testGetGlyphs() {
Map<Integer, Integer> fontGlyphs = cidSub.getGlyphs();

for (Integer key : fontGlyphs.keySet()) {
if (key == 0) {
// the entry 0 -> 0 is set in the CIDSubset constructor
assertEquals(0, fontGlyphs.get(key).intValue());
continue;
}
assertEquals(glyphToSelector.get(key), fontGlyphs.get(key));
}

assertEquals(glyphToSelector.size() + 1, fontGlyphs.size());
}

@Test
public void testGetChars() {
char[] chars = cidSub.getChars();
char[] expected = TEXT.toCharArray();

Arrays.sort(chars);
Arrays.sort(expected);

// checks if the returned arrays contains all the expected chars
for (char c : expected) {
assertTrue(Arrays.binarySearch(chars, c) >= 0);
}

// checks if the returned array do not contains unexpected chars
for (char c : chars) {
if (c == CharUtilities.NOT_A_CHARACTER) {
continue;
}
assertTrue(Arrays.binarySearch(expected, c) >= 0);
}
}

@Test
public void testGetNumberOfGlyphs() {
// +1 because of .notdef
assertEquals(glyphToSelector.size() + 1, cidSub.getNumberOfGlyphs());
}

@Test
public void testGetGlyphIndices() {
assertEquals(bs, cidSub.getGlyphIndices());
}

@Test
public void testGetWidths() {
Arrays.sort(widths);

for (int width : cidSub.getWidths()) {
assertTrue(Arrays.binarySearch(widths, width) >= 0);
}
}
}

+ 139
- 0
fop-core/src/test/java/org/apache/fop/fonts/FontSelectorTestCase.java 查看文件

/*
* 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.junit.Before;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import org.apache.fop.datatypes.PercentBaseContext;
import org.apache.fop.fo.Constants;
import org.apache.fop.fo.FOEventHandler;
import org.apache.fop.fo.FOText;
import org.apache.fop.fo.PropertyList;
import org.apache.fop.fo.expr.PropertyException;
import org.apache.fop.fo.properties.CommonFont;
import org.apache.fop.fo.properties.EnumProperty;
import org.apache.fop.fo.properties.FixedLength;
import org.apache.fop.fo.properties.FontFamilyProperty;
import org.apache.fop.fo.properties.NumberProperty;
import org.apache.fop.fo.properties.Property;


public class FontSelectorTestCase {

private static final FontTriplet LATIN_FONT_TRIPLET = new FontTriplet("Verdana", "normal", 400);
private static final FontTriplet EMOJI_FONT_TRIPLET = new FontTriplet("Emoji", "normal", 400);

private FOText foText;
private PercentBaseContext context;
private Font latinFont;
private Font emojiFont;

@Before
public void setUp() throws Exception {
FontTriplet[] fontState = new FontTriplet[] { LATIN_FONT_TRIPLET, EMOJI_FONT_TRIPLET };

foText = mock(FOText.class);
context = mock(PercentBaseContext.class);
FOEventHandler eventHandler = mock(FOEventHandler.class);
FontInfo fontInfo = mock(FontInfo.class);
CommonFont commonFont = makeCommonFont();
latinFont = mock(Font.class, "Latin Font");
emojiFont = mock(Font.class, "Emoji Font");

when(eventHandler.getFontInfo()).thenReturn(fontInfo);
when(foText.getFOEventHandler()).thenReturn(eventHandler);
when(foText.getCommonFont()).thenReturn(commonFont);
when(commonFont.getFontState(fontInfo)).thenReturn(fontState);
when(fontInfo.getFontInstance(eq(LATIN_FONT_TRIPLET), anyInt())).thenReturn(latinFont);
when(fontInfo.getFontInstance(eq(EMOJI_FONT_TRIPLET), anyInt())).thenReturn(emojiFont);
when(latinFont.hasCodePoint(anyInt())).thenAnswer(new LatinFontAnswer());
when(emojiFont.hasCodePoint(anyInt())).thenAnswer(new EmojiFontAnswer());
}

@Test
public void selectFontForCharactersInText() throws Exception {
String latinText = "Hello FontSelector";
String emojiText = "\uD83D\uDE48\uD83D\uDE49\uD83D\uDE4A";
String mixedText = latinText + emojiText;


Font f = FontSelector.selectFontForCharactersInText(latinText, 0, latinText.length(), foText, context);
assertEquals(latinFont, f);

f = FontSelector.selectFontForCharactersInText(emojiText, 0, emojiText.length(), foText, context);
assertEquals(emojiFont, f);

// When the text is mixed the font that can cover most chars should be returned
f = FontSelector.selectFontForCharactersInText(mixedText, 0, mixedText.length(), foText, context);
assertEquals(latinFont, f);

f = FontSelector.selectFontForCharactersInText(mixedText, latinText.length() - 1, mixedText.length(), foText,
context);
assertEquals(emojiFont, f);
}

private static class LatinFontAnswer implements Answer<Boolean> {

@Override
public Boolean answer(InvocationOnMock invocation) throws Throwable {
int codepoint = (Integer) invocation.getArguments()[0];
return codepoint <= 0xFFFF;
}
}

private static class EmojiFontAnswer implements Answer<Boolean> {

@Override
public Boolean answer(InvocationOnMock invocation) throws Throwable {
int codepoint = (Integer) invocation.getArguments()[0];
return codepoint > 0xFFFF;
}
}

private CommonFont makeCommonFont() throws PropertyException {
PropertyList pList = mock(PropertyList.class);

String fontFamilyVal = LATIN_FONT_TRIPLET.getName() + "," + EMOJI_FONT_TRIPLET.getName();
Property fontFamilyProp = new FontFamilyProperty.Maker(Constants.PR_FONT_FAMILY).make(pList, fontFamilyVal,
null);
Property fontWeightProp = EnumProperty.getInstance(Constants.PR_FONT_WEIGHT, "400");
Property fontStyle = EnumProperty.getInstance(Constants.PR_FONT_STYLE, "normal");
Property fontSizeAdjustProp = NumberProperty.getInstance(1);
Property fontSizeProp = FixedLength.getInstance(12);

when(pList.get(Constants.PR_FONT_FAMILY)).thenReturn(fontFamilyProp);
when(pList.get(Constants.PR_FONT_WEIGHT)).thenReturn(fontWeightProp);
when(pList.get(Constants.PR_FONT_STYLE)).thenReturn(fontStyle);
when(pList.get(Constants.PR_FONT_SIZE_ADJUST)).thenReturn(fontSizeAdjustProp);
when(pList.get(Constants.PR_FONT_SIZE)).thenReturn(fontSizeProp);

return CommonFont.getInstance(pList);
}

}

+ 121
- 9
fop-core/src/test/java/org/apache/fop/fonts/truetype/TTFFileTestCase.java 查看文件

import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.List;
import java.util.Map; import java.util.Map;


import org.junit.Test; import org.junit.Test;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;


import org.apache.fop.fonts.CMapSegment;
import org.apache.fop.fonts.truetype.OpenFont.PostScriptVersion; import org.apache.fop.fonts.truetype.OpenFont.PostScriptVersion;


/** /**
protected final TTFFile droidmonoTTFFile; protected final TTFFile droidmonoTTFFile;
/** The FontFileReader for ttfFile (DroidSansMono) */ /** The FontFileReader for ttfFile (DroidSansMono) */
protected final FontFileReader droidmonoReader; protected final FontFileReader droidmonoReader;
/** The truetype font file (AndroidEmoji) fonr non-BMP codepoints */
protected final TTFFile androidEmojiTTFFile;
/** The FontFileReader for ttfFile (AndroidEmoji) */
protected final FontFileReader androidEmojiReader;





/** /**
* @throws IOException exception * @throws IOException exception
*/ */
public TTFFileTestCase() throws IOException { public TTFFileTestCase() throws IOException {
dejavuTTFFile = new TTFFile();
InputStream dejaStream = new FileInputStream("test/resources/fonts/ttf/DejaVuLGCSerif.ttf"); InputStream dejaStream = new FileInputStream("test/resources/fonts/ttf/DejaVuLGCSerif.ttf");
dejavuTTFFile = new TTFFile();
dejavuReader = new FontFileReader(dejaStream); dejavuReader = new FontFileReader(dejaStream);
String dejavuHeader = OFFontLoader.readHeader(dejavuReader); String dejavuHeader = OFFontLoader.readHeader(dejavuReader);
dejavuTTFFile.readFont(dejavuReader, dejavuHeader); dejavuTTFFile.readFont(dejavuReader, dejavuHeader);
dejaStream.close(); dejaStream.close();


InputStream droidStream = new FileInputStream("test/resources/fonts/ttf/DroidSansMono.ttf"); InputStream droidStream = new FileInputStream("test/resources/fonts/ttf/DroidSansMono.ttf");

droidmonoTTFFile = new TTFFile(); droidmonoTTFFile = new TTFFile();
droidmonoReader = new FontFileReader(droidStream); droidmonoReader = new FontFileReader(droidStream);
String droidmonoHeader = OFFontLoader.readHeader(droidmonoReader); String droidmonoHeader = OFFontLoader.readHeader(droidmonoReader);
droidmonoTTFFile.readFont(droidmonoReader, droidmonoHeader); droidmonoTTFFile.readFont(droidmonoReader, droidmonoHeader);
droidStream.close(); droidStream.close();

InputStream emojiStream = new FileInputStream("test/resources/fonts/ttf/AndroidEmoji.ttf");
androidEmojiTTFFile = new TTFFile();
androidEmojiReader = new FontFileReader(emojiStream);
String androidEmojiHeader = OFFontLoader.readHeader(androidEmojiReader);
androidEmojiTTFFile.readFont(androidEmojiReader, androidEmojiHeader);
emojiStream.close();

} }


/** /**
*/ */
@Test @Test
public void testGetAnsiKerning() { public void testGetAnsiKerning() {
Map<Integer, Map<Integer, Integer>> ansiKerning = dejavuTTFFile.getKerning();
Map<Integer, Map<Integer, Integer>> ansiKerning = dejavuTTFFile.getAnsiKerning();
if (ansiKerning.isEmpty()) { if (ansiKerning.isEmpty()) {
fail(); fail();
} }
Integer k1 = ansiKerning.get((int) 'A').get(
(int) 'T');
Integer k1 = ansiKerning.get((int) 'A').get((int) 'T');
assertEquals(dejavuTTFFile.convertTTFUnit2PDFUnit(-112), k1.intValue()); assertEquals(dejavuTTFFile.convertTTFUnit2PDFUnit(-112), k1.intValue());
Integer k2 = ansiKerning.get((int) 'Y').get((int) 'u'); Integer k2 = ansiKerning.get((int) 'Y').get((int) 'u');
assertEquals(dejavuTTFFile.convertTTFUnit2PDFUnit(-178), k2.intValue()); assertEquals(dejavuTTFFile.convertTTFUnit2PDFUnit(-178), k2.intValue());
if (!ansiKerning.isEmpty()) { if (!ansiKerning.isEmpty()) {
fail("DroidSansMono shouldn't have any kerning data."); fail("DroidSansMono shouldn't have any kerning data.");
} }

// AndroidEmoji doens't have kerning
ansiKerning = androidEmojiTTFFile.getAnsiKerning();
if (!ansiKerning.isEmpty()) {
fail("AndroidEmoji shouldn't have any kerning data.");
}
} }


/** /**
// height of "H" = 1462 // height of "H" = 1462
assertEquals(droidmonoTTFFile.convertTTFUnit2PDFUnit(1462), assertEquals(droidmonoTTFFile.convertTTFUnit2PDFUnit(1462),
droidmonoTTFFile.getCapHeight()); droidmonoTTFFile.getCapHeight());
// AndroidEmoji doesn't have a PCLT table either
// height of "H" = 1462
assertEquals(androidEmojiTTFFile.convertTTFUnit2PDFUnit(1462),
androidEmojiTTFFile.getCapHeight());
} }


/** /**
public void testGetCharSetName() { public void testGetCharSetName() {
assertTrue("WinAnsiEncoding".equals(dejavuTTFFile.getCharSetName())); assertTrue("WinAnsiEncoding".equals(dejavuTTFFile.getCharSetName()));
assertTrue("WinAnsiEncoding".equals(droidmonoTTFFile.getCharSetName())); assertTrue("WinAnsiEncoding".equals(droidmonoTTFFile.getCharSetName()));
//Even though this pass I'm not sure whether is what we want
assertTrue("WinAnsiEncoding".equals(androidEmojiTTFFile.getCharSetName()));
} }


/** /**
for (int i = 0; i < 255; i++) { for (int i = 0; i < 255; i++) {
assertEquals(charWidth, droidmonoTTFFile.getCharWidth(i)); assertEquals(charWidth, droidmonoTTFFile.getCharWidth(i));
} }

// All the glyphs should be the same width in EmojiAndroid (mono-spaced)
charWidth = androidEmojiTTFFile.convertTTFUnit2PDFUnit(2600);
for (int i = 0; i < 255; i++) {
assertEquals(charWidth, androidEmojiTTFFile.getCharWidth(i));
}
} }


/** /**
* TODO: add implementation to this test * TODO: add implementation to this test
*/ */
@Test
public void testGetCMaps() { public void testGetCMaps() {
List<CMapSegment> cmaps = androidEmojiTTFFile.getCMaps();

for (CMapSegment seg : cmaps) {
System.out.println(seg.getUnicodeStart() + "-" + seg.getUnicodeEnd() + " -> " + seg.getGlyphStartIndex());
}
} }


/** /**
for (String name : droidmonoTTFFile.getFamilyNames()) { for (String name : droidmonoTTFFile.getFamilyNames()) {
assertEquals("Droid Sans Mono", name); assertEquals("Droid Sans Mono", name);
} }
assertEquals(1, androidEmojiTTFFile.getFamilyNames().size());
for (String name : androidEmojiTTFFile.getFamilyNames()) {
assertEquals("Android Emoji", name);
}
} }


/** /**
// Not really sure how to test this intelligently // Not really sure how to test this intelligently
assertEquals(0, dejavuTTFFile.getFirstChar()); assertEquals(0, dejavuTTFFile.getFirstChar());
assertEquals(0, droidmonoTTFFile.getFirstChar()); assertEquals(0, droidmonoTTFFile.getFirstChar());
assertEquals(0, androidEmojiTTFFile.getFirstChar());
} }


/** /**
assertEquals(32, flags & 32); assertEquals(32, flags & 32);
assertEquals(2, flags & 2); assertEquals(2, flags & 2);
assertEquals(1, flags & 1); assertEquals(1, flags & 1);
/*
* Android Emoji flags are:
* italic angle = 0
* fixed pitch = 0
* has serifs = true (default value; this font doesn't have a PCLT table)
*/
flags = androidEmojiTTFFile.getFlags();
assertEquals(0, flags & 64);
assertEquals(32, flags & 32);
assertEquals(0, flags & 2);
assertEquals(1, flags & 1);
} }


/** /**
assertEquals(droidmonoTTFFile.convertTTFUnit2PDFUnit(-555), bBox[1]); assertEquals(droidmonoTTFFile.convertTTFUnit2PDFUnit(-555), bBox[1]);
assertEquals(droidmonoTTFFile.convertTTFUnit2PDFUnit(1315), bBox[2]); assertEquals(droidmonoTTFFile.convertTTFUnit2PDFUnit(1315), bBox[2]);
assertEquals(droidmonoTTFFile.convertTTFUnit2PDFUnit(2163), bBox[3]); assertEquals(droidmonoTTFFile.convertTTFUnit2PDFUnit(2163), bBox[3]);

/*
* The head table has the following values (DroidSansMono):
* xMin = -50, yMin = -733, xMax = 2550, yMax = 2181
*/
bBox = androidEmojiTTFFile.getFontBBox();
assertEquals(androidEmojiTTFFile.convertTTFUnit2PDFUnit(-50), bBox[0]);
assertEquals(androidEmojiTTFFile.convertTTFUnit2PDFUnit(-733), bBox[1]);
assertEquals(androidEmojiTTFFile.convertTTFUnit2PDFUnit(2550), bBox[2]);
assertEquals(androidEmojiTTFFile.convertTTFUnit2PDFUnit(2181), bBox[3]);
} }


/** /**
public void testGetFullName() { public void testGetFullName() {
assertEquals("DejaVu LGC Serif", dejavuTTFFile.getFullName()); assertEquals("DejaVu LGC Serif", dejavuTTFFile.getFullName());
assertEquals("Droid Sans Mono", droidmonoTTFFile.getFullName()); assertEquals("Droid Sans Mono", droidmonoTTFFile.getFullName());
assertEquals("Android Emoji", androidEmojiTTFFile.getFullName());
} }


/** /**
public void testGetGlyphName() { public void testGetGlyphName() {
assertEquals("H", dejavuTTFFile.getGlyphName(43)); assertEquals("H", dejavuTTFFile.getGlyphName(43));
assertEquals("H", droidmonoTTFFile.getGlyphName(43)); assertEquals("H", droidmonoTTFFile.getGlyphName(43));
assertEquals("smileface", androidEmojiTTFFile.getGlyphName(64));
} }


/** /**
public void testGetItalicAngle() { public void testGetItalicAngle() {
assertEquals("0", dejavuTTFFile.getItalicAngle()); assertEquals("0", dejavuTTFFile.getItalicAngle());
assertEquals("0", droidmonoTTFFile.getItalicAngle()); assertEquals("0", droidmonoTTFFile.getItalicAngle());
assertEquals("0", androidEmojiTTFFile.getItalicAngle());
} }


/** /**
if (!kerning.isEmpty()) { if (!kerning.isEmpty()) {
fail("DroidSansMono shouldn't have any kerning data"); fail("DroidSansMono shouldn't have any kerning data");
} }

// AndroidEmoji doens't have kerning
kerning = androidEmojiTTFFile.getKerning();
if (!kerning.isEmpty()) {
fail("AndroidEmoji shouldn't have any kerning data.");
}
} }


/** /**
public void testLastChar() { public void testLastChar() {
assertEquals(0xff, dejavuTTFFile.getLastChar()); assertEquals(0xff, dejavuTTFFile.getLastChar());
assertEquals(0xff, droidmonoTTFFile.getLastChar()); assertEquals(0xff, droidmonoTTFFile.getLastChar());
assertEquals(0xae, androidEmojiTTFFile.getLastChar()); // Last ASCII mapped char is REGISTERED SIGN
} }


/** /**
// Curiously the same value // Curiously the same value
assertEquals(droidmonoTTFFile.convertTTFUnit2PDFUnit(1556), assertEquals(droidmonoTTFFile.convertTTFUnit2PDFUnit(1556),
droidmonoTTFFile.getLowerCaseAscent()); droidmonoTTFFile.getLowerCaseAscent());
//TODO: Nedd to be fixed?
// 0 because the font miss letter glyph that are used in this method to guess the ascender:
// OpenFont.guessVerticalMetricsFromGlyphBBox
assertEquals(androidEmojiTTFFile.convertTTFUnit2PDFUnit(0),
androidEmojiTTFFile.getLowerCaseAscent());
} }


/** /**
public void testGetPostScriptName() { public void testGetPostScriptName() {
assertEquals(PostScriptVersion.V2, dejavuTTFFile.getPostScriptVersion()); assertEquals(PostScriptVersion.V2, dejavuTTFFile.getPostScriptVersion());
assertEquals(PostScriptVersion.V2, droidmonoTTFFile.getPostScriptVersion()); assertEquals(PostScriptVersion.V2, droidmonoTTFFile.getPostScriptVersion());
assertEquals(PostScriptVersion.V2, androidEmojiTTFFile.getPostScriptVersion());
} }


/** /**
// Undefined // Undefined
assertEquals("0", dejavuTTFFile.getStemV()); assertEquals("0", dejavuTTFFile.getStemV());
assertEquals("0", droidmonoTTFFile.getStemV()); assertEquals("0", droidmonoTTFFile.getStemV());
assertEquals("0", androidEmojiTTFFile.getStemV());
} }


/** /**
public void testGetSubFamilyName() { public void testGetSubFamilyName() {
assertEquals("Book", dejavuTTFFile.getSubFamilyName()); assertEquals("Book", dejavuTTFFile.getSubFamilyName());
assertEquals("Regular", droidmonoTTFFile.getSubFamilyName()); assertEquals("Regular", droidmonoTTFFile.getSubFamilyName());
assertEquals("Regular", androidEmojiTTFFile.getSubFamilyName());
} }


/** /**
// Retrieved from OS/2 table // Retrieved from OS/2 table
assertEquals(400, dejavuTTFFile.getWeightClass()); assertEquals(400, dejavuTTFFile.getWeightClass());
assertEquals(400, droidmonoTTFFile.getWeightClass()); assertEquals(400, droidmonoTTFFile.getWeightClass());
assertEquals(400, androidEmojiTTFFile.getWeightClass());
} }


/** /**
assertEquals(dejavuTTFFile.convertTTFUnit2PDFUnit(1479), widths[36]); assertEquals(dejavuTTFFile.convertTTFUnit2PDFUnit(1479), widths[36]);
// using the width of '|' index = 95 // using the width of '|' index = 95
assertEquals(dejavuTTFFile.convertTTFUnit2PDFUnit(690), widths[95]); assertEquals(dejavuTTFFile.convertTTFUnit2PDFUnit(690), widths[95]);
widths = droidmonoTTFFile.getWidths();
// DroidSansMono should have all widths the same size (mono-spaced) // DroidSansMono should have all widths the same size (mono-spaced)
int width = droidmonoTTFFile.convertTTFUnit2PDFUnit(1229);
for (int i = 0; i < 255; i++) {
assertEquals(width, widths[i]);
widths = droidmonoTTFFile.getWidths();
int charWidth = droidmonoTTFFile.convertTTFUnit2PDFUnit(1229);
for (OpenFont.UnicodeMapping unicodeMapping : droidmonoTTFFile.unicodeMappings) {
assertEquals(charWidth, widths[unicodeMapping.getGlyphIndex()]);
}

// All the glyphs should be the same width in EmojiAndroid (mono-spaced)
charWidth = androidEmojiTTFFile.convertTTFUnit2PDFUnit(2600);
widths = androidEmojiTTFFile.getWidths();
for (OpenFont.UnicodeMapping unicodeMapping : androidEmojiTTFFile.unicodeMappings) {
assertEquals(charWidth, widths[unicodeMapping.getGlyphIndex()]);
} }
} }


@Test
public void textUnicodeCoverage() {
int nonBMPcount = 0;
for (OpenFont.UnicodeMapping unicodeMapping : droidmonoTTFFile.unicodeMappings) {
nonBMPcount += unicodeMapping.getUnicodeIndex() > 0xFFFF ? 1 : 0;
}
assertEquals("The font DroidSansMono is supposed to have only BMP codepoints", 0, nonBMPcount);

nonBMPcount = 0;
for (OpenFont.UnicodeMapping unicodeMapping : androidEmojiTTFFile.unicodeMappings) {
nonBMPcount += unicodeMapping.getUnicodeIndex() > 0xFFFF ? 1 : 0;
}

assertTrue("The font AndroidEmoji is supposed to have non-BMP codepoints", 0 != nonBMPcount);
}

/** /**
* Test getXHeight() - There are several paths to test: * Test getXHeight() - There are several paths to test:
* 1) The PCLT table (if available) * 1) The PCLT table (if available)
// Neither DejaVu nor DroidSansMono are a compact format font // Neither DejaVu nor DroidSansMono are a compact format font
assertEquals(false, dejavuTTFFile.isCFF()); assertEquals(false, dejavuTTFFile.isCFF());
assertEquals(false, droidmonoTTFFile.isCFF()); assertEquals(false, droidmonoTTFFile.isCFF());
assertEquals(false, androidEmojiTTFFile.isCFF());
} }


/** /**
// Dejavu and DroidSansMono are both embeddable // Dejavu and DroidSansMono are both embeddable
assertEquals(true, dejavuTTFFile.isEmbeddable()); assertEquals(true, dejavuTTFFile.isEmbeddable());
assertEquals(true, droidmonoTTFFile.isEmbeddable()); assertEquals(true, droidmonoTTFFile.isEmbeddable());
assertEquals(true, androidEmojiTTFFile.isEmbeddable());
} }


/** Underline position and thickness. */ /** Underline position and thickness. */
assertEquals(43, dejavuTTFFile.getUnderlineThickness()); assertEquals(43, dejavuTTFFile.getUnderlineThickness());
assertEquals(-75, droidmonoTTFFile.getUnderlinePosition()); assertEquals(-75, droidmonoTTFFile.getUnderlinePosition());
assertEquals(49, droidmonoTTFFile.getUnderlineThickness()); assertEquals(49, droidmonoTTFFile.getUnderlineThickness());
assertEquals(-75, androidEmojiTTFFile.getUnderlinePosition());
assertEquals(49, androidEmojiTTFFile.getUnderlineThickness());
} }


/** Strikeout position and thickness. */ /** Strikeout position and thickness. */
assertEquals(49, dejavuTTFFile.getStrikeoutThickness()); assertEquals(49, dejavuTTFFile.getStrikeoutThickness());
assertEquals(243, droidmonoTTFFile.getStrikeoutPosition()); assertEquals(243, droidmonoTTFFile.getStrikeoutPosition());
assertEquals(49, droidmonoTTFFile.getStrikeoutThickness()); assertEquals(49, droidmonoTTFFile.getStrikeoutThickness());
assertEquals(122, androidEmojiTTFFile.getStrikeoutPosition());
assertEquals(24, androidEmojiTTFFile.getStrikeoutThickness());
} }


/** /**

+ 119
- 0
fop-core/src/test/java/org/apache/fop/render/java2d/Java2DUtilTestCase.java 查看文件

/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/* $Id$ */

package org.apache.fop.render.java2d;

import java.awt.Graphics2D;
import java.awt.font.FontRenderContext;
import java.util.HashMap;
import java.util.Map;

import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import org.apache.fop.fonts.Font;
import org.apache.fop.fonts.FontInfo;
import org.apache.fop.fonts.MultiByteFont;
import org.apache.fop.fonts.SingleByteFont;
import org.apache.fop.fonts.Typeface;
import org.apache.fop.util.CharUtilities;


public class Java2DUtilTestCase {

private static final String MULTI_BYTE_FONT_NAME = "multi";
private static final String SINGLE_BYTE_FONT_NAME = "single";

private static final String TEXT = "Hello World!\uD83D\uDCA9";
private static final String EXPECTED_TEXT_SINGLE = "Hello World!#";
private static final String EXPECTED_TEXT_MULTI = "Hello World!\uD83D\uDCA9";

@Test
public void createGlyphVectorMultiByte() throws Exception {
Graphics2D g2d = mock(Graphics2D.class);
java.awt.Font awtFont = mock(java.awt.Font.class);
Font font = makeFont(MULTI_BYTE_FONT_NAME);
FontInfo fontInfo = makeFontInfo();

int[] codepoints = new int[EXPECTED_TEXT_MULTI.codePointCount(0, EXPECTED_TEXT_MULTI.length())];

int i = 0;
for (int cp : CharUtilities.codepointsIter(EXPECTED_TEXT_MULTI)) {
codepoints[i++] = cp;
}

when(g2d.getFont()).thenReturn(awtFont);

Java2DUtil.createGlyphVector(TEXT, g2d, font, fontInfo);
verify(awtFont).createGlyphVector(any(FontRenderContext.class), eq(codepoints));
}

@Test
public void createGlyphVectorSingleByte() throws Exception {
Graphics2D g2d = mock(Graphics2D.class);
java.awt.Font awtFont = mock(java.awt.Font.class);
Font font = makeFont(SINGLE_BYTE_FONT_NAME);
FontInfo fontInfo = makeFontInfo();

when(g2d.getFont()).thenReturn(awtFont);

Java2DUtil.createGlyphVector(TEXT, g2d, font, fontInfo);
verify(awtFont).createGlyphVector(any(FontRenderContext.class), eq(EXPECTED_TEXT_SINGLE));
}


private FontInfo makeFontInfo() {
Map<String, Typeface> fonts = new HashMap<String, Typeface>();

SingleByteFont singleByteFont = mock(SingleByteFont.class);
MultiByteFont multiByteFont = mock(MultiByteFont.class);
FontInfo fontInfo = mock(FontInfo.class);

fonts.put(MULTI_BYTE_FONT_NAME, multiByteFont);
fonts.put(SINGLE_BYTE_FONT_NAME, singleByteFont);

when(multiByteFont.findGlyphIndex(anyInt())).thenAnswer(new FindGlyphIndexAnswer());
when(fontInfo.getFonts()).thenReturn(fonts);

return fontInfo;
}

private Font makeFont(String fontName) {
Font font = mock(Font.class);
when(font.getFontName()).thenReturn(fontName);
return font;
}


private static class FindGlyphIndexAnswer implements Answer<Integer> {

@Override
public Integer answer(InvocationOnMock invocation) throws Throwable {
return (Integer) invocation.getArguments()[0];
}
}
}

+ 55
- 31
fop-core/src/test/java/org/apache/fop/render/pdf/PDFEncodingTestCase.java 查看文件



package org.apache.fop.render.pdf; package org.apache.fop.render.pdf;




import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.StringTokenizer;


import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;


import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;


import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper;

import org.apache.fop.apps.FOUserAgent; import org.apache.fop.apps.FOUserAgent;



/** Test that characters are correctly encoded in a generated PDF file */ /** Test that characters are correctly encoded in a generated PDF file */
public class PDFEncodingTestCase extends BasePDFTest { public class PDFEncodingTestCase extends BasePDFTest {
private File foBaseDir = new File("test/xml/pdf-encoding"); private File foBaseDir = new File("test/xml/pdf-encoding");
*/ */
final String[] testPatterns = { final String[] testPatterns = {
TEST_MARKER + "1", "Standard", TEST_MARKER + "1", "Standard",
TEST_MARKER + "2", "XX_\\351_XX",
TEST_MARKER + "3", "XX_\\342\\352\\356\\364\\373_XX"
TEST_MARKER + "2", "XX_é_XX",
TEST_MARKER + "3", "XX_âêîôû_XX"
}; };


runTest("test-standard-font.fo", testPatterns); runTest("test-standard-font.fo", testPatterns);
} }


/** /**
* TODO test disabled for now, fails due (probably) do different PDF
* encoding when custom font is used.
* TODO This should be tested using PDFBox. If PDFBox can extract the text correctly,
* everything is fine. The tests here are too unstable.
* Test encoding with a Custom Font using BMP characters.
*
* NB: The Gladiator font do not contain '_' Glyph
* *
* @throws Exception * @throws Exception
* checkstyle wants a comment here, even a silly one * checkstyle wants a comment here, even a silly one
*/ */
@Ignore("This should be tested using PDFBox. If PDFBox can extract the text correctly,"
+ "everything is fine. The tests here are too unstable.")
@Test @Test
public void testPDFEncodingWithCustomFont() throws Exception { public void testPDFEncodingWithCustomFont() throws Exception {


* The following array is used to look for these patterns * The following array is used to look for these patterns
*/ */
final String[] testPatterns = { final String[] testPatterns = {
TEST_MARKER + "1", "(Gladiator)",
TEST_MARKER + "2", "XX_\\351_XX",
TEST_MARKER + "3", "XX_\\342\\352\\356\\364\\373_XX"
TEST_MARKER + "1", "Gladiator",
TEST_MARKER + "2", "XX_é_XX",
TEST_MARKER + "3", "XX_âêîôû_XX"
}; };


runTest("test-custom-font.fo", testPatterns); runTest("test-custom-font.fo", testPatterns);
} }


/**
* Test encoding with a Custom Font using non-BMP characters
*
* @throws Exception
* checkstyle wants a comment here, even a silly one
*/
@Test
public void testPDFEncodingWithNonBMPFont() throws Exception {

final String[] testPatterns = {
TEST_MARKER + "1", "AndroidEmoji",
TEST_MARKER + "2", "\uD800\uDF00",
};

runTest("test-custom-non-bmp-font.fo", testPatterns);
}

/** Test encoding using specified input file and test patterns array */ /** Test encoding using specified input file and test patterns array */
private void runTest(String inputFile, String[] testPatterns) private void runTest(String inputFile, String[] testPatterns)
throws Exception { throws Exception {
private void checkEncoding(byte[] pdf, String[] testPattern) private void checkEncoding(byte[] pdf, String[] testPattern)
throws IOException { throws IOException {


String s = extractTextFromPDF(pdf);

int markersFound = 0; int markersFound = 0;
final String input = new String(pdf);
int pos = 0;
if ((pos = input.indexOf(TEST_MARKER)) >= 0) {
final StringTokenizer tk = new StringTokenizer(
input.substring(pos), "\n");

while (tk.hasMoreTokens()) {
final String line = tk.nextToken();
if (line.indexOf(TEST_MARKER) >= 0) {
markersFound++;
for (int i = 0; i < testPattern.length; i += 2) {
if (line.indexOf(testPattern[i]) >= 0) {
final String ref = testPattern[i + 1];
final boolean patternFound = line.indexOf(ref) >= 0;
assertTrue("line containing '" + testPattern[i]
+ "' must contain '" + ref, patternFound);
}
}
for (String line : s.split("\n")) {
if (!line.contains(TEST_MARKER)) {
continue;
}

markersFound++;

for (int i = 0; i < testPattern.length; i++) {
String marker = testPattern[i];
String pattern = testPattern[++i];

if (!line.contains(marker)) {
continue;
} }

String msg = String.format("line containing '%s' must contain '%s'", marker, pattern);
assertTrue(msg, line.contains(pattern));
} }
} }


assertEquals(nMarkers + " " + TEST_MARKER + " markers must be found", assertEquals(nMarkers + " " + TEST_MARKER + " markers must be found",
nMarkers, markersFound); nMarkers, markersFound);
} }

private static String extractTextFromPDF(byte[] pdfContent) throws IOException {
PDFTextStripper pdfStripper = new PDFTextStripper();
PDDocument pdDoc = PDDocument.load(pdfContent);
return pdfStripper.getText(pdDoc);
}
} }

+ 57
- 17
fop-core/src/test/java/org/apache/fop/render/pdf/PDFPainterTestCase.java 查看文件

import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamResult;


import org.junit.Test; import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;


import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.endsWith; import static org.mockito.Matchers.endsWith;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import org.apache.fop.render.intermediate.IFException; import org.apache.fop.render.intermediate.IFException;
import org.apache.fop.traits.BorderProps; import org.apache.fop.traits.BorderProps;


import junit.framework.Assert;

public class PDFPainterTestCase { public class PDFPainterTestCase {


private FOUserAgent foUserAgent; private FOUserAgent foUserAgent;
pdfDocumentHandler.getContext().setPageNumber(3); pdfDocumentHandler.getContext().setPageNumber(3);
MyPDFPainter pdfPainter = new MyPDFPainter(pdfDocumentHandler, null); MyPDFPainter pdfPainter = new MyPDFPainter(pdfDocumentHandler, null);
pdfPainter.drawImage("test/resources/images/cmyk.jpg", new Rectangle()); pdfPainter.drawImage("test/resources/images/cmyk.jpg", new Rectangle());
Assert.assertEquals(pdfPainter.renderingContext.getHints().get("page-number"), 3);
assertEquals(pdfPainter.renderingContext.getHints().get("page-number"), 3);
} }


class MyPDFPainter extends PDFPainter { class MyPDFPainter extends PDFPainter {


@Test @Test
public void testSimulateStyle() throws IFException { public void testSimulateStyle() throws IFException {
final StringBuilder sb = new StringBuilder();
pdfDocumentHandler = makePDFDocumentHandler(sb);

FontInfo fi = new FontInfo();
fi.addFontProperties("f1", new FontTriplet("a", "italic", 700));
MultiByteFont font = new MultiByteFont(null, null);
font.setSimulateStyle(true);
fi.addMetrics("f1", font);
pdfDocumentHandler.setFontInfo(fi);
MyPDFPainter pdfPainter = new MyPDFPainter(pdfDocumentHandler, null);
pdfPainter.setFont("a", "italic", 700, null, 12, null);
pdfPainter.drawText(0, 0, 0, 0, null, "test");

assertEquals(sb.toString(), "BT\n/f1 0.012 Tf\n1 0 0.3333 -1 0 0 Tm [<0000000000000000>] TJ\n");
verify(pdfContentGenerator).add("q\n");
verify(pdfContentGenerator).add("2 Tr 0.31543 w\n");
verify(pdfContentGenerator).add("Q\n");
}

@Test
public void testDrawTextWithMultiByteFont() throws IFException {
StringBuilder output = new StringBuilder();
PDFDocumentHandler pdfDocumentHandler = makePDFDocumentHandler(output);
//0x48 0x65 0x6C 0x6C 0x6F 0x20 0x4D 0x6F 0x63 0x6B 0x21 0x1F4A9
String text = "Hello Mock!\uD83D\uDCA9";
String expectedHex = "00480065006C006C006F0020004D006F0063006B002101F4A9";

MultiByteFont font = spy(new MultiByteFont(null, null));
when(font.mapCodePoint(anyInt())).thenAnswer(new FontMapCodepointAnswer());

FontInfo fi = new FontInfo();
fi.addFontProperties("f1", new FontTriplet("a", "normal", 400));
fi.addMetrics("f1", font);
pdfDocumentHandler.setFontInfo(fi);

MyPDFPainter pdfPainter = new MyPDFPainter(pdfDocumentHandler, null);
pdfPainter.setFont("a", "normal", 400, null, 12, null);
pdfPainter.drawText(0, 0, 0, 0, null, text);

assertEquals("BT\n/f1 0.012 Tf\n1 0 0 -1 0 0 Tm [<" + expectedHex + ">] TJ\n", output.toString());
}

private PDFDocumentHandler makePDFDocumentHandler(final StringBuilder sb) throws IFException {
FopFactory fopFactory = FopFactory.newInstance(new File(".").toURI()); FopFactory fopFactory = FopFactory.newInstance(new File(".").toURI());
foUserAgent = fopFactory.newFOUserAgent(); foUserAgent = fopFactory.newFOUserAgent();
mockPDFContentGenerator(); mockPDFContentGenerator();
final StringBuilder sb = new StringBuilder();
PDFTextUtil pdfTextUtil = new PDFTextUtil() { PDFTextUtil pdfTextUtil = new PDFTextUtil() {
protected void write(String code) { protected void write(String code) {
sb.append(code); sb.append(code);
pdfDocumentHandler.setResult(new StreamResult(new ByteArrayOutputStream())); pdfDocumentHandler.setResult(new StreamResult(new ByteArrayOutputStream()));
pdfDocumentHandler.startDocument(); pdfDocumentHandler.startDocument();
pdfDocumentHandler.startPage(0, "", "", new Dimension()); pdfDocumentHandler.startPage(0, "", "", new Dimension());
FontInfo fi = new FontInfo();
fi.addFontProperties("f1", new FontTriplet("a", "italic", 700));
MultiByteFont font = new MultiByteFont(null, null);
font.setSimulateStyle(true);
fi.addMetrics("f1", font);
pdfDocumentHandler.setFontInfo(fi);
MyPDFPainter pdfPainter = new MyPDFPainter(pdfDocumentHandler, null);
pdfPainter.setFont("a", "italic", 700, null, 12, null);
pdfPainter.drawText(0, 0, 0, 0, null, "test");
return pdfDocumentHandler;
}


Assert.assertEquals(sb.toString(), "BT\n/f1 0.012 Tf\n1 0 0.3333 -1 0 0 Tm [<0000000000000000>] TJ\n");
verify(pdfContentGenerator).add("q\n");
verify(pdfContentGenerator).add("2 Tr 0.31543 w\n");
verify(pdfContentGenerator).add("Q\n");
private static class FontMapCodepointAnswer implements Answer<Integer> {

@Override
public Integer answer(InvocationOnMock invocation) throws Throwable {
return (Integer) invocation.getArguments()[0];
}
} }
} }

+ 12
- 4
fop-core/src/test/java/org/apache/fop/render/ps/PSPainterTestCase.java 查看文件

import org.apache.fop.render.intermediate.IFException; import org.apache.fop.render.intermediate.IFException;
import org.apache.fop.render.intermediate.IFState; import org.apache.fop.render.intermediate.IFState;
import org.apache.fop.traits.BorderProps; import org.apache.fop.traits.BorderProps;
import org.apache.fop.util.CharUtilities;


public class PSPainterTestCase { public class PSPainterTestCase {


} }


@Test @Test
public void testDrawText() {
public void testDrawText() throws IOException {
int fontSize = 12000; int fontSize = 12000;
String fontName = "MockFont"; String fontName = "MockFont";
PSGenerator psGenerator = mock(PSGenerator.class); PSGenerator psGenerator = mock(PSGenerator.class);
double yAsDouble = (y - dp[0][1]) / 1000.0; double yAsDouble = (y - dp[0][1]) / 1000.0;
when(psGenerator.formatDouble(xAsDouble)).thenReturn("100.100"); when(psGenerator.formatDouble(xAsDouble)).thenReturn("100.100");
when(psGenerator.formatDouble(yAsDouble)).thenReturn("99.900"); when(psGenerator.formatDouble(yAsDouble)).thenReturn("99.900");
String text = "Hello Mock!";

//0x48 0x65 0x6C 0x6C 0x6F 0x20 0x4D 0x6F 0x63 0x6B 0x21 0x1F4A9
String text = "Hello Mock!\uD83D\uDCA9";

for (int cp : CharUtilities.codepointsIter(text)) {
when(font.mapCodePoint(cp)).thenReturn(cp);
}

try { try {
psPainter.drawText(x, y, letterSpacing, wordSpacing, dp, text); psPainter.drawText(x, y, letterSpacing, wordSpacing, dp, text);
verify(psGenerator).writeln("1 0 0 -1 100.100 99.900 Tm"); verify(psGenerator).writeln("1 0 0 -1 100.100 99.900 Tm");
verify(psGenerator).writeln("[<0000> [-100 100] <00000000> [200 -200] <0000> [-300 300] "
+ "<0000000000000000000000000000>] TJ");
verify(psGenerator).writeln("[<0048> [-100 100] <0065006C> [200 -200] <006C> [-300 300] "
+ "<006F0020004D006F0063006B002101F4A9>] TJ");
} catch (Exception e) { } catch (Exception e) {
fail("something broke..."); fail("something broke...");
} }

+ 73
- 0
fop-core/src/test/java/org/apache/fop/util/CharUtilitiesTestCase.java 查看文件

/*
* 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.util;

import org.junit.Test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;


public class CharUtilitiesTestCase {

@Test
public void testIsBmpCodePoint() {
for (int i = 0; i < 0x10FFFF; i++) {
assertEquals(i <= 0xFFFF, CharUtilities.isBmpCodePoint(i));
}
}

@Test
public void testIncrementIfNonBMP() {
for (int i = 0; i < 0x10FFFF; i++) {
if (i <= 0xFFFF) {
assertEquals(0, CharUtilities.incrementIfNonBMP(i));
} else {
assertEquals(1, CharUtilities.incrementIfNonBMP(i));
}
}
}

@Test
public void testIsSurrogatePair() {
for (char i = 0; i < 0xFFFF; i++) {
if (i < 0xD800 || i > 0xDFFF) {
assertFalse(CharUtilities.isSurrogatePair(i));
} else {
assertTrue(CharUtilities.isSurrogatePair(i));
}
}
}

@Test
public void testContainsSurrogatePairAt() {
String withSurrogatePair = "012\uD83D\uDCA94";

assertTrue(CharUtilities.containsSurrogatePairAt(withSurrogatePair, 3));
}

@Test(expected = IllegalArgumentException.class)
public void testContainsSurrogatePairAtWithMalformedUTF8Sequence() {
String malformedUTF8Sequence = "012\uD83D4";

CharUtilities.containsSurrogatePairAt(malformedUTF8Sequence, 3);
}
}

+ 14
- 1
fop-core/src/test/java/org/apache/fop/util/HexEncoderTestCase.java 查看文件

} }
} }



/**
* Tests that codepoints are properly encoded into hex strings.
*/
@Test
public void testEncodeCodepoints() {
char[] digits = new char[] {'0', '1', '0', '0', '0', '0'};
for (int c = 0x10000; c <= 0x1FFFF; c++) {
assertEquals(new String(digits), HexEncoder.encode(c));
increment(digits);
}
}

private static void increment(char[] digits) { private static void increment(char[] digits) {
int d = 4;
int d = digits.length;
do { do {
d--; d--;
digits[d] = successor(digits[d]); digits[d] = successor(digits[d]);

+ 1
- 1
fop/build.xml 查看文件

<include name="org/apache/fop/util/*OutputStream.class"/> <include name="org/apache/fop/util/*OutputStream.class"/>
<include name="org/apache/fop/util/SubInputStream.class"/> <include name="org/apache/fop/util/SubInputStream.class"/>
<include name="org/apache/fop/util/Finalizable.class"/> <include name="org/apache/fop/util/Finalizable.class"/>
<include name="org/apache/fop/util/CharUtilities.class"/>
<include name="org/apache/fop/util/CharUtilities*.class"/>
<include name="org/apache/fop/util/DecimalFormatCache*.class"/> <include name="org/apache/fop/util/DecimalFormatCache*.class"/>
<include name="org/apache/fop/util/ImageObject.class"/> <include name="org/apache/fop/util/ImageObject.class"/>
<include name="org/apache/fop/util/HexEncoder.class"/> <include name="org/apache/fop/util/HexEncoder.class"/>

+ 57
- 0
fop/test/layoutengine/hyphenation-testcases/block_hyphenation_kerning_non_bmp.xml 查看文件

<?xml version="1.1" encoding="utf-8"?>
<!--
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$ -->
<testcase>
<info>
<p>
Checks hyphenation in combination with kerning.
</p>
</info>
<cfg>
<base14kerning>true</base14kerning>
</cfg>
<fo>
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format" language="en" hyphenate="true" font-family="Aegean600">
<fo:layout-master-set>
<fo:simple-page-master master-name="simple" page-height="5in" page-width="5in">
<fo:region-body/>
</fo:simple-page-master>
</fo:layout-master-set>
<fo:page-sequence master-reference="simple">
<fo:flow flow-name="xsl-region-body">
<fo:block font-size="20pt" line-height="1.0" text-align="justify" text-align-last="justify" background-color="lightgray" start-indent="10pt" end-indent="10pt" border="solid 1pt">
hyphenation&#66310;&#66311;&#66315;&#66316; regression advantage foundation&#66311;&#66312;&#66313;&#66314; &#66315;&#66316; vandavanda
</fo:block>
</fo:flow>
</fo:page-sequence>
</fo:root>
</fo>
<checks>
<eval expected="1" xpath="count(//pageViewport)"/>
<eval expected="en" xpath="/areaTree/pageSequence/@xml:lang"/>

<eval expected="47702" xpath="//flow/block[1]/lineArea[1]/text[1]/@twsadjust"/>
<eval expected="36968" xpath="//flow/block[1]/lineArea[2]/text[1]/@twsadjust"/>

<eval expected="advan-" xpath="//flow/block[1]/lineArea[1]/text[1]/word[3]"/>
<eval expected="0 0 0 -500 0 0" xpath="//flow/block[1]/lineArea[1]/text[1]/word[3]/@letter-adjust"/>
<eval expected="tage" xpath="//flow/block[1]/lineArea[2]/text[1]/word[1]"/>
<eval expected="vandavanda" xpath="//flow/block[1]/lineArea[2]/text[1]/word[4]"/>
<eval expected="0 -500 0 0 0 -400 -500 0 0 0" xpath="//flow/block[1]/lineArea[2]/text[1]/word[4]/@letter-adjust"/>
</checks>
</testcase>

+ 3
- 0
fop/test/resources/fonts/ttf/Aegean600.LICENSE 查看文件

http://users.teilar.gr/~g1951d/

In lieu of a licence; fonts and documents in this site are free for any use;

二進制
fop/test/resources/fonts/ttf/Aegean600.ttf 查看文件


+ 18
- 0
fop/test/resources/fonts/ttf/AndroidEmoji.LICENSE 查看文件

Copyright (C) 2008 The Android Open Source Project

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

##########

This directory contains the fonts for the platform. They are licensed
under the Apache 2 license.

二進制
fop/test/resources/fonts/ttf/AndroidEmoji.ttf 查看文件


+ 6
- 3
fop/test/xml/pdf-encoding/pdf-encoding-test.xconf 查看文件

<renderers> <renderers>
<renderer mime="application/pdf"> <renderer mime="application/pdf">
<!-- disable PDF text compression --> <!-- disable PDF text compression -->
<filterList>
<filterList>
<value>null</value> <value>null</value>
</filterList> </filterList>
<filterList type="image"> <filterList type="image">
<value>flate</value> <value>flate</value>
<value>ascii-85</value> <value>ascii-85</value>
</filterList> </filterList>
<!-- use a custom font to show encoding problems --> <!-- use a custom font to show encoding problems -->
<fonts> <fonts>
<font metrics-url="test/resources/fonts/ttf/glb12.ttf.xml" embed-url="test/resources/fonts/ttf/glb12.ttf">
<font metrics-url="../../resources/fonts/ttf/glb12.ttf.xml" embed-url="../../resources/fonts/ttf/glb12.ttf">
<font-triplet name="Gladiator" style="normal" weight="normal"/> <font-triplet name="Gladiator" style="normal" weight="normal"/>
</font> </font>
<font embed-url="../../resources/fonts/ttf/Aegean600.ttf" >
<font-triplet name="Aegean600" style="normal" weight="normal"/>
</font>
</fonts> </fonts>
</renderer> </renderer>
</renderers> </renderers>

+ 1
- 1
fop/test/xml/pdf-encoding/test-custom-font.fo 查看文件

Minimal FO document used to test PDF encoding Minimal FO document used to test PDF encoding
--> -->


<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format" font-family="Gladiator">
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format" font-family="Gladiator" font-size="10px">
<fo:layout-master-set> <fo:layout-master-set>
<fo:simple-page-master master-name="A4" page-height="29.7cm" page-width="21cm" margin="2cm"> <fo:simple-page-master master-name="A4" page-height="29.7cm" page-width="21cm" margin="2cm">
<fo:region-body/> <fo:region-body/>

+ 37
- 0
fop/test/xml/pdf-encoding/test-custom-non-bmp-font.fo 查看文件

<?xml version="1.0" encoding="UTF-8"?>

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

<!--
Minimal FO document used to test PDF encoding
-->

<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format" font-family="Helvetica, Aegean600">
<fo:layout-master-set>
<fo:simple-page-master master-name="A4" page-height="29.7cm" page-width="21cm" margin="2cm">
<fo:region-body/>
</fo:simple-page-master>
</fo:layout-master-set>
<fo:page-sequence master-reference="A4">
<fo:flow flow-name="xsl-region-body">
<!--<fo:block>Testing PDF text encoding using the user-specified AndroidEmoji font which cover non BMP codepoints</fo:block>-->
<fo:block>PDFE_TEST_MARK_1: Hello AndroidEmoji World!</fo:block>
<fo:block>PDFE_TEST_MARK_2: This is a OLD ITALIC LETTER A: XX_ &#x10300; _XX</fo:block>
</fo:flow>
</fo:page-sequence>
</fo:root>

Loading…
取消
儲存