// 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.
///|
/// STAT design axis record.
pub struct StatAxisRecord {
tag : @common.Tag
name_id : Int
ordering : Int
} derive(Eq, Show, ToJson)
///|
/// Axis value record used by format 4.
pub struct AxisValueRecord {
axis_index : Int
value : Int
} derive(Eq, Show, ToJson)
///|
/// Axis value table formats.
pub enum AxisValue {
Format1(Int, Int, Int, Int)
Format2(Int, Int, Int, Int, Int, Int)
Format3(Int, Int, Int, Int, Int)
Format4(Int, Int, Array[AxisValueRecord])
} derive(Show, ToJson)
///|
/// Parsed STAT table.
pub struct StatTable {
version : Int
design_axis_size : Int
design_axes : Array[StatAxisRecord]
axis_values : Array[AxisValue]
elided_fallback_name_id : Int
} derive(Show, ToJson)
///|
fn read_fixed_16_16(data : BytesView, offset : Int) -> Result[Int, SfntError] {
match read_u32(data, offset) {
Err(err) => Err(err)
Ok(value) => Ok(value.reinterpret_as_int())
}
}
///|
fn parse_axis_value(
data : BytesView,
offset : Int,
) -> Result[AxisValue, SfntError] {
let format = read_u16_int(data, offset)
match format {
Err(err) => Err(err)
Ok(format) => {
if format == 1 {
let axis_index = read_u16_int(data, offset + 2)
let flags = read_u16_int(data, offset + 4)
let value_name_id = read_u16_int(data, offset + 6)
let value = read_fixed_16_16(data, offset + 8)
match (axis_index, flags, value_name_id, value) {
(Err(err), _, _, _) => Err(err)
(_, Err(err), _, _) => Err(err)
(_, _, Err(err), _) => Err(err)
(_, _, _, Err(err)) => Err(err)
(Ok(axis_index), Ok(flags), Ok(value_name_id), Ok(value)) =>
Ok(Format1(axis_index, flags, value_name_id, value))
}
} else if format == 2 {
let axis_index = read_u16_int(data, offset + 2)
let flags = read_u16_int(data, offset + 4)
let value_name_id = read_u16_int(data, offset + 6)
let nominal_value = read_fixed_16_16(data, offset + 8)
let range_min = read_fixed_16_16(data, offset + 12)
let range_max = read_fixed_16_16(data, offset + 16)
match (
axis_index,
flags,
value_name_id,
nominal_value,
range_min,
range_max,
) {
(Err(err), _, _, _, _, _) => Err(err)
(_, Err(err), _, _, _, _) => Err(err)
(_, _, Err(err), _, _, _) => Err(err)
(_, _, _, Err(err), _, _) => Err(err)
(_, _, _, _, Err(err), _) => Err(err)
(_, _, _, _, _, Err(err)) => Err(err)
(
Ok(axis_index),
Ok(flags),
Ok(value_name_id),
Ok(nominal_value),
Ok(range_min),
Ok(range_max),
) => Ok(Format2(
axis_index,
flags,
value_name_id,
nominal_value,
range_min,
range_max,
))
}
} else if format == 3 {
let axis_index = read_u16_int(data, offset + 2)
let flags = read_u16_int(data, offset + 4)
let value_name_id = read_u16_int(data, offset + 6)
let value = read_fixed_16_16(data, offset + 8)
let linked_value = read_fixed_16_16(data, offset + 12)
match (axis_index, flags, value_name_id, value, linked_value) {
(Err(err), _, _, _, _) => Err(err)
(_, Err(err), _, _, _) => Err(err)
(_, _, Err(err), _, _) => Err(err)
(_, _, _, Err(err), _) => Err(err)
(_, _, _, _, Err(err)) => Err(err)
(Ok(axis_index), Ok(flags), Ok(value_name_id), Ok(value), Ok(linked_value)) =>
Ok(Format3(axis_index, flags, value_name_id, value, linked_value))
}
} else if format == 4 {
let axis_count = read_u16_int(data, offset + 2)
let flags = read_u16_int(data, offset + 4)
let value_name_id = read_u16_int(data, offset + 6)
match (axis_count, flags, value_name_id) {
(Err(err), _, _) => Err(err)
(_, Err(err), _) => Err(err)
(_, _, Err(err)) => Err(err)
(Ok(axis_count), Ok(flags), Ok(value_name_id)) => {
if axis_count < 0 {
return Err(InvalidFormat)
}
let axis_values : Array[AxisValueRecord] = []
let mut cursor = offset + 8
if cursor + axis_count * 6 > data.length() {
return Err(UnexpectedEof)
}
for _ in 0.. return Err(err)
(_, Err(err)) => return Err(err)
(Ok(axis_index), Ok(value)) =>
axis_values.push(AxisValueRecord::{ axis_index, value })
}
cursor = cursor + 6
}
Ok(Format4(flags, value_name_id, axis_values))
}
}
} else {
Err(InvalidFormat)
}
}
}
}
///|
/// Parse STAT table bytes (formats 1-4).
pub fn StatTable::parse(data : BytesView) -> Result[StatTable, SfntError] {
let version = read_u32_int(data, 0)
let design_axis_size = read_u16_int(data, 4)
let design_axis_count = read_u16_int(data, 6)
let design_axes_offset = read_u32_int(data, 8)
let axis_value_count = read_u16_int(data, 12)
let axis_value_offset = read_u32_int(data, 14)
let elided_fallback_name_id = read_u16_int(data, 18)
match (
version,
design_axis_size,
design_axis_count,
design_axes_offset,
axis_value_count,
axis_value_offset,
elided_fallback_name_id,
) {
(Err(err), _, _, _, _, _, _) => Err(err)
(_, Err(err), _, _, _, _, _) => Err(err)
(_, _, Err(err), _, _, _, _) => Err(err)
(_, _, _, Err(err), _, _, _) => Err(err)
(_, _, _, _, Err(err), _, _) => Err(err)
(_, _, _, _, _, Err(err), _) => Err(err)
(_, _, _, _, _, _, Err(err)) => Err(err)
(
Ok(version),
Ok(design_axis_size),
Ok(design_axis_count),
Ok(design_axes_offset),
Ok(axis_value_count),
Ok(axis_value_offset),
Ok(elided_fallback_name_id),
) => {
if design_axis_size < 8 || design_axis_count < 0 || axis_value_count < 0 {
return Err(InvalidFormat)
}
let table_len = data.length()
let design_axes : Array[StatAxisRecord] = []
if design_axis_count > 0 {
if design_axes_offset < 0 {
return Err(InvalidFormat)
}
let required = design_axes_offset + design_axis_count * design_axis_size
if required > table_len {
return Err(UnexpectedEof)
}
for i in 0.. return Err(err)
(_, Err(err)) => return Err(err)
(Ok(name_id), Ok(ordering)) =>
design_axes.push(StatAxisRecord::{ tag, name_id, ordering })
}
}
}
let axis_values : Array[AxisValue] = []
if axis_value_count > 0 {
if axis_value_offset < 0 {
return Err(InvalidFormat)
}
let offsets_end = axis_value_offset + axis_value_count * 2
if offsets_end > table_len {
return Err(UnexpectedEof)
}
for i in 0.. return Err(err)
Ok(offset) => {
if offset <= 0 {
continue
}
let abs_offset = axis_value_offset + offset
if abs_offset + 2 > table_len {
return Err(UnexpectedEof)
}
let value = match parse_axis_value(data, abs_offset) {
Err(err) => return Err(err)
Ok(value) => value
}
axis_values.push(value)
}
}
}
}
Ok(StatTable::{
version,
design_axis_size,
design_axes,
axis_values,
elided_fallback_name_id,
})
}
}
}