c2pdf/
lib.rs

1#![warn(missing_docs)]
2
3//! # Code To PDF
4//!
5//! This crate provides primitives for generating PDFs containing syntax-highlighted code
6//!
7//! [`code_to_pdf::CodeToPdf`] is the main struct for this so is likely the best place to start
8
9use std::{
10  cmp::Ordering,
11  num::NonZeroU8,
12  path::PathBuf,
13  sync::{Arc, Mutex},
14};
15
16use code_to_pdf::{CodeToPdf, DocumentSubset, HighlighterConfig};
17use dimensions::Dimensions;
18use helpers::ProcessedText;
19use ignore::{WalkBuilder, overrides::OverrideBuilder};
20use log::{error, trace};
21use printpdf::FontId;
22use rayon::iter::{ParallelBridge, ParallelIterator};
23use text_manipulation::TextWrapper;
24use thread_local::ThreadLocal;
25
26pub mod code_to_pdf;
27pub mod dimensions;
28pub mod font_loader;
29pub mod helpers;
30pub mod logging;
31pub mod text_manipulation;
32
33pub use printpdf::{ParsedFont, PdfDocument, PdfSaveOptions};
34
35// Do this here, until I find a good name for a module to plate it in :)
36// Maybe `easy`, like what `syntect` has
37impl CodeToPdf {
38  /// Helper function that handles everything for the basic use-case
39  pub fn run_parallel(
40    font_id: FontId,
41    font_bytes: &[u8],
42    path: PathBuf,
43    exclusions: Vec<String>,
44    page_dimensions: Dimensions,
45    font_size: f32,
46    page_text: Option<String>,
47    include_path: bool,
48    threads: Option<NonZeroU8>,
49  ) -> (Arc<Mutex<DocumentSubset>>, usize) {
50    let doc_subset = DocumentSubset::default();
51    let ss = two_face::syntax::extra_newlines();
52    let ts = two_face::theme::extra();
53    let walker = WalkBuilder::new(path.clone())
54      .overrides({
55        let mut builder = OverrideBuilder::new(path);
56        for exclusion in exclusions.clone() {
57          builder.add(&("!".to_string() + &exclusion)).unwrap();
58        }
59        builder.build().unwrap()
60      })
61      // Ensure that files are given higher precidence than folders
62      // (want files in a folder to be printed breadth-first)
63      .sort_by_file_path(|x, y| {
64        {
65          if x.is_dir() && !y.is_dir() {
66            Ordering::Less
67          } else if y.is_dir() && !x.is_dir() {
68            Ordering::Greater
69          } else {
70            Ordering::Equal
71          }
72        }
73        .reverse()
74      })
75      .build();
76
77    let local_c2pdf = ThreadLocal::<Arc<Mutex<CodeToPdf>>>::new();
78    let local_highlighter_config = ThreadLocal::<Arc<Mutex<HighlighterConfig>>>::new();
79
80    let doc_subset = Arc::new(Mutex::new(doc_subset));
81    if let Some(threads) = threads {
82      // Build the global threadpool with the correct number of threads
83      rayon::ThreadPoolBuilder::new()
84        .num_threads(u8::from(threads) as usize)
85        .build_global()
86        .unwrap();
87    }
88    let mut wrapper = TextWrapper::new(font_bytes, font_size);
89    let additional_text = page_text.and_then(|text| ProcessedText::new(text, &mut wrapper));
90    walker.enumerate().par_bridge().for_each(|(i, result)| {
91      // let mut doc = PdfDocument::new(&args.name);
92      let c2pdf_mutex = local_c2pdf.get_or(|| {
93        Arc::new(Mutex::new(CodeToPdf::new(
94          doc_subset.clone(),
95          font_id.clone(),
96          page_dimensions.clone(),
97          wrapper.clone(),
98          additional_text.clone(),
99          include_path,
100        )))
101      });
102      let highlight_config_mutex = local_highlighter_config.get_or(|| {
103        Arc::new(Mutex::new(HighlighterConfig::new(
104          ss.clone(),
105          ts.get(two_face::theme::EmbeddedThemeName::InspiredGithub)
106            .clone(),
107        )))
108      });
109      match result {
110        Ok(entry) => {
111          if entry.file_type().is_some_and(|f| f.is_file()) {
112            let path = entry.path();
113            trace!("Generating pages for {}, index {i}", path.display());
114            if let Err(err) = c2pdf_mutex.lock().unwrap().process_file(
115              path,
116              &highlight_config_mutex.lock().unwrap(),
117              i,
118            ) {
119              error!("ERROR: {}", err);
120            }
121          }
122        }
123        Err(err) => {
124          error!("ERROR: {}", err);
125        }
126      }
127    });
128    let mut processed_file_count = 0;
129    for local in local_c2pdf.iter() {
130      processed_file_count += local.lock().unwrap().processed_file_count();
131    }
132
133    // doc_subset.lock().unwrap().to_document(doc);
134    (doc_subset, processed_file_count)
135  }
136}