From ebfdb1dafa430786db10dad2ef7d5467c1bdbed1 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Sun, 2 Jul 2023 19:59:52 +0200 Subject: Move everything into `crates/` directory --- docs/src/html.rs | 513 ------------------------------------------------------- 1 file changed, 513 deletions(-) delete mode 100644 docs/src/html.rs (limited to 'docs/src/html.rs') diff --git a/docs/src/html.rs b/docs/src/html.rs deleted file mode 100644 index 0d40f46b..00000000 --- a/docs/src/html.rs +++ /dev/null @@ -1,513 +0,0 @@ -use std::ops::Range; - -use comemo::Prehashed; -use pulldown_cmark as md; -use typed_arena::Arena; -use typst::diag::FileResult; -use typst::eval::Datetime; -use typst::file::FileId; -use typst::font::{Font, FontBook}; -use typst::geom::{Point, Size}; -use typst::syntax::Source; -use typst::util::Bytes; -use typst::World; -use yaml_front_matter::YamlFrontMatter; - -use super::*; - -/// HTML documentation. -#[derive(Serialize)] -#[serde(transparent)] -pub struct Html { - raw: String, - #[serde(skip)] - md: String, - #[serde(skip)] - description: Option, - #[serde(skip)] - outline: Vec, -} - -impl Html { - /// Create HTML from a raw string. - pub fn new(raw: String) -> Self { - Self { - md: String::new(), - raw, - description: None, - outline: vec![], - } - } - - /// Convert markdown to HTML. - #[track_caller] - pub fn markdown(resolver: &dyn Resolver, md: &str) -> Self { - Self::markdown_with_id_base(resolver, md, "") - } - - /// Convert markdown to HTML, preceding all fragment identifiers with the - /// `id_base`. - #[track_caller] - pub fn markdown_with_id_base( - resolver: &dyn Resolver, - md: &str, - id_base: &str, - ) -> Self { - let mut text = md; - let mut description = None; - let document = YamlFrontMatter::parse::(md); - if let Ok(document) = &document { - text = &document.content; - description = Some(document.metadata.description.clone()) - } - - let options = md::Options::ENABLE_TABLES | md::Options::ENABLE_HEADING_ATTRIBUTES; - - let ids = Arena::new(); - let mut handler = Handler::new(resolver, id_base.into(), &ids); - let iter = md::Parser::new_ext(text, options) - .filter_map(|mut event| handler.handle(&mut event).then_some(event)); - - let mut raw = String::new(); - md::html::push_html(&mut raw, iter); - raw.truncate(raw.trim_end().len()); - - Html { - md: text.into(), - raw, - description, - outline: handler.outline, - } - } - - /// The raw HTML. - pub fn as_str(&self) -> &str { - &self.raw - } - - /// The original Markdown, if any. - pub fn md(&self) -> &str { - &self.md - } - - /// The title of the HTML. - /// - /// Returns `None` if the HTML doesn't start with an `h1` tag. - pub fn title(&self) -> Option<&str> { - let mut s = Scanner::new(&self.raw); - s.eat_if("

").then(|| s.eat_until("

")) - } - - /// The outline of the HTML. - pub fn outline(&self) -> Vec { - self.outline.clone() - } - - /// The description from the front matter. - pub fn description(&self) -> Option { - self.description.clone() - } -} - -impl Debug for Html { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "Html({:?})", self.title().unwrap_or("..")) - } -} - -/// Front matter metadata. -#[derive(Deserialize)] -struct Metadata { - description: String, -} - -struct Handler<'a> { - resolver: &'a dyn Resolver, - lang: Option, - code: String, - outline: Vec, - id_base: String, - ids: &'a Arena, -} - -impl<'a> Handler<'a> { - fn new(resolver: &'a dyn Resolver, id_base: String, ids: &'a Arena) -> Self { - Self { - resolver, - lang: None, - code: String::new(), - outline: vec![], - id_base, - ids, - } - } - - fn handle(&mut self, event: &mut md::Event<'a>) -> bool { - match event { - // Rewrite Markdown images. - md::Event::Start(md::Tag::Image(_, path, _)) => { - *path = self.handle_image(path).into(); - } - - // Rewrite HTML images. - md::Event::Html(html) if html.starts_with(" { - let range = html_attr_range(html, "src").unwrap(); - let path = &html[range.clone()]; - let mut buf = html.to_string(); - buf.replace_range(range, &self.handle_image(path)); - *html = buf.into(); - } - - // Register HTML headings for the outline. - md::Event::Start(md::Tag::Heading(level, Some(id), _)) => { - self.handle_heading(id, level); - } - - // Also handle heading closings. - md::Event::End(md::Tag::Heading(level, Some(_), _)) => { - if *level > md::HeadingLevel::H1 && !self.id_base.is_empty() { - nest_heading(level); - } - } - - // Rewrite contributor sections. - md::Event::Html(html) if html.starts_with(" { - let from = html_attr(html, "from").unwrap(); - let to = html_attr(html, "to").unwrap(); - let Some(output) = contributors(self.resolver, from, to) else { return false }; - *html = output.raw.into(); - } - - // Rewrite links. - md::Event::Start(md::Tag::Link(ty, dest, _)) => { - assert!( - matches!(ty, md::LinkType::Inline | md::LinkType::Reference), - "unsupported link type: {ty:?}", - ); - - *dest = self - .handle_link(dest) - .unwrap_or_else(|| panic!("invalid link: {dest}")) - .into(); - } - - // Inline raw. - md::Event::Code(code) => { - let mut chars = code.chars(); - let parser = match (chars.next(), chars.next_back()) { - (Some('['), Some(']')) => typst::syntax::parse, - (Some('{'), Some('}')) => typst::syntax::parse_code, - _ => return true, - }; - - let root = parser(&code[1..code.len() - 1]); - let html = typst::ide::highlight_html(&root); - *event = md::Event::Html(html.into()); - } - - // Code blocks. - md::Event::Start(md::Tag::CodeBlock(md::CodeBlockKind::Fenced(lang))) => { - self.lang = Some(lang.as_ref().into()); - self.code = String::new(); - return false; - } - md::Event::End(md::Tag::CodeBlock(md::CodeBlockKind::Fenced(_))) => { - let Some(lang) = self.lang.take() else { return false }; - let html = code_block(self.resolver, &lang, &self.code); - *event = md::Event::Html(html.raw.into()); - } - - // Example with preview. - md::Event::Text(text) => { - if self.lang.is_some() { - self.code.push_str(text); - return false; - } - } - - _ => {} - } - - true - } - - fn handle_image(&self, link: &str) -> String { - if let Some(file) = FILES.get_file(link) { - self.resolver.image(link, file.contents()) - } else if let Some(url) = self.resolver.link(link) { - url - } else { - panic!("missing image: {link}") - } - } - - fn handle_heading(&mut self, id: &mut &'a str, level: &mut md::HeadingLevel) { - if *level == md::HeadingLevel::H1 { - return; - } - - // Special case for things like "v0.3.0". - let name = if id.starts_with('v') && id.contains('.') { - id.to_string() - } else { - id.to_title_case() - }; - - let mut children = &mut self.outline; - let mut depth = *level as usize; - while depth > 2 { - if !children.is_empty() { - children = &mut children.last_mut().unwrap().children; - } - depth -= 1; - } - - // Put base before id. - if !self.id_base.is_empty() { - nest_heading(level); - *id = self.ids.alloc(format!("{}-{id}", self.id_base)).as_str(); - } - - children.push(OutlineItem { id: id.to_string(), name, children: vec![] }); - } - - fn handle_link(&self, link: &str) -> Option { - if link.starts_with('#') || link.starts_with("http") { - return Some(link.into()); - } - - if !link.starts_with('$') { - return self.resolver.link(link); - } - - let root = link.split('/').next()?; - let rest = &link[root.len()..].trim_matches('/'); - let base = match root { - "$tutorial" => "/docs/tutorial/", - "$reference" => "/docs/reference/", - "$category" => "/docs/reference/", - "$syntax" => "/docs/reference/syntax/", - "$styling" => "/docs/reference/styling/", - "$scripting" => "/docs/reference/scripting/", - "$types" => "/docs/reference/types/", - "$type" => "/docs/reference/types/", - "$func" => "/docs/reference/", - "$guides" => "/docs/guides/", - "$packages" => "/docs/packages/", - "$changelog" => "/docs/changelog/", - "$community" => "/docs/community/", - _ => panic!("unknown link root: {root}"), - }; - - let mut route = base.to_string(); - if root == "$type" && rest.contains('.') { - let mut parts = rest.split('.'); - let ty = parts.next()?; - let method = parts.next()?; - route.push_str(ty); - route.push_str("/#methods-"); - route.push_str(method); - } else if root == "$func" { - let mut parts = rest.split('.').peekable(); - let first = parts.peek().copied(); - let mut focus = &LIBRARY.global; - while let Some(m) = first.and_then(|name| module(focus, name).ok()) { - focus = m; - parts.next(); - } - - let name = parts.next()?; - - let value = focus.get(name).ok()?; - let Value::Func(func) = value else { return None }; - let info = func.info()?; - route.push_str(info.category); - route.push('/'); - - if let Some(group) = GROUPS - .iter() - .filter(|_| first == Some("math")) - .find(|group| group.functions.iter().any(|func| func == info.name)) - { - route.push_str(&group.name); - route.push_str("/#"); - route.push_str(info.name); - if let Some(param) = parts.next() { - route.push_str("-parameters-"); - route.push_str(param); - } - } else { - route.push_str(name); - route.push('/'); - if let Some(next) = parts.next() { - if info.params.iter().any(|param| param.name == next) { - route.push_str("#parameters-"); - route.push_str(next); - } else if info.scope.iter().any(|(name, _)| name == next) { - route.push('#'); - route.push_str(info.name); - route.push('-'); - route.push_str(next); - } else { - return None; - } - } - } - } else { - route.push_str(rest); - } - - if !route.contains('#') && !route.ends_with('/') { - route.push('/'); - } - - Some(route) - } -} - -/// Render a code block to HTML. -fn code_block(resolver: &dyn Resolver, lang: &str, text: &str) -> Html { - let mut display = String::new(); - let mut compile = String::new(); - for line in text.lines() { - if let Some(suffix) = line.strip_prefix(">>>") { - compile.push_str(suffix); - compile.push('\n'); - } else if let Some(suffix) = line.strip_prefix("<<< ") { - display.push_str(suffix); - display.push('\n'); - } else { - display.push_str(line); - display.push('\n'); - compile.push_str(line); - compile.push('\n'); - } - } - - let mut parts = lang.split(':'); - let lang = parts.next().unwrap_or(lang); - - let mut zoom: Option<[Abs; 4]> = None; - let mut single = false; - if let Some(args) = parts.next() { - single = true; - if !args.contains("single") { - zoom = args - .split(',') - .take(4) - .map(|s| Abs::pt(s.parse().unwrap())) - .collect::>() - .try_into() - .ok(); - } - } - - if lang.is_empty() { - let mut buf = String::from("
");
-        md::escape::escape_html(&mut buf, &display).unwrap();
-        buf.push_str("
"); - return Html::new(buf); - } else if !matches!(lang, "example" | "typ") { - let set = &*typst_library::text::SYNTAXES; - let buf = syntect::html::highlighted_html_for_string( - &display, - set, - set.find_syntax_by_token(lang) - .unwrap_or_else(|| panic!("unsupported highlighting language: {lang}")), - &typst_library::text::THEME, - ) - .expect("failed to highlight code"); - return Html::new(buf); - } - - let root = typst::syntax::parse(&display); - let highlighted = Html::new(typst::ide::highlight_html(&root)); - if lang == "typ" { - return Html::new(format!("
{}
", highlighted.as_str())); - } - - let id = FileId::new(None, Path::new("/main.typ")); - let source = Source::new(id, compile); - let world = DocWorld(source); - let mut frames = match typst::compile(&world) { - Ok(doc) => doc.pages, - Err(err) => { - let msg = &err[0].message; - panic!("while trying to compile:\n{text}:\n\nerror: {msg}"); - } - }; - - if let Some([x, y, w, h]) = zoom { - frames[0].translate(Point::new(-x, -y)); - *frames[0].size_mut() = Size::new(w, h); - } - - if single { - frames.truncate(1); - } - - resolver.example(highlighted, &frames) -} - -/// Extract an attribute value from an HTML element. -fn html_attr<'a>(html: &'a str, attr: &str) -> Option<&'a str> { - html.get(html_attr_range(html, attr)?) -} - -/// Extract the range of the attribute value of an HTML element. -fn html_attr_range(html: &str, attr: &str) -> Option> { - let needle = format!("{attr}=\""); - let offset = html.find(&needle)? + needle.len(); - let len = html[offset..].find('"')?; - Some(offset..offset + len) -} - -/// Increase the nesting level of a Markdown heading. -fn nest_heading(level: &mut md::HeadingLevel) { - *level = match &level { - md::HeadingLevel::H1 => md::HeadingLevel::H2, - md::HeadingLevel::H2 => md::HeadingLevel::H3, - md::HeadingLevel::H3 => md::HeadingLevel::H4, - md::HeadingLevel::H4 => md::HeadingLevel::H5, - md::HeadingLevel::H5 => md::HeadingLevel::H6, - v => **v, - }; -} - -/// A world for example compilations. -struct DocWorld(Source); - -impl World for DocWorld { - fn library(&self) -> &Prehashed { - &LIBRARY - } - - fn book(&self) -> &Prehashed { - &FONTS.0 - } - - fn main(&self) -> Source { - self.0.clone() - } - - fn source(&self, _: FileId) -> FileResult { - Ok(self.0.clone()) - } - - fn file(&self, id: FileId) -> FileResult { - assert!(id.package().is_none()); - Ok(FILES - .get_file(id.path().strip_prefix("/").unwrap()) - .unwrap_or_else(|| panic!("failed to load {:?}", id.path().display())) - .contents() - .into()) - } - - fn font(&self, index: usize) -> Option { - Some(FONTS.1[index].clone()) - } - - fn today(&self, _: Option) -> Option { - Some(Datetime::from_ymd(1970, 1, 1).unwrap()) - } -} -- cgit v1.2.3