mirror of
https://github.com/servo/servo.git
synced 2025-06-10 09:33:13 +00:00
layout: Implement text-overflow: ellipsis
per CSS-UI-3 § 6.2.
Only the one-value syntax is supported for now.
This commit is contained in:
parent
aba5c16091
commit
0f8e436745
10 changed files with 195 additions and 56 deletions
|
@ -991,7 +991,8 @@ impl<'a> FlowConstructor<'a> {
|
||||||
let mut unscanned_marker_fragments = DList::new();
|
let mut unscanned_marker_fragments = DList::new();
|
||||||
unscanned_marker_fragments.push_back(Fragment::new_from_specific_info(
|
unscanned_marker_fragments.push_back(Fragment::new_from_specific_info(
|
||||||
node,
|
node,
|
||||||
SpecificFragmentInfo::UnscannedText(UnscannedTextFragmentInfo::from_text(text))));
|
SpecificFragmentInfo::UnscannedText(
|
||||||
|
UnscannedTextFragmentInfo::from_text(text))));
|
||||||
let marker_fragments = TextRunScanner::new().scan_for_runs(
|
let marker_fragments = TextRunScanner::new().scan_for_runs(
|
||||||
self.layout_context.font_context(),
|
self.layout_context.font_context(),
|
||||||
unscanned_marker_fragments);
|
unscanned_marker_fragments);
|
||||||
|
|
|
@ -37,7 +37,9 @@ use servo_util::logical_geometry::{LogicalRect, LogicalSize, LogicalMargin};
|
||||||
use servo_util::range::*;
|
use servo_util::range::*;
|
||||||
use servo_util::smallvec::SmallVec;
|
use servo_util::smallvec::SmallVec;
|
||||||
use servo_util::str::is_whitespace;
|
use servo_util::str::is_whitespace;
|
||||||
|
use std::borrow::ToOwned;
|
||||||
use std::cmp::{max, min};
|
use std::cmp::{max, min};
|
||||||
|
use std::collections::DList;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::num::ToPrimitive;
|
use std::num::ToPrimitive;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
@ -49,6 +51,7 @@ use style::computed_values::{LengthOrPercentage, LengthOrPercentageOrAuto};
|
||||||
use style::computed_values::{LengthOrPercentageOrNone, clear, mix_blend_mode, overflow_wrap};
|
use style::computed_values::{LengthOrPercentageOrNone, clear, mix_blend_mode, overflow_wrap};
|
||||||
use style::computed_values::{position, text_align, text_decoration, vertical_align, white_space};
|
use style::computed_values::{position, text_align, text_decoration, vertical_align, white_space};
|
||||||
use style::computed_values::{word_break};
|
use style::computed_values::{word_break};
|
||||||
|
use text::TextRunScanner;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
/// Fragments (`struct Fragment`) are the leaves of the layout tree. They cannot position
|
/// Fragments (`struct Fragment`) are the leaves of the layout tree. They cannot position
|
||||||
|
@ -575,6 +578,14 @@ pub struct SplitResult {
|
||||||
pub text_run: Arc<Box<TextRun>>,
|
pub text_run: Arc<Box<TextRun>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Describes how a fragment should be truncated.
|
||||||
|
pub struct TruncationResult {
|
||||||
|
/// The part of the fragment remaining after truncation.
|
||||||
|
pub split: SplitInfo,
|
||||||
|
/// The text run which is being truncated.
|
||||||
|
pub text_run: Arc<Box<TextRun>>,
|
||||||
|
}
|
||||||
|
|
||||||
/// Data for an unscanned text fragment. Unscanned text fragments are the results of flow
|
/// Data for an unscanned text fragment. Unscanned text fragments are the results of flow
|
||||||
/// construction that have not yet had their inline-size determined.
|
/// construction that have not yet had their inline-size determined.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -778,14 +789,12 @@ impl Fragment {
|
||||||
|
|
||||||
/// Transforms this fragment into another fragment of the given type, with the given size,
|
/// Transforms this fragment into another fragment of the given type, with the given size,
|
||||||
/// preserving all the other data.
|
/// preserving all the other data.
|
||||||
pub fn transform(&self, size: LogicalSize<Au>, mut info: Box<ScannedTextFragmentInfo>)
|
pub fn transform(&self, size: LogicalSize<Au>, info: SpecificFragmentInfo)
|
||||||
-> Fragment {
|
-> Fragment {
|
||||||
let new_border_box = LogicalRect::from_point_size(self.style.writing_mode,
|
let new_border_box = LogicalRect::from_point_size(self.style.writing_mode,
|
||||||
self.border_box.start,
|
self.border_box.start,
|
||||||
size);
|
size);
|
||||||
|
|
||||||
info.content_size = size.clone();
|
|
||||||
|
|
||||||
Fragment {
|
Fragment {
|
||||||
node: self.node,
|
node: self.node,
|
||||||
style: self.style.clone(),
|
style: self.style.clone(),
|
||||||
|
@ -793,12 +802,37 @@ impl Fragment {
|
||||||
border_box: new_border_box,
|
border_box: new_border_box,
|
||||||
border_padding: self.border_padding,
|
border_padding: self.border_padding,
|
||||||
margin: self.margin,
|
margin: self.margin,
|
||||||
specific: SpecificFragmentInfo::ScannedText(info),
|
specific: info,
|
||||||
inline_context: self.inline_context.clone(),
|
inline_context: self.inline_context.clone(),
|
||||||
debug_id: self.debug_id,
|
debug_id: self.debug_id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Transforms this fragment using the given `SplitInfo`, preserving all the other data.
|
||||||
|
pub fn transform_with_split_info(&self,
|
||||||
|
split: &SplitInfo,
|
||||||
|
text_run: Arc<Box<TextRun>>)
|
||||||
|
-> Fragment {
|
||||||
|
let size = LogicalSize::new(self.style.writing_mode,
|
||||||
|
split.inline_size,
|
||||||
|
self.border_box.size.block);
|
||||||
|
let info = box ScannedTextFragmentInfo::new(text_run, split.range, Vec::new(), size);
|
||||||
|
self.transform(size, SpecificFragmentInfo::ScannedText(info))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transforms this fragment into an ellipsis fragment, preserving all the other data.
|
||||||
|
pub fn transform_into_ellipsis(&self, layout_context: &LayoutContext) -> Fragment {
|
||||||
|
let mut unscanned_ellipsis_fragments = DList::new();
|
||||||
|
unscanned_ellipsis_fragments.push_back(self.transform(
|
||||||
|
self.border_box.size,
|
||||||
|
SpecificFragmentInfo::UnscannedText(UnscannedTextFragmentInfo::from_text(
|
||||||
|
"…".to_owned()))));
|
||||||
|
let ellipsis_fragments = TextRunScanner::new().scan_for_runs(layout_context.font_context(),
|
||||||
|
unscanned_ellipsis_fragments);
|
||||||
|
debug_assert!(ellipsis_fragments.len() == 1);
|
||||||
|
ellipsis_fragments.fragments.into_iter().next().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn restyle_damage(&self) -> RestyleDamage {
|
pub fn restyle_damage(&self) -> RestyleDamage {
|
||||||
self.restyle_damage | self.specific.restyle_damage()
|
self.restyle_damage | self.specific.restyle_damage()
|
||||||
}
|
}
|
||||||
|
@ -1353,6 +1387,36 @@ impl Fragment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Truncates this fragment to the given `max_inline_size`, using a character-based breaking
|
||||||
|
/// strategy. If no characters could fit, returns `None`.
|
||||||
|
pub fn truncate_to_inline_size(&self, max_inline_size: Au) -> Option<TruncationResult> {
|
||||||
|
let text_fragment_info =
|
||||||
|
if let SpecificFragmentInfo::ScannedText(ref text_fragment_info) = self.specific {
|
||||||
|
text_fragment_info
|
||||||
|
} else {
|
||||||
|
return None
|
||||||
|
};
|
||||||
|
|
||||||
|
let character_breaking_strategy =
|
||||||
|
text_fragment_info.run.character_slices_in_range(&text_fragment_info.range);
|
||||||
|
match self.calculate_split_position_using_breaking_strategy(character_breaking_strategy,
|
||||||
|
max_inline_size,
|
||||||
|
SplitOptions::empty()) {
|
||||||
|
None => None,
|
||||||
|
Some(split_info) => {
|
||||||
|
match split_info.inline_start {
|
||||||
|
None => None,
|
||||||
|
Some(split) => {
|
||||||
|
Some(TruncationResult {
|
||||||
|
split: split,
|
||||||
|
text_run: split_info.text_run.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A helper method that uses the breaking strategy described by `slice_iterator` (at present,
|
/// A helper method that uses the breaking strategy described by `slice_iterator` (at present,
|
||||||
/// either natural word breaking or character breaking) to split this fragment.
|
/// either natural word breaking or character breaking) to split this fragment.
|
||||||
fn calculate_split_position_using_breaking_strategy<'a,I>(&self,
|
fn calculate_split_position_using_breaking_strategy<'a,I>(&self,
|
||||||
|
|
|
@ -34,7 +34,7 @@ use std::mem;
|
||||||
use std::num::ToPrimitive;
|
use std::num::ToPrimitive;
|
||||||
use std::ops::{Add, Sub, Mul, Div, Rem, Neg, Shl, Shr, Not, BitOr, BitAnd, BitXor};
|
use std::ops::{Add, Sub, Mul, Div, Rem, Neg, Shl, Shr, Not, BitOr, BitAnd, BitXor};
|
||||||
use std::u16;
|
use std::u16;
|
||||||
use style::computed_values::{text_align, vertical_align, white_space};
|
use style::computed_values::{overflow, text_align, text_overflow, vertical_align, white_space};
|
||||||
use style::ComputedValues;
|
use style::ComputedValues;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
@ -289,7 +289,7 @@ impl LineBreaker {
|
||||||
// `append_fragment_to_line_if_possible` and
|
// `append_fragment_to_line_if_possible` and
|
||||||
// `try_append_to_line_by_new_line` by adding another bit in the reflow
|
// `try_append_to_line_by_new_line` by adding another bit in the reflow
|
||||||
// flags.
|
// flags.
|
||||||
if !self.try_append_to_line_by_new_line(fragment) {
|
if !self.try_append_to_line_by_new_line(layout_context, fragment) {
|
||||||
self.flush_current_line()
|
self.flush_current_line()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -479,7 +479,10 @@ impl LineBreaker {
|
||||||
/// Tries to append the given fragment to the line for `pre`-formatted text, splitting it if
|
/// Tries to append the given fragment to the line for `pre`-formatted text, splitting it if
|
||||||
/// necessary. Returns true if we successfully pushed the fragment to the line or false if we
|
/// necessary. Returns true if we successfully pushed the fragment to the line or false if we
|
||||||
/// couldn't.
|
/// couldn't.
|
||||||
fn try_append_to_line_by_new_line(&mut self, in_fragment: Fragment) -> bool {
|
fn try_append_to_line_by_new_line(&mut self,
|
||||||
|
layout_context: &LayoutContext,
|
||||||
|
in_fragment: Fragment)
|
||||||
|
-> bool {
|
||||||
let should_push = match in_fragment.newline_positions() {
|
let should_push = match in_fragment.newline_positions() {
|
||||||
None => true,
|
None => true,
|
||||||
Some(ref positions) => positions.is_empty(),
|
Some(ref positions) => positions.is_empty(),
|
||||||
|
@ -487,7 +490,7 @@ impl LineBreaker {
|
||||||
if should_push {
|
if should_push {
|
||||||
debug!("LineBreaker: did not find a newline character; pushing the fragment to \
|
debug!("LineBreaker: did not find a newline character; pushing the fragment to \
|
||||||
the line without splitting");
|
the line without splitting");
|
||||||
self.push_fragment_to_line(in_fragment);
|
self.push_fragment_to_line(layout_context, in_fragment);
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -498,16 +501,16 @@ impl LineBreaker {
|
||||||
.expect("LineBreaker: this split case makes no sense!");
|
.expect("LineBreaker: this split case makes no sense!");
|
||||||
let writing_mode = self.floats.writing_mode;
|
let writing_mode = self.floats.writing_mode;
|
||||||
|
|
||||||
let split_fragment = |&:split: SplitInfo| {
|
let split_fragment = |&: split: SplitInfo| {
|
||||||
|
let size = LogicalSize::new(writing_mode,
|
||||||
|
split.inline_size,
|
||||||
|
in_fragment.border_box.size.block);
|
||||||
let info = box ScannedTextFragmentInfo::new(run.clone(),
|
let info = box ScannedTextFragmentInfo::new(run.clone(),
|
||||||
split.range,
|
split.range,
|
||||||
(*in_fragment.newline_positions()
|
(*in_fragment.newline_positions()
|
||||||
.unwrap()).clone(),
|
.unwrap()).clone(),
|
||||||
in_fragment.border_box.size);
|
size);
|
||||||
let size = LogicalSize::new(writing_mode,
|
in_fragment.transform(size, SpecificFragmentInfo::ScannedText(info))
|
||||||
split.inline_size,
|
|
||||||
in_fragment.border_box.size.block);
|
|
||||||
in_fragment.transform(size, info)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
debug!("LineBreaker: Pushing the fragment to the inline_start of the new-line character \
|
debug!("LineBreaker: Pushing the fragment to the inline_start of the new-line character \
|
||||||
|
@ -515,7 +518,7 @@ impl LineBreaker {
|
||||||
let mut inline_start = split_fragment(inline_start);
|
let mut inline_start = split_fragment(inline_start);
|
||||||
inline_start.save_new_line_pos();
|
inline_start.save_new_line_pos();
|
||||||
*inline_start.newline_positions_mut().unwrap() = vec![];
|
*inline_start.newline_positions_mut().unwrap() = vec![];
|
||||||
self.push_fragment_to_line(inline_start);
|
self.push_fragment_to_line(layout_context, inline_start);
|
||||||
|
|
||||||
for inline_end in inline_end.into_iter() {
|
for inline_end in inline_end.into_iter() {
|
||||||
debug!("LineBreaker: Deferring the fragment to the inline_end of the new-line \
|
debug!("LineBreaker: Deferring the fragment to the inline_end of the new-line \
|
||||||
|
@ -567,7 +570,7 @@ impl LineBreaker {
|
||||||
fragment.border_box.size.inline + indentation;
|
fragment.border_box.size.inline + indentation;
|
||||||
if new_inline_size <= green_zone.inline {
|
if new_inline_size <= green_zone.inline {
|
||||||
debug!("LineBreaker: fragment fits without splitting");
|
debug!("LineBreaker: fragment fits without splitting");
|
||||||
self.push_fragment_to_line(fragment);
|
self.push_fragment_to_line(layout_context, fragment);
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -577,47 +580,41 @@ impl LineBreaker {
|
||||||
flags.contains(NO_WRAP_INLINE_REFLOW_FLAG) {
|
flags.contains(NO_WRAP_INLINE_REFLOW_FLAG) {
|
||||||
debug!("LineBreaker: fragment can't split and line {} is empty, so overflowing",
|
debug!("LineBreaker: fragment can't split and line {} is empty, so overflowing",
|
||||||
self.lines.len());
|
self.lines.len());
|
||||||
self.push_fragment_to_line(fragment);
|
self.push_fragment_to_line(layout_context, fragment);
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Split it up!
|
// Split it up!
|
||||||
let available_inline_size = green_zone.inline - self.pending_line.bounds.size.inline -
|
let available_inline_size = green_zone.inline - self.pending_line.bounds.size.inline -
|
||||||
indentation;
|
indentation;
|
||||||
let (inline_start_fragment, inline_end_fragment) =
|
let inline_start_fragment;
|
||||||
match fragment.calculate_split_position(available_inline_size,
|
let inline_end_fragment;
|
||||||
self.pending_line_is_empty()) {
|
let split_result = match fragment.calculate_split_position(available_inline_size,
|
||||||
None => {
|
self.pending_line_is_empty()) {
|
||||||
debug!("LineBreaker: fragment was unsplittable; deferring to next line: {:?}",
|
None => {
|
||||||
fragment);
|
debug!("LineBreaker: fragment was unsplittable; deferring to next line");
|
||||||
self.work_list.push_front(fragment);
|
self.work_list.push_front(fragment);
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
Some(split_result) => {
|
Some(split_result) => split_result,
|
||||||
let split_fragment = |&:split: SplitInfo| {
|
};
|
||||||
let info = box ScannedTextFragmentInfo::new(split_result.text_run.clone(),
|
|
||||||
split.range,
|
inline_start_fragment = split_result.inline_start.as_ref().map(|x| {
|
||||||
Vec::new(),
|
fragment.transform_with_split_info(x, split_result.text_run.clone())
|
||||||
fragment.border_box.size);
|
});
|
||||||
let size = LogicalSize::new(self.floats.writing_mode,
|
inline_end_fragment = split_result.inline_end.as_ref().map(|x| {
|
||||||
split.inline_size,
|
fragment.transform_with_split_info(x, split_result.text_run.clone())
|
||||||
fragment.border_box.size.block);
|
});
|
||||||
fragment.transform(size, info)
|
|
||||||
};
|
|
||||||
(split_result.inline_start.as_ref().map(|x| split_fragment(x.clone())),
|
|
||||||
split_result.inline_end.as_ref().map(|x| split_fragment(x.clone())))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Push the first fragment onto the line we're working on and start off the next line with
|
// Push the first fragment onto the line we're working on and start off the next line with
|
||||||
// the second fragment. If there's no second fragment, the next line will start off empty.
|
// the second fragment. If there's no second fragment, the next line will start off empty.
|
||||||
match (inline_start_fragment, inline_end_fragment) {
|
match (inline_start_fragment, inline_end_fragment) {
|
||||||
(Some(inline_start_fragment), Some(inline_end_fragment)) => {
|
(Some(inline_start_fragment), Some(inline_end_fragment)) => {
|
||||||
self.push_fragment_to_line(inline_start_fragment);
|
self.push_fragment_to_line(layout_context, inline_start_fragment);
|
||||||
self.work_list.push_front(inline_end_fragment)
|
self.work_list.push_front(inline_end_fragment)
|
||||||
},
|
},
|
||||||
(Some(fragment), None) | (None, Some(fragment)) => {
|
(Some(fragment), None) | (None, Some(fragment)) => {
|
||||||
self.push_fragment_to_line(fragment)
|
self.push_fragment_to_line(layout_context, fragment)
|
||||||
}
|
}
|
||||||
(None, None) => {}
|
(None, None) => {}
|
||||||
}
|
}
|
||||||
|
@ -625,8 +622,9 @@ impl LineBreaker {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pushes a fragment to the current line unconditionally.
|
/// Pushes a fragment to the current line unconditionally, possibly truncating it and placing
|
||||||
fn push_fragment_to_line(&mut self, fragment: Fragment) {
|
/// an ellipsis based on the value of `text-overflow`.
|
||||||
|
fn push_fragment_to_line(&mut self, layout_context: &LayoutContext, fragment: Fragment) {
|
||||||
let indentation = self.indentation_for_pending_fragment();
|
let indentation = self.indentation_for_pending_fragment();
|
||||||
if self.pending_line_is_empty() {
|
if self.pending_line_is_empty() {
|
||||||
assert!(self.new_fragments.len() <= (u16::MAX as uint));
|
assert!(self.new_fragments.len() <= (u16::MAX as uint));
|
||||||
|
@ -634,6 +632,39 @@ impl LineBreaker {
|
||||||
FragmentIndex(0));
|
FragmentIndex(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determine if an ellipsis will be necessary to account for `text-overflow`.
|
||||||
|
let mut need_ellipsis = false;
|
||||||
|
let available_inline_size = self.pending_line.green_zone.inline -
|
||||||
|
self.pending_line.bounds.size.inline - indentation;
|
||||||
|
match (fragment.style().get_inheritedtext().text_overflow,
|
||||||
|
fragment.style().get_box().overflow) {
|
||||||
|
(text_overflow::T::clip, _) | (_, overflow::T::visible) => {}
|
||||||
|
(text_overflow::T::ellipsis, _) => {
|
||||||
|
need_ellipsis = fragment.border_box.size.inline > available_inline_size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !need_ellipsis {
|
||||||
|
self.push_fragment_to_line_ignoring_text_overflow(fragment);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let ellipsis = fragment.transform_into_ellipsis(layout_context);
|
||||||
|
if let Some(truncation_info) =
|
||||||
|
fragment.truncate_to_inline_size(available_inline_size -
|
||||||
|
ellipsis.border_box.size.inline) {
|
||||||
|
let fragment = fragment.transform_with_split_info(&truncation_info.split,
|
||||||
|
truncation_info.text_run);
|
||||||
|
self.push_fragment_to_line_ignoring_text_overflow(fragment);
|
||||||
|
}
|
||||||
|
self.push_fragment_to_line_ignoring_text_overflow(ellipsis);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pushes a fragment to the current line unconditionally, without placing an ellipsis in the
|
||||||
|
/// case of `text-overflow: ellipsis`.
|
||||||
|
fn push_fragment_to_line_ignoring_text_overflow(&mut self, fragment: Fragment) {
|
||||||
|
let indentation = self.indentation_for_pending_fragment();
|
||||||
|
|
||||||
self.pending_line.range.extend_by(FragmentIndex(1));
|
self.pending_line.range.extend_by(FragmentIndex(1));
|
||||||
self.pending_line.bounds.size.inline = self.pending_line.bounds.size.inline +
|
self.pending_line.bounds.size.inline = self.pending_line.bounds.size.inline +
|
||||||
fragment.border_box.size.inline +
|
fragment.border_box.size.inline +
|
||||||
|
|
|
@ -194,16 +194,15 @@ impl TextRunScanner {
|
||||||
mem::replace(&mut self.clump, DList::new()).into_iter().enumerate() {
|
mem::replace(&mut self.clump, DList::new()).into_iter().enumerate() {
|
||||||
let range = *new_ranges.get(logical_offset);
|
let range = *new_ranges.get(logical_offset);
|
||||||
if range.is_empty() {
|
if range.is_empty() {
|
||||||
debug!("Elided an `SpecificFragmentInfo::UnscannedText` because it was zero-length after \
|
debug!("Elided an `SpecificFragmentInfo::UnscannedText` because it was \
|
||||||
compression; {:?}",
|
zero-length after compression");
|
||||||
old_fragment);
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
let text_size = old_fragment.border_box.size;
|
let text_size = old_fragment.border_box.size;
|
||||||
let &mut NewLinePositions(ref mut new_line_positions) =
|
let &mut NewLinePositions(ref mut new_line_positions) =
|
||||||
new_line_positions.get_mut(logical_offset);
|
new_line_positions.get_mut(logical_offset);
|
||||||
let new_text_fragment_info =
|
let mut new_text_fragment_info =
|
||||||
box ScannedTextFragmentInfo::new(run.clone(),
|
box ScannedTextFragmentInfo::new(run.clone(),
|
||||||
range,
|
range,
|
||||||
mem::replace(new_line_positions, Vec::new()),
|
mem::replace(new_line_positions, Vec::new()),
|
||||||
|
@ -211,7 +210,10 @@ impl TextRunScanner {
|
||||||
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 = bounding_box_for_run_metrics(&new_metrics,
|
let bounding_box_size = bounding_box_for_run_metrics(&new_metrics,
|
||||||
old_fragment.style.writing_mode);
|
old_fragment.style.writing_mode);
|
||||||
let new_fragment = old_fragment.transform(bounding_box_size, new_text_fragment_info);
|
new_text_fragment_info.content_size = bounding_box_size;
|
||||||
|
let new_fragment =
|
||||||
|
old_fragment.transform(bounding_box_size,
|
||||||
|
SpecificFragmentInfo::ScannedText(new_text_fragment_info));
|
||||||
out_fragments.push(new_fragment)
|
out_fragments.push(new_fragment)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,8 +40,9 @@ use util::{PrivateLayoutData};
|
||||||
use cssparser::RGBA;
|
use cssparser::RGBA;
|
||||||
use gfx::display_list::OpaqueNode;
|
use gfx::display_list::OpaqueNode;
|
||||||
use script::dom::bindings::codegen::InheritTypes::{ElementCast, HTMLIFrameElementCast};
|
use script::dom::bindings::codegen::InheritTypes::{ElementCast, HTMLIFrameElementCast};
|
||||||
use script::dom::bindings::codegen::InheritTypes::{HTMLCanvasElementCast, HTMLImageElementCast, HTMLInputElementCast};
|
use script::dom::bindings::codegen::InheritTypes::{HTMLCanvasElementCast, HTMLImageElementCast};
|
||||||
use script::dom::bindings::codegen::InheritTypes::{HTMLTextAreaElementCast, NodeCast, TextCast};
|
use script::dom::bindings::codegen::InheritTypes::{HTMLInputElementCast, HTMLTextAreaElementCast};
|
||||||
|
use script::dom::bindings::codegen::InheritTypes::{NodeCast, TextCast};
|
||||||
use script::dom::bindings::js::JS;
|
use script::dom::bindings::js::JS;
|
||||||
use script::dom::element::{Element, ElementTypeId};
|
use script::dom::element::{Element, ElementTypeId};
|
||||||
use script::dom::element::{LayoutElementHelpers, RawLayoutElementHelpers};
|
use script::dom::element::{LayoutElementHelpers, RawLayoutElementHelpers};
|
||||||
|
@ -58,6 +59,8 @@ use script::dom::text::Text;
|
||||||
use script::layout_interface::LayoutChan;
|
use script::layout_interface::LayoutChan;
|
||||||
use servo_msg::constellation_msg::{PipelineId, SubpageId};
|
use servo_msg::constellation_msg::{PipelineId, SubpageId};
|
||||||
use servo_util::str::{LengthOrPercentageOrAuto, is_whitespace};
|
use servo_util::str::{LengthOrPercentageOrAuto, is_whitespace};
|
||||||
|
use std::borrow::ToOwned;
|
||||||
|
use std::cell::{Ref, RefMut};
|
||||||
use std::marker::ContravariantLifetime;
|
use std::marker::ContravariantLifetime;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::sync::mpsc::Sender;
|
use std::sync::mpsc::Sender;
|
||||||
|
@ -68,9 +71,6 @@ use style::{LengthAttribute, PropertyDeclarationBlock, SimpleColorAttribute};
|
||||||
use style::{TElement, TElementAttributes, TNode, UnsignedIntegerAttribute};
|
use style::{TElement, TElementAttributes, TNode, UnsignedIntegerAttribute};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use std::borrow::ToOwned;
|
|
||||||
use std::cell::{Ref, RefMut};
|
|
||||||
|
|
||||||
/// Allows some convenience methods on generic layout nodes.
|
/// Allows some convenience methods on generic layout nodes.
|
||||||
pub trait TLayoutNode {
|
pub trait TLayoutNode {
|
||||||
/// Creates a new layout node with the same lifetime as this layout node.
|
/// Creates a new layout node with the same lifetime as this layout node.
|
||||||
|
|
|
@ -116,6 +116,7 @@ partial interface CSSStyleDeclaration {
|
||||||
[TreatNullAs=EmptyString] attribute DOMString wordBreak;
|
[TreatNullAs=EmptyString] attribute DOMString wordBreak;
|
||||||
[TreatNullAs=EmptyString] attribute DOMString wordSpacing;
|
[TreatNullAs=EmptyString] attribute DOMString wordSpacing;
|
||||||
[TreatNullAs=EmptyString] attribute DOMString wordWrap;
|
[TreatNullAs=EmptyString] attribute DOMString wordWrap;
|
||||||
|
[TreatNullAs=EmptyString] attribute DOMString textOverflow;
|
||||||
|
|
||||||
[TreatNullAs=EmptyString] attribute DOMString textAlign;
|
[TreatNullAs=EmptyString] attribute DOMString textAlign;
|
||||||
[TreatNullAs=EmptyString] attribute DOMString textDecoration;
|
[TreatNullAs=EmptyString] attribute DOMString textDecoration;
|
||||||
|
|
|
@ -1213,6 +1213,8 @@ pub mod longhands {
|
||||||
// TODO(pcwalton): Support `word-break: keep-all` once we have better CJK support.
|
// TODO(pcwalton): Support `word-break: keep-all` once we have better CJK support.
|
||||||
${single_keyword("word-break", "normal break-all")}
|
${single_keyword("word-break", "normal break-all")}
|
||||||
|
|
||||||
|
${single_keyword("text-overflow", "clip ellipsis")}
|
||||||
|
|
||||||
${new_style_struct("Text", is_inherited=False)}
|
${new_style_struct("Text", is_inherited=False)}
|
||||||
|
|
||||||
<%self:longhand name="text-decoration">
|
<%self:longhand name="text-decoration">
|
||||||
|
|
|
@ -232,3 +232,4 @@ fragment=top != ../html/acid2.html acid2_ref.html
|
||||||
== filter_opacity_a.html filter_opacity_ref.html
|
== filter_opacity_a.html filter_opacity_ref.html
|
||||||
== filter_sepia_a.html filter_sepia_ref.html
|
== filter_sepia_a.html filter_sepia_ref.html
|
||||||
== mix_blend_mode_a.html mix_blend_mode_ref.html
|
== mix_blend_mode_a.html mix_blend_mode_ref.html
|
||||||
|
!= text_overflow_a.html text_overflow_ref.html
|
||||||
|
|
20
tests/ref/text_overflow_a.html
Normal file
20
tests/ref/text_overflow_a.html
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
p {
|
||||||
|
width: 128px;
|
||||||
|
background: gold;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
#hideme {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p id=hideme>mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm</p>
|
||||||
|
<p>mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
17
tests/ref/text_overflow_ref.html
Normal file
17
tests/ref/text_overflow_ref.html
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
p {
|
||||||
|
width: 128px;
|
||||||
|
background: gold;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm</p>
|
||||||
|
<p>mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue