// Copyright 2026 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
///|
let thai_consonant_nc : Int = 0
///|
let thai_consonant_ac : Int = 1
///|
let thai_consonant_rc : Int = 2
///|
let thai_consonant_dc : Int = 3
///|
let thai_consonant_not : Int = 4
///|
let thai_mark_av : Int = 0
///|
let thai_mark_bv : Int = 1
///|
let thai_mark_t : Int = 2
///|
let thai_mark_not : Int = 3
///|
let thai_action_nop : Int = 0
///|
let thai_action_sd : Int = 1
///|
let thai_action_sl : Int = 2
///|
let thai_action_sdl : Int = 3
///|
let thai_action_rd : Int = 4
///|
let thai_above_t0 : Int = 0
///|
let thai_above_t1 : Int = 1
///|
let thai_above_t2 : Int = 2
///|
let thai_above_t3 : Int = 3
///|
let thai_below_b0 : Int = 0
///|
let thai_below_b1 : Int = 1
///|
let thai_below_b2 : Int = 2
///|
priv struct ThaiStateEdge {
action : Int
next_state : Int
}
///|
let thai_above_start_state : Array[Int] = [
thai_above_t0,
thai_above_t1,
thai_above_t0,
thai_above_t0,
thai_above_t3,
]
///|
let thai_below_start_state : Array[Int] = [
thai_below_b0,
thai_below_b0,
thai_below_b1,
thai_below_b2,
thai_below_b2,
]
///|
let thai_above_state_machine : Array[Array[ThaiStateEdge]] = [
[
ThaiStateEdge::{ action: thai_action_nop, next_state: thai_above_t3 },
ThaiStateEdge::{ action: thai_action_nop, next_state: thai_above_t0 },
ThaiStateEdge::{ action: thai_action_sd, next_state: thai_above_t3 },
],
[
ThaiStateEdge::{ action: thai_action_sl, next_state: thai_above_t2 },
ThaiStateEdge::{ action: thai_action_nop, next_state: thai_above_t1 },
ThaiStateEdge::{ action: thai_action_sdl, next_state: thai_above_t2 },
],
[
ThaiStateEdge::{ action: thai_action_nop, next_state: thai_above_t3 },
ThaiStateEdge::{ action: thai_action_nop, next_state: thai_above_t2 },
ThaiStateEdge::{ action: thai_action_sl, next_state: thai_above_t3 },
],
[
ThaiStateEdge::{ action: thai_action_nop, next_state: thai_above_t3 },
ThaiStateEdge::{ action: thai_action_nop, next_state: thai_above_t3 },
ThaiStateEdge::{ action: thai_action_nop, next_state: thai_above_t3 },
],
]
///|
let thai_below_state_machine : Array[Array[ThaiStateEdge]] = [
[
ThaiStateEdge::{ action: thai_action_nop, next_state: thai_below_b0 },
ThaiStateEdge::{ action: thai_action_nop, next_state: thai_below_b2 },
ThaiStateEdge::{ action: thai_action_nop, next_state: thai_below_b0 },
],
[
ThaiStateEdge::{ action: thai_action_nop, next_state: thai_below_b1 },
ThaiStateEdge::{ action: thai_action_rd, next_state: thai_below_b2 },
ThaiStateEdge::{ action: thai_action_nop, next_state: thai_below_b1 },
],
[
ThaiStateEdge::{ action: thai_action_nop, next_state: thai_below_b2 },
ThaiStateEdge::{ action: thai_action_sd, next_state: thai_below_b2 },
ThaiStateEdge::{ action: thai_action_nop, next_state: thai_below_b2 },
],
]
///|
priv struct ThaiPuaMapping {
u : UInt
win_pua : UInt
mac_pua : UInt
}
///|
let thai_pua_sd : Array[ThaiPuaMapping] = [
ThaiPuaMapping::{ u: 0x0E48U, win_pua: 0xF70AU, mac_pua: 0xF88BU },
ThaiPuaMapping::{ u: 0x0E49U, win_pua: 0xF70BU, mac_pua: 0xF88EU },
ThaiPuaMapping::{ u: 0x0E4AU, win_pua: 0xF70CU, mac_pua: 0xF891U },
ThaiPuaMapping::{ u: 0x0E4BU, win_pua: 0xF70DU, mac_pua: 0xF894U },
ThaiPuaMapping::{ u: 0x0E4CU, win_pua: 0xF70EU, mac_pua: 0xF897U },
ThaiPuaMapping::{ u: 0x0E38U, win_pua: 0xF718U, mac_pua: 0xF89BU },
ThaiPuaMapping::{ u: 0x0E39U, win_pua: 0xF719U, mac_pua: 0xF89CU },
ThaiPuaMapping::{ u: 0x0E3AU, win_pua: 0xF71AU, mac_pua: 0xF89DU },
]
///|
let thai_pua_sdl : Array[ThaiPuaMapping] = [
ThaiPuaMapping::{ u: 0x0E48U, win_pua: 0xF705U, mac_pua: 0xF88CU },
ThaiPuaMapping::{ u: 0x0E49U, win_pua: 0xF706U, mac_pua: 0xF88FU },
ThaiPuaMapping::{ u: 0x0E4AU, win_pua: 0xF707U, mac_pua: 0xF892U },
ThaiPuaMapping::{ u: 0x0E4BU, win_pua: 0xF708U, mac_pua: 0xF895U },
ThaiPuaMapping::{ u: 0x0E4CU, win_pua: 0xF709U, mac_pua: 0xF898U },
]
///|
let thai_pua_sl : Array[ThaiPuaMapping] = [
ThaiPuaMapping::{ u: 0x0E48U, win_pua: 0xF713U, mac_pua: 0xF88AU },
ThaiPuaMapping::{ u: 0x0E49U, win_pua: 0xF714U, mac_pua: 0xF88DU },
ThaiPuaMapping::{ u: 0x0E4AU, win_pua: 0xF715U, mac_pua: 0xF890U },
ThaiPuaMapping::{ u: 0x0E4BU, win_pua: 0xF716U, mac_pua: 0xF893U },
ThaiPuaMapping::{ u: 0x0E4CU, win_pua: 0xF717U, mac_pua: 0xF896U },
ThaiPuaMapping::{ u: 0x0E31U, win_pua: 0xF710U, mac_pua: 0xF884U },
ThaiPuaMapping::{ u: 0x0E34U, win_pua: 0xF701U, mac_pua: 0xF885U },
ThaiPuaMapping::{ u: 0x0E35U, win_pua: 0xF702U, mac_pua: 0xF886U },
ThaiPuaMapping::{ u: 0x0E36U, win_pua: 0xF703U, mac_pua: 0xF887U },
ThaiPuaMapping::{ u: 0x0E37U, win_pua: 0xF704U, mac_pua: 0xF888U },
ThaiPuaMapping::{ u: 0x0E47U, win_pua: 0xF712U, mac_pua: 0xF889U },
ThaiPuaMapping::{ u: 0x0E4DU, win_pua: 0xF711U, mac_pua: 0xF899U },
]
///|
let thai_pua_rd : Array[ThaiPuaMapping] = [
ThaiPuaMapping::{ u: 0x0E0DU, win_pua: 0xF70FU, mac_pua: 0xF89AU },
ThaiPuaMapping::{ u: 0x0E10U, win_pua: 0xF700U, mac_pua: 0xF89EU },
]
///|
fn mask_thai_lao(u : UInt) -> UInt {
u & 0xFFFFFF7FU
}
///|
fn is_sara_am(u : UInt) -> Bool {
mask_thai_lao(u) == 0x0E33U
}
///|
fn nikhahit_from_sara_am(u : UInt) -> UInt {
u - 0x0E33U + 0x0E4DU
}
///|
fn sara_aa_from_sara_am(u : UInt) -> UInt {
u - 1U
}
///|
fn is_above_base_mark(u : UInt) -> Bool {
let v = mask_thai_lao(u)
(v >= 0x0E34U && v <= 0x0E37U) ||
(v >= 0x0E47U && v <= 0x0E4EU) ||
v == 0x0E31U ||
v == 0x0E3BU
}
///|
fn get_consonant_type(u : UInt) -> Int {
let v = mask_thai_lao(u)
if v == 0x0E1BU || v == 0x0E1DU || v == 0x0E1FU {
return thai_consonant_ac
}
if v == 0x0E0DU || v == 0x0E10U {
return thai_consonant_rc
}
if v == 0x0E0EU || v == 0x0E0FU {
return thai_consonant_dc
}
if v >= 0x0E01U && v <= 0x0E2EU {
return thai_consonant_nc
}
thai_consonant_not
}
///|
fn get_mark_type(u : UInt) -> Int {
let v = mask_thai_lao(u)
if v == 0x0E31U || (v >= 0x0E34U && v <= 0x0E37U) ||
v == 0x0E47U || (v >= 0x0E4DU && v <= 0x0E4EU) {
return thai_mark_av
}
if v >= 0x0E38U && v <= 0x0E3AU {
return thai_mark_bv
}
if v >= 0x0E48U && v <= 0x0E4CU {
return thai_mark_t
}
thai_mark_not
}
///|
fn merge_clusters_range(infos : Array[GlyphInfo], start : Int, end : Int) -> Unit {
if start < 0 || start >= end || end > infos.length() {
return ()
}
let mut min_cluster = infos[start].cluster
for i in (start + 1).. Unit {
if buffer.infos.is_empty() {
return ()
}
let infos : Array[GlyphInfo] = []
let positions : Array[GlyphPosition] = []
for i in 0.. 0 && is_above_base_mark(infos[start - 1].codepoint) {
start = start - 1
}
if start + 2 < end {
let n_info = infos[end - 2]
let n_pos = positions[end - 2]
let mut j = end - 2
while j > start {
infos[j] = infos[j - 1]
positions[j] = positions[j - 1]
j = j - 1
}
infos[start] = n_info
positions[start] = n_pos
merge_clusters_range(infos, start, end)
} else if start > 0 {
merge_clusters_range(infos, start - 1, end)
}
}
ignore(buffer.replace_glyphs(infos, positions=positions))
}
///|
fn thai_pua_shape(
u : UInt,
action : Int,
font : @font.Font,
) -> Result[UInt, ShapeError] {
let mappings = if action == thai_action_sd {
thai_pua_sd
} else if action == thai_action_sdl {
thai_pua_sdl
} else if action == thai_action_sl {
thai_pua_sl
} else if action == thai_action_rd {
thai_pua_rd
} else {
[]
}
if mappings.is_empty() {
return Ok(u)
}
for mapping in mappings {
if mapping.u == u {
match font.glyph_for_codepoint(mapping.win_pua) {
Err(err) => return Err(Font(err))
Ok(None) => ()
Ok(Some(_)) => return Ok(mapping.win_pua)
}
match font.glyph_for_codepoint(mapping.mac_pua) {
Err(err) => return Err(Font(err))
Ok(None) => ()
Ok(Some(_)) => return Ok(mapping.mac_pua)
}
break
}
}
Ok(u)
}
///|
fn apply_thai_pua_shaping(
buffer : Buffer,
font : @font.Font,
) -> Result[Unit, ShapeError] {
let mut above_state = thai_above_start_state[thai_consonant_not]
let mut below_state = thai_below_start_state[thai_consonant_not]
let mut base = 0
for i in 0.. return Err(err)
Ok(value) => value
}
buffer.infos[base] = GlyphInfo::{
codepoint: new_codepoint,
cluster: base_info.cluster,
unicode: new_codepoint,
}
} else {
let new_codepoint = match thai_pua_shape(info.codepoint, action, font) {
Err(err) => return Err(err)
Ok(value) => value
}
buffer.infos[i] = GlyphInfo::{ codepoint: new_codepoint, cluster: info.cluster, unicode: new_codepoint }
}
}
Ok(())
}
///|
pub fn Buffer::shape_thai(
self : Buffer,
font : @font.Font,
) -> Result[Unit, ShapeError] {
preprocess_thai_sara_am(self)
let gsub = match font.gsub() {
Err(err) => return Err(Font(err))
Ok(value) => value
}
let mut apply_pua = false
if self.script == @common.script_thai {
match gsub {
None => apply_pua = true
Some(gsub) => {
let script_tag = self.script.to_tag()
if !buffer_layout_has_script(gsub.layout(), script_tag) {
apply_pua = true
}
}
}
}
if apply_pua {
match apply_thai_pua_shaping(self, font) {
Err(err) => return Err(err)
Ok(_) => ()
}
}
self.shape_ot(font, fallback_position=false)
}