// 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.
///|
pub struct AxisRecord {
tag : @common.Tag
min_value : Double
default_value : Double
max_value : Double
flags : Int
name_id : Int
} derive(Eq, Show, ToJson)
///|
pub struct InstanceRecord {
subfamily_name_id : Int
flags : Int
coordinates : Array[Double]
postscript_name_id : Int?
} derive(Eq, Show, ToJson)
///|
pub struct FvarTable {
axes : Array[AxisRecord]
instances : Array[InstanceRecord]
} derive(Eq, Show, ToJson)
///|
pub fn FvarTable::axes(self : FvarTable) -> ArrayView[AxisRecord] {
self.axes[:]
}
///|
pub fn FvarTable::instances(self : FvarTable) -> ArrayView[InstanceRecord] {
self.instances[:]
}
///|
pub fn AxisRecord::normalize_value(self : AxisRecord, value : Double) -> Double {
let min_value = self.min_value
let default_value = self.default_value
let max_value = self.max_value
let v = clamp_double(value, min_value, max_value)
if v == default_value {
0.0
} else if v < default_value {
let denom = default_value - min_value
if denom == 0.0 { 0.0 } else { (v - default_value) / denom }
} else {
let denom = max_value - default_value
if denom == 0.0 { 0.0 } else { (v - default_value) / denom }
}
}
///|
pub fn FvarTable::normalize_axis_value(
self : FvarTable,
axis_index : Int,
value : Double,
) -> Double {
if axis_index < 0 || axis_index >= self.axes.length() {
return 0.0
}
self.axes[axis_index].normalize_value(value)
}
///|
pub fn FvarTable::axis_index(
self : FvarTable,
tag : @common.Tag,
) -> Int? {
for i in 0.. Array[Int] {
let axis_count = self.axes.length()
let out : Array[Int] = Array::make(axis_count, 0)
for i in 0.. Result[FvarTable, VarError] {
let version = read_u32(data, 0)
let axis_offset = read_u16_int(data, 4)
let axis_count = read_u16_int(data, 8)
let axis_size = read_u16_int(data, 10)
let instance_count = read_u16_int(data, 12)
let instance_size = read_u16_int(data, 14)
match (version, axis_offset, axis_count, axis_size, instance_count, instance_size) {
(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(axis_offset), Ok(axis_count), Ok(axis_size), Ok(instance_count), Ok(instance_size)) => {
let major = (version >> 16) & 0xFFFFU
if major != 1U {
return Err(InvalidFormat)
}
if axis_offset < 16 {
return Err(InvalidFormat)
}
if axis_size < 20 {
return Err(InvalidFormat)
}
let axis_bytes = axis_count * axis_size
if axis_offset < 0 || axis_offset + axis_bytes > data.length() {
return Err(UnexpectedEof)
}
if instance_size < axis_count * 4 + 4 {
return Err(InvalidFormat)
}
let axes : Array[AxisRecord] = []
for i in 0.. return Err(err)
(_, Err(err), _, _, _, _) => return Err(err)
(_, _, Err(err), _, _, _) => return Err(err)
(_, _, _, Err(err), _, _) => return Err(err)
(_, _, _, _, Err(err), _) => return Err(err)
(_, _, _, _, _, Err(err)) => return Err(err)
(Ok(tag), Ok(min_raw), Ok(default_raw), Ok(max_raw), Ok(flags), Ok(name_id)) => {
let min_value = fixed16_to_double(min_raw)
let default_value = fixed16_to_double(default_raw)
let max_value = fixed16_to_double(max_raw)
let min_value = if min_value > default_value { default_value } else { min_value }
let max_value = if max_value < default_value { default_value } else { max_value }
axes.push(AxisRecord::{
tag,
min_value,
default_value,
max_value,
flags,
name_id,
})
}
}
}
let instances : Array[InstanceRecord] = []
let mut instance_offset = axis_offset + axis_bytes
for _ in 0.. data.length() {
return Err(UnexpectedEof)
}
let subfamily_name_id = read_u16_int(data, instance_offset)
let flags = read_u16_int(data, instance_offset + 2)
match (subfamily_name_id, flags) {
(Err(err), _) => return Err(err)
(_, Err(err)) => return Err(err)
(Ok(subfamily_name_id), Ok(flags)) => {
let coords : Array[Double] = []
let coords_offset = instance_offset + 4
for i in 0.. return Err(err)
Ok(raw) => coords.push(fixed16_to_double(raw))
}
}
let mut postscript_name_id : Int? = None
let ps_offset = coords_offset + axis_count * 4
if instance_size >= axis_count * 4 + 6 {
let ps = read_u16_int(data, ps_offset)
match ps {
Err(err) => return Err(err)
Ok(id) => postscript_name_id = Some(id)
}
}
instances.push(InstanceRecord::{
subfamily_name_id,
flags,
coordinates: coords,
postscript_name_id,
})
}
}
instance_offset = instance_offset + instance_size
}
Ok(FvarTable::{ axes, instances })
}
}
}