mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
AttributedString class and advanced text layout (stage 1)
This commit is contained in:
parent
15252c7227
commit
4773b388ef
55 changed files with 2528 additions and 35 deletions
|
|
@ -30,11 +30,11 @@ namespace ColourHelpers
|
|||
{
|
||||
uint8 floatToUInt8 (const float n) noexcept
|
||||
{
|
||||
return (uint8) jlimit (0, 255, roundToInt (n * 255.0f));
|
||||
return n <= 0.0f ? 0 : (n >= 1.0f ? 255 : (uint8) (n * 255.0f));
|
||||
}
|
||||
|
||||
// This is an adjusted brightness value, based on the way the human eye responds to
|
||||
// different colour channels..
|
||||
// This is an adjusted brightness value, based on the way the human
|
||||
// eye responds to different colour channels..
|
||||
float getPerceivedBrightness (float r, float g, float b) noexcept
|
||||
{
|
||||
return std::sqrt (r * r * 0.241f
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@
|
|||
#include "../geometry/juce_RectangleList.h"
|
||||
#include "../colour/juce_ColourGradient.h"
|
||||
#include "../colour/juce_FillType.h"
|
||||
|
||||
class AttributedString;
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
|
|
@ -103,6 +103,7 @@ public:
|
|||
virtual void setFont (const Font& newFont) = 0;
|
||||
virtual Font getFont() = 0;
|
||||
virtual void drawGlyph (int glyphNumber, const AffineTransform& transform) = 0;
|
||||
virtual bool drawTextLayout (const AttributedString&, const Rectangle<float>&) { return false; }
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
634
modules/juce_graphics/fonts/juce_AttributedString.cpp
Normal file
634
modules/juce_graphics/fonts/juce_AttributedString.cpp
Normal file
|
|
@ -0,0 +1,634 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library - "Jules' Utility Class Extensions"
|
||||
Copyright 2004-11 by Raw Material Software Ltd.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
JUCE can be redistributed and/or modified under the terms of the GNU General
|
||||
Public License (Version 2), as published by the Free Software Foundation.
|
||||
A copy of the license is included in the JUCE distribution, or can be found
|
||||
online 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.rawmaterialsoftware.com/juce for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
BEGIN_JUCE_NAMESPACE
|
||||
|
||||
//==============================================================================
|
||||
AttributedString::Attribute::Attribute (const Range<int>& range_, const Colour& colour_)
|
||||
: range (range_), colour (new Colour (colour_))
|
||||
{
|
||||
}
|
||||
|
||||
AttributedString::Attribute::Attribute (const 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() {}
|
||||
|
||||
//==============================================================================
|
||||
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;
|
||||
}
|
||||
|
||||
AttributedString::~AttributedString() {}
|
||||
|
||||
void AttributedString::setText (const String& other)
|
||||
{
|
||||
text = other;
|
||||
}
|
||||
|
||||
void AttributedString::setJustification (const 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 (const Range<int>& range, const Colour& colour)
|
||||
{
|
||||
attributes.add (new Attribute (range, colour));
|
||||
}
|
||||
|
||||
void AttributedString::setFont (const Range<int>& range, const Font& font)
|
||||
{
|
||||
attributes.add (new Attribute (range, font));
|
||||
}
|
||||
|
||||
void AttributedString::draw (Graphics& g, const Rectangle<float>& area) const
|
||||
{
|
||||
if (text.isNotEmpty() && g.clipRegionIntersects (area.getSmallestIntegerContainer()))
|
||||
{
|
||||
if (! g.getInternalContext()->drawTextLayout (*this, area))
|
||||
{
|
||||
GlyphLayout layout;
|
||||
layout.setText (*this, area.getWidth());
|
||||
layout.draw (g, area);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//==============================================================================
|
||||
GlyphLayout::Glyph::Glyph (const int glyphCode_, const Point<float>& anchor_) noexcept
|
||||
: glyphCode (glyphCode_), anchor (anchor_)
|
||||
{
|
||||
}
|
||||
|
||||
GlyphLayout::Glyph::~Glyph() {}
|
||||
|
||||
//==============================================================================
|
||||
GlyphLayout::Run::Run()
|
||||
: colour (0xff000000)
|
||||
{
|
||||
}
|
||||
|
||||
GlyphLayout::Run::Run (const Range<int>& range, const int numGlyphsToPreallocate)
|
||||
: stringRange (range), colour (0xff000000)
|
||||
{
|
||||
glyphs.ensureStorageAllocated (numGlyphsToPreallocate);
|
||||
}
|
||||
|
||||
GlyphLayout::Run::~Run() {}
|
||||
|
||||
GlyphLayout::Glyph& GlyphLayout::Run::getGlyph (const int index) const
|
||||
{
|
||||
return *glyphs.getUnchecked (index);
|
||||
}
|
||||
|
||||
void GlyphLayout::Run::ensureStorageAllocated (int numGlyphsNeeded)
|
||||
{
|
||||
glyphs.ensureStorageAllocated (numGlyphsNeeded);
|
||||
}
|
||||
|
||||
void GlyphLayout::Run::setStringRange (const Range<int>& newStringRange) noexcept
|
||||
{
|
||||
stringRange = newStringRange;
|
||||
}
|
||||
|
||||
void GlyphLayout::Run::setFont (const Font& newFont)
|
||||
{
|
||||
font = newFont;
|
||||
}
|
||||
|
||||
void GlyphLayout::Run::setColour (const Colour& newColour) noexcept
|
||||
{
|
||||
colour = newColour;
|
||||
}
|
||||
|
||||
void GlyphLayout::Run::addGlyph (Glyph* glyph)
|
||||
{
|
||||
glyphs.add (glyph);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
GlyphLayout::Line::Line() noexcept
|
||||
: ascent (0.0f), descent (0.0f), leading (0.0f)
|
||||
{
|
||||
}
|
||||
|
||||
GlyphLayout::Line::Line (const Range<int>& stringRange_, const 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);
|
||||
}
|
||||
|
||||
GlyphLayout::Line::~Line()
|
||||
{
|
||||
}
|
||||
|
||||
GlyphLayout::Run& GlyphLayout::Line::getRun (const int index) const noexcept
|
||||
{
|
||||
return *runs.getUnchecked (index);
|
||||
}
|
||||
|
||||
void GlyphLayout::Line::setStringRange (const Range<int>& newStringRange) noexcept
|
||||
{
|
||||
stringRange = newStringRange;
|
||||
}
|
||||
|
||||
void GlyphLayout::Line::setLineOrigin (const Point<float>& newLineOrigin) noexcept
|
||||
{
|
||||
lineOrigin = newLineOrigin;
|
||||
}
|
||||
|
||||
void GlyphLayout::Line::setLeading (float newLeading) noexcept
|
||||
{
|
||||
leading = newLeading;
|
||||
}
|
||||
|
||||
void GlyphLayout::Line::increaseAscentDescent (float newAscent, float newDescent) noexcept
|
||||
{
|
||||
ascent = jmax (ascent, newAscent);
|
||||
descent = jmax (descent, newDescent);
|
||||
}
|
||||
|
||||
void GlyphLayout::Line::addRun (Run* run)
|
||||
{
|
||||
runs.add (run);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
GlyphLayout::GlyphLayout()
|
||||
: width (0), justification (Justification::topLeft)
|
||||
{
|
||||
}
|
||||
|
||||
GlyphLayout::~GlyphLayout()
|
||||
{
|
||||
}
|
||||
|
||||
void GlyphLayout::setText (const AttributedString& text, float maxWidth)
|
||||
{
|
||||
lines.clear();
|
||||
width = maxWidth;
|
||||
justification = text.getJustification();
|
||||
|
||||
if (! createNativeLayout (text))
|
||||
createStandardLayout (text);
|
||||
}
|
||||
|
||||
float GlyphLayout::getHeight() const noexcept
|
||||
{
|
||||
const Line* const lastLine = lines.getLast();
|
||||
|
||||
return lastLine != nullptr ? lastLine->getLineOrigin().getY() + lastLine->getDescent()
|
||||
: 0;
|
||||
}
|
||||
|
||||
GlyphLayout::Line& GlyphLayout::getLine (const int index) const
|
||||
{
|
||||
return *lines[index];
|
||||
}
|
||||
|
||||
void GlyphLayout::ensureStorageAllocated (int numLinesNeeded)
|
||||
{
|
||||
lines.ensureStorageAllocated (numLinesNeeded);
|
||||
}
|
||||
|
||||
void GlyphLayout::addLine (Line* line)
|
||||
{
|
||||
lines.add (line);
|
||||
}
|
||||
|
||||
void GlyphLayout::draw (Graphics& g, const Rectangle<float>& area) const
|
||||
{
|
||||
const Point<float> origin (justification.appliedToRectangle (Rectangle<float> (0, 0, 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.getLineOrigin());
|
||||
|
||||
for (int j = 0; j < line.getNumRuns(); ++j)
|
||||
{
|
||||
const Run& run = line.getRun (j);
|
||||
context.setFont (run.getFont());
|
||||
context.setFill (run.getColour());
|
||||
|
||||
for (int k = 0; k < run.getNumGlyphs(); ++k)
|
||||
{
|
||||
const Glyph& glyph = run.getGlyph (k);
|
||||
context.drawGlyph (glyph.glyphCode, AffineTransform::translation (lineOrigin.x + glyph.anchor.x,
|
||||
lineOrigin.y + glyph.anchor.y));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
namespace GlyphLayoutHelpers
|
||||
{
|
||||
struct FontAndColour
|
||||
{
|
||||
FontAndColour (const Font* font_) noexcept : font (font_), 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& fontAndColour_, const Range<int>& range_) noexcept
|
||||
: fontAndColour (fontAndColour_), range (range_)
|
||||
{}
|
||||
|
||||
FontAndColour fontAndColour;
|
||||
Range<int> range;
|
||||
};
|
||||
|
||||
struct Token
|
||||
{
|
||||
Token (const String& t, const Font& f, const Colour& c, const bool isWhitespace_)
|
||||
: text (t), font (f), colour (c),
|
||||
area (font.getStringWidth (t), roundToInt (f.getHeight())),
|
||||
isWhitespace (isWhitespace_),
|
||||
isNewLine (t.containsChar ('\n') || t.containsChar ('\r'))
|
||||
{}
|
||||
|
||||
const String text;
|
||||
const Font font;
|
||||
const Colour colour;
|
||||
Rectangle<int> area;
|
||||
int line, lineHeight;
|
||||
const bool isWhitespace, isNewLine;
|
||||
|
||||
private:
|
||||
Token& operator= (const Token&);
|
||||
};
|
||||
|
||||
class TokenList
|
||||
{
|
||||
public:
|
||||
TokenList() noexcept : totalLines (0) {}
|
||||
|
||||
void createLayout (const AttributedString& text, GlyphLayout& glyphLayout)
|
||||
{
|
||||
tokens.ensureStorageAllocated (64);
|
||||
glyphLayout.ensureStorageAllocated (totalLines);
|
||||
|
||||
addTextRuns (text);
|
||||
|
||||
layout ((int) glyphLayout.getWidth());
|
||||
|
||||
int charPosition = 0;
|
||||
int lineStartPosition = 0;
|
||||
int runStartPosition = 0;
|
||||
|
||||
GlyphLayout::Line* glyphLine = new GlyphLayout::Line();
|
||||
GlyphLayout::Run* glyphRun = new GlyphLayout::Run();
|
||||
|
||||
for (int i = 0; i < tokens.size(); ++i)
|
||||
{
|
||||
const Token* const t = tokens.getUnchecked (i);
|
||||
const Point<float> tokenPos (t->area.getPosition().toFloat());
|
||||
|
||||
Array <int> newGlyphs;
|
||||
Array <float> xOffsets;
|
||||
t->font.getGlyphPositions (t->text.trimEnd(), newGlyphs, xOffsets);
|
||||
|
||||
glyphRun->ensureStorageAllocated (glyphRun->getNumGlyphs() + newGlyphs.size());
|
||||
|
||||
for (int j = 0; j < newGlyphs.size(); ++j)
|
||||
{
|
||||
if (charPosition == lineStartPosition)
|
||||
glyphLine->setLineOrigin (tokenPos.translated (0, t->font.getAscent()));
|
||||
|
||||
glyphRun->addGlyph (new GlyphLayout::Glyph (newGlyphs.getUnchecked(j),
|
||||
Point<float> (tokenPos.getX() + xOffsets.getUnchecked (j), 0)));
|
||||
++charPosition;
|
||||
}
|
||||
|
||||
if (t->isWhitespace || t->isNewLine)
|
||||
++charPosition;
|
||||
|
||||
const Token* const nextToken = tokens [i + 1];
|
||||
|
||||
if (nextToken == nullptr) // this is the last token
|
||||
{
|
||||
addRun (glyphLine, glyphRun, t, runStartPosition, charPosition);
|
||||
glyphLine->setStringRange (Range<int> (lineStartPosition, charPosition));
|
||||
glyphLayout.addLine (glyphLine);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (t->font != nextToken->font || t->colour != nextToken->colour)
|
||||
{
|
||||
addRun (glyphLine, glyphRun, t, runStartPosition, charPosition);
|
||||
runStartPosition = charPosition;
|
||||
glyphRun = new GlyphLayout::Run();
|
||||
}
|
||||
|
||||
if (t->line != nextToken->line)
|
||||
{
|
||||
addRun (glyphLine, glyphRun, t, runStartPosition, charPosition);
|
||||
glyphLine->setStringRange (Range<int> (lineStartPosition, charPosition));
|
||||
glyphLayout.addLine (glyphLine);
|
||||
|
||||
runStartPosition = charPosition;
|
||||
lineStartPosition = charPosition;
|
||||
glyphLine = new GlyphLayout::Line();
|
||||
glyphRun = new GlyphLayout::Run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((text.getJustification().getFlags() & (Justification::right | Justification::horizontallyCentred)) != 0)
|
||||
{
|
||||
const int totalW = (int) glyphLayout.getWidth();
|
||||
|
||||
for (int i = 0; i < totalLines; ++i)
|
||||
{
|
||||
const int lineW = getLineWidth (i);
|
||||
float dx = 0;
|
||||
|
||||
if ((text.getJustification().getFlags() & Justification::right) != 0)
|
||||
dx = (float) (totalW - lineW);
|
||||
else
|
||||
dx = (totalW - lineW) / 2.0f;
|
||||
|
||||
GlyphLayout::Line& glyphLine = glyphLayout.getLine (i);
|
||||
glyphLine.setLineOrigin (glyphLine.getLineOrigin().translated (dx, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static void addRun (GlyphLayout::Line* glyphLine, GlyphLayout::Run* glyphRun,
|
||||
const Token* const t, const int start, const int end)
|
||||
{
|
||||
glyphRun->setStringRange (Range<int> (start, end));
|
||||
glyphRun->setFont (t->font);
|
||||
glyphRun->setColour (t->colour);
|
||||
glyphLine->increaseAscentDescent (t->font.getAscent(), t->font.getDescent());
|
||||
glyphLine->addRun (glyphRun);
|
||||
}
|
||||
|
||||
void appendText (const AttributedString& text, const Range<int>& stringRange,
|
||||
const Font& font, const Colour& colour)
|
||||
{
|
||||
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;
|
||||
|
||||
int charType;
|
||||
if (c == '\r' || c == '\n')
|
||||
charType = 0;
|
||||
else if (CharacterFunctions::isWhitespace (c))
|
||||
charType = 2;
|
||||
else
|
||||
charType = 1;
|
||||
|
||||
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 layout (const int maxWidth)
|
||||
{
|
||||
int x = 0, y = 0, h = 0;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < tokens.size(); ++i)
|
||||
{
|
||||
Token* const t = tokens.getUnchecked(i);
|
||||
t->area.setPosition (x, y);
|
||||
t->line = totalLines;
|
||||
x += t->area.getWidth();
|
||||
h = jmax (h, t->area.getHeight());
|
||||
|
||||
const Token* nextTok = tokens[i + 1];
|
||||
|
||||
if (nextTok == 0)
|
||||
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 int height) noexcept
|
||||
{
|
||||
while (--i >= 0)
|
||||
{
|
||||
Token* const tok = tokens.getUnchecked (i);
|
||||
|
||||
if (tok->line == totalLines)
|
||||
tok->lineHeight = height;
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int getLineWidth (const int lineNumber) const noexcept
|
||||
{
|
||||
int maxW = 0;
|
||||
|
||||
for (int i = tokens.size(); --i >= 0;)
|
||||
{
|
||||
const Token* const t = tokens.getUnchecked (i);
|
||||
|
||||
if (t->line == lineNumber && ! t->isWhitespace)
|
||||
maxW = jmax (maxW, t->area.getRight());
|
||||
}
|
||||
|
||||
return maxW;
|
||||
}
|
||||
|
||||
void addTextRuns (const AttributedString& text)
|
||||
{
|
||||
Font defaultFont;
|
||||
Array<RunAttribute> runAttributes;
|
||||
|
||||
{
|
||||
const int stringLength = text.getText().length();
|
||||
int rangeStart = 0;
|
||||
FontAndColour lastFontAndColour (nullptr);
|
||||
|
||||
// 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* const attr = text.getAttribute (j);
|
||||
|
||||
// Check if the current character falls within the range of a font attribute
|
||||
if (attr->getFont() != nullptr && (i >= attr->range.getStart()) && (i < attr->range.getEnd()))
|
||||
newFontAndColour.font = attr->getFont();
|
||||
|
||||
// Check if the current character falls within the range of a foreground colour attribute
|
||||
if (attr->getColour() != nullptr && (i >= attr->range.getStart()) && (i < attr->range.getEnd()))
|
||||
newFontAndColour.colour = *attr->getColour();
|
||||
}
|
||||
|
||||
if (i > 0 && (newFontAndColour != lastFontAndColour || i == stringLength - 1))
|
||||
{
|
||||
runAttributes.add (RunAttribute (lastFontAndColour,
|
||||
Range<int> (rangeStart, (i < stringLength - 1) ? i : (i + 1))));
|
||||
rangeStart = i;
|
||||
}
|
||||
|
||||
lastFontAndColour = newFontAndColour;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < runAttributes.size(); ++i)
|
||||
{
|
||||
const RunAttribute& r = runAttributes.getReference(i);
|
||||
appendText (text, r.range, *(r.fontAndColour.font), r.fontAndColour.colour);
|
||||
}
|
||||
}
|
||||
|
||||
OwnedArray<Token> tokens;
|
||||
int totalLines;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (TokenList);
|
||||
};
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void GlyphLayout::createStandardLayout (const AttributedString& text)
|
||||
{
|
||||
GlyphLayoutHelpers::TokenList l;
|
||||
l.createLayout (text, *this);
|
||||
}
|
||||
|
||||
END_JUCE_NAMESPACE
|
||||
318
modules/juce_graphics/fonts/juce_AttributedString.h
Normal file
318
modules/juce_graphics/fonts/juce_AttributedString.h
Normal file
|
|
@ -0,0 +1,318 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library - "Jules' Utility Class Extensions"
|
||||
Copyright 2004-11 by Raw Material Software Ltd.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
JUCE can be redistributed and/or modified under the terms of the GNU General
|
||||
Public License (Version 2), as published by the Free Software Foundation.
|
||||
A copy of the license is included in the JUCE distribution, or can be found
|
||||
online 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.rawmaterialsoftware.com/juce for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifndef __JUCE_ATTRIBUTEDSTRING_JUCEHEADER__
|
||||
#define __JUCE_ATTRIBUTEDSTRING_JUCEHEADER__
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
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().
|
||||
*/
|
||||
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& other);
|
||||
AttributedString& operator= (const AttributedString& other);
|
||||
|
||||
/** Destructor. */
|
||||
~AttributedString();
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the complete text of this attributed string. */
|
||||
const String& getText() const noexcept { return text; }
|
||||
|
||||
/** Sets the text.
|
||||
This will change the text, but won't affect any of the attributes that have
|
||||
been added.
|
||||
*/
|
||||
void setText (const String& newText);
|
||||
|
||||
//==============================================================================
|
||||
/** 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 (const 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 (const Range<int>& range, const Colour& colour);
|
||||
|
||||
/** Creates an attribute that changes the font for a range of characters.
|
||||
@see AttributedString::setFont()
|
||||
*/
|
||||
Attribute (const 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 should be applied. */
|
||||
const Range<int> range;
|
||||
|
||||
private:
|
||||
ScopedPointer<Font> font;
|
||||
ScopedPointer<Colour> colour;
|
||||
|
||||
Attribute& operator= (const 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 (const Range<int>& range, const Colour& colour);
|
||||
|
||||
/** Adds a font attribute for the specified range. */
|
||||
void setFont (const Range<int>& range, const Font& font);
|
||||
|
||||
private:
|
||||
String text;
|
||||
float lineSpacing;
|
||||
Justification justification;
|
||||
WordWrap wordWrap;
|
||||
ReadingDirection readingDirection;
|
||||
OwnedArray<Attribute> attributes;
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
|
||||
*/
|
||||
class JUCE_API GlyphLayout
|
||||
{
|
||||
public:
|
||||
/** Creates an empty layout. */
|
||||
GlyphLayout();
|
||||
|
||||
/** Destructor. */
|
||||
~GlyphLayout();
|
||||
|
||||
//==============================================================================
|
||||
/** Creates a layout from the given attributed string.
|
||||
This will replace any data that is currently stored in the layout.
|
||||
*/
|
||||
void setText (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, const Point<float>& anchor) noexcept;
|
||||
~Glyph();
|
||||
|
||||
const int glyphCode;
|
||||
const Point<float> anchor;
|
||||
|
||||
private:
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Glyph);
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** A sequence of glyphs. */
|
||||
class JUCE_API Run
|
||||
{
|
||||
public:
|
||||
Run();
|
||||
Run (const Range<int>& range, int numGlyphsToPreallocate);
|
||||
~Run();
|
||||
|
||||
int getNumGlyphs() const noexcept { return glyphs.size(); }
|
||||
const Font& getFont() const noexcept { return font; }
|
||||
const Colour& getColour() const { return colour; }
|
||||
Glyph& getGlyph (int index) const;
|
||||
|
||||
void setStringRange (const Range<int>& newStringRange) noexcept;
|
||||
void setFont (const Font& newFont);
|
||||
void setColour (const Colour& newColour) noexcept;
|
||||
|
||||
void addGlyph (Glyph* glyph);
|
||||
void ensureStorageAllocated (int numGlyphsNeeded);
|
||||
|
||||
private:
|
||||
OwnedArray<Glyph> glyphs;
|
||||
Range<int> stringRange;
|
||||
Font font;
|
||||
Colour colour;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Run);
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** A line containing a sequence of glyph-runs. */
|
||||
class JUCE_API Line
|
||||
{
|
||||
public:
|
||||
Line() noexcept;
|
||||
Line (const Range<int>& stringRange, const Point<float>& lineOrigin,
|
||||
float ascent, float descent, float leading, int numRunsToPreallocate);
|
||||
~Line();
|
||||
|
||||
const Point<float>& getLineOrigin() const noexcept { return lineOrigin; }
|
||||
|
||||
float getAscent() const noexcept { return ascent; }
|
||||
float getDescent() const noexcept { return descent; }
|
||||
float getLeading() const noexcept { return leading; }
|
||||
|
||||
int getNumRuns() const noexcept { return runs.size(); }
|
||||
Run& getRun (int index) const noexcept;
|
||||
|
||||
void setStringRange (const Range<int>& newStringRange) noexcept;
|
||||
void setLineOrigin (const Point<float>& newLineOrigin) noexcept;
|
||||
void setLeading (float newLeading) noexcept;
|
||||
void increaseAscentDescent (float newAscent, float newDescent) noexcept;
|
||||
|
||||
void addRun (Run* run);
|
||||
|
||||
private:
|
||||
OwnedArray<Run> runs;
|
||||
Range<int> stringRange;
|
||||
Point<float> lineOrigin;
|
||||
float ascent, descent, leading;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_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 object passed-in will be owned and deleted by the layout
|
||||
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&);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GlyphLayout);
|
||||
};
|
||||
|
||||
#endif // __JUCE_ATTRIBUTEDSTRING_JUCEHEADER__
|
||||
|
|
@ -43,6 +43,12 @@
|
|||
#if JUCE_MAC
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
|
||||
#elif JUCE_WINDOWS
|
||||
#if JUCE_USE_DIRECTWRITE
|
||||
#include <d2d1.h>
|
||||
#include <dwrite.h>
|
||||
#endif
|
||||
|
||||
#elif JUCE_IOS
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
#import <CoreText/CoreText.h>
|
||||
|
|
@ -78,6 +84,7 @@
|
|||
#include "image_formats/juce_GIFLoader.cpp"
|
||||
#include "image_formats/juce_JPEGLoader.cpp"
|
||||
#include "image_formats/juce_PNGLoader.cpp"
|
||||
#include "fonts/juce_AttributedString.cpp"
|
||||
#include "fonts/juce_CustomTypeface.cpp"
|
||||
#include "fonts/juce_Font.cpp"
|
||||
#include "fonts/juce_GlyphArrangement.cpp"
|
||||
|
|
@ -93,13 +100,17 @@ BEGIN_JUCE_NAMESPACE
|
|||
#if JUCE_MAC || JUCE_IOS
|
||||
#include "../juce_core/native/juce_osx_ObjCHelpers.h"
|
||||
#include "../juce_core/native/juce_mac_ObjCSuffix.h"
|
||||
#include "native/juce_mac_CoreGraphicsHelpers.h"
|
||||
#include "native/juce_mac_Fonts.mm"
|
||||
#include "native/juce_mac_CoreGraphicsContext.mm"
|
||||
|
||||
#elif JUCE_WINDOWS
|
||||
#include "../juce_core/native/juce_win32_ComSmartPtr.h"
|
||||
#if JUCE_DIRECT2D
|
||||
#include "native/juce_win32_Direct2DGraphicsContext.cpp"
|
||||
#endif
|
||||
#include "native/juce_win32_DirectWriteTypeface.cpp"
|
||||
#include "native/juce_win32_DirectWriteTypeLayout.cpp"
|
||||
#include "native/juce_win32_Fonts.cpp"
|
||||
|
||||
#elif JUCE_LINUX
|
||||
|
|
|
|||
|
|
@ -40,6 +40,15 @@
|
|||
#define JUCE_USE_COREIMAGE_LOADER 1
|
||||
#endif
|
||||
|
||||
/** Config: JUCE_USE_DIRECTWRITE
|
||||
|
||||
Enabling this flag means that DirectWrite will be used when available for font
|
||||
management and layout.
|
||||
*/
|
||||
#ifndef JUCE_USE_DIRECTWRITE
|
||||
#define JUCE_USE_DIRECTWRITE 1
|
||||
#endif
|
||||
|
||||
#ifndef JUCE_INCLUDE_PNGLIB_CODE
|
||||
#define JUCE_INCLUDE_PNGLIB_CODE 1
|
||||
#endif
|
||||
|
|
@ -132,6 +141,9 @@ BEGIN_JUCE_NAMESPACE
|
|||
#ifndef __JUCE_IMAGEFILEFORMAT_JUCEHEADER__
|
||||
#include "images/juce_ImageFileFormat.h"
|
||||
#endif
|
||||
#ifndef __JUCE_ATTRIBUTEDSTRING_JUCEHEADER__
|
||||
#include "fonts/juce_AttributedString.h"
|
||||
#endif
|
||||
#ifndef __JUCE_CUSTOMTYPEFACE_JUCEHEADER__
|
||||
#include "fonts/juce_CustomTypeface.h"
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ public:
|
|||
void setFont (const Font& newFont);
|
||||
Font getFont();
|
||||
void drawGlyph (int glyphNumber, const AffineTransform& transform);
|
||||
bool drawTextLayout (const AttributedString& text, const Rectangle<float>&);
|
||||
|
||||
private:
|
||||
CGContextRef context;
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@
|
|||
==============================================================================
|
||||
*/
|
||||
|
||||
#include "juce_mac_CoreGraphicsHelpers.h"
|
||||
#include "juce_mac_CoreGraphicsContext.h"
|
||||
|
||||
//==============================================================================
|
||||
|
|
@ -622,6 +621,16 @@ void CoreGraphicsContext::drawGlyph (int glyphNumber, const AffineTransform& tra
|
|||
}
|
||||
}
|
||||
|
||||
bool CoreGraphicsContext::drawTextLayout (const AttributedString& text, const Rectangle<float>& area)
|
||||
{
|
||||
#if JUCE_CORETEXT_AVAILABLE
|
||||
CoreTextTypeLayout::drawToCGContext (text, area, context, flipHeight);
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
CoreGraphicsContext::SavedState::SavedState()
|
||||
: font (1.0f), fontRef (0), fontTransform (CGAffineTransformIdentity),
|
||||
shading (0), numGradientLookupEntries (0)
|
||||
|
|
|
|||
|
|
@ -31,6 +31,270 @@
|
|||
|
||||
#if JUCE_CORETEXT_AVAILABLE
|
||||
|
||||
namespace CoreTextTypeLayout
|
||||
{
|
||||
CFAttributedStringRef createCFAttributedString (const AttributedString& text)
|
||||
{
|
||||
#if JUCE_IOS
|
||||
CGColorSpaceRef rgbColourSpace = CGColorSpaceCreateDeviceRGB();
|
||||
#endif
|
||||
|
||||
CFStringRef cfText = text.getText().toCFString();
|
||||
CFMutableAttributedStringRef attribString = CFAttributedStringCreateMutable (kCFAllocatorDefault, 0);
|
||||
CFAttributedStringReplaceString (attribString, CFRangeMake(0, 0), cfText);
|
||||
CFRelease (cfText);
|
||||
|
||||
const int numCharacterAttributes = text.getNumAttributes();
|
||||
|
||||
for (int i = 0; i < numCharacterAttributes; ++i)
|
||||
{
|
||||
const AttributedString::Attribute* const attr = text.getAttribute (i);
|
||||
|
||||
if (attr->range.getStart() > CFAttributedStringGetLength (attribString))
|
||||
continue;
|
||||
|
||||
Range<int> range (attr->range);
|
||||
range.setEnd (jmin (range.getEnd(), (int) CFAttributedStringGetLength (attribString)));
|
||||
|
||||
if (attr->getFont() != nullptr)
|
||||
{
|
||||
// Apply fontHeightToCGSizeFactor to the font size since this is how glyphs are drawn
|
||||
CTFontRef ctFontRef = CTFontCreateWithName (attr->getFont()->getTypefaceName().toCFString(), 1024, nullptr);
|
||||
CGFontRef cgFontRef = CTFontCopyGraphicsFont (ctFontRef, nullptr);
|
||||
CFRelease (ctFontRef);
|
||||
|
||||
const int totalHeight = abs (CGFontGetAscent (cgFontRef)) + abs (CGFontGetDescent (cgFontRef));
|
||||
float fontHeightToCGSizeFactor = CGFontGetUnitsPerEm (cgFontRef) / (float) totalHeight;
|
||||
CGFontRelease (cgFontRef);
|
||||
|
||||
ctFontRef = CTFontCreateWithName (attr->getFont()->getTypefaceName().toCFString(),
|
||||
attr->getFont()->getHeight() * fontHeightToCGSizeFactor, nullptr);
|
||||
|
||||
CFAttributedStringSetAttribute (attribString, CFRangeMake (range.getStart(), range.getLength()),
|
||||
kCTFontAttributeName, ctFontRef);
|
||||
CFRelease (ctFontRef);
|
||||
}
|
||||
|
||||
if (attr->getColour() != nullptr)
|
||||
{
|
||||
#if JUCE_IOS
|
||||
const CGFloat components[] = { attr->getColour()->getFloatRed(),
|
||||
attr->getColour()->getFloatGreen(),
|
||||
attr->getColour()->getFloatBlue(),
|
||||
attr->getColour()->getFloatAlpha() };
|
||||
CGColorRef colour = CGColorCreate (rgbColourSpace, components);
|
||||
#else
|
||||
CGColorRef colour = CGColorCreateGenericRGB (attr->getColour()->getFloatRed(),
|
||||
attr->getColour()->getFloatGreen(),
|
||||
attr->getColour()->getFloatBlue(),
|
||||
attr->getColour()->getFloatAlpha());
|
||||
#endif
|
||||
|
||||
CFAttributedStringSetAttribute (attribString,
|
||||
CFRangeMake (range.getStart(), range.getLength()),
|
||||
kCTForegroundColorAttributeName, colour);
|
||||
CGColorRelease (colour);
|
||||
}
|
||||
}
|
||||
|
||||
// Paragraph Attributes
|
||||
CTTextAlignment ctTextAlignment = kCTLeftTextAlignment;
|
||||
CTLineBreakMode ctLineBreakMode = kCTLineBreakByWordWrapping;
|
||||
const CGFloat ctLineSpacing = text.getLineSpacing();
|
||||
|
||||
switch (text.getJustification().getOnlyHorizontalFlags())
|
||||
{
|
||||
case Justification::left: break;
|
||||
case Justification::right: ctTextAlignment = kCTRightTextAlignment; break;
|
||||
case Justification::horizontallyCentred: ctTextAlignment = kCTCenterTextAlignment; break;
|
||||
case Justification::horizontallyJustified: ctTextAlignment = kCTJustifiedTextAlignment; break;
|
||||
default: jassertfalse; break; // Illegal justification flags
|
||||
}
|
||||
|
||||
switch (text.getWordWrap())
|
||||
{
|
||||
case AttributedString::byWord: break;
|
||||
case AttributedString::none: ctLineBreakMode = kCTLineBreakByClipping; break;
|
||||
case AttributedString::byChar: ctLineBreakMode = kCTLineBreakByCharWrapping; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
CTParagraphStyleSetting settings[] =
|
||||
{
|
||||
{ kCTParagraphStyleSpecifierAlignment, sizeof (CTTextAlignment), &ctTextAlignment },
|
||||
{ kCTParagraphStyleSpecifierLineBreakMode, sizeof (CTLineBreakMode), &ctLineBreakMode },
|
||||
{ kCTParagraphStyleSpecifierLineSpacing, sizeof (CGFloat), &ctLineSpacing }
|
||||
};
|
||||
|
||||
CTParagraphStyleRef ctParagraphStyleRef = CTParagraphStyleCreate (settings, numElementsInArray (settings));
|
||||
CFAttributedStringSetAttribute (attribString, CFRangeMake (0, CFAttributedStringGetLength (attribString)),
|
||||
kCTParagraphStyleAttributeName, ctParagraphStyleRef);
|
||||
CFRelease (ctParagraphStyleRef);
|
||||
#if JUCE_IOS
|
||||
CGColorSpaceRelease (rgbColourSpace);
|
||||
#endif
|
||||
return attribString;
|
||||
}
|
||||
|
||||
float getTextHeight (CTFrameRef frame, const CGRect& bounds)
|
||||
{
|
||||
CFArrayRef lines = CTFrameGetLines (frame);
|
||||
CFIndex numLines = CFArrayGetCount (lines);
|
||||
CFIndex lastLineIndex = numLines - 1;
|
||||
CGFloat descent;
|
||||
CTLineRef line = (CTLineRef) CFArrayGetValueAtIndex (lines, lastLineIndex);
|
||||
CTLineGetTypographicBounds (line, nullptr, &descent, nullptr);
|
||||
CGPoint lastLineOrigin;
|
||||
CTFrameGetLineOrigins (frame, CFRangeMake (lastLineIndex, 1), &lastLineOrigin);
|
||||
return bounds.size.height - lastLineOrigin.y + descent;
|
||||
}
|
||||
|
||||
void drawToCGContext (const AttributedString& text, const Rectangle<float>& area,
|
||||
const CGContextRef& context, const float flipHeight)
|
||||
{
|
||||
CFAttributedStringRef attribString = CoreTextTypeLayout::createCFAttributedString (text);
|
||||
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString (attribString);
|
||||
CFRelease (attribString);
|
||||
|
||||
CGMutablePathRef path = CGPathCreateMutable();
|
||||
CGRect bounds = CGRectMake ((CGFloat) area.getX(), flipHeight - (CGFloat) area.getBottom(),
|
||||
(CGFloat) area.getWidth(), (CGFloat) area.getHeight());
|
||||
CGPathAddRect (path, nullptr, bounds);
|
||||
|
||||
CTFrameRef frame = CTFramesetterCreateFrame (framesetter, CFRangeMake (0, 0), path, NULL);
|
||||
CFRelease (framesetter);
|
||||
CGPathRelease (path);
|
||||
|
||||
float dy = 0;
|
||||
if (text.getJustification().getOnlyVerticalFlags() == Justification::bottom)
|
||||
dy = bounds.size.height - getTextHeight (frame, bounds);
|
||||
else if (text.getJustification().getOnlyVerticalFlags() == Justification::verticallyCentred)
|
||||
dy = (bounds.size.height - getTextHeight (frame, bounds)) / 2.0f;
|
||||
|
||||
if (dy != 0)
|
||||
{
|
||||
CGContextSaveGState (context);
|
||||
CGContextTranslateCTM (context, 0, -dy);
|
||||
}
|
||||
|
||||
CTFrameDraw (frame, context);
|
||||
|
||||
if (dy != 0)
|
||||
CGContextRestoreGState (context);
|
||||
|
||||
CFRelease (frame);
|
||||
}
|
||||
|
||||
void createLayout (GlyphLayout& glyphLayout, const AttributedString& text)
|
||||
{
|
||||
CFAttributedStringRef attribString = CoreTextTypeLayout::createCFAttributedString (text);
|
||||
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString (attribString);
|
||||
CFRelease (attribString);
|
||||
|
||||
CGMutablePathRef path = CGPathCreateMutable();
|
||||
const CGRect bounds = CGRectMake (0, 0, glyphLayout.getWidth(), 1.0e6f);
|
||||
CGPathAddRect (path, nullptr, bounds);
|
||||
|
||||
CTFrameRef frame = CTFramesetterCreateFrame (framesetter, CFRangeMake(0, 0), path, nullptr);
|
||||
CFRelease (framesetter);
|
||||
CGPathRelease (path);
|
||||
|
||||
CFArrayRef lines = CTFrameGetLines (frame);
|
||||
const CFIndex numLines = CFArrayGetCount (lines);
|
||||
|
||||
glyphLayout.ensureStorageAllocated (numLines);
|
||||
|
||||
for (CFIndex i = 0; i < numLines; ++i)
|
||||
{
|
||||
CTLineRef line = (CTLineRef) CFArrayGetValueAtIndex (lines, i);
|
||||
|
||||
CFArrayRef runs = CTLineGetGlyphRuns (line);
|
||||
const CFIndex numRuns = CFArrayGetCount (runs);
|
||||
|
||||
const CFRange cfrlineStringRange = CTLineGetStringRange (line);
|
||||
const CFIndex lineStringEnd = cfrlineStringRange.location + cfrlineStringRange.length - 1;
|
||||
const Range<int> lineStringRange ((int) cfrlineStringRange.location, (int) lineStringEnd);
|
||||
|
||||
CGPoint cgpLineOrigin;
|
||||
CTFrameGetLineOrigins (frame, CFRangeMake(i, 1), &cgpLineOrigin);
|
||||
|
||||
Point<float> lineOrigin ((float) cgpLineOrigin.x, bounds.size.height - (float) cgpLineOrigin.y);
|
||||
|
||||
CGFloat ascent, descent, leading;
|
||||
CTLineGetTypographicBounds (line, &ascent, &descent, &leading);
|
||||
|
||||
GlyphLayout::Line* const glyphLine = new GlyphLayout::Line (lineStringRange, lineOrigin,
|
||||
(float) ascent, (float) descent, (float) leading,
|
||||
(int) numRuns);
|
||||
glyphLayout.addLine (glyphLine);
|
||||
|
||||
for (CFIndex j = 0; j < numRuns; ++j)
|
||||
{
|
||||
CTRunRef run = (CTRunRef) CFArrayGetValueAtIndex (runs, j);
|
||||
const CFIndex numGlyphs = CTRunGetGlyphCount (run);
|
||||
const CFRange runStringRange = CTRunGetStringRange (run);
|
||||
|
||||
GlyphLayout::Run* const glyphRun = new GlyphLayout::Run (Range<int> ((int) runStringRange.location,
|
||||
(int) (runStringRange.location + runStringRange.length - 1)),
|
||||
(int) numGlyphs);
|
||||
glyphLine->addRun (glyphRun);
|
||||
|
||||
CFDictionaryRef runAttributes = CTRunGetAttributes (run);
|
||||
|
||||
CTFontRef ctRunFont;
|
||||
if (CFDictionaryGetValueIfPresent (runAttributes, kCTFontAttributeName, (const void **) &ctRunFont))
|
||||
{
|
||||
CFStringRef cfsFontName = CTFontCopyPostScriptName (ctRunFont);
|
||||
const String fontName (String::fromCFString (cfsFontName));
|
||||
|
||||
CTFontRef ctFontRef = CTFontCreateWithName (cfsFontName, 1024, nullptr);
|
||||
CFRelease (cfsFontName);
|
||||
CGFontRef cgFontRef = CTFontCopyGraphicsFont (ctFontRef, nullptr);
|
||||
CFRelease (ctFontRef);
|
||||
|
||||
const int totalHeight = std::abs (CGFontGetAscent (cgFontRef)) + std::abs (CGFontGetDescent (cgFontRef));
|
||||
const float fontHeightToCGSizeFactor = CGFontGetUnitsPerEm (cgFontRef) / (float) totalHeight;
|
||||
CGFontRelease (cgFontRef);
|
||||
|
||||
glyphRun->setFont (Font (fontName, CTFontGetSize (ctRunFont) / fontHeightToCGSizeFactor, 0));
|
||||
}
|
||||
|
||||
CGColorRef cgRunColor;
|
||||
if (CFDictionaryGetValueIfPresent (runAttributes, kCTForegroundColorAttributeName, (const void**) &cgRunColor)
|
||||
&& CGColorGetNumberOfComponents (cgRunColor) == 4)
|
||||
{
|
||||
const CGFloat* const components = CGColorGetComponents (cgRunColor);
|
||||
|
||||
glyphRun->setColour (Colour::fromFloatRGBA (components[0], components[1], components[2], components[3]));
|
||||
}
|
||||
|
||||
const CGGlyph* glyphsPtr = CTRunGetGlyphsPtr (run);
|
||||
const CGPoint* posPtr = CTRunGetPositionsPtr (run);
|
||||
HeapBlock <CGGlyph> glyphBuffer;
|
||||
HeapBlock <CGPoint> positionBuffer;
|
||||
|
||||
if (glyphsPtr == nullptr || posPtr == nullptr)
|
||||
{
|
||||
// If we can't get a direct pointer, we have to copy the metrics to get them..
|
||||
glyphBuffer.malloc (numGlyphs);
|
||||
glyphsPtr = glyphBuffer;
|
||||
CTRunGetGlyphs (run, CFRangeMake (0, 0), glyphBuffer);
|
||||
|
||||
positionBuffer.malloc (numGlyphs);
|
||||
posPtr = positionBuffer;
|
||||
CTRunGetPositions (run, CFRangeMake (0, 0), positionBuffer);
|
||||
}
|
||||
|
||||
for (CFIndex k = 0; k < numGlyphs; ++k)
|
||||
glyphRun->addGlyph (new GlyphLayout::Glyph (glyphsPtr[k], Point<float> (posPtr[k].x, posPtr[k].y)));
|
||||
}
|
||||
}
|
||||
|
||||
CFRelease (frame);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//==============================================================================
|
||||
class OSXTypeface : public Typeface
|
||||
{
|
||||
|
|
@ -778,3 +1042,14 @@ Typeface::Ptr Font::getDefaultTypefaceForFont (const Font& font)
|
|||
f.setTypefaceName (faceName);
|
||||
return Typeface::createSystemTypefaceFor (f);
|
||||
}
|
||||
|
||||
bool GlyphLayout::createNativeLayout (const AttributedString& text)
|
||||
{
|
||||
#if JUCE_CORETEXT_AVAILABLE
|
||||
CoreTextTypeLayout::createLayout (*this, text);
|
||||
return true;
|
||||
#else
|
||||
(void) text;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,349 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library - "Jules' Utility Class Extensions"
|
||||
Copyright 2004-11 by Raw Material Software Ltd.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
JUCE can be redistributed and/or modified under the terms of the GNU General
|
||||
Public License (Version 2), as published by the Free Software Foundation.
|
||||
A copy of the license is included in the JUCE distribution, or can be found
|
||||
online 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.rawmaterialsoftware.com/juce for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
//==================================================================================================
|
||||
#if JUCE_USE_DIRECTWRITE
|
||||
namespace DirectWriteTypeLayout
|
||||
{
|
||||
class CustomDirectWriteTextRenderer : public ComBaseClassHelper <IDWriteTextRenderer>
|
||||
{
|
||||
public:
|
||||
CustomDirectWriteTextRenderer (IDWriteFontCollection* const fontCollection_)
|
||||
: fontCollection (fontCollection_),
|
||||
currentLine (-1),
|
||||
lastOriginY (-10000.0f)
|
||||
{
|
||||
resetReferenceCount();
|
||||
}
|
||||
|
||||
JUCE_COMRESULT QueryInterface (REFIID refId, void** result)
|
||||
{
|
||||
#if ! JUCE_MINGW
|
||||
if (refId == __uuidof (IDWritePixelSnapping)) { AddRef(); *result = dynamic_cast <IDWritePixelSnapping*> (this); return S_OK; }
|
||||
#else
|
||||
jassertfalse; // need to find a mingw equivalent of __uuidof to make this possible
|
||||
#endif
|
||||
|
||||
return ComBaseClassHelper<IDWriteTextRenderer>::QueryInterface (refId, result);
|
||||
}
|
||||
|
||||
JUCE_COMRESULT IsPixelSnappingDisabled (void* /*clientDrawingContext*/, BOOL* isDisabled)
|
||||
{
|
||||
*isDisabled = FALSE;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT GetCurrentTransform (void*, DWRITE_MATRIX*) { return S_OK; }
|
||||
JUCE_COMRESULT GetPixelsPerDip (void*, FLOAT*) { return S_OK; }
|
||||
JUCE_COMRESULT DrawUnderline (void*, FLOAT, FLOAT, DWRITE_UNDERLINE const*, IUnknown*) { return S_OK; }
|
||||
JUCE_COMRESULT DrawStrikethrough (void*, FLOAT, FLOAT, DWRITE_STRIKETHROUGH const*, IUnknown*) { return S_OK; }
|
||||
JUCE_COMRESULT DrawInlineObject (void*, FLOAT, FLOAT, IDWriteInlineObject*, BOOL, BOOL, IUnknown*) { return E_NOTIMPL; }
|
||||
|
||||
JUCE_COMRESULT DrawGlyphRun (void* clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY, DWRITE_MEASURING_MODE,
|
||||
DWRITE_GLYPH_RUN const* glyphRun, DWRITE_GLYPH_RUN_DESCRIPTION const* runDescription,
|
||||
IUnknown* clientDrawingEffect)
|
||||
{
|
||||
GlyphLayout* const glyphLayout = static_cast<GlyphLayout*> (clientDrawingContext);
|
||||
|
||||
if (baselineOriginY != lastOriginY)
|
||||
{
|
||||
lastOriginY = baselineOriginY;
|
||||
++currentLine;
|
||||
|
||||
// The x value is only correct when dealing with LTR text
|
||||
glyphLayout->getLine (currentLine).setLineOrigin (Point<float> (baselineOriginX, baselineOriginY));
|
||||
}
|
||||
|
||||
if (currentLine < 0)
|
||||
return S_OK;
|
||||
|
||||
GlyphLayout::Line& glyphLine = glyphLayout->getLine (currentLine);
|
||||
|
||||
DWRITE_FONT_METRICS dwFontMetrics;
|
||||
glyphRun->fontFace->GetMetrics (&dwFontMetrics);
|
||||
|
||||
const float ascent = scaledFontSize (dwFontMetrics.ascent, dwFontMetrics, glyphRun);
|
||||
const float descent = scaledFontSize (dwFontMetrics.descent, dwFontMetrics, glyphRun);
|
||||
glyphLine.increaseAscentDescent (ascent, descent);
|
||||
|
||||
int styleFlags = 0;
|
||||
const String fontName (getFontName (glyphRun, styleFlags));
|
||||
|
||||
GlyphLayout::Run* const glyphRunLayout = new GlyphLayout::Run (Range<int> (runDescription->textPosition,
|
||||
runDescription->textPosition + runDescription->stringLength),
|
||||
glyphRun->glyphCount);
|
||||
glyphLine.addRun (glyphRunLayout);
|
||||
|
||||
glyphRun->fontFace->GetMetrics (&dwFontMetrics);
|
||||
|
||||
const float totalHeight = std::abs ((float) dwFontMetrics.ascent) + std::abs ((float) dwFontMetrics.descent);
|
||||
const float fontHeightToEmSizeFactor = (float) dwFontMetrics.designUnitsPerEm / totalHeight;
|
||||
|
||||
glyphRunLayout->setFont (Font (fontName, glyphRun->fontEmSize / fontHeightToEmSizeFactor, styleFlags));
|
||||
glyphRunLayout->setColour (getColourOf (static_cast<ID2D1SolidColorBrush*> (clientDrawingEffect)));
|
||||
|
||||
const Point<float> lineOrigin (glyphLayout->getLine (currentLine).getLineOrigin());
|
||||
float x = baselineOriginX - lineOrigin.x;
|
||||
|
||||
for (UINT32 i = 0; i < glyphRun->glyphCount; ++i)
|
||||
{
|
||||
if ((glyphRun->bidiLevel & 1) != 0)
|
||||
x -= glyphRun->glyphAdvances[i]; // RTL text
|
||||
|
||||
glyphRunLayout->addGlyph (new GlyphLayout::Glyph (glyphRun->glyphIndices[i], Point<float> (x, baselineOriginY - lineOrigin.y)));
|
||||
|
||||
if ((glyphRun->bidiLevel & 1) == 0)
|
||||
x += glyphRun->glyphAdvances[i]; // LTR text
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
IDWriteFontCollection* const fontCollection;
|
||||
int currentLine;
|
||||
float lastOriginY;
|
||||
|
||||
static float scaledFontSize (int n, const DWRITE_FONT_METRICS& metrics, const DWRITE_GLYPH_RUN* glyphRun) noexcept
|
||||
{
|
||||
return (std::abs ((float) n) / (float) metrics.designUnitsPerEm) * glyphRun->fontEmSize;
|
||||
}
|
||||
|
||||
static Colour getColourOf (ID2D1SolidColorBrush* d2dBrush)
|
||||
{
|
||||
if (d2dBrush == nullptr)
|
||||
return Colours::black;
|
||||
|
||||
const D2D1_COLOR_F colour (d2dBrush->GetColor());
|
||||
return Colour::fromFloatRGBA (colour.r, colour.g, colour.b, colour.a);
|
||||
}
|
||||
|
||||
String getFontName (DWRITE_GLYPH_RUN const* glyphRun, int& styleFlags) const
|
||||
{
|
||||
ComSmartPtr<IDWriteFont> dwFont;
|
||||
|
||||
HRESULT hr = fontCollection->GetFontFromFontFace (glyphRun->fontFace, dwFont.resetAndGetPointerAddress());
|
||||
jassert (dwFont != nullptr);
|
||||
|
||||
if (dwFont->GetWeight() == DWRITE_FONT_WEIGHT_BOLD) styleFlags &= Font::bold;
|
||||
if (dwFont->GetStyle() == DWRITE_FONT_STYLE_ITALIC) styleFlags &= Font::italic;
|
||||
|
||||
ComSmartPtr<IDWriteFontFamily> dwFontFamily;
|
||||
hr = dwFont->GetFontFamily (dwFontFamily.resetAndGetPointerAddress());
|
||||
jassert (dwFontFamily != nullptr);
|
||||
|
||||
// Get the Font Family Names
|
||||
ComSmartPtr<IDWriteLocalizedStrings> dwFamilyNames;
|
||||
hr = dwFontFamily->GetFamilyNames (dwFamilyNames.resetAndGetPointerAddress());
|
||||
jassert (dwFamilyNames != nullptr);
|
||||
|
||||
UINT32 index = 0;
|
||||
BOOL exists = false;
|
||||
hr = dwFamilyNames->FindLocaleName (L"en-us", &index, &exists);
|
||||
if (! exists)
|
||||
index = 0;
|
||||
|
||||
UINT32 length = 0;
|
||||
hr = dwFamilyNames->GetStringLength (index, &length);
|
||||
|
||||
HeapBlock <wchar_t> name (length + 1);
|
||||
hr = dwFamilyNames->GetString (index, name, length + 1);
|
||||
|
||||
return String (name);
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CustomDirectWriteTextRenderer);
|
||||
};
|
||||
|
||||
//==================================================================================================
|
||||
float getFontHeightToEmSizeFactor (const Font& font, IDWriteFontCollection& dwFontCollection)
|
||||
{
|
||||
BOOL fontFound = false;
|
||||
uint32 fontIndex;
|
||||
dwFontCollection.FindFamilyName (font.getTypefaceName().toWideCharPointer(), &fontIndex, &fontFound);
|
||||
|
||||
if (! fontFound)
|
||||
fontIndex = 0;
|
||||
|
||||
ComSmartPtr<IDWriteFontFamily> dwFontFamily;
|
||||
HRESULT hr = dwFontCollection.GetFontFamily (fontIndex, dwFontFamily.resetAndGetPointerAddress());
|
||||
|
||||
ComSmartPtr<IDWriteFont> dwFont;
|
||||
hr = dwFontFamily->GetFirstMatchingFont (DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STRETCH_NORMAL, DWRITE_FONT_STYLE_NORMAL,
|
||||
dwFont.resetAndGetPointerAddress());
|
||||
|
||||
ComSmartPtr<IDWriteFontFace> dwFontFace;
|
||||
hr = dwFont->CreateFontFace (dwFontFace.resetAndGetPointerAddress());
|
||||
|
||||
DWRITE_FONT_METRICS dwFontMetrics;
|
||||
dwFontFace->GetMetrics (&dwFontMetrics);
|
||||
|
||||
const float totalHeight = (float) (std::abs (dwFontMetrics.ascent) + std::abs (dwFontMetrics.descent));
|
||||
return dwFontMetrics.designUnitsPerEm / totalHeight;
|
||||
}
|
||||
|
||||
void setTextFormatProperties (const AttributedString& text, IDWriteTextFormat* const format)
|
||||
{
|
||||
DWRITE_TEXT_ALIGNMENT alignment = DWRITE_TEXT_ALIGNMENT_LEADING;
|
||||
DWRITE_WORD_WRAPPING wrapType = DWRITE_WORD_WRAPPING_WRAP;
|
||||
|
||||
switch (text.getJustification().getOnlyHorizontalFlags())
|
||||
{
|
||||
case Justification::left: break;
|
||||
case Justification::right: alignment = DWRITE_TEXT_ALIGNMENT_TRAILING; break;
|
||||
case Justification::horizontallyCentred: alignment = DWRITE_TEXT_ALIGNMENT_CENTER; break;
|
||||
case Justification::horizontallyJustified: break; // DirectWrite cannot justify text, default to left alignment
|
||||
default: jassertfalse; break; // Illegal justification flags
|
||||
}
|
||||
|
||||
switch (text.getWordWrap())
|
||||
{
|
||||
case AttributedString::none: wrapType = DWRITE_WORD_WRAPPING_NO_WRAP; break;
|
||||
case AttributedString::byWord: break;
|
||||
case AttributedString::byChar: break; // DirectWrite doesn't support wrapping by character, default to word-wrap
|
||||
default: jassertfalse; break; // Illegal flags!
|
||||
}
|
||||
|
||||
format->SetTextAlignment (alignment);
|
||||
format->SetWordWrapping (wrapType);
|
||||
|
||||
// DirectWrite does not automatically set reading direction
|
||||
// This must be set correctly and manually when using RTL Scripts (Hebrew, Arabic)
|
||||
if (text.getReadingDirection() == AttributedString::rightToLeft)
|
||||
format->SetReadingDirection (DWRITE_READING_DIRECTION_RIGHT_TO_LEFT);
|
||||
}
|
||||
|
||||
void addAttributedRange (const AttributedString::Attribute& attr, IDWriteTextLayout* textLayout,
|
||||
const int textLen, ID2D1DCRenderTarget* const renderTarget, IDWriteFontCollection* const fontCollection)
|
||||
{
|
||||
DWRITE_TEXT_RANGE range;
|
||||
range.startPosition = attr.range.getStart();
|
||||
range.length = jmin (attr.range.getLength(), textLen - attr.range.getStart());
|
||||
|
||||
if (attr.getFont() != nullptr)
|
||||
{
|
||||
textLayout->SetFontFamilyName (attr.getFont()->getTypefaceName().toWideCharPointer(), range);
|
||||
|
||||
const float fontHeightToEmSizeFactor = getFontHeightToEmSizeFactor (*attr.getFont(), *fontCollection);
|
||||
textLayout->SetFontSize (attr.getFont()->getHeight() * fontHeightToEmSizeFactor, range);
|
||||
}
|
||||
|
||||
if (attr.getColour() != nullptr)
|
||||
{
|
||||
ComSmartPtr<ID2D1SolidColorBrush> d2dBrush;
|
||||
renderTarget->CreateSolidColorBrush (D2D1::ColorF (D2D1::ColorF (attr.getColour()->getFloatRed(),
|
||||
attr.getColour()->getFloatGreen(),
|
||||
attr.getColour()->getFloatBlue(),
|
||||
attr.getColour()->getFloatAlpha())),
|
||||
d2dBrush.resetAndGetPointerAddress());
|
||||
|
||||
// We need to call SetDrawingEffect with a legimate brush to get DirectWrite to break text based on colours
|
||||
textLayout->SetDrawingEffect (d2dBrush, range);
|
||||
}
|
||||
}
|
||||
|
||||
void createLayout (GlyphLayout& glyphLayout, const AttributedString& text, IDWriteFactory* const directWriteFactory,
|
||||
ID2D1Factory* const direct2dFactory, IDWriteFontCollection* const fontCollection)
|
||||
{
|
||||
// To add color to text, we need to create a D2D render target
|
||||
// Since we are not actually rendering to a D2D context we create a temporary GDI render target
|
||||
|
||||
D2D1_RENDER_TARGET_PROPERTIES d2dRTProp = D2D1::RenderTargetProperties (D2D1_RENDER_TARGET_TYPE_SOFTWARE,
|
||||
D2D1::PixelFormat (DXGI_FORMAT_B8G8R8A8_UNORM,
|
||||
D2D1_ALPHA_MODE_IGNORE),
|
||||
0, 0,
|
||||
D2D1_RENDER_TARGET_USAGE_GDI_COMPATIBLE,
|
||||
D2D1_FEATURE_LEVEL_DEFAULT);
|
||||
ComSmartPtr<ID2D1DCRenderTarget> renderTarget;
|
||||
HRESULT hr = direct2dFactory->CreateDCRenderTarget (&d2dRTProp, renderTarget.resetAndGetPointerAddress());
|
||||
|
||||
Font defaultFont;
|
||||
const float defaultFontHeightToEmSizeFactor = getFontHeightToEmSizeFactor (defaultFont, *fontCollection);
|
||||
|
||||
jassert (directWriteFactory != nullptr);
|
||||
|
||||
ComSmartPtr<IDWriteTextFormat> dwTextFormat;
|
||||
hr = directWriteFactory->CreateTextFormat (defaultFont.getTypefaceName().toWideCharPointer(), fontCollection,
|
||||
DWRITE_FONT_WEIGHT_REGULAR, DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL,
|
||||
defaultFont.getHeight() * defaultFontHeightToEmSizeFactor,
|
||||
L"en-us", dwTextFormat.resetAndGetPointerAddress());
|
||||
|
||||
setTextFormatProperties (text, dwTextFormat);
|
||||
|
||||
const int textLen = text.getText().length();
|
||||
|
||||
ComSmartPtr<IDWriteTextLayout> dwTextLayout;
|
||||
hr = directWriteFactory->CreateTextLayout (text.getText().toWideCharPointer(), textLen,
|
||||
dwTextFormat, glyphLayout.getWidth(),
|
||||
1.0e7f, dwTextLayout.resetAndGetPointerAddress());
|
||||
|
||||
const int numAttributes = text.getNumAttributes();
|
||||
|
||||
for (int i = 0; i < numAttributes; ++i)
|
||||
addAttributedRange (*text.getAttribute (i), dwTextLayout, textLen, renderTarget, fontCollection);
|
||||
|
||||
UINT32 actualLineCount = 0;
|
||||
hr = dwTextLayout->GetLineMetrics (nullptr, 0, &actualLineCount);
|
||||
|
||||
glyphLayout.ensureStorageAllocated (actualLineCount);
|
||||
|
||||
HeapBlock <DWRITE_LINE_METRICS> dwLineMetrics (actualLineCount);
|
||||
hr = dwTextLayout->GetLineMetrics (dwLineMetrics, actualLineCount, &actualLineCount);
|
||||
int lastLocation = 0;
|
||||
|
||||
for (UINT32 i = 0; i < actualLineCount; ++i)
|
||||
{
|
||||
const Range<int> lineStringRange (lastLocation, (int) lastLocation + dwLineMetrics[i].length);
|
||||
lastLocation = dwLineMetrics[i].length;
|
||||
|
||||
GlyphLayout::Line* glyphLine = new GlyphLayout::Line();
|
||||
glyphLayout.addLine (glyphLine);
|
||||
glyphLine->setStringRange (lineStringRange);
|
||||
}
|
||||
|
||||
ComSmartPtr<CustomDirectWriteTextRenderer> textRenderer (new CustomDirectWriteTextRenderer (fontCollection));
|
||||
|
||||
hr = dwTextLayout->Draw (&glyphLayout, textRenderer, 0, 0);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
bool GlyphLayout::createNativeLayout (const AttributedString& text)
|
||||
{
|
||||
#if JUCE_USE_DIRECTWRITE
|
||||
const Direct2DFactories& factories = Direct2DFactories::getInstance();
|
||||
|
||||
if (factories.d2dFactory != nullptr && factories.systemFonts != nullptr)
|
||||
{
|
||||
DirectWriteTypeLayout::createLayout (*this, text, factories.directWriteFactory,
|
||||
factories.d2dFactory, factories.systemFonts);
|
||||
return true;
|
||||
}
|
||||
#else
|
||||
(void) text;
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
253
modules/juce_graphics/native/juce_win32_DirectWriteTypeface.cpp
Normal file
253
modules/juce_graphics/native/juce_win32_DirectWriteTypeface.cpp
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library - "Jules' Utility Class Extensions"
|
||||
Copyright 2004-11 by Raw Material Software Ltd.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
JUCE can be redistributed and/or modified under the terms of the GNU General
|
||||
Public License (Version 2), as published by the Free Software Foundation.
|
||||
A copy of the license is included in the JUCE distribution, or can be found
|
||||
online 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.rawmaterialsoftware.com/juce for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#if JUCE_USE_DIRECTWRITE
|
||||
class Direct2DFactories
|
||||
{
|
||||
public:
|
||||
Direct2DFactories()
|
||||
{
|
||||
if (direct2dDll.open ("d2d1.dll"))
|
||||
{
|
||||
JUCE_DLL_FUNCTION (D2D1CreateFactory, d2d1CreateFactory, HRESULT, direct2dDll, (D2D1_FACTORY_TYPE, REFIID, D2D1_FACTORY_OPTIONS*, void**))
|
||||
|
||||
if (d2d1CreateFactory != nullptr)
|
||||
{
|
||||
D2D1_FACTORY_OPTIONS options;
|
||||
options.debugLevel = D2D1_DEBUG_LEVEL_NONE;
|
||||
|
||||
d2d1CreateFactory (D2D1_FACTORY_TYPE_SINGLE_THREADED, __uuidof (ID2D1Factory), &options,
|
||||
(void**) d2dFactory.resetAndGetPointerAddress());
|
||||
}
|
||||
}
|
||||
|
||||
if (directWriteDll.open ("DWrite.dll"))
|
||||
{
|
||||
JUCE_DLL_FUNCTION (DWriteCreateFactory, dWriteCreateFactory, HRESULT, directWriteDll, (DWRITE_FACTORY_TYPE, REFIID, IUnknown**))
|
||||
|
||||
if (dWriteCreateFactory != nullptr)
|
||||
{
|
||||
dWriteCreateFactory (DWRITE_FACTORY_TYPE_SHARED, __uuidof (IDWriteFactory),
|
||||
(IUnknown**) directWriteFactory.resetAndGetPointerAddress());
|
||||
|
||||
if (directWriteFactory != nullptr)
|
||||
directWriteFactory->GetSystemFontCollection (systemFonts.resetAndGetPointerAddress());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
~Direct2DFactories()
|
||||
{
|
||||
d2dFactory = nullptr; // (need to make sure these are released before deleting the DynamicLibrary objects)
|
||||
directWriteFactory = nullptr;
|
||||
systemFonts = nullptr;
|
||||
}
|
||||
|
||||
static const Direct2DFactories& getInstance()
|
||||
{
|
||||
static Direct2DFactories instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
ComSmartPtr <ID2D1Factory> d2dFactory;
|
||||
ComSmartPtr <IDWriteFactory> directWriteFactory;
|
||||
ComSmartPtr <IDWriteFontCollection> systemFonts;
|
||||
|
||||
private:
|
||||
DynamicLibrary direct2dDll, directWriteDll;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Direct2DFactories);
|
||||
};
|
||||
|
||||
//==================================================================================================
|
||||
class WindowsDirectWriteTypeface : public Typeface
|
||||
{
|
||||
public:
|
||||
WindowsDirectWriteTypeface (const Font& font, IDWriteFontCollection* fontCollection)
|
||||
: Typeface (font.getTypefaceName()),
|
||||
ascent (0.0f)
|
||||
{
|
||||
jassert (fontCollection != nullptr);
|
||||
|
||||
BOOL fontFound = false;
|
||||
uint32 fontIndex = 0;
|
||||
HRESULT hr = fontCollection->FindFamilyName (font.getTypefaceName().toWideCharPointer(), &fontIndex, &fontFound);
|
||||
if (! fontFound)
|
||||
fontIndex = 0;
|
||||
|
||||
// Get the font family using the search results
|
||||
// Fonts like: Times New Roman, Times New Roman Bold, Times New Roman Italic are all in the same font family
|
||||
ComSmartPtr<IDWriteFontFamily> dwFontFamily;
|
||||
hr = fontCollection->GetFontFamily (fontIndex, dwFontFamily.resetAndGetPointerAddress());
|
||||
|
||||
// Get a specific font in the font family using certain weight and style flags
|
||||
ComSmartPtr<IDWriteFont> dwFont;
|
||||
DWRITE_FONT_WEIGHT dwWeight = font.isBold() ? DWRITE_FONT_WEIGHT_BOLD : DWRITE_FONT_WEIGHT_NORMAL;
|
||||
DWRITE_FONT_STYLE dwStyle = font.isItalic() ? DWRITE_FONT_STYLE_ITALIC : DWRITE_FONT_STYLE_NORMAL;
|
||||
|
||||
hr = dwFontFamily->GetFirstMatchingFont (dwWeight, DWRITE_FONT_STRETCH_NORMAL, dwStyle, dwFont.resetAndGetPointerAddress());
|
||||
hr = dwFont->CreateFontFace (dwFontFace.resetAndGetPointerAddress());
|
||||
|
||||
DWRITE_FONT_METRICS dwFontMetrics;
|
||||
dwFontFace->GetMetrics (&dwFontMetrics);
|
||||
|
||||
// All Font Metrics are in design units so we need to get designUnitsPerEm value to get the metrics
|
||||
// into Em/Design Independent Pixels
|
||||
designUnitsPerEm = dwFontMetrics.designUnitsPerEm;
|
||||
|
||||
ascent = std::abs ((float) dwFontMetrics.ascent);
|
||||
const float totalSize = ascent + std::abs ((float) dwFontMetrics.descent);
|
||||
ascent /= totalSize;
|
||||
unitsToHeightScaleFactor = 1.0f / (totalSize / designUnitsPerEm);
|
||||
const float pathAscent = (((float) dwFontMetrics.ascent) / ((float) designUnitsPerEm)) * 1024.0f;
|
||||
const float pathDescent = (((float) dwFontMetrics.descent) / ((float) designUnitsPerEm)) * 1024.0f;
|
||||
const float pathTotalSize = std::abs (pathAscent) + std::abs (pathDescent);
|
||||
pathTransform = AffineTransform::identity.scale (1.0f / pathTotalSize, 1.0f / pathTotalSize);
|
||||
}
|
||||
|
||||
float getAscent() const { return ascent; }
|
||||
float getDescent() const { return 1.0f - ascent; }
|
||||
|
||||
float getStringWidth (const String& text)
|
||||
{
|
||||
const CharPointer_UTF32 textUTF32 (text.toUTF32());
|
||||
const size_t len = textUTF32.length();
|
||||
|
||||
HeapBlock <UINT16> glyphIndices (len);
|
||||
dwFontFace->GetGlyphIndices (textUTF32, len, glyphIndices);
|
||||
|
||||
HeapBlock <DWRITE_GLYPH_METRICS> dwGlyphMetrics (len);
|
||||
dwFontFace->GetDesignGlyphMetrics (glyphIndices, len, dwGlyphMetrics, false);
|
||||
|
||||
float x = 0;
|
||||
for (size_t i = 0; i < len; ++i)
|
||||
x += (float) dwGlyphMetrics[i].advanceWidth / designUnitsPerEm;
|
||||
|
||||
return x * unitsToHeightScaleFactor;
|
||||
}
|
||||
|
||||
void getGlyphPositions (const String& text, Array <int>& resultGlyphs, Array <float>& xOffsets)
|
||||
{
|
||||
xOffsets.add (0);
|
||||
|
||||
const CharPointer_UTF32 textUTF32 (text.toUTF32());
|
||||
const size_t len = textUTF32.length();
|
||||
|
||||
HeapBlock <UINT16> glyphIndices (len);
|
||||
dwFontFace->GetGlyphIndices (textUTF32, len, glyphIndices);
|
||||
HeapBlock <DWRITE_GLYPH_METRICS> dwGlyphMetrics (len);
|
||||
dwFontFace->GetDesignGlyphMetrics (glyphIndices, len, dwGlyphMetrics, false);
|
||||
|
||||
float x = 0;
|
||||
for (size_t i = 0; i < len; ++i)
|
||||
{
|
||||
x += (float) dwGlyphMetrics[i].advanceWidth / designUnitsPerEm;
|
||||
xOffsets.add (x * unitsToHeightScaleFactor);
|
||||
resultGlyphs.add (glyphIndices[i]);
|
||||
}
|
||||
}
|
||||
|
||||
EdgeTable* getEdgeTableForGlyph (int glyphNumber, const AffineTransform& transform)
|
||||
{
|
||||
Path path;
|
||||
|
||||
if (getOutlineForGlyph (glyphNumber, path) && ! path.isEmpty())
|
||||
return new EdgeTable (path.getBoundsTransformed (transform).getSmallestIntegerContainer().expanded (1, 0),
|
||||
path, transform);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool getOutlineForGlyph (int glyphNumber, Path& path)
|
||||
{
|
||||
jassert (path.isEmpty()); // we might need to apply a transform to the path, so this must be empty
|
||||
UINT16 glyphIndex = (UINT16) glyphNumber;
|
||||
ComSmartPtr<PathGeometrySink> pathGeometrySink (new PathGeometrySink());
|
||||
|
||||
dwFontFace->GetGlyphRunOutline (1024.0f, &glyphIndex, nullptr, nullptr, 1, false, false, pathGeometrySink);
|
||||
path = pathGeometrySink->path;
|
||||
|
||||
if (! pathTransform.isIdentity())
|
||||
path.applyTransform (pathTransform);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
ComSmartPtr<IDWriteFontFace> dwFontFace;
|
||||
float unitsToHeightScaleFactor, ascent;
|
||||
int designUnitsPerEm;
|
||||
AffineTransform pathTransform;
|
||||
|
||||
class PathGeometrySink : public ComBaseClassHelper<IDWriteGeometrySink>
|
||||
{
|
||||
public:
|
||||
PathGeometrySink() { resetReferenceCount(); }
|
||||
|
||||
void __stdcall AddBeziers (const D2D1_BEZIER_SEGMENT *beziers, UINT beziersCount)
|
||||
{
|
||||
for (UINT i = 0; i < beziersCount; ++i)
|
||||
path.cubicTo ((float) beziers[i].point1.x, (float) beziers[i].point1.y,
|
||||
(float) beziers[i].point2.x, (float) beziers[i].point2.y,
|
||||
(float) beziers[i].point3.x, (float) beziers[i].point3.y);
|
||||
}
|
||||
|
||||
void __stdcall AddLines (const D2D1_POINT_2F* points, UINT pointsCount)
|
||||
{
|
||||
for (UINT i = 0; i < pointsCount; ++i)
|
||||
path.lineTo ((float) points[i].x,
|
||||
(float) points[i].y);
|
||||
}
|
||||
|
||||
void __stdcall BeginFigure (D2D1_POINT_2F startPoint, D2D1_FIGURE_BEGIN)
|
||||
{
|
||||
path.startNewSubPath ((float) startPoint.x,
|
||||
(float) startPoint.y);
|
||||
}
|
||||
|
||||
void __stdcall EndFigure (D2D1_FIGURE_END figureEnd)
|
||||
{
|
||||
if (figureEnd == D2D1_FIGURE_END_CLOSED)
|
||||
path.closeSubPath();
|
||||
}
|
||||
|
||||
void __stdcall SetFillMode (D2D1_FILL_MODE fillMode)
|
||||
{
|
||||
path.setUsingNonZeroWinding (fillMode == D2D1_FILL_MODE_WINDING);
|
||||
}
|
||||
|
||||
void __stdcall SetSegmentFlags (D2D1_PATH_SEGMENT) {}
|
||||
JUCE_COMRESULT Close() { return S_OK; }
|
||||
|
||||
Path path;
|
||||
|
||||
private:
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PathGeometrySink);
|
||||
};
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WindowsDirectWriteTypeface);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -416,5 +416,12 @@ const MAT2 WindowsTypeface::identityMatrix = { { 0, 1 }, { 0, 0 }, { 0, 0 }, { 0
|
|||
|
||||
Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font)
|
||||
{
|
||||
return new WindowsTypeface (font);
|
||||
#if JUCE_USE_DIRECTWRITE
|
||||
const Direct2DFactories& factories = Direct2DFactories::getInstance();
|
||||
|
||||
if (factories.systemFonts != nullptr)
|
||||
return new WindowsDirectWriteTypeface (font, factories.systemFonts);
|
||||
else
|
||||
#endif
|
||||
return new WindowsTypeface (font);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue