1#![warn(missing_docs)]
2
3use 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
35impl CodeToPdf {
38 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 .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 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 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, processed_file_count)
135 }
136}