001package armyc2.c5isr.renderer;
002
003import android.graphics.Bitmap;
004import android.graphics.Bitmap.Config;
005import android.graphics.Canvas;
006import android.graphics.Paint;
007import android.graphics.Paint.FontMetrics;
008import android.graphics.Point;
009import android.graphics.Rect;
010import android.graphics.RectF;
011import android.util.Log;
012
013import com.caverock.androidsvg.SVG;
014
015import java.util.HashMap;
016import java.util.Map;
017
018import armyc2.c5isr.renderer.utilities.Color;
019import armyc2.c5isr.renderer.utilities.DrawRules;
020import armyc2.c5isr.renderer.utilities.ErrorLogger;
021import armyc2.c5isr.renderer.utilities.ImageInfo;
022import armyc2.c5isr.renderer.utilities.MSInfo;
023import armyc2.c5isr.renderer.utilities.MSLookup;
024import armyc2.c5isr.renderer.utilities.MilStdAttributes;
025import armyc2.c5isr.renderer.utilities.Modifiers;
026import armyc2.c5isr.renderer.utilities.RectUtilities;
027import armyc2.c5isr.renderer.utilities.RendererSettings;
028import armyc2.c5isr.renderer.utilities.RendererUtilities;
029import armyc2.c5isr.renderer.utilities.SVGInfo;
030import armyc2.c5isr.renderer.utilities.SVGLookup;
031import armyc2.c5isr.renderer.utilities.SVGSymbolInfo;
032import armyc2.c5isr.renderer.utilities.SettingsChangedEvent;
033import armyc2.c5isr.renderer.utilities.SettingsChangedEventListener;
034import armyc2.c5isr.renderer.utilities.SymbolDimensionInfo;
035import armyc2.c5isr.renderer.utilities.SymbolID;
036import armyc2.c5isr.renderer.utilities.SymbolUtilities;
037
038public class SinglePointSVGRenderer implements SettingsChangedEventListener
039{
040
041    private final String TAG = "SinglePointRenderer";
042    private static SinglePointSVGRenderer _instance = null;
043
044    private final Object _SinglePointCacheMutex = new Object();
045    private final Object _UnitCacheMutex = new Object();
046
047    private Paint _modifierFont = new Paint();
048    private Paint _modifierOutlineFont = new Paint();
049    private float _modifierDescent = 2;
050    private float _modifierFontHeight = 10;
051    private int _deviceDPI = 72;
052
053
054    private SinglePointSVGRenderer()
055    {
056        RendererSettings.getInstance().addEventListener(this);
057        
058        //get modifier font values.
059        onSettingsChanged(new SettingsChangedEvent(SettingsChangedEvent.EventType_FontChanged));
060    }
061
062    public static synchronized SinglePointSVGRenderer getInstance()
063    {
064        if (_instance == null)
065        {
066            _instance = new SinglePointSVGRenderer();
067        }
068
069        return _instance;
070    }
071
072    /**
073     *
074     * @param symbolID
075     * @param modifiers
076     * @return
077     */
078    public SVGSymbolInfo RenderUnit(String symbolID, Map<String,String> modifiers, Map<String,String> attributes)
079    {
080        SVGSymbolInfo si = null;
081        SymbolDimensionInfo newSDI = null;
082
083        String lineColor = null;//SymbolUtilitiesD.getLineColorOfAffiliation(symbolID);
084        String fillColor = null;
085
086        if(SymbolID.getSymbolSet(symbolID)==SymbolID.SymbolSet_MineWarfare && RendererSettings.getInstance().getSeaMineRenderMethod()==RendererSettings.SeaMineRenderMethod_MEDAL)
087        {
088            lineColor = RendererUtilities.colorToHexString(SymbolUtilities.getLineColorOfAffiliation(symbolID), false);
089            fillColor = RendererUtilities.colorToHexString(SymbolUtilities.getFillColorOfAffiliation(symbolID), true);
090        }
091
092        String iconColor = null;
093
094        int alpha = 255;
095
096        //SVG values
097        String frameID = null;
098        String iconID = null;
099        String mod1ID = null;
100        String mod2ID = null;
101        SVGInfo siFrame = null;
102        SVGInfo siIcon = null;
103        SVGInfo siMod1 = null;
104        SVGInfo siMod2 = null;
105        SVG mySVG = null;
106        int top = 0;
107        int left = 0;
108        int width = 0;
109        int height = 0;
110        String svgStart = null;
111        String strSVG = null;
112        String strSVGFrame = null;
113
114
115        Rect symbolBounds = null;
116        Rect fullBounds = null;
117        Bitmap fullBMP = null;
118
119        boolean hasDisplayModifiers = false;
120        boolean hasTextModifiers = false;
121
122        int pixelSize = -1;
123        boolean keepUnitRatio = true;
124        boolean icon = false;
125        boolean noFrame = false;
126
127        int ver = SymbolID.getVersion(symbolID);
128
129        // <editor-fold defaultstate="collapsed" desc="Parse Attributes">
130        try
131        {
132            if(attributes != null)
133            {
134                if (attributes.containsKey(MilStdAttributes.PixelSize)) {
135                    pixelSize = Integer.parseInt(attributes.get(MilStdAttributes.PixelSize));
136                } else {
137                    pixelSize = RendererSettings.getInstance().getDefaultPixelSize();
138                }
139
140                if (attributes.containsKey(MilStdAttributes.KeepUnitRatio)) {
141                    keepUnitRatio = Boolean.parseBoolean(attributes.get(MilStdAttributes.KeepUnitRatio));
142                }
143
144                if (attributes.containsKey(MilStdAttributes.DrawAsIcon)) {
145                    icon = Boolean.parseBoolean(attributes.get(MilStdAttributes.DrawAsIcon));
146                }
147
148                if (icon)//icon won't show modifiers or display icons
149                {
150                    //TODO: symbolID modifications as necessary
151                    keepUnitRatio = false;
152                    hasDisplayModifiers = false;
153                    hasTextModifiers = false;
154                    //symbolID = symbolID.substring(0, 10) + "-----";
155                } else {
156                    hasDisplayModifiers = ModifierRenderer.hasDisplayModifiers(symbolID, modifiers);
157                    hasTextModifiers = ModifierRenderer.hasTextModifiers(symbolID, modifiers);
158                }
159
160                if (attributes.containsKey(MilStdAttributes.LineColor)) {
161                    lineColor = (attributes.get(MilStdAttributes.LineColor));
162                }
163                if (attributes.containsKey(MilStdAttributes.FillColor)) {
164                    fillColor = (attributes.get(MilStdAttributes.FillColor));
165                }
166                if (attributes.containsKey(MilStdAttributes.IconColor)) {
167                    iconColor = (attributes.get(MilStdAttributes.IconColor));
168                }//*/
169                if (attributes.containsKey(MilStdAttributes.Alpha)) {
170                    alpha = Integer.parseInt(attributes.get(MilStdAttributes.Alpha));
171                }
172            }
173        }
174        catch (Exception excModifiers)
175        {
176            ErrorLogger.LogException("SinglePointSVGRenderer", "RenderUnit", excModifiers);
177        }
178        // </editor-fold>
179
180        try
181        {
182
183            //if not, generate symbol
184            if (si == null)//*/
185            {
186                int version = SymbolID.getVersion(symbolID);
187                //Get SVG pieces of symbol
188                frameID = SVGLookup.getFrameID(symbolID);
189                iconID = SVGLookup.getMainIconID(symbolID);
190                mod1ID = SVGLookup.getMod1ID(symbolID);
191                mod2ID = SVGLookup.getMod2ID(symbolID);
192                siFrame = SVGLookup.getInstance().getSVGLInfo(frameID, version);
193                siIcon = SVGLookup.getInstance().getSVGLInfo(iconID, version);
194
195                if(siFrame == null)
196                {
197                    frameID = SVGLookup.getFrameID(SymbolUtilities.reconcileSymbolID(symbolID));
198                    siFrame = SVGLookup.getInstance().getSVGLInfo(frameID, version);
199                    if(siFrame == null)//still no match, get unknown frame
200                    {
201                        frameID = SVGLookup.getFrameID(SymbolID.setSymbolSet(symbolID,SymbolID.SymbolSet_Unknown));
202                        siFrame = SVGLookup.getInstance().getSVGLInfo(frameID, version);
203                    }
204                }
205
206                if(siIcon == null)
207                {
208                        if(SymbolID.getSymbolSet(symbolID) == SymbolID.SymbolSet_Unknown)
209                            siIcon = SVGLookup.getInstance().getSVGLInfo("00000000", version);//question mark
210                        /*else if(iconID.substring(2,8).equals("000000")==false && MSLookup.getInstance().getMSLInfo(symbolID) == null)
211                            siIcon = SVGLookup.getInstance().getSVGLInfo("98100000", version);//inverted question mark//*/
212                }
213
214                if(RendererSettings.getInstance().getScaleMainIcon())
215                    siIcon = RendererUtilities.scaleIcon(symbolID,siIcon);
216
217                siMod1 = SVGLookup.getInstance().getSVGLInfo(mod1ID, version);
218                siMod2 = SVGLookup.getInstance().getSVGLInfo(mod2ID, version);
219                top = Math.round(siFrame.getBbox().top);
220                left = Math.round(siFrame.getBbox().left);
221                width = Math.round(siFrame.getBbox().width());
222                height = Math.round(siFrame.getBbox().height());
223                if(siFrame.getBbox().bottom > 400)
224                    svgStart = "<svg xmlns:svg=\"http://www.w3.org/2000/svg\" version=\"1.1\" viewBox=\"0 0 612 792\">";
225                else
226                    svgStart = "<svg xmlns:svg=\"http://www.w3.org/2000/svg\" version=\"1.1\" viewBox=\"0 0 400 400\">";
227
228                //update line and fill color of frame SVG
229                if(lineColor != null || fillColor != null)
230                    strSVGFrame = RendererUtilities.setSVGFrameColors(symbolID,siFrame.getSVG(),RendererUtilities.getColorFromHexString(lineColor),RendererUtilities.getColorFromHexString(fillColor));
231                else
232                    strSVGFrame = siFrame.getSVG();
233
234                if(frameID.equals("octagon"))//for the 1 unit symbol that doesn't have a frame: 30 + 15000
235                {
236                    noFrame = true;
237                    strSVGFrame = strSVGFrame.replaceFirst("<g id=\"octagon\">", "<g id=\"octagon\" display=\"none\">");
238                }
239
240
241                //get SVG dimensions and target dimensions
242                symbolBounds = RectUtilities.makeRect(left,top,width,height);
243                Rect rect = new Rect(symbolBounds);
244                float ratio = -1;
245
246                if (pixelSize > 0 && keepUnitRatio == true)
247                {
248                    float heightRatio = SymbolUtilities.getUnitRatioHeight(symbolID);
249                    float widthRatio = SymbolUtilities.getUnitRatioWidth(symbolID);
250
251                    if(noFrame == true)//using octagon with display="none" as frame for a 1x1 shape
252                    {
253                        heightRatio = 1.0f;
254                        widthRatio = 1.0f;
255                    }
256
257                    if (heightRatio > widthRatio)
258                    {
259                        pixelSize = (int) ((pixelSize / 1.5f) * heightRatio);
260                    }
261                    else
262                    {
263                        pixelSize = (int) ((pixelSize / 1.5f) * widthRatio);
264                    }
265                }
266                if (pixelSize > 0)
267                {
268                    float p = pixelSize;
269                    float h = rect.height();
270                    float w = rect.width();
271
272                    ratio = Math.min((p / h), (p / w));
273
274                    symbolBounds = RectUtilities.makeRect(0f, 0f, w * ratio, h * ratio);
275                }
276
277                //StringBuilder sbGroupUnit = new StringBuilder();
278                String sbGroupUnit = "";
279                if(siFrame != null)
280                {
281                    sbGroupUnit += ("<g transform=\"translate(" + (siFrame.getBbox().left * -ratio) + ',' + (siFrame.getBbox().top * -ratio) + ") scale(" + ratio + "," + ratio + ")\"" + ">");
282                    if(siFrame != null)
283                        sbGroupUnit += (strSVGFrame);//(siFrame.getSVG());
284
285                    String color = "";
286                    if(iconColor != null)
287                    {
288                        //make sure string is properly formatted.
289                        iconColor = RendererUtilities.colorToHexString(RendererUtilities.getColorFromHexString(iconColor),false);
290                        if(iconColor != null && iconColor != "#000000" && iconColor != "")
291                            color = " fill=\"" + iconColor + "\" ";
292                        else
293                            iconColor = null;
294                    }
295                    String unit = "<g" + color + ">";
296                    if (siIcon != null)
297                        unit += (siIcon.getSVG());
298                    if (siMod1 != null)
299                        unit += (siMod1.getSVG());
300                    if (siMod2 != null)
301                        unit += (siMod2.getSVG());
302                    if(iconColor != null)
303                        unit = unit.replaceAll("#000000",iconColor);
304                    unit += "</g>";
305
306                    sbGroupUnit += unit + "</g>";
307                }
308
309                //center of octagon is the center of all unit symbols
310                Point centerOctagon = new Point(306, 396);
311                centerOctagon.offset(-left,-top);//offset for the symbol bounds x,y
312                //scale center point by same ratio as the symbol
313                centerOctagon = new Point((int)(centerOctagon.x * ratio), (int)(centerOctagon.y * ratio));
314
315                //set centerpoint of the image
316                Point centerPoint = centerOctagon;
317                Point centerCache = new Point(centerOctagon.x, centerOctagon.y);
318
319                //y offset to get centerpoint so we set back to zero when done.
320                //symbolBounds.top = 0;
321                RectUtilities.shift(symbolBounds,0,(int)-symbolBounds.top);
322
323                //Add core symbol to SVGSymbolInfo
324                si =  new SVGSymbolInfo(sbGroupUnit.toString(), centerPoint,symbolBounds,symbolBounds);
325
326                hasDisplayModifiers = ModifierRenderer.hasDisplayModifiers(symbolID, modifiers);
327                hasTextModifiers = ModifierRenderer.hasTextModifiers(symbolID, modifiers);
328
329                //process display modifiers
330                if (hasDisplayModifiers)
331                {
332                    newSDI = ModifierRenderer.processUnitDisplayModifiers(si, symbolID, modifiers, hasTextModifiers, attributes);
333                    if(newSDI != null)
334                    {
335                        si = (SVGSymbolInfo) newSDI;
336                        newSDI = null;
337                    }
338                }
339            }
340
341            //process text modifiers
342            if (hasTextModifiers)
343            {
344                newSDI = ModifierRenderer.processSPTextModifiers(si, symbolID, modifiers, attributes);
345            }
346
347            if (newSDI != null)
348            {
349                si = (SVGSymbolInfo) newSDI;
350            }
351            newSDI = null;
352
353            if(modifiers != null)
354                si = (SVGSymbolInfo) ModifierRenderer.processSpeedLeader(si,symbolID,modifiers,attributes);
355
356            int widthOffset = 0;
357            if(hasTextModifiers)
358                widthOffset = 2;//add for the text outline
359
360            int svgWidth = (int)(si.getImageBounds().width() + widthOffset);
361            int svgHeight = (int)si.getImageBounds().height();
362            //add SVG tag with dimensions
363            //draw unit from SVG
364            String svgAlpha = "";
365            if(alpha >=0 && alpha <= 255)
366                svgAlpha = " opacity=\"" + alpha/255f + "\"";
367            svgStart = "<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" width=\"" + svgWidth + "\" height=\"" + svgHeight +"\" viewBox=\"" + 0 + " " + 0 + " " + svgWidth + " " + svgHeight + "\"" + svgAlpha + ">\n";
368            String svgTranslateGroup = null;
369
370            double transX = si.getImageBounds().left * -1;
371            double transY = si.getImageBounds().top * -1;
372            Point anchor = si.getCenterPoint();
373            Rect imageBounds = si.getImageBounds();
374            if(transX > 0 || transY > 0)
375            {
376                anchor.offset((int)transX,(int)transY);
377                //ShapeUtilities.offset(anchor,transX,transY);
378                RectUtilities.shift(symbolBounds,(int)transX,(int)transY);
379                //ShapeUtilities.offset(symbolBounds,transX,transY);
380                RectUtilities.shift(imageBounds,(int)transX,(int)transY);
381                //ShapeUtilities.offset(imageBounds,transX,transY);
382                svgTranslateGroup = "<g transform=\"translate(" + transX + "," + transY + ")" +"\">\n";
383            }
384            imageBounds = RectUtilities.makeRect(imageBounds.left,imageBounds.top,svgWidth,svgHeight);
385
386            si = new SVGSymbolInfo(si.getSVG(),anchor,symbolBounds,imageBounds);
387            StringBuilder sbSVG = new StringBuilder();
388            sbSVG.append(svgStart);
389            sbSVG.append(makeDescTag(si));
390            sbSVG.append(makeMetadataTag(symbolID, si));
391            if(svgTranslateGroup != null)
392                sbSVG.append(svgTranslateGroup);
393            sbSVG.append(si.getSVG());
394            if(svgTranslateGroup != null)
395                sbSVG.append("\n</g>");
396            sbSVG.append("\n</svg>");
397            si =  new SVGSymbolInfo(sbSVG.toString(),anchor,symbolBounds,imageBounds);
398
399        }
400        catch (Exception exc)
401        {
402            ErrorLogger.LogException("SinglePointSVGRenderer", "RenderUnit", exc);
403        }
404        return si;
405    }
406
407    /**
408     *
409     * @param symbolID
410     * @param modifiers
411     * @return
412     */
413    @SuppressWarnings("unused")
414    public SVGSymbolInfo RenderSP(String symbolID, Map<String,String> modifiers, Map<String,String> attributes)
415    {
416
417        SVGSymbolInfo si = null;
418
419        ImageInfo temp = null;
420        String basicSymbolID = null;
421
422        Color lineColor = SymbolUtilities.getDefaultLineColor(symbolID);
423        Color fillColor = null;//SymbolUtilities.getFillColorOfAffiliation(symbolID);
424
425        int alpha = -1;
426
427
428        //SVG rendering variables
429        MSInfo msi = null;
430        String iconID = null;
431        SVGInfo siIcon = null;
432        String mod1ID = null;
433        SVGInfo siMod1 = null;
434        int top = 0;
435        int left = 0;
436        int width = 0;
437        int height = 0;
438        String svgStart = null;
439        String strSVG = null;
440        SVG mySVG = null;
441
442        float ratio = 0;
443
444        Rect symbolBounds = null;
445        RectF fullBounds = null;
446        Bitmap fullBMP = null;
447
448        boolean drawAsIcon = false;
449        int pixelSize = -1;
450        boolean keepUnitRatio = true;
451        boolean hasDisplayModifiers = false;
452        boolean hasTextModifiers = false;
453        boolean drawCustomOutline = false;
454
455
456        msi = MSLookup.getInstance().getMSLInfo(symbolID);
457
458        int ss = SymbolID.getSymbolSet(symbolID);
459        int ec = SymbolID.getEntityCode(symbolID);
460        int mod1 = 0;
461        int drawRule = 0;
462        if (msi != null) {
463            drawRule = msi.getDrawRule();
464        }
465        boolean hasAPFill = false;
466        if(RendererSettings.getInstance().getActionPointDefaultFill()) {
467            if (SymbolUtilities.isActionPoint(symbolID) || //action points
468                    ec/100 == 2135 || //sonobuoy
469                    ec == 180100 || ec == 180200 || ec == 180400) //ACP, CCP, PUP
470            {
471                if (SymbolID.getSymbolSet(symbolID) == SymbolID.SymbolSet_ControlMeasure) {
472                    lineColor = Color.BLACK;
473                    hasAPFill = true;
474                }
475            }
476        }
477
478        try
479        {
480            if (modifiers == null)
481                modifiers = new HashMap<>();
482
483
484
485            //get symbol info
486
487            msi = MSLookup.getInstance().getMSLInfo(symbolID);
488
489            if (msi == null)//if lookup fails, fix code/use unknown symbol code.
490            {
491                //TODO: change symbolID to Action Point with bad symbolID  in the T or H field
492            }
493
494
495            if (attributes != null) {
496                if (attributes.containsKey(MilStdAttributes.KeepUnitRatio)) {
497                    keepUnitRatio = Boolean.parseBoolean(attributes.get(MilStdAttributes.KeepUnitRatio));
498                }
499
500                if (attributes.containsKey(MilStdAttributes.LineColor)) {
501                    lineColor = RendererUtilities.getColorFromHexString(attributes.get(MilStdAttributes.LineColor));
502                }
503
504                if (attributes.containsKey(MilStdAttributes.FillColor)) {
505                    fillColor = RendererUtilities.getColorFromHexString(attributes.get(MilStdAttributes.FillColor));
506                }
507
508                if (attributes.containsKey(MilStdAttributes.Alpha)) {
509                    alpha = Integer.parseInt(attributes.get(MilStdAttributes.Alpha));
510                }
511
512                if (attributes.containsKey(MilStdAttributes.DrawAsIcon)) {
513                    drawAsIcon = Boolean.parseBoolean(attributes.get(MilStdAttributes.DrawAsIcon));
514                }
515
516                if (attributes.containsKey(MilStdAttributes.PixelSize)) {
517                    pixelSize = Integer.parseInt(attributes.get(MilStdAttributes.PixelSize));
518                } else {
519                    pixelSize = RendererSettings.getInstance().getDefaultPixelSize();
520                }
521                /*if (keepUnitRatio == true && msi.getSymbolSet() == SymbolID.SymbolSet_ControlMeasure && msi.getGeometry().equalsIgnoreCase("point")) {
522                    if(msi.getDrawRule() == DrawRules.POINT1)//Action Points
523                        pixelSize = (int)Math.ceil((pixelSize/1.5f) * 2.0f);
524                    else if(SymbolID.getSymbolSet(symbolID)==SymbolID.SymbolSet_ControlMeasure &&
525                            ec/100 == 2135)//Sonobuoy
526                    {
527                        pixelSize = (int)Math.ceil((pixelSize/1.5f) * 2.0f);
528                    }
529                    else
530                        pixelSize = (int)Math.ceil((pixelSize/1.5f) * 1.2f);
531                }//*/
532
533                if(!(drawAsIcon==true || hasAPFill==true))//don't outline icons because they're not going on the map and icons with fills don't need it
534                {
535                    if (attributes.containsKey(MilStdAttributes.OutlineSymbol))
536                        drawCustomOutline = Boolean.parseBoolean(attributes.get(MilStdAttributes.OutlineSymbol));
537                    else
538                        drawCustomOutline = RendererSettings.getInstance().getOutlineSPControlMeasures();
539
540                    //Protection of Cultural Property doesn't get outlined
541                    if(ss==25 && ec >= 360000 && ec < 360400)
542                        drawCustomOutline = false;
543                }
544
545                if (SymbolUtilities.isMultiPoint(symbolID))
546                    drawCustomOutline = false;//icon previews for multipoints do not need outlines since they shouldn't be on the map
547            }
548
549            if (drawAsIcon)//icon won't show modifiers or display icons
550            {
551                keepUnitRatio = false;
552                hasDisplayModifiers = false;
553                hasTextModifiers = false;
554                drawCustomOutline = false;
555            } else {
556                hasDisplayModifiers = ModifierRenderer.hasDisplayModifiers(symbolID, modifiers);
557                hasTextModifiers = ModifierRenderer.hasTextModifiers(symbolID, modifiers);
558            }
559
560            //Check if we need to set 'N' to "ENY"
561            int aff = SymbolID.getAffiliation(symbolID);
562            //int ss = msi.getSymbolSet();
563            if (ss == SymbolID.SymbolSet_ControlMeasure &&
564                    (aff == SymbolID.StandardIdentity_Affiliation_Hostile_Faker ||
565                            aff == SymbolID.StandardIdentity_Affiliation_Suspect_Joker) &&
566                    modifiers.containsKey(Modifiers.N_HOSTILE) &&
567                    drawAsIcon == false) {
568                modifiers.put(Modifiers.N_HOSTILE, "ENY");
569            }
570
571        } catch (Exception excModifiers) {
572            ErrorLogger.LogException("SinglePointSVGRenderer", "RenderSP-ParseModifiers", excModifiers);
573        }
574
575        try
576        {
577            int intFill = -1;
578            if (fillColor != null) {
579                intFill = fillColor.toInt();
580            }
581
582
583            if (msi.getSymbolSet() != SymbolID.SymbolSet_ControlMeasure)
584                lineColor = Color.BLACK;//color isn't black but should be fine for weather since colors can't be user defined.
585
586
587            if (SymbolID.getSymbolSet(symbolID) == SymbolID.SymbolSet_ControlMeasure && SymbolID.getEntityCode(symbolID) == 270701)//static depiction
588            {
589                //add mine fill to image
590                mod1 = SymbolID.getModifier1(symbolID);
591                if (!(mod1 >= 13 && mod1 <= 50))
592                    symbolID = SymbolID.setModifier1(symbolID, 13);
593            }
594
595
596            //if not, generate symbol.
597            if (si == null)//*/
598            {
599                int version = SymbolID.getVersion(symbolID);
600                //check symbol size////////////////////////////////////////////
601                Rect rect = null;
602                iconID = SVGLookup.getMainIconID(symbolID);
603                siIcon = SVGLookup.getInstance().getSVGLInfo(iconID, version);
604                if(siIcon==null) {
605                    return null;
606                }
607                mod1ID = SVGLookup.getMod1ID(symbolID);
608                siMod1 = SVGLookup.getInstance().getSVGLInfo(mod1ID, version);
609                float borderPadding = 0;
610                if (drawCustomOutline) {
611                    borderPadding = RendererUtilities.findWidestStrokeWidth(siIcon.getSVG());
612                }
613
614                //Oceanographic / Bottom Feature - essentially italic serif fonts need more vertical space
615                //pixel sizes above 150 it's fine, which is weird
616                if(SymbolUtilities.getBasicSymbolID(symbolID).startsWith("461206"))
617                {
618                    double va = siIcon.getBbox().height() * 0.025;
619                    double ha = siIcon.getBbox().width() * 0.025;//some also need to be slightly wider
620                    Rect adjustment = RectUtilities.makeRect((float)(siIcon.getBbox().left),(float)(siIcon.getBbox().top - va),(float)(siIcon.getBbox().width() + ha),(float)(siIcon.getBbox().height() + va));
621                    siIcon.getBbox().set(adjustment);
622                }
623
624                top = (int)Math.floor(siIcon.getBbox().top);
625                left = (int)Math.floor(siIcon.getBbox().left);
626                width = (int)Math.ceil(siIcon.getBbox().width() + (siIcon.getBbox().left - left));
627                height = (int)Math.ceil(siIcon.getBbox().height() + (siIcon.getBbox().top - top));
628                if (siIcon.getBbox().bottom > 400)
629                    svgStart = "<svg xmlns:svg=\"http://www.w3.org/2000/svg\" version=\"1.1\" viewBox=\"0 0 612 792\">";
630                else
631                    svgStart = "<svg xmlns:svg=\"http://www.w3.org/2000/svg\" version=\"1.1\" viewBox=\"0 0 400 400\">";
632
633                String strSVGIcon = null;
634
635                if(keepUnitRatio)
636                {
637                    double scaler = Math.max(width/(float)height, height/(float)width);
638                    if (scaler < 1.2)
639                        scaler = 1.2;
640                    if (scaler > 2)
641                        scaler = 2;
642
643                    if(!SymbolUtilities.isCBRNEvent(symbolID))
644                        pixelSize = (int) Math.ceil((pixelSize / 1.5f) * scaler);
645
646                    /*
647                    double min = Math.min(width/(float)height, height/(float)width);
648                    if (min < 0.6)//Rectangle
649                        pixelSize = (int) Math.ceil((pixelSize / 1.5f) * 2.0f);
650                    else if(min < 0.85)
651                        pixelSize = (int) Math.ceil((pixelSize / 1.5f) * 1.8f);
652                    else //more of a square
653                        pixelSize = (int) Math.ceil((pixelSize / 1.5f) * 1.2f);//*/
654                }
655
656                if (hasAPFill) //action points and a few others //Sonobuoy //ACP, CCP, PUP
657                {
658                    String apFill;
659                    if (fillColor != null)
660                        apFill = RendererUtilities.colorToHexString(fillColor, false);
661                    else
662                        apFill = RendererUtilities.colorToHexString(SymbolUtilities.getFillColorOfAffiliation(symbolID), false);
663                    siIcon = new SVGInfo(siIcon.getID(), siIcon.getBbox(), siIcon.getSVG().replaceAll("fill=\"none\"", "fill=\"" + apFill + "\""));
664                }
665
666                //Set dash array depending on affiliation and status
667                siIcon = RendererUtilities.setAffiliationDashArray(symbolID, siIcon);
668
669                //update line and fill color of frame SVG
670                if (msi.getSymbolSet() == SymbolID.SymbolSet_ControlMeasure && (lineColor != null || fillColor != null)) {
671                    if (drawCustomOutline) {
672                        // create outline with larger stroke-width first (if selected)
673                        strSVGIcon = RendererUtilities.setSVGSPCMColors(symbolID, siIcon.getSVG(), RendererUtilities.getIdealOutlineColor(lineColor), fillColor, true);
674                    }
675
676                    // append normal symbol SVG to be layered on top of outline
677                    strSVGIcon += RendererUtilities.setSVGSPCMColors(symbolID, siIcon.getSVG(), lineColor, fillColor, false);
678                } else//weather symbol (don't change color of weather graphics)
679                    strSVGIcon = siIcon.getSVG();
680
681                //If symbol is Static Depiction, add internal mine graphic based on sector modifier 1
682                if (SymbolID.getEntityCode(symbolID) == 270701 && siMod1 != null) {
683                    if (drawCustomOutline) {
684                        // create outline with larger stroke-width first (if selected)
685                        strSVGIcon += RendererUtilities.setSVGSPCMColors(mod1ID, siMod1.getSVG(), RendererUtilities.getIdealOutlineColor(RendererUtilities.getColorFromHexString("#00A651")), RendererUtilities.getColorFromHexString("#00A651"), true);
686                    }
687                    //strSVGIcon += siMod1.getSVG();
688                    strSVGIcon += RendererUtilities.setSVGSPCMColors(mod1ID, siMod1.getSVG(), lineColor, fillColor, false);
689                }
690
691                if (pixelSize > 0) {
692                    symbolBounds = RectUtilities.makeRect(left, top, width, height);
693                    rect = new Rect(symbolBounds);
694
695                    //adjust size
696                    float p = pixelSize;
697                    float h = rect.height();
698                    float w = rect.width();
699
700                    ratio = Math.min((p / h), (p / w));
701
702                    symbolBounds = RectUtilities.makeRect(0f, 0f, w * ratio, h * ratio);
703
704                    //make sure border padding isn't excessive.
705                    w = symbolBounds.width();
706                    h = symbolBounds.height();
707
708                    if (h / (h + borderPadding) > 0.10) {
709                        borderPadding = (float) (h * 0.03);
710                    } else if (w / (w + borderPadding) > 0.10) {
711                        borderPadding = (float) (w * 0.03);
712                    }
713
714                }
715
716                Rect borderPaddingBounds = null;
717                int offset = 0;
718                if(msi.getSymbolSet()==SymbolID.SymbolSet_ControlMeasure && drawCustomOutline && borderPadding != 0)
719                {
720                    borderPaddingBounds = RectUtilities.makeRect(0, 0, (rect.width()+(borderPadding)) * ratio, (rect.height()+(borderPadding)) * ratio);//.makeRect(0f, 0f, w * ratio, h * ratio);
721                    symbolBounds = borderPaddingBounds;
722
723                    //grow size SVG to accommodate the outline we added
724                    offset = (int)borderPadding/2;//4;
725                    RectUtilities.grow(rect, offset);
726                }
727
728                String strLineJoin = "";
729
730                if(SymbolUtilities.isActionPoint(symbolID))//smooth out action points
731                    strLineJoin = " stroke-linejoin=\"round\" ";
732
733                StringBuilder sbGroupUnit = new StringBuilder();
734                if(siIcon != null)
735                {
736                    sbGroupUnit.append("<g transform=\"translate(" + (rect.left * -ratio) + ',' + (rect.top * -ratio) + ") scale(" + ratio + "," + ratio + ")\"" + strLineJoin + ">");
737                    sbGroupUnit.append(strSVGIcon);//(siIcon.getSVG());
738                    sbGroupUnit.append("</g>");
739                }
740
741                //Point centerPoint = SymbolUtilities.getCMSymbolAnchorPoint(symbolID, RectUtilities.makeRectangle2DFromRect(offset, offset, symbolBounds.getWidth()-offset, symbolBounds.getHeight()-offset));
742                Point centerPoint = SymbolUtilities.getCMSymbolAnchorPoint(symbolID, RectUtilities.makeRectF(0, 0, symbolBounds.width(), symbolBounds.height()));
743
744                /*if(borderPaddingBounds != null) {
745                    RectUtilities.grow(symbolBounds, 4);
746                }//*/
747
748                si = new SVGSymbolInfo(sbGroupUnit.toString(), centerPoint,symbolBounds,symbolBounds);
749
750            }
751
752            //Process Modifiers
753            SVGSymbolInfo siNew = null;
754            if (drawAsIcon == false && (hasTextModifiers || hasDisplayModifiers)) {
755                SymbolDimensionInfo sdiTemp = null;
756                if (SymbolUtilities.isSPWithSpecialModifierLayout(symbolID))//(SymbolUtilitiesD.isTGSPWithSpecialModifierLayout(symbolID))
757                {
758                    sdiTemp = ModifierRenderer.ProcessTGSPWithSpecialModifierLayout(si, symbolID, modifiers, attributes, lineColor);
759                } else {
760                    sdiTemp = ModifierRenderer.ProcessTGSPModifiers(si, symbolID, modifiers, attributes, lineColor);
761                }
762                siNew = (sdiTemp instanceof SVGSymbolInfo ? (SVGSymbolInfo)sdiTemp : null);
763
764            }
765
766            if (siNew != null) {
767                si = siNew;
768            }
769
770            //add SVG tag with dimensions
771            //draw unit from SVG
772            String svgAlpha = "";
773            if(alpha >=0 && alpha <= 255)
774                svgAlpha = " opacity=\"" + alpha/255f + "\"";
775            svgStart = "<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" width=\"" + (int)si.getImageBounds().width() + "\" height=\"" + (int)si.getImageBounds().height() +"\" viewBox=\"" + 0 + " " + 0 + " " + (int)si.getImageBounds().width() + " " + (int)si.getImageBounds().height() + "\"" + svgAlpha + ">\n";
776            String svgTranslateGroup = null;
777
778            double transX = si.getImageBounds().left * -1;
779            double transY = si.getImageBounds().top * -1;
780            Point anchor = si.getCenterPoint();
781            Rect imageBounds = si.getImageBounds();
782            if(transX > 0 || transY > 0)
783            {
784                //ShapeUtilities.offset(anchor,transX,transY);
785                anchor.offset(Math.round((float)transX),Math.round((float)transY));
786                //ShapeUtilities.offset(symbolBounds,transX,transY);
787                symbolBounds.offset((int)transX,(int)Math.ceil(transY));
788                //ShapeUtilities.offset(imageBounds,transX,transY);
789                imageBounds.offset((int)transX,(int)Math.ceil(transY));
790
791                svgTranslateGroup = "<g transform=\"translate(" + transX + "," + transY + ")" +"\">\n";
792            }
793            si = new SVGSymbolInfo(si.getSVG(),anchor,symbolBounds,imageBounds);
794            StringBuilder sbSVG = new StringBuilder();
795            sbSVG.append(svgStart);
796            sbSVG.append(makeDescTag(si));
797            sbSVG.append(makeMetadataTag(symbolID, si));
798            if(svgTranslateGroup != null)
799                sbSVG.append(svgTranslateGroup);
800            sbSVG.append(si.getSVG());
801            if(svgTranslateGroup != null)
802                sbSVG.append("\n</g>");
803            sbSVG.append("\n</svg>");
804            si =  new SVGSymbolInfo(sbSVG.toString(),anchor,symbolBounds,imageBounds);
805
806            //cleanup
807            //bmp.recycle();
808            symbolBounds = null;
809            fullBMP = null;
810            fullBounds = null;
811            mySVG = null;
812
813
814        } catch (Exception exc) {
815            ErrorLogger.LogException("SinglePointSVGRenderer", "RenderSP", exc);
816            return null;
817        }
818
819        return si;
820
821    }
822
823
824    /**
825     *
826     * @param symbolID
827     * @return
828     */
829    @SuppressWarnings("unused")
830    public ImageInfo RenderModifier(String symbolID, Map<String,String> attributes)
831    {
832        ImageInfo temp = null;
833        String basicSymbolID = null;
834
835        Color lineColor = null;
836        Color fillColor = null;//SymbolUtilities.getFillColorOfAffiliation(symbolID);
837
838        int alpha = -1;
839
840
841        //SVG rendering variables
842        MSInfo msi = null;
843        String iconID = null;
844        SVGInfo siIcon = null;
845        int top = 0;
846        int left = 0;
847        int width = 0;
848        int height = 0;
849        String svgStart = null;
850        String strSVG = null;
851        SVG mySVG = null;
852
853        float ratio = 0;
854
855        Rect symbolBounds = null;
856        RectF fullBounds = null;
857        Bitmap fullBMP = null;
858
859        boolean drawAsIcon = false;
860        int pixelSize = -1;
861        boolean keepUnitRatio = true;
862        boolean hasDisplayModifiers = false;
863        boolean hasTextModifiers = false;
864        int symbolOutlineWidth = RendererSettings.getInstance().getSinglePointSymbolOutlineWidth();
865        boolean drawCustomOutline = false;
866
867        try
868        {
869
870            msi = MSLookup.getInstance().getMSLInfo(symbolID);
871            if (attributes != null)
872            {
873                if (attributes.containsKey(MilStdAttributes.KeepUnitRatio))
874                {
875                    keepUnitRatio = Boolean.parseBoolean(attributes.get(MilStdAttributes.KeepUnitRatio));
876                }
877
878                if (attributes.containsKey(MilStdAttributes.LineColor))
879                {
880                    lineColor = RendererUtilities.getColorFromHexString(attributes.get(MilStdAttributes.LineColor));
881                }
882
883                if (attributes.containsKey(MilStdAttributes.FillColor))
884                {
885                    fillColor = RendererUtilities.getColorFromHexString(attributes.get(MilStdAttributes.FillColor));
886                }
887
888                if (attributes.containsKey(MilStdAttributes.Alpha))
889                {
890                    alpha = Integer.parseInt(attributes.get(MilStdAttributes.Alpha));
891                }
892
893                if (attributes.containsKey(MilStdAttributes.DrawAsIcon))
894                {
895                    drawAsIcon = Boolean.parseBoolean(attributes.get(MilStdAttributes.DrawAsIcon));
896                }
897
898                if (attributes.containsKey(MilStdAttributes.PixelSize))
899                {
900                    pixelSize = Integer.parseInt(attributes.get(MilStdAttributes.PixelSize));
901                    if(msi.getSymbolSet() == SymbolID.SymbolSet_ControlMeasure)
902                    {
903                        if(SymbolID.getEntityCode(symbolID)==270701)//static depiction
904                            pixelSize = (int)(pixelSize * 0.9);//try to scale to be somewhat in line with units
905                    }
906                }
907
908                if(drawAsIcon==false)//don't outline icons because they're not going on the map
909                {
910                    if(attributes.containsKey(MilStdAttributes.OutlineSymbol))
911                        drawCustomOutline = Boolean.parseBoolean(attributes.get(MilStdAttributes.OutlineSymbol));
912                    else
913                        drawCustomOutline = RendererSettings.getInstance().getOutlineSPControlMeasures();
914                }
915
916                if(SymbolUtilities.isMultiPoint(symbolID))
917                    drawCustomOutline=false;//icon previews for multipoints do not need outlines since they shouldn't be on the map
918
919                /*if (attributes.containsKey(MilStdAttributes.OutlineWidth)>=0)
920                 symbolOutlineWidth = Integer.parseInt(attributes.get(MilStdAttributes.OutlineWidth));//*/
921            }
922
923            int outlineOffset = symbolOutlineWidth;
924            if (drawCustomOutline && outlineOffset > 2)
925            {
926                outlineOffset = (outlineOffset - 1) / 2;
927            }
928            else
929            {
930                outlineOffset = 0;
931            }
932
933        }
934        catch (Exception excModifiers)
935        {
936            ErrorLogger.LogException("SinglePointSVGRenderer", "RenderModifier", excModifiers);
937        }
938
939        try
940        {
941            ImageInfo ii = null;
942            int intFill = -1;
943            if (fillColor != null)
944            {
945                intFill = fillColor.toInt();
946            }
947
948
949            if(msi.getSymbolSet() != SymbolID.SymbolSet_ControlMeasure)
950                lineColor = Color.BLACK;//color isn't black but should be fine for weather since colors can't be user defined.
951
952
953            //if not, generate symbol
954            if (ii == null)//*/
955            {
956                int version = SymbolID.getVersion(symbolID);
957                //check symbol size////////////////////////////////////////////
958                Rect rect = null;
959
960                iconID = SVGLookup.getMod1ID(symbolID);
961                siIcon = SVGLookup.getInstance().getSVGLInfo(iconID, version);
962                top = Math.round(siIcon.getBbox().top);
963                left = Math.round(siIcon.getBbox().left);
964                width = Math.round(siIcon.getBbox().width());
965                height = Math.round(siIcon.getBbox().height());
966                if(siIcon.getBbox().bottom > 400)
967                    svgStart = "<svg xmlns:svg=\"http://www.w3.org/2000/svg\" version=\"1.1\" viewBox=\"0 0 612 792\">";
968                else
969                    svgStart = "<svg xmlns:svg=\"http://www.w3.org/2000/svg\" version=\"1.1\" viewBox=\"0 0 400 400\">";
970
971                String strSVGIcon = null;
972                String strSVGOutline = null;
973
974                //update line and fill color of frame SVG
975                if(msi.getSymbolSet() == SymbolID.SymbolSet_ControlMeasure && (lineColor != null || fillColor != null))
976                    strSVGIcon = RendererUtilities.setSVGFrameColors(symbolID,siIcon.getSVG(),lineColor,fillColor);
977                else
978                    strSVGIcon = siIcon.getSVG();
979
980                if (pixelSize > 0)
981                {
982                    symbolBounds = RectUtilities.makeRect(left,top,width,height);
983                    rect = new Rect(symbolBounds);
984
985                    //adjust size
986                    float p = pixelSize;
987                    float h = rect.height();
988                    float w = rect.width();
989
990                    ratio = Math.min((p / h), (p / w));
991
992                    symbolBounds = RectUtilities.makeRect(0f, 0f, w * ratio, h * ratio);
993
994                }
995
996
997                //TODO: figure out how to draw an outline and adjust the symbol bounds accordingly
998
999                //Draw glyphs to bitmap
1000                Bitmap bmp = Bitmap.createBitmap((symbolBounds.width()), (symbolBounds.height()), Config.ARGB_8888);
1001                Canvas canvas = new Canvas(bmp);
1002
1003                symbolBounds = new Rect(0, 0, bmp.getWidth(), bmp.getHeight());
1004
1005                strSVG = svgStart + strSVGIcon + "</svg>";
1006                mySVG = SVG.getFromString(strSVG);
1007                mySVG.setDocumentViewBox(left,top,width,height);
1008                mySVG.renderToCanvas(canvas);
1009
1010                Point centerPoint = SymbolUtilities.getCMSymbolAnchorPoint(symbolID,new RectF(0, 0, symbolBounds.right, symbolBounds.bottom));
1011
1012                ii = new ImageInfo(bmp, centerPoint, symbolBounds);
1013
1014
1015                /*if (drawAsIcon == false && pixelSize <= 100)
1016                {
1017                    _tgCache.put(key, ii);
1018                }//*/
1019            }
1020
1021
1022            //cleanup
1023            //bmp.recycle();
1024            symbolBounds = null;
1025            fullBMP = null;
1026            fullBounds = null;
1027            mySVG = null;
1028
1029
1030            if (drawAsIcon)
1031            {
1032                return ii.getSquareImageInfo();
1033            }
1034            else
1035            {
1036                return ii;
1037            }
1038
1039        }
1040        catch (Exception exc)
1041        {
1042            ErrorLogger.LogException("SinglePointSVGRenderer", "RenderModifier", exc);
1043        }
1044        return null;
1045    }
1046
1047    private String makeDescTag(SVGSymbolInfo si)
1048    {
1049        StringBuilder sbDesc = new StringBuilder();
1050
1051        if(si != null)
1052        {
1053            Rect bounds = si.getSymbolBounds();
1054            Rect iBounds = si.getImageBounds();
1055            sbDesc.append("<desc>").append(si.getCenterX()).append(" ").append(si.getCenterY()).append(" ");
1056            sbDesc.append(bounds.left).append(" ").append(bounds.top).append(" ").append(bounds.width()).append(" ").append(bounds.height()).append(" ");
1057            sbDesc.append(iBounds.left).append(" ").append(iBounds.top).append(" ").append(iBounds.width()).append(" ").append(iBounds.height());
1058            sbDesc.append("</desc>\n");
1059        }
1060        return sbDesc.toString();
1061    }
1062
1063    private String makeMetadataTag(String symbolID, SVGSymbolInfo si)
1064    {
1065        StringBuilder sbDesc = new StringBuilder();
1066
1067        if(si != null)
1068        {
1069            Rect bounds = si.getSymbolBounds();
1070            Rect iBounds = si.getImageBounds();
1071            sbDesc.append("<metadata>\n");
1072            sbDesc.append("<symbolID>").append(symbolID).append("</symbolID>\n");
1073            sbDesc.append("<anchor>").append(si.getCenterX()).append(" ").append(si.getCenterY()).append("</anchor>\n");
1074            sbDesc.append("<symbolBounds>").append(bounds.left).append(" ").append(bounds.top).append(" ").append(bounds.width()).append(" ").append(bounds.height()).append("</symbolBounds>\n");
1075            sbDesc.append("<imageBounds>").append(iBounds.left).append(" ").append(iBounds.top).append(" ").append(iBounds.width()).append(" ").append(iBounds.height()).append("</imageBounds>\n");;
1076            sbDesc.append("</metadata>\n");
1077        }
1078        return sbDesc.toString();
1079    }
1080
1081    public void logError(String tag, Throwable thrown)
1082    {
1083        if (tag == null || tag.equals(""))
1084        {
1085            tag = "singlePointRenderer";
1086        }
1087
1088        String message = thrown.getMessage();
1089        String stack = getStackTrace(thrown);
1090        if (message != null)
1091        {
1092            Log.e(tag, message);
1093        }
1094        if (stack != null)
1095        {
1096            Log.e(tag, stack);
1097        }
1098    }
1099
1100    public String getStackTrace(Throwable thrown)
1101    {
1102        try
1103        {
1104            if (thrown != null)
1105            {
1106                if (thrown.getStackTrace() != null)
1107                {
1108                    String eol = System.getProperty("line.separator");
1109                    StringBuilder sb = new StringBuilder();
1110                    sb.append(thrown.toString());
1111                    sb.append(eol);
1112                    for (StackTraceElement element : thrown.getStackTrace())
1113                    {
1114                        sb.append("        at ");
1115                        sb.append(element);
1116                        sb.append(eol);
1117                    }
1118                    return sb.toString();
1119                }
1120                else
1121                {
1122                    return thrown.getMessage() + "- no stack trace";
1123                }
1124            }
1125            else
1126            {
1127                return "no stack trace";
1128            }
1129        }
1130        catch (Exception exc)
1131        {
1132            Log.e("getStackTrace", exc.getMessage());
1133        }
1134        return thrown.getMessage();
1135    }//
1136
1137    /*
1138     private static String PrintList(ArrayList list)
1139     {
1140     String message = "";
1141     for(Object item : list)
1142     {
1143
1144     message += item.toString() + "\n";
1145     }
1146     return message;
1147     }//*/
1148    /*
1149     private static String PrintObjectMap(Map<String, Object> map)
1150     {
1151     Iterator<Object> itr = map.values().iterator();
1152     String message = "";
1153     String temp = null;
1154     while(itr.hasNext())
1155     {
1156     temp = String.valueOf(itr.next());
1157     if(temp != null)
1158     message += temp + "\n";
1159     }
1160     //ErrorLogger.LogMessage(message);
1161     return message;
1162     }//*/
1163    @Override
1164    public void onSettingsChanged(SettingsChangedEvent sce)
1165    {
1166
1167        if(sce != null && sce.getEventType().equals(SettingsChangedEvent.EventType_FontChanged))
1168        {
1169            synchronized (_modifierFont)
1170            {
1171                _modifierFont = RendererSettings.getInstance().getModiferFont();
1172                _modifierOutlineFont = RendererSettings.getInstance().getModiferFont();
1173                FontMetrics fm = new FontMetrics();
1174                fm = _modifierFont.getFontMetrics();
1175                _modifierDescent = fm.descent;
1176                //_modifierFontHeight = fm.top + fm.bottom;
1177                _modifierFontHeight = fm.bottom - fm.top;
1178
1179                _modifierFont.setStrokeWidth(RendererSettings.getInstance().getTextOutlineWidth());
1180                _modifierOutlineFont.setColor(Color.white.toInt());
1181                _deviceDPI = RendererSettings.getInstance().getDeviceDPI();
1182
1183                ModifierRenderer.setModifierFont(_modifierFont, _modifierFontHeight, _modifierDescent);
1184
1185            }
1186        }
1187    }
1188}