Browse Source

FOP-2486: Soft font support for TrueType fonts in PCL

git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1695082 13f79535-47bb-0310-9956-ffa450edef68
tags/fop-2_1
Robert Meyer 8 years ago
parent
commit
399828422a
41 changed files with 3193 additions and 47 deletions
  1. 4
    0
      findbugs-exclude.xml
  2. 12
    1
      src/java/org/apache/fop/fonts/CIDFull.java
  3. 14
    0
      src/java/org/apache/fop/fonts/CIDSet.java
  4. 18
    0
      src/java/org/apache/fop/fonts/CIDSubset.java
  5. 12
    0
      src/java/org/apache/fop/fonts/CustomFont.java
  6. 18
    0
      src/java/org/apache/fop/fonts/MultiByteFont.java
  7. 12
    0
      src/java/org/apache/fop/fonts/SingleByteFont.java
  8. 3
    3
      src/java/org/apache/fop/fonts/truetype/GlyfTable.java
  9. 2
    2
      src/java/org/apache/fop/fonts/truetype/OFDirTabEntry.java
  10. 26
    1
      src/java/org/apache/fop/fonts/truetype/OpenFont.java
  11. 8
    0
      src/java/org/apache/fop/fonts/truetype/TTFFile.java
  12. 2
    1
      src/java/org/apache/fop/render/java2d/ConfiguredFontCollection.java
  13. 4
    0
      src/java/org/apache/fop/render/java2d/CustomFontMetricsMapper.java
  14. 1
    1
      src/java/org/apache/fop/render/pcl/HardcodedFonts.java
  15. 9
    0
      src/java/org/apache/fop/render/pcl/PCLEventProducer.java
  16. 1
    0
      src/java/org/apache/fop/render/pcl/PCLEventProducer.xml
  17. 9
    0
      src/java/org/apache/fop/render/pcl/PCLGenerator.java
  18. 163
    30
      src/java/org/apache/fop/render/pcl/PCLPainter.java
  19. 1
    0
      src/java/org/apache/fop/render/pcl/PCLRendererConfigurator.java
  20. 7
    8
      src/java/org/apache/fop/render/pcl/PCLRenderingUtil.java
  21. 125
    0
      src/java/org/apache/fop/render/pcl/fonts/PCLByteWriterUtil.java
  22. 147
    0
      src/java/org/apache/fop/render/pcl/fonts/PCLCharacterDefinition.java
  23. 42
    0
      src/java/org/apache/fop/render/pcl/fonts/PCLCharacterWriter.java
  24. 113
    0
      src/java/org/apache/fop/render/pcl/fonts/PCLFontReader.java
  25. 65
    0
      src/java/org/apache/fop/render/pcl/fonts/PCLFontReaderFactory.java
  26. 62
    0
      src/java/org/apache/fop/render/pcl/fonts/PCLFontSegment.java
  27. 160
    0
      src/java/org/apache/fop/render/pcl/fonts/PCLSoftFont.java
  28. 278
    0
      src/java/org/apache/fop/render/pcl/fonts/PCLSoftFontManager.java
  29. 200
    0
      src/java/org/apache/fop/render/pcl/fonts/PCLSymbolSet.java
  30. 154
    0
      src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFCharacterWriter.java
  31. 731
    0
      src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFFontReader.java
  32. 77
    0
      src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFOS2FontTable.java
  33. 115
    0
      src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFPCLTFontTable.java
  34. 51
    0
      src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFPOSTFontTable.java
  35. 47
    0
      src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFTable.java
  36. 49
    0
      src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFTableFactory.java
  37. 51
    0
      test/java/org/apache/fop/render/pcl/fonts/MockPCLTTFFontReader.java
  38. 74
    0
      test/java/org/apache/fop/render/pcl/fonts/PCLByteWriterUtilTestCase.java
  39. 52
    0
      test/java/org/apache/fop/render/pcl/fonts/PCLFontReaderFactoryTestCase.java
  40. 198
    0
      test/java/org/apache/fop/render/pcl/fonts/PCLTTFFontReaderTestCase.java
  41. 76
    0
      test/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFCharacterWriterTestCase.java

+ 4
- 0
findbugs-exclude.xml View File

@@ -238,6 +238,10 @@
<Bug pattern="OS_OPEN_STREAM_EXCEPTION_PATH"/>
</Or>
</Match>
<Match>
<Class name="org.apache.fop.render.pcl.fonts.truetype.PCLTTFTable"/>
<Bug pattern="ICAST_INTEGER_MULTIPLY_CAST_TO_LONG"/>
</Match>
<!-- END - APPROVED EXCLUSIONS -->

<!-- START - TEMPORARY (UNAPPROVED) EXCLUSIONS -->

+ 12
- 1
src/java/org/apache/fop/fonts/CIDFull.java View File

@@ -56,6 +56,18 @@ public class CIDFull implements CIDSet {
return index;
}

/** {@inheritDoc} */
@Override
public char getUnicodeFromGID(int glyphIndex) {
return ' ';
}

/** {@inheritDoc} */
@Override
public int getGIDFromChar(char ch) {
return ch;
}

/** {@inheritDoc} */
public char getUnicode(int index) {
initGlyphIndices();
@@ -109,5 +121,4 @@ public class CIDFull implements CIDSet {
public int[] getWidths() {
return font.getWidths();
}

}

+ 14
- 0
src/java/org/apache/fop/fonts/CIDSet.java View File

@@ -43,6 +43,20 @@ public interface CIDSet {
*/
char getUnicode(int index);

/**
* Gets the unicode character from the original font glyph index
* @param glyphIndex The original glyph index of the character in the font
* @return The character represented by the passed GID
*/
char getUnicodeFromGID(int glyphIndex);

/**
* Returns the glyph index from the original font from a character
* @param ch The character
* @return The glyph index in the original font.
*/
int getGIDFromChar(char ch);

/**
* 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

+ 18
- 0
src/java/org/apache/fop/fonts/CIDSubset.java View File

@@ -53,6 +53,12 @@ public class CIDSubset implements CIDSet {
*/
private Map<Integer, Character> usedCharsIndex = new HashMap<Integer, Character>();

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


private final MultiByteFont font;

public CIDSubset(MultiByteFont mbf) {
@@ -93,6 +99,7 @@ public class CIDSubset implements CIDSet {
usedGlyphs.put(glyphIndex, selector);
usedGlyphsIndex.put(selector, glyphIndex);
usedCharsIndex.put(selector, unicode);
charToGIDs.put(unicode, glyphIndex);
usedGlyphsCount++;
return selector;
} else {
@@ -105,6 +112,17 @@ public class CIDSubset implements CIDSet {
return Collections.unmodifiableMap(this.usedGlyphs);
}

/** {@inheritDoc} */
public char getUnicodeFromGID(int glyphIndex) {
int selector = usedGlyphs.get(glyphIndex);
return usedCharsIndex.get(selector);
}

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

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

+ 12
- 0
src/java/org/apache/fop/fonts/CustomFont.java View File

@@ -568,4 +568,16 @@ public abstract class CustomFont extends Typeface
this.strikeoutThickness = strikeoutThickness;
}

/**
* Returns a Map of used Glyphs.
* @return Map Map of used Glyphs
*/
public abstract Map<Integer, Integer> getUsedGlyphs();

/**
* Returns the character from it's original glyph index in the font
* @param glyphIndex The original index of the character
* @return The character
*/
public abstract char getUnicodeFromGID(int glyphIndex);
}

+ 18
- 0
src/java/org/apache/fop/fonts/MultiByteFont.java View File

@@ -425,6 +425,24 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl
return cidSet.getGlyphs();
}

/**
* Returns the character from it's original glyph index in the font
* @param glyphIndex The original index of the character
* @return The character
*/
public char getUnicodeFromGID(int glyphIndex) {
return cidSet.getUnicodeFromGID(glyphIndex);
}

/**
* Gets the original glyph index in the font from a character.
* @param ch The character
* @return The glyph index in the font
*/
public int getGIDFromChar(char ch) {
return cidSet.getGIDFromChar(ch);
}

/**
* Establishes the glyph definition table.
* @param gdef the glyph definition table to be used by this font

+ 12
- 0
src/java/org/apache/fop/fonts/SingleByteFont.java View File

@@ -63,6 +63,7 @@ public class SingleByteFont extends CustomFont {
private LinkedHashMap<Integer, String> usedGlyphNames;
private Map<Integer, Integer> usedGlyphs;
private Map<Integer, Character> usedCharsIndex;
private Map<Character, Integer> charGIDMappings;

public SingleByteFont(InternalResourceResolver resourceResolver) {
super(resourceResolver);
@@ -76,6 +77,7 @@ public class SingleByteFont extends CustomFont {
usedGlyphNames = new LinkedHashMap<Integer, String>();
usedGlyphs = new HashMap<Integer, Integer>();
usedCharsIndex = new HashMap<Integer, Character>();
charGIDMappings = new HashMap<Character, Integer>();

// The zeroth value is reserved for .notdef
usedGlyphs.put(0, 0);
@@ -234,6 +236,7 @@ public class SingleByteFont extends CustomFont {
int selector = usedGlyphsCount;
usedGlyphs.put(glyphIndex, selector);
usedCharsIndex.put(selector, unicode);
charGIDMappings.put(unicode, glyphIndex);
usedGlyphsCount++;
return selector;
} else {
@@ -519,6 +522,15 @@ public class SingleByteFont extends CustomFont {
return getUnicode(selector);
}

public int getGIDFromChar(char ch) {
return charGIDMappings.get(ch);
}

public char getUnicodeFromGID(int glyphIndex) {
int selector = usedGlyphs.get(glyphIndex);
return usedCharsIndex.get(selector);
}

public void mapUsedGlyphName(int gid, String value) {
usedGlyphNames.put(gid, value);
}

+ 3
- 3
src/java/org/apache/fop/fonts/truetype/GlyfTable.java View File

@@ -47,7 +47,7 @@ public class GlyfTable {
/** All the glyphs that are composed, but do not appear in the subset. */
protected Set<Integer> composedGlyphs = new TreeSet<Integer>();

protected GlyfTable(FontFileReader in, OFMtxEntry[] metrics, OFDirTabEntry dirTableEntry,
public GlyfTable(FontFileReader in, OFMtxEntry[] metrics, OFDirTabEntry dirTableEntry,
Map<Integer, Integer> glyphs) throws IOException {
mtxTab = metrics;
tableOffset = dirTableEntry.getOffset();
@@ -202,7 +202,7 @@ public class GlyfTable {
} while (GlyfFlags.hasMoreComposites(flags));
}

private boolean isComposite(int indexInOriginal) throws IOException {
public boolean isComposite(int indexInOriginal) throws IOException {
int numberOfContours = in.readTTFShort(tableOffset + mtxTab[indexInOriginal].getOffset());
return numberOfContours < 0;
}
@@ -215,7 +215,7 @@ public class GlyfTable {
* @return the set of glyph indices this glyph composes
* @throws IOException an I/O error
*/
private Set<Integer> retrieveComposedGlyphs(int indexInOriginal)
public Set<Integer> retrieveComposedGlyphs(int indexInOriginal)
throws IOException {
Set<Integer> composedGlyphs = new HashSet<Integer>();
long offset = tableOffset + mtxTab[indexInOriginal].getOffset() + 10;

+ 2
- 2
src/java/org/apache/fop/fonts/truetype/OFDirTabEntry.java View File

@@ -30,7 +30,7 @@ import java.util.Arrays;
public class OFDirTabEntry {

private byte[] tag = new byte[4];
private int checksum;
private long checksum;
private long offset;
private long length;

@@ -74,7 +74,7 @@ public class OFDirTabEntry {
* Returns the checksum.
* @return int
*/
public int getChecksum() {
public long getChecksum() {
return checksum;
}


+ 26
- 1
src/java/org/apache/fop/fonts/truetype/OpenFont.java View File

@@ -355,7 +355,7 @@ public abstract class OpenFont {
long offset) throws IOException {
OFDirTabEntry dt = dirTabs.get(tableName);
if (dt == null) {
log.error("Dirtab " + tableName.getName() + " not found.");
log.info("Dirtab " + tableName.getName() + " not found.");
return false;
} else {
in.seekSet(dt.getOffset() + offset);
@@ -965,6 +965,15 @@ public abstract class OpenFont {
return fbb;
}

/**
* Returns the original bounding box values from the HEAD table
* @return An array of bounding box values
*/
public int[] getBBoxRaw() {
int[] bbox = {fontBBox1, fontBBox2, fontBBox3, fontBBox4};
return bbox;
}

/**
* Returns the LowerCaseAscent attribute of the font.
* @return int The LowerCaseAscent
@@ -1047,6 +1056,18 @@ public abstract class OpenFont {
return convertTTFUnit2PDFUnit(ansiWidth[idx]);
}

/**
* Returns the width of a given character in raw units
* @param idx Index of the character
* @return int Width in it's raw form stored in the font
*/
public int getCharWidthRaw(int idx) {
if (ansiWidth != null) {
return ansiWidth[idx];
}
return -1;
}

/**
* Returns the kerning table.
* @return Map The kerning table
@@ -1978,4 +1999,8 @@ public abstract class OpenFont {
IOUtils.closeQuietly(stream);
}
}

public String getCopyrightNotice() {
return notice;
}
}

+ 8
- 0
src/java/org/apache/fop/fonts/truetype/TTFFile.java View File

@@ -188,6 +188,14 @@ public class TTFFile extends OpenFont {
: (fontFile.readTTFUShort() << 1));
}

/**
* Gets the last location of the glyf table
* @return The last location as a long
*/
public long getLastGlyfLocation() {
return lastLoca;
}

@Override
protected void initializeFont(FontFileReader in) throws IOException {
fontFile = in;

+ 2
- 1
src/java/org/apache/fop/render/java2d/ConfiguredFontCollection.java View File

@@ -83,7 +83,8 @@ public class ConfiguredFontCollection implements FontCollection {
font = new CustomFontMetricsMapper(fontMetrics, fontSource);
} else {
FontUris fontUris = new FontUris(fontURI, null);
CustomFont fontMetrics = FontLoader.loadFont(fontUris, null, true,
CustomFont fontMetrics = FontLoader.loadFont(fontUris,
configFontInfo.getSubFontName(), true,
configFontInfo.getEmbeddingMode(), configFontInfo.getEncodingMode(),
configFontInfo.getKerning(), configFontInfo.getAdvanced(), resourceResolver);
font = new CustomFontMetricsMapper(fontMetrics);

+ 4
- 0
src/java/org/apache/fop/render/java2d/CustomFontMetricsMapper.java View File

@@ -289,4 +289,8 @@ public class CustomFontMetricsMapper extends Typeface implements FontMetricsMapp
}
}

public Typeface getRealFont() {
return typeface;
}

}

+ 1
- 1
src/java/org/apache/fop/render/pcl/HardcodedFonts.java View File

@@ -55,7 +55,7 @@ final class HardcodedFonts {
return selectFont(gen, name, size);
}

private static boolean selectFont(PCLGenerator gen, String name, int size) throws IOException {
protected static boolean selectFont(PCLGenerator gen, String name, int size) throws IOException {
int fontcode = 0;
if (name.length() > 1 && name.charAt(0) == 'F') {
try {

+ 9
- 0
src/java/org/apache/fop/render/pcl/PCLEventProducer.java View File

@@ -53,4 +53,13 @@ public interface PCLEventProducer extends EventProducer {
*/
void paperTypeUnavailable(Object source, long pageWidth, long pageHeight, String fallbackPaper);

/**
* The font type is not supported for PCL output.
* @param source The event source
* @param fontName The name of the font not supported
* @param supportedTypes The types of fonts currently supported
* @event.severity ERROR
*/
void fontTypeNotSupported(Object source, String fontName, String supportedTypes);

}

+ 1
- 0
src/java/org/apache/fop/render/pcl/PCLEventProducer.xml View File

@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<catalogue xml:lang="en">
<message key="paperTypeUnavailable">Paper type ({pageWidth} x {pageHeight} mpt) could not be determined. Falling back to: {fallbackPaper}</message>
<message key="fontTypeNotSupported">The font '{fontName}' is not supported. PCL output currently only supports {supportedTypes}</message>
</catalogue>

+ 9
- 0
src/java/org/apache/fop/render/pcl/PCLGenerator.java View File

@@ -140,6 +140,15 @@ public class PCLGenerator {
out.write(s.getBytes(ISO_8859_1));
}

/**
* Writes raw bytes to the output stream
* @param bytes The bytes
* @throws IOException In case of an I/O error
*/
public void writeBytes(byte[] bytes) throws IOException {
out.write(bytes);
}

/**
* Formats a double value with two decimal positions for PCL output.
*

+ 163
- 30
src/java/org/apache/fop/render/pcl/PCLPainter.java View File

@@ -28,7 +28,9 @@ import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Stack;

@@ -42,16 +44,27 @@ import org.apache.xmlgraphics.image.loader.impl.ImageGraphics2D;
import org.apache.xmlgraphics.java2d.GraphicContext;
import org.apache.xmlgraphics.java2d.Graphics2DImagePainter;

import org.apache.fop.fonts.CIDFontType;
import org.apache.fop.fonts.Font;
import org.apache.fop.fonts.FontTriplet;
import org.apache.fop.fonts.FontType;
import org.apache.fop.fonts.LazyFont;
import org.apache.fop.fonts.MultiByteFont;
import org.apache.fop.fonts.Typeface;
import org.apache.fop.render.ImageHandlerUtil;
import org.apache.fop.render.RenderingContext;
import org.apache.fop.render.intermediate.AbstractIFPainter;
import org.apache.fop.render.intermediate.IFException;
import org.apache.fop.render.intermediate.IFState;
import org.apache.fop.render.intermediate.IFUtil;
import org.apache.fop.render.java2d.CustomFontMetricsMapper;
import org.apache.fop.render.java2d.FontMetricsMapper;
import org.apache.fop.render.java2d.Java2DPainter;
import org.apache.fop.render.pcl.fonts.PCLCharacterWriter;
import org.apache.fop.render.pcl.fonts.PCLSoftFont;
import org.apache.fop.render.pcl.fonts.PCLSoftFontManager;
import org.apache.fop.render.pcl.fonts.PCLSoftFontManager.PCLTextSegment;
import org.apache.fop.render.pcl.fonts.truetype.PCLTTFCharacterWriter;
import org.apache.fop.traits.BorderProps;
import org.apache.fop.traits.RuleStyle;
import org.apache.fop.util.CharUtilities;
@@ -73,6 +86,8 @@ public class PCLPainter extends AbstractIFPainter<PCLDocumentHandler> implements
private Stack<GraphicContext> graphicContextStack = new Stack<GraphicContext>();
private GraphicContext graphicContext = new GraphicContext();

private PCLSoftFontManager sfManager = new PCLSoftFontManager();

/**
* Main constructor.
* @param parent the parent document handler
@@ -315,16 +330,51 @@ public class PCLPainter extends AbstractIFPainter<PCLDocumentHandler> implements
//TODO Ignored: state.getFontVariant()
//TODO Opportunity for font caching if font state is more heavily used
String fontKey = getFontKey(triplet);
boolean pclFont = getPCLUtil().isAllTextAsBitmaps() ? false
: HardcodedFonts.setFont(gen, fontKey, state.getFontSize(), text);
Typeface tf = getTypeface(fontKey);
boolean drawAsBitmaps = getPCLUtil().isAllTextAsBitmaps();
boolean pclFont = HardcodedFonts.setFont(gen, fontKey, state.getFontSize(), text);
if (pclFont) {
drawTextNative(x, y, letterSpacing, wordSpacing, dp, text, triplet);
} else {
drawTextAsBitmap(x, y, letterSpacing, wordSpacing, dp, text, triplet);
if (DEBUG) {
state.setTextColor(Color.GRAY);
HardcodedFonts.setFont(gen, "F1", state.getFontSize(), text);
drawTextNative(x, y, letterSpacing, wordSpacing, dp, text, triplet);
// TrueType conversion to a soft font (PCL 5 Technical Reference - Chapter 11)
if (!drawAsBitmaps && isTrueType(tf)) {
boolean madeSF = false;
if (sfManager.getSoftFont(tf, text) == null) {
madeSF = true;
ByteArrayOutputStream baos = sfManager.makeSoftFont(tf);
if (baos != null) {
gen.writeBytes(baos.toByteArray());
}
}
String formattedSize = gen.formatDouble2(state.getFontSize() / 1000.0);
gen.writeCommand(String.format("(s%sV", formattedSize));
List<PCLTextSegment> textSegments = sfManager.getTextSegments(text, tf);
if (textSegments.isEmpty()) {
textSegments.add(new PCLTextSegment(sfManager.getSoftFontID(tf), text));
}
boolean first = true;
for (PCLTextSegment textSegment : textSegments) {
gen.writeCommand(String.format("(%dX", textSegment.getFontID()));
PCLSoftFont softFont = sfManager.getSoftFontFromID(textSegment.getFontID());
PCLCharacterWriter charWriter = new PCLTTFCharacterWriter(softFont);
gen.writeBytes(sfManager.assignFontID(textSegment.getFontID()));
gen.writeBytes(charWriter.writeCharacterDefinitions(textSegment.getText()));
if (first) {
drawTextUsingSoftFont(x, y, letterSpacing, wordSpacing, dp,
textSegment.getText(), triplet, softFont);
first = false;
} else {
drawTextUsingSoftFont(-1, -1, letterSpacing, wordSpacing, dp,
textSegment.getText(), triplet, softFont);
}
}
} else {
drawTextAsBitmap(x, y, letterSpacing, wordSpacing, dp, text, triplet);
if (DEBUG) {
state.setTextColor(Color.GRAY);
HardcodedFonts.setFont(gen, "F1", state.getFontSize(), text);
drawTextNative(x, y, letterSpacing, wordSpacing, dp, text, triplet);
}
}
}
} catch (IOException ioe) {
@@ -332,6 +382,29 @@ public class PCLPainter extends AbstractIFPainter<PCLDocumentHandler> implements
}
}

private boolean isTrueType(Typeface tf) {
if (tf.getFontType().equals(FontType.TRUETYPE)) {
return true;
} else if (tf instanceof CustomFontMetricsMapper) {
Typeface realFont = ((CustomFontMetricsMapper) tf).getRealFont();
if (realFont instanceof MultiByteFont) {
return ((MultiByteFont) realFont).getCIDType().equals(CIDFontType.CIDTYPE2);
}
}
return false;
}

private Typeface getTypeface(String fontName) {
if (fontName == null) {
throw new NullPointerException("fontName must not be null");
}
Typeface tf = getFontInfo().getFonts().get(fontName);
if (tf instanceof LazyFont) {
tf = ((LazyFont)tf).getRealFont();
}
return tf;
}

private void drawTextNative(int x, int y, int letterSpacing, int wordSpacing, int[][] dp,
String text, FontTriplet triplet) throws IOException {
Color textColor = state.getTextColor();
@@ -414,25 +487,87 @@ public class PCLPainter extends AbstractIFPainter<PCLDocumentHandler> implements

}

private void drawTextUsingSoftFont(int x, int y, int letterSpacing, int wordSpacing, int[][] dp,
String text, FontTriplet triplet, PCLSoftFont softFont) throws IOException {
Color textColor = state.getTextColor();
if (textColor != null) {
gen.setTransparencyMode(true, false);
gen.selectGrayscale(textColor);
}

if (x != -1 && y != -1) {
setCursorPos(x, y);
}

float fontSize = state.getFontSize() / 1000f;
Font font = getFontInfo().getFontInstance(triplet, state.getFontSize());
int l = text.length();
int[] dx = IFUtil.convertDPToDX(dp);
int dxl = (dx != null ? dx.length : 0);

StringBuffer sb = new StringBuffer(Math.max(16, l));
if (dx != null && dxl > 0 && dx[0] != 0) {
sb.append("\u001B&a+").append(gen.formatDouble2(dx[0] / 100.0)).append('H');
}
String current = "";
for (int i = 0; i < l; i++) {
char orgChar = text.charAt(i);
float glyphAdjust = 0;
if (!font.hasChar(orgChar)) {
if (CharUtilities.isFixedWidthSpace(orgChar)) {
//Fixed width space are rendered as spaces so copy/paste works in a reader
char ch = font.mapChar(CharUtilities.SPACE);
int spaceDiff = font.getCharWidth(ch) - font.getCharWidth(orgChar);
glyphAdjust = -(10 * spaceDiff / fontSize);
}
}

if ((wordSpacing != 0) && CharUtilities.isAdjustableSpace(orgChar)) {
glyphAdjust += wordSpacing;
}
current += orgChar;
glyphAdjust += letterSpacing;
if (dx != null && i < dxl - 1) {
glyphAdjust += dx[i + 1];
}

if (glyphAdjust != 0) {
gen.getOutputStream().write(sb.toString().getBytes(gen.getTextEncoding()));
for (int j = 0; j < current.length(); j++) {
gen.getOutputStream().write(softFont.getCharCode(current.charAt(j)));
}
sb = new StringBuffer();

String command = (glyphAdjust > 0) ? "\u001B&a+" : "\u001B&a";
sb.append(command).append(gen.formatDouble2(glyphAdjust / 100.0)).append('H');

current = "";
}
}
if (!current.equals("")) {
gen.getOutputStream().write(sb.toString().getBytes(gen.getTextEncoding()));
for (int i = 0; i < current.length(); i++) {
gen.getOutputStream().write(softFont.getCharCode(current.charAt(i)));
}
}
}

private static final double SAFETY_MARGIN_FACTOR = 0.05;

private Rectangle getTextBoundingBox(int x, int y,
int letterSpacing, int wordSpacing, int[][] dp,
String text,
Font font, FontMetricsMapper metrics) {
private Rectangle getTextBoundingBox(int x, int y, int letterSpacing, int wordSpacing,
int[][] dp, String text, Font font, FontMetricsMapper metrics) {
int maxAscent = metrics.getMaxAscent(font.getFontSize()) / 1000;
int descent = metrics.getDescender(font.getFontSize()) / 1000; //is negative
int safetyMargin = (int)(SAFETY_MARGIN_FACTOR * font.getFontSize());
Rectangle boundingRect = new Rectangle(
x, y - maxAscent - safetyMargin,
0, maxAscent - descent + 2 * safetyMargin);
int descent = metrics.getDescender(font.getFontSize()) / 1000; // is negative
int safetyMargin = (int) (SAFETY_MARGIN_FACTOR * font.getFontSize());
Rectangle boundingRect = new Rectangle(x, y - maxAscent - safetyMargin, 0, maxAscent
- descent + 2 * safetyMargin);

int l = text.length();
int[] dx = IFUtil.convertDPToDX(dp);
int dxl = (dx != null ? dx.length : 0);

if (dx != null && dxl > 0 && dx[0] != 0) {
boundingRect.setLocation(boundingRect.x - (int)Math.ceil(dx[0] / 10f), boundingRect.y);
boundingRect.setLocation(boundingRect.x - (int) Math.ceil(dx[0] / 10f), boundingRect.y);
}
float width = 0.0f;
for (int i = 0; i < l; i++) {
@@ -451,19 +586,17 @@ public class PCLPainter extends AbstractIFPainter<PCLDocumentHandler> implements
width += cw + glyphAdjust;
}
int extraWidth = font.getFontSize() / 3;
boundingRect.setSize(
(int)Math.ceil(width) + extraWidth,
boundingRect.height);
boundingRect.setSize((int) Math.ceil(width) + extraWidth, boundingRect.height);
return boundingRect;
}

private void drawTextAsBitmap(final int x, final int y,
final int letterSpacing, final int wordSpacing, final int[][] dp,
final String text, FontTriplet triplet) throws IFException {
//Use Java2D to paint different fonts via bitmap
private void drawTextAsBitmap(final int x, final int y, final int letterSpacing,
final int wordSpacing, final int[][] dp, final String text, FontTriplet triplet)
throws IFException {
// Use Java2D to paint different fonts via bitmap
final Font font = getFontInfo().getFontInstance(triplet, state.getFontSize());

//for cursive fonts, so the text isn't clipped
// for cursive fonts, so the text isn't clipped
FontMetricsMapper mapper;
try {
mapper = (FontMetricsMapper) getFontInfo().getMetricsFor(font.getFontName());
@@ -473,11 +606,11 @@ public class PCLPainter extends AbstractIFPainter<PCLDocumentHandler> implements
final int maxAscent = mapper.getMaxAscent(font.getFontSize()) / 1000;
final int ascent = mapper.getAscender(font.getFontSize()) / 1000;
final int descent = mapper.getDescender(font.getFontSize()) / 1000;
int safetyMargin = (int)(SAFETY_MARGIN_FACTOR * font.getFontSize());
int safetyMargin = (int) (SAFETY_MARGIN_FACTOR * font.getFontSize());
final int baselineOffset = maxAscent + safetyMargin;

final Rectangle boundingBox = getTextBoundingBox(x, y,
letterSpacing, wordSpacing, dp, text, font, mapper);
final Rectangle boundingBox = getTextBoundingBox(x, y, letterSpacing, wordSpacing, dp,
text, font, mapper);
final Dimension dim = boundingBox.getSize();

Graphics2DImagePainter painter = new Graphics2DImagePainter() {
@@ -485,7 +618,7 @@ public class PCLPainter extends AbstractIFPainter<PCLDocumentHandler> implements
public void paint(Graphics2D g2d, Rectangle2D area) {
if (DEBUG) {
g2d.setBackground(Color.LIGHT_GRAY);
g2d.clearRect(0, 0, (int)area.getWidth(), (int)area.getHeight());
g2d.clearRect(0, 0, (int) area.getWidth(), (int) area.getHeight());
}
g2d.translate(-x, -y + baselineOffset);

@@ -501,7 +634,7 @@ public class PCLPainter extends AbstractIFPainter<PCLDocumentHandler> implements
try {
painter.drawText(x, y, letterSpacing, wordSpacing, dp, text);
} catch (IFException e) {
//This should never happen with the Java2DPainter
// This should never happen with the Java2DPainter
throw new RuntimeException("Unexpected error while painting text", e);
}
}

+ 1
- 0
src/java/org/apache/fop/render/pcl/PCLRendererConfigurator.java View File

@@ -67,6 +67,7 @@ public class PCLRendererConfigurator extends PrintRendererConfigurator {
if (config.isTextRendering() != null) {
pclUtil.setAllTextAsBitmaps(config.isTextRendering());
}

}

@Override

+ 7
- 8
src/java/org/apache/fop/render/pcl/PCLRenderingUtil.java View File

@@ -50,12 +50,6 @@ public class PCLRenderingUtil {
/** Controls the dithering quality when rendering gray or color images. */
private float ditheringQuality = 0.5f;

/**
* Controls whether all text should be painted as text. This is a fallback setting in case
* the mixture of native and bitmapped text does not provide the necessary quality.
*/
private boolean allTextAsBitmaps;

/**
* Controls whether an RGB canvas is used when converting Java2D graphics to bitmaps.
* This can be used to work around problems with Apache Batik, for example, but setting
@@ -68,6 +62,12 @@ public class PCLRenderingUtil {
*/
private boolean disabledPJL;

/**
* Controls whether all text should be painted as text. This is a fallback setting in case the mixture of native and
* bitmapped text does not provide the necessary quality.
*/
private boolean allTextAsBitmaps;

PCLRenderingUtil(FOUserAgent userAgent) {
this.userAgent = userAgent;
initialize();
@@ -127,8 +127,7 @@ public class PCLRenderingUtil {
}

/**
* Controls whether all text should be generated as bitmaps or only text for which there's
* no native font.
* Controls whether all text should be generated as bitmaps or only text for which there's no native font.
* @param allTextAsBitmaps true if all text should be painted as bitmaps
*/
public void setAllTextAsBitmaps(boolean allTextAsBitmaps) {

+ 125
- 0
src/java/org/apache/fop/render/pcl/fonts/PCLByteWriterUtil.java View File

@@ -0,0 +1,125 @@
/*
* 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.pcl.fonts;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Arrays;

public class PCLByteWriterUtil {

public byte[] padBytes(byte[] in, int length) {
return padBytes(in, length, 0);
}

public byte[] padBytes(byte[] in, int length, int value) {
byte[] out = new byte[length];
for (int i = 0; i < length; i++) {
if (i < in.length) {
out[i] = in[i];
} else {
out[i] = (byte) value;
}
}
return out;
}

public byte[] signedInt(int s) {
byte b1 = (byte) (s >> 8);
byte b2 = (byte) s;
return new byte[]{b1, b2};
}

public byte signedByte(int s) {
return (byte) s;
}

public byte[] unsignedLongInt(int s) {
return unsignedLongInt((long) s);
}

public byte[] unsignedLongInt(long s) {
byte b1 = (byte) ((s >> 24) & 0xff);
byte b2 = (byte) ((s >> 16) & 0xff);
byte b3 = (byte) ((s >> 8) & 0xff);
byte b4 = (byte) (s & 0xff);
return new byte[]{b1, b2, b3, b4};
}

public byte[] unsignedInt(int s) {
byte b1 = (byte) ((s >> 8) & 0xff);
byte b2 = (byte) (s & 0xff);
return new byte[]{b1, b2};
}

public int unsignedByte(int b) {
return (byte) b & 0xFF;
}

public int maxPower2(int value) {
int test = 2;
while (test < value) {
test *= 2;
}
return test;
}

public int log(int x, int base) {
return (int) (Math.log(x) / Math.log(base));
}

public byte[] toByteArray(int[] s) {
byte[] values = new byte[s.length];
for (int i = 0; i < s.length; i++) {
values[i] = (byte) s[i];
}
return values;
}

public byte[] insertIntoArray(int index, byte[] insertTo, byte[] data) throws IOException {
byte[] preBytes = Arrays.copyOf(insertTo, index);
byte[] postBytes = Arrays.copyOfRange(insertTo, index, insertTo.length);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write(preBytes);
baos.write(data);
baos.write(postBytes);
return baos.toByteArray();
}

public byte[] updateDataAtLocation(byte[] data, byte[] update, int offset) {
int count = 0;
for (int i = offset; i < offset + update.length; i++) {
data[i] = update[count++];
}
return data;
}

/**
* Writes a PCL escape command to the output stream.
* @param cmd the command (without the ESCAPE character)
* @throws IOException In case of an I/O error
*/
public byte[] writeCommand(String cmd) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write(27); // ESC
baos.write(cmd.getBytes("US-ASCII"));
return baos.toByteArray();
}
}

+ 147
- 0
src/java/org/apache/fop/render/pcl/fonts/PCLCharacterDefinition.java View File

@@ -0,0 +1,147 @@
/*
* 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.pcl.fonts;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class PCLCharacterDefinition {
private int charCode;
private int charDefinitionSize;
private byte[] glyfData;
private boolean hasContinuation;
private PCLCharacterFormat charFormat;
private PCLCharacterClass charClass;
private PCLByteWriterUtil pclByteWriter;
private List<PCLCharacterDefinition> composites;
private boolean isComposite;

public PCLCharacterDefinition(int charCode, PCLCharacterFormat charFormat,
PCLCharacterClass charClass, byte[] glyfData, PCLByteWriterUtil pclByteWriter,
boolean isComposite) {
this.charCode = charCode;
this.charFormat = charFormat;
this.charClass = charClass;
this.glyfData = glyfData;
this.pclByteWriter = pclByteWriter;
this.isComposite = isComposite;
// Glyph Data + (Descriptor Size) + (Character Data Size) + (Glyph ID) must
// be less than 32767 otherwise it will result in a continuation structure.
charDefinitionSize = glyfData.length + 4 + 2 + 2;
hasContinuation = charDefinitionSize > 32767;
composites = new ArrayList<PCLCharacterDefinition>();
}

public byte[] getCharacterCommand() throws IOException {
return pclByteWriter.writeCommand(String.format("*c%dE", (isComposite) ? 65535 : charCode));
}

public byte[] getCharacterDefinitionCommand() throws IOException {
return pclByteWriter.writeCommand(String.format("(s%dW", 10 + glyfData.length));
}

public byte[] getData() throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();

// Write Character Descriptor
if (!hasContinuation) {
writeCharacterDescriptorHeader(0, baos);
baos.write(glyfData);
} else {
int continuations = glyfData.length / 32767;
for (int i = 0; i < continuations; i++) {
writeCharacterDescriptorHeader(i == 0 ? 0 : 1, baos);
int continuationStart = i * 32767;
int continuationLength = continuationStart - glyfData.length < 32767
? continuationStart - glyfData.length : 32767;
baos.write(glyfData, continuationStart, continuationLength);
}
}
baos.write(0); // Reserved
byte[] charBytes = baos.toByteArray();
long sum = 0;
for (int i = 4; i < charBytes.length; i++) {
sum += charBytes[i];
}
int remainder = (int) (sum % 256);
baos.write(256 - remainder); // Checksum

return baos.toByteArray();
}

private void writeCharacterDescriptorHeader(int continuation, ByteArrayOutputStream baos) throws IOException {
baos.write(pclByteWriter.unsignedByte(charFormat.getValue()));
baos.write(continuation);
baos.write(pclByteWriter.unsignedByte(2)); // Descriptor size (from this byte to character data)
baos.write(pclByteWriter.unsignedByte(charClass.getValue()));
baos.write(pclByteWriter.unsignedInt(glyfData.length + 4));
baos.write(pclByteWriter.unsignedInt(charCode));
}

public void addCompositeGlyph(PCLCharacterDefinition composite) {
composites.add(composite);
}

public List<PCLCharacterDefinition> getCompositeGlyphs() {
return composites;
}

/**
* Character Format used in PCL Character Descriptor See Table 11-50 from PCL 5 Specification
*/
public enum PCLCharacterFormat {
LaserJet_Raster(4),
Intellifont(10),
TrueType(15);

private int value;

PCLCharacterFormat(int value) {
this.value = value;
}

public int getValue() {
return value;
}
}

/**
* Character Class used in PCL Character Descriptor See Table 11-51 from PCL 5 Specification
*/
public enum PCLCharacterClass {
Bitmap(1),
CompressedBitmap(2),
Contour_Intellifont(3),
Compound_Contour_Intellifont(4),
TrueType(15);

private int value;

PCLCharacterClass(int value) {
this.value = value;
}

public int getValue() {
return value;
}
}
}

+ 42
- 0
src/java/org/apache/fop/render/pcl/fonts/PCLCharacterWriter.java View File

@@ -0,0 +1,42 @@
/*
* 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.pcl.fonts;

import java.io.IOException;

import org.apache.fop.fonts.truetype.FontFileReader;
import org.apache.fop.fonts.truetype.OpenFont;

public abstract class PCLCharacterWriter {

protected PCLSoftFont font;
protected PCLByteWriterUtil pclByteWriter;
protected OpenFont openFont;
protected FontFileReader fontReader;

public PCLCharacterWriter(PCLSoftFont font) throws IOException {
this.font = font;
openFont = font.getOpenFont();
fontReader = font.getReader();
pclByteWriter = new PCLByteWriterUtil();
}

public abstract byte[] writeCharacterDefinitions(String text) throws IOException;
}

+ 113
- 0
src/java/org/apache/fop/render/pcl/fonts/PCLFontReader.java View File

@@ -0,0 +1,113 @@
/*
* 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.pcl.fonts;

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

import org.apache.fop.fonts.CustomFont;
import org.apache.fop.fonts.Typeface;
import org.apache.fop.fonts.truetype.FontFileReader;
import org.apache.fop.fonts.truetype.OpenFont;

public abstract class PCLFontReader {

protected Typeface typeface;
protected PCLByteWriterUtil pclByteWriter;
protected CustomFont font;

public PCLFontReader(Typeface font, PCLByteWriterUtil pclByteWriter) {
this.typeface = font;
this.pclByteWriter = pclByteWriter;
}

public void setFont(CustomFont mbFont) {
this.font = mbFont;
}

/** Header Data **/
public abstract int getDescriptorSize();
public abstract int getHeaderFormat();
public abstract int getFontType();
public abstract int getStyleMSB();
public abstract int getBaselinePosition();
public abstract int getCellWidth();
public abstract int getCellHeight();
public abstract int getOrientation();
public abstract int getSpacing();
public abstract int getSymbolSet();
public abstract int getPitch();
public abstract int getHeight();
public abstract int getXHeight();
public abstract int getWidthType();
public abstract int getStyleLSB();
public abstract int getStrokeWeight();
public abstract int getTypefaceLSB();
public abstract int getTypefaceMSB();
public abstract int getSerifStyle();
public abstract int getQuality();
public abstract int getPlacement();
public abstract int getUnderlinePosition();
public abstract int getUnderlineThickness();
public abstract int getTextHeight();
public abstract int getTextWidth();
public abstract int getFirstCode();
public abstract int getLastCode();
public abstract int getPitchExtended();
public abstract int getHeightExtended();
public abstract int getCapHeight();
public abstract int getFontNumber();
public abstract String getFontName();
public abstract int getScaleFactor() throws IOException;
public abstract int getMasterUnderlinePosition() throws IOException;
public abstract int getMasterUnderlineThickness() throws IOException;
public abstract int getFontScalingTechnology();
public abstract int getVariety();

/** Segmented Font Data **/
public abstract List<PCLFontSegment> getFontSegments(Map<Character, Integer> mappedGlyphs)
throws IOException;

/** Character Definitions **/
public abstract Map<Integer, int[]> getCharacterOffsets() throws IOException;

public abstract OpenFont getFontFile();
public abstract FontFileReader getFontFileReader();

/**
* Gets the most significant byte from a 16-bit integer
* @param s The number
* @return The resulting byte value as an integer
*/
protected int getMSB(int s) {
return s >> 8;
}

/**
* Gets the least significant byte from a 16-bit integer
* @param s The number
* @return The resulting byte value as an integer
*/
protected int getLSB(int s) {
byte b1 = (byte) (s >> 8);
return s;
}
}

+ 65
- 0
src/java/org/apache/fop/render/pcl/fonts/PCLFontReaderFactory.java View File

@@ -0,0 +1,65 @@
/*
* 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.pcl.fonts;

import java.io.IOException;

import org.apache.fop.fonts.CIDFontType;
import org.apache.fop.fonts.CustomFont;
import org.apache.fop.fonts.FontType;
import org.apache.fop.fonts.MultiByteFont;
import org.apache.fop.fonts.Typeface;
import org.apache.fop.render.java2d.CustomFontMetricsMapper;
import org.apache.fop.render.pcl.fonts.truetype.PCLTTFFontReader;

public final class PCLFontReaderFactory {

private PCLByteWriterUtil pclByteWriter;

private PCLFontReaderFactory(PCLByteWriterUtil pclByteWriter) {
this.pclByteWriter = pclByteWriter;
}

public static PCLFontReaderFactory getInstance(PCLByteWriterUtil pclByteWriter) {
return new PCLFontReaderFactory(pclByteWriter);
}

public PCLFontReader createInstance(Typeface font) throws IOException {
if (font.getFontType() == FontType.TRUETYPE || isCIDType2(font)) {
return new PCLTTFFontReader(font, pclByteWriter);
}
// else if (font instanceof MultiByteFont && ((MultiByteFont) font).isOTFFile()) {
// Placeholder for future Type 1 / OTF Soft font implementations e.g.
// return new PCLOTFFontReader(font, pclByteWriter);
// }
return null;
}

private boolean isCIDType2(Typeface font) {
CustomFontMetricsMapper fontMetrics = (CustomFontMetricsMapper) font;
CustomFont customFont = (CustomFont) fontMetrics.getRealFont();

if (customFont instanceof MultiByteFont) {
return ((MultiByteFont) customFont).getCIDType() == CIDFontType.CIDTYPE2;
}
return false;
}

}

+ 62
- 0
src/java/org/apache/fop/render/pcl/fonts/PCLFontSegment.java View File

@@ -0,0 +1,62 @@
/*
* 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.pcl.fonts;

public class PCLFontSegment {
private SegmentID identifier;
private byte[] data;

public PCLFontSegment(SegmentID identifier, byte[] data) {
this.identifier = identifier;
this.data = data;
}

public byte[] getData() {
return data;
}

public SegmentID getIdentifier() {
return identifier;
}

public int getSize() {
return (identifier == SegmentID.NULL) ? 0 : data.length;
}

public enum SegmentID {
CC(17219), // Character Complement
CP(17232), // Copyright
GT(18260), // Global TrueType Data
IF(18758), // Intellifont Face Data
PA(20545), // PANOSE Description
XW(22619), // XWindows Font Name
NULL(65535); // Null Segment

private int complementID;

SegmentID(int complementID) {
this.complementID = complementID;
}

public int getValue() {
return complementID;
}
}
}

+ 160
- 0
src/java/org/apache/fop/render/pcl/fonts/PCLSoftFont.java View File

@@ -0,0 +1,160 @@
/*
* 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.pcl.fonts;

import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

import org.apache.fop.fonts.MultiByteFont;
import org.apache.fop.fonts.Typeface;
import org.apache.fop.fonts.truetype.FontFileReader;
import org.apache.fop.fonts.truetype.OpenFont;
import org.apache.fop.render.java2d.CustomFontMetricsMapper;

public class PCLSoftFont {
private int fontID;
private Typeface font;
private Map<Integer, int[]> charOffsets;
private OpenFont openFont;
private InputStream fontStream;
private FontFileReader reader;
/** Map containing unicode character and it's soft font codepoint **/
private Map<Integer, Integer> charsWritten;
private Map<Character, Integer> mappedChars;
private Map<Integer, Integer> charMtxPositions;
private boolean multiByteFont;
private int charCount = 32;

public PCLSoftFont(int fontID, Typeface font, boolean multiByteFont) {
this.fontID = fontID;
this.font = font;
charsWritten = new HashMap<Integer, Integer>();
mappedChars = new HashMap<Character, Integer>();
this.multiByteFont = multiByteFont;
}

public Typeface getTypeface() {
return font;
}

public int getFontID() {
return fontID;
}

public void setCharacterOffsets(Map<Integer, int[]> charOffsets) {
this.charOffsets = charOffsets;
}

public Map<Integer, int[]> getCharacterOffsets() {
return charOffsets;
}

public OpenFont getOpenFont() {
return openFont;
}

public void setOpenFont(OpenFont openFont) {
this.openFont = openFont;
}

public InputStream getFontStream() {
return fontStream;
}

public void setFontStream(InputStream fontStream) {
this.fontStream = fontStream;
}

public FontFileReader getReader() {
return reader;
}

public void setReader(FontFileReader reader) {
this.reader = reader;
}

public void writeCharacter(int unicode) {
charsWritten.put(unicode, charCount++);
}

public int getUnicodeCodePoint(int unicode) {
if (charsWritten.containsKey(unicode)) {
return charsWritten.get(unicode);
} else {
return -1;
}
}

public boolean hasPreviouslyWritten(int unicode) {
return charsWritten.containsKey(unicode);
}

public int getMtxCharIndex(int unicode) {
if (charMtxPositions.get(unicode) != null) {
return charMtxPositions.get(unicode);
}
return 0;
}

public int getCmapGlyphIndex(int unicode) {
if (font instanceof CustomFontMetricsMapper) {
CustomFontMetricsMapper customFont = (CustomFontMetricsMapper) font;
Typeface realFont = customFont.getRealFont();
if (realFont instanceof MultiByteFont) {
MultiByteFont mbFont = (MultiByteFont) realFont;
return mbFont.findGlyphIndex(unicode);
}
}
return 0;
}

public void setMtxCharIndexes(Map<Integer, Integer> charMtxPositions) {
this.charMtxPositions = charMtxPositions;
}

public int getCharCount() {
return charCount;
}

public void setMappedChars(Map<Character, Integer> mappedChars) {
this.mappedChars = mappedChars;
}

public Map<Character, Integer> getMappedChars() {
return mappedChars;
}

public int getCharIndex(char ch) {
if (mappedChars.containsKey(ch)) {
return mappedChars.get(ch);
} else {
return -1;
}
}

public int getCharCode(char ch) {
if (multiByteFont) {
return getCharIndex(ch);
} else {
return getUnicodeCodePoint(ch);
}
}
}

+ 278
- 0
src/java/org/apache/fop/render/pcl/fonts/PCLSoftFontManager.java View File

@@ -0,0 +1,278 @@
/*
* 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.pcl.fonts;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.fop.fonts.CustomFont;
import org.apache.fop.fonts.Typeface;
import org.apache.fop.render.java2d.CustomFontMetricsMapper;

public class PCLSoftFontManager {
private ByteArrayOutputStream baos = new ByteArrayOutputStream();
private PCLFontReader fontReader;
private PCLByteWriterUtil pclByteWriter = new PCLByteWriterUtil();
private List<PCLSoftFont> fonts = new ArrayList<PCLSoftFont>();
private PCLFontReaderFactory fontReaderFactory;

private static final int SOFT_FONT_SIZE = 255;

public ByteArrayOutputStream makeSoftFont(Typeface font) throws IOException {
List<Map<Character, Integer>> mappedGlyphs = mapFontGlyphs(font);
if (fontReaderFactory == null) {
fontReaderFactory = PCLFontReaderFactory.getInstance(pclByteWriter);
}
fontReader = fontReaderFactory.createInstance(font);
initialize();
if (mappedGlyphs.isEmpty()) {
mappedGlyphs.add(new HashMap<Character, Integer>());
}
if (fontReader != null) {
for (Map<Character, Integer> glyphSet : mappedGlyphs) {
PCLSoftFont softFont = new PCLSoftFont(fonts.size() + 1, font,
mappedGlyphs.get(0).size() != 0);
softFont.setMappedChars(glyphSet);
assignFontID();
writeFontHeader(softFont.getMappedChars());
softFont.setCharacterOffsets(fontReader.getCharacterOffsets());
softFont.setOpenFont(fontReader.getFontFile());
softFont.setReader(fontReader.getFontFileReader());
fonts.add(softFont);
}
return baos;
} else {
return null;
}
}

private List<Map<Character, Integer>> mapFontGlyphs(Typeface tf) {
List<Map<Character, Integer>> mappedGlyphs = new ArrayList<Map<Character, Integer>>();
if (tf instanceof CustomFontMetricsMapper) {
CustomFontMetricsMapper fontMetrics = (CustomFontMetricsMapper) tf;
CustomFont customFont = (CustomFont) fontMetrics.getRealFont();
mappedGlyphs = mapGlyphs(customFont.getUsedGlyphs(), customFont);
}
return mappedGlyphs;
}

private List<Map<Character, Integer>> mapGlyphs(Map<Integer, Integer> usedGlyphs, CustomFont font) {
int charCount = 32;
List<Map<Character, Integer>> mappedGlyphs = new ArrayList<Map<Character, Integer>>();
Map<Character, Integer> fontGlyphs = new HashMap<Character, Integer>();
for (Entry<Integer, Integer> entry : usedGlyphs.entrySet()) {
int glyphID = entry.getKey();
if (glyphID == 0) {
continue;
}
char unicode = font.getUnicodeFromGID(glyphID);
if (charCount > SOFT_FONT_SIZE) {
mappedGlyphs.add(fontGlyphs);
charCount = 32;
fontGlyphs = new HashMap<Character, Integer>();
}
fontGlyphs.put(unicode, charCount++);
}
if (fontGlyphs.size() > 0) {
mappedGlyphs.add(fontGlyphs);
}
return mappedGlyphs;
}

private void initialize() {
baos.reset();
}

private void assignFontID() throws IOException {
baos.write(assignFontID(fonts.size() + 1));
}

public byte[] assignFontID(int fontID) throws IOException {
return pclByteWriter.writeCommand(String.format("*c%dD", fontID));
}

private void writeFontHeader(Map<Character, Integer> mappedGlyphs) throws IOException {
ByteArrayOutputStream header = new ByteArrayOutputStream();
header.write(pclByteWriter.unsignedInt(fontReader.getDescriptorSize()));
header.write(pclByteWriter.unsignedByte(fontReader.getHeaderFormat()));
header.write(pclByteWriter.unsignedByte(fontReader.getFontType()));
header.write(pclByteWriter.unsignedByte(fontReader.getStyleMSB()));
header.write(0); // Reserved
header.write(pclByteWriter.unsignedInt(fontReader.getBaselinePosition()));
header.write(pclByteWriter.unsignedInt(fontReader.getCellWidth()));
header.write(pclByteWriter.unsignedInt(fontReader.getCellHeight()));
header.write(pclByteWriter.unsignedByte(fontReader.getOrientation()));
header.write(fontReader.getSpacing());
header.write(pclByteWriter.unsignedInt(fontReader.getSymbolSet()));
header.write(pclByteWriter.unsignedInt(fontReader.getPitch()));
header.write(pclByteWriter.unsignedInt(fontReader.getHeight()));
header.write(pclByteWriter.unsignedInt(fontReader.getXHeight()));
header.write(pclByteWriter.signedByte(fontReader.getWidthType()));
header.write(pclByteWriter.unsignedByte(fontReader.getStyleLSB()));
header.write(pclByteWriter.signedByte(fontReader.getStrokeWeight()));
header.write(pclByteWriter.unsignedByte(fontReader.getTypefaceLSB()));
header.write(pclByteWriter.unsignedByte(fontReader.getTypefaceMSB()));
header.write(pclByteWriter.unsignedByte(fontReader.getSerifStyle()));
header.write(pclByteWriter.unsignedByte(fontReader.getQuality()));
header.write(pclByteWriter.signedByte(fontReader.getPlacement()));
header.write(pclByteWriter.signedByte(fontReader.getUnderlinePosition()));
header.write(pclByteWriter.unsignedByte(fontReader.getUnderlineThickness()));
header.write(pclByteWriter.unsignedInt(fontReader.getTextHeight()));
header.write(pclByteWriter.unsignedInt(fontReader.getTextWidth()));
header.write(pclByteWriter.unsignedInt(fontReader.getFirstCode()));
header.write(pclByteWriter.unsignedInt(fontReader.getLastCode()));
header.write(pclByteWriter.unsignedByte(fontReader.getPitchExtended()));
header.write(pclByteWriter.unsignedByte(fontReader.getHeightExtended()));
header.write(pclByteWriter.unsignedInt(fontReader.getCapHeight()));
header.write(pclByteWriter.unsignedLongInt(fontReader.getFontNumber()));
header.write(pclByteWriter.padBytes(fontReader.getFontName().getBytes("US-ASCII"), 16, 32));
header.write(pclByteWriter.unsignedInt(fontReader.getScaleFactor()));
header.write(pclByteWriter.signedInt(fontReader.getMasterUnderlinePosition()));
header.write(pclByteWriter.unsignedInt(fontReader.getMasterUnderlineThickness()));
header.write(pclByteWriter.unsignedByte(fontReader.getFontScalingTechnology()));
header.write(pclByteWriter.unsignedByte(fontReader.getVariety()));

writeSegmentedFontData(header, mappedGlyphs);

baos.write(getFontHeaderCommand(header.size()));
baos.write(header.toByteArray());
}

private void writeSegmentedFontData(ByteArrayOutputStream header,
Map<Character, Integer> mappedGlyphs) throws IOException {
List<PCLFontSegment> fontSegments = fontReader.getFontSegments(mappedGlyphs);
for (PCLFontSegment segment : fontSegments) {
writeFontSegment(header, segment);
}
header.write(0); // Reserved
// Checksum must equal 0 when added to byte 64 offset (modulo 256)
long sum = 0;
byte[] headerBytes = header.toByteArray();
for (int i = 64; i < headerBytes.length; i++) {
sum += headerBytes[i];
}
int remainder = (int) (sum % 256);
header.write(256 - remainder);
}

private byte[] getFontHeaderCommand(int headerSize) throws IOException {
return pclByteWriter.writeCommand(String.format(")s%dW", headerSize));
}

private void writeFontSegment(ByteArrayOutputStream header, PCLFontSegment segment) throws IOException {
header.write(pclByteWriter.unsignedInt(segment.getIdentifier().getValue()));
header.write(pclByteWriter.unsignedInt(segment.getData().length));
header.write(segment.getData());
}

/**
* Finds a soft font associated with the given typeface. If more than one instance of the font exists (as each font
* is bound and restricted to 255 characters) it will find the last font with available capacity.
* @param font The typeface associated with the soft font
* @return Returns the PCLSoftFont with available capacity
*/
public PCLSoftFont getSoftFont(Typeface font, String text) {
for (PCLSoftFont sftFont : fonts) {
if (sftFont.getTypeface().equals(font)
&& sftFont.getCharCount() + countNonMatches(sftFont, text) < SOFT_FONT_SIZE) {
return sftFont;
}
}
return null;
}

public PCLSoftFont getSoftFontFromID(int index) {
return fonts.get(index - 1);
}

private int countNonMatches(PCLSoftFont font, String text) {
int result = 0;
for (char ch : text.toCharArray()) {
int value = font.getUnicodeCodePoint(ch);
if (value == -1) {
result++;
}
}
return result;
}

public int getSoftFontID(Typeface tf) throws IOException {
PCLSoftFont font = getSoftFont(tf, "");
for (int i = 0; i < fonts.size(); i++) {
if (fonts.get(i).equals(font)) {
return i + 1;
}
}
return -1;
}

public List<PCLTextSegment> getTextSegments(String text, Typeface font) {
List<PCLTextSegment> textSegments = new ArrayList<PCLTextSegment>();
int curFontID = -1;
String current = "";
for (char ch : text.toCharArray()) {
for (PCLSoftFont softFont : fonts) {
if (curFontID == -1) {
curFontID = softFont.getFontID();
}
if (softFont.getCharIndex(ch) == -1 || !softFont.getTypeface().equals(font)) {
continue;
}
if (current.length() > 0 && curFontID != softFont.getFontID()) {
textSegments.add(new PCLTextSegment(curFontID, current));
current = "";
curFontID = softFont.getFontID();
}
if (curFontID != softFont.getFontID()) {
curFontID = softFont.getFontID();
}
current += ch;
break;
}
}
if (current.length() > 0) {
textSegments.add(new PCLTextSegment(curFontID, current));
}
return textSegments;
}

public static class PCLTextSegment {
private String text;
private int fontID;

public PCLTextSegment(int fontID, String text) {
this.text = text;
this.fontID = fontID;
}

public String getText() {
return text;
}

public int getFontID() {
return fontID;
}
}
}

+ 200
- 0
src/java/org/apache/fop/render/pcl/fonts/PCLSymbolSet.java View File

@@ -0,0 +1,200 @@
/*
* 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.pcl.fonts;

/**
* Table C-1 from http://www.lprng.com/DISTRIB/RESOURCES/DOCS/pcl5comp.pdf
*/
public enum PCLSymbolSet {
// Unbound font containing > 256 characters
Unbound("1X", 56),

// Other symbol sets to use in bound fonts
Bound_Generic("0Q", 17),
GW_3212("18C", 597),
ISO_60_Danish_Norwegian("0D", 4),
Devanagari("2D", 68),
ISO_4_United_Kingdom("1E", 37),
Windows_3_1_Latin2("9E", 293),
ISO_69_French("1F", 38),
ISO_21_German("1G", 39),
Greek_8("8G", 283),
Windows_3_1_Latin_Greek("9G", 295),
PC_851_Latin_Greek("10G", 327),
PC_8_Latin_Greek("12G", 391),
Hebrew_7("0H", 8),
ISO_8859_8_Latin_Hebrew("7H", 232),
Hebrew_8("8H", 264),
PC_862_Latin_Hebrew("15H", 488),
ISO_15_Italian("0I", 9),
Microsoft_Publishing("6J", 202),
DeskTop("7J", 234),
Document("8J", 266),
PC_1004("9J", 298),
PS_Text("10J", 330),
PS_ISO_Latin1("11J", 362),
MC_Text("12J", 394),
Ventura_International3("13J", 426),
Ventura_US3("14J", 458),
Swash_Characters("16J", 522),
Small_Caps_Old_Style_Figures("17J", 554),
Old_Style_Figures("18J", 586),
Fractions("19J", 618),
Lining_Figures("21J", 682),
Small_Caps_and_Lining_Figures("22J", 714),
Alternate_Caps("23J", 746),
Kana_8_JIS_210("8K", 267),
Korean_8("9K", 299),

Line_Draw_7("0L", 12),
HP_Block_Characters("1L", 44),
Tax_Line_Draw("2L", 76),
Line_Draw_8("8L", 268),
Ventura_ITC_Zapf_Dingbats3("9L", 300),
PS_ITC_Zapf_Dingbats("10L", 332),
ITC_Zapf_Dingbats_Series_100("11L", 364),
ITC_Zapf_Dingbats_Series_200("12L", 396),
ITC_Zapf_Dingbats_Series_300("13L", 428),
Windows_Baltic("19L", 620),
Carta("20L", 652),
Ornaments("21L", 684),
Universal_News_Commercial_Pi("22L", 716),
Chess("23L", 748),
Astrology_1("24L", 780),
Pi_Set_1("31L", 1004),
Pi_Set_2("32L", 1036),
Pi_Set_3("33L", 1068),
Pi_Set_4("34L", 1100),
Pi_Set_5("35L", 1132),
Pi_Set_6("36L", 1164),
Wingdings("579L", 18540),
Math_7("0M", 13),
Tech_7("1M", 45),
PS_Math("5M", 173),
Ventura_Math3("6M", 205),
Math_8("8M", 269),
Universal_Greek_Math_Pi("10M", 333),
TeX_Math_Extension("11M", 365),
TeX_Math_Symbol("12M", 397),
TeX_Math_Italic("13M", 429),
Symbol("19M", 621),
ISO_8859_1_Latin_1("0N", 14),
ISO_8859_2_Latin_2("2N", 78),

ISO_8859_3_Latin_3("3N", 110),
ISO_8859_4_Latin_4("4N", 142),
ISO_8859_9_Latin_5("5N", 174),
ISO_8859_10_Latin_6("6N", 206),
ISO_8859_5_Latin_Cyrillic("10N", 334),
ISO_8859_6_Latin_Arabic("11N", 366),
ISO_8859_7_Latin_Greek("12N", 398),
OCR_A("0O", 15),
OCR_B("1O", 47),
OCR_M("2O", 79),
MICR_E13B("10O", 335),
Typewriter_Paired_APL("0P", 16),
Bit_Paired_APL("1P", 48),
Expert("10P", 336),
Alternate("11P", 368),
Fraktur("12P", 400),
Cyrillic_ASCII_8859_5_1986("0R", 18),
Cyrillic("1R", 50),
PC_Cyrillic("3R", 114),
Windows_3_1_Latin_Cyrillic("9R", 306),
ISO_11_Swedish("0S", 19),
ISO_17_Spanish3("2S", 83),
HP_European_Spanish("7S", 243),
HP_Latin_Spanish("8S", 275),
HP_GL_Download("16S", 531),
HP_GL_Drafting("17S", 563),
HP_GL_Special_Symbols("18S", 595),
Sonata("20S", 659),
Thai_8("0T", 20),
TISI_620_2533_Thai("1T", 52),
Windows_3_1_Latin_5("5T", 180),
Turkish_8("8T", 276),

PC_8_Turkish("9T", 308),
Teletex("10T", 340),
ISO_6_ASCII("0U", 21),
Legal("1U", 53),
HPL("5U", 181),
OEM_1("7U", 245),
Roman_8("8U", 277),
Windows_3_0_Latin_1("9U", 309),
PC_8_Code_Page_437("10U", 341),
PC_8_D_N_Danish_Norwegian("11U", 373),
PC_850_Multilingual("12U", 405),
Pi_Font("15U", 501),
PC_857("16U", 533),
PC_852_Latin_2("17U", 565),
Windows_3_1_Latin_1("19U", 629),
PC_860_Portugal("20U", 661),
PC_861_Iceland("21U", 693),
PC_863_Canada_French("23U", 757),
PC_865_Norway("25U", 821),
PC_775("26U", 853),
Arabic_8("8V", 278),
Windows_3_1_Latin_Arabic("9V", 310),
Code_Page_864_Latin_Arabic("10V", 342),
Barcode_3of9("0Y", 25),
Industrial_2_of_5_Barcode("1Y", 57),
Matrix_2_of_5_Barcode("2Y", 89),
Interleaved_2_of_5_Barcode("4Y", 153),
CODABAR_Barcode("5Y", 185),
MSI_Plessey_Barcode("6Y", 217),
Code_11_Barcode("7Y", 249),
UPC_EAN_Barcode("8Y", 281),
MICR_CMC_7("14Y", 473),
USPS_ZIP("5Y", 505),

Math_7_2("0A", 1),
Line_Draw_7_2("0B", 2),
HP_Large_Characters("0C", 3),
ISO_61_Norwegian_Version_2("1D", 36),
Roman_Extension("0E", 5),
ISO_25_French("0F", 6),
HP_German("0G", 7),
ISO_14_JIS_ASCII("0K", 11),
ISO_13_Katakana("1K", 43),
ISO_57_Chinese("2K", 75),
HP_Spanish("1S", 51),
ISO_10_Swedish("3S", 115),
ISO_16_Portuguese("4S", 147),
ISO_84_Portuguese("5S", 179),
ISO_85_Spanish("6S", 211),
ISO_2_International_Reference("2U", 85),
Arabic("0V", 22);

private String symbolSetID;
private int kind1;

PCLSymbolSet(String symbolSetID, int kind1) {
this.kind1 = kind1;
}

public String getSymbolSetID() {
return symbolSetID;
}

public int getKind1() {
return kind1;
}
}

+ 154
- 0
src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFCharacterWriter.java View File

@@ -0,0 +1,154 @@
/*
* 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.pcl.fonts.truetype;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.fop.fonts.truetype.GlyfTable;
import org.apache.fop.fonts.truetype.OFDirTabEntry;
import org.apache.fop.fonts.truetype.OFMtxEntry;
import org.apache.fop.fonts.truetype.OFTableName;
import org.apache.fop.fonts.truetype.TTFFile;
import org.apache.fop.render.pcl.fonts.PCLCharacterDefinition;
import org.apache.fop.render.pcl.fonts.PCLCharacterDefinition.PCLCharacterClass;
import org.apache.fop.render.pcl.fonts.PCLCharacterDefinition.PCLCharacterFormat;
import org.apache.fop.render.pcl.fonts.PCLCharacterWriter;
import org.apache.fop.render.pcl.fonts.PCLSoftFont;

public class PCLTTFCharacterWriter extends PCLCharacterWriter {

private List<OFMtxEntry> mtx;
private OFDirTabEntry tabEntry;

public PCLTTFCharacterWriter(PCLSoftFont softFont) throws IOException {
super(softFont);
softFont.setMtxCharIndexes(scanMtxCharacters());
}

@Override
public byte[] writeCharacterDefinitions(String text) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
for (char ch : text.toCharArray()) {
int character = (int) ch;
if (!font.hasPreviouslyWritten(character)) {
PCLCharacterDefinition pclChar = getCharacterDefinition(ch);
writePCLCharacter(baos, pclChar);
List<PCLCharacterDefinition> compositeGlyphs = pclChar.getCompositeGlyphs();
for (PCLCharacterDefinition composite : compositeGlyphs) {
writePCLCharacter(baos, composite);
}
}
}
return baos.toByteArray();
}

private void writePCLCharacter(ByteArrayOutputStream baos, PCLCharacterDefinition pclChar) throws IOException {
baos.write(pclChar.getCharacterCommand());
baos.write(pclChar.getCharacterDefinitionCommand());
baos.write(pclChar.getData());
}

private Map<Integer, Integer> scanMtxCharacters() throws IOException {
Map<Integer, Integer> charMtxOffsets = new HashMap<Integer, Integer>();
List<OFMtxEntry> mtx = openFont.getMtx();
OFTableName glyfTag = OFTableName.GLYF;
if (openFont.seekTab(fontReader, glyfTag, 0)) {
for (int i = 1; i < mtx.size(); i++) {
OFMtxEntry entry = mtx.get(i);
int charCode = 0;
if (entry.getUnicodeIndex().size() > 0) {
charCode = (Integer) entry.getUnicodeIndex().get(0);
} else {
charCode = entry.getIndex();
}
charMtxOffsets.put(charCode, i);
}
}
return charMtxOffsets;
}

private PCLCharacterDefinition getCharacterDefinition(int unicode) throws IOException {
if (mtx == null) {
mtx = openFont.getMtx();
tabEntry = openFont.getDirectoryEntry(OFTableName.GLYF);
}
if (openFont.seekTab(fontReader, OFTableName.GLYF, 0)) {
int charIndex = font.getMtxCharIndex(unicode);

// Fallback - only works for MultiByte fonts
if (charIndex == 0) {
charIndex = font.getCmapGlyphIndex(unicode);
}

Map<Integer, Integer> subsetGlyphs = new HashMap<Integer, Integer>();
subsetGlyphs.put(charIndex, 1);

byte[] glyphData = getGlyphData(charIndex);

font.writeCharacter(unicode);

PCLCharacterDefinition newChar = new PCLCharacterDefinition(
font.getCharCode((char) unicode),
PCLCharacterFormat.TrueType,
PCLCharacterClass.TrueType, glyphData, pclByteWriter, false);

// Handle composite character definitions
GlyfTable glyfTable = new GlyfTable(fontReader, mtx.toArray(new OFMtxEntry[mtx.size()]),
tabEntry, subsetGlyphs);
if (glyfTable.isComposite(charIndex)) {
Set<Integer> composites = glyfTable.retrieveComposedGlyphs(charIndex);
for (Integer compositeIndex : composites) {
byte[] compositeData = getGlyphData(compositeIndex);
newChar.addCompositeGlyph(new PCLCharacterDefinition(compositeIndex,
PCLCharacterFormat.TrueType,
PCLCharacterClass.TrueType, compositeData, pclByteWriter, true));
}
}

return newChar;
}
return null;
}

private byte[] getGlyphData(int charIndex) throws IOException {
OFMtxEntry entry = mtx.get(charIndex);
OFMtxEntry nextEntry;
int nextOffset = 0;
if (charIndex < mtx.size() - 1) {
nextEntry = mtx.get(charIndex + 1);
nextOffset = (int) nextEntry.getOffset();
} else {
nextOffset = (int) ((TTFFile) openFont).getLastGlyfLocation();
}
int glyphOffset = (int) entry.getOffset();
int glyphLength = nextOffset - glyphOffset;

byte[] glyphData = new byte[0];
if (glyphLength > 0) {
glyphData = fontReader.getBytes((int) tabEntry.getOffset() + glyphOffset, glyphLength);
}
return glyphData;
}
}

+ 731
- 0
src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFFontReader.java View File

@@ -0,0 +1,731 @@
/*
* 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.pcl.fonts.truetype;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.fop.fonts.CustomFont;
import org.apache.fop.fonts.MultiByteFont;
import org.apache.fop.fonts.SingleByteFont;
import org.apache.fop.fonts.Typeface;
import org.apache.fop.fonts.truetype.FontFileReader;
import org.apache.fop.fonts.truetype.OFDirTabEntry;
import org.apache.fop.fonts.truetype.OFFontLoader;
import org.apache.fop.fonts.truetype.OFMtxEntry;
import org.apache.fop.fonts.truetype.OFTableName;
import org.apache.fop.fonts.truetype.OpenFont;
import org.apache.fop.fonts.truetype.TTFFile;
import org.apache.fop.render.java2d.CustomFontMetricsMapper;
import org.apache.fop.render.pcl.fonts.PCLByteWriterUtil;
import org.apache.fop.render.pcl.fonts.PCLFontReader;
import org.apache.fop.render.pcl.fonts.PCLFontSegment;
import org.apache.fop.render.pcl.fonts.PCLFontSegment.SegmentID;
import org.apache.fop.render.pcl.fonts.PCLSymbolSet;

public class PCLTTFFontReader extends PCLFontReader {
protected TTFFile ttfFont;
protected InputStream fontStream;
protected FontFileReader reader;
private PCLTTFPCLTFontTable pcltTable;
private PCLTTFOS2FontTable os2Table;
private PCLTTFPOSTFontTable postTable;
private PCLTTFTableFactory ttfTableFactory;

private static final int HMTX_RESTRICT_SIZE = 50000;

private static final Map<Integer, Integer> FONT_WEIGHT = new HashMap<Integer, Integer>() {
private static final long serialVersionUID = 1L;
{
put(100, -6); // 100 Thin
put(200, -4); // 200 Extra-Light
put(300, -3); // 300 Light
put(400, 0); // 400 Normal (Regular)
put(500, 0); // 500 Medium
put(600, 2); // 600 Semi-bold
put(700, 3); // 700 Bold
put(800, 4); // 800 Extra-bold
put(900, 5); // 900 Black (Heavy)
}
};

private static final Map<Integer, Integer> FONT_SERIF = new HashMap<Integer, Integer>() {
private static final long serialVersionUID = 1L;
{
/** The following are the best guess conversion between serif styles. Unfortunately
* there appears to be no standard and so each specification has it's own set of values.
* Please change if better fit found. **/
put(0, 0); // Any = Normal Sans
put(1, 64); // No Fit = Sans Serif
put(2, 9); // Cove = Script Nonconnecting
put(3, 12); // Obtuse Cove = Script Broken Letter
put(4, 10); // Square Cove = Script Joining
put(5, 0); // Obtuse Square Cove = Sans Serif Square
put(6, 128); // Square = Serif
put(7, 2); // Thin = Serif Line
put(8, 7); // Bone = Rounded Bracket
put(9, 11); // Exeraggerated = Script Calligraphic
put(10, 3); // Triangle = Serif Triangle
put(11, 0); // Normal Sans = Sans Serif Square
put(12, 4); // Obtuse Sans = Serif Swath
put(13, 6); // Perp Sans = Serif Bracket
put(14, 8); // Flared = Flair Serif
put(15, 1); // Rounded = Sans Serif Round
}
};

private static final Map<Integer, Integer> FONT_WIDTH = new HashMap<Integer, Integer>() {
private static final long serialVersionUID = 1L;
{
/** The conversions between TTF and PCL are not 1 to 1 **/
put(1, -5); // 1 = Ultra Compressed
put(2, -4); // 2 = Extra Compressed
put(3, -3); // 3 = Compresses
put(4, -2); // 4 = Condensed
put(5, 0); // 5 = Normal
put(6, 2); // 6 = Expanded
put(7, 3); // 5 = Extra Expanded
}
};

private int scaleFactor = -1;
private PCLSymbolSet symbolSet = PCLSymbolSet.Bound_Generic;

public PCLTTFFontReader(Typeface font, PCLByteWriterUtil pclByteWriter) throws IOException {
super(font, pclByteWriter);
loadFont();
}

protected void loadFont() throws IOException {
if (typeface instanceof CustomFontMetricsMapper) {
CustomFontMetricsMapper fontMetrics = (CustomFontMetricsMapper) typeface;
CustomFont font = (CustomFont) fontMetrics.getRealFont();
setFont((CustomFont) fontMetrics.getRealFont());
String fontName = font.getFullName();
fontStream = font.getInputStream();
reader = new FontFileReader(fontStream);

ttfFont = new TTFFile();
String header = OFFontLoader.readHeader(reader);
ttfFont.readFont(reader, header, fontName);
readFontTables();
} else {
// TODO - Handle when typeface is not in the expected format for a PCL TrueType object
}
}

protected void readFontTables() throws IOException {
PCLTTFTable fontTable;
fontTable = readFontTable(OFTableName.PCLT);
if (fontTable instanceof PCLTTFPCLTFontTable) {
pcltTable = (PCLTTFPCLTFontTable) fontTable;
}
fontTable = readFontTable(OFTableName.OS2);
if (fontTable instanceof PCLTTFOS2FontTable) {
os2Table = (PCLTTFOS2FontTable) fontTable;
}
fontTable = readFontTable(OFTableName.POST);
if (fontTable instanceof PCLTTFPOSTFontTable) {
postTable = (PCLTTFPOSTFontTable) fontTable;
}
}

private PCLTTFTable readFontTable(OFTableName tableName) throws IOException {
if (ttfFont.seekTab(reader, tableName, 0)) {
return getTTFTableFactory().newInstance(tableName);
}
return null;
}

private PCLTTFTableFactory getTTFTableFactory() {
if (ttfTableFactory == null) {
ttfTableFactory = PCLTTFTableFactory.getInstance(reader);
}
return ttfTableFactory;
}

@Override
public int getDescriptorSize() {
return 72; // Descriptor size (leave at 72 for our purposes)
}

@Override
public int getHeaderFormat() {
return 15; // TrueType Scalable Font
}

@Override
public int getFontType() {
if (symbolSet == PCLSymbolSet.Unbound) {
return 11; // Font Type - Unbound TrueType Scalable font
} else {
return 2; // 0-255 (except 0, 7 and 27)
}
}

@Override
public int getStyleMSB() {
if (pcltTable != null) {
return getMSB(pcltTable.getStyle());
}
return 3;
}

@Override
public int getBaselinePosition() {
return 0; // Baseline position must be set to 0 for TTF fonts
}

@Override
public int getCellWidth() {
int[] bbox = ttfFont.getBBoxRaw();
return bbox[2] - bbox[0];
}

@Override
public int getCellHeight() {
int[] bbox = ttfFont.getBBoxRaw();
return bbox[3] - bbox[1];
}

@Override
public int getOrientation() {
return 0; // Scalable fonts (TrueType) must be 0
}

@Override
public int getSpacing() {
if (os2Table != null) {
return (os2Table.getPanose()[4] == 9) ? 0 : 1;
} else if (postTable != null) {
return postTable.getIsFixedPitch();
}
return 1;
}

@Override
public int getSymbolSet() {
if (pcltTable != null) {
return pcltTable.getSymbolSet();
} else {
return symbolSet.getKind1();
}
}

@Override
public int getPitch() {
int pitch = ttfFont.getCharWidthRaw(0x20);
if (pitch < 0) {
// No advance width found for the space character
return 0;
}
return pitch;
}

@Override
public int getHeight() {
return 0; // Fixed zero value for TrueType fonts
}

@Override
public int getXHeight() {
if (pcltTable != null) {
return pcltTable.getXHeight();
} else if (os2Table != null) {
return os2Table.getXHeight();
}
return 0;
}

@Override
public int getWidthType() {
if (pcltTable != null) {
return pcltTable.getWidthType();
} else if (os2Table != null) {
return convertTTFWidthClass(os2Table.getWidthClass());
}
return 0;
}

private int convertTTFWidthClass(int widthClass) {
if (FONT_WIDTH.containsKey(widthClass)) {
return FONT_WIDTH.get(widthClass);
} else {
return 0; // No match - return normal
}
}

@Override
public int getStyleLSB() {
if (pcltTable != null) {
return getLSB(pcltTable.getStyle());
}
return 224;
}

@Override
public int getStrokeWeight() {
if (pcltTable != null) {
return pcltTable.getStrokeWeight();
} else if (os2Table != null) {
return convertTTFWeightClass(os2Table.getWeightClass());
}
return 0;
}

private int convertTTFWeightClass(int weightClass) {
if (FONT_WEIGHT.containsKey(weightClass)) {
return FONT_WEIGHT.get(weightClass);
} else {
return 0; // No match - return normal
}
}

@Override
public int getTypefaceLSB() {
if (pcltTable != null) {
return getLSB(pcltTable.getTypeFamily());
}
return 254;
}

@Override
public int getTypefaceMSB() {
if (pcltTable != null) {
return getMSB(pcltTable.getTypeFamily());
}
return 0;
}

@Override
public int getSerifStyle() {
if (pcltTable != null) {
return pcltTable.getSerifStyle();
} else {
return convertFromTTFSerifStyle();
}
}

private int convertFromTTFSerifStyle() {
if (os2Table != null) {
int serifStyle = os2Table.getPanose()[1];
return FONT_SERIF.get(serifStyle);
}
return 0;
}

@Override
public int getQuality() {
return 2; // Letter quality
}

@Override
public int getPlacement() {
return 0; // Fixed value of 0 for TrueType (scalable fonts)
}

@Override
public int getUnderlinePosition() {
return 0; // Scalable fonts has a fixed value of 0 - See Master Underline Position
}

@Override
public int getUnderlineThickness() {
return 0; // Scalable fonts has a fixed value of 0 - See Master Underline Thickness
}

@Override
public int getTextHeight() {
return 2048;
}

@Override
public int getTextWidth() {
if (os2Table != null) {
return os2Table.getAvgCharWidth();
}
return 0;
}

@Override
public int getFirstCode() {
return 32;
}

@Override
public int getLastCode() {
return 255; // Bound font with a maximum of 255 characters
}

@Override
public int getPitchExtended() {
return 0; // Zero for Scalable fonts
}

@Override
public int getHeightExtended() {
return 0; // Zero for Scalable fonts
}

@Override
public int getCapHeight() {
if (pcltTable != null) {
return pcltTable.getStrokeWeight();
} else if (os2Table != null) {
return os2Table.getCapHeight();
}
return 0;
}

@Override
public int getFontNumber() {
if (pcltTable != null) {
return (int) pcltTable.getFontNumber();
}
return 0;
}

@Override
public String getFontName() {
if (pcltTable != null) {
return pcltTable.getTypeface();
} else {
return ttfFont.getFullName();
}
}

@Override
public int getScaleFactor() throws IOException {
if (scaleFactor == -1) {
OFTableName headTag = OFTableName.HEAD;
if (ttfFont.seekTab(reader, headTag, 0)) {
reader.readTTFLong(); // Version
reader.readTTFLong(); // Font revision
reader.readTTFLong(); // Check sum adjustment
reader.readTTFLong(); // Magic number
reader.readTTFShort(); // Flags
scaleFactor = reader.readTTFUShort(); // Units per em
return scaleFactor;
}
} else {
return scaleFactor;
}
return 0;
}

@Override
public int getMasterUnderlinePosition() throws IOException {
return (int) Math.round(getScaleFactor() * 0.2);
}

@Override
public int getMasterUnderlineThickness() throws IOException {
return (int) Math.round(getScaleFactor() * 0.05);
}

@Override
public int getFontScalingTechnology() {
return 1; // TrueType scalable font
}

@Override
public int getVariety() {
return 0; // TrueType fonts must be set to zero
}

public List<PCLFontSegment> getFontSegments(Map<Character, Integer> mappedGlyphs)
throws IOException {
List<PCLFontSegment> fontSegments = new ArrayList<PCLFontSegment>();
fontSegments.add(new PCLFontSegment(SegmentID.CC, getCharacterComplement()));
fontSegments.add(new PCLFontSegment(SegmentID.PA, pclByteWriter.toByteArray(os2Table.getPanose())));
fontSegments.add(new PCLFontSegment(SegmentID.GT, getGlobalTrueTypeData(mappedGlyphs)));
fontSegments.add(new PCLFontSegment(SegmentID.CP, ttfFont.getCopyrightNotice().getBytes("US-ASCII")));
fontSegments.add(new PCLFontSegment(SegmentID.NULL, new byte[0]));
return fontSegments;
}

/**
* See Font Header Format 11-35 (Character Complement Array) in the PCL 5 Specification. Defined as an array of 8
* bytes specific to certain character sets. In this case specifying 0 for all values (default complement) means the
* font is compatible with any character sets. '110' on least significant bits signifies unicode. See specification
* for further customization.
*/
private byte[] getCharacterComplement() {
byte[] ccUnicode = new byte[8];
ccUnicode[7] = 6;
return ccUnicode;
}

private byte[] getGlobalTrueTypeData(Map<Character, Integer> mappedGlyphs) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
List<TableOffset> tableOffsets = new ArrayList<TableOffset>();
// Version
baos.write(pclByteWriter.unsignedInt(1)); // Major
baos.write(pclByteWriter.unsignedInt(0)); // Minor
int numTables = 5; // head, hhea, hmtx, maxp and gdir
// Optional Hint Tables
OFDirTabEntry headTable = ttfFont.getDirectoryEntry(OFTableName.CVT);
if (headTable != null) {
numTables++;
}
OFDirTabEntry fpgmTable = ttfFont.getDirectoryEntry(OFTableName.FPGM);
if (fpgmTable != null) {
numTables++;
}
OFDirTabEntry prepTable = ttfFont.getDirectoryEntry(OFTableName.PREP);
if (prepTable != null) {
numTables++;
}
baos.write(pclByteWriter.unsignedInt(numTables)); // numTables
int maxPowerNumTables = pclByteWriter.maxPower2(numTables);
int searchRange = maxPowerNumTables * 16;
baos.write(pclByteWriter.unsignedInt(searchRange));
baos.write(pclByteWriter.unsignedInt(pclByteWriter.log(maxPowerNumTables, 2))); // Entry Selector
baos.write(pclByteWriter.unsignedInt(numTables * 16 - searchRange)); // Range shift

// Add default data tables
writeTrueTypeTable(baos, OFTableName.HEAD, tableOffsets);
writeTrueTypeTable(baos, OFTableName.HHEA, tableOffsets);
byte[] hmtxTable = createHmtx(mappedGlyphs);
writeSubsetHMTX(baos, OFTableName.HMTX, tableOffsets, hmtxTable);
writeTrueTypeTable(baos, OFTableName.MAXP, tableOffsets);
// Write the blank GDIR directory which is built in memory on the printer
writeGDIR(baos);

// Add optional data tables (for hints)
writeTrueTypeTable(baos, OFTableName.CVT, tableOffsets);
writeTrueTypeTable(baos, OFTableName.FPGM, tableOffsets);
writeTrueTypeTable(baos, OFTableName.PREP, tableOffsets);

baos = copyTables(tableOffsets, baos, hmtxTable, mappedGlyphs.size());

return baos.toByteArray();
}

private static class TableOffset {
private long originOffset;
private long originLength;
private int newOffset;

public TableOffset(long originOffset, long originLength, int newOffset) {
this.originOffset = originOffset;
this.originLength = originLength;
this.newOffset = newOffset;
}

public long getOriginOffset() {
return originOffset;
}

public long getOriginLength() {
return originLength;
}

public int getNewOffset() {
return newOffset;
}
}

private void writeTrueTypeTable(ByteArrayOutputStream baos, OFTableName table,
List<TableOffset> tableOffsets) throws IOException, UnsupportedEncodingException {
OFDirTabEntry tabEntry = ttfFont.getDirectoryEntry(table);
if (tabEntry != null) {
baos.write(tabEntry.getTag());
baos.write(pclByteWriter.unsignedLongInt(tabEntry.getChecksum()));
TableOffset newTableOffset = new TableOffset(tabEntry.getOffset(),
tabEntry.getLength(), baos.size());
tableOffsets.add(newTableOffset);
baos.write(pclByteWriter.unsignedLongInt(0)); // Offset to be set later
baos.write(pclByteWriter.unsignedLongInt(tabEntry.getLength()));
}
}

private void writeGDIR(ByteArrayOutputStream baos) throws UnsupportedEncodingException, IOException {
baos.write("gdir".getBytes("ISO-8859-1"));
baos.write(pclByteWriter.unsignedLongInt(0)); // Checksum
baos.write(pclByteWriter.unsignedLongInt(0)); // Offset
baos.write(pclByteWriter.unsignedLongInt(0)); // Length
}

private ByteArrayOutputStream copyTables(List<TableOffset> tableOffsets,
ByteArrayOutputStream baos, byte[] hmtxTable, int hmtxSize)
throws IOException {
Map<Integer, byte[]> offsetValues = new HashMap<Integer, byte[]>();
for (TableOffset tableOffset : tableOffsets) {
offsetValues.put(tableOffset.getNewOffset(), pclByteWriter.unsignedLongInt(baos.size()));
if (tableOffset.getOriginOffset() == -1) { // Update the offset in the table directory
baos.write(hmtxTable);
} else {
byte[] tableData = reader.getBytes((int) tableOffset.getOriginOffset(),
(int) tableOffset.getOriginLength());
int index = tableOffsets.indexOf(tableOffset);
if (index == 1) {
tableData = updateHHEA(tableData, hmtxSize + 33);
}

// Write the table data to the end of the TrueType segment output
baos.write(tableData);
}
}
baos = updateOffsets(baos, offsetValues);
return baos;
}

private byte[] updateHHEA(byte[] tableData, int hmtxSize) {
writeUShort(tableData, tableData.length - 2, hmtxSize);
return tableData;
}

private ByteArrayOutputStream updateOffsets(ByteArrayOutputStream baos, Map<Integer, byte[]> offsets)
throws IOException {
byte[] softFont = baos.toByteArray();
for (int offset : offsets.keySet()) {
pclByteWriter.updateDataAtLocation(softFont, offsets.get(offset), offset);
}
baos = new ByteArrayOutputStream();
baos.write(softFont);
return baos;
}

@Override
public Map<Integer, int[]> getCharacterOffsets() throws IOException {
List<OFMtxEntry> mtx = ttfFont.getMtx();
OFTableName glyfTag = OFTableName.GLYF;
Map<Integer, int[]> charOffsets = new HashMap<Integer, int[]>();
OFDirTabEntry tabEntry = ttfFont.getDirectoryEntry(glyfTag);
if (ttfFont.seekTab(reader, glyfTag, 0)) {
for (int i = 1; i < mtx.size(); i++) {
OFMtxEntry entry = mtx.get(i);
OFMtxEntry nextEntry;
int nextOffset = 0;
int charCode = 0;
if (entry.getUnicodeIndex().size() > 0) {
charCode = (Integer) entry.getUnicodeIndex().get(0);
} else {
charCode = entry.getIndex();
}

if (i < mtx.size() - 1) {
nextEntry = mtx.get(i + 1);
nextOffset = (int) nextEntry.getOffset();
} else {
nextOffset = (int) ttfFont.getLastGlyfLocation();
}
int glyphOffset = (int) entry.getOffset();
int glyphLength = nextOffset - glyphOffset;

charOffsets.put(charCode, new int[]{(int) tabEntry.getOffset() + glyphOffset, glyphLength});
}
}
return charOffsets;
}

@Override
public OpenFont getFontFile() {
return ttfFont;
}

@Override
public FontFileReader getFontFileReader() {
return reader;
}

private void writeSubsetHMTX(ByteArrayOutputStream baos, OFTableName table,
List<TableOffset> tableOffsets, byte[] hmtxTable) throws IOException {
OFDirTabEntry tabEntry = ttfFont.getDirectoryEntry(table);
if (tabEntry != null) {
baos.write(tabEntry.getTag());
// Override the original checksum for the subset version
baos.write(pclByteWriter.unsignedLongInt(getCheckSum(hmtxTable, 0, hmtxTable.length)));
TableOffset newTableOffset = new TableOffset(-1, hmtxTable.length, baos.size());
tableOffsets.add(newTableOffset);
baos.write(pclByteWriter.unsignedLongInt(0)); // Offset to be set later
baos.write(pclByteWriter.unsignedLongInt(hmtxTable.length));
}
}

protected static int getCheckSum(byte[] data, int start, int size) {
// All the tables here are aligned on four byte boundaries
// Add remainder to size if it's not a multiple of 4
int remainder = size % 4;
if (remainder != 0) {
size += remainder;
}

long sum = 0;

for (int i = 0; i < size; i += 4) {
long l = 0;
for (int j = 0; j < 4; j++) {
l <<= 8;
if (data.length > (start + i + j)) {
l |= data[start + i + j] & 0xff;
}
}
sum += l;
}
return (int) sum;
}

protected byte[] createHmtx(Map<Character, Integer> mappedGlyphs) throws IOException {
byte[] hmtxTable = new byte[((mappedGlyphs.size() + 32) * 4)];
OFDirTabEntry entry = ttfFont.getDirectoryEntry(OFTableName.HMTX);

if (entry != null) {
for (Entry<Character, Integer> glyphSubset : mappedGlyphs.entrySet()) {
char unicode = glyphSubset.getKey();
int originalIndex = 0;
int softFontGlyphIndex = glyphSubset.getValue();
if (font instanceof MultiByteFont) {
originalIndex = ((MultiByteFont) font).getGIDFromChar(unicode);

writeUShort(hmtxTable, (softFontGlyphIndex) * 4,
ttfFont.getMtx().get(originalIndex).getWx());
writeUShort(hmtxTable, (softFontGlyphIndex) * 4 + 2,
ttfFont.getMtx().get(originalIndex).getLsb());
} else {
originalIndex = ((SingleByteFont) font).getGIDFromChar(unicode);

writeUShort(hmtxTable, (softFontGlyphIndex) * 4,
((SingleByteFont) font).getWidth(originalIndex, 1));
writeUShort(hmtxTable, (softFontGlyphIndex) * 4 + 2, 0);
}
}
}
return hmtxTable;
}

/**
* Appends a USHORT to the output array, updates currentPost but not realSize
*/
private void writeUShort(byte[] out, int offset, int s) {
byte b1 = (byte) ((s >> 8) & 0xff);
byte b2 = (byte) (s & 0xff);
out[offset] = b1;
out[offset + 1] = b2;
}
}

+ 77
- 0
src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFOS2FontTable.java View File

@@ -0,0 +1,77 @@
/*
* 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.pcl.fonts.truetype;

import java.io.IOException;

import org.apache.fop.fonts.truetype.FontFileReader;

public class PCLTTFOS2FontTable extends PCLTTFTable {
private int avgCharWidth;
private int xHeight;
private int widthClass;
private int weightClass;
private int capHeight;
private int[] panose = new int[10];

public PCLTTFOS2FontTable(FontFileReader in) throws IOException {
super(in);
int version = reader.readTTFUShort(); // Version
avgCharWidth = reader.readTTFShort();
weightClass = reader.readTTFShort();
widthClass = reader.readTTFShort();
skipShort(reader, 12);
for (int i = 0; i < 10; i++) {
panose[i] = reader.readTTFByte();
}
skipLong(reader, 4);
skipByte(reader, 4);
skipShort(reader, 8);
if (version >= 2) {
skipLong(reader, 2);
xHeight = reader.readTTFShort();
capHeight = reader.readTTFShort();
}
}

public int getAvgCharWidth() {
return avgCharWidth;
}

public int getXHeight() {
return xHeight;
}

public int getWidthClass() {
return widthClass;
}

public int getWeightClass() {
return weightClass;
}

public int getCapHeight() {
return capHeight;
}

public int[] getPanose() {
return panose;
}
}

+ 115
- 0
src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFPCLTFontTable.java View File

@@ -0,0 +1,115 @@
/*
* 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.pcl.fonts.truetype;

import java.io.IOException;

import org.apache.fop.fonts.truetype.FontFileReader;

public class PCLTTFPCLTFontTable extends PCLTTFTable {
private long version;
private long fontNumber;
private int pitch;
private int xHeight;
private int style;
private int typeFamily;
private int capHeight;
private int symbolSet;
private String typeface;
private String characterComplement;
private String filename;
private int strokeWeight;
private int widthType;
private int serifStyle;

public PCLTTFPCLTFontTable(FontFileReader in) throws IOException {
super(in);
version = reader.readTTFULong();
fontNumber = reader.readTTFULong();
pitch = reader.readTTFUShort();
xHeight = reader.readTTFUShort();
style = reader.readTTFUShort();
typeFamily = reader.readTTFUShort();
capHeight = reader.readTTFUShort();
symbolSet = reader.readTTFUShort();
typeface = reader.readTTFString(16);
characterComplement = reader.readTTFString(8);
filename = reader.readTTFString(6);
strokeWeight = reader.readTTFUShort();
widthType = reader.readTTFUShort();
serifStyle = reader.readTTFUByte();
}

public long getVersion() {
return version;
}

public long getFontNumber() {
return fontNumber;
}

public int getPitch() {
return pitch;
}

public int getXHeight() {
return xHeight;
}

public int getStyle() {
return style;
}

public int getTypeFamily() {
return typeFamily;
}

public int getCapHeight() {
return capHeight;
}

public int getSymbolSet() {
return symbolSet;
}

public String getTypeface() {
return typeface;
}

public String getCharacterComplement() {
return characterComplement;
}

public String getFilename() {
return filename;
}

public int getStrokeWeight() {
return strokeWeight;
}

public int getWidthType() {
return widthType;
}

public int getSerifStyle() {
return serifStyle;
}
}

+ 51
- 0
src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFPOSTFontTable.java View File

@@ -0,0 +1,51 @@
/*
* 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.pcl.fonts.truetype;

import java.io.IOException;

import org.apache.fop.fonts.truetype.FontFileReader;

public class PCLTTFPOSTFontTable extends PCLTTFTable {
private int underlinePosition;
private int underlineThickness;
private int isFixedPitch;

public PCLTTFPOSTFontTable(FontFileReader in) throws IOException {
super(in);
reader.readTTFLong(); // Version
reader.readTTFLong(); // Italic Angle
underlinePosition = reader.readTTFShort();
underlineThickness = reader.readTTFShort();
isFixedPitch = (int) reader.readTTFULong();
}

public int getUnderlinePosition() {
return underlinePosition;
}

public int getUnderlineThickness() {
return underlineThickness;
}

public int getIsFixedPitch() {
return isFixedPitch;
}
}

+ 47
- 0
src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFTable.java View File

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

/* $Id$ */

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

import java.io.IOException;

import org.apache.fop.fonts.truetype.FontFileReader;

public class PCLTTFTable {
protected FontFileReader reader;

public PCLTTFTable(FontFileReader reader) {
this.reader = reader;
}

protected void skipShort(FontFileReader reader, int skips)
throws IOException {
reader.skip(skips * 2);
}

protected void skipLong(FontFileReader reader, int skips)
throws IOException {
reader.skip(skips * 4);
}

protected void skipByte(FontFileReader reader, int skips)
throws IOException {
reader.skip(skips);
}
}

+ 49
- 0
src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFTableFactory.java View File

@@ -0,0 +1,49 @@
/*
* 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.pcl.fonts.truetype;

import java.io.IOException;

import org.apache.fop.fonts.truetype.FontFileReader;
import org.apache.fop.fonts.truetype.OFTableName;

public final class PCLTTFTableFactory {
private FontFileReader reader;

private PCLTTFTableFactory(FontFileReader reader) {
this.reader = reader;
}

public static PCLTTFTableFactory getInstance(FontFileReader reader) {
return new PCLTTFTableFactory(reader);
}

public PCLTTFTable newInstance(OFTableName tableName)
throws IOException {
if (tableName == OFTableName.PCLT) {
return new PCLTTFPCLTFontTable(reader);
} else if (tableName == OFTableName.OS2) {
return new PCLTTFOS2FontTable(reader);
} else if (tableName == OFTableName.POST) {
return new PCLTTFPOSTFontTable(reader);
}
return null;
}
}

+ 51
- 0
test/java/org/apache/fop/render/pcl/fonts/MockPCLTTFFontReader.java View File

@@ -0,0 +1,51 @@
/*
* 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.pcl.fonts;

import java.io.IOException;

import org.apache.fop.fonts.CustomFont;
import org.apache.fop.fonts.Typeface;
import org.apache.fop.fonts.truetype.FontFileReader;
import org.apache.fop.fonts.truetype.TTFFile;
import org.apache.fop.render.java2d.CustomFontMetricsMapper;
import org.apache.fop.render.pcl.fonts.truetype.PCLTTFFontReader;

public class MockPCLTTFFontReader extends PCLTTFFontReader {

public MockPCLTTFFontReader(Typeface font, PCLByteWriterUtil pclByteWriter) throws IOException {
super(font, pclByteWriter);
}

@Override
protected void loadFont() throws IOException {
if (typeface instanceof CustomFontMetricsMapper) {
CustomFontMetricsMapper fontMetrics = (CustomFontMetricsMapper) typeface;
CustomFont customFont = (CustomFont) fontMetrics.getRealFont();
fontStream = customFont.getInputStream();
reader = new FontFileReader(fontStream);

ttfFont = new TTFFile();
ttfFont.readFont(reader, customFont.getFullName());
readFontTables();
} else {
// TODO - Handle when typeface is not in the expected format for a PCL TrueType object
}
}
}

+ 74
- 0
test/java/org/apache/fop/render/pcl/fonts/PCLByteWriterUtilTestCase.java View File

@@ -0,0 +1,74 @@
/*
* 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.pcl.fonts;

import java.io.IOException;

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

import static org.junit.Assert.assertArrayEquals;

public class PCLByteWriterUtilTestCase {
private PCLByteWriterUtil byteWriter;

@Before
public void setUp() {
byteWriter = new PCLByteWriterUtil();
}

@Test
public void testWriteMethods() throws IOException {
byte[] output = byteWriter.writeCommand("(s4X");
// 27 = PCL escape character with rest in ASCII format
byte[] command = {27, 40, 115, 52, 88};
assertArrayEquals(command, output);

byte[] resultB = byteWriter.unsignedLongInt(102494);
byte[] compareB = {0, 1, -112, 94};
assertArrayEquals(compareB, resultB);

byte[] resultC = byteWriter.unsignedInt(1024);
byte[] compareC = {4, 0};
assertArrayEquals(compareC, resultC);
}

@Test
public void testUtilMethods() throws IOException {
byte[] anArray = {1, 2, 3, 4, 5, 9, 10};
byte[] insertArray = {6, 7, 8};
byte[] result = byteWriter.insertIntoArray(5, anArray, insertArray);
byte[] compareA = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
assertArrayEquals(compareA, result);

byte[] reverse = {10, 9, 8, 7, 6};
byteWriter.updateDataAtLocation(compareA, reverse, 5);
byte[] compareB = {1, 2, 3, 4, 5, 10, 9, 8, 7, 6};
assertArrayEquals(compareB, compareA);

byte[] anArrayC = {1, 2, 3, 4, 5};
byte[] resultC = byteWriter.padBytes(anArrayC, 10);
byte[] compareC = {1, 2, 3, 4, 5, 0, 0, 0, 0, 0};
assertArrayEquals(compareC, resultC);

byte[] resultD = byteWriter.padBytes(anArrayC, 10, 1);
byte[] compareD = {1, 2, 3, 4, 5, 1, 1, 1, 1, 1};
assertArrayEquals(compareD, resultD);
}
}

+ 52
- 0
test/java/org/apache/fop/render/pcl/fonts/PCLFontReaderFactoryTestCase.java View File

@@ -0,0 +1,52 @@
/*
* 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.pcl.fonts;

import java.io.File;
import java.io.FileInputStream;
import java.net.URI;

import org.junit.Test;

import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import org.apache.fop.fonts.CustomFont;
import org.apache.fop.fonts.FontType;
import org.apache.fop.render.java2d.CustomFontMetricsMapper;
import org.apache.fop.render.pcl.fonts.truetype.PCLTTFFontReader;

public class PCLFontReaderFactoryTestCase {
private static final String TEST_FONT_TTF = "./test/resources/fonts/ttf/DejaVuLGCSerif.ttf";

@Test
public void verifyTypeIdentification() throws Exception {
CustomFont sbFont = mock(CustomFont.class);
when(sbFont.getInputStream()).thenReturn(new FileInputStream(new File(TEST_FONT_TTF)));
when(sbFont.getEmbedFileURI()).thenReturn(new URI(TEST_FONT_TTF));
CustomFontMetricsMapper customFont = new CustomFontMetricsMapper(sbFont);
when(customFont.getFontType()).thenReturn(FontType.TRUETYPE);
// Have to mock the input stream twice otherwise get a Stream is closed exception
when(((CustomFont) customFont.getRealFont()).getInputStream()).thenReturn(
new FileInputStream(new File(TEST_FONT_TTF)));
PCLFontReaderFactory fontReaderFactory = PCLFontReaderFactory.getInstance(null);
assertTrue(fontReaderFactory.createInstance(customFont) instanceof PCLTTFFontReader);
}
}

+ 198
- 0
test/java/org/apache/fop/render/pcl/fonts/PCLTTFFontReaderTestCase.java View File

@@ -0,0 +1,198 @@
/*
* 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.pcl.fonts;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

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

import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import org.apache.fop.fonts.CustomFont;
import org.apache.fop.fonts.SingleByteFont;
import org.apache.fop.render.java2d.CustomFontMetricsMapper;
import org.apache.fop.render.pcl.fonts.PCLFontSegment.SegmentID;
import org.apache.fop.render.pcl.fonts.truetype.PCLTTFFontReader;

public class PCLTTFFontReaderTestCase {

private CustomFontMetricsMapper customFont = mock(CustomFontMetricsMapper.class);
private PCLByteWriterUtil byteWriter;
private static final String TEST_FONT_A = "./test/resources/fonts/ttf/DejaVuLGCSerif.ttf";

@Before
public void setUp() {
byteWriter = new PCLByteWriterUtil();
}

@Test
public void verifyFontAData() throws Exception {
CustomFont sbFont = mock(CustomFont.class);
when(sbFont.getInputStream()).thenReturn(new FileInputStream(new File(TEST_FONT_A)));
when(customFont.getRealFont()).thenReturn(sbFont);
SingleByteFont font = mock(SingleByteFont.class);
when(font.getGIDFromChar('h')).thenReturn(104);
when(font.getGIDFromChar('e')).thenReturn(101);
when(font.getGIDFromChar('l')).thenReturn(108);
when(font.getGIDFromChar('o')).thenReturn(111);
PCLTTFFontReader reader = new MockPCLTTFFontReader(customFont, byteWriter);
reader.setFont(font);
verifyFontData(reader);
validateOffsets(reader);
validateFontSegments(reader);
}

/**
* Compares the input font data against a sample of the data read and calculated by the reader. The assertions are
* made against data taken from the TrueType Font Analyzer tool.
* @param reader The reader
*/
private void verifyFontData(PCLTTFFontReader reader) {
assertEquals(reader.getCellWidth(), 5015); // Bounding box X2 - X1
assertEquals(reader.getCellHeight(), 3254); // Bounding box Y2 - Y1
assertEquals(reader.getCapHeight(), 0); // OS2Table.capHeight
assertEquals(reader.getFontName(), "DejaVu LGC Serif"); // Full name read by TTFFont object
assertEquals(reader.getFirstCode(), 32); // Always 32 for bound font
assertEquals(reader.getLastCode(), 255); // Always 255 for bound font

// Values that require conversion tables (See PCLTTFFontReader.java)
assertEquals(reader.getStrokeWeight(), 0); // Weight Class 400 (regular) should be equivalent 0
assertEquals(reader.getSerifStyle(), 128); // Serif Style 0 should equal 0
assertEquals(reader.getWidthType(), 0); // Width Class 5 (regular) should be equivalent 0
}

private void validateOffsets(PCLTTFFontReader reader) throws IOException {
// Offsets are stored with their character ID with the array [offset, length]
Map<Integer, int[]> offsets = reader.getCharacterOffsets();

// Test data
int[] charC = {27644, 144}; // Char index = 99
int[] charDollar = {16044, 264}; // Char index = 36
int[] charOne = {17808, 176}; // Char index = 49
int[] charUpperD = {21236, 148}; // Char index = 68
int[] charUpperJ = {22140, 176}; // Char index = 74

assertArrayEquals(offsets.get(99), charC);
assertArrayEquals(offsets.get(36), charDollar);
assertArrayEquals(offsets.get(49), charOne);
assertArrayEquals(offsets.get(68), charUpperD);
assertArrayEquals(offsets.get(74), charUpperJ);
}

/**
* Verifies the font segment data copied originally from the TrueType font. Data was verified using TrueType Font
* Analyzer and PCLParaphernalia tool.
* @param reader The reader
* @throws IOException
*/
private void validateFontSegments(PCLTTFFontReader reader) throws IOException {
HashMap<Character, Integer> mappedChars = new HashMap<Character, Integer>();
mappedChars.put('H', 1);
mappedChars.put('e', 1);
mappedChars.put('l', 1);
mappedChars.put('o', 1);

List<PCLFontSegment> segments = reader.getFontSegments(mappedChars);
assertEquals(segments.size(), 5);
for (PCLFontSegment segment : segments) {
if (segment.getIdentifier() == SegmentID.PA) {
// Panose
assertEquals(segment.getData().length, 10);
byte[] panose = {2, 6, 6, 3, 5, 6, 5, 2, 2, 4};
assertArrayEquals(segment.getData(), panose);
} else if (segment.getIdentifier() == SegmentID.GT) {
verifyGlobalTrueTypeData(segment, mappedChars.size());
} else if (segment.getIdentifier() == SegmentID.NULL) {
// Terminating segment
assertEquals(segment.getData().length, 0);
}
}
}

private void verifyGlobalTrueTypeData(PCLFontSegment segment, int mappedCharsSize)
throws IOException {
byte[] ttfData = segment.getData();
int currentPos = 0;
//Version
assertEquals(readInt(new byte[]{ttfData[currentPos++], ttfData[currentPos++]}), 1);
assertEquals(readInt(new byte[]{ttfData[currentPos++], ttfData[currentPos++]}), 0);
//Number of tables
int numTables = readInt(new byte[]{ttfData[currentPos++], ttfData[currentPos++]});
assertEquals(numTables, 8);
//Search range
assertEquals(readInt(new byte[]{ttfData[currentPos++], ttfData[currentPos++]}), 128);
//Entry Selector
assertEquals(readInt(new byte[]{ttfData[currentPos++], ttfData[currentPos++]}), 3);
//Range shift
assertEquals(readInt(new byte[]{ttfData[currentPos++], ttfData[currentPos++]}), 0);
String[] validTags = {"head", "hhea", "hmtx", "maxp", "gdir"};
int matches = 0;
for (int i = 0; i < numTables; i++) {
String tag = readTag(new byte[]{ttfData[currentPos++], ttfData[currentPos++],
ttfData[currentPos++], ttfData[currentPos++]});
if (Arrays.asList(validTags).contains(tag)) {
matches++;
}
if (tag.equals("hmtx")) {
currentPos += 4;
int offset = readLong(new byte[]{ttfData[currentPos++], ttfData[currentPos++],
ttfData[currentPos++], ttfData[currentPos++]});
int length = readLong(new byte[]{ttfData[currentPos++], ttfData[currentPos++],
ttfData[currentPos++], ttfData[currentPos++]});
verifyHmtx(ttfData, offset, length, mappedCharsSize);
} else {
currentPos += 12;
}
}
assertEquals(matches, 5);
}

private void verifyHmtx(byte[] ttfData, int offset, int length, int mappedCharsSize)
throws IOException {
ByteArrayInputStream bais = new ByteArrayInputStream(ttfData);
byte[] subsetHmtx = new byte[length];
bais.skip(offset);
bais.read(subsetHmtx);
assertEquals(subsetHmtx.length, (mappedCharsSize + 32) * 4);
}

private int readInt(byte[] bytes) {
return ((0xFF & bytes[0]) << 8) | (0xFF & bytes[1]);
}

private int readLong(byte[] bytes) {
return ((0xFF & bytes[0]) << 24) | ((0xFF & bytes[1]) << 16) | ((0xFF & bytes[2]) << 8)
| (0xFF & bytes[3]);
}

private String readTag(byte[] tag) {
return new String(tag);
}
}

+ 76
- 0
test/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFCharacterWriterTestCase.java View File

@@ -0,0 +1,76 @@
/*
* 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.pcl.fonts.truetype;

import java.io.File;
import java.io.FileInputStream;

import org.junit.Test;

import static org.junit.Assert.assertArrayEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import org.apache.fop.fonts.CustomFont;
import org.apache.fop.fonts.truetype.FontFileReader;
import org.apache.fop.fonts.truetype.OFFontLoader;
import org.apache.fop.fonts.truetype.TTFFile;
import org.apache.fop.render.java2d.CustomFontMetricsMapper;
import org.apache.fop.render.pcl.fonts.PCLByteWriterUtil;
import org.apache.fop.render.pcl.fonts.PCLSoftFont;

public class PCLTTFCharacterWriterTestCase {

private PCLTTFCharacterWriter characterWriter;
private PCLSoftFont softFont;
private CustomFontMetricsMapper customFont = mock(CustomFontMetricsMapper.class);
private static final String TEST_FONT_A = "./test/resources/fonts/ttf/DejaVuLGCSerif.ttf";

@Test
public void verifyCharacterDefinition() throws Exception {
CustomFont sbFont = mock(CustomFont.class);
when(customFont.getRealFont()).thenReturn(sbFont);
softFont = new PCLSoftFont(1, customFont, false);
TTFFile openFont = new TTFFile();
FontFileReader reader = new FontFileReader(new FileInputStream(new File(TEST_FONT_A)));
String header = OFFontLoader.readHeader(reader);
openFont.readFont(reader, header);
softFont.setOpenFont(openFont);
softFont.setReader(reader);

characterWriter = new PCLTTFCharacterWriter(softFont);
byte[] charDefinition = characterWriter.writeCharacterDefinitions("f");
PCLByteWriterUtil pclByteWriter = new PCLByteWriterUtil();
// Character command
byte[] command = pclByteWriter.writeCommand(String.format("*c%dE", 32));
assertArrayEquals(getBytes(charDefinition, 0, 6), command);
// Character definition command
byte[] charDefCommand = pclByteWriter.writeCommand(String.format("(s%dW", 210));
assertArrayEquals(getBytes(charDefinition, 6, 7), charDefCommand);
}

private byte[] getBytes(byte[] byteArray, int offset, int length) {
byte[] result = new byte[length];
int count = 0;
for (int i = offset; i < offset + length; i++) {
result[count++] = byteArray[i];
}
return result;
}
}

Loading…
Cancel
Save