text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

//==============================================================================
/**
    Represents a particular font, including its size, style, etc.

    Apart from the typeface to be used, a Font object also dictates whether
    the font is bold, italic, underlined, how big it is, and its kerning and
    horizontal scale factor.

    @see Typeface
*/
class JUCE_API  Font  final
{
public:
    //==============================================================================
    /** A combination of these values is used by the constructor to specify the
        style of font to use.
    */
    enum FontStyleFlags
    {
        plain       = 0,    /**< indicates a plain, non-bold, non-italic version of the font. @see setStyleFlags */
        bold        = 1,    /**< boldens the font. @see setStyleFlags */
        italic      = 2,    /**< finds an italic version of the font. @see setStyleFlags */
        underlined  = 4     /**< underlines the font. @see setStyleFlags */
    };

    //==============================================================================
    /** Creates a sans-serif font in a given size.

        @param fontHeight   the height in pixels (can be fractional)
        @param styleFlags   the style to use - this can be a combination of the
                            Font::bold, Font::italic and Font::underlined, or
                            just Font::plain for the normal style.
        @see FontStyleFlags, getDefaultSansSerifFontName
    */
    Font (float fontHeight, int styleFlags = plain);

    /** Creates a font with a given typeface and parameters.

        @param typefaceName the font family of the typeface to use
        @param fontHeight   the height in pixels (can be fractional)
        @param styleFlags   the style to use - this can be a combination of the
                            Font::bold, Font::italic and Font::underlined, or
                            just Font::plain for the normal style.
        @see FontStyleFlags, getDefaultSansSerifFontName
    */
    Font (const String& typefaceName, float fontHeight, int styleFlags);

    /** Creates a font with a given typeface and parameters.

        @param typefaceName  the font family of the typeface to use
        @param typefaceStyle the font style of the typeface to use
        @param fontHeight    the height in pixels (can be fractional)
    */
    Font (const String& typefaceName, const String& typefaceStyle, float fontHeight);

    /** Creates a copy of another Font object. */
    Font (const Font& other) noexcept;

    /** Creates a font for a typeface. */
    Font (const Typeface::Ptr& typeface);

    /** Creates a basic sans-serif font at a default height.

        You should use one of the other constructors for creating a font that you're planning
        on drawing with - this constructor is here to help initialise objects before changing
        the font's settings later.
    */
    Font();

    /** Move constructor */
    Font (Font&& other) noexcept;

    /** Move assignment operator */
    Font& operator= (Font&& other) noexcept;

    /** Copies this font from another one. */
    Font& operator= (const Font& other) noexcept;

    bool operator== (const Font& other) const noexcept;
    bool operator!= (const Font& other) const noexcept;

    /** Destructor. */
    ~Font() noexcept;

    //==============================================================================
    /** Changes the font family of the typeface.

        e.g. "Arial", "Courier", etc.

        This may also be set to Font::getDefaultSansSerifFontName(), Font::getDefaultSerifFontName(),
        or Font::getDefaultMonospacedFontName(), which are not actual platform-specific font family names,
        but are generic font family names that are used to represent the various default fonts.
        If you need to know the exact typeface font family being used, you can call
        Font::getTypeface()->getName(), which will give you the platform-specific font family.

        If a suitable font isn't found on the machine, it'll just use a default instead.
    */
    void setTypefaceName (const String& faceName);

    /** Returns the font family of the typeface that this font uses.

        e.g. "Arial", "Courier", etc.

        This may also be set to Font::getDefaultSansSerifFontName(), Font::getDefaultSerifFontName(),
        or Font::getDefaultMonospacedFontName(), which are not actual platform-specific font family names,
        but are generic font familiy names that are used to represent the various default fonts.

        If you need to know the exact typeface font family being used, you can call
        Font::getTypeface()->getName(), which will give you the platform-specific font family.
    */
    const String& getTypefaceName() const noexcept;

    //==============================================================================
    /** Returns the font style of the typeface that this font uses.
        @see withTypefaceStyle, getAvailableStyles()
    */
    const String& getTypefaceStyle() const noexcept;

    /** Changes the font style of the typeface.
        @see getAvailableStyles()
    */
    void setTypefaceStyle (const String& newStyle);

    /** Returns a copy of this font with a new typeface style.
        @see getAvailableStyles()
    */
    Font withTypefaceStyle (const String& newStyle) const;

    /** Returns a list of the styles that this font can use. */
    StringArray getAvailableStyles() const;

    //==============================================================================
    /** Returns a typeface font family that represents the default sans-serif font.

        This is also the typeface that will be used when a font is created without
        specifying any typeface details.

        Note that this method just returns a generic placeholder string that means "the default
        sans-serif font" - it's not the actual font family of this font.

        @see setTypefaceName, getDefaultSerifFontName, getDefaultMonospacedFontName
    */
    static const String& getDefaultSansSerifFontName();

    /** Returns a typeface font family that represents the default serif font.

        Note that this method just returns a generic placeholder string that means "the default
        serif font" - it's not the actual font family of this font.

        @see setTypefaceName, getDefaultSansSerifFontName, getDefaultMonospacedFontName
    */
    static const String& getDefaultSerifFontName();

    /** Returns a typeface font family that represents the default monospaced font.

        Note that this method just returns a generic placeholder string that means "the default
        monospaced font" - it's not the actual font family of this font.

        @see setTypefaceName, getDefaultSansSerifFontName, getDefaultSerifFontName
    */
    static const String& getDefaultMonospacedFontName();

    /** Returns a font style name that represents the default style.

        Note that this method just returns a generic placeholder string that means "the default
        font style" - it's not the actual name of the font style of any particular font.

        @see setTypefaceStyle
    */
    static const String& getDefaultStyle();

    /** Returns the default system typeface for the given font. */
    static Typeface::Ptr getDefaultTypefaceForFont (const Font& font);

    //==============================================================================
    /** Returns a copy of this font with a new height. */
    Font withHeight (float height) const;

    /** Returns a copy of this font with a new height, specified in points. */
    Font withPointHeight (float heightInPoints) const;

    /** Changes the font's height.
        @see getHeight, withHeight, setHeightWithoutChangingWidth
    */
    void setHeight (float newHeight);

    /** Changes the font's height without changing its width.
        This alters the horizontal scale to compensate for the change in height.
    */
    void setHeightWithoutChangingWidth (float newHeight);

    /** Returns the total height of this font, in pixels.
        This is the maximum height, from the top of the ascent to the bottom of the
        descenders.

        @see withHeight, setHeightWithoutChangingWidth, getAscent
    */
    float getHeight() const noexcept;

    /** Returns the total height of this font, in points.
        This is the maximum height, from the top of the ascent to the bottom of the
        descenders.

        @see withPointHeight, getHeight
    */
    float getHeightInPoints() const;

    /** Returns the height of the font above its baseline, in pixels.
        This is the maximum height from the baseline to the top.
        @see getHeight, getDescent
    */
    float getAscent() const;

    /** Returns the height of the font above its baseline, in points.
        This is the maximum height from the baseline to the top.
        @see getHeight, getDescent
    */
    float getAscentInPoints() const;

    /** Returns the amount that the font descends below its baseline, in pixels.
        This is calculated as (getHeight() - getAscent()).
        @see getAscent, getHeight
    */
    float getDescent() const;

    /** Returns the amount that the font descends below its baseline, in points.
        This is calculated as (getHeight() - getAscent()).
        @see getAscent, getHeight
    */
    float getDescentInPoints() const;

    //==============================================================================
    /** Returns the font's style flags.
        This will return a bitwise-or'ed combination of values from the FontStyleFlags
        enum, to describe whether the font is bold, italic, etc.
        @see FontStyleFlags, withStyle
    */
    int getStyleFlags() const noexcept;

    /** Returns a copy of this font with the given set of style flags.
        @param styleFlags     a bitwise-or'ed combination of values from the FontStyleFlags enum.
        @see FontStyleFlags, getStyleFlags
    */
    Font withStyle (int styleFlags) const;

    /** Changes the font's style.
        @param newFlags     a bitwise-or'ed combination of values from the FontStyleFlags enum.
        @see FontStyleFlags, withStyle
    */
    void setStyleFlags (int newFlags);

    //==============================================================================
    /** Makes the font bold or non-bold. */
    void setBold (bool shouldBeBold);

    /** Returns a copy of this font with the bold attribute set.
        If the font does not have a bold version, this will return the default font.
     */
    Font boldened() const;

    /** Returns true if the font is bold. */
    bool isBold() const noexcept;

    /** Makes the font italic or non-italic. */
    void setItalic (bool shouldBeItalic);
    /** Returns a copy of this font with the italic attribute set. */
    Font italicised() const;
    /** Returns true if the font is italic. */
    bool isItalic() const noexcept;

    /** Makes the font underlined or non-underlined. */
    void setUnderline (bool shouldBeUnderlined);
    /** Returns true if the font is underlined. */
    bool isUnderlined() const noexcept;

    //==============================================================================
    /** Returns the font's horizontal scale.
        A value of 1.0 is the normal scale, less than this will be narrower, greater
        than 1.0 will be stretched out.

        @see withHorizontalScale
    */
    float getHorizontalScale() const noexcept;

    /** Returns a copy of this font with a new horizontal scale.
        @param scaleFactor  a value of 1.0 is the normal scale, less than this will be
                            narrower, greater than 1.0 will be stretched out.
        @see getHorizontalScale
    */
    Font withHorizontalScale (float scaleFactor) const;

    /** Changes the font's horizontal scale factor.
        @param scaleFactor  a value of 1.0 is the normal scale, less than this will be
                            narrower, greater than 1.0 will be stretched out.
    */
    void setHorizontalScale (float scaleFactor);

    /** Returns the minimum horizontal scale to which fonts may be squashed when trying to
        create a layout.
        @see setDefaultMinimumHorizontalScaleFactor
    */
    static float getDefaultMinimumHorizontalScaleFactor() noexcept;

    /** Sets the minimum horizontal scale to which fonts may be squashed when trying to
        create a text layout.
        @see getDefaultMinimumHorizontalScaleFactor
    */
    static void setDefaultMinimumHorizontalScaleFactor (float newMinimumScaleFactor) noexcept;

    /** Returns the font's kerning.

        This is the extra space added between adjacent characters, as a proportion
        of the font's height.

        A value of zero is normal spacing, positive values will spread the letters
        out more, and negative values make them closer together.
    */
    float getExtraKerningFactor() const noexcept;

    /** Returns a copy of this font with a new kerning factor.
        @param extraKerning     a multiple of the font's height that will be added
                                to space between the characters. So a value of zero is
                                normal spacing, positive values spread the letters out,
                                negative values make them closer together.
    */
    Font withExtraKerningFactor (float extraKerning) const;

    /** Changes the font's kerning.
        @param extraKerning     a multiple of the font's height that will be added
                                to space between the characters. So a value of zero is
                                normal spacing, positive values spread the letters out,
                                negative values make them closer together.
    */
    void setExtraKerningFactor (float extraKerning);

    //==============================================================================
    /** Changes all the font's characteristics with one call. */
    void setSizeAndStyle (float newHeight,
                          int newStyleFlags,
                          float newHorizontalScale,
                          float newKerningAmount);

    /** Changes all the font's characteristics with one call. */
    void setSizeAndStyle (float newHeight,
                          const String& newStyle,
                          float newHorizontalScale,
                          float newKerningAmount);

    //==============================================================================
    /** Returns the total width of a string as it would be drawn using this font.
        For a more accurate floating-point result, use getStringWidthFloat().
    */
    int getStringWidth (const String& text) const;

    /** Returns the total width of a string as it would be drawn using this font.
        @see getStringWidth
    */
    float getStringWidthFloat (const String& text) const;

    /** Returns the series of glyph numbers and their x offsets needed to represent a string.

        An extra x offset is added at the end of the run, to indicate where the right hand
        edge of the last character is.
    */
    void getGlyphPositions (const String& text, Array<int>& glyphs, Array<float>& xOffsets) const;

    //==============================================================================
    /** Returns the typeface used by this font.

        Note that the object returned may go out of scope if this font is deleted
        or has its style changed.
    */
    Typeface* getTypeface() const;

    /** Creates an array of Font objects to represent all the fonts on the system.

        If you just need the font family names of the typefaces, you can also use
        findAllTypefaceNames() instead.

        @param results  the array to which new Font objects will be added.
    */
    static void findFonts (Array<Font>& results);

    /** Returns a list of all the available typeface font families.

        The names returned can be passed into setTypefaceName().

        You can use this instead of findFonts() if you only need their font family names,
        and not font objects.
    */
    static StringArray findAllTypefaceNames();

    /** Returns a list of all the available typeface font styles.

        The names returned can be passed into setTypefaceStyle().

        You can use this instead of findFonts() if you only need their styles, and not
        font objects.
    */
    static StringArray findAllTypefaceStyles (const String& family);

    //==============================================================================
    /** Returns the font family of the typeface to be used for rendering glyphs that aren't
        found in the requested typeface.
    */
    static const String& getFallbackFontName();

    /** Sets the (platform-specific) font family of the typeface to use to find glyphs that
        aren't available in whatever font you're trying to use.
    */
    static void setFallbackFontName (const String& name);

    /** Returns the font style of the typeface to be used for rendering glyphs that aren't
        found in the requested typeface.
    */
    static const String& getFallbackFontStyle();

    /** Sets the (platform-specific) font style of the typeface to use to find glyphs that
        aren't available in whatever font you're trying to use.
    */
    static void setFallbackFontStyle (const String& style);

    //==============================================================================
    /** Creates a string to describe this font.
        The string will contain information to describe the font's typeface, size, and
        style. To recreate the font from this string, use fromString().
    */
    String toString() const;

    /** Recreates a font from its stringified encoding.
        This method takes a string that was created by toString(), and recreates the
        original font.
    */
    static Font fromString (const String& fontDescription);

private:
    //==============================================================================
    class SharedFontInternal;
    ReferenceCountedObjectPtr<SharedFontInternal> font;
    void dupeInternalIfShared();
    void checkTypefaceSuitability();
    float getHeightToPointsFactor() const;

    JUCE_LEAK_DETECTOR (Font)
};

} // namespace juce


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

//==============================================================================
/**
    A typeface that can be populated with custom glyphs.

    You can create a CustomTypeface if you need one that contains your own glyphs,
    or if you need to load a typeface from a Juce-formatted binary stream.

    If you want to create a copy of a native face, you can use addGlyphsFromOtherTypeface()
    to copy glyphs into this face.

    NOTE! For most people this class is almost certainly NOT the right tool to use!
    If what you want to do is to embed a font into your exe, then your best plan is
    probably to embed your TTF/OTF font file into your binary using the Projucer,
    and then call Typeface::createSystemTypefaceFor() to load it from memory.

    @see Typeface, Font
*/
class JUCE_API  CustomTypeface  : public Typeface
{
public:
    //==============================================================================
    /** Creates a new, empty typeface. */
    CustomTypeface();

    /** Loads a typeface from a previously saved stream.
        The stream must have been created by writeToStream().

        NOTE! Since this class was written, support was added for loading real font files from
        memory, so for most people, using Typeface::createSystemTypefaceFor() to load a real font
        is more appropriate than using this class to store it in a proprietary format.

        @see writeToStream
    */
    explicit CustomTypeface (InputStream& serialisedTypefaceStream);

    /** Destructor. */
    ~CustomTypeface();

    //==============================================================================
    /** Resets this typeface, deleting all its glyphs and settings. */
    void clear();

    /** Sets the vital statistics for the typeface.
        @param fontFamily the typeface's font family
        @param ascent     the ascent - this is normalised to a height of 1.0 and this is
                          the value that will be returned by Typeface::getAscent(). The
                          descent is assumed to be (1.0 - ascent)
        @param isBold     should be true if the typeface is bold
        @param isItalic   should be true if the typeface is italic
        @param defaultCharacter   the character to be used as a replacement if there's
                          no glyph available for the character that's being drawn
    */
    void setCharacteristics (const String& fontFamily, float ascent,
                             bool isBold, bool isItalic,
                             juce_wchar defaultCharacter) noexcept;

    /** Sets the vital statistics for the typeface.
        @param fontFamily the typeface's font family
        @param fontStyle  the typeface's font style
        @param ascent     the ascent - this is normalised to a height of 1.0 and this is
                          the value that will be returned by Typeface::getAscent(). The
                          descent is assumed to be (1.0 - ascent)
        @param defaultCharacter  the character to be used as a replacement if there's
                          no glyph available for the character that's being drawn
    */
    void setCharacteristics (const String& fontFamily, const String& fontStyle,
                             float ascent, juce_wchar defaultCharacter) noexcept;

    /** Adds a glyph to the typeface.

        The path that is passed in is normalised so that the font height is 1.0, and its
        origin is the anchor point of the character on its baseline.

        The width is the nominal width of the character, and any extra kerning values that
        are specified will be added to this width.
    */
    void addGlyph (juce_wchar character, const Path& path, float width) noexcept;

    /** Specifies an extra kerning amount to be used between a pair of characters.
        The amount will be added to the nominal width of the first character when laying out a string.
    */
    void addKerningPair (juce_wchar char1, juce_wchar char2, float extraAmount) noexcept;

    /** Adds a range of glyphs from another typeface.
        This will attempt to pull in the paths and kerning information from another typeface and
        add it to this one.
    */
    void addGlyphsFromOtherTypeface (Typeface& typefaceToCopy, juce_wchar characterStartIndex, int numCharacters) noexcept;

    /** Saves this typeface as a Juce-formatted font file.
        A CustomTypeface can be created to reload the data that is written - see the CustomTypeface
        constructor.

        NOTE! Since this class was written, support was added for loading real font files from
        memory, so for most people, using Typeface::createSystemTypefaceFor() to load a real font
        is more appropriate than using this class to store it in a proprietary format.
    */
    bool writeToStream (OutputStream& outputStream);

    //==============================================================================
    // The following methods implement the basic Typeface behaviour.
    float getAscent() const override;
    float getDescent() const override;
    float getHeightToPointsFactor() const override;
    float getStringWidth (const String&) override;
    void getGlyphPositions (const String&, Array<int>& glyphs, Array<float>& xOffsets) override;
    bool getOutlineForGlyph (int glyphNumber, Path&) override;
    EdgeTable* getEdgeTableForGlyph (int glyphNumber, const AffineTransform&, float fontHeight) override;

protected:
    //==============================================================================
    juce_wchar defaultCharacter;
    float ascent;

    //==============================================================================
    /** If a subclass overrides this, it can load glyphs into the font on-demand.
        When methods such as getGlyphPositions() or getOutlineForGlyph() are asked for a
        particular character and there's no corresponding glyph, they'll call this
        method so that a subclass can try to add that glyph, returning true if it
        manages to do so.
    */
    virtual bool loadGlyphIfPossible (juce_wchar characterNeeded);

private:
    //==============================================================================
    class GlyphInfo;
    friend struct ContainerDeletePolicy<GlyphInfo>;
    OwnedArray<GlyphInfo> glyphs;
    short lookupTable [128];

    GlyphInfo* findGlyph (const juce_wchar character, bool loadIfNeeded) noexcept;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CustomTypeface)
};

} // namespace juce

repo_name: fedden/RenderMan

mime_type: text/x-c++


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

namespace FontValues
{
    static float limitFontHeight (const float height) noexcept
    {
        return jlimit (0.1f, 10000.0f, height);
    }

    const float defaultFontHeight = 14.0f;
    float minimumHorizontalScale = 0.7f;
    String fallbackFont;
    String fallbackFontStyle;
}

typedef Typeface::Ptr (*GetTypefaceForFont) (const Font&);
GetTypefaceForFont juce_getTypefaceForFont = nullptr;

float Font::getDefaultMinimumHorizontalScaleFactor() noexcept                { return FontValues::minimumHorizontalScale; }
void Font::setDefaultMinimumHorizontalScaleFactor (float newValue) noexcept  { FontValues::minimumHorizontalScale = newValue; }

//==============================================================================
class TypefaceCache  : private DeletedAtShutdown
{
public:
    TypefaceCache()
    {
        setSize (10);
    }

    ~TypefaceCache()
    {
        clearSingletonInstance();
    }

    juce_DeclareSingleton (TypefaceCache, false)

    void setSize (const int numToCache)
    {
        const ScopedWriteLock sl (lock);

        faces.clear();
        faces.insertMultiple (-1, CachedFace(), numToCache);
    }

    void clear()
    {
        const ScopedWriteLock sl (lock);

        setSize (faces.size());
        defaultFace = nullptr;
    }

    Typeface::Ptr findTypefaceFor (const Font& font)
    {
        const ScopedReadLock slr (lock);

        auto faceName = font.getTypefaceName();
        auto faceStyle = font.getTypefaceStyle();

        jassert (faceName.isNotEmpty());

        for (int i = faces.size(); --i >= 0;)
        {
            CachedFace& face = faces.getReference(i);

            if (face.typefaceName == faceName
                 && face.typefaceStyle == faceStyle
                 && face.typeface != nullptr
                 && face.typeface->isSuitableForFont (font))
            {
                face.lastUsageCount = ++counter;
                return face.typeface;
            }
        }

        const ScopedWriteLock slw (lock);
        int replaceIndex = 0;
        auto bestLastUsageCount = std::numeric_limits<size_t>::max();

        for (int i = faces.size(); --i >= 0;)
        {
            auto lu = faces.getReference(i).lastUsageCount;

            if (bestLastUsageCount > lu)
            {
                bestLastUsageCount = lu;
                replaceIndex = i;
            }
        }

        auto& face = faces.getReference (replaceIndex);
        face.typefaceName = faceName;
        face.typefaceStyle = faceStyle;
        face.lastUsageCount = ++counter;

        if (juce_getTypefaceForFont == nullptr)
            face.typeface = Font::getDefaultTypefaceForFont (font);
        else
            face.typeface = juce_getTypefaceForFont (font);

        jassert (face.typeface != nullptr); // the look and feel must return a typeface!

        if (defaultFace == nullptr && font == Font())
            defaultFace = face.typeface;

        return face.typeface;
    }

    Typeface::Ptr defaultFace;

private:
    struct CachedFace
    {
        CachedFace() noexcept {}

        // Although it seems a bit wacky to store the name here, it's because it may be a
        // placeholder rather than a real one, e.g. "<Sans-Serif>" vs the actual typeface name.
        // Since the typeface itself doesn't know that it may have this alias, the name under
        // which it was fetched needs to be stored separately.
        String typefaceName, typefaceStyle;
        size_t lastUsageCount = 0;
        Typeface::Ptr typeface;
    };

    ReadWriteLock lock;
    Array<CachedFace> faces;
    size_t counter = 0;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TypefaceCache)
};

juce_ImplementSingleton (TypefaceCache)

void Typeface::setTypefaceCacheSize (int numFontsToCache)
{
    TypefaceCache::getInstance()->setSize (numFontsToCache);
}

void (*clearOpenGLGlyphCache)() = nullptr;

void Typeface::clearTypefaceCache()
{
    TypefaceCache::getInstance()->clear();

    RenderingHelpers::SoftwareRendererSavedState::clearGlyphCache();

    if (clearOpenGLGlyphCache != nullptr)
        clearOpenGLGlyphCache();
}

//==============================================================================
class Font::SharedFontInternal  : public ReferenceCountedObject
{
public:
    SharedFontInternal() noexcept
        : typeface (TypefaceCache::getInstance()->defaultFace),
          typefaceName (Font::getDefaultSansSerifFontName()),
          typefaceStyle (Font::getDefaultStyle()),
          height (FontValues::defaultFontHeight)
    {
    }

    SharedFontInternal (int styleFlags, float fontHeight) noexcept
        : typefaceName (Font::getDefaultSansSerifFontName()),
          typefaceStyle (FontStyleHelpers::getStyleName (styleFlags)),
          height (fontHeight),
          underline ((styleFlags & underlined) != 0)
    {
        if (styleFlags == plain)
            typeface = TypefaceCache::getInstance()->defaultFace;
    }

    SharedFontInternal (const String& name, int styleFlags, float fontHeight) noexcept
        : typefaceName (name),
          typefaceStyle (FontStyleHelpers::getStyleName (styleFlags)),
          height (fontHeight),
          underline ((styleFlags & underlined) != 0)
    {
        if (styleFlags == plain && typefaceName.isEmpty())
            typeface = TypefaceCache::getInstance()->defaultFace;
    }

    SharedFontInternal (const String& name, const String& style, float fontHeight) noexcept
        : typefaceName (name), typefaceStyle (style), height (fontHeight)
    {
        if (typefaceName.isEmpty())
            typefaceName = Font::getDefaultSansSerifFontName();
    }

    explicit SharedFontInternal (const Typeface::Ptr& face) noexcept
        : typeface (face),
          typefaceName (face->getName()),
          typefaceStyle (face->getStyle()),
          height (FontValues::defaultFontHeight)
    {
        jassert (typefaceName.isNotEmpty());
    }

    SharedFontInternal (const SharedFontInternal& other) noexcept
        : ReferenceCountedObject(),
          typeface (other.typeface),
          typefaceName (other.typefaceName),
          typefaceStyle (other.typefaceStyle),
          height (other.height),
          horizontalScale (other.horizontalScale),
          kerning (other.kerning),
          ascent (other.ascent),
          underline (other.underline)
    {
    }

    bool operator== (const SharedFontInternal& other) const noexcept
    {
        return height == other.height
                && underline == other.underline
                && horizontalScale == other.horizontalScale
                && kerning == other.kerning
                && typefaceName == other.typefaceName
                && typefaceStyle == other.typefaceStyle;
    }

    Typeface::Ptr typeface;
    String typefaceName, typefaceStyle;
    float height, horizontalScale = 1.0f, kerning = 0, ascent = 0;
    bool underline = false;
};

//==============================================================================
Font::Font()                                : font (new SharedFontInternal()) {}
Font::Font (const Typeface::Ptr& typeface)  : font (new SharedFontInternal (typeface)) {}
Font::Font (const Font& other) noexcept     : font (other.font) {}

Font::Font (float fontHeight, int styleFlags)
    : font (new SharedFontInternal (styleFlags, FontValues::limitFontHeight (fontHeight)))
{
}

Font::Font (const String& typefaceName, float fontHeight, int styleFlags)
    : font (new SharedFontInternal (typefaceName, styleFlags, FontValues::limitFontHeight (fontHeight)))
{
}

Font::Font (const String& typefaceName, const String& typefaceStyle, float fontHeight)
    : font (new SharedFontInternal (typefaceName, typefaceStyle, FontValues::limitFontHeight (fontHeight)))
{
}

Font& Font::operator= (const Font& other) noexcept
{
    font = other.font;
    return *this;
}

Font::Font (Font&& other) noexcept
    : font (static_cast<ReferenceCountedObjectPtr<SharedFontInternal>&&> (other.font))
{
}

Font& Font::operator= (Font&& other) noexcept
{
    font = static_cast<ReferenceCountedObjectPtr<SharedFontInternal>&&> (other.font);
    return *this;
}

Font::~Font() noexcept
{
}

bool Font::operator== (const Font& other) const noexcept
{
    return font == other.font
            || *font == *other.font;
}

bool Font::operator!= (const Font& other) const noexcept
{
    return ! operator== (other);
}

void Font::dupeInternalIfShared()
{
    if (font->getReferenceCount() > 1)
        font = new SharedFontInternal (*font);
}

void Font::checkTypefaceSuitability()
{
    if (font->typeface != nullptr && ! font->typeface->isSuitableForFont (*this))
        font->typeface = nullptr;
}

//==============================================================================
struct FontPlaceholderNames
{
    String sans    { "<Sans-Serif>" },
           serif   { "<Serif>" },
           mono    { "<Monospaced>" },
           regular { "<Regular>" };
};

static const FontPlaceholderNames& getFontPlaceholderNames()
{
    static FontPlaceholderNames names;
    return names;
}

#if JUCE_MSVC
// This is a workaround for the lack of thread-safety in MSVC's handling of function-local
// statics - if multiple threads all try to create the first Font object at the same time,
// it can cause a race-condition in creating these placeholder strings.
struct FontNamePreloader { FontNamePreloader() { getFontPlaceholderNames(); } };
static FontNamePreloader fnp;
#endif

const String& Font::getDefaultSansSerifFontName()       { return getFontPlaceholderNames().sans; }
const String& Font::getDefaultSerifFontName()           { return getFontPlaceholderNames().serif; }
const String& Font::getDefaultMonospacedFontName()      { return getFontPlaceholderNames().mono; }
const String& Font::getDefaultStyle()                   { return getFontPlaceholderNames().regular; }

const String& Font::getTypefaceName() const noexcept    { return font->typefaceName; }
const String& Font::getTypefaceStyle() const noexcept   { return font->typefaceStyle; }

void Font::setTypefaceName (const String& faceName)
{
    if (faceName != font->typefaceName)
    {
        jassert (faceName.isNotEmpty());

        dupeInternalIfShared();
        font->typefaceName = faceName;
        font->typeface = nullptr;
        font->ascent = 0;
    }
}

void Font::setTypefaceStyle (const String& typefaceStyle)
{
    if (typefaceStyle != font->typefaceStyle)
    {
        dupeInternalIfShared();
        font->typefaceStyle = typefaceStyle;
        font->typeface = nullptr;
        font->ascent = 0;
    }
}

Font Font::withTypefaceStyle (const String& newStyle) const
{
    Font f (*this);
    f.setTypefaceStyle (newStyle);
    return f;
}

StringArray Font::getAvailableStyles() const
{
    return findAllTypefaceStyles (getTypeface()->getName());
}

Typeface* Font::getTypeface() const
{
    if (font->typeface == nullptr)
    {
        font->typeface = TypefaceCache::getInstance()->findTypefaceFor (*this);
        jassert (font->typeface != nullptr);
    }

    return font->typeface;
}

//==============================================================================
const String& Font::getFallbackFontName()
{
    return FontValues::fallbackFont;
}

void Font::setFallbackFontName (const String& name)
{
    FontValues::fallbackFont = name;

   #if JUCE_MAC || JUCE_IOS
    jassertfalse; // Note that use of a fallback font isn't currently implemented in OSX..
   #endif
}

const String& Font::getFallbackFontStyle()
{
    return FontValues::fallbackFontStyle;
}

void Font::setFallbackFontStyle (const String& style)
{
    FontValues::fallbackFontStyle = style;

   #if JUCE_MAC || JUCE_IOS
    jassertfalse; // Note that use of a fallback font isn't currently implemented in OSX..
   #endif
}

//==============================================================================
Font Font::withHeight (const float newHeight) const
{
    Font f (*this);
    f.setHeight (newHeight);
    return f;
}

float Font::getHeightToPointsFactor() const
{
    return getTypeface()->getHeightToPointsFactor();
}

Font Font::withPointHeight (float heightInPoints) const
{
    Font f (*this);
    f.setHeight (heightInPoints / getHeightToPointsFactor());
    return f;
}

void Font::setHeight (float newHeight)
{
    newHeight = FontValues::limitFontHeight (newHeight);

    if (font->height != newHeight)
    {
        dupeInternalIfShared();
        font->height = newHeight;
        checkTypefaceSuitability();
    }
}

void Font::setHeightWithoutChangingWidth (float newHeight)
{
    newHeight = FontValues::limitFontHeight (newHeight);

    if (font->height != newHeight)
    {
        dupeInternalIfShared();
        font->horizontalScale *= (font->height / newHeight);
        font->height = newHeight;
        checkTypefaceSuitability();
    }
}

int Font::getStyleFlags() const noexcept
{
    int styleFlags = font->underline ? underlined : plain;

    if (isBold())    styleFlags |= bold;
    if (isItalic())  styleFlags |= italic;

    return styleFlags;
}

Font Font::withStyle (const int newFlags) const
{
    Font f (*this);
    f.setStyleFlags (newFlags);
    return f;
}

void Font::setStyleFlags (const int newFlags)
{
    if (getStyleFlags() != newFlags)
    {
        dupeInternalIfShared();
        font->typeface = nullptr;
        font->typefaceStyle = FontStyleHelpers::getStyleName (newFlags);
        font->underline = (newFlags & underlined) != 0;
        font->ascent = 0;
    }
}

void Font::setSizeAndStyle (float newHeight,
                            const int newStyleFlags,
                            const float newHorizontalScale,
                            const float newKerningAmount)
{
    newHeight = FontValues::limitFontHeight (newHeight);

    if (font->height != newHeight
         || font->horizontalScale != newHorizontalScale
         || font->kerning != newKerningAmount)
    {
        dupeInternalIfShared();
        font->height = newHeight;
        font->horizontalScale = newHorizontalScale;
        font->kerning = newKerningAmount;
        checkTypefaceSuitability();
    }

    setStyleFlags (newStyleFlags);
}

void Font::setSizeAndStyle (float newHeight,
                            const String& newStyle,
                            const float newHorizontalScale,
                            const float newKerningAmount)
{
    newHeight = FontValues::limitFontHeight (newHeight);

    if (font->height != newHeight
         || font->horizontalScale != newHorizontalScale
         || font->kerning != newKerningAmount)
    {
        dupeInternalIfShared();
        font->height = newHeight;
        font->horizontalScale = newHorizontalScale;
        font->kerning = newKerningAmount;
        checkTypefaceSuitability();
    }

    setTypefaceStyle (newStyle);
}

Font Font::withHorizontalScale (const float newHorizontalScale) const
{
    Font f (*this);
    f.setHorizontalScale (newHorizontalScale);
    return f;
}

void Font::setHorizontalScale (const float scaleFactor)
{
    dupeInternalIfShared();
    font->horizontalScale = scaleFactor;
    checkTypefaceSuitability();
}

float Font::getHorizontalScale() const noexcept
{
    return font->horizontalScale;
}

float Font::getExtraKerningFactor() const noexcept
{
    return font->kerning;
}

Font Font::withExtraKerningFactor (const float extraKerning) const
{
    Font f (*this);
    f.setExtraKerningFactor (extraKerning);
    return f;
}

void Font::setExtraKerningFactor (const float extraKerning)
{
    dupeInternalIfShared();
    font->kerning = extraKerning;
    checkTypefaceSuitability();
}

Font Font::boldened() const                 { return withStyle (getStyleFlags() | bold); }
Font Font::italicised() const               { return withStyle (getStyleFlags() | italic); }

bool Font::isBold() const noexcept          { return FontStyleHelpers::isBold   (font->typefaceStyle); }
bool Font::isItalic() const noexcept        { return FontStyleHelpers::isItalic (font->typefaceStyle); }
bool Font::isUnderlined() const noexcept    { return font->underline; }

void Font::setBold (const bool shouldBeBold)
{
    auto flags = getStyleFlags();
    setStyleFlags (shouldBeBold ? (flags | bold)
                                : (flags & ~bold));
}

void Font::setItalic (const bool shouldBeItalic)
{
    auto flags = getStyleFlags();
    setStyleFlags (shouldBeItalic ? (flags | italic)
                                  : (flags & ~italic));
}

void Font::setUnderline (const bool shouldBeUnderlined)
{
    dupeInternalIfShared();
    font->underline = shouldBeUnderlined;
    checkTypefaceSuitability();
}

float Font::getAscent() const
{
    if (font->ascent == 0.0f)
        font->ascent = getTypeface()->getAscent();

    return font->height * font->ascent;
}

float Font::getHeight() const noexcept      { return font->height; }
float Font::getDescent() const              { return font->height - getAscent(); }

float Font::getHeightInPoints() const       { return getHeight()  * getHeightToPointsFactor(); }
float Font::getAscentInPoints() const       { return getAscent()  * getHeightToPointsFactor(); }
float Font::getDescentInPoints() const      { return getDescent() * getHeightToPointsFactor(); }

int Font::getStringWidth (const String& text) const
{
    return (int) std::ceil (getStringWidthFloat (text));
}

float Font::getStringWidthFloat (const String& text) const
{
    // This call isn't thread-safe when there's a message thread running
    jassert (MessageManager::getInstanceWithoutCreating() == nullptr
               || MessageManager::getInstanceWithoutCreating()->currentThreadHasLockedMessageManager());

    auto w = getTypeface()->getStringWidth (text);

    if (font->kerning != 0.0f)
        w += font->kerning * text.length();

    return w * font->height * font->horizontalScale;
}

void Font::getGlyphPositions (const String& text, Array<int>& glyphs, Array<float>& xOffsets) const
{
    // This call isn't thread-safe when there's a message thread running
    jassert (MessageManager::getInstanceWithoutCreating() == nullptr
               || MessageManager::getInstanceWithoutCreating()->currentThreadHasLockedMessageManager());

    getTypeface()->getGlyphPositions (text, glyphs, xOffsets);

    if (auto num = xOffsets.size())
    {
        auto scale = font->height * font->horizontalScale;
        auto* x = xOffsets.getRawDataPointer();

        if (font->kerning != 0.0f)
        {
            for (int i = 0; i < num; ++i)
                x[i] = (x[i] + i * font->kerning) * scale;
        }
        else
        {
            for (int i = 0; i < num; ++i)
                x[i] *= scale;
        }
    }
}

void Font::findFonts (Array<Font>& destArray)
{
    for (auto& name : findAllTypefaceNames())
    {
        auto styles = findAllTypefaceStyles (name);

        String style ("Regular");

        if (! styles.contains (style, true))
            style = styles[0];

        destArray.add (Font (name, style, FontValues::defaultFontHeight));
    }
}

//==============================================================================
String Font::toString() const
{
    String s;

    if (getTypefaceName() != getDefaultSansSerifFontName())
        s << getTypefaceName() << "; ";

    s << String (getHeight(), 1);

    if (getTypefaceStyle() != getDefaultStyle())
        s << ' ' << getTypefaceStyle();

    return s;
}

Font Font::fromString (const String& fontDescription)
{
    const int separator = fontDescription.indexOfChar (';');
    String name;

    if (separator > 0)
        name = fontDescription.substring (0, separator).trim();

    if (name.isEmpty())
        name = getDefaultSansSerifFontName();

    String sizeAndStyle (fontDescription.substring (separator + 1).trimStart());

    float height = sizeAndStyle.getFloatValue();
    if (height <= 0)
        height = 10.0f;

    const String style (sizeAndStyle.fromFirstOccurrenceOf (" ", false, false));

    return Font (name, style, height);
}

} // namespace juce


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

namespace
{
    int getLength (const Array<AttributedString::Attribute>& atts) noexcept
    {
        return atts.size() != 0 ? atts.getReference (atts.size() - 1).range.getEnd() : 0;
    }

    void splitAttributeRanges (Array<AttributedString::Attribute>& atts, int position)
    {
        for (int i = atts.size(); --i >= 0;)
        {
            const AttributedString::Attribute& att = atts.getReference (i);
            const int offset = position - att.range.getStart();

            if (offset >= 0)
            {
                if (offset > 0 && position < att.range.getEnd())
                {
                    atts.insert (i + 1, att);
                    atts.getReference (i).range.setEnd (position);
                    atts.getReference (i + 1).range.setStart (position);
                }

                break;
            }
        }
    }

    Range<int> splitAttributeRanges (Array<AttributedString::Attribute>& atts, Range<int> newRange)
    {
        newRange = newRange.getIntersectionWith (Range<int> (0, getLength (atts)));

        if (! newRange.isEmpty())
        {
            splitAttributeRanges (atts, newRange.getStart());
            splitAttributeRanges (atts, newRange.getEnd());
        }

        return newRange;
    }

    void mergeAdjacentRanges (Array<AttributedString::Attribute>& atts)
    {
        for (int i = atts.size() - 1; --i >= 0;)
        {
            AttributedString::Attribute& a1 = atts.getReference (i);
            AttributedString::Attribute& a2 = atts.getReference (i + 1);

            if (a1.colour == a2.colour && a1.font == a2.font)
            {
                a1.range.setEnd (a2.range.getEnd());
                atts.remove (i + 1);

                if (i < atts.size() - 1)
                    ++i;
            }
        }
    }

    void appendRange (Array<AttributedString::Attribute>& atts,
                      int length, const Font* f, const Colour* c)
    {
        if (atts.size() == 0)
        {
            atts.add (AttributedString::Attribute (Range<int> (0, length),
                                                   f != nullptr ? *f : Font(),
                                                   c != nullptr ? *c : Colour (0xff000000)));
        }
        else
        {
            const int start = getLength (atts);
            atts.add (AttributedString::Attribute (Range<int> (start, start + length),
                                                   f != nullptr ? *f : atts.getReference (atts.size() - 1).font,
                                                   c != nullptr ? *c : atts.getReference (atts.size() - 1).colour));
            mergeAdjacentRanges (atts);
        }
    }

    void applyFontAndColour (Array<AttributedString::Attribute>& atts,
                             Range<int> range, const Font* f, const Colour* c)
    {
        range = splitAttributeRanges (atts, range);

        for (int i = 0; i < atts.size(); ++i)
        {
            AttributedString::Attribute& att = atts.getReference (i);

            if (range.getStart() < att.range.getEnd())
            {
                if (range.getEnd() <= att.range.getStart())
                    break;

                if (c != nullptr) att.colour = *c;
                if (f != nullptr) att.font = *f;
            }
        }

        mergeAdjacentRanges (atts);
    }

    void truncate (Array<AttributedString::Attribute>& atts, int newLength)
    {
        splitAttributeRanges (atts, newLength);

        for (int i = atts.size(); --i >= 0;)
            if (atts.getReference (i).range.getStart() >= newLength)
                atts.remove (i);
    }
}

//==============================================================================
AttributedString::Attribute::Attribute() noexcept : colour (0xff000000) {}
AttributedString::Attribute::~Attribute() noexcept {}

AttributedString::Attribute::Attribute (Attribute&& other) noexcept
    : range (other.range),
      font (static_cast<Font&&> (other.font)),
      colour (other.colour)
{
}

AttributedString::Attribute& AttributedString::Attribute::operator= (Attribute&& other) noexcept
{
    range = other.range;
    font = static_cast<Font&&> (other.font);
    colour = other.colour;
    return *this;
}

AttributedString::Attribute::Attribute (const Attribute& other) noexcept
    : range (other.range),
      font (other.font),
      colour (other.colour)
{
}

AttributedString::Attribute& AttributedString::Attribute::operator= (const Attribute& other) noexcept
{
    range = other.range;
    font = other.font;
    colour = other.colour;
    return *this;
}

AttributedString::Attribute::Attribute (Range<int> r, const Font& f, Colour c) noexcept
    : range (r), font (f), colour (c)
{
}

//==============================================================================
AttributedString::AttributedString()
    : lineSpacing (0.0f),
      justification (Justification::left),
      wordWrap (AttributedString::byWord),
      readingDirection (AttributedString::natural)
{
}

AttributedString::AttributedString (const String& newString)
    : lineSpacing (0.0f),
      justification (Justification::left),
      wordWrap (AttributedString::byWord),
      readingDirection (AttributedString::natural)
{
    setText (newString);
}

AttributedString::AttributedString (const AttributedString& other)
    : text (other.text),
      lineSpacing (other.lineSpacing),
      justification (other.justification),
      wordWrap (other.wordWrap),
      readingDirection (other.readingDirection),
      attributes (other.attributes)
{
}

AttributedString& AttributedString::operator= (const AttributedString& other)
{
    if (this != &other)
    {
        text = other.text;
        lineSpacing = other.lineSpacing;
        justification = other.justification;
        wordWrap = other.wordWrap;
        readingDirection = other.readingDirection;
        attributes = other.attributes;
    }

    return *this;
}

AttributedString::AttributedString (AttributedString&& other) noexcept
    : text (static_cast<String&&> (other.text)),
      lineSpacing (other.lineSpacing),
      justification (other.justification),
      wordWrap (other.wordWrap),
      readingDirection (other.readingDirection),
      attributes (static_cast<Array<Attribute>&&> (other.attributes))
{
}

AttributedString& AttributedString::operator= (AttributedString&& other) noexcept
{
    text = static_cast<String&&> (other.text);
    lineSpacing = other.lineSpacing;
    justification = other.justification;
    wordWrap = other.wordWrap;
    readingDirection = other.readingDirection;
    attributes = static_cast<Array<Attribute>&&> (other.attributes);
    return *this;
}

AttributedString::~AttributedString() noexcept {}

void AttributedString::setText (const String& newText)
{
    const int newLength = newText.length();
    const int oldLength = getLength (attributes);

    if (newLength > oldLength)
        appendRange (attributes, newLength - oldLength, nullptr, nullptr);
    else if (newLength < oldLength)
        truncate (attributes, newLength);

    text = newText;
}

void AttributedString::append (const String& textToAppend)
{
    text += textToAppend;
    appendRange (attributes, textToAppend.length(), nullptr, nullptr);
}

void AttributedString::append (const String& textToAppend, const Font& font)
{
    text += textToAppend;
    appendRange (attributes, textToAppend.length(), &font, nullptr);
}

void AttributedString::append (const String& textToAppend, Colour colour)
{
    text += textToAppend;
    appendRange (attributes, textToAppend.length(), nullptr, &colour);
}

void AttributedString::append (const String& textToAppend, const Font& font, Colour colour)
{
    text += textToAppend;
    appendRange (attributes, textToAppend.length(), &font, &colour);
}

void AttributedString::append (const AttributedString& other)
{
    const int originalLength = getLength (attributes);
    const int originalNumAtts = attributes.size();
    text += other.text;
    attributes.addArray (other.attributes);

    for (int i = originalNumAtts; i < attributes.size(); ++i)
        attributes.getReference (i).range += originalLength;

    mergeAdjacentRanges (attributes);
}

void AttributedString::clear()
{
    text.clear();
    attributes.clear();
}

void AttributedString::setJustification (Justification newJustification) noexcept
{
    justification = newJustification;
}

void AttributedString::setWordWrap (WordWrap newWordWrap) noexcept
{
    wordWrap = newWordWrap;
}

void AttributedString::setReadingDirection (ReadingDirection newReadingDirection) noexcept
{
    readingDirection = newReadingDirection;
}

void AttributedString::setLineSpacing (const float newLineSpacing) noexcept
{
    lineSpacing = newLineSpacing;
}

void AttributedString::setColour (Range<int> range, Colour colour)
{
    applyFontAndColour (attributes, range, nullptr, &colour);
}

void AttributedString::setFont (Range<int> range, const Font& font)
{
    applyFontAndColour (attributes, range, &font, nullptr);
}

void AttributedString::setColour (Colour colour)
{
    setColour (Range<int> (0, getLength (attributes)), colour);
}

void AttributedString::setFont (const Font& font)
{
    setFont (Range<int> (0, getLength (attributes)), font);
}

void AttributedString::draw (Graphics& g, const Rectangle<float>& area) const
{
    if (text.isNotEmpty() && g.clipRegionIntersects (area.getSmallestIntegerContainer()))
    {
        jassert (text.length() == getLength (attributes));

        if (! g.getInternalContext().drawTextLayout (*this, area))
        {
            TextLayout layout;
            layout.createLayout (*this, area.getWidth());
            layout.draw (g, area);
        }
    }
}

} // namespace juce


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

class CustomTypeface::GlyphInfo
{
public:
    GlyphInfo (juce_wchar c, const Path& p, float w) noexcept
        : character (c), path (p), width (w)
    {
    }

    struct KerningPair
    {
        juce_wchar character2;
        float kerningAmount;
    };

    void addKerningPair (juce_wchar subsequentCharacter, float extraKerningAmount) noexcept
    {
        kerningPairs.add ({ subsequentCharacter, extraKerningAmount });
    }

    float getHorizontalSpacing (juce_wchar subsequentCharacter) const noexcept
    {
        if (subsequentCharacter != 0)
            for (auto& kp : kerningPairs)
                if (kp.character2 == subsequentCharacter)
                    return width + kp.kerningAmount;

        return width;
    }

    const juce_wchar character;
    const Path path;
    float width;
    Array<KerningPair> kerningPairs;

private:
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GlyphInfo)
};

//==============================================================================
namespace CustomTypefaceHelpers
{
    static juce_wchar readChar (InputStream& in)
    {
        auto n = (uint32) (uint16) in.readShort();

        if (n >= 0xd800 && n <= 0xdfff)
        {
            auto nextWord = (uint32) (uint16) in.readShort();
            jassert (nextWord >= 0xdc00); // illegal unicode character!

            n = 0x10000 + (((n - 0xd800) << 10) | (nextWord - 0xdc00));
        }

        return (juce_wchar) n;
    }

    static void writeChar (OutputStream& out, juce_wchar charToWrite)
    {
        if (charToWrite >= 0x10000)
        {
            charToWrite -= 0x10000;
            out.writeShort ((short) (uint16) (0xd800 + (charToWrite >> 10)));
            out.writeShort ((short) (uint16) (0xdc00 + (charToWrite & 0x3ff)));
        }
        else
        {
            out.writeShort ((short) (uint16) charToWrite);
        }
    }
}

//==============================================================================
CustomTypeface::CustomTypeface()
    : Typeface (String(), String())
{
    clear();
}

CustomTypeface::CustomTypeface (InputStream& serialisedTypefaceStream)
    : Typeface (String(), String())
{
    clear();

    GZIPDecompressorInputStream gzin (serialisedTypefaceStream);
    BufferedInputStream in (gzin, 32768);

    name = in.readString();

    const bool isBold   = in.readBool();
    const bool isItalic = in.readBool();
    style = FontStyleHelpers::getStyleName (isBold, isItalic);

    ascent = in.readFloat();
    defaultCharacter = CustomTypefaceHelpers::readChar (in);

    auto numChars = in.readInt();

    for (int i = 0; i < numChars; ++i)
    {
        auto c = CustomTypefaceHelpers::readChar (in);
        auto width = in.readFloat();

        Path p;
        p.loadPathFromStream (in);
        addGlyph (c, p, width);
    }

    auto numKerningPairs = in.readInt();

    for (int i = 0; i < numKerningPairs; ++i)
    {
        auto char1 = CustomTypefaceHelpers::readChar (in);
        auto char2 = CustomTypefaceHelpers::readChar (in);

        addKerningPair (char1, char2, in.readFloat());
    }
}

CustomTypeface::~CustomTypeface()
{
}

//==============================================================================
void CustomTypeface::clear()
{
    defaultCharacter = 0;
    ascent = 1.0f;
    style = "Regular";
    zeromem (lookupTable, sizeof (lookupTable));
    glyphs.clear();
}

void CustomTypeface::setCharacteristics (const String& newName, float newAscent, bool isBold,
                                         bool isItalic, juce_wchar newDefaultCharacter) noexcept
{
    name = newName;
    defaultCharacter = newDefaultCharacter;
    ascent = newAscent;
    style = FontStyleHelpers::getStyleName (isBold, isItalic);
}

void CustomTypeface::setCharacteristics (const String& newName, const String& newStyle,
                                         float newAscent, juce_wchar newDefaultCharacter) noexcept
{
    name = newName;
    style = newStyle;
    defaultCharacter = newDefaultCharacter;
    ascent = newAscent;
}

void CustomTypeface::addGlyph (juce_wchar character, const Path& path, float width) noexcept
{
    // Check that you're not trying to add the same character twice..
    jassert (findGlyph (character, false) == nullptr);

    if (isPositiveAndBelow ((int) character, numElementsInArray (lookupTable)))
        lookupTable [character] = (short) glyphs.size();

    glyphs.add (new GlyphInfo (character, path, width));
}

void CustomTypeface::addKerningPair (juce_wchar char1, juce_wchar char2, float extraAmount) noexcept
{
    if (extraAmount != 0.0f)
    {
        if (auto* g = findGlyph (char1, true))
            g->addKerningPair (char2, extraAmount);
        else
            jassertfalse; // can only add kerning pairs for characters that exist!
    }
}

CustomTypeface::GlyphInfo* CustomTypeface::findGlyph (juce_wchar character, bool loadIfNeeded) noexcept
{
    if (isPositiveAndBelow ((int) character, numElementsInArray (lookupTable)) && lookupTable [character] > 0)
        return glyphs [(int) lookupTable [(int) character]];

    for (auto* g : glyphs)
        if (g->character == character)
            return g;

    if (loadIfNeeded && loadGlyphIfPossible (character))
        return findGlyph (character, false);

    return nullptr;
}

bool CustomTypeface::loadGlyphIfPossible (juce_wchar)
{
    return false;
}

void CustomTypeface::addGlyphsFromOtherTypeface (Typeface& typefaceToCopy, juce_wchar characterStartIndex, int numCharacters) noexcept
{
    setCharacteristics (name, style, typefaceToCopy.getAscent(), defaultCharacter);

    for (int i = 0; i < numCharacters; ++i)
    {
        auto c = (juce_wchar) (characterStartIndex + static_cast<juce_wchar> (i));

        Array<int> glyphIndexes;
        Array<float> offsets;
        typefaceToCopy.getGlyphPositions (String::charToString (c), glyphIndexes, offsets);

        const int glyphIndex = glyphIndexes.getFirst();

        if (glyphIndex >= 0 && glyphIndexes.size() > 0)
        {
            auto glyphWidth = offsets[1];

            Path p;
            typefaceToCopy.getOutlineForGlyph (glyphIndex, p);

            addGlyph (c, p, glyphWidth);

            for (int j = glyphs.size() - 1; --j >= 0;)
            {
                auto char2 = glyphs.getUnchecked (j)->character;
                glyphIndexes.clearQuick();
                offsets.clearQuick();
                typefaceToCopy.getGlyphPositions (String::charToString (c) + String::charToString (char2), glyphIndexes, offsets);

                if (offsets.size() > 1)
                    addKerningPair (c, char2, offsets[1] - glyphWidth);
            }
        }
    }
}

bool CustomTypeface::writeToStream (OutputStream& outputStream)
{
    GZIPCompressorOutputStream out (&outputStream);

    out.writeString (name);
    out.writeBool (FontStyleHelpers::isBold (style));
    out.writeBool (FontStyleHelpers::isItalic (style));
    out.writeFloat (ascent);
    CustomTypefaceHelpers::writeChar (out, defaultCharacter);
    out.writeInt (glyphs.size());

    int numKerningPairs = 0;

    for (auto* g : glyphs)
    {
        CustomTypefaceHelpers::writeChar (out, g->character);
        out.writeFloat (g->width);
        g->path.writePathToStream (out);

        numKerningPairs += g->kerningPairs.size();
    }

    out.writeInt (numKerningPairs);

    for (auto* g : glyphs)
    {
        for (auto& p : g->kerningPairs)
        {
            CustomTypefaceHelpers::writeChar (out, g->character);
            CustomTypefaceHelpers::writeChar (out, p.character2);
            out.writeFloat (p.kerningAmount);
        }
    }

    return true;
}

//==============================================================================
float CustomTypeface::getAscent() const                 { return ascent; }
float CustomTypeface::getDescent() const                { return 1.0f - ascent; }
float CustomTypeface::getHeightToPointsFactor() const   { return ascent; }

float CustomTypeface::getStringWidth (const String& text)
{
    float x = 0;

    for (auto t = text.getCharPointer(); ! t.isEmpty();)
    {
        auto c = t.getAndAdvance();

        if (auto* glyph = findGlyph (c, true))
        {
            x += glyph->getHorizontalSpacing (*t);
        }
        else
        {
            if (auto fallbackTypeface = Typeface::getFallbackTypeface())
                if (fallbackTypeface != this)
                    x += fallbackTypeface->getStringWidth (String::charToString (c));
        }
    }

    return x;
}

void CustomTypeface::getGlyphPositions (const String& text, Array<int>& resultGlyphs, Array<float>& xOffsets)
{
    xOffsets.add (0);
    float x = 0;

    for (auto t = text.getCharPointer(); ! t.isEmpty();)
    {
        float width = 0.0f;
        int glyphChar = 0;

        auto c = t.getAndAdvance();

        if (auto* glyph = findGlyph (c, true))
        {
            width = glyph->getHorizontalSpacing (*t);
            glyphChar = (int) glyph->character;
        }
        else
        {
            auto fallbackTypeface = getFallbackTypeface();

            if (fallbackTypeface != nullptr && fallbackTypeface != this)
            {
                Array<int> subGlyphs;
                Array<float> subOffsets;
                fallbackTypeface->getGlyphPositions (String::charToString (c), subGlyphs, subOffsets);

                if (subGlyphs.size() > 0)
                {
                    glyphChar = subGlyphs.getFirst();
                    width = subOffsets[1];
                }
            }
        }

        x += width;
        resultGlyphs.add (glyphChar);
        xOffsets.add (x);
    }
}

bool CustomTypeface::getOutlineForGlyph (int glyphNumber, Path& path)
{
    if (auto* glyph = findGlyph ((juce_wchar) glyphNumber, true))
    {
        path = glyph->path;
        return true;
    }

    if (auto fallbackTypeface = getFallbackTypeface())
        if (fallbackTypeface != this)
            return fallbackTypeface->getOutlineForGlyph (glyphNumber, path);

    return false;
}

EdgeTable* CustomTypeface::getEdgeTableForGlyph (int glyphNumber, const AffineTransform& transform, float fontHeight)
{
    if (auto* glyph = findGlyph ((juce_wchar) glyphNumber, true))
    {
        if (! glyph->path.isEmpty())
            return new EdgeTable (glyph->path.getBoundsTransformed (transform)
                                             .getSmallestIntegerContainer().expanded (1, 0),
                                  glyph->path, transform);
    }
    else
    {
        if (auto fallbackTypeface = getFallbackTypeface())
            if (fallbackTypeface != this)
                return fallbackTypeface->getEdgeTableForGlyph (glyphNumber, transform, fontHeight);
    }

    return nullptr;
}

} // namespace juce


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

PositionedGlyph::PositionedGlyph() noexcept
    : character (0), glyph (0), x (0), y (0), w (0), whitespace (false)
{
}

PositionedGlyph::PositionedGlyph (const Font& font_, juce_wchar character_, int glyphNumber,
                                  float anchorX, float baselineY, float width, bool whitespace_)
    : font (font_), character (character_), glyph (glyphNumber),
      x (anchorX), y (baselineY), w (width), whitespace (whitespace_)
{
}

PositionedGlyph::PositionedGlyph (PositionedGlyph&& other) noexcept
    : font (static_cast<Font&&> (other.font)),
      character (other.character), glyph (other.glyph),
      x (other.x), y (other.y), w (other.w), whitespace (other.whitespace)
{
}

PositionedGlyph& PositionedGlyph::operator= (PositionedGlyph&& other) noexcept
{
    font = static_cast<Font&&> (other.font);
    character = other.character;
    glyph = other.glyph;
    x = other.x;
    y = other.y;
    w = other.w;
    whitespace = other.whitespace;

    return *this;
}

PositionedGlyph::~PositionedGlyph() {}

static inline void drawGlyphWithFont (Graphics& g, int glyph, const Font& font, AffineTransform t)
{
    auto& context = g.getInternalContext();
    context.setFont (font);
    context.drawGlyph (glyph, t);
}

void PositionedGlyph::draw (Graphics& g) const
{
    if (! isWhitespace())
        drawGlyphWithFont (g, glyph, font, AffineTransform::translation (x, y));
}

void PositionedGlyph::draw (Graphics& g, AffineTransform transform) const
{
    if (! isWhitespace())
        drawGlyphWithFont (g, glyph, font, AffineTransform::translation (x, y).followedBy (transform));
}

void PositionedGlyph::createPath (Path& path) const
{
    if (! isWhitespace())
    {
        if (auto* t = font.getTypeface())
        {
            Path p;
            t->getOutlineForGlyph (glyph, p);

            path.addPath (p, AffineTransform::scale (font.getHeight() * font.getHorizontalScale(), font.getHeight())
                                             .translated (x, y));
        }
    }
}

bool PositionedGlyph::hitTest (float px, float py) const
{
    if (getBounds().contains (px, py) && ! isWhitespace())
    {
        if (auto* t = font.getTypeface())
        {
            Path p;
            t->getOutlineForGlyph (glyph, p);

            AffineTransform::translation (-x, -y)
                            .scaled (1.0f / (font.getHeight() * font.getHorizontalScale()), 1.0f / font.getHeight())
                            .transformPoint (px, py);

            return p.contains (px, py);
        }
    }

    return false;
}

void PositionedGlyph::moveBy (float deltaX, float deltaY)
{
    x += deltaX;
    y += deltaY;
}

//==============================================================================
GlyphArrangement::GlyphArrangement()
{
    glyphs.ensureStorageAllocated (128);
}

GlyphArrangement::GlyphArrangement (GlyphArrangement&& other)
    : glyphs (static_cast<Array<PositionedGlyph>&&> (other.glyphs))
{
}

GlyphArrangement& GlyphArrangement::operator= (GlyphArrangement&& other)
{
    glyphs = static_cast<Array<PositionedGlyph>&&> (other.glyphs);

    return *this;
}

GlyphArrangement::~GlyphArrangement() {}

//==============================================================================
void GlyphArrangement::clear()
{
    glyphs.clear();
}

PositionedGlyph& GlyphArrangement::getGlyph (int index) const noexcept
{
    return glyphs.getReference (index);
}

//==============================================================================
void GlyphArrangement::addGlyphArrangement (const GlyphArrangement& other)
{
    glyphs.addArray (other.glyphs);
}

void GlyphArrangement::addGlyph (const PositionedGlyph& glyph)
{
    glyphs.add (glyph);
}

void GlyphArrangement::removeRangeOfGlyphs (int startIndex, int num)
{
    glyphs.removeRange (startIndex, num < 0 ? glyphs.size() : num);
}

//==============================================================================
void GlyphArrangement::addLineOfText (const Font& font, const String& text, float xOffset, float yOffset)
{
    addCurtailedLineOfText (font, text, xOffset, yOffset, 1.0e10f, false);
}

void GlyphArrangement::addCurtailedLineOfText (const Font& font, const String& text,
                                               float xOffset, float yOffset,
                                               float maxWidthPixels, bool useEllipsis)
{
    if (text.isNotEmpty())
    {
        Array<int> newGlyphs;
        Array<float> xOffsets;
        font.getGlyphPositions (text, newGlyphs, xOffsets);
        auto textLen = newGlyphs.size();
        glyphs.ensureStorageAllocated (glyphs.size() + textLen);

        auto t = text.getCharPointer();

        for (int i = 0; i < textLen; ++i)
        {
            auto nextX = xOffsets.getUnchecked (i + 1);

            if (nextX > maxWidthPixels + 1.0f)
            {
                // curtail the string if it's too wide..
                if (useEllipsis && textLen > 3 && glyphs.size() >= 3)
                    insertEllipsis (font, xOffset + maxWidthPixels, 0, glyphs.size());

                break;
            }

            auto thisX = xOffsets.getUnchecked (i);
            bool isWhitespace = t.isWhitespace();

            glyphs.add (PositionedGlyph (font, t.getAndAdvance(),
                                         newGlyphs.getUnchecked(i),
                                         xOffset + thisX, yOffset,
                                         nextX - thisX, isWhitespace));
        }
    }
}

int GlyphArrangement::insertEllipsis (const Font& font, float maxXPos, int startIndex, int endIndex)
{
    int numDeleted = 0;

    if (! glyphs.isEmpty())
    {
        Array<int> dotGlyphs;
        Array<float> dotXs;
        font.getGlyphPositions ("..", dotGlyphs, dotXs);

        auto dx = dotXs[1];
        float xOffset = 0.0f, yOffset = 0.0f;

        while (endIndex > startIndex)
        {
            auto& pg = glyphs.getReference (--endIndex);
            xOffset = pg.x;
            yOffset = pg.y;

            glyphs.remove (endIndex);
            ++numDeleted;

            if (xOffset + dx * 3 <= maxXPos)
                break;
        }

        for (int i = 3; --i >= 0;)
        {
            glyphs.insert (endIndex++, PositionedGlyph (font, '.', dotGlyphs.getFirst(),
                                                        xOffset, yOffset, dx, false));
            --numDeleted;
            xOffset += dx;

            if (xOffset > maxXPos)
                break;
        }
    }

    return numDeleted;
}

void GlyphArrangement::addJustifiedText (const Font& font, const String& text,
                                         float x, float y, float maxLineWidth,
                                         Justification horizontalLayout)
{
    auto lineStartIndex = glyphs.size();
    addLineOfText (font, text, x, y);

    auto originalY = y;

    while (lineStartIndex < glyphs.size())
    {
        int i = lineStartIndex;

        if (glyphs.getReference(i).getCharacter() != '\n'
              && glyphs.getReference(i).getCharacter() != '\r')
            ++i;

        auto lineMaxX = glyphs.getReference (lineStartIndex).getLeft() + maxLineWidth;
        int lastWordBreakIndex = -1;

        while (i < glyphs.size())
        {
            auto& pg = glyphs.getReference (i);
            auto c = pg.getCharacter();

            if (c == '\r' || c == '\n')
            {
                ++i;

                if (c == '\r' && i < glyphs.size()
                     && glyphs.getReference(i).getCharacter() == '\n')
                    ++i;

                break;
            }

            if (pg.isWhitespace())
            {
                lastWordBreakIndex = i + 1;
            }
            else if (pg.getRight() - 0.0001f >= lineMaxX)
            {
                if (lastWordBreakIndex >= 0)
                    i = lastWordBreakIndex;

                break;
            }

            ++i;
        }

        auto currentLineStartX = glyphs.getReference (lineStartIndex).getLeft();
        auto currentLineEndX = currentLineStartX;

        for (int j = i; --j >= lineStartIndex;)
        {
            if (! glyphs.getReference (j).isWhitespace())
            {
                currentLineEndX = glyphs.getReference (j).getRight();
                break;
            }
        }

        float deltaX = 0.0f;

        if (horizontalLayout.testFlags (Justification::horizontallyJustified))
            spreadOutLine (lineStartIndex, i - lineStartIndex, maxLineWidth);
        else if (horizontalLayout.testFlags (Justification::horizontallyCentred))
            deltaX = (maxLineWidth - (currentLineEndX - currentLineStartX)) * 0.5f;
        else if (horizontalLayout.testFlags (Justification::right))
            deltaX = maxLineWidth - (currentLineEndX - currentLineStartX);

        moveRangeOfGlyphs (lineStartIndex, i - lineStartIndex,
                           x + deltaX - currentLineStartX, y - originalY);

        lineStartIndex = i;

        y += font.getHeight();
    }
}

void GlyphArrangement::addFittedText (const Font& f, const String& text,
                                      float x, float y, float width, float height,
                                      Justification layout, int maximumLines,
                                      float minimumHorizontalScale)
{
    if (minimumHorizontalScale == 0.0f)
        minimumHorizontalScale = Font::getDefaultMinimumHorizontalScaleFactor();

    // doesn't make much sense if this is outside a sensible range of 0.5 to 1.0
    jassert (minimumHorizontalScale > 0 && minimumHorizontalScale <= 1.0f);

    if (text.containsAnyOf ("\r\n"))
    {
        addLinesWithLineBreaks (text, f, x, y, width, height, layout);
    }
    else
    {
        auto startIndex = glyphs.size();
        auto trimmed = text.trim();
        addLineOfText (f, trimmed, x, y);
        auto numGlyphs = glyphs.size() - startIndex;

        if (numGlyphs > 0)
        {
            auto lineWidth = glyphs.getReference (glyphs.size() - 1).getRight()
                                - glyphs.getReference (startIndex).getLeft();

            if (lineWidth > 0)
            {
                if (lineWidth * minimumHorizontalScale < width)
                {
                    if (lineWidth > width)
                        stretchRangeOfGlyphs (startIndex, numGlyphs, width / lineWidth);

                    justifyGlyphs (startIndex, numGlyphs, x, y, width, height, layout);
                }
                else if (maximumLines <= 1)
                {
                    fitLineIntoSpace (startIndex, numGlyphs, x, y, width, height,
                                      f, layout, minimumHorizontalScale);
                }
                else
                {
                    splitLines (trimmed, f, startIndex, x, y, width, height,
                                maximumLines, lineWidth, layout, minimumHorizontalScale);
                }
            }
        }
    }
}

//==============================================================================
void GlyphArrangement::moveRangeOfGlyphs (int startIndex, int num, const float dx, const float dy)
{
    jassert (startIndex >= 0);

    if (dx != 0.0f || dy != 0.0f)
    {
        if (num < 0 || startIndex + num > glyphs.size())
            num = glyphs.size() - startIndex;

        while (--num >= 0)
            glyphs.getReference (startIndex++).moveBy (dx, dy);
    }
}

void GlyphArrangement::addLinesWithLineBreaks (const String& text, const Font& f,
                                               float x, float y, float width, float height, Justification layout)
{
    GlyphArrangement ga;
    ga.addJustifiedText (f, text, x, y, width, layout);

    auto bb = ga.getBoundingBox (0, -1, false);
    auto dy = y - bb.getY();

    if (layout.testFlags (Justification::verticallyCentred))   dy += (height - bb.getHeight()) * 0.5f;
    else if (layout.testFlags (Justification::bottom))         dy += (height - bb.getHeight());

    ga.moveRangeOfGlyphs (0, -1, 0.0f, dy);

    glyphs.addArray (ga.glyphs);
}

int GlyphArrangement::fitLineIntoSpace (int start, int numGlyphs, float x, float y, float w, float h, const Font& font,
                                        Justification justification, float minimumHorizontalScale)
{
    int numDeleted = 0;
    auto lineStartX = glyphs.getReference (start).getLeft();
    auto lineWidth  = glyphs.getReference (start + numGlyphs - 1).getRight() - lineStartX;

    if (lineWidth > w)
    {
        if (minimumHorizontalScale < 1.0f)
        {
            stretchRangeOfGlyphs (start, numGlyphs, jmax (minimumHorizontalScale, w / lineWidth));
            lineWidth = glyphs.getReference (start + numGlyphs - 1).getRight() - lineStartX - 0.5f;
        }

        if (lineWidth > w)
        {
            numDeleted = insertEllipsis (font, lineStartX + w, start, start + numGlyphs);
            numGlyphs -= numDeleted;
        }
    }

    justifyGlyphs (start, numGlyphs, x, y, w, h, justification);
    return numDeleted;
}

void GlyphArrangement::stretchRangeOfGlyphs (int startIndex, int num, float horizontalScaleFactor)
{
    jassert (startIndex >= 0);

    if (num < 0 || startIndex + num > glyphs.size())
        num = glyphs.size() - startIndex;

    if (num > 0)
    {
        auto xAnchor = glyphs.getReference (startIndex).getLeft();

        while (--num >= 0)
        {
            auto& pg = glyphs.getReference (startIndex++);

            pg.x = xAnchor + (pg.x - xAnchor) * horizontalScaleFactor;
            pg.font.setHorizontalScale (pg.font.getHorizontalScale() * horizontalScaleFactor);
            pg.w *= horizontalScaleFactor;
        }
    }
}

Rectangle<float> GlyphArrangement::getBoundingBox (int startIndex, int num, bool includeWhitespace) const
{
    jassert (startIndex >= 0);

    if (num < 0 || startIndex + num > glyphs.size())
        num = glyphs.size() - startIndex;

    Rectangle<float> result;

    while (--num >= 0)
    {
        auto& pg = glyphs.getReference (startIndex++);

        if (includeWhitespace || ! pg.isWhitespace())
            result = result.getUnion (pg.getBounds());
    }

    return result;
}

void GlyphArrangement::justifyGlyphs (int startIndex, int num,
                                      float x, float y, float width, float height,
                                      Justification justification)
{
    jassert (num >= 0 && startIndex >= 0);

    if (glyphs.size() > 0 && num > 0)
    {
        auto bb = getBoundingBox (startIndex, num, ! justification.testFlags (Justification::horizontallyJustified
                                                                               | Justification::horizontallyCentred));
        float deltaX = x, deltaY = y;

        if (justification.testFlags (Justification::horizontallyJustified))     deltaX -= bb.getX();
        else if (justification.testFlags (Justification::horizontallyCentred))  deltaX += (width - bb.getWidth()) * 0.5f - bb.getX();
        else if (justification.testFlags (Justification::right))                deltaX += width - bb.getRight();
        else                                                                    deltaX -= bb.getX();

        if (justification.testFlags (Justification::top))                       deltaY -= bb.getY();
        else if (justification.testFlags (Justification::bottom))               deltaY += height - bb.getBottom();
        else                                                                    deltaY += (height - bb.getHeight()) * 0.5f - bb.getY();

        moveRangeOfGlyphs (startIndex, num, deltaX, deltaY);

        if (justification.testFlags (Justification::horizontallyJustified))
        {
            int lineStart = 0;
            auto baseY = glyphs.getReference (startIndex).getBaselineY();

            int i;
            for (i = 0; i < num; ++i)
            {
                auto glyphY = glyphs.getReference (startIndex + i).getBaselineY();

                if (glyphY != baseY)
                {
                    spreadOutLine (startIndex + lineStart, i - lineStart, width);

                    lineStart = i;
                    baseY = glyphY;
                }
            }

            if (i > lineStart)
                spreadOutLine (startIndex + lineStart, i - lineStart, width);
        }
    }
}

void GlyphArrangement::spreadOutLine (int start, int num, float targetWidth)
{
    if (start + num < glyphs.size()
         && glyphs.getReference (start + num - 1).getCharacter() != '\r'
         && glyphs.getReference (start + num - 1).getCharacter() != '\n')
    {
        int numSpaces = 0;
        int spacesAtEnd = 0;

        for (int i = 0; i < num; ++i)
        {
            if (glyphs.getReference (start + i).isWhitespace())
            {
                ++spacesAtEnd;
                ++numSpaces;
            }
            else
            {
                spacesAtEnd = 0;
            }
        }

        numSpaces -= spacesAtEnd;

        if (numSpaces > 0)
        {
            auto startX = glyphs.getReference (start).getLeft();
            auto endX   = glyphs.getReference (start + num - 1 - spacesAtEnd).getRight();

            auto extraPaddingBetweenWords = (targetWidth - (endX - startX)) / (float) numSpaces;
            float deltaX = 0.0f;

            for (int i = 0; i < num; ++i)
            {
                glyphs.getReference (start + i).moveBy (deltaX, 0.0f);

                if (glyphs.getReference (start + i).isWhitespace())
                    deltaX += extraPaddingBetweenWords;
            }
        }
    }
}

static inline bool isBreakableGlyph (const PositionedGlyph& g) noexcept
{
    return g.isWhitespace() || g.getCharacter() == '-';
}

void GlyphArrangement::splitLines (const String& text, Font font, int startIndex,
                                   float x, float y, float width, float height, int maximumLines,
                                   float lineWidth, Justification layout, float minimumHorizontalScale)
{
    auto length = text.length();
    auto originalStartIndex = startIndex;
    int numLines = 1;

    if (length <= 12 && ! text.containsAnyOf (" -\t\r\n"))
        maximumLines = 1;

    maximumLines = jmin (maximumLines, length);

    while (numLines < maximumLines)
    {
        ++numLines;
        auto newFontHeight = height / (float) numLines;

        if (newFontHeight < font.getHeight())
        {
            font.setHeight (jmax (8.0f, newFontHeight));

            removeRangeOfGlyphs (startIndex, -1);
            addLineOfText (font, text, x, y);

            lineWidth = glyphs.getReference (glyphs.size() - 1).getRight()
                            - glyphs.getReference (startIndex).getLeft();
        }

        // Try to estimate the point at which there are enough lines to fit the text,
        // allowing for unevenness in the lengths due to differently sized words.
        const float lineLengthUnevennessAllowance = 80.0f;

        if (numLines > (lineWidth + lineLengthUnevennessAllowance) / width || newFontHeight < 8.0f)
            break;
    }

    if (numLines < 1)
        numLines = 1;

    int lineIndex = 0;
    auto lineY = y;
    auto widthPerLine = jmin (width / minimumHorizontalScale,
                              lineWidth / numLines);

    while (lineY < y + height)
    {
        auto endIndex = startIndex;
        auto lineStartX = glyphs.getReference (startIndex).getLeft();
        auto lineBottomY = lineY + font.getHeight();

        if (lineIndex++ >= numLines - 1
             || lineBottomY >= y + height)
        {
            widthPerLine = width;
            endIndex = glyphs.size();
        }
        else
        {
            while (endIndex < glyphs.size())
            {
                if (glyphs.getReference (endIndex).getRight() - lineStartX > widthPerLine)
                {
                    // got to a point where the line's too long, so skip forward to find a
                    // good place to break it..
                    auto searchStartIndex = endIndex;

                    while (endIndex < glyphs.size())
                    {
                        auto& g = glyphs.getReference (endIndex);

                        if ((g.getRight() - lineStartX) * minimumHorizontalScale < width)
                        {
                            if (isBreakableGlyph (g))
                            {
                                ++endIndex;
                                break;
                            }
                        }
                        else
                        {
                            // can't find a suitable break, so try looking backwards..
                            endIndex = searchStartIndex;

                            for (int back = 1; back < jmin (7, endIndex - startIndex - 1); ++back)
                            {
                                if (isBreakableGlyph (glyphs.getReference (endIndex - back)))
                                {
                                    endIndex -= back - 1;
                                    break;
                                }
                            }

                            break;
                        }

                        ++endIndex;
                    }

                    break;
                }

                ++endIndex;
            }

            auto wsStart = endIndex;
            auto wsEnd   = endIndex;

            while (wsStart > 0 && glyphs.getReference (wsStart - 1).isWhitespace())
                --wsStart;

            while (wsEnd < glyphs.size() && glyphs.getReference (wsEnd).isWhitespace())
                ++wsEnd;

            removeRangeOfGlyphs (wsStart, wsEnd - wsStart);
            endIndex = jmax (wsStart, startIndex + 1);
        }

        endIndex -= fitLineIntoSpace (startIndex, endIndex - startIndex,
                                      x, lineY, width, font.getHeight(), font,
                                      layout.getOnlyHorizontalFlags() | Justification::verticallyCentred,
                                      minimumHorizontalScale);

        startIndex = endIndex;
        lineY = lineBottomY;

        if (startIndex >= glyphs.size())
            break;
    }

    justifyGlyphs (originalStartIndex, glyphs.size() - originalStartIndex,
                   x, y, width, height, layout.getFlags() & ~Justification::horizontallyJustified);
}

//==============================================================================
void GlyphArrangement::drawGlyphUnderline (const Graphics& g, const PositionedGlyph& pg,
                                           int i, AffineTransform transform) const
{
    auto lineThickness = (pg.font.getDescent()) * 0.3f;
    auto nextX = pg.x + pg.w;

    if (i < glyphs.size() - 1 && glyphs.getReference (i + 1).y == pg.y)
        nextX = glyphs.getReference (i + 1).x;

    Path p;
    p.addRectangle (pg.x, pg.y + lineThickness * 2.0f, nextX - pg.x, lineThickness);
    g.fillPath (p, transform);
}

void GlyphArrangement::draw (const Graphics& g) const
{
    draw (g, {});
}

void GlyphArrangement::draw (const Graphics& g, AffineTransform transform) const
{
    auto& context = g.getInternalContext();
    auto lastFont = context.getFont();
    bool needToRestore = false;

    for (int i = 0; i < glyphs.size(); ++i)
    {
        auto& pg = glyphs.getReference(i);

        if (pg.font.isUnderlined())
            drawGlyphUnderline (g, pg, i, transform);

        if (! pg.isWhitespace())
        {
            if (lastFont != pg.font)
            {
                lastFont = pg.font;

                if (! needToRestore)
                {
                    needToRestore = true;
                    context.saveState();
                }

                context.setFont (lastFont);
            }

            context.drawGlyph (pg.glyph, AffineTransform::translation (pg.x, pg.y)
                                                         .followedBy (transform));
        }
    }

    if (needToRestore)
        context.restoreState();
}

void GlyphArrangement::createPath (Path& path) const
{
    for (auto& g : glyphs)
        g.createPath (path);
}

int GlyphArrangement::findGlyphIndexAt (float x, float y) const
{
    for (int i = 0; i < glyphs.size(); ++i)
        if (glyphs.getReference (i).hitTest (x, y))
            return i;

    return -1;
}

} // namespace juce