Adjust `LayoutContext` and `LayoutBox` to construct a `Vec` of boxes rather than an intrusive, garbage-collected linked list.

This commit is contained in:
David Wendt 2020-06-24 23:34:38 -04:00
parent d281452fe1
commit d7a257f93f
2 changed files with 114 additions and 195 deletions

View File

@ -89,7 +89,7 @@ pub struct EditTextData<'gc> {
autosize: AutoSizeMode, autosize: AutoSizeMode,
/// The calculated layout box. /// The calculated layout box.
layout: Option<GcCell<'gc, LayoutBox<'gc>>>, layout: Vec<LayoutBox<'gc>>,
/// The intrinsic bounds of the laid-out text. /// The intrinsic bounds of the laid-out text.
intrinsic_bounds: BoxBounds<Twips>, intrinsic_bounds: BoxBounds<Twips>,
@ -523,12 +523,8 @@ impl<'gc> EditText<'gc> {
} }
/// Render a layout box, plus it's children. /// Render a layout box, plus it's children.
fn render_layout_box( fn render_layout_box(self, context: &mut RenderContext<'_, 'gc>, lbox: &LayoutBox<'gc>) {
self, let box_transform: Transform = lbox.bounds().origin().into();
context: &mut RenderContext<'_, 'gc>,
lbox: GcCell<'gc, LayoutBox<'gc>>,
) {
let box_transform: Transform = lbox.read().bounds().origin().into();
context.transform_stack.push(&box_transform); context.transform_stack.push(&box_transform);
let edit_text = self.0.read(); let edit_text = self.0.read();
@ -538,7 +534,7 @@ impl<'gc> EditText<'gc> {
// Instead, we embed an SWF version of Noto Sans to use as the "device font", and render // Instead, we embed an SWF version of Noto Sans to use as the "device font", and render
// it the same as any other SWF outline text. // it the same as any other SWF outline text.
if let Some((text, _tf, font, params, color)) = if let Some((text, _tf, font, params, color)) =
lbox.read().as_renderable_text(edit_text.text_spans.text()) lbox.as_renderable_text(edit_text.text_spans.text())
{ {
font.evaluate( font.evaluate(
text, text,
@ -555,7 +551,7 @@ impl<'gc> EditText<'gc> {
); );
} }
if let Some(drawing) = lbox.read().as_renderable_drawing() { if let Some(drawing) = lbox.as_renderable_drawing() {
drawing.render(context); drawing.render(context);
} }
@ -612,7 +608,10 @@ impl<'gc> TDisplayObject<'gc> for EditText<'gc> {
.duplicate(context.gc_context, true) .duplicate(context.gc_context, true)
.document(); .document();
text.layout = text.layout.map(|l| l.read().duplicate(context.gc_context)); let mut new_layout = Vec::new();
for layout_box in text.layout.iter() {
new_layout.push(layout_box.duplicate(context.gc_context));
}
} }
fn object(&self) -> Value<'gc> { fn object(&self) -> Value<'gc> {
@ -710,12 +709,8 @@ impl<'gc> TDisplayObject<'gc> for EditText<'gc> {
self.0.read().drawing.render(context); self.0.read().drawing.render(context);
let mut ptr = self.0.read().layout; for layout_box in self.0.read().layout.iter() {
self.render_layout_box(context, layout_box);
while let Some(lbox) = ptr {
self.render_layout_box(context, lbox);
ptr = lbox.read().next_sibling();
} }
context.transform_stack.pop(); context.transform_stack.pop();

View File

@ -53,11 +53,8 @@ pub struct LayoutContext<'a, 'gc> {
/// The highest font size observed within the current line. /// The highest font size observed within the current line.
max_font_size: Twips, max_font_size: Twips,
/// The start of the current chain of layout boxes. /// The growing list of layout boxes to return when layout has finished.
first_box: Option<GcCell<'gc, LayoutBox<'gc>>>, boxes: Vec<LayoutBox<'gc>>,
/// The end of the current chain of layout boxes.
last_box: Option<GcCell<'gc, LayoutBox<'gc>>>,
/// The exterior bounds of all laid-out text, including left and right /// The exterior bounds of all laid-out text, including left and right
/// margins. /// margins.
@ -78,8 +75,11 @@ pub struct LayoutContext<'a, 'gc> {
/// the singular line if we have yet to process a newline. /// the singular line if we have yet to process a newline.
has_line_break: bool, has_line_break: bool,
/// All layout boxes in the current line being laid out. /// The first box within the current line.
current_line: Option<GcCell<'gc, LayoutBox<'gc>>>, ///
/// If equal to the length of the array, then no layout boxes currenly
/// exist for this line.
current_line: usize,
/// The right margin of the first span in the current line. /// The right margin of the first span in the current line.
current_line_span: TextSpan, current_line_span: TextSpan,
@ -96,12 +96,11 @@ impl<'a, 'gc> LayoutContext<'a, 'gc> {
font: None, font: None,
text, text,
max_font_size: Default::default(), max_font_size: Default::default(),
first_box: None, boxes: Vec::new(),
last_box: None,
exterior_bounds: None, exterior_bounds: None,
is_first_line: true, is_first_line: true,
has_line_break: false, has_line_break: false,
current_line: None, current_line: 0,
current_line_span: Default::default(), current_line_span: Default::default(),
max_bounds, max_bounds,
} }
@ -139,11 +138,10 @@ impl<'a, 'gc> LayoutContext<'a, 'gc> {
/// Construct an underline drawing for the current line of text and add it /// Construct an underline drawing for the current line of text and add it
/// to the line. /// to the line.
fn append_underlines(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) { fn append_underlines(&mut self) {
let mut starting_pos: Option<Position<Twips>> = None; let mut starting_pos: Option<Position<Twips>> = None;
let mut current_width: Option<Twips> = None; let mut current_width: Option<Twips> = None;
let mut line_drawing = Drawing::new(); let mut line_drawing = Drawing::new();
let mut line = self.current_line;
let mut has_underline: bool = false; let mut has_underline: bool = false;
line_drawing.set_line_style(Some(swf::LineStyle::new_v1( line_drawing.set_line_style(Some(swf::LineStyle::new_v1(
@ -151,21 +149,24 @@ impl<'a, 'gc> LayoutContext<'a, 'gc> {
swf::Color::from_rgb(0, 255), swf::Color::from_rgb(0, 255),
))); )));
while let Some(linebox) = line { if let Some(linelist) = self.boxes.get(self.current_line..) {
let read = linebox.read(); for linebox in linelist {
if linebox.is_text_box() {
if read.is_text_box() { if let Some((_t, tf, font, params, _color)) =
if let Some((_t, tf, font, params, _color)) = read.as_renderable_text(self.text) { linebox.as_renderable_text(self.text)
{
let underline_baseline = let underline_baseline =
font.get_baseline_for_height(params.height()) + Twips::from_pixels(2.0); font.get_baseline_for_height(params.height()) + Twips::from_pixels(2.0);
let mut line_extended = false; let mut line_extended = false;
if let Some(starting_pos) = starting_pos { if let Some(starting_pos) = starting_pos {
if tf.underline.unwrap_or(false) if tf.underline.unwrap_or(false)
&& underline_baseline + read.bounds().origin().y() == starting_pos.y() && underline_baseline + linebox.bounds().origin().y()
== starting_pos.y()
{ {
//Underline is at the same baseline, extend it //Underline is at the same baseline, extend it
current_width = Some(read.bounds().extent_x() - starting_pos.x()); current_width =
Some(linebox.bounds().extent_x() - starting_pos.x());
line_extended = true; line_extended = true;
} }
@ -184,16 +185,15 @@ impl<'a, 'gc> LayoutContext<'a, 'gc> {
if tf.underline.unwrap_or(false) { if tf.underline.unwrap_or(false) {
starting_pos = Some( starting_pos = Some(
read.bounds().origin() linebox.bounds().origin()
+ Position::from((Twips::zero(), underline_baseline)), + Position::from((Twips::zero(), underline_baseline)),
); );
current_width = Some(read.bounds().width()); current_width = Some(linebox.bounds().width());
}
} }
} }
} }
} }
line = read.next_sibling();
} }
if let (Some(starting_pos), Some(current_width)) = (starting_pos, current_width) { if let (Some(starting_pos), Some(current_width)) = (starting_pos, current_width) {
@ -202,10 +202,7 @@ impl<'a, 'gc> LayoutContext<'a, 'gc> {
} }
if has_underline { if has_underline {
self.append_box( self.append_box(LayoutBox::from_drawing(line_drawing));
context.gc_context,
LayoutBox::from_drawing(context.gc_context, line_drawing),
);
} }
} }
@ -224,22 +221,19 @@ impl<'a, 'gc> LayoutContext<'a, 'gc> {
only_line: bool, only_line: bool,
final_line_of_para: bool, final_line_of_para: bool,
) { ) {
if self.current_line.is_none() { if self.boxes.get_mut(self.current_line..).is_none() {
return; return;
} }
let mut line = self.current_line;
let mut line_bounds = None; let mut line_bounds = None;
let mut box_count: i32 = 0; let mut box_count: i32 = 0;
while let Some(linebox) = line { for linebox in self.boxes.get_mut(self.current_line..).unwrap() {
let mut write = linebox.write(context.gc_context);
line = write.next_sibling();
let (text, _tf, font, params, _color) = let (text, _tf, font, params, _color) =
write.as_renderable_text(self.text).expect("text"); linebox.as_renderable_text(self.text).expect("text");
//Flash ignores trailing spaces when aligning lines, so should we //Flash ignores trailing spaces when aligning lines, so should we
if self.current_line_span.align != swf::TextAlign::Left { if self.current_line_span.align != swf::TextAlign::Left {
write.bounds = write.bounds.with_size(Size::from(font.measure( linebox.bounds = linebox.bounds.with_size(Size::from(font.measure(
text.trim_end(), text.trim_end(),
params, params,
false, false,
@ -247,9 +241,9 @@ impl<'a, 'gc> LayoutContext<'a, 'gc> {
} }
if let Some(line_bounds) = &mut line_bounds { if let Some(line_bounds) = &mut line_bounds {
*line_bounds += write.bounds; *line_bounds += linebox.bounds;
} else { } else {
line_bounds = Some(write.bounds); line_bounds = Some(linebox.bounds);
} }
box_count += 1; box_count += 1;
@ -290,36 +284,32 @@ impl<'a, 'gc> LayoutContext<'a, 'gc> {
self.append_bullet(context, &self.current_line_span.clone()); self.append_bullet(context, &self.current_line_span.clone());
} }
line = self.current_line;
box_count = 0; box_count = 0;
while let Some(linebox) = line { for linebox in self.boxes.get_mut(self.current_line..).unwrap() {
let mut write = linebox.write(context.gc_context);
// TODO: This attempts to keep text of multiple font sizes vertically // TODO: This attempts to keep text of multiple font sizes vertically
// aligned correctly. It does not consider the baseline of the font, // aligned correctly. It does not consider the baseline of the font,
// which is information we don't have yet. // which is information we don't have yet.
let font_size_adjustment = self.max_font_size - write.bounds.height(); let font_size_adjustment = self.max_font_size - linebox.bounds.height();
if write.is_text_box() { if linebox.is_text_box() {
write.bounds += Position::from(( linebox.bounds += Position::from((
left_adjustment + align_adjustment + (interim_adjustment * box_count), left_adjustment + align_adjustment + (interim_adjustment * box_count),
font_size_adjustment, font_size_adjustment,
)); ));
} else if write.is_bullet() { } else if linebox.is_bullet() {
write.bounds += Position::from((Default::default(), font_size_adjustment)); linebox.bounds += Position::from((Default::default(), font_size_adjustment));
} }
line = write.next_sibling();
box_count += 1; box_count += 1;
} }
self.append_underlines(context); self.append_underlines();
line_bounds += line_bounds +=
Position::from((left_adjustment + align_adjustment, Twips::from_pixels(0.0))); Position::from((left_adjustment + align_adjustment, Twips::from_pixels(0.0)));
line_bounds += Size::from((Twips::from_pixels(0.0), font_leading_adjustment)); line_bounds += Size::from((Twips::from_pixels(0.0), font_leading_adjustment));
self.current_line = None; self.current_line = self.boxes.len();
if let Some(eb) = &mut self.exterior_bounds { if let Some(eb) = &mut self.exterior_bounds {
*eb += line_bounds; *eb += line_bounds;
@ -392,7 +382,7 @@ impl<'a, 'gc> LayoutContext<'a, 'gc> {
/// Enter a new span. /// Enter a new span.
fn newspan(&mut self, first_span: &TextSpan) { fn newspan(&mut self, first_span: &TextSpan) {
if self.current_line.is_none() { if self.is_start_of_line() {
self.current_line_span = first_span.clone(); self.current_line_span = first_span.clone();
} }
@ -426,21 +416,13 @@ impl<'a, 'gc> LayoutContext<'a, 'gc> {
/// ///
/// The text given may or may not be separated into fragments, depending on /// The text given may or may not be separated into fragments, depending on
/// what the layout calls for. /// what the layout calls for.
fn append_text( fn append_text(&mut self, text: &'a str, start: usize, end: usize, span: &TextSpan) {
&mut self,
mc: MutationContext<'gc, '_>,
text: &'a str,
start: usize,
end: usize,
span: &TextSpan,
) {
if self.effective_alignment() == swf::TextAlign::Justify { if self.effective_alignment() == swf::TextAlign::Justify {
for word in text.split(' ') { for word in text.split(' ') {
let word_start = word.as_ptr() as usize - text.as_ptr() as usize; let word_start = word.as_ptr() as usize - text.as_ptr() as usize;
let word_end = min(word_start + word.len() + 1, text.len()); let word_end = min(word_start + word.len() + 1, text.len());
self.append_text_fragment( self.append_text_fragment(
mc,
text.get(word_start..word_end).unwrap(), text.get(word_start..word_end).unwrap(),
start + word_start, start + word_start,
start + word_end, start + word_end,
@ -448,7 +430,7 @@ impl<'a, 'gc> LayoutContext<'a, 'gc> {
); );
} }
} else { } else {
self.append_text_fragment(mc, text, start, end, span); self.append_text_fragment(text, start, end, span);
} }
} }
@ -456,24 +438,16 @@ impl<'a, 'gc> LayoutContext<'a, 'gc> {
/// ///
/// This function bypasses the text fragmentation necessary for justify to /// This function bypasses the text fragmentation necessary for justify to
/// work and it should only be called internally. /// work and it should only be called internally.
fn append_text_fragment( fn append_text_fragment(&mut self, text: &'a str, start: usize, end: usize, span: &TextSpan) {
&mut self,
mc: MutationContext<'gc, '_>,
text: &'a str,
start: usize,
end: usize,
span: &TextSpan,
) {
let params = EvalParameters::from_span(span); let params = EvalParameters::from_span(span);
let text_size = Size::from(self.font.unwrap().measure(text, params, false)); let text_size = Size::from(self.font.unwrap().measure(text, params, false));
let text_bounds = BoxBounds::from_position_and_size(self.cursor, text_size); let text_bounds = BoxBounds::from_position_and_size(self.cursor, text_size);
let new_text = LayoutBox::from_text(mc, start, end, self.font.unwrap(), span); let mut new_text = LayoutBox::from_text(start, end, self.font.unwrap(), span);
let mut write = new_text.write(mc);
write.bounds = text_bounds; new_text.bounds = text_bounds;
self.cursor += Position::from((text_size.width(), Twips::default())); self.cursor += Position::from((text_size.width(), Twips::default()));
self.append_box(mc, new_text); self.append_box(new_text);
} }
/// Append a bullet to the start of the current line. /// Append a bullet to the start of the current line.
@ -497,12 +471,11 @@ impl<'a, 'gc> LayoutContext<'a, 'gc> {
let params = EvalParameters::from_span(span); let params = EvalParameters::from_span(span);
let text_size = Size::from(bullet_font.measure("\u{2022}", params, false)); let text_size = Size::from(bullet_font.measure("\u{2022}", params, false));
let text_bounds = BoxBounds::from_position_and_size(bullet_cursor, text_size); let text_bounds = BoxBounds::from_position_and_size(bullet_cursor, text_size);
let new_bullet = LayoutBox::from_bullet(context.gc_context, bullet_font, span); let mut new_bullet = LayoutBox::from_bullet(bullet_font, span);
let mut write = new_bullet.write(context.gc_context);
write.bounds = text_bounds; new_bullet.bounds = text_bounds;
self.append_box(context.gc_context, new_bullet); self.append_box(new_bullet);
} }
} }
@ -511,24 +484,8 @@ impl<'a, 'gc> LayoutContext<'a, 'gc> {
/// The box should have been positioned according to the current cursor /// The box should have been positioned according to the current cursor
/// position. It will be adjusted some time later to properly position it /// position. It will be adjusted some time later to properly position it
/// within the current layout box. /// within the current layout box.
fn append_box( fn append_box(&mut self, to_append: LayoutBox<'gc>) {
&mut self, self.boxes.push(to_append);
gc_context: MutationContext<'gc, '_>,
to_append: GcCell<'gc, LayoutBox<'gc>>,
) {
if self.first_box.is_none() {
self.first_box = Some(to_append);
}
if self.current_line.is_none() {
self.current_line = Some(to_append);
}
if let Some(last) = self.last_box {
last.write(gc_context).next_sibling = Some(to_append);
}
self.last_box = Some(to_append);
} }
/// Calculate the left-align offset of a given line of text given the span /// Calculate the left-align offset of a given line of text given the span
@ -567,17 +524,17 @@ impl<'a, 'gc> LayoutContext<'a, 'gc> {
fn end_layout( fn end_layout(
mut self, mut self,
context: &mut UpdateContext<'_, 'gc, '_>, context: &mut UpdateContext<'_, 'gc, '_>,
) -> (Option<GcCell<'gc, LayoutBox<'gc>>>, BoxBounds<Twips>) { ) -> (Vec<LayoutBox<'gc>>, BoxBounds<Twips>) {
self.fixup_line(context, !self.has_line_break, true); self.fixup_line(context, !self.has_line_break, true);
( (
self.first_box, self.boxes,
self.exterior_bounds.unwrap_or_else(Default::default), self.exterior_bounds.unwrap_or_else(Default::default),
) )
} }
fn is_start_of_line(&self) -> bool { fn is_start_of_line(&self) -> bool {
self.current_line.is_none() self.current_line >= self.boxes.len()
} }
} }
@ -591,9 +548,6 @@ pub struct LayoutBox<'gc> {
/// The rectangle corresponding to the outer boundaries of the /// The rectangle corresponding to the outer boundaries of the
bounds: BoxBounds<Twips>, bounds: BoxBounds<Twips>,
/// The layout box to be placed after this one.
next_sibling: Option<GcCell<'gc, LayoutBox<'gc>>>,
/// What content is contained by the content box. /// What content is contained by the content box.
content: LayoutContent<'gc>, content: LayoutContent<'gc>,
} }
@ -662,20 +616,11 @@ pub enum LayoutContent<'gc> {
impl<'gc> LayoutBox<'gc> { impl<'gc> LayoutBox<'gc> {
/// Construct a text box for a text node. /// Construct a text box for a text node.
pub fn from_text( pub fn from_text(start: usize, end: usize, font: Font<'gc>, span: &TextSpan) -> Self {
mc: MutationContext<'gc, '_>,
start: usize,
end: usize,
font: Font<'gc>,
span: &TextSpan,
) -> GcCell<'gc, Self> {
let params = EvalParameters::from_span(span); let params = EvalParameters::from_span(span);
GcCell::allocate(
mc,
Self { Self {
bounds: Default::default(), bounds: Default::default(),
next_sibling: None,
content: LayoutContent::Text { content: LayoutContent::Text {
start, start,
end, end,
@ -684,48 +629,30 @@ impl<'gc> LayoutBox<'gc> {
params, params,
color: CollectWrapper(span.color.clone()), color: CollectWrapper(span.color.clone()),
}, },
}, }
)
} }
/// Construct a bullet. /// Construct a bullet.
pub fn from_bullet( pub fn from_bullet(font: Font<'gc>, span: &TextSpan) -> Self {
mc: MutationContext<'gc, '_>,
font: Font<'gc>,
span: &TextSpan,
) -> GcCell<'gc, Self> {
let params = EvalParameters::from_span(span); let params = EvalParameters::from_span(span);
GcCell::allocate(
mc,
Self { Self {
bounds: Default::default(), bounds: Default::default(),
next_sibling: None,
content: LayoutContent::Bullet { content: LayoutContent::Bullet {
text_format: span.get_text_format(), text_format: span.get_text_format(),
font, font,
params, params,
color: CollectWrapper(span.color.clone()), color: CollectWrapper(span.color.clone()),
}, },
}, }
)
} }
/// Construct a drawing. /// Construct a drawing.
pub fn from_drawing(mc: MutationContext<'gc, '_>, drawing: Drawing) -> GcCell<'gc, Self> { pub fn from_drawing(drawing: Drawing) -> Self {
GcCell::allocate(
mc,
Self { Self {
bounds: Default::default(), bounds: Default::default(),
next_sibling: None,
content: LayoutContent::Drawing(drawing), content: LayoutContent::Drawing(drawing),
},
)
} }
/// Returns the next sibling box.
pub fn next_sibling(&self) -> Option<GcCell<'gc, LayoutBox<'gc>>> {
self.next_sibling
} }
/// Construct a new layout hierarchy from text spans. /// Construct a new layout hierarchy from text spans.
@ -739,7 +666,7 @@ impl<'gc> LayoutBox<'gc> {
bounds: Twips, bounds: Twips,
is_word_wrap: bool, is_word_wrap: bool,
is_device_font: bool, is_device_font: bool,
) -> (Option<GcCell<'gc, LayoutBox<'gc>>>, BoxBounds<Twips>) { ) -> (Vec<LayoutBox<'gc>>, BoxBounds<Twips>) {
let mut layout_context = LayoutContext::new(movie, bounds, fs.text()); let mut layout_context = LayoutContext::new(movie, bounds, fs.text());
for (span_start, _end, span_text, span) in fs.iter_spans() { for (span_start, _end, span_text, span) in fs.iter_spans() {
@ -798,7 +725,6 @@ impl<'gc> LayoutBox<'gc> {
let next_breakpoint = min(last_breakpoint + breakpoint + 1, text.len()); let next_breakpoint = min(last_breakpoint + breakpoint + 1, text.len());
layout_context.append_text( layout_context.append_text(
context.gc_context,
&text[last_breakpoint..next_breakpoint], &text[last_breakpoint..next_breakpoint],
start + last_breakpoint, start + last_breakpoint,
start + next_breakpoint, start + next_breakpoint,
@ -822,7 +748,6 @@ impl<'gc> LayoutBox<'gc> {
if last_breakpoint < span_end { if last_breakpoint < span_end {
layout_context.append_text( layout_context.append_text(
context.gc_context,
&text[last_breakpoint..span_end], &text[last_breakpoint..span_end],
start + last_breakpoint, start + last_breakpoint,
start + span_end, start + span_end,
@ -902,7 +827,6 @@ impl<'gc> LayoutBox<'gc> {
context, context,
Self { Self {
bounds: self.bounds, bounds: self.bounds,
next_sibling: self.next_sibling.map(|ns| ns.read().duplicate(context)),
content: self.content.clone(), content: self.content.clone(),
}, },
) )