aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile8
-rw-r--r--docs/design/004-text.md39
-rw-r--r--text/shape-build.c229
-rw-r--r--text/shape.c872
-rw-r--r--text/shape.h42
-rw-r--r--text/syllabic-data.c463
-rw-r--r--text/syllabic.h40
-rw-r--r--text/syllabic.py456
8 files changed, 2065 insertions, 84 deletions
diff --git a/Makefile b/Makefile
index bee12a1..e3ffdd8 100644
--- a/Makefile
+++ b/Makefile
@@ -48,9 +48,9 @@ HARFBUZZ_LIBS ?= $(shell pkg-config --libs harfbuzz)
# Drawing them on the GPU takes OpenGL ES 3.1 through EGL.
GL_LIBS ?= $(shell pkg-config --libs egl glesv2)
SYMBOLS = symbols/symbols.c symbols/symbols-gl.c
-SHAPE = text/shape.c text/layout.c text/bidi.c text/linebreak.c text/unicode-data.c
+SHAPE = text/shape.c text/syllabic-data.c text/layout.c text/bidi.c text/linebreak.c text/unicode-data.c
$(BUILD)/vega-symbols: symbols/symbols-tool.c $(SYMBOLS) symbols/symbols.h symbols/symbols-gl.h $(SHAPE) text/shape-build.c \
- text/shape.h text/shape-build.h text/layout.h text/text.h | $(BUILD)
+ text/shape.h text/shape-build.h text/layout.h text/text.h text/syllabic.h | $(BUILD)
$(CC) $(CPPFLAGS) $(CFLAGS) $(FREETYPE_CFLAGS) $(HARFBUZZ_CFLAGS) -Itext symbols/symbols-tool.c $(SYMBOLS) $(SHAPE) \
text/shape-build.c $(LDFLAGS) $(FREETYPE_LIBS) $(HARFBUZZ_LIBS) $(GL_LIBS) -lm $(LDLIBS) -o $@
@@ -58,8 +58,8 @@ TEXT = text/bidi.c text/normalize.c text/linebreak.c text/grapheme.c text/word.c
$(BUILD)/test-text: text/test-text.c $(TEXT) text/text.h | $(BUILD)
$(CC) $(CPPFLAGS) $(CFLAGS) -Itext text/test-text.c $(TEXT) $(LDFLAGS) $(LDLIBS) -o $@
-LAYOUT = text/layout.c text/shape.c text/bidi.c text/linebreak.c text/unicode-data.c
-$(BUILD)/test-layout: text/test-layout.c $(LAYOUT) text/layout.h text/shape.h text/text.h symbols/symbols.c \
+LAYOUT = text/layout.c text/shape.c text/syllabic-data.c text/bidi.c text/linebreak.c text/unicode-data.c
+$(BUILD)/test-layout: text/test-layout.c $(LAYOUT) text/layout.h text/shape.h text/text.h text/syllabic.h symbols/symbols.c \
symbols/symbols.h | $(BUILD)
$(CC) $(CPPFLAGS) $(CFLAGS) -Itext -Isymbols text/test-layout.c $(LAYOUT) symbols/symbols.c $(LDFLAGS) -lm $(LDLIBS) -o $@
diff --git a/docs/design/004-text.md b/docs/design/004-text.md
index 6c8e09e..065c4db 100644
--- a/docs/design/004-text.md
+++ b/docs/design/004-text.md
@@ -129,11 +129,48 @@ HarfBuzz와 다른 파일은 만들어지지 않는다. `vega-symbols build --sh
않았다. Unifont의 히브리는 HarfBuzz보다 2.3배 빠르다(문자당 116 대 272 ns). HarfBuzz는 부호마다 CFF 외곽을 풀어 상자를
재고, Vega는 빌드할 때 잰 상자를 읽는다.
+### 인도계 문자(데바나가리, 벵골, 구르무키, 구자라트, 오리야, 타밀, 텔루구, 칸나다, 말라얄람)
+
+HarfBuzz의 인도계 셰이퍼는 음절 단위로 일한다. 음절을 찾고(Ragel로 짠 문법), 음절마다 기저 자음을 찾아 그 앞뒤로 모음 부호와
+reph를 옮기고, 기본 기능(nukt, akhn, rphf, rkrf, pref, blwf, abvf, half, pstf, vatu, cjct)을 하나씩 음절 안에서만 적용한 뒤,
+그 결과를 보고 모음 부호와 reph와 앞으로 가는 자음을 다시 옮긴다. 그 사이사이가 GSUB의 멈춤(pause)이다.
+
+- 계획: 멈춤은 조회 목록 안의 표지(`VEGA_STEP_PAUSE`와 멈춤 번호)로 싣는다. 셰이퍼가 "이 기능이 이 글리프들을 바꾸겠는가"를
+ 묻는 기능(rphf, pref, blwf, pstf, vatu)은 그 단계의 조회 범위를 계획에 싣고, 실행 중에 조회의 하위 표를 문맥 없이 대어 본다
+ (`would_apply`, 형식마다 HarfBuzz의 판단 그대로). 옛 규격(deva)과 새 규격(dev2)은 글꼴이 고른 문자 체계 태그로 가른다.
+- 음절 기계: [text/syllabic.py](../../text/syllabic.py)가 HarfBuzz의 `.rl` 문법(인도계, 크메르, 미얀마)을 그대로 읽어 정규식에서
+ NFA, DFA를 만들고 최소화한다. Ragel 스캐너의 뜻(각 위치에서 가장 긴 일치, 같으면 먼저 적은 패턴)을 따른다. 같은 문법으로
+ Ragel이 만든 HarfBuzz의 기계(`hb-ot-shaper-*-machine.hh`)를 해석해 무작위 범주열 10만 개씩 대 보면 세 기계 모두 음절 경계와
+ 종류가 하나도 다르지 않다(`--check`). 문자마다의 범주와 자리는 HarfBuzz 10.2의 `hb-ot-shaper-indic-table.cc`를 칸마다 읽는다
+ (유니코드 16 기준이라 여기의 15.1 UCD에서 만들지 않는다).
+- 실행: 모음 제약(다른 모음처럼 읽히는 모음 연쇄에 점선 원), 인도계 분해·합성(몇 글자는 분해하지 않고, 쪼갠 모음 부호는 다시
+ 합치지 않는다), 음절 찾기, 초기 재배치(기저 찾기, 자리순 안정 정렬, 마스크), 깨진 음절의 점선 원, 음절 안에서만 맞추는
+ 조회(`F_PER_SYLLABLE`), 최종 재배치(pref가 만든 기저, 말라얄람의 아래 형태, 모음 부호와 reph와 pref 자음의 자리, 단어
+ 첫머리의 init).
+
+HarfBuzz 10.2의 원본을 기준으로 삼았다. 처음에는 rustybuzz 0.20(HarfBuzz 10.0.1을 옮긴 Rust)을 보고 옮겼는데, 설치된
+HarfBuzz 10.2와 대 보니 넷이 달랐다. rustybuzz가 틀린 것 둘: 새 규격에서 would-substitute를 문맥 없이 묻는 조건이 뒤집혀
+있고, 기저를 찾을 때 미얀마의 자음 집합을 쓴다. 10.0.1 뒤에 HarfBuzz가 바꾼 것 둘: 위첨자 2·3(U+00B2·00B3)이 SMPst라는 새
+범주가 됐고(문법에 `sm = SM | SMPst`), 텔루구 길이 부호의 수정 결합 등급이 0이 아니라 4·5다. 넷 모두 HarfBuzz 원본대로 고쳤다.
+다섯째는 계획기에 있었다. HarfBuzz는 단계마다 조회를 번호로 정렬한 뒤 같은 조회를 합치는데, 합칠 때 음절 제한과 무작위
+깃발은 앞선 쪽의 것을 남긴다. 그 정렬이 자체 퀵정렬(hb_qsort)이라 안정적이지 않고, 앞뒤는 그 알고리즘이 정한다. Noto Sans
+Malayalam은 psts와 rlig가 한 조회를 나눠 쓰는데, 안정 정렬로는 psts 쪽(음절 안에서만)이 남아 음절을 넘는 합자(ma와 결합
+점)가 생기지 않았다. 계획기가 같은 퀵정렬을 그대로 쓰게 했다.
+
+결과(Pi 5):
+
+- 설치된 글꼴 213벌, 계획 1,688개에 각 1천 실행: 다른 실행 0(글자 모양이 비트맵뿐인 Noto Color Emoji는 심볼 단계에서
+ 빠진다). 인도계가 있는 글꼴은 따로 계획마다 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).
+
받지 않는 것(빌드 도구가 이유를 말하고 거부한다):
- `kern` 표의 상태 기계(Apple 형식 1), 등급 형식(2, 3), 교차 방향 커닝. 설치된 글꼴의 `kern` 표는 모두 짝 형식(0)이다.
- AAT 표(morx, kerx, trak), 늘이기(stch), 아랍 형태가 없어 HarfBuzz가 표시형으로 대체 셰이핑을 하는 글꼴(Unifont의 아랍).
-- 기본·아랍·히브리·한글 셰이퍼가 아닌 문자 체계: 인도계, 태국·라오, 크메르, 미얀마, USE 문자 체계들, 시리아.
+- 기본·아랍·히브리·한글·인도계 셰이퍼가 아닌 문자 체계: 태국·라오, 크메르, 미얀마, USE 문자 체계들(싱할라, 티베트, 자바
+ 등), 시리아.
- 이체 선택자(cmap 형식 14)는 싣지 않는다. 선택자는 숨기고 앞 글자는 기본 글리프로 둔다.
## 비례폭 글줄
diff --git a/text/shape-build.c b/text/shape-build.c
index aa653d8..559517e 100644
--- a/text/shape-build.c
+++ b/text/shape-build.c
@@ -3,6 +3,7 @@
* masks and stages), what the section carries, and a check of it against
* HarfBuzz on random runs. */
#include "shape-build.h"
+#include "syllabic.h"
#include "text.h"
#include <hb-ot.h>
#include <stdlib.h>
@@ -25,7 +26,8 @@ typedef struct {
uint32_t mask, one_mask;
bool auto_zwnj, auto_zwj, random, per_syllable, needs_fallback;
} FeatureMap;
-typedef struct { unsigned index; uint32_t mask; bool auto_zwnj, auto_zwj, random, per_syllable; } LookupMap;
+/* A lookup to run, or (index VEGA_STEP_PAUSE) the pause that ends a stage. */
+typedef struct { unsigned index; uint32_t mask; bool auto_zwnj, auto_zwj, random, per_syllable; unsigned pause; } LookupMap;
typedef struct {
hb_face_t *face;
@@ -33,6 +35,7 @@ typedef struct {
hb_tag_t chosen_script[2];
FeatureInfo infos[MAX_FEATURES];
unsigned info_count, current_stage[2];
+ unsigned pause[2][MAX_STAGES]; /* what ends each stage: VEGA_PAUSE_..., or 0 */
bool is_simple;
/* compiled */
FeatureMap features[MAX_FEATURES];
@@ -40,6 +43,7 @@ typedef struct {
uint32_t global_mask;
LookupMap *lookups[2];
unsigned lookup_count[2];
+ unsigned stage_first[2][MAX_STAGES], stage_end[2][MAX_STAGES]; /* each stage's lookups */
} Map;
static const hb_tag_t table_tags[2] = {HB_OT_TAG_GSUB, HB_OT_TAG_GPOS};
@@ -51,7 +55,10 @@ static void add_feature(Map *m, hb_tag_t tag, unsigned flags, unsigned value) {
m->info_count++;
}
static void enable_feature(Map *m, hb_tag_t tag, unsigned flags, unsigned value) { add_feature(m, tag, flags | F_GLOBAL, value); }
-static void add_gsub_pause(Map *m) { m->current_stage[0]++; }
+static void add_gsub_pause(Map *m, unsigned pause) {
+ if (m->current_stage[0] < MAX_STAGES) m->pause[0][m->current_stage[0]] = pause;
+ m->current_stage[0]++;
+}
static bool has_feature(const Map *m, hb_tag_t tag) {
for (int t = 0; t < 2; ++t)
@@ -97,6 +104,17 @@ static uint32_t one_mask(const Map *m, const char *tag) {
return f ? f->one_mask : 0;
}
+static bool push_lookup(Map *m, int table, LookupMap l, unsigned *allocated) {
+ if (m->lookup_count[table] == *allocated) {
+ *allocated = *allocated ? 2 * *allocated : 64;
+ LookupMap *grown = realloc(m->lookups[table], *allocated * sizeof(*grown));
+ if (!grown) return false;
+ m->lookups[table] = grown;
+ }
+ m->lookups[table][m->lookup_count[table]++] = l;
+ return true;
+}
+
static bool add_lookups(Map *m, int table, unsigned feature_index, uint32_t mask, bool auto_zwnj, bool auto_zwj,
bool random, bool per_syllable, unsigned *allocated) {
unsigned table_lookup_count = hb_ot_layout_table_get_lookup_count(m->face, table_tags[table]);
@@ -104,17 +122,11 @@ static bool add_lookups(Map *m, int table, unsigned feature_index, uint32_t mask
do {
len = 32;
hb_ot_layout_feature_get_lookups(m->face, table_tags[table], feature_index, offset, &len, indices);
- for (unsigned i = 0; i < len; ++i) {
- if (indices[i] >= table_lookup_count) continue;
- if (m->lookup_count[table] == *allocated) {
- *allocated = *allocated ? 2 * *allocated : 64;
- LookupMap *grown = realloc(m->lookups[table], *allocated * sizeof(*grown));
- if (!grown) return false;
- m->lookups[table] = grown;
- }
- m->lookups[table][m->lookup_count[table]++] =
- (LookupMap){indices[i], mask, auto_zwnj, auto_zwj, random, per_syllable};
- }
+ for (unsigned i = 0; i < len; ++i)
+ if (indices[i] < table_lookup_count &&
+ !push_lookup(m, table, (LookupMap){indices[i], mask, auto_zwnj, auto_zwj, random, per_syllable, 0},
+ allocated))
+ return false;
offset += len;
} while (len == 32);
return true;
@@ -254,9 +266,9 @@ static bool compile_map(Map *m) {
map->one_mask = (1u << map->shift) & map->mask;
}
if (m->is_simple) qsort(m->features, m->feature_count, sizeof(*m->features), by_tag);
- /* The final pause, then each stage's lookups: sorted, duplicates merged. */
- m->current_stage[0]++;
- m->current_stage[1]++;
+ /* The final pause, then each stage's lookups: sorted, duplicates merged,
+ * the stage's pause after them. */
+ if (++m->current_stage[0] > MAX_STAGES || ++m->current_stage[1] > MAX_STAGES) return false;
for (int t = 0; t < 2; ++t) {
unsigned allocated = 0, last = 0;
for (unsigned stage = 0; stage < m->current_stage[t]; ++stage) {
@@ -284,6 +296,12 @@ static bool compile_map(Map *m) {
}
m->lookup_count[t] = j + 1;
}
+ m->stage_first[t][stage] = last;
+ m->stage_end[t][stage] = m->lookup_count[t];
+ if (m->pause[t][stage] &&
+ !push_lookup(m, t, (LookupMap){VEGA_STEP_PAUSE, 0, false, false, false, false, m->pause[t][stage]},
+ &allocated))
+ return false;
last = m->lookup_count[t];
}
}
@@ -316,9 +334,9 @@ static int categorize(uint32_t script, hb_tag_t gsub_script) {
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))
- return dflt ? VEGA_SHAPER_DEFAULT : -1;
+ if (listed(indic_scripts, sizeof(indic_scripts) / sizeof(*indic_scripts), script))
+ return dflt ? VEGA_SHAPER_DEFAULT : (gsub_script & 0xFF) == '3' ? -1 : VEGA_SHAPER_INDIC;
+ if (listed(use_scripts, sizeof(use_scripts) / sizeof(*use_scripts), script)) return dflt ? VEGA_SHAPER_DEFAULT : -1;
if (script == TAG("Mymr")) return dflt || gsub_script == TAG("mymr") ? VEGA_SHAPER_DEFAULT : -1;
return VEGA_SHAPER_DEFAULT;
}
@@ -330,14 +348,26 @@ static bool has_table(hb_face_t *face, hb_tag_t tag) {
return has;
}
+typedef struct { unsigned first, count; } Stage;
typedef struct {
uint32_t script;
int direction, native, shaper, flags;
uint32_t global_mask, kern_mask, masks[VEGA_PLAN_MASKS];
LookupMap *lookups[2];
unsigned lookup_count[2];
+ unsigned shaper_flags;
+ Stage stages[VEGA_PLAN_STAGES];
} Plan;
+/* The GSUB stage a feature is in (hb_ot_map_t::get_stage_lookups), for a
+ * shaper's would-substitute questions. */
+static Stage stage_of(const Map *m, const char *tag) {
+ const FeatureMap *f = find_feature(m, TAG(tag));
+ if (!f || f->stage[0] >= m->current_stage[0]) return (Stage){0, 0};
+ unsigned first = m->stage_first[0][f->stage[0]];
+ return (Stage){first, m->stage_end[0][f->stage[0]] - first};
+}
+
/* 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
@@ -415,7 +445,7 @@ static bool plan_for(hb_face_t *face, uint32_t script_tag, int direction, Plan *
/* hb_ot_shape_collect_features */
m->is_simple = true;
enable_feature(m, TAG("rvrn"), F_NONE, 1);
- add_gsub_pause(m);
+ add_gsub_pause(m, 0);
if (direction == VEGA_SHAPE_LTR) {
enable_feature(m, TAG("ltra"), F_NONE, 1);
enable_feature(m, TAG("ltrm"), F_NONE, 1);
@@ -434,23 +464,57 @@ 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); /* record_stch */
+ add_gsub_pause(m, 0); /* record_stch */
enable_feature(m, TAG("ccmp"), F_MANUAL_ZWJ, 1);
enable_feature(m, TAG("locl"), F_MANUAL_ZWJ, 1);
- add_gsub_pause(m);
+ add_gsub_pause(m, 0);
for (int i = 0; i < 7; ++i) {
bool syriac = arabic_features[i][3] == '2' || arabic_features[i][3] == '3';
add_feature(m, TAG(arabic_features[i]), F_MANUAL_ZWJ | (script == HB_SCRIPT_ARABIC && !syriac ? F_HAS_FALLBACK : 0), 1);
- add_gsub_pause(m);
+ 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); /* arabic_fallback_shape */
+ if (script == HB_SCRIPT_ARABIC) add_gsub_pause(m, 0); /* arabic_fallback_shape */
enable_feature(m, TAG("calt"), F_MANUAL_ZWJ, 1);
- if (!has_feature(m, TAG("rclt"))) add_gsub_pause(m);
+ if (!has_feature(m, TAG("rclt"))) add_gsub_pause(m, 0);
enable_feature(m, TAG("liga"), F_MANUAL_ZWJ, 1);
enable_feature(m, TAG("clig"), F_MANUAL_ZWJ, 1);
enable_feature(m, TAG("mset"), F_MANUAL_ZWJ, 1);
}
+ /* hb-ot-shaper-indic.cc: syllables found before any lookup; locl and
+ * ccmp; the initial reordering; the basic features one stage each; the
+ * final reordering; the rest. */
+ static const struct { const char *tag; unsigned flags; } indic_features[17] = {
+ {"nukt", F_GLOBAL | F_MANUAL_ZWNJ | F_MANUAL_ZWJ | F_PER_SYLLABLE},
+ {"akhn", F_GLOBAL | F_MANUAL_ZWNJ | F_MANUAL_ZWJ | F_PER_SYLLABLE},
+ {"rphf", F_MANUAL_ZWNJ | F_MANUAL_ZWJ | F_PER_SYLLABLE},
+ {"rkrf", F_GLOBAL | F_MANUAL_ZWNJ | F_MANUAL_ZWJ | F_PER_SYLLABLE},
+ {"pref", F_MANUAL_ZWNJ | F_MANUAL_ZWJ | F_PER_SYLLABLE},
+ {"blwf", F_MANUAL_ZWNJ | F_MANUAL_ZWJ | F_PER_SYLLABLE},
+ {"abvf", F_MANUAL_ZWNJ | F_MANUAL_ZWJ | F_PER_SYLLABLE},
+ {"half", F_MANUAL_ZWNJ | F_MANUAL_ZWJ | F_PER_SYLLABLE},
+ {"pstf", F_MANUAL_ZWNJ | F_MANUAL_ZWJ | F_PER_SYLLABLE},
+ {"vatu", F_GLOBAL | F_MANUAL_ZWNJ | F_MANUAL_ZWJ | F_PER_SYLLABLE},
+ {"cjct", F_GLOBAL | F_MANUAL_ZWNJ | F_MANUAL_ZWJ | F_PER_SYLLABLE},
+ {"init", F_MANUAL_ZWNJ | F_MANUAL_ZWJ | F_PER_SYLLABLE},
+ {"pres", F_GLOBAL | F_MANUAL_ZWNJ | F_MANUAL_ZWJ | F_PER_SYLLABLE},
+ {"abvs", F_GLOBAL | F_MANUAL_ZWNJ | F_MANUAL_ZWJ | F_PER_SYLLABLE},
+ {"blws", F_GLOBAL | F_MANUAL_ZWNJ | F_MANUAL_ZWJ | F_PER_SYLLABLE},
+ {"psts", F_GLOBAL | F_MANUAL_ZWNJ | F_MANUAL_ZWJ | F_PER_SYLLABLE},
+ {"haln", F_GLOBAL | F_MANUAL_ZWNJ | F_MANUAL_ZWJ | F_PER_SYLLABLE}};
+ if (shaper == VEGA_SHAPER_INDIC) {
+ m->is_simple = false;
+ add_gsub_pause(m, VEGA_PAUSE_SYLLABLES);
+ enable_feature(m, TAG("locl"), F_PER_SYLLABLE, 1);
+ enable_feature(m, TAG("ccmp"), F_PER_SYLLABLE, 1);
+ add_gsub_pause(m, VEGA_PAUSE_INITIAL_REORDERING);
+ for (int i = 0; i < 11; ++i) {
+ add_feature(m, TAG(indic_features[i].tag), indic_features[i].flags, 1);
+ add_gsub_pause(m, 0);
+ }
+ add_gsub_pause(m, VEGA_PAUSE_FINAL_REORDERING);
+ for (int i = 11; i < 17; ++i) add_feature(m, TAG(indic_features[i].tag), indic_features[i].flags, 1);
+ }
if (shaper == VEGA_SHAPER_HANGUL) {
m->is_simple = false;
add_feature(m, TAG("ljmo"), F_NONE, 1);
@@ -464,9 +528,14 @@ 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. */
+ /* Hangul turns calt off: fonts put jamo lookups there. Indic turns liga
+ * off, and is done with syllables after GSUB. */
if (shaper == VEGA_SHAPER_HANGUL) add_feature(m, TAG("calt"), F_GLOBAL, 0);
- if (!compile_map(m)) FAIL("out of memory");
+ if (shaper == VEGA_SHAPER_INDIC) {
+ add_feature(m, TAG("liga"), F_GLOBAL, 0);
+ add_gsub_pause(m, VEGA_PAUSE_CLEAR_SYLLABLES);
+ }
+ if (!compile_map(m)) FAIL("out of memory, or more than %d stages", MAX_STAGES);
/* hb_ot_shape_planner_t::compile */
const FeatureMap *kern = find_feature(m, TAG("kern"));
@@ -476,8 +545,9 @@ static bool plan_for(hb_face_t *face, uint32_t script_tag, int direction, Plan *
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;
+ /* Hangul's and Indic's shapers neither zero marks nor place them
+ * without GPOS. */
+ bool script_marks = shaper != VEGA_SHAPER_HANGUL && shaper != VEGA_SHAPER_INDIC;
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);
@@ -499,15 +569,26 @@ static bool plan_for(hb_face_t *face, uint32_t script_tag, int direction, Plan *
(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]);
+ {m->lookup_count[0], m->lookup_count[1]}, 0, {{0, 0}}};
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");
+ if (shaper == VEGA_SHAPER_ARABIC)
+ for (int i = 0; i < 7; ++i) plan->masks[VEGA_MASK_ISOL + i] = one_mask(m, arabic_features[i]);
+ if (shaper == VEGA_SHAPER_HANGUL) {
+ 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");
+ }
+ if (shaper == VEGA_SHAPER_INDIC) {
+ /* the masks of the features set glyph by glyph (not global ones) */
+ static const char *const masked[] = {"rphf", "pref", "blwf", "abvf", "half", "pstf", "init"};
+ for (int i = 0; i < 7; ++i) plan->masks[VEGA_MASK_RPHF + i] = one_mask(m, masked[i]);
+ static const char *const staged[VEGA_PLAN_STAGES] = {"rphf", "pref", "blwf", "pstf", "vatu"};
+ for (int i = 0; i < VEGA_PLAN_STAGES; ++i) plan->stages[i] = stage_of(m, staged[i]);
+ if ((m->chosen_script[0] & 0xFF) != '2') plan->shaper_flags |= VEGA_INDIC_OLD_SPEC;
+ }
free(m);
return true;
fail:
@@ -661,6 +742,13 @@ bool vega_shaping_compile(hb_face_t *face, const uint32_t *scripts, size_t scrip
put32(&o, p->lookup_count[t]);
step += p->lookup_count[t];
}
+ put32(&o, p->shaper_flags);
+ for (int k = 0; k < VEGA_PLAN_STAGES; ++k) {
+ put16(&o, p->stages[k].first);
+ put16(&o, p->stages[k].count);
+ put32(&o, 0);
+ }
+ put32(&o, 0);
}
set32(&o, 56, step);
set32(&o, 60, (uint32_t)o.size);
@@ -669,10 +757,11 @@ bool vega_shaping_compile(hb_face_t *face, const uint32_t *scripts, size_t scrip
for (unsigned k = 0; k < plans[i].lookup_count[t]; ++k) {
const LookupMap *l = &plans[i].lookups[t][k];
put16(&o, l->index);
- unsigned char flags[2] = {(unsigned char)((l->auto_zwnj ? VEGA_STEP_AUTO_ZWNJ : 0) |
- (l->auto_zwj ? VEGA_STEP_AUTO_ZWJ : 0) |
- (l->random ? VEGA_STEP_RANDOM : 0) |
- (l->per_syllable ? VEGA_STEP_PER_SYLLABLE : 0)),
+ unsigned char flags[2] = {(unsigned char)(l->index == VEGA_STEP_PAUSE ? l->pause
+ : (l->auto_zwnj ? VEGA_STEP_AUTO_ZWNJ : 0) |
+ (l->auto_zwj ? VEGA_STEP_AUTO_ZWJ : 0) |
+ (l->random ? VEGA_STEP_RANDOM : 0) |
+ (l->per_syllable ? VEGA_STEP_PER_SYLLABLE : 0)),
0};
put(&o, flags, 2);
put32(&o, l->mask);
@@ -708,7 +797,8 @@ bool vega_shaping_compile(hb_face_t *face, const uint32_t *scripts, size_t scrip
if (hb_font_get_nominal_glyph(font, u, &g)) hb_set_add(glyphs, g);
}
for (size_t i = 0; i < plan_count; ++i)
- for (unsigned k = 0; k < plans[i].lookup_count[0]; ++k) hb_set_add(lookups, plans[i].lookups[0][k].index);
+ for (unsigned k = 0; k < plans[i].lookup_count[0]; ++k)
+ if (plans[i].lookups[0][k].index != VEGA_STEP_PAUSE) hb_set_add(lookups, plans[i].lookups[0][k].index);
hb_ot_layout_lookups_substitute_closure(face, lookups, glyphs);
hb_set_destroy(wanted);
hb_set_destroy(lookups);
@@ -763,6 +853,46 @@ 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);
}
+/* An Indic syllable the ways text spells one (hb-ot-shaper-indic.cc):
+ * consonants joined by viramas (a joiner by some), a Ra and virama first
+ * for a reph, nuktas, matras, syllable modifiers, a virama at the end; a
+ * vowel with its modifiers. indic holds the script's consonants, Ra,
+ * viramas, nuktas, matras, modifiers and vowels. */
+enum { INDIC_CONSONANTS, INDIC_RA, INDIC_HALANTS, INDIC_NUKTAS, INDIC_MATRAS, INDIC_MODIFIERS, INDIC_VOWELS, INDIC_POOLS };
+static size_t indic_syllable(Random *r, const Pool *indic, uint32_t *text, size_t n) {
+ const Pool *c = &indic[INDIC_CONSONANTS], *ra = &indic[INDIC_RA], *h = &indic[INDIC_HALANTS],
+ *nukta = &indic[INDIC_NUKTAS], *m = &indic[INDIC_MATRAS], *sm = &indic[INDIC_MODIFIERS],
+ *v = &indic[INDIC_VOWELS];
+ if (!c->count || !h->count) return n;
+ if (ra->count && !below(r, 4)) {
+ text[n++] = pick(r, ra);
+ text[n++] = pick(r, h);
+ if (!below(r, 8)) text[n++] = 0x200D;
+ }
+ if (v->count && !below(r, 6)) {
+ text[n++] = pick(r, v);
+ if (nukta->count && !below(r, 8)) text[n++] = pick(r, nukta);
+ } else
+ for (unsigned k = 0, count = 1 + below(r, 3); k < count; ++k) {
+ text[n++] = ra->count && !below(r, 5) ? pick(r, ra) : pick(r, c);
+ if (nukta->count && !below(r, 6)) text[n++] = pick(r, nukta);
+ if (k + 1 == count) break;
+ unsigned joiner = below(r, 8);
+ if (joiner == 0) text[n++] = 0x200D;
+ text[n++] = pick(r, h);
+ if (joiner == 1) text[n++] = 0x200D;
+ if (joiner == 2) text[n++] = 0x200C;
+ }
+ if (m->count && below(r, 3)) {
+ text[n++] = pick(r, m);
+ if (!below(r, 8)) text[n++] = pick(r, m);
+ }
+ if (sm->count && !below(r, 4)) text[n++] = pick(r, sm);
+ if (!below(r, 10)) text[n++] = pick(r, h);
+ if (!below(r, 12)) text[n++] = below(r, 2) ? 0x200C : 0x200D;
+ return n;
+}
+
/* 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
@@ -805,8 +935,9 @@ 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}, jamo_pools[3] = {{0}};
+ Pool letters = {0}, marks = {0}, others = {0}, hot = {0}, jamo_pools[3] = {{0}}, indic[INDIC_POOLS] = {{0}};
bool hangul = script == HB_TAG('H', 'a', 'n', 'g');
+ bool syllabic = listed(indic_scripts, sizeof(indic_scripts) / sizeof(*indic_scripts), script);
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)
@@ -820,6 +951,19 @@ void vega_shaping_check(hb_font_t *font, const VegaShaping *s, uint32_t script,
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 (syllabic && (of == script || of == inherited)) {
+ int pool = -1;
+ switch (vega_indic_categories(code) & 0xFF) {
+ case VEGA_OT_C: case VEGA_OT_CS: pool = INDIC_CONSONANTS; break;
+ case VEGA_OT_RA: pool = INDIC_RA; break;
+ case VEGA_OT_H: pool = INDIC_HALANTS; break;
+ case VEGA_OT_N: pool = INDIC_NUKTAS; break;
+ case VEGA_OT_M: case VEGA_OT_MPST: pool = INDIC_MATRAS; break;
+ case VEGA_OT_SM: case VEGA_OT_A: pool = INDIC_MODIFIERS; break;
+ case VEGA_OT_V: pool = INDIC_VOWELS; break;
+ }
+ if (pool >= 0) pool_add(&indic[pool], 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);
@@ -851,6 +995,10 @@ void vega_shaping_check(hb_font_t *font, const VegaShaping *s, uint32_t script,
n = hangul_syllable(&random, jamo_pools, text, n);
continue;
}
+ if (syllabic && below(&random, 3)) {
+ n = indic_syllable(&random, indic, 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);
@@ -916,4 +1064,5 @@ void vega_shaping_check(hb_font_t *font, const VegaShaping *s, uint32_t script,
free(others.items);
free(hot.items);
for (int k = 0; k < 3; ++k) free(jamo_pools[k].items);
+ for (int k = 0; k < INDIC_POOLS; ++k) free(indic[k].items);
}
diff --git a/text/shape.c b/text/shape.c
index 0813bae..3443b0b 100644
--- a/text/shape.c
+++ b/text/shape.c
@@ -1,15 +1,18 @@
-/* Shaping as HarfBuzz 10 does it (text/shape.h), with its default, Arabic
- * and Hebrew shapers. The code follows HarfBuzz's own step for step
- * (hb-ot-shape.cc, hb-ot-shape-normalize.cc, hb-buffer.cc, hb-ot-layout-
- * gsubgpos.hh, the GSUB and GPOS lookups, hb-ot-shaper-arabic.cc and
- * -hebrew.cc), under HarfBuzz's names, so that the two read side by side;
- * the build tool compares the results with HarfBuzz's (symbols-tool.c). */
+/* Shaping as HarfBuzz 10 does it (text/shape.h), with its default, Arabic,
+ * Hebrew, Hangul and Indic shapers. The code follows HarfBuzz's own step
+ * for step (hb-ot-shape.cc, hb-ot-shape-normalize.cc, hb-buffer.cc,
+ * hb-ot-layout-gsubgpos.hh, the GSUB and GPOS lookups, hb-ot-shaper-*.cc),
+ * under HarfBuzz's names, so that the two read side by side; the build
+ * tool compares the results with HarfBuzz's (symbols-tool.c). */
#include "shape.h"
+#include "syllabic.h"
#include "text.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#define TAG4(a, b, c, d) ((uint32_t)(a) << 24 | (uint32_t)(b) << 16 | (uint32_t)(c) << 8 | (uint32_t)(d))
+
/* ---- OpenType tables, read in place: a read past a table's end gives 0 */
static uint32_t get16(VegaTable t, uint32_t at) {
@@ -223,17 +226,22 @@ 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 + 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)
+ 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_INDIC)
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);
- for (uint32_t k = 0; k < ranges[table][1]; ++k)
- if (le16(s->steps + (size_t)(ranges[table][0] + k) * VEGA_STEP_BYTES) >= s->lookup_count[table])
+ 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_CLEAR_SYLLABLES : le16(step) >= s->lookup_count[table])
REFUSE("plan %u: a lookup the font lacks", i);
+ }
}
+ for (int k = 0; k < VEGA_PLAN_STAGES; ++k)
+ if ((uint32_t)le16(p + 100 + 8 * k) + le16(p + 102 + 8 * k) > ranges[0][1]) REFUSE("plan %u: stages", i);
}
return true;
}
@@ -290,7 +298,13 @@ typedef struct {
uint32_t codepoint; /* a character, then (map_glyphs) a glyph */
uint32_t mask, cluster, glyph_index;
uint16_t uprops, gprops;
- uint8_t lig_props, action;
+ uint8_t lig_props;
+ union {
+ uint8_t action; /* Arabic's joining action, Hangul's jamo feature */
+ uint8_t position; /* Indic's */
+ };
+ uint8_t category; /* Indic's */
+ uint8_t syllable; /* serial << 4 | type, once a shaper finds syllables */
} Info;
typedef struct {
int32_t x_advance, y_advance, x_offset, y_offset;
@@ -311,7 +325,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_VARIATION_SELECTOR_FALLBACK = 32, HAS_BROKEN_SYLLABLE = 64 };
VegaShaper *vega_shaper_new(void) { return calloc(1, sizeof(VegaShaper)); }
@@ -390,6 +404,11 @@ static void output_glyph(VegaShaper *b, uint32_t glyph) {
b->out[b->out_len++].codepoint = glyph;
}
+static void output_info(VegaShaper *b, Info info) {
+ if (!ensure(b, b->out_len + 1)) return;
+ b->out[b->out_len++] = info;
+}
+
/* 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);
@@ -674,12 +693,15 @@ static void form_clusters(VegaShaper *b) {
/* ---- The plan */
+typedef struct { uint32_t first, count; } Stage;
typedef struct {
uint32_t script;
int direction, native, shaper, flags;
uint32_t global_mask, kern_mask, masks[VEGA_PLAN_MASKS];
const unsigned char *gsub, *gpos;
uint32_t gsub_count, gpos_count;
+ unsigned shaper_flags;
+ Stage stages[VEGA_PLAN_STAGES];
} Plan;
static bool find_plan(const VegaShaping *s, uint32_t script, int direction, Plan *plan) {
@@ -691,9 +713,12 @@ static bool find_plan(const VegaShaping *s, uint32_t script, int direction, Plan
}
if (!found) return false;
*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)};
+ s->steps + (size_t)le32(found + 80) * VEGA_STEP_BYTES,
+ s->steps + (size_t)le32(found + 88) * VEGA_STEP_BYTES, le32(found + 84), le32(found + 92), found[96],
+ {{0, 0}}};
for (int k = 0; k < VEGA_PLAN_MASKS; ++k) plan->masks[k] = le32(found + 16 + 4 * k);
+ for (int k = 0; k < VEGA_PLAN_STAGES; ++k)
+ plan->stages[k] = (Stage){le16(found + 100 + 8 * k), le16(found + 102 + 8 * k)};
return true;
}
@@ -783,8 +808,23 @@ static bool compose_hebrew(const Normalizer *c, uint32_t a, uint32_t b, uint32_t
return *ab != 0;
}
+/* Indic's: no split matra composes again, and Bengali YA with nukta does,
+ * though Unicode excludes it. */
+static bool compose_indic(uint32_t a, uint32_t b, uint32_t *ab) {
+ if (is_mark_category(vega_category(a))) return false;
+ if (a == 0x09AF && b == 0x09BC) {
+ *ab = 0x09DF;
+ return true;
+ }
+ return compose_unicode(a, b, ab);
+}
+
static bool compose(const Normalizer *c, uint32_t a, uint32_t b, uint32_t *ab) {
- return c->plan->shaper == VEGA_SHAPER_HEBREW ? compose_hebrew(c, a, b, ab) : compose_unicode(a, b, ab);
+ switch (c->plan->shaper) {
+ case VEGA_SHAPER_HEBREW: return compose_hebrew(c, a, b, ab);
+ case VEGA_SHAPER_INDIC: return compose_indic(a, b, ab);
+ default: return compose_unicode(a, b, ab);
+ }
}
static bool nominal(const VegaShaping *s, uint32_t u, uint32_t *glyph) { return (*glyph = vega_shaping_glyph(s, u)) != 0; }
@@ -801,10 +841,17 @@ static void next_char(VegaShaper *b, uint32_t glyph) {
next_glyph(b);
}
+/* The shaper's decomposition: Indic keeps a few letters whole. */
+static bool decompose_char(const Normalizer *c, uint32_t ab, uint32_t *a, uint32_t *b) {
+ if (c->plan->shaper == VEGA_SHAPER_INDIC && (ab == 0x0931 || ab == 0x09DC || ab == 0x09DD || ab == 0x0B94))
+ return false;
+ return decompose_unicode(ab, a, b);
+}
+
/* Returns how many characters ab became, 0 if it did not decompose. */
static unsigned decompose(const Normalizer *c, bool shortest, uint32_t ab) {
uint32_t a, bb, a_glyph = 0, b_glyph = 0;
- if (!decompose_unicode(ab, &a, &bb) || (bb && !nominal(c->s, bb, &b_glyph))) return 0;
+ if (!decompose_char(c, ab, &a, &bb) || (bb && !nominal(c->s, bb, &b_glyph))) return 0;
bool has_a = nominal(c->s, a, &a_glyph);
if (shortest && has_a) {
output_char(c->b, a, a_glyph);
@@ -976,9 +1023,10 @@ 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); Hangul's is
- * none: what the font has stays, and nothing is composed again. */
+ * none: what the font has stays, and nothing is composed again;
+ * Indic's decomposes what decomposes even when the font has it whole. */
const bool none = plan->shaper == VEGA_SHAPER_HANGUL;
- const bool might_short_circuit = true, always_short_circuit = none;
+ const bool always_short_circuit = none, might_short_circuit = none || plan->shaper != VEGA_SHAPER_INDIC;
bool all_simple = true;
clear_output(b);
size_t count = b->len;
@@ -1237,6 +1285,7 @@ typedef struct {
uint32_t lookup_mask, lookup_props;
bool auto_zwnj, auto_zwj, random, has_glyph_classes;
int nesting_left;
+ bool per_syllable; /* GSUB matches within the syllable it starts in */
uint32_t random_state;
long last_base;
size_t last_base_until;
@@ -1271,6 +1320,7 @@ typedef struct {
Matcher matcher;
uint32_t glyph_data;
size_t idx, end;
+ uint8_t syllable; /* per syllable, from where the lookup is: only its glyphs match */
} Skippy;
enum { SKIP_NO, SKIP_YES, SKIP_MAYBE };
@@ -1278,9 +1328,11 @@ enum { MATCH_NO, MATCH_YES, MATCH_MAYBE };
enum { MATCH, NOT_MATCH, SKIP };
static void skippy_init(Skippy *it, Apply *c, bool context_match, size_t start) {
+ VegaShaper *b = c->b;
*it = (Skippy){c, c->lookup_props, context_match ? 0xFFFFFFFFu : c->lookup_mask,
c->table == 1 || (context_match && c->auto_zwnj), context_match || c->auto_zwj, c->table == 1,
- {MATCH_ANY, 0, 0}, 0, start, c->b->len};
+ {MATCH_ANY, 0, 0}, 0, start, b->len,
+ !c->table && c->per_syllable && start == b->idx && b->idx < b->len ? b->info[b->idx].syllable : 0};
}
static int may_skip(const Skippy *it, const Info *info) {
@@ -1302,7 +1354,7 @@ static bool matches(const Apply *c, const Matcher *m, uint32_t index, uint32_t g
}
static int may_match(const Skippy *it, const Info *info) {
- if (!(info->mask & it->mask)) return MATCH_NO;
+ if (!(info->mask & it->mask) || (it->syllable && it->syllable != info->syllable)) return MATCH_NO;
if (it->matcher.kind != MATCH_ANY) return matches(it->c, &it->matcher, it->glyph_data, info->codepoint) ? MATCH_YES : MATCH_NO;
return MATCH_MAYBE;
}
@@ -2105,13 +2157,23 @@ static VegaDigest buffer_digest(const VegaShaper *b) {
return d;
}
-static void apply_table(const VegaShaping *s, VegaShaper *b, int table, const unsigned char *steps, uint32_t count) {
+static bool shaper_pause(const VegaShaping *s, const Plan *plan, VegaShaper *b, unsigned pause);
+
+/* hb_ot_map_t::apply: the plan's lookups for a table in order; at a pause,
+ * the shaper's part (when it changed the buffer, the digest starts over). */
+static void apply_table(const VegaShaping *s, const Plan *plan, VegaShaper *b, int table) {
+ const unsigned char *steps = table ? plan->gpos : plan->gsub;
+ uint32_t count = table ? plan->gpos_count : plan->gsub_count;
/* A lookup can apply only where the buffer has a glyph it starts from. */
- Apply c = {s, b, table, table ? s->gpos : s->gsub, 0, 0, true, true, false, s->glyph_classes != 0,
- MAX_NESTING_LEVEL, 1, -1, 0, buffer_digest(b)};
+ Apply c = {.s = s, .b = b, .table = table, .t = table ? s->gpos : s->gsub, .has_glyph_classes = s->glyph_classes != 0,
+ .nesting_left = MAX_NESTING_LEVEL, .random_state = 1, .last_base = -1, .digest = buffer_digest(b)};
for (uint32_t i = 0; i < count; ++i) {
const unsigned char *step = steps + (size_t)i * VEGA_STEP_BYTES;
uint32_t index = le16(step);
+ if (index == VEGA_STEP_PAUSE) {
+ if (shaper_pause(s, plan, b, step[2])) c.digest = buffer_digest(b);
+ continue;
+ }
if (!digest_may_intersect(&s->lookups[table][index].digest, &c.digest)) continue;
c.lookup_mask = le32(step + 4);
c.last_base = -1;
@@ -2119,6 +2181,7 @@ static void apply_table(const VegaShaping *s, VegaShaper *b, int table, const un
c.auto_zwnj = step[2] & VEGA_STEP_AUTO_ZWNJ;
c.auto_zwj = step[2] & VEGA_STEP_AUTO_ZWJ;
c.random = step[2] & VEGA_STEP_RANDOM;
+ c.per_syllable = step[2] & VEGA_STEP_PER_SYLLABLE;
apply_string(&c, &s->lookups[table][index]);
}
}
@@ -2160,8 +2223,9 @@ static void kern_table(const VegaShaping *s, VegaShaper *b, const Plan *plan) {
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}}};
+ Apply c = {.s = s, .b = b, .table = 1, .t = s->gpos, .lookup_mask = plan->kern_mask, .lookup_props = 0x08,
+ .auto_zwnj = true, .auto_zwj = true, .has_glyph_classes = s->glyph_classes != 0,
+ .nesting_left = MAX_NESTING_LEVEL, .random_state = 1, .last_base = -1};
for (size_t idx = 0; idx < b->len;) {
if (!(b->info[idx].mask & plan->kern_mask)) {
idx++;
@@ -2385,6 +2449,756 @@ static void fallback_marks(const VegaShaping *s, const Plan *plan, VegaShaper *b
position_cluster(s, plan, b, start, b->len, adjust);
}
+/* ---- Would a lookup substitute glyphs as they are, alone
+ * (hb_ot_layout_lookup_would_substitute): the Indic shaper's question of
+ * its basic features. */
+
+static bool would_match_input(const Apply *c, Matcher m, const uint32_t *glyphs, unsigned len, unsigned count) {
+ if (count != len) return false;
+ for (unsigned i = 1; i < count; ++i)
+ if (!matches(c, &m, i - 1, glyphs[i])) return false;
+ return true;
+}
+
+static bool would_apply_rules(const Apply *c, int type, uint32_t set, Matcher input, const uint32_t *glyphs,
+ unsigned len, bool zero_context) {
+ VegaTable t = c->t;
+ for (uint32_t r = 0, n = set ? get16(t, set) : 0; r < n; ++r) {
+ uint32_t rule = offset16(t, set, set + 2 + 2 * r);
+ if (type == 5) {
+ input.values = rule + 4;
+ if (would_match_input(c, input, glyphs, len, get16(t, rule))) return true;
+ continue;
+ }
+ unsigned back_count = get16(t, rule);
+ uint32_t input_at = rule + 2 + 2 * back_count;
+ unsigned input_count = get16(t, input_at);
+ unsigned ahead_count = input_count ? get16(t, input_at + 2 + 2 * (input_count - 1)) : 0;
+ if (zero_context && (back_count || ahead_count)) continue;
+ input.values = input_at + 2;
+ if (would_match_input(c, input, glyphs, len, input_count)) return true;
+ }
+ return false;
+}
+
+static bool would_apply_subtable(const Apply *c, const VegaSubtable *st, const uint32_t *glyphs, unsigned len,
+ bool zero_context) {
+ VegaTable t = c->t;
+ uint32_t at = st->at, format = get16(t, at);
+ switch (st->type) {
+ case 1: case 2: case 3: case 8:
+ if (format != 1 && !(st->type == 1 && format == 2)) return false;
+ return len == 1 && coverage(t, offset16(t, at, at + 2), glyphs[0]) != NOT_COVERED;
+ case 4: {
+ uint32_t index = coverage(t, offset16(t, at, at + 2), glyphs[0]);
+ if (format != 1 || index == NOT_COVERED || index >= get16(t, at + 4)) return false;
+ uint32_t set = offset16(t, at, at + 6 + 2 * index);
+ for (uint32_t k = 0, n = set ? get16(t, set) : 0; k < n; ++k) {
+ uint32_t lig = offset16(t, set, set + 2 + 2 * k);
+ if (would_match_input(c, (Matcher){MATCH_GLYPH, 0, lig + 4}, glyphs, len, get16(t, lig + 2))) return true;
+ }
+ return false;
+ }
+ case 5: case 6: {
+ bool chain = st->type == 6;
+ if (format == 1 || format == 2) {
+ /* format 2 takes the first glyph's class whatever the coverage */
+ uint32_t classes = format == 2 ? offset16(t, at, at + (chain ? 6 : 4)) : 0;
+ uint32_t index = format == 1 ? coverage(t, offset16(t, at, at + 2), glyphs[0]) : class_of(t, classes, glyphs[0]);
+ uint32_t sets_at = format == 1 ? at + 4 : at + (chain ? 10 : 6);
+ if (index == NOT_COVERED || index >= get16(t, sets_at)) return false;
+ Matcher input = {format == 2 ? MATCH_CLASS : MATCH_GLYPH, classes, 0};
+ return would_apply_rules(c, st->type, offset16(t, at, sets_at + 2 + 2 * index), input, glyphs, len,
+ zero_context);
+ }
+ if (format != 3) return false;
+ if (!chain) return would_match_input(c, (Matcher){MATCH_COVERAGE, at, at + 8}, glyphs, len, get16(t, at + 2));
+ unsigned back_count = get16(t, at + 2);
+ uint32_t input_at = at + 4 + 2 * back_count;
+ unsigned input_count = get16(t, input_at);
+ unsigned ahead_count = get16(t, input_at + 2 + 2 * input_count);
+ if (zero_context && (back_count || ahead_count)) return false;
+ return would_match_input(c, (Matcher){MATCH_COVERAGE, at, input_at + 4}, glyphs, len, input_count);
+ }
+ default: return false;
+ }
+}
+
+/* Whether the lookups of a feature's stage (all of them, whatever their
+ * masks) would substitute glyphs, len of them. */
+static bool would_substitute(const VegaShaping *s, const Plan *plan, int stage, const uint32_t *glyphs, unsigned len,
+ bool zero_context) {
+ const Stage *st = &plan->stages[stage];
+ Apply c = {.s = s, .t = s->gsub};
+ for (uint32_t k = 0; k < st->count; ++k) {
+ const unsigned char *step = plan->gsub + (size_t)(st->first + k) * VEGA_STEP_BYTES;
+ uint32_t index = le16(step);
+ if (index == VEGA_STEP_PAUSE) continue;
+ const VegaLookup *l = &s->lookups[0][index];
+ if (!len || !digest_may_have(&l->digest, glyphs[0])) continue;
+ for (uint32_t i = 0; i < l->count; ++i)
+ if (would_apply_subtable(&c, &s->subtables[l->first + i], glyphs, len, zero_context)) return true;
+ }
+ return false;
+}
+
+/* ---- Syllables (hb-ot-shaper-*-machine.rl, hb-ot-shaper-syllabic.cc) */
+
+/* A shaper's machine over the buffer's categories: from each position the
+ * longest syllable it accepts; each numbered, 1 to 15 over again, beside
+ * its type. */
+static void find_syllables(VegaShaper *b, const VegaMachine *m) {
+ unsigned serial = 1;
+ for (size_t p = 0; p < b->len;) {
+ unsigned state = 0, type = 0;
+ size_t end = p + 1;
+ for (size_t q = p; q < b->len; ++q) {
+ size_t cell = (size_t)state * m->classes + m->class_of[b->info[q].category < 63 ? b->info[q].category : 63];
+ state = m->width == 1 ? ((const uint8_t *)m->next)[cell] : ((const uint16_t *)m->next)[cell];
+ if (state >= m->states) break;
+ if (m->accept[state]) type = m->accept[state], end = q + 1;
+ }
+ if (type == m->broken) b->scratch |= HAS_BROKEN_SYLLABLE;
+ for (size_t i = p; i < end; ++i) b->info[i].syllable = (uint8_t)(serial << 4 | (type ? type - 1 : 0));
+ if (++serial == 16) serial = 1;
+ p = end;
+ }
+}
+
+static size_t next_syllable(const VegaShaper *b, size_t start) {
+ size_t end = start + 1;
+ while (end < b->len && b->info[end].syllable == b->info[start].syllable) end++;
+ return end;
+}
+
+/* A dotted circle at the start of each broken syllable (after a repha that
+ * starts it), for its marks to sit on; false when none went in. */
+static bool insert_dotted_circles(const VegaShaping *s, VegaShaper *b, unsigned broken, unsigned category, int repha,
+ unsigned position) {
+ uint32_t glyph;
+ if (!(b->scratch & HAS_BROKEN_SYLLABLE) || !nominal(s, 0x25CC, &glyph)) return false;
+ /* All of it zero but these: HarfBuzz's is a fresh glyph_info. */
+ Info circle = {.codepoint = glyph, .uprops = VEGA_CC, .position = (uint8_t)position, .category = (uint8_t)category};
+ clear_output(b);
+ unsigned last = 0;
+ while (b->idx < b->len && b->successful) {
+ unsigned syllable = b->info[b->idx].syllable;
+ if (last != syllable && (syllable & 0x0F) == broken) {
+ last = syllable;
+ Info c = circle;
+ c.cluster = b->info[b->idx].cluster;
+ c.mask = b->info[b->idx].mask;
+ c.syllable = (uint8_t)syllable;
+ if (repha >= 0)
+ while (b->idx < b->len && b->successful && b->info[b->idx].syllable == last &&
+ b->info[b->idx].category == (unsigned)repha)
+ next_glyph(b);
+ output_info(b, c);
+ } else
+ next_glyph(b);
+ }
+ sync(b);
+ return true;
+}
+
+/* Vowel sequences that read as another vowel get a dotted circle between
+ * (hb-ot-shaper-vowel-constraints.cc, from the Universal Shaping Engine's
+ * IndicShapingInvalidCluster.txt): each first letter with what may not
+ * follow it. */
+static const struct { uint32_t script, first, second[12]; } vowel_constraints[] = {
+ {TAG4('D', 'e', 'v', 'a'), 0x0905, {0x093A, 0x093B, 0x093E, 0x0945, 0x0946, 0x0949, 0x094A, 0x094B, 0x094C, 0x094F, 0x0956, 0x0957}},
+ {TAG4('D', 'e', 'v', 'a'), 0x0906, {0x093A, 0x0945, 0x0946, 0x0947, 0x0948}},
+ {TAG4('D', 'e', 'v', 'a'), 0x0909, {0x0941}},
+ {TAG4('D', 'e', 'v', 'a'), 0x090F, {0x0945, 0x0946, 0x0947}},
+ {TAG4('B', 'e', 'n', 'g'), 0x0985, {0x09BE}},
+ {TAG4('B', 'e', 'n', 'g'), 0x098B, {0x09C3}},
+ {TAG4('B', 'e', 'n', 'g'), 0x098C, {0x09E2}},
+ {TAG4('G', 'u', 'r', 'u'), 0x0A05, {0x0A3E, 0x0A48, 0x0A4C}},
+ {TAG4('G', 'u', 'r', 'u'), 0x0A72, {0x0A3F, 0x0A40, 0x0A47}},
+ {TAG4('G', 'u', 'r', 'u'), 0x0A73, {0x0A41, 0x0A42, 0x0A4B}},
+ {TAG4('G', 'u', 'j', 'r'), 0x0A85, {0x0ABE, 0x0AC5, 0x0AC7, 0x0AC8, 0x0AC9, 0x0ACB, 0x0ACC}},
+ {TAG4('G', 'u', 'j', 'r'), 0x0AC5, {0x0ABE}},
+ {TAG4('O', 'r', 'y', 'a'), 0x0B05, {0x0B3E}},
+ {TAG4('O', 'r', 'y', 'a'), 0x0B0F, {0x0B57}},
+ {TAG4('O', 'r', 'y', 'a'), 0x0B13, {0x0B57}},
+ {TAG4('T', 'a', 'm', 'l'), 0x0B85, {0x0BC2}},
+ {TAG4('T', 'e', 'l', 'u'), 0x0C12, {0x0C4C, 0x0C55}},
+ {TAG4('T', 'e', 'l', 'u'), 0x0C3F, {0x0C55}},
+ {TAG4('T', 'e', 'l', 'u'), 0x0C46, {0x0C55}},
+ {TAG4('T', 'e', 'l', 'u'), 0x0C4A, {0x0C55}},
+ {TAG4('K', 'n', 'd', 'a'), 0x0C89, {0x0CBE}},
+ {TAG4('K', 'n', 'd', 'a'), 0x0C8B, {0x0CBE}},
+ {TAG4('K', 'n', 'd', 'a'), 0x0C92, {0x0CCC}},
+ {TAG4('M', 'l', 'y', 'm'), 0x0D07, {0x0D57}},
+ {TAG4('M', 'l', 'y', 'm'), 0x0D09, {0x0D57}},
+ {TAG4('M', 'l', 'y', 'm'), 0x0D0E, {0x0D46}},
+ {TAG4('M', 'l', 'y', 'm'), 0x0D12, {0x0D3E, 0x0D57}},
+ {TAG4('S', 'i', 'n', 'h'), 0x0D85, {0x0DCF, 0x0DD0, 0x0DD1}},
+ {TAG4('S', 'i', 'n', 'h'), 0x0D8B, {0x0DDF}},
+ {TAG4('S', 'i', 'n', 'h'), 0x0D8D, {0x0DD8}},
+ {TAG4('S', 'i', 'n', 'h'), 0x0D8F, {0x0DDF}},
+ {TAG4('S', 'i', 'n', 'h'), 0x0D91, {0x0DCA, 0x0DD9, 0x0DDA, 0x0DDC, 0x0DDD, 0x0DDE}},
+ {TAG4('S', 'i', 'n', 'h'), 0x0D94, {0x0DDF}},
+ {TAG4('B', 'r', 'a', 'h'), 0x11005, {0x11038}},
+ {TAG4('B', 'r', 'a', 'h'), 0x1100B, {0x1103E}},
+ {TAG4('B', 'r', 'a', 'h'), 0x1100F, {0x11042}},
+ {TAG4('K', 'h', 'o', 'j'), 0x11200, {0x1122C, 0x11231, 0x11233}},
+ {TAG4('K', 'h', 'o', 'j'), 0x11206, {0x1122C}},
+ {TAG4('K', 'h', 'o', 'j'), 0x1122C, {0x11230, 0x11231}},
+ {TAG4('K', 'h', 'o', 'j'), 0x11240, {0x1122E}},
+ {TAG4('S', 'i', 'n', 'd'), 0x112B0, {0x112E0, 0x112E5, 0x112E6, 0x112E7, 0x112E8}},
+ {TAG4('T', 'i', 'r', 'h'), 0x11481, {0x114B0}},
+ {TAG4('T', 'i', 'r', 'h'), 0x1148B, {0x114BA}},
+ {TAG4('T', 'i', 'r', 'h'), 0x1148D, {0x114BA}},
+ {TAG4('T', 'i', 'r', 'h'), 0x114AA, {0x114B5, 0x114B6}},
+ {TAG4('M', 'o', 'd', 'i'), 0x11600, {0x11639, 0x1163A}},
+ {TAG4('M', 'o', 'd', 'i'), 0x11601, {0x11639, 0x1163A}},
+ {TAG4('T', 'a', 'k', 'r'), 0x11680, {0x116AD, 0x116B4, 0x116B5}},
+ {TAG4('T', 'a', 'k', 'r'), 0x11686, {0x116B2}},
+};
+
+/* A dotted circle out before the current character, as a copy of it (its
+ * properties, less continuing the character before). */
+static void output_dotted_circle(VegaShaper *b) {
+ output_glyph(b, 0x25CC);
+ if (b->out_len) b->out[b->out_len - 1].uprops &= (uint16_t)~UPROPS_CONTINUATION;
+}
+
+static void preprocess_vowel_constraints(const Plan *plan, VegaShaper *b) {
+ bool any = false;
+ for (size_t k = 0; k < sizeof(vowel_constraints) / sizeof(*vowel_constraints) && !any; ++k)
+ any = vowel_constraints[k].script == plan->script;
+ if (!any && plan->script != TAG4('D', 'e', 'v', 'a')) return;
+ clear_output(b);
+ while (b->idx + 1 < b->len && b->successful) {
+ uint32_t u = b->info[b->idx].codepoint, next = b->info[b->idx + 1].codepoint;
+ bool matched = false;
+ for (size_t k = 0; k < sizeof(vowel_constraints) / sizeof(*vowel_constraints) && !matched; ++k)
+ if (vowel_constraints[k].script == plan->script && vowel_constraints[k].first == u)
+ for (int j = 0; j < 12 && vowel_constraints[k].second[j] && !matched; ++j)
+ matched = vowel_constraints[k].second[j] == next;
+ /* Devanagari's RA, virama, I: the circle goes before the I */
+ if (plan->script == TAG4('D', 'e', 'v', 'a') && u == 0x0930 && next == 0x094D && b->idx + 2 < b->len &&
+ b->info[b->idx + 2].codepoint == 0x0907) {
+ next_glyph(b);
+ next_glyph(b);
+ output_dotted_circle(b);
+ }
+ next_glyph(b);
+ if (matched) {
+ output_dotted_circle(b);
+ next_glyph(b);
+ }
+ }
+ sync(b);
+}
+
+/* ---- Indic (hb-ot-shaper-indic.cc): each consonant syllable's base found,
+ * its pre-base matras, reph and the like moved around the base and masked
+ * for the basic features; after those, the matras, reph and
+ * pre-base-reordering consonants placed again by what the features made. */
+
+enum { REPH_IMPLICIT, REPH_EXPLICIT, REPH_LOG_REPHA };
+typedef struct { uint32_t script, virama; uint8_t reph_pos, reph_mode; bool blwf_post_only; } IndicConfig;
+static const IndicConfig indic_configs[] = {
+ {0, 0, VEGA_POS_BEFORE_POST, REPH_IMPLICIT, false},
+ {TAG4('D', 'e', 'v', 'a'), 0x094D, VEGA_POS_BEFORE_POST, REPH_IMPLICIT, false},
+ {TAG4('B', 'e', 'n', 'g'), 0x09CD, VEGA_POS_AFTER_SUB, REPH_IMPLICIT, false},
+ {TAG4('G', 'u', 'r', 'u'), 0x0A4D, VEGA_POS_BEFORE_SUB, REPH_IMPLICIT, false},
+ {TAG4('G', 'u', 'j', 'r'), 0x0ACD, VEGA_POS_BEFORE_POST, REPH_IMPLICIT, false},
+ {TAG4('O', 'r', 'y', 'a'), 0x0B4D, VEGA_POS_AFTER_MAIN, REPH_IMPLICIT, false},
+ {TAG4('T', 'a', 'm', 'l'), 0x0BCD, VEGA_POS_AFTER_POST, REPH_IMPLICIT, false},
+ {TAG4('T', 'e', 'l', 'u'), 0x0C4D, VEGA_POS_AFTER_POST, REPH_EXPLICIT, true},
+ {TAG4('K', 'n', 'd', 'a'), 0x0CCD, VEGA_POS_AFTER_POST, REPH_IMPLICIT, true},
+ {TAG4('M', 'l', 'y', 'm'), 0x0D4D, VEGA_POS_AFTER_MAIN, REPH_LOG_REPHA, false},
+};
+
+typedef struct {
+ const VegaShaping *s;
+ const Plan *plan;
+ const IndicConfig *config;
+ bool old_spec, zero_context;
+ uint32_t virama; /* its glyph; 0 when the font has none */
+} Indic;
+
+static Indic indic_of(const VegaShaping *s, const Plan *plan) {
+ Indic d = {s, plan, &indic_configs[0], plan->shaper_flags & VEGA_INDIC_OLD_SPEC, false, 0};
+ for (size_t i = 1; i < sizeof(indic_configs) / sizeof(*indic_configs); ++i)
+ if (indic_configs[i].script == plan->script) d.config = &indic_configs[i];
+ /* New specifications match the basic features without context; so do
+ * scripts with one, but Malayalam's both take context. */
+ d.zero_context = !d.old_spec && plan->script != TAG4('M', 'l', 'y', 'm');
+ if (d.config->virama) nominal(s, d.config->virama, &d.virama);
+ return d;
+}
+
+#define FLAG(x) (1u << (x))
+#define FLAG_UNSAFE(x) ((x) < 32 ? 1u << (x) : 0u)
+static bool is_one_of(const Info *i, uint32_t flags) {
+ return !(i->gprops & LIGATED) && (FLAG_UNSAFE(i->category) & flags); /* a ligature is none of them */
+}
+static bool is_consonant(const Info *i) {
+ return is_one_of(i, FLAG(VEGA_OT_C) | FLAG(VEGA_OT_CS) | FLAG(VEGA_OT_RA) | FLAG(VEGA_OT_CM) | FLAG(VEGA_OT_V) |
+ FLAG(VEGA_OT_PLACEHOLDER) | FLAG(VEGA_OT_DOTTEDCIRCLE));
+}
+static bool is_joiner(const Info *i) { return is_one_of(i, FLAG(VEGA_OT_ZWJ) | FLAG(VEGA_OT_ZWNJ)); }
+static bool is_halant(const Info *i) { return is_one_of(i, FLAG(VEGA_OT_H)); }
+static bool ligated_and_didnt_multiply(const Info *i) { return (i->gprops & LIGATED) && !(i->gprops & MULTIPLIED); }
+
+static bool indic_would(const Indic *d, int stage, const uint32_t *glyphs, unsigned len) {
+ return would_substitute(d->s, d->plan, stage, glyphs, len, d->zero_context);
+}
+
+/* Below-base, post-base or base: what the font's features would make of
+ * the consonant with a virama, either way round (fonts copied old lookups
+ * into new scripts). */
+static uint8_t consonant_position(const Indic *d, uint32_t consonant) {
+ uint32_t glyphs[3] = {d->virama, consonant, d->virama};
+ if (indic_would(d, VEGA_STAGE_BLWF, glyphs, 2) || indic_would(d, VEGA_STAGE_BLWF, glyphs + 1, 2) ||
+ indic_would(d, VEGA_STAGE_VATU, glyphs, 2) || indic_would(d, VEGA_STAGE_VATU, glyphs + 1, 2))
+ return VEGA_POS_BELOW_C;
+ if (indic_would(d, VEGA_STAGE_PSTF, glyphs, 2) || indic_would(d, VEGA_STAGE_PSTF, glyphs + 1, 2) ||
+ indic_would(d, VEGA_STAGE_PREF, glyphs, 2) || indic_would(d, VEGA_STAGE_PREF, glyphs + 1, 2))
+ return VEGA_POS_POST_C;
+ return VEGA_POS_BASE_C;
+}
+
+static void setup_masks_indic(VegaShaper *b) {
+ for (size_t i = 0; i < b->len; ++i) {
+ uint16_t categories = vega_indic_categories(b->info[i].codepoint);
+ b->info[i].category = categories & 0xFF;
+ b->info[i].position = categories >> 8;
+ }
+}
+
+static void indic_reorder_consonant_syllable(const Indic *d, VegaShaper *b, size_t start, size_t end) {
+ Info *info = b->info;
+ const uint32_t *masks = d->plan->masks;
+ uint32_t script = d->plan->script;
+ /* Kannada's Ra, halant, ZWJ is Ra, ZWJ, halant. */
+ if (script == TAG4('K', 'n', 'd', 'a') && start + 3 <= end && is_one_of(&info[start], FLAG(VEGA_OT_RA)) &&
+ is_one_of(&info[start + 1], FLAG(VEGA_OT_H)) && is_one_of(&info[start + 2], FLAG(VEGA_OT_ZWJ))) {
+ merge_clusters(b, start + 1, start + 3);
+ Info t = info[start + 1];
+ info[start + 1] = info[start + 2];
+ info[start + 2] = t;
+ }
+
+ /* 1. The base: from the end back, the first consonant without a
+ * below-base or post-base form (those follow below-base ones), or the
+ * first consonant. An initial Ra, halant that the font makes a reph
+ * is no candidate. */
+ size_t base = end, limit = start;
+ bool has_reph = false;
+ if (masks[VEGA_MASK_RPHF] && start + 3 <= end &&
+ ((d->config->reph_mode == REPH_IMPLICIT && !is_joiner(&info[start + 2])) ||
+ (d->config->reph_mode == REPH_EXPLICIT && info[start + 2].category == VEGA_OT_ZWJ))) {
+ uint32_t glyphs[3] = {info[start].codepoint, info[start + 1].codepoint,
+ d->config->reph_mode == REPH_EXPLICIT ? info[start + 2].codepoint : 0};
+ if (indic_would(d, VEGA_STAGE_RPHF, glyphs, 2) ||
+ (d->config->reph_mode == REPH_EXPLICIT && indic_would(d, VEGA_STAGE_RPHF, glyphs, 3))) {
+ limit += 2;
+ while (limit < end && is_joiner(&info[limit])) limit++;
+ base = start;
+ has_reph = true;
+ }
+ } else if (d->config->reph_mode == REPH_LOG_REPHA && info[start].category == VEGA_OT_REPHA) {
+ limit += 1;
+ while (limit < end && is_joiner(&info[limit])) limit++;
+ base = start;
+ has_reph = true;
+ }
+ {
+ size_t i = end;
+ bool seen_below = false;
+ do {
+ i--;
+ if (is_consonant(&info[i])) {
+ if (info[i].position != VEGA_POS_BELOW_C && (info[i].position != VEGA_POS_POST_C || seen_below)) {
+ base = i;
+ break;
+ }
+ if (info[i].position == VEGA_POS_BELOW_C) seen_below = true;
+ base = i;
+ } else if (start < i && info[i].category == VEGA_OT_ZWJ && info[i - 1].category == VEGA_OT_H)
+ break; /* a ZWJ after a halant asks for a half form: the search stops */
+ } while (i > limit);
+ }
+ if (has_reph && base == start && limit - base <= 2) has_reph = false; /* Ra alone is the base */
+
+ /* 2 and 3 (matras decomposed, marks in canonical order) normalization
+ * did. Reorder: */
+ for (size_t i = start; i < base; ++i)
+ if (info[i].position > VEGA_POS_PRE_C) info[i].position = VEGA_POS_PRE_C;
+ if (base < end) info[base].position = VEGA_POS_BASE_C;
+ if (has_reph) info[start].position = VEGA_POS_RA_TO_BECOME_REPH;
+
+ /* Old specifications: the first post-base halant after the last
+ * consonant (Kannada not past a halant already there). */
+ if (d->old_spec) {
+ bool disallow_double_halants = script == TAG4('K', 'n', 'd', 'a');
+ for (size_t i = base + 1; i < end; ++i)
+ if (info[i].category == VEGA_OT_H) {
+ size_t j;
+ for (j = end - 1; j > i; j--)
+ if (is_consonant(&info[j]) || (disallow_double_halants && info[j].category == VEGA_OT_H)) break;
+ if (info[j].category != VEGA_OT_H && j > i) {
+ Info t = info[i];
+ memmove(&info[i], &info[i + 1], (j - i) * sizeof(Info));
+ info[j] = t;
+ }
+ break;
+ }
+ }
+
+ /* Joiners, nuktas, register shifters, medials and halants go with what
+ * is before them (a halant not with a left matra). */
+ {
+ uint8_t last_pos = VEGA_POS_START;
+ for (size_t i = start; i < end; ++i) {
+ if (FLAG_UNSAFE(info[i].category) & (FLAG(VEGA_OT_ZWJ) | FLAG(VEGA_OT_ZWNJ) | FLAG(VEGA_OT_N) |
+ FLAG(VEGA_OT_RS) | FLAG(VEGA_OT_CM) | FLAG(VEGA_OT_H))) {
+ info[i].position = last_pos;
+ if (info[i].category == VEGA_OT_H && info[i].position == VEGA_POS_PRE_M)
+ for (size_t j = i; j > start; j--)
+ if (info[j - 1].position != VEGA_POS_PRE_M) {
+ info[i].position = info[j - 1].position;
+ break;
+ }
+ } else if (info[i].position != VEGA_POS_SMVD) {
+ if (info[i].category == VEGA_OT_MPST && i > start && info[i - 1].category == VEGA_OT_SM)
+ info[i - 1].position = info[i].position;
+ last_pos = info[i].position;
+ }
+ }
+ }
+ /* A post-base consonant owns what is before it since the last
+ * consonant or matra. */
+ {
+ size_t last = base;
+ for (size_t i = base + 1; i < end; ++i)
+ if (is_consonant(&info[i])) {
+ for (size_t j = last + 1; j < i; ++j)
+ if (info[j].position < VEGA_POS_SMVD) info[j].position = info[i].position;
+ last = i;
+ } else if (FLAG_UNSAFE(info[i].category) & (FLAG(VEGA_OT_M) | FLAG(VEGA_OT_MPST)))
+ last = i;
+ }
+
+ {
+ /* Sorted by position, stably; syllable holds each glyph's place
+ * before, to merge the clusters of what moved past base. */
+ uint8_t syllable = info[start].syllable;
+ for (size_t i = start; i < end; ++i) info[i].syllable = (uint8_t)(i - start);
+ for (size_t i = start + 1; i < end; ++i) {
+ size_t j = i;
+ while (j > start && info[j - 1].position > info[i].position) j--;
+ if (j == i) continue;
+ Info t = info[i];
+ memmove(&info[j + 1], &info[j], (i - j) * sizeof(Info));
+ info[j] = t;
+ }
+ /* The base again; a run of left matras back in their order. */
+ size_t first_left_matra = end, last_left_matra = end;
+ base = end;
+ for (size_t i = start; i < end; ++i) {
+ if (info[i].position == VEGA_POS_BASE_C) {
+ base = i;
+ break;
+ }
+ if (info[i].position == VEGA_POS_PRE_M) {
+ if (first_left_matra == end) first_left_matra = i;
+ last_left_matra = i;
+ }
+ }
+ if (first_left_matra < last_left_matra) {
+ reverse_range(b, first_left_matra, last_left_matra + 1, false);
+ size_t i = first_left_matra;
+ for (size_t j = i; j <= last_left_matra; ++j)
+ if (FLAG_UNSAFE(info[j].category) & (FLAG(VEGA_OT_M) | FLAG(VEGA_OT_MPST))) {
+ reverse_range(b, i, j + 1, false);
+ i = j + 1;
+ }
+ }
+ if (d->old_spec || end - start > 127)
+ merge_clusters(b, base, end);
+ else
+ for (size_t i = base; i < end; ++i)
+ if (info[i].syllable != 255) {
+ size_t min = i, max = i, j = start + info[i].syllable;
+ while (j != i) {
+ if (j < min) min = j;
+ if (j > max) max = j;
+ size_t next = start + info[j].syllable;
+ info[j].syllable = 255;
+ j = next;
+ }
+ merge_clusters(b, base > min ? base : min, max + 1);
+ }
+ for (size_t i = start; i < end; ++i) info[i].syllable = syllable;
+ }
+
+ /* Masks: reph; pre-base (half, and blwf in new specifications that
+ * have it there); post-base. */
+ for (size_t i = start; i < end && info[i].position == VEGA_POS_RA_TO_BECOME_REPH; ++i)
+ info[i].mask |= masks[VEGA_MASK_RPHF];
+ uint32_t mask = masks[VEGA_MASK_HALF];
+ if (!d->old_spec && !d->config->blwf_post_only) mask |= masks[VEGA_MASK_BLWF];
+ for (size_t i = start; i < base; ++i) info[i].mask |= mask;
+ mask = masks[VEGA_MASK_BLWF] | masks[VEGA_MASK_ABVF] | masks[VEGA_MASK_PSTF];
+ for (size_t i = base + 1; i < end; ++i) info[i].mask |= mask;
+
+ /* Old-spec Devanagari: an eyelash Ra below a half form too, but not
+ * with a ZWJ. */
+ if (d->old_spec && script == TAG4('D', 'e', 'v', 'a'))
+ for (size_t i = start; i + 1 < base; ++i)
+ if (info[i].category == VEGA_OT_RA && info[i + 1].category == VEGA_OT_H &&
+ (i + 2 == base || info[i + 2].category != VEGA_OT_ZWJ)) {
+ info[i].mask |= masks[VEGA_MASK_BLWF];
+ info[i + 1].mask |= masks[VEGA_MASK_BLWF];
+ }
+
+ /* A halant, Ra the font reorders before the base: marked for pref. */
+ if (masks[VEGA_MASK_PREF] && base + 2 < end)
+ for (size_t i = base + 1; i + 1 < end; ++i) {
+ uint32_t glyphs[2] = {info[i].codepoint, info[i + 1].codepoint};
+ if (indic_would(d, VEGA_STAGE_PREF, glyphs, 2)) {
+ info[i].mask |= masks[VEGA_MASK_PREF];
+ info[i + 1].mask |= masks[VEGA_MASK_PREF];
+ break;
+ }
+ }
+
+ /* A ZWNJ keeps what is before it back to a consonant from half forms. */
+ for (size_t i = start + 1; i < end; ++i)
+ if (is_joiner(&info[i])) {
+ bool non_joiner = info[i].category == VEGA_OT_ZWNJ;
+ size_t j = i;
+ do {
+ j--;
+ if (non_joiner) info[j].mask &= ~masks[VEGA_MASK_HALF];
+ } while (j > start && !is_consonant(&info[j]));
+ }
+}
+
+static bool indic_initial_reordering(const Indic *d, VegaShaper *b) {
+ if (d->virama)
+ for (size_t i = 0; i < b->len; ++i)
+ if (b->info[i].position == VEGA_POS_BASE_C) b->info[i].position = consonant_position(d, b->info[i].codepoint);
+ bool changed = insert_dotted_circles(d->s, b, 4, VEGA_OT_DOTTEDCIRCLE, VEGA_OT_REPHA, VEGA_POS_END);
+ for (size_t start = 0, end; start < b->len; start = end) {
+ end = next_syllable(b, start);
+ /* Vowel syllables look like consonant ones, and broken ones have
+ * their dotted circle, like a standalone cluster's. */
+ unsigned type = b->info[start].syllable & 0x0F;
+ if (type <= 2 || type == 4) indic_reorder_consonant_syllable(d, b, start, end);
+ }
+ return changed;
+}
+
+/* Categories of the preceding character that keep a left matra from
+ * 'init': HarfBuzz's Cf to Mn. */
+static bool word_character(unsigned c) {
+ switch (c) {
+ case VEGA_CF: case VEGA_CN: case VEGA_CO: case VEGA_CS: case VEGA_LL: case VEGA_LM: case VEGA_LO: case VEGA_LT:
+ case VEGA_LU: case VEGA_MC: case VEGA_ME: case VEGA_MN:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static void indic_final_syllable(const Indic *d, VegaShaper *b, size_t start, size_t end) {
+ Info *info = b->info;
+ const uint32_t *masks = d->plan->masks;
+ uint32_t script = d->plan->script;
+ bool malayalam_or_tamil = script == TAG4('M', 'l', 'y', 'm') || script == TAG4('T', 'a', 'm', 'l');
+ /* A virama that ligated and multiplied back out is a halant again. */
+ if (d->virama)
+ for (size_t i = start; i < end; ++i)
+ if (info[i].codepoint == d->virama && (info[i].gprops & LIGATED) && (info[i].gprops & MULTIPLIED)) {
+ info[i].category = VEGA_OT_H;
+ info[i].gprops &= (uint16_t)~(LIGATED | MULTIPLIED);
+ }
+
+ /* 4. The base again: where a pref candidate formed nothing, or (in
+ * Malayalam) past unformed below-base forms. */
+ bool try_pref = masks[VEGA_MASK_PREF] != 0;
+ size_t base;
+ for (base = start; base < end; base++)
+ if (info[base].position >= VEGA_POS_BASE_C) {
+ if (try_pref && base + 1 < end)
+ for (size_t i = base + 1; i < end; ++i)
+ if (info[i].mask & masks[VEGA_MASK_PREF]) {
+ if (!((info[i].gprops & SUBSTITUTED) && ligated_and_didnt_multiply(&info[i]))) {
+ base = i;
+ while (base < end && is_halant(&info[base])) base++;
+ if (base < end) info[base].position = VEGA_POS_BASE_C;
+ try_pref = false;
+ }
+ break;
+ }
+ if (script == TAG4('M', 'l', 'y', 'm'))
+ for (size_t i = base + 1; i < end; ++i) {
+ while (i < end && is_joiner(&info[i])) i++;
+ if (i == end || !is_halant(&info[i])) break;
+ i++;
+ while (i < end && is_joiner(&info[i])) i++;
+ if (i < end && is_consonant(&info[i]) && info[i].position == VEGA_POS_BELOW_C) {
+ base = i;
+ info[base].position = VEGA_POS_BASE_C;
+ }
+ }
+ if (start < base && info[base].position > VEGA_POS_BASE_C) base--;
+ break;
+ }
+ if (base == end && start < base && is_one_of(&info[base - 1], FLAG(VEGA_OT_ZWJ))) base--;
+ if (base < end)
+ while (start < base && is_one_of(&info[base], FLAG(VEGA_OT_N) | FLAG(VEGA_OT_H))) base--;
+
+ /* Pre-base matras: after the last halant before the base (not one a
+ * ZWJ follows, nor the matra's own), else where they are. */
+ if (start + 1 < end && start < base) {
+ size_t new_pos = base == end ? base - 2 : base - 1;
+ if (!malayalam_or_tamil) {
+ for (;;) {
+ while (new_pos > start &&
+ !is_one_of(&info[new_pos], FLAG(VEGA_OT_M) | FLAG(VEGA_OT_MPST) | FLAG(VEGA_OT_H)))
+ new_pos--;
+ if (is_halant(&info[new_pos]) && info[new_pos].position != VEGA_POS_PRE_M) {
+ if (new_pos + 1 < end && info[new_pos + 1].category == VEGA_OT_ZWJ && new_pos > start) {
+ new_pos--;
+ continue; /* keep searching */
+ }
+ } else
+ new_pos = start;
+ break;
+ }
+ }
+ if (start < new_pos && info[new_pos].position != VEGA_POS_PRE_M) {
+ for (size_t i = new_pos; i > start; i--)
+ if (info[i - 1].position == VEGA_POS_PRE_M) {
+ size_t old_pos = i - 1;
+ if (old_pos < base && base <= new_pos) base--;
+ Info t = info[old_pos];
+ memmove(&info[old_pos], &info[old_pos + 1], (new_pos - old_pos) * sizeof(Info));
+ info[new_pos] = t;
+ merge_clusters(b, new_pos, end < base + 1 ? end : base + 1);
+ new_pos--;
+ }
+ } else
+ for (size_t i = start; i < base; ++i)
+ if (info[i].position == VEGA_POS_PRE_M) {
+ merge_clusters(b, i, end < base + 1 ? end : base + 1);
+ break;
+ }
+ }
+
+ /* Reph: moved only if it formed (or, encoded as a repha, if it did
+ * not ligate), to where the script puts it. */
+ if (start + 1 < end && info[start].position == VEGA_POS_RA_TO_BECOME_REPH &&
+ ((info[start].category == VEGA_OT_REPHA) ^ ligated_and_didnt_multiply(&info[start]))) {
+ size_t new_reph_pos = start;
+ unsigned reph_pos = d->config->reph_pos;
+ bool found = false;
+ if (reph_pos != VEGA_POS_AFTER_POST) {
+ /* 2. after the first explicit halant before the base (and a
+ * joiner after it) */
+ new_reph_pos = start + 1;
+ while (new_reph_pos < base && !is_halant(&info[new_reph_pos])) new_reph_pos++;
+ if (new_reph_pos < base && is_halant(&info[new_reph_pos])) {
+ if (new_reph_pos + 1 < base && is_joiner(&info[new_reph_pos + 1])) new_reph_pos++;
+ found = true;
+ }
+ /* 3. after the main consonant */
+ if (!found && reph_pos == VEGA_POS_AFTER_MAIN) {
+ new_reph_pos = base;
+ while (new_reph_pos + 1 < end && info[new_reph_pos + 1].position <= VEGA_POS_AFTER_MAIN) new_reph_pos++;
+ found = new_reph_pos < end;
+ }
+ /* 4. before the first post-base consonant or matra */
+ if (!found && reph_pos == VEGA_POS_AFTER_SUB) {
+ new_reph_pos = base;
+ while (new_reph_pos + 1 < end &&
+ !(FLAG_UNSAFE(info[new_reph_pos + 1].position) &
+ (FLAG(VEGA_POS_POST_C) | FLAG(VEGA_POS_AFTER_POST) | FLAG(VEGA_POS_SMVD))))
+ new_reph_pos++;
+ found = new_reph_pos < end;
+ }
+ }
+ if (!found) {
+ /* 5. as step 2 */
+ new_reph_pos = start + 1;
+ while (new_reph_pos < base && !is_halant(&info[new_reph_pos])) new_reph_pos++;
+ if (new_reph_pos < base && is_halant(&info[new_reph_pos])) {
+ if (new_reph_pos + 1 < base && is_joiner(&info[new_reph_pos + 1])) new_reph_pos++;
+ found = true;
+ }
+ }
+ if (!found) {
+ /* 6. the end, before any syllable modifiers; before a halant
+ * that follows a matra */
+ new_reph_pos = end - 1;
+ while (new_reph_pos > start && info[new_reph_pos].position == VEGA_POS_SMVD) new_reph_pos--;
+ if (is_halant(&info[new_reph_pos]))
+ for (size_t i = base + 1; i < new_reph_pos; ++i)
+ if (FLAG_UNSAFE(info[i].category) & (FLAG(VEGA_OT_M) | FLAG(VEGA_OT_MPST))) new_reph_pos--;
+ }
+ merge_clusters(b, start, new_reph_pos + 1);
+ Info reph = info[start];
+ memmove(&info[start], &info[start + 1], (new_reph_pos - start) * sizeof(Info));
+ info[new_reph_pos] = reph;
+ if (start < base && base <= new_reph_pos) base--;
+ }
+
+ /* A pre-base-reordering consonant that pref formed: before the base,
+ * where a pre-base matra would go. */
+ if (try_pref && base + 1 < end)
+ for (size_t i = base + 1; i < end; ++i)
+ if (info[i].mask & masks[VEGA_MASK_PREF]) {
+ if (ligated_and_didnt_multiply(&info[i])) {
+ size_t new_pos = base;
+ if (!malayalam_or_tamil)
+ while (new_pos > start &&
+ !is_one_of(&info[new_pos - 1], FLAG(VEGA_OT_M) | FLAG(VEGA_OT_MPST) | FLAG(VEGA_OT_H)))
+ new_pos--;
+ if (new_pos > start && is_halant(&info[new_pos - 1]) && new_pos < end && is_joiner(&info[new_pos]))
+ new_pos++;
+ size_t old_pos = i;
+ merge_clusters(b, new_pos, old_pos + 1);
+ Info t = info[old_pos];
+ memmove(&info[new_pos + 1], &info[new_pos], (old_pos - new_pos) * sizeof(Info));
+ info[new_pos] = t;
+ if (new_pos <= base && base < old_pos) base++;
+ }
+ break;
+ }
+
+ /* 'init' on a left matra that starts a word. */
+ if (info[start].position == VEGA_POS_PRE_M && (!start || !word_character(gen_cat(&info[start - 1]))))
+ info[start].mask |= masks[VEGA_MASK_INDIC_INIT];
+}
+
+static void indic_final_reordering(const Indic *d, VegaShaper *b) {
+ for (size_t start = 0, end; start < b->len; start = end) {
+ end = next_syllable(b, start);
+ indic_final_syllable(d, b, start, end);
+ }
+}
+
+/* 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) {
+ if (plan->shaper != VEGA_SHAPER_INDIC) return false;
+ Indic d = indic_of(s, plan);
+ switch (pause) {
+ case VEGA_PAUSE_SYLLABLES: find_syllables(b, &vega_indic_machine); return false;
+ case VEGA_PAUSE_INITIAL_REORDERING: return indic_initial_reordering(&d, b);
+ case VEGA_PAUSE_FINAL_REORDERING: indic_final_reordering(&d, b); return false;
+ default: return false;
+ }
+}
+
/* ---- Positions */
static void fallback_spaces(const VegaShaping *s, VegaShaper *b) {
@@ -2449,7 +3263,7 @@ static void position(const VegaShaping *s, const Plan *plan, VegaShaper *b) {
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_GPOS) apply_table(s, plan, b, 1);
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)
@@ -2484,13 +3298,14 @@ long vega_shape(VegaShaper *b, const VegaShaping *s, const uint32_t *codes, size
b->max_len = count * 64 > 16384 ? count * 64 : 16384;
b->max_ops = (long)(count * 1024 > 16384 ? count * 1024 : 16384);
if (!ensure(b, count + 1)) return -1;
- for (size_t i = 0; i < count; ++i) b->info[i] = (Info){codes[i], plan.global_mask, (uint32_t)i, 0, 0, 0, 0, 0};
+ for (size_t i = 0; i < count; ++i) b->info[i] = (Info){.codepoint = codes[i], .mask = plan.global_mask, .cluster = (uint32_t)i};
b->len = count;
set_unicode_props(b);
form_clusters(b);
ensure_native_direction(b, &plan);
if (plan.shaper == VEGA_SHAPER_HANGUL) preprocess_hangul(s, b);
+ if (plan.shaper == VEGA_SHAPER_INDIC) preprocess_vowel_constraints(&plan, b);
/* Mirror what the font mirrors; mark the rest for rtlm. */
if (direction == VEGA_SHAPE_RTL)
@@ -2502,6 +3317,7 @@ 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_INDIC) setup_masks_indic(b);
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)
@@ -2517,7 +3333,7 @@ long vega_shape(VegaShaper *b, const VegaShaping *s, const uint32_t *codes, size
if (plan.flags & VEGA_PLAN_GUESS_CLASSES)
info->gprops = gen_cat(info) != VEGA_MN || is_default_ignorable(info) ? BASE_GLYPH : MARK;
}
- apply_table(s, b, 0, plan.gsub, plan.gsub_count);
+ apply_table(s, &plan, b, 0);
if (!b->successful || !ensure(b, b->len)) return -1;
position(s, &plan, b);
diff --git a/text/shape.h b/text/shape.h
index fdec7ee..7b12009 100644
--- a/text/shape.h
+++ b/text/shape.h
@@ -6,7 +6,8 @@
/* Shaping (docs/design/004-text.md): a run of characters (one script, one
* direction) to glyphs and their positions, as HarfBuzz 10 shapes it with
- * its default, Arabic and Hebrew shapers, glyph for glyph and unit for unit.
+ * its default, Arabic, Hebrew, Hangul and Indic shapers, glyph for glyph and
+ * unit for unit.
*
* What a font's shaping needs travels in a symbol file (its shaping
* section), written by the build tool:
@@ -31,15 +32,22 @@
* no plan positions marks without
* GPOS; x_bearing INT16_MIN: none),
* u32 0
- * Offsets are from the section's start. A plan:
+ * 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,
* 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 = 80, VEGA_PLAN_BYTES = 88, VEGA_STEP_BYTES = 8, VEGA_PLAN_MASKS = 14 };
+ * GSUB step, u32 GSUB steps, u32 first GPOS step, u32 GPOS steps,
+ * u8 shaper flags, u8 0[3],
+ * the stages the shaper asks whether a feature would substitute in
+ * (VEGA_PLAN_STAGES of: u16 first step, counted from the plan's first
+ * GSUB step, u16 steps, u32 0), u32 0.
+ * A step, a lookup to run: u16 lookup index, u8 flags, u8 0, u32 mask; or
+ * a pause, where the shaper does its part: u16 VEGA_STEP_PAUSE, u8 which
+ * (VEGA_PAUSE_...), u8 0, u32 0. */
+enum { VEGA_SHAPING_HEADER = 80, VEGA_PLAN_BYTES = 144, VEGA_STEP_BYTES = 8, VEGA_PLAN_MASKS = 16,
+ VEGA_PLAN_STAGES = 5 };
enum { VEGA_SHAPE_LTR = 1, VEGA_SHAPE_RTL = 2 };
-enum { VEGA_SHAPER_DEFAULT, VEGA_SHAPER_ARABIC, VEGA_SHAPER_HEBREW, VEGA_SHAPER_HANGUL };
+enum { VEGA_SHAPER_DEFAULT, VEGA_SHAPER_ARABIC, VEGA_SHAPER_HEBREW, VEGA_SHAPER_HANGUL, VEGA_SHAPER_INDIC };
/* 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
@@ -47,12 +55,24 @@ enum { VEGA_SHAPER_DEFAULT, VEGA_SHAPER_ARABIC, VEGA_SHAPER_HEBREW, VEGA_SHAPER_
* 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_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_LJMO, VEGA_MASK_VJMO,
- VEGA_MASK_TJMO };
-/* Step flags. */
+/* The masks a plan names, for the features set glyph by glyph: those of
+ * every shaper, then each shaper's own. */
+enum { VEGA_MASK_RTLM, VEGA_MASK_FRAC, VEGA_MASK_NUMR, VEGA_MASK_DNOM, VEGA_MASK_SHAPER };
+enum { VEGA_MASK_ISOL = VEGA_MASK_SHAPER, VEGA_MASK_FINA, VEGA_MASK_FIN2, VEGA_MASK_FIN3, VEGA_MASK_MEDI,
+ VEGA_MASK_MED2, VEGA_MASK_INIT }; /* Arabic */
+enum { VEGA_MASK_LJMO = VEGA_MASK_SHAPER, VEGA_MASK_VJMO, VEGA_MASK_TJMO }; /* Hangul */
+enum { VEGA_MASK_RPHF = VEGA_MASK_SHAPER, VEGA_MASK_PREF, VEGA_MASK_BLWF, VEGA_MASK_ABVF, VEGA_MASK_HALF,
+ VEGA_MASK_PSTF, VEGA_MASK_INDIC_INIT }; /* Indic */
+/* 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). */
+enum { VEGA_INDIC_OLD_SPEC = 1 };
+/* 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 };
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
diff --git a/text/syllabic-data.c b/text/syllabic-data.c
new file mode 100644
index 0000000..3b3e6ff
--- /dev/null
+++ b/text/syllabic-data.c
@@ -0,0 +1,463 @@
+/* Generated by text/syllabic.py from HarfBuzz 10.2.0's sources; do not edit. */
+#include "syllabic.h"
+
+/* Each character's Indic category and position (hb-ot-shaper-indic-table.cc, Unicode 16):
+ * category | position << 8. */
+static const uint16_t indic_cells[1769] = {
+ 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x040A, 0x0E00, 0x0E00, 0x040A, 0x040A, 0x040A, 0x040A,
+ 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00,
+ 0x040A, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00,
+ 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0D39, 0x0D39, 0x0E00, 0x0E00, 0x0E00, 0x0E00,
+ 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00,
+ 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00,
+ 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x040A, 0x0D08, 0x0D08, 0x0D08, 0x0D08,
+ 0x0402, 0x0402, 0x0402, 0x0402, 0x0402, 0x0402, 0x0402, 0x0402, 0x0402, 0x0402, 0x0402, 0x0402,
+ 0x0402, 0x0402, 0x0402, 0x0402, 0x0402, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401,
+ 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401,
+ 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x040F, 0x0401, 0x0401, 0x0401,
+ 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0907, 0x0907, 0x0E03, 0x0D11, 0x0907, 0x0207,
+ 0x0907, 0x0907, 0x0907, 0x0907, 0x0907, 0x0907, 0x0907, 0x0907, 0x0907, 0x0907, 0x0907, 0x0907,
+ 0x0907, 0x0804, 0x0207, 0x0907, 0x0E00, 0x0D09, 0x0D09, 0x0D08, 0x0D08, 0x0907, 0x0907, 0x0907,
+ 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0402, 0x0402, 0x0907, 0x0907,
+ 0x0E00, 0x0E00, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A,
+ 0x0E00, 0x0E00, 0x0402, 0x0402, 0x0402, 0x0402, 0x0402, 0x0402, 0x0401, 0x0401, 0x0401, 0x0401,
+ 0x0401, 0x0401, 0x0401, 0x0401, 0x040A, 0x0D08, 0x0D08, 0x0D08, 0x0E00, 0x0402, 0x0402, 0x0402,
+ 0x0402, 0x0402, 0x0402, 0x0402, 0x0402, 0x0E00, 0x0E00, 0x0402, 0x0402, 0x0E00, 0x0E00, 0x0402,
+ 0x0402, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401,
+ 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0E00, 0x0401, 0x0401,
+ 0x0401, 0x0401, 0x0401, 0x0401, 0x040F, 0x0E00, 0x0401, 0x0E00, 0x0E00, 0x0E00, 0x0401, 0x0401,
+ 0x0401, 0x0401, 0x0E00, 0x0E00, 0x0E03, 0x0D11, 0x0C07, 0x0207, 0x0C07, 0x0907, 0x0907, 0x0907,
+ 0x0907, 0x0E00, 0x0E00, 0x0207, 0x0207, 0x0E00, 0x0E00, 0x0C07, 0x0C07, 0x0804, 0x0401, 0x0E00,
+ 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0C07, 0x0E00, 0x0E00, 0x0E00, 0x0E00,
+ 0x0401, 0x0401, 0x0E00, 0x0401, 0x0402, 0x0402, 0x0907, 0x0907, 0x0E00, 0x0E00, 0x040A, 0x040A,
+ 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040F, 0x0401, 0x0E00, 0x0E00,
+ 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x040A, 0x0E00, 0x0D08, 0x0E00,
+ 0x0E00, 0x0D08, 0x0D08, 0x0D08, 0x0E00, 0x0402, 0x0402, 0x0402, 0x0402, 0x0402, 0x0402, 0x0E00,
+ 0x0E00, 0x0E00, 0x0E00, 0x0402, 0x0402, 0x0E00, 0x0E00, 0x0402, 0x0402, 0x0401, 0x0401, 0x0401,
+ 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401,
+ 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0E00, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401,
+ 0x040F, 0x0E00, 0x0401, 0x0401, 0x0E00, 0x0401, 0x0401, 0x0E00, 0x0401, 0x0401, 0x0E00, 0x0E00,
+ 0x0E03, 0x0E00, 0x0C07, 0x0207, 0x0C0D, 0x0C07, 0x0C07, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0C07,
+ 0x0C07, 0x0E00, 0x0E00, 0x0C07, 0x0C07, 0x0804, 0x0E00, 0x0E00, 0x0E00, 0x0807, 0x0E00, 0x0E00,
+ 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0401, 0x0401, 0x0401, 0x0401, 0x0E00, 0x0401, 0x0E00,
+ 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A,
+ 0x040A, 0x040A, 0x040A, 0x040A, 0x0D08, 0x0D08, 0x0401, 0x0401, 0x0E00, 0x0410, 0x0E00, 0x0E00,
+ 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0D08, 0x0D08, 0x0D08,
+ 0x0E00, 0x0402, 0x0402, 0x0402, 0x0402, 0x0402, 0x0402, 0x0402, 0x0402, 0x0402, 0x0E00, 0x0402,
+ 0x0402, 0x0402, 0x0E00, 0x0402, 0x0402, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401,
+ 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401,
+ 0x0401, 0x0E00, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x040F, 0x0E00, 0x0401, 0x0401,
+ 0x0E00, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0E00, 0x0E00, 0x0E03, 0x0D11, 0x0C07, 0x0207,
+ 0x0C07, 0x0C07, 0x0C07, 0x0C07, 0x0C07, 0x0907, 0x0E00, 0x0907, 0x0907, 0x0C07, 0x0E00, 0x0C07,
+ 0x0C07, 0x0804, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00,
+ 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0402, 0x0402, 0x0C07, 0x0C07,
+ 0x0E00, 0x0E00, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A,
+ 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0401, 0x0D09, 0x0E03,
+ 0x0D09, 0x0E03, 0x0E03, 0x0E03, 0x0E00, 0x0708, 0x0D08, 0x0D08, 0x0E00, 0x0402, 0x0402, 0x0402,
+ 0x0402, 0x0402, 0x0402, 0x0402, 0x0402, 0x0E00, 0x0E00, 0x0402, 0x0402, 0x0E00, 0x0E00, 0x0402,
+ 0x0402, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401,
+ 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0E00, 0x0401, 0x0401,
+ 0x0401, 0x0401, 0x0401, 0x0401, 0x040F, 0x0E00, 0x0401, 0x0401, 0x0E00, 0x0401, 0x0401, 0x0401,
+ 0x0401, 0x0401, 0x0E00, 0x0E00, 0x0E03, 0x0D11, 0x0C07, 0x0507, 0x0C07, 0x0907, 0x0907, 0x0907,
+ 0x0907, 0x0E00, 0x0E00, 0x0207, 0x0507, 0x0E00, 0x0E00, 0x0C07, 0x0C07, 0x0804, 0x0E00, 0x0E00,
+ 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E03, 0x0507, 0x0C07, 0x0E00, 0x0E00, 0x0E00, 0x0E00,
+ 0x0401, 0x0401, 0x0E00, 0x0401, 0x0402, 0x0402, 0x0907, 0x0907, 0x0E00, 0x0E00, 0x040A, 0x040A,
+ 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x0E00, 0x0401, 0x0E00, 0x0E00,
+ 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00,
+ 0x0E00, 0x0E00, 0x0D08, 0x0E00, 0x0E00, 0x0402, 0x0402, 0x0402, 0x0402, 0x0402, 0x0402, 0x0E00,
+ 0x0E00, 0x0E00, 0x0402, 0x0402, 0x0402, 0x0E00, 0x0402, 0x0402, 0x0402, 0x0401, 0x0E00, 0x0E00,
+ 0x0E00, 0x0401, 0x0401, 0x0E00, 0x0401, 0x0E00, 0x0401, 0x0401, 0x0E00, 0x0E00, 0x0E00, 0x0401,
+ 0x0401, 0x0E00, 0x0E00, 0x0E00, 0x0401, 0x0401, 0x0401, 0x0E00, 0x0E00, 0x0E00, 0x0401, 0x0401,
+ 0x040F, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0E00, 0x0E00,
+ 0x0E00, 0x0E00, 0x0C07, 0x0C07, 0x0907, 0x0C07, 0x0C07, 0x0E00, 0x0E00, 0x0E00, 0x0207, 0x0207,
+ 0x0207, 0x0E00, 0x0C07, 0x0C07, 0x0C07, 0x0604, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00,
+ 0x0E00, 0x0E00, 0x0E00, 0x0C07, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00,
+ 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A,
+ 0x040A, 0x040A, 0x040A, 0x040A, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00,
+ 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0D08, 0x0D08, 0x0D08, 0x0D08,
+ 0x0D08, 0x0402, 0x0402, 0x0402, 0x0402, 0x0402, 0x0402, 0x0402, 0x0402, 0x0E00, 0x0402, 0x0402,
+ 0x0402, 0x0E00, 0x0402, 0x0402, 0x0402, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401,
+ 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401,
+ 0x0401, 0x0E00, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x040F, 0x0401, 0x0401, 0x0401,
+ 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0E00, 0x0E00, 0x0E03, 0x0D11, 0x0707, 0x0707,
+ 0x0707, 0x0707, 0x0707, 0x0907, 0x0907, 0x0E00, 0x0707, 0x0707, 0x0707, 0x0E00, 0x0707, 0x0707,
+ 0x0707, 0x0604, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0707, 0x0707, 0x0E00,
+ 0x0401, 0x0401, 0x0401, 0x0E00, 0x0E00, 0x0401, 0x0E00, 0x0E00, 0x0402, 0x0402, 0x0707, 0x0707,
+ 0x0E00, 0x0E00, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A,
+ 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00,
+ 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x040A, 0x0D08, 0x0D08, 0x0D08, 0x0E00, 0x0402, 0x0402, 0x0402,
+ 0x0402, 0x0402, 0x0402, 0x0402, 0x0402, 0x0E00, 0x0402, 0x0402, 0x0402, 0x0E00, 0x0402, 0x0402,
+ 0x0402, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401,
+ 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0E00, 0x0401, 0x0401,
+ 0x0401, 0x0401, 0x0401, 0x0401, 0x040F, 0x0401, 0x0401, 0x0401, 0x0E00, 0x0401, 0x0401, 0x0401,
+ 0x0401, 0x0401, 0x0E00, 0x0E00, 0x0E03, 0x0D11, 0x0707, 0x0707, 0x0707, 0x0707, 0x0707, 0x0907,
+ 0x0907, 0x0E00, 0x0707, 0x0907, 0x0907, 0x0E00, 0x0907, 0x0907, 0x0707, 0x0604, 0x0E00, 0x0E00,
+ 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0907, 0x0907, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00,
+ 0x0E00, 0x0401, 0x0401, 0x0E00, 0x0402, 0x0402, 0x0707, 0x0707, 0x0E00, 0x0E00, 0x040A, 0x040A,
+ 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x0E00, 0x0412, 0x0412, 0x0D08,
+ 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00,
+ 0x0D08, 0x0D08, 0x0D08, 0x0D08, 0x040A, 0x0402, 0x0402, 0x0402, 0x0402, 0x0402, 0x0402, 0x0402,
+ 0x0402, 0x0E00, 0x0402, 0x0402, 0x0402, 0x0E00, 0x0402, 0x0402, 0x0402, 0x0401, 0x0401, 0x0401,
+ 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401,
+ 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401,
+ 0x040F, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0907,
+ 0x0907, 0x0D11, 0x0C07, 0x0C07, 0x0C07, 0x0C07, 0x0C07, 0x0C07, 0x0C07, 0x0E00, 0x0207, 0x0207,
+ 0x0207, 0x0E00, 0x0C07, 0x0C07, 0x0C07, 0x0604, 0x0E0E, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00,
+ 0x0401, 0x0401, 0x0401, 0x0C07, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0402,
+ 0x0402, 0x0402, 0x0C07, 0x0C07, 0x0E00, 0x0E00, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A,
+ 0x040A, 0x040A, 0x040A, 0x040A, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00,
+ 0x0E00, 0x0E00, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401,
+ 0x040F, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401,
+ 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x040F,
+ 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0402, 0x0402, 0x0402, 0x0402, 0x0402, 0x0402, 0x0402,
+ 0x0402, 0x0402, 0x0402, 0x0B17, 0x0B17, 0x0614, 0x0614, 0x0815, 0x0815, 0x0316, 0x0D09, 0x0614,
+ 0x0614, 0x0614, 0x0D09, 0x0E03, 0x0D08, 0x0E04, 0x0E20, 0x0E26, 0x0E24, 0x0E25, 0x0E23, 0x0401,
+ 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A,
+ 0x0E00, 0x0E00, 0x0401, 0x0E00, 0x0401, 0x0401, 0x0402, 0x0402, 0x0402, 0x0402, 0x0B17, 0x0B17,
+ 0x0815, 0x0815, 0x040F, 0x0401, 0x0401, 0x0401, 0x0E26, 0x0E26, 0x0E29, 0x0401, 0x0B17, 0x0E27,
+ 0x0E27, 0x0401, 0x0401, 0x0B17, 0x0B17, 0x0E27, 0x0E27, 0x0E27, 0x0E27, 0x0E27, 0x0401, 0x0401,
+ 0x0401, 0x0614, 0x0614, 0x0614, 0x0614, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401,
+ 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0E25, 0x0B17, 0x0316, 0x0614, 0x0614, 0x0D08,
+ 0x0D08, 0x0D08, 0x0D08, 0x0D08, 0x0D08, 0x0D08, 0x0401, 0x0D08, 0x040A, 0x040A, 0x040A, 0x040A,
+ 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x0D08, 0x0D08, 0x0D08, 0x0614, 0x0E00, 0x0E00,
+ 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401,
+ 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401,
+ 0x0401, 0x0401, 0x040F, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0402,
+ 0x0402, 0x0402, 0x0402, 0x0402, 0x0402, 0x0402, 0x0402, 0x0402, 0x0402, 0x0402, 0x0402, 0x0402,
+ 0x0402, 0x0402, 0x0402, 0x0402, 0x0E00, 0x0E00, 0x0B17, 0x0614, 0x0614, 0x0614, 0x0614, 0x0815,
+ 0x0815, 0x0815, 0x0614, 0x0B17, 0x0B17, 0x0316, 0x0316, 0x0316, 0x0B17, 0x0B17, 0x0E1A, 0x0E1B,
+ 0x0E1B, 0x0E19, 0x0E19, 0x0E1A, 0x0E19, 0x0E1A, 0x0E1A, 0x0E1A, 0x0E1A, 0x0E1A, 0x0E04, 0x0E1B,
+ 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x040A, 0x0E00, 0x0E00, 0x0D11, 0x0E1B, 0x0E00, 0x0E00,
+ 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x0E00, 0x0E00,
+ 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0D09, 0x0D09, 0x0D09, 0x0E00, 0x0D09, 0x0D09, 0x0D09, 0x0D09,
+ 0x0D09, 0x0D09, 0x0D09, 0x0D09, 0x0D09, 0x0D09, 0x0D09, 0x0D09, 0x0D09, 0x0D09, 0x0D09, 0x0D09,
+ 0x0D09, 0x0D09, 0x0D09, 0x0D09, 0x0D09, 0x0D11, 0x0D11, 0x0D11, 0x0D11, 0x0D09, 0x0D11, 0x0D11,
+ 0x0D11, 0x0D11, 0x0401, 0x0401, 0x0D09, 0x0401, 0x0401, 0x0D09, 0x0D09, 0x0D09, 0x040A, 0x0E00,
+ 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E05, 0x0E06, 0x0E00, 0x0E00,
+ 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00,
+ 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x040A, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00,
+ 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0D39, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00,
+ 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0D39, 0x0D39, 0x0D39, 0x0E00, 0x0E00, 0x0E00,
+ 0x040B, 0x0E00, 0x0E00, 0x0E00, 0x040A, 0x040A, 0x040A, 0x040A, 0x0E00, 0x0D09, 0x0D09, 0x0D09,
+ 0x0D09, 0x0D09, 0x0D09, 0x0D09, 0x0D09, 0x0D09, 0x0D09, 0x0D09, 0x0D09, 0x0D09, 0x0D09, 0x0D09,
+ 0x0D09, 0x0D09, 0x0D09, 0x0D11, 0x0D11, 0x0D11, 0x0D11, 0x0D11, 0x0D11, 0x0E00, 0x0E00, 0x0E00,
+ 0x0E00, 0x0E00, 0x0E00, 0x0402, 0x0907, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0614, 0x0E00,
+ 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x040A, 0x040A, 0x040A,
+ 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401,
+ 0x0E00, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0401,
+ 0x0401, 0x0401, 0x0401, 0x0401, 0x0401, 0x0E00, 0x0401, 0x0401, 0x0401, 0x040A, 0x040A, 0x040A,
+ 0x0E00, 0x0E00, 0x0E00, 0x0401, 0x0E27, 0x0E03, 0x0E03, 0x0401, 0x0401, 0x0E28, 0x0E28, 0x0E28,
+ 0x0E28, 0x0E28, 0x0E28, 0x0E28, 0x0E28, 0x0E28, 0x0E28, 0x0E28, 0x0E28, 0x0E28, 0x0E28, 0x0E28,
+ 0x0E28, 0x0E00, 0x0D08, 0x0D08, 0x0D08, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00, 0x0E00,
+ 0x0E03, 0x0E03, 0x0E00, 0x0E00, 0x0E00, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A,
+ 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A, 0x040A,
+ 0x040A, 0x0E00, 0x0E00, 0x0E00, 0x0E00,
+};
+static const struct { uint32_t first, last, at; } indic_runs[17] = {
+ {0x0028, 0x003F, 0},
+ {0x00A0, 0x00D7, 24},
+ {0x0900, 0x0D7F, 80},
+ {0x1000, 0x109F, 1232},
+ {0x1780, 0x17EF, 1392},
+ {0x1CD0, 0x1CFF, 1504},
+ {0x2008, 0x2027, 1552},
+ {0x2070, 0x2087, 1584},
+ {0x25CC, 0x25CC, 1608},
+ {0x25F8, 0x25FF, 1609},
+ {0xA8E0, 0xA8FF, 1617},
+ {0xA9E0, 0xA9FF, 1649},
+ {0xAA60, 0xAA7F, 1681},
+ {0xFE00, 0xFE0F, 1713},
+ {0x11300, 0x11307, 1729},
+ {0x11338, 0x1133F, 1737},
+ {0x116D0, 0x116E7, 1745},
+};
+
+uint16_t vega_indic_categories(uint32_t u) {
+ for (unsigned i = 0; i < 17 && u >= indic_runs[i].first; ++i)
+ if (u <= indic_runs[i].last) return indic_cells[indic_runs[i].at + u - indic_runs[i].first];
+ return 0x0E00;
+}
+
+/* The syllable machines: a DFA over classes of symbol; a state's accept is
+ * the syllable type it ends, plus one. */
+/* indic (hb-ot-shaper-indic-machine.rl): 137 states, 20 classes of symbol; types indic_consonant_syllable, indic_vowel_syllable, indic_standalone_cluster, indic_symbol_cluster, indic_broken_cluster, indic_non_indic_cluster. */
+static const uint8_t indic_class[64] = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 0, 0, 0, 0, 0, 0,
+};
+static const uint8_t indic_next[137][20] = {
+ {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 11, 12, 8, 13, 14, 15, 16, 17, 18},
+ {137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137},
+ {137, 137, 137, 19, 20, 21, 22, 23, 24, 25, 137, 137, 26, 23, 137, 137, 27, 137, 137, 24},
+ {137, 137, 137, 28, 29, 30, 31, 32, 33, 34, 137, 137, 35, 32, 137, 137, 36, 137, 137, 33},
+ {137, 137, 137, 37, 5, 38, 38, 8, 9, 10, 137, 137, 137, 8, 137, 137, 15, 137, 137, 9},
+ {137, 39, 137, 137, 137, 40, 41, 137, 42, 10, 137, 137, 137, 137, 137, 39, 137, 137, 137, 42},
+ {137, 137, 137, 137, 43, 44, 44, 8, 9, 137, 137, 137, 12, 8, 137, 137, 137, 137, 137, 9},
+ {137, 137, 137, 137, 43, 44, 44, 8, 9, 137, 137, 137, 137, 8, 137, 137, 137, 137, 137, 9},
+ {137, 137, 137, 45, 46, 47, 47, 8, 9, 10, 137, 137, 137, 8, 137, 137, 137, 137, 137, 9},
+ {137, 137, 137, 137, 137, 10, 137, 137, 48, 10, 137, 137, 137, 8, 137, 137, 137, 137, 137, 48},
+ {137, 137, 137, 137, 137, 137, 137, 137, 137, 10, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137},
+ {137, 137, 137, 49, 50, 51, 52, 53, 54, 55, 137, 137, 56, 53, 137, 137, 57, 137, 137, 54},
+ {137, 137, 137, 4, 5, 38, 38, 8, 9, 10, 137, 137, 137, 8, 137, 137, 15, 137, 137, 9},
+ {137, 2, 3, 4, 5, 58, 38, 8, 9, 10, 11, 11, 12, 8, 137, 2, 15, 137, 137, 9},
+ {137, 137, 137, 19, 59, 21, 22, 23, 24, 25, 137, 137, 26, 23, 137, 137, 27, 137, 137, 24},
+ {137, 137, 137, 137, 60, 61, 61, 8, 9, 10, 137, 137, 137, 8, 137, 137, 137, 137, 137, 9},
+ {137, 137, 137, 62, 137, 63, 63, 137, 64, 65, 137, 137, 137, 137, 137, 137, 137, 137, 137, 64},
+ {137, 2, 137, 137, 137, 137, 137, 137, 137, 137, 11, 137, 137, 137, 137, 2, 137, 137, 137, 137},
+ {137, 137, 137, 137, 137, 10, 137, 137, 48, 10, 137, 137, 137, 8, 137, 137, 137, 137, 137, 48},
+ {137, 137, 137, 66, 20, 67, 67, 23, 24, 25, 137, 137, 137, 23, 137, 137, 27, 137, 137, 24},
+ {137, 2, 137, 137, 137, 68, 69, 137, 70, 25, 137, 137, 137, 137, 137, 2, 137, 137, 137, 70},
+ {137, 137, 137, 137, 71, 72, 72, 23, 24, 137, 137, 137, 26, 23, 137, 137, 137, 137, 137, 24},
+ {137, 137, 137, 19, 20, 21, 67, 23, 24, 25, 137, 137, 26, 23, 137, 137, 27, 137, 137, 24},
+ {137, 137, 137, 73, 74, 75, 75, 23, 24, 25, 137, 137, 137, 23, 137, 137, 137, 137, 137, 24},
+ {137, 137, 137, 137, 137, 25, 137, 137, 76, 25, 137, 137, 137, 23, 137, 137, 137, 137, 137, 76},
+ {137, 137, 137, 137, 137, 137, 137, 137, 137, 25, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137},
+ {137, 137, 137, 19, 20, 67, 67, 23, 24, 25, 137, 137, 137, 23, 137, 137, 27, 137, 137, 24},
+ {137, 137, 137, 137, 77, 78, 78, 23, 24, 25, 137, 137, 137, 23, 137, 137, 137, 137, 137, 24},
+ {137, 137, 137, 79, 29, 80, 31, 32, 33, 34, 137, 137, 137, 32, 137, 137, 36, 137, 137, 33},
+ {137, 81, 137, 137, 137, 82, 83, 137, 84, 34, 137, 137, 137, 137, 137, 81, 137, 137, 137, 84},
+ {137, 137, 137, 137, 85, 86, 86, 32, 33, 137, 137, 137, 35, 32, 137, 137, 137, 137, 137, 33},
+ {137, 137, 137, 137, 85, 86, 86, 32, 33, 137, 137, 137, 137, 32, 137, 137, 137, 137, 137, 33},
+ {137, 137, 137, 87, 88, 89, 89, 32, 33, 34, 137, 137, 137, 32, 137, 137, 137, 137, 137, 33},
+ {137, 137, 137, 137, 137, 34, 137, 137, 90, 34, 137, 137, 137, 32, 137, 137, 137, 137, 137, 90},
+ {137, 137, 137, 137, 137, 137, 137, 137, 137, 34, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137},
+ {137, 137, 137, 28, 29, 80, 31, 32, 33, 34, 137, 137, 137, 32, 137, 137, 36, 137, 137, 33},
+ {137, 137, 137, 137, 91, 92, 92, 32, 33, 34, 137, 137, 137, 32, 137, 137, 137, 137, 137, 33},
+ {137, 137, 137, 137, 5, 38, 38, 8, 9, 10, 137, 137, 137, 8, 137, 137, 15, 137, 137, 9},
+ {137, 137, 137, 137, 43, 44, 44, 8, 9, 137, 137, 137, 137, 8, 137, 137, 137, 137, 137, 9},
+ {137, 137, 137, 4, 5, 58, 93, 8, 9, 10, 137, 137, 12, 8, 137, 137, 15, 137, 137, 9},
+ {137, 137, 137, 137, 137, 94, 94, 137, 42, 10, 137, 137, 137, 137, 137, 137, 137, 137, 137, 42},
+ {137, 39, 137, 95, 137, 94, 94, 137, 42, 10, 137, 137, 137, 137, 137, 39, 137, 137, 137, 42},
+ {137, 137, 137, 137, 137, 10, 137, 137, 48, 10, 137, 137, 137, 137, 137, 137, 137, 137, 137, 48},
+ {137, 39, 137, 137, 137, 94, 41, 137, 42, 10, 137, 137, 137, 137, 137, 39, 137, 137, 137, 42},
+ {137, 137, 137, 137, 137, 44, 44, 8, 96, 137, 137, 137, 137, 8, 137, 137, 137, 137, 137, 96},
+ {137, 137, 137, 137, 46, 47, 47, 8, 9, 10, 137, 137, 137, 8, 137, 137, 137, 137, 137, 9},
+ {137, 137, 137, 137, 137, 47, 47, 8, 9, 10, 137, 137, 137, 8, 137, 137, 137, 137, 137, 9},
+ {137, 137, 137, 137, 137, 44, 44, 8, 9, 137, 137, 137, 137, 8, 137, 137, 137, 137, 137, 9},
+ {137, 137, 137, 137, 137, 10, 137, 137, 137, 10, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137},
+ {137, 137, 137, 97, 50, 52, 52, 53, 54, 55, 137, 137, 137, 53, 137, 137, 57, 137, 137, 54},
+ {137, 98, 137, 137, 137, 99, 100, 137, 101, 55, 137, 137, 137, 137, 137, 98, 137, 137, 137, 101},
+ {137, 137, 137, 137, 102, 103, 103, 53, 54, 137, 137, 137, 56, 53, 137, 137, 137, 137, 137, 54},
+ {137, 137, 137, 137, 102, 103, 103, 53, 54, 137, 137, 137, 137, 53, 137, 137, 137, 137, 137, 54},
+ {137, 137, 137, 104, 105, 106, 106, 53, 54, 55, 137, 137, 137, 53, 137, 137, 137, 137, 137, 54},
+ {137, 137, 137, 137, 137, 55, 137, 137, 107, 55, 137, 137, 137, 53, 137, 137, 137, 137, 137, 107},
+ {137, 137, 137, 137, 137, 137, 137, 137, 137, 55, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137},
+ {137, 137, 137, 49, 50, 52, 52, 53, 54, 55, 137, 137, 137, 53, 137, 137, 57, 137, 137, 54},
+ {137, 137, 137, 137, 108, 109, 109, 53, 54, 55, 137, 137, 137, 53, 137, 137, 137, 137, 137, 54},
+ {137, 137, 137, 137, 43, 44, 44, 8, 9, 137, 137, 137, 12, 8, 137, 137, 137, 137, 137, 9},
+ {137, 2, 3, 4, 5, 110, 111, 8, 112, 25, 137, 11, 12, 8, 137, 2, 15, 137, 137, 112},
+ {137, 137, 137, 137, 137, 40, 113, 137, 42, 10, 137, 137, 137, 137, 137, 137, 137, 137, 137, 42},
+ {137, 137, 137, 137, 114, 44, 44, 8, 9, 137, 137, 137, 137, 8, 137, 137, 137, 137, 137, 9},
+ {137, 137, 137, 137, 137, 63, 63, 137, 64, 65, 137, 137, 137, 137, 137, 137, 137, 137, 137, 64},
+ {137, 137, 137, 137, 137, 137, 137, 137, 64, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 64},
+ {137, 137, 137, 137, 137, 65, 137, 137, 115, 65, 137, 137, 137, 137, 137, 137, 137, 137, 137, 115},
+ {137, 137, 137, 137, 137, 137, 137, 137, 137, 65, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137},
+ {137, 137, 137, 137, 20, 67, 67, 23, 24, 25, 137, 137, 137, 23, 137, 137, 27, 137, 137, 24},
+ {137, 137, 137, 137, 71, 72, 72, 23, 24, 137, 137, 137, 137, 23, 137, 137, 137, 137, 137, 24},
+ {137, 137, 137, 137, 137, 116, 116, 137, 70, 25, 137, 137, 137, 137, 137, 137, 137, 137, 137, 70},
+ {137, 2, 137, 117, 137, 116, 116, 137, 70, 25, 137, 137, 137, 137, 137, 2, 137, 137, 137, 70},
+ {137, 137, 137, 137, 137, 25, 137, 137, 76, 25, 137, 137, 137, 137, 137, 137, 137, 137, 137, 76},
+ {137, 2, 137, 137, 137, 116, 69, 137, 70, 25, 137, 137, 137, 137, 137, 2, 137, 137, 137, 70},
+ {137, 137, 137, 137, 137, 72, 72, 23, 118, 137, 137, 137, 137, 23, 137, 137, 137, 137, 137, 118},
+ {137, 137, 137, 137, 74, 75, 75, 23, 24, 25, 137, 137, 137, 23, 137, 137, 137, 137, 137, 24},
+ {137, 137, 137, 137, 137, 75, 75, 23, 24, 25, 137, 137, 137, 23, 137, 137, 137, 137, 137, 24},
+ {137, 137, 137, 137, 137, 72, 72, 23, 24, 137, 137, 137, 137, 23, 137, 137, 137, 137, 137, 24},
+ {137, 137, 137, 137, 137, 25, 137, 137, 137, 25, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137},
+ {137, 137, 137, 137, 137, 68, 119, 137, 70, 25, 137, 137, 137, 137, 137, 137, 137, 137, 137, 70},
+ {137, 137, 137, 137, 120, 72, 72, 23, 24, 137, 137, 137, 137, 23, 137, 137, 137, 137, 137, 24},
+ {137, 137, 137, 137, 29, 80, 31, 32, 33, 34, 137, 137, 137, 32, 137, 137, 36, 137, 137, 33},
+ {137, 137, 137, 137, 85, 86, 86, 32, 33, 137, 137, 137, 137, 32, 137, 137, 137, 137, 137, 33},
+ {137, 137, 137, 121, 29, 122, 123, 32, 33, 34, 137, 137, 124, 32, 137, 137, 36, 137, 137, 33},
+ {137, 137, 137, 137, 137, 125, 125, 137, 84, 34, 137, 137, 137, 137, 137, 137, 137, 137, 137, 84},
+ {137, 81, 137, 126, 137, 125, 125, 137, 84, 34, 137, 137, 137, 137, 137, 81, 137, 137, 137, 84},
+ {137, 137, 137, 137, 137, 34, 137, 137, 90, 34, 137, 137, 137, 137, 137, 137, 137, 137, 137, 90},
+ {137, 81, 137, 137, 137, 125, 83, 137, 84, 34, 137, 137, 137, 137, 137, 81, 137, 137, 137, 84},
+ {137, 137, 137, 137, 137, 86, 86, 32, 127, 137, 137, 137, 137, 32, 137, 137, 137, 137, 137, 127},
+ {137, 137, 137, 137, 88, 89, 89, 32, 33, 34, 137, 137, 137, 32, 137, 137, 137, 137, 137, 33},
+ {137, 137, 137, 137, 137, 89, 89, 32, 33, 34, 137, 137, 137, 32, 137, 137, 137, 137, 137, 33},
+ {137, 137, 137, 137, 137, 86, 86, 32, 33, 137, 137, 137, 137, 32, 137, 137, 137, 137, 137, 33},
+ {137, 137, 137, 137, 137, 34, 137, 137, 137, 34, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137},
+ {137, 137, 137, 137, 137, 82, 128, 137, 84, 34, 137, 137, 137, 137, 137, 137, 137, 137, 137, 84},
+ {137, 137, 137, 137, 129, 86, 86, 32, 33, 137, 137, 137, 137, 32, 137, 137, 137, 137, 137, 33},
+ {137, 137, 137, 4, 5, 58, 38, 8, 9, 10, 137, 137, 12, 8, 137, 137, 15, 137, 137, 9},
+ {137, 137, 137, 137, 137, 137, 137, 137, 42, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 42},
+ {137, 39, 137, 137, 137, 94, 94, 137, 42, 10, 137, 137, 137, 137, 137, 39, 137, 137, 137, 42},
+ {137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 8, 137, 137, 137, 137, 137, 137},
+ {137, 137, 137, 137, 50, 52, 52, 53, 54, 55, 137, 137, 137, 53, 137, 137, 57, 137, 137, 54},
+ {137, 137, 137, 49, 50, 51, 11, 53, 54, 55, 137, 137, 56, 53, 137, 137, 57, 137, 137, 54},
+ {137, 137, 137, 137, 137, 130, 130, 137, 101, 55, 137, 137, 137, 137, 137, 137, 137, 137, 137, 101},
+ {137, 98, 137, 131, 137, 130, 130, 137, 101, 55, 137, 137, 137, 137, 137, 98, 137, 137, 137, 101},
+ {137, 137, 137, 137, 137, 55, 137, 137, 107, 55, 137, 137, 137, 137, 137, 137, 137, 137, 137, 107},
+ {137, 98, 137, 137, 137, 130, 100, 137, 101, 55, 137, 137, 137, 137, 137, 98, 137, 137, 137, 101},
+ {137, 137, 137, 137, 137, 103, 103, 53, 132, 137, 137, 137, 137, 53, 137, 137, 137, 137, 137, 132},
+ {137, 137, 137, 137, 105, 106, 106, 53, 54, 55, 137, 137, 137, 53, 137, 137, 137, 137, 137, 54},
+ {137, 137, 137, 137, 137, 106, 106, 53, 54, 55, 137, 137, 137, 53, 137, 137, 137, 137, 137, 54},
+ {137, 137, 137, 137, 137, 103, 103, 53, 54, 137, 137, 137, 137, 53, 137, 137, 137, 137, 137, 54},
+ {137, 137, 137, 137, 137, 55, 137, 137, 137, 55, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137},
+ {137, 137, 137, 137, 137, 99, 133, 137, 101, 55, 137, 137, 137, 137, 137, 137, 137, 137, 137, 101},
+ {137, 137, 137, 137, 134, 103, 103, 53, 54, 137, 137, 137, 137, 53, 137, 137, 137, 137, 137, 54},
+ {137, 137, 137, 137, 43, 135, 135, 8, 112, 25, 137, 137, 12, 8, 137, 137, 137, 137, 137, 112},
+ {137, 2, 137, 117, 43, 135, 135, 8, 112, 25, 137, 137, 137, 8, 137, 2, 137, 137, 137, 112},
+ {137, 137, 137, 137, 137, 25, 137, 137, 76, 25, 137, 137, 137, 8, 137, 137, 137, 137, 137, 76},
+ {137, 137, 137, 40, 137, 94, 94, 137, 42, 10, 137, 137, 137, 137, 137, 137, 137, 137, 137, 42},
+ {137, 137, 137, 137, 137, 94, 113, 137, 42, 10, 137, 137, 137, 137, 137, 137, 137, 137, 137, 42},
+ {137, 137, 137, 137, 137, 65, 137, 137, 137, 65, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137},
+ {137, 137, 137, 137, 137, 137, 137, 137, 70, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 70},
+ {137, 2, 137, 137, 137, 116, 116, 137, 70, 25, 137, 137, 137, 137, 137, 2, 137, 137, 137, 70},
+ {137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 23, 137, 137, 137, 137, 137, 137},
+ {137, 137, 137, 68, 137, 116, 116, 137, 70, 25, 137, 137, 137, 137, 137, 137, 137, 137, 137, 70},
+ {137, 137, 137, 137, 137, 116, 119, 137, 70, 25, 137, 137, 137, 137, 137, 137, 137, 137, 137, 70},
+ {137, 137, 137, 136, 29, 80, 80, 32, 33, 34, 137, 137, 137, 32, 137, 137, 36, 137, 137, 33},
+ {137, 137, 137, 137, 85, 86, 86, 32, 33, 137, 137, 137, 124, 32, 137, 137, 137, 137, 137, 33},
+ {137, 137, 137, 121, 29, 122, 80, 32, 33, 34, 137, 137, 124, 32, 137, 137, 36, 137, 137, 33},
+ {137, 137, 137, 121, 29, 80, 80, 32, 33, 34, 137, 137, 137, 32, 137, 137, 36, 137, 137, 33},
+ {137, 137, 137, 137, 137, 137, 137, 137, 84, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 84},
+ {137, 81, 137, 137, 137, 125, 125, 137, 84, 34, 137, 137, 137, 137, 137, 81, 137, 137, 137, 84},
+ {137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 32, 137, 137, 137, 137, 137, 137},
+ {137, 137, 137, 82, 137, 125, 125, 137, 84, 34, 137, 137, 137, 137, 137, 137, 137, 137, 137, 84},
+ {137, 137, 137, 137, 137, 125, 128, 137, 84, 34, 137, 137, 137, 137, 137, 137, 137, 137, 137, 84},
+ {137, 137, 137, 137, 137, 137, 137, 137, 101, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 101},
+ {137, 98, 137, 137, 137, 130, 130, 137, 101, 55, 137, 137, 137, 137, 137, 98, 137, 137, 137, 101},
+ {137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 53, 137, 137, 137, 137, 137, 137},
+ {137, 137, 137, 99, 137, 130, 130, 137, 101, 55, 137, 137, 137, 137, 137, 137, 137, 137, 137, 101},
+ {137, 137, 137, 137, 137, 130, 133, 137, 101, 55, 137, 137, 137, 137, 137, 137, 137, 137, 137, 101},
+ {137, 137, 137, 137, 137, 44, 44, 8, 112, 137, 137, 137, 137, 8, 137, 137, 137, 137, 137, 112},
+ {137, 137, 137, 137, 29, 80, 80, 32, 33, 34, 137, 137, 137, 32, 137, 137, 36, 137, 137, 33},
+};
+static const uint8_t indic_accept[137] = {
+ 5, 6, 1, 2, 5, 5, 6, 6, 5, 5, 5, 3, 5, 5, 1, 5, 4, 6, 6, 1, 1, 0, 1, 1, 1, 1, 1, 1, 2, 2, 0, 2,
+ 2, 2, 2, 2, 2, 5, 0, 5, 5, 5, 5, 5, 0, 5, 5, 0, 5, 3, 3, 0, 0, 3, 3, 3, 3, 3, 0, 1, 5, 0, 4, 0,
+ 4, 4, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 2, 0, 2, 2, 2, 2, 2, 0, 2, 2, 0, 2, 2, 0, 5, 0, 5,
+ 0, 3, 3, 3, 3, 3, 3, 0, 3, 3, 0, 3, 3, 0, 1, 1, 1, 5, 5, 4, 0, 1, 0, 1, 1, 2, 0, 2, 2, 0, 2, 0,
+ 2, 2, 0, 3, 0, 3, 3, 0, 2,
+};
+const VegaMachine vega_indic_machine = {indic_class, 20, 137, indic_next[0], sizeof(indic_next[0][0]), indic_accept, 5};
+
+/* khmer (hb-ot-shaper-khmer-machine.rl): 44 states, 12 classes of symbol; types khmer_consonant_syllable, khmer_broken_cluster, khmer_non_khmer_cluster. */
+static const uint8_t khmer_class[64] = {
+ 0, 1, 1, 0, 2, 3, 3, 0, 0, 0, 4, 4, 0, 0, 0, 1, 0, 0, 0, 0, 5, 6, 7, 8, 0, 9, 10, 11, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+static const uint8_t khmer_next[44][12] = {
+ {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12},
+ {44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44},
+ {44, 44, 13, 14, 44, 15, 16, 17, 18, 5, 19, 20},
+ {44, 21, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44},
+ {44, 44, 44, 22, 44, 6, 44, 44, 44, 44, 11, 44},
+ {44, 44, 13, 23, 44, 15, 16, 17, 18, 24, 19, 20},
+ {44, 44, 25, 26, 44, 44, 44, 44, 9, 44, 6, 12},
+ {44, 44, 25, 27, 44, 6, 44, 44, 9, 44, 7, 12},
+ {44, 44, 25, 28, 44, 6, 7, 44, 9, 44, 8, 12},
+ {44, 44, 25, 29, 44, 44, 44, 44, 44, 44, 9, 12},
+ {44, 44, 3, 30, 44, 6, 7, 8, 9, 44, 11, 12},
+ {44, 44, 25, 30, 44, 6, 7, 8, 9, 44, 11, 12},
+ {44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 12},
+ {44, 31, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44},
+ {44, 44, 44, 32, 44, 15, 44, 44, 44, 5, 19, 44},
+ {44, 44, 33, 34, 44, 44, 44, 44, 18, 44, 15, 20},
+ {44, 44, 33, 35, 44, 15, 44, 44, 18, 44, 16, 20},
+ {44, 44, 33, 36, 44, 15, 16, 44, 18, 44, 17, 20},
+ {44, 44, 33, 37, 44, 44, 44, 44, 44, 44, 18, 20},
+ {44, 44, 33, 23, 44, 15, 16, 17, 18, 44, 19, 20},
+ {44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 20},
+ {44, 44, 3, 38, 44, 6, 7, 8, 9, 10, 11, 12},
+ {44, 44, 44, 22, 44, 44, 44, 44, 44, 44, 11, 44},
+ {44, 44, 44, 32, 44, 15, 44, 44, 44, 44, 19, 44},
+ {44, 44, 13, 23, 44, 15, 16, 17, 18, 44, 19, 20},
+ {44, 12, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44},
+ {44, 44, 44, 26, 44, 44, 44, 44, 44, 44, 6, 44},
+ {44, 44, 44, 39, 44, 6, 44, 44, 44, 44, 7, 44},
+ {44, 44, 44, 40, 44, 6, 44, 44, 44, 44, 8, 44},
+ {44, 44, 44, 29, 44, 44, 44, 44, 44, 44, 9, 44},
+ {44, 44, 44, 22, 44, 6, 44, 44, 44, 44, 11, 44},
+ {44, 44, 13, 41, 44, 15, 16, 17, 18, 24, 19, 20},
+ {44, 44, 44, 32, 44, 44, 44, 44, 44, 44, 19, 44},
+ {44, 20, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44},
+ {44, 44, 44, 34, 44, 44, 44, 44, 44, 44, 15, 44},
+ {44, 44, 44, 42, 44, 15, 44, 44, 44, 44, 16, 44},
+ {44, 44, 44, 43, 44, 15, 44, 44, 44, 44, 17, 44},
+ {44, 44, 44, 37, 44, 44, 44, 44, 44, 44, 18, 44},
+ {44, 44, 44, 22, 44, 6, 44, 44, 44, 10, 11, 44},
+ {44, 44, 44, 39, 44, 44, 44, 44, 44, 44, 7, 44},
+ {44, 44, 44, 40, 44, 44, 44, 44, 44, 44, 8, 44},
+ {44, 44, 44, 32, 44, 15, 44, 44, 44, 24, 19, 44},
+ {44, 44, 44, 42, 44, 44, 44, 44, 44, 44, 16, 44},
+ {44, 44, 44, 43, 44, 44, 44, 44, 44, 44, 17, 44},
+};
+static const uint8_t khmer_accept[44] = {
+ 2, 3, 1, 2, 3, 1, 2, 2, 2, 2, 2, 2, 2, 1, 0, 1, 1, 1, 1, 1, 1, 2, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+const VegaMachine vega_khmer_machine = {khmer_class, 12, 44, khmer_next[0], sizeof(khmer_next[0][0]), khmer_accept, 2};
+
+/* myanmar (hb-ot-shaper-myanmar-machine.rl): 57 states, 23 classes of symbol; types myanmar_consonant_syllable, myanmar_broken_cluster, myanmar_non_myanmar_cluster. */
+static const uint8_t myanmar_class[64] = {
+ 0, 1, 1, 2, 3, 4, 4, 0, 5, 6, 7, 7, 0, 0, 0, 8, 0, 0, 9, 0, 10, 11, 12, 13, 0, 0, 0, 0, 0, 0, 0, 0,
+ 14, 0, 0, 15, 16, 17, 18, 19, 20, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 0, 0, 0, 0, 0, 0,
+};
+static const uint8_t myanmar_next[57][23] = {
+ {1, 2, 3, 4, 1, 5, 6, 2, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21},
+ {57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57},
+ {57, 57, 22, 23, 24, 25, 26, 57, 57, 57, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 25},
+ {57, 57, 57, 57, 39, 5, 57, 57, 57, 57, 57, 57, 57, 12, 40, 57, 57, 57, 57, 18, 57, 57, 5},
+ {57, 41, 57, 57, 57, 57, 57, 57, 41, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57},
+ {57, 57, 57, 57, 39, 5, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 18, 57, 57, 5},
+ {57, 57, 3, 57, 39, 5, 6, 57, 57, 57, 57, 57, 57, 12, 57, 57, 57, 57, 57, 18, 57, 57, 5},
+ {57, 57, 22, 23, 24, 25, 26, 57, 57, 57, 27, 28, 29, 30, 42, 32, 33, 34, 35, 36, 37, 38, 25},
+ {57, 2, 57, 57, 57, 57, 57, 2, 2, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57},
+ {57, 57, 3, 57, 39, 5, 6, 57, 57, 57, 9, 10, 57, 12, 57, 57, 57, 57, 57, 18, 57, 57, 5},
+ {57, 57, 3, 57, 39, 5, 6, 57, 57, 57, 57, 10, 57, 12, 57, 57, 57, 57, 57, 18, 57, 57, 5},
+ {57, 57, 3, 57, 39, 5, 6, 57, 57, 57, 9, 10, 11, 12, 57, 57, 57, 57, 57, 18, 43, 57, 5},
+ {57, 57, 3, 57, 39, 5, 6, 57, 57, 57, 44, 57, 57, 12, 45, 46, 57, 57, 57, 18, 57, 45, 5},
+ {57, 57, 3, 57, 39, 5, 6, 57, 57, 57, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 57, 20, 5},
+ {57, 57, 3, 57, 39, 5, 6, 57, 57, 57, 9, 10, 11, 12, 43, 57, 57, 57, 57, 18, 57, 20, 5},
+ {57, 57, 3, 57, 39, 5, 6, 57, 57, 57, 9, 10, 11, 12, 57, 14, 57, 16, 57, 18, 57, 20, 5},
+ {57, 57, 3, 57, 39, 5, 6, 57, 57, 57, 9, 10, 11, 12, 43, 14, 57, 57, 57, 18, 57, 20, 5},
+ {57, 57, 3, 57, 39, 5, 6, 57, 57, 57, 9, 10, 11, 12, 47, 14, 15, 16, 57, 18, 57, 20, 5},
+ {57, 57, 48, 57, 39, 5, 18, 57, 57, 57, 57, 57, 57, 57, 5, 57, 57, 57, 57, 18, 57, 57, 5},
+ {57, 57, 3, 4, 39, 5, 6, 57, 57, 57, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 57, 20, 5},
+ {57, 57, 3, 57, 39, 5, 6, 57, 57, 57, 9, 10, 11, 12, 43, 57, 57, 57, 57, 18, 57, 57, 5},
+ {57, 57, 57, 57, 39, 5, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 18, 57, 57, 5},
+ {57, 57, 57, 57, 24, 25, 57, 57, 57, 57, 57, 57, 57, 30, 49, 57, 57, 57, 57, 36, 57, 57, 25},
+ {57, 2, 57, 57, 57, 57, 57, 57, 2, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57},
+ {57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57},
+ {57, 57, 57, 57, 24, 25, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 36, 57, 57, 25},
+ {57, 57, 22, 57, 24, 25, 26, 57, 57, 57, 57, 57, 57, 30, 57, 57, 57, 57, 57, 36, 57, 57, 25},
+ {57, 57, 22, 57, 24, 25, 26, 57, 57, 57, 27, 28, 57, 30, 57, 57, 57, 57, 57, 36, 57, 57, 25},
+ {57, 57, 22, 57, 24, 25, 26, 57, 57, 57, 57, 28, 57, 30, 57, 57, 57, 57, 57, 36, 57, 57, 25},
+ {57, 57, 22, 57, 24, 25, 26, 57, 57, 57, 27, 28, 29, 30, 57, 57, 57, 57, 57, 36, 50, 57, 25},
+ {57, 57, 22, 57, 24, 25, 26, 57, 57, 57, 51, 57, 57, 30, 52, 53, 57, 57, 57, 36, 57, 52, 25},
+ {57, 57, 22, 57, 24, 25, 26, 57, 57, 57, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 57, 38, 25},
+ {57, 57, 22, 57, 24, 25, 26, 57, 57, 57, 27, 28, 29, 30, 50, 57, 57, 57, 57, 36, 57, 38, 25},
+ {57, 57, 22, 57, 24, 25, 26, 57, 57, 57, 27, 28, 29, 30, 57, 32, 57, 34, 57, 36, 57, 38, 25},
+ {57, 57, 22, 57, 24, 25, 26, 57, 57, 57, 27, 28, 29, 30, 50, 32, 57, 57, 57, 36, 57, 38, 25},
+ {57, 57, 22, 57, 24, 25, 26, 57, 57, 57, 27, 28, 29, 30, 54, 32, 33, 34, 57, 36, 57, 38, 25},
+ {57, 57, 55, 57, 24, 25, 36, 57, 57, 57, 57, 57, 57, 57, 25, 57, 57, 57, 57, 36, 57, 57, 25},
+ {57, 57, 22, 23, 24, 25, 26, 57, 57, 57, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 57, 38, 25},
+ {57, 57, 22, 57, 24, 25, 26, 57, 57, 57, 27, 28, 29, 30, 50, 57, 57, 57, 57, 36, 57, 57, 25},
+ {57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57},
+ {57, 57, 57, 57, 39, 5, 57, 57, 57, 57, 57, 57, 57, 12, 57, 57, 57, 57, 57, 18, 57, 57, 5},
+ {57, 57, 3, 4, 39, 5, 6, 57, 57, 57, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 5},
+ {57, 57, 22, 56, 24, 25, 26, 57, 57, 57, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 57, 38, 25},
+ {57, 57, 3, 57, 39, 5, 6, 57, 57, 57, 9, 10, 11, 12, 57, 57, 57, 57, 57, 18, 57, 57, 5},
+ {57, 57, 3, 57, 39, 5, 6, 57, 57, 57, 44, 57, 57, 12, 57, 57, 57, 57, 57, 18, 57, 57, 5},
+ {57, 57, 3, 57, 39, 5, 6, 57, 57, 57, 44, 57, 57, 12, 45, 57, 57, 57, 57, 18, 57, 57, 5},
+ {57, 57, 3, 57, 39, 5, 6, 57, 57, 57, 44, 57, 57, 12, 45, 57, 57, 57, 57, 18, 57, 45, 5},
+ {57, 57, 3, 57, 39, 5, 6, 57, 57, 57, 9, 10, 11, 12, 57, 14, 15, 16, 57, 18, 57, 20, 5},
+ {57, 57, 57, 57, 39, 5, 57, 57, 57, 57, 57, 57, 57, 57, 5, 57, 57, 57, 57, 18, 57, 57, 5},
+ {57, 57, 57, 57, 24, 25, 57, 57, 57, 57, 57, 57, 57, 30, 57, 57, 57, 57, 57, 36, 57, 57, 25},
+ {57, 57, 22, 57, 24, 25, 26, 57, 57, 57, 27, 28, 29, 30, 57, 57, 57, 57, 57, 36, 57, 57, 25},
+ {57, 57, 22, 57, 24, 25, 26, 57, 57, 57, 51, 57, 57, 30, 57, 57, 57, 57, 57, 36, 57, 57, 25},
+ {57, 57, 22, 57, 24, 25, 26, 57, 57, 57, 51, 57, 57, 30, 52, 57, 57, 57, 57, 36, 57, 57, 25},
+ {57, 57, 22, 57, 24, 25, 26, 57, 57, 57, 51, 57, 57, 30, 52, 57, 57, 57, 57, 36, 57, 52, 25},
+ {57, 57, 22, 57, 24, 25, 26, 57, 57, 57, 27, 28, 29, 30, 57, 32, 33, 34, 57, 36, 57, 38, 25},
+ {57, 57, 57, 57, 24, 25, 57, 57, 57, 57, 57, 57, 57, 57, 25, 57, 57, 57, 57, 36, 57, 57, 25},
+ {57, 2, 3, 4, 39, 5, 6, 2, 2, 57, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 5},
+};
+static const uint8_t myanmar_accept[57] = {
+ 2, 3, 1, 2, 2, 2, 2, 1, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 2,
+};
+const VegaMachine vega_myanmar_machine = {myanmar_class, 23, 57, myanmar_next[0], sizeof(myanmar_next[0][0]), myanmar_accept, 2};
+
diff --git a/text/syllabic.h b/text/syllabic.h
new file mode 100644
index 0000000..8ab8699
--- /dev/null
+++ b/text/syllabic.h
@@ -0,0 +1,40 @@
+#ifndef VEGA_SYLLABIC_H
+#define VEGA_SYLLABIC_H
+#include <stdint.h>
+
+/* What HarfBuzz's syllabic shapers read, as text/syllabic.py makes it
+ * (text/syllabic-data.c): characters' categories, and the machines that
+ * find syllables in runs of them. */
+
+/* Categories (hb-ot-shaper-indic.hh's ot_category_t: Indic's, and Khmer's
+ * and Myanmar's after them) and positions (ot_position_t). */
+enum {
+ VEGA_OT_X = 0, VEGA_OT_C = 1, VEGA_OT_V = 2, VEGA_OT_N = 3, VEGA_OT_H = 4, VEGA_OT_ZWNJ = 5, VEGA_OT_ZWJ = 6,
+ VEGA_OT_M = 7, VEGA_OT_SM = 8, VEGA_OT_A = 9, VEGA_OT_VD = 9, VEGA_OT_PLACEHOLDER = 10, VEGA_OT_DOTTEDCIRCLE = 11,
+ VEGA_OT_RS = 12, VEGA_OT_MPST = 13, VEGA_OT_REPHA = 14, VEGA_OT_RA = 15, VEGA_OT_CM = 16, VEGA_OT_SYMBOL = 17,
+ VEGA_OT_CS = 18, VEGA_OT_SMPST = 57, VEGA_OT_VABV = 20, VEGA_OT_VBLW = 21, VEGA_OT_VPRE = 22, VEGA_OT_VPST = 23, VEGA_OT_ROBATIC = 25,
+ VEGA_OT_XGROUP = 26, VEGA_OT_YGROUP = 27, VEGA_OT_AS = 32, VEGA_OT_MH = 35, VEGA_OT_MR = 36, VEGA_OT_MW = 37,
+ VEGA_OT_MY = 38, VEGA_OT_PT = 39, VEGA_OT_VS = 40, VEGA_OT_ML = 41
+};
+enum {
+ VEGA_POS_START, VEGA_POS_RA_TO_BECOME_REPH, VEGA_POS_PRE_M, VEGA_POS_PRE_C, VEGA_POS_BASE_C, VEGA_POS_AFTER_MAIN,
+ VEGA_POS_ABOVE_C, VEGA_POS_BEFORE_SUB, VEGA_POS_BELOW_C, VEGA_POS_AFTER_SUB, VEGA_POS_BEFORE_POST, VEGA_POS_POST_C,
+ VEGA_POS_AFTER_POST, VEGA_POS_SMVD, VEGA_POS_END
+};
+/* A character's category | position << 8 (hb_indic_get_categories). */
+uint16_t vega_indic_categories(uint32_t u);
+
+/* A syllable machine: a DFA over classes of symbols (categories, those
+ * past 63 taken as 63). From each position the longest run it accepts is
+ * a syllable, ties to the pattern its grammar lists first (Ragel's
+ * scanner), which the accepting state already says. */
+typedef struct {
+ const uint8_t *class_of; /* [64] */
+ unsigned classes, states;
+ const void *next; /* [states][classes]: the next state, states when none */
+ unsigned width; /* bytes of a next state: 1 or 2 */
+ const uint8_t *accept; /* [states]: the syllable type a state ends, plus one; 0 for none */
+ unsigned broken; /* the broken cluster's type, plus one */
+} VegaMachine;
+extern const VegaMachine vega_indic_machine, vega_khmer_machine, vega_myanmar_machine;
+#endif
diff --git a/text/syllabic.py b/text/syllabic.py
new file mode 100644
index 0000000..6eb7c93
--- /dev/null
+++ b/text/syllabic.py
@@ -0,0 +1,456 @@
+#!/usr/bin/env python3
+"""The tables HarfBuzz's syllabic 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
+
+HARFBUZZ_SRC is HarfBuzz 10.2.0's src/ directory; what is read from it:
+
+- Each character's Indic category and position: hb-ot-shaper-indic-
+ table.cc, the table HarfBuzz's gen-indic-table.py made from Unicode 16's
+ IndicSyllabicCategory and IndicPositionalCategory with its own overrides,
+ read cell for cell (Unicode here is 15.1: the table is taken as HarfBuzz
+ has it). Khmer and Myanmar read the same table.
+
+- 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
+ folded into classes that behave alike. --check runs the DFAs beside the
+ machines Ragel generated from the same grammars (hb-ot-shaper-*-
+ machine.hh) on random runs of symbols, and counts where they differ.
+
+The output is deterministic: the same inputs give the same file.
+"""
+import random
+import re
+import sys
+from pathlib import Path
+
+HB = Path(sys.argv[1] if len(sys.argv) > 1 else '.')
+MACHINES = ['indic', 'khmer', 'myanmar']
+ALPHABET = 64 # categories past 63 are folded into 63, which only `any` takes
+
+
+# ---- Grammars: Ragel's regular expressions over symbols
+
+class Parser:
+ """Ragel's expressions as the machines use them: names, `a | b`,
+ `a . b` or `a b`, postfix `? * +`, parentheses."""
+
+ def __init__(self, text, names):
+ self.tokens = re.findall(r'[A-Za-z_][A-Za-z_0-9]*|[()|.?*+]', text)
+ self.at = 0
+ self.names = names
+
+ def peek(self):
+ return self.tokens[self.at] if self.at < len(self.tokens) else None
+
+ def take(self):
+ self.at += 1
+ return self.tokens[self.at - 1]
+
+ def alternation(self):
+ node = self.concatenation()
+ while self.peek() == '|':
+ self.take()
+ node = ('alt', node, self.concatenation())
+ return node
+
+ def concatenation(self):
+ node = self.postfix()
+ while self.peek() not in (None, '|', ')'):
+ if self.peek() == '.':
+ self.take()
+ node = ('cat', node, self.postfix())
+ return node
+
+ def postfix(self):
+ node = self.atom()
+ while self.peek() in ('?', '*', '+'):
+ node = ({'?': 'opt', '*': 'star', '+': 'plus'}[self.take()], node)
+ return node
+
+ def atom(self):
+ token = self.take()
+ if token == '(':
+ node = self.alternation()
+ assert self.take() == ')'
+ return node
+ return self.names[token]
+
+
+def parse(text, names):
+ parser = Parser(text, names)
+ node = parser.alternation()
+ assert parser.peek() is None, (text, parser.peek())
+ return node
+
+
+class Grammar:
+ """A machine's .rl: its symbols (exports), syllable types (the enum), and
+ the scanner's patterns in order, each with the type it finds."""
+
+ def __init__(self, name):
+ text = (HB / f'hb-ot-shaper-{name}-machine.rl').read_text()
+ enum = re.search(r'enum \w+_syllable_type_t \{(.*?)\};', text, re.S).group(1)
+ self.types = [t.strip() for t in enum.split(',') if t.strip()]
+ body = '\n'.join(block for block in re.findall(r'%%\{(.*?)\}%%', text, re.S) if 'main :=' in block)
+ body = re.sub(r'#[^\n]*', '', body)
+ main = re.search(r'main := \|\*(.*?)\*\|;', body, re.S).group(1)
+ rest = body.replace(re.search(r'main := \|\*.*?\*\|;', body, re.S).group(0), '')
+ self.symbols = {name: int(value) for name, value in re.findall(r'export (\w+)\s*=\s*(\d+);', rest)}
+ names = {name: ('sym', frozenset([value])) for name, value in self.symbols.items()}
+ names['any'] = ('any',)
+ for statement in rest.split(';'):
+ statement = statement.strip()
+ if not statement or statement.startswith('export'):
+ continue
+ name, expression = statement.split('=', 1)
+ names[name.strip()] = parse(expression, names)
+ self.patterns = []
+ for pattern, action in re.findall(r'([^;{}]+?)=>\s*\{(.*?)\};', main, re.S):
+ kind = re.search(r'found_syllable \((\w+)\)', action).group(1)
+ self.patterns.append((parse(pattern, names), self.types.index(kind)))
+ self.broken = next(i for i, t in enumerate(self.types) if t.endswith('broken_cluster'))
+
+
+class NFA:
+ def __init__(self):
+ self.edges = [] # state: [(symbols or None for epsilon, target)]
+ self.accept = {} # state: pattern index
+
+ def state(self):
+ self.edges.append([])
+ return len(self.edges) - 1
+
+ def build(self, node):
+ """Thompson's construction: (start, end) of node."""
+ kind = node[0]
+ if kind in ('sym', 'any'):
+ a, b = self.state(), self.state()
+ symbols = frozenset(range(ALPHABET)) if kind == 'any' else frozenset(min(s, ALPHABET - 1) for s in node[1])
+ self.edges[a].append((symbols, b))
+ return a, b
+ if kind == 'alt':
+ a, b = self.state(), self.state()
+ for part in node[1:]:
+ s, e = self.build(part)
+ self.edges[a].append((None, s))
+ self.edges[e].append((None, b))
+ return a, b
+ if kind == 'cat':
+ s1, e1 = self.build(node[1])
+ s2, e2 = self.build(node[2])
+ self.edges[e1].append((None, s2))
+ return s1, e2
+ s, e = self.build(node[1])
+ a, b = self.state(), self.state()
+ self.edges[a].append((None, s))
+ self.edges[e].append((None, b))
+ if kind in ('opt', 'star'):
+ self.edges[a].append((None, b))
+ if kind in ('star', 'plus'):
+ self.edges[e].append((None, s))
+ return a, b
+
+ def closure(self, states):
+ seen, stack = set(states), list(states)
+ while stack:
+ for symbols, target in self.edges[stack.pop()]:
+ if symbols is None and target not in seen:
+ seen.add(target)
+ stack.append(target)
+ return frozenset(seen)
+
+
+def machine(grammar):
+ """The scanner's DFA, minimal: each state's transitions by symbol (None
+ for none) and the syllable type it accepts (the first pattern's), or
+ None; the start is state 0."""
+ nfa = NFA()
+ start = nfa.state()
+ for index, (tree, kind) in enumerate(grammar.patterns):
+ s, e = nfa.build(tree)
+ nfa.edges[start].append((None, s))
+ nfa.accept[e] = index
+ first = nfa.closure([start])
+ states, order, transitions = {first: 0}, [first], []
+ for current in order:
+ row = []
+ for symbol in range(ALPHABET):
+ targets = [t for s in current for symbols, t in nfa.edges[s] if symbols is not None and symbol in symbols]
+ if not targets:
+ row.append(None)
+ continue
+ target = nfa.closure(targets)
+ if target not in states:
+ states[target] = len(order)
+ order.append(target)
+ row.append(states[target])
+ transitions.append(row)
+ accepts = []
+ for current in order:
+ matched = [nfa.accept[s] for s in current if s in nfa.accept]
+ accepts.append(grammar.patterns[min(matched)][1] if matched else None)
+ # Moore's partition refinement
+ n = len(transitions)
+ block = list(accepts)
+ while True:
+ signature = [(block[s],) + tuple(None if t is None else block[t] for t in transitions[s]) for s in range(n)]
+ numbering = {}
+ refined = [numbering.setdefault(signature[s], len(numbering)) for s in range(n)]
+ if len(numbering) == len(set(block)):
+ break
+ block = refined
+ new = {}
+ for s in range(n):
+ new.setdefault(block[s], len(new))
+ table, accept = [None] * len(new), [None] * len(new)
+ for s in range(n):
+ table[new[block[s]]] = [None if t is None else new[block[t]] for t in transitions[s]]
+ accept[new[block[s]]] = accepts[s]
+ # every symbol starts a syllable: the scanner never stalls
+ assert all(table[0][symbol] is not None and accept[table[0][symbol]] is not None for symbol in range(ALPHABET))
+ return table, accept
+
+
+def classes(table):
+ """Symbols whose columns are the same, folded."""
+ columns, of = {}, []
+ for symbol in range(ALPHABET):
+ of.append(columns.setdefault(tuple(row[symbol] for row in table), len(columns)))
+ return of, len(columns)
+
+
+def scan(table, accept, symbols):
+ """Syllables of a run of symbols by a DFA: (start, end, type)."""
+ found, p = [], 0
+ while p < len(symbols):
+ state, end, kind = 0, None, None
+ for q in range(p, len(symbols)):
+ state = table[state][min(symbols[q], ALPHABET - 1)]
+ if state is None:
+ break
+ if accept[state] is not None:
+ end, kind = q + 1, accept[state]
+ found.append((p, end, kind))
+ p = end
+ return found
+
+
+# ---- The Indic table
+
+def indic_table(grammars):
+ """Code point to (category, position) for the characters HarfBuzz's
+ table has, and the default for the others."""
+ text = (HB / 'hb-ot-shaper-indic-table.cc').read_text()
+ positions = {name: int(value) for name, value in
+ re.findall(r'(POS_\w+) = (\d+)', (HB / 'hb-ot-shaper-indic.hh').read_text())}
+ machines = {'I': grammars['indic'].symbols, 'K': grammars['khmer'].symbols, 'M': grammars['myanmar'].symbols}
+ long_names = {name: machines[m][cat] for name, m, cat in re.findall(r'#define (OT_\w+) ([IKM])_Cat\((\w+)\)', text)}
+ short = {name: long_names[target] for name, target in re.findall(r'#define (_OT_\w+)\s+(OT_\w+)', text)}
+ short.update({name: positions[target] for name, target in re.findall(r'#define (_POS_\w+)\s+(POS_\w+)', text)})
+
+ 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)'):]
+ 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())
+ return cells, default
+
+
+def runs_of(cells):
+ """Runs of covered code points, gaps under 16 kept inside."""
+ runs = []
+ for code in sorted(cells):
+ if runs and code - runs[-1][1] <= 16:
+ runs[-1][1] = code
+ else:
+ runs.append([code, code])
+ return runs
+
+
+# ---- Checking against Ragel's machines (hb-ot-shaper-*-machine.hh)
+
+def ragel(name):
+ """A scanner from the flat tables and actions Ragel generated: symbols
+ to (start, end, type)."""
+ text = (HB / f'hb-ot-shaper-{name}-machine.hh').read_text()
+ grammar = Grammar(name)
+
+ def array(field):
+ body = re.search(rf'_{name}_syllable_machine_{field}\[\] = \{{(.*?)\}};', text, re.S).group(1)
+ return [int(v) for v in re.findall(r'-?\d+', body)]
+
+ keys, spans, offsets, indicies = array('trans_keys'), array('key_spans'), array('index_offsets'), array('indicies')
+ targets, actions = array('trans_targs'), array('trans_actions')
+ to_state, from_state, eof_trans = array('to_state_actions'), array('from_state_actions'), array('eof_trans')
+ start = int(re.search(rf'{name}_syllable_machine_start = (\d+);', text).group(1))
+
+ def cases(switch):
+ """The action cases of the switch on an array: number to code."""
+ at = text.index(f'switch ( _{name}_syllable_machine_{switch}[')
+ depth, begin = 0, text.index('{', at)
+ for k in range(begin, len(text)):
+ depth += {'{': 1, '}': -1}.get(text[k], 0)
+ if depth == 0:
+ body = text[begin + 1:k]
+ break
+ out, depth, current, k = {}, 0, None, 0
+ while k < len(body):
+ m = re.compile(r'case (\d+):').match(body, k) if depth == 0 else None
+ if m:
+ current = int(m.group(1))
+ out[current] = ''
+ k = m.end()
+ continue
+ depth += {'{': 1, '}': -1}.get(body[k], 0)
+ if current is not None:
+ out[current] += body[k]
+ k += 1
+ return out
+
+ trans_cases, to_cases, from_cases = cases('trans_actions'), cases('to_state_actions'), cases('from_state_actions')
+
+ def run(code, st):
+ # the `switch( act )` of a backtracking action: only the chosen case
+ m = re.search(r'switch\( act \) \{(.*)\}', code, re.S)
+ if m:
+ chosen = re.search(rf'case {st["act"]}:(.*?)break;', m.group(1), re.S)
+ code = code[:m.start()] + (chosen.group(1) if chosen else '')
+ for statement in re.findall(r'ts = p;|ts = 0;|te = p\+1;|te = p;|p--;|p = \(\(te\)\)-1;|act = \d+;|'
+ r'found_syllable \(\w+\)', code):
+ if statement == 'ts = p;': st['ts'] = st['p']
+ elif statement == 'ts = 0;': st['ts'] = 0
+ elif statement == 'te = p+1;': st['te'] = st['p'] + 1
+ elif statement == 'te = p;': st['te'] = st['p']
+ elif statement == 'p--;': st['p'] -= 1
+ elif statement == 'p = ((te))-1;': st['p'] = st['te'] - 1
+ elif statement.startswith('act'): st['act'] = int(statement[6:-1])
+ else:
+ kind = statement[len('found_syllable ('):-1]
+ st['found'].append((st['ts'], st['te'], grammar.types.index(kind)))
+
+ def scanner(symbols):
+ st = {'ts': 0, 'te': 0, 'act': 0, 'p': 0, 'found': []}
+ cs, pe = start, len(symbols)
+ state = 'resume' if pe else 'test_eof'
+ while True:
+ if state == 'resume':
+ if from_state[cs]:
+ run(from_cases[from_state[cs]], st)
+ c, low, high, span = symbols[st['p']], keys[2 * cs], keys[2 * cs + 1], spans[cs]
+ trans = indicies[offsets[cs] + (c - low if span > 0 and low <= c <= high else span)]
+ state = 'eof_trans'
+ if state == 'eof_trans':
+ cs = targets[trans]
+ if actions[trans]:
+ run(trans_cases[actions[trans]], st)
+ if to_state[cs]:
+ run(to_cases[to_state[cs]], st)
+ st['p'] += 1
+ state = 'resume' if st['p'] != pe else 'test_eof'
+ continue
+ if state == 'test_eof':
+ if st['p'] == pe and eof_trans[cs] > 0:
+ trans = eof_trans[cs] - 1
+ state = 'eof_trans'
+ continue
+ return st['found']
+ return scanner
+
+
+def check(runs=100000):
+ rng = random.Random(1)
+ for name in MACHINES:
+ grammar = Grammar(name)
+ table, accept = machine(grammar)
+ theirs = ragel(name)
+ symbols = sorted(set(grammar.symbols.values())) + [0, 19, 63]
+ differ = 0
+ for _ in range(runs):
+ run = [rng.choice(symbols) for _ in range(rng.randint(1, 14))]
+ a, b = scan(table, accept, run), theirs(run)
+ if a != b:
+ differ += 1
+ if differ <= 3:
+ print(name, 'differ:', run, a, b, file=sys.stderr)
+ print(f'{name}: {len(table)} states, {runs} runs, {differ} differ')
+
+
+# ---- The C file
+
+def emit_machine(out, name, grammar):
+ table, accept = machine(grammar)
+ of, count = classes(table)
+ dead = len(table)
+ column_of = {}
+ for symbol in range(ALPHABET):
+ column_of.setdefault(of[symbol], symbol)
+ width = 'uint8_t' if dead < 256 else 'uint16_t'
+ out.append(f'/* {name} (hb-ot-shaper-{name}-machine.rl): {len(table)} states, {count} classes of symbol;'
+ f' types {", ".join(grammar.types)}. */')
+ out.append(f'static const uint8_t {name}_class[{ALPHABET}] = {{')
+ for k in range(0, ALPHABET, 32):
+ out.append(' ' + ', '.join(str(c) for c in of[k:k + 32]) + ',')
+ out.append('};')
+ out.append(f'static const {width} {name}_next[{len(table)}][{count}] = {{')
+ for row in table:
+ out.append(' {' + ', '.join(str(dead if row[column_of[k]] is None else row[column_of[k]])
+ for k in range(count)) + '},')
+ out.append('};')
+ out.append(f'static const uint8_t {name}_accept[{len(table)}] = {{')
+ cells = [str(0 if a is None else a + 1) for a in accept]
+ for k in range(0, len(cells), 32):
+ out.append(' ' + ', '.join(cells[k:k + 32]) + ',')
+ out.append('};')
+ out.append(f'const VegaMachine vega_{name}_machine = {{{name}_class, {count}, {len(table)}, {name}_next[0], '
+ f'sizeof({name}_next[0][0]), {name}_accept, {grammar.broken + 1}}};')
+ out.append('')
+
+
+def main():
+ if len(sys.argv) > 2 and sys.argv[2] == '--check':
+ check()
+ return
+ grammars = {name: Grammar(name) for name in MACHINES}
+ cells, default = indic_table(grammars)
+ flat, starts = [], []
+ for a, b in runs_of(cells):
+ starts.append((a, b, len(flat)))
+ flat.extend(cells.get(code, default) for code in range(a, b + 1))
+ out = ['/* Generated by text/syllabic.py from HarfBuzz 10.2.0\'s sources; do not edit. */',
+ '#include "syllabic.h"', '',
+ '/* Each character\'s Indic category and position (hb-ot-shaper-indic-table.cc, Unicode 16):',
+ ' * category | position << 8. */',
+ f'static const uint16_t indic_cells[{len(flat)}] = {{']
+ for k in range(0, len(flat), 12):
+ out.append(' ' + ', '.join(f'0x{c | p << 8:04X}' for c, p in flat[k:k + 12]) + ',')
+ out.append('};')
+ out.append(f'static const struct {{ uint32_t first, last, at; }} indic_runs[{len(starts)}] = {{')
+ for a, b, at in starts:
+ out.append(f' {{0x{a:04X}, 0x{b:04X}, {at}}},')
+ out.append('};')
+ out.append('')
+ out.append('uint16_t vega_indic_categories(uint32_t u) {')
+ out.append(f' for (unsigned i = 0; i < {len(starts)} && u >= indic_runs[i].first; ++i)')
+ out.append(' if (u <= indic_runs[i].last) return indic_cells[indic_runs[i].at + u - indic_runs[i].first];')
+ out.append(f' return 0x{default[0] | default[1] << 8:04X};')
+ out.append('}')
+ out.append('')
+ out.append('/* The syllable machines: a DFA over classes of symbol; a state\'s accept is')
+ out.append(' * the syllable type it ends, plus one. */')
+ for name in MACHINES:
+ emit_machine(out, name, grammars[name])
+ sys.stdout.write('\n'.join(out) + '\n')
+
+
+if __name__ == '__main__':
+ main()