diff --git a/packages/app-expo/assets/reader/reader.html b/packages/app-expo/assets/reader/reader.html
index 30bac870a..2efb14da8 100644
--- a/packages/app-expo/assets/reader/reader.html
+++ b/packages/app-expo/assets/reader/reader.html
@@ -4725,8 +4725,8 @@
diff --git a/packages/foliate-js/epub.js b/packages/foliate-js/epub.js
index e52c7ecdf..ead4a634a 100644
--- a/packages/foliate-js/epub.js
+++ b/packages/foliate-js/epub.js
@@ -1109,6 +1109,13 @@ class Loader {
el.setAttribute(attr, await this.loadHref(el.getAttribute(attr), href, parents));
const replaceSrcset = async (el, attr) =>
el.setAttribute(attr, await this.replaceSrcset(el.getAttribute(attr), href, parents));
+ // remember whether the document originally linked any external
+ // stylesheet; used below for the malformed-book fallback
+ const stylesheetHrefs = new Set(
+ Array.from(doc.querySelectorAll('link[rel~="stylesheet" i]'), (el) =>
+ el.getAttribute("href"),
+ ),
+ );
for (const el of doc.querySelectorAll("link[href]")) await replace(el, "href");
for (const el of doc.querySelectorAll("[src]")) await replace(el, "src");
for (const el of doc.querySelectorAll("[srcset]")) await replaceSrcset(el, "srcset");
@@ -1131,6 +1138,23 @@ class Loader {
if (el.textContent) el.textContent = await this.replaceCSS(el.textContent, href, parents);
for (const el of doc.querySelectorAll("[style]"))
el.setAttribute("style", await this.replaceCSS(el.getAttribute("style"), href, parents));
+ // Malformed-book fallback: if the document has no
+ // at all, inject every CSS file declared in the package manifest (in
+ // manifest order) so the book's styling is still applied.
+ if (stylesheetHrefs.size === 0) {
+ const head = doc.head;
+ if (head) {
+ for (const cssItem of this.manifest) {
+ if (cssItem.mediaType !== MIME.CSS) continue;
+ const url = await this.loadItem(cssItem, parents.concat(href));
+ if (!url) continue;
+ const link = doc.createElementNS(NS.XHTML, "link");
+ link.rel = "stylesheet";
+ link.href = url;
+ head.appendChild(link);
+ }
+ }
+ }
// TODO: replace inline scripts? probably not worth the trouble
const result = new XMLSerializer().serializeToString(doc);
return this.createURL(href, result, item.mediaType, parent);