kobofix 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kobofix-1.0.0/LICENSE +21 -0
- kobofix-1.0.0/PKG-INFO +295 -0
- kobofix-1.0.0/README.md +272 -0
- kobofix-1.0.0/kobofix.egg-info/PKG-INFO +295 -0
- kobofix-1.0.0/kobofix.egg-info/SOURCES.txt +9 -0
- kobofix-1.0.0/kobofix.egg-info/dependency_links.txt +1 -0
- kobofix-1.0.0/kobofix.egg-info/entry_points.txt +2 -0
- kobofix-1.0.0/kobofix.egg-info/top_level.txt +1 -0
- kobofix-1.0.0/kobofix.py +1890 -0
- kobofix-1.0.0/pyproject.toml +35 -0
- kobofix-1.0.0/setup.cfg +4 -0
kobofix-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 dmang-dev
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
kobofix-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kobofix
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Make EPUBs work on Kobo e-readers (Adobe RMSDK): detect and fix the modern CSS that breaks them.
|
|
5
|
+
Author: dmang-dev
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/dmang-dev/kobofix
|
|
8
|
+
Project-URL: Repository, https://github.com/dmang-dev/kobofix
|
|
9
|
+
Project-URL: Issues, https://github.com/dmang-dev/kobofix/issues
|
|
10
|
+
Keywords: epub,kobo,rmsdk,css,ebook,epubcheck,adobe-digital-editions,kepub
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Topic :: Text Processing :: Markup
|
|
18
|
+
Classifier: Topic :: Utilities
|
|
19
|
+
Requires-Python: >=3.7
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
|
|
24
|
+
# kobofix
|
|
25
|
+
|
|
26
|
+
Make an EPUB that **passes `epubcheck` but won't open (or renders broken) on a Kobo**
|
|
27
|
+
work on a Kobo. Pure Python, standard library only — no `pip install`.
|
|
28
|
+
|
|
29
|
+
This is the problem described in
|
|
30
|
+
[*"Your EPUB is fine, Kobo disagrees — blame Adobe"*](https://andreklein.net/your-epub-is-fine-kobo-disagrees-blame-adobe/),
|
|
31
|
+
generalized to every known cause, not just the one line in that post.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Three ways to use it
|
|
36
|
+
|
|
37
|
+
- **Web app (no install):** [`web/`](web/) is a 100%-client-side version for
|
|
38
|
+
GitHub Pages — drag in an `.epub`, see what breaks on Kobo, download the fixed
|
|
39
|
+
file. The book never leaves the browser. See [web/README.md](web/README.md).
|
|
40
|
+
- **CLI fixer:** `python kobofix.py book.epub` → repaired EPUB.
|
|
41
|
+
- **CLI linter:** `python kobofix.py --check book.epub` → report, optionally
|
|
42
|
+
merged with EPUBCheck.
|
|
43
|
+
|
|
44
|
+
A worked demo on a *real* book: [`make_modern_alice.py`](make_modern_alice.py)
|
|
45
|
+
takes real Project Gutenberg *Alice* content + the modern stylesheet a
|
|
46
|
+
contemporary authoring tool emits → EPUBCheck passes it clean, kobofix flags 11
|
|
47
|
+
book-breaking errors and repairs them.
|
|
48
|
+
|
|
49
|
+
## Why your "valid" EPUB fails on Kobo
|
|
50
|
+
|
|
51
|
+
Kobo renders sideloaded `.epub` files through Adobe's **legacy RMSDK** engine,
|
|
52
|
+
whose CSS parser is frozen around 2013. Two independent things bite you:
|
|
53
|
+
|
|
54
|
+
### 1. RMSDK has no CSS fault tolerance
|
|
55
|
+
|
|
56
|
+
A normal browser, on hitting one CSS declaration it doesn't understand, drops
|
|
57
|
+
*that one declaration* and keeps the rest. RMSDK does the opposite: a single
|
|
58
|
+
**value-function token it can't parse — `calc()`, `min()`, `max()`, `clamp()`,
|
|
59
|
+
`var()`, `env()` — makes it throw away the *entire stylesheet***, and on some
|
|
60
|
+
firmware **refuse to open the book** ("this book is corrupted").
|
|
61
|
+
|
|
62
|
+
The critical consequence: **a fallback declaration placed *before* the modern
|
|
63
|
+
one does not help** — the whole sheet (fallback included) is discarded. The
|
|
64
|
+
modern construct has to be *removed*, not shadowed. That's the core thing
|
|
65
|
+
`kobofix` does.
|
|
66
|
+
|
|
67
|
+
```css
|
|
68
|
+
/* This whole stylesheet is discarded by RMSDK because of one token: */
|
|
69
|
+
.copyright img { max-width: min(150px, 30vw); }
|
|
70
|
+
|
|
71
|
+
/* kobofix rewrites it to: */
|
|
72
|
+
.copyright img { max-width: 150px; }
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 2. "Corrupted" is often a packaging bug, not CSS
|
|
76
|
+
|
|
77
|
+
The most common literal-"corrupted" cause is the EPUB's ZIP packaging: the
|
|
78
|
+
`mimetype` entry must be **first**, **uncompressed (STORED)**, with **no extra
|
|
79
|
+
field** and the **exact bytes** `application/epub+zip`. Many tools (some
|
|
80
|
+
calibre save paths, Windows "Send to → compressed folder", naive zip libraries)
|
|
81
|
+
violate this and Kobo is the strictest mainstream reader about it. `kobofix`
|
|
82
|
+
**always re-emits a spec-correct OCF ZIP**, so it fixes this even when no CSS
|
|
83
|
+
needed changing.
|
|
84
|
+
|
|
85
|
+
`epubcheck` catches neither problem class reliably: the CSS is valid CSS4 so it
|
|
86
|
+
passes, and older `epubcheck` didn't flag a compressed `mimetype`. Passing
|
|
87
|
+
`epubcheck` is **not** a signal of Kobo/RMSDK compatibility.
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Install
|
|
92
|
+
|
|
93
|
+
Nothing to install. You need Python 3.7+ (tested on 3.12).
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
python kobofix.py --version
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Usage
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
python kobofix.py INPUT.epub [-o OUTPUT.epub] [options]
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Default output is `INPUT.kobofixed.epub` next to the input. The original is
|
|
106
|
+
never modified.
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
# Fix a book
|
|
110
|
+
python kobofix.py mybook.epub
|
|
111
|
+
|
|
112
|
+
# Choose the output name
|
|
113
|
+
python kobofix.py mybook.epub -o mybook-kobo.epub
|
|
114
|
+
|
|
115
|
+
# Just analyze — write nothing, print the report
|
|
116
|
+
python kobofix.py mybook.epub --dry-run
|
|
117
|
+
|
|
118
|
+
# LINT mode: report Kobo/RMSDK issues (KOBO-* ids + line numbers) WITHOUT changing
|
|
119
|
+
# anything, and merge in EPUBCheck's spec report for one combined readout.
|
|
120
|
+
python kobofix.py --check mybook.epub --epubcheck path\to\epubcheck.jar
|
|
121
|
+
|
|
122
|
+
# Machine-readable report
|
|
123
|
+
python kobofix.py mybook.epub --report json --report-file report.json
|
|
124
|
+
|
|
125
|
+
# Also run EPUBCheck on the result (you supply the jar; Java must be on PATH)
|
|
126
|
+
python kobofix.py mybook.epub --epubcheck path\to\epubcheck.jar
|
|
127
|
+
|
|
128
|
+
# Prove the engine works on your machine
|
|
129
|
+
python kobofix.py --selftest
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
You can also pass an **already-extracted EPUB folder** as the input; the tool
|
|
133
|
+
will read it and produce a correctly-packaged `.epub`.
|
|
134
|
+
|
|
135
|
+
### Options
|
|
136
|
+
|
|
137
|
+
| Option | Default | Meaning |
|
|
138
|
+
|---|---|---|
|
|
139
|
+
| `-o, --output PATH` | `<input>.kobofixed.epub` | Output file |
|
|
140
|
+
| `--check` | off | Lint only: report `KOBO-*` issues + line numbers, merge EPUBCheck; write nothing |
|
|
141
|
+
| `--dry-run` | off | Analyze and report only; write nothing |
|
|
142
|
+
| `--report {text,json}` | `text` | Report format |
|
|
143
|
+
| `--report-file PATH` | — | Also write the report to a file |
|
|
144
|
+
| `--no-rem` | rem→px on | Don't convert `rem` to `px` |
|
|
145
|
+
| `--root-font-size PX` | `16` | Pixel value of `1rem` for the conversion |
|
|
146
|
+
| `--no-viewport` | vw/vh→px on | Don't convert standalone `vw/vh/vmin/vmax` |
|
|
147
|
+
| `--vw-base PX` | `600` | Assumed reader viewport **width** for `vw` |
|
|
148
|
+
| `--vh-base PX` | `800` | Assumed reader viewport **height** for `vh` |
|
|
149
|
+
| `--clamp-pick {min,pref,max}` | `pref` | Which `clamp()` term to keep when none is an absolute length |
|
|
150
|
+
| `--epubcheck [PATH]` | off | Run EPUBCheck after building (`epubcheck`/`*.jar`, or no value to auto-detect) |
|
|
151
|
+
| `--strict` | off | Exit non-zero if any manual-review warnings were found |
|
|
152
|
+
|
|
153
|
+
Exit codes: `0` clean · `1` `--strict` with warnings · `2` output failed its
|
|
154
|
+
own packaging self-check.
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## What it fixes automatically
|
|
159
|
+
|
|
160
|
+
These are the changes that make Kobo **open and style** the book. Every one
|
|
161
|
+
leaves **zero** residual modern-value tokens in RMSDK-visible CSS.
|
|
162
|
+
|
|
163
|
+
| Construct | Action |
|
|
164
|
+
|---|---|
|
|
165
|
+
| `var(--x)` / `--x:` | Resolved to the literal value and inlined; the `--x` declarations are deleted |
|
|
166
|
+
| `calc(...)` | Evaluated when units allow (`calc(2em + 1em)`→`3em`); mixed-unit (`calc(100% - 20px)`) keeps the first term and is flagged |
|
|
167
|
+
| `min(a,b)` / `max(a,b)` | Reduced to the absolute-length term (`min(150px,30vw)`→`150px`) |
|
|
168
|
+
| `clamp(a,b,c)` | Reduced to one static size (prefers an absolute term; else `--clamp-pick`) |
|
|
169
|
+
| `env(x, fallback)` | Replaced with the fallback (or removed) |
|
|
170
|
+
| `rem` | Converted to `px` (RMSDK mis-treats `rem` as `em`, compounding sizes) |
|
|
171
|
+
| `vw/vh/vmin/vmax` | Converted to `px` against the assumed viewport (verified crash: `margin:50vh`) |
|
|
172
|
+
| empty `@media {}` / `@supports {}` | Removed (crashes old RMSDK) |
|
|
173
|
+
| `mimetype` packaging | Rewritten first / STORED / no-extra / exact bytes |
|
|
174
|
+
|
|
175
|
+
All of the above are applied inside standalone `.css` files **and** inside
|
|
176
|
+
`<style>` blocks and `style="..."` attributes in XHTML.
|
|
177
|
+
|
|
178
|
+
## What it flags but does NOT change
|
|
179
|
+
|
|
180
|
+
These have no safe automatic equivalent — converting them risks silently
|
|
181
|
+
reflowing your book, so `kobofix` reports them (with file and line) and leaves
|
|
182
|
+
the decision to you:
|
|
183
|
+
|
|
184
|
+
- **flexbox** and **grid** (and grid sub-properties)
|
|
185
|
+
- **`position: absolute/fixed/sticky`** in reflowable content
|
|
186
|
+
- **`transform`**, **`transition`/`animation`/`@keyframes`**
|
|
187
|
+
- **`object-fit`**, **`aspect-ratio`**
|
|
188
|
+
- **`:has()`**, **`:is()`/`:where()`**
|
|
189
|
+
- **`writing-mode`** — *kept on purpose*; Kobo supports it for vertical CJK text
|
|
190
|
+
|
|
191
|
+
The typical fix for flex/grid is to wrap the modern rules in
|
|
192
|
+
`@supports (display:flex) { … }` (legacy RMSDK skips the whole block) with a
|
|
193
|
+
plain block/float fallback outside it.
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## How to verify the result
|
|
198
|
+
|
|
199
|
+
1. `kobofix` self-checks its own output (mimetype first/STORED/no-extra/exact).
|
|
200
|
+
2. Open the fixed file in **Adobe Digital Editions** — it uses the same RMSDK
|
|
201
|
+
engine as Kobo, so if it opens and styles correctly there, Kobo will too.
|
|
202
|
+
This is the single highest-value manual test.
|
|
203
|
+
3. Optionally run EPUBCheck (`--epubcheck`) for container/structural validity.
|
|
204
|
+
4. Sideload to an actual Kobo for final confirmation.
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## Checking mode (`--check`) — the Kobo readiness linter
|
|
209
|
+
|
|
210
|
+
`kobofix --check book.epub` makes **no changes**. It reports every RMSDK landmine
|
|
211
|
+
with a stable rule id and a `file:line` location (correct even inside `<style>`
|
|
212
|
+
blocks and `style=""` attributes), and — if you point it at an EPUBCheck jar —
|
|
213
|
+
runs EPUBCheck too and prints **one combined report**: spec conformance from the
|
|
214
|
+
validator you already trust, plus the Kobo/RMSDK layer EPUBCheck can't see.
|
|
215
|
+
|
|
216
|
+
```
|
|
217
|
+
python kobofix.py --check book.epub --epubcheck path\to\epubcheck.jar
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
If `--epubcheck` is given with no value, kobofix auto-discovers an `epubcheck`
|
|
221
|
+
on `PATH` or a bundled `tools/**/epubcheck.jar`. Exit codes: **2** if any
|
|
222
|
+
book-breaking issue (a `KOBO-001` function or `KOBO-000` packaging error, or an
|
|
223
|
+
EPUBCheck error/fatal), **1** with `--strict` if only warnings, else **0** —
|
|
224
|
+
so it drops straight into CI. Use `--report json` for machine output.
|
|
225
|
+
|
|
226
|
+
### Rule ids
|
|
227
|
+
|
|
228
|
+
| Rule | Sev. | What |
|
|
229
|
+
|---|---|---|
|
|
230
|
+
| `KOBO-000` | error | `mimetype` packaging wrong (the literal "corrupted" cause) |
|
|
231
|
+
| `KOBO-001` | error | `calc/min/max/clamp/var/env` — drops the whole stylesheet / fails to open |
|
|
232
|
+
| `KOBO-002` | warning | viewport unit `vw/vh/vmin/vmax` (in a margin: blank-screen crash) |
|
|
233
|
+
| `KOBO-003` | warning | empty `@media`/`@supports` block (crashes old RMSDK) |
|
|
234
|
+
| `KOBO-004` | warning | `rem` (rendered as `em` → wrong sizes) |
|
|
235
|
+
| `KOBO-010..019` | warn/info | flexbox, grid, position, transform, animation, object-fit, aspect-ratio, `:has()`, `:is()/:where()`, writing-mode |
|
|
236
|
+
|
|
237
|
+
## Why not just add this to EPUBCheck?
|
|
238
|
+
|
|
239
|
+
Short answer: **you can't, and you shouldn't have to.** This was researched
|
|
240
|
+
against EPUBCheck's actual source and issue history:
|
|
241
|
+
|
|
242
|
+
- **Upstreaming is out of scope.** EPUBCheck is the *spec-conformance* checker.
|
|
243
|
+
Valid EPUB3 CSS like `calc()` is **not** a conformance violation, and the
|
|
244
|
+
maintainers have repeatedly closed CSS-lint requests as out of scope
|
|
245
|
+
([publ-cg#69](https://github.com/w3c/publ-cg/issues/69),
|
|
246
|
+
[#935](https://github.com/w3c/epubcheck/issues/935),
|
|
247
|
+
[#149](https://github.com/w3c/epubcheck/issues/149)). In publ-cg#69 — which
|
|
248
|
+
raises this *exact* "old SDK drops the stylesheet on `calc()`/`var()`" problem —
|
|
249
|
+
the lead maintainer's recommendation was to use **user-side severity overrides
|
|
250
|
+
or a separate tool**. The only CSS warning ever accepted (`position:fixed`)
|
|
251
|
+
landed solely because the EPUB CSS Profile itself flags it.
|
|
252
|
+
- **`--customMessages` can't add a rule.** It only re-maps the severity/text of
|
|
253
|
+
*existing* message ids; there is no `calc()`-used id to re-map.
|
|
254
|
+
- **A fork is possible but costly.** EPUBCheck has no plugin API, so new checks
|
|
255
|
+
mean editing the Java (`CSSHandler.declaration()`), adding `KOBO-*` message
|
|
256
|
+
ids, and rebuilding the jar — which needs a JDK + Maven, perpetual rebasing on
|
|
257
|
+
upstream, and a rename (BSD-3-Clause forbids shipping it as "EPUBCheck").
|
|
258
|
+
EPUBCheck 5.x also requires Java 11+ to run, so a runnable-on-older-readers
|
|
259
|
+
build means forking the **4.2.6** tag (which, conveniently, is the version
|
|
260
|
+
Kobo itself runs).
|
|
261
|
+
|
|
262
|
+
So `kobofix --check` *is* the integration: it wraps your real, unmodified
|
|
263
|
+
EPUBCheck and adds the Kobo layer beside it — exactly the workflow EPUBCheck's
|
|
264
|
+
own maintainer recommended. (A source fork that emits native `KOBO-001/002/003`
|
|
265
|
+
messages inside EPUBCheck's stream is fully specced in `epubcheck-fork-notes.md`
|
|
266
|
+
if a pipeline ever needs that instead.)
|
|
267
|
+
|
|
268
|
+
## Known limitations
|
|
269
|
+
|
|
270
|
+
- **Heuristic value substitution.** Reducing `clamp()`/mixed `calc()`/viewport
|
|
271
|
+
units to one static value is inherently lossy. Defaults are sensible
|
|
272
|
+
(`16px`/rem, `600×800` viewport) and tunable, but check any
|
|
273
|
+
layout-critical sizes. All such reductions are listed in the report.
|
|
274
|
+
- **Custom-property scope.** Variables are resolved with a global "last
|
|
275
|
+
definition wins" map. Books that redefine the same `--name` differently in
|
|
276
|
+
different selectors (rare in practice; almost all ebook CSS defines them once
|
|
277
|
+
in `:root`) may resolve to the wrong value — review the report.
|
|
278
|
+
- **Layout modules are reported, not converted** (see above).
|
|
279
|
+
- **DRM / font obfuscation.** The tool does not decrypt DRM'd books and does not
|
|
280
|
+
touch `encryption.xml`; run it on un-DRM'd EPUBs you have the right to edit.
|
|
281
|
+
- **It targets the RMSDK reflowable path** (sideloaded `.epub` on Kobo / ADE),
|
|
282
|
+
which is the one that breaks. It does not generate Kobo's `.kepub.epub`.
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## Sources
|
|
287
|
+
|
|
288
|
+
The behavior rules are drawn from (and were re-verified against) these:
|
|
289
|
+
|
|
290
|
+
- André Klein — *Your EPUB is fine, Kobo disagrees — blame Adobe*
|
|
291
|
+
- dvschultz/99problems #53 — *Legacy RMSDK will ignore the entire stylesheet if you use `calc()`*
|
|
292
|
+
- Jiminy Panoz — *Five interesting facts about Adobe legacy eBook RMSDK* (`calc()` whole-sheet drop, `rem`-as-`em`, empty `@media` crash)
|
|
293
|
+
- Readium CSS docs — `CSS21-epub_compat`, `CSS07-variables`
|
|
294
|
+
- J-Novel Club forum — *Blank epub on Kobo?* (`margin:50vh` crash)
|
|
295
|
+
- EPUB OCF 3.x / W3C EPUB 3.3 — `mimetype` ZIP packaging rules
|
kobofix-1.0.0/README.md
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
# kobofix
|
|
2
|
+
|
|
3
|
+
Make an EPUB that **passes `epubcheck` but won't open (or renders broken) on a Kobo**
|
|
4
|
+
work on a Kobo. Pure Python, standard library only — no `pip install`.
|
|
5
|
+
|
|
6
|
+
This is the problem described in
|
|
7
|
+
[*"Your EPUB is fine, Kobo disagrees — blame Adobe"*](https://andreklein.net/your-epub-is-fine-kobo-disagrees-blame-adobe/),
|
|
8
|
+
generalized to every known cause, not just the one line in that post.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Three ways to use it
|
|
13
|
+
|
|
14
|
+
- **Web app (no install):** [`web/`](web/) is a 100%-client-side version for
|
|
15
|
+
GitHub Pages — drag in an `.epub`, see what breaks on Kobo, download the fixed
|
|
16
|
+
file. The book never leaves the browser. See [web/README.md](web/README.md).
|
|
17
|
+
- **CLI fixer:** `python kobofix.py book.epub` → repaired EPUB.
|
|
18
|
+
- **CLI linter:** `python kobofix.py --check book.epub` → report, optionally
|
|
19
|
+
merged with EPUBCheck.
|
|
20
|
+
|
|
21
|
+
A worked demo on a *real* book: [`make_modern_alice.py`](make_modern_alice.py)
|
|
22
|
+
takes real Project Gutenberg *Alice* content + the modern stylesheet a
|
|
23
|
+
contemporary authoring tool emits → EPUBCheck passes it clean, kobofix flags 11
|
|
24
|
+
book-breaking errors and repairs them.
|
|
25
|
+
|
|
26
|
+
## Why your "valid" EPUB fails on Kobo
|
|
27
|
+
|
|
28
|
+
Kobo renders sideloaded `.epub` files through Adobe's **legacy RMSDK** engine,
|
|
29
|
+
whose CSS parser is frozen around 2013. Two independent things bite you:
|
|
30
|
+
|
|
31
|
+
### 1. RMSDK has no CSS fault tolerance
|
|
32
|
+
|
|
33
|
+
A normal browser, on hitting one CSS declaration it doesn't understand, drops
|
|
34
|
+
*that one declaration* and keeps the rest. RMSDK does the opposite: a single
|
|
35
|
+
**value-function token it can't parse — `calc()`, `min()`, `max()`, `clamp()`,
|
|
36
|
+
`var()`, `env()` — makes it throw away the *entire stylesheet***, and on some
|
|
37
|
+
firmware **refuse to open the book** ("this book is corrupted").
|
|
38
|
+
|
|
39
|
+
The critical consequence: **a fallback declaration placed *before* the modern
|
|
40
|
+
one does not help** — the whole sheet (fallback included) is discarded. The
|
|
41
|
+
modern construct has to be *removed*, not shadowed. That's the core thing
|
|
42
|
+
`kobofix` does.
|
|
43
|
+
|
|
44
|
+
```css
|
|
45
|
+
/* This whole stylesheet is discarded by RMSDK because of one token: */
|
|
46
|
+
.copyright img { max-width: min(150px, 30vw); }
|
|
47
|
+
|
|
48
|
+
/* kobofix rewrites it to: */
|
|
49
|
+
.copyright img { max-width: 150px; }
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### 2. "Corrupted" is often a packaging bug, not CSS
|
|
53
|
+
|
|
54
|
+
The most common literal-"corrupted" cause is the EPUB's ZIP packaging: the
|
|
55
|
+
`mimetype` entry must be **first**, **uncompressed (STORED)**, with **no extra
|
|
56
|
+
field** and the **exact bytes** `application/epub+zip`. Many tools (some
|
|
57
|
+
calibre save paths, Windows "Send to → compressed folder", naive zip libraries)
|
|
58
|
+
violate this and Kobo is the strictest mainstream reader about it. `kobofix`
|
|
59
|
+
**always re-emits a spec-correct OCF ZIP**, so it fixes this even when no CSS
|
|
60
|
+
needed changing.
|
|
61
|
+
|
|
62
|
+
`epubcheck` catches neither problem class reliably: the CSS is valid CSS4 so it
|
|
63
|
+
passes, and older `epubcheck` didn't flag a compressed `mimetype`. Passing
|
|
64
|
+
`epubcheck` is **not** a signal of Kobo/RMSDK compatibility.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Install
|
|
69
|
+
|
|
70
|
+
Nothing to install. You need Python 3.7+ (tested on 3.12).
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
python kobofix.py --version
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Usage
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
python kobofix.py INPUT.epub [-o OUTPUT.epub] [options]
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Default output is `INPUT.kobofixed.epub` next to the input. The original is
|
|
83
|
+
never modified.
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# Fix a book
|
|
87
|
+
python kobofix.py mybook.epub
|
|
88
|
+
|
|
89
|
+
# Choose the output name
|
|
90
|
+
python kobofix.py mybook.epub -o mybook-kobo.epub
|
|
91
|
+
|
|
92
|
+
# Just analyze — write nothing, print the report
|
|
93
|
+
python kobofix.py mybook.epub --dry-run
|
|
94
|
+
|
|
95
|
+
# LINT mode: report Kobo/RMSDK issues (KOBO-* ids + line numbers) WITHOUT changing
|
|
96
|
+
# anything, and merge in EPUBCheck's spec report for one combined readout.
|
|
97
|
+
python kobofix.py --check mybook.epub --epubcheck path\to\epubcheck.jar
|
|
98
|
+
|
|
99
|
+
# Machine-readable report
|
|
100
|
+
python kobofix.py mybook.epub --report json --report-file report.json
|
|
101
|
+
|
|
102
|
+
# Also run EPUBCheck on the result (you supply the jar; Java must be on PATH)
|
|
103
|
+
python kobofix.py mybook.epub --epubcheck path\to\epubcheck.jar
|
|
104
|
+
|
|
105
|
+
# Prove the engine works on your machine
|
|
106
|
+
python kobofix.py --selftest
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
You can also pass an **already-extracted EPUB folder** as the input; the tool
|
|
110
|
+
will read it and produce a correctly-packaged `.epub`.
|
|
111
|
+
|
|
112
|
+
### Options
|
|
113
|
+
|
|
114
|
+
| Option | Default | Meaning |
|
|
115
|
+
|---|---|---|
|
|
116
|
+
| `-o, --output PATH` | `<input>.kobofixed.epub` | Output file |
|
|
117
|
+
| `--check` | off | Lint only: report `KOBO-*` issues + line numbers, merge EPUBCheck; write nothing |
|
|
118
|
+
| `--dry-run` | off | Analyze and report only; write nothing |
|
|
119
|
+
| `--report {text,json}` | `text` | Report format |
|
|
120
|
+
| `--report-file PATH` | — | Also write the report to a file |
|
|
121
|
+
| `--no-rem` | rem→px on | Don't convert `rem` to `px` |
|
|
122
|
+
| `--root-font-size PX` | `16` | Pixel value of `1rem` for the conversion |
|
|
123
|
+
| `--no-viewport` | vw/vh→px on | Don't convert standalone `vw/vh/vmin/vmax` |
|
|
124
|
+
| `--vw-base PX` | `600` | Assumed reader viewport **width** for `vw` |
|
|
125
|
+
| `--vh-base PX` | `800` | Assumed reader viewport **height** for `vh` |
|
|
126
|
+
| `--clamp-pick {min,pref,max}` | `pref` | Which `clamp()` term to keep when none is an absolute length |
|
|
127
|
+
| `--epubcheck [PATH]` | off | Run EPUBCheck after building (`epubcheck`/`*.jar`, or no value to auto-detect) |
|
|
128
|
+
| `--strict` | off | Exit non-zero if any manual-review warnings were found |
|
|
129
|
+
|
|
130
|
+
Exit codes: `0` clean · `1` `--strict` with warnings · `2` output failed its
|
|
131
|
+
own packaging self-check.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## What it fixes automatically
|
|
136
|
+
|
|
137
|
+
These are the changes that make Kobo **open and style** the book. Every one
|
|
138
|
+
leaves **zero** residual modern-value tokens in RMSDK-visible CSS.
|
|
139
|
+
|
|
140
|
+
| Construct | Action |
|
|
141
|
+
|---|---|
|
|
142
|
+
| `var(--x)` / `--x:` | Resolved to the literal value and inlined; the `--x` declarations are deleted |
|
|
143
|
+
| `calc(...)` | Evaluated when units allow (`calc(2em + 1em)`→`3em`); mixed-unit (`calc(100% - 20px)`) keeps the first term and is flagged |
|
|
144
|
+
| `min(a,b)` / `max(a,b)` | Reduced to the absolute-length term (`min(150px,30vw)`→`150px`) |
|
|
145
|
+
| `clamp(a,b,c)` | Reduced to one static size (prefers an absolute term; else `--clamp-pick`) |
|
|
146
|
+
| `env(x, fallback)` | Replaced with the fallback (or removed) |
|
|
147
|
+
| `rem` | Converted to `px` (RMSDK mis-treats `rem` as `em`, compounding sizes) |
|
|
148
|
+
| `vw/vh/vmin/vmax` | Converted to `px` against the assumed viewport (verified crash: `margin:50vh`) |
|
|
149
|
+
| empty `@media {}` / `@supports {}` | Removed (crashes old RMSDK) |
|
|
150
|
+
| `mimetype` packaging | Rewritten first / STORED / no-extra / exact bytes |
|
|
151
|
+
|
|
152
|
+
All of the above are applied inside standalone `.css` files **and** inside
|
|
153
|
+
`<style>` blocks and `style="..."` attributes in XHTML.
|
|
154
|
+
|
|
155
|
+
## What it flags but does NOT change
|
|
156
|
+
|
|
157
|
+
These have no safe automatic equivalent — converting them risks silently
|
|
158
|
+
reflowing your book, so `kobofix` reports them (with file and line) and leaves
|
|
159
|
+
the decision to you:
|
|
160
|
+
|
|
161
|
+
- **flexbox** and **grid** (and grid sub-properties)
|
|
162
|
+
- **`position: absolute/fixed/sticky`** in reflowable content
|
|
163
|
+
- **`transform`**, **`transition`/`animation`/`@keyframes`**
|
|
164
|
+
- **`object-fit`**, **`aspect-ratio`**
|
|
165
|
+
- **`:has()`**, **`:is()`/`:where()`**
|
|
166
|
+
- **`writing-mode`** — *kept on purpose*; Kobo supports it for vertical CJK text
|
|
167
|
+
|
|
168
|
+
The typical fix for flex/grid is to wrap the modern rules in
|
|
169
|
+
`@supports (display:flex) { … }` (legacy RMSDK skips the whole block) with a
|
|
170
|
+
plain block/float fallback outside it.
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## How to verify the result
|
|
175
|
+
|
|
176
|
+
1. `kobofix` self-checks its own output (mimetype first/STORED/no-extra/exact).
|
|
177
|
+
2. Open the fixed file in **Adobe Digital Editions** — it uses the same RMSDK
|
|
178
|
+
engine as Kobo, so if it opens and styles correctly there, Kobo will too.
|
|
179
|
+
This is the single highest-value manual test.
|
|
180
|
+
3. Optionally run EPUBCheck (`--epubcheck`) for container/structural validity.
|
|
181
|
+
4. Sideload to an actual Kobo for final confirmation.
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Checking mode (`--check`) — the Kobo readiness linter
|
|
186
|
+
|
|
187
|
+
`kobofix --check book.epub` makes **no changes**. It reports every RMSDK landmine
|
|
188
|
+
with a stable rule id and a `file:line` location (correct even inside `<style>`
|
|
189
|
+
blocks and `style=""` attributes), and — if you point it at an EPUBCheck jar —
|
|
190
|
+
runs EPUBCheck too and prints **one combined report**: spec conformance from the
|
|
191
|
+
validator you already trust, plus the Kobo/RMSDK layer EPUBCheck can't see.
|
|
192
|
+
|
|
193
|
+
```
|
|
194
|
+
python kobofix.py --check book.epub --epubcheck path\to\epubcheck.jar
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
If `--epubcheck` is given with no value, kobofix auto-discovers an `epubcheck`
|
|
198
|
+
on `PATH` or a bundled `tools/**/epubcheck.jar`. Exit codes: **2** if any
|
|
199
|
+
book-breaking issue (a `KOBO-001` function or `KOBO-000` packaging error, or an
|
|
200
|
+
EPUBCheck error/fatal), **1** with `--strict` if only warnings, else **0** —
|
|
201
|
+
so it drops straight into CI. Use `--report json` for machine output.
|
|
202
|
+
|
|
203
|
+
### Rule ids
|
|
204
|
+
|
|
205
|
+
| Rule | Sev. | What |
|
|
206
|
+
|---|---|---|
|
|
207
|
+
| `KOBO-000` | error | `mimetype` packaging wrong (the literal "corrupted" cause) |
|
|
208
|
+
| `KOBO-001` | error | `calc/min/max/clamp/var/env` — drops the whole stylesheet / fails to open |
|
|
209
|
+
| `KOBO-002` | warning | viewport unit `vw/vh/vmin/vmax` (in a margin: blank-screen crash) |
|
|
210
|
+
| `KOBO-003` | warning | empty `@media`/`@supports` block (crashes old RMSDK) |
|
|
211
|
+
| `KOBO-004` | warning | `rem` (rendered as `em` → wrong sizes) |
|
|
212
|
+
| `KOBO-010..019` | warn/info | flexbox, grid, position, transform, animation, object-fit, aspect-ratio, `:has()`, `:is()/:where()`, writing-mode |
|
|
213
|
+
|
|
214
|
+
## Why not just add this to EPUBCheck?
|
|
215
|
+
|
|
216
|
+
Short answer: **you can't, and you shouldn't have to.** This was researched
|
|
217
|
+
against EPUBCheck's actual source and issue history:
|
|
218
|
+
|
|
219
|
+
- **Upstreaming is out of scope.** EPUBCheck is the *spec-conformance* checker.
|
|
220
|
+
Valid EPUB3 CSS like `calc()` is **not** a conformance violation, and the
|
|
221
|
+
maintainers have repeatedly closed CSS-lint requests as out of scope
|
|
222
|
+
([publ-cg#69](https://github.com/w3c/publ-cg/issues/69),
|
|
223
|
+
[#935](https://github.com/w3c/epubcheck/issues/935),
|
|
224
|
+
[#149](https://github.com/w3c/epubcheck/issues/149)). In publ-cg#69 — which
|
|
225
|
+
raises this *exact* "old SDK drops the stylesheet on `calc()`/`var()`" problem —
|
|
226
|
+
the lead maintainer's recommendation was to use **user-side severity overrides
|
|
227
|
+
or a separate tool**. The only CSS warning ever accepted (`position:fixed`)
|
|
228
|
+
landed solely because the EPUB CSS Profile itself flags it.
|
|
229
|
+
- **`--customMessages` can't add a rule.** It only re-maps the severity/text of
|
|
230
|
+
*existing* message ids; there is no `calc()`-used id to re-map.
|
|
231
|
+
- **A fork is possible but costly.** EPUBCheck has no plugin API, so new checks
|
|
232
|
+
mean editing the Java (`CSSHandler.declaration()`), adding `KOBO-*` message
|
|
233
|
+
ids, and rebuilding the jar — which needs a JDK + Maven, perpetual rebasing on
|
|
234
|
+
upstream, and a rename (BSD-3-Clause forbids shipping it as "EPUBCheck").
|
|
235
|
+
EPUBCheck 5.x also requires Java 11+ to run, so a runnable-on-older-readers
|
|
236
|
+
build means forking the **4.2.6** tag (which, conveniently, is the version
|
|
237
|
+
Kobo itself runs).
|
|
238
|
+
|
|
239
|
+
So `kobofix --check` *is* the integration: it wraps your real, unmodified
|
|
240
|
+
EPUBCheck and adds the Kobo layer beside it — exactly the workflow EPUBCheck's
|
|
241
|
+
own maintainer recommended. (A source fork that emits native `KOBO-001/002/003`
|
|
242
|
+
messages inside EPUBCheck's stream is fully specced in `epubcheck-fork-notes.md`
|
|
243
|
+
if a pipeline ever needs that instead.)
|
|
244
|
+
|
|
245
|
+
## Known limitations
|
|
246
|
+
|
|
247
|
+
- **Heuristic value substitution.** Reducing `clamp()`/mixed `calc()`/viewport
|
|
248
|
+
units to one static value is inherently lossy. Defaults are sensible
|
|
249
|
+
(`16px`/rem, `600×800` viewport) and tunable, but check any
|
|
250
|
+
layout-critical sizes. All such reductions are listed in the report.
|
|
251
|
+
- **Custom-property scope.** Variables are resolved with a global "last
|
|
252
|
+
definition wins" map. Books that redefine the same `--name` differently in
|
|
253
|
+
different selectors (rare in practice; almost all ebook CSS defines them once
|
|
254
|
+
in `:root`) may resolve to the wrong value — review the report.
|
|
255
|
+
- **Layout modules are reported, not converted** (see above).
|
|
256
|
+
- **DRM / font obfuscation.** The tool does not decrypt DRM'd books and does not
|
|
257
|
+
touch `encryption.xml`; run it on un-DRM'd EPUBs you have the right to edit.
|
|
258
|
+
- **It targets the RMSDK reflowable path** (sideloaded `.epub` on Kobo / ADE),
|
|
259
|
+
which is the one that breaks. It does not generate Kobo's `.kepub.epub`.
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## Sources
|
|
264
|
+
|
|
265
|
+
The behavior rules are drawn from (and were re-verified against) these:
|
|
266
|
+
|
|
267
|
+
- André Klein — *Your EPUB is fine, Kobo disagrees — blame Adobe*
|
|
268
|
+
- dvschultz/99problems #53 — *Legacy RMSDK will ignore the entire stylesheet if you use `calc()`*
|
|
269
|
+
- Jiminy Panoz — *Five interesting facts about Adobe legacy eBook RMSDK* (`calc()` whole-sheet drop, `rem`-as-`em`, empty `@media` crash)
|
|
270
|
+
- Readium CSS docs — `CSS21-epub_compat`, `CSS07-variables`
|
|
271
|
+
- J-Novel Club forum — *Blank epub on Kobo?* (`margin:50vh` crash)
|
|
272
|
+
- EPUB OCF 3.x / W3C EPUB 3.3 — `mimetype` ZIP packaging rules
|