// 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.
///|
/// Glyph bounding box from glyf header.
pub struct GlyphBounds {
contours : Int
x_min : Int
y_min : Int
x_max : Int
y_max : Int
} derive(Eq, Show, ToJson)
///|
/// Outline point from glyf data.
pub struct GlyphPoint {
mut x : Double
mut y : Double
flag : Int
mut is_end_point : Bool
} derive(Show, ToJson)
///|
pub fn GlyphPoint::new(
x : Double,
y : Double,
flag : Int,
is_end_point : Bool,
) -> GlyphPoint {
GlyphPoint::{ x, y, flag, is_end_point }
}
///|
pub fn GlyphPoint::translate(self : GlyphPoint, dx : Double, dy : Double) -> Unit {
self.x = self.x + dx
self.y = self.y + dy
}
///|
pub fn GlyphPoint::set(self : GlyphPoint, x : Double, y : Double) -> Unit {
self.x = x
self.y = y
}
///|
pub fn GlyphPoint::transform(self : GlyphPoint, a : Double, b : Double, c : Double, d : Double) -> Unit {
let nx = self.x * a + self.y * c
let ny = self.x * b + self.y * d
self.x = nx
self.y = ny
}
///|
pub fn GlyphBounds::new(
contours : Int,
x_min : Int,
y_min : Int,
x_max : Int,
y_max : Int,
) -> GlyphBounds {
GlyphBounds::{ contours, x_min, y_min, x_max, y_max }
}
///|
/// Parsed glyf table (raw view).
pub struct GlyfTable {
data : BytesView
} derive(Show, ToJson)
///|
pub struct CompositeComponent {
glyph : Int
flags : Int
arg1 : Int
arg2 : Int
a : Double
b : Double
c : Double
d : Double
tx : Double
ty : Double
is_anchored : Bool
use_my_metrics : Bool
scaled_offset : Bool
} derive(Show, ToJson)
///|
/// Parse glyf table from its blob.
pub fn GlyfTable::parse(blob : @blob.Blob) -> Result[GlyfTable, SfntError] {
Ok(GlyfTable::{ data: blob.as_view() })
}
///|
/// Read glyph bounds at a given offset/length.
pub fn GlyfTable::glyph_bounds(
self : GlyfTable,
offset : Int,
length : Int,
) -> Result[GlyphBounds?, SfntError] {
if offset < 0 || length < 0 {
return Err(InvalidFormat)
}
let data_len = self.data.length()
if offset > data_len {
return Err(UnexpectedEof)
}
if length == 0 {
return Ok(None)
}
if length < 10 || offset + length > data_len || offset + 10 > data_len {
return Err(UnexpectedEof)
}
let contours = read_i16(self.data, offset)
let x_min = read_i16(self.data, offset + 2)
let y_min = read_i16(self.data, offset + 4)
let x_max = read_i16(self.data, offset + 6)
let y_max = read_i16(self.data, offset + 8)
match (contours, x_min, y_min, x_max, y_max) {
(Err(err), _, _, _, _) => Err(err)
(_, Err(err), _, _, _) => Err(err)
(_, _, Err(err), _, _) => Err(err)
(_, _, _, Err(err), _) => Err(err)
(_, _, _, _, Err(err)) => Err(err)
(Ok(contours), Ok(x_min), Ok(y_min), Ok(x_max), Ok(y_max)) =>
Ok(Some(GlyphBounds::{ contours, x_min, y_min, x_max, y_max }))
}
}
fn read_simple_flags(
data : BytesView,
offset : Int,
end : Int,
count : Int,
) -> Result[(Array[Int], Int), SfntError] {
if count < 0 {
return Err(InvalidFormat)
}
let flags : Array[Int] = []
let mut pos = offset
let mut i = 0
while i < count {
if pos >= end {
return Err(UnexpectedEof)
}
let flag = data[pos].to_int()
pos = pos + 1
let mut repeat = 1
if (flag & 0x08) != 0 {
if pos >= end {
return Err(UnexpectedEof)
}
repeat = data[pos].to_int() + 1
pos = pos + 1
}
if i + repeat > count {
return Err(InvalidFormat)
}
for _ in 0.. Result[(Array[Int], Int), SfntError] {
let coords : Array[Int] = []
let mut pos = offset
let mut value = 0
for flag in flags {
if (flag & short_flag) != 0 {
if pos >= end {
return Err(UnexpectedEof)
}
let delta = data[pos].to_int()
pos = pos + 1
if (flag & same_flag) != 0 {
value = value + delta
} else {
value = value - delta
}
} else {
if (flag & same_flag) == 0 {
if pos + 2 > end {
return Err(UnexpectedEof)
}
let delta = read_i16(data, pos)
match delta {
Err(err) => return Err(err)
Ok(v) => value = value + v
}
pos = pos + 2
}
}
coords.push(value)
}
Ok((coords, pos))
}
fn read_i8(data : BytesView, offset : Int) -> Result[Int, SfntError] {
if offset < 0 || offset >= data.length() {
return Err(UnexpectedEof)
}
let v = data[offset].to_int()
Ok(if v >= 0x80 { v - 0x100 } else { v })
}
fn f2dot14_to_double(value : Int) -> Double {
value.to_double() / 16384.0
}
///|
/// Read simple glyph outline points. Returns None for composite glyphs.
pub fn GlyfTable::glyph_simple_points(
self : GlyfTable,
offset : Int,
length : Int,
) -> Result[Array[GlyphPoint]?, SfntError] {
if offset < 0 || length < 0 {
return Err(InvalidFormat)
}
let data_len = self.data.length()
if offset > data_len {
return Err(UnexpectedEof)
}
if length == 0 {
return Ok(Some([]))
}
if length < 10 || offset + length > data_len {
return Err(UnexpectedEof)
}
let contours = read_i16(self.data, offset)
let num_contours = match contours {
Err(err) => return Err(err)
Ok(value) => value
}
if num_contours < 0 {
return Ok(None)
}
if num_contours == 0 {
return Ok(Some([]))
}
let end_pts_offset = offset + 10
let end_pts_total = num_contours * 2
if end_pts_offset + end_pts_total > offset + length {
return Err(UnexpectedEof)
}
let end_points : Array[Int] = []
let mut pos = end_pts_offset
for _ in 0.. return Err(err)
Ok(v) => end_points.push(v)
}
pos = pos + 2
}
if end_points.is_empty() {
return Ok(Some([]))
}
let last_end = end_points[end_points.length() - 1]
if last_end < 0 {
return Err(InvalidFormat)
}
let point_count = last_end + 1
if pos + 2 > offset + length {
return Err(UnexpectedEof)
}
let instruction_len = read_u16_int(self.data, pos)
let instr_len = match instruction_len {
Err(err) => return Err(err)
Ok(v) => v
}
if instr_len < 0 {
return Err(InvalidFormat)
}
pos = pos + 2 + instr_len
if pos > offset + length {
return Err(UnexpectedEof)
}
let (flags, after_flags) = match read_simple_flags(
self.data,
pos,
offset + length,
point_count,
) {
Err(err) => return Err(err)
Ok(value) => value
}
pos = after_flags
let (x_coords, after_x) = match read_simple_coords(
self.data,
pos,
offset + length,
flags,
0x02,
0x10,
) {
Err(err) => return Err(err)
Ok(value) => value
}
pos = after_x
let (y_coords, _) = match read_simple_coords(
self.data,
pos,
offset + length,
flags,
0x04,
0x20,
) {
Err(err) => return Err(err)
Ok(value) => value
}
let points : Array[GlyphPoint] = []
for i in 0..= point_count {
return Err(InvalidFormat)
}
points[end_point].is_end_point = true
}
Ok(Some(points))
}
///|
/// Read composite glyph component records. Returns None for non-composite glyphs.
pub fn GlyfTable::glyph_composite_components(
self : GlyfTable,
offset : Int,
length : Int,
) -> Result[Array[CompositeComponent]?, SfntError] {
if offset < 0 || length < 0 {
return Err(InvalidFormat)
}
let data_len = self.data.length()
if offset > data_len {
return Err(UnexpectedEof)
}
if length == 0 {
return Ok(Some([]))
}
if length < 10 || offset + length > data_len {
return Err(UnexpectedEof)
}
let contours = read_i16(self.data, offset)
let num_contours = match contours {
Err(err) => return Err(err)
Ok(value) => value
}
if num_contours >= 0 {
return Ok(None)
}
let mut pos = offset + 10
let end = offset + length
let components : Array[CompositeComponent] = []
let mut last_flags = 0
while true {
if pos + 4 > end {
return Err(UnexpectedEof)
}
let flags = read_u16_int(self.data, pos)
let glyph_index = read_u16_int(self.data, pos + 2)
let flags = match flags {
Err(err) => return Err(err)
Ok(value) => value
}
let mut glyph = match glyph_index {
Err(err) => return Err(err)
Ok(value) => value
}
pos = pos + 4
if (flags & 0x2000) != 0 {
let gid24 = read_u24(self.data, pos)
match gid24 {
Err(err) => return Err(err)
Ok(value) => glyph = value.reinterpret_as_int()
}
pos = pos + 3
}
let args_are_words = (flags & 0x0001) != 0
let args_are_xy = (flags & 0x0002) != 0
let mut arg1 = 0
let mut arg2 = 0
if args_are_words {
if args_are_xy {
let a1 = read_i16(self.data, pos)
let a2 = read_i16(self.data, pos + 2)
match (a1, a2) {
(Err(err), _) => return Err(err)
(_, Err(err)) => return Err(err)
(Ok(v1), Ok(v2)) => {
arg1 = v1
arg2 = v2
}
}
} else {
let a1 = read_u16_int(self.data, pos)
let a2 = read_u16_int(self.data, pos + 2)
match (a1, a2) {
(Err(err), _) => return Err(err)
(_, Err(err)) => return Err(err)
(Ok(v1), Ok(v2)) => {
arg1 = v1
arg2 = v2
}
}
}
pos = pos + 4
} else {
if args_are_xy {
let a1 = read_i8(self.data, pos)
let a2 = read_i8(self.data, pos + 1)
match (a1, a2) {
(Err(err), _) => return Err(err)
(_, Err(err)) => return Err(err)
(Ok(v1), Ok(v2)) => {
arg1 = v1
arg2 = v2
}
}
} else {
let a1 = read_u8_int(self.data, pos)
let a2 = read_u8_int(self.data, pos + 1)
match (a1, a2) {
(Err(err), _) => return Err(err)
(_, Err(err)) => return Err(err)
(Ok(v1), Ok(v2)) => {
arg1 = v1
arg2 = v2
}
}
}
pos = pos + 2
}
let mut a = 1.0
let mut b = 0.0
let mut c = 0.0
let mut d = 1.0
if (flags & 0x0008) != 0 {
let scale = read_i16(self.data, pos)
match scale {
Err(err) => return Err(err)
Ok(value) => {
let s = f2dot14_to_double(value)
a = s
d = s
}
}
pos = pos + 2
} else if (flags & 0x0040) != 0 {
let xscale = read_i16(self.data, pos)
let yscale = read_i16(self.data, pos + 2)
match (xscale, yscale) {
(Err(err), _) => return Err(err)
(_, Err(err)) => return Err(err)
(Ok(v1), Ok(v2)) => {
a = f2dot14_to_double(v1)
d = f2dot14_to_double(v2)
}
}
pos = pos + 4
} else if (flags & 0x0080) != 0 {
let v0 = read_i16(self.data, pos)
let v1 = read_i16(self.data, pos + 2)
let v2 = read_i16(self.data, pos + 4)
let v3 = read_i16(self.data, pos + 6)
match (v0, v1, v2, v3) {
(Err(err), _, _, _) => return Err(err)
(_, Err(err), _, _) => return Err(err)
(_, _, Err(err), _) => return Err(err)
(_, _, _, Err(err)) => return Err(err)
(Ok(v0), Ok(v1), Ok(v2), Ok(v3)) => {
a = f2dot14_to_double(v0)
b = f2dot14_to_double(v1)
c = f2dot14_to_double(v2)
d = f2dot14_to_double(v3)
}
}
pos = pos + 8
}
let is_anchored = !args_are_xy
let use_my_metrics = (flags & 0x0200) != 0
let scaled_offset = (flags & 0x0800) != 0 && (flags & 0x1000) == 0
let tx = if is_anchored { 0.0 } else { arg1.to_double() }
let ty = if is_anchored { 0.0 } else { arg2.to_double() }
components.push(CompositeComponent::{
glyph,
flags,
arg1,
arg2,
a,
b,
c,
d,
tx,
ty,
is_anchored,
use_my_metrics,
scaled_offset,
})
last_flags = flags
if (flags & 0x0020) == 0 {
break
}
}
if (last_flags & 0x0100) != 0 {
if pos + 2 > end {
return Err(UnexpectedEof)
}
let instr_len = read_u16_int(self.data, pos)
let instr_len = match instr_len {
Err(err) => return Err(err)
Ok(value) => value
}
pos = pos + 2 + instr_len
if pos > end {
return Err(UnexpectedEof)
}
}
Ok(Some(components))
}