From 3d941413da0d5663585e3945145b8763f91d4d3d Mon Sep 17 00:00:00 2001 From: Deokjin Kim Date: Thu, 16 Jan 2014 20:31:24 +0900 Subject: [PATCH] Implement white-space property(pre) In order to support line-break by new-line character, Fist, calculate new-line character's position.(fn flush_clump_to_list) Second, split box(which includes new-line character) and do line-break.(fn scan_for_lines) --- src/components/gfx/text/util.rs | 29 +++++++++---- src/components/main/layout/box_.rs | 50 +++++++++++++++++++++- src/components/main/layout/inline.rs | 37 ++++++++++++++++- src/components/main/layout/text.rs | 55 ++++++++++++++++++------- src/components/style/properties.rs.mako | 2 + 5 files changed, 147 insertions(+), 26 deletions(-) diff --git a/src/components/gfx/text/util.rs b/src/components/gfx/text/util.rs index 924d6273ea0..e80596bde87 100644 --- a/src/components/gfx/text/util.rs +++ b/src/components/gfx/text/util.rs @@ -20,10 +20,11 @@ enum CompressionMode { // * Issue #114: record skipped and kept chars for mapping original to new text // // * Untracked: various edge cases for bidi, CJK, etc. -pub fn transform_text(text: &str, mode: CompressionMode, incoming_whitespace: bool) -> (~str, bool) { +pub fn transform_text(text: &str, mode: CompressionMode, incoming_whitespace: bool, new_line_pos: &mut ~[uint]) -> (~str, bool) { let mut out_str: ~str = ~""; let out_whitespace = match mode { CompressNone | DiscardNewline => { + let mut new_line_index = 0; for ch in text.chars() { if is_discardable_char(ch, mode) { // TODO: record skipped char @@ -31,8 +32,17 @@ pub fn transform_text(text: &str, mode: CompressionMode, incoming_whitespace: bo // TODO: record kept char if ch == '\t' { // TODO: set "has tab" flag + } else if ch == '\n' { + // Save new-line's position for line-break + // This value is relative(not absolute) + new_line_pos.push(new_line_index); + new_line_index = 0; } - out_str.push_char(ch); + + if ch != '\n' { + new_line_index += 1; + } + out_str.push_char(ch); } } text.len() > 0 && is_in_whitespace(text.char_at_reverse(0), mode) @@ -139,7 +149,8 @@ fn test_transform_compress_none() { let mode = CompressNone; for i in range(0, test_strs.len()) { - let (trimmed_str, _out) = transform_text(test_strs[i], mode, true); + let mut new_line_pos = ~[]; + let (trimmed_str, _out) = transform_text(test_strs[i], mode, true, &mut new_line_pos); assert_eq!(&trimmed_str, &test_strs[i]) } } @@ -167,7 +178,8 @@ fn test_transform_discard_newline() { let mode = DiscardNewline; for i in range(0, test_strs.len()) { - let (trimmed_str, _out) = transform_text(test_strs[i], mode, true); + let mut new_line_pos = ~[]; + let (trimmed_str, _out) = transform_text(test_strs[i], mode, true, &mut new_line_pos); assert_eq!(&trimmed_str, &oracle_strs[i]) } } @@ -195,7 +207,8 @@ fn test_transform_compress_whitespace() { let mode = CompressWhitespace; for i in range(0, test_strs.len()) { - let (trimmed_str, _out) = transform_text(test_strs[i], mode, true); + let mut new_line_pos = ~[]; + let (trimmed_str, _out) = transform_text(test_strs[i], mode, true, &mut new_line_pos); assert_eq!(&trimmed_str, &oracle_strs[i]) } } @@ -222,7 +235,8 @@ fn test_transform_compress_whitespace_newline() { let mode = CompressWhitespaceNewline; for i in range(0, test_strs.len()) { - let (trimmed_str, _out) = transform_text(test_strs[i], mode, true); + let mut new_line_pos = ~[]; + let (trimmed_str, _out) = transform_text(test_strs[i], mode, true, &mut new_line_pos); assert_eq!(&trimmed_str, &oracle_strs[i]) } } @@ -252,7 +266,8 @@ fn test_transform_compress_whitespace_newline_no_incoming() { let mode = CompressWhitespaceNewline; for i in range(0, test_strs.len()) { - let (trimmed_str, _out) = transform_text(test_strs[i], mode, false); + let mut new_line_pos = ~[]; + let (trimmed_str, _out) = transform_text(test_strs[i], mode, false, &mut new_line_pos); assert_eq!(&trimmed_str, &oracle_strs[i]) } } diff --git a/src/components/main/layout/box_.rs b/src/components/main/layout/box_.rs index d7e2de5763c..297f70e5adf 100644 --- a/src/components/main/layout/box_.rs +++ b/src/components/main/layout/box_.rs @@ -29,7 +29,7 @@ use std::num::Zero; use style::{ComputedValues, TElement, TNode, cascade}; use style::computed_values::{LengthOrPercentage, LengthOrPercentageOrAuto, overflow, LPA_Auto}; use style::computed_values::{border_style, clear, font_family, line_height}; -use style::computed_values::{text_align, text_decoration, vertical_align, visibility}; +use style::computed_values::{text_align, text_decoration, vertical_align, visibility, white_space}; use css::node_style::StyledNode; use layout::context::LayoutContext; @@ -91,6 +91,9 @@ pub struct Box { /// Inline data inline_info: RefCell>, + + /// New-line chracter(\n)'s positions(relative, not absolute) + new_line_pos: ~[uint], } /// Info specific to the kind of box. Keep this enum small. @@ -330,6 +333,7 @@ impl Box { specific: specific, position_offsets: RefCell::new(Zero::zero()), inline_info: RefCell::new(None), + new_line_pos: ~[], } } @@ -419,6 +423,7 @@ impl Box { specific: specific, position_offsets: RefCell::new(Zero::zero()), inline_info: self.inline_info.clone(), + new_line_pos: self.new_line_pos.clone(), } } @@ -578,6 +583,10 @@ impl Box { self.style().Box.vertical_align } + pub fn white_space(&self) -> white_space::T { + self.style().Text.white_space + } + /// Returns the text decoration of this box, according to the style of the nearest ancestor /// element. /// @@ -1023,6 +1032,45 @@ impl Box { } } + /// Split box which includes new-line character + pub fn split_by_new_line(&self) -> SplitBoxResult { + match self.specific { + GenericBox | IframeBox(_) | ImageBox(_) => CannotSplit, + UnscannedTextBox(_) => fail!("Unscanned text boxes should have been scanned by now!"), + ScannedTextBox(ref text_box_info) => { + let mut new_line_pos = self.new_line_pos.clone(); + let cur_new_line_pos = new_line_pos.shift(); + + let left_range = Range::new(text_box_info.range.begin(), cur_new_line_pos); + let right_range = Range::new(text_box_info.range.begin() + cur_new_line_pos + 1, text_box_info.range.length() - (cur_new_line_pos + 1)); + + // Left box is for left text of first founded new-line character. + let left_box = if left_range.length() > 0 { + let new_text_box_info = ScannedTextBoxInfo::new(text_box_info.run.clone(), left_range); + let new_metrics = new_text_box_info.run.get().metrics_for_range(&left_range); + let mut new_box = self.transform(new_metrics.bounding_box.size, ScannedTextBox(new_text_box_info)); + new_box.new_line_pos = ~[]; + Some(new_box) + } else { + None + }; + + // Right box is for right text of first founded new-line character. + let right_box = if right_range.length() > 0 { + let new_text_box_info = ScannedTextBoxInfo::new(text_box_info.run.clone(), right_range); + let new_metrics = new_text_box_info.run.get().metrics_for_range(&right_range); + let mut new_box = self.transform(new_metrics.bounding_box.size, ScannedTextBox(new_text_box_info)); + new_box.new_line_pos = new_line_pos; + Some(new_box) + } else { + None + }; + + SplitDidFit(left_box, right_box) + } + } + } + /// Attempts to split this box so that its width is no more than `max_width`. pub fn split_to_width(&self, max_width: Au, starts_line: bool) -> SplitBoxResult { match self.specific { diff --git a/src/components/main/layout/inline.rs b/src/components/main/layout/inline.rs index 552ea88de6e..d3328510460 100644 --- a/src/components/main/layout/inline.rs +++ b/src/components/main/layout/inline.rs @@ -22,7 +22,7 @@ use servo_util::range::Range; use std::cell::RefCell; use std::u16; use std::util; -use style::computed_values::{text_align, vertical_align}; +use style::computed_values::{text_align, vertical_align, white_space}; /// Lineboxes are represented as offsets into the child list, rather than /// as an object that "owns" boxes. Choosing a different set of line @@ -116,7 +116,11 @@ impl LineboxScanner { box_ }; - let box_was_appended = self.try_append_to_line(cur_box, flow); + let box_was_appended = match cur_box.white_space() { + white_space::normal => self.try_append_to_line(cur_box, flow), + white_space::pre => self.try_append_to_line_by_new_line(cur_box), + }; + if !box_was_appended { debug!("LineboxScanner: Box wasn't appended, because line {:u} was full.", self.lines.len()); @@ -306,6 +310,35 @@ impl LineboxScanner { false } + fn try_append_to_line_by_new_line(&mut self, in_box: Box) -> bool { + if in_box.new_line_pos.len() == 0 { + // In case of box does not include new-line character + self.push_box_to_line(in_box); + true + } else { + // In case of box includes new-line character + match in_box.split_by_new_line() { + SplitDidFit(left, right) => { + match (left, right) { + (Some(left_box), Some(right_box)) => { + self.push_box_to_line(left_box); + self.work_list.push_front(right_box); + } + (Some(left_box), None) => { + self.push_box_to_line(left_box); + } + (None, Some(right_box)) => { + self.work_list.push_front(right_box); + } + (None, None) => error!("LineboxScanner: This split case makes no sense!"), + } + } + _ => {} + } + false + } + } + /// Tries to append the given box to the line, splitting it if necessary. Returns false only if /// we should break the line. fn try_append_to_line(&mut self, in_box: Box, flow: &mut InlineFlow) -> bool { diff --git a/src/components/main/layout/text.rs b/src/components/main/layout/text.rs index 72695c691b2..a43a0077e03 100644 --- a/src/components/main/layout/text.rs +++ b/src/components/main/layout/text.rs @@ -10,9 +10,10 @@ use layout::flow::Flow; use extra::arc::Arc; use gfx::text::text_run::TextRun; -use gfx::text::util::{CompressWhitespaceNewline, transform_text}; +use gfx::text::util::{CompressWhitespaceNewline, transform_text, CompressNone}; use servo_util::range::Range; use std::vec; +use style::computed_values::white_space; /// A stack-allocated object for scanning an inline flow into `TextRun`-containing `TextBox`es. pub struct TextRunScanner { @@ -111,11 +112,18 @@ impl TextRunScanner { let decoration = old_box.text_decoration(); // TODO(#115): Use the actual CSS `white-space` property of the relevant style. - let compression = CompressWhitespaceNewline; + let compression = match old_box.white_space() { + white_space::normal => CompressWhitespaceNewline, + white_space::pre => CompressNone, + }; + + let mut new_line_pos = ~[]; let (transformed_text, whitespace) = transform_text(*text, compression, - last_whitespace); + last_whitespace, + &mut new_line_pos); + new_whitespace = whitespace; if transformed_text.len() > 0 { @@ -131,14 +139,32 @@ impl TextRunScanner { let range = Range::new(0, run.char_len()); let new_metrics = run.metrics_for_range(&range); let new_text_box_info = ScannedTextBoxInfo::new(Arc::new(run), range); - let new_box = old_box.transform(new_metrics.bounding_box.size, + let mut new_box = old_box.transform(new_metrics.bounding_box.size, ScannedTextBox(new_text_box_info)); + new_box.new_line_pos = new_line_pos; out_boxes.push(new_box) } }, (false, true) => { + // TODO(#177): Text run creation must account for the renderability of text by + // font group fonts. This is probably achieved by creating the font group above + // and then letting `FontGroup` decide which `Font` to stick into the text run. + let in_box = &in_boxes[self.clump.begin()]; + let font_style = in_box.font_style(); + let fontgroup = ctx.font_ctx.get_resolved_font_for_style(&font_style); + let decoration = in_box.text_decoration(); + // TODO(#115): Use the actual CSS `white-space` property of the relevant style. - let compression = CompressWhitespaceNewline; + let compression = match in_box.white_space() { + white_space::normal => CompressWhitespaceNewline, + white_space::pre => CompressNone, + }; + + struct NewLinePositions { + new_line_pos: ~[uint], + } + + let mut new_line_positions: ~[NewLinePositions] = ~[]; // First, transform/compress text of all the nodes. let mut last_whitespace_in_clump = new_whitespace; @@ -152,9 +178,14 @@ impl TextRunScanner { _ => fail!("Expected an unscanned text box!"), }; + let mut new_line_pos = ~[]; + let (new_str, new_whitespace) = transform_text(*in_box, compression, - last_whitespace_in_clump); + last_whitespace_in_clump, + &mut new_line_pos); + new_line_positions.push(NewLinePositions { new_line_pos: new_line_pos }); + last_whitespace_in_clump = new_whitespace; new_str }); @@ -173,15 +204,6 @@ impl TextRunScanner { } // Now create the run. - // - // TODO(#177): Text run creation must account for the renderability of text by - // font group fonts. This is probably achieved by creating the font group above - // and then letting `FontGroup` decide which `Font` to stick into the text run. - let in_box = &in_boxes[self.clump.begin()]; - let font_style = in_box.font_style(); - let fontgroup = ctx.font_ctx.get_resolved_font_for_style(&font_style); - let decoration = in_box.text_decoration(); - // TextRuns contain a cycle which is usually resolved by the teardown // sequence. If no clump takes ownership, however, it will leak. let clump = self.clump; @@ -208,8 +230,9 @@ impl TextRunScanner { let new_text_box_info = ScannedTextBoxInfo::new(run.get_ref().clone(), range); let new_metrics = new_text_box_info.run.get().metrics_for_range(&range); - let new_box = in_boxes[i].transform(new_metrics.bounding_box.size, + let mut new_box = in_boxes[i].transform(new_metrics.bounding_box.size, ScannedTextBox(new_text_box_info)); + new_box.new_line_pos = new_line_positions[i].new_line_pos.clone(); out_boxes.push(new_box) } } diff --git a/src/components/style/properties.rs.mako b/src/components/style/properties.rs.mako index 553464659cd..602a0a695c4 100644 --- a/src/components/style/properties.rs.mako +++ b/src/components/style/properties.rs.mako @@ -709,6 +709,8 @@ pub mod longhands { } + ${single_keyword("white-space", "normal pre", inherited=True)} + // CSS 2.1, Section 17 - Tables // CSS 2.1, Section 18 - User interface