add index to location table

This commit is contained in:
lauchmelder 2025-03-02 20:51:12 +01:00
parent d77396a6ec
commit b3f9246e99
6 changed files with 168 additions and 41 deletions

View file

@ -2,6 +2,7 @@ mod table_directory;
mod character_map;
mod maximum_profile;
mod font_header;
mod index_to_location;
mod search;
use std::{fs::File, io::{BufReader, Read}};
@ -9,6 +10,7 @@ use std::{fs::File, io::{BufReader, Read}};
use bincode::Options;
use character_map::CharacterMap;
use font_header::FontHeader;
use index_to_location::{IndexToLocation, IndexToLocationArgs};
use log::{debug, info};
use maximum_profile::MaximumProfile;
use table_directory::TableDirectory;
@ -22,7 +24,8 @@ pub struct Font {
table_directory: TableDirectory,
font_header: FontHeader,
character_map: CharacterMap,
maximum_profile: MaximumProfile
maximum_profile: MaximumProfile,
index_to_location: IndexToLocation
}
impl Font {
@ -30,20 +33,26 @@ impl Font {
let mut buf_reader = BufReader::new(file.try_clone()?);
let table_directory = TableDirectory::new(&mut buf_reader)?;
let font_header = table_directory.get_table(&file)?;
let font_header: FontHeader = table_directory.get_table(&file)?;
debug!("{font_header:#?}");
let character_map = table_directory.get_table(&file)?;
debug!("{character_map:#?}");
let maximum_profile = table_directory.get_table(&file)?;
let maximum_profile: MaximumProfile = table_directory.get_table(&file)?;
debug!("{maximum_profile:#?}");
let index_to_location = table_directory.get_table_with_args(&file, Some(IndexToLocationArgs {
format: font_header.index_to_loc_format,
num_glyphs: maximum_profile.num_glyphs()
}))?;
Ok(Font {
table_directory,
font_header,
character_map,
maximum_profile
maximum_profile,
index_to_location
})
}
@ -54,6 +63,12 @@ impl Font {
info!("glyph id for char '{glyph}' is {glyph_id}");
let Some(offset) = self.index_to_location.get_offset(glyph_id) else {
return Err("Failed to get glyph offset for char");
};
info!("glyph has offset {offset}");
Ok(())
}
}

View file

@ -137,8 +137,9 @@ impl CharacterMap {
impl FontTable for CharacterMap {
const TAG: &'static str = "cmap";
type InitData = ();
fn decode(mut reader: BufReader<File>, table: &TableDirectoryRecord) -> Result<Self>
fn decode(mut reader: BufReader<File>, table: &TableDirectoryRecord, _: Option<Self::InitData>) -> Result<Self>
where Self: Sized
{
reader.seek_relative(2)?; // Skip version because i dont care

View file

@ -14,36 +14,37 @@ pub struct Version {
#[derive(Debug, Deserialize)]
pub struct FontHeader {
#[serde(skip)]
version: Version,
pub version: Version,
#[serde(skip)]
font_revision: Version,
pub font_revision: Version,
checksum_adjust: u32,
magic_number: u32,
flags: u16,
units_per_em: u16,
pub flags: u16,
pub units_per_em: u16,
created: i64,
modified: i64,
pub created: i64,
pub modified: i64,
xmin: i16,
ymin: i16,
xmax: i16,
ymax: i16,
pub xmin: i16,
pub ymin: i16,
pub xmax: i16,
pub ymax: i16,
mac_style: u16,
lowest_rec_ppem: u16,
pub mac_style: u16,
pub lowest_rec_ppem: u16,
font_direction_hint: i16,
index_to_loc_hint: i16,
glyph_data_format: i16
pub font_direction_hint: i16,
pub index_to_loc_format: i16,
pub glyph_data_format: i16
}
impl FontTable for FontHeader {
const TAG: &'static str = "head";
type InitData = ();
fn decode(reader: BufReader<File>, _: &TableDirectoryRecord) -> bincode::Result<Self>
fn decode(reader: BufReader<File>, _: &TableDirectoryRecord, _: Option<Self::InitData>) -> bincode::Result<Self>
where Self: Sized
{
let version = deserialize::<_, Version>(reader.get_ref())?;

View file

@ -0,0 +1,95 @@
use std::{collections::HashMap, fs::File, io::{BufReader, Seek, SeekFrom}};
use log::{debug, error};
use serde::de::DeserializeOwned;
use crate::font::deserialize;
use super::table_directory::{FontTable, TableDirectoryRecord};
#[derive(Debug)]
pub struct IndexToLocation {
data_source: BufReader<File>,
offset: u32,
num_glyphs: u16,
stride: u16,
cache: HashMap<u16, u32>
}
impl IndexToLocation {
fn read_offset_with_stride<T: DeserializeOwned>(&self) -> Option<u32>
where u32: From<T>
{
match deserialize::<_, T>(self.data_source.get_ref()) {
Ok(val) => Some(u32::from(val)),
Err(err) => {
error!("{err}");
None
}
}
}
fn read_offset(&self) -> Option<u32> {
match self.stride {
2 => self.read_offset_with_stride::<u16>(),
4 => self.read_offset_with_stride::<u32>(),
_ => None
}
}
pub fn get_offset(&mut self, glyph_id: u16) -> Option<u32> {
if glyph_id > self.num_glyphs {
error!("glyph id {glyph_id} out of range");
return None;
}
if let Some(value) = self.cache.get(&glyph_id) {
debug!("Found glyph {glyph_id} in cache");
return Some(*value);
}
if let Err(err) = self.data_source.seek(SeekFrom::Start(self.offset as u64 + (glyph_id * self.stride) as u64)) {
error!("{err}");
return None;
}
debug!("Reading glyph offset from file");
let offset = self.read_offset()?;
self.cache.insert(glyph_id, offset);
Some(offset)
}
}
pub struct IndexToLocationArgs {
pub format: i16,
pub num_glyphs: u16
}
impl FontTable for IndexToLocation {
const TAG: &'static str = "loca";
type InitData = IndexToLocationArgs;
fn decode(reader: BufReader<File>, table: &TableDirectoryRecord, args: Option<Self::InitData>) -> bincode::Result<Self>
where Self: Sized
{
let args = args.unwrap();
if args.format > 1 {
return Err(bincode::ErrorKind::Custom(format!("Invalid index to location format: '{}'", args.format)).into());
}
Ok(IndexToLocation {
data_source: reader,
offset: table.offset,
stride: 2 + (args.format as u16) * 2,
num_glyphs: args.num_glyphs,
cache: HashMap::new()
})
}
}

View file

@ -8,25 +8,25 @@ use super::{deserialize, table_directory::{FontTable, TableDirectoryRecord}};
#[derive(Debug, Deserialize)]
pub struct MaximumProfileV05 {
num_glyphs: u16
pub num_glyphs: u16
}
#[derive(Debug, Deserialize)]
pub struct MaximumProfileV10 {
num_glyphs: u16,
max_points: u16,
max_countours: u16,
max_composite_points: u16,
max_composite_countours: u16,
max_zones: u16,
max_twilight_points: u16,
max_storage: u16,
max_function_defs: u16,
max_instruction_defs: u16,
max_stack_elements: u16,
max_size_of_instructions: u16,
max_component_elements: u16,
max_component_depth: u16
pub num_glyphs: u16,
pub max_points: u16,
pub max_countours: u16,
pub max_composite_points: u16,
pub max_composite_countours: u16,
pub max_zones: u16,
pub max_twilight_points: u16,
pub max_storage: u16,
pub max_function_defs: u16,
pub max_instruction_defs: u16,
pub max_stack_elements: u16,
pub max_size_of_instructions: u16,
pub max_component_elements: u16,
pub max_component_depth: u16
}
#[derive(Debug)]
@ -35,10 +35,20 @@ pub enum MaximumProfile {
V1_0(MaximumProfileV10)
}
impl MaximumProfile {
pub fn num_glyphs(&self) -> u16 {
match self {
Self::V0_5(profile) => profile.num_glyphs,
Self::V1_0(profile) => profile.num_glyphs
}
}
}
impl FontTable for MaximumProfile {
const TAG: &'static str = "maxp";
type InitData = ();
fn decode(mut reader: BufReader<File>, _: &TableDirectoryRecord) -> Result<Self>
fn decode(mut reader: BufReader<File>, _: &TableDirectoryRecord, _: Option<Self::InitData>) -> Result<Self>
where Self: Sized
{
let version: u32 = deserialize(reader.by_ref())?;

View file

@ -38,28 +38,33 @@ impl TableDirectory {
}
pub fn get_table<T: FontTable>(&self, file: &File) -> Result<T> {
self.get_table_with_args(file, None)
}
pub fn get_table_with_args<T: FontTable>(&self, file: &File, init_data: Option<T::InitData>) -> Result<T> {
let table_id = T::TAG.as_bytes().first_chunk::<4>().unwrap().iter().fold(0u32, |running, &val| {
(running << 8) | (val as u32)
});
T::find_and_decode(file, self.0.get(&table_id).ok_or(bincode::ErrorKind::Custom(format!("No table with id '{}' found", T::TAG)))?)
T::find_and_decode(file, self.0.get(&table_id).ok_or(bincode::ErrorKind::Custom(format!("No table with id '{}' found", T::TAG)))?, init_data)
}
}
pub trait FontTable {
const TAG: &'static str;
type InitData;
fn decode(reader: BufReader<File>, table: &TableDirectoryRecord) -> Result<Self>
fn decode(reader: BufReader<File>, table: &TableDirectoryRecord, init_data: Option<Self::InitData>) -> Result<Self>
where Self: Sized;
fn find_and_decode(file: &File, table: &TableDirectoryRecord) -> Result<Self>
fn find_and_decode(file: &File, table: &TableDirectoryRecord, init_data: Option<Self::InitData>) -> Result<Self>
where Self: Sized
{
let file = file.try_clone()?;
let mut reader = BufReader::new(file);
reader.seek(SeekFrom::Start(table.offset as u64))?;
Self::decode(reader, table)
Self::decode(reader, table, init_data)
}
}