#markdown #book #pdf #author #epub #format #produce

bin+lib bookbinder

从 markdown 生成各种格式的书籍,具备对结构语义和渲染选项的一些理解

1 个不稳定版本

0.1.0 2020年10月23日

#26 in #produce

MIT/Apache

1MB
7.5K SLoC

从 markdown 创建 pdf 或 epub 书籍。

基本示例

use bookbinder::{BookSrc, BookSrcBuilder, create_epub, create_pdf, EpubOptions, LatexOptions};

let src = BookSrcBuilder::new("A Book")
    .author("A.N. Author")
    .add_mainmatter("# Greetings\n\n Hello world...")
    .process();
let epub_options = EpubOptions::default();
let epub = create_epub(src.clone(), epub_options)
    .expect("Error producing epub");

let pdf_options = LatexOptions::default();
let pdf = create_pdf(src, pdf_options)
    .expect("Error producing pdf");

为什么使用这个

从 markdown 创建书籍有许多选择;在 Rust 世界中,有 mdbookcrowbook;还有 Haskell 中的惊人的 Pandoc

但是这些通常会将 Markdown 构造转换为书籍。当它起作用时,这很好,但书籍是复杂的东西。所以,以一个简单的例子来说,一个顶级 markdown 标题 () 可能表示一个部分、一个章节或一个部分,这取决于书籍的结构。然后它可能需要带有标签 (Chapter 1: Header) 或单独作为数字 (IOne1) 进行渲染,或单独作为标题 (Header)...

如果它看起来更像是 ,你将遇到另一组问题:在合适的书中,这些应该以不同的方式显示--例如,前言中的页码可能应该用罗马数字表示,而任何标签都应该指附录,如 Appendix A,而不是 Chapter 23

然后你将遇到其他困难--大多数书籍都将想要一个扉页和一个扉页背页(版权页),并且它们经常包含一些东西,如题词或献词。所有这些都需要特殊的格式化,但你不能无限期地扩展 markdown 来包含它们,否则你不知道自己已经重新创造了 TEI 而没有严格的规范。

这个软件包依赖于这样一个观点:在一个被分成语义角色的文本中,Markdown 是一个理想的选择--你可以说“强调这个文本在题词内”,一切都会好起来,但你不能在 Markdown 本身中说“这个文本是题词”。由于书籍的可能部分不是无限多的,而且像 epub 结构语义词汇表 这样的东西已经定义了一系列的可能选择,因此相对容易设置一个容器,它可以在更复杂的语义系统中渲染 Markdown。

由于其中一些元素,如标题页或版权页,主要是元数据的结果,所以我们进一步扩展功能,使其可以从已包含的元数据自动生成——如果您愿意的话。

换句话说,您可以这样做

let introduction = "This is an introduction...";
let dedication = "This is dedicated to someone";
let foreword_with_custom_heading = "# A peculiar light\n\nForeword goes here...";
let mainmatter = "# Early Life\n\n## I am born\n\nThe day of my birth was a dark cold day...";

let src = BookSrcBuilder::new("A Book") // Start with a title
    .author("A.N. Author")
    .publisher("Publisher Name")
    .add_introduction(introduction, None, "Introduction Author")
    .set_dedication(dedication)
    .add_foreword(foreword_with_custom_heading, None, vec!["First Foreword Author", "Second Foreword Author"])
    .add_mainmatter(mainmatter);

并获得一本带有标题页和版权信息、语义化细节展示以及非常好的美感的书。

上述示例将为您提供半标题、标题页、版权页,其中说明了这是A.N. 作者今年版权并由出版社名称出版的;然后您会得到一系列前言页面,包括带有标题 Introduction 的介绍和带有说明它是序言的标签但标题为 A Peculiar Light 的序言,以及一个未标记的献词,通过其格式(通常是minipage中的斜体文本,但您可以指定其他格式)表明它是一篇献词。(当然,如果您想避免生成版权页等,或提供自己的,这也很容易做到!)

接下来的主体部分会将 作为部分标题处理,将 作为章节标题处理,但如果它们都是顶级标题,则都会被处理为章节(或者如果一个是二级标题,另一个是三级标题,并且没有顶级标题,它们将被处理为章节和部分。)

等等。这很酷。

更好的是,更改这些基本语义结构表示方式很容易——如果您只想忽略那些有标题的章节,并将它们称为 OneTwoThree,您只需设置一个选项即可!如果您想使您的PDF书籍全部使用A4纸,并使用Comic Sans字体,但标题使用Papyrus字体——许多事情都是可能的,但只有一些是明智的。

let options = LatexOptions::default()
    .use_words_for_chapter_labels() // this says 'Number chapters using words!'
    .only_number_chapters() // And actually, while you're about it, don't do anything *but* number chapters
    .set_serif_typeface("Comic Sans") // and set the main text in Comic Sans
    .set_heading_typeface("Papyrus") // and headers in Papyrus
    .set_papersize(PaperSize::A4Paper); // and an A4 book is a good size

只需通过将适用于特定输出格式的选项与通过 BookSrcBuilder 创建的源组合,就可以生成一本书。

BookSrcBuilder 允许您添加各种元数据(这本书由某个人翻译,另一个人做笔记并提供介绍,但作者是另一个人)和书籍元素(这里有介绍,这里有正文,这里有翻译者的注释)。

同时,有一系列选项可以更改此源的处理方式。不同格式的选项不同,因为我们能对不同格式做的事情也不同——在epub没有页码时更改页码没有意义,但在没有封面图的PDF文件上设置封面图像也没有多少意义!

目标

这个库旨在提供一个易于使用的创建格式优美的书籍的方法,这些书籍可以处理相当细粒度的复杂性,但不应该将这种复杂性强加给不需要它的用户。

非常愿意听到有关简化用户体验或缺失功能的反馈!

我们的Markdown

说我们使用简单的Markdown有点夸张——实际上,书籍需要一些CommonMark没有的功能。因此,源字符串或文件使用受Pandoc启发的Markdown方言进行解析

  • 智能地处理直引号,将连字符序列替换为适当的破折号,并使用省略号代替 ...
  • 包括脚注
  • 包括下标和上标

有关详细信息,请参阅 extended_pulldown。顺便说一句,这样做的一个优点是构建管道非常容易 任意输入格式 -> pandoc -> markdown -> bookbinder,这样可以从诸如Word文档之类的文件构建书籍。

技术细节

我们使用自定义解决方案来打包epub,但pdf文件是通过调用XeLaTeX通过latexmk生成的。因此,您需要安装LaTeX才能生成pdf!此外,如果您想在pdf中包含图像,您还需要安装pdftocairo

从架构上讲,这个crate是一个非常薄的包装器,覆盖了

  1. bookbinder_ast,它定义了一个抽象的书源,以及
  2. bookbinder_epubbookbinder_latex,它们定义了如何将该源渲染成特定的输出格式以及渲染的各种选项。

因此,要详细了解某物的工作方式,您最好查看特定的crate!这种分离的设计旨在允许将来添加不同的后端 - 添加创建html书籍的方法将很有趣,并且可能有一种替代方法来生成pdf也可能很有价值,因为LaTeX很漂亮但速度慢,并且安装它是一件大事。最有可能的候选者是neatroffSILE的嵌入版本。

反序列化

这个crate包含两个函数create_pdf_from_jsoncreate_epub_from_json,它们支持从输入json反序列化和渲染,而不是通过手动构建对象来控制流程。

那里使用的json代表了一种简化的格式,它对流程的控制稍微少一点,主要是为了使反序列化更容易、更易于理解。

有关详细信息,请参阅deserialization模块。

依赖关系

~28MB
~351K SLoC