// 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 subtable_type_rearrangement = 0
let subtable_type_contextual = 1
let subtable_type_ligature = 2
let subtable_type_noncontextual = 4
let subtable_type_insertion = 5
///|
pub(all) struct NoncontextualSubtable {
coverage_flags : Int
sub_feature_flags : UInt
lookup : LookupTable
} derive(Eq, Show, ToJson)
///|
pub(all) struct StateEntry {
new_state : Int
flags : Int
data1 : Int
data2 : Int
} derive(Eq, Show, ToJson)
///|
pub(all) enum ClassTable {
Lookup(LookupTable)
Array(UInt, Array[Int])
} derive(Eq, Show, ToJson)
///|
pub(all) struct StateTable {
n_classes : Int
class_table : ClassTable
state_array : Array[Int]
entries : Array[StateEntry]
state_array_offset : Int
extended : Bool
} derive(Eq, Show, ToJson)
///|
pub(all) struct RearrangementSubtable {
coverage_flags : Int
sub_feature_flags : UInt
table : StateTable
} derive(Eq, Show, ToJson)
///|
pub(all) enum ContextualSubstitutions {
Lookups(Array[LookupTable])
Values(Int, Array[UInt])
} derive(Eq, Show, ToJson)
///|
pub(all) struct ContextualSubtable {
coverage_flags : Int
sub_feature_flags : UInt
table : StateTable
substitutions : ContextualSubstitutions
} derive(Eq, Show, ToJson)
///|
pub(all) struct LigatureSubtable {
coverage_flags : Int
sub_feature_flags : UInt
table : StateTable
lig_actions : Array[UInt]
components : Array[UInt]
ligatures : Array[UInt]
lig_action_offset : Int
component_offset_words : Int
ligature_offset : Int
} derive(Eq, Show, ToJson)
///|
pub(all) struct InsertionSubtable {
coverage_flags : Int
sub_feature_flags : UInt
table : StateTable
insertion_action : Array[UInt]
} derive(Eq, Show, ToJson)
///|
pub(all) enum MorxSubtable {
Noncontextual(NoncontextualSubtable)
Rearrangement(RearrangementSubtable)
Contextual(ContextualSubtable)
Ligature(LigatureSubtable)
Insertion(InsertionSubtable)
} derive(Eq, Show, ToJson)
///|
pub struct MorxTable {
version : Int
subtables : Array[MorxSubtable]
} derive(Eq, Show, ToJson)
///|
pub struct MortTable {
version : Int
subtables : Array[MorxSubtable]
} derive(Eq, Show, ToJson)
fn coverage_flags_from(coverage : UInt, extended : Bool) -> Int {
if extended {
((coverage >> 24) & 0xffU).reinterpret_as_int()
} else {
((coverage >> 8) & 0xffU).reinterpret_as_int()
}
}
fn coverage_type_from(coverage : UInt) -> Int {
(coverage & 0xffU).reinterpret_as_int()
}
fn coverage_allows(flags : Int, horizontal : Bool) -> Bool {
let all_directions = (flags & 0x20) != 0
if all_directions {
return true
}
let vertical = (flags & 0x80) != 0
if horizontal { !vertical } else { vertical }
}
fn NoncontextualSubtable::apply(
self : NoncontextualSubtable,
glyphs : Array[UInt],
horizontal : Bool,
) -> Bool {
if !coverage_allows(self.coverage_flags, horizontal) {
return false
}
let mut changed = false
for i in 0.. ()
Some(value) => {
if value != glyph {
glyphs[i] = value
changed = true
}
}
}
}
changed
}
let rearr_mark_first = 0x8000
let rearr_dont_advance = 0x4000
let rearr_mark_last = 0x2000
let rearr_verb_mask = 0x000f
let rearrange_map : Array[Int] = [
0x00,
0x10,
0x01,
0x11,
0x20,
0x30,
0x02,
0x03,
0x12,
0x13,
0x21,
0x31,
0x22,
0x32,
0x23,
0x33,
]
fn StateTable::class_for(self : StateTable, glyph : UInt) -> Int {
if glyph == 0xffffU {
return 2
}
match self.class_table {
Lookup(lookup) =>
match lookup.value_for(glyph) {
None => 1
Some(value) => {
let klass = value.reinterpret_as_int()
if klass < 0 || klass >= self.n_classes { 1 } else { klass }
}
}
Array(first, classes) => {
let idx = (glyph - first).reinterpret_as_int()
if idx < 0 || idx >= classes.length() {
1
} else {
let klass = classes[idx]
if klass < 0 || klass >= self.n_classes { 1 } else { klass }
}
}
}
}
fn StateTable::entry_for(self : StateTable, state : Int, klass : Int) -> StateEntry {
if self.n_classes <= 0 {
return StateEntry::{ new_state: 0, flags: 0, data1: 0, data2: 0 }
}
let mut safe_state = state
if safe_state < 0 {
safe_state = 0
}
let mut safe_klass = klass
if safe_klass < 0 || safe_klass >= self.n_classes {
safe_klass = 1
}
let index = safe_state * self.n_classes + safe_klass
if index < 0 || index >= self.state_array.length() {
return StateEntry::{ new_state: 0, flags: 0, data1: 0, data2: 0 }
}
let entry_index = self.state_array[index]
if entry_index < 0 || entry_index >= self.entries.length() {
StateEntry::{ new_state: 0, flags: 0, data1: 0, data2: 0 }
} else {
self.entries[entry_index]
}
}
fn StateTable::new_state(self : StateTable, raw : Int) -> Int {
if self.n_classes <= 0 {
return 0
}
if self.extended {
raw
} else {
(raw - self.state_array_offset) / self.n_classes
}
}
fn merge_clusters(clusters : Array[Int], start : Int, end : Int) -> Unit {
if start < 0 || end > clusters.length() || start >= end {
return ()
}
let mut min_cluster = clusters[start]
for i in (start + 1).. Unit {
let total = end - start
if total < l + r || total > 4 {
return ()
}
let buf_glyphs : Array[UInt] = [0U, 0U, 0U, 0U]
let buf_clusters : Array[Int] = [0, 0, 0, 0]
for i in 0.. 0 {
let temp_glyphs : Array[UInt] = []
let temp_clusters : Array[Int] = []
for i in 0..= 2 {
let g = glyphs[end - 1]
glyphs[end - 1] = glyphs[end - 2]
glyphs[end - 2] = g
let c = clusters[end - 1]
clusters[end - 1] = clusters[end - 2]
clusters[end - 2] = c
}
if reverse_r && r >= 2 {
let g = glyphs[start]
glyphs[start] = glyphs[start + 1]
glyphs[start + 1] = g
let c = clusters[start]
clusters[start] = clusters[start + 1]
clusters[start + 1] = c
}
}
fn RearrangementSubtable::apply(
self : RearrangementSubtable,
glyphs : Array[UInt],
clusters : Array[Int],
horizontal : Bool,
) -> Bool {
if !coverage_allows(self.coverage_flags, horizontal) {
return false
}
if glyphs.length() != clusters.length() {
return false
}
let table = self.table
if table.n_classes <= 0 {
return false
}
let mut changed = false
let mut state = 0
let mut idx = 0
let len = glyphs.length()
let mut start = 0
let mut end = 0
while true {
let klass = if idx < len {
table.class_for(glyphs[idx])
} else {
0
}
let entry = table.entry_for(state, klass)
let flags = entry.flags
if (flags & rearr_mark_first) != 0 {
start = idx
}
if (flags & rearr_mark_last) != 0 {
end = if idx + 1 < len { idx + 1 } else { len }
}
let verb = flags & rearr_verb_mask
if verb != 0 && start < end {
let m = rearrange_map[verb]
let l = if (m >> 4) < 2 { m >> 4 } else { 2 }
let r = if (m & 0xf) < 2 { m & 0xf } else { 2 }
let reverse_l = (m >> 4) == 3
let reverse_r = (m & 0xf) == 3
if end - start >= l + r && end - start <= 4 {
merge_clusters(clusters, start, if idx + 1 < len { idx + 1 } else { len })
merge_clusters(clusters, start, end)
rearrange_range(glyphs, clusters, start, end, l, r, reverse_l, reverse_r)
changed = true
}
}
state = table.new_state(entry.new_state)
if idx >= len {
break
}
if (flags & rearr_dont_advance) == 0 {
idx = idx + 1
}
}
changed
}
let contextual_set_mark = 0x8000
let contextual_dont_advance = 0x4000
fn contextual_replacement(
substitutions : ContextualSubstitutions,
index : Int,
glyph : UInt,
) -> UInt? {
match substitutions {
Lookups(lookups) => {
if index < 0 || index >= lookups.length() {
None
} else {
lookups[index].value_for(glyph)
}
}
Values(base_word, values) => {
let glyph_int = glyph.reinterpret_as_int()
let word_index = index + glyph_int
let value_index = word_index - base_word
if value_index < 0 || value_index >= values.length() {
None
} else {
let value = values[value_index]
if value == 0U { None } else { Some(value) }
}
}
}
}
fn ContextualSubtable::apply(
self : ContextualSubtable,
glyphs : Array[UInt],
clusters : Array[Int],
horizontal : Bool,
) -> Bool {
if !coverage_allows(self.coverage_flags, horizontal) {
return false
}
if glyphs.length() != clusters.length() {
return false
}
let table = self.table
if table.n_classes <= 0 {
return false
}
let mut changed = false
let mut state = 0
let mut idx = 0
let len = glyphs.length()
let mut mark = 0
let mut mark_set = false
while true {
let klass = if idx < len {
table.class_for(glyphs[idx])
} else {
0
}
let entry = table.entry_for(state, klass)
let flags = entry.flags
if !(idx >= len && !mark_set) {
if entry.data1 != 0xffff && len > 0 {
if mark >= 0 && mark < len {
match contextual_replacement(self.substitutions, entry.data1, glyphs[mark]) {
None => ()
Some(replacement) => {
if replacement != glyphs[mark] {
glyphs[mark] = replacement
changed = true
}
}
}
}
}
if entry.data2 != 0xffff && len > 0 {
let current_idx = if idx < len { idx } else { len - 1 }
if current_idx >= 0 && current_idx < len {
match contextual_replacement(self.substitutions, entry.data2, glyphs[current_idx]) {
None => ()
Some(replacement) => {
if replacement != glyphs[current_idx] {
glyphs[current_idx] = replacement
changed = true
}
}
}
}
}
if (flags & contextual_set_mark) != 0 {
mark_set = true
mark = if idx < len { idx } else { len }
}
}
state = table.new_state(entry.new_state)
if idx >= len {
break
}
if (flags & contextual_dont_advance) == 0 {
idx = idx + 1
}
}
changed
}
let ligature_set_component = 0x8000
let ligature_dont_advance = 0x4000
let ligature_perform_action = 0x2000
let ligature_offset_mask = 0x3fff
let lig_action_last = 0x80000000U
let lig_action_store = 0x40000000U
let lig_action_offset_mask = 0x3fffffffU
fn sign_extend_30(value : UInt) -> Int {
let mut offset = value & lig_action_offset_mask
if (offset & 0x20000000U) != 0U {
offset = offset | 0xC0000000U
}
offset.reinterpret_as_int()
}
fn shift_positions(values : Array[Int], start : Int, delta : Int) -> Unit {
for i in 0..= start {
values[i] = values[i] + delta
}
}
}
fn LigatureSubtable::apply(
self : LigatureSubtable,
glyphs : Array[UInt],
clusters : Array[Int],
horizontal : Bool,
) -> Bool {
if !coverage_allows(self.coverage_flags, horizontal) {
return false
}
if glyphs.length() != clusters.length() {
return false
}
let table = self.table
if table.n_classes <= 0 {
return false
}
let mut changed = false
let mut state = 0
let mut idx = 0
let match_positions : Array[Int] = []
while true {
let len = glyphs.length()
let klass = if idx < len {
table.class_for(glyphs[idx])
} else {
0
}
let entry = table.entry_for(state, klass)
let flags = entry.flags
if (flags & ligature_set_component) != 0 && idx < len {
if match_positions.length() > 0 {
let last = match_positions[match_positions.length() - 1]
if last == idx {
ignore(match_positions.pop())
}
}
match_positions.push(idx)
}
let perform = if table.extended {
(flags & ligature_perform_action) != 0
} else {
(flags & ligature_offset_mask) != 0
}
if perform {
if match_positions.length() == 0 || idx >= len {
()
} else {
let mut action_index = if table.extended { entry.data1 } else { flags & ligature_offset_mask }
if !table.extended {
action_index = (action_index - self.lig_action_offset) / 4
}
if action_index >= 0 && action_index < self.lig_actions.length() {
let mut cursor = match_positions.length()
let mut ligature_idx = 0
let mut action_pos = action_index
while action_pos < self.lig_actions.length() {
if cursor == 0 {
match_positions.clear()
break
}
cursor = cursor - 1
let comp_pos = match_positions[cursor]
if comp_pos < 0 || comp_pos >= glyphs.length() {
action_pos = action_pos + 1
continue
}
let action = self.lig_actions[action_pos]
let offset = sign_extend_30(action)
let glyph = glyphs[comp_pos].reinterpret_as_int()
let component_base = glyph + offset
let component_index = if table.extended {
component_base
} else {
component_base - self.component_offset_words
}
if component_index < 0 || component_index >= self.components.length() {
break
}
let component_value = self.components[component_index].reinterpret_as_int()
ligature_idx = ligature_idx + component_value
if (action & (lig_action_store | lig_action_last)) != 0U {
let lig_index = if table.extended {
ligature_idx
} else {
(ligature_idx - self.ligature_offset) / 2
}
if lig_index >= 0 && lig_index < self.ligatures.length() {
let lig_glyph = self.ligatures[lig_index]
let mut min_cluster = clusters[comp_pos]
for i in cursor..= 0 && pos < clusters.length() {
let value = clusters[pos]
if value < min_cluster {
min_cluster = value
}
}
}
glyphs[comp_pos] = lig_glyph
clusters[comp_pos] = min_cluster
changed = true
let mut removed = 0
let mut j = match_positions.length() - 1
while j > cursor {
let del_pos = match_positions[j]
if del_pos >= 0 && del_pos < glyphs.length() {
ignore(glyphs.remove(del_pos))
ignore(clusters.remove(del_pos))
shift_positions(match_positions, del_pos, -1)
if del_pos < idx {
idx = idx - 1
}
removed = removed + 1
}
if j == 0 { break }
j = j - 1
}
if removed > 0 {
match_positions.truncate(cursor + 1)
}
}
}
action_pos = action_pos + 1
if (action & lig_action_last) != 0U {
break
}
}
}
}
}
state = table.new_state(entry.new_state)
if idx >= glyphs.length() {
break
}
if (flags & ligature_dont_advance) == 0 {
idx = idx + 1
}
}
changed
}
let insertion_set_mark = 0x8000
let insertion_dont_advance = 0x4000
let _insertion_current_kashida = 0x2000
let _insertion_marked_kashida = 0x1000
let insertion_current_before = 0x0800
let insertion_marked_before = 0x0400
let insertion_current_count = 0x03e0
let insertion_marked_count = 0x001f
fn adjust_index(idx : Int, pos : Int, delta : Int) -> Int {
if idx >= pos { idx + delta } else { idx }
}
fn insert_glyphs_from(
glyphs : Array[UInt],
clusters : Array[Int],
pos : Int,
source : Array[UInt],
start : Int,
count : Int,
cluster_value : Int,
) -> Int {
if count <= 0 {
return 0
}
if start < 0 || start >= source.length() {
return 0
}
let mut actual = count
if start + actual > source.length() {
actual = source.length() - start
}
if actual <= 0 {
return 0
}
let mut insert_pos = pos
if insert_pos < 0 {
insert_pos = 0
}
if insert_pos > glyphs.length() {
insert_pos = glyphs.length()
}
for i in 0.. Bool {
if !coverage_allows(self.coverage_flags, horizontal) {
return false
}
if glyphs.length() != clusters.length() {
return false
}
let table = self.table
if table.n_classes <= 0 {
return false
}
let mut changed = false
let mut state = 0
let mut idx = 0
let mut mark = 0
while true {
let len = glyphs.length()
let klass = if idx < len {
table.class_for(glyphs[idx])
} else {
0
}
let entry = table.entry_for(state, klass)
let flags = entry.flags
let mark_loc = idx
if entry.data2 != 0xffff {
let count = flags & insertion_marked_count
if count > 0 {
let start = entry.data2
let before = (flags & insertion_marked_before) != 0
let mark_cluster = if mark >= 0 && mark < clusters.length() { clusters[mark] } else { 0 }
let insert_pos = if before { mark } else { mark + 1 }
let inserted = insert_glyphs_from(
glyphs,
clusters,
insert_pos,
self.insertion_action,
start,
count,
mark_cluster,
)
if inserted > 0 {
changed = true
idx = adjust_index(idx, insert_pos, inserted)
mark = adjust_index(mark, insert_pos, inserted)
}
}
}
if (flags & insertion_set_mark) != 0 {
mark = mark_loc
}
let mut inserted_before = 0
let mut inserted_after = 0
if entry.data1 != 0xffff {
let count = (flags & insertion_current_count) >> 5
if count > 0 {
let start = entry.data1
let before = (flags & insertion_current_before) != 0
let current_cluster = if idx >= 0 && idx < clusters.length() { clusters[idx] } else { 0 }
let insert_pos = if before { idx } else { idx + 1 }
let inserted = insert_glyphs_from(
glyphs,
clusters,
insert_pos,
self.insertion_action,
start,
count,
current_cluster,
)
if inserted > 0 {
changed = true
if before {
inserted_before = inserted
} else {
inserted_after = inserted
}
idx = adjust_index(idx, insert_pos, inserted)
mark = adjust_index(mark, insert_pos, inserted)
}
}
}
state = table.new_state(entry.new_state)
if idx >= glyphs.length() {
break
}
if (flags & insertion_dont_advance) != 0 {
if inserted_after > 0 {
idx = idx + 1
} else if inserted_before > 0 {
idx = idx - inserted_before
if idx < 0 {
idx = 0
}
}
} else {
idx = idx + 1 + inserted_after
}
}
changed
}
fn read_count(
data : BytesView,
offset : Int,
extended : Bool,
) -> Result[Int, AatError] {
if extended {
read_u32_non_negative(data, offset)
} else {
read_u16_int(data, offset)
}
}
fn read_u8_int(data : BytesView, offset : Int) -> Result[Int, AatError] {
if offset < 0 || offset >= data.length() {
return Err(UnexpectedEof)
}
Ok(data[offset].to_int())
}
fn entry_count_from_state(state_array : Array[Int]) -> Result[Int, AatError] {
let mut max_entry = -1
for value in state_array {
if value < 0 {
return Err(InvalidFormat)
}
if value > max_entry {
max_entry = value
}
}
if max_entry < 0 { Ok(0) } else { Ok(max_entry + 1) }
}
fn parse_state_table(
data : BytesView,
extended : Bool,
num_glyphs : Int,
entry_data_fields : Int,
) -> Result[StateTable, AatError] {
let n_classes = read_count(data, 0, extended)
let class_offset = read_count(data, if extended { 4 } else { 2 }, extended)
let state_offset = read_count(data, if extended { 8 } else { 4 }, extended)
let entry_offset = read_count(data, if extended { 12 } else { 6 }, extended)
match (n_classes, class_offset, state_offset, entry_offset) {
(Err(err), _, _, _) => Err(err)
(_, Err(err), _, _) => Err(err)
(_, _, Err(err), _) => Err(err)
(_, _, _, Err(err)) => Err(err)
(Ok(n_classes), Ok(class_offset), Ok(state_offset), Ok(entry_offset)) => {
if n_classes < 0 {
return Err(InvalidFormat)
}
if class_offset < 0 || state_offset < 0 || entry_offset < 0 {
return Err(InvalidFormat)
}
if class_offset >= data.length() || state_offset >= data.length() || entry_offset > data.length() {
return Err(UnexpectedEof)
}
if state_offset > entry_offset {
return Err(InvalidFormat)
}
let class_table = if extended {
let lookup = parse_lookup(data, class_offset, num_glyphs)
match lookup {
Err(err) => return Err(err)
Ok(lookup) => ClassTable::Lookup(lookup)
}
} else {
let first = read_u16(data, class_offset)
let length = read_u16_int(data, class_offset + 2)
match (first, length) {
(Err(err), _) => return Err(err)
(_, Err(err)) => return Err(err)
(Ok(first), Ok(length)) => {
if length < 0 {
return Err(InvalidFormat)
}
let array_start = class_offset + 4
if array_start < 0 || array_start + length > data.length() {
return Err(UnexpectedEof)
}
let classes : Array[Int] = []
for i in 0.. return Err(err)
Ok(value) => classes.push(value)
}
}
ClassTable::Array(first, classes)
}
}
}
let element_size = if extended { 2 } else { 1 }
let state_bytes = entry_offset - state_offset
if state_bytes < 0 || state_bytes % element_size != 0 {
return Err(InvalidFormat)
}
let state_count = state_bytes / element_size
let state_array : Array[Int] = []
for i in 0.. return Err(err)
Ok(value) => state_array.push(value)
}
}
let entry_bytes = data.length() - entry_offset
if entry_bytes < 0 {
return Err(InvalidFormat)
}
if entry_data_fields < 0 || entry_data_fields > 2 {
return Err(InvalidFormat)
}
let entry_size = 4 + entry_data_fields * 2
if entry_size <= 0 {
return Err(InvalidFormat)
}
let entry_count = match entry_count_from_state(state_array) {
Err(err) => return Err(err)
Ok(value) => value
}
if entry_offset < 0 || entry_offset + entry_count * entry_size > data.length() {
return Err(UnexpectedEof)
}
let entries : Array[StateEntry] = []
for i in 0..= 1 { read_u16_int(data, base + 4) } else { Ok(0) }
let data2 = if entry_data_fields >= 2 { read_u16_int(data, base + 6) } else { Ok(0) }
match (new_state, flags, data1, data2) {
(Err(err), _, _, _) => return Err(err)
(_, Err(err), _, _) => return Err(err)
(_, _, Err(err), _) => return Err(err)
(_, _, _, Err(err)) => return Err(err)
(Ok(new_state), Ok(flags), Ok(data1), Ok(data2)) =>
entries.push(StateEntry::{ new_state, flags, data1, data2 })
}
}
Ok(StateTable::{
n_classes,
class_table,
state_array,
entries,
state_array_offset: state_offset,
extended,
})
}
}
}
fn parse_contextual_subtable(
data : BytesView,
extended : Bool,
num_glyphs : Int,
coverage_flags : Int,
sub_feature_flags : UInt,
) -> Result[MorxSubtable, AatError] {
let table = match parse_state_table(data, extended, num_glyphs, 2) {
Err(err) => return Err(err)
Ok(value) => value
}
let header_size = if extended { 16 } else { 8 }
let subs_offset = read_count(data, header_size, extended)
let subs_offset = match subs_offset {
Err(err) => return Err(err)
Ok(value) => value
}
if subs_offset < 0 || subs_offset >= data.length() {
return Err(UnexpectedEof)
}
let substitutions = if extended {
let mut max_index = -1
for entry in table.entries {
if entry.data1 != 0xffff && entry.data1 > max_index {
max_index = entry.data1
}
if entry.data2 != 0xffff && entry.data2 > max_index {
max_index = entry.data2
}
}
let count = if max_index < 0 { 0 } else { max_index + 1 }
let lookups : Array[LookupTable] = []
for i in 0.. return Err(err)
Ok(offset) => {
let lookup_offset = subs_offset + offset
if lookup_offset < 0 || lookup_offset >= data.length() {
return Err(UnexpectedEof)
}
let lookup = match parse_lookup(data, lookup_offset, num_glyphs) {
Err(err) => return Err(err)
Ok(value) => value
}
lookups.push(lookup)
}
}
}
ContextualSubstitutions::Lookups(lookups)
} else {
if (subs_offset & 1) != 0 {
return Err(InvalidFormat)
}
let bytes_len = data.length() - subs_offset
if bytes_len < 0 || (bytes_len & 1) != 0 {
return Err(InvalidFormat)
}
let count = bytes_len / 2
let values : Array[UInt] = []
for i in 0.. return Err(err)
Ok(value) => values.push(value)
}
}
ContextualSubstitutions::Values(subs_offset / 2, values)
}
Ok(
MorxSubtable::Contextual(
ContextualSubtable::{
coverage_flags,
sub_feature_flags,
table,
substitutions,
},
),
)
}
fn parse_ligature_subtable(
data : BytesView,
extended : Bool,
num_glyphs : Int,
coverage_flags : Int,
sub_feature_flags : UInt,
) -> Result[MorxSubtable, AatError] {
let table = match parse_state_table(data, extended, num_glyphs, if extended { 1 } else { 0 }) {
Err(err) => return Err(err)
Ok(value) => value
}
let header_size = if extended { 16 } else { 8 }
let lig_action_offset = read_count(data, header_size, extended)
let component_offset = read_count(data, header_size + (if extended { 4 } else { 2 }), extended)
let ligature_offset = read_count(data, header_size + (if extended { 8 } else { 4 }), extended)
let (lig_action_offset, component_offset, ligature_offset) =
match (lig_action_offset, component_offset, ligature_offset) {
(Err(err), _, _) => return Err(err)
(_, Err(err), _) => return Err(err)
(_, _, Err(err)) => return Err(err)
(Ok(lig_action_offset), Ok(component_offset), Ok(ligature_offset)) =>
(lig_action_offset, component_offset, ligature_offset)
}
if lig_action_offset <= 0 || component_offset <= 0 || ligature_offset <= 0 {
return Err(InvalidFormat)
}
if lig_action_offset >= data.length() ||
component_offset >= data.length() ||
ligature_offset >= data.length() {
return Err(UnexpectedEof)
}
if (component_offset & 1) != 0 {
return Err(InvalidFormat)
}
let mut lig_action_end = data.length()
if component_offset > lig_action_offset && component_offset < lig_action_end {
lig_action_end = component_offset
}
if ligature_offset > lig_action_offset && ligature_offset < lig_action_end {
lig_action_end = ligature_offset
}
if lig_action_end <= lig_action_offset {
return Err(InvalidFormat)
}
let mut component_end = data.length()
if lig_action_offset > component_offset && lig_action_offset < component_end {
component_end = lig_action_offset
}
if ligature_offset > component_offset && ligature_offset < component_end {
component_end = ligature_offset
}
if component_end <= component_offset {
return Err(InvalidFormat)
}
let mut ligature_end = data.length()
if lig_action_offset > ligature_offset && lig_action_offset < ligature_end {
ligature_end = lig_action_offset
}
if component_offset > ligature_offset && component_offset < ligature_end {
ligature_end = component_offset
}
if ligature_end <= ligature_offset {
return Err(InvalidFormat)
}
let lig_action_bytes = lig_action_end - lig_action_offset
if lig_action_bytes < 0 || (lig_action_bytes & 3) != 0 {
return Err(InvalidFormat)
}
let lig_action_count = lig_action_bytes / 4
let lig_actions : Array[UInt] = []
for i in 0.. return Err(err)
Ok(value) => lig_actions.push(value)
}
}
let component_bytes = component_end - component_offset
if component_bytes < 0 || (component_bytes & 1) != 0 {
return Err(InvalidFormat)
}
let component_count = component_bytes / 2
let components : Array[UInt] = []
for i in 0.. return Err(err)
Ok(value) => components.push(value)
}
}
let ligature_bytes = ligature_end - ligature_offset
if ligature_bytes < 0 || (ligature_bytes & 1) != 0 {
return Err(InvalidFormat)
}
let ligature_count = ligature_bytes / 2
let ligatures : Array[UInt] = []
for i in 0.. return Err(err)
Ok(value) => ligatures.push(value)
}
}
Ok(
MorxSubtable::Ligature(
LigatureSubtable::{
coverage_flags,
sub_feature_flags,
table,
lig_actions,
components,
ligatures,
lig_action_offset,
component_offset_words: component_offset / 2,
ligature_offset,
},
),
)
}
fn parse_insertion_subtable(
data : BytesView,
extended : Bool,
num_glyphs : Int,
coverage_flags : Int,
sub_feature_flags : UInt,
) -> Result[MorxSubtable, AatError] {
let table = match parse_state_table(data, extended, num_glyphs, 2) {
Err(err) => return Err(err)
Ok(value) => value
}
let header_size = if extended { 16 } else { 8 }
let insertion_offset = read_count(data, header_size, extended)
let insertion_offset = match insertion_offset {
Err(err) => return Err(err)
Ok(value) => value
}
if insertion_offset < 0 || insertion_offset >= data.length() {
return Err(UnexpectedEof)
}
if (insertion_offset & 1) != 0 {
return Err(InvalidFormat)
}
let bytes_len = data.length() - insertion_offset
if bytes_len < 0 || (bytes_len & 1) != 0 {
return Err(InvalidFormat)
}
let count = bytes_len / 2
let insertion_action : Array[UInt] = []
for i in 0.. return Err(err)
Ok(value) => insertion_action.push(value)
}
}
Ok(
MorxSubtable::Insertion(
InsertionSubtable::{
coverage_flags,
sub_feature_flags,
table,
insertion_action,
},
),
)
}
fn parse_chain(
data : BytesView,
offset : Int,
extended : Bool,
num_glyphs : Int,
) -> Result[(Array[MorxSubtable], Int), AatError] {
let default_flags = read_u32_int(data, offset)
let length = read_u32_non_negative(data, offset + 4)
match (default_flags, length) {
(Err(err), _) => Err(err)
(_, Err(err)) => Err(err)
(Ok(_), Ok(length)) => {
let header_size = if extended { 16 } else { 12 }
if length < header_size {
return Err(InvalidFormat)
}
if offset < 0 || offset + length > data.length() {
return Err(UnexpectedEof)
}
let feature_count = read_count(data, offset + 8, extended)
let subtable_count = if extended {
read_count(data, offset + 12, extended)
} else {
read_count(data, offset + 10, extended)
}
match (feature_count, subtable_count) {
(Err(err), _) => Err(err)
(_, Err(err)) => Err(err)
(Ok(feature_count), Ok(subtable_count)) => {
if feature_count < 0 || subtable_count < 0 {
return Err(InvalidFormat)
}
let feature_bytes = feature_count * 12
let mut subtable_offset = offset + header_size + feature_bytes
if subtable_offset < 0 || subtable_offset > offset + length {
return Err(UnexpectedEof)
}
let subtables : Array[MorxSubtable] = []
for _ in 0.. return Err(err)
(_, Err(err), _) => return Err(err)
(_, _, Err(err)) => return Err(err)
(Ok(sub_length), Ok(coverage), Ok(sub_feature_flags)) => {
let header_size = if extended { 12 } else { 8 }
if sub_length < header_size {
return Err(InvalidFormat)
}
let sub_end = subtable_offset + sub_length
if subtable_offset < 0 || sub_end > offset + length || sub_end > data.length() {
return Err(UnexpectedEof)
}
let coverage_u = coverage.reinterpret_as_uint()
let sub_type = coverage_type_from(coverage_u)
let flags = coverage_flags_from(coverage_u, extended)
if sub_type == subtable_type_noncontextual {
let sub_data = data[subtable_offset + header_size:sub_end]
let lookup = parse_lookup(sub_data, 0, num_glyphs)
match lookup {
Err(err) => return Err(err)
Ok(lookup) => {
subtables.push(
MorxSubtable::Noncontextual(
NoncontextualSubtable::{
coverage_flags: flags,
sub_feature_flags,
lookup,
},
),
)
}
}
} else if sub_type == subtable_type_rearrangement {
let sub_data = data[subtable_offset + header_size:sub_end]
let table = parse_state_table(sub_data, extended, num_glyphs, 0)
match table {
Err(err) => return Err(err)
Ok(table) =>
subtables.push(
MorxSubtable::Rearrangement(
RearrangementSubtable::{
coverage_flags: flags,
sub_feature_flags,
table,
},
),
)
}
} else if sub_type == subtable_type_contextual {
let sub_data = data[subtable_offset + header_size:sub_end]
let contextual = parse_contextual_subtable(
sub_data,
extended,
num_glyphs,
flags,
sub_feature_flags,
)
match contextual {
Err(err) => return Err(err)
Ok(contextual) => subtables.push(contextual)
}
} else if sub_type == subtable_type_ligature {
let sub_data = data[subtable_offset + header_size:sub_end]
let ligature = parse_ligature_subtable(
sub_data,
extended,
num_glyphs,
flags,
sub_feature_flags,
)
match ligature {
Err(err) => return Err(err)
Ok(ligature) => subtables.push(ligature)
}
} else if sub_type == subtable_type_insertion {
let sub_data = data[subtable_offset + header_size:sub_end]
let insertion = parse_insertion_subtable(
sub_data,
extended,
num_glyphs,
flags,
sub_feature_flags,
)
match insertion {
Err(err) => return Err(err)
Ok(insertion) => subtables.push(insertion)
}
}
subtable_offset = sub_end
}
}
}
Ok((subtables, length))
}
}
}
}
}
fn parse_mort_morx(
data : BytesView,
extended : Bool,
num_glyphs : Int,
) -> Result[(Int, Array[MorxSubtable]), AatError] {
let version = read_u16_int(data, 0)
let chain_count = read_u32_non_negative(data, 4)
match (version, chain_count) {
(Err(err), _) => Err(err)
(_, Err(err)) => Err(err)
(Ok(version), Ok(chain_count)) => {
let subtables : Array[MorxSubtable] = []
let mut chain_offset = 8
for _ in 0.. return Err(err)
Ok((chain_subtables, length)) => {
for subtable in chain_subtables {
subtables.push(subtable)
}
chain_offset = chain_offset + length
}
}
}
Ok((version, subtables))
}
}
}
///|
pub fn MorxTable::parse(
data : BytesView,
num_glyphs : Int,
) -> Result[MorxTable, AatError] {
match parse_mort_morx(data, true, num_glyphs) {
Err(err) => Err(err)
Ok((version, subtables)) => Ok(MorxTable::{ version, subtables })
}
}
///|
pub fn MortTable::parse(
data : BytesView,
num_glyphs : Int,
) -> Result[MortTable, AatError] {
match parse_mort_morx(data, false, num_glyphs) {
Err(err) => Err(err)
Ok((version, subtables)) => Ok(MortTable::{ version, subtables })
}
}
///|
pub fn MorxTable::has_data(self : MorxTable) -> Bool {
self.version != 0
}
///|
pub fn MortTable::has_data(self : MortTable) -> Bool {
self.version != 0
}
fn MorxSubtable::apply(
self : MorxSubtable,
glyphs : Array[UInt],
clusters : Array[Int],
horizontal : Bool,
) -> Bool {
match self {
Noncontextual(subtable) => subtable.apply(glyphs, horizontal)
Rearrangement(subtable) => subtable.apply(glyphs, clusters, horizontal)
Contextual(subtable) => subtable.apply(glyphs, clusters, horizontal)
Ligature(subtable) => subtable.apply(glyphs, clusters, horizontal)
Insertion(subtable) => subtable.apply(glyphs, clusters, horizontal)
}
}
///|
pub fn MorxTable::apply(
self : MorxTable,
glyphs : Array[UInt],
clusters : Array[Int],
horizontal : Bool,
) -> Bool {
let mut changed = false
for subtable in self.subtables {
if subtable.apply(glyphs, clusters, horizontal) {
changed = true
}
}
changed
}
///|
pub fn MortTable::apply(
self : MortTable,
glyphs : Array[UInt],
clusters : Array[Int],
horizontal : Bool,
) -> Bool {
let mut changed = false
for subtable in self.subtables {
if subtable.apply(glyphs, clusters, horizontal) {
changed = true
}
}
changed
}