add frontend project and docker containers

This commit is contained in:
lauchmelder 2025-03-05 13:47:59 +01:00
parent ae8525b891
commit 00a78f33b6
36 changed files with 3729 additions and 61 deletions

2
server/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/target
*.old

1822
server/Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

21
server/Cargo.toml Normal file
View file

@ -0,0 +1,21 @@
[package]
name = "font-explorer"
version = "0.1.0"
edition = "2021"
[lib]
name = "fontloader"
path = "src/lib.rs"
[[bin]]
name = "webview"
path = "src/webview/main.rs"
[dependencies]
bincode = "1.3.3"
bitfield = "0.19.0"
clap = { version = "4.5.21", features = ["derive"] }
log = "0.4.26"
rocket = "0.5.1"
serde = { version = "1.0.215", features = ["derive", "serde_derive"] }
simplelog = { version = "0.12.2", features = ["termcolor"] }

8
server/Dockerfile Normal file
View file

@ -0,0 +1,8 @@
FROM debian:bookworm-slim
WORKDIR /app
VOLUME /app
VOLUME /fonts
CMD [ "/app/webview" ]

94
server/src/font.rs Normal file
View file

@ -0,0 +1,94 @@
mod table_directory;
mod character_map;
mod maximum_profile;
mod font_header;
mod index_to_location;
mod glyph_data;
mod search;
use std::{error::Error, fs::File, io::{BufReader, Read}};
use bincode::Options;
use character_map::CharacterMap;
use font_header::FontHeader;
use glyph_data::GlyphTable;
use index_to_location::{IndexToLocation, IndexToLocationArgs};
use log::{debug, info};
use maximum_profile::MaximumProfile;
use table_directory::TableDirectory;
pub use glyph_data::{GlyphData, GlyphHeader, GlyphPoint, Glyph, SimpleGlyph, SplineElement};
fn deserialize<R: Read, T: serde::de::DeserializeOwned>(reader: R) -> bincode::Result<T> {
bincode::options().with_big_endian().with_fixint_encoding().deserialize_from::<R, T>(reader)
}
fn deserialize_vec<R: Read, T: serde::de::DeserializeOwned>(reader: &mut R, entries: usize) -> bincode::Result<Vec<T>> {
if entries == 0 {
return Ok(vec![]);
}
let mut data: Vec<T> = Vec::with_capacity(entries);
for _ in 0..entries {
data.push(deserialize::<_, T>(reader.by_ref())?);
}
Ok(data)
}
#[derive(Debug)]
pub struct Font {
table_directory: TableDirectory,
font_header: FontHeader,
character_map: CharacterMap,
maximum_profile: MaximumProfile,
index_to_location: IndexToLocation,
glyph_table: GlyphTable
}
impl Font {
pub fn new(file: File) -> bincode::Result<Self> {
let mut buf_reader = BufReader::new(file.try_clone()?);
let table_directory = TableDirectory::new(&mut buf_reader)?;
debug!("{table_directory:#?}");
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: 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()
}))?;
let glyph_table = table_directory.get_table(&file)?;
Ok(Font {
table_directory,
font_header,
character_map,
maximum_profile,
index_to_location,
glyph_table
})
}
pub fn get_data_for(&mut self, glyph: char) -> Result<Glyph, Box<dyn Error>> {
let Some(glyph_id) = self.character_map.get_char(glyph) else {
return Err("Failed to get glyph id for char".into());
};
info!("glyph id for char '{glyph}' is {glyph_id}");
let glyph = self.glyph_table.get_glyph(glyph_id, &mut self.index_to_location)?;
Ok(glyph)
}
}

View file

@ -0,0 +1,168 @@
use std::{fs::File, io::{BufReader, Read, Seek, SeekFrom}};
use bincode::{ErrorKind, Result};
use log::{debug, error, warn};
use serde::{de::DeserializeOwned, Deserialize};
use super::{deserialize, search::SearchParameters, table_directory::{FontTable, TableDirectoryRecord}};
#[derive(Debug, Deserialize, Copy, Clone)]
struct SegmentToDeltaHeader {
_length: u16,
_language: u16,
}
#[derive(Debug)]
struct SegmentToDelta {
data_source: BufReader<File>,
header: SegmentToDeltaHeader,
search_params: SearchParameters,
end_code: Vec<u16>,
start_code: Vec<u16>,
id_delta: Vec<i16>,
id_range_offset: Vec<u16>,
glyph_table: u32
}
impl SegmentToDelta {
fn get_next_vec<R: Read + Seek, T: DeserializeOwned>(reader: &mut R, seg_count: u16) -> Result<Vec<T>> {
let mut values: Vec<T> = vec!();
for _ in 0..seg_count {
values.push(deserialize(reader.by_ref())?);
}
Ok(values)
}
fn new(mut reader: BufReader<File>) -> Result<SegmentToDelta> {
let header: SegmentToDeltaHeader = deserialize(reader.by_ref())?;
let search_params: SearchParameters = deserialize(reader.by_ref())?;
let end_code = Self::get_next_vec(&mut reader, search_params.seg_count_x2 >> 1)?;
reader.seek_relative(2)?;
let start_code = Self::get_next_vec(&mut reader, search_params.seg_count_x2 >> 1)?;
let id_delta = Self::get_next_vec(&mut reader, search_params.seg_count_x2 >> 1)?;
let id_range_offset = Self::get_next_vec(&mut reader, search_params.seg_count_x2 >> 1)?;
let glyph_table_pos = reader.stream_position()? as u32;
Ok(SegmentToDelta {
data_source: reader,
glyph_table: glyph_table_pos,
header,
search_params,
end_code,
start_code,
id_delta,
id_range_offset
})
}
fn get_glyph_id(&mut self, c: u16) -> Option<u16> {
// Find index of first endcode that is greater or equal to c
let index =self.search_params.find_first_goe(self.end_code.as_slice(), c)?;
debug!("First endcode that is >={c} has index {index} (={})", self.end_code[index]);
if self.start_code[index] > c {
error!("Start code belonging to this index ({}) is too big", self.start_code[index]);
return None
}
let id_range_offset = self.id_range_offset[index];
debug!("range offset = {id_range_offset}");
if id_range_offset == 0 {
debug!("delta = {}", self.id_delta[index]);
return Some((c as i16 + self.id_delta[index]) as u16 % u16::MAX);
}
let glyph_map_offset = ((id_range_offset >> 1) as usize) + ((c - self.start_code[index]) as usize) - index;
if let Err(error) = self.data_source.seek(SeekFrom::Start(self.glyph_table as u64 + glyph_map_offset as u64)) {
error!("{error}");
return None;
}
if let Ok(glyph) = deserialize::<_, u16>(self.data_source.by_ref()) {
return Some(glyph);
}
return None;
}
}
#[derive(Debug)]
pub struct CharacterMap {
_table_start: u32,
_length: u32,
mapping_table: SegmentToDelta
}
#[derive(Debug, Deserialize)]
struct EncodingRecord {
platform: u16,
encoding: u16,
offset: u32,
}
impl CharacterMap {
fn find_unicode_table<R: Read + Seek>(reader: &mut R, num_tables: u16) -> Option<EncodingRecord> {
for _ in 0..num_tables {
let Ok(table) = deserialize::<_, EncodingRecord>(reader.by_ref()) else {
return None;
};
if table.platform == 0 && table.encoding == 3 {
return Some(table);
}
}
None
}
pub fn get_char(&mut self, c: char) -> Option<u16> {
let code = c as u32;
if code > (u16::MAX as u32) {
warn!("Codepoint {code} for '{c}' is not a valid codepoint");
return None;
}
let code = (code & 0xFFFF) as u16;
debug!("Getting glyph id for codepoint {code}");
return self.mapping_table.get_glyph_id(code);
}
}
impl FontTable for CharacterMap {
const TAG: &'static str = "cmap";
type InitData = ();
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
let num_tables: u16 = deserialize(reader.by_ref())?;
let Some(record) = Self::find_unicode_table(&mut reader, num_tables) else {
return Err(Box::new(ErrorKind::Custom("No encoding for Unicode 2.0 BMP".into())));
};
reader.seek(SeekFrom::Start((table.offset + record.offset) as u64))?;
let format: u16 = deserialize(reader.by_ref())?;
if format != 4 {
todo!();
}
Ok(CharacterMap {
mapping_table: SegmentToDelta::new(reader)?,
_table_start: table.offset,
_length: table.length,
})
}
}

View file

@ -0,0 +1,63 @@
use std::{fs::File, io::BufReader};
use log::warn;
use serde::Deserialize;
use super::{deserialize, table_directory::{FontTable, TableDirectoryRecord}};
#[derive(Debug, Deserialize, Default)]
pub struct Version {
major: u16,
minor: u16
}
#[derive(Debug, Deserialize)]
pub struct FontHeader {
#[serde(skip)]
pub version: Version,
#[serde(skip)]
pub font_revision: Version,
checksum_adjust: u32,
magic_number: u32,
pub flags: u16,
pub units_per_em: u16,
pub created: i64,
pub modified: i64,
pub xmin: i16,
pub ymin: i16,
pub xmax: i16,
pub ymax: i16,
pub mac_style: u16,
pub lowest_rec_ppem: u16,
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, _: Option<Self::InitData>) -> bincode::Result<Self>
where Self: Sized
{
let version = deserialize::<_, Version>(reader.get_ref())?;
if version.major != 1 && version.minor != 0 {
warn!("Font version is not 1.0");
}
let font_revision = deserialize::<_, Version>(reader.get_ref())?;
let mut font_header: FontHeader = deserialize(reader.get_ref())?;
font_header.version = version;
font_header.font_revision = font_revision;
Ok(font_header)
}
}

View file

@ -0,0 +1,442 @@
use std::{collections::HashMap, fs::File, io::{BufReader, Read, Seek, SeekFrom}, iter::{zip, Peekable}, slice::Iter};
use bincode::{ErrorKind, Result};
use bitfield::bitfield;
use log::{debug, error, info, warn};
use serde::Deserialize;
use super::{deserialize, deserialize_vec, index_to_location::IndexToLocation, table_directory::{FontTable, TableDirectoryRecord}};
#[derive(Debug, Deserialize)]
pub struct GlyphHeader {
num_countours: i16,
pub xmin: i16,
pub ymin: i16,
pub xmax: i16,
pub ymax: i16
}
bitfield! {
#[derive(Copy, Clone, Deserialize)]
struct GlyphFlagData(u8);
impl Debug;
on_curve, _: 0;
x_short, _: 1;
y_short, _: 2;
repeat, _: 3;
x_same_or_positive, _: 4;
y_same_or_positive, _: 5;
overlap_simple, _: 6;
reserved, _: 7;
}
#[derive(Debug)]
struct GlyphFlag {
flags: GlyphFlagData,
repeat: u8
}
#[derive(Debug)]
struct GlyphFlags<'a> {
flags: &'a Vec<GlyphFlag>,
current_item: usize,
repetitions: u16
}
impl<'a> GlyphFlags<'a> {
fn new(flags: &'a Vec<GlyphFlag>) -> GlyphFlags<'a> {
GlyphFlags { flags, current_item: 0, repetitions: 0 }
}
}
impl<'a> Iterator for GlyphFlags<'a> {
type Item = GlyphFlagData;
fn next(&mut self) -> Option<Self::Item> {
if self.current_item >= self.flags.len() {
debug!("no more logical flags");
return None;
}
let current = &self.flags[self.current_item];
if self.repetitions <= current.repeat as u16 {
if self.repetitions > 0 {
debug!("flag #{} repeated {} times", self.current_item, self.repetitions);
} else {
debug!("yield flag #{}", self.current_item);
}
self.repetitions += 1;
return Some(current.flags);
}
self.repetitions = 0;
self.current_item += 1;
self.next()
}
}
#[derive(Debug, Copy, Clone)]
pub struct GlyphPoint {
pub x: i32,
pub y: i32,
pub on_curve: bool,
pub is_virtual: bool,
pub is_endpoint: bool,
}
#[derive(Debug)]
pub struct SimpleGlyph {
instructions: Vec<u8>,
points: Vec<GlyphPoint>
}
impl SimpleGlyph {
fn read_flags<R: Read + Seek>(reader: &mut R, num_points: usize) -> Result<Vec<GlyphFlag>> {
debug!("reading flags from position {}", reader.stream_position()?);
let mut flags: Vec<GlyphFlag> = vec![];
let mut points_processed: usize = 0;
while points_processed <= num_points {
let flag: GlyphFlagData = deserialize(reader.by_ref())?;
let repetitions: u8 = if flag.repeat() { deserialize(reader.by_ref())? } else { 0 };
flags.push(GlyphFlag {
flags: flag,
repeat: repetitions
});
points_processed += repetitions as usize + 1;
debug!("Flag: {flag:#?}, repeated {repetitions} times, logical elements processed: {points_processed}")
}
// debug!("glyph flags: {flags:#?}");
Ok(flags)
}
fn read_coordinates<R: Read + Seek>(reader: &mut R, flags: GlyphFlags, is_y: bool) -> Result<Vec<(i32, bool)>> {
debug!("reading batch of {} coordinates at {}", if is_y { "y" } else { "x" }, reader.stream_position()?);
let mut coordinates: Vec<(i32, bool)> = vec![];
let mut current_coordinate = 0i32;
for flag in flags {
debug!("reading next coordinate");
let short_vec = if is_y { flag.y_short() } else { flag.x_short() };
let same_or_positive = if is_y { flag.y_same_or_positive() } else { flag.x_same_or_positive() };
debug!("short vec: {short_vec}, same or positive: {same_or_positive}");
if short_vec {
let sign: i32 = if same_or_positive { 1 } else { -1 };
let delta = sign * (deserialize::<_, u8>(reader.by_ref())? as i32);
debug!("delta = (u8){delta}");
current_coordinate += delta;
coordinates.push((current_coordinate, flag.on_curve()));
} else {
if same_or_positive {
debug!("coordinate is repeated");
coordinates.push((current_coordinate, flag.on_curve()));
} else {
let delta: i16 = deserialize(reader.by_ref())?;
debug!("delta = (i16){delta}");
current_coordinate += delta as i32;
coordinates.push((current_coordinate, flag.on_curve()));
}
}
debug!("current coordinate is now {current_coordinate}");
}
Ok(coordinates)
}
fn new<R: Read + Seek>(header: &GlyphHeader, reader: &mut R) -> Result<SimpleGlyph> {
let end_points: Vec<u16> = deserialize_vec(reader, header.num_countours as usize)?;
debug!("glyph end points: {end_points:#?}");
let num_instructions: u16 = deserialize(reader.by_ref())?;
let instructions: Vec<u8> = deserialize_vec(reader, num_instructions as usize)?;
debug!("glyph instructions: {num_instructions}");
let flags = SimpleGlyph::read_flags(reader, *end_points.last().unwrap() as usize)?;
let x_coords = SimpleGlyph::read_coordinates(reader, GlyphFlags::new(&flags), false)?;
let y_coords = SimpleGlyph::read_coordinates(reader, GlyphFlags::new(&flags), true)?;
if x_coords.len() != *end_points.last().unwrap() as usize + 1 {
warn!("expected {} x coordinates, got {} instead", end_points.last().unwrap() + 1, x_coords.len());
}
if y_coords.len() != *end_points.last().unwrap() as usize + 1 {
warn!("expected {} y coordinates, got {} instead", end_points.last().unwrap() + 1, y_coords.len());
}
let mut current_point: u16 = 0;
let coordinates = zip(x_coords.iter().cloned(), y_coords.iter().cloned())
.map(|(x, y)| {
let point = GlyphPoint {
x: x.0, y: y.0, on_curve: x.1, is_endpoint: end_points.contains(&current_point), is_virtual: false
};
current_point += 1;
point
});
// coordinates.for_each(|point| debug!("({}, {}, {})", point.x, point.y, if point.on_curve { "on" } else { "off" }));
Ok(SimpleGlyph { instructions, points: coordinates.collect() })
}
pub fn iter<'a>(&'a self) -> SimpleGlyphIter<'a> {
SimpleGlyphIter {
glyph: self,
last_point: None,
iter: self.points.iter().peekable()
}
}
}
struct SimpleGlyphIter<'a> {
glyph: &'a SimpleGlyph,
last_point: Option<GlyphPoint>,
iter: Peekable<Iter<'a, GlyphPoint>>
}
impl<'a> Iterator for SimpleGlyphIter<'a> {
type Item = GlyphPoint;
fn next(&mut self) -> Option<Self::Item> {
if let Some(last_point) = self.last_point {
let Some(&next_point) = self.iter.peek() else {
return None
};
if !last_point.on_curve && !next_point.on_curve {
// Interpolate a point
let virtual_point = GlyphPoint {
x: last_point.x + (next_point.x - last_point.x) / 2,
y: last_point.y + (next_point.y - last_point.y) / 2,
is_endpoint: false,
is_virtual: true,
on_curve: true
};
self.last_point = Some(virtual_point);
return self.last_point;
}
}
self.last_point = self.iter.next().copied();
self.last_point
}
}
#[derive(Debug)]
pub enum GlyphData {
Simple(SimpleGlyph),
Composite
}
#[derive(Debug)]
pub struct Glyph {
pub header: GlyphHeader,
pub data: GlyphData
}
#[derive(Debug)]
pub enum SplineElement {
Line(GlyphPoint, GlyphPoint),
Bezier(GlyphPoint, GlyphPoint, GlyphPoint)
}
pub struct SplineIter<'a> {
points: Box<dyn Iterator<Item = GlyphPoint> + 'a>,
last_point: Option<GlyphPoint>,
final_control: Option<GlyphPoint>,
first_point: Option<GlyphPoint>
}
impl<'a> SplineIter<'a> {
fn handle_spline_start(&mut self) -> Result<()> {
let Some(point) = self.points.next() else {
return Err(ErrorKind::Custom("Not enough points in contous to form a spline".into()).into());
};
if point.on_curve {
self.last_point = Some(point);
self.first_point = Some(point);
return Ok(());
} else {
self.final_control = Some(point);
}
let Some(point) = self.points.next() else {
return Err(ErrorKind::Custom("Not enough points in contous to form a spline".into()).into());
};
if !point.on_curve {
return Err(ErrorKind::Custom("Iterator returned invalid point sequence".into()).into());
}
self.last_point = Some(point);
self.first_point = Some(point);
Ok(())
}
fn handle_spline_close(&mut self) -> Option<SplineElement> {
let Some(last_point) = self.last_point else {
return None;
};
if let Some(final_control) = self.final_control {
Some(SplineElement::Bezier(last_point, final_control, self.first_point.unwrap()))
} else {
Some(SplineElement::Line(last_point, self.first_point.unwrap()))
}
}
}
impl<'a> Iterator for SplineIter<'a> {
type Item = SplineElement;
fn next(&mut self) -> Option<Self::Item> {
let Some(last_point) = self.last_point else {
debug!("spline iter: last point is None");
return None;
};
if last_point.is_endpoint {
debug!("spline iter: point is an endpoint of contour. closing spline, starting new one (if possible)");
let result = self.handle_spline_close();
if let Err(err) = self.handle_spline_start() {
debug!("spline iter: no more contours");
self.last_point = None;
}
return result;
}
let Some(point) = self.points.next() else {
self.last_point = None;
debug!("spline iter: no more points, closing spline");
return self.handle_spline_close();
};
if point.on_curve {
self.last_point = Some(point);
debug!("spline iter: point is on curve");
return Some(SplineElement::Line(last_point, point));
}
debug!("spline iter: point is control point");
let Some(next_point) = self.points.next() else {
debug!("spline iter: no more data after control point, closing spline");
self.last_point = None;
return self.handle_spline_close();
};
debug!("spline iter: returning bezier");
self.last_point = Some(next_point);
Some(SplineElement::Bezier(last_point, point, next_point))
}
}
impl Glyph {
pub fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = GlyphPoint> + 'a> {
Box::new(
match &self.data {
GlyphData::Simple(glyph) => glyph.iter(),
GlyphData::Composite => todo!()
}
)
}
pub fn splines<'a>(&'a self) -> Result<SplineIter<'a>> {
let mut splines = SplineIter {
points: self.iter(),
last_point: None,
final_control: None,
first_point: None
};
splines.handle_spline_start()?;
Ok(splines)
}
}
#[derive(Debug)]
pub struct GlyphTable {
data_source: BufReader<File>,
offset: u32,
cache: HashMap<u16, Glyph>
}
impl GlyphTable {
pub fn get_glyph(&mut self, glyph_id: u16, location: &mut IndexToLocation) -> Result<Glyph>{
let Some(offset) = location.get_offset(glyph_id) else {
return Err(ErrorKind::Custom("Failed to look up glyph location".into()).into());
};
debug!("glyph has offset {offset}");
self.data_source.seek(SeekFrom::Start(self.offset as u64 + offset as u64))?;
debug!("reading glyph data from position {}", self.offset as u64 + offset as u64);
let header: GlyphHeader = deserialize(self.data_source.get_ref())?;
debug!("glyph header: {header:#?}");
let result = if header.num_countours >= 0 {
Ok(Glyph {
data: GlyphData::Simple(SimpleGlyph::new(&header, self.data_source.by_ref())?),
header
})
} else {
todo!();
};
// sanity check
let stream_pos = self.data_source.stream_position()?;
let Some(next_offset) = location.get_offset(glyph_id + 1) else {
warn!("Failed to perform sanity check after glyph parsing");
return result;
};
let remaining_bytes = stream_pos as i64 - (self.offset + next_offset) as i64;
if remaining_bytes != 0 {
warn!("sanity check failed, there are still {remaining_bytes} bytes remaining in this glyph entry (read head: {}, table off: {}, location: [{offset}, {next_offset}])", stream_pos, self.offset);
}
result
}
}
impl FontTable for GlyphTable {
const TAG: &'static str = "glyf";
type InitData = ();
fn decode(reader: BufReader<File>, table: &TableDirectoryRecord, _: Option<Self::InitData>) -> bincode::Result<Self>
where Self: Sized
{
Ok(GlyphTable {
data_source: reader,
offset: table.offset,
cache: HashMap::new()
})
}
}

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

@ -0,0 +1,64 @@
use std::{fs::File, io::{BufReader, Read, Seek, SeekFrom}};
use bincode::{de::read, Result};
use log::debug;
use serde::Deserialize;
use super::{deserialize, table_directory::{FontTable, TableDirectoryRecord}};
#[derive(Debug, Deserialize)]
pub struct MaximumProfileV05 {
pub num_glyphs: u16
}
#[derive(Debug, Deserialize)]
pub struct MaximumProfileV10 {
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)]
pub enum MaximumProfile {
V0_5(MaximumProfileV05),
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, _: Option<Self::InitData>) -> Result<Self>
where Self: Sized
{
let version: u32 = deserialize(reader.by_ref())?;
debug!("maxp table version: {:#08x}", version);
match version {
0x00005000 => Ok(MaximumProfile::V0_5(deserialize(reader.by_ref())?)),
0x00010000 => Ok(MaximumProfile::V1_0(deserialize(reader.by_ref())?)),
_ => { return Err(bincode::ErrorKind::Custom("Invalid maxp table version".into()).into()); }
}
}
}

65
server/src/font/search.rs Normal file
View file

@ -0,0 +1,65 @@
use std::fmt::Debug;
use log::debug;
use serde::Deserialize;
#[derive(Debug, Copy, Clone, Deserialize, Default)]
pub struct SearchParameters {
pub seg_count_x2: u16, // num of elements x2
pub search_range: u16, // largest power of two smaller than num of elements
pub entry_selector: u16, // max levels in binary search
pub _range_shift: u16 // remaining elements in array
}
impl SearchParameters {
fn binary_search(&self, array: &[u16], target: u16) -> Option<usize> {
let mut index = (self.search_range >> 2) as usize;
for level in 1..=self.entry_selector {
let end_code = array[index];
if end_code >= target && array[index - 1] < target {
debug!("binary search found result");
return Some(index);
}
let mut correction = (self.search_range >> (level + 2)) as usize;
if correction == 0 {
correction = 1;
}
if end_code < target {
index += correction;
debug!("considered endcode smaller than target, setting index to {index}");
} else {
let new_index = index - correction;
if correction == 1 && array[new_index] < target {
debug!("no more candidates");
return Some(index);
}
index = new_index;
debug!("considered endcode larger than target, setting index to {index}");
}
}
Some(index + 1)
}
pub fn find_first_goe(&self, array: &[u16], target: u16) -> Option<usize> {
if target > array[(self.seg_count_x2 as usize >> 1) - 1] {
return None;
}
if target > array[(self.search_range as usize >> 1) - 1] {
debug!("{}", array[(self.search_range as usize >> 1) - 1]);
for i in (self.search_range as usize >> 1)..array.len() {
if array[i] >= target {
return Some(i);
}
}
}
self.binary_search(array, target)
}
}

View file

@ -0,0 +1,72 @@
use std::{collections::HashMap, fs::File, io::{BufReader, Read, Seek, SeekFrom}};
use bincode::Result;
use log::debug;
use serde::Deserialize;
use crate::font::deserialize;
#[derive(Deserialize, Debug)]
struct TableDirectoryHeader {
sfnt_version: u32,
num_tables: u16,
}
#[derive(Deserialize, Debug, Copy, Clone)]
pub struct TableDirectoryRecord {
pub table_tag: u32,
pub checksum: u32,
pub offset: u32,
pub length: u32
}
#[derive(Debug, Default)]
pub struct TableDirectory(pub HashMap<u32, TableDirectoryRecord>);
impl TableDirectory {
pub fn new<R: Read + Seek>(reader: &mut R) -> Result<TableDirectory> {
let header: TableDirectoryHeader = deserialize(reader.by_ref())?;
debug!("{header:#x?}");
let mut tables: HashMap<u32, TableDirectoryRecord> = HashMap::with_capacity(header.num_tables as usize);
reader.seek_relative(3 * size_of::<u16>() as i64)?;
for _ in 0..header.num_tables {
let record: TableDirectoryRecord = deserialize(reader.by_ref())?;
tables.insert(record.table_tag, record);
}
Ok(TableDirectory(tables))
}
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)))?, init_data)
}
}
pub trait FontTable {
const TAG: &'static str;
type InitData;
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, 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, init_data)
}
}

21
server/src/lib.rs Normal file
View file

@ -0,0 +1,21 @@
use std::error::Error;
use log::LevelFilter;
use simplelog::{ColorChoice, Config, TermLogger, TerminalMode};
pub mod font;
pub mod writer;
pub use font::Font;
pub use writer::SvgWriter;
pub fn init() -> Result<(), Box<dyn Error>> {
TermLogger::init(
LevelFilter::Debug,
Config::default(),
TerminalMode::Mixed,
ColorChoice::Auto
)?;
Ok(())
}

View file

@ -0,0 +1,46 @@
#[macro_use] extern crate rocket;
use std::{fs::File, net::{IpAddr, Ipv4Addr}, sync::Mutex};
use fontloader::{writer::Visitor, Font, SvgWriter};
use rocket::{response::{content, status}, Config, State};
struct SharedFont {
font: Mutex<Font>
}
#[get("/glyph/<glyph>")]
fn get_glyph(font: &State<SharedFont>, glyph: &str) -> status::Accepted<content::RawHtml<String>> {
let Some(char) = glyph.chars().next() else {
return status::Accepted(content::RawHtml("Please provide a char".into()));
};
let result = match font.font.lock().unwrap().get_data_for(char) {
Ok(result) => result,
Err(err) => return status::Accepted(content::RawHtml(err.to_string()))
};
let mut svg_buf = vec![];
SvgWriter::new(&mut svg_buf).write(&result);
status::Accepted(content::RawHtml(String::from_utf8(svg_buf).unwrap()))
}
#[launch]
fn rocket() -> _ {
let _ = fontloader::init();
let font = Font::new(File::open("/fonts/Helvetica.ttf").unwrap());
let Ok(font) = font else {
let err = font.unwrap_err().to_string();
panic!("{err}");
};
let mut config = Config::release_default();
config.address = IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0));
config.port = 8000;
rocket::custom(config)
.manage(SharedFont { font: Mutex::from(font) })
.mount("/", routes![get_glyph])
}

23
server/src/writer.rs Normal file
View file

@ -0,0 +1,23 @@
mod svg;
use crate::font::{Glyph, GlyphHeader, GlyphPoint, SplineElement};
use log::debug;
pub use svg::SvgWriter;
pub trait Visitor {
fn write_prefix(&mut self, header: &GlyphHeader);
fn write_point(&mut self, point: &SplineElement);
fn write_suffix(&mut self);
fn write(&mut self, glyph: &Glyph) {
self.write_prefix(&glyph.header);
glyph.splines().unwrap().for_each(|spline_element| {
debug!("{spline_element:#?}");
self.write_point(&spline_element)
});
self.write_suffix();
}
}

105
server/src/writer/svg.rs Normal file
View file

@ -0,0 +1,105 @@
use std::{fs::File, io::{BufWriter, Write}};
use crate::font::{GlyphHeader, GlyphPoint, SplineElement};
use super::Visitor;
pub struct SvgWriter<'a, W: Sized + Write> {
first_point: bool,
writer: &'a mut W,
points: Vec<(i32, i32)>,
control_points: Vec<(i32, i32)>,
virtual_points: Vec<(i32, i32)>
}
impl<'a, W: Sized + Write> SvgWriter<'a, W> {
pub fn new(out: &'a mut W) -> Self {
SvgWriter {
first_point: true,
writer: out,
points: vec![],
control_points: vec![],
virtual_points: vec![]
}
}
}
impl<'a, W: Sized + Write> SvgWriter<'a, W> {
fn handle_start_point(&mut self, point: &GlyphPoint) {
if self.first_point {
write!(self.writer, "M{} {} ", point.x, -point.y);
self.first_point = false;
}
}
fn handle_end_point(&mut self, point: &GlyphPoint) {
if point.is_endpoint {
self.first_point = true;
}
}
fn write_points(&mut self) {
for (x, y) in &self.points {
write!(self.writer, "<circle cx=\"{x}\" cy=\"{y}\" r=\"10\" fill=\"red\" />");
}
for (x, y) in &self.control_points {
write!(self.writer, "<circle cx=\"{x}\" cy=\"{y}\" r=\"10\" fill=\"blue\" />");
}
for (x, y) in &self.virtual_points {
write!(self.writer, "<circle cx=\"{x}\" cy=\"{y}\" r=\"10\" fill=\"green\" />");
}
}
}
impl<'a, W: Sized + Write> Visitor for SvgWriter<'a, W> {
fn write_prefix(&mut self, header: &GlyphHeader) {
write!(
self.writer,
"<svg width=\"1000\" height=\"1000\" viewBox=\"{} {} {} {}\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"",
header.xmin, -header.ymax,
header.xmax - header.xmin,
header.ymax - header.ymin
);
}
fn write_point(&mut self, point: &SplineElement) {
match point {
SplineElement::Line(start, end) => {
self.handle_start_point(start);
write!(self.writer, "L{} {} ", end.x, -end.y);
self.points.push((start.x, -start.y));
self.handle_end_point(start);
},
SplineElement::Bezier(start, control, end) => {
self.handle_start_point(start);
write!(self.writer, "Q{} {} {} {} ", control.x, -control.y, end.x, -end.y);
if start.is_virtual {
self.virtual_points.push((start.x, -start.y));
} else {
self.points.push((start.x, -start.y));
}
self.control_points.push((control.x, -control.y));
self.handle_end_point(start);
}
}
}
fn write_suffix(&mut self) {
write!(
self.writer,
"\" style=\"fill:none; stroke:black; stroke-width:3;\" />"
);
self.write_points();
write!(self.writer, "</svg>");
}
}