From 330a0c896970d99320529a47f186c831a6ecc4a0 Mon Sep 17 00:00:00 2001 From: "Simen A. W. Olsen" Date: Thu, 28 May 2026 23:54:24 +0200 Subject: feat: add table of content instead of sidebar --- README.md | 23 +++- epubs/zig-0.1.1.epub | Bin 201853 -> 0 bytes epubs/zig-0.10.0.epub | Bin 1130432 -> 0 bytes epubs/zig-0.10.1.epub | Bin 1133513 -> 0 bytes epubs/zig-0.11.0.epub | Bin 1081055 -> 1083315 bytes epubs/zig-0.12.0.epub | Bin 922795 -> 925023 bytes epubs/zig-0.12.1.epub | Bin 933356 -> 935649 bytes epubs/zig-0.13.0.epub | Bin 933399 -> 935698 bytes epubs/zig-0.14.0.epub | Bin 964357 -> 966701 bytes epubs/zig-0.14.1.epub | Bin 967595 -> 970004 bytes epubs/zig-0.15.1.epub | Bin 970943 -> 973287 bytes epubs/zig-0.15.2.epub | Bin 970121 -> 972519 bytes epubs/zig-0.16.0.epub | Bin 983739 -> 986269 bytes epubs/zig-0.2.0.epub | Bin 238448 -> 0 bytes epubs/zig-0.3.0.epub | Bin 525326 -> 0 bytes epubs/zig-0.4.0.epub | Bin 656333 -> 0 bytes epubs/zig-0.5.0.epub | Bin 719277 -> 0 bytes epubs/zig-0.6.0.epub | Bin 709449 -> 0 bytes epubs/zig-0.7.0.epub | Bin 763214 -> 0 bytes epubs/zig-0.7.1.epub | Bin 763304 -> 0 bytes epubs/zig-0.8.0.epub | Bin 799052 -> 0 bytes epubs/zig-0.8.1.epub | Bin 802163 -> 0 bytes epubs/zig-0.9.0.epub | Bin 1113158 -> 1115228 bytes epubs/zig-0.9.1.epub | Bin 1113237 -> 1115381 bytes epubs/zig-master.epub | Bin 967090 -> 969372 bytes src/epub.zig | 27 ++++- src/html.zig | 305 +++++++++++++++++++++++++++++++++++++++++++++++--- src/main.zig | 17 ++- 28 files changed, 347 insertions(+), 25 deletions(-) delete mode 100644 epubs/zig-0.1.1.epub delete mode 100644 epubs/zig-0.10.0.epub delete mode 100644 epubs/zig-0.10.1.epub delete mode 100644 epubs/zig-0.2.0.epub delete mode 100644 epubs/zig-0.3.0.epub delete mode 100644 epubs/zig-0.4.0.epub delete mode 100644 epubs/zig-0.5.0.epub delete mode 100644 epubs/zig-0.6.0.epub delete mode 100644 epubs/zig-0.7.0.epub delete mode 100644 epubs/zig-0.7.1.epub delete mode 100644 epubs/zig-0.8.0.epub delete mode 100644 epubs/zig-0.8.1.epub diff --git a/README.md b/README.md index 0a5ea18..6252224 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,8 @@ Four decoupled concerns, each a small module that does one thing: | ------------- | --------------------------------------------------------- | | `index.zig` | discover versions + docs URLs from `download/index.json` | | `fetch.zig` | HTTP GET via `std.http.Client` | -| `html.zig` | transform the page HTML5 → well-formed XHTML5 | -| `epub.zig` | assemble the EPUB3 container | +| `html.zig` | HTML5 → XHTML5; extract the in-page TOC; drop the sidebar | +| `epub.zig` | assemble the EPUB3 container (TOC navigation + content) | | `zip.zig` | minimal, reproducible ZIP writer (stored entries) | No external runtime dependencies — Zig's standard library provides HTTP and @@ -28,6 +28,25 @@ spec-compliant at the cost of larger files. Output is **byte-for-byte reproducible** (fixed ZIP timestamps), so CI only commits when the upstream docs actually change. +### Table of contents + +The docs page renders its table of contents in a `position: fixed` sidebar, which +e-readers repaint on every page. The tool extracts that TOC into a proper EPUB3 +navigation document (`nav.xhtml`): it shows in the reader's built-in Contents +menu, is the book's opening page, and each entry jumps to the matching section. +The original fixed sidebar (and its dead version-switcher dropdown) is removed +from the content so it no longer bleeds across pages. + +### Well-formedness gate + +Each transformed page is checked for well-formed, balanced XHTML before +packaging; versions that fail are **skipped** (logged as `NotWellFormed`) rather +than shipped as invalid EPUB3. A handful of older docs pages (currently 0.1.1–0.8.1 +and 0.10.x) contain malformed upstream HTML — unclosed `

`/`

  • ` tags and +outright typos — that can't be repaired without a full HTML5 tree-builder. Every +EPUB this tool produces is therefore strictly well-formed; newer versions are +picked up automatically as they are released. + ## Usage Requires **Zig 0.15.2**. diff --git a/epubs/zig-0.1.1.epub b/epubs/zig-0.1.1.epub deleted file mode 100644 index 42c91ec..0000000 Binary files a/epubs/zig-0.1.1.epub and /dev/null differ diff --git a/epubs/zig-0.10.0.epub b/epubs/zig-0.10.0.epub deleted file mode 100644 index 49a331d..0000000 Binary files a/epubs/zig-0.10.0.epub and /dev/null differ diff --git a/epubs/zig-0.10.1.epub b/epubs/zig-0.10.1.epub deleted file mode 100644 index b153576..0000000 Binary files a/epubs/zig-0.10.1.epub and /dev/null differ diff --git a/epubs/zig-0.11.0.epub b/epubs/zig-0.11.0.epub index 7aecf9c..1dfc0f0 100644 Binary files a/epubs/zig-0.11.0.epub and b/epubs/zig-0.11.0.epub differ diff --git a/epubs/zig-0.12.0.epub b/epubs/zig-0.12.0.epub index c72d0d1..05bb36e 100644 Binary files a/epubs/zig-0.12.0.epub and b/epubs/zig-0.12.0.epub differ diff --git a/epubs/zig-0.12.1.epub b/epubs/zig-0.12.1.epub index d1be901..1907d2f 100644 Binary files a/epubs/zig-0.12.1.epub and b/epubs/zig-0.12.1.epub differ diff --git a/epubs/zig-0.13.0.epub b/epubs/zig-0.13.0.epub index bfd1991..98cce35 100644 Binary files a/epubs/zig-0.13.0.epub and b/epubs/zig-0.13.0.epub differ diff --git a/epubs/zig-0.14.0.epub b/epubs/zig-0.14.0.epub index 53a9137..39d7ea3 100644 Binary files a/epubs/zig-0.14.0.epub and b/epubs/zig-0.14.0.epub differ diff --git a/epubs/zig-0.14.1.epub b/epubs/zig-0.14.1.epub index a533700..1f14838 100644 Binary files a/epubs/zig-0.14.1.epub and b/epubs/zig-0.14.1.epub differ diff --git a/epubs/zig-0.15.1.epub b/epubs/zig-0.15.1.epub index 68bb208..be73887 100644 Binary files a/epubs/zig-0.15.1.epub and b/epubs/zig-0.15.1.epub differ diff --git a/epubs/zig-0.15.2.epub b/epubs/zig-0.15.2.epub index bce9cd8..f4845af 100644 Binary files a/epubs/zig-0.15.2.epub and b/epubs/zig-0.15.2.epub differ diff --git a/epubs/zig-0.16.0.epub b/epubs/zig-0.16.0.epub index 154ea4b..6490d41 100644 Binary files a/epubs/zig-0.16.0.epub and b/epubs/zig-0.16.0.epub differ diff --git a/epubs/zig-0.2.0.epub b/epubs/zig-0.2.0.epub deleted file mode 100644 index 34f1342..0000000 Binary files a/epubs/zig-0.2.0.epub and /dev/null differ diff --git a/epubs/zig-0.3.0.epub b/epubs/zig-0.3.0.epub deleted file mode 100644 index f34192d..0000000 Binary files a/epubs/zig-0.3.0.epub and /dev/null differ diff --git a/epubs/zig-0.4.0.epub b/epubs/zig-0.4.0.epub deleted file mode 100644 index 1d9afa6..0000000 Binary files a/epubs/zig-0.4.0.epub and /dev/null differ diff --git a/epubs/zig-0.5.0.epub b/epubs/zig-0.5.0.epub deleted file mode 100644 index 2b6ed40..0000000 Binary files a/epubs/zig-0.5.0.epub and /dev/null differ diff --git a/epubs/zig-0.6.0.epub b/epubs/zig-0.6.0.epub deleted file mode 100644 index 78ade26..0000000 Binary files a/epubs/zig-0.6.0.epub and /dev/null differ diff --git a/epubs/zig-0.7.0.epub b/epubs/zig-0.7.0.epub deleted file mode 100644 index 81e36ff..0000000 Binary files a/epubs/zig-0.7.0.epub and /dev/null differ diff --git a/epubs/zig-0.7.1.epub b/epubs/zig-0.7.1.epub deleted file mode 100644 index 02ccf63..0000000 Binary files a/epubs/zig-0.7.1.epub and /dev/null differ diff --git a/epubs/zig-0.8.0.epub b/epubs/zig-0.8.0.epub deleted file mode 100644 index 8105508..0000000 Binary files a/epubs/zig-0.8.0.epub and /dev/null differ diff --git a/epubs/zig-0.8.1.epub b/epubs/zig-0.8.1.epub deleted file mode 100644 index 43d73f5..0000000 Binary files a/epubs/zig-0.8.1.epub and /dev/null differ diff --git a/epubs/zig-0.9.0.epub b/epubs/zig-0.9.0.epub index 47ae0b9..514d283 100644 Binary files a/epubs/zig-0.9.0.epub and b/epubs/zig-0.9.0.epub differ diff --git a/epubs/zig-0.9.1.epub b/epubs/zig-0.9.1.epub index 212a0fd..9914fdd 100644 Binary files a/epubs/zig-0.9.1.epub and b/epubs/zig-0.9.1.epub differ diff --git a/epubs/zig-master.epub b/epubs/zig-master.epub index 4b59c34..139d828 100644 Binary files a/epubs/zig-master.epub and b/epubs/zig-master.epub differ 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, + /// `
      ...
    ` 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 { \\ \\ \\ + \\ \\ \\ \\ @@ -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 + "
      \n
    1. Contents
    2. \n
    "; return std.fmt.allocPrint(allocator, \\ \\ \\ {s} ({s}) \\ \\ \\ \\ \\ - , .{ 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 = "\nhi", + .toc = "
    1. Intro
    ", }); 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); } diff --git a/src/html.zig b/src/html.zig index 56c5d91..ec9ecfd 100644 --- a/src/html.zig +++ b/src/html.zig @@ -1,13 +1,17 @@ //! Transform the Zig docs single-page HTML5 into well-formed XHTML5 for EPUB. //! //! The page is self-contained (embedded