aboutsummaryrefslogtreecommitdiffstats
path: root/text/shape.c
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-09-28 15:07:51 +0100
committereunhhu <[email protected]>2026-09-28 15:07:51 +0100
commit218298bb16ef6c147aa6dafc3ea88f62c6fa6ef6 (patch)
tree851fa75c1a649fce5b8010c97f1ab36d5a60a035 /text/shape.c
parent5788d7e5bd6a26421346d69ae517502527940a0b (diff)
downloadvega-218298bb16ef6c147aa6dafc3ea88f62c6fa6ef6.tar.gz
vega-218298bb16ef6c147aa6dafc3ea88f62c6fa6ef6.zip
Shape Thai and Lao as HarfBuzz does
The Thai shaper, from HarfBuzz 10.2's source: SARA AM comes apart into NIKHAHIT and SARA AA, the NIKHAHIT moving back over the above-base marks (and counting as a mark when advances are zeroed). A font with no Thai script of its own (only DFLT or latn in GSUB) gets its marks shifted to its private-use glyphs, Windows' before the Mac's, as the stacking around the base asks; the planner records, as HarfBuzz's does, whether the font had the script under its own tag. Noto Sans Thai and Lao, TLWG Loma, FreeSerif's Thai, DejaVu Sans's Lao and Unifont shape as HarfBuzz does at 3,000 runs a plan, and Loma with its thai script taken out (so the private-use path runs) at 5,000.
Diffstat (limited to 'text/shape.c')
-rw-r--r--text/shape.c119
1 files changed, 118 insertions, 1 deletions
diff --git a/text/shape.c b/text/shape.c
index 76d5409..20fea05 100644
--- a/text/shape.c
+++ b/text/shape.c
@@ -227,7 +227,7 @@ bool vega_shaping_open(VegaShaping *s, const unsigned char *bytes, size_t size,
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 + 80), le32(p + 84)}, {le32(p + 88), le32(p + 92)}};
- if ((p[4] != VEGA_SHAPE_LTR && p[4] != VEGA_SHAPE_RTL) || p[5] > VEGA_SHAPE_RTL || p[6] > VEGA_SHAPER_USE)
+ if ((p[4] != VEGA_SHAPE_LTR && p[4] != VEGA_SHAPE_RTL) || p[5] > VEGA_SHAPE_RTL || p[6] > VEGA_SHAPER_THAI)
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) {
@@ -3510,6 +3510,122 @@ static bool use_reorder(const VegaShaping *s, VegaShaper *b) {
return changed;
}
+/* ---- Thai and Lao (hb-ot-shaper-thai.cc): SARA AM taken apart into
+ * NIKHAHIT and SARA AA, the NIKHAHIT moved back over the above-base marks;
+ * in a font with no Thai script, marks shifted by the font's private-use
+ * glyphs (Windows' or the Mac's) as the stacking around the base asks. */
+
+enum { THAI_NC, THAI_AC, THAI_RC, THAI_DC, THAI_NOT_CONSONANT };
+enum { THAI_AV, THAI_BV, THAI_T, THAI_NOT_MARK };
+enum { THAI_NOP, THAI_SD, THAI_SL, THAI_SDL, THAI_RD };
+
+static unsigned thai_consonant_type(uint32_t u) {
+ if (u == 0x0E1B || u == 0x0E1D || u == 0x0E1F) return THAI_AC;
+ if (u == 0x0E0D || u == 0x0E10) return THAI_RC;
+ if (u == 0x0E0E || u == 0x0E0F) return THAI_DC;
+ if (u >= 0x0E01 && u <= 0x0E2E) return THAI_NC;
+ return THAI_NOT_CONSONANT;
+}
+
+static unsigned thai_mark_type(uint32_t u) {
+ if (u == 0x0E31 || (u >= 0x0E34 && u <= 0x0E37) || u == 0x0E47 || (u >= 0x0E4D && u <= 0x0E4E)) return THAI_AV;
+ if (u >= 0x0E38 && u <= 0x0E3A) return THAI_BV;
+ if (u >= 0x0E48 && u <= 0x0E4C) return THAI_T;
+ return THAI_NOT_MARK;
+}
+
+/* The private-use code point the font has for u shifted by action
+ * (Windows' before the Mac's), or u. */
+static uint32_t thai_pua_shape(const VegaShaping *s, uint32_t u, unsigned action) {
+ static const uint16_t sd[][3] = {{0x0E48, 0xF70A, 0xF88B}, {0x0E49, 0xF70B, 0xF88E}, {0x0E4A, 0xF70C, 0xF891},
+ {0x0E4B, 0xF70D, 0xF894}, {0x0E4C, 0xF70E, 0xF897}, {0x0E38, 0xF718, 0xF89B},
+ {0x0E39, 0xF719, 0xF89C}, {0x0E3A, 0xF71A, 0xF89D}, {0, 0, 0}};
+ static const uint16_t sdl[][3] = {{0x0E48, 0xF705, 0xF88C}, {0x0E49, 0xF706, 0xF88F}, {0x0E4A, 0xF707, 0xF892},
+ {0x0E4B, 0xF708, 0xF895}, {0x0E4C, 0xF709, 0xF898}, {0, 0, 0}};
+ static const uint16_t sl[][3] = {{0x0E48, 0xF713, 0xF88A}, {0x0E49, 0xF714, 0xF88D}, {0x0E4A, 0xF715, 0xF890},
+ {0x0E4B, 0xF716, 0xF893}, {0x0E4C, 0xF717, 0xF896}, {0x0E31, 0xF710, 0xF884},
+ {0x0E34, 0xF701, 0xF885}, {0x0E35, 0xF702, 0xF886}, {0x0E36, 0xF703, 0xF887},
+ {0x0E37, 0xF704, 0xF888}, {0x0E47, 0xF712, 0xF889}, {0x0E4D, 0xF711, 0xF899},
+ {0, 0, 0}};
+ static const uint16_t rd[][3] = {{0x0E0D, 0xF70F, 0xF89A}, {0x0E10, 0xF700, 0xF89E}, {0, 0, 0}};
+ const uint16_t(*mappings)[3] = action == THAI_SD ? sd : action == THAI_SDL ? sdl : action == THAI_SL ? sl
+ : action == THAI_RD ? rd : NULL;
+ if (!mappings) return u;
+ uint32_t glyph;
+ for (; mappings[0][0]; mappings++)
+ if (mappings[0][0] == u) {
+ if (nominal(s, mappings[0][1], &glyph)) return mappings[0][1];
+ if (nominal(s, mappings[0][2], &glyph)) return mappings[0][2];
+ break;
+ }
+ return u;
+}
+
+static void thai_pua_shaping(const VegaShaping *s, VegaShaper *b) {
+ /* above: T0..T3; below: B0..B2 (hb-ot-shaper-thai.cc's state machines) */
+ static const uint8_t above_start[5] = {0, 1, 0, 0, 3}, below_start[5] = {0, 0, 1, 2, 2};
+ static const uint8_t above[4][3][2] = {{{THAI_NOP, 3}, {THAI_NOP, 0}, {THAI_SD, 3}},
+ {{THAI_SL, 2}, {THAI_NOP, 1}, {THAI_SDL, 2}},
+ {{THAI_NOP, 3}, {THAI_NOP, 2}, {THAI_SL, 3}},
+ {{THAI_NOP, 3}, {THAI_NOP, 3}, {THAI_NOP, 3}}};
+ static const uint8_t below[3][3][2] = {{{THAI_NOP, 0}, {THAI_NOP, 2}, {THAI_NOP, 0}},
+ {{THAI_NOP, 1}, {THAI_RD, 2}, {THAI_NOP, 1}},
+ {{THAI_NOP, 2}, {THAI_SD, 2}, {THAI_NOP, 2}}};
+ unsigned above_state = above_start[THAI_NOT_CONSONANT], below_state = below_start[THAI_NOT_CONSONANT];
+ size_t base = 0;
+ for (size_t i = 0; i < b->len; ++i) {
+ unsigned mt = thai_mark_type(b->info[i].codepoint);
+ if (mt == THAI_NOT_MARK) {
+ unsigned ct = thai_consonant_type(b->info[i].codepoint);
+ above_state = above_start[ct];
+ below_state = below_start[ct];
+ base = i;
+ continue;
+ }
+ const uint8_t *a = above[above_state][mt], *w = below[below_state][mt];
+ above_state = a[1];
+ below_state = w[1];
+ unsigned action = a[0] != THAI_NOP ? a[0] : w[0];
+ if (action == THAI_RD) b->info[base].codepoint = thai_pua_shape(s, b->info[base].codepoint, action);
+ else b->info[i].codepoint = thai_pua_shape(s, b->info[i].codepoint, action);
+ }
+}
+
+static bool thai_above_base_mark(uint32_t u) {
+ u &= ~0x80u;
+ return (u >= 0x0E34 && u <= 0x0E37) || (u >= 0x0E47 && u <= 0x0E4E) || u == 0x0E31 || u == 0x0E3B;
+}
+
+static void preprocess_thai(const VegaShaping *s, const Plan *plan, VegaShaper *b) {
+ clear_output(b);
+ size_t count = b->len;
+ while (b->idx < count && b->successful) {
+ uint32_t u = b->info[b->idx].codepoint;
+ if ((u & ~0x80u) != 0x0E33) { /* not SARA AM */
+ next_glyph(b);
+ continue;
+ }
+ output_glyph(b, u - 0x0E33 + 0x0E4D); /* NIKHAHIT, continuing the character before */
+ if (b->out_len) b->out[b->out_len - 1].uprops |= UPROPS_CONTINUATION;
+ replace_glyph(b, u - 1); /* SARA AA */
+ if (!b->successful) break;
+ size_t end = b->out_len;
+ /* The NIKHAHIT counts as a mark (of class 0) when widths are zeroed. */
+ b->out[end - 2].uprops = (uint16_t)((b->out[end - 2].uprops & ~0x1Fu) | VEGA_MN);
+ size_t start = end - 2;
+ while (start > 0 && thai_above_base_mark(b->out[start - 1].codepoint)) start--;
+ if (start + 2 < end) {
+ merge_out_clusters(b, start, end);
+ Info t = b->out[end - 2];
+ memmove(b->out + start + 1, b->out + start, (end - start - 2) * sizeof(Info));
+ b->out[start] = t;
+ } else if (start)
+ merge_out_clusters(b, start - 1, end);
+ }
+ sync(b);
+ if (plan->shaper_flags & VEGA_THAI_PUA) thai_pua_shaping(s, b);
+}
+
/* The shaper's part of the work at a pause in GSUB; whether it changed the
* buffer. */
static bool shaper_pause(const VegaShaping *s, const Plan *plan, VegaShaper *b, unsigned pause) {
@@ -3656,6 +3772,7 @@ long vega_shape(VegaShaper *b, const VegaShaping *s, const uint32_t *codes, size
ensure_native_direction(b, &plan);
if (plan.shaper == VEGA_SHAPER_HANGUL) preprocess_hangul(s, b);
if (plan.shaper == VEGA_SHAPER_INDIC || plan.shaper == VEGA_SHAPER_USE) preprocess_vowel_constraints(&plan, b);
+ if (plan.shaper == VEGA_SHAPER_THAI) preprocess_thai(s, &plan, b);
/* Mirror what the font mirrors; mark the rest for rtlm. */
if (direction == VEGA_SHAPE_RTL)