use super::*;
pub(super) fn build_style_fragments(
ctx: &mut ExportContext,
used_styles: &std::collections::BTreeSet<u64>,
) -> Vec<KfxFragment> {
let style_pairs = ctx.style_registry.drain_to_ion();
style_pairs
.into_iter()
.filter(|(name, _)| {
ctx.symbols
.get(name)
.is_some_and(|sym| used_styles.contains(&sym))
})
.map(|(name, ion)| KfxFragment::new(KfxSymbol::Style, &name, ion))
.collect()
}
pub(super) fn build_metadata_fragment(ctx: &ExportContext) -> KfxFragment {
let sections: Vec<IonValue> = ctx
.section_ids
.iter()
.map(|&id| IonValue::Symbol(id))
.collect();
let reading_order = IonValue::Struct(vec![
(
KfxSymbol::ReadingOrderName as u64,
IonValue::Symbol(KfxSymbol::Default as u64),
),
(KfxSymbol::Sections as u64, IonValue::List(sections)),
]);
let reading_orders = IonValue::List(vec![reading_order]);
let metadata = IonValue::Struct(vec![(KfxSymbol::ReadingOrders as u64, reading_orders)]);
KfxFragment::singleton(KfxSymbol::Metadata, metadata)
}
pub(super) fn build_book_metadata_fragment(
book: &Book,
container_id: &str,
ctx: &ExportContext,
) -> KfxFragment {
let meta = book.metadata();
let cover_resource_name = meta.cover_image.as_ref().and_then(|path| {
if let Some(name) = ctx.resource_registry.get_name(path) {
return Some(name);
}
let with_prefix = format!("epub/{}", path);
if let Some(name) = ctx.resource_registry.get_name(&with_prefix) {
return Some(name);
}
let filename = std::path::Path::new(path)
.file_name()
.and_then(|n| n.to_str())?;
for (href, _) in ctx.resource_registry.iter() {
if href.ends_with(filename) {
return ctx.resource_registry.get_name(href);
}
}
None
});
let id_seed = if !meta.identifier.is_empty() {
meta.identifier.clone()
} else {
format!("{}\n{}", meta.title, meta.authors.join("&"))
};
let book_id = Some(generate_book_id(&id_seed));
let content_id = Some(generate_content_id(&id_seed));
let meta_ctx = MetadataContext {
version: Some(env!("CARGO_PKG_VERSION")),
cover_resource_name,
asset_id: Some(container_id),
book_id,
content_id,
};
let categories = [
MetadataCategory::KindleEbook,
MetadataCategory::KindleTitle,
MetadataCategory::KindleAudit,
];
let categorised: Vec<IonValue> = categories
.iter()
.map(|&cat| {
let entries = build_category_entries(cat, meta, &meta_ctx);
let mut ion_entries: Vec<IonValue> = entries
.into_iter()
.map(|(k, v)| metadata_kv(k, &v))
.collect();
if cat == MetadataCategory::KindleTitle {
ion_entries.push(metadata_kv_bool("is_sample", false));
ion_entries.push(metadata_kv_bool("override_kindle_font", false));
}
IonValue::Struct(vec![
(
KfxSymbol::Category as u64,
IonValue::String(cat.as_str().to_string()),
),
(KfxSymbol::Metadata as u64, IonValue::List(ion_entries)),
])
})
.collect();
let book_metadata = IonValue::Struct(vec![(
KfxSymbol::CategorisedMetadata as u64,
IonValue::List(categorised),
)]);
KfxFragment::singleton(KfxSymbol::BookMetadata, book_metadata)
}
pub(super) fn metadata_kv(key: &str, value: &str) -> IonValue {
IonValue::Struct(vec![
(KfxSymbol::Key as u64, IonValue::String(key.to_string())),
(KfxSymbol::Value as u64, IonValue::String(value.to_string())),
])
}
fn metadata_kv_bool(key: &str, value: bool) -> IonValue {
IonValue::Struct(vec![
(KfxSymbol::Key as u64, IonValue::String(key.to_string())),
(KfxSymbol::Value as u64, IonValue::Bool(value)),
])
}
pub(super) fn build_content_features_fragment(ctx: &ExportContext) -> KfxFragment {
let mut features = vec![
feature_entry("com.amazon.yjconversion", "reflow-style", 6),
feature_entry("SDK.Marker", "CanonicalFormat", 1),
];
if ctx.has_hdv_image {
features.push(feature_entry("com.amazon.yjconversion", "yj_hdv", 1));
}
if ctx.jpg_rst_marker_present {
features.push(feature_entry(
"com.amazon.yjconversion",
"yj_jpg_rst_marker_present",
1,
));
}
if ctx.has_tables {
features.push(feature_entry("com.amazon.yjconversion", "yj_table", 7));
features.push(feature_entry(
"com.amazon.yjconversion",
"yj_table_viewer",
1,
));
}
let max_section_pids = max_section_position_count(ctx);
if max_section_pids > 65536 {
let size = (((max_section_pids - 65536) / 16384) + 2).min(256);
features.push(feature_entry(
"com.amazon.yjconversion",
"reflow-section-size",
size,
));
}
let content_features =
IonValue::Struct(vec![(KfxSymbol::Features as u64, IonValue::List(features))]);
KfxFragment::singleton(KfxSymbol::ContentFeatures, content_features)
}
fn feature_entry(namespace: &str, key: &str, major: i64) -> IonValue {
IonValue::Struct(vec![
(
KfxSymbol::Namespace as u64,
IonValue::String(namespace.to_string()),
),
(KfxSymbol::Key as u64, IonValue::String(key.to_string())),
(
KfxSymbol::VersionInfo as u64,
IonValue::Struct(vec![(
KfxSymbol::Version as u64,
IonValue::Struct(vec![
(KfxSymbol::MajorVersion as u64, IonValue::Int(major)),
(KfxSymbol::MinorVersion as u64, IonValue::Int(0)),
]),
)]),
),
])
}
pub(super) fn build_document_data_fragment(ctx: &ExportContext) -> KfxFragment {
let sections: Vec<IonValue> = ctx
.section_ids
.iter()
.map(|&id| IonValue::Symbol(id))
.collect();
let reading_order = IonValue::Struct(vec![
(
KfxSymbol::ReadingOrderName as u64,
IonValue::Symbol(KfxSymbol::Default as u64),
),
(KfxSymbol::Sections as u64, IonValue::List(sections)),
]);
let max_id = ctx.max_eid();
let document_data = IonValue::Struct(vec![
(
KfxSymbol::Direction as u64,
IonValue::Symbol(KfxSymbol::Ltr as u64),
),
(
KfxSymbol::ColumnCount as u64,
IonValue::Symbol(KfxSymbol::Auto as u64),
),
(
KfxSymbol::FontSize as u64,
IonValue::Struct(vec![
(KfxSymbol::Value as u64, IonValue::Decimal("1".to_string())),
(
KfxSymbol::Unit as u64,
IonValue::Symbol(KfxSymbol::Em as u64),
),
]),
),
(
KfxSymbol::WritingMode as u64,
IonValue::Symbol(KfxSymbol::HorizontalTb as u64),
),
(
KfxSymbol::Selection as u64,
IonValue::Symbol(KfxSymbol::Enabled as u64),
),
(KfxSymbol::MaxId as u64, IonValue::Int(max_id as i64)),
(
KfxSymbol::LineHeight as u64,
IonValue::Struct(vec![
(
KfxSymbol::Value as u64,
IonValue::Decimal("1.2".to_string()),
),
(
KfxSymbol::Unit as u64,
IonValue::Symbol(KfxSymbol::Em as u64),
),
]),
),
(
KfxSymbol::SpacingPercentBase as u64,
IonValue::Symbol(KfxSymbol::Width as u64),
),
(
KfxSymbol::ReadingOrders as u64,
IonValue::List(vec![reading_order]),
),
]);
KfxFragment::singleton(KfxSymbol::DocumentData, document_data)
}