fix macOS build (following Projucer changes made in Windows, which removed /Applications/JUCE/modules from its headers). move JUCE headers under source control, so that Windows and macOS can both build against same version of JUCE. remove AUv3 target (I think it's an iOS thing, so it will never work with this macOS fluidsynth dylib).
This commit is contained in:
356
modules/juce_graphics/fonts/juce_AttributedString.cpp
Normal file
356
modules/juce_graphics/fonts/juce_AttributedString.cpp
Normal file
@ -0,0 +1,356 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
209
modules/juce_graphics/fonts/juce_AttributedString.h
Normal file
209
modules/juce_graphics/fonts/juce_AttributedString.h
Normal file
@ -0,0 +1,209 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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 text string with a set of colour/font settings that are associated with sub-ranges
|
||||
of the text.
|
||||
|
||||
An attributed string lets you create a string with varied fonts, colours, word-wrapping,
|
||||
layout, etc., and draw it using AttributedString::draw().
|
||||
|
||||
@see TextLayout
|
||||
|
||||
@tags{Graphics}
|
||||
*/
|
||||
class JUCE_API AttributedString
|
||||
{
|
||||
public:
|
||||
/** Creates an empty attributed string. */
|
||||
AttributedString();
|
||||
|
||||
/** Creates an attributed string with the given text. */
|
||||
explicit AttributedString (const String& text);
|
||||
|
||||
AttributedString (const AttributedString&);
|
||||
AttributedString& operator= (const AttributedString&);
|
||||
AttributedString (AttributedString&&) noexcept;
|
||||
AttributedString& operator= (AttributedString&&) noexcept;
|
||||
|
||||
/** Destructor. */
|
||||
~AttributedString() noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the complete text of this attributed string. */
|
||||
const String& getText() const noexcept { return text; }
|
||||
|
||||
/** Replaces all the text.
|
||||
This will change the text, but won't affect any of the colour or font attributes
|
||||
that have been added.
|
||||
*/
|
||||
void setText (const String& newText);
|
||||
|
||||
/** Appends some text (with a default font and colour). */
|
||||
void append (const String& textToAppend);
|
||||
/** Appends some text, with a specified font, and the default colour (black). */
|
||||
void append (const String& textToAppend, const Font& font);
|
||||
/** Appends some text, with a specified colour, and the default font. */
|
||||
void append (const String& textToAppend, Colour colour);
|
||||
/** Appends some text, with a specified font and colour. */
|
||||
void append (const String& textToAppend, const Font& font, Colour colour);
|
||||
|
||||
/** Appends another AttributedString to this one.
|
||||
Note that this will only append the text, fonts, and colours - it won't copy any
|
||||
other properties such as justification, line-spacing, etc from the other object.
|
||||
*/
|
||||
void append (const AttributedString& other);
|
||||
|
||||
/** Resets the string, clearing all text and attributes.
|
||||
Note that this won't affect global settings like the justification type,
|
||||
word-wrap mode, etc.
|
||||
*/
|
||||
void clear();
|
||||
|
||||
//==============================================================================
|
||||
/** Draws this string within the given area.
|
||||
The layout of the string within the rectangle is controlled by the justification
|
||||
value passed to setJustification().
|
||||
*/
|
||||
void draw (Graphics& g, const Rectangle<float>& area) const;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the justification that should be used for laying-out the text.
|
||||
This may include both vertical and horizontal flags.
|
||||
*/
|
||||
Justification getJustification() const noexcept { return justification; }
|
||||
|
||||
/** Sets the justification that should be used for laying-out the text.
|
||||
This may include both vertical and horizontal flags.
|
||||
*/
|
||||
void setJustification (Justification newJustification) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Types of word-wrap behaviour.
|
||||
@see getWordWrap, setWordWrap
|
||||
*/
|
||||
enum WordWrap
|
||||
{
|
||||
none, /**< No word-wrapping: lines extend indefinitely. */
|
||||
byWord, /**< Lines are wrapped on a word boundary. */
|
||||
byChar, /**< Lines are wrapped on a character boundary. */
|
||||
};
|
||||
|
||||
/** Returns the word-wrapping behaviour. */
|
||||
WordWrap getWordWrap() const noexcept { return wordWrap; }
|
||||
|
||||
/** Sets the word-wrapping behaviour. */
|
||||
void setWordWrap (WordWrap newWordWrap) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Types of reading direction that can be used.
|
||||
@see getReadingDirection, setReadingDirection
|
||||
*/
|
||||
enum ReadingDirection
|
||||
{
|
||||
natural,
|
||||
leftToRight,
|
||||
rightToLeft,
|
||||
};
|
||||
|
||||
/** Returns the reading direction for the text. */
|
||||
ReadingDirection getReadingDirection() const noexcept { return readingDirection; }
|
||||
|
||||
/** Sets the reading direction that should be used for the text. */
|
||||
void setReadingDirection (ReadingDirection newReadingDirection) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the extra line-spacing distance. */
|
||||
float getLineSpacing() const noexcept { return lineSpacing; }
|
||||
|
||||
/** Sets an extra line-spacing distance. */
|
||||
void setLineSpacing (float newLineSpacing) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** An attribute that has been applied to a range of characters in an AttributedString. */
|
||||
class JUCE_API Attribute
|
||||
{
|
||||
public:
|
||||
Attribute() noexcept;
|
||||
~Attribute() noexcept;
|
||||
Attribute (const Attribute&) noexcept;
|
||||
Attribute& operator= (const Attribute&) noexcept;
|
||||
Attribute (Attribute&&) noexcept;
|
||||
Attribute& operator= (Attribute&&) noexcept;
|
||||
|
||||
/** Creates an attribute that specifies the font and colour for a range of characters. */
|
||||
Attribute (Range<int> range, const Font& font, Colour colour) noexcept;
|
||||
|
||||
/** The range of characters to which this attribute will be applied. */
|
||||
Range<int> range;
|
||||
|
||||
/** The font for this range of characters. */
|
||||
Font font;
|
||||
|
||||
/** The colour for this range of characters. */
|
||||
Colour colour;
|
||||
|
||||
private:
|
||||
JUCE_LEAK_DETECTOR (Attribute)
|
||||
};
|
||||
|
||||
/** Returns the number of attributes that have been added to this string. */
|
||||
int getNumAttributes() const noexcept { return attributes.size(); }
|
||||
|
||||
/** Returns one of the string's attributes.
|
||||
The index provided must be less than getNumAttributes(), and >= 0.
|
||||
*/
|
||||
const Attribute& getAttribute (int index) const noexcept { return attributes.getReference (index); }
|
||||
|
||||
//==============================================================================
|
||||
/** Adds a colour attribute for the specified range. */
|
||||
void setColour (Range<int> range, Colour colour);
|
||||
|
||||
/** Removes all existing colour attributes, and applies this colour to the whole string. */
|
||||
void setColour (Colour colour);
|
||||
|
||||
/** Adds a font attribute for the specified range. */
|
||||
void setFont (Range<int> range, const Font& font);
|
||||
|
||||
/** Removes all existing font attributes, and applies this font to the whole string. */
|
||||
void setFont (const Font& font);
|
||||
|
||||
private:
|
||||
String text;
|
||||
float lineSpacing;
|
||||
Justification justification;
|
||||
WordWrap wordWrap;
|
||||
ReadingDirection readingDirection;
|
||||
Array<Attribute> attributes;
|
||||
|
||||
JUCE_LEAK_DETECTOR (AttributedString)
|
||||
};
|
||||
|
||||
} // namespace juce
|
399
modules/juce_graphics/fonts/juce_CustomTypeface.cpp
Normal file
399
modules/juce_graphics/fonts/juce_CustomTypeface.cpp
Normal file
@ -0,0 +1,399 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
167
modules/juce_graphics/fonts/juce_CustomTypeface.h
Normal file
167
modules/juce_graphics/fonts/juce_CustomTypeface.h
Normal file
@ -0,0 +1,167 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
|
||||
@tags{Graphics}
|
||||
*/
|
||||
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
|
722
modules/juce_graphics/fonts/juce_Font.cpp
Normal file
722
modules/juce_graphics/fonts/juce_Font.cpp
Normal file
@ -0,0 +1,722 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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_DECLARE_SINGLETON (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_IMPLEMENT_SINGLETON (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
|
480
modules/juce_graphics/fonts/juce_Font.h
Normal file
480
modules/juce_graphics/fonts/juce_Font.h
Normal file
@ -0,0 +1,480 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
|
||||
@tags{Graphics}
|
||||
*/
|
||||
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
|
791
modules/juce_graphics/fonts/juce_GlyphArrangement.cpp
Normal file
791
modules/juce_graphics/fonts/juce_GlyphArrangement.cpp
Normal file
@ -0,0 +1,791 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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) 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
|
332
modules/juce_graphics/fonts/juce_GlyphArrangement.h
Normal file
332
modules/juce_graphics/fonts/juce_GlyphArrangement.h
Normal file
@ -0,0 +1,332 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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 glyph from a particular font, with a particular size, style,
|
||||
typeface and position.
|
||||
|
||||
You should rarely need to use this class directly - for most purposes, the
|
||||
GlyphArrangement class will do what you need for text layout.
|
||||
|
||||
@see GlyphArrangement, Font
|
||||
|
||||
@tags{Graphics}
|
||||
*/
|
||||
class JUCE_API PositionedGlyph final
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
PositionedGlyph() noexcept;
|
||||
|
||||
PositionedGlyph (const Font& font, juce_wchar character, int glyphNumber,
|
||||
float anchorX, float baselineY, float width, bool isWhitespace);
|
||||
|
||||
PositionedGlyph (const PositionedGlyph&) = default;
|
||||
PositionedGlyph& operator= (const PositionedGlyph&) = default;
|
||||
|
||||
// VS2013 can't default move constructors and assignmants
|
||||
PositionedGlyph (PositionedGlyph&&) noexcept;
|
||||
PositionedGlyph& operator= (PositionedGlyph&&) noexcept;
|
||||
|
||||
~PositionedGlyph();
|
||||
|
||||
/** Returns the character the glyph represents. */
|
||||
juce_wchar getCharacter() const noexcept { return character; }
|
||||
/** Checks whether the glyph is actually empty. */
|
||||
bool isWhitespace() const noexcept { return whitespace; }
|
||||
|
||||
/** Returns the position of the glyph's left-hand edge. */
|
||||
float getLeft() const noexcept { return x; }
|
||||
/** Returns the position of the glyph's right-hand edge. */
|
||||
float getRight() const noexcept { return x + w; }
|
||||
/** Returns the y position of the glyph's baseline. */
|
||||
float getBaselineY() const noexcept { return y; }
|
||||
/** Returns the y position of the top of the glyph. */
|
||||
float getTop() const { return y - font.getAscent(); }
|
||||
/** Returns the y position of the bottom of the glyph. */
|
||||
float getBottom() const { return y + font.getDescent(); }
|
||||
/** Returns the bounds of the glyph. */
|
||||
Rectangle<float> getBounds() const { return { x, getTop(), w, font.getHeight() }; }
|
||||
|
||||
//==============================================================================
|
||||
/** Shifts the glyph's position by a relative amount. */
|
||||
void moveBy (float deltaX, float deltaY);
|
||||
|
||||
//==============================================================================
|
||||
/** Draws the glyph into a graphics context.
|
||||
(Note that this may change the context's currently selected font).
|
||||
*/
|
||||
void draw (Graphics& g) const;
|
||||
|
||||
/** Draws the glyph into a graphics context, with an extra transform applied to it.
|
||||
(Note that this may change the context's currently selected font).
|
||||
*/
|
||||
void draw (Graphics& g, AffineTransform transform) const;
|
||||
|
||||
/** Returns the path for this glyph.
|
||||
@param path the glyph's outline will be appended to this path
|
||||
*/
|
||||
void createPath (Path& path) const;
|
||||
|
||||
/** Checks to see if a point lies within this glyph. */
|
||||
bool hitTest (float x, float y) const;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
friend class GlyphArrangement;
|
||||
Font font;
|
||||
juce_wchar character;
|
||||
int glyph;
|
||||
float x, y, w;
|
||||
bool whitespace;
|
||||
|
||||
JUCE_LEAK_DETECTOR (PositionedGlyph)
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A set of glyphs, each with a position.
|
||||
|
||||
You can create a GlyphArrangement, text to it and then draw it onto a
|
||||
graphics context. It's used internally by the text methods in the
|
||||
Graphics class, but can be used directly if more control is needed.
|
||||
|
||||
@see Font, PositionedGlyph
|
||||
|
||||
@tags{Graphics}
|
||||
*/
|
||||
class JUCE_API GlyphArrangement final
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an empty arrangement. */
|
||||
GlyphArrangement();
|
||||
|
||||
GlyphArrangement (const GlyphArrangement&) = default;
|
||||
GlyphArrangement& operator= (const GlyphArrangement&) = default;
|
||||
|
||||
// VS2013 can't default move constructors and assignmants
|
||||
GlyphArrangement (GlyphArrangement&&);
|
||||
GlyphArrangement& operator= (GlyphArrangement&&);
|
||||
|
||||
/** Destructor. */
|
||||
~GlyphArrangement();
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the total number of glyphs in the arrangement. */
|
||||
int getNumGlyphs() const noexcept { return glyphs.size(); }
|
||||
|
||||
/** Returns one of the glyphs from the arrangement.
|
||||
|
||||
@param index the glyph's index, from 0 to (getNumGlyphs() - 1). Be
|
||||
careful not to pass an out-of-range index here, as it
|
||||
doesn't do any bounds-checking.
|
||||
*/
|
||||
PositionedGlyph& getGlyph (int index) noexcept;
|
||||
|
||||
const PositionedGlyph* begin() const { return glyphs.begin(); }
|
||||
const PositionedGlyph* end() const { return glyphs.end(); }
|
||||
|
||||
//==============================================================================
|
||||
/** Clears all text from the arrangement and resets it. */
|
||||
void clear();
|
||||
|
||||
/** Appends a line of text to the arrangement.
|
||||
|
||||
This will add the text as a single line, where x is the left-hand edge of the
|
||||
first character, and y is the position for the text's baseline.
|
||||
|
||||
If the text contains new-lines or carriage-returns, this will ignore them - use
|
||||
addJustifiedText() to add multi-line arrangements.
|
||||
*/
|
||||
void addLineOfText (const Font& font,
|
||||
const String& text,
|
||||
float x, float y);
|
||||
|
||||
/** Adds a line of text, truncating it if it's wider than a specified size.
|
||||
|
||||
This is the same as addLineOfText(), but if the line's width exceeds the value
|
||||
specified in maxWidthPixels, it will be truncated using either ellipsis (i.e. dots: "..."),
|
||||
if useEllipsis is true, or if this is false, it will just drop any subsequent characters.
|
||||
*/
|
||||
void addCurtailedLineOfText (const Font& font,
|
||||
const String& text,
|
||||
float x, float y,
|
||||
float maxWidthPixels,
|
||||
bool useEllipsis);
|
||||
|
||||
/** Adds some multi-line text, breaking lines at word-boundaries if they are too wide.
|
||||
|
||||
This will add text to the arrangement, breaking it into new lines either where there
|
||||
is a new-line or carriage-return character in the text, or where a line's width
|
||||
exceeds the value set in maxLineWidth.
|
||||
|
||||
Each line that is added will be laid out using the flags set in horizontalLayout, so
|
||||
the lines can be left- or right-justified, or centred horizontally in the space
|
||||
between x and (x + maxLineWidth).
|
||||
|
||||
The y coordinate is the position of the baseline of the first line of text - subsequent
|
||||
lines will be placed below it, separated by a distance of font.getHeight().
|
||||
*/
|
||||
void addJustifiedText (const Font& font,
|
||||
const String& text,
|
||||
float x, float y,
|
||||
float maxLineWidth,
|
||||
Justification horizontalLayout);
|
||||
|
||||
/** Tries to fit some text within a given space.
|
||||
|
||||
This does its best to make the given text readable within the specified rectangle,
|
||||
so it useful for labelling things.
|
||||
|
||||
If the text is too big, it'll be squashed horizontally or broken over multiple lines
|
||||
if the maximumLinesToUse value allows this. If the text just won't fit into the space,
|
||||
it'll cram as much as possible in there, and put some ellipsis at the end to show that
|
||||
it's been truncated.
|
||||
|
||||
A Justification parameter lets you specify how the text is laid out within the rectangle,
|
||||
both horizontally and vertically.
|
||||
|
||||
The minimumHorizontalScale parameter specifies how much the text can be squashed horizontally
|
||||
to try to squeeze it into the space. If you don't want any horizontal scaling to occur, you
|
||||
can set this value to 1.0f. Pass 0 if you want it to use the default value.
|
||||
|
||||
@see Graphics::drawFittedText
|
||||
*/
|
||||
void addFittedText (const Font& font,
|
||||
const String& text,
|
||||
float x, float y, float width, float height,
|
||||
Justification layout,
|
||||
int maximumLinesToUse,
|
||||
float minimumHorizontalScale = 0.0f);
|
||||
|
||||
/** Appends another glyph arrangement to this one. */
|
||||
void addGlyphArrangement (const GlyphArrangement&);
|
||||
|
||||
/** Appends a custom glyph to the arrangement. */
|
||||
void addGlyph (const PositionedGlyph&);
|
||||
|
||||
//==============================================================================
|
||||
/** Draws this glyph arrangement to a graphics context.
|
||||
|
||||
This uses cached bitmaps so is much faster than the draw (Graphics&, AffineTransform)
|
||||
method, which renders the glyphs as filled vectors.
|
||||
*/
|
||||
void draw (const Graphics&) const;
|
||||
|
||||
/** Draws this glyph arrangement to a graphics context.
|
||||
|
||||
This renders the paths as filled vectors, so is far slower than the draw (Graphics&)
|
||||
method for non-transformed arrangements.
|
||||
*/
|
||||
void draw (const Graphics&, AffineTransform) const;
|
||||
|
||||
/** Converts the set of glyphs into a path.
|
||||
@param path the glyphs' outlines will be appended to this path
|
||||
*/
|
||||
void createPath (Path& path) const;
|
||||
|
||||
/** Looks for a glyph that contains the given coordinate.
|
||||
@returns the index of the glyph, or -1 if none were found.
|
||||
*/
|
||||
int findGlyphIndexAt (float x, float y) const;
|
||||
|
||||
//==============================================================================
|
||||
/** Finds the smallest rectangle that will enclose a subset of the glyphs.
|
||||
|
||||
|
||||
@param startIndex the first glyph to test
|
||||
@param numGlyphs the number of glyphs to include; if this is < 0, all glyphs after
|
||||
startIndex will be included
|
||||
@param includeWhitespace if true, the extent of any whitespace characters will also
|
||||
be taken into account
|
||||
*/
|
||||
Rectangle<float> getBoundingBox (int startIndex, int numGlyphs, bool includeWhitespace) const;
|
||||
|
||||
/** Shifts a set of glyphs by a given amount.
|
||||
|
||||
@param startIndex the first glyph to transform
|
||||
@param numGlyphs the number of glyphs to move; if this is < 0, all glyphs after
|
||||
startIndex will be used
|
||||
@param deltaX the amount to add to their x-positions
|
||||
@param deltaY the amount to add to their y-positions
|
||||
*/
|
||||
void moveRangeOfGlyphs (int startIndex, int numGlyphs,
|
||||
float deltaX, float deltaY);
|
||||
|
||||
/** Removes a set of glyphs from the arrangement.
|
||||
|
||||
@param startIndex the first glyph to remove
|
||||
@param numGlyphs the number of glyphs to remove; if this is < 0, all glyphs after
|
||||
startIndex will be deleted
|
||||
*/
|
||||
void removeRangeOfGlyphs (int startIndex, int numGlyphs);
|
||||
|
||||
/** Expands or compresses a set of glyphs horizontally.
|
||||
|
||||
@param startIndex the first glyph to transform
|
||||
@param numGlyphs the number of glyphs to stretch; if this is < 0, all glyphs after
|
||||
startIndex will be used
|
||||
@param horizontalScaleFactor how much to scale their horizontal width by
|
||||
*/
|
||||
void stretchRangeOfGlyphs (int startIndex, int numGlyphs,
|
||||
float horizontalScaleFactor);
|
||||
|
||||
/** Justifies a set of glyphs within a given space.
|
||||
|
||||
This moves the glyphs as a block so that the whole thing is located within the
|
||||
given rectangle with the specified layout.
|
||||
|
||||
If the Justification::horizontallyJustified flag is specified, each line will
|
||||
be stretched out to fill the specified width.
|
||||
*/
|
||||
void justifyGlyphs (int startIndex, int numGlyphs,
|
||||
float x, float y, float width, float height,
|
||||
Justification justification);
|
||||
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
Array<PositionedGlyph> glyphs;
|
||||
|
||||
int insertEllipsis (const Font&, float maxXPos, int startIndex, int endIndex);
|
||||
int fitLineIntoSpace (int start, int numGlyphs, float x, float y, float w, float h, const Font&,
|
||||
Justification, float minimumHorizontalScale);
|
||||
void spreadOutLine (int start, int numGlyphs, float targetWidth);
|
||||
void splitLines (const String&, Font, int start, float x, float y, float w, float h, int maxLines,
|
||||
float lineWidth, Justification, float minimumHorizontalScale);
|
||||
void addLinesWithLineBreaks (const String&, const Font&, float x, float y, float width, float height, Justification);
|
||||
void drawGlyphUnderline (const Graphics&, const PositionedGlyph&, int, AffineTransform) const;
|
||||
|
||||
JUCE_LEAK_DETECTOR (GlyphArrangement)
|
||||
};
|
||||
|
||||
} // namespace juce
|
614
modules/juce_graphics/fonts/juce_TextLayout.cpp
Normal file
614
modules/juce_graphics/fonts/juce_TextLayout.cpp
Normal file
@ -0,0 +1,614 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
TextLayout::Glyph::Glyph (int glyph, Point<float> anch, float w) noexcept
|
||||
: glyphCode (glyph), anchor (anch), width (w)
|
||||
{
|
||||
}
|
||||
|
||||
TextLayout::Glyph::Glyph (const Glyph& other) noexcept
|
||||
: glyphCode (other.glyphCode), anchor (other.anchor), width (other.width)
|
||||
{
|
||||
}
|
||||
|
||||
TextLayout::Glyph& TextLayout::Glyph::operator= (const Glyph& other) noexcept
|
||||
{
|
||||
glyphCode = other.glyphCode;
|
||||
anchor = other.anchor;
|
||||
width = other.width;
|
||||
return *this;
|
||||
}
|
||||
|
||||
TextLayout::Glyph::~Glyph() noexcept {}
|
||||
|
||||
//==============================================================================
|
||||
TextLayout::Run::Run() noexcept
|
||||
: colour (0xff000000)
|
||||
{
|
||||
}
|
||||
|
||||
TextLayout::Run::Run (Range<int> range, int numGlyphsToPreallocate)
|
||||
: colour (0xff000000), stringRange (range)
|
||||
{
|
||||
glyphs.ensureStorageAllocated (numGlyphsToPreallocate);
|
||||
}
|
||||
|
||||
TextLayout::Run::Run (const Run& other)
|
||||
: font (other.font),
|
||||
colour (other.colour),
|
||||
glyphs (other.glyphs),
|
||||
stringRange (other.stringRange)
|
||||
{
|
||||
}
|
||||
|
||||
TextLayout::Run::~Run() noexcept {}
|
||||
|
||||
Range<float> TextLayout::Run::getRunBoundsX() const noexcept
|
||||
{
|
||||
Range<float> range;
|
||||
bool isFirst = true;
|
||||
|
||||
for (auto& glyph : glyphs)
|
||||
{
|
||||
Range<float> r (glyph.anchor.x, glyph.anchor.x + glyph.width);
|
||||
|
||||
if (isFirst)
|
||||
{
|
||||
isFirst = false;
|
||||
range = r;
|
||||
}
|
||||
else
|
||||
{
|
||||
range = range.getUnionWith (r);
|
||||
}
|
||||
}
|
||||
|
||||
return range;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
TextLayout::Line::Line() noexcept
|
||||
: ascent (0.0f), descent (0.0f), leading (0.0f)
|
||||
{
|
||||
}
|
||||
|
||||
TextLayout::Line::Line (Range<int> range, Point<float> o, float asc, float desc,
|
||||
float lead, int numRunsToPreallocate)
|
||||
: stringRange (range), lineOrigin (o),
|
||||
ascent (asc), descent (desc), leading (lead)
|
||||
{
|
||||
runs.ensureStorageAllocated (numRunsToPreallocate);
|
||||
}
|
||||
|
||||
TextLayout::Line::Line (const Line& other)
|
||||
: stringRange (other.stringRange), lineOrigin (other.lineOrigin),
|
||||
ascent (other.ascent), descent (other.descent), leading (other.leading)
|
||||
{
|
||||
runs.addCopiesOf (other.runs);
|
||||
}
|
||||
|
||||
TextLayout::Line::~Line() noexcept
|
||||
{
|
||||
}
|
||||
|
||||
Range<float> TextLayout::Line::getLineBoundsX() const noexcept
|
||||
{
|
||||
Range<float> range;
|
||||
bool isFirst = true;
|
||||
|
||||
for (auto* run : runs)
|
||||
{
|
||||
auto runRange = run->getRunBoundsX();
|
||||
|
||||
if (isFirst)
|
||||
{
|
||||
isFirst = false;
|
||||
range = runRange;
|
||||
}
|
||||
else
|
||||
{
|
||||
range = range.getUnionWith (runRange);
|
||||
}
|
||||
}
|
||||
|
||||
return range + lineOrigin.x;
|
||||
}
|
||||
|
||||
Range<float> TextLayout::Line::getLineBoundsY() const noexcept
|
||||
{
|
||||
return { lineOrigin.y - ascent,
|
||||
lineOrigin.y + descent };
|
||||
}
|
||||
|
||||
Rectangle<float> TextLayout::Line::getLineBounds() const noexcept
|
||||
{
|
||||
auto x = getLineBoundsX();
|
||||
auto y = getLineBoundsY();
|
||||
|
||||
return { x.getStart(), y.getStart(), x.getLength(), y.getLength() };
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
TextLayout::TextLayout()
|
||||
: width (0), height (0), justification (Justification::topLeft)
|
||||
{
|
||||
}
|
||||
|
||||
TextLayout::TextLayout (const TextLayout& other)
|
||||
: width (other.width), height (other.height),
|
||||
justification (other.justification)
|
||||
{
|
||||
lines.addCopiesOf (other.lines);
|
||||
}
|
||||
|
||||
TextLayout::TextLayout (TextLayout&& other) noexcept
|
||||
: lines (static_cast<OwnedArray<Line>&&> (other.lines)),
|
||||
width (other.width), height (other.height),
|
||||
justification (other.justification)
|
||||
{
|
||||
}
|
||||
|
||||
TextLayout& TextLayout::operator= (TextLayout&& other) noexcept
|
||||
{
|
||||
lines = static_cast<OwnedArray<Line>&&> (other.lines);
|
||||
width = other.width;
|
||||
height = other.height;
|
||||
justification = other.justification;
|
||||
return *this;
|
||||
}
|
||||
|
||||
TextLayout& TextLayout::operator= (const TextLayout& other)
|
||||
{
|
||||
width = other.width;
|
||||
height = other.height;
|
||||
justification = other.justification;
|
||||
lines.clear();
|
||||
lines.addCopiesOf (other.lines);
|
||||
return *this;
|
||||
}
|
||||
|
||||
TextLayout::~TextLayout()
|
||||
{
|
||||
}
|
||||
|
||||
TextLayout::Line& TextLayout::getLine (int index) const noexcept
|
||||
{
|
||||
return *lines.getUnchecked (index);
|
||||
}
|
||||
|
||||
void TextLayout::ensureStorageAllocated (int numLinesNeeded)
|
||||
{
|
||||
lines.ensureStorageAllocated (numLinesNeeded);
|
||||
}
|
||||
|
||||
void TextLayout::addLine (Line* line)
|
||||
{
|
||||
lines.add (line);
|
||||
}
|
||||
|
||||
void TextLayout::draw (Graphics& g, Rectangle<float> area) const
|
||||
{
|
||||
auto origin = justification.appliedToRectangle (Rectangle<float> (width, getHeight()), area).getPosition();
|
||||
|
||||
auto& context = g.getInternalContext();
|
||||
auto clip = context.getClipBounds();
|
||||
auto clipTop = clip.getY() - origin.y;
|
||||
auto clipBottom = clip.getBottom() - origin.y;
|
||||
|
||||
for (auto* line : lines)
|
||||
{
|
||||
auto lineRangeY = line->getLineBoundsY();
|
||||
|
||||
if (lineRangeY.getEnd() < clipTop)
|
||||
continue;
|
||||
|
||||
if (lineRangeY.getStart() > clipBottom)
|
||||
break;
|
||||
|
||||
auto lineOrigin = origin + line->lineOrigin;
|
||||
|
||||
for (auto* run : line->runs)
|
||||
{
|
||||
context.setFont (run->font);
|
||||
context.setFill (run->colour);
|
||||
|
||||
for (auto& glyph : run->glyphs)
|
||||
context.drawGlyph (glyph.glyphCode, AffineTransform::translation (lineOrigin.x + glyph.anchor.x,
|
||||
lineOrigin.y + glyph.anchor.y));
|
||||
|
||||
if (run->font.isUnderlined())
|
||||
{
|
||||
auto runExtent = run->getRunBoundsX();
|
||||
auto lineThickness = run->font.getDescent() * 0.3f;
|
||||
|
||||
context.fillRect ({ runExtent.getStart() + lineOrigin.x, lineOrigin.y + lineThickness * 2.0f,
|
||||
runExtent.getLength(), lineThickness });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TextLayout::createLayout (const AttributedString& text, float maxWidth)
|
||||
{
|
||||
createLayout (text, maxWidth, 1.0e7f);
|
||||
}
|
||||
|
||||
void TextLayout::createLayout (const AttributedString& text, float maxWidth, float maxHeight)
|
||||
{
|
||||
lines.clear();
|
||||
width = maxWidth;
|
||||
height = maxHeight;
|
||||
justification = text.getJustification();
|
||||
|
||||
if (! createNativeLayout (text))
|
||||
createStandardLayout (text);
|
||||
|
||||
recalculateSize();
|
||||
}
|
||||
|
||||
void TextLayout::createLayoutWithBalancedLineLengths (const AttributedString& text, float maxWidth)
|
||||
{
|
||||
createLayoutWithBalancedLineLengths (text, maxWidth, 1.0e7f);
|
||||
}
|
||||
|
||||
void TextLayout::createLayoutWithBalancedLineLengths (const AttributedString& text, float maxWidth, float maxHeight)
|
||||
{
|
||||
auto minimumWidth = maxWidth / 2.0f;
|
||||
auto bestWidth = maxWidth;
|
||||
float bestLineProportion = 0.0f;
|
||||
|
||||
while (maxWidth > minimumWidth)
|
||||
{
|
||||
createLayout (text, maxWidth, maxHeight);
|
||||
|
||||
if (getNumLines() < 2)
|
||||
return;
|
||||
|
||||
auto line1 = lines.getUnchecked (lines.size() - 1)->getLineBoundsX().getLength();
|
||||
auto line2 = lines.getUnchecked (lines.size() - 2)->getLineBoundsX().getLength();
|
||||
auto shortest = jmin (line1, line2);
|
||||
auto longest = jmax (line1, line2);
|
||||
auto prop = shortest > 0 ? longest / shortest : 1.0f;
|
||||
|
||||
if (prop > 0.9f && prop < 1.1f)
|
||||
return;
|
||||
|
||||
if (prop > bestLineProportion)
|
||||
{
|
||||
bestLineProportion = prop;
|
||||
bestWidth = maxWidth;
|
||||
}
|
||||
|
||||
maxWidth -= 10.0f;
|
||||
}
|
||||
|
||||
if (bestWidth != maxWidth)
|
||||
createLayout (text, bestWidth, maxHeight);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
namespace TextLayoutHelpers
|
||||
{
|
||||
struct Token
|
||||
{
|
||||
Token (const String& t, const Font& f, Colour c, bool whitespace)
|
||||
: text (t), font (f), colour (c),
|
||||
area (font.getStringWidthFloat (t), f.getHeight()),
|
||||
isWhitespace (whitespace),
|
||||
isNewLine (t.containsChar ('\n') || t.containsChar ('\r'))
|
||||
{}
|
||||
|
||||
const String text;
|
||||
const Font font;
|
||||
const Colour colour;
|
||||
Rectangle<float> area;
|
||||
int line;
|
||||
float lineHeight;
|
||||
const bool isWhitespace, isNewLine;
|
||||
|
||||
Token& operator= (const Token&) = delete;
|
||||
};
|
||||
|
||||
struct TokenList
|
||||
{
|
||||
TokenList() noexcept {}
|
||||
|
||||
void createLayout (const AttributedString& text, TextLayout& layout)
|
||||
{
|
||||
layout.ensureStorageAllocated (totalLines);
|
||||
|
||||
addTextRuns (text);
|
||||
layoutRuns (layout.getWidth(), text.getLineSpacing(), text.getWordWrap());
|
||||
|
||||
int charPosition = 0;
|
||||
int lineStartPosition = 0;
|
||||
int runStartPosition = 0;
|
||||
|
||||
std::unique_ptr<TextLayout::Line> currentLine;
|
||||
std::unique_ptr<TextLayout::Run> currentRun;
|
||||
|
||||
bool needToSetLineOrigin = true;
|
||||
|
||||
for (int i = 0; i < tokens.size(); ++i)
|
||||
{
|
||||
auto& t = *tokens.getUnchecked (i);
|
||||
|
||||
Array<int> newGlyphs;
|
||||
Array<float> xOffsets;
|
||||
t.font.getGlyphPositions (getTrimmedEndIfNotAllWhitespace (t.text), newGlyphs, xOffsets);
|
||||
|
||||
if (currentRun == nullptr) currentRun .reset (new TextLayout::Run());
|
||||
if (currentLine == nullptr) currentLine.reset (new TextLayout::Line());
|
||||
|
||||
if (newGlyphs.size() > 0)
|
||||
{
|
||||
currentRun->glyphs.ensureStorageAllocated (currentRun->glyphs.size() + newGlyphs.size());
|
||||
auto tokenOrigin = t.area.getPosition().translated (0, t.font.getAscent());
|
||||
|
||||
if (needToSetLineOrigin)
|
||||
{
|
||||
needToSetLineOrigin = false;
|
||||
currentLine->lineOrigin = tokenOrigin;
|
||||
}
|
||||
|
||||
auto glyphOffset = tokenOrigin - currentLine->lineOrigin;
|
||||
|
||||
for (int j = 0; j < newGlyphs.size(); ++j)
|
||||
{
|
||||
auto x = xOffsets.getUnchecked (j);
|
||||
currentRun->glyphs.add (TextLayout::Glyph (newGlyphs.getUnchecked(j),
|
||||
glyphOffset.translated (x, 0),
|
||||
xOffsets.getUnchecked (j + 1) - x));
|
||||
}
|
||||
|
||||
charPosition += newGlyphs.size();
|
||||
}
|
||||
|
||||
if (t.isWhitespace || t.isNewLine)
|
||||
++charPosition;
|
||||
|
||||
if (auto* nextToken = tokens[i + 1])
|
||||
{
|
||||
if (t.font != nextToken->font || t.colour != nextToken->colour)
|
||||
{
|
||||
addRun (*currentLine, currentRun.release(), t, runStartPosition, charPosition);
|
||||
runStartPosition = charPosition;
|
||||
}
|
||||
|
||||
if (t.line != nextToken->line)
|
||||
{
|
||||
if (currentRun == nullptr)
|
||||
currentRun.reset (new TextLayout::Run());
|
||||
|
||||
addRun (*currentLine, currentRun.release(), t, runStartPosition, charPosition);
|
||||
currentLine->stringRange = { lineStartPosition, charPosition };
|
||||
|
||||
if (! needToSetLineOrigin)
|
||||
layout.addLine (currentLine.release());
|
||||
|
||||
runStartPosition = charPosition;
|
||||
lineStartPosition = charPosition;
|
||||
needToSetLineOrigin = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
addRun (*currentLine, currentRun.release(), t, runStartPosition, charPosition);
|
||||
currentLine->stringRange = { lineStartPosition, charPosition };
|
||||
|
||||
if (! needToSetLineOrigin)
|
||||
layout.addLine (currentLine.release());
|
||||
|
||||
needToSetLineOrigin = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ((text.getJustification().getFlags() & (Justification::right | Justification::horizontallyCentred)) != 0)
|
||||
{
|
||||
auto totalW = layout.getWidth();
|
||||
bool isCentred = (text.getJustification().getFlags() & Justification::horizontallyCentred) != 0;
|
||||
|
||||
for (int i = 0; i < layout.getNumLines(); ++i)
|
||||
{
|
||||
auto dx = totalW - layout.getLine(i).getLineBoundsX().getLength();
|
||||
|
||||
if (isCentred)
|
||||
dx /= 2.0f;
|
||||
|
||||
layout.getLine(i).lineOrigin.x += dx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static void addRun (TextLayout::Line& glyphLine, TextLayout::Run* glyphRun,
|
||||
const Token& t, int start, int end)
|
||||
{
|
||||
glyphRun->stringRange = { start, end };
|
||||
glyphRun->font = t.font;
|
||||
glyphRun->colour = t.colour;
|
||||
glyphLine.ascent = jmax (glyphLine.ascent, t.font.getAscent());
|
||||
glyphLine.descent = jmax (glyphLine.descent, t.font.getDescent());
|
||||
glyphLine.runs.add (glyphRun);
|
||||
}
|
||||
|
||||
static int getCharacterType (juce_wchar c) noexcept
|
||||
{
|
||||
if (c == '\r' || c == '\n')
|
||||
return 0;
|
||||
|
||||
return CharacterFunctions::isWhitespace (c) ? 2 : 1;
|
||||
}
|
||||
|
||||
void appendText (const String& stringText, const Font& font, Colour colour)
|
||||
{
|
||||
auto t = stringText.getCharPointer();
|
||||
String currentString;
|
||||
int lastCharType = 0;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
auto c = t.getAndAdvance();
|
||||
|
||||
if (c == 0)
|
||||
break;
|
||||
|
||||
auto charType = getCharacterType (c);
|
||||
|
||||
if (charType == 0 || charType != lastCharType)
|
||||
{
|
||||
if (currentString.isNotEmpty())
|
||||
tokens.add (new Token (currentString, font, colour,
|
||||
lastCharType == 2 || lastCharType == 0));
|
||||
|
||||
currentString = String::charToString (c);
|
||||
|
||||
if (c == '\r' && *t == '\n')
|
||||
currentString += t.getAndAdvance();
|
||||
}
|
||||
else
|
||||
{
|
||||
currentString += c;
|
||||
}
|
||||
|
||||
lastCharType = charType;
|
||||
}
|
||||
|
||||
if (currentString.isNotEmpty())
|
||||
tokens.add (new Token (currentString, font, colour, lastCharType == 2));
|
||||
}
|
||||
|
||||
void layoutRuns (float maxWidth, float extraLineSpacing, AttributedString::WordWrap wordWrap)
|
||||
{
|
||||
float x = 0, y = 0, h = 0;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < tokens.size(); ++i)
|
||||
{
|
||||
auto& t = *tokens.getUnchecked(i);
|
||||
t.area.setPosition (x, y);
|
||||
t.line = totalLines;
|
||||
x += t.area.getWidth();
|
||||
h = jmax (h, t.area.getHeight() + extraLineSpacing);
|
||||
|
||||
auto* nextTok = tokens[i + 1];
|
||||
|
||||
if (nextTok == nullptr)
|
||||
break;
|
||||
|
||||
bool tokenTooLarge = (x + nextTok->area.getWidth() > maxWidth);
|
||||
|
||||
if (t.isNewLine || ((! nextTok->isWhitespace) && (tokenTooLarge && wordWrap != AttributedString::none)))
|
||||
{
|
||||
setLastLineHeight (i + 1, h);
|
||||
x = 0;
|
||||
y += h;
|
||||
h = 0;
|
||||
++totalLines;
|
||||
}
|
||||
}
|
||||
|
||||
setLastLineHeight (jmin (i + 1, tokens.size()), h);
|
||||
++totalLines;
|
||||
}
|
||||
|
||||
void setLastLineHeight (int i, float height) noexcept
|
||||
{
|
||||
while (--i >= 0)
|
||||
{
|
||||
auto& tok = *tokens.getUnchecked (i);
|
||||
|
||||
if (tok.line == totalLines)
|
||||
tok.lineHeight = height;
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void addTextRuns (const AttributedString& text)
|
||||
{
|
||||
auto numAttributes = text.getNumAttributes();
|
||||
tokens.ensureStorageAllocated (jmax (64, numAttributes));
|
||||
|
||||
for (int i = 0; i < numAttributes; ++i)
|
||||
{
|
||||
auto& attr = text.getAttribute (i);
|
||||
|
||||
appendText (text.getText().substring (attr.range.getStart(), attr.range.getEnd()),
|
||||
attr.font, attr.colour);
|
||||
}
|
||||
}
|
||||
|
||||
static String getTrimmedEndIfNotAllWhitespace (const String& s)
|
||||
{
|
||||
auto trimmed = s.trimEnd();
|
||||
|
||||
if (trimmed.isEmpty() && s.isNotEmpty())
|
||||
trimmed = s.replaceCharacters ("\r\n\t", " ");
|
||||
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
OwnedArray<Token> tokens;
|
||||
int totalLines = 0;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (TokenList)
|
||||
};
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void TextLayout::createStandardLayout (const AttributedString& text)
|
||||
{
|
||||
TextLayoutHelpers::TokenList l;
|
||||
l.createLayout (text, *this);
|
||||
}
|
||||
|
||||
void TextLayout::recalculateSize()
|
||||
{
|
||||
if (! lines.isEmpty())
|
||||
{
|
||||
auto bounds = lines.getFirst()->getLineBounds();
|
||||
|
||||
for (auto* line : lines)
|
||||
bounds = bounds.getUnion (line->getLineBounds());
|
||||
|
||||
for (auto* line : lines)
|
||||
line->lineOrigin.x -= bounds.getX();
|
||||
|
||||
width = bounds.getWidth();
|
||||
height = bounds.getHeight();
|
||||
}
|
||||
else
|
||||
{
|
||||
width = 0;
|
||||
height = 0;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace juce
|
200
modules/juce_graphics/fonts/juce_TextLayout.h
Normal file
200
modules/juce_graphics/fonts/juce_TextLayout.h
Normal file
@ -0,0 +1,200 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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 Pre-formatted piece of text, which may contain multiple fonts and colours.
|
||||
|
||||
A TextLayout is created from an AttributedString, and once created can be
|
||||
quickly drawn into a Graphics context.
|
||||
|
||||
@see AttributedString
|
||||
|
||||
@tags{Graphics}
|
||||
*/
|
||||
class JUCE_API TextLayout final
|
||||
{
|
||||
public:
|
||||
/** Creates an empty layout.
|
||||
Having created a TextLayout, you can populate it using createLayout() or
|
||||
createLayoutWithBalancedLineLengths().
|
||||
*/
|
||||
TextLayout();
|
||||
TextLayout (const TextLayout&);
|
||||
TextLayout& operator= (const TextLayout&);
|
||||
TextLayout (TextLayout&&) noexcept;
|
||||
TextLayout& operator= (TextLayout&&) noexcept;
|
||||
|
||||
/** Destructor. */
|
||||
~TextLayout();
|
||||
|
||||
//==============================================================================
|
||||
/** Creates a layout from the given attributed string.
|
||||
This will replace any data that is currently stored in the layout.
|
||||
*/
|
||||
void createLayout (const AttributedString&, float maxWidth);
|
||||
|
||||
/** Creates a layout from the given attributed string, given some size constraints.
|
||||
This will replace any data that is currently stored in the layout.
|
||||
*/
|
||||
void createLayout (const AttributedString&, float maxWidth, float maxHeight);
|
||||
|
||||
/** Creates a layout, attempting to choose a width which results in lines
|
||||
of a similar length.
|
||||
|
||||
This will be slower than the normal createLayout method, but produces a
|
||||
tidier result.
|
||||
*/
|
||||
void createLayoutWithBalancedLineLengths (const AttributedString&, float maxWidth);
|
||||
|
||||
/** Creates a layout, attempting to choose a width which results in lines
|
||||
of a similar length.
|
||||
|
||||
This will be slower than the normal createLayout method, but produces a
|
||||
tidier result.
|
||||
*/
|
||||
void createLayoutWithBalancedLineLengths (const AttributedString&, float maxWidth, float maxHeight);
|
||||
|
||||
/** Draws the layout within the specified area.
|
||||
The position of the text within the rectangle is controlled by the justification
|
||||
flags set in the original AttributedString that was used to create this layout.
|
||||
*/
|
||||
void draw (Graphics&, Rectangle<float> area) const;
|
||||
|
||||
//==============================================================================
|
||||
/** A positioned glyph. */
|
||||
class JUCE_API Glyph
|
||||
{
|
||||
public:
|
||||
Glyph (int glyphCode, Point<float> anchor, float width) noexcept;
|
||||
Glyph (const Glyph&) noexcept;
|
||||
Glyph& operator= (const Glyph&) noexcept;
|
||||
~Glyph() noexcept;
|
||||
|
||||
/** The code number of this glyph. */
|
||||
int glyphCode;
|
||||
|
||||
/** The glyph's anchor point - this is relative to the line's origin.
|
||||
@see TextLayout::Line::lineOrigin
|
||||
*/
|
||||
Point<float> anchor;
|
||||
|
||||
float width;
|
||||
|
||||
private:
|
||||
JUCE_LEAK_DETECTOR (Glyph)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** A sequence of glyphs with a common font and colour. */
|
||||
class JUCE_API Run
|
||||
{
|
||||
public:
|
||||
Run() noexcept;
|
||||
Run (const Run&);
|
||||
Run (Range<int> stringRange, int numGlyphsToPreallocate);
|
||||
~Run() noexcept;
|
||||
|
||||
/** Returns the X position range which contains all the glyphs in this run. */
|
||||
Range<float> getRunBoundsX() const noexcept;
|
||||
|
||||
Font font; /**< The run's font. */
|
||||
Colour colour; /**< The run's colour. */
|
||||
Array<Glyph> glyphs; /**< The glyphs in this run. */
|
||||
Range<int> stringRange; /**< The character range that this run represents in the
|
||||
original string that was used to create it. */
|
||||
private:
|
||||
Run& operator= (const Run&);
|
||||
JUCE_LEAK_DETECTOR (Run)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** A line containing a sequence of glyph-runs. */
|
||||
class JUCE_API Line
|
||||
{
|
||||
public:
|
||||
Line() noexcept;
|
||||
Line (const Line&);
|
||||
Line (Range<int> stringRange, Point<float> lineOrigin,
|
||||
float ascent, float descent, float leading, int numRunsToPreallocate);
|
||||
~Line() noexcept;
|
||||
|
||||
/** Returns the X position range which contains all the glyphs in this line. */
|
||||
Range<float> getLineBoundsX() const noexcept;
|
||||
|
||||
/** Returns the Y position range which contains all the glyphs in this line. */
|
||||
Range<float> getLineBoundsY() const noexcept;
|
||||
|
||||
/** Returns the smallest rectangle which contains all the glyphs in this line. */
|
||||
Rectangle<float> getLineBounds() const noexcept;
|
||||
|
||||
OwnedArray<Run> runs; /**< The glyph-runs in this line. */
|
||||
Range<int> stringRange; /**< The character range that this line represents in the
|
||||
original string that was used to create it. */
|
||||
Point<float> lineOrigin; /**< The line's baseline origin. */
|
||||
float ascent, descent, leading;
|
||||
|
||||
private:
|
||||
Line& operator= (const Line&);
|
||||
JUCE_LEAK_DETECTOR (Line)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the maximum width of the content. */
|
||||
float getWidth() const noexcept { return width; }
|
||||
|
||||
/** Returns the maximum height of the content. */
|
||||
float getHeight() const noexcept { return height; }
|
||||
|
||||
/** Returns the number of lines in the layout. */
|
||||
int getNumLines() const noexcept { return lines.size(); }
|
||||
|
||||
/** Returns one of the lines. */
|
||||
Line& getLine (int index) const noexcept;
|
||||
|
||||
/** Adds a line to the layout. The layout will take ownership of this line object
|
||||
and will delete it when it is no longer needed. */
|
||||
void addLine (Line*);
|
||||
|
||||
/** Pre-allocates space for the specified number of lines. */
|
||||
void ensureStorageAllocated (int numLinesNeeded);
|
||||
|
||||
private:
|
||||
OwnedArray<Line> lines;
|
||||
float width, height;
|
||||
Justification justification;
|
||||
|
||||
void createStandardLayout (const AttributedString&);
|
||||
bool createNativeLayout (const AttributedString&);
|
||||
void recalculateSize();
|
||||
|
||||
JUCE_LEAK_DETECTOR (TextLayout)
|
||||
};
|
||||
|
||||
} // namespace juce
|
267
modules/juce_graphics/fonts/juce_Typeface.cpp
Normal file
267
modules/juce_graphics/fonts/juce_Typeface.cpp
Normal file
@ -0,0 +1,267 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
struct FontStyleHelpers
|
||||
{
|
||||
static const char* getStyleName (const bool bold,
|
||||
const bool italic) noexcept
|
||||
{
|
||||
if (bold && italic) return "Bold Italic";
|
||||
if (bold) return "Bold";
|
||||
if (italic) return "Italic";
|
||||
return "Regular";
|
||||
}
|
||||
|
||||
static const char* getStyleName (const int styleFlags) noexcept
|
||||
{
|
||||
return getStyleName ((styleFlags & Font::bold) != 0,
|
||||
(styleFlags & Font::italic) != 0);
|
||||
}
|
||||
|
||||
static bool isBold (const String& style) noexcept
|
||||
{
|
||||
return style.containsWholeWordIgnoreCase ("Bold");
|
||||
}
|
||||
|
||||
static bool isItalic (const String& style) noexcept
|
||||
{
|
||||
return style.containsWholeWordIgnoreCase ("Italic")
|
||||
|| style.containsWholeWordIgnoreCase ("Oblique");
|
||||
}
|
||||
|
||||
static bool isPlaceholderFamilyName (const String& family)
|
||||
{
|
||||
return family == Font::getDefaultSansSerifFontName()
|
||||
|| family == Font::getDefaultSerifFontName()
|
||||
|| family == Font::getDefaultMonospacedFontName();
|
||||
}
|
||||
|
||||
struct ConcreteFamilyNames
|
||||
{
|
||||
ConcreteFamilyNames()
|
||||
: sans (findName (Font::getDefaultSansSerifFontName())),
|
||||
serif (findName (Font::getDefaultSerifFontName())),
|
||||
mono (findName (Font::getDefaultMonospacedFontName()))
|
||||
{
|
||||
}
|
||||
|
||||
String lookUp (const String& placeholder)
|
||||
{
|
||||
if (placeholder == Font::getDefaultSansSerifFontName()) return sans;
|
||||
if (placeholder == Font::getDefaultSerifFontName()) return serif;
|
||||
if (placeholder == Font::getDefaultMonospacedFontName()) return mono;
|
||||
|
||||
return findName (placeholder);
|
||||
}
|
||||
|
||||
private:
|
||||
static String findName (const String& placeholder)
|
||||
{
|
||||
const Font f (placeholder, Font::getDefaultStyle(), 15.0f);
|
||||
return Font::getDefaultTypefaceForFont (f)->getName();
|
||||
}
|
||||
|
||||
String sans, serif, mono;
|
||||
};
|
||||
|
||||
static String getConcreteFamilyNameFromPlaceholder (const String& placeholder)
|
||||
{
|
||||
static ConcreteFamilyNames names;
|
||||
return names.lookUp (placeholder);
|
||||
}
|
||||
|
||||
static String getConcreteFamilyName (const Font& font)
|
||||
{
|
||||
const String& family = font.getTypefaceName();
|
||||
|
||||
return isPlaceholderFamilyName (family) ? getConcreteFamilyNameFromPlaceholder (family)
|
||||
: family;
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
Typeface::Typeface (const String& faceName, const String& styleName) noexcept
|
||||
: name (faceName), style (styleName)
|
||||
{
|
||||
}
|
||||
|
||||
Typeface::~Typeface()
|
||||
{
|
||||
}
|
||||
|
||||
Typeface::Ptr Typeface::getFallbackTypeface()
|
||||
{
|
||||
const Font fallbackFont (Font::getFallbackFontName(), Font::getFallbackFontStyle(), 10.0f);
|
||||
return fallbackFont.getTypeface();
|
||||
}
|
||||
|
||||
EdgeTable* Typeface::getEdgeTableForGlyph (int glyphNumber, const AffineTransform& transform, float fontHeight)
|
||||
{
|
||||
Path path;
|
||||
|
||||
if (getOutlineForGlyph (glyphNumber, path) && ! path.isEmpty())
|
||||
{
|
||||
applyVerticalHintingTransform (fontHeight, path);
|
||||
|
||||
return new EdgeTable (path.getBoundsTransformed (transform).getSmallestIntegerContainer().expanded (1, 0),
|
||||
path, transform);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct Typeface::HintingParams
|
||||
{
|
||||
HintingParams (Typeface& t)
|
||||
: cachedSize (0), top (0), middle (0), bottom (0)
|
||||
{
|
||||
Font font (&t);
|
||||
font = font.withHeight ((float) standardHeight);
|
||||
|
||||
top = getAverageY (font, "BDEFPRTZOQ", true);
|
||||
middle = getAverageY (font, "acegmnopqrsuvwxy", true);
|
||||
bottom = getAverageY (font, "BDELZOC", false);
|
||||
}
|
||||
|
||||
void applyVerticalHintingTransform (float fontSize, Path& path)
|
||||
{
|
||||
if (cachedSize != fontSize)
|
||||
{
|
||||
cachedSize = fontSize;
|
||||
cachedScale = Scaling (top, middle, bottom, fontSize);
|
||||
}
|
||||
|
||||
if (bottom < top + 3.0f / fontSize)
|
||||
return;
|
||||
|
||||
Path result;
|
||||
|
||||
for (Path::Iterator i (path); i.next();)
|
||||
{
|
||||
switch (i.elementType)
|
||||
{
|
||||
case Path::Iterator::startNewSubPath: result.startNewSubPath (i.x1, cachedScale.apply (i.y1)); break;
|
||||
case Path::Iterator::lineTo: result.lineTo (i.x1, cachedScale.apply (i.y1)); break;
|
||||
case Path::Iterator::quadraticTo: result.quadraticTo (i.x1, cachedScale.apply (i.y1),
|
||||
i.x2, cachedScale.apply (i.y2)); break;
|
||||
case Path::Iterator::cubicTo: result.cubicTo (i.x1, cachedScale.apply (i.y1),
|
||||
i.x2, cachedScale.apply (i.y2),
|
||||
i.x3, cachedScale.apply (i.y3)); break;
|
||||
case Path::Iterator::closePath: result.closeSubPath(); break;
|
||||
default: jassertfalse; break;
|
||||
}
|
||||
}
|
||||
|
||||
result.swapWithPath (path);
|
||||
}
|
||||
|
||||
private:
|
||||
struct Scaling
|
||||
{
|
||||
Scaling() noexcept : middle(), upperScale(), upperOffset(), lowerScale(), lowerOffset() {}
|
||||
|
||||
Scaling (float t, float m, float b, float fontSize) noexcept : middle (m)
|
||||
{
|
||||
const float newT = std::floor (fontSize * t + 0.5f) / fontSize;
|
||||
const float newB = std::floor (fontSize * b + 0.5f) / fontSize;
|
||||
const float newM = std::floor (fontSize * m + 0.3f) / fontSize; // this is slightly biased so that lower-case letters
|
||||
// are more likely to become taller than shorter.
|
||||
upperScale = jlimit (0.9f, 1.1f, (newM - newT) / (m - t));
|
||||
lowerScale = jlimit (0.9f, 1.1f, (newB - newM) / (b - m));
|
||||
|
||||
upperOffset = newM - m * upperScale;
|
||||
lowerOffset = newB - b * lowerScale;
|
||||
}
|
||||
|
||||
float apply (float y) const noexcept
|
||||
{
|
||||
return y < middle ? (y * upperScale + upperOffset)
|
||||
: (y * lowerScale + lowerOffset);
|
||||
}
|
||||
|
||||
float middle, upperScale, upperOffset, lowerScale, lowerOffset;
|
||||
};
|
||||
|
||||
float cachedSize;
|
||||
Scaling cachedScale;
|
||||
|
||||
static float getAverageY (const Font& font, const char* chars, bool getTop)
|
||||
{
|
||||
GlyphArrangement ga;
|
||||
ga.addLineOfText (font, chars, 0, 0);
|
||||
|
||||
Array<float> yValues;
|
||||
|
||||
for (auto& glyph : ga)
|
||||
{
|
||||
Path p;
|
||||
glyph.createPath (p);
|
||||
auto bounds = p.getBounds();
|
||||
|
||||
if (! p.isEmpty())
|
||||
yValues.add (getTop ? bounds.getY() : bounds.getBottom());
|
||||
}
|
||||
|
||||
std::sort (yValues.begin(), yValues.end());
|
||||
|
||||
auto median = yValues[yValues.size() / 2];
|
||||
float total = 0;
|
||||
int num = 0;
|
||||
|
||||
for (auto y : yValues)
|
||||
{
|
||||
if (std::abs (median - y) < 0.05f * (float) standardHeight)
|
||||
{
|
||||
total += y;
|
||||
++num;
|
||||
}
|
||||
}
|
||||
|
||||
return num < 4 ? 0.0f : total / (num * (float) standardHeight);
|
||||
}
|
||||
|
||||
enum { standardHeight = 100 };
|
||||
float top, middle, bottom;
|
||||
};
|
||||
|
||||
void Typeface::applyVerticalHintingTransform (float fontSize, Path& path)
|
||||
{
|
||||
if (fontSize > 3.0f && fontSize < 25.0f)
|
||||
{
|
||||
ScopedLock sl (hintingLock);
|
||||
|
||||
if (hintingParams == nullptr)
|
||||
hintingParams.reset (new HintingParams (*this));
|
||||
|
||||
return hintingParams->applyVerticalHintingTransform (fontSize, path);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace juce
|
163
modules/juce_graphics/fonts/juce_Typeface.h
Normal file
163
modules/juce_graphics/fonts/juce_Typeface.h
Normal file
@ -0,0 +1,163 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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 represents a size-independent font.
|
||||
|
||||
This base class is abstract, but calling createSystemTypefaceFor() will return
|
||||
a platform-specific subclass that can be used.
|
||||
|
||||
The CustomTypeface subclass allow you to build your own typeface, and to
|
||||
load and save it in the JUCE typeface format.
|
||||
|
||||
Normally you should never need to deal directly with Typeface objects - the Font
|
||||
class does everything you typically need for rendering text.
|
||||
|
||||
@see CustomTypeface, Font
|
||||
|
||||
@tags{Graphics}
|
||||
*/
|
||||
class JUCE_API Typeface : public ReferenceCountedObject
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** A handy typedef for a pointer to a typeface. */
|
||||
using Ptr = ReferenceCountedObjectPtr<Typeface>;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the font family of the typeface.
|
||||
@see Font::getTypefaceName
|
||||
*/
|
||||
const String& getName() const noexcept { return name; }
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the font style of the typeface.
|
||||
@see Font::getTypefaceStyle
|
||||
*/
|
||||
const String& getStyle() const noexcept { return style; }
|
||||
|
||||
//==============================================================================
|
||||
/** Creates a new system typeface. */
|
||||
static Ptr createSystemTypefaceFor (const Font& font);
|
||||
|
||||
/** Attempts to create a font from some raw font file data (e.g. a TTF or OTF file image).
|
||||
The system will take its own internal copy of the data, so you can free the block once
|
||||
this method has returned.
|
||||
*/
|
||||
static Ptr createSystemTypefaceFor (const void* fontFileData, size_t fontFileDataSize);
|
||||
|
||||
//==============================================================================
|
||||
/** Destructor. */
|
||||
virtual ~Typeface();
|
||||
|
||||
/** Returns true if this typeface can be used to render the specified font.
|
||||
When called, the font will already have been checked to make sure that its name and
|
||||
style flags match the typeface.
|
||||
*/
|
||||
virtual bool isSuitableForFont (const Font&) const { return true; }
|
||||
|
||||
/** Returns the ascent of the font, as a proportion of its height.
|
||||
The height is considered to always be normalised as 1.0, so this will be a
|
||||
value less that 1.0, indicating the proportion of the font that lies above
|
||||
its baseline.
|
||||
*/
|
||||
virtual float getAscent() const = 0;
|
||||
|
||||
/** Returns the descent of the font, as a proportion of its height.
|
||||
The height is considered to always be normalised as 1.0, so this will be a
|
||||
value less that 1.0, indicating the proportion of the font that lies below
|
||||
its baseline.
|
||||
*/
|
||||
virtual float getDescent() const = 0;
|
||||
|
||||
/** Returns the value by which you should multiply a JUCE font-height value to
|
||||
convert it to the equivalent point-size.
|
||||
*/
|
||||
virtual float getHeightToPointsFactor() const = 0;
|
||||
|
||||
/** Measures the width of a line of text.
|
||||
The distance returned is based on the font having an normalised height of 1.0.
|
||||
You should never need to call this directly! Use Font::getStringWidth() instead!
|
||||
*/
|
||||
virtual float getStringWidth (const String& text) = 0;
|
||||
|
||||
/** Converts a line of text into its glyph numbers and their positions.
|
||||
The distances returned are based on the font having an normalised height of 1.0.
|
||||
You should never need to call this directly! Use Font::getGlyphPositions() instead!
|
||||
*/
|
||||
virtual void getGlyphPositions (const String& text, Array<int>& glyphs, Array<float>& xOffsets) = 0;
|
||||
|
||||
/** Returns the outline for a glyph.
|
||||
The path returned will be normalised to a font height of 1.0.
|
||||
*/
|
||||
virtual bool getOutlineForGlyph (int glyphNumber, Path& path) = 0;
|
||||
|
||||
/** Returns a new EdgeTable that contains the path for the givem glyph, with the specified transform applied. */
|
||||
virtual EdgeTable* getEdgeTableForGlyph (int glyphNumber, const AffineTransform& transform, float fontHeight);
|
||||
|
||||
/** Returns true if the typeface uses hinting. */
|
||||
virtual bool isHinted() const { return false; }
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the number of fonts that are cached in memory. */
|
||||
static void setTypefaceCacheSize (int numFontsToCache);
|
||||
|
||||
/** Clears any fonts that are currently cached in memory. */
|
||||
static void clearTypefaceCache();
|
||||
|
||||
/** On some platforms, this allows a specific path to be scanned.
|
||||
Currently only available when using FreeType.
|
||||
*/
|
||||
static void scanFolderForFonts (const File& folder);
|
||||
|
||||
/** Makes an attempt at performing a good overall distortion that will scale a font of
|
||||
the given size to align vertically with the pixel grid. The path should be an unscaled
|
||||
(i.e. normalised to height of 1.0) path for a glyph.
|
||||
*/
|
||||
void applyVerticalHintingTransform (float fontHeight, Path& path);
|
||||
|
||||
protected:
|
||||
//==============================================================================
|
||||
String name, style;
|
||||
|
||||
Typeface (const String& name, const String& style) noexcept;
|
||||
|
||||
static Ptr getFallbackTypeface();
|
||||
|
||||
private:
|
||||
struct HintingParams;
|
||||
friend struct ContainerDeletePolicy<HintingParams>;
|
||||
std::unique_ptr<HintingParams> hintingParams;
|
||||
CriticalSection hintingLock;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Typeface)
|
||||
};
|
||||
|
||||
} // namespace juce
|
Reference in New Issue
Block a user