Skip to main content

oxihuman_export/
html_export.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3
4//! Export mesh stats as a simple HTML page.
5
6#![allow(dead_code)]
7
8/// An HTML export document.
9#[allow(dead_code)]
10#[derive(Debug, Clone, Default)]
11pub struct HtmlExport {
12    pub title: String,
13    pub sections: Vec<HtmlSection>,
14}
15
16/// A named section containing HTML table rows.
17#[allow(dead_code)]
18#[derive(Debug, Clone)]
19pub struct HtmlSection {
20    pub heading: String,
21    pub rows: Vec<[String; 2]>,
22}
23
24/// Create a new HTML export document.
25#[allow(dead_code)]
26pub fn new_html_export(title: &str) -> HtmlExport {
27    HtmlExport {
28        title: title.to_string(),
29        sections: Vec::new(),
30    }
31}
32
33/// Add a section with a heading.
34#[allow(dead_code)]
35pub fn add_html_section(doc: &mut HtmlExport, heading: &str) {
36    doc.sections.push(HtmlSection {
37        heading: heading.to_string(),
38        rows: Vec::new(),
39    });
40}
41
42/// Add a key-value row to the last section.
43#[allow(dead_code)]
44pub fn add_html_row(doc: &mut HtmlExport, key: &str, value: &str) {
45    if let Some(sec) = doc.sections.last_mut() {
46        sec.rows.push([key.to_string(), value.to_string()]);
47    }
48}
49
50/// Return the total number of rows across all sections.
51#[allow(dead_code)]
52pub fn total_row_count(doc: &HtmlExport) -> usize {
53    doc.sections.iter().map(|s| s.rows.len()).sum()
54}
55
56/// Return the number of sections.
57#[allow(dead_code)]
58pub fn section_count(doc: &HtmlExport) -> usize {
59    doc.sections.len()
60}
61
62/// Render the document as an HTML string.
63#[allow(dead_code)]
64pub fn to_html_string(doc: &HtmlExport) -> String {
65    let mut out = format!(
66        "<!DOCTYPE html>\n<html>\n<head><title>{}</title></head>\n<body>\n<h1>{}</h1>\n",
67        doc.title, doc.title
68    );
69    for sec in &doc.sections {
70        out.push_str(&format!("<h2>{}</h2>\n<table border=\"1\">\n", sec.heading));
71        for row in &sec.rows {
72            out.push_str(&format!(
73                "<tr><td>{}</td><td>{}</td></tr>\n",
74                row[0], row[1]
75            ));
76        }
77        out.push_str("</table>\n");
78    }
79    out.push_str("</body>\n</html>");
80    out
81}
82
83/// Export mesh stats as an HTML page.
84#[allow(dead_code)]
85pub fn export_mesh_stats_html(vertex_count: usize, index_count: usize, name: &str) -> String {
86    let mut doc = new_html_export("Mesh Stats");
87    add_html_section(&mut doc, name);
88    add_html_row(&mut doc, "Vertices", &vertex_count.to_string());
89    add_html_row(&mut doc, "Indices", &index_count.to_string());
90    add_html_row(&mut doc, "Triangles", &(index_count / 3).to_string());
91    to_html_string(&doc)
92}
93
94/// Escape HTML special characters in a string.
95#[allow(dead_code)]
96pub fn html_escape(s: &str) -> String {
97    s.replace('&', "&amp;")
98        .replace('<', "&lt;")
99        .replace('>', "&gt;")
100        .replace('"', "&quot;")
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106
107    #[test]
108    fn test_new_html_export_empty() {
109        let doc = new_html_export("Test");
110        assert_eq!(section_count(&doc), 0);
111        assert_eq!(total_row_count(&doc), 0);
112    }
113
114    #[test]
115    fn test_add_section() {
116        let mut doc = new_html_export("T");
117        add_html_section(&mut doc, "Section 1");
118        assert_eq!(section_count(&doc), 1);
119    }
120
121    #[test]
122    fn test_add_row() {
123        let mut doc = new_html_export("T");
124        add_html_section(&mut doc, "S");
125        add_html_row(&mut doc, "key", "val");
126        assert_eq!(total_row_count(&doc), 1);
127    }
128
129    #[test]
130    fn test_to_html_contains_title() {
131        let doc = new_html_export("My Page");
132        let s = to_html_string(&doc);
133        assert!(s.contains("My Page"));
134    }
135
136    #[test]
137    fn test_to_html_contains_table() {
138        let mut doc = new_html_export("T");
139        add_html_section(&mut doc, "S");
140        add_html_row(&mut doc, "k", "v");
141        let s = to_html_string(&doc);
142        assert!(s.contains("<table"));
143    }
144
145    #[test]
146    fn test_to_html_contains_row_data() {
147        let mut doc = new_html_export("T");
148        add_html_section(&mut doc, "S");
149        add_html_row(&mut doc, "vertices", "512");
150        let s = to_html_string(&doc);
151        assert!(s.contains("512"));
152    }
153
154    #[test]
155    fn test_export_mesh_stats_html() {
156        let s = export_mesh_stats_html(100, 300, "head");
157        assert!(s.contains("Vertices"));
158        assert!(s.contains("head"));
159    }
160
161    #[test]
162    fn test_html_escape_ampersand() {
163        assert_eq!(html_escape("a & b"), "a &amp; b");
164    }
165
166    #[test]
167    fn test_html_escape_angle_brackets() {
168        assert_eq!(html_escape("<tag>"), "&lt;tag&gt;");
169    }
170
171    #[test]
172    fn test_doctype_in_output() {
173        let doc = new_html_export("T");
174        let s = to_html_string(&doc);
175        assert!(s.starts_with("<!DOCTYPE html>"));
176    }
177}