5

用 Markdown 写一本自己的电子书吧(一)手动篇

 2 years ago
source link: https://blog.krimeshu.com/2021/11/28/build-your-ebook-with-markdown-1/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

用 Markdown 写一本自己的电子书吧(一)手动篇

发表于

2021-11-28 更新于 2021-11-29 分类于 Study

不知道大家平时有没有阅读电子书的习惯,这里指的并不是 .txt 的文本文档,而是通常带有精美封面、便捷目录、图文并茂的 .epub 电子书。它是怎样实现这些效果的呢?我们能不能把自己平时用 Markdown 写的技术笔记、博客文章做成一本属于自己的电子书呢?

EPUB 格式是什么

其实做 Web 开发的同学,如果把 .epub 文件通过 zip 打开后就会发现,其实它并不神秘,反而相当开放直观和熟悉──其内在就是一堆 xhtml 页面、css 样式、图片,以及描述这些资源关系的 xml 配置信息,把它们一起打个 zip 包就是 .epub 电子书了。

在我们解压出来的文件,往往会有一个 .opf 文件,内容开头一般是:

<?xml version="1.0"  encoding="UTF-8"?>
<package xmlns="http://www.idpf.org/2007/opf" ...>
<!-- ... -->
</package>

我们只要访问命名空间属性中的这个 http://www.idpf.org/2007/opf 链接,就可以查询到关于这个 OPF 电子书的所有规范描述了。

简单来说,这就是一个由国际数字出版论坛和 W3C 组织一起完成的开放电子书标准,在 2007 年 9 月取代了之前的 Open eBook,被国际数字出版论坛选为新的正式标准。

既然 epub 内部就是 html 页面,我们的 Markdown 文章也能编译成 html,那我们写个工具将以往的文档处理成符合 epub 标准的文件包,不就可以做一本自己的电子书了?

开始动手:手动篇

1. 创建电子书

1-1. 基本结构

我们先创建一个 example 目录,其中包含 META-INFEPUB 两个子目录。然后在现有目录结构中创建 mimetype, META-INF/container.xmlEPUB/package.opf 文件:

example
├── EPUB
│ └── package.opf
├── META-INF
│ └── container.xml
└── mimetype

文件 mimetype 内容:

application/epub+zip

文件 META-INF/container.xml 内容:

<?xml version="1.0" encoding="utf-8" standalone="no"?>
<container xmlns="urn:oasis:names:tc:opendocument:xmlns:container" version="1.0">
<rootfiles>
<rootfile full-path="EPUB/package.opf" media-type="application/oebps-package+xml"/>
</rootfiles>
</container>

文件 EPUB/package.opf 内容:

<?xml version="1.0" encoding="utf-8" standalone="no"?>
<package xmlns="http://www.idpf.org/2007/opf"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:dcterms="http://purl.org/dc/terms/"
version="3.0"
xml:lang="en"
unique-identifier="pub-identifier">
<metadata>
<dc:identifier id="pub-identifier">kepub:20211120:000000001</dc:identifier>
<dc:title id="pub-title">Example Book</dc:title>
<dc:language id="pub-language">en</dc:language>
<dc:date>2021-11-20</dc:date>
<meta property="dcterms:modified">2021-11-20T14:50:00Z</meta>
</metadata>
<manifest>
<!-- TODO -->
</manifest>
<spine>
<!-- TODO -->
</spine>
</package>

以上就是我们的 .epub 文件中最基础的三个文件。

其中 package.opf 中,我们在 package > metadata 内定义了一些 .epub 必备的元信息。以后我们向电子书添加内容时,还需要根据实际情况继续更新其中 package > manifest 资源清单package > spine 书脊 的相关信息。

1-2. 添加页面

接下来就是向其中添加内容了。

在之前的基础上,我们再创建一个 EPUB/book 目录,在其中添加一个 EPUB/book/page-1.xhtml 文件:

example
├── EPUB
│ ├── book
│ │ └── page-1.xhtml
│ └── package.opf
├── META-INF
│ └── container.xml
└── mimetype

文件 EPUB/book/page-1.xhtml 内容:

<?xml version="1.0" encoding="utf-8" standalone="no"?>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:epub="http://www.idpf.org/2007/ops"
xml:lang="en"
lang="en">
<head>
<title>Page 1</title>
</head>
<body>
<h1>Hello world!</h1>
</body>
</html>

然后修改 package.opf 中的资源清单和书脊:

<manifest>
<item id="page-1" href="book/page-1.xhtml" media-type="application/xhtml+xml"/>
</manifest>
<spine>
<itemref idref="page-1"/>
</spine>

对于刚才的页面,我们创建了 package > manifest > item 条目,标记了它的 [media-type] 类型,并且设定了一个 [id="page-1"] 属性,将其以 itemref[idref="page-1"] 的形式在书脊内进行了引用。

此时,如果将 example 目录的内容进行 zip 打包,生成文件名称改为 example.epub,就已经可以在一些 epub 阅读器中正常打开进行阅读了。但部分基于导航目录进行内容索引的阅读器(比如 微信读书)还无法正常浏览,需要再做一点小小的改动。

1-3. 导航目录

我们再创建一个 EPUB/toc.xhtml,内容:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:epub="http://www.idpf.org/2007/ops"
xml:lang="en"
lang="en">
<head>
<title>TOC</title>
</head>
<body>
<nav epub:type="toc" id="toc">
<h1>Table of Contents</h1>
<ol>
<li>
<a href="book-page1.xhtml">Page 1</a>
</li>
</ol>
</nav>
</body>
</html>

注意其中的 nav[epub:type="toc"],这是 epub3 与 epub2 的区别之一,可以将目录页面的部分作为书籍的导航目录,不再需要单独提供 .ncx 文件。

同样的,继续修改 package.opf 的资源清单和书脊:

<manifest>
<item id="htmltoc" href="toc.xhtml" media-type="application/xhtml+xml" properties="nav"/>
<item id="page-1" href="book/page-1.xhtml" media-type="application/xhtml+xml"/>
</manifest>
<spine>
<itemref idref="htmltoc"/>
<itemref idref="page-1"/>
</spine>

其中 item#htmldoc 添加了 [properties="nav"] 表示这个页面是导航目录。

这次,再将 example 目录内容打包为 example.epub 后,就能在大部分阅读器内都正常打开了。

1-4. 封面页

我们还可以给自己的电子书添加一个好看的封面,比如:

cover.jpg

将其保存为 EPUB/images/cover.jpg,然后创建 EPUB/cover.xhtml,内容为:

<?xml version="1.0" encoding="utf-8" standalone="no"?>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:epub="http://www.idpf.org/2007/ops"
xml:lang="en"
lang="en">
<head>
<title>Cover</title>
<style type="text/css">
img {
max-width: 100%;
}
</style>
</head>
<body>
<figure id="cover-image">
<img src="images/cover.jpg"
alt="Book Cover" />
</figure>
</body>
</html>

老规矩,继续更新 package.opf 的资源清单和书脊:

<manifest>
<item id="htmltoc" href="toc.xhtml" media-type="application/xhtml+xml" properties="nav"/>
<item id="cover" href="cover.xhtml" media-type="application/xhtml+xml"/>
<item id="cover-image" href="images/cover.jpg" media-type="image/jpeg" properties="cover-image"/>
<item id="page-1" href="book/page-1.xhtml" media-type="application/xhtml+xml"/>
</manifest>
<spine>
<itemref idref="cover" linear="no"/>
<itemref idref="htmltoc"/>
<itemref idref="page-1"/>
</spine>

这次,增加的图片文件也需要登记在 package > manifest 资源清单内,并且添加了 properties="cover-image" 属性,将其标记为书籍的封面图片。

而创建的 cover.xhtml 文件,是为了让我们在打开书籍后,也能在内容内看到封面的效果。

1-5. 其它

如果我们需要在电子书内,添加更多页面、引用更多图片、添加装饰样式、改用自定义字体,也是相同的操作:

  • 将资源添加到项目内。
  • 更新 package.opf 内的 manifest 资源清单。
  • 如果是页面,再添加对应 [id] 引用到 spine 书脊内,并且更新 toc.xhtml 内的 nav 记录。
    • toc.xhtml 内的 nav 导航目录,支持 ol > li > ol > li ... 嵌套实现多级目录。
    • 部分阅读器不支持 nav 导航目录使用 ul 无须列表。
    • 如果需要为导航索引型阅读器提供页面引用,又不想让对应记录出现在目录内;或者重复引用于不同位置的相同页面记录,可以为对应 ol / li 设置 [hidden=""] 属性,进行隐藏处理。

2. 自动流程准备

基于上面的原理,我们已经能够开始手动编写我们的电子书了。

不过这个过程中还有很多手动操作并不便捷的步骤,比如 每篇文章进行 Markdown to Html 转化、文章中所有图片添加到资源清单、更新文章目录结构 ,如果文章页面、引用资源稍微多一些,就基本没法手动处理过来了。

所以我们需要更高效的自动处理方案。

2-1. 自动的与手动的

刚才提到的资源清单内容,大致可以分为两类:

  • 文章的页面文件。
  • 文章内引用的图片资源

其中,后者可以直接在 Markdown 文档渲染成 html 文件后,进行 html 解析再对所有 img 标签进行汇总即可得出配置列表。

前者可以基于 .md 文件本身的目录结构进行资源列表的整合,但是 对于页面在书脊和导航目录内的顺序 无法进行很好的控制。

如果基于文件名进行排序,相当于引入了一套不可控的潜规则,对于书籍迁移、页面删减维护都不太方便。而且如果需要处理导航目录内隐藏、重新引用的场景,还要引入更复杂的潜规则。

不如增加一个简化的 .json 配置文件,统一管理页面在导航目录内的顺序和层级关系。

2-2. 新的电子书结构

我们重新创建一个 new-book 目录,并在其中创建一些子目录和文件:

new-book
├── chapter-1
│ ├── index.md
│ └── ep-1.md
└── book.json

文件 book.json 内容:

{
"meta": {
"id": "kepub:20211120:000000001",
"title": "Example Book",
"lang": "en",
"date": "2021-11-20",
"modified": "2021-11-20T14:50:00Z"
},
"pages": [
{
"title": "Chapter.01",
"file": "chapter-1/index.md",
"children": [
{
"title": "Episode.01",
"file": "chapter-1/ep-1.md",
"hidden": true
}
]
}
]
}

文件 chapter-1/index.md 内容:

Hello world!

文件 chapter-1/ep-1.md 内容:

This is episode 1.

这样,我们日常创建一本电子书时,真正需要自己定制的内容基本就已收录其中。而且能方便地在其中定义和调整页面的顺序和层级关系,控制对应条目是否在导航内隐藏了。

接下来,我们只需要再编写一些脚本,将上面的结构自动转化成电子书需要的 mimetype, container.xml, package.opf 和各种页面文件,并且汇总对应的资源清单、书脊和导航目录。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK