use crate::*;
pub struct CenterInPreferredHeight<E: Element>(pub E);
impl<E: Element> Element for CenterInPreferredHeight<E> {
fn first_location_usage(&self, ctx: FirstLocationUsageCtx) -> FirstLocationUsage {
let layout = self.layout(
ctx.text_pieces_cache,
ctx.width,
ctx.first_height,
Some(ctx.full_height),
);
if layout.pre_break {
FirstLocationUsage::WillSkip
} else if layout.size.height.is_some() {
FirstLocationUsage::WillUse
} else {
FirstLocationUsage::NoneHeight
}
}
fn measure(&self, mut ctx: MeasureCtx) -> ElementSize {
let layout = self.layout(
ctx.text_pieces_cache,
ctx.width,
ctx.first_height,
ctx.breakable.as_ref().map(|b| b.full_height),
);
if layout.pre_break {
let breakable = ctx.breakable.as_mut().unwrap();
*breakable.break_count = 1;
}
layout.size
}
fn draw(&self, ctx: DrawCtx) -> ElementSize {
let layout = self.layout(
ctx.text_pieces_cache,
ctx.width,
ctx.first_height,
ctx.breakable.as_ref().map(|b| b.full_height),
);
let height_available;
let mut location;
let center_height;
if layout.size.height.is_none() {
return layout.size;
} else if layout.pre_break {
let breakable = ctx.breakable.unwrap();
location = (breakable.do_break)(ctx.pdf, 0, None);
height_available = breakable.full_height;
center_height = if breakable.preferred_height_break_count == 1 {
ctx.preferred_height.unwrap_or(0.)
} else {
breakable.full_height
};
} else {
location = ctx.location;
height_available = ctx.first_height;
center_height = if ctx
.breakable
.map(|b| b.preferred_height_break_count == 0)
.unwrap_or(true)
{
ctx.preferred_height.unwrap_or(0.)
} else {
ctx.first_height
};
}
let y_offset = if let Some(height) = layout.size.height {
(center_height - height).max(0.) / 2.
} else {
0.
};
location.pos.1 -= y_offset;
self.0.draw(DrawCtx {
pdf: ctx.pdf,
text_pieces_cache: ctx.text_pieces_cache,
location,
width: ctx.width,
first_height: height_available,
preferred_height: None,
breakable: None,
});
ElementSize {
width: layout.size.width,
height: Some(center_height),
}
}
}
#[derive(Debug)]
struct Layout {
pre_break: bool,
size: ElementSize,
}
impl<E: Element> CenterInPreferredHeight<E> {
fn layout(
&self,
text_pieces_cache: &TextPiecesCache,
width: WidthConstraint,
first_height: f32,
full_height: Option<f32>,
) -> Layout {
let height_available = full_height.unwrap_or(first_height);
let size = self.0.measure(MeasureCtx {
text_pieces_cache,
width,
first_height: height_available,
breakable: None,
});
let pre_break;
if let (Some(height), Some(full_height)) = (size.height, full_height) {
pre_break = height > first_height && full_height > first_height;
} else {
pre_break = false;
};
Layout { pre_break, size }
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
elements::ref_element::RefElement,
test_utils::{record_passes::RecordPasses, *},
};
use insta::*;
#[test]
fn test_unbreakable() {
let output = test_element(
TestElementParams {
width: WidthConstraint {
max: 12.,
expand: true,
},
first_height: 21.,
preferred_height: Some(20.),
breakable: None,
pos: (11., 29.0),
..Default::default()
},
|assert, callback| {
let content = RecordPasses::new(FakeText {
lines: 3,
line_height: 5.,
width: 3.,
});
let element = CenterInPreferredHeight(RefElement(&content));
let ret = callback.call(element);
if assert {
assert_debug_snapshot!(content.into_passes());
}
ret
},
);
assert_debug_snapshot!(output);
}
#[test]
fn test_breakable() {
let output = test_element(
TestElementParams {
width: WidthConstraint {
max: 12.,
expand: true,
},
first_height: 21.,
preferred_height: Some(19.),
breakable: Some(TestElementParamsBreakable {
full_height: 25.,
preferred_height_break_count: 0,
..Default::default()
}),
pos: (11., 29.0),
..Default::default()
},
|assert, callback| {
let content = RecordPasses::new(FakeText {
lines: 3,
line_height: 5.,
width: 3.,
});
let element = CenterInPreferredHeight(RefElement(&content));
let ret = callback.call(element);
if assert {
assert_debug_snapshot!(content.into_passes());
}
ret
},
);
assert_debug_snapshot!(output);
}
#[test]
fn test_pre_break() {
let output = test_element(
TestElementParams {
width: WidthConstraint {
max: 12.,
expand: true,
},
first_height: 21.,
preferred_height: Some(25.),
breakable: Some(TestElementParamsBreakable {
full_height: 26.,
preferred_height_break_count: 1,
..Default::default()
}),
pos: (11., 29.0),
..Default::default()
},
|assert, callback| {
let content = RecordPasses::new(FakeText {
lines: 5,
line_height: 5.,
width: 3.,
});
let element = CenterInPreferredHeight(RefElement(&content));
let ret = callback.call(element);
if assert {
assert_debug_snapshot!(content.into_passes());
}
ret
},
);
assert_debug_snapshot!(output);
}
#[test]
fn test_preferred_height() {
let output = test_element(
TestElementParams {
width: WidthConstraint {
max: 12.,
expand: true,
},
first_height: 21.,
breakable: Some(TestElementParamsBreakable {
full_height: 26.,
preferred_height_break_count: 4,
}),
pos: (11., 29.0),
preferred_height: None,
..Default::default()
},
|assert, callback| {
let content = RecordPasses::new(FakeText {
lines: 5,
line_height: 5.,
width: 3.,
});
let element = CenterInPreferredHeight(RefElement(&content));
let ret = callback.call(element);
if assert {
assert_debug_snapshot!(content.into_passes());
}
ret
},
);
assert_debug_snapshot!(output);
}
}