From cf6bcf8697589d2a6fc3c6d98bc52bf7eb1ea4ae Mon Sep 17 00:00:00 2001 From: eunhhu Date: Mon, 28 Sep 2026 16:07:05 +0100 Subject: Carry variation sequences (cmap format 14) The section now carries each sequence the font maps, a base and a selector, with the glyph HarfBuzz gives the pair (a default one's the base's own). As HarfBuzz's normalizer does, a sequence the font has becomes that one glyph, the two characters' clusters merged; one it lacks still passes on as two, the selector hidden. The header grows to 88 bytes for the table. The random check now mixes in the font's own sequences and stray selectors: 30000 runs over Noto Sans CJK's 14787 ideographic variation sequences are HarfBuzz's, and so are the installed fonts' and Noto's. HarfBuzz's suite: 5014 of 5199 the same, none different; what is left is what the build refuses and one run without a plan. --- text/shape.c | 53 +++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 43 insertions(+), 10 deletions(-) (limited to 'text/shape.c') diff --git a/text/shape.c b/text/shape.c index 926632d..2c8d2a6 100644 --- a/text/shape.c +++ b/text/shape.c @@ -229,6 +229,16 @@ bool vega_shaping_open(VegaShaping *s, const unsigned char *bytes, size_t size, s->attach_classes = offset16(s->gdef, 0, 10); s->mark_sets = minor >= 2 ? offset16(s->gdef, 0, 12) : 0; } + s->sequence_count = le32(bytes + 80); + uint32_t sequences = le32(bytes + 84); + if (!within(size, sequences, (uint64_t)s->sequence_count * 12)) REFUSE("variation sequences"); + s->sequences = bytes + sequences; + for (uint32_t i = 0; i < s->sequence_count; ++i) { + const unsigned char *q = s->sequences + 12 * (size_t)i; + if ((i && (le32(q) < le32(q - 12) || (le32(q) == le32(q - 12) && le32(q + 4) <= le32(q - 8)))) || + le32(q + 8) >= s->glyph_count) + REFUSE("variation sequence %u", i); + } size_t subtables = 0, allocated = 0; uint32_t fallback = le32(bytes + 76); if (fallback) { @@ -857,6 +867,23 @@ static bool compose(const Normalizer *c, uint32_t a, uint32_t b, uint32_t *ab) { static bool nominal(const VegaShaping *s, uint32_t u, uint32_t *glyph) { return (*glyph = vega_shaping_glyph(s, u)) != 0; } +/* The glyph the font gives base followed by selector (cmap format 14). */ +static bool variation_glyph(const VegaShaping *s, uint32_t base, uint32_t selector, uint32_t *glyph) { + uint32_t low = 0, high = s->sequence_count; + while (low < high) { + uint32_t middle = (low + high) / 2; + const unsigned char *q = s->sequences + 12 * (size_t)middle; + uint32_t b = le32(q), v = le32(q + 4); + if (b < base || (b == base && v < selector)) low = middle + 1; + else if (b > base || v > selector) high = middle; + else { + *glyph = le32(q + 8); + return true; + } + } + return false; +} + static void output_char(VegaShaper *b, uint32_t unichar, uint32_t glyph) { b->info[b->idx].glyph_index = glyph; size_t before = b->out_len; @@ -951,21 +978,27 @@ static void decompose_current_character(const Normalizer *c, bool shortest) { next_char(b, 0); } -/* A variation selector's cluster: normalization gives up on it. (Variation - * sequences the font maps, cmap format 14, are not carried: each character - * passes on alone, the selector hidden.) */ +/* A variation selector's cluster: normalization gives up on it. A + * sequence the font maps (cmap format 14) becomes its glyph; otherwise + * each character passes on alone, the selector hidden. */ static void handle_variation_selector_cluster(const Normalizer *c, size_t end) { VegaShaper *b = c->b; uint32_t glyph; while (b->idx + 1 < end && b->successful) { if (variation_selector(b->info[b->idx + 1].codepoint)) { - b->info[b->idx].glyph_index = nominal(c->s, b->info[b->idx].codepoint, &glyph) ? glyph : 0; - next_glyph(b); - b->scratch |= HAS_VARIATION_SELECTOR_FALLBACK; - Info *vs = &b->info[b->idx]; - vs->uprops = (uint16_t)((vs->uprops & ~0x1Fu & 0xFF) | VEGA_CF | UPROPS_VS); - vs->glyph_index = nominal(c->s, vs->codepoint, &glyph) ? glyph : 0; - next_glyph(b); + if (variation_glyph(c->s, b->info[b->idx].codepoint, b->info[b->idx + 1].codepoint, &glyph)) { + b->info[b->idx].glyph_index = glyph; + uint32_t unicode = b->info[b->idx].codepoint; + replace_glyphs(b, 2, 1, &unicode); + } else { + b->info[b->idx].glyph_index = nominal(c->s, b->info[b->idx].codepoint, &glyph) ? glyph : 0; + next_glyph(b); + b->scratch |= HAS_VARIATION_SELECTOR_FALLBACK; + Info *vs = &b->info[b->idx]; + vs->uprops = (uint16_t)((vs->uprops & ~0x1Fu & 0xFF) | VEGA_CF | UPROPS_VS); + vs->glyph_index = nominal(c->s, vs->codepoint, &glyph) ? glyph : 0; + next_glyph(b); + } while (b->idx < end && b->successful && variation_selector(b->info[b->idx].codepoint)) { b->info[b->idx].glyph_index = nominal(c->s, b->info[b->idx].codepoint, &glyph) ? glyph : 0; next_glyph(b); -- cgit v1.2.3