// Copyright 2025 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.
///|
/// Primary attributes typically used for font classification and selection.
///
/// Ported from `fontations/skrifa/src/attribute.rs` (Apache-2.0 OR MIT).
const TAG_HEAD : UInt = 0x68656164 // "head"
///|
const TAG_OS2 : UInt = 0x4F532F32 // "OS/2"
///|
const TAG_POST : UInt = 0x706F7374 // "post"
///|
const MACSTYLE_BOLD : Int = 0x0001
///|
const MACSTYLE_ITALIC : Int = 0x0002
///|
const FSSELECTION_ITALIC : Int = 0x0001
///|
const FSSELECTION_OBLIQUE : Int = 0x0200
///|
fn attr_read_u16_be(view : BytesView, offset : Int) -> Int? {
if offset < 0 || offset + 2 > view.length() {
None
} else {
let b0 = view.at(offset).to_int()
let b1 = view.at(offset + 1).to_int()
Some((b0 << 8) | b1)
}
}
///|
fn attr_read_u32_be(view : BytesView, offset : Int) -> UInt? {
if offset < 0 || offset + 4 > view.length() {
None
} else {
let b0 = view.at(offset).to_uint()
let b1 = view.at(offset + 1).to_uint()
let b2 = view.at(offset + 2).to_uint()
let b3 = view.at(offset + 3).to_uint()
Some((b0 << 24) | (b1 << 16) | (b2 << 8) | b3)
}
}
///|
fn attr_read_i32_be(view : BytesView, offset : Int) -> Int? {
match attr_read_u32_be(view, offset) {
None => None
Some(u) => Some(u.reinterpret_as_int())
}
}
///|
/// Stretch, style and weight attributes of a font.
pub struct Attributes {
stretch : Stretch
style : Style
weight : Weight
}
///|
pub fn Attributes::Attributes(font : FontRef) -> Attributes {
font.attributes()
}
///|
pub fn Attributes::stretch(self : Attributes) -> Stretch {
self.stretch
}
///|
pub fn Attributes::style(self : Attributes) -> Style {
self.style
}
///|
pub fn Attributes::weight(self : Attributes) -> Weight {
self.weight
}
///|
pub struct Stretch {
priv ratio : Double
}
///|
pub fn Stretch::ultra_condensed() -> Stretch {
Stretch(0.5)
}
///|
pub fn Stretch::extra_condensed() -> Stretch {
Stretch(0.625)
}
///|
pub fn Stretch::condensed() -> Stretch {
Stretch(0.75)
}
///|
pub fn Stretch::semi_condensed() -> Stretch {
Stretch(0.875)
}
///|
pub fn Stretch::normal() -> Stretch {
Stretch(1.0)
}
///|
pub fn Stretch::semi_expanded() -> Stretch {
Stretch(1.125)
}
///|
pub fn Stretch::expanded() -> Stretch {
Stretch(1.25)
}
///|
pub fn Stretch::extra_expanded() -> Stretch {
Stretch(1.5)
}
///|
pub fn Stretch::ultra_expanded() -> Stretch {
Stretch(2.0)
}
///|
pub fn Stretch::Stretch(ratio : Double) -> Stretch {
{ ratio, }
}
///|
pub fn Stretch::default() -> Stretch {
Stretch::normal()
}
///|
pub fn Stretch::ratio(self : Stretch) -> Double {
self.ratio
}
///|
pub fn Stretch::percentage(self : Stretch) -> Double {
self.ratio * 100.0
}
///|
fn Stretch::from_width_class(width_class : Int) -> Stretch {
match width_class {
0 | 1 => Stretch::ultra_condensed()
2 => Stretch::extra_condensed()
3 => Stretch::condensed()
4 => Stretch::semi_condensed()
5 => Stretch::normal()
6 => Stretch::semi_expanded()
7 => Stretch::expanded()
8 => Stretch::extra_expanded()
_ => Stretch::ultra_expanded()
}
}
///|
pub(all) enum Style {
Normal
Italic
Oblique(Double?)
}
///|
pub struct Weight {
priv value : Double
}
///|
pub fn Weight::thin() -> Weight {
Weight(100.0)
}
///|
pub fn Weight::extra_light() -> Weight {
Weight(200.0)
}
///|
pub fn Weight::light() -> Weight {
Weight(300.0)
}
///|
pub fn Weight::semi_light() -> Weight {
Weight(350.0)
}
///|
pub fn Weight::normal() -> Weight {
Weight(400.0)
}
///|
pub fn Weight::medium() -> Weight {
Weight(500.0)
}
///|
pub fn Weight::semi_bold() -> Weight {
Weight(600.0)
}
///|
pub fn Weight::bold() -> Weight {
Weight(700.0)
}
///|
pub fn Weight::extra_bold() -> Weight {
Weight(800.0)
}
///|
pub fn Weight::black() -> Weight {
Weight(900.0)
}
///|
pub fn Weight::extra_black() -> Weight {
Weight(950.0)
}
///|
pub fn Weight::Weight(value : Double) -> Weight {
{ value, }
}
///|
pub fn Weight::default() -> Weight {
Weight::normal()
}
///|
pub fn Weight::value(self : Weight) -> Double {
self.value
}
///|
pub fn FontRef::attributes(self : FontRef) -> Attributes {
// Prefer OS/2 if present, otherwise fall back to head.macStyle.
match self.table(TAG_OS2) {
Some(os2) => {
let width_class = attr_read_u16_be(os2, 6).unwrap_or(5)
let weight_class = attr_read_u16_be(os2, 4).unwrap_or(400)
let fs_selection = attr_read_u16_be(os2, 62).unwrap_or(0)
let stretch = Stretch::from_width_class(width_class)
let style = if (fs_selection & FSSELECTION_ITALIC) != 0 {
Italic
} else if (fs_selection & FSSELECTION_OBLIQUE) != 0 {
let angle = match self.table(TAG_POST) {
None => None
Some(post) =>
// post.italicAngle is a signed 16.16 fixed point at offset 4.
match attr_read_i32_be(post, 4) {
None => None
Some(bits) => Some(bits.to_double() / 65536.0)
}
}
Oblique(angle)
} else {
Normal
}
{ stretch, style, weight: Weight(weight_class.to_double()) }
}
None =>
match self.table(TAG_HEAD) {
Some(head) => {
let mac_style = attr_read_u16_be(head, 44).unwrap_or(0)
let style = if (mac_style & MACSTYLE_ITALIC) != 0 {
Italic
} else {
Normal
}
let weight = if (mac_style & MACSTYLE_BOLD) != 0 {
Weight::bold()
} else {
Weight::default()
}
{ stretch: Stretch::default(), style, weight }
}
None =>
{
stretch: Stretch::default(),
style: Normal,
weight: Weight::default(),
}
}
}
}