Could a Font::setFallbackDefaultFont() be added?


#1

If you use a “strange” language like Chinese or Hindi, you usually end up with text like “something in english [something in Chinese] etc…” (as they are words that only exist in English).

Currently, if you select a font for Chinese (SimYun for one, or Mangal for Hindi), as the default font via Font::setDefaultSerifFont, the english part of the text is renderer as squares because the unicode font doesn’t contains ASCII code (low 0x00-0xFF) in it.

It would be really useful to be able to set a fallback font to use when the glyph for the ASCII code doesn’t exist in the foreign font.

This would means adding a static method to Font, but also modifying the GlyphArrangement methods to handle the missing glyph by querying the glyph from the fallback font instead. If the fallback font doesn’t have the glyph either, then fallback on the squares…

It’s the way MS does in its software, and it’s way better than those awful squares in Juce.


#2

That’s not a bad idea! I’ll look into it…


#3

In fact such code would be, really, really simple to implement:

In juce_Typeface.cpp

const TypefaceGlyphInfo* Typeface::getGlyph (const juce_wchar character) throw()
{
    if (((unsigned int) character) < 128 && lookupTable [character] > 0)
        return (const TypefaceGlyphInfo*) glyphs [(int) lookupTable [character]];

    for (int i = 0; i < glyphs.size(); ++i)
    {
        const TypefaceGlyphInfo* const g = (const TypefaceGlyphInfo*) glyphs.getUnchecked(i);

        if (g->character == character)
            return g;
    }

    if (! isFullyPopulated)
    {
        findAndAddSystemGlyph (character);

        for (int i = 0; i < glyphs.size(); ++i)
        {
            const TypefaceGlyphInfo* const g = (const TypefaceGlyphInfo*) glyphs.getUnchecked(i);

            if (g->character == character)
                return g;
        }
    }

    if (CharacterFunctions::isWhitespace (character) && character != L' ')
        return getGlyph (L' ');
    else if (character != defaultCharacter)
    {
        // Check the fallback font for this character
        if (Font::getFallbackFont() && Font::getFallbackFont() != this)
               return Font::getFallbackFont()->getGlyph(character);
        else return getGlyph (defaultCharacter);
    }

    return 0;
}

In juce_Font.h:

/** Changes the fallback font family name.
      The fallback font is looked after when a glyph is missing while rendering text 
      This changes the value that is returned by getFallbackFont(), so changing this will change the default system font used.

      @see getDefaultSansSerifFontName
*/
static void setDefaultFallbackFontName (const String& name) throw();

/** Returns a platform-specific font that is used when a glyph is not found while rendering text.

      @see setDefaultFallbackFontName, getDefaultSansSerifFontName, getDefaultMonospacedFontName
*/
static const Font * getFallbackFont() throw();

// In the members:
    static Font * fallbackFont;

And finally in juce_Font.cpp:

static Font * fallbackFont = 0;

void Font::setDefaultFallbackFontName (const String& name)
{
    Font * oldFont = fallbackFont;    
    // An atomic set should be used here to avoid locking 
    fallbackFont = new Font(name, getHeight(), plain);
    delete oldFont;
}

Font * Font::getFallbackFont()
{
    return fallbackFont;
}

I’ve chosen not to follow the other getDefault__ method as the font is likely to be use very often.
You’ll have to had a “delete Font::getFallbackFont()” in the application deletion code through.

Let me know what you think about this design.
BTW, for multithread access to the fallback font, an atomic exchange (InterlockedExchangePtr under Win32, and gcc builtins atomic for other platform) should be used in setDefaultFallbackFontName() so that thread always see consistent pointer value. We could critsec protect this, but I don’t think it’s worth it.


#4

Looks like a very sensible design to me. I’ll throw something very similar into the build shortly!


#5

After thinking a little about this, I came to:

struct ReferFont
{
    Font *  ptr;
    int       referenceCount;
    ReferFont(Font * _ptr) : ptr(_ptr), referenceCount(1) {}
};

class MonitoredFont
{
private:
    const Font * ptr;
    mutable int & referenceCount;

public:
    inline operator const Font * () const { return ptr; }

    MonitoredFont(const ReferFont & font) : referenceCount(font.referenceCount), ptr(font.ptr) { referenceCount ++;}
    ~MonitoredFont() { referenceCount--; if (!referenceCount) delete ptr; }

   friend void Font::setDefaultFallbackFontName(const String & name);
};

static ReferFont fallbackFont(0);

const MonitoredFont getFallbackFont()
{
    return MonitoredFont(fallbackFont);
} // Return a temporary that will be used like a pointer by other code
  // As soon as the temporary is destructed, the ref count is decremented

void setDefaultFallbackFontName (const String& name)
{
    MonitoredFont tmpUsed(fallbackFont);
   
    // An atomic set should be used here to avoid locking
    ReferFont newFallback;
    newFallback.ptr = new Font(name, getHeight(), plain);
    // This doesn't need to be atomic
    fallbackFont = newFallback;
    // We want the older object to be destructed ASAP
    // If we are the only one using fallbackFont, the reference count will be 0-ed here and then destructed
    // Else the last thread using it will delete it 
    tmpUsed.referenceCount --;
}

In my previous (lame) attempt, a thread could still have a pointer on the fallbackFont while the setDefaultFallbackFontName deleted it.

This version simply add refcounting so if a thread has the pointer, it’s going to stay valid until the thread destruct the monitored object.
The reference counting should be atomic too (but I guess you’ve already done this thousand times in the String class).


#6

Typefaces already use reference counting - I’ll work out something simpler than this.


#7