aboutsummaryrefslogtreecommitdiffstats
path: root/src/epub.zig
diff options
context:
space:
mode:
authorSimen A. W. Olsen <hello@simenandre.no>2026-05-28 23:54:24 +0200
committerSimen A. W. Olsen <hello@simenandre.no>2026-05-28 23:54:24 +0200
commit330a0c896970d99320529a47f186c831a6ecc4a0 (patch)
tree3dc8b3ac700a6e2715f0f81b96e7957320f6352e /src/epub.zig
parentb2d7d90add48e5cd3b644949b9b401fafc6eafbc (diff)
downloadziglang-docs-epub-330a0c896970d99320529a47f186c831a6ecc4a0.tar.gz
ziglang-docs-epub-330a0c896970d99320529a47f186c831a6ecc4a0.zip
feat: add table of content instead of sidebar
Diffstat (limited to 'src/epub.zig')
-rw-r--r--src/epub.zig27
1 files changed, 21 insertions, 6 deletions
diff --git a/src/epub.zig b/src/epub.zig
index 0bdf563..7e35089 100644
--- a/src/epub.zig
+++ b/src/epub.zig
@@ -13,8 +13,11 @@ pub const Options = struct {
/// 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.toXhtml).
+ /// Well-formed XHTML5 body (output of html.transform).
xhtml: []const u8,
+ /// `<ol>...</ol>` 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.
@@ -74,6 +77,7 @@ fn buildOpf(allocator: std.mem.Allocator, opts: Options) ![]u8 {
\\ <item id="index" href="index.xhtml" media-type="application/xhtml+xml"/>
\\ </manifest>
\\ <spine>
+ \\ <itemref idref="nav"/>
\\ <itemref idref="index"/>
\\ </spine>
\\</package>
@@ -82,21 +86,23 @@ fn buildOpf(allocator: std.mem.Allocator, opts: Options) ![]u8 {
}
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
+ "<ol>\n <li><a href=\"index.xhtml\">Contents</a></li>\n </ol>";
return std.fmt.allocPrint(allocator,
\\<?xml version="1.0" encoding="utf-8"?>
\\<html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops" lang="{s}">
\\ <head><title>{s} ({s})</title></head>
\\ <body>
\\ <nav epub:type="toc" id="toc">
- \\ <h1>Contents</h1>
- \\ <ol>
- \\ <li><a href="index.xhtml">{s} ({s})</a></li>
- \\ </ol>
+ \\ <h1>{s} ({s})</h1>
+ \\ {s}
\\ </nav>
\\ </body>
\\</html>
\\
- , .{ opts.lang, opts.title, opts.version, opts.title, opts.version });
+ , .{ opts.lang, opts.title, opts.version, opts.title, opts.version, list });
}
test "epub starts with PK and mimetype, and round-trips" {
@@ -105,6 +111,7 @@ test "epub starts with PK and mimetype, and round-trips" {
.title = "Zig Language Reference",
.version = "0.15.2",
.xhtml = "<?xml version=\"1.0\"?>\n<html xmlns=\"http://www.w3.org/1999/xhtml\"><body>hi</body></html>",
+ .toc = "<ol><li><a href=\"index.xhtml#Intro\">Intro</a></li></ol>",
});
defer gpa.free(bytes);
@@ -126,4 +133,12 @@ test "epub starts with PK and mimetype, and round-trips" {
const opf = try tmp.dir.readFileAlloc(gpa, "OEBPS/content.opf", 1 << 16);
defer gpa.free(opf);
try std.testing.expect(std.mem.indexOf(u8, opf, "0.15.2") != null);
+ // nav must come before index in the spine (TOC is the opening page).
+ const nav_ref = std.mem.indexOf(u8, opf, "idref=\"nav\"").?;
+ const idx_ref = std.mem.indexOf(u8, opf, "idref=\"index\"").?;
+ try std.testing.expect(nav_ref < idx_ref);
+
+ const nav = try tmp.dir.readFileAlloc(gpa, "OEBPS/nav.xhtml", 1 << 16);
+ defer gpa.free(nav);
+ try std.testing.expect(std.mem.indexOf(u8, nav, "href=\"index.xhtml#Intro\"") != null);
}