polybar/include/cairo/font.hpp

284 lines
7.7 KiB
C++

#pragma once
#include <cairo/cairo-ft.h>
#include <list>
#include <set>
#include <string>
#include "common.hpp"
#include "errors.hpp"
#include "settings.hpp"
#include "utils/math.hpp"
#include "utils/scope.hpp"
#include "utils/string.hpp"
POLYBAR_NS
namespace cairo {
namespace details {
/**
* @brief Global pointer to the Freetype library handler
*/
static FT_Library g_ftlib;
/**
* @brief RAII wrapper used to access the underlying
* FT_Face of a scaled font face
*/
class ft_face_lock {
public:
explicit ft_face_lock(cairo_scaled_font_t* font) : m_font(font) {
m_face = cairo_ft_scaled_font_lock_face(m_font);
}
~ft_face_lock() {
cairo_ft_scaled_font_unlock_face(m_font);
}
operator FT_Face() const {
return m_face;
}
private:
cairo_scaled_font_t* m_font;
FT_Face m_face;
};
}
/**
* @brief Unicode character containing converted codepoint
* and details on where its position in the source string
*/
struct unicode_character {
explicit unicode_character() : codepoint(0), offset(0), length(0) {}
unsigned long codepoint;
int offset;
int length;
};
using unicode_charlist = std::list<unicode_character>;
/**
* @brief Abstract font face
*/
class font {
public:
explicit font(cairo_t* cairo, double offset) : m_cairo(cairo), m_offset(offset) {}
virtual ~font() {};
virtual string name() const = 0;
virtual string file() const = 0;
virtual double offset() const = 0;
virtual double size() const = 0;
virtual void use(cairo_t* c) {
cairo_set_font_face(c, cairo_font_face_reference(m_font_face));
}
virtual size_t match(unicode_charlist& charlist) = 0;
virtual size_t render(const string& text) = 0;
virtual void textwidth(const string& text, cairo_text_extents_t* extents) = 0;
cairo_font_extents_t extents() const {
return m_extents;
}
protected:
cairo_t* m_cairo;
cairo_font_face_t* m_font_face{nullptr};
cairo_font_extents_t m_extents{};
double m_offset{0.0};
};
/**
* @brief Font based on fontconfig/freetype
*/
class font_fc : public font {
public:
explicit font_fc(cairo_t* cairo, FcPattern* pattern, double offset) : font(cairo, offset), m_pattern(pattern) {
cairo_matrix_t fm;
cairo_matrix_t ctm;
cairo_matrix_init_scale(&fm, size(), size());
cairo_get_matrix(m_cairo, &ctm);
auto fontface = cairo_ft_font_face_create_for_pattern(m_pattern);
auto opts = cairo_font_options_create();
m_scaled = cairo_scaled_font_create(fontface, &fm, &ctm, opts);
cairo_font_options_destroy(opts);
cairo_font_face_destroy(fontface);
auto status = cairo_scaled_font_status(m_scaled);
if (status != CAIRO_STATUS_SUCCESS) {
throw application_error(sstream() << "cairo_scaled_font_create(): " << cairo_status_to_string(status));
}
auto lock = make_unique<details::ft_face_lock>(cairo_scaled_font_reference(m_scaled));
auto face = static_cast<FT_Face>(*lock);
if (FT_Select_Charmap(face, FT_ENCODING_UNICODE) == FT_Err_Ok) {
return;
} else if (FT_Select_Charmap(face, FT_ENCODING_BIG5) == FT_Err_Ok) {
return;
} else if (FT_Select_Charmap(face, FT_ENCODING_SJIS) == FT_Err_Ok) {
return;
}
}
~font_fc() override {
if (m_scaled != nullptr) {
cairo_scaled_font_destroy(m_scaled);
}
if (m_pattern != nullptr) {
FcPatternDestroy(m_pattern);
}
}
string name() const override {
return property("family");
}
string file() const override {
return property("file");
}
double offset() const override {
return m_offset;
}
double size() const override {
bool scalable;
double px;
property(FC_SCALABLE, &scalable);
if (scalable) {
property(FC_SIZE, &px);
} else {
property(FC_PIXEL_SIZE, &px);
px = static_cast<int>(px + 0.5);
}
return px;
}
void use(cairo_t* c) override {
cairo_set_scaled_font(c, cairo_scaled_font_reference(m_scaled));
}
size_t match(unicode_charlist& charlist) override {
auto lock = make_unique<details::ft_face_lock>(cairo_scaled_font_reference(m_scaled));
auto face = static_cast<FT_Face>(*lock);
size_t available_chars = 0;
for (auto&& c : charlist) {
if (FT_Get_Char_Index(face, c.codepoint)) {
available_chars++;
} else {
break;
}
}
return available_chars;
}
size_t render(const string& text) override {
double x, y;
cairo_get_current_point(m_cairo, &x, &y);
cairo_glyph_t* glyphs{nullptr};
cairo_text_cluster_t* clusters{nullptr};
cairo_text_cluster_flags_t cf{};
int nglyphs = 0, nclusters = 0;
string utf8 = string(text);
auto status = cairo_scaled_font_text_to_glyphs(cairo_scaled_font_reference(m_scaled), x, y, utf8.c_str(),
utf8.size(), &glyphs, &nglyphs, &clusters, &nclusters, &cf);
if (status != CAIRO_STATUS_SUCCESS) {
throw application_error(sstream() << "cairo_scaled_font_status()" << cairo_status_to_string(status));
}
size_t bytes = 0;
for (int g = 0; g < nglyphs; g++) {
if (glyphs[g].index) {
bytes += clusters[g].num_bytes;
} else {
break;
}
}
if (bytes) {
utf8 = text.substr(0, bytes);
cairo_scaled_font_text_to_glyphs(
m_scaled, x, y, utf8.c_str(), utf8.size(), &glyphs, &nglyphs, &clusters, &nclusters, &cf);
cairo_show_text_glyphs(m_cairo, utf8.c_str(), utf8.size(), glyphs, nglyphs, clusters, nclusters, cf);
}
return bytes;
}
void textwidth(const string& text, cairo_text_extents_t* extents) override {
cairo_scaled_font_text_extents(cairo_scaled_font_reference(m_scaled), text.c_str(), extents);
}
protected:
string property(string&& property) const {
FcChar8* file;
if (FcPatternGetString(m_pattern, property.c_str(), 0, &file) == FcResultMatch) {
return string(reinterpret_cast<char*>(file));
} else {
return "";
}
}
void property(string&& property, bool* dst) const {
FcBool b;
FcPatternGetBool(m_pattern, property.c_str(), 0, &b);
*dst = b;
}
void property(string&& property, double* dst) const {
FcPatternGetDouble(m_pattern, property.c_str(), 0, dst);
}
void property(string&& property, int* dst) const {
FcPatternGetInteger(m_pattern, property.c_str(), 0, dst);
}
private:
cairo_scaled_font_t* m_scaled{nullptr};
FcPattern* m_pattern{nullptr};
};
/**
* Match and create font from given fontconfig pattern
*/
decltype(auto) make_font(cairo_t* cairo, string&& fontname, double offset) {
static bool fc_init{false};
if (!fc_init && !(fc_init = FcInit())) {
throw application_error("Could not load fontconfig");
} else if (FT_Init_FreeType(&details::g_ftlib) != FT_Err_Ok) {
throw application_error("Could not load FreeType");
}
static auto fc_cleanup = scope_util::make_exit_handler([] {
FT_Done_FreeType(details::g_ftlib);
FcFini();
});
auto pattern = FcNameParse((FcChar8*)fontname.c_str());
FcDefaultSubstitute(pattern);
FcConfigSubstitute(nullptr, pattern, FcMatchPattern);
FcResult result;
FcPattern* match = FcFontMatch(nullptr, pattern, &result);
FcPatternDestroy(pattern);
if (match == nullptr) {
throw application_error("Could not load font \"" + fontname + "\"");
}
#ifdef DEBUG_FONTCONFIG
FcPatternPrint(match);
#endif
return make_unique<font_fc>(cairo, match, offset);
}
}
POLYBAR_NS_END