From 4e53551e060451d62e9204d9dc0393ba3ce09db1 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Thu, 5 May 2016 18:23:54 -0400 Subject: [PATCH] Split unix/draw.c into a bunch of smaller files. --- unix/GNUfiles.mk | 4 + unix/draw.c | 666 +--------------------------------------------- unix/draw.h | 10 + unix/drawmatrix.c | 134 ++++++++++ unix/drawpath.c | 199 ++++++++++++++ unix/drawtext.c | 328 +++++++++++++++++++++++ 6 files changed, 678 insertions(+), 663 deletions(-) create mode 100644 unix/draw.h create mode 100644 unix/drawmatrix.c create mode 100644 unix/drawpath.c create mode 100644 unix/drawtext.c diff --git a/unix/GNUfiles.mk b/unix/GNUfiles.mk index 83f979aa..4987f177 100644 --- a/unix/GNUfiles.mk +++ b/unix/GNUfiles.mk @@ -11,6 +11,9 @@ CFILES += \ unix/control.c \ unix/datetimepicker.c \ unix/draw.c \ + unix/drawmatrix.c \ + unix/drawpath.c \ + unix/drawtext.c \ unix/entry.c \ unix/fontbutton.c \ unix/group.c \ @@ -30,6 +33,7 @@ CFILES += \ unix/window.c HFILES += \ + unix/draw.h \ unix/uipriv_unix.h # TODO split into a separate file or put in GNUmakefile.libui somehow? diff --git a/unix/draw.c b/unix/draw.c index 4ea592ef..11076fc5 100644 --- a/unix/draw.c +++ b/unix/draw.c @@ -1,200 +1,6 @@ // 6 september 2015 #include "uipriv_unix.h" - -struct uiDrawPath { - GArray *pieces; - uiDrawFillMode fillMode; - gboolean ended; -}; - -struct piece { - int type; - double d[8]; - int b; -}; - -enum { - newFigure, - newFigureArc, - lineTo, - arcTo, - bezierTo, - closeFigure, - addRect, -}; - -uiDrawPath *uiDrawNewPath(uiDrawFillMode mode) -{ - uiDrawPath *p; - - p = uiNew(uiDrawPath); - p->pieces = g_array_new(FALSE, TRUE, sizeof (struct piece)); - p->fillMode = mode; - return p; -} - -void uiDrawFreePath(uiDrawPath *p) -{ - g_array_free(p->pieces, TRUE); - uiFree(p); -} - -static void add(uiDrawPath *p, struct piece *piece) -{ - if (p->ended) - complain("path ended in add()"); - g_array_append_vals(p->pieces, piece, 1); -} - -void uiDrawPathNewFigure(uiDrawPath *p, double x, double y) -{ - struct piece piece; - - piece.type = newFigure; - piece.d[0] = x; - piece.d[1] = y; - add(p, &piece); -} - -void uiDrawPathNewFigureWithArc(uiDrawPath *p, double xCenter, double yCenter, double radius, double startAngle, double sweep, int negative) -{ - struct piece piece; - - if (sweep > 2 * M_PI) - sweep = 2 * M_PI; - piece.type = newFigureArc; - piece.d[0] = xCenter; - piece.d[1] = yCenter; - piece.d[2] = radius; - piece.d[3] = startAngle; - piece.d[4] = sweep; - piece.b = negative; - add(p, &piece); -} - -void uiDrawPathLineTo(uiDrawPath *p, double x, double y) -{ - struct piece piece; - - piece.type = lineTo; - piece.d[0] = x; - piece.d[1] = y; - add(p, &piece); -} - -void uiDrawPathArcTo(uiDrawPath *p, double xCenter, double yCenter, double radius, double startAngle, double sweep, int negative) -{ - struct piece piece; - - if (sweep > 2 * M_PI) - sweep = 2 * M_PI; - piece.type = arcTo; - piece.d[0] = xCenter; - piece.d[1] = yCenter; - piece.d[2] = radius; - piece.d[3] = startAngle; - piece.d[4] = sweep; - piece.b = negative; - add(p, &piece); -} - -void uiDrawPathBezierTo(uiDrawPath *p, double c1x, double c1y, double c2x, double c2y, double endX, double endY) -{ - struct piece piece; - - piece.type = bezierTo; - piece.d[0] = c1x; - piece.d[1] = c1y; - piece.d[2] = c2x; - piece.d[3] = c2y; - piece.d[4] = endX; - piece.d[5] = endY; - add(p, &piece); -} - -void uiDrawPathCloseFigure(uiDrawPath *p) -{ - struct piece piece; - - piece.type = closeFigure; - add(p, &piece); -} - -void uiDrawPathAddRectangle(uiDrawPath *p, double x, double y, double width, double height) -{ - struct piece piece; - - piece.type = addRect; - piece.d[0] = x; - piece.d[1] = y; - piece.d[2] = width; - piece.d[3] = height; - add(p, &piece); -} - -void uiDrawPathEnd(uiDrawPath *p) -{ - p->ended = TRUE; -} - -static void runPath(uiDrawPath *p, cairo_t *cr) -{ - guint i; - struct piece *piece; - void (*arc)(cairo_t *, double, double, double, double, double); - - if (!p->ended) - complain("path not ended in runPath()"); - cairo_new_path(cr); - for (i = 0; i < p->pieces->len; i++) { - piece = &g_array_index(p->pieces, struct piece, i); - switch (piece->type) { - case newFigure: - cairo_move_to(cr, piece->d[0], piece->d[1]); - break; - case newFigureArc: - cairo_new_sub_path(cr); - // fall through - case arcTo: - arc = cairo_arc; - if (piece->b) - arc = cairo_arc_negative; - (*arc)(cr, - piece->d[0], - piece->d[1], - piece->d[2], - piece->d[3], - piece->d[3] + piece->d[4]); - break; - case lineTo: - cairo_line_to(cr, piece->d[0], piece->d[1]); - break; - case bezierTo: - cairo_curve_to(cr, - piece->d[0], - piece->d[1], - piece->d[2], - piece->d[3], - piece->d[4], - piece->d[5]); - break; - case closeFigure: - cairo_close_path(cr); - break; - case addRect: - cairo_rectangle(cr, - piece->d[0], - piece->d[1], - piece->d[2], - piece->d[3]); - break; - } - } -} - -struct uiDrawContext { - cairo_t *cr; -}; +#include "draw.h" uiDrawContext *newContext(cairo_t *cr) { @@ -290,7 +96,7 @@ void uiDrawFill(uiDrawContext *c, uiDrawPath *path, uiDrawBrush *b) runPath(path, c->cr); pat = mkbrush(b); cairo_set_source(c->cr, pat); - switch (path->fillMode) { + switch (pathFillMode(path)) { case uiDrawFillModeWinding: cairo_set_fill_rule(c->cr, CAIRO_FILL_RULE_WINDING); break; @@ -302,131 +108,10 @@ void uiDrawFill(uiDrawContext *c, uiDrawPath *path, uiDrawBrush *b) cairo_pattern_destroy(pat); } -static void m2c(uiDrawMatrix *m, cairo_matrix_t *c) -{ - c->xx = m->M11; - c->yx = m->M12; - c->xy = m->M21; - c->yy = m->M22; - c->x0 = m->M31; - c->y0 = m->M32; -} - -static void c2m(cairo_matrix_t *c, uiDrawMatrix *m) -{ - m->M11 = c->xx; - m->M12 = c->yx; - m->M21 = c->xy; - m->M22 = c->yy; - m->M31 = c->x0; - m->M32 = c->y0; -} - -void uiDrawMatrixSetIdentity(uiDrawMatrix *m) -{ - setIdentity(m); -} - -void uiDrawMatrixTranslate(uiDrawMatrix *m, double x, double y) -{ - cairo_matrix_t c; - - m2c(m, &c); - cairo_matrix_translate(&c, x, y); - c2m(&c, m); -} - -void uiDrawMatrixScale(uiDrawMatrix *m, double xCenter, double yCenter, double x, double y) -{ - cairo_matrix_t c; - double xt, yt; - - m2c(m, &c); - // TODO explain why the translation must come first - xt = x; - yt = y; - scaleCenter(xCenter, yCenter, &xt, &yt); - cairo_matrix_translate(&c, xt, yt); - cairo_matrix_scale(&c, x, y); - // TODO undo the translation? - c2m(&c, m); -} - -void uiDrawMatrixRotate(uiDrawMatrix *m, double x, double y, double amount) -{ - cairo_matrix_t c; - - m2c(m, &c); - cairo_matrix_translate(&c, x, y); - cairo_matrix_rotate(&c, amount); - // TODO undo the translation? also cocoa backend - cairo_matrix_translate(&c, -x, -y); - c2m(&c, m); -} - -void uiDrawMatrixSkew(uiDrawMatrix *m, double x, double y, double xamount, double yamount) -{ - fallbackSkew(m, x, y, xamount, yamount); -} - -void uiDrawMatrixMultiply(uiDrawMatrix *dest, uiDrawMatrix *src) -{ - cairo_matrix_t c; - cairo_matrix_t d; - - m2c(dest, &c); - m2c(src, &d); - cairo_matrix_multiply(&c, &c, &d); - c2m(&c, dest); -} - -int uiDrawMatrixInvertible(uiDrawMatrix *m) -{ - cairo_matrix_t c; - - m2c(m, &c); - return cairo_matrix_invert(&c) == CAIRO_STATUS_SUCCESS; -} - -int uiDrawMatrixInvert(uiDrawMatrix *m) -{ - cairo_matrix_t c; - - m2c(m, &c); - if (cairo_matrix_invert(&c) != CAIRO_STATUS_SUCCESS) - return 0; - c2m(&c, m); - return 1; -} - -void uiDrawMatrixTransformPoint(uiDrawMatrix *m, double *x, double *y) -{ - cairo_matrix_t c; - - m2c(m, &c); - cairo_matrix_transform_point(&c, x, y); -} - -void uiDrawMatrixTransformSize(uiDrawMatrix *m, double *x, double *y) -{ - cairo_matrix_t c; - - m2c(m, &c); - cairo_matrix_transform_distance(&c, x, y); -} - -void uiDrawTransform(uiDrawContext *c, uiDrawMatrix *m) -{ - cairo_matrix_t cm; - - m2c(m, &cm); - cairo_transform(c->cr, &cm); -} - void uiDrawClip(uiDrawContext *c, uiDrawPath *path) { runPath(path, c->cr); - switch (path->fillMode) { + switch (pathFillMode(path)) { case uiDrawFillModeWinding: cairo_set_fill_rule(c->cr, CAIRO_FILL_RULE_WINDING); break; @@ -436,348 +121,3 @@ void uiDrawClip(uiDrawContext *c, uiDrawPath *path) } cairo_clip(c->cr); } - -void uiDrawSave(uiDrawContext *c) -{ - cairo_save(c->cr); -} - -void uiDrawRestore(uiDrawContext *c) -{ - cairo_restore(c->cr); -} - -// TODO split everything after this into a drawtext.c - -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; -//TODO PangoVariant variant; - - 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]); -#if 0 -TODO - variant = PANGO_VARIANT_NORMAL; - if (desc->SmallCaps) - variant = PANGO_VARIANT_SMALL_CAPS; - pango_font_description_set_variant(pdesc, variant); -#endif - 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); - } -} diff --git a/unix/draw.h b/unix/draw.h new file mode 100644 index 00000000..0619fd53 --- /dev/null +++ b/unix/draw.h @@ -0,0 +1,10 @@ +// 5 may 2016 + +// draw.c +struct uiDrawContext { + cairo_t *cr; +}; + +// drawpath.c +extern void runPath(uiDrawPath *p, cairo_t *cr); +extern uiDrawFillMode pathFillMode(uiDrawPath *path); diff --git a/unix/drawmatrix.c b/unix/drawmatrix.c new file mode 100644 index 00000000..99da9944 --- /dev/null +++ b/unix/drawmatrix.c @@ -0,0 +1,134 @@ +// 6 september 2015 +#include "uipriv_unix.h" +#include "draw.h" + +static void m2c(uiDrawMatrix *m, cairo_matrix_t *c) +{ + c->xx = m->M11; + c->yx = m->M12; + c->xy = m->M21; + c->yy = m->M22; + c->x0 = m->M31; + c->y0 = m->M32; +} + +static void c2m(cairo_matrix_t *c, uiDrawMatrix *m) +{ + m->M11 = c->xx; + m->M12 = c->yx; + m->M21 = c->xy; + m->M22 = c->yy; + m->M31 = c->x0; + m->M32 = c->y0; +} + +void uiDrawMatrixSetIdentity(uiDrawMatrix *m) +{ + setIdentity(m); +} + +void uiDrawMatrixTranslate(uiDrawMatrix *m, double x, double y) +{ + cairo_matrix_t c; + + m2c(m, &c); + cairo_matrix_translate(&c, x, y); + c2m(&c, m); +} + +void uiDrawMatrixScale(uiDrawMatrix *m, double xCenter, double yCenter, double x, double y) +{ + cairo_matrix_t c; + double xt, yt; + + m2c(m, &c); + // TODO explain why the translation must come first + xt = x; + yt = y; + scaleCenter(xCenter, yCenter, &xt, &yt); + cairo_matrix_translate(&c, xt, yt); + cairo_matrix_scale(&c, x, y); + // TODO undo the translation? + c2m(&c, m); +} + +void uiDrawMatrixRotate(uiDrawMatrix *m, double x, double y, double amount) +{ + cairo_matrix_t c; + + m2c(m, &c); + cairo_matrix_translate(&c, x, y); + cairo_matrix_rotate(&c, amount); + // TODO undo the translation? also cocoa backend + cairo_matrix_translate(&c, -x, -y); + c2m(&c, m); +} + +void uiDrawMatrixSkew(uiDrawMatrix *m, double x, double y, double xamount, double yamount) +{ + fallbackSkew(m, x, y, xamount, yamount); +} + +void uiDrawMatrixMultiply(uiDrawMatrix *dest, uiDrawMatrix *src) +{ + cairo_matrix_t c; + cairo_matrix_t d; + + m2c(dest, &c); + m2c(src, &d); + cairo_matrix_multiply(&c, &c, &d); + c2m(&c, dest); +} + +int uiDrawMatrixInvertible(uiDrawMatrix *m) +{ + cairo_matrix_t c; + + m2c(m, &c); + return cairo_matrix_invert(&c) == CAIRO_STATUS_SUCCESS; +} + +int uiDrawMatrixInvert(uiDrawMatrix *m) +{ + cairo_matrix_t c; + + m2c(m, &c); + if (cairo_matrix_invert(&c) != CAIRO_STATUS_SUCCESS) + return 0; + c2m(&c, m); + return 1; +} + +void uiDrawMatrixTransformPoint(uiDrawMatrix *m, double *x, double *y) +{ + cairo_matrix_t c; + + m2c(m, &c); + cairo_matrix_transform_point(&c, x, y); +} + +void uiDrawMatrixTransformSize(uiDrawMatrix *m, double *x, double *y) +{ + cairo_matrix_t c; + + m2c(m, &c); + cairo_matrix_transform_distance(&c, x, y); +} + +void uiDrawTransform(uiDrawContext *c, uiDrawMatrix *m) +{ + cairo_matrix_t cm; + + m2c(m, &cm); + cairo_transform(c->cr, &cm); +} + +void uiDrawSave(uiDrawContext *c) +{ + cairo_save(c->cr); +} + +void uiDrawRestore(uiDrawContext *c) +{ + cairo_restore(c->cr); +} diff --git a/unix/drawpath.c b/unix/drawpath.c new file mode 100644 index 00000000..1204315b --- /dev/null +++ b/unix/drawpath.c @@ -0,0 +1,199 @@ +// 6 september 2015 +#include "uipriv_unix.h" +#include "draw.h" + +struct uiDrawPath { + GArray *pieces; + uiDrawFillMode fillMode; + gboolean ended; +}; + +struct piece { + int type; + double d[8]; + int b; +}; + +enum { + newFigure, + newFigureArc, + lineTo, + arcTo, + bezierTo, + closeFigure, + addRect, +}; + +uiDrawPath *uiDrawNewPath(uiDrawFillMode mode) +{ + uiDrawPath *p; + + p = uiNew(uiDrawPath); + p->pieces = g_array_new(FALSE, TRUE, sizeof (struct piece)); + p->fillMode = mode; + return p; +} + +void uiDrawFreePath(uiDrawPath *p) +{ + g_array_free(p->pieces, TRUE); + uiFree(p); +} + +static void add(uiDrawPath *p, struct piece *piece) +{ + if (p->ended) + complain("path ended in add()"); + g_array_append_vals(p->pieces, piece, 1); +} + +void uiDrawPathNewFigure(uiDrawPath *p, double x, double y) +{ + struct piece piece; + + piece.type = newFigure; + piece.d[0] = x; + piece.d[1] = y; + add(p, &piece); +} + +void uiDrawPathNewFigureWithArc(uiDrawPath *p, double xCenter, double yCenter, double radius, double startAngle, double sweep, int negative) +{ + struct piece piece; + + if (sweep > 2 * M_PI) + sweep = 2 * M_PI; + piece.type = newFigureArc; + piece.d[0] = xCenter; + piece.d[1] = yCenter; + piece.d[2] = radius; + piece.d[3] = startAngle; + piece.d[4] = sweep; + piece.b = negative; + add(p, &piece); +} + +void uiDrawPathLineTo(uiDrawPath *p, double x, double y) +{ + struct piece piece; + + piece.type = lineTo; + piece.d[0] = x; + piece.d[1] = y; + add(p, &piece); +} + +void uiDrawPathArcTo(uiDrawPath *p, double xCenter, double yCenter, double radius, double startAngle, double sweep, int negative) +{ + struct piece piece; + + if (sweep > 2 * M_PI) + sweep = 2 * M_PI; + piece.type = arcTo; + piece.d[0] = xCenter; + piece.d[1] = yCenter; + piece.d[2] = radius; + piece.d[3] = startAngle; + piece.d[4] = sweep; + piece.b = negative; + add(p, &piece); +} + +void uiDrawPathBezierTo(uiDrawPath *p, double c1x, double c1y, double c2x, double c2y, double endX, double endY) +{ + struct piece piece; + + piece.type = bezierTo; + piece.d[0] = c1x; + piece.d[1] = c1y; + piece.d[2] = c2x; + piece.d[3] = c2y; + piece.d[4] = endX; + piece.d[5] = endY; + add(p, &piece); +} + +void uiDrawPathCloseFigure(uiDrawPath *p) +{ + struct piece piece; + + piece.type = closeFigure; + add(p, &piece); +} + +void uiDrawPathAddRectangle(uiDrawPath *p, double x, double y, double width, double height) +{ + struct piece piece; + + piece.type = addRect; + piece.d[0] = x; + piece.d[1] = y; + piece.d[2] = width; + piece.d[3] = height; + add(p, &piece); +} + +void uiDrawPathEnd(uiDrawPath *p) +{ + p->ended = TRUE; +} + +void runPath(uiDrawPath *p, cairo_t *cr) +{ + guint i; + struct piece *piece; + void (*arc)(cairo_t *, double, double, double, double, double); + + if (!p->ended) + complain("path not ended in runPath()"); + cairo_new_path(cr); + for (i = 0; i < p->pieces->len; i++) { + piece = &g_array_index(p->pieces, struct piece, i); + switch (piece->type) { + case newFigure: + cairo_move_to(cr, piece->d[0], piece->d[1]); + break; + case newFigureArc: + cairo_new_sub_path(cr); + // fall through + case arcTo: + arc = cairo_arc; + if (piece->b) + arc = cairo_arc_negative; + (*arc)(cr, + piece->d[0], + piece->d[1], + piece->d[2], + piece->d[3], + piece->d[3] + piece->d[4]); + break; + case lineTo: + cairo_line_to(cr, piece->d[0], piece->d[1]); + break; + case bezierTo: + cairo_curve_to(cr, + piece->d[0], + piece->d[1], + piece->d[2], + piece->d[3], + piece->d[4], + piece->d[5]); + break; + case closeFigure: + cairo_close_path(cr); + break; + case addRect: + cairo_rectangle(cr, + piece->d[0], + piece->d[1], + piece->d[2], + piece->d[3]); + break; + } + } +} + +uiDrawFillMode pathFillMode(uiDrawPath *path) +{ + return path->fillMode; +} diff --git a/unix/drawtext.c b/unix/drawtext.c new file mode 100644 index 00000000..597f2643 --- /dev/null +++ b/unix/drawtext.c @@ -0,0 +1,328 @@ +// 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); + } +}