//! Current Column slots and their persistent composition. Shape contains only //! original structural paths; target nodes are read from the current document. use std::sync::Arc; use super::evaluation::{record, EvalRecord, EvalWork, RenderScope, RenderedChange}; use super::line_plan::{ChangeKind, LineOp, LinePlan, LineSplice, PlanChange, ProjectionState}; use super::{LayoutContext, LayoutNode, Line, LocalStep, Rendered}; #[derive(Debug, Default)] struct ShapeRoute { slot: Option, next: Box<[(LocalStep, ShapeRoute)]>, } #[derive(Debug)] struct ColumnShape { paths: Box<[Arc<[LocalStep]>]>, routes: ShapeRoute, } impl ColumnShape { fn new(paths: Vec>) -> Arc { // column_leaves supplies paths in original lexicographic order. Freeze // each prefix group once; updates only binary-search these shared arrays. fn build(paths: &[Arc<[LocalStep]>], first: usize, depth: usize) -> ShapeRoute { if paths.first().is_some_and(|path| path.len() == depth) { assert_eq!(paths.len(), 1, "one leaf at each original Column path"); return ShapeRoute { slot: Some(first), next: Box::new([]), }; } let mut next = Vec::new(); let mut start = 0; while start < paths.len() { let step = paths[start][depth]; let mut end = start + 1; while end < paths.len() && paths[end][depth] == step { end += 1; } record(EvalWork { column_shape_steps_visited: (end - start) as u64, ..EvalWork::default() }); next.push((step, build(&paths[start..end], first + start, depth + 1))); start = end; } ShapeRoute { slot: None, next: next.into_boxed_slice(), } } let routes = build(&paths, 0, 0); Arc::new(Self { paths: paths.into_boxed_slice(), routes, }) } fn dirty_slots(&self, scope: &RenderScope<'_>) -> Vec { fn collect( route: &ShapeRoute, scope: &RenderScope<'_>, path: &mut Vec, all: bool, slots: &mut Vec, ) { record(EvalWork { column_shape_steps_visited: 1, ..EvalWork::default() }); if let Some(slot) = route.slot { slots.push(slot); return; } let dirty = scope.dirty_route(path); if all || dirty.local { for (step, child) in &route.next { path.push(*step); collect(child, scope, path, true, slots); path.pop(); } } else { for step in dirty.next { if let Ok(index) = route.next.binary_search_by_key(&step, |(key, _)| *key) { path.push(step); collect(&route.next[index].1, scope, path, false, slots); path.pop(); } } } } let mut slots = Vec::new(); collect(&self.routes, scope, &mut Vec::new(), false, &mut slots); slots } fn scope<'a>(&self, scope: &RenderScope<'a>, index: usize) -> Result, String> { let mut result = scope.clone(); for step in self.paths[index].iter() { record(EvalWork { column_shape_steps_visited: 1, ..EvalWork::default() }); let (LayoutNode::Column { children, .. }, LocalStep::ColumnChild(index)) = (result.view.node, step) else { return Err("Native retained Column shape lost its original path".to_owned()); }; let child = children .get(*index) .ok_or_else(|| "Native retained Column shape lost its child slot".to_owned())?; result = result.child(*step, child); } Ok(result) } } #[derive(Debug)] struct ColumnSlot { record: Option>, raw: LinePlan, normalized: LinePlan, projection: Option>, } impl ColumnSlot { fn normalize( raw: LinePlan, child: Option>, proof: Option<&PlanChange>, target: i64, previous: Option<&Self>, ) -> (Arc, Option) { let extra = (target - raw.first_width()).max(0); let (normalized, projection, change) = if extra == 0 { let change = previous.map(|old| { proof .filter(|proof| proof.applies_to(&old.normalized, &raw)) .cloned() .unwrap_or_else(|| PlanChange::replace_all(&old.normalized, &raw)) }); (raw.clone(), None, change) } else { let ops = Arc::from([LineOp::AppendSpace(extra), LineOp::ClearBreaks]); if let Some(old) = previous.and_then(|old| old.projection.as_ref()) { let update = old.update(raw.clone(), proof, ops); ( update.state.plan().clone(), Some(update.state), Some(update.change), ) } else { let projection = ProjectionState::new(raw.clone(), ops); let normalized = projection.plan().clone(); let change = previous.map(|old| PlanChange::replace_all(&old.normalized, &normalized)); (normalized, Some(projection), change) } }; ( Arc::new(Self { record: child, raw, normalized, projection, }), change, ) } } #[derive(Clone, Copy, Debug, Default)] struct ColumnMeasure { slots: usize, lines: usize, max_first: i64, max_width: i64, max_width_minus_first: Option, first_nonempty: Option, } impl ColumnMeasure { fn leaf(slot: &ColumnSlot) -> Self { let first = slot.raw.first_width(); let width = slot.raw.max_width(); Self { slots: 1, lines: slot.raw.len(), max_first: first, max_width: width, max_width_minus_first: (!slot.raw.is_empty()).then_some(width - first), first_nonempty: (!slot.raw.is_empty()).then_some(first), } } fn combine(self, other: Self) -> Self { Self { slots: self.slots + other.slots, lines: self.lines + other.lines, max_first: self.max_first.max(other.max_first), max_width: self.max_width.max(other.max_width), max_width_minus_first: self.max_width_minus_first.max(other.max_width_minus_first), first_nonempty: self.first_nonempty.or(other.first_nonempty), } } fn normalized_max(self, target: i64) -> i64 { self.max_width_minus_first .map_or(0, |delta| self.max_width.max(target + delta)) } } #[derive(Debug)] enum ColumnNode { Leaf { slot: Arc, measure: ColumnMeasure, }, Branch { left: Arc, right: Arc, measure: ColumnMeasure, lines: LinePlan, }, } impl ColumnNode { fn leaf(slot: Arc) -> Arc { record(EvalWork { column_tree_nodes_created: 1, ..EvalWork::default() }); Arc::new(Self::Leaf { measure: ColumnMeasure::leaf(&slot), slot, }) } fn branch(left: Arc, right: Arc, previous: Option<&Self>) -> Arc { record(EvalWork { column_tree_nodes_created: 1, ..EvalWork::default() }); let lines = match previous { Some(Self::Branch { left: old_left, right: old_right, lines, .. }) if left.lines().ptr_eq(old_left.lines()) && right.lines().ptr_eq(old_right.lines()) => { lines.clone() } _ => left.lines().concat(right.lines()), }; Arc::new(Self::Branch { measure: left.measure().combine(right.measure()), left, right, lines, }) } fn build(slots: &[Arc]) -> Option> { match slots.len() { 0 => None, 1 => Some(Self::leaf(Arc::clone(&slots[0]))), count => { let mid = count / 2; Some(Self::branch( Self::build(&slots[..mid]).unwrap(), Self::build(&slots[mid..]).unwrap(), None, )) } } } fn measure(&self) -> ColumnMeasure { match self { Self::Leaf { measure, .. } | Self::Branch { measure, .. } => *measure, } } fn lines(&self) -> &LinePlan { match self { Self::Leaf { slot, .. } => &slot.normalized, Self::Branch { lines, .. } => lines, } } fn slot(&self, index: usize) -> &Arc { record(EvalWork { column_tree_nodes_visited: 1, ..EvalWork::default() }); match self { Self::Leaf { slot, .. } => { assert_eq!(index, 0); slot } Self::Branch { left, right, .. } => { let left_len = left.measure().slots; if index < left_len { left.slot(index) } else { right.slot(index - left_len) } } } } fn prefix_lines(&self, end: usize) -> usize { record(EvalWork { column_tree_nodes_visited: 1, ..EvalWork::default() }); if end == 0 { return 0; } if end == self.measure().slots { return self.measure().lines; } match self { Self::Leaf { .. } => unreachable!("validated Column prefix"), Self::Branch { left, right, .. } => { let left_len = left.measure().slots; if end <= left_len { left.prefix_lines(end) } else { left.measure().lines + right.prefix_lines(end - left_len) } } } } fn replace(&self, index: usize, slot: Arc) -> Arc { record(EvalWork { column_tree_nodes_visited: 1, ..EvalWork::default() }); match self { Self::Leaf { .. } => { assert_eq!(index, 0); Self::leaf(slot) } Self::Branch { left, right, .. } => { let left_len = left.measure().slots; if index < left_len { Self::branch(left.replace(index, slot), Arc::clone(right), Some(self)) } else { Self::branch( Arc::clone(left), right.replace(index - left_len, slot), Some(self), ) } } } } fn retarget(&self, target: i64) -> Arc { record(EvalWork { column_tree_nodes_visited: 1, ..EvalWork::default() }); match self { Self::Leaf { slot, .. } => { record(EvalWork { column_slots_updated: 1, ..EvalWork::default() }); let same = PlanChange::same(&slot.raw); Self::leaf( ColumnSlot::normalize( slot.raw.clone(), slot.record.clone(), Some(&same), target, Some(slot), ) .0, ) } Self::Branch { left, right, .. } => { Self::branch(left.retarget(target), right.retarget(target), Some(self)) } } } } #[derive(Debug)] pub(super) struct ColumnState { shape: Arc, root: Option>, target: i64, lines: LinePlan, } #[cfg(test)] impl ColumnState { pub(super) fn shares_slot(&self, other: &Self, index: usize) -> bool { Arc::ptr_eq( self.root.as_ref().unwrap().slot(index), other.root.as_ref().unwrap().slot(index), ) } pub(super) fn shares_shape(&self, other: &Self) -> bool { Arc::ptr_eq(&self.shape, &other.shape) } } fn target_width(context: LayoutContext, intrinsic: bool, maximum: i64) -> i64 { if intrinsic || context.inline_auto_width_intrinsic || !context.viewport_width_known { maximum } else { context.viewport_width.max(0) } } fn output(root: Option<&Arc>, previous: Option<&ColumnState>) -> LinePlan { if let Some(root) = root.filter(|root| root.measure().lines > 0) { return root.lines().clone(); } if let Some(previous) = previous.filter(|previous| { previous .root .as_ref() .is_none_or(|root| root.measure().lines == 0) }) { return previous.lines.clone(); } LinePlan::from_lines([Line::default()]) } // A concatenation owns its joining break, including the former last line when // an empty boundary moves. Include that predecessor in each changed span. fn include_join_boundaries(splices: Vec) -> Vec { let mut result: Vec = Vec::with_capacity(splices.len()); for mut splice in splices { if splice.old.start == 0 || splice.new.start == 0 { splice.old.start = 0; splice.new.start = 0; } else { splice.old.start -= 1; splice.new.start -= 1; } if let Some(previous) = result.last_mut().filter(|previous| { splice.old.start <= previous.old.end || splice.new.start <= previous.new.end }) { previous.old.end = previous.old.end.max(splice.old.end); previous.new.end = previous.new.end.max(splice.new.end); } else { result.push(splice); } } result } pub(super) fn render_column<'a>( scope: &RenderScope<'a>, children: &'a [LayoutNode], context: LayoutContext, intrinsic: bool, ) -> Result { let state = if let Some(previous) = scope.previous_column() { let dirty = previous.shape.dirty_slots(scope); scope.inherit_column_uses(); let mut root = previous.root.clone(); let mut changes = Vec::with_capacity(dirty.len()); for index in dirty { let child_scope = previous.shape.scope(scope, index)?; let result = child_scope.render_with_change(context, intrinsic, None)?; let old = previous .root .as_ref() .expect("dirty Column has slots") .slot(index); let (slot, change) = ColumnSlot::normalize( result.rendered.lines, result.record, result.change.as_ref(), previous.target, Some(old), ); record(EvalWork { column_slots_updated: 1, ..EvalWork::default() }); root = Some( root.as_ref() .expect("dirty Column root") .replace(index, slot), ); changes.push((index, change.expect("previous normalized child"))); } let target = target_width( context, intrinsic, root.as_ref().map_or(0, |root| root.measure().max_first), ); if target != previous.target { record(EvalWork { column_full_rebuilds: 1, ..EvalWork::default() }); root = root.map(|root| root.retarget(target)); } let lines = output(root.as_ref(), Some(&previous)); let change = if lines.ptr_eq(&previous.lines) { PlanChange::same(&lines) } else if target != previous.target || previous .root .as_ref() .is_none_or(|root| root.measure().lines == 0) || root.as_ref().is_none_or(|root| root.measure().lines == 0) { PlanChange::replace_all(&previous.lines, &lines) } else { let old_root = previous.root.as_ref().unwrap(); let new_root = root.as_ref().unwrap(); let mut splices = Vec::new(); for (index, change) in changes { let old_offset = old_root.prefix_lines(index); let new_offset = new_root.prefix_lines(index); match change.kind() { ChangeKind::Same => {} ChangeKind::Splices(parts) => { splices.extend(parts.iter().map(|part| LineSplice { old: old_offset + part.old.start..old_offset + part.old.end, new: new_offset + part.new.start..new_offset + part.new.end, })); } ChangeKind::ReplaceAll => splices.push(LineSplice { old: old_offset..old_offset + old_root.slot(index).raw.len(), new: new_offset..new_offset + new_root.slot(index).raw.len(), }), } } PlanChange::splices(&previous.lines, &lines, include_join_boundaries(splices)) }; scope.publish_change(change); Arc::new(ColumnState { shape: Arc::clone(&previous.shape), root, target, lines, }) } else { record(EvalWork { column_full_rebuilds: 1, ..EvalWork::default() }); let mut paths = Vec::new(); let mut results = Vec::new(); for child in super::column_leaves(scope, children) { let path = &child.view.address.path[scope.view.address.path.len()..]; record(EvalWork { source_path_steps_copied: path.len() as u64, column_slots_built: 1, ..EvalWork::default() }); paths.push(Arc::from(path)); results.push(child.render_with_change(context, intrinsic, None)?); } let maximum = results .iter() .map(|result| result.rendered.first_width()) .max() .unwrap_or(0); let target = target_width(context, intrinsic, maximum); let slots = results .into_iter() .map( |RenderedChange { rendered, record, .. }| { ColumnSlot::normalize(rendered.lines, record, None, target, None).0 }, ) .collect::>(); let root = ColumnNode::build(&slots); let lines = output(root.as_ref(), None); Arc::new(ColumnState { shape: ColumnShape::new(paths), root, target, lines, }) }; debug_assert_eq!( state .root .as_ref() .map_or(0, |root| root.measure().normalized_max(state.target)), state.lines.max_width() ); let rendered = Rendered::from_line_plan(state.lines.clone()); scope.store_column(state); Ok(rendered) }