diff options
author | bors-servo <release+servo@mozilla.com> | 2013-08-27 14:42:40 -0700 |
---|---|---|
committer | bors-servo <release+servo@mozilla.com> | 2013-08-27 14:42:40 -0700 |
commit | 4e68c1a4f76a50fe0b721d3fddcd8eb15c1bfae0 (patch) | |
tree | f176ad9725739c310590e2a92011f0072ec784bc /src | |
parent | 79956abb384177fb5035e5d1f00f1ac152e081e5 (diff) | |
parent | 729ac0c79609de1a1ac24d0c7b99c5c3c0b47f9d (diff) | |
download | servo-4e68c1a4f76a50fe0b721d3fddcd8eb15c1bfae0.tar.gz servo-4e68c1a4f76a50fe0b721d3fddcd8eb15c1bfae0.zip |
auto merge of #764 : june0cho/servo/verticalalign, r=metajack
This is a modification of the previous PR #741 to support 'vertical-align' and 'line-height'. Added test cases for vertical-align, line-height, and mixed.
In the last commit, after applying 'vertical-align' property, the current line-box's height and following line-boxes' heights are being updated. This is because line-box's height was already assigned when the line-box is created.
But there may be a better way, though I'm not sure. In Gecko, line-boxes' heights seem to be assigned at one time after 'vertical-align'. @metajack
Diffstat (limited to 'src')
-rw-r--r-- | src/components/main/layout/box.rs | 6 | ||||
-rw-r--r-- | src/components/main/layout/inline.rs | 255 | ||||
-rw-r--r-- | src/test/html/lineheight-simple.css | 2 | ||||
-rw-r--r-- | src/test/html/lineheight-simple.html | 2 | ||||
-rw-r--r-- | src/test/html/test-lineheight-verticalalign.html | 13 | ||||
-rw-r--r-- | src/test/html/vertical_align_simple.html | 24 |
6 files changed, 249 insertions, 53 deletions
diff --git a/src/components/main/layout/box.rs b/src/components/main/layout/box.rs index 2d30a2d584b..844e37954f5 100644 --- a/src/components/main/layout/box.rs +++ b/src/components/main/layout/box.rs @@ -30,7 +30,7 @@ use newcss::units::{Cursive, Fantasy, Monospace, SansSerif, Serif}; use newcss::values::{CSSClearNone, CSSClearLeft, CSSClearRight, CSSClearBoth}; use newcss::values::{CSSFontFamilyFamilyName, CSSFontFamilyGenericFamily}; use newcss::values::{CSSFontSizeLength, CSSFontStyleItalic, CSSFontStyleNormal}; -use newcss::values::{CSSFontStyleOblique, CSSTextAlign, CSSTextDecoration, CSSLineHeight}; +use newcss::values::{CSSFontStyleOblique, CSSTextAlign, CSSTextDecoration, CSSLineHeight, CSSVerticalAlign}; use newcss::values::{CSSTextDecorationNone, CSSFloatNone, CSSPositionStatic}; use newcss::values::{CSSDisplayInlineBlock, CSSDisplayInlineTable}; use script::dom::node::{AbstractNode, LayoutView}; @@ -817,6 +817,10 @@ impl RenderBox { self.nearest_ancestor_element().style().line_height() } + pub fn vertical_align(&self) -> CSSVerticalAlign { + self.nearest_ancestor_element().style().vertical_align() + } + /// Returns the text decoration of the computed style of the nearest `Element` node pub fn text_decoration(&self) -> CSSTextDecoration { /// Computes the propagated value of text-decoration, as specified in CSS 2.1 § 16.3.1 diff --git a/src/components/main/layout/inline.rs b/src/components/main/layout/inline.rs index 08119ee308c..418c5fce73f 100644 --- a/src/components/main/layout/inline.rs +++ b/src/components/main/layout/inline.rs @@ -2,6 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +use css::node_style::StyledNode; use std::cell::Cell; use layout::box::{CannotSplit, GenericRenderBoxClass, ImageRenderBoxClass, RenderBox}; use layout::box::{SplitDidFit, SplitDidNotFit, TextRenderBoxClass}; @@ -17,9 +18,13 @@ use std::util; use geom::{Point2D, Rect, Size2D}; use gfx::display_list::DisplayList; use gfx::geometry::Au; -use newcss::values::{CSSTextAlignLeft, CSSTextAlignCenter, CSSTextAlignRight, CSSTextAlignJustify}; use newcss::units::{Em, Px}; +use newcss::values::{CSSFontSizeLength}; +use newcss::values::{CSSTextAlignLeft, CSSTextAlignCenter, CSSTextAlignRight, CSSTextAlignJustify}; use newcss::values::{CSSLineHeightNormal, CSSLineHeightNumber, CSSLineHeightLength, CSSLineHeightPercentage}; +use newcss::values::{CSSVerticalAlignBaseline, CSSVerticalAlignMiddle, CSSVerticalAlignSub, CSSVerticalAlignSuper, + CSSVerticalAlignTextTop, CSSVerticalAlignTextBottom, CSSVerticalAlignTop, CSSVerticalAlignBottom, + CSSVerticalAlignLength, CSSVerticalAlignPercentage}; use servo_util::range::Range; use servo_util::tree::TreeNodeRef; use extra::container::Deque; @@ -164,6 +169,16 @@ impl LineboxScanner { self.reset_linebox(); } + fn calculate_line_height(&self, box: RenderBox, font_size: Au) -> Au { + match box.line_height() { + CSSLineHeightNormal => font_size.scale_by(1.14f), + CSSLineHeightNumber(l) => font_size.scale_by(l), + CSSLineHeightLength(Em(l)) => font_size.scale_by(l), + CSSLineHeightLength(Px(l)) => Au::from_frac_px(l), + CSSLineHeightPercentage(p) => font_size.scale_by(p / 100.0f) + } + } + fn box_height(&self, box: RenderBox) -> Au { match box { ImageRenderBoxClass(image_box) => { @@ -180,13 +195,7 @@ impl LineboxScanner { // Compute the height based on the line-height and font size let text_bounds = run.metrics_for_range(range).bounding_box; let em_size = text_bounds.size.height; - let line_height = match box.line_height() { - CSSLineHeightNormal => em_size.scale_by(1.14f), - CSSLineHeightNumber(l) => em_size.scale_by(l), - CSSLineHeightLength(Em(l)) => em_size.scale_by(l), - CSSLineHeightLength(Px(l)) => Au::from_frac_px(l), - CSSLineHeightPercentage(p) => em_size.scale_by(p / 100.0f) - }; + let line_height = self.calculate_line_height(box, em_size); line_height } @@ -600,8 +609,10 @@ impl InlineFlowData { let mut scanner = LineboxScanner::new(scanner_floats); scanner.scan_for_lines(self); + let mut line_height_offset = Au(0); + // Now, go through each line and lay out the boxes inside - for line in self.lines.iter() { + for line in self.lines.mut_iter() { // We need to distribute extra width based on text-align. let mut slack_width = line.green_zone.width - line.bounds.size.width; if slack_width < Au(0) { @@ -654,80 +665,224 @@ impl InlineFlowData { } }; + // Set the top y position of the current linebox. + // `line_height_offset` is updated at the end of the previous loop. + line.bounds.origin.y = line.bounds.origin.y + line_height_offset; + + // Calculate the distance from baseline to the top of the linebox. + let mut topmost = Au(0); + // Calculate the distance from baseline to the bottom of the linebox. + let mut bottommost = Au(0); + + // Calculate the biggest height among boxes with 'top' value. + let mut biggest_top = Au(0); + // Calculate the biggest height among boxes with 'bottom' value. + let mut biggest_bottom = Au(0); - // Get the baseline offset, assuming that the tallest text box will determine - // the baseline. - let mut baseline_offset = Au(0); - let mut max_height = Au(0); for box_i in line.range.eachi() { let cur_box = self.boxes[box_i]; - match cur_box { + let (top_from_base, bottom_from_base, ascent) = match cur_box { ImageRenderBoxClass(image_box) => { let size = image_box.image.get_size(); - let height = Au::from_px(size.unwrap_or_default(Size2D(0, 0)).height); + let mut height = Au::from_px(size.unwrap_or_default(Size2D(0, 0)).height); + + // TODO: margin, border, padding's top and bottom should be calculated in advance, + // since baseline of image is bottom margin edge. + let mut top = Au(0); + let mut bottom = Au(0); + do cur_box.with_model |model| { + top = model.border.top + model.padding.top + model.margin.top; + bottom = model.border.bottom + model.padding.bottom + model.margin.bottom; + } + let noncontent_height = top + bottom; + height = height + noncontent_height; image_box.base.position.size.height = height; + image_box.base.position.translate(&Point2D(Au(0), -height)); - image_box.base.position.translate(&Point2D(Au(0), -height)) - } + let ascent = height + bottom; + (height, Au(0), ascent) + }, TextRenderBoxClass(text_box) => { - let range = &text_box.range; let run = &text_box.run; // Compute the height based on the line-height and font size let text_bounds = run.metrics_for_range(range).bounding_box; let em_size = text_bounds.size.height; - let line_height = match cur_box.line_height() { - CSSLineHeightNormal => em_size.scale_by(1.14f), - CSSLineHeightNumber(l) => em_size.scale_by(l), - CSSLineHeightLength(Em(l)) => em_size.scale_by(l), - CSSLineHeightLength(Px(l)) => Au::from_frac_px(l), - CSSLineHeightPercentage(p) => em_size.scale_by(p / 100.0f) - }; + let line_height = scanner.calculate_line_height(cur_box, em_size); - // If this is the current tallest box then use it for baseline - // calculations. - // TODO: this will need to take into account type of line-height - // and the vertical-align value. - if line_height > max_height { - max_height = line_height; - let linebox_height = line.bounds.size.height; - // Offset from the top of the linebox is 1/2 of the leading + ascent - baseline_offset = text_box.run.font.metrics.ascent + - (linebox_height - em_size).scale_by(0.5f); - } - text_bounds.translate(&Point2D(text_box.base.position.origin.x, Au(0))) - } + // Find the top and bottom of the content area. + // Those are used in text-top and text-bottom value of 'vertex-align' + let text_ascent = text_box.run.font.metrics.ascent; + + // Offset from the top of the box is 1/2 of the leading + ascent + let text_offset = text_ascent + (line_height - em_size).scale_by(0.5f); + text_bounds.translate(&Point2D(text_box.base.position.origin.x, Au(0))); + + (text_offset, line_height - text_offset, text_ascent) + }, GenericRenderBoxClass(generic_box) => { - generic_box.position - } + (generic_box.position.size.height, Au(0), generic_box.position.size.height) + }, // FIXME(pcwalton): This isn't very type safe! _ => { fail!(fmt!("Tried to assign height to unknown Box variant: %s", cur_box.debug_str())) } }; + let mut top_from_base = top_from_base; + let mut bottom_from_base = bottom_from_base; + + // To calculate text-top and text-bottom value of 'vertex-align', + // we should find the top and bottom of the content area of parent box. + // The content area is defined in "http://www.w3.org/TR/CSS2/visudet.html#inline-non-replaced". + // TODO: We should extract em-box info from font size of parent + // and calcuate the distances from baseline to the top and the bottom of parent's content area. + + // It should calculate the distance from baseline to the top of parent's content area. + // But, it is assumed now as font size of parent. + let mut parent_text_top = Au(0); + // It should calculate the distance from baseline to the bottom of parent's content area. + // But, it is assumed now as 0. + let parent_text_bottom = Au(0); + do cur_box.with_mut_base |base| { + // Get parent node + let parent = base.node.parent_node().map_default(base.node, |parent| *parent); + // TODO: When the calculation of font-size style is supported, it should be updated. + let font_size = match parent.style().font_size() { + CSSFontSizeLength(Px(length)) => length, + // todo: this is based on a hard coded font size, should be the parent element's font size + CSSFontSizeLength(Em(length)) => length * 16f, + _ => 16f // px units + }; + parent_text_top = Au::from_frac_px(font_size); + } + + // This flag decides whether topmost and bottommost are updated or not. + // That is, if the box has top or bottom value, no_update_flag becomes true. + let mut no_update_flag = false; + // Calculate a relative offset from baseline. + let offset = match cur_box.vertical_align() { + CSSVerticalAlignBaseline => { + -ascent + }, + CSSVerticalAlignMiddle => { + // TODO: x-height value should be used from font info. + let xheight = Au(0); + -(xheight + scanner.box_height(cur_box)).scale_by(0.5) + }, + CSSVerticalAlignSub => { + // TODO: The proper position for subscripts should be used. + // Lower the baseline to the proper position for subscripts + let sub_offset = Au(0); + (sub_offset - ascent) + }, + CSSVerticalAlignSuper => { + // TODO: The proper position for superscripts should be used. + // Raise the baseline to the proper position for superscripts + let super_offset = Au(0); + (-super_offset - ascent) + }, + CSSVerticalAlignTextTop => { + let box_height = top_from_base + bottom_from_base; + let prev_bottom_from_base = bottom_from_base; + top_from_base = parent_text_top; + bottom_from_base = box_height - top_from_base; + (bottom_from_base - prev_bottom_from_base - ascent) + }, + CSSVerticalAlignTextBottom => { + let box_height = top_from_base + bottom_from_base; + let prev_bottom_from_base = bottom_from_base; + bottom_from_base = parent_text_bottom; + top_from_base = box_height - bottom_from_base; + (bottom_from_base - prev_bottom_from_base - ascent) + }, + CSSVerticalAlignTop => { + if biggest_top < (top_from_base + bottom_from_base) { + biggest_top = top_from_base + bottom_from_base; + } + let offset_top = top_from_base - ascent; + no_update_flag = true; + offset_top + }, + CSSVerticalAlignBottom => { + if biggest_bottom < (top_from_base + bottom_from_base) { + biggest_bottom = top_from_base + bottom_from_base; + } + let offset_bottom = -(bottom_from_base + ascent); + no_update_flag = true; + offset_bottom + }, + CSSVerticalAlignLength(length) => { + let length_offset = match length { + Em(l) => Au::from_frac_px(cur_box.font_style().pt_size * l), + Px(l) => Au::from_frac_px(l), + }; + -(length_offset + ascent) + }, + CSSVerticalAlignPercentage(p) => { + let pt_size = cur_box.font_style().pt_size; + let line_height = scanner.calculate_line_height(cur_box, Au::from_pt(pt_size)); + let percent_offset = line_height.scale_by(p / 100.0f); + -(percent_offset + ascent) + } + }; + + // If the current box has 'top' or 'bottom' value, no_update_flag is true. + // Otherwise, topmost and bottomost are updated. + if !no_update_flag && top_from_base > topmost { + topmost = top_from_base; + } + if !no_update_flag && bottom_from_base > bottommost { + bottommost = bottom_from_base; + } + + do cur_box.with_mut_base |base| { + base.position.origin.y = line.bounds.origin.y + offset; + } + } + + // Calculate the distance from baseline to the top of the biggest box with 'bottom' value. + // Then, if necessary, update the topmost. + let topmost_of_bottom = biggest_bottom - bottommost; + if topmost_of_bottom > topmost { + topmost = topmost_of_bottom; } - // Now go back and adjust the Y coordinates to match the baseline we determined. + // Calculate the distance from baseline to the bottom of the biggest box with 'top' value. + // Then, if necessary, update the bottommost. + let bottommost_of_top = biggest_top - topmost; + if bottommost_of_top > bottommost { + bottommost = bottommost_of_top; + } + + // Now, the baseline offset from the top of linebox is set as topmost. + let baseline_offset = topmost; + + // All boxes' y position is updated following the new baseline offset. for box_i in line.range.eachi() { let cur_box = self.boxes[box_i]; - - // TODO(#226): This is completely wrong. We need to use the element's `line-height` - // when calculating line box height. Then we should go back over and set Y offsets - // according to the `vertical-align` property of the containing block. - let offset = match cur_box { - TextRenderBoxClass(text_box) => { - baseline_offset - text_box.run.font.metrics.ascent + let adjust_offset = match cur_box.vertical_align() { + CSSVerticalAlignTop => { + Au(0) + }, + CSSVerticalAlignBottom => { + baseline_offset + bottommost }, - _ => Au(0), + _ => { + baseline_offset + } }; do cur_box.with_mut_base |base| { - base.position.origin.y = offset + line.bounds.origin.y; + base.position.origin.y = base.position.origin.y + adjust_offset; } } + + // This is used to set the top y position of the next linebox in the next loop. + line_height_offset = line_height_offset + topmost + bottommost - line.bounds.size.height; + line.bounds.size.height = topmost + bottommost; } // End of `lines.each` loop. self.common.position.size.height = diff --git a/src/test/html/lineheight-simple.css b/src/test/html/lineheight-simple.css index bd8510f8061..6baa2fa488a 100644 --- a/src/test/html/lineheight-simple.css +++ b/src/test/html/lineheight-simple.css @@ -5,6 +5,6 @@ #larger2 { font-size: 30px; - line-height: 1; + line-height: 3; } diff --git a/src/test/html/lineheight-simple.html b/src/test/html/lineheight-simple.html index ed18ccdac80..f9c748da88b 100644 --- a/src/test/html/lineheight-simple.html +++ b/src/test/html/lineheight-simple.html @@ -5,6 +5,6 @@ </head> <body> <div>Regular font <span id="larger1">Even larger with line-height 2</span></div> -<div id="larger2">Large line 2!</div> +<div id="larger2">line-height 3!</div> </body> </html> diff --git a/src/test/html/test-lineheight-verticalalign.html b/src/test/html/test-lineheight-verticalalign.html new file mode 100644 index 00000000000..e9fcd0019f3 --- /dev/null +++ b/src/test/html/test-lineheight-verticalalign.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<html> +<head> +</head> +<body> + <div style="line-height: 10;">[line-height 10] + <span style="font-size: 15pt; line-height: 3; vertical-align: top">[line-height:3 + vertical-align:top]</span> + <span style="font-size: 15pt; line-height: 1; vertical-align: top">[line-height:1 + vertical-align:top]</span> + [Split inline, still line-height 5] + </div> + <div style="font-size: 30px; line-height: 3">New line, line-height 3</div> +</body> +</html> diff --git a/src/test/html/vertical_align_simple.html b/src/test/html/vertical_align_simple.html new file mode 100644 index 00000000000..a33ca2135ad --- /dev/null +++ b/src/test/html/vertical_align_simple.html @@ -0,0 +1,24 @@ +<html> + <head> + </head> + <body> + <p> + [test1] <b style="font-size:20px">baseline</b><img src="test.jpeg"></img> + </p> + <p> + [test2] <b style="font-size:20px; vertical-align:top">vertical-align:top</b><img src="test.jpeg"></img> + </p> + <p> + [test3] <b style="font-size:20px; vertical-align:middle">vertical-align:middle</b><img src="test.jpeg"></img> + </p> + <p> + [test4] <b style="font-size:20px; vertical-align:bottom">vertical-align:bottom</b><img src="test.jpeg"></img> + </p> + <p> + [test5] <b style="font-size:20px;">img=>text-top</b><img src="test.jpeg" style="vertical-align: text-top;"></img> + </p> + <p> + [test6] <b style="font-size:20px;">img=>text-bottom</b><img src="test.jpeg" style="vertical-align: text-bottom;"></img> + </p> + </body> +</html> |