Subversion Repositories wimsdev

Rev

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

Rev Author Line No. Line
7246 schaersvoo 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
{
7292 schaersvoo 22
    DecimalFormat df = new DecimalFormat("#.##",new DecimalFormatSymbols(Locale.US));
7246 schaersvoo 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";
7293 schaersvoo 31
    public String svg_id = "SVG_1000000";
7246 schaersvoo 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;
7293 schaersvoo 234
            svg_id = MainApplet.svg_id;
7296 schaersvoo 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)\">");
7246 schaersvoo 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");
7296 schaersvoo 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)\">");
7246 schaersvoo 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
}