use core::fmt::{self, Write as _};
use crate::ParseError;
use crate::namespace::KeyMap;
use crate::types::ParseErrorKind;
#[cfg(feature = "wasm")]
use crate::web_context::WebContext;
use bon::bon;
use phf::phf_map;
#[cfg(feature = "wasm")]
use wasm_bindgen::UnwrapThrowExt as _;
#[cfg(feature = "wasm")]
use web_sys;
use crate::mathml_tree::MathNode;
use crate::options::Options;
use crate::svg_geometry::PATH_MAP;
use crate::tree::{DocumentFragment, VirtualNode};
use crate::types::ClassList;
use crate::types::{CssProperty, CssStyle};
use crate::unicode::script_from_codepoint;
use crate::units::make_em;
use crate::utils::escape_into;
const EMPTY_CLASS_LIST: ClassList = ClassList::Empty;
#[derive(Debug, Clone, PartialEq)]
pub struct Span<T> {
pub children: Vec<T>,
pub attributes: KeyMap<String, String>,
pub classes: ClassList,
pub height: f64,
pub depth: f64,
pub width: Option<f64>,
pub max_font_size: f64,
pub style: CssStyle,
pub is_middle: Option<(String, Options)>,
pub italic: Option<f64>,
}
#[bon]
impl<T> Span<T> {
#[builder]
#[expect(clippy::option_option)]
pub fn new(
#[builder(finish_fn)]
options: Option<&Options>,
children: Vec<T>,
attributes: Option<KeyMap<String, String>>,
classes: Option<ClassList>,
height: Option<f64>,
depth: Option<f64>,
width: Option<Option<f64>>,
max_font_size: Option<f64>,
style: Option<CssStyle>,
is_middle: Option<(String, Options)>,
) -> Self {
let mut span = Self {
children,
attributes: attributes.unwrap_or_default(),
classes: classes.unwrap_or_default(),
height: height.unwrap_or_default(),
depth: depth.unwrap_or_default(),
width: width.unwrap_or(None),
max_font_size: max_font_size.unwrap_or_default(),
style: style.unwrap_or_default(),
is_middle,
italic: None,
};
if let Some(options) = options {
init_node(&mut span.classes, &mut span.style, options);
}
span
}
pub(crate) fn from_parts(
children: Vec<T>,
classes: ClassList,
style: Option<CssStyle>,
options: Option<&Options>,
) -> Self {
let mut span = Self {
children,
attributes: KeyMap::default(),
classes,
height: 0.0,
depth: 0.0,
width: None,
max_font_size: 0.0,
style: style.unwrap_or_default(),
is_middle: None,
italic: None,
};
if let Some(options) = options {
init_node(&mut span.classes, &mut span.style, options);
}
span
}
}
#[derive(Debug, Clone)]
pub struct Anchor {
pub children: Vec<HtmlDomNode>,
pub attributes: KeyMap<String, String>,
pub classes: ClassList,
pub height: f64,
pub depth: f64,
pub max_font_size: f64,
pub style: CssStyle,
}
impl From<Anchor> for HtmlDomNode {
fn from(anchor: Anchor) -> Self {
Self::Anchor(anchor)
}
}
#[bon]
impl Anchor {
#[builder]
pub fn new(
#[builder(finish_fn)]
options: Option<&Options>,
children: Option<Vec<HtmlDomNode>>,
attributes: Option<KeyMap<String, String>>,
classes: Option<ClassList>,
height: Option<f64>,
depth: Option<f64>,
max_font_size: Option<f64>,
style: Option<CssStyle>,
) -> Self {
let mut anchor = Self {
children: children.unwrap_or_default(),
attributes: attributes.unwrap_or_default(),
classes: classes.unwrap_or_default(),
height: height.unwrap_or_default(),
depth: depth.unwrap_or_default(),
max_font_size: max_font_size.unwrap_or_default(),
style: style.unwrap_or_default(),
};
if let Some(options) = options {
init_node(&mut anchor.classes, &mut anchor.style, options);
}
anchor
}
}
impl Anchor {
#[must_use]
pub const fn new(
children: Vec<HtmlDomNode>,
attributes: KeyMap<String, String>,
classes: ClassList,
height: f64,
depth: f64,
max_font_size: f64,
style: CssStyle,
) -> Self {
Self {
children,
attributes,
classes,
height,
depth,
max_font_size,
style,
}
}
}
#[derive(Debug, Clone)]
pub struct Img {
pub src: String,
pub alt: String,
pub classes: ClassList,
pub height: f64,
pub depth: f64,
pub max_font_size: f64,
pub style: CssStyle,
}
impl Img {
#[must_use]
pub const fn new(
src: String,
alt: String,
height: f64,
depth: f64,
max_font_size: f64,
style: CssStyle,
) -> Self {
Self {
src,
alt,
classes: ClassList::Static("mord"),
height,
depth,
max_font_size,
style,
}
}
}
#[derive(Debug, Clone)]
pub struct SymbolNode {
pub text: String,
pub height: f64,
pub depth: f64,
pub italic: f64,
pub skew: f64,
pub width: f64,
pub max_font_size: f64,
pub classes: ClassList,
pub style: CssStyle,
}
impl From<SymbolNode> for HtmlDomNode {
fn from(symbol: SymbolNode) -> Self {
Self::Symbol(symbol)
}
}
const I_COMBINATIONS: phf::Map<&str, &str> = phf_map! {
"\u{ee}" => "\u{0131}\u{0302}",
"\u{ef}" => "\u{0131}\u{0308}",
"\u{ed}" => "\u{0131}\u{0301}",
"\u{ec}" => "\u{0131}\u{0300}",
};
#[bon]
impl SymbolNode {
#[builder]
pub fn new(
text: &str,
height: Option<f64>,
depth: Option<f64>,
italic: Option<f64>,
skew: Option<f64>,
width: Option<f64>,
max_font_size: Option<f64>,
classes: Option<ClassList>,
style: Option<CssStyle>,
) -> Self {
let mut classes = classes.unwrap_or_default();
if let Some(first_ch) = text.chars().next()
&& let Some(script) = script_from_codepoint(first_ch as u32)
{
classes.push(format!("{script}_fallback"));
}
let text = I_COMBINATIONS
.get(text)
.map_or_else(|| text.to_owned(), ToString::to_string);
Self {
text,
height: height.unwrap_or_default(),
depth: depth.unwrap_or_default(),
italic: italic.unwrap_or_default(),
skew: skew.unwrap_or_default(),
width: width.unwrap_or_default(),
max_font_size: max_font_size.unwrap_or_default(),
classes,
style: style.unwrap_or_default(),
}
}
}
pub type DomSpan = Span<HtmlDomNode>;
#[derive(Debug, Clone)]
pub enum HtmlDomNode {
DomSpan(Span<HtmlDomNode>),
Anchor(Anchor),
Img(Img),
Symbol(SymbolNode),
SvgNode(SvgNode),
MathML(MathNode),
Fragment(HtmlDomFragment),
}
impl From<Span<Self>> for HtmlDomNode {
fn from(span: Span<Self>) -> Self {
Self::DomSpan(span)
}
}
#[derive(Debug, Clone)]
pub enum SvgChildNode {
Path(PathNode),
Line(LineNode),
}
impl SvgChildNode {
pub fn to_markup(&self) -> Result<String, ParseError> {
match self {
Self::Path(path_node) => path_node.to_markup(),
Self::Line(line_node) => line_node.to_markup(),
}
}
#[cfg(feature = "wasm")]
#[must_use]
pub fn to_node(&self, ctx: &WebContext) -> web_sys::Node {
match self {
Self::Path(path_node) => path_node.to_node(ctx),
Self::Line(line_node) => line_node.to_node(ctx),
}
}
}
pub type HtmlDomFragment = DocumentFragment<HtmlDomNode>;
impl From<HtmlDomFragment> for HtmlDomNode {
fn from(fragment: HtmlDomFragment) -> Self {
Self::Fragment(fragment)
}
}
#[derive(Debug, Clone)]
pub struct SvgNode {
pub children: Vec<SvgChildNode>,
pub attributes: KeyMap<String, String>,
}
#[bon]
impl SvgNode {
#[builder]
pub fn new(
children: Vec<SvgChildNode>,
attributes: Option<KeyMap<String, String>>,
) -> Self {
Self {
children,
attributes: attributes.unwrap_or_default(),
}
}
}
#[must_use]
pub fn create_class(classes: &ClassList) -> String {
let mut result = String::new();
let mut first = true;
for class in classes {
if class.is_empty() {
continue;
}
if first {
first = false;
} else {
result.push(' ');
}
result.push_str(class);
}
result
}
#[inline]
fn init_node(classes: &mut ClassList, style: &mut CssStyle, options: &Options) {
if options.style.is_tight() {
classes.push("mtight");
}
if let Some(color) = options.get_color() {
style.insert(CssProperty::Color, color);
}
}
#[cfg(feature = "wasm")]
#[must_use]
pub fn to_node(node: &HtmlDomNode, ctx: &WebContext) -> web_sys::Node {
node.to_node(ctx)
}
pub fn to_markup(node: &HtmlDomNode) -> Result<String, ParseError> {
node.to_markup()
}
fn map_fmt(result: fmt::Result) -> Result<(), ParseError> {
result.map_err(ParseError::from)
}
fn write_node_class<W: fmt::Write>(writer: &mut W, classes: &ClassList) -> fmt::Result {
let mut iter = classes.into_iter();
if let Some(first) = iter.next() {
writer.write_str(" class=\"")?;
escape_into(writer, first)?;
for class in iter {
writer.write_char(' ')?;
escape_into(writer, class)?;
}
writer.write_char('"')?;
}
Ok(())
}
fn write_node_style<W: fmt::Write>(writer: &mut W, style: &CssStyle) -> fmt::Result {
if style.is_empty() {
return Ok(());
}
writer.write_str(" style=\"")?;
style.write_to(writer)?;
writer.write_char('"')
}
#[cfg(feature = "wasm")]
fn class_to_node(element: &web_sys::Element, classes: &ClassList) {
if !classes.is_empty() {
let class_attr = create_class(classes);
set_attribute(element, "class", &class_attr);
}
}
#[cfg(feature = "wasm")]
fn style_to_node(element: &web_sys::Element, style: &CssStyle) {
if !style.is_empty() {
let mut styles = String::new();
let _ = write!(styles, "{style}");
set_attribute(element, "style", &styles);
}
}
#[cfg(feature = "wasm")]
fn set_attribute(element: &web_sys::Element, name: &str, value: &str) {
element.set_attribute(name, value).unwrap_throw();
}
#[cfg(feature = "wasm")]
fn append_child(parent: &web_sys::Element, child: &web_sys::Node) {
parent.append_child(child).unwrap_throw();
}
#[cfg(feature = "wasm")]
fn create_element(ctx: &WebContext, name: &str) -> web_sys::Element {
ctx.document.create_element(name).unwrap_throw()
}
#[cfg(feature = "wasm")]
fn create_element_ns(ctx: &WebContext, ns: &str, name: &str) -> web_sys::Element {
ctx.document
.create_element_ns(Some(ns), name)
.unwrap_throw()
}
impl<T: VirtualNode> VirtualNode for Span<T> {
fn write_markup(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), ParseError> {
map_fmt(fmt.write_str("<span"))?;
map_fmt(write_node_class(fmt, &self.classes))?;
map_fmt(write_node_style(fmt, &self.style))?;
node_attributes_to_markup(fmt, &self.attributes)?;
map_fmt(fmt.write_char('>'))?;
for child in &self.children {
child.write_markup(fmt)?;
}
map_fmt(fmt.write_str("</span>"))?;
Ok(())
}
#[cfg(feature = "wasm")]
fn to_node(&self, ctx: &WebContext) -> web_sys::Node {
use wasm_bindgen::JsCast as _;
let element = create_element(ctx, "span");
class_to_node(&element, &self.classes);
style_to_node(&element, &self.style);
node_attributes_to_node(&element, &self.attributes);
for child in &self.children {
let child_node = child.to_node(ctx);
append_child(&element, &child_node);
}
element.unchecked_into::<web_sys::Node>()
}
}
impl VirtualNode for Anchor {
fn write_markup(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), ParseError> {
map_fmt(fmt.write_str("<a"))?;
map_fmt(write_node_class(fmt, &self.classes))?;
map_fmt(write_node_style(fmt, &self.style))?;
node_attributes_to_markup(fmt, &self.attributes)?;
map_fmt(fmt.write_char('>'))?;
for child in &self.children {
child.write_markup(fmt)?;
}
map_fmt(fmt.write_str("</a>"))?;
Ok(())
}
#[cfg(feature = "wasm")]
fn to_node(&self, ctx: &WebContext) -> web_sys::Node {
use wasm_bindgen::JsCast as _;
let element = create_element(ctx, "a");
class_to_node(&element, &self.classes);
style_to_node(&element, &self.style);
node_attributes_to_node(&element, &self.attributes);
for child in &self.children {
let child_node = child.to_node(ctx);
append_child(&element, &child_node);
}
element.unchecked_into::<web_sys::Node>()
}
}
impl VirtualNode for Img {
fn write_markup(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), ParseError> {
map_fmt(fmt.write_str("<img src=\""))?;
map_fmt(escape_into(fmt, &self.src))?;
map_fmt(fmt.write_str("\" alt=\""))?;
map_fmt(escape_into(fmt, &self.alt))?;
map_fmt(fmt.write_char('"'))?;
map_fmt(write_node_class(fmt, &self.classes))?;
map_fmt(write_node_style(fmt, &self.style))?;
map_fmt(fmt.write_str("/"))?;
map_fmt(fmt.write_char('>'))?;
Ok(())
}
#[cfg(feature = "wasm")]
fn to_node(&self, ctx: &WebContext) -> web_sys::Node {
use wasm_bindgen::JsCast as _;
let element = create_element(ctx, "img");
set_attribute(&element, "src", &self.src);
set_attribute(&element, "alt", &self.alt);
class_to_node(&element, &self.classes);
style_to_node(&element, &self.style);
element.unchecked_into::<web_sys::Node>()
}
}
fn write_symbol_style<W: fmt::Write>(writer: &mut W, italic: f64, style: &CssStyle) -> fmt::Result {
if italic <= 0.0 && style.is_empty() {
return Ok(());
}
writer.write_str(" style=\"")?;
if italic > 0.0 {
writer.write_str("margin-right:")?;
writer.write_str(&make_em(italic))?;
writer.write_char(';')?;
}
style.write_to(writer)?;
writer.write_char('"')
}
#[cfg(feature = "wasm")]
fn symbol_node_style_str(italic: f64, style: &CssStyle) -> String {
let mut styles = String::new();
if italic > 0.0 {
let _ = write!(styles, "margin-right:{};", make_em(italic));
}
let _ = write!(styles, "{style}");
let mut escaped = String::with_capacity(styles.len() * 9 / 8);
let _ = escape_into(&mut escaped, &styles);
escaped
}
impl VirtualNode for SymbolNode {
fn write_markup(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), ParseError> {
let needs_span = self.italic > 0.0 || !self.classes.is_empty() || !self.style.is_empty();
if needs_span {
map_fmt(fmt.write_str("<span"))?;
map_fmt(write_node_class(fmt, &self.classes))?;
map_fmt(write_symbol_style(fmt, self.italic, &self.style))?;
map_fmt(fmt.write_char('>'))?;
map_fmt(escape_into(fmt, &self.text))?;
map_fmt(fmt.write_str("</span>"))?;
} else {
map_fmt(escape_into(fmt, &self.text))?;
}
Ok(())
}
#[cfg(feature = "wasm")]
fn to_node(&self, ctx: &WebContext) -> web_sys::Node {
use wasm_bindgen::JsCast as _;
let needs_span = self.italic > 0.0 || !self.classes.is_empty() || !self.style.is_empty();
if needs_span {
let element = create_element(ctx, "span");
if !self.classes.is_empty() {
let class_attr = create_class(&self.classes);
set_attribute(&element, "class", &class_attr);
}
let styles = symbol_node_style_str(self.italic, &self.style);
if !styles.is_empty() {
set_attribute(&element, "style", &styles);
}
let text_node = ctx.document.create_text_node(&self.text);
append_child(&element, &text_node);
element.unchecked_into::<web_sys::Node>()
} else {
ctx.document
.create_text_node(&self.text)
.unchecked_into::<web_sys::Node>()
}
}
}
impl VirtualNode for SvgNode {
fn write_markup(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), ParseError> {
map_fmt(fmt.write_str("<svg xmlns=\"http://www.w3.org/2000/svg\""))?;
node_attributes_to_markup(fmt, &self.attributes)?;
map_fmt(fmt.write_char('>'))?;
for child in &self.children {
match child {
SvgChildNode::Path(path) => path.write_markup(fmt)?,
SvgChildNode::Line(line) => line.write_markup(fmt)?,
}
}
map_fmt(fmt.write_str("</svg>"))?;
Ok(())
}
#[cfg(feature = "wasm")]
fn to_node(&self, ctx: &WebContext) -> web_sys::Node {
use wasm_bindgen::JsCast as _;
let element = create_element_ns(ctx, "http://www.w3.org/2000/svg", "svg");
node_attributes_to_node(&element, &self.attributes);
for child in &self.children {
let child_node = child.to_node(ctx);
append_child(&element, &child_node);
}
element.unchecked_into::<web_sys::Node>()
}
}
impl VirtualNode for HtmlDomNode {
fn write_markup(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), ParseError> {
match self {
Self::DomSpan(span) => span.write_markup(fmt),
Self::Anchor(anchor) => anchor.write_markup(fmt),
Self::Img(img) => img.write_markup(fmt),
Self::Symbol(symbol) => symbol.write_markup(fmt),
Self::SvgNode(svg_node) => svg_node.write_markup(fmt),
Self::MathML(math_node) => math_node.write_markup(fmt),
Self::Fragment(fragment) => fragment.write_markup(fmt),
}
}
#[cfg(feature = "wasm")]
fn to_node(&self, ctx: &WebContext) -> web_sys::Node {
match self {
Self::DomSpan(span) => span.to_node(ctx),
Self::Anchor(anchor) => anchor.to_node(ctx),
Self::Img(img) => img.to_node(ctx),
Self::Symbol(symbol) => symbol.to_node(ctx),
Self::SvgNode(svg_node) => svg_node.to_node(ctx),
Self::MathML(math_node) => math_node.to_node(ctx),
Self::Fragment(fragment) => fragment.to_node(ctx),
}
}
}
impl HtmlDomNode {
#[must_use]
pub const fn classes(&self) -> &ClassList {
match self {
Self::DomSpan(span) => &span.classes,
Self::Anchor(anchor) => &anchor.classes,
Self::Img(img) => &img.classes,
Self::Symbol(symbol) => &symbol.classes,
Self::Fragment(fragment) => &fragment.classes,
Self::SvgNode(_) | Self::MathML { .. } => &EMPTY_CLASS_LIST,
}
}
pub const fn classes_mut(&mut self) -> Option<&mut ClassList> {
match self {
Self::DomSpan(span) => Some(&mut span.classes),
Self::Anchor(anchor) => Some(&mut anchor.classes),
Self::Img(img) => Some(&mut img.classes),
Self::Symbol(symbol) => Some(&mut symbol.classes),
Self::SvgNode(_) | Self::MathML { .. } => None,
Self::Fragment(fragment) => Some(&mut fragment.classes),
}
}
#[must_use]
pub const fn height(&self) -> f64 {
match self {
Self::DomSpan(span) => span.height,
Self::Anchor(anchor) => anchor.height,
Self::Img(img) => img.height,
Self::Symbol(symbol) => symbol.height,
Self::SvgNode(_) | Self::MathML { .. } => 0.0,
Self::Fragment(fragment) => fragment.height,
}
}
pub const fn height_mut(&mut self) -> Option<&mut f64> {
match self {
Self::DomSpan(span) => Some(&mut span.height),
Self::Anchor(anchor) => Some(&mut anchor.height),
Self::Img(img) => Some(&mut img.height),
Self::Symbol(symbol) => Some(&mut symbol.height),
Self::SvgNode(_) | Self::MathML { .. } => None,
Self::Fragment(fragment) => Some(&mut fragment.height),
}
}
#[must_use]
pub const fn depth(&self) -> f64 {
match self {
Self::DomSpan(span) => span.depth,
Self::Anchor(anchor) => anchor.depth,
Self::Img(img) => img.depth,
Self::Symbol(symbol) => symbol.depth,
Self::SvgNode(_) | Self::MathML { .. } => 0.0,
Self::Fragment(fragment) => fragment.depth,
}
}
pub const fn depth_mut(&mut self) -> Option<&mut f64> {
match self {
Self::DomSpan(span) => Some(&mut span.depth),
Self::Anchor(anchor) => Some(&mut anchor.depth),
Self::Img(img) => Some(&mut img.depth),
Self::Symbol(symbol) => Some(&mut symbol.depth),
Self::SvgNode(_) | Self::MathML { .. } => None,
Self::Fragment(fragment) => Some(&mut fragment.depth),
}
}
#[must_use]
pub const fn max_font_size(&self) -> f64 {
match self {
Self::DomSpan(span) => span.max_font_size,
Self::Anchor(anchor) => anchor.max_font_size,
Self::Img(img) => img.max_font_size,
Self::Symbol(symbol) => symbol.max_font_size,
Self::SvgNode(_) | Self::MathML { .. } => 0.0,
Self::Fragment(fragment) => fragment.max_font_size,
}
}
pub const fn max_font_size_mut(&mut self) -> Option<&mut f64> {
match self {
Self::DomSpan(span) => Some(&mut span.max_font_size),
Self::Anchor(anchor) => Some(&mut anchor.max_font_size),
Self::Img(img) => Some(&mut img.max_font_size),
Self::Symbol(symbol) => Some(&mut symbol.max_font_size),
Self::SvgNode(_) | Self::MathML { .. } => None,
Self::Fragment(fragment) => Some(&mut fragment.max_font_size),
}
}
#[must_use]
pub const fn width(&self) -> Option<f64> {
match self {
Self::DomSpan(span) => span.width,
Self::Anchor(_)
| Self::Img(_)
| Self::SvgNode(_)
| Self::MathML { .. }
| Self::Fragment(_) => None,
Self::Symbol(symbol) => Some(symbol.width),
}
}
#[must_use]
pub const fn style(&self) -> Option<&CssStyle> {
match self {
Self::DomSpan(span) => Some(&span.style),
Self::Anchor(anchor) => Some(&anchor.style),
Self::Img(img) => Some(&img.style),
Self::Symbol(symbol) => Some(&symbol.style),
Self::Fragment(fragment) => Some(&fragment.style),
Self::SvgNode(_) | Self::MathML { .. } => None,
}
}
pub const fn style_mut(&mut self) -> Option<&mut CssStyle> {
match self {
Self::DomSpan(span) => Some(&mut span.style),
Self::Anchor(anchor) => Some(&mut anchor.style),
Self::Img(img) => Some(&mut img.style),
Self::Symbol(symbol) => Some(&mut symbol.style),
Self::SvgNode(_) | Self::MathML { .. } => None,
Self::Fragment(fragment) => Some(&mut fragment.style),
}
}
#[must_use]
pub fn has_class(&self, class_name: &str) -> bool {
self.classes().contains(class_name)
}
#[must_use]
pub const fn attributes(&self) -> Option<&KeyMap<String, String>> {
match self {
Self::DomSpan(span) => Some(&span.attributes),
Self::Anchor(anchor) => Some(&anchor.attributes),
Self::Img(_) | Self::Symbol(_) | Self::Fragment(_) => None,
Self::SvgNode(svg_node) => Some(&svg_node.attributes),
Self::MathML(mathml) => Some(&mathml.attributes),
}
}
}
#[derive(Debug, Clone)]
pub struct PathNode {
pub path_name: String,
pub alternate: Option<String>,
}
impl VirtualNode for PathNode {
fn write_markup(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), ParseError> {
let path_data = self.alternate.as_ref().map_or_else(
|| {
PATH_MAP
.get(&self.path_name)
.map_or_else(String::new, |s| (*s).to_owned())
},
Clone::clone,
);
map_fmt(fmt.write_str("<path d=\""))?;
map_fmt(escape_into(fmt, &path_data))?;
map_fmt(fmt.write_str("\"/>"))?;
Ok(())
}
#[cfg(feature = "wasm")]
fn to_node(&self, ctx: &WebContext) -> web_sys::Node {
use wasm_bindgen::JsCast as _;
let element = create_element_ns(ctx, "http://www.w3.org/2000/svg", "path");
let path_data = self.alternate.as_ref().map_or_else(
|| {
PATH_MAP
.get(&self.path_name)
.map_or_else(String::new, |s| (*s).to_owned())
},
Clone::clone,
);
set_attribute(&element, "d", &path_data);
element.unchecked_into::<web_sys::Node>()
}
}
#[derive(Debug, Clone)]
pub struct LineNode {
pub attributes: KeyMap<String, String>,
}
fn node_attributes_to_markup<W: fmt::Write>(
writer: &mut W,
attributes: &KeyMap<String, String>,
) -> Result<(), ParseError> {
for (attr, value) in attributes {
if !attr.is_empty() {
if attr.contains(|c: char| {
c.is_whitespace() || "\"'>/=".contains(c) || ('\x00'..='\x1f').contains(&c)
}) {
return Err(ParseErrorKind::InvalidAttributeName { attr: attr.clone() }.into());
}
map_fmt(write!(writer, " {attr}=\""))?;
map_fmt(escape_into(writer, value))?;
map_fmt(writer.write_char('"'))?;
}
}
Ok(())
}
#[cfg(feature = "wasm")]
fn node_attributes_to_node(element: &web_sys::Element, attributes: &KeyMap<String, String>) {
for (attr, value) in attributes {
if !attr.is_empty() {
if attr.contains(|c: char| {
c.is_whitespace() || "\"'>/=".contains(c) || ('\x00'..='\x1f').contains(&c)
}) {
continue;
}
set_attribute(element, attr, value);
}
}
}
impl VirtualNode for LineNode {
fn write_markup(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), ParseError> {
map_fmt(fmt.write_str("<line"))?;
node_attributes_to_markup(fmt, &self.attributes)?;
map_fmt(fmt.write_str("/"))?;
map_fmt(fmt.write_char('>'))?;
Ok(())
}
#[cfg(feature = "wasm")]
fn to_node(&self, ctx: &WebContext) -> web_sys::Node {
use wasm_bindgen::JsCast as _;
let element = create_element_ns(ctx, "http://www.w3.org/2000/svg", "line");
node_attributes_to_node(&element, &self.attributes);
element.unchecked_into::<web_sys::Node>()
}
}