1
0
Fork 0
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:
Felix Faire 2014-10-29 15:55:23 +00:00
parent fefcf7aca6
commit ff6520a89a
1141 changed files with 438491 additions and 94 deletions

View file

@ -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);
}
}
}

View file

@ -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

View file

@ -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;
}

View file

@ -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

View file

@ -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);
}

View file

@ -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

View file

@ -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;
}

View file

@ -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

View file

@ -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();
}
}

View file

@ -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

View file

@ -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);
}
}

View file

@ -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