diff options
Diffstat (limited to 'text/shape-build.c')
| -rw-r--r-- | text/shape-build.c | 222 |
1 files changed, 217 insertions, 5 deletions
diff --git a/text/shape-build.c b/text/shape-build.c index 96bc748..7cb251d 100644 --- a/text/shape-build.c +++ b/text/shape-build.c @@ -334,7 +334,9 @@ static int categorize(uint32_t script, hb_tag_t gsub_script) { if (script == TAG("Hang")) return VEGA_SHAPER_HANGUL; if (script == TAG("Khmr")) return VEGA_SHAPER_KHMER; if (script == TAG("Thai") || script == TAG("Laoo")) return VEGA_SHAPER_THAI; - if (script == TAG("Syrc") || script == TAG("Qaag")) return -1; + /* Syriac is Arabic's, unless the font has only DFLT for it. */ + if (script == TAG("Syrc")) return gsub_script != HB_OT_TAG_DEFAULT_SCRIPT ? VEGA_SHAPER_ARABIC : VEGA_SHAPER_DEFAULT; + if (script == TAG("Qaag")) return -1; if (listed(indic_scripts, sizeof(indic_scripts) / sizeof(*indic_scripts), script)) return dflt ? VEGA_SHAPER_DEFAULT : (gsub_script & 0xFF) == '3' ? VEGA_SHAPER_USE : VEGA_SHAPER_INDIC; if (listed(use_scripts, sizeof(use_scripts) / sizeof(*use_scripts), script)) @@ -466,7 +468,7 @@ static bool plan_for(hb_face_t *face, uint32_t script_tag, int direction, Plan * if (shaper == VEGA_SHAPER_ARABIC) { m->is_simple = false; enable_feature(m, TAG("stch"), F_NONE, 1); - add_gsub_pause(m, 0); /* record_stch */ + add_gsub_pause(m, VEGA_PAUSE_RECORD_STCH); enable_feature(m, TAG("ccmp"), F_MANUAL_ZWJ, 1); enable_feature(m, TAG("locl"), F_MANUAL_ZWJ, 1); add_gsub_pause(m, 0); @@ -476,7 +478,7 @@ static bool plan_for(hb_face_t *face, uint32_t script_tag, int direction, Plan * add_gsub_pause(m, 0); } enable_feature(m, TAG("rlig"), F_MANUAL_ZWJ | F_HAS_FALLBACK, 1); - if (script == HB_SCRIPT_ARABIC) add_gsub_pause(m, 0); /* arabic_fallback_shape */ + if (script == HB_SCRIPT_ARABIC) add_gsub_pause(m, VEGA_PAUSE_ARABIC_FALLBACK); enable_feature(m, TAG("calt"), F_MANUAL_ZWJ, 1); if (!has_feature(m, TAG("rclt"))) add_gsub_pause(m, 0); enable_feature(m, TAG("liga"), F_MANUAL_ZWJ, 1); @@ -614,15 +616,15 @@ static bool plan_for(hb_face_t *face, uint32_t script_tag, int direction, Plan * bool zeroes = shaper != VEGA_SHAPER_HANGUL && shaper != VEGA_SHAPER_INDIC && shaper != VEGA_SHAPER_KHMER; bool script_marks = zeroes && shaper != VEGA_SHAPER_MYANMAR && shaper != VEGA_SHAPER_USE && shaper != VEGA_SHAPER_THAI; bool adjust = !apply_gpos && (!apply_kern || !kern_table.cross), fallback_marks = adjust && script_marks; + bool arabic_fallback = false; if (shaper == VEGA_SHAPER_ARABIC) { - if (one_mask(m, "stch")) FAIL("%s: the font stretches (stch)", name); bool fallback = script == HB_SCRIPT_ARABIC; for (int i = 0; i < 7; ++i) { const FeatureMap *f = find_feature(m, TAG(arabic_features[i])); bool syriac = arabic_features[i][3] == '2' || arabic_features[i][3] == '3'; fallback = fallback && (syriac || (f && f->needs_fallback)); } - if (fallback) FAIL("%s: the font lacks Arabic's forms (HarfBuzz would fall back)", name); + arabic_fallback = fallback; } hb_direction_t native = hb_script_get_horizontal_direction(script); uint32_t frac = one_mask(m, "frac"), numr = one_mask(m, "numr"), dnom = one_mask(m, "dnom"); @@ -641,6 +643,9 @@ static bool plan_for(hb_face_t *face, uint32_t script_tag, int direction, Plan * plan->masks[VEGA_MASK_DNOM] = dnom; if (shaper == VEGA_SHAPER_ARABIC || shaper == VEGA_SHAPER_USE) for (int i = 0; i < 7; ++i) plan->masks[VEGA_MASK_ISOL + i] = one_mask(m, arabic_features[i]); + if (shaper == VEGA_SHAPER_ARABIC) plan->masks[VEGA_MASK_ARABIC_RLIG] = one_mask(m, "rlig"); + if (arabic_fallback) plan->shaper_flags |= VEGA_ARABIC_FALLBACK; + if (shaper == VEGA_SHAPER_ARABIC && one_mask(m, "stch")) plan->shaper_flags |= VEGA_ARABIC_STCH; if (shaper == VEGA_SHAPER_USE) plan->masks[VEGA_MASK_USE_RPHF] = one_mask(m, "rphf"); if (shaper == VEGA_SHAPER_HANGUL) { plan->masks[VEGA_MASK_LJMO] = one_mask(m, "ljmo"); @@ -705,6 +710,194 @@ static void set32(Out *o, size_t at, uint32_t v) { for (int i = 0; i < 4; ++i) o->bytes[at + i] = (unsigned char)(v >> (8 * i)); } +/* ---- Arabic fallback shaping (hb-ot-shaper-arabic-fallback.hh): for a font + * with presentation forms but no Arabic features, lookups made from its + * character map, written as OpenType (the runtime applies them as any + * GSUB lookup): single substitutions to the init, medi, fina and isol + * forms, and rlig's ligatures of three, of two, and of marks. */ + +typedef struct { unsigned char *bytes; size_t size, allocated; bool failed; } Bytes; +static void be16(Bytes *b, uint32_t v) { + if (b->failed) return; + if (b->size + 2 > b->allocated) { + size_t a = b->allocated ? 2 * b->allocated : 256; + unsigned char *grown = realloc(b->bytes, a); + if (!grown) { + b->failed = true; + return; + } + b->bytes = grown; + b->allocated = a; + } + b->bytes[b->size++] = (unsigned char)(v >> 8); + b->bytes[b->size++] = (unsigned char)v; +} +static void be16_at(Bytes *b, size_t at, uint32_t v) { + if (b->failed) return; + b->bytes[at] = (unsigned char)(v >> 8); + b->bytes[at + 1] = (unsigned char)v; +} + +static bool glyph_of(hb_font_t *font, uint32_t u, uint32_t *glyph) { + hb_codepoint_t g; + if (!u || !hb_font_get_nominal_glyph(font, u, &g)) return false; + *glyph = g; + return true; +} + +/* Glyphs sorted stably, their partners carried along; false if two are + * the same (the font maps two characters to one glyph). */ +static bool sort_pairs(uint32_t *keys, uint32_t *values, size_t n) { + for (size_t i = 1; i < n; ++i) + for (size_t j = i; j > 0 && keys[j - 1] > keys[j]; --j) { + uint32_t k = keys[j], v = values[j]; + keys[j] = keys[j - 1], values[j] = values[j - 1]; + keys[j - 1] = k, values[j - 1] = v; + } + for (size_t i = 1; i < n; ++i) + if (keys[i] == keys[i - 1]) return false; + return true; +} + +/* A lookup (type, flags, one subtable) at the end of b; the subtable's + * start, for its offsets. */ +static size_t start_lookup(Bytes *b, unsigned type, unsigned flags) { + size_t at = b->size; + be16(b, type); + be16(b, flags); + be16(b, 1); + be16(b, 8); /* the subtable, right after */ + return at + 8; +} +static void coverage_of(Bytes *b, const uint32_t *glyphs, size_t n) { + be16(b, 1); + be16(b, (uint32_t)n); + for (size_t i = 0; i < n; ++i) be16(b, glyphs[i]); +} + +/* The lookup for fallback feature k into b; false with nothing written + * when it would be empty, and *refused when the font cannot have it. */ +static bool fallback_lookup(hb_font_t *font, int k, Bytes *b, bool *refused) { + enum { FORMS = VEGA_ARABIC_FORMS_LAST - VEGA_ARABIC_FORMS_FIRST + 1 }; + if (k < 4) { + uint32_t glyphs[FORMS], substitutes[FORMS]; + size_t n = 0; + for (uint32_t u = VEGA_ARABIC_FORMS_FIRST; u <= VEGA_ARABIC_FORMS_LAST; ++u) { + uint32_t g, s; + if (!glyph_of(font, u, &g) || !glyph_of(font, vega_arabic_forms[u - VEGA_ARABIC_FORMS_FIRST][k], &s) || g == s) + continue; + glyphs[n] = g; + substitutes[n++] = s; + } + if (!n) return false; + if (!sort_pairs(glyphs, substitutes, n)) return !(*refused = true); + size_t sub = start_lookup(b, 1, 0x0008); + be16(b, 2); + be16(b, (uint32_t)(6 + 2 * n)); /* the coverage, after the substitutes */ + be16(b, (uint32_t)n); + for (size_t i = 0; i < n; ++i) be16(b, substitutes[i]); + coverage_of(b, glyphs, n); + (void)sub; + return true; + } + const VegaArabicLigatures *table = &vega_arabic_ligatures[k - 4]; + /* its sets: runs of one first character */ + uint32_t firsts[64], sets[64], set_start[65]; + size_t n_sets = 0; + for (unsigned i = 0; i < table->count; ++i) + if (!i || table->ligatures[i].first != table->ligatures[i - 1].first) { + if (n_sets == 64) return !(*refused = true); + set_start[n_sets++] = i; + } + set_start[n_sets] = table->count; + size_t n = 0; + for (size_t i = 0; i < n_sets; ++i) + if (glyph_of(font, table->ligatures[set_start[i]].first, &firsts[n])) sets[n++] = (uint32_t)i; + if (!sort_pairs(firsts, sets, n)) return !(*refused = true); + /* what each set keeps: ligatures whose glyph and components the font has */ + unsigned total = 0; + for (size_t i = 0; i < n; ++i) + for (uint32_t l = set_start[sets[i]]; l < set_start[sets[i] + 1]; ++l) { + const VegaArabicLigature *lig = &table->ligatures[l]; + uint32_t g; + bool ok = glyph_of(font, lig->ligature, &g); + for (unsigned c = 0; ok && c + 1 < table->length; ++c) ok = glyph_of(font, lig->components[c], &g); + total += ok; + } + if (!total) return false; + size_t sub = start_lookup(b, 4, k == 6 ? 0 : 0x0008); + be16(b, 1); + size_t coverage_at = b->size; + be16(b, 0); + be16(b, (uint32_t)n); + size_t offsets_at = b->size; + for (size_t i = 0; i < n; ++i) be16(b, 0); + for (size_t i = 0; i < n; ++i) { + be16_at(b, offsets_at + 2 * i, (uint32_t)(b->size - sub)); + size_t set_at = b->size, count_at = b->size; + be16(b, 0); + unsigned count = 0; + size_t lig_offsets = b->size; + for (uint32_t l = set_start[sets[i]]; l < set_start[sets[i] + 1]; ++l) { + const VegaArabicLigature *lig = &table->ligatures[l]; + uint32_t g; + bool ok = glyph_of(font, lig->ligature, &g); + for (unsigned c = 0; ok && c + 1 < table->length; ++c) ok = glyph_of(font, lig->components[c], &g); + if (ok) be16(b, 0), count++; + } + be16_at(b, count_at, count); + unsigned written = 0; + for (uint32_t l = set_start[sets[i]]; l < set_start[sets[i] + 1]; ++l) { + const VegaArabicLigature *lig = &table->ligatures[l]; + uint32_t g, components[2]; + bool ok = glyph_of(font, lig->ligature, &g); + for (unsigned c = 0; ok && c + 1 < table->length; ++c) ok = glyph_of(font, lig->components[c], &components[c]); + if (!ok) continue; + be16_at(b, lig_offsets + 2 * written++, (uint32_t)(b->size - set_at)); + be16(b, g); + be16(b, table->length); + for (unsigned c = 0; c + 1 < table->length; ++c) be16(b, components[c]); + } + } + be16_at(b, coverage_at, (uint32_t)(b->size - sub)); + coverage_of(b, firsts, n); + return true; +} + +/* The fallback lookups as a GSUB table (empty script and feature lists), + * and what each lookup is for. */ +static bool fallback_table(hb_font_t *font, Bytes *gsub, uint8_t *features, unsigned *count, char *error, + size_t error_size) { + Bytes lookups[7] = {{0}}; + *count = 0; + bool refused = false; + for (int k = 0; k < 7 && !refused; ++k) + if (fallback_lookup(font, k, &lookups[*count], &refused)) features[(*count)++] = (uint8_t)k; + if (refused) { + snprintf(error, error_size, "Arab: the font maps two characters to one glyph among those fallback shaping uses"); + for (int k = 0; k < 7; ++k) free(lookups[k].bytes); + return false; + } + be16(gsub, 1); + be16(gsub, 0); + be16(gsub, 10); /* scripts */ + be16(gsub, 12); /* features */ + be16(gsub, 14); /* lookups */ + be16(gsub, 0); + be16(gsub, 0); + be16(gsub, *count); + size_t list = 14, offsets = gsub->size; + for (unsigned i = 0; i < *count; ++i) be16(gsub, 0); + for (unsigned i = 0; i < *count; ++i) { + be16_at(gsub, offsets + 2 * i, (uint32_t)(gsub->size - list)); + for (size_t k = 0; k < lookups[i].size; k += 2) be16(gsub, (uint32_t)lookups[i].bytes[k] << 8 | lookups[i].bytes[k + 1]); + } + bool failed = gsub->failed || gsub->size > 0xFFFF; + for (int k = 0; k < 7; ++k) failed |= lookups[k].failed, free(lookups[k].bytes); + if (failed) snprintf(error, error_size, "Arab: fallback lookups: out of memory or over 64 KiB"); + return !failed; +} + bool vega_shaping_compile(hb_face_t *face, const uint32_t *scripts, size_t script_count, const uint32_t *codes, size_t code_count, VegaSection *out, hb_set_t *glyphs, char *error, size_t error_size) { Plan *plans = calloc(2 * script_count + 1, sizeof(*plans)); @@ -794,6 +987,25 @@ bool vega_shaping_compile(hb_face_t *face, const uint32_t *scripts, size_t scrip put16(&o, (uint32_t)((known ? e.height : 0) & 0xFFFF)); } } + bool falls_back = false; + for (size_t i = 0; i < plan_count; ++i) falls_back |= plans[i].shaper_flags & VEGA_ARABIC_FALLBACK; + if (falls_back) { + Bytes gsub = {0}; + uint8_t features[7] = {0}; + unsigned count; + if (!fallback_table(font, &gsub, features, &count, error, error_size)) { + free(gsub.bytes); + goto fail; + } + align4(&o); + set32(&o, 76, (uint32_t)o.size); + unsigned char head[8] = {(unsigned char)count}; + memcpy(head + 1, features, 7); + put(&o, head, 8); + put32(&o, (uint32_t)gsub.size); + put(&o, gsub.bytes, gsub.size); + free(gsub.bytes); + } align4(&o); set32(&o, 48, (uint32_t)plan_count); set32(&o, 52, (uint32_t)o.size); |
