From 88e16d6f971287afd0ab15d1cde73608cde43b1b Mon Sep 17 00:00:00 2001 From: eunhhu Date: Mon, 28 Sep 2026 13:54:31 +0100 Subject: Shape Hangul, kern with kern tables, and place marks without GPOS, as HarfBuzz does The planner now picks who positions as HarfBuzz 10.2 does: GPOS, the old kern table when GPOS has no kern feature, and marks placed by combining class and ink boxes when there is no GPOS; the Hangul shaper composes syllables the font has whole, takes apart those it lacks into jamo for ljmo, vjmo and tjmo, and moves tone marks before their syllable. The section grows to an 80-byte header (the kern table, and each glyph's ink box when a plan places marks without GPOS) and 88-byte plans (the kern mask, the jamo masks). Kern tables are applied pair subtable by pair subtable with HarfBuzz's own binary search; state machines, class subtables and cross-stream kerning, which no installed font has, are refused. The check spells Hangul syllables every way text does. Every installed font Vega accepts shapes as HarfBuzz does (209 fonts, 1,520 plans, a thousand runs each), and the fonts refused before at 20,000 runs a plan: Noto Sans CJK KR and Baekmuk for Hangul, IPA Gothic and Noto Mono for marks without GPOS, FreeSans, FreeSerif, Liberation and DejaVu Sans ExtraLight for kern tables, Unifont's Latin, Greek, Cyrillic, Hebrew and Hangul. --- text/shape.c | 436 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 425 insertions(+), 11 deletions(-) (limited to 'text/shape.c') diff --git a/text/shape.c b/text/shape.c index fbb53b6..0016fa0 100644 --- a/text/shape.c +++ b/text/shape.c @@ -201,6 +201,11 @@ bool vega_shaping_open(VegaShaping *s, const unsigned char *bytes, size_t size, if (!within(size, at, n)) REFUSE("layout table %d", k); *tables[k] = (VegaTable){n ? bytes + at : NULL, n}; } + uint32_t kern = le32(bytes + 64), kern_size = le32(bytes + 68), extents = le32(bytes + 72); + if (!within(size, kern, kern_size)) REFUSE("kern table"); + s->kern = (VegaTable){kern_size ? bytes + kern : NULL, kern_size}; + if (extents && !within(size, extents, 8 * (uint64_t)s->glyph_count)) REFUSE("extents"); + s->extents = extents ? bytes + extents : NULL; s->plan_count = le32(bytes + 48); uint32_t plans = le32(bytes + 52), step_count = le32(bytes + 56), steps = le32(bytes + 60); if (!within(size, plans, (uint64_t)s->plan_count * VEGA_PLAN_BYTES)) REFUSE("plans"); @@ -218,9 +223,10 @@ bool vega_shaping_open(VegaShaping *s, const unsigned char *bytes, size_t size, REFUSE("out of memory"); for (uint32_t i = 0; i < s->plan_count; ++i) { const unsigned char *p = s->plans + (size_t)i * VEGA_PLAN_BYTES; - uint32_t ranges[2][2] = {{le32(p + 56), le32(p + 60)}, {le32(p + 64), le32(p + 68)}}; - if ((p[4] != VEGA_SHAPE_LTR && p[4] != VEGA_SHAPE_RTL) || p[5] > VEGA_SHAPE_RTL || p[6] > VEGA_SHAPER_HEBREW) + uint32_t ranges[2][2] = {{le32(p + 72), le32(p + 76)}, {le32(p + 80), le32(p + 84)}}; + if ((p[4] != VEGA_SHAPE_LTR && p[4] != VEGA_SHAPE_RTL) || p[5] > VEGA_SHAPE_RTL || p[6] > VEGA_SHAPER_HANGUL) REFUSE("plan %u", i); + if ((p[7] & VEGA_PLAN_FALLBACK_MARKS) && !s->extents) REFUSE("plan %u: marks placed without extents", i); for (int table = 0; table < 2; ++table) { if (ranges[table][0] > step_count || ranges[table][1] > step_count - ranges[table][0]) REFUSE("plan %u: steps", i); @@ -384,6 +390,21 @@ static void output_glyph(VegaShaper *b, uint32_t glyph) { b->out[b->out_len++].codepoint = glyph; } +/* num_in glyphs become num_out, each a copy of the first with its own + * codepoint; their clusters merge. */ +static void merge_clusters(VegaShaper *b, size_t start, size_t end); +static void replace_glyphs(VegaShaper *b, size_t num_in, size_t num_out, const uint32_t *glyphs) { + if (!ensure(b, b->out_len + num_out)) return; + merge_clusters(b, b->idx, b->idx + num_in); + Info original = b->idx < b->len ? b->info[b->idx] : b->out[b->out_len - 1]; + for (size_t i = 0; i < num_out; ++i) { + b->out[b->out_len + i] = original; + b->out[b->out_len + i].codepoint = glyphs[i]; + } + b->idx += num_in; + b->out_len += num_out; +} + static void sync(VegaShaper *b) { if (!b->successful) { b->have_output = false; @@ -654,7 +675,7 @@ static void form_clusters(VegaShaper *b) { typedef struct { uint32_t script; int direction, native, shaper, flags; - uint32_t global_mask, masks[VEGA_PLAN_MASKS]; + uint32_t global_mask, kern_mask, masks[VEGA_PLAN_MASKS]; const unsigned char *gsub, *gpos; uint32_t gsub_count, gpos_count; } Plan; @@ -667,10 +688,10 @@ static bool find_plan(const VegaShaping *s, uint32_t script, int direction, Plan if (le32(p) == (pass ? 0x5A797979u : script) && p[4] == direction) found = p; } if (!found) return false; - *plan = (Plan){le32(found), found[4], found[5], found[6], found[7], le32(found + 8), {0}, - s->steps + (size_t)le32(found + 56) * VEGA_STEP_BYTES, - s->steps + (size_t)le32(found + 64) * VEGA_STEP_BYTES, le32(found + 60), le32(found + 68)}; - for (int k = 0; k < VEGA_PLAN_MASKS; ++k) plan->masks[k] = le32(found + 12 + 4 * k); + *plan = (Plan){le32(found), found[4], found[5], found[6], found[7], le32(found + 8), le32(found + 12), {0}, + s->steps + (size_t)le32(found + 72) * VEGA_STEP_BYTES, + s->steps + (size_t)le32(found + 80) * VEGA_STEP_BYTES, le32(found + 76), le32(found + 84)}; + for (int k = 0; k < VEGA_PLAN_MASKS; ++k) plan->masks[k] = le32(found + 16 + 4 * k); return true; } @@ -952,8 +973,10 @@ static void reorder_marks_hebrew(VegaShaper *b, size_t start, size_t end) { static void normalize(const VegaShaping *s, const Plan *plan, VegaShaper *b) { if (!b->len) return; Normalizer c = {s, plan, b}; - /* The shapers' preference is "auto": composed diacritics. */ - const bool might_short_circuit = true, always_short_circuit = false; + /* The shapers' preference is "auto" (composed diacritics); Hangul's is + * none: what the font has stays, and nothing is composed again. */ + const bool none = plan->shaper == VEGA_SHAPER_HANGUL; + const bool might_short_circuit = true, always_short_circuit = none; bool all_simple = true; clear_output(b); size_t count = b->len; @@ -997,7 +1020,7 @@ static void normalize(const VegaShaping *s, const Plan *plan, VegaShaper *b) { (!info_cc(&b->info[i + 1]) || info_cc(&b->info[i - 1]) <= info_cc(&b->info[i + 1]))) b->info[i].uprops &= (uint16_t)~UPROPS_HIDDEN; - if (!all_simple && b->successful) { + if (!all_simple && b->successful && !none) { clear_output(b); count = b->len; size_t starter = 0; @@ -1090,6 +1113,118 @@ static void setup_masks_arabic(VegaShaper *b, const Plan *plan) { if (b->info[i].action < NONE) b->info[i].mask |= plan->masks[VEGA_MASK_ISOL + b->info[i].action]; } +/* ---- Hangul (hb-ot-shaper-hangul.cc): a syllable the font has whole is + * composed; one it lacks is taken apart into jamo, which the ljmo, vjmo and + * tjmo features then shape; a tone mark goes before its syllable. */ + +enum { LJMO = 1, VJMO = 2, TJMO = 3 }; +static bool combining_l(uint32_t u) { return u >= LBASE && u < LBASE + LCOUNT; } +static bool combining_v(uint32_t u) { return u >= VBASE && u < VBASE + VCOUNT; } +static bool combining_t(uint32_t u) { return u > TBASE && u < TBASE + TCOUNT; } +static bool jamo_l(uint32_t u) { return (u >= 0x1100 && u <= 0x115F) || (u >= 0xA960 && u <= 0xA97C); } +static bool jamo_v(uint32_t u) { return (u >= 0x1160 && u <= 0x11A7) || (u >= 0xD7B0 && u <= 0xD7C6); } +static bool jamo_t(uint32_t u) { return (u >= 0x11A8 && u <= 0x11FF) || (u >= 0xD7CB && u <= 0xD7FB); } +static bool zero_width(const VegaShaping *s, uint32_t u) { + uint32_t glyph; + return nominal(s, u, &glyph) && advance_of(s, glyph) == 0; +} + +static void preprocess_hangul(const VegaShaping *s, VegaShaper *b) { + clear_output(b); + size_t start = 0, end = 0; /* the last syllable, when start < end */ + uint32_t glyph; + while (b->idx < b->len && b->successful) { + uint32_t u = b->info[b->idx].codepoint; + if (u == 0x302E || u == 0x302F) { + if (start < end && end == b->out_len) { + /* a tone mark goes before its syllable, unless it overstrikes */ + next_glyph(b); + if (!zero_width(s, u)) { + merge_out_clusters(b, start, end + 1); + Info tone = b->out[end]; + memmove(&b->out[start + 1], &b->out[start], (end - start) * sizeof(Info)); + b->out[start] = tone; + } + } else if (nominal(s, 0x25CC, &glyph)) { + uint32_t chars[2] = {u, 0x25CC}; + if (zero_width(s, u)) chars[0] = 0x25CC, chars[1] = u; + replace_glyphs(b, 1, 2, chars); + } else + next_glyph(b); + start = end = b->out_len; + continue; + } + start = b->out_len; + if (jamo_l(u) && b->idx + 1 < b->len) { + uint32_t l = u, v = b->info[b->idx + 1].codepoint; + if (jamo_v(v)) { + uint32_t t = 0, tindex = 0; + if (b->idx + 2 < b->len) { + t = b->info[b->idx + 2].codepoint; + if (jamo_t(t)) tindex = t - TBASE; + else t = 0; + } + if (combining_l(l) && combining_v(v) && (!t || combining_t(t))) { + uint32_t syllable = SBASE + (l - LBASE) * NCOUNT + (v - VBASE) * TCOUNT + tindex; + if (nominal(s, syllable, &glyph)) { + replace_glyphs(b, t ? 3 : 2, 1, &syllable); + end = start + 1; + continue; + } + } + b->info[b->idx].action = LJMO; + next_glyph(b); + b->info[b->idx].action = VJMO; + next_glyph(b); + if (t) { + b->info[b->idx].action = TJMO; + next_glyph(b); + end = start + 3; + } else + end = start + 2; + merge_out_clusters(b, start, end); + continue; + } + } else if (u - SBASE < SCOUNT) { + bool has = nominal(s, u, &glyph); + uint32_t lindex = (u - SBASE) / NCOUNT, nindex = (u - SBASE) % NCOUNT, vindex = nindex / TCOUNT, + tindex = nindex % TCOUNT; + if (!tindex && b->idx + 1 < b->len && combining_t(b->info[b->idx + 1].codepoint)) { + uint32_t whole = u + (b->info[b->idx + 1].codepoint - TBASE); + if (nominal(s, whole, &glyph)) { + replace_glyphs(b, 2, 1, &whole); + end = start + 1; + continue; + } + } + if (!has || (!tindex && b->idx + 1 < b->len && jamo_t(b->info[b->idx + 1].codepoint))) { + uint32_t parts[3] = {LBASE + lindex, VBASE + vindex, TBASE + tindex}; + if (nominal(s, parts[0], &glyph) && nominal(s, parts[1], &glyph) && (!tindex || nominal(s, parts[2], &glyph))) { + size_t n = tindex ? 3 : 2; + replace_glyphs(b, 1, n, parts); + if (has && !tindex) { /* the T that did not combine is part of it */ + next_glyph(b); + ++n; + } + end = start + n; + b->out[start].action = LJMO; + b->out[start + 1].action = VJMO; + if (start + 2 < end) b->out[start + 2].action = TJMO; + merge_out_clusters(b, start, end); + continue; + } + } + if (has) { + end = start + 1; + next_glyph(b); + continue; + } + } + next_glyph(b); + } + sync(b); +} + /* ---- Applying lookups (hb-ot-layout-gsubgpos.hh) */ typedef struct { @@ -1986,6 +2121,268 @@ static void apply_table(const VegaShaping *s, VegaShaper *b, int table, const un } } +/* ---- The kern table (hb-ot-kern-table.hh, hb-kern.hh): OpenType's (version + * 0) and Apple's (1.0), their pair subtables (format 0), each a pass over + * the glyphs with marks skipped, the kerning split between the pair. (The + * build tool refuses the other formats, and cross-stream kerning, which no + * font installed here has.) */ + +static uint32_t get8(VegaTable t, uint32_t at) { return at < t.size ? t.p[at] : 0; } + +static int32_t kern_pair(VegaTable t, uint32_t pairs, uint32_t left, uint32_t right) { + /* hb_bsearch's probes, so a table out of order answers as it does */ + int low = 0, high = (int)get16(t, pairs) - 1; + uint32_t key = left << 16 | right; + while (low <= high) { + int middle = (int)(((unsigned)low + (unsigned)high) / 2); + uint32_t at = pairs + 8 + 6 * (uint32_t)middle, pair = get16(t, at) << 16 | get16(t, at + 2); + if (key < pair) high = middle - 1; + else if (key > pair) low = middle + 1; + else return gets16(t, at + 4); + } + return 0; +} + +static void kern_table(const VegaShaping *s, VegaShaper *b, const Plan *plan) { + VegaTable t = s->kern; + uint32_t major = get16(t, 0); + if (major > 1 || !plan->kern_mask) return; + bool apple = major == 1; + uint32_t count = apple ? get32(t, 4) : get16(t, 2), at = apple ? 8 : 4; + for (uint32_t k = 0; k < count && at < t.size; ++k) { + uint32_t length = apple ? get32(t, at) : get16(t, at + 2); + unsigned format = get8(t, at + (apple ? 5 : 4)), coverage = get8(t, at + (apple ? 4 : 5)); + bool horizontal = apple ? !(coverage & 0x80) : coverage & 1; + uint32_t pairs = at + (apple ? 8 : 6); + at += length ? length : t.size; + if ((apple && (coverage & 0x20)) || !horizontal || format != 0) continue; + bool reverse = b->direction == VEGA_SHAPE_RTL; + if (reverse) reverse_range(b, 0, b->len, true); + Apply c = {s, b, 1, s->gpos, plan->kern_mask, 0x08, true, true, false, s->glyph_classes != 0, MAX_NESTING_LEVEL, 1, + -1, 0, {{0, 0, 0}}}; + for (size_t idx = 0; idx < b->len;) { + if (!(b->info[idx].mask & plan->kern_mask)) { + idx++; + continue; + } + Skippy it; + skippy_init(&it, &c, false, idx); + if (!skippy_next(&it, NULL)) { + idx++; + continue; + } + size_t i = idx, j = it.idx; + int32_t kern = kern_pair(t, pairs, b->info[i].codepoint, b->info[j].codepoint); + if (kern) { + int32_t first = kern >> 1, second = kern - first; + b->pos[i].x_advance += first; + b->pos[j].x_advance += second; + b->pos[j].x_offset += second; + } + idx = j; + } + if (reverse) reverse_range(b, 0, b->len, true); + } +} + +/* ---- Marks without GPOS (hb-ot-shape-fallback.cc): each mark placed around + * its base by its combining class and the glyphs' ink boxes. */ + +typedef struct { int32_t x_bearing, y_bearing, width, height; } Extents; + +static bool extents_of(const VegaShaping *s, uint32_t glyph, Extents *e) { + if (!s->extents || glyph >= s->glyph_count) return false; + const unsigned char *p = s->extents + 8 * (size_t)glyph; + int16_t x = (int16_t)le16(p); + if (x == INT16_MIN) return false; + *e = (Extents){x, (int16_t)le16(p + 2), (int16_t)le16(p + 4), (int16_t)le16(p + 6)}; + return true; +} + +enum { CC_ATTACHED_BELOW_LEFT = 200, CC_ATTACHED_BELOW = 202, CC_ATTACHED_ABOVE = 214, CC_ATTACHED_ABOVE_RIGHT = 216, + CC_BELOW_LEFT = 218, CC_BELOW = 220, CC_BELOW_RIGHT = 222, CC_ABOVE_LEFT = 228, CC_ABOVE = 230, + CC_ABOVE_RIGHT = 232, CC_DOUBLE_BELOW = 233, CC_DOUBLE_ABOVE = 234 }; + +/* Where the classes put marks, for those HarfBuzz reorders by other classes. */ +static unsigned recategorize(uint32_t u, unsigned klass) { + if (klass >= 200) return klass; + if ((u & ~0xFFu) == 0x0E00) { + if (!klass) { + switch (u) { + case 0x0E31: case 0x0E34: case 0x0E35: case 0x0E36: case 0x0E37: case 0x0E47: case 0x0E4C: case 0x0E4D: + case 0x0E4E: klass = CC_ABOVE_RIGHT; break; + case 0x0EB1: case 0x0EB4: case 0x0EB5: case 0x0EB6: case 0x0EB7: case 0x0EBB: case 0x0ECC: case 0x0ECD: + klass = CC_ABOVE; break; + case 0x0EBC: klass = CC_BELOW; break; + } + } else if (u == 0x0E3A) + klass = CC_BELOW_RIGHT; + } + switch (klass) { + case 22: case 15: case 16: case 17: case 23: case 18: case 19: case 20: case 21: case 24: case 25: + return CC_BELOW; /* Hebrew points below, meteg */ + case 13: return CC_ATTACHED_ABOVE; /* rafe */ + case 10: return CC_ABOVE_RIGHT; /* shin dot */ + case 11: case 14: return CC_ABOVE_LEFT; /* sin dot, holam */ + case 26: return CC_ABOVE; /* point varika */ + case 12: return klass; /* dagesh */ + case 28: case 29: case 31: case 32: case 27: case 34: case 35: case 36: + return CC_ABOVE; /* Arabic and Syriac above */ + case 30: case 33: return CC_BELOW; /* kasratan, kasra */ + case 3: return CC_BELOW_RIGHT; /* Thai sara u, uu */ + case 107: return CC_ABOVE_RIGHT; /* Thai mai */ + case 118: return CC_BELOW; /* Lao sign u, uu */ + case 122: return CC_ABOVE; /* Lao mai */ + case 129: return CC_BELOW; /* Tibetan sign aa */ + case 132: return CC_ABOVE; /* sign i */ + case 131: return CC_BELOW; /* sign u */ + default: return klass; + } +} + +static void position_mark(const VegaShaping *s, int direction, uint32_t glyph, Position *pos, Extents *base, + unsigned klass) { + Extents mark; + if (!extents_of(s, glyph, &mark)) return; + int32_t y_gap = s->upem / 16; + pos->x_offset = pos->y_offset = 0; + switch (klass) { + case CC_DOUBLE_BELOW: + case CC_DOUBLE_ABOVE: + if (direction == VEGA_SHAPE_LTR) pos->x_offset += base->x_bearing + base->width - mark.width / 2 - mark.x_bearing; + else pos->x_offset += base->x_bearing - mark.width / 2 - mark.x_bearing; + break; + case CC_ATTACHED_BELOW_LEFT: case CC_BELOW_LEFT: case CC_ABOVE_LEFT: + pos->x_offset += base->x_bearing - mark.x_bearing; + break; + case CC_ATTACHED_ABOVE_RIGHT: case CC_BELOW_RIGHT: case CC_ABOVE_RIGHT: + pos->x_offset += base->x_bearing + base->width - mark.width - mark.x_bearing; + break; + default: + pos->x_offset += base->x_bearing + (base->width - mark.width) / 2 - mark.x_bearing; + break; + } + switch (klass) { + case CC_DOUBLE_BELOW: case CC_BELOW_LEFT: case CC_BELOW: case CC_BELOW_RIGHT: + base->height -= y_gap; + /* fall through */ + case CC_ATTACHED_BELOW_LEFT: case CC_ATTACHED_BELOW: + pos->y_offset = base->y_bearing + base->height - mark.y_bearing; + if ((y_gap > 0) == (pos->y_offset > 0)) { /* below marks never go up */ + base->height -= pos->y_offset; + pos->y_offset = 0; + } + base->height += mark.height; + break; + case CC_DOUBLE_ABOVE: case CC_ABOVE_LEFT: case CC_ABOVE: case CC_ABOVE_RIGHT: + base->y_bearing += y_gap; + base->height -= y_gap; + /* fall through */ + case CC_ATTACHED_ABOVE: case CC_ATTACHED_ABOVE_RIGHT: { + pos->y_offset = base->y_bearing - (mark.y_bearing + mark.height); + if ((y_gap > 0) != (pos->y_offset > 0)) { /* nor above marks too far down */ + int32_t correction = -pos->y_offset / 2; + base->y_bearing += correction; + base->height -= correction; + pos->y_offset += correction; + } + base->y_bearing -= mark.height; + base->height += mark.height; + break; + } + default: break; + } +} + +static void zero_mark_advances(VegaShaper *b, size_t start, size_t end, bool adjust) { + for (size_t i = start; i < end; ++i) + if (gen_cat(&b->info[i]) == VEGA_MN) { + if (adjust) { + b->pos[i].x_offset -= b->pos[i].x_advance; + b->pos[i].y_offset -= b->pos[i].y_advance; + } + b->pos[i].x_advance = b->pos[i].y_advance = 0; + } +} + +static void position_around_base(const VegaShaping *s, const Plan *plan, VegaShaper *b, size_t base, size_t end, + bool adjust) { + Extents base_extents; + if (!extents_of(s, b->info[base].codepoint, &base_extents)) { + zero_mark_advances(b, base + 1, end, adjust); + return; + } + base_extents.y_bearing += b->pos[base].y_offset; + base_extents.x_bearing = 0; + base_extents.width = advance_of(s, b->info[base].codepoint); + unsigned id = lig_id(&b->info[base]); + int components = lig_num_comps(&b->info[base]); + int32_t x_offset = 0, y_offset = 0; + bool forward = b->direction == VEGA_SHAPE_LTR; + if (forward) { + x_offset -= b->pos[base].x_advance; + y_offset -= b->pos[base].y_advance; + } + Extents component = base_extents, cluster = base_extents; + int last_component = -1; + unsigned last_class = 255; + for (size_t i = base + 1; i < end; ++i) { + unsigned klass = info_cc(&b->info[i]); + if (klass) { + if (components > 1) { + unsigned this_id = lig_id(&b->info[i]); + int this_component = lig_comp(&b->info[i]) - 1; + if (!id || id != this_id || this_component >= components) this_component = components - 1; + if (last_component != this_component) { + last_component = this_component; + last_class = 255; + component = base_extents; + if (plan->direction == VEGA_SHAPE_LTR) + component.x_bearing += this_component * component.width / components; + else + component.x_bearing += (components - 1 - this_component) * component.width / components; + component.width /= components; + } + } + if (last_class != klass) { + last_class = klass; + cluster = component; + } + position_mark(s, b->direction, b->info[i].codepoint, &b->pos[i], &cluster, klass); + b->pos[i].x_advance = b->pos[i].y_advance = 0; + b->pos[i].x_offset += x_offset; + b->pos[i].y_offset += y_offset; + } else if (forward) { + x_offset -= b->pos[i].x_advance; + y_offset -= b->pos[i].y_advance; + } else { + x_offset += b->pos[i].x_advance; + y_offset += b->pos[i].y_advance; + } + } +} + +static void position_cluster(const VegaShaping *s, const Plan *plan, VegaShaper *b, size_t start, size_t end, bool adjust) { + if (end - start < 2) return; + for (size_t i = start; i < end; ++i) + if (!is_unicode_mark(&b->info[i])) { + size_t j = i + 1; + while (j < end && is_unicode_mark(&b->info[j])) j++; + position_around_base(s, plan, b, i, j, adjust); + i = j - 1; + } +} + +static void fallback_marks(const VegaShaping *s, const Plan *plan, VegaShaper *b, bool adjust) { + size_t start = 0; + for (size_t i = 1; i < b->len; ++i) + if (!is_unicode_mark(&b->info[i])) { + position_cluster(s, plan, b, start, i, adjust); + start = i; + } + position_cluster(s, plan, b, start, b->len, adjust); +} + /* ---- Positions */ static void fallback_spaces(const VegaShaping *s, VegaShaper *b) { @@ -2049,16 +2446,25 @@ static void position(const VegaShaping *s, const Plan *plan, VegaShaper *b) { memset(b->pos, 0, b->len * sizeof(Position)); for (size_t i = 0; i < b->len; ++i) b->pos[i].x_advance = advance_of(s, b->info[i].codepoint); if (b->scratch & HAS_SPACE_FALLBACK) fallback_spaces(s, b); + bool adjust = (plan->flags & VEGA_PLAN_ADJUST_ZEROING) && b->direction == VEGA_SHAPE_LTR; if (plan->flags & VEGA_PLAN_GPOS) apply_table(s, b, 1, plan->gpos, plan->gpos_count); + if (plan->flags & VEGA_PLAN_KERN) kern_table(s, b, plan); if (plan->flags & VEGA_PLAN_ZERO_MARKS) for (size_t i = 0; i < b->len; ++i) - if (b->info[i].gprops & MARK) b->pos[i].x_advance = b->pos[i].y_advance = 0; + if (b->info[i].gprops & MARK) { + if (adjust) { + b->pos[i].x_offset -= b->pos[i].x_advance; + b->pos[i].y_offset -= b->pos[i].y_advance; + } + b->pos[i].x_advance = b->pos[i].y_advance = 0; + } if (b->scratch & HAS_DEFAULT_IGNORABLES) for (size_t i = 0; i < b->len; ++i) if (is_default_ignorable(&b->info[i])) b->pos[i].x_advance = b->pos[i].y_advance = b->pos[i].x_offset = b->pos[i].y_offset = 0; if (b->scratch & HAS_GPOS_ATTACHMENT) for (size_t i = 0; i < b->len; ++i) propagate_attachment_offsets(b->pos, b->len, i, b->direction, MAX_NESTING_LEVEL); + if (plan->flags & VEGA_PLAN_FALLBACK_MARKS) fallback_marks(s, plan, b, adjust); if (b->direction == VEGA_SHAPE_RTL) reverse_range(b, 0, b->len, true); } @@ -2082,6 +2488,7 @@ long vega_shape(VegaShaper *b, const VegaShaping *s, const uint32_t *codes, size set_unicode_props(b); form_clusters(b); ensure_native_direction(b, &plan); + if (plan.shaper == VEGA_SHAPER_HANGUL) preprocess_hangul(s, b); /* Mirror what the font mirrors; mark the rest for rtlm. */ if (direction == VEGA_SHAPE_RTL) @@ -2093,6 +2500,13 @@ long vega_shape(VegaShaper *b, const VegaShaping *s, const uint32_t *codes, size normalize(s, &plan, b); setup_masks_fraction(b, &plan); if (plan.shaper == VEGA_SHAPER_ARABIC) setup_masks_arabic(b, &plan); + if (plan.shaper == VEGA_SHAPER_HANGUL) + for (size_t i = 0; i < b->len; ++i) + if (b->info[i].action >= LJMO && b->info[i].action <= TJMO) + b->info[i].mask |= plan.masks[VEGA_MASK_LJMO + b->info[i].action - LJMO]; + if (plan.flags & VEGA_PLAN_FALLBACK_MARKS) + for (size_t i = 0; i < b->len; ++i) + if (gen_cat(&b->info[i]) == VEGA_MN) set_info_cc(&b->info[i], recategorize(b->info[i].codepoint, info_cc(&b->info[i]))); for (size_t i = 0; i < b->len; ++i) { Info *info = &b->info[i]; info->codepoint = info->glyph_index; -- cgit v1.2.3