aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/design/004-text.md19
-rw-r--r--text/shape-build.c12
-rw-r--r--text/shape.c119
-rw-r--r--text/shape.h11
4 files changed, 147 insertions, 14 deletions
diff --git a/docs/design/004-text.md b/docs/design/004-text.md
index ec681e1..c62ff65 100644
--- a/docs/design/004-text.md
+++ b/docs/design/004-text.md
@@ -159,9 +159,9 @@ Malayalam은 psts와 rlig가 한 조회를 나눠 쓰는데, 안정 정렬로는
결과(Pi 5):
-- 설치된 글꼴 213벌, 계획 1,748개에 각 1천 실행: 다른 실행 0(글자 모양이 비트맵뿐인 Noto Color Emoji는 심볼 단계에서
- 빠진다). 인터넷에서 받은 Noto 21벌(인도계 아홉, 싱할라, 크메르, 미얀마, 티베트, 자바, 발리, 몽골 등), 계획 80개에 각 3천
- 실행: 다른 실행 0. 인도계가 있는 글꼴은 따로 계획마다 2만 실행: FreeSerif(여덟 문자), FreeSans, 굵은 꼴들, FreeMono, Unifont(아홉
+- 설치된 글꼴 213벌, 계획 1,804개에 각 1천 실행: 다른 실행 0(글자 모양이 비트맵뿐인 Noto Color Emoji는 심볼 단계에서
+ 빠진다). 인터넷에서 받은 Noto 21벌(인도계 아홉, 싱할라, 크메르, 미얀마, 티베트, 자바, 발리, 몽골, 태국, 라오 등), 계획
+ 84개에 각 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).
@@ -197,11 +197,22 @@ Malayalam은 psts와 rlig가 한 조회를 나눠 쓰는데, 안정 정렬로는
계획마다 5천 실행, 다른 실행 0. Unifont의 몽골에서 한 번 달랐다. HarfBuzz 10.2의 기본 무시 문자 목록은 몽골 블록에서
U+180B–180E만 넣고 네 번째 자유 이체 선택자(U+180F)를 빠뜨렸다. Vega는 유니코드대로 넣었었는데, HarfBuzz를 따라 뺐다.
+### 태국과 라오
+
+`hb-ot-shaper-thai.cc`: SARA AM을 NIKHAHIT와 SARA AA로 풀고 NIKHAHIT를 위 부호들 앞으로 옮긴다(NIKHAHIT는 폭을 없앨 때
+부호로 셈한다). 글꼴에 태국 문자 체계가 없으면(GSUB가 DFLT나 latn만 가지면) 부호를 쌓임에 따라 글꼴의 사용자 영역
+글리프(Windows의 F700대, 없으면 Mac의 F880대)로 바꾼다. 계획기가 HarfBuzz처럼 글꼴이 문자 체계를 제 태그로 가졌는지
+기록한다.
+
+결과: Noto Sans Thai·Lao, TLWG Loma 네 벌, FreeSerif 계열의 태국, DejaVu Sans 계열의 라오, Unifont, 계획마다 3천 실행, 다른
+실행 0. 사용자 영역 경로는 설치된 글꼴에 쓸 일이 없어서, Loma에서 GSUB의 thai 문자 체계만 지운 글꼴을 만들어 5천 실행으로
+확인했다(다른 실행 0).
+
받지 않는 것(빌드 도구가 이유를 말하고 거부한다):
- `kern` 표의 상태 기계(Apple 형식 1), 등급 형식(2, 3), 교차 방향 커닝. 설치된 글꼴의 `kern` 표는 모두 짝 형식(0)이다.
- AAT 표(morx, kerx, trak), 늘이기(stch), 아랍 형태가 없어 HarfBuzz가 표시형으로 대체 셰이핑을 하는 글꼴(Unifont의 아랍).
-- 기본·아랍·히브리·한글·인도계·크메르·미얀마·USE 셰이퍼가 아닌 문자 체계: 태국·라오, 시리아.
+- 시리아 문자(아랍 셰이퍼의 Alaph·Dalath-Rish 연결과 fin2·fin3·med2), 미얀마 Zawgyi.
- 이체 선택자(cmap 형식 14)는 싣지 않는다. 선택자는 숨기고 앞 글자는 기본 글리프로 둔다.
## 비례폭 글줄
diff --git a/text/shape-build.c b/text/shape-build.c
index a3b924e..96bc748 100644
--- a/text/shape-build.c
+++ b/text/shape-build.c
@@ -33,6 +33,7 @@ typedef struct {
hb_face_t *face;
unsigned script_index[2], language_index[2];
hb_tag_t chosen_script[2];
+ bool found_script[2]; /* one of the script's own tags, not DFLT or latn */
FeatureInfo infos[MAX_FEATURES];
unsigned info_count, current_stage[2];
unsigned pause[2][MAX_STAGES]; /* what ends each stage: VEGA_PAUSE_..., or 0 */
@@ -332,7 +333,8 @@ static int categorize(uint32_t script, hb_tag_t gsub_script) {
if (script == TAG("Hebr")) return VEGA_SHAPER_HEBREW;
if (script == TAG("Hang")) return VEGA_SHAPER_HANGUL;
if (script == TAG("Khmr")) return VEGA_SHAPER_KHMER;
- if (script == TAG("Syrc") || script == TAG("Thai") || script == TAG("Laoo") || script == TAG("Qaag")) return -1;
+ if (script == TAG("Thai") || script == TAG("Laoo")) return VEGA_SHAPER_THAI;
+ if (script == TAG("Syrc") || 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))
@@ -430,8 +432,8 @@ static bool plan_for(hb_face_t *face, uint32_t script_tag, int direction, Plan *
hb_ot_tags_from_script_and_language(script, HB_LANGUAGE_INVALID, &script_count, script_tags, &language_count,
language_tags);
for (int t = 0; t < 2; ++t) {
- hb_ot_layout_table_select_script(face, table_tags[t], script_count, script_tags, &m->script_index[t],
- &m->chosen_script[t]);
+ m->found_script[t] = hb_ot_layout_table_select_script(face, table_tags[t], script_count, script_tags,
+ &m->script_index[t], &m->chosen_script[t]);
hb_ot_layout_script_select_language(face, table_tags[t], m->script_index[t], language_count, language_tags,
&m->language_index[t]);
}
@@ -610,7 +612,7 @@ static bool plan_for(hb_face_t *face, uint32_t script_tag, int direction, Plan *
/* Hangul's, Indic's and Khmer's shapers zero no marks (Myanmar's does,
* before GPOS); none of these places marks without GPOS. */
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;
+ 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;
if (shaper == VEGA_SHAPER_ARABIC) {
if (one_mask(m, "stch")) FAIL("%s: the font stretches (stch)", name);
@@ -655,6 +657,8 @@ static bool plan_for(hb_face_t *face, uint32_t script_tag, int direction, Plan *
}
if (shaper == VEGA_SHAPER_KHMER)
for (int i = 0; i < 5; ++i) plan->masks[VEGA_MASK_KHMER_PREF + i] = one_mask(m, khmer_basic[i]);
+ if (shaper == VEGA_SHAPER_THAI && script == HB_SCRIPT_THAI && !m->found_script[0])
+ plan->shaper_flags |= VEGA_THAI_PUA;
free(m);
return true;
fail:
diff --git a/text/shape.c b/text/shape.c
index 76d5409..20fea05 100644
--- a/text/shape.c
+++ b/text/shape.c
@@ -227,7 +227,7 @@ bool vega_shaping_open(VegaShaping *s, const unsigned char *bytes, size_t size,
for (uint32_t i = 0; i < s->plan_count; ++i) {
const unsigned char *p = s->plans + (size_t)i * VEGA_PLAN_BYTES;
uint32_t ranges[2][2] = {{le32(p + 80), le32(p + 84)}, {le32(p + 88), le32(p + 92)}};
- if ((p[4] != VEGA_SHAPE_LTR && p[4] != VEGA_SHAPE_RTL) || p[5] > VEGA_SHAPE_RTL || p[6] > VEGA_SHAPER_USE)
+ if ((p[4] != VEGA_SHAPE_LTR && p[4] != VEGA_SHAPE_RTL) || p[5] > VEGA_SHAPE_RTL || p[6] > VEGA_SHAPER_THAI)
REFUSE("plan %u", i);
if ((p[7] & VEGA_PLAN_FALLBACK_MARKS) && !s->extents) REFUSE("plan %u: marks placed without extents", i);
for (int table = 0; table < 2; ++table) {
@@ -3510,6 +3510,122 @@ static bool use_reorder(const VegaShaping *s, VegaShaper *b) {
return changed;
}
+/* ---- Thai and Lao (hb-ot-shaper-thai.cc): SARA AM taken apart into
+ * NIKHAHIT and SARA AA, the NIKHAHIT moved back over the above-base marks;
+ * in a font with no Thai script, marks shifted by the font's private-use
+ * glyphs (Windows' or the Mac's) as the stacking around the base asks. */
+
+enum { THAI_NC, THAI_AC, THAI_RC, THAI_DC, THAI_NOT_CONSONANT };
+enum { THAI_AV, THAI_BV, THAI_T, THAI_NOT_MARK };
+enum { THAI_NOP, THAI_SD, THAI_SL, THAI_SDL, THAI_RD };
+
+static unsigned thai_consonant_type(uint32_t u) {
+ if (u == 0x0E1B || u == 0x0E1D || u == 0x0E1F) return THAI_AC;
+ if (u == 0x0E0D || u == 0x0E10) return THAI_RC;
+ if (u == 0x0E0E || u == 0x0E0F) return THAI_DC;
+ if (u >= 0x0E01 && u <= 0x0E2E) return THAI_NC;
+ return THAI_NOT_CONSONANT;
+}
+
+static unsigned thai_mark_type(uint32_t u) {
+ if (u == 0x0E31 || (u >= 0x0E34 && u <= 0x0E37) || u == 0x0E47 || (u >= 0x0E4D && u <= 0x0E4E)) return THAI_AV;
+ if (u >= 0x0E38 && u <= 0x0E3A) return THAI_BV;
+ if (u >= 0x0E48 && u <= 0x0E4C) return THAI_T;
+ return THAI_NOT_MARK;
+}
+
+/* The private-use code point the font has for u shifted by action
+ * (Windows' before the Mac's), or u. */
+static uint32_t thai_pua_shape(const VegaShaping *s, uint32_t u, unsigned action) {
+ static const uint16_t sd[][3] = {{0x0E48, 0xF70A, 0xF88B}, {0x0E49, 0xF70B, 0xF88E}, {0x0E4A, 0xF70C, 0xF891},
+ {0x0E4B, 0xF70D, 0xF894}, {0x0E4C, 0xF70E, 0xF897}, {0x0E38, 0xF718, 0xF89B},
+ {0x0E39, 0xF719, 0xF89C}, {0x0E3A, 0xF71A, 0xF89D}, {0, 0, 0}};
+ static const uint16_t sdl[][3] = {{0x0E48, 0xF705, 0xF88C}, {0x0E49, 0xF706, 0xF88F}, {0x0E4A, 0xF707, 0xF892},
+ {0x0E4B, 0xF708, 0xF895}, {0x0E4C, 0xF709, 0xF898}, {0, 0, 0}};
+ static const uint16_t sl[][3] = {{0x0E48, 0xF713, 0xF88A}, {0x0E49, 0xF714, 0xF88D}, {0x0E4A, 0xF715, 0xF890},
+ {0x0E4B, 0xF716, 0xF893}, {0x0E4C, 0xF717, 0xF896}, {0x0E31, 0xF710, 0xF884},
+ {0x0E34, 0xF701, 0xF885}, {0x0E35, 0xF702, 0xF886}, {0x0E36, 0xF703, 0xF887},
+ {0x0E37, 0xF704, 0xF888}, {0x0E47, 0xF712, 0xF889}, {0x0E4D, 0xF711, 0xF899},
+ {0, 0, 0}};
+ static const uint16_t rd[][3] = {{0x0E0D, 0xF70F, 0xF89A}, {0x0E10, 0xF700, 0xF89E}, {0, 0, 0}};
+ const uint16_t(*mappings)[3] = action == THAI_SD ? sd : action == THAI_SDL ? sdl : action == THAI_SL ? sl
+ : action == THAI_RD ? rd : NULL;
+ if (!mappings) return u;
+ uint32_t glyph;
+ for (; mappings[0][0]; mappings++)
+ if (mappings[0][0] == u) {
+ if (nominal(s, mappings[0][1], &glyph)) return mappings[0][1];
+ if (nominal(s, mappings[0][2], &glyph)) return mappings[0][2];
+ break;
+ }
+ return u;
+}
+
+static void thai_pua_shaping(const VegaShaping *s, VegaShaper *b) {
+ /* above: T0..T3; below: B0..B2 (hb-ot-shaper-thai.cc's state machines) */
+ static const uint8_t above_start[5] = {0, 1, 0, 0, 3}, below_start[5] = {0, 0, 1, 2, 2};
+ static const uint8_t above[4][3][2] = {{{THAI_NOP, 3}, {THAI_NOP, 0}, {THAI_SD, 3}},
+ {{THAI_SL, 2}, {THAI_NOP, 1}, {THAI_SDL, 2}},
+ {{THAI_NOP, 3}, {THAI_NOP, 2}, {THAI_SL, 3}},
+ {{THAI_NOP, 3}, {THAI_NOP, 3}, {THAI_NOP, 3}}};
+ static const uint8_t below[3][3][2] = {{{THAI_NOP, 0}, {THAI_NOP, 2}, {THAI_NOP, 0}},
+ {{THAI_NOP, 1}, {THAI_RD, 2}, {THAI_NOP, 1}},
+ {{THAI_NOP, 2}, {THAI_SD, 2}, {THAI_NOP, 2}}};
+ unsigned above_state = above_start[THAI_NOT_CONSONANT], below_state = below_start[THAI_NOT_CONSONANT];
+ size_t base = 0;
+ for (size_t i = 0; i < b->len; ++i) {
+ unsigned mt = thai_mark_type(b->info[i].codepoint);
+ if (mt == THAI_NOT_MARK) {
+ unsigned ct = thai_consonant_type(b->info[i].codepoint);
+ above_state = above_start[ct];
+ below_state = below_start[ct];
+ base = i;
+ continue;
+ }
+ const uint8_t *a = above[above_state][mt], *w = below[below_state][mt];
+ above_state = a[1];
+ below_state = w[1];
+ unsigned action = a[0] != THAI_NOP ? a[0] : w[0];
+ if (action == THAI_RD) b->info[base].codepoint = thai_pua_shape(s, b->info[base].codepoint, action);
+ else b->info[i].codepoint = thai_pua_shape(s, b->info[i].codepoint, action);
+ }
+}
+
+static bool thai_above_base_mark(uint32_t u) {
+ u &= ~0x80u;
+ return (u >= 0x0E34 && u <= 0x0E37) || (u >= 0x0E47 && u <= 0x0E4E) || u == 0x0E31 || u == 0x0E3B;
+}
+
+static void preprocess_thai(const VegaShaping *s, const Plan *plan, VegaShaper *b) {
+ clear_output(b);
+ size_t count = b->len;
+ while (b->idx < count && b->successful) {
+ uint32_t u = b->info[b->idx].codepoint;
+ if ((u & ~0x80u) != 0x0E33) { /* not SARA AM */
+ next_glyph(b);
+ continue;
+ }
+ output_glyph(b, u - 0x0E33 + 0x0E4D); /* NIKHAHIT, continuing the character before */
+ if (b->out_len) b->out[b->out_len - 1].uprops |= UPROPS_CONTINUATION;
+ replace_glyph(b, u - 1); /* SARA AA */
+ if (!b->successful) break;
+ size_t end = b->out_len;
+ /* The NIKHAHIT counts as a mark (of class 0) when widths are zeroed. */
+ b->out[end - 2].uprops = (uint16_t)((b->out[end - 2].uprops & ~0x1Fu) | VEGA_MN);
+ size_t start = end - 2;
+ while (start > 0 && thai_above_base_mark(b->out[start - 1].codepoint)) start--;
+ if (start + 2 < end) {
+ merge_out_clusters(b, start, end);
+ Info t = b->out[end - 2];
+ memmove(b->out + start + 1, b->out + start, (end - start - 2) * sizeof(Info));
+ b->out[start] = t;
+ } else if (start)
+ merge_out_clusters(b, start - 1, end);
+ }
+ sync(b);
+ if (plan->shaper_flags & VEGA_THAI_PUA) thai_pua_shaping(s, b);
+}
+
/* The shaper's part of the work at a pause in GSUB; whether it changed the
* buffer. */
static bool shaper_pause(const VegaShaping *s, const Plan *plan, VegaShaper *b, unsigned pause) {
@@ -3656,6 +3772,7 @@ long vega_shape(VegaShaper *b, const VegaShaping *s, const uint32_t *codes, size
ensure_native_direction(b, &plan);
if (plan.shaper == VEGA_SHAPER_HANGUL) preprocess_hangul(s, b);
if (plan.shaper == VEGA_SHAPER_INDIC || plan.shaper == VEGA_SHAPER_USE) preprocess_vowel_constraints(&plan, b);
+ if (plan.shaper == VEGA_SHAPER_THAI) preprocess_thai(s, &plan, b);
/* Mirror what the font mirrors; mark the rest for rtlm. */
if (direction == VEGA_SHAPE_RTL)
diff --git a/text/shape.h b/text/shape.h
index bd38d96..6e56819 100644
--- a/text/shape.h
+++ b/text/shape.h
@@ -6,8 +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, Hebrew, Hangul, Indic, Khmer, Myanmar and Universal
- * (USE) shapers, glyph for glyph and unit for unit.
+ * its default, Arabic, Hebrew, Hangul, Indic, Khmer, Myanmar, Universal
+ * (USE) and Thai 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:
@@ -48,7 +48,7 @@ enum { VEGA_SHAPING_HEADER = 80, VEGA_PLAN_BYTES = 144, VEGA_STEP_BYTES = 8, VEG
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, VEGA_SHAPER_INDIC,
- VEGA_SHAPER_KHMER, VEGA_SHAPER_MYANMAR, VEGA_SHAPER_USE };
+ VEGA_SHAPER_KHMER, VEGA_SHAPER_MYANMAR, VEGA_SHAPER_USE, VEGA_SHAPER_THAI };
/* 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
@@ -71,8 +71,9 @@ enum { VEGA_MASK_USE_RPHF = VEGA_MASK_INIT + 1 }; /* USE: that, and Arab
* 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 };
+ * 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 };
/* 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 };