diff options
Diffstat (limited to 'text')
| -rw-r--r-- | text/shape-build.c | 160 | ||||
| -rw-r--r-- | text/shape-build.h | 11 | ||||
| -rw-r--r-- | text/shape.c | 436 | ||||
| -rw-r--r-- | text/shape.h | 31 |
4 files changed, 600 insertions, 38 deletions
diff --git a/text/shape-build.c b/text/shape-build.c index c5fd237..3910c5d 100644 --- a/text/shape-build.c +++ b/text/shape-build.c @@ -254,8 +254,9 @@ static int categorize(uint32_t script, hb_tag_t gsub_script) { bool dflt = gsub_script == HB_OT_TAG_DEFAULT_SCRIPT || gsub_script == TAG("latn"); if (script == TAG("Arab")) return VEGA_SHAPER_ARABIC; if (script == TAG("Hebr")) return VEGA_SHAPER_HEBREW; - if (script == TAG("Syrc") || script == TAG("Thai") || script == TAG("Laoo") || script == TAG("Hang") || - script == TAG("Khmr") || script == TAG("Qaag")) + if (script == TAG("Hang")) return VEGA_SHAPER_HANGUL; + if (script == TAG("Syrc") || script == TAG("Thai") || script == TAG("Laoo") || script == TAG("Khmr") || + script == TAG("Qaag")) return -1; if (listed(indic_scripts, sizeof(indic_scripts) / sizeof(*indic_scripts), script) || listed(use_scripts, sizeof(use_scripts) / sizeof(*use_scripts), script)) @@ -274,11 +275,53 @@ static bool has_table(hb_face_t *face, hb_tag_t tag) { 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]; LookupMap *lookups[2]; unsigned lookup_count[2]; } Plan; +/* What HarfBuzz makes of the font's kern table (hb-ot-kern-table.hh): one + * with subtables that pass its checks (data), Apple's state machines among + * them (machine), cross-stream kerning (cross); and whether it has what + * Vega does not apply (other: a format but pairs, a version but 0 and 1). */ +typedef struct { bool data, machine, cross, other; } Kerning; + +static Kerning kerning_of(hb_face_t *face) { + Kerning k = {false, false, false, false}; + hb_blob_t *blob = hb_face_reference_table(face, HB_TAG('k', 'e', 'r', 'n')); + unsigned length; + const unsigned char *p = (const unsigned char *)hb_blob_get_data(blob, &length); + #define U16(at) ((uint64_t)(at) + 2 <= length ? (uint32_t)p[at] << 8 | p[(at) + 1] : 0) + #define U32(at) ((uint64_t)(at) + 4 <= length ? U16(at) << 16 | U16((at) + 2) : 0) + uint32_t major = U16(0); + k.data = U32(0) != 0; + k.other = major > 1; + bool apple = major == 1, valid = length >= (apple ? 8u : 4u); + uint32_t count = apple ? U32(4) : U16(2), header = apple ? 8 : 6; + uint64_t at = apple ? 8 : 4; + for (uint32_t i = 0; k.data && !k.other && valid && i < count; ++i) { + /* each subtable within the table (the last one's pairs may run past + * the length it gives: HarfBuzz lets them, for fonts whose 16 bits + * overflowed) */ + valid = at + header <= length; + if (!valid) break; + uint32_t size = apple ? U32(at) : U16(at + 2); + unsigned format = p[at + (apple ? 5 : 4)], coverage = p[at + (apple ? 4 : 5)]; + valid = size >= header && at + size <= length; + uint64_t end = i + 1 < count ? at + size : length; + if (valid && format == 0) valid = at + header + 8 + 6 * (uint64_t)U16(at + header) <= end; + k.machine |= format == 1; + k.other |= format != 0; + k.cross |= apple ? coverage & 0x40 : coverage & 4; + at += size; + } + #undef U16 + #undef U32 + if (!valid) k = (Kerning){false, false, false, false}; /* HarfBuzz drops a table that fails its checks */ + hb_blob_destroy(blob); + return k; +} + #define FAIL(...) \ do { \ snprintf(error, error_size, __VA_ARGS__); \ @@ -305,6 +348,7 @@ static bool plan_for(hb_face_t *face, uint32_t script_tag, int direction, Plan * &m->language_index[t]); } int shaper = categorize(script_tag, m->chosen_script[0]); + Kerning kern_table = kerning_of(face); if (shaper < 0) FAIL("%s: HarfBuzz shapes it with a shaper Vega does not have", name); if (has_table(face, HB_TAG('m', 'o', 'r', 'x')) || has_table(face, HB_TAG('k', 'e', 'r', 'x')) || has_table(face, HB_TAG('t', 'r', 'a', 'k'))) @@ -349,6 +393,12 @@ static bool plan_for(hb_face_t *face, uint32_t script_tag, int direction, Plan * enable_feature(m, TAG("clig"), F_MANUAL_ZWJ, 1); enable_feature(m, TAG("mset"), F_MANUAL_ZWJ, 1); } + if (shaper == VEGA_SHAPER_HANGUL) { + m->is_simple = false; + add_feature(m, TAG("ljmo"), F_NONE, 1); + add_feature(m, TAG("vjmo"), F_NONE, 1); + add_feature(m, TAG("tjmo"), F_NONE, 1); + } enable_feature(m, TAG("Buzz"), F_NONE, 1); enable_feature(m, TAG("BUZZ"), F_NONE, 1); static const char *const common[] = {"abvm", "blwm", "ccmp", "locl", "mark", "mkmk", "rlig"}; @@ -356,6 +406,8 @@ static bool plan_for(hb_face_t *face, uint32_t script_tag, int direction, Plan * add_feature(m, TAG(common[i]), F_GLOBAL | (i == 4 || i == 5 ? F_MANUAL_ZWNJ | F_MANUAL_ZWJ : 0), 1); static const char *const horizontal[] = {"calt", "clig", "curs", "dist", "kern", "liga", "rclt"}; for (int i = 0; i < 7; ++i) add_feature(m, TAG(horizontal[i]), F_GLOBAL | (i == 4 ? F_HAS_FALLBACK : 0), 1); + /* Hangul turns calt off: fonts put jamo lookups there. */ + if (shaper == VEGA_SHAPER_HANGUL) add_feature(m, TAG("calt"), F_GLOBAL, 0); if (!compile_map(m)) FAIL("out of memory"); /* hb_ot_shape_planner_t::compile */ @@ -363,9 +415,12 @@ static bool plan_for(hb_face_t *face, uint32_t script_tag, int direction, Plan * bool has_gpos_kern = kern && kern->index[1] != NO_FEATURE; bool disable_gpos = shaper == VEGA_SHAPER_HEBREW && m->chosen_script[1] != TAG("hebr"); bool apply_gpos = !disable_gpos && hb_ot_layout_has_positioning(face); - if ((!has_gpos_kern || !apply_gpos) && has_table(face, HB_TAG('k', 'e', 'r', 'n'))) - FAIL("%s: HarfBuzz would kern with the font's kern table", name); - if (!apply_gpos) FAIL("%s: HarfBuzz would position without GPOS (fallback positioning)", name); + bool apply_kern = (!has_gpos_kern || !apply_gpos) && kern_table.data; + if (apply_kern && (kern_table.other || kern_table.cross)) + FAIL("%s: the font's kern table has more than pairs (state machines, classes, cross-stream kerning)", name); + /* Hangul's shaper neither zeroes marks nor places them without GPOS. */ + bool script_marks = shaper != VEGA_SHAPER_HANGUL; + bool adjust = !apply_gpos && (!apply_kern || !kern_table.cross), fallback_marks = adjust && script_marks; if (shaper == VEGA_SHAPER_ARABIC) { if (one_mask(m, "stch")) FAIL("%s: the font stretches (stch)", name); bool fallback = script == HB_SCRIPT_ARABIC; @@ -380,14 +435,21 @@ static bool plan_for(hb_face_t *face, uint32_t script_tag, int direction, Plan * uint32_t frac = one_mask(m, "frac"), numr = one_mask(m, "numr"), dnom = one_mask(m, "dnom"); *plan = (Plan){script_tag, direction, native == HB_DIRECTION_LTR ? VEGA_SHAPE_LTR : native == HB_DIRECTION_RTL ? VEGA_SHAPE_RTL : 0, shaper, - VEGA_PLAN_GPOS | VEGA_PLAN_ZERO_MARKS | (hb_ot_layout_has_glyph_classes(face) ? 0 : VEGA_PLAN_GUESS_CLASSES) | - (one_mask(m, "mark") ? VEGA_PLAN_GPOS_MARK : 0) | (frac || (numr && dnom) ? VEGA_PLAN_FRACTIONS : 0), - m->global_mask, {0}, {m->lookups[0], m->lookups[1]}, {m->lookup_count[0], m->lookup_count[1]}}; + (apply_gpos ? VEGA_PLAN_GPOS : 0) | (script_marks ? VEGA_PLAN_ZERO_MARKS : 0) | + (hb_ot_layout_has_glyph_classes(face) ? 0 : VEGA_PLAN_GUESS_CLASSES) | + (one_mask(m, "mark") ? VEGA_PLAN_GPOS_MARK : 0) | (frac || (numr && dnom) ? VEGA_PLAN_FRACTIONS : 0) | + (apply_kern ? VEGA_PLAN_KERN : 0) | (fallback_marks ? VEGA_PLAN_FALLBACK_MARKS : 0) | + (adjust ? VEGA_PLAN_ADJUST_ZEROING : 0), + m->global_mask, kern ? kern->one_mask : 0, {0}, {m->lookups[0], m->lookups[1]}, + {m->lookup_count[0], m->lookup_count[1]}}; for (int i = 0; i < 7; ++i) plan->masks[VEGA_MASK_ISOL + i] = one_mask(m, arabic_features[i]); plan->masks[VEGA_MASK_RTLM] = one_mask(m, "rtlm"); plan->masks[VEGA_MASK_FRAC] = frac; plan->masks[VEGA_MASK_NUMR] = numr; plan->masks[VEGA_MASK_DNOM] = dnom; + plan->masks[VEGA_MASK_LJMO] = one_mask(m, "ljmo"); + plan->masks[VEGA_MASK_VJMO] = one_mask(m, "vjmo"); + plan->masks[VEGA_MASK_TJMO] = one_mask(m, "tjmo"); free(m); return true; fail: @@ -463,7 +525,7 @@ bool vega_shaping_compile(hb_face_t *face, const uint32_t *scripts, size_t scrip put32(&o, 0); put16(&o, hb_face_get_upem(face)); put16(&o, glyph_count); - for (int i = 0; i < 13; ++i) put32(&o, 0); + for (int i = 0; i < 17; ++i) put32(&o, 0); set32(&o, 12, cmap_count); set32(&o, 16, (uint32_t)o.size); for (hb_codepoint_t u = HB_SET_VALUE_INVALID; hb_set_next(unicodes, &u);) { @@ -491,6 +553,38 @@ bool vega_shaping_compile(hb_face_t *face, const uint32_t *scripts, size_t scrip put(&o, data, length); hb_blob_destroy(tables[k]); } + /* The kern table as it is, for the plans that kern with it; and the + * glyphs' ink boxes, for those that place marks without GPOS. */ + bool kerns = false, boxes = false; + for (size_t i = 0; i < plan_count; ++i) { + kerns |= plans[i].flags & VEGA_PLAN_KERN; + boxes |= plans[i].flags & VEGA_PLAN_FALLBACK_MARKS; + } + if (kerns) { + hb_blob_t *kern = hb_face_reference_table(face, HB_TAG('k', 'e', 'r', 'n')); + unsigned length; + const char *data = hb_blob_get_data(kern, &length); + align4(&o); + set32(&o, 64, (uint32_t)o.size); + set32(&o, 68, length); + put(&o, data, length); + hb_blob_destroy(kern); + } + if (boxes) { + align4(&o); + set32(&o, 72, (uint32_t)o.size); + for (unsigned g = 0; g < glyph_count; ++g) { + hb_glyph_extents_t e; + bool known = hb_font_get_glyph_extents(font, g, &e); + if (known && (e.x_bearing < -32767 || e.x_bearing > 32767 || e.y_bearing < -32768 || e.y_bearing > 32767 || + e.width < -32768 || e.width > 32767 || e.height < -32768 || e.height > 32767)) + FAIL("glyph %u: an ink box outside 16 bits", g); + put16(&o, known ? (uint32_t)(e.x_bearing & 0xFFFF) : 0x8000); + put16(&o, (uint32_t)((known ? e.y_bearing : 0) & 0xFFFF)); + put16(&o, (uint32_t)((known ? e.width : 0) & 0xFFFF)); + put16(&o, (uint32_t)((known ? e.height : 0) & 0xFFFF)); + } + } align4(&o); set32(&o, 48, (uint32_t)plan_count); set32(&o, 52, (uint32_t)o.size); @@ -502,6 +596,7 @@ bool vega_shaping_compile(hb_face_t *face, const uint32_t *scripts, size_t scrip (unsigned char)p->flags}; put(&o, small, 4); put32(&o, p->global_mask); + put32(&o, p->kern_mask); for (int k = 0; k < VEGA_PLAN_MASKS; ++k) put32(&o, p->masks[k]); for (int t = 0; t < 2; ++t) { put32(&o, step); @@ -604,6 +699,40 @@ static void pool_add(Pool *p, uint32_t code) { } static uint32_t pick(Random *r, const Pool *p) { return p->items[below(r, (uint32_t)p->count)]; } +/* A jamo of one part of a Hangul syllable: half the time one that composes + * (whether the font has it or not), half the time any the font has. */ +static uint32_t jamo(Random *r, const Pool *p, uint32_t first, uint32_t count) { + return p->count && below(r, 2) ? pick(r, p) : first + below(r, count); +} + +/* A Hangul syllable the ways text spells one (hb-ot-shaper-hangul.cc): jamo + * in the orders that compose (L V, L V T, LV T), a syllable whole, jamo in + * any order; now and then a tone mark after. jamo holds the font's L, V + * and T. */ +static size_t hangul_syllable(Random *r, const Pool *jamo_pools, uint32_t *text, size_t n) { + const Pool *l = &jamo_pools[0], *v = &jamo_pools[1], *t = &jamo_pools[2]; + switch (below(r, 5)) { + case 0: + case 1: + text[n++] = jamo(r, l, 0x1100, 19); + text[n++] = jamo(r, v, 0x1161, 21); + if (below(r, 2)) text[n++] = jamo(r, t, 0x11A8, 27); + break; + case 2: + text[n++] = 0xAC00 + 28 * below(r, 19 * 21); + text[n++] = jamo(r, t, 0x11A8, 27); + break; + case 3: text[n++] = 0xAC00 + below(r, 11172); break; + default: + for (unsigned k = 1 + below(r, 3); k--;) { + unsigned part = below(r, 3); + text[n++] = jamo(r, &jamo_pools[part], part == 0 ? 0x1100 : part == 1 ? 0x1161 : 0x11A8, part == 0 ? 19 : part == 1 ? 21 : 27); + } + } + if (!below(r, 8)) text[n++] = 0x302E + below(r, 2); + return n; +} + static void describe(FILE *report, const char *who, const VegaShaped *g, unsigned count) { fprintf(report, " %-8s", who); for (unsigned i = 0; i < count; ++i) @@ -618,7 +747,8 @@ void vega_shaping_check(hb_font_t *font, const VegaShaping *s, uint32_t script, /* Letters of the script, marks it can take (its own and inherited * ones), the rest of its characters and Common's (spaces, digits, * punctuation), and those whose glyphs some substitution starts from. */ - Pool letters = {0}, marks = {0}, others = {0}, hot = {0}; + Pool letters = {0}, marks = {0}, others = {0}, hot = {0}, jamo_pools[3] = {{0}}; + bool hangul = script == HB_TAG('H', 'a', 'n', 'g'); hb_set_t *inputs = hb_set_create(); uint32_t common = HB_TAG('Z', 'y', 'y', 'y'), inherited = HB_TAG('Z', 'i', 'n', 'h'); for (unsigned t = 0; t < s->lookup_count[0]; ++t) @@ -629,6 +759,9 @@ void vega_shaping_check(hb_font_t *font, const VegaShaping *s, uint32_t script, hb_codepoint_t glyph; if (!hb_font_get_nominal_glyph(font, code, &glyph) || c == VEGA_CC || c == VEGA_CS || c == VEGA_CO) continue; bool mark = c == VEGA_MN || c == VEGA_MC || c == VEGA_ME; + if (hangul && ((code >= 0x1100 && code <= 0x115F) || (code >= 0xA960 && code <= 0xA97C))) pool_add(&jamo_pools[0], code); + if (hangul && ((code >= 0x1160 && code <= 0x11A7) || (code >= 0xD7B0 && code <= 0xD7C6))) pool_add(&jamo_pools[1], code); + if (hangul && ((code >= 0x11A8 && code <= 0x11FF) || (code >= 0xD7CB && code <= 0xD7FB))) pool_add(&jamo_pools[2], code); if (mark && (of == script || of == inherited)) pool_add(&marks, code); else if (!mark && of == script && c >= VEGA_LU && c <= VEGA_LO) pool_add(&letters, code); else if (!mark && (of == script || of == common) && c != VEGA_ZL && c != VEGA_ZP) pool_add(&others, code); @@ -656,6 +789,10 @@ void vega_shaping_check(hb_font_t *font, const VegaShaping *s, uint32_t script, } unsigned length = 1 + below(&random, 8); for (unsigned k = 0; k < length && n < 200; ++k) { + if (hangul && below(&random, 2)) { + n = hangul_syllable(&random, jamo_pools, text, n); + continue; + } text[n++] = hot.count && below(&random, 3) == 0 ? pick(&random, &hot) : pick(&random, &letters); if (marks.count && below(&random, 100) < mark_rate) { text[n++] = pick(&random, &marks); @@ -720,4 +857,5 @@ void vega_shaping_check(hb_font_t *font, const VegaShaping *s, uint32_t script, free(marks.items); free(others.items); free(hot.items); + for (int k = 0; k < 3; ++k) free(jamo_pools[k].items); } diff --git a/text/shape-build.h b/text/shape-build.h index dd93177..ad92085 100644 --- a/text/shape-build.h +++ b/text/shape-build.h @@ -10,12 +10,11 @@ typedef struct { unsigned char *bytes; size_t size; } VegaSection; /* A section for face: plans for each script (ISO 15924 tags) and both - * directions, compiled as - * HarfBuzz's planner compiles it. Refuses a script HarfBuzz shapes with a - * shaper other than the default, Arabic and Hebrew ones, or a font that - * needs what those do without GPOS (fallback positioning, 'kern' tables, - * AAT). glyphs, when given, gets every glyph shaping can reach from codes - * (for their outlines). */ + * directions, compiled as HarfBuzz's planner compiles it. Refuses a script + * HarfBuzz shapes with a shaper other than the default, Arabic, Hebrew and + * Hangul ones, and a font with AAT tables (morx, kerx, trak) or a kern + * table of state machines. glyphs, when given, gets every glyph shaping can + * reach from codes (for their outlines). */ 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); 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; diff --git a/text/shape.h b/text/shape.h index 5fe7c4e..fdec7ee 100644 --- a/text/shape.h +++ b/text/shape.h @@ -24,22 +24,33 @@ * u32 advances_offset (glyph_count u16), * u32 offset, u32 size of GDEF, of GSUB and of GPOS (0 0 when absent), * u32 plan_count, u32 plans_offset (VEGA_PLAN_BYTES each), - * u32 step_count, u32 steps_offset (8 bytes each) + * u32 step_count, u32 steps_offset (8 bytes each), + * u32 offset, u32 size of the kern table (0 0 when absent), + * u32 extents_offset (glyph_count of i16 x_bearing, + * y_bearing, width, height; 0 when + * no plan positions marks without + * GPOS; x_bearing INT16_MIN: none), + * u32 0 * Offsets are from the section's start. A plan: * u32 script (ISO 15924 as a tag: 'Latn'), u8 direction, u8 native * direction (of its script; 0 when it has none), u8 shaper, u8 flags, - * u32 global mask, u32 masks[VEGA_PLAN_MASKS], u32 first GSUB step, - * u32 GSUB steps, u32 first GPOS step, u32 GPOS steps. + * u32 global mask, u32 kern mask, u32 masks[VEGA_PLAN_MASKS], u32 first + * GSUB step, u32 GSUB steps, u32 first GPOS step, u32 GPOS steps. * A step, a lookup to run: u16 lookup index, u8 flags, u8 0, u32 mask. */ -enum { VEGA_SHAPING_HEADER = 64, VEGA_PLAN_BYTES = 72, VEGA_STEP_BYTES = 8, VEGA_PLAN_MASKS = 11 }; +enum { VEGA_SHAPING_HEADER = 80, VEGA_PLAN_BYTES = 88, VEGA_STEP_BYTES = 8, VEGA_PLAN_MASKS = 14 }; enum { VEGA_SHAPE_LTR = 1, VEGA_SHAPE_RTL = 2 }; -enum { VEGA_SHAPER_DEFAULT, VEGA_SHAPER_ARABIC, VEGA_SHAPER_HEBREW }; -/* Plan flags. */ +enum { VEGA_SHAPER_DEFAULT, VEGA_SHAPER_ARABIC, VEGA_SHAPER_HEBREW, VEGA_SHAPER_HANGUL }; +/* Plan flags: GPOS runs; mark advances are zeroed after it (by GDEF); glyph + * classes come from Unicode (the font has none); the font positions marks + * (a 'mark' feature); automatic fractions; the kern table kerns; marks are + * placed around their base without GPOS, and zeroing a mark's advance keeps + * it where it was. */ enum { VEGA_PLAN_GPOS = 1, VEGA_PLAN_ZERO_MARKS = 2, VEGA_PLAN_GUESS_CLASSES = 4, VEGA_PLAN_GPOS_MARK = 8, - VEGA_PLAN_FRACTIONS = 16 }; + VEGA_PLAN_FRACTIONS = 16, VEGA_PLAN_KERN = 32, VEGA_PLAN_FALLBACK_MARKS = 64, VEGA_PLAN_ADJUST_ZEROING = 128 }; /* The masks a plan names, for the features set glyph by glyph. */ enum { VEGA_MASK_ISOL, VEGA_MASK_FINA, VEGA_MASK_FIN2, VEGA_MASK_FIN3, VEGA_MASK_MEDI, VEGA_MASK_MED2, - VEGA_MASK_INIT, VEGA_MASK_RTLM, VEGA_MASK_FRAC, VEGA_MASK_NUMR, VEGA_MASK_DNOM }; + VEGA_MASK_INIT, VEGA_MASK_RTLM, VEGA_MASK_FRAC, VEGA_MASK_NUMR, VEGA_MASK_DNOM, VEGA_MASK_LJMO, VEGA_MASK_VJMO, + VEGA_MASK_TJMO }; /* Step flags. */ enum { VEGA_STEP_AUTO_ZWNJ = 1, VEGA_STEP_AUTO_ZWJ = 2, VEGA_STEP_RANDOM = 4, VEGA_STEP_PER_SYLLABLE = 8 }; @@ -64,8 +75,8 @@ typedef struct { size_t size; uint16_t upem, glyph_count; uint32_t cmap_count, plan_count; - const unsigned char *cmap, *advances, *plans, *steps; - VegaTable gdef, gsub, gpos; + const unsigned char *cmap, *advances, *plans, *steps, *extents; + VegaTable gdef, gsub, gpos, kern; uint32_t glyph_classes, attach_classes, mark_sets; /* in GDEF; 0 when absent */ VegaLookup *lookups[2]; /* GSUB, GPOS */ uint32_t lookup_count[2]; |
