diff --git a/servo/components/gfx/platform/macos/font.rs b/servo/components/gfx/platform/macos/font.rs index 97def3641610..adbaccf3c502 100644 --- a/servo/components/gfx/platform/macos/font.rs +++ b/servo/components/gfx/platform/macos/font.rs @@ -306,7 +306,7 @@ impl FontHandleMethods for FontHandle { .map(Au::from_f64_px) .unwrap_or(max_advance_width); - let metrics = FontMetrics { + let metrics = FontMetrics { underline_size: au_from_pt(self.ctfont.underline_thickness() as f64), // TODO(Issue #201): underline metrics are not reliable. Have to pull out of font table // directly. @@ -317,7 +317,7 @@ impl FontHandleMethods for FontHandle { strikeout_size: Au(0), // FIXME(Issue #942) strikeout_offset: Au(0), // FIXME(Issue #942) leading: au_from_pt(leading), - x_height: au_from_pt(self.ctfont.x_height() as f64), + x_height: au_from_pt((self.ctfont.x_height() as f64) * scale), em_size: em_size, ascent: au_from_pt(ascent * scale), descent: au_from_pt(descent * scale), diff --git a/servo/components/layout/construct.rs b/servo/components/layout/construct.rs index 9722cc852d79..5cc982fcc3fa 100644 --- a/servo/components/layout/construct.rs +++ b/servo/components/layout/construct.rs @@ -476,14 +476,9 @@ impl<'a, ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode> { // FIXME(#6503): Use Arc::get_mut().unwrap() here. let inline_flow = flow_ref::deref_mut(&mut inline_flow_ref).as_mut_inline(); - - - let (ascent, descent) = - inline_flow.compute_minimum_ascent_and_descent(&mut self.layout_context - .font_context(), - &node.style(self.style_context())); - inline_flow.minimum_block_size_above_baseline = ascent; - inline_flow.minimum_depth_below_baseline = descent; + inline_flow.minimum_line_metrics = + inline_flow.minimum_line_metrics(&mut self.layout_context.font_context(), + &node.style(self.style_context())) } inline_flow_ref.finish(); diff --git a/servo/components/layout/flow.rs b/servo/components/layout/flow.rs index f206e9fc5a88..28694686ef08 100644 --- a/servo/components/layout/flow.rs +++ b/servo/components/layout/flow.rs @@ -1401,7 +1401,7 @@ impl<'a> ImmutableFlowUtils for &'a Flow { for kid in base(self).children.iter().rev() { if kid.is_inline_flow() { if let Some(baseline_offset) = kid.as_inline().baseline_offset_of_last_line() { - return Some(baseline_offset) + return Some(base(kid).position.start.b + baseline_offset) } } if kid.is_block_like() && @@ -1576,7 +1576,7 @@ impl ContainingBlockLink { if flow.is_block_like() { flow.as_block().explicit_block_containing_size(shared_context) } else if flow.is_inline_flow() { - Some(flow.as_inline().minimum_block_size_above_baseline) + Some(flow.as_inline().minimum_line_metrics.space_above_baseline) } else { None } diff --git a/servo/components/layout/fragment.rs b/servo/components/layout/fragment.rs index 910497daca2e..0faf0df22af9 100644 --- a/servo/components/layout/fragment.rs +++ b/servo/components/layout/fragment.rs @@ -19,7 +19,7 @@ use gfx::text::glyph::ByteIndex; use gfx::text::text_run::{TextRun, TextRunSlice}; use gfx_traits::{FragmentType, LayerId, LayerType, StackingContextId}; use inline::{FIRST_FRAGMENT_OF_ELEMENT, InlineFragmentContext, InlineFragmentNodeInfo}; -use inline::{InlineMetrics, LAST_FRAGMENT_OF_ELEMENT}; +use inline::{InlineMetrics, LAST_FRAGMENT_OF_ELEMENT, LineMetrics}; use ipc_channel::ipc::IpcSender; #[cfg(debug_assertions)] use layout_debug; @@ -54,6 +54,10 @@ use text; use text::TextRunScanner; use url::Url; +// From gfxFontConstants.h in Firefox. +static FONT_SUBSCRIPT_OFFSET_RATIO: f32 = 0.20; +static FONT_SUPERSCRIPT_OFFSET_RATIO: f32 = 0.34; + /// Fragments (`struct Fragment`) are the leaves of the layout tree. They cannot position /// themselves. In general, fragments do not have a simple correspondence with CSS fragments in the /// specification: @@ -643,6 +647,7 @@ impl ReplacedImageFragmentInfo { inline_size + noncontent_inline_size } + /// Here, `noncontent_block_size` represents the sum of border and padding, but not margin. pub fn calculate_replaced_block_size(&mut self, style: &ServoComputedValues, noncontent_block_size: Au, @@ -650,7 +655,6 @@ impl ReplacedImageFragmentInfo { fragment_inline_size: Au, fragment_block_size: Au) -> Au { - // TODO(ksh8281): compute border,margin,padding let style_block_size = style.content_block_size(); let style_min_block_size = style.min_block_size(); let style_max_block_size = style.max_block_size(); @@ -1178,13 +1182,6 @@ impl Fragment { } } - pub fn calculate_line_height(&self, layout_context: &LayoutContext) -> Au { - let font_style = self.style.get_font_arc(); - let font_metrics = text::font_metrics_for_style(&mut layout_context.font_context(), - font_style); - text::line_height_from_style(&*self.style, &font_metrics) - } - /// Returns the sum of the inline-sizes of all the borders of this fragment. Note that this /// can be expensive to compute, so if possible use the `border_padding` field instead. #[inline] @@ -2132,38 +2129,52 @@ impl Fragment { } } + /// Returns true if this fragment is replaced content or an inline-block or false otherwise. + pub fn is_replaced_or_inline_block(&self) -> bool { + match self.specific { + SpecificFragmentInfo::Canvas(_) | + SpecificFragmentInfo::Iframe(_) | + SpecificFragmentInfo::Image(_) | + SpecificFragmentInfo::InlineAbsoluteHypothetical(_) | + SpecificFragmentInfo::InlineBlock(_) | + SpecificFragmentInfo::Svg(_) => true, + SpecificFragmentInfo::Generic | + SpecificFragmentInfo::GeneratedContent(_) | + SpecificFragmentInfo::InlineAbsolute(_) | + SpecificFragmentInfo::Table | + SpecificFragmentInfo::TableCell | + SpecificFragmentInfo::TableColumn(_) | + SpecificFragmentInfo::TableRow | + SpecificFragmentInfo::TableWrapper | + SpecificFragmentInfo::Multicol | + SpecificFragmentInfo::MulticolColumn | + SpecificFragmentInfo::ScannedText(_) | + SpecificFragmentInfo::UnscannedText(_) => false, + } + } + /// Calculates block-size above baseline, depth below baseline, and ascent for this fragment /// when used in an inline formatting context. See CSS 2.1 § 10.8.1. - pub fn inline_metrics(&self, layout_context: &LayoutContext) -> InlineMetrics { - return match self.specific { - SpecificFragmentInfo::Image(ref image_fragment_info) => { - let computed_block_size = image_fragment_info.replaced_image_fragment_info - .computed_block_size(); + /// + /// This does not take `vertical-align` into account. For that, use `aligned_inline_metrics()`. + fn content_inline_metrics(&self, layout_context: &LayoutContext) -> InlineMetrics { + // CSS 2.1 § 10.8: "The height of each inline-level box in the line box is + // calculated. For replaced elements, inline-block elements, and inline-table + // elements, this is the height of their margin box." + // + // FIXME(pcwalton): We have to handle `Generic` and `GeneratedContent` here to avoid + // crashing in a couple of `css21_dev/html4/content-` WPTs, but I don't see how those two + // fragment types should end up inside inlines. (In the case of `GeneratedContent`, those + // fragment types should have been resolved by now…) + let inline_metrics = match self.specific { + SpecificFragmentInfo::Canvas(_) | SpecificFragmentInfo::Iframe(_) | + SpecificFragmentInfo::Image(_) | SpecificFragmentInfo::Svg(_) | + SpecificFragmentInfo::Generic | SpecificFragmentInfo::GeneratedContent(_) => { + let ascent = self.border_box.size.block + self.margin.block_start; InlineMetrics { - block_size_above_baseline: computed_block_size + - self.border_padding.block_start, - depth_below_baseline: self.border_padding.block_end, - ascent: computed_block_size + self.border_padding.block_start, - } - } - SpecificFragmentInfo::Canvas(ref canvas_fragment_info) => { - let computed_block_size = canvas_fragment_info.replaced_image_fragment_info - .computed_block_size(); - InlineMetrics { - block_size_above_baseline: computed_block_size + - self.border_padding.block_start, - depth_below_baseline: self.border_padding.block_end, - ascent: computed_block_size + self.border_padding.block_start, - } - } - SpecificFragmentInfo::Svg(ref svg_fragment_info) => { - let computed_block_size = svg_fragment_info.replaced_image_fragment_info - .computed_block_size(); - InlineMetrics { - block_size_above_baseline: computed_block_size + - self.border_padding.block_start, - depth_below_baseline: self.border_padding.block_end, - ascent: computed_block_size + self.border_padding.block_start, + space_above_baseline: ascent, + space_below_baseline: self.margin.block_end, + ascent: ascent, } } SpecificFragmentInfo::ScannedText(ref info) => { @@ -2173,14 +2184,10 @@ impl Fragment { return InlineMetrics::new(Au(0), Au(0), Au(0)); } // See CSS 2.1 § 10.8.1. - let line_height = self.calculate_line_height(layout_context); - let font_derived_metrics = - InlineMetrics::from_font_metrics(&info.run.font_metrics, line_height); - InlineMetrics { - block_size_above_baseline: font_derived_metrics.block_size_above_baseline, - depth_below_baseline: font_derived_metrics.depth_below_baseline, - ascent: font_derived_metrics.ascent + self.border_padding.block_start, - } + let font_metrics = text::font_metrics_for_style(&mut layout_context.font_context(), + self.style.get_font_arc()); + let line_height = text::line_height_from_style(&*self.style, &font_metrics); + InlineMetrics::from_font_metrics(&info.run.font_metrics, line_height) } SpecificFragmentInfo::InlineBlock(ref info) => { inline_metrics_of_block(&info.flow_ref, &*self.style) @@ -2191,21 +2198,26 @@ impl Fragment { SpecificFragmentInfo::InlineAbsolute(_) => { InlineMetrics::new(Au(0), Au(0), Au(0)) } - _ => { - InlineMetrics { - block_size_above_baseline: self.border_box.size.block, - depth_below_baseline: Au(0), - ascent: self.border_box.size.block, - } + SpecificFragmentInfo::Table | + SpecificFragmentInfo::TableCell | + SpecificFragmentInfo::TableColumn(_) | + SpecificFragmentInfo::TableRow | + SpecificFragmentInfo::TableWrapper | + SpecificFragmentInfo::Multicol | + SpecificFragmentInfo::MulticolColumn | + SpecificFragmentInfo::UnscannedText(_) => { + unreachable!("Shouldn't see fragments of this type here!") } }; + return inline_metrics; fn inline_metrics_of_block(flow: &FlowRef, style: &ServoComputedValues) -> InlineMetrics { - // See CSS 2.1 § 10.8.1. + // CSS 2.1 § 10.8: "The height of each inline-level box in the line box is calculated. + // For replaced elements, inline-block elements, and inline-table elements, this is the + // height of their margin box." let block_flow = flow.as_block(); let is_auto = style.get_position().height == LengthOrPercentageOrAuto::Auto; - let baseline_offset = flow.baseline_offset_of_last_line_box_in_flow(); - let baseline_offset = match baseline_offset { + let baseline_offset = match flow.baseline_offset_of_last_line_box_in_flow() { Some(baseline_offset) if is_auto => baseline_offset, _ => block_flow.fragment.border_box.size.block, }; @@ -2218,6 +2230,107 @@ impl Fragment { } } + /// Calculates the offset from the baseline that applies to this fragment due to + /// `vertical-align`. Positive values represent downward displacement. + /// + /// If `actual_line_metrics` is supplied, then these metrics are used to determine the + /// displacement of the fragment when `top` or `bottom` `vertical-align` values are + /// encountered. If this is not supplied, then `top` and `bottom` values are ignored. + fn vertical_alignment_offset(&self, + layout_context: &LayoutContext, + content_inline_metrics: &InlineMetrics, + minimum_line_metrics: &LineMetrics, + actual_line_metrics: Option<&LineMetrics>) + -> Au { + let mut offset = Au(0); + for style in self.inline_styles() { + // If any of the inline styles say `top` or `bottom`, adjust the vertical align + // appropriately. + // + // FIXME(#5624, pcwalton): This passes our current reftests but isn't the right thing + // to do. + match style.get_box().vertical_align { + vertical_align::T::baseline => {} + vertical_align::T::middle => { + let font_metrics = + text::font_metrics_for_style(&mut layout_context.font_context(), + style.get_font_arc()); + offset += (content_inline_metrics.ascent - + content_inline_metrics.space_below_baseline - + font_metrics.x_height).scale_by(0.5) + } + vertical_align::T::sub => { + offset += minimum_line_metrics.space_needed() + .scale_by(FONT_SUBSCRIPT_OFFSET_RATIO) + } + vertical_align::T::super_ => { + offset -= minimum_line_metrics.space_needed() + .scale_by(FONT_SUPERSCRIPT_OFFSET_RATIO) + } + vertical_align::T::text_top => { + offset = self.content_inline_metrics(layout_context).ascent - + minimum_line_metrics.space_above_baseline + } + vertical_align::T::text_bottom => { + offset = minimum_line_metrics.space_below_baseline - + self.content_inline_metrics(layout_context).space_below_baseline + } + vertical_align::T::top => { + if let Some(actual_line_metrics) = actual_line_metrics { + offset = content_inline_metrics.ascent - + actual_line_metrics.space_above_baseline + } + } + vertical_align::T::bottom => { + if let Some(actual_line_metrics) = actual_line_metrics { + offset = actual_line_metrics.space_below_baseline - + content_inline_metrics.space_below_baseline + } + } + vertical_align::T::LengthOrPercentage(LengthOrPercentage::Length(length)) => { + offset -= length + } + vertical_align::T::LengthOrPercentage(LengthOrPercentage::Percentage( + percentage)) => { + offset -= minimum_line_metrics.space_needed().scale_by(percentage) + } + vertical_align::T::LengthOrPercentage(LengthOrPercentage::Calc(formula)) => { + offset -= minimum_line_metrics.space_needed().scale_by(formula.percentage()) + + formula.length() + } + } + } + offset + } + + /// Calculates block-size above baseline, depth below baseline, and ascent for this fragment + /// when used in an inline formatting context, taking `vertical-align` (other than `top` or + /// `bottom`) into account. See CSS 2.1 § 10.8.1. + /// + /// If `actual_line_metrics` is supplied, then these metrics are used to determine the + /// displacement of the fragment when `top` or `bottom` `vertical-align` values are + /// encountered. If this is not supplied, then `top` and `bottom` values are ignored. + pub fn aligned_inline_metrics(&self, + layout_context: &LayoutContext, + minimum_line_metrics: &LineMetrics, + actual_line_metrics: Option<&LineMetrics>) + -> InlineMetrics { + let content_inline_metrics = self.content_inline_metrics(layout_context); + let vertical_alignment_offset = self.vertical_alignment_offset(layout_context, + &content_inline_metrics, + minimum_line_metrics, + actual_line_metrics); + let mut space_above_baseline = match actual_line_metrics { + None => content_inline_metrics.space_above_baseline, + Some(actual_line_metrics) => actual_line_metrics.space_above_baseline, + }; + space_above_baseline = space_above_baseline - vertical_alignment_offset; + let space_below_baseline = content_inline_metrics.space_below_baseline + + vertical_alignment_offset; + let ascent = content_inline_metrics.ascent - vertical_alignment_offset; + InlineMetrics::new(space_above_baseline, space_below_baseline, ascent) + } + /// Returns true if this fragment is a hypothetical box. See CSS 2.1 § 10.3.7. pub fn is_hypothetical(&self) -> bool { match self.specific { diff --git a/servo/components/layout/inline.rs b/servo/components/layout/inline.rs index f0622f1bbcb1..c06e3631719c 100644 --- a/servo/components/layout/inline.rs +++ b/servo/components/layout/inline.rs @@ -37,14 +37,9 @@ use style::computed_values::{text_overflow, vertical_align, white_space}; use style::context::{SharedStyleContext, StyleContext}; use style::logical_geometry::{LogicalRect, LogicalSize, WritingMode}; use style::properties::ServoComputedValues; -use style::values::computed::LengthOrPercentage; use text; use unicode_bidi; -// From gfxFontConstants.h in Firefox -static FONT_SUBSCRIPT_OFFSET_RATIO: f32 = 0.20; -static FONT_SUPERSCRIPT_OFFSET_RATIO: f32 = 0.34; - /// `Line`s are represented as offsets into the child list, rather than /// as an object that "owns" fragments. Choosing a different set of line /// breaks requires a new list of offsets, and possibly some splitting and @@ -158,31 +153,22 @@ pub struct Line { /// ~~~ pub green_zone: LogicalSize, - /// The minimum block size above the baseline for this line, as specified by the style. - pub minimum_block_size_above_baseline: Au, + /// The minimum metrics for this line, as specified by the style. + pub minimum_metrics: LineMetrics, - /// The minimum depth below the baseline for this line, as specified by the style. - pub minimum_depth_below_baseline: Au, - - /// The inline metrics for this line. - pub inline_metrics: InlineMetrics, + /// The actual metrics for this line. + pub metrics: LineMetrics, } impl Line { - fn new(writing_mode: WritingMode, - minimum_block_size_above_baseline: Au, - minimum_depth_below_baseline: Au) - -> Line { + fn new(writing_mode: WritingMode, minimum_metrics: &LineMetrics) -> Line { Line { range: Range::empty(), visual_runs: None, bounds: LogicalRect::zero(writing_mode), green_zone: LogicalSize::zero(writing_mode), - minimum_block_size_above_baseline: minimum_block_size_above_baseline, - minimum_depth_below_baseline: minimum_depth_below_baseline, - inline_metrics: InlineMetrics::new(minimum_block_size_above_baseline, - minimum_depth_below_baseline, - minimum_block_size_above_baseline), + minimum_metrics: *minimum_metrics, + metrics: *minimum_metrics, } } @@ -190,29 +176,31 @@ impl Line { /// /// FIXME(pcwalton): this assumes that the tallest fragment in the line determines the line /// block-size. This might not be the case with some weird text fonts. - fn new_inline_metrics(&self, new_fragment: &Fragment, layout_context: &LayoutContext) - -> InlineMetrics { + fn new_metrics_for_fragment(&self, new_fragment: &Fragment, layout_context: &LayoutContext) + -> LineMetrics { if !new_fragment.is_vertically_aligned_to_top_or_bottom() { - let fragment_inline_metrics = new_fragment.inline_metrics(layout_context); - self.inline_metrics.max(&fragment_inline_metrics) + let fragment_inline_metrics = + new_fragment.aligned_inline_metrics(layout_context, &self.minimum_metrics, None); + self.metrics.new_metrics_for_fragment(&fragment_inline_metrics) } else { - self.inline_metrics + self.metrics } } /// Returns the new block size that this line would have if `new_fragment` were added to it. /// `new_inline_metrics` represents the new inline metrics that this line would have; it can /// be computed with `new_inline_metrics()`. - fn new_block_size(&self, - new_fragment: &Fragment, - new_inline_metrics: &InlineMetrics, - layout_context: &LayoutContext) - -> Au { + fn new_block_size_for_fragment(&self, + new_fragment: &Fragment, + new_line_metrics: &LineMetrics, + layout_context: &LayoutContext) + -> Au { let new_block_size = if new_fragment.is_vertically_aligned_to_top_or_bottom() { - max(new_fragment.inline_metrics(layout_context).block_size(), - self.minimum_block_size_above_baseline + self.minimum_depth_below_baseline) + max(new_fragment.aligned_inline_metrics(layout_context, &self.minimum_metrics, None) + .space_needed(), + self.minimum_metrics.space_needed()) } else { - new_inline_metrics.block_size() + new_line_metrics.space_needed() }; max(self.bounds.size.block, new_block_size) } @@ -243,34 +231,24 @@ struct LineBreaker { cur_b: Au, /// The computed value of the indentation for the first line (`text-indent`, CSS 2.1 § 16.1). first_line_indentation: Au, - /// The minimum block-size above the baseline for each line, as specified by the line height - /// and font style. - minimum_block_size_above_baseline: Au, - /// The minimum depth below the baseline for each line, as specified by the line height and - /// font style. - minimum_depth_below_baseline: Au, + /// The minimum metrics for each line, as specified by the line height and font style. + minimum_metrics: LineMetrics, } impl LineBreaker { /// Creates a new `LineBreaker` with a set of floats and the indentation of the first line. - fn new(float_context: Floats, - first_line_indentation: Au, - minimum_block_size_above_baseline: Au, - minimum_depth_below_baseline: Au) + fn new(float_context: Floats, first_line_indentation: Au, minimum_line_metrics: &LineMetrics) -> LineBreaker { LineBreaker { new_fragments: Vec::new(), work_list: VecDeque::new(), - pending_line: Line::new(float_context.writing_mode, - minimum_block_size_above_baseline, - minimum_depth_below_baseline), + pending_line: Line::new(float_context.writing_mode, minimum_line_metrics), floats: float_context, lines: Vec::new(), cur_b: Au(0), last_known_line_breaking_opportunity: None, first_line_indentation: first_line_indentation, - minimum_block_size_above_baseline: minimum_block_size_above_baseline, - minimum_depth_below_baseline: minimum_depth_below_baseline, + minimum_metrics: *minimum_line_metrics, } } @@ -285,9 +263,8 @@ impl LineBreaker { /// Reinitializes the pending line to blank data. fn reset_line(&mut self) -> Line { self.last_known_line_breaking_opportunity = None; - mem::replace(&mut self.pending_line, Line::new(self.floats.writing_mode, - self.minimum_block_size_above_baseline, - self.minimum_depth_below_baseline)) + mem::replace(&mut self.pending_line, + Line::new(self.floats.writing_mode, &self.minimum_metrics)) } /// Reflows fragments for the given inline flow. @@ -574,10 +551,11 @@ impl LineBreaker { // `green_zone.block < self.pending_line.bounds.size.block`, then we committed a line that // overlaps with floats. let green_zone = self.pending_line.green_zone; - let new_inline_metrics = self.pending_line.new_inline_metrics(&fragment, layout_context); - let new_block_size = self.pending_line.new_block_size(&fragment, - &new_inline_metrics, - layout_context); + let new_line_metrics = self.pending_line.new_metrics_for_fragment(&fragment, + layout_context); + let new_block_size = self.pending_line.new_block_size_for_fragment(&fragment, + &new_line_metrics, + layout_context); if new_block_size > green_zone.block { // Uh-oh. Float collision imminent. Enter the float collision avoider! if !self.avoid_floats(flow, fragment, new_block_size) { @@ -747,14 +725,13 @@ impl LineBreaker { if !fragment.is_inline_absolute() && !fragment.is_hypothetical() { self.pending_line.bounds.size.inline = self.pending_line.bounds.size.inline + - fragment.margin_box_inline_size() + - indentation; - self.pending_line.inline_metrics = - self.pending_line.new_inline_metrics(&fragment, layout_context); + fragment.margin_box_inline_size() + indentation; + self.pending_line.metrics = self.pending_line.new_metrics_for_fragment(&fragment, + layout_context); self.pending_line.bounds.size.block = - self.pending_line.new_block_size(&fragment, - &self.pending_line.inline_metrics, - layout_context); + self.pending_line.new_block_size_for_fragment(&fragment, + &self.pending_line.metrics, + layout_context); } self.new_fragments.push(fragment); @@ -864,13 +841,8 @@ pub struct InlineFlow { /// lines. pub lines: Vec, - /// The minimum block-size above the baseline for each line, as specified by the line height - /// and font style. - pub minimum_block_size_above_baseline: Au, - - /// The minimum depth below the baseline for each line, as specified by the line height and - /// font style. - pub minimum_depth_below_baseline: Au, + /// The minimum metrics for each line, as specified by the line height and font style. + pub minimum_line_metrics: LineMetrics, /// The amount of indentation to use on the first line. This is determined by our block parent /// (because percentages are relative to the containing block, and we aren't in a position to @@ -884,8 +856,7 @@ impl InlineFlow { base: BaseFlow::new(None, writing_mode, ForceNonfloatedFlag::ForceNonfloated), fragments: fragments, lines: Vec::new(), - minimum_block_size_above_baseline: Au(0), - minimum_depth_below_baseline: Au(0), + minimum_line_metrics: LineMetrics::new(Au(0), Au(0)), first_line_indentation: Au(0), }; @@ -1050,90 +1021,62 @@ impl InlineFlow { /// Sets final fragment positions in the block direction for one line. fn set_block_fragment_positions(fragments: &mut InlineFragments, line: &Line, - minimum_block_size_above_baseline: Au, - minimum_depth_below_baseline: Au, + minimum_line_metrics: &LineMetrics, layout_context: &LayoutContext) { for fragment_index in line.range.each_index() { - // If any of the inline styles say `top` or `bottom`, adjust the vertical align - // appropriately. - // - // FIXME(#5624, pcwalton): This passes our current reftests but isn't the right thing - // to do. let fragment = fragments.get_mut(fragment_index.to_usize()); - let fragment_inline_metrics = fragment.inline_metrics(layout_context); - let line_block_metrics = LineBlockMetrics::new(line, fragment, layout_context); - let mut block_start = line_block_metrics.start + - line_block_metrics.size_above_baseline - - fragment_inline_metrics.ascent; + let line_metrics = LineMetrics::for_line_and_fragment(line, fragment, layout_context); + let inline_metrics = fragment.aligned_inline_metrics(layout_context, + minimum_line_metrics, + Some(&line_metrics)); - for style in fragment.inline_styles() { - match style.get_box().vertical_align { - vertical_align::T::baseline => {} - vertical_align::T::middle => {} - vertical_align::T::sub => { - let sub_offset = - (minimum_block_size_above_baseline + - minimum_depth_below_baseline).scale_by(FONT_SUBSCRIPT_OFFSET_RATIO); - block_start = block_start + sub_offset - } - vertical_align::T::super_ => { - let super_offset = - (minimum_block_size_above_baseline + - minimum_depth_below_baseline).scale_by(FONT_SUPERSCRIPT_OFFSET_RATIO); - block_start = block_start - super_offset - } - vertical_align::T::text_top => { - block_start = line_block_metrics.start + - line_block_metrics.size_above_baseline - - minimum_block_size_above_baseline - } - vertical_align::T::text_bottom => { - block_start = line_block_metrics.start + - line_block_metrics.size_above_baseline + - minimum_depth_below_baseline - - fragment.border_box.size.block - } - vertical_align::T::top => { - block_start = line_block_metrics.start - } - vertical_align::T::bottom => { - block_start = line_block_metrics.start + line_block_metrics.size - - fragment.border_box.size.block - } - vertical_align::T::LengthOrPercentage(LengthOrPercentage::Length(length)) => { - block_start = block_start - length - } - vertical_align::T::LengthOrPercentage(LengthOrPercentage::Percentage( - percentage)) => { - let line_height = fragment.calculate_line_height(layout_context); - let length = line_height.scale_by(percentage); - block_start = block_start - length - } - vertical_align::T::LengthOrPercentage(LengthOrPercentage::Calc(calc)) => { - let line_height = fragment.calculate_line_height(layout_context); - let percentage_length = line_height.scale_by(calc.percentage()); - block_start = block_start - percentage_length - calc.length() - } - } + // Align the top of the fragment's border box with its ascent above the baseline. + fragment.border_box.start.b = line.bounds.start.b + line_metrics.space_above_baseline - + inline_metrics.ascent; + + // CSS 2.1 § 10.8: "The height of each inline-level box in the line box is + // calculated. For replaced elements, inline-block elements, and inline-table + // elements, this is the height of their margin box; for inline boxes, this is their + // 'line-height'." + // + // CSS 2.1 § 10.8.1: "Although margins, borders, and padding of non-replaced elements + // do not enter into the line box calculation, they are still rendered around inline + // boxes." + // + // Effectively, if the fragment is a non-replaced element (excluding inline-block), we + // need to align its ascent above the baseline with the top of the *content box*, not + // the border box. Since the code above has already aligned it to the border box, we + // simply need to adjust it in this case. + if !fragment.is_replaced_or_inline_block() { + fragment.border_box.start.b -= fragment.border_padding.block_start } - fragment.border_box.start.b = block_start; fragment.update_late_computed_block_position_if_necessary(); } } - /// Computes the minimum ascent and descent for each line. This is done during flow - /// construction. + /// Computes the minimum metrics for each line. This is done during flow construction. /// /// `style` is the style of the block. - pub fn compute_minimum_ascent_and_descent(&self, + pub fn minimum_line_metrics(&self, font_context: &mut FontContext, style: &ServoComputedValues) + -> LineMetrics { + InlineFlow::minimum_line_metrics_for_fragments(&self.fragments.fragments, + font_context, + style) + } + + /// Computes the minimum line metrics for the given fragments. This is typically done during + /// flow construction. + /// + /// `style` is the style of the block that these fragments belong to. + pub fn minimum_line_metrics_for_fragments(fragments: &[Fragment], font_context: &mut FontContext, style: &ServoComputedValues) - -> (Au, Au) { + -> LineMetrics { // As a special case, if this flow contains only hypothetical fragments, then the entire // flow is hypothetical and takes up no space. See CSS 2.1 § 10.3.7. - if self.fragments.fragments.iter().all(|fragment| fragment.is_hypothetical()) { - return (Au(0), Au(0)) + if fragments.iter().all(Fragment::is_hypothetical) { + return LineMetrics::new(Au(0), Au(0)) } let font_style = style.get_font_arc(); @@ -1141,86 +1084,73 @@ impl InlineFlow { let line_height = text::line_height_from_style(style, &font_metrics); let inline_metrics = InlineMetrics::from_font_metrics(&font_metrics, line_height); - let mut block_size_above_baseline = Au(0); - let mut depth_below_baseline = Au(i32::MIN); + let mut line_metrics = LineMetrics::new(Au(0), Au(i32::MIN)); let mut largest_block_size_for_top_fragments = Au(0); let mut largest_block_size_for_bottom_fragments = Au(0); // We use `vertical_align::T::baseline` here because `vertical-align` must not apply to // the inside of inline blocks. - update_inline_metrics(&inline_metrics, - style.get_box().display, - vertical_align::T::baseline, - &mut block_size_above_baseline, - &mut depth_below_baseline, - &mut largest_block_size_for_top_fragments, - &mut largest_block_size_for_bottom_fragments); + update_line_metrics_for_fragment(&mut line_metrics, + &inline_metrics, + style.get_box().display, + vertical_align::T::baseline, + &mut largest_block_size_for_top_fragments, + &mut largest_block_size_for_bottom_fragments); // According to CSS 2.1 § 10.8, `line-height` of any inline element specifies the minimal // height of line boxes within the element. - for frag in &self.fragments.fragments { - if let Some(ref inline_context) = frag.inline_context { - for node in &inline_context.nodes { - let font_style = node.style.get_font_arc(); - let font_metrics = text::font_metrics_for_style(font_context, font_style); - let line_height = text::line_height_from_style(&*node.style, &font_metrics); - let inline_metrics = InlineMetrics::from_font_metrics(&font_metrics, - line_height); + for inline_context in fragments.iter() + .filter_map(|fragment| fragment.inline_context.as_ref()) { + for node in &inline_context.nodes { + let font_style = node.style.get_font_arc(); + let font_metrics = text::font_metrics_for_style(font_context, font_style); + let line_height = text::line_height_from_style(&*node.style, &font_metrics); + let inline_metrics = InlineMetrics::from_font_metrics(&font_metrics, line_height); - update_inline_metrics(&inline_metrics, - node.style.get_box().display, - node.style.get_box().vertical_align, - &mut block_size_above_baseline, - &mut depth_below_baseline, - &mut largest_block_size_for_top_fragments, - &mut largest_block_size_for_bottom_fragments); + update_line_metrics_for_fragment(&mut line_metrics, + &inline_metrics, + node.style.get_box().display, + node.style.get_box().vertical_align, + &mut largest_block_size_for_top_fragments, + &mut largest_block_size_for_bottom_fragments); - } } } - block_size_above_baseline = - max(block_size_above_baseline, - largest_block_size_for_bottom_fragments - max(depth_below_baseline, Au(0))); - depth_below_baseline = - max(depth_below_baseline, - largest_block_size_for_top_fragments - block_size_above_baseline); + line_metrics.space_above_baseline = + max(line_metrics.space_above_baseline, + largest_block_size_for_bottom_fragments - max(line_metrics.space_below_baseline, + Au(0))); + line_metrics.space_below_baseline = + max(line_metrics.space_below_baseline, + largest_block_size_for_top_fragments - line_metrics.space_above_baseline); - return (block_size_above_baseline, depth_below_baseline); + return line_metrics; - fn update_inline_metrics(inline_metrics: &InlineMetrics, - display_value: display::T, - vertical_align_value: vertical_align::T, - block_size_above_baseline: &mut Au, - depth_below_baseline: &mut Au, - largest_block_size_for_top_fragments: &mut Au, - largest_block_size_for_bottom_fragments: &mut Au) { + fn update_line_metrics_for_fragment(line_metrics: &mut LineMetrics, + inline_metrics: &InlineMetrics, + display_value: display::T, + vertical_align_value: vertical_align::T, + largest_block_size_for_top_fragments: &mut Au, + largest_block_size_for_bottom_fragments: &mut Au) { match (display_value, vertical_align_value) { (display::T::inline, vertical_align::T::top) | (display::T::block, vertical_align::T::top) | (display::T::inline_block, vertical_align::T::top) if - inline_metrics.block_size_above_baseline >= Au(0) => { - *largest_block_size_for_top_fragments = - max(*largest_block_size_for_top_fragments, - inline_metrics.block_size_above_baseline + - inline_metrics.depth_below_baseline) + inline_metrics.space_above_baseline >= Au(0) => { + *largest_block_size_for_top_fragments = max( + *largest_block_size_for_top_fragments, + inline_metrics.space_above_baseline + inline_metrics.space_below_baseline) } (display::T::inline, vertical_align::T::bottom) | (display::T::block, vertical_align::T::bottom) | (display::T::inline_block, vertical_align::T::bottom) if - inline_metrics.depth_below_baseline >= Au(0) => { - *largest_block_size_for_bottom_fragments = - max(*largest_block_size_for_bottom_fragments, - inline_metrics.block_size_above_baseline + - inline_metrics.depth_below_baseline) - } - _ => { - *block_size_above_baseline = - max(*block_size_above_baseline, - inline_metrics.block_size_above_baseline); - *depth_below_baseline = max(*depth_below_baseline, - inline_metrics.depth_below_baseline); + inline_metrics.space_below_baseline >= Au(0) => { + *largest_block_size_for_bottom_fragments = max( + *largest_block_size_for_bottom_fragments, + inline_metrics.space_above_baseline + inline_metrics.space_below_baseline) } + _ => *line_metrics = line_metrics.new_metrics_for_fragment(inline_metrics), } } } @@ -1284,7 +1214,7 @@ impl InlineFlow { pub fn baseline_offset_of_last_line(&self) -> Option { self.last_line_containing_real_fragments().map(|line| { - line.bounds.start.b + line.bounds.size.block - line.inline_metrics.depth_below_baseline + line.bounds.start.b + line.bounds.size.block - line.metrics.space_below_baseline }) } @@ -1476,8 +1406,7 @@ impl Flow for InlineFlow { // Perform line breaking. let mut scanner = LineBreaker::new(self.base.floats.clone(), indentation, - self.minimum_block_size_above_baseline, - self.minimum_depth_below_baseline); + &self.minimum_line_metrics); scanner.scan_for_lines(self, layout_context); // Now, go through each line and lay out the fragments inside. @@ -1493,8 +1422,7 @@ impl Flow for InlineFlow { // Compute the final positions in the block direction of each fragment. InlineFlow::set_block_fragment_positions(&mut self.fragments, line, - self.minimum_block_size_above_baseline, - self.minimum_depth_below_baseline, + &self.minimum_line_metrics, layout_context); // This is used to set the block-start position of the next line in the next iteration @@ -1849,22 +1777,28 @@ fn inline_contexts_are_equal(inline_context_a: &Option, } } -/// Block-size above the baseline, depth below the baseline, and ascent for a fragment. See CSS 2.1 -/// § 10.8.1. +/// Ascent and space needed above and below the baseline for a fragment. See CSS 2.1 § 10.8.1. +/// +/// Descent is not included in this structure because it can be computed from the fragment's +/// border/content box and the ascent. #[derive(Clone, Copy, Debug, RustcEncodable)] pub struct InlineMetrics { - pub block_size_above_baseline: Au, - pub depth_below_baseline: Au, + /// The amount of space above the baseline needed for this fragment. + pub space_above_baseline: Au, + /// The amount of space below the baseline needed for this fragment. + pub space_below_baseline: Au, + /// The distance from the baseline to the top of this fragment. This can differ from + /// `block_size_above_baseline` if the fragment needs some empty space above it due to + /// line-height, etc. pub ascent: Au, } impl InlineMetrics { /// Creates a new set of inline metrics. - pub fn new(block_size_above_baseline: Au, depth_below_baseline: Au, ascent: Au) - -> InlineMetrics { + pub fn new(space_above_baseline: Au, space_below_baseline: Au, ascent: Au) -> InlineMetrics { InlineMetrics { - block_size_above_baseline: block_size_above_baseline, - depth_below_baseline: depth_below_baseline, + space_above_baseline: space_above_baseline, + space_below_baseline: space_below_baseline, ascent: ascent, } } @@ -1873,29 +1807,22 @@ impl InlineMetrics { #[inline] pub fn from_font_metrics(font_metrics: &FontMetrics, line_height: Au) -> InlineMetrics { let leading = line_height - (font_metrics.ascent + font_metrics.descent); + // Calculating the half leading here and then using leading - half_leading // below ensure that we don't introduce any rounding accuracy issues here. // The invariant is that the resulting total line height must exactly // equal the requested line_height. let half_leading = leading.scale_by(0.5); InlineMetrics { - block_size_above_baseline: font_metrics.ascent + half_leading, - depth_below_baseline: font_metrics.descent + leading - half_leading, + space_above_baseline: font_metrics.ascent + half_leading, + space_below_baseline: font_metrics.descent + leading - half_leading, ascent: font_metrics.ascent, } } - pub fn block_size(&self) -> Au { - self.block_size_above_baseline + self.depth_below_baseline - } - - pub fn max(&self, other: &InlineMetrics) -> InlineMetrics { - InlineMetrics { - block_size_above_baseline: max(self.block_size_above_baseline, - other.block_size_above_baseline), - depth_below_baseline: max(self.depth_below_baseline, other.depth_below_baseline), - ascent: max(self.ascent, other.ascent), - } + /// Returns the sum of the space needed above and below the baseline. + fn space_needed(&self) -> Au { + self.space_above_baseline + self.space_below_baseline } } @@ -1905,31 +1832,55 @@ enum LineFlushMode { Flush, } -struct LineBlockMetrics { - start: Au, - size: Au, - size_above_baseline: Au, +#[derive(Copy, Clone, Debug, RustcEncodable)] +pub struct LineMetrics { + pub space_above_baseline: Au, + pub space_below_baseline: Au, } -impl LineBlockMetrics { - fn new(line: &Line, fragment: &Fragment, layout_context: &LayoutContext) -> LineBlockMetrics { +impl LineMetrics { + pub fn new(space_above_baseline: Au, space_below_baseline: Au) -> LineMetrics { + LineMetrics { + space_above_baseline: space_above_baseline, + space_below_baseline: space_below_baseline, + } + } + + /// Returns the line metrics that result from combining the line that these metrics represent + /// with a fragment with the given metrics. + fn new_metrics_for_fragment(&self, fragment_inline_metrics: &InlineMetrics) -> LineMetrics { + LineMetrics { + space_above_baseline: max(self.space_above_baseline, + fragment_inline_metrics.space_above_baseline), + space_below_baseline: max(self.space_below_baseline, + fragment_inline_metrics.space_below_baseline), + } + } + + fn for_line_and_fragment(line: &Line, fragment: &Fragment, layout_context: &LayoutContext) + -> LineMetrics { if !fragment.is_hypothetical() { - return LineBlockMetrics { - start: line.bounds.start.b, - size: line.bounds.size.block, - size_above_baseline: line.inline_metrics.block_size_above_baseline, + let space_above_baseline = line.metrics.space_above_baseline; + return LineMetrics { + space_above_baseline: space_above_baseline, + space_below_baseline: line.bounds.size.block - space_above_baseline, } } - let hypothetical_inline_metrics = line.new_inline_metrics(fragment, layout_context); - let hypothetical_block_size = line.new_block_size(fragment, - &hypothetical_inline_metrics, - layout_context); - LineBlockMetrics { - start: line.bounds.start.b, - size: hypothetical_block_size, - size_above_baseline: hypothetical_inline_metrics.block_size_above_baseline, + let hypothetical_line_metrics = line.new_metrics_for_fragment(fragment, layout_context); + let hypothetical_block_size = line.new_block_size_for_fragment(fragment, + &hypothetical_line_metrics, + layout_context); + let hypothetical_space_above_baseline = hypothetical_line_metrics.space_above_baseline; + LineMetrics { + space_above_baseline: hypothetical_space_above_baseline, + space_below_baseline: hypothetical_block_size - hypothetical_space_above_baseline, } } + + /// Returns the sum of the space needed above and below the baseline. + pub fn space_needed(&self) -> Au { + self.space_above_baseline + self.space_below_baseline + } } diff --git a/servo/components/layout/list_item.rs b/servo/components/layout/list_item.rs index 9bcb38cd5ade..75f420160768 100644 --- a/servo/components/layout/list_item.rs +++ b/servo/components/layout/list_item.rs @@ -18,14 +18,13 @@ use fragment::{CoordinateSystem, Fragment, FragmentBorderBoxIterator, GeneratedC use fragment::Overflow; use generated_content; use gfx::display_list::StackingContext; -use inline::InlineMetrics; +use inline::InlineFlow; use script_layout_interface::restyle_damage::RESOLVE_GENERATED_CONTENT; use std::sync::Arc; use style::computed_values::{list_style_type, position}; use style::context::SharedStyleContext; use style::logical_geometry::LogicalSize; use style::properties::ServoComputedValues; -use text; /// A block with the CSS `display` property equal to `list-item`. #[derive(Debug)] @@ -105,21 +104,20 @@ impl Flow for ListItemFlow { fn assign_block_size<'a>(&mut self, layout_context: &'a LayoutContext<'a>) { self.block_flow.assign_block_size(layout_context); + // FIXME(pcwalton): Do this during flow construction, like `InlineFlow` does? + let marker_line_metrics = + InlineFlow::minimum_line_metrics_for_fragments(&self.marker_fragments, + &mut layout_context.font_context(), + &*self.block_flow.fragment.style); for marker in &mut self.marker_fragments { let containing_block_block_size = self.block_flow.base.block_container_explicit_block_size; marker.assign_replaced_block_size_if_necessary(containing_block_block_size); - - let font_metrics = - text::font_metrics_for_style(&mut layout_context.font_context(), - marker.style.get_font_arc()); - let line_height = text::line_height_from_style(&*marker.style, &font_metrics); - let item_inline_metrics = InlineMetrics::from_font_metrics(&font_metrics, line_height); - let marker_inline_metrics = marker.inline_metrics(layout_context); - marker.border_box.start.b = item_inline_metrics.block_size_above_baseline - + let marker_inline_metrics = marker.aligned_inline_metrics(layout_context, + &marker_line_metrics, + Some(&marker_line_metrics)); + marker.border_box.start.b = marker_line_metrics.space_above_baseline - marker_inline_metrics.ascent; - marker.border_box.size.block = marker_inline_metrics.ascent + - marker_inline_metrics.depth_below_baseline; } } diff --git a/servo/resources/servo.css b/servo/resources/servo.css index e5ed6f1bfed3..2d187e49841f 100644 --- a/servo/resources/servo.css +++ b/servo/resources/servo.css @@ -42,7 +42,6 @@ input[type="reset"] { border-right: solid 1px #999999; border-bottom: solid 1px #999999; text-align: center; - vertical-align: middle; color: black; }