天道酬勤

epub 文件转pdf

前言

目前从其它地方下载到了epub资料文件,想打印出来,但打印需要pdf,这个记录一下简单的转换过程。

epub文件格式

epub是开放式电子书格式,简单理解就是用网页技术包装的zip压缩包,具体格式如下

example.epub
├── mimetype                # 固定内容 application/epub+zip,必须存在
├── META-INF/
│   └── container.xml       # 入口文件,指向主 OPF 文件的位置
└── OEBPS/                  # 通常叫 OEBPS,存放所有内容
    ├── content.opf         # 核心清单文件(元数据 + 文件列表 + 阅读顺序)
    ├── toc.ncx / toc.xhtml # 目录文件(EPUB 2 用 ncx,EPUB 3 用 xhtml)
    ├── chapter1.xhtml      # 正文内容(XHTML/HTML5)
    ├── chapter2.xhtml
    ├── styles.css          # 样式表
    └── images/             # 图片等资源

这里简单的方法就是读取 opf文件,解析 <spine> 和 <manifest> ,得到按照顺序排列的XHTML文件路径

然后读取xhtml,从中截图需要的内容,最终输入到pdf中

VersOne.Epub

自己读取opf文件,需要处理各种关系,这里可以通过类库 VersOne.Epub来自动处理zip解压、OPF、NCX解析等任务。

using var epubBook = EpubReader.OpenBook(epubFilePath);
// epubBook.Title    标题
// epubBook.Author 作者
// epubBook.Description 简介

下一步就是解析整个epub文件的读取顺序, 通过 GetNavigation方法 获取整个的顺序,这里要注意 它的NestItems 要进行递归处理。

foreach (var navigationItem in epubBook.GetNavigation())
{
    PrintNavigationItem(navigationItem, 0,document,pdfWriter);
}

public static void PrintNavItem(EpubNavigationItemRef itemRef, int identLevel)
{
    Log.Information(itemRef.Title);

    foreach (EpubNavigationItemRef nestedNavigationItem in itemRef.NestedItems)
    {
        PrintNavItem(nestedNavigationItem, identLevel + 1);
    }
}

顺序找到之后就要要对每个顺序的XHTML文档进行处理。

EpubNavigationItemRef 类有HtmlContentFileRef 属性,通过这个属性来读取对应的XHTML 文件,这样整个顺序和文件都可以了。

 if (navigationItem.HtmlContentFileRef != null)
 {
     bool isToc = navigationItem.Title == "目录";
     string text = navigationItem.HtmlContentFileRef.ReadContent();

     if (isToc) 
     {
         PrintTocContent(text,document);
     }
     else
     {
         PrintContent(text,document,pdfWriter);
     }  
 }

通过 ReadContext 方法,把对应的XHMTL文件整个读取出来,后续就是解析XHTML文件,从中读取 对应的章节和章节内容。

XHTML 文件读取和解析

XHTML文件通过研究,有两个类型,一个是TOC 目录,一个是纯粹内容,其中TOC目录的大体如下:

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops" epub:prefix="z3998: http://www.daisy.org/z3998/2012/vocab/structure/#" lang="zh" xml:lang="zh">
  <head>
    <title>目录</title>
    <link href="stylesheet.css" rel="stylesheet" type="text/css"/>
  </head>
  <body><h1>目录</h1>
<nav epub:type="toc" id="inline-toc">
  <p class="no-indent">点击章节标题可跳转到对应正文位置。</p>
  <h3>第一卷:默认</h3>
  <ol>
    <li><a href="chapter_00001.xhtml">第1章 银子去哪儿了</a></li>
   
    <li><a href="chapter_00571.xhtml">第570章 乱世出枭雄(完)</a></li>
  </ol>
</nav>
  </body>
</html>

具体内容的XHTML 如下:

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops" epub:prefix="z3998: http://www.daisy.org/z3998/2012/vocab/structure/#" lang="zh" xml:lang="zh">
  <head>
    <title>第2章 忽悠抢夺,自入陷阱</title>
    <link href="stylesheet.css" rel="stylesheet" type="text/css"/>
  </head>
  <body><h1>第2章 忽悠抢夺,自入陷阱</h1>
<p>赵弘祖从抽屉拿出一大块卤肉,又拿出两壶酒,还有一碟花生,抬手示意他落座,语气亲热中带着试探,</p>
<p>这小子是真贼,早猜到这是大舅哥骆养性的买卖。</p>
<p>望着消失在后院门口的林威,赵弘祖嘴角露出一丝邪魅,内心暗笑,有点意思,瞌睡来了个枕头,关老头,这可不是我拉你徒弟下水啊,是他自找的。</p>
  </body>
</html>

HtmlAgilityPack解析

这里就用HtmlAgilityPack 通过 xpath来进行进行解析

目录的解析是 在body节点下通过xpath ".//ol/li/a" 获取所有的目录

内容的解析是 在body节点下通过xpath ”.//p" 获取所有的内容

pdf构建

pdf这里通过iTextSharp来构建,目录页面对应pdf暂不需要,这里直接就通过 把 内容解析中的 p标签对应为 iTextsharp的 Paragraph对象即可。

这里再稍微加一个处理,内容解析中还有 h1 标签,这里作为每一章的标题,对应iTextSharp的 Chapter对象,最后就是构建的代码:

public static void PrintContent(string text, Document document, PdfWriter pdfWriter)
{
    HtmlDocument htmlDocument = new();
    htmlDocument.LoadHtml(text);
    HtmlNode bodyNode = htmlDocument.DocumentNode.SelectSingleNode("//body");
    if (bodyNode != null)
    {
        Chapter chapter = null;
        //解析文章的标题,用 Chapter对象存储
        var h1Node = bodyNode.SelectSingleNode("./h1");
        if (h1Node != null)
        {
            chapter = new Chapter(new Paragraph(h1Node.InnerText, titleFont), 1);
            chapter.NumberDepth = 0;
        }

        var pNodes = bodyNode.SelectNodes(".//p");
        if (pNodes != null)
        {
            foreach (var pNode in pNodes)
            {
         //解析文章的具体内容,用 Paragraph 对象存储 Paragraph p
=new Paragraph(pNode.InnerText, commonFont); p.FirstLineIndent = 20f; chapter.Add(p); } } document.Add(chapter); } document.NewPage(); }

pdf字体

一般中文字体是 宋体,这里推荐一个好看的中文字体(霞鹜文楷),效果如下图

eb23a81115ee4597a265028caf83e247

 结尾

最终的转换只是有内容,没有图片和样式的支持,算是一个简化版本,而且pdf的目录也不支持,后续可以参考此文章进行细化。

posted @ 2026-08-06 11:27  JulyLuo  阅读(2)  评论(0)    收藏  举报