use std::collections::HashMap;
use std::rc::Rc;
use crate::docx::{Block, Float, NoteKind, ParaStyle};
use super::LayoutCtx;
use super::cache::{CacheKey, cached};
use super::env::TextEnv;
use super::fragment::{BoxFragment, BoxKind, Child, ImageKey, Item, PlacedLine};
use super::ids::{FloatId, ParaId};
use super::job::{Memos, Yield};
use super::line::{break_lines, neighbour_borders};
use super::paragraph::{contextual_in, gap, gap_plain, space_after, space_before};
use super::space::ParaSpace;
use super::table::TableLayout;
use super::text::{FieldValues, Kind, Object, ShapedPara, para_id, shape, shape_space};
const EPS: f64 = 1e-6;
#[derive(Clone, Debug)]
pub struct StoryUnit {
pub top: f64,
pub height: f64,
pub below: f64,
pub after: f64,
pub content: Rc<[Child]>,
pub block: usize,
pub widow: bool,
pub notes: Box<[(NoteKind, i64)]>,
}
#[derive(Clone, Debug, Default)]
pub struct StoryLayout {
pub units: Vec<StoryUnit>,
pub height: f64,
}
impl PartialEq for StoryLayout {
fn eq(&self, o: &Self) -> bool {
self.height == o.height
&& self.units.len() == o.units.len()
&& self
.units
.iter()
.zip(&o.units)
.all(|(a, b)| a.top == b.top && a.height == b.height && a.below == b.below && a.block == b.block && a.notes == b.notes)
}
}
#[derive(Clone, Copy)]
pub struct StoryEnv<'a> {
pub ctx: LayoutCtx<'a>,
pub fields: &'a FieldValues,
pub notes: &'a HashMap<(NoteKind, i64), String>,
}
#[derive(Clone, Debug)]
pub enum ChildId {
Para(Rc<ShapedPara>),
Table(Rc<TableLayout>),
}
impl PartialEq for ChildId {
fn eq(&self, o: &Self) -> bool {
match (self, o) {
(ChildId::Para(a), ChildId::Para(b)) => Rc::ptr_eq(a, b),
(ChildId::Table(a), ChildId::Table(b)) => Rc::ptr_eq(a, b),
_ => false,
}
}
}
pub fn child_ids(blocks: &[&Block], width: Option<f64>, env: &StoryEnv, memos: &mut Memos) -> Result<Vec<ChildId>, Yield> {
let mut out = Vec::with_capacity(blocks.len());
for b in blocks {
out.push(match b {
Block::Para(p) => ChildId::Para(shape(p, &shape_space(p, env.fields, env.notes, &env.ctx.env.text), env.ctx.fonts, memos)?),
Block::Table(t) => ChildId::Table(match width {
Some(w) => super::table::layout(t, w, env, false, memos)?,
None => super::table::layout(t, 1e9, env, true, memos)?,
}),
});
}
Ok(out)
}
#[derive(Clone, Debug, PartialEq)]
pub struct StoryKey {
children: Vec<ChildId>,
width: u64,
in_cell: bool,
text: TextEnv,
fonts: u64,
}
impl StoryKey {
pub fn new(children: Vec<ChildId>, width: Option<f64>, in_cell: bool, env: &StoryEnv) -> StoryKey {
StoryKey { children, width: width.map_or(0, f64::to_bits), in_cell, text: env.ctx.env.text.clone(), fonts: env.ctx.fonts.revision() }
}
}
impl CacheKey for StoryKey {
type Probe<'a> = StoryKey;
fn bucket(p: &StoryKey) -> u64 {
use std::hash::{Hash, Hasher};
let mut h = super::cache::KeyHasher::default();
(p.width, p.in_cell).hash(&mut h);
for c in &p.children {
match c {
ChildId::Para(s) => Rc::as_ptr(s).hash(&mut h),
ChildId::Table(t) => Rc::as_ptr(t).hash(&mut h),
}
}
h.finish()
}
fn matches(&self, p: &StoryKey) -> bool {
self == p
}
fn from_probe(p: &StoryKey) -> StoryKey {
p.clone()
}
}
fn story_bytes(s: &StoryLayout) -> usize {
std::mem::size_of::<StoryLayout>() + s.units.len() * (std::mem::size_of::<StoryUnit>() + 32)
}
pub fn layout(blocks: &[&Block], width: f64, env: &StoryEnv, in_cell: bool, memos: &mut Memos) -> Result<Rc<StoryLayout>, Yield> {
let children = child_ids(blocks, Some(width), env, memos)?;
let probe = StoryKey::new(children.clone(), Some(width), in_cell, env);
cached(memos, |c| &mut c.stories, probe, story_bytes, |m| layout_with(blocks, &children, width, env, in_cell, m))
}
pub fn layout_with(
blocks: &[&Block],
resolved: &[ChildId],
width: f64,
env: &StoryEnv,
in_cell: bool,
memos: &mut Memos,
) -> Result<StoryLayout, Yield> {
let text = &env.ctx.env.text;
let mut units = Vec::new();
let mut y = 0.0;
let mut prev: Option<&ParaStyle> = None;
let mut prev_after = 0.0;
let contextual = contextual_in(text, in_cell);
for (bi, b) in blocks.iter().enumerate() {
match b {
Block::Para(p) => {
let next = match blocks.get(bi + 1) {
Some(Block::Para(n)) => Some(&*n.style),
_ => None,
};
let shaped = match resolved.get(bi) {
Some(ChildId::Para(s)) => s.clone(),
_ => shape(p, &shape_space(p, env.fields, env.notes, &env.ctx.env.text), env.ctx.fonts, memos)?,
};
let ps = ParaSpace { width, start: 0, borders: neighbour_borders(&p.style, prev, next), x0: 0.0, env: text.clone() };
let lines = break_lines(&shaped, p, &ps, env.ctx.fonts, memos)?;
let (before, after) = (space_before(&p.style), space_after(&p.style));
let g = match prev {
None => before,
Some(_) if !contextual => gap_plain(text, prev_after, before),
Some(ps) => gap(text, ps, prev_after, &p.style, before),
};
y += g;
let id = para_id(p);
let para_top = y;
for (i, line) in lines.lines.iter().enumerate() {
let placed = PlacedLine { x: 0.0, y: 0.0, line: lines.fragment(i, p, env.ctx.fonts) };
let mut content = vec![Child::Line { para: Some(id), placed }];
if in_cell {
let fr = super::float::Frames { page_w: width, page_h: 0.0, margin: [0.0; 4], column: (0.0, width) };
for k in line.start..line.end.max(line.start + 1) {
if lines.pieces().get(k).is_none_or(|q| q.kind != Kind::Anchor) {
continue;
}
let Some(Object::Float(f)) =
lines.shaped.object(lines.from + k).filter(|o| matches!(o, Object::Float(f) if f.wrap == "none"))
else {
continue;
};
let h = float_height(f, env, memos)?;
let (fx, fy) = (super::float::h_pos(f, &fr), super::float::v_pos(f, h, &fr, para_top, y) - y);
content.push(float_box(f, [fx, fy, fx + f.w, fy + h], id, env, memos)?);
}
}
units.push(StoryUnit {
top: y,
height: line.pitch,
below: line.below,
after: if i + 1 == lines.lines.len() { after } else { 0.0 },
content: content.into(),
block: bi,
widow: p.style.widow_control,
notes: lines.notes(i).into_boxed_slice(),
});
y += line.pitch;
}
prev = Some(&p.style);
prev_after = after;
}
Block::Table(t) => {
y += prev_after; let (rows, h) = match resolved.get(bi) {
Some(ChildId::Table(tb)) => super::table::table_units(tb),
_ => super::table::units(t, width, env, memos)?,
};
let shift = if in_cell && text.compat <= 14 { super::table::nested_shift(t) } else { 0.0 };
for mut u in rows {
u.block = bi;
u.top += y;
if shift != 0.0 {
u.content = Rc::from([Child::Group { offset: (shift, 0.0), children: u.content }]);
}
units.push(u);
}
y += h;
prev = None;
prev_after = 0.0;
}
}
}
y += prev_after;
Ok(StoryLayout { units, height: y })
}
pub fn float_height(f: &Float, env: &StoryEnv, memos: &mut Memos) -> Result<f64, Yield> {
Ok(match &f.text_box {
Some(tb) if tb.auto_fit => {
let w = (f.w - tb.insets[0] - tb.insets[2] - tb.outline).max(1.0);
let blocks: Vec<&Block> = tb.blocks.iter().collect();
let fields = FieldValues::default();
let h = layout(&blocks, w, &StoryEnv { fields: &fields, ..*env }, false, memos)?.height;
let h = h + tb.insets[1] + tb.insets[3] + tb.outline;
if f.auto_w || tb.insets[1] == 0.0 && tb.insets[3] == 0.0 && tb.outline == 0.0 { h.max(f.h) } else { h }
}
_ => f.h,
})
}
pub fn float_box(f: &Float, rect: [f64; 4], anchor: ParaId, env: &StoryEnv, memos: &mut Memos) -> Result<Child, Yield> {
let [x0, y0, x1, y1] = rect;
let mut items = Vec::new();
let mut groups = Vec::new();
if let Some(img) = f.image {
items.push(Item::Image { rect, key: ImageKey { index: Some(img), generation: 0 } });
}
if let Some(tb) = &f.text_box {
let o = tb.outline;
let w = (f.w - tb.insets[0] - tb.insets[2] - o).max(1.0);
let blocks: Vec<&Block> = tb.blocks.iter().collect();
let fields = FieldValues::default();
let story = layout(&blocks, w, &StoryEnv { fields: &fields, ..*env }, false, memos)?;
let (cx, cy) = (x0 + tb.insets[0] + o / 2.0, y0 + tb.insets[1] + o / 2.0);
let content_bottom = y1 - tb.insets[3] - o / 2.0;
for u in &story.units {
if !tb.auto_fit && cy + u.top >= content_bottom - EPS {
continue;
}
groups.push(Child::Group { offset: (cx, cy + u.top), children: u.content.clone() });
}
if o > 0.0 {
let h = o / 2.0;
let outline = [[x0 - h, y0 - h, x1 + h, y0 + h], [x0 - h, y1 - h, x1 + h, y1 + h], [x0 - h, y0, x0 + h, y1], [x1 - h, y0, x1 + h, y1]];
groups.push(Child::Items(outline.iter().map(|r| Item::Rect { rect: *r, colour: 0 }).collect()));
}
}
let mut children = Vec::new();
if !items.is_empty() {
children.push(Child::Items(items.into()));
}
children.extend(groups);
Ok(Child::Box(BoxFragment {
kind: BoxKind::Float { anchor, float: FloatId(f.id), rect },
id: None,
offset: (0.0, 0.0),
size: (x1 - x0, y1 - y0),
children: children.into(),
}))
}
pub fn hf_limit(compat: i64, page_h: f64) -> f64 {
if compat <= 14 { page_h / 2.0 } else { f64::INFINITY }
}
pub fn stack_limited(story: &StoryLayout, x0: f64, top: f64, limit: f64) -> (Vec<Child>, f64) {
let mut out = Vec::new();
let mut kept = 0.0;
let mut cut = false;
for u in &story.units {
if !cut && u.top + u.height <= limit + EPS {
out.push(Child::Group { offset: (x0, top + u.top), children: u.content.clone() });
kept = u.top + u.height;
} else {
cut = true;
}
}
(out, if cut { kept } else { story.height })
}