電子書的規範
由於公司有開發電子書的需求,遂希望有一個便於製作電子書的平台,功能不外乎有匯入文章、編輯文章、編輯目錄與書籍資訊⋯⋯等,最後『一鍵匯出電子書』。功能本身不難,但要先理解電子書的架構,確保符合電子書規範;以前未接觸過這領域,所以簡單紀錄一下學習內容。
電子書的架構
一本電子書 epub 主要由三個規範組成,分別是 OCF、OPF 與 OPS:
OCF (Open Container Format)以 zip 封裝所有檔案,完成一個 epub 容器。
OPF (Open Packaging Format)以 xml 描述 epub 的檔案結構,連結各種資源。
OPS (Open Publication Structure)以 xhtml 建構電子書內容,確保內容一致性。
總結來說,一個 epub 先遵守最底層的 OPS 規範來製作內容,接著透過 OPF 規範組織內容,最後以 OCF 規範完成封裝,形成一個 epub 檔。
也由於 epub 遵循 OCF 規範以 zip 封裝,因此也可以反封裝 epub,得到下面的檔案結構:
- META-INF
- container.xml
- OEBPS
- content.opf
- toc.ncx
- xxx.xhtml
- mimetype
提醒
mimetype 與 container.xml 為 OCF 所規範,檔名與檔案位置皆不可改。
content.opf 為 OPF 所規範,檔名與檔案位置未嚴格限制,但多數電子書開發工具習慣以此命名,並與 OPS 所規範的檔案合併在 OEBPS (Open eBook Publication Structure) 目錄。
mimetype
mimetype 對應 OCF 規範,主要用來宣告電子書的檔案類型,內容僅有一行:
application/epub+zip
注意
mimetype 檔名不可更改,且內容不能包含空格與其他空白行。
container.xml
container.xml 同樣對應於 OCF 規範,是電子書閱讀軟體優先讀取的檔案,主要紀錄 opf 檔的位置,引導電子書閱讀軟體讀取 opf 檔,進而獲取 OPS 內容( opf 檔主要描述這本電子書包含哪些受 OPS 規範的內容)。檔案內容如下:
<?xml version="1.0"?>
<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>
基本上,唯一會改的可能只有 <rootfile> 的 full-path 這個屬性,要注意的是其路徑是相對於整個電子書的專案頂層,而不是相對於 META-INF 目錄。
content.opf
content.opf 顧名思義,對應的是 OPF 規範,主要紀錄電子書內容資源的位置,指引電子書閱讀軟體找到屬於 OPS 所規範的檔案。內容大致如下:
<?xml version='1.0' encoding='utf-8'?>
<package version="2.0"
xmlns="http://www.idpf.org/2007/opf"
xmlns:dc="http://purl.org/dc/elements/1.1/"
unique-identifier="book_id">
<metadata>
<dc:title>...</dc:title>
<dc:identifier id="book_id">...</dc:identifier>
</metadata>
<manifest>
<item id="ncx" href="toc.ncx"
media-type="application/x-dtbncx+xml"/>
<item id="ch1" href="ch1.xhtml"
media-type="application/xhtml+xml"/>
...
</manifest>
<spine toc="ncx">
<itemref idref="ch1"/>
...
</spine>
</package>
<package> schemas & namespace
epub 是由國際數位出版論壇 (International Digital Publishing Forum, IDPF) 所提出的電子書格式,因此須引用 http://www.idpf.org/2007/opf 為命名空間;此外在描述 <metadata> 時也引用了 Dublin Core 的元素集,因此也須加入 http://purl.org/dc/elements/1.1/。
<metadata>
Dublin Core 定義一組常用項目來描述多種數位內容(不單為了電子書),epub 引入了這些項目,因此可在 <metadata> 中定義:
title 及 identifier 為必要項目,依照規範 identifier 必須是唯一值,出版商一般會使用含 ISBN 在內的一組字串;其他書籍創造者則使用一組隨機產生的 UUID 填寫。
提示
<dc:identifier> 的 id 須對應 <package> 的 unique-identifier。
<manifest>
電子書的『內容』一般是編輯在 xhtml 裡面,<manifest> 負責指出所有在電子書專案中與這些內容相關的檔案。要注意的是:除了 xhtml 本身之外,該檔案所引入的 css、image⋯⋯等樣式等、媒體檔,也要列入裡面。
提示
每個檔案都必須賦予一個 id,並標記該檔案的位置 (href) 與媒體類型 (media-type),幫助閱讀軟體能夠找到並打開該檔案。
除了與內容相關的檔案之外,<manifest> 還會列出一個與 metadata 相關的檔案:toc.ncx。
<spine>
條列完這本電子書有哪些內容資源後,接下來要想辦法組織、編排,便在 <spine> 處理,告訴閱讀軟體這些內容資源的讀取順序,也就是我們讀者的閱讀順序。
提示
<spine> 的 toc 必須與 <manifest> 中指向 toc.ncx 的 <item> 的 id 一致。
每個 <itemref> 都要有一個 idref,同樣要與 <manifest> 中所對應的 <item> 的 id 一致。
toc.ncx
僅管前述的 content.opf 已經定義了大多數 metadata,但 epub 還同時借用了 DAISY 的 NCX (Navigation Center eXtended) DTD 來定義書籍的目錄表格,以應付複雜的書籍目錄 (例如分層章節這種巢狀目錄結構)。大致內容如下:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE ncx PUBLIC "-//NISO//DTD ncx 2005-1//EN" "http://www.daisy.org/z3986/2005/ncx-2005-1.dtd">
<ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" version="2005-1">
<head>
<meta name="dtb:uid" content="..."/>
<meta name="dtb:depth" content="1"/>
<meta name="dtb:totalPageCount" content="0"/>
<meta name="dtb:maxPageNumber" content="0"/>
</head>
<docTitle>
<text>...</text>
</docTitle>
<navMap>
<navPoint id="navpoint-1" playOrder="1">
<navLabel><text>Chapter 1</text></navLabel>
<content src="ch1.xhtml"/>
</navPoint>
...
</navMap>
</ncx>
<meta>
toc.ncx 須在 <head> 加入 4 個 <meta> 元素:
uid為電子書的唯一ID,應與 content.opf 中的dc:identifer一致。depth為目錄層級。totalPageCount與maxPageNumber主要應用於紙本書籍,因此在電子書這邊可設為 0 就好。
<docTitle>
電子書的書籍名稱,應與 content.opf 中的 dc:title 一致。
<navMap>
主要描述電子書內容的順序,是 ncx 檔中最重要的一個部分。它包含了多個 <navPoint> 元素,每個 navPoint 相當於目錄節點,其 <navLabel> 底下的 <text> 內容即節點標題,而 <content> 則對應該節點所連結的檔案資源。
說明
ncx 的 <navMap> 與 opf 的 <spine> 皆描述了電子書內容的順序,然而實際上有很大的差異,較好的解讀方式是用紙本書類比:
opf
<spine>的描述相當於紙本書的『裝訂』,將書的各章節『實際』裝訂在一起。例如將第一章與第二章綁定,那麼在閱讀到第一章的最後一頁時,翻下一頁即會接續閱讀第二章的第一頁。ncx
<navMap>的描述則是書籍的目錄,目錄所顯示的編排順序未必等同於實際的裝訂順序。今天也許你點了目錄的第一篇文章,實際上卻跳到了書籍的最後一篇;明明目錄顯示後面還有文章,卻無法透過翻下一頁跳轉,只能點回目錄重新選下一篇文章跳轉。
xxx.html
在電子書內容的編輯方面,主要以 xhtml 為主,其開發方式與 html 類似,大致內容如下:
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>BOOK TITLE</title>
<link type="text/css" rel="stylesheet" href="stylesheet.css" />
</head>
<body>
<!-- 將書籍內容以 html 元素呈現 -->
</body>
</html>
不過 epub 雖沒有對 xhtml 有特定規範,但與一般網頁開發相比還是有些要注意的地方,例如:
使用圖片時盡可能參考本地端圖檔,避免從外部連結引入,否則當閱讀裝置連不到網路時,電子書的圖片便無法呈現。
epub 3.0 雖已可執行 javascript,但 epub 並未要求每個裝置都必須能夠支援執行 js,因此非必要時仍盡量避免使用 script 區域。
epub 並非完全支援所有 css 的效果等。
封裝電子書
將所有內容都開發好後,透過 zip 指令封裝,就可以完成一本 epub 電子書了。
$ zip -0 -X xxx.epub mimetype
$ zip -9 -r xxx.epub */
注意
封裝時,mimetype 必須是 zip 內的第一個檔案,且本身不可被壓縮。
流式版型 (Float Layout)
前面紀錄了電子書的規範跟架構,接下來紀錄下電子書中幾個常用的版型,最常見的就是——流式版型。
隨著視窗流動
流式版型的特色,就是讓電子書像網頁一般,在不同螢幕大小的裝置讓文字重新編排,以最適於該裝置的形式提供閱讀。因此,同樣一篇文章,在電腦螢幕可能只有四十行,每行三十個字;到了手機螢幕卻變成有八十行,每行則只有十五個字,以適應手機螢幕寬度較窄但高度較高的閱讀方式。
甚至,有些裝置的閱讀器還會提供調整字體大小的功能,即使是不同的字體大小,流式版型也會重新調整文字排列,不會讓文章超出版面。
那麼…
要怎麼設定讓電子書成為流式版型呢?
什麼都不用做 (喂)
預設就是流式版型
電子書的格式之所以被提出,就是為了讓書本內容在不同電子裝置上也能被舒適地閱讀;OPS 規範中明確定義書的內容必須以 xhtml 建構,以確保內容在各裝置的一致性。因此,我們並不需要做什麼特別的設定,要的僅僅是對一些例外情況做 CSS 調整:
比如說⋯⋯ 中文小說的『竪排右書』排版
向左走向右走
儘管在網頁的世界中,大部分中文網頁都已經是『橫排左書』的排版方式,我們也都已經習慣『由左至右、由上而下』的閱讀順序;但在中文書籍中(特別是小說類書籍)仍有多數內容使用『竪排右書』、也就是『由上而下、由右至左』的閱讀順序排版。
為了讓內容在電子書上面以竪排右書的方式呈現,我們需要對 CSS 進行一些設定。epub 3 支援 CSS 3,令許多原本不可能的事情一下成真——例如改變文字的排列方向,調整 CSS 設定的writing-model:
@namespace "http://www.w3.org/1999/xhtml";
@namespace epub "http://www.idpf.org/2007/ops";
html {
-ms-writing-mode: tb-rl;
-epub-writing-mode: tb-rl;
-webkit-writing-mode: tb-rl;
writing-mode: tb-rl;
}
提示
t、b、r、l 即 top、bottom、right、left。
提醒
在設定 writing-model 時,必須特別注意『連號前後的順序』,tb-rl 與 rl-tb 代表的是完全不同的意思:
tb-rl 為『由上至下,再由右至左』,是中文小說的常見排版。
rl-tb 為『由右至左,再由上至下』,會變成從右邊開始書寫,完成一個『橫排』之後再寫下一個橫排的奇妙版型。
眼睛往哪看
文字排列的方向不同,連帶影響的是閱讀方向也不同,當然書籍頁面的方向也會不同。一般來說,橫排左書的書籍頁面習慣由左至右,上一頁在左邊,下一頁在右邊,翻頁時是由右往左翻頁;竪排右書的書籍 (或是其他如圖畫書、漫畫等) 則相反,頁面習慣由右至左,下一頁在上一頁的左邊,翻頁時則是由左至右翻頁。
如何設置電子書的頁面方向?
只要在 OEBPS/content.opf 檔案中,編輯 <spine> 的 page-progression-direction 屬性為 ltr(由左至右,預設)或 rtl(由右至左)即可。
...
<spine toc="ncx" page-progression-direction="rtl">
<itemref idref="cover" linear="no"/>
<itemref idref="ch1"/>
...
</spine>
...
其他的小設定
在一本書籍當中,封面是重要的一環,許多人會將封面設置在 <metadata> 中,一些閱讀器也會從這邊抓取封面,以此作為在閱讀器書籍清單中顯示的封面。
封面頁除了在閱讀器的書籍清單顯示之外,也會作為書籍內容的一部分顯示在書籍當中。但是,讀者在進入書籍內容前,早已在書籍清單中看過封面,實在不需要在翻開書後再看一次封面,因此可以在 <spine> 中作為封面頁的 <itemref> 設置 liner 屬性為 no,意思為此項目不會出現在讀者的閱讀順序中,讀者進入這本書時將會忽略此項目,直接進入下一個內容。
...
<metadata>
...
<meta name="cover" content="img" />
</metadata>
<manifest>
...
</manifest>
<spine toc=”ncx” page-progression-direction="rtl">
<itemref idref=”cover” linear=”no”/>
...
</spine>
...
提示
<meta> 的 content 須與 <manifest> 中作為封面圖的 <item> 的 id 一致。
固定版型 (Fixed Layout)
流式版型雖然有利於在不同裝置間閱讀,但對於版型的可控性很低,較適合以文字為主的內容;對於一些需高度依賴版型的內容(例如以圖畫為主的圖文書),則需要採用另一種固定版型 (Fixed Layout) 才行。
定住不許動
有些書籍內容(例如圖文書)不像流式版型的內容,適合跟著螢幕的大小跟比例而調動;圖跟文必須緊密相連,才能保留原本要傳達的意義。為了因應這些內容,epub 提供了固定版型的設定,讓創作者能夠更好地發揮。
內容設定
固定版型的內容同樣遵循 OPS 規範,由 xhtml 建構;但與流式版型不同的是,除了 xhtml 之外,固定版型也支援 svg、bitmap images 等組成內容,因此創作者也可將圖文輸出成圖片來呈現。
不論以哪種檔案類型來編輯內容,首先要定義 ICB (initial containing block) dimensions,可看作是定義版型的大小 / 比例,以此來固定版型。
若內容是以 xhtml 建構,須在 <head> 中定義 viewport 的 meta:
<head>
...
<meta name="viewport" content="width=1200, height=600"/>
...
</head>
而若是 svg,則定義其 viewBox:
<svg xmlns="http://www.w3.org/2000/svg"
version="1.1" width="100%" height="100%"
viewBox="0 0 844 1200">
...
</svg>
提示
viewBox 的四個參數分別是 min-x、min-y、width、height。
最後若是 bitmap images,則會以圖片原始寬高來直接設定。
綁定內容順序
完成 OPS 所規範的內容之後,接著是 OPF 的部分,固定版型的 opf 檔與流式版型在設定方面有些許不同:
由於接下來在 <metadata> 中會設定 rendition 屬性,因此在 <package> 中加入 prefix 屬性,並將其對應到 http://www.idpf.org/vocab/rendition/ (當然原先在流式版型中的設定還是要有)。
<package ...
prefix="rendition: http://www.idpf.org/vocab/rendition/#">
<metadata>
...
<meta property="rendition:layout">pre-paginated</meta>
<meta property="rendition:orientation">auto</meta>
<meta property="rendition:spread">auto</meta>
</metadata>
...
<spine toc="ncx" page-progression-direction="rtl">
...
<itemref idref="s4" properties="rendition:page-spread-left"/>
...
</spine>
</package>
接著在 <metadata> 中設定 rendition,共有 layout、orientaion、spread 三個面向:
rendition:layout主要設定書籍內容的分頁版型,有兩種:
reflowable
書籍內容沒有被事先分頁,當螢幕足夠一次呈現兩頁時,系統會自動不斷地將下一頁遞補呈現。
pre-paginated
書籍的部份內容 (spine item) 有被事先分頁,通常適用於一張圖版面過大,不得被拆開成兩頁呈現時,確保這兩頁必須在同一面呈現。
rendition:orientation設定閱讀裝置要以哪種方向開啟書籍內容:
auto
原始設定,依閱讀裝置當前的方向來開啟書籍內容。
landscape
強制閱讀裝置以水平模式開啟書籍內容。
portrait
強制閱讀裝置以垂直模式開啟書籍內容。
rendition:spread設定當閱讀裝置為何種情況時,能夠雙頁顯示:
none
設定裝置在不論何種情況下都只能單頁呈現,不會雙頁顯示。
landscape
閱讀裝置只在水平模式下才會雙頁顯示。
portrait
閱讀裝置只在垂直模式下才會雙頁顯示。
both
不論閱讀裝置在何種情況下都能雙頁顯示。
auto
由閱讀系統自行判斷裝置是否適合雙頁顯示。
編輯目錄
編輯完 opf 之後,這本書基本上就已經成形了,接著提供目錄便大功告成。相較於以文字為主的流式版型,固定版型多是以圖為主,層級也都是一頁一頁的單層結構,不像流式版型得動用到巢狀結構的目錄。
打開 ncx ,一般流式版型是透過支援巢狀結構的 <navMap> 來定義目錄:
<ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" version="2005-1">
...
<navMap>
<navPoint id="navpoint-1" playOrder="1">
<navLabel><text>Chapter 1</text></navLabel>
<content src="ch1.xhtml"/>
</navPoint>
...
</navMap>
</ncx>
固定版型則是透過 <pageList> 來定義:
<ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" version="2005-1">
...
<pageList>
<pageTarget type="normal" value="1">
<navLabel><text>1</text></navLabel>
<content src="s1.svg"/>
</pageTarget>
...
</pageList>
</ncx>
提示
在 pageTarget 中,value 指的是該頁的頁數,type 則有三種類型:
- front 通常指目錄頁、前言使用的羅馬數字。
- normal 是一般常用的阿拉伯數字。
- special 指其他特殊數字。
至此,一本固定式版型的電子書完成,最後透過 OCF 規範將檔案以 zip 形式封裝成 epub 後就結束了。
2019 更新
先前開發的 ncx 已是 epub 2 時代的產物,epub 3 改用 Navigation Document 來取代,詳細可參考官方文件。