katana_document_viewer/export_quality/
score.rs1use crate::export_quality::html_score::HtmlQualityScore;
2use crate::export_quality::image_score::ImageQualityScore;
3use crate::export_quality::types::{
4 ExportFormatQualityScore, ExportQualityArtifacts, ExportQualityReport, TOTAL_SCORE_MAX, check,
5};
6use crate::forge::ExportFormat;
7
8pub struct ExportQualityGate;
9
10impl ExportQualityGate {
11 pub fn evaluate(artifacts: &ExportQualityArtifacts<'_>) -> ExportQualityReport {
12 let format_scores = vec![
13 Self::score_html(artifacts.html),
14 Self::score_pdf(artifacts.pdf),
15 Self::score_png(artifacts.png),
16 Self::score_jpeg(artifacts.jpeg),
17 ];
18 let fatal_failures = format_scores
19 .iter()
20 .flat_map(|score| score.fatal_failures())
21 .collect::<Vec<_>>();
22 let warnings = format_scores
23 .iter()
24 .filter(|score| !score.is_pass())
25 .map(|score| format!("{:?} quality score is {}/100", score.format, score.score))
26 .collect::<Vec<_>>();
27 let total_score = format_scores.iter().map(|score| score.score).sum();
28 ExportQualityReport {
29 total_score,
30 max_score: TOTAL_SCORE_MAX,
31 format_scores,
32 fatal_failures,
33 warnings,
34 }
35 }
36
37 fn score_html(bytes: &[u8]) -> ExportFormatQualityScore {
38 HtmlQualityScore::score(bytes)
39 }
40
41 fn score_pdf(bytes: &[u8]) -> ExportFormatQualityScore {
42 let text = String::from_utf8_lossy(bytes);
43 ExportFormatQualityScore::new(
44 ExportFormat::Pdf,
45 vec![
46 check("pdf is non-empty", !bytes.is_empty(), true, 20),
47 check("pdf has header", bytes.starts_with(b"%PDF-1.4"), true, 20),
48 check(
49 "pdf embeds page image",
50 text.contains("/Subtype /Image"),
51 true,
52 20,
53 ),
54 check(
55 "pdf has page tree",
56 text.contains("/Type /Pages") && text.contains("/Type /Page"),
57 true,
58 20,
59 ),
60 check(
61 "pdf keeps link annotations",
62 text.contains("/Subtype /Link"),
63 true,
64 20,
65 ),
66 ],
67 )
68 }
69
70 fn score_png(bytes: &[u8]) -> ExportFormatQualityScore {
71 let decoded = ImageQualityScore::decode_dimensions(bytes);
72 Self::score_image(ExportFormat::Png, bytes, decoded, b"\x89PNG\r\n\x1a\n")
73 }
74
75 fn score_jpeg(bytes: &[u8]) -> ExportFormatQualityScore {
76 let decoded = ImageQualityScore::decode_dimensions(bytes);
77 Self::score_image(ExportFormat::Jpeg, bytes, decoded, b"\xff\xd8\xff")
78 }
79
80 fn score_image(
81 format: ExportFormat,
82 bytes: &[u8],
83 decoded: Result<(u32, u32), image::ImageError>,
84 signature: &[u8],
85 ) -> ExportFormatQualityScore {
86 ImageQualityScore::score(format, bytes, decoded, signature)
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93
94 #[test]
95 fn evaluate_reports_warnings_for_failed_formats() {
96 let artifacts = ExportQualityArtifacts {
97 html: b"",
98 pdf: b"",
99 png: b"",
100 jpeg: b"",
101 };
102
103 let report = ExportQualityGate::evaluate(&artifacts);
104
105 assert!(!report.is_pass());
106 assert!(
107 report
108 .warnings
109 .iter()
110 .any(|warning| warning.contains("Html"))
111 );
112 assert!(!report.fatal_failures.is_empty());
113 }
114}