mod tfm;
use crate::commands::{PrimitiveCommand, ResolvedToken, TeXCommand};
use crate::engine::filesystem::{File, FileSystem};
use crate::engine::fontsystem::tfm::TfmFile;
use crate::engine::{EngineAux, EngineReferences, EngineTypes};
use crate::prelude::CommandCode;
use crate::tex::characters::Character;
use crate::tex::numerics::{Numeric, TeXDimen, TeXInt};
use crate::tex::tokens::control_sequences::{CSHandler, CSName};
use crate::utils::errors::{TeXError, TeXResult};
use crate::utils::{HMap, Ptr};
use std::path::PathBuf;
use std::sync::RwLock;
pub trait FontSystem: Clone + std::fmt::Debug {
type Char: Character;
type CS: CSName<Self::Char>;
type Int: TeXInt;
type Font: Font<Char = Self::Char, CS = Self::CS, Dim = Self::Dim, Int = Self::Int>;
type Dim: TeXDimen;
fn null(&self) -> Self::Font;
fn new<ET: EngineTypes<Char = Self::Char, CSName = Self::CS>>(aux: &mut EngineAux<ET>) -> Self;
fn new_font<S: AsRef<str>, F: FileSystem>(
&mut self,
path: S,
macroname: <Self::Font as Font>::CS,
fs: &mut F,
) -> Self::Font;
}
pub trait Font: Clone + std::fmt::Debug {
type Char: Character;
type Dim: TeXDimen;
type Int: TeXInt;
type CS: CSName<Self::Char>;
fn get_at(&self) -> Self::Dim;
fn has_at_set(&self) -> bool;
fn set_at(&mut self, d: Self::Dim);
fn name(&self) -> &Self::CS;
fn filename(&self) -> &str;
fn get_dim(&self, idx: u16) -> Self::Dim;
fn set_dim(&mut self, idx: u16, d: Self::Dim);
fn get_hyphenchar(&self) -> Self::Int;
fn set_hyphenchar(&mut self, c: Self::Int);
fn get_skewchar(&self) -> Self::Int;
fn set_skewchar(&mut self, c: Self::Int);
fn has_char(&self, c: Self::Char) -> bool;
fn display<W: std::fmt::Write>(
&self,
i: &<Self::CS as CSName<Self::Char>>::Handler,
w: W,
) -> std::fmt::Result;
fn get_wd(&self, c: Self::Char) -> Self::Dim;
fn get_ht(&self, c: Self::Char) -> Self::Dim;
fn get_dp(&self, c: Self::Char) -> Self::Dim;
fn get_ic(&self, c: Self::Char) -> Self::Dim;
fn set_ic(&mut self, c: Self::Char, d: Self::Dim);
fn ligature(&self, c1: Self::Char, c2: Self::Char) -> Option<Self::Char>;
}
#[derive(Clone, Debug)]
pub struct TfmFontSystem<I: TeXInt, D: TeXDimen + Numeric<I>, CS: CSName<u8>> {
files: HMap<PathBuf, Ptr<TfmFile>>,
null: Ptr<TfmFontI<I, D, CS>>,
}
impl<I: TeXInt, D: TeXDimen + Numeric<I>, CS: CSName<u8>> FontSystem for TfmFontSystem<I, D, CS> {
type Char = u8;
type Int = I;
type Font = TfmFont<I, D, CS>;
type Dim = D;
type CS = CS;
fn new<ET: EngineTypes<Char = Self::Char, CSName = Self::CS>>(aux: &mut EngineAux<ET>) -> Self {
let null_file = TfmFile {
hyphenchar: 45,
skewchar: 255,
dimen: vec![],
size: 0,
widths: [0.0; 256],
defined: [false; 256],
heights: [0.0; 256],
depths: [0.0; 256],
ics: [0.0; 256],
ligs: HMap::default(),
filepath: std::path::PathBuf::from("/nullfont"),
};
let muts = Mutables::default();
let null = Ptr::new(TfmFontI {
file: Ptr::new(null_file),
muts: RwLock::new(muts),
name: aux.memory.cs_interner_mut().cs_from_str("nullfont"),
});
TfmFontSystem {
files: HMap::default(),
null,
}
}
fn new_font<S: AsRef<str>, F: FileSystem>(
&mut self,
path: S,
macroname: CS,
fs: &mut F,
) -> Self::Font {
let path = path.as_ref();
let f = if path.ends_with(".tfm") {
fs.get(path)
} else {
fs.get(format!("{path}.tfm"))
};
let ff = match self.files.get(f.path()) {
Some(ff) => ff.clone(),
None => {
let ff = Ptr::new(TfmFile::new(f.path().to_path_buf()));
self.files.insert(f.path().to_path_buf(), ff.clone());
ff
}
};
let muts = Mutables::default();
let font = TfmFontI {
file: ff,
muts: RwLock::new(muts),
name: macroname,
};
Ptr::new(font)
}
fn null(&self) -> Self::Font {
self.null.clone()
}
}
#[derive(Default)]
pub(crate) struct Mutables<I: TeXInt, D: TeXDimen + Numeric<I>> {
at: Option<D>,
#[cfg(feature = "pdflatex")]
pub(crate) lps: HMap<u8, I>,
#[cfg(feature = "pdflatex")]
pub(crate) rps: HMap<u8, I>,
ics: HMap<u8, D>,
hyphenchar: Option<I>,
skewchar: Option<I>,
dimens: Vec<D>,
}
pub struct TfmFontI<I: TeXInt, D: TeXDimen + Numeric<I>, CS: CSName<u8>> {
file: Ptr<TfmFile>,
name: CS,
pub(crate) muts: RwLock<Mutables<I, D>>,
}
impl<I: TeXInt, D: TeXDimen + Numeric<I>, CS: CSName<u8>> PartialEq for TfmFontI<I, D, CS> {
fn eq(&self, other: &Self) -> bool {
self.name == other.name
}
}
impl<I: TeXInt, D: TeXDimen + Numeric<I>, CS: CSName<u8>> std::fmt::Debug for TfmFontI<I, D, CS> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Font {:?}", self.name)
}
}
pub type TfmFont<I, D, CS> = Ptr<TfmFontI<I, D, CS>>;
impl<I: TeXInt, D: TeXDimen + Numeric<I>, CS: CSName<u8>> Font for TfmFont<I, D, CS> {
type Char = u8;
type CS = CS;
type Int = I;
type Dim = D;
fn has_char(&self, c: Self::Char) -> bool {
self.file.defined[c as usize]
}
fn get_hyphenchar(&self) -> I {
match self.muts.read().unwrap().hyphenchar {
Some(c) => c,
None => (self.file.hyphenchar as i32).into(),
}
}
fn set_hyphenchar(&mut self, c: I) {
self.muts.write().unwrap().hyphenchar = Some(c);
}
fn get_skewchar(&self) -> I {
match self.muts.read().unwrap().skewchar {
Some(c) => c,
None => (self.file.skewchar as i32).into(),
}
}
fn set_skewchar(&mut self, c: I) {
self.muts.write().unwrap().skewchar = Some(c);
}
fn get_at(&self) -> Self::Dim {
let at = self.muts.read().unwrap().at;
match at {
Some(d) => d,
None => D::from_sp(self.file.size),
}
}
fn has_at_set(&self) -> bool {
self.muts.read().unwrap().at.is_some()
}
fn get_dim(&self, idx: u16) -> Self::Dim {
match self.muts.read().unwrap().dimens.get(idx as usize) {
Some(d) => *d,
None => match self.file.dimen.get(idx as usize) {
Some(d) => self.get_at().scale_float(*d as f64),
None => D::default(),
},
}
}
fn set_dim(&mut self, idx: u16, d: Self::Dim) {
let v = &mut self.muts.write().unwrap().dimens;
if idx as usize >= v.len() {
v.resize(idx as usize + 1, D::default());
}
v[idx as usize] = d;
}
fn set_at(&mut self, d: Self::Dim) {
self.muts.write().unwrap().at = Some(d);
}
fn name(&self) -> &Self::CS {
&self.name
}
fn filename(&self) -> &str {
self.file.name()
}
fn display<W: std::fmt::Write>(
&self,
_i: &<Self::CS as CSName<u8>>::Handler,
mut w: W,
) -> std::fmt::Result {
let at = self.muts.read().unwrap().at;
match at {
Some(d) => write!(w, "{} at {}", self.file.name(), d),
None => write!(w, "{}", self.file.name()),
}
}
fn get_ic(&self, c: Self::Char) -> Self::Dim {
let v = &mut self.muts.write().unwrap().ics;
match v.get(&c) {
Some(d) => *d,
None => {
let d = self.file.ics[c as usize];
self.get_at().scale_float(d as f64)
}
}
}
fn set_ic(&mut self, c: Self::Char, d: Self::Dim) {
let v = &mut self.muts.write().unwrap().ics;
v.insert(c, d);
}
fn get_wd(&self, c: Self::Char) -> Self::Dim {
let d = self.file.widths[c as usize].max(0.0);
self.get_at().scale_float(d as f64)
}
fn get_ht(&self, c: Self::Char) -> Self::Dim {
let d = self.file.heights[c as usize].max(0.0);
self.get_at().scale_float(d as f64)
}
fn get_dp(&self, c: Self::Char) -> Self::Dim {
let d = self.file.depths[c as usize].max(0.0);
self.get_at().scale_float(d as f64)
}
fn ligature(&self, char1: Self::Char, char2: Self::Char) -> Option<Self::Char> {
self.file.ligs.get(&(char1, char2)).copied()
}
}
impl<ET: EngineTypes> EngineReferences<'_, ET> {
pub fn read_font(
&mut self,
skip_eq: bool,
token: &ET::Token,
) -> TeXResult<<ET::FontSystem as FontSystem>::Font, ET> {
let mut had_eq = !skip_eq;
crate::expand_loop!(self,token,
ResolvedToken::Cmd(Some(c)) => match c {
TeXCommand::Font(f) => return Ok(f.clone()),
TeXCommand::Primitive{cmd:PrimitiveCommand::FontCmd{read,..},..} => {
return read(self,token)
}
_ => {
self.general_error("Missing font identifier.".to_string())?;
return Ok(self.fontsystem.null())
}
}
ResolvedToken::Tk {char,code:CommandCode::Other} if !had_eq && matches!(char.try_into(),Ok(b'=')) => {
had_eq = true;
}
_ => {
self.general_error("Missing font identifier.".to_string())?;
return Ok(self.fontsystem.null())
}
);
TeXError::file_end_while_use(self.aux, self.state, self.mouth, token)?;
Ok(self.fontsystem.null())
}
}