blob: 64ce932babab39269b98a582db9ad2579e1aa852 [file]
#ifdef WITH_RIVE_TEXT
#include "rive/text/raw_text.hpp"
#include "rive/text_engine.hpp"
#include "rive/factory.hpp"
#include "rive/shapes/paint/color.hpp"
using namespace rive;
RawText::RawText(Factory* factory) : m_factory(factory) {}
bool RawText::empty() const { return m_styled.empty(); }
void RawText::append(const std::string& text,
rcp<RenderPaint> paint,
rcp<Font> font,
float size,
float lineHeight,
float letterSpacing,
ColorInt foregroundColor)
{
int styleIndex = 0;
for (RenderStyle& style : m_styles)
{
if (style.paint == paint && style.foregroundColor == foregroundColor)
{
break;
}
styleIndex++;
}
if (styleIndex == (int)m_styles.size())
{
m_styles.emplace_back();
auto& style = m_styles.back();
style.paint = paint;
style.isEmpty = true;
style.foregroundColor = foregroundColor;
}
m_styled.append(font, size, lineHeight, letterSpacing, text, styleIndex);
markDirty();
}
void RawText::clear()
{
m_styled.clear();
m_styles.clear();
markDirty();
}
void RawText::markDirty()
{
m_dirty = true;
m_lookupDirty = true;
m_revision++;
}
TextSizing RawText::sizing() const { return m_sizing; }
TextOverflow RawText::overflow() const { return m_overflow; }
TextAlign RawText::align() const { return m_align; }
RawText::LogicalAlign RawText::logicalAlign() const { return m_logicalAlign; }
TextWrap RawText::wrap() const { return m_wrap; }
TextWordBreak RawText::wordBreak() const { return m_wordBreak; }
TextOrigin RawText::origin() const { return m_origin; }
int RawText::directionFlag() const { return m_directionFlag; }
float RawText::maxWidth() const { return m_maxWidth; }
float RawText::maxHeight() const { return m_maxHeight; }
float RawText::paragraphSpacing() const { return m_paragraphSpacing; }
void RawText::sizing(TextSizing value)
{
if (m_sizing != value)
{
m_sizing = value;
markDirty();
}
}
void RawText::overflow(TextOverflow value)
{
if (m_overflow != value)
{
m_overflow = value;
markDirty();
}
}
void RawText::align(TextAlign value)
{
if (m_align != value)
{
m_align = value;
markDirty();
}
}
void RawText::logicalAlign(LogicalAlign value)
{
if (m_logicalAlign != value)
{
m_logicalAlign = value;
markDirty();
}
}
void RawText::wrap(TextWrap value)
{
if (m_wrap != value)
{
m_wrap = value;
markDirty();
}
}
void RawText::wordBreak(TextWordBreak value)
{
if (m_wordBreak != value)
{
m_wordBreak = value;
markDirty();
}
}
void RawText::origin(TextOrigin value)
{
if (m_origin != value)
{
m_origin = value;
markDirty();
}
}
void RawText::directionFlag(int value)
{
if (m_directionFlag != value)
{
m_directionFlag = value;
markDirty();
}
}
int RawText::alignIndex() const
{
return m_logicalAlign == LogicalAlign::none ? (int)m_align
: 2 + (int)m_logicalAlign;
}
void RawText::alignIndex(int value)
{
if (value >= 0 && value <= 2)
{
align((TextAlign)value);
logicalAlign(LogicalAlign::none);
}
else if (value == 3 || value == 4)
{
logicalAlign((LogicalAlign)(value - 2));
}
}
void RawText::directionIndex(int value)
{
if (value >= 0 && value <= 2)
{
directionFlag(value - 1);
}
}
void RawText::sizingIndex(int value)
{
if (value >= 0 && value <= (int)TextSizing::fixed)
{
sizing((TextSizing)value);
}
}
void RawText::overflowIndex(int value)
{
// fit and fitFontSize live in the Text component, not here.
if (value >= 0 && value <= (int)TextOverflow::ellipsis)
{
overflow((TextOverflow)value);
}
}
void RawText::wrapIndex(int value)
{
if (value >= 0 && value <= (int)TextWrap::noWrap)
{
wrap((TextWrap)value);
}
}
void RawText::wordBreakIndex(int value)
{
if (value >= 0 && value <= (int)TextWordBreak::breakAll)
{
wordBreak((TextWordBreak)value);
}
}
void RawText::originIndex(int value)
{
if (value >= 0 && value <= (int)TextOrigin::baseline)
{
origin((TextOrigin)value);
}
}
void RawText::paragraphSpacing(float value)
{
if (m_paragraphSpacing != value)
{
m_paragraphSpacing = value;
markDirty();
}
}
void RawText::maxWidth(float value)
{
if (m_maxWidth != value)
{
m_maxWidth = value;
markDirty();
}
}
void RawText::maxHeight(float value)
{
if (m_maxHeight != value)
{
m_maxHeight = value;
markDirty();
}
}
void RawText::update()
{
for (RenderStyle& style : m_styles)
{
style.path.rewind();
style.isEmpty = true;
}
m_renderStyles.clear();
m_drawCommands.clear();
m_orderedLines.clear();
m_lineDirections.clear();
m_lineGlyphCounts.clear();
if (m_styled.empty())
{
// Readers of the layout must not see the previous text's lines.
m_shape = SimpleArray<Paragraph>();
m_lines = SimpleArray<SimpleArray<GlyphLine>>();
m_bounds = AABB(0.0f, 0.0f, 0.0f, 0.0f);
return;
}
auto runs = m_styled.runs();
m_shape =
runs[0].font->shapeText(m_styled.unichars(), runs, m_directionFlag);
TextAlign align = m_align;
if (m_logicalAlign != LogicalAlign::none && !m_shape.empty())
{
bool rtl = m_shape[0].baseDirection() == TextDirection::rtl;
align = (m_logicalAlign == LogicalAlign::start) != rtl
? TextAlign::left
: TextAlign::right;
}
m_lines =
Text::BreakLines(m_shape,
m_sizing == TextSizing::autoWidth ? -1.0f : m_maxWidth,
align,
m_wrap,
m_wordBreak);
m_ellipsisRun = {};
// build render styles.
if (m_shape.empty())
{
m_bounds = AABB(0.0f, 0.0f, 0.0f, 0.0f);
return;
}
// Build up ordered runs as we go.
int paragraphIndex = 0;
float y = 0.0f;
float minY = 0.0f;
float measuredWidth = 0.0f;
if (m_origin == TextOrigin::baseline && !m_lines.empty() &&
!m_lines[0].empty())
{
y -= m_lines[0][0].baseline;
minY = y;
}
int ellipsisLine = -1;
bool isEllipsisLineLast = false;
// Find the line to put the ellipsis on (line before the one that
// overflows).
bool wantEllipsis =
m_overflow == TextOverflow::ellipsis && m_sizing == TextSizing::fixed;
int lastLineIndex = -1;
for (const SimpleArray<GlyphLine>& paragraphLines : m_lines)
{
const Paragraph& paragraph = m_shape[paragraphIndex++];
for (const GlyphLine& line : paragraphLines)
{
const GlyphRun& endRun = paragraph.runs[line.endRunIndex];
const GlyphRun& startRun = paragraph.runs[line.startRunIndex];
float width = endRun.xpos[line.endGlyphIndex] -
startRun.xpos[line.startGlyphIndex];
if (width > measuredWidth)
{
measuredWidth = width;
}
lastLineIndex++;
if (wantEllipsis && y + line.bottom <= m_maxHeight)
{
ellipsisLine++;
}
}
if (!paragraphLines.empty())
{
y += paragraphLines.back().bottom;
}
y += m_paragraphSpacing;
}
if (wantEllipsis && ellipsisLine == -1)
{
// Nothing fits, just show the first line and ellipse it.
ellipsisLine = 0;
}
isEllipsisLineLast = lastLineIndex == ellipsisLine;
int lineIndex = 0;
switch (m_sizing)
{
case TextSizing::autoWidth:
m_bounds = AABB(0.0f,
minY,
measuredWidth,
std::max(minY, y - m_paragraphSpacing));
break;
case TextSizing::autoHeight:
m_bounds = AABB(0.0f,
minY,
m_maxWidth,
std::max(minY, y - m_paragraphSpacing));
break;
case TextSizing::fixed:
m_bounds = AABB(0.0f, minY, m_maxWidth, minY + m_maxHeight);
break;
}
// Build the clip path if we want it.
if (m_overflow == TextOverflow::clipped)
{
if (m_clipRenderPath == nullptr)
{
m_clipRenderPath = m_factory->makeEmptyRenderPath();
}
else
{
m_clipRenderPath->rewind();
}
m_clipRenderPath->addRect(m_bounds.minX,
m_bounds.minY,
m_bounds.width(),
m_bounds.height());
}
else
{
m_clipRenderPath = nullptr;
}
y = 0;
if (m_origin == TextOrigin::baseline && !m_lines.empty() &&
!m_lines[0].empty())
{
y -= m_lines[0][0].baseline;
}
paragraphIndex = 0;
for (const SimpleArray<GlyphLine>& paragraphLines : m_lines)
{
const Paragraph& paragraph = m_shape[paragraphIndex++];
for (const GlyphLine& line : paragraphLines)
{
switch (m_overflow)
{
case TextOverflow::hidden:
if (m_sizing == TextSizing::fixed &&
y + line.bottom > m_maxHeight)
{
return;
}
break;
case TextOverflow::clipped:
if (m_sizing == TextSizing::fixed &&
y + line.top > m_maxHeight)
{
return;
}
break;
default:
break;
}
float renderY = y + line.baseline;
if (lineIndex >= m_orderedLines.size())
{
// We need to still compute this line's ordered runs.
m_orderedLines.emplace_back(
OrderedLine(paragraph,
line,
m_maxWidth,
ellipsisLine == lineIndex,
isEllipsisLineLast,
&m_ellipsisRun,
renderY));
}
const OrderedLine& orderedLine = m_orderedLines[lineIndex];
m_lineDirections.push_back((uint8_t)paragraph.baseDirection());
uint32_t lineGlyphs = 0;
float x = line.startX;
for (auto glyphItr : orderedLine)
{
lineGlyphs++;
const GlyphRun* run = std::get<0>(glyphItr);
size_t glyphIndex = std::get<1>(glyphItr);
const Font* font = run->font.get();
const Vec2D& offset = run->offsets[glyphIndex];
GlyphID glyphId = run->glyphs[glyphIndex];
float advance = run->advances[glyphIndex];
assert(run->styleId < m_styles.size());
RenderStyle* style = &m_styles[run->styleId];
assert(style != nullptr);
Mat2D transform(run->size,
0.0f,
0.0f,
run->size,
x + offset.x,
renderY + offset.y);
x += advance;
if (font->isColorGlyph(glyphId))
{
RawTextDrawCommand cmd;
cmd.type = RawTextDrawCommand::kColorGlyph;
cmd.colorGlyph = {run->font,
glyphId,
transform,
style->foregroundColor};
m_drawCommands.push_back(std::move(cmd));
}
else
{
RawPath path = font->getPath(glyphId);
style->path.addPathClockwise(path, &transform);
if (style->isEmpty)
{
style->isEmpty = false;
m_renderStyles.push_back(style);
RawTextDrawCommand cmd;
cmd.type = RawTextDrawCommand::kStylePath;
cmd.style = style;
m_drawCommands.push_back(std::move(cmd));
}
}
}
m_lineGlyphCounts.push_back(lineGlyphs);
if (lineIndex == ellipsisLine)
{
return;
}
lineIndex++;
}
if (!paragraphLines.empty())
{
y += paragraphLines.back().bottom;
}
y += m_paragraphSpacing;
}
}
AABB RawText::bounds()
{
if (m_dirty)
{
update();
m_dirty = false;
}
return m_bounds;
}
void RawText::render(Renderer* renderer, rcp<RenderPaint> paint)
{
if (m_dirty)
{
update();
m_dirty = false;
}
if (m_overflow == TextOverflow::clipped && m_clipRenderPath)
{
renderer->save();
renderer->clipPath(m_clipRenderPath.get());
}
for (auto& cmd : m_drawCommands)
{
if (cmd.type == RawTextDrawCommand::kStylePath)
{
auto renderPaint = paint ? paint.get() : cmd.style->paint.get();
if (renderPaint != nullptr)
{
renderer->drawPath(cmd.style->path.renderPath(m_factory),
renderPaint);
}
}
else
{
auto& info = cmd.colorGlyph;
drawColorGlyph(renderer,
*info.font,
info.glyphId,
info.transform,
info.foregroundColor);
}
}
if (m_overflow == TextOverflow::clipped && m_clipRenderPath)
{
renderer->restore();
}
}
void RawText::drawColorGlyph(Renderer* renderer,
const Font& font,
GlyphID glyphId,
const Mat2D& transform,
ColorInt foregroundColor)
{
std::vector<Font::ColorGlyphLayer> layers;
if (font.getColorLayers(glyphId, layers, foregroundColor) == 0)
{
return;
}
renderer->save();
renderer->transform(transform);
for (auto& layer : layers)
{
drawColorGlyphLayer(renderer, m_factory, layer, 1.0f);
}
renderer->restore();
}
RawPath RawText::glyphPath(const Font& font, GlyphID glyphId)
{
ShapePaintPath path;
path.addPathClockwise(font.getPath(glyphId), nullptr);
return *path.rawPath();
}
const std::vector<OrderedLine>& RawText::orderedLines()
{
if (m_dirty)
{
update();
m_dirty = false;
}
return m_orderedLines;
}
float RawText::lineTop(uint32_t line)
{
const OrderedLine& orderedLine = orderedLines()[line];
const GlyphLine& glyphLine = orderedLine.glyphLine();
return orderedLine.y() - glyphLine.baseline + glyphLine.top;
}
TextDirection RawText::lineDirection(uint32_t line)
{
orderedLines();
return (TextDirection)m_lineDirections[line];
}
uint32_t RawText::lineGlyphCount(uint32_t line)
{
orderedLines();
return m_lineGlyphCounts[line];
}
TextLayoutView RawText::layoutView()
{
orderedLines();
if (m_lookupDirty)
{
m_glyphLookup.compute(m_styled.unichars(), m_shape);
m_lookupDirty = false;
}
return TextLayoutView(m_shape,
m_lines,
m_orderedLines,
m_glyphLookup,
(uint32_t)m_styled.unichars().size());
}
RenderPaint* RawText::stylePaint(uint16_t styleId) const
{
return styleId < m_styles.size() ? m_styles[styleId].paint.get() : nullptr;
}
ColorInt RawText::styleForegroundColor(uint16_t styleId) const
{
return styleId < m_styles.size() ? m_styles[styleId].foregroundColor
: 0xFF000000;
}
#endif