From 634cee89458324a1f9b9e1039c094c0f93315e28 Mon Sep 17 00:00:00 2001 From: Chris Emerson Date: Sat, 12 Oct 2024 08:09:07 +0100 Subject: [PATCH 1/4] Add CSS support for white-space: pre Switch to implemending
 with this.
---
 src/css.rs        | 30 ++++++++++++++++++++++++++++--
 src/css/parser.rs | 24 +++++++++++++++++++++++-
 src/lib.rs        | 19 ++++++++++++++++++-
 3 files changed, 69 insertions(+), 4 deletions(-)

diff --git a/src/css.rs b/src/css.rs
index a7bb8a6..31cc1b1 100644
--- a/src/css.rs
+++ b/src/css.rs
@@ -176,6 +176,7 @@ pub(crate) enum Style {
     Colour(Colour),
     BgColour(Colour),
     DisplayNone,
+    WhiteSpace(WhiteSpace),
 }
 
 #[derive(Debug, Clone)]
@@ -201,7 +202,7 @@ pub(crate) struct WithSpec {
     important: bool,
 }
 impl WithSpec {
-    fn maybe_update(
+    pub(crate) fn maybe_update(
         &mut self,
         important: bool,
         origin: StyleOrigin,
@@ -255,6 +256,17 @@ impl Default for WithSpec {
     }
 }
 
+#[derive(Debug, Copy, Clone, Default, PartialEq)]
+pub (crate) enum WhiteSpace {
+    #[default]
+    Normal,
+    // NoWrap,
+    Pre,
+    PreWrap,
+    // PreLine,
+    // BreakSpaces,
+}
+
 #[derive(Debug, Copy, Clone, Default)]
 pub(crate) struct ComputedStyle {
     /// The computed foreground colour, if any
@@ -264,6 +276,8 @@ pub(crate) struct ComputedStyle {
     pub(crate) bg_colour: WithSpec,
     /// If set, indicates whether `display: none` or something equivalent applies
     pub(crate) display_none: WithSpec,
+    /// The CSS white-space property
+    pub(crate) white_space: WithSpec,
 }
 
 #[derive(Debug, Clone)]
@@ -354,7 +368,14 @@ fn styles_from_properties(decls: &[parser::Declaration]) -> Vec {
                         importance: decl.important,
                     });
                 }
-            } /*
+            }
+            parser::Decl::WhiteSpace { value } => {
+                styles.push(StyleDecl {
+                    style: Style::WhiteSpace(*value),
+                    importance: decl.important,
+                });
+            }
+            /*
               _ => {
                   html_trace_quiet!("CSS: Unhandled property {:?}", decl);
               }
@@ -527,6 +548,11 @@ impl StyleData {
                     .display_none
                     .maybe_update(important, origin, specificity, true);
             }
+            Style::WhiteSpace(ws) => {
+                result
+                    .white_space
+                    .maybe_update(important, origin, specificity, ws);
+            }
         }
     }
 }
diff --git a/src/css/parser.rs b/src/css/parser.rs
index 29450c1..b77f99a 100644
--- a/src/css/parser.rs
+++ b/src/css/parser.rs
@@ -64,6 +64,7 @@ pub enum Display {
 }
 
 #[derive(Debug, PartialEq)]
+#[non_exhaustive]
 pub enum Decl {
     Color {
         value: Colour,
@@ -86,6 +87,9 @@ pub enum Decl {
     Display {
         value: Display,
     },
+    WhiteSpace {
+        value: WhiteSpace,
+    },
     Unknown {
         name: PropertyName,
         //        value: Vec,
@@ -161,7 +165,7 @@ pub struct Declaration {
     pub important: Importance,
 }
 
-use super::{Selector, SelectorComponent};
+use super::{Selector, SelectorComponent, WhiteSpace};
 
 #[derive(Debug, PartialEq)]
 pub(crate) struct RuleSet {
@@ -427,6 +431,10 @@ pub fn parse_declaration(text: &str) -> IResult<&str, Option> {
             let value = parse_display(&value)?;
             Decl::Display { value }
         }
+        "white-space" => {
+            let value = parse_white_space(&value)?;
+            Decl::WhiteSpace { value }
+        }
         _ => Decl::Unknown {
             name: prop,
             //            value: /*value*/"".into(),
@@ -679,6 +687,20 @@ fn parse_display(value: &RawValue) -> Result Result>> {
+    for tok in &value.tokens {
+        if let Token::Ident(word) = tok {
+            match word.deref() {
+                "normal" => return Ok(WhiteSpace::Normal),
+                "pre" => return Ok(WhiteSpace::Pre),
+                "pre-wrap" => return Ok(WhiteSpace::PreWrap),
+                _ => (),
+            }
+        }
+    }
+    Ok(WhiteSpace::Normal)
+}
+
 pub fn parse_rules(text: &str) -> IResult<&str, Vec> {
     separated_list0(
         tuple((tag(";"), skip_optional_whitespace)),
diff --git a/src/lib.rs b/src/lib.rs
index abbc164..5377f21 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -71,6 +71,7 @@ use css::ComputedStyle;
 #[derive(Copy, Clone, Debug, Default)]
 struct ComputedStyle;
 
+use css::WhiteSpace;
 use render::text_renderer::{
     RenderLine, RenderOptions, RichAnnotation, SubRenderer, TaggedLine, TextRenderer,
 };
@@ -1335,7 +1336,9 @@ fn process_dom_node<'a, T: Write>(
                     Ok(Some(RenderNode::new_styled(Div(cs), computed)))
                 }),
                 expanded_name!(html "pre") => pending(input, move |_, cs| {
-                    Ok(Some(RenderNode::new_styled(Pre(cs), computed)))
+                    let mut computed = computed;
+                    computed.white_space.maybe_update(false, css::StyleOrigin::Agent, Default::default(), WhiteSpace::Pre);
+                    Ok(Some(RenderNode::new_styled(Block(cs), computed)))
                 }),
                 expanded_name!(html "br") => Finished(RenderNode::new_styled(Break, computed)),
                 expanded_name!(html "table") => table_to_render_tree(input, computed, err_out),
@@ -1491,6 +1494,7 @@ fn pending2<
 struct PushedStyleInfo {
     colour: bool,
     bgcolour: bool,
+    white_space: bool,
 }
 
 impl PushedStyleInfo {
@@ -1507,6 +1511,16 @@ impl PushedStyleInfo {
                 render.push_bgcolour(col);
                 result.bgcolour = true;
             }
+            if let Some(ws) = style.white_space.val() {
+                match ws {
+                    WhiteSpace::Normal => {},
+                    WhiteSpace::Pre => {
+                        render.start_pre();
+                        result.white_space = true;
+                    }
+                    WhiteSpace::PreWrap => todo!(),
+                }
+            }
         }
         #[cfg(not(feature = "css"))]
         {
@@ -1522,6 +1536,9 @@ impl PushedStyleInfo {
         if self.colour {
             renderer.pop_colour();
         }
+        if self.white_space {
+            renderer.end_pre();
+        }
     }
 }
 

From 801afc8ed55375f0d46b75de9a963a616884e33e Mon Sep 17 00:00:00 2001
From: Chris Emerson 
Date: Sat, 12 Oct 2024 09:00:08 +0100
Subject: [PATCH 2/4] Implement white-space: pre-wrap;

---
 src/css.rs                  |  12 +++-
 src/lib.rs                  |  25 ++------
 src/render/mod.rs           |   9 +--
 src/render/text_renderer.rs | 115 +++++++++++++++++++++++++++---------
 src/tests.rs                |  25 +++++++-
 5 files changed, 133 insertions(+), 53 deletions(-)

diff --git a/src/css.rs b/src/css.rs
index 31cc1b1..1cac531 100644
--- a/src/css.rs
+++ b/src/css.rs
@@ -267,6 +267,16 @@ pub (crate) enum WhiteSpace {
     // BreakSpaces,
 }
 
+impl WhiteSpace {
+    pub fn preserve_whitespace(&self) -> bool {
+        match self {
+            WhiteSpace::Normal => false,
+            WhiteSpace::Pre |
+            WhiteSpace::PreWrap => true,
+        }
+    }
+}
+
 #[derive(Debug, Copy, Clone, Default)]
 pub(crate) struct ComputedStyle {
     /// The computed foreground colour, if any
@@ -407,7 +417,7 @@ impl StyleData {
                         styles: styles.clone(),
                     };
                     html_trace_quiet!("Adding ruleset {ruleset:?}");
-                    rules.push(ruleset);
+                    rules.push(dbg!(ruleset));
                 }
             }
         }
diff --git a/src/lib.rs b/src/lib.rs
index 5377f21..926c7fa 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -420,8 +420,6 @@ enum RenderNodeInfo {
     Header(usize, Vec),
     /// A Div element with children
     Div(Vec),
-    /// A preformatted region.
-    Pre(Vec),
     /// A blockquote
     BlockQuote(Vec),
     /// An unordered list
@@ -533,7 +531,7 @@ impl RenderNode {
             }
 
             Container(ref v) | Em(ref v) | Strong(ref v) | Strikeout(ref v) | Code(ref v)
-            | Block(ref v) | Div(ref v) | Pre(ref v) | Dl(ref v) | Dt(ref v) | ListItem(ref v)
+            | Block(ref v) | Div(ref v) | Dl(ref v) | Dt(ref v) | ListItem(ref v)
             | Sup(ref v) => v
                 .iter()
                 .map(recurse)
@@ -630,7 +628,6 @@ impl RenderNode {
             | Block(ref v)
             | ListItem(ref v)
             | Div(ref v)
-            | Pre(ref v)
             | BlockQuote(ref v)
             | Dl(ref v)
             | Dt(ref v)
@@ -671,7 +668,6 @@ fn precalc_size_estimate<'a, 'b: 'a, D: TextDecorator>(
         | Block(ref v)
         | ListItem(ref v)
         | Div(ref v)
-        | Pre(ref v)
         | BlockQuote(ref v)
         | Ul(ref v)
         | Ol(_, ref v)
@@ -1139,7 +1135,6 @@ fn prepend_marker(prefix: RenderNode, mut orig: RenderNode) -> RenderNode {
         Block(ref mut children)
         | ListItem(ref mut children)
         | Div(ref mut children)
-        | Pre(ref mut children)
         | BlockQuote(ref mut children)
         | Container(ref mut children)
         | TableCell(RenderTableCell {
@@ -1514,11 +1509,11 @@ impl PushedStyleInfo {
             if let Some(ws) = style.white_space.val() {
                 match ws {
                     WhiteSpace::Normal => {},
-                    WhiteSpace::Pre => {
-                        render.start_pre();
+                    WhiteSpace::Pre |
+                    WhiteSpace::PreWrap => {
+                        render.push_ws(ws);
                         result.white_space = true;
                     }
-                    WhiteSpace::PreWrap => todo!(),
                 }
             }
         }
@@ -1537,7 +1532,7 @@ impl PushedStyleInfo {
             renderer.pop_colour();
         }
         if self.white_space {
-            renderer.end_pre();
+            renderer.pop_ws();
         }
     }
 }
@@ -1645,16 +1640,6 @@ fn do_render_node(
                 Ok(Some(None))
             })
         }
-        Pre(children) => {
-            renderer.new_line()?;
-            renderer.start_pre();
-            pending2(children, |renderer: &mut TextRenderer, _| {
-                renderer.new_line()?;
-                renderer.end_pre();
-                pushed_style.unwind(renderer);
-                Ok(Some(None))
-            })
-        }
         BlockQuote(children) => {
             let prefix = renderer.quote_prefix();
             debug_assert!(size_estimate.prefix_size == prefix.len());
diff --git a/src/render/mod.rs b/src/render/mod.rs
index 574622e..3d1fb76 100644
--- a/src/render/mod.rs
+++ b/src/render/mod.rs
@@ -1,6 +1,7 @@
 //! Module containing the `Renderer` interface for constructing a
 //! particular text output.
 
+use crate::css::WhiteSpace;
 use crate::Colour;
 use crate::Error;
 
@@ -45,11 +46,11 @@ pub(crate) trait Renderer {
     }
 
     /// Begin a preformatted block.  Until the corresponding end,
-    /// whitespace will used verbatim.  Pre regions can nest.
-    fn start_pre(&mut self);
+    /// whitespace handlinng will be modified.  Can be nested.
+    fn push_ws(&mut self, ws: WhiteSpace);
 
-    /// Finish a preformatted block started with `start_pre`.
-    fn end_pre(&mut self);
+    /// Finish a preformatted block started with `push_ws`.
+    fn pop_ws(&mut self);
 
     /// Add some inline text (which should be wrapped at the
     /// appropriate width) to the current block.
diff --git a/src/render/text_renderer.rs b/src/render/text_renderer.rs
index dd511e5..0944247 100644
--- a/src/render/text_renderer.rs
+++ b/src/render/text_renderer.rs
@@ -3,6 +3,7 @@
 //! This module implements helpers and concrete types for rendering from HTML
 //! into different text formats.
 
+use crate::css::WhiteSpace;
 use crate::Colour;
 use crate::Error;
 
@@ -332,7 +333,7 @@ impl WrappedBlock {
         }
     }
 
-    fn flush_word(&mut self) -> Result<(), Error> {
+    fn flush_word(&mut self, with_space: bool) -> Result<(), Error> {
         use self::TaggedLineElement::Str;
 
         /* Finish the word. */
@@ -343,7 +344,7 @@ impl WrappedBlock {
             let space_needed = self.wordlen + if self.linelen > 0 { 1 } else { 0 }; // space
             if space_needed <= space_in_line {
                 html_trace!("Got enough space");
-                if self.linelen > 0 {
+                if self.linelen > 0 && with_space {
                     self.line.push(Str(TaggedString {
                         s: " ".into(),
                         tag: self.spacetag.clone().unwrap_or_else(|| Default::default()),
@@ -458,7 +459,7 @@ impl WrappedBlock {
     }
 
     fn flush(&mut self) -> Result<(), Error> {
-        self.flush_word()?;
+        self.flush_word(true)?;
         self.flush_line();
         Ok(())
     }
@@ -492,7 +493,7 @@ impl WrappedBlock {
         for c in text.chars() {
             if c.is_whitespace() {
                 /* Whitespace is mostly ignored, except to terminate words. */
-                self.flush_word()?;
+                self.flush_word(true)?;
                 self.spacetag = Some(tag.clone());
             } else if let Some(charwidth) = UnicodeWidthChar::width(c) {
                 /* Not whitespace; add to the current word. */
@@ -518,7 +519,7 @@ impl WrappedBlock {
         );
         // Make sure that any previous word has been sent to the line, as we
         // bypass the word buffer.
-        self.flush_word()?;
+        self.flush_word(true)?;
 
         for c in text.chars() {
             if let Some(charwidth) = UnicodeWidthChar::width(c) {
@@ -569,6 +570,59 @@ impl WrappedBlock {
         Ok(())
     }
 
+    fn add_text_prewrap(
+        &mut self,
+        text: &str,
+        tag: &T,
+    ) -> Result<(), Error> {
+        html_trace!("WrappedBlock::add_text_prewrap({}), {:?}", text, tag);
+        for c in text.chars() {
+            if c.is_whitespace() {
+                // Wrap if needed
+                self.flush_word(false)?;
+                match c {
+                    '\n' => {
+                        self.force_flush_line();
+                    }
+                    '\t' => {
+                        let tab_stop = 8;
+                        let mut at_least_one_space = false;
+                        while self.linelen % tab_stop != 0 || !at_least_one_space {
+                            if self.linelen >= self.width {
+                                self.flush_line();
+                            } else {
+                                self.line.push_char(
+                                    ' ',
+                                    tag
+                                );
+                                self.linelen += 1;
+                                at_least_one_space = true;
+                            }
+                        }
+                    }
+                    _ => {
+                        if let Some(width) = UnicodeWidthChar::width(c) {
+                            if self.width - self.linelen < width {
+                                self.flush_line();
+                            }
+                            if self.width - self.linelen >= width {
+                                // Check for space again to avoid pathological issues
+                                self.line.push_char(c, tag);
+                                self.linelen += width;
+                            }
+                        }
+                    }
+                }
+            } else if let Some(charwidth) = UnicodeWidthChar::width(c) {
+                /* Not whitespace; add to the current word. */
+                self.word.push_char(c, tag);
+                self.wordlen += charwidth;
+            }
+            html_trace_quiet!("  Added char {:?}, wordlen={}", c, self.wordlen);
+        }
+        Ok(())
+    }
+
     fn add_element(&mut self, elt: TaggedLineElement) {
         self.word.push(elt);
     }
@@ -900,7 +954,7 @@ pub(crate) struct SubRenderer {
     ann_stack: Vec,
     text_filter_stack: Vec Option>,
     /// The depth of `
` block stacking.
-    pre_depth: usize,
+    ws_stack: Vec,
 }
 
 impl std::fmt::Debug for SubRenderer {
@@ -910,7 +964,7 @@ impl std::fmt::Debug for SubRenderer {
             .field("lines", &self.lines)
             //.field("decorator", &self.decorator)
             .field("ann_stack", &self.ann_stack)
-            .field("pre_depth", &self.pre_depth)
+            .field("ws_stack", &self.ws_stack)
             .field("wrapping", &self.wrapping)
             .finish()
     }
@@ -993,7 +1047,7 @@ impl SubRenderer {
             wrapping: None,
             decorator,
             ann_stack: Vec::new(),
-            pre_depth: 0,
+            ws_stack: Vec::new(),
             text_filter_stack: Vec::new(),
         }
     }
@@ -1104,6 +1158,10 @@ impl SubRenderer {
         }
         Ok(new_width.max(min_width))
     }
+
+    fn ws_mode(&self) -> WhiteSpace {
+        self.ws_stack.last().cloned().unwrap_or(WhiteSpace::Normal)
+    }
 }
 
 fn filter_text_strikeout(s: &str) -> Option {
@@ -1186,16 +1244,12 @@ impl Renderer for SubRenderer {
         Ok(())
     }
 
-    fn start_pre(&mut self) {
-        self.pre_depth += 1;
+    fn push_ws(&mut self, ws: WhiteSpace) {
+        self.ws_stack.push(ws);
     }
 
-    fn end_pre(&mut self) {
-        if self.pre_depth > 0 {
-            self.pre_depth -= 1;
-        } else {
-            panic!("Attempt to end a preformatted block which wasn't opened.");
-        }
+    fn pop_ws(&mut self) {
+        self.ws_stack.pop();
     }
 
     fn end_block(&mut self) {
@@ -1204,7 +1258,7 @@ impl Renderer for SubRenderer {
 
     fn add_inline_text(&mut self, text: &str) -> crate::Result<()> {
         html_trace!("add_inline_text({}, {})", self.width, text);
-        if self.pre_depth == 0 && self.at_block_end && text.chars().all(char::is_whitespace) {
+        if !self.ws_mode().preserve_whitespace() && self.at_block_end && text.chars().all(char::is_whitespace) {
             // Ignore whitespace between blocks.
             return Ok(());
         }
@@ -1220,16 +1274,23 @@ impl Renderer for SubRenderer {
             }
         }
         let filtered_text = s.as_deref().unwrap_or(text);
-        if self.pre_depth == 0 {
-            get_wrapping_or_insert::(&mut self.wrapping, &self.options, self.width)
-                .add_text(filtered_text, &self.ann_stack)?;
-        } else {
-            let mut tag_first = self.ann_stack.clone();
-            let mut tag_cont = self.ann_stack.clone();
-            tag_first.push(self.decorator.decorate_preformat_first());
-            tag_cont.push(self.decorator.decorate_preformat_cont());
-            get_wrapping_or_insert::(&mut self.wrapping, &self.options, self.width)
-                .add_preformatted_text(filtered_text, &tag_first, &tag_cont)?;
+        let ws_mode = self.ws_mode();
+        let wrapping = get_wrapping_or_insert::(&mut self.wrapping, &self.options, self.width);
+        match ws_mode {
+            WhiteSpace::Normal => {
+                wrapping.add_text(filtered_text, &self.ann_stack)?;
+            }
+            WhiteSpace::Pre => {
+                let mut tag_first = self.ann_stack.clone();
+                let mut tag_cont = self.ann_stack.clone();
+                tag_first.push(self.decorator.decorate_preformat_first());
+                tag_cont.push(self.decorator.decorate_preformat_cont());
+                get_wrapping_or_insert::(&mut self.wrapping, &self.options, self.width)
+                    .add_preformatted_text(filtered_text, &tag_first, &tag_cont)?;
+            }
+            WhiteSpace::PreWrap => {
+                wrapping.add_text_prewrap(filtered_text, &self.ann_stack)?;
+            }
         }
         Ok(())
     }
diff --git a/src/tests.rs b/src/tests.rs
index 0092de3..04f5c11 100644
--- a/src/tests.rs
+++ b/src/tests.rs
@@ -2074,7 +2074,7 @@ foo
 
 #[cfg(feature = "css")]
 mod css_tests {
-    use super::{test_html_coloured, test_html_coloured_conf, test_html_css, test_html_style};
+    use super::{test_html_coloured, test_html_coloured_conf, test_html_conf, test_html_css, test_html_style};
 
     #[test]
     fn test_disp_none() {
@@ -2551,4 +2551,27 @@ Row│Three
             },
         );
     }
+
+    #[test]
+    fn test_pre_wrap() {
+        test_html_conf(
+            br#"

Hi + a + b + x longword +c d e +

"#, + r#"Hi + a + b + x +longword +c d e +"#, + 10, + |conf| { + conf.add_css(r#".prewrap { white-space: pre-wrap; }"#).unwrap() + } + ); + } } From ace8cac56228129cee6e564a3ee374a7fb8f4bf5 Mon Sep 17 00:00:00 2001 From: Chris Emerson Date: Sat, 12 Oct 2024 09:43:19 +0100 Subject: [PATCH 3/4] Cargo fmt. --- src/css.rs | 8 +++----- src/css/parser.rs | 4 +++- src/lib.rs | 15 +++++++++------ src/render/text_renderer.rs | 16 ++++++---------- src/tests.rs | 9 ++++++--- 5 files changed, 27 insertions(+), 25 deletions(-) diff --git a/src/css.rs b/src/css.rs index 1cac531..f2b39ad 100644 --- a/src/css.rs +++ b/src/css.rs @@ -257,7 +257,7 @@ impl Default for WithSpec { } #[derive(Debug, Copy, Clone, Default, PartialEq)] -pub (crate) enum WhiteSpace { +pub(crate) enum WhiteSpace { #[default] Normal, // NoWrap, @@ -271,8 +271,7 @@ impl WhiteSpace { pub fn preserve_whitespace(&self) -> bool { match self { WhiteSpace::Normal => false, - WhiteSpace::Pre | - WhiteSpace::PreWrap => true, + WhiteSpace::Pre | WhiteSpace::PreWrap => true, } } } @@ -384,8 +383,7 @@ fn styles_from_properties(decls: &[parser::Declaration]) -> Vec { style: Style::WhiteSpace(*value), importance: decl.important, }); - } - /* + } /* _ => { html_trace_quiet!("CSS: Unhandled property {:?}", decl); } diff --git a/src/css/parser.rs b/src/css/parser.rs index b77f99a..607c341 100644 --- a/src/css/parser.rs +++ b/src/css/parser.rs @@ -687,7 +687,9 @@ fn parse_display(value: &RawValue) -> Result Result>> { +fn parse_white_space( + value: &RawValue, +) -> Result>> { for tok in &value.tokens { if let Token::Ident(word) = tok { match word.deref() { diff --git a/src/lib.rs b/src/lib.rs index 926c7fa..267c5e6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -531,8 +531,7 @@ impl RenderNode { } Container(ref v) | Em(ref v) | Strong(ref v) | Strikeout(ref v) | Code(ref v) - | Block(ref v) | Div(ref v) | Dl(ref v) | Dt(ref v) | ListItem(ref v) - | Sup(ref v) => v + | Block(ref v) | Div(ref v) | Dl(ref v) | Dt(ref v) | ListItem(ref v) | Sup(ref v) => v .iter() .map(recurse) .fold(Default::default(), SizeEstimate::add), @@ -1332,7 +1331,12 @@ fn process_dom_node<'a, T: Write>( }), expanded_name!(html "pre") => pending(input, move |_, cs| { let mut computed = computed; - computed.white_space.maybe_update(false, css::StyleOrigin::Agent, Default::default(), WhiteSpace::Pre); + computed.white_space.maybe_update( + false, + css::StyleOrigin::Agent, + Default::default(), + WhiteSpace::Pre, + ); Ok(Some(RenderNode::new_styled(Block(cs), computed))) }), expanded_name!(html "br") => Finished(RenderNode::new_styled(Break, computed)), @@ -1508,9 +1512,8 @@ impl PushedStyleInfo { } if let Some(ws) = style.white_space.val() { match ws { - WhiteSpace::Normal => {}, - WhiteSpace::Pre | - WhiteSpace::PreWrap => { + WhiteSpace::Normal => {} + WhiteSpace::Pre | WhiteSpace::PreWrap => { render.push_ws(ws); result.white_space = true; } diff --git a/src/render/text_renderer.rs b/src/render/text_renderer.rs index 0944247..ef59cfe 100644 --- a/src/render/text_renderer.rs +++ b/src/render/text_renderer.rs @@ -570,11 +570,7 @@ impl WrappedBlock { Ok(()) } - fn add_text_prewrap( - &mut self, - text: &str, - tag: &T, - ) -> Result<(), Error> { + fn add_text_prewrap(&mut self, text: &str, tag: &T) -> Result<(), Error> { html_trace!("WrappedBlock::add_text_prewrap({}), {:?}", text, tag); for c in text.chars() { if c.is_whitespace() { @@ -591,10 +587,7 @@ impl WrappedBlock { if self.linelen >= self.width { self.flush_line(); } else { - self.line.push_char( - ' ', - tag - ); + self.line.push_char(' ', tag); self.linelen += 1; at_least_one_space = true; } @@ -1258,7 +1251,10 @@ impl Renderer for SubRenderer { fn add_inline_text(&mut self, text: &str) -> crate::Result<()> { html_trace!("add_inline_text({}, {})", self.width, text); - if !self.ws_mode().preserve_whitespace() && self.at_block_end && text.chars().all(char::is_whitespace) { + if !self.ws_mode().preserve_whitespace() + && self.at_block_end + && text.chars().all(char::is_whitespace) + { // Ignore whitespace between blocks. return Ok(()); } diff --git a/src/tests.rs b/src/tests.rs index 04f5c11..f93e6af 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -2074,7 +2074,9 @@ foo #[cfg(feature = "css")] mod css_tests { - use super::{test_html_coloured, test_html_coloured_conf, test_html_conf, test_html_css, test_html_style}; + use super::{ + test_html_coloured, test_html_coloured_conf, test_html_conf, test_html_css, test_html_style, + }; #[test] fn test_disp_none() { @@ -2570,8 +2572,9 @@ c d e "#, 10, |conf| { - conf.add_css(r#".prewrap { white-space: pre-wrap; }"#).unwrap() - } + conf.add_css(r#".prewrap { white-space: pre-wrap; }"#) + .unwrap() + }, ); } } From 15229ce308e17971bc94ee4b4172839b9fd608a1 Mon Sep 17 00:00:00 2001 From: Chris Emerson Date: Sat, 12 Oct 2024 09:53:56 +0100 Subject: [PATCH 4/4] Adjust #[cfg] etc. to make new
 work without CSS.

---
 src/css.rs                  | 146 +-------------------------------
 src/lib.rs                  | 160 +++++++++++++++++++++++++++++++++---
 src/render/mod.rs           |   2 +-
 src/render/text_renderer.rs |   2 +-
 4 files changed, 155 insertions(+), 155 deletions(-)

diff --git a/src/css.rs b/src/css.rs
index f2b39ad..5471fea 100644
--- a/src/css.rs
+++ b/src/css.rs
@@ -10,7 +10,8 @@ use crate::{
         Handle,
         NodeData::{self, Comment, Document, Element},
     },
-    tree_map_reduce, Colour, Result, TreeMapResult,
+    tree_map_reduce, Colour, ComputedStyle, Result, Specificity, StyleOrigin, TreeMapResult,
+    WhiteSpace,
 };
 
 use self::parser::Importance;
@@ -25,43 +26,6 @@ pub(crate) enum SelectorComponent {
     CombDescendant,
 }
 
-#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
-pub(crate) struct Specificity {
-    inline: bool,
-    id: u16,
-    class: u16,
-    typ: u16,
-}
-
-impl Specificity {
-    fn inline() -> Self {
-        Specificity {
-            inline: true,
-            id: 0,
-            class: 0,
-            typ: 0,
-        }
-    }
-}
-
-impl PartialOrd for Specificity {
-    fn partial_cmp(&self, other: &Self) -> Option {
-        match self.inline.partial_cmp(&other.inline) {
-            Some(core::cmp::Ordering::Equal) => {}
-            ord => return ord,
-        }
-        match self.id.partial_cmp(&other.id) {
-            Some(core::cmp::Ordering::Equal) => {}
-            ord => return ord,
-        }
-        match self.class.partial_cmp(&other.class) {
-            Some(core::cmp::Ordering::Equal) => {}
-            ord => return ord,
-        }
-        self.typ.partial_cmp(&other.typ)
-    }
-}
-
 #[derive(Debug, Clone, PartialEq)]
 pub(crate) struct Selector {
     // List of components, right first so we match from the leaf.
@@ -185,110 +149,6 @@ pub(crate) struct StyleDecl {
     importance: Importance,
 }
 
-#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, PartialOrd)]
-pub(crate) enum StyleOrigin {
-    #[default]
-    None,
-    Agent,
-    User,
-    Author,
-}
-
-#[derive(Clone, Copy, Debug)]
-pub(crate) struct WithSpec {
-    val: Option,
-    origin: StyleOrigin,
-    specificity: Specificity,
-    important: bool,
-}
-impl WithSpec {
-    pub(crate) fn maybe_update(
-        &mut self,
-        important: bool,
-        origin: StyleOrigin,
-        specificity: Specificity,
-        val: T,
-    ) {
-        if self.val.is_some() {
-            // We already have a value, so need to check.
-            if self.important && !important {
-                // important takes priority over not important.
-                return;
-            }
-            // importance is the same.  Next is checking the origin.
-            {
-                use StyleOrigin::*;
-                match (self.origin, origin) {
-                    (Agent, Agent) | (User, User) | (Author, Author) => {
-                        // They're the same so continue the comparison
-                    }
-                    (mine, theirs) => {
-                        if (important && theirs > mine) || (!important && mine > theirs) {
-                            return;
-                        }
-                    }
-                }
-            }
-            // We're now from the same origin an importance
-            if specificity < self.specificity {
-                return;
-            }
-        }
-        self.val = Some(val);
-        self.origin = origin;
-        self.specificity = specificity;
-        self.important = important;
-    }
-
-    pub fn val(&self) -> Option {
-        self.val
-    }
-}
-
-impl Default for WithSpec {
-    fn default() -> Self {
-        WithSpec {
-            val: None,
-            origin: StyleOrigin::None,
-            specificity: Default::default(),
-            important: false,
-        }
-    }
-}
-
-#[derive(Debug, Copy, Clone, Default, PartialEq)]
-pub(crate) enum WhiteSpace {
-    #[default]
-    Normal,
-    // NoWrap,
-    Pre,
-    PreWrap,
-    // PreLine,
-    // BreakSpaces,
-}
-
-impl WhiteSpace {
-    pub fn preserve_whitespace(&self) -> bool {
-        match self {
-            WhiteSpace::Normal => false,
-            WhiteSpace::Pre | WhiteSpace::PreWrap => true,
-        }
-    }
-}
-
-#[derive(Debug, Copy, Clone, Default)]
-pub(crate) struct ComputedStyle {
-    /// The computed foreground colour, if any
-    pub(crate) colour: WithSpec,
-    /// The specificity for colour
-    /// The computed background colour, if any
-    pub(crate) bg_colour: WithSpec,
-    /// If set, indicates whether `display: none` or something equivalent applies
-    pub(crate) display_none: WithSpec,
-    /// The CSS white-space property
-    pub(crate) white_space: WithSpec,
-}
-
 #[derive(Debug, Clone)]
 struct Ruleset {
     selector: Selector,
@@ -643,7 +503,7 @@ pub(crate) fn dom_to_stylesheet(handle: Handle, err_out: &mut T) -> Re
 
 #[cfg(test)]
 mod tests {
-    use crate::css::Specificity;
+    use crate::Specificity;
 
     use super::parser::parse_selector;
 
diff --git a/src/lib.rs b/src/lib.rs
index 267c5e6..48ca823 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -64,14 +64,6 @@ mod macros;
 pub mod css;
 pub mod render;
 
-#[cfg(feature = "css")]
-use css::ComputedStyle;
-
-#[cfg(not(feature = "css"))]
-#[derive(Copy, Clone, Debug, Default)]
-struct ComputedStyle;
-
-use css::WhiteSpace;
 use render::text_renderer::{
     RenderLine, RenderOptions, RichAnnotation, SubRenderer, TaggedLine, TextRenderer,
 };
@@ -97,6 +89,27 @@ use std::io;
 use std::io::Write;
 use std::iter::{once, repeat};
 
+#[derive(Debug, Copy, Clone, Default, PartialEq)]
+pub(crate) enum WhiteSpace {
+    #[default]
+    Normal,
+    // NoWrap,
+    Pre,
+    #[allow(unused)]
+    PreWrap,
+    // PreLine,
+    // BreakSpaces,
+}
+
+impl WhiteSpace {
+    pub fn preserve_whitespace(&self) -> bool {
+        match self {
+            WhiteSpace::Normal => false,
+            WhiteSpace::Pre | WhiteSpace::PreWrap => true,
+        }
+    }
+}
+
 /// An RGB colour value
 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
 pub struct Colour {
@@ -108,6 +121,132 @@ pub struct Colour {
     pub b: u8,
 }
 
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, PartialOrd)]
+pub(crate) enum StyleOrigin {
+    #[default]
+    None,
+    Agent,
+    #[allow(unused)]
+    User,
+    #[allow(unused)]
+    Author,
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
+pub(crate) struct Specificity {
+    inline: bool,
+    id: u16,
+    class: u16,
+    typ: u16,
+}
+
+impl Specificity {
+    #[cfg(feature = "css")]
+    fn inline() -> Self {
+        Specificity {
+            inline: true,
+            id: 0,
+            class: 0,
+            typ: 0,
+        }
+    }
+}
+
+impl PartialOrd for Specificity {
+    fn partial_cmp(&self, other: &Self) -> Option {
+        match self.inline.partial_cmp(&other.inline) {
+            Some(core::cmp::Ordering::Equal) => {}
+            ord => return ord,
+        }
+        match self.id.partial_cmp(&other.id) {
+            Some(core::cmp::Ordering::Equal) => {}
+            ord => return ord,
+        }
+        match self.class.partial_cmp(&other.class) {
+            Some(core::cmp::Ordering::Equal) => {}
+            ord => return ord,
+        }
+        self.typ.partial_cmp(&other.typ)
+    }
+}
+
+#[derive(Clone, Copy, Debug)]
+pub(crate) struct WithSpec {
+    val: Option,
+    origin: StyleOrigin,
+    specificity: Specificity,
+    important: bool,
+}
+impl WithSpec {
+    pub(crate) fn maybe_update(
+        &mut self,
+        important: bool,
+        origin: StyleOrigin,
+        specificity: Specificity,
+        val: T,
+    ) {
+        if self.val.is_some() {
+            // We already have a value, so need to check.
+            if self.important && !important {
+                // important takes priority over not important.
+                return;
+            }
+            // importance is the same.  Next is checking the origin.
+            {
+                use StyleOrigin::*;
+                match (self.origin, origin) {
+                    (Agent, Agent) | (User, User) | (Author, Author) => {
+                        // They're the same so continue the comparison
+                    }
+                    (mine, theirs) => {
+                        if (important && theirs > mine) || (!important && mine > theirs) {
+                            return;
+                        }
+                    }
+                }
+            }
+            // We're now from the same origin an importance
+            if specificity < self.specificity {
+                return;
+            }
+        }
+        self.val = Some(val);
+        self.origin = origin;
+        self.specificity = specificity;
+        self.important = important;
+    }
+
+    pub fn val(&self) -> Option {
+        self.val
+    }
+}
+
+impl Default for WithSpec {
+    fn default() -> Self {
+        WithSpec {
+            val: None,
+            origin: StyleOrigin::None,
+            specificity: Default::default(),
+            important: false,
+        }
+    }
+}
+
+#[derive(Debug, Copy, Clone, Default)]
+pub(crate) struct ComputedStyle {
+    #[cfg(feature = "css")]
+    /// The computed foreground colour, if any
+    pub(crate) colour: WithSpec,
+    #[cfg(feature = "css")]
+    /// The computed background colour, if any
+    pub(crate) bg_colour: WithSpec,
+    #[cfg(feature = "css")]
+    /// If set, indicates whether `display: none` or something equivalent applies
+    pub(crate) display_none: WithSpec,
+    /// The CSS white-space property
+    pub(crate) white_space: WithSpec,
+}
+
 /// Errors from reading or rendering HTML
 #[derive(thiserror::Error, Debug)]
 #[non_exhaustive]
@@ -1333,7 +1472,7 @@ fn process_dom_node<'a, T: Write>(
                     let mut computed = computed;
                     computed.white_space.maybe_update(
                         false,
-                        css::StyleOrigin::Agent,
+                        StyleOrigin::Agent,
                         Default::default(),
                         WhiteSpace::Pre,
                     );
@@ -1500,12 +1639,13 @@ impl PushedStyleInfo {
     fn apply(render: &mut TextRenderer, style: &ComputedStyle) -> Self {
         #[allow(unused_mut)]
         let mut result: PushedStyleInfo = Default::default();
-        #[cfg(feature = "css")]
         {
+            #[cfg(feature = "css")]
             if let Some(col) = style.colour.val() {
                 render.push_colour(col);
                 result.colour = true;
             }
+            #[cfg(feature = "css")]
             if let Some(col) = style.bg_colour.val() {
                 render.push_bgcolour(col);
                 result.bgcolour = true;
diff --git a/src/render/mod.rs b/src/render/mod.rs
index 3d1fb76..5437920 100644
--- a/src/render/mod.rs
+++ b/src/render/mod.rs
@@ -1,9 +1,9 @@
 //! Module containing the `Renderer` interface for constructing a
 //! particular text output.
 
-use crate::css::WhiteSpace;
 use crate::Colour;
 use crate::Error;
+use crate::WhiteSpace;
 
 pub(crate) mod text_renderer;
 
diff --git a/src/render/text_renderer.rs b/src/render/text_renderer.rs
index ef59cfe..5b9a986 100644
--- a/src/render/text_renderer.rs
+++ b/src/render/text_renderer.rs
@@ -3,9 +3,9 @@
 //! This module implements helpers and concrete types for rendering from HTML
 //! into different text formats.
 
-use crate::css::WhiteSpace;
 use crate::Colour;
 use crate::Error;
+use crate::WhiteSpace;
 
 use super::Renderer;
 use std::cell::Cell;