use alloc::borrow::Cow;
use phf::{Set, phf_set};
use crate::namespace::KeyMap;
use crate::build_common::{
VListChild, VListElem, VListKern, VListParam, lookup_symbol, make_span, make_svg_span,
make_symbol, make_v_list,
};
use crate::dom_tree::{DomSpan, HtmlDomNode, PathNode, SvgChildNode, SvgNode, SymbolNode};
use crate::options::Options;
use crate::style::{SCRIPT, SCRIPTSCRIPT, Style, TEXT};
use crate::svg_geometry::{inner_path, sqrt_path, tall_delim};
use crate::symbols::Mode;
use crate::types::ClassList;
use crate::types::{CssProperty, ParseError, ParseErrorKind};
use crate::units::make_em;
use crate::{CharacterMetrics, KatexContext};
const SIZE_TO_MAX_HEIGHT: [f64; 5] = [0.0, 1.2, 1.8, 2.4, 3.0];
pub fn size_to_max_height<T>(size: T) -> f64
where
T: Into<usize>,
{
let size = size.into().min(SIZE_TO_MAX_HEIGHT.len() - 1); SIZE_TO_MAX_HEIGHT[size]
}
const VB_PAD: f64 = 80.0;
const EM_PAD: f64 = 0.08;
const LAP_IN_EMS: f64 = 0.008;
#[derive(Debug, Clone)]
pub enum DelimiterType {
Small(&'static Style),
Large(usize),
Stack,
}
#[derive(Debug)]
pub struct DelimiterSizing {
pub delimiter_type: DelimiterType,
pub height: f64,
}
#[derive(Debug)]
pub struct SqrtImageResult {
pub span: DomSpan,
pub rule_width: f64,
pub advance_width: f64,
}
fn get_metrics(
ctx: &KatexContext,
symbol: &str,
font: &str,
mode: Mode,
) -> Result<CharacterMetrics, ParseError> {
let replace = ctx.symbols.get_math(symbol).and_then(|s| s.replace);
let mut buf = [0u8; 4];
let symbol = replace.map_or(symbol, |s| s.encode_utf8(&mut buf));
let look_up = lookup_symbol(ctx, symbol, font, mode)?;
if let Some(look_up) = look_up
&& let Some(metrics) = look_up.metrics
{
Ok(metrics)
} else {
Err(ParseError::new(ParseErrorKind::UnsupportedSymbolFont {
symbol: symbol.to_owned(),
font: font.to_owned(),
}))
}
}
fn style_wrap(
delim: HtmlDomNode,
to_style: &'static Style,
options: &Options,
classes: ClassList,
) -> DomSpan {
let new_options = options.having_base_style(Some(to_style));
let mut combined_classes = classes;
combined_classes.extend(new_options.sizing_classes(options));
let mut span = make_span(combined_classes, vec![delim], Some(options), None);
let multiplier = new_options.size_multiplier / options.size_multiplier;
span.height *= multiplier;
span.depth *= multiplier;
span.max_font_size = new_options.size_multiplier;
span
}
pub fn make_small_delim<T: Into<ClassList>>(
ctx: &KatexContext,
delim: &str,
style: &'static Style,
center: bool,
options: &Options,
mode: Mode,
classes: T,
) -> Result<DomSpan, ParseError> {
let classes = classes.into();
let text = make_symbol(
ctx,
delim,
"Main-Regular",
mode,
Some(options),
classes.clone(),
)?;
let mut span = style_wrap(text.into(), style, options, classes);
if center {
span = center_span(span, options, TEXT);
}
Ok(span)
}
fn mathrm_size(
ctx: &KatexContext,
value: &str,
size: usize,
mode: Mode,
options: &Options,
) -> Result<SymbolNode, ParseError> {
let font_name = format!("Size{size}-Regular");
make_symbol(
ctx,
value,
&font_name,
mode,
Some(options),
ClassList::Empty,
)
}
pub fn make_large_delim<T: Into<ClassList>>(
ctx: &KatexContext,
delim: &str,
size: usize,
center: bool,
options: &Options,
mode: Mode,
classes: T,
) -> Result<DomSpan, ParseError> {
let classes = classes.into();
let inner = mathrm_size(ctx, delim, size, mode, options)?;
let mut span = style_wrap(
make_span(
vec![
Cow::Borrowed("delimsizing"),
Cow::Owned(format!("size{size}")),
],
vec![inner.into()],
Some(options),
None,
)
.into(),
TEXT,
options,
classes,
);
if center {
span = center_span(span, options, TEXT);
}
Ok(span)
}
fn center_span(mut span: DomSpan, options: &Options, style: &'static Style) -> DomSpan {
let new_options = options.having_base_style(Some(style));
let shift = (1.0 - options.size_multiplier / new_options.size_multiplier)
* options.font_metrics().axis_height;
span.classes.push("delimcenter");
span.height -= shift;
span.depth += shift;
span.style.insert(CssProperty::Top, make_em(shift));
span
}
fn make_glyph_span(
ctx: &KatexContext,
symbol: &str,
font: &str,
mode: Mode,
) -> Result<VListChild, ParseError> {
let size_classes = if font == "Size1-Regular" {
&["delimsizinginner", "delim-size1"]
} else {
&["delimsizinginner", "delim-size4"]
};
let corner = make_span(
ClassList::Const(size_classes),
vec![
make_span(
ClassList::Empty,
vec![make_symbol(ctx, symbol, font, mode, None, ClassList::Empty)?.into()],
None,
None,
)
.into(),
],
None,
None,
);
Ok(VListElem::builder().elem(corner.into()).build().into())
}
fn make_inner(
ctx: &KatexContext,
ch: &str,
height: f64,
options: &Options,
) -> Result<VListChild, ParseError> {
let Some(first_char) = ch.chars().next() else {
return Err(ParseError::new(ParseErrorKind::EmptyDelimiterCharacter));
};
let width = if let Some(metric) = ctx
.font_metrics
.get_metric("Size4-Regular", first_char as u32)?
{
metric.width
} else {
ctx.font_metrics
.get_metric("Size1-Regular", first_char as u32)?
.map_or(0.0, |metrics| metrics.width)
};
let path = PathNode {
path_name: "inner".to_owned(),
alternate: Some(inner_path(ch, (1000.0 * height).round())),
};
let mut svg_attributes = KeyMap::default();
svg_attributes.extend([
("width".to_owned(), make_em(width)),
("height".to_owned(), make_em(height)),
("style".to_owned(), format!("width:{}", make_em(width))),
(
"viewBox".to_owned(),
format!("0 0 {} {}", (1000.0 * width), (1000.0 * height).round()),
),
("preserveAspectRatio".to_owned(), "xMinYMin".to_owned()),
]);
let svg_children = vec![SvgChildNode::Path(path)];
let svg_node = SvgNode::builder()
.children(svg_children)
.attributes(svg_attributes)
.build();
let mut span = make_svg_span(ClassList::Empty, vec![svg_node], options);
span.height = height;
span.style.insert(CssProperty::Height, make_em(height));
span.style.insert(CssProperty::Width, make_em(width));
Ok(VListElem::builder().elem(span.into()).build().into())
}
pub fn make_stacked_delim<T: Into<ClassList>>(
ctx: &KatexContext,
delim: &str,
height_total: f64,
center: bool,
options: &Options,
mode: Mode,
classes: T,
) -> Result<DomSpan, ParseError> {
let classes = classes.into();
let mut top = delim.to_owned();
let mut middle = None;
let mut repeat = delim.to_owned();
let mut bottom = delim.to_owned();
let mut svg_label = String::new();
let mut view_box_width = 0.0;
let mut font = "Size1-Regular".to_owned();
match delim {
"\\uparrow" => {
"\u{23d0}".clone_into(&mut repeat);
"\u{23d0}".clone_into(&mut bottom);
}
"\\Uparrow" => {
"\u{2016}".clone_into(&mut repeat);
"\u{2016}".clone_into(&mut bottom);
}
"\\downarrow" => {
"\u{23d0}".clone_into(&mut top);
"\u{23d0}".clone_into(&mut repeat);
}
"\\Downarrow" => {
"\u{2016}".clone_into(&mut top);
"\u{2016}".clone_into(&mut repeat);
}
"\\updownarrow" => {
"\\uparrow".clone_into(&mut top);
"\u{23d0}".clone_into(&mut repeat);
"\\downarrow".clone_into(&mut bottom);
}
"\\Updownarrow" => {
"\\Uparrow".clone_into(&mut top);
"\u{2016}".clone_into(&mut repeat);
"\\Downarrow".clone_into(&mut bottom);
}
"|" | "\\lvert" | "\\rvert" | "\\vert" => {
"\u{2223}".clone_into(&mut repeat);
"vert".clone_into(&mut svg_label);
view_box_width = 333.0;
}
"\\|" | "\\lVert" | "\\rVert" | "\\Vert" => {
"\u{2225}".clone_into(&mut repeat);
"doublevert".clone_into(&mut svg_label);
view_box_width = 556.0;
}
"[" | "\\lbrack" => {
"\u{23a1}".clone_into(&mut top);
"\u{23a2}".clone_into(&mut repeat);
"\u{23a3}".clone_into(&mut bottom);
"Size4-Regular".clone_into(&mut font);
"lbrack".clone_into(&mut svg_label);
view_box_width = 667.0;
}
"]" | "\\rbrack" => {
"\u{23a4}".clone_into(&mut top);
"\u{23a5}".clone_into(&mut repeat);
"\u{23a6}".clone_into(&mut bottom);
"Size4-Regular".clone_into(&mut font);
"rbrack".clone_into(&mut svg_label);
view_box_width = 667.0;
}
"\\lfloor" | "\u{230a}" => {
"\u{23a2}".clone_into(&mut top);
"\u{23a2}".clone_into(&mut repeat);
"\u{23a3}".clone_into(&mut bottom);
"Size4-Regular".clone_into(&mut font);
"lfloor".clone_into(&mut svg_label);
view_box_width = 667.0;
}
"\\lceil" | "\u{2308}" => {
"\u{23a1}".clone_into(&mut top);
"\u{23a2}".clone_into(&mut repeat);
"\u{23a2}".clone_into(&mut bottom);
"Size4-Regular".clone_into(&mut font);
"lceil".clone_into(&mut svg_label);
view_box_width = 667.0;
}
"\\rfloor" | "\u{230b}" => {
"\u{23a5}".clone_into(&mut top);
"\u{23a5}".clone_into(&mut repeat);
"\u{23a6}".clone_into(&mut bottom);
"Size4-Regular".clone_into(&mut font);
"rfloor".clone_into(&mut svg_label);
view_box_width = 667.0;
}
"\\rceil" | "\u{2309}" => {
"\u{23a4}".clone_into(&mut top);
"\u{23a5}".clone_into(&mut repeat);
"\u{23a5}".clone_into(&mut bottom);
"Size4-Regular".clone_into(&mut font);
"rceil".clone_into(&mut svg_label);
view_box_width = 667.0;
}
"(" | "\\lparen" => {
"\u{239b}".clone_into(&mut top);
"\u{239c}".clone_into(&mut repeat);
"\u{239d}".clone_into(&mut bottom);
"Size4-Regular".clone_into(&mut font);
"lparen".clone_into(&mut svg_label);
view_box_width = 875.0;
}
")" | "\\rparen" => {
"\u{239e}".clone_into(&mut top);
"\u{239f}".clone_into(&mut repeat);
"\u{23a0}".clone_into(&mut bottom);
"Size4-Regular".clone_into(&mut font);
"rparen".clone_into(&mut svg_label);
view_box_width = 875.0;
}
"\\{" | "\\lbrace" => {
"\u{23a7}".clone_into(&mut top);
middle = Some("\u{23a8}".to_owned());
"\u{23a9}".clone_into(&mut bottom);
"\u{23aa}".clone_into(&mut repeat);
"Size4-Regular".clone_into(&mut font);
}
"\\}" | "\\rbrace" => {
"\u{23ab}".clone_into(&mut top);
middle = Some("\u{23ac}".to_owned());
"\u{23ad}".clone_into(&mut bottom);
"\u{23aa}".clone_into(&mut repeat);
"Size4-Regular".clone_into(&mut font);
}
"\\lgroup" | "\u{27ee}" => {
"\u{23a7}".clone_into(&mut top);
"\u{23a9}".clone_into(&mut bottom);
"\u{23aa}".clone_into(&mut repeat);
"Size4-Regular".clone_into(&mut font);
}
"\\rgroup" | "\u{27ef}" => {
"\u{23ab}".clone_into(&mut top);
"\u{23ad}".clone_into(&mut bottom);
"\u{23aa}".clone_into(&mut repeat);
"Size4-Regular".clone_into(&mut font);
}
"\\lmoustache" | "\u{23b0}" => {
"\u{23a7}".clone_into(&mut top);
"\u{23ad}".clone_into(&mut bottom);
"\u{23aa}".clone_into(&mut repeat);
"Size4-Regular".clone_into(&mut font);
}
"\\rmoustache" | "\u{23b1}" => {
"\u{23ab}".clone_into(&mut top);
"\u{23a9}".clone_into(&mut bottom);
"\u{23aa}".clone_into(&mut repeat);
"Size4-Regular".clone_into(&mut font);
}
_ => {}
}
let top_metrics = get_metrics(ctx, &top, &font, mode)?;
let top_height_total = top_metrics.height + top_metrics.depth;
let repeat_metrics = get_metrics(ctx, &repeat, &font, mode)?;
let repeat_height_total = repeat_metrics.height + repeat_metrics.depth;
let bottom_metrics = get_metrics(ctx, &bottom, &font, mode)?;
let bottom_height_total = bottom_metrics.height + bottom_metrics.depth;
let mut middle_height_total = 0.0;
let middle_factor = if let Some(middle_sym) = &middle {
let middle_metrics = get_metrics(ctx, middle_sym, &font, mode)?;
middle_height_total = middle_metrics.height + middle_metrics.depth;
2.0 } else {
1.0
};
let minimal_height = top_height_total + bottom_height_total + middle_height_total;
let delta = (height_total - minimal_height) / (middle_factor * repeat_height_total);
let repeat_count = delta.ceil().max(0.0) as i32;
let real_height_total =
(f64::from(repeat_count) * middle_factor).mul_add(repeat_height_total, minimal_height);
let axis_height = options.font_metrics().axis_height;
let adjusted_axis = if center {
axis_height * options.size_multiplier
} else {
axis_height
};
let depth = real_height_total / 2.0 - adjusted_axis;
let mut stack: Vec<VListChild> = Vec::new();
if svg_label.is_empty() {
stack.push(make_glyph_span(ctx, &bottom, &font, mode)?);
stack.push(VListChild::Kern(VListKern { size: -LAP_IN_EMS }));
if middle.is_none() {
let inner_height = 2.0f64.mul_add(
LAP_IN_EMS,
real_height_total - top_height_total - bottom_height_total,
);
stack.push(make_inner(ctx, &repeat, inner_height, options)?);
} else {
let inner_height = 2.0f64.mul_add(
LAP_IN_EMS,
(real_height_total - top_height_total - bottom_height_total - middle_height_total)
/ 2.0,
);
stack.push(make_inner(ctx, &repeat, inner_height, options)?);
stack.push(VListChild::Kern(VListKern { size: -LAP_IN_EMS }));
if let Some(middle_sym) = &middle {
stack.push(make_glyph_span(ctx, middle_sym, &font, mode)?);
}
stack.push(VListChild::Kern(VListKern { size: -LAP_IN_EMS }));
stack.push(make_inner(ctx, &repeat, inner_height, options)?);
}
stack.push(VListChild::Kern(VListKern { size: -LAP_IN_EMS }));
stack.push(make_glyph_span(ctx, &top, &font, mode)?);
} else {
let mid_height = real_height_total - top_height_total - bottom_height_total;
let view_box_height = (real_height_total * 1000.0).round();
let path_str = tall_delim(&svg_label, (mid_height * 1000.0).round())?;
let path = PathNode {
path_name: svg_label,
alternate: Some(path_str),
};
let width = format!("{:.3}em", view_box_width / 1000.0);
let height = format!("{:.3}em", view_box_height / 1000.0);
let mut svg_attributes = KeyMap::default();
svg_attributes.extend([
("width".to_owned(), width.clone()),
("height".to_owned(), height.clone()),
(
"viewBox".to_owned(),
format!("0 0 {view_box_width} {view_box_height}"),
),
]);
let svg_node = SvgNode::builder()
.children(vec![SvgChildNode::Path(path)])
.attributes(svg_attributes)
.build();
let mut wrapper = make_svg_span(ClassList::Empty, vec![svg_node], options);
wrapper.height = view_box_height / 1000.0;
wrapper.style.insert(CssProperty::Width, width);
wrapper.style.insert(CssProperty::Height, height);
stack.push(VListElem::builder().elem(wrapper.into()).build().into());
}
let new_options = options.having_base_style(Some(TEXT));
let inner = make_v_list(
VListParam::Bottom {
position_data: depth,
children: stack,
},
&new_options,
)?;
let span = style_wrap(
make_span(
ClassList::Const(&["delimsizing", "mult"]),
vec![inner.into()],
Some(&new_options),
None,
)
.into(),
TEXT,
options,
classes,
);
Ok(span)
}
pub fn make_sqrt_image(
ctx: &KatexContext,
height: f64,
options: &Options,
) -> Result<SqrtImageResult, ParseError> {
let new_options = options.having_base_sizing();
let delimiter_type = traverse_sequence(
ctx,
"\\surd",
height * new_options.size_multiplier,
STACK_LARGE_DELIMITER_SEQUENCE,
&new_options,
)?;
let mut size_multiplier = new_options.size_multiplier;
let extra_vinculum =
(options.min_rule_thickness - options.font_metrics().sqrt_rule_thickness).max(0.0);
let span_height;
let tex_height;
let view_box_height;
let advance_width;
let mut span = match delimiter_type {
DelimiterType::Small(_style) => {
view_box_height = 1000.0f64.mul_add(extra_vinculum, 1000.0) + VB_PAD;
if height < 1.0 {
size_multiplier = 1.0; } else if height < 1.4 {
size_multiplier = 0.7; }
span_height = (1.0 + extra_vinculum + EM_PAD) / size_multiplier;
tex_height = (1.0 + extra_vinculum) / size_multiplier;
let span = sqrt_svg(
"sqrtMain",
span_height,
view_box_height as i32,
extra_vinculum,
options,
);
advance_width = 0.833 / size_multiplier;
span
}
DelimiterType::Large(size) => {
view_box_height = (1000.0 + VB_PAD) * SIZE_TO_MAX_HEIGHT[*size];
tex_height = (SIZE_TO_MAX_HEIGHT[*size] + extra_vinculum) / size_multiplier;
span_height = (SIZE_TO_MAX_HEIGHT[*size] + extra_vinculum + EM_PAD) / size_multiplier;
let mut span = sqrt_svg(
&format!("sqrtSize{size}"),
span_height,
view_box_height as i32,
extra_vinculum,
options,
);
span.style
.insert(CssProperty::MinWidth, "1.02em".to_owned());
advance_width = 1.0 / size_multiplier;
span
}
DelimiterType::Stack => {
span_height = height + extra_vinculum + EM_PAD;
tex_height = height + extra_vinculum;
view_box_height = 1000.0f64.mul_add(height, extra_vinculum).round();
let mut span = sqrt_svg(
"sqrtTall",
span_height,
view_box_height as i32,
extra_vinculum,
options,
);
span.style
.insert(CssProperty::MinWidth, "0.742em".to_owned());
advance_width = 1.056;
span
}
};
span.height = tex_height;
span.style.insert(CssProperty::Height, make_em(span_height));
Ok(SqrtImageResult {
span,
rule_width: (options.font_metrics().sqrt_rule_thickness + extra_vinculum) * size_multiplier,
advance_width,
})
}
fn sqrt_svg(
sqrt_name: &str,
height: f64,
view_box_height: i32,
extra_vinculum: f64,
options: &Options,
) -> DomSpan {
let path = sqrt_path(
sqrt_name,
1000.0 * extra_vinculum,
f64::from(view_box_height),
);
let path_node = PathNode {
path_name: sqrt_name.to_owned(),
alternate: Some(path),
};
let svg_children = vec![SvgChildNode::Path(path_node)];
let mut svg_attributes = KeyMap::default();
svg_attributes.extend([
("width".to_owned(), "400em".to_owned()),
("height".to_owned(), make_em(height)),
(
"viewBox".to_owned(),
format!("0 0 400000 {view_box_height}"),
),
(
"preserveAspectRatio".to_owned(),
"xMinYMin slice".to_owned(),
),
]);
let svg_node = SvgNode::builder()
.children(svg_children)
.attributes(svg_attributes)
.build();
let mut svg = make_svg_span("hide-tail", vec![svg_node], options);
svg.style
.insert(CssProperty::MinWidth, "0.853em".to_owned());
svg.style.insert(CssProperty::Height, make_em(height));
svg
}
pub fn sized_delim<T: Into<ClassList>>(
ctx: &KatexContext,
delim: &str,
size: usize,
options: &Options,
mode: Mode,
classes: T,
) -> Result<DomSpan, ParseError> {
let classes = classes.into();
let delim = match delim {
"<" | "\\lt" | "\u{27e8}" => "\\langle",
">" | "\\gt" | "\u{27e9}" => "\\rangle",
_ => delim,
};
if STACK_LARGE_DELIMITERS.contains(delim) || STACK_NEVER_DELIMITERS.contains(delim) {
make_large_delim(ctx, delim, size, false, options, mode, classes)
} else if STACK_ALWAYS_DELIMITERS.contains(delim) {
make_stacked_delim(
ctx,
delim,
SIZE_TO_MAX_HEIGHT[size],
false,
options,
mode,
classes,
)
} else {
Err(ParseError::new(ParseErrorKind::IllegalDelimiter {
delim: delim.to_owned(),
}))
}
}
fn traverse_sequence<'a>(
ctx: &KatexContext,
delim: &str,
height: f64,
sequence: &'a [DelimiterType],
options: &Options,
) -> Result<&'a DelimiterType, ParseError> {
let start = (3 - options.style.size).min(2);
for delim_type in sequence.iter().skip(start) {
if matches!(delim_type, DelimiterType::Stack) {
break;
}
let font = delim_type_to_font(delim_type);
let metrics = get_metrics(ctx, delim, &font, Mode::Math)?;
let mut height_depth = metrics.height + metrics.depth;
if let DelimiterType::Small(style) = &delim_type {
let new_options = options.having_base_style(Some(style));
height_depth *= new_options.size_multiplier;
}
if height_depth > height {
return Ok(delim_type);
}
}
Ok(&sequence[sequence.len() - 1])
}
fn delim_type_to_font(delimiter_type: &DelimiterType) -> String {
match delimiter_type {
DelimiterType::Small(_) => "Main-Regular".to_owned(),
DelimiterType::Large(size) => format!("Size{size}-Regular"),
DelimiterType::Stack => "Size4-Regular".to_owned(),
}
}
pub fn custom_sized_delim<T: Into<ClassList>>(
ctx: &KatexContext,
delim: &str,
height: f64,
center: bool,
options: &Options,
mode: Mode,
classes: T,
) -> Result<DomSpan, ParseError> {
let classes = classes.into();
let delim = match delim {
"<" | "\\lt" | "\u{27e8}" => "\\langle",
">" | "\\gt" | "\u{27e9}" => "\\rangle",
_ => delim,
};
let sequence = if STACK_NEVER_DELIMITERS.contains(delim) {
STACK_NEVER_DELIMITER_SEQUENCE
} else if STACK_LARGE_DELIMITERS.contains(delim) {
STACK_LARGE_DELIMITER_SEQUENCE
} else {
STACK_ALWAYS_DELIMITER_SEQUENCE
};
let delimiter_type = traverse_sequence(ctx, delim, height, sequence, options)?;
match delimiter_type {
DelimiterType::Small(style) => {
make_small_delim(ctx, delim, style, center, options, mode, classes)
}
DelimiterType::Large(size) => {
make_large_delim(ctx, delim, *size, center, options, mode, classes)
}
DelimiterType::Stack => {
make_stacked_delim(ctx, delim, height, center, options, mode, classes)
}
}
}
pub fn left_right_delim<T: Into<ClassList>>(
ctx: &KatexContext,
delim: &str,
height: f64,
depth: f64,
options: &Options,
mode: Mode,
classes: T,
) -> Result<DomSpan, ParseError> {
let classes = classes.into();
let axis_height = options.font_metrics().axis_height * options.size_multiplier;
let delimiter_factor = 901.0;
let delimiter_extend = 5.0 / options.font_metrics().pt_per_em;
let max_dist_from_axis = (height - axis_height).max(depth + axis_height);
let total_height = (max_dist_from_axis / 500.0 * delimiter_factor)
.max(2.0f64.mul_add(max_dist_from_axis, -delimiter_extend));
custom_sized_delim(ctx, delim, total_height, true, options, mode, classes)
}
const STACK_LARGE_DELIMITERS: Set<&str> = phf_set!(
"(", "\\lparen", ")", "\\rparen", "[", "\\lbrack", "]", "\\rbrack", "\\{", "\\lbrace", "\\}",
"\\rbrace", "\\lfloor", "\\rfloor", "\u{230a}", "\u{230b}", "\\lceil", "\\rceil", "\u{2308}",
"\u{2309}", "\\surd",
);
const STACK_ALWAYS_DELIMITERS: Set<&str> = phf_set!(
"\\uparrow",
"\\downarrow",
"\\updownarrow",
"\\Uparrow",
"\\Downarrow",
"\\Updownarrow",
"|",
"\\|",
"\\vert",
"\\Vert",
"\\lvert",
"\\rvert",
"\\lVert",
"\\rVert",
"\\lgroup",
"\\rgroup",
"\u{27ee}",
"\u{27ef}",
"\\lmoustache",
"\\rmoustache",
"\u{23b0}",
"\u{23b1}",
);
const STACK_NEVER_DELIMITERS: Set<&str> = phf_set!(
"<",
">",
"\\langle",
"\\rangle",
"/",
"\\backslash",
"\\lt",
"\\gt",
);
const STACK_NEVER_DELIMITER_SEQUENCE: &[DelimiterType] = &[
DelimiterType::Small(SCRIPTSCRIPT),
DelimiterType::Small(SCRIPT),
DelimiterType::Small(TEXT),
DelimiterType::Large(1),
DelimiterType::Large(2),
DelimiterType::Large(3),
DelimiterType::Large(4),
];
const STACK_ALWAYS_DELIMITER_SEQUENCE: &[DelimiterType] = &[
DelimiterType::Small(SCRIPTSCRIPT),
DelimiterType::Small(SCRIPT),
DelimiterType::Small(TEXT),
DelimiterType::Stack,
];
const STACK_LARGE_DELIMITER_SEQUENCE: &[DelimiterType] = &[
DelimiterType::Small(SCRIPTSCRIPT),
DelimiterType::Small(SCRIPT),
DelimiterType::Small(TEXT),
DelimiterType::Large(1),
DelimiterType::Large(2),
DelimiterType::Large(3),
DelimiterType::Large(4),
DelimiterType::Stack,
];