use super::observe::{Observer, Progress, refused};
use super::{Detection, InputFormat, detect_source, pdf_range};
use crate::caj::convert_caj;
use crate::hnc8::{
ApplicationInfoStatus, C8FontSource, C8FontSources, C8PageFonts, ComposeOptions, ComposePage,
ComposeVisitor, OutlineReport, convert_document_pdf, uses_native_text,
};
use crate::jbig2::text::TextHeaderPolicy;
use crate::kdh::convert_kdh;
use crate::pdf::copy_pdf_range;
use crate::qm::QmTable;
use crate::{Error, ErrorKind, Limits, RangedSource, Result};
use std::io::Write;
pub const FONTS_REQUIRE_HNC8: &str =
"explicit native font resources require converting a C8 or HN-B document";
pub struct Fonts<'a> {
pub sources: Vec<C8FontSource<Box<dyn RangedSource + 'a>>>,
pub roles: Option<C8PageFonts>,
}
pub struct ConversionOptions<'a> {
pub format: Option<InputFormat>,
pub include_bookmarks: bool,
pub allow_damaged: bool,
pub text_header_policy: TextHeaderPolicy,
pub fonts: Option<Fonts<'a>>,
}
impl Default for ConversionOptions<'_> {
fn default() -> Self {
Self {
format: None,
include_bookmarks: true,
allow_damaged: false,
text_header_policy: TextHeaderPolicy::HnC8UnusedRefinementTemplate,
fonts: None,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct OmittedPage {
pub page_index: u32,
pub offset: u64,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct ImageCounts {
pub type0: u64,
pub jpeg: u64,
pub type3: u64,
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct ConversionReport {
pub input_bytes_read: u64,
pub output_bytes_written: u64,
pub pages_converted: u32,
pub substituted_glyphs: u64,
pub bookmarks_written: u32,
pub omitted_pages: Vec<OmittedPage>,
pub outline: OutlineReport,
pub application_info: ApplicationInfoStatus,
pub images: ImageCounts,
}
pub fn convert<S: RangedSource, W: Write>(
source: &mut S,
sink: &mut W,
options: ConversionOptions<'_>,
limits: &Limits,
progress: &mut dyn Progress,
) -> Result<ConversionReport> {
let observer = Observer::new(progress);
let mut source = observer.track(source);
let Detection {
format,
header_offset,
bytes_read,
} = observer.resolve(&mut source, options.format, limits)?;
if options.fonts.is_some() && !matches!(format, InputFormat::Hn | InputFormat::C8) {
return Err(Error::invalid(FONTS_REQUIRE_HNC8));
}
let mut report = match format {
InputFormat::Pdf => {
let range = pdf_range(source.size(), header_offset);
copy_pdf_range(&mut source, sink, range, limits, &observer)?
}
InputFormat::Caj => convert_caj(&mut source, sink, &options, limits, &observer)?,
InputFormat::Kdh => convert_kdh(&mut source, sink, limits, &observer)?,
InputFormat::Hn | InputFormat::C8 => {
convert_hnc8(&mut source, sink, options, limits, &observer)?
}
InputFormat::Nh | InputFormat::Teb => return Err(refused()),
};
report.input_bytes_read = report.input_bytes_read.saturating_add(bytes_read);
Ok(report)
}
struct CompletePages;
impl ComposeVisitor for CompletePages {
fn page(&mut self, page: ComposePage<'_>) -> Result<()> {
if page.output_page.is_none() {
return Err(Error::from(ErrorKind::UnsupportedFormat)
.because("HN/C8 conversion cannot omit source pages without image content"));
}
Ok(())
}
}
fn convert_hnc8<S: RangedSource, W: Write>(
source: &mut S,
sink: &mut W,
options: ConversionOptions<'_>,
limits: &Limits,
observer: &Observer<'_>,
) -> Result<ConversionReport> {
let compose = ComposeOptions {
text_header_policy: options.text_header_policy,
include_bookmarks: options.include_bookmarks,
};
let mut fonts = match options.fonts {
None => None,
Some(Fonts { sources, roles }) => {
let roles = roles.ok_or(Error::invalid("C8 font resources require explicit roles"))?;
for font in &sources {
limits.check_input_size(font.source.size())?;
}
Some((sources, roles))
}
};
let report = convert_document_pdf(
source,
sink,
fonts.as_mut().map(|(sources, roles)| C8FontSources {
sources,
roles: *roles,
}),
Some(&QmTable::standard()),
&mut CompletePages,
compose,
limits,
observer,
)?;
Ok(ConversionReport {
outline: report.outline,
application_info: report.application_info,
images: ImageCounts {
type0: report.type0_images,
jpeg: report.jpeg_images,
type3: report.type3_images,
},
..report.conversion
})
}
pub fn needs_fonts<S: RangedSource>(
source: &mut S,
limits: &Limits,
progress: &mut dyn Progress,
) -> Result<bool> {
let observer = Observer::new(progress);
let mut source = observer.track(source);
match detect_source(&mut source, limits, &observer) {
Ok(Some(Detection {
format: InputFormat::Hn | InputFormat::C8,
..
})) => uses_native_text(&mut source, limits, &observer),
_ => Ok(false),
}
}