/* * EuroCarbDB, a framework for carbohydrate bioinformatics * * Copyright (c) 2006-2009, Eurocarb project, or third-party contributors as * indicated by the @author tags or express copyright attribution * statements applied by the authors. * * This copyrighted material is made available to anyone wishing to use, modify, * copy, or redistribute it subject to the terms and conditions of the GNU * Lesser General Public License, as published by the Free Software Foundation. * A copy of this license accompanies this distribution in the file LICENSE.txt. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * Last commit: $Rev: 1210 $ by $Author: glycoslave $ on $Date:: 2009-06-12 #$ */ package org.eurocarbdb.resourcesdb.representation; import java.awt.Dimension; import java.awt.Shape; import java.io.*; import javax.swing.JFrame; import org.apache.batik.dom.svg.SVGDOMImplementation; import org.apache.batik.svggen.*; import org.apache.batik.swing.JSVGCanvas; import org.apache.batik.transcoder.TranscoderInput; import org.apache.batik.transcoder.TranscoderOutput; import org.apache.batik.transcoder.image.ImageTranscoder; import org.apache.batik.transcoder.image.JPEGTranscoder; import org.apache.batik.transcoder.image.PNGTranscoder; import org.eurocarbdb.resourcesdb.monosaccharide.Monosaccharide; import org.w3c.dom.DOMImplementation; import org.w3c.dom.svg.SVGDocument; /** * This class provides some general methods needed for generating SVG representations of monosaccharides and converting them to other graphics formats. * To actually generate the monosaccharide graphics, use the Classes that extend this one (Haworth.java, Fischer.java) * @see Haworth * @see Fischer * @author Thomas Lütteke */ public class SvgFactory { private int textSize = 11; private boolean resizeIfOutOfBounds = true; private boolean rescaleIfOutOfBounds = false; private SVGGraphics2D graph2D; private SVGDocument svgDoc; private int svgWidth = 0; private int svgHeight = 0; private Monosaccharide monosacc; private int xMin = Integer.MAX_VALUE; private int xMax = Integer.MIN_VALUE; private int yMin = Integer.MAX_VALUE; private int yMax = Integer.MIN_VALUE; private int currentGroupXShift = 0; private int currentGroupYShift = 0; //***************************************************************************** //*** Constructors: *********************************************************** //***************************************************************************** public SvgFactory() { this.setupFactory(); } public SvgFactory(boolean rescale, boolean resize) { this.setupFactory(); this.setRescaleIfOutOfBounds(rescale); this.setResizeIfOutOfBounds(resize); } /** * Create the SVGDocument and the SVGGraphics2D objects of this SvgFactory. * This method is called by the constructors. */ private void setupFactory() { DOMImplementation impl = SVGDOMImplementation.getDOMImplementation(); String svgNS = SVGDOMImplementation.SVG_NAMESPACE_URI; this.setSvgDoc((SVGDocument) impl.createDocument(svgNS, "svg", null)); SVGGeneratorContext ctx = SVGGeneratorContext.createDefault(this.getSvgDoc()); ctx.setComment("Generated by MonosaccharideDB (www.monosaccharidedb.org) with Batik SVG Generator"); this.setSVGGraph2D(new SVGGraphics2D(ctx, false)); } //***************************************************************************** //*** getters/setters: ******************************************************** //***************************************************************************** public int getTextSize() { return textSize; } public void setTextSize(int textSize) { this.textSize = textSize; if(this.getSVGGraph2D() != null) { this.getSVGGraph2D().setFont(this.getSVGGraph2D().getFont().deriveFont((float)textSize)); } } public int getSvgHeight() { return svgHeight; } public void setSvgHeight(int svgHeight) { this.svgHeight = svgHeight; } public int getSvgWidth() { return svgWidth; } public void setSvgWidth(int svgWidth) { this.svgWidth = svgWidth; } public boolean isRescaleIfOutOfBounds() { return rescaleIfOutOfBounds; } public void setRescaleIfOutOfBounds(boolean rescaleIfOutOfBounds) { this.rescaleIfOutOfBounds = rescaleIfOutOfBounds; } public boolean isResizeIfOutOfBounds() { return resizeIfOutOfBounds; } public void setResizeIfOutOfBounds(boolean resizeIfOutOfBounds) { this.resizeIfOutOfBounds = resizeIfOutOfBounds; } public SVGGraphics2D getSVGGraph2D() { return graph2D; } public void setSVGGraph2D(SVGGraphics2D graph2d) { this.setSVGGraph2D(graph2d, true); } public void setSVGGraph2D(SVGGraphics2D graph2d, boolean setFontSize) { if(setFontSize && this.getTextSize() > 0) { graph2d.setFont(graph2d.getFont().deriveFont((float) this.getTextSize())); } this.graph2D = graph2d; } public SVGDocument getSvgDoc() { return this.svgDoc; } public void setSvgDoc(SVGDocument svgdoc) { this.svgDoc = svgdoc; } public Monosaccharide getMonosacc() { return this.monosacc; } public void setMonosacc(Monosaccharide ms) { this.monosacc = ms; } public int getXMax() { return this.xMax; } public int getXMin() { return this.xMin; } public int getYMax() { return this.yMax; } public int getYMin() { return this.yMin; } public int getCurrentGroupXShift() { return this.currentGroupXShift; } public void setCurrentGroupXShift(int xShift) { this.currentGroupXShift = xShift; } public int getCurrentGroupYShift() { return this.currentGroupYShift; } public void setCurrentGroupYShift(int yShift) { this.currentGroupYShift = yShift; } /** * Get the top level group of the SVGGraph2D field of this object. * Using this method instead of getSVGGraph2D().getTopLevelGroup() preserves the group element in the SVGGraph2D object, * which is replaced by a new group element when the top level group is accessed directly. * @return the top level group of the SVGGraph2D or null if the SVGGraph2D is null itself */ protected org.w3c.dom.Element getTopLevelGroup() { if(this.getSVGGraph2D() == null) { return null; } org.w3c.dom.Element tlg = this.getSVGGraph2D().getTopLevelGroup(); this.getSVGGraph2D().setTopLevelGroup(tlg); return tlg; } public org.w3c.dom.Element getRoot() { return this.getRoot(null); } public org.w3c.dom.Element getRoot(org.w3c.dom.Element svgElem) { if(this.getSVGGraph2D() == null) { return null; } org.w3c.dom.Element tlg = this.getTopLevelGroup(); org.w3c.dom.Element root = this.getSVGGraph2D().getRoot(svgElem); this.getSVGGraph2D().setTopLevelGroup(tlg); return root; } //***************************************************************************** //*** drawing methods: ******************************************************** //***************************************************************************** protected void drawLine(int x1, int y1, int x2, int y2) { this.getSVGGraph2D().drawLine(x1, y1, x2, y2); this.checkMinMax(x1, y1); this.checkMinMax(x2, y2); } protected void drawLineWithOffset(int x1, int y1, int x2, int y2, double offset) { int dx = x2 - x1; int dy = y2 - y1; int lineLength = (int)Math.floor(Math.sqrt(dx * dx + dy * dy) + 0.5); x1 = x1 + (int)Math.floor((dx * (offset / lineLength)) + 0.5); y1 = y1 + (int)Math.floor((dy * (offset / lineLength)) + 0.5); this.getSVGGraph2D().drawLine(x1, y1, x2, y2); } protected void drawShape(Shape s) { this.getSVGGraph2D().draw(s); this.checkMinMax((int)s.getBounds2D().getMaxX(), (int)s.getBounds2D().getMaxY()); this.checkMinMax((int)s.getBounds2D().getMinX(), (int)s.getBounds2D().getMinY()); } protected void fillShape(Shape s) { this.getSVGGraph2D().fill(s); this.checkMinMax((int)s.getBounds2D().getMaxX(), (int)s.getBounds2D().getMaxY()); this.checkMinMax((int)s.getBounds2D().getMinX(), (int)s.getBounds2D().getMinY()); } protected void drawString(String str, int x, int y) { this.getSVGGraph2D().drawString(str, x, y); this.checkMinMax(x, y); this.checkMinMax(x + this.getSVGGraph2D().getFontMetrics().stringWidth(str), y - this.getSVGGraph2D().getFontMetrics().getHeight()); } /** * Add another SvgFactory as a subtree to this SvgFactory * @param subSvg the SvgFactory to add * @param x the x position of the upper left corner of the added factory within this factory * @param y the y position of the upper left corner of the added factory within this factory */ protected void addSubGraphic(SvgFactory subSvg, int x, int y) { /*subSvg.translate(x, y); this.getTopLevelGroup().appendChild(subSvg.getTopLevelGroup()); this.checkMinMax(subSvg.getXMin(), subSvg.getYMin()); this.checkMinMax(subSvg.getXMax(), subSvg.getYMax());*/ } //***************************************************************************** //*** transforming methods: *************************************************** //***************************************************************************** /*protected void addToplevelGroupWithTranslate(SvgFactory svgFact, int translateX, int translateY) { org.w3c.dom.Element tlg = svgFact.getTopLevelGroup(); tlg.setAttribute("transform", "translate(" + translateX + "," + translateY + ")"); this.getSvgDoc().appendChild(tlg); this.checkMinMax(svgFact.getXMin() + translateX, svgFact.getYMin() + translateY); this.checkMinMax(svgFact.getXMax() + translateX, svgFact.getYMax() + translateY); }*/ protected void translate(int translateX, int translateY) { this.xMin += translateX; this.xMax += translateX; this.yMin += translateY; this.yMax += translateY; org.w3c.dom.Element g = this.getSVGGraph2D().getTopLevelGroup(); org.w3c.dom.Element tlg = this.getTopLevelGroup(); tlg.setAttribute("transform", "translate(" + translateX + "," + translateY + ")"); tlg.appendChild(g); } protected void scale(double scaleFactor) { if(scaleFactor == 0.0) { return; } this.xMin *= scaleFactor; this.xMax *= scaleFactor; this.yMin *= scaleFactor; this.yMax *= scaleFactor; org.w3c.dom.Element g = this.getSVGGraph2D().getTopLevelGroup(); org.w3c.dom.Element tlg = this.getTopLevelGroup(); tlg.setAttribute("transform", "scale(" + String.valueOf(Math.floor(scaleFactor * 10) / 10) + ")"); tlg.appendChild(g); } //***************************************************************************** //*** other methods: ********************************************************** //***************************************************************************** /** * Get the offset that is to be added to the x position of a text to have the center of the first or last character of that text at the current x position. * @param text the text to be aligned * @param alignRight if set to true, the last character will be aligned, otherwise the first one * @return */ protected int getCharacterCenterXOffset(String text, boolean alignRight) { int offset = 0; if(text == null || text.length() == 0) { return 0; } SVGGraphics2D g2d = this.getSVGGraph2D(); char centeredChar = text.charAt(0); if(alignRight) { centeredChar = text.charAt(text.length() - 1); } else { centeredChar = text.charAt(0); } int ccWidth = g2d.getFontMetrics().charWidth(centeredChar); if(alignRight) { offset = -g2d.getFontMetrics().stringWidth(text) + (ccWidth / 2); } else { offset = -ccWidth / 2; } return offset; } protected int getStringWidth(String str) { return this.getSVGGraph2D().getFontMetrics().stringWidth(str); } /** * Check, if the given x and y coordinates are within the current range of coordinates (min/max x/y values used by the svg elements), * and adjust the current range if they are not. * @param x * @param y */ private void checkMinMax(int x, int y) { if(x + this.currentGroupXShift < this.xMin) { this.xMin = x + this.currentGroupXShift; } if(x + this.currentGroupXShift > this.xMax) { this.xMax = x + this.currentGroupXShift; } if(y + this.currentGroupYShift < this.yMin) { this.yMin = y + this.currentGroupYShift; } if(y + this.currentGroupYShift > this.yMax) { this.yMax = y + this.currentGroupYShift; } } /** * Check, if there are any coordinates outside the range that is displayed in this svg graphic * @return true, if isOutOfXBounds() or isOutOfYBounds() is true */ protected boolean isOutOfBounds() { return (this.isOutOfXBounds() || this.isOutOfYBounds()); } /** * Check, if there are x coordinates outside the range that is displayed in this svg graphic * @return true, if xMin is smaller than zero or xMax is larger or equal the svg witdh */ protected boolean isOutOfXBounds() { if(this.xMin < 0 || this.xMax >= this.getSvgWidth()) { return true; } return false; } /** * Check, if there are y coordinates outside the range that is displayed in this svg graphic * @return true, if yMin is smaller than zero or yMax is larger or equal the svg height */ protected boolean isOutOfYBounds() { if(this.yMin < 0 || this.yMax >= this.getSvgHeight()) { return true; } return false; } /** * Check, if the image fits into the boundaries set in the svgWidth and svgHeight fields. * If it doesn't, this method tries to adjust the image: * If isResizeIfOutOfBounds() is true, the svgWidth or svgHeight fields are adjusted to fit the image. * Otherwise, if isRescaleIfOutOfBounds() is true, the image is scaled to fit into the given boundaries. * @return true, if the image fits into the boundaries (or could be adjusted to fit), otherwise false */ public boolean checkSize() { //*** make sure that svgWidth and svgHeight are not 0: *** if(this.getSvgWidth() == 0) { if(this.isResizeIfOutOfBounds()) { this.setSvgWidth(this.xMax - this.xMin + 1); } else { return false; } } if(this.getSvgHeight() == 0) { if(this.isResizeIfOutOfBounds()) { this.setSvgHeight(this.yMax - this.yMin + 1); } else { return false; } } //*** try and move graphic so that there is no overlap on the left and on the top: *** int translateX = 0; int translateY = 0; if(this.isOutOfXBounds()) { if(this.isRescaleIfOutOfBounds() || this.isResizeIfOutOfBounds() || ((this.xMax - this.xMin) < this.svgWidth)) { translateX = 0 - this.xMin; } } if(this.isOutOfYBounds()) { if(this.isRescaleIfOutOfBounds() || this.isResizeIfOutOfBounds() || ((this.yMax - this.yMin) < this.svgHeight)) { translateY = 0 - this.yMin; } } if(translateX != 0 || translateY != 0) { this.translate(translateX, translateY); //org.w3c.dom.Element tlg = this.getTopLevelGroup(); //tlg.setAttribute("transform", "translate(" + translateX + "," + translateY + ")"); } //*** if graphic still does not fit boundaries, then rescale graphic or resize svg range: *** if(this.isOutOfBounds()) { if(this.isResizeIfOutOfBounds()) { //*** enlarge svg width and/or height to fit the entire graphic *** if(this.xMax >= this.svgWidth) { this.svgWidth = this.xMax + 1; } if(this.yMax >= this.svgHeight) { this.svgHeight = this.yMax + 1; } this.getSVGGraph2D().setSVGCanvasSize(new Dimension(this.svgWidth, this.svgHeight)); } else if(this.isRescaleIfOutOfBounds()) { //*** rescale graphic to fit it into the given svg size *** double scaleFactorX = ((double)this.svgWidth) / this.xMax; double scaleFactorY = ((double)this.svgHeight) / this.yMax; double scaleFactor = Math.min(scaleFactorX, scaleFactorY); /*org.w3c.dom.Element g = this.getSVGGraph2D().getTopLevelGroup(); org.w3c.dom.Element tlg = this.getTopLevelGroup(); tlg.appendChild(g); tlg.setAttribute("transform", "translate(" + translateX + "," + translateY + ")"); g.setAttribute("transform", "scale(" + String.valueOf(Math.floor(scaleFactor * 10) / 10) + ")");*/ this.scale(scaleFactor); this.translate(translateX, translateY); } else { return false; } } return true; } //***************************************************************************** //*** export methods: ********************************************************* //***************************************************************************** public void stream(Writer out, boolean useCss) throws SVGGraphics2DIOException { SVGGraphics2D g = (SVGGraphics2D) this.getSVGGraph2D(); org.w3c.dom.Element root = this.getRoot(); g.stream(root, out, useCss); } public void stream(Writer out) throws SVGGraphics2DIOException { this.stream(out, false); } public String getSvgString() { String outStr = null; StringWriter outWriter = new StringWriter(); try { this.stream(outWriter); outStr = outWriter.getBuffer().toString(); } catch(SVGGraphics2DIOException sgio) { System.out.println("Exception: " + sgio); } return outStr; } public byte[] getSvgByteArr() { String svgStr = this.getSvgString(); if(svgStr == null) { return null; } return svgStr.getBytes(); } public void display() { SVGDocument doc = this.getSvgDoc(); JSVGCanvas canvas = new JSVGCanvas(); org.w3c.dom.Element root = doc.getDocumentElement(); this.getRoot(root); JFrame f = new JFrame(); f.getContentPane().add(canvas); canvas.setSVGDocument(doc); f.pack(); f.setVisible(true); //f.dispose(); } public byte[] createJpgImage() throws Exception { //*** Create a JPEGTranscoder and set its quality hint *** JPEGTranscoder t = new JPEGTranscoder(); t.addTranscodingHint(JPEGTranscoder.KEY_QUALITY, new Float(.8)); //*** Perform the transcoding *** return this.createImage(t); } public byte[] createPngImage() throws Exception { //*** Create a PNGTranscoder *** PNGTranscoder t = new PNGTranscoder(); //*** Perform the transcoding *** return this.createImage(t); } private byte[] createImage(ImageTranscoder t) throws Exception { //*** get the SVG document *** SVGDocument doc = this.getSvgDoc(); org.w3c.dom.Element root = doc.getDocumentElement(); this.getRoot(root); //*** set the transcoder input and output *** TranscoderInput input = new TranscoderInput(doc); ByteArrayOutputStream ostream = new ByteArrayOutputStream(); TranscoderOutput output = new TranscoderOutput(ostream); //*** perform the transcoding *** t.transcode(input, output); ostream.flush(); ostream.close(); return ostream.toByteArray(); } }