From b3f4e37b69eb0ef907452624568f3bfc1f60d52d Mon Sep 17 00:00:00 2001 From: David Capello Date: Fri, 11 Apr 2025 17:15:47 -0300 Subject: [PATCH] Add option to switch font hinting (fix #4931, aseprite/laf#138) --- data/strings/en.ini | 5 ++ data/widgets/font_style.xml | 10 +++ src/app/fonts/font_data.cpp | 30 +++----- src/app/fonts/font_data.h | 18 ++++- src/app/fonts/font_info.cpp | 39 +++++++++- src/app/fonts/font_info.h | 12 ++- src/app/fonts/fonts.cpp | 4 +- src/app/ui/font_entry.cpp | 144 ++++++++++++++++++++++-------------- src/app/ui/font_entry.h | 10 +-- src/app/ui/font_popup.cpp | 3 +- 10 files changed, 186 insertions(+), 89 deletions(-) create mode 100644 data/widgets/font_style.xml diff --git a/data/strings/en.ini b/data/strings/en.ini index acb8657f1..5636bc57a 100644 --- a/data/strings/en.ini +++ b/data/strings/en.ini @@ -785,6 +785,11 @@ load = Load External Font select_truetype_fonts = Select a Font File empty_fonts = No system fonts were found +[font_style] +antialias = Antialias +hinting = Hinting +ligatures = Ligatures + [frame_combo] all_frames = All frames selected_frames = Selected frames diff --git a/data/widgets/font_style.xml b/data/widgets/font_style.xml new file mode 100644 index 000000000..92284caed --- /dev/null +++ b/data/widgets/font_style.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/app/fonts/font_data.cpp b/src/app/fonts/font_data.cpp index 02d6e2851..9cad37bb0 100644 --- a/src/app/fonts/font_data.cpp +++ b/src/app/fonts/font_data.cpp @@ -32,17 +32,11 @@ text::FontRef FontData::getFont(text::FontMgrRef& fontMgr, float size) { ASSERT(fontMgr); - // Use cache - if (m_antialias) { - auto it = m_antialiasFonts.find(size); - if (it != m_antialiasFonts.end()) - return it->second; - } - else { - auto it = m_fonts.find(size); - if (it != m_fonts.end()) - return it->second; - } + // Use cached fonts + const Cache::Key cacheKey{ size, m_antialias, m_hinting != text::FontHinting::None }; + auto it = m_cache.fonts.find(cacheKey); + if (it != m_cache.fonts.end()) + return it->second; // Cache hit text::FontRef font = nullptr; @@ -55,12 +49,18 @@ text::FontRef FontData::getFont(text::FontMgrRef& fontMgr, float size) break; case text::FontType::FreeType: { font = fontMgr->loadTrueTypeFont(m_filename.c_str(), size); - if (font) + if (font) { font->setAntialias(m_antialias); + font->setHinting(m_hinting); + } break; } } + // Cache this font + m_cache.fonts[cacheKey] = font; + + // Load fallback if (m_fallback) { text::FontRef fallback = m_fallback->getFont(fontMgr, m_fallbackSize); if (font) @@ -68,12 +68,6 @@ text::FontRef FontData::getFont(text::FontMgrRef& fontMgr, float size) else return fallback; // Don't double-cache the fallback font } - - // Cache this font - if (m_antialias) - m_antialiasFonts[size] = font; - else - m_fonts[size] = font; return font; } diff --git a/src/app/fonts/font_data.h b/src/app/fonts/font_data.h index 7e0e6d524..3e75d8ee5 100644 --- a/src/app/fonts/font_data.h +++ b/src/app/fonts/font_data.h @@ -39,11 +39,25 @@ public: const std::string& filename() const { return m_filename; } private: + // Cache of loaded fonts so we avoid re-loading them. + struct Cache { + struct Key { + float size; + bool antialias : 1; + bool hinting : 1; + bool operator<(const Key& b) const + { + return size < b.size || antialias < b.antialias || hinting < b.hinting; + } + }; + std::map fonts; + }; + text::FontType m_type; std::string m_filename; bool m_antialias; - std::map m_fonts; // key=font size, value=real font - std::map m_antialiasFonts; + text::FontHinting m_hinting = text::FontHinting::Normal; + Cache m_cache; FontData* m_fallback; float m_fallbackSize; float m_descent = 0.0f; diff --git a/src/app/fonts/font_info.cpp b/src/app/fonts/font_info.cpp index 6355e9bd1..41b1896d0 100644 --- a/src/app/fonts/font_info.cpp +++ b/src/app/fonts/font_info.cpp @@ -27,24 +27,28 @@ FontInfo::FontInfo(Type type, const std::string& name, const float size, const text::FontStyle style, - const Flags flags) + const Flags flags, + const text::FontHinting hinting) : m_type(type) , m_name(name) , m_size(size) , m_style(style) , m_flags(flags) + , m_hinting(hinting) { } FontInfo::FontInfo(const FontInfo& other, const float size, const text::FontStyle style, - const Flags flags) + const Flags flags, + text::FontHinting hinting) : m_type(other.type()) , m_name(other.name()) , m_size(size) , m_style(style) , m_flags(flags) + , m_hinting(hinting) { } @@ -130,6 +134,12 @@ std::string FontInfo::humanString() const result += " Antialias"; if (ligatures()) result += " Ligatures"; + switch (hinting()) { + case text::FontHinting::None: result += " No Hinting"; break; + case text::FontHinting::Slight: result += " Slight Hinting"; break; + case text::FontHinting::Normal: break; + case text::FontHinting::Full: result += " Full Hinting"; break; + } } return result; } @@ -150,6 +160,7 @@ app::FontInfo convert_to(const std::string& from) bool bold = false; bool italic = false; app::FontInfo::Flags flags = app::FontInfo::Flags::None; + text::FontHinting hinting = text::FontHinting::Normal; if (!parts.empty()) { if (parts[0].compare(0, 5, "file=") == 0) { @@ -176,6 +187,17 @@ app::FontInfo convert_to(const std::string& from) else if (parts[i].compare(0, 5, "size=") == 0) { size = std::strtof(parts[i].substr(5).c_str(), nullptr); } + else if (parts[i].compare(0, 8, "hinting=") == 0) { + std::string hintingStr = parts[i].substr(8); + if (hintingStr == "none") + hinting = text::FontHinting::None; + else if (hintingStr == "slight") + hinting = text::FontHinting::Slight; + else if (hintingStr == "normal") + hinting = text::FontHinting::Normal; + else if (hintingStr == "full") + hinting = text::FontHinting::Full; + } } } @@ -187,7 +209,7 @@ app::FontInfo convert_to(const std::string& from) else if (italic) style = text::FontStyle::Italic(); - return app::FontInfo(type, name, size, style, flags); + return app::FontInfo(type, name, size, style, flags, hinting); } template<> @@ -213,6 +235,17 @@ std::string convert_to(const app::FontInfo& from) result += ",antialias"; if (from.ligatures()) result += ",ligatures"; + if (from.hinting() != text::FontHinting::Normal) { + result += ",hinting="; + switch (from.hinting()) { + case text::FontHinting::None: result += "none"; break; + case text::FontHinting::Slight: result += "slight"; break; + case text::FontHinting::Normal: + // Filtered out by above if + break; + case text::FontHinting::Full: result += "full"; break; + } + } } return result; } diff --git a/src/app/fonts/font_info.h b/src/app/fonts/font_info.h index ed7ad0a10..d89463fe6 100644 --- a/src/app/fonts/font_info.h +++ b/src/app/fonts/font_info.h @@ -10,6 +10,7 @@ #include "base/convert_to.h" #include "base/enum_flags.h" +#include "text/font_hinting.h" #include "text/font_style.h" #include "text/font_type.h" #include "text/fwd.h" @@ -49,9 +50,14 @@ public: const std::string& name = {}, float size = kDefaultSize, text::FontStyle style = text::FontStyle(), - Flags flags = Flags::None); + Flags flags = Flags::None, + text::FontHinting hinting = text::FontHinting::Normal); - FontInfo(const FontInfo& other, float size, text::FontStyle style, Flags flags); + FontInfo(const FontInfo& other, + float size, + text::FontStyle style, + Flags flags, + text::FontHinting hinting); bool isValid() const { return m_type != Type::Unknown; } bool useDefaultSize() const { return m_size == kDefaultSize; } @@ -72,6 +78,7 @@ public: Flags flags() const { return m_flags; } bool antialias() const; bool ligatures() const; + text::FontHinting hinting() const { return m_hinting; } text::TypefaceRef findTypeface(const text::FontMgrRef& fontMgr) const; @@ -92,6 +99,7 @@ private: float m_size = kDefaultSize; text::FontStyle m_style; Flags m_flags = Flags::None; + text::FontHinting m_hinting = text::FontHinting::Normal; }; LAF_ENUM_FLAGS(FontInfo::Flags); diff --git a/src/app/fonts/fonts.cpp b/src/app/fonts/fonts.cpp index ecab39dba..b8b188a23 100644 --- a/src/app/fonts/fonts.cpp +++ b/src/app/fonts/fonts.cpp @@ -82,8 +82,10 @@ text::FontRef Fonts::fontFromInfo(const FontInfo& fontInfo) } } - if (font) + if (font) { font->setAntialias(fontInfo.antialias()); + font->setHinting(fontInfo.hinting()); + } return font; } diff --git a/src/app/ui/font_entry.cpp b/src/app/ui/font_entry.cpp index f3894c326..90a2ede03 100644 --- a/src/app/ui/font_entry.cpp +++ b/src/app/ui/font_entry.cpp @@ -19,10 +19,13 @@ #include "base/scoped_value.h" #include "fmt/format.h" #include "ui/display.h" +#include "ui/fit_bounds.h" #include "ui/manager.h" #include "ui/message.h" #include "ui/scale.h" +#include "font_style.xml.h" + #include #include #include @@ -248,31 +251,22 @@ void FontEntry::FontSize::onEntryChange() Change(); } -FontEntry::FontStyle::FontStyle() : ButtonSet(2, true) +FontEntry::FontStyle::FontStyle() : ButtonSet(3, true) { addItem("B"); addItem("I"); + addItem("..."); setMultiMode(MultiMode::Set); } -FontEntry::FontLigatures::FontLigatures() : ButtonSet(1, true) -{ - addItem("fi"); - setMultiMode(MultiMode::Set); -} - -FontEntry::FontEntry() : m_antialias("Antialias") +FontEntry::FontEntry() { m_face.setExpansive(true); m_size.setExpansive(false); m_style.setExpansive(false); - m_ligatures.setExpansive(false); - m_antialias.setExpansive(false); addChild(&m_face); addChild(&m_size); addChild(&m_style); - addChild(&m_ligatures); - addChild(&m_antialias); m_face.setMinSize(gfx::Size(128 * guiscale(), 0)); @@ -280,52 +274,20 @@ FontEntry::FontEntry() : m_antialias("Antialias") if (newTypeName.size() > 0) setInfo(newTypeName, from); else { - setInfo(FontInfo(newTypeName, m_info.size(), m_info.style(), m_info.flags()), from); + setInfo( + FontInfo(newTypeName, m_info.size(), m_info.style(), m_info.flags(), m_info.hinting()), + from); } invalidate(); }); m_size.Change.connect([this]() { const float newSize = std::strtof(m_size.getValue().c_str(), nullptr); - setInfo(FontInfo(m_info, newSize, m_info.style(), m_info.flags()), From::Size); + setInfo(FontInfo(m_info, newSize, m_info.style(), m_info.flags(), m_info.hinting()), + From::Size); }); - m_style.ItemChange.connect([this](ButtonSet::Item* item) { - text::FontStyle style = m_info.style(); - switch (m_style.getItemIndex(item)) { - // Bold button changed - case 0: { - const bool bold = m_style.getItem(0)->isSelected(); - style = text::FontStyle( - bold ? text::FontStyle::Weight::Bold : text::FontStyle::Weight::Normal, - style.width(), - style.slant()); - break; - } - // Italic button changed - case 1: { - const bool italic = m_style.getItem(1)->isSelected(); - style = text::FontStyle( - style.weight(), - style.width(), - italic ? text::FontStyle::Slant::Italic : text::FontStyle::Slant::Upright); - break; - } - } - - setInfo(FontInfo(m_info, m_info.size(), style, m_info.flags()), From::Style); - }); - - auto flagsChange = [this]() { - FontInfo::Flags flags = FontInfo::Flags::None; - if (m_antialias.isSelected()) - flags |= FontInfo::Flags::Antialias; - if (m_ligatures.getItem(0)->isSelected()) - flags |= FontInfo::Flags::Ligatures; - setInfo(FontInfo(m_info, m_info.size(), m_info.style(), flags), From::Flags); - }; - m_ligatures.ItemChange.connect(flagsChange); - m_antialias.Click.connect(flagsChange); + m_style.ItemChange.connect(&FontEntry::onStyleItemClick, this); } // Defined here as FontPopup type is not fully defined in the header @@ -351,12 +313,84 @@ void FontEntry::setInfo(const FontInfo& info, const From fromField) m_style.getItem(1)->setSelected(info.style().slant() != text::FontStyle::Slant::Upright); } - if (fromField != From::Flags) { - m_ligatures.getItem(0)->setSelected(info.ligatures()); - m_antialias.setSelected(info.antialias()); - } - FontChange(m_info, fromField); } +void FontEntry::onStyleItemClick(ButtonSet::Item* item) +{ + text::FontStyle style = m_info.style(); + + switch (m_style.getItemIndex(item)) { + // Bold button changed + case 0: { + const bool bold = m_style.getItem(0)->isSelected(); + style = text::FontStyle( + bold ? text::FontStyle::Weight::Bold : text::FontStyle::Weight::Normal, + style.width(), + style.slant()); + + setInfo(FontInfo(m_info, m_info.size(), style, m_info.flags(), m_info.hinting()), + From::Style); + break; + } + // Italic button changed + case 1: { + const bool italic = m_style.getItem(1)->isSelected(); + style = text::FontStyle( + style.weight(), + style.width(), + italic ? text::FontStyle::Slant::Italic : text::FontStyle::Slant::Upright); + + setInfo(FontInfo(m_info, m_info.size(), style, m_info.flags(), m_info.hinting()), + From::Style); + break; + } + case 2: { + item->setSelected(false); // Unselect the "..." button + + ui::PopupWindow popup; + app::gen::FontStyle content; + + content.antialias()->setSelected(m_info.antialias()); + content.ligatures()->setSelected(m_info.ligatures()); + content.hinting()->setSelected(m_info.hinting() == text::FontHinting::Normal); + + auto flagsChange = [this, &content]() { + FontInfo::Flags flags = FontInfo::Flags::None; + if (content.antialias()->isSelected()) + flags |= FontInfo::Flags::Antialias; + if (content.ligatures()->isSelected()) + flags |= FontInfo::Flags::Ligatures; + setInfo(FontInfo(m_info, m_info.size(), m_info.style(), flags, m_info.hinting()), + From::Flags); + }; + + auto hintingChange = [this, &content]() { + auto hinting = (content.hinting()->isSelected() ? text::FontHinting::Normal : + text::FontHinting::None); + + setInfo(FontInfo(m_info, m_info.size(), m_info.style(), m_info.flags(), hinting), + From::Hinting); + }; + + content.antialias()->Click.connect(flagsChange); + content.ligatures()->Click.connect(flagsChange); + content.hinting()->Click.connect(hintingChange); + + popup.addChild(&content); + popup.remapWindow(); + + gfx::Rect rc = item->bounds(); + rc.y += rc.h - popup.border().bottom(); + + ui::fit_bounds(display(), &popup, gfx::Rect(rc.origin(), popup.sizeHint())); + + popup.Open.connect([&popup] { popup.setHotRegion(gfx::Region(popup.boundsOnScreen())); }); + + popup.openWindowInForeground(); + break; + } + } +} + } // namespace app diff --git a/src/app/ui/font_entry.h b/src/app/ui/font_entry.h index 5481338ae..570accdec 100644 --- a/src/app/ui/font_entry.h +++ b/src/app/ui/font_entry.h @@ -28,6 +28,7 @@ public: Size, Style, Flags, + Hinting, Popup, }; @@ -40,6 +41,8 @@ public: obs::signal FontChange; private: + void onStyleItemClick(ButtonSet::Item* item); + class FontFace : public SearchEntry { public: FontFace(); @@ -73,17 +76,10 @@ private: FontStyle(); }; - class FontLigatures : public ButtonSet { - public: - FontLigatures(); - }; - FontInfo m_info; FontFace m_face; FontSize m_size; FontStyle m_style; - FontLigatures m_ligatures; - ui::CheckBox m_antialias; bool m_lockFace = false; }; diff --git a/src/app/ui/font_popup.cpp b/src/app/ui/font_popup.cpp index ba22ddc52..7cfcf2cfb 100644 --- a/src/app/ui/font_popup.cpp +++ b/src/app/ui/font_popup.cpp @@ -181,7 +181,8 @@ private: const FontInfo fontInfoDefSize(m_fontInfo, FontInfo::kDefaultSize, text::FontStyle(), - FontInfo::Flags::Antialias); + FontInfo::Flags::Antialias, + text::FontHinting::Normal); const text::FontRef font = fonts->fontFromInfo(fontInfoDefSize); if (!font) return;