aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorbors-servo <release+servo@mozilla.com>2013-08-27 14:42:40 -0700
committerbors-servo <release+servo@mozilla.com>2013-08-27 14:42:40 -0700
commit4e68c1a4f76a50fe0b721d3fddcd8eb15c1bfae0 (patch)
treef176ad9725739c310590e2a92011f0072ec784bc /src
parent79956abb384177fb5035e5d1f00f1ac152e081e5 (diff)
parent729ac0c79609de1a1ac24d0c7b99c5c3c0b47f9d (diff)
downloadservo-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.rs6
-rw-r--r--src/components/main/layout/inline.rs255
-rw-r--r--src/test/html/lineheight-simple.css2
-rw-r--r--src/test/html/lineheight-simple.html2
-rw-r--r--src/test/html/test-lineheight-verticalalign.html13
-rw-r--r--src/test/html/vertical_align_simple.html24
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>