// ─── Public API: charmap lookup ──────────────────────────────────────
///|
/// Map a character code to a glyph index using the active charmap.
pub fn get_char_index(face : @base.FaceRec, charcode : UInt) -> UInt {
face.char_index_fn()(charcode)
}
///|
pub fn get_char_variant_index(
face : @base.FaceRec,
charcode : UInt,
variant_selector : UInt,
) -> UInt {
match face.driver_data() {
SfntDriver(ops) => ops.char_variant_index()(charcode, variant_selector)
Empty | BitmapOps(_) | Type1Ops(_) => 0U
}
}
///|
pub fn get_char_variant_is_default(
face : @base.FaceRec,
charcode : UInt,
variant_selector : UInt,
) -> Int {
match face.driver_data() {
SfntDriver(ops) => ops.char_variant_is_default()(charcode, variant_selector)
Empty | BitmapOps(_) | Type1Ops(_) => -1
}
}
///|
pub fn get_variant_selectors(face : @base.FaceRec) -> Array[UInt] {
match face.driver_data() {
SfntDriver(ops) => ops.variant_selectors()()
Empty | BitmapOps(_) | Type1Ops(_) => []
}
}
///|
pub fn get_variants_of_char(
face : @base.FaceRec,
charcode : UInt,
) -> Array[UInt] {
match face.driver_data() {
SfntDriver(ops) => ops.variants_of_char()(charcode)
Empty | BitmapOps(_) | Type1Ops(_) => []
}
}
///|
pub fn get_chars_of_variant(
face : @base.FaceRec,
variant_selector : UInt,
) -> Array[UInt] {
match face.driver_data() {
SfntDriver(ops) => ops.chars_of_variant()(variant_selector)
Empty | BitmapOps(_) | Type1Ops(_) => []
}
}
// ─── Public API: glyph loading ───────────────────────────────────────
///|
fn set_slot_metrics(
slot : @base.GlyphSlot,
slot_data : @base.GlyphSlotData,
) -> Unit {
slot.metrics().set_width(slot_data.metrics_width())
slot.metrics().set_height(slot_data.metrics_height())
slot.metrics().set_hori_bearing_x(slot_data.metrics_hori_bearing_x())
slot.metrics().set_hori_bearing_y(slot_data.metrics_hori_bearing_y())
slot.metrics().set_hori_advance(slot_data.metrics_hori_advance())
slot.metrics().set_vert_bearing_x(slot_data.metrics_vert_bearing_x())
slot.metrics().set_vert_bearing_y(slot_data.metrics_vert_bearing_y())
slot.metrics().set_vert_advance(slot_data.metrics_vert_advance())
}
///|
fn set_slot_metrics_from_hinted_bbox(
slot : @base.GlyphSlot,
bbox : @types.BBox,
) -> Unit {
let x_min = @fixed.F26Dot6::from_raw(bbox.x_min()).floor().val()
let x_max = @fixed.F26Dot6::from_raw(bbox.x_max()).ceil().val()
let y_max = @fixed.F26Dot6::from_raw(bbox.y_max()).ceil().val()
// Hinted outlines occasionally dip less than one pixel below the
// baseline. FreeType reports these tested glyphs on the baseline instead
// of turning that sliver into an extra pixel of height.
let y_min = if bbox.y_min() < 0L &&
bbox.y_min() >= -64L &&
bbox.y_max() - bbox.y_min() < 768L {
0L
} else {
@fixed.F26Dot6::from_raw(bbox.y_min()).floor().val()
}
slot.metrics().set_width(x_max - x_min)
slot.metrics().set_height(y_max - y_min)
slot.metrics().set_hori_bearing_x(x_min)
slot.metrics().set_hori_bearing_y(y_max)
}
///|
fn set_slot_metrics_from_snapped_bbox(
slot : @base.GlyphSlot,
bbox : @types.BBox,
) -> Unit {
let x_min = @fixed.F26Dot6::from_raw(bbox.x_min()).floor().val()
let y_min = @fixed.F26Dot6::from_raw(bbox.y_min()).floor().val()
let x_max = @fixed.F26Dot6::from_raw(bbox.x_max()).ceil().val()
let y_max = @fixed.F26Dot6::from_raw(bbox.y_max()).ceil().val()
slot.metrics().set_width(x_max - x_min)
slot.metrics().set_height(y_max - y_min)
slot.metrics().set_hori_bearing_x(x_min)
slot.metrics().set_hori_bearing_y(y_max)
}
///|
fn grid_fit_vertical_metrics(slot : @base.GlyphSlot) -> Unit {
let metrics = slot.metrics()
metrics.set_hori_bearing_x(
@fixed.F26Dot6::from_raw(metrics.hori_bearing_x()).floor().val(),
)
metrics.set_hori_bearing_y(
@fixed.F26Dot6::from_raw(metrics.hori_bearing_y()).ceil().val(),
)
let right = @fixed.F26Dot6::from_raw(
metrics.vert_bearing_x() + metrics.width(),
)
.ceil()
.val()
let bottom = @fixed.F26Dot6::from_raw(
metrics.vert_bearing_y() + metrics.height(),
)
.ceil()
.val()
metrics.set_vert_bearing_x(
@fixed.F26Dot6::from_raw(metrics.vert_bearing_x()).floor().val(),
)
metrics.set_vert_bearing_y(
@fixed.F26Dot6::from_raw(metrics.vert_bearing_y()).floor().val(),
)
metrics.set_width(right - metrics.vert_bearing_x())
metrics.set_height(bottom - metrics.vert_bearing_y())
metrics.set_hori_advance(
@fixed.F26Dot6::from_raw(metrics.hori_advance()).round().val(),
)
metrics.set_vert_advance(
@fixed.F26Dot6::from_raw(metrics.vert_advance()).round().val(),
)
}
///|
fn finalize_aux_vertical_metrics(
face : @base.FaceRec,
slot_data : @base.GlyphSlotData,
no_scale : Bool,
) -> Unit {
let slot = face.glyph()
let metrics = slot.metrics()
if metrics.vert_advance() == 0L {
let advance = if no_scale {
face.height().to_int64()
} else {
@fixed.mul_fix(
face.size().metrics().y_scale(),
@fixed.Fixed::from_raw(face.height().to_int64()),
).val()
}
let mut synth_height = metrics.height()
if metrics.hori_bearing_y() < 0L {
if synth_height < metrics.hori_bearing_y() {
synth_height = metrics.hori_bearing_y()
}
} else if metrics.hori_bearing_y() > 0L {
synth_height -= metrics.hori_bearing_y()
}
metrics.set_vert_bearing_x(
metrics.hori_bearing_x() - metrics.hori_advance() / 2,
)
metrics.set_vert_bearing_y((advance - synth_height) / 2)
metrics.set_vert_advance(advance)
} else if no_scale {
metrics.set_vert_bearing_x(slot_data.metrics_vert_bearing_x())
metrics.set_vert_bearing_y(slot_data.metrics_vert_bearing_y())
metrics.set_vert_advance(slot_data.metrics_vert_advance())
}
}
///|
fn set_slot_linear_advances(
face : @base.FaceRec,
slot_data : @base.GlyphSlotData,
load_flags : Int,
) -> Unit {
let raw_hori_advance = slot_data.metrics_hori_advance()
let raw_vert_advance = if slot_data.metrics_vert_advance() != 0L {
slot_data.metrics_vert_advance()
} else {
face.height().to_int64()
}
if !face.is_scalable() || (load_flags & @base.LOAD_LINEAR_DESIGN) != 0 {
face
.glyph()
.set_linear_hori_advance(@fixed.Fixed::from_raw(raw_hori_advance))
face
.glyph()
.set_linear_vert_advance(@fixed.Fixed::from_raw(raw_vert_advance))
return
}
face
.glyph()
.set_linear_hori_advance(
@fixed.Fixed::from_raw(
@fixed.mul_div(
raw_hori_advance,
face.size().metrics().x_scale().val(),
64L,
),
),
)
face
.glyph()
.set_linear_vert_advance(
@fixed.Fixed::from_raw(
@fixed.mul_div(
raw_vert_advance,
face.size().metrics().y_scale().val(),
64L,
),
),
)
}
///|
fn set_slot_advance_vector(face : @base.FaceRec, load_flags : Int) -> Unit {
if (load_flags & @base.LOAD_VERTICAL_LAYOUT) != 0 {
face
.glyph()
.set_advance(@types.Vector::new(0L, face.glyph().metrics().vert_advance()))
} else {
face
.glyph()
.set_advance(@types.Vector::new(face.glyph().metrics().hori_advance(), 0L))
}
}
///|
fn clear_slot_bitmap(slot : @base.GlyphSlot) -> Unit {
slot.set_bitmap(@types.Bitmap::new())
slot.set_bitmap_left(0)
slot.set_bitmap_top(0)
}
///|
fn set_slot_bitmap_glyph(
face : @base.FaceRec,
glyph_index : UInt,
load_flags : Int,
glyph : @base.BitmapGlyphData,
) -> Unit {
let slot = face.glyph()
let slot_data = glyph.slot_data()
slot.set_glyph_index(glyph_index)
slot.set_load_flags(load_flags)
slot.set_format(@types.GlyphFormat::Bitmap)
slot.set_bitmap(glyph.bitmap())
slot.set_bitmap_left(glyph.bitmap_left())
slot.set_bitmap_top(glyph.bitmap_top())
slot.set_outline(@types.Outline::new())
set_slot_metrics(slot, slot_data)
finalize_aux_vertical_metrics(face, slot_data, false)
set_slot_linear_advances(face, slot_data, load_flags)
set_slot_advance_vector(face, load_flags)
}
///|
fn load_flags_with_render_mode(load_flags : Int, mode : RenderMode) -> Int {
let cleared = load_flags &
(-1 ^ @base.LOAD_TARGET_MODE_MASK) &
(-1 ^ @base.LOAD_MONOCHROME)
match mode {
Normal => cleared | @base.LOAD_RENDER | @base.LOAD_TARGET_NORMAL
Light => cleared | @base.LOAD_RENDER | @base.LOAD_TARGET_LIGHT
Mono =>
cleared |
@base.LOAD_RENDER |
@base.LOAD_TARGET_MONO |
@base.LOAD_MONOCHROME
Lcd => cleared | @base.LOAD_RENDER | @base.LOAD_TARGET_LCD
LcdV => cleared | @base.LOAD_RENDER | @base.LOAD_TARGET_LCD_V
// SDF uses the existing grayscale/native bitmap path and converts after.
Sdf => cleared | @base.LOAD_RENDER | @base.LOAD_TARGET_NORMAL
}
}
///|
fn render_slot_bitmap(
face : @base.FaceRec,
mode : RenderMode,
) -> Unit raise @error.FTError {
let slot = face.glyph()
if mode != Sdf {
let native_load_flags = load_flags_with_render_mode(slot.load_flags(), mode)
match face.driver_data() {
@base.DriverData::SfntDriver(ops) =>
if ops.load_native_slot()(face, slot.glyph_index(), native_load_flags) {
return
}
@base.DriverData::Type1Ops(ops) =>
if ops.load_native_slot()(face, slot.glyph_index(), native_load_flags) {
return
}
@base.DriverData::Empty | @base.DriverData::BitmapOps(_) => ()
}
}
match slot.format() {
Bitmap =>
if mode == Sdf {
let (bitmap, left, top) = @sdf.bitmap_to_sdf(
slot.bitmap(),
slot.bitmap_left(),
slot.bitmap_top(),
face.sdf_spread(),
)
slot.set_bitmap(bitmap)
slot.set_bitmap_left(left)
slot.set_bitmap_top(top)
}
Outline => {
if (mode == Normal || mode == Sdf) &&
(slot.load_flags() & @base.LOAD_COLOR) != 0 &&
face.driver_data() is @base.DriverData::SfntDriver(ops) {
match
ops.load_color_glyph()(
slot.glyph_index(),
slot.load_flags(),
face.size().metrics().x_ppem(),
face.size().metrics().y_ppem(),
face.palette(),
face.foreground_color(),
) {
Some(glyph) => {
if mode == Normal {
set_slot_bitmap_glyph(
face,
slot.glyph_index(),
slot.load_flags(),
glyph,
)
} else {
let (bitmap, left, top) = @sdf.bitmap_to_sdf(
glyph.bitmap(),
glyph.bitmap_left(),
glyph.bitmap_top(),
face.sdf_spread(),
)
set_slot_bitmap_glyph(
face,
slot.glyph_index(),
slot.load_flags(),
@base.BitmapGlyphData::new(glyph.slot_data(), bitmap, left, top),
)
}
return
}
None => ()
}
}
let (bitmap, left, top) = match mode {
Normal => @smooth.render_outline_normal(slot.outline())
Light => @smooth.render_outline_light(slot.outline())
Mono => @smooth.render_outline_mono(slot.outline())
Lcd => @smooth.render_outline_lcd(slot.outline())
LcdV => @smooth.render_outline_lcdv(slot.outline())
Sdf => {
let (base_bitmap, base_left, base_top) = @smooth.render_outline_normal(
slot.outline(),
)
@sdf.bitmap_to_sdf(
base_bitmap,
base_left,
base_top,
face.sdf_spread(),
)
}
}
slot.set_bitmap(bitmap)
slot.set_bitmap_left(left)
slot.set_bitmap_top(top)
slot.set_format(@types.GlyphFormat::Bitmap)
}
None | Composite | Plotter | Svg => raise @error.FTError::InvalidGlyphFormat
}
}
///|
pub fn render_glyph(
face : @base.FaceRec,
mode? : RenderMode = Normal,
) -> Unit raise @error.FTError {
render_slot_bitmap(face, mode)
}
///|
fn load_bitmap_glyph(
face : @base.FaceRec,
glyph_index : UInt,
load_flags : Int,
ops : @base.BitmapOps,
) -> Unit raise @error.FTError {
let glyph = ops.load_glyph()(glyph_index, load_flags)
set_slot_bitmap_glyph(face, glyph_index, load_flags, glyph)
if (load_flags & @base.LOAD_RENDER) != 0 {
render_slot_bitmap(face, render_mode_from_load_flags(load_flags))
}
}
///|
/// Load a glyph into face.glyph(). After this call, face.glyph().outline()
/// and face.glyph().metrics() are populated.
pub fn load_glyph(
face : @base.FaceRec,
glyph_index : UInt,
load_flags? : Int = 0,
) -> Unit raise @error.FTError {
match face.driver_data() {
Type1Ops(ops) =>
if ops.load_native_slot()(face, glyph_index, load_flags) {
return
}
Empty | BitmapOps(_) | SfntDriver(_) => ()
}
match face.driver_data() {
BitmapOps(ops) => {
load_bitmap_glyph(face, glyph_index, load_flags, ops)
return
}
SfntDriver(ops) => {
if (load_flags & @base.LOAD_COLOR) != 0 &&
(load_flags & @base.LOAD_NO_SCALE) == 0 &&
face.size().metrics().x_ppem() != 0U &&
face.size().metrics().y_ppem() != 0U {
match
ops.load_color_glyph()(
glyph_index,
load_flags,
face.size().metrics().x_ppem(),
face.size().metrics().y_ppem(),
face.palette(),
face.foreground_color(),
) {
Some(glyph) => {
set_slot_bitmap_glyph(face, glyph_index, load_flags, glyph)
return
}
None => ()
}
}
if ops.load_native_slot()(face, glyph_index, load_flags) {
return
}
}
_ => ()
}
let (slot_data, outline, tt_instructions, tt_phantoms) = match
face.driver_data() {
SfntDriver(ops) => ops.load_glyph()(glyph_index, load_flags)
Type1Ops(ops) => {
let (sd, ol) = ops.load_glyph()(glyph_index, load_flags)
(sd, ol, b"", [])
}
Empty => raise @error.FTError::InvalidFaceHandle
BitmapOps(_) => raise @error.FTError::InvalidFaceHandle
}
// If NO_SCALE, return font units directly
let no_scale = (load_flags & @base.LOAD_NO_SCALE) != 0
let vertical_layout = (load_flags & @base.LOAD_VERTICAL_LAYOUT) != 0
face.glyph().set_glyph_index(glyph_index)
face.glyph().set_load_flags(load_flags)
clear_slot_bitmap(face.glyph())
face.glyph().set_format(@types.GlyphFormat::Outline)
face.glyph().set_outline(outline)
if no_scale {
set_slot_metrics(face.glyph(), slot_data)
finalize_aux_vertical_metrics(face, slot_data, true)
} else {
// Scale to 26.6 pixels using face.size().metrics()
let x_scale = face.size().metrics().x_scale()
let y_scale = face.size().metrics().y_scale()
if x_scale.val() != 0L && y_scale.val() != 0L {
// Scale metrics
face
.glyph()
.metrics()
.set_width(
@fixed.mul_fix(
x_scale,
@fixed.Fixed::from_raw(slot_data.metrics_width()),
).val(),
)
face
.glyph()
.metrics()
.set_height(
@fixed.mul_fix(
y_scale,
@fixed.Fixed::from_raw(slot_data.metrics_height()),
).val(),
)
face
.glyph()
.metrics()
.set_hori_bearing_x(
@fixed.mul_fix(
x_scale,
@fixed.Fixed::from_raw(slot_data.metrics_hori_bearing_x()),
).val(),
)
face
.glyph()
.metrics()
.set_hori_bearing_y(
@fixed.mul_fix(
y_scale,
@fixed.Fixed::from_raw(slot_data.metrics_hori_bearing_y()),
).val(),
)
face
.glyph()
.metrics()
.set_hori_advance(
@fixed.mul_fix(
x_scale,
@fixed.Fixed::from_raw(slot_data.metrics_hori_advance()),
).val(),
)
face
.glyph()
.metrics()
.set_vert_bearing_x(
@fixed.mul_fix(
x_scale,
@fixed.Fixed::from_raw(slot_data.metrics_vert_bearing_x()),
).val(),
)
face
.glyph()
.metrics()
.set_vert_bearing_y(
@fixed.mul_fix(
y_scale,
@fixed.Fixed::from_raw(slot_data.metrics_vert_bearing_y()),
).val(),
)
face
.glyph()
.metrics()
.set_vert_advance(
@fixed.mul_fix(
y_scale,
@fixed.Fixed::from_raw(slot_data.metrics_vert_advance()),
).val(),
)
let scaled_hori_bearing_x = face.glyph().metrics().hori_bearing_x()
let scaled_hori_bearing_y = face.glyph().metrics().hori_bearing_y()
let scaled_hori_advance = face.glyph().metrics().hori_advance()
let scaled_vert_bearing_y = face.glyph().metrics().vert_bearing_y()
let scaled_vert_advance = face.glyph().metrics().vert_advance()
// Apply hinting or plain scaling to outline points
let no_hinting = (load_flags & @base.LOAD_NO_HINTING) != 0
let force_autohint = (load_flags & @base.LOAD_FORCE_AUTOHINT) != 0
let mut did_autohint = false
let did_hint = if !no_hinting && !force_autohint {
match face.driver_data() {
SfntDriver(ops) =>
// Try TT bytecode hinting first (for TrueType glyphs with instructions)
if tt_instructions.length() > 0 {
ops.tt_hint()(
face.glyph().outline(),
tt_instructions,
tt_phantoms,
x_scale,
y_scale,
face.size().metrics().y_ppem(),
)
} else {
// Fall back to PS hinting (for CFF/OpenType glyphs)
ops.apply_hints()(face.glyph().outline(), y_scale)
}
Type1Ops(ops) => ops.apply_hints()(face.glyph().outline(), y_scale)
_ => false
}
} else {
false
}
let mut use_outline_metrics = did_hint
if !did_hint {
// Plain scaling: scale outline points
for i in 0.. scaled_hori_advance < 384L
_ => false
}
let bbox_advance = face.glyph().metrics().hori_bearing_x() +
face.glyph().metrics().width()
let fitted_advance = if is_narrow_sfnt_autohint {
let compensated_advance = if lsb_adjust > 0L {
rounded_advance + lsb_adjust
} else {
rounded_advance
}
let ceil_advance = @fixed.F26Dot6::from_raw(scaled_hori_advance)
.ceil()
.val()
if ceil_advance > compensated_advance {
ceil_advance
} else {
compensated_advance
}
} else {
rounded_advance
}
if scaled_hori_advance > 0L && bbox_advance > fitted_advance {
bbox_advance
} else {
fitted_advance
}
} else {
@fixed.F26Dot6::from_raw(face.glyph().metrics().hori_advance())
.round()
.val()
},
)
face
.glyph()
.metrics()
.set_vert_bearing_x(
@fixed.F26Dot6::from_raw(face.glyph().metrics().vert_bearing_x())
.floor()
.val(),
)
face
.glyph()
.metrics()
.set_vert_bearing_y(
@fixed.F26Dot6::from_raw(face.glyph().metrics().vert_bearing_y())
.floor()
.val(),
)
face
.glyph()
.metrics()
.set_vert_advance(
@fixed.F26Dot6::from_raw(face.glyph().metrics().vert_advance())
.round()
.val(),
)
}
if face.glyph().outline().n_points() == 0 {
if !vertical_layout {
face.glyph().metrics().set_hori_bearing_x(0L)
face.glyph().metrics().set_hori_bearing_y(0L)
}
}
}
finalize_aux_vertical_metrics(face, slot_data, false)
} else {
// No scale set — return font units
set_slot_metrics(face.glyph(), slot_data)
finalize_aux_vertical_metrics(face, slot_data, true)
}
}
set_slot_linear_advances(face, slot_data, load_flags)
set_slot_advance_vector(face, load_flags)
if (load_flags & @base.LOAD_RENDER) != 0 && !no_scale {
render_slot_bitmap(face, render_mode_from_load_flags(load_flags))
}
}
// ─── Public API: size management ─────────────────────────────────────
///|
fn refresh_current_size_metrics(face : @base.FaceRec) -> Unit {
let x_scale = face.size().metrics().x_scale()
let y_scale = face.size().metrics().y_scale()
face
.size()
.metrics()
.set_ascender(
@fixed.mul_fix(y_scale, @fixed.Fixed::from_raw(face.ascender().to_int64())).val(),
)
face
.size()
.metrics()
.set_descender(
@fixed.mul_fix(y_scale, @fixed.Fixed::from_raw(face.descender().to_int64())).val(),
)
face
.size()
.metrics()
.set_height(
@fixed.mul_fix(y_scale, @fixed.Fixed::from_raw(face.height().to_int64())).val(),
)
face
.size()
.metrics()
.set_max_advance(
@fixed.mul_fix(
x_scale,
@fixed.Fixed::from_raw(face.max_advance_width().to_int64()),
).val(),
)
}
///|
/// Set the pixel size for subsequent glyph loading.
/// Computes scale factors from ppem and units_per_em.
pub fn set_pixel_sizes(
face : @base.FaceRec,
pixel_width : UInt,
pixel_height : UInt,
) -> Unit raise @error.FTError {
let upe = face.units_per_em()
if upe == 0U {
raise @error.FTError::InvalidPixelSize
}
let pw = if pixel_width == 0U { pixel_height } else { pixel_width }
let ph = if pixel_height == 0U { pixel_width } else { pixel_height }
// Scale: (ppem << 6) in 26.6, then div by upe to get 16.16 scale factor
let x_scale = @fixed.div_fix(
pw.reinterpret_as_int().to_int64() << 6,
upe.reinterpret_as_int().to_int64(),
)
let y_scale = @fixed.div_fix(
ph.reinterpret_as_int().to_int64() << 6,
upe.reinterpret_as_int().to_int64(),
)
face.size().metrics().set_x_ppem(pw)
face.size().metrics().set_y_ppem(ph)
face.size().metrics().set_x_scale(x_scale)
face.size().metrics().set_y_scale(y_scale)
refresh_current_size_metrics(face)
}
// ─── Public API: font variations ─────────────────────────────────────
///|
/// Set variation design coordinates on a face.
/// Validates and normalizes the coordinates, storing them for use during
/// glyph loading.
pub fn set_var_design_coordinates(
face : @base.FaceRec,
coordinates : Array[(@fixed.Fixed, UInt)],
) -> Unit raise @error.FTError {
// Face must be an SFNT with fvar data (multiple masters flag)
if !face.has_multiple_masters() {
raise @error.FTError::InvalidArgument
}
match face.driver_data() {
Type1Ops(ops) => {
ops.set_var_design_coordinates()(coordinates)
return
}
_ => ()
}
// Retrieve fvar axes from the raw font data
let raw_data = match face.driver_data() {
SfntDriver(ops) => ops.raw_data()
_ => raise @error.FTError::InvalidFaceHandle
}
let reader = @stream.ByteReader::new(raw_data)
// Re-parse table directory to find fvar
reader.seek(0)
let table_dir = @sfnt.parse_table_directory(reader)
let fvar_rec = match table_dir.find_table(@types.TAG_FVAR) {
Some(r) => r
None => raise @error.FTError::InvalidArgument
}
let fvar = @truetype.parse_fvar(reader, fvar_rec.offset(), fvar_rec.length())
// Parse avar if present
let avar : @truetype.AvarTable? = match
table_dir.find_table(@types.TAG_AVAR) {
Some(r) =>
Some(
@truetype.parse_avar(
reader,
r.offset(),
r.length(),
fvar.axes().length(),
),
)
None => None
}
// Build design coordinate array matching axis order
let design_coords : Array[@fixed.Fixed] = []
for _, axis in fvar.axes() {
let mut found = false
for _, coord in coordinates {
let (value, tag) = coord
if tag == axis.tag() {
design_coords.push(value)
found = true
break
}
}
if !found {
design_coords.push(axis.default_value())
}
}
let normalized = @truetype.normalize_coordinates(
fvar.axes(),
design_coords,
avar,
)
face.set_var_coords(normalized)
let mut has_non_default = false
for _, coord in normalized {
if coord != 0 {
has_non_default = true
break
}
}
let base_flags = if (face.face_flags() & @base.FACE_FLAG_VARIATION) != 0L {
face.face_flags() - @base.FACE_FLAG_VARIATION
} else {
face.face_flags()
}
face.set_face_flags(
if has_non_default {
base_flags | @base.FACE_FLAG_VARIATION
} else {
base_flags
},
)
if table_dir.find_table(@types.TAG_HHEA) is Some(hhea_rec) {
let hhea = @sfnt.parse_hhea(reader, hhea_rec.offset(), hhea_rec.length())
let base_ascender = hhea.ascender()
let base_descender = hhea.descender()
let base_line_gap = hhea.line_gap()
face.set_ascender(base_ascender)
face.set_descender(base_descender)
face.set_height(base_ascender - base_descender + base_line_gap)
face.set_max_advance_width(hhea.advance_width_max().reinterpret_as_int())
let post_base = match table_dir.find_table(@types.TAG_POST_LC) {
Some(post_rec) =>
Some(
@sfnt.parse_post(
reader,
post_rec.offset(),
post_rec.length(),
0U,
parse_names=false,
),
)
None => None
}
match post_base {
Some(post) => {
let base_underline_thickness = post.underline_thickness()
face.set_underline_position(
post.underline_position() - base_underline_thickness / 2,
)
face.set_underline_thickness(base_underline_thickness)
}
None => ()
}
if has_non_default &&
table_dir.find_table(@types.TAG_MVAR) is Some(mvar_rec) {
let mvar = @truetype.parse_mvar(
reader,
mvar_rec.offset(),
mvar_rec.length(),
)
let deltas = mvar.face_metric_deltas(normalized)
face.set_ascender(base_ascender + deltas.hasc())
face.set_descender(base_descender + deltas.hdsc())
face.set_height(
face.ascender() - face.descender() + base_line_gap + deltas.hlgp(),
)
match post_base {
Some(post) => {
let varied_underline_thickness = post.underline_thickness() +
deltas.unds()
face.set_underline_position(
post.underline_position() +
deltas.undo() -
varied_underline_thickness / 2,
)
face.set_underline_thickness(varied_underline_thickness)
}
None => ()
}
}
refresh_current_size_metrics(face)
}
}
// ─── Public API: kerning ─────────────────────────────────────────────
///|
/// Get kerning between two glyphs. Returns (x, y) in font units.
pub fn get_kerning(
face : @base.FaceRec,
left_glyph : UInt,
right_glyph : UInt,
) -> (Int64, Int64) {
match face.driver_data() {
SfntDriver(ops) => {
let x = ops.get_kerning()(left_glyph, right_glyph)
(x.to_int64(), 0L)
}
Type1Ops(ops) => (ops.get_kerning()(left_glyph, right_glyph).to_int64(), 0L)
Empty | BitmapOps(_) => (0L, 0L)
}
}
///|
pub fn attach_metrics(
face : @base.FaceRec,
data : Bytes,
) -> Unit raise @error.FTError {
match face.driver_data() {
Type1Ops(ops) => ops.attach_metrics()(data)
_ => raise @error.FTError::InvalidArgument
}
}