diff --git a/Cargo.lock b/Cargo.lock index b12e0a6..bbfcbb1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -435,7 +435,7 @@ name = "mark" version = "0.1.0" dependencies = [ "comrak", - "regex", + "nom", ] [[package]] @@ -454,6 +454,15 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + [[package]] name = "num-conv" version = "0.2.2" @@ -658,18 +667,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "regex" -version = "1.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - [[package]] name = "regex-automata" version = "0.4.14" diff --git a/Cargo.toml b/Cargo.toml index 1b0a471..e9d9103 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ mark = { path = "mark" } [profile.release] strip = true -lto = true +panic = "abort" +lto = "fat" codegen-units = 1 diff --git a/mark/Cargo.toml b/mark/Cargo.toml index e3d4c12..c571713 100644 --- a/mark/Cargo.toml +++ b/mark/Cargo.toml @@ -6,4 +6,4 @@ edition = "2024" [dependencies] comrak = "0.52.0" -regex = "1.12.4" +nom = "8" diff --git a/mark/src/image.rs b/mark/src/image.rs new file mode 100644 index 0000000..e2c804a --- /dev/null +++ b/mark/src/image.rs @@ -0,0 +1,135 @@ +use comrak::html::{ChildRendering, Context, dangerous_url}; +use comrak::nodes::NodeLink; +use nom::branch::alt; +use nom::bytes::tag; + +use nom::Parser; +use nom::character::complete::digit1; +use nom::combinator::{eof, map_res, opt}; +use std::fmt; +use std::fmt::{Display, Write}; + +enum ImgDimension { + Px(u32), + Percent(u32), +} + +impl Display for ImgDimension { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Px(n) => write!(f, "{n}px"), + Self::Percent(n) => write!(f, "{n}%"), + } + } +} + +struct SithImg<'a> { + url: &'a str, + width: Option, + height: Option, +} + +struct ParseDimensionError; + +/// Given a querystring that may contain image dimensions, +/// parse it and return the result +/// +/// ## Note +/// +/// If the dimension string is valid, it will always contain the image width. +/// However, the height is optional. +fn parse_dimensions(s: &str) -> Result<(ImgDimension, Option), ParseDimensionError> { + fn parse_dim(dim: &str) -> Result<(&str, ImgDimension), ParseDimensionError> { + let parsed = ( + map_res(digit1::<_, (_, _)>, str::parse), + opt(alt((eof, tag("%"), tag("px")))), + ) + .parse(dim); + let Ok((remaining, (val, unit))) = parsed else { + return Err(ParseDimensionError); + }; + match unit { + Some("%") => Ok((remaining, ImgDimension::Percent(val))), + None | Some("px") | Some("") => Ok((remaining, ImgDimension::Px(val))), + _ => Err(ParseDimensionError), + } + } + let (remaining, width) = parse_dim(s)?; + let height = if let Some(stripped) = remaining.strip_prefix("x") { + Some(parse_dim(stripped)?.1) + } else { + None + }; + + Ok((width, height)) +} + +impl<'a> From<&'a str> for SithImg<'a> { + fn from(s: &'a str) -> Self { + // if the url contained dimension instructions, remove the query part + // else, leave the url untouched + if let Some((url, query)) = s.rsplit_once('?') + && let Ok((width, height)) = parse_dimensions(query) + { + Self { + url, + width: Some(width), + height, + } + } else { + Self { + url: s, + width: None, + height: None, + } + } + } +} + +/// Render an image, with eventual size modifiers. +/// +/// This is basically a copy-paste of the comrack `render_image` function, +/// with some code added +pub(crate) fn render_image( + context: &mut Context, + entering: bool, + nl: &NodeLink, +) -> Result { + if entering { + let img_data = SithImg::from(nl.url.as_str()); + if context.options.render.figure_with_caption { + context.write_str("
")?; + } + context.write_str("\"")?;")?; + if context.options.render.figure_with_caption { + if !nl.title.is_empty() { + context.write_str("
")?; + context.escape(&nl.title)?; + context.write_str("
")?; + } + context.write_str("
")?; + } + } + + Ok(ChildRendering::HTML) +} diff --git a/mark/src/lib.rs b/mark/src/lib.rs index ec052fc..ad52d08 100644 --- a/mark/src/lib.rs +++ b/mark/src/lib.rs @@ -1,14 +1,9 @@ -use comrak::html::{ChildRendering, Context, dangerous_url}; -use comrak::nodes::{NodeLink, NodeValue}; +mod image; + +use crate::image::render_image; +use comrak::nodes::NodeValue; use comrak::options::{Extension, Parse, Render}; use comrak::{Arena, Options, create_formatter, parse_document}; -use regex::Regex; -use std::fmt; -use std::fmt::Write; -use std::sync::LazyLock; - -const DIMENSION_PATTERN: &str = r"^(?P\d+(%|px)?)(x(?P\d+(%|px)?))?$"; -static DIMENSION_RE: LazyLock = LazyLock::new(|| Regex::new(DIMENSION_PATTERN).unwrap()); #[inline(always)] fn options<'a>() -> Options<'a> { @@ -22,8 +17,6 @@ fn options<'a>() -> Options<'a> { footnotes: true, description_lists: true, multiline_block_quotes: true, - math_dollars: true, - math_code: true, shortcodes: true, underline: true, subscript: true, @@ -37,71 +30,12 @@ fn options<'a>() -> Options<'a> { ..Default::default() }, render: Render { + escape: true, ..Default::default() }, } } -/// Render an image, with eventual size modifiers. -/// -/// This is basically a copy-paste of the comrack `render_image` function, -/// with some code added -fn render_image( - context: &mut Context, - entering: bool, - nl: &NodeLink, -) -> Result { - if entering { - if context.options.render.figure_with_caption { - context.write_str("
")?; - } - context.write_str("\"")?;")?; - if context.options.render.figure_with_caption { - if !nl.title.is_empty() { - context.write_str("
")?; - context.escape(&nl.title)?; - context.write_str("
")?; - } - context.write_str("
")?; - } - } - - Ok(ChildRendering::HTML) -} - create_formatter!(CustomFormatter, { NodeValue::Image(ref nl) => |context, entering| { return render_image(context, entering, nl); @@ -122,7 +56,8 @@ pub fn markdown(s: &str) -> String { } } - let mut html = String::new(); + // html result will be at least as long as md input, so let's allocate that + let mut html = String::with_capacity(s.len()); CustomFormatter::format_document(root, &options, &mut html).unwrap(); html }