aboutsummaryrefslogtreecommitdiffstats
path: root/text/shape.c
diff options
context:
space:
mode:
Diffstat (limited to 'text/shape.c')
-rw-r--r--text/shape.c436
1 files changed, 425 insertions, 11 deletions
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;