diff --git a/common/opentype.c b/common/opentype.c index aa0a389d..a3e4f852 100644 --- a/common/opentype.c +++ b/common/opentype.c @@ -5,8 +5,6 @@ // Notes: // - Each tag should only appear in quotes once (including within comments); this allows automated tools to determine what we cover and don't cover -typedef void (*specToOpenTypeEnumFunc)(const char *featureTag, uint32_t param, void *data); - static void boolspec(uiAttributeSpec *spec, const char *featureTag, specToOpenTypeEnumFunc f, void *data) { if (spec->Value != 0) { diff --git a/common/uipriv.h b/common/uipriv.h index 553073f5..1c61f9c7 100644 --- a/common/uipriv.h +++ b/common/uipriv.h @@ -101,6 +101,10 @@ struct caretDrawParams { extern void caretDrawParams(uiDrawContext *c, double height, struct caretDrawParams *p); extern void drawTextBackground(uiDrawContext *c, double x, double y, uiDrawTextLayout *layout, size_t start, size_t end, uiDrawBrush *brush, int isSelection); +// opentype.c +typedef void (*specToOpenTypeEnumFunc)(const char *featureTag, uint32_t param, void *data); +extern void specToOpenType(uiAttributeSpec *spec, specToOpenTypeEnumFunc f, void *data); + #ifdef __cplusplus } #endif diff --git a/unix/CMakeLists.txt b/unix/CMakeLists.txt index 9300bcb7..949c4a11 100644 --- a/unix/CMakeLists.txt +++ b/unix/CMakeLists.txt @@ -6,6 +6,7 @@ pkg_check_modules(GTK REQUIRED gtk+-3.0) list(APPEND _LIBUI_SOURCES unix/alloc.c unix/area.c + unix/attrstr.c unix/box.c unix/button.c unix/cellrendererbutton.c diff --git a/unix/attrstr.c b/unix/attrstr.c new file mode 100644 index 00000000..12cbb12d --- /dev/null +++ b/unix/attrstr.c @@ -0,0 +1,254 @@ +// 12 february 2017 +#include "uipriv_unix.h" + +// we need to collect all the OpenType features and background blocks and add them all at once +// TODO rename this struct to something that isn't exclusively foreach-ing? +struct foreachParams { + PangoAttrList *attrs; + // keys are pointers to size_t maintained by g_new0()/g_free() + // values are GStrings + GHashTable *features; +//TODO GArray *backgroundBlocks; +}; + +static gboolean featurePosEqual(gconstpointer a, gconstpointer b) +{ + size_t *sa = (size_t *) a; + size_t *sb = (size_t *) b; + + return *sa == *sb; +} + +static guint featurePosHash(gconstpointer n) +{ + size_t *sn = (size_t *) n; + + return (guint) (*sn); +} + +static void freeFeatureString(gpointer s) +{ + g_string_free((GString *) s, TRUE); +} + +static void ensureFeaturesInRange(struct foreachParams *p, size_t start, size_t end) +{ + size_t i; + size_t *key; + GString *new; + + for (i = start; i < end; i++) { + new = (GString *) g_hash_table_lookup(p->features, &i); + if (new != NULL) + continue; + new = g_string_new(""); + key = g_new0(size_t, 1); + *key = i; + g_hash_table_replace(p->features, key, new); + } +} + +#if 0 /* TODO */ +static backgroundBlock mkBackgroundBlock(size_t start, size_t end, double r, double g, double b, double a) +{ + return Block_copy(^(uiDrawContext *c, uiDrawTextLayout *layout, double x, double y) { + uiDrawBrush brush; + + brush.Type = uiDrawBrushTypeSolid; + brush.R = r; + brush.G = g; + brush.B = b; + brush.A = a; + drawTextBackground(c, x, y, layout, start, end, &brush, 0); + }); +} +#endif + +struct otParam { + struct foreachParams *p; + size_t start; + size_t end; +}; + +// see https://developer.mozilla.org/en/docs/Web/CSS/font-feature-settings +static void doOpenType(const char *featureTag, uint32_t param, void *data) +{ + struct otParam *p = (struct otParam *) data; + size_t i; + GString *s; + + ensureFeaturesInRange(p->p, p->start, p->end); + for (i = p->start; i < p->end; i++) { + s = (GString *) g_hash_table_lookup(p->p->features, &i); + g_string_append_printf(s, "\"%s\" %" PRIu32 ", ", + featureTag, param); + } +} + +static void addattr(struct foreachParams *p, size_t start, size_t end, PangoAttribute *attr) +{ + if (attr == NULL) // in case of a future attribute + return; + attr->start_index = start; + attr->end_index = end; + pango_attr_list_insert(p->attrs, attr); +} + +static int processAttribute(uiAttributedString *s, uiAttributeSpec *spec, size_t start, size_t end, void *data) +{ + struct foreachParams *p = (struct foreachParams *) data; +//TODO backgroundBlock block; + PangoGravity gravity; + PangoUnderline underline; + PangoLanguage *lang; + struct otParam op; + + switch (spec->Type) { + case uiAttributeFamily: + addattr(p, start, end, + pango_attr_family_new((const char *) (spec->Value))); + break; +#if 0 /* TODO */ + case uiAttributeSize: + addattr(p, start, end, + pango_attr_size_new(cairoToPango(spec->Double))); + break; + case uiAttributeWeight: + ensureFontInRange(p, start, end); + adjustFontInRange(p, start, end, ^(struct fontParams *fp) { + fp->desc.Weight = (uiDrawTextWeight) (spec->Value); + }); + break; + case uiAttributeItalic: + ensureFontInRange(p, start, end); + adjustFontInRange(p, start, end, ^(struct fontParams *fp) { + fp->desc.Italic = (uiDrawTextItalic) (spec->Value); + }); + break; + case uiAttributeStretch: + ensureFontInRange(p, start, end); + adjustFontInRange(p, start, end, ^(struct fontParams *fp) { + fp->desc.Stretch = (uiDrawTextStretch) (spec->Value); + }); + break; +#endif + case uiAttributeColor: + addattr(p, start, end, + pango_attr_foreground_new( + (guint16) (spec->R * 65535.0), + (guint16) (spec->G * 65535.0), + (guint16) (spec->B * 65535.0))); + addattr(p, start, end, + FUTURE_pango_attr_foreground_alpha_new( + (guint16) (spec->A * 65535.0))); + break; +#if 0 /* TODO */ + case uiAttributeBackground: + block = mkBackgroundBlock(ostart, oend, + spec->R, spec->G, spec->B, spec->A); + [p->backgroundBlocks addObject:block]; + Block_release(block); + break; +#endif + case uiAttributeVerticalForms: + gravity = PANGO_GRAVITY_SOUTH; + if (spec->Value != 0) + gravity = PANGO_GRAVITY_EAST; + addattr(p, start, end, + pango_attr_gravity_new(gravity)); + break; + case uiAttributeUnderline: + switch (spec->Value) { + case uiDrawUnderlineStyleNone: + underline = PANGO_UNDERLINE_NONE; + break; + case uiDrawUnderlineStyleSingle: + underline = PANGO_UNDERLINE_SINGLE; + break; + case uiDrawUnderlineStyleDouble: + underline = PANGO_UNDERLINE_DOUBLE; + break; + case uiDrawUnderlineStyleSuggestion: + underline = PANGO_UNDERLINE_ERROR; + break; + } + addattr(p, start, end, + pango_attr_underline_new(underline)); + break; + case uiAttributeUnderlineColor: + switch (spec->Value) { + case uiDrawUnderlineColorCustom: + addattr(p, start, end, + pango_attr_underline_color_new( + (guint16) (spec->R * 65535.0), + (guint16) (spec->G * 65535.0), + (guint16) (spec->B * 65535.0))); + break; + case uiDrawUnderlineColorSpelling: + // TODO GtkTextView style property error-underline-color + addattr(p, start, end, + pango_attr_underline_color_new(65535, 0, 0)); + break; + case uiDrawUnderlineColorGrammar: + // TODO find a more appropriate color + addattr(p, start, end, + pango_attr_underline_color_new(0, 65535, 0)); + break; + case uiDrawUnderlineColorAuxiliary: + // TODO find a more appropriate color + addattr(p, start, end, + pango_attr_underline_color_new(0, 0, 65535)); + break; + } + break; + // language strings are specified as BCP 47: https://developer.gnome.org/pango/1.30/pango-Scripts-and-Languages.html#pango-language-from-string https://www.ietf.org/rfc/rfc3066.txt + case uiAttributeLanguage: + lang = pango_language_from_string((const char *) (spec->Value)); + addattr(p, start, end, + pango_attr_language_new(lang)); + // lang *cannot* be freed + break; + // TODO + default: + // handle typographic features + op.p = p; + op.start = start; + op.end = end; + // TODO check if unhandled and complain + specToOpenType(spec, doOpenType, &op); + break; + } + return 0; +} + +static gboolean applyFeatures(gpointer key, gpointer value, gpointer data) +{ + struct foreachParams *p = (struct foreachParams *) data; + size_t *pos = (size_t *) key; + GString *s = (GString *) value; + + // remove the trailing comma/space + g_string_truncate(s, s->len - 2); + addattr(p, *pos, 1, + FUTURE_pango_attr_font_features_new(s->str)); + return TRUE; // always delete; we're emptying the map +} + +static void applyAndFreeFeatureAttributes(struct foreachParams *p) +{ + g_hash_table_foreach_remove(p->features, applyFeatures, p); + g_hash_table_destroy(p->features); +} + +PangoAttrList *attrstrToPangoAttrList(uiDrawTextLayoutParams *p/*TODO, NSArray **backgroundBlocks*/) +{ + struct foreachParams fep; + + fep.attrs = pango_attr_list_new(); + fep.features = g_hash_table_new_full( + featurePosHash, featurePosEqual, + g_free, freeFeatureString); + uiAttributedStringForEachAttribute(p->String, processAttribute, &fep); + applyAndFreeFeatureAttributes(&fep); + return fep.attrs; +} diff --git a/unix/future.c b/unix/future.c index 1f9f532b..3d63e9e0 100644 --- a/unix/future.c +++ b/unix/future.c @@ -6,6 +6,7 @@ // in others, because parts of GTK+ being unstable until recently also sucks :/ // added in pango 1.38; we need 1.36 +static PangoAttribute *(*newFeaturesAttr)(const gchar *features) = NULL; static PangoAttribute *(*newFGAlphaAttr)(guint16 alpha) = NULL; // added in GTK+ 3.20; we need 3.10 @@ -21,11 +22,19 @@ void loadFutures(void) if (handle == NULL) return; #define GET(var, fn) *((void **) (&var)) = dlsym(handle, #fn) + GET(newFeaturesAttr, pango_attr_font_features_new); GET(newFGAlphaAttr, pango_attr_foreground_alpha_new); GET(gwpIterSetObjectName, gtk_widget_path_iter_set_object_name); dlclose(handle); } +PangoAttribute *FUTURE_pango_attr_font_features_new(const gchar *features) +{ + if (newFeaturesAttr == NULL) + return NULL; + return (*newFeaturesAttr)(features); +} + PangoAttribute *FUTURE_pango_attr_foreground_alpha_new(guint16 alpha) { if (newFGAlphaAttr == NULL) diff --git a/unix/uipriv_unix.h b/unix/uipriv_unix.h index aaae8b34..d2ef5bf0 100644 --- a/unix/uipriv_unix.h +++ b/unix/uipriv_unix.h @@ -9,6 +9,7 @@ #include #include #include +#include #include "../ui.h" #include "../ui_unix.h" #include "../common/uipriv.h" @@ -54,8 +55,12 @@ extern GtkCellRenderer *newCellRendererButton(void); // future.c extern void loadFutures(void); +extern PangoAttribute *FUTURE_pango_attr_font_features_new(const gchar *features); extern PangoAttribute *FUTURE_pango_attr_foreground_alpha_new(guint16 alpha); extern gboolean FUTURE_gtk_widget_path_iter_set_object_name(GtkWidgetPath *path, gint pos, const char *name); // drawtext.c extern void fontdescFromPangoFontDescription(PangoFontDescription *pdesc, uiDrawFontDescriptor *uidesc); + +// attrstr.c +extern PangoAttrList *attrstrToPangoAttrList(uiDrawTextLayoutParams *p/*TODO, NSArray **backgroundBlocks*/);