///|
fn round_double_to_int(value : Double) -> Int {
if value < 0.0 {
(value - 0.5).to_int()
} else {
(value + 0.5).to_int()
}
}
///|
fn double_to_fixed(value : Double) -> @fixed.Fixed {
@fixed.Fixed::from_raw(round_double_to_int(value * 65536.0).to_int64())
}
///|
fn afm_pair_key(left : UInt, right : UInt) -> Int64 {
(left.to_int().to_int64() << 32) | right.to_int().to_int64()
}
///|
fn is_ascii_space(byte : Byte) -> Bool {
byte == b' ' || byte == b'\t' || byte == b'\r' || byte == b'\n'
}
///|
fn skip_ascii_space(data : Bytes, start : Int, end : Int) -> Int {
let mut pos = start
while pos < end && is_ascii_space(data[pos]) {
pos += 1
}
pos
}
///|
fn read_ascii_token(data : Bytes, start : Int, end : Int) -> (String, Int) {
let token_start = skip_ascii_space(data, start, end)
let mut token_end = token_start
while token_end < end && !is_ascii_space(data[token_end]) {
token_end += 1
}
let buf = StringBuilder::new()
for i in token_start.. Int? {
let mut pos = skip_ascii_space(data, start, end)
if pos >= end {
return None
}
let mut sign = 1
if data[pos] == b'-' {
sign = -1
pos += 1
} else if data[pos] == b'+' {
pos += 1
}
let mut value = 0
let mut saw_digit = false
while pos < end {
let byte = data[pos]
if byte < b'0' || byte > b'9' {
break
}
saw_digit = true
value = value * 10 + (byte.to_int() - b'0'.to_int())
pos += 1
}
if saw_digit {
Some(value * sign)
} else {
None
}
}
///|
fn parse_afm_kerning_pairs(
data : Bytes,
name_to_index : Map[String, UInt],
) -> Map[Int64, Int] {
let pairs : Map[Int64, Int] = {}
let mut line_start = 0
while line_start < data.length() {
let mut line_end = line_start
while line_end < data.length() &&
data[line_end] != b'\n' &&
data[line_end] != b'\r' {
line_end += 1
}
if line_end - line_start >= 4 &&
data[line_start] == b'K' &&
data[line_start + 1] == b'P' &&
data[line_start + 2] == b'X' &&
data[line_start + 3] == b' ' {
let (left_name, next0) = read_ascii_token(data, line_start + 4, line_end)
let (right_name, next1) = read_ascii_token(data, next0, line_end)
match parse_ascii_int(data, next1, line_end) {
Some(value) =>
match (name_to_index.get(left_name), name_to_index.get(right_name)) {
(Some(left), Some(right)) =>
pairs[afm_pair_key(left, right)] = value
_ => ()
}
None => ()
}
}
line_start = line_end
if line_start < data.length() && data[line_start] == b'\r' {
line_start += 1
}
if line_start < data.length() && data[line_start] == b'\n' {
line_start += 1
}
}
pairs
}
///|
fn read_u16_le(data : Bytes, offset : Int) -> Int? {
if offset < 0 || offset + 1 >= data.length() {
None
} else {
Some(data[offset].to_int() | (data[offset + 1].to_int() << 8))
}
}
///|
fn read_u32_le(data : Bytes, offset : Int) -> Int? {
if offset < 0 || offset + 3 >= data.length() {
None
} else {
Some(
data[offset].to_int() |
(data[offset + 1].to_int() << 8) |
(data[offset + 2].to_int() << 16) |
(data[offset + 3].to_int() << 24),
)
}
}
///|
fn read_i16_le(data : Bytes, offset : Int) -> Int? {
match read_u16_le(data, offset) {
Some(value) => Some(if value >= 0x8000 { value - 0x10000 } else { value })
None => None
}
}
///|
fn looks_like_pfm_metrics(data : Bytes) -> Bool {
if data.length() <= 6 || data[1] >= b'\x04' {
return false
}
match read_u32_le(data, 2) {
Some(size) => size == data.length()
None => false
}
}
///|
fn parse_pfm_kerning_pairs(
data : Bytes,
char_index_for_code : (UInt) -> UInt,
) -> Map[Int64, Int] raise @error.FTError {
let width_table_length = match read_u16_le(data, 99) {
Some(value) => value
None => raise @error.FTError::InvalidFileFormat
}
let extension_offset = 99 + 2 + 18 + width_table_length
match read_u16_le(data, extension_offset) {
Some(size) if size >= 0x12 => ()
Some(_) => return {}
None => return {}
}
let kerning_offset = match read_u32_le(data, extension_offset + 14) {
Some(value) => value
None => raise @error.FTError::InvalidFileFormat
}
if kerning_offset == 0 {
return {}
}
let pair_count = match read_u16_le(data, kerning_offset) {
Some(value) => value
None => raise @error.FTError::InvalidFileFormat
}
let pair_data_start = kerning_offset + 2
if pair_data_start + pair_count * 4 > data.length() {
raise @error.FTError::InvalidFileFormat
}
let pairs : Map[Int64, Int] = {}
for pair_index in 0..
if left != 0U && right != 0U {
pairs[afm_pair_key(left, right)] = value
}
None => raise @error.FTError::InvalidFileFormat
}
}
pairs
}
///|
fn normalize_mm_design_coord(
value : @fixed.Fixed,
min_value : @fixed.Fixed,
default_value : @fixed.Fixed,
max_value : @fixed.Fixed,
) -> Int {
if value.val() == default_value.val() {
return 0
}
if value.val() < default_value.val() {
let denom = default_value.val() - min_value.val()
if denom <= 0L {
return -0x4000
}
let num = value.val() - default_value.val()
let scaled = (num * 0x4000L / denom).max(-0x4000L)
scaled.to_int()
} else {
let denom = max_value.val() - default_value.val()
if denom <= 0L {
return 0x4000
}
let num = value.val() - default_value.val()
let scaled = (num * 0x4000L / denom).min(0x4000L)
scaled.to_int()
}
}
///|
fn type1_font_transform(
font_matrix : Array[Double],
) -> (@fixed.Matrix, Int64, Int64) {
if font_matrix.length() < 6 {
return (@fixed.Matrix::identity(), 0L, 0L)
}
let scale = if font_matrix[3] < 0.0 {
-font_matrix[3]
} else {
font_matrix[3]
}
if scale <= 0.0 {
return (@fixed.Matrix::identity(), 0L, 0L)
}
let yy = if font_matrix[3] < 0.0 { -1.0 } else { 1.0 }
(
@fixed.Matrix::new(
double_to_fixed(font_matrix[0] / scale),
double_to_fixed(font_matrix[2] / scale),
double_to_fixed(font_matrix[1] / scale),
double_to_fixed(yy),
),
(font_matrix[4] / scale).to_int().to_int64(),
(font_matrix[5] / scale).to_int().to_int64(),
)
}
///|
/// Load a Type 1 PFB font.
fn load_type1_pfb(data : Bytes) -> @base.FaceRec raise @error.FTError {
let font = @type1.parse_type1_pfb(data)
let face = @base.FaceRec::new()
let (font_transform, font_offset_x, font_offset_y) = type1_font_transform(
font.font_matrix(),
)
let family_name = font.family_name()
face.set_family_name(
if family_name != "" {
family_name
} else {
font.font_name()
},
)
face.set_face_flags(
@base.FACE_FLAG_SCALABLE |
@base.FACE_FLAG_HORIZONTAL |
@base.FACE_FLAG_GLYPH_NAMES |
@base.FACE_FLAG_HINTER,
)
face.set_num_glyphs(font.charstrings().length().to_int64())
face.set_units_per_em(font.units_per_em().reinterpret_as_uint())
if font.font_bbox().length() == 4 {
face.set_bbox(
@types.BBox::new(
font.font_bbox()[0].to_int64(),
font.font_bbox()[1].to_int64(),
font.font_bbox()[2].to_int64(),
font.font_bbox()[3].to_int64(),
),
)
face.set_ascender(font.font_bbox()[3])
face.set_descender(font.font_bbox()[1])
}
let bbox_height = face.ascender() - face.descender()
let default_height = font.units_per_em() * 12 / 10
face.set_height(
if default_height > bbox_height {
default_height
} else {
bbox_height
},
)
face.set_underline_position(font.underline_position())
face.set_underline_thickness(font.underline_thickness())
let glyph_names = if font.glyph_order().length() > 0 {
font.glyph_order()
} else {
font.charstrings().keys().collect()
}
let mut notdef_index : Int? = None
for i, name in glyph_names {
if name == ".notdef" {
notdef_index = Some(i)
break
}
}
if glyph_names.length() > 1 {
match notdef_index {
Some(idx) if idx != 0 => {
let first = glyph_names[0]
glyph_names[0] = glyph_names[idx]
glyph_names[idx] = first
}
_ => ()
}
}
face.set_num_glyphs(glyph_names.length().to_int64())
// Build glyph name list for index lookup
// Build O(1) name-to-index map
let name_to_index : Map[String, UInt] = {}
for i, name in glyph_names {
name_to_index[name] = i.reinterpret_as_uint()
}
let attached_kerning : Ref[Map[Int64, Int]] = Ref::new({})
// Build Unicode charmap from glyph names when possible.
let unicode_cmap = @psnames.build_unicode_map(glyph_names)
// Pre-compute charcode -> glyph_index cache (256 entries)
let cached_cmap : FixedArray[UInt] = FixedArray::make(256, 0U)
let enc = font.encoding()
for code in 0..<256 {
let glyph_name = enc[code]
if glyph_name != ".notdef" {
match name_to_index.get(glyph_name) {
Some(idx) => cached_cmap[code] = idx
None => ()
}
}
}
let has_unicode = !unicode_cmap.is_empty()
if has_unicode {
face.charmaps().push(@base.CharMapRec::new(@base.Encoding::Unicode, 3U, 1U))
}
let encoding_charmap = if font.custom_encoding() {
@base.CharMapRec::new(@base.Encoding::AdobeCustom, 7U, 2U)
} else if font.expert_encoding() {
@base.CharMapRec::new(@base.Encoding::AdobeExpert, 7U, 1U)
} else if font.latin1_encoding() {
@base.CharMapRec::new(@base.Encoding::AdobeLatin1, 7U, 3U)
} else {
@base.CharMapRec::new(@base.Encoding::AdobeStandard, 7U, 0U)
}
face.charmaps().push(encoding_charmap)
if face.charmaps().length() > 0 {
face.set_active_charmap(0)
}
if font.multiple_master() && font.mm_axis_tags().length() > 0 {
face.set_face_flags(face.face_flags() | @base.FACE_FLAG_MULTIPLE_MASTERS)
face.set_var_coords(Array::make(font.mm_axis_tags().length(), 0))
}
// Set up Type1Ops driver data. Public API uses the active charmap, which we
// synthesize as Unicode when glyph names allow it.
let unicode_char_index_fn : (UInt) -> UInt = fn(charcode) {
match unicode_cmap.get(charcode) {
Some(gid) => gid
None => 0U
}
}
let encoding_char_index_fn : (UInt) -> UInt = fn(charcode) {
if charcode >= 256U {
return 0U
}
cached_cmap.unsafe_get(charcode.reinterpret_as_int())
}
let unicode_charmap_index = if has_unicode { 0 } else { -1 }
let encoding_charmap_index = if has_unicode { 1 } else { 0 }
let char_index_fn : (UInt) -> UInt = fn(charcode) {
if face.active_charmap() == encoding_charmap_index {
return encoding_char_index_fn(charcode)
}
if face.active_charmap() == unicode_charmap_index && has_unicode {
return unicode_char_index_fn(charcode)
}
if has_unicode {
unicode_char_index_fn(charcode)
} else {
encoding_char_index_fn(charcode)
}
}
let hint_h_hints : Ref[Array[@psaux.StemHint]] = Ref::new([])
let hint_v_hints : Ref[Array[@psaux.StemHint]] = Ref::new([])
let hint_has_hints : Ref[Bool] = Ref::new(false)
let hint_globals_ref : Ref[@pshinter.PsGlobals?] = Ref::new(None)
let ensure_hint_globals = fn() -> @pshinter.PsGlobals {
match hint_globals_ref.val {
Some(g) => g
None => {
let defaults = @pshinter.PsGlobals::new()
let g = @pshinter.build_ps_globals(
font.blue_values(),
font.other_blues(),
font.std_hw(),
font.std_vw(),
defaults.blue_scale(),
defaults.blue_shift().to_int(),
defaults.blue_fuzz().to_int(),
)
hint_globals_ref.val = Some(g)
g
}
}
}
let load_glyph_fn : (UInt, Int) -> (@base.GlyphSlotData, @types.Outline) raise @error.FTError = fn(
glyph_index,
load_flags,
) raise @error.FTError {
let idx = glyph_index.reinterpret_as_int()
if idx < 0 || idx >= glyph_names.length() {
raise @error.FTError::InvalidGlyphIndex
}
let name = glyph_names[idx]
let no_hinting = (load_flags & @base.LOAD_NO_HINTING) != 0
let (outline, raw_width, hh, hv) = @type1.load_type1_glyph_full_data(
font,
name,
normalized_coords=face.var_coords(),
)
let has_transform = font_transform.xx().val() != 0x10000L ||
font_transform.xy().val() != 0L ||
font_transform.yx().val() != 0L ||
font_transform.yy().val() != 0x10000L
if has_transform {
@base.outline_transform(outline, font_transform)
}
if font_offset_x != 0L || font_offset_y != 0L {
@base.outline_translate(outline, font_offset_x, font_offset_y)
}
let width = @fixed.mul_div(raw_width, font_transform.xx().val(), 0x10000L) +
font_offset_x
if no_hinting {
hint_has_hints.val = false
} else {
hint_h_hints.val = hh
hint_v_hints.val = hv
hint_has_hints.val = hh.length() > 0 || hv.length() > 0
}
let bb = @base.outline_get_bbox(outline)
let slot_data = @base.GlyphSlotData::new(
bb.x_max() - bb.x_min(),
bb.y_max() - bb.y_min(),
bb.x_min(),
bb.y_max(),
width,
)
(slot_data, outline)
}
let apply_hints_fn : (@types.Outline, @fixed.Fixed) -> Bool = fn(
outline,
y_scale,
) {
if hint_has_hints.val {
let globals = ensure_hint_globals()
@pshinter.hint_outline(
outline,
hint_h_hints.val,
hint_v_hints.val,
globals,
y_scale,
)
hint_has_hints.val = false
true
} else {
false
}
}
face.set_char_index_fn(char_index_fn)
face.set_driver_data(
@base.DriverData::Type1Ops(
@base.Type1Ops::new(
char_index=encoding_char_index_fn,
load_glyph=load_glyph_fn,
apply_hints=apply_hints_fn,
get_kerning=fn(left, right) {
match attached_kerning.val.get(afm_pair_key(left, right)) {
Some(value) => value
None => 0
}
},
set_var_design_coordinates=coordinates => {
if !font.multiple_master() || font.mm_axis_tags().length() == 0 {
raise @error.FTError::InvalidArgument
}
let normalized : Array[Int] = []
let mut has_non_default = false
for axis_index, axis_tag in font.mm_axis_tags() {
let mut design_value = font.mm_axis_defaults()[axis_index]
for _, coordinate in coordinates {
let (coord_value, tag) = coordinate
if tag == axis_tag {
design_value = coord_value
break
}
}
let norm = normalize_mm_design_coord(
design_value,
font.mm_axis_mins()[axis_index],
font.mm_axis_defaults()[axis_index],
font.mm_axis_maxs()[axis_index],
)
normalized.push(norm)
if norm != 0 {
has_non_default = true
}
}
face.set_var_coords(normalized)
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
},
)
},
attach_metrics=metrics_data => {
let parsed_pairs = parse_afm_kerning_pairs(
metrics_data, name_to_index,
)
let final_pairs = if !parsed_pairs.is_empty() {
parsed_pairs
} else if looks_like_pfm_metrics(metrics_data) {
parse_pfm_kerning_pairs(metrics_data, encoding_char_index_fn)
} else {
{}
}
if parsed_pairs.is_empty() &&
final_pairs.is_empty() &&
!looks_like_pfm_metrics(metrics_data) {
raise @error.FTError::InvalidFileFormat
}
let merged = attached_kerning.val
for pair, value in final_pairs {
merged[pair] = value
}
attached_kerning.val = merged
if !final_pairs.is_empty() {
face.set_face_flags(face.face_flags() | @base.FACE_FLAG_KERNING)
}
},
),
),
)
face
}