Skip to content

Commit f0a623e

Browse files
Extend anstyle-svg API to make it easier to integrate it into bigger HTML pages (#261)
I'm using this crate within `triagebot` for syntax highlighting of the logs. We have extra content we need to add in the HTML, meaning I can't simply use `render_html` or I'd need to parse the HTML, which would be suboptimal. So instead, I keep the two parts that I need: the style and the HTML "body".
1 parent fd785c4 commit f0a623e

File tree

1 file changed

+93
-53
lines changed

1 file changed

+93
-53
lines changed

crates/anstyle-svg/src/lib.rs

Lines changed: 93 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,6 @@ impl Term {
9595
use std::fmt::Write as _;
9696
use unicode_width::UnicodeWidthStr as _;
9797

98-
const FG: &str = "fg";
99-
const BG: &str = "bg";
100-
10198
let mut styled = adapter::AnsiBytes::new();
10299
let mut elements = styled.extract_next(ansi.as_bytes()).collect::<Vec<_>>();
103100
preprocess_invert_style(&mut elements, self.bg_color, self.fg_color);
@@ -108,8 +105,7 @@ impl Term {
108105
let bg_color = rgb_value(self.bg_color, self.palette);
109106
let font_family = self.font_family;
110107

111-
let line_height = 18;
112-
let height = styled_lines.len() * line_height + self.padding_px * 2;
108+
let height = styled_lines.len() * LINE_HEIGHT + self.padding_px * 2;
113109
let max_width = styled_lines
114110
.iter()
115111
.map(|l| l.iter().map(|e| e.text.width()).sum())
@@ -148,13 +144,13 @@ impl Term {
148144
}
149145
writeln!(&mut buffer, r#" .container {{"#).unwrap();
150146
writeln!(&mut buffer, r#" padding: 0 10px;"#).unwrap();
151-
writeln!(&mut buffer, r#" line-height: {line_height}px;"#).unwrap();
147+
writeln!(&mut buffer, r#" line-height: {LINE_HEIGHT}px;"#).unwrap();
152148
writeln!(&mut buffer, r#" }}"#).unwrap();
153149
write_effects_in_use(&mut buffer, &elements);
154150
writeln!(&mut buffer, r#" tspan {{"#).unwrap();
155151
writeln!(&mut buffer, r#" font: 14px {font_family};"#).unwrap();
156152
writeln!(&mut buffer, r#" white-space: pre;"#).unwrap();
157-
writeln!(&mut buffer, r#" line-height: {line_height}px;"#).unwrap();
153+
writeln!(&mut buffer, r#" line-height: {LINE_HEIGHT}px;"#).unwrap();
158154
writeln!(&mut buffer, r#" }}"#).unwrap();
159155
writeln!(&mut buffer, r#" </style>"#).unwrap();
160156
writeln!(&mut buffer).unwrap();
@@ -169,7 +165,7 @@ impl Term {
169165
}
170166

171167
let text_x = self.padding_px;
172-
let mut text_y = self.padding_px + line_height;
168+
let mut text_y = self.padding_px + LINE_HEIGHT;
173169
writeln!(
174170
&mut buffer,
175171
r#" <text xml:space="preserve" class="container {FG}">"#
@@ -200,7 +196,7 @@ impl Term {
200196
writeln!(&mut buffer).unwrap();
201197
writeln!(&mut buffer, r#"</tspan>"#).unwrap();
202198

203-
text_y += line_height;
199+
text_y += LINE_HEIGHT;
204200
}
205201
writeln!(&mut buffer, r#" </text>"#).unwrap();
206202
writeln!(&mut buffer).unwrap();
@@ -216,21 +212,12 @@ impl Term {
216212
pub fn render_html(&self, ansi: &str) -> String {
217213
use std::fmt::Write as _;
218214

219-
const FG: &str = "fg";
220-
const BG: &str = "bg";
221-
222215
let mut styled = adapter::AnsiBytes::new();
223216
let mut elements = styled.extract_next(ansi.as_bytes()).collect::<Vec<_>>();
224217
preprocess_invert_style(&mut elements, self.bg_color, self.fg_color);
225218

226219
let styled_lines = split_lines(&elements);
227220

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-
234221
let mut buffer = String::new();
235222
writeln!(&mut buffer, r#"<!DOCTYPE html>"#).unwrap();
236223
writeln!(&mut buffer, r#"<html>"#).unwrap();
@@ -247,73 +234,123 @@ impl Term {
247234
)
248235
.unwrap();
249236
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) {
237+
self.render_classes(&mut buffer, &elements);
238+
writeln!(&mut buffer, r#" </style>"#).unwrap();
239+
writeln!(&mut buffer, r#"</head>"#).unwrap();
240+
writeln!(&mut buffer).unwrap();
241+
242+
if !self.background {
243+
writeln!(&mut buffer, r#"<body>"#).unwrap();
244+
} else {
245+
writeln!(&mut buffer, r#"<body class="{BG}">"#).unwrap();
246+
}
247+
writeln!(&mut buffer).unwrap();
248+
249+
self.render_content(&mut buffer, styled_lines);
250+
writeln!(&mut buffer).unwrap();
251+
252+
writeln!(&mut buffer, r#"</body>"#).unwrap();
253+
writeln!(&mut buffer, r#"</html>"#).unwrap();
254+
buffer
255+
}
256+
257+
fn render_classes(&self, buffer: &mut String, elements: &[adapter::Element]) {
258+
use std::fmt::Write as _;
259+
260+
let fg_color = rgb_value(self.fg_color, self.palette);
261+
let bg_color = rgb_value(self.bg_color, self.palette);
262+
let font_family = self.font_family;
263+
264+
writeln!(buffer, r#" .{FG} {{ color: {fg_color} }}"#).unwrap();
265+
writeln!(buffer, r#" .{BG} {{ background: {bg_color} }}"#).unwrap();
266+
for (name, rgb) in color_styles(elements, self.palette) {
253267
if name.starts_with(FG_PREFIX) {
254-
writeln!(&mut buffer, r#" .{name} {{ color: {rgb} }}"#).unwrap();
268+
writeln!(buffer, r#" .{name} {{ color: {rgb} }}"#).unwrap();
255269
}
256270
if name.starts_with(BG_PREFIX) {
257271
writeln!(
258-
&mut buffer,
272+
buffer,
259273
r#" .{name} {{ background: {rgb}; user-select: none; }}"#
260274
)
261275
.unwrap();
262276
}
263277
if name.starts_with(UNDERLINE_PREFIX) {
264278
writeln!(
265-
&mut buffer,
279+
buffer,
266280
r#" .{name} {{ text-decoration-line: underline; text-decoration-color: {rgb} }}"#
267281
)
268282
.unwrap();
269283
}
270284
}
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();
285+
writeln!(buffer, r#" .container {{"#).unwrap();
286+
writeln!(buffer, r#" line-height: {LINE_HEIGHT}px;"#).unwrap();
287+
writeln!(buffer, r#" }}"#).unwrap();
288+
write_effects_in_use(buffer, elements);
289+
writeln!(buffer, r#" span {{"#).unwrap();
290+
writeln!(buffer, r#" font: 14px {font_family};"#).unwrap();
291+
writeln!(buffer, r#" white-space: pre;"#).unwrap();
292+
writeln!(buffer, r#" line-height: {LINE_HEIGHT}px;"#).unwrap();
293+
writeln!(buffer, r#" }}"#).unwrap();
294+
}
295+
296+
fn render_content(&self, buffer: &mut String, styled_lines: Vec<Vec<adapter::Element>>) {
297+
use std::fmt::Write as _;
290298

291-
writeln!(&mut buffer, r#" <div class="container {FG}">"#).unwrap();
299+
writeln!(buffer, r#" <div class="container {FG}">"#).unwrap();
292300
for line in &styled_lines {
293301
if line.iter().any(|e| e.style.get_bg_color().is_some()) {
294302
for element in line {
295303
if element.text.is_empty() {
296304
continue;
297305
}
298-
write_bg_span(&mut buffer, "span", &element.style, &element.text);
306+
write_bg_span(buffer, "span", &element.style, &element.text);
299307
}
300-
writeln!(&mut buffer, r#"<br />"#).unwrap();
308+
writeln!(buffer, r#"<br />"#).unwrap();
301309
}
302310

303311
for element in line {
304312
if element.text.is_empty() {
305313
continue;
306314
}
307-
write_fg_span(&mut buffer, "span", element, &element.text);
315+
write_fg_span(buffer, "span", element, &element.text);
308316
}
309-
writeln!(&mut buffer, r#"<br />"#).unwrap();
317+
writeln!(buffer, r#"<br />"#).unwrap();
310318
}
311-
writeln!(&mut buffer, r#" </div>"#).unwrap();
312-
writeln!(&mut buffer).unwrap();
319+
writeln!(buffer, r#" </div>"#).unwrap();
320+
}
313321

314-
writeln!(&mut buffer, r#"</body>"#).unwrap();
315-
writeln!(&mut buffer, r#"</html>"#).unwrap();
316-
buffer
322+
/// Returns the various parts needed to create an HTML page.
323+
pub fn render_html_fragments(&self, ansi: &str) -> HtmlFragments {
324+
let mut styled = adapter::AnsiBytes::new();
325+
let mut elements = styled.extract_next(ansi.as_bytes()).collect::<Vec<_>>();
326+
preprocess_invert_style(&mut elements, self.bg_color, self.fg_color);
327+
328+
let styled_lines = split_lines(&elements);
329+
330+
let mut style = String::new();
331+
let mut body = String::new();
332+
333+
self.render_classes(&mut style, &elements);
334+
self.render_content(&mut body, styled_lines);
335+
HtmlFragments { style, body }
336+
}
337+
}
338+
339+
/// Contains the different parts of a HTML rendered page.
340+
pub struct HtmlFragments {
341+
style: String,
342+
body: String,
343+
}
344+
345+
impl HtmlFragments {
346+
/// Content that can be used directly in a `<style>` tag.
347+
pub fn style(&self) -> &str {
348+
&self.style
349+
}
350+
351+
/// Content that can be put in the HTML body or any tag inside the `<body>`.
352+
pub fn body(&self) -> &str {
353+
&self.body
317354
}
318355
}
319356

@@ -518,9 +555,12 @@ fn rgb_value(color: anstyle::Color, palette: Palette) -> String {
518555
format!("#{r:02X}{g:02X}{b:02X}")
519556
}
520557

558+
const FG: &str = "fg";
559+
const BG: &str = "bg";
521560
const FG_PREFIX: &str = "fg";
522561
const BG_PREFIX: &str = "bg";
523562
const UNDERLINE_PREFIX: &str = "underline";
563+
const LINE_HEIGHT: usize = 18;
524564

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

0 commit comments

Comments
 (0)