[prev in list] [next in list] [prev in thread] [next in thread] 

List:       openjdk-2d-dev
Subject:    [OpenJDK 2D-Dev] [PATCH] JDK-4627340 : RFE: A way to improve text printing performance for postscrip
From:       Alex Geller <ag () 4js ! com>
Date:       2014-01-17 15:46:29
Message-ID: 52D95055.7060807 () 4js ! com
[Download RAW message or body]

Hi,
in my quest looking for a sponsor and based on the encouraging comments, 
here is an improved implementation of the proof of concept made in my 
previous post.

The improvements are as follows:
1) The text width is now correct also for non factional metrics. It is 
possible to visually verify that in the test output of the attached 
program PSTest.java by comparing text length with a horizontal ruler 
that is drawn on top. The length of the ruler is computed using 
Font.getStringBounds()).
In the case of antaliasing or non fractional metrics , and only in these 
cases, the width is adjusted to the requested width as follows:
If the string can be displayed using the Postscipt "show" instruction 
then the text adjustment is computed in Postscript using "stringwidth" 
and "ashow" by the same Postscript procedure "S" that is used for 
printer fonts.
If the string has to be displayed using the Postscript "glyphshow" 
instruction because it contains one or more characters that are not in 
the basic 256 characters set, then the adjustment value ax is computed 
in the Java code and "ax 0 rmoveto" commands are placed between the 
"glyphshow" commands used to display the string. Moving this computation 
into the Postscript document is also feasible.
2) Ligatures like Lam-Alif (\u0644\u0627) are now correctly  handled 
(should see the ligature  \ufefb). This is achieved by returning false 
when such characters are detected in the string to render. In that case 
the string is rendered as before the patch was introduced.
This is the area on which I have the most doubt. The implementation can 
currently only render strings where there is a one to one relationship 
between characters and glyphs and so the function should return false 
for all other strings.
The implementation of this test is most likely oversimplified (Strings 
containing Arabic, Thai and some other character ranges are excluded 
from the algorithm).
A very expensive but probably safe method would be to render the text 
using TextLayout and comparing the resulting glyph vector to the result 
of Font.drawGlyphVector(). If the two are identical then the string can 
be rendered.
We could of course be conservative and just allow the Latin1 character 
set as the current implementation does for printer fonts but that would 
be a shame since I think that it renders eastern European text as well 
as Chinese text just fine.

I incidentally discovered a beneficial side effect of using embedded 
fonts over glyph vectors which is the fact that text is preserved in the 
resulting documents so that for example the Ghostscript tool ps2txt will 
yield the text of the document. Similarly ps2pdf will yield compact PDF 
documents from which the text can be copied in the user interface (e.g. 
Acroread). Currently this is not working for characters that are not in 
the base set. If this should work for all characters then we probably 
have to move to a CID-keyed font which also exists in the Type 3 flavor 
but which has a more complex encoding structure and may not lend itself 
to incremental updates.

The program PSTest can be invoked with the following parameters (The 
parameters are expected at fixed positions).
java PSTest font-name font-name-for-chinese-text rendering-method 
render-into-buffered-image use-fractional-metrics
where "rendering-method" is "DrawString" or "DrawGlyphVector", 
"render-into-buffered-image" is a Boolean (0/1) that makes the program 
render into a 300 DPI image for reference and "use-factional-metrics" is 
a Boolean that controls whether or not the FRACTIONAL_METRICS rendering 
hint is set.
The program always produces a Postscript file called "out.ps". The 
settings by which a document was created are written as text at the 
bottom of each page of the document.
Regards,
Alex
PS: @2d-dev-bounces: This replaces the message that bounced earlier 
today because the attachments exceeded the limit.


["openjdk1.patch" (text/plain)]

diff -rupN /tmp/openjdk/jdk/src/share/classes/sun/print/PSPathGraphics.java \
/home/alex/openjdk_7_b147_jun_11/openjdk/jdk/src/share/classes/sun/print/PSPathGraphics.java
                
--- /tmp/openjdk/jdk/src/share/classes/sun/print/PSPathGraphics.java	2011-06-27 \
                19:38:47.000000000 +0200
+++ /home/alex/openjdk_7_b147_jun_11/openjdk/jdk/src/share/classes/sun/print/PSPathGraphics.java	2014-01-16 \
12:22:08.000000000 +0100 @@ -228,6 +228,10 @@ class PSPathGraphics extends \
PathGraphic  drawnWithPS = psPrinterJob.textOut(this, str,
                                                    x+translateX, y+translateY,
                                                    font, frc, w);
+                if(!drawnWithPS)
+                    drawnWithPS=psPrinterJob.textOutType3(this,str,
+                                                   x+translateX,y+translateY,
+                                                   font, frc, w);
             }
         }
 
diff -rupN /tmp/openjdk/jdk/src/share/classes/sun/print/PSPrinterJob.java \
/home/alex/openjdk_7_b147_jun_11/openjdk/jdk/src/share/classes/sun/print/PSPrinterJob.java
                
--- /tmp/openjdk/jdk/src/share/classes/sun/print/PSPrinterJob.java	2011-06-27 \
                19:38:48.000000000 +0200
+++ /home/alex/openjdk_7_b147_jun_11/openjdk/jdk/src/share/classes/sun/print/PSPrinterJob.java	2014-01-17 \
16:04:14.000000000 +0100 @@ -94,6 +94,11 @@ import java.nio.charset.*;
 import java.nio.CharBuffer;
 import java.nio.ByteBuffer;
 
+import java.awt.font.GlyphVector;
+import java.awt.geom.AffineTransform;
+import java.util.HashMap;
+import java.util.BitSet;
+
 //REMIND: Remove use of this class when IPPPrintService is moved to share directory.
 import java.lang.reflect.Method;
 
@@ -332,6 +337,25 @@ public class PSPrinterJob extends Raster
     */
    private static Properties mFontProps = null;
 
+    static final AffineTransform IDENTITY_TRANSFORM=new AffineTransform();
+    static final FontRenderContext PS_TYPE3_FRC=new \
FontRenderContext(IDENTITY_TRANSFORM,false,true); +
+
+//Postscript supports "StandardEncoding", ISOLatin1Encoding" (ISO-8859-1) and 
+//CE (Cp1250). 
+//If we are only interested in getting the most compact representation
+//then we can use any single byte encoding such as ISO-8859-2 which would for
+//example create compact Postcript for the Czech language.
+//If however we are interested in retrieving the text from the Postscript
+//document (e.g. ps2txt) or to create PDF documents from which the text can be
+//correctly copied and pasted then the encoding will have to be one of the 
+//three supported encodings.
+    CharsetEncoder mType3Encoder=Charset.forName("ISO-8859-15")
+                   .newEncoder().onMalformedInput(CodingErrorAction.REPORT)
+                   .onUnmappableCharacter(CodingErrorAction.REPORT);
+    HashMap<String,FontInfo> mSeenFonts=new HashMap<String,FontInfo>();
+    int mFontIdCounter=0;
+
     /* Class static initialiser block */
     static {
        //enable priviledges so initProps can access system properties,
@@ -2018,6 +2042,332 @@ public class PSPrinterJob extends Raster
 
     }
 
+//TODO: Make sure that long strings are broken down into chunks of maximum 
+//      64 K (MAX_PSSTR) size
+    protected boolean textOutType3(PSPathGraphics g, String str, float x,
+                               float y, Font font, FontRenderContext frc,
+                               float width) {
+//TODO: how to share the comment with textOut()?
+    /* On-screen drawString renders most control chars as the missing
+     * glyph and have the non-zero advance of that glyph.
+     * Exceptions are \t, \n and \r which are considered zero-width.
+     * Postscript handles control chars mostly as a missing glyph.
+     * But we use 'ashow' specifying a width for the string which
+     * assumes zero-width for those three exceptions, and Postscript
+     * tries to squeeze the extra char in, with the result that the
+     * glyphs look compressed or even overlap.
+     * So exclude those control chars from the string sent to PS.
+     */
+    str = removeControlChars(str);
+
+    int sLen=str.length();
+
+    if (sLen == 0) {
+        return true;
+    }
+
+    for(int i=0;i<sLen;i++) {
+        char c=str.charAt(i);
+        if(
+//TODO: Check this list. Is there a utility class somewhere that can be used?
+           (c>=0x590&&c<=0x5ff)|| //Hebrew
+           (c>=0x600&&c<=0x6ff)|| // Arabic
+           (c>=0x750&&c<=0x77f)|| // Arabic Supplement
+           (c>=0x8A0&&c<=0x8ff)|| // Arabic Extended-A
+           (c>=0xfb50&&c<=0xfdff)|| // Arabic Presentation Forms A
+           (c>=0xfe70&&c<=0xfeff)|| // Arabic Presentation Forms B
+           (c>=0xe00&&c<=0xe7f)|| // Thai
+           (c>=0x900&&c<=0x97f)|| // Devanagari
+           (c>=0xa8e0&&c<=0xa8ff)|| // Devanagari Extended
+           (c>=0x11000&&c<=0x1107f) // Brahmi
+           ) {
+               return false;
+        }
+    }
+ 
+//TODO: Can there be two different fonts that have the same 
+//      Font.getFamily()/Font.getStyle() combination? If yes, then this needs 
+//      to be changed and some other method needs to be found that identifies 
+//      different fonts.
+        Font font1000=font.deriveFont(1000f);
+        String key=font.getFamily()+font.getStyle();
+        FontInfo fi=mSeenFonts.get(key);
+        if(fi==null) { // seen font for the first time
+            fi=new FontInfo();
+            mSeenFonts.put(key,fi);
+
+            int missingGlyphCode[] = new int[1];
+            missingGlyphCode[0]=font1000.getMissingGlyphCode();
+            GlyphVector missingGlyphGv=font1000.createGlyphVector(frc, 
+                        missingGlyphCode);
+            Shape missingGlyphShape=missingGlyphGv.getGlyphOutline(0);
+
+            float[] bbox=new float[] { Float.MAX_VALUE, Float.MAX_VALUE, 
+                                      -Float.MAX_VALUE, -Float.MAX_VALUE};
+            adjustBBox(missingGlyphShape,bbox);
+            byte[] bytes=new byte[256];
+            for(int i=0;i<256;i++) bytes[i]=(byte)i;
+            CharBuffer cb=mType3Encoder.charset().decode(ByteBuffer.wrap(bytes));
+            assert cb.limit()==256;
+            for(int i=0;i<256;i++) {
+                char code=cb.get(i);
+                if(font1000.canDisplay(code)) {
+                    String c=""+code;
+                    GlyphVector gv=font1000.createGlyphVector(frc,c);
+                    Shape sh=gv.getGlyphOutline(0);
+                    if(sh==null) continue;
+                    adjustBBox(sh,bbox);
+                }
+            }
+
+            mPSStream.println("true setglobal");
+            mPSStream.println("globaldict begin");
+    
+            mPSStream.println("8 dict begin");
+            mPSStream.println("/FontType 3 def");
+            mPSStream.println("/FontMatrix [.001 0 0 .001 0 0] def");
+            mPSStream.println("/FontBBox ["+trunc(bbox[0])+" "+trunc(bbox[1])
+                              +" "+trunc(bbox[2])+" "+trunc(bbox[3])+"] def");
+            mPSStream.println("/Encoding 256 array def");
+
+//first loop for printing the encoding table
+            for(int i=0;i<256;i++) {
+                char code=(char)i;
+                if(font1000.canDisplay(code)) {
+                    mPSStream.println("Encoding "+i+" /c"+
+                                         Integer.toHexString((int)code)+" put");
+                }
+                else {
+                    mPSStream.println("Encoding "+i+" /.notdef put");
+                }
+            }
+//second loop for printing the character definitions
+            mPSStream.println("/CharProcs 3 dict def");
+            mPSStream.println("CharProcs begin");
+            printGlyphDefinition(".notdef",missingGlyphGv);
+    
+            mPSStream.println("end");
+            mPSStream.println("/BuildGlyph");
+            mPSStream.println("{");
+            mPSStream.println("exch /CharProcs get exch");
+            mPSStream.println("2 copy known not");
+            mPSStream.println("{ pop /.notdef }");
+            mPSStream.println("if");
+            mPSStream.println("get exec");
+            mPSStream.println("} bind def");
+            mPSStream.println("/BuildChar");
+            mPSStream.println("{ 1 index /Encoding get exch get");
+            mPSStream.println("1 index /BuildGlyph get exec");
+            mPSStream.println("} bind def");
+            mPSStream.println("currentdict");
+            mPSStream.println("end ");
+    
+            mPSStream.println("/"+fi.getName()+" exch definefont pop");
+    
+            mPSStream.println("end");
+            mPSStream.println("false setglobal");
+        } // end of initial font definition
+
+        boolean isFirst=true;
+        for(int i=0;i<sLen;i++) {
+            char code=str.charAt(i);
+            if(!fi.isSeen(code)) {
+                
+                fi.markAsSeen(code);
+                if(isFirst) {
+                    isFirst=false;
+                    mPSStream.println("true setglobal");
+                    mPSStream.println("globaldict begin");
+                    mPSStream.println("/"+fi.getName()+" findfont");
+                    mPSStream.println("/CharProcs get");
+                    mPSStream.println("begin");
+                }
+//TODO: grow bbox either here (if allowed) or do it on the initial definition 
+//      by walking over all characters and not only the first 256 as it is 
+//      currently the case.
+                GlyphVector gv=font1000.createGlyphVector(frc,""+code);
+                printGlyphDefinition("c"+Integer.toHexString((int)code),gv);
+            }
+        }
+        if(!isFirst) {
+            mPSStream.println("end");
+            mPSStream.println("end");
+            mPSStream.println("false setglobal");
+        }
+        mPSStream.println("/"+fi.getName()+" findfont "+font.getSize2D()+
+                                                           " scalefont setfont");
+
+        boolean needToAdjustWidth=sLen>1&&(frc.isAntiAliased()||
+                                                  !frc.usesFractionalMetrics());
+
+        float desiredWidth=width;
+
+        if(needToAdjustWidth && width == 0f) {
+            desiredWidth = (float)font.getStringBounds(str,frc).getWidth();
+        }
+
+        try {
+            ByteBuffer encodedBytes=mType3Encoder.encode(CharBuffer.wrap(str));
+            if(!needToAdjustWidth) {
+                mPSStream.println(""+x + " " + y + MOVETO_STR);
+            }
+            mPSStream.print("<");
+            int cnt=encodedBytes.limit();
+            for(int i=0;i<cnt;i++) {
+                mPSStream.print(Integer.toHexString(encodedBytes.get()&0xff));
+            }
+            if(needToAdjustWidth) {
+                    mPSStream.println("> " +
+                                      desiredWidth + " " + x + " " + y + " " +
+                                      DrawStringName);
+            } else {
+                mPSStream.println("> show");
+            }
+        }
+        catch(Exception e) {
+//TODO: Replace the try/catch with an alternative low cost method to determine 
+//      wether or not all characters can be encoded as bytes.
+            mPSStream.println(""+x + " " + y + MOVETO_STR);
+            if(needToAdjustWidth) {
+//TODO: Should we leave the computation to Postscript using "stringwidth" as it
+//      is done in the "S" procedure?
+                float actualWidth=(float)font.getStringBounds(str,
+                                                       PS_TYPE3_FRC).getWidth();
+                mPSStream.println("/c"+Integer.toHexString((int)str.charAt(0))
+                                                                 +" glyphshow");
+                assert sLen>1;
+                float ax=(desiredWidth-actualWidth)/(float)(sLen-1);
+                for(int i=1;i<sLen;i++) {
+                    mPSStream.println(""+ax + " 0  rmoveto");
+                    mPSStream.println("/c"+
+                          Integer.toHexString((int)str.charAt(i))+" glyphshow");
+                }
+            } else {
+                mPSStream.println(""+x + " " + y + MOVETO_STR);
+                for(int i=0;i<sLen;i++) {
+                    mPSStream.println("/c"+
+                           Integer.toHexString((int)str.charAt(i))+" glyphshow");
+                }
+            }
+        }
+        return true;
+    }
+    private static void adjustBBox(Shape s,float[] bbox) {
+        if(!(s instanceof java.awt.geom.GeneralPath)) s=
+                                               new java.awt.geom.GeneralPath(s);
+        java.awt.geom.GeneralPath gp=(java.awt.geom.GeneralPath)s;
+        java.awt.geom.PathIterator pi=gp.getPathIterator(IDENTITY_TRANSFORM);
+        float coords[]=new float[6];
+        while(!pi.isDone()) {
+            switch(pi.currentSegment(coords)) {
+                case java.awt.geom.PathIterator.SEG_CUBICTO: {
+                    if(coords[4]<bbox[0]) bbox[0]=coords[4];
+                    if(coords[4]>bbox[2]) bbox[2]=coords[4];
+                    if(coords[5]<bbox[1]) bbox[1]=coords[5];
+                    if(coords[5]>bbox[3]) bbox[3]=coords[5];
+                }
+                case java.awt.geom.PathIterator.SEG_QUADTO: {
+                    if(coords[2]<bbox[0]) bbox[0]=coords[2];
+                    if(coords[2]>bbox[2]) bbox[2]=coords[2];
+                    if(coords[3]<bbox[1]) bbox[1]=coords[3];
+                    if(coords[3]>bbox[3]) bbox[3]=coords[3];
+                }
+                case java.awt.geom.PathIterator.SEG_MOVETO:
+                case java.awt.geom.PathIterator.SEG_LINETO: {
+                    if(coords[0]<bbox[0]) bbox[0]=coords[0];
+                    if(coords[0]>bbox[2]) bbox[2]=coords[0];
+                    if(coords[1]<bbox[1]) bbox[1]=coords[1];
+                    if(coords[1]>bbox[3]) bbox[3]=coords[1];
+                    break;
+                }
+            }
+            pi.next();
+        }
+    }
+    private void printGlyphDefinition(String name,GlyphVector gv) {
+        Shape s=gv.getGlyphOutline(0);
+        assert s!=null;
+        if(s==null) return;
+        mPSStream.println("/"+name);
+        mPSStream.println("{");
+        float[] bbox=new float[] { Float.MAX_VALUE, Float.MAX_VALUE, 
+                                  -Float.MAX_VALUE, -Float.MAX_VALUE};
+        adjustBBox(s,bbox);
+        mPSStream.println(trunc(gv.getGlyphMetrics(0).getAdvanceX())+" "+
+                                    trunc(gv.getGlyphMetrics(0).getAdvanceY()));
+        if(bbox[0]>bbox[2]||bbox[1]>bbox[3])
+            mPSStream.println("0 0 0 0");
+        else
+            mPSStream.println(trunc(bbox[0])+" "+trunc(bbox[1])+" "+
+                                             trunc(bbox[2])+" "+trunc(bbox[3]));
+        mPSStream.println("setcachedevice");
+        if(!(s instanceof java.awt.geom.GeneralPath)) s=
+                                                new java.awt.geom.GeneralPath(s);
+        java.awt.geom.GeneralPath gp=(java.awt.geom.GeneralPath)s;
+        java.awt.geom.PathIterator pi=gp.getPathIterator(IDENTITY_TRANSFORM);
+        float coords[]=new float[6];
+        float lastX=0,lastY=0;
+        while(!pi.isDone()) {
+            switch(pi.currentSegment(coords)) {
+                case java.awt.geom.PathIterator.SEG_CLOSE: {
+                    mPSStream.println(CLOSEPATH_STR);
+                    break;
+                }
+                case java.awt.geom.PathIterator.SEG_LINETO: {
+                    mPSStream.println(trunc(coords[0])+" "+trunc(coords[1])+
+                                                                     LINETO_STR);
+                    lastX=coords[0];
+                    lastY=coords[1];
+                    break;
+                }
+                case java.awt.geom.PathIterator.SEG_MOVETO: {
+                    mPSStream.println(trunc(coords[0])+" "+trunc(coords[1])+
+                                                                     MOVETO_STR);
+                    lastX=coords[0];
+                    lastY=coords[1];
+                    break;
+                }
+                case java.awt.geom.PathIterator.SEG_QUADTO: {
+                    float c1x = lastX + (coords[0] - lastX) * 2 / 3;
+                    float c1y = lastY + (coords[1] - lastY) * 2 / 3;
+                    float c2x = coords[2] - (coords[2] - coords[0]) * 2/ 3;
+                    float c2y = coords[3] - (coords[3] - coords[1]) * 2/ 3;
+                    mPSStream.println(trunc(c1x)+" "+trunc(c1y)+" "+trunc(c2x)
+                                 +" "+trunc(c2y)+" "+trunc(coords[2])+" "
+                                     +trunc(coords[3])+CURVETO_STR);
+                    lastX=coords[2];
+                    lastY=coords[3];
+                    break;
+                }
+                case java.awt.geom.PathIterator.SEG_CUBICTO: {
+                    mPSStream.println(trunc(coords[0])+" "+trunc(coords[1])+" "
+                                     +trunc(coords[2])+" "+trunc(coords[3])+" "
+                                     +trunc(coords[4])+" "+trunc(coords[5])+
+                                                                    CURVETO_STR);
+                    lastX=coords[4];
+                    lastY=coords[5];
+                    break;
+                }
+            }
+            pi.next();
+        }
+        mPSStream.println("fill");
+        mPSStream.println("} bind def");
+    }
+    private class FontInfo {
+        int mId=mFontIdCounter++;
+        BitSet mSeenCharacters=new BitSet();
+        public String getName() {
+            return "f"+Integer.toHexString(mId);
+        }
+        void markAsSeen(char c) {
+            mSeenCharacters.set((int)c);
+        }
+        public boolean isSeen(char c) {
+            return mSeenCharacters.get((int)c);
+        }
+    }
+
     /**
      * PluginPrinter generates EPSF wrapped with a header and trailer
      * comment. This conforms to the new requirements of Mozilla 1.7


["PSTest.java" (text/plain)]

//Derived from http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4483236
/*
 * Copyright 2001 Sun Microsystems, Inc. All Rights Reserved.
 *
 * This software is the proprietary information of Sun Microsystems, Inc.
 * Use is subject to license terms.
 *
 */

import java.io.*;
import java.awt.*;
import java.awt.geom.*;
import java.awt.print.*;
import java.awt.image.*;
import javax.print.*;
import javax.print.attribute.*;
import javax.print.attribute.standard.*;

public class PSTest implements Printable{

    enum RenderingMethod { DrawString,DrawGlyphVector};
    static String fontName="SansSerif";
    static String fontNameForChinese="WenQuanYi Zen Hei";
    static RenderingMethod renderingMethod=RenderingMethod.DrawString;
    static boolean renderIntoBufferedImage=false;
    static boolean useFractionalMetrics=true;
    static float imageDPI=300;

    public PSTest() {
        DocFlavor flavor = DocFlavor.SERVICE_FORMATTED.PRINTABLE;

        String psMimeType = DocFlavor.BYTE_ARRAY.POSTSCRIPT.getMimeType
();

        StreamPrintServiceFactory[] factories =
        StreamPrintServiceFactory.lookupStreamPrintServiceFactories( flavor, \
psMimeType);  if (factories.length == 0) {
            System.err.println("No suitable factories");
            System.exit(0);
        }

        try {
            FileOutputStream fos = new FileOutputStream("out.ps");

            StreamPrintService sps = factories[0].getPrintService(fos);

            DocPrintJob pj = sps.createPrintJob();
            PrintRequestAttributeSet aset = new HashPrintRequestAttributeSet();

            Doc doc = new SimpleDoc(this, flavor, null);

            pj.print(doc, aset);
            fos.close();

        } catch (PrintException pe) {
            System.err.println(pe);
        } catch (IOException ie) {
            System.err.println(ie);
        }
    }
    public int print(Graphics g,PageFormat pf,int pageIndex) {
        if (pageIndex <= 1) {
            Graphics2D g2d = (Graphics2D)g;
            g2d.translate(pf.getImageableX(), pf.getImageableY());
            BufferedImage bi=null;
            if(renderIntoBufferedImage) {
                float factor=imageDPI/72f;
                bi=new \
BufferedImage((int)(pf.getImageableWidth()*factor),(int)(pf.getImageableHeight()*factor),BufferedImage.TYPE_INT_RGB);
  g2d=bi.createGraphics();
                g2d.setColor(Color.white);
                g2d.fillRect(0,0,bi.getWidth(),bi.getHeight());
                g2d.setColor(Color.black);
                g2d.scale(factor,factor);
            }
            if(useFractionalMetrics) {
                RenderingHints rh=g2d.getRenderingHints();
                assert rh!=null;
                if(rh==null)
                    rh=new RenderingHints(null);
                rh.put(RenderingHints.KEY_FRACTIONALMETRICS,RenderingHints.VALUE_FRACTIONALMETRICS_ON);
  g2d.setRenderingHints(rh);
            }
    
            int y=0;
            g2d.setFont(new Font(fontName,Font.PLAIN,12));
            drawString(g2d,"Page "+(pageIndex+1)+":", 0, y+=14);
            g2d.setFont(new Font(fontName,Font.PLAIN,8));
            if(pageIndex==0) {
                drawString(g2d,"Note the incremental font update. All new characters \
                on this line but the ", 0, y+=10);
                drawString(g2d,"characters 'P', 'a', 'g', 'e', ' ' and '1' are added \
                to the font only now.", 0, y+=10);
                drawString(g2d,"Also note which strings are displayed using \
                \"show\"", 0, y+=10);
                drawString(g2d,"and which are displayed character by character using \
\"glyphshow\".", 0, y+=10);  } else {
                drawString(g2d,"Note that the fonts defintions are valid thru", 0, \
                y+=10);
                drawString(g2d,"the entire document and not only on a single page.", \
0, y+=10);  }
            g2d.setFont(new Font(fontName,Font.PLAIN,12));
            drawString(g2d,"A non ASCII character from ISO-8859-1 (A dieresis): \
                \u00c4", 0, y+=14);
            drawString(g2d,"A non ASCII character not in ISO-8859-1 (C with hacek): \
\u010c", 0, y+=14);  g2d.setFont(new Font(fontNameForChinese,Font.PLAIN,12));
            drawString(g2d,"Simplified Chinese: \u7535\u8111\u4f60\u597d\uff01", 0, \
                y+=14);
            drawString(g2d,"Traditional Chinese: \u96fb\u8166\u4f60\u597d\ufe57", 0, \
y+=14);  g2d.setFont(new Font(fontName,Font.PLAIN,12));
            drawString(g2d,"A unicode character with a value larger than 0xff (Hebrew \
aleph): \u05d0", 0, y+=14);  drawString(g2d,"A ligature \"fi\"", 0, y+=14);
            drawString(g2d,"Lam: \u0644, Alif: \u0627, LamAlif: \u0644\u0627 (should \
see: \ufefb)", 0, y+=14);  g2d.setFont(new Font(fontName,Font.ITALIC,12));
            drawString(g2d,"This time in italic style", 0, y+=14);
            drawString(g2d,"An ASCII string", 0, y+=14);
            drawString(g2d,"Another ASCII string with more character so demonstrate \
                the incremental font update", 0, y+=14);
            drawString(g2d,"A non ASCII character from ISO-8859-1 (A dieresis): \
                \u00c4", 0, y+=14);
            drawString(g2d,"A non ASCII character not in ISO-8859-1 (C with hacek): \
                \u010c", 0, y+=14);
            drawString(g2d,"A unicode character with a value larger than 0xff (Hebrew \
aleph): \u05d0", 0, y+=14);  drawString(g2d,"A ligature \"fi\"", 0, y+=14);
            drawString(g2d,"Lam: \u0644, Alif: \u0627, LamAlif: \u0644\u0627 (should \
see: \ufefb)", 0, y+=14);  g2d.setFont(new Font(fontName,Font.BOLD,12));
            drawString(g2d,"This time in bold style (There is a bug in this. The text \
is too wide)", 0, y+=14);  drawString(g2d,"An ASCII string", 0, y+=14);
            drawString(g2d,"Another ASCII string with more character so demonstrate \
                the incremental font update", 0, y+=14);
            drawString(g2d,"A non ASCII character from ISO-8859-1 (A dieresis): \
                \u00c4", 0, y+=14);
            drawString(g2d,"A non ASCII character not in ISO-8859-1 (C with hacek): \
                \u010c", 0, y+=14);
            drawString(g2d,"A unicode character with a value larger than 0xff (Hebrew \
                aleph): \u05d0", 0, y+=14);
            drawString(g2d,"The ISO-8859-15 character set starting at 0xa0:", 0, \
                y+=14);
            drawString(g2d,"\u00A0\u00A1\u00A2\u00A3\u20AC\u00A5\u0160\u00A7\u0161\u00A9\u00AA\u00AB\u00AC\u00AD\u00AE\u00AF\u00B0\u00B1\u00B2\u00B3",0,y+=14);
                
            drawString(g2d,"\u017D\u00B5\u00B6\u00B7\u017E\u00B9\u00BA\u00BB\u0152\u0153\u0178\u00BF\u00C0\u00C1\u00C2\u00C3\u00C4\u00C5\u00C6\u00C7",0,y+=14);
                
            drawString(g2d,"\u00C8\u00C9\u00CA\u00CB\u00CC\u00CD\u00CE\u00CF\u00D0\u00D1\u00D2\u00D3\u00D4\u00D5\u00D6\u00D7\u00D8\u00D9\u00DA\u00DB",0,y+=14);
                
            drawString(g2d,"\u00DC\u00DD\u00DE\u00DF\u00E0\u00E1\u00E2\u00E3\u00E4\u00E5\u00E6\u00E7\u00E8\u00E9\u00EA\u00EB\u00EC\u00ED\u00EE\u00EF",0,y+=14);
                
            drawString(g2d,"\u00F0\u00F1\u00F2\u00F3\u00F4\u00F5\u00F6\u00F7\u00F8\u00F9\u00FA\u00FB\u00FC\u00FD\u00FE\u00FF",0,y+=14);
  y+=14;
            g2d.setFont(new Font(fontName,Font.PLAIN,10));
            drawString(g2d,"Settings used in this document:", 0, y+=14);
            drawString(g2d,"Font: "+fontName, 0, y+=14);
            drawString(g2d,"Font for Chinese text: "+fontNameForChinese, 0, y+=14);
            drawString(g2d,"Drawing Method: "+renderingMethod, 0, y+=14);
            drawString(g2d,"Use fractional metrics?: "+useFractionalMetrics, 0, \
                y+=14);
            drawString(g2d,"Render into buffered image?: "+renderIntoBufferedImage, \
0, y+=14);  drawString(g2d,"Image DPI: "+imageDPI, 0, y+=14);
            if(renderIntoBufferedImage) {
                g.drawImage(bi,0,0,(int)pf.getImageableWidth(),(int)pf.getImageableHeight(),(ImageObserver)null);
  g2d.dispose();
            }
            return Printable.PAGE_EXISTS;
        } else {
        return Printable.NO_SUCH_PAGE;
        }
    }
    static void drawString(Graphics2D g2d,String text,float x,float y) {
        if(renderingMethod==RenderingMethod.DrawString) {
            g2d.drawString(text,x,y);
        } else {
            g2d.drawGlyphVector(g2d.getFont().createGlyphVector(g2d.getFontRenderContext(),text),x,y);
  }
        Rectangle2D r=g2d.getFont().getStringBounds(text,g2d.getFontRenderContext());
        g2d.drawLine((int)x,(int)y,(int)(x+r.getWidth()),(int)y);
    }

    public static void main(String args[]) {
        if(args.length>0) fontName=args[0];
        if(args.length>1) fontNameForChinese=args[1];
        if(args.length>2) renderingMethod=RenderingMethod.valueOf(args[2]);
        if(args.length>3) renderIntoBufferedImage=Integer.parseInt(args[3])!=0;
        if(args.length>4) useFractionalMetrics=Integer.parseInt(args[4])!=0;
        PSTest sp = new PSTest();
    }
}



[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic