"""KEPUB 文件的 XML/XHTML 模板"""
MIMETYPE = "application/epub+zip"
CONTAINER_XML = """\
<?xml version="1.0" encoding="UTF-8"?>
<container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container">
<rootfiles>
<rootfile full-path="OEBPS/content.opf" media-type="application/oebps-package+xml"/>
</rootfiles>
</container>"""
STYLE_CSS = """\
body {
margin: 0;
padding: 0;
}
svg {
width: 100%;
height: 100%;
}"""
def content_opf(
*,
uuid: str,
title: str,
author: str | None,
language: str,
description: str | None,
series_name: str | None,
series_index: float | None,
modified: str,
manifest_items: str,
spine_items: str,
viewport_w: int,
viewport_h: int,
) -> str:
"""生成 content.opf 包文档。"""
meta_extra = ""
if description:
# 转义 XML 特殊字符
desc_escaped = (
description.replace("&", "&")
.replace("<", "<")
.replace(">", ">")
)
meta_extra += f"\n <dc:description>{desc_escaped}</dc:description>"
if series_name:
series_escaped = series_name.replace("&", "&").replace('"', """)
meta_extra += f'\n <meta name="calibre:series" content="{series_escaped}"/>'
if series_index is not None:
meta_extra += (
f'\n <meta name="calibre:series_index" content="{series_index}"/>'
)
author_tag = ""
if author:
author_escaped = author.replace("&", "&").replace("<", "<")
author_tag = f"\n <dc:creator>{author_escaped}</dc:creator>"
return f"""\
<?xml version="1.0" encoding="UTF-8"?>
<package xmlns="http://www.idpf.org/2007/opf" version="3.0"
unique-identifier="BookId"
prefix="rendition: http://www.idpf.org/vocab/rendition/#">
<metadata xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:opf="http://www.idpf.org/2007/opf">
<dc:identifier id="BookId">urn:uuid:{uuid}</dc:identifier>
<dc:title>{title}</dc:title>{author_tag}
<dc:language>{language}</dc:language>
<meta property="dcterms:modified">{modified}</meta>
<meta property="rendition:layout">pre-paginated</meta>
<meta property="rendition:orientation">portrait</meta>
<meta property="rendition:spread">none</meta>{meta_extra}
</metadata>
<manifest>
<item id="ncx" href="toc.ncx" media-type="application/x-dtbncx+xml"/>
<item id="nav" href="nav.xhtml" media-type="application/xhtml+xml" properties="nav"/>
<item id="css" href="style.css" media-type="text/css"/>
{manifest_items}
</manifest>
<spine toc="ncx" page-progression-direction="rtl">
{spine_items}
</spine>
</package>"""
def toc_ncx(*, uuid: str, title: str, nav_points: str) -> str:
"""生成 toc.ncx 导航文件。"""
title_escaped = title.replace("&", "&").replace("<", "<")
return f"""\
<?xml version="1.0" encoding="UTF-8"?>
<ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" version="2005-1">
<head>
<meta name="dtb:uid" content="urn:uuid:{uuid}"/>
<meta name="dtb:depth" content="1"/>
<meta name="dtb:totalPageCount" content="0"/>
<meta name="dtb:maxPageNumber" content="0"/>
</head>
<docTitle><text>{title_escaped}</text></docTitle>
<navMap>
{nav_points}
</navMap>
</ncx>"""
def nav_xhtml(*, title: str, nav_items: str) -> str:
"""生成 EPUB3 nav.xhtml 导航文档。"""
title_escaped = title.replace("&", "&").replace("<", "<")
return f"""\
<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:epub="http://www.idpf.org/2007/ops">
<head>
<title>{title_escaped}</title>
</head>
<body>
<nav epub:type="toc">
<ol>
{nav_items}
</ol>
</nav>
</body>
</html>"""
def page_xhtml(
*, page_num: int, image_filename: str, viewport_w: int, viewport_h: int
) -> str:
"""生成单页 XHTML(固定布局,SVG 包裹图片)。"""
return f"""\
<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:epub="http://www.idpf.org/2007/ops">
<head>
<title>Page {page_num}</title>
<meta name="viewport" content="width={viewport_w}, height={viewport_h}"/>
<link rel="stylesheet" type="text/css" href="../style.css"/>
</head>
<body>
<div>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="100%" height="100%"
viewBox="0 0 {viewport_w} {viewport_h}">
<image width="{viewport_w}" height="{viewport_h}"
xlink:href="../Images/{image_filename}"/>
</svg>
</div>
<span id="kobo.{page_num}.1" class="koboSpan"></span>
</body>
</html>"""