git subrepo clone --branch=sono6good https://github.com/essej/JUCE.git deps/juce
subrepo: subdir: "deps/juce" merged: "b13f9084e" upstream: origin: "https://github.com/essej/JUCE.git" branch: "sono6good" commit: "b13f9084e" git-subrepo: version: "0.4.3" origin: "https://github.com/ingydotnet/git-subrepo.git" commit: "2f68596"
This commit is contained in:
254
deps/juce/modules/juce_graphics/fonts/juce_AttributedString.cpp
vendored
Normal file
254
deps/juce/modules/juce_graphics/fonts/juce_AttributedString.cpp
vendored
Normal file
@ -0,0 +1,254 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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 auto& att = atts.getUnchecked (i);
|
||||
auto offset = position - att.range.getStart();
|
||||
|
||||
if (offset >= 0)
|
||||
{
|
||||
if (offset > 0 && position < att.range.getEnd())
|
||||
{
|
||||
atts.insert (i + 1, AttributedString::Attribute (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 ({ 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;)
|
||||
{
|
||||
auto& a1 = atts.getReference (i);
|
||||
auto& 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 ({ Range<int> (0, length), f != nullptr ? *f : Font(), c != nullptr ? *c : Colour (0xff000000) });
|
||||
}
|
||||
else
|
||||
{
|
||||
auto start = getLength (atts);
|
||||
atts.add ({ 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 (auto& att : atts)
|
||||
{
|
||||
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 (Range<int> r, const Font& f, Colour c) noexcept
|
||||
: range (r), font (f), colour (c)
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void AttributedString::setText (const String& newText)
|
||||
{
|
||||
auto newLength = newText.length();
|
||||
auto 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)
|
||||
{
|
||||
auto originalLength = getLength (attributes);
|
||||
auto originalNumAtts = attributes.size();
|
||||
text += other.text;
|
||||
attributes.addArray (other.attributes);
|
||||
|
||||
for (auto 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 ({ 0, getLength (attributes) }, colour);
|
||||
}
|
||||
|
||||
void AttributedString::setFont (const Font& font)
|
||||
{
|
||||
setFont ({ 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
|
205
deps/juce/modules/juce_graphics/fonts/juce_AttributedString.h
vendored
Normal file
205
deps/juce/modules/juce_graphics/fonts/juce_AttributedString.h
vendored
Normal file
@ -0,0 +1,205 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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() = default;
|
||||
|
||||
/** Creates an attributed string with the given text. */
|
||||
explicit AttributedString (const String& newString) { setText (newString); }
|
||||
|
||||
AttributedString (const AttributedString&) = default;
|
||||
AttributedString& operator= (const AttributedString&) = default;
|
||||
AttributedString (AttributedString&&) noexcept = default;
|
||||
AttributedString& operator= (AttributedString&&) noexcept = default;
|
||||
|
||||
//==============================================================================
|
||||
/** 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() = default;
|
||||
|
||||
Attribute (const Attribute&) = default;
|
||||
Attribute& operator= (const Attribute&) = default;
|
||||
Attribute (Attribute&&) noexcept = default;
|
||||
Attribute& operator= (Attribute&&) noexcept = default;
|
||||
|
||||
/** 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 { 0xff000000 };
|
||||
|
||||
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 = 0.0f;
|
||||
Justification justification = Justification::left;
|
||||
WordWrap wordWrap = AttributedString::byWord;
|
||||
ReadingDirection readingDirection = AttributedString::natural;
|
||||
Array<Attribute> attributes;
|
||||
|
||||
JUCE_LEAK_DETECTOR (AttributedString)
|
||||
};
|
||||
|
||||
} // namespace juce
|
398
deps/juce/modules/juce_graphics/fonts/juce_CustomTypeface.cpp
vendored
Normal file
398
deps/juce/modules/juce_graphics/fonts/juce_CustomTypeface.cpp
vendored
Normal file
@ -0,0 +1,398 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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.get() != 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.get() != 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.get() != 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.get() != this)
|
||||
return fallbackTypeface->getEdgeTableForGlyph (glyphNumber, transform, fontHeight);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace juce
|
165
deps/juce/modules/juce_graphics/fonts/juce_CustomTypeface.h
vendored
Normal file
165
deps/juce/modules/juce_graphics/fonts/juce_CustomTypeface.h
vendored
Normal file
@ -0,0 +1,165 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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() override;
|
||||
|
||||
//==============================================================================
|
||||
/** 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;
|
||||
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
|
822
deps/juce/modules/juce_graphics/fonts/juce_Font.cpp
vendored
Normal file
822
deps/juce/modules/juce_graphics/fonts/juce_Font.cpp
vendored
Normal file
@ -0,0 +1,822 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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;
|
||||
}
|
||||
|
||||
using GetTypefaceForFont = Typeface::Ptr (*)(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 auto faceName = font.getTypefaceName();
|
||||
const auto faceStyle = font.getTypefaceStyle();
|
||||
|
||||
jassert (faceName.isNotEmpty());
|
||||
|
||||
{
|
||||
const ScopedReadLock slr (lock);
|
||||
|
||||
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 getDefaultFace() const noexcept
|
||||
{
|
||||
const ScopedReadLock slr (lock);
|
||||
return 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;
|
||||
};
|
||||
|
||||
Typeface::Ptr defaultFace;
|
||||
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()->getDefaultFace()),
|
||||
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()->getDefaultFace();
|
||||
}
|
||||
|
||||
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()->getDefaultFace();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/* The typeface and ascent data members may be read/set from multiple threads
|
||||
simultaneously, e.g. in the case that two Font instances reference the same
|
||||
SharedFontInternal and call getTypefacePtr() simultaneously.
|
||||
|
||||
We lock in functions that modify the typeface or ascent in order to
|
||||
ensure thread safety.
|
||||
*/
|
||||
|
||||
Typeface::Ptr getTypefacePtr (const Font& f)
|
||||
{
|
||||
const ScopedLock lock (mutex);
|
||||
|
||||
if (typeface == nullptr)
|
||||
{
|
||||
typeface = TypefaceCache::getInstance()->findTypefaceFor (f);
|
||||
jassert (typeface != nullptr);
|
||||
}
|
||||
|
||||
return typeface;
|
||||
}
|
||||
|
||||
void checkTypefaceSuitability (const Font& f)
|
||||
{
|
||||
const ScopedLock lock (mutex);
|
||||
|
||||
if (typeface != nullptr && ! typeface->isSuitableForFont (f))
|
||||
typeface = nullptr;
|
||||
}
|
||||
|
||||
float getAscent (const Font& f)
|
||||
{
|
||||
const ScopedLock lock (mutex);
|
||||
|
||||
if (ascent == 0.0f)
|
||||
ascent = getTypefacePtr (f)->getAscent();
|
||||
|
||||
return height * ascent;
|
||||
}
|
||||
|
||||
/* We do not need to lock in these functions, as it's guaranteed
|
||||
that these data members can only change if there is a single Font
|
||||
instance referencing the shared state.
|
||||
*/
|
||||
|
||||
String getTypefaceName() const { return typefaceName; }
|
||||
String getTypefaceStyle() const { return typefaceStyle; }
|
||||
float getHeight() const { return height; }
|
||||
float getHorizontalScale() const { return horizontalScale; }
|
||||
float getKerning() const { return kerning; }
|
||||
bool getUnderline() const { return underline; }
|
||||
|
||||
/* This shared state may be shared between two or more Font instances that are being
|
||||
read/modified from multiple threads.
|
||||
Before modifying a shared instance you *must* call dupeInternalIfShared to
|
||||
ensure that only one Font instance is pointing to the SharedFontInternal instance
|
||||
during the modification.
|
||||
*/
|
||||
|
||||
void setTypeface (Typeface::Ptr x)
|
||||
{
|
||||
jassert (getReferenceCount() == 1);
|
||||
typeface = std::move (x);
|
||||
}
|
||||
|
||||
void setTypefaceName (String x)
|
||||
{
|
||||
jassert (getReferenceCount() == 1);
|
||||
typefaceName = std::move (x);
|
||||
}
|
||||
|
||||
void setTypefaceStyle (String x)
|
||||
{
|
||||
jassert (getReferenceCount() == 1);
|
||||
typefaceStyle = std::move (x);
|
||||
}
|
||||
|
||||
void setHeight (float x)
|
||||
{
|
||||
jassert (getReferenceCount() == 1);
|
||||
height = x;
|
||||
}
|
||||
|
||||
void setHorizontalScale (float x)
|
||||
{
|
||||
jassert (getReferenceCount() == 1);
|
||||
horizontalScale = x;
|
||||
}
|
||||
|
||||
void setKerning (float x)
|
||||
{
|
||||
jassert (getReferenceCount() == 1);
|
||||
kerning = x;
|
||||
}
|
||||
|
||||
void setAscent (float x)
|
||||
{
|
||||
jassert (getReferenceCount() == 1);
|
||||
ascent = x;
|
||||
}
|
||||
|
||||
void setUnderline (bool x)
|
||||
{
|
||||
jassert (getReferenceCount() == 1);
|
||||
underline = x;
|
||||
}
|
||||
|
||||
private:
|
||||
Typeface::Ptr typeface;
|
||||
String typefaceName, typefaceStyle;
|
||||
float height = 0.0f, horizontalScale = 1.0f, kerning = 0.0f, ascent = 0.0f;
|
||||
bool underline = false;
|
||||
|
||||
CriticalSection mutex;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
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 (std::move (other.font))
|
||||
{
|
||||
}
|
||||
|
||||
Font& Font::operator= (Font&& other) noexcept
|
||||
{
|
||||
font = std::move (other.font);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Font::~Font() noexcept = default;
|
||||
|
||||
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()
|
||||
{
|
||||
font->checkTypefaceSuitability (*this);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
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; }
|
||||
|
||||
String Font::getTypefaceName() const noexcept { return font->getTypefaceName(); }
|
||||
String Font::getTypefaceStyle() const noexcept { return font->getTypefaceStyle(); }
|
||||
|
||||
void Font::setTypefaceName (const String& faceName)
|
||||
{
|
||||
if (faceName != font->getTypefaceName())
|
||||
{
|
||||
jassert (faceName.isNotEmpty());
|
||||
|
||||
dupeInternalIfShared();
|
||||
font->setTypefaceName (faceName);
|
||||
font->setTypeface (nullptr);
|
||||
font->setAscent (0);
|
||||
}
|
||||
}
|
||||
|
||||
void Font::setTypefaceStyle (const String& typefaceStyle)
|
||||
{
|
||||
if (typefaceStyle != font->getTypefaceStyle())
|
||||
{
|
||||
dupeInternalIfShared();
|
||||
font->setTypefaceStyle (typefaceStyle);
|
||||
font->setTypeface (nullptr);
|
||||
font->setAscent (0);
|
||||
}
|
||||
}
|
||||
|
||||
Font Font::withTypefaceStyle (const String& newStyle) const
|
||||
{
|
||||
Font f (*this);
|
||||
f.setTypefaceStyle (newStyle);
|
||||
return f;
|
||||
}
|
||||
|
||||
StringArray Font::getAvailableStyles() const
|
||||
{
|
||||
return findAllTypefaceStyles (getTypefacePtr()->getName());
|
||||
}
|
||||
|
||||
Typeface::Ptr Font::getTypefacePtr() const
|
||||
{
|
||||
return font->getTypefacePtr (*this);
|
||||
}
|
||||
|
||||
Typeface* Font::getTypeface() const
|
||||
{
|
||||
return getTypefacePtr().get();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
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 getTypefacePtr()->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->getHeight() != newHeight)
|
||||
{
|
||||
dupeInternalIfShared();
|
||||
font->setHeight (newHeight);
|
||||
checkTypefaceSuitability();
|
||||
}
|
||||
}
|
||||
|
||||
void Font::setHeightWithoutChangingWidth (float newHeight)
|
||||
{
|
||||
newHeight = FontValues::limitFontHeight (newHeight);
|
||||
|
||||
if (font->getHeight() != newHeight)
|
||||
{
|
||||
dupeInternalIfShared();
|
||||
font->setHorizontalScale (font->getHorizontalScale() * (font->getHeight() / newHeight));
|
||||
font->setHeight (newHeight);
|
||||
checkTypefaceSuitability();
|
||||
}
|
||||
}
|
||||
|
||||
int Font::getStyleFlags() const noexcept
|
||||
{
|
||||
int styleFlags = font->getUnderline() ? 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->setTypeface (nullptr);
|
||||
font->setTypefaceStyle (FontStyleHelpers::getStyleName (newFlags));
|
||||
font->setUnderline ((newFlags & underlined) != 0);
|
||||
font->setAscent (0);
|
||||
}
|
||||
}
|
||||
|
||||
void Font::setSizeAndStyle (float newHeight,
|
||||
const int newStyleFlags,
|
||||
const float newHorizontalScale,
|
||||
const float newKerningAmount)
|
||||
{
|
||||
newHeight = FontValues::limitFontHeight (newHeight);
|
||||
|
||||
if (font->getHeight() != newHeight
|
||||
|| font->getHorizontalScale() != newHorizontalScale
|
||||
|| font->getKerning() != newKerningAmount)
|
||||
{
|
||||
dupeInternalIfShared();
|
||||
font->setHeight (newHeight);
|
||||
font->setHorizontalScale (newHorizontalScale);
|
||||
font->setKerning (newKerningAmount);
|
||||
checkTypefaceSuitability();
|
||||
}
|
||||
|
||||
setStyleFlags (newStyleFlags);
|
||||
}
|
||||
|
||||
void Font::setSizeAndStyle (float newHeight,
|
||||
const String& newStyle,
|
||||
const float newHorizontalScale,
|
||||
const float newKerningAmount)
|
||||
{
|
||||
newHeight = FontValues::limitFontHeight (newHeight);
|
||||
|
||||
if (font->getHeight() != newHeight
|
||||
|| font->getHorizontalScale() != newHorizontalScale
|
||||
|| font->getKerning() != newKerningAmount)
|
||||
{
|
||||
dupeInternalIfShared();
|
||||
font->setHeight (newHeight);
|
||||
font->setHorizontalScale (newHorizontalScale);
|
||||
font->setKerning (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->setHorizontalScale (scaleFactor);
|
||||
checkTypefaceSuitability();
|
||||
}
|
||||
|
||||
float Font::getHorizontalScale() const noexcept
|
||||
{
|
||||
return font->getHorizontalScale();
|
||||
}
|
||||
|
||||
float Font::getExtraKerningFactor() const noexcept
|
||||
{
|
||||
return font->getKerning();
|
||||
}
|
||||
|
||||
Font Font::withExtraKerningFactor (const float extraKerning) const
|
||||
{
|
||||
Font f (*this);
|
||||
f.setExtraKerningFactor (extraKerning);
|
||||
return f;
|
||||
}
|
||||
|
||||
void Font::setExtraKerningFactor (const float extraKerning)
|
||||
{
|
||||
dupeInternalIfShared();
|
||||
font->setKerning (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->getTypefaceStyle()); }
|
||||
bool Font::isItalic() const noexcept { return FontStyleHelpers::isItalic (font->getTypefaceStyle()); }
|
||||
bool Font::isUnderlined() const noexcept { return font->getUnderline(); }
|
||||
|
||||
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->setUnderline (shouldBeUnderlined);
|
||||
checkTypefaceSuitability();
|
||||
}
|
||||
|
||||
float Font::getAscent() const
|
||||
{
|
||||
return font->getAscent (*this);
|
||||
}
|
||||
|
||||
float Font::getHeight() const noexcept { return font->getHeight(); }
|
||||
float Font::getDescent() const { return font->getHeight() - 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
|
||||
{
|
||||
auto w = getTypefacePtr()->getStringWidth (text);
|
||||
|
||||
if (font->getKerning() != 0.0f)
|
||||
w += font->getKerning() * (float) text.length();
|
||||
|
||||
return w * font->getHeight() * font->getHorizontalScale();
|
||||
}
|
||||
|
||||
void Font::getGlyphPositions (const String& text, Array<int>& glyphs, Array<float>& xOffsets) const
|
||||
{
|
||||
getTypefacePtr()->getGlyphPositions (text, glyphs, xOffsets);
|
||||
|
||||
if (auto num = xOffsets.size())
|
||||
{
|
||||
auto scale = font->getHeight() * font->getHorizontalScale();
|
||||
auto* x = xOffsets.getRawDataPointer();
|
||||
|
||||
if (font->getKerning() != 0.0f)
|
||||
{
|
||||
for (int i = 0; i < num; ++i)
|
||||
x[i] = (x[i] + (float) i * font->getKerning()) * 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
|
483
deps/juce/modules/juce_graphics/fonts/juce_Font.h
vendored
Normal file
483
deps/juce/modules/juce_graphics/fonts/juce_Font.h
vendored
Normal file
@ -0,0 +1,483 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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::getTypefacePtr()->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 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::getTypefacePtr()->getName(), which will give you the platform-specific font family.
|
||||
*/
|
||||
String getTypefaceName() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the font style of the typeface that this font uses.
|
||||
@see withTypefaceStyle, getAvailableStyles()
|
||||
*/
|
||||
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;
|
||||
|
||||
//==============================================================================
|
||||
#ifndef DOXYGEN
|
||||
/** 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.
|
||||
*/
|
||||
[[deprecated ("This method is unsafe, use getTypefacePtr() instead.")]]
|
||||
Typeface* getTypeface() const;
|
||||
#endif
|
||||
|
||||
/** Returns the typeface used by this font. */
|
||||
Typeface::Ptr getTypefacePtr() 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
|
757
deps/juce/modules/juce_graphics/fonts/juce_GlyphArrangement.cpp
vendored
Normal file
757
deps/juce/modules/juce_graphics/fonts/juce_GlyphArrangement.cpp
vendored
Normal file
@ -0,0 +1,757 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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() {}
|
||||
|
||||
static 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.getTypefacePtr())
|
||||
{
|
||||
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.getTypefacePtr())
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
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,
|
||||
float leading)
|
||||
{
|
||||
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() + leading;
|
||||
}
|
||||
}
|
||||
|
||||
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 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 ((float) 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 / (float) 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
|
328
deps/juce/modules/juce_graphics/fonts/juce_GlyphArrangement.h
vendored
Normal file
328
deps/juce/modules/juce_graphics/fonts/juce_GlyphArrangement.h
vendored
Normal file
@ -0,0 +1,328 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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;
|
||||
PositionedGlyph (PositionedGlyph&&) noexcept = default;
|
||||
PositionedGlyph& operator= (PositionedGlyph&&) noexcept = default;
|
||||
|
||||
~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;
|
||||
GlyphArrangement (GlyphArrangement&&) = default;
|
||||
GlyphArrangement& operator= (GlyphArrangement&&) = default;
|
||||
|
||||
/** Destructor. */
|
||||
~GlyphArrangement() = default;
|
||||
|
||||
//==============================================================================
|
||||
/** 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() + leading.
|
||||
*/
|
||||
void addJustifiedText (const Font& font,
|
||||
const String& text,
|
||||
float x, float y,
|
||||
float maxLineWidth,
|
||||
Justification horizontalLayout,
|
||||
float leading = 0.0f);
|
||||
|
||||
/** 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's 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
|
599
deps/juce/modules/juce_graphics/fonts/juce_TextLayout.cpp
vendored
Normal file
599
deps/juce/modules/juce_graphics/fonts/juce_TextLayout.cpp
vendored
Normal file
@ -0,0 +1,599 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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
|
||||
{
|
||||
|
||||
static String substring (const String& text, Range<int> range)
|
||||
{
|
||||
return text.substring (range.getStart(), range.getEnd());
|
||||
}
|
||||
|
||||
TextLayout::Glyph::Glyph (int glyph, Point<float> anch, float w) noexcept
|
||||
: glyphCode (glyph), anchor (anch), width (w)
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
TextLayout::Run::Run (Range<int> range, int numGlyphsToPreallocate)
|
||||
: stringRange (range)
|
||||
{
|
||||
glyphs.ensureStorageAllocated (numGlyphsToPreallocate);
|
||||
}
|
||||
|
||||
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 (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& TextLayout::Line::operator= (const Line& other)
|
||||
{
|
||||
auto copy = other;
|
||||
swap (copy);
|
||||
return *this;
|
||||
}
|
||||
|
||||
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() };
|
||||
}
|
||||
|
||||
void TextLayout::Line::swap (Line& other) noexcept
|
||||
{
|
||||
std::swap (other.runs, runs);
|
||||
std::swap (other.stringRange, stringRange);
|
||||
std::swap (other.lineOrigin, lineOrigin);
|
||||
std::swap (other.ascent, ascent);
|
||||
std::swap (other.descent, descent);
|
||||
std::swap (other.leading, leading);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
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 (std::move (other.lines)),
|
||||
width (other.width), height (other.height),
|
||||
justification (other.justification)
|
||||
{
|
||||
}
|
||||
|
||||
TextLayout& TextLayout::operator= (TextLayout&& other) noexcept
|
||||
{
|
||||
lines = std::move (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 (std::unique_ptr<Line> line)
|
||||
{
|
||||
lines.add (line.release());
|
||||
}
|
||||
|
||||
void TextLayout::draw (Graphics& g, Rectangle<float> area) const
|
||||
{
|
||||
auto origin = justification.appliedToRectangle (Rectangle<float> (width, getHeight()), area).getPosition();
|
||||
|
||||
auto& context = g.getInternalContext();
|
||||
context.saveState();
|
||||
|
||||
auto clip = context.getClipBounds();
|
||||
auto clipTop = (float) clip.getY() - origin.y;
|
||||
auto clipBottom = (float) clip.getBottom() - origin.y;
|
||||
|
||||
for (auto& line : *this)
|
||||
{
|
||||
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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context.restoreState();
|
||||
}
|
||||
|
||||
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 = std::make_unique<TextLayout::Run>();
|
||||
if (currentLine == nullptr) currentLine = std::make_unique<TextLayout::Line>();
|
||||
|
||||
const auto numGlyphs = newGlyphs.size();
|
||||
charPosition += numGlyphs;
|
||||
|
||||
if (numGlyphs > 0
|
||||
&& (! (t.isWhitespace || t.isNewLine) || needToSetLineOrigin))
|
||||
{
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
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 = std::make_unique<TextLayout::Run>();
|
||||
|
||||
addRun (*currentLine, currentRun.release(), t, runStartPosition, charPosition);
|
||||
currentLine->stringRange = { lineStartPosition, charPosition };
|
||||
|
||||
if (! needToSetLineOrigin)
|
||||
layout.addLine (std::move (currentLine));
|
||||
|
||||
runStartPosition = charPosition;
|
||||
lineStartPosition = charPosition;
|
||||
needToSetLineOrigin = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
addRun (*currentLine, currentRun.release(), t, runStartPosition, charPosition);
|
||||
currentLine->stringRange = { lineStartPosition, charPosition };
|
||||
|
||||
if (! needToSetLineOrigin)
|
||||
layout.addLine (std::move (currentLine));
|
||||
|
||||
needToSetLineOrigin = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ((text.getJustification().getFlags() & (Justification::right | Justification::horizontallyCentred)) != 0)
|
||||
{
|
||||
auto totalW = layout.getWidth();
|
||||
bool isCentred = (text.getJustification().getFlags() & Justification::horizontallyCentred) != 0;
|
||||
|
||||
for (auto& line : layout)
|
||||
{
|
||||
auto dx = totalW - line.getLineBoundsX().getLength();
|
||||
|
||||
if (isCentred)
|
||||
dx /= 2.0f;
|
||||
|
||||
line.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 (substring (text.getText(), attr.range),
|
||||
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
|
270
deps/juce/modules/juce_graphics/fonts/juce_TextLayout.h
vendored
Normal file
270
deps/juce/modules/juce_graphics/fonts/juce_TextLayout.h
vendored
Normal file
@ -0,0 +1,270 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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
|
||||
{
|
||||
private:
|
||||
template <typename Iterator>
|
||||
class DereferencingIterator
|
||||
{
|
||||
public:
|
||||
using value_type = typename std::remove_reference<decltype(**std::declval<Iterator>())>::type;
|
||||
using difference_type = typename std::iterator_traits<Iterator>::difference_type;
|
||||
using pointer = value_type*;
|
||||
using reference = value_type&;
|
||||
using iterator_category = typename std::iterator_traits<Iterator>::iterator_category;
|
||||
|
||||
explicit DereferencingIterator (Iterator in) : iterator (std::move (in)) {}
|
||||
|
||||
DereferencingIterator& operator+= (difference_type distance)
|
||||
{
|
||||
iterator += distance;
|
||||
return *this;
|
||||
}
|
||||
|
||||
friend DereferencingIterator operator+ (DereferencingIterator i, difference_type d) { return i += d; }
|
||||
friend DereferencingIterator operator+ (difference_type d, DereferencingIterator i) { return i += d; }
|
||||
|
||||
DereferencingIterator& operator-= (difference_type distance)
|
||||
{
|
||||
iterator -= distance;
|
||||
return *this;
|
||||
}
|
||||
|
||||
friend DereferencingIterator operator- (DereferencingIterator i, difference_type d) { return i -= d; }
|
||||
|
||||
friend difference_type operator- (DereferencingIterator a, DereferencingIterator b) { return a.iterator - b.iterator; }
|
||||
|
||||
reference operator[] (difference_type d) const { return *iterator[d]; }
|
||||
|
||||
friend bool operator< (DereferencingIterator a, DereferencingIterator b) { return a.iterator < b.iterator; }
|
||||
friend bool operator<= (DereferencingIterator a, DereferencingIterator b) { return a.iterator <= b.iterator; }
|
||||
friend bool operator> (DereferencingIterator a, DereferencingIterator b) { return a.iterator > b.iterator; }
|
||||
friend bool operator>= (DereferencingIterator a, DereferencingIterator b) { return a.iterator >= b.iterator; }
|
||||
friend bool operator== (DereferencingIterator a, DereferencingIterator b) { return a.iterator == b.iterator; }
|
||||
friend bool operator!= (DereferencingIterator a, DereferencingIterator b) { return a.iterator != b.iterator; }
|
||||
|
||||
DereferencingIterator& operator++() { ++iterator; return *this; }
|
||||
DereferencingIterator& operator--() { --iterator; return *this; }
|
||||
DereferencingIterator operator++ (int) const { DereferencingIterator copy (*this); ++(*this); return copy; }
|
||||
DereferencingIterator operator-- (int) const { DereferencingIterator copy (*this); --(*this); return copy; }
|
||||
|
||||
reference operator* () const { return **iterator; }
|
||||
pointer operator->() const { return *iterator; }
|
||||
|
||||
private:
|
||||
Iterator iterator;
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
/** 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() = default;
|
||||
Run (Range<int> stringRange, int numGlyphsToPreallocate);
|
||||
|
||||
/** 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 { 0xff000000 }; /**< 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:
|
||||
JUCE_LEAK_DETECTOR (Run)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** A line containing a sequence of glyph-runs. */
|
||||
class JUCE_API Line
|
||||
{
|
||||
public:
|
||||
Line() = default;
|
||||
Line (Range<int> stringRange, Point<float> lineOrigin,
|
||||
float ascent, float descent, float leading, int numRunsToPreallocate);
|
||||
|
||||
Line (const Line&);
|
||||
Line& operator= (const Line&);
|
||||
|
||||
Line (Line&&) noexcept = default;
|
||||
Line& operator= (Line&&) noexcept = default;
|
||||
|
||||
~Line() noexcept = default;
|
||||
|
||||
/** 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;
|
||||
|
||||
void swap (Line& other) 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 = 0.0f, descent = 0.0f, leading = 0.0f;
|
||||
|
||||
private:
|
||||
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 (std::unique_ptr<Line>);
|
||||
|
||||
/** Pre-allocates space for the specified number of lines. */
|
||||
void ensureStorageAllocated (int numLinesNeeded);
|
||||
|
||||
using iterator = DereferencingIterator< Line* const*>;
|
||||
using const_iterator = DereferencingIterator<const Line* const*>;
|
||||
|
||||
/** Returns an iterator over the lines of content */
|
||||
iterator begin() { return iterator (lines.begin()); }
|
||||
const_iterator begin() const { return const_iterator (lines.begin()); }
|
||||
const_iterator cbegin() const { return const_iterator (lines.begin()); }
|
||||
|
||||
/** Returns an iterator over the lines of content */
|
||||
iterator end() { return iterator (lines.end()); }
|
||||
const_iterator end() const { return const_iterator (lines.end()); }
|
||||
const_iterator cend() const { return const_iterator (lines.end()); }
|
||||
|
||||
/** If you modify the TextLayout after creating it, call this to compute
|
||||
the new dimensions of the content.
|
||||
*/
|
||||
void recalculateSize();
|
||||
|
||||
private:
|
||||
OwnedArray<Line> lines;
|
||||
float width, height;
|
||||
Justification justification;
|
||||
|
||||
void createStandardLayout (const AttributedString&);
|
||||
bool createNativeLayout (const AttributedString&);
|
||||
|
||||
JUCE_LEAK_DETECTOR (TextLayout)
|
||||
};
|
||||
|
||||
} // namespace juce
|
263
deps/juce/modules/juce_graphics/fonts/juce_Typeface.cpp
vendored
Normal file
263
deps/juce/modules/juce_graphics/fonts/juce_Typeface.cpp
vendored
Normal file
@ -0,0 +1,263 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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() = default;
|
||||
|
||||
Typeface::Ptr Typeface::getFallbackTypeface()
|
||||
{
|
||||
const Font fallbackFont (Font::getFallbackFontName(), Font::getFallbackFontStyle(), 10.0f);
|
||||
return fallbackFont.getTypefacePtr();
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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 = 0;
|
||||
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 / ((float) num * (float) standardHeight);
|
||||
}
|
||||
|
||||
enum { standardHeight = 100 };
|
||||
float top = 0, middle = 0, bottom = 0;
|
||||
};
|
||||
|
||||
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
|
161
deps/juce/modules/juce_graphics/fonts/juce_Typeface.h
vendored
Normal file
161
deps/juce/modules/juce_graphics/fonts/juce_Typeface.h
vendored
Normal file
@ -0,0 +1,161 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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. */
|
||||
~Typeface() override;
|
||||
|
||||
/** 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 given 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.
|
||||
On macOS you can load .ttf and .otf files, otherwise this is 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;
|
||||
std::unique_ptr<HintingParams> hintingParams;
|
||||
CriticalSection hintingLock;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Typeface)
|
||||
};
|
||||
|
||||
} // namespace juce
|
Reference in New Issue
Block a user