// 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.
///|
/// TrueType outlines from the `glyf` table (MVP).
///
/// This only supports simple glyphs (contour streams) for now.
const TAG_HEAD : UInt = 0x68656164 // "head"
///|
const TAG_LOCA : UInt = 0x6C6F6361 // "loca"
///|
const TAG_MAXP : UInt = 0x6D617870 // "maxp"
///|
const COMPOSITE_ARG_1_AND_2_ARE_WORDS : Int = 0x0001
///|
const COMPOSITE_ARGS_ARE_XY_VALUES : Int = 0x0002
///|
const COMPOSITE_ROUND_XY_TO_GRID : Int = 0x0004
///|
const COMPOSITE_WE_HAVE_A_SCALE : Int = 0x0008
///|
const COMPOSITE_MORE_COMPONENTS : Int = 0x0020
///|
const COMPOSITE_WE_HAVE_AN_X_AND_Y_SCALE : Int = 0x0040
///|
const COMPOSITE_WE_HAVE_A_TWO_BY_TWO : Int = 0x0080
///|
const COMPOSITE_WE_HAVE_INSTRUCTIONS : Int = 0x0100
///|
const COMPOSITE_OVERLAP_COMPOUND : Int = 0x0400
///|
const COMPOSITE_SCALED_COMPONENT_OFFSET : Int = 0x0800
///|
const COMPOSITE_UNSCALED_COMPONENT_OFFSET : Int = 0x1000
///|
const SIMPLE_OVERLAP_FLAG : Int = 0x40
///|
fn glyf_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 glyf_read_i16_be(view : BytesView, offset : Int) -> Int? {
match glyf_read_u16_be(view, offset) {
None => None
Some(u) => if u >= 0x8000 { Some(u - 0x10000) } else { Some(u) }
}
}
///|
fn glyf_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 glyf_read_f2dot14(view : BytesView, offset : Int) -> Double? {
match glyf_read_i16_be(view, offset) {
None => None
Some(v) => Some(v.to_double() / 16384.0)
}
}
///|
fn glyf_units_per_em(font : @moon_skrifa.FontRef) -> UInt16 {
match font.table(TAG_HEAD) {
None => 0
Some(head) =>
match glyf_read_u16_be(head, 18) {
None => 0
Some(v) => v.to_uint16()
}
}
}
///|
fn glyf_compute_scale_bits(
size : @moon_skrifa.Size,
upem : UInt16,
) -> (Bool, Int) {
match size.ppem() {
None => (false, 0x10000)
Some(ppem) => {
let upem_i = upem.to_int()
if upem_i <= 0 {
(false, 0x10000)
} else {
// Matches the reference compute_scale() shape without relying on Int64
// (works around wasm-gc toolchain crashes in some environments).
let a = (ppem * 64.0).to_int()
let q = (a.to_double() * 65536.0 / upem_i.to_double()).round().to_int()
(true, q)
}
}
}
}
///|
fn glyf_mul_16_16(v : Int, scale_bits : Int) -> Int {
(v.to_double() * scale_bits.to_double() / 65536.0).round().to_int()
}
///|
fn glyf_mul_16_16_f(v : Double, scale_bits : Int) -> Int {
(v * scale_bits.to_double() / 65536.0).round().to_int()
}
///|
fn glyf_f26dot6_bits_to_double(bits : Int) -> Double {
bits.to_double() / 64.0
}
///|
fn glyf_midpoint_i32(a : Int, b : Int) -> Int {
// Matches read-fonts midpoint_i32: a.wrapping_add(b) / 2.
(a + b) / 2
}
///|
priv struct GlyfTransform {
a : Double
b : Double
c : Double
d : Double
dx : Double
dy : Double
}
///|
priv struct GlyfCompositeComp {
flags : Int
gid : Int
dx : Double
dy : Double
a : Double
b : Double
c : Double
d : Double
have_xform : Bool
}
///|
/// FreeType's "guess" for hypot(a, b) used when scaling composite anchor
/// offsets (see fontations-reference).
fn glyf_hypot_guess(a : Double, b : Double) -> Double {
let aa = a.abs()
let bb = b.abs()
if aa > bb {
aa + 3.0 * bb / 8.0
} else {
bb + 3.0 * aa / 8.0
}
}
///|
fn glyf_transform_point(
t : GlyfTransform,
x : Double,
y : Double,
) -> (Double, Double) {
(t.a * x + t.b * y + t.dx, t.c * x + t.d * y + t.dy)
}
///|
fn glyf_apply_transform(
path : Array[PathElement],
t : GlyfTransform,
) -> Array[PathElement] {
let out : Array[PathElement] = Array::new()
for e in path {
match e {
MoveTo(x, y) => {
let (nx, ny) = glyf_transform_point(t, x, y)
out.push(MoveTo(nx, ny))
}
LineTo(x, y) => {
let (nx, ny) = glyf_transform_point(t, x, y)
out.push(LineTo(nx, ny))
}
QuadTo(cx, cy, x, y) => {
let (ncx, ncy) = glyf_transform_point(t, cx, cy)
let (nx, ny) = glyf_transform_point(t, x, y)
out.push(QuadTo(ncx, ncy, nx, ny))
}
CurveTo(cx0, cy0, cx1, cy1, x, y) => {
let (ncx0, ncy0) = glyf_transform_point(t, cx0, cy0)
let (ncx1, ncy1) = glyf_transform_point(t, cx1, cy1)
let (nx, ny) = glyf_transform_point(t, x, y)
out.push(CurveTo(ncx0, ncy0, ncx1, ncy1, nx, ny))
}
Close => out.push(Close)
}
}
out
}
///|
priv struct GlyfPoint {
x : Int
y : Int
on : Bool
}
///|
priv struct GlyfPointF {
x : Double
y : Double
on : Bool
}
///|
priv struct GlyfPointFT {
x : Int
y : Int
on : Bool
}
///|
fn glyf_emit_contour_harfbuzz(
out : Array[PathElement],
points : ArrayView[GlyfPointF],
start : Int,
end : Int,
path_style : PathStyle,
) -> Unit {
let n = end - start + 1
if n <= 0 {
return
}
let first = points.at(start)
let last = points.at(end)
let (sx, sy, start_idx) = if first.on {
(first.x, first.y, start)
} else {
match path_style {
PathStyle::FreeType =>
if last.on {
(last.x, last.y, end)
} else {
let mx = (last.x + first.x) / 2.0
let my = (last.y + first.y) / 2.0
(mx, my, -1)
}
PathStyle::HarfBuzz =>
if n >= 2 {
let second = points.at(start + 1)
if second.on {
(second.x, second.y, start + 1)
} else {
let mx = (first.x + second.x) / 2.0
let my = (first.y + second.y) / 2.0
(mx, my, -1)
}
} else {
(first.x, first.y, start)
}
}
}
out.push(MoveTo(sx, sy))
let mut control : (Double, Double)? = None
if start_idx == -1 {
for i in start..<(end + 1) {
let p = points.at(i)
if p.on {
match control {
None => out.push(LineTo(p.x, p.y))
Some((cx, cy)) => {
out.push(QuadTo(cx, cy, p.x, p.y))
control = None
}
}
} else {
match control {
None => control = Some((p.x, p.y))
Some((cx, cy)) => {
let mx = (cx + p.x) / 2.0
let my = (cy + p.y) / 2.0
out.push(QuadTo(cx, cy, mx, my))
control = Some((p.x, p.y))
}
}
}
}
} else {
let base = start_idx - start
for step in 1.. out.push(LineTo(p.x, p.y))
Some((cx, cy)) => {
out.push(QuadTo(cx, cy, p.x, p.y))
control = None
}
}
} else {
match control {
None => control = Some((p.x, p.y))
Some((cx, cy)) => {
let mx = (cx + p.x) / 2.0
let my = (cy + p.y) / 2.0
out.push(QuadTo(cx, cy, mx, my))
control = Some((p.x, p.y))
}
}
}
}
}
match control {
Some((cx, cy)) => out.push(QuadTo(cx, cy, sx, sy))
None => ()
}
out.push(Close)
}
///|
fn glyf_emit_contour_freetype(
out : Array[PathElement],
points : ArrayView[GlyfPointFT],
start : Int,
end : Int,
) -> Unit {
let n = end - start + 1
if n <= 0 {
return
}
let first = points.at(start)
let last = points.at(end)
let (sx, sy, start_idx) = if first.on {
(first.x, first.y, start)
} else if last.on {
(last.x, last.y, end)
} else {
(glyf_midpoint_i32(last.x, first.x), glyf_midpoint_i32(last.y, first.y), -1)
}
out.push(
MoveTo(glyf_f26dot6_bits_to_double(sx), glyf_f26dot6_bits_to_double(sy)),
)
let mut control : (Int, Int)? = None
if start_idx == -1 {
for i in start..<(end + 1) {
let p = points.at(i)
if p.on {
match control {
None =>
out.push(
LineTo(
glyf_f26dot6_bits_to_double(p.x),
glyf_f26dot6_bits_to_double(p.y),
),
)
Some((cx, cy)) => {
out.push(
QuadTo(
glyf_f26dot6_bits_to_double(cx),
glyf_f26dot6_bits_to_double(cy),
glyf_f26dot6_bits_to_double(p.x),
glyf_f26dot6_bits_to_double(p.y),
),
)
control = None
}
}
} else {
match control {
None => control = Some((p.x, p.y))
Some((cx, cy)) => {
let mx = glyf_midpoint_i32(cx, p.x)
let my = glyf_midpoint_i32(cy, p.y)
out.push(
QuadTo(
glyf_f26dot6_bits_to_double(cx),
glyf_f26dot6_bits_to_double(cy),
glyf_f26dot6_bits_to_double(mx),
glyf_f26dot6_bits_to_double(my),
),
)
control = Some((p.x, p.y))
}
}
}
}
} else {
let base = start_idx - start
for step in 1..
out.push(
LineTo(
glyf_f26dot6_bits_to_double(p.x),
glyf_f26dot6_bits_to_double(p.y),
),
)
Some((cx, cy)) => {
out.push(
QuadTo(
glyf_f26dot6_bits_to_double(cx),
glyf_f26dot6_bits_to_double(cy),
glyf_f26dot6_bits_to_double(p.x),
glyf_f26dot6_bits_to_double(p.y),
),
)
control = None
}
}
} else {
match control {
None => control = Some((p.x, p.y))
Some((cx, cy)) => {
let mx = glyf_midpoint_i32(cx, p.x)
let my = glyf_midpoint_i32(cy, p.y)
out.push(
QuadTo(
glyf_f26dot6_bits_to_double(cx),
glyf_f26dot6_bits_to_double(cy),
glyf_f26dot6_bits_to_double(mx),
glyf_f26dot6_bits_to_double(my),
),
)
control = Some((p.x, p.y))
}
}
}
}
}
match control {
Some((cx, cy)) =>
out.push(
QuadTo(
glyf_f26dot6_bits_to_double(cx),
glyf_f26dot6_bits_to_double(cy),
glyf_f26dot6_bits_to_double(sx),
glyf_f26dot6_bits_to_double(sy),
),
)
None => ()
}
out.push(Close)
}
///|
fn glyf_simple_path(
ctx : GlyfCtx,
glyph : BytesView,
gid : @moon_skrifa.GlyphId,
path_style : PathStyle,
) -> Array[PathElement]? {
let number_of_contours = match glyf_read_i16_be(glyph, 0) {
None => return None
Some(v) => v
}
if number_of_contours == 0 {
return Some(Array::new())
}
if number_of_contours < 0 {
// Handled by composite parsing.
return None
}
// Skip bbox (xMin..yMax).
let mut off = 10
let end_pts : Array[Int] = Array::new()
for _ in 0.. return None
Some(u) => u
}
end_pts.push(v)
off = off + 2
}
let instruction_len = match glyf_read_u16_be(glyph, off) {
None => return None
Some(v) => v
}
off = off + 2
if instruction_len < 0 || off + instruction_len > glyph.length() {
return None
}
off = off + instruction_len
let last_end = end_pts.at(end_pts.length() - 1)
let num_points = last_end + 1
if num_points <= 0 {
return Some(Array::new())
}
// Flags stream.
let flags : Array[Int] = Array::new()
while flags.length() < num_points {
if off >= glyph.length() {
return None
}
let flag = glyph.at(off).to_int()
off = off + 1
flags.push(flag)
if (flag & 0x08) != 0 {
if off >= glyph.length() {
return None
}
let repeat = glyph.at(off).to_int()
off = off + 1
for _ in 0..= glyph.length() {
return None
}
let b = glyph.at(off).to_int()
off = off + 1
dxs.push(if same { b } else { -b })
} else if same {
dxs.push(0)
} else {
let d = match glyf_read_i16_be(glyph, off) {
None => return None
Some(v) => v
}
off = off + 2
dxs.push(d)
}
}
// y deltas
let dys : Array[Int] = Array::new()
for i in 0..= glyph.length() {
return None
}
let b = glyph.at(off).to_int()
off = off + 1
dys.push(if same { b } else { -b })
} else if same {
dys.push(0)
} else {
let d = match glyf_read_i16_be(glyph, off) {
None => return None
Some(v) => v
}
off = off + 2
dys.push(d)
}
}
for i in 0.. {
// In FreeType mode, points are stored in 26.6 fixed (as Int bits).
let scaled : Array[GlyfPointFT] = Array::new()
for i in 0..= scaled.length() {
return None
}
glyf_emit_contour_freetype(
out,
scaled.op_as_view(),
contour_start,
contour_end,
)
contour_start = contour_end + 1
}
Some(out)
}
PathStyle::HarfBuzz => {
// In HarfBuzz mode, points are stored as f32 in 16.16 fixed units (as Double here).
let scale = if ctx.is_scaled { ctx.hb_scale } else { 1.0 }
let scaled : Array[GlyfPointF] = Array::new()
for i in 0..= scaled.length() {
return None
}
glyf_emit_contour_harfbuzz(
out,
scaled.op_as_view(),
contour_start,
contour_end,
path_style,
)
contour_start = contour_end + 1
}
Some(out)
}
}
}
///|
priv struct GlyfCtx {
font : @moon_skrifa.FontRef
glyf : BytesView
loca : BytesView
num_glyphs : Int
index_to_loc_format : Int
coords : ArrayView[@moon_skrifa.NormalizedCoord]
is_scaled : Bool
is_hinted : Bool
scale_bits : Int
hb_scale : Double
}
///|
fn glyf_glyph_slice(ctx : GlyfCtx, idx : Int) -> BytesView? {
if idx < 0 || idx >= ctx.num_glyphs {
return None
}
let (start, end) = if ctx.index_to_loc_format == 0 {
let need = (ctx.num_glyphs + 1) * 2
if ctx.loca.length() < need {
return None
}
let o0 = match glyf_read_u16_be(ctx.loca, idx * 2) {
None => return None
Some(v) => v
}
let o1 = match glyf_read_u16_be(ctx.loca, (idx + 1) * 2) {
None => return None
Some(v) => v
}
(o0 * 2, o1 * 2)
} else if ctx.index_to_loc_format == 1 {
let need = (ctx.num_glyphs + 1) * 4
if ctx.loca.length() < need {
return None
}
let o0 = match glyf_read_u32_be(ctx.loca, idx * 4) {
None => return None
Some(v) => v.to_uint64().to_int()
}
let o1 = match glyf_read_u32_be(ctx.loca, (idx + 1) * 4) {
None => return None
Some(v) => v.to_uint64().to_int()
}
(o0, o1)
} else {
return None
}
if start < 0 || end < start || end > ctx.glyf.length() {
return None
}
Some(ctx.glyf.sub(start~, end~))
}
///|
fn glyf_composite_path(
ctx : GlyfCtx,
glyph : BytesView,
gid : @moon_skrifa.GlyphId,
visited : Array[Bool],
path_style : PathStyle,
) -> Array[PathElement]? {
if glyph.length() < 10 {
return None
}
// Skip bbox (xMin..yMax).
let mut off = 10
let comps : Array[GlyfCompositeComp] = Array::new()
let mut flags = 0
while true {
flags = match glyf_read_u16_be(glyph, off) {
None => return None
Some(v) => v
}
let comp_gid = match glyf_read_u16_be(glyph, off + 2) {
None => return None
Some(v) => v
}
off = off + 4
let arg_words = (flags & COMPOSITE_ARG_1_AND_2_ARE_WORDS) != 0
let args_xy = (flags & COMPOSITE_ARGS_ARE_XY_VALUES) != 0
if !args_xy {
// Point matching not supported by MVP.
return None
}
let (dx, dy) = if arg_words {
let a1 = match glyf_read_i16_be(glyph, off) {
None => return None
Some(v) => v
}
let a2 = match glyf_read_i16_be(glyph, off + 2) {
None => return None
Some(v) => v
}
off = off + 4
(a1.to_double(), a2.to_double())
} else {
if off + 2 > glyph.length() {
return None
}
let b1 = glyph.at(off).to_int()
let b2 = glyph.at(off + 1).to_int()
off = off + 2
let a1 = if b1 >= 128 { b1 - 256 } else { b1 }
let a2 = if b2 >= 128 { b2 - 256 } else { b2 }
(a1.to_double(), a2.to_double())
}
let mut a = 1.0
let mut b = 0.0
let mut c = 0.0
let mut d = 1.0
let mut have_xform = false
if (flags & COMPOSITE_WE_HAVE_A_SCALE) != 0 {
let s = match glyf_read_f2dot14(glyph, off) {
None => return None
Some(v) => v
}
off = off + 2
a = s
d = s
have_xform = true
} else if (flags & COMPOSITE_WE_HAVE_AN_X_AND_Y_SCALE) != 0 {
let sx = match glyf_read_f2dot14(glyph, off) {
None => return None
Some(v) => v
}
let sy = match glyf_read_f2dot14(glyph, off + 2) {
None => return None
Some(v) => v
}
off = off + 4
a = sx
d = sy
have_xform = true
} else if (flags & COMPOSITE_WE_HAVE_A_TWO_BY_TWO) != 0 {
let ca = match glyf_read_f2dot14(glyph, off) {
None => return None
Some(v) => v
}
let cb = match glyf_read_f2dot14(glyph, off + 2) {
None => return None
Some(v) => v
}
let cc = match glyf_read_f2dot14(glyph, off + 4) {
None => return None
Some(v) => v
}
let cd = match glyf_read_f2dot14(glyph, off + 6) {
None => return None
Some(v) => v
}
off = off + 8
a = ca
b = cb
c = cc
d = cd
have_xform = true
}
comps.push(GlyfCompositeComp::{
flags,
gid: comp_gid,
dx,
dy,
a,
b,
c,
d,
have_xform,
})
if (flags & COMPOSITE_MORE_COMPONENTS) == 0 {
break
}
}
if (flags & COMPOSITE_WE_HAVE_INSTRUCTIONS) != 0 {
if off + 2 > glyph.length() {
return None
}
let ins_len = match glyf_read_u16_be(glyph, off) {
None => return None
Some(v) => v
}
off = off + 2
if ins_len < 0 || off + ins_len > glyph.length() {
return None
}
}
let mut delta_x : Array[Double]? = None
let mut delta_y : Array[Double]? = None
match glyf_gvar_composite_deltas(ctx.font, gid, ctx.coords, comps.length()) {
None => ()
Some((xs, ys)) => {
delta_x = Some(xs)
delta_y = Some(ys)
}
}
let out : Array[PathElement] = Array::new()
for i in 0..
if i < dx_arr.length() && i < dy_arr.length() {
match path_style {
PathStyle::FreeType => {
// FreeType rounds off the fractional part of component deltas.
dx = dx + dx_arr.at(i).to_int().to_double()
dy = dy + dy_arr.at(i).to_int().to_double()
}
PathStyle::HarfBuzz => {
dx = dx + dx_arr.at(i)
dy = dy + dy_arr.at(i)
}
}
}
_ => ()
}
let (sdx, sdy) = match path_style {
PathStyle::FreeType => {
let dx_i = dx.to_int()
let dy_i = dy.to_int()
let dx_bits = if ctx.is_scaled {
glyf_mul_16_16(dx_i, ctx.scale_bits)
} else {
dx_i * 64
}
let mut dy_bits = if ctx.is_scaled {
glyf_mul_16_16(dy_i, ctx.scale_bits)
} else {
dy_i * 64
}
if ctx.is_scaled &&
ctx.is_hinted &&
(comp.flags & COMPOSITE_ROUND_XY_TO_GRID) != 0 {
// Match fontations-reference (FreeType behavior): only round the y
// component, and only when hinting is enabled.
dy_bits = (dy_bits + 32) & -64
}
(
glyf_f26dot6_bits_to_double(dx_bits),
glyf_f26dot6_bits_to_double(dy_bits),
)
}
PathStyle::HarfBuzz =>
// Matches fontations-reference HarfBuzzScaler: component offsets are
// applied without scaling, even when points are scaled to 16.16.
(dx, dy)
}
let t = GlyfTransform::{
a: comp.a,
b: comp.b,
c: comp.c,
d: comp.d,
dx: sdx,
dy: sdy,
}
let comp_path = match
glyf_outline_path_idx(ctx, comp.gid, visited, path_style) {
None => return None
Some(p) => p
}
let transformed = glyf_apply_transform(comp_path, t)
for e in transformed {
out.push(e)
}
}
Some(out)
}
///|
fn glyf_outline_path_idx(
ctx : GlyfCtx,
idx : Int,
visited : Array[Bool],
path_style : PathStyle,
) -> Array[PathElement]? {
if idx < 0 || idx >= ctx.num_glyphs {
return None
}
if visited.at(idx) {
return None
}
visited.set(idx, true)
let res = match glyf_glyph_slice(ctx, idx) {
None => None
Some(glyph) =>
if glyph.length() == 0 {
Some(Array::new())
} else {
match glyf_read_i16_be(glyph, 0) {
None => None
Some(nc) =>
if nc < 0 {
glyf_composite_path(
ctx,
glyph,
@moon_skrifa.GlyphId::new(idx.to_uint16()),
visited,
path_style,
)
} else {
glyf_simple_path(
ctx,
glyph,
@moon_skrifa.GlyphId::new(idx.to_uint16()),
path_style,
)
}
}
}
}
visited.set(idx, false)
res
}
///|
fn glyf_outline_path(
font : @moon_skrifa.FontRef,
gid : @moon_skrifa.GlyphId,
coords : ArrayView[@moon_skrifa.NormalizedCoord],
path_style : PathStyle,
size : @moon_skrifa.Size,
is_hinted : Bool,
) -> Array[PathElement]? {
let upem = glyf_units_per_em(font)
let (is_scaled, scale_bits) = glyf_compute_scale_bits(size, upem)
// HarfBuzz uses an exact 16.16 scale factor. Do not inherit FreeType's
// fixed-point rounding of the scale factor used for 26.6.
let hb_scale = match size.ppem() {
None => 1.0
Some(ppem) => {
let upem_i = upem.to_int()
if upem_i <= 0 {
1.0
} else {
ppem * 65536.0 / upem_i.to_double()
}
}
}
let glyf = match font.table(TAG_GLYF) {
None => return None
Some(v) => v
}
let maxp = match font.table(TAG_MAXP) {
None => return None
Some(v) => v
}
let head = match font.table(TAG_HEAD) {
None => return None
Some(v) => v
}
let loca = match font.table(TAG_LOCA) {
None => return None
Some(v) => v
}
let num_glyphs = match glyf_read_u16_be(maxp, 4) {
None => return None
Some(v) => v
}
let idx = gid.to_uint64().to_int()
if idx < 0 || idx >= num_glyphs {
return None
}
let index_to_loc_format = match glyf_read_i16_be(head, 50) {
None => return None
Some(v) => v
}
let ctx = GlyfCtx::{
font,
glyf,
loca,
num_glyphs,
index_to_loc_format,
coords,
is_scaled,
is_hinted,
scale_bits,
hb_scale,
}
let visited = Array::make(num_glyphs, false)
glyf_outline_path_idx(ctx, idx, visited, path_style)
}
///|
fn glyf_glyph_count(font : @moon_skrifa.FontRef) -> Int? {
let maxp = match font.table(TAG_MAXP) {
None => return None
Some(v) => v
}
// maxp.numGlyphs at offset 4.
match glyf_read_u16_be(maxp, 4) {
None => None
Some(v) => Some(v)
}
}
///|
fn glyf_outline_info_from_glyph(glyph : BytesView) -> (Bool, Bool)? {
if glyph.length() < 10 {
return None
}
let number_of_contours = match glyf_read_i16_be(glyph, 0) {
None => return None
Some(v) => v
}
if number_of_contours == 0 {
return Some((false, false))
}
if number_of_contours < 0 {
// Composite.
let mut off = 10
let mut has_overlaps = false
let mut flags = 0
while true {
flags = match glyf_read_u16_be(glyph, off) {
None => return None
Some(v) => v
}
has_overlaps = has_overlaps || (flags & COMPOSITE_OVERLAP_COMPOUND) != 0
// component glyph id
match glyf_read_u16_be(glyph, off + 2) {
None => return None
Some(_) => ()
}
off = off + 4
let arg_words = (flags & COMPOSITE_ARG_1_AND_2_ARE_WORDS) != 0
let args_len = if arg_words { 4 } else { 2 }
if off + args_len > glyph.length() {
return None
}
off = off + args_len
// Transform parameters.
if (flags & COMPOSITE_WE_HAVE_A_SCALE) != 0 {
off = off + 2
} else if (flags & COMPOSITE_WE_HAVE_AN_X_AND_Y_SCALE) != 0 {
off = off + 4
} else if (flags & COMPOSITE_WE_HAVE_A_TWO_BY_TWO) != 0 {
off = off + 8
}
if off < 0 || off > glyph.length() {
return None
}
if (flags & COMPOSITE_MORE_COMPONENTS) == 0 {
break
}
}
let has_hinting = (flags & COMPOSITE_WE_HAVE_INSTRUCTIONS) != 0
return Some((has_overlaps, has_hinting))
}
// Simple: scan flags for overlap bit; hinting if instruction length > 0.
let mut off = 10
let end_pts : Array[Int] = Array::new()
for _ in 0.. return None
Some(u) => u
}
end_pts.push(v)
off = off + 2
}
let instruction_len = match glyf_read_u16_be(glyph, off) {
None => return None
Some(v) => v
}
off = off + 2
if instruction_len < 0 || off + instruction_len > glyph.length() {
return None
}
let has_hinting = instruction_len > 0
off = off + instruction_len
let last_end = end_pts.at(end_pts.length() - 1)
let num_points = last_end + 1
if num_points <= 0 {
return Some((false, has_hinting))
}
let mut has_overlaps = false
let flags : Array[Int] = Array::new()
while flags.length() < num_points {
if off >= glyph.length() {
return None
}
let flag = glyph.at(off).to_int()
off = off + 1
has_overlaps = has_overlaps || (flag & SIMPLE_OVERLAP_FLAG) != 0
flags.push(flag)
if (flag & 0x08) != 0 {
if off >= glyph.length() {
return None
}
let repeat = glyph.at(off).to_int()
off = off + 1
for _ in 0.. (Bool, Bool)? {
let glyf = match font.table(TAG_GLYF) {
None => return None
Some(v) => v
}
let maxp = match font.table(TAG_MAXP) {
None => return None
Some(v) => v
}
let head = match font.table(TAG_HEAD) {
None => return None
Some(v) => v
}
let loca = match font.table(TAG_LOCA) {
None => return None
Some(v) => v
}
let num_glyphs = match glyf_read_u16_be(maxp, 4) {
None => return None
Some(v) => v
}
let idx = gid.to_uint64().to_int()
if idx < 0 || idx >= num_glyphs {
return None
}
let index_to_loc_format = match glyf_read_i16_be(head, 50) {
None => return None
Some(v) => v
}
let empty : Array[@moon_skrifa.NormalizedCoord] = Array::new()
let ctx = GlyfCtx::{
font,
glyf,
loca,
num_glyphs,
index_to_loc_format,
coords: empty.op_as_view(),
is_scaled: false,
is_hinted: false,
scale_bits: 0x10000,
hb_scale: 1.0,
}
match glyf_glyph_slice(ctx, idx) {
None => None
Some(g) => glyf_outline_info_from_glyph(g)
}
}
///|
fn glyf_has_overlaps(
font : @moon_skrifa.FontRef,
gid : @moon_skrifa.GlyphId,
) -> Bool? {
match glyf_outline_info(font, gid) {
None => None
Some((has_overlaps, _)) => Some(has_overlaps)
}
}
///|
fn glyf_has_hinting(
font : @moon_skrifa.FontRef,
gid : @moon_skrifa.GlyphId,
) -> Bool? {
match glyf_outline_info(font, gid) {
None => None
Some((_, has_hinting)) => Some(has_hinting)
}
}