use crate::render::dimension::Pt;
use crate::render::geometry::PtRect;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FloatSource {
Image,
Shape,
Table {
owner_block_idx: usize,
},
}
#[derive(Debug, Clone)]
pub struct ActiveFloat {
pub page_x: Pt,
pub page_y_start: Pt,
pub page_y_end: Pt,
pub width: Pt,
pub source: FloatSource,
pub wrap_text: WrapTextSide,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WrapTextSide {
BothSides,
Left,
Right,
Largest,
}
impl From<crate::model::WrapText> for WrapTextSide {
fn from(w: crate::model::WrapText) -> Self {
match w {
crate::model::WrapText::BothSides => Self::BothSides,
crate::model::WrapText::Left => Self::Left,
crate::model::WrapText::Right => Self::Right,
crate::model::WrapText::Largest => Self::Largest,
}
}
}
impl ActiveFloat {
pub fn overlaps_y(&self, y: Pt) -> bool {
y >= self.page_y_start && y < self.page_y_end
}
pub fn rect(&self) -> PtRect {
PtRect::from_xywh(
self.page_x,
self.page_y_start,
self.width,
self.page_y_end - self.page_y_start,
)
}
}
pub fn float_adjustments(
floats: &[ActiveFloat],
line_y: Pt,
page_x: Pt,
content_width: Pt,
) -> (Pt, Pt) {
float_adjustments_with_height(floats, line_y, Pt::ZERO, page_x, content_width)
}
pub fn float_adjustments_with_height(
floats: &[ActiveFloat],
line_y: Pt,
line_height: Pt,
page_x: Pt,
content_width: Pt,
) -> (Pt, Pt) {
let mut indent_left = Pt::ZERO;
let mut indent_right = Pt::ZERO;
let line_bottom = line_y + line_height;
for float in floats {
if line_bottom <= float.page_y_start || line_y >= float.page_y_end {
continue;
}
let float_right_edge = float.page_x + float.width;
let content_right = page_x + content_width;
let left_shift = (float_right_edge - page_x).max(Pt::ZERO);
let right_shift = (content_right - float.page_x).max(Pt::ZERO);
let narrow_left = match float.wrap_text {
WrapTextSide::Right => true,
WrapTextSide::Left => false,
WrapTextSide::Largest => {
let left_remaining = float.page_x - page_x;
let right_remaining = content_right - float_right_edge;
left_remaining < right_remaining
}
WrapTextSide::BothSides => {
let content_center = page_x + content_width * 0.5;
let float_center = float.page_x + float.width * 0.5;
float_center < content_center
}
};
if narrow_left {
if left_shift > indent_left {
indent_left = left_shift;
}
} else if right_shift > indent_right {
indent_right = right_shift;
}
}
(indent_left, indent_right)
}
pub fn prune_floats(floats: &mut Vec<ActiveFloat>, cursor_y: Pt) {
floats.retain(|f| cursor_y < f.page_y_end);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn no_floats_no_adjustment() {
let (l, r) = float_adjustments(&[], Pt::new(100.0), Pt::new(72.0), Pt::new(468.0));
assert_eq!(l.raw(), 0.0);
assert_eq!(r.raw(), 0.0);
}
#[test]
fn float_on_left_pushes_text_right() {
let floats = vec![ActiveFloat {
page_x: Pt::new(72.0), page_y_start: Pt::new(80.0),
page_y_end: Pt::new(200.0),
width: Pt::new(100.0),
source: FloatSource::Image,
wrap_text: WrapTextSide::BothSides,
}];
let (l, r) = float_adjustments(&floats, Pt::new(100.0), Pt::new(72.0), Pt::new(468.0));
assert_eq!(l.raw(), 100.0, "push right by float width");
assert_eq!(r.raw(), 0.0);
}
#[test]
fn float_on_right_pushes_text_left() {
let floats = vec![ActiveFloat {
page_x: Pt::new(440.0), page_y_start: Pt::new(80.0),
page_y_end: Pt::new(200.0),
width: Pt::new(100.0),
source: FloatSource::Image,
wrap_text: WrapTextSide::BothSides,
}];
let (l, r) = float_adjustments(&floats, Pt::new(100.0), Pt::new(72.0), Pt::new(468.0));
assert_eq!(l.raw(), 0.0);
assert!(r.raw() > 0.0, "should indent from right");
}
#[test]
fn float_not_overlapping_line_no_adjustment() {
let floats = vec![ActiveFloat {
page_x: Pt::new(72.0),
page_y_start: Pt::new(200.0),
page_y_end: Pt::new(300.0),
width: Pt::new(100.0),
source: FloatSource::Image,
wrap_text: WrapTextSide::BothSides,
}];
let (l, r) = float_adjustments(&floats, Pt::new(100.0), Pt::new(72.0), Pt::new(468.0));
assert_eq!(l.raw(), 0.0, "line is above float");
assert_eq!(r.raw(), 0.0);
}
#[test]
fn prune_removes_passed_floats() {
let mut floats = vec![
ActiveFloat {
page_x: Pt::ZERO,
page_y_start: Pt::new(0.0),
page_y_end: Pt::new(100.0),
width: Pt::new(50.0),
source: FloatSource::Image,
wrap_text: WrapTextSide::BothSides,
},
ActiveFloat {
page_x: Pt::ZERO,
page_y_start: Pt::new(0.0),
page_y_end: Pt::new(300.0),
width: Pt::new(50.0),
source: FloatSource::Image,
wrap_text: WrapTextSide::BothSides,
},
];
prune_floats(&mut floats, Pt::new(150.0));
assert_eq!(floats.len(), 1, "first float pruned, second still active");
}
#[test]
fn wrap_text_right_forces_narrow_from_left_even_if_float_is_right() {
let floats = vec![ActiveFloat {
page_x: Pt::new(440.0),
page_y_start: Pt::new(80.0),
page_y_end: Pt::new(200.0),
width: Pt::new(100.0),
source: FloatSource::Image,
wrap_text: WrapTextSide::Right,
}];
let (l, r) = float_adjustments(&floats, Pt::new(100.0), Pt::new(72.0), Pt::new(468.0));
assert!(
l.raw() > 0.0,
"text on right of float means narrow from left"
);
assert_eq!(r.raw(), 0.0);
}
#[test]
fn wrap_text_left_forces_narrow_from_right_even_if_float_is_left() {
let floats = vec![ActiveFloat {
page_x: Pt::new(72.0),
page_y_start: Pt::new(80.0),
page_y_end: Pt::new(200.0),
width: Pt::new(100.0),
source: FloatSource::Image,
wrap_text: WrapTextSide::Left,
}];
let (l, r) = float_adjustments(&floats, Pt::new(100.0), Pt::new(72.0), Pt::new(468.0));
assert_eq!(l.raw(), 0.0);
assert!(
r.raw() > 0.0,
"text on left of float means narrow from right"
);
}
#[test]
fn wrap_text_largest_picks_wider_remaining_side() {
let floats = vec![ActiveFloat {
page_x: Pt::new(200.0),
page_y_start: Pt::new(80.0),
page_y_end: Pt::new(200.0),
width: Pt::new(50.0),
source: FloatSource::Image,
wrap_text: WrapTextSide::Largest,
}];
let (l, r) = float_adjustments(&floats, Pt::new(100.0), Pt::new(72.0), Pt::new(468.0));
assert!(l.raw() > 0.0);
assert_eq!(r.raw(), 0.0);
}
#[test]
fn overlaps_y_boundary() {
let f = ActiveFloat {
page_x: Pt::ZERO,
page_y_start: Pt::new(100.0),
page_y_end: Pt::new(200.0),
width: Pt::new(50.0),
source: FloatSource::Image,
wrap_text: WrapTextSide::BothSides,
};
assert!(!f.overlaps_y(Pt::new(99.0)));
assert!(f.overlaps_y(Pt::new(100.0)));
assert!(f.overlaps_y(Pt::new(150.0)));
assert!(!f.overlaps_y(Pt::new(200.0)));
}
}