/************************************************************************* * Copyright (c) 2011 AT&T Intellectual Property * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * https://www.eclipse.org/legal/epl-v10.html * * Contributors: Details at https://graphviz.org *************************************************************************/ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include "gvgetfontlist.h" #ifdef HAVE_PANGO_FC_FONT_LOCK_FACE #include #endif static void pango_free_layout (void *layout) { g_object_unref(layout); } static char* pango_psfontResolve (PostscriptAlias* pa) { agxbuf buf = {0}; agxbprint(&buf, "%s,", pa->family); if (pa->weight) { agxbprint(&buf, " %s", pa->weight); } if (pa->stretch) { agxbprint(&buf, " %s", pa->stretch); } if (pa->style) { agxbprint(&buf, " %s", pa->style); } return agxbdisown(&buf); } #define FONT_DPI 96. #define ENABLE_PANGO_MARKUP // wrapper to handle difference in calling conventions between `agxbput` and // `xml_escape`’s `cb` static int agxbput_int(void *buffer, const char *s) { size_t len = agxbput(buffer, s); assert(len <= INT_MAX); return (int)len; } static bool pango_textlayout(textspan_t * span, char **fontpath) { static agxbuf buf; // returned in fontpath, only good until next call static PangoFontMap *fontmap; static PangoContext *context; static PangoFontDescription *desc; static char *fontname; static double fontsize; static gv_font_map* gv_fmap; char *fnt, *psfnt = NULL; PangoFont *font; #ifdef ENABLE_PANGO_MARKUP PangoAttrList *attrs; GError *error = NULL; int flags; #endif char *text; if (!context) { fontmap = pango_cairo_font_map_new(); gv_fmap = get_font_mapping(fontmap); context = pango_font_map_create_context (fontmap); cairo_font_options_t* options = cairo_font_options_create(); cairo_font_options_set_antialias(options,CAIRO_ANTIALIAS_GRAY); cairo_font_options_set_hint_style(options,CAIRO_HINT_STYLE_FULL); cairo_font_options_set_hint_metrics(options,CAIRO_HINT_METRICS_ON); cairo_font_options_set_subpixel_order(options,CAIRO_SUBPIXEL_ORDER_BGR); pango_cairo_context_set_font_options(context, options); pango_cairo_context_set_resolution(context, FONT_DPI); cairo_font_options_destroy(options); g_object_unref(fontmap); } if (!fontname || strcmp(fontname, span->font->name) != 0 || fontsize != span->font->size) { /* check if the conversion to Pango units below will overflow */ if (INT_MAX / PANGO_SCALE < span->font->size) { return false; } free(fontname); fontname = gv_strdup(span->font->name); fontsize = span->font->size; pango_font_description_free (desc); PostscriptAlias *pA = span->font->postscript_alias; bool psfnt_needs_free = false; if (pA) { psfnt = fnt = gv_fmap[pA->xfig_code].gv_font; if(!psfnt) { psfnt = fnt = pango_psfontResolve (pA); psfnt_needs_free = true; } } else fnt = fontname; desc = pango_font_description_from_string(fnt); /* all text layout is done at a scale of FONT_DPI (nominaly 96.) */ pango_font_description_set_size (desc, (int)(fontsize * PANGO_SCALE)); if (fontpath && (font = pango_font_map_load_font(fontmap, context, desc))) { /* -v support */ const char *fontclass = G_OBJECT_CLASS_NAME(G_OBJECT_GET_CLASS(font)); agxbclear(&buf); if (psfnt) { agxbprint(&buf, "(ps:pango %s) ", psfnt); } agxbprint(&buf, "(%s) ", fontclass); #ifdef HAVE_PANGO_FC_FONT_LOCK_FACE if (strcmp(fontclass, "PangoCairoFcFont") == 0) { PangoFcFont *fcfont = PANGO_FC_FONT(font); FT_Face face = pango_fc_font_lock_face(fcfont); if (face) { agxbprint(&buf, "\"%s, %s\" ", face->family_name, face->style_name); FT_Stream stream = face->stream; if (stream) { FT_StreamDesc streamdesc = stream->pathname; if (streamdesc.pointer) agxbput(&buf, streamdesc.pointer); else agxbput(&buf, "*no pathname available*"); } else agxbput(&buf, "*no stream available*"); } pango_fc_font_unlock_face(fcfont); } else #endif { PangoFontDescription *tdesc = pango_font_describe(font); char *tfont = pango_font_description_to_string(tdesc); agxbprint(&buf, "\"%s\" ", tfont); g_free(tfont); } *fontpath = agxbuse(&buf); } if (psfnt_needs_free) { free(psfnt); } } #ifdef ENABLE_PANGO_MARKUP if ((span->font) && (flags = span->font->flags)) { agxbuf xb = {0}; agxbput(&xb,""); if (flags & HTML_SUP) agxbput(&xb,""); if (flags & HTML_SUB) agxbput(&xb,""); const xml_flags_t xml_flags = {.raw = 1, .dash = 1, .nbsp = 1}; xml_escape(span->str, xml_flags, agxbput_int, &xb); if (flags & HTML_SUB) agxbput(&xb,""); if (flags & HTML_SUP) agxbput(&xb,""); agxbput (&xb,""); if (!pango_parse_markup (agxbuse(&xb), -1, 0, &attrs, &text, NULL, &error)) { fprintf (stderr, "Error - pango_parse_markup: %s\n", error->message); text = span->str; attrs = NULL; } agxbfree (&xb); } else { text = span->str; attrs = NULL; } #else text = span->str; #endif PangoLayout *layout = pango_layout_new (context); span->layout = layout; /* layout free with textspan - see labels.c */ span->free_layout = pango_free_layout; /* function for freeing pango layout */ pango_layout_set_text (layout, text, -1); pango_layout_set_font_description (layout, desc); #ifdef ENABLE_PANGO_MARKUP if (attrs) pango_layout_set_attributes (layout, attrs); #endif PangoRectangle logical_rect; pango_layout_get_extents (layout, NULL, &logical_rect); /* if pango doesn't like the font then it sets width=0 but height = garbage */ if (logical_rect.width == 0) logical_rect.height = 0; const double textlayout_scale = POINTS_PER_INCH / (FONT_DPI * PANGO_SCALE); span->size.x = logical_rect.width * textlayout_scale; span->size.y = logical_rect.height * textlayout_scale; /* The y offset from baseline to 0,0 of the bitmap representation */ span->yoffset_layout = pango_layout_get_baseline (layout) * textlayout_scale; /* The distance below midline for y centering of text strings */ span->yoffset_centerline = 0.05 * span->font->size; return logical_rect.width != 0 || strcmp(text, "") == 0; } static gvtextlayout_engine_t pango_textlayout_engine = { pango_textlayout, }; gvplugin_installed_t gvtextlayout_pango_types[] = { {0, "textlayout", 10, &pango_textlayout_engine, NULL}, {0, NULL, 0, NULL, NULL} };