Add option to switch font hinting (fix #4931, aseprite/laf#138)

This commit is contained in:
David Capello 2025-04-11 17:15:47 -03:00
parent d8632b6208
commit b3f4e37b69
10 changed files with 186 additions and 89 deletions

View File

@ -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

View File

@ -0,0 +1,10 @@
<!-- Aseprite -->
<!-- Copyright (C) 2025 by Igara Studio S.A. -->
<gui>
<vbox id="font_style">
<check id="antialias" text="@.antialias" />
<check id="hinting" text="@.hinting" />
<separator horizontal="true" />
<check id="ligatures" text="@.ligatures" />
</vbox>
</gui>

View File

@ -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;
}

View File

@ -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<Key, text::FontRef> fonts;
};
text::FontType m_type;
std::string m_filename;
bool m_antialias;
std::map<float, text::FontRef> m_fonts; // key=font size, value=real font
std::map<float, text::FontRef> m_antialiasFonts;
text::FontHinting m_hinting = text::FontHinting::Normal;
Cache m_cache;
FontData* m_fallback;
float m_fallbackSize;
float m_descent = 0.0f;

View File

@ -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;
}

View File

@ -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);

View File

@ -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;
}

View File

@ -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 <algorithm>
#include <cstdlib>
#include <vector>
@ -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

View File

@ -28,6 +28,7 @@ public:
Size,
Style,
Flags,
Hinting,
Popup,
};
@ -40,6 +41,8 @@ public:
obs::signal<void(const FontInfo&, From)> 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;
};

View File

@ -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;