PDF文档转换成mobi格式(for kindle),并解决排版问题

PDF文档转换成mobi格式,并解决排版问题

  • 0. 前言
  • 1. 下载和安装calibre
  • 2. PDF导入calibre,并转换为azw3格式
  • 3. 编辑电子书,获取HTML内容和图片
  • 4. 程序处理HTML文档
  • 5. 将HTML文档导入calibre,并转换成azw3格式
  • 6. 编辑azw3文档
  • 7. 将azw3文档转换成mobi格式
  • 8. 附录

0. 前言

正式介绍之前,先回答下面几个问题:
PDF文档转换成mobi格式(for kindle),并解决排版问题_第1张图片

1. 为什么要将PDF转换成mobi?
想要将PDF转换成mobi格式,初衷在于想在kindle上面看一些从网上获取到的PDF文档。直接将PDF导入kindle本来也可以,但是效果不是很好——要么竖着看,但是字体很小;要么横着看,字体会大一些,但是总感觉比较别扭,而且PDF的一页需要在kindle上翻3页。
kindle支持azw3、mobi等格式,但是不支持直接将azw3格式的文档直接导入到kindle,所以需要将PDF文档转换成mobi格式

2. 为什么不直接用在线转换工具?
其实网上有很多工具支持将PDF转换成mobi格式,但是效果都很差:

  1. 章节标题和正文内容没有区别;
  2. 正文内容格式混乱,在kindle上看是以PDF的一行进行的分段,行首也没有空格
    PDF文档转换成mobi格式(for kindle),并解决排版问题_第2张图片

3. 将PDF转换成mobi格式,我大概怎么做?
将PDF转换成mobi格式,我主要是借助于calibre工具:
PDF文档转换成mobi格式(for kindle),并解决排版问题_第3张图片

4. 转换效果如何?
转换之后效果如下图,其中对章节标题和段落划分进行了处理:

1. 下载和安装calibre

calibre下载地址:
https://calibre-ebook.com/download

根据自己的系统下载安装即可

2. PDF导入calibre,并转换为azw3格式

打开calibre,点击菜单栏的“添加数据”,选择PDF格式文件,点击“Open”

在calibre的主窗口中选中刚导入的图书,点击菜单栏的“转换书籍”,在弹出的转换窗口,将输出格式选择为“AZW3”,然后点击“确定”。

转换过程可能会持续一小段时间,看calibre主窗口右下角的任务执行结束即转换完成

【注】这里有一个坑,图书转换的设置,目录针对章节有一个默认的最大值,需要根据图书的实际情况进行调整。我一般将两个值分别设置为1000,50
PDF文档转换成mobi格式(for kindle),并解决排版问题_第4张图片

3. 编辑电子书,获取HTML内容和图片

在calibre主窗口选中目标电子书,点击菜单栏的“编辑书籍”打开编辑书籍窗口

在编辑书籍窗口,选中文本下的“part0000.html”,点击右键,选择“导出part0000.html”

同样的方式,全选图片下的所有图片,然后导出

4. 程序处理HTML文档

由上一步可以看到,直接由PDF转换得到的HTML文档,PDF里面的每一行转换之后在HTML文档中都独立成了一段,而且段首也没有空格,章节标题也没有突出展示。

针对这个问题,于是自己写了一段Java代码,主要是根据语义对文档内容进行分段,同时将标题突出,然后将每一张的起始位置独立成页。

具体的代码见文末,里面也有比较详细的注释,有一些特殊情况的处理可以根据自己的要求适当修改代码。
PDF文档转换成mobi格式(for kindle),并解决排版问题_第5张图片

5. 将HTML文档导入calibre,并转换成azw3格式

首先,将calibre中已有的同名电子书删掉,操作方式是:在calibre主窗口,选中要删除的电子书,右键,选择“移除书籍” > “移除选定书籍”,之后在弹出窗口做二次确认
PDF文档转换成mobi格式(for kindle),并解决排版问题_第6张图片
然后,将处理之后的HTML文档导入到calibre,操作方式同第2章。

最后,由于直接导入的HTML文档是ZIP格式,不能直接进行编辑,所以需要再次将文档转换成azw3格式,操作方式同第3章。可以看到,导入HTML文档之后,图书的封面丢失了,可以在转换的时候,直接修改封面图(可以用第3章导出保存的图片),如下图:

6. 编辑azw3文档

这一步是非必须的,但是如果电子书中有插图,那么就需要在这一步进行补充。另外也可以在文件预览中检查各个HTML文件,如有必要,也可以进行简单编辑。

进入编辑操作同第3章所介绍,这里主要介绍一下补充图片。

在编辑书籍窗口,点击菜单栏的“新增文件”按钮。

然后在弹出框选择“导入文件”,选择需要导入的图片。注意,文件的路径最终要是“images/xx”格式。

最后点击“确定”。

如果有多张图片,则重复上面的操作。目前calibre不支持批量导入。

编辑完成之后,点击编辑书籍窗口菜单栏的“保存”按钮进行保存。

7. 将azw3文档转换成mobi格式

在calibre主窗口,选中电子书,点击“转换书籍”,在弹出窗口中,输入格式选择“AZW3”
,输出格式选择“MOBI”。点击“确定”。然后等转换任务结束。

转换结束之后,在calibre主窗口,选中电子书,在右侧边栏,点击“点击打开”即可获取到mobi格式的电子书。然后将其共享至kindle就可以了。

8. 附录

推荐的PDF电子书网址:
http://www.80pdf.com/
(大家有其他好的电子书链接也欢迎评论区共享。)

HTML格式文档处理代码:

package com.amwalle.walle.util;

import org.springframework.util.StringUtils;

import java.io.*;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class PDFConverter {

    public static void main(String[] args) throws IOException {
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入要转换的文件路径: ");
        String filePath = scanner.nextLine();
        System.out.println(filePath);
        convert(filePath);
    }

    public enum HeaderLevel {
        Part("H1", ".*第.*部分.*"),
        Chapter("H2", "(.*第.*章.*)|(.*[1-9].*)");

        private String headerLevel;
        private String expression;

        HeaderLevel(String headerLevel, String expression) {
            this.headerLevel = headerLevel;
            this.expression = expression;
        }

        public static String getHeaderLevel(String input) {
            if (StringUtils.isEmpty(input) || (!input.contains("") && !input.contains("

"))) { return null; } String dummy = preHandleHeader(input); for (HeaderLevel level : HeaderLevel.values()) { Pattern pattern = Pattern.compile(level.expression); Matcher matcher = pattern.matcher(dummy); if (matcher.find()) { return level.headerLevel; } } return null; } private static String preHandleHeader(String input) { String dummy = input; if (input.contains("") && input.contains("

"
)) { dummy = dummy.substring(dummy.indexOf("") + 4, dummy.indexOf("

"
)); } else if (input.contains("

") && input.contains("

"
)) { dummy = dummy.substring(dummy.indexOf("

") + 20, dummy.indexOf("

"
)); } dummy = dummy.replaceAll(" ", ""); dummy = dummy.replaceAll("", ""); dummy = dummy.replaceAll(" ", ""); dummy = dummy.replaceAll("", ""); dummy = removeSpace(dummy); return dummy; } public String getHeaderLevel() { return headerLevel; } public void setHeaderLevel(String headerLevel) { this.headerLevel = headerLevel; } public String getExpression() { return expression; } public void setExpression(String expression) { this.expression = expression; } } public static void convert(String filePath) throws IOException { File file = new File(filePath); if (!file.isFile() || !file.canRead() || !file.getName().endsWith(".html")) { System.out.println("请输入正确的html文件路径,并保证文件可读!"); } String title = file.getName(); title = title.replace(".html", ""); File outFile = new File(filePath.replace(".html", "1.html")); if (outFile.exists()) { outFile.delete(); outFile.createNewFile(); } try (Scanner scanner = new Scanner(file, "UTF-8"); BufferedWriter outWriter = new BufferedWriter(new FileWriter(outFile))) { Set<String> catalog = new HashSet<>(); StringBuffer output = new StringBuffer(); boolean isIndentationNeeded = false; int count = 0; while (scanner.hasNextLine()) { count++; // 有些文件太大,需要在中间存一下文件 if (count >= 10000) { outWriter.append(output); count = 0; output.delete(0, output.length()); } String line = scanner.nextLine(); // 目录前面的内容(封面、版权等) if (catalog.isEmpty() && !line.startsWith("

)) { output.append(line).append("\n"); continue; } // 处理目录:目录数据存set用于后续增加标题格式 if (line.endsWith("

"
)) { if (catalog.isEmpty()) { output.delete(output.length() - 1, output.length()); String header = output.toString(); String catalogTitle = header.substring(header.lastIndexOf("\n") + 1); // 格式化目录标题,增加目录前的分页 if (catalogTitle.contains("目录") || catalogTitle.contains("Contents")) { catalogTitle = "

" + catalogTitle + "

"
+ "\n"; output.delete(header.lastIndexOf("\n") + 1, output.length()); output.append(addPagination(title)); output.append(catalogTitle); } else { output.append("\n"); output.append(addPagination(title)); } } line = line.replaceAll("part0000\\.html", ""); output.append(line).append("\n"); String temp = line.substring(line.lastIndexOf("\">") + 2, line.indexOf("

"
)); temp = removeSpace(temp); catalog.add(temp); continue; } if (StringUtils.isEmpty(line)) { output.append("\n"); continue; } // 处理章节标题 if (isLineATitle(line, catalog)) { isIndentationNeeded = true; String titleLevel = HeaderLevel.getHeaderLevel(line); if (HeaderLevel.Part.headerLevel.equals(titleLevel)) { output.append(addPagination(title)); output.append("

").append(line).append("

"
).append("\n"); continue; } if (HeaderLevel.Chapter.headerLevel.equals(titleLevel)) { output.append(addPagination(title)); output.append("

").append(line).append("

"
).append("\n"); continue; } output.append("

").append(line).append("

"
).append("\n"); continue; } // 首行缩进 if (isIndentationNeeded) { line = line.replace("

", "

"); line = line.replace("

"
, ""); output.append(line).append("\n"); isIndentationNeeded = false; continue; } // 文本内容分段 if (isParagraphEnd(line)) { isIndentationNeeded = true; line = line.replace("

", ""); line = line.replace("

"
, ""); output.append(line).append("\n"); continue; } // 普通行处理:去掉换行 if (line.startsWith("

") && line.endsWith("

"
)) { line = line.replace("

", ""); line = line.replace("

"
, ""); output.append(line); continue; } output.append(line).append("\n"); } outWriter.append(output); System.out.println("********************************************"); System.out.println("Done! 生成的新文件路径: "); System.out.println(outFile.getPath()); } catch (FileNotFoundException e) { System.out.println("File convert failed!"); e.printStackTrace(); } } private static String removeSpace(String input) { String result = input.trim().replaceAll("\\.", "").replaceAll("·", ""); result = result.replaceAll("\\s*", "").replaceAll("\\u00a0", "").replaceAll((char) 12288 + "", ""); return result; } private static String addPagination(String title) { return "\n" + "\n" + "\n" + "\n" + "\n" + ""</span> <span class="token operator">+</span> title <span class="token operator">+</span> <span class="token string">"\n" + " \n" + " \n" + "\n" + "\n" + "\n"; } private static boolean isLineATitle(String input, Set<String> catalog) { if (StringUtils.isEmpty(input) || (!input.contains("") && !input.contains("

"))) { return false; } String dummy = HeaderLevel.preHandleHeader(input); for (String catalogEntry : catalog) { if (StringUtils.isEmpty(catalogEntry)) { continue; } if (catalogEntry.equals(dummy)) { return true; } } return false; } private static boolean isParagraphEnd(String input) { Pattern pattern = Pattern.compile(".*(\\w

)$"
); Matcher matcher = pattern.matcher(input); if (matcher.matches()) { return false; } // 句子结尾的标点符号:./!/?/……/"」 (英文+中文) Pattern pattern1 = Pattern.compile(".*(([\\.|!|\\?|\"|\\u3002|\\uff01|\\uff1f|\\u2026|\\u201d|\\u300d])|(\\. ))

$"
); Matcher matcher1 = pattern1.matcher(input); return matcher1.matches(); } }

你可能感兴趣的:(Tools,java,kindle,PDF转mobi,kindle导入PDF格式美化)