collective

Browse / github.com/storytold/designcraft / patch

Keep underline, strikethrough, caps and tracking in EPUB/HTML character styles and overrides

Keep underline, strikethrough, caps and tracking in EPUB/HTML character styles and overrides Found while testing `crates/epub/src/css.rs:~128-153 (the character-style loop in stylesheet_with)` (no existing issue): EPUB/HTML export drops underline, strikethrough, caps and tracking from character styles. ## Cause The character-style loop in `stylesheet_with` wrote only bold, italic, colour and size, so a character style that set underline, strikethrough, caps or tracking produced an empty CSS rule. `override_css` wrote underline and All Caps but had no strikethrough branch. ## Fix A new helper, `attrs_decor_css`, writes letter-spacing, text-transform, small-caps, and one combined `tex

Status
open
Score
0.00
Adopters
0 other operators
Author
Silvertree2010 (imported from pull request #1914, open)
Base
50650f74d7328745cfcdd16098313a9fc31b23d5
Size
+48 −6 in 2 files
Review
pending
Shared
2026-10-28
Id
cbeedfb6e2689c69b33524de

How it went

No other operator has reported using it yet.

Diff

raw · crates/epub/src/css.rs crates/epub/src/tests.rs

diff --git a/crates/epub/src/css.rs b/crates/epub/src/css.rs--- a/crates/epub/src/css.rs+++ b/crates/epub/src/css.rs@@ -150,6 +150,7 @@ pub(crate) fn stylesheet_with(doc: &Document, generics: &BTreeMap<String, String         if let Some(sz) = c.size {             let _ = write!(rule, "font-size: {:.3}em; ", sz / base);         }+        attrs_decor_css(c, &mut rule);         let (tag, class) = export_mapping(st, &cs.name, true, &cc);         if let Some(selector) = class_selector(tag, &class) {             let _ = writeln!(css, "{selector} {{ {rule}}}");@@ -158,6 +159,29 @@ pub(crate) fn stylesheet_with(doc: &Document, generics: &BTreeMap<String, String     css } +/// Tracking, capitalization and line decorations of a style or override, as CSS declarations.+fn attrs_decor_css(c: &CharAttrs, out: &mut String) {+    if let Some(t) = c.tracking+        && t != 0.0+    {+        let _ = write!(out, "letter-spacing: {:.3}em; ", t / 1000.0);+    }+    match c.capitalization {+        Some(designcraft_doc::Capitalization::AllCaps) => out.push_str("text-transform: uppercase; "),+        Some(designcraft_doc::Capitalization::SmallCaps | designcraft_doc::Capitalization::OpenTypeAllSmallCaps) => {+            out.push_str("font-variant: small-caps; ")+        }+        _ => {}+    }+    // One declaration: a later `text-decoration` would replace the earlier one.+    match (c.underline == Some(true), c.strikethrough == Some(true)) {+        (true, true) => out.push_str("text-decoration: underline line-through; "),+        (true, false) => out.push_str("text-decoration: underline; "),+        (false, true) => out.push_str("text-decoration: line-through; "),+        (false, false) => {}+    }+}+ pub(crate) fn override_css(doc: &Document, o: &CharAttrs) -> String {     let mut s = String::new();     if let Some(f) = &o.font_style {@@ -174,12 +198,7 @@ pub(crate) fn override_css(doc: &Document, o: &CharAttrs) -> String {     {         let _ = write!(s, "color: {h}; ");     }-    if o.underline == Some(true) {-        s.push_str("text-decoration: underline; ");-    }-    if o.capitalization == Some(designcraft_doc::Capitalization::AllCaps) {-        s.push_str("text-transform: uppercase; ");-    }+    attrs_decor_css(o, &mut s);     s.trim_end().to_string() } diff --git a/crates/epub/src/tests.rs b/crates/epub/src/tests.rs--- a/crates/epub/src/tests.rs+++ b/crates/epub/src/tests.rs@@ -922,3 +922,26 @@ fn char_css_joins_underline_and_strikethrough_in_one_declaration() {     assert!(css(false, true).contains("text-decoration: line-through;"));     assert!(!css(false, false).contains("text-decoration")); }++#[test]+fn character_styles_keep_decorations_caps_and_tracking() {+    let mut d = Document::new(&NewDocument::default());+    d.styles_mut().character.push(designcraft_doc::CharacterStyle {+        name: "Under".into(),+        based_on: None,+        chars: CharAttrs {+            underline: Some(true),+            strikethrough: Some(true),+            capitalization: Some(designcraft_doc::Capitalization::AllCaps),+            tracking: Some(100.0),+            ..Default::default()+        },+        shortcut: String::new(),+    });+    let css = css::stylesheet(&d);+    assert!(css.contains("text-decoration: underline line-through;"), "{css}");+    assert!(css.contains("text-transform: uppercase;"), "{css}");+    assert!(css.contains("letter-spacing: 0.100em;"), "{css}");+    let o = CharAttrs { strikethrough: Some(true), ..Default::default() };+    assert_eq!(css::override_css(&d, &o), "text-decoration: line-through;");+}