git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/branches/Temp_PCLSoftFonts@1694075 13f79535-47bb-0310-9956-ffa450edef68tags/fop-2_1
@@ -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(); | |||
} | |||
} |
@@ -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 |
@@ -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]; |
@@ -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); | |||
} |
@@ -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 |
@@ -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); | |||
} |
@@ -30,6 +30,7 @@ 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; | |||
@@ -62,6 +63,7 @@ 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; | |||
@@ -337,24 +339,35 @@ public class PCLPainter extends AbstractIFPainter<PCLDocumentHandler> implements | |||
// 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) { | |||
@@ -482,7 +495,9 @@ public class PCLPainter extends AbstractIFPainter<PCLDocumentHandler> implements | |||
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()); | |||
@@ -519,8 +534,7 @@ public class PCLPainter extends AbstractIFPainter<PCLDocumentHandler> implements | |||
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(); | |||
@@ -533,7 +547,7 @@ public class PCLPainter extends AbstractIFPainter<PCLDocumentHandler> implements | |||
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))); | |||
} | |||
} | |||
} |
@@ -25,7 +25,6 @@ import java.util.ArrayList; | |||
import java.util.List; | |||
public class PCLCharacterDefinition { | |||
private int glyphID; | |||
private int charCode; | |||
private int charDefinitionSize; | |||
private byte[] glyfData; | |||
@@ -34,15 +33,17 @@ public class PCLCharacterDefinition { | |||
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; | |||
@@ -51,7 +52,7 @@ public class PCLCharacterDefinition { | |||
} | |||
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 { | |||
@@ -93,7 +94,7 @@ public class PCLCharacterDefinition { | |||
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) { |
@@ -23,6 +23,7 @@ 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; | |||
@@ -31,12 +32,17 @@ 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(); | |||
@@ -77,7 +83,8 @@ public abstract class PCLFontReader { | |||
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; |
@@ -38,13 +38,17 @@ public class PCLSoftFont { | |||
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() { | |||
@@ -92,7 +96,11 @@ public class PCLSoftFont { | |||
} | |||
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) { | |||
@@ -125,4 +133,28 @@ public class PCLSoftFont { | |||
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); | |||
} | |||
} | |||
} |
@@ -22,45 +22,98 @@ 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 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())); | |||
@@ -95,22 +148,21 @@ public class PCLSoftFontManager { | |||
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); | |||
} | |||
@@ -135,27 +187,39 @@ public class PCLSoftFontManager { | |||
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; | |||
@@ -164,7 +228,51 @@ public class PCLSoftFontManager { | |||
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; | |||
} | |||
} | |||
} |
@@ -109,19 +109,21 @@ public class PCLTTFCharacterWriter extends PCLCharacterWriter { | |||
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)); | |||
} | |||
} | |||
@@ -30,6 +30,8 @@ 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; | |||
@@ -121,13 +123,15 @@ public class PCLTTFFontReader extends PCLFontReader { | |||
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 | |||
@@ -453,11 +457,12 @@ public class PCLTTFFontReader extends PCLFontReader { | |||
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; | |||
@@ -475,9 +480,9 @@ public class PCLTTFFontReader extends PCLFontReader { | |||
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 | |||
@@ -505,9 +510,9 @@ public class PCLTTFFontReader extends PCLFontReader { | |||
// 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); | |||
@@ -516,23 +521,46 @@ public class PCLTTFFontReader extends PCLFontReader { | |||
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())); | |||
} | |||
} | |||
@@ -543,26 +571,35 @@ public class PCLTTFFontReader extends PCLFontReader { | |||
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(); | |||
@@ -616,4 +653,79 @@ public class PCLTTFFontReader extends PCLFontReader { | |||
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; | |||
} | |||
} |
@@ -18,9 +18,12 @@ | |||
/* $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; | |||
@@ -33,6 +36,7 @@ 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; | |||
@@ -53,7 +57,13 @@ public class PCLTTFFontReaderTestCase { | |||
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); | |||
@@ -103,7 +113,13 @@ public class PCLTTFFontReaderTestCase { | |||
* @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) { | |||
@@ -111,10 +127,72 @@ public class PCLTTFFontReaderTestCase { | |||
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); | |||
} | |||
} |
@@ -46,7 +46,7 @@ public class PCLTTFCharacterWriterTestCase { | |||
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); |