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