From 370aa5dac4f792718e2db1e569afeade9be31b37 Mon Sep 17 00:00:00 2001 From: Kayshen-X Date: Sat, 18 Jul 2026 13:10:51 +0800 Subject: [PATCH] feat(html): scaffold op-html importer crate with public API --- crates/op-html/Cargo.toml | 12 ++++++++ crates/op-html/src/lib.rs | 63 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 crates/op-html/Cargo.toml create mode 100644 crates/op-html/src/lib.rs diff --git a/crates/op-html/Cargo.toml b/crates/op-html/Cargo.toml new file mode 100644 index 000000000..d92df5905 --- /dev/null +++ b/crates/op-html/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "op-html" +version.workspace = true +edition = "2021" +description = "HTML/CSS-subset importer: converts HTML into editable PenNode trees" +license = "MIT" + +[dependencies] +html5ever = "0.27" +markup5ever_rcdom = "0.3" +base64 = "0.22" +jian-ops-schema = { path = "../../vendor/jian/crates/jian-ops-schema" } diff --git a/crates/op-html/src/lib.rs b/crates/op-html/src/lib.rs new file mode 100644 index 000000000..1a78e72f1 --- /dev/null +++ b/crates/op-html/src/lib.rs @@ -0,0 +1,63 @@ +//! HTML → PenNode importer (structured path, CSS-subset cascade). + +use jian_ops_schema::node::PenNode; + +pub struct HtmlImportOptions { + pub viewport_width: f64, + pub base_font_size: f64, + pub document_name: Option, +} + +impl Default for HtmlImportOptions { + fn default() -> Self { + Self { + viewport_width: 1440.0, + base_font_size: 16.0, + document_name: None, + } + } +} + +pub struct HtmlImportResult { + pub nodes: Vec, + pub warnings: Vec, +} + +pub fn import_html(source: &str, opts: &HtmlImportOptions) -> HtmlImportResult { + let _ = opts; + let mut warnings = Vec::new(); + if source.trim().is_empty() { + warnings.push("no importable content: input HTML is empty".to_string()); + return HtmlImportResult { + nodes: Vec::new(), + warnings, + }; + } + // Pipeline lands in later tasks; non-empty input is wired up in Task 11. + warnings.push("no importable content: importer pipeline not yet implemented".to_string()); + HtmlImportResult { + nodes: Vec::new(), + warnings, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn empty_input_yields_no_nodes_and_a_warning() { + let r = import_html("", &HtmlImportOptions::default()); + assert!(r.nodes.is_empty()); + assert_eq!(r.warnings.len(), 1); + assert!(r.warnings[0].contains("no importable content")); + } + + #[test] + fn options_default_values() { + let o = HtmlImportOptions::default(); + assert_eq!(o.viewport_width, 1440.0); + assert_eq!(o.base_font_size, 16.0); + assert!(o.document_name.is_none()); + } +}