001/*
002 * Copyright 2006 - 2013
003 *     Stefan Balev     <stefan.balev@graphstream-project.org>
004 *     Julien Baudry    <julien.baudry@graphstream-project.org>
005 *     Antoine Dutot    <antoine.dutot@graphstream-project.org>
006 *     Yoann Pigné      <yoann.pigne@graphstream-project.org>
007 *     Guilhelm Savin   <guilhelm.savin@graphstream-project.org>
008 * 
009 * This file is part of GraphStream <http://graphstream-project.org>.
010 * 
011 * GraphStream is a library whose purpose is to handle static or dynamic
012 * graph, create them from scratch, file or any source and display them.
013 * 
014 * This program is free software distributed under the terms of two licenses, the
015 * CeCILL-C license that fits European law, and the GNU Lesser General Public
016 * License. You can  use, modify and/ or redistribute the software under the terms
017 * of the CeCILL-C license as circulated by CEA, CNRS and INRIA at the following
018 * URL <http://www.cecill.info> or under the terms of the GNU LGPL as published by
019 * the Free Software Foundation, either version 3 of the License, or (at your
020 * option) any later version.
021 * 
022 * This program is distributed in the hope that it will be useful, but WITHOUT ANY
023 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
024 * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
025 * 
026 * You should have received a copy of the GNU Lesser General Public License
027 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
028 * 
029 * The fact that you are presently reading this means that you have had
030 * knowledge of the CeCILL-C and LGPL licenses and that you accept their terms.
031 */
032package org.graphstream.stream.file;
033
034import java.awt.Color;
035import java.io.FileWriter;
036import java.io.IOException;
037import java.io.OutputStream;
038import java.io.OutputStreamWriter;
039import java.io.Writer;
040import java.util.HashMap;
041import java.util.HashSet;
042import java.util.Iterator;
043import java.util.Locale;
044
045import javax.xml.stream.FactoryConfigurationError;
046import javax.xml.stream.XMLOutputFactory;
047import javax.xml.stream.XMLStreamException;
048import javax.xml.stream.XMLStreamWriter;
049
050import org.graphstream.graph.Edge;
051import org.graphstream.graph.Element;
052import org.graphstream.graph.Graph;
053import org.graphstream.graph.Node;
054import org.graphstream.ui.graphicGraph.StyleGroup;
055import org.graphstream.ui.graphicGraph.StyleGroupSet;
056import org.graphstream.ui.graphicGraph.stylesheet.Colors;
057import org.graphstream.ui.graphicGraph.stylesheet.Selector;
058import org.graphstream.ui.graphicGraph.stylesheet.StyleConstants;
059import org.graphstream.ui.graphicGraph.stylesheet.Value;
060import org.graphstream.ui.graphicGraph.stylesheet.Values;
061import org.graphstream.ui.graphicGraph.stylesheet.Selector.Type;
062import org.graphstream.ui.graphicGraph.stylesheet.StyleConstants.StrokeMode;
063import org.graphstream.ui.graphicGraph.stylesheet.StyleSheet;
064
065public class FileSinkSVG2 implements FileSink {
066
067        /*
068         * (non-Javadoc)
069         * 
070         * @see org.graphstream.stream.file.FileSink#begin(java.lang.String)
071         */
072        public void begin(String fileName) throws IOException {
073                throw new UnsupportedOperationException();
074        }
075
076        /*
077         * (non-Javadoc)
078         * 
079         * @see org.graphstream.stream.file.FileSink#begin(java.io.OutputStream)
080         */
081        public void begin(OutputStream stream) throws IOException {
082                throw new UnsupportedOperationException();
083        }
084
085        /*
086         * (non-Javadoc)
087         * 
088         * @see org.graphstream.stream.file.FileSink#begin(java.io.Writer)
089         */
090        public void begin(Writer writer) throws IOException {
091                throw new UnsupportedOperationException();
092        }
093
094        /*
095         * (non-Javadoc)
096         * 
097         * @see org.graphstream.stream.file.FileSink#end()
098         */
099        public void end() throws IOException {
100                throw new UnsupportedOperationException();
101        }
102
103        /*
104         * (non-Javadoc)
105         * 
106         * @see org.graphstream.stream.file.FileSink#flush()
107         */
108        public void flush() throws IOException {
109        }
110
111        /*
112         * (non-Javadoc)
113         * 
114         * @see
115         * org.graphstream.stream.file.FileSink#writeAll(org.graphstream.graph.Graph
116         * , java.lang.String)
117         */
118        public void writeAll(Graph graph, String fileName) throws IOException {
119                FileWriter out = new FileWriter(fileName);
120                writeAll(graph, out);
121                out.close();
122        }
123
124        /*
125         * (non-Javadoc)
126         * 
127         * @see
128         * org.graphstream.stream.file.FileSink#writeAll(org.graphstream.graph.Graph
129         * , java.io.OutputStream)
130         */
131        public void writeAll(Graph graph, OutputStream stream) throws IOException {
132                OutputStreamWriter out = new OutputStreamWriter(stream);
133                writeAll(graph, out);
134        }
135
136        /*
137         * (non-Javadoc)
138         * 
139         * @see
140         * org.graphstream.stream.file.FileSink#writeAll(org.graphstream.graph.Graph
141         * , java.io.Writer)
142         */
143        public void writeAll(Graph g, Writer w) throws IOException {
144                XMLWriter out = new XMLWriter();
145                SVGContext ctx = new SVGContext();
146
147                try {
148                        out.start(w);
149                } catch (XMLStreamException e) {
150                        throw new IOException(e);
151                } catch (FactoryConfigurationError e) {
152                        throw new RuntimeException(e);
153                }
154
155                try {
156                        ctx.init(out, g);
157                        ctx.writeElements(out, g);
158                        ctx.end(out);
159                } catch (XMLStreamException e) {
160                        throw new IOException(e);
161                }
162
163                try {
164                        out.end();
165                } catch (XMLStreamException e) {
166                        throw new IOException(e);
167                }
168        }
169
170        private static String d(double d) {
171                return String.format(Locale.ROOT, "%f", d);
172        }
173
174        private static double getX(Node n) {
175                if (n.hasNumber("x"))
176                        return n.getNumber("x");
177
178                if (n.hasArray("xy")) {
179                        Object[] xy = n.getArray("xy");
180
181                        if (xy != null && xy.length > 0 && xy[0] instanceof Number)
182                                return ((Number) xy[0]).doubleValue();
183                }
184
185                if (n.hasArray("xyz")) {
186                        Object[] xyz = n.getArray("xyz");
187
188                        if (xyz != null && xyz.length > 0 && xyz[0] instanceof Number)
189                                return ((Number) xyz[0]).doubleValue();
190                }
191
192                System.err.printf("[WARNING] no x attribute for node \"%s\" %s\n",
193                                n.getId(), n.hasAttribute("xyz"));
194
195                return Math.random();
196        }
197
198        private static double getY(Node n) {
199                if (n.hasNumber("y"))
200                        return n.getNumber("y");
201
202                if (n.hasArray("xy")) {
203                        Object[] xy = n.getArray("xy");
204
205                        if (xy != null && xy.length > 1 && xy[1] instanceof Number)
206                                return ((Number) xy[1]).doubleValue();
207                }
208
209                if (n.hasArray("xyz")) {
210                        Object[] xyz = n.getArray("xyz");
211
212                        if (xyz != null && xyz.length > 1 && xyz[1] instanceof Number)
213                                return ((Number) xyz[1]).doubleValue();
214                }
215
216                return Math.random();
217        }
218
219        private static String getSize(Value v) {
220                String u = v.units.name().toLowerCase();
221                return String.format(Locale.ROOT, "%f%s", v.value, u);
222        }
223
224        private static String getSize(Values v, int index) {
225                String u = v.units.name().toLowerCase();
226                return String.format(Locale.ROOT, "%f%s", v.get(index), u);
227        }
228
229        static class SVGContext {
230                StyleGroupSet groups;
231                StyleSheet stylesheet;
232                HashMap<StyleGroup, SVGStyle> svgStyles;
233                ViewBox viewBox;
234
235                public SVGContext() {
236                        stylesheet = new StyleSheet();
237                        groups = new StyleGroupSet(stylesheet);
238                        svgStyles = new HashMap<StyleGroup, SVGStyle>();
239                        viewBox = new ViewBox(0, 0, 1000, 1000);
240                }
241
242                public void init(XMLWriter out, Graph g) throws IOException,
243                                XMLStreamException {
244
245                        if (g.hasAttribute("ui.stylesheet")) {
246                                stylesheet.load(((String) g.getAttribute("ui.stylesheet")));
247                        }
248
249                        groups.addElement(g);
250                        viewBox.compute(g, groups.getStyleFor(g));
251
252                        out.open("svg");
253                        out.attribute("xmlns", "http://www.w3.org/2000/svg");
254                        out.attribute("xmlns:dc", "http://purl.org/dc/elements/1.1/");
255                        out.attribute("xmlns:cc", "http://creativecommons.org/ns#");
256                        out.attribute("xmlns:rdf",
257                                        "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
258                        out.attribute("xmlns:svg", "http://www.w3.org/2000/svg");
259
260                        out.attribute("viewBox", String.format(Locale.ROOT, "%f %f %f %f",
261                                        viewBox.x1, viewBox.y1, viewBox.x2, viewBox.y2));
262
263                        out.attribute("id", g.getId());
264                        out.attribute("version", "1.1");
265
266                        for (Edge e : g.getEachEdge()) {
267                                groups.addElement(e);
268
269                                if (e.hasAttribute("ui.style"))
270                                        stylesheet.parseStyleFromString(
271                                                        new Selector(Type.EDGE, e.getId(), null),
272                                                        (String) e.getAttribute("ui.style"));
273
274                                groups.checkElementStyleGroup(e);
275                        }
276
277                        for (Node n : g.getEachNode()) {
278                                groups.addElement(n);
279
280                                if (n.hasAttribute("ui.style"))
281                                        stylesheet.parseStyleFromString(
282                                                        new Selector(Type.NODE, n.getId(), null),
283                                                        (String) n.getAttribute("ui.style"));
284
285                                groups.checkElementStyleGroup(n);
286                        }
287
288                        for (StyleGroup group : groups.groups())
289                                svgStyles.put(group, new SVGStyle(group));
290
291                        out.open("defs");
292                        for (SVGStyle svgStyle : svgStyles.values())
293                                svgStyle.writeDef(out);
294                        out.close();
295                }
296
297                public void end(XMLWriter out) throws XMLStreamException {
298                        out.close();
299                }
300
301                public void writeElements(XMLWriter out, Graph g)
302                                throws XMLStreamException {
303                        out.open("g");
304                        out.attribute("id", "graph-misc");
305                        writeElement(out, g);
306                        out.close();
307
308                        Iterator<HashSet<StyleGroup>> it = groups.getZIterator();
309
310                        out.open("g");
311                        out.attribute("id", "elements");
312
313                        while (it.hasNext()) {
314                                HashSet<StyleGroup> set = it.next();
315
316                                for (StyleGroup sg : set)
317                                        for (Element e : sg.elements())
318                                                writeElement(out, e);
319                        }
320
321                        out.close();
322                }
323
324                public void writeElement(XMLWriter out, Element e)
325                                throws XMLStreamException {
326                        String id = "";
327                        SVGStyle style = null;
328                        String transform = null;
329
330                        if (e instanceof Edge) {
331                                id = String.format("egde-%s", e.getId());
332                                style = svgStyles.get(groups.getStyleFor((Edge) e));
333                        } else if (e instanceof Node) {
334                                id = String.format("node-%s", e.getId());
335                                style = svgStyles.get(groups.getStyleFor((Node) e));
336                                transform = String.format(Locale.ROOT, "translate(%f,%f)",
337                                                viewBox.convertX((Node) e), viewBox.convertY((Node) e));
338                        } else if (e instanceof Graph) {
339                                id = "graph-background";
340                                style = svgStyles.get(groups.getStyleFor((Graph) e));
341                        }
342
343                        out.open("g");
344                        out.attribute("id", id);
345                        out.open("path");
346
347                        if (style != null)
348                                out.attribute("style", style.getElementStyle(e));
349
350                        if (transform != null)
351                                out.attribute("transform", transform);
352
353                        out.attribute("d", getPath(e, style));
354                        out.close();
355
356                        if (e.hasLabel("label"))
357                                writeElementText(out, (String) e.getAttribute("label"), e,
358                                                style.group);
359
360                        out.close();
361                }
362
363                public void writeElementText(XMLWriter out, String text, Element e,
364                                StyleGroup style) throws XMLStreamException {
365                        if (style == null
366                                        || style.getTextVisibilityMode() != StyleConstants.TextVisibilityMode.HIDDEN) {
367                                double x, y;
368
369                                x = 0;
370                                y = 0;
371
372                                if (e instanceof Node) {
373                                        x = viewBox.convertX((Node) e);
374                                        y = viewBox.convertY((Node) e);
375                                } else if (e instanceof Edge) {
376                                        Node n0, n1;
377
378                                        n0 = ((Edge) e).getNode0();
379                                        n1 = ((Edge) e).getNode0();
380
381                                        x = viewBox.convertX((getX(n0) + getX(n1)) / 2);
382                                        y = viewBox.convertY((getY(n0) + getY(n1)) / 2);
383                                }
384
385                                out.open("g");
386                                out.open("text");
387                                out.attribute("x", d(x));
388                                out.attribute("y", d(y));
389
390                                if (style != null) {
391                                        if (style.getTextColorCount() > 0)
392                                                out.attribute("fill", toHexColor(style.getTextColor(0)));
393
394                                        switch (style.getTextAlignment()) {
395                                        case CENTER:
396                                                out.attribute("text-anchor", "middle");
397                                                out.attribute("alignment-baseline", "central");
398                                                break;
399                                        case LEFT:
400                                                out.attribute("text-anchor", "start");
401                                                break;
402                                        case RIGHT:
403                                                out.attribute("text-anchor", "end");
404                                                break;
405                                        default:
406                                                break;
407                                        }
408
409                                        switch (style.getTextSize().units) {
410                                        case PX:
411                                        case GU:
412                                                out.attribute("font-size", d(style.getTextSize().value));
413                                                break;
414                                        case PERCENTS:
415                                                out.attribute("font-size", d(style.getTextSize().value)
416                                                                + "%");
417                                                break;
418                                        }
419
420                                        if (style.getTextFont() != null)
421                                                out.attribute("font-family", style.getTextFont());
422
423                                        switch (style.getTextStyle()) {
424                                        case NORMAL:
425                                                break;
426                                        case ITALIC:
427                                                out.attribute("font-style", "italic");
428                                                break;
429                                        case BOLD:
430                                                out.attribute("font-weight", "bold");
431                                                break;
432                                        case BOLD_ITALIC:
433                                                out.attribute("font-weight", "bold");
434                                                out.attribute("font-style", "italic");
435                                                break;
436                                        }
437                                }
438
439                                out.characters(text);
440                                out.close();
441                                out.close();
442                        }
443                }
444
445                public String getPath(Element e, SVGStyle style) {
446                        StringBuilder buffer = new StringBuilder();
447
448                        if (e instanceof Node) {
449                                double sx, sy;
450                                Values size = style.group.getSize();
451
452                                sx = getValue(size.get(0), size.units, true);
453
454                                if (size.getValueCount() > 1)
455                                        sy = getValue(size.get(1), size.units, false);
456                                else
457                                        sy = getValue(size.get(0), size.units, false);
458
459                                switch (style.group.getShape()) {
460                                case ROUNDED_BOX:
461                                        double rx,
462                                        ry;
463
464                                        rx = Math.min(5, sx / 2);
465                                        ry = Math.min(5, sy / 2);
466
467                                        concat(buffer, " m ", d(-sx / 2 + rx), " ", d(-sy / 2));
468                                        concat(buffer, " h ", d(sx - 2 * rx));
469                                        concat(buffer, " a ", d(rx), ",", d(ry), " 0 0 1 ", d(rx),
470                                                        ",", d(ry));
471                                        concat(buffer, " v ", d(sy - 2 * ry));
472                                        concat(buffer, " a ", d(rx), ",", d(ry), " 0 0 1 -", d(rx),
473                                                        ",", d(ry));
474                                        concat(buffer, " h ", d(-sx + 2 * rx));
475                                        concat(buffer, " a ", d(rx), ",", d(ry), " 0 0 1 -", d(rx),
476                                                        ",-", d(ry));
477                                        concat(buffer, " v ", d(-sy + 2 * ry));
478                                        concat(buffer, " a ", d(rx), ",", d(ry), " 0 0 1 ", d(rx),
479                                                        "-", d(ry));
480                                        concat(buffer, " z");
481                                        break;
482                                case BOX:
483                                        concat(buffer, " m ", d(-sx / 2), " ", d(-sy / 2));
484                                        concat(buffer, " h ", d(sx));
485                                        concat(buffer, " v ", d(sy));
486                                        concat(buffer, " h ", d(-sx));
487                                        concat(buffer, " z");
488                                        break;
489                                case DIAMOND:
490                                        concat(buffer, " m ", d(-sx / 2), " 0");
491                                        concat(buffer, " l ", d(sx / 2), " ", d(-sy / 2));
492                                        concat(buffer, " l ", d(sx / 2), " ", d(sy / 2));
493                                        concat(buffer, " l ", d(-sx / 2), " ", d(sy / 2));
494                                        concat(buffer, " z");
495                                        break;
496                                case TRIANGLE:
497                                        concat(buffer, " m ", d(0), " ", d(-sy / 2));
498                                        concat(buffer, " l ", d(sx / 2), " ", d(sy));
499                                        concat(buffer, " h ", d(-sx));
500                                        concat(buffer, " z");
501                                        break;
502                                default:
503                                case CIRCLE:
504                                        concat(buffer, " m ", d(-sx / 2), " 0");
505                                        concat(buffer, " a ", d(sx / 2), ",", d(sy / 2), " 0 1 0 ",
506                                                        d(sx), ",0");
507                                        concat(buffer, " ", d(sx / 2), ",", d(sy / 2), " 0 1 0 -",
508                                                        d(sx), ",0");
509                                        concat(buffer, " z");
510                                        break;
511                                }
512                        } else if (e instanceof Graph) {
513                                concat(buffer, " M ", d(viewBox.x1), " ", d(viewBox.y1));
514                                concat(buffer, " L ", d(viewBox.x2), " ", d(viewBox.y1));
515                                concat(buffer, " L ", d(viewBox.x2), " ", d(viewBox.y2));
516                                concat(buffer, " L ", d(viewBox.x1), " ", d(viewBox.y2));
517                                concat(buffer, " Z");
518                        } else if (e instanceof Edge) {
519                                Node src, trg;
520
521                                double x1, y1;
522                                double x2, y2;
523
524                                src = ((Edge) e).getSourceNode();
525                                trg = ((Edge) e).getTargetNode();
526
527                                x1 = viewBox.convertX(src);
528                                y1 = viewBox.convertY(src);
529                                x2 = viewBox.convertX(trg);
530                                y2 = viewBox.convertY(trg);
531
532                                concat(buffer, " M ", d(x1), " ", d(y1));
533                                concat(buffer, " L ", d(x2), " ", d(y2));
534                        }
535
536                        return buffer.toString();
537                }
538
539                public double getValue(Value v, boolean horizontal) {
540                        return getValue(v.value, v.units, horizontal);
541                }
542
543                public double getValue(double d, StyleConstants.Units units,
544                                boolean horizontal) {
545                        switch (units) {
546                        case PX:
547                                // TODO
548                                return d;
549                        case GU:
550                                // TODO
551                                return d;
552                        case PERCENTS:
553                                if (horizontal)
554                                        return (viewBox.x2 - viewBox.x1) * d / 100.0;
555                                else
556                                        return (viewBox.y2 - viewBox.y1) * d / 100.0;
557                        }
558
559                        return d;
560                }
561        }
562
563        static class ViewBox {
564                double x1, y1, x2, y2;
565                double x3, y3, x4, y4;
566
567                double[] padding = { 0, 0 };
568
569                ViewBox(double x1, double y1, double x2, double y2) {
570                        this.x1 = x1;
571                        this.y1 = y1;
572                        this.x2 = x2;
573                        this.y2 = y2;
574                }
575
576                void compute(Graph g, StyleGroup style) {
577                        x3 = y3 = Double.MAX_VALUE;
578                        x4 = y4 = Double.MIN_VALUE;
579
580                        for (Node n : g.getEachNode()) {
581                                x3 = Math.min(x3, getX(n));
582                                y3 = Math.min(y3, getY(n));
583
584                                x4 = Math.max(x4, getX(n));
585                                y4 = Math.max(y4, getY(n));
586                        }
587
588                        Values v = style.getPadding();
589
590                        if (v.getValueCount() > 0) {
591                                padding[0] = v.get(0);
592                                padding[1] = v.getValueCount() > 1 ? v.get(1) : v.get(0);
593                        }
594                }
595
596                double convertX(double x) {
597                        return (x2 - x1 - 2 * padding[0]) * (x - x3) / (x4 - x3) + x1
598                                        + padding[0];
599                }
600
601                double convertX(Node n) {
602                        return convertX(getX(n));
603                }
604
605                double convertY(double y) {
606                        return (y2 - y1 - 2 * padding[1]) * (y - y3) / (y4 - y3) + y1
607                                        + padding[1];
608                }
609
610                double convertY(Node n) {
611                        return convertY(getY(n));
612                }
613        }
614
615        static class SVGStyle {
616
617                static int gradientId = 0;
618
619                String style;
620                StyleGroup group;
621                boolean gradient;
622                boolean dynfill;
623
624                public SVGStyle(StyleGroup group) throws XMLStreamException {
625
626                        this.group = group;
627                        this.gradient = false;
628                        this.dynfill = false;
629
630                        switch (group.getType()) {
631                        case EDGE:
632                                buildEdgeStyle();
633                                break;
634                        case NODE:
635                                buildNodeStyle();
636                                break;
637                        case GRAPH:
638                                buildGraphStyle();
639                                break;
640                        case SPRITE:
641                        default:
642                                break;
643                        }
644                }
645
646                void buildNodeStyle() {
647                        StringBuilder styleSB = new StringBuilder();
648
649                        switch (group.getFillMode()) {
650                        case GRADIENT_RADIAL:
651                        case GRADIENT_HORIZONTAL:
652                        case GRADIENT_VERTICAL:
653                        case GRADIENT_DIAGONAL1:
654                        case GRADIENT_DIAGONAL2:
655                                concat(styleSB, "fill:url(#%gradient-id%);");
656                                this.gradient = true;
657                                break;
658                        case PLAIN:
659                                concat(styleSB, "fill:", toHexColor(group.getFillColor(0)), ";");
660                                concat(styleSB, "fill-opacity:", d(group.getFillColor(0)
661                                                .getAlpha() / 255.0), ";");
662                                break;
663                        case DYN_PLAIN:
664                                dynfill = true;
665                                concat(styleSB, "fill:%fill-color%;");
666                                concat(styleSB, "fill-opacity:%fill-opacity%;");
667                                break;
668                        case IMAGE_TILED:
669                        case IMAGE_SCALED:
670                        case IMAGE_SCALED_RATIO_MAX:
671                        case IMAGE_SCALED_RATIO_MIN:
672                        case NONE:
673                                break;
674                        }
675
676                        concat(styleSB, "fill-rule:nonzero;");
677
678                        if (group.getStrokeMode() != StrokeMode.NONE) {
679                                concat(styleSB, "stroke:", toHexColor(group.getStrokeColor(0)),
680                                                ";");
681                                concat(styleSB, "stroke-width:",
682                                                getSize(group.getStrokeWidth()), ";");
683                        }
684
685                        style = styleSB.toString();
686                }
687
688                void buildGraphStyle() {
689                        buildNodeStyle();
690                }
691
692                void buildEdgeStyle() {
693                        StringBuilder styleSB = new StringBuilder();
694
695                        switch (group.getFillMode()) {
696                        case GRADIENT_RADIAL:
697                        case GRADIENT_HORIZONTAL:
698                        case GRADIENT_VERTICAL:
699                        case GRADIENT_DIAGONAL1:
700                        case GRADIENT_DIAGONAL2:
701                                concat(styleSB, "stroke:url(#%gradient-id%);");
702                                this.gradient = true;
703                                break;
704                        case PLAIN:
705                        case DYN_PLAIN:
706                                concat(styleSB, "stroke:", toHexColor(group.getFillColor(0)),
707                                                ";");
708                                break;
709                        case IMAGE_TILED:
710                        case IMAGE_SCALED:
711                        case IMAGE_SCALED_RATIO_MAX:
712                        case IMAGE_SCALED_RATIO_MIN:
713                        case NONE:
714                                break;
715                        }
716
717                        concat(styleSB, "stroke-width:", getSize(group.getSize(), 0), ";");
718
719                        style = styleSB.toString();
720                }
721
722                public void writeDef(XMLWriter out) throws XMLStreamException {
723                        if (gradient) {
724                                String gid = String.format("gradient%x", gradientId++);
725                                String type = "linearGradient";
726                                String x1 = null, x2 = null, y1 = null, y2 = null;
727
728                                switch (group.getFillMode()) {
729                                case GRADIENT_RADIAL:
730                                        type = "radialGradient";
731                                        break;
732                                case GRADIENT_HORIZONTAL:
733                                        x1 = "0%";
734                                        y1 = "50%";
735                                        x2 = "100%";
736                                        y2 = "50%";
737                                        break;
738                                case GRADIENT_VERTICAL:
739                                        x1 = "50%";
740                                        y1 = "0%";
741                                        x2 = "50%";
742                                        y2 = "100%";
743                                        break;
744                                case GRADIENT_DIAGONAL1:
745                                        x1 = "0%";
746                                        y1 = "0%";
747                                        x2 = "100%";
748                                        y2 = "100%";
749                                        break;
750                                case GRADIENT_DIAGONAL2:
751                                        x1 = "100%";
752                                        y1 = "100%";
753                                        x2 = "0%";
754                                        y2 = "0%";
755                                        break;
756                                default:
757                                        break;
758                                }
759
760                                out.open(type);
761                                out.attribute("id", gid);
762                                out.attribute("gradientUnits", "objectBoundingBox");
763
764                                if (type.equals("linearGradient")) {
765                                        out.attribute("x1", x1);
766                                        out.attribute("y1", y1);
767                                        out.attribute("x2", x2);
768                                        out.attribute("y2", y2);
769                                }
770
771                                for (int i = 0; i < group.getFillColorCount(); i++) {
772                                        out.open("stop");
773                                        out.attribute("stop-color",
774                                                        toHexColor(group.getFillColor(i)));
775                                        out.attribute("stop-opacity", d(group.getFillColor(i)
776                                                        .getAlpha() / 255.0));
777                                        out.attribute(
778                                                        "offset",
779                                                        Double.toString(i
780                                                                        / (double) (group.getFillColorCount() - 1)));
781                                        out.close();
782                                }
783
784                                out.close();
785
786                                style = style.replace("%gradient-id%", gid);
787                        }
788                }
789
790                public String getElementStyle(Element e) {
791                        String st = style;
792
793                        if (dynfill) {
794                                if (group.getFillColorCount() > 1) {
795                                        String color, opacity;
796                                        double d = e.hasNumber("ui.color") ? e
797                                                        .getNumber("ui.color") : 0;
798
799                                        double a, b;
800                                        Colors colors = group.getFillColors();
801                                        int s = Math.min((int) (d * group.getFillColorCount()),
802                                                        colors.size() - 2);
803
804                                        a = s / (double) (colors.size() - 1);
805                                        b = (s + 1) / (double) (colors.size() - 1);
806
807                                        d = (d - a) / (b - a);
808
809                                        Color c1 = colors.get(s), c2 = colors.get(s + 1);
810
811                                        color = String.format(
812                                                        "#%02x%02x%02x",
813                                                        (int) (c1.getRed() + d
814                                                                        * (c2.getRed() - c1.getRed())),
815                                                        (int) (c1.getGreen() + d
816                                                                        * (c2.getGreen() - c1.getGreen())),
817                                                        (int) (c1.getBlue() + d
818                                                                        * (c2.getBlue() - c1.getBlue())));
819
820                                        opacity = Double.toString((c1.getAlpha() + d
821                                                        * (c2.getAlpha() - c1.getAlpha())) / 255.0);
822
823                                        st = st.replace("%fill-color%", color);
824                                        st = st.replace("%fill-opacity%", opacity);
825                                }
826                        }
827
828                        return st;
829                }
830        }
831
832        static class XMLWriter {
833                XMLStreamWriter out;
834                int depth;
835                boolean closed;
836
837                void start(Writer w) throws XMLStreamException,
838                                FactoryConfigurationError, IOException {
839                        if (out != null)
840                                end();
841
842                        out = XMLOutputFactory.newInstance().createXMLStreamWriter(w);
843                        out.writeStartDocument();
844                }
845
846                void end() throws XMLStreamException {
847                        out.writeEndDocument();
848                        out.flush();
849                        out.close();
850                        out = null;
851                }
852
853                void open(String name) throws XMLStreamException {
854                        out.writeCharacters("\n");
855                        for (int i = 0; i < depth; i++)
856                                out.writeCharacters("  ");
857
858                        out.writeStartElement(name);
859                        depth++;
860                }
861
862                void close() throws XMLStreamException {
863                        out.writeEndElement();
864                        depth--;
865                }
866
867                void attribute(String key, String value) throws XMLStreamException {
868                        out.writeAttribute(key, value);
869                }
870
871                void characters(String data) throws XMLStreamException {
872                        out.writeCharacters(data);
873                }
874        }
875
876        private static void concat(StringBuilder buffer, Object... args) {
877                if (args != null) {
878                        for (int i = 0; i < args.length; i++)
879                                buffer.append(args[i].toString());
880                }
881        }
882
883        private static String toHexColor(Color c) {
884                return String.format("#%02x%02x%02x", c.getRed(), c.getGreen(),
885                                c.getBlue());
886        }
887
888        /*
889         * (non-Javadoc)
890         * 
891         * @see
892         * org.graphstream.stream.AttributeSink#edgeAttributeAdded(java.lang.String,
893         * long, java.lang.String, java.lang.String, java.lang.Object)
894         */
895        public void edgeAttributeAdded(String sourceId, long timeId, String edgeId,
896                        String attribute, Object value) {
897        }
898
899        /*
900         * (non-Javadoc)
901         * 
902         * @see
903         * org.graphstream.stream.AttributeSink#edgeAttributeChanged(java.lang.String
904         * , long, java.lang.String, java.lang.String, java.lang.Object,
905         * java.lang.Object)
906         */
907        public void edgeAttributeChanged(String sourceId, long timeId,
908                        String edgeId, String attribute, Object oldValue, Object newValue) {
909        }
910
911        /*
912         * (non-Javadoc)
913         * 
914         * @see
915         * org.graphstream.stream.AttributeSink#edgeAttributeRemoved(java.lang.String
916         * , long, java.lang.String, java.lang.String)
917         */
918        public void edgeAttributeRemoved(String sourceId, long timeId,
919                        String edgeId, String attribute) {
920        }
921
922        /*
923         * (non-Javadoc)
924         * 
925         * @see
926         * org.graphstream.stream.AttributeSink#graphAttributeAdded(java.lang.String
927         * , long, java.lang.String, java.lang.Object)
928         */
929        public void graphAttributeAdded(String sourceId, long timeId,
930                        String attribute, Object value) {
931        }
932
933        /*
934         * (non-Javadoc)
935         * 
936         * @see
937         * org.graphstream.stream.AttributeSink#graphAttributeChanged(java.lang.
938         * String, long, java.lang.String, java.lang.Object, java.lang.Object)
939         */
940        public void graphAttributeChanged(String sourceId, long timeId,
941                        String attribute, Object oldValue, Object newValue) {
942        }
943
944        /*
945         * (non-Javadoc)
946         * 
947         * @see
948         * org.graphstream.stream.AttributeSink#graphAttributeRemoved(java.lang.
949         * String, long, java.lang.String)
950         */
951        public void graphAttributeRemoved(String sourceId, long timeId,
952                        String attribute) {
953        }
954
955        /*
956         * (non-Javadoc)
957         * 
958         * @see
959         * org.graphstream.stream.AttributeSink#nodeAttributeAdded(java.lang.String,
960         * long, java.lang.String, java.lang.String, java.lang.Object)
961         */
962        public void nodeAttributeAdded(String sourceId, long timeId, String nodeId,
963                        String attribute, Object value) {
964        }
965
966        /*
967         * (non-Javadoc)
968         * 
969         * @see
970         * org.graphstream.stream.AttributeSink#nodeAttributeChanged(java.lang.String
971         * , long, java.lang.String, java.lang.String, java.lang.Object,
972         * java.lang.Object)
973         */
974        public void nodeAttributeChanged(String sourceId, long timeId,
975                        String nodeId, String attribute, Object oldValue, Object newValue) {
976        }
977
978        /*
979         * (non-Javadoc)
980         * 
981         * @see
982         * org.graphstream.stream.AttributeSink#nodeAttributeRemoved(java.lang.String
983         * , long, java.lang.String, java.lang.String)
984         */
985        public void nodeAttributeRemoved(String sourceId, long timeId,
986                        String nodeId, String attribute) {
987        }
988
989        /*
990         * (non-Javadoc)
991         * 
992         * @see org.graphstream.stream.ElementSink#edgeAdded(java.lang.String, long,
993         * java.lang.String, java.lang.String, java.lang.String, boolean)
994         */
995        public void edgeAdded(String sourceId, long timeId, String edgeId,
996                        String fromNodeId, String toNodeId, boolean directed) {
997        }
998
999        /*
1000         * (non-Javadoc)
1001         * 
1002         * @see org.graphstream.stream.ElementSink#edgeRemoved(java.lang.String,
1003         * long, java.lang.String)
1004         */
1005        public void edgeRemoved(String sourceId, long timeId, String edgeId) {
1006        }
1007
1008        /*
1009         * (non-Javadoc)
1010         * 
1011         * @see org.graphstream.stream.ElementSink#graphCleared(java.lang.String,
1012         * long)
1013         */
1014        public void graphCleared(String sourceId, long timeId) {
1015        }
1016
1017        /*
1018         * (non-Javadoc)
1019         * 
1020         * @see org.graphstream.stream.ElementSink#nodeAdded(java.lang.String, long,
1021         * java.lang.String)
1022         */
1023        public void nodeAdded(String sourceId, long timeId, String nodeId) {
1024        }
1025
1026        /*
1027         * (non-Javadoc)
1028         * 
1029         * @see org.graphstream.stream.ElementSink#nodeRemoved(java.lang.String,
1030         * long, java.lang.String)
1031         */
1032        public void nodeRemoved(String sourceId, long timeId, String nodeId) {
1033        }
1034
1035        /*
1036         * (non-Javadoc)
1037         * 
1038         * @see org.graphstream.stream.ElementSink#stepBegins(java.lang.String,
1039         * long, double)
1040         */
1041        public void stepBegins(String sourceId, long timeId, double step) {
1042        }
1043}