// 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.
///|
/// Minimal horizontal metrics (maxp/hhea/hmtx).
///
/// Ported from `fontations/skrifa/src/metrics.rs` (Apache-2.0 OR MIT).
const TAG_MAXP : UInt = 0x6D617870 // "maxp"
///|
const TAG_HHEA : UInt = 0x68686561 // "hhea"
///|
const TAG_HMTX : UInt = 0x686D7478 // "hmtx"
///|
const METRICS_TAG_HEAD : UInt = 0x68656164 // "head"
///|
const METRICS_TAG_OS2 : UInt = 0x4F532F32 // "OS/2"
///|
const METRICS_TAG_POST : UInt = 0x706F7374 // "post"
///|
const METRICS_TAG_HVAR : UInt = 0x48564152 // "HVAR"
///|
const METRICS_TAG_GVAR : UInt = 0x67564152 // "gvar"
///|
const METRICS_TAG_GLYF : UInt = 0x676C7966 // "glyf"
///|
const METRICS_TAG_LOCA : UInt = 0x6C6F6361 // "loca"
///|
const METRICS_F2DOT14_SCALE : Double = 16384.0
///|
const METRICS_COMPOSITE_ARG_1_AND_2_ARE_WORDS : Int = 0x0001
///|
const METRICS_COMPOSITE_MORE_COMPONENTS : Int = 0x0020
///|
const METRICS_COMPOSITE_WE_HAVE_A_SCALE : Int = 0x0008
///|
const METRICS_COMPOSITE_WE_HAVE_AN_X_AND_Y_SCALE : Int = 0x0040
///|
const METRICS_COMPOSITE_WE_HAVE_A_TWO_BY_TWO : Int = 0x0080
///|
fn metrics_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 metrics_read_i16_be(view : BytesView, offset : Int) -> Int? {
match metrics_read_u16_be(view, offset) {
None => None
Some(u) => if u >= 0x8000 { Some(u - 0x10000) } else { Some(u) }
}
}
///|
fn metrics_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 metrics_read_u32_be_int(view : BytesView, offset : Int) -> Int? {
match metrics_read_u32_be(view, offset) {
None => None
Some(u) => Some(u.to_uint64().to_int())
}
}
///|
fn metrics_read_i32_be(view : BytesView, offset : Int) -> Int? {
match metrics_read_u32_be(view, offset) {
None => None
Some(u) => Some(u.reinterpret_as_int())
}
}
///|
fn metrics_read_u8(view : BytesView, offset : Int) -> Int? {
if offset < 0 || offset + 1 > view.length() {
None
} else {
Some(view.at(offset).to_int())
}
}
///|
fn metrics_read_i8(view : BytesView, offset : Int) -> Int? {
match metrics_read_u8(view, offset) {
None => None
Some(u) => if u >= 0x80 { Some(u - 0x100) } else { Some(u) }
}
}
///|
fn metrics_read_u24_be(view : BytesView, offset : Int) -> Int? {
if offset < 0 || offset + 3 > view.length() {
None
} else {
let b0 = view.at(offset).to_int()
let b1 = view.at(offset + 1).to_int()
let b2 = view.at(offset + 2).to_int()
Some((b0 << 16) | (b1 << 8) | b2)
}
}
///|
pub struct HmtxMetrics {
priv num_glyphs : Int
priv num_hmetrics : Int
priv hmtx : BytesView
}
///|
/// Parses required tables (maxp/hhea/hmtx) from the font.
pub fn HmtxMetrics::from_font(font : FontRef) -> HmtxMetrics? {
let maxp = match font.table(TAG_MAXP) {
None => return None
Some(v) => v
}
let hhea = match font.table(TAG_HHEA) {
None => return None
Some(v) => v
}
let hmtx = match font.table(TAG_HMTX) {
None => return None
Some(v) => v
}
let num_glyphs = match metrics_read_u16_be(maxp, 4) {
None => return None
Some(v) => v
}
let num_hmetrics = match metrics_read_u16_be(hhea, 34) {
None => return None
Some(v) => v
}
if num_glyphs <= 0 || num_hmetrics <= 0 || num_hmetrics > num_glyphs {
return None
}
let need = num_hmetrics * 4 + (num_glyphs - num_hmetrics) * 2
if need < 0 || hmtx.length() < need {
return None
}
Some({ num_glyphs, num_hmetrics, hmtx })
}
///|
pub fn HmtxMetrics::num_glyphs(self : HmtxMetrics) -> Int {
self.num_glyphs
}
///|
pub fn HmtxMetrics::number_of_hmetrics(self : HmtxMetrics) -> Int {
self.num_hmetrics
}
///|
fn HmtxMetrics::glyph_index(self : HmtxMetrics, gid : GlyphId) -> Int? {
let idx = gid.to_uint64().to_int()
if idx < 0 || idx >= self.num_glyphs {
None
} else {
Some(idx)
}
}
///|
/// Returns advance width for a glyph in font units.
pub fn HmtxMetrics::advance_width(self : HmtxMetrics, gid : GlyphId) -> UInt? {
match self.glyph_index(gid) {
None => None
Some(idx) => {
let pos = if idx < self.num_hmetrics {
idx * 4
} else {
(self.num_hmetrics - 1) * 4
}
match metrics_read_u16_be(self.hmtx, pos) {
None => None
Some(v) => Some(v.reinterpret_as_uint())
}
}
}
}
///|
/// Returns left side bearing for a glyph in font units.
pub fn HmtxMetrics::lsb(self : HmtxMetrics, gid : GlyphId) -> Int? {
match self.glyph_index(gid) {
None => None
Some(idx) =>
if idx < self.num_hmetrics {
metrics_read_i16_be(self.hmtx, idx * 4 + 2)
} else {
let extra = idx - self.num_hmetrics
metrics_read_i16_be(self.hmtx, self.num_hmetrics * 4 + extra * 2)
}
}
}
///|
pub fn FontRef::hmtx_metrics(self : FontRef) -> HmtxMetrics? {
HmtxMetrics::from_font(self)
}
///|
pub struct Decoration {
offset : Double
thickness : Double
}
///|
pub fn Decoration::offset(self : Decoration) -> Double {
self.offset
}
///|
pub fn Decoration::thickness(self : Decoration) -> Double {
self.thickness
}
///|
pub struct Metrics {
units_per_em : UInt16
glyph_count : UInt16
is_monospace : Bool
italic_angle : Double
ascent : Double
descent : Double
leading : Double
cap_height : Double?
x_height : Double?
average_width : Double?
max_width : Double?
underline : Decoration?
strikeout : Decoration?
bounds : GlyphBounds?
}
///|
pub fn Metrics::units_per_em(self : Metrics) -> UInt16 {
self.units_per_em
}
///|
pub fn Metrics::glyph_count(self : Metrics) -> UInt16 {
self.glyph_count
}
///|
pub fn Metrics::is_monospace(self : Metrics) -> Bool {
self.is_monospace
}
///|
pub fn Metrics::italic_angle(self : Metrics) -> Double {
self.italic_angle
}
///|
pub fn Metrics::ascent(self : Metrics) -> Double {
self.ascent
}
///|
pub fn Metrics::descent(self : Metrics) -> Double {
self.descent
}
///|
pub fn Metrics::leading(self : Metrics) -> Double {
self.leading
}
///|
pub fn Metrics::cap_height(self : Metrics) -> Double? {
self.cap_height
}
///|
pub fn Metrics::x_height(self : Metrics) -> Double? {
self.x_height
}
///|
pub fn Metrics::average_width(self : Metrics) -> Double? {
self.average_width
}
///|
pub fn Metrics::max_width(self : Metrics) -> Double? {
self.max_width
}
///|
pub fn Metrics::underline(self : Metrics) -> Decoration? {
self.underline
}
///|
pub fn Metrics::strikeout(self : Metrics) -> Decoration? {
self.strikeout
}
///|
pub fn Metrics::bounds(self : Metrics) -> GlyphBounds? {
self.bounds
}
///|
pub fn Metrics::Metrics(
font : FontRef,
size : Size,
_location : LocationRef,
) -> Metrics {
let mut units_per_em : UInt16 = 0
let mut bounds : GlyphBounds? = None
match font.table(METRICS_TAG_HEAD) {
None => ()
Some(head) => {
let upem = metrics_read_u16_be(head, 18).unwrap_or(0)
units_per_em = upem.to_uint16()
// head bbox: xMin/yMin/xMax/yMax at offsets 36..44.
if head.length() >= 44 {
let scale = size.linear_scale(units_per_em)
let x_min = metrics_read_i16_be(head, 36).unwrap_or(0).to_double() *
scale
let y_min = metrics_read_i16_be(head, 38).unwrap_or(0).to_double() *
scale
let x_max = metrics_read_i16_be(head, 40).unwrap_or(0).to_double() *
scale
let y_max = metrics_read_i16_be(head, 42).unwrap_or(0).to_double() *
scale
bounds = Some({ x_min, y_min, x_max, y_max })
}
}
}
let scale = size.linear_scale(units_per_em)
let mut glyph_count : UInt16 = 0
match font.table(TAG_MAXP) {
None => ()
Some(maxp) => {
let n = metrics_read_u16_be(maxp, 4).unwrap_or(0)
glyph_count = n.to_uint16()
}
}
let mut max_width : Double? = None
match font.table(TAG_HHEA) {
None => ()
Some(hhea) => {
// advanceWidthMax at offset 10 (u16).
let aw = metrics_read_u16_be(hhea, 10).unwrap_or(-1)
if aw >= 0 {
max_width = Some(aw.to_double() * scale)
}
}
}
let mut is_monospace = false
let mut italic_angle = 0.0
let mut underline : Decoration? = None
let mut strikeout : Decoration? = None
let mut cap_height : Double? = None
let mut x_height : Double? = None
let mut average_width : Double? = None
match font.table(METRICS_TAG_POST) {
None => ()
Some(post) => {
let fixed_pitch = match metrics_read_u32_be(post, 12) {
None => 0
Some(v) => v.reinterpret_as_int()
}
is_monospace = fixed_pitch != 0
italic_angle = metrics_read_i32_be(post, 4).unwrap_or(0).to_double() /
65536.0
let ul_pos = metrics_read_i16_be(post, 8).unwrap_or(0).to_double() * scale
let ul_thick = metrics_read_i16_be(post, 10).unwrap_or(0).to_double() *
scale
underline = Some({ offset: ul_pos, thickness: ul_thick })
}
}
let mut ascent = 0.0
let mut descent = 0.0
let mut leading = 0.0
let os2 = font.table(METRICS_TAG_OS2)
let mut used_typo_metrics = false
match os2 {
None => ()
Some(os2_tbl) => {
// xAvgCharWidth at offset 2 (i16).
let x_avg = metrics_read_i16_be(os2_tbl, 2).unwrap_or(0).to_double() *
scale
average_width = Some(x_avg)
// Strikeout position/size at offsets 28/30 (i16).
let so_pos = metrics_read_i16_be(os2_tbl, 28).unwrap_or(0).to_double() *
scale
let so_size = metrics_read_i16_be(os2_tbl, 30).unwrap_or(0).to_double() *
scale
strikeout = Some({ offset: so_pos, thickness: so_size })
// sxHeight and sCapHeight are present in OS/2 v2+ (offsets 86/88).
if os2_tbl.length() >= 90 {
let xh = metrics_read_i16_be(os2_tbl, 86).unwrap_or(0)
let ch = metrics_read_i16_be(os2_tbl, 88).unwrap_or(0)
x_height = Some(xh.to_double() * scale)
cap_height = Some(ch.to_double() * scale)
}
// fsSelection at offset 62, USE_TYPO_METRICS = 0x0080.
let fs_selection = metrics_read_u16_be(os2_tbl, 62).unwrap_or(0)
if (fs_selection & 0x0080) != 0 {
ascent = metrics_read_i16_be(os2_tbl, 68).unwrap_or(0).to_double() *
scale
descent = metrics_read_i16_be(os2_tbl, 70).unwrap_or(0).to_double() *
scale
leading = metrics_read_i16_be(os2_tbl, 72).unwrap_or(0).to_double() *
scale
used_typo_metrics = true
}
}
}
if !used_typo_metrics {
match font.table(TAG_HHEA) {
None => ()
Some(hhea) => {
ascent = metrics_read_i16_be(hhea, 4).unwrap_or(0).to_double() * scale
descent = metrics_read_i16_be(hhea, 6).unwrap_or(0).to_double() * scale
leading = metrics_read_i16_be(hhea, 8).unwrap_or(0).to_double() * scale
}
}
if ascent == 0.0 && descent == 0.0 {
match os2 {
None => ()
Some(os2_tbl) => {
let typo_a = metrics_read_i16_be(os2_tbl, 68).unwrap_or(0)
let typo_d = metrics_read_i16_be(os2_tbl, 70).unwrap_or(0)
if typo_a != 0 || typo_d != 0 {
ascent = typo_a.to_double() * scale
descent = typo_d.to_double() * scale
leading = metrics_read_i16_be(os2_tbl, 72).unwrap_or(0).to_double() *
scale
} else {
let win_a = metrics_read_u16_be(os2_tbl, 74)
.unwrap_or(0)
.to_double() *
scale
let win_d = metrics_read_u16_be(os2_tbl, 76)
.unwrap_or(0)
.to_double() *
scale
ascent = win_a
descent = -win_d
}
}
}
}
}
{
units_per_em,
glyph_count,
is_monospace,
italic_angle,
ascent,
descent,
leading,
cap_height,
x_height,
average_width,
max_width,
underline,
strikeout,
bounds,
}
}
///|
pub fn FontRef::metrics(
self : FontRef,
size : Size,
location : LocationRef,
) -> Metrics {
Metrics(self, size, location)
}
///|
pub fn FontRef::font_metrics(self : FontRef, size : Size) -> Metrics {
Metrics(self, size, LocationRef::default())
}
///|
pub fn FontRef::font_metrics_at(
self : FontRef,
size : Size,
location : LocationRef,
) -> Metrics {
Metrics(self, size, location)
}
///|
/// Glyph specific metrics for a particular size and variation location.
///
/// Port of `skrifa::metrics::GlyphMetrics`, including:
/// - base metrics from `hmtx`
/// - variation deltas from `HVAR`
/// - glyph-variation phantom-point deltas from `gvar`
pub struct GlyphMetrics {
priv font : FontRef
priv glyph_count : Int
priv base : HmtxMetrics?
priv scale : Double
priv coords : ArrayView[NormalizedCoord]
priv hvar : BytesView?
priv hvar_store_off : Int
priv hvar_aw_map_off : Int?
priv hvar_lsb_map_off : Int?
priv gvar : BytesView?
priv gvar_axis_count : Int
priv gvar_shared_tuple_count : Int
priv gvar_shared_tuples_off : Int
priv gvar_glyph_count : Int
priv gvar_flags : Int
priv gvar_data_array_off : Int
}
///|
pub struct GlyphBounds {
priv x_min : Double
priv y_min : Double
priv x_max : Double
priv y_max : Double
}
///|
pub fn GlyphBounds::x_min(self : GlyphBounds) -> Double {
self.x_min
}
///|
pub fn GlyphBounds::y_min(self : GlyphBounds) -> Double {
self.y_min
}
///|
pub fn GlyphBounds::x_max(self : GlyphBounds) -> Double {
self.x_max
}
///|
pub fn GlyphBounds::y_max(self : GlyphBounds) -> Double {
self.y_max
}
///|
pub fn GlyphMetrics::GlyphMetrics(
font : FontRef,
size : Size,
location : LocationRef,
) -> GlyphMetrics {
let mut glyph_count = 0
match font.table(TAG_MAXP) {
None => ()
Some(maxp) => glyph_count = metrics_read_u16_be(maxp, 4).unwrap_or(0)
}
let base = HmtxMetrics::from_font(font)
let mut upem : UInt16 = 0
match font.table(METRICS_TAG_HEAD) {
None => ()
Some(head) => {
let v = metrics_read_u16_be(head, 18).unwrap_or(0)
upem = v.to_uint16()
}
}
let scale = size.linear_scale(upem)
let coords = location.effective_coords()
let mut hvar : BytesView? = None
let mut store_off = -1
let mut aw_map_off : Int? = None
let mut lsb_map_off : Int? = None
match font.table(METRICS_TAG_HVAR) {
None => ()
Some(tbl) =>
// HVAR header is 20 bytes.
if tbl.length() >= 20 {
let so = metrics_read_u32_be_int(tbl, 4).unwrap_or(-1)
let awo = metrics_read_u32_be_int(tbl, 8).unwrap_or(0)
let lsbo = metrics_read_u32_be_int(tbl, 12).unwrap_or(0)
if so > 0 && so < tbl.length() {
hvar = Some(tbl)
store_off = so
if awo > 0 && awo < tbl.length() {
aw_map_off = Some(awo)
}
if lsbo > 0 && lsbo < tbl.length() {
lsb_map_off = Some(lsbo)
}
}
}
}
let mut gvar : BytesView? = None
let mut gvar_axis_count = -1
let mut gvar_shared_tuple_count = -1
let mut gvar_shared_tuples_off = -1
let mut gvar_glyph_count = -1
let mut gvar_flags = -1
let mut gvar_data_array_off = -1
match font.table(METRICS_TAG_GVAR) {
None => ()
Some(tbl) =>
// gvar header is 20 bytes.
if tbl.length() >= 20 {
let axis_count = metrics_read_u16_be(tbl, 4).unwrap_or(-1)
let shared_tuple_count = metrics_read_u16_be(tbl, 6).unwrap_or(-1)
let shared_tuples_off = metrics_read_u32_be_int(tbl, 8).unwrap_or(-1)
let glyph_count = metrics_read_u16_be(tbl, 12).unwrap_or(-1)
let flags = metrics_read_u16_be(tbl, 14).unwrap_or(-1)
let data_array_off = metrics_read_u32_be_int(tbl, 16).unwrap_or(-1)
if axis_count >= 0 &&
shared_tuple_count >= 0 &&
glyph_count >= 0 &&
flags >= 0 &&
data_array_off >= 0 &&
data_array_off <= tbl.length() {
gvar = Some(tbl)
gvar_axis_count = axis_count
gvar_shared_tuple_count = shared_tuple_count
gvar_shared_tuples_off = shared_tuples_off
gvar_glyph_count = glyph_count
gvar_flags = flags
gvar_data_array_off = data_array_off
}
}
}
{
font,
glyph_count,
base,
scale,
coords,
hvar,
hvar_store_off: store_off,
hvar_aw_map_off: aw_map_off,
hvar_lsb_map_off: lsb_map_off,
gvar,
gvar_axis_count,
gvar_shared_tuple_count,
gvar_shared_tuples_off,
gvar_glyph_count,
gvar_flags,
gvar_data_array_off,
}
}
///|
pub fn GlyphMetrics::glyph_count(self : GlyphMetrics) -> Int {
self.glyph_count
}
///|
fn metrics_glyf_simple_bounds(
glyf : BytesView,
start : Int,
end : Int,
) -> (Int, Int, Int, Int)? {
if start < 0 || end < start || end > glyf.length() || start + 10 > end {
return None
}
let contours = metrics_read_i16_be(glyf, start).unwrap_or(-32768)
if contours <= 0 {
return None
}
let end_pts_off = start + 10
let need_end_pts = end_pts_off + contours * 2
if need_end_pts < end_pts_off || need_end_pts > end {
return None
}
let last = metrics_read_u16_be(glyf, end_pts_off + (contours - 1) * 2).unwrap_or(
-1,
)
if last < 0 {
return None
}
let num_points = last + 1
let instr_len_off = end_pts_off + contours * 2
let instr_len = metrics_read_u16_be(glyf, instr_len_off).unwrap_or(-1)
if instr_len < 0 {
return None
}
let mut pos = instr_len_off + 2 + instr_len
if pos < instr_len_off || pos > end {
return None
}
// Flags
let flags : Array[Int] = Array::make(num_points, 0)
let mut i = 0
while i < num_points {
let f = metrics_read_u8(glyf, pos).unwrap_or(-1)
if f < 0 {
return None
}
pos = pos + 1
flags.set(i, f)
i = i + 1
if (f & 0x08) != 0 {
let rep = metrics_read_u8(glyf, pos).unwrap_or(-1)
if rep < 0 {
return None
}
pos = pos + 1
for _ in 0..= num_points {
break
}
flags.set(i, f)
i = i + 1
}
}
}
// Decode X coordinates.
let xs : Array[Int] = Array::make(num_points, 0)
let mut x = 0
for j in 0.. x_max {
x_max = px
}
if y < y_min {
y_min = y
}
if y > y_max {
y_max = y
}
}
}
Some((x_min, y_min, x_max, y_max))
}
///|
/// Returns scaled bounds for a glyf glyph (if present).
pub fn GlyphMetrics::bounds(self : GlyphMetrics, gid : GlyphId) -> GlyphBounds? {
let idx = gid.to_uint64().to_int()
let (glyf, start, end) = match
metrics_glyf_glyph_range(self.font, self.glyph_count, idx) {
None => return None
Some(v) => v
}
if start == end {
return None
}
let (x_min, y_min, x_max, y_max) = match
metrics_glyf_simple_bounds(glyf, start, end) {
Some(v) => v
None => {
// Fallback to the bbox fields in the glyph header.
if start + 10 > end {
return None
}
(
metrics_read_i16_be(glyf, start + 2).unwrap_or(0),
metrics_read_i16_be(glyf, start + 4).unwrap_or(0),
metrics_read_i16_be(glyf, start + 6).unwrap_or(0),
metrics_read_i16_be(glyf, start + 8).unwrap_or(0),
)
}
}
Some({
x_min: x_min.to_double() * self.scale,
y_min: y_min.to_double() * self.scale,
x_max: x_max.to_double() * self.scale,
y_max: y_max.to_double() * self.scale,
})
}
///|
fn metrics_hvar_entry_size(entry_format : Int) -> Int {
((entry_format & 0x30) >> 4) + 1
}
///|
fn metrics_hvar_bit_count(entry_format : Int) -> Int {
(entry_format & 0x0F) + 1
}
///|
fn metrics_hvar_delta_set_index_map_get(
hvar : BytesView,
map_off : Int,
index : Int,
) -> (Int, Int)? {
let format = metrics_read_u8(hvar, map_off).unwrap_or(-1)
if format != 0 && format != 1 {
return None
}
let entry_format = metrics_read_u8(hvar, map_off + 1).unwrap_or(-1)
if entry_format < 0 {
return None
}
let entry_size = metrics_hvar_entry_size(entry_format)
let bit_count = metrics_hvar_bit_count(entry_format)
if entry_size < 1 || entry_size > 4 || bit_count < 1 || bit_count > 16 {
return None
}
let mut map_count = -1
let mut data_off = -1
if format == 0 {
map_count = metrics_read_u16_be(hvar, map_off + 2).unwrap_or(-1)
data_off = map_off + 4
} else {
map_count = metrics_read_u32_be_int(hvar, map_off + 2).unwrap_or(-1)
data_off = map_off + 6
}
if map_count <= 0 {
return None
}
let idx = if index < map_count { index } else { map_count - 1 }
let entry_off = data_off + idx * entry_size
let entry : Int? = match entry_size {
1 => metrics_read_u8(hvar, entry_off)
2 => metrics_read_u16_be(hvar, entry_off)
3 => metrics_read_u24_be(hvar, entry_off)
_ => metrics_read_u32_be_int(hvar, entry_off)
}
match entry {
None => None
Some(e) => {
let outer = (e >> bit_count) & 0xFFFF
let inner_mask = (1 << bit_count) - 1
let inner = e & inner_mask
Some((outer, inner))
}
}
}
///|
fn metrics_hvar_region_scalar(
store : BytesView,
region_list_off : Int,
region_index : Int,
coords : ArrayView[NormalizedCoord],
) -> Double {
let axis_count = metrics_read_u16_be(store, region_list_off).unwrap_or(-1)
let region_count = metrics_read_u16_be(store, region_list_off + 2).unwrap_or(
-1,
)
if axis_count <= 0 || region_count <= 0 {
return 0.0
}
if region_index < 0 || region_index >= region_count {
return 0.0
}
let region_rec_len = axis_count * 6
let region_off = region_list_off + 4 + region_index * region_rec_len
let mut scalar = 1.0
for i in 0.. peak || peak > end || (start < 0.0 && end > 0.0) {
continue
}
let coord = if i < coords.length() {
coords.at(i).to_double() / METRICS_F2DOT14_SCALE
} else {
0.0
}
if coord < start || coord > end {
return 0.0
}
if coord == peak {
continue
}
if coord < peak {
scalar = scalar * (coord - start) / (peak - start)
} else {
scalar = scalar * (end - coord) / (end - peak)
}
}
scalar
}
///|
fn metrics_hvar_item_variation_store_delta(
store : BytesView,
store_off : Int,
outer : Int,
inner : Int,
coords : ArrayView[NormalizedCoord],
) -> Int {
if coords.is_empty() {
return 0
}
// ItemVariationStore header:
// u16 format, u32 regionListOff, u16 dataCount, u32[dataCount] dataOffsets
let region_list_rel = metrics_read_u32_be_int(store, store_off + 2).unwrap_or(
-1,
)
let data_count = metrics_read_u16_be(store, store_off + 6).unwrap_or(-1)
if region_list_rel <= 0 || data_count <= 0 {
return 0
}
if outer < 0 || outer >= data_count {
return 0
}
let offsets_off = store_off + 8
let data_rel = metrics_read_u32_be_int(store, offsets_off + outer * 4).unwrap_or(
0,
)
if data_rel <= 0 {
return 0
}
let data_off = store_off + data_rel
// ItemVariationData header:
// u16 itemCount, u16 shortDeltaCount, u16 regionIndexCount, u16[regionIndexCount] regionIndexes, deltaSets...
let item_count = metrics_read_u16_be(store, data_off).unwrap_or(-1)
let short_count = metrics_read_u16_be(store, data_off + 2).unwrap_or(-1)
let region_index_count = metrics_read_u16_be(store, data_off + 4).unwrap_or(
-1,
)
if item_count <= 0 || region_index_count <= 0 {
return 0
}
if inner < 0 || inner >= item_count {
return 0
}
let region_indexes_off = data_off + 6
let deltas_off = region_indexes_off + region_index_count * 2
let short = if short_count < 0 { 0 } else { short_count }
let row_size = short * 2 + (region_index_count - short)
if row_size <= 0 {
return 0
}
let row_off = deltas_off + inner * row_size
let region_list_off = store_off + region_list_rel
let mut accum = 0.0
for i in 0.. Int {
match map_off {
None => 0
Some(off) =>
match
metrics_hvar_delta_set_index_map_get(
hvar,
off,
gid.to_uint64().to_int(),
) {
None => 0
Some((outer, inner)) =>
metrics_hvar_item_variation_store_delta(
hvar, store_off, outer, inner, coords,
)
}
}
}
///|
fn metrics_glyf_glyph_range(
font : FontRef,
num_glyphs : Int,
gid : Int,
) -> (BytesView, Int, Int)? {
if gid < 0 || gid >= num_glyphs {
return None
}
let head = match font.table(METRICS_TAG_HEAD) {
None => return None
Some(v) => v
}
let loca = match font.table(METRICS_TAG_LOCA) {
None => return None
Some(v) => v
}
let glyf = match font.table(METRICS_TAG_GLYF) {
None => return None
Some(v) => v
}
let loca_format = metrics_read_i16_be(head, 50).unwrap_or(-1)
let idx = gid
if loca_format == 0 {
let need = (num_glyphs + 1) * 2
if need < 0 || loca.length() < need {
return None
}
let o0 = metrics_read_u16_be(loca, idx * 2).unwrap_or(-1)
let o1 = metrics_read_u16_be(loca, (idx + 1) * 2).unwrap_or(-1)
if o0 < 0 || o1 < o0 {
return None
}
let start = o0 * 2
let end = o1 * 2
if start < 0 || end < start || end > glyf.length() {
None
} else {
Some((glyf, start, end))
}
} else if loca_format == 1 {
let need = (num_glyphs + 1) * 4
if need < 0 || loca.length() < need {
return None
}
let o0 = metrics_read_u32_be_int(loca, idx * 4).unwrap_or(-1)
let o1 = metrics_read_u32_be_int(loca, (idx + 1) * 4).unwrap_or(-1)
if o0 < 0 || o1 < o0 {
return None
}
if o0 < 0 || o1 < 0 || o1 > glyf.length() {
None
} else {
Some((glyf, o0, o1))
}
} else {
None
}
}
///|
fn metrics_glyf_point_count_idx(
font : FontRef,
num_glyphs : Int,
gid : Int,
visited : Array[Bool],
) -> Int? {
if gid < 0 || gid >= num_glyphs {
return None
}
if visited.at(gid) {
return None
}
visited.set(gid, true)
let (glyf, start, end) = match
metrics_glyf_glyph_range(font, num_glyphs, gid) {
None => return None
Some(v) => v
}
if start == end {
return Some(0)
}
if start + 10 > end {
return None
}
let contours = metrics_read_i16_be(glyf, start).unwrap_or(-32768)
if contours >= 0 {
if contours == 0 {
return Some(0)
}
let n = contours
let end_pts_off = start + 10
let last = metrics_read_u16_be(glyf, end_pts_off + (n - 1) * 2).unwrap_or(
-1,
)
if last < 0 {
None
} else {
Some(last + 1)
}
} else {
// Composite glyph: sum component point counts.
let mut pos = start + 10
let mut total = 0
while true {
if pos + 4 > end {
return None
}
let flags = metrics_read_u16_be(glyf, pos).unwrap_or(-1)
let comp_gid = metrics_read_u16_be(glyf, pos + 2).unwrap_or(-1)
if flags < 0 || comp_gid < 0 {
return None
}
pos = pos + 4
let arg_words = (flags & METRICS_COMPOSITE_ARG_1_AND_2_ARE_WORDS) != 0
pos = pos + (if arg_words { 4 } else { 2 })
if (flags & METRICS_COMPOSITE_WE_HAVE_A_SCALE) != 0 {
pos = pos + 2
} else if (flags & METRICS_COMPOSITE_WE_HAVE_AN_X_AND_Y_SCALE) != 0 {
pos = pos + 4
} else if (flags & METRICS_COMPOSITE_WE_HAVE_A_TWO_BY_TWO) != 0 {
pos = pos + 8
}
let pc = match
metrics_glyf_point_count_idx(font, num_glyphs, comp_gid, visited) {
None => return None
Some(v) => v
}
total = total + pc
if (flags & METRICS_COMPOSITE_MORE_COMPONENTS) == 0 {
break
}
}
Some(total)
}
}
///|
fn metrics_glyf_point_count(
font : FontRef,
num_glyphs : Int,
gid : GlyphId,
) -> Int? {
let idx = gid.to_uint64().to_int()
if idx < 0 || idx >= num_glyphs {
return None
}
let visited : Array[Bool] = Array::make(num_glyphs, false)
metrics_glyf_point_count_idx(font, num_glyphs, idx, visited)
}
///|
fn metrics_gvar_points(
gvar : BytesView,
offset : Int,
total_points : Int,
) -> (Array[Int]?, Int)? {
let b0 = metrics_read_u8(gvar, offset).unwrap_or(-1)
if b0 < 0 {
return None
}
let mut count = 0
let mut off = offset
if (b0 & 0x80) == 0 {
count = b0
off = off + 1
} else {
let b1 = metrics_read_u8(gvar, offset + 1).unwrap_or(-1)
if b1 < 0 {
return None
}
count = ((b0 & 0x7F) << 8) | b1
off = off + 2
}
if count == 0 {
return Some((None, off))
}
let points : Array[Int] = Array::new()
let mut last = 0
while points.length() < count {
let ctrl = metrics_read_u8(gvar, off).unwrap_or(-1)
if ctrl < 0 {
return None
}
off = off + 1
let run_len = (ctrl & 0x7F) + 1
let words = (ctrl & 0x80) != 0
for _ in 0..= count {
break
}
let delta = if words {
metrics_read_u16_be(gvar, off).unwrap_or(-1)
} else {
metrics_read_u8(gvar, off).unwrap_or(-1)
}
if delta < 0 {
return None
}
off = off + (if words { 2 } else { 1 })
last = last + delta
if last < 0 || last >= total_points {
return None
}
points.push(last)
}
}
Some((Some(points), off))
}
///|
fn metrics_gvar_deltas_capture(
gvar : BytesView,
offset : Int,
count : Int,
pos0 : Int,
pos1 : Int,
) -> (Int, Int, Int)? {
let mut off = offset
let mut idx = 0
let mut v0 = 0
let mut v1 = 0
while idx < count {
let ctrl = metrics_read_u8(gvar, off).unwrap_or(-1)
if ctrl < 0 {
return None
}
off = off + 1
let run_len = (ctrl & 0x3F) + 1
if idx + run_len > count {
return None
}
let words = (ctrl & 0x80) != 0
let zeros = !words && (ctrl & 0x40) != 0
if zeros {
for _ in 0.. Double {
let axis_count = peak.length()
let mut scalar = 1.0
for i in 0..
(
s.at(i).to_double() / METRICS_F2DOT14_SCALE,
e.at(i).to_double() / METRICS_F2DOT14_SCALE,
)
_ => if peak_v > 0.0 { (0.0, 1.0) } else { (-1.0, 0.0) }
}
if start_v > peak_v || peak_v > end_v || (start_v < 0.0 && end_v > 0.0) {
continue
}
if coord < start_v || coord > end_v {
return 0.0
}
if coord == peak_v {
continue
}
if coord < peak_v {
scalar = scalar * (coord - start_v) / (peak_v - start_v)
} else {
scalar = scalar * (end_v - coord) / (end_v - peak_v)
}
}
scalar
}
///|
fn GlyphMetrics::gvar_phantom_deltas(
self : GlyphMetrics,
gid : GlyphId,
) -> (Int, Int) {
if self.gvar is None || self.coords.is_empty() {
return (0, 0)
}
let gvar = self.gvar.unwrap()
let gid_i = gid.to_uint64().to_int()
if gid_i < 0 || gid_i >= self.gvar_glyph_count {
return (0, 0)
}
let base_points = match
metrics_glyf_point_count(self.font, self.glyph_count, gid) {
None => return (0, 0)
Some(v) => v
}
let total_points = base_points + 4
if total_points <= 0 {
return (0, 0)
}
let offsets_off = 20
let long_offsets = (self.gvar_flags & 1) != 0
let entry_size = if long_offsets { 4 } else { 2 }
let need = offsets_off + (self.gvar_glyph_count + 1) * entry_size
if need < 0 || need > gvar.length() {
return (0, 0)
}
let off0 = if long_offsets {
metrics_read_u32_be_int(gvar, offsets_off + gid_i * 4).unwrap_or(-1)
} else {
metrics_read_u16_be(gvar, offsets_off + gid_i * 2).unwrap_or(-1) * 2
}
let off1 = if long_offsets {
metrics_read_u32_be_int(gvar, offsets_off + (gid_i + 1) * 4).unwrap_or(-1)
} else {
metrics_read_u16_be(gvar, offsets_off + (gid_i + 1) * 2).unwrap_or(-1) * 2
}
if off0 < 0 || off1 < off0 {
return (0, 0)
}
let start = self.gvar_data_array_off + off0
let end = self.gvar_data_array_off + off1
if start < 0 || end < start || end > gvar.length() {
return (0, 0)
}
if start == end {
return (0, 0)
}
let tuple_count_flags = metrics_read_u16_be(gvar, start).unwrap_or(0)
let tuple_count = tuple_count_flags & 0x0FFF
let shared_points = (tuple_count_flags & 0x8000) != 0
let data_rel = metrics_read_u16_be(gvar, start + 2).unwrap_or(-1)
if tuple_count <= 0 || data_rel < 0 {
return (0, 0)
}
let headers_off = start + 4
let headers_end = headers_off + tuple_count * 4
if headers_end < headers_off || headers_end > end {
return (0, 0)
}
let sizes : Array[Int] = Array::new()
let tuple_indexes : Array[Int] = Array::new()
for i in 0.. end {
return (0, 0)
}
for ax in 0..= self.gvar_shared_tuple_count {
return (0, 0)
}
let base = self.gvar_shared_tuples_off + shared_ix * axis_count * 2
let need = base + axis_count * 2
if base < 0 || need < base || need > gvar.length() {
return (0, 0)
}
for ax in 0.. end {
return (0, 0)
}
for ax in 0.. end {
return (0, 0)
}
let phantom0 = total_points - 4
let phantom1 = total_points - 3
let mut cursor = data_off
let mut shared_points_list : Array[Int]? = None
if shared_points {
let (pts, off2) = match metrics_gvar_points(gvar, cursor, total_points) {
None => return (0, 0)
Some(v) => v
}
shared_points_list = pts
cursor = off2
}
let mut accum0 = 0.0
let mut accum1 = 0.0
for i in 0.. end {
return (0, 0)
}
let scalar = scalars.at(i)
// tupleIndex flag: PRIVATE_POINT_NUMBERS = 0x2000.
let private_points = (ti & 0x2000) != 0
let mut points : Array[Int]? = None
let mut after_points = cursor
if private_points || !shared_points {
let (pts, off2) = match metrics_gvar_points(gvar, cursor, total_points) {
None => return (0, 0)
Some(v) => v
}
points = pts
after_points = off2
} else {
points = shared_points_list
after_points = cursor
}
let count = match points {
None => total_points
Some(arr) => arr.length()
}
if count <= 0 {
cursor = tuple_end
continue
}
let mut pos0 = -1
let mut pos1 = -1
match points {
None => {
pos0 = phantom0
pos1 = phantom1
}
Some(arr) =>
for j in 0.. return (0, 0)
Some(v) => v
}
let (_, _, after_y) = match
metrics_gvar_deltas_capture(gvar, after_x, count, -1, -1) {
None => return (0, 0)
Some(v) => v
}
if after_y > tuple_end {
return (0, 0)
}
if scalar != 0.0 {
accum0 = accum0 + dx0.to_double() * scalar
accum1 = accum1 + dx1.to_double() * scalar
}
cursor = tuple_end
}
(accum0.round().to_int(), accum1.round().to_int())
}
///|
pub fn GlyphMetrics::advance_width(
self : GlyphMetrics,
gid : GlyphId,
) -> Double? {
let idx = gid.to_uint64().to_int()
if idx < 0 || idx >= self.glyph_count {
return None
}
let mut aw = match self.base {
None => 0
Some(base) => base.advance_width(gid).unwrap_or(0).to_uint64().to_int()
}
if !self.coords.is_empty() {
if self.hvar is Some(hvar) && self.hvar_aw_map_off is Some(_) {
aw = aw +
metrics_hvar_delta(
hvar,
self.hvar_store_off,
self.hvar_aw_map_off,
gid,
self.coords,
)
} else {
let (dx0, dx1) = self.gvar_phantom_deltas(gid)
aw = aw + (dx1 - dx0)
}
}
Some(aw.to_double() * self.scale)
}
///|
pub fn GlyphMetrics::left_side_bearing(
self : GlyphMetrics,
gid : GlyphId,
) -> Double? {
let idx = gid.to_uint64().to_int()
if idx < 0 || idx >= self.glyph_count {
return None
}
let mut lsb = match self.base {
None => 0
Some(base) => base.lsb(gid).unwrap_or(0)
}
if !self.coords.is_empty() {
if self.hvar is Some(hvar) && self.hvar_lsb_map_off is Some(_) {
lsb = lsb +
metrics_hvar_delta(
hvar,
self.hvar_store_off,
self.hvar_lsb_map_off,
gid,
self.coords,
)
} else {
let (dx0, _) = self.gvar_phantom_deltas(gid)
lsb = lsb + dx0
}
}
Some(lsb.to_double() * self.scale)
}
///|
pub fn FontRef::glyph_metrics(
self : FontRef,
size : Size,
location : LocationRef,
) -> GlyphMetrics {
GlyphMetrics(self, size, location)
}