Subversion Repositories wimsdev

Rev

Rev 7293 | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. /*
  2.     Sketch Elements: Chemistry molecular diagram drawing tool.
  3.    
  4.     (c) 2008 Dr. Alex M. Clark
  5.    
  6.     Released as GNUware, under the Gnu Public License (GPL)
  7.    
  8.     See www.gnu.org for details.
  9. */
  10.  
  11. package WIMSchem;
  12.  
  13. import java.util.*;
  14. import java.io.*;
  15. import java.text.*;
  16. /*
  17.     A drawing container which allows primitives to be composed, and once complete, rendered into an SVG output stream.
  18. */
  19.  
  20. public class SVGBuilder
  21. {
  22.     DecimalFormat df = new DecimalFormat("#.##",new DecimalFormatSymbols(Locale.US));
  23.     public final static int NOCOLOUR=-1;
  24.     public final static int TXTSTYLE_NORMAL=0;
  25.     public final static int TXTSTYLE_BOLD=0x01;
  26.     public final static int TXTSTYLE_ITALIC=0x02;
  27.     public final static int TXTALIGN_CENTRE=0;
  28.     public final static int TXTALIGN_LEFT=1;
  29.     public final static int TXTALIGN_RIGHT=2;
  30.     public String g_id = "g_SVG_1000000";
  31.     public String svg_id = "SVG_1000000";
  32.  
  33.     private boolean fresh=true;
  34.     private double lowX=0,lowY=0,highX=0,highY=0;
  35.    
  36.     private boolean[] charMask=new boolean[96];
  37.  
  38.     private final static int ATOM_LINE=1,ATOM_RECT=2,ATOM_OVAL=3,ATOM_PATH=4,ATOM_TEXT=5;
  39.     abstract class Atom {int AtomClass,TypeRef;}
  40.  
  41.     class LineAtom extends Atom {double X1,Y1,X2,Y2;}
  42.     class LineType {double Thickness; int Colour;}
  43.    
  44.     class RectAtom extends Atom {double X,Y,W,H;}
  45.     class RectType {int EdgeCol,FillCol; double Thickness;}
  46.    
  47.     class OvalAtom extends Atom {double CX,CY,RW,RH;}
  48.     class OvalType {int EdgeCol,FillCol; double Thickness;}
  49.    
  50.     class PathAtom extends Atom {int N; double[] X,Y; boolean[] Ctrl; boolean Closed;}
  51.     class PathType {int EdgeCol,FillCol; double Thickness; boolean HardEdge;}
  52.  
  53.     class TextAtom extends Atom {double X,Y; String Txt;}
  54.     class TextType {double Sz; int Colour,Style,Align;}
  55.    
  56.     private ArrayList<Atom> atoms=new ArrayList<Atom>();
  57.     private ArrayList<LineType> lineTypes=new ArrayList<LineType>();
  58.     private ArrayList<RectType> rectTypes=new ArrayList<RectType>();
  59.     private ArrayList<OvalType> ovalTypes=new ArrayList<OvalType>();
  60.     private ArrayList<PathType> pathTypes=new ArrayList<PathType>();
  61.     private ArrayList<TextType> textTypes=new ArrayList<TextType>();
  62.  
  63.     // ------------------------------------------------ public functions ------------------------------------------------
  64.  
  65.     public SVGBuilder()
  66.     {
  67.         for (int n=0;n<96;n++) charMask[n]=false;
  68.     }
  69.    
  70.     // query the boundaries of the drawing, post factum
  71.     public double lowX() {return lowX;}
  72.     public double lowY() {return lowY;}
  73.     public double highX() {return highX;}
  74.     public double highY() {return highY;}
  75.    
  76.     // atomic drawing options
  77.     public void drawLine(double X1,double Y1,double X2,double Y2,int Colour,double Thickness)
  78.     {
  79.         updateBounds(X1-Thickness,Y1-Thickness); updateBounds(X1+Thickness,Y1+Thickness);
  80.         updateBounds(X2-Thickness,Y2-Thickness); updateBounds(X2+Thickness,Y2+Thickness);
  81.        
  82.         LineType type=new LineType();
  83.         type.Thickness=Thickness;
  84.         type.Colour=Colour;
  85.  
  86.         LineAtom atom=new LineAtom();
  87.         atom.AtomClass=ATOM_LINE;
  88.         atom.X1=X1; atom.Y1=Y1; atom.X2=X2; atom.Y2=Y2;
  89.         atom.TypeRef=registerLineType(type);
  90.         atoms.add(atom);
  91.     }
  92.     public void drawRect(double X,double Y,double W,double H,int EdgeCol,double Thickness,int FillCol)
  93.     {
  94.         updateBounds(X-Thickness,Y-Thickness); updateBounds(X+W+Thickness,Y+H+Thickness);
  95.        
  96.         RectType type=new RectType();
  97.         type.EdgeCol=EdgeCol;
  98.         type.Thickness=Thickness;
  99.         type.FillCol=FillCol;
  100.        
  101.         RectAtom atom=new RectAtom();
  102.         atom.AtomClass=ATOM_RECT;
  103.         atom.X=X; atom.Y=Y; atom.W=W; atom.H=H;
  104.         atom.TypeRef=registerRectType(type);
  105.         atoms.add(atom);
  106.     }
  107.     public void drawOval(double CX,double CY,double RW,double RH,int EdgeCol,double Thickness,int FillCol)
  108.     {
  109.         updateBounds(CX-RW-Thickness,CY-RH-Thickness); updateBounds(CX+RW+Thickness,CY+RH+Thickness);
  110.        
  111.         OvalType type=new OvalType();
  112.         type.EdgeCol=EdgeCol;
  113.         type.Thickness=Thickness;
  114.         type.FillCol=FillCol;
  115.        
  116.         OvalAtom atom=new OvalAtom();
  117.         atom.AtomClass=ATOM_OVAL;
  118.         atom.CX=CX; atom.CY=CY; atom.RW=RW; atom.RH=RH;
  119.         atom.TypeRef=registerOvalType(type);
  120.         atoms.add(atom);
  121.     }
  122.     public void drawPoly(double[] X,double[] Y,int EdgeCol,double Thickness,int FillCol,boolean Closed)
  123.     {
  124.         PathType type=new PathType();
  125.         type.EdgeCol=EdgeCol;
  126.         type.FillCol=FillCol;
  127.         type.Thickness=Thickness;
  128.         type.HardEdge=true;
  129.    
  130.         PathAtom atom=new PathAtom();
  131.         atom.AtomClass=ATOM_PATH;
  132.         atom.N=X.length;
  133.         atom.X=new double[atom.N];
  134.         atom.Y=new double[atom.N];
  135.         atom.Ctrl=new boolean[atom.N];
  136.         atom.Closed=Closed;
  137.         for (int n=0;n<atom.N;n++)
  138.         {
  139.             updateBounds(X[n]-Thickness,Y[n]-Thickness);
  140.             updateBounds(X[n]+Thickness,Y[n]+Thickness);
  141.             atom.X[n]=X[n];
  142.             atom.Y[n]=Y[n];
  143.             atom.Ctrl[n]=false;
  144.         }
  145.         atom.TypeRef=registerPathType(type);
  146.         atoms.add(atom);
  147.     }
  148.     public void drawCurve(double[] X,double[] Y,boolean[] Ctrl,int EdgeCol,double Thickness,int FillCol,boolean Closed)
  149.     {
  150.         PathType type=new PathType();
  151.         type.EdgeCol=EdgeCol;
  152.         type.FillCol=FillCol;
  153.         type.Thickness=Thickness;
  154.         type.HardEdge=false;
  155.    
  156.         PathAtom atom=new PathAtom();
  157.         atom.AtomClass=ATOM_PATH;
  158.         atom.N=X.length;
  159.         atom.X=new double[atom.N];
  160.         atom.Y=new double[atom.N];
  161.         atom.Ctrl=new boolean[atom.N];
  162.         atom.Closed=Closed;
  163.         for (int n=0;n<atom.N;n++)
  164.         {
  165.             // (NOTE: if this is a control point, the boundary could be extended too far, but whatever...)
  166.             updateBounds(X[n]-Thickness,Y[n]-Thickness);
  167.             updateBounds(X[n]+Thickness,Y[n]+Thickness);
  168.             atom.X[n]=X[n];
  169.             atom.Y[n]=Y[n];
  170.             atom.Ctrl[n]=Ctrl[n];
  171.         }
  172.         atom.TypeRef=registerPathType(type);
  173.         atoms.add(atom);
  174.     }
  175.     public void drawText(double X,double Y,String Txt,double Sz,int Colour,int Style,int Align)
  176.     {
  177.         for (int n=0;n<Txt.length();n++) {int i=Txt.charAt(n); if (i>=32 && i<=127) charMask[i-32]=true;}
  178.    
  179.         double[] metrics=measureText(Txt,Sz,Style);
  180.         if (Align==TXTALIGN_CENTRE) {updateBounds(X-0.5*metrics[0],Y-metrics[1]); updateBounds(X+0.5*metrics[0],Y+metrics[2]);}
  181.         else if (Align==TXTALIGN_LEFT) {updateBounds(X,Y-metrics[1]); updateBounds(X+metrics[0],Y+metrics[2]);}
  182.         else if (Align==TXTALIGN_RIGHT) {updateBounds(X-metrics[0],Y-metrics[1]); updateBounds(X,Y+metrics[2]);}
  183.        
  184.         // assumes that HTML tags are not wanted
  185.         Txt=Txt.replace("&","&amp;");
  186.         Txt=Txt.replace("<","&lt;");
  187.         Txt=Txt.replace(">","&gt;");
  188.  
  189.         TextType type=new TextType();
  190.         type.Sz=Sz;
  191.         type.Colour=Colour;
  192.         type.Style=Style;
  193.         type.Align=Align;
  194.        
  195.         TextAtom atom=new TextAtom();
  196.         atom.AtomClass=ATOM_TEXT;
  197.         atom.X=X; atom.Y=Y; atom.Txt=Txt;
  198.         atom.TypeRef=registerTextType(type);
  199.         atoms.add(atom);
  200.     }
  201.    
  202.     // measures a text string, at a given size; the return value has to be considered approximate;
  203.     // the return array is of the form {width,ascent,descent}
  204.     public double[] measureText(String Txt,double Sz,int Style)
  205.     {
  206.         int w=0;
  207.         for (int n=0;n<Txt.length();n++)
  208.         {
  209.             int i=Txt.charAt(n)-32;
  210.             if (i>=0 && i<96) w+=SVGFont.HORIZ_ADV_X[i]; else w+=SVGFont.MISSING_HORZ;
  211.            
  212.             if (n<Txt.length()-1)
  213.             {
  214.                 int j=Txt.charAt(n+1)-32;
  215.                 for (int k=0;k<SVGFont.KERN_K.length;k++)
  216.                     if ((SVGFont.KERN_G1[k]==i && SVGFont.KERN_G2[k]==j) || (SVGFont.KERN_G1[k]==j && SVGFont.KERN_G2[k]==i))
  217.                         {w+=SVGFont.KERN_K[k]; break;}
  218.             }
  219.         }
  220.         double[] ret=new double[3];
  221.         ret[0]=Sz*w/SVGFont.UNITS_PER_EM;
  222.         ret[1]=Sz*SVGFont.ASCENT/SVGFont.UNITS_PER_EM;
  223.         ret[2]=Sz*-SVGFont.DESCENT/SVGFont.UNITS_PER_EM;
  224.         return ret;
  225.     }
  226.        
  227.     // builds the SVG content proper; the output is expected to fit in a box of dimensions (0,0,W,H), which is specified in the parameters;
  228.     // the transformations (OX,OY,SW,SH) exist to facilitate fitting the result into this box; OX and OY are in destination units and are
  229.     // added after scaling, while SW and SH are fractional scaling factors, where 1==unchanged; anisotropic scaling is not recommended
  230.     public void build(PrintWriter Out,int W,int H,double OX,double OY,double SW,double SH)
  231.     {
  232.         if( MainPanel.appletMode ){
  233.             g_id = MainApplet.g_id;
  234.             svg_id = MainApplet.svg_id;
  235.             Out.println("<svg onclick=\"javascript:SVG_zoom('"+svg_id+"','"+g_id+"','"+W+"','"+H+"');\" id=\""+svg_id+"\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\"");
  236.             Out.println(" version=\"1\" x=\"0\" y=\"0\" width=\""+W+"\" height=\""+H+"\" viewBox=\"0 0 "+W+" "+H+"\"><g id=\""+g_id+"\" transform=\"matrix(1 0 0 1 0 0)\">");
  237.         }
  238.         else
  239.         {
  240.             Out.println("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n");
  241.             Out.println("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 20010904//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
  242.             Out.println("<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\"");
  243.             Out.println(" version=\"1\" x=\"0\" y=\"0\" width=\""+W+"\" height=\""+H+"\" viewBox=\"0 0 "+W+" "+H+"\"><g transform=\"matrix(1 0 0 1 0 0)\">");
  244.         }
  245.         Out.println();
  246.         // now write out the font definition
  247.         Out.println("<defs><font id=\""+SVGFont.FONT_FAMILY+"\" horiz-adv-x=\""+SVGFont.FONT_ADV+"\">");
  248.         Out.println("<font-face font-family=\""+SVGFont.FONT_FAMILY+"\" units-per-em=\""+SVGFont.UNITS_PER_EM+"\" "+" panose-1=\""+SVGFont.PANOSE_1+"\" ascent=\""+SVGFont.ASCENT+"\" descent=\""+SVGFont.DESCENT+"\" alphabetic=\"0\"/>");
  249.         Out.println("<missing-glyph horiz-adv-x=\""+SVGFont.MISSING_HORZ+"\" d=\""+SVGFont.MISSING_DATA+"\"/>");
  250.         for (int n=0;n<96;n++) if (charMask[n])
  251.         {
  252.             Out.print("<glyph unicode=\""+SVGFont.UNICODE[n]+"\" glyph-name=\""+SVGFont.GLYPH_NAME[n]+"\""+" horiz-adv-x=\""+SVGFont.HORIZ_ADV_X[n]+"\"");
  253.            
  254.             if (SVGFont.GLYPH_DATA[n].length()>0) Out.print(" d=\""+SVGFont.GLYPH_DATA[n]+"\"");
  255.             Out.println("/>");
  256.         }
  257.         for (int n=0;n<SVGFont.KERN_K.length;n++) if (charMask[SVGFont.KERN_G1[n]] && charMask[SVGFont.KERN_G2[n]])
  258.         {
  259.             Out.println("<hkern g1=\""+SVGFont.KERN_G1[n]+"\" g2=\""+SVGFont.KERN_G2[n]+"\" k=\""+SVGFont.KERN_K[n]+"\"/>");
  260.         }
  261.  
  262.         Out.println("</font></defs>");
  263.         Out.println();
  264.                
  265.         // transform everything
  266.        
  267.         for (int n=0;n<atoms.size();n++)
  268.         {
  269.             Atom a=atoms.get(n);
  270.             if (a.AtomClass==ATOM_LINE)
  271.             {
  272.                 LineAtom la=(LineAtom)a;
  273.                 la.X1=OX+((la.X1-lowX)*SW+lowX); la.Y1=OY+((la.Y1-lowY)*SH+lowY);
  274.                 la.X2=OX+((la.X2-lowX)*SW+lowX); la.Y2=OY+((la.Y2-lowY)*SH+lowY);
  275.             }
  276.             else if (a.AtomClass==ATOM_RECT)
  277.             {
  278.                 RectAtom ra=(RectAtom)a;
  279.                 ra.X=OX+((ra.X-lowX)*SW+lowX); ra.Y=OY+((ra.Y-lowY)*SH+lowY);
  280.                 ra.W=ra.W*SW; ra.H=ra.H*SH;
  281.             }
  282.             else if (a.AtomClass==ATOM_OVAL)
  283.             {
  284.                 OvalAtom oa=(OvalAtom)a;
  285.                 oa.CX=OX+((oa.CX-lowX)*SW+lowX); oa.CY=OY+((oa.CY-lowY)*SH+lowY);
  286.                 oa.RW*=SW; oa.RH*=SH;
  287.             }
  288.             else if (a.AtomClass==ATOM_PATH)
  289.             {
  290.                 PathAtom pa=(PathAtom)a;
  291.                 for (int i=0;i<pa.N;i++) {pa.X[i]=OX+((pa.X[i]-lowX)*SW+lowX); pa.Y[i]=OY+((pa.Y[i]-lowY)*SH+lowY);}
  292.             }
  293.             else if (a.AtomClass==ATOM_TEXT)
  294.             {
  295.                 TextAtom ta=(TextAtom)a;
  296.                 // !! ta.X=OX+(ta.X*SW); ta.Y=OY+(ta.Y*SH);
  297.                 ta.X=OX+((ta.X-lowX)*SW+lowX); ta.Y=OY+((ta.Y-lowY)*SH+lowY);
  298.             }
  299.         }
  300.         double swsh=0.5*(SW+SH);
  301.         for (int n=0;n<lineTypes.size();n++) lineTypes.get(n).Thickness*=swsh;
  302.         for (int n=0;n<rectTypes.size();n++) rectTypes.get(n).Thickness*=swsh;
  303.         for (int n=0;n<ovalTypes.size();n++) ovalTypes.get(n).Thickness*=swsh;
  304.         for (int n=0;n<pathTypes.size();n++) pathTypes.get(n).Thickness*=swsh;
  305.         for (int n=0;n<textTypes.size();n++) textTypes.get(n).Sz*=swsh;
  306.  
  307.         // emit everything, in singlets or in groups
  308.         int p=0;
  309.         while (p<atoms.size())
  310.         {
  311.             Atom a=atoms.get(p);
  312.             int sz=1;
  313.             if (a.AtomClass!=ATOM_PATH) // (these are not rendered in groups)
  314.                 for (int n=p+1;n<atoms.size();n++,sz++)
  315.             {
  316.                 Atom ax=atoms.get(n);
  317.                 if (a.TypeRef!=ax.TypeRef || a.AtomClass!=ax.AtomClass) break;
  318.             }
  319.             if (a.AtomClass==ATOM_LINE) {if (sz==1) outputLine1(Out,(LineAtom)a); else outputLineN(Out,(LineAtom)a,p,sz);}
  320.             else if (a.AtomClass==ATOM_RECT) {if (sz==1) outputRect1(Out,(RectAtom)a); else outputRectN(Out,(RectAtom)a,p,sz);}
  321.             else if (a.AtomClass==ATOM_OVAL) {if (sz==1) outputOval1(Out,(OvalAtom)a); else outputOvalN(Out,(OvalAtom)a,p,sz);}
  322.             else if (a.AtomClass==ATOM_PATH) outputPath(Out,(PathAtom)a);
  323.             else if (a.AtomClass==ATOM_TEXT) {if (sz==1) outputText1(Out,(TextAtom)a); else outputTextN(Out,(TextAtom)a,p,sz);}
  324.            
  325.             p+=sz;
  326.         }
  327.  
  328.         Out.println("</g></svg>");
  329.         Out.flush();
  330.     }
  331.    
  332.     // a conveniently overloaded version which computes the size based on the properties of the drawing itself; the returned value provides
  333.     // the calculated width & height
  334.     public int[] build(PrintWriter Out)
  335.     {
  336.         int w=(int)Math.ceil(highX-lowX)+2,h=(int)Math.ceil(highY-lowY)+2;
  337.         double ox=1-lowX,oy=1-lowY;
  338.         build(Out,w,h,ox,oy,1,1);
  339.         return new int[]{w,h};
  340.     }
  341.    
  342.     // ------------------------------------------------ private functions ------------------------------------------------
  343.    
  344.     private void updateBounds(double X,double Y)
  345.     {
  346.         if (fresh) {lowX=highX=X; lowY=highY=Y; fresh=false;}
  347.         lowX=Math.min(lowX,X);
  348.         lowY=Math.min(lowY,Y);
  349.         highX=Math.max(highX,X);
  350.         highY=Math.max(highY,Y);
  351.     }
  352.    
  353.     private int registerLineType(LineType T)
  354.     {
  355.         for (int n=0;n<lineTypes.size();n++)
  356.         {
  357.             LineType tx=lineTypes.get(n);
  358.             if (Util.dblEqual(T.Thickness,tx.Thickness) && T.Colour==tx.Colour) return n;
  359.         }
  360.         lineTypes.add(T);
  361.         return lineTypes.size()-1;
  362.     }
  363.     private int registerRectType(RectType T)
  364.     {
  365.         for (int n=0;n<rectTypes.size();n++)
  366.         {
  367.             RectType tx=rectTypes.get(n);
  368.             if (T.EdgeCol==tx.EdgeCol && Util.dblEqual(T.Thickness,tx.Thickness) && T.FillCol==tx.FillCol) return n;
  369.         }
  370.         rectTypes.add(T);
  371.         return rectTypes.size()-1;
  372.     }
  373.     private int registerOvalType(OvalType T)
  374.     {
  375.         for (int n=0;n<ovalTypes.size();n++)
  376.         {
  377.             OvalType tx=ovalTypes.get(n);
  378.             if (T.EdgeCol==tx.EdgeCol && Util.dblEqual(T.Thickness,tx.Thickness) && T.FillCol==tx.FillCol) return n;
  379.         }
  380.         ovalTypes.add(T);
  381.         return ovalTypes.size()-1;
  382.     }
  383.     private int registerPathType(PathType T)
  384.     {
  385.         for (int n=0;n<pathTypes.size();n++)
  386.         {
  387.             PathType tx=pathTypes.get(n);
  388.             if (T.EdgeCol==tx.EdgeCol && T.FillCol==tx.FillCol &&
  389.                 Util.dblEqual(T.Thickness,tx.Thickness) && T.HardEdge==tx.HardEdge) return n;
  390.         }
  391.         pathTypes.add(T);
  392.         return pathTypes.size()-1;
  393.     }
  394.     private int registerTextType(TextType T)
  395.     {
  396.         for (int n=0;n<textTypes.size();n++)
  397.         {
  398.             TextType tx=textTypes.get(n);
  399.             if (Util.dblEqual(T.Sz,tx.Sz) && T.Colour==tx.Colour && T.Style==tx.Style && T.Align==tx.Align) return n;
  400.         }
  401.         textTypes.add(T);
  402.         return textTypes.size()-1;
  403.     }
  404.    
  405.     private void outputLine1(PrintWriter Out,LineAtom A)
  406.     {
  407.         LineType type=lineTypes.get(A.TypeRef);
  408.         Out.println(
  409.             "<line x1=\""+df.format(A.X1)+"\" y1=\""+df.format(A.Y1)+"\" x2=\""+df.format(A.X2)+"\" y2=\""+df.format(A.Y2)+"\""+" stroke=\""+Util.colourHTML(type.Colour)+"\" stroke-width=\""+type.Thickness+"\"  stroke-linecap=\"round\"/>");
  410.     }
  411.     private void outputLineN(PrintWriter Out,LineAtom A,int N,int Sz)
  412.     {
  413.         LineType type=lineTypes.get(A.TypeRef);
  414.         Out.println("<g stroke=\""+Util.colourHTML(type.Colour)+"\" stroke-width=\""+type.Thickness+"\" stroke-linecap=\"round\">");
  415.         for (int n=0;n<Sz;n++)
  416.         {
  417.             LineAtom a=n==0 ? A : (LineAtom)atoms.get(N+n);
  418.             Out.println("<line x1=\""+df.format(a.X1)+"\" y1=\""+df.format(a.Y1)+"\" x2=\""+df.format(a.X2)+"\" y2=\""+df.format(a.Y2)+"\"/>");
  419.         }
  420.         Out.println("</g>");
  421.     }
  422.     private void outputRect1(PrintWriter Out,RectAtom A)
  423.     {
  424.         RectType type=rectTypes.get(A.TypeRef);
  425.         String edge=type.EdgeCol==NOCOLOUR ? "none" : Util.colourHTML(type.EdgeCol);
  426.         String fill=type.FillCol==NOCOLOUR ? "none" : Util.colourHTML(type.FillCol);
  427.    
  428.         Out.println("<rect x=\""+df.format(A.X)+"\" y=\""+df.format(A.Y)+"\" width=\""+df.format(A.W)+"\" height=\""+df.format(A.H)+"\""+" stroke=\""+edge+"\" stroke-width=\""+type.Thickness+"\"fill=\""+fill+"\"/>");
  429.     }
  430.     private void outputRectN(PrintWriter Out,RectAtom A,int N,int Sz)
  431.     {
  432.         RectType type=rectTypes.get(A.TypeRef);
  433.         String edge=type.EdgeCol==NOCOLOUR ? "none" : Util.colourHTML(type.EdgeCol);
  434.         String fill=type.FillCol==NOCOLOUR ? "none" : Util.colourHTML(type.FillCol);
  435.  
  436.         Out.println("<g stroke=\""+edge+"\" stroke-width=\""+type.Thickness+"\" fill=\""+fill+"\">");
  437.         for (int n=0;n<Sz;n++)
  438.         {
  439.             RectAtom a=n==0 ? A : (RectAtom)atoms.get(N+n);
  440.             Out.println("<rect x=\""+df.format(a.X)+"\" y=\""+df.format(a.Y)+"\" width=\""+df.format(a.W)+"\" height=\""+df.format(a.H)+"\"/>");
  441.         }
  442.         Out.println("</g>");
  443.     }
  444.     private void outputOval1(PrintWriter Out,OvalAtom A)
  445.     {
  446.         OvalType type=ovalTypes.get(A.TypeRef);
  447.         String edge=type.EdgeCol==NOCOLOUR ? "none" : Util.colourHTML(type.EdgeCol);
  448.         String fill=type.FillCol==NOCOLOUR ? "none" : Util.colourHTML(type.FillCol);
  449.    
  450.         Out.println("<ellipse cx=\""+df.format(A.CX)+"\" cy=\""+df.format(A.CY)+"\" rx=\""+df.format(A.RW)+"\" ry=\""+df.format(A.RH)+"\""+" stroke=\""+edge+"\" stroke-width=\""+type.Thickness+"\" fill=\""+fill+"\"/>");
  451.     }
  452.     private void outputOvalN(PrintWriter Out,OvalAtom A,int N,int Sz)
  453.     {
  454.         OvalType type=ovalTypes.get(A.TypeRef);
  455.         String edge=type.EdgeCol==NOCOLOUR ? "none" : Util.colourHTML(type.EdgeCol);
  456.         String fill=type.FillCol==NOCOLOUR ? "none" : Util.colourHTML(type.FillCol);
  457.  
  458.         Out.println("<g stroke=\""+edge+"\" stroke-width=\""+type.Thickness+"\" fill=\""+fill+"\">");
  459.         for (int n=0;n<Sz;n++)
  460.         {
  461.             OvalAtom a=n==0 ? A : (OvalAtom)atoms.get(N+n);
  462.             Out.println("<ellipse cx=\""+df.format(a.CX)+"\" cy=\""+df.format(a.CY)+"\" rx=\""+df.format(a.RW)+"\" ry=\""+df.format(a.RH)+"\"/>");
  463.         }
  464.         Out.println("</g>");
  465.     }
  466.     private void outputPath(PrintWriter Out,PathAtom A)
  467.     {
  468.         PathType type=pathTypes.get(A.TypeRef);
  469.         String edge=type.EdgeCol==NOCOLOUR ? "none" : Util.colourHTML(type.EdgeCol);
  470.         String fill=type.FillCol==NOCOLOUR ? "none" : Util.colourHTML(type.FillCol);
  471.         String join=type.HardEdge ? "miter" : "round",cap=type.HardEdge ? "square" : "round";
  472.         String shape="M "+A.X[0]+" "+A.Y[0];
  473.         int n=1;
  474.         while (n<A.N)
  475.         {
  476.             if (!A.Ctrl[n]) {shape+=" L "+A.X[n]+" "+A.Y[n]; n++;}
  477.             else if (A.Ctrl[n] && n<A.N-1 && !A.Ctrl[n+1])
  478.             {
  479.                 shape+=" Q "+A.X[n]+" "+A.Y[n]+" "+A.X[n+1]+" "+A.Y[n+1];
  480.                 n+=2;
  481.             }
  482.             else if (A.Ctrl[n] && n<A.N-2 && A.Ctrl[n+1] && !A.Ctrl[n+2])
  483.             {
  484.                 shape+=" C "+A.X[n]+" "+A.Y[n]+" "+A.X[n+1]+" "+A.Y[n+1]+" "+A.X[n+2]+" "+A.Y[n+2];
  485.                 n+=3;
  486.             }
  487.             else n++; // (dunno, so skip)
  488.         }
  489.         if (A.Closed) shape+=" Z";
  490.        
  491.         Out.println("<path d=\""+shape+"\" stroke=\""+edge+"\" fill=\""+fill+"\" stroke-width=\""+type.Thickness+"\""+" stroke-linejoin=\""+join+"\" stroke-linecap=\""+cap+"\"/>");
  492.     }
  493.     private void outputText1(PrintWriter Out,TextAtom A)
  494.     {
  495.         TextType type=textTypes.get(A.TypeRef);
  496.         String anchor=type.Align==TXTALIGN_LEFT ? "start" : type.Align==TXTALIGN_RIGHT ? "end" : "middle";
  497.  
  498.         // !! don't forget style...
  499.  
  500.         Out.println(
  501.             "<text x=\""+df.format(A.X)+"\" y=\""+df.format(A.Y)+"\" font-family=\"Verdana\" font-size=\""+type.Sz+"\""+" text-anchor=\""+anchor+"\" fill=\""+Util.colourHTML(type.Colour)+"\">"+A.Txt+"</text>");
  502.     }
  503.     private void outputTextN(PrintWriter Out,TextAtom A,int N,int Sz)
  504.     {
  505.         TextType type=textTypes.get(A.TypeRef);
  506.         String anchor=type.Align==TXTALIGN_LEFT ? "start" : type.Align==TXTALIGN_RIGHT ? "end" : "middle";
  507.  
  508.         // !! don't forget style...
  509.  
  510.         Out.println(
  511.             "<g font-family=\"Verdana\" font-size=\""+type.Sz+"\""+" text-anchor=\""+anchor+"\" fill=\""+Util.colourHTML(type.Colour)+"\">");
  512.         for (int n=0;n<Sz;n++)
  513.         {
  514.             TextAtom a=n==0 ? A : (TextAtom)atoms.get(N+n);
  515.             Out.println("<text x=\""+df.format(a.X)+"\" y=\""+df.format(a.Y)+"\">"+a.Txt+"</text>");
  516.         }
  517.         Out.println("</g>");
  518.     }
  519. }
  520.