From 1e7790e82370eaf36efe6751667b2c369ec59ca1 Mon Sep 17 00:00:00 2001 From: Robert Meyer Date: Tue, 4 Aug 2015 15:05:19 +0000 Subject: [PATCH] FOP-2486 - Enhancements and fixes to existing patch git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/branches/Temp_PCLSoftFonts@1694075 13f79535-47bb-0310-9956-ffa450edef68 --- src/java/org/apache/fop/fonts/CIDFull.java | 13 +- src/java/org/apache/fop/fonts/CIDSet.java | 14 ++ src/java/org/apache/fop/fonts/CIDSubset.java | 18 ++ src/java/org/apache/fop/fonts/CustomFont.java | 12 ++ .../org/apache/fop/fonts/MultiByteFont.java | 18 ++ .../org/apache/fop/fonts/SingleByteFont.java | 12 ++ .../org/apache/fop/render/pcl/PCLPainter.java | 40 +++-- .../pcl/fonts/PCLCharacterDefinition.java | 13 +- .../fop/render/pcl/fonts/PCLFontReader.java | 9 +- .../fop/render/pcl/fonts/PCLSoftFont.java | 36 +++- .../render/pcl/fonts/PCLSoftFontManager.java | 160 ++++++++++++++--- .../fonts/truetype/PCLTTFCharacterWriter.java | 14 +- .../pcl/fonts/truetype/PCLTTFFontReader.java | 170 +++++++++++++++--- .../pcl/fonts/PCLTTFFontReaderTestCase.java | 80 ++++++++- .../PCLTTFCharacterWriterTestCase.java | 2 +- 15 files changed, 525 insertions(+), 86 deletions(-) diff --git a/src/java/org/apache/fop/fonts/CIDFull.java b/src/java/org/apache/fop/fonts/CIDFull.java index ee062a2bb..1130459b7 100644 --- a/src/java/org/apache/fop/fonts/CIDFull.java +++ b/src/java/org/apache/fop/fonts/CIDFull.java @@ -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(); } - } diff --git a/src/java/org/apache/fop/fonts/CIDSet.java b/src/java/org/apache/fop/fonts/CIDSet.java index 7530ea6e7..acfc705c8 100644 --- a/src/java/org/apache/fop/fonts/CIDSet.java +++ b/src/java/org/apache/fop/fonts/CIDSet.java @@ -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 diff --git a/src/java/org/apache/fop/fonts/CIDSubset.java b/src/java/org/apache/fop/fonts/CIDSubset.java index f442c13ed..01b8495f8 100644 --- a/src/java/org/apache/fop/fonts/CIDSubset.java +++ b/src/java/org/apache/fop/fonts/CIDSubset.java @@ -53,6 +53,12 @@ public class CIDSubset implements CIDSet { */ private Map usedCharsIndex = new HashMap(); + /** + * A map between the original character and it's GID in the original font. + */ + private Map charToGIDs = new HashMap(); + + 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]; diff --git a/src/java/org/apache/fop/fonts/CustomFont.java b/src/java/org/apache/fop/fonts/CustomFont.java index fffb429ed..6f325d96d 100644 --- a/src/java/org/apache/fop/fonts/CustomFont.java +++ b/src/java/org/apache/fop/fonts/CustomFont.java @@ -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 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); } diff --git a/src/java/org/apache/fop/fonts/MultiByteFont.java b/src/java/org/apache/fop/fonts/MultiByteFont.java index 296c86de2..22b5116bb 100644 --- a/src/java/org/apache/fop/fonts/MultiByteFont.java +++ b/src/java/org/apache/fop/fonts/MultiByteFont.java @@ -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 diff --git a/src/java/org/apache/fop/fonts/SingleByteFont.java b/src/java/org/apache/fop/fonts/SingleByteFont.java index e12da81cc..e3037a524 100644 --- a/src/java/org/apache/fop/fonts/SingleByteFont.java +++ b/src/java/org/apache/fop/fonts/SingleByteFont.java @@ -63,6 +63,7 @@ public class SingleByteFont extends CustomFont { private LinkedHashMap usedGlyphNames; private Map usedGlyphs; private Map usedCharsIndex; + private Map charGIDMappings; public SingleByteFont(InternalResourceResolver resourceResolver) { super(resourceResolver); @@ -76,6 +77,7 @@ public class SingleByteFont extends CustomFont { usedGlyphNames = new LinkedHashMap(); usedGlyphs = new HashMap(); usedCharsIndex = new HashMap(); + charGIDMappings = new HashMap(); // 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); } diff --git a/src/java/org/apache/fop/render/pcl/PCLPainter.java b/src/java/org/apache/fop/render/pcl/PCLPainter.java index 26a6da66b..69465a7fd 100644 --- a/src/java/org/apache/fop/render/pcl/PCLPainter.java +++ b/src/java/org/apache/fop/render/pcl/PCLPainter.java @@ -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 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 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 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 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 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))); } } } diff --git a/src/java/org/apache/fop/render/pcl/fonts/PCLCharacterDefinition.java b/src/java/org/apache/fop/render/pcl/fonts/PCLCharacterDefinition.java index be1ba7adc..c275084dc 100644 --- a/src/java/org/apache/fop/render/pcl/fonts/PCLCharacterDefinition.java +++ b/src/java/org/apache/fop/render/pcl/fonts/PCLCharacterDefinition.java @@ -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 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) { diff --git a/src/java/org/apache/fop/render/pcl/fonts/PCLFontReader.java b/src/java/org/apache/fop/render/pcl/fonts/PCLFontReader.java index 22e97605b..6bd5192ac 100644 --- a/src/java/org/apache/fop/render/pcl/fonts/PCLFontReader.java +++ b/src/java/org/apache/fop/render/pcl/fonts/PCLFontReader.java @@ -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 getFontSegments() throws IOException; + public abstract List getFontSegments(Map mappedGlyphs) + throws IOException; /** Character Definitions **/ public abstract Map getCharacterOffsets() throws IOException; diff --git a/src/java/org/apache/fop/render/pcl/fonts/PCLSoftFont.java b/src/java/org/apache/fop/render/pcl/fonts/PCLSoftFont.java index 87f399839..8412534eb 100644 --- a/src/java/org/apache/fop/render/pcl/fonts/PCLSoftFont.java +++ b/src/java/org/apache/fop/render/pcl/fonts/PCLSoftFont.java @@ -38,13 +38,17 @@ public class PCLSoftFont { private FontFileReader reader; /** Map containing unicode character and it's soft font codepoint **/ private Map charsWritten; + private Map mappedChars; private Map 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(); + mappedChars = new HashMap(); + 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 mappedChars) { + this.mappedChars = mappedChars; + } + + public Map 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); + } + } } diff --git a/src/java/org/apache/fop/render/pcl/fonts/PCLSoftFontManager.java b/src/java/org/apache/fop/render/pcl/fonts/PCLSoftFontManager.java index 77da40a26..621ea4f18 100644 --- a/src/java/org/apache/fop/render/pcl/fonts/PCLSoftFontManager.java +++ b/src/java/org/apache/fop/render/pcl/fonts/PCLSoftFontManager.java @@ -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 fonts = new ArrayList(); 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> mappedGlyphs = mapFontGlyphs(font); + if (fontReaderFactory == null) { + fontReaderFactory = PCLFontReaderFactory.getInstance(pclByteWriter); + } fontReader = fontReaderFactory.createInstance(font); + initialize(); + if (mappedGlyphs.isEmpty()) { + mappedGlyphs.add(new HashMap()); + } if (fontReader != null) { - initialize(); - assignFontID(); - writeFontHeader(); - softFont.setCharacterOffsets(fontReader.getCharacterOffsets()); - softFont.setOpenFont(fontReader.getFontFile()); - softFont.setReader(fontReader.getFontFileReader()); - fonts.add(softFont); + for (Map 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> mapFontGlyphs(Typeface tf) { + List> mappedGlyphs = new ArrayList>(); + if (tf instanceof CustomFontMetricsMapper) { + CustomFontMetricsMapper fontMetrics = (CustomFontMetricsMapper) tf; + CustomFont customFont = (CustomFont) fontMetrics.getRealFont(); + mappedGlyphs = mapGlyphs(customFont.getUsedGlyphs(), customFont); + } + return mappedGlyphs; + } + + private List> mapGlyphs(Map usedGlyphs, CustomFont font) { + int charCount = 32; + List> mappedGlyphs = new ArrayList>(); + Map fontGlyphs = new HashMap(); + for (Entry 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(); + } + 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 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 fontSegments = fontReader.getFontSegments(); + private void writeSegmentedFontData(ByteArrayOutputStream header, + Map mappedGlyphs) throws IOException { + List fontSegments = fontReader.getFontSegments(mappedGlyphs); for (PCLFontSegment segment : fontSegments) { writeFontSegment(header, segment); } @@ -135,27 +187,39 @@ public class PCLSoftFontManager { header.write(segment.getData()); } - public List 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 getTextSegments(String text, Typeface font) { + List textSegments = new ArrayList(); + 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; + } } } diff --git a/src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFCharacterWriter.java b/src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFCharacterWriter.java index 0d2eaf41e..41fefaaf0 100644 --- a/src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFCharacterWriter.java +++ b/src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFCharacterWriter.java @@ -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 composite = glyfTable.retrieveComposedGlyphs(charIndex); - for (Integer compositeIndex : composite) { + Set 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)); } } diff --git a/src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFFontReader.java b/src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFFontReader.java index c52d2d5ab..1a054953c 100644 --- a/src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFFontReader.java +++ b/src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFFontReader.java @@ -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 getFontSegments() throws IOException { + public List getFontSegments(Map mappedGlyphs) + throws IOException { List fontSegments = new ArrayList(); 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 mappedGlyphs) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); - Map tableOffsets = new HashMap(); + List tableOffsets = new ArrayList(); // 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 tableOffsets) throws IOException, UnsupportedEncodingException { + List 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 tableOffsets, ByteArrayOutputStream baos) + private ByteArrayOutputStream copyTables(List tableOffsets, + ByteArrayOutputStream baos, byte[] hmtxTable, int hmtxSize) throws IOException { Map offsetValues = new HashMap(); - //for (OFDirTabEntry table : tableOffsets.keySet()) { - for (Entry 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 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 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 mappedGlyphs) throws IOException { + byte[] hmtxTable = new byte[((mappedGlyphs.size() + 32) * 4)]; + OFDirTabEntry entry = ttfFont.getDirectoryEntry(OFTableName.HMTX); + + if (entry != null) { + for (Entry 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 diff --git a/test/java/org/apache/fop/render/pcl/fonts/PCLTTFFontReaderTestCase.java b/test/java/org/apache/fop/render/pcl/fonts/PCLTTFFontReaderTestCase.java index 88c613e7d..5673efbb4 100644 --- a/test/java/org/apache/fop/render/pcl/fonts/PCLTTFFontReaderTestCase.java +++ b/test/java/org/apache/fop/render/pcl/fonts/PCLTTFFontReaderTestCase.java @@ -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 segments = reader.getFontSegments(); + HashMap mappedChars = new HashMap(); + mappedChars.put('H', 1); + mappedChars.put('e', 1); + mappedChars.put('l', 1); + mappedChars.put('o', 1); + + List 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); + } } diff --git a/test/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFCharacterWriterTestCase.java b/test/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFCharacterWriterTestCase.java index 10263d067..04849db87 100644 --- a/test/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFCharacterWriterTestCase.java +++ b/test/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFCharacterWriterTestCase.java @@ -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); -- 2.39.5