diff --git a/src/epub/x4.rs b/src/epub/x4.rs index e0e936a..7ccfca8 100644 --- a/src/epub/x4.rs +++ b/src/epub/x4.rs @@ -30,6 +30,9 @@ pub const MAX_WORD_CHARS: usize = 200; /// U+00AD, invisible unless the renderer actually needs to break there. const SOFT_HYPHEN: char = '\u{00ad}'; +/// U+00A0, rendered as visible indentation even when the X4 collapses whitespace. +const NO_BREAK_SPACE: char = '\u{00a0}'; + /// Elements whose content is code, not prose, and must be copied through /// untouched — a soft hyphen inside a stylesheet would corrupt it. const RAW_TEXT_ELEMENTS: &[&str] = &["script", "style"]; @@ -127,14 +130,28 @@ pub fn simplify_xhtml(xhtml: &str) -> String { break_long_words(&strip_attributes(xhtml, DROPPED_ATTRIBUTES)) } -/// Insert soft hyphens into words longer than [`MAX_WORD_CHARS`], in text -/// content only (§3.10). +/// Insert soft hyphens into over-long words and make `
` whitespace
+/// explicit, in text content only (§3.10).
fn break_long_words(html: &str) -> String {
let mut out = String::with_capacity(html.len());
let mut cursor = 0usize;
+ let mut inside_pre = false;
+ let mut at_line_start = false;
+ // A newline right after `` (or ``) is not a blank line:
+ // browsers drop it, so we do too.
+ let mut pre_just_opened = false;
while let Some(rel) = html[cursor..].find('<') {
let start = cursor + rel;
- soften_text(&html[cursor..start], &mut out);
+ if inside_pre {
+ preserve_pre_whitespace(
+ &html[cursor..start],
+ &mut out,
+ &mut at_line_start,
+ &mut pre_just_opened,
+ );
+ } else {
+ soften_text(&html[cursor..start], &mut out);
+ }
let Some(end) = tag_end(html, start) else {
out.push_str(&html[start..]);
return out;
@@ -142,6 +159,17 @@ fn break_long_words(html: &str) -> String {
let tag = &html[start..end];
out.push_str(tag);
cursor = end;
+ if is_element_tag(tag, "pre", false) {
+ inside_pre = true;
+ at_line_start = true;
+ pre_just_opened = true;
+ } else if is_element_tag(tag, "pre", true) {
+ inside_pre = false;
+ } else if inside_pre && is_element_tag(tag, "br", false) {
+ at_line_start = true;
+ } else if inside_pre && !is_element_tag(tag, "code", false) {
+ pre_just_opened = false;
+ }
// `