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();
public int[] getWidths() {
return font.getWidths();
}
-
}
*/
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
*/
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) {
usedGlyphs.put(glyphIndex, selector);
usedGlyphsIndex.put(selector, glyphIndex);
usedCharsIndex.put(selector, unicode);
+ charToGIDs.put(unicode, glyphIndex);
usedGlyphsCount++;
return selector;
} else {
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];
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);
}
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
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);
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);
int selector = usedGlyphsCount;
usedGlyphs.put(glyphIndex, selector);
usedCharsIndex.put(selector, unicode);
+ charGIDMappings.put(unicode, glyphIndex);
usedGlyphsCount++;
return selector;
} else {
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);
}
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;
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;
// TrueType conversion to a soft font (PCL 5 Technical Reference - Chapter 11)
if (!drawAsBitmaps && isTrueType(tf)) {
boolean madeSF = false;
- if (sfManager.getSoftFont(tf) == null) {
+ if (sfManager.getSoftFont(tf, text) == null) {
madeSF = true;
ByteArrayOutputStream baos = sfManager.makeSoftFont(tf);
if (baos != null) {
gen.writeBytes(baos.toByteArray());
}
}
- int fontID = sfManager.getSoftFontID(tf);
String formattedSize = gen.formatDouble2(state.getFontSize() / 1000.0);
gen.writeCommand(String.format("(s%sV", formattedSize));
- gen.writeCommand(String.format("(%dX", fontID));
- PCLSoftFont softFont = sfManager.getSoftFont(tf);
- PCLCharacterWriter charWriter = new PCLTTFCharacterWriter(softFont);
- if (!madeSF) {
- gen.writeBytes(sfManager.writeFontIDCommand(fontID));
+ 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);
+ }
}
- gen.writeBytes(charWriter.writeCharacterDefinitions(text));
- drawTextUsingSoftFont(x, y, letterSpacing, wordSpacing, dp, text, triplet, softFont);
} else {
drawTextAsBitmap(x, y, letterSpacing, wordSpacing, dp, text, triplet);
if (DEBUG) {
gen.selectGrayscale(textColor);
}
- setCursorPos(x, y);
+ if (x != -1 && y != -1) {
+ setCursorPos(x, y);
+ }
float fontSize = state.getFontSize() / 1000f;
Font font = getFontInfo().getFontInstance(triplet, state.getFontSize());
if (glyphAdjust != 0) {
gen.getOutputStream().write(sb.toString().getBytes(gen.getTextEncoding()));
for (int j = 0; j < current.length(); j++) {
- gen.getOutputStream().write(
- softFont.getUnicodeCodePoint((int) current.charAt(j)));
+ gen.getOutputStream().write(softFont.getCharCode(current.charAt(j)));
}
sb = new StringBuffer();
if (!current.equals("")) {
gen.getOutputStream().write(sb.toString().getBytes(gen.getTextEncoding()));
for (int i = 0; i < current.length(); i++) {
- gen.getOutputStream().write(softFont.getUnicodeCodePoint((int) current.charAt(i)));
+ gen.getOutputStream().write(softFont.getCharCode(current.charAt(i)));
}
}
}
import java.util.List;
public class PCLCharacterDefinition {
- private int glyphID;
private int charCode;
private int charDefinitionSize;
private byte[] glyfData;
private PCLCharacterClass charClass;
private PCLByteWriterUtil pclByteWriter;
private List<PCLCharacterDefinition> composites;
+ private boolean isComposite;
- public PCLCharacterDefinition(int glyphID, int charCode, PCLCharacterFormat charFormat,
- PCLCharacterClass charClass, byte[] glyfData, PCLByteWriterUtil pclByteWriter) {
- this.glyphID = glyphID;
+ 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;
}
public byte[] getCharacterCommand() throws IOException {
- return pclByteWriter.writeCommand(String.format("*c%dE", charCode));
+ return pclByteWriter.writeCommand(String.format("*c%dE", (isComposite) ? 65535 : charCode));
}
public byte[] getCharacterDefinitionCommand() throws IOException {
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(glyphID));
+ baos.write(pclByteWriter.unsignedInt(charCode));
}
public void addCompositeGlyph(PCLCharacterDefinition composite) {
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;
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 getVariety();
/** Segmented Font Data **/
- public abstract List<PCLFontSegment> getFontSegments() throws IOException;
+ public abstract List<PCLFontSegment> getFontSegments(Map<Character, Integer> mappedGlyphs)
+ throws IOException;
/** Character Definitions **/
public abstract Map<Integer, int[]> getCharacterOffsets() throws IOException;
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) {
+ 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() {
}
public int getUnicodeCodePoint(int unicode) {
- return charsWritten.get(unicode);
+ if (charsWritten.containsKey(unicode)) {
+ return charsWritten.get(unicode);
+ } else {
+ return -1;
+ }
}
public boolean hasPreviouslyWritten(int unicode) {
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);
+ }
+ }
}
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 int byte64Offset;
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 {
- PCLSoftFont softFont = new PCLSoftFont(fonts.size() + 1, font);
- fontReaderFactory = PCLFontReaderFactory.getInstance(pclByteWriter);
+ 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) {
- initialize();
- assignFontID();
- writeFontHeader();
- softFont.setCharacterOffsets(fontReader.getCharacterOffsets());
- softFont.setOpenFont(fontReader.getFontFile());
- softFont.setReader(fontReader.getFontFileReader());
- fonts.add(softFont);
+ 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(pclByteWriter.writeCommand(String.format("*c%dD", fonts.size() + 1)));
+ baos.write(assignFontID(fonts.size() + 1));
}
- private void writeFontHeader() throws IOException {
+ 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.unsignedInt(fontReader.getCapHeight()));
header.write(pclByteWriter.unsignedLongInt(fontReader.getFontNumber()));
header.write(pclByteWriter.padBytes(fontReader.getFontName().getBytes("US-ASCII"), 16, 32));
- // Byte 64 starting point stored for checksum
- byte64Offset = header.size();
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, byte64Offset);
+ writeSegmentedFontData(header, mappedGlyphs);
baos.write(getFontHeaderCommand(header.size()));
baos.write(header.toByteArray());
}
- private void writeSegmentedFontData(ByteArrayOutputStream header, int byte64Offset) throws IOException {
- List<PCLFontSegment> fontSegments = fontReader.getFontSegments();
+ 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(segment.getData());
}
- public List<PCLSoftFont> getSoftFonts() {
- return fonts;
- }
-
/**
* 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) {
+ public PCLSoftFont getSoftFont(Typeface font, String text) {
for (PCLSoftFont sftFont : fonts) {
- if (sftFont.getTypeface().equals(font) && sftFont.getCharCount() < 255) {
+ 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);
+ PCLSoftFont font = getSoftFont(tf, "");
for (int i = 0; i < fonts.size(); i++) {
if (fonts.get(i).equals(font)) {
return i + 1;
return -1;
}
- public byte[] writeFontIDCommand(int fontID) throws IOException {
- return pclByteWriter.writeCommand(String.format("*c%dD", fontID));
+ 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;
+ }
}
}
font.writeCharacter(unicode);
- PCLCharacterDefinition newChar = new PCLCharacterDefinition(charIndex, font.getUnicodeCodePoint(unicode),
+ PCLCharacterDefinition newChar = new PCLCharacterDefinition(
+ font.getCharCode((char) unicode),
PCLCharacterFormat.TrueType,
- PCLCharacterClass.TrueType, glyphData, pclByteWriter);
+ 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> composite = glyfTable.retrieveComposedGlyphs(charIndex);
- for (Integer compositeIndex : composite) {
+ Set<Integer> composites = glyfTable.retrieveComposedGlyphs(charIndex);
+ for (Integer compositeIndex : composites) {
byte[] compositeData = getGlyphData(compositeIndex);
- newChar.addCompositeGlyph(new PCLCharacterDefinition(compositeIndex, 65535,
- PCLCharacterFormat.TrueType, PCLCharacterClass.TrueType, compositeData, pclByteWriter));
+ newChar.addCompositeGlyph(new PCLCharacterDefinition(compositeIndex,
+ PCLCharacterFormat.TrueType,
+ PCLCharacterClass.TrueType, compositeData, pclByteWriter, true));
}
}
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;
protected void loadFont() throws IOException {
if (typeface instanceof CustomFontMetricsMapper) {
CustomFontMetricsMapper fontMetrics = (CustomFontMetricsMapper) typeface;
- CustomFont customFont = (CustomFont) fontMetrics.getRealFont();
- fontStream = customFont.getInputStream();
+ 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, customFont.getFullName());
+ ttfFont.readFont(reader, header, fontName);
readFontTables();
} else {
// TODO - Handle when typeface is not in the expected format for a PCL TrueType object
return 0; // TrueType fonts must be set to zero
}
- public List<PCLFontSegment> getFontSegments() throws IOException {
+ 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()));
+ 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;
return ccUnicode;
}
- private byte[] getGlobalTrueTypeData() throws IOException {
+ private byte[] getGlobalTrueTypeData(Map<Character, Integer> mappedGlyphs) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
- Map<OFDirTabEntry, Integer> tableOffsets = new HashMap<OFDirTabEntry, Integer>();
+ List<TableOffset> tableOffsets = new ArrayList<TableOffset>();
// Version
baos.write(pclByteWriter.unsignedInt(1)); // Major
baos.write(pclByteWriter.unsignedInt(0)); // Minor
// Add default data tables
writeTrueTypeTable(baos, OFTableName.HEAD, tableOffsets);
writeTrueTypeTable(baos, OFTableName.HHEA, tableOffsets);
- writeTrueTypeTable(baos, OFTableName.HMTX, 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);
writeTrueTypeTable(baos, OFTableName.FPGM, tableOffsets);
writeTrueTypeTable(baos, OFTableName.PREP, tableOffsets);
- baos = copyTables(tableOffsets, baos);
+ 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,
- Map<OFDirTabEntry, Integer> tableOffsets) throws IOException, UnsupportedEncodingException {
+ List<TableOffset> tableOffsets) throws IOException, UnsupportedEncodingException {
OFDirTabEntry tabEntry = ttfFont.getDirectoryEntry(table);
if (tabEntry != null) {
baos.write(tabEntry.getTag());
baos.write(pclByteWriter.unsignedLongInt(tabEntry.getChecksum()));
- tableOffsets.put(tabEntry, baos.size());
+ TableOffset newTableOffset = new TableOffset(tabEntry.getOffset(),
+ tabEntry.getLength(), baos.size());
+ tableOffsets.add(newTableOffset);
baos.write(pclByteWriter.unsignedLongInt(0)); // Offset to be set later
- long length = (tabEntry.getLength() > HMTX_RESTRICT_SIZE)
- ? HMTX_RESTRICT_SIZE
- : tabEntry.getLength();
- baos.write(pclByteWriter.unsignedLongInt(length));
+ baos.write(pclByteWriter.unsignedLongInt(tabEntry.getLength()));
}
}
baos.write(pclByteWriter.unsignedLongInt(0)); // Length
}
- private ByteArrayOutputStream copyTables(Map<OFDirTabEntry, Integer> tableOffsets, ByteArrayOutputStream baos)
+ private ByteArrayOutputStream copyTables(List<TableOffset> tableOffsets,
+ ByteArrayOutputStream baos, byte[] hmtxTable, int hmtxSize)
throws IOException {
Map<Integer, byte[]> offsetValues = new HashMap<Integer, byte[]>();
- //for (OFDirTabEntry table : tableOffsets.keySet()) {
- for (Entry<OFDirTabEntry, Integer> table : tableOffsets.entrySet()) {
- byte[] tableData = reader.getBytes((int) table.getKey().getOffset(), (int) table.getKey().getLength());
- if (tableData.length > HMTX_RESTRICT_SIZE) {
- byte[] truncated = new byte[HMTX_RESTRICT_SIZE];
- System.arraycopy(tableData, 0, truncated, 0, HMTX_RESTRICT_SIZE);
- tableData = truncated;
+ 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);
}
- // Update the offset in the table directory
- offsetValues.put(table.getValue(), pclByteWriter.unsignedLongInt(baos.size()));
- // 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();
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;
+ }
+}
\ No newline at end of file
/* $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 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;
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);
* @throws IOException
*/
private void validateFontSegments(PCLTTFFontReader reader) throws IOException {
- List<PCLFontSegment> segments = reader.getFontSegments();
+ 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) {
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);
+ }
}
public void verifyCharacterDefinition() throws Exception {
CustomFont sbFont = mock(CustomFont.class);
when(customFont.getRealFont()).thenReturn(sbFont);
- softFont = new PCLSoftFont(1, customFont);
+ 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);