SVG Scaling to PDF

675 views
Skip to first unread message

fork...@gmail.com

unread,
Sep 16, 2013, 10:46:23 AM9/16/13
to flying-sa...@googlegroups.com
I'm having a little trouble scaling SVG images to a PDF. In the XHTML, I'm going to link to an external SVG file via an <object> tag. Ideally, I'd like to scale the image based on the size specified in CSS. The SVG file itself, in most cases, will probably supply a width and height in its outer <svg> tag, but it seems useful to be able to ultimately specify the size in CSS.

I haven't been able to find a comprehensive example of using SVGs rendered down to PDF (using Flying Saucer). In one popular example, I've had position issues as noted by others. So, I tried to come up with my own by combining a couple of sources:
  1. The Flying Saucer browser example which uses ITextReplacedElementFactory and ITextImageElement to render an <img> to a PDF.
  2. The SVG to PDF example in the iText in Action book.
The ITextImageElement class has given me no positioning issues, so it seemed like a good class to emulate. The class loads the <img> to a Flying Saucer FSImage. It then uses the drawImage method in Flying Saucer's ITextOutputDevice class. I figured if I could load an SVG into a FSImage, then I could use the drawImage method to let it handle positioning, etc. My code is working fine, except for the scaling. Any thoughts on how I could improve this code?

I created a class, SvgReplacedElement, that stores an org.w3c.dom.svg.SVGDocument. It's paint method then does:

public void paint(RenderingContext renderingContext, ITextOutputDevice outputDevice, BlockBox box) {
    
    // Follow the the SVG example in iText in Action
    PdfContentByte dc = outputDevice.getWriter().getDirectContent();
    UserAgent userAgent = new UserAgentAdapter();
    DocumentLoader loader = new DocumentLoader(userAgent);
    BridgeContext ctx = new BridgeContext(userAgent, loader);
    ctx.setDynamicState(BridgeContext.DYNAMIC);
    GVTBuilder builder = new GVTBuilder();
    // this.svg is an instance of org.w3c.dom.svg.SVGDocument
    GraphicsNode graphics = builder.build(ctx, this.svg);
    
    SVGSVGElement svgElement = this.svg.getRootElement();
    
    // Get the size specified in the SVG file. The size specified in CSS is already
    // available as instance variables cssWidth and cssHeight. 
    //
    // Here are are some example values that I'm seeing in the debugger.
    //
    // If I specify in CSS a width of .5in, the cssWidth is equal to 960.
    // If I specify in the external SVG file a size of .5in, the svgWidth is 48.
    float svgWidth = svgElement.getWidth().getBaseVal().getValue();
    float svgHeight = svgElement.getHeight().getBaseVal().getValue();
    
    
    // Continue on with converting to an iText image per this example class.
    
    /**
     * svgFactor needs to be calculated depending on the screen DPI by the PDF DPI
     * This is 96 / 72 = 4 / 3 ~= 1.3333333 on Windows, but might be different on *nix.
     */
    final float svgFactor = 25.4f / userAgent.getPixelUnitToMillimeter() / 72f; // 25.4 mm = 1 inch TODO: Might need to get 72 from somewhere else?
    
    PdfTemplate map = dc.createTemplate(cssWidth * svgFactor, cssHeight * svgFactor);
    Graphics2D g2d = new PdfGraphics2D(map, cssWidth * svgFactor, cssHeight * svgFactor);
    graphics.paint(g2d);
    g2d.dispose();
    

    // Now that we've loaded the SVG into iText, convert to an iText Image
    Image image = null;
    try
    {
        image = Image.getInstance(map);
    }
    catch (BadElementException e)
    {
        throw new XRRuntimeException("Unable to convert SVG to an iText image", e);
    }
    
    // At this point, should image.scaleToFit() be called? What is the proper arithmetic
    // to scale?. Ultimately, the image should scale to fit with the size specified in CSS.
    
    
    // Wrap the iText Image in a Flying Saucer FSImage to satisfy the drawImage() call below
    ITextFSImage fsImage = new ITextFSImage(image);
    
    
    // I'm not 100% sure if this is correct
    Rectangle contentBounds = box.getContentAreaEdge(box.getAbsX(), box.getAbsY(), renderingContext);
    
    outputDevice.drawImage(fsImage, contentBounds.x, contentBounds.y);




fork...@gmail.com

unread,
Sep 18, 2013, 10:54:36 AM9/18/13
to flying-sa...@googlegroups.com
I found a solution. After the Image is created, it is scaled like this:

image.scalePercent(100 * (this.cssWidth/svgWidth), 100 * (this.cssHeight/svgHeight));

That causes the image to scale such that it fits within the CSS height and width boundaries.
Reply all
Reply to author
Forward
0 new messages