Escape backslashes and line breaks in CSS font-family export (#1752)

* Escape backslashes and line breaks in CSS font-family export

Found while testing crates/engine/src/dev/codegen/css.rs:223 (text_decls, the font-family put)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Escape quotes and < in CSS font-family export

A double quote in a family name is escaped instead of turned into a single quote (CodeRabbit on
#1752), and < is written as \3C so a name can't end the generated page's <style> block.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
NetterSilver
2026-10-31 10:37:13 -04:00
committed by GitHub
co-authored by Claude Opus 5.5 Brandon Thomas
parent fad47ac8bc
commit 45e5a25370
2 changed files with 28 additions and 1 deletions
+13 -1
View File
@@ -220,7 +220,19 @@ fn effect_decls(n: &Node, put: &mut impl FnMut(&str, String)) {
fn text_decls(doc: &Document, n: &Node, tx: &figcraft_doc::TextData, t: &mut Tokens, put: &mut impl FnMut(&str, String)) {
let px = |v: f64| format!("{}px", num(v));
let s = &tx.style;
put("font-family", tok(doc, n, &["fontFamily"], format!("\"{}\"", s.font_family.replace('"', "'")), t));
put(
"font-family",
tok(
doc,
n,
&["fontFamily"],
format!(
"\"{}\"",
s.font_family.replace('\\', "\\\\").replace('"', "\\\"").replace('<', "\\3C ").replace("\r\n", "\\A ").replace(['\n', '\r'], "\\A ")
),
t,
),
);
put("font-size", tok(doc, n, &["fontSize"], px(s.font_size), t));
put("font-weight", s.weight().to_string());
if s.italic() {
+15
View File
@@ -535,3 +535,18 @@ fn auto_layout_places_right_aligned_auto_width_text() {
let c = s.doc().unwrap().node(t).unwrap().transform.as_coeffs();
assert_eq!((c[4], c[5]), (10.0, 10.0), "the auto layout keeps it at its padding");
}
#[test]
fn css_font_family_escapes_backslash_and_newline() {
let mut s = Session::new();
let t = text(&mut s, "abc", json!({"fontFamily": "Foo\\"}));
let css = crate::codegen::css_decls(s.doc().unwrap(), s.doc().unwrap().node(t).unwrap(), &mut Default::default(), (true, true));
assert!(css.contains(r#"font-family: "Foo\\";"#), "{css}");
let t = text(&mut s, "abc", json!({"fontFamily": "Foo\nBar"}));
let css = crate::codegen::css_decls(s.doc().unwrap(), s.doc().unwrap().node(t).unwrap(), &mut Default::default(), (true, true));
assert!(css.contains(r#"font-family: "Foo\A Bar";"#), "{css}");
// A quote stays a quote, and a name can't close the page's <style> block.
let t = text(&mut s, "abc", json!({"fontFamily": "Foo\"</style>"}));
let css = crate::codegen::css_decls(s.doc().unwrap(), s.doc().unwrap().node(t).unwrap(), &mut Default::default(), (true, true));
assert!(css.contains(r#"font-family: "Foo\"\3C /style>";"#), "{css}");
}