/*
* 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();
}
}