mirror of
https://github.com/servo/servo.git
synced 2025-06-29 19:43:39 +01:00
Instead of painting hoisted position fragments in the order to which they are hoisted, paint them in tree order and properly incorporate them into the stacking context. We do this by creating a placeholder fragment in the original tree position of hoisted fragments. The ghost fragment contains an atomic id which links back to the hoisted fragment in the containing block. While building the stacking context, we keep track of containing blocks and their children. When encountering a placeholder fragment we look at the containing block's hoisted children in order to properly paint the hoisted fragment. One notable design modification in this change is that hoisted fragments no longer need an AnonymousFragment as their parent. Instead they are now direct children of the fragment that establishes their containing block.
760 lines
30 KiB
Rust
760 lines
30 KiB
Rust
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
|
|
use crate::context::LayoutContext;
|
|
use crate::flow::float::FloatBox;
|
|
use crate::flow::FlowLayout;
|
|
use crate::formatting_contexts::IndependentFormattingContext;
|
|
use crate::fragments::{
|
|
AbsoluteOrFixedPositionedFragment, AnonymousFragment, BoxFragment, CollapsedBlockMargins,
|
|
DebugId, Fragment, TextFragment,
|
|
};
|
|
use crate::geom::flow_relative::{Rect, Sides, Vec2};
|
|
use crate::positioned::{relative_adjustement, AbsolutelyPositionedBox, PositioningContext};
|
|
use crate::sizing::ContentSizes;
|
|
use crate::style_ext::{ComputedValuesExt, Display, DisplayGeneratingBox, DisplayOutside};
|
|
use crate::ContainingBlock;
|
|
use app_units::Au;
|
|
use gfx::text::text_run::GlyphRun;
|
|
use servo_arc::Arc;
|
|
use style::dom::OpaqueNode;
|
|
use style::properties::ComputedValues;
|
|
use style::values::computed::{Length, LengthPercentage, Percentage};
|
|
use style::values::specified::text::TextAlignKeyword;
|
|
use style::Zero;
|
|
use webrender_api::FontInstanceKey;
|
|
|
|
#[derive(Debug, Default, Serialize)]
|
|
pub(crate) struct InlineFormattingContext {
|
|
pub(super) inline_level_boxes: Vec<Arc<InlineLevelBox>>,
|
|
}
|
|
|
|
#[derive(Debug, Serialize)]
|
|
pub(crate) enum InlineLevelBox {
|
|
InlineBox(InlineBox),
|
|
TextRun(TextRun),
|
|
OutOfFlowAbsolutelyPositionedBox(AbsolutelyPositionedBox),
|
|
OutOfFlowFloatBox(FloatBox),
|
|
Atomic(IndependentFormattingContext),
|
|
}
|
|
|
|
#[derive(Debug, Serialize)]
|
|
pub(crate) struct InlineBox {
|
|
pub tag: OpaqueNode,
|
|
#[serde(skip_serializing)]
|
|
pub style: Arc<ComputedValues>,
|
|
pub first_fragment: bool,
|
|
pub last_fragment: bool,
|
|
pub children: Vec<Arc<InlineLevelBox>>,
|
|
}
|
|
|
|
/// https://www.w3.org/TR/css-display-3/#css-text-run
|
|
#[derive(Debug, Serialize)]
|
|
pub(crate) struct TextRun {
|
|
pub tag: OpaqueNode,
|
|
#[serde(skip_serializing)]
|
|
pub parent_style: Arc<ComputedValues>,
|
|
pub text: String,
|
|
}
|
|
|
|
struct InlineNestingLevelState<'box_tree> {
|
|
remaining_boxes: std::slice::Iter<'box_tree, Arc<InlineLevelBox>>,
|
|
fragments_so_far: Vec<Fragment>,
|
|
inline_start: Length,
|
|
max_block_size_of_fragments_so_far: Length,
|
|
}
|
|
|
|
struct PartialInlineBoxFragment<'box_tree> {
|
|
tag: OpaqueNode,
|
|
style: Arc<ComputedValues>,
|
|
start_corner: Vec2<Length>,
|
|
padding: Sides<Length>,
|
|
border: Sides<Length>,
|
|
margin: Sides<Length>,
|
|
last_box_tree_fragment: bool,
|
|
parent_nesting_level: InlineNestingLevelState<'box_tree>,
|
|
}
|
|
|
|
struct InlineFormattingContextState<'box_tree, 'a, 'b> {
|
|
positioning_context: &'a mut PositioningContext<'box_tree>,
|
|
containing_block: &'b ContainingBlock<'b>,
|
|
lines: Lines,
|
|
inline_position: Length,
|
|
partial_inline_boxes_stack: Vec<PartialInlineBoxFragment<'box_tree>>,
|
|
current_nesting_level: InlineNestingLevelState<'box_tree>,
|
|
}
|
|
|
|
struct Lines {
|
|
// One anonymous fragment per line
|
|
fragments: Vec<Fragment>,
|
|
next_line_block_position: Length,
|
|
}
|
|
|
|
impl InlineFormattingContext {
|
|
// This works on an already-constructed `InlineFormattingContext`,
|
|
// Which would have to change if/when
|
|
// `BlockContainer::construct` parallelize their construction.
|
|
pub(super) fn inline_content_sizes(&self, layout_context: &LayoutContext) -> ContentSizes {
|
|
struct Computation {
|
|
paragraph: ContentSizes,
|
|
current_line: ContentSizes,
|
|
current_line_percentages: Percentage,
|
|
}
|
|
impl Computation {
|
|
fn traverse(
|
|
&mut self,
|
|
layout_context: &LayoutContext,
|
|
inline_level_boxes: &[Arc<InlineLevelBox>],
|
|
) {
|
|
for inline_level_box in inline_level_boxes {
|
|
match &**inline_level_box {
|
|
InlineLevelBox::InlineBox(inline_box) => {
|
|
let padding = inline_box.style.padding();
|
|
let border = inline_box.style.border_width();
|
|
let margin = inline_box.style.margin();
|
|
macro_rules! add {
|
|
($condition: ident, $side: ident) => {
|
|
if inline_box.$condition {
|
|
self.add_lengthpercentage(padding.$side);
|
|
self.add_length(border.$side);
|
|
if let Some(lp) = margin.$side.non_auto() {
|
|
self.add_lengthpercentage(lp)
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
add!(first_fragment, inline_start);
|
|
self.traverse(layout_context, &inline_box.children);
|
|
add!(last_fragment, inline_end);
|
|
},
|
|
InlineLevelBox::TextRun(text_run) => {
|
|
let BreakAndShapeResult {
|
|
runs,
|
|
break_at_start,
|
|
..
|
|
} = text_run.break_and_shape(layout_context);
|
|
if break_at_start {
|
|
self.line_break_opportunity()
|
|
}
|
|
for run in &runs {
|
|
let advance = Length::from(run.glyph_store.total_advance());
|
|
if run.glyph_store.is_whitespace() {
|
|
self.line_break_opportunity()
|
|
} else {
|
|
self.current_line.min_content += advance
|
|
}
|
|
self.current_line.max_content += advance
|
|
}
|
|
},
|
|
InlineLevelBox::Atomic(atomic) => {
|
|
let (outer, pc) = atomic
|
|
.content_sizes
|
|
.outer_inline_and_percentages(&atomic.style);
|
|
self.current_line.min_content += outer.min_content;
|
|
self.current_line.max_content += outer.max_content;
|
|
self.current_line_percentages += pc;
|
|
},
|
|
InlineLevelBox::OutOfFlowFloatBox(_) |
|
|
InlineLevelBox::OutOfFlowAbsolutelyPositionedBox(_) => {},
|
|
}
|
|
}
|
|
}
|
|
|
|
fn add_lengthpercentage(&mut self, lp: LengthPercentage) {
|
|
if let Some(l) = lp.to_length() {
|
|
self.add_length(l);
|
|
}
|
|
if let Some(p) = lp.to_percentage() {
|
|
self.current_line_percentages += p;
|
|
}
|
|
}
|
|
|
|
fn add_length(&mut self, l: Length) {
|
|
self.current_line.min_content += l;
|
|
self.current_line.max_content += l;
|
|
}
|
|
|
|
fn line_break_opportunity(&mut self) {
|
|
self.paragraph
|
|
.min_content
|
|
.max_assign(take(&mut self.current_line.min_content));
|
|
}
|
|
|
|
fn forced_line_break(&mut self) {
|
|
self.line_break_opportunity();
|
|
self.current_line
|
|
.adjust_for_pbm_percentages(take(&mut self.current_line_percentages));
|
|
self.paragraph
|
|
.max_content
|
|
.max_assign(take(&mut self.current_line.max_content));
|
|
}
|
|
}
|
|
fn take<T: Zero>(x: &mut T) -> T {
|
|
std::mem::replace(x, T::zero())
|
|
}
|
|
let mut computation = Computation {
|
|
paragraph: ContentSizes::zero(),
|
|
current_line: ContentSizes::zero(),
|
|
current_line_percentages: Percentage::zero(),
|
|
};
|
|
computation.traverse(layout_context, &self.inline_level_boxes);
|
|
computation.forced_line_break();
|
|
computation.paragraph
|
|
}
|
|
|
|
pub(super) fn layout<'a>(
|
|
&'a self,
|
|
layout_context: &LayoutContext,
|
|
positioning_context: &mut PositioningContext<'a>,
|
|
containing_block: &ContainingBlock,
|
|
tree_rank: usize,
|
|
) -> FlowLayout {
|
|
let mut ifc = InlineFormattingContextState {
|
|
positioning_context,
|
|
containing_block,
|
|
partial_inline_boxes_stack: Vec::new(),
|
|
lines: Lines {
|
|
fragments: Vec::new(),
|
|
next_line_block_position: Length::zero(),
|
|
},
|
|
inline_position: Length::zero(),
|
|
current_nesting_level: InlineNestingLevelState {
|
|
remaining_boxes: self.inline_level_boxes.iter(),
|
|
fragments_so_far: Vec::with_capacity(self.inline_level_boxes.len()),
|
|
inline_start: Length::zero(),
|
|
max_block_size_of_fragments_so_far: Length::zero(),
|
|
},
|
|
};
|
|
loop {
|
|
if let Some(child) = ifc.current_nesting_level.remaining_boxes.next() {
|
|
match &**child {
|
|
InlineLevelBox::InlineBox(inline) => {
|
|
let partial = inline.start_layout(&mut ifc);
|
|
ifc.partial_inline_boxes_stack.push(partial)
|
|
},
|
|
InlineLevelBox::TextRun(run) => run.layout(layout_context, &mut ifc),
|
|
InlineLevelBox::Atomic(a) => layout_atomic(layout_context, &mut ifc, a),
|
|
InlineLevelBox::OutOfFlowAbsolutelyPositionedBox(box_) => {
|
|
let initial_start_corner =
|
|
match Display::from(box_.contents.style.get_box().original_display) {
|
|
Display::GeneratingBox(DisplayGeneratingBox::OutsideInside {
|
|
outside,
|
|
inside: _,
|
|
}) => Vec2 {
|
|
inline: match outside {
|
|
DisplayOutside::Inline => ifc.inline_position,
|
|
DisplayOutside::Block => Length::zero(),
|
|
},
|
|
block: ifc.lines.next_line_block_position,
|
|
},
|
|
Display::Contents => {
|
|
panic!("display:contents does not generate an abspos box")
|
|
},
|
|
Display::None => {
|
|
panic!("display:none does not generate an abspos box")
|
|
},
|
|
};
|
|
let hoisted_fragment = box_.to_hoisted(initial_start_corner, tree_rank);
|
|
let hoisted_fragment_id = hoisted_fragment.fragment_id;
|
|
ifc.positioning_context.push(hoisted_fragment);
|
|
ifc.lines
|
|
.fragments
|
|
.push(Fragment::AbsoluteOrFixedPositioned(
|
|
AbsoluteOrFixedPositionedFragment(hoisted_fragment_id),
|
|
));
|
|
},
|
|
InlineLevelBox::OutOfFlowFloatBox(_box_) => {
|
|
// TODO
|
|
},
|
|
}
|
|
} else
|
|
// Reached the end of ifc.remaining_boxes
|
|
if let Some(mut partial) = ifc.partial_inline_boxes_stack.pop() {
|
|
partial.finish_layout(
|
|
&mut ifc.current_nesting_level,
|
|
&mut ifc.inline_position,
|
|
false,
|
|
);
|
|
ifc.current_nesting_level = partial.parent_nesting_level
|
|
} else {
|
|
ifc.lines.finish_line(
|
|
&mut ifc.current_nesting_level,
|
|
containing_block,
|
|
ifc.inline_position,
|
|
);
|
|
return FlowLayout {
|
|
fragments: ifc.lines.fragments,
|
|
content_block_size: ifc.lines.next_line_block_position,
|
|
collapsible_margins_in_children: CollapsedBlockMargins::zero(),
|
|
};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Lines {
|
|
fn finish_line(
|
|
&mut self,
|
|
top_nesting_level: &mut InlineNestingLevelState,
|
|
containing_block: &ContainingBlock,
|
|
line_content_inline_size: Length,
|
|
) {
|
|
let mut line_contents = std::mem::take(&mut top_nesting_level.fragments_so_far);
|
|
let line_block_size = std::mem::replace(
|
|
&mut top_nesting_level.max_block_size_of_fragments_so_far,
|
|
Length::zero(),
|
|
);
|
|
enum TextAlign {
|
|
Start,
|
|
Center,
|
|
End,
|
|
}
|
|
let line_left_is_inline_start = containing_block
|
|
.style
|
|
.writing_mode
|
|
.line_left_is_inline_start();
|
|
let text_align = match containing_block.style.clone_text_align() {
|
|
TextAlignKeyword::Start => TextAlign::Start,
|
|
TextAlignKeyword::Center => TextAlign::Center,
|
|
TextAlignKeyword::End => TextAlign::End,
|
|
TextAlignKeyword::Left => {
|
|
if line_left_is_inline_start {
|
|
TextAlign::Start
|
|
} else {
|
|
TextAlign::End
|
|
}
|
|
},
|
|
TextAlignKeyword::Right => {
|
|
if line_left_is_inline_start {
|
|
TextAlign::End
|
|
} else {
|
|
TextAlign::Start
|
|
}
|
|
},
|
|
};
|
|
let move_by = match text_align {
|
|
TextAlign::Start => Length::zero(),
|
|
TextAlign::Center => (containing_block.inline_size - line_content_inline_size) / 2.,
|
|
TextAlign::End => containing_block.inline_size - line_content_inline_size,
|
|
};
|
|
if move_by > Length::zero() {
|
|
for fragment in &mut line_contents {
|
|
fragment.offset_inline(&move_by);
|
|
}
|
|
}
|
|
let start_corner = Vec2 {
|
|
inline: Length::zero(),
|
|
block: self.next_line_block_position,
|
|
};
|
|
let size = Vec2 {
|
|
inline: containing_block.inline_size,
|
|
block: line_block_size,
|
|
};
|
|
self.next_line_block_position += size.block;
|
|
self.fragments
|
|
.push(Fragment::Anonymous(AnonymousFragment::new(
|
|
Rect { start_corner, size },
|
|
line_contents,
|
|
containing_block.style.writing_mode,
|
|
)))
|
|
}
|
|
}
|
|
|
|
impl InlineBox {
|
|
fn start_layout<'box_tree>(
|
|
&'box_tree self,
|
|
ifc: &mut InlineFormattingContextState<'box_tree, '_, '_>,
|
|
) -> PartialInlineBoxFragment<'box_tree> {
|
|
let style = self.style.clone();
|
|
let cbis = ifc.containing_block.inline_size;
|
|
let mut padding = style.padding().percentages_relative_to(cbis);
|
|
let mut border = style.border_width();
|
|
let mut margin = style
|
|
.margin()
|
|
.percentages_relative_to(cbis)
|
|
.auto_is(Length::zero);
|
|
if self.first_fragment {
|
|
ifc.inline_position += padding.inline_start + border.inline_start + margin.inline_start;
|
|
} else {
|
|
padding.inline_start = Length::zero();
|
|
border.inline_start = Length::zero();
|
|
margin.inline_start = Length::zero();
|
|
}
|
|
let mut start_corner = Vec2 {
|
|
block: padding.block_start + border.block_start + margin.block_start,
|
|
inline: ifc.inline_position - ifc.current_nesting_level.inline_start,
|
|
};
|
|
if style.clone_position().is_relative() {
|
|
start_corner += &relative_adjustement(&style, ifc.containing_block)
|
|
}
|
|
PartialInlineBoxFragment {
|
|
tag: self.tag,
|
|
style,
|
|
start_corner,
|
|
padding,
|
|
border,
|
|
margin,
|
|
last_box_tree_fragment: self.last_fragment,
|
|
parent_nesting_level: std::mem::replace(
|
|
&mut ifc.current_nesting_level,
|
|
InlineNestingLevelState {
|
|
remaining_boxes: self.children.iter(),
|
|
fragments_so_far: Vec::with_capacity(self.children.len()),
|
|
inline_start: ifc.inline_position,
|
|
max_block_size_of_fragments_so_far: Length::zero(),
|
|
},
|
|
),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'box_tree> PartialInlineBoxFragment<'box_tree> {
|
|
fn finish_layout(
|
|
&mut self,
|
|
nesting_level: &mut InlineNestingLevelState,
|
|
inline_position: &mut Length,
|
|
at_line_break: bool,
|
|
) {
|
|
let content_rect = Rect {
|
|
size: Vec2 {
|
|
inline: *inline_position - self.start_corner.inline,
|
|
block: nesting_level.max_block_size_of_fragments_so_far,
|
|
},
|
|
start_corner: self.start_corner.clone(),
|
|
};
|
|
|
|
let mut fragment = BoxFragment::new(
|
|
self.tag,
|
|
self.style.clone(),
|
|
std::mem::take(&mut nesting_level.fragments_so_far),
|
|
content_rect,
|
|
self.padding.clone(),
|
|
self.border.clone(),
|
|
self.margin.clone(),
|
|
CollapsedBlockMargins::zero(),
|
|
None, // hoisted_fragment_id
|
|
);
|
|
let last_fragment = self.last_box_tree_fragment && !at_line_break;
|
|
if last_fragment {
|
|
*inline_position += fragment.padding.inline_end +
|
|
fragment.border.inline_end +
|
|
fragment.margin.inline_end;
|
|
} else {
|
|
fragment.padding.inline_end = Length::zero();
|
|
fragment.border.inline_end = Length::zero();
|
|
fragment.margin.inline_end = Length::zero();
|
|
}
|
|
self.parent_nesting_level
|
|
.max_block_size_of_fragments_so_far
|
|
.max_assign(
|
|
fragment.content_rect.size.block +
|
|
fragment.padding.block_sum() +
|
|
fragment.border.block_sum() +
|
|
fragment.margin.block_sum(),
|
|
);
|
|
self.parent_nesting_level
|
|
.fragments_so_far
|
|
.push(Fragment::Box(fragment));
|
|
}
|
|
}
|
|
|
|
fn layout_atomic<'box_tree>(
|
|
layout_context: &LayoutContext,
|
|
ifc: &mut InlineFormattingContextState<'box_tree, '_, '_>,
|
|
atomic: &'box_tree IndependentFormattingContext,
|
|
) {
|
|
let cbis = ifc.containing_block.inline_size;
|
|
let padding = atomic.style.padding().percentages_relative_to(cbis);
|
|
let border = atomic.style.border_width();
|
|
let margin = atomic
|
|
.style
|
|
.margin()
|
|
.percentages_relative_to(cbis)
|
|
.auto_is(Length::zero);
|
|
let pbm = &(&padding + &border) + &margin;
|
|
ifc.inline_position += pbm.inline_start;
|
|
let mut start_corner = Vec2 {
|
|
block: pbm.block_start,
|
|
inline: ifc.inline_position - ifc.current_nesting_level.inline_start,
|
|
};
|
|
if atomic.style.clone_position().is_relative() {
|
|
start_corner += &relative_adjustement(&atomic.style, ifc.containing_block)
|
|
}
|
|
|
|
let fragment = match atomic.as_replaced() {
|
|
Ok(replaced) => {
|
|
let size = replaced.used_size_as_if_inline_element(ifc.containing_block, &atomic.style);
|
|
let fragments = replaced.make_fragments(&atomic.style, size.clone());
|
|
let content_rect = Rect { start_corner, size };
|
|
BoxFragment::new(
|
|
atomic.tag,
|
|
atomic.style.clone(),
|
|
fragments,
|
|
content_rect,
|
|
padding,
|
|
border,
|
|
margin,
|
|
CollapsedBlockMargins::zero(),
|
|
None, // hoisted_fragment_id
|
|
)
|
|
},
|
|
Err(non_replaced) => {
|
|
let box_size = atomic.style.box_size();
|
|
let max_box_size = atomic
|
|
.style
|
|
.max_box_size()
|
|
.percentages_relative_to(ifc.containing_block);
|
|
let min_box_size = atomic
|
|
.style
|
|
.min_box_size()
|
|
.percentages_relative_to(ifc.containing_block)
|
|
.auto_is(Length::zero);
|
|
|
|
// https://drafts.csswg.org/css2/visudet.html#inlineblock-width
|
|
let tentative_inline_size =
|
|
box_size.inline.percentage_relative_to(cbis).auto_is(|| {
|
|
let available_size = cbis - pbm.inline_sum();
|
|
atomic.content_sizes.shrink_to_fit(available_size)
|
|
});
|
|
|
|
// https://drafts.csswg.org/css2/visudet.html#min-max-widths
|
|
// In this case “applying the rules above again” with a non-auto inline-size
|
|
// always results in that size.
|
|
let inline_size = tentative_inline_size
|
|
.clamp_between_extremums(min_box_size.inline, max_box_size.inline);
|
|
|
|
let block_size = box_size
|
|
.block
|
|
.maybe_percentage_relative_to(ifc.containing_block.block_size.non_auto());
|
|
let containing_block_for_children = ContainingBlock {
|
|
inline_size,
|
|
block_size,
|
|
style: &atomic.style,
|
|
};
|
|
assert_eq!(
|
|
ifc.containing_block.style.writing_mode,
|
|
containing_block_for_children.style.writing_mode,
|
|
"Mixed writing modes are not supported yet"
|
|
);
|
|
// FIXME is this correct?
|
|
let dummy_tree_rank = 0;
|
|
// FIXME: Do we need to call `adjust_static_positions` somewhere near here?
|
|
let independent_layout = non_replaced.layout(
|
|
layout_context,
|
|
ifc.positioning_context,
|
|
&containing_block_for_children,
|
|
dummy_tree_rank,
|
|
);
|
|
|
|
// https://drafts.csswg.org/css2/visudet.html#block-root-margin
|
|
let tentative_block_size = block_size.auto_is(|| independent_layout.content_block_size);
|
|
|
|
// https://drafts.csswg.org/css2/visudet.html#min-max-heights
|
|
// In this case “applying the rules above again” with a non-auto block-size
|
|
// always results in that size.
|
|
let block_size = tentative_block_size
|
|
.clamp_between_extremums(min_box_size.block, max_box_size.block);
|
|
|
|
let content_rect = Rect {
|
|
start_corner,
|
|
size: Vec2 {
|
|
block: block_size,
|
|
inline: inline_size,
|
|
},
|
|
};
|
|
BoxFragment::new(
|
|
atomic.tag,
|
|
atomic.style.clone(),
|
|
independent_layout.fragments,
|
|
content_rect,
|
|
padding,
|
|
border,
|
|
margin,
|
|
CollapsedBlockMargins::zero(),
|
|
None, // hoisted_fragment_id
|
|
)
|
|
},
|
|
};
|
|
|
|
ifc.inline_position += pbm.inline_end + fragment.content_rect.size.inline;
|
|
ifc.current_nesting_level
|
|
.max_block_size_of_fragments_so_far
|
|
.max_assign(pbm.block_sum() + fragment.content_rect.size.block);
|
|
ifc.current_nesting_level
|
|
.fragments_so_far
|
|
.push(Fragment::Box(fragment));
|
|
}
|
|
|
|
struct BreakAndShapeResult {
|
|
font_ascent: Au,
|
|
font_line_gap: Au,
|
|
font_key: FontInstanceKey,
|
|
runs: Vec<GlyphRun>,
|
|
break_at_start: bool,
|
|
}
|
|
|
|
impl TextRun {
|
|
fn break_and_shape(&self, layout_context: &LayoutContext) -> BreakAndShapeResult {
|
|
use gfx::font::ShapingFlags;
|
|
use style::computed_values::text_rendering::T as TextRendering;
|
|
use style::computed_values::word_break::T as WordBreak;
|
|
|
|
let font_style = self.parent_style.clone_font();
|
|
let inherited_text_style = self.parent_style.get_inherited_text();
|
|
let letter_spacing = if inherited_text_style.letter_spacing.0.px() != 0. {
|
|
Some(app_units::Au::from(inherited_text_style.letter_spacing.0))
|
|
} else {
|
|
None
|
|
};
|
|
|
|
let mut flags = ShapingFlags::empty();
|
|
if letter_spacing.is_some() {
|
|
flags.insert(ShapingFlags::IGNORE_LIGATURES_SHAPING_FLAG);
|
|
}
|
|
if inherited_text_style.text_rendering == TextRendering::Optimizespeed {
|
|
flags.insert(ShapingFlags::IGNORE_LIGATURES_SHAPING_FLAG);
|
|
flags.insert(ShapingFlags::DISABLE_KERNING_SHAPING_FLAG)
|
|
}
|
|
if inherited_text_style.word_break == WordBreak::KeepAll {
|
|
flags.insert(ShapingFlags::KEEP_ALL_FLAG);
|
|
}
|
|
|
|
crate::context::with_thread_local_font_context(layout_context, |font_context| {
|
|
let font_group = font_context.font_group(font_style);
|
|
let font = font_group
|
|
.borrow_mut()
|
|
.first(font_context)
|
|
.expect("could not find font");
|
|
let mut font = font.borrow_mut();
|
|
|
|
let word_spacing = &inherited_text_style.word_spacing;
|
|
let word_spacing = word_spacing
|
|
.to_length()
|
|
.map(|l| l.into())
|
|
.unwrap_or_else(|| {
|
|
let space_width = font
|
|
.glyph_index(' ')
|
|
.map(|glyph_id| font.glyph_h_advance(glyph_id))
|
|
.unwrap_or(gfx::font::LAST_RESORT_GLYPH_ADVANCE);
|
|
word_spacing.to_used_value(Au::from_f64_px(space_width))
|
|
});
|
|
|
|
let shaping_options = gfx::font::ShapingOptions {
|
|
letter_spacing,
|
|
word_spacing,
|
|
script: unicode_script::Script::Common,
|
|
flags,
|
|
};
|
|
|
|
let (runs, break_at_start) = gfx::text::text_run::TextRun::break_and_shape(
|
|
&mut font,
|
|
&self.text,
|
|
&shaping_options,
|
|
&mut None,
|
|
);
|
|
|
|
BreakAndShapeResult {
|
|
font_ascent: font.metrics.ascent,
|
|
font_line_gap: font.metrics.line_gap,
|
|
font_key: font.font_key,
|
|
runs,
|
|
break_at_start,
|
|
}
|
|
})
|
|
}
|
|
|
|
fn layout(&self, layout_context: &LayoutContext, ifc: &mut InlineFormattingContextState) {
|
|
use style::values::generics::text::LineHeight;
|
|
|
|
let BreakAndShapeResult {
|
|
font_ascent,
|
|
font_line_gap,
|
|
font_key,
|
|
runs,
|
|
break_at_start: _,
|
|
} = self.break_and_shape(layout_context);
|
|
let font_size = self.parent_style.get_font().font_size.size.0;
|
|
let mut runs = runs.iter();
|
|
loop {
|
|
let mut glyphs = vec![];
|
|
let mut advance_width = Length::zero();
|
|
let mut last_break_opportunity = None;
|
|
loop {
|
|
let next = runs.next();
|
|
if next
|
|
.as_ref()
|
|
.map_or(true, |run| run.glyph_store.is_whitespace())
|
|
{
|
|
if advance_width > ifc.containing_block.inline_size - ifc.inline_position {
|
|
if let Some((len, width, iter)) = last_break_opportunity.take() {
|
|
glyphs.truncate(len);
|
|
advance_width = width;
|
|
runs = iter;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if let Some(run) = next {
|
|
if run.glyph_store.is_whitespace() {
|
|
last_break_opportunity = Some((glyphs.len(), advance_width, runs.clone()));
|
|
}
|
|
glyphs.push(run.glyph_store.clone());
|
|
advance_width += Length::from(run.glyph_store.total_advance());
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
let line_height = match self.parent_style.get_inherited_text().line_height {
|
|
LineHeight::Normal => font_line_gap.into(),
|
|
LineHeight::Number(n) => font_size * n.0,
|
|
LineHeight::Length(l) => l.0,
|
|
};
|
|
let rect = Rect {
|
|
start_corner: Vec2 {
|
|
block: Length::zero(),
|
|
inline: ifc.inline_position - ifc.current_nesting_level.inline_start,
|
|
},
|
|
size: Vec2 {
|
|
block: line_height,
|
|
inline: advance_width,
|
|
},
|
|
};
|
|
ifc.inline_position += advance_width;
|
|
ifc.current_nesting_level
|
|
.max_block_size_of_fragments_so_far
|
|
.max_assign(line_height);
|
|
ifc.current_nesting_level
|
|
.fragments_so_far
|
|
.push(Fragment::Text(TextFragment {
|
|
tag: self.tag,
|
|
debug_id: DebugId::new(),
|
|
parent_style: self.parent_style.clone(),
|
|
rect,
|
|
ascent: font_ascent.into(),
|
|
font_key,
|
|
glyphs,
|
|
}));
|
|
if runs.is_empty() {
|
|
break;
|
|
} else {
|
|
// New line
|
|
ifc.current_nesting_level.inline_start = Length::zero();
|
|
let mut nesting_level = &mut ifc.current_nesting_level;
|
|
for partial in ifc.partial_inline_boxes_stack.iter_mut().rev() {
|
|
partial.finish_layout(nesting_level, &mut ifc.inline_position, true);
|
|
partial.start_corner.inline = Length::zero();
|
|
partial.padding.inline_start = Length::zero();
|
|
partial.border.inline_start = Length::zero();
|
|
partial.margin.inline_start = Length::zero();
|
|
partial.parent_nesting_level.inline_start = Length::zero();
|
|
nesting_level = &mut partial.parent_nesting_level;
|
|
}
|
|
ifc.lines
|
|
.finish_line(nesting_level, ifc.containing_block, ifc.inline_position);
|
|
ifc.inline_position = Length::zero();
|
|
}
|
|
}
|
|
}
|
|
}
|