Add way to mix spritesheet+truetype fonts

This commit is contained in:
David Capello 2017-02-24 17:56:57 -03:00
parent 776566463b
commit 867ab891bf
26 changed files with 534 additions and 83 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

26
data/fonts/fonts.xml Normal file
View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<fonts>
<!-- Preset fonts that can be used by all themes -->
<font name="Arial"
type="truetype"
file="Arial.ttf" />
<font name="Arial Unicode"
type="truetype"
file_win="ARIALUNI.TTF"
file_mac="Arial Unicode.ttf" />
<font name="Aseprite"
type="spritesheet"
file="aseprite_font.png">
<fallback font="Arial Unicode" size="8" />
</font>
<font name="Aseprite Mini"
type="spritesheet"
file="aseprite_mini.png">
<fallback font="Arial Unicode" size="6" />
</font>
</fonts>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

View File

@ -3,6 +3,11 @@
author="Ilija Melentijevic &amp; David Capello"
url="http://ilkke.blogspot.com/">
<fonts>
<font id="default" font="Aseprite" />
<font id="mini" font="Aseprite Mini" />
</fonts>
<dimensions>
<dim id="scrollbar_size" value="12" />
<dim id="mini_scrollbar_size" value="6" />

View File

@ -89,6 +89,20 @@ include_directories(${CMAKE_BINARY_DIR}/third_party/cmark)
######################################################################
# app-lib target
# These specific-platform files should be in an external library
# (e.g. "base" or "she").
set(app_platform_files)
if(WIN32)
set(app_platform_files
font_path_win32.cpp)
elseif(APPLE)
set(app_platform_files
font_path_osx.mm)
else()
set(app_platform_files
font_path_linux.cpp)
endif()
set(data_recovery_files)
if(NOT ENABLE_TRIAL_MODE)
set(data_recovery_files
@ -356,6 +370,7 @@ add_library(app-lib
file_system.cpp
filename_formatter.cpp
flatten.cpp
font_path.cpp
gui_xml.cpp
i18n/strings.cpp
ini_file.cpp
@ -455,6 +470,7 @@ add_library(app-lib
ui/search_entry.cpp
ui/select_accelerator.cpp
ui/skin/button_icon_impl.cpp
ui/skin/font_data.cpp
ui/skin/skin_part.cpp
ui/skin/skin_property.cpp
ui/skin/skin_slider_property.cpp
@ -490,6 +506,7 @@ add_library(app-lib
widget_loader.cpp
xml_document.cpp
xml_exception.cpp
${app_platform_files}
${data_recovery_files}
${scripting_files}
${generated_files})

32
src/app/font_path.cpp Normal file
View File

@ -0,0 +1,32 @@
// Aseprite
// Copyright (C) 2017 David Capello
//
// 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/font_path.h"
#include "base/fs.h"
namespace app {
std::string find_font(const std::string& filename)
{
std::vector<std::string> fontDirs;
get_font_dirs(fontDirs);
std::string fn;
for (const std::string& dir : fontDirs) {
fn = base::join_path(dir, filename);
if (base::is_file(fn))
return fn;
}
return std::string();
}
} // namespace app

21
src/app/font_path.h Normal file
View File

@ -0,0 +1,21 @@
// Aseprite
// Copyright (C) 2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_FONT_PATH_H_INCLUDED
#define APP_FONT_PATH_H_INCLUDED
#pragma once
#include <string>
#include <vector>
namespace app {
void get_font_dirs(std::vector<std::string>& fontDirs);
std::string find_font(const std::string& filename);
} // namespace app
#endif

23
src/app/font_path_osx.mm Normal file
View File

@ -0,0 +1,23 @@
// Aseprite
// Copyright (C) 2017 David Capello
//
// 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/font_path.h"
namespace app {
void get_font_dirs(std::vector<std::string>& fontDirs)
{
// TODO use a Cocoa API to get the list of paths
fontDirs.push_back("~/Library/Fonts");
fontDirs.push_back("/Library/Fonts");
fontDirs.push_back("/System/Library/Fonts/");
}
} // namespace app

View File

@ -0,0 +1,49 @@
// Aseprite
// Copyright (C) 2017 David Capello
//
// 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/font_path.h"
#include "base/fs.h"
#include <queue>
namespace app {
std::vector<std::string> g_cache;
void get_font_dirs(std::vector<std::string>& fontDirs)
{
if (!g_cache.empty()) {
fontDirs = g_cache;
return;
}
std::queue<std::string> q;
q.push("~/.fonts");
q.push("/usr/local/share/fonts");
q.push("/usr/share/fonts");
while (!q.empty()) {
std::string fontDir = q.front();
q.pop();
fontDirs.push_back(fontDir);
for (const auto& file : base::list_files(fontDir)) {
std::string fullpath = base::join_path(fontDir, file);
if (base::is_directory(fullpath))
q.push_back(fontDir); // Add subdirectory in the queue
}
}
g_cache = fontDirs;
}
} // namespace app

31
src/app/font_path_win.cpp Normal file
View File

@ -0,0 +1,31 @@
// Aseprite
// Copyright (C) 2017 David Capello
//
// 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/font_path.h"
#include "base/string.h"
#include <cctype>
#include <windows.h>
#include <shlobj.h>
namespace app {
void get_font_dirs(std::vector<std::string>& fontDirs)
{
std::vector<wchar_t> buf(MAX_PATH+1);
HRESULT hr = SHGetFolderPath(
nullptr, CSIDL_FONTS, nullptr,
SHGFP_TYPE_DEFAULT, &buf[0]);
if (hr == S_OK)
fontDirs.push_back(base::to_utf8(&buf[0]));
}
} // namespace app

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2016 David Capello
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -13,6 +13,7 @@
#include "app/commands/cmd_set_palette.h"
#include "app/commands/commands.h"
#include "app/console.h"
#include "app/font_path.h"
#include "app/ui/skin/skin_theme.h"
#include "app/ui_context.h"
#include "app/util/freetype_utils.h"
@ -38,7 +39,6 @@
#include <windows.h>
#endif
#include <queue>
#include <map>
namespace app {
@ -144,43 +144,16 @@ FontPopup::FontPopup()
m_popup->view()->attachToView(&m_listBox);
std::queue<std::string> fontDirs;
#if _WIN32
{
std::vector<wchar_t> buf(MAX_PATH);
HRESULT hr = SHGetFolderPath(NULL, CSIDL_FONTS, NULL,
SHGFP_TYPE_DEFAULT, &buf[0]);
if (hr == S_OK) {
fontDirs.push(base::to_utf8(&buf[0]));
}
}
#elif __APPLE__
{
fontDirs.push("/System/Library/Fonts/");
fontDirs.push("/Library/Fonts");
fontDirs.push("~/Library/Fonts");
}
#else // Unix-like
{
fontDirs.push("/usr/share/fonts");
fontDirs.push("/usr/local/share/fonts");
fontDirs.push("~/.fonts");
}
#endif
std::vector<std::string> fontDirs;
get_font_dirs(fontDirs);
// Create a list of fullpaths to every font found in all font
// directories (fontDirs)
std::vector<std::string> files;
while (!fontDirs.empty()) {
std::string fontDir = fontDirs.front();
fontDirs.pop();
auto fontDirFiles = base::list_files(fontDir);
for (const auto& file : fontDirFiles) {
for (const auto& fontDir : fontDirs) {
for (const auto& file : base::list_files(fontDir)) {
std::string fullpath = base::join_path(fontDir, file);
if (base::is_directory(fullpath))
fontDirs.push(fullpath); // Add subdirectory
else
if (base::is_file(fullpath))
files.push_back(fullpath);
}
}

View File

@ -0,0 +1,63 @@
// Aseprite
// Copyright (C) 2001-2017 David Capello
//
// 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/ui/skin/font_data.h"
#include "she/font.h"
#include "she/system.h"
namespace app {
namespace skin {
FontData::FontData(she::FontType type)
: m_type(type)
, m_antialias(false)
, m_fallback(nullptr)
, m_fallbackSize(0)
{
}
FontData::~FontData()
{
// Destroy all fonts
for (auto& it : m_fonts)
it.second->dispose();
}
she::Font* FontData::getFont(int size)
{
if (m_type == she::FontType::kSpriteSheet)
size = 0; // Same size always
auto it = m_fonts.find(size);
if (it != m_fonts.end())
return it->second;
she::Font* font = nullptr;
switch (m_type) {
case she::FontType::kSpriteSheet:
font = she::instance()->loadSpriteSheetFont(m_filename.c_str());
break;
case she::FontType::kTrueType:
font = she::instance()->loadTrueTypeFont(m_filename.c_str(), size);
if (font)
font->setAntialias(m_antialias);
break;
}
if (font && m_fallback)
font->setFallback(m_fallback->getFont(m_fallbackSize));
return m_fonts[size] = font;
}
} // namespace skin
} // namespace app

View File

@ -0,0 +1,47 @@
// Aseprite
// Copyright (C) 2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_UI_SKIN_FONT_DATA_H_INCLUDED
#define APP_UI_SKIN_FONT_DATA_H_INCLUDED
#pragma once
#include "base/disable_copying.h"
#include "she/font.h"
#include <map>
namespace app {
namespace skin {
class FontData {
public:
FontData(she::FontType type);
~FontData();
void setFilename(const std::string& filename) { m_filename = filename; }
void setAntialias(bool antialias) { m_antialias = antialias; }
void setFallback(FontData* fallback, int fallbackSize) {
m_fallback = fallback;
m_fallbackSize = fallbackSize;
}
she::Font* getFont(int size);
private:
she::FontType m_type;
std::string m_filename;
bool m_antialias;
std::map<int, she::Font*> m_fonts; // key=font size, value=real font
FontData* m_fallback;
int m_fallbackSize;
DISABLE_COPYING(FontData);
};
} // namespace skin
} // namespace app
#endif

View File

@ -9,12 +9,14 @@
#endif
#include "app/console.h"
#include "app/font_path.h"
#include "app/modules/gui.h"
#include "app/pref/preferences.h"
#include "app/resource_finder.h"
#include "app/ui/app_menuitem.h"
#include "app/ui/keyboard_shortcuts.h"
#include "app/ui/skin/button_icon_impl.h"
#include "app/ui/skin/font_data.h"
#include "app/ui/skin/skin_property.h"
#include "app/ui/skin/skin_slider_property.h"
#include "app/ui/skin/skin_style_property.h"
@ -94,6 +96,104 @@ static css::Value value_or_none(const char* valueStr)
return css::Value(valueStr);
}
static FontData* load_font(std::map<std::string, FontData*>& fonts,
const TiXmlElement* 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;
}
const char* nameStr = xmlFont->Attribute("name");
if (!nameStr)
throw base::Exception("No \"name\" or \"font\" attributes specified on <font>");
std::string name(nameStr);
LOG(VERBOSE) << "SKIN: Loading font '" << name << "'\n";
const char* typeStr = xmlFont->Attribute("type");
if (!typeStr)
throw base::Exception("<font> without 'type' attribute in '%s'\n",
xmlFilename.c_str());
std::string type(typeStr);
base::UniquePtr<FontData> font(nullptr);
if (type == "spritesheet") {
const char* fileStr = xmlFont->Attribute("file");
if (fileStr) {
font.reset(new FontData(she::FontType::kSpriteSheet));
font->setFilename(
base::join_path(
base::get_file_path(xmlFilename),
fileStr));
}
}
else if (type == "truetype") {
const char* platformFileAttrName =
#ifdef _WIN32
"file_win"
#elif defined __APPLE__
"file_mac"
#else
"file_linux"
#endif
;
const char* platformFileStr = xmlFont->Attribute(platformFileAttrName);
const char* fileStr = xmlFont->Attribute("file");
bool antialias = true;
if (xmlFont->Attribute("antialias"))
antialias = bool_attr_is_true(xmlFont, "antialias");
std::string fontFilename;
if (platformFileStr)
fontFilename = app::find_font(platformFileStr);
if (fileStr && fontFilename.empty())
fontFilename = app::find_font(fileStr);
if (!fontFilename.empty()) {
font.reset(new FontData(she::FontType::kTrueType));
font->setFilename(fontFilename);
font->setAntialias(antialias);
}
else {
throw base::Exception("Invalid file for <font name=\"%s\" ...> in '%s'\n",
name.c_str(), xmlFilename.c_str());
}
}
else {
throw base::Exception("Invalid type=\"%s\" in '%s' for <font name=\"%s\" ...>\n",
type.c_str(), xmlFilename.c_str(), name.c_str());
}
FontData* result = nullptr;
if (font) {
fonts[name] = result = font.get();
font.release();
// Fallback font
const TiXmlElement* xmlFallback =
(const TiXmlElement*)xmlFont->FirstChild("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);
result->setFallback(fallback, size);
}
}
}
return result;
}
// static
SkinTheme* SkinTheme::instance()
{
@ -101,13 +201,11 @@ SkinTheme* SkinTheme::instance()
}
SkinTheme::SkinTheme()
: m_cursors(ui::kCursorTypes, NULL)
: m_sheet(nullptr)
, m_cursors(ui::kCursorTypes, nullptr)
, m_defaultFont(nullptr)
, m_miniFont(nullptr)
{
m_defaultFont = nullptr;
m_miniFont = nullptr;
// Initialize all graphics in NULL (these bitmaps are loaded from the skin)
m_sheet = NULL;
}
SkinTheme::~SkinTheme()
@ -127,11 +225,8 @@ SkinTheme::~SkinTheme()
m_parts_by_id.clear();
// Destroy fonts
if (m_defaultFont)
m_defaultFont->dispose();
if (m_miniFont)
m_miniFont->dispose();
for (auto kv : m_fonts)
delete kv.second; // Delete all FontDatas
}
void SkinTheme::onRegenerate()
@ -160,12 +255,37 @@ void SkinTheme::onRegenerate()
}
}
void SkinTheme::loadFontData()
{
LOG("SKIN: Loading fonts\n");
std::string fonstFilename("fonts/fonts.xml");
ResourceFinder rf;
rf.includeDataDir(fonstFilename.c_str());
if (!rf.findFirst())
throw base::Exception("File %s not found", fonstFilename.c_str());
XmlDocumentRef doc = open_xml(rf.filename());
TiXmlHandle handle(doc.get());
TiXmlElement* xmlFont = handle
.FirstChild("fonts")
.FirstChild("font").ToElement();
while (xmlFont) {
load_font(m_fonts, xmlFont, rf.filename());
xmlFont = xmlFont->NextSiblingElement();
}
}
void SkinTheme::loadAll(const std::string& skinId)
{
LOG("SKIN: Loading theme %s\n", skinId.c_str());
if (m_fonts.empty())
loadFontData();
loadSheet(skinId);
loadFonts(skinId);
loadXml(skinId);
}
@ -190,17 +310,6 @@ void SkinTheme::loadSheet(const std::string& skinId)
}
}
void SkinTheme::loadFonts(const std::string& skinId)
{
if (m_defaultFont) m_defaultFont->dispose();
if (m_miniFont) m_miniFont->dispose();
Preferences& pref = Preferences::instance();
m_defaultFont = loadFont(pref.theme.font(), themeFileName(skinId, "font.png"));
m_miniFont = loadFont(pref.theme.miniFont(), themeFileName(skinId, "minifont.png"));
}
void SkinTheme::loadXml(const std::string& skinId)
{
// Load the skin XML
@ -213,6 +322,42 @@ void SkinTheme::loadXml(const std::string& skinId)
XmlDocumentRef doc = open_xml(rf.filename());
TiXmlHandle handle(doc.get());
// Load fonts
{
TiXmlElement* xmlFont = handle
.FirstChild("theme")
.FirstChild("fonts")
.FirstChild("font").ToElement();
while (xmlFont) {
const char* idStr = xmlFont->Attribute("id");
FontData* fontData = load_font(m_fonts, xmlFont, rf.filename());
if (idStr && fontData) {
std::string id(idStr);
LOG(VERBOSE) << "SKIN: Loading theme font '" << id << "\n";
int size = 10;
const char* sizeStr = xmlFont->Attribute("size");
if (sizeStr)
size = std::strtol(sizeStr, nullptr, 10);
if (id == "default") {
m_defaultFont = fontData->getFont(size);
}
else if (id == "mini") {
m_miniFont = fontData->getFont(size);
}
}
xmlFont = xmlFont->NextSiblingElement();
}
}
// No available font to run the program
if (!m_defaultFont)
throw base::Exception("There is no default font");
if (!m_miniFont)
m_miniFont = m_defaultFont;
// Load dimension
{
TiXmlElement* xmlDim = handle
@ -1778,30 +1923,6 @@ void SkinTheme::paintIcon(Widget* widget, Graphics* g, IButtonIcon* iconInterfac
g->drawRgbaSurface(icon_bmp, x, y);
}
she::Font* SkinTheme::loadFont(const std::string& userFont, const std::string& themeFont)
{
// Directories to find the font
ResourceFinder rf;
if (!userFont.empty())
rf.addPath(userFont.c_str());
rf.includeDataDir(themeFont.c_str());
// Try to load the font
while (rf.next()) {
try {
she::Font* f = she::instance()->loadSpriteSheetFont(rf.filename().c_str(), guiscale());
if (f->isScalable())
f->setSize(8);
return f;
}
catch (const std::exception&) {
// Do nothing
}
}
return nullptr;
}
std::string SkinTheme::themeFileName(const std::string& skinId,
const std::string& fileName) const
{

View File

@ -33,6 +33,8 @@ namespace she {
namespace app {
namespace skin {
class FontData;
// This is the GUI theme used by Aseprite (which use images from
// data/skins directory).
class SkinTheme : public ui::Theme
@ -114,9 +116,9 @@ namespace app {
void onRegenerate() override;
private:
void loadFontData();
void loadAll(const std::string& skinId);
void loadSheet(const std::string& skinId);
void loadFonts(const std::string& skinId);
void loadXml(const std::string& skinId);
she::Surface* sliceSheet(she::Surface* sur, const gfx::Rect& bounds);
@ -128,7 +130,6 @@ namespace app {
void paintIcon(ui::Widget* widget, ui::Graphics* g, ui::IButtonIcon* iconInterface, int x, int y);
she::Font* loadFont(const std::string& userFont, const std::string& themeFont);
std::string themeFileName(const std::string& skinId,
const std::string& fileName) const;
@ -140,6 +141,7 @@ namespace app {
std::vector<ui::Cursor*> m_cursors;
StyleSheet m_stylesheet;
std::map<std::string, ui::Style*> m_styles;
std::map<std::string, FontData*> m_fonts;
she::Font* m_defaultFont;
she::Font* m_miniFont;
};

View File

@ -69,6 +69,11 @@ void FreeTypeFont::setAntialias(bool antialias)
m_face.setAntialias(antialias);
}
bool FreeTypeFont::hasCodePoint(int codepoint) const
{
return true; // TODO
}
FreeTypeFont* loadFreeTypeFont(const char* filename, int height)
{
FreeTypeFont* font = new FreeTypeFont(filename, height);

View File

@ -30,6 +30,7 @@ namespace she {
bool isScalable() const override;
void setSize(int size) override;
void setAntialias(bool antialias) override;
bool hasCodePoint(int codepoint) const override;
Face& face() { return m_face; }

View File

@ -63,6 +63,11 @@ public:
// Do nothing
}
bool hasCodePoint(int codepoint) const override {
codepoint -= (int)' ';
return (codepoint >= 0 && codepoint < (int)m_chars.size());
}
Surface* getSurfaceSheet() const {
return m_sheet;
}

View File

@ -29,6 +29,24 @@ gfx::Rect draw_text(Surface* surface, Font* font,
base::utf8_const_iterator it = begin;
gfx::Rect textBounds;
retry:;
// Check if this font is enough to draw the given string or we will
// need the fallback for some special Unicode chars
if (font->fallback()) {
// TODO compose unicode characters and check those codepoints, the
// same in the drawing code of sprite sheet font
for (auto it=begin; it!=end; ++it) {
uint32_t code = *it;
if (code && !font->hasCodePoint(code)) {
Font* newFont = font->fallback();
y += font->height()/2 - newFont->height()/2;
font = newFont;
goto retry;
}
}
}
switch (font->type()) {
case FontType::kSpriteSheet: {

View File

@ -20,6 +20,7 @@ namespace she {
class Font {
public:
Font() : m_fallback(nullptr) { }
virtual ~Font() { }
virtual void dispose() = 0;
virtual FontType type() = 0;
@ -28,6 +29,17 @@ namespace she {
virtual bool isScalable() const = 0;
virtual void setSize(int size) = 0;
virtual void setAntialias(bool antialias) = 0;
virtual bool hasCodePoint(int codepoint) const = 0;
she::Font* fallback() const {
return m_fallback;
}
void setFallback(she::Font* font) {
m_fallback = font;
}
private:
she::Font* m_fallback;
};
} // namespace she