/* * Glyph shaping support * * Copyright 2010 Aric Stewart for CodeWeavers * Copyright 2014 Nikolay Sivov for CodeWeavers * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA * * * Shaping features processing, default features, and lookups handling logic are * derived from HarfBuzz implementation. Consult project documentation for the full * list of its authors. */ #define COBJMACROS #include "dwrite_private.h" #include "winternl.h" #include "wine/debug.h" WINE_DEFAULT_DEBUG_CHANNEL(dwrite); #ifdef WORDS_BIGENDIAN #define GET_BE_DWORD(x) (x) #else #define GET_BE_DWORD(x) RtlUlongByteSwap(x) #endif struct scriptshaping_cache *create_scriptshaping_cache(void *context, const struct shaping_font_ops *font_ops) { struct scriptshaping_cache *cache; cache = heap_alloc_zero(sizeof(*cache)); if (!cache) return NULL; cache->font = font_ops; cache->context = context; opentype_layout_scriptshaping_cache_init(cache); cache->upem = cache->font->get_font_upem(cache->context); return cache; } void release_scriptshaping_cache(struct scriptshaping_cache *cache) { if (!cache) return; cache->font->release_font_table(cache->context, cache->gdef.table.context); cache->font->release_font_table(cache->context, cache->gsub.table.context); cache->font->release_font_table(cache->context, cache->gpos.table.context); heap_free(cache); } static unsigned int shape_select_script(const struct scriptshaping_cache *cache, DWORD kind, const DWORD *scripts, unsigned int *script_index) { static const DWORD fallback_scripts[] = { DWRITE_MAKE_OPENTYPE_TAG('D','F','L','T'), DWRITE_MAKE_OPENTYPE_TAG('d','f','l','t'), DWRITE_MAKE_OPENTYPE_TAG('l','a','t','n'), 0, }; unsigned int script; /* Passed scripts in ascending priority. */ while (scripts && *scripts) { if ((script = opentype_layout_find_script(cache, kind, *scripts, script_index))) return script; scripts++; } /* 'DFLT' -> 'dflt' -> 'latn' */ scripts = fallback_scripts; while (*scripts) { if ((script = opentype_layout_find_script(cache, kind, *scripts, script_index))) return script; scripts++; } return 0; } static DWORD shape_select_language(const struct scriptshaping_cache *cache, DWORD kind, unsigned int script_index, DWORD language, unsigned int *language_index) { /* Specified language -> 'dflt'. */ if ((language = opentype_layout_find_language(cache, kind, language, script_index, language_index))) return language; if ((language = opentype_layout_find_language(cache, kind, DWRITE_MAKE_OPENTYPE_TAG('d','f','l','t'), script_index, language_index))) return language; return 0; } static void shape_add_feature_full(struct shaping_features *features, unsigned int tag, unsigned int flags, unsigned int value) { unsigned int i = features->count; if (!dwrite_array_reserve((void **)&features->features, &features->capacity, features->count + 1, sizeof(*features->features))) return; features->features[i].tag = tag; features->features[i].flags = flags; features->features[i].max_value = value; features->features[i].default_value = flags & FEATURE_GLOBAL ? value : 0; features->features[i].stage = features->stage; features->count++; } static void shape_add_feature(struct shaping_features *features, unsigned int tag) { shape_add_feature_full(features, tag, FEATURE_GLOBAL, 1); } static int features_sorting_compare(const void *a, const void *b) { const struct shaping_feature *left = a, *right = b; return left->tag != right->tag ? (left->tag < right->tag ? -1 : 1) : 0; }; static void shape_merge_features(struct scriptshaping_context *context, struct shaping_features *features) { const DWRITE_TYPOGRAPHIC_FEATURES **user_features = context->user_features.features; unsigned int i, j; /* For now only consider global, enabled user features. */ if (user_features && context->user_features.range_lengths) { unsigned int flags = context->user_features.range_count == 1 && context->user_features.range_lengths[0] == context->length ? FEATURE_GLOBAL : 0; for (i = 0; i < context->user_features.range_count; ++i) { for (j = 0; j < user_features[i]->featureCount; ++j) shape_add_feature_full(features, user_features[i]->features[j].nameTag, flags, user_features[i]->features[j].parameter); } } /* Sort and merge duplicates. */ qsort(features->features, features->count, sizeof(*features->features), features_sorting_compare); for (i = 1, j = 0; i < features->count; ++i) { if (features->features[i].tag != features->features[j].tag) features->features[++j] = features->features[i]; else { if (features->features[i].flags & FEATURE_GLOBAL) { features->features[j].flags |= FEATURE_GLOBAL; features->features[j].max_value = features->features[i].max_value; features->features[j].default_value = features->features[i].default_value; } else { if (features->features[j].flags & FEATURE_GLOBAL) features->features[j].flags ^= FEATURE_GLOBAL; features->features[j].max_value = max(features->features[j].max_value, features->features[i].max_value); } features->features[j].stage = min(features->features[j].stage, features->features[i].stage); } } features->count = j + 1; } HRESULT shape_get_positions(struct scriptshaping_context *context, const unsigned int *scripts) { static const unsigned int common_features[] = { DWRITE_MAKE_OPENTYPE_TAG('a','b','v','m'), DWRITE_MAKE_OPENTYPE_TAG('b','l','w','m'), DWRITE_MAKE_OPENTYPE_TAG('m','a','r','k'), DWRITE_MAKE_OPENTYPE_TAG('m','k','m','k'), }; static const unsigned int horizontal_features[] = { DWRITE_MAKE_OPENTYPE_TAG('c','u','r','s'), DWRITE_MAKE_OPENTYPE_TAG('d','i','s','t'), DWRITE_MAKE_OPENTYPE_TAG('k','e','r','n'), }; struct scriptshaping_cache *cache = context->cache; unsigned int script_index, language_index, script, i; struct shaping_features features = { 0 }; for (i = 0; i < ARRAY_SIZE(common_features); ++i) shape_add_feature(&features, common_features[i]); /* Horizontal features */ if (!context->is_sideways) { for (i = 0; i < ARRAY_SIZE(horizontal_features); ++i) shape_add_feature(&features, horizontal_features[i]); } shape_merge_features(context, &features); /* Resolve script tag to actually supported script. */ if (cache->gpos.table.data) { if ((script = shape_select_script(cache, MS_GPOS_TAG, scripts, &script_index))) { DWORD language = context->language_tag; if ((language = shape_select_language(cache, MS_GPOS_TAG, script_index, language, &language_index))) { TRACE("script %s, language %s.\n", debugstr_tag(script), language != ~0u ? debugstr_tag(language) : "deflangsys"); opentype_layout_apply_gpos_features(context, script_index, language_index, &features); } } } for (i = 0; i < context->glyph_count; ++i) if (context->u.pos.glyph_props[i].isZeroWidthSpace) context->advances[i] = 0.0f; heap_free(features.features); return S_OK; } static unsigned int shape_get_script_lang_index(struct scriptshaping_context *context, const unsigned int *scripts, unsigned int table, unsigned int *script_index, unsigned int *language_index) { unsigned int script; /* Resolve script tag to actually supported script. */ if ((script = shape_select_script(context->cache, table, scripts, script_index))) { unsigned int language = context->language_tag; if ((language = shape_select_language(context->cache, table, *script_index, language, language_index))) return script; } return 0; } HRESULT shape_get_glyphs(struct scriptshaping_context *context, const unsigned int *scripts) { static const unsigned int common_features[] = { DWRITE_MAKE_OPENTYPE_TAG('c','c','m','p'), DWRITE_MAKE_OPENTYPE_TAG('l','o','c','l'), DWRITE_MAKE_OPENTYPE_TAG('r','l','i','g'), }; static const unsigned int horizontal_features[] = { DWRITE_MAKE_OPENTYPE_TAG('c','a','l','t'), DWRITE_MAKE_OPENTYPE_TAG('c','l','i','g'), DWRITE_MAKE_OPENTYPE_TAG('l','i','g','a'), DWRITE_MAKE_OPENTYPE_TAG('r','c','l','t'), }; unsigned int script_index, language_index; struct shaping_features features = { 0 }; unsigned int i; if (!context->is_sideways) { if (context->is_rtl) { shape_add_feature(&features, DWRITE_MAKE_OPENTYPE_TAG('r','t','l','a')); shape_add_feature_full(&features, DWRITE_MAKE_OPENTYPE_TAG('r','t','l','m'), 0, 1); } else { shape_add_feature(&features, DWRITE_MAKE_OPENTYPE_TAG('l','t','r','a')); shape_add_feature(&features, DWRITE_MAKE_OPENTYPE_TAG('l','t','r','m')); } } for (i = 0; i < ARRAY_SIZE(common_features); ++i) shape_add_feature(&features, common_features[i]); /* Horizontal features */ if (!context->is_sideways) { for (i = 0; i < ARRAY_SIZE(horizontal_features); ++i) shape_add_feature(&features, horizontal_features[i]); } else shape_add_feature_full(&features, DWRITE_MAKE_OPENTYPE_TAG('v','e','r','t'), FEATURE_GLOBAL | FEATURE_GLOBAL_SEARCH, 1); shape_merge_features(context, &features); /* Resolve script tag to actually supported script. */ shape_get_script_lang_index(context, scripts, MS_GSUB_TAG, &script_index, &language_index); opentype_layout_apply_gsub_features(context, script_index, language_index, &features); heap_free(features.features); return (context->glyph_count <= context->u.subst.max_glyph_count) ? S_OK : E_NOT_SUFFICIENT_BUFFER; } static int tag_array_sorting_compare(const void *a, const void *b) { unsigned int left = GET_BE_DWORD(*(unsigned int *)a), right = GET_BE_DWORD(*(unsigned int *)b); return left != right ? (left < right ? -1 : 1) : 0; }; HRESULT shape_get_typographic_features(struct scriptshaping_context *context, const unsigned int *scripts, unsigned int max_tagcount, unsigned int *actual_tagcount, unsigned int *tags) { unsigned int i, j, script_index, language_index; struct tag_array t = { 0 }; /* Collect from both tables, sort and remove duplicates. */ shape_get_script_lang_index(context, scripts, MS_GSUB_TAG, &script_index, &language_index); opentype_get_typographic_features(&context->cache->gsub, script_index, language_index, &t); shape_get_script_lang_index(context, scripts, MS_GPOS_TAG, &script_index, &language_index); opentype_get_typographic_features(&context->cache->gpos, script_index, language_index, &t); /* Sort and remove duplicates. */ qsort(t.tags, t.count, sizeof(*t.tags), tag_array_sorting_compare); for (i = 1, j = 0; i < t.count; ++i) { if (t.tags[i] != t.tags[j]) t.tags[++j] = t.tags[i]; } t.count = j + 1; if (t.count <= max_tagcount) memcpy(tags, t.tags, t.count * sizeof(*t.tags)); *actual_tagcount = t.count; heap_free(t.tags); return t.count <= max_tagcount ? S_OK : E_NOT_SUFFICIENT_BUFFER; } HRESULT shape_check_typographic_feature(struct scriptshaping_context *context, const unsigned int *scripts, unsigned int tag, unsigned int glyph_count, const UINT16 *glyphs, UINT8 *feature_applies) { static const unsigned int tables[] = { MS_GSUB_TAG, MS_GPOS_TAG }; struct shaping_feature feature = { .tag = tag }; unsigned int script_index, language_index; unsigned int i; memset(feature_applies, 0, glyph_count * sizeof(*feature_applies)); for (i = 0; i < ARRAY_SIZE(tables); ++i) { shape_get_script_lang_index(context, scripts, tables[i], &script_index, &language_index); context->table = tables[i] == MS_GSUB_TAG ? &context->cache->gsub : &context->cache->gpos; /* Skip second table if feature applies to all. */ if (opentype_layout_check_feature(context, script_index, language_index, &feature, glyph_count, glyphs, feature_applies)) { break; } } return S_OK; }