Merge pull request #3181 from SimonSapin/sideways-text

Sideways text
This commit is contained in:
Simon Sapin 2014-08-29 17:51:26 +01:00
commit db05417a25
7 changed files with 182 additions and 122 deletions

View file

@ -21,7 +21,7 @@ use text::TextRun;
use collections::dlist::DList; use collections::dlist::DList;
use collections::dlist; use collections::dlist;
use geom::{Point2D, Rect, SideOffsets2D, Size2D}; use geom::{Point2D, Rect, SideOffsets2D, Size2D, Matrix2D};
use libc::uintptr_t; use libc::uintptr_t;
use servo_net::image::base::Image; use servo_net::image::base::Image;
use servo_util::geometry::Au; 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 /// Draws the display list into the given render context. The display list must be flattened
/// first for correct painting. /// 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."); debug!("Beginning display list.");
for item in self.list.iter() { for item in self.list.iter() {
item.draw_into_context(render_context) item.draw_into_context(render_context, current_transform)
} }
debug!("Ending display list."); debug!("Ending display list.");
} }
@ -469,17 +470,6 @@ pub struct SolidColorDisplayItem {
pub color: Color, 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. /// Renders text.
#[deriving(Clone)] #[deriving(Clone)]
pub struct TextDisplayItem { pub struct TextDisplayItem {
@ -495,8 +485,15 @@ pub struct TextDisplayItem {
/// The color of the text. /// The color of the text.
pub text_color: Color, pub text_color: Color,
/// Text decorations in effect. pub baseline_origin: Point2D<Au>,
pub text_decorations: TextDecorations, pub orientation: TextOrientation,
}
#[deriving(Clone, Eq, PartialEq)]
pub enum TextOrientation {
Upright,
SidewaysLeft,
SidewaysRight,
} }
/// Renders an image. /// Renders an image.
@ -574,7 +571,8 @@ impl<'a> Iterator<&'a DisplayItem> for DisplayItemIterator<'a> {
impl DisplayItem { impl DisplayItem {
/// Renders this display item into the given render context. /// 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. // This should have been flattened to the content stacking level first.
assert!(self.base().level == ContentStackingLevel); assert!(self.base().level == ContentStackingLevel);
@ -586,56 +584,61 @@ impl DisplayItem {
ClipDisplayItemClass(ref clip) => { ClipDisplayItemClass(ref clip) => {
render_context.draw_push_clip(&clip.base.bounds); render_context.draw_push_clip(&clip.base.bounds);
for item in clip.children.iter() { 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(); render_context.draw_pop_clip();
} }
TextDisplayItemClass(ref text) => { TextDisplayItemClass(ref text) => {
debug!("Drawing text at {:?}.", text.base.bounds); debug!("Drawing text at {}.", text.base.bounds);
// FIXME(pcwalton): Allocating? Why? // Optimization: Dont set a transform matrix for upright text,
let text_run = text.text_run.clone(); // and pass a strart point to `draw_text_into_context`.
// For sideways text, its easier to do the rotation such that its center
// (the baselines 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(&current_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(&current_transform.mul(
&Matrix2D::new(
0., 1.,
-1., 0.,
x, y
)
));
Zero::zero()
}
};
let font = render_context.font_ctx.get_render_font_from_template( render_context.font_ctx.get_render_font_from_template(
&text_run.font_template, &text.text_run.font_template,
text_run.pt_size, text.text_run.pt_size,
render_context.opts.render_backend); render_context.opts.render_backend
let font = font.borrow(); ).borrow().draw_text_into_context(
render_context,
&*text.text_run,
&text.range,
baseline_origin,
text.text_color
);
let origin = text.base.bounds.origin; // Undo the transform, only when we did one.
let baseline_origin = Point2D(origin.x, origin.y + text_run.font_metrics.ascent); if text.orientation != Upright {
{ render_context.draw_target.set_transform(current_transform)
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);
}
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);
} }
} }

View file

@ -352,7 +352,7 @@ impl<C:RenderListener + Send> RenderTask<C> {
// Draw the display list. // Draw the display list.
profile(time::RenderingDrawingCategory, self.time_profiler_chan.clone(), || { 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(); ctx.draw_target.flush();
}); });
} }

View file

@ -28,7 +28,8 @@ use gfx::display_list::{ContentStackingLevel, DisplayItem, DisplayList, ImageDis
use gfx::display_list::{ImageDisplayItemClass, LineDisplayItem}; use gfx::display_list::{ImageDisplayItemClass, LineDisplayItem};
use gfx::display_list::{LineDisplayItemClass, OpaqueNode, PseudoDisplayItemClass}; use gfx::display_list::{LineDisplayItemClass, OpaqueNode, PseudoDisplayItemClass};
use gfx::display_list::{SolidColorDisplayItem, SolidColorDisplayItemClass, StackingLevel}; 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::font::FontStyle;
use gfx::text::glyph::CharIndex; use gfx::text::glyph::CharIndex;
use gfx::text::text_run::TextRun; use gfx::text::text_run::TextRun;
@ -46,7 +47,7 @@ use std::fmt;
use std::from_str::FromStr; use std::from_str::FromStr;
use std::mem; use std::mem;
use std::num::Zero; 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::{LengthOrPercentageOrAuto, overflow, LPA_Auto, background_attachment};
use style::computed_values::{background_repeat, border_style, clear, position, text_align}; use style::computed_values::{background_repeat, border_style, clear, position, text_align};
use style::computed_values::{text_decoration, vertical_align, visibility, white_space}; use style::computed_values::{text_decoration, vertical_align, visibility, white_space};
@ -861,11 +862,12 @@ impl Fragment {
-> ChildDisplayListAccumulator { -> ChildDisplayListAccumulator {
// FIXME(#2795): Get the real container size // FIXME(#2795): Get the real container size
let container_size = Size2D::zero(); 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. // 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_to_absolute(self.border_box);
let absolute_fragment_bounds = Rect(
fragment_bounds.origin + flow_origin,
fragment_bounds.size);
debug!("Fragment::build_display_list at rel={}, abs={}: {}", debug!("Fragment::build_display_list at rel={}, abs={}: {}",
self.border_box, self.border_box,
absolute_fragment_bounds, absolute_fragment_bounds,
@ -925,45 +927,85 @@ impl Fragment {
level); level);
} }
let content_box = self.content_box();
let absolute_content_box = rect_to_absolute(content_box);
// Add a clip, if applicable. // Add a clip, if applicable.
match self.specific { match self.specific {
UnscannedTextFragment(_) => fail!("Shouldn't see unscanned fragments here."), UnscannedTextFragment(_) => fail!("Shouldn't see unscanned fragments here."),
TableColumnFragment(_) => fail!("Shouldn't see table column fragments here."), TableColumnFragment(_) => fail!("Shouldn't see table column fragments here."),
ScannedTextFragment(ref text_fragment) => { ScannedTextFragment(ref text_fragment) => {
// Compute text color. // Create the text display item.
let text_color = self.style().get_color().color.to_gfx_color(); let orientation = if self.style.writing_mode.is_vertical() {
if self.style.writing_mode.is_sideways_left() {
// Compute text decorations. SidewaysLeft
let text_decorations_in_effect = self.style() } else {
.get_inheritedtext() SidewaysRight
._servo_text_decorations_in_effect; }
let text_decorations = TextDecorations { } else {
underline: text_decorations_in_effect.underline.map(|c| c.to_gfx_color()), Upright
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()),
}; };
let mut bounds = absolute_fragment_bounds.clone(); let metrics = &text_fragment.run.font_metrics;
let mut border_padding = self.border_padding.clone(); let baseline_origin ={
border_padding.block_start = Au::new(0); let mut tmp = content_box.start;
border_padding.block_end = Au::new(0); tmp.b = tmp.b + metrics.ascent;
let border_padding = border_padding.to_physical(self.style.writing_mode); tmp.to_physical(self.style.writing_mode, container_size) + flow_origin
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 { 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(), text_run: text_fragment.run.clone(),
range: text_fragment.range, range: text_fragment.range,
text_color: text_color, text_color: self.style().get_color().color.to_gfx_color(),
text_decorations: text_decorations, orientation: orientation,
baseline_origin: baseline_origin,
}; };
accumulator.push(display_list, TextDisplayItemClass(text_display_item)); 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. // Draw debug frames for text bounds.
// //
// FIXME(#2263, pcwalton): This is a bit of an abuse of the logging infrastructure. // 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)) debug!("{:?}", self.build_debug_borders_around_fragment(display_list, flow_origin))
}, },
ImageFragment(_) => { 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 { match self.specific {
ImageFragment(ref image_fragment) => { ImageFragment(ref image_fragment) => {
let image_ref = &image_fragment.image; let image_ref = &image_fragment.image;
@ -995,11 +1030,11 @@ impl Fragment {
// Place the image into the display list. // Place the image into the display list.
let image_display_item = box ImageDisplayItem { let image_display_item = box ImageDisplayItem {
base: BaseDisplayItem::new(bounds, base: BaseDisplayItem::new(absolute_content_box,
self.node, self.node,
ContentStackingLevel), ContentStackingLevel),
image: image.clone(), image: image.clone(),
stretch_size: bounds.size, stretch_size: absolute_content_box.size,
}; };
accumulator.push(display_list, accumulator.push(display_list,
ImageDisplayItemClass(image_display_item)) ImageDisplayItemClass(image_display_item))
@ -1128,13 +1163,7 @@ impl Fragment {
/// values are needed and that will save computation. /// values are needed and that will save computation.
#[inline] #[inline]
pub fn content_box(&self) -> LogicalRect<Au> { pub fn content_box(&self) -> LogicalRect<Au> {
LogicalRect::new( self.border_box - self.border_padding
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(),
)
} }
/// Find the split of a fragment that includes a new-line character. /// 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 flow::mut_base(parent).display_list = display_list
} }
} }

View file

@ -1100,13 +1100,10 @@ impl Flow for InlineFlow {
line_distance_from_flow_block_start = line_distance_from_flow_block_start + line.bounds.size.block; line_distance_from_flow_block_start = line_distance_from_flow_block_start + line.bounds.size.block;
} // End of `lines.each` loop. } // End of `lines.each` loop.
self.base.position.size.block = self.base.position.size.block = match self.lines.as_slice().last() {
if self.lines.len() > 0 { Some(ref last_line) => last_line.bounds.start.b + last_line.bounds.size.block,
self.lines.as_slice().last().get_ref().bounds.start.b + None => Au::new(0)
self.lines.as_slice().last().get_ref().bounds.size.block };
} else {
Au::new(0)
};
self.base.floats = scanner.floats(); self.base.floats = scanner.floats();
self.base.floats.translate(LogicalSize::new( self.base.floats.translate(LogicalSize::new(

View file

@ -9,16 +9,16 @@
use flow::Flow; use flow::Flow;
use fragment::{Fragment, ScannedTextFragment, ScannedTextFragmentInfo, UnscannedTextFragment}; use fragment::{Fragment, ScannedTextFragment, ScannedTextFragmentInfo, UnscannedTextFragment};
use gfx::font::{FontMetrics, FontStyle}; use gfx::font::{FontMetrics, FontStyle, RunMetrics};
use gfx::font_context::FontContext; use gfx::font_context::FontContext;
use gfx::text::glyph::CharIndex; use gfx::text::glyph::CharIndex;
use gfx::text::text_run::TextRun; use gfx::text::text_run::TextRun;
use gfx::text::util::{CompressWhitespaceNewline, transform_text, CompressNone}; use gfx::text::util::{CompressWhitespaceNewline, transform_text, CompressNone};
use servo_util::geometry::Au; use servo_util::geometry::Au;
use servo_util::logical_geometry::LogicalSize; use servo_util::logical_geometry::{LogicalSize, WritingMode};
use servo_util::range::Range; use servo_util::range::Range;
use style::ComputedValues; 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; use sync::Arc;
struct NewLinePositions { struct NewLinePositions {
@ -150,8 +150,8 @@ impl TextRunScanner {
*text); *text);
let range = Range::new(CharIndex(0), run.char_len()); let range = Range::new(CharIndex(0), run.char_len());
let new_metrics = run.metrics_for_range(&range); let new_metrics = run.metrics_for_range(&range);
let bounding_box_size = LogicalSize::from_physical( let bounding_box_size = bounding_box_for_run_metrics(
old_fragment.style.writing_mode, new_metrics.bounding_box.size); &new_metrics, old_fragment.style.writing_mode);
let new_text_fragment_info = ScannedTextFragmentInfo::new(Arc::new(run), range); let new_text_fragment_info = ScannedTextFragmentInfo::new(Arc::new(run), range);
let mut new_fragment = old_fragment.transform( let mut new_fragment = old_fragment.transform(
bounding_box_size, ScannedTextFragment(new_text_fragment_info)); 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 new_text_fragment_info = ScannedTextFragmentInfo::new(run.get_ref().clone(), range);
let old_fragment = &in_fragments[i.to_uint()]; let old_fragment = &in_fragments[i.to_uint()];
let new_metrics = new_text_fragment_info.run.metrics_for_range(&range); let new_metrics = new_text_fragment_info.run.metrics_for_range(&range);
let bounding_box_size = LogicalSize::from_physical( let bounding_box_size = bounding_box_for_run_metrics(
old_fragment.style.writing_mode, new_metrics.bounding_box.size); &new_metrics, old_fragment.style.writing_mode);
let mut new_fragment = old_fragment.transform( let mut new_fragment = old_fragment.transform(
bounding_box_size, ScannedTextFragment(new_text_fragment_info)); bounding_box_size, ScannedTextFragment(new_text_fragment_info));
new_fragment.new_line_pos = new_line_positions[logical_offset.to_uint()].new_line_pos.clone(); 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`. } // 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. /// 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. /// `#[inline]` because often the caller only needs a few fields from the font metrics.

View file

@ -41,6 +41,7 @@ pub use properties::longhands;
pub use node::{TElement, TNode}; pub use node::{TElement, TNode};
pub use selectors::{PseudoElement, Before, After, SelectorList, parse_selector_list_from_str}; pub use selectors::{PseudoElement, Before, After, SelectorList, parse_selector_list_from_str};
pub use selectors::{AttrSelector, NamespaceConstraint, SpecificNamespace, AnyNamespace}; pub use selectors::{AttrSelector, NamespaceConstraint, SpecificNamespace, AnyNamespace};
pub use cssparser::{Color, RGBA};
mod stylesheets; mod stylesheets;
mod errors; mod errors;

View file

@ -41,6 +41,11 @@ impl WritingMode {
pub fn is_bidi_ltr(&self) -> bool { pub fn is_bidi_ltr(&self) -> bool {
!self.intersects(FlagRTL) !self.intersects(FlagRTL)
} }
#[inline]
pub fn is_sideways_left(&self) -> bool {
self.intersects(FlagSidewaysLeft)
}
} }
impl Show for WritingMode { impl Show for WritingMode {