Skip to content

Instantly share code, notes, and snippets.

@jamesu
Last active January 22, 2024 13:42
Show Gist options
  • Select an option

  • Save jamesu/136f47fcf20b0b775b3e6e60da241aba to your computer and use it in GitHub Desktop.

Select an option

Save jamesu/136f47fcf20b0b775b3e6e60da241aba to your computer and use it in GitHub Desktop.

Revisions

  1. jamesu renamed this gist Jan 22, 2024. 1 changed file with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions AUTHORS → STB Font Prototype Code
    Original file line number Diff line number Diff line change
    @@ -1 +1,3 @@
    AUTHORS

    James S Urquhart ([email protected])
  2. jamesu created this gist Jan 22, 2024.
    1 change: 1 addition & 0 deletions AUTHORS
    Original file line number Diff line number Diff line change
    @@ -0,0 +1 @@
    James S Urquhart ([email protected])
    480 changes: 480 additions & 0 deletions dglFontMeshBuilder.h
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,480 @@
    //-----------------------------------------------------------------------------
    // Copyright (c) 2023 tgemit contributors.
    // See AUTHORS file and git repository for contributor information.
    //
    // SPDX-License-Identifier: MIT
    //-----------------------------------------------------------------------------
    #pragma once

    #include "dgl/gFont.h"

    /// Helper class which turns text into a mesh for rendering
    class DGLFontMeshBuilder
    {
    protected:
    enum
    {
    SheetChunkSize = 4096,

    COLOR_INDEX_BITMAP_MODULATION=0,
    COLOR_INDEX_ANCHOR=1,
    COLOR_INDEX_COLOR_TABLE=2,
    };

    struct DrawChar
    {
    Point2F pos; // NOTE: non-scaled
    U16 charCode;
    U16 colorIndex;
    };

    struct DrawSheet
    {
    U16 bitmapIndex;
    U16 charsUsed;
    DrawChar firstChar;

    DrawChar* getChars()
    {
    return &firstChar;
    }
    };

    /// Main font we are drawing with
    GFont* mFont;

    /// Allocator for DrawSheet
    DataChunker mSheetChunker;

    /// Sheets to output to
    Vector<DrawSheet*> mDrawSheets;

    /// Sheets we have outputted
    Vector<DrawSheet*> mOutputSheets;

    /// Color State
    /// {
    ColorF mBitmapModulation;

    ColorF mAnchorColor;

    const ColorI* mColorTable;

    U32 mColorTableSize;

    U32 mColorIndex;

    U32 mSavedColorIndex;
    /// }

    /// Offset we start at
    Point2F mDrawOrigin;

    /// Where we are
    Point2F mCurrentPoint;

    MatrixF mBaseTransform;

    public:

    DGLFontMeshBuilder() :
    mFont(NULL),
    mBitmapModulation(1.0f,1.0f,1.0f,1.0f),
    mAnchorColor(1.0f,1.0f,1.0f,1.0f),
    mColorTable(NULL),
    mColorTableSize(0),
    mColorIndex(0),
    mSavedColorIndex(0),
    mDrawOrigin(0,0),
    mCurrentPoint(0,0),
    mBaseTransform(1)
    {
    }

    RectI getDrawBounds()
    {
    Point2F minP = Point2F(F32_MAX, F32_MAX);
    Point2F maxP = Point2F(F32_MIN, F32_MIN);

    for (DrawSheet* outSheet : mOutputSheets)
    {
    for (S32 i=0; i<outSheet->charsUsed; i += std::max<S32>(outSheet->charsUsed-1, 1))
    {
    auto info = outSheet->getChars()[i];
    const PlatformFont::CharInfo &ci = mFont->getCharInfo(info.charCode);

    Point2F ep1 = info.pos;
    Point2F ep2 = info.pos + Point2F(ci.width, ci.height);
    minP.x = std::min(minP.x, ep1.x);
    minP.y = std::min(minP.y, ep1.y);

    maxP.x = std::max(maxP.x, ep1.x);
    maxP.y = std::max(maxP.y, ep1.y);

    minP.x = std::min(minP.x, ep2.x);
    minP.y = std::min(minP.y, ep2.y);

    maxP.x = std::max(maxP.x, ep2.x);
    maxP.y = std::max(maxP.y, ep2.y);
    }
    }

    return RectI(mDrawOrigin.x + minP.x, mDrawOrigin.y + minP.y, maxP.x - minP.x, maxP.y - minP.y);
    }

    inline void setLastDrawPoint(const Point2F point) { mCurrentPoint = point; }
    inline Point2F getLastDrawPoint() const { return mCurrentPoint; }

    inline bool getDrawSheet(DrawSheet** outSheet, U32 bitmapIndex)
    {
    if (bitmapIndex >= mDrawSheets.size())
    {
    *outSheet = NULL;
    return false;
    }

    DrawSheet* sheet = mDrawSheets[bitmapIndex];
    if (!sheet)
    {
    sheet = allocSheet(bitmapIndex);
    mDrawSheets[bitmapIndex] = sheet;
    }

    *outSheet = sheet;
    return true;
    }

    /// Returns color for index
    inline DGLPackedPointU16 getColor(U32 index)
    {
    switch (index)
    {
    case COLOR_INDEX_BITMAP_MODULATION:
    return mBitmapModulation;
    break;
    case COLOR_INDEX_ANCHOR:
    return mAnchorColor;
    break;
    default:
    U32 realIndex = index-COLOR_INDEX_COLOR_TABLE;
    return mColorTable ? mColorTable[realIndex] : ColorI(255,255,255,255);
    break;
    }
    }

    /// Allocate a sheet to draw
    DrawSheet* allocSheet(U16 bitmapIndex)
    {
    const U32 allocSize = sizeof(DrawSheet) + (sizeof(DrawChar) * SheetChunkSize);
    DrawSheet* sheet = (DrawSheet*)mSheetChunker.alloc(allocSize);
    sheet->bitmapIndex = bitmapIndex;
    memset(sheet, '\0', allocSize);

    mOutputSheets.push_back(sheet);

    return sheet;
    }

    /// Begin drawing a line at origin
    void begin(GFont* font, Point2I origin, F32 rot=0.0f, const ColorI* colorTable=NULL, U32 maxColorIndex=0)
    {
    clear();

    mFont = font;
    dglGetTextAnchorColor(&mAnchorColor);
    dglGetBitmapModulation(&mBitmapModulation);
    mColorIndex = COLOR_INDEX_BITMAP_MODULATION;
    mSavedColorIndex = COLOR_INDEX_BITMAP_MODULATION;
    mColorTable = colorTable;
    mColorTableSize = maxColorIndex;

    mDrawSheets.setSize(font->mTextureSheets.size());
    for (auto itr = mDrawSheets.begin(), itrEnd = mDrawSheets.end(); itr != itrEnd; itr++)
    {
    *itr = NULL;
    }

    mDrawOrigin = Point2F(origin.x, origin.y);
    mCurrentPoint = Point2F(0,0); // relative to mDrawOrigin
    mBaseTransform = MatrixF( EulerF( 0.0, 0.0, mDegToRad( rot ) ) );
    }

    /// Add text to batch
    U32 addText(U32 numChars, const UTF16* chars)
    {
    const F32 invMetricScale = mFont->getInvMetricScale();
    const F32 invTexScale = 1.0f / mFont->getTextureScale();

    Point2F pt = mCurrentPoint;
    DrawSheet* currentSheet = NULL;
    U32 lastBitmapIndex = UINT_MAX;
    U32 i = 0;

    for (i=0; i<numChars; i++)
    {
    UTF16 c = chars[i];

    // We have to do a little dance here since \t = 0x9, \n = 0xa, and \r = 0xd
    if ((c >= 1 && c <= 7) ||
    (c >= 11 && c <= 12) ||
    (c == 14))
    {
    // Color code
    if (mColorTable)
    {
    static U8 remap[15] =
    {
    0x0, // 0 special null terminator
    0x0, // 1 ascii start-of-heading??
    0x1,
    0x2,
    0x3,
    0x4,
    0x5,
    0x6,
    0x0, // 8 special backspace
    0x0, // 9 special tab
    0x0, // a special \n
    0x7,
    0x8,
    0x0, // a special \r
    0x9
    };

    U8 remapped = remap[c];
    // Ignore if the color is greater than the specified max index:
    if ( remapped <= mColorTableSize )
    {
    mColorIndex = COLOR_INDEX_COLOR_TABLE + remapped;
    }
    }
    continue;
    }

    // reset color?
    if ( c == 15 )
    {
    mColorIndex = COLOR_INDEX_ANCHOR;
    continue;
    }

    // push color:
    if ( c == 16 )
    {
    mSavedColorIndex = mColorIndex;
    continue;
    }

    // pop color:
    if ( c == 17 )
    {
    mColorIndex = mSavedColorIndex;
    continue;
    }

    // Tab character
    if ( c == dT('\t') )
    {
    const PlatformFont::CharInfo &ci = mFont->getCharInfo( dT(' ') );
    pt.x += ci.xIncrement * GFont::TabWidthInSpaces * invMetricScale;
    continue;
    }

    if( !mFont->isValidChar( c ) )
    continue;

    const PlatformFont::CharInfo &ci = mFont->getCharInfo(c);
    const F32 realXOrigin = ci.xOrigin * invMetricScale;
    const F32 realYOrigin = ci.yOrigin * invMetricScale;

    if (ci.bitmapIndex == -1)
    {
    pt.x += (realXOrigin * invTexScale * invMetricScale) + (ci.xIncrement * invMetricScale);
    continue;
    }

    if (ci.bitmapIndex != lastBitmapIndex)
    {
    // Grab sheet
    if (!getDrawSheet(&currentSheet, ci.bitmapIndex))
    {
    lastBitmapIndex = UINT_MAX;
    continue;
    }

    lastBitmapIndex = ci.bitmapIndex;
    }

    if (ci.width != 0 && ci.height != 0)
    {
    pt.y = mFont->getBaseline() - (realYOrigin * invTexScale);
    F32 charExtra = realXOrigin * invTexScale;
    pt.x += charExtra;

    DrawChar outChar = {};
    outChar.pos = pt;
    outChar.colorIndex = mColorIndex;
    outChar.charCode = c;

    currentSheet->getChars()[currentSheet->charsUsed++] = outChar;

    // End sheet if full
    if (currentSheet->charsUsed >= SheetChunkSize)
    {
    mDrawSheets[ci.bitmapIndex] = NULL;
    currentSheet = NULL;
    lastBitmapIndex = UINT_MAX;
    }

    pt.x += (ci.xIncrement * invMetricScale) - charExtra;
    }
    else
    {
    pt.x += ci.xIncrement * invMetricScale;
    }
    }

    mCurrentPoint = pt;
    return i;
    }

    /// Fill in mesh info
    void fillAllocParams(DGLMeshAllocParams& params)
    {
    U32 vertCount = 0;
    U32 indCount = 0;
    U32 primCount = 0;

    for (DrawSheet* outSheet : mOutputSheets)
    {
    vertCount += outSheet->charsUsed * 4;
    indCount += outSheet->charsUsed * 6;
    primCount++;
    }

    params.primType = DGLPrimitiveTriangle;
    params.vertCode = GuiVert::getFmtCode();
    params.numVerts = vertCount;
    params.numInds = indCount;
    params.numPrims = primCount;
    }

    template<class Y> void fillAlloc(DGLGuiMeshDataRef &outRef, Y* pipeline)
    {
    const F32 invMetricScale = mFont->getInvMetricScale();
    const F32 invTexScale = 1.0f / mFont->getTextureScale();
    GuiVert* outVerts = outRef.vertPtr;
    U16* outInds = outRef.indexPtr;
    DGLPrimitive* outPrims = outRef.primPtr;
    U16 curInd = 0;

    for (DrawSheet* outSheet : mOutputSheets)
    {
    TextureObject* lastTexture = mFont->getTextureHandle(outSheet->bitmapIndex);
    if (lastTexture == NULL)
    continue;

    DGLPrimitive prim = {};
    GuiVert* startVertPtr = outVerts;
    U16* startIndPtr = outInds;
    prim.primType = DGLPrimitiveTriangle;
    prim.startVert = (U32)(outVerts - outRef.vertPtr);
    prim.startInd = (U32)(outInds - outRef.indexPtr);
    prim.indexed = 1;
    curInd = 0;
    Point3F points[4];

    for (U32 cn=0; cn < outSheet->charsUsed; cn++)
    {
    DrawChar& dc = outSheet->getChars()[cn];
    const PlatformFont::CharInfo &ci = mFont->getCharInfo(dc.charCode);
    Point2F pt = dc.pos;
    DGLPackedPointU16 currentColor = getColor(dc.colorIndex);

    F32 texLeft = F32(ci.xOffset) / F32(lastTexture->getTextureWidth());
    F32 texRight = F32(ci.xOffset + ci.width) / F32(lastTexture->getTextureWidth());
    F32 texTop = F32(ci.yOffset) / F32(lastTexture->getTextureHeight());
    F32 texBottom = F32(ci.yOffset + ci.height) / F32(lastTexture->getTextureHeight());

    F32 screenLeft = pt.x;
    F32 screenRight = pt.x + (ci.width * invTexScale);
    F32 screenTop = pt.y;
    F32 screenBottom = pt.y + (ci.height * invTexScale);

    points[0] = Point3F( screenLeft, screenTop, 0.0);
    points[1] = Point3F( screenRight, screenTop, 0.0);
    points[2] = Point3F( screenLeft, screenBottom, 0.0);
    points[3] = Point3F( screenRight, screenBottom, 0.0);

    for( int i=0; i<4; i++ )
    {
    mBaseTransform.mulP( points[i] );
    points[i] += Point3F(mDrawOrigin.x, mDrawOrigin.y, 0.0f);
    }

    // Verts
    outVerts[0].setValues(points[0], Point2F(texLeft, texTop), currentColor);
    outVerts[1].setValues(points[1], Point2F(texRight, texTop), currentColor);
    outVerts[2].setValues(points[2], Point2F(texLeft, texBottom), currentColor);
    outVerts[3].setValues(points[3], Point2F(texRight, texBottom), currentColor);

    // Indices
    outInds[0] = curInd;
    outInds[1] = curInd+1;
    outInds[2] = curInd+2;

    outInds[3] = curInd+2;
    outInds[4] = curInd+1;
    outInds[5] = curInd+3;

    outVerts += 4;
    curInd += 4;
    outInds += 6;
    }

    prim.numVerts = (U32)(outVerts - startVertPtr);
    prim.numElements = (U32)(outInds - startIndPtr);
    *outPrims++ = prim;

    outInds += prim.numElements;
    outVerts += prim.numVerts;
    }
    }

    /// Outputs to a pipeline mesh outRef
    template<typename T> void endToPipelineMesh(T* pipeline, DGLGuiMeshDataRef& outRef)
    {
    DGLMeshAllocParams allocParams = {};
    fillAllocParams(allocParams);

    if (pipeline->allocMesh(allocParams, outRef))
    {
    fillAlloc(outRef, pipeline);
    }
    }

    void clear()
    {
    mOutputSheets.clear();
    mSheetChunker.freeBlocks(true);
    mDrawSheets.clear();
    }

    /// Helper function to draw output sheets to the pipeline with the correct textures
    void drawPipelineMesh(DGLMeshIndex outMeshIndex, DGLPipelineState* pipeline)
    {
    U32 primNum = 0;

    for (DrawSheet* outSheet : mOutputSheets)
    {
    TextureObject* lastTexture = mFont->getTextureHandle(outSheet->bitmapIndex);
    if (lastTexture == NULL)
    continue;

    pipeline->quickSetTexture(lastTexture->getGLTextureName(), DGLSamplerStates::GuiText);
    pipeline->drawMesh(outMeshIndex, primNum++);
    }
    }

    static DGLFontMeshBuilder* getInstance();
    };
    1,395 changes: 1,395 additions & 0 deletions gFont.cc
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,1395 @@
    //-----------------------------------------------------------------------------
    // Copyright (c) 2013 GarageGames, LLC
    //
    // Permission is hereby granted, free of charge, to any person obtaining a copy
    // of this software and associated documentation files (the "Software"), to
    // deal in the Software without restriction, including without limitation the
    // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
    // sell copies of the Software, and to permit persons to whom the Software is
    // furnished to do so, subject to the following conditions:
    //
    // The above copyright notice and this permission notice shall be included in
    // all copies or substantial portions of the Software.
    //
    // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
    // IN THE SOFTWARE.
    //-----------------------------------------------------------------------------

    #include "platform/platform.h"
    #include "core/stream.h"
    #include "core/frameAllocator.h"
    #include "core/findMatch.h"
    #include "core/unicode.h"
    #include "dgl/gFont.h"
    #include "dgl/gBitmap.h"
    #include "core/fileStream.h"
    #include "dgl/gTexManager.h"
    #include "util/safeDelete.h"
    #include "platform/profiler.h"
    #include "zlib.h"
    #include <algorithm>

    S32 GFont::smSheetIdCount = 0;
    const U32 GFont::csm_fileVersion = 3;
    U32 GFont::BaseTextureSheetSize = 256;

    ConsoleFunction(populateFontCacheString, void, 4, 4, "(faceName, size, string) "
    "Populate the font cache for the specified font with characters from the specified string."
    "@param faceName The font's name\n"
    "@param size The size of the font.\n"
    "@param string The string to use to fill font cache\n"
    "@return No return value.")
    {
    Resource<GFont> f = GFont::create(argv[1], dAtoi(argv[2]), Con::getVariable("$GUI::fontCacheDirectory"));

    if(f.isNull())
    {
    Con::errorf("populateFontCacheString - could not load font '%s %d'!", argv[1], dAtoi(argv[2]));
    return;
    }

    if(!f->hasPlatformFont())
    {
    Con::errorf("populateFontCacheString - font '%s %d' has no platform font! Cannot generate more characters.", argv[1], dAtoi(argv[2]));
    return;
    }

    // This has the side effect of generating character info, including the bitmaps.
    f->getStrWidthPrecise(argv[3]);
    }

    ConsoleFunction(populateFontCacheRange, void, 5, 5, "(faceName, size, rangeStart, rangeEnd) - "
    "Populate the font cache for the specified font with Unicode code points in the specified range. "
    "Note we only support BMP-0, so code points range from 0 to 65535."
    "@param faceName The name of the font\n"
    "@param size The size of the font.\n"
    "@param rangeStart The initial Unicode point\n"
    "@param rangeEnd The final Unicode point in range\n"
    "@return No return value")
    {
    Resource<GFont> f = GFont::create(argv[1], dAtoi(argv[2]), Con::getVariable("$GUI::fontCacheDirectory"));

    if(f.isNull())
    {
    Con::errorf("populateFontCacheRange - could not load font '%s %d'!", argv[1], dAtoi(argv[2]));
    return;
    }

    U32 rangeStart = dAtoi(argv[3]);
    U32 rangeEnd = dAtoi(argv[4]);

    if(rangeStart > rangeEnd)
    {
    Con::errorf("populateFontCacheRange - range start is after end!");
    return;
    }

    if(!f->hasPlatformFont())
    {
    Con::errorf("populateFontCacheRange - font '%s %d' has no platform font! Cannot generate more characters.", argv[1], dAtoi(argv[2]));
    return;
    }

    // This has the side effect of generating character info, including the bitmaps.
    for(U32 i=rangeStart; i<rangeEnd; i++)
    {
    if(f->isValidChar(i))
    f->getCharWidth(i);
    else
    Con::warnf("populateFontCacheRange - skipping invalid char 0x%x", i);
    }

    // All done!
    }

    ConsoleFunction(dumpFontCacheStatus, void, 1, 1, "() Dump a full description "
    "of all cached fonts, along with info on the codepoints each contains.\n"
    "@return No return value")
    {
    FindMatch match("*.uft", 4096);
    ResourceManager->findMatches(&match);

    Con::printf("--------------------------------------------------------------------------");
    Con::printf(" Font Cache Usage Report (%d fonts found)", match.numMatches());

    for (U32 i = 0; i < (U32)match.numMatches(); i++)
    {
    char *curMatch = match.matchList[i];
    Resource<GFont> font = ResourceManager->load(curMatch);

    // Deal with inexplicably missing or failed to load fonts.
    if (font.isNull())
    {
    Con::errorf(" o Couldn't load font : %s", curMatch);
    continue;
    }

    // Ok, dump info!
    font->dumpInfo();
    }
    }

    ConsoleFunction(writeFontCache, void, 1, 1, "() force all cached fonts to"
    "serialize themselves to the cache."
    "@return No return value")
    {
    FindMatch match("*.uft", 4096);
    ResourceManager->findMatches(&match);

    Con::printf("--------------------------------------------------------------------------");
    Con::printf(" Writing font cache to disk (%d fonts found)", match.numMatches());

    for (U32 i = 0; i < (U32)match.numMatches(); i++)
    {
    char *curMatch = match.matchList[i];
    Resource<GFont> font = ResourceManager->load(curMatch);

    // Deal with inexplicably missing or failed to load fonts.
    if (font.isNull())
    {
    Con::errorf(" o Couldn't find font : %s", curMatch);
    continue;
    }

    // Ok, dump info!
    FileStream stream;
    if(ResourceManager->openFileForWrite(stream, curMatch))
    {
    Con::printf(" o Writing '%s' to disk...", curMatch);
    font->write(stream);
    stream.close();
    }
    else
    {
    Con::errorf(" o Could not open '%s' for write!", curMatch);
    }
    }
    }

    ConsoleFunction(populateAllFontCacheString, void, 2, 2, "(string inString) "
    "Populate the font cache for all fonts with characters from the specified string.\n"
    "@param inString The string to use to set the font caches\n"
    "@return No return value.")
    {
    FindMatch match("*.uft", 4096);
    ResourceManager->findMatches(&match);

    Con::printf("Populating font cache with string '%s' (%d fonts found)", argv[1], match.numMatches());

    for (U32 i = 0; i < (U32)match.numMatches(); i++)
    {
    char *curMatch = match.matchList[i];
    Resource<GFont> font = ResourceManager->load(curMatch);

    // Deal with inexplicably missing or failed to load fonts.
    if (font.isNull())
    {
    Con::errorf(" o Couldn't load font : %s", curMatch);
    continue;
    }

    if(!font->hasPlatformFont())
    {
    Con::errorf("populateAllFontCacheString - font '%s' has no platform font! Cannot generate more characters.", curMatch);
    continue;
    }

    // This has the side effect of generating character info, including the bitmaps.
    font->getStrWidthPrecise(argv[1]);
    }
    }

    ConsoleFunction(populateAllFontCacheRange, void, 3, 3, "(rangeStart, rangeEnd) "
    "Populate the font cache for all fonts with Unicode code points in the specified range. "
    "Note we only support BMP-0, so code points range from 0 to 65535.\n"
    "@param rangeStart, rangeEnd The range of the unicode points to populate caches with\n"
    "@return No return value")
    {
    U32 rangeStart = dAtoi(argv[1]);
    U32 rangeEnd = dAtoi(argv[2]);

    if(rangeStart > rangeEnd)
    {
    Con::errorf("populateAllFontCacheRange - range start is after end!");
    return;
    }

    FindMatch match("*.uft", 4096);
    ResourceManager->findMatches(&match);

    Con::printf("Populating font cache with range 0x%x to 0x%x (%d fonts found)", rangeStart, rangeEnd, match.numMatches());

    for (U32 i = 0; i < (U32)match.numMatches(); i++)
    {
    char *curMatch = match.matchList[i];
    Resource<GFont> font = ResourceManager->load(curMatch);

    // Deal with inexplicably missing or failed to load fonts.
    if (font.isNull())
    {
    Con::errorf(" o Couldn't load font : %s", curMatch);
    continue;
    }

    if(!font->hasPlatformFont())
    {
    Con::errorf("populateAllFontCacheRange - font '%s' has no platform font! Cannot generate more characters.", curMatch);
    continue;
    }

    // This has the side effect of generating character info, including the bitmaps.
    Con::printf(" o Populating font '%s'", curMatch);
    for(U32 i=rangeStart; i<rangeEnd; i++)
    {
    if(font->isValidChar(i))
    font->getCharWidth(i);
    else
    Con::warnf("populateAllFontCacheRange - skipping invalid char 0x%x", i);
    }
    }
    // All done!
    }

    ConsoleFunction(exportCachedFont, void, 6, 6, "(fontName, size, fileName, padding, kerning) - "
    "Export specified font to the specified filename as a PNG. The "
    "image can then be processed in Photoshop or another tool and "
    "reimported using importCachedFont. Characters in the font are"
    "exported as one long strip.\n"
    "@param fontName The name of the font to export.\n"
    "@param size The size of the font\n"
    "@param fileName The export file name.\n"
    "@param padding Desired padding settings.\n"
    "@param kerning Kerning settings (space between elements)\n"
    "@return No return value.")
    {
    // Read in some params.
    const char *fileName = argv[3];
    S32 padding = dAtoi(argv[4]);
    S32 kerning = dAtoi(argv[5]);

    // Tell the font to export itself.
    Resource<GFont> f = GFont::create(argv[1], dAtoi(argv[2]), Con::getVariable("$GUI::fontCacheDirectory"));

    if(f.isNull())
    {
    Con::errorf("populateFontCacheString - could not load font '%s %d'!", argv[1], dAtoi(argv[2]));
    return;
    }

    f->exportStrip(fileName, padding, kerning);
    }

    ConsoleFunction(importCachedFont, void, 6, 6, "(fontName, size, fileName, padding, kerning) "
    "Import an image strip from exportCachedFont. Call with the "
    "same parameters you called exportCachedFont."
    "@param fontName The name of the font to import.\n"
    "@param size The size of the font\n"
    "@param fileName The imported file name.\n"
    "@param padding Desired padding settings.\n"
    "@param kerning Kerning settings (space between elements)\n"
    "@return No return value.")
    {
    // Read in some params.
    const char *fileName = argv[3];
    S32 padding = dAtoi(argv[4]);
    S32 kerning = dAtoi(argv[5]);

    // Tell the font to import itself.
    Resource<GFont> f = GFont::create(argv[1], dAtoi(argv[2]), Con::getVariable("$GUI::fontCacheDirectory"));

    if(f.isNull())
    {
    Con::errorf("populateFontCacheString - could not load font '%s %d'!", argv[1], dAtoi(argv[2]));
    return;
    }

    f->importStrip(fileName, padding, kerning);
    }

    ConsoleFunction(duplicateCachedFont, void, 4, 4, "(oldFontName, oldFontSize, newFontName) "
    "Copy the specified old font to a new name. The new copy will not have a "
    "platform font backing it, and so will never have characters added to it. "
    "But this is useful for making copies of fonts to add postprocessing effects "
    "to via exportCachedFont.\n"
    "@param oldFontName The original font.\n"
    "@param oldFontSize The original font's size property.\n"
    "@param newFontName The name to set the copy to.\n"
    "@return No return value.")
    {
    char newFontFile[256];
    GFont::getFontCacheFilename(argv[3], dAtoi(argv[2]), 256, newFontFile);

    // Load the original font.
    Resource<GFont> font = GFont::create(argv[1], dAtoi(argv[2]), Con::getVariable("$GUI::fontCacheDirectory"));

    // Deal with inexplicably missing or failed to load fonts.
    if (font.isNull())
    {
    Con::errorf(" o Couldn't find font : %s", newFontFile);
    return;
    }

    // Ok, dump info!
    FileStream stream;
    if(ResourceManager->openFileForWrite(stream, newFontFile))
    {
    Con::printf(" o Writing duplicate font '%s' to disk...", newFontFile);
    font->write(stream);
    stream.close();
    }
    else
    {
    Con::errorf(" o Could not open '%s' for write!", newFontFile);
    }
    }

    static PlatformFont* createSafePlatformFont(const char *name, U32 size, U32 charset = TGE_ANSI_CHARSET)
    {
    PlatformFont *platFont = createPlatformFont(name, size, charset);

    if (platFont == NULL)
    {
    Con::errorf("Loading platform font failed, trying font fallbacks...");
    // Couldn't load the requested font. This probably will be common
    // since many unix boxes don't have arial or lucida console installed.
    // Attempt to map the font name into a font we're pretty sure exist
    // Lucida Console is a common code & console font on windows, and
    // Monaco is the recommended code & console font on mac.

    // this is the name of the final fallback font.

    #ifdef TORQUE_OS_LINUX
    const char* fallback = "Arial";
    #else
    const char* fallback = "Helvetica";
    #endif

    if(dStricmp(name, fallback) == 0)
    {
    Con::errorf("Font fallback utterly failed.");
    return NULL;
    }
    else if (dStricmp(name, "arial") == 0)
    fallback = "Helvetica";
    else if (dStricmp(name, "lucida console") == 0)
    fallback = "Monaco";
    else if (dStricmp(name, "monaco") == 0)
    fallback = "Courier";

    platFont = createSafePlatformFont(fallback, size, charset);
    }

    return platFont;
    }

    ResourceInstance* constructNewFont(Stream& stream)
    {
    GFont *ret = new GFont;

    if(!ret->read(stream))
    {
    SAFE_DELETE(ret);
    }

    if(ret)
    {
    ret->mPlatformFont = createSafePlatformFont(ret->mFaceName, ret->mSize, ret->mCharSet);
    }

    return ret;
    }

    void GFont::getFontCacheFilename(const char *faceName, U32 size, U32 buffLen, char *outBuff)
    {
    dSprintf(outBuff, buffLen, "%s/%s %d (%s).uft", Con::getVariable("$GUI::fontCacheDirectory"), faceName, size, getFontCharSetName(0));
    }

    Resource<GFont> GFont::create(const char *faceName, U32 size, const char *cacheDirectory, U32 charset /* = TGE_ANSI_CHARSET */)
    {
    char buf[256];
    dSprintf(buf, sizeof(buf), "%s/%s %d (%s).uft", cacheDirectory, faceName, size, getFontCharSetName(charset));

    Resource<GFont> ret = ResourceManager->load(buf);
    if(bool(ret))
    {
    ret->mGFTFile = StringTable->insert(buf);
    return ret;
    }

    PlatformFont *platFont = createSafePlatformFont(faceName, size, charset);

    AssertFatal(platFont, "platFont is null");

    GFont *resFont = new GFont;
    resFont->mPlatformFont = platFont;
    resFont->addSheet();
    resFont->mGFTFile = StringTable->insert(buf);
    resFont->mFaceName = StringTable->insert(faceName);
    resFont->mSize = size;
    resFont->mCharSet = charset;

    resFont->mHeight = platFont->getFontHeight();
    resFont->mBaseline = platFont->getFontBaseLine();
    resFont->mAscent = platFont->getFontBaseLine();
    resFont->mDescent = platFont->getFontHeight() - platFont->getFontBaseLine();

    resFont->mTexLineHeight = platFont->getTexLineHeight();
    resFont->mTextureScale = platFont->getTexScale();
    resFont->mMetricScale = platFont->getMetricScale();
    resFont->mSDF = platFont->isSDF();

    ResourceManager->add(buf, resFont, false);
    return ResourceManager->load(buf);
    }

    //-------------------------------------------------------------------------

    GFont::GFont()
    {
    VECTOR_SET_ASSOCIATION(mCharInfoList);
    VECTOR_SET_ASSOCIATION(mTextureSheets);

    for (U32 i = 0; i < (sizeof(mRemapTable) / sizeof(S32)); i++)
    mRemapTable[i] = -1;

    mCurX = mCurY = mCurSheet = -1;

    mPlatformFont = NULL;
    mGFTFile = NULL;
    mFaceName = NULL;
    mSize = 0;
    mCharSet = 0;
    mNeedSave = false;
    mSDF = false;
    mTexLineHeight = 0;
    mTextureScale = 1.0f;
    mMetricScale = 1.0f;

    mMutex = Mutex::createMutex();
    }

    GFont::~GFont()
    {

    // Need to stop this for now!
    mNeedSave = false;
    if(mNeedSave)
    {
    FileStream stream;
    if(ResourceManager->openFileForWrite(stream, mGFTFile))
    {
    write(stream);
    stream.close();
    }
    }

    S32 i;

    for(i = 0;i < mCharInfoList.size();i++)
    {
    SAFE_DELETE_ARRAY(mCharInfoList[i].bitmapData);
    }

    //Luma: decrement reference of the texture handles too
    for(i=0;i<mTextureSheets.size();i++)
    {
    mTextureSheets[i] = 0;
    }

    SAFE_DELETE(mPlatformFont);

    Mutex::destroyMutex(mMutex);
    }

    void GFont::dumpInfo()
    {
    // Number and extent of mapped characters?
    U32 mapCount = 0, mapBegin=0xFFFF, mapEnd=0;
    for(U32 i=0; i<0x10000; i++)
    {
    if(mRemapTable[i] != -1)
    {
    mapCount++;
    if(i<mapBegin) mapBegin = i;
    if(i>mapEnd) mapEnd = i;
    }
    }


    // Let's write out all the info we can on this font.
    Con::printf(" '%s' %dpt", mFaceName, mSize);
    Con::printf(" - %d texture sheets, %d mapped characters.", mTextureSheets.size(), mapCount);

    if(mapCount)
    Con::printf(" - Codepoints range from 0x%x to 0x%x.", mapBegin, mapEnd);
    else
    Con::printf(" - No mapped codepoints.", mapBegin, mapEnd);
    Con::printf(" - Platform font is %s.", (mPlatformFont ? "present" : "not present") );
    }

    //////////////////////////////////////////////////////////////////////////

    bool GFont::loadCharInfo(const UTF16 ch)
    {
    if(mRemapTable[ch] != -1)
    return true; // Not really an error

    if(mPlatformFont && mPlatformFont->isValidChar(ch))
    {
    Mutex::lockMutex(mMutex); // the CharInfo returned by mPlatformFont is static data, must protect from changes.
    PlatformFont::CharInfo &ci = mPlatformFont->getCharInfo(ch);
    if(ci.bitmapData)
    addBitmap(ci);

    mCharInfoList.push_back(ci);
    mRemapTable[ch] = mCharInfoList.size() - 1;
    //don't save UFTs on the iPhone
    #ifndef TORQUE_OS_IOS
    mNeedSave = true;
    #endif

    Mutex::unlockMutex(mMutex);
    return true;
    }

    return false;
    }

    void GFont::addBitmap(PlatformFont::CharInfo &charInfo)
    {
    U32 platformHeight = std::max(mPlatformFont ? mPlatformFont->getTexLineHeight() : 0, mTexLineHeight);

    const U32 padding = mSDF ? 2 : 1; // jamesu - best to have padding here in all cases I think
    U32 nextCurX = U32(mCurX + charInfo.width + padding); /*7) & ~0x3;*/
    U32 nextCurY = U32(mCurY + platformHeight + padding); // + 7) & ~0x3;

    if (charInfo.height > mTexLineHeight)
    {
    U32 delta = charInfo.height - mTexLineHeight;
    mTexLineHeight = charInfo.height;
    Con::warnf("Character for font %s %u exceeds texture height by %u pixels, extending.", mFaceName, mSize, delta);
    }

    // These are here for postmortem debugging.
    bool routeA = false, routeB = false;

    const U32 RealTextureSheetSize = mSDF ? 2*BaseTextureSheetSize : BaseTextureSheetSize;

    if(mCurSheet == -1 || nextCurY >= RealTextureSheetSize)
    {
    routeA = true;
    addSheet();

    // Recalc our nexts.
    nextCurX = U32(mCurX + charInfo.width + padding); // + 7) & ~0x3;
    nextCurY = U32(mCurY + platformHeight + padding); // + 7) & ~0x3;
    }

    if( nextCurX >= RealTextureSheetSize)
    {
    routeB = true;
    mCurX = 0;
    mCurY = nextCurY;
    mCurX = 0;

    // Recalc our nexts.
    nextCurX = U32(mCurX + charInfo.width + padding); // + 7) & ~0x3;
    nextCurY = U32(mCurY + platformHeight + padding); // + 7) & ~0x3;
    }

    // Check the Y once more - sometimes we advance to a new row and run off
    // the end.
    if(nextCurY >= RealTextureSheetSize)
    {
    routeA = true;
    addSheet();

    // Recalc our nexts.
    nextCurX = U32(mCurX + charInfo.width + padding); // + 7) & ~0x3;
    nextCurY = U32(mCurY + platformHeight + padding); // + 7) & ~0x3;
    }

    charInfo.bitmapIndex = mCurSheet;
    charInfo.xOffset = mCurX;
    charInfo.yOffset = mCurY;

    mCurX = nextCurX;

    GBitmap *bmp = mTextureSheets[mCurSheet].getBitmap();

    AssertFatal(bmp->getFormat() == GBitmap::Alpha, "GFont::addBitmap - cannot added characters to non-greyscale textures!");

    for(S32 y = 0;y < charInfo.height;y++)
    for(S32 x = 0;x < charInfo.width;x++)
    *bmp->getAddress(x + charInfo.xOffset, y + charInfo.yOffset) = charInfo.bitmapData[y * charInfo.width + x];

    mTextureSheets[mCurSheet].refresh();
    }

    void GFont::addSheet()
    {
    char buf[30];
    dSprintf(buf, sizeof(buf), "newfont_%d", smSheetIdCount++);

    const U32 RealTextureSheetSize = mSDF ? 2*BaseTextureSheetSize : BaseTextureSheetSize;
    GBitmap *bitmap = new GBitmap(RealTextureSheetSize, RealTextureSheetSize, false, GBitmap::Alpha);

    // Set everything to transparent.
    U8 *bits = bitmap->getWritableBits();
    dMemset(bits, 0, RealTextureSheetSize*RealTextureSheetSize);

    TextureHandle handle = TextureHandle(buf, bitmap, BitmapKeepTexture);
    handle.setFilterNearest();

    mTextureSheets.increment();
    constructInPlace(&mTextureSheets.last());
    mTextureSheets.last() = handle;

    mCurX = 0;
    mCurY = 0;
    mCurSheet = mTextureSheets.size() - 1;
    }

    //////////////////////////////////////////////////////////////////////////

    const PlatformFont::CharInfo &GFont::getCharInfo(const UTF16 in_charIndex)
    {
    PROFILE_START(NewFontGetCharInfo);

    AssertFatal(in_charIndex, "GFont::getCharInfo - can't get info for char 0!");

    if(mRemapTable[in_charIndex] == -1)
    {
    loadCharInfo(in_charIndex);
    }

    AssertFatal(mRemapTable[in_charIndex] != -1, "No remap info for this character");

    PROFILE_END();

    // if we still have no character info, return the default char info.
    if(mRemapTable[in_charIndex] == -1)
    return getDefaultCharInfo();
    else
    return mCharInfoList[mRemapTable[in_charIndex]];
    }

    const PlatformFont::CharInfo &GFont::getDefaultCharInfo()
    {
    static PlatformFont::CharInfo c;
    // c is initialized by the CharInfo default constructor.
    return c;
    }

    //////////////////////////////////////////////////////////////////////////

    U32 GFont::getStrWidth(const UTF8* in_pString)
    {
    AssertFatal(in_pString != NULL, "GFont::getStrWidth: String is NULL, width is undefined");
    // If we ain't running debug...
    if (in_pString == NULL || *in_pString == '\0')
    return 0;

    return getStrNWidth(in_pString, dStrlen(in_pString));
    }

    U32 GFont::getStrWidthPrecise(const UTF8* in_pString)
    {
    AssertFatal(in_pString != NULL, "GFont::getStrWidth: String is NULL, height is undefined");
    // If we ain't running debug...
    if (in_pString == NULL)
    return 0;

    return getStrNWidthPrecise(in_pString, dStrlen(in_pString));
    }

    //////////////////////////////////////////////////////////////////////////
    U32 GFont::getStrNWidth(const UTF8 *str, U32 n)
    {
    // UTF8 conversion is expensive. Avoid converting in a tight loop.
    FrameTemp<UTF16> str16(n + 1);
    convertUTF8toUTF16(str, str16, n+1);
    return getStrNWidth(str16, dStrlen(str16));
    }

    U32 GFont::getStrNWidth(const UTF16 *str, U32 n)
    {
    AssertFatal(str != NULL, "GFont::getStrNWidth: String is NULL");

    if (str == NULL || str[0] == '\0' || n == 0)
    return 0;

    F32 totWidth = 0;
    UTF16 curChar;
    U32 charCount;

    for(charCount = 0; charCount < n; charCount++)
    {
    curChar = str[charCount];
    if(curChar == '\0')
    break;

    if(isValidChar(curChar))
    {
    const PlatformFont::CharInfo& rChar = getCharInfo(curChar);
    totWidth += rChar.xIncrement;
    }
    else if (curChar == dT('\t'))
    {
    const PlatformFont::CharInfo& rChar = getCharInfo(dT(' '));
    totWidth += rChar.xIncrement * TabWidthInSpaces;
    }
    }

    totWidth *= getInvMetricScale();

    return (U32)mCeil(totWidth);
    }

    U32 GFont::getStrNWidthPrecise(const UTF8 *str, U32 n)
    {
    FrameTemp<UTF16> str16(n + 1);
    convertUTF8toUTF16(str, str16, n+1);
    return getStrNWidthPrecise(str16, n);
    }

    U32 GFont::getStrNWidthPrecise(const UTF16 *str, U32 n)
    {
    AssertFatal(str != NULL, "GFont::getStrNWidth: String is NULL");

    if (str == NULL || str[0] == '\0' || n == 0)
    return(0);

    F32 totWidth = 0;
    UTF16 curChar;
    S32 charCount = 0;
    const F32 invTexScale = 1.0f / getTextureScale();

    for(charCount = 0; charCount < n; charCount++)
    {
    curChar = str[charCount];
    if(curChar == '\0')
    {
    n = charCount;
    break;
    }

    if(isValidChar(curChar))
    {
    const PlatformFont::CharInfo& rChar = getCharInfo(curChar);
    totWidth += rChar.xIncrement;
    }
    else if (curChar == dT('\t'))
    {
    const PlatformFont::CharInfo& rChar = getCharInfo(dT(' '));
    totWidth += rChar.xIncrement * TabWidthInSpaces;
    }
    }

    // Add in width after last char
    UTF16 endChar = str[getMax(0, ((S32)n)-1)];
    if (isValidChar(endChar))
    {
    const PlatformFont::CharInfo& rChar = getCharInfo(endChar);
    U32 realMetricWidth = mCeil((F32)rChar.width * invTexScale * getMetricScale());
    if (realMetricWidth > rChar.xIncrement)
    totWidth += (realMetricWidth - rChar.xIncrement);
    }

    totWidth *= getInvMetricScale();

    return (U32)mCeil(totWidth);
    }

    U32 GFont::getBreakPos(const UTF16 *str16, U32 slen, U32 width, bool breakOnWhitespace)
    {
    // Some early out cases.
    if(slen==0)
    return 0;

    U32 ret = 0;
    U32 lastws = 0;
    UTF16 c;
    U32 charCount = 0;

    const F32 metricScale = getMetricScale();
    const F32 invMetricScale = getInvMetricScale();
    U32 scaledWidth = width * metricScale;

    for( charCount=0; charCount < slen; charCount++)
    {
    c = str16[charCount];
    if(c == '\0')
    break;

    if(c == dT('\t'))
    c = dT(' ');
    if(!isValidChar(c))
    {
    ret++;
    continue;
    }
    if(c == dT(' '))
    lastws = ret+1;
    const PlatformFont::CharInfo& rChar = getCharInfo(c);

    if(rChar.width > scaledWidth || rChar.xIncrement > (S32)scaledWidth)
    {
    if(lastws && breakOnWhitespace)
    return lastws;
    return ret;
    }

    scaledWidth -= rChar.xIncrement;

    ret++;
    }
    return ret;
    }

    void GFont::wrapString(const UTF8 *txt, U32 lineWidth, Vector<U32> &startLineOffset, Vector<U32> &lineLen)
    {
    Con::errorf("GFont::wrapString(): Not yet converted to be UTF-8 safe");

    startLineOffset.clear();
    lineLen.clear();

    if (!txt || !txt[0] || lineWidth < getCharWidth('W')) //make sure the line width is greater then a single character
    return;

    U32 len = dStrlen(txt);

    U32 startLine;
    U32 scaledLineWidth = lineWidth * getMetricScale();

    for (U32 i = 0; i < len;)
    {
    startLine = i;
    startLineOffset.push_back(startLine);

    // loop until the string is too large
    bool needsNewLine = false;
    U32 lineStrWidth = 0;
    for (; i < len; i++)
    {
    if(isValidChar(txt[i]))
    {
    lineStrWidth += getCharInfo(txt[i]).xIncrement;
    if ( txt[i] == '\n' || lineStrWidth > scaledLineWidth )
    {
    needsNewLine = true;
    break;
    }
    }
    }

    if (!needsNewLine)
    {
    // we are done!
    lineLen.push_back(i - startLine);
    return;
    }

    // now determine where to put the newline
    // else we need to backtrack until we find a either space character
    // or \\ character to break up the line.
    S32 j;
    for (j = i - 1; j >= (S32)startLine; j--)
    {
    if (dIsspace(txt[j]))
    break;
    }

    if (j < (S32)startLine)
    {
    // the line consists of a single word!
    // So, just break up the word
    j = i - 1;
    }
    lineLen.push_back(j - startLine);
    i = j;

    // now we need to increment through any space characters at the
    // beginning of the next line
    for (i++; i < len; i++)
    {
    if (!dIsspace(txt[i]) || txt[i] == '\n')
    break;
    }
    }
    }

    //////////////////////////////////////////////////////////////////////////

    bool GFont::read(Stream& io_rStream)
    {
    // Handle versioning
    U32 version;
    io_rStream.read(&version);
    if((version & 0xFF) != csm_fileVersion)
    return false;

    mSDF = (version & GFont::SDFFlag) != 0;

    char buf[256];
    io_rStream.readString(buf);
    mFaceName = StringTable->insert(buf);

    io_rStream.read(&mSize);
    io_rStream.read(&mCharSet);

    io_rStream.read(&mHeight);
    io_rStream.read(&mBaseline);
    io_rStream.read(&mAscent);
    io_rStream.read(&mDescent);

    if ((version & GFont::VariantHeightFlag) != 0)
    {
    io_rStream.read(&mTexLineHeight);
    }
    else
    {
    mTexLineHeight = mHeight;
    }

    if ((version & GFont::CustomScaleFlag) != 0)
    {
    io_rStream.read(&mTextureScale);
    io_rStream.read(&mMetricScale);
    }
    else
    {
    mTextureScale = 1.0f;
    mMetricScale = 1.0f;
    }

    U32 size = 0;
    io_rStream.read(&size);
    mCharInfoList.setSize(size);
    U32 i;
    for(i = 0; i < size; i++)
    {
    PlatformFont::CharInfo *ci = &mCharInfoList[i];
    io_rStream.read(&ci->bitmapIndex);
    io_rStream.read(&ci->xOffset);
    io_rStream.read(&ci->yOffset);
    io_rStream.read(&ci->width);
    io_rStream.read(&ci->height);
    io_rStream.read(&ci->xOrigin);
    io_rStream.read(&ci->yOrigin);
    io_rStream.read(&ci->xIncrement);
    ci->bitmapData = NULL;
    }

    U32 numSheets = 0;
    io_rStream.read(&numSheets);

    for(i = 0; i < numSheets; i++)
    {
    GBitmap *bmp = new GBitmap;
    if(!bmp->readPNG(io_rStream))
    {
    delete bmp;
    return false;
    }

    char buf[30];
    dSprintf(buf, sizeof(buf), "font_%d", smSheetIdCount++);

    mTextureSheets.increment();
    constructInPlace(&mTextureSheets.last());
    mTextureSheets.last() = TextureHandle(buf, bmp, BitmapKeepTexture);
    mTextureSheets.last().setFilterNearest();
    }

    // Read last position info
    io_rStream.read(&mCurX);
    io_rStream.read(&mCurY);
    io_rStream.read(&mCurSheet);

    // Read the remap table.
    U32 minGlyph, maxGlyph;
    io_rStream.read(&minGlyph);
    io_rStream.read(&maxGlyph);

    if(maxGlyph >= minGlyph)
    {
    // Length of buffer..
    U32 buffLen;
    io_rStream.read(&buffLen);

    // Read the buffer.
    FrameTemp<S32> inBuff(buffLen);
    io_rStream.read(buffLen, inBuff);

    // Decompress.
    uLongf destLen = (maxGlyph-minGlyph+1)*sizeof(S32);
    uncompress((Bytef*)&mRemapTable[minGlyph], &destLen, (Bytef*)(S32*)inBuff, buffLen);

    AssertISV(destLen == (maxGlyph-minGlyph+1)*sizeof(S32), "GFont::read - invalid remap table data!");

    // Make sure we've got the right endianness.
    for(i = minGlyph; i <= maxGlyph; i++) {
    mRemapTable[i] = convertBEndianToHost(mRemapTable[i]);
    //if( mRemapTable[i] == -1 ) {
    // Con::errorf( "bogus mRemapTable[i] value in %s %i", mFaceName, mSize );
    //}
    }
    }

    return (io_rStream.getStatus() == Stream::Ok);
    }

    bool GFont::write(Stream& stream)
    {
    // Handle versioning
    U32 extraFlags = 0;
    if (mSDF) extraFlags |= GFont::SDFFlag;
    if (mTexLineHeight != mHeight) extraFlags |= GFont::VariantHeightFlag;
    if (mTextureScale != 1.0f || mMetricScale != 1.0f) extraFlags |= GFont::CustomScaleFlag;
    stream.write(csm_fileVersion | extraFlags);

    // Write font info
    stream.writeString(mFaceName);
    stream.write(mSize);
    stream.write(mCharSet);

    stream.write(mHeight);
    stream.write(mBaseline);
    stream.write(mAscent);
    stream.write(mDescent);

    if ((extraFlags & GFont::VariantHeightFlag) != 0)
    {
    stream.write(mTexLineHeight);
    }

    if ((extraFlags & GFont::CustomScaleFlag) != 0)
    {
    stream.write(&mTextureScale);
    stream.write(&mMetricScale);
    }
    else
    {
    mTextureScale = 1.0f;
    mMetricScale = 1.0f;
    }

    // Get the min/max we have values for, and only write that range out.
    S32 minGlyph = S32_MAX, maxGlyph = 0;
    S32 i;

    for(i = 0; i < 65536; i++)
    {
    if(mRemapTable[i] != -1)
    {
    if(i > maxGlyph) maxGlyph = i;
    if(i < minGlyph) minGlyph = i;
    }
    }

    //-Mat make sure all our character info is good before writing it
    for(i = minGlyph; i <= maxGlyph; i++) {
    if( mRemapTable[i] == -1 ) {
    //-Mat get info and try this again
    getCharInfo(i);
    if( mRemapTable[i] == -1 ) {
    Con::errorf( "GFont::write() couldn't get character info for char %i", i);
    }
    }
    }

    // Write char info list
    stream.write(U32(mCharInfoList.size()));
    for(i = 0; i < mCharInfoList.size(); i++)
    {
    const PlatformFont::CharInfo *ci = &mCharInfoList[i];
    stream.write(ci->bitmapIndex);
    stream.write(ci->xOffset);
    stream.write(ci->yOffset);
    stream.write(ci->width);
    stream.write(ci->height);
    stream.write(ci->xOrigin);
    stream.write(ci->yOrigin);
    stream.write(ci->xIncrement);
    }

    stream.write(mTextureSheets.size());
    for(i = 0; i < mTextureSheets.size(); i++) {
    mTextureSheets[i].getBitmap()->writePNG(stream);
    }

    stream.write(mCurX);
    stream.write(mCurY);
    stream.write(mCurSheet);


    stream.write(minGlyph);
    stream.write(maxGlyph);

    // Skip it if we don't have any glyphs to do...
    if(maxGlyph >= minGlyph)
    {
    // Put everything big endian, to be consistent. Do this inplace.
    for(i = minGlyph; i <= maxGlyph; i++)
    mRemapTable[i] = convertHostToBEndian(mRemapTable[i]);

    {
    // Compress.
    const U32 buffSize = 128 * 1024;
    FrameTemp<S32> outBuff(buffSize);
    uLongf destLen = buffSize * sizeof(S32);
    compress2((Bytef*)(S32*)outBuff, &destLen, (Bytef*)(S32*)&mRemapTable[minGlyph], (maxGlyph-minGlyph+1)*sizeof(S32), 9);

    // Write out.
    stream.write((U32)destLen);
    stream.write(destLen, outBuff);
    }

    // Put us back to normal.
    for(i = minGlyph; i <= maxGlyph; i++) {
    mRemapTable[i] = convertBEndianToHost(mRemapTable[i]);

    //if( mRemapTable[i] == -1 ) {
    // Con::errorf( "bogus mRemapTable[i] value in %s %i", mFaceName, mSize );
    //}
    }
    }

    return (stream.getStatus() == Stream::Ok);
    }

    void GFont::exportStrip(const char *fileName, U32 padding, U32 kerning)
    {
    // Figure dimensions of our strip by iterating over all the char infos.
    U32 totalHeight = 0;
    U32 totalWidth = 0;

    S32 heightMin=0, heightMax=0;

    for(S32 i=0; i<mCharInfoList.size(); i++)
    {
    totalWidth += mCharInfoList[i].width + kerning + 2*padding;
    heightMin = getMin((S32)heightMin, (S32)getBaseline() - (S32)mCharInfoList[i].yOrigin);
    heightMax = getMax((S32)heightMax, (S32)getBaseline() - (S32)mCharInfoList[i].yOrigin + (S32)mCharInfoList[i].height);
    }

    totalHeight = heightMax - heightMin + 2*padding;

    // Make the bitmap.
    GBitmap gb(totalWidth, totalHeight, false, mTextureSheets[0].getBitmap()->getFormat());

    dMemset(gb.getWritableBits(), 0, sizeof(U8) * totalHeight * totalWidth );

    // Ok, copy some rects, taking into account padding, kerning, offset.
    U32 curWidth = kerning + padding;

    for(S32 i=0; i<mCharInfoList.size(); i++)
    {
    // Skip invalid stuff.
    if(mCharInfoList[i].bitmapIndex == -1 || mCharInfoList[i].height == 0 || mCharInfoList[i].width == 0)
    continue;

    // Copy the rect.
    U32 bitmap = mCharInfoList[i].bitmapIndex;

    RectI ri(mCharInfoList[i].xOffset, mCharInfoList[i].yOffset, mCharInfoList[i].width, mCharInfoList[i].height );
    Point2I outRi(curWidth, padding + getBaseline() - mCharInfoList[i].yOrigin);
    gb.copyRect(mTextureSheets[bitmap].getBitmap(), ri, outRi);

    // Advance.
    curWidth += mCharInfoList[i].width + kerning + 2*padding;
    }

    // Write the image!
    FileStream fs;

    if(!ResourceManager->openFileForWrite(fs, fileName))
    {
    Con::errorf("GFont::exportStrip - failed to open '%s' for writing.", fileName);
    return;
    }

    // Done!
    gb.writePNG(fs, false);
    }

    /// Used for repacking in GFont::importStrip.
    struct GlyphMap
    {
    U32 charId;
    GBitmap *bitmap;
    };

    static S32 QSORT_CALLBACK GlyphMapCompare(const void *a, const void *b)
    {
    S32 ha = ((GlyphMap *) a)->bitmap->height;
    S32 hb = ((GlyphMap *) b)->bitmap->height;

    return hb - ha;
    }


    void GFont::importStrip(const char *fileName, U32 padding, U32 kerning)
    {
    // Wipe our texture sheets, and reload bitmap data from the specified file.
    // Also deal with kerning.
    // Also, we may have to load RGBA instead of RGB.

    // Wipe our texture sheets.
    mCurSheet = mCurX = mCurY = 0;
    mTextureSheets.clear();

    // Now, load the font strip.
    GBitmap *strip = GBitmap::load(fileName);

    if(!strip)
    {
    Con::errorf("GFont::importStrip - could not load file '%s'!", fileName);
    return;
    }

    // And get parsing and copying - load up all the characters as separate
    // GBitmaps, sort, then pack. Not terribly efficient but this is basically
    // on offline task anyway.

    // Ok, snag some glyphs.
    Vector<GlyphMap> glyphList;
    glyphList.reserve(mCharInfoList.size());

    U32 curWidth = 0;
    for(S32 i=0; i<mCharInfoList.size(); i++)
    {
    // Skip invalid stuff.
    if(mCharInfoList[i].bitmapIndex == -1 || mCharInfoList[i].height == 0 || mCharInfoList[i].width == 0)
    continue;

    // Allocate a new bitmap for this glyph, taking into account kerning and padding.
    glyphList.increment();
    glyphList.last().bitmap = new GBitmap(mCharInfoList[i].width + kerning + 2*padding, mCharInfoList[i].height + 2*padding, false, strip->getFormat());
    glyphList.last().charId = i;

    // Copy the rect.
    RectI ri(curWidth, getBaseline() - mCharInfoList[i].yOrigin, glyphList.last().bitmap->width, glyphList.last().bitmap->height);
    Point2I outRi(0,0);
    glyphList.last().bitmap->copyRect(strip, ri, outRi);

    // Update glyph attributes.
    mCharInfoList[i].width = glyphList.last().bitmap->width;
    mCharInfoList[i].height = glyphList.last().bitmap->height;
    mCharInfoList[i].xOffset -= kerning + padding;
    mCharInfoList[i].xIncrement += kerning;
    mCharInfoList[i].yOffset -= padding;

    // Advance.
    curWidth += ri.extent.x;
    }

    // Ok, we have a big list of glyphmaps now. So let's sort them, then pack them.
    dQsort(glyphList.address(), glyphList.size(), sizeof(GlyphMap), GlyphMapCompare);

    // They're sorted by height, so now we can do some sort of awesome packing.
    Point2I curSheetSize(256, 256);
    Vector<U32> sheetSizes;

    S32 curY = 0;
    S32 curX = 0;
    S32 curLnHeight = 0;
    S32 maxHeight = 0;
    for(U32 i = 0; i < (U32)glyphList.size(); i++)
    {
    PlatformFont::CharInfo *ci = &mCharInfoList[glyphList[i].charId];

    if(ci->height > (U32)maxHeight)
    maxHeight = ci->height;

    if(curX + ci->width > (U32)curSheetSize.x)
    {
    curY += curLnHeight;
    curX = 0;
    curLnHeight = 0;
    }

    if(curY + ci->height > (U32)curSheetSize.y)
    {
    sheetSizes.push_back(curSheetSize.y);
    curX = 0;
    curY = 0;
    curLnHeight = 0;
    }

    if(ci->height > (U32)curLnHeight)
    curLnHeight = ci->height;

    ci->bitmapIndex = sheetSizes.size();
    ci->xOffset = curX;
    ci->yOffset = curY;
    curX += ci->width;
    }

    // Terminate the packing loop calculations.
    curY += curLnHeight;

    if(curY < 64)
    curSheetSize.y = 64;
    else if(curY < 128)
    curSheetSize.y = 128;

    sheetSizes.push_back(curSheetSize.y);

    if(getHeight() + padding * 2 > (U32)maxHeight)
    maxHeight = getHeight() + padding * 2;

    // Allocate texture pages.
    for(S32 i=0; i<sheetSizes.size(); i++)
    {
    char buf[30];
    dSprintf(buf, sizeof(buf), "newfont_%d", smSheetIdCount++);

    GBitmap *bitmap = new GBitmap(BaseTextureSheetSize, BaseTextureSheetSize, false, strip->getFormat());

    // Set everything to transparent.
    U8 *bits = bitmap->getWritableBits();
    dMemset(bits, 0, sizeof(U8) *BaseTextureSheetSize*BaseTextureSheetSize * strip->bytesPerPixel);

    TextureHandle handle = TextureHandle( buf, bitmap, BitmapKeepTexture );
    mTextureSheets.increment();
    constructInPlace(&mTextureSheets.last());
    mTextureSheets.last() = handle;
    }


    // Alright, we're ready to copy bits!
    for(S32 i=0; i<glyphList.size(); i++)
    {
    // Copy each glyph into the appropriate place.
    PlatformFont::CharInfo *ci = &mCharInfoList[glyphList[i].charId];
    U32 bi = ci->bitmapIndex;
    mTextureSheets[bi].getBitmap()->copyRect(glyphList[i].bitmap, RectI(0,0, glyphList[i].bitmap->width,glyphList[i].bitmap->height), Point2I(ci->xOffset, ci->yOffset));
    }

    // Ok, all done! Just refresh some textures and we're set.
    for(S32 i=0; i<sheetSizes.size(); i++)
    mTextureSheets[i].refresh();
    }

    ConsoleFunction(fontAtlasTest, void, 1, 1, "")
    {
    Resource<GFont> f = GFont::create("Arial", 12, Con::getVariable("$GUI::fontCacheDirectory"));
    f->getStrWidthPrecise("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 !");


    for(int i = 0; i < f->mTextureSheets.size(); i++) {
    FileStream fs;

    char buf[1024];
    dSprintf(buf, 1024, "%s-%u.png", "Arial", i);
    fs.open(buf, FileStream::Write);
    f->mTextureSheets[i].getBitmap()->writePNG(fs);
    }
    }
    230 changes: 230 additions & 0 deletions gFont.h
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,230 @@
    //-----------------------------------------------------------------------------
    // Copyright (c) 2013 GarageGames, LLC
    //
    // Permission is hereby granted, free of charge, to any person obtaining a copy
    // of this software and associated documentation files (the "Software"), to
    // deal in the Software without restriction, including without limitation the
    // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
    // sell copies of the Software, and to permit persons to whom the Software is
    // furnished to do so, subject to the following conditions:
    //
    // The above copyright notice and this permission notice shall be included in
    // all copies or substantial portions of the Software.
    //
    // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
    // IN THE SOFTWARE.
    //-----------------------------------------------------------------------------

    #ifndef _GFONT_H_
    #define _GFONT_H_

    //Includes
    #ifndef _PLATFORM_H_
    #include "platform/platform.h"
    #endif
    #ifndef _GBITMAP_H_
    #include "dgl/gBitmap.h"
    #endif
    #ifndef _TVECTOR_H_
    #include "core/tVector.h"
    #endif
    #ifndef _MRECT_H_
    #include "math/mRect.h"
    #endif
    #ifndef _RESMANAGER_H_
    #include "core/resManager.h"
    #endif
    #ifndef _GTEXMANAGER_H_
    #include "dgl/gTexManager.h"
    #endif

    //-Mat use this to make space characters default to a certain x increment
    #define PUAP_SPACE_CHAR_X_INCREMENT 5




    extern ResourceInstance* constructNewFont(Stream& stream);

    class TextureHandle;

    class GFont : public ResourceInstance
    {
    friend ResourceInstance* constructNewFont(Stream& stream);

    static const U32 csm_fileVersion;
    static S32 smSheetIdCount;

    // Enumerations and structs available to everyone...
    public:

    enum Constants
    {
    TabWidthInSpaces = 3,
    SDFFlag = (1<<31),
    VariantHeightFlag = (1<<30),
    CustomScaleFlag = (1<<29)
    };


    // Enumerations and structures available to derived classes
    private:
    PlatformFont *mPlatformFont;
    public:
    Vector<TextureHandle> mTextureSheets;

    S32 mCurX;
    S32 mCurY;
    S32 mCurSheet;

    bool mNeedSave;
    bool mSDF; // Uses a signed distance field
    StringTableEntry mGFTFile;
    StringTableEntry mFaceName;
    U32 mSize;
    U32 mCharSet;

    U32 mHeight;
    U32 mBaseline;
    U32 mAscent;
    U32 mDescent;

    U32 mTexLineHeight; ///< Texture padding may differ, thus this
    F32 mTextureScale; ///< Additional scale for font texture (e.g. when doing retina)
    F32 mMetricScale; ///< Scale for CharInfo metric values (since they are stored as ints)

    Vector<PlatformFont::CharInfo> mCharInfoList; // - List of character info structures, must
    // be accessed through the getCharInfo(U32)
    // function to account for remapping...
    S32 mRemapTable[65536]; // - Index remapping
    public:
    GFont();
    virtual ~GFont();

    protected:
    bool loadCharInfo(const UTF16 ch);
    void addBitmap(PlatformFont::CharInfo &charInfo);
    void addSheet(void);
    void assignSheet(S32 sheetNum, GBitmap *bmp);

    void *mMutex;

    public:
    static Resource<GFont> create(const char *faceName, U32 size, const char *cacheDirectory, U32 charset = TGE_ANSI_CHARSET);

    TextureHandle getTextureHandle(S32 index)
    {
    return mTextureSheets[index];
    }

    const PlatformFont::CharInfo& getCharInfo(const UTF16 in_charIndex);
    static const PlatformFont::CharInfo& getDefaultCharInfo();

    U32 getCharHeight(const UTF16 in_charIndex);
    U32 getCharWidth(const UTF16 in_charIndex);
    U32 getCharXIncrement(const UTF16 in_charIndex);

    bool isValidChar(const UTF16 in_charIndex) const;

    const U32 getHeight() const { return mHeight; }
    const U32 getBaseline() const { return mBaseline; }
    const U32 getAscent() const { return mAscent; }
    const U32 getDescent() const { return mDescent; }
    const F32 getTextureScale() const { return mTextureScale; }
    const F32 getMetricScale() const { return mMetricScale; }
    const F32 getInvMetricScale() const { return 1.0f / mMetricScale; }

    U32 getBreakPos(const UTF16 *string, U32 strlen, U32 width, bool breakOnWhitespace);

    /// These are the preferred width functions.
    U32 getStrNWidth(const UTF16*, U32 n);
    U32 getStrNWidthPrecise(const UTF16*, U32 n);

    /// These UTF8 versions of the width functions will be deprecated, please avoid them.
    U32 getStrWidth(const UTF8*); // Note: ignores c/r
    U32 getStrNWidth(const UTF8*, U32 n);

    U32 getStrWidthPrecise(const UTF8*); // Note: ignores c/r
    U32 getStrNWidthPrecise(const UTF8*, U32 n);

    void wrapString(const UTF8 *string, U32 width, Vector<U32> &startLineOffset, Vector<U32> &lineLen);

    /// Dump information about this font to the console.
    void dumpInfo();

    /// Export to an image strip for image processing.
    void exportStrip(const char *fileName, U32 padding, U32 kerning);

    /// Import an image strip generated with exportStrip, make sure parameters match!
    void importStrip(const char *fileName, U32 padding, U32 kerning);

    /// Query as to presence of platform font. If absent, we cannot generate more
    /// chars!
    const bool hasPlatformFont() const
    {
    return mPlatformFont != NULL;
    }

    /// Query to determine if we should use add or modulate (as A8 textures
    /// are treated as having 0 for RGB).
    bool isAlphaOnly()
    {
    return mTextureSheets[0].getBitmap()->getFormat() == GBitmap::Alpha;
    }

    /// Get the filename for a cached font.
    static void getFontCacheFilename(const char *faceName, U32 faceSize, U32 buffLen, char *outBuff);

    /// Get the face name of the font.
    StringTableEntry getFontFaceName() const { return mFaceName; };

    bool read(Stream& io_rStream);
    bool write(Stream& io_rStream);

    /// Override existing platform font if any with a new one from an external
    /// source. This is primarily used in font processing tools to enable
    /// trickery (ie, putting characters from multiple fonts in a single
    /// GFT) and should be used with caution!
    void forcePlatformFont(PlatformFont *pf)
    {
    mPlatformFont = pf;
    }

    static U32 BaseTextureSheetSize;
    };

    inline U32 GFont::getCharXIncrement(const UTF16 in_charIndex)
    {
    const PlatformFont::CharInfo& rChar = getCharInfo(in_charIndex);
    return rChar.xIncrement;
    }

    inline U32 GFont::getCharWidth(const UTF16 in_charIndex)
    {
    const PlatformFont::CharInfo& rChar = getCharInfo(in_charIndex);
    return rChar.width;
    }

    inline U32 GFont::getCharHeight(const UTF16 in_charIndex)
    {
    const PlatformFont::CharInfo& rChar = getCharInfo(in_charIndex);
    return rChar.height;
    }

    inline bool GFont::isValidChar(const UTF16 in_charIndex) const
    {
    if(mRemapTable[in_charIndex] != -1)
    return true;

    if(mPlatformFont)
    return mPlatformFont->isValidChar(in_charIndex);

    return false;
    }

    #endif //_GFONT_H_
    10 changes: 10 additions & 0 deletions sdP.hlsl
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,10 @@
    float4 main( float4 color_in : COLOR0,
    float2 texCoord_in : TEXCOORD0,
    uniform sampler2D diffuseMap : register(S0) ) : COLOR0
    {
    float distance = tex2D(diffuseMap, texCoord_in).a;
    const float w = 0.5/3;
    const float threshold = 0.5f;
    float alpha = smoothstep( threshold-w, threshold+w, distance );
    return float4(color_in.rgb,alpha * color_in.a);
    }
    387 changes: 387 additions & 0 deletions stbFont.cc
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,387 @@
    //-----------------------------------------------------------------------------
    // Copyright (c) 2021 tgemit contributors.
    // See AUTHORS file and git repository for contributor information.
    //
    // SPDX-License-Identifier: MIT
    //-----------------------------------------------------------------------------

    #include "platform/platform.h"
    #include "core/unicode.h"
    #include "console/console.h"
    #include "platformGeneric/stbFont.h"
    #include "core/tVector.h"
    #include "core/stringTable.h"
    #include "core/fileStream.h"
    #include <unordered_map>
    #include <algorithm>


    #define STB_TRUETYPE_IMPLEMENTATION
    #include "stb/stb_truetype.h"

    Vector<STBFont::SizeCorrection> STBFont::smCorrectedSizes;
    Vector<STBFont::FontSubstitution> STBFont::smSubstitutions;
    bool gSTBInit;

    //
    class STBFontCollection
    {
    public:

    struct LoadedInfo
    {
    U8* data;
    StringTableEntry filename;
    };

    Vector<LoadedInfo> loadedFontData;
    std::unordered_map<StringTableEntry, stbtt_fontinfo*> loadedFonts;

    static STBFontCollection smInstance;

    ~STBFontCollection()
    {
    for (LoadedInfo info : loadedFontData)
    {
    delete info.data;
    }
    for (auto &kv : loadedFonts)
    {
    delete kv.second;
    }
    loadedFontData.clear();
    loadedFonts.clear();
    }

    bool loadData(const char* path)
    {
    FileStream fs;
    if (fs.open(path, FileStream::Read))
    {
    U32 len = fs.getStreamSize();
    U8* data = new U8[len];
    fs.read(len, data);
    fs.close();

    LoadedInfo info = {data, StringTable->insert(path)};
    loadedFontData.push_back(info);
    //printf("FONT FILE LOADED %s\n", path);
    }
    }

    void loadDataFolder(const char* path)
    {
    Vector<Platform::FileInfo> files;
    Platform::dumpPath(path, files, 2);

    for (Platform::FileInfo &file : files)
    {
    const char *ext = dStrrchr(file.pFileName, '.');
    if (ext && (dStricmp(ext, ".ttf") == 0 || dStricmp(ext, ".otf") == 0))
    {
    char buffer[4096];
    dSprintf(buffer, sizeof(buffer), "%s/%s", file.pFullPath, file.pFileName);
    loadData(buffer);
    }
    }
    }

    stbtt_fontinfo* findFont(const char* name)
    {
    char buffer[256];
    const char* namePtr = name;
    bool valid = false;
    stbtt_fontinfo* info = NULL;

    StringTableEntry realName = STBFont::getCorrectName(StringTable->insert(name));

    auto itr = loadedFonts.find(realName);
    if (itr != loadedFonts.end())
    {
    return info;
    }

    auto tryLoadFont = [this, &realName, &info](const LoadedInfo dataBlock) {
    int fontOffset = stbtt_FindMatchingFont(dataBlock.data, realName, STBTT_MACSTYLE_DONTCARE);
    if (fontOffset != -1)
    {
    info = new stbtt_fontinfo;

    if (stbtt_InitFont(info, dataBlock.data, fontOffset) == 0)
    {
    Con::errorf("STBFontCollection couldn't init font %s!", realName);
    delete info;
    info = NULL;
    }
    else
    {
    loadedFonts[realName] = info;
    return true;
    }
    }

    return false;
    };

    std::find_if(loadedFontData.begin(), loadedFontData.end(), tryLoadFont);

    // Try x Regular
    if (info == NULL)
    {
    dSprintf(buffer, 256, "%s Regular", realName);
    realName = STBFont::getCorrectName(StringTable->insert(&buffer[0]));
    std::find_if(loadedFontData.begin(), loadedFontData.end(), tryLoadFont);
    }

    return info;
    }
    };

    STBFontCollection STBFontCollection::smInstance;

    STBFont::STBFont() :
    mBaseline(0),
    mHeight(0),
    mTexLineHeight(0),
    mSize(0),
    mScaleMapping(0.0f),
    mTextureScale(1.0f),
    mMetricScale(10.0f),
    mFont(NULL),
    mSDF(false)
    {
    }

    STBFont::~STBFont()
    {
    }

    bool STBFont::isValidChar(const UTF16 ch) const
    {
    return (ch < 0x20) ? false : true;
    }

    bool STBFont::isValidChar(const UTF8 *str) const
    {
    UTF32 theChar = oneUTF8toUTF32(str);
    return isValidChar(theChar);
    }

    PlatformFont::CharInfo &STBFont::getCharInfo(const UTF16 ch) const
    {
    static PlatformFont::CharInfo cinfo;
    memset(&cinfo, 0, sizeof(cinfo));

    // prep values for GFont::addBitmap()
    cinfo.bitmapIndex = 0;
    cinfo.xOffset = 0;
    cinfo.yOffset = 0;

    int leftSideBearing = 0;
    int width = 0;
    int height = 0;
    int __ix0,__iy0,__ix1,__iy1;
    int box_w,box_h;
    int glyph = stbtt_FindGlyphIndex(mFont, ch);

    // Basic metrics
    stbtt_GetGlyphHMetrics(mFont, glyph, &cinfo.xIncrement, &leftSideBearing); // leftSideBearing?

    stbtt_GetGlyphBitmapBoxSubpixel(mFont, glyph, mScaleMapping, mScaleMapping, 0.0f,0.0f, &__ix0,&__iy0,&__ix1,&__iy1);
    box_w = (__ix1 - __ix0);
    box_h = (__iy1 - __iy0);

    U8* field = NULL;

    if (mSDF)
    {
    // SDF
    field = stbtt_GetGlyphSDF(mFont, mScaleMapping * mTextureScale, glyph, 2, 128, 128.0/3.0, &width, &height, &cinfo.xOrigin, &cinfo.yOrigin);
    }
    else
    {
    F32 theScale = mScaleMapping * mTextureScale;
    field = stbtt_GetGlyphBitmap(mFont, theScale, theScale, glyph, &width, &height, &cinfo.xOrigin, &cinfo.yOrigin);
    }

    cinfo.width = width;
    cinfo.height = height;

    // ascent = baseline - yOrigin
    cinfo.yOrigin = ((float)-cinfo.yOrigin) * mMetricScale;
    cinfo.xOrigin = ((float)cinfo.xOrigin) * mMetricScale;

    cinfo.xIncrement *= mScaleMapping * mMetricScale;

    if (cinfo.height > getTexLineHeight())
    {
    Con::warnf("Warning: Not enough texure height for glyph %u, atlas height will be extended.", (U32)ch);
    }

    // Finish if character is undrawable.
    if ( (cinfo.width == 0 && cinfo.height == 0) || field == NULL )
    {
    if (field && mSDF)
    stbtt_FreeSDF(field, NULL);
    else if (field && !mSDF)
    stbtt_FreeBitmap(field, NULL);
    return cinfo;
    }

    // Allocate a bitmap surface.
    const U32 bitmapSize = cinfo.width * cinfo.height;
    if (bitmapSize > 0)
    {
    cinfo.bitmapData = new U8[cinfo.width * cinfo.height];
    memcpy(cinfo.bitmapData, field, cinfo.width * cinfo.height);
    }

    if (field)
    {
    if (mSDF)
    stbtt_FreeSDF(field, NULL);
    else
    stbtt_FreeBitmap(field, NULL);
    }

    #if 0
    Con::printf("Char %u Width:%f, Height:%f, OriginX:%f, OriginY:%f",
    character,
    cinfo.width,
    cinfo.height,
    cinfo.xOrigin,
    cinfo.yOrigin );
    #endif

    // Return character information.
    return cinfo;
    }

    PlatformFont::CharInfo &STBFont::getCharInfo(const UTF8 *str) const
    {
    return getCharInfo(oneUTF32toUTF16(oneUTF8toUTF32(str, NULL)));
    }

    bool STBFont::create(const char *name, U32 size, U32 charset)
    {
    stbtt_fontinfo* info = STBFontCollection::smInstance.findFont(name);
    if (info == NULL)
    {
    if (STBFontCollection::smInstance.loadedFontData.size() != 0)
    Con::errorf( "Could not generate a font reference to font name '%s' of size '%d'", name, size );
    return false;
    }

    // See if we have a correction value
    StringTableEntry steName = StringTable->insert(name);
    for (STBFont::SizeCorrection &correction : smCorrectedSizes)
    {
    if (correction.name == steName && size == correction.inSize)
    {
    size = correction.outSize;
    break;
    }
    }

    mFont = info;
    mSize = size;

    mScaleMapping = stbtt_ScaleForMappingEmToPixels(mFont, mSize);
    mTextureScale = mSDF ? 2.0 : 1.0f;
    mMetricScale = 10.0f;

    // Fetch font metrics.
    int ascent=0;
    int descent=0;
    stbtt_GetFontVMetrics(mFont, &ascent, &descent, 0);
    stbtt_GetFontVMetricsOS2(mFont, &ascent, &descent, 0);

    AssertFatal(descent <= 0, "unexpected value");

    int act_height = (ascent + (-descent));
    F32 basicDesignScale = (mSize) / (act_height);

    // Set metrics

    mBaseline = (U32)roundf((F32)ascent * mScaleMapping);

    ascent = STBTT_iceil(ascent * mScaleMapping);
    descent = STBTT_ifloor(descent * mScaleMapping);

    mTexLineHeight = ((ceilf((ascent + (-descent)))) * mTextureScale) + 2 + 2;
    mHeight = ceilf((ascent + (-descent)));

    return true;
    }

    StringTableEntry STBFont::getCorrectName(StringTableEntry name)
    {
    auto itr = std::find_if(smSubstitutions.begin(), smSubstitutions.end(), [name](auto &val){
    return name == val.inName;
    });

    return (itr == smSubstitutions.end() ? name : itr->outName);
    }

    static void initSTBDefaults()
    {
    #if defined(TORQUE_OS_WIN32)
    STBFontCollection::smInstance.loadDataFolder("C:\\Windows\\Fonts");
    #elif defined(TORQUE_OS_LINUX)
    STBFontCollection::smInstance.loadDataFolder("/usr/share/fonts/truetype");
    STBFontCollection::smInstance.loadDataFolder("/usr/share/fonts/opentype");
    #elif defined(TORQUE_OS_OSX)
    STBFontCollection::smInstance.loadDataFolder("/System/Library/Fonts");
    #endif

    gSTBInit = true;
    }

    void PlatformFont::enumeratePlatformFonts( Vector<StringTableEntry>& fonts )
    {
    if (!gSTBInit)
    {
    initSTBDefaults();
    }
    }

    ConsoleFunction(addPlatformFontDirectory, void, 2, 2, "path")
    {
    gSTBInit = true;
    STBFontCollection::smInstance.loadDataFolder(argv[1]);
    }

    ConsoleFunction(adjustPlatformFontSize, void, 4, 4, "name, old size, new size")
    {
    STBFont::SizeCorrection size;
    size.name = StringTable->insert(argv[1]);
    size.inSize = dAtof(argv[2]);
    size.outSize = dAtof(argv[3]);
    STBFont::smCorrectedSizes.push_back(size);
    }

    ConsoleFunction(adjustPlatformFontName, void, 3, 3, "old name, new name")
    {
    STBFont::FontSubstitution correction;
    correction.inName = StringTable->insert(argv[1]);
    correction.outName = StringTable->insert(argv[2]);
    STBFont::smSubstitutions.push_back(correction);
    }


    PlatformFont* createPlatformFont( const char* name, U32 size, U32 charset )
    {
    if (!gSTBInit)
    {
    initSTBDefaults();
    }

    PlatformFont* pFont = new STBFont();

    if ( pFont->create(name, size, charset) )
    return pFont;

    delete pFont;

    return NULL;
    }
    69 changes: 69 additions & 0 deletions stbFont.h
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,69 @@
    //-----------------------------------------------------------------------------
    // Copyright (c) 2021 tgemit contributors.
    // See AUTHORS file and git repository for contributor information.
    //
    // SPDX-License-Identifier: MIT
    //-----------------------------------------------------------------------------
    #ifndef _STB_FONT_H_
    #define _STB_FONT_H_

    #include "platform/platform.h"
    #include "core/unicode.h"
    #include "stb/stb_truetype.h"
    #include <unordered_map>

    struct stbtt_fontinfo;

    class STBFont : public PlatformFont
    {
    public:

    struct FontSubstitution
    {
    StringTableEntry inName;
    StringTableEntry outName;
    };

    struct SizeCorrection
    {
    StringTableEntry name;
    U32 inSize;
    U32 outSize;
    };

    U32 mBaseline; // i.e. ascent
    U32 mHeight; // distance between lines
    U32 mTexLineHeight; // distance + padding between lines in texture

    F32 mSize; ///< Point size of font
    F32 mScaleMapping; ///< Font scale needed to match mSize (internal)
    F32 mTextureScale; ///< Scale of font texture
    F32 mMetricScale; ///< Scale of font metrics

    bool mSDF;

    stbtt_fontinfo* mFont;

    STBFont();
    virtual ~STBFont();

    virtual bool isValidChar(const UTF16 ch) const;
    virtual bool isValidChar(const UTF8 *str) const;
    virtual bool isSDF() const { return mSDF; }

    virtual U32 getFontHeight() const { return mHeight; }
    virtual U32 getFontBaseLine() const { return mBaseline; }
    virtual U32 getTexLineHeight() const { return mTexLineHeight; }
    virtual F32 getTexScale() const { return mTextureScale; }
    virtual F32 getMetricScale() const { return mMetricScale; }

    virtual PlatformFont::CharInfo &getCharInfo(const UTF16 ch) const;
    virtual PlatformFont::CharInfo &getCharInfo(const UTF8 *str) const;
    virtual bool create(const char *name, U32 size, U32 charset = TGE_ANSI_CHARSET);

    static StringTableEntry getCorrectName(StringTableEntry name);
    static Vector<SizeCorrection> smCorrectedSizes;
    static Vector<FontSubstitution> smSubstitutions;
    };

    #endif