From 5a7195cae382c971145f722d9404174c17827c99 Mon Sep 17 00:00:00 2001 From: Robert Meyer Date: Fri, 12 Jun 2015 15:52:55 +0000 Subject: [PATCH] Creation of soft fonts for TrueType fonts in PCL git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/branches/Temp_PCLSoftFonts@1685112 13f79535-47bb-0310-9956-ffa450edef68 --- findbugs-exclude.xml | 4 + .../apache/fop/fonts/truetype/GlyfTable.java | 6 +- .../fop/fonts/truetype/OFDirTabEntry.java | 4 +- .../apache/fop/fonts/truetype/OpenFont.java | 27 +- .../apache/fop/fonts/truetype/TTFFile.java | 8 + .../java2d/ConfiguredFontCollection.java | 3 +- .../java2d/CustomFontMetricsMapper.java | 4 + .../apache/fop/render/pcl/HardcodedFonts.java | 2 +- .../fop/render/pcl/PCLEventProducer.java | 9 + .../fop/render/pcl/PCLEventProducer.xml | 1 + .../apache/fop/render/pcl/PCLGenerator.java | 9 + .../org/apache/fop/render/pcl/PCLPainter.java | 179 ++++- .../render/pcl/PCLRendererConfigurator.java | 1 + .../fop/render/pcl/PCLRenderingUtil.java | 15 +- .../render/pcl/fonts/PCLByteWriterUtil.java | 125 ++++ .../pcl/fonts/PCLCharacterDefinition.java | 146 +++++ .../render/pcl/fonts/PCLCharacterWriter.java | 42 ++ .../fop/render/pcl/fonts/PCLFontReader.java | 106 +++ .../pcl/fonts/PCLFontReaderFactory.java | 65 ++ .../fop/render/pcl/fonts/PCLFontSegment.java | 62 ++ .../fop/render/pcl/fonts/PCLSoftFont.java | 128 ++++ .../render/pcl/fonts/PCLSoftFontManager.java | 170 +++++ .../fop/render/pcl/fonts/PCLSymbolSet.java | 200 ++++++ .../fonts/truetype/PCLTTFCharacterWriter.java | 152 +++++ .../pcl/fonts/truetype/PCLTTFFontReader.java | 619 ++++++++++++++++++ .../fonts/truetype/PCLTTFOS2FontTable.java | 77 +++ .../fonts/truetype/PCLTTFPCLTFontTable.java | 115 ++++ .../fonts/truetype/PCLTTFPOSTFontTable.java | 51 ++ .../pcl/fonts/truetype/PCLTTFTable.java | 47 ++ .../fonts/truetype/PCLTTFTableFactory.java | 49 ++ .../pcl/fonts/MockPCLTTFFontReader.java | 51 ++ .../pcl/fonts/PCLByteWriterUtilTestCase.java | 74 +++ .../fonts/PCLFontReaderFactoryTestCase.java | 52 ++ .../pcl/fonts/PCLTTFFontReaderTestCase.java | 120 ++++ .../PCLTTFCharacterWriterTestCase.java | 76 +++ 35 files changed, 2753 insertions(+), 46 deletions(-) create mode 100644 src/java/org/apache/fop/render/pcl/fonts/PCLByteWriterUtil.java create mode 100644 src/java/org/apache/fop/render/pcl/fonts/PCLCharacterDefinition.java create mode 100644 src/java/org/apache/fop/render/pcl/fonts/PCLCharacterWriter.java create mode 100644 src/java/org/apache/fop/render/pcl/fonts/PCLFontReader.java create mode 100644 src/java/org/apache/fop/render/pcl/fonts/PCLFontReaderFactory.java create mode 100644 src/java/org/apache/fop/render/pcl/fonts/PCLFontSegment.java create mode 100644 src/java/org/apache/fop/render/pcl/fonts/PCLSoftFont.java create mode 100644 src/java/org/apache/fop/render/pcl/fonts/PCLSoftFontManager.java create mode 100644 src/java/org/apache/fop/render/pcl/fonts/PCLSymbolSet.java create mode 100644 src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFCharacterWriter.java create mode 100644 src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFFontReader.java create mode 100644 src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFOS2FontTable.java create mode 100644 src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFPCLTFontTable.java create mode 100644 src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFPOSTFontTable.java create mode 100644 src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFTable.java create mode 100644 src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFTableFactory.java create mode 100644 test/java/org/apache/fop/render/pcl/fonts/MockPCLTTFFontReader.java create mode 100644 test/java/org/apache/fop/render/pcl/fonts/PCLByteWriterUtilTestCase.java create mode 100644 test/java/org/apache/fop/render/pcl/fonts/PCLFontReaderFactoryTestCase.java create mode 100644 test/java/org/apache/fop/render/pcl/fonts/PCLTTFFontReaderTestCase.java create mode 100644 test/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFCharacterWriterTestCase.java diff --git a/findbugs-exclude.xml b/findbugs-exclude.xml index 2eaa8580b..13f811251 100644 --- a/findbugs-exclude.xml +++ b/findbugs-exclude.xml @@ -238,6 +238,10 @@ + + + + diff --git a/src/java/org/apache/fop/fonts/truetype/GlyfTable.java b/src/java/org/apache/fop/fonts/truetype/GlyfTable.java index 1233a586e..6ad479a0e 100644 --- a/src/java/org/apache/fop/fonts/truetype/GlyfTable.java +++ b/src/java/org/apache/fop/fonts/truetype/GlyfTable.java @@ -47,7 +47,7 @@ public class GlyfTable { /** All the glyphs that are composed, but do not appear in the subset. */ protected Set composedGlyphs = new TreeSet(); - protected GlyfTable(FontFileReader in, OFMtxEntry[] metrics, OFDirTabEntry dirTableEntry, + public GlyfTable(FontFileReader in, OFMtxEntry[] metrics, OFDirTabEntry dirTableEntry, Map glyphs) throws IOException { mtxTab = metrics; tableOffset = dirTableEntry.getOffset(); @@ -202,7 +202,7 @@ public class GlyfTable { } while (GlyfFlags.hasMoreComposites(flags)); } - private boolean isComposite(int indexInOriginal) throws IOException { + public boolean isComposite(int indexInOriginal) throws IOException { int numberOfContours = in.readTTFShort(tableOffset + mtxTab[indexInOriginal].getOffset()); return numberOfContours < 0; } @@ -215,7 +215,7 @@ public class GlyfTable { * @return the set of glyph indices this glyph composes * @throws IOException an I/O error */ - private Set retrieveComposedGlyphs(int indexInOriginal) + public Set retrieveComposedGlyphs(int indexInOriginal) throws IOException { Set composedGlyphs = new HashSet(); long offset = tableOffset + mtxTab[indexInOriginal].getOffset() + 10; diff --git a/src/java/org/apache/fop/fonts/truetype/OFDirTabEntry.java b/src/java/org/apache/fop/fonts/truetype/OFDirTabEntry.java index 0e468eb7b..34e7fba14 100644 --- a/src/java/org/apache/fop/fonts/truetype/OFDirTabEntry.java +++ b/src/java/org/apache/fop/fonts/truetype/OFDirTabEntry.java @@ -30,7 +30,7 @@ import java.util.Arrays; public class OFDirTabEntry { private byte[] tag = new byte[4]; - private int checksum; + private long checksum; private long offset; private long length; @@ -74,7 +74,7 @@ public class OFDirTabEntry { * Returns the checksum. * @return int */ - public int getChecksum() { + public long getChecksum() { return checksum; } diff --git a/src/java/org/apache/fop/fonts/truetype/OpenFont.java b/src/java/org/apache/fop/fonts/truetype/OpenFont.java index 9d3a4769d..62a016c8a 100644 --- a/src/java/org/apache/fop/fonts/truetype/OpenFont.java +++ b/src/java/org/apache/fop/fonts/truetype/OpenFont.java @@ -355,7 +355,7 @@ public abstract class OpenFont { long offset) throws IOException { OFDirTabEntry dt = dirTabs.get(tableName); if (dt == null) { - log.error("Dirtab " + tableName.getName() + " not found."); + log.info("Dirtab " + tableName.getName() + " not found."); return false; } else { in.seekSet(dt.getOffset() + offset); @@ -965,6 +965,15 @@ public abstract class OpenFont { return fbb; } + /** + * Returns the original bounding box values from the HEAD table + * @return An array of bounding box values + */ + public int[] getBBoxRaw() { + int[] bbox = {fontBBox1, fontBBox2, fontBBox3, fontBBox4}; + return bbox; + } + /** * Returns the LowerCaseAscent attribute of the font. * @return int The LowerCaseAscent @@ -1047,6 +1056,18 @@ public abstract class OpenFont { return convertTTFUnit2PDFUnit(ansiWidth[idx]); } + /** + * Returns the width of a given character in raw units + * @param idx Index of the character + * @return int Width in it's raw form stored in the font + */ + public int getCharWidthRaw(int idx) { + if (ansiWidth != null) { + return ansiWidth[idx]; + } + return -1; + } + /** * Returns the kerning table. * @return Map The kerning table @@ -1979,4 +2000,8 @@ public abstract class OpenFont { IOUtils.closeQuietly(stream); } } + + public String getCopyrightNotice() { + return notice; + } } diff --git a/src/java/org/apache/fop/fonts/truetype/TTFFile.java b/src/java/org/apache/fop/fonts/truetype/TTFFile.java index 52df45ffb..4b0e3d628 100644 --- a/src/java/org/apache/fop/fonts/truetype/TTFFile.java +++ b/src/java/org/apache/fop/fonts/truetype/TTFFile.java @@ -188,6 +188,14 @@ public class TTFFile extends OpenFont { : (fontFile.readTTFUShort() << 1)); } + /** + * Gets the last location of the glyf table + * @return The last location as a long + */ + public long getLastGlyfLocation() { + return lastLoca; + } + @Override protected void initializeFont(FontFileReader in) throws IOException { fontFile = in; diff --git a/src/java/org/apache/fop/render/java2d/ConfiguredFontCollection.java b/src/java/org/apache/fop/render/java2d/ConfiguredFontCollection.java index b9b7539fc..c6f02506e 100644 --- a/src/java/org/apache/fop/render/java2d/ConfiguredFontCollection.java +++ b/src/java/org/apache/fop/render/java2d/ConfiguredFontCollection.java @@ -83,7 +83,8 @@ public class ConfiguredFontCollection implements FontCollection { font = new CustomFontMetricsMapper(fontMetrics, fontSource); } else { FontUris fontUris = new FontUris(fontURI, null); - CustomFont fontMetrics = FontLoader.loadFont(fontUris, null, true, + CustomFont fontMetrics = FontLoader.loadFont(fontUris, + configFontInfo.getSubFontName(), true, configFontInfo.getEmbeddingMode(), configFontInfo.getEncodingMode(), configFontInfo.getKerning(), configFontInfo.getAdvanced(), resourceResolver); font = new CustomFontMetricsMapper(fontMetrics); diff --git a/src/java/org/apache/fop/render/java2d/CustomFontMetricsMapper.java b/src/java/org/apache/fop/render/java2d/CustomFontMetricsMapper.java index 62283915f..c7f5da116 100644 --- a/src/java/org/apache/fop/render/java2d/CustomFontMetricsMapper.java +++ b/src/java/org/apache/fop/render/java2d/CustomFontMetricsMapper.java @@ -289,4 +289,8 @@ public class CustomFontMetricsMapper extends Typeface implements FontMetricsMapp } } + public Typeface getRealFont() { + return typeface; + } + } diff --git a/src/java/org/apache/fop/render/pcl/HardcodedFonts.java b/src/java/org/apache/fop/render/pcl/HardcodedFonts.java index 185e1ece5..82e622098 100644 --- a/src/java/org/apache/fop/render/pcl/HardcodedFonts.java +++ b/src/java/org/apache/fop/render/pcl/HardcodedFonts.java @@ -55,7 +55,7 @@ final class HardcodedFonts { return selectFont(gen, name, size); } - private static boolean selectFont(PCLGenerator gen, String name, int size) throws IOException { + protected static boolean selectFont(PCLGenerator gen, String name, int size) throws IOException { int fontcode = 0; if (name.length() > 1 && name.charAt(0) == 'F') { try { diff --git a/src/java/org/apache/fop/render/pcl/PCLEventProducer.java b/src/java/org/apache/fop/render/pcl/PCLEventProducer.java index 7d2ed252e..3a8a1de55 100644 --- a/src/java/org/apache/fop/render/pcl/PCLEventProducer.java +++ b/src/java/org/apache/fop/render/pcl/PCLEventProducer.java @@ -53,4 +53,13 @@ public interface PCLEventProducer extends EventProducer { */ void paperTypeUnavailable(Object source, long pageWidth, long pageHeight, String fallbackPaper); + /** + * The font type is not supported for PCL output. + * @param source The event source + * @param fontName The name of the font not supported + * @param supportedTypes The types of fonts currently supported + * @event.severity ERROR + */ + void fontTypeNotSupported(Object source, String fontName, String supportedTypes); + } diff --git a/src/java/org/apache/fop/render/pcl/PCLEventProducer.xml b/src/java/org/apache/fop/render/pcl/PCLEventProducer.xml index 5d5785a4c..463fcb16a 100644 --- a/src/java/org/apache/fop/render/pcl/PCLEventProducer.xml +++ b/src/java/org/apache/fop/render/pcl/PCLEventProducer.xml @@ -1,4 +1,5 @@ Paper type ({pageWidth} x {pageHeight} mpt) could not be determined. Falling back to: {fallbackPaper} + The font '{fontName}' is not supported. PCL output currently only supports {supportedTypes} diff --git a/src/java/org/apache/fop/render/pcl/PCLGenerator.java b/src/java/org/apache/fop/render/pcl/PCLGenerator.java index 05a0fc666..67d37fbb5 100644 --- a/src/java/org/apache/fop/render/pcl/PCLGenerator.java +++ b/src/java/org/apache/fop/render/pcl/PCLGenerator.java @@ -140,6 +140,15 @@ public class PCLGenerator { out.write(s.getBytes(ISO_8859_1)); } + /** + * Writes raw bytes to the output stream + * @param bytes The bytes + * @throws IOException In case of an I/O error + */ + public void writeBytes(byte[] bytes) throws IOException { + out.write(bytes); + } + /** * Formats a double value with two decimal positions for PCL output. * diff --git a/src/java/org/apache/fop/render/pcl/PCLPainter.java b/src/java/org/apache/fop/render/pcl/PCLPainter.java index 9f7de4930..26a6da66b 100644 --- a/src/java/org/apache/fop/render/pcl/PCLPainter.java +++ b/src/java/org/apache/fop/render/pcl/PCLPainter.java @@ -28,6 +28,7 @@ import java.awt.Rectangle; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.Map; import java.util.Stack; @@ -42,16 +43,26 @@ import org.apache.xmlgraphics.image.loader.impl.ImageGraphics2D; import org.apache.xmlgraphics.java2d.GraphicContext; import org.apache.xmlgraphics.java2d.Graphics2DImagePainter; +import org.apache.fop.fonts.CIDFontType; import org.apache.fop.fonts.Font; import org.apache.fop.fonts.FontTriplet; +import org.apache.fop.fonts.FontType; +import org.apache.fop.fonts.LazyFont; +import org.apache.fop.fonts.MultiByteFont; +import org.apache.fop.fonts.Typeface; import org.apache.fop.render.ImageHandlerUtil; import org.apache.fop.render.RenderingContext; import org.apache.fop.render.intermediate.AbstractIFPainter; import org.apache.fop.render.intermediate.IFException; import org.apache.fop.render.intermediate.IFState; import org.apache.fop.render.intermediate.IFUtil; +import org.apache.fop.render.java2d.CustomFontMetricsMapper; import org.apache.fop.render.java2d.FontMetricsMapper; import org.apache.fop.render.java2d.Java2DPainter; +import org.apache.fop.render.pcl.fonts.PCLCharacterWriter; +import org.apache.fop.render.pcl.fonts.PCLSoftFont; +import org.apache.fop.render.pcl.fonts.PCLSoftFontManager; +import org.apache.fop.render.pcl.fonts.truetype.PCLTTFCharacterWriter; import org.apache.fop.traits.BorderProps; import org.apache.fop.traits.RuleStyle; import org.apache.fop.util.CharUtilities; @@ -73,6 +84,8 @@ public class PCLPainter extends AbstractIFPainter implements private Stack graphicContextStack = new Stack(); private GraphicContext graphicContext = new GraphicContext(); + private PCLSoftFontManager sfManager = new PCLSoftFontManager(); + /** * Main constructor. * @param parent the parent document handler @@ -315,16 +328,40 @@ public class PCLPainter extends AbstractIFPainter implements //TODO Ignored: state.getFontVariant() //TODO Opportunity for font caching if font state is more heavily used String fontKey = getFontKey(triplet); - boolean pclFont = getPCLUtil().isAllTextAsBitmaps() ? false - : HardcodedFonts.setFont(gen, fontKey, state.getFontSize(), text); + Typeface tf = getTypeface(fontKey); + boolean drawAsBitmaps = getPCLUtil().isAllTextAsBitmaps(); + boolean pclFont = HardcodedFonts.setFont(gen, fontKey, state.getFontSize(), text); if (pclFont) { drawTextNative(x, y, letterSpacing, wordSpacing, dp, text, triplet); } else { - drawTextAsBitmap(x, y, letterSpacing, wordSpacing, dp, text, triplet); - if (DEBUG) { - state.setTextColor(Color.GRAY); - HardcodedFonts.setFont(gen, "F1", state.getFontSize(), text); - drawTextNative(x, y, letterSpacing, wordSpacing, dp, text, triplet); + // TrueType conversion to a soft font (PCL 5 Technical Reference - Chapter 11) + if (!drawAsBitmaps && isTrueType(tf)) { + boolean madeSF = false; + if (sfManager.getSoftFont(tf) == 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)); + } + 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) { + state.setTextColor(Color.GRAY); + HardcodedFonts.setFont(gen, "F1", state.getFontSize(), text); + drawTextNative(x, y, letterSpacing, wordSpacing, dp, text, triplet); + } } } } catch (IOException ioe) { @@ -332,6 +369,29 @@ public class PCLPainter extends AbstractIFPainter implements } } + private boolean isTrueType(Typeface tf) { + if (tf.getFontType().equals(FontType.TRUETYPE)) { + return true; + } else if (tf instanceof CustomFontMetricsMapper) { + Typeface realFont = ((CustomFontMetricsMapper) tf).getRealFont(); + if (realFont instanceof MultiByteFont) { + return ((MultiByteFont) realFont).getCIDType().equals(CIDFontType.CIDTYPE2); + } + } + return false; + } + + private Typeface getTypeface(String fontName) { + if (fontName == null) { + throw new NullPointerException("fontName must not be null"); + } + Typeface tf = getFontInfo().getFonts().get(fontName); + if (tf instanceof LazyFont) { + tf = ((LazyFont)tf).getRealFont(); + } + return tf; + } + private void drawTextNative(int x, int y, int letterSpacing, int wordSpacing, int[][] dp, String text, FontTriplet triplet) throws IOException { Color textColor = state.getTextColor(); @@ -414,25 +474,86 @@ public class PCLPainter extends AbstractIFPainter implements } + private void drawTextUsingSoftFont(int x, int y, int letterSpacing, int wordSpacing, int[][] dp, + String text, FontTriplet triplet, PCLSoftFont softFont) throws IOException { + Color textColor = state.getTextColor(); + if (textColor != null) { + gen.setTransparencyMode(true, false); + gen.selectGrayscale(textColor); + } + + setCursorPos(x, y); + + float fontSize = state.getFontSize() / 1000f; + Font font = getFontInfo().getFontInstance(triplet, state.getFontSize()); + int l = text.length(); + int[] dx = IFUtil.convertDPToDX(dp); + int dxl = (dx != null ? dx.length : 0); + + StringBuffer sb = new StringBuffer(Math.max(16, l)); + if (dx != null && dxl > 0 && dx[0] != 0) { + sb.append("\u001B&a+").append(gen.formatDouble2(dx[0] / 100.0)).append('H'); + } + String current = ""; + for (int i = 0; i < l; i++) { + char orgChar = text.charAt(i); + float glyphAdjust = 0; + if (!font.hasChar(orgChar)) { + if (CharUtilities.isFixedWidthSpace(orgChar)) { + //Fixed width space are rendered as spaces so copy/paste works in a reader + char ch = font.mapChar(CharUtilities.SPACE); + int spaceDiff = font.getCharWidth(ch) - font.getCharWidth(orgChar); + glyphAdjust = -(10 * spaceDiff / fontSize); + } + } + + if ((wordSpacing != 0) && CharUtilities.isAdjustableSpace(orgChar)) { + glyphAdjust += wordSpacing; + } + current += orgChar; + glyphAdjust += letterSpacing; + if (dx != null && i < dxl - 1) { + glyphAdjust += dx[i + 1]; + } + + if (glyphAdjust != 0) { + gen.getOutputStream().write(sb.toString().getBytes(gen.getTextEncoding())); + for (int j = 0; j < current.length(); j++) { + gen.getOutputStream().write( + softFont.getUnicodeCodePoint((int) current.charAt(j))); + } + sb = new StringBuffer(); + + String command = (glyphAdjust > 0) ? "\u001B&a+" : "\u001B&a"; + sb.append(command).append(gen.formatDouble2(glyphAdjust / 100.0)).append('H'); + + current = ""; + } + } + if (!current.equals("")) { + gen.getOutputStream().write(sb.toString().getBytes(gen.getTextEncoding())); + for (int i = 0; i < current.length(); i++) { + gen.getOutputStream().write(softFont.getUnicodeCodePoint((int) current.charAt(i))); + } + } + } + private static final double SAFETY_MARGIN_FACTOR = 0.05; - private Rectangle getTextBoundingBox(int x, int y, - int letterSpacing, int wordSpacing, int[][] dp, - String text, - Font font, FontMetricsMapper metrics) { + private Rectangle getTextBoundingBox(int x, int y, int letterSpacing, int wordSpacing, + int[][] dp, String text, Font font, FontMetricsMapper metrics) { int maxAscent = metrics.getMaxAscent(font.getFontSize()) / 1000; - int descent = metrics.getDescender(font.getFontSize()) / 1000; //is negative - int safetyMargin = (int)(SAFETY_MARGIN_FACTOR * font.getFontSize()); - Rectangle boundingRect = new Rectangle( - x, y - maxAscent - safetyMargin, - 0, maxAscent - descent + 2 * safetyMargin); + int descent = metrics.getDescender(font.getFontSize()) / 1000; // is negative + int safetyMargin = (int) (SAFETY_MARGIN_FACTOR * font.getFontSize()); + Rectangle boundingRect = new Rectangle(x, y - maxAscent - safetyMargin, 0, maxAscent + - descent + 2 * safetyMargin); int l = text.length(); int[] dx = IFUtil.convertDPToDX(dp); int dxl = (dx != null ? dx.length : 0); if (dx != null && dxl > 0 && dx[0] != 0) { - boundingRect.setLocation(boundingRect.x - (int)Math.ceil(dx[0] / 10f), boundingRect.y); + boundingRect.setLocation(boundingRect.x - (int) Math.ceil(dx[0] / 10f), boundingRect.y); } float width = 0.0f; for (int i = 0; i < l; i++) { @@ -451,19 +572,17 @@ public class PCLPainter extends AbstractIFPainter implements width += cw + glyphAdjust; } int extraWidth = font.getFontSize() / 3; - boundingRect.setSize( - (int)Math.ceil(width) + extraWidth, - boundingRect.height); + boundingRect.setSize((int) Math.ceil(width) + extraWidth, boundingRect.height); return boundingRect; } - private void drawTextAsBitmap(final int x, final int y, - final int letterSpacing, final int wordSpacing, final int[][] dp, - final String text, FontTriplet triplet) throws IFException { - //Use Java2D to paint different fonts via bitmap + private void drawTextAsBitmap(final int x, final int y, final int letterSpacing, + final int wordSpacing, final int[][] dp, final String text, FontTriplet triplet) + throws IFException { + // Use Java2D to paint different fonts via bitmap final Font font = getFontInfo().getFontInstance(triplet, state.getFontSize()); - //for cursive fonts, so the text isn't clipped + // for cursive fonts, so the text isn't clipped FontMetricsMapper mapper; try { mapper = (FontMetricsMapper) getFontInfo().getMetricsFor(font.getFontName()); @@ -473,11 +592,11 @@ public class PCLPainter extends AbstractIFPainter implements final int maxAscent = mapper.getMaxAscent(font.getFontSize()) / 1000; final int ascent = mapper.getAscender(font.getFontSize()) / 1000; final int descent = mapper.getDescender(font.getFontSize()) / 1000; - int safetyMargin = (int)(SAFETY_MARGIN_FACTOR * font.getFontSize()); + int safetyMargin = (int) (SAFETY_MARGIN_FACTOR * font.getFontSize()); final int baselineOffset = maxAscent + safetyMargin; - final Rectangle boundingBox = getTextBoundingBox(x, y, - letterSpacing, wordSpacing, dp, text, font, mapper); + final Rectangle boundingBox = getTextBoundingBox(x, y, letterSpacing, wordSpacing, dp, + text, font, mapper); final Dimension dim = boundingBox.getSize(); Graphics2DImagePainter painter = new Graphics2DImagePainter() { @@ -485,7 +604,7 @@ public class PCLPainter extends AbstractIFPainter implements public void paint(Graphics2D g2d, Rectangle2D area) { if (DEBUG) { g2d.setBackground(Color.LIGHT_GRAY); - g2d.clearRect(0, 0, (int)area.getWidth(), (int)area.getHeight()); + g2d.clearRect(0, 0, (int) area.getWidth(), (int) area.getHeight()); } g2d.translate(-x, -y + baselineOffset); @@ -501,7 +620,7 @@ public class PCLPainter extends AbstractIFPainter implements try { painter.drawText(x, y, letterSpacing, wordSpacing, dp, text); } catch (IFException e) { - //This should never happen with the Java2DPainter + // This should never happen with the Java2DPainter throw new RuntimeException("Unexpected error while painting text", e); } } diff --git a/src/java/org/apache/fop/render/pcl/PCLRendererConfigurator.java b/src/java/org/apache/fop/render/pcl/PCLRendererConfigurator.java index dcda49059..33376655f 100644 --- a/src/java/org/apache/fop/render/pcl/PCLRendererConfigurator.java +++ b/src/java/org/apache/fop/render/pcl/PCLRendererConfigurator.java @@ -67,6 +67,7 @@ public class PCLRendererConfigurator extends PrintRendererConfigurator { if (config.isTextRendering() != null) { pclUtil.setAllTextAsBitmaps(config.isTextRendering()); } + } @Override diff --git a/src/java/org/apache/fop/render/pcl/PCLRenderingUtil.java b/src/java/org/apache/fop/render/pcl/PCLRenderingUtil.java index 3fc8b5258..32693764f 100644 --- a/src/java/org/apache/fop/render/pcl/PCLRenderingUtil.java +++ b/src/java/org/apache/fop/render/pcl/PCLRenderingUtil.java @@ -50,12 +50,6 @@ public class PCLRenderingUtil { /** Controls the dithering quality when rendering gray or color images. */ private float ditheringQuality = 0.5f; - /** - * Controls whether all text should be painted as text. This is a fallback setting in case - * the mixture of native and bitmapped text does not provide the necessary quality. - */ - private boolean allTextAsBitmaps; - /** * Controls whether an RGB canvas is used when converting Java2D graphics to bitmaps. * This can be used to work around problems with Apache Batik, for example, but setting @@ -68,6 +62,12 @@ public class PCLRenderingUtil { */ private boolean disabledPJL; + /** + * Controls whether all text should be painted as text. This is a fallback setting in case the mixture of native and + * bitmapped text does not provide the necessary quality. + */ + private boolean allTextAsBitmaps; + PCLRenderingUtil(FOUserAgent userAgent) { this.userAgent = userAgent; initialize(); @@ -127,8 +127,7 @@ public class PCLRenderingUtil { } /** - * Controls whether all text should be generated as bitmaps or only text for which there's - * no native font. + * Controls whether all text should be generated as bitmaps or only text for which there's no native font. * @param allTextAsBitmaps true if all text should be painted as bitmaps */ public void setAllTextAsBitmaps(boolean allTextAsBitmaps) { diff --git a/src/java/org/apache/fop/render/pcl/fonts/PCLByteWriterUtil.java b/src/java/org/apache/fop/render/pcl/fonts/PCLByteWriterUtil.java new file mode 100644 index 000000000..1b9382f7b --- /dev/null +++ b/src/java/org/apache/fop/render/pcl/fonts/PCLByteWriterUtil.java @@ -0,0 +1,125 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pcl.fonts; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Arrays; + +public class PCLByteWriterUtil { + + public byte[] padBytes(byte[] in, int length) { + return padBytes(in, length, 0); + } + + public byte[] padBytes(byte[] in, int length, int value) { + byte[] out = new byte[length]; + for (int i = 0; i < length; i++) { + if (i < in.length) { + out[i] = in[i]; + } else { + out[i] = (byte) value; + } + } + return out; + } + + public byte[] signedInt(int s) { + byte b1 = (byte) (s >> 8); + byte b2 = (byte) s; + return new byte[]{b1, b2}; + } + + public byte signedByte(int s) { + return (byte) s; + } + + public byte[] unsignedLongInt(int s) { + return unsignedLongInt((long) s); + } + + public byte[] unsignedLongInt(long s) { + byte b1 = (byte) ((s >> 24) & 0xff); + byte b2 = (byte) ((s >> 16) & 0xff); + byte b3 = (byte) ((s >> 8) & 0xff); + byte b4 = (byte) (s & 0xff); + return new byte[]{b1, b2, b3, b4}; + } + + public byte[] unsignedInt(int s) { + byte b1 = (byte) ((s >> 8) & 0xff); + byte b2 = (byte) (s & 0xff); + return new byte[]{b1, b2}; + } + + public int unsignedByte(int b) { + return (byte) b & 0xFF; + } + + public int maxPower2(int value) { + int test = 2; + while (test < value) { + test *= 2; + } + return test; + } + + public int log(int x, int base) { + return (int) (Math.log(x) / Math.log(base)); + } + + public byte[] toByteArray(int[] s) { + byte[] values = new byte[s.length]; + for (int i = 0; i < s.length; i++) { + values[i] = (byte) s[i]; + } + return values; + } + + public byte[] insertIntoArray(int index, byte[] insertTo, byte[] data) throws IOException { + byte[] preBytes = Arrays.copyOf(insertTo, index); + byte[] postBytes = Arrays.copyOfRange(insertTo, index, insertTo.length); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(preBytes); + baos.write(data); + baos.write(postBytes); + return baos.toByteArray(); + } + + public byte[] updateDataAtLocation(byte[] data, byte[] update, int offset) { + int count = 0; + for (int i = offset; i < offset + update.length; i++) { + data[i] = update[count++]; + } + return data; + } + + /** + * Writes a PCL escape command to the output stream. + * @param cmd the command (without the ESCAPE character) + * @throws IOException In case of an I/O error + */ + public byte[] writeCommand(String cmd) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(27); // ESC + baos.write(cmd.getBytes("US-ASCII")); + return baos.toByteArray(); + } +} diff --git a/src/java/org/apache/fop/render/pcl/fonts/PCLCharacterDefinition.java b/src/java/org/apache/fop/render/pcl/fonts/PCLCharacterDefinition.java new file mode 100644 index 000000000..be1ba7adc --- /dev/null +++ b/src/java/org/apache/fop/render/pcl/fonts/PCLCharacterDefinition.java @@ -0,0 +1,146 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pcl.fonts; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class PCLCharacterDefinition { + private int glyphID; + private int charCode; + private int charDefinitionSize; + private byte[] glyfData; + private boolean hasContinuation; + private PCLCharacterFormat charFormat; + private PCLCharacterClass charClass; + private PCLByteWriterUtil pclByteWriter; + private List composites; + + public PCLCharacterDefinition(int glyphID, int charCode, PCLCharacterFormat charFormat, + PCLCharacterClass charClass, byte[] glyfData, PCLByteWriterUtil pclByteWriter) { + this.glyphID = glyphID; + this.charCode = charCode; + this.charFormat = charFormat; + this.charClass = charClass; + this.glyfData = glyfData; + this.pclByteWriter = pclByteWriter; + // Glyph Data + (Descriptor Size) + (Character Data Size) + (Glyph ID) must + // be less than 32767 otherwise it will result in a continuation structure. + charDefinitionSize = glyfData.length + 4 + 2 + 2; + hasContinuation = charDefinitionSize > 32767; + composites = new ArrayList(); + } + + public byte[] getCharacterCommand() throws IOException { + return pclByteWriter.writeCommand(String.format("*c%dE", charCode)); + } + + public byte[] getCharacterDefinitionCommand() throws IOException { + return pclByteWriter.writeCommand(String.format("(s%dW", 10 + glyfData.length)); + } + + public byte[] getData() throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + // Write Character Descriptor + if (!hasContinuation) { + writeCharacterDescriptorHeader(0, baos); + baos.write(glyfData); + } else { + int continuations = glyfData.length / 32767; + for (int i = 0; i < continuations; i++) { + writeCharacterDescriptorHeader(i == 0 ? 0 : 1, baos); + int continuationStart = i * 32767; + int continuationLength = continuationStart - glyfData.length < 32767 + ? continuationStart - glyfData.length : 32767; + baos.write(glyfData, continuationStart, continuationLength); + } + } + baos.write(0); // Reserved + byte[] charBytes = baos.toByteArray(); + long sum = 0; + for (int i = 4; i < charBytes.length; i++) { + sum += charBytes[i]; + } + int remainder = (int) (sum % 256); + baos.write(256 - remainder); // Checksum + + return baos.toByteArray(); + } + + private void writeCharacterDescriptorHeader(int continuation, ByteArrayOutputStream baos) throws IOException { + baos.write(pclByteWriter.unsignedByte(charFormat.getValue())); + baos.write(continuation); + baos.write(pclByteWriter.unsignedByte(2)); // Descriptor size (from this byte to character data) + baos.write(pclByteWriter.unsignedByte(charClass.getValue())); + baos.write(pclByteWriter.unsignedInt(glyfData.length + 4)); + baos.write(pclByteWriter.unsignedInt(glyphID)); + } + + public void addCompositeGlyph(PCLCharacterDefinition composite) { + composites.add(composite); + } + + public List getCompositeGlyphs() { + return composites; + } + + /** + * Character Format used in PCL Character Descriptor See Table 11-50 from PCL 5 Specification + */ + public enum PCLCharacterFormat { + LaserJet_Raster(4), + Intellifont(10), + TrueType(15); + + private int value; + + PCLCharacterFormat(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + } + + /** + * Character Class used in PCL Character Descriptor See Table 11-51 from PCL 5 Specification + */ + public enum PCLCharacterClass { + Bitmap(1), + CompressedBitmap(2), + Contour_Intellifont(3), + Compound_Contour_Intellifont(4), + TrueType(15); + + private int value; + + PCLCharacterClass(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + } +} diff --git a/src/java/org/apache/fop/render/pcl/fonts/PCLCharacterWriter.java b/src/java/org/apache/fop/render/pcl/fonts/PCLCharacterWriter.java new file mode 100644 index 000000000..df04371e0 --- /dev/null +++ b/src/java/org/apache/fop/render/pcl/fonts/PCLCharacterWriter.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pcl.fonts; + +import java.io.IOException; + +import org.apache.fop.fonts.truetype.FontFileReader; +import org.apache.fop.fonts.truetype.OpenFont; + +public abstract class PCLCharacterWriter { + + protected PCLSoftFont font; + protected PCLByteWriterUtil pclByteWriter; + protected OpenFont openFont; + protected FontFileReader fontReader; + + public PCLCharacterWriter(PCLSoftFont font) throws IOException { + this.font = font; + openFont = font.getOpenFont(); + fontReader = font.getReader(); + pclByteWriter = new PCLByteWriterUtil(); + } + + public abstract byte[] writeCharacterDefinitions(String text) throws IOException; +} diff --git a/src/java/org/apache/fop/render/pcl/fonts/PCLFontReader.java b/src/java/org/apache/fop/render/pcl/fonts/PCLFontReader.java new file mode 100644 index 000000000..22e97605b --- /dev/null +++ b/src/java/org/apache/fop/render/pcl/fonts/PCLFontReader.java @@ -0,0 +1,106 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pcl.fonts; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import org.apache.fop.fonts.Typeface; +import org.apache.fop.fonts.truetype.FontFileReader; +import org.apache.fop.fonts.truetype.OpenFont; + +public abstract class PCLFontReader { + + protected Typeface typeface; + protected PCLByteWriterUtil pclByteWriter; + + public PCLFontReader(Typeface font, PCLByteWriterUtil pclByteWriter) { + this.typeface = font; + this.pclByteWriter = pclByteWriter; + } + + /** Header Data **/ + public abstract int getDescriptorSize(); + public abstract int getHeaderFormat(); + public abstract int getFontType(); + public abstract int getStyleMSB(); + public abstract int getBaselinePosition(); + public abstract int getCellWidth(); + public abstract int getCellHeight(); + public abstract int getOrientation(); + public abstract int getSpacing(); + public abstract int getSymbolSet(); + public abstract int getPitch(); + public abstract int getHeight(); + public abstract int getXHeight(); + public abstract int getWidthType(); + public abstract int getStyleLSB(); + public abstract int getStrokeWeight(); + public abstract int getTypefaceLSB(); + public abstract int getTypefaceMSB(); + public abstract int getSerifStyle(); + public abstract int getQuality(); + public abstract int getPlacement(); + public abstract int getUnderlinePosition(); + public abstract int getUnderlineThickness(); + public abstract int getTextHeight(); + public abstract int getTextWidth(); + public abstract int getFirstCode(); + public abstract int getLastCode(); + public abstract int getPitchExtended(); + public abstract int getHeightExtended(); + public abstract int getCapHeight(); + public abstract int getFontNumber(); + public abstract String getFontName(); + public abstract int getScaleFactor() throws IOException; + public abstract int getMasterUnderlinePosition() throws IOException; + public abstract int getMasterUnderlineThickness() throws IOException; + public abstract int getFontScalingTechnology(); + public abstract int getVariety(); + + /** Segmented Font Data **/ + public abstract List getFontSegments() throws IOException; + + /** Character Definitions **/ + public abstract Map getCharacterOffsets() throws IOException; + + public abstract OpenFont getFontFile(); + public abstract FontFileReader getFontFileReader(); + + /** + * Gets the most significant byte from a 16-bit integer + * @param s The number + * @return The resulting byte value as an integer + */ + protected int getMSB(int s) { + return s >> 8; + } + + /** + * Gets the least significant byte from a 16-bit integer + * @param s The number + * @return The resulting byte value as an integer + */ + protected int getLSB(int s) { + byte b1 = (byte) (s >> 8); + return s; + } +} diff --git a/src/java/org/apache/fop/render/pcl/fonts/PCLFontReaderFactory.java b/src/java/org/apache/fop/render/pcl/fonts/PCLFontReaderFactory.java new file mode 100644 index 000000000..15c4e8d54 --- /dev/null +++ b/src/java/org/apache/fop/render/pcl/fonts/PCLFontReaderFactory.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pcl.fonts; + +import java.io.IOException; + +import org.apache.fop.fonts.CIDFontType; +import org.apache.fop.fonts.CustomFont; +import org.apache.fop.fonts.FontType; +import org.apache.fop.fonts.MultiByteFont; +import org.apache.fop.fonts.Typeface; +import org.apache.fop.render.java2d.CustomFontMetricsMapper; +import org.apache.fop.render.pcl.fonts.truetype.PCLTTFFontReader; + +public final class PCLFontReaderFactory { + + private PCLByteWriterUtil pclByteWriter; + + private PCLFontReaderFactory(PCLByteWriterUtil pclByteWriter) { + this.pclByteWriter = pclByteWriter; + } + + public static PCLFontReaderFactory getInstance(PCLByteWriterUtil pclByteWriter) { + return new PCLFontReaderFactory(pclByteWriter); + } + + public PCLFontReader createInstance(Typeface font) throws IOException { + if (font.getFontType() == FontType.TRUETYPE || isCIDType2(font)) { + return new PCLTTFFontReader(font, pclByteWriter); + } + // else if (font instanceof MultiByteFont && ((MultiByteFont) font).isOTFFile()) { + // Placeholder for future Type 1 / OTF Soft font implementations e.g. + // return new PCLOTFFontReader(font, pclByteWriter); + // } + return null; + } + + private boolean isCIDType2(Typeface font) { + CustomFontMetricsMapper fontMetrics = (CustomFontMetricsMapper) font; + CustomFont customFont = (CustomFont) fontMetrics.getRealFont(); + + if (customFont instanceof MultiByteFont) { + return ((MultiByteFont) customFont).getCIDType() == CIDFontType.CIDTYPE2; + } + return false; + } + +} diff --git a/src/java/org/apache/fop/render/pcl/fonts/PCLFontSegment.java b/src/java/org/apache/fop/render/pcl/fonts/PCLFontSegment.java new file mode 100644 index 000000000..111dbf932 --- /dev/null +++ b/src/java/org/apache/fop/render/pcl/fonts/PCLFontSegment.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pcl.fonts; + +public class PCLFontSegment { + private SegmentID identifier; + private byte[] data; + + public PCLFontSegment(SegmentID identifier, byte[] data) { + this.identifier = identifier; + this.data = data; + } + + public byte[] getData() { + return data; + } + + public SegmentID getIdentifier() { + return identifier; + } + + public int getSize() { + return (identifier == SegmentID.NULL) ? 0 : data.length; + } + + public enum SegmentID { + CC(17219), // Character Complement + CP(17232), // Copyright + GT(18260), // Global TrueType Data + IF(18758), // Intellifont Face Data + PA(20545), // PANOSE Description + XW(22619), // XWindows Font Name + NULL(65535); // Null Segment + + private int complementID; + + SegmentID(int complementID) { + this.complementID = complementID; + } + + public int getValue() { + return complementID; + } + } +} diff --git a/src/java/org/apache/fop/render/pcl/fonts/PCLSoftFont.java b/src/java/org/apache/fop/render/pcl/fonts/PCLSoftFont.java new file mode 100644 index 000000000..87f399839 --- /dev/null +++ b/src/java/org/apache/fop/render/pcl/fonts/PCLSoftFont.java @@ -0,0 +1,128 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pcl.fonts; + +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; + +import org.apache.fop.fonts.MultiByteFont; +import org.apache.fop.fonts.Typeface; +import org.apache.fop.fonts.truetype.FontFileReader; +import org.apache.fop.fonts.truetype.OpenFont; +import org.apache.fop.render.java2d.CustomFontMetricsMapper; + +public class PCLSoftFont { + private int fontID; + private Typeface font; + private Map charOffsets; + private OpenFont openFont; + private InputStream fontStream; + private FontFileReader reader; + /** Map containing unicode character and it's soft font codepoint **/ + private Map charsWritten; + private Map charMtxPositions; + private int charCount = 32; + + public PCLSoftFont(int fontID, Typeface font) { + this.fontID = fontID; + this.font = font; + charsWritten = new HashMap(); + } + + public Typeface getTypeface() { + return font; + } + + public int getFontID() { + return fontID; + } + + public void setCharacterOffsets(Map charOffsets) { + this.charOffsets = charOffsets; + } + + public Map getCharacterOffsets() { + return charOffsets; + } + + public OpenFont getOpenFont() { + return openFont; + } + + public void setOpenFont(OpenFont openFont) { + this.openFont = openFont; + } + + public InputStream getFontStream() { + return fontStream; + } + + public void setFontStream(InputStream fontStream) { + this.fontStream = fontStream; + } + + public FontFileReader getReader() { + return reader; + } + + public void setReader(FontFileReader reader) { + this.reader = reader; + } + + public void writeCharacter(int unicode) { + charsWritten.put(unicode, charCount++); + } + + public int getUnicodeCodePoint(int unicode) { + return charsWritten.get(unicode); + } + + public boolean hasPreviouslyWritten(int unicode) { + return charsWritten.containsKey(unicode); + } + + public int getMtxCharIndex(int unicode) { + if (charMtxPositions.get(unicode) != null) { + return charMtxPositions.get(unicode); + } + return 0; + } + + public int getCmapGlyphIndex(int unicode) { + if (font instanceof CustomFontMetricsMapper) { + CustomFontMetricsMapper customFont = (CustomFontMetricsMapper) font; + Typeface realFont = customFont.getRealFont(); + if (realFont instanceof MultiByteFont) { + MultiByteFont mbFont = (MultiByteFont) realFont; + return mbFont.findGlyphIndex(unicode); + } + } + return 0; + } + + public void setMtxCharIndexes(Map charMtxPositions) { + this.charMtxPositions = charMtxPositions; + } + + public int getCharCount() { + return charCount; + } +} diff --git a/src/java/org/apache/fop/render/pcl/fonts/PCLSoftFontManager.java b/src/java/org/apache/fop/render/pcl/fonts/PCLSoftFontManager.java new file mode 100644 index 000000000..77da40a26 --- /dev/null +++ b/src/java/org/apache/fop/render/pcl/fonts/PCLSoftFontManager.java @@ -0,0 +1,170 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pcl.fonts; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.apache.fop.fonts.Typeface; + +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; + + public ByteArrayOutputStream makeSoftFont(Typeface font) throws IOException { + PCLSoftFont softFont = new PCLSoftFont(fonts.size() + 1, font); + fontReaderFactory = PCLFontReaderFactory.getInstance(pclByteWriter); + fontReader = fontReaderFactory.createInstance(font); + if (fontReader != null) { + initialize(); + assignFontID(); + writeFontHeader(); + softFont.setCharacterOffsets(fontReader.getCharacterOffsets()); + softFont.setOpenFont(fontReader.getFontFile()); + softFont.setReader(fontReader.getFontFileReader()); + fonts.add(softFont); + return baos; + } else { + return null; + } + } + + private void initialize() { + baos.reset(); + } + + private void assignFontID() throws IOException { + baos.write(pclByteWriter.writeCommand(String.format("*c%dD", fonts.size() + 1))); + } + + private void writeFontHeader() throws IOException { + ByteArrayOutputStream header = new ByteArrayOutputStream(); + header.write(pclByteWriter.unsignedInt(fontReader.getDescriptorSize())); + header.write(pclByteWriter.unsignedByte(fontReader.getHeaderFormat())); + header.write(pclByteWriter.unsignedByte(fontReader.getFontType())); + header.write(pclByteWriter.unsignedByte(fontReader.getStyleMSB())); + header.write(0); // Reserved + header.write(pclByteWriter.unsignedInt(fontReader.getBaselinePosition())); + header.write(pclByteWriter.unsignedInt(fontReader.getCellWidth())); + header.write(pclByteWriter.unsignedInt(fontReader.getCellHeight())); + header.write(pclByteWriter.unsignedByte(fontReader.getOrientation())); + header.write(fontReader.getSpacing()); + header.write(pclByteWriter.unsignedInt(fontReader.getSymbolSet())); + header.write(pclByteWriter.unsignedInt(fontReader.getPitch())); + header.write(pclByteWriter.unsignedInt(fontReader.getHeight())); + header.write(pclByteWriter.unsignedInt(fontReader.getXHeight())); + header.write(pclByteWriter.signedByte(fontReader.getWidthType())); + header.write(pclByteWriter.unsignedByte(fontReader.getStyleLSB())); + header.write(pclByteWriter.signedByte(fontReader.getStrokeWeight())); + header.write(pclByteWriter.unsignedByte(fontReader.getTypefaceLSB())); + header.write(pclByteWriter.unsignedByte(fontReader.getTypefaceMSB())); + header.write(pclByteWriter.unsignedByte(fontReader.getSerifStyle())); + header.write(pclByteWriter.unsignedByte(fontReader.getQuality())); + header.write(pclByteWriter.signedByte(fontReader.getPlacement())); + header.write(pclByteWriter.signedByte(fontReader.getUnderlinePosition())); + header.write(pclByteWriter.unsignedByte(fontReader.getUnderlineThickness())); + header.write(pclByteWriter.unsignedInt(fontReader.getTextHeight())); + header.write(pclByteWriter.unsignedInt(fontReader.getTextWidth())); + header.write(pclByteWriter.unsignedInt(fontReader.getFirstCode())); + header.write(pclByteWriter.unsignedInt(fontReader.getLastCode())); + header.write(pclByteWriter.unsignedByte(fontReader.getPitchExtended())); + header.write(pclByteWriter.unsignedByte(fontReader.getHeightExtended())); + header.write(pclByteWriter.unsignedInt(fontReader.getCapHeight())); + header.write(pclByteWriter.unsignedLongInt(fontReader.getFontNumber())); + header.write(pclByteWriter.padBytes(fontReader.getFontName().getBytes("US-ASCII"), 16, 32)); + // 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); + + baos.write(getFontHeaderCommand(header.size())); + baos.write(header.toByteArray()); + } + + private void writeSegmentedFontData(ByteArrayOutputStream header, int byte64Offset) throws IOException { + List fontSegments = fontReader.getFontSegments(); + for (PCLFontSegment segment : fontSegments) { + writeFontSegment(header, segment); + } + header.write(0); // Reserved + // Checksum must equal 0 when added to byte 64 offset (modulo 256) + long sum = 0; + byte[] headerBytes = header.toByteArray(); + for (int i = 64; i < headerBytes.length; i++) { + sum += headerBytes[i]; + } + int remainder = (int) (sum % 256); + header.write(256 - remainder); + } + + private byte[] getFontHeaderCommand(int headerSize) throws IOException { + return pclByteWriter.writeCommand(String.format(")s%dW", headerSize)); + } + + private void writeFontSegment(ByteArrayOutputStream header, PCLFontSegment segment) throws IOException { + header.write(pclByteWriter.unsignedInt(segment.getIdentifier().getValue())); + header.write(pclByteWriter.unsignedInt(segment.getData().length)); + header.write(segment.getData()); + } + + 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) { + for (PCLSoftFont sftFont : fonts) { + if (sftFont.getTypeface().equals(font) && sftFont.getCharCount() < 255) { + return sftFont; + } + } + return null; + } + + public int getSoftFontID(Typeface tf) throws IOException { + PCLSoftFont font = getSoftFont(tf); + for (int i = 0; i < fonts.size(); i++) { + if (fonts.get(i).equals(font)) { + return i + 1; + } + } + return -1; + } + + public byte[] writeFontIDCommand(int fontID) throws IOException { + return pclByteWriter.writeCommand(String.format("*c%dD", fontID)); + } +} diff --git a/src/java/org/apache/fop/render/pcl/fonts/PCLSymbolSet.java b/src/java/org/apache/fop/render/pcl/fonts/PCLSymbolSet.java new file mode 100644 index 000000000..a2c50df23 --- /dev/null +++ b/src/java/org/apache/fop/render/pcl/fonts/PCLSymbolSet.java @@ -0,0 +1,200 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pcl.fonts; + +/** + * Table C-1 from http://www.lprng.com/DISTRIB/RESOURCES/DOCS/pcl5comp.pdf + */ +public enum PCLSymbolSet { + // Unbound font containing > 256 characters + Unbound("1X", 56), + + // Other symbol sets to use in bound fonts + Bound_Generic("0Q", 17), + GW_3212("18C", 597), + ISO_60_Danish_Norwegian("0D", 4), + Devanagari("2D", 68), + ISO_4_United_Kingdom("1E", 37), + Windows_3_1_Latin2("9E", 293), + ISO_69_French("1F", 38), + ISO_21_German("1G", 39), + Greek_8("8G", 283), + Windows_3_1_Latin_Greek("9G", 295), + PC_851_Latin_Greek("10G", 327), + PC_8_Latin_Greek("12G", 391), + Hebrew_7("0H", 8), + ISO_8859_8_Latin_Hebrew("7H", 232), + Hebrew_8("8H", 264), + PC_862_Latin_Hebrew("15H", 488), + ISO_15_Italian("0I", 9), + Microsoft_Publishing("6J", 202), + DeskTop("7J", 234), + Document("8J", 266), + PC_1004("9J", 298), + PS_Text("10J", 330), + PS_ISO_Latin1("11J", 362), + MC_Text("12J", 394), + Ventura_International3("13J", 426), + Ventura_US3("14J", 458), + Swash_Characters("16J", 522), + Small_Caps_Old_Style_Figures("17J", 554), + Old_Style_Figures("18J", 586), + Fractions("19J", 618), + Lining_Figures("21J", 682), + Small_Caps_and_Lining_Figures("22J", 714), + Alternate_Caps("23J", 746), + Kana_8_JIS_210("8K", 267), + Korean_8("9K", 299), + + Line_Draw_7("0L", 12), + HP_Block_Characters("1L", 44), + Tax_Line_Draw("2L", 76), + Line_Draw_8("8L", 268), + Ventura_ITC_Zapf_Dingbats3("9L", 300), + PS_ITC_Zapf_Dingbats("10L", 332), + ITC_Zapf_Dingbats_Series_100("11L", 364), + ITC_Zapf_Dingbats_Series_200("12L", 396), + ITC_Zapf_Dingbats_Series_300("13L", 428), + Windows_Baltic("19L", 620), + Carta("20L", 652), + Ornaments("21L", 684), + Universal_News_Commercial_Pi("22L", 716), + Chess("23L", 748), + Astrology_1("24L", 780), + Pi_Set_1("31L", 1004), + Pi_Set_2("32L", 1036), + Pi_Set_3("33L", 1068), + Pi_Set_4("34L", 1100), + Pi_Set_5("35L", 1132), + Pi_Set_6("36L", 1164), + Wingdings("579L", 18540), + Math_7("0M", 13), + Tech_7("1M", 45), + PS_Math("5M", 173), + Ventura_Math3("6M", 205), + Math_8("8M", 269), + Universal_Greek_Math_Pi("10M", 333), + TeX_Math_Extension("11M", 365), + TeX_Math_Symbol("12M", 397), + TeX_Math_Italic("13M", 429), + Symbol("19M", 621), + ISO_8859_1_Latin_1("0N", 14), + ISO_8859_2_Latin_2("2N", 78), + + ISO_8859_3_Latin_3("3N", 110), + ISO_8859_4_Latin_4("4N", 142), + ISO_8859_9_Latin_5("5N", 174), + ISO_8859_10_Latin_6("6N", 206), + ISO_8859_5_Latin_Cyrillic("10N", 334), + ISO_8859_6_Latin_Arabic("11N", 366), + ISO_8859_7_Latin_Greek("12N", 398), + OCR_A("0O", 15), + OCR_B("1O", 47), + OCR_M("2O", 79), + MICR_E13B("10O", 335), + Typewriter_Paired_APL("0P", 16), + Bit_Paired_APL("1P", 48), + Expert("10P", 336), + Alternate("11P", 368), + Fraktur("12P", 400), + Cyrillic_ASCII_8859_5_1986("0R", 18), + Cyrillic("1R", 50), + PC_Cyrillic("3R", 114), + Windows_3_1_Latin_Cyrillic("9R", 306), + ISO_11_Swedish("0S", 19), + ISO_17_Spanish3("2S", 83), + HP_European_Spanish("7S", 243), + HP_Latin_Spanish("8S", 275), + HP_GL_Download("16S", 531), + HP_GL_Drafting("17S", 563), + HP_GL_Special_Symbols("18S", 595), + Sonata("20S", 659), + Thai_8("0T", 20), + TISI_620_2533_Thai("1T", 52), + Windows_3_1_Latin_5("5T", 180), + Turkish_8("8T", 276), + + PC_8_Turkish("9T", 308), + Teletex("10T", 340), + ISO_6_ASCII("0U", 21), + Legal("1U", 53), + HPL("5U", 181), + OEM_1("7U", 245), + Roman_8("8U", 277), + Windows_3_0_Latin_1("9U", 309), + PC_8_Code_Page_437("10U", 341), + PC_8_D_N_Danish_Norwegian("11U", 373), + PC_850_Multilingual("12U", 405), + Pi_Font("15U", 501), + PC_857("16U", 533), + PC_852_Latin_2("17U", 565), + Windows_3_1_Latin_1("19U", 629), + PC_860_Portugal("20U", 661), + PC_861_Iceland("21U", 693), + PC_863_Canada_French("23U", 757), + PC_865_Norway("25U", 821), + PC_775("26U", 853), + Arabic_8("8V", 278), + Windows_3_1_Latin_Arabic("9V", 310), + Code_Page_864_Latin_Arabic("10V", 342), + Barcode_3of9("0Y", 25), + Industrial_2_of_5_Barcode("1Y", 57), + Matrix_2_of_5_Barcode("2Y", 89), + Interleaved_2_of_5_Barcode("4Y", 153), + CODABAR_Barcode("5Y", 185), + MSI_Plessey_Barcode("6Y", 217), + Code_11_Barcode("7Y", 249), + UPC_EAN_Barcode("8Y", 281), + MICR_CMC_7("14Y", 473), + USPS_ZIP("5Y", 505), + + Math_7_2("0A", 1), + Line_Draw_7_2("0B", 2), + HP_Large_Characters("0C", 3), + ISO_61_Norwegian_Version_2("1D", 36), + Roman_Extension("0E", 5), + ISO_25_French("0F", 6), + HP_German("0G", 7), + ISO_14_JIS_ASCII("0K", 11), + ISO_13_Katakana("1K", 43), + ISO_57_Chinese("2K", 75), + HP_Spanish("1S", 51), + ISO_10_Swedish("3S", 115), + ISO_16_Portuguese("4S", 147), + ISO_84_Portuguese("5S", 179), + ISO_85_Spanish("6S", 211), + ISO_2_International_Reference("2U", 85), + Arabic("0V", 22); + + private String symbolSetID; + private int kind1; + + PCLSymbolSet(String symbolSetID, int kind1) { + this.kind1 = kind1; + } + + public String getSymbolSetID() { + return symbolSetID; + } + + public int getKind1() { + return kind1; + } +} 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 new file mode 100644 index 000000000..0d2eaf41e --- /dev/null +++ b/src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFCharacterWriter.java @@ -0,0 +1,152 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pcl.fonts.truetype; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.fop.fonts.truetype.GlyfTable; +import org.apache.fop.fonts.truetype.OFDirTabEntry; +import org.apache.fop.fonts.truetype.OFMtxEntry; +import org.apache.fop.fonts.truetype.OFTableName; +import org.apache.fop.fonts.truetype.TTFFile; +import org.apache.fop.render.pcl.fonts.PCLCharacterDefinition; +import org.apache.fop.render.pcl.fonts.PCLCharacterDefinition.PCLCharacterClass; +import org.apache.fop.render.pcl.fonts.PCLCharacterDefinition.PCLCharacterFormat; +import org.apache.fop.render.pcl.fonts.PCLCharacterWriter; +import org.apache.fop.render.pcl.fonts.PCLSoftFont; + +public class PCLTTFCharacterWriter extends PCLCharacterWriter { + + private List mtx; + private OFDirTabEntry tabEntry; + + public PCLTTFCharacterWriter(PCLSoftFont softFont) throws IOException { + super(softFont); + softFont.setMtxCharIndexes(scanMtxCharacters()); + } + + @Override + public byte[] writeCharacterDefinitions(String text) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + for (char ch : text.toCharArray()) { + int character = (int) ch; + if (!font.hasPreviouslyWritten(character)) { + PCLCharacterDefinition pclChar = getCharacterDefinition(ch); + writePCLCharacter(baos, pclChar); + List compositeGlyphs = pclChar.getCompositeGlyphs(); + for (PCLCharacterDefinition composite : compositeGlyphs) { + writePCLCharacter(baos, composite); + } + } + } + return baos.toByteArray(); + } + + private void writePCLCharacter(ByteArrayOutputStream baos, PCLCharacterDefinition pclChar) throws IOException { + baos.write(pclChar.getCharacterCommand()); + baos.write(pclChar.getCharacterDefinitionCommand()); + baos.write(pclChar.getData()); + } + + private Map scanMtxCharacters() throws IOException { + Map charMtxOffsets = new HashMap(); + List mtx = openFont.getMtx(); + OFTableName glyfTag = OFTableName.GLYF; + if (openFont.seekTab(fontReader, glyfTag, 0)) { + for (int i = 1; i < mtx.size(); i++) { + OFMtxEntry entry = mtx.get(i); + int charCode = 0; + if (entry.getUnicodeIndex().size() > 0) { + charCode = (Integer) entry.getUnicodeIndex().get(0); + } else { + charCode = entry.getIndex(); + } + charMtxOffsets.put(charCode, i); + } + } + return charMtxOffsets; + } + + private PCLCharacterDefinition getCharacterDefinition(int unicode) throws IOException { + if (mtx == null) { + mtx = openFont.getMtx(); + tabEntry = openFont.getDirectoryEntry(OFTableName.GLYF); + } + if (openFont.seekTab(fontReader, OFTableName.GLYF, 0)) { + int charIndex = font.getMtxCharIndex(unicode); + + // Fallback - only works for MultiByte fonts + if (charIndex == 0) { + charIndex = font.getCmapGlyphIndex(unicode); + } + + Map subsetGlyphs = new HashMap(); + subsetGlyphs.put(charIndex, 1); + + byte[] glyphData = getGlyphData(charIndex); + + font.writeCharacter(unicode); + + PCLCharacterDefinition newChar = new PCLCharacterDefinition(charIndex, font.getUnicodeCodePoint(unicode), + PCLCharacterFormat.TrueType, + PCLCharacterClass.TrueType, glyphData, pclByteWriter); + + // 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) { + byte[] compositeData = getGlyphData(compositeIndex); + newChar.addCompositeGlyph(new PCLCharacterDefinition(compositeIndex, 65535, + PCLCharacterFormat.TrueType, PCLCharacterClass.TrueType, compositeData, pclByteWriter)); + } + } + + return newChar; + } + return null; + } + + private byte[] getGlyphData(int charIndex) throws IOException { + OFMtxEntry entry = mtx.get(charIndex); + OFMtxEntry nextEntry; + int nextOffset = 0; + if (charIndex < mtx.size() - 1) { + nextEntry = mtx.get(charIndex + 1); + nextOffset = (int) nextEntry.getOffset(); + } else { + nextOffset = (int) ((TTFFile) openFont).getLastGlyfLocation(); + } + int glyphOffset = (int) entry.getOffset(); + int glyphLength = nextOffset - glyphOffset; + + byte[] glyphData = new byte[0]; + if (glyphLength > 0) { + glyphData = fontReader.getBytes((int) tabEntry.getOffset() + glyphOffset, glyphLength); + } + return glyphData; + } +} 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 new file mode 100644 index 000000000..c52d2d5ab --- /dev/null +++ b/src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFFontReader.java @@ -0,0 +1,619 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pcl.fonts.truetype; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.fop.fonts.CustomFont; +import org.apache.fop.fonts.Typeface; +import org.apache.fop.fonts.truetype.FontFileReader; +import org.apache.fop.fonts.truetype.OFDirTabEntry; +import org.apache.fop.fonts.truetype.OFFontLoader; +import org.apache.fop.fonts.truetype.OFMtxEntry; +import org.apache.fop.fonts.truetype.OFTableName; +import org.apache.fop.fonts.truetype.OpenFont; +import org.apache.fop.fonts.truetype.TTFFile; +import org.apache.fop.render.java2d.CustomFontMetricsMapper; +import org.apache.fop.render.pcl.fonts.PCLByteWriterUtil; +import org.apache.fop.render.pcl.fonts.PCLFontReader; +import org.apache.fop.render.pcl.fonts.PCLFontSegment; +import org.apache.fop.render.pcl.fonts.PCLFontSegment.SegmentID; +import org.apache.fop.render.pcl.fonts.PCLSymbolSet; + +public class PCLTTFFontReader extends PCLFontReader { + protected TTFFile ttfFont; + protected InputStream fontStream; + protected FontFileReader reader; + private PCLTTFPCLTFontTable pcltTable; + private PCLTTFOS2FontTable os2Table; + private PCLTTFPOSTFontTable postTable; + private PCLTTFTableFactory ttfTableFactory; + + private static final int HMTX_RESTRICT_SIZE = 50000; + + private static final Map FONT_WEIGHT = new HashMap() { + private static final long serialVersionUID = 1L; + { + put(100, -6); // 100 Thin + put(200, -4); // 200 Extra-Light + put(300, -3); // 300 Light + put(400, 0); // 400 Normal (Regular) + put(500, 0); // 500 Medium + put(600, 2); // 600 Semi-bold + put(700, 3); // 700 Bold + put(800, 4); // 800 Extra-bold + put(900, 5); // 900 Black (Heavy) + } + }; + + private static final Map FONT_SERIF = new HashMap() { + private static final long serialVersionUID = 1L; + { + /** The following are the best guess conversion between serif styles. Unfortunately + * there appears to be no standard and so each specification has it's own set of values. + * Please change if better fit found. **/ + put(0, 0); // Any = Normal Sans + put(1, 64); // No Fit = Sans Serif + put(2, 9); // Cove = Script Nonconnecting + put(3, 12); // Obtuse Cove = Script Broken Letter + put(4, 10); // Square Cove = Script Joining + put(5, 0); // Obtuse Square Cove = Sans Serif Square + put(6, 128); // Square = Serif + put(7, 2); // Thin = Serif Line + put(8, 7); // Bone = Rounded Bracket + put(9, 11); // Exeraggerated = Script Calligraphic + put(10, 3); // Triangle = Serif Triangle + put(11, 0); // Normal Sans = Sans Serif Square + put(12, 4); // Obtuse Sans = Serif Swath + put(13, 6); // Perp Sans = Serif Bracket + put(14, 8); // Flared = Flair Serif + put(15, 1); // Rounded = Sans Serif Round + } + }; + + private static final Map FONT_WIDTH = new HashMap() { + private static final long serialVersionUID = 1L; + { + /** The conversions between TTF and PCL are not 1 to 1 **/ + put(1, -5); // 1 = Ultra Compressed + put(2, -4); // 2 = Extra Compressed + put(3, -3); // 3 = Compresses + put(4, -2); // 4 = Condensed + put(5, 0); // 5 = Normal + put(6, 2); // 6 = Expanded + put(7, 3); // 5 = Extra Expanded + } + }; + + private int scaleFactor = -1; + private PCLSymbolSet symbolSet = PCLSymbolSet.Bound_Generic; + + public PCLTTFFontReader(Typeface font, PCLByteWriterUtil pclByteWriter) throws IOException { + super(font, pclByteWriter); + loadFont(); + } + + protected void loadFont() throws IOException { + if (typeface instanceof CustomFontMetricsMapper) { + CustomFontMetricsMapper fontMetrics = (CustomFontMetricsMapper) typeface; + CustomFont customFont = (CustomFont) fontMetrics.getRealFont(); + fontStream = customFont.getInputStream(); + reader = new FontFileReader(fontStream); + + ttfFont = new TTFFile(); + String header = OFFontLoader.readHeader(reader); + ttfFont.readFont(reader, header, customFont.getFullName()); + readFontTables(); + } else { + // TODO - Handle when typeface is not in the expected format for a PCL TrueType object + } + } + + protected void readFontTables() throws IOException { + PCLTTFTable fontTable; + fontTable = readFontTable(OFTableName.PCLT); + if (fontTable instanceof PCLTTFPCLTFontTable) { + pcltTable = (PCLTTFPCLTFontTable) fontTable; + } + fontTable = readFontTable(OFTableName.OS2); + if (fontTable instanceof PCLTTFOS2FontTable) { + os2Table = (PCLTTFOS2FontTable) fontTable; + } + fontTable = readFontTable(OFTableName.POST); + if (fontTable instanceof PCLTTFPOSTFontTable) { + postTable = (PCLTTFPOSTFontTable) fontTable; + } + } + + private PCLTTFTable readFontTable(OFTableName tableName) throws IOException { + if (ttfFont.seekTab(reader, tableName, 0)) { + return getTTFTableFactory().newInstance(tableName); + } + return null; + } + + private PCLTTFTableFactory getTTFTableFactory() { + if (ttfTableFactory == null) { + ttfTableFactory = PCLTTFTableFactory.getInstance(reader); + } + return ttfTableFactory; + } + + @Override + public int getDescriptorSize() { + return 72; // Descriptor size (leave at 72 for our purposes) + } + + @Override + public int getHeaderFormat() { + return 15; // TrueType Scalable Font + } + + @Override + public int getFontType() { + if (symbolSet == PCLSymbolSet.Unbound) { + return 11; // Font Type - Unbound TrueType Scalable font + } else { + return 2; // 0-255 (except 0, 7 and 27) + } + } + + @Override + public int getStyleMSB() { + if (pcltTable != null) { + return getMSB(pcltTable.getStyle()); + } + return 3; + } + + @Override + public int getBaselinePosition() { + return 0; // Baseline position must be set to 0 for TTF fonts + } + + @Override + public int getCellWidth() { + int[] bbox = ttfFont.getBBoxRaw(); + return bbox[2] - bbox[0]; + } + + @Override + public int getCellHeight() { + int[] bbox = ttfFont.getBBoxRaw(); + return bbox[3] - bbox[1]; + } + + @Override + public int getOrientation() { + return 0; // Scalable fonts (TrueType) must be 0 + } + + @Override + public int getSpacing() { + if (os2Table != null) { + return (os2Table.getPanose()[4] == 9) ? 0 : 1; + } else if (postTable != null) { + return postTable.getIsFixedPitch(); + } + return 1; + } + + @Override + public int getSymbolSet() { + if (pcltTable != null) { + return pcltTable.getSymbolSet(); + } else { + return symbolSet.getKind1(); + } + } + + @Override + public int getPitch() { + int pitch = ttfFont.getCharWidthRaw(0x20); + if (pitch < 0) { + // No advance width found for the space character + return 0; + } + return pitch; + } + + @Override + public int getHeight() { + return 0; // Fixed zero value for TrueType fonts + } + + @Override + public int getXHeight() { + if (pcltTable != null) { + return pcltTable.getXHeight(); + } else if (os2Table != null) { + return os2Table.getXHeight(); + } + return 0; + } + + @Override + public int getWidthType() { + if (pcltTable != null) { + return pcltTable.getWidthType(); + } else if (os2Table != null) { + return convertTTFWidthClass(os2Table.getWidthClass()); + } + return 0; + } + + private int convertTTFWidthClass(int widthClass) { + if (FONT_WIDTH.containsKey(widthClass)) { + return FONT_WIDTH.get(widthClass); + } else { + return 0; // No match - return normal + } + } + + @Override + public int getStyleLSB() { + if (pcltTable != null) { + return getLSB(pcltTable.getStyle()); + } + return 224; + } + + @Override + public int getStrokeWeight() { + if (pcltTable != null) { + return pcltTable.getStrokeWeight(); + } else if (os2Table != null) { + return convertTTFWeightClass(os2Table.getWeightClass()); + } + return 0; + } + + private int convertTTFWeightClass(int weightClass) { + if (FONT_WEIGHT.containsKey(weightClass)) { + return FONT_WEIGHT.get(weightClass); + } else { + return 0; // No match - return normal + } + } + + @Override + public int getTypefaceLSB() { + if (pcltTable != null) { + return getLSB(pcltTable.getTypeFamily()); + } + return 254; + } + + @Override + public int getTypefaceMSB() { + if (pcltTable != null) { + return getMSB(pcltTable.getTypeFamily()); + } + return 0; + } + + @Override + public int getSerifStyle() { + if (pcltTable != null) { + return pcltTable.getSerifStyle(); + } else { + return convertFromTTFSerifStyle(); + } + } + + private int convertFromTTFSerifStyle() { + if (os2Table != null) { + int serifStyle = os2Table.getPanose()[1]; + return FONT_SERIF.get(serifStyle); + } + return 0; + } + + @Override + public int getQuality() { + return 2; // Letter quality + } + + @Override + public int getPlacement() { + return 0; // Fixed value of 0 for TrueType (scalable fonts) + } + + @Override + public int getUnderlinePosition() { + return 0; // Scalable fonts has a fixed value of 0 - See Master Underline Position + } + + @Override + public int getUnderlineThickness() { + return 0; // Scalable fonts has a fixed value of 0 - See Master Underline Thickness + } + + @Override + public int getTextHeight() { + return 2048; + } + + @Override + public int getTextWidth() { + if (os2Table != null) { + return os2Table.getAvgCharWidth(); + } + return 0; + } + + @Override + public int getFirstCode() { + return 32; + } + + @Override + public int getLastCode() { + return 255; // Bound font with a maximum of 255 characters + } + + @Override + public int getPitchExtended() { + return 0; // Zero for Scalable fonts + } + + @Override + public int getHeightExtended() { + return 0; // Zero for Scalable fonts + } + + @Override + public int getCapHeight() { + if (pcltTable != null) { + return pcltTable.getStrokeWeight(); + } else if (os2Table != null) { + return os2Table.getCapHeight(); + } + return 0; + } + + @Override + public int getFontNumber() { + if (pcltTable != null) { + return (int) pcltTable.getFontNumber(); + } + return 0; + } + + @Override + public String getFontName() { + if (pcltTable != null) { + return pcltTable.getTypeface(); + } else { + return ttfFont.getFullName(); + } + } + + @Override + public int getScaleFactor() throws IOException { + if (scaleFactor == -1) { + OFTableName headTag = OFTableName.HEAD; + if (ttfFont.seekTab(reader, headTag, 0)) { + reader.readTTFLong(); // Version + reader.readTTFLong(); // Font revision + reader.readTTFLong(); // Check sum adjustment + reader.readTTFLong(); // Magic number + reader.readTTFShort(); // Flags + scaleFactor = reader.readTTFUShort(); // Units per em + return scaleFactor; + } + } else { + return scaleFactor; + } + return 0; + } + + @Override + public int getMasterUnderlinePosition() throws IOException { + return (int) Math.round(getScaleFactor() * 0.2); + } + + @Override + public int getMasterUnderlineThickness() throws IOException { + return (int) Math.round(getScaleFactor() * 0.05); + } + + @Override + public int getFontScalingTechnology() { + return 1; // TrueType scalable font + } + + @Override + public int getVariety() { + return 0; // TrueType fonts must be set to zero + } + + public List getFontSegments() 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.CP, ttfFont.getCopyrightNotice().getBytes("US-ASCII"))); + fontSegments.add(new PCLFontSegment(SegmentID.NULL, new byte[0])); + return fontSegments; + } + + /** + * See Font Header Format 11-35 (Character Complement Array) in the PCL 5 Specification. Defined as an array of 8 + * bytes specific to certain character sets. In this case specifying 0 for all values (default complement) means the + * font is compatible with any character sets. '110' on least significant bits signifies unicode. See specification + * for further customization. + */ + private byte[] getCharacterComplement() { + byte[] ccUnicode = new byte[8]; + ccUnicode[7] = 6; + return ccUnicode; + } + + private byte[] getGlobalTrueTypeData() throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Map tableOffsets = new HashMap(); + // Version + baos.write(pclByteWriter.unsignedInt(1)); // Major + baos.write(pclByteWriter.unsignedInt(0)); // Minor + int numTables = 5; // head, hhea, hmtx, maxp and gdir + // Optional Hint Tables + OFDirTabEntry headTable = ttfFont.getDirectoryEntry(OFTableName.CVT); + if (headTable != null) { + numTables++; + } + OFDirTabEntry fpgmTable = ttfFont.getDirectoryEntry(OFTableName.FPGM); + if (fpgmTable != null) { + numTables++; + } + OFDirTabEntry prepTable = ttfFont.getDirectoryEntry(OFTableName.PREP); + if (prepTable != null) { + numTables++; + } + baos.write(pclByteWriter.unsignedInt(numTables)); // numTables + int maxPowerNumTables = pclByteWriter.maxPower2(numTables); + int searchRange = maxPowerNumTables * 16; + baos.write(pclByteWriter.unsignedInt(searchRange)); + baos.write(pclByteWriter.unsignedInt(pclByteWriter.log(maxPowerNumTables, 2))); // Entry Selector + baos.write(pclByteWriter.unsignedInt(numTables * 16 - searchRange)); // Range shift + + // Add default data tables + writeTrueTypeTable(baos, OFTableName.HEAD, tableOffsets); + writeTrueTypeTable(baos, OFTableName.HHEA, tableOffsets); + writeTrueTypeTable(baos, OFTableName.HMTX, tableOffsets); + writeTrueTypeTable(baos, OFTableName.MAXP, tableOffsets); + + // Write the blank GDIR directory which is built in memory on the printer + writeGDIR(baos); + + // Add optional data tables (for hints) + writeTrueTypeTable(baos, OFTableName.CVT, tableOffsets); + writeTrueTypeTable(baos, OFTableName.FPGM, tableOffsets); + writeTrueTypeTable(baos, OFTableName.PREP, tableOffsets); + + baos = copyTables(tableOffsets, baos); + + return baos.toByteArray(); + } + + private void writeTrueTypeTable(ByteArrayOutputStream baos, OFTableName table, + Map 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()); + 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)); + } + } + + private void writeGDIR(ByteArrayOutputStream baos) throws UnsupportedEncodingException, IOException { + baos.write("gdir".getBytes("ISO-8859-1")); + baos.write(pclByteWriter.unsignedLongInt(0)); // Checksum + baos.write(pclByteWriter.unsignedLongInt(0)); // Offset + baos.write(pclByteWriter.unsignedLongInt(0)); // Length + } + + private ByteArrayOutputStream copyTables(Map tableOffsets, ByteArrayOutputStream baos) + 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; + } + // 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 ByteArrayOutputStream updateOffsets(ByteArrayOutputStream baos, Map offsets) + throws IOException { + byte[] softFont = baos.toByteArray(); + for (int offset : offsets.keySet()) { + pclByteWriter.updateDataAtLocation(softFont, offsets.get(offset), offset); + } + baos = new ByteArrayOutputStream(); + baos.write(softFont); + return baos; + } + + @Override + public Map getCharacterOffsets() throws IOException { + List mtx = ttfFont.getMtx(); + OFTableName glyfTag = OFTableName.GLYF; + Map charOffsets = new HashMap(); + OFDirTabEntry tabEntry = ttfFont.getDirectoryEntry(glyfTag); + if (ttfFont.seekTab(reader, glyfTag, 0)) { + for (int i = 1; i < mtx.size(); i++) { + OFMtxEntry entry = mtx.get(i); + OFMtxEntry nextEntry; + int nextOffset = 0; + int charCode = 0; + if (entry.getUnicodeIndex().size() > 0) { + charCode = (Integer) entry.getUnicodeIndex().get(0); + } else { + charCode = entry.getIndex(); + } + + if (i < mtx.size() - 1) { + nextEntry = mtx.get(i + 1); + nextOffset = (int) nextEntry.getOffset(); + } else { + nextOffset = (int) ttfFont.getLastGlyfLocation(); + } + int glyphOffset = (int) entry.getOffset(); + int glyphLength = nextOffset - glyphOffset; + + charOffsets.put(charCode, new int[]{(int) tabEntry.getOffset() + glyphOffset, glyphLength}); + } + } + return charOffsets; + } + + @Override + public OpenFont getFontFile() { + return ttfFont; + } + + @Override + public FontFileReader getFontFileReader() { + return reader; + } +} diff --git a/src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFOS2FontTable.java b/src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFOS2FontTable.java new file mode 100644 index 000000000..4c5f98d8b --- /dev/null +++ b/src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFOS2FontTable.java @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pcl.fonts.truetype; + +import java.io.IOException; + +import org.apache.fop.fonts.truetype.FontFileReader; + +public class PCLTTFOS2FontTable extends PCLTTFTable { + private int avgCharWidth; + private int xHeight; + private int widthClass; + private int weightClass; + private int capHeight; + private int[] panose = new int[10]; + + public PCLTTFOS2FontTable(FontFileReader in) throws IOException { + super(in); + int version = reader.readTTFUShort(); // Version + avgCharWidth = reader.readTTFShort(); + weightClass = reader.readTTFShort(); + widthClass = reader.readTTFShort(); + skipShort(reader, 12); + for (int i = 0; i < 10; i++) { + panose[i] = reader.readTTFByte(); + } + skipLong(reader, 4); + skipByte(reader, 4); + skipShort(reader, 8); + if (version >= 2) { + skipLong(reader, 2); + xHeight = reader.readTTFShort(); + capHeight = reader.readTTFShort(); + } + } + + public int getAvgCharWidth() { + return avgCharWidth; + } + + public int getXHeight() { + return xHeight; + } + + public int getWidthClass() { + return widthClass; + } + + public int getWeightClass() { + return weightClass; + } + + public int getCapHeight() { + return capHeight; + } + + public int[] getPanose() { + return panose; + } +} diff --git a/src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFPCLTFontTable.java b/src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFPCLTFontTable.java new file mode 100644 index 000000000..5be46a4ec --- /dev/null +++ b/src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFPCLTFontTable.java @@ -0,0 +1,115 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pcl.fonts.truetype; + +import java.io.IOException; + +import org.apache.fop.fonts.truetype.FontFileReader; + +public class PCLTTFPCLTFontTable extends PCLTTFTable { + private long version; + private long fontNumber; + private int pitch; + private int xHeight; + private int style; + private int typeFamily; + private int capHeight; + private int symbolSet; + private String typeface; + private String characterComplement; + private String filename; + private int strokeWeight; + private int widthType; + private int serifStyle; + + public PCLTTFPCLTFontTable(FontFileReader in) throws IOException { + super(in); + version = reader.readTTFULong(); + fontNumber = reader.readTTFULong(); + pitch = reader.readTTFUShort(); + xHeight = reader.readTTFUShort(); + style = reader.readTTFUShort(); + typeFamily = reader.readTTFUShort(); + capHeight = reader.readTTFUShort(); + symbolSet = reader.readTTFUShort(); + typeface = reader.readTTFString(16); + characterComplement = reader.readTTFString(8); + filename = reader.readTTFString(6); + strokeWeight = reader.readTTFUShort(); + widthType = reader.readTTFUShort(); + serifStyle = reader.readTTFUByte(); + } + + public long getVersion() { + return version; + } + + public long getFontNumber() { + return fontNumber; + } + + public int getPitch() { + return pitch; + } + + public int getXHeight() { + return xHeight; + } + + public int getStyle() { + return style; + } + + public int getTypeFamily() { + return typeFamily; + } + + public int getCapHeight() { + return capHeight; + } + + public int getSymbolSet() { + return symbolSet; + } + + public String getTypeface() { + return typeface; + } + + public String getCharacterComplement() { + return characterComplement; + } + + public String getFilename() { + return filename; + } + + public int getStrokeWeight() { + return strokeWeight; + } + + public int getWidthType() { + return widthType; + } + + public int getSerifStyle() { + return serifStyle; + } +} diff --git a/src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFPOSTFontTable.java b/src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFPOSTFontTable.java new file mode 100644 index 000000000..9995f7f55 --- /dev/null +++ b/src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFPOSTFontTable.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pcl.fonts.truetype; + +import java.io.IOException; + +import org.apache.fop.fonts.truetype.FontFileReader; + +public class PCLTTFPOSTFontTable extends PCLTTFTable { + private int underlinePosition; + private int underlineThickness; + private int isFixedPitch; + + public PCLTTFPOSTFontTable(FontFileReader in) throws IOException { + super(in); + reader.readTTFLong(); // Version + reader.readTTFLong(); // Italic Angle + underlinePosition = reader.readTTFShort(); + underlineThickness = reader.readTTFShort(); + isFixedPitch = (int) reader.readTTFULong(); + } + + public int getUnderlinePosition() { + return underlinePosition; + } + + public int getUnderlineThickness() { + return underlineThickness; + } + + public int getIsFixedPitch() { + return isFixedPitch; + } +} diff --git a/src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFTable.java b/src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFTable.java new file mode 100644 index 000000000..cfe866474 --- /dev/null +++ b/src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFTable.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pcl.fonts.truetype; + +import java.io.IOException; + +import org.apache.fop.fonts.truetype.FontFileReader; + +public class PCLTTFTable { + protected FontFileReader reader; + + public PCLTTFTable(FontFileReader reader) { + this.reader = reader; + } + + protected void skipShort(FontFileReader reader, int skips) + throws IOException { + reader.skip(skips * 2); + } + + protected void skipLong(FontFileReader reader, int skips) + throws IOException { + reader.skip(skips * 4); + } + + protected void skipByte(FontFileReader reader, int skips) + throws IOException { + reader.skip(skips); + } +} diff --git a/src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFTableFactory.java b/src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFTableFactory.java new file mode 100644 index 000000000..e72563e96 --- /dev/null +++ b/src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFTableFactory.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pcl.fonts.truetype; + +import java.io.IOException; + +import org.apache.fop.fonts.truetype.FontFileReader; +import org.apache.fop.fonts.truetype.OFTableName; + +public final class PCLTTFTableFactory { + private FontFileReader reader; + + private PCLTTFTableFactory(FontFileReader reader) { + this.reader = reader; + } + + public static PCLTTFTableFactory getInstance(FontFileReader reader) { + return new PCLTTFTableFactory(reader); + } + + public PCLTTFTable newInstance(OFTableName tableName) + throws IOException { + if (tableName == OFTableName.PCLT) { + return new PCLTTFPCLTFontTable(reader); + } else if (tableName == OFTableName.OS2) { + return new PCLTTFOS2FontTable(reader); + } else if (tableName == OFTableName.POST) { + return new PCLTTFPOSTFontTable(reader); + } + return null; + } +} diff --git a/test/java/org/apache/fop/render/pcl/fonts/MockPCLTTFFontReader.java b/test/java/org/apache/fop/render/pcl/fonts/MockPCLTTFFontReader.java new file mode 100644 index 000000000..a155dd43d --- /dev/null +++ b/test/java/org/apache/fop/render/pcl/fonts/MockPCLTTFFontReader.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ +package org.apache.fop.render.pcl.fonts; + +import java.io.IOException; + +import org.apache.fop.fonts.CustomFont; +import org.apache.fop.fonts.Typeface; +import org.apache.fop.fonts.truetype.FontFileReader; +import org.apache.fop.fonts.truetype.TTFFile; +import org.apache.fop.render.java2d.CustomFontMetricsMapper; +import org.apache.fop.render.pcl.fonts.truetype.PCLTTFFontReader; + +public class MockPCLTTFFontReader extends PCLTTFFontReader { + + public MockPCLTTFFontReader(Typeface font, PCLByteWriterUtil pclByteWriter) throws IOException { + super(font, pclByteWriter); + } + + @Override + protected void loadFont() throws IOException { + if (typeface instanceof CustomFontMetricsMapper) { + CustomFontMetricsMapper fontMetrics = (CustomFontMetricsMapper) typeface; + CustomFont customFont = (CustomFont) fontMetrics.getRealFont(); + fontStream = customFont.getInputStream(); + reader = new FontFileReader(fontStream); + + ttfFont = new TTFFile(); + ttfFont.readFont(reader, customFont.getFullName()); + readFontTables(); + } else { + // TODO - Handle when typeface is not in the expected format for a PCL TrueType object + } + } +} diff --git a/test/java/org/apache/fop/render/pcl/fonts/PCLByteWriterUtilTestCase.java b/test/java/org/apache/fop/render/pcl/fonts/PCLByteWriterUtilTestCase.java new file mode 100644 index 000000000..a21f204bf --- /dev/null +++ b/test/java/org/apache/fop/render/pcl/fonts/PCLByteWriterUtilTestCase.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ +package org.apache.fop.render.pcl.fonts; + +import java.io.IOException; + +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertArrayEquals; + +public class PCLByteWriterUtilTestCase { + private PCLByteWriterUtil byteWriter; + + @Before + public void setUp() { + byteWriter = new PCLByteWriterUtil(); + } + + @Test + public void testWriteMethods() throws IOException { + byte[] output = byteWriter.writeCommand("(s4X"); + // 27 = PCL escape character with rest in ASCII format + byte[] command = {27, 40, 115, 52, 88}; + assertArrayEquals(command, output); + + byte[] resultB = byteWriter.unsignedLongInt(102494); + byte[] compareB = {0, 1, -112, 94}; + assertArrayEquals(compareB, resultB); + + byte[] resultC = byteWriter.unsignedInt(1024); + byte[] compareC = {4, 0}; + assertArrayEquals(compareC, resultC); + } + + @Test + public void testUtilMethods() throws IOException { + byte[] anArray = {1, 2, 3, 4, 5, 9, 10}; + byte[] insertArray = {6, 7, 8}; + byte[] result = byteWriter.insertIntoArray(5, anArray, insertArray); + byte[] compareA = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + assertArrayEquals(compareA, result); + + byte[] reverse = {10, 9, 8, 7, 6}; + byteWriter.updateDataAtLocation(compareA, reverse, 5); + byte[] compareB = {1, 2, 3, 4, 5, 10, 9, 8, 7, 6}; + assertArrayEquals(compareB, compareA); + + byte[] anArrayC = {1, 2, 3, 4, 5}; + byte[] resultC = byteWriter.padBytes(anArrayC, 10); + byte[] compareC = {1, 2, 3, 4, 5, 0, 0, 0, 0, 0}; + assertArrayEquals(compareC, resultC); + + byte[] resultD = byteWriter.padBytes(anArrayC, 10, 1); + byte[] compareD = {1, 2, 3, 4, 5, 1, 1, 1, 1, 1}; + assertArrayEquals(compareD, resultD); + } +} diff --git a/test/java/org/apache/fop/render/pcl/fonts/PCLFontReaderFactoryTestCase.java b/test/java/org/apache/fop/render/pcl/fonts/PCLFontReaderFactoryTestCase.java new file mode 100644 index 000000000..2860afbdf --- /dev/null +++ b/test/java/org/apache/fop/render/pcl/fonts/PCLFontReaderFactoryTestCase.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ +package org.apache.fop.render.pcl.fonts; + +import java.io.File; +import java.io.FileInputStream; +import java.net.URI; + +import org.junit.Test; + +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.apache.fop.fonts.CustomFont; +import org.apache.fop.fonts.FontType; +import org.apache.fop.render.java2d.CustomFontMetricsMapper; +import org.apache.fop.render.pcl.fonts.truetype.PCLTTFFontReader; + +public class PCLFontReaderFactoryTestCase { + private static final String TEST_FONT_TTF = "./test/resources/fonts/ttf/DejaVuLGCSerif.ttf"; + + @Test + public void verifyTypeIdentification() throws Exception { + CustomFont sbFont = mock(CustomFont.class); + when(sbFont.getInputStream()).thenReturn(new FileInputStream(new File(TEST_FONT_TTF))); + when(sbFont.getEmbedFileURI()).thenReturn(new URI(TEST_FONT_TTF)); + CustomFontMetricsMapper customFont = new CustomFontMetricsMapper(sbFont); + when(customFont.getFontType()).thenReturn(FontType.TRUETYPE); + // Have to mock the input stream twice otherwise get a Stream is closed exception + when(((CustomFont) customFont.getRealFont()).getInputStream()).thenReturn( + new FileInputStream(new File(TEST_FONT_TTF))); + PCLFontReaderFactory fontReaderFactory = PCLFontReaderFactory.getInstance(null); + assertTrue(fontReaderFactory.createInstance(customFont) instanceof PCLTTFFontReader); + } +} diff --git a/test/java/org/apache/fop/render/pcl/fonts/PCLTTFFontReaderTestCase.java b/test/java/org/apache/fop/render/pcl/fonts/PCLTTFFontReaderTestCase.java new file mode 100644 index 000000000..88c613e7d --- /dev/null +++ b/test/java/org/apache/fop/render/pcl/fonts/PCLTTFFontReaderTestCase.java @@ -0,0 +1,120 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ +package org.apache.fop.render.pcl.fonts; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.apache.fop.fonts.CustomFont; +import org.apache.fop.render.java2d.CustomFontMetricsMapper; +import org.apache.fop.render.pcl.fonts.PCLFontSegment.SegmentID; +import org.apache.fop.render.pcl.fonts.truetype.PCLTTFFontReader; + +public class PCLTTFFontReaderTestCase { + + private CustomFontMetricsMapper customFont = mock(CustomFontMetricsMapper.class); + private PCLByteWriterUtil byteWriter; + private static final String TEST_FONT_A = "./test/resources/fonts/ttf/DejaVuLGCSerif.ttf"; + + @Before + public void setUp() { + byteWriter = new PCLByteWriterUtil(); + } + + @Test + public void verifyFontAData() throws Exception { + CustomFont sbFont = mock(CustomFont.class); + when(sbFont.getInputStream()).thenReturn(new FileInputStream(new File(TEST_FONT_A))); + when(customFont.getRealFont()).thenReturn(sbFont); + PCLTTFFontReader reader = new MockPCLTTFFontReader(customFont, byteWriter); + verifyFontData(reader); + validateOffsets(reader); + validateFontSegments(reader); + } + + /** + * Compares the input font data against a sample of the data read and calculated by the reader. The assertions are + * made against data taken from the TrueType Font Analyzer tool. + * @param reader The reader + */ + private void verifyFontData(PCLTTFFontReader reader) { + assertEquals(reader.getCellWidth(), 5015); // Bounding box X2 - X1 + assertEquals(reader.getCellHeight(), 3254); // Bounding box Y2 - Y1 + assertEquals(reader.getCapHeight(), 0); // OS2Table.capHeight + assertEquals(reader.getFontName(), "DejaVu LGC Serif"); // Full name read by TTFFont object + assertEquals(reader.getFirstCode(), 32); // Always 32 for bound font + assertEquals(reader.getLastCode(), 255); // Always 255 for bound font + + // Values that require conversion tables (See PCLTTFFontReader.java) + assertEquals(reader.getStrokeWeight(), 0); // Weight Class 400 (regular) should be equivalent 0 + assertEquals(reader.getSerifStyle(), 128); // Serif Style 0 should equal 0 + assertEquals(reader.getWidthType(), 0); // Width Class 5 (regular) should be equivalent 0 + } + + private void validateOffsets(PCLTTFFontReader reader) throws IOException { + // Offsets are stored with their character ID with the array [offset, length] + Map offsets = reader.getCharacterOffsets(); + + // Test data + int[] charC = {27644, 144}; // Char index = 99 + int[] charDollar = {16044, 264}; // Char index = 36 + int[] charOne = {17808, 176}; // Char index = 49 + int[] charUpperD = {21236, 148}; // Char index = 68 + int[] charUpperJ = {22140, 176}; // Char index = 74 + + assertArrayEquals(offsets.get(99), charC); + assertArrayEquals(offsets.get(36), charDollar); + assertArrayEquals(offsets.get(49), charOne); + assertArrayEquals(offsets.get(68), charUpperD); + assertArrayEquals(offsets.get(74), charUpperJ); + } + + /** + * Verifies the font segment data copied originally from the TrueType font. Data was verified using TrueType Font + * Analyzer and PCLParaphernalia tool. + * @param reader The reader + * @throws IOException + */ + private void validateFontSegments(PCLTTFFontReader reader) throws IOException { + List segments = reader.getFontSegments(); + assertEquals(segments.size(), 5); + for (PCLFontSegment segment : segments) { + if (segment.getIdentifier() == SegmentID.PA) { + // Panose + assertEquals(segment.getData().length, 10); + byte[] panose = {2, 6, 6, 3, 5, 6, 5, 2, 2, 4}; + assertArrayEquals(segment.getData(), panose); + } else if (segment.getIdentifier() == SegmentID.NULL) { + // Terminating segment + assertEquals(segment.getData().length, 0); + } + } + } +} 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 new file mode 100644 index 000000000..10263d067 --- /dev/null +++ b/test/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFCharacterWriterTestCase.java @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ +package org.apache.fop.render.pcl.fonts.truetype; + +import java.io.File; +import java.io.FileInputStream; + +import org.junit.Test; + +import static org.junit.Assert.assertArrayEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.apache.fop.fonts.CustomFont; +import org.apache.fop.fonts.truetype.FontFileReader; +import org.apache.fop.fonts.truetype.OFFontLoader; +import org.apache.fop.fonts.truetype.TTFFile; +import org.apache.fop.render.java2d.CustomFontMetricsMapper; +import org.apache.fop.render.pcl.fonts.PCLByteWriterUtil; +import org.apache.fop.render.pcl.fonts.PCLSoftFont; + +public class PCLTTFCharacterWriterTestCase { + + private PCLTTFCharacterWriter characterWriter; + private PCLSoftFont softFont; + private CustomFontMetricsMapper customFont = mock(CustomFontMetricsMapper.class); + private static final String TEST_FONT_A = "./test/resources/fonts/ttf/DejaVuLGCSerif.ttf"; + + @Test + public void verifyCharacterDefinition() throws Exception { + CustomFont sbFont = mock(CustomFont.class); + when(customFont.getRealFont()).thenReturn(sbFont); + softFont = new PCLSoftFont(1, customFont); + TTFFile openFont = new TTFFile(); + FontFileReader reader = new FontFileReader(new FileInputStream(new File(TEST_FONT_A))); + String header = OFFontLoader.readHeader(reader); + openFont.readFont(reader, header); + softFont.setOpenFont(openFont); + softFont.setReader(reader); + + characterWriter = new PCLTTFCharacterWriter(softFont); + byte[] charDefinition = characterWriter.writeCharacterDefinitions("f"); + PCLByteWriterUtil pclByteWriter = new PCLByteWriterUtil(); + // Character command + byte[] command = pclByteWriter.writeCommand(String.format("*c%dE", 32)); + assertArrayEquals(getBytes(charDefinition, 0, 6), command); + // Character definition command + byte[] charDefCommand = pclByteWriter.writeCommand(String.format("(s%dW", 210)); + assertArrayEquals(getBytes(charDefinition, 6, 7), charDefCommand); + } + + private byte[] getBytes(byte[] byteArray, int offset, int length) { + byte[] result = new byte[length]; + int count = 0; + for (int i = offset; i < offset + length; i++) { + result[count++] = byteArray[i]; + } + return result; + } +} -- 2.39.5