01f4957485
If FcNameParse returns NULL FcDefaultSubstitute segfaults We log and throw an error here because the signal emitter swallows all exceptions so if that exception was thrown, the bar would stop rendering without any indication as to why. Fixes #1435
286 lines
8.3 KiB
C++
286 lines
8.3 KiB
C++
#pragma once
|
|
|
|
#include <cairo/cairo-ft.h>
|
|
|
|
#include "cairo/types.hpp"
|
|
#include "cairo/utils.hpp"
|
|
#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 {
|
|
/**
|
|
* @brief Global pointer to the Freetype library handler
|
|
*/
|
|
static FT_Library g_ftlib;
|
|
|
|
/**
|
|
* @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(double dpi) const = 0;
|
|
|
|
virtual cairo_font_extents_t extents() = 0;
|
|
|
|
virtual void use() {
|
|
cairo_set_font_face(m_cairo, cairo_font_face_reference(m_font_face));
|
|
}
|
|
|
|
virtual size_t match(utils::unicode_character& character) = 0;
|
|
virtual size_t match(utils::unicode_charlist& charlist) = 0;
|
|
virtual size_t render(const string& text, double x = 0.0, double y = 0.0) = 0;
|
|
virtual void textwidth(const string& text, cairo_text_extents_t* extents) = 0;
|
|
|
|
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, double dpi_x, double dpi_y) : font(cairo, offset), m_pattern(pattern) {
|
|
cairo_matrix_t fm;
|
|
cairo_matrix_t ctm;
|
|
cairo_matrix_init_scale(&fm, size(dpi_x), size(dpi_y));
|
|
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<utils::ft_face_lock>(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;
|
|
}
|
|
|
|
lock.reset();
|
|
}
|
|
|
|
~font_fc() override {
|
|
if (m_scaled != nullptr) {
|
|
cairo_scaled_font_destroy(m_scaled);
|
|
}
|
|
if (m_pattern != nullptr) {
|
|
FcPatternDestroy(m_pattern);
|
|
}
|
|
}
|
|
|
|
cairo_font_extents_t extents() override {
|
|
cairo_scaled_font_extents(m_scaled, &m_extents);
|
|
return m_extents;
|
|
}
|
|
|
|
string name() const override {
|
|
return property("family");
|
|
}
|
|
|
|
string file() const override {
|
|
return property("file");
|
|
}
|
|
|
|
double offset() const override {
|
|
return m_offset;
|
|
}
|
|
|
|
double size(double dpi) const override {
|
|
bool scalable;
|
|
double px;
|
|
property(FC_SCALABLE, &scalable);
|
|
if (scalable) {
|
|
// convert from pt to px using the provided dpi
|
|
property(FC_SIZE, &px);
|
|
px = static_cast<int>(px * dpi / 72.0 + 0.5);
|
|
} else {
|
|
property(FC_PIXEL_SIZE, &px);
|
|
px = static_cast<int>(px + 0.5);
|
|
}
|
|
return px;
|
|
}
|
|
|
|
void use() override {
|
|
cairo_set_scaled_font(m_cairo, m_scaled);
|
|
}
|
|
|
|
size_t match(utils::unicode_character& character) override {
|
|
auto lock = make_unique<utils::ft_face_lock>(m_scaled);
|
|
auto face = static_cast<FT_Face>(*lock);
|
|
return FT_Get_Char_Index(face, character.codepoint) ? 1 : 0;
|
|
}
|
|
|
|
size_t match(utils::unicode_charlist& charlist) override {
|
|
auto lock = make_unique<utils::ft_face_lock>(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, double x = 0.0, double y = 0.0) override {
|
|
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(
|
|
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_text_to_glyphs()" << 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 && bytes < text.size()) {
|
|
cairo_glyph_free(glyphs);
|
|
cairo_text_cluster_free(clusters);
|
|
|
|
utf8 = text.substr(0, bytes);
|
|
auto status = cairo_scaled_font_text_to_glyphs(
|
|
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_text_to_glyphs()" << cairo_status_to_string(status));
|
|
}
|
|
}
|
|
|
|
if (bytes) {
|
|
// auto lock = make_unique<utils::device_lock>(cairo_surface_get_device(cairo_get_target(m_cairo)));
|
|
// if (lock.get()) {
|
|
// cairo_glyph_path(m_cairo, glyphs, nglyphs);
|
|
// }
|
|
|
|
cairo_text_extents_t extents{};
|
|
cairo_scaled_font_glyph_extents(m_scaled, glyphs, nglyphs, &extents);
|
|
cairo_show_text_glyphs(m_cairo, utf8.c_str(), utf8.size(), glyphs, nglyphs, clusters, nclusters, cf);
|
|
cairo_fill(m_cairo);
|
|
cairo_move_to(m_cairo, x + extents.x_advance, 0.0);
|
|
}
|
|
|
|
cairo_glyph_free(glyphs);
|
|
cairo_text_cluster_free(clusters);
|
|
|
|
return bytes;
|
|
}
|
|
|
|
void textwidth(const string& text, cairo_text_extents_t* extents) override {
|
|
cairo_scaled_font_text_extents(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
|
|
*/
|
|
inline decltype(auto) make_font(cairo_t* cairo, string&& fontname, double offset, double dpi_x, double dpi_y) {
|
|
static bool fc_init{false};
|
|
if (!fc_init && !(fc_init = FcInit())) {
|
|
throw application_error("Could not load fontconfig");
|
|
} else if (FT_Init_FreeType(&g_ftlib) != FT_Err_Ok) {
|
|
throw application_error("Could not load FreeType");
|
|
}
|
|
|
|
static auto fc_cleanup = scope_util::make_exit_handler([] {
|
|
FT_Done_FreeType(g_ftlib);
|
|
FcFini();
|
|
});
|
|
|
|
auto pattern = FcNameParse((FcChar8*)fontname.c_str());
|
|
|
|
if(!pattern) {
|
|
logger::make().err("Could not parse font \"%s\"", fontname);
|
|
throw application_error("Could not parse font \"" + fontname + "\"");
|
|
}
|
|
|
|
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_shared<font_fc>(cairo, match, offset, dpi_x, dpi_y);
|
|
}
|
|
}
|
|
|
|
POLYBAR_NS_END
|