use core::cell::RefCell;
use core::fmt;
use alloc::sync::Arc;
use bon::bon;
use crate::macro_expander::MacroMap;
use crate::namespace::KeyMap;
use crate::types::{ErrorLocationProvider, ParseError, ParseErrorKind};
use crate::utils::protocol_from_url;
#[cfg(feature = "wasm")]
use wasm_bindgen::prelude::wasm_bindgen;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OutputFormat {
HtmlAndMathml,
Html,
Mathml,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StrictMode {
Ignore,
Warn,
Error,
}
#[cfg_attr(feature = "wasm", wasm_bindgen)]
#[derive(Debug, Clone)]
pub struct Settings {
pub display_mode: bool,
#[cfg_attr(feature = "wasm", wasm_bindgen(skip))]
pub output: OutputFormat,
pub leqno: bool,
pub fleqn: bool,
pub throw_on_error: bool,
#[cfg_attr(feature = "wasm", wasm_bindgen(getter_with_clone))]
pub error_color: String,
#[cfg_attr(feature = "wasm", wasm_bindgen(skip))]
pub macros: RefCell<MacroMap>,
pub min_rule_thickness: f64,
pub color_is_text_color: bool,
#[cfg_attr(feature = "wasm", wasm_bindgen(skip))]
pub strict: StrictSetting,
#[cfg_attr(feature = "wasm", wasm_bindgen(skip))]
pub trust: TrustSetting,
pub max_size: f64,
pub max_expand: usize,
pub global_group: bool,
pub size_multiplier: f64,
#[cfg_attr(feature = "wasm", wasm_bindgen(getter_with_clone))]
pub color: Option<String>,
}
#[bon]
impl Settings {
#[must_use]
#[builder]
pub fn new(
display_mode: Option<bool>,
output: Option<OutputFormat>,
leqno: Option<bool>,
fleqn: Option<bool>,
throw_on_error: Option<bool>,
error_color: Option<String>,
macros: Option<MacroMap>,
min_rule_thickness: Option<f64>,
color_is_text_color: Option<bool>,
strict: Option<StrictSetting>,
trust: Option<TrustSetting>,
max_size: Option<f64>,
max_expand: Option<usize>,
global_group: Option<bool>,
size_multiplier: Option<f64>,
color: Option<String>,
) -> Self {
Self {
display_mode: display_mode.unwrap_or(false),
output: output.unwrap_or(OutputFormat::HtmlAndMathml),
leqno: leqno.unwrap_or(false),
fleqn: fleqn.unwrap_or(false),
throw_on_error: throw_on_error.unwrap_or(true),
error_color: error_color.unwrap_or_else(|| "#cc0000".to_owned()),
macros: RefCell::from(macros.unwrap_or_default()),
min_rule_thickness: min_rule_thickness.unwrap_or(0.0),
color_is_text_color: color_is_text_color.unwrap_or(false),
strict: strict.unwrap_or_default(),
trust: trust.unwrap_or_default(),
max_size: max_size.unwrap_or(f64::INFINITY).max(0.0),
max_expand: max_expand.unwrap_or(1000),
global_group: global_group.unwrap_or(false),
size_multiplier: size_multiplier.unwrap_or(1.0),
color,
}
}
#[expect(clippy::print_stderr)]
pub fn report_nonstrict(
&self,
error_code: &str,
error_msg: &str,
token: Option<&dyn ErrorLocationProvider>,
) -> Result<(), ParseError> {
match self.resolve_strict(error_code, error_msg, token) {
StrictMode::Ignore => Ok(()),
StrictMode::Error => {
let kind = ParseErrorKind::StrictModeError {
message: error_msg.to_owned(),
code: error_code.to_owned(),
};
if let Some(t) = token {
Err(ParseError::with_token(kind, t))
} else {
Err(ParseError::new(kind))
}
}
StrictMode::Warn => {
eprintln!(
"LaTeX-incompatible input and strict mode is set to 'warn': {error_msg} [{error_code}]"
);
Ok(())
}
}
}
#[must_use]
#[expect(clippy::print_stderr)]
pub fn use_strict_behavior(
&self,
error_code: &str,
error_msg: &str,
token: Option<&dyn ErrorLocationProvider>,
) -> bool {
match self.resolve_strict_catch(error_code, error_msg, token) {
StrictMode::Ignore => false,
StrictMode::Error => true,
StrictMode::Warn => {
eprintln!(
"LaTeX-incompatible input and strict mode is set to 'warn': {error_msg} [{error_code}]"
);
false
}
}
}
pub fn is_trusted(&self, context: &mut TrustContext) -> bool {
if context.protocol.is_none()
&& let Some(url) = &context.url
{
if let Some(protocol) = protocol_from_url(url) {
context.protocol = Some(protocol);
} else {
return false;
}
}
match &self.trust {
TrustSetting::Bool(b) => *b,
TrustSetting::Function(f) => f(context).unwrap_or(false),
}
}
fn resolve_strict(
&self,
error_code: &str,
error_msg: &str,
token: Option<&dyn ErrorLocationProvider>,
) -> StrictMode {
match &self.strict {
StrictSetting::Mode(m) => *m,
StrictSetting::Bool(b) => {
if *b {
StrictMode::Error
} else {
StrictMode::Ignore
}
}
StrictSetting::Function(f) => match f(error_code, error_msg, token) {
Some(StrictReturn::Mode(m)) => m,
Some(StrictReturn::Bool(b)) => {
if b {
StrictMode::Error
} else {
StrictMode::Ignore
}
}
None => StrictMode::Ignore,
},
}
}
fn resolve_strict_catch(
&self,
error_code: &str,
error_msg: &str,
token: Option<&dyn ErrorLocationProvider>,
) -> StrictMode {
match &self.strict {
StrictSetting::Mode(m) => *m,
StrictSetting::Bool(b) => {
if *b {
StrictMode::Error
} else {
StrictMode::Ignore
}
}
StrictSetting::Function(func) => {
let f = Arc::clone(func);
let error_code = error_code.to_owned();
let error_msg = error_msg.to_owned();
let token_ref = token;
let res = f(&error_code, &error_msg, token_ref);
match res {
Some(StrictReturn::Mode(m)) => m,
Some(StrictReturn::Bool(b)) => {
if b {
StrictMode::Error
} else {
StrictMode::Ignore
}
}
None => StrictMode::Ignore,
}
}
}
}
}
impl Default for Settings {
fn default() -> Self {
Self::builder().build()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StrictReturn {
Bool(bool),
Mode(StrictMode),
}
pub type StrictFunction =
dyn Fn(&str, &str, Option<&dyn ErrorLocationProvider>) -> Option<StrictReturn> + Send + Sync;
#[derive(Clone)]
pub enum StrictSetting {
Mode(StrictMode),
Bool(bool),
Function(Arc<StrictFunction>),
}
impl fmt::Debug for StrictSetting {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Mode(m) => write!(f, "StrictSetting::Mode({m:?})"),
Self::Bool(b) => write!(f, "StrictSetting::Bool({b})"),
Self::Function(_) => write!(f, "StrictSetting::Function(<fn>)"),
}
}
}
impl Default for StrictSetting {
fn default() -> Self {
Self::Mode(StrictMode::Ignore)
}
}
#[derive(Debug, Clone, Default)]
pub struct TrustContext {
pub command: String,
pub url: Option<String>,
pub protocol: Option<String>,
pub class: Option<String>,
pub id: Option<String>,
pub style: Option<String>,
pub attributes: Option<KeyMap<String, String>>,
}
pub type TrustFunction = dyn Fn(&mut TrustContext) -> Option<bool> + Send + Sync;
#[derive(Clone)]
pub enum TrustSetting {
Bool(bool),
Function(Arc<TrustFunction>),
}
impl fmt::Debug for TrustSetting {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Bool(b) => write!(f, "TrustSetting::Bool({b})"),
Self::Function(_) => write!(f, "TrustSetting::Function(<fn>)"),
}
}
}
impl Default for TrustSetting {
fn default() -> Self {
Self::Bool(false)
}
}