c2pdf/
helpers.rs

1//! Miscellaneous helper functions
2
3use std::{path::Path, str::Lines};
4
5use printpdf::{FontId, Mm, Op, Point, Pt, TextItem, TextMatrix, TextRenderingMode};
6
7use crate::{dimensions::Dimensions, text_manipulation::TextWrapper};
8/// Processed additional text.
9///
10/// Pretty much just serves to cache the maximum line width within the text so it doesn't have to be recalculated
11#[derive(Clone)]
12pub struct ProcessedText {
13  text: String,
14  width: f32,
15}
16impl ProcessedText {
17  /// Creates a new instance of [`ProcessedText`]
18  pub fn new(text: String, wrapper: &mut TextWrapper) -> Option<Self> {
19    let width = text
20      .lines()
21      .map(|line| wrapper.get_width(line).0)
22      .reduce(f32::max)?;
23    Some(Self { text, width })
24  }
25  fn lines(&self) -> Lines {
26    self.text.lines()
27  }
28}
29/// Generates a new page with basic contents
30pub fn init_page(
31  contents: &mut Vec<Op>,
32  page_dimensions: &Dimensions,
33  font_id: FontId,
34  font_size: f32,
35  path: &Path,
36  additional_text: Option<&ProcessedText>,
37  include_path: bool,
38  wrapper: &mut TextWrapper,
39) {
40  contents.extend_from_slice(&[
41    Op::StartTextSection,
42    Op::SetLineHeight {
43      lh: Pt(font_size * 1.2),
44    },
45    Op::SetFontSize {
46      size: Pt(font_size),
47      font: font_id.clone(),
48    },
49  ]);
50  // Write additional text
51  if let Some(text) = additional_text {
52    contents.extend_from_slice(&[
53      Op::SetTextMatrix {
54        matrix: TextMatrix::Translate(Pt(0.0), Pt(0.0)),
55      },
56      Op::SetTextCursor {
57        pos: Point {
58          x: (page_dimensions.width - page_dimensions.margin_right).into_pt() - Pt(text.width),
59          y: (page_dimensions.height - Mm(7.5)).into(),
60        },
61      },
62    ]);
63    for line in text.lines() {
64      contents.push(Op::WriteText {
65        items: vec![TextItem::Text(line.to_string())],
66        font: font_id.clone(),
67      });
68      contents.push(Op::AddLineBreak);
69    }
70  }
71  if include_path {
72    contents.extend_from_slice(&[
73      Op::SetTextMatrix {
74        matrix: TextMatrix::Translate(Pt(0.0), Pt(0.0)),
75      },
76      // Write metadata
77      Op::SetTextCursor {
78        pos: Point {
79          x: page_dimensions.margin_left.into(),
80          y: (page_dimensions.height - Mm(7.5)).into(),
81        },
82      },
83    ]);
84    let max_path_width = (page_dimensions.max_text_width()
85      - if let Some(text) = &additional_text {
86        Mm::from(Pt(text.width)) + Mm(5.0)
87      } else {
88        Mm(0.0)
89      })
90    .into_pt();
91    for (line, _) in wrapper.split_into_lines(&path.display().to_string(), |_| max_path_width) {
92      contents.push(Op::WriteText {
93        items: vec![TextItem::Text(line)],
94        font: font_id.clone(),
95      });
96      contents.push(Op::AddLineBreak);
97    }
98  }
99
100  // Set cursor to main body
101  contents.extend_from_slice(&[
102    // This allows me to reset the text cursor for some reason
103    Op::SetTextMatrix {
104      matrix: TextMatrix::Translate(Pt(0.0), Pt(0.0)),
105    },
106    Op::SetTextCursor {
107      pos: Point {
108        x: page_dimensions.margin_left.into(),
109        y: (page_dimensions.height - page_dimensions.margin_top).into(),
110      },
111    },
112    Op::SetTextRenderingMode {
113      mode: TextRenderingMode::Fill,
114    },
115  ]);
116}