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.PointF;
010import android.graphics.Rect;
011import android.graphics.RectF;
012import android.util.Log;
013import android.util.LruCache;
014
015import com.caverock.androidsvg.SVG;
016
017import java.util.HashMap;
018import java.util.Map;
019
020import armyc2.c5isr.renderer.utilities.Color;
021import armyc2.c5isr.renderer.utilities.DrawRules;
022import armyc2.c5isr.renderer.utilities.ErrorLogger;
023import armyc2.c5isr.renderer.utilities.ImageInfo;
024import armyc2.c5isr.renderer.utilities.MSInfo;
025import armyc2.c5isr.renderer.utilities.MSLookup;
026import armyc2.c5isr.renderer.utilities.MilStdAttributes;
027import armyc2.c5isr.renderer.utilities.Modifiers;
028import armyc2.c5isr.renderer.utilities.RectUtilities;
029import armyc2.c5isr.renderer.utilities.RendererSettings;
030import armyc2.c5isr.renderer.utilities.RendererUtilities;
031import armyc2.c5isr.renderer.utilities.SVGInfo;
032import armyc2.c5isr.renderer.utilities.SVGLookup;
033import armyc2.c5isr.renderer.utilities.SettingsChangedEvent;
034import armyc2.c5isr.renderer.utilities.SettingsChangedEventListener;
035import armyc2.c5isr.renderer.utilities.SymbolDimensionInfo;
036import armyc2.c5isr.renderer.utilities.SymbolID;
037import armyc2.c5isr.renderer.utilities.SymbolUtilities;
038
039public class SinglePointRenderer implements SettingsChangedEventListener
040{
041
042    private final String TAG = "SinglePointRenderer";
043    private static SinglePointRenderer _instance = null;
044
045    private final Object _SinglePointCacheMutex = new Object();
046    private final Object _UnitCacheMutex = new Object();
047
048    private final Object _modifierFontMutex = new Object();
049    private Paint _modifierFont = new Paint();
050    private Paint _modifierOutlineFont = new Paint();
051    private float _modifierDescent = 2;
052    private float _modifierFontHeight = 10;
053    private int _deviceDPI = 72;
054
055    //private LruCache<String, ImageInfo> _unitCache = new LruCache<String, ImageInfo>(15);
056    //private LruCache<String, ImageInfo> _tgCache = new LruCache<String, ImageInfo>(7);
057    private LruCache<String, ImageInfo> _unitCache = new LruCache<String, ImageInfo>(1024);
058    private LruCache<String, ImageInfo> _tgCache = new LruCache<String, ImageInfo>(1024);
059    private final int maxMemory = (int) (Runtime.getRuntime().maxMemory());// / 1024);
060    private int cacheSize = 5;//RendererSettings.getInstance().getCacheSize() / 2;
061    private int maxCachedEntrySize = cacheSize / 5;
062    private boolean cacheEnabled = RendererSettings.getInstance().getCacheEnabled();
063
064    private SinglePointRenderer()
065    {
066        RendererSettings.getInstance().addEventListener(this);
067        
068        //get modifier font values.
069        onSettingsChanged(new SettingsChangedEvent(SettingsChangedEvent.EventType_FontChanged));
070        //set cache
071        onSettingsChanged(new SettingsChangedEvent(SettingsChangedEvent.EventType_CacheSizeChanged));
072
073    }
074
075    public static synchronized SinglePointRenderer getInstance()
076    {
077        if (_instance == null)
078        {
079            _instance = new SinglePointRenderer();
080        }
081
082        return _instance;
083    }
084
085    /**
086     *
087     * @param symbolID
088     * @param modifiers
089     * @return
090     */
091    public ImageInfo RenderUnit(String symbolID, Map<String,String> modifiers, Map<String,String> attributes)
092    {
093        Color lineColor = SymbolUtilities.getLineColorOfAffiliation(symbolID);
094        Color fillColor = SymbolUtilities.getFillColorOfAffiliation(symbolID);
095        Color iconColor = null;
096
097        int alpha = -1;
098
099
100        //SVG values
101        String frameID = null;
102        String iconID = null;
103        String mod1ID = null;
104        String mod2ID = null;
105        SVGInfo siFrame = null;
106        SVGInfo siIcon = null;
107        SVGInfo siMod1 = null;
108        SVGInfo siMod2 = null;
109        SVG mySVG = null;
110        int top = 0;
111        int left = 0;
112        int width = 0;
113        int height = 0;
114        String svgStart = null;
115        String strSVG = null;
116        String strSVGFrame = null;
117
118
119        Rect symbolBounds = null;
120        Rect fullBounds = null;
121        Bitmap fullBMP = null;
122
123        boolean hasDisplayModifiers = false;
124        boolean hasTextModifiers = false;
125
126        int pixelSize = -1;
127        boolean keepUnitRatio = true;
128        boolean icon = false;
129        boolean noFrame = false;
130
131        int ver = SymbolID.getVersion(symbolID);
132
133        // <editor-fold defaultstate="collapsed" desc="Parse Attributes">
134        try
135        {
136
137            if (attributes.containsKey(MilStdAttributes.PixelSize))
138            {
139                pixelSize = Integer.parseInt(attributes.get(MilStdAttributes.PixelSize));
140            }
141            else
142            {
143                pixelSize = RendererSettings.getInstance().getDefaultPixelSize();
144            }
145
146            if (attributes.containsKey(MilStdAttributes.KeepUnitRatio))
147            {
148                keepUnitRatio = Boolean.parseBoolean(attributes.get(MilStdAttributes.KeepUnitRatio));
149            }
150
151            if (attributes.containsKey(MilStdAttributes.DrawAsIcon))
152            {
153                icon = Boolean.parseBoolean(attributes.get(MilStdAttributes.DrawAsIcon));
154            }
155
156            if (icon)//icon won't show modifiers or display icons
157            {
158                //TODO: symbolID modifications as necessary
159                keepUnitRatio = false;
160                hasDisplayModifiers = false;
161                hasTextModifiers = false;
162                //symbolID = symbolID.substring(0, 10) + "-----";
163            }
164            else
165            {
166                hasDisplayModifiers = ModifierRenderer.hasDisplayModifiers(symbolID, modifiers);
167                hasTextModifiers = ModifierRenderer.hasTextModifiers(symbolID, modifiers);
168            }
169
170            if (attributes.containsKey(MilStdAttributes.LineColor))
171            {
172                lineColor = new Color(attributes.get(MilStdAttributes.LineColor));
173            }
174            if (attributes.containsKey(MilStdAttributes.FillColor))
175            {
176                fillColor = new Color(attributes.get(MilStdAttributes.FillColor));
177            }
178            if (attributes.containsKey(MilStdAttributes.IconColor))
179            {
180                iconColor = new Color(attributes.get(MilStdAttributes.IconColor));
181            }//*/
182            if (attributes.containsKey(MilStdAttributes.Alpha))
183            {
184                alpha = Integer.parseInt(attributes.get(MilStdAttributes.Alpha));
185            }
186
187        }
188        catch (Exception excModifiers)
189        {
190            ErrorLogger.LogException("MilStdIconRenderer", "RenderUnit", excModifiers);
191        }
192        // </editor-fold>
193
194        try
195        {
196
197            ImageInfo ii = null;
198            String key = makeCacheKey(symbolID, lineColor.toInt(), fillColor.toInt(), String.valueOf(iconColor),pixelSize, keepUnitRatio, false);
199
200            //see if it's in the cache
201            if(_unitCache != null) {
202                ii = _unitCache.get(key);
203                //safety check in case bitmaps are getting recycled while still in the LRU cache
204                if (ii != null && ii.getImage() != null && ii.getImage().isRecycled()) {
205                    synchronized (_UnitCacheMutex) {
206                        _unitCache.remove(key);
207                        ii = null;
208                    }
209                }
210            }
211            //if not, generate symbol
212            if (ii == null)//*/
213            {
214                int version = SymbolID.getVersion(symbolID);
215                //Get SVG pieces of symbol
216                frameID = SVGLookup.getFrameID(symbolID);
217                iconID = SVGLookup.getMainIconID(symbolID);
218                mod1ID = SVGLookup.getMod1ID(symbolID);
219                mod2ID = SVGLookup.getMod2ID(symbolID);
220                siFrame = SVGLookup.getInstance().getSVGLInfo(frameID, version);
221                siIcon = SVGLookup.getInstance().getSVGLInfo(iconID, version);
222
223                if(siFrame == null)
224                {
225                    frameID = SVGLookup.getFrameID(SymbolUtilities.reconcileSymbolID(symbolID));
226                    siFrame = SVGLookup.getInstance().getSVGLInfo(frameID, version);
227                    if(siFrame == null)//still no match, get unknown frame
228                    {
229                        frameID = SVGLookup.getFrameID(SymbolID.setSymbolSet(symbolID,SymbolID.SymbolSet_Unknown));
230                        siFrame = SVGLookup.getInstance().getSVGLInfo(frameID, version);
231                    }
232                }
233
234                if(siIcon == null)
235                {
236                        if(iconID.substring(2,8).equals("000000")==false && MSLookup.getInstance().getMSLInfo(symbolID) == null)
237                            siIcon = SVGLookup.getInstance().getSVGLInfo("98100000", version);//inverted question mark
238                        else if(SymbolID.getSymbolSet(symbolID) == SymbolID.SymbolSet_Unknown)
239                            siIcon = SVGLookup.getInstance().getSVGLInfo("00000000", version);//question mark
240                }
241
242                if(RendererSettings.getInstance().getScaleMainIcon())
243                    siIcon = RendererUtilities.scaleIcon(symbolID,siIcon);
244
245                siMod1 = SVGLookup.getInstance().getSVGLInfo(mod1ID, version);
246                siMod2 = SVGLookup.getInstance().getSVGLInfo(mod2ID, version);
247                top = Math.round(siFrame.getBbox().top);
248                left = Math.round(siFrame.getBbox().left);
249                width = Math.round(siFrame.getBbox().width());
250                height = Math.round(siFrame.getBbox().height());
251                if(siFrame.getBbox().bottom > 400)
252                    svgStart = "<svg xmlns:svg=\"http://www.w3.org/2000/svg\" version=\"1.1\" viewBox=\"0 0 612 792\">";
253                else
254                    svgStart = "<svg xmlns:svg=\"http://www.w3.org/2000/svg\" version=\"1.1\" viewBox=\"0 0 400 400\">";
255
256                //update line and fill color of frame SVG
257                if(lineColor != null || fillColor != null)
258                    strSVGFrame = RendererUtilities.setSVGFrameColors(symbolID,siFrame.getSVG(),lineColor,fillColor);
259                else
260                    strSVGFrame = siFrame.getSVG();
261
262                if(frameID.equals("octagon"))//for the 1 unit symbol that doesn't have a frame: 30 + 15000
263                {
264                    noFrame = true;
265                    strSVGFrame = strSVGFrame.replaceFirst("<g id=\"octagon\">", "<g id=\"octagon\" display=\"none\">");
266                }
267
268
269                //get SVG dimensions and target dimensions
270                symbolBounds = RectUtilities.makeRect(left,top,width,height);
271                Rect rect = new Rect(symbolBounds);
272                float ratio = -1;
273
274                if (pixelSize > 0 && keepUnitRatio == true)
275                {
276                    float heightRatio = SymbolUtilities.getUnitRatioHeight(symbolID);
277                    float widthRatio = SymbolUtilities.getUnitRatioWidth(symbolID);
278
279                    if(noFrame == true)//using octagon with display="none" as frame for a 1x1 shape
280                    {
281                        heightRatio = 1.0f;
282                        widthRatio = 1.0f;
283                    }
284
285                    if (heightRatio > widthRatio)
286                    {
287                        pixelSize = (int) ((pixelSize / 1.5f) * heightRatio);
288                    }
289                    else
290                    {
291                        pixelSize = (int) ((pixelSize / 1.5f) * widthRatio);
292                    }
293                }
294                if (pixelSize > 0)
295                {
296                    float p = pixelSize;
297                    float h = rect.height();
298                    float w = rect.width();
299
300                    ratio = Math.min((p / h), (p / w));
301
302                    symbolBounds = RectUtilities.makeRect(0f, 0f, w * ratio, h * ratio);
303                }
304
305                //center of octagon is the center of all unit symbols
306                Point centerOctagon = new Point(306, 396);
307                centerOctagon.offset(-left,-top);//offset for the symbol bounds x,y
308                //scale center point by same ratio as the symbol
309                centerOctagon = new Point((int)(centerOctagon.x * ratio), (int)(centerOctagon.y * ratio));
310
311                //set centerpoint of the image
312                Point centerPoint = centerOctagon;
313                Point centerCache = new Point(centerOctagon.x, centerOctagon.y);
314
315                //y offset to get centerpoint so we set back to zero when done.
316                symbolBounds.top = 0;
317
318                //Create destination BMP
319                Bitmap bmp = Bitmap.createBitmap(symbolBounds.width(), symbolBounds.height(), Config.ARGB_8888);
320                Canvas canvas = new Canvas(bmp);
321
322                //draw unit from SVG
323                StringBuilder sb = new StringBuilder();
324                sb.append(svgStart);
325
326                if(strSVGFrame != null)
327                    sb.append(strSVGFrame);
328
329                String color = "";
330                String strokeFill = "";
331                if(iconColor != null)
332                {
333                    //make sure string is properly formatted.
334                    color = RendererUtilities.colorToHexString(iconColor,false);
335                    if(color != null && color != "#000000" && color != "")
336                        strokeFill = " stroke=\"" + color + "\" fill=\"" + color + "\" ";
337                    else
338                        color = null;
339                }
340                String unit = "<g" + strokeFill + ">";
341                if (siIcon != null)
342                    unit += (siIcon.getSVG());
343                if (siMod1 != null)
344                    unit += (siMod1.getSVG());
345                if (siMod2 != null)
346                    unit += (siMod2.getSVG());
347                if(iconColor != null && color != null && color != "")
348                    unit = unit.replaceAll("#000000",color);
349                sb.append(unit + "</g>");
350
351                sb.append("</svg>");
352
353                strSVG = sb.toString();
354
355                mySVG = SVG.getFromString(strSVG);
356                mySVG.setDocumentViewBox(left,top,width,height);
357                mySVG.renderToCanvas(canvas);
358
359
360                //adjust centerpoint for HQStaff if present
361                if (SymbolUtilities.isHQ(symbolID))
362                {
363                    PointF point1 = new PointF();
364                    PointF point2 = new PointF();
365                    int affiliation = SymbolID.getAffiliation(symbolID);
366                    int ss = SymbolID.getStandardIdentity(symbolID);
367                    if (affiliation == SymbolID.StandardIdentity_Affiliation_Friend
368                            || affiliation == SymbolID.StandardIdentity_Affiliation_AssumedFriend
369                            || affiliation == SymbolID.StandardIdentity_Affiliation_Neutral
370                            || ss == 15 || ss == 16)//exercise joker or faker
371                    {
372                        point1.x = (symbolBounds.left);
373                        point1.y = symbolBounds.top + (symbolBounds.height());
374                        point2.x = point1.x;
375                        point2.y = point1.y + symbolBounds.height();
376                    }
377                    else
378                    {
379                        point1.x = (symbolBounds.left + 1);
380                        point1.y = symbolBounds.top + (symbolBounds.height() / 2);
381                        point2.x = point1.x;
382                        point2.y = point1.y + symbolBounds.height();
383                    }
384                    centerPoint = new Point((int) point2.x, (int) point2.y);
385                }
386
387                ii = new ImageInfo(bmp, centerPoint, symbolBounds);
388
389                if(cacheEnabled && icon == false && bmp.getAllocationByteCount() <= maxCachedEntrySize)
390                {
391                    synchronized (_UnitCacheMutex)
392                    {
393                        if(_unitCache != null && _unitCache.get(key) == null)
394                            _unitCache.put(key, new ImageInfo(bmp, new Point(centerCache), new Rect(symbolBounds)));
395                    }
396                }
397
398                /*if(icon == false && pixelSize <= 100)
399                {
400                    _unitCache.put(key, new ImageInfo(bmp, new Point(centerCache), new Rect(symbolBounds)));
401                }//*/
402            }
403
404            ImageInfo iiNew = null;
405            SymbolDimensionInfo sdiTemp = null;
406            ////////////////////////////////////////////////////////////////////
407            //process display modifiers
408            if (hasDisplayModifiers)
409            {
410                sdiTemp = ModifierRenderer.processUnitDisplayModifiers( ii, symbolID, modifiers, hasTextModifiers, attributes);
411                iiNew = (sdiTemp instanceof ImageInfo ? (ImageInfo)sdiTemp : null);
412                sdiTemp = null;
413            }
414
415            if (iiNew != null)
416            {
417                ii = iiNew;
418            }
419            iiNew = null;
420
421            //process text modifiers
422            if (hasTextModifiers)
423            {
424                int ss = SymbolID.getSymbolSet(symbolID);
425                switch(ss)
426                {
427                    case SymbolID.SymbolSet_LandUnit:
428                    case SymbolID.SymbolSet_LandCivilianUnit_Organization:
429                        if(ver >= SymbolID.Version_2525E)
430                            sdiTemp = ModifierRenderer.processLandUnitTextModifiersE(ii, symbolID, modifiers, attributes);
431                        else
432                            sdiTemp = ModifierRenderer.processLandUnitTextModifiers(ii, symbolID, modifiers, attributes);
433                        break;
434                    case SymbolID.SymbolSet_LandEquipment:
435                    case SymbolID.SymbolSet_SignalsIntelligence_Land:
436                        if(ver >= SymbolID.Version_2525E)
437                            sdiTemp = ModifierRenderer.processLandEquipmentTextModifiersE(ii, symbolID, modifiers, attributes);
438                        else
439                            sdiTemp = ModifierRenderer.processLandEquipmentTextModifiers(ii, symbolID, modifiers, attributes);
440                        break;
441                    case SymbolID.SymbolSet_LandInstallation:
442                        if(ver >= SymbolID.Version_2525E)
443                            sdiTemp = ModifierRenderer.processLandInstallationTextModifiersE(ii, symbolID, modifiers, attributes);
444                        else
445                            sdiTemp = ModifierRenderer.processLandInstallationTextModifiers(ii, symbolID, modifiers, attributes);
446                        break;
447                    case SymbolID.SymbolSet_DismountedIndividuals:
448                        sdiTemp = ModifierRenderer.processDismountedIndividualsTextModifiers(ii, symbolID, modifiers, attributes);
449                        break;
450                    case SymbolID.SymbolSet_Space:
451                    case SymbolID.SymbolSet_SpaceMissile:
452                    case SymbolID.SymbolSet_Air:
453                    case SymbolID.SymbolSet_AirMissile:
454                    case SymbolID.SymbolSet_SignalsIntelligence_Air:
455                        if(ver >= SymbolID.Version_2525E)
456                            sdiTemp = ModifierRenderer.processAirSpaceUnitTextModifiersE(ii, symbolID, modifiers, attributes);
457                        else
458                            sdiTemp = ModifierRenderer.processAirSpaceUnitTextModifiers(ii, symbolID, modifiers, attributes);
459                        break;
460                    case SymbolID.SymbolSet_SignalsIntelligence_Space:
461                        if(ver < SymbolID.Version_2525E)
462                            sdiTemp = ModifierRenderer.processAirSpaceUnitTextModifiers(ii, symbolID, modifiers, attributes);
463                        else//SIGINT in 2525E+ uses modifer places based on frame shape
464                        {
465                            char frameShape = SymbolID.getFrameShape(symbolID);
466                            if(frameShape == SymbolID.FrameShape_Space || frameShape == SymbolID.FrameShape_Air)
467                                sdiTemp = ModifierRenderer.processAirSpaceUnitTextModifiersE(ii, symbolID, modifiers, attributes);
468                            else if(frameShape == SymbolID.FrameShape_LandEquipment_SeaSurface)//sea surface, but can't tell which so default land equip
469                                sdiTemp = ModifierRenderer.processLandEquipmentTextModifiersE(ii, symbolID, modifiers, attributes);
470                            else if(frameShape == SymbolID.FrameShape_SeaSubsurface)
471                                sdiTemp = ModifierRenderer.processSeaSubSurfaceTextModifiersE(ii, symbolID, modifiers, attributes);
472                            else//default land equipment
473                                sdiTemp = ModifierRenderer.processLandEquipmentTextModifiersE(ii, symbolID, modifiers, attributes);
474                        }
475                        break;
476                    case SymbolID.SymbolSet_SeaSurface:
477                    case SymbolID.SymbolSet_SignalsIntelligence_SeaSurface:
478                        if(ver >= SymbolID.Version_2525E)
479                            sdiTemp = ModifierRenderer.processSeaSurfaceTextModifiersE(ii, symbolID, modifiers, attributes);
480                        else
481                            sdiTemp = ModifierRenderer.processSeaSurfaceTextModifiers(ii, symbolID, modifiers, attributes);
482                        break;
483                    case SymbolID.SymbolSet_SeaSubsurface:
484                    case SymbolID.SymbolSet_SignalsIntelligence_SeaSubsurface:
485                        if(ver >= SymbolID.Version_2525E)
486                            sdiTemp = ModifierRenderer.processSeaSubSurfaceTextModifiersE(ii, symbolID, modifiers, attributes);
487                        else
488                            sdiTemp = ModifierRenderer.processSeaSubSurfaceTextModifiers(ii, symbolID, modifiers, attributes);
489                        break;
490                    case SymbolID.SymbolSet_Activities:
491                        if(ver >= SymbolID.Version_2525E)
492                            sdiTemp = ModifierRenderer.processActivitiesTextModifiersE(ii, symbolID, modifiers, attributes);
493                        else
494                            sdiTemp = ModifierRenderer.processActivitiesTextModifiers(ii, symbolID, modifiers, attributes);
495                        break;
496                    case SymbolID.SymbolSet_CyberSpace:
497                        sdiTemp = ModifierRenderer.processCyberSpaceTextModifiers(ii, symbolID, modifiers, attributes);
498                        break;
499                    case SymbolID.SymbolSet_MineWarfare:
500                        break;//no modifiers
501                    case SymbolID.SymbolSet_Unknown:
502                    default: //in theory, will never get here
503                        sdiTemp = ModifierRenderer.processUnknownTextModifiers(ii, symbolID, modifiers, attributes);
504                }
505
506            }
507
508            iiNew = (sdiTemp instanceof ImageInfo ? (ImageInfo)sdiTemp : null);
509            if (iiNew != null)
510            {
511                ii = iiNew;
512            }
513            iiNew = null;
514
515            //cleanup///////////////////////////////////////////////////////////
516            //bmp.recycle();
517            symbolBounds = null;
518            fullBMP = null;
519            fullBounds = null;
520            mySVG = null;
521            ////////////////////////////////////////////////////////////////////
522
523            if (icon == true)
524            {
525                return ii.getSquareImageInfo();
526            }
527            else
528            {
529                return ii;
530            }
531
532        }
533        catch (Exception exc)
534        {
535            ErrorLogger.LogException("MilStdIconRenderer", "RenderUnit", exc);
536        }
537        return null;
538    }
539
540    /**
541     *
542     * @param symbolID
543     * @param modifiers
544     * @return
545     */
546    @SuppressWarnings("unused")
547    public ImageInfo RenderSP(String symbolID, Map<String,String> modifiers, Map<String,String> attributes)
548    {
549        ImageInfo temp = null;
550        String basicSymbolID = null;
551
552        Color lineColor = SymbolUtilities.getDefaultLineColor(symbolID);
553        Color fillColor = null;//SymbolUtilities.getFillColorOfAffiliation(symbolID);
554
555        int alpha = -1;
556
557
558        //SVG rendering variables
559        MSInfo msi = null;
560        String iconID = null;
561        SVGInfo siIcon = null;
562        String mod1ID = null;
563        SVGInfo siMod1 = null;
564        int top = 0;
565        int left = 0;
566        int width = 0;
567        int height = 0;
568        String svgStart = null;
569        String strSVG = null;
570        SVG mySVG = null;
571
572        float ratio = 0;
573
574        Rect symbolBounds = null;
575        RectF fullBounds = null;
576        Bitmap fullBMP = null;
577
578        boolean drawAsIcon = false;
579        int pixelSize = -1;
580        boolean keepUnitRatio = true;
581        boolean hasDisplayModifiers = false;
582        boolean hasTextModifiers = false;
583        boolean drawCustomOutline = false;
584
585        msi = MSLookup.getInstance().getMSLInfo(symbolID);
586        int ss = SymbolID.getSymbolSet(symbolID);
587        int ec = SymbolID.getEntityCode(symbolID);
588        int mod1 = 0;
589        int drawRule = 0;
590        if(msi!=null){drawRule = msi.getDrawRule();}
591        boolean hasAPFill = false;
592        if(RendererSettings.getInstance().getActionPointDefaultFill()) {
593            if (SymbolUtilities.isActionPoint(symbolID) || //action points
594                    drawRule == DrawRules.POINT10 || //Sonobuoy
595                    ec == 180100 || ec == 180200 || ec == 180400) //ACP, CCP, PUP
596            {
597                if (SymbolID.getSymbolSet(symbolID) == SymbolID.SymbolSet_ControlMeasure) {
598                    lineColor = Color.BLACK;
599                    hasAPFill = true;
600                }
601            }
602        }
603
604        try
605        {
606            if (modifiers == null)
607            {
608                modifiers = new HashMap<>();
609            }
610
611
612            //get symbol info
613
614            msi = MSLookup.getInstance().getMSLInfo(symbolID);
615
616            if (msi == null)//if lookup fails, fix code/use unknown symbol code.
617            {
618                //TODO: change symbolID to Action Point with bad symbolID  in the T or H field
619            }
620
621            /* Fills built into SVG
622            if (SymbolUtilities.hasDefaultFill(symbolID))
623            {
624                fillColor = SymbolUtilities.getFillColorOfAffiliation(symbolID);
625            }
626            if (SymbolUtilities.isTGSPWithFill(symbolID))
627            {
628                fillID = SymbolUtilities.getTGFillSymbolCode(symbolID);
629                if (fillID != null)
630                {
631                    charFillIndex = SinglePointLookup.getInstance().getCharCodeFromSymbol(fillID, symStd);
632                }
633            }
634            else if (SymbolUtilities.isWeatherSPWithFill(symbolID))
635            {
636                charFillIndex = charFrameIndex + 1;
637                fillColor = SymbolUtilities.getFillColorOfWeather(symbolID);
638
639            }//*/
640
641            if (attributes != null)
642            {
643                if (attributes.containsKey(MilStdAttributes.KeepUnitRatio))
644                {
645                    keepUnitRatio = Boolean.parseBoolean(attributes.get(MilStdAttributes.KeepUnitRatio));
646                }
647
648                if (attributes.containsKey(MilStdAttributes.LineColor))
649                {
650                    lineColor = RendererUtilities.getColorFromHexString(attributes.get(MilStdAttributes.LineColor));
651                }
652
653                if (attributes.containsKey(MilStdAttributes.FillColor))
654                {
655                    fillColor = RendererUtilities.getColorFromHexString(attributes.get(MilStdAttributes.FillColor));
656                }
657
658                if (attributes.containsKey(MilStdAttributes.Alpha))
659                {
660                    alpha = Integer.parseInt(attributes.get(MilStdAttributes.Alpha));
661                }
662
663                if (attributes.containsKey(MilStdAttributes.DrawAsIcon))
664                {
665                    drawAsIcon = Boolean.parseBoolean(attributes.get(MilStdAttributes.DrawAsIcon));
666                }
667
668                if (attributes.containsKey(MilStdAttributes.PixelSize))
669                {
670                    pixelSize = Integer.parseInt(attributes.get(MilStdAttributes.PixelSize));
671                }
672                else
673                {
674                    pixelSize = RendererSettings.getInstance().getDefaultPixelSize();
675                }
676                if(keepUnitRatio == true && msi.getSymbolSet() == SymbolID.SymbolSet_ControlMeasure && msi.getGeometry().equalsIgnoreCase("point"))
677                {
678                    if(msi.getDrawRule() == DrawRules.POINT1)//Action Points
679                        pixelSize = (int)Math.ceil((pixelSize/1.5f) * 1.5f);
680                    else
681                        pixelSize = (int)Math.ceil((pixelSize/1.5f) * 1.2f);
682                }
683
684                if(attributes.containsKey(MilStdAttributes.OutlineSymbol))
685                    drawCustomOutline = Boolean.parseBoolean(attributes.get(MilStdAttributes.OutlineSymbol));
686                else
687                    drawCustomOutline = RendererSettings.getInstance().getOutlineSPControlMeasures();
688
689                if(SymbolUtilities.isMultiPoint(symbolID))
690                    drawCustomOutline=false;//icon previews for multipoints do not need outlines since they shouldn't be on the map
691            }
692
693            if (drawAsIcon)//icon won't show modifiers or display icons
694            {
695                keepUnitRatio = false;
696                hasDisplayModifiers = false;
697                hasTextModifiers = false;
698                drawCustomOutline = false;
699            }
700            else
701            {
702                hasDisplayModifiers = ModifierRenderer.hasDisplayModifiers(symbolID, modifiers);
703                hasTextModifiers = ModifierRenderer.hasTextModifiers(symbolID, modifiers);
704            }
705
706            //Check if we need to set 'N' to "ENY"
707            int aff = SymbolID.getAffiliation(symbolID);
708            //int ss = msi.getSymbolSet();
709            if (ss == SymbolID.SymbolSet_ControlMeasure &&
710                    (aff == SymbolID.StandardIdentity_Affiliation_Hostile_Faker ||
711                 aff == SymbolID.StandardIdentity_Affiliation_Suspect_Joker ) &&
712                    modifiers.containsKey(Modifiers.N_HOSTILE) &&
713                    drawAsIcon == false)
714            {
715                modifiers.put(Modifiers.N_HOSTILE, "ENY");
716            }
717
718        }
719        catch (Exception excModifiers)
720        {
721            ErrorLogger.LogException("MilStdIconRenderer", "RenderSP", excModifiers);
722        }
723
724        try
725        {
726            ImageInfo ii = null;
727            int intFill = -1;
728            if (fillColor != null)
729            {
730                intFill = fillColor.toInt();
731            }
732
733
734            if(msi.getSymbolSet() != SymbolID.SymbolSet_ControlMeasure)
735                lineColor = Color.BLACK;//color isn't black but should be fine for weather since colors can't be user defined.
736
737
738
739            if (SymbolID.getSymbolSet(symbolID)==SymbolID.SymbolSet_ControlMeasure && SymbolID.getEntityCode(symbolID) == 270701)//static depiction
740            {
741                //add mine fill to image
742                mod1 = SymbolID.getModifier1(symbolID);
743                if (!(mod1 >= 13 && mod1 <= 50))
744                    symbolID = SymbolID.setModifier1(symbolID, 13);
745            }
746
747            String key = makeCacheKey(symbolID, lineColor.toInt(), intFill, pixelSize, keepUnitRatio, drawCustomOutline);
748
749            //see if it's in the cache
750            if(_tgCache != null) {
751                ii = _tgCache.get(key);
752                //safety check in case bitmaps are getting recycled while still in the LRU cache
753                if (ii != null && ii.getImage() != null && ii.getImage().isRecycled()) {
754                    synchronized (_SinglePointCacheMutex) {
755                        _tgCache.remove(key);
756                        ii = null;
757                    }
758                }
759            }
760
761            //if not, generate symbol.
762            if (ii == null)//*/
763            {
764                int version = SymbolID.getVersion(symbolID);
765                //check symbol size////////////////////////////////////////////
766                Rect rect = null;
767                iconID = SVGLookup.getMainIconID(symbolID);
768                siIcon = SVGLookup.getInstance().getSVGLInfo(iconID, version);
769                mod1ID = SVGLookup.getMod1ID(symbolID);
770                siMod1 = SVGLookup.getInstance().getSVGLInfo(mod1ID, version);
771                float borderPadding = 0;
772                if (drawCustomOutline) {
773                    borderPadding = RendererUtilities.findWidestStrokeWidth(siIcon.getSVG());
774                }
775                top = Math.round(siIcon.getBbox().top);
776                left = Math.round(siIcon.getBbox().left);
777                width = Math.round(siIcon.getBbox().width());
778                height = Math.round(siIcon.getBbox().height());
779                if(siIcon.getBbox().bottom > 400)
780                    svgStart = "<svg xmlns:svg=\"http://www.w3.org/2000/svg\" version=\"1.1\" viewBox=\"0 0 612 792\">";
781                else
782                    svgStart = "<svg xmlns:svg=\"http://www.w3.org/2000/svg\" version=\"1.1\" viewBox=\"0 0 400 400\">";
783
784                String strSVGIcon = null;
785
786
787                if(hasAPFill) //action points and a few others //Sonobuoy //ACP, CCP, PUP
788                {
789                    String apFill;
790                    if(fillColor != null)
791                        apFill = RendererUtilities.colorToHexString(fillColor,false);
792                    else
793                        apFill = RendererUtilities.colorToHexString(SymbolUtilities.getFillColorOfAffiliation(symbolID),false);
794                    siIcon = new SVGInfo(siIcon.getID(),siIcon.getBbox(), siIcon.getSVG().replaceAll("fill=\"none\"","fill=\"" + apFill + "\""));
795                }
796
797                //update line and fill color of frame SVG
798                if(msi.getSymbolSet() == SymbolID.SymbolSet_ControlMeasure && (lineColor != null || fillColor != null)) {
799                    if (drawCustomOutline) {
800                        // create outline with larger stroke-width first (if selected)
801                        strSVGIcon = RendererUtilities.setSVGSPCMColors(symbolID, siIcon.getSVG(), RendererUtilities.getIdealOutlineColor(lineColor), fillColor, true);
802                    }
803
804                    // append normal symbol SVG to be layered on top of outline
805                    strSVGIcon += RendererUtilities.setSVGSPCMColors(symbolID, siIcon.getSVG(), lineColor, fillColor, false);
806                }
807                else//weather symbol (don't change color of weather graphics)
808                    strSVGIcon = siIcon.getSVG();
809
810                //If symbol is Static Depiction, add internal mine graphic based on sector modifier 1
811                if(SymbolID.getEntityCode(symbolID) == 270701 && siMod1 != null)
812                {
813                    if (drawCustomOutline) {
814                        // create outline with larger stroke-width first (if selected)
815                        strSVGIcon += RendererUtilities.setSVGSPCMColors(mod1ID, siMod1.getSVG(), RendererUtilities.getIdealOutlineColor(RendererUtilities.getColorFromHexString("#00A651")), RendererUtilities.getColorFromHexString("#00A651"), true);
816                    }
817                    //strSVGIcon += siMod1.getSVG();
818                    strSVGIcon += RendererUtilities.setSVGSPCMColors(mod1ID, siMod1.getSVG(), lineColor, fillColor, false);
819                }
820
821                if (pixelSize > 0)
822                {
823                    symbolBounds = RectUtilities.makeRect(left,top,width,height);
824                    rect = new Rect(symbolBounds);
825
826                    //adjust size
827                    float p = pixelSize;
828                    float h = rect.height();
829                    float w = rect.width();
830
831                    ratio = Math.min((p / h), (p / w));
832
833                    symbolBounds = RectUtilities.makeRect(0f, 0f, w * ratio, h * ratio);
834
835                    //make sure border padding isn't excessive.
836                    w = symbolBounds.width();
837                    h = symbolBounds.height();
838
839                    if(h/(h+borderPadding) > 0.10)
840                    {
841                        borderPadding = (float)(h * 0.1);
842                    }
843                    else if(w/(w+borderPadding) > 0.10)
844                    {
845                        borderPadding = (float)(w * 0.1);
846                    }
847
848                }
849
850                //Draw glyphs to bitmap
851                Bitmap bmp = Bitmap.createBitmap((symbolBounds.width() + Math.round(borderPadding)), (symbolBounds.height() + Math.round(borderPadding)), Config.ARGB_8888);
852                Canvas canvas = new Canvas(bmp);
853
854                symbolBounds = new Rect(0, 0, bmp.getWidth(), bmp.getHeight());
855
856                //grow size SVG to accommodate the outline we added
857                int offset = 0;
858                if(drawCustomOutline)
859                {
860                    //TODO: maybe come up with a calculation vs just the #2, although it seems to work well.
861                    RectUtilities.grow(rect, 2);
862                    offset = 4;
863                }//*/
864
865
866                if(msi.getSymbolSet()==SymbolID.SymbolSet_ControlMeasure && msi.getDrawRule()==DrawRules.POINT1)//smooth out action points
867                    strSVGIcon = "/n<g stroke-linejoin=\"round\" >/n" + strSVGIcon + "/n</g>";
868
869                strSVG = svgStart + strSVGIcon + "</svg>";
870                mySVG = SVG.getFromString(strSVG);
871                //mySVG.setDocumentViewBox(left,top,width,height);
872                mySVG.setDocumentViewBox(rect.left,rect.top,rect.width(),rect.height());
873                mySVG.renderToCanvas(canvas);
874
875                Point centerPoint = SymbolUtilities.getCMSymbolAnchorPoint(symbolID,new RectF(offset, offset, symbolBounds.right, symbolBounds.bottom));
876
877                ii = new ImageInfo(bmp, centerPoint, symbolBounds);
878
879                if(cacheEnabled && drawAsIcon == false && bmp.getAllocationByteCount() <= maxCachedEntrySize)
880                {
881                    synchronized (_SinglePointCacheMutex)
882                    {
883                        if(_tgCache.get(key) == null)
884                            _tgCache.put(key, ii);
885                    }
886                }
887                /*if (drawAsIcon == false && pixelSize <= 100)
888
889                    _tgCache.put(key, ii);
890                }//*/
891            }
892
893            //Process Modifiers
894            ImageInfo iiNew = null;
895            if (drawAsIcon == false && (hasTextModifiers || hasDisplayModifiers))
896            {
897                SymbolDimensionInfo sdiTemp = null;
898                if (SymbolUtilities.isSPWithSpecialModifierLayout(symbolID))//(SymbolUtilitiesD.isTGSPWithSpecialModifierLayout(symbolID))
899                {
900                    sdiTemp = ModifierRenderer.ProcessTGSPWithSpecialModifierLayout(ii, symbolID, modifiers, attributes, lineColor);
901                }
902                else
903                {
904                    sdiTemp = ModifierRenderer.ProcessTGSPModifiers(ii, symbolID, modifiers, attributes, lineColor);
905                }
906                iiNew = (sdiTemp instanceof ImageInfo ? (ImageInfo)sdiTemp : null);
907            }
908
909            if (iiNew != null)
910            {
911                ii = iiNew;
912            }
913
914            //cleanup
915            //bmp.recycle();
916            symbolBounds = null;
917            fullBMP = null;
918            fullBounds = null;
919            mySVG = null;
920
921
922            if (drawAsIcon)
923            {
924                return ii.getSquareImageInfo();
925            }
926            else
927            {
928                return ii;
929            }
930
931        }
932        catch (Exception exc)
933        {
934            ErrorLogger.LogException("MilStdIconRenderer", "RenderSP", exc);
935        }
936        return null;
937    }
938
939
940    /**
941     *
942     * @param symbolID
943     * @return
944     */
945    @SuppressWarnings("unused")
946    public ImageInfo RenderModifier(String symbolID, Map<String,String> attributes)
947    {
948        ImageInfo temp = null;
949        String basicSymbolID = null;
950
951        Color lineColor = null;
952        Color fillColor = null;//SymbolUtilities.getFillColorOfAffiliation(symbolID);
953
954        int alpha = -1;
955
956
957        //SVG rendering variables
958        MSInfo msi = null;
959        String iconID = null;
960        SVGInfo siIcon = null;
961        int top = 0;
962        int left = 0;
963        int width = 0;
964        int height = 0;
965        String svgStart = null;
966        String strSVG = null;
967        SVG mySVG = null;
968
969        float ratio = 0;
970
971        Rect symbolBounds = null;
972        RectF fullBounds = null;
973        Bitmap fullBMP = null;
974
975        boolean drawAsIcon = false;
976        int pixelSize = -1;
977        boolean keepUnitRatio = true;
978        boolean hasDisplayModifiers = false;
979        boolean hasTextModifiers = false;
980        int symbolOutlineWidth = RendererSettings.getInstance().getSinglePointSymbolOutlineWidth();
981        boolean drawCustomOutline = false;
982
983        try
984        {
985
986            msi = MSLookup.getInstance().getMSLInfo(symbolID);
987            if (attributes != null)
988            {
989                if (attributes.containsKey(MilStdAttributes.KeepUnitRatio))
990                {
991                    keepUnitRatio = Boolean.parseBoolean(attributes.get(MilStdAttributes.KeepUnitRatio));
992                }
993
994                if (attributes.containsKey(MilStdAttributes.LineColor))
995                {
996                    lineColor = RendererUtilities.getColorFromHexString(attributes.get(MilStdAttributes.LineColor));
997                }
998
999                if (attributes.containsKey(MilStdAttributes.FillColor))
1000                {
1001                    fillColor = RendererUtilities.getColorFromHexString(attributes.get(MilStdAttributes.FillColor));
1002                }
1003
1004                if (attributes.containsKey(MilStdAttributes.Alpha))
1005                {
1006                    alpha = Integer.parseInt(attributes.get(MilStdAttributes.Alpha));
1007                }
1008
1009                if (attributes.containsKey(MilStdAttributes.DrawAsIcon))
1010                {
1011                    drawAsIcon = Boolean.parseBoolean(attributes.get(MilStdAttributes.DrawAsIcon));
1012                }
1013
1014                if (attributes.containsKey(MilStdAttributes.PixelSize))
1015                {
1016                    pixelSize = Integer.parseInt(attributes.get(MilStdAttributes.PixelSize));
1017                    if(msi.getSymbolSet() == SymbolID.SymbolSet_ControlMeasure)
1018                    {
1019                        if(SymbolID.getEntityCode(symbolID)==270701)//static depiction
1020                            pixelSize = (int)(pixelSize * 0.9);//try to scale to be somewhat in line with units
1021                    }
1022                }
1023
1024                if(drawAsIcon==false)//don't outline icons because they're not going on the map
1025                {
1026                    if(attributes.containsKey(MilStdAttributes.OutlineSymbol))
1027                        drawCustomOutline = Boolean.parseBoolean(attributes.get(MilStdAttributes.OutlineSymbol));
1028                    else
1029                        drawCustomOutline = RendererSettings.getInstance().getOutlineSPControlMeasures();
1030                }
1031
1032                if(SymbolUtilities.isMultiPoint(symbolID))
1033                    drawCustomOutline=false;//icon previews for multipoints do not need outlines since they shouldn't be on the map
1034
1035                /*if (attributes.containsKey(MilStdAttributes.OutlineWidth)>=0)
1036                 symbolOutlineWidth = Integer.parseInt(attributes.get(MilStdAttributes.OutlineWidth));//*/
1037            }
1038
1039            int outlineOffset = symbolOutlineWidth;
1040            if (drawCustomOutline && outlineOffset > 2)
1041            {
1042                outlineOffset = (outlineOffset - 1) / 2;
1043            }
1044            else
1045            {
1046                outlineOffset = 0;
1047            }
1048
1049        }
1050        catch (Exception excModifiers)
1051        {
1052            ErrorLogger.LogException("MilStdIconRenderer", "RenderSP", excModifiers);
1053        }
1054
1055        try
1056        {
1057            ImageInfo ii = null;
1058            int intFill = -1;
1059            if (fillColor != null)
1060            {
1061                intFill = fillColor.toInt();
1062            }
1063
1064
1065            if(msi.getSymbolSet() != SymbolID.SymbolSet_ControlMeasure)
1066                lineColor = Color.BLACK;//color isn't black but should be fine for weather since colors can't be user defined.
1067
1068
1069            //if not, generate symbol
1070            if (ii == null)//*/
1071            {
1072                int version = SymbolID.getVersion(symbolID);
1073                //check symbol size////////////////////////////////////////////
1074                Rect rect = null;
1075
1076                iconID = SVGLookup.getMod1ID(symbolID);
1077                siIcon = SVGLookup.getInstance().getSVGLInfo(iconID, version);
1078                top = Math.round(siIcon.getBbox().top);
1079                left = Math.round(siIcon.getBbox().left);
1080                width = Math.round(siIcon.getBbox().width());
1081                height = Math.round(siIcon.getBbox().height());
1082                if(siIcon.getBbox().bottom > 400)
1083                    svgStart = "<svg xmlns:svg=\"http://www.w3.org/2000/svg\" version=\"1.1\" viewBox=\"0 0 612 792\">";
1084                else
1085                    svgStart = "<svg xmlns:svg=\"http://www.w3.org/2000/svg\" version=\"1.1\" viewBox=\"0 0 400 400\">";
1086
1087                String strSVGIcon = null;
1088                String strSVGOutline = null;
1089
1090                //update line and fill color of frame SVG
1091                if(msi.getSymbolSet() == SymbolID.SymbolSet_ControlMeasure && (lineColor != null || fillColor != null))
1092                    strSVGIcon = RendererUtilities.setSVGFrameColors(symbolID,siIcon.getSVG(),lineColor,fillColor);
1093                else
1094                    strSVGIcon = siIcon.getSVG();
1095
1096                if (pixelSize > 0)
1097                {
1098                    symbolBounds = RectUtilities.makeRect(left,top,width,height);
1099                    rect = new Rect(symbolBounds);
1100
1101                    //adjust size
1102                    float p = pixelSize;
1103                    float h = rect.height();
1104                    float w = rect.width();
1105
1106                    ratio = Math.min((p / h), (p / w));
1107
1108                    symbolBounds = RectUtilities.makeRect(0f, 0f, w * ratio, h * ratio);
1109
1110                }
1111
1112
1113                //TODO: figure out how to draw an outline and adjust the symbol bounds accordingly
1114
1115                //Draw glyphs to bitmap
1116                Bitmap bmp = Bitmap.createBitmap((symbolBounds.width()), (symbolBounds.height()), Config.ARGB_8888);
1117                Canvas canvas = new Canvas(bmp);
1118
1119                symbolBounds = new Rect(0, 0, bmp.getWidth(), bmp.getHeight());
1120
1121                strSVG = svgStart + strSVGIcon + "</svg>";
1122                mySVG = SVG.getFromString(strSVG);
1123                mySVG.setDocumentViewBox(left,top,width,height);
1124                mySVG.renderToCanvas(canvas);
1125
1126                Point centerPoint = SymbolUtilities.getCMSymbolAnchorPoint(symbolID,new RectF(0, 0, symbolBounds.right, symbolBounds.bottom));
1127
1128                ii = new ImageInfo(bmp, centerPoint, symbolBounds);
1129
1130
1131                /*if (drawAsIcon == false && pixelSize <= 100)
1132                {
1133                    _tgCache.put(key, ii);
1134                }//*/
1135            }
1136
1137
1138            //cleanup
1139            //bmp.recycle();
1140            symbolBounds = null;
1141            fullBMP = null;
1142            fullBounds = null;
1143            mySVG = null;
1144
1145
1146            if (drawAsIcon)
1147            {
1148                return ii.getSquareImageInfo();
1149            }
1150            else
1151            {
1152                return ii;
1153            }
1154
1155        }
1156        catch (Exception exc)
1157        {
1158            ErrorLogger.LogException("MilStdIconRenderer", "RenderSP", exc);
1159        }
1160        return null;
1161    }
1162
1163
1164
1165    /**
1166     *
1167     * @param symbolID
1168     * @param lineColor
1169     * @param fillColor
1170     * @param size
1171     * @param keepUnitRatio
1172     * @param drawOutline (only for single-point Control Measures)
1173     * @return
1174     */
1175    private static String makeCacheKey(String symbolID, int lineColor, int fillColor, int size, boolean keepUnitRatio, boolean drawOutline)
1176    {
1177        return makeCacheKey(symbolID, lineColor, fillColor, "null",size, keepUnitRatio, false);
1178    }
1179
1180    private static String makeCacheKey(String symbolID, int lineColor, int fillColor, String iconColor, int size, boolean keepUnitRatio, boolean drawOutline)
1181    {
1182        //String key = symbolID.substring(0, 20) + String.valueOf(lineColor) + String.valueOf(fillColor) + String.valueOf(size) + String.valueOf(keepUnitRatio);
1183        String key = symbolID.substring(0, 7) + symbolID.substring(10, 20) + SymbolID.getFrameShape(symbolID) + lineColor + fillColor + iconColor + size + keepUnitRatio + drawOutline;
1184        return key;
1185    }
1186
1187    public void logError(String tag, Throwable thrown)
1188    {
1189        if (tag == null || tag.equals(""))
1190        {
1191            tag = "singlePointRenderer";
1192        }
1193
1194        String message = thrown.getMessage();
1195        String stack = getStackTrace(thrown);
1196        if (message != null)
1197        {
1198            Log.e(tag, message);
1199        }
1200        if (stack != null)
1201        {
1202            Log.e(tag, stack);
1203        }
1204    }
1205
1206    public String getStackTrace(Throwable thrown)
1207    {
1208        try
1209        {
1210            if (thrown != null)
1211            {
1212                if (thrown.getStackTrace() != null)
1213                {
1214                    String eol = System.getProperty("line.separator");
1215                    StringBuilder sb = new StringBuilder();
1216                    sb.append(thrown.toString());
1217                    sb.append(eol);
1218                    for (StackTraceElement element : thrown.getStackTrace())
1219                    {
1220                        sb.append("        at ");
1221                        sb.append(element);
1222                        sb.append(eol);
1223                    }
1224                    return sb.toString();
1225                }
1226                else
1227                {
1228                    return thrown.getMessage() + "- no stack trace";
1229                }
1230            }
1231            else
1232            {
1233                return "no stack trace";
1234            }
1235        }
1236        catch (Exception exc)
1237        {
1238            Log.e("getStackTrace", exc.getMessage());
1239        }
1240        return thrown.getMessage();
1241    }//
1242
1243    /*
1244     private static String PrintList(ArrayList list)
1245     {
1246     String message = "";
1247     for(Object item : list)
1248     {
1249
1250     message += item.toString() + "\n";
1251     }
1252     return message;
1253     }//*/
1254    /*
1255     private static String PrintObjectMap(Map<String, Object> map)
1256     {
1257     Iterator<Object> itr = map.values().iterator();
1258     String message = "";
1259     String temp = null;
1260     while(itr.hasNext())
1261     {
1262     temp = String.valueOf(itr.next());
1263     if(temp != null)
1264     message += temp + "\n";
1265     }
1266     //ErrorLogger.LogMessage(message);
1267     return message;
1268     }//*/
1269    @Override
1270    public void onSettingsChanged(SettingsChangedEvent sce)
1271    {
1272
1273        if(sce != null && sce.getEventType().equals(SettingsChangedEvent.EventType_FontChanged))
1274        {
1275            synchronized (_modifierFontMutex)
1276            {
1277                _modifierFont = RendererSettings.getInstance().getModiferFont();
1278                _modifierOutlineFont = RendererSettings.getInstance().getModiferFont();
1279                FontMetrics fm = new FontMetrics();
1280                fm = _modifierFont.getFontMetrics();
1281                _modifierDescent = fm.descent;
1282                //_modifierFontHeight = fm.top + fm.bottom;
1283                _modifierFontHeight = fm.bottom - fm.top;
1284
1285                _modifierFont.setStrokeWidth(RendererSettings.getInstance().getTextOutlineWidth());
1286                _modifierOutlineFont.setColor(Color.white.toInt());
1287                _deviceDPI = RendererSettings.getInstance().getDeviceDPI();
1288
1289                ModifierRenderer.setModifierFont(_modifierFont, _modifierFontHeight, _modifierDescent);
1290
1291            }
1292        }
1293
1294        if(sce != null && sce.getEventType().equals(SettingsChangedEvent.EventType_CacheSizeChanged))
1295        {
1296
1297            int cSize = RendererSettings.getInstance().getCacheSize()/2;
1298            //adjust unit cache
1299            if(cSize != cacheSize) {
1300                cacheSize = cSize;
1301                if (cacheSize >= 5)
1302                    maxCachedEntrySize = cacheSize / 5;
1303                else
1304                    maxCachedEntrySize = 1;
1305
1306                if(cacheEnabled) //if cache enabled, update cache
1307                {
1308
1309                    synchronized (_UnitCacheMutex) {
1310                        if(_unitCache != null)
1311                            _unitCache.evictAll();
1312                        _unitCache = new LruCache<String, ImageInfo>(cSize) {
1313                            @Override
1314                            protected int sizeOf(String key, ImageInfo ii) {
1315                                return ii.getByteCount();// / 1024;
1316                            }
1317                        };
1318                    }
1319                    //adjust tg cache
1320                    synchronized (_SinglePointCacheMutex) {
1321                        if(_tgCache != null)
1322                            _tgCache.evictAll();
1323                        _tgCache = new LruCache<String, ImageInfo>(cSize) {
1324                            @Override
1325                            protected int sizeOf(String key, ImageInfo ii) {
1326                                return ii.getByteCount();// / 1024;
1327                            }
1328                        };
1329                    }
1330                }
1331            }
1332        }
1333        if(sce != null && sce.getEventType().equals(SettingsChangedEvent.EventType_CacheToggled))
1334        {
1335            if(cacheEnabled != RendererSettings.getInstance().getCacheEnabled())
1336            {
1337                cacheEnabled = RendererSettings.getInstance().getCacheEnabled();
1338
1339                if (cacheEnabled == false)
1340                {
1341                    synchronized (_SinglePointCacheMutex)
1342                    {
1343                        if (_tgCache != null)
1344                            _tgCache.evictAll();
1345                        _tgCache = null;
1346                    }
1347                    synchronized (_UnitCacheMutex)
1348                    {
1349                        if (_unitCache != null)
1350                            _unitCache.evictAll();
1351                        _unitCache = null;
1352                    }
1353                }
1354                else
1355                {
1356                    int cSize = RendererSettings.getInstance().getCacheSize() / 2;
1357                    synchronized (_SinglePointCacheMutex)
1358                    {
1359                        if(_tgCache != null)
1360                            _tgCache.evictAll();
1361                        _tgCache = new LruCache<String, ImageInfo>(cSize) {
1362                            @Override
1363                            protected int sizeOf(String key, ImageInfo ii) {
1364                                return ii.getByteCount();// / 1024;
1365                            }
1366                        };
1367                    }
1368                    synchronized (_UnitCacheMutex)
1369                    {
1370                        if(_unitCache != null)
1371                            _unitCache.evictAll();
1372                        _unitCache = new LruCache<String, ImageInfo>(cSize) {
1373                            @Override
1374                            protected int sizeOf(String key, ImageInfo ii) {
1375                                return ii.getByteCount();// / 1024;
1376                            }
1377                        };
1378                    }
1379                }
1380            }
1381        }
1382    }
1383}