use crate::hbox::PureHorzBox;
use crate::length::Length;
use crate::vbox::VertBox;
pub const MIN_FIRST_ASCENDER: Length = Length::pt(9.0);
#[derive(Clone, Debug, PartialEq, syan::visit::Ast)]
#[subast(crate::hbox::PureHorzBox)]
pub struct PlacedLine {
pub x: Length,
pub baseline_y: Length,
pub contents: Vec<(Length, PureHorzBox)>,
}
#[derive(Clone, Debug, Default, PartialEq, syan::visit::Ast)]
#[subast(crate::pagebreak::PlacedLine)]
pub struct Page {
pub lines: Vec<PlacedLine>,
pub body_lines: usize,
}
pub fn placed_line_extent(line: &PlacedLine) -> Option<(Length, Length)> {
fn go(bx: &PureHorzBox, height: &mut Length, depth: &mut Length, has_real: &mut bool) {
match bx {
PureHorzBox::HookPageBreak { .. }
| PureHorzBox::FrameMarker { .. }
| PureHorzBox::Footnote { .. } => {}
PureHorzBox::OuterEmpty { .. } | PureHorzBox::OuterFil | PureHorzBox::FixedEmpty { .. } => {}
PureHorzBox::InnerString { height: h, depth: d, .. }
| PureHorzBox::Graphics { height: h, depth: d, .. }
| PureHorzBox::GraphicsOuter { height: h, depth: d, .. }
| PureHorzBox::Math { height: h, depth: d, .. }
| PureHorzBox::EmbeddedBlock { height: h, depth: d, .. }
| PureHorzBox::Frame { height: h, depth: d, .. } => {
*has_real = true;
*height = (*height).max(*h);
*depth = (*depth).max(*d);
}
PureHorzBox::Image { height: h, .. } => {
*has_real = true;
*height = (*height).max(*h);
}
PureHorzBox::Tabular(tab) => {
*has_real = true;
*height = (*height).max(tab.height);
*depth = (*depth).max(tab.depth);
}
PureHorzBox::Discretionary { no_break, .. } => {
for p in no_break {
go(p, height, depth, has_real);
}
}
PureHorzBox::InlineMark(_) => {}
PureHorzBox::InlineFrameMarker { height: h, depth: d, .. } => {
*height = (*height).max(*h);
*depth = (*depth).max(*d);
}
}
}
let mut height = Length::ZERO;
let mut depth = Length::ZERO;
let mut has_real = false;
for (_, bx) in &line.contents {
go(bx, &mut height, &mut depth, &mut has_real);
}
has_real.then_some((height, depth))
}
pub fn chop_page(
origin: (Length, Length),
height: Length,
vboxes: &mut Vec<VertBox>,
) -> Vec<PlacedLine> {
let (x0, y0) = origin;
let y_limit = y0 + height;
let mut lines: Vec<PlacedLine> = Vec::new();
let mut footnotes: Vec<VertBox> = Vec::new();
let mut footnote_h = Length::ZERO;
let mut prev_baseline: Option<Length> = None;
let mut prev_depth = Length::ZERO;
let mut pending_skip = Length::ZERO;
let mut pending_pad = Length::ZERO;
let mut placed_real_line = false;
let mut placed_any_line = false;
let mut carried_pads: Vec<Length> = vboxes
.iter()
.take(unmatched_frame_ends(vboxes))
.filter_map(|vb| match vb {
VertBox::FramePad(l) => Some(*l),
_ => None,
})
.collect();
let mut open_pads: Vec<Option<Length>> = Vec::new();
let mut idx = 0;
while idx < vboxes.len() {
match &vboxes[idx] {
VertBox::Skip(l) => {
pending_skip = pending_skip.max(*l);
idx += 1;
}
VertBox::ParagTop(l) => {
pending_skip = pending_skip.max(*l);
idx += 1;
}
VertBox::FramePad(l) => {
pending_pad += *l;
if let Some(slot @ None) = open_pads.last_mut() {
*slot = Some(*l);
}
idx += 1;
}
VertBox::ClearPage => {
idx += 1;
if placed_any_line {
break; }
}
VertBox::HookPageBreak(id) => {
let pos = prev_baseline.unwrap_or(y0);
lines.push(PlacedLine {
x: x0,
baseline_y: pos,
contents: vec![(Length::ZERO, PureHorzBox::HookPageBreak { id: *id })],
});
idx += 1;
}
VertBox::FrameStart(id) => {
let pos = prev_baseline.unwrap_or(y0);
open_pads.push(None);
lines.push(PlacedLine {
x: x0,
baseline_y: pos,
contents: vec![(
Length::ZERO,
PureHorzBox::FrameMarker { id: *id, end: false },
)],
});
idx += 1;
}
VertBox::FrameEnd(id) => {
let pos = prev_baseline.unwrap_or(y0);
if open_pads.pop().is_none() {
carried_pads.pop();
}
lines.push(PlacedLine {
x: x0,
baseline_y: pos,
contents: vec![(
Length::ZERO,
PureHorzBox::FrameMarker { id: *id, end: true },
)],
});
idx += 1;
}
VertBox::ListMark(_) => {
idx += 1;
}
VertBox::Line {
height: h,
depth,
leading,
contents,
} => {
let baseline = match prev_baseline {
None => y0 + pending_pad + *h,
Some(b) if pending_skip + pending_pad > Length::ZERO => {
b + prev_depth + pending_skip + pending_pad + *h
}
Some(b) => b + leading.max(prev_depth + *h),
};
let mut new_footnotes = Vec::new();
collect_footnotes(contents, &mut new_footnotes);
let footnote_h_new = if new_footnotes.is_empty() {
footnote_h
} else {
let mut all = footnotes.clone();
all.extend(new_footnotes.iter().cloned());
stack_height(&all)
};
if baseline + *depth > y_limit - footnote_h_new && placed_real_line {
break; }
pending_skip = Length::ZERO;
pending_pad = Length::ZERO;
prev_baseline = Some(baseline);
prev_depth = *depth;
placed_any_line = true;
if *h + *depth > Length::ZERO {
placed_real_line = true;
}
lines.push(PlacedLine {
x: x0,
baseline_y: baseline,
contents: contents.clone(),
});
footnotes.extend(new_footnotes);
footnote_h = footnote_h_new;
idx += 1;
}
}
}
vboxes.drain(0..idx);
if !vboxes.is_empty() {
let reopened: Vec<VertBox> = carried_pads
.iter()
.copied()
.chain(open_pads.into_iter().flatten())
.map(VertBox::FramePad)
.collect();
vboxes.splice(0..0, reopened);
}
if !footnotes.is_empty() {
let fh = stack_height(&footnotes);
lines.extend(place_block_at((x0, y0 + height - fh), footnotes));
}
lines
}
fn unmatched_frame_ends(vboxes: &[VertBox]) -> usize {
let mut depth: i32 = 0;
let mut deepest: i32 = 0;
for vb in vboxes {
match vb {
VertBox::FrameStart(_) => depth += 1,
VertBox::FrameEnd(_) => {
depth -= 1;
deepest = deepest.min(depth);
}
_ => {}
}
}
(-deepest) as usize
}
fn stack_height(vboxes: &[VertBox]) -> Length {
let mut prev_baseline: Option<Length> = None;
let mut prev_depth = Length::ZERO;
let mut pending_skip = Length::ZERO;
let mut pending_pad = Length::ZERO;
let mut bottom = Length::ZERO;
for vb in vboxes {
match vb {
VertBox::Skip(l) | VertBox::ParagTop(l) => pending_skip = pending_skip.max(*l),
VertBox::FramePad(l) => pending_pad += *l,
VertBox::ClearPage | VertBox::HookPageBreak(_) => {}
VertBox::FrameStart(_) | VertBox::FrameEnd(_) => {}
VertBox::ListMark(_) => {}
VertBox::Line {
height,
depth,
leading,
..
} => {
let baseline = match prev_baseline {
None => pending_skip + pending_pad + *height,
Some(b) if pending_skip + pending_pad > Length::ZERO => {
b + prev_depth + pending_skip + pending_pad + *height
}
Some(b) => b + leading.max(prev_depth + *height),
};
pending_skip = Length::ZERO;
pending_pad = Length::ZERO;
prev_baseline = Some(baseline);
prev_depth = *depth;
bottom = baseline + *depth;
}
}
}
bottom
}
fn collect_footnotes(contents: &[(Length, PureHorzBox)], out: &mut Vec<VertBox>) {
for (_, bx) in contents {
collect_footnotes_in_box(bx, out);
}
}
fn collect_footnotes_in_box(bx: &PureHorzBox, out: &mut Vec<VertBox>) {
match bx {
PureHorzBox::Footnote { block } => out.extend(block.iter().cloned()),
PureHorzBox::Discretionary { no_break, .. } => {
for b in no_break {
collect_footnotes_in_box(b, out);
}
}
PureHorzBox::Tabular(tab) => {
for cell in &tab.cells {
for (_, cbx) in &cell.contents {
collect_footnotes_in_box(cbx, out);
}
}
}
PureHorzBox::EmbeddedBlock { block, .. } => {
for vb in block {
if let VertBox::Line { contents, .. } = vb {
collect_footnotes(contents, out);
}
}
}
PureHorzBox::Frame { contents, .. } => {
for (_, cbx) in contents {
collect_footnotes_in_box(cbx, out);
}
}
_ => {}
}
}
pub fn place_block_at(origin: (Length, Length), vboxes: Vec<VertBox>) -> Vec<PlacedLine> {
let (x0, y0) = origin;
let mut lines = Vec::new();
let mut prev_baseline: Option<Length> = None;
let mut prev_depth = Length::ZERO;
let mut pending_skip = Length::ZERO;
let mut pending_pad = Length::ZERO;
for vbox in vboxes {
match vbox {
VertBox::Skip(l) | VertBox::ParagTop(l) => pending_skip = pending_skip.max(l),
VertBox::FramePad(l) => pending_pad += l,
VertBox::ClearPage => {}
VertBox::HookPageBreak(id) => {
let pos = prev_baseline.unwrap_or(y0);
lines.push(PlacedLine {
x: x0,
baseline_y: pos,
contents: vec![(Length::ZERO, PureHorzBox::HookPageBreak { id })],
});
}
VertBox::FrameStart(id) => {
let pos = prev_baseline.unwrap_or(y0);
lines.push(PlacedLine {
x: x0,
baseline_y: pos,
contents: vec![(Length::ZERO, PureHorzBox::FrameMarker { id, end: false })],
});
}
VertBox::FrameEnd(id) => {
let pos = prev_baseline.unwrap_or(y0);
lines.push(PlacedLine {
x: x0,
baseline_y: pos,
contents: vec![(Length::ZERO, PureHorzBox::FrameMarker { id, end: true })],
});
}
VertBox::ListMark(_) => {}
VertBox::Line {
height,
depth,
leading,
contents,
} => {
let baseline = match prev_baseline {
None => y0 + height,
Some(b) if pending_skip + pending_pad > Length::ZERO => {
b + prev_depth + pending_skip + pending_pad + height
}
Some(b) => b + leading.max(prev_depth + height),
};
pending_skip = Length::ZERO;
pending_pad = Length::ZERO;
prev_baseline = Some(baseline);
prev_depth = depth;
lines.push(PlacedLine {
x: x0,
baseline_y: baseline,
contents,
});
}
}
}
lines
}