IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

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

    krimeshu发表于 2023-08-27 05:13:46
    love 0

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

    背景

    EPUB 格式是什么

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

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

    1
    2
    3
    4
    <?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-INF 和 EPUB 两个子目录。然后在现有目录结构中创建 mimetype, META-INF/container.xml 和 EPUB/package.opf 文件:

    1
    2
    3
    4
    5
    6
    example
    ├── EPUB
    │ └── package.opf
    ├── META-INF
    │ └── container.xml
    └── mimetype

    文件 mimetype 内容:

    1
    application/epub+zip

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

    1
    2
    3
    4
    5
    6
    <?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 内容:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    <?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 文件:

    1
    2
    3
    4
    5
    6
    7
    8
    example
    ├── EPUB
    │ ├── book
    │ │ └── page-1.xhtml
    │ └── package.opf
    ├── META-INF
    │ └── container.xml
    └── mimetype

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <?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 中的资源清单和书脊:

    1
    2
    3
    4
    5
    6
    <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,内容:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <?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 的资源清单和书脊:

    1
    2
    3
    4
    5
    6
    7
    8
    <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. 封面页

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <?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 的资源清单和书脊:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <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 目录,并在其中创建一些子目录和文件:

    1
    2
    3
    4
    5
    new-book
    ├── chapter-1
    │ ├── index.md
    │ └── ep-1.md
    └── book.json

    文件 book.json 内容:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    {
    "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": [
    {
    "file": "chapter-1/ep-1.md",
    "hidden": true
    }
    ]
    }
    ]
    }

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

    1
    2
    3
    # Chapter.01

    Hello world!

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

    1
    2
    3
    # Episode.01

    This is episode 1.

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

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



沪ICP备19023445号-2号
友情链接