libui/unix/drawtext.c

329 lines
9.6 KiB
C

// 6 september 2015
#include "uipriv_unix.h"
#include "draw.h"
struct uiDrawFontFamilies {
PangoFontFamily **f;
int n;
};
uiDrawFontFamilies *uiDrawListFontFamilies(void)
{
uiDrawFontFamilies *ff;
PangoFontMap *map;
ff = uiNew(uiDrawFontFamilies);
map = pango_cairo_font_map_get_default();
pango_font_map_list_families(map, &(ff->f), &(ff->n));
// do not free map; it's a shared resource
return ff;
}
uintmax_t uiDrawFontFamiliesNumFamilies(uiDrawFontFamilies *ff)
{
return ff->n;
}
char *uiDrawFontFamiliesFamily(uiDrawFontFamilies *ff, uintmax_t n)
{
PangoFontFamily *f;
f = ff->f[n];
return uiUnixStrdupText(pango_font_family_get_name(f));
}
void uiDrawFreeFontFamilies(uiDrawFontFamilies *ff)
{
g_free(ff->f);
uiFree(ff);
}
struct uiDrawTextFont {
PangoFont *f;
};
uiDrawTextFont *mkTextFont(PangoFont *f, gboolean ref)
{
uiDrawTextFont *font;
font = uiNew(uiDrawTextFont);
font->f = f;
if (ref)
g_object_ref(font->f);
return font;
}
static const PangoWeight pangoWeights[] = {
[uiDrawTextWeightThin] = PANGO_WEIGHT_THIN,
[uiDrawTextWeightUltraLight] = PANGO_WEIGHT_ULTRALIGHT,
[uiDrawTextWeightLight] = PANGO_WEIGHT_LIGHT,
[uiDrawTextWeightBook] = PANGO_WEIGHT_BOOK,
[uiDrawTextWeightNormal] = PANGO_WEIGHT_NORMAL,
[uiDrawTextWeightMedium] = PANGO_WEIGHT_MEDIUM,
[uiDrawTextWeightSemiBold] = PANGO_WEIGHT_SEMIBOLD,
[uiDrawTextWeightBold] = PANGO_WEIGHT_BOLD,
[uiDrawTextWeightUtraBold] = PANGO_WEIGHT_ULTRABOLD,
[uiDrawTextWeightHeavy] = PANGO_WEIGHT_HEAVY,
[uiDrawTextWeightUltraHeavy] = PANGO_WEIGHT_ULTRAHEAVY,
};
static const PangoStyle pangoItalics[] = {
[uiDrawTextItalicNormal] = PANGO_STYLE_NORMAL,
[uiDrawTextItalicOblique] = PANGO_STYLE_OBLIQUE,
[uiDrawTextItalicItalic] = PANGO_STYLE_ITALIC,
};
static const PangoStretch pangoStretches[] = {
[uiDrawTextStretchUltraCondensed] = PANGO_STRETCH_ULTRA_CONDENSED,
[uiDrawTextStretchExtraCondensed] = PANGO_STRETCH_EXTRA_CONDENSED,
[uiDrawTextStretchCondensed] = PANGO_STRETCH_CONDENSED,
[uiDrawTextStretchSemiCondensed] = PANGO_STRETCH_SEMI_CONDENSED,
[uiDrawTextStretchNormal] = PANGO_STRETCH_NORMAL,
[uiDrawTextStretchSemiExpanded] = PANGO_STRETCH_SEMI_EXPANDED,
[uiDrawTextStretchExpanded] = PANGO_STRETCH_EXPANDED,
[uiDrawTextStretchExtraExpanded] = PANGO_STRETCH_EXTRA_EXPANDED,
[uiDrawTextStretchUltraExpanded] = PANGO_STRETCH_ULTRA_EXPANDED,
};
// we need a context for a few things
// the documentation suggests creating cairo_t-specific, GdkScreen-specific, or even GtkWidget-specific contexts, but we can't really do that because we want our uiDrawTextFonts and uiDrawTextLayouts to be context-independent
// so this will have to do
// TODO really see if there's a better way instead; what do GDK and GTK+ do internally? gdk_pango_context_get()?
#define mkGenericPangoCairoContext() (pango_font_map_create_context(pango_cairo_font_map_get_default()))
PangoFont *pangoDescToPangoFont(PangoFontDescription *pdesc)
{
PangoFont *f;
PangoContext *context;
// in this case, the context is necessary for the metrics to be correct
context = mkGenericPangoCairoContext();
f = pango_font_map_load_font(pango_cairo_font_map_get_default(), context, pdesc);
if (f == NULL) {
// TODO
g_error("[libui] no match in pangoDescToPangoFont(); report to andlabs");
}
g_object_unref(context);
return f;
}
uiDrawTextFont *uiDrawLoadClosestFont(const uiDrawTextFontDescriptor *desc)
{
PangoFont *f;
PangoFontDescription *pdesc;
pdesc = pango_font_description_new();
pango_font_description_set_family(pdesc,
desc->Family);
pango_font_description_set_size(pdesc,
(gint) (desc->Size * PANGO_SCALE));
pango_font_description_set_weight(pdesc,
pangoWeights[desc->Weight]);
pango_font_description_set_style(pdesc,
pangoItalics[desc->Italic]);
pango_font_description_set_stretch(pdesc,
pangoStretches[desc->Stretch]);
f = pangoDescToPangoFont(pdesc);
pango_font_description_free(pdesc);
return mkTextFont(f, FALSE); // we hold the initial reference; no need to ref
}
void uiDrawFreeTextFont(uiDrawTextFont *font)
{
g_object_unref(font->f);
uiFree(font);
}
uintptr_t uiDrawTextFontHandle(uiDrawTextFont *font)
{
return (uintptr_t) (font->f);
}
void uiDrawTextFontDescribe(uiDrawTextFont *font, uiDrawTextFontDescriptor *desc)
{
PangoFontDescription *pdesc;
// this creates a copy; we free it later
pdesc = pango_font_describe(font->f);
// TODO
pango_font_description_free(pdesc);
}
// See https://developer.gnome.org/pango/1.30/pango-Cairo-Rendering.html#pango-Cairo-Rendering.description
// Note that we convert to double before dividing to make sure the floating-point stuff is right
#define pangoToCairo(pango) (((double) (pango)) / PANGO_SCALE)
#define cairoToPango(cairo) ((gint) ((cairo) * PANGO_SCALE))
// TODO this isn't enough; pango adds extra space to each layout
void uiDrawTextFontGetMetrics(uiDrawTextFont *font, uiDrawTextFontMetrics *metrics)
{
PangoFontMetrics *pm;
pm = pango_font_get_metrics(font->f, NULL);
metrics->Ascent = pangoToCairo(pango_font_metrics_get_ascent(pm));
metrics->Descent = pangoToCairo(pango_font_metrics_get_descent(pm));
// Pango doesn't seem to expose this :( Use 0 and hope for the best.
metrics->Leading = 0;
metrics->UnderlinePos = pangoToCairo(pango_font_metrics_get_underline_position(pm));
metrics->UnderlineThickness = pangoToCairo(pango_font_metrics_get_underline_thickness(pm));
pango_font_metrics_unref(pm);
}
// note: PangoCairoLayouts are tied to a given cairo_t, so we can't store one in this device-independent structure
struct uiDrawTextLayout {
char *s;
ptrdiff_t *charsToBytes;
PangoFont *defaultFont;
double width;
PangoAttrList *attrs;
};
static ptrdiff_t *computeCharsToBytes(const char *s)
{
ptrdiff_t *charsToBytes;
glong i, charlen;
// we INCLUDE the null terminator as a character in the string
// g_utf8_offset_to_pointer() doesn't stop on null terminator so this should work
charlen = g_utf8_strlen(s, -1) + 1;
charsToBytes = (ptrdiff_t *) uiAlloc(charlen * sizeof (ptrdiff_t), "ptrdiff_t[]");
// TODO speed this up by not needing to scan the whole string again
for (i = 0; i < charlen; i++)
charsToBytes[i] = g_utf8_offset_to_pointer(s, i) - s;
return charsToBytes;
}
uiDrawTextLayout *uiDrawNewTextLayout(const char *text, uiDrawTextFont *defaultFont, double width)
{
uiDrawTextLayout *layout;
layout = uiNew(uiDrawTextLayout);
layout->s = g_strdup(text);
layout->charsToBytes = computeCharsToBytes(layout->s);
layout->defaultFont = defaultFont->f;
g_object_ref(layout->defaultFont); // retain a copy
uiDrawTextLayoutSetWidth(layout, width);
layout->attrs = pango_attr_list_new();
return layout;
}
void uiDrawFreeTextLayout(uiDrawTextLayout *layout)
{
pango_attr_list_unref(layout->attrs);
g_object_unref(layout->defaultFont);
uiFree(layout->charsToBytes);
g_free(layout->s);
uiFree(layout);
}
void uiDrawTextLayoutSetWidth(uiDrawTextLayout *layout, double width)
{
layout->width = width;
}
static void prepareLayout(uiDrawTextLayout *layout, PangoLayout *pl)
{
PangoFontDescription *desc;
int width;
pango_layout_set_text(pl, layout->s, -1);
// again, this makes a copy
desc = pango_font_describe(layout->defaultFont);
// this is safe; the description is copied
pango_layout_set_font_description(pl, desc);
pango_font_description_free(desc);
width = cairoToPango(layout->width);
if (layout->width < 0)
width = -1;
pango_layout_set_width(pl, width);
pango_layout_set_attributes(pl, layout->attrs);
}
void uiDrawTextLayoutExtents(uiDrawTextLayout *layout, double *width, double *height)
{
PangoContext *context;
PangoLayout *pl;
PangoRectangle logical;
// in this case, the context is necessary to create the layout
context = mkGenericPangoCairoContext();
pl = pango_layout_new(context);
// TODO g_object_unref() context?
prepareLayout(layout, pl);
pango_layout_get_extents(pl, NULL, &logical);
g_object_unref(pl);
*width = pangoToCairo(logical.width);
*height = pangoToCairo(logical.height);
}
void uiDrawText(uiDrawContext *c, double x, double y, uiDrawTextLayout *layout)
{
PangoLayout *pl;
pl = pango_cairo_create_layout(c->cr);
prepareLayout(layout, pl);
cairo_move_to(c->cr, x, y);
pango_cairo_show_layout(c->cr, pl);
g_object_unref(pl);
}
static void addAttr(uiDrawTextLayout *layout, PangoAttribute *attr, intmax_t startChar, intmax_t endChar)
{
attr->start_index = layout->charsToBytes[startChar];
attr->end_index = layout->charsToBytes[endChar];
pango_attr_list_insert(layout->attrs, attr);
// pango_attr_list_insert() takes attr; we don't free it
}
// these attributes are only supported on 1.38 and higher; we need to support 1.36
// use dynamic linking to make them work at least on newer systems
// TODO warn programmers
static PangoAttribute *(*newFGAlphaAttr)(guint16 alpha) = NULL;
static gboolean tried138 = FALSE;
// note that we treat any error as "the 1.38 symbols aren't there" (and don't care if dlclose() failed)
static void try138(void)
{
void *handle;
tried138 = TRUE;
// dlsym() walks the dependency chain, so opening the current process should be sufficient
handle = dlopen(NULL, RTLD_LAZY);
if (handle == NULL)
return;
*((void **) (&newFGAlphaAttr)) = dlsym(handle, "pango_attr_foreground_alpha_new");
dlclose(handle);
}
void uiDrawTextLayoutSetColor(uiDrawTextLayout *layout, intmax_t startChar, intmax_t endChar, double r, double g, double b, double a)
{
PangoAttribute *attr;
guint16 rr, gg, bb, aa;
rr = (guint16) (r * 65535);
gg = (guint16) (g * 65535);
bb = (guint16) (b * 65535);
aa = (guint16) (a * 65535);
attr = pango_attr_foreground_new(rr, gg, bb);
addAttr(layout, attr, startChar, endChar);
if (!tried138)
try138();
// TODO what if aa == 0?
if (newFGAlphaAttr != NULL) {
attr = (*newFGAlphaAttr)(aa);
addAttr(layout, attr, startChar, endChar);
}
}