diff options
author | bors-servo <release+servo@mozilla.com> | 2013-12-09 19:43:16 -0800 |
---|---|---|
committer | bors-servo <release+servo@mozilla.com> | 2013-12-09 19:43:16 -0800 |
commit | 6014bd38130793211f59c78c78d49330eefe4807 (patch) | |
tree | 66feb4687a1ffd68b30bdfd8f6c1453efece8dd6 | |
parent | 30bbaa49b700140574c5848955abba4b8d2dc48c (diff) | |
parent | 2624b937aa970f2553cb33bf35b02327f0e85141 (diff) | |
download | servo-6014bd38130793211f59c78c78d49330eefe4807.tar.gz servo-6014bd38130793211f59c78c78d49330eefe4807.zip |
auto merge of #1365 : pcwalton/servo/minor-layout-refactoring, r=metajack
r? @metajack
-rw-r--r-- | src/components/main/layout/inline.rs | 496 |
1 files changed, 255 insertions, 241 deletions
diff --git a/src/components/main/layout/inline.rs b/src/components/main/layout/inline.rs index 11852060d6f..151a702fe84 100644 --- a/src/components/main/layout/inline.rs +++ b/src/components/main/layout/inline.rs @@ -277,10 +277,56 @@ impl LineboxScanner { } - /// Returns false only if we should break the line. - fn try_append_to_line(&mut self, in_box: @Box, flow: &mut InlineFlow) -> bool { - let line_is_empty: bool = self.pending_line.range.length() == 0; + /// Performs float collision avoidance. This is called when adding a box is going to increase + /// the height, and because of that we will collide with some floats. + /// + /// We have two options here: + /// 1) Move the entire line so that it doesn't collide any more. + /// 2) Break the line and put the new box on the next line. + /// + /// The problem with option 1 is that we might move the line and then wind up breaking anyway, + /// which violates the standard. + /// But option 2 is going to look weird sometimes. + /// + /// So we'll try to move the line whenever we can, but break if we have to. + /// + /// Returns false if and only if we should break the line. + fn avoid_floats(&mut self, + in_box: @Box, + flow: &mut InlineFlow, + new_height: Au, + line_is_empty: bool) + -> bool { + debug!("LineboxScanner: entering float collision avoider!"); + + // First predict where the next line is going to be. + let this_line_y = self.pending_line.bounds.origin.y; + let (next_line, first_box_width) = self.initial_line_placement(in_box, this_line_y, flow); + let next_green_zone = next_line.size; + + let new_width = self.pending_line.bounds.size.width + first_box_width; + + // Now, see if everything can fit at the new location. + if next_green_zone.width >= new_width && next_green_zone.height >= new_height { + debug!("LineboxScanner: case=adding box collides vertically with floats: moving line"); + + self.pending_line.bounds.origin = next_line.origin; + self.pending_line.green_zone = next_green_zone; + + assert!(!line_is_empty, "Non-terminating line breaking"); + self.work_list.push_front(in_box); + return true + } + debug!("LineboxScanner: case=adding box collides vertically with floats: breaking line"); + self.work_list.push_front(in_box); + 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 { + let line_is_empty = self.pending_line.range.length() == 0; if line_is_empty { let (line_bounds, _) = self.initial_line_placement(in_box, self.cur_y, flow); self.pending_line.bounds.origin = line_bounds.origin; @@ -294,132 +340,85 @@ impl LineboxScanner { self.pending_line.green_zone, in_box.debug_str()); - let green_zone = self.pending_line.green_zone; - //assert!(green_zone.width >= self.pending_line.bounds.size.width && - // green_zone.height >= self.pending_line.bounds.size.height, - // "Committed a line that overlaps with floats"); + // NB: At this point, if `green_zone.width < self.pending_line.bounds.size.width` or + // `green_zone.height < self.pending_line.bounds.size.height`, then we committed a line + // that overlaps with floats. let new_height = self.new_height_for_line(in_box); if new_height > green_zone.height { - debug!("LineboxScanner: entering float collision avoider!"); - - // Uh-oh. Adding this box is going to increase the height, - // and because of that we will collide with some floats. - - // We have two options here: - // 1) Move the entire line so that it doesn't collide any more. - // 2) Break the line and put the new box on the next line. - - // The problem with option 1 is that we might move the line - // and then wind up breaking anyway, which violates the standard. - // But option 2 is going to look weird sometimes. - - // So we'll try to move the line whenever we can, but break - // if we have to. - - // First predict where the next line is going to be - let this_line_y = self.pending_line.bounds.origin.y; - let (next_line, first_box_width) = self.initial_line_placement(in_box, this_line_y, flow); - let next_green_zone = next_line.size; - - let new_width = self.pending_line.bounds.size.width + first_box_width; - // Now, see if everything can fit at the new location. - if next_green_zone.width >= new_width && next_green_zone.height >= new_height{ - debug!("LineboxScanner: case=adding box collides vertically with floats: moving line"); - - self.pending_line.bounds.origin = next_line.origin; - self.pending_line.green_zone = next_green_zone; - - assert!(!line_is_empty, "Non-terminating line breaking"); - self.work_list.push_front(in_box); - return true; - } else { - debug!("LineboxScanner: case=adding box collides vertically with floats: breaking line"); - self.work_list.push_front(in_box); - return false; - } + // Uh-oh. Float collision imminent. Enter the float collision avoider… + return self.avoid_floats(in_box, flow, new_height, line_is_empty) } // If we're not going to overflow the green zone vertically, we might still do so - // horizontally. We'll try to place the whole box on this line and break somewhere - // if it doesn't fit. + // horizontally. We'll try to place the whole box on this line and break somewhere if it + // doesn't fit. let new_width = self.pending_line.bounds.size.width + in_box.position.get().size.width; - if new_width <= green_zone.width { debug!("LineboxScanner: case=box fits without splitting"); self.push_box_to_line(in_box); - return true; + return true } if !in_box.can_split() { - // TODO(Issue #224): signal that horizontal overflow happened? + // TODO(eatkinson, issue #224): Signal that horizontal overflow happened? if line_is_empty { debug!("LineboxScanner: case=box can't split and line {:u} is empty, so \ overflowing.", self.lines.len()); - self.push_box_to_line(in_box); - return true; + self.push_box_to_line(in_box) } else { debug!("LineboxScanner: Case=box can't split, not appending."); - return false; } - } else { - let available_width = green_zone.width - self.pending_line.bounds.size.width; + return line_is_empty + } - match in_box.split_to_width(available_width, line_is_empty) { - CannotSplit(_) => { - error!("LineboxScanner: Tried to split unsplittable render box! {:s}", - in_box.debug_str()); - return false; - } - SplitDidFit(left, right) => { - debug!("LineboxScanner: case=split box did fit; deferring remainder box."); - 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.push_box_to_line(right_box), - (None, None) => error!("LineboxScanner: This split case makes no sense!"), - } - return true; - } - SplitDidNotFit(left, right) => { - if line_is_empty { - debug!("LineboxScanner: case=split box didn't fit and line {:u} is empty, so overflowing and deferring remainder box.", - self.lines.len()); - // TODO(Issue #224): signal that horizontal overflow happened? - 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.push_box_to_line(right_box); - } - (None, None) => { - error!("LineboxScanner: This split case makes no sense!"); - } - } - return true; - } else { - debug!("LineboxScanner: case=split box didn't fit, not appending and deferring original box."); - self.work_list.push_front(in_box); - return false; - } - } + let available_width = green_zone.width - self.pending_line.bounds.size.width; + let split = in_box.split_to_width(available_width, line_is_empty); + let (left, right) = match (split, line_is_empty) { + (CannotSplit(_), _) => { + error!("LineboxScanner: Tried to split unsplittable render box! {:s}", + in_box.debug_str()); + return false + } + (SplitDidNotFit(_, _), false) => { + debug!("LineboxScanner: case=split box didn't fit, not appending and deferring \ + original box."); + self.work_list.push_front(in_box); + return false + } + (SplitDidFit(left, right), _) => { + debug!("LineboxScanner: case=split box did fit; deferring remainder box."); + (left, right) + // Fall through to push boxes to the line. + } + (SplitDidNotFit(left, right), true) => { + // TODO(eatkinson, issue #224): Signal that horizontal overflow happened? + debug!("LineboxScanner: case=split box didn't fit and line {:u} is empty, so \ + overflowing and deferring remainder box.", + self.lines.len()); + (left, right) + // Fall though to push boxes to the line. } + }; + + 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.push_box_to_line(right_box), + (None, None) => error!("LineboxScanner: This split case makes no sense!"), } + + true } - // unconditional push + // An unconditional push. fn push_box_to_line(&mut self, box: @Box) { debug!("LineboxScanner: Pushing box {} to line {:u}", box.debug_id(), self.lines.len()); @@ -511,6 +510,114 @@ impl InlineFlow { // For now, don't traverse the subtree rooted here true } + + /// Returns the relative offset from the baseline for this box, taking into account the value + /// of the CSS `vertical-align` property. + /// + /// The extra boolean is set if and only if `biggest_top` and/or `biggest_bottom` were updated. + /// That is, if the box has a `top` or `bottom` value, true is returned. + fn relative_offset_from_baseline(cur_box: @Box, + ascent: Au, + parent_text_top: Au, + parent_text_bottom: Au, + top_from_base: &mut Au, + bottom_from_base: &mut Au, + biggest_top: &mut Au, + biggest_bottom: &mut Au) + -> (Au, bool) { + match cur_box.vertical_align() { + vertical_align::baseline => (-ascent, false), + vertical_align::middle => { + // TODO: x-height value should be used from font info. + let xheight = Au::new(0); + (-(xheight + cur_box.box_height()).scale_by(0.5), false) + }, + vertical_align::sub => { + // TODO: The proper position for subscripts should be used. + // Lower the baseline to the proper position for subscripts + let sub_offset = Au::new(0); + (sub_offset - ascent, false) + }, + vertical_align::super_ => { + // TODO: The proper position for superscripts should be used. + // Raise the baseline to the proper position for superscripts + let super_offset = Au::new(0); + (-super_offset - ascent, false) + }, + vertical_align::text_top => { + 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, false) + }, + vertical_align::text_bottom => { + 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, false) + }, + vertical_align::top => { + if *biggest_top < (*top_from_base + *bottom_from_base) { + *biggest_top = *top_from_base + *bottom_from_base; + } + let offset_top = *top_from_base - ascent; + (offset_top, true) + }, + vertical_align::bottom => { + if *biggest_bottom < (*top_from_base + *bottom_from_base) { + *biggest_bottom = *top_from_base + *bottom_from_base; + } + let offset_bottom = -(*bottom_from_base + ascent); + (offset_bottom, true) + }, + vertical_align::Length(length) => (-(length + ascent), false), + vertical_align::Percentage(p) => { + let pt_size = cur_box.font_style().pt_size; + let line_height = cur_box.calculate_line_height(Au::from_pt(pt_size)); + let percent_offset = line_height.scale_by(p); + (-(percent_offset + ascent), false) + } + } + } + + /// Sets box X positions based on alignment for one line. + fn set_horizontal_box_positions(boxes: &[@Box], line: &LineBox) { + // Figure out how much width we have. + let slack_width = Au::max(Au(0), line.green_zone.width - line.bounds.size.width); + + // Get the text alignment. + // + // TODO(burg, issue #222): use 'text-align' property from `InlineFlow`'s block container, + // not from the style of the first box child. + let linebox_align = if line.range.begin() < boxes.len() { + let first_box = boxes[line.range.begin()]; + first_box.nearest_ancestor_element().style().Text.text_align + } else { + // Nothing to lay out, so assume left alignment. + text_align::left + }; + + // Set the box x positions based on that alignment. + let mut offset_x = line.bounds.origin.x; + offset_x = offset_x + match linebox_align { + // So sorry, but justified text is more complicated than shuffling linebox + // coordinates. + // + // TODO(burg, issue #213): Implement `text-align: justify`. + text_align::left | text_align::justify => Au(0), + text_align::center => slack_width.scale_by(0.5), + text_align::right => slack_width, + }; + + for i in line.range.eachi() { + let box = &boxes[i]; + let size = box.position.get().size; + box.position.set(Rect(Point2D(offset_x, box.position.get().origin.y), size)); + offset_x = offset_x + size.width; + } + } } impl Flow for InlineFlow { @@ -590,7 +697,8 @@ impl Flow for InlineFlow { fn assign_height(&mut self, _: &mut LayoutContext) { debug!("assign_height_inline: assigning height for flow {}", self.base.id); - // Divide the boxes into lines + // Divide the boxes into lines. + // // TODO(#226): Get the CSS `line-height` property from the containing block's style to // determine minimum linebox height. // @@ -608,68 +716,20 @@ impl Flow for InlineFlow { let mut line_height_offset = Au::new(0); - // Now, go through each line and lay out the boxes inside + // Now, go through each line and lay out the boxes inside. 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::new(0) { - slack_width = Au::new(0); - } - - // Get the text alignment. - // TODO(Issue #222): use 'text-align' property from InlineFlow's - // block container, not from the style of the first box child. - let linebox_align = if line.range.begin() < self.boxes.len() { - let first_box = self.boxes[line.range.begin()]; - first_box.nearest_ancestor_element().style().Text.text_align - } else { - // Nothing to lay out, so assume left alignment. - text_align::left - }; - - // Set the box x positions - let mut offset_x = line.bounds.origin.x; - match linebox_align { - // So sorry, but justified text is more complicated than shuffling linebox coordinates. - // TODO(Issue #213): implement `text-align: justify` - text_align::left | text_align::justify => { - for i in line.range.eachi() { - let box = &self.boxes[i]; - box.position.mutate().ptr.origin.x = offset_x; - offset_x = offset_x + box.position.get().size.width; - } - } - text_align::center => { - offset_x = offset_x + slack_width.scale_by(0.5); - for i in line.range.eachi() { - let box = &self.boxes[i]; - box.position.mutate().ptr.origin.x = offset_x; - offset_x = offset_x + box.position.get().size.width; - } - } - text_align::right => { - offset_x = offset_x + slack_width; - for i in line.range.eachi() { - let box = &self.boxes[i]; - box.position.mutate().ptr.origin.x = offset_x; - offset_x = offset_x + box.position.get().size.width; - } - } - }; + // Lay out boxes horizontally. + InlineFlow::set_horizontal_box_positions(self.boxes, line); // 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::new(0); - // Calculate the distance from baseline to the bottom of the linebox. - let mut bottommost = Au::new(0); - - // Calculate the biggest height among boxes with 'top' value. - let mut biggest_top = Au::new(0); - // Calculate the biggest height among boxes with 'bottom' value. - let mut biggest_bottom = Au::new(0); + // Calculate the distance from baseline to the top and bottom of the linebox. + let (mut topmost, mut bottommost) = (Au(0), Au(0)); + // Calculate the biggest height among boxes with 'top' and 'bottom' values + // respectively. + let (mut biggest_top, mut biggest_bottom) = (Au(0), Au(0)); for box_i in line.range.eachi() { let cur_box = self.boxes[box_i]; @@ -679,8 +739,8 @@ impl Flow for InlineFlow { ImageBox(ref image_box) => { let mut height = image_box.image_height(cur_box); - // TODO: margin, border, padding's top and bottom should be calculated in advance, - // since baseline of image is bottom margin edge. + // TODO: margin, border, padding's top and bottom should be calculated in + // advance, since baseline of image is bottom margin edge. let mut top; let mut bottom; { @@ -727,92 +787,45 @@ impl Flow for InlineFlow { fail!("Unscanned text boxes should have been scanned by now.") } }; + 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 'vertical-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. + // 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 the font size of the parent and + // calculate the distances from the baseline to the top and the bottom of the + // parent's content area. + + // We should calculate the distance from baseline to the top of parent's content + // area. But for now we assume it's the parent's font size. let mut parent_text_top; - // 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::new(0); - // Get parent node - let parent = cur_box.node.parent_node(); + // We should calculate the distance from baseline to the bottom of the parent's + // content area. But for now we assume it's zero. + let parent_text_bottom = Au::new(0); + + let parent = cur_box.node.parent_node(); let font_size = parent.unwrap().style().Font.font_size; parent_text_top = 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() { - vertical_align::baseline => { - -ascent - }, - vertical_align::middle => { - // TODO: x-height value should be used from font info. - let xheight = Au::new(0); - -(xheight + cur_box.box_height()).scale_by(0.5) - }, - vertical_align::sub => { - // TODO: The proper position for subscripts should be used. - // Lower the baseline to the proper position for subscripts - let sub_offset = Au::new(0); - (sub_offset - ascent) - }, - vertical_align::super_ => { - // TODO: The proper position for superscripts should be used. - // Raise the baseline to the proper position for superscripts - let super_offset = Au::new(0); - (-super_offset - ascent) - }, - vertical_align::text_top => { - 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) - }, - vertical_align::text_bottom => { - 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) - }, - vertical_align::top => { - 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 - }, - vertical_align::bottom => { - 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 - }, - vertical_align::Length(length) => { - -(length + ascent) - }, - vertical_align::Percentage(p) => { - let pt_size = cur_box.font_style().pt_size; - let line_height = cur_box.calculate_line_height(Au::from_pt(pt_size)); - let percent_offset = line_height.scale_by(p); - -(percent_offset + ascent) - } - }; + // Calculate a relative offset from the baseline. + // + // The no-update flag decides whether `biggest_top` and `biggest_bottom` are + // updated or not. That is, if the box has a `top` or `bottom` value, + // `no_update_flag` becomes true. + let (offset, no_update_flag) = + InlineFlow::relative_offset_from_baseline(cur_box, + ascent, + parent_text_top, + parent_text_bottom, + &mut top_from_base, + &mut bottom_from_base, + &mut biggest_top, + &mut biggest_bottom); // If the current box has 'top' or 'bottom' value, no_update_flag is true. // Otherwise, topmost and bottomost are updated. @@ -826,15 +839,15 @@ impl Flow for InlineFlow { cur_box.position.mutate().ptr.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. + // 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; } - // Calculate the distance from baseline to the bottom of the biggest box with 'top' value. - // Then, if necessary, update the bottommost. + // 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; @@ -857,7 +870,8 @@ impl Flow for InlineFlow { } // 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_height_offset = line_height_offset + topmost + bottommost - + line.bounds.size.height; line.bounds.size.height = topmost + bottommost; } // End of `lines.each` loop. |