Package org.openscience.jchempaint.renderer.visitor

Source Code of org.openscience.jchempaint.renderer.visitor.SVGGenerator

/**
*  Copyright (C) 2001-2007  The Chemistry Development Kit (CDK) Project
*
*  Contact: cdk-devel@lists.sourceforge.net
*
*  This program is free software; you can redistribute it and/or
*  modify it under the terms of the GNU Lesser General Public License
*  as published by the Free Software Foundation; either version 2.1
*  of the License, or (at your option) any later version.
*  All we ask is that proper credit is given for our work, which includes
*  - but is not limited to - adding the above copyright notice to the beginning
*  of your source code files, and to any copyright notice that you may distribute
*  with programs based on this work.
*
*  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.
*
*  You should have received a copy of the GNU Lesser General Public License
*  along with this program; if not, write to the Free Software
*  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
*/
package org.openscience.jchempaint.renderer.visitor;

import java.awt.Color;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import javax.vecmath.Point2d;
import javax.vecmath.Vector2d;

import org.openscience.cdk.renderer.font.IFontManager;
import org.openscience.jchempaint.renderer.JChemPaintRendererModel;
import org.openscience.cdk.renderer.elements.ArrowElement;
import org.openscience.cdk.renderer.elements.AtomSymbolElement;
import org.openscience.cdk.renderer.elements.ElementGroup;
import org.openscience.cdk.renderer.elements.IRenderingElement;
import org.openscience.cdk.renderer.elements.LineElement;
import org.openscience.cdk.renderer.elements.OvalElement;
import org.openscience.cdk.renderer.elements.PathElement;
import org.openscience.cdk.renderer.elements.RectangleElement;
import org.openscience.jchempaint.renderer.elements.TextElement;
import org.openscience.jchempaint.renderer.elements.TextGroupElement;
import org.openscience.cdk.renderer.elements.WedgeLineElement;
import org.openscience.jchempaint.renderer.elements.WigglyLineElement;
import org.openscience.jchempaint.renderer.font.FreeSansBoldGM;
import org.openscience.jchempaint.renderer.font.GlyphMetrics;

/**
* We can only guarantee the same quality of SVG output everywhere
* by drawing paths and not using fonts. This is an indirect
* consequence of font commercialisation which has successfully
* prevented SVG fonts from becoming usable on all browsers. See
* https://github.com/JChemPaint/jchempaint/wiki/The-svg-font-problem-and-its-solution
*
* So, we convert an open font to SVG paths and use these.
* To resolve the problem of placement, we use bbox, advance and
* (maybe later) kerning values from the same font.
* To make sure bonds don't cross text, we use two passes where
* text is drawn first and the bonds second.
*
* Two-pass implementation (c) 2012 by
* @author Ralf Stephan <ralf@ark.in-berlin.de>
* @jcp.issue #2
*
* First code layer (c) 2007 by
* @author maclean
* @cdk.module rendersvg
* @cdk.bug 2403250
*/
public class SVGGenerator implements IDrawVisitor {

    /**
     * The renderer model cannot be set by the constructor as it needs to
     * be managed by the Renderer.
     */
    private JChemPaintRendererModel rendererModel;

    public static final String HEADER = "<?xml version=\"1.0\"?>\n" +
            "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n" +
            "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n" +
            "<svg xmlns=\"http://www.w3.org/2000/svg\" " +
            "xmlns:xlink=\"http://www.w3.org/1999/xlink\" " +
            "viewBox=\"0 0 1234567890\">\n" +
            "<g transform=\"translate(12345,67890)\">";

    private final StringBuffer svg = new StringBuffer();

    private FreeSansBoldGM the_fm;

    private AffineTransform transform;

    private List<IRenderingElement>   elList;
    private List<String>              tcList;
    private HashMap<String, Point2d>  tgMap;
    private HashMap<Integer, Point2d> ptMap;
    private List<Rectangle2D>         bbList;

    private double trscale, subscale, subshift, tgpadding, vbpadding;
    private Rectangle2D bbox;

    //------------------------------------------------------------------

    public SVGGenerator() {
        the_fm = new FreeSansBoldGM();
        the_fm.init();
        elList = new ArrayList<IRenderingElement>();
        tcList = new ArrayList<String>();
        tgMap = new HashMap<String, Point2d>();
        ptMap = new HashMap<Integer, Point2d>();
        bbList = new ArrayList<Rectangle2D>();
        bbox = null;

        svg.append(SVGGenerator.HEADER);
        newline();
        svg.append("<defs>");
        tgpadding = 4;
        vbpadding = 40;
        trscale = 0.03;
        subscale = trscale * 0.7;
        subshift = 0.5;
    }

    private void newline() {
        svg.append("\n");
    }

    public double[] transformPoint(double x, double y) {
        double[] src = new double[]{x, y};
        double[] dest = new double[2];
        this.transform.transform(src, 0, dest, 0, 1);
        return dest;
    }

    public double[] invTransformPoint(double x, double y) {
        double[] src = new double[]{x, y};
        double[] dest = new double[2];
        try {
            this.transform.createInverse().transform(src, 0, dest, 0, 1);
        } catch (NoninvertibleTransformException e) {
            System.err.println("Cannot invert transform!\n");
        }
        return dest;
    }

    /**
     * Fills two lists: tcList contains all characters used in
     * atoms, tgList has all strings. We also write the character
     * paths immediately as DEFS into the SVG, for reference.
     *
     * @param e
     */
    private void writeDEFS(TextGroupElement e) {
        if (e.text.length() > 1 && !tgMap.containsKey(e.text))
            tgMap.put(e.text, new Point2d(0, 0));
        for (char c : e.text.toCharArray()) {
            String idstr = "Atom-" + c;
            GlyphMetrics m = the_fm.map.get((int) c);
            if (!ptMap.containsKey((int) c))
                ptMap.put((int) c, new Point2d(m.xMax, m.yMax - m.yMin));
            if (!tcList.contains(idstr)) {
                tcList.add(idstr);
                newline();
                svg.append(String.format(
                        "  <path id=\"%s\" transform=\"scale(%1.3f,%1.3f)\" d=\"%s\" />",
                        idstr, trscale, -trscale, m.outline));
            }
        }

        // Set hyd and hPos according to entry
        int hyd = 0, hPos = 0;
        for (TextGroupElement.Child ch : e.children) {
            if (ch.text.equals("H")) {
                if (ch.subscript == null) hyd = 1;
                else if (ch.subscript.equals("2")) hyd = 2;
                else hyd = 3;
                if (ch.position == TextGroupElement.Position.E) hPos = 1;
                else if (ch.position == TextGroupElement.Position.W) hPos = -1;
            }
        }
        if (hyd > 0) {
            if (!tcList.contains("Atom-H")) {
                tcList.add("Atom-H");
                GlyphMetrics m = the_fm.map.get((int) "H".charAt(0));
                svg.append(String.format(
                        "  <path id=\"Atom-H\" transform=\"scale(%1.3f,%1.3f)\" d=\"%s\" />",
                        trscale, -trscale, m.outline));
            }
            if (hyd >= 2) {
                char c = '2';
                if (hyd == 3) c = '3';
                String idstr = "Atom-" + c;
                GlyphMetrics m = the_fm.map.get((int) c);
                if (!tcList.contains(idstr)) {
                    tcList.add(idstr);
                    newline();
                    svg.append(String.format(
                            "  <path id=\"%s\" transform=\"scale(%1.4f,%1.4f)\" d=\"%s\" />",
                            idstr, subscale, -subscale, m.outline));
                }
            }
        }
    }

    /**
     * In this first pass, visiting elements are copied to
     * a list, and DEFS/PATH elements are written for all TextGroups.
     */
    public void visit(IRenderingElement element) {
        elList.add(element);

        if (element instanceof ElementGroup)
            ((ElementGroup) element).visitChildren(this);
        else if (element instanceof TextGroupElement)
            writeDEFS ((TextGroupElement) element);
  }
 
  public void draw (OvalElement oval) {
    newline();
    double[] p1 = transformPoint(oval.xCoord - oval.radius, oval.yCoord - oval.radius);
    double[] p2 = transformPoint(oval.xCoord + oval.radius, oval.yCoord + oval.radius);
    double x, y, w, h;
    x = Math.min(p1[0], p2[0]);
    y = Math.min(p1[1], p2[1]);
    w = Math.abs(p2[0] - p1[0]);
    h = Math.abs(p2[1] - p1[1]);
    Rectangle2D rect = new Rectangle2D.Double(x, y, w, h);
    if (bbox==null) bbox=rect;
    else bbox = bbox.createUnion(rect);
    double r = w / 2;
    svg.append(String.format(
        "<ellipse cx=\"%4.2f\" cy=\"%4.2f\" rx=\"%4.2f\" ry=\"%4.2f\" " +
        "style=\"stroke:black; stroke-width:1px; fill:none;\" />",
        x + r, y + r, r, r));
  }

  public void draw (AtomSymbolElement atomSymbol) {
    newline();
    double[] p = transformPoint(atomSymbol.xCoord, atomSymbol.yCoord);
    svg.append(String.format(
        "<text x=\"%s\" y=\"%s\" style=\"fill:%s\"" +
        ">%s</text>",
        p[0],
        p[1],
        toColorString(atomSymbol.color),
        atomSymbol.text
        ));
  }

  // this is a stupid method, but no idea how else to do it...
  private String toColorString(Color color) {
    if (color == Color.RED) {
      return "red";
    } else if (color == Color.BLUE) {
      return "blue";
    } else {
      return "black";
    }
  }

  public void draw (TextElement textElement) {
    newline();
    double[] p = transformPoint(textElement.x, textElement.y);
    svg.append(String.format(
        "<text x=\"%s\" y=\"%s\">%s</text>",
        p[0],
        p[1],
        textElement.text
        ));
  }
 
  /**
   * At the time of this call, all that we need is in place:
   * the SVG character macros are written and the bboxes computed.
   * The textgroup text is now placed with its center at the
   * position of the atom. Implicit hydrogens are added.
   *
   * @param e
   */
  public void draw (TextGroupElement e) {
    newline();
    double[] pos = transformPoint(e.x, e.y);
   
    // Determine the bbox of the Atom symbol text
    Point2d bb;
    if (e.text.length() == 1)
      bb = ptMap.get((int)e.text.charAt(0));
    else
      bb = tgMap.get(e.text);
   
    // Set hyd and hPos according to entry
    int hyd=0, hPos=0;
    for (TextGroupElement.Child c : e.children) {
      if (c.text.equals ("H")) {
        if (c.subscript == null) hyd=1;
        else if (c.subscript.equals("2")) hyd=2;
        else hyd=3;
        if (c.position==TextGroupElement.Position.E) hPos=1;
        else if (c.position==TextGroupElement.Position.W) hPos=-1;
      }
    }

    // Set v to the bbox of the whole TextGroup, add it to
    // the list of such bboxes and enlarge the viewport bbox
    double x, y, w, h;
    x = pos[0]-trscale*bb.x/2;
    y = pos[1]-trscale*bb.y/2-tgpadding;
    w = trscale*bb.x+tgpadding;
    h = trscale*bb.y+2*tgpadding;
    Rectangle2D v = new Rectangle2D.Double(x, y, w, h);
    bbList.add (v);
    if (bbox==null) bbox = v;
                else bbox = bbox.createUnion (v);
    // Output use command(s)
    x = pos[0] - trscale*bb.x/2;
    y = pos[1] + trscale*bb.y/2;
    svg.append(String.format(
        "<use xlink:href=\"#Atom-%s\" x=\"%4.2f\" y=\"%4.2f\"/>",
        e.text, x, y));
    if (hyd != 0) {
      GlyphMetrics m = the_fm.map.get(50+hyd);
      if (hPos>0)
        x += trscale*bb.x;
      else {
        x -= trscale* the_fm.map.get((int) "H".charAt(0)).adv;
        if (hyd>=2)
          x -= subscale*m.adv;
      }
      svg.append(String.format(
          "<use xlink:href=\"#Atom-H\" x=\"%4.2f\" y=\"%4.2f\"/>",
          x, y));
      if (hyd>=2) {
        char c = '2';
        if (hyd==3) c='3';
        String idstr = "Atom-" + c;
        x += trscale* the_fm.map.get((int) "H".charAt(0)).adv;
        y += subshift*subscale*(m.yMax-m.yMin);
        svg.append(String.format(
            "<use xlink:href=\"#%s\" x=\"%4.2f\" y=\"%4.2f\"/>",
            idstr, x, y));
      }
    }
  }
 
  /**
   * In this second pass, everything except bonds (and arrows)
   * is placed, the textgroups referring to the DEFS ids. For
   * intermediate caching, we first add strings to the DEFS block.
   */
  public void drawNoBonds() {
    newline();
    svg.append("</defs>");
    if (!tgMap.isEmpty()) { newline(); svg.append("<defs>"); }
    for (String s : tgMap.keySet()) {
      newline();
      svg.append(String.format("<g id=\"Atom-%s\">", s));
      boolean first = true;
      int advance = 0;
      int xMin = 9999, xMax = 0, yMin = 9999, yMax = 0;
      for (char c : s.toCharArray()) {
        svg.append(String.format("<use xlink:href=\"#Atom-%c\" ", c));
        if (first) {
          first=false;
        }
        else {
          svg.append(String.format("transform=\"translate(%4.2f,0)\"", advance*trscale));
        }
        GlyphMetrics m = the_fm.map.get((int)c);
        if (m.xMin + advance < xMin) xMin = m.xMin + advance;
        if (m.xMax + advance > xMax) xMax = m.xMax + advance;
        if (m.yMin < yMin) yMin = m.yMin;
        if (m.yMax > yMax) yMax = m.yMax;
        advance += m.adv;
        svg.append("/>");
      }
      svg.append("</g>");
      Point2d p = tgMap.get(s);
      p.x = xMax - xMin;
      p.y = yMax - yMin;
    }
    if (!tgMap.isEmpty()) { newline(); svg.append("</defs>"); }

    for (IRenderingElement element : elList) {
      if (element instanceof OvalElement)
        draw((OvalElement) element);
          else if (element instanceof TextGroupElement)
              draw((TextGroupElement) element);
      else if (element instanceof AtomSymbolElement)
        draw((AtomSymbolElement) element);
      else if (element instanceof TextElement)
        draw((TextElement) element);
      else if (element instanceof RectangleElement)
        draw((RectangleElement) element);
      else if (element instanceof PathElement)
        draw((PathElement) element);
    }
  }
 
  /**
   * In the third pass, bonds (and arrows) are drawn,
   * taking care to leave a small distance to atoms with
   * text.
   */
  public void drawBonds() {
    for (IRenderingElement element : elList) {
      if (element instanceof WedgeLineElement)
        draw((WedgeLineElement) element);
      else if (element instanceof LineElement)
        draw((LineElement) element);
          else if (element instanceof ArrowElement)
              draw((ArrowElement) element);
          else if (element instanceof WigglyLineElement)
            draw((WigglyLineElement) element);
    }
  }

  /**
   * This is where most of the work is done by calling
   * the 2nd and 3rd passes, and finally computing
   * width and height of the document which is set at last.
   * @return the SVG document as String
   */
  public String getResult() {
    drawNoBonds();
    drawBonds();
    newline();
    svg.append("</g>\n</svg>\n");
   
    int i = svg.indexOf ("0 0 1234567890");
        if (bbox == null)
            bbox = new Rectangle2D.Double(0, 0, 0, 0);
    svg.replace(i, i+14, String.format("0 0 %4.0f %4.0f",
        bbox.getWidth()+2*vbpadding,
        bbox.getHeight()+2*vbpadding));
    i = svg.indexOf ("12345,67890");
    svg.replace(i, i+11, String.format ("%4.0f,%4.0f",
        -bbox.getMinX()+vbpadding,
        -bbox.getMinY()+vbpadding));
    return svg.toString();
  }

  /**
   * Applies all collected bboxes to the two points and, if one is
   * inside a bbox, places it at the bbox's edge, same direction.
   * Intended to be cumulative, i.e., both points may be moved more
   * than once, or never. Returns true if any segment is left.
   * @param p1
   * @param p2
   */
  private boolean shorten_line(double[] p1, double[] p2)
  {
    for (Rectangle2D v : bbList) {   // shorten line acc. to bboxes
      boolean inside1 = v.contains(p1[0], p1[1]);
      boolean inside2 = v.contains(p2[0], p2[1]);
      if (!inside1 && !inside2) continue;
      if (inside1 && inside2) return false;
                       
      double px, py, qx, qy, cx=0.0, cy=0.0;
      if (inside1) {
        px = p1[0]; py = p1[1];
        qx = p2[0]; qy = p2[1];
      } else {
        px = p2[0]; py = p2[1];
        qx = p1[0]; qy = p1[1];
      }
      if (qx<v.getX() && v.getX()<px) cx = v.getX();
      if (px<v.getMaxX() && v.getMaxX()<qx) cx = v.getMaxX();
      if (qy<v.getY() && v.getY()<py) cy = v.getY();
      if (py<v.getMaxY() && v.getMaxY()<qy) cy = v.getMaxY();
                       
                        double rx, ry;
      if (qy==py) { rx=cx; ry=py; }
      else if (cx == 0.0) { ry = cy; rx = px + (cy-py)*(qx-px)/(qy-py); }
      else if (qx==px) { ry=cy; rx=px; }
      else if (cy == 0.0) { rx = cx; ry = py + (cx-px)*(qy-py)/(qx-px); }
      else // cx, cy, qx-px, qy-py all nonzero
        if (Math.abs((cx-px)/(cy-py)) > Math.abs((qx-px)/(qy-py)))
        { ry = cy; rx = px + (cy-py)*(qx-px)/(qy-py); }
        else
        { rx = cx; ry = py + (cx-px)*(qy-py)/(qx-px); }
      }
      if (inside1) {
        p1[0] = (int)rx; p1[1] = (int)ry;
      } else {
        p2[0] = (int)rx; p2[1] = (int)ry;
      }
    }
    return true;
  }

  public void draw (WedgeLineElement wedge) {
    double[] p1 = transformPoint(wedge.firstPointX, wedge.firstPointY);
    double[] p2 = transformPoint(wedge.secondPointX, wedge.secondPointY);
    if (bbox==null) bbox = new Rectangle2D.Double(
                            Math.min(p1[0], p2[0]), Math.min(p1[1], p2[1]),
                            Math.abs(p2[0] - p1[0]), Math.abs(p2[1] - p1[1]));
                else bbox = bbox.createUnion(new Rectangle2D.Double(
                        Math.min(p1[0], p2[0]), Math.min(p1[1], p2[1]),
                        Math.abs(p2[0] - p1[0]), Math.abs(p2[1] - p1[1])));
    if (!shorten_line (p1, p2)) return;
    double w1[] = invTransformPoint (p1[0], p1[1]);
    double w2[] = invTransformPoint (p2[0], p2[1]);
        // make the vector normal to the wedge axis
        Vector2d normal =
            new Vector2d(w1[1] - w2[1], w2[0] - w1[0]);
        normal.normalize();
        normal.scale(rendererModel.getWedgeWidth() / rendererModel.getScale())
       
        // make the triangle corners
        Point2d vertexA = new Point2d(w1[0], w1[1]);
        Point2d vertexB = new Point2d(w2[0], w2[1]);
        Point2d vertexC = new Point2d(vertexB);
        vertexB.add(normal);
        vertexC.sub(normal);
        if (wedge.type == WedgeLineElement.TYPE.DASHED) {
            this.drawDashedWedge(vertexA, vertexB, vertexC);
        } else if (wedge.type == WedgeLineElement.TYPE.WEDGED) {
            this.drawFilledWedge(vertexA, vertexB, vertexC);
        } else {
          this.drawCrissCrossWedge(vertexA, vertexB, vertexC);
        }
  }
 
      public void draw (WigglyLineElement wedge) {
          //TODO add code. see http://www.w3.org/TR/SVG/paths.html#PathDataCurveCommands
        }
 
      private void drawCrissCrossWedge(Point2d vertexA, Point2d vertexB,
            Point2d vertexC) {
             
              // calculate the distances between lines
              double distance = vertexB.distance(vertexA);
              double gapFactor = 0.1;
              double gap = distance * gapFactor;
              double numberOfDashes = distance / gap;
              double d = 0;
              double[] old=null;
             
              // draw by interpolating along the edges of the triangle
              for (int i = 0; i < numberOfDashes; i++) {
                  double d2 = d-gapFactor;
                  Point2d p1 = new Point2d();
                  p1.interpolate(vertexA, vertexB, d);
                  Point2d p2 = new Point2d();
                  p2.interpolate(vertexA, vertexC, d2);
                  double[] p1T = this.transformPoint(p1.x, p1.y);
                  double[] p2T = this.transformPoint(p2.x, p2.y);
              svg.append(String.format(
                "<line x1=\"%4.2f\" y1=\"%4.2f\" x2=\"%4.2f\" y2=\"%4.2f\" " +
                "style=\"stroke:black; stroke-width:1px;\" />",
                p1T[0],
                p1T[1],
                p2T[0],
                p2T[1]
                ));
                  if(old==null)
                    old = p2T;
              svg.append(String.format(
                "<line x1=\"%4.2f\" y1=\"%4.2f\" x2=\"%4.2f\" y2=\"%4.2f\" " +
                "style=\"stroke:black; stroke-width:1px;\" />",
                old[0],
                old[1],
                p2T[0],
                p2T[1]
                ));
                  old = p1T;
                  if (distance * (d + gapFactor) >= distance) {
                      break;
                  } else {
                      d += gapFactor*2;
                  }
              }
        }

 
    private void drawFilledWedge(
            Point2d vertexA, Point2d vertexB, Point2d vertexC) {
        double[] pB = this.transformPoint(vertexB.x, vertexB.y);
        double[] pC = this.transformPoint(vertexC.x, vertexC.y);
        double[] pA = this.transformPoint(vertexA.x, vertexA.y);
       
    svg.append(String.format(
        "<polygon points=\"%4.2f,%4.2f %4.2f,%4.2f %4.2f,%4.2f\" "+
          "style=\"fill:black;"+
          "stroke:black;stroke-width:1\"/>",
        pB[0],pB[1],
        pC[0],pC[1],
        pA[0],pA[1]
        ));
    }

  public void draw (PathElement path) {

  }
 

  public void draw (LineElement line) {
    newline();
    double[] p1 = transformPoint(line.firstPointX, line.firstPointY);
    double[] p2 = transformPoint(line.secondPointX, line.secondPointY);
    if (!shorten_line (p1, p2)) return;
                if (bbox == null) {
                    bbox = new Rectangle2D.Double(
                            Math.min(p1[0], p2[0]), Math.min(p1[1], p2[1]),
                            Math.abs(p2[0] - p1[0]), Math.abs(p2[1] - p1[1]));
                } else {
                    bbox = bbox.createUnion(new Rectangle2D.Double(
                            Math.min(p1[0], p2[0]), Math.min(p1[1], p2[1]),
                            Math.abs(p2[0] - p1[0]), Math.abs(p2[1] - p1[1])));
                }
        svg.append(String.format(
        "<line x1=\"%4.2f\" y1=\"%4.2f\" x2=\"%4.2f\" y2=\"%4.2f\" " +
        "style=\"stroke:black; stroke-width:3px;\" />",
        p1[0],
        p1[1],
        p2[0],
        p2[1]
        ));
  }
 
    private void drawDashedWedge(
            Point2d vertexA, Point2d vertexB, Point2d vertexC) {
       
        // calculate the distances between lines
        double distance = vertexB.distance(vertexA);
        double gapFactor = 0.1;
        double gap = distance * gapFactor;
        double numberOfDashes = distance / gap;
        double d = 0;
       
        // draw by interpolating along the edges of the triangle
        for (int i = 0; i < numberOfDashes; i++) {
            Point2d p1 = new Point2d();
            p1.interpolate(vertexA, vertexB, d);
            Point2d p2 = new Point2d();
            p2.interpolate(vertexA, vertexC, d);
            double[] p1T = this.transformPoint(p1.x, p1.y);
            double[] p2T = this.transformPoint(p2.x, p2.y);
        svg.append(String.format(
          "<line x1=\"%4.2f\" y1=\"%4.2f\" x2=\"%4.2f\" y2=\"%4.2f\" " +
          "style=\"stroke:black; stroke-width:1px;\" />",
          p1T[0],
          p1T[1],
          p2T[0],
          p2T[1]
          ));
           
            if (distance * (d + gapFactor) >= distance) {
                break;
            } else {
                d += gapFactor;
            }
        }
    }

    public void draw (ArrowElement line) {
     
        int w = (int) (line.width * this.rendererModel.getScale());
        double[] a = this.transformPoint(line.startX, line.startY);
        double[] b = this.transformPoint(line.startX, line.startY);
        newline();
    svg.append(String.format(
        "<line x1=\"%4.2f\" y1=\"%4.2f\" x2=\"%4.2f\" y2=\"%4.2f\" " +
        "style=\"stroke:black; stroke-width:"+w+"px;\" />",
        a[0],
        a[1],
        b[0],
        b[1]
        ));
        double aW = rendererModel.getArrowHeadWidth() / rendererModel.getScale();
        if(line.direction){
          double[] c = this.transformPoint(line.startX-aW, line.startY-aW);
          double[] d = this.transformPoint(line.startX-aW, line.startY+aW);
          newline();
      svg.append(String.format(
          "<line x1=\"%s\" y1=\"%s\" x2=\"%s\" y2=\"%s\" " +
          "style=\"stroke:black; stroke-width:"+w+"px;\" />",
          a[0],
          a[1],
          c[0],
          c[1]
          ));
      newline();
      svg.append(String.format(
          "<line x1=\"%s\" y1=\"%s\" x2=\"%s\" y2=\"%s\" " +
          "style=\"stroke:black; stroke-width:"+w+"px;\" />",
          a[0],
          a[1],
          d[0],
          d[1]
          ));
        }else{
          double[] c = this.transformPoint(line.endX+aW, line.endY-aW);
          double[] d = this.transformPoint(line.endX+aW, line.endY+aW);
          newline();
      svg.append(String.format(
          "<line x1=\"%s\" y1=\"%s\" x2=\"%s\" y2=\"%s\" " +
          "style=\"stroke:black; stroke-width:"+w+"px;\" />",
          a[0],
          a[1],
          c[0],
          c[1]
          ));
      newline();
      svg.append(String.format(
          "<line x1=\"%s\" y1=\"%s\" x2=\"%s\" y2=\"%s\" " +
          "style=\"stroke:black; stroke-width:"+w+"px;\" />",
          a[0],
          a[1],
          d[0],
          d[1]
          ));
        }       
    }


  public void draw (RectangleElement rectangleElement) {
        double[] pA = this.transformPoint(rectangleElement.xCoord, rectangleElement.yCoord);
        double[] pB = this.transformPoint(rectangleElement.xCoord+rectangleElement.width, rectangleElement.yCoord);
        double[] pC = this.transformPoint(rectangleElement.xCoord, rectangleElement.yCoord+rectangleElement.height);
        double[] pD = this.transformPoint(rectangleElement.xCoord+rectangleElement.width, rectangleElement.yCoord+rectangleElement.height);
       
        newline();
    svg.append(String.format(
        "<polyline points=\"%4.2f,%4.2f %4.2f,%4.2f %4.2f,%4.2f %4.2f,%4.2f %4.2f,%4.2f\" "+
          "style=\"fill:none;"+
          "stroke:black;stroke-width:1\"/>",
        pA[0],pA[1],
        pB[0],pB[1],
        pD[0],pD[1],
        pC[0],pC[1],
        pA[0],pA[1]
        ));

  }

    public void setTransform(AffineTransform transform) {
    this.transform = transform;
    this.transform.setToScale(30, -30);
//    System.err.println(transform.toString());
//    System.err.println(String.format("scale=%f zoom=%f\n", transform.getScaleX(), transform.getScaleY()));
  }

    public void setFontManager(IFontManager fontManager) {
    }

    public void setRendererModel(JChemPaintRendererModel rendererModel) {
        this.rendererModel = rendererModel;
    }

}
TOP

Related Classes of org.openscience.jchempaint.renderer.visitor.SVGGenerator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.