From 90942f111b834de31c03bd1e6eb810c25b76303e Mon Sep 17 00:00:00 2001 From: Samuel Ugochukwu Date: Thu, 19 Dec 2024 12:17:18 +0100 Subject: [PATCH] Add `alignment-baseline` and `dominant-baseline` support --- source/graphics.cpp | 8 +++++ source/graphics.h | 1 + source/svglayoutstate.cpp | 47 ++++++++++++++++++++++++++ source/svglayoutstate.h | 6 ++++ source/svgproperty.cpp | 2 ++ source/svgproperty.h | 32 ++++++++++++++++++ source/svgtextelement.cpp | 69 ++++++++++++++++++++++++++++++++++++++- source/svgtextelement.h | 4 +++ 8 files changed, 168 insertions(+), 1 deletion(-) diff --git a/source/graphics.cpp b/source/graphics.cpp index c5eb3be..27a7514 100644 --- a/source/graphics.cpp +++ b/source/graphics.cpp @@ -517,6 +517,14 @@ float Font::height() const return ascent + descent; } +float Font::xHeight() const +{ + plutovg_rect_t extents = {0}; + if(m_size > 0.f && !m_face.isNull()) + plutovg_font_face_get_glyph_metrics(m_face.get(), m_size, 'x', nullptr, nullptr, &extents); + return extents.h; +} + float Font::measureText(const std::u32string_view& text) const { if(m_size > 0.f && !m_face.isNull()) diff --git a/source/graphics.h b/source/graphics.h index cd276d2..d981379 100644 --- a/source/graphics.h +++ b/source/graphics.h @@ -439,6 +439,7 @@ class Font { float ascent() const; float descent() const; float height() const; + float xHeight() const; float measureText(const std::u32string_view& text) const; diff --git a/source/svglayoutstate.cpp b/source/svglayoutstate.cpp index a557784..9255879 100644 --- a/source/svglayoutstate.cpp +++ b/source/svglayoutstate.cpp @@ -232,6 +232,46 @@ static FontStyle parseFontStyle(const std::string_view& input) return parseEnumValue(input, entries, FontStyle::Normal); } +static AlignmentBaseline parseAlignmentBaseline(const std::string_view& input) +{ + static const SVGEnumerationEntry entries[] = { + {AlignmentBaseline::Auto, "auto"}, + {AlignmentBaseline::Baseline, "baseline"}, + {AlignmentBaseline::BeforeEdge, "before-edge"}, + {AlignmentBaseline::TextBeforeEdge, "text-before-edge"}, + {AlignmentBaseline::Middle, "middle"}, + {AlignmentBaseline::Central, "central"}, + {AlignmentBaseline::AfterEdge, "after-edge"}, + {AlignmentBaseline::TextAfterEdge, "text-after-edge"}, + {AlignmentBaseline::Ideographic, "ideographic"}, + {AlignmentBaseline::Alphabetic, "alphabetic"}, + {AlignmentBaseline::Hanging, "hanging"}, + {AlignmentBaseline::Mathematical, "mathematical"} + }; + + return parseEnumValue(input, entries, AlignmentBaseline::Auto); +} + +static DominantBaseline parseDominantBaseline(const std::string_view& input) +{ + static const SVGEnumerationEntry entries[] = { + {DominantBaseline::Auto, "auto"}, + {DominantBaseline::UseScript, "use-script"}, + {DominantBaseline::NoChange, "no-change"}, + {DominantBaseline::ResetSize, "reset-size"}, + {DominantBaseline::Ideographic, "ideographic"}, + {DominantBaseline::Alphabetic, "alphabetic"}, + {DominantBaseline::Hanging, "hanging"}, + {DominantBaseline::Mathematical, "mathematical"}, + {DominantBaseline::Central, "central"}, + {DominantBaseline::Middle, "middle"}, + {DominantBaseline::TextAfterEdge, "text-after-edge"}, + {DominantBaseline::TextBeforeEdge, "text-before-edge"} + }; + + return parseEnumValue(input, entries, DominantBaseline::Auto); +} + static Direction parseDirection(const std::string_view& input) { static const SVGEnumerationEntry entries[] = { @@ -329,6 +369,7 @@ SVGLayoutState::SVGLayoutState(const SVGLayoutState& parent, const SVGElement* e , m_clip_rule(parent.clip_rule()) , m_font_weight(parent.font_weight()) , m_font_style(parent.font_style()) + , m_dominant_baseline(parent.dominant_baseline()) , m_text_anchor(parent.text_anchor()) , m_white_space(parent.white_space()) , m_direction(parent.direction()) @@ -405,6 +446,12 @@ SVGLayoutState::SVGLayoutState(const SVGLayoutState& parent, const SVGElement* e case PropertyID::Font_Style: m_font_style = parseFontStyle(input); break; + case PropertyID::Alignment_Baseline: + m_alignment_baseline = parseAlignmentBaseline(input); + break; + case PropertyID::Dominant_Baseline: + m_dominant_baseline = parseDominantBaseline(input); + break; case PropertyID::Direction: m_direction = parseDirection(input); break; diff --git a/source/svglayoutstate.h b/source/svglayoutstate.h index 7fe60b7..34af91d 100644 --- a/source/svglayoutstate.h +++ b/source/svglayoutstate.h @@ -40,6 +40,9 @@ class SVGLayoutState { FontWeight font_weight() const { return m_font_weight; } FontStyle font_style() const { return m_font_style; } + AlignmentBaseline alignment_baseline() const { return m_alignment_baseline; } + DominantBaseline dominant_baseline() const { return m_dominant_baseline; } + TextAnchor text_anchor() const { return m_text_anchor; } WhiteSpace white_space() const { return m_white_space; } Direction direction() const { return m_direction; } @@ -89,6 +92,9 @@ class SVGLayoutState { FontWeight m_font_weight = FontWeight::Normal; FontStyle m_font_style = FontStyle::Normal; + AlignmentBaseline m_alignment_baseline = AlignmentBaseline::Auto; + DominantBaseline m_dominant_baseline = DominantBaseline::Auto; + TextAnchor m_text_anchor = TextAnchor::Start; WhiteSpace m_white_space = WhiteSpace::Default; Direction m_direction = Direction::Ltr; diff --git a/source/svgproperty.cpp b/source/svgproperty.cpp index b52bd98..0b873c8 100644 --- a/source/svgproperty.cpp +++ b/source/svgproperty.cpp @@ -71,12 +71,14 @@ PropertyID csspropertyid(const std::string_view& name) std::string_view name; PropertyID value; } table[] = { + {"alignment-baseline", PropertyID::Alignment_Baseline}, {"baseline-shift", PropertyID::Baseline_Shift}, {"clip-path", PropertyID::Clip_Path}, {"clip-rule", PropertyID::Clip_Rule}, {"color", PropertyID::Color}, {"direction", PropertyID::Direction}, {"display", PropertyID::Display}, + {"dominant-baseline", PropertyID::Dominant_Baseline}, {"fill", PropertyID::Fill}, {"fill-opacity", PropertyID::Fill_Opacity}, {"fill-rule", PropertyID::Fill_Rule}, diff --git a/source/svgproperty.h b/source/svgproperty.h index 31e4eb6..73163fd 100644 --- a/source/svgproperty.h +++ b/source/svgproperty.h @@ -9,6 +9,7 @@ namespace lunasvg { enum class PropertyID : uint8_t { Unknown = 0, + Alignment_Baseline, Baseline_Shift, Class, ClipPathUnits, @@ -20,6 +21,7 @@ enum class PropertyID : uint8_t { D, Direction, Display, + Dominant_Baseline, Dx, Dy, Fill, @@ -162,6 +164,36 @@ enum class FontWeight : uint8_t { Bold }; +enum class AlignmentBaseline : uint8_t { + Auto, + Baseline, + BeforeEdge, + TextBeforeEdge, + Middle, + Central, + AfterEdge, + TextAfterEdge, + Ideographic, + Alphabetic, + Hanging, + Mathematical +}; + +enum class DominantBaseline : uint8_t { + Auto, + UseScript, + NoChange, + ResetSize, + Ideographic, + Alphabetic, + Hanging, + Mathematical, + Central, + Middle, + TextAfterEdge, + TextBeforeEdge +}; + enum class TextAnchor : uint8_t { Start, Middle, diff --git a/source/svgtextelement.cpp b/source/svgtextelement.cpp index 2a771d1..45e4223 100644 --- a/source/svgtextelement.cpp +++ b/source/svgtextelement.cpp @@ -18,11 +18,76 @@ inline const SVGTextPositioningElement* toSVGTextPositioningElement(const SVGNod return static_cast(node); } +static AlignmentBaseline resolveDominantBaseline(const SVGTextPositioningElement* element) +{ + switch(element->dominant_baseline()) { + case DominantBaseline::Auto: + case DominantBaseline::UseScript: + case DominantBaseline::NoChange: + case DominantBaseline::ResetSize: + return AlignmentBaseline::Auto; + case DominantBaseline::Ideographic: + return AlignmentBaseline::Ideographic; + case DominantBaseline::Alphabetic: + return AlignmentBaseline::Alphabetic; + case DominantBaseline::Hanging: + return AlignmentBaseline::Hanging; + case DominantBaseline::Mathematical: + return AlignmentBaseline::Mathematical; + case DominantBaseline::Central: + return AlignmentBaseline::Central; + case DominantBaseline::Middle: + return AlignmentBaseline::Middle; + case DominantBaseline::TextAfterEdge: + return AlignmentBaseline::TextAfterEdge; + case DominantBaseline::TextBeforeEdge: + return AlignmentBaseline::TextBeforeEdge; + default: + assert(false); + } + + return AlignmentBaseline::Auto; +} + static float calculateBaselineOffset(const SVGTextPositioningElement* element) { auto offset = element->baseline_offset(); - for(auto parent = element->parent(); parent->isTextPositioningElement(); parent = parent->parent()) + for(auto parent = element->parent(); parent->isTextPositioningElement(); parent = parent->parent()) { offset += toSVGTextPositioningElement(parent)->baseline_offset(); + } + + auto baseline = element->alignment_baseline(); + if(baseline == AlignmentBaseline::Baseline || baseline == AlignmentBaseline::Auto) { + baseline = resolveDominantBaseline(element); + } + + const auto& font = element->font(); + switch(baseline) { + case AlignmentBaseline::BeforeEdge: + case AlignmentBaseline::TextBeforeEdge: + offset -= font.ascent(); + break; + case AlignmentBaseline::Middle: + offset -= font.xHeight() / 2.f; + break; + case AlignmentBaseline::Central: + offset -= (font.ascent() - font.descent()) / 2.f; + break; + case AlignmentBaseline::AfterEdge: + case AlignmentBaseline::TextAfterEdge: + case AlignmentBaseline::Ideographic: + offset -= font.descent(); + break; + case AlignmentBaseline::Hanging: + offset -= font.ascent() * 8.f / 10.f; + break; + case AlignmentBaseline::Mathematical: + offset -= font.ascent() / 2.f; + break; + default: + break; + } + return offset; } @@ -289,6 +354,8 @@ void SVGTextPositioningElement::layoutElement(const SVGLayoutState& state) LengthContext lengthContext(this); m_stroke_width = lengthContext.valueForLength(state.stroke_width(), LengthDirection::Diagonal); m_baseline_offset = convertBaselineOffset(state.baseline_shit()); + m_alignment_baseline = state.alignment_baseline(); + m_dominant_baseline = state.dominant_baseline(); m_text_anchor = state.text_anchor(); m_white_space = state.white_space(); m_direction = state.direction(); diff --git a/source/svgtextelement.h b/source/svgtextelement.h index 7b8009b..8b0a0e2 100644 --- a/source/svgtextelement.h +++ b/source/svgtextelement.h @@ -83,6 +83,8 @@ class SVGTextPositioningElement : public SVGGraphicsElement { float stroke_width() const { return m_stroke_width; } float baseline_offset() const { return m_baseline_offset; } + AlignmentBaseline alignment_baseline() const { return m_alignment_baseline; } + DominantBaseline dominant_baseline() const { return m_dominant_baseline; } TextAnchor text_anchor() const { return m_text_anchor; } WhiteSpace white_space() const { return m_white_space; } Direction direction() const { return m_direction; } @@ -103,6 +105,8 @@ class SVGTextPositioningElement : public SVGGraphicsElement { float m_stroke_width = 1.f; float m_baseline_offset = 0.f; + AlignmentBaseline m_alignment_baseline = AlignmentBaseline::Auto; + DominantBaseline m_dominant_baseline = DominantBaseline::Auto; TextAnchor m_text_anchor = TextAnchor::Start; WhiteSpace m_white_space = WhiteSpace::Default; Direction m_direction = Direction::Ltr;