Skip to content

Commit

Permalink
add text vertical alignment
Browse files Browse the repository at this point in the history
PR that adds support for vertical alignment.
It is behind the same feature flag as text wrap and overflow fit features.

https://github.com/user-attachments/assets/46f87f86-67b8-4828-843a-925b3de5929e

Diffs=
6e89e230a add text vertical alignment (#8091)

Co-authored-by: hernan <[email protected]>
  • Loading branch information
bodymovin and bodymovin committed Sep 7, 2024
1 parent e3b39c0 commit a3a236b
Show file tree
Hide file tree
Showing 7 changed files with 152 additions and 24 deletions.
2 changes: 1 addition & 1 deletion .rive_head
Original file line number Diff line number Diff line change
@@ -1 +1 @@
fcbc28bbc33304c71071ffcc96cfd1c917e7e5e7
6e89e230aa03f391815ab23b2f966993e2add0e7
8 changes: 8 additions & 0 deletions dev/defs/text/text.json
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,14 @@
"string": "wrapvalue"
},
"description": "One of wrap, noWrap"
},
"verticalAlignValue": {
"type": "uint",
"initialValue": "0",
"key": {
"int": 685,
"string": "verticalalignvalue"
}
}
}
}
8 changes: 8 additions & 0 deletions include/rive/generated/core_registry.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1137,6 +1137,9 @@ class CoreRegistry
case TextBase::wrapValuePropertyKey:
object->as<TextBase>()->wrapValue(value);
break;
case TextBase::verticalAlignValuePropertyKey:
object->as<TextBase>()->verticalAlignValue(value);
break;
case TextValueRunBase::styleIdPropertyKey:
object->as<TextValueRunBase>()->styleId(value);
break;
Expand Down Expand Up @@ -2160,6 +2163,8 @@ class CoreRegistry
return object->as<TextBase>()->originValue();
case TextBase::wrapValuePropertyKey:
return object->as<TextBase>()->wrapValue();
case TextBase::verticalAlignValuePropertyKey:
return object->as<TextBase>()->verticalAlignValue();
case TextValueRunBase::styleIdPropertyKey:
return object->as<TextValueRunBase>()->styleId();
case FileAssetBase::assetIdPropertyKey:
Expand Down Expand Up @@ -2778,6 +2783,7 @@ class CoreRegistry
case TextBase::overflowValuePropertyKey:
case TextBase::originValuePropertyKey:
case TextBase::wrapValuePropertyKey:
case TextBase::verticalAlignValuePropertyKey:
case TextValueRunBase::styleIdPropertyKey:
case FileAssetBase::assetIdPropertyKey:
case AudioEventBase::assetIdPropertyKey:
Expand Down Expand Up @@ -3395,6 +3401,8 @@ class CoreRegistry
return object->is<TextBase>();
case TextBase::wrapValuePropertyKey:
return object->is<TextBase>();
case TextBase::verticalAlignValuePropertyKey:
return object->is<TextBase>();
case TextValueRunBase::styleIdPropertyKey:
return object->is<TextValueRunBase>();
case FileAssetBase::assetIdPropertyKey:
Expand Down
18 changes: 18 additions & 0 deletions include/rive/generated/text/text_base.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class TextBase : public Drawable
static const uint16_t paragraphSpacingPropertyKey = 371;
static const uint16_t originValuePropertyKey = 377;
static const uint16_t wrapValuePropertyKey = 683;
static const uint16_t verticalAlignValuePropertyKey = 685;

private:
uint32_t m_AlignValue = 0;
Expand All @@ -56,6 +57,7 @@ class TextBase : public Drawable
float m_ParagraphSpacing = 0.0f;
uint32_t m_OriginValue = 0;
uint32_t m_WrapValue = 0;
uint32_t m_VerticalAlignValue = 0;

public:
inline uint32_t alignValue() const { return m_AlignValue; }
Expand Down Expand Up @@ -168,6 +170,17 @@ class TextBase : public Drawable
wrapValueChanged();
}

inline uint32_t verticalAlignValue() const { return m_VerticalAlignValue; }
void verticalAlignValue(uint32_t value)
{
if (m_VerticalAlignValue == value)
{
return;
}
m_VerticalAlignValue = value;
verticalAlignValueChanged();
}

Core* clone() const override;
void copy(const TextBase& object)
{
Expand All @@ -181,6 +194,7 @@ class TextBase : public Drawable
m_ParagraphSpacing = object.m_ParagraphSpacing;
m_OriginValue = object.m_OriginValue;
m_WrapValue = object.m_WrapValue;
m_VerticalAlignValue = object.m_VerticalAlignValue;
Drawable::copy(object);
}

Expand Down Expand Up @@ -218,6 +232,9 @@ class TextBase : public Drawable
case wrapValuePropertyKey:
m_WrapValue = CoreUintType::deserialize(reader);
return true;
case verticalAlignValuePropertyKey:
m_VerticalAlignValue = CoreUintType::deserialize(reader);
return true;
}
return Drawable::deserialize(propertyKey, reader);
}
Expand All @@ -233,6 +250,7 @@ class TextBase : public Drawable
virtual void paragraphSpacingChanged() {}
virtual void originValueChanged() {}
virtual void wrapValueChanged() {}
virtual void verticalAlignValueChanged() {}
};
} // namespace rive

Expand Down
3 changes: 2 additions & 1 deletion include/rive/text/text.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ class Text : public TextBase
void modifierShapeDirty();
void markPaintDirty();
void update(ComponentDirt value) override;
Mat2D m_fitScale;
Mat2D m_transform;

TextSizing sizing() const { return (TextSizing)sizingValue(); }
TextSizing effectiveSizing() const
Expand All @@ -186,6 +186,7 @@ class Text : public TextBase
TextOverflow overflow() const { return (TextOverflow)overflowValue(); }
TextOrigin textOrigin() const { return (TextOrigin)originValue(); }
TextWrap wrap() const { return (TextWrap)wrapValue(); }
VerticalTextAlign verticalAlign() const { return (VerticalTextAlign)verticalAlignValue(); }
void overflow(TextOverflow value) { return overflowValue((uint32_t)value); }
void buildRenderStyles();
const TextStyle* styleFromShaperId(uint16_t id) const;
Expand Down
8 changes: 8 additions & 0 deletions include/rive/text_engine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@ enum class TextWrap : uint8_t
noWrap = 1
};

// The alignment of each word wrapped line in a paragraph.
enum class VerticalTextAlign : uint8_t
{
top = 0,
bottom = 1,
middle = 2
};

// A horizontal line of text within a paragraph, after line-breaking.
struct GlyphLine
{
Expand Down
129 changes: 107 additions & 22 deletions src/text/text.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -291,8 +291,9 @@ void Text::buildRenderStyles()
bool isEllipsisLineLast = false;
// Find the line to put the ellipsis on (line before the one that
// overflows).
bool wantEllipsis =
overflow() == TextOverflow::ellipsis && effectiveSizing() == TextSizing::fixed;
bool wantEllipsis = overflow() == TextOverflow::ellipsis &&
effectiveSizing() == TextSizing::fixed &&
verticalAlign() == VerticalTextAlign::top;

int lastLineIndex = -1;
for (const SimpleArray<GlyphLine>& paragraphLines : m_lines)
Expand Down Expand Up @@ -321,6 +322,7 @@ void Text::buildRenderStyles()
}
y += paragraphSpace;
}
auto totalHeight = y;
if (wantEllipsis && ellipsisLine == -1)
{
// Nothing fits, just show the first line and ellipse it.
Expand All @@ -343,6 +345,19 @@ void Text::buildRenderStyles()
break;
}

auto verticalAlignOffset = 0.0f;
switch (verticalAlign())
{
case VerticalTextAlign::middle:
verticalAlignOffset = (totalHeight - m_bounds.height()) / 2;
break;
case VerticalTextAlign::bottom:
verticalAlignOffset = totalHeight - m_bounds.height();
break;
default:
break;
}

// Build the clip path if we want it.
if (overflow() == TextOverflow::clipped)
{
Expand All @@ -357,7 +372,10 @@ void Text::buildRenderStyles()

AABB bounds = localBounds();

m_clipRenderPath->addRect(bounds.minX, bounds.minY, bounds.width(), bounds.height());
m_clipRenderPath->addRect(bounds.minX,
bounds.minY,
bounds.width(),
bounds.height() + verticalAlignOffset);
}
else
{
Expand All @@ -381,24 +399,74 @@ void Text::buildRenderStyles()
}
}
float minX = std::numeric_limits<float>::max();
bool drawLine = true;
for (const SimpleArray<GlyphLine>& paragraphLines : m_lines)
{
const Paragraph& paragraph = m_shape[paragraphIndex++];
for (const GlyphLine& line : paragraphLines)
{
drawLine = true;
switch (overflow())
{
case TextOverflow::hidden:
if (effectiveSizing() == TextSizing::fixed &&
y + line.bottom > effectiveHeight())
if (effectiveSizing() == TextSizing::fixed)
{
return;
switch (verticalAlign())
{
case VerticalTextAlign::top:
if (y + line.bottom > effectiveHeight())
{
return;
}
break;
case VerticalTextAlign::middle:
if (y + line.top < totalHeight / 2 - effectiveHeight() / 2)
{
drawLine = false;
}
if (y + line.bottom > totalHeight / 2 + effectiveHeight() / 2)
{
drawLine = false;
return;
}
break;
case VerticalTextAlign::bottom:
if (y + line.top < totalHeight - effectiveHeight())
{
continue;
}
break;
}
}
break;
case TextOverflow::clipped:
if (effectiveSizing() == TextSizing::fixed && y + line.top > effectiveHeight())
if (effectiveSizing() == TextSizing::fixed)
{
return;
switch (verticalAlign())
{
case VerticalTextAlign::top:
if (y + line.top > effectiveHeight())
{
return;
}
break;
case VerticalTextAlign::middle:
if (y + line.bottom < totalHeight / 2 - effectiveHeight() / 2)
{
drawLine = false;
}
if (y + line.top > totalHeight / 2 + effectiveHeight() / 2)
{
return;
}
break;
case VerticalTextAlign::bottom:
if (y + line.bottom < totalHeight - effectiveHeight())
{
drawLine = false;
}
break;
}
}
break;
default:
Expand Down Expand Up @@ -480,12 +548,16 @@ void Text::buildRenderStyles()
}
}
}
if (style->addPath(path, opacity))
if (drawLine)
{
// This was the first path added to the style, so let's mark
// it in our draw list.
m_renderStyles.push_back(style);
style->propagateOpacity(renderOpacity());

if (style->addPath(path, opacity))
{
// This was the first path added to the style, so let's mark
// it in our draw list.
m_renderStyles.push_back(style);
style->propagateOpacity(renderOpacity());
}
}
}
if (lineIndex == ellipsisLine)
Expand All @@ -500,20 +572,23 @@ void Text::buildRenderStyles()
}
y += paragraphSpace;
}
auto scale = 1.0f;
auto xOffset = 0.0f;
auto yOffset = 0.0f;
if (overflow() == TextOverflow::fit)
{
auto xScale = (effectiveSizing() != TextSizing::autoWidth && maxWidth > m_bounds.width())
? m_bounds.width() / maxWidth
: 1;
auto baseline = m_lines[0][0].baseline;
auto yScale = (effectiveSizing() == TextSizing::fixed && y > m_bounds.height())
? (m_bounds.height() - baseline) / (y - baseline)
auto yScale = (effectiveSizing() == TextSizing::fixed && totalHeight > m_bounds.height())
? (m_bounds.height() - baseline) / (totalHeight - baseline)
: 1;
if (xScale != 1 || yScale != 1)
{
auto scale = std::max(0.0f, xScale > yScale ? yScale : xScale);
auto yOffset = baseline * (1 - scale);
auto xOffset = 0.0f;
scale = std::max(0.0f, xScale > yScale ? yScale : xScale);
yOffset = (baseline - m_bounds.height() * originY()) * (1 - scale);
xOffset = 0.0f;
switch ((TextAlign)alignValue())
{
case TextAlign::center:
Expand All @@ -525,13 +600,23 @@ void Text::buildRenderStyles()
default:
break;
}
m_fitScale = Mat2D::fromScaleAndTranslation(scale, scale, xOffset, yOffset);
}
else
}
if (verticalAlign() != VerticalTextAlign::top)
{
if (effectiveSizing() == TextSizing::fixed)
{
m_fitScale = Mat2D();
if (verticalAlign() == VerticalTextAlign::middle)
{
yOffset = (m_bounds.height() - totalHeight * scale) / 2;
}
else if (verticalAlign() == VerticalTextAlign::bottom)
{
yOffset = m_bounds.height() - totalHeight * scale;
}
}
}
m_transform = Mat2D::fromScaleAndTranslation(scale, scale, xOffset, yOffset);
}

const TextStyle* Text::styleFromShaperId(uint16_t id) const
Expand All @@ -551,7 +636,7 @@ void Text::draw(Renderer* renderer)
}
if (clipResult != ClipResult::emptyClip)
{
renderer->transform(m_WorldTransform * m_fitScale);
renderer->transform(m_WorldTransform * m_transform);
if (overflow() == TextOverflow::clipped && m_clipRenderPath)
{
renderer->clipPath(m_clipRenderPath.get());
Expand Down

0 comments on commit a3a236b

Please sign in to comment.