Letter spacing! Still needs a new icon and one consideration around computed width but generally ready for action and can animate! <img width="927" alt="CleanShot 2023-08-09 at 11 49 55@2x" src="https://github.com/rive-app/rive/assets/454182/d11a3fba-b5a8-4bac-9624-ed7282b293d6"> Diffs= 30351d475 Letter spacing! (#5774) Co-authored-by: Alex Gibson <agibson.uk@gmail.com> Co-authored-by: Luigi Rosso <luigi-rosso@users.noreply.github.com>
diff --git a/.rive_head b/.rive_head index d5eb3a6..bca50bc 100644 --- a/.rive_head +++ b/.rive_head
@@ -1 +1 @@ -3ab062dd27b3769005e530e8f3cc9bdfc8a3458e +30351d475a956cf0fd13dbe26257e057cc14524f
diff --git a/dev/defs/text/text_style.json b/dev/defs/text/text_style.json index 346731c..c20fba6 100644 --- a/dev/defs/text/text_style.json +++ b/dev/defs/text/text_style.json
@@ -24,6 +24,15 @@ "string": "lineheight" } }, + "letterSpacing": { + "type": "double", + "initialValue": "0.0", + "animates": true, + "key": { + "int": 390, + "string": "letterspacing" + } + }, "fontAssetId": { "type": "Id", "typeRuntime": "uint",
diff --git a/include/rive/generated/core_registry.hpp b/include/rive/generated/core_registry.hpp index cac7f25..efc7388 100644 --- a/include/rive/generated/core_registry.hpp +++ b/include/rive/generated/core_registry.hpp
@@ -981,6 +981,9 @@ case TextStyleBase::lineHeightPropertyKey: object->as<TextStyleBase>()->lineHeight(value); break; + case TextStyleBase::letterSpacingPropertyKey: + object->as<TextStyleBase>()->letterSpacing(value); + break; case TextStyleAxisBase::axisValuePropertyKey: object->as<TextStyleAxisBase>()->axisValue(value); break; @@ -1531,6 +1534,8 @@ return object->as<TextStyleBase>()->fontSize(); case TextStyleBase::lineHeightPropertyKey: return object->as<TextStyleBase>()->lineHeight(); + case TextStyleBase::letterSpacingPropertyKey: + return object->as<TextStyleBase>()->letterSpacing(); case TextStyleAxisBase::axisValuePropertyKey: return object->as<TextStyleAxisBase>()->axisValue(); case TextBase::widthPropertyKey: @@ -1830,6 +1835,7 @@ case TextModifierGroupBase::scaleYPropertyKey: case TextStyleBase::fontSizePropertyKey: case TextStyleBase::lineHeightPropertyKey: + case TextStyleBase::letterSpacingPropertyKey: case TextStyleAxisBase::axisValuePropertyKey: case TextBase::widthPropertyKey: case TextBase::heightPropertyKey:
diff --git a/include/rive/generated/text/text_style_base.hpp b/include/rive/generated/text/text_style_base.hpp index cabef2b..6076d91 100644 --- a/include/rive/generated/text/text_style_base.hpp +++ b/include/rive/generated/text/text_style_base.hpp
@@ -32,11 +32,13 @@ static const uint16_t fontSizePropertyKey = 274; static const uint16_t lineHeightPropertyKey = 370; + static const uint16_t letterSpacingPropertyKey = 390; static const uint16_t fontAssetIdPropertyKey = 279; private: float m_FontSize = 12.0f; float m_LineHeight = -1.0f; + float m_LetterSpacing = 0.0f; uint32_t m_FontAssetId = -1; public: @@ -62,6 +64,17 @@ lineHeightChanged(); } + inline float letterSpacing() const { return m_LetterSpacing; } + void letterSpacing(float value) + { + if (m_LetterSpacing == value) + { + return; + } + m_LetterSpacing = value; + letterSpacingChanged(); + } + inline uint32_t fontAssetId() const { return m_FontAssetId; } void fontAssetId(uint32_t value) { @@ -78,6 +91,7 @@ { m_FontSize = object.m_FontSize; m_LineHeight = object.m_LineHeight; + m_LetterSpacing = object.m_LetterSpacing; m_FontAssetId = object.m_FontAssetId; ContainerComponent::copy(object); } @@ -92,6 +106,9 @@ case lineHeightPropertyKey: m_LineHeight = CoreDoubleType::deserialize(reader); return true; + case letterSpacingPropertyKey: + m_LetterSpacing = CoreDoubleType::deserialize(reader); + return true; case fontAssetIdPropertyKey: m_FontAssetId = CoreUintType::deserialize(reader); return true; @@ -102,6 +119,7 @@ protected: virtual void fontSizeChanged() {} virtual void lineHeightChanged() {} + virtual void letterSpacingChanged() {} virtual void fontAssetIdChanged() {} }; } // namespace rive
diff --git a/include/rive/text/text.hpp b/include/rive/text/text.hpp index 05ea78e..95f4278 100644 --- a/include/rive/text/text.hpp +++ b/include/rive/text/text.hpp
@@ -47,6 +47,7 @@ void append(rcp<Font> font, float size, float lineHeight, + float letterSpacing, const std::string& text, uint16_t styleId); const std::vector<Unichar>& unichars() const { return m_value; }
diff --git a/include/rive/text/text_style.hpp b/include/rive/text/text_style.hpp index a1b6802..3bef063 100644 --- a/include/rive/text/text_style.hpp +++ b/include/rive/text/text_style.hpp
@@ -43,6 +43,7 @@ protected: void fontSizeChanged() override; void lineHeightChanged() override; + void letterSpacingChanged() override; private: std::unique_ptr<TextVariationHelper> m_variationHelper;
diff --git a/include/rive/text_engine.hpp b/include/rive/text_engine.hpp index 9d0da4b..d244f04 100644 --- a/include/rive/text_engine.hpp +++ b/include/rive/text_engine.hpp
@@ -38,7 +38,7 @@ center = 2 }; -// A horizontal line of text with a paragraph, after line-breaking. +// A horizontal line of text within a paragraph, after line-breaking. struct GlyphLine { uint32_t startRunIndex; @@ -187,6 +187,7 @@ rcp<Font> font; float size; float lineHeight; + float letterSpacing; uint32_t unicharCount; uint32_t script; uint16_t styleId; @@ -217,6 +218,7 @@ rcp<Font> font; float size; float lineHeight; + float letterSpacing; // List of glyphs, represented by font specific glyph ids. Length is equal to number of glyphs // in the run.
diff --git a/src/text/font_hb.cpp b/src/text/font_hb.cpp index 0e75107..a1af534 100644 --- a/src/text/font_hb.cpp +++ b/src/text/font_hb.cpp
@@ -345,18 +345,17 @@ gr.font = tr.font; gr.size = tr.size; gr.lineHeight = tr.lineHeight; + gr.letterSpacing = tr.letterSpacing; gr.styleId = tr.styleId; gr.dir = tr.dir; const float scale = tr.size / kStdScale; for (unsigned int i = 0; i < glyph_count; i++) { - // hb_position_t x_offset = glyph_pos[i].x_offset; - // hb_position_t y_offset = glyph_pos[i].y_offset; unsigned int index = tr.dir == rive::TextDirection::rtl ? glyph_count - 1 - i : i; gr.glyphs[i] = (uint16_t)glyph_info[index].codepoint; gr.textIndices[i] = textOffset + glyph_info[index].cluster; - gr.advances[i] = gr.xpos[i] = glyph_pos[index].x_advance * scale; + gr.advances[i] = gr.xpos[i] = glyph_pos[index].x_advance * scale + tr.letterSpacing; gr.offsets[i] = rive::Vec2D(glyph_pos[index].x_offset * scale, -glyph_pos[index].y_offset * scale); } @@ -376,6 +375,7 @@ subset.font = std::move(orig.font); subset.size = orig.size; subset.lineHeight = orig.lineHeight; + subset.letterSpacing = orig.letterSpacing; subset.dir = orig.dir; subset.xpos.back() = 0; // since we're now the end of a run subset.styleId = orig.styleId; @@ -408,6 +408,7 @@ fallbackFont, orig.size, orig.lineHeight, + origTextRun.letterSpacing, textCount, origTextRun.script, orig.styleId, @@ -477,6 +478,7 @@ tr.font, tr.size, tr.lineHeight, + tr.letterSpacing, tr.unicharCount - runTextIndex, (uint32_t)lastScript, tr.styleId, @@ -514,6 +516,7 @@ back.font, back.size, back.lineHeight, + back.letterSpacing, tr.unicharCount - runTextIndex, (uint32_t)script, back.styleId,
diff --git a/src/text/line_breaker.cpp b/src/text/line_breaker.cpp index a3be8ef..298c3e7 100644 --- a/src/text/line_breaker.cpp +++ b/src/text/line_breaker.cpp
@@ -16,7 +16,8 @@ { maxLineWidth = std::max(maxLineWidth, runs[line.endRunIndex].xpos[line.endGlyphIndex] - - runs[line.startRunIndex].xpos[line.startGlyphIndex]); + runs[line.startRunIndex].xpos[line.startGlyphIndex] - + runs[line.endRunIndex].letterSpacing); } return maxLineWidth; } @@ -84,7 +85,8 @@ line.bottom = Y; auto lineWidth = runs[line.endRunIndex].xpos[line.endGlyphIndex] - - runs[line.startRunIndex].xpos[line.startGlyphIndex]; + runs[line.startRunIndex].xpos[line.startGlyphIndex] - + runs[line.endRunIndex].letterSpacing; switch (align) { case TextAlign::right:
diff --git a/src/text/text.cpp b/src/text/text.cpp index c0cfafe..b2bb540 100644 --- a/src/text/text.cpp +++ b/src/text/text.cpp
@@ -169,6 +169,7 @@ TextRun ellipsisRuns[] = {{ellipsisFont, ellipsisFontSize, run.lineHeight, + run.letterSpacing, (uint32_t)ellipsisCodePoints.size()}}; auto nextEllipsisShape = ellipsisFont->shapeText(ellipsisCodePoints, Span<TextRun>(ellipsisRuns, 1)); @@ -264,7 +265,8 @@ { const GlyphRun& endRun = paragraph.runs[line.endRunIndex]; const GlyphRun& startRun = paragraph.runs[line.startRunIndex]; - float width = endRun.xpos[line.endGlyphIndex] - startRun.xpos[line.startGlyphIndex]; + float width = endRun.xpos[line.endGlyphIndex] - startRun.xpos[line.startGlyphIndex] - + endRun.letterSpacing; if (width > maxWidth) { maxWidth = width; @@ -544,6 +546,7 @@ void StyledText::append(rcp<Font> font, float size, float lineHeight, + float letterSpacing, const std::string& text, uint16_t styleId) { @@ -554,7 +557,7 @@ m_value.push_back(UTF::NextUTF8(&ptr)); n += 1; } - m_runs.push_back({std::move(font), size, lineHeight, n, 0, styleId}); + m_runs.push_back({std::move(font), size, lineHeight, letterSpacing, n, 0, styleId}); } bool Text::makeStyled(StyledText& styledText, bool withModifiers) const @@ -570,7 +573,12 @@ runIndex++; continue; } - styledText.append(style->font(), style->fontSize(), style->lineHeight(), text, runIndex++); + styledText.append(style->font(), + style->fontSize(), + style->lineHeight(), + style->letterSpacing(), + text, + runIndex++); } if (withModifiers) {
diff --git a/src/text/text_modifier_group.cpp b/src/text/text_modifier_group.cpp index 44f3859..3569ca2 100644 --- a/src/text/text_modifier_group.cpp +++ b/src/text/text_modifier_group.cpp
@@ -174,6 +174,7 @@ source.font, source.size, source.lineHeight, + source.letterSpacing, unicharCount, source.script, source.styleId, @@ -210,6 +211,7 @@ m_variableFont, run.size, run.lineHeight, + run.letterSpacing, run.unicharCount, run.script, run.styleId,
diff --git a/src/text/text_style.cpp b/src/text/text_style.cpp index 45439ec..21e5b65 100644 --- a/src/text/text_style.cpp +++ b/src/text/text_style.cpp
@@ -212,6 +212,8 @@ void TextStyle::lineHeightChanged() { parent()->as<Text>()->markShapeDirty(); } +void TextStyle::letterSpacingChanged() { parent()->as<Text>()->markShapeDirty(); } + Core* TextStyle::clone() const { TextStyle* twin = TextStyleBase::clone()->as<TextStyle>();
diff --git a/test/font_test.cpp b/test/font_test.cpp index fed4fb8..2bdfce0 100644 --- a/test/font_test.cpp +++ b/test/font_test.cpp
@@ -19,7 +19,7 @@ unichars->push_back(rive::UTF::NextUTF8(&ptr)); n += 1; } - return {std::move(font), size, -1.0f, n, 0}; + return {std::move(font), size, -1.0f, 0.0f, n, 0}; } static rcp<Font> loadFont(const char* filename)
diff --git a/test/line_break_test.cpp b/test/line_break_test.cpp index e993596..34dad5b 100644 --- a/test/line_break_test.cpp +++ b/test/line_break_test.cpp
@@ -22,7 +22,7 @@ unichars->push_back(rive::UTF::NextUTF8(&ptr)); n += 1; } - return {std::move(font), size, -1.0f, n, 0}; + return {std::move(font), size, -1.0f, 0.0f, n, 0}; } static rcp<Font> loadFont(const char* filename)
diff --git a/viewer/src/viewer_content/text_content.cpp b/viewer/src/viewer_content/text_content.cpp index 29470ff..81f2637 100644 --- a/viewer/src/viewer_content/text_content.cpp +++ b/viewer/src/viewer_content/text_content.cpp
@@ -147,7 +147,7 @@ unichars->push_back(rive::UTF::NextUTF8(&ptr)); n += 1; } - return {std::move(font), size, lineHeight, n}; + return {std::move(font), size, lineHeight, 0.0f, n}; } class TextContent : public ViewerContent