aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-09-28 15:20:08 +0100
committereunhhu <[email protected]>2026-09-28 15:20:08 +0100
commitbafb710f0047751e26b87897eaf2a05609708536 (patch)
treea5ccb5e8a3c7cc936b1b9f2c7e46fbeeee36cf92
parent218298bb16ef6c147aa6dafc3ea88f62c6fa6ef6 (diff)
downloadvega-bafb710f0047751e26b87897eaf2a05609708536.tar.gz
vega-bafb710f0047751e26b87897eaf2a05609708536.zip
Shape Syriac, stretch with stch, and fall back for fonts without Arabic forms
The rest of HarfBuzz 10.2's Arabic shaper. Joining types now come from HarfBuzz's own table (Unicode 16, with Syriac's Alaph and Dalath-Rish groups the state machine's last two columns use), the general category deciding where it has none, so Syriac shapes with fin2, fin3 and med2. What stch multiplies is recorded and, after positioning, stretched under the word before it, repeating pieces copied as often as fit. A font with presentation forms but no Arabic features gets HarfBuzz's fallback lookups, which the build tool writes as OpenType and the runtime applies at their pause like any GSUB lookup. The generator now reads HarfBuzz's generated tables through their lookup functions' ranges and offsets: rows whose range starts mid-row have blank cells in front, which the row comments alone miscounted (Arabic Supplement); the Indic table, read again this way, did not change. Noto Sans Syriac, FreeSans's Syriac, Unifont's Arabic and Noto Sans Arabic shape as HarfBuzz does at 3,000 runs a plan; no installed font is refused any more (213 fonts, 1,822 plans).
-rw-r--r--docs/design/004-text.md27
-rw-r--r--text/shape-build.c222
-rw-r--r--text/shape.c177
-rw-r--r--text/shape.h21
-rw-r--r--text/syllabic-data.c328
-rw-r--r--text/syllabic.h13
-rw-r--r--text/syllabic.py105
7 files changed, 853 insertions, 40 deletions
diff --git a/docs/design/004-text.md b/docs/design/004-text.md
index c62ff65..13f9d96 100644
--- a/docs/design/004-text.md
+++ b/docs/design/004-text.md
@@ -159,9 +159,9 @@ Malayalam은 psts와 rlig가 한 조회를 나눠 쓰는데, 안정 정렬로는
결과(Pi 5):
-- 설치된 글꼴 213벌, 계획 1,804개에 각 1천 실행: 다른 실행 0(글자 모양이 비트맵뿐인 Noto Color Emoji는 심볼 단계에서
- 빠진다). 인터넷에서 받은 Noto 21벌(인도계 아홉, 싱할라, 크메르, 미얀마, 티베트, 자바, 발리, 몽골, 태국, 라오 등), 계획
- 84개에 각 2천 실행: 다른 실행 0. 인도계가 있는 글꼴은 따로 계획마다 2만 실행: FreeSerif(여덟 문자), FreeSans, 굵은 꼴들, FreeMono, Unifont(아홉
+- 설치된 글꼴 213벌, 계획 1,822개에 각 1천 실행: 다른 실행 0(글자 모양이 비트맵뿐인 Noto Color Emoji는 심볼 단계에서
+ 빠진다). 설치된 글꼴이 거부되는 일은 이제 없다. 인터넷에서 받은 Noto 21벌(인도계 아홉, 싱할라, 크메르, 미얀마, 티베트,
+ 자바, 발리, 몽골, 태국, 라오, 시리아, 아랍 등)과 태국 문자 체계를 지운 Loma, 계획 92개에 각 2천 실행: 다른 실행 0. 인도계가 있는 글꼴은 따로 계획마다 2만 실행: FreeSerif(여덟 문자), FreeSans, 굵은 꼴들, FreeMono, Unifont(아홉
문자, GSUB 없음) 130개 계획 260만 실행, 다른 실행 0.
- 새 규격 글꼴(dev2 등): 인터넷에서 받은 Noto Sans 아홉 벌(데바나가리부터 말라얄람까지), 계획마다 5천 실행, 다른 실행 0.
- 속도: HarfBuzz의 1.25–1.3배 시간(Noto Sans Devanagari 문자당 1,614 대 1,235 ns, Tamil 674 대 526 ns).
@@ -208,11 +208,28 @@ U+180B–180E만 넣고 네 번째 자유 이체 선택자(U+180F)를 빠뜨렸�
실행 0. 사용자 영역 경로는 설치된 글꼴에 쓸 일이 없어서, Loma에서 GSUB의 thai 문자 체계만 지운 글꼴을 만들어 5천 실행으로
확인했다(다른 실행 0).
+### 아랍 셰이퍼의 나머지: 시리아 문자, 늘이기, 대체 셰이핑
+
+- 연결 종류는 HarfBuzz의 표(`hb-ot-shaper-arabic-table.hh`, 유니코드 16)에서 읽는다. 시리아 문자의 Alaph와 Dalath-Rish
+ 무리가 거기 있고, 상태 기계의 두 열이 그것을 쓴다(fin2, fin3, med2). 표에 없는 글자는 일반 범주로 가른다(Mn, Me, Cf는
+ T). 이 표를 읽다가 생성기의 결함을 찾았다. HarfBuzz의 생성 표는 범위가 행 중간에서 시작하면 앞 칸을 공백으로 비우는데,
+ 행 주석만 믿고 칸을 세던 파서가 그런 행에서 어긋났다(아랍 보충 블록 등). 이제 표를 조회 함수의 범위와 오프셋으로 읽는다.
+ 인도계 표를 같은 방식으로 다시 읽어 보니 한 칸도 바뀌지 않았다.
+- 늘이기(stch): 시리아 약어 표시(U+070F)와 아랍 숫자 기호(U+0600–0605) 같은 글자를 글꼴의 stch가 여러 조각으로 나누면,
+ 위치를 다 잡은 뒤 조각들이 앞 낱말의 폭을 덮도록 반복 조각을 복사해 늘어놓는다(재고, 넘치면 조금씩 겹친다).
+- 대체 셰이핑: 아랍 형태 기능이 없는 글꼴(Unifont)이면 HarfBuzz는 글꼴의 문자 지도에 있는 표시형(U+FB50–FEFF)으로 조회를
+ 만들어 적용한다. 빌드 도구가 같은 조회를 OpenType으로 직접 써서(init·medi·fina·isol의 단일 치환, rlig의 세 합자 조회)
+ 구역에 싣고, 런타임은 멈춤에서 그것을 여느 GSUB 조회처럼 적용한다. 두 글자가 한 글리프를 나누는 글꼴은 HarfBuzz의 정렬
+ 순서가 결과를 가르므로 거부한다.
+
+결과: Noto Sans Syriac, FreeSans의 시리아 문자(늘이기 포함), Unifont의 아랍(대체 셰이핑), Noto Sans Arabic, 계획마다 3천
+실행, 다른 실행 0. 약어 표시 하나가 낱말 아래로 아홉 조각까지 늘어나는 것도 HarfBuzz와 글리프·위치가 같다.
+
받지 않는 것(빌드 도구가 이유를 말하고 거부한다):
- `kern` 표의 상태 기계(Apple 형식 1), 등급 형식(2, 3), 교차 방향 커닝. 설치된 글꼴의 `kern` 표는 모두 짝 형식(0)이다.
-- AAT 표(morx, kerx, trak), 늘이기(stch), 아랍 형태가 없어 HarfBuzz가 표시형으로 대체 셰이핑을 하는 글꼴(Unifont의 아랍).
-- 시리아 문자(아랍 셰이퍼의 Alaph·Dalath-Rish 연결과 fin2·fin3·med2), 미얀마 Zawgyi.
+- AAT 표(morx, kerx, trak).
+- 미얀마 Zawgyi(표준이 아닌 인코딩, 문자 체계 태그 Qaag로만 오는 것).
- 이체 선택자(cmap 형식 14)는 싣지 않는다. 선택자는 숨기고 앞 글자는 기본 글리프로 둔다.
## 비례폭 글줄
diff --git a/text/shape-build.c b/text/shape-build.c
index 96bc748..7cb251d 100644
--- a/text/shape-build.c
+++ b/text/shape-build.c
@@ -334,7 +334,9 @@ static int categorize(uint32_t script, hb_tag_t gsub_script) {
if (script == TAG("Hang")) return VEGA_SHAPER_HANGUL;
if (script == TAG("Khmr")) return VEGA_SHAPER_KHMER;
if (script == TAG("Thai") || script == TAG("Laoo")) return VEGA_SHAPER_THAI;
- if (script == TAG("Syrc") || script == TAG("Qaag")) return -1;
+ /* Syriac is Arabic's, unless the font has only DFLT for it. */
+ if (script == TAG("Syrc")) return gsub_script != HB_OT_TAG_DEFAULT_SCRIPT ? VEGA_SHAPER_ARABIC : VEGA_SHAPER_DEFAULT;
+ if (script == TAG("Qaag")) return -1;
if (listed(indic_scripts, sizeof(indic_scripts) / sizeof(*indic_scripts), script))
return dflt ? VEGA_SHAPER_DEFAULT : (gsub_script & 0xFF) == '3' ? VEGA_SHAPER_USE : VEGA_SHAPER_INDIC;
if (listed(use_scripts, sizeof(use_scripts) / sizeof(*use_scripts), script))
@@ -466,7 +468,7 @@ static bool plan_for(hb_face_t *face, uint32_t script_tag, int direction, Plan *
if (shaper == VEGA_SHAPER_ARABIC) {
m->is_simple = false;
enable_feature(m, TAG("stch"), F_NONE, 1);
- add_gsub_pause(m, 0); /* record_stch */
+ add_gsub_pause(m, VEGA_PAUSE_RECORD_STCH);
enable_feature(m, TAG("ccmp"), F_MANUAL_ZWJ, 1);
enable_feature(m, TAG("locl"), F_MANUAL_ZWJ, 1);
add_gsub_pause(m, 0);
@@ -476,7 +478,7 @@ static bool plan_for(hb_face_t *face, uint32_t script_tag, int direction, Plan *
add_gsub_pause(m, 0);
}
enable_feature(m, TAG("rlig"), F_MANUAL_ZWJ | F_HAS_FALLBACK, 1);
- if (script == HB_SCRIPT_ARABIC) add_gsub_pause(m, 0); /* arabic_fallback_shape */
+ if (script == HB_SCRIPT_ARABIC) add_gsub_pause(m, VEGA_PAUSE_ARABIC_FALLBACK);
enable_feature(m, TAG("calt"), F_MANUAL_ZWJ, 1);
if (!has_feature(m, TAG("rclt"))) add_gsub_pause(m, 0);
enable_feature(m, TAG("liga"), F_MANUAL_ZWJ, 1);
@@ -614,15 +616,15 @@ static bool plan_for(hb_face_t *face, uint32_t script_tag, int direction, Plan *
bool zeroes = shaper != VEGA_SHAPER_HANGUL && shaper != VEGA_SHAPER_INDIC && shaper != VEGA_SHAPER_KHMER;
bool script_marks = zeroes && shaper != VEGA_SHAPER_MYANMAR && shaper != VEGA_SHAPER_USE && shaper != VEGA_SHAPER_THAI;
bool adjust = !apply_gpos && (!apply_kern || !kern_table.cross), fallback_marks = adjust && script_marks;
+ bool arabic_fallback = false;
if (shaper == VEGA_SHAPER_ARABIC) {
- if (one_mask(m, "stch")) FAIL("%s: the font stretches (stch)", name);
bool fallback = script == HB_SCRIPT_ARABIC;
for (int i = 0; i < 7; ++i) {
const FeatureMap *f = find_feature(m, TAG(arabic_features[i]));
bool syriac = arabic_features[i][3] == '2' || arabic_features[i][3] == '3';
fallback = fallback && (syriac || (f && f->needs_fallback));
}
- if (fallback) FAIL("%s: the font lacks Arabic's forms (HarfBuzz would fall back)", name);
+ arabic_fallback = fallback;
}
hb_direction_t native = hb_script_get_horizontal_direction(script);
uint32_t frac = one_mask(m, "frac"), numr = one_mask(m, "numr"), dnom = one_mask(m, "dnom");
@@ -641,6 +643,9 @@ static bool plan_for(hb_face_t *face, uint32_t script_tag, int direction, Plan *
plan->masks[VEGA_MASK_DNOM] = dnom;
if (shaper == VEGA_SHAPER_ARABIC || shaper == VEGA_SHAPER_USE)
for (int i = 0; i < 7; ++i) plan->masks[VEGA_MASK_ISOL + i] = one_mask(m, arabic_features[i]);
+ if (shaper == VEGA_SHAPER_ARABIC) plan->masks[VEGA_MASK_ARABIC_RLIG] = one_mask(m, "rlig");
+ if (arabic_fallback) plan->shaper_flags |= VEGA_ARABIC_FALLBACK;
+ if (shaper == VEGA_SHAPER_ARABIC && one_mask(m, "stch")) plan->shaper_flags |= VEGA_ARABIC_STCH;
if (shaper == VEGA_SHAPER_USE) plan->masks[VEGA_MASK_USE_RPHF] = one_mask(m, "rphf");
if (shaper == VEGA_SHAPER_HANGUL) {
plan->masks[VEGA_MASK_LJMO] = one_mask(m, "ljmo");
@@ -705,6 +710,194 @@ static void set32(Out *o, size_t at, uint32_t v) {
for (int i = 0; i < 4; ++i) o->bytes[at + i] = (unsigned char)(v >> (8 * i));
}
+/* ---- Arabic fallback shaping (hb-ot-shaper-arabic-fallback.hh): for a font
+ * with presentation forms but no Arabic features, lookups made from its
+ * character map, written as OpenType (the runtime applies them as any
+ * GSUB lookup): single substitutions to the init, medi, fina and isol
+ * forms, and rlig's ligatures of three, of two, and of marks. */
+
+typedef struct { unsigned char *bytes; size_t size, allocated; bool failed; } Bytes;
+static void be16(Bytes *b, uint32_t v) {
+ if (b->failed) return;
+ if (b->size + 2 > b->allocated) {
+ size_t a = b->allocated ? 2 * b->allocated : 256;
+ unsigned char *grown = realloc(b->bytes, a);
+ if (!grown) {
+ b->failed = true;
+ return;
+ }
+ b->bytes = grown;
+ b->allocated = a;
+ }
+ b->bytes[b->size++] = (unsigned char)(v >> 8);
+ b->bytes[b->size++] = (unsigned char)v;
+}
+static void be16_at(Bytes *b, size_t at, uint32_t v) {
+ if (b->failed) return;
+ b->bytes[at] = (unsigned char)(v >> 8);
+ b->bytes[at + 1] = (unsigned char)v;
+}
+
+static bool glyph_of(hb_font_t *font, uint32_t u, uint32_t *glyph) {
+ hb_codepoint_t g;
+ if (!u || !hb_font_get_nominal_glyph(font, u, &g)) return false;
+ *glyph = g;
+ return true;
+}
+
+/* Glyphs sorted stably, their partners carried along; false if two are
+ * the same (the font maps two characters to one glyph). */
+static bool sort_pairs(uint32_t *keys, uint32_t *values, size_t n) {
+ for (size_t i = 1; i < n; ++i)
+ for (size_t j = i; j > 0 && keys[j - 1] > keys[j]; --j) {
+ uint32_t k = keys[j], v = values[j];
+ keys[j] = keys[j - 1], values[j] = values[j - 1];
+ keys[j - 1] = k, values[j - 1] = v;
+ }
+ for (size_t i = 1; i < n; ++i)
+ if (keys[i] == keys[i - 1]) return false;
+ return true;
+}
+
+/* A lookup (type, flags, one subtable) at the end of b; the subtable's
+ * start, for its offsets. */
+static size_t start_lookup(Bytes *b, unsigned type, unsigned flags) {
+ size_t at = b->size;
+ be16(b, type);
+ be16(b, flags);
+ be16(b, 1);
+ be16(b, 8); /* the subtable, right after */
+ return at + 8;
+}
+static void coverage_of(Bytes *b, const uint32_t *glyphs, size_t n) {
+ be16(b, 1);
+ be16(b, (uint32_t)n);
+ for (size_t i = 0; i < n; ++i) be16(b, glyphs[i]);
+}
+
+/* The lookup for fallback feature k into b; false with nothing written
+ * when it would be empty, and *refused when the font cannot have it. */
+static bool fallback_lookup(hb_font_t *font, int k, Bytes *b, bool *refused) {
+ enum { FORMS = VEGA_ARABIC_FORMS_LAST - VEGA_ARABIC_FORMS_FIRST + 1 };
+ if (k < 4) {
+ uint32_t glyphs[FORMS], substitutes[FORMS];
+ size_t n = 0;
+ for (uint32_t u = VEGA_ARABIC_FORMS_FIRST; u <= VEGA_ARABIC_FORMS_LAST; ++u) {
+ uint32_t g, s;
+ if (!glyph_of(font, u, &g) || !glyph_of(font, vega_arabic_forms[u - VEGA_ARABIC_FORMS_FIRST][k], &s) || g == s)
+ continue;
+ glyphs[n] = g;
+ substitutes[n++] = s;
+ }
+ if (!n) return false;
+ if (!sort_pairs(glyphs, substitutes, n)) return !(*refused = true);
+ size_t sub = start_lookup(b, 1, 0x0008);
+ be16(b, 2);
+ be16(b, (uint32_t)(6 + 2 * n)); /* the coverage, after the substitutes */
+ be16(b, (uint32_t)n);
+ for (size_t i = 0; i < n; ++i) be16(b, substitutes[i]);
+ coverage_of(b, glyphs, n);
+ (void)sub;
+ return true;
+ }
+ const VegaArabicLigatures *table = &vega_arabic_ligatures[k - 4];
+ /* its sets: runs of one first character */
+ uint32_t firsts[64], sets[64], set_start[65];
+ size_t n_sets = 0;
+ for (unsigned i = 0; i < table->count; ++i)
+ if (!i || table->ligatures[i].first != table->ligatures[i - 1].first) {
+ if (n_sets == 64) return !(*refused = true);
+ set_start[n_sets++] = i;
+ }
+ set_start[n_sets] = table->count;
+ size_t n = 0;
+ for (size_t i = 0; i < n_sets; ++i)
+ if (glyph_of(font, table->ligatures[set_start[i]].first, &firsts[n])) sets[n++] = (uint32_t)i;
+ if (!sort_pairs(firsts, sets, n)) return !(*refused = true);
+ /* what each set keeps: ligatures whose glyph and components the font has */
+ unsigned total = 0;
+ for (size_t i = 0; i < n; ++i)
+ for (uint32_t l = set_start[sets[i]]; l < set_start[sets[i] + 1]; ++l) {
+ const VegaArabicLigature *lig = &table->ligatures[l];
+ uint32_t g;
+ bool ok = glyph_of(font, lig->ligature, &g);
+ for (unsigned c = 0; ok && c + 1 < table->length; ++c) ok = glyph_of(font, lig->components[c], &g);
+ total += ok;
+ }
+ if (!total) return false;
+ size_t sub = start_lookup(b, 4, k == 6 ? 0 : 0x0008);
+ be16(b, 1);
+ size_t coverage_at = b->size;
+ be16(b, 0);
+ be16(b, (uint32_t)n);
+ size_t offsets_at = b->size;
+ for (size_t i = 0; i < n; ++i) be16(b, 0);
+ for (size_t i = 0; i < n; ++i) {
+ be16_at(b, offsets_at + 2 * i, (uint32_t)(b->size - sub));
+ size_t set_at = b->size, count_at = b->size;
+ be16(b, 0);
+ unsigned count = 0;
+ size_t lig_offsets = b->size;
+ for (uint32_t l = set_start[sets[i]]; l < set_start[sets[i] + 1]; ++l) {
+ const VegaArabicLigature *lig = &table->ligatures[l];
+ uint32_t g;
+ bool ok = glyph_of(font, lig->ligature, &g);
+ for (unsigned c = 0; ok && c + 1 < table->length; ++c) ok = glyph_of(font, lig->components[c], &g);
+ if (ok) be16(b, 0), count++;
+ }
+ be16_at(b, count_at, count);
+ unsigned written = 0;
+ for (uint32_t l = set_start[sets[i]]; l < set_start[sets[i] + 1]; ++l) {
+ const VegaArabicLigature *lig = &table->ligatures[l];
+ uint32_t g, components[2];
+ bool ok = glyph_of(font, lig->ligature, &g);
+ for (unsigned c = 0; ok && c + 1 < table->length; ++c) ok = glyph_of(font, lig->components[c], &components[c]);
+ if (!ok) continue;
+ be16_at(b, lig_offsets + 2 * written++, (uint32_t)(b->size - set_at));
+ be16(b, g);
+ be16(b, table->length);
+ for (unsigned c = 0; c + 1 < table->length; ++c) be16(b, components[c]);
+ }
+ }
+ be16_at(b, coverage_at, (uint32_t)(b->size - sub));
+ coverage_of(b, firsts, n);
+ return true;
+}
+
+/* The fallback lookups as a GSUB table (empty script and feature lists),
+ * and what each lookup is for. */
+static bool fallback_table(hb_font_t *font, Bytes *gsub, uint8_t *features, unsigned *count, char *error,
+ size_t error_size) {
+ Bytes lookups[7] = {{0}};
+ *count = 0;
+ bool refused = false;
+ for (int k = 0; k < 7 && !refused; ++k)
+ if (fallback_lookup(font, k, &lookups[*count], &refused)) features[(*count)++] = (uint8_t)k;
+ if (refused) {
+ snprintf(error, error_size, "Arab: the font maps two characters to one glyph among those fallback shaping uses");
+ for (int k = 0; k < 7; ++k) free(lookups[k].bytes);
+ return false;
+ }
+ be16(gsub, 1);
+ be16(gsub, 0);
+ be16(gsub, 10); /* scripts */
+ be16(gsub, 12); /* features */
+ be16(gsub, 14); /* lookups */
+ be16(gsub, 0);
+ be16(gsub, 0);
+ be16(gsub, *count);
+ size_t list = 14, offsets = gsub->size;
+ for (unsigned i = 0; i < *count; ++i) be16(gsub, 0);
+ for (unsigned i = 0; i < *count; ++i) {
+ be16_at(gsub, offsets + 2 * i, (uint32_t)(gsub->size - list));
+ for (size_t k = 0; k < lookups[i].size; k += 2) be16(gsub, (uint32_t)lookups[i].bytes[k] << 8 | lookups[i].bytes[k + 1]);
+ }
+ bool failed = gsub->failed || gsub->size > 0xFFFF;
+ for (int k = 0; k < 7; ++k) failed |= lookups[k].failed, free(lookups[k].bytes);
+ if (failed) snprintf(error, error_size, "Arab: fallback lookups: out of memory or over 64 KiB");
+ return !failed;
+}
+
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) {
Plan *plans = calloc(2 * script_count + 1, sizeof(*plans));
@@ -794,6 +987,25 @@ bool vega_shaping_compile(hb_face_t *face, const uint32_t *scripts, size_t scrip
put16(&o, (uint32_t)((known ? e.height : 0) & 0xFFFF));
}
}
+ bool falls_back = false;
+ for (size_t i = 0; i < plan_count; ++i) falls_back |= plans[i].shaper_flags & VEGA_ARABIC_FALLBACK;
+ if (falls_back) {
+ Bytes gsub = {0};
+ uint8_t features[7] = {0};
+ unsigned count;
+ if (!fallback_table(font, &gsub, features, &count, error, error_size)) {
+ free(gsub.bytes);
+ goto fail;
+ }
+ align4(&o);
+ set32(&o, 76, (uint32_t)o.size);
+ unsigned char head[8] = {(unsigned char)count};
+ memcpy(head + 1, features, 7);
+ put(&o, head, 8);
+ put32(&o, (uint32_t)gsub.size);
+ put(&o, gsub.bytes, gsub.size);
+ free(gsub.bytes);
+ }
align4(&o);
set32(&o, 48, (uint32_t)plan_count);
set32(&o, 52, (uint32_t)o.size);
diff --git a/text/shape.c b/text/shape.c
index 20fea05..c9b8c0c 100644
--- a/text/shape.c
+++ b/text/shape.c
@@ -128,16 +128,17 @@ static uint32_t first_coverage(VegaTable t, int table, const VegaSubtable *st) {
return offset16(t, at, at + 2);
}
-/* A table's lookups, their subtables' types resolved through extensions. */
-static bool resolve_lookups(VegaShaping *s, int table, size_t *subtables, size_t *allocated) {
- VegaTable t = table ? s->gpos : s->gsub;
+/* A table's lookups (table 0 GSUB, 1 GPOS), their subtables' types
+ * resolved through extensions. */
+static bool resolve_lookups(VegaShaping *s, VegaTable t, int table, VegaLookup **out, uint32_t *out_count,
+ size_t *subtables, size_t *allocated) {
uint32_t list = t.size ? offset16(t, 0, 8) : 0, count = list ? get16(t, list) : 0;
- s->lookup_count[table] = count;
- if (!(s->lookups[table] = calloc(count ? count : 1, sizeof(VegaLookup)))) return false;
+ *out_count = count;
+ if (!(*out = calloc(count ? count : 1, sizeof(VegaLookup)))) return false;
uint16_t extension = table ? 9 : 7;
for (uint32_t i = 0; i < count; ++i) {
uint32_t at = offset16(t, list, list + 2 + 2 * i), n = at ? get16(t, at + 4) : 0;
- VegaLookup *l = &s->lookups[table][i];
+ VegaLookup *l = &(*out)[i];
l->type = (uint16_t)get16(t, at);
l->flags = (uint16_t)get16(t, at + 2);
l->props = l->flags | (l->flags & 0x10 ? get16(t, at + 6 + 2 * n) << 16 : 0);
@@ -222,8 +223,20 @@ bool vega_shaping_open(VegaShaping *s, const unsigned char *bytes, size_t size,
s->mark_sets = minor >= 2 ? offset16(s->gdef, 0, 12) : 0;
}
size_t subtables = 0, allocated = 0;
- if (!resolve_lookups(s, 0, &subtables, &allocated) || !resolve_lookups(s, 1, &subtables, &allocated))
+ uint32_t fallback = le32(bytes + 76);
+ if (fallback) {
+ if (!within(size, fallback, 12) || bytes[fallback] > 7 || !within(size, fallback + 12, le32(bytes + fallback + 8)))
+ REFUSE("fallback lookups");
+ s->fallback = (VegaTable){bytes + fallback + 12, le32(bytes + fallback + 8)};
+ memcpy(s->fallback_features, bytes + fallback + 1, 7);
+ }
+ if (!resolve_lookups(s, s->gsub, 0, &s->lookups[0], &s->lookup_count[0], &subtables, &allocated) ||
+ !resolve_lookups(s, s->gpos, 1, &s->lookups[1], &s->lookup_count[1], &subtables, &allocated) ||
+ !resolve_lookups(s, s->fallback, 0, &s->fallback_lookups, &s->fallback_count, &subtables, &allocated))
REFUSE("out of memory");
+ if (fallback && s->fallback_count != bytes[fallback]) REFUSE("fallback lookups");
+ for (uint32_t i = 0; i < s->fallback_count; ++i)
+ if (s->fallback_features[i] > 6) REFUSE("fallback lookups");
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)}};
@@ -236,7 +249,7 @@ bool vega_shaping_open(VegaShaping *s, const unsigned char *bytes, size_t size,
for (uint32_t k = 0; k < ranges[table][1]; ++k) {
const unsigned char *step = s->steps + (size_t)(ranges[table][0] + k) * VEGA_STEP_BYTES;
bool pause = le16(step) == VEGA_STEP_PAUSE;
- if (pause ? table || !step[2] || step[2] > VEGA_PAUSE_RECORD_PREF : le16(step) >= s->lookup_count[table])
+ if (pause ? table || !step[2] || step[2] > VEGA_PAUSE_RECORD_STCH : le16(step) >= s->lookup_count[table])
REFUSE("plan %u: a lookup the font lacks", i);
}
}
@@ -250,6 +263,8 @@ void vega_shaping_close(VegaShaping *s) {
for (int i = 0; i < 256; ++i) free(s->bmp[i]), s->bmp[i] = NULL;
free(s->lookups[0]);
free(s->lookups[1]);
+ free(s->fallback_lookups);
+ s->fallback_lookups = NULL;
free(s->subtables);
s->lookups[0] = s->lookups[1] = NULL;
s->subtables = NULL;
@@ -326,7 +341,7 @@ struct VegaShaper {
};
enum { HAS_NON_ASCII = 1, HAS_DEFAULT_IGNORABLES = 2, HAS_SPACE_FALLBACK = 4, HAS_GPOS_ATTACHMENT = 8, HAS_CGJ = 16,
- HAS_VARIATION_SELECTOR_FALLBACK = 32, HAS_BROKEN_SYLLABLE = 64 };
+ HAS_VARIATION_SELECTOR_FALLBACK = 32, HAS_BROKEN_SYLLABLE = 64, HAS_STCH = 128 };
VegaShaper *vega_shaper_new(void) { return calloc(1, sizeof(VegaShaper)); }
@@ -1137,17 +1152,16 @@ static void setup_masks_fraction(VegaShaper *b, const Plan *plan) {
}
}
-enum { ISOL, FINA, FIN2, FIN3, MEDI, MED2, INIT, NONE };
+enum { ISOL, FINA, FIN2, FIN3, MEDI, MED2, INIT, NONE, STCH_FIXED, STCH_REPEATING };
enum { JT_U, JT_L, JT_R, JT_D, JG_ALAPH, JG_DALATH_RISH, JT_T = 7 };
-static unsigned joining_type(uint32_t u) {
- switch (vega_joining(u)) {
- case VEGA_JOINING_L: return JT_L;
- case VEGA_JOINING_R: return JT_R;
- case VEGA_JOINING_D: case VEGA_JOINING_C: return JT_D;
- case VEGA_JOINING_T: return JT_T;
- default: return JT_U;
- }
+/* HarfBuzz's joining type (hb-ot-shaper-arabic-table.hh); where its table
+ * has none, T for a non-spacing or enclosing mark or a format character. */
+static unsigned joining_type(const Info *info) {
+ unsigned type = vega_arabic_joining(info->codepoint);
+ if (type != 8) return type;
+ unsigned c = gen_cat(info);
+ return c == VEGA_MN || c == VEGA_ME || c == VEGA_CF ? JT_T : JT_U;
}
static void setup_masks_arabic(VegaShaper *b, const Plan *plan) {
@@ -1162,7 +1176,7 @@ static void setup_masks_arabic(VegaShaper *b, const Plan *plan) {
size_t prev = (size_t)-1;
unsigned state = 0;
for (size_t i = 0; i < b->len; ++i) {
- unsigned type = joining_type(b->info[i].codepoint);
+ unsigned type = joining_type(&b->info[i]);
if (type == JT_T) {
b->info[i].action = NONE;
continue;
@@ -3626,10 +3640,132 @@ static void preprocess_thai(const VegaShaping *s, const Plan *plan, VegaShaper *
if (plan->shaper_flags & VEGA_THAI_PUA) thai_pua_shaping(s, b);
}
+/* Arabic fallback shaping (hb-ot-shaper-arabic-fallback.hh): the lookups
+ * the build tool made from the font's presentation forms, each under its
+ * feature's mask, applied as HarfBuzz applies a GSUB lookup outside a plan
+ * (both joiners skipped, no syllables). */
+static bool arabic_fallback(const VegaShaping *s, const Plan *plan, VegaShaper *b) {
+ if (!(plan->shaper_flags & VEGA_ARABIC_FALLBACK)) return false;
+ static const int forms[4] = {VEGA_MASK_INIT, VEGA_MASK_MEDI, VEGA_MASK_FINA, VEGA_MASK_ISOL};
+ for (uint32_t i = 0; i < s->fallback_count; ++i) {
+ unsigned feature = s->fallback_features[i];
+ uint32_t mask = plan->masks[feature < 4 ? forms[feature] : VEGA_MASK_ARABIC_RLIG];
+ if (!mask) continue;
+ Apply c = {.s = s, .b = b, .table = 0, .t = s->fallback, .lookup_mask = mask, .auto_zwnj = true,
+ .auto_zwj = true, .has_glyph_classes = s->glyph_classes != 0, .nesting_left = MAX_NESTING_LEVEL,
+ .random_state = 1, .last_base = -1};
+ apply_string(&c, &s->fallback_lookups[i]);
+ }
+ return true;
+}
+
+/* Stretching (hb-ot-shaper-arabic.cc's apply_stch), after positioning: each
+ * run of stretch pieces fills the width of the word before it, its
+ * repeating pieces copied as often as fits (overlapping a little to fit
+ * closer), fixed pieces once. Measured first, then cut. */
+static bool arabic_word_category(unsigned c) {
+ switch (c) {
+ case VEGA_CN: case VEGA_CO: case VEGA_LM: case VEGA_LO: case VEGA_MC: case VEGA_ME: case VEGA_MN: case VEGA_ND:
+ case VEGA_NL: case VEGA_NO: case VEGA_SC: case VEGA_SK: case VEGA_SM: case VEGA_SO:
+ return true;
+ default:
+ return false;
+ }
+}
+static bool stretching(const Info *i) { return i->action == STCH_FIXED || i->action == STCH_REPEATING; }
+
+static void apply_stch(const VegaShaping *s, VegaShaper *b) {
+ if (!(b->scratch & HAS_STCH)) return;
+ bool rtl = b->direction == VEGA_SHAPE_RTL;
+ if (!rtl) reverse_range(b, 0, b->len, true);
+ size_t extra = 0;
+ for (int cut = 0; cut <= 1; ++cut) {
+ size_t count = b->len, new_len = count + extra, j = new_len;
+ Info *info = b->info;
+ Position *pos = b->pos;
+ for (size_t i = count; i; i--) {
+ if (!stretching(&info[i - 1])) {
+ if (cut) {
+ --j;
+ info[j] = info[i - 1];
+ pos[j] = pos[i - 1];
+ }
+ continue;
+ }
+ int32_t w_total = 0, w_fixed = 0, w_repeating = 0;
+ int n_repeating = 0;
+ size_t end = i;
+ while (i && stretching(&info[i - 1])) {
+ i--;
+ int32_t width = advance_of(s, info[i].codepoint);
+ if (info[i].action == STCH_FIXED) w_fixed += width;
+ else w_repeating += width, n_repeating++;
+ }
+ size_t start = i, context = i;
+ while (context && !stretching(&info[context - 1]) &&
+ (is_default_ignorable(&info[context - 1]) || arabic_word_category(gen_cat(&info[context - 1])))) {
+ context--;
+ w_total += pos[context].x_advance;
+ }
+ i++;
+ int n_copies = 0;
+ int32_t w_remaining = w_total - w_fixed;
+ if (w_remaining > w_repeating && w_repeating > 0) n_copies = w_remaining / w_repeating - 1;
+ int32_t overlap = 0, shortfall = w_remaining - w_repeating * (n_copies + 1);
+ if (shortfall > 0 && n_repeating > 0) {
+ ++n_copies;
+ int32_t excess = (n_copies + 1) * w_repeating - w_remaining;
+ if (excess > 0) {
+ overlap = excess / (n_copies * n_repeating);
+ w_remaining = 0;
+ }
+ }
+ if (!cut) {
+ extra += (size_t)n_copies * (size_t)n_repeating;
+ continue;
+ }
+ int32_t x_offset = w_remaining / 2;
+ for (size_t k = end; k > start; k--) {
+ int32_t width = advance_of(s, info[k - 1].codepoint);
+ unsigned repeat = 1 + (info[k - 1].action == STCH_REPEATING ? (unsigned)n_copies : 0);
+ pos[k - 1].x_advance = 0;
+ for (unsigned n = 0; n < repeat; n++) {
+ if (rtl) {
+ x_offset -= width;
+ if (n > 0) x_offset += overlap;
+ }
+ pos[k - 1].x_offset = x_offset;
+ --j;
+ info[j] = info[k - 1];
+ pos[j] = pos[k - 1];
+ if (!rtl) {
+ x_offset += width;
+ if (n > 0) x_offset -= overlap;
+ }
+ }
+ }
+ }
+ if (!cut) {
+ if (!ensure(b, count + extra)) break;
+ } else
+ b->len = new_len;
+ }
+ if (!rtl) reverse_range(b, 0, b->len, true);
+}
+
/* 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) {
switch (plan->shaper) {
+ case VEGA_SHAPER_ARABIC:
+ /* stch just applied: what it multiplied is stretched after positioning */
+ if (pause == VEGA_PAUSE_RECORD_STCH && (plan->shaper_flags & VEGA_ARABIC_STCH))
+ for (size_t i = 0; i < b->len; ++i)
+ if (b->info[i].gprops & MULTIPLIED) {
+ b->info[i].action = lig_comp(&b->info[i]) % 2 ? STCH_REPEATING : STCH_FIXED;
+ b->scratch |= HAS_STCH;
+ }
+ return pause == VEGA_PAUSE_ARABIC_FALLBACK && arabic_fallback(s, plan, b);
case VEGA_SHAPER_INDIC: {
Indic d = indic_of(s, plan);
switch (pause) {
@@ -3843,6 +3979,9 @@ long vega_shape(VegaShaper *b, const VegaShaping *s, const uint32_t *codes, size
}
}
+ if (plan.shaper == VEGA_SHAPER_ARABIC) apply_stch(s, b);
+ if (!b->successful) return -1;
+
if (b->glyphs_allocated < b->len) {
VegaShaped *glyphs = realloc(b->glyphs, b->len * sizeof(*glyphs));
if (!glyphs) return -1;
diff --git a/text/shape.h b/text/shape.h
index 6e56819..9381754 100644
--- a/text/shape.h
+++ b/text/shape.h
@@ -31,7 +31,12 @@
* y_bearing, width, height; 0 when
* no plan positions marks without
* GPOS; x_bearing INT16_MIN: none),
- * u32 0
+ * u32 fallback_offset (Arabic fallback shaping, 0 when no
+ * plan falls back: u8 lookups, u8
+ * feature[7] each is for (init,
+ * medi, fina, isol, rlig x3), u32
+ * size, then a GSUB table of just
+ * those lookups)
* Offsets are from the section's start. A plan (VEGA_PLAN_BYTES):
* 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,
@@ -67,18 +72,22 @@ enum { VEGA_MASK_RPHF = VEGA_MASK_SHAPER, VEGA_MASK_PREF, VEGA_MASK_BLWF, VEGA_M
enum { VEGA_MASK_KHMER_PREF = VEGA_MASK_SHAPER, VEGA_MASK_KHMER_BLWF, VEGA_MASK_KHMER_ABVF, VEGA_MASK_KHMER_PSTF,
VEGA_MASK_KHMER_CFAR }; /* Khmer */
enum { VEGA_MASK_USE_RPHF = VEGA_MASK_INIT + 1 }; /* USE: that, and Arabic's for its joining forms */
+enum { VEGA_MASK_ARABIC_RLIG = VEGA_MASK_INIT + 1 }; /* Arabic, for fallback shaping */
/* The stages a plan names: Indic's, whose features decide a consonant's
* position by whether they would substitute it. */
enum { VEGA_STAGE_RPHF, VEGA_STAGE_PREF, VEGA_STAGE_BLWF, VEGA_STAGE_PSTF, VEGA_STAGE_VATU };
/* Shaper flags: Indic's font follows the old specification (deva, not
* dev2); Thai's font has no Thai script of its own, so its marks take the
- * font's private-use glyphs where it has them. */
-enum { VEGA_INDIC_OLD_SPEC = 1, VEGA_THAI_PUA = 2 };
+ * font's private-use glyphs where it has them; Arabic's font lacks Arabic's
+ * forms, so lookups made from its presentation forms stand in; Arabic's
+ * font stretches (stch) what the stretching feature multiplied. */
+enum { VEGA_INDIC_OLD_SPEC = 1, VEGA_THAI_PUA = 2, VEGA_ARABIC_FALLBACK = 4, VEGA_ARABIC_STCH = 8 };
/* Step flags; pauses, which the shaper tells apart. */
enum { VEGA_STEP_AUTO_ZWNJ = 1, VEGA_STEP_AUTO_ZWJ = 2, VEGA_STEP_RANDOM = 4, VEGA_STEP_PER_SYLLABLE = 8 };
enum { VEGA_STEP_PAUSE = 0xFFFF };
enum { VEGA_PAUSE_SYLLABLES = 1, VEGA_PAUSE_INITIAL_REORDERING, VEGA_PAUSE_FINAL_REORDERING, VEGA_PAUSE_CLEAR_SYLLABLES,
- VEGA_PAUSE_CLEAR_SUBSTITUTED, VEGA_PAUSE_RECORD_RPHF, VEGA_PAUSE_RECORD_PREF };
+ VEGA_PAUSE_CLEAR_SUBSTITUTED, VEGA_PAUSE_RECORD_RPHF, VEGA_PAUSE_RECORD_PREF, VEGA_PAUSE_ARABIC_FALLBACK,
+ VEGA_PAUSE_RECORD_STCH };
typedef struct { const unsigned char *p; uint32_t size; } VegaTable;
/* Which glyphs may be in a set, as three 64-bit patterns of glyph bits
@@ -103,6 +112,10 @@ typedef struct {
uint32_t cmap_count, plan_count;
const unsigned char *cmap, *advances, *plans, *steps, *extents;
VegaTable gdef, gsub, gpos, kern;
+ VegaTable fallback; /* Arabic fallback shaping's lookups, a GSUB table */
+ uint32_t fallback_count;
+ uint8_t fallback_features[7]; /* what each is for: init, medi, fina, isol, rlig x3 (0..6) */
+ VegaLookup *fallback_lookups;
uint32_t glyph_classes, attach_classes, mark_sets; /* in GDEF; 0 when absent */
VegaLookup *lookups[2]; /* GSUB, GPOS */
uint32_t lookup_count[2];
diff --git a/text/syllabic-data.c b/text/syllabic-data.c
index cfff1b5..cd7e59b 100644
--- a/text/syllabic-data.c
+++ b/text/syllabic-data.c
@@ -452,6 +452,334 @@ static const uint16_t use_u16[856] = {
static unsigned use_b4(const uint8_t *a, unsigned i) { return (a[i >> 1] >> ((i & 1u) << 2)) & 15u; }
uint8_t vega_use_category(uint32_t u) { return (uint8_t)(u<921600u?use_u8[2953+(((use_u8[625+(((use_u16[((use_u8[113+(((use_b4(use_u8,u>>1>>3>>3>>5))<<5)+((u>>1>>3>>3)&31u))])<<3)+((u>>1>>3)&7u)])<<3)+((u>>1)&7u))])<<1)+((u)&1u))]:0); }
+/* Arabic joining types (hb-ot-shaper-arabic-table.hh, Unicode 16): U 0, L 1, R 2, D and C 3,
+ * Alaph 4, Dalath-Rish 5, T 7; 8 where the general category decides. */
+static const uint8_t joining_cells[1434] = {
+ 0, 0, 0, 0, 0, 0, 8, 8, 0, 8, 8, 0, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 3, 0, 2, 2, 2, 2, 3, 2, 3, 2, 3, 3, 3, 3, 3, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 2, 3, 3, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 3, 3, 8, 2, 2, 2, 0, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 2, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 3, 2, 3, 3, 2, 2, 8, 2, 8, 8, 8, 8, 8, 8, 8, 0, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 2, 2, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 3, 3, 3, 8, 8, 3,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 7, 4, 8, 3, 3, 3, 5, 5, 2, 2, 2, 3, 3, 3, 3, 2, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 2, 3, 5, 3, 2, 3, 3, 5, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 3, 3, 3, 3, 2, 3, 2, 2, 3, 3, 3, 2, 2, 3, 3, 3, 3, 3, 3,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 3, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 2, 3, 3, 3, 3, 3, 2, 2, 3, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 3, 2, 2, 2, 8, 8, 8, 8, 8, 8, 8,
+ 3, 0, 3, 3, 3, 3, 0, 2, 3, 2, 2, 8, 8, 8, 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 3, 3, 3, 3, 0, 0, 3, 3, 3, 3, 3, 2, 8, 0, 0, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 0, 2, 3, 3, 2, 2, 3, 3, 3, 3, 3, 3, 2, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 0, 0, 3, 8, 8, 3, 8, 8, 8, 0, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 8, 8, 8, 8, 8, 8, 8, 0, 0, 0,
+ 0, 0, 7, 7, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 8, 3, 0, 3, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 0, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 0, 3, 3, 3, 3, 3, 2,
+ 0, 2, 0, 2, 2, 0, 0, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 1, 3, 3, 3, 3, 3, 2, 3, 3, 3, 2, 0, 0, 2, 8,
+ 8, 8, 8, 8, 8, 3, 3, 3, 3, 2, 3, 2, 3, 2, 2, 2, 3, 3, 3, 2, 3, 3, 2, 3, 2, 2, 3, 2, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 2, 2, 2, 2, 3, 3, 0, 1, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 3, 2, 3,
+ 3, 3, 3, 3, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 3, 3, 3, 2, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 3, 3, 3, 3, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 3, 0, 3, 3, 2, 2, 2, 0, 3, 2, 2, 3, 3, 2, 3, 3, 0, 3, 2, 2, 3, 0, 0, 0, 0, 2, 3, 1, 0, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 8, 8, 8, 8, 8, 8, 8, 7,
+};
+static const struct { uint32_t first, last, at; } joining_runs[11] = {
+ {0x0600, 0x08E2, 0},
+ {0x1806, 0x18AA, 739},
+ {0x200C, 0x2069, 904},
+ {0xA840, 0xA873, 998},
+ {0x10AC0, 0x10AEF, 1050},
+ {0x10B80, 0x10BAF, 1098},
+ {0x10D00, 0x10D23, 1146},
+ {0x10EC2, 0x10EC4, 1182},
+ {0x10F30, 0x10FCB, 1185},
+ {0x110BD, 0x110CD, 1341},
+ {0x1E900, 0x1E94B, 1358},
+};
+uint8_t vega_arabic_joining(uint32_t u) {
+ for (unsigned i = 0; i < 11 && u >= joining_runs[i].first; ++i)
+ if (u <= joining_runs[i].last) return joining_cells[joining_runs[i].at + u - joining_runs[i].first];
+ return 8;
+}
+
+/* Presentation forms of U+0621..U+06D3: init, medi, fina, isol. */
+const uint16_t vega_arabic_forms[179][4] = {
+ {0x0000, 0x0000, 0x0000, 0xFE80},
+ {0x0000, 0x0000, 0xFE82, 0xFE81},
+ {0x0000, 0x0000, 0xFE84, 0xFE83},
+ {0x0000, 0x0000, 0xFE86, 0xFE85},
+ {0x0000, 0x0000, 0xFE88, 0xFE87},
+ {0xFE8B, 0xFE8C, 0xFE8A, 0xFE89},
+ {0x0000, 0x0000, 0xFE8E, 0xFE8D},
+ {0xFE91, 0xFE92, 0xFE90, 0xFE8F},
+ {0x0000, 0x0000, 0xFE94, 0xFE93},
+ {0xFE97, 0xFE98, 0xFE96, 0xFE95},
+ {0xFE9B, 0xFE9C, 0xFE9A, 0xFE99},
+ {0xFE9F, 0xFEA0, 0xFE9E, 0xFE9D},
+ {0xFEA3, 0xFEA4, 0xFEA2, 0xFEA1},
+ {0xFEA7, 0xFEA8, 0xFEA6, 0xFEA5},
+ {0x0000, 0x0000, 0xFEAA, 0xFEA9},
+ {0x0000, 0x0000, 0xFEAC, 0xFEAB},
+ {0x0000, 0x0000, 0xFEAE, 0xFEAD},
+ {0x0000, 0x0000, 0xFEB0, 0xFEAF},
+ {0xFEB3, 0xFEB4, 0xFEB2, 0xFEB1},
+ {0xFEB7, 0xFEB8, 0xFEB6, 0xFEB5},
+ {0xFEBB, 0xFEBC, 0xFEBA, 0xFEB9},
+ {0xFEBF, 0xFEC0, 0xFEBE, 0xFEBD},
+ {0xFEC3, 0xFEC4, 0xFEC2, 0xFEC1},
+ {0xFEC7, 0xFEC8, 0xFEC6, 0xFEC5},
+ {0xFECB, 0xFECC, 0xFECA, 0xFEC9},
+ {0xFECF, 0xFED0, 0xFECE, 0xFECD},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0xFED3, 0xFED4, 0xFED2, 0xFED1},
+ {0xFED7, 0xFED8, 0xFED6, 0xFED5},
+ {0xFEDB, 0xFEDC, 0xFEDA, 0xFED9},
+ {0xFEDF, 0xFEE0, 0xFEDE, 0xFEDD},
+ {0xFEE3, 0xFEE4, 0xFEE2, 0xFEE1},
+ {0xFEE7, 0xFEE8, 0xFEE6, 0xFEE5},
+ {0xFEEB, 0xFEEC, 0xFEEA, 0xFEE9},
+ {0x0000, 0x0000, 0xFEEE, 0xFEED},
+ {0xFBE8, 0xFBE9, 0xFEF0, 0xFEEF},
+ {0xFEF3, 0xFEF4, 0xFEF2, 0xFEF1},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0xFB51, 0xFB50},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0xFBDD},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0xFB68, 0xFB69, 0xFB67, 0xFB66},
+ {0xFB60, 0xFB61, 0xFB5F, 0xFB5E},
+ {0xFB54, 0xFB55, 0xFB53, 0xFB52},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0xFB58, 0xFB59, 0xFB57, 0xFB56},
+ {0xFB64, 0xFB65, 0xFB63, 0xFB62},
+ {0xFB5C, 0xFB5D, 0xFB5B, 0xFB5A},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0xFB78, 0xFB79, 0xFB77, 0xFB76},
+ {0xFB74, 0xFB75, 0xFB73, 0xFB72},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0xFB7C, 0xFB7D, 0xFB7B, 0xFB7A},
+ {0xFB80, 0xFB81, 0xFB7F, 0xFB7E},
+ {0x0000, 0x0000, 0xFB89, 0xFB88},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0xFB85, 0xFB84},
+ {0x0000, 0x0000, 0xFB83, 0xFB82},
+ {0x0000, 0x0000, 0xFB87, 0xFB86},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0xFB8D, 0xFB8C},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0xFB8B, 0xFB8A},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0xFB6C, 0xFB6D, 0xFB6B, 0xFB6A},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0xFB70, 0xFB71, 0xFB6F, 0xFB6E},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0xFB90, 0xFB91, 0xFB8F, 0xFB8E},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0xFBD5, 0xFBD6, 0xFBD4, 0xFBD3},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0xFB94, 0xFB95, 0xFB93, 0xFB92},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0xFB9C, 0xFB9D, 0xFB9B, 0xFB9A},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0xFB98, 0xFB99, 0xFB97, 0xFB96},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0xFB9F, 0xFB9E},
+ {0xFBA2, 0xFBA3, 0xFBA1, 0xFBA0},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0xFBAC, 0xFBAD, 0xFBAB, 0xFBAA},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0xFBA5, 0xFBA4},
+ {0xFBA8, 0xFBA9, 0xFBA7, 0xFBA6},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0xFBE1, 0xFBE0},
+ {0x0000, 0x0000, 0xFBDA, 0xFBD9},
+ {0x0000, 0x0000, 0xFBD8, 0xFBD7},
+ {0x0000, 0x0000, 0xFBDC, 0xFBDB},
+ {0x0000, 0x0000, 0xFBE3, 0xFBE2},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0xFBDF, 0xFBDE},
+ {0xFBFE, 0xFBFF, 0xFBFD, 0xFBFC},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0xFBE6, 0xFBE7, 0xFBE5, 0xFBE4},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0xFBAF, 0xFBAE},
+ {0x0000, 0x0000, 0xFBB1, 0xFBB0},
+};
+/* Ligatures fallback shaping forms: three components, two, and marks. */
+static const VegaArabicLigature ligatures_three[3] = {
+ {0xFEDF, 0xFD88, {0xFEE4, 0xFEA4}},
+ {0xFEDF, 0xF201, {0xFEE0, 0xFEEA}},
+ {0xFEDF, 0xF211, {0xFEE4, 0xFEA0}},
+};
+static const VegaArabicLigature ligatures_two[57] = {
+ {0xFE91, 0xFC08, {0xFEE2, 0x0000}},
+ {0xFE91, 0xFC9F, {0xFEE4, 0x0000}},
+ {0xFE91, 0xFC9C, {0xFEA0, 0x0000}},
+ {0xFE91, 0xFC9D, {0xFEA4, 0x0000}},
+ {0xFE91, 0xFC9E, {0xFEA8, 0x0000}},
+ {0xFE92, 0xFC6A, {0xFEAE, 0x0000}},
+ {0xFE92, 0xFC6D, {0xFEE6, 0x0000}},
+ {0xFE92, 0xFC6F, {0xFEF2, 0x0000}},
+ {0xFE97, 0xFC0E, {0xFEE2, 0x0000}},
+ {0xFE97, 0xFCA4, {0xFEE4, 0x0000}},
+ {0xFE97, 0xFCA1, {0xFEA0, 0x0000}},
+ {0xFE97, 0xFCA2, {0xFEA4, 0x0000}},
+ {0xFE97, 0xFCA3, {0xFEA8, 0x0000}},
+ {0xFE98, 0xFC70, {0xFEAE, 0x0000}},
+ {0xFE98, 0xFC73, {0xFEE6, 0x0000}},
+ {0xFE98, 0xFC75, {0xFEF2, 0x0000}},
+ {0xFE9B, 0xFC12, {0xFEE2, 0x0000}},
+ {0xFE9F, 0xFCA8, {0xFEE4, 0x0000}},
+ {0xFEA3, 0xFCAA, {0xFEE4, 0x0000}},
+ {0xFEA7, 0xFCAC, {0xFEE4, 0x0000}},
+ {0xFEB3, 0xFCB0, {0xFEE4, 0x0000}},
+ {0xFEB7, 0xFD30, {0xFEE4, 0x0000}},
+ {0xFED3, 0xFC32, {0xFEF2, 0x0000}},
+ {0xFEDF, 0xFC3F, {0xFE9E, 0x0000}},
+ {0xFEDF, 0xFCC9, {0xFEA0, 0x0000}},
+ {0xFEDF, 0xFC40, {0xFEA2, 0x0000}},
+ {0xFEDF, 0xFCCA, {0xFEA4, 0x0000}},
+ {0xFEDF, 0xFC41, {0xFEA6, 0x0000}},
+ {0xFEDF, 0xFCCB, {0xFEA8, 0x0000}},
+ {0xFEDF, 0xFC42, {0xFEE2, 0x0000}},
+ {0xFEDF, 0xFCCC, {0xFEE4, 0x0000}},
+ {0xFEDF, 0xFC44, {0xFEF2, 0x0000}},
+ {0xFEDF, 0xFCCD, {0xFEEC, 0x0000}},
+ {0xFEDF, 0xFEF5, {0xFE82, 0x0000}},
+ {0xFEDF, 0xFEF7, {0xFE84, 0x0000}},
+ {0xFEDF, 0xFEF9, {0xFE88, 0x0000}},
+ {0xFEDF, 0xFEFB, {0xFE8E, 0x0000}},
+ {0xFEE0, 0xFC86, {0xFEF0, 0x0000}},
+ {0xFEE0, 0xFEF6, {0xFE82, 0x0000}},
+ {0xFEE0, 0xFEF8, {0xFE84, 0x0000}},
+ {0xFEE0, 0xFEFA, {0xFE88, 0x0000}},
+ {0xFEE0, 0xFEFC, {0xFE8E, 0x0000}},
+ {0xFEE3, 0xFCCE, {0xFEA0, 0x0000}},
+ {0xFEE3, 0xFCCF, {0xFEA4, 0x0000}},
+ {0xFEE3, 0xFCD0, {0xFEA8, 0x0000}},
+ {0xFEE3, 0xFCD1, {0xFEE4, 0x0000}},
+ {0xFEE7, 0xFC4E, {0xFEE2, 0x0000}},
+ {0xFEE7, 0xFCD5, {0xFEE4, 0x0000}},
+ {0xFEE7, 0xFCD2, {0xFEA0, 0x0000}},
+ {0xFEE7, 0xFCD3, {0xFEA4, 0x0000}},
+ {0xFEE8, 0xFC8F, {0xFEF2, 0x0000}},
+ {0xFEF3, 0xFCDA, {0xFEA0, 0x0000}},
+ {0xFEF3, 0xFCDB, {0xFEA4, 0x0000}},
+ {0xFEF3, 0xFCDC, {0xFEA8, 0x0000}},
+ {0xFEF3, 0xFCDD, {0xFEE4, 0x0000}},
+ {0xFEF4, 0xFC91, {0xFEAE, 0x0000}},
+ {0xFEF4, 0xFC94, {0xFEE6, 0x0000}},
+};
+static const VegaArabicLigature ligatures_marks[5] = {
+ {0x0651, 0xFC5E, {0x064C, 0x0000}},
+ {0x0651, 0xFC60, {0x064E, 0x0000}},
+ {0x0651, 0xFC61, {0x064F, 0x0000}},
+ {0x0651, 0xFC62, {0x0650, 0x0000}},
+ {0x0651, 0xF2EE, {0x064B, 0x0000}},
+};
+const VegaArabicLigatures vega_arabic_ligatures[3] = {
+ {ligatures_three, 3, 3},
+ {ligatures_two, 57, 2},
+ {ligatures_marks, 5, 2},
+};
+
/* The categories syllabic.h names are the machines' own. */
_Static_assert(VEGA_OT_A == 9, "indic A");
_Static_assert(VEGA_OT_C == 1, "indic C");
diff --git a/text/syllabic.h b/text/syllabic.h
index d03116b..767fada 100644
--- a/text/syllabic.h
+++ b/text/syllabic.h
@@ -51,4 +51,17 @@ typedef struct {
unsigned broken; /* the broken cluster's type, plus one */
} VegaMachine;
extern const VegaMachine vega_indic_machine, vega_khmer_machine, vega_myanmar_machine, vega_use_machine;
+
+/* Arabic's joining type (hb-ot-shaper-arabic-table.hh): U 0, L 1, R 2, D and
+ * C 3, Syriac's Alaph 4 and Dalath-Rish 5, T 7, and 8 where the general
+ * category decides (T for Mn, Me and Cf, else U). */
+uint8_t vega_arabic_joining(uint32_t u);
+/* For fallback shaping: the presentation forms (init, medi, fina, isol) of
+ * U+0621..06D3, and the ligatures of presentation forms, in HarfBuzz's
+ * order: those of three components, of two, of marks. */
+enum { VEGA_ARABIC_FORMS_FIRST = 0x0621, VEGA_ARABIC_FORMS_LAST = 0x06D3 };
+extern const uint16_t vega_arabic_forms[VEGA_ARABIC_FORMS_LAST - VEGA_ARABIC_FORMS_FIRST + 1][4];
+typedef struct { uint16_t first, ligature, components[2]; } VegaArabicLigature;
+typedef struct { const VegaArabicLigature *ligatures; unsigned count, length; } VegaArabicLigatures;
+extern const VegaArabicLigatures vega_arabic_ligatures[3];
#endif
diff --git a/text/syllabic.py b/text/syllabic.py
index 4a79869..b4de81a 100644
--- a/text/syllabic.py
+++ b/text/syllabic.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
-"""The tables HarfBuzz's syllabic shapers read, as C (text/syllabic-data.c).
+"""The tables HarfBuzz's complex shapers read, as C (text/syllabic-data.c).
python3 text/syllabic.py HARFBUZZ_SRC > text/syllabic-data.c
python3 text/syllabic.py HARFBUZZ_SRC --check
@@ -16,6 +16,10 @@ HARFBUZZ_SRC is HarfBuzz 10.2.0's src/ directory; what is read from it:
shaper-use-table.hh, whose arrays and lookup (gen-use-table.py's) are
carried over as they are, the categories named in them resolved.
+- Arabic's: each character's joining type (with Syriac's Alaph and
+ Dalath-Rish groups), and the presentation forms and ligatures fallback
+ shaping builds lookups from (hb-ot-shaper-arabic-table.hh).
+
- The syllable machines: hb-ot-shaper-*-machine.rl, the Ragel grammars.
This compiles each to a DFA with Ragel's scanner semantics (the longest
match from each position, ties to the pattern listed first), its symbols
@@ -242,6 +246,29 @@ def scan(table, accept, symbols):
return found
+# ---- HarfBuzz's generated tables: a flat array (rows commented with the
+# code point they start at, a row's first cells left blank where a range
+# starts inside it) and a lookup of ranges into it
+
+def ranged_table(text, array, function, cell):
+ """Code point to cell for a table whose lookup function returns
+ array[u - first + offset] over ranges; cell() reads one cell's text."""
+ body = text[text.index(f'{array}[] =') :]
+ body = body[body.index('{') + 1:body.index('\n};')]
+ body = re.sub(r'/\*.*?\*/', ' ', body, flags=re.S)
+ body = re.sub(r'#define[^\n]*', ' ', body)
+ cells = cell(body)
+ offsets = {int(k, 16): int(v) for k, v in re.findall(rf'#define {array.split("_")[0]}_offset_0x([0-9a-f]+)u (\d+)', text)}
+ lookup = text[text.index(function):]
+ table = {}
+ for first, last, key in re.findall(r'hb_in_range<hb_codepoint_t> \(u, 0x([0-9A-F]+)u, 0x([0-9A-F]+)u\)\) return '
+ rf'{array}\[u - 0x[0-9A-F]+u \+ \w+_offset_0x([0-9a-f]+)u\]', lookup):
+ first, last, at = int(first, 16), int(last, 16), offsets[int(key, 16)]
+ for u in range(first, last + 1):
+ table[u] = cells[at + u - first]
+ return table, lookup
+
+
# ---- The Indic table
def indic_table(grammars):
@@ -258,12 +285,8 @@ def indic_table(grammars):
def cell(s, m):
return short['_OT_' + s], short['_POS_' + m]
- cells = {}
- table = text[text.index('static const uint16_t indic_table[]'):text.index('}; /* Table items:')]
- for start, row in re.findall(r'/\* ([0-9A-F]{4,5}) \*/(.*)', table):
- for k, (s, m) in enumerate(re.findall(r'_\((\w+),(\w+)\)', row)):
- cells[int(start, 16) + k] = cell(s, m)
- lookup = text[text.index('hb_indic_get_categories (hb_codepoint_t u)'):]
+ cells, lookup = ranged_table(text, 'indic_table', 'hb_indic_get_categories (hb_codepoint_t u)',
+ lambda body: [cell(s, m) for s, m in re.findall(r'_\((\w+),(\w+)\)', body)])
for code, s, m in re.findall(r'u == 0x([0-9A-F]+)u\)\) return _\((\w+),(\w+)\);', lookup):
cells[int(code, 16)] = cell(s, m)
default = cell(*re.search(r'return _\((\w+),(\w+)\);\s*\}', lookup).groups())
@@ -297,6 +320,34 @@ def use_table(grammar):
return arrays, expression[:-1] + str(grammar.symbols['O'])
+def arabic_tables():
+ """hb-ot-shaper-arabic-table.hh: joining type by code point (U 0, L 1,
+ R 2, D and C 3, Alaph 4, Dalath-Rish 5, T 7, X 8 elsewhere); the
+ presentation forms of U+0621..06D3 (init, medi, fina, isol); the
+ ligature sets, each as (first, ligature, components)."""
+ text = (HB / 'hb-ot-shaper-arabic-table.hh').read_text()
+ values = {'U': 0, 'L': 1, 'R': 2, 'D': 3, 'C': 3, 'A': 4, 'DR': 5, 'T': 7, 'X': 8}
+ joining, _ = ranged_table(text, 'joining_table', 'joining_type (hb_codepoint_t u)',
+ lambda body: [values[n] for n in re.findall(r'[A-Z]+', body)])
+ shaping = [tuple(int(v, 16) for v in re.findall(r'0x([0-9A-F]{4})u', row))
+ for row in re.findall(r'\{(0x[^}]*)\}, /\* U\+', text[text.index('shaping_table[][4]'):])]
+ first = int(re.search(r'SHAPING_TABLE_FIRST\s+0x([0-9A-F]+)u', text).group(1), 16)
+ last = int(re.search(r'SHAPING_TABLE_LAST\s+0x([0-9A-F]+)u', text).group(1), 16)
+ assert len(shaping) == last - first + 1 and all(len(r) == 4 for r in shaping)
+ ligatures = {}
+ for name in ('ligature_table', 'ligature_mark_table', 'ligature_3_table'):
+ body = text[text.index(f'}} {name}[] =') :]
+ body = body[:body.index('\n};')]
+ sets = []
+ for first_u, entries in re.findall(r'\{ 0x([0-9A-F]+)u, \{(.*?)\}\},', body, re.S):
+ for components, ligature in re.findall(r'\{ \{([^}]*)\}, 0x([0-9A-F]+)u ?\}', entries):
+ sets.append((int(first_u, 16), int(ligature, 16),
+ [int(c, 16) for c in re.findall(r'0x([0-9A-F]+)u', components)]))
+ assert sets, name
+ ligatures[name] = sets
+ return joining, (first, shaping), ligatures
+
+
# ---- Checking against Ragel's machines (hb-ot-shaper-*-machine.hh)
def ragel(name):
@@ -476,6 +527,46 @@ def main():
out.append('static unsigned use_b4(const uint8_t *a, unsigned i) { return (a[i >> 1] >> ((i & 1u) << 2)) & 15u; }')
out.append(f'uint8_t vega_use_category(uint32_t u) {{ return (uint8_t)({expression}); }}')
out.append('')
+ joining, (first, shaping), ligatures = arabic_tables()
+ runs, flat = [], []
+ for a, b in runs_of(joining):
+ runs.append((a, b, len(flat)))
+ flat.extend(joining.get(code, 8) for code in range(a, b + 1))
+ out.append('/* Arabic joining types (hb-ot-shaper-arabic-table.hh, Unicode 16): U 0, L 1, R 2, D and C 3,')
+ out.append(' * Alaph 4, Dalath-Rish 5, T 7; 8 where the general category decides. */')
+ out.append(f'static const uint8_t joining_cells[{len(flat)}] = {{')
+ for k in range(0, len(flat), 32):
+ out.append(' ' + ', '.join(str(v) for v in flat[k:k + 32]) + ',')
+ out.append('};')
+ out.append(f'static const struct {{ uint32_t first, last, at; }} joining_runs[{len(runs)}] = {{')
+ for a, b, at in runs:
+ out.append(f' {{0x{a:04X}, 0x{b:04X}, {at}}},')
+ out.append('};')
+ out.append('uint8_t vega_arabic_joining(uint32_t u) {')
+ out.append(f' for (unsigned i = 0; i < {len(runs)} && u >= joining_runs[i].first; ++i)')
+ out.append(' if (u <= joining_runs[i].last) return joining_cells[joining_runs[i].at + u - joining_runs[i].first];')
+ out.append(' return 8;')
+ out.append('}')
+ out.append('')
+ out.append(f'/* Presentation forms of U+{first:04X}..U+{first + len(shaping) - 1:04X}: init, medi, fina, isol. */')
+ out.append(f'const uint16_t vega_arabic_forms[{len(shaping)}][4] = {{')
+ for row in shaping:
+ out.append(' {' + ', '.join(f'0x{v:04X}' for v in row) + '},')
+ out.append('};')
+ out.append('/* Ligatures fallback shaping forms: three components, two, and marks. */')
+ for name, cname in (('ligature_3_table', 'three'), ('ligature_table', 'two'), ('ligature_mark_table', 'marks')):
+ sets = ligatures[name]
+ out.append(f'static const VegaArabicLigature ligatures_{cname}[{len(sets)}] = {{')
+ for first_u, ligature, components in sets:
+ cs = components + [0] * (2 - len(components))
+ out.append(f' {{0x{first_u:04X}, 0x{ligature:04X}, {{0x{cs[0]:04X}, 0x{cs[1]:04X}}}}},')
+ out.append('};')
+ out.append('const VegaArabicLigatures vega_arabic_ligatures[3] = {')
+ for name, cname, n in (('ligature_3_table', 'three', 3), ('ligature_table', 'two', 2),
+ ('ligature_mark_table', 'marks', 2)):
+ out.append(f' {{ligatures_{cname}, {len(ligatures[name])}, {n}}},')
+ out.append('};')
+ out.append('')
out.append('/* The categories syllabic.h names are the machines\' own. */')
for name, prefix in (('indic', 'VEGA_OT_'), ('khmer', 'VEGA_OT_'), ('myanmar', 'VEGA_OT_'), ('use', 'VEGA_USE_')):
for symbol, value in sorted(grammars[name].symbols.items()):