343 lines
9.6 KiB
C++
343 lines
9.6 KiB
C++
#pragma once
|
|
|
|
#include <cairo/cairo-ft.h>
|
|
|
|
#include "cairo/types.hpp"
|
|
#include "cairo/utils.hpp"
|
|
#include "common.hpp"
|
|
#include "components/logger.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(string_util::unicode_character& character) = 0;
|
|
virtual size_t match(string_util::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;
|
|
}
|
|
|
|
/**
|
|
* Calculates the font size in pixels for the given dpi
|
|
*
|
|
* We use the two font properties size and pixelsize. size is in points and
|
|
* needs to be scaled with the given dpi. pixelsize is not scaled.
|
|
*
|
|
* If both size properties are 0, we fall back to a default value of 10
|
|
* points for scalable fonts or 10 pixel for non-scalable ones. This should
|
|
* only happen if both properties are purposefully set to 0
|
|
*
|
|
* For scalable fonts we try to use the size property scaled according to
|
|
* the dpi.
|
|
* For non-scalable fonts we try to use the pixelsize property as-is
|
|
*/
|
|
double size(double dpi) const override {
|
|
bool scalable;
|
|
double fc_pixelsize = 0, fc_size = 0;
|
|
|
|
property(FC_SCALABLE, &scalable);
|
|
|
|
// Size in points
|
|
property(FC_SIZE, &fc_size);
|
|
|
|
// Size in pixels
|
|
property(FC_PIXEL_SIZE, &fc_pixelsize);
|
|
|
|
// Fall back to a default value if the size is 0
|
|
double pixelsize = fc_pixelsize == 0 ? 10 : fc_pixelsize;
|
|
double size = fc_size == 0 ? 10 : fc_size;
|
|
|
|
// Font size in pixels if we use the pixelsize property
|
|
int px_pixelsize = pixelsize + 0.5;
|
|
|
|
/*
|
|
* Font size in pixels if we use the size property. Since the size
|
|
* specifies the font size in points, this is converted to pixels
|
|
* according to the dpi given.
|
|
* One point is 1/72 inches, thus this gives us the number of 'dots'
|
|
* (or pixels) for this font
|
|
*/
|
|
int px_size = size / 72.0 * dpi + 0.5;
|
|
|
|
if (fc_size == 0 && fc_pixelsize == 0) {
|
|
return scalable ? px_size : px_pixelsize;
|
|
}
|
|
|
|
if (scalable) {
|
|
/*
|
|
* Use the point size if it's not 0. The pixelsize is only used if the
|
|
* size property is 0 and pixelsize is not
|
|
*/
|
|
if (fc_size != 0) {
|
|
return px_size;
|
|
} else {
|
|
return px_pixelsize;
|
|
}
|
|
} else {
|
|
/*
|
|
* Non-scalable fonts do it the other way around, here the size
|
|
* property is only used if pixelsize is 0 and size is not
|
|
*/
|
|
if (fc_pixelsize != 0) {
|
|
return px_pixelsize;
|
|
} else {
|
|
return px_size;
|
|
}
|
|
}
|
|
}
|
|
|
|
void use() override {
|
|
cairo_set_scaled_font(m_cairo, m_scaled);
|
|
}
|
|
|
|
size_t match(string_util::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(string_util::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;
|
|
int 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 scope_util::on_exit fc_cleanup([] {
|
|
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);
|
|
}
|
|
} // namespace cairo
|
|
|
|
POLYBAR_NS_END
|