Skip to content

Commit 6ade54f

Browse files
Extend anstyle-svg API to make it easier to integrate it into bigger HTML pages
1 parent fd785c4 commit 6ade54f

1 file changed

Lines changed: 82 additions & 54 deletions

File tree

crates/anstyle-svg/src/lib.rs

Lines changed: 82 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,16 @@ pub use anstyle_lossy::palette::Palette;
3131
pub use anstyle_lossy::palette::VGA;
3232
pub use anstyle_lossy::palette::WIN10_CONSOLE;
3333

34+
use std::fmt::Write as _;
35+
36+
/// Contains the different parts of a HTML rendered page.
37+
pub struct HtmlParts {
38+
/// Content that can be used directly in a `<style>` tag.
39+
pub style: String,
40+
/// Content that can be put in the HTML body or any tag inside the `<body>`.
41+
pub body: String,
42+
}
43+
3444
/// Define the terminal-like settings for rendering output
3545
#[derive(Copy, Clone, Debug)]
3646
pub struct Term {
@@ -108,8 +118,7 @@ impl Term {
108118
let bg_color = rgb_value(self.bg_color, self.palette);
109119
let font_family = self.font_family;
110120

111-
let line_height = 18;
112-
let height = styled_lines.len() * line_height + self.padding_px * 2;
121+
let height = styled_lines.len() * LINE_HEIGHT + self.padding_px * 2;
113122
let max_width = styled_lines
114123
.iter()
115124
.map(|l| l.iter().map(|e| e.text.width()).sum())
@@ -148,13 +157,13 @@ impl Term {
148157
}
149158
writeln!(&mut buffer, r#" .container {{"#).unwrap();
150159
writeln!(&mut buffer, r#" padding: 0 10px;"#).unwrap();
151-
writeln!(&mut buffer, r#" line-height: {line_height}px;"#).unwrap();
160+
writeln!(&mut buffer, r#" line-height: {LINE_HEIGHT}px;"#).unwrap();
152161
writeln!(&mut buffer, r#" }}"#).unwrap();
153162
write_effects_in_use(&mut buffer, &elements);
154163
writeln!(&mut buffer, r#" tspan {{"#).unwrap();
155164
writeln!(&mut buffer, r#" font: 14px {font_family};"#).unwrap();
156165
writeln!(&mut buffer, r#" white-space: pre;"#).unwrap();
157-
writeln!(&mut buffer, r#" line-height: {line_height}px;"#).unwrap();
166+
writeln!(&mut buffer, r#" line-height: {LINE_HEIGHT}px;"#).unwrap();
158167
writeln!(&mut buffer, r#" }}"#).unwrap();
159168
writeln!(&mut buffer, r#" </style>"#).unwrap();
160169
writeln!(&mut buffer).unwrap();
@@ -169,7 +178,7 @@ impl Term {
169178
}
170179

171180
let text_x = self.padding_px;
172-
let mut text_y = self.padding_px + line_height;
181+
let mut text_y = self.padding_px + LINE_HEIGHT;
173182
writeln!(
174183
&mut buffer,
175184
r#" <text xml:space="preserve" class="container {FG}">"#
@@ -200,7 +209,7 @@ impl Term {
200209
writeln!(&mut buffer).unwrap();
201210
writeln!(&mut buffer, r#"</tspan>"#).unwrap();
202211

203-
text_y += line_height;
212+
text_y += LINE_HEIGHT;
204213
}
205214
writeln!(&mut buffer, r#" </text>"#).unwrap();
206215
writeln!(&mut buffer).unwrap();
@@ -214,23 +223,12 @@ impl Term {
214223
/// **Note:** Lines are not wrapped. This is intentional as this attempts to convey the exact
215224
/// output with escape codes translated to HTML elements.
216225
pub fn render_html(&self, ansi: &str) -> String {
217-
use std::fmt::Write as _;
218-
219-
const FG: &str = "fg";
220-
const BG: &str = "bg";
221-
222226
let mut styled = adapter::AnsiBytes::new();
223227
let mut elements = styled.extract_next(ansi.as_bytes()).collect::<Vec<_>>();
224228
preprocess_invert_style(&mut elements, self.bg_color, self.fg_color);
225229

226230
let styled_lines = split_lines(&elements);
227231

228-
let fg_color = rgb_value(self.fg_color, self.palette);
229-
let bg_color = rgb_value(self.bg_color, self.palette);
230-
let font_family = self.font_family;
231-
232-
let line_height = 18;
233-
234232
let mut buffer = String::new();
235233
writeln!(&mut buffer, r#"<!DOCTYPE html>"#).unwrap();
236234
writeln!(&mut buffer, r#"<html>"#).unwrap();
@@ -247,73 +245,100 @@ impl Term {
247245
)
248246
.unwrap();
249247
writeln!(&mut buffer, r#" <style>"#).unwrap();
250-
writeln!(&mut buffer, r#" .{FG} {{ color: {fg_color} }}"#).unwrap();
251-
writeln!(&mut buffer, r#" .{BG} {{ background: {bg_color} }}"#).unwrap();
252-
for (name, rgb) in color_styles(&elements, self.palette) {
248+
self.render_classes(&mut buffer, &elements);
249+
writeln!(&mut buffer, r#" </style>"#).unwrap();
250+
writeln!(&mut buffer, r#"</head>"#).unwrap();
251+
writeln!(&mut buffer).unwrap();
252+
253+
if !self.background {
254+
writeln!(&mut buffer, r#"<body>"#).unwrap();
255+
} else {
256+
writeln!(&mut buffer, r#"<body class="{BG}">"#).unwrap();
257+
}
258+
writeln!(&mut buffer).unwrap();
259+
self.render_content(&mut buffer, styled_lines);
260+
writeln!(&mut buffer).unwrap();
261+
262+
writeln!(&mut buffer, r#"</body>"#).unwrap();
263+
writeln!(&mut buffer, r#"</html>"#).unwrap();
264+
buffer
265+
}
266+
267+
/// Returns the various parts needed to create an HTML page.
268+
pub fn render_html_parts(&self, ansi: &str) -> HtmlParts {
269+
let mut styled = adapter::AnsiBytes::new();
270+
let mut elements = styled.extract_next(ansi.as_bytes()).collect::<Vec<_>>();
271+
preprocess_invert_style(&mut elements, self.bg_color, self.fg_color);
272+
273+
let styled_lines = split_lines(&elements);
274+
275+
let mut style = String::new();
276+
let mut body = String::new();
277+
278+
self.render_classes(&mut style, &elements);
279+
self.render_content(&mut body, styled_lines);
280+
HtmlParts { style, body }
281+
}
282+
283+
fn render_classes(&self, buffer: &mut String, elements: &[adapter::Element]) {
284+
let fg_color = rgb_value(self.fg_color, self.palette);
285+
let bg_color = rgb_value(self.bg_color, self.palette);
286+
let font_family = self.font_family;
287+
288+
writeln!(buffer, r#" .{FG} {{ color: {fg_color} }}"#).unwrap();
289+
writeln!(buffer, r#" .{BG} {{ background: {bg_color} }}"#).unwrap();
290+
for (name, rgb) in color_styles(elements, self.palette) {
253291
if name.starts_with(FG_PREFIX) {
254-
writeln!(&mut buffer, r#" .{name} {{ color: {rgb} }}"#).unwrap();
292+
writeln!(buffer, r#" .{name} {{ color: {rgb} }}"#).unwrap();
255293
}
256294
if name.starts_with(BG_PREFIX) {
257295
writeln!(
258-
&mut buffer,
296+
buffer,
259297
r#" .{name} {{ background: {rgb}; user-select: none; }}"#
260298
)
261299
.unwrap();
262300
}
263301
if name.starts_with(UNDERLINE_PREFIX) {
264302
writeln!(
265-
&mut buffer,
303+
buffer,
266304
r#" .{name} {{ text-decoration-line: underline; text-decoration-color: {rgb} }}"#
267305
)
268306
.unwrap();
269307
}
270308
}
271-
writeln!(&mut buffer, r#" .container {{"#).unwrap();
272-
writeln!(&mut buffer, r#" line-height: {line_height}px;"#).unwrap();
273-
writeln!(&mut buffer, r#" }}"#).unwrap();
274-
write_effects_in_use(&mut buffer, &elements);
275-
writeln!(&mut buffer, r#" span {{"#).unwrap();
276-
writeln!(&mut buffer, r#" font: 14px {font_family};"#).unwrap();
277-
writeln!(&mut buffer, r#" white-space: pre;"#).unwrap();
278-
writeln!(&mut buffer, r#" line-height: {line_height}px;"#).unwrap();
279-
writeln!(&mut buffer, r#" }}"#).unwrap();
280-
writeln!(&mut buffer, r#" </style>"#).unwrap();
281-
writeln!(&mut buffer, r#"</head>"#).unwrap();
282-
writeln!(&mut buffer).unwrap();
283-
284-
if !self.background {
285-
writeln!(&mut buffer, r#"<body>"#).unwrap();
286-
} else {
287-
writeln!(&mut buffer, r#"<body class="{BG}">"#).unwrap();
288-
}
289-
writeln!(&mut buffer).unwrap();
290-
291-
writeln!(&mut buffer, r#" <div class="container {FG}">"#).unwrap();
309+
writeln!(buffer, r#" .container {{"#).unwrap();
310+
writeln!(buffer, r#" line-height: {LINE_HEIGHT}px;"#).unwrap();
311+
writeln!(buffer, r#" }}"#).unwrap();
312+
write_effects_in_use(buffer, elements);
313+
writeln!(buffer, r#" span {{"#).unwrap();
314+
writeln!(buffer, r#" font: 14px {font_family};"#).unwrap();
315+
writeln!(buffer, r#" white-space: pre;"#).unwrap();
316+
writeln!(buffer, r#" line-height: {LINE_HEIGHT}px;"#).unwrap();
317+
writeln!(buffer, r#" }}"#).unwrap();
318+
}
319+
320+
fn render_content(&self, buffer: &mut String, styled_lines: Vec<Vec<adapter::Element>>) {
321+
writeln!(buffer, r#" <div class="container {FG}">"#).unwrap();
292322
for line in &styled_lines {
293323
if line.iter().any(|e| e.style.get_bg_color().is_some()) {
294324
for element in line {
295325
if element.text.is_empty() {
296326
continue;
297327
}
298-
write_bg_span(&mut buffer, "span", &element.style, &element.text);
328+
write_bg_span(buffer, "span", &element.style, &element.text);
299329
}
300-
writeln!(&mut buffer, r#"<br />"#).unwrap();
330+
writeln!(buffer, r#"<br />"#).unwrap();
301331
}
302332

303333
for element in line {
304334
if element.text.is_empty() {
305335
continue;
306336
}
307-
write_fg_span(&mut buffer, "span", element, &element.text);
337+
write_fg_span(buffer, "span", element, &element.text);
308338
}
309-
writeln!(&mut buffer, r#"<br />"#).unwrap();
339+
writeln!(buffer, r#"<br />"#).unwrap();
310340
}
311-
writeln!(&mut buffer, r#" </div>"#).unwrap();
312-
writeln!(&mut buffer).unwrap();
313-
314-
writeln!(&mut buffer, r#"</body>"#).unwrap();
315-
writeln!(&mut buffer, r#"</html>"#).unwrap();
316-
buffer
341+
writeln!(buffer, r#" </div>"#).unwrap();
317342
}
318343
}
319344

@@ -518,9 +543,12 @@ fn rgb_value(color: anstyle::Color, palette: Palette) -> String {
518543
format!("#{r:02X}{g:02X}{b:02X}")
519544
}
520545

546+
const FG: &str = "fg";
547+
const BG: &str = "bg";
521548
const FG_PREFIX: &str = "fg";
522549
const BG_PREFIX: &str = "bg";
523550
const UNDERLINE_PREFIX: &str = "underline";
551+
const LINE_HEIGHT: usize = 18;
524552

525553
fn color_name(prefix: &str, color: anstyle::Color) -> String {
526554
match color {

0 commit comments

Comments
 (0)