001package squidpony.squidgrid.gui.gdx;
002
003import com.badlogic.gdx.Gdx;
004import com.badlogic.gdx.assets.AssetDescriptor;
005import com.badlogic.gdx.assets.AssetManager;
006import com.badlogic.gdx.graphics.Color;
007import com.badlogic.gdx.graphics.Pixmap;
008import com.badlogic.gdx.graphics.Texture;
009import com.badlogic.gdx.graphics.g2d.Batch;
010import com.badlogic.gdx.graphics.g2d.BitmapFont;
011import com.badlogic.gdx.graphics.g2d.TextureRegion;
012import com.badlogic.gdx.graphics.glutils.ShaderProgram;
013import com.badlogic.gdx.scenes.scene2d.Actor;
014import com.badlogic.gdx.scenes.scene2d.ui.Image;
015import com.badlogic.gdx.scenes.scene2d.ui.Label;
016import com.badlogic.gdx.utils.Align;
017import com.badlogic.gdx.utils.Disposable;
018import regexodus.ds.CharCharMap;
019import squidpony.IColorCenter;
020import squidpony.Maker;
021import squidpony.StringKit;
022import squidpony.squidmath.NumberTools;
023import squidpony.squidmath.OrderedMap;
024
025import java.util.ArrayList;
026import java.util.Collection;
027
028/**
029 * Class for creating text blocks.
030 *
031 * This class defaults to having no padding and having no font set. You can use a
032 * default square or narrow font by calling the appropriate method, or set the font
033 * to any AngelCode bitmap font on the classpath (typically in libGDX, this would be
034 * in the assets folder; these fonts can be created by Hiero in the libGDX tools,
035 * see https://github.com/libgdx/libgdx/wiki/Hiero for more)
036 *
037 * After all settings are set, one of the initialization methods must be called
038 * before the factory can be used.
039 *
040 * In order to easily support Unicode, strings are treated as a series of code
041 * points.
042 *
043 * All images have transparent backgrounds.
044 *
045 * @author Eben Howard - http://squidpony.com - howard@squidpony.com
046 * @author Tommy Ettinger
047 */
048public class TextCellFactory implements Disposable {
049
050    /**
051     * How many styles are supported by this TextCellFactory; always 1 unless changed in a subclass.
052     */
053    public int supportedStyles() {
054        return 1;
055    }
056    /**
057     * The commonly used symbols in roguelike games.
058     */
059    public static final String DEFAULT_FITTING =
060            " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";
061    /**
062     * All symbols that squidlib-util permits itself to generate; this is a shared subset across most of the larger
063     * fonts in {@link DefaultResources}.
064     */
065    public static final String SQUID_FITTING = StringKit.PERMISSIBLE_CHARS;
066    /**
067     * The {@link AssetManager} from where to load the font. Use it to share
068     * loading of a font's file across multiple factories.
069     */
070    protected /* Nullable */AssetManager assetManager;
071    public BitmapFont bmpFont;
072    protected TextureRegion block;
073    protected TextureRegion dirMarker;
074    protected String fitting = SQUID_FITTING;
075    protected IColorCenter<Color> scc;
076    protected int leftPadding, rightPadding, topPadding, bottomPadding;
077    protected float width = 1, height = 1;
078    public float actualCellWidth = 1, actualCellHeight = 1;
079    protected float distanceFieldScaleX = 36f, distanceFieldScaleY = 36f;
080    protected boolean initialized, initializedByFont, initializedBySize;
081    protected boolean distanceField;
082    protected boolean msdf;
083    /**
084     * For distance field and MSDF fonts, this is the ShaderProgram that will produce the intended effect.
085     * Usually should not be changed manually unless you know what you are doing.
086     */
087    public ShaderProgram shader;
088    protected float smoothingMultiplier = 1.2f;
089    protected float descent, lineHeight;
090    protected Label.LabelStyle style;
091    protected CharCharMap swap = new CharCharMap(8);
092    {
093        swap.defaultReturnValue('\uffff');
094    }
095    protected char directionGlyph = '^';
096    protected OrderedMap<Character, TextureRegion> glyphTextures = new OrderedMap<>(16);
097
098    protected StringBuilder mut = new StringBuilder(1).append('\0');
099
100    /**
101     * Creates a default valued factory. One of the initialization methods must
102     * be called before this factory can be used!
103     */
104    public TextCellFactory() {
105        this(null);
106    }
107
108    /**
109     * A default valued factory that uses the given {@link AssetManager} to load
110     * the font file. Use this constructor if you are likely to load the same
111     * font over and over (recall that, without an {@link AssetManager}, each
112     * instance of {@link TextCellFactory} will load its font from disk). This
113     * primarily matters if you are using fonts not bundled with SquidLib, since
114     * accessing a BitmapFont with a method (not a String) from DefaultResources
115     * caches the BitmapFont already.
116     *
117     * @param assetManager an ordinary libGDX AssetManager
118     */
119    public TextCellFactory(/* Nullable */ AssetManager assetManager) {
120        this.assetManager = assetManager;
121        scc = DefaultResources.getSCC();
122        swap.put('\u0006', ' ');
123    }
124
125    public TextCellFactory copy()
126    {
127        TextCellFactory next = new TextCellFactory(assetManager);
128        if(bmpFont == null)
129            bmpFont = DefaultResources.getIncludedFont();
130        next.bmpFont = DefaultResources.copyFont(bmpFont);
131        next.block = block;
132        next.swap = swap.clone(); // explicitly implemented by CharCharMap
133        next.swap.defaultReturnValue('\uffff'); // ... but it forgets to copy this field
134        next.distanceField = distanceField;
135        next.msdf = msdf;
136        next.distanceFieldScaleX = distanceFieldScaleX;
137        next.distanceFieldScaleY = distanceFieldScaleY;
138        next.shader = null;
139        next.fitting = fitting;
140        next.height = height;
141        next.width = width;
142        next.actualCellWidth = actualCellWidth;
143        next.actualCellHeight = actualCellHeight;
144        next.descent = descent;
145        next.lineHeight = lineHeight;
146        next.smoothingMultiplier = smoothingMultiplier;
147        next.scc = scc;
148        next.directionGlyph = directionGlyph;
149        if(initializedBySize)
150            next.initBySize();
151        else if(initializedByFont)
152            next.initByFont();
153        return next;
154    }
155    /**
156     * Initializes the factory to then be able to create text cells on demand.
157     * <br>
158     * Will match the width and height to the space width of the font and the
159     * line height of the font, respectively. Calling this after the factory
160     * has already been initialized will re-initialize it. This will not work
161     * with distance field or MSDF fonts; for those, use {@link #initBySize()}.
162     *
163     * @return this for method chaining
164     */
165    public TextCellFactory initByFont() {
166        if(bmpFont == null)
167            bmpFont = DefaultResources.getIncludedFont();
168        bmpFont.setFixedWidthGlyphs(fitting);
169        width = bmpFont.getSpaceXadvance();
170        lineHeight = bmpFont.getLineHeight();
171        height = (lineHeight);
172        descent = bmpFont.getDescent();
173
174        actualCellWidth = width;
175        actualCellHeight = height;
176        BitmapFont.Glyph g = bmpFont.getData().missingGlyph;
177        if(g != null)
178        {
179            block = new TextureRegion(bmpFont.getRegion(), g.srcX + 1, g.srcY + 1, 1, 1);
180        }
181        else {
182            Pixmap temp = new Pixmap(1, 1, Pixmap.Format.RGBA8888);
183            temp.setColor(Color.WHITE);
184            temp.fill();
185            Texture white = new Texture(1, 1, Pixmap.Format.RGBA8888);
186            white.draw(temp, 0, 0);
187            block = new TextureRegion(white);
188            temp.dispose();
189        }
190        style = new Label.LabelStyle(bmpFont, null);
191        BitmapFont.Glyph dg = bmpFont.getData().getGlyph(directionGlyph);
192        if(dg != null)
193            dirMarker = new TextureRegion(bmpFont.getRegion(dg.page), dg.srcX, dg.srcY, dg.width, dg.height);
194        initialized = true;
195        initializedByFont = true;
196        return this;
197    }
198
199    /**
200     * Initializes the factory to then be able to create text cells on demand.
201     * <br>
202     * Will strictly use the provided width and height values to size the cells.
203     * Calling this after the factory has already been initialized will
204     * re-initialize it, but will re-create several objects (including compiling
205     * a ShaderProgram, which can be very challenging for a GPU to do each frame).
206     * If you need to re-initialize often to adjust size, use {@link #resetSize()},
207     * which does not allocate new objects if this was already initialized, and
208     * will just delegate to this method if this wasn't initialized.
209     *
210     * @return this for method chaining
211     */
212    public TextCellFactory initBySize() {
213        if(bmpFont == null)
214            bmpFont = DefaultResources.getIncludedFont();
215        BitmapFont.Glyph g = bmpFont.getData().missingGlyph;
216        if(g != null)
217        {
218            block = new TextureRegion(bmpFont.getRegion(), g.srcX + 1, g.srcY + 1, 1, 1);
219        }
220        else {
221            Pixmap temp = new Pixmap(1, 1, Pixmap.Format.RGBA8888);
222            temp.setColor(Color.WHITE);
223            temp.fill();
224            Texture white = new Texture(1, 1, Pixmap.Format.RGBA8888);
225            white.draw(temp, 0, 0);
226            block = new TextureRegion(white);
227            temp.dispose();
228        }
229        if(msdf)
230        {
231            bmpFont.getData().setScale(width / distanceFieldScaleX, height / distanceFieldScaleY);
232
233            shader = new ShaderProgram(DefaultResources.vertexShader, DefaultResources.msdfFragmentShader);
234            if (!shader.isCompiled()) {
235                Gdx.app.error("shader", "Distance Field font shader compilation failed:\n" + shader.getLog());
236            }
237            //lineTweak = lineHeight / 20f;
238            //distanceFieldScaleX *= (((float)width) / height) / (distanceFieldScaleX / distanceFieldScaleY);
239        }
240        else if(distanceField)
241        {
242            bmpFont.getData().setScale(width / distanceFieldScaleX, height / distanceFieldScaleY);
243
244            shader = new ShaderProgram(DefaultResources.vertexShader, DefaultResources.fragmentShader);
245            if (!shader.isCompiled()) {
246                Gdx.app.error("shader", "Distance Field font shader compilation failed:\n" + shader.getLog());
247            }
248        }
249        else {
250            shader = FilterBatch.createDefaultShader();
251        }
252        lineHeight = bmpFont.getLineHeight();
253        descent = bmpFont.getDescent();
254        style = new Label.LabelStyle(bmpFont, null);
255        BitmapFont.Glyph dg = bmpFont.getData().getGlyph(directionGlyph);
256        if(dg != null)
257            dirMarker = new TextureRegion(bmpFont.getRegion(dg.page), dg.srcX, dg.srcY, dg.width, dg.height);
258        initialized = true;
259        initializedBySize = true;
260        return this;
261    }
262
263    /**
264     * Acts like calling {@link #initBySize()}, but doesn't create new ShaderPrograms or other objects if this has
265     * already been initialized. If this has not been initialized, simply returns initBySize(). This method is safe to
266     * call every frame if the font size continually changes, where initBySize() is not.
267     * @return this for chaining
268     */
269    public TextCellFactory resetSize()
270    {
271        return resetSize(width, height);
272    }
273
274    /**
275     * Acts like calling {@link #width(float)}, {@link #height(float)}, and {@link #initBySize()} in succession, but
276     * doesn't create new ShaderPrograms or other objects if this has already been initialized. If this has not been
277     * initialized, calls width() and height() and then simply returns initBySize(). This method is safe to call every
278     * frame if the font size continually changes, where initBySize() is not.
279     * @param newWidth the new width of a single cell, as a float that usually corresponds to pixels
280     * @param newHeight the new height of a single cell, as a float that usually corresponds to pixels
281     * @return this for chaining
282     */
283    public TextCellFactory resetSize(final float newWidth, final float newHeight)
284    {
285        width(newWidth);
286        height(newHeight);
287        if(!initialized)
288            return initBySize();
289        if(msdf || distanceField)
290        {
291            bmpFont.getData().setScale(width / distanceFieldScaleX, height / distanceFieldScaleY);
292        }
293        lineHeight = bmpFont.getLineHeight();
294        descent = bmpFont.getDescent();
295
296        return this;
297    }
298
299    /**
300     * Identical to {@link #initBySize()}.
301     *
302     * @see #initBySize() The docs for initBySize() apply here.
303     * @return this for method chaining
304     */
305    public TextCellFactory initVerbatim() {
306        return initBySize();
307    }
308
309    /**
310     * Returns the {@link BitmapFont} used by this factory.
311     *
312     * @return the BitmapFont this uses
313     */
314    public BitmapFont font() {
315        return bmpFont;
316    }
317
318    /**
319     * Sets this factory to use the provided font.
320     *
321     * This is a way to complete a needed step; the font must be set before initializing, which can be done by a few
322     * methods in this class.
323     *
324     * This should be called with an argument such as "Rogue-Zodiac-6x12.fnt", that is, it should have the .fnt
325     * extension as opposed to the .png that accompanies such a bitmap font. The bitmap font should be either in the
326     * internal folder that libGDX knows about, which means it is in the assets folder of your project usually.
327     * These BitmapFont resources are already known by SquidLib, but may need separate downloads (detailed in the linked
328     * DefaultResources documentation):
329     * <ul>
330     *     <li>{@link DefaultResources#getDefaultFont()} = "Zodiac-Square-12x12.fnt"</li>
331     *     <li>{@link DefaultResources#getDefaultNarrowFont()} = "Rogue-Zodiac-6x12.fnt"</li>
332     *     <li>{@link DefaultResources#getDefaultUnicodeFont()} = "Mandrill-6x16.fnt"</li>
333     *     <li>{@link DefaultResources#getSmoothFont()} = "Inconsolata-LGC-8x18.fnt"</li>
334     *     <li>{@link DefaultResources#getLargeFont()} = "Zodiac-Square-24x24.fnt"</li>
335     *     <li>{@link DefaultResources#getLargeNarrowFont()} = "Rogue-Zodiac-12x24.fnt"</li>
336     *     <li>{@link DefaultResources#getLargeUnicodeFont()} = "Mandrill-12x32.fnt"</li>
337     *     <li>{@link DefaultResources#getLargeSmoothFont()} = "Inconsolata-LGC-12x24.fnt"</li>
338     *     <li>{@link DefaultResources#getExtraLargeNarrowFont()} = "Rogue-Zodiac-18x36.fnt"</li>
339     * </ul>
340     * "Rogue-Zodiac-12x24.fnt", which is easily accessed by the field DefaultResources.narrowNameLarge , can also
341     * be set using TextCellFactory.defaultNarrowFont() instead of font(). "Zodiac-Square-12x12.fnt", also accessible
342     * as DefaultResources.squareName , can be set using TextCellFactory.defaultSquareFont() instead of font().
343     * "Inconsolata-LGC-12x24.fnt", also accessible as DefaultResources.smoothNameLarge , can be set using
344     * TextCellFactory.defaultFont() instead of font(). All three of these alternatives will cache the BitmapFont if
345     * the same one is requested later, but this font() method will not.
346     * <br>
347     * See https://github.com/libgdx/libgdx/wiki/Hiero for some ways to create a bitmap font this can use. Several fonts
348     * in this list were created using Hiero (not Hiero4), and several were created with AngelCode's BMFont tool.
349     *
350     * @param fontpath the path to the font to use
351     * @return this factory for method chaining
352     */
353    public TextCellFactory font(String fontpath) {
354        if (assetManager == null) {
355            if (Gdx.files.internal(fontpath).exists())
356                bmpFont = new BitmapFont(Gdx.files.internal(fontpath));
357            else if (Gdx.files.classpath(fontpath).exists())
358                bmpFont = new BitmapFont(Gdx.files.classpath(fontpath));
359            else
360                bmpFont = DefaultResources.getIncludedFont();
361        }
362        else {
363            assetManager.load(new AssetDescriptor<>(fontpath, BitmapFont.class));
364            /*
365             * We're using the AssetManager not be asynchronous, but to avoid
366             * loading a file twice (because that takes some time (tens of
367             * milliseconds)). Hence this KISS code to avoid having to handle a
368             * not-yet-loaded font:
369             */
370            assetManager.finishLoading();
371            bmpFont = assetManager.get(fontpath, BitmapFont.class);
372        }
373        return this;
374    }
375    /**
376     * Sets this factory to use the provided BitmapFont as its font without re-constructing anything.
377     *
378     * This is a way to complete a needed step; the font must be set before initializing, which can be done by a few
379     * methods in this class.
380     *
381     * This should be called with an argument such as {@code DefaultResources.getDefaultFont()} or any other variable
382     * with BitmapFont as its type. The bitmap font will not be loaded from file with this method, which it would be if
383     * you called the overload of font() that takes a String more than once. These BitmapFont resources are already
384     * known by SquidLib, but may need separate downloads (detailed in the linked DefaultResources documentation):
385     * <ul>
386     *     <li>{@link DefaultResources#getDefaultFont()} = "Zodiac-Square-12x12.fnt"</li>
387     *     <li>{@link DefaultResources#getDefaultNarrowFont()} = "Rogue-Zodiac-6x12.fnt"</li>
388     *     <li>{@link DefaultResources#getDefaultUnicodeFont()} = "Mandrill-6x16.fnt"</li>
389     *     <li>{@link DefaultResources#getSmoothFont()} = "Inconsolata-LGC-8x18.fnt"</li>
390     *     <li>{@link DefaultResources#getLargeFont()} = "Zodiac-Square-24x24.fnt"</li>
391     *     <li>{@link DefaultResources#getLargeNarrowFont()} = "Rogue-Zodiac-12x24.fnt"</li>
392     *     <li>{@link DefaultResources#getLargeUnicodeFont()} = "Mandrill-12x32.fnt"</li>
393     *     <li>{@link DefaultResources#getLargeSmoothFont()} = "Inconsolata-LGC-12x24.fnt"</li>
394     *     <li>{@link DefaultResources#getExtraLargeNarrowFont()} = "Rogue-Zodiac-18x36.fnt"</li>
395     * </ul>
396     * "Rogue-Zodiac-12x24.fnt", which is easily accessed by the method DefaultResources.getLargeNarrowFont() , can also
397     * be set using TextCellFactory.defaultNarrowFont() instead of font(). "Zodiac-Square-12x12.fnt", also accessible
398     * with DefaultResources.getDefaultFont() , can be set using TextCellFactory.defaultSquareFont() instead of font().
399     * "Inconsolata-LGC-12x24.fnt", also accessible with DefaultResources.getLargeSmoothFont() , can be set using
400     * TextCellFactory.defaultFont() instead of font(). All three of these alternatives will cache the BitmapFont if
401     * the same one is requested later, but this font() method will not.
402     * <br>
403     * See https://github.com/libgdx/libgdx/wiki/Hiero for some ways to create a bitmap font this can use. Several fonts
404     * in this list were created using Hiero (not Hiero4), and several were created with AngelCode's BMFont tool.
405     *
406     * @param bitmapFont the BitmapFont this should use
407     * @return this factory for method chaining
408     */
409    public TextCellFactory font(BitmapFont bitmapFont) {
410        if (bitmapFont == null) {
411            bmpFont = DefaultResources.getIncludedFont();
412        }
413        else {
414            bmpFont = bitmapFont;
415        }
416        return this;
417    }
418
419    /**
420     * Sets the font to a distance field font with the given String path to a .fnt file and String path to a texture.
421     * Distance field fonts should scale cleanly to multiple resolutions without artifacts. Does not use AssetManager
422     * since you shouldn't need to reload the font if it scales with one image. You need to configure the shader to use
423     * distance field fonts unless a class already does this for you (SquidLayers handles shader configuration
424     * internally, for example). TextCellFactory has a method, configureShader(Batch), that does this and should be
425     * called while that Batch has begun rendering, typically in an override of some containing Scene2D Group's
426     * draw(Batch, float) method.
427     * <br>
428     * At least two distance field fonts are included in SquidLib; one is square, one is narrow, and they can both be
429     * accessed using either the predefined TextCellFactory objects in DefaultResources, accessible with
430     * getStretchableFont() for narrow or getStretchableSquareFont() for square, or the setter methods in this class,
431     * defaultDistanceFieldFont() for square and defaultNarrowDistanceFieldFont() for narrow.
432     * <br>
433     * To create distance field fonts that work well with monospace layout is... time-consuming and error-prone, though
434     * not especially difficult for most fonts. The process is documented as well as we can, given how differently all
435     * fonts are made, in a file not included in the distribution JAR but present on GitHub:
436     * <a href="https://github.com/SquidPony/SquidLib/blob/master/squidlib/etc/making-distance-field-fonts.txt">Instructions here</a>.
437     * A separate project is dedicated to automating this process somewhat,
438     * <a href="https://github.com/tommyettinger/Glamer">Glamer</a>; Glamer can also produce MSDF fonts.
439     * @param fontPath the path to a .fnt bitmap font file with distance field effects applied, which requires a complex
440     *                 process to create.
441     * @param texturePath the path to the texture used by the bitmap font
442     * @return this factory for method chaining
443     */
444    public TextCellFactory fontDistanceField(String fontPath, String texturePath) {
445        Texture tex;
446        if (Gdx.files.internal(texturePath).exists()) {
447            Gdx.app.debug("font", "Using internal font texture at " + texturePath);
448            tex = new Texture(Gdx.files.internal(texturePath), false);
449            tex.setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear);
450        } else if (Gdx.files.classpath(texturePath).exists()) {
451            Gdx.app.debug("font", "Using classpath font texture at " + texturePath);
452            tex = new Texture(Gdx.files.classpath(texturePath), false);
453            tex.setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear);
454        } else {
455            bmpFont = DefaultResources.getIncludedFont();
456            Gdx.app.error("TextCellFactory", "Could not find font file: " + texturePath + ", using defaults");
457            return this;
458        }
459        if (Gdx.files.internal(fontPath).exists()) {
460            Gdx.app.debug("font", "Using internal font at " + fontPath);
461            bmpFont = new BitmapFont(Gdx.files.internal(fontPath), new TextureRegion(tex), false);
462            distanceField = true;
463        } else if (Gdx.files.classpath(fontPath).exists()) {
464            Gdx.app.debug("font", "Using classpath font at " + fontPath);
465            bmpFont = new BitmapFont(Gdx.files.classpath(fontPath), new TextureRegion(tex), false);
466            distanceField = true;
467        } else {
468            bmpFont = DefaultResources.getIncludedFont();
469            Gdx.app.error("TextCellFactory", "Could not find font file: " + fontPath + ", using defaults");
470        }
471        //bmpFont.getData().padBottom = bmpFont.getDescent();
472        distanceFieldScaleX = bmpFont.getSpaceXadvance() - 1f;
473        distanceFieldScaleY = bmpFont.getLineHeight() - 1f;
474        return this;
475    }
476    /**
477     * @param fontPath the path to a .fnt bitmap font file with multi-channel distance field effects applied, which
478     *                 requires a complex process to create.
479     * @param texturePath the path to the texture used by the bitmap font
480     * @return this factory for method chaining
481     */
482    public TextCellFactory fontMultiDistanceField(String fontPath, String texturePath) {
483        Texture tex;
484        if (Gdx.files.internal(texturePath).exists()) {
485            Gdx.app.debug("font", "Using internal font texture at " + texturePath);
486            tex = new Texture(Gdx.files.internal(texturePath), Pixmap.Format.RGB888, false);
487            tex.setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear);
488        } else if (Gdx.files.classpath(texturePath).exists()) {
489            Gdx.app.debug("font", "Using classpath font texture at " + texturePath);
490            tex = new Texture(Gdx.files.classpath(texturePath), Pixmap.Format.RGB888, false);
491            tex.setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear);
492        } else {
493            bmpFont = DefaultResources.getIncludedFont();
494            Gdx.app.error("TextCellFactory", "Could not find font file: " + texturePath + ", using defaults");
495            return this;
496        }
497        if (Gdx.files.internal(fontPath).exists()) {
498            Gdx.app.debug("font", "Using internal font at " + fontPath);
499            bmpFont = new BitmapFont(Gdx.files.internal(fontPath), new TextureRegion(tex), false);
500            msdf = true;
501        } else if (Gdx.files.classpath(fontPath).exists()) {
502            Gdx.app.debug("font", "Using classpath font at " + fontPath);
503            bmpFont = new BitmapFont(Gdx.files.classpath(fontPath), new TextureRegion(tex), false);
504            msdf = true;
505        } else {
506            bmpFont = DefaultResources.getIncludedFont();
507            Gdx.app.error("TextCellFactory", "Could not find font file: " + fontPath + ", using defaults");
508        }
509        //bmpFont.getData().padBottom = bmpFont.getDescent();
510        if(msdf)
511        {
512            bmpFont.getData().setScale(0.75f, 1f);
513        }
514        distanceFieldScaleX = bmpFont.getSpaceXadvance() - 1f;
515        distanceFieldScaleY = bmpFont.getLineHeight() - 1f;
516        return this;
517    }
518    /**
519     * Sets this factory to use the one font included with libGDX, which is Arial at size 15 px. Does it correctly
520     * display when used in a grid? Probably not as well as you'd hope. You should probably get some of the assets that
521     * accompany SquidLib, and can be downloaded directly from GitHub (not available as one monolithic jar via Maven
522     * Central, but that lets you pick and choose individual assets). Get a .fnt and its matching .png file from
523     * https://github.com/SquidPony/SquidLib/tree/master/assets and you can pass them to {@link #font(String)} or
524     * {@link #fontDistanceField(String, String)}.
525     *
526     * @return this factory for method chaining
527     */
528    public TextCellFactory includedFont()
529    {
530        bmpFont = DefaultResources.getIncludedFont();
531        return this;
532    }/**
533     * Sets this factory to use a default 12x24 font that supports Latin, Greek, Cyrillic, and many more, including
534     * box-drawing characters, zodiac signs, playing-card suits, and chess piece symbols. This is enough to support the
535     * output of anything that DungeonUtility can make for a dungeon or FakeLanguageGen can make for text with its
536     * defaults, which is difficult for any font to do. The box-drawing characters in this don't quite line up, and in
537     * some colors there may appear to be gaps (white text on black backgrounds will show it, but not much else).
538     *
539     * This is a way to complete a needed step; the font must be set before initializing, which can be done by a few
540     * methods in this class.
541     *
542     * @return this factory for method chaining
543     */
544    public TextCellFactory defaultFont()
545    {
546        bmpFont = DefaultResources.getLargeSmoothFont();
547        return this;
548    }
549    /**
550     * Sets this factory to use a default 12x24 font that renders very accurately, with no gaps between box-drawing
551     * characters and very geometric lines.
552     *
553     * This is a way to complete a needed step; the font must be set before initializing, which can be done by a few
554     * methods in this class.
555     *
556     * @return this factory for method chaining
557     */
558    public TextCellFactory defaultNarrowFont()
559    {
560        bmpFont = DefaultResources.getLargeNarrowFont();
561        return this;
562    }
563
564    /**
565     * Sets this factory to use a default 12x12 font, which... is square, and doesn't look as bad as many square fonts
566     * do, plus it supports box-drawing characters with no gaps.
567     *
568     * This is a way to complete a needed step; the font must be set before initializing, which can be done by a few
569     * methods in this class.
570     *
571     * @return this factory for method chaining
572     */
573    public TextCellFactory defaultSquareFont()
574    {
575        bmpFont = DefaultResources.getDefaultFont();
576        return this;
577    }
578
579    /**
580     * Sets the TextCellFactory to use a square distance field font that will resize to whatever size you request.
581     * You must configure the shader if you use a distance field font, unless a class does it for you, like SquidLayers.
582     * The configureShader(Batch) method of this class can be used to set up the shader if you don't use SquidLayers;
583     * see its docs for more information.
584     * @return this TextCellFactory set to use a square distance field font
585     */
586    public TextCellFactory defaultDistanceFieldFont()
587    {
588        fontDistanceField(DefaultResources.distanceFieldSquare, DefaultResources.distanceFieldSquareTexture);
589        return this;
590    }
591    /**
592     * Sets the TextCellFactory to use a half-square distance field font that will resize to whatever size you request.
593     * You must configure the shader if you use a distance field font, unless a class does it for you, like SquidLayers.
594     * The configureShader(Batch) method of this class can be used to set up the shader if you don't use SquidLayers;
595     * see its docs for more information.
596     * @return this TextCellFactory set to use a half-square distance field font
597     */
598    public TextCellFactory defaultNarrowDistanceFieldFont()
599    {
600        fontDistanceField(DefaultResources.distanceFieldNarrow, DefaultResources.distanceFieldNarrowTexture);
601        return this;
602    }
603
604    /**
605     * Returns the width of a single cell.
606     *
607     * @return the width
608     */
609    public float width() {
610        return width;
611    }
612
613    /**
614     * Sets the factory's cell width to the provided value. Clamps at 1 on the
615     * lower bound to ensure valid calculations.
616     *
617     * @param width the desired width
618     * @return this factory for method chaining
619     */
620    public TextCellFactory width(float width) {
621        this.width = Math.max(1, width);
622        actualCellWidth = this.width;
623        return this;
624    }
625
626    /**
627     * Returns the height of a single cell.
628     *
629     * @return the height of a single cell
630     */
631    public float height() {
632        return height;
633    }
634
635    /**
636     * Sets the factory's cell height to the provided value. Clamps at 1 on the
637     * lower bound to ensure valid calculations.
638     *
639     * @param height the desired width
640     * @return this factory for method chaining
641     */
642    public TextCellFactory height(float height) {
643        this.height = Math.max(1, height);
644        //modifiedHeight = this.height;
645        actualCellHeight = this.height;
646        return this;
647    }
648    /**
649     * Sets the factory's height used for text to the provided value, but does not change the size of a cell. Clamps at
650     * 1 on the lower bound to ensure valid calculations.
651     *
652     * @param width the desired width
653     * @return this factory for method chaining
654     */
655    public TextCellFactory tweakWidth(float width) {
656        this.width = Math.max(1, width);
657        return this;
658    }
659
660    /**
661     * Sets the factory's height used for text to the provided value, but does not change the size of a cell. Clamps at
662     * 1 on the lower bound to ensure valid calculations.
663     *
664     * @param height the desired height
665     * @return this factory for method chaining
666     */
667    public TextCellFactory tweakHeight(float height) {
668        this.height = Math.max(1, height);
669        //modifiedHeight = this.height;
670        return this;
671    }
672
673    /**
674     * Returns the current String of code points that are used for sizing the
675     * cells.
676     *
677     * Note that this is actually a set of codepoints and treating them as an
678     * array of chars might give undesired results.
679     *
680     * @return the String used for sizing calculations
681     */
682    public String fit() {
683        return fitting;
684    }
685
686    /**
687     * Sets the characters that will be guaranteed to fit to the provided ones.
688     * This will override any previously set string.
689     *
690     * @param fit the String of code points to size to
691     * @return this factory for method chaining
692     */
693    public TextCellFactory fit(String fit) {
694        fitting = fit;
695        bmpFont.setFixedWidthGlyphs(fitting);
696        width = bmpFont.getSpaceXadvance();
697        return this;
698    }
699
700    /**
701     * Adds the code points in the string to the list of characters that will be
702     * guaranteed to fit.
703     *
704     * @param fit the String of code points to size to
705     * @return this factory for method chaining
706     */
707    public TextCellFactory addFit(String fit) {
708        fitting += fit;
709        bmpFont.setFixedWidthGlyphs(fitting);
710        width = bmpFont.getSpaceXadvance();
711        return this;
712    }
713
714    /**
715     * Returns whether this factory is currently set to do antialiasing on the
716     * characters rendered, which is always true.
717     *
718     * @return true if antialiasing is set, so, always
719     */
720    public boolean antialias() {
721        return true;
722    }
723
724    /**
725     * All fonts will be rendered with antialiasing, this doesn't do anything.
726     *
727     * @param antialias ignored, will always use antialiasing
728     * @return this factory for method chaining
729     * @deprecated AA is the wave of the future!
730     */
731    @Deprecated
732    public TextCellFactory antialias(boolean antialias) {
733        return this;
734    }
735
736    /**
737     * Sets the amount of padding on all sides to the provided value.
738     *
739     * @param padding how much padding in pixels
740     * @return this for method chaining
741     */
742    public TextCellFactory padding(int padding) {
743        leftPadding = padding;
744        rightPadding = padding;
745        topPadding = padding;
746        bottomPadding = padding;
747        return this;
748    }
749
750    /**
751     * Returns the padding on the left side.
752     *
753     * @return amount of padding in pixels
754     */
755    public int leftPadding() {
756        return leftPadding;
757    }
758
759    /**
760     * Sets the amount of padding on the left side to the provided value.
761     *
762     * @param padding how much padding in pixels
763     * @return this for method chaining
764     */
765    public TextCellFactory leftPadding(int padding) {
766        leftPadding = padding;
767        return this;
768    }
769
770    /**
771     * Returns the padding on the right side.
772     *
773     * @return amount of padding in pixels
774     */
775    public int rightPadding() {
776        return rightPadding;
777    }
778
779    /**
780     * Sets the amount of padding on the right side to the provided value.
781     *
782     * @param padding how much padding in pixels
783     * @return this for method chaining
784     */
785    public TextCellFactory rightPadding(int padding) {
786        rightPadding = padding;
787        return this;
788    }
789
790    /**
791     * Returns the padding on the top side.
792     *
793     * @return amount of padding in pixels
794     */
795    public int topPadding() {
796        return topPadding;
797    }
798
799    /**
800     * Sets the amount of padding on the top side to the provided value.
801     *
802     * @param padding how much padding in pixels
803     * @return this for method chaining
804     */
805    public TextCellFactory topPadding(int padding) {
806        topPadding = padding;
807        return this;
808    }
809
810    /**
811     * Returns the padding on the bottom side.
812     *
813     * @return amount of padding in pixels
814     */
815    public int bottomPadding() {
816        return bottomPadding;
817    }
818
819    /**
820     * Sets the amount of padding on the bottom side to the provided value.
821     *
822     * @param padding how much padding in pixels
823     * @return this for method chaining
824     */
825    public TextCellFactory bottomPadding(int padding) {
826        bottomPadding = padding;
827        return this;
828    }
829
830    /**
831     * @param icc
832     *            The color center to use. Should not be {@code null}.
833     * @return {@code this}
834     * @throws NullPointerException
835     *             If {@code icc} is {@code null}.
836     */
837    public TextCellFactory setColorCenter(IColorCenter<Color> icc) {
838        if (icc == null)
839            /* Better fail now than later */
840            throw new NullPointerException(
841                    "The color center should not be null in " + getClass().getSimpleName());
842        scc = icc;
843        return this;
844    }
845
846    /**
847     * Returns true if this factory is fully initialized and ready to build text cells.
848     *
849     * @return true if initialized
850     */
851    public boolean initialized() {
852        return initialized;
853    }
854
855    /**
856     * Returns true if the given character will fit inside the current cell
857     * dimensions with the current font.
858     *
859     * ISO Control characters, non-printing characters and invalid unicode
860     * characters are all considered by definition to fit.
861     *
862     * @param codepoint the codepoint of the char in question
863     * @return true if the char will fit, or if it is non-printing in some way; false otherwise
864     */
865    public boolean willFit(int codepoint) {
866        if (!initialized) {
867            throw new IllegalStateException("This factory has not yet been initialized!");
868        }
869
870        if (!Character.isValidCodePoint(codepoint) ||
871                (codepoint <= 0x001F) || (codepoint >= 0x007F && codepoint <= 0x009F)) // same as isIsoControl
872        {
873            return true;
874        }
875
876        return fitting.contains(String.valueOf(Character.toChars(codepoint)));
877    }
878    
879    private char getOrDefault(final char toGet)
880    {
881        final char got = swap.get(toGet);
882        return got == '\uffff' ? toGet : got;
883    }
884    /**
885     * Use the specified Batch to draw a String or other CharSequence (often just one char long) with the default color
886     * (white), with x and y determining the world-space coordinates for the upper-left corner.
887     *
888     * @param batch the LibGDX Batch to do the drawing
889     * @param s the string to draw, often but not necessarily one char. Can be null to draw a solid block instead.
890     * @param x x of the upper-left corner of the region of text in world coordinates.
891     * @param y y of the upper-left corner of the region of text in world coordinates.
892     */
893    public void draw(Batch batch, CharSequence s, float x, float y) {
894        if (!initialized) {
895            throw new IllegalStateException("This factory has not yet been initialized!");
896        }
897
898        // + descent * 3 / 2f
899        // - distanceFieldScaleY / 12f
900
901        //height - lineTweak * 2f
902        if (s == null || s.length() == 0 || s.charAt(0) == 0) {
903            batch.setPackedColor(SColor.FLOAT_WHITE);
904            batch.draw(block, x, y - actualCellHeight, actualCellWidth, actualCellHeight); // + descent * 1 / 3f
905        } else {
906            batch.setPackedColor(SColor.FLOAT_WHITE); // round trip so FilterBatch can filter BitmapFontCache
907            Color.abgr8888ToColor(bmpFont.getColor(), batch.getPackedColor());
908            bmpFont.draw(batch, s, x, y - descent + 1/* * 1.5f*//* - lineHeight * 0.2f */ /* + descent*/, width, Align.center, false);
909        }
910    }
911
912
913    /**
914     * Use the specified Batch to draw a char with the default color (white), with x and y determining the world-space
915     * coordinates for the upper-left corner. If c is {@code '\0'}, will draw a solid white block instead.
916     *
917     * @param batch the LibGDX Batch to do the drawing
918     * @param c the char to draw. Can be {@code '\0'} to draw a solid block instead.
919     * @param x x of the upper-left corner of the region of text in world coordinates.
920     * @param y y of the upper-left corner of the region of text in world coordinates.
921     */
922    public void draw(Batch batch, char c, float x, float y) {
923        if (!initialized) {
924            throw new IllegalStateException("This factory has not yet been initialized!");
925        }
926
927        // + descent * 3 / 2f
928        // - distanceFieldScaleY / 12f
929
930        //height - lineTweak * 2f
931        if (c == 0) {
932            batch.setColor(1f,1f,1f,1f);
933            batch.draw(block, x, y - actualCellHeight, actualCellWidth, actualCellHeight); // + descent * 1 / 3f
934        } else {
935            batch.setPackedColor(SColor.FLOAT_WHITE); // round trip so FilterBatch can filter BitmapFontCache
936            Color.abgr8888ToColor(bmpFont.getColor(), batch.getPackedColor());
937            mut.setCharAt(0, getOrDefault(c));
938            bmpFont.draw(batch, mut, x, y - descent + 1/* * 1.5f*//* - lineHeight * 0.2f */ /* + descent*/, width, Align.center, false);
939        }
940    }
941    /**
942     * Use the specified Batch to draw a String or other CharSequence (often just one char long) in the specified rgba
943     * color, with x and y determining the world-space coordinates for the upper-left corner.
944     *
945     * @param batch the LibGDX Batch to do the drawing
946     * @param s the string to draw, often but not necessarily one char. Can be null to draw a solid block instead.
947     * @param r 0.0f to 1.0f red value
948     * @param g 0.0f to 1.0f green value
949     * @param b 0.0f to 1.0f blue value
950     * @param a 0.0f to 1.0f alpha value
951     * @param x x of the upper-left corner of the region of text in world coordinates.
952     * @param y y of the upper-left corner of the region of text in world coordinates.
953     */
954    public void draw(Batch batch, CharSequence s, float r, float g, float b, float a, float x, float y) {
955        if (!initialized) {
956            throw new IllegalStateException("This factory has not yet been initialized!");
957        }
958
959        if (s == null) {
960            float orig = batch.getPackedColor();
961            batch.setColor(r, g, b, a);
962            batch.draw(block, x, y - actualCellHeight, actualCellWidth, actualCellHeight); // descent * 1 / 3f
963            batch.setPackedColor(orig);
964        } else if(s.length() > 0 && s.charAt(0) == '\0') {
965            float orig = batch.getPackedColor();
966            batch.setColor(r, g, b, a);
967            batch.draw(block, x, y - actualCellHeight, actualCellWidth * s.length(), actualCellHeight);
968            batch.setPackedColor(orig);
969        } else {
970            batch.setColor(r, g, b, a); // round trip so FilterBatch can filter BitmapFontCache
971            Color.abgr8888ToColor(bmpFont.getColor(), batch.getPackedColor());
972            bmpFont.draw(batch, s, x, y - descent + 1, width, Align.center, false);
973        }
974    }
975
976    /**
977     * Use the specified Batch to draw a String or other CharSequence (often just one char long) in the specified LibGDX
978     * Color, with x and y
979     * determining the world-space coordinates for the upper-left corner.
980     *
981     * @param batch the LibGDX Batch to do the drawing
982     * @param s the string to draw, often but not necessarily one char. Can be null to draw a solid block instead.
983     * @param color the LibGDX Color to draw the char(s) with, all the same color
984     * @param x x of the upper-left corner of the region of text in world coordinates.
985     * @param y y of the upper-left corner of the region of text in world coordinates.
986     */
987    public void draw(Batch batch, CharSequence s, Color color, float x, float y) {
988        if (!initialized) {
989            throw new IllegalStateException("This factory has not yet been initialized!");
990        }
991
992        if (s == null) {
993            float orig = batch.getPackedColor();
994            batch.setColor(color);
995            batch.draw(block, x, y - actualCellHeight, actualCellWidth, actualCellHeight);
996            batch.setPackedColor(orig);
997        } else if(s.length() > 0 && s.charAt(0) == '\0') {
998            float orig = batch.getPackedColor();
999            batch.setColor(color);
1000            batch.draw(block, x, y - actualCellHeight, actualCellWidth * s.length(), actualCellHeight);
1001            batch.setPackedColor(orig);
1002        } else {
1003            batch.setColor(color); // round trip so FilterBatch can filter BitmapFontCache
1004            Color.abgr8888ToColor(bmpFont.getColor(), batch.getPackedColor());
1005            bmpFont.draw(batch, s, x, y - descent + 1, width, Align.center, false);
1006        }
1007    }
1008    /**
1009     * Use the specified Batch to draw a String or other CharSequence (often just one char long) in the specified LibGDX
1010     * Color, with x and y determining the world-space coordinates for the upper-left corner.
1011     *
1012     * @param batch the LibGDX Batch to do the drawing
1013     * @param s the string to draw, often but not necessarily one char. Can be null to draw a solid block instead.
1014     * @param encodedColor the LibGDX Color to use, converted to float as by {@link Color#toFloatBits()}
1015     * @param x x of the upper-left corner of the region of text in world coordinates.
1016     * @param y y of the upper-left corner of the region of text in world coordinates.
1017     */
1018    public void draw(Batch batch, CharSequence s, float encodedColor, float x, float y) {
1019        if (!initialized) {
1020            throw new IllegalStateException("This factory has not yet been initialized!");
1021        }
1022
1023        if (s == null) {
1024            float orig = batch.getPackedColor();
1025            batch.setPackedColor(encodedColor);
1026            batch.draw(block, x, y - actualCellHeight, actualCellWidth, actualCellHeight);
1027            batch.setPackedColor(orig);
1028        } else if(s.length() > 0 && s.charAt(0) == '\0') {
1029            float orig = batch.getPackedColor();
1030            batch.setPackedColor(encodedColor);
1031            batch.draw(block, x, y - actualCellHeight, actualCellWidth * s.length(), actualCellHeight);
1032            batch.setPackedColor(orig);
1033        } else
1034        {
1035            batch.setPackedColor(encodedColor); // round trip so FilterBatch can filter BitmapFontCache
1036            Color.abgr8888ToColor(bmpFont.getColor(), batch.getPackedColor());
1037            bmpFont.draw(batch, s, x, y - descent + 1, width, Align.center, false);
1038        }
1039    }
1040
1041    /**
1042     * Use the specified Batch to draw a char in the specified LibGDX Color, with x and y
1043     * determining the world-space coordinates for the upper-left corner.
1044     *
1045     * @param batch the LibGDX Batch to do the drawing
1046     * @param c the char to draw. Can be {@code '\0'} to draw a solid block instead.
1047     * @param color the LibGDX Color (or SquidLib SColor) to use, as an object
1048     * @param x x of the upper-left corner of the region of text in world coordinates.
1049     * @param y y of the upper-left corner of the region of text in world coordinates.
1050     */
1051    public void draw(Batch batch, char c, Color color, float x, float y) {
1052        if (!initialized) {
1053            throw new IllegalStateException("This factory has not yet been initialized!");
1054        }
1055        if (c == 0) {
1056            float orig = batch.getPackedColor();
1057            batch.setColor(color);
1058            batch.draw(block, x, y - actualCellHeight, actualCellWidth, actualCellHeight);
1059            batch.setPackedColor(orig);
1060        } else
1061        {
1062            batch.setColor(color); // round trip so FilterBatch can filter BitmapFontCache
1063            Color.abgr8888ToColor(bmpFont.getColor(), batch.getPackedColor());
1064            mut.setCharAt(0, getOrDefault(c));
1065            bmpFont.draw(batch, mut, x, y - descent + 1, width, Align.center, false);
1066        }
1067    }
1068
1069    /**
1070     * Use the specified Batch to draw a String (often just one char long) in the specified LibGDX Color, with x and y
1071     * determining the world-space coordinates for the upper-left corner.
1072     *
1073     * @param batch the LibGDX Batch to do the drawing
1074     * @param c the char to draw. Can be {@code '\0'} to draw a solid block instead.
1075     * @param encodedColor the LibGDX Color to use, converted to float as by {@link Color#toFloatBits()}
1076     * @param x x of the upper-left corner of the region of text in world coordinates.
1077     * @param y y of the upper-left corner of the region of text in world coordinates.
1078     */
1079    public void draw(Batch batch, char c, float encodedColor, float x, float y) {
1080        if (!initialized) {
1081            throw new IllegalStateException("This factory has not yet been initialized!");
1082        }
1083        if (c == 0) {
1084            float orig = batch.getPackedColor();
1085            batch.setPackedColor(encodedColor);
1086            batch.draw(block, x, y - actualCellHeight, actualCellWidth, actualCellHeight);
1087            batch.setPackedColor(orig);
1088        } else
1089        {
1090            batch.setPackedColor(encodedColor); // round trip so FilterBatch can filter BitmapFontCache
1091            Color.abgr8888ToColor(bmpFont.getColor(), batch.getPackedColor());
1092            mut.setCharAt(0, getOrDefault(c));
1093            bmpFont.draw(batch, mut, x, y - descent + 1, width, Align.center, false);
1094        }
1095    }
1096
1097    /**
1098     * Use the specified Batch to draw a TextureRegion with the default tint color (white, so un-tinted), with x and y
1099     * determining the world-space coordinates for the upper-left corner. The TextureRegion will be stretched
1100     * if its size does not match what this TextCellFactory uses for width and height.
1101     *
1102     * @param batch the LibGDX Batch to do the drawing
1103     * @param tr the TextureRegion to draw. Can be null to draw a solid block instead.
1104     * @param x x of the upper-left corner of the region of text in world coordinates.
1105     * @param y y of the upper-left corner of the region of text in world coordinates.
1106     */
1107    public void draw(Batch batch, TextureRegion tr, float x, float y) {
1108        if (!initialized) {
1109            throw new IllegalStateException("This factory has not yet been initialized!");
1110        }
1111
1112        if (tr == null) {
1113            batch.draw(block, x, y - height, actualCellWidth, actualCellHeight);
1114        } else {
1115            batch.draw(tr, x, y - height, width, height);
1116        }
1117    }
1118    /**
1119     * Use the specified Batch to draw a TextureRegion tinted with the specified rgba color, with x and y
1120     * determining the world-space coordinates for the upper-left corner. The TextureRegion will be stretched
1121     * if its size does not match what this TextCellFactory uses for width and height.
1122     *
1123     * @param batch the LibGDX Batch to do the drawing
1124     * @param tr the TextureRegion to draw. Can be null to draw a solid block instead.
1125     * @param r 0.0 to 0.1 red value
1126     * @param g 0.0 to 0.1 green value
1127     * @param b 0.0 to 0.1 blue value
1128     * @param a 0.0 to 0.1 alpha value
1129     * @param x x of the upper-left corner of the region of text in world coordinates.
1130     * @param y y of the upper-left corner of the region of text in world coordinates.
1131     */
1132    public void draw(Batch batch, TextureRegion tr, float r, float g, float b, float a, float x, float y) {
1133        if (!initialized) {
1134            throw new IllegalStateException("This factory has not yet been initialized!");
1135        }
1136
1137        if (tr == null) {
1138            float orig = batch.getPackedColor();
1139            batch.setColor(r, g, b, a);
1140            batch.draw(block, x, y - height, actualCellWidth, actualCellHeight);
1141            batch.setPackedColor(orig);
1142        } else {
1143            float orig = batch.getPackedColor();
1144            batch.setColor(r, g, b, a);
1145            batch.draw(tr, x, y - height, width, height);
1146            batch.setPackedColor(orig);
1147        }
1148    }
1149
1150    /**
1151     * Use the specified Batch to draw a TextureRegion tinted with the specified LibGDX Color, with x and y
1152     * determining the world-space coordinates for the upper-left corner. The TextureRegion will be stretched
1153     * if its size does not match what this TextCellFactory uses for width and height.
1154     *
1155     * @param batch the LibGDX Batch to do the drawing
1156     * @param tr the TextureRegion to draw. Can be null to draw a solid block instead.
1157     * @param color the LibGDX Color to draw the char(s) with, all the same color
1158     * @param x x of the upper-left corner of the region of text in world coordinates.
1159     * @param y y of the upper-left corner of the region of text in world coordinates.
1160     */
1161    public void draw(Batch batch, TextureRegion tr, Color color, float x, float y) {
1162        if (!initialized) {
1163            throw new IllegalStateException("This factory has not yet been initialized!");
1164        }
1165
1166        if (tr == null) {
1167            float orig = batch.getPackedColor();
1168            batch.setColor(color);
1169            batch.draw(block, x, y - height, actualCellWidth, actualCellHeight);
1170            batch.setPackedColor(orig);
1171        } else {
1172            float orig = batch.getPackedColor();
1173            batch.setColor(color);
1174            batch.draw(tr, x, y - height, width, height);
1175            batch.setPackedColor(orig);
1176        }
1177    }
1178    /**
1179     * Use the specified Batch to draw a TextureRegion tinted with the specified encoded color as a float, with x and y
1180     * determining the world-space coordinates for the upper-left corner. The TextureRegion will be stretched
1181     * if its size does not match what this TextCellFactory uses for width and height. Colors can be converted to and
1182     * from floats using methods in SColor such as {@link SColor#floatGet(float, float, float, float)},
1183     * {@link SColor#toFloatBits()}, {@link SColor#colorFromFloat(Color, float)}, and
1184     * {@link SColor#lerpFloatColors(float, float, float)}.
1185     *
1186     * @param batch the LibGDX Batch to do the drawing
1187     * @param tr the TextureRegion to draw. Can be null to draw a solid block instead.
1188     * @param encodedColor the float encoding a color (as ABGR8888; SColor can produce these) to draw the image with
1189     * @param x x of the upper-left corner of the image in world coordinates.
1190     * @param y y of the upper-left corner of the image in world coordinates.
1191     */
1192    public void draw(Batch batch, TextureRegion tr, float encodedColor, float x, float y)
1193    {
1194        if (!initialized) {
1195            throw new IllegalStateException("This factory has not yet been initialized!");
1196        }
1197
1198        if (tr == null) {
1199            float orig = batch.getPackedColor();
1200            batch.setPackedColor(encodedColor);
1201            batch.draw(block, x, y - height, actualCellWidth, actualCellHeight);
1202            batch.setPackedColor(orig);
1203        } else {
1204            float orig = batch.getPackedColor();
1205            batch.setPackedColor(encodedColor);
1206            batch.draw(tr, x, y - height, width, height);
1207            batch.setPackedColor(orig);
1208        }
1209    }
1210
1211    /**
1212     * Use the specified Batch to draw a TextureRegion with the default tint color (white, so un-tinted), with x and y
1213     * determining the world-space coordinates for the upper-left corner. The TextureRegion will be stretched
1214     * only if the supplied width and height do not match what its own dimensions are.
1215     *
1216     * @param batch the LibGDX Batch to do the drawing
1217     * @param tr the TextureRegion to draw. Can be null to draw a solid block instead.
1218     * @param x x of the upper-left corner of the region of text in world coordinates.
1219     * @param y y of the upper-left corner of the region of text in world coordinates.
1220     * @param width the width of the TextureRegion or solid block in pixels.
1221     * @param height the height of the TextureRegion or solid block in pixels.
1222     */
1223    public void draw(Batch batch, TextureRegion tr, float x, float y, float width, float height) {
1224        if (!initialized) {
1225            throw new IllegalStateException("This factory has not yet been initialized!");
1226        }
1227
1228        if (tr == null) {
1229            batch.draw(block, x, y - height, width, height);
1230        } else {
1231            batch.draw(tr, x, y - height, width, height);
1232        }
1233    }
1234    /**
1235     * Use the specified Batch to draw a TextureRegion tinted with the specified rgba color, with x and y
1236     * determining the world-space coordinates for the upper-left corner. The TextureRegion will be stretched
1237     * only if the supplied width and height do not match what its own dimensions are.
1238     *
1239     * @param batch the LibGDX Batch to do the drawing
1240     * @param tr the TextureRegion to draw. Can be null to draw a solid block instead.
1241     * @param r 0.0 to 0.1 red value
1242     * @param g 0.0 to 0.1 green value
1243     * @param b 0.0 to 0.1 blue value
1244     * @param a 0.0 to 0.1 alpha value
1245     * @param x x of the upper-left corner of the region of text in world coordinates.
1246     * @param y y of the upper-left corner of the region of text in world coordinates.
1247     * @param width the width of the TextureRegion or solid block in pixels.
1248     * @param height the height of the TextureRegion or solid block in pixels.
1249     */
1250    public void draw(Batch batch, TextureRegion tr, float r, float g, float b, float a, float x, float y, float width, float height) {
1251        if (!initialized) {
1252            throw new IllegalStateException("This factory has not yet been initialized!");
1253        }
1254
1255        if (tr == null) {
1256            float orig = batch.getPackedColor();
1257            batch.setColor(r, g, b, a);
1258            batch.draw(block, x, y - height, width, height);
1259            batch.setPackedColor(orig);
1260        } else {
1261            float orig = batch.getPackedColor();
1262            batch.setColor(r, g, b, a);
1263            batch.draw(tr, x, y - height, width, height);
1264            batch.setPackedColor(orig);
1265        }
1266    }
1267
1268    /**
1269     * Use the specified Batch to draw a TextureRegion tinted with the specified LibGDX Color, with x and y
1270     * determining the world-space coordinates for the upper-left corner. The TextureRegion will be stretched
1271     * only if the supplied width and height do not match what its own dimensions are.
1272     *
1273     * @param batch the LibGDX Batch to do the drawing
1274     * @param tr the TextureRegion to draw. Can be null to draw a solid block instead.
1275     * @param color the LibGDX Color to draw the image with, all the same color
1276     * @param x x of the upper-left corner of the image in world coordinates.
1277     * @param y y of the upper-left corner of the image in world coordinates.
1278     * @param width the width of the TextureRegion or solid block in pixels.
1279     * @param height the height of the TextureRegion or solid block in pixels.
1280     */
1281    public void draw(Batch batch, TextureRegion tr, Color color, float x, float y, float width, float height) {
1282        if (!initialized) {
1283            throw new IllegalStateException("This factory has not yet been initialized!");
1284        }
1285
1286        if (tr == null) {
1287            float orig = batch.getPackedColor();
1288            batch.setColor(color);
1289            batch.draw(block, x, y - height, width, height);
1290            batch.setPackedColor(orig);
1291        } else {
1292            float orig = batch.getPackedColor();
1293            batch.setColor(color);
1294            batch.draw(tr, x, y - height, width, height);
1295            batch.setPackedColor(orig);
1296        }
1297    }
1298
1299    /**
1300     * Use the specified Batch to draw a TextureRegion tinted with the specified LibGDX Color, with x and y
1301     * determining the world-space coordinates for the upper-left corner. The TextureRegion will be stretched
1302     * only if the supplied width and height do not match what its own dimensions are.
1303     *
1304     * @param batch the LibGDX Batch to do the drawing
1305     * @param tr the TextureRegion to draw. Can be null to draw a solid block instead.
1306     * @param encodedColor the float encoding a color (as ABGR8888; SColor can produce these) to draw the image with
1307     * @param x x of the upper-left corner of the image in world coordinates.
1308     * @param y y of the upper-left corner of the image in world coordinates.
1309     * @param width the width of the TextureRegion or solid block in pixels.
1310     * @param height the height of the TextureRegion or solid block in pixels.
1311     */
1312    public void draw(Batch batch, TextureRegion tr, float encodedColor, float x, float y, float width, float height) {
1313        if (!initialized) {
1314            throw new IllegalStateException("This factory has not yet been initialized!");
1315        }
1316
1317        if (tr == null) {
1318            float orig = batch.getPackedColor();
1319            batch.setPackedColor(encodedColor);
1320            batch.draw(block, x, y - height, width, height);
1321            batch.setPackedColor(orig);
1322        } else {
1323            float orig = batch.getPackedColor();
1324            batch.setPackedColor(encodedColor);
1325            batch.draw(tr, x, y - height, width, height);
1326            batch.setPackedColor(orig);
1327        }
1328    }
1329
1330    /**
1331     * Draws a 2D array of floats that represent encoded colors, with each float used for a block of color the size of
1332     * one character cell (it will be {@link #actualCellWidth} by {@link #actualCellHeight} in size, using world units).
1333     * @param batch the LibGDX Batch to do the drawing
1334     * @param encodedColors a 2D float array of encoded colors, each usually produced by {@link Color#toFloatBits()} or an SColor method
1335     * @param x the x-position where this should render the block of many colors 
1336     * @param y the y-position where this should render the block of many colors
1337     */
1338    public void draw(Batch batch, float[][] encodedColors, float x, float y)
1339    {
1340        float orig = batch.getPackedColor();
1341        final int w = encodedColors.length, h = encodedColors[0].length;
1342        float wm = x, hm;
1343        for (int i = 0; i < w; i++, wm += actualCellWidth) {
1344            hm = y + (h - 1) * actualCellHeight;
1345            for (int j = 0; j < h; j++, hm -= actualCellHeight) {
1346                if(encodedColors[i][j] == 0f)
1347                    continue;
1348                batch.setPackedColor(encodedColors[i][j]);
1349                batch.draw(block, wm, hm, actualCellWidth, actualCellHeight); // descent * 1 / 3f
1350            }
1351        }
1352        batch.setPackedColor(orig);
1353    }
1354
1355    /**
1356     * Draws a 2D array of floats that represent encoded colors, with each float used for a block of color that will be
1357     * a fraction of the size of one character cell (it will be {@link #actualCellWidth} divided by {@code xSubCells} by
1358     * {@link #actualCellHeight} divided by {@code ySubCells} in size, using world units). Typically the 2D float array
1359     * should be larger than the 2D storage for text drawn over the colorful blocks (such as a 2D char array), since
1360     * more than one subcell may be drawn in the space of one character cell.
1361     * @param batch the LibGDX Batch to do the drawing
1362     * @param encodedColors a 2D float array of encoded colors, each usually produced by {@link Color#toFloatBits()} or an SColor method
1363     * @param x the x-position where this should render the block of many colors 
1364     * @param y the y-position where this should render the block of many colors
1365     * @param xSubCells how many blocks of color should fit across the span of {@link #actualCellWidth}, each one float; must not be 0
1366     * @param ySubCells how many blocks of color should fit across the span of {@link #actualCellHeight}, each one float; must not be 0
1367     */
1368    public void draw(Batch batch, float[][] encodedColors, float x, float y, int xSubCells, int ySubCells)
1369    {
1370        float orig = batch.getPackedColor();
1371        final int w = encodedColors.length, h = encodedColors[0].length;
1372        final float subW = actualCellWidth / xSubCells, subH = actualCellHeight / ySubCells;
1373        float wm = x, hm;
1374        for (int i = 0; i < w; i++, wm += subW) {
1375            hm = y + (h - 1) * subH;
1376            for (int j = 0; j < h; j++, hm -= subH) {
1377                if(encodedColors[i][j] == 0f)
1378                    continue;
1379                batch.setPackedColor(encodedColors[i][j]);
1380                batch.draw(block, wm, hm, subW, subH);
1381            }
1382        }
1383        batch.setPackedColor(orig);
1384    }
1385
1386    /**
1387     * Given a Pixmap and a 2D float array, uses each float in the array as an encoded color for a pixel in the Pixmap
1388     * at a location determined by the float's position in the array. This is meant to be used to draw backgrounds that
1389     * will be stretched by the {@link #actualCellWidth} and {@link #actualCellHeight} of this TextCellFactory.
1390     * <br>
1391     * Oddly, this doesn't seem to be very fast compared to {@link #draw(Batch, float[][], float, float)}, so if you
1392     * need to draw backgrounds then prefer that method (as SparseLayers does). It also doesn't work with a FilterBatch,
1393     * so if you want to use FloatFilter to adjust colors, you would need to use the other draw method for backgrounds.
1394     * @param pixmap a non-null Pixmap that will be modified
1395     * @param encodedColors a 2D float array that must be non-null and non-empty, and contains packed colors
1396     */
1397    public void draw(Pixmap pixmap, float[][] encodedColors)
1398    {
1399        final int w = Math.min(pixmap.getWidth(), encodedColors.length), h = Math.min(pixmap.getHeight(), encodedColors[0].length);
1400        for (int i = 0; i < w; i++) {
1401            for (int j = h - 1; j >= 0; j--) {
1402                if (encodedColors[i][j] == 0f)
1403                    continue;
1404                pixmap.drawPixel(i, j, NumberTools.floatToReversedIntBits(encodedColors[i][j]));
1405            }
1406        }
1407    }
1408
1409    /**
1410     * Converts a String into a Label, or if the argument s is null, creates an Image of a solid block. Can be used
1411     * for preparing glyphs for animation effects.
1412     * @param s a String to make into an Actor, which can be null for a solid block.
1413     * @return the Actor, with no position set.
1414     */
1415    public Label makeWrappingString(String s) {
1416        if (!initialized) {
1417            throw new IllegalStateException("This factory has not yet been initialized!");
1418        }
1419        if (s == null) {
1420            s = "";
1421        }
1422        Label lb = new Label(s, style);
1423        lb.setWrap(true);
1424        // lb.setPosition(x - width * 0.5f, y - height * 0.5f, Align.center);
1425        return lb;
1426
1427    }
1428
1429    /**
1430     * Converts a String into a Label, or if the argument s is null, creates an Image of a solid block. Can be used
1431     * for preparing glyphs for animation effects, and is used internally for this purpose.
1432     * @param s a String to make into an Actor, which can be null for a solid block.
1433     * @param color a Color to tint s with.
1434     * @return the Actor, with no position set.
1435     */
1436    public Actor makeActor(String s, Color color) {
1437        if (!initialized) {
1438            throw new IllegalStateException("This factory has not yet been initialized!");
1439        }
1440        if (s == null) {
1441            Image im = new Image(block);
1442            im.setColor(scc.filter(color));
1443            //im.setSize(width, height - MathUtils.ceil(bmpFont.getDescent() / 2f));
1444            im.setSize(actualCellWidth, actualCellHeight + (distanceField ? 1 : 0)); //  - lineHeight / actualCellHeight //+ lineTweak * 1f
1445            // im.setPosition(x - width * 0.5f, y - height * 0.5f, Align.center);
1446            return im;
1447        } else if(s.length() > 0 && s.charAt(0) == '\0') {
1448            Image im = new Image(block);
1449            im.setColor(scc.filter(color));
1450            //im.setSize(width * s.length(), height - MathUtils.ceil(bmpFont.getDescent() / 2f));
1451            im.setSize(actualCellWidth * s.length(), actualCellHeight + (distanceField ? 1 : 0)); //   - lineHeight / actualCellHeight //+ lineTweak * 1f
1452            // im.setPosition(x - width * 0.5f, y - height * 0.5f, Align.center);
1453            return im;
1454        } else {
1455            Label lb = new Label(s, style);
1456            //lb.setFontScale(bmpFont.getData().scaleX, bmpFont.getData().scaleY);
1457            lb.setSize(width * s.length(), height - descent); //+ lineTweak * 1f
1458            lb.setColor(scc.filter(color));
1459            // lb.setPosition(x - width * 0.5f, y - height * 0.5f, Align.center);
1460            return lb;
1461        }
1462    }
1463
1464    /**
1465     * Converts a String into a ColorChangeLabel, or if the argument s is null, creates a ColorChangeImage of a solid
1466     * block. Can be used for preparing glyphs for animation effects, and is used internally for this purpose. The
1467     * ColorChange classes will rotate between all colors given in the List each second, and are not affected by setColor,
1468     * though they are affected by their setColors methods. Their color change is not considered an animation for the
1469     * purposes of things like SquidPanel.hasActiveAnimations() .
1470     * @param s a String to make into an Actor, which can be null for a solid block.
1471     * @param colors a List of Color to tint s with, looping through all elements in the list each second
1472     * @return the Actor, with no position set.
1473     */
1474    public Actor makeActor(String s, Collection<Color> colors) {
1475        return makeActor(s, colors, 2f, false);
1476    }
1477    /**
1478     * Converts a String into a ColorChangeLabel, or if the argument s is null, creates a ColorChangeImage of a solid
1479     * block. Can be used for preparing glyphs for animation effects, and is used internally for this purpose. The
1480     * ColorChange classes will rotate between all colors given in the List each second, and are not affected by setColor,
1481     * though they are affected by their setColors methods. Their color change is not considered an animation for the
1482     * purposes of things like SquidPanel.hasActiveAnimations() .
1483     * @param s a String to make into an Actor, which can be null for a solid block.
1484     * @param colors a List of Color to tint s with, looping through all elements in the list each second
1485     * @param loopTime the amount of time, in seconds, to spend looping through all colors in the list
1486     * @return the Actor, with no position set.
1487     */
1488    public Actor makeActor(String s, Collection<Color> colors, float loopTime)
1489    {
1490        return makeActor(s, colors, loopTime, false);
1491    }
1492    /**
1493     * Converts a String into a ColorChangeLabel, or if the argument s is null, creates a ColorChangeImage of a solid
1494     * block. Can be used for preparing glyphs for animation effects, and is used internally for this purpose. The
1495     * ColorChange classes will rotate between all colors given in the List each second, and are not affected by setColor,
1496     * though they are affected by their setColors methods. Their color change is not considered an animation for the
1497     * purposes of things like SquidPanel.hasActiveAnimations() .
1498     * @param s a String to make into an Actor, which can be null for a solid block.
1499     * @param colors a List of Color to tint s with, looping through all elements in the list each second
1500     * @param loopTime the amount of time, in seconds, to spend looping through all colors in the list
1501     * @return the Actor, with no position set.
1502     */
1503    public Actor makeActor(String s, Collection<Color> colors, float loopTime, boolean doubleWidth) {
1504        if (!initialized) {
1505            throw new IllegalStateException("This factory has not yet been initialized!");
1506        }
1507        ArrayList<Color> colors2 = null;
1508        if(colors != null && !colors.isEmpty())
1509        {
1510            colors2 = new ArrayList<>(colors.size());
1511            for (Color c : colors) {
1512                colors2.add(scc.filter(c));
1513            }
1514        }
1515        if (s == null) {
1516            ColorChangeImage im = new ColorChangeImage(block, loopTime, doubleWidth, colors2);
1517            //im.setSize(width, height - MathUtils.ceil(bmpFont.getDescent() / 2f));
1518            im.setSize(actualCellWidth * (doubleWidth ? 2 : 1), actualCellHeight + (distanceField ? 1 : 0)); //  - lineHeight / actualCellHeight //+ lineTweak * 1f
1519            // im.setPosition(x - width * 0.5f, y - height * 0.5f, Align.center);
1520            return im;
1521        } else if(s.length() > 0 && s.charAt(0) == '\0') {
1522            ColorChangeImage im = new ColorChangeImage(block, loopTime, doubleWidth, colors2);
1523            //im.setSize(width * s.length(), height - MathUtils.ceil(bmpFont.getDescent() / 2f));
1524            im.setSize(actualCellWidth * s.length() * (doubleWidth ? 2 : 1), actualCellHeight + (distanceField ? 1 : 0)); //   - lineHeight / actualCellHeight //+ lineTweak * 1f
1525            // im.setPosition(x - width * 0.5f, y - height * 0.5f, Align.center);
1526            return im;
1527        } else {
1528            ColorChangeLabel lb = new ColorChangeLabel(s, style, loopTime, doubleWidth, colors2);
1529            lb.setSize(width * s.length(), height - descent); //+ lineTweak * 1f
1530            // lb.setPosition(x - width * 0.5f, y - height * 0.5f, Align.center);
1531            return lb;
1532        }
1533    }
1534
1535    /**
1536     * Converts a char into a Label, or if the argument c is '\0', creates an Image of a solid block. Can be used
1537     * for preparing glyphs for animation effects, and is used internally for this purpose.
1538     * @param c a char to make into an Actor, which can be the character with Unicode value 0 for a solid block.
1539     * @param color a Color to tint c with.
1540     * @return the Actor, with no position set.
1541     */
1542    public Actor makeActor(char c, Color color) {
1543        if (!initialized) {
1544            throw new IllegalStateException("This factory has not yet been initialized!");
1545        }
1546        if (c == 0) {
1547            Image im = new Image(block);
1548            im.setColor(scc.filter(color));
1549            //im.setSize(width, height - MathUtils.ceil(bmpFont.getDescent() / 2f));
1550            im.setSize(actualCellWidth, actualCellHeight + (distanceField ? 1 : 0)); //  - lineHeight / actualCellHeight //+ lineTweak * 1f
1551            // im.setPosition(x - width * 0.5f, y - height * 0.5f, Align.center);
1552            return im;
1553        } else {
1554            mut.setCharAt(0, getOrDefault(c));
1555            Label lb = new Label(mut, style);
1556            lb.setSize(width, height - descent); //+ lineTweak * 1f
1557            lb.setColor(scc.filter(color));
1558            return lb;
1559        }
1560    }
1561
1562    /**
1563     * Converts a char into a Label, or if the argument c is '\0', creates an Image of a solid block. Can be used
1564     * for preparing glyphs for animation effects, and is used internally for this purpose. Instead of a libGDX Color
1565     * object, this takes an encoded float that represents a color as libGDX often does internally, ABGR-packed format.
1566     * You can use various methods in SColor to produce these, like {@link SColor#floatGet(float, float, float, float)}
1567     * or {@link Color#toFloatBits()}.
1568     * @param c a char to make into an Actor, which can be the character with Unicode value 0 for a solid block.
1569     * @param encodedColor an ABGR packed float (as produced by {@link SColor#floatGet(float, float, float, float)}) to use as c's color
1570     * @return the Actor, with no position set.
1571     */
1572    public Actor makeActor(char c, float encodedColor) {
1573        if (!initialized) {
1574            throw new IllegalStateException("This factory has not yet been initialized!");
1575        }
1576        if (c == 0) {
1577            Image im = new Image(block);
1578            Color.abgr8888ToColor(im.getColor(), encodedColor);
1579            //im.setSize(width, height - MathUtils.ceil(bmpFont.getDescent() / 2f));
1580            im.setSize(actualCellWidth, actualCellHeight + (distanceField ? 1 : 0)); //  - lineHeight / actualCellHeight //+ lineTweak * 1f
1581            // im.setPosition(x - width * 0.5f, y - height * 0.5f, Align.center);
1582            return im;
1583        } else {
1584            mut.setCharAt(0, getOrDefault(c));
1585            Label lb = new Label(mut, style);
1586            //lb.setFontScale(bmpFont.getData().scaleX, bmpFont.getData().scaleY);
1587            lb.setSize(width, height - descent); //+ lineTweak * 1f
1588            Color.abgr8888ToColor(lb.getColor(), encodedColor);
1589            // lb.setPosition(x - width * 0.5f, y - height * 0.5f, Align.center);
1590            return lb;
1591        }
1592    }
1593
1594    /**
1595     * Converts a char into a ColorChangeLabel, or if the argument c is '\0', creates a ColorChangeImage of a solid
1596     * block. Can be used for preparing glyphs for animation effects, and is used internally for this purpose. The
1597     * ColorChange classes will rotate between all colors given in the List each second, and are not affected by setColor,
1598     * though they are affected by their setColors methods. Their color change is not considered an animation for the
1599     * purposes of things like SquidPanel.hasActiveAnimations() .
1600     * @param c a char to make into an Actor, which can be the character with Unicode value 0 for a solid block.
1601     * @param colors a List of Color to tint c with, looping through all elements in the list each second
1602     * @return the Actor, with no position set.
1603     */
1604    public Actor makeActor(char c, Collection<Color> colors) {
1605        return makeActor(c, colors, 2f, false);
1606    }
1607    /**
1608     * Converts a char into a ColorChangeLabel, or if the argument c is '\0', creates a ColorChangeImage of a solid
1609     * block. Can be used for preparing glyphs for animation effects, and is used internally for this purpose. The
1610     * ColorChange classes will rotate between all colors given in the List each second, and are not affected by setColor,
1611     * though they are affected by their setColors methods. Their color change is not considered an animation for the
1612     * purposes of things like SquidPanel.hasActiveAnimations() .
1613     * @param c a char to make into an Actor, which can be the character with Unicode value 0 for a solid block.
1614     * @param colors a List of Color to tint c with, looping through all elements in the list each second
1615     * @param loopTime the amount of time, in seconds, to spend looping through all colors in the list
1616     * @return the Actor, with no position set.
1617     */
1618    public Actor makeActor(char c, Collection<Color> colors, float loopTime)
1619    {
1620        return makeActor(c, colors, loopTime, false);
1621    }
1622    /**
1623     * Converts a char into a ColorChangeLabel, or if the argument c is '\0', creates a ColorChangeImage of a solid
1624     * block. Can be used for preparing glyphs for animation effects, and is used internally for this purpose. The
1625     * ColorChange classes will rotate between all colors given in the List each second, and are not affected by setColor,
1626     * though they are affected by their setColors methods. Their color change is not considered an animation for the
1627     * purposes of things like SquidPanel.hasActiveAnimations() .
1628     * @param c a char to make into an Actor, which can be the character with Unicode value 0 for a solid block.
1629     * @param colors a List of Color to tint c with, looping through all elements in the list each second
1630     * @param loopTime the amount of time, in seconds, to spend looping through all colors in the list
1631     * @return the Actor, with no position set.
1632     */
1633    public Actor makeActor(char c, Collection<Color> colors, float loopTime, boolean doubleWidth) {
1634        if (!initialized) {
1635            throw new IllegalStateException("This factory has not yet been initialized!");
1636        }
1637        ArrayList<Color> colors2 = null;
1638        if(colors != null && !colors.isEmpty())
1639        {
1640            colors2 = new ArrayList<>(colors.size());
1641            for (Color color : colors) {
1642                colors2.add(scc.filter(color));
1643            }
1644        }
1645        if (c == 0) {
1646            ColorChangeImage im = new ColorChangeImage(block, loopTime, doubleWidth, colors2);
1647            //im.setSize(width, height - MathUtils.ceil(bmpFont.getDescent() / 2f));
1648            im.setSize(actualCellWidth * (doubleWidth ? 2 : 1), actualCellHeight + (distanceField ? 1 : 0)); //  - lineHeight / actualCellHeight //+ lineTweak * 1f
1649            // im.setPosition(x - width * 0.5f, y - height * 0.5f, Align.center);
1650            return im;
1651        } else {
1652            mut.setCharAt(0, getOrDefault(c));
1653            ColorChangeLabel lb = new ColorChangeLabel(mut, style, loopTime, doubleWidth, colors2);
1654            lb.setSize(width, height - descent); //+ lineTweak * 1f
1655            // lb.setPosition(x - width * 0.5f, y - height * 0.5f, Align.center);
1656            return lb;
1657        }
1658    }
1659    public Actor makeActor(TextureRegion tr, Collection<Color> colors)
1660    {
1661        return makeActor(tr, colors, 2f, false);
1662    }
1663    public Actor makeActor(TextureRegion tr, Collection<Color> colors, float loopTime)
1664    {
1665        return makeActor(tr, colors, loopTime, false);
1666    }
1667    /**
1668     * Converts a TextureRegion into a ColorChangeImage that will cycle through the given colors.
1669     * ColorChange classes will rotate between all colors given in the List each loopTime, and are not affected by
1670     * setColor, though they are affected by their setColors methods. Their color change is not considered an animation
1671     * for the purposes of things like SquidPanel.hasActiveAnimations() .
1672     * @param tr a TextureRegion to make into an Actor, which can be null for a solid block.
1673     * @param colors a List of Color to tint c with, looping through all elements in the list each second
1674     * @param loopTime the amount of time, in seconds, to spend looping through all colors in the list
1675     * @return the Actor, with no position set.
1676     */
1677    public Actor makeActor(TextureRegion tr, Collection<Color> colors, float loopTime, boolean doubleWidth){
1678        return makeActor(tr, colors, loopTime, doubleWidth,
1679                actualCellWidth * (doubleWidth ? 2 : 1), actualCellHeight + (distanceField ? 1 : 0));
1680    }
1681
1682    /**
1683     * Converts a TextureRegion into a ColorChangeImage that will cycle through the given colors.
1684     * ColorChange classes will rotate between all colors given in the List each loopTime, and are not affected by
1685     * setColor, though they are affected by their setColors methods. Their color change is not considered an animation
1686     * for the purposes of things like SquidPanel.hasActiveAnimations() .
1687     * @param tr a TextureRegion to make into an Actor, which can be null for a solid block.
1688     * @param colors a List of Color to tint c with, looping through all elements in the list each second
1689     * @param loopTime the amount of time, in seconds, to spend looping through all colors in the list
1690     * @return the Actor, with no position set.
1691     */
1692    public Actor makeActor(TextureRegion tr, Collection<Color> colors, float loopTime, boolean doubleWidth,
1693                           float width, float height){
1694        if (!initialized) {
1695            throw new IllegalStateException("This factory has not yet been initialized!");
1696        }
1697        ArrayList<Color> colors2 = null;
1698        if(colors != null && !colors.isEmpty())
1699        {
1700            colors2 = new ArrayList<>(colors.size());
1701            for (Color color : colors) {
1702                colors2.add(scc.filter(color));
1703            }
1704        }
1705        if (tr == null) {
1706            ColorChangeImage im = new ColorChangeImage(block, loopTime, doubleWidth, colors2);
1707            im.setSize(width, height);
1708            return im;
1709        } else {
1710            ColorChangeImage im = new ColorChangeImage(tr, loopTime, doubleWidth, colors2);
1711            im.setSize(width, height);
1712            return im;
1713        }
1714    }
1715
1716    /**
1717     * Creates a ColorChangeImage Actor that should look like the glyph '^' in this font, but will be rotate-able. The
1718     * ColorChange classes will rotate between all colors given in the List in the given amount of loopTime, and are not
1719     * affected by setColor, though they are affected by their setColors methods. Their color change is not considered
1720     * an animation for the purposes of things like SquidPanel.hasActiveAnimations() .
1721     * @param colors a List of Color to tint the '^' with, looping through all elements in the list each second
1722     * @param loopTime the amount of time, in seconds, to spend looping through all colors in the list
1723     * @return the Actor, with no position set.
1724     */
1725    public Image makeDirectionMarker(Collection<Color> colors, float loopTime, boolean doubleWidth) {
1726        if (!initialized) {
1727            throw new IllegalStateException("This factory has not yet been initialized!");
1728        }
1729        ArrayList<Color> colors2 = null;
1730        if (colors != null && !colors.isEmpty()) {
1731            colors2 = new ArrayList<>(colors.size());
1732            for (Color c : colors) {
1733                colors2.add(scc.filter(c));
1734            }
1735        }
1736        ColorChangeImage im = new ColorChangeImage(dirMarker, loopTime, doubleWidth,
1737                actualCellWidth, actualCellHeight + (distanceField ? 1 : 0), colors2);
1738        im.setAlign(2);
1739        im.setSize(actualCellWidth * (doubleWidth ? 2 : 1), actualCellHeight + (distanceField ? 1 : 0)); //  - lineHeight / actualCellHeight //+ lineTweak * 1f
1740        return im;
1741    }
1742    /**
1743     * Creates a Image Actor that should look like the glyph '^' in this font, but will be rotate-able.
1744     * @param color a Color to tint the '^' with
1745     * @return the Actor, with no position set.
1746     */
1747    public Image makeDirectionMarker(Color color) {
1748        if (!initialized) {
1749            throw new IllegalStateException("This factory has not yet been initialized!");
1750        }
1751        Image im = new Image(dirMarker);
1752        im.setColor(scc.filter(color));
1753        im.setSize(actualCellWidth, actualCellHeight + (distanceField ? 1 : 0)); //  - lineHeight / actualCellHeight //+ lineTweak * 1f
1754        im.setOrigin(1); //center
1755        return im;
1756    }
1757
1758    /**
1759     * Given a char and a Color, makes a ColorChangeImage of that colored char so it can be rotated and so on.
1760     * @param glyph a char; should probably be visible, so not space or tab
1761     * @param color a Color to use for the image; will not change even though ColorChangeImage permits this
1762     * @return the ColorChangeImage with the one color for glyph
1763     */
1764    public ColorChangeImage makeGlyphImage(char glyph, Color color)
1765    {
1766        return makeGlyphImage(glyph, color, false);
1767    }
1768    /**
1769     * Given a char and a Color, makes a ColorChangeImage of that colored char so it can be rotated and so on.
1770     * This overload allows double-width mode to be specified.
1771     * @param glyph a char; should probably be visible, so not space or tab
1772     * @param color a Color to use for the image; will not change even though ColorChangeImage permits this
1773     * @param doubleWidth true if layout uses two chars of width per logical cell, false otherwise
1774     * @return the ColorChangeImage with the one color for glyph
1775     */
1776    public ColorChangeImage makeGlyphImage(char glyph, Color color, boolean doubleWidth)
1777    {
1778        if (!initialized) {
1779            throw new IllegalStateException("This factory has not yet been initialized!");
1780        }
1781        TextureRegion tr;
1782        if (glyphTextures.containsKey(glyph))
1783        {
1784            tr = glyphTextures.get(glyph);
1785        }
1786        else
1787        {
1788            BitmapFont.Glyph g = bmpFont.getData().getGlyph(glyph);
1789            tr = new TextureRegion(bmpFont.getRegion(g.page), g.srcX, g.srcY, g.width, g.height);
1790            glyphTextures.put(glyph, tr);
1791        }
1792
1793        ColorChangeImage im = new ColorChangeImage(tr, 1, doubleWidth,
1794                actualCellWidth, actualCellHeight + (distanceField ? 1 : 0), Maker.makeList(scc.filter(color)));
1795        im.setAlign(2);
1796        im.setSize(actualCellWidth * (doubleWidth ? 2 : 1), actualCellHeight + (distanceField ? 1 : 0)); //  - lineHeight / actualCellHeight //+ lineTweak * 1f
1797        return im;
1798    }
1799    /**
1800     * Given a char and a Collection of Color (such as a List that might be produced by
1801     * {@link SquidColorCenter#gradient(Color, Color)}), makes a ColorChangeImage of that char so it can be rotated and
1802     * so on. The colors will cycle through the given collection onceper {@code loopTime} seconds. You can specify
1803     * double-width mode if you use it, probably in SquidPanel or SquidLayers.
1804     * @param glyph a char; should probably be visible, so not space or tab
1805     * @param colors a Collection of Colors to use for the image; the image will cycle through them in order
1806     * @param loopTime the amount of time, in seconds, it should take for all colors to be cycled through
1807     * @param doubleWidth true if layout uses two chars of width per logical cell, false otherwise
1808     * @return the ColorChangeImage with the one color for glyph
1809     */
1810    public ColorChangeImage makeGlyphImage(char glyph, Collection<Color> colors, float loopTime, boolean doubleWidth) {
1811        if (!initialized) {
1812            throw new IllegalStateException("This factory has not yet been initialized!");
1813        }
1814        TextureRegion tr;
1815        if (glyphTextures.containsKey(glyph))
1816        {
1817            tr = glyphTextures.get(glyph);
1818        }
1819        else
1820        {
1821            BitmapFont.Glyph g = bmpFont.getData().getGlyph(glyph);
1822            tr = new TextureRegion(bmpFont.getRegion(g.page), g.srcX, g.srcY, g.width, g.height);
1823            glyphTextures.put(glyph, tr);
1824        }
1825        ArrayList<Color> colors2 = null;
1826        if (colors != null && !colors.isEmpty()) {
1827            colors2 = new ArrayList<>(colors.size());
1828            for (Color c : colors) {
1829                colors2.add(scc.filter(c));
1830            }
1831        }
1832        ColorChangeImage im = new ColorChangeImage(tr, loopTime, doubleWidth,
1833                actualCellWidth, actualCellHeight + (distanceField ? 1 : 0), colors2);
1834        im.setAlign(2);
1835        im.setSize(actualCellWidth * (doubleWidth ? 2 : 1), actualCellHeight + (distanceField ? 1 : 0)); //  - lineHeight / actualCellHeight //+ lineTweak * 1f
1836        return im;
1837    }
1838
1839    /**
1840     * Converts a TextureRegion into an Image, or if the argument s is null, creates an Image of a solid block. Can be
1841     * used for preparing images for animation effects. Stretches the TextureRegion to match a single cell's dimensions.
1842     * @param tr a TextureRegion to make into an Actor, which can be null for a solid block.
1843     * @param color a Color to tint tr with.
1844     * @return the Actor, with no position set.
1845     */
1846    public Actor makeActor(TextureRegion tr, Color color) {
1847        if (!initialized) {
1848            throw new IllegalStateException("This factory has not yet been initialized!");
1849        }
1850        if (tr == null) {
1851            Image im = new Image(block);
1852            im.setColor(scc.filter(color));
1853            im.setSize(width, height);
1854            // im.setPosition(x - width * 0.5f, y - height * 0.5f, Align.center);
1855            return im;
1856        } else {
1857            Image im = new Image(tr);
1858            im.setColor(scc.filter(color));
1859            im.setSize(width, height);
1860            // im.setPosition(x - width * 0.5f, y - height * 0.5f, Align.center);
1861            return im;
1862        }
1863    }
1864
1865    /**
1866     * Converts a TextureRegion into an Image, or if the argument s is null, creates an Image of a solid block. Can be
1867     * used for preparing images for animation effects. Ensures the returned Image has the given width and height.
1868     * @param tr a TextureRegion to make into an Actor, which can be null for a solid block.
1869     * @param color a Color to tint tr with.
1870     * @return the Actor, with no position set.
1871     */
1872    public Actor makeActor(TextureRegion tr, Color color, float width, float height) {
1873        if (!initialized) {
1874            throw new IllegalStateException("This factory has not yet been initialized!");
1875        }
1876        if (tr == null) {
1877            Image im = new Image(block);
1878            im.setColor(scc.filter(color));
1879            im.setSize(width, height);
1880            // im.setPosition(x - width * 0.5f, y - height * 0.5f, Align.center);
1881            return im;
1882        } else {
1883            Image im = new Image(tr);
1884            im.setColor(scc.filter(color));
1885            im.setSize(width, height);
1886            // im.setPosition(x - width * 0.5f, y - height * 0.5f, Align.center);
1887            return im;
1888        }
1889    }
1890
1891    /**
1892     * Returns a solid block of white, 1x1 pixel in size; can be drawn at other sizes by Batch.
1893     * @return a white 1x1 pixel Texture.
1894     */
1895    public TextureRegion getSolid() {
1896        if (!initialized) {
1897            throw new IllegalStateException("This factory has not yet been initialized!");
1898        }
1899        return block;
1900    }
1901    /**
1902     * Gets a Glyph with the given char to show, libGDX Color, and position as x and y in world coordinates.
1903     * Glyph is a kind of scene2d Actor that uses this TextCellFactory to handle its rendering instead of delegating
1904     * that to the Label class from scene2d.ui.
1905     * @param shown char to show; if this is the char with codepoint 0, then this will show a solid block
1906     * @param color the color as a libGDX Color; can also be an SColor or some other subclass of Color
1907     * @param x the x position of the Glyph in world coordinates, as would be passed to draw()
1908     * @param y the y position of the Glyph in world coordinates, as would be passed to draw()
1909     * @return a new Glyph that will use the specified char, color, and position
1910     */
1911
1912    public Glyph glyph(char shown, Color color, float x, float y)
1913    {
1914        return new Glyph(shown, color, x, y);
1915    }
1916
1917    /**
1918     * Gets a Glyph with the given char to show, color as a packed float, and position as x and y in world coordinates.
1919     * Glyph is a kind of scene2d Actor that uses this TextCellFactory to handle its rendering instead of delegating
1920     * that to the Label class from scene2d.ui.
1921     * @param shown char to show; if this is the char with codepoint 0, then this will show a solid block
1922     * @param encodedColor the encoded color as a float, as produced by {@link Color#toFloatBits()}
1923     * @param x the x position of the Glyph in world coordinates, as would be passed to draw()
1924     * @param y the y position of the Glyph in world coordinates, as would be passed to draw()
1925     * @return a new Glyph that will use the specified char, color, and position
1926     */
1927    public Glyph glyph(char shown, float encodedColor, float x, float y)
1928    {
1929        return new Glyph(shown, encodedColor, x, y);
1930    }
1931
1932    public boolean isMultiDistanceField() {
1933        return msdf;
1934    }
1935
1936    public boolean isDistanceField() {
1937        return distanceField;
1938    }
1939
1940    public float getDistanceFieldScaleX() {
1941        return distanceFieldScaleX;
1942    }
1943    public float getDistanceFieldScaleY() {
1944        return distanceFieldScaleY;
1945    }
1946
1947    /**
1948     * If this uses a distance field font, the smoothing multiplier affects how crisp or blurry lines are, with higher
1949     * numbers generally resulting in more crisp fonts, but numbers that are too high cause jagged aliasing. This starts
1950     * at 1.2f, usually ({@link DefaultResources} notes when a different value is used).
1951     * @return the current smoothing multiplier as a float, which starts at 1.2f for most fonts
1952     */
1953    public float getSmoothingMultiplier() {
1954        return smoothingMultiplier;
1955    }
1956
1957    /**
1958     * If this uses a distance field font, the smoothing multiplier affects how crisp or blurry lines are, with higher
1959     * numbers generally resulting in more crisp fonts, but numbers that are too high cause jagged aliasing. Before this
1960     * is called, the smoothing multiplier is usually 1.2f; {@link DefaultResources} notes when other values are used.
1961     * @param smoothingMultiplier the new value for the smoothing multiplier as a float; should be fairly close to 1f.
1962     * @return this for chaining
1963     */
1964    public TextCellFactory setSmoothingMultiplier(float smoothingMultiplier) {
1965        this.smoothingMultiplier = smoothingMultiplier;
1966        return this;
1967    }
1968
1969    /**
1970     * If using a distance field font, you MUST call this at some point while the batch has begun, or use code that
1971     * calls it for you (which is now much of SquidLib). A typical point to call it is in the
1972     * "void draw(Batch batch, float parentAlpha)" method or an overriding method for a Scene2D class. You should call
1973     * configureShader rarely, typically only a few times per frame if there are no images to render, and this means the
1974     * logical place to call it is in the outermost Group that contains any SquidPanel objects or other widgets. If you
1975     * have multipleTextCellFactory objects, each one needs to have configureShader called before it is used to draw.
1976     * <br>
1977     * SquidLayers, SparseLayers, SubcellLayers, SquidPanel, TextPanel, and ImageSquidPanel already call
1978     * this method in their draw overrides, so you don't need to call this manually if you use any of those. None of
1979     * those classes change the shader after they set it for their uses, so you may need to set the shader on your Batch
1980     * to null to revert to the default shader if you need to draw full-color art.
1981     * <br>
1982     * If you don't use a distance field font, you don't need to call this, but calling it won't cause problems.
1983     *
1984     * @param batch the Batch, such as a {@link FilterBatch}, to configure to render distance field fonts if necessary.
1985     */
1986    public void configureShader(Batch batch) {
1987        if(initialized)
1988        {
1989            if(msdf)
1990            {
1991                if(!shader.equals(batch.getShader())) 
1992                    batch.setShader(shader);
1993                shader.setUniformf("u_smoothing", 2.1198158f * smoothingMultiplier * bmpFont.getData().scaleX);
1994            }
1995            else if (distanceField) {
1996                if(!shader.equals(batch.getShader()))
1997                    batch.setShader(shader);
1998                shader.setUniformf("u_smoothing", 0.35f / (1.9f * smoothingMultiplier * (bmpFont.getData().scaleX + bmpFont.getData().scaleY)));
1999            }
2000        }
2001    }
2002    /**
2003     * Releases all resources of this object.
2004     */
2005    @Override
2006    public void dispose() {
2007        if(block != null) block.getTexture().dispose();
2008        if(bmpFont != null) bmpFont.dispose();
2009    }
2010
2011    /**
2012     * Gets the descent of this TextCellFactory's BitmapFont, which may be useful for layout outside this class.
2013     * @return the descent of the BitmapFont this object uses
2014     */
2015    public float getDescent() {
2016        return descent;
2017    }
2018
2019    public char getDirectionGlyph() {
2020        return directionGlyph;
2021    }
2022
2023    public TextCellFactory setDirectionGlyph(char directionGlyph) {
2024        this.directionGlyph = directionGlyph;
2025        return this;
2026    }
2027
2028    /**
2029     * Adds a pair of Strings (typically both with length 1) as a replacement pair, so when the find String is requested
2030     * to be drawn, the replace String is used instead. Swaps are used when drawing text in each cell in SquidPanel and
2031     * related classes, so Strings longer than 1 char are effectively disregarded beyond the first char.
2032     * <br>
2033     * This can be useful when you want to use certain defaults in squidlib-util's dungeon generation, like '~' for deep
2034     * water, but not others, like ',' for shallow water, and would rather have a glyph of your choice replace something
2035     * that would be drawn. Replacements will not be chained; that is, if you {@code addSwap("^", ",")} and also
2036     * {@code addSwap(",", ":")}, then a requested '^' will be drawn as ',', not ':', but a requested ',' will be drawn
2037     * as ':' (only one swap will be performed). Typically you want a different TextCellFactory for UI elements that use
2038     * swapping, like a top-down char-based map, and elements that should not, like those that display normal text.
2039     * @param find the requested String that will be changed
2040     * @param replace the replacement String that will be used in place of find
2041     * @return this for chaining
2042     */
2043    public TextCellFactory addSwap(String find, String replace)
2044    {
2045        if(find == null || replace == null || find.isEmpty()  || replace.isEmpty() || find.charAt(0) == 0)
2046            return this;
2047        swap.put(find.charAt(0), replace.charAt(0));
2048        return this;
2049    }
2050
2051    /**
2052     * Adds a pair of chars as a replacement pair, so when the find char is requested to be drawn, the replace char is
2053     * used instead.
2054     * <br>
2055     * This can be useful when you want to use certain defaults in squidlib-util's dungeon generation, like '~' for deep
2056     * water, but not others, like ',' for shallow water, and would rather have a glyph of your choice replace something
2057     * that would be drawn. Replacements will not be chained; that is, if you {@code addSwap('^', ',')} and also
2058     * {@code addSwap(',', ':')}, then a requested '^' will be drawn as ',', not ':', but a requested ',' will be drawn
2059     * as ':' (only one swap will be performed). Typically you want a different TextCellFactory for UI elements that use
2060     * swapping, like a top-down char-based map, and elements that should not, like those that display normal text.
2061     * @param find the requested char that will be changed (converted to a length-1 String)
2062     * @param replace the replacement char that will be used in place of find (converted to a length-1 String)
2063     * @return this for chaining
2064     */
2065    public TextCellFactory addSwap(char find, char replace)
2066    {
2067        if(find == 0)
2068            return this;
2069        swap.put(find, replace);
2070        return this;
2071    }
2072
2073    /**
2074     * Removes the replacement pair, if present, that searches for the given key, find. Swaps are used when drawing text
2075     * in each cell in SquidPanel and related classes, so Strings longer than 1 char are effectively disregarded beyond
2076     * the first char.
2077     * <br>
2078     * This can be useful when you want to use certain defaults in squidlib-util's dungeon generation, like '~' for deep
2079     * water, but not others, like ',' for shallow water, and would rather have a glyph of your choice replace something
2080     * that would be drawn. Replacements will not be chained; that is, if you {@code addSwap('^', ',')} and also
2081     * {@code addSwap(',', ':')}, then a requested '^' will be drawn as ',', not ':', but a requested ',' will be drawn
2082     * as ':' (only one swap will be performed). Typically you want a different TextCellFactory for UI elements that use
2083     * swapping, like a top-down char-based map, and elements that should not, like those that display normal text.
2084     * @param find the String that would be changed in the replacement pair
2085     * @return this for chaining
2086     */
2087    public TextCellFactory removeSwap(String find)
2088    {
2089
2090        if(find != null && !find.isEmpty() && find.charAt(0) != 0)
2091            swap.remove(find.charAt(0));
2092        return this;
2093    }
2094
2095    /**
2096     * Removes the replacement pair, if present, that searches for the given key, find.
2097     * <br>
2098     * This can be useful when you want to use certain defaults in squidlib-util's dungeon generation, like '~' for deep
2099     * water, but not others, like ',' for shallow water, and would rather have a glyph of your choice replace something
2100     * that would be drawn. Replacements will not be chained; that is, if you {@code addSwap('^', ',')} and also
2101     * {@code addSwap(',', ':')}, then a requested '^' will be drawn as ',', not ':', but a requested ',' will be drawn
2102     * as ':' (only one swap will be performed). Typically you want a different TextCellFactory for UI elements that use
2103     * swapping, like a top-down char-based map, and elements that should not, like those that display normal text.
2104     * @return this for chaining
2105     */
2106    public TextCellFactory removeSwap(char find)
2107    {
2108        swap.remove(find);
2109        return this;
2110    }
2111
2112    /**
2113     * Gets the current mapping of "swaps", or replacement pairs, to replace keys requested for drawing with their
2114     * values in the {@link CharCharMap}. CharCharMap is a class from RegExodus (a dependency of squidlib-util that is
2115     * used for text matching and Unicode support), which is used here to avoid making yet another primitive-backed
2116     * collection class.
2117     * <br>
2118     * This can be useful when you want to use certain defaults in squidlib-util's dungeon generation, like '~' for deep
2119     * water, but not others, like ',' for shallow water, and would rather have a glyph of your choice replace something
2120     * that would be drawn. Replacements will not be chained; that is, if you {@code addSwap('^', ',')} and also
2121     * {@code addSwap(',', ':')}, then a requested '^' will be drawn as ',', not ':', but a requested ',' will be drawn
2122     * as ':' (only one swap will be performed). Typically you want a different TextCellFactory for UI elements that use
2123     * swapping, like a top-down char-based map, and elements that should not, like those that display normal text.
2124     * @return the mapping of replacement pairs
2125     */
2126    public CharCharMap getAllSwaps() {
2127        return swap;
2128    }
2129
2130    /**
2131     * Sets the mapping of replacement pairs to a different one as a Map of Character keys to String values.
2132     * <br>
2133     * This can be useful when you want to use certain defaults in squidlib-util's dungeon generation, like '~' for deep
2134     * water, but not others, like ',' for shallow water, and would rather have a glyph of your choice replace something
2135     * that would be drawn. Replacements will not be chained; that is, if you {@code addSwap('^', ',')} and also
2136     * {@code addSwap(',', ':')}, then a requested '^' will be drawn as ',', not ':', but a requested ',' will be drawn
2137     * as ':' (only one swap will be performed). Typically you want a different TextCellFactory for UI elements that use
2138     * swapping, like a top-down char-based map, and elements that should not, like those that display normal text.
2139     * @param swaps the Map of replacement pairs; keys requested for drawing will be replaced with their values
2140     * @return this for chaining
2141     */
2142    public TextCellFactory setAllSwaps(OrderedMap<Character, Character> swaps) {
2143        swap.clear();
2144        for (int i = 0; i < swaps.size(); i++) {
2145            if(!swaps.keyAt(i).equals('\0'))
2146                swap.put(swaps.keyAt(i), swaps.getAt(i));
2147        }
2148        return this;
2149    }
2150
2151    /**
2152     * Appends to the mapping of replacement pairs, adding or replacing any entries in the current mapping with the
2153     * entries in a Map of Character keys to String values.
2154     * <br>
2155     * This can be useful when you want to use certain defaults in squidlib-util's dungeon generation, like '~' for deep
2156     * water, but not others, like ',' for shallow water, and would rather have a glyph of your choice replace something
2157     * that would be drawn. Replacements will not be chained; that is, if you {@code addSwap('^', ',')} and also
2158     * {@code addSwap(',', ':')}, then a requested '^' will be drawn as ',', not ':', but a requested ',' will be drawn
2159     * as ':' (only one swap will be performed). Typically you want a different TextCellFactory for UI elements that use
2160     * swapping, like a top-down char-based map, and elements that should not, like those that display normal text.
2161     * @param swaps the Map of replacement pairs to add; keys requested for drawing will be replaced with their values
2162     * @return this for chaining
2163     */
2164    public TextCellFactory addSwaps(OrderedMap<Character, Character> swaps) {
2165        for (int i = 0; i < swaps.size(); i++) {
2166            if(!swaps.keyAt(i).equals('\0'))
2167                swap.put(swaps.keyAt(i), swaps.getAt(i));
2168        }
2169        return this;
2170    }
2171
2172    /**
2173     * Clears all replacement pairs this has been told to swap.
2174     * <br>
2175     * This can be useful when you want to use certain defaults in squidlib-util's dungeon generation, like '~' for deep
2176     * water, but not others, like ',' for shallow water, and would rather have a glyph of your choice replace something
2177     * that would be drawn. Replacements will not be chained; that is, if you {@code addSwap('^', ',')} and also
2178     * {@code addSwap(',', ':')}, then a requested '^' will be drawn as ',', not ':', but a requested ',' will be drawn
2179     * as ':' (only one swap will be performed). Typically you want a different TextCellFactory for UI elements that use
2180     * swapping, like a top-down char-based map, and elements that should not, like those that display normal text.
2181     * @return this for chaining
2182     */
2183    public TextCellFactory clearSwaps()
2184    {
2185        swap.clear();
2186        return this;
2187    }
2188
2189    /**
2190     * Not implemented in this class; in subclasses that do, this should change the currently-used font style, such as
2191     * from regular to italic or bold. Calling this method on a TextCellFactory does nothing, but won't cause problems.
2192     * @param style an int, typically a constant in a class that implements this, that determines what style to use.
2193     */
2194    public void setStyle(int style)
2195    {
2196    }
2197
2198    /**
2199     * A kind of Actor for one char (with one color) that is innately drawn with a specific TextCellFactory, and will
2200     * match the layout behavior of that TextCellFactory when it is used for other purposes. This is an inner class of
2201     * TextCellFactory, and can't be constructed without a TextCellFactory being involved; usually you instantiate
2202     * Glyphs with {@link #glyph(char, Color, float, float)} or an overload of that method on TextCellFactory.
2203     */
2204    public class Glyph extends Actor implements ICellVisible
2205    {
2206        /**
2207         * The char that will be shown for this Glyph.
2208         */
2209        public char shown;
2210        
2211        @Override
2212        public char getSymbol()
2213        {
2214            return shown;
2215        }
2216
2217        /**
2218         * Makes an orange '@' Glyph at 0,0 in world coordinates.
2219         */
2220        public Glyph() {
2221            this('@', SColor.SAFETY_ORANGE, 0f, 0f);
2222        }
2223
2224        /**
2225         * Makes a Glyph of the given char in the given Color, at the specified world coordinates.
2226         * @param shown the char to show
2227         * @param color the Color to use; if null this will instead use {@link SColor#TRANSPARENT}
2228         * @param x x position in world coordinates
2229         * @param y y position in world coordinates
2230         */
2231        public Glyph(char shown, Color color, float x, float y)
2232        {
2233            super();
2234            this.shown = shown;
2235            super.getColor().set(color == null ? SColor.TRANSPARENT : color);
2236            setPosition(x, y);
2237        }
2238
2239        /**
2240         * Makes a Glyph of the given char in the given packed float color, at the specified world coordinates.
2241         * @param shown the char to show
2242         * @param color the packed float color to use, as produced by {@link Color#toFloatBits()}
2243         * @param x x position in world coordinates
2244         * @param y y position in world coordinates
2245         */
2246        public Glyph(char shown, float color, float x, float y) {
2247            super();
2248            this.shown = shown;
2249            Color.abgr8888ToColor(super.getColor(), color);
2250            setPosition(x, y);
2251        }
2252
2253        /**
2254         * Gets the color of this Glyph as a packed float color; does not allocate any objects.
2255         * @return the color of this Glyph as a packed float
2256         */
2257        @Override
2258        public float getPackedColor() {
2259            return getColor().toFloatBits();
2260        }
2261
2262        /**
2263         * Sets the color of this Glyph with the given packed float color; does not allocate any objects.
2264         * @param color the color to set this Glyph to, as a packed float
2265         */
2266        public void setPackedColor(float color) {
2267            Color.abgr8888ToColor(super.getColor(), color);
2268        }
2269
2270        @Override
2271        public String toString() {
2272            return "Glyph{'" +
2273                    + shown +
2274                    "' with color " + getColor() +
2275                    ", position (" + getX() +
2276                    "," + getY() +
2277                    ")}";
2278        }
2279
2280        /**
2281         * Draws the actor. The batch is configured to draw in the parent's coordinate system.
2282         * {@link Batch#begin()} must have already been called on the batch before this method.
2283         *
2284         * @param batch the batch should be between begin() and end(), usually handled by Stage
2285         * @param parentAlpha Multiplied with this Glyph's alpha, allowing a parent's alpha to affect all children.
2286         */
2287        @Override
2288        public void draw(Batch batch, float parentAlpha) {
2289            TextCellFactory.this.draw(batch, shown, SColor.multiplyAlpha(getColor(), parentAlpha), getX(), getY() + 1);
2290        }
2291    }
2292}