//! Package transformed XHTML content into a valid EPUB3 file (a ZIP with a
//! specific structure). The `mimetype` entry must be first and stored uncompressed.
const std = @import("std");
const assert = std.debug.assert;
const zip = @import("zip.zig");
/// Fixed modification timestamp for reproducible output.
const modified = "2021-01-01T00:00:00Z";
pub const Options = struct {
title: []const u8,
/// Version name, used in the title and as the unique identifier basis.
version: []const u8,
lang: []const u8 = "en",
/// Well-formed XHTML5 body (output of html.transform).
xhtml: []const u8,
/// `
...` of TOC entries extracted from the page, or null. When
/// present it becomes the navigation document and the book's opening page.
toc: ?[]const u8 = null,
};
/// Build an EPUB3 archive from `opts`. Caller owns the returned bytes.
pub fn build(allocator: std.mem.Allocator, opts: Options) ![]u8 {
assert(opts.version.len > 0);
assert(opts.xhtml.len > 0);
var w = zip.Writer.init(allocator);
defer w.deinit();
// 1. mimetype — MUST be the first entry and stored uncompressed.
try w.addStored("mimetype", "application/epub+zip");
// 2. container.xml — points the reader at the package document.
try w.addStored("META-INF/container.xml", container_xml);
// 3. content.opf — package metadata, manifest, spine.
const opf = try buildOpf(allocator, opts);
defer allocator.free(opf);
try w.addStored("OEBPS/content.opf", opf);
// 4. nav.xhtml — minimal EPUB3 navigation document.
const nav = try buildNav(allocator, opts);
defer allocator.free(nav);
try w.addStored("OEBPS/nav.xhtml", nav);
// 5. index.xhtml — the transformed reference content.
try w.addStored("OEBPS/index.xhtml", opts.xhtml);
const bytes = try w.finish();
assert(std.mem.startsWith(u8, bytes, "PK"));
return bytes;
}
const container_xml =
\\
\\
\\
\\
\\
\\
\\
;
fn buildOpf(allocator: std.mem.Allocator, opts: Options) ![]u8 {
return std.fmt.allocPrint(allocator,
\\
\\
\\
\\ urn:ziglang-docs:{s}
\\ {s} ({s})
\\ {s}
\\ {s}
\\
\\
\\
\\
\\
\\
\\
\\
\\
\\
\\
, .{ opts.version, opts.title, opts.version, opts.lang, modified });
}
fn buildNav(allocator: std.mem.Allocator, opts: Options) ![]u8 {
// Use the page's own table of contents when we extracted one; otherwise fall
// back to a single link to the whole document.
const list = opts.toc orelse
"\n