001package armyc2.c5isr.renderer.utilities;
002
003import android.graphics.Canvas;
004import android.graphics.Paint;
005import android.graphics.Paint.Style;
006import android.graphics.Point;
007import android.graphics.RectF;
008import android.util.SparseArray;
009
010import java.util.Map;
011import java.util.TreeSet;
012import java.util.logging.Level;
013import java.util.regex.Matcher;
014import java.util.regex.Pattern;
015
016public class RendererUtilities {
017
018    private static final float OUTLINE_SCALING_FACTOR = 2.5f;
019        private static SparseArray<Color> pastIdealOutlineColors = new SparseArray<Color>();
020        /**
021     * 
022     * @param color {String} color like "#FFFFFF"
023     * @return {String}
024     */
025    public static Color getIdealOutlineColor(Color color){
026        Color idealColor = Color.white;
027        
028        if(color != null && pastIdealOutlineColors.indexOfKey(color.toInt())>=0)
029        {
030            return pastIdealOutlineColors.get(color.toInt());
031        }//*/
032        
033        if(color != null)
034        {
035                
036                int threshold = RendererSettings.getInstance().getTextBackgroundAutoColorThreshold();
037                        
038            int r = color.getRed();
039            int g = color.getGreen();
040            int b = color.getBlue();
041        
042            float delta = ((r * 0.299f) + (g * 0.587f) + (b * 0.114f));
043            
044            if((255 - delta < threshold))
045            {
046                idealColor = Color.black;
047            }
048            else
049            {
050                idealColor = Color.white;
051            }
052        }
053        
054        if(color != null)
055                pastIdealOutlineColors.put(color.toInt(),idealColor);
056        
057        return idealColor;
058    }
059    
060    public static void renderSymbolCharacter(Canvas ctx, String symbol, int x, int y, Paint paint, Color color, int outlineWidth)
061    {
062        int tbm = RendererSettings.getInstance().getTextBackgroundMethod();
063
064        Color outlineColor = RendererUtilities.getIdealOutlineColor(color);
065
066        //if(tbm == RendererSettings.TextBackgroundMethod_OUTLINE_QUICK)
067        //{    
068            //draw symbol outline
069                paint.setStyle(Style.FILL);
070
071                paint.setColor(outlineColor.toInt());
072            if(outlineWidth > 0)
073            {
074                for(int i = 1; i <= outlineWidth; i++)
075                {
076                        if(i % 2 == 1)
077                        {
078                                ctx.drawText(symbol, x - i, y, paint);
079                        ctx.drawText(symbol, x + i, y, paint);
080                        ctx.drawText(symbol, x, y + i, paint);
081                        ctx.drawText(symbol, x, y - i, paint);
082                        }
083                        else
084                        {
085                                ctx.drawText(symbol, x - i, y - i, paint);
086                        ctx.drawText(symbol, x + i, y - i, paint);
087                        ctx.drawText(symbol, x - i, y + i, paint);
088                        ctx.drawText(symbol, x + i, y + i, paint);
089                        }
090                        
091                }
092                
093            }
094            //draw symbol
095            paint.setColor(color.toInt());
096            
097                ctx.drawText(symbol, x, y, paint);
098            
099        /*}
100        else
101        {
102            //draw text outline
103                paint.setStyle(Style.STROKE);
104                paint.setStrokeWidth(RendererSettings.getInstance().getTextOutlineWidth());
105                paint.setColor(outlineColor.toInt());
106            if(outlineWidth > 0)
107            {
108                
109                ctx.drawText(symbol, x, y, paint);
110                
111            }
112            //draw text
113            paint.setColor(color.toInt());
114            paint.setStyle(Style.FILL);
115            
116                ctx.drawText(symbol, x, y, paint);
117        }//*/     
118    }
119
120    /**
121     * Create a copy of the {@Color} object with the passed alpha value.
122     * @param color {@Color} object used for RGB values
123     * @param alpha {@float} value between 0 and 1
124     * @return
125     */
126    public static Color setColorAlpha(Color color, float alpha) {
127        if (color != null)
128        {
129            if(alpha >= 0 && alpha <= 1)
130                return new Color(color.getRed(),color.getGreen(),color.getBlue(),(int)(alpha*255f));
131            else
132                return color;
133        }
134        else
135            return null;
136    }
137    public static String colorToHexString(Color color, Boolean withAlpha)
138    {
139        if(color != null)
140        {
141            String hex = color.toHexString();
142            hex = hex.toUpperCase();
143            if(withAlpha)
144                return "#" + hex;
145            else
146                return "#" + hex.substring(2);
147        }
148        return null;
149    }
150
151    /**
152     *
153     * @param hexValue - String representing hex value (formatted "0xRRGGBB"
154     * i.e. "0xFFFFFF") OR formatted "0xAARRGGBB" i.e. "0x00FFFFFF" for a color
155     * with an alpha value I will also put up with "RRGGBB" and "AARRGGBB"
156     * without the starting "0x"
157     * @return
158     */
159    public static Color getColorFromHexString(String hexValue)
160    {
161        try
162        {
163            if(hexValue==null || hexValue.isEmpty())
164                return null;
165            String hexOriginal = hexValue;
166
167            String hexAlphabet = "0123456789ABCDEF";
168
169            if (hexValue.charAt(0) == '#')
170            {
171                hexValue = hexValue.substring(1);
172            }
173            if (hexValue.substring(0, 2).equals("0x") || hexValue.substring(0, 2).equals("0X"))
174            {
175                hexValue = hexValue.substring(2);
176            }
177
178            hexValue = hexValue.toUpperCase();
179
180            int count = hexValue.length();
181            int[] value = null;
182            int k = 0;
183            int int1 = 0;
184            int int2 = 0;
185
186            if (count == 8 || count == 6)
187            {
188                value = new int[(count / 2)];
189                for (int i = 0; i < count; i += 2)
190                {
191                    int1 = hexAlphabet.indexOf(hexValue.charAt(i));
192                    int2 = hexAlphabet.indexOf(hexValue.charAt(i + 1));
193
194                    if(int1 == -1 || int2 == -1)
195                    {
196                        ErrorLogger.LogMessage("RendererUtilities", "getColorFromHexString", "Bad hex value: " + hexOriginal, Level.WARNING);
197                        return null;
198                    }
199
200                    value[k] = (int1 * 16) + int2;
201                    k++;
202                }
203
204                if (count == 8)
205                {
206                    return new Color(value[1], value[2], value[3], value[0]);
207                }
208                else if (count == 6)
209                {
210                    return new Color(value[0], value[1], value[2]);
211                }
212            }
213            else
214            {
215                ErrorLogger.LogMessage("RendererUtilities", "getColorFromHexString", "Bad hex value: " + hexOriginal, Level.WARNING);
216            }
217            return null;
218        }
219        catch (Exception exc)
220        {
221            ErrorLogger.LogException("RendererUtilities", "getColorFromHexString", exc);
222            return null;
223        }
224    }
225
226    /**
227     * For Renderer Use Only
228     * Assumes a fresh SVG String from the SVGLookup with its default values
229     * @param symbolID
230     * @param svg
231     * @param strokeColor hex value like "#FF0000";
232     * @param fillColor hex value like "#FF0000";
233     * @return SVG String
234     */
235    public static String setSVGFrameColors(String symbolID, String svg, Color strokeColor, Color fillColor)
236    {
237        String returnSVG = null;
238        String hexStrokeColor = null;
239        String hexFillColor = null;
240        float strokeAlpha = 1;
241        float fillAlpha = 1;
242        String strokeOpacity = "";
243        String fillOpacity = "";
244
245        int ss = SymbolID.getSymbolSet(symbolID);
246        int ver = SymbolID.getVersion(symbolID);
247        int affiliation = SymbolID.getAffiliation(symbolID);
248        String defaultFillColor = null;
249        returnSVG = svg;
250        if(strokeColor != null)
251        {
252            if(strokeColor.getAlpha() != 255)
253            {
254                strokeAlpha = strokeColor.getAlpha() / 255.0f;
255                strokeOpacity =  " stroke-opacity=\"" + String.valueOf(strokeAlpha) + "\"";
256                fillOpacity =  " fill-opacity=\"" + String.valueOf(strokeAlpha) + "\"";
257            }
258
259            hexStrokeColor = colorToHexString(strokeColor,false);
260            returnSVG = svg.replaceAll("stroke=\"#000000\"", "stroke=\"" + hexStrokeColor + "\"" + strokeOpacity);
261            returnSVG = returnSVG.replaceAll("fill=\"#000000\"", "fill=\"" + hexStrokeColor + "\"" + fillOpacity);
262
263            if(ss == SymbolID.SymbolSet_LandInstallation ||
264                    ss == SymbolID.SymbolSet_Space ||
265                    ss == SymbolID.SymbolSet_CyberSpace ||
266                    ss == SymbolID.SymbolSet_Activities)
267            {//add group fill so the extra shapes in these frames have the new frame color
268                String svgStart =  "<g id=\"" + SVGLookup.getFrameID(symbolID) + "\">";
269                String svgStartReplace = svgStart.substring(0,svgStart.length()-1) + " fill=\"" + hexStrokeColor + "\"" + fillOpacity + ">";
270                returnSVG = returnSVG.replace(svgStart,svgStartReplace);
271            }
272
273            if((SymbolID.getSymbolSet(symbolID)==SymbolID.SymbolSet_LandInstallation && SymbolID.getFrameShape(symbolID)=='0') ||
274                    SymbolID.getFrameShape(symbolID)==SymbolID.FrameShape_LandInstallation)
275            {
276                int i1 = findInstIndIndex(returnSVG)+5;
277                //make sure installation indicator matches line color
278                returnSVG = returnSVG.substring(0,i1) + " fill=\"" + hexStrokeColor + "\"" + returnSVG.substring(i1);
279            }
280
281        }
282        else if((SymbolID.getSymbolSet(symbolID)==SymbolID.SymbolSet_LandInstallation && SymbolID.getFrameShape(symbolID)=='0') ||
283                SymbolID.getFrameShape(symbolID)==SymbolID.FrameShape_LandInstallation)
284        {
285            int i1 = findInstIndIndex(returnSVG)+5;
286            //No line color change so make sure installation indicator stays black
287            returnSVG = returnSVG.substring(0,i1) + " fill=\"#000000\"" + returnSVG.substring(i1);
288        }
289
290        if(fillColor != null)
291        {
292            if(fillColor.getAlpha() != 255)
293            {
294                fillAlpha = fillColor.getAlpha() / 255.0f;
295                fillOpacity =  " fill-opacity=\"" + String.valueOf(fillAlpha) + "\"";
296            }
297
298            hexFillColor = colorToHexString(fillColor,false);
299            switch(affiliation)
300            {
301                case SymbolID.StandardIdentity_Affiliation_Friend:
302                case SymbolID.StandardIdentity_Affiliation_AssumedFriend:
303                    defaultFillColor = "fill=\"#80E0FF\"";//friendly frame fill
304                    break;
305                case SymbolID.StandardIdentity_Affiliation_Hostile_Faker:
306                    defaultFillColor = "fill=\"#FF8080\"";//hostile frame fill
307                    break;
308                case SymbolID.StandardIdentity_Affiliation_Suspect_Joker:
309                    if(SymbolID.getVersion(symbolID) >= SymbolID.Version_2525E)
310                        defaultFillColor = "fill=\"#FFE599\"";//suspect frame fill
311                    else
312                        defaultFillColor = "fill=\"#FF8080\"";//hostile frame fill
313                    break;
314                case SymbolID.StandardIdentity_Affiliation_Unknown:
315                case SymbolID.StandardIdentity_Affiliation_Pending:
316                    defaultFillColor = "fill=\"#FFFF80\"";//unknown frame fill
317                    break;
318                case SymbolID.StandardIdentity_Affiliation_Neutral:
319                    defaultFillColor = "fill=\"#AAFFAA\"";//neutral frame fill
320                    break;
321                default:
322                    defaultFillColor = "fill=\"#80E0FF\"";//friendly frame fill
323                    break;
324            }
325
326            int fillIndex = returnSVG.lastIndexOf(defaultFillColor);
327            if(fillIndex != -1)
328                returnSVG = returnSVG.substring(0,fillIndex) + "fill=\"" + hexFillColor + "\"" + fillOpacity + returnSVG.substring(fillIndex + defaultFillColor.length());
329
330            //returnSVG = returnSVG.replaceFirst(defaultFillColor, "fill=\"" + hexFillColor + "\"" + fillOpacity);
331        }
332
333        if(returnSVG != null)
334            return returnSVG;
335        else
336            return svg;
337    }
338
339    /**
340     * For Renderer Use Only
341     * Changes colors for single point control measures
342     * @param symbolID
343     * @param svg
344     * @param strokeColor hex value like "#FF0000";
345     * @param fillColor hex value like "#FF0000";
346     * @param isOutline true if this represents a thicker outline to render first beneath the normal symbol (the function must be called twice)
347     * @return SVG String
348     */
349    public static String setSVGSPCMColors(String symbolID, String svg, Color strokeColor, Color fillColor, boolean isOutline)
350    {
351        String returnSVG = svg;
352        String hexStrokeColor = null;
353        String hexFillColor = null;
354        float strokeAlpha = 1;
355        float fillAlpha = 1;
356        String strokeOpacity = "";
357        String fillOpacity = "";
358        String strokeCapSquare = " stroke-linecap=\"square\"";
359        String strokeCapButt = " stroke-linecap=\"butt\"";
360        String strokeCapRound = " stroke-linecap=\"round\"";
361
362        int affiliation = SymbolID.getAffiliation(symbolID);
363        String defaultFillColor = null;
364        if(strokeColor != null)
365        {
366            if(strokeColor.getAlpha() != 255)
367            {
368                strokeAlpha = strokeColor.getAlpha() / 255.0f;
369                strokeOpacity =  " stroke-opacity=\"" + strokeAlpha + "\"";
370                fillOpacity =  " fill-opacity=\"" + strokeAlpha + "\"";
371            }
372
373            hexStrokeColor = colorToHexString(strokeColor,false);
374            String defaultStrokeColor = "#000000";
375            if(symbolID.length()==5)
376            {
377                int mod = Integer.valueOf(symbolID.substring(2,4));
378                if(mod >= 13)
379                    defaultStrokeColor = "#00A651";
380
381            }
382            //key terrain
383            if(symbolID.length() >= 20 &&
384                    SymbolUtilities.getBasicSymbolID(symbolID).equals("25132100") &&
385                    SymbolID.getVersion(symbolID) >= SymbolID.Version_2525E)
386            {
387                defaultStrokeColor = "#800080";
388            }
389            returnSVG = returnSVG.replaceAll("stroke=\"" + defaultStrokeColor + "\"", "stroke=\"" + hexStrokeColor + "\"" + strokeOpacity);
390            returnSVG = returnSVG.replaceAll("fill=\"" + defaultStrokeColor + "\"", "fill=\"" + hexStrokeColor + "\"" + fillOpacity);
391        }
392        else
393        {
394            strokeColor = Color.BLACK;
395        }
396
397        if (isOutline) {
398            // Capture and scale stroke-widths to create outlines. Note that some stroke-widths are not integral numbers.
399            Pattern pattern = Pattern.compile("(stroke-width=\")(\\d+\\.?\\d*)\"");
400            Matcher m = pattern.matcher(svg);
401            TreeSet<String> strokeWidthStrings = new TreeSet<>();
402            while (m.find()) {
403                strokeWidthStrings.add(m.group(0));
404            }
405            // replace stroke width values in SVG from greatest to least to avoid unintended replacements
406            // TODO This might not actually sort strings from greatest to least stroke-width values because they're alphabetical
407            for (String target : strokeWidthStrings.descendingSet()) {
408                Pattern numPattern = Pattern.compile("\\d+\\.?\\d*");
409                Matcher numMatcher = numPattern.matcher(target);
410                numMatcher.find();
411                float f = Float.parseFloat(numMatcher.group(0));
412                String replacement = "stroke-width=\"" + (f * OUTLINE_SCALING_FACTOR) + "\"";
413                returnSVG = returnSVG.replace(target, replacement);
414            }
415
416            // add stroke-width and stroke (color) to all groups
417            pattern = Pattern.compile("(<g)");
418            m = pattern.matcher(svg);
419            TreeSet<String> groupStrings = new TreeSet<>();
420            while (m.find()) {
421                groupStrings.add(m.group(0));
422            }
423            for (String target : groupStrings) {
424                String replacement = target + strokeCapSquare + " stroke-width=\"" + (2.5f * OUTLINE_SCALING_FACTOR) + "\" stroke=\"#" + strokeColor.toHexString().substring(2) + "\" ";
425                returnSVG = returnSVG.replace(target, replacement);
426            }
427
428        }
429        else
430        {
431            /*
432            Pattern pattern = Pattern.compile("(font-size=\"\\d+\\.?\\d*)\"");
433            Matcher m = pattern.matcher(svg);
434            TreeSet<String> fontStrings = new TreeSet<>();
435            while (m.find()) {
436                fontStrings.add(m.group(0));
437            }
438            for (String target : fontStrings) {
439                String replacement = target + " fill=\"#" + strokeColor.toHexString().substring(2) + "\" ";
440                returnSVG = returnSVG.replace(target, replacement);
441            }//*/
442
443            String replacement = " fill=\"" + colorToHexString(strokeColor,false) + "\" ";
444            returnSVG = returnSVG.replace("fill=\"#000000\"",replacement);//only replace black fills, leave white fills alone.
445
446            //In case there are lines that don't have stroke defined, apply stroke color to the top level group.
447            String topGroupTag = "<g id=\"" + SymbolUtilities.getBasicSymbolID(symbolID) + "\">";//<g id="25212902">
448            String newGroupTag = "<g id=\"" + SymbolUtilities.getBasicSymbolID(symbolID) + "\" stroke=\"" + hexStrokeColor + "\"" + strokeOpacity + " " + replacement + ">";
449            returnSVG = returnSVG.replace(topGroupTag,newGroupTag);
450        }
451
452        if(fillColor != null)
453        {
454            if(fillColor.getAlpha() != 255)
455            {
456                fillAlpha = fillColor.getAlpha() / 255.0f;
457                fillOpacity =  " fill-opacity=\"" + fillAlpha + "\"";
458            }
459
460            hexFillColor = colorToHexString(fillColor,false);
461            defaultFillColor = "fill=\"#000000\"";
462
463            returnSVG = returnSVG.replaceAll(defaultFillColor, "fill=\"" + hexFillColor + "\"" + fillOpacity);
464        }
465
466        return returnSVG;
467    }
468
469    /**
470     * Sets SVG stroke-dasharray when action points are in planned status
471     * @param symbolID
472     * @param siIcon
473     * @return
474     */
475    public static SVGInfo setAffiliationDashArray(String symbolID, SVGInfo siIcon)
476    {
477        String svg = siIcon.getSVG();
478        int status = SymbolID.getStatus(symbolID);
479        int aff = SymbolID.getAffiliation(symbolID);
480        SVGInfo returnVal = siIcon;
481        if(status == SymbolID.Status_Planned_Anticipated_Suspect)
482        {
483            if(SymbolUtilities.isActionPoint(symbolID))
484            {
485                svg = svg.replaceFirst("<rect ","<rect stroke-dasharray=\"20 19\" ");
486                svg = svg.replaceFirst("<polygon ","<polygon stroke-dasharray=\"20 20\" ");
487                returnVal = new SVGInfo(siIcon.getID(),siIcon.getBbox(), svg);
488            }
489        }
490        /*else if(aff == SymbolID.StandardIdentity_Affiliation_Pending ||
491                aff == SymbolID.StandardIdentity_Affiliation_AssumedFriend ||
492                aff == SymbolID.StandardIdentity_Affiliation_Suspect_Joker)
493        {
494            //Dot pattern if Control Measures use it?
495        }//*/
496
497        return returnVal;
498    }
499    public static float findWidestStrokeWidth(String svg) {
500        Pattern pattern = Pattern.compile("(stroke-width=\")(\\d+\\.?\\d*)\"");
501        Matcher m = pattern.matcher(svg);
502        TreeSet<Float> strokeWidths = new TreeSet<>();
503        while (m.find()) {
504            // Log.d("found stroke width", m.group(0));
505            strokeWidths.add(Float.valueOf(m.group(2)));
506        }
507
508        float largest = 4.0f;
509        if (!strokeWidths.isEmpty()) {
510            largest = strokeWidths.descendingSet().first();
511        }
512        return largest * OUTLINE_SCALING_FACTOR;
513    }
514
515    public static int findInstIndIndex(String svg)
516    {
517        int start = -1;
518        int stop = -1;
519
520        start = svg.indexOf("<rect");
521        stop = svg.indexOf(">",start);
522
523        String rect = svg.substring(start,stop+1);
524        if(!rect.contains("fill"))//no set fill so it's the indicator
525        {
526            return start;
527        }
528        else //it's the next rect
529        {
530            start = svg.indexOf("<rect",stop);
531        }
532
533        return start;
534    }
535
536    public static SVGInfo scaleIcon(String symbolID, SVGInfo icon)
537    {
538        SVGInfo retVal= icon;
539        //safe square inside octagon:  <rect x="220" y="310" width="170" height="170"/>
540        double maxSize = 170;
541        RectF bbox = null;
542        if(icon != null)
543            bbox = icon.getBbox();
544        double length = 0;
545        if(bbox != null)
546            length = Math.max(bbox.width(),bbox.height());
547        if(length < 100 && length > 0 &&
548                SymbolID.getCommonModifier1(symbolID)==0 &&
549                SymbolID.getCommonModifier2(symbolID)==0 &&
550                SymbolID.getModifier1(symbolID)==0 &&
551                SymbolID.getModifier2(symbolID)==0)//if largest side smaller than 100 and there are no section mods, make it bigger
552        {
553            double ratio = maxSize / length;
554            double transx = ((bbox.left + (bbox.width()/2)) * ratio) - (bbox.left + (bbox.width()/2));
555            double transy = ((bbox.top + (bbox.height()/2)) * ratio) - (bbox.top + (bbox.height()/2));
556            String transform = " transform=\"translate(-" + transx + ",-" + transy + ") scale(" + ratio + " " + ratio + ")\">";
557            String svg = icon.getSVG();
558            svg = svg.replaceFirst(">",transform);
559            RectF newBbox = RectUtilities.makeRectF((float)(bbox.left - transx),(float)(bbox.top - transy),(float)(bbox.width() * ratio), (float) (bbox.height() * ratio));
560            retVal = new SVGInfo(icon.getID(),newBbox,svg);
561        }
562        return retVal;
563    }
564
565    public static int getDistanceBetweenPoints(Point pt1, Point pt2)
566    {
567        int distance = (int)(Math.sqrt(Math.pow((pt2.x - pt1.x) ,2) + Math.pow((pt2.y - pt1.y) ,2)));
568        return distance;
569    }
570
571    /**
572     * A starting point for calculating map scale.
573     * The User may prefer a different calculation depending on how their maps works.
574     * @param mapPixelWidth Width of your map in pixels
575     * @param eastLon East Longitude of your map
576     * @param westLon West Longitude of your map
577     * @return Map scale value to use in the RenderSymbol function {@link armyc2.c5isr.web.render.WebRenderer#RenderSymbol(String, String, String, String, String, String, double, String, Map, Map, int)}
578     */
579    public static double calculateMapScale(int mapPixelWidth, double eastLon, double westLon)
580    {
581        return calculateMapScale(mapPixelWidth,eastLon,westLon,RendererSettings.getInstance().getDeviceDPI());
582    }
583
584    /**
585     * A starting point for calculating map scale.
586     * The User may prefer a different calculation depending on how their maps works.
587     * @param mapPixelWidth Width of your map in pixels
588     * @param eastLon East Longitude of your map
589     * @param westLon West Longitude of your map
590     * @param dpi Dots Per Inch of your device
591     * @return Map scale value to use in the RenderSymbol function {@link armyc2.c5isr.web.render.WebRenderer#RenderSymbol(String, String, String, String, String, String, double, String, Map, Map, int)}
592     */
593    public static double calculateMapScale(int mapPixelWidth, double eastLon, double westLon, int dpi)
594    {
595        double INCHES_PER_METER = 39.3700787;
596        double METERS_PER_DEG = 40075017.0 / 360.0; // Earth's circumference in meters / 360 degrees
597
598        try
599        {
600            double sizeSquare = Math.abs(eastLon - westLon);
601            if (sizeSquare > 180)
602                sizeSquare = 360 - sizeSquare;
603
604            // physical screen length (in meters) = pixels in screen / pixels per inch / inch per meter
605            double screenLength = mapPixelWidth / dpi / INCHES_PER_METER;
606            // meters on screen = degrees on screen * meters per degree
607            double metersOnScreen = sizeSquare * METERS_PER_DEG;
608
609            double scale = metersOnScreen/screenLength;
610            return scale;
611        }
612        catch(Exception exc)
613        {
614            ErrorLogger.LogException("RendererUtilities","calculateMapScale",exc,Level.WARNING);
615        }
616        return 0;
617    }
618
619    // Overloaded method to return non-outline symbols as normal.
620    public static String setSVGSPCMColors(String symbolID, String svg, Color strokeColor, Color fillColor) {
621        return setSVGSPCMColors(symbolID, svg, strokeColor, fillColor, false);
622    }
623}