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 = " 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                        if(ver >= SymbolID.Version_2525E)
498                            sdiTemp = ModifierRenderer.processCyberSpaceTextModifiersE(ii, symbolID, modifiers, attributes);
499                        else
500                            sdiTemp = ModifierRenderer.processCyberSpaceTextModifiers(ii, symbolID, modifiers, attributes);
501                        break;
502                    case SymbolID.SymbolSet_MineWarfare:
503                        break;//no modifiers
504                    case SymbolID.SymbolSet_Unknown:
505                    default: //in theory, will never get here
506                        sdiTemp = ModifierRenderer.processUnknownTextModifiers(ii, symbolID, modifiers, attributes);
507                }
508
509            }
510
511            iiNew = (sdiTemp instanceof ImageInfo ? (ImageInfo)sdiTemp : null);
512            if (iiNew != null)
513            {
514                ii = iiNew;
515            }
516            iiNew = null;
517
518            ii = (ImageInfo) ModifierRenderer.processSpeedLeader(ii,symbolID,modifiers,attributes);
519
520            //cleanup///////////////////////////////////////////////////////////
521            //bmp.recycle();
522            symbolBounds = null;
523            fullBMP = null;
524            fullBounds = null;
525            mySVG = null;
526            ////////////////////////////////////////////////////////////////////
527
528            if (icon == true)
529            {
530                return ii.getSquareImageInfo();
531            }
532            else
533            {
534                return ii;
535            }
536
537        }
538        catch (Exception exc)
539        {
540            ErrorLogger.LogException("MilStdIconRenderer", "RenderUnit", exc);
541        }
542        return null;
543    }
544
545    /**
546     *
547     * @param symbolID
548     * @param modifiers
549     * @return
550     */
551    @SuppressWarnings("unused")
552    public ImageInfo RenderSP(String symbolID, Map<String,String> modifiers, Map<String,String> attributes)
553    {
554        ImageInfo temp = null;
555        String basicSymbolID = null;
556
557        Color lineColor = SymbolUtilities.getDefaultLineColor(symbolID);
558        Color fillColor = null;//SymbolUtilities.getFillColorOfAffiliation(symbolID);
559
560        int alpha = -1;
561
562
563        //SVG rendering variables
564        MSInfo msi = null;
565        String iconID = null;
566        SVGInfo siIcon = null;
567        String mod1ID = null;
568        SVGInfo siMod1 = null;
569        int top = 0;
570        int left = 0;
571        int width = 0;
572        int height = 0;
573        String svgStart = null;
574        String strSVG = null;
575        SVG mySVG = null;
576
577        float ratio = 0;
578
579        Rect symbolBounds = null;
580        RectF fullBounds = null;
581        Bitmap fullBMP = null;
582
583        boolean drawAsIcon = false;
584        int pixelSize = -1;
585        boolean keepUnitRatio = true;
586        boolean hasDisplayModifiers = false;
587        boolean hasTextModifiers = false;
588        boolean drawCustomOutline = false;
589
590        msi = MSLookup.getInstance().getMSLInfo(symbolID);
591        int ss = SymbolID.getSymbolSet(symbolID);
592        int ec = SymbolID.getEntityCode(symbolID);
593        int mod1 = 0;
594        int drawRule = 0;
595        if(msi!=null){drawRule = msi.getDrawRule();}
596        boolean hasAPFill = false;
597        if(RendererSettings.getInstance().getActionPointDefaultFill()) {
598            if (SymbolUtilities.isActionPoint(symbolID) || //action points
599                    drawRule == DrawRules.POINT10 || //Sonobuoy
600                    ec == 180100 || ec == 180200 || ec == 180400) //ACP, CCP, PUP
601            {
602                if (SymbolID.getSymbolSet(symbolID) == SymbolID.SymbolSet_ControlMeasure) {
603                    lineColor = Color.BLACK;
604                    hasAPFill = true;
605                }
606            }
607        }
608
609        try
610        {
611            if (modifiers == null)
612            {
613                modifiers = new HashMap<>();
614            }
615
616
617            //get symbol info
618
619            msi = MSLookup.getInstance().getMSLInfo(symbolID);
620
621            if (msi == null)//if lookup fails, fix code/use unknown symbol code.
622            {
623                //TODO: change symbolID to Action Point with bad symbolID  in the T or H field
624            }
625
626            /* Fills built into SVG
627            if (SymbolUtilities.hasDefaultFill(symbolID))
628            {
629                fillColor = SymbolUtilities.getFillColorOfAffiliation(symbolID);
630            }
631            if (SymbolUtilities.isTGSPWithFill(symbolID))
632            {
633                fillID = SymbolUtilities.getTGFillSymbolCode(symbolID);
634                if (fillID != null)
635                {
636                    charFillIndex = SinglePointLookup.getInstance().getCharCodeFromSymbol(fillID, symStd);
637                }
638            }
639            else if (SymbolUtilities.isWeatherSPWithFill(symbolID))
640            {
641                charFillIndex = charFrameIndex + 1;
642                fillColor = SymbolUtilities.getFillColorOfWeather(symbolID);
643
644            }//*/
645
646            if (attributes != null)
647            {
648                if (attributes.containsKey(MilStdAttributes.KeepUnitRatio))
649                {
650                    keepUnitRatio = Boolean.parseBoolean(attributes.get(MilStdAttributes.KeepUnitRatio));
651                }
652
653                if (attributes.containsKey(MilStdAttributes.LineColor))
654                {
655                    lineColor = RendererUtilities.getColorFromHexString(attributes.get(MilStdAttributes.LineColor));
656                }
657
658                if (attributes.containsKey(MilStdAttributes.FillColor))
659                {
660                    fillColor = RendererUtilities.getColorFromHexString(attributes.get(MilStdAttributes.FillColor));
661                }
662
663                if (attributes.containsKey(MilStdAttributes.Alpha))
664                {
665                    alpha = Integer.parseInt(attributes.get(MilStdAttributes.Alpha));
666                }
667
668                if (attributes.containsKey(MilStdAttributes.DrawAsIcon))
669                {
670                    drawAsIcon = Boolean.parseBoolean(attributes.get(MilStdAttributes.DrawAsIcon));
671                }
672
673                if (attributes.containsKey(MilStdAttributes.PixelSize))
674                {
675                    pixelSize = Integer.parseInt(attributes.get(MilStdAttributes.PixelSize));
676                }
677                else
678                {
679                    pixelSize = RendererSettings.getInstance().getDefaultPixelSize();
680                }
681                if(keepUnitRatio == true && msi.getSymbolSet() == SymbolID.SymbolSet_ControlMeasure && msi.getGeometry().equalsIgnoreCase("point"))
682                {
683                    if(msi.getDrawRule() == DrawRules.POINT1)//Action Points
684                        pixelSize = (int)Math.ceil((pixelSize/1.5f) * 2.0f);
685                    else
686                        pixelSize = (int)Math.ceil((pixelSize/1.5f) * 1.2f);
687                }
688
689                if(attributes.containsKey(MilStdAttributes.OutlineSymbol))
690                    drawCustomOutline = Boolean.parseBoolean(attributes.get(MilStdAttributes.OutlineSymbol));
691                else
692                    drawCustomOutline = RendererSettings.getInstance().getOutlineSPControlMeasures();
693
694                if(SymbolUtilities.isMultiPoint(symbolID))
695                    drawCustomOutline=false;//icon previews for multipoints do not need outlines since they shouldn't be on the map
696            }
697
698            if (drawAsIcon)//icon won't show modifiers or display icons
699            {
700                keepUnitRatio = false;
701                hasDisplayModifiers = false;
702                hasTextModifiers = false;
703                drawCustomOutline = false;
704            }
705            else
706            {
707                hasDisplayModifiers = ModifierRenderer.hasDisplayModifiers(symbolID, modifiers);
708                hasTextModifiers = ModifierRenderer.hasTextModifiers(symbolID, modifiers);
709            }
710
711            //Check if we need to set 'N' to "ENY"
712            int aff = SymbolID.getAffiliation(symbolID);
713            //int ss = msi.getSymbolSet();
714            if (ss == SymbolID.SymbolSet_ControlMeasure &&
715                    (aff == SymbolID.StandardIdentity_Affiliation_Hostile_Faker ||
716                 aff == SymbolID.StandardIdentity_Affiliation_Suspect_Joker ) &&
717                    modifiers.containsKey(Modifiers.N_HOSTILE) &&
718                    drawAsIcon == false)
719            {
720                modifiers.put(Modifiers.N_HOSTILE, "ENY");
721            }
722
723        }
724        catch (Exception excModifiers)
725        {
726            ErrorLogger.LogException("MilStdIconRenderer", "RenderSP", excModifiers);
727        }
728
729        try
730        {
731            ImageInfo ii = null;
732            int intFill = -1;
733            if (fillColor != null)
734            {
735                intFill = fillColor.toInt();
736            }
737
738
739            if(msi.getSymbolSet() != SymbolID.SymbolSet_ControlMeasure)
740                lineColor = Color.BLACK;//color isn't black but should be fine for weather since colors can't be user defined.
741
742
743
744            if (SymbolID.getSymbolSet(symbolID)==SymbolID.SymbolSet_ControlMeasure && SymbolID.getEntityCode(symbolID) == 270701)//static depiction
745            {
746                //add mine fill to image
747                mod1 = SymbolID.getModifier1(symbolID);
748                if (!(mod1 >= 13 && mod1 <= 50))
749                    symbolID = SymbolID.setModifier1(symbolID, 13);
750            }
751
752            String key = makeCacheKey(symbolID, lineColor.toInt(), intFill, pixelSize, keepUnitRatio, drawCustomOutline);
753
754            //see if it's in the cache
755            if(_tgCache != null) {
756                ii = _tgCache.get(key);
757                //safety check in case bitmaps are getting recycled while still in the LRU cache
758                if (ii != null && ii.getImage() != null && ii.getImage().isRecycled()) {
759                    synchronized (_SinglePointCacheMutex) {
760                        _tgCache.remove(key);
761                        ii = null;
762                    }
763                }
764            }
765
766            //if not, generate symbol.
767            if (ii == null)//*/
768            {
769                int version = SymbolID.getVersion(symbolID);
770                //check symbol size////////////////////////////////////////////
771                Rect rect = null;
772                iconID = SVGLookup.getMainIconID(symbolID);
773                siIcon = SVGLookup.getInstance().getSVGLInfo(iconID, version);
774                mod1ID = SVGLookup.getMod1ID(symbolID);
775                siMod1 = SVGLookup.getInstance().getSVGLInfo(mod1ID, version);
776                float borderPadding = 0;
777                if (drawCustomOutline) {
778                    borderPadding = RendererUtilities.findWidestStrokeWidth(siIcon.getSVG());
779                }
780                top = (int)Math.floor(siIcon.getBbox().top);
781                left = (int)Math.floor(siIcon.getBbox().left);
782                width = (int)Math.ceil(siIcon.getBbox().width() + (siIcon.getBbox().left - left));
783                height = (int)Math.ceil(siIcon.getBbox().height() + (siIcon.getBbox().top - top));
784                if(siIcon.getBbox().bottom > 400)
785                    svgStart = "<svg xmlns:svg=\"http://www.w3.org/2000/svg\" version=\"1.1\" viewBox=\"0 0 612 792\">";
786                else
787                    svgStart = "<svg xmlns:svg=\"http://www.w3.org/2000/svg\" version=\"1.1\" viewBox=\"0 0 400 400\">";
788
789                String strSVGIcon = null;
790
791
792                if(hasAPFill) //action points and a few others //Sonobuoy //ACP, CCP, PUP
793                {
794                    String apFill;
795                    if(fillColor != null)
796                        apFill = RendererUtilities.colorToHexString(fillColor,false);
797                    else
798                        apFill = RendererUtilities.colorToHexString(SymbolUtilities.getFillColorOfAffiliation(symbolID),false);
799                    siIcon = new SVGInfo(siIcon.getID(),siIcon.getBbox(), siIcon.getSVG().replaceAll("fill=\"none\"","fill=\"" + apFill + "\""));
800                }
801
802                //update line and fill color of frame SVG
803                if(msi.getSymbolSet() == SymbolID.SymbolSet_ControlMeasure && (lineColor != null || fillColor != null)) {
804                    if (drawCustomOutline) {
805                        // create outline with larger stroke-width first (if selected)
806                        strSVGIcon = RendererUtilities.setSVGSPCMColors(symbolID, siIcon.getSVG(), RendererUtilities.getIdealOutlineColor(lineColor), fillColor, true);
807                    }
808
809                    // append normal symbol SVG to be layered on top of outline
810                    strSVGIcon += RendererUtilities.setSVGSPCMColors(symbolID, siIcon.getSVG(), lineColor, fillColor, false);
811                }
812                else//weather symbol (don't change color of weather graphics)
813                    strSVGIcon = siIcon.getSVG();
814
815                //If symbol is Static Depiction, add internal mine graphic based on sector modifier 1
816                if(SymbolID.getEntityCode(symbolID) == 270701 && siMod1 != null)
817                {
818                    if (drawCustomOutline) {
819                        // create outline with larger stroke-width first (if selected)
820                        strSVGIcon += RendererUtilities.setSVGSPCMColors(mod1ID, siMod1.getSVG(), RendererUtilities.getIdealOutlineColor(RendererUtilities.getColorFromHexString("#00A651")), RendererUtilities.getColorFromHexString("#00A651"), true);
821                    }
822                    //strSVGIcon += siMod1.getSVG();
823                    strSVGIcon += RendererUtilities.setSVGSPCMColors(mod1ID, siMod1.getSVG(), lineColor, fillColor, false);
824                }
825
826                if (pixelSize > 0)
827                {
828                    symbolBounds = RectUtilities.makeRect(left,top,width,height);
829                    rect = new Rect(symbolBounds);
830
831                    //adjust size
832                    float p = pixelSize;
833                    float h = rect.height();
834                    float w = rect.width();
835
836                    ratio = Math.min((p / h), (p / w));
837
838                    symbolBounds = RectUtilities.makeRect(0f, 0f, w * ratio, h * ratio);
839
840                    //make sure border padding isn't excessive.
841                    w = symbolBounds.width();
842                    h = symbolBounds.height();
843
844                    if(h/(h+borderPadding) > 0.10)
845                    {
846                        borderPadding = (float)(h * 0.1);
847                    }
848                    else if(w/(w+borderPadding) > 0.10)
849                    {
850                        borderPadding = (float)(w * 0.1);
851                    }
852
853                }
854
855                //Draw glyphs to bitmap
856                Bitmap bmp = Bitmap.createBitmap((symbolBounds.width() + Math.round(borderPadding)), (symbolBounds.height() + Math.round(borderPadding)), Config.ARGB_8888);
857                Canvas canvas = new Canvas(bmp);
858
859                symbolBounds = new Rect(0, 0, bmp.getWidth(), bmp.getHeight());
860
861                //grow size SVG to accommodate the outline we added
862                int offset = 0;
863                if(drawCustomOutline)
864                {
865                    //TODO: maybe come up with a calculation vs just the #2, although it seems to work well.
866                    RectUtilities.grow(rect, 2);
867                    offset = 4;
868                }//*/
869
870
871                if(msi.getSymbolSet()==SymbolID.SymbolSet_ControlMeasure && msi.getDrawRule()==DrawRules.POINT1)//smooth out action points
872                    strSVGIcon = "/n<g stroke-linejoin=\"round\" >/n" + strSVGIcon + "/n</g>";
873
874                strSVG = svgStart + strSVGIcon + "</svg>";
875                mySVG = SVG.getFromString(strSVG);
876                //mySVG.setDocumentViewBox(left,top,width,height);
877                mySVG.setDocumentViewBox(rect.left,rect.top,rect.width(),rect.height());
878                mySVG.renderToCanvas(canvas);
879
880                Point centerPoint = SymbolUtilities.getCMSymbolAnchorPoint(symbolID,new RectF(offset, offset, symbolBounds.right, symbolBounds.bottom));
881
882                ii = new ImageInfo(bmp, centerPoint, symbolBounds);
883
884                if(cacheEnabled && drawAsIcon == false && bmp.getAllocationByteCount() <= maxCachedEntrySize)
885                {
886                    synchronized (_SinglePointCacheMutex)
887                    {
888                        if(_tgCache.get(key) == null)
889                            _tgCache.put(key, ii);
890                    }
891                }
892                /*if (drawAsIcon == false && pixelSize <= 100)
893
894                    _tgCache.put(key, ii);
895                }//*/
896            }
897
898            //Process Modifiers
899            ImageInfo iiNew = null;
900            if (drawAsIcon == false && (hasTextModifiers || hasDisplayModifiers))
901            {
902                SymbolDimensionInfo sdiTemp = null;
903                if (SymbolUtilities.isSPWithSpecialModifierLayout(symbolID))//(SymbolUtilitiesD.isTGSPWithSpecialModifierLayout(symbolID))
904                {
905                    sdiTemp = ModifierRenderer.ProcessTGSPWithSpecialModifierLayout(ii, symbolID, modifiers, attributes, lineColor);
906                }
907                else
908                {
909                    sdiTemp = ModifierRenderer.ProcessTGSPModifiers(ii, symbolID, modifiers, attributes, lineColor);
910                }
911                iiNew = (sdiTemp instanceof ImageInfo ? (ImageInfo)sdiTemp : null);
912            }
913
914            if (iiNew != null)
915            {
916                ii = iiNew;
917            }
918
919            //cleanup
920            //bmp.recycle();
921            symbolBounds = null;
922            fullBMP = null;
923            fullBounds = null;
924            mySVG = null;
925
926
927            if (drawAsIcon)
928            {
929                return ii.getSquareImageInfo();
930            }
931            else
932            {
933                return ii;
934            }
935
936        }
937        catch (Exception exc)
938        {
939            ErrorLogger.LogException("MilStdIconRenderer", "RenderSP", exc);
940        }
941        return null;
942    }
943
944
945    /**
946     *
947     * @param symbolID
948     * @return
949     */
950    @SuppressWarnings("unused")
951    public ImageInfo RenderModifier(String symbolID, Map<String,String> attributes)
952    {
953        ImageInfo temp = null;
954        String basicSymbolID = null;
955
956        Color lineColor = null;
957        Color fillColor = null;//SymbolUtilities.getFillColorOfAffiliation(symbolID);
958
959        int alpha = -1;
960
961
962        //SVG rendering variables
963        MSInfo msi = null;
964        String iconID = null;
965        SVGInfo siIcon = null;
966        int top = 0;
967        int left = 0;
968        int width = 0;
969        int height = 0;
970        String svgStart = null;
971        String strSVG = null;
972        SVG mySVG = null;
973
974        float ratio = 0;
975
976        Rect symbolBounds = null;
977        RectF fullBounds = null;
978        Bitmap fullBMP = null;
979
980        boolean drawAsIcon = false;
981        int pixelSize = -1;
982        boolean keepUnitRatio = true;
983        boolean hasDisplayModifiers = false;
984        boolean hasTextModifiers = false;
985        int symbolOutlineWidth = RendererSettings.getInstance().getSinglePointSymbolOutlineWidth();
986        boolean drawCustomOutline = false;
987
988        try
989        {
990
991            msi = MSLookup.getInstance().getMSLInfo(symbolID);
992            if (attributes != null)
993            {
994                if (attributes.containsKey(MilStdAttributes.KeepUnitRatio))
995                {
996                    keepUnitRatio = Boolean.parseBoolean(attributes.get(MilStdAttributes.KeepUnitRatio));
997                }
998
999                if (attributes.containsKey(MilStdAttributes.LineColor))
1000                {
1001                    lineColor = RendererUtilities.getColorFromHexString(attributes.get(MilStdAttributes.LineColor));
1002                }
1003
1004                if (attributes.containsKey(MilStdAttributes.FillColor))
1005                {
1006                    fillColor = RendererUtilities.getColorFromHexString(attributes.get(MilStdAttributes.FillColor));
1007                }
1008
1009                if (attributes.containsKey(MilStdAttributes.Alpha))
1010                {
1011                    alpha = Integer.parseInt(attributes.get(MilStdAttributes.Alpha));
1012                }
1013
1014                if (attributes.containsKey(MilStdAttributes.DrawAsIcon))
1015                {
1016                    drawAsIcon = Boolean.parseBoolean(attributes.get(MilStdAttributes.DrawAsIcon));
1017                }
1018
1019                if (attributes.containsKey(MilStdAttributes.PixelSize))
1020                {
1021                    pixelSize = Integer.parseInt(attributes.get(MilStdAttributes.PixelSize));
1022                    if(msi.getSymbolSet() == SymbolID.SymbolSet_ControlMeasure)
1023                    {
1024                        if(SymbolID.getEntityCode(symbolID)==270701)//static depiction
1025                            pixelSize = (int)(pixelSize * 0.9);//try to scale to be somewhat in line with units
1026                    }
1027                }
1028
1029                if(drawAsIcon==false)//don't outline icons because they're not going on the map
1030                {
1031                    if(attributes.containsKey(MilStdAttributes.OutlineSymbol))
1032                        drawCustomOutline = Boolean.parseBoolean(attributes.get(MilStdAttributes.OutlineSymbol));
1033                    else
1034                        drawCustomOutline = RendererSettings.getInstance().getOutlineSPControlMeasures();
1035                }
1036
1037                if(SymbolUtilities.isMultiPoint(symbolID))
1038                    drawCustomOutline=false;//icon previews for multipoints do not need outlines since they shouldn't be on the map
1039
1040                /*if (attributes.containsKey(MilStdAttributes.OutlineWidth)>=0)
1041                 symbolOutlineWidth = Integer.parseInt(attributes.get(MilStdAttributes.OutlineWidth));//*/
1042            }
1043
1044            int outlineOffset = symbolOutlineWidth;
1045            if (drawCustomOutline && outlineOffset > 2)
1046            {
1047                outlineOffset = (outlineOffset - 1) / 2;
1048            }
1049            else
1050            {
1051                outlineOffset = 0;
1052            }
1053
1054        }
1055        catch (Exception excModifiers)
1056        {
1057            ErrorLogger.LogException("MilStdIconRenderer", "RenderSP", excModifiers);
1058        }
1059
1060        try
1061        {
1062            ImageInfo ii = null;
1063            int intFill = -1;
1064            if (fillColor != null)
1065            {
1066                intFill = fillColor.toInt();
1067            }
1068
1069
1070            if(msi.getSymbolSet() != SymbolID.SymbolSet_ControlMeasure)
1071                lineColor = Color.BLACK;//color isn't black but should be fine for weather since colors can't be user defined.
1072
1073
1074            //if not, generate symbol
1075            if (ii == null)//*/
1076            {
1077                int version = SymbolID.getVersion(symbolID);
1078                //check symbol size////////////////////////////////////////////
1079                Rect rect = null;
1080
1081                iconID = SVGLookup.getMod1ID(symbolID);
1082                siIcon = SVGLookup.getInstance().getSVGLInfo(iconID, version);
1083                top = Math.round(siIcon.getBbox().top);
1084                left = Math.round(siIcon.getBbox().left);
1085                width = Math.round(siIcon.getBbox().width());
1086                height = Math.round(siIcon.getBbox().height());
1087                if(siIcon.getBbox().bottom > 400)
1088                    svgStart = "<svg xmlns:svg=\"http://www.w3.org/2000/svg\" version=\"1.1\" viewBox=\"0 0 612 792\">";
1089                else
1090                    svgStart = "<svg xmlns:svg=\"http://www.w3.org/2000/svg\" version=\"1.1\" viewBox=\"0 0 400 400\">";
1091
1092                String strSVGIcon = null;
1093                String strSVGOutline = null;
1094
1095                //update line and fill color of frame SVG
1096                if(msi.getSymbolSet() == SymbolID.SymbolSet_ControlMeasure && (lineColor != null || fillColor != null))
1097                    strSVGIcon = RendererUtilities.setSVGFrameColors(symbolID,siIcon.getSVG(),lineColor,fillColor);
1098                else
1099                    strSVGIcon = siIcon.getSVG();
1100
1101                if (pixelSize > 0)
1102                {
1103                    symbolBounds = RectUtilities.makeRect(left,top,width,height);
1104                    rect = new Rect(symbolBounds);
1105
1106                    //adjust size
1107                    float p = pixelSize;
1108                    float h = rect.height();
1109                    float w = rect.width();
1110
1111                    ratio = Math.min((p / h), (p / w));
1112
1113                    symbolBounds = RectUtilities.makeRect(0f, 0f, w * ratio, h * ratio);
1114
1115                }
1116
1117
1118                //TODO: figure out how to draw an outline and adjust the symbol bounds accordingly
1119
1120                //Draw glyphs to bitmap
1121                Bitmap bmp = Bitmap.createBitmap((symbolBounds.width()), (symbolBounds.height()), Config.ARGB_8888);
1122                Canvas canvas = new Canvas(bmp);
1123
1124                symbolBounds = new Rect(0, 0, bmp.getWidth(), bmp.getHeight());
1125
1126                strSVG = svgStart + strSVGIcon + "</svg>";
1127                mySVG = SVG.getFromString(strSVG);
1128                mySVG.setDocumentViewBox(left,top,width,height);
1129                mySVG.renderToCanvas(canvas);
1130
1131                Point centerPoint = SymbolUtilities.getCMSymbolAnchorPoint(symbolID,new RectF(0, 0, symbolBounds.right, symbolBounds.bottom));
1132
1133                ii = new ImageInfo(bmp, centerPoint, symbolBounds);
1134
1135
1136                /*if (drawAsIcon == false && pixelSize <= 100)
1137                {
1138                    _tgCache.put(key, ii);
1139                }//*/
1140            }
1141
1142
1143            //cleanup
1144            //bmp.recycle();
1145            symbolBounds = null;
1146            fullBMP = null;
1147            fullBounds = null;
1148            mySVG = null;
1149
1150
1151            if (drawAsIcon)
1152            {
1153                return ii.getSquareImageInfo();
1154            }
1155            else
1156            {
1157                return ii;
1158            }
1159
1160        }
1161        catch (Exception exc)
1162        {
1163            ErrorLogger.LogException("MilStdIconRenderer", "RenderSP", exc);
1164        }
1165        return null;
1166    }
1167
1168
1169
1170    /**
1171     *
1172     * @param symbolID
1173     * @param lineColor
1174     * @param fillColor
1175     * @param size
1176     * @param keepUnitRatio
1177     * @param drawOutline (only for single-point Control Measures)
1178     * @return
1179     */
1180    private static String makeCacheKey(String symbolID, int lineColor, int fillColor, int size, boolean keepUnitRatio, boolean drawOutline)
1181    {
1182        return makeCacheKey(symbolID, lineColor, fillColor, "null",size, keepUnitRatio, false);
1183    }
1184
1185    private static String makeCacheKey(String symbolID, int lineColor, int fillColor, String iconColor, int size, boolean keepUnitRatio, boolean drawOutline)
1186    {
1187        //String key = symbolID.substring(0, 20) + String.valueOf(lineColor) + String.valueOf(fillColor) + String.valueOf(size) + String.valueOf(keepUnitRatio);
1188        String key = symbolID.substring(0, 7) + symbolID.substring(10, 20) + SymbolID.getFrameShape(symbolID) + lineColor + fillColor + iconColor + size + keepUnitRatio + drawOutline;
1189        return key;
1190    }
1191
1192    public void logError(String tag, Throwable thrown)
1193    {
1194        if (tag == null || tag.equals(""))
1195        {
1196            tag = "singlePointRenderer";
1197        }
1198
1199        String message = thrown.getMessage();
1200        String stack = getStackTrace(thrown);
1201        if (message != null)
1202        {
1203            Log.e(tag, message);
1204        }
1205        if (stack != null)
1206        {
1207            Log.e(tag, stack);
1208        }
1209    }
1210
1211    public String getStackTrace(Throwable thrown)
1212    {
1213        try
1214        {
1215            if (thrown != null)
1216            {
1217                if (thrown.getStackTrace() != null)
1218                {
1219                    String eol = System.getProperty("line.separator");
1220                    StringBuilder sb = new StringBuilder();
1221                    sb.append(thrown.toString());
1222                    sb.append(eol);
1223                    for (StackTraceElement element : thrown.getStackTrace())
1224                    {
1225                        sb.append("        at ");
1226                        sb.append(element);
1227                        sb.append(eol);
1228                    }
1229                    return sb.toString();
1230                }
1231                else
1232                {
1233                    return thrown.getMessage() + "- no stack trace";
1234                }
1235            }
1236            else
1237            {
1238                return "no stack trace";
1239            }
1240        }
1241        catch (Exception exc)
1242        {
1243            Log.e("getStackTrace", exc.getMessage());
1244        }
1245        return thrown.getMessage();
1246    }//
1247
1248    /*
1249     private static String PrintList(ArrayList list)
1250     {
1251     String message = "";
1252     for(Object item : list)
1253     {
1254
1255     message += item.toString() + "\n";
1256     }
1257     return message;
1258     }//*/
1259    /*
1260     private static String PrintObjectMap(Map<String, Object> map)
1261     {
1262     Iterator<Object> itr = map.values().iterator();
1263     String message = "";
1264     String temp = null;
1265     while(itr.hasNext())
1266     {
1267     temp = String.valueOf(itr.next());
1268     if(temp != null)
1269     message += temp + "\n";
1270     }
1271     //ErrorLogger.LogMessage(message);
1272     return message;
1273     }//*/
1274    @Override
1275    public void onSettingsChanged(SettingsChangedEvent sce)
1276    {
1277
1278        if(sce != null && sce.getEventType().equals(SettingsChangedEvent.EventType_FontChanged))
1279        {
1280            synchronized (_modifierFontMutex)
1281            {
1282                _modifierFont = RendererSettings.getInstance().getModiferFont();
1283                _modifierOutlineFont = RendererSettings.getInstance().getModiferFont();
1284                FontMetrics fm = new FontMetrics();
1285                fm = _modifierFont.getFontMetrics();
1286                _modifierDescent = fm.descent;
1287                //_modifierFontHeight = fm.top + fm.bottom;
1288                _modifierFontHeight = fm.bottom - fm.top;
1289
1290                _modifierFont.setStrokeWidth(RendererSettings.getInstance().getTextOutlineWidth());
1291                _modifierOutlineFont.setColor(Color.white.toInt());
1292                _deviceDPI = RendererSettings.getInstance().getDeviceDPI();
1293
1294                ModifierRenderer.setModifierFont(_modifierFont, _modifierFontHeight, _modifierDescent);
1295
1296            }
1297        }
1298
1299        if(sce != null && sce.getEventType().equals(SettingsChangedEvent.EventType_CacheSizeChanged))
1300        {
1301
1302            int cSize = RendererSettings.getInstance().getCacheSize()/2;
1303            //adjust unit cache
1304            if(cSize != cacheSize) {
1305                cacheSize = cSize;
1306                if (cacheSize >= 5)
1307                    maxCachedEntrySize = cacheSize / 5;
1308                else
1309                    maxCachedEntrySize = 1;
1310
1311                if(cacheEnabled) //if cache enabled, update cache
1312                {
1313
1314                    synchronized (_UnitCacheMutex) {
1315                        if(_unitCache != null)
1316                            _unitCache.evictAll();
1317                        _unitCache = new LruCache<String, ImageInfo>(cSize) {
1318                            @Override
1319                            protected int sizeOf(String key, ImageInfo ii) {
1320                                return ii.getByteCount();// / 1024;
1321                            }
1322                        };
1323                    }
1324                    //adjust tg cache
1325                    synchronized (_SinglePointCacheMutex) {
1326                        if(_tgCache != null)
1327                            _tgCache.evictAll();
1328                        _tgCache = new LruCache<String, ImageInfo>(cSize) {
1329                            @Override
1330                            protected int sizeOf(String key, ImageInfo ii) {
1331                                return ii.getByteCount();// / 1024;
1332                            }
1333                        };
1334                    }
1335                }
1336            }
1337        }
1338        if(sce != null && sce.getEventType().equals(SettingsChangedEvent.EventType_CacheToggled))
1339        {
1340            if(cacheEnabled != RendererSettings.getInstance().getCacheEnabled())
1341            {
1342                cacheEnabled = RendererSettings.getInstance().getCacheEnabled();
1343
1344                if (cacheEnabled == false)
1345                {
1346                    synchronized (_SinglePointCacheMutex)
1347                    {
1348                        if (_tgCache != null)
1349                            _tgCache.evictAll();
1350                        _tgCache = null;
1351                    }
1352                    synchronized (_UnitCacheMutex)
1353                    {
1354                        if (_unitCache != null)
1355                            _unitCache.evictAll();
1356                        _unitCache = null;
1357                    }
1358                }
1359                else
1360                {
1361                    int cSize = RendererSettings.getInstance().getCacheSize() / 2;
1362                    synchronized (_SinglePointCacheMutex)
1363                    {
1364                        if(_tgCache != null)
1365                            _tgCache.evictAll();
1366                        _tgCache = new LruCache<String, ImageInfo>(cSize) {
1367                            @Override
1368                            protected int sizeOf(String key, ImageInfo ii) {
1369                                return ii.getByteCount();// / 1024;
1370                            }
1371                        };
1372                    }
1373                    synchronized (_UnitCacheMutex)
1374                    {
1375                        if(_unitCache != null)
1376                            _unitCache.evictAll();
1377                        _unitCache = new LruCache<String, ImageInfo>(cSize) {
1378                            @Override
1379                            protected int sizeOf(String key, ImageInfo ii) {
1380                                return ii.getByteCount();// / 1024;
1381                            }
1382                        };
1383                    }
1384                }
1385            }
1386        }
1387    }
1388}