Centralize defined fonts in new app::Fonts class

This commit is contained in:
David Capello 2025-03-04 13:27:39 -03:00
parent 2706d2d75a
commit c949f4a5a6
14 changed files with 224 additions and 119 deletions

View File

@ -547,6 +547,7 @@ target_sources(app-lib PRIVATE
fonts/font_data.cpp
fonts/font_info.cpp
fonts/font_path.cpp
fonts/fonts.cpp
gui_xml.cpp
i18n/strings.cpp
i18n/xml_translator.cpp

View File

@ -19,6 +19,7 @@
#include "app/file/file.h"
#include "app/file_selector.h"
#include "app/fonts/font_data.h"
#include "app/fonts/fonts.h"
#include "app/i18n/strings.h"
#include "app/ini_file.h"
#include "app/launcher.h"
@ -1103,7 +1104,7 @@ private:
FontInfo fi;
auto* theme = skin::SkinTheme::get(this);
text::FontMgrRef fontMgr = theme->fontMgr();
for (auto kv : theme->getWellKnownFonts()) {
for (const auto& kv : Fonts::instance()->definedFonts()) {
if (kv.second->getFont(fontMgr, themeFont->height(), guiscale()) == themeFont) {
fi = FontInfo(FontInfo::Type::Name,
kv.first,
@ -1118,8 +1119,8 @@ private:
void updateFontPreviews()
{
m_font = get_font_from_info(themeFont()->info());
m_miniFont = get_font_from_info(themeMiniFont()->info());
m_font = Fonts::instance()->fontFromInfo(themeFont()->info());
m_miniFont = Fonts::instance()->fontFromInfo(themeMiniFont()->info());
if (!m_miniFont)
m_miniFont = skin::SkinTheme::get(this)->getMiniFont();

View File

@ -17,7 +17,8 @@
namespace app {
// TODO should we merge this with FontInfo?
// Represents a defined font in a <font> element from "data/fonts/fonts.xml" file
// and theme fonts (<font> elements from "data/extensions/aseprite-theme/theme.xml").
class FontData {
public:
FontData(text::FontType type);

View File

@ -19,7 +19,14 @@
namespace app {
// TODO should we merge this with skin::FontData?
// Represents a font reference from any place:
// - Name: a font referenced by name, a font that came from fonts.xml files (Fonts/FontData)
// - File: an external font loaded from a .ttf file
// - System: native laf-os fonts (i.e. Skia fonts loaded from the operating system)
//
// This font reference can be serialize to a string to be saved in the
// aseprite.ini configuration (e.g. latest font used in text tool, or
// custom theme fonts, etc.).
class FontInfo {
public:
enum class Type {

91
src/app/fonts/fonts.cpp Normal file
View File

@ -0,0 +1,91 @@
// Aseprite
// Copyright (C) 2025 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/fonts/fonts.h"
#include "app/fonts/font_data.h"
#include "app/fonts/font_info.h"
#include "text/font_mgr.h"
namespace app {
static Fonts* g_instance = nullptr;
// static
Fonts* Fonts::instance()
{
ASSERT(g_instance);
return g_instance;
}
Fonts::Fonts(const text::FontMgrRef& fontMgr) : m_fontMgr(fontMgr)
{
ASSERT(!g_instance);
g_instance = this;
}
Fonts::~Fonts()
{
ASSERT(g_instance == this);
g_instance = nullptr;
}
void Fonts::addFontData(const std::string& name, std::unique_ptr<FontData>&& fontData)
{
m_fonts[name] = std::move(fontData);
}
FontData* Fonts::fontDataByName(const std::string& name)
{
auto it = m_fonts.find(name);
if (it == m_fonts.end())
return nullptr;
return it->second.get();
}
text::FontRef Fonts::fontByName(const std::string& name, int size)
{
auto it = m_fonts.find(name);
if (it == m_fonts.end())
return nullptr;
return it->second->getFont(m_fontMgr, size);
}
text::FontRef Fonts::fontFromInfo(const FontInfo& fontInfo)
{
ASSERT(m_fontMgr);
if (!m_fontMgr)
return nullptr;
text::FontRef font;
if (fontInfo.type() == FontInfo::Type::System) {
// Just in case the typeface is not present in the FontInfo
auto typeface = fontInfo.findTypeface(m_fontMgr);
font = m_fontMgr->makeFont(typeface);
if (!fontInfo.useDefaultSize())
font->setSize(fontInfo.size());
}
else {
const int size = (fontInfo.useDefaultSize() ? 18 : fontInfo.size());
font = fontByName(fontInfo.name(), size);
if (!font && fontInfo.type() == FontInfo::Type::File) {
font = m_fontMgr->loadTrueTypeFont(fontInfo.name().c_str(), size);
}
}
if (font)
font->setAntialias(fontInfo.antialias());
return font;
}
} // namespace app

49
src/app/fonts/fonts.h Normal file
View File

@ -0,0 +1,49 @@
// Aseprite
// Copyright (C) 2025 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_FONTS_FONTS_H_INCLUDED
#define APP_FONTS_FONTS_H_INCLUDED
#pragma once
#include "text/fwd.h"
#include <map>
#include <memory>
#include <string>
namespace app {
class FontData;
using FontDataMap = std::map<std::string, std::unique_ptr<FontData>>;
class FontInfo;
// Available defined fonts in "data/fonts/fonts.xml" file and theme
// fonts (<font> elements from "data/extensions/aseprite-theme/theme.xml").
class Fonts {
public:
static Fonts* instance();
Fonts(const text::FontMgrRef& fontMgr);
~Fonts();
const text::FontMgrRef& fontMgr() const { return m_fontMgr; }
const FontDataMap& definedFonts() const { return m_fonts; }
bool isEmpty() const { return m_fonts.empty(); }
void addFontData(const std::string& name, std::unique_ptr<FontData>&& fontData);
FontData* fontDataByName(const std::string& name);
text::FontRef fontByName(const std::string& name, int size);
text::FontRef fontFromInfo(const FontInfo& fontInfo);
private:
text::FontMgrRef m_fontMgr;
FontDataMap m_fonts;
};
} // namespace app
#endif

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2018-2025 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of

View File

@ -12,6 +12,7 @@
#include "app/app.h"
#include "app/fonts/font_info.h"
#include "app/fonts/fonts.h"
#include "app/ui/context_bar.h"
#include "app/ui/editor/editor.h"
#include "app/ui/editor/writing_text_state.h"
@ -62,7 +63,7 @@ void SelectTextBoxState::onQuickboxEnd(Editor* editor, const gfx::Rect& rect0, u
gfx::Rect rect = rect0;
if (rect.w <= 3 || rect.h <= 3) {
FontInfo fontInfo = App::instance()->contextBar()->fontInfo();
if (auto font = get_font_from_info(fontInfo)) {
if (auto font = Fonts::instance()->fontFromInfo(fontInfo)) {
rect.w = std::min(4 * font->height(), editor->sprite()->width());
rect.h = font->height();
}

View File

@ -63,7 +63,7 @@ public:
renderExtraCelBase();
FontInfo fontInfo = App::instance()->contextBar()->fontInfo();
if (auto font = get_font_from_info(fontInfo))
if (auto font = Fonts::instance()->fontFromInfo(fontInfo))
setFont(font);
}
@ -497,7 +497,7 @@ void WritingTextState::onBeforeCommandExecution(CommandExecutionEvent& ev)
void WritingTextState::onFontChange(const FontInfo& fontInfo, FontEntry::From fromField)
{
if (auto font = get_font_from_info(fontInfo)) {
if (auto font = Fonts::instance()->fontFromInfo(fontInfo)) {
m_entry->setFont(font);
m_entry->invalidate();
m_editor->invalidate();

View File

@ -16,6 +16,7 @@
#include "app/fonts/font_data.h"
#include "app/fonts/font_info.h"
#include "app/fonts/font_path.h"
#include "app/fonts/fonts.h"
#include "app/i18n/strings.h"
#include "app/match_words.h"
#include "app/recent_files.h"
@ -242,8 +243,9 @@ FontPopup::FontPopup(const FontInfo& fontInfo)
m_listBox.addChild(m_pinnedSeparator);
// Default fonts
Fonts* fonts = Fonts::instance();
bool first = true;
for (auto kv : skin::SkinTheme::get(this)->getWellKnownFonts()) {
for (const auto& kv : fonts->definedFonts()) {
if (!kv.second->filename().empty()) {
if (first) {
m_listBox.addChild(new SeparatorInView(Strings::font_popup_theme_fonts()));
@ -258,7 +260,7 @@ FontPopup::FontPopup(const FontInfo& fontInfo)
bool empty = true;
// Get system fonts from laf-text module
const text::FontMgrRef fontMgr = theme()->fontMgr();
const text::FontMgrRef fontMgr = fonts->fontMgr();
const int n = fontMgr->countFamilies();
if (n > 0) {
for (int i = 0; i < n; ++i) {

View File

@ -155,16 +155,16 @@ static const char* g_cursor_names[kCursorTypes] = {
"size_nw", // kSizeNWCursor
};
static FontData* load_font(std::map<std::string, FontData*>& fonts,
const XMLElement* xmlFont,
const std::string& xmlFilename)
static FontData* load_font(const XMLElement* xmlFont, const std::string& xmlFilename)
{
const char* fontRef = xmlFont->Attribute("font");
if (fontRef) {
auto it = fonts.find(fontRef);
if (it == fonts.end())
throw base::Exception("Font named '%s' not found\n", fontRef);
return it->second;
Fonts* fonts = Fonts::instance();
ASSERT(fonts);
if (const char* fontId = xmlFont->Attribute("font")) {
if (FontData* fontData = fonts->fontDataByName(fontId))
return fontData;
else
throw base::Exception("Font named '%s' not found\n", fontId);
}
const char* nameStr = xmlFont->Attribute("name");
@ -174,9 +174,8 @@ static FontData* load_font(std::map<std::string, FontData*>& fonts,
std::string name(nameStr);
// Use cached font data
auto it = fonts.find(name);
if (it != fonts.end())
return it->second;
if (FontData* fontData = fonts->fontDataByName(name))
return fontData;
LOG(VERBOSE, "THEME: Loading font '%s'\n", name.c_str());
@ -191,7 +190,7 @@ static FontData* load_font(std::map<std::string, FontData*>& fonts,
if (type == "spritesheet") {
const char* fileStr = xmlFont->Attribute("file");
if (fileStr) {
font.reset(new FontData(text::FontType::SpriteSheet));
font = std::make_unique<FontData>(text::FontType::SpriteSheet);
font->setFilename(base::join_path(xmlDir, fileStr));
}
}
@ -221,7 +220,7 @@ static FontData* load_font(std::map<std::string, FontData*>& fonts,
// The filename can be empty if the font was not found, anyway we
// want to keep the font information (e.g. to use the fallback
// information of this font).
font.reset(new FontData(text::FontType::FreeType));
font = std::make_unique<FontData>(text::FontType::FreeType);
font->setFilename(fontFilename);
font->setAntialias(antialias);
@ -235,23 +234,23 @@ static FontData* load_font(std::map<std::string, FontData*>& fonts,
name.c_str());
}
FontData* result = nullptr;
if (font) {
fonts[name] = result = font.get();
font.release();
if (!font)
return nullptr;
// Fallback font
const XMLElement* xmlFallback = (const XMLElement*)xmlFont->FirstChildElement("fallback");
if (xmlFallback) {
FontData* fallback = load_font(fonts, xmlFallback, xmlFilename);
if (fallback) {
int size = 10;
const char* sizeStr = xmlFont->Attribute("size");
if (sizeStr)
size = std::strtol(sizeStr, nullptr, 10);
FontData* result = font.get();
fonts->addFontData(name, std::move(font)); // "font" variable is invalid from now on
result->setFallback(fallback, size);
}
// Fallback font
const XMLElement* xmlFallback = (const XMLElement*)xmlFont->FirstChildElement("fallback");
if (xmlFallback) {
FontData* fallback = load_font(xmlFallback, xmlFilename);
if (fallback) {
int size = 10;
const char* sizeStr = xmlFont->Attribute("size");
if (sizeStr)
size = std::strtol(sizeStr, nullptr, 10);
result->setFallback(fallback, size);
}
}
return result;
@ -275,7 +274,11 @@ SkinTheme* SkinTheme::get(const ui::Widget* widget)
return static_cast<SkinTheme*>(widget->theme());
}
SkinTheme::SkinTheme() : m_sheet(nullptr), m_preferredScreenScaling(-1), m_preferredUIScaling(-1)
SkinTheme::SkinTheme()
: m_fonts(m_fontMgr)
, m_sheet(nullptr)
, m_preferredScreenScaling(-1)
, m_preferredUIScaling(-1)
{
m_standardCursors.fill(nullptr);
}
@ -294,11 +297,6 @@ SkinTheme::~SkinTheme()
for (auto style : m_styles)
delete style.second;
m_styles.clear();
// Destroy fonts
for (auto& kv : m_fonts)
delete kv.second; // Delete all FontDatas
m_fonts.clear();
}
void SkinTheme::onRegenerateTheme()
@ -348,7 +346,7 @@ void SkinTheme::loadFontData()
XMLElement* xmlFont = handle.FirstChildElement("fonts").FirstChildElement("font").ToElement();
while (xmlFont) {
load_font(m_fonts, xmlFont, rf.filename());
load_font(xmlFont, rf.filename());
xmlFont = xmlFont->NextSiblingElement();
}
}
@ -357,7 +355,7 @@ void SkinTheme::loadAll(const std::string& themeId, BackwardCompatibility* backw
{
LOG("THEME: Loading theme %s\n", themeId.c_str());
if (m_fonts.empty())
if (Fonts::instance()->isEmpty())
loadFontData();
m_path = findThemePath(themeId);
@ -404,6 +402,7 @@ void SkinTheme::loadSheet()
void SkinTheme::loadXml(BackwardCompatibility* backward)
{
Fonts* fonts = Fonts::instance();
const int scale = guiscale();
// Load the skin XML
@ -435,7 +434,7 @@ void SkinTheme::loadXml(BackwardCompatibility* backward)
.ToElement();
while (xmlFont) {
const char* idStr = xmlFont->Attribute("id");
FontData* fontData = load_font(m_fonts, xmlFont, xml_filename);
FontData* fontData = load_font(xmlFont, xml_filename);
if (idStr && fontData) {
std::string id(idStr);
LOG(VERBOSE, "THEME: Loading theme font %s\n", idStr);
@ -478,12 +477,12 @@ void SkinTheme::loadXml(BackwardCompatibility* backward)
Preferences& pref = Preferences::instance();
if (!pref.theme.font().empty()) {
auto fi = base::convert_to<FontInfo>(pref.theme.font());
if (auto f = get_font_from_info(fi, this))
if (auto f = fonts->fontFromInfo(fi))
m_defaultFont = f;
}
if (!pref.theme.miniFont().empty()) {
auto fi = base::convert_to<FontInfo>(pref.theme.miniFont());
if (auto f = get_font_from_info(fi, this))
if (auto f = fonts->fontFromInfo(fi))
m_miniFont = f;
}
@ -1733,14 +1732,6 @@ void SkinTheme::drawEntryCaret(ui::Graphics* g, Entry* widget, int x, int y)
g->drawVLine(color, u, y + textHeight / 2 - caretSize.h / 2, caretSize.h);
}
text::FontRef SkinTheme::getFontByName(const std::string& name, const int size)
{
auto it = m_fonts.find(name);
if (it == m_fonts.end())
return nullptr;
return it->second->getFont(m_fontMgr, size);
}
SkinPartPtr SkinTheme::getToolPart(const char* toolId) const
{
return getPartById(std::string("tool_") + toolId);

View File

@ -9,6 +9,7 @@
#define APP_UI_SKIN_SKIN_THEME_H_INCLUDED
#pragma once
#include "app/fonts/fonts.h"
#include "app/ui/skin/skin_part.h"
#include "gfx/color.h"
#include "gfx/fwd.h"
@ -29,12 +30,7 @@ class Entry;
class Graphics;
} // namespace ui
namespace app {
class FontData;
using FontDataMap = std::map<std::string, FontData*>;
namespace skin {
namespace app { namespace skin {
class ThemeFont {
public:
@ -178,9 +174,6 @@ public:
void drawEntryCaret(ui::Graphics* g, ui::Entry* widget, int x, int y);
const FontDataMap& getWellKnownFonts() const { return m_fonts; }
text::FontRef getFontByName(const std::string& name, int size);
protected:
void onRegenerateTheme() override;
@ -207,6 +200,7 @@ private:
std::string findThemePath(const std::string& themeId) const;
Fonts m_fonts;
std::string m_path;
os::SurfaceRef m_sheet;
// Contains the sheet surface as is, without any scale.
@ -219,7 +213,6 @@ private:
std::map<std::string, ui::Cursor*> m_cursors;
std::array<ui::Cursor*, ui::kCursorTypes> m_standardCursors;
std::map<std::string, ui::Style*> m_styles;
FontDataMap m_fonts;
std::map<std::string, ThemeFont> m_themeFonts;
// Stores the unscaled font version of the Font pointer used as a key.
std::map<text::Font*, text::FontRef> m_unscaledFonts;
@ -229,7 +222,6 @@ private:
int m_preferredUIScaling;
};
} // namespace skin
} // namespace app
}} // namespace app::skin
#endif

View File

@ -12,13 +12,14 @@
#include "app/util/render_text.h"
#include "app/fonts/font_info.h"
#include "app/ui/skin/skin_theme.h"
#include "app/fonts/fonts.h"
#include "base/fs.h"
#include "doc/blend_funcs.h"
#include "doc/blend_internals.h"
#include "doc/color.h"
#include "doc/image.h"
#include "doc/primitives.h"
#include "os/paint.h"
#include "text/draw_text.h"
#include "text/font_metrics.h"
#include "text/text_blob.h"
@ -63,53 +64,18 @@ private:
} // anonymous namespace
text::FontRef get_font_from_info(const FontInfo& fontInfo, skin::SkinTheme* theme)
{
if (!theme) {
theme = skin::SkinTheme::instance();
ASSERT(theme);
if (!theme)
return nullptr;
}
const text::FontMgrRef fontMgr = theme->fontMgr();
if (!fontMgr)
return nullptr;
text::FontRef font;
if (fontInfo.type() == FontInfo::Type::System) {
// Just in case the typeface is not present in the FontInfo
auto typeface = fontInfo.findTypeface(fontMgr);
font = fontMgr->makeFont(typeface);
if (!fontInfo.useDefaultSize())
font->setSize(fontInfo.size());
}
else {
const int size = (fontInfo.useDefaultSize() ? 18 : fontInfo.size());
font = theme->getFontByName(fontInfo.name(), size);
if (!font && fontInfo.type() == FontInfo::Type::File) {
font = fontMgr->loadTrueTypeFont(fontInfo.name().c_str(), size);
}
}
if (font)
font->setAntialias(fontInfo.antialias());
return font;
}
text::TextBlobRef create_text_blob(const FontInfo& fontInfo, const std::string& text)
{
const text::FontRef font = get_font_from_info(fontInfo);
Fonts* fonts = Fonts::instance();
ASSERT(fonts);
if (!fonts)
return nullptr;
const text::FontRef font = fonts->fontFromInfo(fontInfo);
if (!font)
return nullptr;
auto* theme = skin::SkinTheme::instance();
const text::FontMgrRef fontMgr = theme->fontMgr();
return text::TextBlob::MakeWithShaper(fontMgr, font, text);
return text::TextBlob::MakeWithShaper(fonts->fontMgr(), font, text);
}
gfx::Size get_text_blob_required_size(const text::TextBlobRef& blob)
@ -154,12 +120,17 @@ doc::ImageRef render_text_blob(const text::TextBlobRef& blob, gfx::Color color)
doc::ImageRef render_text(const FontInfo& fontInfo, const std::string& text, gfx::Color color)
{
const text::FontRef font = get_font_from_info(fontInfo);
Fonts* fonts = Fonts::instance();
ASSERT(fonts);
if (!fonts)
return nullptr;
const text::FontRef font = fonts->fontFromInfo(fontInfo);
if (!font)
return nullptr;
auto* theme = skin::SkinTheme::instance();
const text::FontMgrRef fontMgr = theme->fontMgr();
const text::FontMgrRef fontMgr = fonts->fontMgr();
ASSERT(fontMgr);
os::Paint paint;
paint.style(os::Paint::StrokeAndFill);

View File

@ -24,8 +24,6 @@ namespace skin {
class SkinTheme;
}
text::FontRef get_font_from_info(const FontInfo& fontInfo, skin::SkinTheme* theme = nullptr);
text::TextBlobRef create_text_blob(const FontInfo& fontInfo, const std::string& text);
// Returns the exact bounds that are required to draw this TextBlob,