use fop_core::{tree::RetrievePosition, NodeId};
use fop_types::Length;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub(super) struct MarkerEntry {
pub(super) node_id: NodeId,
pub(super) starts_on_page: bool,
pub(super) ends_on_page: bool,
}
#[derive(Debug, Default)]
pub(super) struct MarkerMap {
pub(super) markers: HashMap<String, Vec<MarkerEntry>>,
}
impl MarkerMap {
pub(super) fn new() -> Self {
Self {
markers: HashMap::new(),
}
}
#[allow(dead_code)]
pub(super) fn add_marker(
&mut self,
class_name: String,
node_id: NodeId,
starts_on_page: bool,
ends_on_page: bool,
) {
let entry = MarkerEntry {
node_id,
starts_on_page,
ends_on_page,
};
self.markers.entry(class_name).or_default().push(entry);
}
pub(super) fn retrieve_marker(
&self,
class_name: &str,
position: RetrievePosition,
) -> Option<NodeId> {
let entries = self.markers.get(class_name)?;
match position {
RetrievePosition::FirstStartingWithinPage => {
entries.iter().find(|e| e.starts_on_page).map(|e| e.node_id)
}
RetrievePosition::FirstIncludingCarryover => {
entries.first().map(|e| e.node_id)
}
RetrievePosition::LastStartingWithinPage => {
entries
.iter()
.rev()
.find(|e| e.starts_on_page)
.map(|e| e.node_id)
}
RetrievePosition::LastEndingWithinPage => {
entries
.iter()
.rev()
.find(|e| e.ends_on_page)
.map(|e| e.node_id)
}
}
}
pub(super) fn clear(&mut self) {
self.markers.clear();
}
}
#[derive(Debug, Clone, Copy)]
pub(super) struct PageRegionGeometry {
pub page_width: Length,
pub page_height: Length,
pub before_rect: fop_types::Rect,
pub after_rect: fop_types::Rect,
pub start_rect: fop_types::Rect,
pub end_rect: fop_types::Rect,
pub body_rect: fop_types::Rect,
}
#[derive(Debug, Clone)]
pub struct MultiColumnLayout {
pub column_count: i32,
pub column_gap: Length,
pub available_width: Length,
pub column_width: Length,
pub current_column: i32,
pub column_y: Length,
pub max_column_height: Option<Length>,
}
impl MultiColumnLayout {
pub fn new(column_count: i32, column_gap: Length, available_width: Length) -> Self {
let total_gap = column_gap * (column_count - 1);
let column_width = (available_width - total_gap) / column_count;
Self {
column_count,
column_gap,
available_width,
column_width,
current_column: 0,
column_y: Length::ZERO,
max_column_height: None,
}
}
pub fn with_max_height(mut self, max_height: Length) -> Self {
self.max_column_height = Some(max_height);
self
}
pub fn current_column_x(&self) -> Length {
(self.column_width + self.column_gap) * self.current_column
}
pub fn is_column_filled(&self, content_height: Length) -> bool {
if let Some(max_height) = self.max_column_height {
self.column_y + content_height > max_height
} else {
false
}
}
pub fn next_column(&mut self) -> bool {
if self.current_column + 1 < self.column_count {
self.current_column += 1;
self.column_y = Length::ZERO;
true
} else {
false
}
}
pub fn allocate(&mut self, height: Length) -> (Length, Length) {
let x = self.current_column_x();
let y = self.column_y;
self.column_y += height;
(x, y)
}
pub fn reset(&mut self) {
self.current_column = 0;
self.column_y = Length::ZERO;
}
pub fn column_count(&self) -> i32 {
self.column_count
}
pub fn column_width(&self) -> Length {
self.column_width
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FloatSide {
Left,
Right,
Start,
End,
Inside,
Outside,
None,
}
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ClearSide {
Left,
Right,
Both,
Start,
End,
None,
}
#[derive(Debug, Clone)]
pub(super) struct FloatInfo {
#[allow(dead_code)]
pub(super) area_id: crate::area::AreaId,
pub(super) side: FloatSide,
pub(super) top: Length,
pub(super) bottom: Length,
pub(super) width: Length,
}
#[derive(Debug, Default)]
pub(super) struct FloatManager {
pub(super) left_floats: Vec<FloatInfo>,
pub(super) right_floats: Vec<FloatInfo>,
}
impl FloatManager {
pub(super) fn new() -> Self {
Self {
left_floats: Vec::new(),
right_floats: Vec::new(),
}
}
pub(super) fn add_float(&mut self, float: FloatInfo, is_odd_page: bool) {
match float.side {
FloatSide::Left | FloatSide::Start => {
self.left_floats.push(float);
}
FloatSide::Right | FloatSide::End => {
self.right_floats.push(float);
}
FloatSide::Inside => {
if is_odd_page {
self.left_floats.push(float);
} else {
self.right_floats.push(float);
}
}
FloatSide::Outside => {
if is_odd_page {
self.right_floats.push(float);
} else {
self.left_floats.push(float);
}
}
FloatSide::None => {}
}
}
pub(super) fn available_width(&self, y: Length, container_width: Length) -> (Length, Length) {
let left_offset = self.get_left_offset(y);
let right_offset = self.get_right_offset(y);
let available = container_width - left_offset - right_offset;
(left_offset, available)
}
pub(super) fn get_left_offset(&self, y: Length) -> Length {
self.left_floats
.iter()
.filter(|f| f.top <= y && y < f.bottom)
.map(|f| f.width)
.fold(Length::ZERO, |acc, w| acc + w)
}
pub(super) fn get_right_offset(&self, y: Length) -> Length {
self.right_floats
.iter()
.filter(|f| f.top <= y && y < f.bottom)
.map(|f| f.width)
.fold(Length::ZERO, |acc, w| acc + w)
}
#[allow(dead_code)]
pub(super) fn get_clear_position(&self, clear: ClearSide, current_y: Length) -> Length {
match clear {
ClearSide::Left | ClearSide::Start => self
.left_floats
.iter()
.filter(|f| f.bottom > current_y)
.map(|f| f.bottom)
.max()
.unwrap_or(current_y),
ClearSide::Right | ClearSide::End => self
.right_floats
.iter()
.filter(|f| f.bottom > current_y)
.map(|f| f.bottom)
.max()
.unwrap_or(current_y),
ClearSide::Both => {
let left_bottom = self
.left_floats
.iter()
.filter(|f| f.bottom > current_y)
.map(|f| f.bottom)
.max()
.unwrap_or(current_y);
let right_bottom = self
.right_floats
.iter()
.filter(|f| f.bottom > current_y)
.map(|f| f.bottom)
.max()
.unwrap_or(current_y);
left_bottom.max(right_bottom)
}
ClearSide::None => current_y,
}
}
pub(super) fn remove_floats_above(&mut self, y: Length) {
self.left_floats.retain(|f| f.bottom > y);
self.right_floats.retain(|f| f.bottom > y);
}
pub(super) fn clear(&mut self) {
self.left_floats.clear();
self.right_floats.clear();
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub(super) struct PageContext {
pub(super) page_number: usize,
pub(super) total_pages: Option<usize>,
pub(super) is_first: bool,
pub(super) is_last: bool,
}
impl PageContext {
#[allow(dead_code)]
pub(super) fn new() -> Self {
Self {
page_number: 1,
total_pages: None,
is_first: true,
is_last: false,
}
}
#[allow(dead_code)]
pub(super) fn is_odd_page(&self) -> bool {
self.page_number % 2 == 1
}
#[allow(dead_code)]
pub(super) fn is_even_page(&self) -> bool {
self.page_number.is_multiple_of(2)
}
#[allow(dead_code)]
pub(super) fn is_first_page(&self) -> bool {
self.is_first
}
#[allow(dead_code)]
pub(super) fn is_last_page(&self) -> bool {
self.is_last
}
}
#[allow(dead_code)]
pub(super) fn parse_fo_length(s: &str) -> Option<Length> {
if let Some(v) = s.strip_suffix("pt") {
v.parse::<f64>().ok().map(Length::from_pt)
} else if let Some(v) = s.strip_suffix("mm") {
v.parse::<f64>().ok().map(Length::from_mm)
} else if let Some(v) = s.strip_suffix("cm") {
v.parse::<f64>().ok().map(Length::from_cm)
} else if let Some(v) = s.strip_suffix("in") {
v.parse::<f64>().ok().map(Length::from_inch)
} else if let Some(v) = s.strip_suffix("px") {
v.parse::<f64>().ok().map(|px| Length::from_pt(px * 0.75))
} else {
None
}
}