~cytrogen/kobo-manga

ref: 4e504823f4bf8d2b5f4279da3f4d4ebe98fc97ad kobo-manga/src/kobo_manga/converter/templates.py -rw-r--r-- 4.6 KiB
4e504823 — HallowDem Initial commit: kobo-manga pipeline a day ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
"""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("&", "&amp;")
            .replace("<", "&lt;")
            .replace(">", "&gt;")
        )
        meta_extra += f"\n    <dc:description>{desc_escaped}</dc:description>"
    if series_name:
        series_escaped = series_name.replace("&", "&amp;").replace('"', "&quot;")
        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("&", "&amp;").replace("<", "&lt;")
        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("&", "&amp;").replace("<", "&lt;")
    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("&", "&amp;").replace("<", "&lt;")
    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>"""