use std::collections::HashSet;
use super::iwork::{first_bytes, Archive};
use super::keynote::{
reading_order, titled, Geometry, Placed, Presentation, Slide, DEFAULT_SLIDE_HEIGHT,
DEFAULT_SLIDE_WIDTH,
};
use super::ooxml::Package;
use super::pages::{Block, Comment, Paragraph};
use super::pages_iwa::{
drawable_geometry, index_objects, read_point, reference_field, reference_list, Reader,
SHAPE_STORAGE_FIELD, TSWP_STORAGE_ARCHIVE,
};
use crate::error::ConversionError;
const KN_DOCUMENT_ARCHIVE: u32 = 1;
const KN_SHOW_ARCHIVE: u32 = 2;
const KN_SLIDE_NODE_ARCHIVE: u32 = 4;
const KN_SLIDE_ARCHIVE: u32 = 5;
const KN_PLACEHOLDER_ARCHIVE: u32 = 7;
const KN_NOTE_ARCHIVE: u32 = 15;
const KN_COMMENT_ARCHIVE: u32 = 2014;
const DOCUMENT_SHOW_FIELD: u32 = 2;
const SHOW_SLIDE_TREE_FIELD: u32 = 3;
const SHOW_SLIDE_SIZE_FIELD: u32 = 4;
const SLIDE_TREE_NODES_FIELD: u32 = 2;
const SLIDE_NODE_SLIDE_FIELD: u32 = 2;
const SLIDE_TITLE_FIELD: u32 = 5;
const SLIDE_BODY_FIELD: u32 = 6;
const SLIDE_DRAWABLES_FIELD: u32 = 7;
const SLIDE_NUMBER_FIELD: u32 = 20;
const SLIDE_NOTE_FIELD: u32 = 27;
const PLACEHOLDER_SHAPE_FIELD: u32 = 1;
const NOTE_TEXT_FIELD: u32 = 1;
const COMMENT_STORAGE_FIELD: u32 = 2;
fn fail(msg: &str) -> ConversionError {
ConversionError::Parse(format!("iwork: {msg}"))
}
pub(crate) fn read_content(
archives: &[Archive],
container: &mut Package,
data_prefix: &str,
) -> Result<Presentation, ConversionError> {
let (objects, order) = index_objects(archives);
let document = order
.iter()
.filter_map(|id| objects.get(id).copied())
.find(|a| a.ty == KN_DOCUMENT_ARCHIVE)
.ok_or_else(|| {
fail(
"the Keynote document has no KN.DocumentArchive; the container may be corrupt \
or password-protected",
)
})?;
let show = reference_field(&document.payload, DOCUMENT_SHOW_FIELD)
.and_then(|id| objects.get(&id).copied())
.filter(|a| a.ty == KN_SHOW_ARCHIVE)
.ok_or_else(|| {
fail("the Keynote document does not reference a KN.ShowArchive, so it has no slides")
})?;
let (width, height) = slide_size(show);
let mut reader = KeynoteReader {
inner: {
let mut reader = Reader::new(&objects, &order, container, data_prefix);
reader.charts = true;
reader
},
};
Ok(Presentation {
slides: reader.slides(show),
width,
height,
})
}
fn slide_size(show: &Archive) -> (f64, f64) {
match first_bytes(&show.payload, SHOW_SLIDE_SIZE_FIELD).and_then(read_point) {
Some((w, h)) if w > 0.0 && h > 0.0 => (w, h),
_ => (DEFAULT_SLIDE_WIDTH, DEFAULT_SLIDE_HEIGHT),
}
}
struct KeynoteReader<'a, 'p> {
inner: Reader<'a, 'p>,
}
impl<'a> KeynoteReader<'a, '_> {
fn slides(&mut self, show: &Archive) -> Vec<Slide> {
let Some(tree) = first_bytes(&show.payload, SHOW_SLIDE_TREE_FIELD) else {
return Vec::new();
};
reference_list(tree, SLIDE_TREE_NODES_FIELD)
.into_iter()
.filter_map(|node| self.slide(node))
.collect()
}
fn slide(&mut self, node_id: u64) -> Option<Slide> {
let node = self.inner.typed(node_id, KN_SLIDE_NODE_ARCHIVE)?;
let slide = reference_field(&node.payload, SLIDE_NODE_SLIDE_FIELD)
.and_then(|id| self.inner.typed(id, KN_SLIDE_ARCHIVE))?;
let title = reference_field(&slide.payload, SLIDE_TITLE_FIELD);
let number = reference_field(&slide.payload, SLIDE_NUMBER_FIELD);
let placed = self.placed(slide, number);
let geometries: Vec<Option<Geometry>> = placed.iter().map(|(_, g)| *g).collect();
let mut blocks = Vec::new();
let mut comments = Vec::new();
for position in reading_order(&geometries) {
let (identifier, geometry) = placed[position];
let (found, said) = self.blocks(identifier, Some(identifier) == title);
blocks.extend(found.into_iter().map(|block| Placed { block, geometry }));
comments.extend(said);
}
Some(Slide {
blocks,
notes: self.notes(slide),
comments,
})
}
fn placed(&self, slide: &Archive, number: Option<u64>) -> Vec<(u64, Option<Geometry>)> {
let mut identifiers: Vec<u64> = [SLIDE_TITLE_FIELD, SLIDE_BODY_FIELD]
.into_iter()
.filter_map(|field| reference_field(&slide.payload, field))
.collect();
identifiers.extend(reference_list(&slide.payload, SLIDE_DRAWABLES_FIELD));
let mut placed = Vec::new();
let mut seen: HashSet<u64> = HashSet::new();
for identifier in identifiers {
if Some(identifier) == number || !seen.insert(identifier) {
continue;
}
let geometry = self
.inner
.object(identifier)
.and_then(|d| drawable_geometry(&d.payload));
placed.push((identifier, geometry));
}
placed
}
fn blocks(&mut self, identifier: u64, title: bool) -> (Vec<Block>, Vec<Comment>) {
let Some(drawable) = self.inner.object(identifier) else {
return (Vec::new(), Vec::new());
};
match drawable.ty {
KN_PLACEHOLDER_ARCHIVE => {
if !self.inner.emitted.insert(identifier) {
return (Vec::new(), Vec::new());
}
let blocks = self
.placeholder_blocks(drawable)
.into_iter()
.map(|b| titled(b, title))
.collect();
(blocks, Vec::new())
}
KN_COMMENT_ARCHIVE => {
if !self.inner.emitted.insert(identifier) {
return (Vec::new(), Vec::new());
}
(Vec::new(), self.comments(drawable))
}
_ => {
let blocks = self
.inner
.drawable_blocks(identifier)
.into_iter()
.map(|b| titled(b, false))
.collect();
(blocks, Vec::new())
}
}
}
fn placeholder_blocks(&mut self, placeholder: &'a Archive) -> Vec<Block> {
let Some(shape) = first_bytes(&placeholder.payload, PLACEHOLDER_SHAPE_FIELD) else {
return Vec::new();
};
let Some(storage) = reference_field(shape, SHAPE_STORAGE_FIELD)
.and_then(|id| self.inner.typed(id, TSWP_STORAGE_ARCHIVE))
else {
return Vec::new();
};
self.inner.storage_blocks(storage)
}
fn comments(&self, shape: &Archive) -> Vec<Comment> {
let head = reference_field(&shape.payload, COMMENT_STORAGE_FIELD);
self.inner
.thread(head)
.into_iter()
.map(|text| Comment {
text,
anchor: String::new(),
})
.collect()
}
fn notes(&self, slide: &Archive) -> Vec<Paragraph> {
let Some(note) = reference_field(&slide.payload, SLIDE_NOTE_FIELD)
.and_then(|id| self.inner.typed(id, KN_NOTE_ARCHIVE))
else {
return Vec::new();
};
self.inner
.storage_paragraphs(reference_field(¬e.payload, NOTE_TEXT_FIELD))
}
}