From 9dafe173d0ad250d90411412800b9605adb46eae Mon Sep 17 00:00:00 2001 From: Keiron Liddle Date: Fri, 5 Oct 2001 09:49:21 +0000 Subject: [PATCH] added svg renderer git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@194489 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/fop/apps/CommandLineOptions.java | 17 + src/org/apache/fop/apps/Driver.java | 9 + .../apache/fop/render/svg/SVGRenderer.java | 640 ++++++++++++++++++ 3 files changed, 666 insertions(+) create mode 100644 src/org/apache/fop/render/svg/SVGRenderer.java diff --git a/src/org/apache/fop/apps/CommandLineOptions.java b/src/org/apache/fop/apps/CommandLineOptions.java index 29039d780..44737ef62 100644 --- a/src/org/apache/fop/apps/CommandLineOptions.java +++ b/src/org/apache/fop/apps/CommandLineOptions.java @@ -48,6 +48,8 @@ public class CommandLineOptions { private static final int PS_OUTPUT = 6; /* output: text file */ private static final int TXT_OUTPUT = 7; + /* output: svg file */ + private static final int SVG_OUTPUT = 8; /* System buffers */ private static final int BUFFER_FILE = 8; @@ -247,6 +249,14 @@ public class CommandLineOptions { outfile = new File(args[i + 1]); i++; } + } else if (args[i].equals("-svg")) { + setOutputMode(SVG_OUTPUT); + if ((i + 1 == args.length) + || (args[i + 1].charAt(0) == '-')) { + throw new FOPException("you must specify the svg output file"); } else { + outfile = new File(args[i + 1]); + i++; + } } else if (args[i].charAt(0) != '-') { if (inputmode == NOT_SET) { inputmode = FO_INPUT; @@ -376,6 +386,8 @@ public class CommandLineOptions { return Driver.RENDER_PS; case TXT_OUTPUT: return Driver.RENDER_TXT; + case SVG_OUTPUT: + return Driver.RENDER_SVG; case AREA_OUTPUT: rendererOptions.put("fineDetail", isCoarseAreaXml()); return Driver.RENDER_XML; @@ -532,6 +544,7 @@ public class CommandLineOptions { + " -pcl outfile input will be rendered as pcl file (outfile req'd) \n" + " -ps outfile input will be rendered as PostScript file (outfile req'd) \n" + " -txt outfile input will be rendered as text file (outfile req'd) \n" + + " -svg outfile input will be rendered as an svg slides file (outfile req'd) \n" + " -at outfile representation of area tree as XML (outfile req'd) \n" + " -print input file will be rendered and sent to the printer \n" + " see options with \"-print help\" \n\n" @@ -614,6 +627,10 @@ public class CommandLineOptions { log.debug("txt"); log.debug("output file: " + outfile.toString()); break; + case SVG_OUTPUT: + log.debug("svg"); + log.debug("output file: " + outfile.toString()); + break; default: log.debug("unknown input type"); } diff --git a/src/org/apache/fop/apps/Driver.java b/src/org/apache/fop/apps/Driver.java index 8d11fc8cd..203d062e1 100644 --- a/src/org/apache/fop/apps/Driver.java +++ b/src/org/apache/fop/apps/Driver.java @@ -131,6 +131,11 @@ public class Driver implements Loggable { */ public static final int RENDER_TXT = 8; + /** + * Render to SVG. OutputStream must be set + */ + public static final int RENDER_SVG = 9; + /** * the FO tree builder */ @@ -308,6 +313,7 @@ public class Driver implements Loggable { *
  • RENDER_PCL *
  • RENDER_PS *
  • RENDER_TXT + *
  • RENDER_SVG * * @param renderer the type of renderer to use */ @@ -335,6 +341,9 @@ public class Driver implements Loggable { case RENDER_XML: setRenderer(new org.apache.fop.render.xml.XMLRenderer()); break; + case RENDER_SVG: + setRenderer(new org.apache.fop.render.svg.SVGRenderer()); + break; default: throw new IllegalArgumentException("Unknown renderer type"); } diff --git a/src/org/apache/fop/render/svg/SVGRenderer.java b/src/org/apache/fop/render/svg/SVGRenderer.java new file mode 100644 index 000000000..af616d6d5 --- /dev/null +++ b/src/org/apache/fop/render/svg/SVGRenderer.java @@ -0,0 +1,640 @@ +/* + * $Id$ + * Copyright (C) 2001 The Apache Software Foundation. All rights reserved. + * For details on use and redistribution please refer to the + * LICENSE file included with these sources. + */ + +package org.apache.fop.render.svg; + +import org.apache.fop.layout.*; +import org.apache.fop.layout.inline.*; +import org.apache.fop.datatypes.*; +import org.apache.fop.image.*; +import org.apache.fop.svg.*; +import org.apache.fop.render.pdf.*; +import org.apache.fop.render.awt.*; +import org.apache.fop.viewer.*; +import org.apache.fop.apps.*; +import org.apache.fop.svg.SVGUtilities; + +import org.w3c.dom.*; +import org.w3c.dom.svg.*; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.DOMImplementation; + +import org.apache.batik.bridge.*; +import org.apache.batik.swing.svg.*; +import org.apache.batik.swing.gvt.*; +import org.apache.batik.gvt.*; +import org.apache.batik.gvt.renderer.*; +import org.apache.batik.gvt.filter.*; +import org.apache.batik.gvt.event.*; +import org.apache.batik.dom.svg.SVGDOMImplementation; +import org.apache.batik.dom.svg.SVGOMElement; +import org.apache.batik.dom.util.XMLSupport; +import org.apache.batik.transcoder.svg2svg.SVGTranscoder; +import org.apache.batik.transcoder.TranscoderInput; +import org.apache.batik.transcoder.TranscoderOutput; +import org.apache.batik.transcoder.TranscoderException; + +import java.awt.*; +import java.awt.Image; +import java.awt.image.*; +import java.awt.geom.*; +import java.awt.font.*; +import java.util.*; +import java.net.URL; +import java.net.MalformedURLException; +import java.io.*; +import java.beans.*; +import javax.swing.*; +import java.awt.print.*; +import java.awt.image.BufferedImage; +import java.text.*; + +import org.apache.fop.render.AbstractRenderer; + +import org.apache.batik.util.SVGConstants; +import org.apache.batik.svggen.SVGGraphics2D; +import org.apache.batik.dom.svg.ExtensibleSVGDOMImplementation; + +public class SVGRenderer extends AbstractRenderer { + static final String svgNS = SVGDOMImplementation.SVG_NAMESPACE_URI; + Document svgDocument; + Element svgRoot; + Element currentPageG = null; + Element lastLink = null; + + float totalWidth = 0; + float totalHeight = 0; + + protected int pageWidth = 0; + protected int pageHeight = 0; + protected int pageNumber = 0; + + protected Hashtable fontNames = new Hashtable(); + protected Hashtable fontStyles = new Hashtable(); + protected Color saveColor = null; + + protected IDReferences idReferences = null; + + /** + * The current (internal) font name + */ + protected String currentFontName; + + /** + * The current font size in millipoints + */ + protected int currentFontSize; + + /** + * The current colour's red, green and blue component + */ + protected float currentRed = 0; + protected float currentGreen = 0; + protected float currentBlue = 0; + + /** + * The parent component, used to set up the font. + * This is needed as FontSetup needs a live AWT component + * in order to generate valid font measures. + */ + protected Component parent; + + /** + * options + */ + protected Hashtable options; + + /** + * set up renderer options + */ + public void setOptions(Hashtable options) { + this.options = options; + } + + public SVGRenderer() { + } + + /** + * add a line to the current stream + * + * @param x1 the start x location in millipoints + * @param y1 the start y location in millipoints + * @param x2 the end x location in millipoints + * @param y2 the end y location in millipoints + * @param th the thickness in millipoints + * @param r the red component + * @param g the green component + * @param b the blue component + */ + protected void addLine(int x1, int y1, int x2, int y2, int th, float r, + float g, float b) { + Element line = SVGUtilities.createLine(svgDocument, x1 / 1000f, pageHeight - (y1 / 1000f), x2 / 1000f, pageHeight - (y2 / 1000f)); + line.setAttributeNS(null, "style", "stroke-width:" + (Math.abs(th) / 1000f) + + ";stroke:rgb(" + ((int)(255 * r)) + "," + ((int)(255 * g)) + "," + ((int)(255 * b)) + ")"); + currentPageG.appendChild(line); + } + + /** + * draw a rectangle + * + * @param x the x position of left edge in millipoints + * @param y the y position of top edge in millipoints + * @param w the width in millipoints + * @param h the height in millipoints + * @param r the red component + * @param g the green component + * @param b the blue component + */ + protected void addRect(int x, int y, int w, int h, float r, float g, + float b) { + Element rect = SVGUtilities.createRect(svgDocument, x / 1000f, pageHeight - (y / 1000f), w / 1000f, h / 1000f); + rect.setAttributeNS(null, "style", "stroke:rgb(" + ((int)(255 * r)) + "," + ((int)(255 * g)) + "," + ((int)(255 * b)) + ")"); + currentPageG.appendChild(rect); + } + + /** + * draw a filled rectangle + * + * @param x the x position of left edge in millipoints + * @param y the y position of top edge in millipoints + * @param w the width in millipoints + * @param h the height in millipoints + * @param r the red component of edges + * @param g the green component of edges + * @param b the blue component of edges + * @param fr the red component of the fill + * @param fg the green component of the fill + * @param fb the blue component of the fill + */ + protected void addRect(int x, int y, int w, int h, float r, float g, + float b, float fr, float fg, float fb) { + Element rect = SVGUtilities.createRect(svgDocument, x / 1000f, pageHeight - (y / 1000f), w / 1000f, h / 1000f); + rect.setAttributeNS(null, "style", "stroke:rgb(" + ((int)(255 * r)) + "," + ((int)(255 * g)) + "," + ((int)(255 * b)) + ");fill:rgb(" + ((int)(255 * fr)) + "," + ((int)(255 * fg)) + "," + ((int)(255 * fb)) + ")"); + currentPageG.appendChild(rect); + } + + /** + * draw a filled rectangle in the current color + * + * @param x the x position of left edge in millipoints + * @param y the y position of top edge in millipoints + * @param w the width in millipoints + * @param h the height in millipoints + * @param drawAsOutline true for draw, false for fill + */ + protected void addRect(int x, int y, int w, int h, + boolean drawAsOutline) { + int startx = (x + 500) / 1000; + int starty = pageHeight - ((y + 500) / 1000); + int endx = (x + w + 500) / 1000; + int endy = pageHeight - ((y + h + 500) / 1000); + if (drawAsOutline) { + Element rect = SVGUtilities.createRect(svgDocument, startx, starty, endx - startx, endy - starty); + rect.setAttributeNS(null, "style", "fill:none"); + currentPageG.appendChild(rect); + } else { + Element rect = SVGUtilities.createRect(svgDocument, startx, starty, endx - startx, starty - endy); + rect.setAttributeNS(null, "style", "stroke:none"); + currentPageG.appendChild(rect); + } + } + + protected void addFilledRect(int x, int y, int w, int h, + ColorType col) { + float r = col.red(); + float g = col.green(); + float b = col.blue(); + addRect(x, y, w, h, r, g, b, r, g, b); + } + + protected void drawFrame() { + int width = pageWidth; + int height = pageHeight; + Element rect = SVGUtilities.createRect(svgDocument, 0, 0, width, height); + rect.setAttributeNS(null, "style", "fill:none;stroke:black"); + currentPageG.appendChild(rect); + } + + public void render(Page page, OutputStream stream) + throws IOException { + pageNumber++; + this.render(page); + } + + public void render(Page page) + throws IOException { + idReferences = page.getIDReferences(); + + int lastWidth = pageWidth; + int lastHeight = pageHeight; + + pageWidth = (int)((float)page.getWidth() / 1000f + .5); + pageHeight = (int)((float)page.getHeight() / 1000f + .5); + + if(lastLink != null && currentPageG != null) { + lastLink.setAttributeNS(null, "xlink:href", "#svgView(viewBox(0, "+ (totalHeight) + ", " + pageWidth + ", " + pageHeight + "))"); + currentPageG.appendChild(lastLink); + } + + totalHeight += pageHeight; + if(totalWidth < pageWidth) { + totalWidth = pageWidth; + } + + currentPageG = SVGUtilities.createG(svgDocument); + currentPageG.setAttributeNS(null, "id", /*title + */"Page-" + pageNumber); + currentPageG.setAttributeNS(null, "style", "font-family:sanserif;font-size:12"); + svgRoot.appendChild(currentPageG); + + drawFrame(); + + renderPage(page); + + currentPageG.setAttributeNS(null, "transform", "translate(0," + (totalHeight - pageHeight) + ")"); + + Element lastPageLink = svgDocument.createElementNS(svgNS, "a"); + if(lastLink != null) { + lastPageLink.setAttributeNS(null, "xlink:href", "#svgView(viewBox(0, " + (totalHeight - pageHeight - lastHeight) + ", " + lastWidth + ", " + lastHeight + "))"); + } else { + lastPageLink.setAttributeNS(null, "xlink:href", "#svgView(viewBox(0, " + (totalHeight - pageHeight) + ", " + pageWidth + ", " + pageHeight + "))"); + } + currentPageG.appendChild(lastPageLink); + Element rect = SVGUtilities.createRect(svgDocument, 0, 0, pageWidth / 2, pageHeight); + rect.setAttributeNS(null, "style", "fill:blue;visibility:hidden"); + lastPageLink.appendChild(rect); + + lastLink = svgDocument.createElementNS(svgNS, "a"); + rect = SVGUtilities.createRect(svgDocument, pageWidth / 2, 0, pageWidth / 2, pageHeight); + rect.setAttributeNS(null, "style", "fill:blue;visibility:hidden"); + lastLink.appendChild(rect); + + /* + * if (page.hasLinks()) { + * .... + * } + */ + + } + + public void renderPage(Page page) { + BodyAreaContainer body; + AreaContainer before, after; + + body = page.getBody(); + before = page.getBefore(); + after = page.getAfter(); + + this.currentFontName = ""; + this.currentFontSize = 0; + + renderBodyAreaContainer(body); + + if (before != null) { + renderAreaContainer(before); + } + + if (after != null) { + renderAreaContainer(after); + } + } + + protected void doFrame(org.apache.fop.layout.Area area) { + int w, h; + int rx = this.currentAreaContainerXPosition; + w = area.getContentWidth(); + + if (area instanceof BlockArea) { + rx += ((BlockArea)area).getStartIndent(); + } + + h = area.getContentHeight(); + int ry = this.currentYPosition; + ColorType bg = area.getBackgroundColor(); + + rx = rx - area.getPaddingLeft(); + ry = ry + area.getPaddingTop(); + w = w + area.getPaddingLeft() + area.getPaddingRight(); + h = h + area.getPaddingTop() + area.getPaddingBottom(); + + // I'm not sure I should have to check for bg being null + // but I do + if ((bg != null) && (bg.alpha() == 0)) { + this.addRect(rx, ry, w, h, bg.red(), bg.green(), bg.blue(), + bg.red(), bg.green(), bg.blue()); + } + + rx = rx - area.getBorderLeftWidth(); + ry = ry + area.getBorderTopWidth(); + w = w + area.getBorderLeftWidth() + area.getBorderRightWidth(); + h = h + area.getBorderTopWidth() + area.getBorderBottomWidth(); + + BorderAndPadding bp = area.getBorderAndPadding(); + ColorType borderColor; + + if (area.getBorderTopWidth() != 0) { + borderColor = bp.getBorderColor(BorderAndPadding.TOP); + addLine(rx, ry, rx + w, ry, area.getBorderTopWidth(), + borderColor.red(), borderColor.green(), + borderColor.blue()); + } + + if (area.getBorderLeftWidth() != 0) { + borderColor = bp.getBorderColor(BorderAndPadding.LEFT); + addLine(rx, ry, rx, ry - h, area.getBorderLeftWidth(), + borderColor.red(), borderColor.green(), + borderColor.blue()); + } + + if (area.getBorderRightWidth() != 0) { + borderColor = bp.getBorderColor(BorderAndPadding.RIGHT); + addLine(rx + w, ry, rx + w, ry - h, + area.getBorderRightWidth(), borderColor.red(), + borderColor.green(), + borderColor.blue()); + } + + if (area.getBorderBottomWidth() != 0) { + borderColor = bp.getBorderColor(BorderAndPadding.BOTTOM); + addLine(rx, ry - h, rx + w, ry - h, area.getBorderBottomWidth(), + borderColor.red(), borderColor.green(), + borderColor.blue()); + } + } + + protected Rectangle2D getBounds(org.apache.fop.layout.Area a) { + return new Rectangle2D.Double(currentAreaContainerXPosition, + currentYPosition, + a.getAllocationWidth(), a.getHeight()); + } + + public void setupFontInfo(FontInfo fontInfo) { + // create a temp Image to test font metrics on + BufferedImage fontImage = + new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB); + org.apache.fop.render.awt.FontSetup.setup(fontInfo, fontImage.createGraphics()); + } + + public void renderDisplaySpace(DisplaySpace space) { + int d = space.getSize(); + this.currentYPosition -= d; + } + + public void renderImageArea(ImageArea area) { + + int x = currentXPosition + area.getXOffset(); + int y = currentYPosition; + int w = area.getContentWidth(); + int h = area.getHeight(); + this.currentYPosition -= h; + + FopImage img = area.getImage(); + + if (img == null) { + log.error("Error while loading image : area.getImage() is null"); + + addRect(x, y, w, h, true); // use helper function + + } else { + if (img instanceof SVGImage) { + try { + SVGDocument svg = ((SVGImage)img).getSVGDocument(); + renderSVGDocument(svg, x / 1000f, pageHeight - y / 1000f); + } catch (FopImageException e) {} + + } else { + + String urlString = img.getURL(); + try { + URL url = new URL(urlString); + + ImageIcon icon = new ImageIcon(url); + Image image = icon.getImage(); + + int startx = (x + 500) / 1000; + int starty = pageHeight - ((y + 500) / 1000); + int endx = (x + w + 500) / 1000; + int endy = pageHeight - ((y + h + 500) / 1000); + + // reverse start and end y because h is positive + //graphics.drawImage(image, startx, starty, endx - startx, + // starty - endy, null); + + } catch (MalformedURLException mue) { + // cannot normally occur because, if URL is wrong, constructing FopImage + // will already have failed earlier on + } + } + } + + this.currentXPosition += area.getContentWidth(); + } + + public void renderWordArea(WordArea area) { + char ch; + StringBuffer pdf = new StringBuffer(); + + String name = area.getFontState().getFontFamily(); + int size = area.getFontState().getFontSize(); + boolean underlined = area.getUnderlined(); + + float red = area.getRed(); + float green = area.getGreen(); + float blue = area.getBlue(); + + if ((!name.equals(this.currentFontName)) + || (size != this.currentFontSize)) { + this.currentFontName = name; + this.currentFontSize = size; + } + + if ((red != this.currentRed) || (green != this.currentGreen) + || (blue != this.currentBlue)) { + this.currentRed = red; + this.currentGreen = green; + this.currentBlue = blue; + } + + int rx = this.currentXPosition; + int bl = this.currentYPosition; + + String s; // = area.getText(); + if (area.getPageNumberID() + != null) { // this text is a page number, so resolve it + s = idReferences.getPageNumber(area.getPageNumberID()); + if (s == null) { + s = ""; + } + } else { + s = area.getText(); + } + + if (saveColor != null) { + if (saveColor.getRed() != red || saveColor.getGreen() != green + || saveColor.getBlue() != blue) { + saveColor = new Color(red, green, blue); + } + } else { + saveColor = new Color(red, green, blue); + } + + Element text = SVGUtilities.createText(svgDocument, rx / 1000f, pageHeight - bl / 1000f, s); + String st = null; + if(!"sans-serif".equals(this.currentFontName)) { + st = "font-family:" + this.currentFontName; + } + if(this.currentFontSize != 12000) { + if(st == null) { + st = ""; + } else { + st += ";"; + } + st += "font-size:" + (this.currentFontSize / 1000f); + } + if(red != 0 || green != 0 || blue != 0) { + if(st == null) { + st = ""; + } else { + st += ";"; + } + st += "fill:rgb(" + ((int)(255 * red)) + "," + ((int)(255 * green)) + "," + ((int)(255 * blue)) + ")"; + } + String fweight = area.getFontState().getFontWeight(); + if(!"normal".equals(fweight)) { + if(st == null) { + st = ""; + } else { + st += ";"; + } + st += "font-weight:" + fweight; + } + String fstyle = area.getFontState().getFontStyle(); + if(!"normal".equals(fstyle)) { + if(st == null) { + st = ""; + } else { + st += ";"; + } + st += "font-style:" + fstyle; + } + + if(st != null) { + text.setAttributeNS(null, "style", st); + } + currentPageG.appendChild(text); + + this.currentXPosition += area.getContentWidth(); + } + + public void renderInlineSpace(InlineSpace space) { + this.currentXPosition += space.getSize(); + } + + /** + * + * @param area area to render + */ + public void renderLeaderArea(LeaderArea area) { + + int rx = this.currentXPosition; + int ry = this.currentYPosition; + int w = area.getLeaderLength(); + int h = area.getHeight(); + int th = area.getRuleThickness(); + int st = area.getRuleStyle(); // not used at the moment + float r = area.getRed(); + float g = area.getGreen(); + float b = area.getBlue(); + + //graphics.setColor(new Color(r, g, b)); + + addRect(rx, ry, w, th, false); + + this.currentXPosition += area.getContentWidth(); + } + + public void renderSVGArea(SVGArea area) { + + float x = this.currentXPosition / 1000f; + float y = pageHeight - this.currentYPosition / 1000f; + int w = area.getContentWidth(); + int h = area.getHeight(); + + Document doc = area.getSVGDocument(); + renderSVGDocument(doc, x, y); + this.currentXPosition += area.getContentWidth(); + } + + protected void renderSVGDocument(Document doc, float x, float y) { + SVGSVGElement svg = ((SVGDocument)doc).getRootElement(); + Element view = svgDocument.createElementNS(svgNS, "svg"); + Node newsvg = svgDocument.importNode(svg, true); + //view.setAttributeNS(null, "viewBox", "0 0 "); + view.setAttributeNS(null, "x", "" + x); + view.setAttributeNS(null, "y", "" + y); + + // this fixes a problem where the xmlns is repeated sometimes + Element ele = (Element)newsvg; + ele.setAttributeNS(XMLSupport.XMLNS_NAMESPACE_URI, "xmlns", svgNS); + if(ele.hasAttributeNS(null, "xmlns")) { + ele.removeAttributeNS(null, "xmlns"); + } + + view.appendChild(newsvg); + currentPageG.appendChild(view); + } + + public void setProducer(String producer) { + // defined in Renderer Interface + } + + public static Color colorType2Color(ColorType ct) { + if (ct == null) { + return null; + } + return new Color(ct.red(), ct.green(), ct.blue()); + } + + public void renderForeignObjectArea(ForeignObjectArea area) { + area.getObject().render(this); + } + + public void startRenderer(OutputStream outputStream) + throws IOException { + DOMImplementation impl = SVGDOMImplementation.getDOMImplementation(); + svgDocument = impl.createDocument(svgNS, "svg", null); + + svgRoot = svgDocument.getDocumentElement(); + } + + public void stopRenderer(OutputStream outputStream) + throws IOException { + svgRoot.setAttributeNS(null, "width", "" + totalWidth); + svgRoot.setAttributeNS(null, "height", "" + totalHeight); + //svgRoot.setAttributeNS(null, "viewBox", "0 0 " + pageWidth + " " + pageHeight); + SVGTranscoder svgT = new SVGTranscoder(); + TranscoderInput input = new TranscoderInput(svgDocument); + TranscoderOutput output = new TranscoderOutput(new OutputStreamWriter(outputStream)); + try { + svgT.transcode(input, output); + } catch(TranscoderException e) { + log.error("could not write svg file :" + e.getMessage(), e); + } + outputStream.flush(); + + svgDocument = null; + svgRoot = null; + currentPageG = null; + lastLink = null; + + totalWidth = 0; + totalHeight = 0; + + pageNumber = 0; + } + +} -- 2.39.5