代码具有很强的局限性,并不适合所有的电子书目录。代码生成目录主要是针对于 《啊哈!算法》,其他的目录结构可能生成结果不符合预期。
有些时候我们下载的电子书没有书签,虽然不影响阅读,但是使用体验很差,尤其是需要跳转时。因为之前在项目中也是用过itext用于生成企业的信用报告(图片,目录,水印,锚点),所以相对来说比较熟悉些,看过官网的文档也知道可以实现。然后就在网上找了些demo试着使用itext生成书签,解放双手。
准备
该版本比较简单,局限性特别大,只能识别特定的简单目录结构。
引入依赖
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.11</version>
</dependency>
获取PDF文档的目录
在当当上或者京东等网站上找到对应的书籍,在商品介绍里面一般都会提供书籍的目录信息。复制目录,并对目录做一些小的处理。
- 复制的目录中的空格是中文空格,需要将所有的中文空格修改为英文空格。当然也可以使用中文空格(代码需要修改)
- 为了操作方便,删除了“目录”文本部分。当然也可以对目录页也添加对应的页码关系
- 需要手动的找出文档正文的起始页码。 比如啊哈!算法的正文是第13页。
- 如果想要把目录也放在文件中,需要进行文本转码(utf-8)。Windows记事本的转码的utf8文件实际上是
utf-8-bom
,这种格式是不对的。
啊哈!算法。 文本中带目录对应页码信息的的文本。注意需要手动的找出目录对应的实际页数
目录 11
第1章 一大波数正在靠近——排序 1
第1节 最快最简单的排序——桶排序 2
第2节 邻居好说话——冒泡排序 7
第3节 最常用的排序——快速排序 12
第4节 小哼买书 20
..............................
spring源码解析的目录
第一部分 核心实现
第1章 Spring整体架构和环境搭建 2
1.1 Spring的整体架构 2
1.2 环境搭建 4
1.2.1 安装GitHub 4
1.2.2 安装Gradle 5
1.2.3 下载Spring 6
第2章 容器的基本实现 10
2.1 容器基本用法 10
2.2 功能分析 11
2.3 工程搭建 12
。。。。。。。。。。.。。。。
代码针对了这两种格式的目录文件(嗯,第二种没有测试,可能有问题)。
生成书签
下面以啊哈!算法的书签生成为例
手动的找到正文的实际页码,即“第1章 一大波数正在靠近——排序 1 ”是第13页。
代码
注意需要对目录文件进行utf8转码(utf8-no-bom),utf8转码,utf8转码,utf8转码,utf8转码,可以使用editplus等文本编辑工具来修改编码。Windows记事本的转码是utf-8-with-bom。
不进行转码的话,书签会生成失败。因为下面代码永不执行。因为使用IDE工具会发现,
sectionNumberStr实际上等于"\ufeff目录"。这是因为Microsoft 建议所有的 Unicode 文件应该以 \uFEFF 字符开头,作为标记字节顺序存储的标记。标准的utf8编码是不需要设置 这个字节顺序标记。
else if("目录".equals(sectionNumberStr)){
}
该方式需要传入目录对应的实际页码。
目录 11
第1章 一大波数正在靠近——排序 1
第1节 最快最简单的排序——桶排序 2
第2节 邻居好说话——冒泡排序 7
第3节 最常用的排序——快速排序 12
第4节 小哼买书 20
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.pdf.*;
import java.io.BufferedReader;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class GenerateBookmarkUtil {
private Pattern spacePattern = Pattern.compile("\\s*");
public static void main(String[] args) {
GenerateBookmarkUtil zcc = new GenerateBookmarkUtil();
String sourceDocPath = "E:\\BaiduNetdiskDownload\\啊哈!算法.pdf"; //电子书路径
String sourceTextPath = "C:\\Users\\Administrator\\Desktop\\aha.txt";//生成的目录文本路径
String desFilename = "D:\\testhaha1.pdf"; // 生成的带有目录的文件的路径(问价是复制,不是覆盖)
try {
zcc.createPdf(sourceTextPath, sourceDocPath, desFilename,13);
} catch (DocumentException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
*@param sTxtPath 书签文件的路径
*@param sPdfPath 需要生成书签的文件
*@param filename 生成书签后的文件路径
*@param startPage 正文的实际页数-1
*/
public void createPdf(String sTxtPath, String sPdfPath, String filename,int startPage) throws DocumentException, IOException {
if(startPage>0)
startpage--;
//可以创建新的文件,也可以直接操作源文件,添加书签
// step 1
Document document = new Document();
// step 2 输出文件
PdfCopy writer = new PdfCopy(document, new FileOutputStream(filename)); // step 3
writer.setViewerPreferences(PdfWriter.PageModeUseOutlines);//设置打开pdf文件时显示书签
document.open();
// step 4 逐页读入pdf文件并写入输出文件
PdfReader reader = new PdfReader(sPdfPath);
int n = reader.getNumberOfPages();
for (int page = 1; page <= n; page++) {
writer.addPage(writer.getImportedPage(reader, page));
}
writer.freeReader(reader);
// step 5 添加书签
PdfOutline root = writer.getRootOutline();
PdfAction action;//标识书签点击后的跳转动作,通过它设置跳转的页码
try {
//读入保存书签的TXT文件,分拆为书签名及跳转页码;
BufferedReader bufRead = new BufferedReader(new FileReader(sTxtPath));
String str;
String[] ss = null;
int skipPage = startPage; //正文书签跳过的页数
/*数据的存储结构
*
* page 页码
*title 标题
* kids 子书签 page,title ,kids
*/
List<Map<String, Object>> outlines = new ArrayList<>();//存放解析的数据
String pageNumStr = null;
int pageNum = 0;
//遍历读取目录文件,每次读取一行,并解析出该行的标题和页码值
while ((str = bufRead.readLine()) != null) {
String title = "";
int parentPage = 0;
String childTitle = "";
int childPage = 0;
str = str.trim();
//过滤空行
Matcher matcher = spacePattern.matcher(str);
if (matcher.matches()) {
continue;
}
//获取页码
ss = str.split(" ");
pageNumStr = ss[ss.length - 1].trim();
//不包含页码的标题
for (int j = 0; j < ss.length - 1; j++) {
title += ss[j] + " ";
}
if (pageNumStr.matches("\\d+")) {//存在页码
try {
pageNum = Integer.valueOf(pageNumStr);
pageNum += skipPage;
} catch (Exception e) {
}
String category="目录";
String sectionNumberStr = ss[0]; //获取标题的序号
if (sectionNumberStr.contains("章")) {//章节
List<Map<String, Object>> kids = null;
kids = new ArrayList();
Map<String, Object> chapterMap = new HashMap<>();
chapterMap.put("title", title);
chapterMap.put("page", pageNum);
chapterMap.put("kids", kids);
outlines.add(chapterMap);
}else if("目录".equals(sectionNumberStr)){
//目录书签
new PdfOutline(root,
PdfAction.gotoLocalPage(pageNum-skipPage, new PdfDestination(PdfDestination.FIT), writer), "目 录");
}else {//小节
List<Map<String, Object>> kids = (List<Map<String, Object>>) outlines.get(outlines.size() - 1).get("kids");//将小节添追加到自书签列表
Map<String, Object> littleChapter = new HashMap<>();
littleChapter.put("title", title);
littleChapter.put("page", pageNum);
littleChapter.put("kids", new ArrayList<>());
kids.add(littleChapter);
}
} else {//不存在页码,可能是大标题 或者目录 例如:第一部分 核心实现
Map<String, Object> bigSection = new HashMap<>();
bigSection.put("title", title);
bigSection.put("page", -1);
bigSection.put("kids", new ArrayList<>());
outlines.add(bigSection);
}
}
//数据遍历,添加书签
outlines.forEach(map -> {
String sectionTitle = (String) map.get("title");
int page = (int) map.get("page");
List<Map<String, Object>> kids = (List<Map<String, Object>>) map.get("kids");
PdfOutline sectionOutline = null;
if (page < 0) {//该部分是大类别
int parentPage = (int) kids.get(0).get("page") - 1; //获取大分类的页数,一般就是子目录页数减一
PdfAction action1 = PdfAction.gotoLocalPage(parentPage,
new PdfDestination(PdfDestination.FIT), writer);//设置书签动作
sectionOutline = new PdfOutline(root, action1, sectionTitle, false); //大分类的
} else {
PdfAction action1 = PdfAction.gotoLocalPage(page,
new PdfDestination(PdfDestination.FIT), writer);//设置书签动作
sectionOutline = new PdfOutline(root, action1, sectionTitle, false); //一级章节标题;
}
for (Map<String, Object> kid : kids) { //一级章节
String firstTitle = (String) kid.get("title");
int firstPage = (int) kid.get("page");
List<Map<String, Object>> firstKids = (List<Map<String, Object>>) kid.get("kids");
PdfAction action2 = PdfAction.gotoLocalPage(firstPage,
new PdfDestination(PdfDestination.FIT), writer);//设置书签动作
PdfOutline firstOutline = new PdfOutline(sectionOutline, action2, firstTitle, false); //一级标题的outline
for (Map<String, Object> secondKid : firstKids) {//二级章节
String secondTitle = (String) kid.get("title");
int secondPage = (int) kid.get("page");
List<Map<String, Object>> secondKids = (List<Map<String, Object>>) kid.get("kids");
PdfAction action3 = PdfAction.gotoLocalPage(secondPage,
new PdfDestination(PdfDestination.FIT), writer);//设置书签动作
PdfOutline secondOutline = new PdfOutline(firstOutline, action2, secondTitle, false); //二级标题的outline
}
}
});
} catch (IOException ioe) {
}
document.close();
}
}
生成好的书签。代码的局限性比较高,可以自定义自己的书签类