001package sec.web.render;
002
003import java.util.ArrayList;
004
005import armyc2.c2sd.graphics2d.BasicStroke;
006import armyc2.c2sd.graphics2d.Point2D;
007import armyc2.c2sd.graphics2d.Rectangle;
008import armyc2.c2sd.graphics2d.Rectangle2D;
009import armyc2.c2sd.renderer.utilities.Color;
010import armyc2.c2sd.renderer.utilities.ErrorLogger;
011import armyc2.c2sd.renderer.utilities.IPointConversion;
012import armyc2.c2sd.renderer.utilities.RendererSettings;
013import armyc2.c2sd.renderer.utilities.RendererUtilities;
014import armyc2.c2sd.renderer.utilities.ShapeInfo;
015import armyc2.c2sd.renderer.utilities.SymbolUtilities;
016import sec.web.render.utilities.Path;
017import sec.web.render.utilities.SVGTextInfo;
018
019public class MultiPointHandlerSVG {
020    public static String GeoSVGize(String id, String name, String description, String symbolID, ArrayList<ShapeInfo> shapes, ArrayList<ShapeInfo> modifiers, IPointConversion ipc, boolean normalize, String textColor, String textBackgroundColor, boolean wasClipped) {
021        return GeoSVGize(id, name, description, symbolID, shapes, modifiers, ipc, normalize, textColor, textBackgroundColor, wasClipped, null);
022    }
023
024    /**
025     * Generates an SVG which can be draped on a map.
026     * Better with RenderSymbol2D
027     *
028     * @param id
029     * @param name
030     * @param description
031     * @param symbolID
032     * @param shapes              {@link ShapeInfo[]}
033     * @param modifiers           {@link ShapeInfo[]}
034     * @param ipc                 {@link IPointConversion}
035     * @param normalize
036     * @param textColor
037     * @param textBackgroundColor
038     * @param wasClipped
039     * @return
040     */
041    public static String GeoSVGize(String id, String name, String description, String symbolID, ArrayList<ShapeInfo> shapes, ArrayList<ShapeInfo> modifiers, IPointConversion ipc, boolean normalize, String textColor, String textBackgroundColor, boolean wasClipped, Rectangle bbox) {
042
043        int height = 10;
044
045        Rectangle tempBounds = null;
046        ArrayList<String> paths = new ArrayList<>();
047        Rectangle pathBounds = null;
048        ArrayList<SVGTextInfo> labels = new ArrayList<>();
049        Rectangle labelBounds = null;
050        Rectangle unionBounds = null;
051        float lineWidth;
052        String fillTexture = null;
053        Point2D geoCoordTL = null;
054        Point2D geoCoordTR = null;
055        Point2D geoCoordBL = null;
056        Point2D geoCoordBR = null;
057        Point2D west = null;
058        Point2D north = null;
059        Point2D south = null;
060        Point2D east = null;
061        int len = shapes.size();
062
063        try {
064            height = RendererSettings.getInstance().getMPModifierFontSize();
065
066            for (int i = 0; i < len; i++) {
067                tempBounds = new Rectangle();
068                String svg = MultiPointHandlerSVG.ShapesToGeoSVG(symbolID, shapes.get(i), tempBounds, ipc, normalize);
069                if (svg != null) {
070                    BasicStroke bsTemp = (BasicStroke)shapes.get(i).getStroke();
071                    lineWidth = bsTemp.getLineWidth();
072                    tempBounds.grow(Math.round(lineWidth), Math.round(lineWidth));//adjust for line width so nothing gets clipped.
073                    if (pathBounds == null)
074                        pathBounds = new Rectangle(tempBounds);
075                    else
076                        pathBounds = pathBounds.union(tempBounds);
077                    paths.add(svg);
078
079                    if (shapes.get(i).getPatternFillImage() != null && fillTexture == null)
080                        fillTexture = getFillPattern(shapes.get(i));
081                }
082            }
083
084            ShapeInfo tempModifier;
085            int len2 = modifiers.size();
086            SVGTextInfo tiTemp = null;
087            for (int j = 0; j < len2; j++) {
088                tempModifier = modifiers.get(j);
089
090                if (tempModifier.getModifierString() != null && !tempModifier.getModifierString().isEmpty()) {
091                    Point2D tempLocation = tempModifier.getModifierStringPosition();
092
093                    int justify = tempModifier.getTextJustify();
094                    String strJustify = "start";
095                    if (justify == ShapeInfo.justify_left)
096                        strJustify = "start";
097                    else if (justify == ShapeInfo.justify_center)
098                        strJustify = "middle";
099                    else if (justify == ShapeInfo.justify_right)
100                        strJustify = "end";
101
102                    double degrees = tempModifier.getModifierStringAngle();
103                    tiTemp = new SVGTextInfo(tempModifier.getModifierString(), tempLocation, strJustify, degrees);
104
105                    Rectangle bounds = tiTemp.getTextBounds().getBounds();
106
107                    //make sure labels are in the bbox, otherwise they can
108                    //make the canvas grow out of control.
109                    //if (tiTemp && bbox.containsRectangle(bounds))
110                    //if(bbox !== null)
111                    if (tiTemp != null) {
112                        if ((bbox != null && bbox.intersects(bounds)) || bbox == null) {
113                            labels.add(tiTemp);
114                            if (bounds != null) {
115                                if (labelBounds != null)
116                                    labelBounds = labelBounds.union(bounds);
117                                else
118                                    labelBounds = bounds;
119                            }
120                        }
121                    }
122                }
123                /*else if (tempModifier.getModifierImage() != null) {
124                    Bitmap imgModifier = tempModifier.getModifierImage();
125                    Rectangle bounds = new Rectangle(0, 0, imgModifier.getWidth(), imgModifier.getHeight());
126
127                    Point2D tempLocation = tempModifier.getModifierPosition();
128                    tempLocation.setLocation(tempLocation.getX() - bounds.getWidth() / 2, tempLocation.getY() - bounds.getHeight() / 2);
129                    int x = (int) tempLocation.getX();
130                    int y = (int) tempLocation.getY();
131                    bounds.setLocation(x, y);
132
133                    double angle = tempModifier.getModifierAngle();
134                    paths.add("<image transform=\"translate(" + x + ',' + y + ") rotate(" + angle + ")\" href=\"" + MultiPointHandler.bitmapToString(tempModifier.getModifierImage()) + "\" />");
135                    if (angle != 0) {
136                        bounds = SVGTextInfo.getRotatedRectangleBounds(bounds, tempLocation, -angle, "middle");
137                    }
138                    if (bounds != null) {
139                        if ((bbox != null && bbox.intersects(bounds)) || bbox == null) {
140                            if (pathBounds != null)
141                                pathBounds = pathBounds.union(bounds);
142                            else
143                                pathBounds = bounds;
144                        }
145                    }
146                }//*/
147            }
148            if (pathBounds != null) {
149                unionBounds = new Rectangle(pathBounds);
150            }
151            if (labelBounds != null) {
152                if (unionBounds != null) {
153                    unionBounds = unionBounds.union(labelBounds);
154                } else {
155                    unionBounds = labelBounds;
156                }
157            }
158
159            //get geo bounds for canvas
160
161            if (unionBounds != null) {
162                Point2D coordTL = new Point2D.Double();
163                coordTL.setLocation(unionBounds.getX(), unionBounds.getY());
164                Point2D coordBR = new Point2D.Double();
165                coordBR.setLocation(unionBounds.getX() + unionBounds.getWidth(), unionBounds.getY() + unionBounds.getHeight());
166
167                Point2D coordTR = new Point2D.Double();
168                coordTR.setLocation(unionBounds.getX() + unionBounds.getWidth(), unionBounds.getY());
169                Point2D coordBL = new Point2D.Double();
170                coordBL.setLocation(unionBounds.getX(), unionBounds.getY() + unionBounds.getHeight());
171
172                south = new Point2D.Double(unionBounds.getX() + unionBounds.getWidth() / 2, unionBounds.getY() + unionBounds.getHeight());
173                north = new Point2D.Double(unionBounds.getX() + unionBounds.getWidth() / 2, unionBounds.getY());
174                east = new Point2D.Double(unionBounds.getX() + unionBounds.getWidth(), unionBounds.getY() + unionBounds.getHeight() / 2);
175                west = new Point2D.Double(unionBounds.getX(), unionBounds.getY() + unionBounds.getHeight() / 2);
176
177
178                geoCoordTL = ipc.PixelsToGeo(coordTL);
179                geoCoordBR = ipc.PixelsToGeo(coordBR);
180                geoCoordTR = ipc.PixelsToGeo(coordTR);
181                geoCoordBL = ipc.PixelsToGeo(coordBL);
182
183                north = ipc.PixelsToGeo(north);
184                south = ipc.PixelsToGeo(south);
185                east = ipc.PixelsToGeo(east);
186                west = ipc.PixelsToGeo(west);
187
188
189                if (normalize) {
190                    geoCoordTL = MultiPointHandler.NormalizeCoordToGECoord(geoCoordTL);
191                    geoCoordBR = MultiPointHandler.NormalizeCoordToGECoord(geoCoordBR);
192                    geoCoordTR = MultiPointHandler.NormalizeCoordToGECoord(geoCoordTR);
193                    geoCoordBL = MultiPointHandler.NormalizeCoordToGECoord(geoCoordBL);
194
195                    north = MultiPointHandler.NormalizeCoordToGECoord(north);
196                    south = MultiPointHandler.NormalizeCoordToGECoord(south);
197                    east = MultiPointHandler.NormalizeCoordToGECoord(east);
198                    west = MultiPointHandler.NormalizeCoordToGECoord(west);
199                }
200            } else//nothing to draw
201            {
202                geoCoordTL = new Point2D.Double(0, 0);
203                geoCoordBR = new Point2D.Double(0, 0);
204                geoCoordTR = new Point2D.Double(0, 0);
205                geoCoordBL = new Point2D.Double(0, 0);
206
207                north = new Point2D.Double(0, 0);
208                south = new Point2D.Double(0, 0);
209                east = new Point2D.Double(0, 0);
210                west = new Point2D.Double(0, 0);
211            }
212        } catch (Exception err) {
213            ErrorLogger.LogException("MultiPointHandlerSVG", "GeoSVGize", err);
214        }
215
216        if (paths != null && len > 0 && unionBounds != null) {
217            //create group with offset translation
218            //ctx.translate(bounds.getX() * -1, bounds.getY() * -1);
219            String group = "<g transform=\"translate(" + (unionBounds.getX() * -1) + ',' + (unionBounds.getY() * -1) + ")\">";
220
221            //loop through paths and labels and build SVG.
222            for (int i = 0; i < paths.size(); i++) {
223                group += paths.get(i);
224            }
225
226            ArrayList<String> labelStrs = renderTextElement(labels, textColor, textBackgroundColor);
227            for (int j = 0; j < labelStrs.size(); j++) {
228                group += labelStrs.get(j);
229            }
230            //close
231            group += "</g>";
232
233            //wrap in SVG
234            String geoSVG = "<svg width=\"" + Math.ceil(unionBounds.getWidth()) + "px\" height=\"" + Math.ceil(unionBounds.getHeight()) + "px\" preserveAspectRatio=\"none\" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">";
235
236            geoSVG += ("<metadata>\n");
237            geoSVG += ("<id>") + id + ("</id>\n");
238            geoSVG += ("<name>") + name + ("</name>\n");
239            geoSVG += ("<description>") + description + ("</description>\n");
240            geoSVG += ("<symbolID>") + symbolID + ("</symbolID>\n");
241            geoSVG += ("<geoTL>") + geoCoordTL.getX() + " " + geoCoordTL.getY() + ("</geoTL>\n");
242            geoSVG += ("<geoBR>") + geoCoordBR.getX() + " " + geoCoordBR.getY() + ("</geoBR>\n");
243            geoSVG += ("<geoTR>") + geoCoordTR.getX() + " " + geoCoordTR.getY() + ("</geoTR>\n");
244            geoSVG += ("<geoBL>") + geoCoordBL.getX() + " " + geoCoordBL.getY() + ("</geoBL>\n");
245            geoSVG += ("<north>") + north.getY() + ("</north>\n");
246            geoSVG += ("<south>") + south.getY() + ("</south>\n");
247            geoSVG += ("<east>") + east.getX() + ("</east>\n");
248            geoSVG += ("<west>") + west.getX() + ("</west>\n");
249            geoSVG += ("<wasClipped>") + wasClipped + ("</wasClipped>\n");
250            geoSVG += ("<width>") + unionBounds.getWidth() + ("</width>\n");
251            geoSVG += ("<height>") + unionBounds.getHeight() + ("</height>\n");
252            geoSVG += ("</metadata>\n");
253
254
255            /*//Scale the image, commented out as I decided to alter scale in getReasonableScale rather than adjust after the fact.
256            var tempWidth = Math.ceil(unionBounds.getWidth());
257            var tempHeight = Math.ceil(unionBounds.getHeight());
258            var quality = 1.0;
259            var bigger = Math.max(tempWidth, tempHeight);
260            var max = 1000;
261            if(!converter)
262            {
263                if(bigger < max)
264                {
265                    if(bigger * 2 < max)
266                    {
267                        quality = 2;
268                    }
269                    else
270                    {
271                        quality = max / bigger;
272                    }
273                }
274                else
275                {
276                    quality = 1;
277                }
278            }
279            var geoSVG = '<svg viewBox="0 0 ' + tempWidth + ' ' + tempHeight + '"' + ' width="' + (tempWidth * quality) + 'px" height="' + (tempHeight * quality) + 'px" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg" version="1.1">';//*/
280            if (fillTexture != null)
281                geoSVG += fillTexture;
282            geoSVG += group;
283            geoSVG += "</svg>";//*/
284
285            return geoSVG;
286
287        } else {
288            //return blank 2x2 SVG
289            return "<svg width=\"2px\" height=\"2px\" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\"></svg>";
290        }
291    }
292
293    /**
294     * @param tiArray      {@link SVGTextInfo[]}
295     * @param color        a hex string "#000000"
296     * @param outlineColor a hex string "#000000"
297     */
298    static ArrayList<String> renderTextElement(ArrayList<SVGTextInfo> tiArray, String color, String outlineColor) {
299        //ctx.lineCap = "butt";
300        //ctx.lineJoin = "miter";
301        //ctx.miterLimit = 3;
302        /*ctx.lineCap = "round";
303        ctx.lineJoin = "round";
304        ctx.miterLimit = 3;*/
305        ArrayList<String> svgElements = new ArrayList<>();
306
307        int size = tiArray.size();
308        SVGTextInfo tempShape = null;
309        String textColor = "#000000";
310        int tbm = RendererSettings.getInstance().getTextBackgroundMethod();
311        int outlineWidth = RendererSettings.getInstance().getTextOutlineWidth();
312
313        if (color != null) {
314            textColor = color;
315        }
316
317
318        if (outlineColor == null || outlineColor.isEmpty()) {
319            outlineColor = RendererUtilities.colorToHexString(RendererUtilities.getIdealOutlineColor(RendererUtilities.getColorFromHexString(textColor)), false);
320        }
321
322
323        if (tbm == RendererSettings.TextBackgroundMethod_OUTLINE
324                || tbm == RendererSettings.TextBackgroundMethod_OUTLINE_QUICK
325                || tbm == RendererSettings.TextBackgroundMethod_COLORFILL) {
326            for (int i = 0; i < size; i++) {
327                tempShape = tiArray.get(i);
328                svgElements.add(tempShape.toSVGElement(textColor, outlineColor, outlineWidth));
329            }
330        } else //if(tbm == RendererSettings.TextBackgroundMethod_NONE)
331        {
332            for (int j = 0; j < size; j++) {
333                tempShape = tiArray.get(j);
334                svgElements.add(tempShape.toSVGElement(textColor, null, 0));
335            }
336        }
337
338        return svgElements;
339    }
340
341    static String getFillPattern(ShapeInfo shapeInfo) {
342        if (shapeInfo.getPatternFillImage() != null) {
343            int width = shapeInfo.getPatternFillImage().getWidth();
344            int height = shapeInfo.getPatternFillImage().getHeight();
345            return "<defs><pattern id=\"fillPattern\" patternUnits=\"userSpaceOnUse\" width=\"" + width + "\" height=\"" + height + "\"><image href=\"" + MultiPointHandler.bitmapToString(shapeInfo.getPatternFillImage()) + "\" /></pattern></defs>";
346        } else {
347            return null;
348        }
349    }
350
351    /**
352     * @param symbolID
353     * @param shapeInfo  {@link ShapeInfo}
354     * @param pathBounds {@link Rectangle}
355     * @param ipc        {@link IPointConversion}
356     * @param normalize
357     */
358    static String ShapesToGeoSVG(String symbolID, ShapeInfo shapeInfo, Rectangle pathBounds, IPointConversion ipc, boolean normalize) {
359        Path path = null;
360        String fillColor = null;
361        String lineColor = null;
362        int lineWidth = 0;
363        double lineAlpha = 1.0;
364        double fillAlpha = 1.0;
365        float[] dashArray = null;
366        String fillPattern = null;
367
368        if (shapeInfo.getLineColor() != null) {
369            Color lineColorTemp = shapeInfo.getLineColor();
370            lineAlpha = lineColorTemp.getAlpha() / 255.0;
371            lineColor = RendererUtilities.colorToHexString(lineColorTemp, false);
372        }
373        if (shapeInfo.getFillColor() != null) {
374            Color fillColorTemp = shapeInfo.getFillColor();
375            fillAlpha = fillColorTemp.getAlpha() / 255.0;
376            fillColor = RendererUtilities.colorToHexString(fillColorTemp, false);
377        }
378
379        BasicStroke stroke = (BasicStroke)shapeInfo.getStroke();
380        if (stroke != null) {
381            lineWidth = Math.round(stroke.getLineWidth());
382            dashArray = stroke.getDashArray();
383        }
384
385        ArrayList<ArrayList<Point2D>> shapesArray = shapeInfo.getPolylines();
386        path = new Path();
387        if (dashArray != null && dashArray.length > 0)
388            path.setLineDash(dashArray);
389        for (int i = 0; i < shapesArray.size(); i++) {
390            ArrayList<Point2D> shape = shapesArray.get(i);
391
392            for (int j = 0; j < shape.size(); j++) {
393                Point2D coord = shape.get(j);
394                if (j == 0) {
395                    path.moveTo(coord.getX(), coord.getY());
396                } else if (dashArray != null) {
397                    path.dashedLineTo(coord.getX(), coord.getY());
398                } else {
399                    path.lineTo(coord.getX(), coord.getY());
400                }
401            }
402        }
403        if (shapeInfo.getPatternFillImage() != null)
404            fillColor = "url(#fillPattern)";
405        String svgElement = path.toSVGElement(lineColor, lineWidth, fillColor, lineAlpha, fillAlpha);
406        pathBounds.setRect(new Rectangle(path.getBounds()));
407        return svgElement;
408    }
409}