// 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.
///|
/// Justification modification list.
pub struct JstfModList {
indices : Array[Int]
} derive(Show, ToJson)
///|
/// Justification max table (offsets to GPOS lookups).
pub struct JstfMax {
lookup_offsets : Array[Int]
} derive(Show, ToJson)
///|
/// Justification priority table.
pub struct JstfPriority {
shrinkage_enable_gsub : JstfModList?
shrinkage_disable_gsub : JstfModList?
shrinkage_enable_gpos : JstfModList?
shrinkage_disable_gpos : JstfModList?
shrinkage_jstf_max : JstfMax?
extension_enable_gsub : JstfModList?
extension_disable_gsub : JstfModList?
extension_enable_gpos : JstfModList?
extension_disable_gpos : JstfModList?
extension_jstf_max : JstfMax?
} derive(Show, ToJson)
///|
/// Justification language system table.
pub struct JstfLangSys {
priorities : Array[JstfPriority?]
} derive(Show, ToJson)
///|
/// Justification language system record.
pub struct JstfLangSysRecord {
tag : @common.Tag
lang_sys : JstfLangSys?
} derive(Show, ToJson)
///|
/// Justification script table.
pub struct JstfScript {
extender_glyphs : Array[Int]
default_lang_sys : JstfLangSys?
lang_sys_records : Array[JstfLangSysRecord]
} derive(Show, ToJson)
///|
/// Justification script record.
pub struct JstfScriptRecord {
tag : @common.Tag
script : JstfScript?
} derive(Show, ToJson)
///|
/// Parsed JSTF table.
pub struct JstfTable {
major_version : Int
minor_version : Int
scripts : Array[JstfScriptRecord]
} derive(Show, ToJson)
///|
fn parse_jstf_mod_list(data : BytesView, offset : Int) -> Result[JstfModList, SfntError] {
let count = read_u16_int(data, offset)
match count {
Err(err) => Err(err)
Ok(count) => {
if count < 0 {
return Err(InvalidFormat)
}
let indices : Array[Int] = []
let base = offset + 2
if base + count * 2 > data.length() {
return Err(UnexpectedEof)
}
for i in 0.. return Err(err)
Ok(index) => indices.push(index)
}
}
Ok(JstfModList::{ indices })
}
}
}
///|
fn parse_jstf_max(data : BytesView, offset : Int) -> Result[JstfMax, SfntError] {
let count = read_u16_int(data, offset)
match count {
Err(err) => Err(err)
Ok(count) => {
if count < 0 {
return Err(InvalidFormat)
}
let offsets : Array[Int] = []
let base = offset + 2
if base + count * 2 > data.length() {
return Err(UnexpectedEof)
}
for i in 0.. return Err(err)
Ok(value) => offsets.push(value)
}
}
Ok(JstfMax::{ lookup_offsets: offsets })
}
}
}
///|
fn parse_jstf_mod_optional(
data : BytesView,
base : Int,
value : Int,
) -> Result[JstfModList?, SfntError] {
if value < 0 {
return Err(InvalidFormat)
}
if value == 0 {
Ok(None)
} else {
match parse_jstf_mod_list(data, base + value) {
Err(err) => Err(err)
Ok(list) => Ok(Some(list))
}
}
}
///|
fn parse_jstf_max_optional(
data : BytesView,
base : Int,
value : Int,
) -> Result[JstfMax?, SfntError] {
if value < 0 {
return Err(InvalidFormat)
}
if value == 0 {
Ok(None)
} else {
match parse_jstf_max(data, base + value) {
Err(err) => Err(err)
Ok(list) => Ok(Some(list))
}
}
}
///|
fn parse_jstf_priority(data : BytesView, offset : Int) -> Result[JstfPriority, SfntError] {
let offsets : Array[Int] = []
for i in 0..<10 {
let value = read_u16_int(data, offset + i * 2)
match value {
Err(err) => return Err(err)
Ok(value) => offsets.push(value)
}
}
let shrinkage_enable_gsub = match parse_jstf_mod_optional(data, offset, offsets[0]) {
Err(err) => return Err(err)
Ok(value) => value
}
let shrinkage_disable_gsub = match parse_jstf_mod_optional(data, offset, offsets[1]) {
Err(err) => return Err(err)
Ok(value) => value
}
let shrinkage_enable_gpos = match parse_jstf_mod_optional(data, offset, offsets[2]) {
Err(err) => return Err(err)
Ok(value) => value
}
let shrinkage_disable_gpos = match parse_jstf_mod_optional(data, offset, offsets[3]) {
Err(err) => return Err(err)
Ok(value) => value
}
let shrinkage_jstf_max = match parse_jstf_max_optional(data, offset, offsets[4]) {
Err(err) => return Err(err)
Ok(value) => value
}
let extension_enable_gsub = match parse_jstf_mod_optional(data, offset, offsets[5]) {
Err(err) => return Err(err)
Ok(value) => value
}
let extension_disable_gsub = match parse_jstf_mod_optional(data, offset, offsets[6]) {
Err(err) => return Err(err)
Ok(value) => value
}
let extension_enable_gpos = match parse_jstf_mod_optional(data, offset, offsets[7]) {
Err(err) => return Err(err)
Ok(value) => value
}
let extension_disable_gpos = match parse_jstf_mod_optional(data, offset, offsets[8]) {
Err(err) => return Err(err)
Ok(value) => value
}
let extension_jstf_max = match parse_jstf_max_optional(data, offset, offsets[9]) {
Err(err) => return Err(err)
Ok(value) => value
}
Ok(JstfPriority::{
shrinkage_enable_gsub,
shrinkage_disable_gsub,
shrinkage_enable_gpos,
shrinkage_disable_gpos,
shrinkage_jstf_max,
extension_enable_gsub,
extension_disable_gsub,
extension_enable_gpos,
extension_disable_gpos,
extension_jstf_max,
})
}
///|
fn parse_jstf_lang_sys(data : BytesView, offset : Int) -> Result[JstfLangSys, SfntError] {
let count = read_u16_int(data, offset)
match count {
Err(err) => Err(err)
Ok(count) => {
if count < 0 {
return Err(InvalidFormat)
}
let priorities : Array[JstfPriority?] = []
let base = offset + 2
if base + count * 2 > data.length() {
return Err(UnexpectedEof)
}
for i in 0.. return Err(err)
Ok(value) => {
if value < 0 {
return Err(InvalidFormat)
}
if value == 0 {
priorities.push(None)
} else {
let priority = match parse_jstf_priority(data, offset + value) {
Err(err) => return Err(err)
Ok(value) => value
}
priorities.push(Some(priority))
}
}
}
}
Ok(JstfLangSys::{ priorities })
}
}
}
///|
fn parse_extender_glyphs(data : BytesView, offset : Int) -> Result[Array[Int], SfntError] {
let count = read_u16_int(data, offset)
match count {
Err(err) => Err(err)
Ok(count) => {
if count < 0 {
return Err(InvalidFormat)
}
let glyphs : Array[Int] = []
let base = offset + 2
if base + count * 2 > data.length() {
return Err(UnexpectedEof)
}
for i in 0.. return Err(err)
Ok(glyph) => glyphs.push(glyph)
}
}
Ok(glyphs)
}
}
}
///|
fn parse_jstf_lang_sys_record(
data : BytesView,
offset : Int,
base : Int,
) -> Result[JstfLangSysRecord, SfntError] {
if offset < 0 || offset + 6 > data.length() {
return Err(UnexpectedEof)
}
let tag = @common.Tag::from_bytes(
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
)
let lang_sys_offset = read_u16_int(data, offset + 4)
match lang_sys_offset {
Err(err) => Err(err)
Ok(lang_sys_offset) => {
if lang_sys_offset < 0 {
return Err(InvalidFormat)
}
let lang_sys = if lang_sys_offset == 0 {
None
} else {
Some(match parse_jstf_lang_sys(data, base + lang_sys_offset) {
Err(err) => return Err(err)
Ok(value) => value
})
}
Ok(JstfLangSysRecord::{ tag, lang_sys })
}
}
}
///|
fn parse_jstf_script(data : BytesView, offset : Int) -> Result[JstfScript, SfntError] {
let extender_offset = read_u16_int(data, offset)
let default_lang_sys_offset = read_u16_int(data, offset + 2)
let count = read_u16_int(data, offset + 4)
match (extender_offset, default_lang_sys_offset, count) {
(Err(err), _, _) => Err(err)
(_, Err(err), _) => Err(err)
(_, _, Err(err)) => Err(err)
(Ok(extender_offset), Ok(default_lang_sys_offset), Ok(count)) => {
if extender_offset < 0 || default_lang_sys_offset < 0 || count < 0 {
return Err(InvalidFormat)
}
let extender_glyphs : Array[Int] = if extender_offset == 0 {
[]
} else {
match parse_extender_glyphs(data, offset + extender_offset) {
Err(err) => return Err(err)
Ok(value) => value
}
}
let default_lang_sys = if default_lang_sys_offset == 0 {
None
} else {
Some(match parse_jstf_lang_sys(data, offset + default_lang_sys_offset) {
Err(err) => return Err(err)
Ok(value) => value
})
}
let lang_sys_records : Array[JstfLangSysRecord] = []
let record_base = offset + 6
if record_base + count * 6 > data.length() {
return Err(UnexpectedEof)
}
for i in 0.. return Err(err)
Ok(value) => value
}
lang_sys_records.push(record)
}
Ok(JstfScript::{ extender_glyphs, default_lang_sys, lang_sys_records })
}
}
}
///|
/// Parse JSTF table bytes.
pub fn JstfTable::parse(data : BytesView) -> Result[JstfTable, SfntError] {
if data.length() < 6 {
return Err(UnexpectedEof)
}
let version = read_u32_int(data, 0)
let count = read_u16_int(data, 4)
match (version, count) {
(Err(err), _) => Err(err)
(_, Err(err)) => Err(err)
(Ok(version), Ok(count)) => {
if count < 0 {
return Err(InvalidFormat)
}
let major = version >> 16
let minor = version & 0xffff
let scripts : Array[JstfScriptRecord] = []
let record_base = 6
if record_base + count * 6 > data.length() {
return Err(UnexpectedEof)
}
for i in 0.. return Err(err)
Ok(script_offset) => {
if script_offset < 0 {
return Err(InvalidFormat)
}
let script = if script_offset == 0 {
None
} else {
Some(match parse_jstf_script(data, script_offset) {
Err(err) => return Err(err)
Ok(value) => value
})
}
scripts.push(JstfScriptRecord::{ tag, script })
}
}
}
Ok(JstfTable::{ major_version: major, minor_version: minor, scripts })
}
}
}