git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1827168 13f79535-47bb-0310-9956-ffa450edef68tags/fop-2_3
<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> |
/** | /** | ||||
* 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 |
*/ | */ | ||||
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. |
} | } | ||||
/** {@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} */ |
* @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). |
/** | /** | ||||
* 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} */ |
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; | |||||
} | } | ||||
/** | /** |
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; | ||||
} | } | ||||
} | } |
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? |
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); | ||||
} | } |
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; | ||||
} | } | ||||
* 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; |
//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]; |
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)); | |||||
} | } | ||||
} | } | ||||
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); | |||||
} | } | ||||
/** | /** |
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; |
return typeface.hasKerningInfo(); | return typeface.hasKerningInfo(); | ||||
} | } | ||||
/** {@inheritDoc} */ | |||||
public boolean isMultiByte() { | |||||
return typeface.isMultiByte(); | |||||
} | |||||
/** | /** | ||||
* {@inheritDoc} | * {@inheritDoc} | ||||
*/ | */ |
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); | ||||
} | } | ||||
} | } |
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)) { |
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; | |||||
} | |||||
} | } |
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(); | ||||
} | } |
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) { |
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) { |
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 > 0xFFFF, 0 otherwise | |||||
* @return 1 if codePoint > 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(); | |||||
} | |||||
}; | |||||
} | |||||
}; | |||||
} | |||||
} | } |
} | } | ||||
/** | /** | ||||
* 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); | |||||
} | |||||
} | } | ||||
} | } |
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; | ||||
} | } |
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); | |||||
} | } | ||||
} | } | ||||
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); | |||||
} | } | ||||
} | } | ||||
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(); |
/* | |||||
* 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); | |||||
} | |||||
} | |||||
} |
/* | |||||
* 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); | |||||
} | |||||
} |
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()); | |||||
} | } | ||||
/** | /** |
/* | |||||
* 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]; | |||||
} | |||||
} | |||||
} |
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); | |||||
} | |||||
} | } |
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]; | |||||
} | |||||
} | } | ||||
} | } |
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..."); | ||||
} | } |
/* | |||||
* 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); | |||||
} | |||||
} |
} | } | ||||
} | } | ||||
/** | |||||
* 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]); |
<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"/> |
<?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𐌆𐌇𐌋𐌌 regression advantage foundation𐌇𐌈𐌉𐌊 𐌋𐌌 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> |
http://users.teilar.gr/~g1951d/ | |||||
In lieu of a licence; fonts and documents in this site are free for any use; |
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. |
<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> |
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/> |
<?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_ 𐌀 _XX</fo:block> | |||||
</fo:flow> | |||||
</fo:page-sequence> | |||||
</fo:root> |