You are viewing a plain text version of this content. The canonical link for it is here.
Posted to batik-users@xmlgraphics.apache.org by Jakob Ilves <ja...@oracle.com> on 2003/03/03 13:05:25 UTC

Hype, issues and questions regarding Squiggle printing SVG graphics.

Hello!

Ok, this is a long mail, It's a bad habit I have :-).


MY BACKGROUND

For several reasons, SVG caught my interest as the basis for describing
graphics as they were printed on paper.  If stuff looked nice on screen,
I would course appreciate it, but it was accurate, high resolution
printouts I primarly had in mind.  As the I read the SVG 1.0 spec, I
realized that the standard provideded excellend control over distances
and metrics on printed media as well as printing various graphic
primitives at the *printer's* resolution.  And also, it supported
ECMAscript (which I figured out to pretty much be the same as
JavaScript) which provided a lot of intresting possibilities (such as
describing the image by providing code which is executed and arranges
the SVG graphics upon display upon displaying the document in the Batik
browser).

So, when looking for an SVG agent I had two features I found important:
ECMAscript support and good printouts at the printers resolution.
(Features like code stability, managebility etc were of course also
important ;-).  As SVG is a pretty new standard, I was prepared to find
varying support for it, to say the least.


HYPE

After trying around with different agents (and being dissappointed), the
Batik Squiggle was a pleasant surprise.  When it comes to printing, it's
"almost there" and even better, the pieces missing are pretty easily
fixed.  "Hard to implement stuff" like printing vectored graphics is
already in place and works!  On a printout done at 600dpi tiny features
appears clear and distinct even if those would not be visible on the
computer screen.  It doesn't first raster the images on screen and then
dump out that rastered bitmap (such as ImageMagick or the Adobe SVG
plugin do in MSIE), it sends vectors to the printer directly.  Scripting
seem to work as well (even if I've not "torture tested" that feature
:-).  (And yes, I've not yet tried out to use system fonts which some
messages in the archive hint can have issues in printouts, but that
isn't critical to me).

The only issue was the scale, but after some (ugly) hardcoding in the
Squiggly source I managed to get SVG docs with a viewport expressed in
pixels to print out at a scale of 96 screen pixels per printed media
inch (which is exactly what I wanted).  All in all, it's VERY promising!
So, to you people who develops batik and Squiggle I just want to say
this...

<caps_stuck>...KEEP UP THE EXCELLENT WORK!!!</caps_stuck>

Ok, that was the well deserved hype.  Now something different...


ISSUES and QUESTIONS:

Squiggle currently always autoscales graphics to fit the printer page.
I would highly appreciate if that was made optional, so users like me
who want's unscaled printouts can get what we want.  When adding that
feature, it can be worth providing an option where the user can specify
how big a "pixel" should be on the printer in the case graphics are
expressed in "pixels" as the length unit.  Perhaps one could have the
option to express this as either "dpi" or as "mm per pixel".  The
question is, from a GUI perspective, where to put those options in
Squiggle?  Of course, when graphics are unscaled, an inch in the SVG
code should still map to an inch on the printout (and equally for cm, mm
etc).

I know that Squiggle is mainly a demonstration of how to write an
application using the Batik framework but as it excels in printout
quality compared to the rest "out there" and it is sooo close to do
*exactly* what I need an SVG user agent to do I would highly appreciate
the above feature.

BTW, how big is the focus on printout quality?  After searching the
archives, I get a feeling that on screen display is top priority and
that printing quality is of lower importance.  Is this correct?  Note,
I'm not questioning that priority, if it would happen to be the case
because I find it reasonable and also, even if you don't prioritize
print quality, you've managed to excel in that area anyway ;-).  But in
the case you wonders if there's anyone on the planet who views SVG
primarly as a tool for conveying descriptions of printed documents,
there's at least one of us (me :-).


HACKING THE CODE...

And now comes the part of the mail which would better be discussed on
the developers list:  It contains what I've done to the poor source code
to tweak around with scaling.

First said, the code is neat and nicely organized and easy to navigate
in (I consider the fact that I didn't (?) get lost in it as a proof of
that statement).

Another point worth noting: the discussion below let dpi (dots per inch)
denote the resolution on the printer while "pixel per inch" means how
many screen pixels an inch on the printed media corresponds to. Example,
if I print an image at a 96 pixels per inch resolution on a 600 dpi
printer that means that a square on screen being 96 pixels long and 96
pixels high will be 1 by 1 inch on the printed media and it will be made
up by 600 by 600 printer dots.

As a test, I did a quick and really dirty hard coded hack in the
Squiggle code, where I added a few lines in the method
PrintAction.actionPerformed() (found in the file JSVGViewerFrame.java)
where I added yet another "hint" to the PrintTranscoder:
"KEY_SCALE_TO_PAGE" where set to Boolean.FALSE.

This was a fun test.  When printing out my graphics, they were indeed
not scaled, they were instead small and tiny but still crisp (600 dpi
lasers and Squiggle... unbeatable combo...:-).  There was only one
issue: even if smaller as expected, the graphics were still
approximately 33% larger than stated in the SVG document.  A 100mm
square in the document became 133mm on print and half inch squares
became approximately 1/3 larger than intended.  When using the "pixel"
as the length unit, the graphics were not printed as 96 pixels per inch
but instead at 72 pixels per inch.  After chasing stuff around in the
source code, I found out that batik hardcodes a pixel to be  approx
0.2645833333333... mm (in the method
UserAgentAdapter.getPixelUnitToMillimeter() in UserAgentAdapter.java)
and according to a comment on the same line the intent is to get a
resolution of... 96 pixels per inch!

(Worth noting, I only use length units in the size attribute in the
outermost SVG tag.  All coordinates for graphical primitives are in the
Viewport coordinate space, without any length units.  This all follows
the recommendations found in the SVG 1.0 spec. )

Has anyone else replicated this issue with 33% too large printouts?  I
use JDK 1.4.0_02 on Windows 2000 (service pack 3) and the beta release
1_5beta4 of Batik (downloaded 2003-Feb-26).

So, after some thinking I came up with a wild guess of what's
happening:  When printing, Batik initially wants stuff to be printed at
96 pixels per inch and lays out the graphics accordingly.  Somewhere in
the path of transforming the SVG document into a printout, Batik
accidently "switches" to a 72 pixels per inch resolution (perhaps just
before the laid out SVG graphics are sent to the printer) and therefore
the graphics become enlarged by a factor 96/72=4/3=1.333333... !!  Maybe
the java.awt libraries makes some unfortunate guess regarding how many
pixels per inch should be used and therefore things break.  (Ok, I
admit, I just speculate here...)

Ok, it's a guess.  But to investigate, I went into the source again and
added the following hint: "KEY_PIXEL_UNIT_TO_MILLIMETER" being set to
0.1984375 to "scale down" a pixel to 3/4 of it's usual size
(0.2645833333.... * 3/4 = 0.1984375).  I also set the deprecated
"KEY_PIXEL_TO_MM" to that values just to be sure ;-).  This caused no
effect because "KEY_SCALE_TO_PAGE" were false which prevents ANY scaling
(I read the code and realized that a a false value of
"KEY_SCALE_TO_PAGE" makes the PrintTranscoder completely ignore
"KEY_PIXEL_UNIT_TO_MILLIMETER" and "KEY_PIXEL_TO_MM").

So, I did some more fearless hacking...  in PrintTranscoder.print() I
edited the code which set scale=1 if "KEY_SCALE_TO_PAGE" had a false
value.  The change I did was that the code also checked if either
"KEY_PIXEL_UNIT_TO_MILLIMETER" or "KEY_PIXEL_TO_MM" were set and if so,
use them to calculate the scale.  If they weren't set, scale were set to
1.

The result were that when I specified a document to use "pixels" as it's
size, the graphics are printed at 96 pixels per inch (success!!) but as
soon as I supplied another length unit such as mm, cm, in etc to the SVG
documents size, the graphics were again 33% too large.

At that stage I decided to stop hacking.  This for two reasons:  1.) I
finally got the kind of printouts I wanted (SVG docs using "pixels" as
length unit appears on print at a 96 pixels per inch resolution) so I
hade a OK workaround and 2.) I realized that I didn't really know what I
were doing with the code as I haven't understood it fully enough and
therefore, other people who really know how it works should have a look
at it instead.  I suspect that to fix the issue of printed out docs
being 33% larger than expected one have to make nothing but a tiny
change but one have to do it at the right place.

So, to make Squiggle amazingly useful for printing paper docs defined in
SVG only the following is needed:
1.) An option to disable autoscaling and instead print graphics unscaled
(in Squiggle).
2.) A proper bugfix to the problem of graphics being 33% too big when
unscaled (my hack is not a proper fix ;-).  Would require some fix
somewhere in the Batik framework code itself.

I provide "context patches" below of my hacked versions of
PrintTranscoder.java and JSVGViewerFrame.java for those who happen to be
intrested.  I put qoutes around "context patches" because I create these
by hand (I have no good "diff" utility on my Windows box) by simply
including a fairly large part of the code before and after my changes
(actually, the entire method definition for the altered methods).  One
alternative would have been to the entire files as attachments but I've
found out the hard way that won't work... (the resulting mail becomes
larger than 100k and is therefore rejected...)


DISCLAIMER (or, I know I do the following wrong here...)

I am aware that I in this email break against a number of holy rules of
how to ask for help on maillists:
1.) I write a very long email
2.) I submit hand made context patches which the "patch" program most
likely would not accept (sorry, I sit on a windows box, I have no good
diff program :-< )
3.) I use the deprecated KEY_PIXEL_TO_MM, thus provoking the java
compiler to emit unneeded noise.
4.) I've not produced a usable patch which can be included directly into
the product.
5.) In spite of having several flavors of OSes (WinME, Linux Mandrake
8.1, HP-UX 11.00) and several versions of JDK/JRE and different printers
as well at my disposal I haven't explored these combinations to track
down if this issue might be related with the a.) OS, b.) Java Version,
c.) printer or d.) a combination of these.
6.) I make speculations about what can be the cause of the problem
instead of sticking to just document the symptoms and leave diagnostics
to those who actually knows the product.
7.) I mix content suitable for batik-users and batik-dev in the same
mail!

(But I tried to search the list archives and I did read the FAQ.  Also,
I've expressed my candid opinion about the quality of the product and
it's superb printing abilities!!)

In spite of all virtues of nettiquette I've ignored I hope you folks
don't mind me sending this email to the list.

Best regards and a lot of thanks in advance

/IlvJa

Hand made diff context for PrintTranscoder.java

----8<--------8<--------8<--------8<--------8<--------8<--------8<--------8<----

    public class PrintAction extends AbstractAction {
        public PrintAction() {}
        public void actionPerformed(ActionEvent e) {
            if (svgDocument != null) {
                final SVGDocument doc = svgDocument;
                new Thread() {
                    public void run(){
                        String uri = doc.getURL();
                        String fragment =
svgCanvas.getFragmentIdentifier();
                        if (fragment != null) {
                            uri += "#"+fragment;
                        }

                        //
                        // Build a PrintTranscoder to handle printing
                        // of the svgDocument object
                        //
                        PrintTranscoder pt = new PrintTranscoder();

                        //
                        // Set transcoding hints
                        //

pt.addTranscodingHint(pt.KEY_XML_PARSER_CLASSNAME,

application.getXMLParserClassName());

                        pt.addTranscodingHint(pt.KEY_SHOW_PAGE_DIALOG,
                                              Boolean.TRUE);



pt.addTranscodingHint(pt.KEY_SHOW_PRINTER_DIALOG,
                                              Boolean.TRUE);

                        //
                        // Jakob Ilves hardcoded (and therefore ugly)
hack.
                        //
                        // As I want to print UNSCALED graphics I want
the
                        // transcoder hint KEY_SCALE_TO_PAGE to be set
to
                        // false.  How to best do that I have to discuss
with
                        // people who develop batik.
                        //
                        // Also, it appears that the graphics on screen
is
                        // assumed to be 96dpi while the printer is
assumed
                        // to be 72dpi and somewhere a mistake occurs so
the
                        // printed graphics becomes 4/3 times larger
than
                        // wanted.  So, I also (ab-)use the deprecated
                        // KEY_PIXEL_TO_MM to adjust things.
                        //

                        pt.addTranscodingHint(pt.KEY_SCALE_TO_PAGE,
                                             Boolean.FALSE);


pt.addTranscodingHint(pt.KEY_PIXEL_UNIT_TO_MILLIMETER,
                                              new Float(0.1984375));

                        pt.addTranscodingHint(pt.KEY_PIXEL_TO_MM,
                                              new Float(0.1984375));
                        //
                        // Do transcoding now
                        //
                        pt.transcode(new TranscoderInput(uri), null);

                        //
                        // Print
                        //
                        try {
                            pt.print();
                        } catch (PrinterException ex) {
                            userAgent.displayError(ex);
                        }
                    }
                }.start();
            }
        }
    }


----8<--------8<--------8<--------8<--------8<--------8<--------8<--------8<----



Hand made diff context for JSVGViewerFrame.java

----8<--------8<--------8<--------8<--------8<--------8<--------8<--------8<----

    /**
     * Printable implementation
     */
    public int print(Graphics _g, PageFormat pageFormat, int pageIndex){

        //
        // On the first page, take a snapshot of the vector of
        // TranscodeInputs.
        //
        if(pageIndex == 0){
            printedInputs = (Vector)inputs.clone();
        }

        //
        // If we have already printed each page, return
        //
        if(pageIndex >= printedInputs.size()){
            curIndex = -1;
            System.out.println("Done");
            return NO_SUCH_PAGE;
        }

        //
        // Load a new document now if we are printing a new page
        //
        if(curIndex != pageIndex){
            // The following call will invoke this class' transcode
            // method which takes a document as an input. That method
            // builds the GVT root tree.{
            try{

super.transcode((TranscoderInput)printedInputs.elementAt(pageIndex),
                                null);
                curIndex = pageIndex;
            }catch(TranscoderException e){
                drawError(_g, e);
                return PAGE_EXISTS;
            }
        }

        // Cast to Graphics2D to access Java 2D features
        Graphics2D g = (Graphics2D)_g;
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                           RenderingHints.VALUE_ANTIALIAS_ON);
        g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
                           RenderingHints.VALUE_INTERPOLATION_BILINEAR);

        g.setRenderingHint(RenderingHintsKeyExt.KEY_TRANSCODING,

RenderingHintsKeyExt.VALUE_TRANSCODING_PRINTING);

        //
        // Compute transform so that the SVG document fits on one page
        //
        AffineTransform t = g.getTransform();
        Shape clip = g.getClip();

        Rectangle2D bounds = curAOI;

        double scaleX = pageFormat.getImageableWidth() /
bounds.getWidth();
        double scaleY = pageFormat.getImageableHeight() /
bounds.getHeight();
        double scale = scaleX < scaleY ? scaleX : scaleY;

        // Check hint to know if scaling is really needed
        Boolean scaleToPage = (Boolean)hints.get(KEY_SCALE_TO_PAGE);
        if(scaleToPage != null && !scaleToPage.booleanValue()){
            //
            // Jakob Ilves ugly addition:
            // If KEY_SCALE_TO_PAGE is set to FALSE but still
            // KEY_PIXEL_UNIT_TO_MILLIMETER or the deprecated
            // KEY_PIXEL_TO_MM is set honor that value.  Note, the
default
            // appears to be 72dpi (for some reason), so if you set to
            // the value to 0.264583333... (72dpi), the scale should be
1.
            // If you set it to 0.1984375 (96dpi) the scaling becomes
0.75.
            //
            // This hack should be brought up with the Batik developers.

            //

            Float px2mm;

            px2mm = (Float)hints.get(KEY_PIXEL_UNIT_TO_MILLIMETER);

            if(px2mm == null)
            {
              px2mm = (Float)hints.get(KEY_PIXEL_TO_MM);
            }

            if(px2mm != null)
            {
              scale =
px2mm.floatValue()/0.2645833333333333333333333333333f;
            }
            else
            {
              scale = 1;
            }
        }

        double xMargin = (pageFormat.getImageableWidth() -
bounds.getWidth()*scale)/2;
        double yMargin = (pageFormat.getImageableHeight() -
bounds.getHeight()*scale)/2;
        g.translate(pageFormat.getImageableX() + xMargin,
                    pageFormat.getImageableY() + yMargin);
        g.scale(scale, scale);


        //
        // Append transform to selected area
        //
        g.transform(curTxf);

        g.clip(curAOI);

        //
        // Delegate rendering to painter
        //
        try{
            root.paint(g);
        }catch(Exception e){
            g.setTransform(t);
            g.setClip(clip);
            drawError(_g, e);
        }

        //
        // Restore transform and clip
        //
        g.setTransform(t);
        g.setClip(clip);

        // g.setPaint(Color.black);
        // g.drawString(uris[pageIndex], 30, 30);


        //
        // Return status indicated that we did paint a page
        //
        return PAGE_EXISTS;
    }

----8<--------8<--------8<--------8<--------8<--------8<--------8<--------8<----



Re: Hype, issues and questions regarding Squiggle printing SVG graphics.

Posted by Jakob Ilves <ja...@oracle.com>.
Hello again!

Appearently my included "patches" in my previous mail on this thread were malformed
by my mailclient.  So, I saved them to files and ancluded them as attachments
instead.  Note, it's still "context patches", I don't attach the entire modified
files.

Sorry for any inconvenience.

Best regards

/IlvJa