diff options
author | Simon Sapin <simon.sapin@exyr.org> | 2014-08-29 17:51:26 +0100 |
---|---|---|
committer | Simon Sapin <simon.sapin@exyr.org> | 2014-08-29 17:51:26 +0100 |
commit | db05417a25b493a06a08c15524d75910757cd54f (patch) | |
tree | c342e52a6e4d91cf968b1f33c37815fe04d41379 /src | |
parent | e40f54bfefe29af943c1b4aebe9cfdf127fe0050 (diff) | |
parent | 80704aeabd1c8882760f8efc49b8bc5abca91a60 (diff) | |
download | servo-db05417a25b493a06a08c15524d75910757cd54f.tar.gz servo-db05417a25b493a06a08c15524d75910757cd54f.zip |
Merge pull request #3181 from SimonSapin/sideways-text
Sideways text
Diffstat (limited to 'src')
-rw-r--r-- | src/components/gfx/display_list/mod.rs | 125 | ||||
-rw-r--r-- | src/components/gfx/render_task.rs | 2 | ||||
-rw-r--r-- | src/components/layout/fragment.rs | 126 | ||||
-rw-r--r-- | src/components/layout/inline.rs | 11 | ||||
-rw-r--r-- | src/components/layout/text.rs | 40 | ||||
-rw-r--r-- | src/components/style/style.rs | 1 | ||||
-rw-r--r-- | src/components/util/logical_geometry.rs | 5 |
7 files changed, 185 insertions, 125 deletions
diff --git a/src/components/gfx/display_list/mod.rs b/src/components/gfx/display_list/mod.rs index 0ad7d945ae1..b50689422ac 100644 --- a/src/components/gfx/display_list/mod.rs +++ b/src/components/gfx/display_list/mod.rs @@ -21,7 +21,7 @@ use text::TextRun; use collections::dlist::DList; use collections::dlist; -use geom::{Point2D, Rect, SideOffsets2D, Size2D}; +use geom::{Point2D, Rect, SideOffsets2D, Size2D, Matrix2D}; use libc::uintptr_t; use servo_net::image::base::Image; use servo_util::geometry::Au; @@ -337,10 +337,11 @@ impl DisplayList { /// Draws the display list into the given render context. The display list must be flattened /// first for correct painting. - pub fn draw_into_context(&self, render_context: &mut RenderContext) { + pub fn draw_into_context(&self, render_context: &mut RenderContext, + current_transform: &Matrix2D<AzFloat>) { debug!("Beginning display list."); for item in self.list.iter() { - item.draw_into_context(render_context) + item.draw_into_context(render_context, current_transform) } debug!("Ending display list."); } @@ -469,17 +470,6 @@ pub struct SolidColorDisplayItem { pub color: Color, } -/// Text decoration information. -#[deriving(Clone)] -pub struct TextDecorations { - /// The color to use for underlining, if any. - pub underline: Option<Color>, - /// The color to use for overlining, if any. - pub overline: Option<Color>, - /// The color to use for line-through, if any. - pub line_through: Option<Color>, -} - /// Renders text. #[deriving(Clone)] pub struct TextDisplayItem { @@ -495,8 +485,15 @@ pub struct TextDisplayItem { /// The color of the text. pub text_color: Color, - /// Text decorations in effect. - pub text_decorations: TextDecorations, + pub baseline_origin: Point2D<Au>, + pub orientation: TextOrientation, +} + +#[deriving(Clone, Eq, PartialEq)] +pub enum TextOrientation { + Upright, + SidewaysLeft, + SidewaysRight, } /// Renders an image. @@ -574,7 +571,8 @@ impl<'a> Iterator<&'a DisplayItem> for DisplayItemIterator<'a> { impl DisplayItem { /// Renders this display item into the given render context. - fn draw_into_context(&self, render_context: &mut RenderContext) { + fn draw_into_context(&self, render_context: &mut RenderContext, + current_transform: &Matrix2D<AzFloat>) { // This should have been flattened to the content stacking level first. assert!(self.base().level == ContentStackingLevel); @@ -586,56 +584,61 @@ impl DisplayItem { ClipDisplayItemClass(ref clip) => { render_context.draw_push_clip(&clip.base.bounds); for item in clip.children.iter() { - (*item).draw_into_context(render_context); + (*item).draw_into_context(render_context, current_transform); } render_context.draw_pop_clip(); } TextDisplayItemClass(ref text) => { - debug!("Drawing text at {:?}.", text.base.bounds); - - // FIXME(pcwalton): Allocating? Why? - let text_run = text.text_run.clone(); - - let font = render_context.font_ctx.get_render_font_from_template( - &text_run.font_template, - text_run.pt_size, - render_context.opts.render_backend); - let font = font.borrow(); - - let origin = text.base.bounds.origin; - let baseline_origin = Point2D(origin.x, origin.y + text_run.font_metrics.ascent); - { - font.draw_text_into_context(render_context, - &*text.text_run, - &text.range, - baseline_origin, - text.text_color); - } - let width = text.base.bounds.size.width; - let underline_size = text_run.font_metrics.underline_size; - let underline_offset = text_run.font_metrics.underline_offset; - let strikeout_size = text_run.font_metrics.strikeout_size; - let strikeout_offset = text_run.font_metrics.strikeout_offset; - - for underline_color in text.text_decorations.underline.iter() { - let underline_y = baseline_origin.y - underline_offset; - let underline_bounds = Rect(Point2D(baseline_origin.x, underline_y), - Size2D(width, underline_size)); - render_context.draw_solid_color(&underline_bounds, *underline_color); - } - - for overline_color in text.text_decorations.overline.iter() { - let overline_bounds = Rect(Point2D(baseline_origin.x, origin.y), - Size2D(width, underline_size)); - render_context.draw_solid_color(&overline_bounds, *overline_color); - } + debug!("Drawing text at {}.", text.base.bounds); + + // Optimization: Don’t set a transform matrix for upright text, + // and pass a strart point to `draw_text_into_context`. + // For sideways text, it’s easier to do the rotation such that its center + // (the baseline’s start point) is at (0, 0) coordinates. + let baseline_origin = match text.orientation { + Upright => text.baseline_origin, + SidewaysLeft => { + let x = text.baseline_origin.x.to_nearest_px() as AzFloat; + let y = text.baseline_origin.y.to_nearest_px() as AzFloat; + render_context.draw_target.set_transform(¤t_transform.mul( + &Matrix2D::new( + 0., -1., + 1., 0., + x, y + ) + )); + Zero::zero() + }, + SidewaysRight => { + let x = text.baseline_origin.x.to_nearest_px() as AzFloat; + let y = text.baseline_origin.y.to_nearest_px() as AzFloat; + render_context.draw_target.set_transform(¤t_transform.mul( + &Matrix2D::new( + 0., 1., + -1., 0., + x, y + ) + )); + Zero::zero() + } + }; - for line_through_color in text.text_decorations.line_through.iter() { - let strikeout_y = baseline_origin.y - strikeout_offset; - let strikeout_bounds = Rect(Point2D(baseline_origin.x, strikeout_y), - Size2D(width, strikeout_size)); - render_context.draw_solid_color(&strikeout_bounds, *line_through_color); + render_context.font_ctx.get_render_font_from_template( + &text.text_run.font_template, + text.text_run.pt_size, + render_context.opts.render_backend + ).borrow().draw_text_into_context( + render_context, + &*text.text_run, + &text.range, + baseline_origin, + text.text_color + ); + + // Undo the transform, only when we did one. + if text.orientation != Upright { + render_context.draw_target.set_transform(current_transform) } } diff --git a/src/components/gfx/render_task.rs b/src/components/gfx/render_task.rs index 710a08bff9e..2d7b8b5e2d7 100644 --- a/src/components/gfx/render_task.rs +++ b/src/components/gfx/render_task.rs @@ -352,7 +352,7 @@ impl<C:RenderListener + Send> RenderTask<C> { // Draw the display list. profile(time::RenderingDrawingCategory, self.time_profiler_chan.clone(), || { - display_list.draw_into_context(&mut ctx); + display_list.draw_into_context(&mut ctx, &matrix); ctx.draw_target.flush(); }); } diff --git a/src/components/layout/fragment.rs b/src/components/layout/fragment.rs index 42386a6f5a6..f00bb8a7f7c 100644 --- a/src/components/layout/fragment.rs +++ b/src/components/layout/fragment.rs @@ -28,7 +28,8 @@ use gfx::display_list::{ContentStackingLevel, DisplayItem, DisplayList, ImageDis use gfx::display_list::{ImageDisplayItemClass, LineDisplayItem}; use gfx::display_list::{LineDisplayItemClass, OpaqueNode, PseudoDisplayItemClass}; use gfx::display_list::{SolidColorDisplayItem, SolidColorDisplayItemClass, StackingLevel}; -use gfx::display_list::{TextDecorations, TextDisplayItem, TextDisplayItemClass}; +use gfx::display_list::{TextDisplayItem, TextDisplayItemClass}; +use gfx::display_list::{Upright, SidewaysLeft, SidewaysRight}; use gfx::font::FontStyle; use gfx::text::glyph::CharIndex; use gfx::text::text_run::TextRun; @@ -46,7 +47,7 @@ use std::fmt; use std::from_str::FromStr; use std::mem; use std::num::Zero; -use style::{ComputedValues, TElement, TNode, cascade_anonymous}; +use style::{ComputedValues, TElement, TNode, cascade_anonymous, RGBA}; use style::computed_values::{LengthOrPercentageOrAuto, overflow, LPA_Auto, background_attachment}; use style::computed_values::{background_repeat, border_style, clear, position, text_align}; use style::computed_values::{text_decoration, vertical_align, visibility, white_space}; @@ -861,11 +862,12 @@ impl Fragment { -> ChildDisplayListAccumulator { // FIXME(#2795): Get the real container size let container_size = Size2D::zero(); + let rect_to_absolute = |logical_rect: LogicalRect<Au>| { + let physical_rect = logical_rect.to_physical(self.style.writing_mode, container_size); + Rect(physical_rect.origin + flow_origin, physical_rect.size) + }; // Fragment position wrt to the owning flow. - let fragment_bounds = self.border_box.to_physical(self.style.writing_mode, container_size); - let absolute_fragment_bounds = Rect( - fragment_bounds.origin + flow_origin, - fragment_bounds.size); + let absolute_fragment_bounds = rect_to_absolute(self.border_box); debug!("Fragment::build_display_list at rel={}, abs={}: {}", self.border_box, absolute_fragment_bounds, @@ -925,45 +927,85 @@ impl Fragment { level); } + let content_box = self.content_box(); + let absolute_content_box = rect_to_absolute(content_box); + // Add a clip, if applicable. match self.specific { UnscannedTextFragment(_) => fail!("Shouldn't see unscanned fragments here."), TableColumnFragment(_) => fail!("Shouldn't see table column fragments here."), ScannedTextFragment(ref text_fragment) => { - // Compute text color. - let text_color = self.style().get_color().color.to_gfx_color(); - - // Compute text decorations. - let text_decorations_in_effect = self.style() - .get_inheritedtext() - ._servo_text_decorations_in_effect; - let text_decorations = TextDecorations { - underline: text_decorations_in_effect.underline.map(|c| c.to_gfx_color()), - overline: text_decorations_in_effect.overline.map(|c| c.to_gfx_color()), - line_through: text_decorations_in_effect.line_through - .map(|c| c.to_gfx_color()), + // Create the text display item. + let orientation = if self.style.writing_mode.is_vertical() { + if self.style.writing_mode.is_sideways_left() { + SidewaysLeft + } else { + SidewaysRight + } + } else { + Upright + }; + + let metrics = &text_fragment.run.font_metrics; + let baseline_origin ={ + let mut tmp = content_box.start; + tmp.b = tmp.b + metrics.ascent; + tmp.to_physical(self.style.writing_mode, container_size) + flow_origin }; - let mut bounds = absolute_fragment_bounds.clone(); - let mut border_padding = self.border_padding.clone(); - border_padding.block_start = Au::new(0); - border_padding.block_end = Au::new(0); - let border_padding = border_padding.to_physical(self.style.writing_mode); - bounds.origin.x = bounds.origin.x + border_padding.left; - bounds.origin.y = bounds.origin.y + border_padding.top; - bounds.size.width = bounds.size.width - border_padding.horizontal(); - bounds.size.height = bounds.size.height - border_padding.vertical(); - - // Create the text fragment. let text_display_item = box TextDisplayItem { - base: BaseDisplayItem::new(bounds, self.node, ContentStackingLevel), + base: BaseDisplayItem::new( + absolute_content_box, self.node, ContentStackingLevel), text_run: text_fragment.run.clone(), range: text_fragment.range, - text_color: text_color, - text_decorations: text_decorations, + text_color: self.style().get_color().color.to_gfx_color(), + orientation: orientation, + baseline_origin: baseline_origin, }; accumulator.push(display_list, TextDisplayItemClass(text_display_item)); + + // Create display items for text decoration + { + let line = |maybe_color: Option<RGBA>, rect: || -> LogicalRect<Au>| { + match maybe_color { + None => {}, + Some(color) => { + accumulator.push(display_list, SolidColorDisplayItemClass( + box SolidColorDisplayItem { + base: BaseDisplayItem::new( + rect_to_absolute(rect()), + self.node, ContentStackingLevel), + color: color.to_gfx_color(), + } + )); + } + } + }; + + let text_decorations = + self.style().get_inheritedtext()._servo_text_decorations_in_effect; + line(text_decorations.underline, || { + let mut rect = content_box.clone(); + rect.start.b = rect.start.b + metrics.ascent - metrics.underline_offset; + rect.size.block = metrics.underline_size; + rect + }); + + line(text_decorations.overline, || { + let mut rect = content_box.clone(); + rect.size.block = metrics.underline_size; + rect + }); + + line(text_decorations.line_through, || { + let mut rect = content_box.clone(); + rect.start.b = rect.start.b + metrics.ascent - metrics.strikeout_offset; + rect.size.block = metrics.strikeout_size; + rect + }); + } + // Draw debug frames for text bounds. // // FIXME(#2263, pcwalton): This is a bit of an abuse of the logging infrastructure. @@ -979,13 +1021,6 @@ impl Fragment { debug!("{:?}", self.build_debug_borders_around_fragment(display_list, flow_origin)) }, ImageFragment(_) => { - let mut bounds = absolute_fragment_bounds.clone(); - let border_padding = self.border_padding.to_physical(self.style.writing_mode); - bounds.origin.x = bounds.origin.x + border_padding.left; - bounds.origin.y = bounds.origin.y + border_padding.top; - bounds.size.width = bounds.size.width - border_padding.horizontal(); - bounds.size.height = bounds.size.height - border_padding.vertical(); - match self.specific { ImageFragment(ref image_fragment) => { let image_ref = &image_fragment.image; @@ -995,11 +1030,11 @@ impl Fragment { // Place the image into the display list. let image_display_item = box ImageDisplayItem { - base: BaseDisplayItem::new(bounds, + base: BaseDisplayItem::new(absolute_content_box, self.node, ContentStackingLevel), image: image.clone(), - stretch_size: bounds.size, + stretch_size: absolute_content_box.size, }; accumulator.push(display_list, ImageDisplayItemClass(image_display_item)) @@ -1128,13 +1163,7 @@ impl Fragment { /// values are needed and that will save computation. #[inline] pub fn content_box(&self) -> LogicalRect<Au> { - LogicalRect::new( - self.style.writing_mode, - self.border_box.start.i + self.border_padding.inline_start, - self.border_box.start.b + self.border_padding.block_start, - self.border_box.size.inline - self.border_padding.inline_start_end(), - self.border_box.size.block - self.border_padding.block_start_end(), - ) + self.border_box - self.border_padding } /// Find the split of a fragment that includes a new-line character. @@ -1535,4 +1564,3 @@ impl ChildDisplayListAccumulator { flow::mut_base(parent).display_list = display_list } } - diff --git a/src/components/layout/inline.rs b/src/components/layout/inline.rs index bdbabe31f5e..c5157709c27 100644 --- a/src/components/layout/inline.rs +++ b/src/components/layout/inline.rs @@ -1100,13 +1100,10 @@ impl Flow for InlineFlow { line_distance_from_flow_block_start = line_distance_from_flow_block_start + line.bounds.size.block; } // End of `lines.each` loop. - self.base.position.size.block = - if self.lines.len() > 0 { - self.lines.as_slice().last().get_ref().bounds.start.b + - self.lines.as_slice().last().get_ref().bounds.size.block - } else { - Au::new(0) - }; + self.base.position.size.block = match self.lines.as_slice().last() { + Some(ref last_line) => last_line.bounds.start.b + last_line.bounds.size.block, + None => Au::new(0) + }; self.base.floats = scanner.floats(); self.base.floats.translate(LogicalSize::new( diff --git a/src/components/layout/text.rs b/src/components/layout/text.rs index 70b2cc7a3cc..e90272e218a 100644 --- a/src/components/layout/text.rs +++ b/src/components/layout/text.rs @@ -9,16 +9,16 @@ use flow::Flow; use fragment::{Fragment, ScannedTextFragment, ScannedTextFragmentInfo, UnscannedTextFragment}; -use gfx::font::{FontMetrics, FontStyle}; +use gfx::font::{FontMetrics, FontStyle, RunMetrics}; use gfx::font_context::FontContext; use gfx::text::glyph::CharIndex; use gfx::text::text_run::TextRun; use gfx::text::util::{CompressWhitespaceNewline, transform_text, CompressNone}; use servo_util::geometry::Au; -use servo_util::logical_geometry::LogicalSize; +use servo_util::logical_geometry::{LogicalSize, WritingMode}; use servo_util::range::Range; use style::ComputedValues; -use style::computed_values::{font_family, line_height, white_space}; +use style::computed_values::{font_family, line_height, text_orientation, white_space}; use sync::Arc; struct NewLinePositions { @@ -150,8 +150,8 @@ impl TextRunScanner { *text); let range = Range::new(CharIndex(0), run.char_len()); let new_metrics = run.metrics_for_range(&range); - let bounding_box_size = LogicalSize::from_physical( - old_fragment.style.writing_mode, new_metrics.bounding_box.size); + let bounding_box_size = bounding_box_for_run_metrics( + &new_metrics, old_fragment.style.writing_mode); let new_text_fragment_info = ScannedTextFragmentInfo::new(Arc::new(run), range); let mut new_fragment = old_fragment.transform( bounding_box_size, ScannedTextFragment(new_text_fragment_info)); @@ -234,8 +234,8 @@ impl TextRunScanner { let new_text_fragment_info = ScannedTextFragmentInfo::new(run.get_ref().clone(), range); let old_fragment = &in_fragments[i.to_uint()]; let new_metrics = new_text_fragment_info.run.metrics_for_range(&range); - let bounding_box_size = LogicalSize::from_physical( - old_fragment.style.writing_mode, new_metrics.bounding_box.size); + let bounding_box_size = bounding_box_for_run_metrics( + &new_metrics, old_fragment.style.writing_mode); let mut new_fragment = old_fragment.transform( bounding_box_size, ScannedTextFragment(new_text_fragment_info)); new_fragment.new_line_pos = new_line_positions[logical_offset.to_uint()].new_line_pos.clone(); @@ -251,6 +251,32 @@ impl TextRunScanner { } // End of `flush_clump_to_list`. } + +#[inline] +fn bounding_box_for_run_metrics(metrics: &RunMetrics, writing_mode: WritingMode) + -> LogicalSize<Au> { + + // This does nothing, but it will fail to build + // when more values are added to the `text-orientation` CSS property. + // This will be a reminder to update the code below. + let dummy: Option<text_orientation::T> = None; + match dummy { + Some(text_orientation::sideways_right) | + Some(text_orientation::sideways_left) | + Some(text_orientation::sideways) | + None => {} + } + + // In vertical sideways or horizontal upgright text, + // the "width" of text metrics is always inline + // This will need to be updated when other text orientations are supported. + LogicalSize::new( + writing_mode, + metrics.bounding_box.size.width, + metrics.bounding_box.size.height) + +} + /// Returns the metrics of the font represented by the given `FontStyle`, respectively. /// /// `#[inline]` because often the caller only needs a few fields from the font metrics. diff --git a/src/components/style/style.rs b/src/components/style/style.rs index cd3950b3bc2..2c544cccb8b 100644 --- a/src/components/style/style.rs +++ b/src/components/style/style.rs @@ -41,6 +41,7 @@ pub use properties::longhands; pub use node::{TElement, TNode}; pub use selectors::{PseudoElement, Before, After, SelectorList, parse_selector_list_from_str}; pub use selectors::{AttrSelector, NamespaceConstraint, SpecificNamespace, AnyNamespace}; +pub use cssparser::{Color, RGBA}; mod stylesheets; mod errors; diff --git a/src/components/util/logical_geometry.rs b/src/components/util/logical_geometry.rs index e2cfde31f31..8f2356a5f88 100644 --- a/src/components/util/logical_geometry.rs +++ b/src/components/util/logical_geometry.rs @@ -41,6 +41,11 @@ impl WritingMode { pub fn is_bidi_ltr(&self) -> bool { !self.intersects(FlagRTL) } + + #[inline] + pub fn is_sideways_left(&self) -> bool { + self.intersects(FlagSidewaysLeft) + } } impl Show for WritingMode { |