diff --git a/libsrc/gnujpdf/nbproject/project.properties b/libsrc/gnujpdf/nbproject/project.properties index a010b7c31..13fae8141 100644 --- a/libsrc/gnujpdf/nbproject/project.properties +++ b/libsrc/gnujpdf/nbproject/project.properties @@ -39,12 +39,13 @@ javac.classpath= # Space-separated list of extra javac options javac.compilerargs= javac.deprecation=false +javac.external.vm=false javac.modulepath= javac.processormodulepath= javac.processorpath=\ ${javac.classpath} -javac.source=1.7 -javac.target=1.7 +javac.source=1.8 +javac.target=1.8 javac.test.classpath=\ ${javac.classpath}:\ ${build.classes.dir} @@ -55,6 +56,7 @@ javac.test.processorpath=\ javadoc.additionalparam= javadoc.author=false javadoc.encoding=${source.encoding} +javadoc.html5=false javadoc.noindex=false javadoc.nonavbar=false javadoc.notree=false @@ -63,6 +65,8 @@ javadoc.splitindex=true javadoc.use=true javadoc.version=false javadoc.windowtitle= +jlink.launcher=false +jlink.launcher.name=gnujpdf main.class=gnu.jpdf.Test manifest.file=manifest.mf meta.inf.dir=${src.dir}/META-INF diff --git a/libsrc/gnujpdf/src/gnu/jpdf/MyRect.java b/libsrc/gnujpdf/src/gnu/jpdf/MyRect.java new file mode 100644 index 000000000..ae5e3f697 --- /dev/null +++ b/libsrc/gnujpdf/src/gnu/jpdf/MyRect.java @@ -0,0 +1,20 @@ +package gnu.jpdf; + +/** + * + * @author JPEXS + */ +public class MyRect { + public int xMin; + public int yMin; + public int xMax; + public int yMax; + + public MyRect(int xMin, int yMin, int xMax, int yMax) { + this.xMin = xMin; + this.yMin = yMin; + this.xMax = xMax; + this.yMax = yMax; + } + +} diff --git a/libsrc/gnujpdf/src/gnu/jpdf/PDFCatalog.java b/libsrc/gnujpdf/src/gnu/jpdf/PDFCatalog.java index adf9b0f1a..5bcf41f06 100644 --- a/libsrc/gnujpdf/src/gnu/jpdf/PDFCatalog.java +++ b/libsrc/gnujpdf/src/gnu/jpdf/PDFCatalog.java @@ -78,7 +78,8 @@ public class PDFCatalog extends PDFObject writeStart(os); // now the objects body - + os.write("/Version /1.7\n".getBytes()); + // the /Pages object os.write("/Pages ".getBytes()); os.write(pdfPageList.toString().getBytes()); diff --git a/libsrc/gnujpdf/src/gnu/jpdf/PDFDocument.java b/libsrc/gnujpdf/src/gnu/jpdf/PDFDocument.java index 8ba964922..472276417 100644 --- a/libsrc/gnujpdf/src/gnu/jpdf/PDFDocument.java +++ b/libsrc/gnujpdf/src/gnu/jpdf/PDFDocument.java @@ -29,6 +29,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.Serializable; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Vector; @@ -293,54 +294,104 @@ public class PDFDocument implements Serializable // the font wasn't found, so create it fontid++; - TtfParser par = new TtfParser(); + final TtfParser par = new TtfParser(); try { - par.loadFromTTF(file, 1024); + par.loadFromTTF(file); } catch (FontFormatException ex) { Logger.getLogger(PDFDocument.class.getName()).log(Level.SEVERE, null, ex); } - PDFFontDescriptor fontDescriptor = new PDFFontDescriptor(font, par.getAscent(), par.getDescent(), par.getCapHeight(), 80/*?*/, "" + fontFile2.getSerialID() + " 0 R"); - add(fontDescriptor); - final List widthsList = new ArrayList<>(); - List glyphAdvances = par.getAdvanceWidths(); - Map cmap = par.getCmap(); - for (int i = 32; i <= 255; i++) { - if (cmap.containsKey(i)) { - widthsList.add(glyphAdvances.get(cmap.get(i))); - } else { - widthsList.add(0); - } - } + PDFStream cidToGidMap = new PDFStream(); + cidToGidMap.getOutputStream().write(par.getCidtogidmap()); + cidToGidMap.setDeflate(true); + add(cidToGidMap); - PDFObject widthStream = new PDFObject(null) { + //ToUnicode map for Identity-H stream + String uniIdentityH = "/CIDInit /ProcSet findresource begin\n12 dict begin\nbegincmap\n/CIDSystemInfo << /Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def\n/CMapName /Adobe-Identity-UCS def\n/CMapType 2 def\n/WMode 0 def\n1 begincodespacerange\n<0000> \nendcodespacerange\n100 beginbfrange\n<0000> <00ff> <0000>\n<0100> <01ff> <0100>\n<0200> <02ff> <0200>\n<0300> <03ff> <0300>\n<0400> <04ff> <0400>\n<0500> <05ff> <0500>\n<0600> <06ff> <0600>\n<0700> <07ff> <0700>\n<0800> <08ff> <0800>\n<0900> <09ff> <0900>\n<0a00> <0aff> <0a00>\n<0b00> <0bff> <0b00>\n<0c00> <0cff> <0c00>\n<0d00> <0dff> <0d00>\n<0e00> <0eff> <0e00>\n<0f00> <0fff> <0f00>\n<1000> <10ff> <1000>\n<1100> <11ff> <1100>\n<1200> <12ff> <1200>\n<1300> <13ff> <1300>\n<1400> <14ff> <1400>\n<1500> <15ff> <1500>\n<1600> <16ff> <1600>\n<1700> <17ff> <1700>\n<1800> <18ff> <1800>\n<1900> <19ff> <1900>\n<1a00> <1aff> <1a00>\n<1b00> <1bff> <1b00>\n<1c00> <1cff> <1c00>\n<1d00> <1dff> <1d00>\n<1e00> <1eff> <1e00>\n<1f00> <1fff> <1f00>\n<2000> <20ff> <2000>\n<2100> <21ff> <2100>\n<2200> <22ff> <2200>\n<2300> <23ff> <2300>\n<2400> <24ff> <2400>\n<2500> <25ff> <2500>\n<2600> <26ff> <2600>\n<2700> <27ff> <2700>\n<2800> <28ff> <2800>\n<2900> <29ff> <2900>\n<2a00> <2aff> <2a00>\n<2b00> <2bff> <2b00>\n<2c00> <2cff> <2c00>\n<2d00> <2dff> <2d00>\n<2e00> <2eff> <2e00>\n<2f00> <2fff> <2f00>\n<3000> <30ff> <3000>\n<3100> <31ff> <3100>\n<3200> <32ff> <3200>\n<3300> <33ff> <3300>\n<3400> <34ff> <3400>\n<3500> <35ff> <3500>\n<3600> <36ff> <3600>\n<3700> <37ff> <3700>\n<3800> <38ff> <3800>\n<3900> <39ff> <3900>\n<3a00> <3aff> <3a00>\n<3b00> <3bff> <3b00>\n<3c00> <3cff> <3c00>\n<3d00> <3dff> <3d00>\n<3e00> <3eff> <3e00>\n<3f00> <3fff> <3f00>\n<4000> <40ff> <4000>\n<4100> <41ff> <4100>\n<4200> <42ff> <4200>\n<4300> <43ff> <4300>\n<4400> <44ff> <4400>\n<4500> <45ff> <4500>\n<4600> <46ff> <4600>\n<4700> <47ff> <4700>\n<4800> <48ff> <4800>\n<4900> <49ff> <4900>\n<4a00> <4aff> <4a00>\n<4b00> <4bff> <4b00>\n<4c00> <4cff> <4c00>\n<4d00> <4dff> <4d00>\n<4e00> <4eff> <4e00>\n<4f00> <4fff> <4f00>\n<5000> <50ff> <5000>\n<5100> <51ff> <5100>\n<5200> <52ff> <5200>\n<5300> <53ff> <5300>\n<5400> <54ff> <5400>\n<5500> <55ff> <5500>\n<5600> <56ff> <5600>\n<5700> <57ff> <5700>\n<5800> <58ff> <5800>\n<5900> <59ff> <5900>\n<5a00> <5aff> <5a00>\n<5b00> <5bff> <5b00>\n<5c00> <5cff> <5c00>\n<5d00> <5dff> <5d00>\n<5e00> <5eff> <5e00>\n<5f00> <5fff> <5f00>\n<6000> <60ff> <6000>\n<6100> <61ff> <6100>\n<6200> <62ff> <6200>\n<6300> <63ff> <6300>\nendbfrange\n100 beginbfrange\n<6400> <64ff> <6400>\n<6500> <65ff> <6500>\n<6600> <66ff> <6600>\n<6700> <67ff> <6700>\n<6800> <68ff> <6800>\n<6900> <69ff> <6900>\n<6a00> <6aff> <6a00>\n<6b00> <6bff> <6b00>\n<6c00> <6cff> <6c00>\n<6d00> <6dff> <6d00>\n<6e00> <6eff> <6e00>\n<6f00> <6fff> <6f00>\n<7000> <70ff> <7000>\n<7100> <71ff> <7100>\n<7200> <72ff> <7200>\n<7300> <73ff> <7300>\n<7400> <74ff> <7400>\n<7500> <75ff> <7500>\n<7600> <76ff> <7600>\n<7700> <77ff> <7700>\n<7800> <78ff> <7800>\n<7900> <79ff> <7900>\n<7a00> <7aff> <7a00>\n<7b00> <7bff> <7b00>\n<7c00> <7cff> <7c00>\n<7d00> <7dff> <7d00>\n<7e00> <7eff> <7e00>\n<7f00> <7fff> <7f00>\n<8000> <80ff> <8000>\n<8100> <81ff> <8100>\n<8200> <82ff> <8200>\n<8300> <83ff> <8300>\n<8400> <84ff> <8400>\n<8500> <85ff> <8500>\n<8600> <86ff> <8600>\n<8700> <87ff> <8700>\n<8800> <88ff> <8800>\n<8900> <89ff> <8900>\n<8a00> <8aff> <8a00>\n<8b00> <8bff> <8b00>\n<8c00> <8cff> <8c00>\n<8d00> <8dff> <8d00>\n<8e00> <8eff> <8e00>\n<8f00> <8fff> <8f00>\n<9000> <90ff> <9000>\n<9100> <91ff> <9100>\n<9200> <92ff> <9200>\n<9300> <93ff> <9300>\n<9400> <94ff> <9400>\n<9500> <95ff> <9500>\n<9600> <96ff> <9600>\n<9700> <97ff> <9700>\n<9800> <98ff> <9800>\n<9900> <99ff> <9900>\n<9a00> <9aff> <9a00>\n<9b00> <9bff> <9b00>\n<9c00> <9cff> <9c00>\n<9d00> <9dff> <9d00>\n<9e00> <9eff> <9e00>\n<9f00> <9fff> <9f00>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \nendbfrange\n56 beginbfrange\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \nendbfrange\nendcmap\nCMapName currentdict /CMap defineresource pop\nend\nend"; + + PDFStream toUnicode = new PDFStream(); + toUnicode.getOutputStream().write(uniIdentityH.getBytes()); + toUnicode.setDeflate(true); + + add(toUnicode); + + PDFObject fontDescriptor = new PDFObject("/FontDescriptor") { @Override public void write(OutputStream os) throws IOException { - os.write(Integer.toString(objser).getBytes()); - os.write(" 0 obj\n".getBytes()); - os.write("[ ".getBytes()); - for (int i = 0; i < widthsList.size(); i++) { - if (i > 0) { - os.write(" ".getBytes()); - } - os.write(("" + widthsList.get(i)).getBytes()); - } - os.write(" ]\n".getBytes()); - os.write("endobj\n".getBytes()); + writeStart(os); + os.write(("/FontName " + font + "\n").getBytes()); + os.write(("/Flags " + par.getFlags() + "\n").getBytes()); + os.write(("/FontBBox [" + par.getBbox().xMin + " " + par.getBbox().yMin + " " + par.getBbox().xMax + " " + par.getBbox().yMax + "]\n").getBytes()); + os.write(("/ItalicAngle 0" + par.getItalicAngle() + "\n").getBytes()); + os.write(("/Ascent " + par.getAscent() + "\n").getBytes()); + os.write(("/Descent " + par.getDescent() + "\n").getBytes()); + os.write(("/Leading " + par.getLeading() + "\n").getBytes()); + os.write(("/CapHeight " + par.getCapHeight() + "\n").getBytes()); + os.write(("/XHeight " + par.getxHeight() + "\n").getBytes()); + os.write(("/StemV " + par.getStemV() + "\n").getBytes()); + os.write(("/StemH " + par.getStemH() + "\n").getBytes()); + os.write(("/AvgWidth " + par.getAvgWidth() + "\n").getBytes()); + os.write(("/MaxWidth " + par.getMaxWidth() + "\n").getBytes()); + os.write(("/MissingWidth " + par.getMissingWidth() + "\n").getBytes()); + os.write(("/FontFile2 " + fontFile2.getSerialID() + " 0 R\n").getBytes()); + writeEnd(os); } + }; + add(fontDescriptor); + PDFObject descendantFont = new PDFObject("/Font") { + @Override + public void write(OutputStream os) throws IOException { + writeStart(os); + os.write(" /Subtype /CIDFontType2".getBytes()); + os.write((" /BaseFont " + font).getBytes()); + String cidinfo = "/Registry " + PDFStringHelper.makePDFString("Adobe"); + cidinfo += " /Ordering " + PDFStringHelper.makePDFString("Identity"); + cidinfo += " /Supplement 0"; + os.write((" /CIDSystemInfo << " + cidinfo + " >>").getBytes()); + os.write((" /FontDescriptor " + fontDescriptor.getSerialID() + " 0 R").getBytes()); + os.write((" /DW " + par.getDw() + "\n").getBytes()); // default width + os.write((getFontWidths(par.getCw()) + "\n").getBytes()); + //if (isset($font['ctg']) AND (!TCPDF_STATIC::empty_string($font['ctg']))) { + os.write(("/CIDToGIDMap " + cidToGidMap.getSerialID() + " 0 R").getBytes()); + //}*/ + writeEnd(os); + } }; - add(widthStream); + add(descendantFont); - - PDFFont ft = new PDFEmbeddedFont("/F" + fontid, font, style, "" + fontDescriptor.getSerialID() + " 0 R", widthStream.getSerialID() + " 0 R", 32, 255); + PDFFont ft = new PDFEmbeddedFont("/F" + fontid, font, style, descendantFont.getSerialID() + " 0 R", toUnicode.getSerialID() + " 0 R"); add(ft); fonts.addElement(ft); return ft; } - + + public static String getFontWidths(Map cw) { + StringBuilder widths = new StringBuilder(); + int prevC = -5; + boolean first = true; + for (int c : cw.keySet()) { + int w = cw.get(c); + + if (first) { + widths.append("" + c + " [" + w); + first = false; + } + else if (c == prevC + 1) { + widths.append(" " + w); + } else { + widths.append("]"); + widths.append(" " + c + " [" + w); + } + prevC = c; + } + if (!first) { + widths.append("]"); + } + return "/W [" + widths.toString() + " ]"; + } /** * Sets a unique name to a PDFImage * @param img PDFImage to set the name of diff --git a/libsrc/gnujpdf/src/gnu/jpdf/PDFEmbeddedFont.java b/libsrc/gnujpdf/src/gnu/jpdf/PDFEmbeddedFont.java index 86a09dfc3..c2ba06b93 100644 --- a/libsrc/gnujpdf/src/gnu/jpdf/PDFEmbeddedFont.java +++ b/libsrc/gnujpdf/src/gnu/jpdf/PDFEmbeddedFont.java @@ -13,23 +13,19 @@ import java.util.List; */ public class PDFEmbeddedFont extends PDFFont { - private final String descriptor; private String name; private String font; private String type; - private final String widths; - private final int firstChar; - private final int lastChar; + private final String descendantFont; + private final String toUnicode; - public PDFEmbeddedFont(String name, String font, int style, String descriptor, String widths, int firstChar, int lastChar) { + public PDFEmbeddedFont(String name, String font, int style, String descendantFont, String toUnicode) { super(name, "/TrueType", font, style); - this.descriptor = descriptor; this.name = name; this.font = font; this.type = "/TrueType"; - this.widths = widths; - this.firstChar = firstChar; - this.lastChar = lastChar; + this.descendantFont = descendantFont; + this.toUnicode = toUnicode; } @Override @@ -38,19 +34,13 @@ public class PDFEmbeddedFont extends PDFFont { writeStart(os); // now the objects body - os.write("/Subtype ".getBytes()); - os.write(type.getBytes()); - os.write("\n/Name ".getBytes()); - os.write(name.getBytes()); - os.write("\n/BaseFont ".getBytes()); - os.write(font.getBytes()); - os.write("\n".getBytes()); - - //os.write("/Encoding /WinAnsiEncoding\n".getBytes()); - - os.write(("/FirstChar " + firstChar + "\n").getBytes()); - os.write(("/LastChar " + lastChar + "\n").getBytes()); - os.write(("/Widths " + widths + "\n").getBytes()); + os.write("/Subtype /Type0\n".getBytes()); + os.write(("/BaseFont " + font + "\n").getBytes()); + os.write(("/Name " + name + "\n").getBytes()); + os.write("/Encoding /Identity-H\n".getBytes()); + //System.err.println("descendantFont=" + descendantFont); + os.write(("/ToUnicode " + toUnicode + "\n").getBytes()); + os.write(("/DescendantFonts [" + descendantFont + "]\n").getBytes()); /*int cnt = 300; os.write(("/FirstChar 0\n").getBytes()); @@ -64,9 +54,9 @@ public class PDFEmbeddedFont extends PDFFont { */ //os.write("/Widths [500 583 587 796]".getBytes()); - os.write("/FontDescriptor ".getBytes()); + /*os.write("/FontDescriptor ".getBytes()); os.write(descriptor.getBytes()); - os.write("\n".getBytes()); + os.write("\n".getBytes());*/ /*os.write("/ToUnicode ".getBytes()); os.write(toUnicode.getBytes()); diff --git a/libsrc/gnujpdf/src/gnu/jpdf/PDFGraphics.java b/libsrc/gnujpdf/src/gnu/jpdf/PDFGraphics.java index 35ce71dbd..ebdd768ae 100644 --- a/libsrc/gnujpdf/src/gnu/jpdf/PDFGraphics.java +++ b/libsrc/gnujpdf/src/gnu/jpdf/PDFGraphics.java @@ -1 +1 @@ -/* * $Id: PDFGraphics.java,v 1.6 2007/09/22 12:58:40 gil1 Exp $ * * $Date: 2007/09/22 12:58:40 $ * * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package gnu.jpdf; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Composite; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Frame; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GraphicsConfiguration; import java.awt.Image; import java.awt.Paint; import java.awt.Polygon; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Shape; import java.awt.Stroke; import java.awt.RenderingHints.Key; import java.awt.font.FontRenderContext; import java.awt.font.GlyphVector; import java.awt.geom.AffineTransform; import java.awt.geom.Area; import java.awt.geom.NoninvertibleTransformException; import java.awt.geom.PathIterator; import java.awt.geom.Point2D; import java.awt.image.BufferedImage; import java.awt.image.BufferedImageOp; import java.awt.image.ColorModel; import java.awt.image.ImageObserver; import java.awt.image.RenderedImage; import java.awt.image.WritableRaster; import java.awt.image.renderable.RenderableImage; import java.awt.print.PageFormat; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.io.Serializable; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.util.Hashtable; import java.util.Locale; import java.util.Map; import java.util.WeakHashMap; /** * This class is our implementation of AWT's Graphics class. It provides a * Java standard way of rendering into a PDF Document's Page. * * @author Peter T Mount, http://www.retep.org.uk/pdf/ * @author Eric Z. Beard, ericzbeard@hotmail.com * @author Gilbert DeLeeuw, gil1@users.sourceforge.net * @version $Revision: 1.6 $, $Date: 2007/09/22 12:58:40 $ * @see gnu.jpdf.PDFGraphics */ public class PDFGraphics extends Graphics2D implements Serializable { /** * One degree in radians */ private static final double degrees_to_radians = Math.PI/180.0; private static final int FILL = 1; private static final int STROKE = 2; private static final int CLIP = 3; private static final AffineTransform IDENTITY = new AffineTransform(); private static final Stroke DEF_STROKE = new BasicStroke(); /* * NOTE: The original class is the work of Peter T. Mount, who released it * in the uk.org.retep.pdf package. It was modified by Eric Z. Beard as * follows: * The package name was changed to gnu.pdf. * The formatting was changed a little bit. * This used to subclass an abstract class in a different package with * the same name (confusing). Now it's one concrete class. * drawImage() was implemented * It is still licensed under the LGPL. */ // Implementation notes: // // Pages 333-335 of the PDF Reference Manual // // Unless absolutely required, use the moveto, lineto and rectangle // operators to perform those actions. // They contain some extra optimizations // which will reduce the output size by up to half in some cases. // // About fill operators: For correct operation, any fill operation should // start with closeBlock(), which will ensure any previous path is completed, // otherwise you may find the fill will include previous items private static final DecimalFormat df = new DecimalFormat("#.###", new DecimalFormatSymbols(Locale.ENGLISH)); //JPEXS: cache for already used images private static Map usedImages = new WeakHashMap(); private Color background; /** * This is true for any Graphics instance that didn't create the stream. * @see #create */ private boolean child; private Area clip; /** * This holds the current clipRectangle */ protected Rectangle clipRectangle; private Composite composite; private Graphics2D dg2 = new BufferedImage(2, 2, BufferedImage.TYPE_INT_RGB).createGraphics(); /** * This is the current font (in Java format) */ private Font font; /** * Part of the optimizer: * When true, we are drawing a path. */ private boolean inStroke; /** * Part of the optimizer: * When true, we are within a Text Block. */ private boolean inText; // true if within a Text Block - see newTextBlock() /** * The stroke line cap code; */ private int lineCap = 0; /** * The stroke line join code */ private int lineJoin = 0; /** * The stroke line width */ private float lineWidth = 1.0f; /** * Part of the optimizer: * The last known moveto/lineto x coordinate * @see #moveto * @see #lineto */ private float lx; // last known moveto/lineto coordinates /** * Part of the optimizer: * The last known moveto/lineto y coordinate * @see #moveto * @see #lineto */ private float ly; // last known moveto/lineto coordinates private float miterLimit = 10.0f; /** * Part of the optimizer: * When true, the font has changed. */ private boolean newFont; // true if the font changes - see newTextBlock() private Stroke originalStroke; // Original transform private AffineTransform oTransform; /** * This is a reference to the PDFPage we are rendering to. */ private PDFPage page; /** * This is the current pen/fill color */ private Paint paint; /** * This is the current font (in PDF format) */ private PDFFont pdffont; /** * Part of the optimizer: * This is written to the stream when the newPath() is called. np then clears * this value. */ private String pre_np; // PDF space transform private AffineTransform pTransform; /** * This is the PrintWriter used to write PDF drawing commands to the Stream */ private PrintWriter pw; /** * RenderingHints */ private RenderingHints rhints = new RenderingHints(null); private Stroke stroke; // Start of Graphics2D properties // Java space transform private AffineTransform transform; private AffineTransform realTransform; /** * This is used to translate coordinates */ protected float trax; /** * This is used to translate coordinates */ protected float tray; /** * Part of the optimizer: * The last x coordinate when rendering text */ private float tx; // the last coordinate for text rendering /** * Part of the optimizer: * The last y coordinate when rendering text */ private float ty; // the last coordinate for text rendering /** * @see Graphics2D#addRenderingHints(Map) */ public void addRenderingHints(Map hints) { rhints.putAll(hints); } /** * This produces an arc by breaking it down into one or more Bezier curves. * It is used internally to implement the drawArc and fillArc methods. * * @param axc X coordinate of arc centre * @param ayc Y coordinate of arc centre * @param width of bounding rectangle * @param height of bounding rectangle * @param ang1 Start angle * @param ang2 End angle * @param clockwise true to draw clockwise, false anti-clockwise */ public void arc(double axc,double ayc, double width,double height, double ang1,double ang2, boolean clockwise) { double adiff; double x0, y0; double x3r, y3r; boolean first = true; // may not need this //if( ar < 0 ) { //ang1 += fixed_180; //ang2 += fixed_180; //ar = - ar; //} double ang1r = (ang1%360.0)*degrees_to_radians; double sin0 = Math.sin(ang1r); double cos0 = Math.cos(ang1r); x0 = axc + width*cos0; y0 = ayc + height*sin0; // NB: !clockwise here as Java Space is inverted to User Space if( !clockwise ) { // Quadrant reduction while ( ang1 < ang2 ) ang2 -= 360.0; while ( (adiff = ang2 - ang1) < -90.0 ) { double w = sin0; sin0 = -cos0; cos0 = w; x3r = axc + width*cos0; y3r = ayc + height*sin0; arc_add(first, width, height, x0, y0, x3r, y3r, (x0 + width*cos0), (y0 + height*sin0) ); x0 = x3r; y0 = y3r; ang1 -= 90.0; first = false; } } else { // Quadrant reduction while ( ang2 < ang1 ) ang2 += 360.0; while ( (adiff = ang2 - ang1) > 90.0 ) { double w = cos0; cos0 = -sin0; sin0 = w; x3r = axc + width*cos0; y3r = ayc + height*sin0; arc_add(first, width, height, x0, y0, x3r, y3r, (x0 + width*cos0), (y0 + height*sin0) ); x0 = x3r; y0 = y3r; ang1 += 90.0; first = false; } } // Compute the intersection of the tangents. // We know that -fixed_90 <= adiff <= fixed_90. double trad = Math.tan(adiff * (degrees_to_radians / 2)); double ang2r = ang2 * degrees_to_radians; double xt = x0 - trad * width*sin0; double yt = y0 + trad * height*cos0; arc_add(first, width, height, x0, y0, (axc + width * Math.cos(ang2r)), (ayc + height * Math.sin(ang2r)), xt, yt); } /** * Used by the arc method to actually add an arc to the path * Important: We write directly to the stream here, because this method * operates in User space, rather than Java space. * @param first true if the first arc * @param w width * @param h height * @param x0 coordinate * @param y0 coordinate * @param x3 coordinate * @param y3 coordinate * @param xt coordinate * @param yt coordinate */ private void arc_add(boolean first, double w,double h, double x0,double y0, double x3,double y3, double xt,double yt) { double dx = xt - x0, dy = yt - y0; double dist = dx*dx + dy*dy; double w2 = w*w, h2=h*h; double r2 = w2+h2; double fw = 0.0, fh = 0.0; if(dist < (r2*1.0e8)) { // JM fw = (w2 != 0.0) ? ((4.0/3.0)/(1+Math.sqrt(1+dist/w2))) : 0.0; fh = (h2 != 0.0) ? ((4.0/3.0)/(1+Math.sqrt(1+dist/h2))) : 0.0; } // The path must have a starting point if(first) moveto(x0,y0); double x = x0+((xt-x0)*fw); double y = y0+((yt-y0)*fh); x0 = x3+((xt-x3)*fw); y0 = y3+((yt-y3)*fh); // Finally the actual curve. curveto(x,y,x0,y0,x3,y3); } /** * This simply draws a White Rectangle to clear the area * @param x coordinate * @param y coordinate * @param w width * @param h height */ public void clearRect(int x,int y,int w,int h) { closeBlock(); pw.print("q 1 1 1 RG ");// save state, set colour to White drawRect(x,y,w,h); closeBlock("B Q"); // close fill & stroke, then restore state } /** * @see Graphics2D#clip(Shape) */ public void clip(Shape s) { if (s == null) { setClip(null); return; } s = transform.createTransformedShape(s); if (clip == null) clip = new Area(s); else clip.intersect(new Area(s)); // followPath(s, CLIP); } /** * This extra method allows PDF users to clip to a Polygon. * *

In theory you could use setClip(), except that java.awt.Graphics * only supports Rectangle with that method, so we will have an extra * method. * @param p Polygon to clip to */ public void clipPolygon(Polygon p) { closeBlock(); // finish off any existing path polygon(p.xpoints,p.ypoints,p.npoints); closeBlock("W"); // clip to current path clipRectangle = p.getBounds(); } /** * Clips to a set of coordinates * @param x coordinate * @param y coordinate * @param w width * @param h height */ public void clipRect(int x,int y,int w,int h) { setClip(x,y,w,h); } /** * All functions should call this to close any existing optimized blocks. */ void closeBlock() { closeBlock("S"); } /** *

This is used by code that use the path in any way other than Stroke * (like Fill, close path & Stroke etc). Usually this is used internally.

* * @param code PDF operators that will close the path */ void closeBlock(String code) { if(inText) { pw.println("ET Q"); // setOrientation(); // fixes Orientation matrix } if(inStroke) { pw.println(code); } inStroke=inText=false; } /** * This is unsupported - how do you do this with Vector graphics? * @param x coordinate * @param y coordinate * @param w width * @param h height * @param dx coordinate * @param dy coordinate */ public void copyArea(int x,int y,int w,int h,int dx,int dy) { // Hmm... Probably need to keep track of everything // that has been drawn so far to get the contents of an area } //============ Line operations ======================= /** *

This returns a child instance of this Graphics object. As with AWT, the * affects of using the parent instance while the child exists, is not * determined.

* *

Once complete, the child should be released with it's dispose() * method which will restore the graphics state to it's parent.

* * @return Graphics object to render onto the page */ public Graphics create() { closeBlock(); PDFGraphics g = createGraphic(page,pw); // The new instance inherits a few items // g.media = new Rectangle(media); g.trax = trax; g.tray = tray; g.clipRectangle = new Rectangle(clipRectangle); return (Graphics) g; } // end create() /** * This method creates a new instance of the class based on the page * and a print writer. * * @param page the page to attach to * @param pw the PrintWriter to attach to. */ protected PDFGraphics createGraphic(PDFPage page, PrintWriter pw) { PDFGraphics g = new PDFGraphics(); g.init(page,pw); return g; } /** * This extension appends a Bezier curve to the path. The curve * extends from the current point to (x2,y2) using the current * point and (x1,y1) as the Bezier control points. *

The new current point is (x2,y2) * * @param x1 Second control point * @param y1 Second control point * @param x2 Destination point * @param y2 Destination point */ public void curveto(double x1,double y1,double x2,double y2) { newPath(); pw.println(cxy(x1,y1)+cxy(x2,y2)+"v"); lx=(float)x2; ly=(float)y2; } /** * This extension appends a Bezier curve to the path. The curve * extends from the current point to (x3,y3) using (x1,y1) and * (x2,y2) as the Bezier control points. *

The new current point is (x3,y3) * * @param x1 First control point * @param y1 First control point * @param x2 Second control point * @param y2 Second control point * @param x3 Destination point * @param y3 Destination point */ public void curveto(double x1,double y1,double x2,double y2,double x3,double y3) { newPath(); pw.println(cxy(x1,y1)+cxy(x2,y2)+cxy(x3,y3)+"c"); lx=(float)x3; ly=(float)y3; } /** * This extension appends a Bezier curve to the path. The curve * extends from the current point to (x2,y2) using the current * point and (x1,y1) as the Bezier control points. *

The new current point is (x2,y2) * * @param x1 Second control point * @param y1 Second control point * @param x2 Destination point * @param y2 Destination point */ public void curveto(int x1,int y1,int x2,int y2) { newPath(); pw.println(cxy(x1,y1)+cxy(x2,y2)+"v"); lx=x2; ly=y2; } /** * This extension appends a Bezier curve to the path. The curve * extends from the current point to (x3,y3) using (x1,y1) and * (x2,y2) as the Bezier control points. *

The new current point is (x3,y3) * * @param x1 First control point * @param y1 First control point * @param x2 Second control point * @param y2 Second control point * @param x3 Destination point * @param y3 Destination point */ public void curveto(int x1,int y1,int x2,int y2,int x3,int y3) { newPath(); pw.println(cxy(x1,y1)+cxy(x2,y2)+cxy(x3,y3)+"c"); lx=x3; ly=y3; } /** * This extension appends a Bezier curve to the path. The curve * extends from the current point to (x2,y2) using (x1,y1) and * the end point as the Bezier control points. *

The new current point is (x2,y2) * * @param x1 Second control point * @param y1 Second control point * @param x2 Destination point * @param y2 Destination point */ public void curveto2(double x1,double y1,double x2,double y2) { newPath(); pw.println(cxy(x1,y1)+cxy(x2,y2)+"y"); lx=(float)x2; ly=(float)y2; } // Arcs are horrible and complex. They are at the end of the // file, because they are the largest. This is because, unlike // Postscript, PDF doesn't have any arc operators, so we must // implement them by converting into one or more Bezier curves // (which is how Postscript does them internally). /** * This extension appends a Bezier curve to the path. The curve * extends from the current point to (x2,y2) using (x1,y1) and * the end point as the Bezier control points. *

The new current point is (x2,y2) * * @param x1 Second control point * @param y1 Second control point * @param x2 Destination point * @param y2 Destination point */ public void curveto2(int x1,int y1,int x2,int y2) { newPath(); pw.println(cxy(x1,y1)+cxy(x2,y2)+"y"); lx=x2; ly=y2; } /** * Converts the Java space dimension into pdf. * @param w width * @param h height * @return String containing the coordinates in PDF space */ private String cwh(double w,double h) { double nw=w,nh=h; // scratch // switch(mediaRot) { // case PageFormat.PORTRAIT: // Portrait //nw = w; nh = -h; // break; // // case PageFormat.LANDSCAPE: // // Landscape // nw = h; // nh = w; // break; // //// case 180: //// // Inverse Portrait //// nw = -w; //// //nh = h; //// break; // // case PageFormat.REVERSE_LANDSCAPE: // // Seascape // nw = -h; // nh = -w; // break; // } return ""+df.format(nw)+" "+df.format(nh)+" "; } /** * Converts the Java space dimension into pdf. * @param w width * @param h height * @return String containing the coordinates in PDF space */ private String cwh(int w,int h) { return cwh((double)w,(double)h); } /** * Converts the Java space coordinates into pdf. * @param x coordinate * @param y coordinate * @return String containing the coordinates in PDF space */ private String cxy(double x, double y) { // double nx = x, ny = y; // scratch // double mh = page.getPageFormat().getHeight(); Point2D ptSrc = new Point2D.Double(x, y); Point2D ptDst = new Point2D.Double(); transform.transform(ptSrc, ptDst); // x += trax; // y += tray; // // nx = x; // ny = mh - y; // // System.out.println("\ncxy(" + ptSrc.getX() + ", " + ptSrc.getY() + ")"); // System.out.println("Old [" + nx + "," + ny + "]"); // System.out.println("Trn [" + ptDst.getX() + ", " + ptDst.getY() + "]"); // // return "" + df.format(nx) + " " + df.format(ny) + " "; return ""+df.format(ptDst.getX())+" "+df.format(ptDst.getY())+" "; } /** * Converts the Java space coordinates into pdf. * @param x coordinate * @param y coordinate * @return String containing the coordinates in PDF space */ private String cxy(int x,int y) { return cxy((double)x,(double)y); } /** *

This releases any resources used by this Graphics object. You must use * this method once finished with it. Leaving it open will leave the PDF * stream in an inconsistent state, and will produce errors.

* *

If this was created with Graphics.create() then the parent instance * can be used again. If not, then this closes the graphics operations for * this page when used with PDFJob.

* *

When using PDFPage, you can create another fresh Graphics instance, * which will draw over this one.

* */ public void dispose() { closeBlock(); if(child) { pw.println("Q"); // restore graphics context } else { pw.close(); // close the stream if were the parent } } // ********************************************* // **** Implementation of java.awt.Graphics **** // ********************************************* //============ Rectangle operations ======================= /** * @see Graphics2D#draw(Shape) */ public void draw(Shape s) { followPath(s, STROKE); } /** *

Not implemented

* *

Draws a 3-D highlighted outline of the specified rectangle. * The edges of the rectangle are highlighted so that they appear * to be beveled and lit from the upper left corner. * The colors used for the highlighting effect are determined based on * the current color. The resulting rectangle covers an area that * is width + 1 pixels wide by height + 1 pixels tall. *

* * @param x an int value * @param y an int value * @param width an int value * @param height an int value * @param raised a boolean value */ public void draw3DRect(int x, int y, int width, int height, boolean raised) { // Not implemented } /** * Draws an arc * @param x coordinate * @param y coordinate * @param w width * @param h height * @param sa Start angle * @param aa End angle */ public void drawArc(int x,int y,int w,int h,int sa,int aa) { w=w>>1; h=h>>1; x+=w; y+=h; arc((double)x,(double)y, (double)w,(double)h, (double)-sa,(double)(-sa-aa), false); } /** *

Not implemented

* * @param data a byte[] value * @param offset an int value * @param length an int value * @param x an int value * @param y an int value */ public void drawBytes(byte[] data, int offset, int length, int x, int y) { } //============ Optimizers ======================= /** * @see Graphics2D#drawGlyphVector(GlyphVector, float, float) */ public void drawGlyphVector(GlyphVector g, float x, float y) { Shape s = g.getOutline(x, y); fill(s); } /** * @see Graphics2D#drawImage(BufferedImage, BufferedImageOp, int, int) */ public void drawImage(BufferedImage img, BufferedImageOp op, int x, int y) { BufferedImage result = img; if (op != null) { result = op.createCompatibleDestImage(img, img.getColorModel()); result = op.filter(img, result); } drawImage(result, x, y, null); } /** * @see Graphics2D#drawImage(Image, AffineTransform, ImageObserver) */ public boolean drawImage(Image img, AffineTransform xform, ImageObserver obs) { // return drawImage(img, null, xform, null, obs); return true; } /** *

Draw's an image onto the page, with a backing colour.

* * @param img The java.awt.Image * @param x coordinate on page * @param y coordinate on page * @param bgcolor Background colour * @param obs ImageObserver * @return true if drawn */ public boolean drawImage(Image img,int x,int y,Color bgcolor, ImageObserver obs) { return drawImage(img,x,y,img.getWidth(obs),img.getHeight(obs), bgcolor,obs); } /** * Draw's an image onto the page * @param img The java.awt.Image * @param x coordinate on page * @param y coordinate on page * @param obs ImageObserver * @return true if drawn */ public boolean drawImage(Image img,int x,int y,ImageObserver obs) { return drawImage(img,x,y,img.getWidth(obs),img.getHeight(obs),obs); } /** *

Draw's an image onto the page, with a backing colour.

* * @param img The java.awt.Image * @param x coordinate on page * @param y coordinate on page * @param w Width on page * @param h height on page * @param bgcolor Background colour * @param obs ImageObserver * @return true if drawn */ public boolean drawImage(Image img,int x,int y,int w,int h, Color bgcolor,ImageObserver obs) { closeBlock(); pw.print("q "); // save state Color c = getColor(); // save current colour setColor(bgcolor); // change the colour drawRect(x,y,w,h); closeBlock("B Q"); // fill stroke, restore state paint = c; // restore original colour return drawImage(img,x,y,img.getWidth(obs),img.getHeight(obs),obs); } /** *

Draws an image onto the page.

* *

This method is implemented with ASCIIbase85 encoding and the * zip stream deflater. It results in a stream that is anywhere * from 3 to 10 times as big as the image. This obviously needs some * improvement, but it works well for small images

* * @param img The java.awt.Image * @param x coordinate on page * @param y coordinate on page * @param w Width on page * @param h height on page * @param obs ImageObserver * @return true if drawn */ public boolean drawImage(Image img,int x,int y,int w,int h, ImageObserver obs) { closeBlock(); PDFImage image; if (usedImages.containsKey(img)) { image = usedImages.get(img); } else { image = new PDFImage(img, x, y, w, h, obs); // The image needs to be registered in several places page.getPDFDocument().setImageName(image); page.getPDFDocument().add(image); usedImages.put(img, image); } page.addToProcset("/ImageC"); page.addImageResource(image.getName() + " " + image.getSerialID() + " 0 R"); // JM /*page.addResource("/XObject << " + image.getName() + " " + image.getSerialID() + " 0 R >>");*/ // q w 0 0 h x y cm % the coordinate matrix pw.print("q " + w + " 0 0 " + h + " " + x + " " + ((int) page.getDimension().getHeight() - y - h) + " cm \n" + image.getName() + " Do\nQ\n"); return false; } /** * Draw's an image onto the page, with scaling *

This is not yet supported. * * @param img The java.awt.Image * @param dx1 coordinate on page * @param dy1 coordinate on page * @param dx2 coordinate on page * @param dy2 coordinate on page * @param sx1 coordinate on image * @param sy1 coordinate on image * @param sx2 coordinate on image * @param sy2 coordinate on image * @param bgcolor Background colour * @param obs ImageObserver * @return true if drawn */ public boolean drawImage(Image img,int dx1,int dy1,int dx2, int dy2,int sx1,int sy1,int sx2,int sy2, Color bgcolor,ImageObserver obs) { return false; } //============ Clipping operations ======================= /** * Draw's an image onto the page, with scaling *

This is not yet supported. * * @param img The java.awt.Image * @param dx1 coordinate on page * @param dy1 coordinate on page * @param dx2 coordinate on page * @param dy2 coordinate on page * @param sx1 coordinate on image * @param sy1 coordinate on image * @param sx2 coordinate on image * @param sy2 coordinate on image * @param obs ImageObserver * @return true if drawn */ public boolean drawImage(Image img,int dx1,int dy1,int dx2, int dy2,int sx1,int sy1,int sx2,int sy2, ImageObserver obs) { // This shouldn't be too bad, just change the coordinate matrix return false; } /** * Draws a line between two coordinates. * * If the first coordinate is the same as the last one drawn * (i.e. a previous drawLine, moveto, etc) it is ignored. * @param x1 coordinate * @param y1 coordinate * @param x2 coordinate * @param y2 coordinate */ public void drawLine(int x1,int y1,int x2,int y2) { moveto(x1,y1); lineto(x2,y2); } //============ Arcs operations ============================== // These are the standard Graphics operators. They use the // arc extension operators to achieve the affect. /** *

Draws an oval

* * @param x coordinate * @param y coordinate * @param w width * @param h height */ public void drawOval(int x,int y,int w,int h) { drawArc(x, y, w, h, 0, 360); } /** * Draws a polygon, linking the first and last coordinates. * @param xp Array of x coordinates * @param yp Array of y coordinates * @param np number of points in polygon */ public void drawPolygon(int[] xp,int[] yp,int np) { polygon(xp,yp,np); closeBlock("s"); // close path and stroke } /** * Draws a polyline. The first and last coordinates are not linked. * @param xp Array of x coordinates * @param yp Array of y coordinates * @param np number of points in polyline */ public void drawPolyline(int[] xp,int[] yp,int np) { polygon(xp,yp,np); // no stroke, as we keep the optimizer in stroke state } /** * We override Graphics.drawRect as it doesn't join the 4 lines. * Also, PDF provides us with a Rectangle operator, so we will use that. * @param x coordinate * @param y coordinate * @param w width * @param h height */ public void drawRect(int x,int y,int w,int h) { newPath(); pw.print(cxy(x,y)+cwh(w,h)+"re "); // rectangle lx=x; // I don't know if this is correct, but lets see if PDF ends ly=y; // the rectangle at it's start. // stroke (optimized) } /** * @see Graphics2D#drawRenderableImage(RenderableImage, AffineTransform) */ public void drawRenderableImage(RenderableImage img, AffineTransform xform) { drawRenderedImage(img.createDefaultRendering(), xform); } /** * @see Graphics2D#drawRenderedImage(RenderedImage, AffineTransform) */ public void drawRenderedImage(RenderedImage img, AffineTransform xform) { BufferedImage image = null; if (img instanceof BufferedImage) { image = (BufferedImage)img; } else { ColorModel cm = img.getColorModel(); int width = img.getWidth(); int height = img.getHeight(); WritableRaster raster = cm.createCompatibleWritableRaster(width, height); boolean isAlphaPremultiplied = cm.isAlphaPremultiplied(); Hashtable properties = new Hashtable(); String[] keys = img.getPropertyNames(); if (keys!=null) { for (int i = 0; i < keys.length; i++) { properties.put(keys[i], img.getProperty(keys[i])); } } BufferedImage result = new BufferedImage(cm, raster, isAlphaPremultiplied, properties); img.copyData(raster); image=result; } drawImage(image, xform, null); } /** * This is not yet implemented * * @param x coordinate * @param y coordinate * @param w width * @param h height * @param aw a-width * @param ah a-height */ public void drawRoundRect(int x,int y,int w,int h,int aw,int ah) { } //============ Oval operations ======================= /** * Draws a string using a AttributedCharacterIterator. *

This is not supported yet, as I have no idea what an * AttributedCharacterIterator is. *

This method is new to the Java2 API. */ public void drawString(java.text.AttributedCharacterIterator aci, float x,float y) { } /** * Draws a string using a AttributedCharacterIterator. *

This is not supported yet, as I have no idea what an * AttributedCharacterIterator is. *

This method is new to the Java2 API. */ public void drawString(java.text.AttributedCharacterIterator aci, int x,int y) { } public void drawString(String s,float x,float y) { newTextBlock(x, y); pw.println(PDFStringHelper.makePDFString(s) + " Tj"); closeBlock(); } /** * This draws a string. * * @oaran s String to draw * @param x coordinate * @param y coordinate */ public void drawString(String s,int x,int y) { drawString(s, (float) x, (float) y); } public void drawTransparentString(String s, float x, float y) { newTextBlock(x, y); pw.println("3 Tr"); pw.println(PDFStringHelper.makePDFString(s) + " Tj"); closeBlock(); } /** * This draws a transparent string. * * @oaran s String to draw * @param x coordinate * @param y coordinate */ public void drawTransparentString(String s, int x, int y) { drawTransparentString(s, (float) x, (float) y); } /** * @see Graphics2D#fill(Shape) */ public void fill(Shape s) { followPath(s, FILL); } /** *

Not implemented

* * @param x an int value * @param y an int value * @param width an int value * @param height an int value * @param raised a boolean value */ public void fill3DRect(int x, int y, int width, int height, boolean raised) { // Not implemented } /** * Fills an arc, joining the start and end coordinates * @param x coordinate * @param y coordinate * @param w width * @param h height * @param sa Start angle * @param aa End angle */ public void fillArc(int x,int y,int w,int h,int sa,int aa) { // here we fool the optimizer. We force any open path to be closed, // then draw the arc. Finally, as the optimizer hasn't stroke'd the // path, we close and fill it, and mark the Stroke as closed. // // Note: The lineto to the centre of the object is required, because // the fill only fills the arc. Skipping this includes an extra // chord, which isn't correct. Peter May 31 2000 closeBlock(); drawArc(x,y,w,h,sa,aa); lineto(x+(w>>1),y+(h>>1)); closeBlock("b"); // closepath and fill } //============ Extension operations ============================== // These are extensions, and provide access to PDF Specific // operators. /** *

Draws a filled oval

* * @param x coordinate * @param y coordinate * @param w width * @param h height */ public void fillOval(int x,int y,int w,int h) { fillArc(x, y, w, h, 0, 360); } //============ Polygon operations ======================= /** * Fills a polygon. * @param xp Array of x coordinates * @param yp Array of y coordinates * @param np number of points in polygon */ public void fillPolygon(int[] xp,int[] yp,int np) { closeBlock(); // finish off any previous paths polygon(xp,yp,np); closeBlock("b"); // closepath, fill and stroke } //============ Image operations ======================= /** * Fills a rectangle with the current colour * * @param x coordinate * @param y coordinate * @param w width * @param h height */ public void fillRect(int x,int y,int w,int h) { // end any path & stroke. This ensures the fill is on this // rectangle, and not on any previous graphics closeBlock(); drawRect(x,y,w,h); closeBlock("B"); // rectangle, fill stroke } //============ Round Rectangle operations ======================= /** * This is not yet implemented * * @param x coordinate * @param y coordinate * @param w width * @param h height * @param aw a-width * @param ah a-height */ public void fillRoundRect(int x,int y,int w,int h,int aw,int ah) { } /////////////////////////////////////////////// // // // implementation specific methods // // private void followPath(Shape s, int drawType) { PathIterator points; if (s==null) return; if (drawType==STROKE) { if (!(stroke instanceof BasicStroke)) { s = stroke.createStrokedShape(s); followPath(s, FILL); return; } } // if (drawType==STROKE) { // setStrokeDiff(stroke, oldStroke); // oldStroke = stroke; // setStrokePaint(); // } // else if (drawType==FILL) // setFillPaint(); points = s.getPathIterator(IDENTITY); int segments = 0; float[] coords = new float[6]; while(!points.isDone()) { segments++; int segtype = points.currentSegment(coords); switch(segtype) { case PathIterator.SEG_CLOSE: pw.print("h "); break; case PathIterator.SEG_CUBICTO: curveto(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]); break; case PathIterator.SEG_LINETO: lineto(coords[0], coords[1]); break; case PathIterator.SEG_MOVETO: moveto(coords[0], coords[1]); break; case PathIterator.SEG_QUADTO: curveto(coords[0], coords[1], coords[2], coords[3]); break; } points.next(); } switch (drawType) { case FILL: if (segments > 0) { if (points.getWindingRule() == PathIterator.WIND_EVEN_ODD) closeBlock("B*"); else closeBlock("B"); } break; case STROKE: if (segments > 0) closeBlock("S"); break; case CLIP: default: //drawType==CLIP if (segments == 0) drawRect(0, 0, 0, 0); if (points.getWindingRule() == PathIterator.WIND_EVEN_ODD) closeBlock("W*"); else closeBlock("W"); } } /** * @see Graphics2D#getBackground() */ public Color getBackground() { return background; } /** * Returns the Shape of the clipping region * As my JDK docs say, this may break with Java 2D. * @return Shape of the clipping region */ public Shape getClip() { return null; } /** * Returns the Rectangle that fits the current clipping region * @return the Rectangle that fits the current clipping region */ public Rectangle getClipBounds() { return clipRectangle; } //============ Color operations ======================= /** * Returns the current pen Colour * @return the current pen Colour */ public Color getColor() { return (paint instanceof Color) ? (Color) paint : Color.black; } /** * @see Graphics2D#getComposite() */ public Composite getComposite() { return composite; } /** * @see Graphics2D#getDeviceConfiguration() */ public GraphicsConfiguration getDeviceConfiguration() { return dg2.getDeviceConfiguration(); } /** * Return's the current font. * @return the current font. */ public Font getFont() { if(font==null) setFont(new Font("SansSerif",Font.PLAIN,12)); return font; } /** * Returns the FontMetrics for a font. *

This doesn't work correctly. Perhaps having some way of mapping * the base 14 fonts to our own FontMetrics implementation? * @param font The java.awt.Font to return the metrics for * @return FontMetrics for a font */ public FontMetrics getFontMetrics(Font font) { Frame dummy = new Frame(); dummy.addNotify(); Image image = dummy.createImage(100, 100); if (image == null) { System.err.println("getFontMetrics: image is null"); } Graphics graphics = image.getGraphics(); return graphics.getFontMetrics(font); } /** * @see Graphics2D#getFontRenderContext() */ public FontRenderContext getFontRenderContext() { boolean antialias = RenderingHints.VALUE_TEXT_ANTIALIAS_ON.equals(getRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING)); boolean fractions = RenderingHints.VALUE_FRACTIONALMETRICS_ON.equals(getRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS)); return new FontRenderContext(new AffineTransform(), antialias, fractions); } /** * Returns the associated PDFPage for this graphic * @return the associated PDFPage for this graphic */ public PDFPage getPage() { return page; } /** * Returns the current pen Colour * @return the current pen Colour */ public Paint getPaint() { return paint; } /** * @param arg0 a key * @return the rendering hint */ public Object getRenderingHint(Key arg0) { return rhints.get(arg0); } /** * @see Graphics2D#getRenderingHints() */ public RenderingHints getRenderingHints() { return rhints; } /** * @see Graphics2D#getStroke() */ public Stroke getStroke() { return originalStroke; } /** * @see Graphics2D#getTransform() */ public AffineTransform getTransform() { return new AffineTransform(oTransform); } /** * Returns the PrintWriter handling the underlying stream * @return the PrintWriter handling the underlying stream */ public PrintWriter getWriter() { return pw; } /** * @see Graphics2D#hit(Rectangle, Shape, boolean) */ public boolean hit(Rectangle rect, Shape s, boolean onStroke) { if (onStroke) { s = stroke.createStrokedShape(s); } s = transform.createTransformedShape(s); Area area = new Area(s); if (clip != null) area.intersect(clip); return area.intersects(rect.x, rect.y, rect.width, rect.height); } /** * This initialises the stream by saving the current graphics state, and * setting up the default line width (for us). * * It also sets up the instance ready for graphic operations and any * optimisations. * *

For child instances, the stream is already open, so this should keep * things happy. */ private void init() { PageFormat pf = page.getPageFormat(); // save graphics state (restored by dispose) if(child) { pw.print("q "); } // now initialise the instance //setColor(Color.black); paint = Color.black; // possible: if parent.color is not black, then force black? // must check to see what AWT does? // Original User Space Transform (identity) oTransform = new AffineTransform(); // Transform from Java Space to PDF Space pTransform = new AffineTransform(); pTransform.translate(0, pf.getHeight()); pTransform.scale(1d, -1d); // Combined Transform User->Java->PDF transform = new AffineTransform(oTransform); transform.preConcatenate(pTransform); // Set the line width setStroke(DEF_STROKE); } /** * This is called by PDFPage when creating a Graphcis instance. * @param page The PDFPage to draw onto. */ protected void init(PDFPage page) { this.page = page; // We are the parent instance child = false; // Now create a stream to store the graphics in PDFStream stream = new PDFStream(); // To view detail in uncompressed format comment out the next line stream.setDeflate(true); page.getPDFDocument().add(stream); page.add(stream); pw = stream.getWriter(); // initially, we are limited to the page size clipRectangle = page.getImageableArea(); // finally initialise the stream init(); } /** * This method is used internally by create() and by the PDFJob class * @param page PDFPage to draw into * @param pw PrintWriter to use */ protected void init(PDFPage page,PrintWriter pw) { this.page = page; this.pw = pw; // In this case, we didn't create the stream (our parent did) // so child is true (see dispose) child = true; // finally initialise the stream init(); } /** * This adds a line segment to the current path * @param x coordinate * @param y coordinate */ public void lineto(double x,double y) { newPath(); // no optimisation here as it may introduce errors on decimal coordinates. pw.print(cxy(x,y)+"l "); lx=(float)x; ly=(float)y; } /** * This adds a line segment to the current path * @param x coordinate * @param y coordinate */ public void lineto(int x,int y) { newPath(); if(lx!=x && ly!=y) pw.print(cxy(x,y)+"l "); lx=x; ly=y; } /** * This moves the current drawing point. * @param x coordinate * @param y coordinate */ public void moveto(double x,double y) { newPath(); // no optimisation here as it may introduce errors on decimal coordinates. pw.print(cxy(x,y)+"m "); lx=(float)x; ly=(float)y; } /** * This moves the current drawing point. * @param x coordinate * @param y coordinate */ public void moveto(int x,int y) { newPath(); if(lx!=x || ly!=y) pw.print(cxy(x,y)+"m "); lx=x; ly=y; } /** * Functions that draw lines should start by calling this. It starts a * new path unless inStroke is set, in that case it uses the existing path */ void newPath() { if(inText) { closeBlock(); } if(!inStroke) { if(pre_np!=null) { pw.print(pre_np); // this is the prefix set by setOrientation() pre_np = null; } pw.print("n "); } inText=false; inStroke=true; // an unlikely coordinate to fool the moveto() optimizer lx = ly = -9999; } /** *

Functions that draw text should start by calling this. It starts a text * block (accounting for media orientation) unless we are already in a Text * block.

* *

It also handles if the font has been changed since the current text * block was started, so your function will be current.

* * @param x x coordinate in java space * @param y y coordinate in java space */ void newTextBlock(float x,float y) { // close the current path if there is one if(inStroke) { closeBlock(); } // create the text block if one is not current. If we are, the newFont // condition at the end catches font changes if(!inText) { // This ensures that there is a font available getFont(); pw.print("q BT "); tx=ty=0; if (realTransform != null) { pw.println("" + df.format(pTransform.getScaleX()) + " " + "" + df.format(pTransform.getShearY()) + " " + "" + df.format(pTransform.getShearX()) + " " + "" + df.format(pTransform.getScaleY()) + " " + "" + df.format(pTransform.getTranslateX()) + " " + "" + df.format(pTransform.getTranslateY()) + " Tm" ); } // produce the text matrix for the media // switch(mediaRot) { // case PageFormat.PORTRAIT: // Portrait // //pw.println("1 0 0 1 0 0 Tm"); // break; // // case PageFormat.LANDSCAPE: // Landscape // pw.println("0 1 -1 0 0 0 Tm"); // rotate // break; // // case 180: // Inverted Portrait // pw.println("1 0 0 -1 0 0 Tm"); // break; // // case PageFormat.REVERSE_LANDSCAPE: // Seascape // pw.println("0 -1 1 0 0 0 Tm"); // rotate // break; // } // move the text cursor by an absolute amount pw.print(txy(x,y)+"Td "); } else { // move the text cursor by a relative amount pw.print(twh(x, y, tx, ty) + "Td "); //pw.print(txy(x,y)+"Td "); } // preserve the coordinates for the next time tx = x; ty = y; if(newFont || !inText) pw.print(pdffont.getName()+" "+font.getSize()+" Tf "); // later add colour changes here (if required) inStroke = newFont = false; inText = true; } /** * This is used to add a polygon to the current path. * Used by drawPolygon(), drawPolyline() and fillPolygon() etal * @param xp Array of x coordinates * @param yp Array of y coordinates * @param np number of points in polygon * @see #drawPolygon * @see #drawPolyline * @see #fillPolygon */ public void polygon(int[] xp,int[] yp,int np) { // newPath() not needed here as moveto does it ;-) moveto(xp[0],yp[0]); for(int i=1;iSets the clipping region to that of a Shape. * @param s Shape to clip to. */ public void setClip(Shape s) { Rectangle r = s.getBounds(); setClip(r.x,r.y,r.width,r.height); } /** * Sets the color for drawing * @param c Color to use */ public void setColor(Color c) { setPaint(c); } /** * @see Graphics2D#setComposite(Composite) */ public void setComposite(Composite comp) { this.composite = comp; } /** * This extension sets the line width to the default of 1mm which is what * Java uses when drawing to a PrintJob. */ public void setDefaultLineWidth() { closeBlock(); // draw any path before we change the line width pw.println("1 w"); } /** * This sets the font. * @param f java.awt.Font to set to. */ public void setFont(Font f) { // optimize: Save some space if the font is already the current one. if(font!=f) { font = f; pdffont = page.getFont("/Type1",f.getName(),f.getStyle()); // mark the font as changed newFont = true; } } public void setExistingTtfFont(Font f) { if (font != f) { font = f; pdffont = page.getFont("/TrueType", f.getName(), f.getStyle()); // mark the font as changed newFont = true; } } public void setTtfFont(Font f, File file) throws IOException { if (font != f) { font = f; pdffont = page.getEmbeddedFont(f.getName(), f.getStyle(), file); // mark the font as changed newFont = true; } } private void setLineCap(int cap) { int lineCap = 0; switch (cap) { case BasicStroke.JOIN_MITER: lineCap = 0; break; case BasicStroke.JOIN_ROUND: lineCap = 1; break; case BasicStroke.JOIN_BEVEL: lineCap = 2; break; } if (this.lineCap != lineCap) { closeBlock(); // draw any path before we change the line width this.lineCap = lineCap; pw.println(""+lineCap+" J"); } } private void setLineJoin(int join) { int lineJoin = 0; switch (join) { case BasicStroke.JOIN_MITER: lineJoin = 0; break; case BasicStroke.JOIN_ROUND: lineJoin = 1; break; case BasicStroke.JOIN_BEVEL: lineJoin = 2; break; } if (this.lineJoin != lineJoin) { closeBlock(); // draw any path before we change the line width this.lineJoin = lineJoin; pw.println(""+lineJoin+" j"); } } /** * This extension allows the width of the drawn line to be set * @param width Line width in pdf graphic units (points) */ public void setLineWidth(float width) { if (width != this.lineWidth) { closeBlock(); // draw any path before we change the line width this.lineWidth = width; pw.println(""+width+" w"); } } private void setMiterLimit(float limit) { if (limit != this.miterLimit) { closeBlock(); // draw any path before we change the line width this.miterLimit = limit; pw.println(""+limit+" M"); } } /** * Sets the paint for drawing * @param paint Paint to use */ public void setPaint(Paint paint) { this.paint = paint; if (paint instanceof Color) { Color c = (Color) paint; double r = ((double) c.getRed()) / 255.0; double g = ((double) c.getGreen()) / 255.0; double b = ((double) c.getBlue()) / 255.0; closeBlock(); // This ensures any paths are drawn in the previous // colours pw.println("" + r + " " + g + " " + b + " rg " + r + " " + g + " " + b + " RG"); } } /** * Not implemented, as this is not supported in the PDF specification. */ public void setPaintMode() { } /** * Sets a rendering hint * @param arg0 * @param arg1 */ public void setRenderingHint(Key arg0, Object arg1) { if (arg1 != null) { rhints.put(arg0, arg1); } else { rhints.remove(arg0); } } // Add Graphics2D methods. /** * @see Graphics2D#setRenderingHints(Map) */ public void setRenderingHints(Map hints) { rhints.clear(); rhints.putAll(hints); } /** * @see Graphics2D#setStroke(Stroke) */ public void setStroke(Stroke s) { originalStroke = s; this.stroke = transformStroke(s); if (stroke instanceof BasicStroke) { BasicStroke bs = (BasicStroke) stroke; setLineCap(bs.getEndCap()); setLineJoin(bs.getLineJoin()); setLineWidth(bs.getLineWidth()); setMiterLimit(bs.getMiterLimit()); // TODO: Line dash pattern } } /** * @see Graphics2D#setTransform(AffineTransform) */ public void setTransform(AffineTransform t) { // Save copy of original transform. oTransform = t; // Working copy of transform transform = new AffineTransform(t); // Concatenate Java Space to PDF Space transform transform.preConcatenate(pTransform); this.stroke = transformStroke(originalStroke); inText = false; } /** * Not implemented, as this is not supported in the PDF specification. * @param c1 Color to xor with */ public void setXORMode(Color c1) { } //============ Text operations ======================= /** * @see Graphics2D#shear(double, double) */ public void shear(double shx, double shy) { transform.shear(shx, shy); } /** * @see Graphics2D#transform(AffineTransform) */ public void transform(AffineTransform tx) { transform.concatenate(tx); this.stroke = transformStroke(originalStroke); } private Stroke transformStroke(Stroke stroke) { if (!(stroke instanceof BasicStroke)) return stroke; BasicStroke st = (BasicStroke)stroke; float scale = (float)Math.sqrt(Math.abs(transform.getDeterminant())); float dash[] = st.getDashArray(); if (dash != null) { for (int k = 0; k < dash.length; ++k) dash[k] *= scale; } return new BasicStroke(st.getLineWidth() * scale, st.getEndCap(), st.getLineJoin(), st.getMiterLimit(), dash, st.getDashPhase() * scale); } /** * @see Graphics2D#translate(double, double) */ public void translate(double tx, double ty) { transform.translate(tx, ty); trax = (float) tx; tray = (float) ty; } /** * @see Graphics#translate(int, int) */ public void translate(int x, int y) { translate((double)x, (double)y); } /** * Converts the Java space coordinates into pdf text space. * @param x coordinate * @param y coordinate * @param tx coordinate * @param ty coordinate * @return String containing the coordinates in PDF text space */ private String twh(float x,float y,float tx,float ty) { float nx=x, ny=y; float ntx=tx, nty=ty; /*int mh = (int) page.getPageFormat().getHeight(); int sx=1,sy=1;*/ Point2D ptSrc = new Point2D.Float(x, y); Point2D ptDst = new Point2D.Float(); transform.transform(ptSrc, ptDst); Point2D pt2Src = new Point2D.Float(tx, ty); Point2D pt2Dst = new Point2D.Float(); transform.transform(pt2Src, pt2Dst); /*ny = mh - y; nty = mh - ty; nx = sx*(nx-ntx); ny = sy*(ny-nty);*/ nx = (float) (ptDst.getX() - pt2Dst.getX()); ny = (float) (ptDst.getY() - pt2Dst.getY()); return ""+df.format(nx)+" "+df.format(ny)+" "; } /** * Converts the Java space coordinates into pdf text space. * @param x coordinate * @param y coordinate * @return String containing the coordinates in PDF text space */ private String txy(float x,float y) { // float nx=x, ny=y; // int mh = (int) page.getPageFormat().getHeight(); Point2D ptSrc = new Point2D.Float(x, y); Point2D ptDst = new Point2D.Float(); transform.transform(ptSrc, ptDst); // // handle any translations // x+=trax; // y+=tray; // // nx = x; // ny = mh - y; // // System.out.println("\ntxy(" + ptSrc.getX() + ", " + ptSrc.getY() + ")"); // System.out.println("Old [" + nx + "," + ny + "]"); // System.out.println("Trn [" + ptDst.getX() + ", " + ptDst.getY() + "]"); // // return ""+df.format(nx)+" "+df.format(ny)+" "; return ""+df.format(ptDst.getX())+" "+df.format(ptDst.getY())+" "; } private void realTransform(AffineTransform t) { inText = false; AffineTransform newTransform = new AffineTransform(t); newTransform.preConcatenate(pTransform); realTransform = newTransform; transform = new AffineTransform(pTransform); pw.println("" + df.format(newTransform.getScaleX()) + " " + "" + df.format(newTransform.getShearY()) + " " + "" + df.format(newTransform.getShearX()) + " " + "" + df.format(newTransform.getScaleY()) + " " + "" + df.format(newTransform.getTranslateX()) + " " + "" + df.format(newTransform.getTranslateY()) + " cm" ); } private void saveState() { pw.println("q"); } private void restoreState() { pw.println("Q"); } public void drawStringWithTransform(String s, float x, float y, AffineTransform trans) { saveState(); realTransform(trans); drawString(s, x, y); restoreState(); realTransform = null; } public void drawTransparentStringWithTransform(String s, float x, float y, AffineTransform trans) { saveState(); realTransform(trans); drawTransparentString(s, x, y); restoreState(); realTransform = null; } } // end class PDFGraphics \ No newline at end of file +/* * $Id: PDFGraphics.java,v 1.6 2007/09/22 12:58:40 gil1 Exp $ * * $Date: 2007/09/22 12:58:40 $ * * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package gnu.jpdf; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Composite; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Frame; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GraphicsConfiguration; import java.awt.Image; import java.awt.Paint; import java.awt.Polygon; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Shape; import java.awt.Stroke; import java.awt.RenderingHints.Key; import java.awt.font.FontRenderContext; import java.awt.font.GlyphVector; import java.awt.geom.AffineTransform; import java.awt.geom.Area; import java.awt.geom.NoninvertibleTransformException; import java.awt.geom.PathIterator; import java.awt.geom.Point2D; import java.awt.image.BufferedImage; import java.awt.image.BufferedImageOp; import java.awt.image.ColorModel; import java.awt.image.ImageObserver; import java.awt.image.RenderedImage; import java.awt.image.WritableRaster; import java.awt.image.renderable.RenderableImage; import java.awt.print.PageFormat; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.io.PrintWriter; import java.io.Serializable; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.util.Hashtable; import java.util.Locale; import java.util.Map; import java.util.WeakHashMap; /** * This class is our implementation of AWT's Graphics class. It provides a * Java standard way of rendering into a PDF Document's Page. * * @author Peter T Mount, http://www.retep.org.uk/pdf/ * @author Eric Z. Beard, ericzbeard@hotmail.com * @author Gilbert DeLeeuw, gil1@users.sourceforge.net * @version $Revision: 1.6 $, $Date: 2007/09/22 12:58:40 $ * @see gnu.jpdf.PDFGraphics */ public class PDFGraphics extends Graphics2D implements Serializable { /** * One degree in radians */ private static final double degrees_to_radians = Math.PI/180.0; private static final int FILL = 1; private static final int STROKE = 2; private static final int CLIP = 3; private static final AffineTransform IDENTITY = new AffineTransform(); private static final Stroke DEF_STROKE = new BasicStroke(); /* * NOTE: The original class is the work of Peter T. Mount, who released it * in the uk.org.retep.pdf package. It was modified by Eric Z. Beard as * follows: * The package name was changed to gnu.pdf. * The formatting was changed a little bit. * This used to subclass an abstract class in a different package with * the same name (confusing). Now it's one concrete class. * drawImage() was implemented * It is still licensed under the LGPL. */ // Implementation notes: // // Pages 333-335 of the PDF Reference Manual // // Unless absolutely required, use the moveto, lineto and rectangle // operators to perform those actions. // They contain some extra optimizations // which will reduce the output size by up to half in some cases. // // About fill operators: For correct operation, any fill operation should // start with closeBlock(), which will ensure any previous path is completed, // otherwise you may find the fill will include previous items private static final DecimalFormat df = new DecimalFormat("#.###", new DecimalFormatSymbols(Locale.ENGLISH)); //JPEXS: cache for already used images private static Map usedImages = new WeakHashMap(); private Color background; /** * This is true for any Graphics instance that didn't create the stream. * @see #create */ private boolean child; private Area clip; /** * This holds the current clipRectangle */ protected Rectangle clipRectangle; private Composite composite; private Graphics2D dg2 = new BufferedImage(2, 2, BufferedImage.TYPE_INT_RGB).createGraphics(); /** * This is the current font (in Java format) */ private Font font; /** * Part of the optimizer: * When true, we are drawing a path. */ private boolean inStroke; /** * Part of the optimizer: * When true, we are within a Text Block. */ private boolean inText; // true if within a Text Block - see newTextBlock() /** * The stroke line cap code; */ private int lineCap = 0; /** * The stroke line join code */ private int lineJoin = 0; /** * The stroke line width */ private float lineWidth = 1.0f; /** * Part of the optimizer: * The last known moveto/lineto x coordinate * @see #moveto * @see #lineto */ private float lx; // last known moveto/lineto coordinates /** * Part of the optimizer: * The last known moveto/lineto y coordinate * @see #moveto * @see #lineto */ private float ly; // last known moveto/lineto coordinates private float miterLimit = 10.0f; /** * Part of the optimizer: * When true, the font has changed. */ private boolean newFont; // true if the font changes - see newTextBlock() private Stroke originalStroke; // Original transform private AffineTransform oTransform; /** * This is a reference to the PDFPage we are rendering to. */ private PDFPage page; /** * This is the current pen/fill color */ private Paint paint; /** * This is the current font (in PDF format) */ private PDFFont pdffont; /** * Part of the optimizer: * This is written to the stream when the newPath() is called. np then clears * this value. */ private String pre_np; // PDF space transform private AffineTransform pTransform; /** * This is the PrintWriter used to write PDF drawing commands to the Stream */ private RawPrintWriter pw; /** * RenderingHints */ private RenderingHints rhints = new RenderingHints(null); private Stroke stroke; // Start of Graphics2D properties // Java space transform private AffineTransform transform; private AffineTransform realTransform; /** * This is used to translate coordinates */ protected float trax; /** * This is used to translate coordinates */ protected float tray; /** * Part of the optimizer: * The last x coordinate when rendering text */ private float tx; // the last coordinate for text rendering /** * Part of the optimizer: * The last y coordinate when rendering text */ private float ty; // the last coordinate for text rendering /** * @see Graphics2D#addRenderingHints(Map) */ public void addRenderingHints(Map hints) { rhints.putAll(hints); } /** * This produces an arc by breaking it down into one or more Bezier curves. * It is used internally to implement the drawArc and fillArc methods. * * @param axc X coordinate of arc centre * @param ayc Y coordinate of arc centre * @param width of bounding rectangle * @param height of bounding rectangle * @param ang1 Start angle * @param ang2 End angle * @param clockwise true to draw clockwise, false anti-clockwise */ public void arc(double axc,double ayc, double width,double height, double ang1,double ang2, boolean clockwise) { double adiff; double x0, y0; double x3r, y3r; boolean first = true; // may not need this //if( ar < 0 ) { //ang1 += fixed_180; //ang2 += fixed_180; //ar = - ar; //} double ang1r = (ang1%360.0)*degrees_to_radians; double sin0 = Math.sin(ang1r); double cos0 = Math.cos(ang1r); x0 = axc + width*cos0; y0 = ayc + height*sin0; // NB: !clockwise here as Java Space is inverted to User Space if( !clockwise ) { // Quadrant reduction while ( ang1 < ang2 ) ang2 -= 360.0; while ( (adiff = ang2 - ang1) < -90.0 ) { double w = sin0; sin0 = -cos0; cos0 = w; x3r = axc + width*cos0; y3r = ayc + height*sin0; arc_add(first, width, height, x0, y0, x3r, y3r, (x0 + width*cos0), (y0 + height*sin0) ); x0 = x3r; y0 = y3r; ang1 -= 90.0; first = false; } } else { // Quadrant reduction while ( ang2 < ang1 ) ang2 += 360.0; while ( (adiff = ang2 - ang1) > 90.0 ) { double w = cos0; cos0 = -sin0; sin0 = w; x3r = axc + width*cos0; y3r = ayc + height*sin0; arc_add(first, width, height, x0, y0, x3r, y3r, (x0 + width*cos0), (y0 + height*sin0) ); x0 = x3r; y0 = y3r; ang1 += 90.0; first = false; } } // Compute the intersection of the tangents. // We know that -fixed_90 <= adiff <= fixed_90. double trad = Math.tan(adiff * (degrees_to_radians / 2)); double ang2r = ang2 * degrees_to_radians; double xt = x0 - trad * width*sin0; double yt = y0 + trad * height*cos0; arc_add(first, width, height, x0, y0, (axc + width * Math.cos(ang2r)), (ayc + height * Math.sin(ang2r)), xt, yt); } /** * Used by the arc method to actually add an arc to the path * Important: We write directly to the stream here, because this method * operates in User space, rather than Java space. * @param first true if the first arc * @param w width * @param h height * @param x0 coordinate * @param y0 coordinate * @param x3 coordinate * @param y3 coordinate * @param xt coordinate * @param yt coordinate */ private void arc_add(boolean first, double w,double h, double x0,double y0, double x3,double y3, double xt,double yt) { double dx = xt - x0, dy = yt - y0; double dist = dx*dx + dy*dy; double w2 = w*w, h2=h*h; double r2 = w2+h2; double fw = 0.0, fh = 0.0; if(dist < (r2*1.0e8)) { // JM fw = (w2 != 0.0) ? ((4.0/3.0)/(1+Math.sqrt(1+dist/w2))) : 0.0; fh = (h2 != 0.0) ? ((4.0/3.0)/(1+Math.sqrt(1+dist/h2))) : 0.0; } // The path must have a starting point if(first) moveto(x0,y0); double x = x0+((xt-x0)*fw); double y = y0+((yt-y0)*fh); x0 = x3+((xt-x3)*fw); y0 = y3+((yt-y3)*fh); // Finally the actual curve. curveto(x,y,x0,y0,x3,y3); } /** * This simply draws a White Rectangle to clear the area * @param x coordinate * @param y coordinate * @param w width * @param h height */ public void clearRect(int x,int y,int w,int h) { closeBlock(); pw.print("q 1 1 1 RG ");// save state, set colour to White drawRect(x,y,w,h); closeBlock("B Q"); // close fill & stroke, then restore state } /** * @see Graphics2D#clip(Shape) */ public void clip(Shape s) { if (s == null) { setClip(null); return; } s = transform.createTransformedShape(s); if (clip == null) clip = new Area(s); else clip.intersect(new Area(s)); // followPath(s, CLIP); } /** * This extra method allows PDF users to clip to a Polygon. * *

In theory you could use setClip(), except that java.awt.Graphics * only supports Rectangle with that method, so we will have an extra * method. * @param p Polygon to clip to */ public void clipPolygon(Polygon p) { closeBlock(); // finish off any existing path polygon(p.xpoints,p.ypoints,p.npoints); closeBlock("W"); // clip to current path clipRectangle = p.getBounds(); } /** * Clips to a set of coordinates * @param x coordinate * @param y coordinate * @param w width * @param h height */ public void clipRect(int x,int y,int w,int h) { setClip(x,y,w,h); } /** * All functions should call this to close any existing optimized blocks. */ void closeBlock() { closeBlock("S"); } /** *

This is used by code that use the path in any way other than Stroke * (like Fill, close path & Stroke etc). Usually this is used internally.

* * @param code PDF operators that will close the path */ void closeBlock(String code) { if(inText) { pw.println("ET Q"); // setOrientation(); // fixes Orientation matrix } if(inStroke) { pw.println(code); } inStroke=inText=false; } /** * This is unsupported - how do you do this with Vector graphics? * @param x coordinate * @param y coordinate * @param w width * @param h height * @param dx coordinate * @param dy coordinate */ public void copyArea(int x,int y,int w,int h,int dx,int dy) { // Hmm... Probably need to keep track of everything // that has been drawn so far to get the contents of an area } //============ Line operations ======================= /** *

This returns a child instance of this Graphics object. As with AWT, the * affects of using the parent instance while the child exists, is not * determined.

* *

Once complete, the child should be released with it's dispose() * method which will restore the graphics state to it's parent.

* * @return Graphics object to render onto the page */ public Graphics create() { closeBlock(); PDFGraphics g = createGraphic(page,pw); // The new instance inherits a few items // g.media = new Rectangle(media); g.trax = trax; g.tray = tray; g.clipRectangle = new Rectangle(clipRectangle); return (Graphics) g; } // end create() /** * This method creates a new instance of the class based on the page * and a print writer. * * @param page the page to attach to * @param pw the PrintWriter to attach to. */ protected PDFGraphics createGraphic(PDFPage page, RawPrintWriter pw) { PDFGraphics g = new PDFGraphics(); g.init(page,pw); return g; } /** * This extension appends a Bezier curve to the path. The curve * extends from the current point to (x2,y2) using the current * point and (x1,y1) as the Bezier control points. *

The new current point is (x2,y2) * * @param x1 Second control point * @param y1 Second control point * @param x2 Destination point * @param y2 Destination point */ public void curveto(double x1,double y1,double x2,double y2) { newPath(); pw.println(cxy(x1,y1)+cxy(x2,y2)+"v"); lx=(float)x2; ly=(float)y2; } /** * This extension appends a Bezier curve to the path. The curve * extends from the current point to (x3,y3) using (x1,y1) and * (x2,y2) as the Bezier control points. *

The new current point is (x3,y3) * * @param x1 First control point * @param y1 First control point * @param x2 Second control point * @param y2 Second control point * @param x3 Destination point * @param y3 Destination point */ public void curveto(double x1,double y1,double x2,double y2,double x3,double y3) { newPath(); pw.println(cxy(x1,y1)+cxy(x2,y2)+cxy(x3,y3)+"c"); lx=(float)x3; ly=(float)y3; } /** * This extension appends a Bezier curve to the path. The curve * extends from the current point to (x2,y2) using the current * point and (x1,y1) as the Bezier control points. *

The new current point is (x2,y2) * * @param x1 Second control point * @param y1 Second control point * @param x2 Destination point * @param y2 Destination point */ public void curveto(int x1,int y1,int x2,int y2) { newPath(); pw.println(cxy(x1,y1)+cxy(x2,y2)+"v"); lx=x2; ly=y2; } /** * This extension appends a Bezier curve to the path. The curve * extends from the current point to (x3,y3) using (x1,y1) and * (x2,y2) as the Bezier control points. *

The new current point is (x3,y3) * * @param x1 First control point * @param y1 First control point * @param x2 Second control point * @param y2 Second control point * @param x3 Destination point * @param y3 Destination point */ public void curveto(int x1,int y1,int x2,int y2,int x3,int y3) { newPath(); pw.println(cxy(x1,y1)+cxy(x2,y2)+cxy(x3,y3)+"c"); lx=x3; ly=y3; } /** * This extension appends a Bezier curve to the path. The curve * extends from the current point to (x2,y2) using (x1,y1) and * the end point as the Bezier control points. *

The new current point is (x2,y2) * * @param x1 Second control point * @param y1 Second control point * @param x2 Destination point * @param y2 Destination point */ public void curveto2(double x1,double y1,double x2,double y2) { newPath(); pw.println(cxy(x1,y1)+cxy(x2,y2)+"y"); lx=(float)x2; ly=(float)y2; } // Arcs are horrible and complex. They are at the end of the // file, because they are the largest. This is because, unlike // Postscript, PDF doesn't have any arc operators, so we must // implement them by converting into one or more Bezier curves // (which is how Postscript does them internally). /** * This extension appends a Bezier curve to the path. The curve * extends from the current point to (x2,y2) using (x1,y1) and * the end point as the Bezier control points. *

The new current point is (x2,y2) * * @param x1 Second control point * @param y1 Second control point * @param x2 Destination point * @param y2 Destination point */ public void curveto2(int x1,int y1,int x2,int y2) { newPath(); pw.println(cxy(x1,y1)+cxy(x2,y2)+"y"); lx=x2; ly=y2; } /** * Converts the Java space dimension into pdf. * @param w width * @param h height * @return String containing the coordinates in PDF space */ private String cwh(double w,double h) { double nw=w,nh=h; // scratch // switch(mediaRot) { // case PageFormat.PORTRAIT: // Portrait //nw = w; nh = -h; // break; // // case PageFormat.LANDSCAPE: // // Landscape // nw = h; // nh = w; // break; // //// case 180: //// // Inverse Portrait //// nw = -w; //// //nh = h; //// break; // // case PageFormat.REVERSE_LANDSCAPE: // // Seascape // nw = -h; // nh = -w; // break; // } return ""+df.format(nw)+" "+df.format(nh)+" "; } /** * Converts the Java space dimension into pdf. * @param w width * @param h height * @return String containing the coordinates in PDF space */ private String cwh(int w,int h) { return cwh((double)w,(double)h); } /** * Converts the Java space coordinates into pdf. * @param x coordinate * @param y coordinate * @return String containing the coordinates in PDF space */ private String cxy(double x, double y) { // double nx = x, ny = y; // scratch // double mh = page.getPageFormat().getHeight(); Point2D ptSrc = new Point2D.Double(x, y); Point2D ptDst = new Point2D.Double(); transform.transform(ptSrc, ptDst); // x += trax; // y += tray; // // nx = x; // ny = mh - y; // // System.out.println("\ncxy(" + ptSrc.getX() + ", " + ptSrc.getY() + ")"); // System.out.println("Old [" + nx + "," + ny + "]"); // System.out.println("Trn [" + ptDst.getX() + ", " + ptDst.getY() + "]"); // // return "" + df.format(nx) + " " + df.format(ny) + " "; return ""+df.format(ptDst.getX())+" "+df.format(ptDst.getY())+" "; } /** * Converts the Java space coordinates into pdf. * @param x coordinate * @param y coordinate * @return String containing the coordinates in PDF space */ private String cxy(int x,int y) { return cxy((double)x,(double)y); } /** *

This releases any resources used by this Graphics object. You must use * this method once finished with it. Leaving it open will leave the PDF * stream in an inconsistent state, and will produce errors.

* *

If this was created with Graphics.create() then the parent instance * can be used again. If not, then this closes the graphics operations for * this page when used with PDFJob.

* *

When using PDFPage, you can create another fresh Graphics instance, * which will draw over this one.

* */ public void dispose() { closeBlock(); if(child) { pw.println("Q"); // restore graphics context } else { pw.close(); // close the stream if were the parent } } // ********************************************* // **** Implementation of java.awt.Graphics **** // ********************************************* //============ Rectangle operations ======================= /** * @see Graphics2D#draw(Shape) */ public void draw(Shape s) { followPath(s, STROKE); } /** *

Not implemented

* *

Draws a 3-D highlighted outline of the specified rectangle. * The edges of the rectangle are highlighted so that they appear * to be beveled and lit from the upper left corner. * The colors used for the highlighting effect are determined based on * the current color. The resulting rectangle covers an area that * is width + 1 pixels wide by height + 1 pixels tall. *

* * @param x an int value * @param y an int value * @param width an int value * @param height an int value * @param raised a boolean value */ public void draw3DRect(int x, int y, int width, int height, boolean raised) { // Not implemented } /** * Draws an arc * @param x coordinate * @param y coordinate * @param w width * @param h height * @param sa Start angle * @param aa End angle */ public void drawArc(int x,int y,int w,int h,int sa,int aa) { w=w>>1; h=h>>1; x+=w; y+=h; arc((double)x,(double)y, (double)w,(double)h, (double)-sa,(double)(-sa-aa), false); } /** *

Not implemented

* * @param data a byte[] value * @param offset an int value * @param length an int value * @param x an int value * @param y an int value */ public void drawBytes(byte[] data, int offset, int length, int x, int y) { } //============ Optimizers ======================= /** * @see Graphics2D#drawGlyphVector(GlyphVector, float, float) */ public void drawGlyphVector(GlyphVector g, float x, float y) { Shape s = g.getOutline(x, y); fill(s); } /** * @see Graphics2D#drawImage(BufferedImage, BufferedImageOp, int, int) */ public void drawImage(BufferedImage img, BufferedImageOp op, int x, int y) { BufferedImage result = img; if (op != null) { result = op.createCompatibleDestImage(img, img.getColorModel()); result = op.filter(img, result); } drawImage(result, x, y, null); } /** * @see Graphics2D#drawImage(Image, AffineTransform, ImageObserver) */ public boolean drawImage(Image img, AffineTransform xform, ImageObserver obs) { // return drawImage(img, null, xform, null, obs); return true; } /** *

Draw's an image onto the page, with a backing colour.

* * @param img The java.awt.Image * @param x coordinate on page * @param y coordinate on page * @param bgcolor Background colour * @param obs ImageObserver * @return true if drawn */ public boolean drawImage(Image img,int x,int y,Color bgcolor, ImageObserver obs) { return drawImage(img,x,y,img.getWidth(obs),img.getHeight(obs), bgcolor,obs); } /** * Draw's an image onto the page * @param img The java.awt.Image * @param x coordinate on page * @param y coordinate on page * @param obs ImageObserver * @return true if drawn */ public boolean drawImage(Image img,int x,int y,ImageObserver obs) { return drawImage(img,x,y,img.getWidth(obs),img.getHeight(obs),obs); } /** *

Draw's an image onto the page, with a backing colour.

* * @param img The java.awt.Image * @param x coordinate on page * @param y coordinate on page * @param w Width on page * @param h height on page * @param bgcolor Background colour * @param obs ImageObserver * @return true if drawn */ public boolean drawImage(Image img,int x,int y,int w,int h, Color bgcolor,ImageObserver obs) { closeBlock(); pw.print("q "); // save state Color c = getColor(); // save current colour setColor(bgcolor); // change the colour drawRect(x,y,w,h); closeBlock("B Q"); // fill stroke, restore state paint = c; // restore original colour return drawImage(img,x,y,img.getWidth(obs),img.getHeight(obs),obs); } /** *

Draws an image onto the page.

* *

This method is implemented with ASCIIbase85 encoding and the * zip stream deflater. It results in a stream that is anywhere * from 3 to 10 times as big as the image. This obviously needs some * improvement, but it works well for small images

* * @param img The java.awt.Image * @param x coordinate on page * @param y coordinate on page * @param w Width on page * @param h height on page * @param obs ImageObserver * @return true if drawn */ public boolean drawImage(Image img,int x,int y,int w,int h, ImageObserver obs) { closeBlock(); PDFImage image; if (usedImages.containsKey(img)) { image = usedImages.get(img); } else { image = new PDFImage(img, x, y, w, h, obs); // The image needs to be registered in several places page.getPDFDocument().setImageName(image); page.getPDFDocument().add(image); usedImages.put(img, image); } page.addToProcset("/ImageC"); page.addImageResource(image.getName() + " " + image.getSerialID() + " 0 R"); // JM /*page.addResource("/XObject << " + image.getName() + " " + image.getSerialID() + " 0 R >>");*/ // q w 0 0 h x y cm % the coordinate matrix pw.print("q " + w + " 0 0 " + h + " " + x + " " + ((int) page.getDimension().getHeight() - y - h) + " cm \n" + image.getName() + " Do\nQ\n"); return false; } /** * Draw's an image onto the page, with scaling *

This is not yet supported. * * @param img The java.awt.Image * @param dx1 coordinate on page * @param dy1 coordinate on page * @param dx2 coordinate on page * @param dy2 coordinate on page * @param sx1 coordinate on image * @param sy1 coordinate on image * @param sx2 coordinate on image * @param sy2 coordinate on image * @param bgcolor Background colour * @param obs ImageObserver * @return true if drawn */ public boolean drawImage(Image img,int dx1,int dy1,int dx2, int dy2,int sx1,int sy1,int sx2,int sy2, Color bgcolor,ImageObserver obs) { return false; } //============ Clipping operations ======================= /** * Draw's an image onto the page, with scaling *

This is not yet supported. * * @param img The java.awt.Image * @param dx1 coordinate on page * @param dy1 coordinate on page * @param dx2 coordinate on page * @param dy2 coordinate on page * @param sx1 coordinate on image * @param sy1 coordinate on image * @param sx2 coordinate on image * @param sy2 coordinate on image * @param obs ImageObserver * @return true if drawn */ public boolean drawImage(Image img,int dx1,int dy1,int dx2, int dy2,int sx1,int sy1,int sx2,int sy2, ImageObserver obs) { // This shouldn't be too bad, just change the coordinate matrix return false; } /** * Draws a line between two coordinates. * * If the first coordinate is the same as the last one drawn * (i.e. a previous drawLine, moveto, etc) it is ignored. * @param x1 coordinate * @param y1 coordinate * @param x2 coordinate * @param y2 coordinate */ public void drawLine(int x1,int y1,int x2,int y2) { moveto(x1,y1); lineto(x2,y2); } //============ Arcs operations ============================== // These are the standard Graphics operators. They use the // arc extension operators to achieve the affect. /** *

Draws an oval

* * @param x coordinate * @param y coordinate * @param w width * @param h height */ public void drawOval(int x,int y,int w,int h) { drawArc(x, y, w, h, 0, 360); } /** * Draws a polygon, linking the first and last coordinates. * @param xp Array of x coordinates * @param yp Array of y coordinates * @param np number of points in polygon */ public void drawPolygon(int[] xp,int[] yp,int np) { polygon(xp,yp,np); closeBlock("s"); // close path and stroke } /** * Draws a polyline. The first and last coordinates are not linked. * @param xp Array of x coordinates * @param yp Array of y coordinates * @param np number of points in polyline */ public void drawPolyline(int[] xp,int[] yp,int np) { polygon(xp,yp,np); // no stroke, as we keep the optimizer in stroke state } /** * We override Graphics.drawRect as it doesn't join the 4 lines. * Also, PDF provides us with a Rectangle operator, so we will use that. * @param x coordinate * @param y coordinate * @param w width * @param h height */ public void drawRect(int x,int y,int w,int h) { newPath(); pw.print(cxy(x,y)+cwh(w,h)+"re "); // rectangle lx=x; // I don't know if this is correct, but lets see if PDF ends ly=y; // the rectangle at it's start. // stroke (optimized) } /** * @see Graphics2D#drawRenderableImage(RenderableImage, AffineTransform) */ public void drawRenderableImage(RenderableImage img, AffineTransform xform) { drawRenderedImage(img.createDefaultRendering(), xform); } /** * @see Graphics2D#drawRenderedImage(RenderedImage, AffineTransform) */ public void drawRenderedImage(RenderedImage img, AffineTransform xform) { BufferedImage image = null; if (img instanceof BufferedImage) { image = (BufferedImage)img; } else { ColorModel cm = img.getColorModel(); int width = img.getWidth(); int height = img.getHeight(); WritableRaster raster = cm.createCompatibleWritableRaster(width, height); boolean isAlphaPremultiplied = cm.isAlphaPremultiplied(); Hashtable properties = new Hashtable(); String[] keys = img.getPropertyNames(); if (keys!=null) { for (int i = 0; i < keys.length; i++) { properties.put(keys[i], img.getProperty(keys[i])); } } BufferedImage result = new BufferedImage(cm, raster, isAlphaPremultiplied, properties); img.copyData(raster); image=result; } drawImage(image, xform, null); } /** * This is not yet implemented * * @param x coordinate * @param y coordinate * @param w width * @param h height * @param aw a-width * @param ah a-height */ public void drawRoundRect(int x,int y,int w,int h,int aw,int ah) { } //============ Oval operations ======================= /** * Draws a string using a AttributedCharacterIterator. *

This is not supported yet, as I have no idea what an * AttributedCharacterIterator is. *

This method is new to the Java2 API. */ public void drawString(java.text.AttributedCharacterIterator aci, float x,float y) { } /** * Draws a string using a AttributedCharacterIterator. *

This is not supported yet, as I have no idea what an * AttributedCharacterIterator is. *

This method is new to the Java2 API. */ public void drawString(java.text.AttributedCharacterIterator aci, int x,int y) { } public void drawString(String s,float x,float y) { newTextBlock(x, y); pw.print("[("); pw.printRaw(PDFStringHelper.makeRawPDFString(s)); pw.println(")] TJ"); closeBlock(); } /** * This draws a string. * * @oaran s String to draw * @param x coordinate * @param y coordinate */ public void drawString(String s,int x,int y) { drawString(s, (float) x, (float) y); } public void drawTransparentString(String s, float x, float y) { newTextBlock(x, y); pw.println("3 Tr"); pw.print("[("); pw.printRaw(PDFStringHelper.makeRawPDFString(s)); pw.println(")] TJ"); closeBlock(); } /** * This draws a transparent string. * * @oaran s String to draw * @param x coordinate * @param y coordinate */ public void drawTransparentString(String s, int x, int y) { drawTransparentString(s, (float) x, (float) y); } /** * @see Graphics2D#fill(Shape) */ public void fill(Shape s) { followPath(s, FILL); } /** *

Not implemented

* * @param x an int value * @param y an int value * @param width an int value * @param height an int value * @param raised a boolean value */ public void fill3DRect(int x, int y, int width, int height, boolean raised) { // Not implemented } /** * Fills an arc, joining the start and end coordinates * @param x coordinate * @param y coordinate * @param w width * @param h height * @param sa Start angle * @param aa End angle */ public void fillArc(int x,int y,int w,int h,int sa,int aa) { // here we fool the optimizer. We force any open path to be closed, // then draw the arc. Finally, as the optimizer hasn't stroke'd the // path, we close and fill it, and mark the Stroke as closed. // // Note: The lineto to the centre of the object is required, because // the fill only fills the arc. Skipping this includes an extra // chord, which isn't correct. Peter May 31 2000 closeBlock(); drawArc(x,y,w,h,sa,aa); lineto(x+(w>>1),y+(h>>1)); closeBlock("b"); // closepath and fill } //============ Extension operations ============================== // These are extensions, and provide access to PDF Specific // operators. /** *

Draws a filled oval

* * @param x coordinate * @param y coordinate * @param w width * @param h height */ public void fillOval(int x,int y,int w,int h) { fillArc(x, y, w, h, 0, 360); } //============ Polygon operations ======================= /** * Fills a polygon. * @param xp Array of x coordinates * @param yp Array of y coordinates * @param np number of points in polygon */ public void fillPolygon(int[] xp,int[] yp,int np) { closeBlock(); // finish off any previous paths polygon(xp,yp,np); closeBlock("b"); // closepath, fill and stroke } //============ Image operations ======================= /** * Fills a rectangle with the current colour * * @param x coordinate * @param y coordinate * @param w width * @param h height */ public void fillRect(int x,int y,int w,int h) { // end any path & stroke. This ensures the fill is on this // rectangle, and not on any previous graphics closeBlock(); drawRect(x,y,w,h); closeBlock("B"); // rectangle, fill stroke } //============ Round Rectangle operations ======================= /** * This is not yet implemented * * @param x coordinate * @param y coordinate * @param w width * @param h height * @param aw a-width * @param ah a-height */ public void fillRoundRect(int x,int y,int w,int h,int aw,int ah) { } /////////////////////////////////////////////// // // // implementation specific methods // // private void followPath(Shape s, int drawType) { PathIterator points; if (s==null) return; if (drawType==STROKE) { if (!(stroke instanceof BasicStroke)) { s = stroke.createStrokedShape(s); followPath(s, FILL); return; } } // if (drawType==STROKE) { // setStrokeDiff(stroke, oldStroke); // oldStroke = stroke; // setStrokePaint(); // } // else if (drawType==FILL) // setFillPaint(); points = s.getPathIterator(IDENTITY); int segments = 0; float[] coords = new float[6]; while(!points.isDone()) { segments++; int segtype = points.currentSegment(coords); switch(segtype) { case PathIterator.SEG_CLOSE: pw.print("h "); break; case PathIterator.SEG_CUBICTO: curveto(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]); break; case PathIterator.SEG_LINETO: lineto(coords[0], coords[1]); break; case PathIterator.SEG_MOVETO: moveto(coords[0], coords[1]); break; case PathIterator.SEG_QUADTO: curveto(coords[0], coords[1], coords[2], coords[3]); break; } points.next(); } switch (drawType) { case FILL: if (segments > 0) { if (points.getWindingRule() == PathIterator.WIND_EVEN_ODD) closeBlock("B*"); else closeBlock("B"); } break; case STROKE: if (segments > 0) closeBlock("S"); break; case CLIP: default: //drawType==CLIP if (segments == 0) drawRect(0, 0, 0, 0); if (points.getWindingRule() == PathIterator.WIND_EVEN_ODD) closeBlock("W*"); else closeBlock("W"); } } /** * @see Graphics2D#getBackground() */ public Color getBackground() { return background; } /** * Returns the Shape of the clipping region * As my JDK docs say, this may break with Java 2D. * @return Shape of the clipping region */ public Shape getClip() { return null; } /** * Returns the Rectangle that fits the current clipping region * @return the Rectangle that fits the current clipping region */ public Rectangle getClipBounds() { return clipRectangle; } //============ Color operations ======================= /** * Returns the current pen Colour * @return the current pen Colour */ public Color getColor() { return (paint instanceof Color) ? (Color) paint : Color.black; } /** * @see Graphics2D#getComposite() */ public Composite getComposite() { return composite; } /** * @see Graphics2D#getDeviceConfiguration() */ public GraphicsConfiguration getDeviceConfiguration() { return dg2.getDeviceConfiguration(); } /** * Return's the current font. * @return the current font. */ public Font getFont() { if(font==null) setFont(new Font("SansSerif",Font.PLAIN,12)); return font; } /** * Returns the FontMetrics for a font. *

This doesn't work correctly. Perhaps having some way of mapping * the base 14 fonts to our own FontMetrics implementation? * @param font The java.awt.Font to return the metrics for * @return FontMetrics for a font */ public FontMetrics getFontMetrics(Font font) { Frame dummy = new Frame(); dummy.addNotify(); Image image = dummy.createImage(100, 100); if (image == null) { System.err.println("getFontMetrics: image is null"); } Graphics graphics = image.getGraphics(); return graphics.getFontMetrics(font); } /** * @see Graphics2D#getFontRenderContext() */ public FontRenderContext getFontRenderContext() { boolean antialias = RenderingHints.VALUE_TEXT_ANTIALIAS_ON.equals(getRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING)); boolean fractions = RenderingHints.VALUE_FRACTIONALMETRICS_ON.equals(getRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS)); return new FontRenderContext(new AffineTransform(), antialias, fractions); } /** * Returns the associated PDFPage for this graphic * @return the associated PDFPage for this graphic */ public PDFPage getPage() { return page; } /** * Returns the current pen Colour * @return the current pen Colour */ public Paint getPaint() { return paint; } /** * @param arg0 a key * @return the rendering hint */ public Object getRenderingHint(Key arg0) { return rhints.get(arg0); } /** * @see Graphics2D#getRenderingHints() */ public RenderingHints getRenderingHints() { return rhints; } /** * @see Graphics2D#getStroke() */ public Stroke getStroke() { return originalStroke; } /** * @see Graphics2D#getTransform() */ public AffineTransform getTransform() { return new AffineTransform(oTransform); } /** * Returns the PrintWriter handling the underlying stream * @return the PrintWriter handling the underlying stream */ public RawPrintWriter getWriter() { return pw; } /** * @see Graphics2D#hit(Rectangle, Shape, boolean) */ public boolean hit(Rectangle rect, Shape s, boolean onStroke) { if (onStroke) { s = stroke.createStrokedShape(s); } s = transform.createTransformedShape(s); Area area = new Area(s); if (clip != null) area.intersect(clip); return area.intersects(rect.x, rect.y, rect.width, rect.height); } /** * This initialises the stream by saving the current graphics state, and * setting up the default line width (for us). * * It also sets up the instance ready for graphic operations and any * optimisations. * *

For child instances, the stream is already open, so this should keep * things happy. */ private void init() { PageFormat pf = page.getPageFormat(); // save graphics state (restored by dispose) if(child) { pw.print("q "); } // now initialise the instance //setColor(Color.black); paint = Color.black; // possible: if parent.color is not black, then force black? // must check to see what AWT does? // Original User Space Transform (identity) oTransform = new AffineTransform(); // Transform from Java Space to PDF Space pTransform = new AffineTransform(); pTransform.translate(0, pf.getHeight()); pTransform.scale(1d, -1d); // Combined Transform User->Java->PDF transform = new AffineTransform(oTransform); transform.preConcatenate(pTransform); // Set the line width setStroke(DEF_STROKE); } /** * This is called by PDFPage when creating a Graphcis instance. * @param page The PDFPage to draw onto. */ protected void init(PDFPage page) { this.page = page; // We are the parent instance child = false; // Now create a stream to store the graphics in PDFStream stream = new PDFStream(); // To view detail in uncompressed format comment out the next line stream.setDeflate(true); page.getPDFDocument().add(stream); page.add(stream); pw = new RawPrintWriter(stream.getOutputStream()); // initially, we are limited to the page size clipRectangle = page.getImageableArea(); // finally initialise the stream init(); } /** * This method is used internally by create() and by the PDFJob class * @param page PDFPage to draw into * @param pw PrintWriter to use */ protected void init(PDFPage page, RawPrintWriter pw) { this.page = page; this.pw = pw; // In this case, we didn't create the stream (our parent did) // so child is true (see dispose) child = true; // finally initialise the stream init(); } /** * This adds a line segment to the current path * @param x coordinate * @param y coordinate */ public void lineto(double x,double y) { newPath(); // no optimisation here as it may introduce errors on decimal coordinates. pw.print(cxy(x,y)+"l "); lx=(float)x; ly=(float)y; } /** * This adds a line segment to the current path * @param x coordinate * @param y coordinate */ public void lineto(int x,int y) { newPath(); if(lx!=x && ly!=y) pw.print(cxy(x,y)+"l "); lx=x; ly=y; } /** * This moves the current drawing point. * @param x coordinate * @param y coordinate */ public void moveto(double x,double y) { newPath(); // no optimisation here as it may introduce errors on decimal coordinates. pw.print(cxy(x,y)+"m "); lx=(float)x; ly=(float)y; } /** * This moves the current drawing point. * @param x coordinate * @param y coordinate */ public void moveto(int x,int y) { newPath(); if(lx!=x || ly!=y) pw.print(cxy(x,y)+"m "); lx=x; ly=y; } /** * Functions that draw lines should start by calling this. It starts a * new path unless inStroke is set, in that case it uses the existing path */ void newPath() { if(inText) { closeBlock(); } if(!inStroke) { if(pre_np!=null) { pw.print(pre_np); // this is the prefix set by setOrientation() pre_np = null; } pw.print("n "); } inText=false; inStroke=true; // an unlikely coordinate to fool the moveto() optimizer lx = ly = -9999; } /** *

Functions that draw text should start by calling this. It starts a text * block (accounting for media orientation) unless we are already in a Text * block.

* *

It also handles if the font has been changed since the current text * block was started, so your function will be current.

* * @param x x coordinate in java space * @param y y coordinate in java space */ void newTextBlock(float x,float y) { // close the current path if there is one if(inStroke) { closeBlock(); } // create the text block if one is not current. If we are, the newFont // condition at the end catches font changes if(!inText) { // This ensures that there is a font available getFont(); pw.print("q BT "); tx=ty=0; if (realTransform != null) { pw.println("" + df.format(pTransform.getScaleX()) + " " + "" + df.format(pTransform.getShearY()) + " " + "" + df.format(pTransform.getShearX()) + " " + "" + df.format(pTransform.getScaleY()) + " " + "" + df.format(pTransform.getTranslateX()) + " " + "" + df.format(pTransform.getTranslateY()) + " Tm" ); } // produce the text matrix for the media // switch(mediaRot) { // case PageFormat.PORTRAIT: // Portrait // //pw.println("1 0 0 1 0 0 Tm"); // break; // // case PageFormat.LANDSCAPE: // Landscape // pw.println("0 1 -1 0 0 0 Tm"); // rotate // break; // // case 180: // Inverted Portrait // pw.println("1 0 0 -1 0 0 Tm"); // break; // // case PageFormat.REVERSE_LANDSCAPE: // Seascape // pw.println("0 -1 1 0 0 0 Tm"); // rotate // break; // } // move the text cursor by an absolute amount pw.print(txy(x,y)+"Td "); } else { // move the text cursor by a relative amount pw.print(twh(x, y, tx, ty) + "Td "); //pw.print(txy(x,y)+"Td "); } // preserve the coordinates for the next time tx = x; ty = y; if(newFont || !inText) pw.print(pdffont.getName()+" "+font.getSize()+" Tf "); // later add colour changes here (if required) inStroke = newFont = false; inText = true; } /** * This is used to add a polygon to the current path. * Used by drawPolygon(), drawPolyline() and fillPolygon() etal * @param xp Array of x coordinates * @param yp Array of y coordinates * @param np number of points in polygon * @see #drawPolygon * @see #drawPolyline * @see #fillPolygon */ public void polygon(int[] xp,int[] yp,int np) { // newPath() not needed here as moveto does it ;-) moveto(xp[0],yp[0]); for(int i=1;iSets the clipping region to that of a Shape. * @param s Shape to clip to. */ public void setClip(Shape s) { Rectangle r = s.getBounds(); setClip(r.x,r.y,r.width,r.height); } /** * Sets the color for drawing * @param c Color to use */ public void setColor(Color c) { setPaint(c); } /** * @see Graphics2D#setComposite(Composite) */ public void setComposite(Composite comp) { this.composite = comp; } /** * This extension sets the line width to the default of 1mm which is what * Java uses when drawing to a PrintJob. */ public void setDefaultLineWidth() { closeBlock(); // draw any path before we change the line width pw.println("1 w"); } /** * This sets the font. * @param f java.awt.Font to set to. */ public void setFont(Font f) { // optimize: Save some space if the font is already the current one. if(font!=f) { font = f; pdffont = page.getFont("/Type1",f.getName(),f.getStyle()); // mark the font as changed newFont = true; } } public void setExistingTtfFont(Font f) { if (font != f) { font = f; pdffont = page.getFont("/TrueType", f.getName(), f.getStyle()); // mark the font as changed newFont = true; } } public void setTtfFont(Font f, File file) throws IOException { if (font != f) { font = f; pdffont = page.getEmbeddedFont(f.getName(), f.getStyle(), file); // mark the font as changed newFont = true; } } private void setLineCap(int cap) { int lineCap = 0; switch (cap) { case BasicStroke.JOIN_MITER: lineCap = 0; break; case BasicStroke.JOIN_ROUND: lineCap = 1; break; case BasicStroke.JOIN_BEVEL: lineCap = 2; break; } if (this.lineCap != lineCap) { closeBlock(); // draw any path before we change the line width this.lineCap = lineCap; pw.println(""+lineCap+" J"); } } private void setLineJoin(int join) { int lineJoin = 0; switch (join) { case BasicStroke.JOIN_MITER: lineJoin = 0; break; case BasicStroke.JOIN_ROUND: lineJoin = 1; break; case BasicStroke.JOIN_BEVEL: lineJoin = 2; break; } if (this.lineJoin != lineJoin) { closeBlock(); // draw any path before we change the line width this.lineJoin = lineJoin; pw.println(""+lineJoin+" j"); } } /** * This extension allows the width of the drawn line to be set * @param width Line width in pdf graphic units (points) */ public void setLineWidth(float width) { if (width != this.lineWidth) { closeBlock(); // draw any path before we change the line width this.lineWidth = width; pw.println(""+width+" w"); } } private void setMiterLimit(float limit) { if (limit != this.miterLimit) { closeBlock(); // draw any path before we change the line width this.miterLimit = limit; pw.println(""+limit+" M"); } } /** * Sets the paint for drawing * @param paint Paint to use */ public void setPaint(Paint paint) { this.paint = paint; if (paint instanceof Color) { Color c = (Color) paint; double r = ((double) c.getRed()) / 255.0; double g = ((double) c.getGreen()) / 255.0; double b = ((double) c.getBlue()) / 255.0; closeBlock(); // This ensures any paths are drawn in the previous // colours pw.println("" + r + " " + g + " " + b + " rg " + r + " " + g + " " + b + " RG"); } } /** * Not implemented, as this is not supported in the PDF specification. */ public void setPaintMode() { } /** * Sets a rendering hint * @param arg0 * @param arg1 */ public void setRenderingHint(Key arg0, Object arg1) { if (arg1 != null) { rhints.put(arg0, arg1); } else { rhints.remove(arg0); } } // Add Graphics2D methods. /** * @see Graphics2D#setRenderingHints(Map) */ public void setRenderingHints(Map hints) { rhints.clear(); rhints.putAll(hints); } /** * @see Graphics2D#setStroke(Stroke) */ public void setStroke(Stroke s) { originalStroke = s; this.stroke = transformStroke(s); if (stroke instanceof BasicStroke) { BasicStroke bs = (BasicStroke) stroke; setLineCap(bs.getEndCap()); setLineJoin(bs.getLineJoin()); setLineWidth(bs.getLineWidth()); setMiterLimit(bs.getMiterLimit()); // TODO: Line dash pattern } } /** * @see Graphics2D#setTransform(AffineTransform) */ public void setTransform(AffineTransform t) { // Save copy of original transform. oTransform = t; // Working copy of transform transform = new AffineTransform(t); // Concatenate Java Space to PDF Space transform transform.preConcatenate(pTransform); this.stroke = transformStroke(originalStroke); inText = false; } /** * Not implemented, as this is not supported in the PDF specification. * @param c1 Color to xor with */ public void setXORMode(Color c1) { } //============ Text operations ======================= /** * @see Graphics2D#shear(double, double) */ public void shear(double shx, double shy) { transform.shear(shx, shy); } /** * @see Graphics2D#transform(AffineTransform) */ public void transform(AffineTransform tx) { transform.concatenate(tx); this.stroke = transformStroke(originalStroke); } private Stroke transformStroke(Stroke stroke) { if (!(stroke instanceof BasicStroke)) return stroke; BasicStroke st = (BasicStroke)stroke; float scale = (float)Math.sqrt(Math.abs(transform.getDeterminant())); float dash[] = st.getDashArray(); if (dash != null) { for (int k = 0; k < dash.length; ++k) dash[k] *= scale; } return new BasicStroke(st.getLineWidth() * scale, st.getEndCap(), st.getLineJoin(), st.getMiterLimit(), dash, st.getDashPhase() * scale); } /** * @see Graphics2D#translate(double, double) */ public void translate(double tx, double ty) { transform.translate(tx, ty); trax = (float) tx; tray = (float) ty; } /** * @see Graphics#translate(int, int) */ public void translate(int x, int y) { translate((double)x, (double)y); } /** * Converts the Java space coordinates into pdf text space. * @param x coordinate * @param y coordinate * @param tx coordinate * @param ty coordinate * @return String containing the coordinates in PDF text space */ private String twh(float x,float y,float tx,float ty) { float nx=x, ny=y; float ntx=tx, nty=ty; /*int mh = (int) page.getPageFormat().getHeight(); int sx=1,sy=1;*/ Point2D ptSrc = new Point2D.Float(x, y); Point2D ptDst = new Point2D.Float(); transform.transform(ptSrc, ptDst); Point2D pt2Src = new Point2D.Float(tx, ty); Point2D pt2Dst = new Point2D.Float(); transform.transform(pt2Src, pt2Dst); /*ny = mh - y; nty = mh - ty; nx = sx*(nx-ntx); ny = sy*(ny-nty);*/ nx = (float) (ptDst.getX() - pt2Dst.getX()); ny = (float) (ptDst.getY() - pt2Dst.getY()); return ""+df.format(nx)+" "+df.format(ny)+" "; } /** * Converts the Java space coordinates into pdf text space. * @param x coordinate * @param y coordinate * @return String containing the coordinates in PDF text space */ private String txy(float x,float y) { // float nx=x, ny=y; // int mh = (int) page.getPageFormat().getHeight(); Point2D ptSrc = new Point2D.Float(x, y); Point2D ptDst = new Point2D.Float(); transform.transform(ptSrc, ptDst); // // handle any translations // x+=trax; // y+=tray; // // nx = x; // ny = mh - y; // // System.out.println("\ntxy(" + ptSrc.getX() + ", " + ptSrc.getY() + ")"); // System.out.println("Old [" + nx + "," + ny + "]"); // System.out.println("Trn [" + ptDst.getX() + ", " + ptDst.getY() + "]"); // // return ""+df.format(nx)+" "+df.format(ny)+" "; return ""+df.format(ptDst.getX())+" "+df.format(ptDst.getY())+" "; } private void realTransform(AffineTransform t) { inText = false; AffineTransform newTransform = new AffineTransform(t); newTransform.preConcatenate(pTransform); realTransform = newTransform; transform = new AffineTransform(pTransform); pw.println("" + df.format(newTransform.getScaleX()) + " " + "" + df.format(newTransform.getShearY()) + " " + "" + df.format(newTransform.getShearX()) + " " + "" + df.format(newTransform.getScaleY()) + " " + "" + df.format(newTransform.getTranslateX()) + " " + "" + df.format(newTransform.getTranslateY()) + " cm" ); } private void saveState() { pw.println("q"); } private void restoreState() { pw.println("Q"); } public void drawStringWithTransform(String s, float x, float y, AffineTransform trans) { saveState(); realTransform(trans); drawString(s, x, y); restoreState(); realTransform = null; } public void drawTransparentStringWithTransform(String s, float x, float y, AffineTransform trans) { saveState(); realTransform(trans); drawTransparentString(s, x, y); restoreState(); realTransform = null; } } // end class PDFGraphics \ No newline at end of file diff --git a/libsrc/gnujpdf/src/gnu/jpdf/PDFJob.java b/libsrc/gnujpdf/src/gnu/jpdf/PDFJob.java index 82646814c..1696429e7 100644 --- a/libsrc/gnujpdf/src/gnu/jpdf/PDFJob.java +++ b/libsrc/gnujpdf/src/gnu/jpdf/PDFJob.java @@ -362,7 +362,7 @@ public class PDFJob extends PrintJob implements Serializable /** * This is used by our version of create() */ - graphic(PDFPage page,PDFJob job,PrintWriter pw) { + graphic(PDFPage page, PDFJob job, RawPrintWriter pw) { super(); this.init(page,pw); this.job = job; diff --git a/libsrc/gnujpdf/src/gnu/jpdf/PDFStringHelper.java b/libsrc/gnujpdf/src/gnu/jpdf/PDFStringHelper.java index 1d6b14987..25b80b514 100644 --- a/libsrc/gnujpdf/src/gnu/jpdf/PDFStringHelper.java +++ b/libsrc/gnujpdf/src/gnu/jpdf/PDFStringHelper.java @@ -24,6 +24,12 @@ package gnu.jpdf; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + /** * String manipulation methods * @@ -47,7 +53,37 @@ public class PDFStringHelper s = replace(s,")","\\)"); return "("+s+")"; - } + } + + public static byte[] makeRawPDFString(String s) { + try { + byte[] utf16be = s.getBytes("UTF-16BE"); + List bytes = new ArrayList<>(); + for (int i = 0; i < utf16be.length; i += 2) { + if (utf16be[i] == 0 && utf16be[i + 1] == '(') { + bytes.add((byte) 0); + bytes.add((byte) '\\'); + bytes.add((byte) '('); + } else if (utf16be[i] == 0 && utf16be[i + 1] == ')') { + bytes.add((byte) 0); + bytes.add((byte) '\\'); + bytes.add((byte) ')'); + } else { + bytes.add(utf16be[i]); + bytes.add(utf16be[i + 1]); + } + } + byte[] ret = new byte[bytes.size()]; + for (int i = 0; i < bytes.size(); i++) { + ret[i] = bytes.get(i); + } + return ret; + + } catch (UnsupportedEncodingException ex) { + Logger.getLogger(PDFStringHelper.class.getName()).log(Level.SEVERE, null, ex); + } + return null; + } /** * Helper method for toString() diff --git a/libsrc/gnujpdf/src/gnu/jpdf/RawPrintWriter.java b/libsrc/gnujpdf/src/gnu/jpdf/RawPrintWriter.java new file mode 100644 index 000000000..35bd3f807 --- /dev/null +++ b/libsrc/gnujpdf/src/gnu/jpdf/RawPrintWriter.java @@ -0,0 +1,54 @@ +package gnu.jpdf; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.io.Writer; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + * @author JPEXS + */ +public class RawPrintWriter { + + private final OutputStream os; + + public RawPrintWriter(OutputStream os) { + this.os = os; + } + + public void print(String s) { + try { + os.write(s.getBytes()); + } catch (IOException ex) { + Logger.getLogger(RawPrintWriter.class.getName()).log(Level.SEVERE, null, ex); + } + } + + public void printRaw(byte[] data) { + try { + os.write(data); + } catch (IOException ex) { + Logger.getLogger(RawPrintWriter.class.getName()).log(Level.SEVERE, null, ex); + } + } + + public void println(String s) { + try { + os.write((s + "\n").getBytes()); + } catch (IOException ex) { + Logger.getLogger(RawPrintWriter.class.getName()).log(Level.SEVERE, null, ex); + } + } + + public void close() { + try { + os.close(); + } catch (IOException ex) { + Logger.getLogger(RawPrintWriter.class.getName()).log(Level.SEVERE, null, ex); + } + } + +} diff --git a/libsrc/gnujpdf/src/gnu/jpdf/TtfParser.java b/libsrc/gnujpdf/src/gnu/jpdf/TtfParser.java index 80498a9fe..0fbdee710 100644 --- a/libsrc/gnujpdf/src/gnu/jpdf/TtfParser.java +++ b/libsrc/gnujpdf/src/gnu/jpdf/TtfParser.java @@ -7,6 +7,7 @@ import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TreeMap; @@ -17,68 +18,160 @@ import java.util.TreeMap; */ public class TtfParser { - private int size = -1; - - - private long headOffset = -1; - - private long os2Offset = -1; - private long hheaOffset = -1; - private long hmtxOffset = -1; - private long cmapOffset = -1; - - private Font font; - private int ascent; private int descent; - private int capHeight; + private int leading; + private int maxWidth; private float scale; - private int numOfHMetrics; + private int numberOfHMetrics; - private List advanceWidths = new ArrayList<>(); - private Map cmap = new TreeMap<>(); + private Map cw = new TreeMap<>(); + private Map ctg = new TreeMap<>(); + private Map cbbox = new HashMap<>(); - private int firstChar = -1; - private int lastChar = -1; + private MyRect bbox; - public void loadFromTTF(File file, int size) throws IOException, FontFormatException { - font = Font.createFont(Font.TRUETYPE_FONT, file); + private int indexToLocFormat; + + private int flags; + + private String name; + + private float italicAngle; + private int underlinePosition; + private int underlineThickness; + + private int numGlyphs; + private int xHeight; + private int capHeight; + private int missingWidth; + + private int avgWidth; + private int stemV; + private int stemH; + + private int dw; + + Map indexToLoc = new HashMap<>(); + + Map tableOffsets = new HashMap<>(); + Map tableLengths = new HashMap<>(); + + private byte[] cidtogidmap = new byte[131072]; + + + public void loadFromTTF(File file) throws IOException, FontFormatException { RandomAccessFile input = new RandomAccessFile(file, "r"); - this.size = size; - if (input == null) { - throw new IllegalArgumentException("input cannot be null."); - } readTableDirectory(input); - if (headOffset == -1) { - throw new IOException("HEAD table not found."); - } readHEAD(input); readOS2(input); readHHEA(input); - readHMTX(input); readCMAP(input); + readLOCA(input); + readNAME(input); + readPOST(input); + readMAXP(input); + readGLYF(input); + readHMTX(input); + + if (missingWidth > 0) { + dw = missingWidth; + } else { + dw = avgWidth; + } + + for (int cid : ctg.keySet()) { + int gid = ctg.get(cid); + if ((gid >= 0) && (gid <= 0xFFFF) && (gid >= 0)) { + if (gid > 0xFFFF) { + gid -= 0x10000; + } + cidtogidmap[(cid * 2)] = (byte) (gid >> 8); + cidtogidmap[((cid * 2) + 1)] = (byte) (gid & 0xFF); + } + } input.close(); } - public int getFirstChar() { - return firstChar; + public byte[] getCidtogidmap() { + return cidtogidmap; } - public int getLastChar() { - return lastChar; + public int getAvgWidth() { + return avgWidth; } + public MyRect getBbox() { + return bbox; + } - public List getAdvanceWidths() { - return advanceWidths; + public Map getCbbox() { + return cbbox; + } + + public Map getCw() { + return cw; } public Map getCmap() { - return cmap; + return ctg; } + public int getLeading() { + return leading; + } + + public int getMaxWidth() { + return maxWidth; + } + + public Map getCtg() { + return ctg; + } + + public int getFlags() { + return flags; + } + + public String getName() { + return name; + } + + public float getItalicAngle() { + return italicAngle; + } + + public float getUnderlinePosition() { + return underlinePosition; + } + + public float getUnderlineThickness() { + return underlineThickness; + } + + public int getxHeight() { + return xHeight; + } + + public int getMissingWidth() { + return missingWidth; + } + + public int getStemV() { + return stemV; + } + + public int getStemH() { + return stemH; + } + + public int getDw() { + return dw; + } + + private void readTableDirectory(RandomAccessFile input) throws IOException { skip(input, 4); int tableCount = readUnsignedShort(input); @@ -96,27 +189,15 @@ public class TtfParser { long length = readUnsignedLong(input); prevOffset = offset; String tag = new String(tagBytes, "ISO-8859-1"); - //System.err.println("tag " + tag + ", length: " + length); - if (tag.equals("head")) { - headOffset = offset; - } - if (tag.equals("OS/2")) { - os2Offset = offset; - } - if (tag.equals("hhea")) { - hheaOffset = offset; - } - if (tag.equals("hmtx")) { - hmtxOffset = offset; - } - if (tag.equals("cmap")) { - cmapOffset = offset; - } + + tableOffsets.put(tag, offset); + tableLengths.put(tag, length); + //System.err.println("tag " + tag + ", length: " + length); } } private void readCMAP(RandomAccessFile input) throws IOException { - seek(input, cmapOffset); + seek(input, tableOffsets.get("cmap")); int version = readUnsignedShort(input); int numberSubtables = readUnsignedShort(input); @@ -124,7 +205,7 @@ public class TtfParser { int platFormId = readUnsignedShort(input); int platFormSpecificId = readUnsignedShort(input); long offset = readUnsignedLong(input); - seek(input, cmapOffset + offset); + seek(input, tableOffsets.get("cmap") + offset); int format = readUnsignedShort(input); switch (format) { @@ -142,7 +223,6 @@ public class TtfParser { List idDeltas = new ArrayList<>(); List idRangeOffsets = new ArrayList<>(); - for (int j = 0; j < segCount; j++) { int endCode = readUnsignedShort(input); endCodes.add(endCode); @@ -163,9 +243,8 @@ public class TtfParser { List glyphIndices = new ArrayList<>(); long startA = input.getFilePointer(); - ; long a = startA; - for (; a < cmapOffset + offset + length; a += 2) { + for (; a < tableOffsets.get("cmap") + offset + length; a += 2) { int glyphIndex = readUnsignedShort(input); glyphIndices.add(glyphIndex); } @@ -177,53 +256,196 @@ public class TtfParser { } if (idRangeOffsets.get(j) == 0) { int glyph = (idDeltas.get(j) + k) % 65536; - cmap.put(k, glyph); + ctg.put(k, glyph); } else { int glyphIndex = (idRangeOffsets.get(j) - 2 * (segCount - j)) / 2 + (k - startCodes.get(j)); int glyph = glyphIndices.get(glyphIndex); - cmap.put(k, glyph); + ctg.put(k, glyph); } } } break; } - + break; } - //System.err.println("format=" + format); + if (!ctg.containsKey(0)) { + ctg.put(0, 0); + } + } private void readHMTX(RandomAccessFile input) throws IOException { - seek(input, hmtxOffset); - for (int i = 0; i < numOfHMetrics; i++) { + seek(input, tableOffsets.get("hmtx")); + List cw = new ArrayList<>(); + for (int i = 0; i < numberOfHMetrics; i++) { int advanceWidth = readUnsignedShort(input); - advanceWidths.add((int) (scale * advanceWidth)); - int leftSideBearing = readUnsignedShort(input); + cw.add(Math.round(scale * advanceWidth)); + skip(input, 2); //skip leftSideBearing + } + if (numberOfHMetrics < numGlyphs) { + for (int i = numberOfHMetrics; i < numGlyphs; i++) { + cw.add(cw.get(numberOfHMetrics - 1)); + } + } + missingWidth = cw.get(0); + for (int cid = 0; cid <= 65535; cid++) { + if (ctg.containsKey(cid)) { + if (ctg.get(cid) < cw.size() && ctg.get(cid) >= 0) { + this.cw.put(cid, cw.get(ctg.get(cid))); + } + } } } private void readHHEA(RandomAccessFile input) throws IOException { - seek(input, hheaOffset + 4); - ascent = (int) (scale * readShort(input)); - descent = (int) (scale * readShort(input)); - capHeight = ascent; - seek(input, hheaOffset + 34); - numOfHMetrics = readUnsignedShort(input); + seek(input, tableOffsets.get("hhea")); + skip(input, 4); // skip Table version number + ascent = Math.round(scale * readShort(input)); + descent = Math.round(scale * readShort(input)); + leading = Math.round(scale * readShort(input)); + maxWidth = Math.round(scale * readShort(input)); + skip(input, 22); + numberOfHMetrics = readUnsignedShort(input); + } + private void readHEAD(RandomAccessFile input) throws IOException { - seek(input, headOffset + 2 * 4 + 2 * 4 + 2); + seek(input, tableOffsets.get("head")); + skip(input, 2 * 4 + 2 * 4 + 2); int unitsPerEm = readUnsignedShort(input); - scale = (float) size / (float) unitsPerEm; + scale = (float) 1000 / (float) unitsPerEm; + + skip(input, 16); + int xMin = Math.round(scale * readShort(input)); + int yMin = Math.round(scale * readShort(input)); + int xMax = Math.round(scale * readShort(input)); + int yMax = Math.round(scale * readShort(input)); + bbox = new MyRect(xMin, yMin, xMax, yMax); + + flags = 32; + int macStyle = readUnsignedShort(input); + + if ((macStyle & 2) == 2) { + flags |= 64; + } + + seek(input, tableOffsets.get("head") + 50); + indexToLocFormat = readShort(input); + } + + private void readLOCA(RandomAccessFile input) throws IOException { + seek(input, tableOffsets.get("loca")); + + boolean shortOffset = indexToLocFormat == 0; + + if (shortOffset) { + int totNumGlyphs = (int) (tableLengths.get("local") / 2); // numGlyphs + 1 + for (int i = 0; i < totNumGlyphs; i++) { + indexToLoc.put(i, (long) readUnsignedShort(input) * 2); + if (indexToLoc.containsKey(i - 1) && ((long) indexToLoc.get(i) == (long) indexToLoc.get(i - 1))) { + // the last glyph didn't have an outline + indexToLoc.remove(i - 1); + } + } + } else { + int totNumGlyphs = (int) Math.floor(tableLengths.get("loca") / 4); // numGlyphs + 1 + for (int i = 0; i < totNumGlyphs; i++) { + indexToLoc.put(i, readUnsignedLong(input)); + if (indexToLoc.containsKey(i - 1) && ((long) indexToLoc.get(i) == (long) indexToLoc.get(i - 1))) { + // the last glyph didn't have an outline + indexToLoc.remove(i - 1); + } + } + } } private void readOS2(RandomAccessFile input) throws IOException { - seek(input, os2Offset + 68); - //ascent = readShort(input); - //descent = readShort(input); - seek(input, os2Offset + 88); - //capHeight = readShort(input); + seek(input, tableOffsets.get("OS/2")); + skip(input, 2); + avgWidth = Math.round(readShort(input) * scale); + float usWeightClass = readUnsignedShort(input) * scale; + stemV = Math.round((70 * usWeightClass) / 400); + stemH = Math.round((30 * usWeightClass) / 400); + skip(input, 2); //usWidthClass + int fsType = readShort(input); + if (fsType == 2) { + //This Font cannot be modified, embedded or exchanged in any manner without first obtaining permission of the legal owner. + } + } + + private void readNAME(RandomAccessFile input) throws IOException { + seek(input, tableOffsets.get("name")); + skip(input, 2); + int numNameRecords = readUnsignedShort(input); + int stringStorageOffset = readUnsignedShort(input); + for (int i = 0; i < numNameRecords; i++) { + skip(input, 6); //skip Platform ID, Platform-specific encoding ID, Language ID. + int nameID = readUnsignedShort(input); + if (nameID == 6) { + int stringLength = readUnsignedShort(input); + int stringOffset = readUnsignedShort(input); + long offset = tableOffsets.get("name") + stringStorageOffset + stringOffset; + byte data[] = new byte[stringLength]; + seek(input, offset); + input.read(data); + name = new String(data); + name = name.replaceAll("[^a-zA-Z0-9_\\-]", ""); + } else { + skip(input, 4);// skip String length, String offset + } + } + } + + private void readPOST(RandomAccessFile input) throws IOException { + seek(input, tableOffsets.get("post")); + skip(input, 4); // skip Format Type + italicAngle = readFixed(input); + underlinePosition = Math.round(readShort(input) * scale); + underlineThickness = Math.round(readShort(input) * scale); + boolean isFixedPitch = readUnsignedLong(input) == 0 ? false : true; + if (isFixedPitch) { + flags |= 1; + } + } + + private void readMAXP(RandomAccessFile input) throws IOException { + seek(input, tableOffsets.get("maxp")); + skip(input, 4); //skip Table version number + numGlyphs = readUnsignedShort(input); + + } + + private void readGLYF(RandomAccessFile input) throws IOException { + if (ctg.containsKey(120)) { + seek(input, tableOffsets.get("glyf") + indexToLoc.get(ctg.get(120)) + 4); + int yMin = readShort(input); + skip(input, 2); + int yMax = readShort(input); + xHeight = Math.round((yMax - yMin) * scale); + } + if (ctg.containsKey(72)) { + seek(input, tableOffsets.get("glyf") + indexToLoc.get(ctg.get(72)) + 4); + int yMin = readShort(input); + skip(input, 2); + int yMax = readShort(input); + capHeight = Math.round((yMax - yMin) * scale); + } + + for (int cid = 0; cid <= 65535; cid++) { + if (ctg.containsKey(cid)) { + if (indexToLoc.containsKey(ctg.get(cid))) { + seek(input, tableOffsets.get("glyf") + indexToLoc.get(ctg.get(cid))); + int xMin = readShort(input); + int yMin = readShort(input); + int xMax = readShort(input); + int yMax = readShort(input); + cbbox.put(cid, new MyRect(xMin, yMin, xMax, yMax)); + } + } + } } public int getAscent() { @@ -238,6 +460,11 @@ public class TtfParser { return capHeight; } + private float readFixed(RandomAccessFile input) throws IOException { + int m = readShort(input); + int f = readUnsignedShort(input); + return Float.parseFloat("" + m + "." + f); + } private int readUnsignedByte(RandomAccessFile input) throws IOException { int b = input.read();