1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
//! The [loca (Index to Location)][loca] table
//!
//! [loca]: https://docs.microsoft.com/en-us/typography/opentype/spec/loca
use read_fonts::TopLevelTable;
use types::Tag;
use crate::{
validate::{Validate, ValidationCtx},
FontWrite,
};
/// The [loca] table.
///
/// [loca]: https://docs.microsoft.com/en-us/typography/opentype/spec/loca
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Loca {
// we just store u32, and then convert to u16 if needed in the `FontWrite` impl
pub(crate) offsets: Vec<u32>,
loca_format: LocaFormat,
}
/// Whether or not the 'loca' table uses short or long offsets.
///
/// This flag is stored in the 'head' table's [indexToLocFormat][locformat] field.
/// See the ['loca' spec][spec] for more information.
///
/// [locformat]: super::head::Head::index_to_loc_format
/// [spec]: https://learn.microsoft.com/en-us/typography/opentype/spec/loca
#[repr(u8)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum LocaFormat {
#[default]
Short = 0,
Long = 1,
}
impl TopLevelTable for Loca {
const TAG: Tag = Tag::new(b"loca");
}
impl Loca {
/// Create a new loca table from 32-bit offsets.
///
/// The loca format will be calculated based on the raw values.
///
/// You generally do not construct this directly; it is constructed alongside
/// the corresponding 'glyf' table using the
/// [GlyfLocaBuilder](super::glyf::GlyfLocaBuilder).
pub fn new(offsets: Vec<u32>) -> Self {
let loca_format = LocaFormat::new(&offsets);
Loca {
offsets,
loca_format,
}
}
pub fn format(&self) -> LocaFormat {
self.loca_format
}
}
impl LocaFormat {
fn new(loca: &[u32]) -> LocaFormat {
// https://github.com/fonttools/fonttools/blob/1c283756a5e39d69459eea80ed12792adc4922dd/Lib/fontTools/ttLib/tables/_l_o_c_a.py#L37
const MAX_SHORT_LOCA_VALUE: u32 = 0x20000;
if loca.last().copied().unwrap_or_default() < MAX_SHORT_LOCA_VALUE
&& loca.iter().all(|offset| offset % 2 == 0)
{
LocaFormat::Short
} else {
LocaFormat::Long
}
}
}
impl FontWrite for Loca {
fn write_into(&self, writer: &mut crate::TableWriter) {
match self.loca_format {
LocaFormat::Long => self.offsets.write_into(writer),
LocaFormat::Short => self
.offsets
.iter()
.for_each(|off| ((off >> 1) as u16).write_into(writer)),
}
}
}
impl Validate for Loca {
fn validate_impl(&self, _ctx: &mut ValidationCtx) {}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn no_glyphs_is_short() {
assert_eq!(LocaFormat::Short, LocaFormat::new(&Vec::new()));
}
#[test]
fn some_glyphs_is_short() {
assert_eq!(LocaFormat::Short, LocaFormat::new(&[24, 48, 112]));
}
#[test]
fn unpadded_glyphs_is_long() {
assert_eq!(LocaFormat::Long, LocaFormat::new(&[24, 7, 112]));
}
#[test]
fn big_glyphs_is_long() {
assert_eq!(
LocaFormat::Long,
LocaFormat::new(&(0..=32).map(|i| i * 0x1000).collect::<Vec<_>>())
);
}
}