// Copyright 2001, FreeHEP. package org.freehep.postscript; import java.awt.Font; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.io.IOException; /** * Font Operators for PostScript Processor * * @author Mark Donszelmann * @version $Id: FontOperator.java 10178 2006-12-08 09:03:07Z duns $ */ public class FontOperator extends PSOperator { protected static FontCache fontCache; static { fontCache = new FontCache(); // add standard 14 entries (PDF like) // QUESTION: should we point to Lucida Fonts! fontCache.put("Courier", fontCache.get("Monospaced.plain")); fontCache.put("Courier-Bold", fontCache.get("Monospaced.bold")); fontCache.put("Courier-Oblique", fontCache.get("Monospaced.italic")); fontCache.put("Courier-BoldOblique", fontCache.get("Monospaced.bolditalic")); fontCache.put("Helvetica", fontCache.get("SansSerif.plain")); fontCache.put("Helvetica-Bold", fontCache.get("SansSerif.bold")); fontCache.put("Helvetica-Oblique", fontCache.get("SansSerif.italic")); fontCache.put("Helvetica-BoldOblique", fontCache.get("SansSerif.bolditalic")); fontCache.put("Times-Roman", fontCache.get("Serif.plain")); fontCache.put("Times-Bold", fontCache.get("Serif.bold")); fontCache.put("Times-Italic", fontCache.get("Serif.italic")); fontCache.put("Times-BoldItalic", fontCache.get("Serif.bolditalic")); // FIXME: windows specific, does not work, handled in font selection // fontCache.put("ZapfDingbats", fontCache.get("Wingdings")); // fontCache.put("Symbol", fontCache.get("Symbol")); /* // add extra entries for testing (FIXME: this should all be read from a file) fontCache.put("Palatino-Roman", fontCache.get("Lucida Bright-Regular")); fontCache.put("Palatino-Italic", fontCache.get("Lucida Bright-Italic")); fontCache.put("Palatino-Bold", fontCache.get("Lucida Bright-Demibold")); fontCache.put("Palatino-BoldItalic", fontCache.get("Lucida Bright-DemiboldItalic")); fontCache.put("Optima", fontCache.get("Lucida Sans-Regular")); // No Oblique anymore (after 1.3?) fontCache.put("Optima-Oblique", fontCache.get("Lucida Sans-Regular")); fontCache.put("Optima-Bold", fontCache.get("Lucida Sans-Demibold")); // No Oblique anymore (after 1.3?) fontCache.put("Optima-BoldOblique", fontCache.get("Lucida Sans-Demibold")); fontCache.put("ZapfChancery-MediumItalic",fontCache.get("Lucida Bright-DemiboldItalic")); */ } public static Class[] operators = { DefineFont.class, ComposeFont.class, UndefineFont.class, FindFont.class, ScaleFont.class, MakeFont.class, SetFont.class, RootFont.class, CurrentFont.class, SelectFont.class, Show.class, AShow.class, WidthShow.class, AWidthShow.class, XShow.class, XYShow.class, YShow.class, GlyphShow.class, StringWidth.class, CShow.class, KShow.class, FindEncoding.class, SetCacheDevice.class, SetCacheDevice2.class, SetCharWidth.class }; protected PSDictionary findFont(PSDictionary fontDirectory, PSName key) { PSDictionary font = (PSDictionary)fontDirectory.get(key); if (font == null) { String fontName = key.getValue(); String encoding = "STDLatin"; if (fontName.equals("Symbol")) { encoding = "Symbol"; fontName = "SansSerif.plain"; // 31 chars missing, mainly math } else if (fontName.equals("ZapfDingbats")) { encoding = "Zapfdingbats"; fontName = "SansSerif.plain"; } Font javaFont = fontCache.get(fontName); font = new PSFontDictionary(javaFont, encoding); } return font; } protected void defineFont(PSDictionary fontDirectory, PSName key, PSDictionary font) { // FREEHEP-149: no check done on the font font.put("FID", new PSFontID()); font.changeAccess(PSDictionary.READ_ONLY); fontDirectory.put(key, font); } protected PSDictionary makeFont(PSDictionary font, double[] matrix) { PSJavaFont psfont = (PSJavaFont)font.get("javafont"); if (psfont == null) { double[] cfm = font.getPackedArray("FontMatrix").toDoubles(); AffineTransform at = new AffineTransform(cfm); at.concatenate(new AffineTransform(matrix)); at.getMatrix(cfm); PSDictionary fontCopy = (PSDictionary)font.copy(); fontCopy.put("FontMatrix", new PSArray(cfm)); return fontCopy; } else { String encoding = font.getString("javaEncoding"); Font javaFont = psfont.getFont(); AffineTransform at = new AffineTransform(matrix); at.concatenate(javaFont.getTransform()); javaFont = javaFont.deriveFont(at); return new PSFontDictionary(javaFont, encoding); } } protected void setFont(OperandStack os, PSDictionary font) { // FIXME: is this valid for type 1 fonts? font.put("_CachedGlyphs", new PSDictionary()); os.gstate().setFont(font); } protected PSGlyph getCachedGlyph(PSDictionary font, PSName name) { PSDictionary cache = font.getDictionary("_CachedGlyphs"); if (cache == null) { cache = new PSDictionary(); font.put("_CachedGlyphs", cache); } PSGlyph glyph = (PSGlyph)cache.get(name); if (glyph == null) { glyph = new PSGlyph(); cache.put(name, glyph); } font.put("_CurrentGlyph", glyph); return glyph; } protected void show(OperandStack os, int cc) { show(os, cc, null); } protected void show(OperandStack os, int cc, PSName name) { PSGState gs = os.gstate(); PSDictionary font = gs.font(); show(os, gs, font, cc, name); } // CHECK: this static? prevents 2 ps processors running at the same time. // maybe should move to the graphics state... protected PSGlyph currentGlyph; protected void show(OperandStack os, PSGState gs, PSDictionary font, int cc, PSName name) { int type = font.getInteger("FontType"); PSPackedArray encoding = font.getPackedArray("Encoding"); if (name == null) { if (type == 0) { name = new PSName(".notdef"); } else { name = encoding.getName(cc); } } // System.out.println("Switched to Font "+font.get("FontName")+" "+type+" "+name); currentGlyph = getCachedGlyph(font, name); switch(type) { case 0: // Composed fonts // FIXME: nested Type 0 fonts not handled PSPackedArray fontList = font.getPackedArray("FDepVector"); PSDictionary currentFont = font.getDictionary("_CurrentFont"); switch (font.getInteger("FMapType")) { case 2: // 8/8 Mapping if ((currentFont == null) || (font.getBoolean("_ChangeFont"))) { currentFont = setCurrentFont(cc, font, encoding, fontList); return; } show(os, gs, currentFont, cc, null); font.put("_ChangeFont", true); break; case 3: // Escape Mapping if (currentFont == null) { currentFont = setCurrentFont(0, font, encoding, fontList); } if (font.getBoolean("_ChangeFont")) { currentFont = setCurrentFont(cc, font, encoding, fontList); return; } if (cc == font.getInteger("EscChar")) { font.put("_ChangeFont", true); return; } show(os, gs, currentFont, cc, null); break; case 4: // 1/7 Mapping currentFont = setCurrentFont((cc >> 7) & 0x01 , font, encoding, fontList); show(os, gs, currentFont, cc & 0x7f, null); break; case 5: // 9/7 Mapping if ((currentFont == null) || (!font.getBoolean("_ChangeFont"))) { font.put("_FontOffset", cc << 1); font.put("_ChangeFont", true); return; } int fontNumber = font.getInteger("_FontOffset"); fontNumber += ((cc >> 7) & 0x01); currentFont = setCurrentFont(fontNumber, font, encoding, fontList); show(os, gs, currentFont, cc & 0x7f, null); break; case 6: // SubsVector Mapping System.out.println("Type 0 font with SubsVector Mapping ignored."); break; case 7: // Double Escape Mapping if (currentFont == null) { currentFont = setCurrentFont(0, font, encoding, fontList); } if (font.getBoolean("_ChangeFont")) { if (cc == font.getInteger("EscChar")) { font.put("_FontOffset", 256); } else { cc += font.getInteger("_FontOffset"); currentFont = setCurrentFont(cc, font, encoding, fontList); } return; } if (cc == font.getInteger("EscChar")) { font.put("_ChangeFont", true); return; } show(os, gs, currentFont, cc, null); break; case 8: // Shift Mapping if (currentFont == null) { currentFont = setCurrentFont(0, font, encoding, fontList); if (font.get("ShiftIn") == null) font.put("ShiftIn", 15); if (font.get("ShiftOut") == null) font.put("ShiftOut", 14); } if (cc == font.getInteger("ShiftIn")) { currentFont = setCurrentFont(0, font, encoding, fontList); return; } else if (cc == font.getInteger("ShiftOut")) { currentFont = setCurrentFont(1, font, encoding, fontList); return; } show(os, gs, currentFont, cc, null); break; case 9: // CMap Mapping System.out.println("Type 0 font with CMap Mapping ignored."); break; default: System.out.println("Type 0 font with invalid FMapType "+font.getInteger("FMapType")+" ignored."); break; } break; case 1: // Quasi Type 1 Font, FIXME... for caching... PSDictionary charstrings = font.getDictionary("CharStrings"); PSObject obj = charstrings.get(name); if (obj instanceof PSGlyph) { show(os, gs, (PSGlyph)obj); } else if (obj instanceof PSPackedArray) { // FIXME does not handle FontMatrix // FIXME does only work for glyphshow, refer page 352 os.push(os.dictStack().systemDictionary()); os.push(font); os.push(name); // FIXME should be CC for a show! os.execStack().push(obj); // FIXME: we should pop the 2 dictionaries, but how? } else if (obj instanceof PSString) { PSCharStringDecoder decoder = new PSCharStringDecoder(os.dictStack().systemDictionary()); try { PSGlyph glyph = decoder.decode((PSString)obj); charstrings.put(name, glyph); show(os, gs, glyph); } catch (IOException e) { System.err.println("IOError while reading charstring '" + name + "'."); } } else { System.out.println("Show Ignored "+obj); } break; case 3: // Type 3 Font PSPackedArray proc = font.getPackedArray("BuildGlyph"); if (proc != null) { // BuildGlyph os.push(font); os.push(name); } else { // BuildChar proc = font.getPackedArray("BuildChar"); os.push(font); os.push(cc); } os.execStack().push(new GRestore()); os.execStack().push(proc); os.execStack().push(new GSave()); break; default: os.execStack().pop(); error(os, new InvalidFont()); break; } } private void show(OperandStack os, PSGState gs, PSGlyph g) { currentGlyph.wy = g.wy; currentGlyph.wx = g.wx; currentGlyph.llx = g.llx; currentGlyph.urx = g.urx; if (g instanceof PSJavaGlyph) { // FIXME: no idea why the 0.5 factor is here // seems like the lsb given by Java is not // really accurate, especially with derived (small) fonts gs.show(((PSJavaGlyph)g).getGlyph(), (float)(currentGlyph.getLSB()*0.5), 0); } else { // Embedded type1 font procedure os.execStack().push(new GRestore()); os.execStack().push(((PSType1Glyph)g).getProc()); os.execStack().push(new GSave()); } } private PSDictionary setCurrentFont(int fontNumber, PSDictionary type0font, PSPackedArray encoding, PSPackedArray fontList) { int fontIndex = encoding.getInteger(fontNumber); PSDictionary currentFont = fontList.getDictionary(fontIndex); type0font.put("_CurrentFont", currentFont); type0font.put("_FontOffset", 0); type0font.put("_ChangeFont", false); return currentFont; } public static float stringWidth(OperandStack os, int cc) { PSGState gs = os.gstate(); PSDictionary font = gs.font(); // FIXME: only works for type 1 and 3 PSName name; switch (font.getInteger("FontType")) { case 1: case 3: name = font.getPackedArray("Encoding").getName(cc); break; default: name = new PSName(".notdef"); break; } return stringWidth(font, name); } protected static float stringWidth(PSDictionary font, PSName name) { PSDictionary metrics = font.getDictionary("Metrics"); // FIXME: not correct if (metrics == null) return 0.0f; PSObject obj = metrics.get(name); // FIXME: obj may be an array of 2 or 4 float width = ((PSNumber)obj).getFloat(); // System.out.println(name+" "+width); return width; } public boolean execute(OperandStack os) { throw new RuntimeException("Cannot execute class: "+getClass()); } } class DefineFont extends FontOperator { { operandTypes = new Class[] {PSName.class, PSDictionary.class}; } public boolean execute(OperandStack os) { PSDictionary font = os.popDictionary(); PSName name = os.popName(); defineFont(os.dictStack().fontDirectory(), name, font); os.push(font); return true; } } class ComposeFont extends FontOperator { { operandTypes = new Class[] {PSName.class, PSObject.class, PSPackedArray.class}; } public boolean execute(OperandStack os) { // Level 3 error(os, new Unimplemented()); return true; } } class UndefineFont extends FontOperator { { operandTypes = new Class[] {PSName.class}; } public boolean execute(OperandStack os) { PSName key = os.popName(); os.dictStack().fontDirectory().remove(key); return true; } } class FindFont extends FontOperator { { operandTypes = new Class[] {PSName.class}; } public boolean execute(OperandStack os) { PSName name = os.popName(); PSDictionary font = findFont(os.dictStack().fontDirectory(), name); if (font == null) { error(os, new InvalidFont()); } else { defineFont(os.dictStack().fontDirectory(), name, font); os.push(font); } return true; } } class ScaleFont extends FontOperator { { operandTypes = new Class[] {PSDictionary.class, PSNumber.class}; } public boolean execute(OperandStack os) { double scale = os.popNumber().getDouble(); PSDictionary font = os.popDictionary(); os.push(makeFont(font, new double[] {scale, 0, 0, scale, 0, 0})); return true; } } class MakeFont extends FontOperator { { operandTypes = new Class[] {PSDictionary.class, PSPackedArray.class}; } public boolean execute(OperandStack os) { PSPackedArray matrix = os.popPackedArray(); PSDictionary font = os.popDictionary(); os.push(makeFont(font, matrix.toDoubles())); return true; } } class SetFont extends FontOperator { { operandTypes = new Class[] {PSDictionary.class}; } public boolean execute(OperandStack os) { PSDictionary font = os.popDictionary(); setFont(os, font); return true; } } class RootFont extends FontOperator { public boolean execute(OperandStack os) { // FIXME: wrong for type 0 font os.push(os.gstate().font()); return true; } } class CurrentFont extends FontOperator { public boolean execute(OperandStack os) { os.push(os.gstate().font()); return true; } } class SelectFont extends FontOperator { { operandTypes = new Class[] {PSName.class, PSObject.class}; } public boolean execute(OperandStack os) { PSName key; PSPackedArray matrix; if (os.checkType(PSName.class, PSNumber.class)) { double scale = os.popNumber().getDouble(); matrix = new PSPackedArray(new double[] {scale, 0, 0, scale, 0, 0}); key = os.popName(); } else if (os.checkType(PSName.class, PSPackedArray.class)) { matrix = os.popPackedArray(); key = os.popName(); } else { error(os, new TypeCheck()); return true; } PSDictionary font = findFont(os.dictStack().fontDirectory(), key); if (font == null) { error(os, new InvalidFont()); return true; } defineFont(os.dictStack().fontDirectory(), key, font); // FIXME: should be a copy //font = makeFont((PSDictionary)font.copy(), matrix.toDoubles()); font = makeFont(font, matrix.toDoubles()); setFont(os, font); return true; } } class Show extends FontOperator { private String text; private int index; private double sx, sy; public Show() { } public Show(String t, double sx, double sy) { text = t; index = -1; this.sx = sx; this.sy = sy; } public boolean execute(OperandStack os) { PSGState gs = os.gstate(); if (text == null) { if (!os.checkType(PSString.class)) { error(os, new TypeCheck()); return true; } Point2D point = gs.position(); if (point == null) { error(os, new NoCurrentPoint()); return true; } float x0 = (float)point.getX(); float y0 = (float)point.getY(); String t = os.popString().getValue(); double[] cfm = gs.font().getPackedArray("FontMatrix").toDoubles(); AffineTransform at = new AffineTransform(cfm[0], cfm[1], cfm[2], cfm[3], x0, y0); double sx = at.getScaleX(); double sy = at.getScaleY(); os.execStack().pop(); os.execStack().push(new Show(t, sx, sy)); os.gsave(); gs.transform(at); return false; } if (index >= 0) { // System.out.println("Width "+currentGlyph.wx+" "+currentGlyph.getLSB()+" "+currentGlyph.getRSB()+" "+gs.font().get("FontName")); gs.translate(currentGlyph.wx, currentGlyph.wy); } index++; if (index >= text.length()) { Point2D t = gs.position(); os.grestore(); Point2D p = os.gstate().position(); os.gstate().path().moveTo((float)(p.getX()-(t.getX()*sx)), (float)(p.getY()-(t.getY()*sy))); return true; } int ch = text.charAt(index); show(os, ch); return false; } } class AShow extends FontOperator { private String text; private int index; private double ax, ay; public AShow() { } public AShow(String t, double xa, double ya) { text = t; ax = xa; ay = ya; index = 0; } public boolean execute(OperandStack os) { PSGState gs = os.gstate(); if (text == null) { if (!os.checkType(PSNumber.class, PSNumber.class, PSString.class)) { error(os, new TypeCheck()); return true; } Point2D point = gs.position(); if (point == null) { error(os, new NoCurrentPoint()); return true; } float x0 = (float)point.getX(); float y0 = (float)point.getY(); String t = os.popString().getValue(); double ya = os.popNumber().getDouble(); double xa = os.popNumber().getDouble(); os.execStack().pop(); os.execStack().push(new AShow(t, ya, xa)); os.gsave(); double[] cfm = gs.font().getPackedArray("FontMatrix").toDoubles(); AffineTransform at = new AffineTransform(cfm[0], cfm[1], cfm[2], cfm[3], x0, y0); gs.transform(at); return false; } if (index >= 0) { os.gstate().translate(currentGlyph.wx + ax, currentGlyph.wy + ay); } index++; if (index >= text.length()) { os.grestore(); return true; } int ch = text.charAt(index); show(os, ch); return false; } } class WidthShow extends FontOperator { private String text; private int index; private int chd; private double cx, cy; public WidthShow(String t, int c, double xc, double yc) { text = t; index = -1; chd = c; cx = xc; cy = yc; } public WidthShow() { } public boolean execute(OperandStack os) { PSGState gs = os.gstate(); if (text == null) { if (!os.checkType(PSNumber.class, PSNumber.class, PSInteger.class, PSString.class)) { error(os, new TypeCheck()); return true; } Point2D point = gs.position(); if (point == null) { error(os, new NoCurrentPoint()); return true; } float x0 = (float)point.getX(); float y0 = (float)point.getY(); String t = os.popString().getValue(); int c = os.popInteger().getValue(); double yc = os.popNumber().getDouble(); double xc = os.popNumber().getDouble(); os.execStack().pop(); os.execStack().push(new WidthShow(t, c, xc, yc)); os.gsave(); double[] cfm = gs.font().getPackedArray("FontMatrix").toDoubles(); AffineTransform at = new AffineTransform(cfm[0], cfm[1], cfm[2], cfm[3], x0, y0); gs.transform(at); return false; } if (index >= 0) { double wx = currentGlyph.wx; double wy = currentGlyph.wy; if (text.charAt(index) == chd) { wx += cx; wy += cy; } os.gstate().translate(wx, wy); } index++; if (index >= text.length()) { os.grestore(); return true; } int ch = text.charAt(index); show(os, ch); return false; } } class AWidthShow extends FontOperator { private String text; private int index; private int chd; private double ax, ay; private double cx, cy; public AWidthShow(String t, int c, double xc, double yc, double xa, double ya) { text = t; index = -1; chd = c; cx = xc; cy = yc; ax = xa; ay = ya; } public AWidthShow() { } public boolean execute(OperandStack os) { PSGState gs = os.gstate(); if (text == null) { if (!os.checkType(new Class[] {PSNumber.class, PSNumber.class, PSInteger.class, PSNumber.class, PSNumber.class, PSString.class})) { error(os, new TypeCheck()); return true; } Point2D point = gs.position(); if (point == null) { error(os, new NoCurrentPoint()); return true; } float x0 = (float)point.getX(); float y0 = (float)point.getY(); String t = os.popString().getValue(); double ay = os.popNumber().getDouble(); double ax = os.popNumber().getDouble(); int c = os.popInteger().getValue(); double yc = os.popNumber().getDouble(); double xc = os.popNumber().getDouble(); os.execStack().pop(); os.execStack().push(new AWidthShow(t, c, xc, yc, ax, ay)); os.gsave(); double[] cfm = gs.font().getPackedArray("FontMatrix").toDoubles(); AffineTransform at = new AffineTransform(cfm[0], cfm[1], cfm[2], cfm[3], x0, y0); gs.transform(at); return false; } if (index >= 0) { double wx = currentGlyph.wx + ax; double wy = currentGlyph.wy + ay; if (text.charAt(index) == chd) { wx += cx; wy += cy; } os.gstate().translate(wx, wy); } index++; if (index >= text.length()) { os.grestore(); return true; } int ch = text.charAt(index); show(os, ch); return false; } } class XShow extends FontOperator { private String text; private int index; private float[] offset; public XShow() { } public XShow(String t, float[] o) { text = t; offset = o; index = -1; } public boolean execute(OperandStack os) { PSGState gs = os.gstate(); if (text == null) { if (os.checkType(PSString.class, PSPackedArray.class)) { Point2D point = gs.position(); if (point == null) { error(os, new NoCurrentPoint()); return true; } float x0 = (float)point.getX(); float y0 = (float)point.getY(); float[] o = os.popPackedArray().toFloats(); String t = os.popString().getValue(); if (text.length() > offset.length) { error(os, new RangeCheck()); return true; } os.execStack().pop(); os.execStack().push(new XShow(t, o)); os.gsave(); double[] cfm = gs.font().getPackedArray("FontMatrix").toDoubles(); AffineTransform at = new AffineTransform(cfm[0], cfm[1], cfm[2], cfm[3], x0, y0); gs.transform(at); return false; } else if (os.checkType(PSString.class, PSString.class)) { error(os, new Unimplemented()); return true; } else { error(os, new TypeCheck()); return true; } } if (index >= 0) { os.gstate().translate(offset[index], 0); } index++; if (index >= text.length()) { os.grestore(); return true; } int ch = text.charAt(index); show(os, ch); return false; } } class XYShow extends FontOperator { private String text; private int index; private float[] offset; public XYShow() { } public XYShow(String t, float[] o) { text = t; offset = o; index = -1; } public boolean execute(OperandStack os) { PSGState gs = os.gstate(); if (text == null) { if (os.checkType(PSString.class, PSPackedArray.class)) { Point2D point = gs.position(); if (point == null) { error(os, new NoCurrentPoint()); return true; } float x0 = (float)point.getX(); float y0 = (float)point.getY(); float[] o = os.popPackedArray().toFloats(); String t = os.popString().getValue(); if (text.length()*2 > offset.length) { error(os, new RangeCheck()); return true; } os.execStack().pop(); os.execStack().push(new XYShow(t, o)); os.gsave(); double[] cfm = gs.font().getPackedArray("FontMatrix").toDoubles(); AffineTransform at = new AffineTransform(cfm[0], cfm[1], cfm[2], cfm[3], x0, y0); gs.transform(at); return false; } else if (os.checkType(PSString.class, PSString.class)) { error(os, new Unimplemented()); return true; } else { error(os, new TypeCheck()); return true; } } if (index >= 0) { os.gstate().translate(offset[index*2], offset[index*2+1]); } index++; if (index >= text.length()) { os.grestore(); return true; } int ch = text.charAt(index); show(os, ch); return false; } } class YShow extends FontOperator { private String text; private int index; private float[] offset; public YShow() { } public YShow(String t, float[] o) { text = t; offset = o; index = -1; } public boolean execute(OperandStack os) { PSGState gs = os.gstate(); if (text == null) { if (os.checkType(PSString.class, PSPackedArray.class)) { Point2D point = gs.position(); if (point == null) { error(os, new NoCurrentPoint()); return true; } float x0 = (float)point.getX(); float y0 = (float)point.getY(); float[] o = os.popPackedArray().toFloats(); String t = os.popString().getValue(); if (text.length() > offset.length) { error(os, new RangeCheck()); return true; } os.execStack().pop(); os.execStack().push(new YShow(t, o)); os.gsave(); double[] cfm = gs.font().getPackedArray("FontMatrix").toDoubles(); AffineTransform at = new AffineTransform(cfm[0], cfm[1], cfm[2], cfm[3], x0, y0); gs.transform(at); return false; } else if (os.checkType(PSString.class, PSString.class)) { error(os, new Unimplemented()); return true; } else { error(os, new TypeCheck()); return true; } } if (index >= 0) { os.gstate().translate(0, offset[index]); } index++; if (index >= text.length()) { os.grestore(); return true; } int ch = text.charAt(index); show(os, ch); return false; } } class GlyphShow extends FontOperator { { operandTypes = new Class[] {PSObject.class}; } public boolean execute(OperandStack os) { if (os.checkType(PSName.class)) { PSName name = os.popName(); Point2D point = os.gstate().position(); if (point == null) { error(os, new NoCurrentPoint()); return true; } // FIXME for positioning and matrix // pop myself, replace it with a char proc, or just show, // but do not pop again os.execStack().pop(); show(os, 0, name); return false; } else if (os.checkType(PSInteger.class)) { error(os, new Unimplemented()); } else { error(os, new TypeCheck()); } return true; } } class StringWidth extends FontOperator { { operandTypes = new Class[] {PSString.class}; } public boolean execute(OperandStack os) { // FIXME: cannot calculate stringwidth of a type 3 font... // since we would need some kind of inactive graphics state // so that the BuildGlyph procedure can be executed // without doing the actual drawing... double width = 0; String text = os.popString().getValue(); for (int i=0; i<text.length(); i++) { width += stringWidth(os, text.charAt(i)); } os.push(width); os.push(0.0); return true; } } class CShow extends FontOperator { { operandTypes = new Class[] {PSPackedArray.class, PSString.class}; } public boolean execute(OperandStack os) { // FIXME error(os, new Unimplemented()); return true; } } class KShow extends FontOperator { private int index; private String text; private PSPackedArray proc; private boolean skip; private KShow(PSPackedArray p, String t) { proc = p; text = t; index = -1; skip = true; } public KShow() { } public boolean execute(OperandStack os) { PSGState gs = os.gstate(); if (proc == null) { if (!os.checkType(PSPackedArray.class, PSString.class)) { error(os, new TypeCheck()); return true; } Point2D point = gs.position(); if (point == null) { error(os, new NoCurrentPoint()); return true; } float x0 = (float)point.getX(); float y0 = (float)point.getY(); String t = os.popString().getValue(); PSPackedArray p = os.popPackedArray(); os.execStack().pop(); os.execStack().push(new KShow(p, t)); os.gsave(); double[] cfm = gs.font().getPackedArray("FontMatrix").toDoubles(); AffineTransform at = new AffineTransform(cfm[0], cfm[1], cfm[2], cfm[3], x0, y0); gs.transform(at); return false; } if (!skip) { double wx = currentGlyph.wx; double wy = currentGlyph.wy; os.gstate().translate(wx, wy); if (index < text.length()-1) { int c0 = (int)text.charAt(index); int c1 = (int)text.charAt(index+1); os.push(c0); os.push(c1); os.execStack().push(proc); skip = true; return false; } } index++; if (index >= text.length()) { os.grestore(); return true; } int ch = text.charAt(index); show(os, ch); skip = false; return false; } } class FindEncoding extends FontOperator { { operandTypes = new Class[] {PSName.class}; } public boolean execute(OperandStack os) { PSName key = os.popName(); PSObject object = os.dictStack().lookup(key); if ((object == null) || !(object instanceof PSPackedArray)) { error(os, new UndefinedResource()); } os.push(object); return true; } } class SetCacheDevice extends FontOperator { { operandTypes = new Class[] {PSNumber.class, PSNumber.class, PSNumber.class, PSNumber.class, PSNumber.class, PSNumber.class}; } public boolean execute(OperandStack os) { PSGlyph g = (PSGlyph)os.gstate().font().get("_CurrentGlyph"); g.ury = os.popNumber().getDouble(); g.urx = os.popNumber().getDouble(); g.lly = os.popNumber().getDouble(); g.llx = os.popNumber().getDouble(); g.wy = os.popNumber().getDouble(); g.wx = os.popNumber().getDouble(); return true; } } class SetCacheDevice2 extends FontOperator { { operandTypes = new Class[] {PSNumber.class, PSNumber.class, PSNumber.class, PSNumber.class, PSNumber.class, PSNumber.class, PSNumber.class, PSNumber.class, PSNumber.class, PSNumber.class}; } public boolean execute(OperandStack os) { PSGlyph g = (PSGlyph)os.gstate().font().get("_CurrentGlyph"); // FIXME error(os, new Unimplemented()); return true; } } class SetCharWidth extends FontOperator { { operandTypes = new Class[] {PSNumber.class, PSNumber.class}; } public boolean execute(OperandStack os) { PSGlyph g = (PSGlyph)os.gstate().font().get("_CurrentGlyph"); g.wy = os.popNumber().getDouble(); g.wx = os.popNumber().getDouble(); return true; } }