Added text style modifiers

This commit is contained in:
Yousef Khadadeh 2025-11-04 10:58:26 +03:00
parent 640d4673ad
commit b43fedaaac
11 changed files with 385 additions and 34 deletions

View file

@ -54,6 +54,7 @@ set(sources "src/main.cpp"
"src/Server/MainServer.cpp"
"src/Server/ServerData.cpp"
"src/RichTextBox/RichTextBox.cpp"
"src/RichTextBox/TextStyleModifier.cpp"
"src/DrawingProgram/DrawingProgramCache.cpp"
"src/DrawingProgram/DrawingProgram.cpp"
"src/DrawingProgram/BrushTool.cpp"

View file

@ -4,9 +4,22 @@
#include <nlohmann/json.hpp>
#include <boost/multiprecision/cpp_int.hpp>
#include "FixedPoint.hpp"
#include <include/core/SkString.h>
#include <include/core/SkColor.h>
template <class Archive> void save(Archive& ar, SkString const & str) {
ar(cereal::make_size_tag(static_cast<uint64_t>(str.size())));
ar(cereal::binary_data(str.data(), str.size() * sizeof(char)));
}
template <class Archive> void load(Archive& ar, SkString& str) {
uint64_t size;
ar(cereal::make_size_tag(size));
str.resize(static_cast<size_t>(size));
ar(cereal::binary_data(const_cast<char*>(str.data()), static_cast<size_t>(size) * sizeof(char)));
}
namespace cereal
{
// https://stackoverflow.com/questions/22884216/serializing-eigenmatrix-using-cereal-library (with some modifications using constexpr)

View file

@ -26,11 +26,7 @@ bool TextBoxEditTool::edit_gui(const std::shared_ptr<DrawComponent>& comp) {
//});
t.gui.pop_id();
TextBoxEditToolAllData newData = get_all_data(a);
bool editHappened = newData != oldData;
if(editHappened)
oldData = newData;
return editHappened;
return true; // NOTE: In general, the update data should always be sent even if no changes were made, because we're sending the data over an unreliable channel.
}
void TextBoxEditTool::commit_edit_updates(const std::shared_ptr<DrawComponent>& comp, std::any& prevData) {
@ -74,8 +70,7 @@ void TextBoxEditTool::edit_start(EditTool& editTool, const std::shared_ptr<DrawC
cur = std::make_shared<RichTextBox::Cursor>();
Vector2f textSelectPos = a->get_mouse_pos(drawP);
textbox->process_mouse_left_button(*cur, textSelectPos, true, false, false);
oldData = get_all_data(a);
prevData = oldData;
prevData = get_all_data(a);
a->d.editing = true;
a->client_send_update(drawP, false);
editTool.add_point_handle({&a->d.p1, nullptr, &a->d.p2});

View file

@ -18,10 +18,6 @@ class TextBoxEditTool : public DrawingProgramEditToolBase {
struct TextBoxEditToolAllData {
DrawTextBox::Data textboxData;
RichTextBox::RichTextData richText;
bool operator==(const TextBoxEditToolAllData& o) const = default;
bool operator!=(const TextBoxEditToolAllData& o) const = default;
};
TextBoxEditToolAllData get_all_data(const std::shared_ptr<DrawTextBox>& a);
TextBoxEditToolAllData oldData;
};

View file

@ -38,7 +38,7 @@ void SelectableButton::update(UpdateInputData& io, DrawType drawType, const std:
.cornerRadius = CLAY_CORNER_RADIUS(4),
.border = {
.color = convert_vec4<Clay_Color>(borderColor),
.width = CLAY_BORDER_OUTSIDE(1)
.width = CLAY_BORDER_OUTSIDE(2)
}
}) {
CLAY({.layout = {

View file

@ -59,13 +59,23 @@ template <typename T> class TextBox : public Element {
canvas->clipRect(SkRect::MakeXYWH(bb.min.x(), bb.min.y(), bb.width(), bb.height()));
canvas->translate(bb.min.x(), bb.min.y());
canvas->translate(bb.min.x() + 2.0f, bb.min.y());
RichTextBox::PaintOpts paintOpts;
if(selection.selected)
paintOpts.cursor = *cur;
paintOpts.cursorColor = {io.theme->fillColor1.fR, io.theme->fillColor1.fG, io.theme->fillColor1.fB};
auto fontFamiliesMod = std::make_shared<FontFamiliesTextStyleModifier>();
fontFamiliesMod->families.emplace_back("Roboto");
textbox->set_initial_text_style_modifier(fontFamiliesMod);
auto fontSizeMod = std::make_shared<SizeTextStyleModifier>();
fontSizeMod->size = io.fontSize;
textbox->set_initial_text_style_modifier(fontSizeMod);
auto fontColorMod = std::make_shared<ColorTextStyleModifier>();
fontColorMod->color = convert_vec4<Vector4f>(io.theme->frontColor1);
textbox->set_initial_text_style_modifier(fontColorMod);
textbox->paint(canvas, paintOpts);
canvas->restore();
@ -80,6 +90,7 @@ template <typename T> class TextBox : public Element {
textbox = std::make_shared<RichTextBox>();
textbox->set_font_collection(io.fontCollection);
textbox->set_allow_newlines(!singleLine);
cur = std::make_shared<RichTextBox::Cursor>();
if(data) {

View file

@ -209,6 +209,16 @@ void GUIManager::draw(SkCanvas* canvas) {
break;
}
case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: {
Clay_ClipRenderData* clip = &command->renderData.clip;
if(!clip->horizontal) {
bb.x = 0.0f;
bb.width = std::numeric_limits<float>::max();
}
if(!clip->vertical) {
bb.y = 0.0f;
bb.height = std::numeric_limits<float>::max();
}
canvas->save();
SkRect clipRect = SkRect::MakeXYWH(bb.x - 1.0f, bb.y - 1.0f, bb.width + 1.0f, bb.height + 1.0f);
canvas->clipRect(clipRect);
@ -578,6 +588,7 @@ void GUIManager::dropdown_select(const std::string& id, size_t* val, const std::
CLAY({
.layout = {
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(0)},
.childGap = 1,
.childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_TOP},
.layoutDirection = CLAY_TOP_TO_BOTTOM
}
@ -727,7 +738,7 @@ void GUIManager::scroll_bar_many_entries_area(const std::string& uniqueId, float
scrollAmount = std::fabs(scrollAmount);
float entryHeight = 25.0f;
size_t startPoint = scrollAmount / entryHeight;
size_t elementsContainable = (containerHeight / entryHeight) + 1;
size_t elementsContainable = (containerHeight / entryHeight) + 2; // Displaying 2 more elements ensures that there isn't any empty space in the container
size_t endPoint = std::min(entryCount, startPoint + elementsContainable);
if(startPoint != 0) {

View file

@ -1,4 +1,5 @@
#include "RichTextBox.hpp"
#include "TextStyleModifier.hpp"
#include <limits>
#include <modules/skparagraph/include/DartTypes.h>
#include <modules/skparagraph/include/Metrics.h>
@ -9,6 +10,37 @@
#include <modules/skparagraph/src/ParagraphImpl.h>
#include <Helpers/MathExtras.hpp>
#include <src/base/SkUTF.h>
#include <cereal/types/string.hpp>
void RichTextBox::RichTextData::Paragraph::save(cereal::PortableBinaryOutputArchive& a) const {
a(text);
a(static_cast<uint32_t>(tStyles.size()));
for(auto& [pos, styles] : tStyles) {
a(pos, static_cast<uint16_t>(styles.size()));
for(auto& [styleType, style] : styles)
a(static_cast<uint16_t>(styleType), *style);
}
}
void RichTextBox::RichTextData::Paragraph::load(cereal::PortableBinaryInputArchive& a) {
a(text);
uint32_t styleCount;
a(styleCount);
tStyles.clear();
for(uint32_t i = 0; i < styleCount; i++) {
uint32_t pos;
uint16_t styleCountAtPos;
a(pos, styleCountAtPos);
TextModAtPosContainer& mods = tStyles[pos];
for(uint16_t j = 0; j < styleCountAtPos; j++) {
uint16_t styleType;
a(styleType);
std::shared_ptr<TextStyleModifier> modifier = TextStyleModifier::allocate_modifier(static_cast<TextStyleModifier::ModifierType>(styleType));
a(*modifier);
mods.emplace(static_cast<TextStyleModifier::ModifierType>(styleType), modifier);
}
}
}
bool RichTextBox::TextPosition::operator<(const RichTextBox::TextPosition& o) const {
return (this->fParagraphIndex < o.fParagraphIndex) || (this->fParagraphIndex == o.fParagraphIndex && this->fTextByteIndex < o.fTextByteIndex);
@ -50,13 +82,13 @@ void RichTextBox::process_key_input(Cursor& cur, InputKey in, bool ctrl, bool sh
if(cur.selectionBeginPos != cur.selectionEndPos)
cur.selectionEndPos = cur.selectionBeginPos = cur.pos = remove(cur.selectionBeginPos, cur.selectionEndPos);
else
cur.selectionEndPos = cur.selectionBeginPos = cur.pos = remove(cur.pos, move(Movement::LEFT, cur.pos));
cur.selectionEndPos = cur.selectionBeginPos = cur.pos = remove(cur.pos, move(ctrl ? Movement::LEFT_WORD : Movement::LEFT, cur.pos));
break;
case InputKey::DELETE:
if(cur.selectionBeginPos != cur.selectionEndPos)
cur.selectionEndPos = cur.selectionBeginPos = cur.pos = remove(cur.selectionBeginPos, cur.selectionEndPos);
else
cur.selectionEndPos = cur.selectionBeginPos = cur.pos = remove(cur.pos, move(Movement::RIGHT, cur.pos));
cur.selectionEndPos = cur.selectionBeginPos = cur.pos = remove(cur.pos, move(ctrl ? Movement::RIGHT_WORD : Movement::RIGHT, cur.pos));
break;
case InputKey::ENTER:
process_text_input(cur, "\n");
@ -217,7 +249,13 @@ RichTextBox::TextPosition RichTextBox::get_text_pos_closest_to_point(Vector2f po
}
RichTextBox::RichTextData RichTextBox::get_rich_text_data() {
return {get_string()};
RichTextData toRet;
for(auto& p : paragraphs) {
toRet.paragraphs.emplace_back();
toRet.paragraphs.back().text = p.text;
toRet.paragraphs.back().tStyles = p.tStyles;
}
return toRet;
}
void RichTextBox::clear_text() {
@ -231,7 +269,15 @@ void RichTextBox::set_string(const std::string& str) {
}
void RichTextBox::set_rich_text_data(const RichTextData& richText) {
set_string(richText.text);
paragraphs.clear();
for(auto& p : richText.paragraphs) {
paragraphs.emplace_back();
paragraphs.back().text = p.text;
paragraphs.back().tStyles = p.tStyles;
}
if(richText.paragraphs.empty())
paragraphs.emplace_back();
needsRebuild = true;
}
std::string RichTextBox::get_string() {
@ -261,6 +307,16 @@ std::string RichTextBox::get_text_between(TextPosition p1, TextPosition p2) {
return toRet;
}
void RichTextBox::set_initial_text_style_modifier(const std::shared_ptr<TextStyleModifier>& modifier) {
std::shared_ptr<TextStyleModifier>& modToSet = paragraphs.back().tStyles[0][modifier->get_type()];
needsRebuild |= ((modToSet == nullptr) || (!modifier->equivalent(*modToSet)));
modToSet = modifier;
}
void RichTextBox::set_text_style_modifier_between(TextPosition p1, TextPosition p2, const std::shared_ptr<TextStyleModifier>& modifier) {
needsRebuild = true;
}
RichTextBox::TextPosition RichTextBox::move(Movement movement, TextPosition pos, std::optional<float>* previousX, bool flipDependingOnTextDirection) {
if(pos.fParagraphIndex >= paragraphs.size()) {
pos.fParagraphIndex = paragraphs.size() - 1;
@ -457,18 +513,35 @@ void RichTextBox::set_font_collection(const sk_sp<skia::textlayout::FontCollecti
void RichTextBox::rebuild() {
if(needsRebuild) {
float heightOffset = 0.0f;
skia::textlayout::TextStyle tStyle;
for(ParagraphData& pData : paragraphs) {
pData.pStyle.setTextAlign(skia::textlayout::TextAlign::kLeft);
pData.pStyle.setTextDirection(skia::textlayout::TextDirection::kLtr);
skia::textlayout::TextStyle tStyle;
tStyle.setFontSize(16.0f);
tStyle.setFontFamilies({SkString{"Roboto"}});
pData.pStyle.setTextStyle(tStyle);
skia::textlayout::ParagraphBuilderImpl a(pData.pStyle, fontCollection, SkUnicodes::ICU::Make());
a.addText(pData.text.c_str(), pData.text.length());
size_t startPos = 0;
for(auto& [pos, tStylesAtPos] : pData.tStyles) {
if(pos > pData.text.size())
break;
for(auto& [styleType, styleMod] : tStylesAtPos)
styleMod->modify_text_style(tStyle);
if(startPos != pos) {
a.pushStyle(tStyle);
a.addText(pData.text.c_str() + startPos, pos - startPos);
a.pop();
}
startPos = pos;
}
if(startPos < pData.text.size()) {
a.pushStyle(tStyle);
a.addText(pData.text.c_str() + startPos, pData.text.size() - startPos);
a.pop();
}
pData.p = a.Build();
pData.p->layout(width);
pData.heightOffset = heightOffset;

View file

@ -3,10 +3,16 @@
#include <include/core/SkCanvas.h>
#include <modules/skparagraph/include/FontCollection.h>
#include <modules/skparagraph/include/Paragraph.h>
#include <map>
#include "../SharedTypes.hpp"
#include "TextStyleModifier.hpp"
#include "cereal/archives/portable_binary.hpp"
class RichTextBox {
public:
typedef std::unordered_map<TextStyleModifier::ModifierType, std::shared_ptr<TextStyleModifier>> TextModAtPosContainer;
typedef std::map<size_t, TextModAtPosContainer> TextModContainer;
RichTextBox();
enum class Movement {
@ -48,15 +54,16 @@ class RichTextBox {
};
struct RichTextData {
std::string text;
bool operator==(const RichTextData& o) const = default;
bool operator!=(const RichTextData& o) const = default;
template <typename Archive> void save(Archive& a) const {
a(text);
}
template <typename Archive> void load(Archive& a) {
a(text);
struct Paragraph {
std::string text;
TextModContainer tStyles;
void save(cereal::PortableBinaryOutputArchive& a) const;
void load(cereal::PortableBinaryInputArchive& a);
};
template<typename Archive> void serialize(Archive& a) {
a(paragraphs);
}
std::vector<Paragraph> paragraphs;
};
TextPosition move(Movement movement, TextPosition pos, std::optional<float>* previousX = nullptr, bool flipDependingOnTextDirection = false);
@ -81,6 +88,9 @@ class RichTextBox {
SELECT_ALL
};
void set_initial_text_style_modifier(const std::shared_ptr<TextStyleModifier>& modifier);
void set_text_style_modifier_between(TextPosition p1, TextPosition p2, const std::shared_ptr<TextStyleModifier>& modifier);
RichTextData get_rich_text_data();
void set_rich_text_data(const RichTextData& richText);
void clear_text();
@ -100,6 +110,7 @@ class RichTextBox {
static size_t next_grapheme(const std::string& text, size_t textBytePos);
static size_t prev_grapheme(const std::string& text, size_t textBytePos);
static TextPosition get_text_pos_from_byte_pos(const std::string& text, size_t textBytePos);
size_t get_byte_pos_from_text_pos(TextPosition textPos);
void rects_between_text_positions_func(TextPosition p1, TextPosition p2, std::function<void(const SkRect& r)> f);
@ -112,7 +123,6 @@ class RichTextBox {
bool newlinesAllowed = true;
float width = 0.0f;
float fontSize = 0.0f;
bool needsRebuild = true;
unsigned tabWidth = 8;
sk_sp<skia::textlayout::FontCollection> fontCollection;
@ -122,6 +132,7 @@ class RichTextBox {
std::unique_ptr<skia::textlayout::Paragraph> p;
float heightOffset;
skia::textlayout::ParagraphStyle pStyle;
TextModContainer tStyles;
};
std::vector<ParagraphData> paragraphs;

View file

@ -0,0 +1,155 @@
#include "TextStyleModifier.hpp"
#include <Helpers/ConvertVec.hpp>
#include <Helpers/Serializers.hpp>
#define WEIGHT_VALUE_MODIFIER 100
std::shared_ptr<TextStyleModifier> TextStyleModifier::allocate_modifier(ModifierType type) {
switch(type) {
case ModifierType::SIZE:
return std::make_shared<SizeTextStyleModifier>();
case ModifierType::COLOR:
return std::make_shared<ColorTextStyleModifier>();
case ModifierType::WEIGHT:
return std::make_shared<WeightTextStyleModifier>();
case ModifierType::SLANT:
return std::make_shared<SlantTextStyleModifier>();
case ModifierType::FONT_FAMILIES:
return std::make_shared<FontFamiliesTextStyleModifier>();
}
return nullptr;
}
bool TextStyleModifier::equivalent(TextStyleModifier& modifier) const {
if(modifier.get_type() != get_type())
return false;
return equivalent_data(modifier);
}
TextStyleModifier::~TextStyleModifier() {}
TextStyleModifier::ModifierType WeightTextStyleModifier::get_type() const {
return ModifierType::WEIGHT;
}
void WeightTextStyleModifier::save(cereal::PortableBinaryOutputArchive& a) const {
a(weightValue);
}
void WeightTextStyleModifier::load(cereal::PortableBinaryInputArchive& a) {
a(weightValue);
}
void WeightTextStyleModifier::set_weight(SkFontStyle::Weight newWeight) {
weightValue = newWeight / WEIGHT_VALUE_MODIFIER;
}
SkFontStyle::Weight WeightTextStyleModifier::get_weight() {
return static_cast<SkFontStyle::Weight>(static_cast<int>(weightValue) * WEIGHT_VALUE_MODIFIER);
}
void WeightTextStyleModifier::modify_text_style(skia::textlayout::TextStyle& style) const {
style.setFontStyle(SkFontStyle(static_cast<int>(weightValue) * WEIGHT_VALUE_MODIFIER, style.getFontStyle().width(), style.getFontStyle().slant()));
}
bool WeightTextStyleModifier::equivalent_data(TextStyleModifier& modifier) const {
return weightValue == static_cast<WeightTextStyleModifier&>(modifier).weightValue;
}
TextStyleModifier::ModifierType SlantTextStyleModifier::get_type() const {
return ModifierType::SLANT;
}
void SlantTextStyleModifier::save(cereal::PortableBinaryOutputArchive& a) const {
a(slantValue);
}
void SlantTextStyleModifier::load(cereal::PortableBinaryInputArchive& a) {
a(slantValue);
}
void SlantTextStyleModifier::set_slant(SkFontStyle::Slant newSlant) {
slantValue = newSlant;
}
SkFontStyle::Slant SlantTextStyleModifier::get_slant() {
return static_cast<SkFontStyle::Slant>(slantValue);
}
void SlantTextStyleModifier::modify_text_style(skia::textlayout::TextStyle& style) const {
style.setFontStyle(SkFontStyle(style.getFontStyle().weight(), style.getFontStyle().width(), static_cast<SkFontStyle::Slant>(slantValue)));
}
bool SlantTextStyleModifier::equivalent_data(TextStyleModifier& modifier) const {
return slantValue == static_cast<SlantTextStyleModifier&>(modifier).slantValue;
}
TextStyleModifier::ModifierType ColorTextStyleModifier::get_type() const {
return ModifierType::COLOR;
}
void ColorTextStyleModifier::save(cereal::PortableBinaryOutputArchive& a) const {
a(color);
}
void ColorTextStyleModifier::load(cereal::PortableBinaryInputArchive& a) {
a(color);
}
void ColorTextStyleModifier::modify_text_style(skia::textlayout::TextStyle& style) const {
style.setForegroundPaint(SkPaint{convert_vec4<SkColor4f>(color)});
}
bool ColorTextStyleModifier::equivalent_data(TextStyleModifier& modifier) const {
return color == static_cast<ColorTextStyleModifier&>(modifier).color;
}
TextStyleModifier::ModifierType SizeTextStyleModifier::get_type() const {
return ModifierType::SIZE;
}
void SizeTextStyleModifier::save(cereal::PortableBinaryOutputArchive& a) const {
a(size);
}
void SizeTextStyleModifier::load(cereal::PortableBinaryInputArchive& a) {
a(size);
}
void SizeTextStyleModifier::modify_text_style(skia::textlayout::TextStyle& style) const {
style.setFontSize(size);
}
bool SizeTextStyleModifier::equivalent_data(TextStyleModifier& modifier) const {
return size == static_cast<SizeTextStyleModifier&>(modifier).size;
}
TextStyleModifier::ModifierType FontFamiliesTextStyleModifier::get_type() const {
return ModifierType::FONT_FAMILIES;
}
void FontFamiliesTextStyleModifier::save(cereal::PortableBinaryOutputArchive& a) const {
a(families);
}
void FontFamiliesTextStyleModifier::load(cereal::PortableBinaryInputArchive& a) {
a(families);
}
void FontFamiliesTextStyleModifier::modify_text_style(skia::textlayout::TextStyle& style) const {
style.setFontFamilies(families);
}
bool FontFamiliesTextStyleModifier::equivalent_data(TextStyleModifier& modifier) const {
return families == static_cast<FontFamiliesTextStyleModifier&>(modifier).families;
}

View file

@ -0,0 +1,85 @@
#pragma once
#include <cstdint>
#include <modules/skparagraph/include/Paragraph.h>
#include <modules/skparagraph/include/TextStyle.h>
#include "cereal/archives/portable_binary.hpp"
#include "../SharedTypes.hpp"
class TextStyleModifier {
public:
enum class ModifierType : uint16_t {
SIZE = 0,
COLOR,
WEIGHT,
SLANT,
FONT_FAMILIES
};
static std::shared_ptr<TextStyleModifier> allocate_modifier(ModifierType type);
virtual ModifierType get_type() const = 0;
virtual void save(cereal::PortableBinaryOutputArchive& a) const = 0;
virtual void load(cereal::PortableBinaryInputArchive& a) = 0;
virtual void modify_text_style(skia::textlayout::TextStyle& style) const = 0;
bool equivalent(TextStyleModifier& modifier) const;
virtual ~TextStyleModifier();
private:
virtual bool equivalent_data(TextStyleModifier& modifier) const = 0;
};
class WeightTextStyleModifier : public TextStyleModifier {
public:
virtual ModifierType get_type() const override;
virtual void save(cereal::PortableBinaryOutputArchive& a) const override;
virtual void load(cereal::PortableBinaryInputArchive& a) override;
virtual void modify_text_style(skia::textlayout::TextStyle& style) const override;
void set_weight(SkFontStyle::Weight newWeight);
SkFontStyle::Weight get_weight();
private:
virtual bool equivalent_data(TextStyleModifier& modifier) const override;
uint8_t weightValue;
};
class SlantTextStyleModifier : public TextStyleModifier {
public:
virtual ModifierType get_type() const override;
virtual void save(cereal::PortableBinaryOutputArchive& a) const override;
virtual void load(cereal::PortableBinaryInputArchive& a) override;
virtual void modify_text_style(skia::textlayout::TextStyle& style) const override;
void set_slant(SkFontStyle::Slant newSlant);
SkFontStyle::Slant get_slant();
private:
virtual bool equivalent_data(TextStyleModifier& modifier) const override;
uint8_t slantValue;
};
class ColorTextStyleModifier : public TextStyleModifier {
public:
virtual ModifierType get_type() const override;
virtual void save(cereal::PortableBinaryOutputArchive& a) const override;
virtual void load(cereal::PortableBinaryInputArchive& a) override;
virtual void modify_text_style(skia::textlayout::TextStyle& style) const override;
Vector4f color;
private:
virtual bool equivalent_data(TextStyleModifier& modifier) const override;
};
class SizeTextStyleModifier : public TextStyleModifier {
public:
virtual ModifierType get_type() const override;
virtual void save(cereal::PortableBinaryOutputArchive& a) const override;
virtual void load(cereal::PortableBinaryInputArchive& a) override;
virtual void modify_text_style(skia::textlayout::TextStyle& style) const override;
float size;
private:
virtual bool equivalent_data(TextStyleModifier& modifier) const override;
};
class FontFamiliesTextStyleModifier : public TextStyleModifier {
public:
virtual ModifierType get_type() const override;
virtual void save(cereal::PortableBinaryOutputArchive& a) const override;
virtual void load(cereal::PortableBinaryInputArchive& a) override;
virtual void modify_text_style(skia::textlayout::TextStyle& style) const override;
std::vector<SkString> families;
private:
virtual bool equivalent_data(TextStyleModifier& modifier) const override;
};