Subversion Repositories wimsdev

Rev

Rev 7292 | Go to most recent revision | 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;
7246 schaersvoo 235
        }
236
        else
237
        {
238
            Out.println("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n");
239
            Out.println("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 20010904//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
240
        }
7293 schaersvoo 241
        Out.println("<svg id=\""+svg_id+"\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\"");
242
        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 243
        Out.println();
244
        // now write out the font definition
245
        Out.println("<defs><font id=\""+SVGFont.FONT_FAMILY+"\" horiz-adv-x=\""+SVGFont.FONT_ADV+"\">");
246
        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\"/>");
247
        Out.println("<missing-glyph horiz-adv-x=\""+SVGFont.MISSING_HORZ+"\" d=\""+SVGFont.MISSING_DATA+"\"/>");
248
        for (int n=0;n<96;n++) if (charMask[n])
249
        {
250
            Out.print("<glyph unicode=\""+SVGFont.UNICODE[n]+"\" glyph-name=\""+SVGFont.GLYPH_NAME[n]+"\""+" horiz-adv-x=\""+SVGFont.HORIZ_ADV_X[n]+"\"");
251
 
252
            if (SVGFont.GLYPH_DATA[n].length()>0) Out.print(" d=\""+SVGFont.GLYPH_DATA[n]+"\"");
253
            Out.println("/>");
254
        }
255
        for (int n=0;n<SVGFont.KERN_K.length;n++) if (charMask[SVGFont.KERN_G1[n]] && charMask[SVGFont.KERN_G2[n]])
256
        {
257
            Out.println("<hkern g1=\""+SVGFont.KERN_G1[n]+"\" g2=\""+SVGFont.KERN_G2[n]+"\" k=\""+SVGFont.KERN_K[n]+"\"/>");
258
        }
259
 
260
        Out.println("</font></defs>");
261
        Out.println();
262
 
263
        // transform everything
264
 
265
        for (int n=0;n<atoms.size();n++)
266
        {
267
            Atom a=atoms.get(n);
268
            if (a.AtomClass==ATOM_LINE)
269
            {
270
                LineAtom la=(LineAtom)a;
271
                la.X1=OX+((la.X1-lowX)*SW+lowX); la.Y1=OY+((la.Y1-lowY)*SH+lowY);
272
                la.X2=OX+((la.X2-lowX)*SW+lowX); la.Y2=OY+((la.Y2-lowY)*SH+lowY);
273
            }
274
            else if (a.AtomClass==ATOM_RECT)
275
            {
276
                RectAtom ra=(RectAtom)a;
277
                ra.X=OX+((ra.X-lowX)*SW+lowX); ra.Y=OY+((ra.Y-lowY)*SH+lowY);
278
                ra.W=ra.W*SW; ra.H=ra.H*SH;
279
            }
280
            else if (a.AtomClass==ATOM_OVAL)
281
            {
282
                OvalAtom oa=(OvalAtom)a;
283
                oa.CX=OX+((oa.CX-lowX)*SW+lowX); oa.CY=OY+((oa.CY-lowY)*SH+lowY);
284
                oa.RW*=SW; oa.RH*=SH;
285
            }
286
            else if (a.AtomClass==ATOM_PATH)
287
            {
288
                PathAtom pa=(PathAtom)a;
289
                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);}
290
            }
291
            else if (a.AtomClass==ATOM_TEXT)
292
            {
293
                TextAtom ta=(TextAtom)a;
294
                // !! ta.X=OX+(ta.X*SW); ta.Y=OY+(ta.Y*SH); 
295
                ta.X=OX+((ta.X-lowX)*SW+lowX); ta.Y=OY+((ta.Y-lowY)*SH+lowY);
296
            }
297
        }
298
        double swsh=0.5*(SW+SH);
299
        for (int n=0;n<lineTypes.size();n++) lineTypes.get(n).Thickness*=swsh;
300
        for (int n=0;n<rectTypes.size();n++) rectTypes.get(n).Thickness*=swsh;
301
        for (int n=0;n<ovalTypes.size();n++) ovalTypes.get(n).Thickness*=swsh;
302
        for (int n=0;n<pathTypes.size();n++) pathTypes.get(n).Thickness*=swsh;
303
        for (int n=0;n<textTypes.size();n++) textTypes.get(n).Sz*=swsh;
304
 
305
        // emit everything, in singlets or in groups
306
        int p=0;
307
        while (p<atoms.size())
308
        {
309
            Atom a=atoms.get(p);
310
            int sz=1;
311
            if (a.AtomClass!=ATOM_PATH) // (these are not rendered in groups)
312
                for (int n=p+1;n<atoms.size();n++,sz++)
313
            {
314
                Atom ax=atoms.get(n);
315
                if (a.TypeRef!=ax.TypeRef || a.AtomClass!=ax.AtomClass) break;
316
            }
317
            if (a.AtomClass==ATOM_LINE) {if (sz==1) outputLine1(Out,(LineAtom)a); else outputLineN(Out,(LineAtom)a,p,sz);}
318
            else if (a.AtomClass==ATOM_RECT) {if (sz==1) outputRect1(Out,(RectAtom)a); else outputRectN(Out,(RectAtom)a,p,sz);}
319
            else if (a.AtomClass==ATOM_OVAL) {if (sz==1) outputOval1(Out,(OvalAtom)a); else outputOvalN(Out,(OvalAtom)a,p,sz);}
320
            else if (a.AtomClass==ATOM_PATH) outputPath(Out,(PathAtom)a);
321
            else if (a.AtomClass==ATOM_TEXT) {if (sz==1) outputText1(Out,(TextAtom)a); else outputTextN(Out,(TextAtom)a,p,sz);}
322
 
323
            p+=sz;
324
        }
325
 
326
        Out.println("</g></svg>");
327
        Out.flush();
328
    }
329
 
330
    // a conveniently overloaded version which computes the size based on the properties of the drawing itself; the returned value provides
331
    // the calculated width & height
332
    public int[] build(PrintWriter Out)
333
    {
334
        int w=(int)Math.ceil(highX-lowX)+2,h=(int)Math.ceil(highY-lowY)+2;
335
        double ox=1-lowX,oy=1-lowY;
336
        build(Out,w,h,ox,oy,1,1);
337
        return new int[]{w,h};
338
    }
339
 
340
    // ------------------------------------------------ private functions ------------------------------------------------
341
 
342
    private void updateBounds(double X,double Y)
343
    {
344
        if (fresh) {lowX=highX=X; lowY=highY=Y; fresh=false;}
345
        lowX=Math.min(lowX,X);
346
        lowY=Math.min(lowY,Y);
347
        highX=Math.max(highX,X);
348
        highY=Math.max(highY,Y);
349
    }
350
 
351
    private int registerLineType(LineType T)
352
    {
353
        for (int n=0;n<lineTypes.size();n++)
354
        {
355
            LineType tx=lineTypes.get(n);
356
            if (Util.dblEqual(T.Thickness,tx.Thickness) && T.Colour==tx.Colour) return n;
357
        }
358
        lineTypes.add(T);
359
        return lineTypes.size()-1;
360
    }
361
    private int registerRectType(RectType T)
362
    {
363
        for (int n=0;n<rectTypes.size();n++)
364
        {
365
            RectType tx=rectTypes.get(n);
366
            if (T.EdgeCol==tx.EdgeCol && Util.dblEqual(T.Thickness,tx.Thickness) && T.FillCol==tx.FillCol) return n;
367
        }
368
        rectTypes.add(T);
369
        return rectTypes.size()-1;
370
    }
371
    private int registerOvalType(OvalType T)
372
    {
373
        for (int n=0;n<ovalTypes.size();n++)
374
        {
375
            OvalType tx=ovalTypes.get(n);
376
            if (T.EdgeCol==tx.EdgeCol && Util.dblEqual(T.Thickness,tx.Thickness) && T.FillCol==tx.FillCol) return n;
377
        }
378
        ovalTypes.add(T);
379
        return ovalTypes.size()-1;
380
    }
381
    private int registerPathType(PathType T)
382
    {
383
        for (int n=0;n<pathTypes.size();n++)
384
        {
385
            PathType tx=pathTypes.get(n);
386
            if (T.EdgeCol==tx.EdgeCol && T.FillCol==tx.FillCol &&
387
                Util.dblEqual(T.Thickness,tx.Thickness) && T.HardEdge==tx.HardEdge) return n;
388
        }
389
        pathTypes.add(T);
390
        return pathTypes.size()-1;
391
    }
392
    private int registerTextType(TextType T)
393
    {
394
        for (int n=0;n<textTypes.size();n++)
395
        {
396
            TextType tx=textTypes.get(n);
397
            if (Util.dblEqual(T.Sz,tx.Sz) && T.Colour==tx.Colour && T.Style==tx.Style && T.Align==tx.Align) return n;
398
        }
399
        textTypes.add(T);
400
        return textTypes.size()-1;
401
    }
402
 
403
    private void outputLine1(PrintWriter Out,LineAtom A)
404
    {
405
        LineType type=lineTypes.get(A.TypeRef);
406
        Out.println(
407
            "<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\"/>");
408
    }
409
    private void outputLineN(PrintWriter Out,LineAtom A,int N,int Sz)
410
    {
411
        LineType type=lineTypes.get(A.TypeRef);
412
        Out.println("<g stroke=\""+Util.colourHTML(type.Colour)+"\" stroke-width=\""+type.Thickness+"\" stroke-linecap=\"round\">");
413
        for (int n=0;n<Sz;n++)
414
        {
415
            LineAtom a=n==0 ? A : (LineAtom)atoms.get(N+n);
416
            Out.println("<line x1=\""+df.format(a.X1)+"\" y1=\""+df.format(a.Y1)+"\" x2=\""+df.format(a.X2)+"\" y2=\""+df.format(a.Y2)+"\"/>");
417
        }
418
        Out.println("</g>");
419
    }
420
    private void outputRect1(PrintWriter Out,RectAtom A)
421
    {
422
        RectType type=rectTypes.get(A.TypeRef);
423
        String edge=type.EdgeCol==NOCOLOUR ? "none" : Util.colourHTML(type.EdgeCol);
424
        String fill=type.FillCol==NOCOLOUR ? "none" : Util.colourHTML(type.FillCol);
425
 
426
        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+"\"/>");
427
    }
428
    private void outputRectN(PrintWriter Out,RectAtom A,int N,int Sz)
429
    {
430
        RectType type=rectTypes.get(A.TypeRef);
431
        String edge=type.EdgeCol==NOCOLOUR ? "none" : Util.colourHTML(type.EdgeCol);
432
        String fill=type.FillCol==NOCOLOUR ? "none" : Util.colourHTML(type.FillCol);
433
 
434
        Out.println("<g stroke=\""+edge+"\" stroke-width=\""+type.Thickness+"\" fill=\""+fill+"\">");
435
        for (int n=0;n<Sz;n++)
436
        {
437
            RectAtom a=n==0 ? A : (RectAtom)atoms.get(N+n);
438
            Out.println("<rect x=\""+df.format(a.X)+"\" y=\""+df.format(a.Y)+"\" width=\""+df.format(a.W)+"\" height=\""+df.format(a.H)+"\"/>");
439
        }
440
        Out.println("</g>");
441
    }
442
    private void outputOval1(PrintWriter Out,OvalAtom A)
443
    {
444
        OvalType type=ovalTypes.get(A.TypeRef);
445
        String edge=type.EdgeCol==NOCOLOUR ? "none" : Util.colourHTML(type.EdgeCol);
446
        String fill=type.FillCol==NOCOLOUR ? "none" : Util.colourHTML(type.FillCol);
447
 
448
        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+"\"/>");
449
    }
450
    private void outputOvalN(PrintWriter Out,OvalAtom A,int N,int Sz)
451
    {
452
        OvalType type=ovalTypes.get(A.TypeRef);
453
        String edge=type.EdgeCol==NOCOLOUR ? "none" : Util.colourHTML(type.EdgeCol);
454
        String fill=type.FillCol==NOCOLOUR ? "none" : Util.colourHTML(type.FillCol);
455
 
456
        Out.println("<g stroke=\""+edge+"\" stroke-width=\""+type.Thickness+"\" fill=\""+fill+"\">");
457
        for (int n=0;n<Sz;n++)
458
        {
459
            OvalAtom a=n==0 ? A : (OvalAtom)atoms.get(N+n);
460
            Out.println("<ellipse cx=\""+df.format(a.CX)+"\" cy=\""+df.format(a.CY)+"\" rx=\""+df.format(a.RW)+"\" ry=\""+df.format(a.RH)+"\"/>");
461
        }
462
        Out.println("</g>");
463
    }
464
    private void outputPath(PrintWriter Out,PathAtom A)
465
    {
466
        PathType type=pathTypes.get(A.TypeRef);
467
        String edge=type.EdgeCol==NOCOLOUR ? "none" : Util.colourHTML(type.EdgeCol);
468
        String fill=type.FillCol==NOCOLOUR ? "none" : Util.colourHTML(type.FillCol);
469
        String join=type.HardEdge ? "miter" : "round",cap=type.HardEdge ? "square" : "round";
470
        String shape="M "+A.X[0]+" "+A.Y[0];
471
        int n=1;
472
        while (n<A.N)
473
        {
474
            if (!A.Ctrl[n]) {shape+=" L "+A.X[n]+" "+A.Y[n]; n++;}
475
            else if (A.Ctrl[n] && n<A.N-1 && !A.Ctrl[n+1])
476
            {
477
                shape+=" Q "+A.X[n]+" "+A.Y[n]+" "+A.X[n+1]+" "+A.Y[n+1];
478
                n+=2;
479
            }
480
            else if (A.Ctrl[n] && n<A.N-2 && A.Ctrl[n+1] && !A.Ctrl[n+2])
481
            {
482
                shape+=" C "+A.X[n]+" "+A.Y[n]+" "+A.X[n+1]+" "+A.Y[n+1]+" "+A.X[n+2]+" "+A.Y[n+2];
483
                n+=3;
484
            }
485
            else n++; // (dunno, so skip)
486
        }
487
        if (A.Closed) shape+=" Z";
488
 
489
        Out.println("<path d=\""+shape+"\" stroke=\""+edge+"\" fill=\""+fill+"\" stroke-width=\""+type.Thickness+"\""+" stroke-linejoin=\""+join+"\" stroke-linecap=\""+cap+"\"/>");
490
    }
491
    private void outputText1(PrintWriter Out,TextAtom A)
492
    {
493
        TextType type=textTypes.get(A.TypeRef);
494
        String anchor=type.Align==TXTALIGN_LEFT ? "start" : type.Align==TXTALIGN_RIGHT ? "end" : "middle";
495
 
496
        // !! don't forget style...
497
 
498
        Out.println(
499
            "<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>");
500
    }
501
    private void outputTextN(PrintWriter Out,TextAtom A,int N,int Sz)
502
    {
503
        TextType type=textTypes.get(A.TypeRef);
504
        String anchor=type.Align==TXTALIGN_LEFT ? "start" : type.Align==TXTALIGN_RIGHT ? "end" : "middle";
505
 
506
        // !! don't forget style...
507
 
508
        Out.println(
509
            "<g font-family=\"Verdana\" font-size=\""+type.Sz+"\""+" text-anchor=\""+anchor+"\" fill=\""+Util.colourHTML(type.Colour)+"\">");
510
        for (int n=0;n<Sz;n++)
511
        {
512
            TextAtom a=n==0 ? A : (TextAtom)atoms.get(N+n);
513
            Out.println("<text x=\""+df.format(a.X)+"\" y=\""+df.format(a.Y)+"\">"+a.Txt+"</text>");
514
        }
515
        Out.println("</g>");
516
    }
517
}