mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-14 00:14:18 +00:00
Added Animated App template and examples
This commit is contained in:
parent
fefcf7aca6
commit
ff6520a89a
1141 changed files with 438491 additions and 94 deletions
|
|
@ -0,0 +1,232 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
AttributedString::Attribute::Attribute (Range<int> range_, Colour colour_)
|
||||
: range (range_), colour (new Colour (colour_))
|
||||
{
|
||||
}
|
||||
|
||||
AttributedString::Attribute::Attribute (Range<int> range_, const Font& font_)
|
||||
: range (range_), font (new Font (font_))
|
||||
{
|
||||
}
|
||||
|
||||
AttributedString::Attribute::Attribute (const Attribute& other)
|
||||
: range (other.range),
|
||||
font (other.font.createCopy()),
|
||||
colour (other.colour.createCopy())
|
||||
{
|
||||
}
|
||||
|
||||
AttributedString::Attribute::Attribute (const Attribute& other, const int offset)
|
||||
: range (other.range + offset),
|
||||
font (other.font.createCopy()),
|
||||
colour (other.colour.createCopy())
|
||||
{
|
||||
}
|
||||
|
||||
AttributedString::Attribute::~Attribute() {}
|
||||
|
||||
//==============================================================================
|
||||
AttributedString::AttributedString()
|
||||
: lineSpacing (0.0f),
|
||||
justification (Justification::left),
|
||||
wordWrap (AttributedString::byWord),
|
||||
readingDirection (AttributedString::natural)
|
||||
{
|
||||
}
|
||||
|
||||
AttributedString::AttributedString (const String& newString)
|
||||
: text (newString),
|
||||
lineSpacing (0.0f),
|
||||
justification (Justification::left),
|
||||
wordWrap (AttributedString::byWord),
|
||||
readingDirection (AttributedString::natural)
|
||||
{
|
||||
}
|
||||
|
||||
AttributedString::AttributedString (const AttributedString& other)
|
||||
: text (other.text),
|
||||
lineSpacing (other.lineSpacing),
|
||||
justification (other.justification),
|
||||
wordWrap (other.wordWrap),
|
||||
readingDirection (other.readingDirection)
|
||||
{
|
||||
attributes.addCopiesOf (other.attributes);
|
||||
}
|
||||
|
||||
AttributedString& AttributedString::operator= (const AttributedString& other)
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
text = other.text;
|
||||
lineSpacing = other.lineSpacing;
|
||||
justification = other.justification;
|
||||
wordWrap = other.wordWrap;
|
||||
readingDirection = other.readingDirection;
|
||||
attributes.clear();
|
||||
attributes.addCopiesOf (other.attributes);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS
|
||||
AttributedString::AttributedString (AttributedString&& other) noexcept
|
||||
: text (static_cast <String&&> (other.text)),
|
||||
lineSpacing (other.lineSpacing),
|
||||
justification (other.justification),
|
||||
wordWrap (other.wordWrap),
|
||||
readingDirection (other.readingDirection),
|
||||
attributes (static_cast <OwnedArray<Attribute>&&> (other.attributes))
|
||||
{
|
||||
}
|
||||
|
||||
AttributedString& AttributedString::operator= (AttributedString&& other) noexcept
|
||||
{
|
||||
text = static_cast <String&&> (other.text);
|
||||
lineSpacing = other.lineSpacing;
|
||||
justification = other.justification;
|
||||
wordWrap = other.wordWrap;
|
||||
readingDirection = other.readingDirection;
|
||||
attributes = static_cast <OwnedArray<Attribute>&&> (other.attributes);
|
||||
return *this;
|
||||
}
|
||||
#endif
|
||||
|
||||
AttributedString::~AttributedString() {}
|
||||
|
||||
void AttributedString::setText (const String& other)
|
||||
{
|
||||
text = other;
|
||||
}
|
||||
|
||||
void AttributedString::append (const String& textToAppend)
|
||||
{
|
||||
text += textToAppend;
|
||||
}
|
||||
|
||||
void AttributedString::append (const String& textToAppend, const Font& font)
|
||||
{
|
||||
const int oldLength = text.length();
|
||||
const int newLength = textToAppend.length();
|
||||
|
||||
text += textToAppend;
|
||||
setFont (Range<int> (oldLength, oldLength + newLength), font);
|
||||
}
|
||||
|
||||
void AttributedString::append (const String& textToAppend, Colour colour)
|
||||
{
|
||||
const int oldLength = text.length();
|
||||
const int newLength = textToAppend.length();
|
||||
|
||||
text += textToAppend;
|
||||
setColour (Range<int> (oldLength, oldLength + newLength), colour);
|
||||
}
|
||||
|
||||
void AttributedString::append (const String& textToAppend, const Font& font, Colour colour)
|
||||
{
|
||||
const int oldLength = text.length();
|
||||
const int newLength = textToAppend.length();
|
||||
|
||||
text += textToAppend;
|
||||
setFont (Range<int> (oldLength, oldLength + newLength), font);
|
||||
setColour (Range<int> (oldLength, oldLength + newLength), colour);
|
||||
}
|
||||
|
||||
void AttributedString::append (const AttributedString& other)
|
||||
{
|
||||
const int originalLength = text.length();
|
||||
text += other.text;
|
||||
|
||||
for (int i = 0; i < other.attributes.size(); ++i)
|
||||
attributes.add (new Attribute (*other.attributes.getUnchecked(i), originalLength));
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
attributes.add (new Attribute (range, colour));
|
||||
}
|
||||
|
||||
void AttributedString::setColour (Colour colour)
|
||||
{
|
||||
for (int i = attributes.size(); --i >= 0;)
|
||||
if (attributes.getUnchecked(i)->getColour() != nullptr)
|
||||
attributes.remove (i);
|
||||
|
||||
setColour (Range<int> (0, text.length()), colour);
|
||||
}
|
||||
|
||||
void AttributedString::setFont (Range<int> range, const Font& font)
|
||||
{
|
||||
attributes.add (new Attribute (range, font));
|
||||
}
|
||||
|
||||
void AttributedString::setFont (const Font& font)
|
||||
{
|
||||
for (int i = attributes.size(); --i >= 0;)
|
||||
if (attributes.getUnchecked(i)->getFont() != nullptr)
|
||||
attributes.remove (i);
|
||||
|
||||
setFont (Range<int> (0, text.length()), font);
|
||||
}
|
||||
|
||||
void AttributedString::draw (Graphics& g, const Rectangle<float>& area) const
|
||||
{
|
||||
if (text.isNotEmpty() && g.clipRegionIntersects (area.getSmallestIntegerContainer()))
|
||||
{
|
||||
if (! g.getInternalContext().drawTextLayout (*this, area))
|
||||
{
|
||||
TextLayout layout;
|
||||
layout.createLayout (*this, area.getWidth());
|
||||
layout.draw (g, area);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,218 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifndef JUCE_ATTRIBUTEDSTRING_H_INCLUDED
|
||||
#define JUCE_ATTRIBUTEDSTRING_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
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
|
||||
*/
|
||||
class JUCE_API AttributedString
|
||||
{
|
||||
public:
|
||||
/** Creates an empty attributed string. */
|
||||
AttributedString();
|
||||
|
||||
/** Creates an attributed string with the given text. */
|
||||
explicit AttributedString (const String& text);
|
||||
|
||||
AttributedString (const AttributedString&);
|
||||
AttributedString& operator= (const AttributedString&);
|
||||
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS
|
||||
AttributedString (AttributedString&&) noexcept;
|
||||
AttributedString& operator= (AttributedString&&) noexcept;
|
||||
#endif
|
||||
|
||||
/** Destructor. */
|
||||
~AttributedString();
|
||||
|
||||
//==============================================================================
|
||||
/** 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:
|
||||
/** Creates an attribute that changes the colour for a range of characters.
|
||||
@see AttributedString::setColour()
|
||||
*/
|
||||
Attribute (Range<int> range, Colour colour);
|
||||
|
||||
/** Creates an attribute that changes the font for a range of characters.
|
||||
@see AttributedString::setFont()
|
||||
*/
|
||||
Attribute (Range<int> range, const Font& font);
|
||||
|
||||
Attribute (const Attribute&);
|
||||
~Attribute();
|
||||
|
||||
/** If this attribute specifies a font, this returns it; otherwise it returns nullptr. */
|
||||
const Font* getFont() const noexcept { return font; }
|
||||
|
||||
/** If this attribute specifies a colour, this returns it; otherwise it returns nullptr. */
|
||||
const Colour* getColour() const noexcept { return colour; }
|
||||
|
||||
/** The range of characters to which this attribute will be applied. */
|
||||
const Range<int> range;
|
||||
|
||||
private:
|
||||
ScopedPointer<Font> font;
|
||||
ScopedPointer<Colour> colour;
|
||||
|
||||
friend class AttributedString;
|
||||
Attribute (const Attribute&, int);
|
||||
Attribute& operator= (const Attribute&);
|
||||
|
||||
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.getUnchecked (index); }
|
||||
|
||||
//==============================================================================
|
||||
/** Adds a colour attribute for the specified range. */
|
||||
void setColour (Range<int> range, Colour colour);
|
||||
|
||||
/** Removes all existing colour attributes, and applies this colour to the whole string. */
|
||||
void setColour (Colour colour);
|
||||
|
||||
/** Adds a font attribute for the specified range. */
|
||||
void setFont (Range<int> range, const Font& font);
|
||||
|
||||
/** Removes all existing font attributes, and applies this font to the whole string. */
|
||||
void setFont (const Font& font);
|
||||
|
||||
private:
|
||||
String text;
|
||||
float lineSpacing;
|
||||
Justification justification;
|
||||
WordWrap wordWrap;
|
||||
ReadingDirection readingDirection;
|
||||
OwnedArray<Attribute> attributes;
|
||||
|
||||
JUCE_LEAK_DETECTOR (AttributedString)
|
||||
};
|
||||
|
||||
#endif // JUCE_ATTRIBUTEDSTRING_H_INCLUDED
|
||||
|
|
@ -0,0 +1,406 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
class CustomTypeface::GlyphInfo
|
||||
{
|
||||
public:
|
||||
GlyphInfo (const juce_wchar c, const Path& p, const float w) noexcept
|
||||
: character (c), path (p), width (w)
|
||||
{
|
||||
}
|
||||
|
||||
struct KerningPair
|
||||
{
|
||||
juce_wchar character2;
|
||||
float kerningAmount;
|
||||
};
|
||||
|
||||
void addKerningPair (const juce_wchar subsequentCharacter,
|
||||
const float extraKerningAmount) noexcept
|
||||
{
|
||||
KerningPair kp;
|
||||
kp.character2 = subsequentCharacter;
|
||||
kp.kerningAmount = extraKerningAmount;
|
||||
kerningPairs.add (kp);
|
||||
}
|
||||
|
||||
float getHorizontalSpacing (const juce_wchar subsequentCharacter) const noexcept
|
||||
{
|
||||
if (subsequentCharacter != 0)
|
||||
for (int i = kerningPairs.size(); --i >= 0;)
|
||||
if (kerningPairs.getReference(i).character2 == subsequentCharacter)
|
||||
return width + kerningPairs.getReference(i).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)
|
||||
{
|
||||
uint32 n = (uint32) (uint16) in.readShort();
|
||||
|
||||
if (n >= 0xd800 && n <= 0xdfff)
|
||||
{
|
||||
const uint32 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);
|
||||
|
||||
int numChars = in.readInt();
|
||||
|
||||
for (int i = 0; i < numChars; ++i)
|
||||
{
|
||||
const juce_wchar c = CustomTypefaceHelpers::readChar (in);
|
||||
const float width = in.readFloat();
|
||||
|
||||
Path p;
|
||||
p.loadPathFromStream (in);
|
||||
addGlyph (c, p, width);
|
||||
}
|
||||
|
||||
const int numKerningPairs = in.readInt();
|
||||
|
||||
for (int i = 0; i < numKerningPairs; ++i)
|
||||
{
|
||||
const juce_wchar char1 = CustomTypefaceHelpers::readChar (in);
|
||||
const juce_wchar 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, const float newAscent, const bool isBold,
|
||||
const bool isItalic, const juce_wchar newDefaultCharacter) noexcept
|
||||
{
|
||||
name = newName;
|
||||
defaultCharacter = newDefaultCharacter;
|
||||
ascent = newAscent;
|
||||
style = FontStyleHelpers::getStyleName (isBold, isItalic);
|
||||
}
|
||||
|
||||
void CustomTypeface::setCharacteristics (const String& newName, const String& newStyle, const float newAscent,
|
||||
const juce_wchar newDefaultCharacter) noexcept
|
||||
{
|
||||
name = newName;
|
||||
style = newStyle;
|
||||
defaultCharacter = newDefaultCharacter;
|
||||
ascent = newAscent;
|
||||
}
|
||||
|
||||
void CustomTypeface::addGlyph (const juce_wchar character, const Path& path, const float width) noexcept
|
||||
{
|
||||
// Check that you're not trying to add the same character twice..
|
||||
jassert (findGlyph (character, false) == nullptr);
|
||||
|
||||
if (isPositiveAndBelow ((int) character, (int) numElementsInArray (lookupTable)))
|
||||
lookupTable [character] = (short) glyphs.size();
|
||||
|
||||
glyphs.add (new GlyphInfo (character, path, width));
|
||||
}
|
||||
|
||||
void CustomTypeface::addKerningPair (const juce_wchar char1, const juce_wchar char2, const float extraAmount) noexcept
|
||||
{
|
||||
if (extraAmount != 0)
|
||||
{
|
||||
if (GlyphInfo* const g = findGlyph (char1, true))
|
||||
g->addKerningPair (char2, extraAmount);
|
||||
else
|
||||
jassertfalse; // can only add kerning pairs for characters that exist!
|
||||
}
|
||||
}
|
||||
|
||||
CustomTypeface::GlyphInfo* CustomTypeface::findGlyph (const juce_wchar character, const bool loadIfNeeded) noexcept
|
||||
{
|
||||
if (isPositiveAndBelow ((int) character, (int) numElementsInArray (lookupTable)) && lookupTable [character] > 0)
|
||||
return glyphs [(int) lookupTable [(int) character]];
|
||||
|
||||
for (int i = 0; i < glyphs.size(); ++i)
|
||||
{
|
||||
GlyphInfo* const g = glyphs.getUnchecked(i);
|
||||
if (g->character == character)
|
||||
return g;
|
||||
}
|
||||
|
||||
if (loadIfNeeded && loadGlyphIfPossible (character))
|
||||
return findGlyph (character, false);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool CustomTypeface::loadGlyphIfPossible (const juce_wchar /*characterNeeded*/)
|
||||
{
|
||||
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)
|
||||
{
|
||||
const juce_wchar c = (juce_wchar) (characterStartIndex + 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)
|
||||
{
|
||||
const float glyphWidth = offsets[1];
|
||||
|
||||
Path p;
|
||||
typefaceToCopy.getOutlineForGlyph (glyphIndex, p);
|
||||
|
||||
addGlyph (c, p, glyphWidth);
|
||||
|
||||
for (int j = glyphs.size() - 1; --j >= 0;)
|
||||
{
|
||||
const juce_wchar 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 (int i = 0; i < glyphs.size(); ++i)
|
||||
{
|
||||
const GlyphInfo* const g = glyphs.getUnchecked (i);
|
||||
CustomTypefaceHelpers::writeChar (out, g->character);
|
||||
out.writeFloat (g->width);
|
||||
g->path.writePathToStream (out);
|
||||
|
||||
numKerningPairs += g->kerningPairs.size();
|
||||
}
|
||||
|
||||
out.writeInt (numKerningPairs);
|
||||
|
||||
for (int i = 0; i < glyphs.size(); ++i)
|
||||
{
|
||||
const GlyphInfo* const g = glyphs.getUnchecked (i);
|
||||
|
||||
for (int j = 0; j < g->kerningPairs.size(); ++j)
|
||||
{
|
||||
const GlyphInfo::KerningPair& p = g->kerningPairs.getReference (j);
|
||||
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 (String::CharPointerType t (text.getCharPointer()); ! t.isEmpty();)
|
||||
{
|
||||
const juce_wchar c = t.getAndAdvance();
|
||||
|
||||
if (const GlyphInfo* const glyph = findGlyph (c, true))
|
||||
{
|
||||
x += glyph->getHorizontalSpacing (*t);
|
||||
}
|
||||
else
|
||||
{
|
||||
const Typeface::Ptr fallbackTypeface (Typeface::getFallbackTypeface());
|
||||
|
||||
if (fallbackTypeface != nullptr && fallbackTypeface != this)
|
||||
x += fallbackTypeface->getStringWidth (String::charToString (c));
|
||||
}
|
||||
}
|
||||
|
||||
return x;
|
||||
}
|
||||
|
||||
void CustomTypeface::getGlyphPositions (const String& text, Array <int>& resultGlyphs, Array<float>& xOffsets)
|
||||
{
|
||||
xOffsets.add (0);
|
||||
float x = 0;
|
||||
|
||||
for (String::CharPointerType t (text.getCharPointer()); ! t.isEmpty();)
|
||||
{
|
||||
float width = 0.0f;
|
||||
int glyphChar = 0;
|
||||
|
||||
const juce_wchar c = t.getAndAdvance();
|
||||
|
||||
if (const GlyphInfo* const glyph = findGlyph (c, true))
|
||||
{
|
||||
width = glyph->getHorizontalSpacing (*t);
|
||||
glyphChar = (int) glyph->character;
|
||||
}
|
||||
else
|
||||
{
|
||||
const Typeface::Ptr fallbackTypeface (getFallbackTypeface());
|
||||
|
||||
if (fallbackTypeface != nullptr && fallbackTypeface != this)
|
||||
{
|
||||
Array <int> subGlyphs;
|
||||
Array <float> subOffsets;
|
||||
fallbackTypeface->getGlyphPositions (String::charToString (c), subGlyphs, subOffsets);
|
||||
|
||||
if (subGlyphs.size() > 0)
|
||||
{
|
||||
glyphChar = subGlyphs.getFirst();
|
||||
width = subOffsets[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
x += width;
|
||||
resultGlyphs.add (glyphChar);
|
||||
xOffsets.add (x);
|
||||
}
|
||||
}
|
||||
|
||||
bool CustomTypeface::getOutlineForGlyph (int glyphNumber, Path& path)
|
||||
{
|
||||
if (const GlyphInfo* const glyph = findGlyph ((juce_wchar) glyphNumber, true))
|
||||
{
|
||||
path = glyph->path;
|
||||
return true;
|
||||
}
|
||||
|
||||
const Typeface::Ptr fallbackTypeface (getFallbackTypeface());
|
||||
|
||||
if (fallbackTypeface != nullptr && fallbackTypeface != this)
|
||||
return fallbackTypeface->getOutlineForGlyph (glyphNumber, path);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
EdgeTable* CustomTypeface::getEdgeTableForGlyph (int glyphNumber, const AffineTransform& transform, float fontHeight)
|
||||
{
|
||||
if (const GlyphInfo* const 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
|
||||
{
|
||||
const Typeface::Ptr fallbackTypeface (getFallbackTypeface());
|
||||
|
||||
if (fallbackTypeface != nullptr && fallbackTypeface != this)
|
||||
return fallbackTypeface->getEdgeTableForGlyph (glyphNumber, transform, fontHeight);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
|
@ -0,0 +1,164 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifndef JUCE_CUSTOMTYPEFACE_H_INCLUDED
|
||||
#define JUCE_CUSTOMTYPEFACE_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
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 Introjucer,
|
||||
and then call Typeface::createSystemTypefaceFor() to load it from memory.
|
||||
|
||||
@see Typeface, Font
|
||||
*/
|
||||
class JUCE_API CustomTypeface : public Typeface
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a new, empty typeface. */
|
||||
CustomTypeface();
|
||||
|
||||
/** Loads a typeface from a previously saved stream.
|
||||
The stream must have been created by writeToStream().
|
||||
|
||||
NOTE! Since this class was written, support was added for loading real font files from
|
||||
memory, so for most people, using Typeface::createSystemTypefaceFor() to load a real font
|
||||
is more appropriate than using this class to store it in a proprietary format.
|
||||
|
||||
@see writeToStream
|
||||
*/
|
||||
explicit CustomTypeface (InputStream& serialisedTypefaceStream);
|
||||
|
||||
/** Destructor. */
|
||||
~CustomTypeface();
|
||||
|
||||
//==============================================================================
|
||||
/** Resets this typeface, deleting all its glyphs and settings. */
|
||||
void clear();
|
||||
|
||||
/** Sets the vital statistics for the typeface.
|
||||
@param fontFamily the typeface's font family
|
||||
@param ascent the ascent - this is normalised to a height of 1.0 and this is
|
||||
the value that will be returned by Typeface::getAscent(). The
|
||||
descent is assumed to be (1.0 - ascent)
|
||||
@param isBold should be true if the typeface is bold
|
||||
@param isItalic should be true if the typeface is italic
|
||||
@param defaultCharacter the character to be used as a replacement if there's
|
||||
no glyph available for the character that's being drawn
|
||||
*/
|
||||
void setCharacteristics (const String& fontFamily, float ascent,
|
||||
bool isBold, bool isItalic,
|
||||
juce_wchar defaultCharacter) noexcept;
|
||||
|
||||
/** Sets the vital statistics for the typeface.
|
||||
@param fontFamily the typeface's font family
|
||||
@param fontStyle the typeface's font style
|
||||
@param ascent the ascent - this is normalised to a height of 1.0 and this is
|
||||
the value that will be returned by Typeface::getAscent(). The
|
||||
descent is assumed to be (1.0 - ascent)
|
||||
@param defaultCharacter the character to be used as a replacement if there's
|
||||
no glyph available for the character that's being drawn
|
||||
*/
|
||||
void setCharacteristics (const String& fontFamily, const String& fontStyle,
|
||||
float ascent, juce_wchar defaultCharacter) noexcept;
|
||||
|
||||
/** Adds a glyph to the typeface.
|
||||
|
||||
The path that is passed in is normalised so that the font height is 1.0, and its
|
||||
origin is the anchor point of the character on its baseline.
|
||||
|
||||
The width is the nominal width of the character, and any extra kerning values that
|
||||
are specified will be added to this width.
|
||||
*/
|
||||
void addGlyph (juce_wchar character, const Path& path, float width) noexcept;
|
||||
|
||||
/** Specifies an extra kerning amount to be used between a pair of characters.
|
||||
The amount will be added to the nominal width of the first character when laying out a string.
|
||||
*/
|
||||
void addKerningPair (juce_wchar char1, juce_wchar char2, float extraAmount) noexcept;
|
||||
|
||||
/** Adds a range of glyphs from another typeface.
|
||||
This will attempt to pull in the paths and kerning information from another typeface and
|
||||
add it to this one.
|
||||
*/
|
||||
void addGlyphsFromOtherTypeface (Typeface& typefaceToCopy, juce_wchar characterStartIndex, int numCharacters) noexcept;
|
||||
|
||||
/** Saves this typeface as a Juce-formatted font file.
|
||||
A CustomTypeface can be created to reload the data that is written - see the CustomTypeface
|
||||
constructor.
|
||||
|
||||
NOTE! Since this class was written, support was added for loading real font files from
|
||||
memory, so for most people, using Typeface::createSystemTypefaceFor() to load a real font
|
||||
is more appropriate than using this class to store it in a proprietary format.
|
||||
*/
|
||||
bool writeToStream (OutputStream& outputStream);
|
||||
|
||||
//==============================================================================
|
||||
// The following methods implement the basic Typeface behaviour.
|
||||
float getAscent() const override;
|
||||
float getDescent() const override;
|
||||
float getHeightToPointsFactor() const override;
|
||||
float getStringWidth (const String&) override;
|
||||
void getGlyphPositions (const String&, Array <int>& glyphs, Array<float>& xOffsets) override;
|
||||
bool getOutlineForGlyph (int glyphNumber, Path&) override;
|
||||
EdgeTable* getEdgeTableForGlyph (int glyphNumber, const AffineTransform&, float fontHeight) override;
|
||||
|
||||
protected:
|
||||
//==============================================================================
|
||||
juce_wchar defaultCharacter;
|
||||
float ascent;
|
||||
|
||||
//==============================================================================
|
||||
/** If a subclass overrides this, it can load glyphs into the font on-demand.
|
||||
When methods such as getGlyphPositions() or getOutlineForGlyph() are asked for a
|
||||
particular character and there's no corresponding glyph, they'll call this
|
||||
method so that a subclass can try to add that glyph, returning true if it
|
||||
manages to do so.
|
||||
*/
|
||||
virtual bool loadGlyphIfPossible (juce_wchar characterNeeded);
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
class GlyphInfo;
|
||||
friend struct ContainerDeletePolicy<GlyphInfo>;
|
||||
OwnedArray<GlyphInfo> glyphs;
|
||||
short lookupTable [128];
|
||||
|
||||
GlyphInfo* findGlyph (const juce_wchar character, bool loadIfNeeded) noexcept;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CustomTypeface)
|
||||
};
|
||||
|
||||
#endif // JUCE_CUSTOMTYPEFACE_H_INCLUDED
|
||||
|
|
@ -0,0 +1,720 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace FontValues
|
||||
{
|
||||
static float limitFontHeight (const float height) noexcept
|
||||
{
|
||||
return jlimit (0.1f, 10000.0f, height);
|
||||
}
|
||||
|
||||
const float defaultFontHeight = 14.0f;
|
||||
String fallbackFont;
|
||||
String fallbackFontStyle;
|
||||
}
|
||||
|
||||
typedef Typeface::Ptr (*GetTypefaceForFont) (const Font&);
|
||||
GetTypefaceForFont juce_getTypefaceForFont = nullptr;
|
||||
|
||||
//==============================================================================
|
||||
class TypefaceCache : private DeletedAtShutdown
|
||||
{
|
||||
public:
|
||||
TypefaceCache() : counter (0)
|
||||
{
|
||||
setSize (10);
|
||||
}
|
||||
|
||||
~TypefaceCache()
|
||||
{
|
||||
clearSingletonInstance();
|
||||
}
|
||||
|
||||
juce_DeclareSingleton (TypefaceCache, false);
|
||||
|
||||
void setSize (const int numToCache)
|
||||
{
|
||||
const ScopedWriteLock sl (lock);
|
||||
|
||||
faces.clear();
|
||||
faces.insertMultiple (-1, CachedFace(), numToCache);
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
const ScopedWriteLock sl (lock);
|
||||
|
||||
setSize (faces.size());
|
||||
defaultFace = nullptr;
|
||||
}
|
||||
|
||||
Typeface::Ptr findTypefaceFor (const Font& font)
|
||||
{
|
||||
const ScopedReadLock slr (lock);
|
||||
|
||||
const String faceName (font.getTypefaceName());
|
||||
const String faceStyle (font.getTypefaceStyle());
|
||||
|
||||
jassert (faceName.isNotEmpty());
|
||||
|
||||
for (int i = faces.size(); --i >= 0;)
|
||||
{
|
||||
CachedFace& face = faces.getReference(i);
|
||||
|
||||
if (face.typefaceName == faceName
|
||||
&& face.typefaceStyle == faceStyle
|
||||
&& face.typeface != nullptr
|
||||
&& face.typeface->isSuitableForFont (font))
|
||||
{
|
||||
face.lastUsageCount = ++counter;
|
||||
return face.typeface;
|
||||
}
|
||||
}
|
||||
|
||||
const ScopedWriteLock slw (lock);
|
||||
int replaceIndex = 0;
|
||||
size_t bestLastUsageCount = std::numeric_limits<size_t>::max();
|
||||
|
||||
for (int i = faces.size(); --i >= 0;)
|
||||
{
|
||||
const size_t lu = faces.getReference(i).lastUsageCount;
|
||||
|
||||
if (bestLastUsageCount > lu)
|
||||
{
|
||||
bestLastUsageCount = lu;
|
||||
replaceIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
CachedFace& face = faces.getReference (replaceIndex);
|
||||
face.typefaceName = faceName;
|
||||
face.typefaceStyle = faceStyle;
|
||||
face.lastUsageCount = ++counter;
|
||||
|
||||
if (juce_getTypefaceForFont == nullptr)
|
||||
face.typeface = Font::getDefaultTypefaceForFont (font);
|
||||
else
|
||||
face.typeface = juce_getTypefaceForFont (font);
|
||||
|
||||
jassert (face.typeface != nullptr); // the look and feel must return a typeface!
|
||||
|
||||
if (defaultFace == nullptr && font == Font())
|
||||
defaultFace = face.typeface;
|
||||
|
||||
return face.typeface;
|
||||
}
|
||||
|
||||
Typeface::Ptr defaultFace;
|
||||
|
||||
private:
|
||||
struct CachedFace
|
||||
{
|
||||
CachedFace() noexcept : lastUsageCount (0) {}
|
||||
|
||||
// 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;
|
||||
Typeface::Ptr typeface;
|
||||
};
|
||||
|
||||
ReadWriteLock lock;
|
||||
Array<CachedFace> faces;
|
||||
size_t counter;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TypefaceCache)
|
||||
};
|
||||
|
||||
juce_ImplementSingleton (TypefaceCache)
|
||||
|
||||
void Typeface::setTypefaceCacheSize (int numFontsToCache)
|
||||
{
|
||||
TypefaceCache::getInstance()->setSize (numFontsToCache);
|
||||
}
|
||||
|
||||
#if JUCE_MODULE_AVAILABLE_juce_opengl
|
||||
extern void clearOpenGLGlyphCache();
|
||||
#endif
|
||||
|
||||
void Typeface::clearTypefaceCache()
|
||||
{
|
||||
TypefaceCache::getInstance()->clear();
|
||||
|
||||
RenderingHelpers::SoftwareRendererSavedState::clearGlyphCache();
|
||||
|
||||
#if JUCE_MODULE_AVAILABLE_juce_opengl
|
||||
clearOpenGLGlyphCache();
|
||||
#endif
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class Font::SharedFontInternal : public ReferenceCountedObject
|
||||
{
|
||||
public:
|
||||
SharedFontInternal() noexcept
|
||||
: typeface (TypefaceCache::getInstance()->defaultFace),
|
||||
typefaceName (Font::getDefaultSansSerifFontName()),
|
||||
typefaceStyle (Font::getDefaultStyle()),
|
||||
height (FontValues::defaultFontHeight),
|
||||
horizontalScale (1.0f), kerning (0), ascent (0), underline (false)
|
||||
{
|
||||
}
|
||||
|
||||
SharedFontInternal (int styleFlags, float fontHeight) noexcept
|
||||
: typefaceName (Font::getDefaultSansSerifFontName()),
|
||||
typefaceStyle (FontStyleHelpers::getStyleName (styleFlags)),
|
||||
height (fontHeight),
|
||||
horizontalScale (1.0f), kerning (0), ascent (0), underline ((styleFlags & underlined) != 0)
|
||||
{
|
||||
if (styleFlags == plain)
|
||||
typeface = TypefaceCache::getInstance()->defaultFace;
|
||||
}
|
||||
|
||||
SharedFontInternal (const String& name, int styleFlags, float fontHeight) noexcept
|
||||
: typefaceName (name),
|
||||
typefaceStyle (FontStyleHelpers::getStyleName (styleFlags)),
|
||||
height (fontHeight),
|
||||
horizontalScale (1.0f), kerning (0), ascent (0), underline ((styleFlags & underlined) != 0)
|
||||
{
|
||||
if (styleFlags == plain && typefaceName.isEmpty())
|
||||
typeface = TypefaceCache::getInstance()->defaultFace;
|
||||
}
|
||||
|
||||
SharedFontInternal (const String& name, const String& style, float fontHeight) noexcept
|
||||
: typefaceName (name), typefaceStyle (style), height (fontHeight),
|
||||
horizontalScale (1.0f), kerning (0), ascent (0), underline (false)
|
||||
{
|
||||
if (typefaceName.isEmpty())
|
||||
typefaceName = Font::getDefaultSansSerifFontName();
|
||||
}
|
||||
|
||||
explicit SharedFontInternal (const Typeface::Ptr& face) noexcept
|
||||
: typeface (face),
|
||||
typefaceName (face->getName()),
|
||||
typefaceStyle (face->getStyle()),
|
||||
height (FontValues::defaultFontHeight),
|
||||
horizontalScale (1.0f), kerning (0), ascent (0), underline (false)
|
||||
{
|
||||
jassert (typefaceName.isNotEmpty());
|
||||
}
|
||||
|
||||
SharedFontInternal (const SharedFontInternal& other) noexcept
|
||||
: ReferenceCountedObject(),
|
||||
typeface (other.typeface),
|
||||
typefaceName (other.typefaceName),
|
||||
typefaceStyle (other.typefaceStyle),
|
||||
height (other.height),
|
||||
horizontalScale (other.horizontalScale),
|
||||
kerning (other.kerning),
|
||||
ascent (other.ascent),
|
||||
underline (other.underline)
|
||||
{
|
||||
}
|
||||
|
||||
bool operator== (const SharedFontInternal& other) const noexcept
|
||||
{
|
||||
return height == other.height
|
||||
&& underline == other.underline
|
||||
&& horizontalScale == other.horizontalScale
|
||||
&& kerning == other.kerning
|
||||
&& typefaceName == other.typefaceName
|
||||
&& typefaceStyle == other.typefaceStyle;
|
||||
}
|
||||
|
||||
Typeface::Ptr typeface;
|
||||
String typefaceName, typefaceStyle;
|
||||
float height, horizontalScale, kerning, ascent;
|
||||
bool underline;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
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;
|
||||
}
|
||||
|
||||
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS
|
||||
Font::Font (Font&& other) noexcept
|
||||
: font (static_cast<ReferenceCountedObjectPtr<SharedFontInternal>&&> (other.font))
|
||||
{
|
||||
}
|
||||
|
||||
Font& Font::operator= (Font&& other) noexcept
|
||||
{
|
||||
font = static_cast<ReferenceCountedObjectPtr<SharedFontInternal>&&> (other.font);
|
||||
return *this;
|
||||
}
|
||||
#endif
|
||||
|
||||
Font::~Font() noexcept
|
||||
{
|
||||
}
|
||||
|
||||
bool Font::operator== (const Font& other) const noexcept
|
||||
{
|
||||
return font == other.font
|
||||
|| *font == *other.font;
|
||||
}
|
||||
|
||||
bool Font::operator!= (const Font& other) const noexcept
|
||||
{
|
||||
return ! operator== (other);
|
||||
}
|
||||
|
||||
void Font::dupeInternalIfShared()
|
||||
{
|
||||
if (font->getReferenceCount() > 1)
|
||||
font = new SharedFontInternal (*font);
|
||||
}
|
||||
|
||||
void Font::checkTypefaceSuitability()
|
||||
{
|
||||
if (font->typeface != nullptr && ! font->typeface->isSuitableForFont (*this))
|
||||
font->typeface = nullptr;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct FontPlaceholderNames
|
||||
{
|
||||
FontPlaceholderNames()
|
||||
: sans ("<Sans-Serif>"),
|
||||
serif ("<Serif>"),
|
||||
mono ("<Monospaced>"),
|
||||
regular ("<Regular>")
|
||||
{
|
||||
}
|
||||
|
||||
String sans, serif, mono, regular;
|
||||
};
|
||||
|
||||
const FontPlaceholderNames& getFontPlaceholderNames()
|
||||
{
|
||||
static FontPlaceholderNames names;
|
||||
return names;
|
||||
}
|
||||
|
||||
#if JUCE_MSVC
|
||||
// This is a workaround for the lack of thread-safety in MSVC's handling of function-local
|
||||
// statics - if multiple threads all try to create the first Font object at the same time,
|
||||
// it can cause a race-condition in creating these placeholder strings.
|
||||
struct FontNamePreloader { FontNamePreloader() { getFontPlaceholderNames(); } };
|
||||
static FontNamePreloader fnp;
|
||||
#endif
|
||||
|
||||
const String& Font::getDefaultSansSerifFontName() { return getFontPlaceholderNames().sans; }
|
||||
const String& Font::getDefaultSerifFontName() { return getFontPlaceholderNames().serif; }
|
||||
const String& Font::getDefaultMonospacedFontName() { return getFontPlaceholderNames().mono; }
|
||||
const String& Font::getDefaultStyle() { return getFontPlaceholderNames().regular; }
|
||||
|
||||
const String& Font::getTypefaceName() const noexcept { return font->typefaceName; }
|
||||
const String& Font::getTypefaceStyle() const noexcept { return font->typefaceStyle; }
|
||||
|
||||
void Font::setTypefaceName (const String& faceName)
|
||||
{
|
||||
if (faceName != font->typefaceName)
|
||||
{
|
||||
jassert (faceName.isNotEmpty());
|
||||
|
||||
dupeInternalIfShared();
|
||||
font->typefaceName = faceName;
|
||||
font->typeface = nullptr;
|
||||
font->ascent = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void Font::setTypefaceStyle (const String& typefaceStyle)
|
||||
{
|
||||
if (typefaceStyle != font->typefaceStyle)
|
||||
{
|
||||
dupeInternalIfShared();
|
||||
font->typefaceStyle = typefaceStyle;
|
||||
font->typeface = nullptr;
|
||||
font->ascent = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Font Font::withTypefaceStyle (const String& newStyle) const
|
||||
{
|
||||
Font f (*this);
|
||||
f.setTypefaceStyle (newStyle);
|
||||
return f;
|
||||
}
|
||||
|
||||
StringArray Font::getAvailableStyles() const
|
||||
{
|
||||
return findAllTypefaceStyles (getTypeface()->getName());
|
||||
}
|
||||
|
||||
Typeface* Font::getTypeface() const
|
||||
{
|
||||
if (font->typeface == nullptr)
|
||||
{
|
||||
font->typeface = TypefaceCache::getInstance()->findTypefaceFor (*this);
|
||||
jassert (font->typeface != nullptr);
|
||||
}
|
||||
|
||||
return font->typeface;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
const String& Font::getFallbackFontName()
|
||||
{
|
||||
return FontValues::fallbackFont;
|
||||
}
|
||||
|
||||
void Font::setFallbackFontName (const String& name)
|
||||
{
|
||||
FontValues::fallbackFont = name;
|
||||
|
||||
#if JUCE_MAC || JUCE_IOS
|
||||
jassertfalse; // Note that use of a fallback font isn't currently implemented in OSX..
|
||||
#endif
|
||||
}
|
||||
|
||||
const String& Font::getFallbackFontStyle()
|
||||
{
|
||||
return FontValues::fallbackFontStyle;
|
||||
}
|
||||
|
||||
void Font::setFallbackFontStyle (const String& style)
|
||||
{
|
||||
FontValues::fallbackFontStyle = style;
|
||||
|
||||
#if JUCE_MAC || JUCE_IOS
|
||||
jassertfalse; // Note that use of a fallback font isn't currently implemented in OSX..
|
||||
#endif
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
Font Font::withHeight (const float newHeight) const
|
||||
{
|
||||
Font f (*this);
|
||||
f.setHeight (newHeight);
|
||||
return f;
|
||||
}
|
||||
|
||||
float Font::getHeightToPointsFactor() const
|
||||
{
|
||||
return getTypeface()->getHeightToPointsFactor();
|
||||
}
|
||||
|
||||
Font Font::withPointHeight (float heightInPoints) const
|
||||
{
|
||||
Font f (*this);
|
||||
f.setHeight (heightInPoints / getHeightToPointsFactor());
|
||||
return f;
|
||||
}
|
||||
|
||||
void Font::setHeight (float newHeight)
|
||||
{
|
||||
newHeight = FontValues::limitFontHeight (newHeight);
|
||||
|
||||
if (font->height != newHeight)
|
||||
{
|
||||
dupeInternalIfShared();
|
||||
font->height = newHeight;
|
||||
checkTypefaceSuitability();
|
||||
}
|
||||
}
|
||||
|
||||
void Font::setHeightWithoutChangingWidth (float newHeight)
|
||||
{
|
||||
newHeight = FontValues::limitFontHeight (newHeight);
|
||||
|
||||
if (font->height != newHeight)
|
||||
{
|
||||
dupeInternalIfShared();
|
||||
font->horizontalScale *= (font->height / newHeight);
|
||||
font->height = newHeight;
|
||||
checkTypefaceSuitability();
|
||||
}
|
||||
}
|
||||
|
||||
int Font::getStyleFlags() const noexcept
|
||||
{
|
||||
int styleFlags = font->underline ? underlined : plain;
|
||||
|
||||
if (isBold()) styleFlags |= bold;
|
||||
if (isItalic()) styleFlags |= italic;
|
||||
|
||||
return styleFlags;
|
||||
}
|
||||
|
||||
Font Font::withStyle (const int newFlags) const
|
||||
{
|
||||
Font f (*this);
|
||||
f.setStyleFlags (newFlags);
|
||||
return f;
|
||||
}
|
||||
|
||||
void Font::setStyleFlags (const int newFlags)
|
||||
{
|
||||
if (getStyleFlags() != newFlags)
|
||||
{
|
||||
dupeInternalIfShared();
|
||||
font->typeface = nullptr;
|
||||
font->typefaceStyle = FontStyleHelpers::getStyleName (newFlags);
|
||||
font->underline = (newFlags & underlined) != 0;
|
||||
font->ascent = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void Font::setSizeAndStyle (float newHeight,
|
||||
const int newStyleFlags,
|
||||
const float newHorizontalScale,
|
||||
const float newKerningAmount)
|
||||
{
|
||||
newHeight = FontValues::limitFontHeight (newHeight);
|
||||
|
||||
if (font->height != newHeight
|
||||
|| font->horizontalScale != newHorizontalScale
|
||||
|| font->kerning != newKerningAmount)
|
||||
{
|
||||
dupeInternalIfShared();
|
||||
font->height = newHeight;
|
||||
font->horizontalScale = newHorizontalScale;
|
||||
font->kerning = newKerningAmount;
|
||||
checkTypefaceSuitability();
|
||||
}
|
||||
|
||||
setStyleFlags (newStyleFlags);
|
||||
}
|
||||
|
||||
void Font::setSizeAndStyle (float newHeight,
|
||||
const String& newStyle,
|
||||
const float newHorizontalScale,
|
||||
const float newKerningAmount)
|
||||
{
|
||||
newHeight = FontValues::limitFontHeight (newHeight);
|
||||
|
||||
if (font->height != newHeight
|
||||
|| font->horizontalScale != newHorizontalScale
|
||||
|| font->kerning != newKerningAmount)
|
||||
{
|
||||
dupeInternalIfShared();
|
||||
font->height = newHeight;
|
||||
font->horizontalScale = newHorizontalScale;
|
||||
font->kerning = newKerningAmount;
|
||||
checkTypefaceSuitability();
|
||||
}
|
||||
|
||||
setTypefaceStyle (newStyle);
|
||||
}
|
||||
|
||||
Font Font::withHorizontalScale (const float newHorizontalScale) const
|
||||
{
|
||||
Font f (*this);
|
||||
f.setHorizontalScale (newHorizontalScale);
|
||||
return f;
|
||||
}
|
||||
|
||||
void Font::setHorizontalScale (const float scaleFactor)
|
||||
{
|
||||
dupeInternalIfShared();
|
||||
font->horizontalScale = scaleFactor;
|
||||
checkTypefaceSuitability();
|
||||
}
|
||||
|
||||
float Font::getHorizontalScale() const noexcept
|
||||
{
|
||||
return font->horizontalScale;
|
||||
}
|
||||
|
||||
float Font::getExtraKerningFactor() const noexcept
|
||||
{
|
||||
return font->kerning;
|
||||
}
|
||||
|
||||
Font Font::withExtraKerningFactor (const float extraKerning) const
|
||||
{
|
||||
Font f (*this);
|
||||
f.setExtraKerningFactor (extraKerning);
|
||||
return f;
|
||||
}
|
||||
|
||||
void Font::setExtraKerningFactor (const float extraKerning)
|
||||
{
|
||||
dupeInternalIfShared();
|
||||
font->kerning = extraKerning;
|
||||
checkTypefaceSuitability();
|
||||
}
|
||||
|
||||
Font Font::boldened() const { return withStyle (getStyleFlags() | bold); }
|
||||
Font Font::italicised() const { return withStyle (getStyleFlags() | italic); }
|
||||
|
||||
bool Font::isBold() const noexcept { return FontStyleHelpers::isBold (font->typefaceStyle); }
|
||||
bool Font::isItalic() const noexcept { return FontStyleHelpers::isItalic (font->typefaceStyle); }
|
||||
bool Font::isUnderlined() const noexcept { return font->underline; }
|
||||
|
||||
void Font::setBold (const bool shouldBeBold)
|
||||
{
|
||||
const int flags = getStyleFlags();
|
||||
setStyleFlags (shouldBeBold ? (flags | bold)
|
||||
: (flags & ~bold));
|
||||
}
|
||||
|
||||
void Font::setItalic (const bool shouldBeItalic)
|
||||
{
|
||||
const int flags = getStyleFlags();
|
||||
setStyleFlags (shouldBeItalic ? (flags | italic)
|
||||
: (flags & ~italic));
|
||||
}
|
||||
|
||||
void Font::setUnderline (const bool shouldBeUnderlined)
|
||||
{
|
||||
dupeInternalIfShared();
|
||||
font->underline = shouldBeUnderlined;
|
||||
checkTypefaceSuitability();
|
||||
}
|
||||
|
||||
float Font::getAscent() const
|
||||
{
|
||||
if (font->ascent == 0)
|
||||
font->ascent = getTypeface()->getAscent();
|
||||
|
||||
return font->height * font->ascent;
|
||||
}
|
||||
|
||||
float Font::getHeight() const noexcept { return font->height; }
|
||||
float Font::getDescent() const { return font->height - getAscent(); }
|
||||
|
||||
float Font::getHeightInPoints() const { return getHeight() * getHeightToPointsFactor(); }
|
||||
float Font::getAscentInPoints() const { return getAscent() * getHeightToPointsFactor(); }
|
||||
float Font::getDescentInPoints() const { return getDescent() * getHeightToPointsFactor(); }
|
||||
|
||||
int Font::getStringWidth (const String& text) const
|
||||
{
|
||||
return roundToInt (getStringWidthFloat (text));
|
||||
}
|
||||
|
||||
float Font::getStringWidthFloat (const String& text) const
|
||||
{
|
||||
float w = getTypeface()->getStringWidth (text);
|
||||
|
||||
if (font->kerning != 0)
|
||||
w += font->kerning * text.length();
|
||||
|
||||
return w * font->height * font->horizontalScale;
|
||||
}
|
||||
|
||||
void Font::getGlyphPositions (const String& text, Array<int>& glyphs, Array<float>& xOffsets) const
|
||||
{
|
||||
getTypeface()->getGlyphPositions (text, glyphs, xOffsets);
|
||||
|
||||
const int num = xOffsets.size();
|
||||
|
||||
if (num > 0)
|
||||
{
|
||||
const float scale = font->height * font->horizontalScale;
|
||||
float* const x = xOffsets.getRawDataPointer();
|
||||
|
||||
if (font->kerning != 0)
|
||||
{
|
||||
for (int i = 0; i < num; ++i)
|
||||
x[i] = (x[i] + i * font->kerning) * scale;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < num; ++i)
|
||||
x[i] *= scale;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Font::findFonts (Array<Font>& destArray)
|
||||
{
|
||||
const StringArray names (findAllTypefaceNames());
|
||||
|
||||
for (int i = 0; i < names.size(); ++i)
|
||||
{
|
||||
const StringArray styles (findAllTypefaceStyles (names[i]));
|
||||
|
||||
String style ("Regular");
|
||||
|
||||
if (! styles.contains (style, true))
|
||||
style = styles[0];
|
||||
|
||||
destArray.add (Font (names[i], 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);
|
||||
}
|
||||
|
|
@ -0,0 +1,460 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifndef JUCE_FONT_H_INCLUDED
|
||||
#define JUCE_FONT_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Represents a particular font, including its size, style, etc.
|
||||
|
||||
Apart from the typeface to be used, a Font object also dictates whether
|
||||
the font is bold, italic, underlined, how big it is, and its kerning and
|
||||
horizontal scale factor.
|
||||
|
||||
@see Typeface
|
||||
*/
|
||||
class JUCE_API Font
|
||||
{
|
||||
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();
|
||||
|
||||
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS
|
||||
Font (Font&& other) noexcept;
|
||||
Font& operator= (Font&& other) noexcept;
|
||||
#endif
|
||||
|
||||
/** Copies this font from another one. */
|
||||
Font& operator= (const Font& other) noexcept;
|
||||
|
||||
bool operator== (const Font& other) const noexcept;
|
||||
bool operator!= (const Font& other) const noexcept;
|
||||
|
||||
/** Destructor. */
|
||||
~Font() noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the font family of the typeface.
|
||||
|
||||
e.g. "Arial", "Courier", etc.
|
||||
|
||||
This may also be set to Font::getDefaultSansSerifFontName(), Font::getDefaultSerifFontName(),
|
||||
or Font::getDefaultMonospacedFontName(), which are not actual platform-specific font family names,
|
||||
but are generic font family names that are used to represent the various default fonts.
|
||||
If you need to know the exact typeface font family being used, you can call
|
||||
Font::getTypeface()->getName(), which will give you the platform-specific font family.
|
||||
|
||||
If a suitable font isn't found on the machine, it'll just use a default instead.
|
||||
*/
|
||||
void setTypefaceName (const String& faceName);
|
||||
|
||||
/** Returns the font family of the typeface that this font uses.
|
||||
|
||||
e.g. "Arial", "Courier", etc.
|
||||
|
||||
This may also be set to Font::getDefaultSansSerifFontName(), Font::getDefaultSerifFontName(),
|
||||
or Font::getDefaultMonospacedFontName(), which are not actual platform-specific font family names,
|
||||
but are generic font familiy names that are used to represent the various default fonts.
|
||||
|
||||
If you need to know the exact typeface font family being used, you can call
|
||||
Font::getTypeface()->getName(), which will give you the platform-specific font family.
|
||||
*/
|
||||
const String& getTypefaceName() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the font style of the typeface that this font uses.
|
||||
@see withTypefaceStyle, getAvailableStyles()
|
||||
*/
|
||||
const String& getTypefaceStyle() const noexcept;
|
||||
|
||||
/** Changes the font style of the typeface.
|
||||
@see getAvailableStyles()
|
||||
*/
|
||||
void setTypefaceStyle (const String& newStyle);
|
||||
|
||||
/** Returns a copy of this font with a new typeface style.
|
||||
@see getAvailableStyles()
|
||||
*/
|
||||
Font withTypefaceStyle (const String& newStyle) const;
|
||||
|
||||
/** Returns a list of the styles that this font can use. */
|
||||
StringArray getAvailableStyles() const;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns a typeface font family that represents the default sans-serif font.
|
||||
|
||||
This is also the typeface that will be used when a font is created without
|
||||
specifying any typeface details.
|
||||
|
||||
Note that this method just returns a generic placeholder string that means "the default
|
||||
sans-serif font" - it's not the actual font family of this font.
|
||||
|
||||
@see setTypefaceName, getDefaultSerifFontName, getDefaultMonospacedFontName
|
||||
*/
|
||||
static const String& getDefaultSansSerifFontName();
|
||||
|
||||
/** Returns a typeface font family that represents the default serif font.
|
||||
|
||||
Note that this method just returns a generic placeholder string that means "the default
|
||||
serif font" - it's not the actual font family of this font.
|
||||
|
||||
@see setTypefaceName, getDefaultSansSerifFontName, getDefaultMonospacedFontName
|
||||
*/
|
||||
static const String& getDefaultSerifFontName();
|
||||
|
||||
/** Returns a typeface font family that represents the default monospaced font.
|
||||
|
||||
Note that this method just returns a generic placeholder string that means "the default
|
||||
monospaced font" - it's not the actual font family of this font.
|
||||
|
||||
@see setTypefaceName, getDefaultSansSerifFontName, getDefaultSerifFontName
|
||||
*/
|
||||
static const String& getDefaultMonospacedFontName();
|
||||
|
||||
/** Returns a font style name that represents the default style.
|
||||
|
||||
Note that this method just returns a generic placeholder string that means "the default
|
||||
font style" - it's not the actual name of the font style of any particular font.
|
||||
|
||||
@see setTypefaceStyle
|
||||
*/
|
||||
static const String& getDefaultStyle();
|
||||
|
||||
/** Returns the default system typeface for the given font. */
|
||||
static Typeface::Ptr getDefaultTypefaceForFont (const Font& font);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns a copy of this font with a new height. */
|
||||
Font withHeight (float height) const;
|
||||
|
||||
/** Returns a copy of this font with a new height, specified in points. */
|
||||
Font withPointHeight (float heightInPoints) const;
|
||||
|
||||
/** Changes the font's height.
|
||||
@see getHeight, withHeight, setHeightWithoutChangingWidth
|
||||
*/
|
||||
void setHeight (float newHeight);
|
||||
|
||||
/** Changes the font's height without changing its width.
|
||||
This alters the horizontal scale to compensate for the change in height.
|
||||
*/
|
||||
void setHeightWithoutChangingWidth (float newHeight);
|
||||
|
||||
/** Returns the total height of this font, in pixels.
|
||||
This is the maximum height, from the top of the ascent to the bottom of the
|
||||
descenders.
|
||||
|
||||
@see withHeight, setHeightWithoutChangingWidth, getAscent
|
||||
*/
|
||||
float getHeight() const noexcept;
|
||||
|
||||
/** Returns the total height of this font, in points.
|
||||
This is the maximum height, from the top of the ascent to the bottom of the
|
||||
descenders.
|
||||
|
||||
@see withPointHeight, getHeight
|
||||
*/
|
||||
float getHeightInPoints() const;
|
||||
|
||||
/** Returns the height of the font above its baseline, in pixels.
|
||||
This is the maximum height from the baseline to the top.
|
||||
@see getHeight, getDescent
|
||||
*/
|
||||
float getAscent() const;
|
||||
|
||||
/** Returns the height of the font above its baseline, in points.
|
||||
This is the maximum height from the baseline to the top.
|
||||
@see getHeight, getDescent
|
||||
*/
|
||||
float getAscentInPoints() const;
|
||||
|
||||
/** Returns the amount that the font descends below its baseline, in pixels.
|
||||
This is calculated as (getHeight() - getAscent()).
|
||||
@see getAscent, getHeight
|
||||
*/
|
||||
float getDescent() const;
|
||||
|
||||
/** Returns the amount that the font descends below its baseline, in points.
|
||||
This is calculated as (getHeight() - getAscent()).
|
||||
@see getAscent, getHeight
|
||||
*/
|
||||
float getDescentInPoints() const;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the font's style flags.
|
||||
This will return a bitwise-or'ed combination of values from the FontStyleFlags
|
||||
enum, to describe whether the font is bold, italic, etc.
|
||||
@see FontStyleFlags, withStyle
|
||||
*/
|
||||
int getStyleFlags() const noexcept;
|
||||
|
||||
/** Returns a copy of this font with the given set of style flags.
|
||||
@param styleFlags a bitwise-or'ed combination of values from the FontStyleFlags enum.
|
||||
@see FontStyleFlags, getStyleFlags
|
||||
*/
|
||||
Font withStyle (int styleFlags) const;
|
||||
|
||||
/** Changes the font's style.
|
||||
@param newFlags a bitwise-or'ed combination of values from the FontStyleFlags enum.
|
||||
@see FontStyleFlags, withStyle
|
||||
*/
|
||||
void setStyleFlags (int newFlags);
|
||||
|
||||
//==============================================================================
|
||||
/** Makes the font bold or non-bold. */
|
||||
void setBold (bool shouldBeBold);
|
||||
/** Returns a copy of this font with the bold attribute set. */
|
||||
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 font's kerning.
|
||||
|
||||
This is the extra space added between adjacent characters, as a proportion
|
||||
of the font's height.
|
||||
|
||||
A value of zero is normal spacing, positive values will spread the letters
|
||||
out more, and negative values make them closer together.
|
||||
*/
|
||||
float getExtraKerningFactor() const noexcept;
|
||||
|
||||
/** Returns a copy of this font with a new kerning factor.
|
||||
@param extraKerning a multiple of the font's height that will be added
|
||||
to space between the characters. So a value of zero is
|
||||
normal spacing, positive values spread the letters out,
|
||||
negative values make them closer together.
|
||||
*/
|
||||
Font withExtraKerningFactor (float extraKerning) const;
|
||||
|
||||
/** Changes the font's kerning.
|
||||
@param extraKerning a multiple of the font's height that will be added
|
||||
to space between the characters. So a value of zero is
|
||||
normal spacing, positive values spread the letters out,
|
||||
negative values make them closer together.
|
||||
*/
|
||||
void setExtraKerningFactor (float extraKerning);
|
||||
|
||||
//==============================================================================
|
||||
/** Changes all the font's characteristics with one call. */
|
||||
void setSizeAndStyle (float newHeight,
|
||||
int newStyleFlags,
|
||||
float newHorizontalScale,
|
||||
float newKerningAmount);
|
||||
|
||||
/** Changes all the font's characteristics with one call. */
|
||||
void setSizeAndStyle (float newHeight,
|
||||
const String& newStyle,
|
||||
float newHorizontalScale,
|
||||
float newKerningAmount);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the total width of a string as it would be drawn using this font.
|
||||
For a more accurate floating-point result, use getStringWidthFloat().
|
||||
*/
|
||||
int getStringWidth (const String& text) const;
|
||||
|
||||
/** Returns the total width of a string as it would be drawn using this font.
|
||||
@see getStringWidth
|
||||
*/
|
||||
float getStringWidthFloat (const String& text) const;
|
||||
|
||||
/** Returns the series of glyph numbers and their x offsets needed to represent a string.
|
||||
|
||||
An extra x offset is added at the end of the run, to indicate where the right hand
|
||||
edge of the last character is.
|
||||
*/
|
||||
void getGlyphPositions (const String& text, Array <int>& glyphs, Array <float>& xOffsets) const;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the typeface used by this font.
|
||||
|
||||
Note that the object returned may go out of scope if this font is deleted
|
||||
or has its style changed.
|
||||
*/
|
||||
Typeface* getTypeface() const;
|
||||
|
||||
/** Creates an array of Font objects to represent all the fonts on the system.
|
||||
|
||||
If you just need the font family names of the typefaces, you can also use
|
||||
findAllTypefaceNames() instead.
|
||||
|
||||
@param results the array to which new Font objects will be added.
|
||||
*/
|
||||
static void findFonts (Array<Font>& results);
|
||||
|
||||
/** Returns a list of all the available typeface font families.
|
||||
|
||||
The names returned can be passed into setTypefaceName().
|
||||
|
||||
You can use this instead of findFonts() if you only need their font family names,
|
||||
and not font objects.
|
||||
*/
|
||||
static StringArray findAllTypefaceNames();
|
||||
|
||||
/** Returns a list of all the available typeface font styles.
|
||||
|
||||
The names returned can be passed into setTypefaceStyle().
|
||||
|
||||
You can use this instead of findFonts() if you only need their styles, and not
|
||||
font objects.
|
||||
*/
|
||||
static StringArray findAllTypefaceStyles (const String& family);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the font family of the typeface to be used for rendering glyphs that aren't
|
||||
found in the requested typeface.
|
||||
*/
|
||||
static const String& getFallbackFontName();
|
||||
|
||||
/** Sets the (platform-specific) font family of the typeface to use to find glyphs that
|
||||
aren't available in whatever font you're trying to use.
|
||||
*/
|
||||
static void setFallbackFontName (const String& name);
|
||||
|
||||
/** Returns the font style of the typeface to be used for rendering glyphs that aren't
|
||||
found in the requested typeface.
|
||||
*/
|
||||
static const String& getFallbackFontStyle();
|
||||
|
||||
/** Sets the (platform-specific) font style of the typeface to use to find glyphs that
|
||||
aren't available in whatever font you're trying to use.
|
||||
*/
|
||||
static void setFallbackFontStyle (const String& style);
|
||||
|
||||
//==============================================================================
|
||||
/** Creates a string to describe this font.
|
||||
The string will contain information to describe the font's typeface, size, and
|
||||
style. To recreate the font from this string, use fromString().
|
||||
*/
|
||||
String toString() const;
|
||||
|
||||
/** Recreates a font from its stringified encoding.
|
||||
This method takes a string that was created by toString(), and recreates the
|
||||
original font.
|
||||
*/
|
||||
static Font fromString (const String& fontDescription);
|
||||
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
class SharedFontInternal;
|
||||
ReferenceCountedObjectPtr<SharedFontInternal> font;
|
||||
void dupeInternalIfShared();
|
||||
void checkTypefaceSuitability();
|
||||
float getHeightToPointsFactor() const;
|
||||
|
||||
JUCE_LEAK_DETECTOR (Font)
|
||||
};
|
||||
|
||||
#endif // JUCE_FONT_H_INCLUDED
|
||||
|
|
@ -0,0 +1,798 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
PositionedGlyph::PositionedGlyph() noexcept
|
||||
: character (0), glyph (0), x (0), y (0), w (0), whitespace (false)
|
||||
{
|
||||
}
|
||||
|
||||
PositionedGlyph::PositionedGlyph (const Font& font_, const juce_wchar character_, const int glyph_,
|
||||
const float x_, const float y_, const float w_, const bool whitespace_)
|
||||
: font (font_), character (character_), glyph (glyph_),
|
||||
x (x_), y (y_), w (w_), whitespace (whitespace_)
|
||||
{
|
||||
}
|
||||
|
||||
PositionedGlyph::PositionedGlyph (const PositionedGlyph& other)
|
||||
: font (other.font), character (other.character), glyph (other.glyph),
|
||||
x (other.x), y (other.y), w (other.w), whitespace (other.whitespace)
|
||||
{
|
||||
}
|
||||
|
||||
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS
|
||||
PositionedGlyph::PositionedGlyph (PositionedGlyph&& other) noexcept
|
||||
: font (static_cast<Font&&> (other.font)),
|
||||
character (other.character), glyph (other.glyph),
|
||||
x (other.x), y (other.y), w (other.w), whitespace (other.whitespace)
|
||||
{
|
||||
}
|
||||
|
||||
PositionedGlyph& PositionedGlyph::operator= (PositionedGlyph&& other) noexcept
|
||||
{
|
||||
font = static_cast<Font&&> (other.font);
|
||||
character = other.character;
|
||||
glyph = other.glyph;
|
||||
x = other.x;
|
||||
y = other.y;
|
||||
w = other.w;
|
||||
whitespace = other.whitespace;
|
||||
return *this;
|
||||
}
|
||||
#endif
|
||||
|
||||
PositionedGlyph::~PositionedGlyph() {}
|
||||
|
||||
PositionedGlyph& PositionedGlyph::operator= (const PositionedGlyph& other)
|
||||
{
|
||||
font = other.font;
|
||||
character = other.character;
|
||||
glyph = other.glyph;
|
||||
x = other.x;
|
||||
y = other.y;
|
||||
w = other.w;
|
||||
whitespace = other.whitespace;
|
||||
return *this;
|
||||
}
|
||||
|
||||
static inline void drawGlyphWithFont (const Graphics& g, int glyph, const Font& font, const AffineTransform& t)
|
||||
{
|
||||
LowLevelGraphicsContext& context = g.getInternalContext();
|
||||
context.setFont (font);
|
||||
context.drawGlyph (glyph, t);
|
||||
}
|
||||
|
||||
void PositionedGlyph::draw (const Graphics& g) const
|
||||
{
|
||||
if (! isWhitespace())
|
||||
drawGlyphWithFont (g, glyph, font, AffineTransform::translation (x, y));
|
||||
}
|
||||
|
||||
void PositionedGlyph::draw (const Graphics& g, const AffineTransform& transform) const
|
||||
{
|
||||
if (! isWhitespace())
|
||||
drawGlyphWithFont (g, glyph, font, AffineTransform::translation (x, y).followedBy (transform));
|
||||
}
|
||||
|
||||
void PositionedGlyph::createPath (Path& path) const
|
||||
{
|
||||
if (! isWhitespace())
|
||||
{
|
||||
if (Typeface* const t = font.getTypeface())
|
||||
{
|
||||
Path p;
|
||||
t->getOutlineForGlyph (glyph, p);
|
||||
|
||||
path.addPath (p, AffineTransform::scale (font.getHeight() * font.getHorizontalScale(), font.getHeight())
|
||||
.translated (x, y));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool PositionedGlyph::hitTest (float px, float py) const
|
||||
{
|
||||
if (getBounds().contains (px, py) && ! isWhitespace())
|
||||
{
|
||||
if (Typeface* const t = font.getTypeface())
|
||||
{
|
||||
Path p;
|
||||
t->getOutlineForGlyph (glyph, p);
|
||||
|
||||
AffineTransform::translation (-x, -y)
|
||||
.scaled (1.0f / (font.getHeight() * font.getHorizontalScale()), 1.0f / font.getHeight())
|
||||
.transformPoint (px, py);
|
||||
|
||||
return p.contains (px, py);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void PositionedGlyph::moveBy (const float deltaX,
|
||||
const float deltaY)
|
||||
{
|
||||
x += deltaX;
|
||||
y += deltaY;
|
||||
}
|
||||
|
||||
|
||||
//==============================================================================
|
||||
GlyphArrangement::GlyphArrangement()
|
||||
{
|
||||
glyphs.ensureStorageAllocated (128);
|
||||
}
|
||||
|
||||
GlyphArrangement::GlyphArrangement (const GlyphArrangement& other)
|
||||
: glyphs (other.glyphs)
|
||||
{
|
||||
}
|
||||
|
||||
GlyphArrangement& GlyphArrangement::operator= (const GlyphArrangement& other)
|
||||
{
|
||||
glyphs = other.glyphs;
|
||||
return *this;
|
||||
}
|
||||
|
||||
GlyphArrangement::~GlyphArrangement()
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void GlyphArrangement::clear()
|
||||
{
|
||||
glyphs.clear();
|
||||
}
|
||||
|
||||
PositionedGlyph& GlyphArrangement::getGlyph (const int index) const noexcept
|
||||
{
|
||||
return glyphs.getReference (index);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void GlyphArrangement::addGlyphArrangement (const GlyphArrangement& other)
|
||||
{
|
||||
glyphs.addArray (other.glyphs);
|
||||
}
|
||||
|
||||
void GlyphArrangement::addGlyph (const PositionedGlyph& glyph)
|
||||
{
|
||||
glyphs.add (glyph);
|
||||
}
|
||||
|
||||
void GlyphArrangement::removeRangeOfGlyphs (int startIndex, const int num)
|
||||
{
|
||||
glyphs.removeRange (startIndex, num < 0 ? glyphs.size() : num);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void GlyphArrangement::addLineOfText (const Font& font,
|
||||
const String& text,
|
||||
const float xOffset,
|
||||
const float yOffset)
|
||||
{
|
||||
addCurtailedLineOfText (font, text, xOffset, yOffset, 1.0e10f, false);
|
||||
}
|
||||
|
||||
void GlyphArrangement::addCurtailedLineOfText (const Font& font,
|
||||
const String& text,
|
||||
const float xOffset,
|
||||
const float yOffset,
|
||||
const float maxWidthPixels,
|
||||
const bool useEllipsis)
|
||||
{
|
||||
if (text.isNotEmpty())
|
||||
{
|
||||
Array<int> newGlyphs;
|
||||
Array<float> xOffsets;
|
||||
font.getGlyphPositions (text, newGlyphs, xOffsets);
|
||||
const int textLen = newGlyphs.size();
|
||||
glyphs.ensureStorageAllocated (glyphs.size() + textLen);
|
||||
|
||||
String::CharPointerType t (text.getCharPointer());
|
||||
|
||||
for (int i = 0; i < textLen; ++i)
|
||||
{
|
||||
const float 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;
|
||||
}
|
||||
else
|
||||
{
|
||||
const float thisX = xOffsets.getUnchecked (i);
|
||||
const 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, const float maxXPos,
|
||||
const int startIndex, int endIndex)
|
||||
{
|
||||
int numDeleted = 0;
|
||||
|
||||
if (glyphs.size() > 0)
|
||||
{
|
||||
Array<int> dotGlyphs;
|
||||
Array<float> dotXs;
|
||||
font.getGlyphPositions ("..", dotGlyphs, dotXs);
|
||||
|
||||
const float dx = dotXs[1];
|
||||
float xOffset = 0.0f, yOffset = 0.0f;
|
||||
|
||||
while (endIndex > startIndex)
|
||||
{
|
||||
const PositionedGlyph& 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,
|
||||
const float maxLineWidth,
|
||||
Justification horizontalLayout)
|
||||
{
|
||||
int lineStartIndex = glyphs.size();
|
||||
addLineOfText (font, text, x, y);
|
||||
|
||||
const float originalY = y;
|
||||
|
||||
while (lineStartIndex < glyphs.size())
|
||||
{
|
||||
int i = lineStartIndex;
|
||||
|
||||
if (glyphs.getReference(i).getCharacter() != '\n'
|
||||
&& glyphs.getReference(i).getCharacter() != '\r')
|
||||
++i;
|
||||
|
||||
const float lineMaxX = glyphs.getReference (lineStartIndex).getLeft() + maxLineWidth;
|
||||
int lastWordBreakIndex = -1;
|
||||
|
||||
while (i < glyphs.size())
|
||||
{
|
||||
const PositionedGlyph& pg = glyphs.getReference (i);
|
||||
const juce_wchar c = pg.getCharacter();
|
||||
|
||||
if (c == '\r' || c == '\n')
|
||||
{
|
||||
++i;
|
||||
|
||||
if (c == '\r' && i < glyphs.size()
|
||||
&& glyphs.getReference(i).getCharacter() == '\n')
|
||||
++i;
|
||||
|
||||
break;
|
||||
}
|
||||
else if (pg.isWhitespace())
|
||||
{
|
||||
lastWordBreakIndex = i + 1;
|
||||
}
|
||||
else if (pg.getRight() - 0.0001f >= lineMaxX)
|
||||
{
|
||||
if (lastWordBreakIndex >= 0)
|
||||
i = lastWordBreakIndex;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
++i;
|
||||
}
|
||||
|
||||
const float currentLineStartX = glyphs.getReference (lineStartIndex).getLeft();
|
||||
float currentLineEndX = currentLineStartX;
|
||||
|
||||
for (int j = i; --j >= lineStartIndex;)
|
||||
{
|
||||
if (! glyphs.getReference (j).isWhitespace())
|
||||
{
|
||||
currentLineEndX = glyphs.getReference (j).getRight();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
float deltaX = 0.0f;
|
||||
|
||||
if (horizontalLayout.testFlags (Justification::horizontallyJustified))
|
||||
spreadOutLine (lineStartIndex, i - lineStartIndex, maxLineWidth);
|
||||
else if (horizontalLayout.testFlags (Justification::horizontallyCentred))
|
||||
deltaX = (maxLineWidth - (currentLineEndX - currentLineStartX)) * 0.5f;
|
||||
else if (horizontalLayout.testFlags (Justification::right))
|
||||
deltaX = maxLineWidth - (currentLineEndX - currentLineStartX);
|
||||
|
||||
moveRangeOfGlyphs (lineStartIndex, i - lineStartIndex,
|
||||
x + deltaX - currentLineStartX, y - originalY);
|
||||
|
||||
lineStartIndex = i;
|
||||
|
||||
y += font.getHeight();
|
||||
}
|
||||
}
|
||||
|
||||
void GlyphArrangement::addFittedText (const Font& f,
|
||||
const String& text,
|
||||
const float x, const float y,
|
||||
const float width, const float height,
|
||||
Justification layout,
|
||||
int maximumLines,
|
||||
const float minimumHorizontalScale)
|
||||
{
|
||||
// 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
|
||||
{
|
||||
const int startIndex = glyphs.size();
|
||||
const String trimmed (text.trim());
|
||||
addLineOfText (f, trimmed, x, y);
|
||||
const int numGlyphs = glyphs.size() - startIndex;
|
||||
|
||||
if (numGlyphs > 0)
|
||||
{
|
||||
const float 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);
|
||||
|
||||
const Rectangle<float> bb (ga.getBoundingBox (0, -1, false));
|
||||
|
||||
float 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;
|
||||
const float lineStartX = glyphs.getReference (start).getLeft();
|
||||
float 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,
|
||||
const float horizontalScaleFactor)
|
||||
{
|
||||
jassert (startIndex >= 0);
|
||||
|
||||
if (num < 0 || startIndex + num > glyphs.size())
|
||||
num = glyphs.size() - startIndex;
|
||||
|
||||
if (num > 0)
|
||||
{
|
||||
const float xAnchor = glyphs.getReference (startIndex).getLeft();
|
||||
|
||||
while (--num >= 0)
|
||||
{
|
||||
PositionedGlyph& 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, const bool includeWhitespace) const
|
||||
{
|
||||
jassert (startIndex >= 0);
|
||||
|
||||
if (num < 0 || startIndex + num > glyphs.size())
|
||||
num = glyphs.size() - startIndex;
|
||||
|
||||
Rectangle<float> result;
|
||||
|
||||
while (--num >= 0)
|
||||
{
|
||||
const PositionedGlyph& pg = glyphs.getReference (startIndex++);
|
||||
|
||||
if (includeWhitespace || ! pg.isWhitespace())
|
||||
result = result.getUnion (pg.getBounds());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void GlyphArrangement::justifyGlyphs (const int startIndex, const int num,
|
||||
const float x, const float y, const float width, const float height,
|
||||
Justification justification)
|
||||
{
|
||||
jassert (num >= 0 && startIndex >= 0);
|
||||
|
||||
if (glyphs.size() > 0 && num > 0)
|
||||
{
|
||||
const Rectangle<float> bb (getBoundingBox (startIndex, num, ! justification.testFlags (Justification::horizontallyJustified
|
||||
| Justification::horizontallyCentred)));
|
||||
float deltaX = 0.0f, deltaY = 0.0f;
|
||||
|
||||
if (justification.testFlags (Justification::horizontallyJustified)) deltaX = x - bb.getX();
|
||||
else if (justification.testFlags (Justification::horizontallyCentred)) deltaX = x + (width - bb.getWidth()) * 0.5f - bb.getX();
|
||||
else if (justification.testFlags (Justification::right)) deltaX = x + width - bb.getRight();
|
||||
else deltaX = x - bb.getX();
|
||||
|
||||
if (justification.testFlags (Justification::top)) deltaY = y - bb.getY();
|
||||
else if (justification.testFlags (Justification::bottom)) deltaY = y + height - bb.getBottom();
|
||||
else deltaY = y + (height - bb.getHeight()) * 0.5f - bb.getY();
|
||||
|
||||
moveRangeOfGlyphs (startIndex, num, deltaX, deltaY);
|
||||
|
||||
if (justification.testFlags (Justification::horizontallyJustified))
|
||||
{
|
||||
int lineStart = 0;
|
||||
float baseY = glyphs.getReference (startIndex).getBaselineY();
|
||||
|
||||
int i;
|
||||
for (i = 0; i < num; ++i)
|
||||
{
|
||||
const float 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 (const int start, const int num, const 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)
|
||||
{
|
||||
const float startX = glyphs.getReference (start).getLeft();
|
||||
const float endX = glyphs.getReference (start + num - 1 - spacesAtEnd).getRight();
|
||||
|
||||
const float 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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)
|
||||
{
|
||||
const int length = text.length();
|
||||
const int originalStartIndex = startIndex;
|
||||
int numLines = 1;
|
||||
|
||||
if (length <= 12 && ! text.containsAnyOf (" -\t\r\n"))
|
||||
maximumLines = 1;
|
||||
|
||||
maximumLines = jmin (maximumLines, length);
|
||||
|
||||
while (numLines < maximumLines)
|
||||
{
|
||||
++numLines;
|
||||
|
||||
const float newFontHeight = height / (float) numLines;
|
||||
|
||||
if (newFontHeight < font.getHeight())
|
||||
{
|
||||
font.setHeight (jmax (8.0f, newFontHeight));
|
||||
|
||||
removeRangeOfGlyphs (startIndex, -1);
|
||||
addLineOfText (font, text, x, y);
|
||||
|
||||
lineWidth = glyphs.getReference (glyphs.size() - 1).getRight()
|
||||
- glyphs.getReference (startIndex).getLeft();
|
||||
}
|
||||
|
||||
// Try to estimate the point at which there are enough lines to fit the text,
|
||||
// allowing for unevenness in the lengths due to differently sized words.
|
||||
const float lineLengthUnevennessAllowance = 80.0f;
|
||||
|
||||
if (numLines > (lineWidth + lineLengthUnevennessAllowance) / width || newFontHeight < 8.0f)
|
||||
break;
|
||||
}
|
||||
|
||||
if (numLines < 1)
|
||||
numLines = 1;
|
||||
|
||||
float lineY = y;
|
||||
float widthPerLine = lineWidth / numLines;
|
||||
|
||||
for (int line = 0; line < numLines; ++line)
|
||||
{
|
||||
int i = startIndex;
|
||||
float lineStartX = glyphs.getReference (startIndex).getLeft();
|
||||
|
||||
if (line == numLines - 1)
|
||||
{
|
||||
widthPerLine = width;
|
||||
i = glyphs.size();
|
||||
}
|
||||
else
|
||||
{
|
||||
while (i < glyphs.size())
|
||||
{
|
||||
lineWidth = (glyphs.getReference (i).getRight() - lineStartX);
|
||||
|
||||
if (lineWidth > widthPerLine)
|
||||
{
|
||||
// got to a point where the line's too long, so skip forward to find a
|
||||
// good place to break it..
|
||||
const int searchStartIndex = i;
|
||||
|
||||
while (i < glyphs.size())
|
||||
{
|
||||
if ((glyphs.getReference (i).getRight() - lineStartX) * minimumHorizontalScale < width)
|
||||
{
|
||||
if (glyphs.getReference (i).isWhitespace()
|
||||
|| glyphs.getReference (i).getCharacter() == '-')
|
||||
{
|
||||
++i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// can't find a suitable break, so try looking backwards..
|
||||
i = searchStartIndex;
|
||||
|
||||
for (int back = 1; back < jmin (7, i - startIndex - 1); ++back)
|
||||
{
|
||||
if (glyphs.getReference (i - back).isWhitespace()
|
||||
|| glyphs.getReference (i - back).getCharacter() == '-')
|
||||
{
|
||||
i -= back - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
++i;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
++i;
|
||||
}
|
||||
|
||||
int wsStart = i;
|
||||
while (wsStart > 0 && glyphs.getReference (wsStart - 1).isWhitespace())
|
||||
--wsStart;
|
||||
|
||||
int wsEnd = i;
|
||||
while (wsEnd < glyphs.size() && glyphs.getReference (wsEnd).isWhitespace())
|
||||
++wsEnd;
|
||||
|
||||
removeRangeOfGlyphs (wsStart, wsEnd - wsStart);
|
||||
i = jmax (wsStart, startIndex + 1);
|
||||
}
|
||||
|
||||
i -= fitLineIntoSpace (startIndex, i - startIndex,
|
||||
x, lineY, width, font.getHeight(), font,
|
||||
layout.getOnlyHorizontalFlags() | Justification::verticallyCentred,
|
||||
minimumHorizontalScale);
|
||||
|
||||
startIndex = i;
|
||||
lineY += font.getHeight();
|
||||
|
||||
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,
|
||||
const int i, const AffineTransform& transform) const
|
||||
{
|
||||
const float lineThickness = (pg.font.getDescent()) * 0.3f;
|
||||
|
||||
float 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
|
||||
{
|
||||
for (int i = 0; i < glyphs.size(); ++i)
|
||||
{
|
||||
const PositionedGlyph& pg = glyphs.getReference(i);
|
||||
|
||||
if (pg.font.isUnderlined())
|
||||
drawGlyphUnderline (g, pg, i, AffineTransform::identity);
|
||||
|
||||
pg.draw (g);
|
||||
}
|
||||
}
|
||||
|
||||
void GlyphArrangement::draw (const Graphics& g, const AffineTransform& transform) const
|
||||
{
|
||||
for (int i = 0; i < glyphs.size(); ++i)
|
||||
{
|
||||
const PositionedGlyph& pg = glyphs.getReference(i);
|
||||
|
||||
if (pg.font.isUnderlined())
|
||||
drawGlyphUnderline (g, pg, i, transform);
|
||||
|
||||
pg.draw (g, transform);
|
||||
}
|
||||
}
|
||||
|
||||
void GlyphArrangement::createPath (Path& path) const
|
||||
{
|
||||
for (int i = 0; i < glyphs.size(); ++i)
|
||||
glyphs.getReference (i).createPath (path);
|
||||
}
|
||||
|
||||
int GlyphArrangement::findGlyphIndexAt (const float x, const float y) const
|
||||
{
|
||||
for (int i = 0; i < glyphs.size(); ++i)
|
||||
if (glyphs.getReference (i).hitTest (x, y))
|
||||
return i;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
|
@ -0,0 +1,319 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifndef JUCE_GLYPHARRANGEMENT_H_INCLUDED
|
||||
#define JUCE_GLYPHARRANGEMENT_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
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
|
||||
*/
|
||||
class JUCE_API PositionedGlyph
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
PositionedGlyph() noexcept;
|
||||
PositionedGlyph (const Font& font, juce_wchar character, int glyphNumber,
|
||||
float anchorX, float baselineY, float width, bool isWhitespace);
|
||||
|
||||
PositionedGlyph (const PositionedGlyph&);
|
||||
PositionedGlyph& operator= (const PositionedGlyph&);
|
||||
|
||||
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS
|
||||
PositionedGlyph (PositionedGlyph&&) noexcept;
|
||||
PositionedGlyph& operator= (PositionedGlyph&&) noexcept;
|
||||
#endif
|
||||
|
||||
~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 Rectangle<float> (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. */
|
||||
void draw (const Graphics& g) const;
|
||||
|
||||
/** Draws the glyph into a graphics context, with an extra transform applied to it. */
|
||||
void draw (const Graphics& g, const 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
|
||||
*/
|
||||
class JUCE_API GlyphArrangement
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an empty arrangement. */
|
||||
GlyphArrangement();
|
||||
|
||||
/** Takes a copy of another arrangement. */
|
||||
GlyphArrangement (const GlyphArrangement&);
|
||||
|
||||
/** Copies another arrangement onto this one.
|
||||
To add another arrangement without clearing this one, use addGlyphArrangement().
|
||||
*/
|
||||
GlyphArrangement& operator= (const GlyphArrangement&);
|
||||
|
||||
/** Destructor. */
|
||||
~GlyphArrangement();
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the total number of glyphs in the arrangement. */
|
||||
int getNumGlyphs() const noexcept { return glyphs.size(); }
|
||||
|
||||
/** Returns one of the glyphs from the arrangement.
|
||||
|
||||
@param index the glyph's index, from 0 to (getNumGlyphs() - 1). Be
|
||||
careful not to pass an out-of-range index here, as it
|
||||
doesn't do any bounds-checking.
|
||||
*/
|
||||
PositionedGlyph& getGlyph (int index) const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Clears all text from the arrangement and resets it. */
|
||||
void clear();
|
||||
|
||||
/** Appends a line of text to the arrangement.
|
||||
|
||||
This will add the text as a single line, where x is the left-hand edge of the
|
||||
first character, and y is the position for the text's baseline.
|
||||
|
||||
If the text contains new-lines or carriage-returns, this will ignore them - use
|
||||
addJustifiedText() to add multi-line arrangements.
|
||||
*/
|
||||
void addLineOfText (const Font& font,
|
||||
const String& text,
|
||||
float x, float y);
|
||||
|
||||
/** Adds a line of text, truncating it if it's wider than a specified size.
|
||||
|
||||
This is the same as addLineOfText(), but if the line's width exceeds the value
|
||||
specified in maxWidthPixels, it will be truncated using either ellipsis (i.e. dots: "..."),
|
||||
if useEllipsis is true, or if this is false, it will just drop any subsequent characters.
|
||||
*/
|
||||
void addCurtailedLineOfText (const Font& font,
|
||||
const String& text,
|
||||
float x, float y,
|
||||
float maxWidthPixels,
|
||||
bool useEllipsis);
|
||||
|
||||
/** Adds some multi-line text, breaking lines at word-boundaries if they are too wide.
|
||||
|
||||
This will add text to the arrangement, breaking it into new lines either where there
|
||||
is a new-line or carriage-return character in the text, or where a line's width
|
||||
exceeds the value set in maxLineWidth.
|
||||
|
||||
Each line that is added will be laid out using the flags set in horizontalLayout, so
|
||||
the lines can be left- or right-justified, or centred horizontally in the space
|
||||
between x and (x + maxLineWidth).
|
||||
|
||||
The y coordinate is the position of the baseline of the first line of text - subsequent
|
||||
lines will be placed below it, separated by a distance of font.getHeight().
|
||||
*/
|
||||
void addJustifiedText (const Font& font,
|
||||
const String& text,
|
||||
float x, float y,
|
||||
float maxLineWidth,
|
||||
Justification horizontalLayout);
|
||||
|
||||
/** Tries to fit some text withing a given space.
|
||||
|
||||
This does its best to make the given text readable within the specified rectangle,
|
||||
so it useful for labelling things.
|
||||
|
||||
If the text is too big, it'll be squashed horizontally or broken over multiple lines
|
||||
if the maximumLinesToUse value allows this. If the text just won't fit into the space,
|
||||
it'll cram as much as possible in there, and put some ellipsis at the end to show that
|
||||
it's been truncated.
|
||||
|
||||
A Justification parameter lets you specify how the text is laid out within the rectangle,
|
||||
both horizontally and vertically.
|
||||
|
||||
@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.7f);
|
||||
|
||||
/** 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&, const 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&, const 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, const AffineTransform&) const;
|
||||
|
||||
JUCE_LEAK_DETECTOR (GlyphArrangement)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_GLYPHARRANGEMENT_H_INCLUDED
|
||||
|
|
@ -0,0 +1,620 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
TextLayout::Glyph::Glyph (const int glyphCode_, Point<float> anchor_, float width_) noexcept
|
||||
: glyphCode (glyphCode_), anchor (anchor_), width (width_)
|
||||
{
|
||||
}
|
||||
|
||||
TextLayout::Glyph::Glyph (const Glyph& other) noexcept
|
||||
: glyphCode (other.glyphCode), anchor (other.anchor), width (other.width)
|
||||
{
|
||||
}
|
||||
|
||||
TextLayout::Glyph& TextLayout::Glyph::operator= (const Glyph& other) noexcept
|
||||
{
|
||||
glyphCode = other.glyphCode;
|
||||
anchor = other.anchor;
|
||||
width = other.width;
|
||||
return *this;
|
||||
}
|
||||
|
||||
TextLayout::Glyph::~Glyph() noexcept {}
|
||||
|
||||
//==============================================================================
|
||||
TextLayout::Run::Run() noexcept
|
||||
: colour (0xff000000)
|
||||
{
|
||||
}
|
||||
|
||||
TextLayout::Run::Run (Range<int> range, const int numGlyphsToPreallocate)
|
||||
: colour (0xff000000), stringRange (range)
|
||||
{
|
||||
glyphs.ensureStorageAllocated (numGlyphsToPreallocate);
|
||||
}
|
||||
|
||||
TextLayout::Run::Run (const Run& other)
|
||||
: font (other.font),
|
||||
colour (other.colour),
|
||||
glyphs (other.glyphs),
|
||||
stringRange (other.stringRange)
|
||||
{
|
||||
}
|
||||
|
||||
TextLayout::Run::~Run() noexcept {}
|
||||
|
||||
//==============================================================================
|
||||
TextLayout::Line::Line() noexcept
|
||||
: ascent (0.0f), descent (0.0f), leading (0.0f)
|
||||
{
|
||||
}
|
||||
|
||||
TextLayout::Line::Line (Range<int> stringRange_, Point<float> lineOrigin_,
|
||||
const float ascent_, const float descent_, const float leading_,
|
||||
const int numRunsToPreallocate)
|
||||
: stringRange (stringRange_), lineOrigin (lineOrigin_),
|
||||
ascent (ascent_), descent (descent_), leading (leading_)
|
||||
{
|
||||
runs.ensureStorageAllocated (numRunsToPreallocate);
|
||||
}
|
||||
|
||||
TextLayout::Line::Line (const Line& other)
|
||||
: stringRange (other.stringRange), lineOrigin (other.lineOrigin),
|
||||
ascent (other.ascent), descent (other.descent), leading (other.leading)
|
||||
{
|
||||
runs.addCopiesOf (other.runs);
|
||||
}
|
||||
|
||||
TextLayout::Line::~Line() noexcept
|
||||
{
|
||||
}
|
||||
|
||||
Range<float> TextLayout::Line::getLineBoundsX() const noexcept
|
||||
{
|
||||
Range<float> range;
|
||||
bool isFirst = true;
|
||||
|
||||
for (int i = runs.size(); --i >= 0;)
|
||||
{
|
||||
const Run& run = *runs.getUnchecked(i);
|
||||
|
||||
if (run.glyphs.size() > 0)
|
||||
{
|
||||
float minX = run.glyphs.getReference(0).anchor.x;
|
||||
float maxX = minX;
|
||||
|
||||
for (int j = run.glyphs.size(); --j >= 0;)
|
||||
{
|
||||
const Glyph& glyph = run.glyphs.getReference (j);
|
||||
const float x = glyph.anchor.x;
|
||||
minX = jmin (minX, x);
|
||||
maxX = jmax (maxX, x + glyph.width);
|
||||
}
|
||||
|
||||
if (isFirst)
|
||||
{
|
||||
isFirst = false;
|
||||
range = Range<float> (minX, maxX);
|
||||
}
|
||||
else
|
||||
{
|
||||
range = range.getUnionWith (Range<float> (minX, maxX));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return range + lineOrigin.x;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
TextLayout::TextLayout()
|
||||
: width (0), justification (Justification::topLeft)
|
||||
{
|
||||
}
|
||||
|
||||
TextLayout::TextLayout (const TextLayout& other)
|
||||
: width (other.width),
|
||||
justification (other.justification)
|
||||
{
|
||||
lines.addCopiesOf (other.lines);
|
||||
}
|
||||
|
||||
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS
|
||||
TextLayout::TextLayout (TextLayout&& other) noexcept
|
||||
: lines (static_cast <OwnedArray<Line>&&> (other.lines)),
|
||||
width (other.width),
|
||||
justification (other.justification)
|
||||
{
|
||||
}
|
||||
|
||||
TextLayout& TextLayout::operator= (TextLayout&& other) noexcept
|
||||
{
|
||||
lines = static_cast <OwnedArray<Line>&&> (other.lines);
|
||||
width = other.width;
|
||||
justification = other.justification;
|
||||
return *this;
|
||||
}
|
||||
#endif
|
||||
|
||||
TextLayout& TextLayout::operator= (const TextLayout& other)
|
||||
{
|
||||
width = other.width;
|
||||
justification = other.justification;
|
||||
lines.clear();
|
||||
lines.addCopiesOf (other.lines);
|
||||
return *this;
|
||||
}
|
||||
|
||||
TextLayout::~TextLayout()
|
||||
{
|
||||
}
|
||||
|
||||
float TextLayout::getHeight() const noexcept
|
||||
{
|
||||
if (const Line* const lastLine = lines.getLast())
|
||||
return lastLine->lineOrigin.y + lastLine->descent;
|
||||
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
TextLayout::Line& TextLayout::getLine (const int index) const
|
||||
{
|
||||
return *lines[index];
|
||||
}
|
||||
|
||||
void TextLayout::ensureStorageAllocated (int numLinesNeeded)
|
||||
{
|
||||
lines.ensureStorageAllocated (numLinesNeeded);
|
||||
}
|
||||
|
||||
void TextLayout::addLine (Line* line)
|
||||
{
|
||||
lines.add (line);
|
||||
}
|
||||
|
||||
void TextLayout::draw (Graphics& g, const Rectangle<float>& area) const
|
||||
{
|
||||
const Point<float> origin (justification.appliedToRectangle (Rectangle<float> (width, getHeight()), area).getPosition());
|
||||
|
||||
LowLevelGraphicsContext& context = g.getInternalContext();
|
||||
|
||||
for (int i = 0; i < getNumLines(); ++i)
|
||||
{
|
||||
const Line& line = getLine (i);
|
||||
const Point<float> lineOrigin (origin + line.lineOrigin);
|
||||
|
||||
for (int j = 0; j < line.runs.size(); ++j)
|
||||
{
|
||||
const Run& run = *line.runs.getUnchecked (j);
|
||||
context.setFont (run.font);
|
||||
context.setFill (run.colour);
|
||||
|
||||
for (int k = 0; k < run.glyphs.size(); ++k)
|
||||
{
|
||||
const Glyph& glyph = run.glyphs.getReference (k);
|
||||
context.drawGlyph (glyph.glyphCode, AffineTransform::translation (lineOrigin.x + glyph.anchor.x,
|
||||
lineOrigin.y + glyph.anchor.y));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TextLayout::createLayout (const AttributedString& text, float maxWidth)
|
||||
{
|
||||
lines.clear();
|
||||
width = maxWidth;
|
||||
justification = text.getJustification();
|
||||
|
||||
if (! createNativeLayout (text))
|
||||
createStandardLayout (text);
|
||||
|
||||
recalculateWidth (text);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
namespace TextLayoutHelpers
|
||||
{
|
||||
struct FontAndColour
|
||||
{
|
||||
FontAndColour (const Font* f) noexcept : font (f), colour (0xff000000) {}
|
||||
|
||||
const Font* font;
|
||||
Colour colour;
|
||||
|
||||
bool operator!= (const FontAndColour& other) const noexcept
|
||||
{
|
||||
return (font != other.font && *font != *other.font) || colour != other.colour;
|
||||
}
|
||||
};
|
||||
|
||||
struct RunAttribute
|
||||
{
|
||||
RunAttribute (const FontAndColour& fc, const Range<int> r) noexcept
|
||||
: fontAndColour (fc), range (r)
|
||||
{}
|
||||
|
||||
FontAndColour fontAndColour;
|
||||
Range<int> range;
|
||||
};
|
||||
|
||||
struct Token
|
||||
{
|
||||
Token (const String& t, const Font& f, Colour c, const 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;
|
||||
|
||||
private:
|
||||
Token& operator= (const Token&);
|
||||
};
|
||||
|
||||
class TokenList
|
||||
{
|
||||
public:
|
||||
TokenList() noexcept : totalLines (0) {}
|
||||
|
||||
void createLayout (const AttributedString& text, TextLayout& layout)
|
||||
{
|
||||
tokens.ensureStorageAllocated (64);
|
||||
layout.ensureStorageAllocated (totalLines);
|
||||
|
||||
addTextRuns (text);
|
||||
layoutRuns (layout.getWidth());
|
||||
|
||||
int charPosition = 0;
|
||||
int lineStartPosition = 0;
|
||||
int runStartPosition = 0;
|
||||
|
||||
ScopedPointer<TextLayout::Line> currentLine;
|
||||
ScopedPointer<TextLayout::Run> currentRun;
|
||||
|
||||
bool needToSetLineOrigin = true;
|
||||
|
||||
for (int i = 0; i < tokens.size(); ++i)
|
||||
{
|
||||
const Token& t = *tokens.getUnchecked (i);
|
||||
|
||||
Array <int> newGlyphs;
|
||||
Array <float> xOffsets;
|
||||
t.font.getGlyphPositions (getTrimmedEndIfNotAllWhitespace (t.text), newGlyphs, xOffsets);
|
||||
|
||||
if (currentRun == nullptr) currentRun = new TextLayout::Run();
|
||||
if (currentLine == nullptr) currentLine = new TextLayout::Line();
|
||||
|
||||
if (newGlyphs.size() > 0)
|
||||
{
|
||||
currentRun->glyphs.ensureStorageAllocated (currentRun->glyphs.size() + newGlyphs.size());
|
||||
const Point<float> tokenOrigin (t.area.getPosition().translated (0, t.font.getAscent()));
|
||||
|
||||
if (needToSetLineOrigin)
|
||||
{
|
||||
needToSetLineOrigin = false;
|
||||
currentLine->lineOrigin = tokenOrigin;
|
||||
}
|
||||
|
||||
const Point<float> glyphOffset (tokenOrigin - currentLine->lineOrigin);
|
||||
|
||||
for (int j = 0; j < newGlyphs.size(); ++j)
|
||||
{
|
||||
const float x = xOffsets.getUnchecked (j);
|
||||
currentRun->glyphs.add (TextLayout::Glyph (newGlyphs.getUnchecked(j),
|
||||
glyphOffset.translated (x, 0),
|
||||
xOffsets.getUnchecked (j + 1) - x));
|
||||
}
|
||||
|
||||
charPosition += newGlyphs.size();
|
||||
}
|
||||
|
||||
if (t.isWhitespace || t.isNewLine)
|
||||
++charPosition;
|
||||
|
||||
const Token* const nextToken = tokens [i + 1];
|
||||
|
||||
if (nextToken == nullptr) // this is the last token
|
||||
{
|
||||
addRun (*currentLine, currentRun.release(), t, runStartPosition, charPosition);
|
||||
currentLine->stringRange = Range<int> (lineStartPosition, charPosition);
|
||||
|
||||
if (! needToSetLineOrigin)
|
||||
layout.addLine (currentLine.release());
|
||||
|
||||
needToSetLineOrigin = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
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 = new TextLayout::Run();
|
||||
|
||||
addRun (*currentLine, currentRun.release(), t, runStartPosition, charPosition);
|
||||
currentLine->stringRange = Range<int> (lineStartPosition, charPosition);
|
||||
|
||||
if (! needToSetLineOrigin)
|
||||
layout.addLine (currentLine.release());
|
||||
|
||||
runStartPosition = charPosition;
|
||||
lineStartPosition = charPosition;
|
||||
needToSetLineOrigin = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((text.getJustification().getFlags() & (Justification::right | Justification::horizontallyCentred)) != 0)
|
||||
{
|
||||
const float totalW = layout.getWidth();
|
||||
const bool isCentred = (text.getJustification().getFlags() & Justification::horizontallyCentred) != 0;
|
||||
|
||||
for (int i = 0; i < layout.getNumLines(); ++i)
|
||||
{
|
||||
float dx = totalW - layout.getLine(i).getLineBoundsX().getLength();
|
||||
|
||||
if (isCentred)
|
||||
dx /= 2.0f;
|
||||
|
||||
layout.getLine(i).lineOrigin.x += dx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static void addRun (TextLayout::Line& glyphLine, TextLayout::Run* glyphRun,
|
||||
const Token& t, const int start, const int end)
|
||||
{
|
||||
glyphRun->stringRange = Range<int> (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 (const juce_wchar c) noexcept
|
||||
{
|
||||
if (c == '\r' || c == '\n')
|
||||
return 0;
|
||||
|
||||
return CharacterFunctions::isWhitespace (c) ? 2 : 1;
|
||||
}
|
||||
|
||||
void appendText (const AttributedString& text, const Range<int> stringRange,
|
||||
const Font& font, Colour colour)
|
||||
{
|
||||
const String stringText (text.getText().substring (stringRange.getStart(), stringRange.getEnd()));
|
||||
String::CharPointerType t (stringText.getCharPointer());
|
||||
String currentString;
|
||||
int lastCharType = 0;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
const juce_wchar c = t.getAndAdvance();
|
||||
if (c == 0)
|
||||
break;
|
||||
|
||||
const int 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 (const float maxWidth)
|
||||
{
|
||||
float x = 0, y = 0, h = 0;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < tokens.size(); ++i)
|
||||
{
|
||||
Token& t = *tokens.getUnchecked(i);
|
||||
t.area.setPosition (x, y);
|
||||
t.line = totalLines;
|
||||
x += t.area.getWidth();
|
||||
h = jmax (h, t.area.getHeight());
|
||||
|
||||
const Token* const nextTok = tokens[i + 1];
|
||||
|
||||
if (nextTok == nullptr)
|
||||
break;
|
||||
|
||||
if (t.isNewLine || ((! nextTok->isWhitespace) && x + nextTok->area.getWidth() > maxWidth))
|
||||
{
|
||||
setLastLineHeight (i + 1, h);
|
||||
x = 0;
|
||||
y += h;
|
||||
h = 0;
|
||||
++totalLines;
|
||||
}
|
||||
}
|
||||
|
||||
setLastLineHeight (jmin (i + 1, tokens.size()), h);
|
||||
++totalLines;
|
||||
}
|
||||
|
||||
void setLastLineHeight (int i, const float height) noexcept
|
||||
{
|
||||
while (--i >= 0)
|
||||
{
|
||||
Token& tok = *tokens.getUnchecked (i);
|
||||
|
||||
if (tok.line == totalLines)
|
||||
tok.lineHeight = height;
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void addTextRuns (const AttributedString& text)
|
||||
{
|
||||
Font defaultFont;
|
||||
Array<RunAttribute> runAttributes;
|
||||
|
||||
{
|
||||
const int stringLength = text.getText().length();
|
||||
int rangeStart = 0;
|
||||
FontAndColour lastFontAndColour (&defaultFont);
|
||||
|
||||
// Iterate through every character in the string
|
||||
for (int i = 0; i < stringLength; ++i)
|
||||
{
|
||||
FontAndColour newFontAndColour (&defaultFont);
|
||||
const int numCharacterAttributes = text.getNumAttributes();
|
||||
|
||||
for (int j = 0; j < numCharacterAttributes; ++j)
|
||||
{
|
||||
const AttributedString::Attribute& attr = *text.getAttribute (j);
|
||||
|
||||
if (attr.range.contains (i))
|
||||
{
|
||||
if (const Font* f = attr.getFont()) newFontAndColour.font = f;
|
||||
if (const Colour* c = attr.getColour()) newFontAndColour.colour = *c;
|
||||
}
|
||||
}
|
||||
|
||||
if (i > 0 && newFontAndColour != lastFontAndColour)
|
||||
{
|
||||
runAttributes.add (RunAttribute (lastFontAndColour, Range<int> (rangeStart, i)));
|
||||
rangeStart = i;
|
||||
}
|
||||
|
||||
lastFontAndColour = newFontAndColour;
|
||||
}
|
||||
|
||||
if (rangeStart < stringLength)
|
||||
runAttributes.add (RunAttribute (lastFontAndColour, Range<int> (rangeStart, stringLength)));
|
||||
}
|
||||
|
||||
for (int i = 0; i < runAttributes.size(); ++i)
|
||||
{
|
||||
const RunAttribute& r = runAttributes.getReference(i);
|
||||
appendText (text, r.range, *(r.fontAndColour.font), r.fontAndColour.colour);
|
||||
}
|
||||
}
|
||||
|
||||
static String getTrimmedEndIfNotAllWhitespace (const String& s)
|
||||
{
|
||||
String trimmed (s.trimEnd());
|
||||
if (trimmed.isEmpty() && ! s.isEmpty())
|
||||
trimmed = s.replaceCharacters ("\r\n\t", " ");
|
||||
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
OwnedArray<Token> tokens;
|
||||
int totalLines;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (TokenList)
|
||||
};
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void TextLayout::createLayoutWithBalancedLineLengths (const AttributedString& text, float maxWidth)
|
||||
{
|
||||
const float minimumWidth = maxWidth / 2.0f;
|
||||
float bestWidth = maxWidth;
|
||||
float bestLineProportion = 0.0f;
|
||||
|
||||
while (maxWidth > minimumWidth)
|
||||
{
|
||||
createLayout (text, maxWidth);
|
||||
|
||||
if (getNumLines() < 2)
|
||||
return;
|
||||
|
||||
const float line1 = lines.getUnchecked (lines.size() - 1)->getLineBoundsX().getLength();
|
||||
const float line2 = lines.getUnchecked (lines.size() - 2)->getLineBoundsX().getLength();
|
||||
const float shortestLine = jmin (line1, line2);
|
||||
const float prop = (shortestLine > 0) ? jmax (line1, line2) / shortestLine : 1.0f;
|
||||
|
||||
if (prop > 0.9f)
|
||||
return;
|
||||
|
||||
if (prop > bestLineProportion)
|
||||
{
|
||||
bestLineProportion = prop;
|
||||
bestWidth = maxWidth;
|
||||
}
|
||||
|
||||
maxWidth -= 10.0f;
|
||||
}
|
||||
|
||||
if (bestWidth != maxWidth)
|
||||
createLayout (text, bestWidth);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void TextLayout::createStandardLayout (const AttributedString& text)
|
||||
{
|
||||
TextLayoutHelpers::TokenList l;
|
||||
l.createLayout (text, *this);
|
||||
}
|
||||
|
||||
void TextLayout::recalculateWidth (const AttributedString& text)
|
||||
{
|
||||
if (lines.size() > 0 && text.getReadingDirection() != AttributedString::rightToLeft)
|
||||
{
|
||||
Range<float> range (lines.getFirst()->getLineBoundsX());
|
||||
|
||||
for (int i = lines.size(); --i > 0;)
|
||||
range = range.getUnionWith (lines.getUnchecked(i)->getLineBoundsX());
|
||||
|
||||
for (int i = lines.size(); --i >= 0;)
|
||||
lines.getUnchecked(i)->lineOrigin.x -= range.getStart();
|
||||
|
||||
width = range.getLength();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,177 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifndef JUCE_TEXTLAYOUT_H_INCLUDED
|
||||
#define JUCE_TEXTLAYOUT_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
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
|
||||
*/
|
||||
class JUCE_API TextLayout
|
||||
{
|
||||
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&);
|
||||
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS
|
||||
TextLayout (TextLayout&& other) noexcept;
|
||||
TextLayout& operator= (TextLayout&&) noexcept;
|
||||
#endif
|
||||
|
||||
/** 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& text, 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& text, float maxWidth);
|
||||
|
||||
/** 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& g, const Rectangle<float>& area) const;
|
||||
|
||||
//==============================================================================
|
||||
/** A positioned glyph. */
|
||||
class JUCE_API Glyph
|
||||
{
|
||||
public:
|
||||
Glyph (int glyphCode, Point<float> anchor, float width) noexcept;
|
||||
Glyph (const Glyph&) noexcept;
|
||||
Glyph& operator= (const Glyph&) noexcept;
|
||||
~Glyph() noexcept;
|
||||
|
||||
/** The code number of this glyph. */
|
||||
int glyphCode;
|
||||
|
||||
/** The glyph's anchor point - this is relative to the line's origin.
|
||||
@see TextLayout::Line::lineOrigin
|
||||
*/
|
||||
Point<float> anchor;
|
||||
|
||||
float width;
|
||||
|
||||
private:
|
||||
JUCE_LEAK_DETECTOR (Glyph)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** A sequence of glyphs with a common font and colour. */
|
||||
class JUCE_API Run
|
||||
{
|
||||
public:
|
||||
Run() noexcept;
|
||||
Run (const Run&);
|
||||
Run (Range<int> stringRange, int numGlyphsToPreallocate);
|
||||
~Run() noexcept;
|
||||
|
||||
Font font; /**< The run's font. */
|
||||
Colour colour; /**< The run's colour. */
|
||||
Array<Glyph> glyphs; /**< The glyphs in this run. */
|
||||
Range<int> stringRange; /**< The character range that this run represents in the
|
||||
original string that was used to create it. */
|
||||
private:
|
||||
Run& operator= (const Run&);
|
||||
JUCE_LEAK_DETECTOR (Run)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** A line containing a sequence of glyph-runs. */
|
||||
class JUCE_API Line
|
||||
{
|
||||
public:
|
||||
Line() noexcept;
|
||||
Line (const Line&);
|
||||
Line (Range<int> stringRange, Point<float> lineOrigin,
|
||||
float ascent, float descent, float leading, int numRunsToPreallocate);
|
||||
~Line() noexcept;
|
||||
|
||||
/** Returns the X position range which contains all the glyphs in this line. */
|
||||
Range<float> getLineBoundsX() const noexcept;
|
||||
|
||||
OwnedArray<Run> runs; /**< The glyph-runs in this line. */
|
||||
Range<int> stringRange; /**< The character range that this line represents in the
|
||||
original string that was used to create it. */
|
||||
Point<float> lineOrigin; /**< The line's baseline origin. */
|
||||
float ascent, descent, leading;
|
||||
|
||||
private:
|
||||
Line& operator= (const Line&);
|
||||
JUCE_LEAK_DETECTOR (Line)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the maximum width of the content. */
|
||||
float getWidth() const noexcept { return width; }
|
||||
|
||||
/** Returns the maximum height of the content. */
|
||||
float getHeight() const noexcept;
|
||||
|
||||
/** 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;
|
||||
|
||||
/** Adds a line to the layout. The layout will take ownership of this line object
|
||||
and will delete it when it is no longer needed. */
|
||||
void addLine (Line* line);
|
||||
|
||||
/** Pre-allocates space for the specified number of lines. */
|
||||
void ensureStorageAllocated (int numLinesNeeded);
|
||||
|
||||
private:
|
||||
OwnedArray<Line> lines;
|
||||
float width;
|
||||
Justification justification;
|
||||
|
||||
void createStandardLayout (const AttributedString&);
|
||||
bool createNativeLayout (const AttributedString&);
|
||||
void recalculateWidth (const AttributedString&);
|
||||
|
||||
JUCE_LEAK_DETECTOR (TextLayout)
|
||||
};
|
||||
|
||||
#endif // JUCE_TEXTLAYOUT_H_INCLUDED
|
||||
|
|
@ -0,0 +1,260 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
struct FontStyleHelpers
|
||||
{
|
||||
static const char* getStyleName (const bool bold,
|
||||
const bool italic) noexcept
|
||||
{
|
||||
if (bold && italic) return "Bold Italic";
|
||||
if (bold) return "Bold";
|
||||
if (italic) return "Italic";
|
||||
return "Regular";
|
||||
}
|
||||
|
||||
static const char* getStyleName (const int styleFlags) noexcept
|
||||
{
|
||||
return getStyleName ((styleFlags & Font::bold) != 0,
|
||||
(styleFlags & Font::italic) != 0);
|
||||
}
|
||||
|
||||
static bool isBold (const String& style) noexcept
|
||||
{
|
||||
return style.containsWholeWordIgnoreCase ("Bold");
|
||||
}
|
||||
|
||||
static bool isItalic (const String& style) noexcept
|
||||
{
|
||||
return style.containsWholeWordIgnoreCase ("Italic")
|
||||
|| style.containsWholeWordIgnoreCase ("Oblique");
|
||||
}
|
||||
|
||||
static bool isPlaceholderFamilyName (const String& family)
|
||||
{
|
||||
return family == Font::getDefaultSansSerifFontName()
|
||||
|| family == Font::getDefaultSerifFontName()
|
||||
|| family == Font::getDefaultMonospacedFontName();
|
||||
}
|
||||
|
||||
struct ConcreteFamilyNames
|
||||
{
|
||||
ConcreteFamilyNames()
|
||||
: sans (findName (Font::getDefaultSansSerifFontName())),
|
||||
serif (findName (Font::getDefaultSerifFontName())),
|
||||
mono (findName (Font::getDefaultMonospacedFontName()))
|
||||
{
|
||||
}
|
||||
|
||||
String lookUp (const String& placeholder)
|
||||
{
|
||||
if (placeholder == Font::getDefaultSansSerifFontName()) return sans;
|
||||
if (placeholder == Font::getDefaultSerifFontName()) return serif;
|
||||
if (placeholder == Font::getDefaultMonospacedFontName()) return mono;
|
||||
|
||||
return findName (placeholder);
|
||||
}
|
||||
|
||||
private:
|
||||
static String findName (const String& placeholder)
|
||||
{
|
||||
const Font f (placeholder, Font::getDefaultStyle(), 15.0f);
|
||||
return Font::getDefaultTypefaceForFont (f)->getName();
|
||||
}
|
||||
|
||||
String sans, serif, mono;
|
||||
};
|
||||
|
||||
static String getConcreteFamilyNameFromPlaceholder (const String& placeholder)
|
||||
{
|
||||
static ConcreteFamilyNames names;
|
||||
return names.lookUp (placeholder);
|
||||
}
|
||||
|
||||
static String getConcreteFamilyName (const Font& font)
|
||||
{
|
||||
const String& family = font.getTypefaceName();
|
||||
|
||||
return isPlaceholderFamilyName (family) ? getConcreteFamilyNameFromPlaceholder (family)
|
||||
: family;
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
Typeface::Typeface (const String& faceName, const String& styleName) noexcept
|
||||
: name (faceName), style (styleName)
|
||||
{
|
||||
}
|
||||
|
||||
Typeface::~Typeface()
|
||||
{
|
||||
}
|
||||
|
||||
Typeface::Ptr Typeface::getFallbackTypeface()
|
||||
{
|
||||
const Font fallbackFont (Font::getFallbackFontName(), Font::getFallbackFontStyle(), 10.0f);
|
||||
return fallbackFont.getTypeface();
|
||||
}
|
||||
|
||||
EdgeTable* Typeface::getEdgeTableForGlyph (int glyphNumber, const AffineTransform& transform, float fontHeight)
|
||||
{
|
||||
Path path;
|
||||
|
||||
if (getOutlineForGlyph (glyphNumber, path) && ! path.isEmpty())
|
||||
{
|
||||
applyVerticalHintingTransform (fontHeight, path);
|
||||
|
||||
return new EdgeTable (path.getBoundsTransformed (transform).getSmallestIntegerContainer().expanded (1, 0),
|
||||
path, transform);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct Typeface::HintingParams
|
||||
{
|
||||
HintingParams (Typeface& t)
|
||||
: cachedSize (0), top (0), middle (0), bottom (0)
|
||||
{
|
||||
Font font (&t);
|
||||
font = font.withHeight ((float) standardHeight);
|
||||
|
||||
top = getAverageY (font, "BDEFPRTZOQ", true);
|
||||
middle = getAverageY (font, "acegmnopqrsuvwxy", true);
|
||||
bottom = getAverageY (font, "BDELZOC", false);
|
||||
}
|
||||
|
||||
void applyVerticalHintingTransform (float fontSize, Path& path)
|
||||
{
|
||||
if (cachedSize != fontSize)
|
||||
{
|
||||
cachedSize = fontSize;
|
||||
cachedScale = Scaling (top, middle, bottom, fontSize);
|
||||
}
|
||||
|
||||
if (bottom < top + 3.0f / fontSize)
|
||||
return;
|
||||
|
||||
Path result;
|
||||
|
||||
for (Path::Iterator i (path); i.next();)
|
||||
{
|
||||
switch (i.elementType)
|
||||
{
|
||||
case Path::Iterator::startNewSubPath: result.startNewSubPath (i.x1, cachedScale.apply (i.y1)); break;
|
||||
case Path::Iterator::lineTo: result.lineTo (i.x1, cachedScale.apply (i.y1)); break;
|
||||
case Path::Iterator::quadraticTo: result.quadraticTo (i.x1, cachedScale.apply (i.y1),
|
||||
i.x2, cachedScale.apply (i.y2)); break;
|
||||
case Path::Iterator::cubicTo: result.cubicTo (i.x1, cachedScale.apply (i.y1),
|
||||
i.x2, cachedScale.apply (i.y2),
|
||||
i.x3, cachedScale.apply (i.y3)); break;
|
||||
case Path::Iterator::closePath: result.closeSubPath(); break;
|
||||
default: jassertfalse; break;
|
||||
}
|
||||
}
|
||||
|
||||
result.swapWithPath (path);
|
||||
}
|
||||
|
||||
private:
|
||||
struct Scaling
|
||||
{
|
||||
Scaling() noexcept : middle(), upperScale(), upperOffset(), lowerScale(), lowerOffset() {}
|
||||
|
||||
Scaling (float t, float m, float b, float fontSize) noexcept : middle (m)
|
||||
{
|
||||
const float newT = std::floor (fontSize * t + 0.5f) / fontSize;
|
||||
const float newB = std::floor (fontSize * b + 0.5f) / fontSize;
|
||||
const float newM = std::floor (fontSize * m + 0.3f) / fontSize; // this is slightly biased so that lower-case letters
|
||||
// are more likely to become taller than shorter.
|
||||
upperScale = jlimit (0.9f, 1.1f, (newM - newT) / (m - t));
|
||||
lowerScale = jlimit (0.9f, 1.1f, (newB - newM) / (b - m));
|
||||
|
||||
upperOffset = newM - m * upperScale;
|
||||
lowerOffset = newB - b * lowerScale;
|
||||
}
|
||||
|
||||
float apply (float y) const noexcept
|
||||
{
|
||||
return y < middle ? (y * upperScale + upperOffset)
|
||||
: (y * lowerScale + lowerOffset);
|
||||
}
|
||||
|
||||
float middle, upperScale, upperOffset, lowerScale, lowerOffset;
|
||||
};
|
||||
|
||||
float cachedSize;
|
||||
Scaling cachedScale;
|
||||
|
||||
static float getAverageY (const Font& font, const char* chars, bool getTop)
|
||||
{
|
||||
GlyphArrangement ga;
|
||||
ga.addLineOfText (font, chars, 0, 0);
|
||||
|
||||
Array<float> y;
|
||||
DefaultElementComparator<float> sorter;
|
||||
|
||||
for (int i = 0; i < ga.getNumGlyphs(); ++i)
|
||||
{
|
||||
Path p;
|
||||
ga.getGlyph (i).createPath (p);
|
||||
Rectangle<float> bounds (p.getBounds());
|
||||
|
||||
if (! p.isEmpty())
|
||||
y.addSorted (sorter, getTop ? bounds.getY() : bounds.getBottom());
|
||||
}
|
||||
|
||||
float median = y[y.size() / 2];
|
||||
|
||||
float total = 0;
|
||||
int num = 0;
|
||||
|
||||
for (int i = 0; i < y.size(); ++i)
|
||||
{
|
||||
if (std::abs (median - y.getUnchecked(i)) < 0.05f * (float) standardHeight)
|
||||
{
|
||||
total += y.getUnchecked(i);
|
||||
++num;
|
||||
}
|
||||
}
|
||||
|
||||
return num < 4 ? 0.0f : total / (num * (float) standardHeight);
|
||||
}
|
||||
|
||||
enum { standardHeight = 100 };
|
||||
float top, middle, bottom;
|
||||
};
|
||||
|
||||
void Typeface::applyVerticalHintingTransform (float fontSize, Path& path)
|
||||
{
|
||||
if (fontSize > 3.0f && fontSize < 25.0f)
|
||||
{
|
||||
ScopedLock sl (hintingLock);
|
||||
|
||||
if (hintingParams == nullptr)
|
||||
hintingParams = new HintingParams (*this);
|
||||
|
||||
return hintingParams->applyVerticalHintingTransform (fontSize, path);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,161 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifndef JUCE_TYPEFACE_H_INCLUDED
|
||||
#define JUCE_TYPEFACE_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
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
|
||||
*/
|
||||
class JUCE_API Typeface : public ReferenceCountedObject
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** A handy typedef for a pointer to a typeface. */
|
||||
typedef ReferenceCountedObjectPtr <Typeface> Ptr;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the font family of the typeface.
|
||||
@see Font::getTypefaceName
|
||||
*/
|
||||
const String& getName() const noexcept { return name; }
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the font style of the typeface.
|
||||
@see Font::getTypefaceStyle
|
||||
*/
|
||||
const String& getStyle() const noexcept { return style; }
|
||||
|
||||
//==============================================================================
|
||||
/** Creates a new system typeface. */
|
||||
static Ptr createSystemTypefaceFor (const Font& font);
|
||||
|
||||
/** Attempts to create a font from some raw font file data (e.g. a TTF or OTF file image).
|
||||
The system will take its own internal copy of the data, so you can free the block once
|
||||
this method has returned.
|
||||
*/
|
||||
static Ptr createSystemTypefaceFor (const void* fontFileData, size_t fontFileDataSize);
|
||||
|
||||
//==============================================================================
|
||||
/** Destructor. */
|
||||
virtual ~Typeface();
|
||||
|
||||
/** Returns true if this typeface can be used to render the specified font.
|
||||
When called, the font will already have been checked to make sure that its name and
|
||||
style flags match the typeface.
|
||||
*/
|
||||
virtual bool isSuitableForFont (const Font&) const { return true; }
|
||||
|
||||
/** Returns the ascent of the font, as a proportion of its height.
|
||||
The height is considered to always be normalised as 1.0, so this will be a
|
||||
value less that 1.0, indicating the proportion of the font that lies above
|
||||
its baseline.
|
||||
*/
|
||||
virtual float getAscent() const = 0;
|
||||
|
||||
/** Returns the descent of the font, as a proportion of its height.
|
||||
The height is considered to always be normalised as 1.0, so this will be a
|
||||
value less that 1.0, indicating the proportion of the font that lies below
|
||||
its baseline.
|
||||
*/
|
||||
virtual float getDescent() const = 0;
|
||||
|
||||
/** Returns the value by which you should multiply a juce font-height value to
|
||||
convert it to the equivalent point-size.
|
||||
*/
|
||||
virtual float getHeightToPointsFactor() const = 0;
|
||||
|
||||
/** Measures the width of a line of text.
|
||||
The distance returned is based on the font having an normalised height of 1.0.
|
||||
You should never need to call this directly! Use Font::getStringWidth() instead!
|
||||
*/
|
||||
virtual float getStringWidth (const String& text) = 0;
|
||||
|
||||
/** Converts a line of text into its glyph numbers and their positions.
|
||||
The distances returned are based on the font having an normalised height of 1.0.
|
||||
You should never need to call this directly! Use Font::getGlyphPositions() instead!
|
||||
*/
|
||||
virtual void getGlyphPositions (const String& text, Array <int>& glyphs, Array<float>& xOffsets) = 0;
|
||||
|
||||
/** Returns the outline for a glyph.
|
||||
The path returned will be normalised to a font height of 1.0.
|
||||
*/
|
||||
virtual bool getOutlineForGlyph (int glyphNumber, Path& path) = 0;
|
||||
|
||||
/** Returns a new EdgeTable that contains the path for the givem glyph, with the specified transform applied. */
|
||||
virtual EdgeTable* getEdgeTableForGlyph (int glyphNumber, const AffineTransform& transform, float fontHeight);
|
||||
|
||||
/** Returns true if the typeface uses hinting. */
|
||||
virtual bool isHinted() const { return false; }
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the number of fonts that are cached in memory. */
|
||||
static void setTypefaceCacheSize (int numFontsToCache);
|
||||
|
||||
/** Clears any fonts that are currently cached in memory. */
|
||||
static void clearTypefaceCache();
|
||||
|
||||
/** On some platforms, this allows a specific path to be scanned.
|
||||
Currently only available when using FreeType.
|
||||
*/
|
||||
static void scanFolderForFonts (const File& folder);
|
||||
|
||||
/** Makes an attempt at performing a good overall distortion that will scale a font of
|
||||
the given size to align vertically with the pixel grid. The path should be an unscaled
|
||||
(i.e. normalised to height of 1.0) path for a glyph.
|
||||
*/
|
||||
void applyVerticalHintingTransform (float fontHeight, Path& path);
|
||||
|
||||
protected:
|
||||
//==============================================================================
|
||||
String name, style;
|
||||
|
||||
Typeface (const String& name, const String& style) noexcept;
|
||||
|
||||
static Ptr getFallbackTypeface();
|
||||
|
||||
private:
|
||||
struct HintingParams;
|
||||
friend struct ContainerDeletePolicy<HintingParams>;
|
||||
ScopedPointer<HintingParams> hintingParams;
|
||||
CriticalSection hintingLock;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Typeface)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_TYPEFACE_H_INCLUDED
|
||||
Loading…
Add table
Add a link
Reference in a new issue