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, NativeDecorationGlyph, NativeFontGlyphAlias, NativeSymbolGlyph, OutlineReport,
SymbolFontIdentity, 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";
#[derive(Default)]
pub struct Fonts<'a> {
pub sources: Vec<C8FontSource<Box<dyn RangedSource + 'a>>>,
pub roles: Option<C8PageFonts>,
pub embedding: crate::pdf::FontEmbedding,
pub glyph_aliases: Vec<NativeFontGlyphAlias>,
pub symbol_glyphs: Vec<NativeSymbolGlyph>,
pub symbol_font: Option<SymbolFontIdentity>,
pub decoration_glyphs: Vec<NativeDecorationGlyph>,
pub decoration_font: Option<SymbolFontIdentity>,
}
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> {
convert_inner(source, sink, options, None, limits, progress)
}
pub fn convert_with_ttkn_response<S: RangedSource, W: Write>(
source: &mut S,
sink: &mut W,
options: ConversionOptions<'_>,
response: &crate::pdf::TtknResponse,
limits: &Limits,
progress: &mut dyn Progress,
) -> Result<ConversionReport> {
convert_inner(source, sink, options, Some(response), limits, progress)
}
fn convert_inner<S: RangedSource, W: Write>(
source: &mut S,
sink: &mut W,
options: ConversionOptions<'_>,
response: Option<&crate::pdf::TtknResponse>,
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));
}
if response.is_some() && format != InputFormat::Pdf {
return Err(Error::invalid("TTKN response requires a PDF input"));
}
let mut report = match format {
InputFormat::Pdf => {
let range = pdf_range(source.size(), header_offset);
match response {
Some(response) => {
crate::pdf::convert_ttkn(&mut source, sink, range, response, limits, &observer)?
}
None => 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 | InputFormat::Caa => 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 = options
.fonts
.map(|fonts| {
let roles = fonts
.roles
.ok_or(Error::invalid("C8 font resources require explicit roles"))?;
for font in &fonts.sources {
limits.check_input_size(font.source.size())?;
}
Ok::<_, Error>((fonts, roles))
})
.transpose()?;
let report = convert_document_pdf(
source,
sink,
fonts.as_mut().map(|(fonts, roles)| C8FontSources {
sources: &mut fonts.sources,
roles: *roles,
embedding: fonts.embedding,
glyph_aliases: &fonts.glyph_aliases,
symbol_glyphs: &fonts.symbol_glyphs,
symbol_font: fonts.symbol_font.as_ref(),
decoration_glyphs: &fonts.decoration_glyphs,
decoration_font: fonts.decoration_font.as_ref(),
}),
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),
}
}
pub fn native_font_mode<S: RangedSource>(
source: &mut S,
limits: &Limits,
progress: &mut dyn Progress,
) -> Result<Option<u32>> {
if !needs_fonts(source, limits, progress)? {
return Ok(None);
}
let observer = Observer::new(progress);
let mut source = observer.track(source);
Ok(
crate::hnc8::Hnc8Reader::open(&mut source, limits, &observer)?
.header()
.native_mode,
)
}