use std::{collections::HashMap, io::Read, path::PathBuf};
use tectonic_errors::prelude::*;
use tectonic_io_base::OpenResult;
use tectonic_status_base::tt_warning;
use crate::{
fonts::FontEnsemble, html::Element, specials::Special, templating::Templating, Common,
EmittingState, FixedPoint, TexFontNum,
};
#[derive(Debug)]
pub(crate) struct InitializationState {
templates: HashMap<String, String>,
next_template_path: String,
next_output_path: String,
fonts: FontEnsemble,
main_body_font_num: Option<i32>,
tag_associations: HashMap<Element, TexFontNum>,
cur_font_family_definition: Option<FontFamilyBuilder>,
cur_font_family_tag_associations: Option<FontFamilyTagAssociator>,
variables: HashMap<String, String>,
}
impl Default for InitializationState {
fn default() -> Self {
InitializationState {
templates: Default::default(),
next_template_path: Default::default(),
next_output_path: "index.html".to_owned(),
fonts: Default::default(),
main_body_font_num: None,
tag_associations: Default::default(),
cur_font_family_definition: None,
cur_font_family_tag_associations: None,
variables: Default::default(),
}
}
}
impl InitializationState {
pub(crate) fn in_endable_init(&self) -> bool {
self.cur_font_family_definition.is_none() && self.cur_font_family_tag_associations.is_none()
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn handle_define_native_font(
&mut self,
name: &str,
font_num: TexFontNum,
size: FixedPoint,
face_index: u32,
color_rgba: Option<u32>,
extend: Option<u32>,
slant: Option<u32>,
embolden: Option<u32>,
common: &mut Common,
) -> Result<()> {
if self.fonts.contains(font_num) {
return Ok(());
}
let io = common.hooks.io();
let mut texpath = String::default();
let mut ih = None;
for ext in &["", ".otf"] {
texpath = format!("{name}{ext}");
match io.input_open_name(&texpath, common.status) {
OpenResult::Ok(h) => {
ih = Some(h);
break;
}
OpenResult::NotAvailable => continue,
OpenResult::Err(e) => return Err(e),
};
}
let ih = a_ok_or!(ih;
["failed to find a font file associated with the name `{}`", name]
);
self.fonts.register_tex_font(
font_num, size, face_index, color_rgba, extend, slant, embolden, texpath, ih, common,
)
}
pub(crate) fn handle_special(
&mut self,
special: Special<'_>,
common: &mut Common,
) -> Result<()> {
match special {
Special::AddTemplate(t) => self.handle_add_template(t, common),
Special::SetTemplate(t) => self.handle_set_template(t, common),
Special::SetOutputPath(t) => self.handle_set_output_path(t, common),
Special::SetTemplateVariable(t) => self.handle_set_template_variable(t, common),
Special::StartDefineFontFamily => self.handle_start_define_font_family(),
Special::EndDefineFontFamily => self.handle_end_define_font_family(common),
Special::StartFontFamilyTagAssociations => {
self.handle_start_font_family_tag_associations()
}
Special::EndFontFamilyTagAssociations => {
self.handle_end_font_family_tag_associations(common)
}
Special::ProvideFile(_) => {
tt_warning!(common.status, "ignoring too-soon tdux:provideFile special");
Ok(())
}
_ => Ok(()),
}
}
fn handle_add_template(&mut self, texpath: &str, common: &mut Common) -> Result<()> {
let mut ih = atry!(
common.hooks.io().input_open_name(texpath, common.status).must_exist();
["unable to open input HTML template `{}`", texpath]
);
let mut contents = String::new();
atry!(
ih.read_to_string(&mut contents);
["unable to read input HTML template `{}`", texpath]
);
self.templates.insert(texpath.to_owned(), contents);
let (name, digest_opt) = ih.into_name_digest();
common
.hooks
.event_input_closed(name, digest_opt, common.status);
Ok(())
}
fn handle_set_template(&mut self, texpath: &str, _common: &mut Common) -> Result<()> {
texpath.clone_into(&mut self.next_template_path);
Ok(())
}
fn handle_set_output_path(&mut self, texpath: &str, _common: &mut Common) -> Result<()> {
texpath.clone_into(&mut self.next_output_path);
Ok(())
}
fn handle_set_template_variable(&mut self, remainder: &str, common: &mut Common) -> Result<()> {
if let Some((varname, varval)) = remainder.split_once(' ') {
self.variables.insert(varname.to_owned(), varval.to_owned());
} else {
tt_warning!(
common.status,
"ignoring malformatted tdux:setTemplateVariable special `{}`",
remainder
);
}
Ok(())
}
fn handle_start_define_font_family(&mut self) -> Result<()> {
self.cur_font_family_definition = Some(FontFamilyBuilder::default());
Ok(())
}
fn handle_end_define_font_family(&mut self, common: &mut Common) -> Result<()> {
if let Some(b) = self.cur_font_family_definition.take() {
let family_name = b.family_name;
let regular = a_ok_or!(b.regular; ["no regular face defined"]);
let bold = a_ok_or!(b.bold; ["no bold face defined"]);
let italic = a_ok_or!(b.italic; ["no italic face defined"]);
let bold_italic = a_ok_or!(b.bold_italic; ["no bold-italic face defined"]);
self.fonts
.register_family(family_name, regular, bold, italic, bold_italic)?;
} else {
tt_warning!(
common.status,
"end of font-family definition block that didn't start"
);
}
Ok(())
}
fn handle_start_font_family_tag_associations(&mut self) -> Result<()> {
self.cur_font_family_tag_associations = Some(FontFamilyTagAssociator::default());
Ok(())
}
fn handle_end_font_family_tag_associations(&mut self, common: &mut Common) -> Result<()> {
if let Some(mut a) = self.cur_font_family_tag_associations.take() {
for (k, v) in a.assoc.drain() {
self.tag_associations.insert(k, v);
}
} else {
tt_warning!(
common.status,
"end of font-family tag-association block that didn't start"
);
}
Ok(())
}
pub(crate) fn handle_text_and_glyphs(
&mut self,
font_num: TexFontNum,
text: &str,
_glyphs: &[u16],
_xs: &[i32],
_ys: &[i32],
common: &mut Common,
) -> Result<()> {
if let Some(b) = self.cur_font_family_definition.as_mut() {
if text.starts_with("bold-italic") {
b.bold_italic = Some(font_num);
} else if text.starts_with("bold") {
b.bold = Some(font_num);
} else if text.starts_with("italic") {
b.italic = Some(font_num);
} else {
b.regular = Some(font_num);
b.family_name = if let Some(fname) = text.strip_prefix("family-name:") {
fname.to_owned()
} else {
format!("tdux{font_num}")
};
if self.main_body_font_num.is_none() {
self.main_body_font_num = Some(font_num);
}
}
} else if let Some(a) = self.cur_font_family_tag_associations.as_mut() {
for tagname in text.split_whitespace() {
let el: Element = tagname.parse().unwrap();
a.assoc.insert(el, font_num);
}
} else {
tt_warning!(
common.status,
"internal bug; losing text `{}` in initialization phase",
text
);
}
Ok(())
}
pub(crate) fn initialization_finished(mut self, common: &mut Common) -> Result<EmittingState> {
if let Some(precomputed) = common.precomputed_assets.as_ref() {
precomputed.check_runtime_fonts(&mut self.fonts, common)?;
}
let mut context = tera::Context::default();
let tempdir = atry!(
tempfile::Builder::new().prefix("tectonic_tera_workaround").tempdir();
["couldn't create empty temporary directory for Tera"]
);
let mut p = PathBuf::from(tempdir.path());
p.push("*");
let p = a_ok_or!(
p.to_str();
["couldn't convert Tera temporary directory name to UTF8 as required"]
);
let mut tera = atry!(
tera::Tera::parse(p);
["couldn't initialize Tera templating engine in temporary directory `{}`", p]
);
atry!(
tera.add_raw_templates(self.templates.iter());
["couldn't compile Tera templates"]
);
for (varname, varvalue) in self.variables {
context.insert(varname, &varvalue);
}
let templating = Templating::new(
tera,
context,
self.next_template_path,
self.next_output_path,
);
EmittingState::new_from_init(
self.fonts,
self.main_body_font_num,
templating,
self.tag_associations,
)
}
}
#[derive(Debug, Default)]
struct FontFamilyBuilder {
family_name: String,
regular: Option<TexFontNum>,
bold: Option<TexFontNum>,
italic: Option<TexFontNum>,
bold_italic: Option<TexFontNum>,
}
#[derive(Debug, Default)]
struct FontFamilyTagAssociator {
assoc: HashMap<Element, TexFontNum>,
}