// 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.
///|
/// Font and glyph metrics.
///
/// Ported from `swash/src/metrics.rs` (swash is dual-licensed Apache-2.0 OR MIT).
///|
const VERTICAL_KIND_VMTX_GLYF : UInt = 0
///|
const VERTICAL_KIND_VMTX_VORG : UInt = 1
///|
const VERTICAL_KIND_SYNTHESIZED : UInt = 2
///|
/// Proxy for rematerializing metrics.
struct MetricsProxy {
mut units_per_em : UInt16
mut glyph_count : UInt16
mut is_monospace : Bool
mut has_vertical_metrics : Bool
mut ascent : Int
mut descent : Int
mut leading : Int
mut vertical_ascent : Int
mut vertical_descent : Int
mut vertical_leading : Int
mut cap_height : Int
mut x_height : Int
mut average_width : UInt16
mut max_width : UInt16
mut underline_offset : Int
mut strikeout_offset : Int
mut stroke_size : Int
mut mvar : UInt
mut hmtx : UInt
mut hvar : UInt
mut hmtx_count : UInt
mut has_vvar : Bool
mut vertical_kind : UInt
mut vertical_loca_fmt : UInt
mut vertical_long_count : UInt
mut vertical_vmtx : UInt
mut vertical_vvar : UInt
mut vertical_glyf : UInt
mut vertical_loca : UInt
mut vertical_vorg : UInt
mut vertical_synth_mvar : UInt
mut vertical_synth_advance : Double
mut vertical_synth_origin : Double
}
///|
pub fn MetricsProxy::from_font(font : FontRef) -> MetricsProxy {
let m = MetricsProxy::{
units_per_em: (1).to_uint16(),
glyph_count: (0).to_uint16(),
is_monospace: false,
has_vertical_metrics: false,
ascent: 0,
descent: 0,
leading: 0,
vertical_ascent: 0,
vertical_descent: 0,
vertical_leading: 0,
cap_height: 0,
x_height: 0,
average_width: (0).to_uint16(),
max_width: (0).to_uint16(),
underline_offset: 0,
strikeout_offset: 0,
stroke_size: 0,
mvar: 0,
hmtx: 0,
hvar: 0,
hmtx_count: 0,
has_vvar: false,
vertical_kind: VERTICAL_KIND_SYNTHESIZED,
vertical_loca_fmt: 0,
vertical_long_count: 0,
vertical_vmtx: 0,
vertical_vvar: 0,
vertical_glyf: 0,
vertical_loca: 0,
vertical_vorg: 0,
vertical_synth_mvar: 0,
vertical_synth_advance: 0.0,
vertical_synth_origin: 0.0,
}
m.fill(font) |> ignore
m
}
///|
/// Materializes font metrics for the specified font and normalized variation
/// coordinates. This proxy must have been created from the same font.
pub fn MetricsProxy::materialize_metrics(
self : MetricsProxy,
font : FontRef,
coords : ArrayView[NormalizedCoord],
) -> Metrics {
let mut ascent = self.ascent.to_double()
let mut descent = self.descent.to_double()
let mut leading = self.leading.to_double()
let mut vertical_ascent = self.vertical_ascent.to_double()
let mut vertical_descent = self.vertical_descent.to_double()
let mut vertical_leading = self.vertical_leading.to_double()
let mut cap_height = self.cap_height.to_double()
let mut x_height = self.x_height.to_double()
let average_width = self.average_width.to_int().to_double()
let max_width = self.max_width.to_int().to_double()
let mut underline_offset = self.underline_offset.to_double()
let mut strikeout_offset = self.strikeout_offset.to_double()
let mut stroke_size = self.stroke_size.to_double()
if self.mvar != 0 && coords.length() > 0 {
match @internal.Mvar::from_data(font.data(), self.mvar, coords) {
None => ()
Some(v) => {
ascent = ascent + v.delta(@internal.HASC)
descent = descent + v.delta(@internal.HDSC)
leading = leading + v.delta(@internal.HLGP)
if self.has_vertical_metrics {
vertical_ascent = vertical_ascent + v.delta(@internal.VASC)
vertical_descent = vertical_descent + v.delta(@internal.VDSC)
vertical_leading = vertical_leading + v.delta(@internal.VLGP)
}
cap_height = cap_height + v.delta(@internal.CPHT)
x_height = x_height + v.delta(@internal.XHGT)
underline_offset = underline_offset + v.delta(@internal.UNDO)
strikeout_offset = strikeout_offset + v.delta(@internal.STRO)
stroke_size = stroke_size + v.delta(@internal.UNDS)
}
}
}
Metrics::{
units_per_em: self.units_per_em,
glyph_count: self.glyph_count,
is_monospace: self.is_monospace,
has_vertical_metrics: self.has_vertical_metrics,
ascent,
descent,
leading,
vertical_ascent,
vertical_descent,
vertical_leading,
cap_height,
x_height,
average_width,
max_width,
underline_offset,
strikeout_offset,
stroke_size,
}
}
///|
/// Materializes glyph metrics for the specified font and normalized variation
/// coordinates. This proxy must have been created from the same font.
pub fn MetricsProxy::materialize_glyph_metrics(
self : MetricsProxy,
font : FontRef,
coords : ArrayView[NormalizedCoord],
) -> GlyphMetrics {
let kind = self.vertical_kind
let mut synth_advance = self.vertical_synth_advance
let mut synth_origin = self.vertical_synth_origin
if coords.length() > 0 &&
kind == VERTICAL_KIND_SYNTHESIZED &&
self.vertical_synth_mvar != 0 {
match
@internal.Mvar::from_data(font.data(), self.vertical_synth_mvar, coords) {
None => ()
Some(v) => {
let ascent_delta = v.delta(@internal.HASC)
let descent_delta = v.delta(@internal.HDSC)
synth_advance = synth_advance + ascent_delta + descent_delta
synth_origin = synth_origin + ascent_delta
}
}
}
GlyphMetrics::{
data: font.data(),
coords,
units_per_em: self.units_per_em,
glyph_count: self.glyph_count,
hmtx: self.hmtx,
hvar: self.hvar,
hmtx_count: self.hmtx_count,
has_vvar: self.has_vvar,
vertical_kind: kind,
vertical_loca_fmt: self.vertical_loca_fmt,
vertical_long_count: self.vertical_long_count,
vertical_vmtx: self.vertical_vmtx,
vertical_vvar: self.vertical_vvar,
vertical_glyf: self.vertical_glyf,
vertical_loca: self.vertical_loca,
vertical_vorg: self.vertical_vorg,
vertical_synth_advance: synth_advance,
vertical_synth_origin: synth_origin,
scale: 1.0,
}
}
///|
pub fn MetricsProxy::units_per_em(self : MetricsProxy) -> UInt16 {
self.units_per_em
}
///|
pub fn MetricsProxy::glyph_count(self : MetricsProxy) -> UInt16 {
self.glyph_count
}
///|
fn MetricsProxy::fill(self : MetricsProxy, font : FontRef) -> Unit? {
let head = match font.head() {
None => return None
Some(t) => t
}
self.units_per_em = head.units_per_em().to_uint16()
self.glyph_count = match font.maxp() {
None => return None
Some(t) => t.glyph_count().to_uint16()
}
let mut have_line_metrics = false
match font.os2() {
None => ()
Some(os2) => {
let flags = os2.selection_flags()
self.average_width = os2.average_char_width().to_uint16()
self.strikeout_offset = os2.strikeout_position()
self.stroke_size = os2.strikeout_size()
self.x_height = os2.x_height()
self.cap_height = os2.cap_height()
if flags.use_typographic_metrics() {
self.ascent = os2.typographic_ascender()
self.descent = -os2.typographic_descender()
self.leading = os2.typographic_line_gap()
have_line_metrics = self.ascent != 0
}
}
}
let hhea = font.hhea()
match hhea {
None => ()
Some(hhea) => {
self.max_width = hhea.max_advance().to_uint16()
if !have_line_metrics {
self.ascent = hhea.ascender()
self.descent = -hhea.descender()
self.leading = hhea.line_gap()
}
}
}
let vhea = font.vhea()
match vhea {
Some(vhea) => {
self.has_vertical_metrics = true
self.vertical_ascent = vhea.ascender()
self.vertical_descent = -vhea.descender()
self.vertical_leading = vhea.line_gap()
}
None => {
let half = self.units_per_em.to_int() / 2
self.vertical_ascent = half
self.vertical_descent = half
}
}
match font.post() {
None => ()
Some(post) => {
self.underline_offset = post.underline_position()
self.stroke_size = post.underline_size()
self.is_monospace = post.is_fixed_pitch()
}
}
self.mvar = font.table_offset(@internal.MVAR)
self.hmtx_count = match hhea {
None => (1).reinterpret_as_uint()
Some(t) => t.num_long_metrics()
}
self.hmtx = font.table_offset(@internal.HMTX)
self.hvar = font.table_offset(@internal.HVAR)
let mut vmtx : UInt = 0
if vhea is Some(_) {
vmtx = font.table_offset(@internal.VMTX)
}
if vmtx != 0 {
let long_count = match vhea {
None => (0).reinterpret_as_uint()
Some(t) => t.num_long_metrics()
}
let vvar = font.table_offset(@internal.VVAR)
self.has_vvar = vvar != 0
let vorg = font.table_offset(@internal.VORG)
if vorg != 0 {
self.vertical_kind = VERTICAL_KIND_VMTX_VORG
self.vertical_long_count = long_count
self.vertical_vmtx = vmtx
self.vertical_vvar = vvar
self.vertical_vorg = vorg
} else {
let glyf = font.table_offset(@internal.GLYF)
let loca = font.table_offset(@internal.LOCA)
let loca_fmt = font
.head()
.map(t => t.index_to_location_format())
.unwrap_or((0xFF).reinterpret_as_uint())
if glyf != 0 && loca != 0 && loca_fmt != (0xFF).reinterpret_as_uint() {
self.vertical_kind = VERTICAL_KIND_VMTX_GLYF
self.vertical_loca_fmt = loca_fmt
self.vertical_long_count = long_count
self.vertical_vmtx = vmtx
self.vertical_vvar = vvar
self.vertical_glyf = glyf
self.vertical_loca = loca
}
}
} else {
self.vertical_kind = VERTICAL_KIND_SYNTHESIZED
self.vertical_synth_mvar = self.mvar
self.vertical_synth_advance = self.ascent.to_double() +
self.descent.to_double()
self.vertical_synth_origin = self.ascent.to_double()
}
Some(())
}
///|
/// Global font metrics.
pub struct Metrics {
units_per_em : UInt16
glyph_count : UInt16
is_monospace : Bool
has_vertical_metrics : Bool
ascent : Double
descent : Double
leading : Double
vertical_ascent : Double
vertical_descent : Double
vertical_leading : Double
cap_height : Double
x_height : Double
average_width : Double
max_width : Double
underline_offset : Double
strikeout_offset : Double
stroke_size : Double
}
///|
fn Metrics::from_font(
font : FontRef,
coords : ArrayView[NormalizedCoord],
) -> Metrics {
let proxy = MetricsProxy::from_font(font)
proxy.materialize_metrics(font, coords)
}
///|
/// Creates a new set of metrics scaled for the specified pixels per em unit.
pub fn Metrics::scale(self : Metrics, ppem : Double) -> Metrics {
self.linear_scale(
if self.units_per_em != 0 {
ppem / self.units_per_em.to_int().to_double()
} else {
1.0
},
)
}
///|
/// Creates a new set of metrics scaled by the specified factor.
pub fn Metrics::linear_scale(self : Metrics, s : Double) -> Metrics {
Metrics::{
units_per_em: self.units_per_em,
glyph_count: self.glyph_count,
is_monospace: self.is_monospace,
has_vertical_metrics: self.has_vertical_metrics,
ascent: self.ascent * s,
descent: self.descent * s,
leading: self.leading * s,
vertical_ascent: self.vertical_ascent * s,
vertical_descent: self.vertical_descent * s,
vertical_leading: self.vertical_leading * s,
cap_height: self.cap_height * s,
x_height: self.x_height * s,
average_width: self.average_width * s,
max_width: self.max_width * s,
underline_offset: self.underline_offset * s,
strikeout_offset: self.strikeout_offset * s,
stroke_size: self.stroke_size * s,
}
}
///|
/// Glyph advances, side bearings and vertical origins.
struct GlyphMetrics {
data : Bytes
coords : ArrayView[NormalizedCoord]
units_per_em : UInt16
glyph_count : UInt16
hmtx : UInt
hvar : UInt
hmtx_count : UInt
has_vvar : Bool
vertical_kind : UInt
vertical_loca_fmt : UInt
vertical_long_count : UInt
vertical_vmtx : UInt
vertical_vvar : UInt
vertical_glyf : UInt
vertical_loca : UInt
vertical_vorg : UInt
vertical_synth_advance : Double
vertical_synth_origin : Double
mut scale : Double
}
///|
fn GlyphMetrics::from_font(
font : FontRef,
coords : ArrayView[NormalizedCoord],
) -> GlyphMetrics {
let proxy = MetricsProxy::from_font(font)
proxy.materialize_glyph_metrics(font, coords)
}
///|
pub fn GlyphMetrics::units_per_em(self : GlyphMetrics) -> UInt16 {
self.units_per_em
}
///|
pub fn GlyphMetrics::glyph_count(self : GlyphMetrics) -> UInt16 {
self.glyph_count
}
///|
/// Returns true if the font provides canonical vertical glyph metrics.
pub fn GlyphMetrics::has_vertical_metrics(self : GlyphMetrics) -> Bool {
self.vertical_kind != VERTICAL_KIND_SYNTHESIZED
}
///|
/// Returns true if variations are supported.
pub fn GlyphMetrics::has_variations(self : GlyphMetrics) -> Bool {
self.hvar != 0 || self.has_vvar
}
///|
/// Creates a new set of metrics scaled for the specified pixels per em unit.
pub fn GlyphMetrics::scale(self : GlyphMetrics, ppem : Double) -> GlyphMetrics {
self.linear_scale(
if self.units_per_em != 0 {
ppem / self.units_per_em.to_int().to_double()
} else {
1.0
},
)
}
///|
/// Creates a new set of metrics scaled by the specified factor.
pub fn GlyphMetrics::linear_scale(
self : GlyphMetrics,
scale : Double,
) -> GlyphMetrics {
let copy = self
copy.scale = scale
copy
}
///|
/// Returns the horizontal advance for the specified glyph.
pub fn GlyphMetrics::advance_width(
self : GlyphMetrics,
glyph_id : GlyphId,
) -> Double {
let mut v = @internal.advance(
self.data,
self.hmtx,
self.hmtx_count,
glyph_id.to_uint(),
)
.reinterpret_as_int()
.to_double()
if self.hvar != 0 {
v = v +
@internal.advance_delta(
self.data,
self.hvar,
glyph_id.to_uint(),
self.coords,
)
}
v * self.scale
}
///|
/// Returns the left side bearing for the specified glyph.
pub fn GlyphMetrics::lsb(self : GlyphMetrics, glyph_id : GlyphId) -> Double {
let mut v = @internal.sb(
self.data,
self.hmtx,
self.hmtx_count,
glyph_id.to_uint(),
).to_double()
if self.hvar != 0 {
v = v +
@internal.sb_delta(self.data, self.hvar, glyph_id.to_uint(), self.coords)
}
v * self.scale
}
///|
/// Returns the vertical advance for the specified glyph.
pub fn GlyphMetrics::advance_height(
self : GlyphMetrics,
glyph_id : GlyphId,
) -> Double {
let mut v = self.vertical_synth_advance
if self.vertical_kind == VERTICAL_KIND_VMTX_GLYF ||
self.vertical_kind == VERTICAL_KIND_VMTX_VORG {
v = @internal.advance(
self.data,
self.vertical_vmtx,
self.vertical_long_count,
glyph_id.to_uint(),
)
.reinterpret_as_int()
.to_double()
if self.vertical_vvar != 0 {
v = v +
@internal.advance_delta(
self.data,
self.vertical_vvar,
glyph_id.to_uint(),
self.coords,
)
}
}
v * self.scale
}
///|
/// Returns the top side bearing for the specified glyph.
pub fn GlyphMetrics::tsb(self : GlyphMetrics, glyph_id : GlyphId) -> Double {
let mut v = 0.0
if self.vertical_kind == VERTICAL_KIND_VMTX_GLYF ||
self.vertical_kind == VERTICAL_KIND_VMTX_VORG {
v = @internal.sb(
self.data,
self.vertical_vmtx,
self.vertical_long_count,
glyph_id.to_uint(),
).to_double()
if self.vertical_vvar != 0 {
v = v +
@internal.sb_delta(
self.data,
self.vertical_vvar,
glyph_id.to_uint(),
self.coords,
)
}
}
v * self.scale
}
///|
/// Returns the vertical origin for the specified glyph id.
pub fn GlyphMetrics::vertical_origin(
self : GlyphMetrics,
glyph_id : GlyphId,
) -> Double {
let mut v = self.vertical_synth_origin
if self.vertical_kind == VERTICAL_KIND_VMTX_GLYF {
v = match
@internal.ymax(
self.data,
self.vertical_loca_fmt,
self.vertical_loca,
self.vertical_glyf,
glyph_id.to_uint(),
) {
None => self.units_per_em.to_int().to_double()
Some(max_y) => max_y.to_double() + self.tsb(glyph_id)
}
} else if self.vertical_kind == VERTICAL_KIND_VMTX_VORG {
v = match
@internal.origin(self.data, self.vertical_vorg, glyph_id.to_uint()) {
None => self.units_per_em.to_int().to_double()
Some(o) => o.to_double()
}
}
v * self.scale
}
///|
/// Returns metrics for the font and the specified normalized variation
/// coordinates.
pub fn FontRef::metrics(
self : FontRef,
coords : ArrayView[NormalizedCoord],
) -> Metrics {
Metrics::from_font(self, coords)
}
///|
/// Returns glyph metrics for the font and the specified normalized variation
/// coordinates.
pub fn FontRef::glyph_metrics(
self : FontRef,
coords : ArrayView[NormalizedCoord],
) -> GlyphMetrics {
GlyphMetrics::from_font(self, coords)
}