book-kit 0.0.0-stage → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/NOTICE.md +72 -0
- package/README.md +251 -2
- package/bin/book-kit.js +115 -0
- package/commands/_emoji_io.py +32 -0
- package/commands/audit-chapter-lengths.js +244 -0
- package/commands/check-epub-size.js +68 -0
- package/commands/check-pdf-page-count.js +162 -0
- package/commands/composite-kindle-emoji.py +85 -0
- package/commands/composite-print-emoji.py +98 -0
- package/commands/conventions.js +76 -0
- package/commands/convert-pdfx.sh +194 -0
- package/commands/download-icc-profile.sh +105 -0
- package/commands/fonts.py +212 -0
- package/commands/generate-reference-docx.py +344 -0
- package/commands/kindle-fonts.js +173 -0
- package/commands/links-to-footnotes.js +366 -0
- package/commands/markdownlint.js +25 -0
- package/commands/minify-css.js +43 -0
- package/commands/optimize-images.js +73 -0
- package/commands/parity.sh +150 -0
- package/commands/postprocess-epub.js +541 -0
- package/commands/prep-print-src.js +77 -0
- package/commands/prose.js +38 -0
- package/commands/release-files.js +89 -0
- package/commands/reorder-print-frontmatter.js +79 -0
- package/commands/strip-page-rules.js +1168 -0
- package/commands/strip-property-rules.js +93 -0
- package/commands/update-key-takeaways.js +314 -0
- package/commands/validate.js +283 -0
- package/configs/markdownlint-cli2.jsonc +98 -0
- package/configs/textlintrc.json +22 -0
- package/css/theme/01-tokens.css +83 -0
- package/css/theme/02-fonts.css +79 -0
- package/css/theme/03-prose.css +53 -0
- package/css/theme/04-base.css +226 -0
- package/css/theme/05-components.css +546 -0
- package/css/theme/06-utilities.css +40 -0
- package/css/theme/07-ereader.css +245 -0
- package/css/theme/08-epub.css +865 -0
- package/css/theme/09-back-matter.css +47 -0
- package/css/theme/10-dark.css +302 -0
- package/css/theme/11-print.css +1229 -0
- package/css/theme.css +38 -0
- package/defaults/book.config.yml +318 -0
- package/defaults/pandoc-base.yml +19 -0
- package/defaults/pandoc-docx.yml +6 -0
- package/defaults/pandoc-epub-fonts.yml +6 -0
- package/defaults/pandoc-epub.yml +15 -0
- package/defaults/pandoc-kindle.yml +6 -0
- package/emoji/1f382.png +0 -0
- package/emoji/1f389.png +0 -0
- package/emoji/1f440.png +0 -0
- package/emoji/1f44d.png +0 -0
- package/emoji/1f44f.png +0 -0
- package/emoji/1f454.png +0 -0
- package/emoji/1f4a1.png +0 -0
- package/emoji/1f4bb.png +0 -0
- package/emoji/1f602.png +0 -0
- package/emoji/1f603.png +0 -0
- package/emoji/1f604.png +0 -0
- package/emoji/1f60a.png +0 -0
- package/emoji/1f60f.png +0 -0
- package/emoji/1f64c.png +0 -0
- package/emoji/1f64f.png +0 -0
- package/emoji/1f914.png +0 -0
- package/emoji/1f923.png +0 -0
- package/emoji/2714.png +0 -0
- package/emoji/274c.png +0 -0
- package/filters/add-div-titles.lua +196 -0
- package/filters/arc-notice.lua +36 -0
- package/filters/body-emoji-images.lua +48 -0
- package/filters/callout-classes.lua +40 -0
- package/filters/lib/book_kit.lua +110 -0
- package/filters/revision.lua +17 -0
- package/filters/strip-comments.lua +40 -0
- package/filters/strip-emoji-kindle.lua +134 -0
- package/fonts/LICENSE-source-code-pro.md +93 -0
- package/fonts/LICENSE-source-sans.md +93 -0
- package/fonts/LICENSE-source-serif.md +93 -0
- package/fonts/recipe.json +41 -0
- package/just/book-kit.just +229 -0
- package/lib/acronyms.js +47 -0
- package/lib/callouts.js +17 -0
- package/lib/config.js +211 -0
- package/lib/is-main.js +21 -0
- package/lib/lint.js +148 -0
- package/lib/pandoc.js +148 -0
- package/lib/paragraphs.js +173 -0
- package/lib/revision.js +35 -0
- package/lib/structure.js +39 -0
- package/lib/textlint-github-formatter.js +58 -0
- package/lib/validate.js +202 -0
- package/package.json +98 -4
- package/presets/business-nonfiction/CONVENTIONS.md +35 -0
- package/presets/business-nonfiction/preset.yml +128 -0
- package/profiles/pdfx-def.ps +42 -0
- package/requirements.txt +14 -0
- package/rules/no-ai-like-patterns.js +209 -0
- package/rules/no-assumptive.js +76 -0
- package/rules/no-bare-acronyms.js +129 -0
- package/rules/no-cliches.js +544 -0
- package/rules/no-cultural-idioms.js +422 -0
- package/rules/no-vague-intensifiers.js +94 -0
- package/templates/template-epub.html +79 -0
- package/templates/template.html +73 -0
- package/toolchain.json +42 -0
- package/validators/acronym-expansion.js +94 -0
- package/validators/bold-keywords.js +56 -0
- package/validators/callout-balance.js +74 -0
- package/validators/callout-divs.js +66 -0
- package/validators/callout-labels.js +102 -0
- package/validators/chapter-lengths.js +14 -0
- package/validators/chapter.js +57 -0
- package/validators/cross-references.js +45 -0
- package/validators/doubled-words.js +76 -0
- package/validators/duplicate-headings.js +52 -0
- package/validators/em-dashes.js +73 -0
- package/validators/escaped-smallcaps.js +46 -0
- package/validators/footnotes.js +78 -0
- package/validators/heading-hierarchy.js +58 -0
- package/validators/hypothetical-hooks.js +103 -0
- package/validators/index-integrity.js +28 -0
- package/validators/index.js +73 -0
- package/validators/list-punctuation.js +123 -0
- package/validators/min-opener-length.js +87 -0
- package/validators/no-bare-url-link-text.js +38 -0
- package/validators/no-todo-markers.js +49 -0
- package/validators/no-transition-todos.js +34 -0
- package/validators/number-range-dashes.js +72 -0
- package/validators/paragraph-length.js +62 -0
- package/validators/quote-punctuation.js +85 -0
- package/validators/section-intro-final-lead.js +90 -0
- package/validators/section-intro-has-lead.js +41 -0
- package/validators/section-intro-no-tldr.js +37 -0
- package/validators/section-intros.js +33 -0
- package/validators/sentence-starters.js +132 -0
- package/validators/smallcaps.js +67 -0
- package/validators/tldr-length.js +58 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
-- arc-notice.lua
|
|
2
|
+
-- Stamps an "Advance Reader Copy — not for redistribution" notice onto the
|
|
3
|
+
-- copyright page. Active ONLY when the build passes `--metadata arc=true`
|
|
4
|
+
-- (the `just arc` recipe); every other build is a no-op and returns the
|
|
5
|
+
-- document untouched.
|
|
6
|
+
--
|
|
7
|
+
-- The notice is prepended to the `#copyright-container` div so it reads as the
|
|
8
|
+
-- first thing on the copyright page. It establishes that the free advance file
|
|
9
|
+
-- is licensed for review, not for resale or redistribution. The text comes from
|
|
10
|
+
-- strings.arc_notice in book.config.yml.
|
|
11
|
+
|
|
12
|
+
package.path = pandoc.path.directory(PANDOC_SCRIPT_FILE) .. "/lib/?.lua;" .. package.path
|
|
13
|
+
local book_kit = require("book_kit")
|
|
14
|
+
|
|
15
|
+
function Pandoc(doc)
|
|
16
|
+
if not doc.meta.arc then return nil end
|
|
17
|
+
local text = book_kit.config().strings.arc_notice
|
|
18
|
+
|
|
19
|
+
return doc:walk {
|
|
20
|
+
Div = function(el)
|
|
21
|
+
if el.identifier ~= "copyright-container" then return nil end
|
|
22
|
+
|
|
23
|
+
local notice = pandoc.Div(
|
|
24
|
+
pandoc.Para {
|
|
25
|
+
pandoc.Strong { pandoc.Str(text.heading) },
|
|
26
|
+
pandoc.Space(),
|
|
27
|
+
pandoc.Str(text.body),
|
|
28
|
+
},
|
|
29
|
+
pandoc.Attr("", { "arc-notice" }, {})
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
table.insert(el.content, 1, notice)
|
|
33
|
+
return el
|
|
34
|
+
end,
|
|
35
|
+
}
|
|
36
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
-- Lua filter: convert body-text color emoji to inline Twemoji <img> for the
|
|
2
|
+
-- PRINT PDF only.
|
|
3
|
+
--
|
|
4
|
+
-- Why: WeasyPrint renders raw emoji characters via a fallback emoji font, where
|
|
5
|
+
-- they come out undersized and sitting high above the baseline (small + too
|
|
6
|
+
-- high). Converting them to sized <img class="emoji"> elements lets the
|
|
7
|
+
-- `img.emoji` rule in @media print control their size and baseline alignment so
|
|
8
|
+
-- they match the surrounding serif text.
|
|
9
|
+
--
|
|
10
|
+
-- Scope: this filter is wired only into the PDF pipeline (justfile `pdf`
|
|
11
|
+
-- recipe). It mirrors the Kindle emoji-image approach in strip-emoji-kindle.lua
|
|
12
|
+
-- but keeps the color Twemoji from emoji.source_dir — no B&W swaps and no
|
|
13
|
+
-- stripping; every non-emoji character is preserved exactly. Callout-label
|
|
14
|
+
-- emoji are emitted as Image elements by add-div-titles.lua and are never Str,
|
|
15
|
+
-- so they are untouched here.
|
|
16
|
+
--
|
|
17
|
+
-- Only emoji listed in emoji.images (book.config.yml) are converted; anything
|
|
18
|
+
-- else is left as text.
|
|
19
|
+
|
|
20
|
+
package.path = pandoc.path.directory(PANDOC_SCRIPT_FILE) .. "/lib/?.lua;" .. package.path
|
|
21
|
+
local book_kit = require("book_kit")
|
|
22
|
+
local body_dir = book_kit.config().emoji.pdf_body_dir
|
|
23
|
+
|
|
24
|
+
-- emoji character sequence → {cp = PNG name, alt = alt text}, from
|
|
25
|
+
-- emoji.images in book.config.yml.
|
|
26
|
+
local image_map = book_kit.config().emoji.images
|
|
27
|
+
|
|
28
|
+
-- Build an inline Image element for an emoji. emoji.pdf_body_dir is relative
|
|
29
|
+
-- to the PDF base-url (dist/). Those PNGs are opaque, flattened onto white
|
|
30
|
+
-- (commands/composite-print-emoji.py) so the PDF/X-1a step's -dNOTRANSPARENCY has
|
|
31
|
+
-- no alpha to drop — body emoji sit on the white page, so a white flatten is
|
|
32
|
+
-- invisible. Size and vertical alignment are left to `img.emoji` in @media print.
|
|
33
|
+
local function emoji_image(info)
|
|
34
|
+
return pandoc.Image(
|
|
35
|
+
{pandoc.Str(info.alt)},
|
|
36
|
+
body_dir .. info.cp .. ".png",
|
|
37
|
+
"",
|
|
38
|
+
pandoc.Attr("", {"emoji"}, {{"width", "16"}, {"height", "16"}})
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
local image_keys = book_kit.sorted_keys(image_map)
|
|
43
|
+
|
|
44
|
+
function Str(el)
|
|
45
|
+
if book_kit.contains_any(el.text, image_keys) then
|
|
46
|
+
return book_kit.split_emoji(el.text, image_map, image_keys, emoji_image)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
-- callout-classes.lua
|
|
2
|
+
-- Gives a book's own callouts the theme's look. The kit theme styles its
|
|
3
|
+
-- core callouts (note, tip, warning) and business-nonfiction's by class; any
|
|
4
|
+
-- other class in the callouts registry gets the generic `callout` class (the
|
|
5
|
+
-- .note look in every format) and a data-label attribute, which the theme
|
|
6
|
+
-- draws as the label in the web edition. add-div-titles.lua adds the label to
|
|
7
|
+
-- the content in every other format.
|
|
8
|
+
--
|
|
9
|
+
-- A registry entry with `generic: false` opts out, for a book that styles that
|
|
10
|
+
-- callout in its own CSS.
|
|
11
|
+
|
|
12
|
+
package.path = pandoc.path.directory(PANDOC_SCRIPT_FILE) .. "/lib/?.lua;" .. package.path
|
|
13
|
+
local book_kit = require("book_kit")
|
|
14
|
+
|
|
15
|
+
-- The callout classes css/theme/ styles itself.
|
|
16
|
+
local THEMED = {
|
|
17
|
+
["note"] = true, ["tip"] = true, ["warning"] = true,
|
|
18
|
+
["tldr"] = true, ["pro-tip"] = true, ["for-managers"] = true, ["for-ics"] = true,
|
|
19
|
+
["common-objection"] = true, ["takeaways"] = true,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
local generic = {}
|
|
23
|
+
for _, c in ipairs(book_kit.config().callouts or {}) do
|
|
24
|
+
if not THEMED[c.class] and c.generic ~= false then table.insert(generic, c) end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
-- Nothing to do for a book that only uses themed callouts.
|
|
28
|
+
if #generic == 0 then return {} end
|
|
29
|
+
|
|
30
|
+
function Div(div)
|
|
31
|
+
for _, c in ipairs(generic) do
|
|
32
|
+
if div.classes:includes(c.class) then
|
|
33
|
+
if not div.classes:includes("callout") then div.classes:insert("callout") end
|
|
34
|
+
if c.label then
|
|
35
|
+
div.attributes["data-label"] = c.emoji and (c.emoji .. " " .. c.label) or c.label
|
|
36
|
+
end
|
|
37
|
+
return div
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
-- Shared helpers for book-kit Lua filters.
|
|
2
|
+
--
|
|
3
|
+
-- Filters load this with:
|
|
4
|
+
-- package.path = pandoc.path.directory(PANDOC_SCRIPT_FILE) .. "/lib/?.lua;" .. package.path
|
|
5
|
+
-- local book_kit = require("book_kit")
|
|
6
|
+
|
|
7
|
+
local M = {}
|
|
8
|
+
|
|
9
|
+
-- The resolved book.config.yml, as JSON. The build writes it to
|
|
10
|
+
-- dist/.book-kit/config.json (`book-kit prepare`) and may point
|
|
11
|
+
-- $BOOK_KIT_CONFIG at it. When neither file exists (pandoc run by hand, or a
|
|
12
|
+
-- book's tests calling a filter directly on a fresh checkout), ask the kit's
|
|
13
|
+
-- CLI for the same JSON, so a filter works without a prior build.
|
|
14
|
+
function M.config()
|
|
15
|
+
if M._config then return M._config end
|
|
16
|
+
local path = os.getenv("BOOK_KIT_CONFIG") or "dist/.book-kit/config.json"
|
|
17
|
+
local text
|
|
18
|
+
local f = io.open(path, "r")
|
|
19
|
+
if f then
|
|
20
|
+
text = f:read("a")
|
|
21
|
+
f:close()
|
|
22
|
+
else
|
|
23
|
+
local cli = pandoc.path.join({ pandoc.path.directory(PANDOC_SCRIPT_FILE), "..", "bin", "book-kit.js" })
|
|
24
|
+
local ok, out = pcall(pandoc.pipe, "node", { cli, "config" }, "")
|
|
25
|
+
if not ok then
|
|
26
|
+
error(PANDOC_SCRIPT_FILE .. ": can't read " .. path .. " or run `book-kit config` (" ..
|
|
27
|
+
tostring(out) .. "). Run from the book's directory, or set BOOK_KIT_CONFIG.")
|
|
28
|
+
end
|
|
29
|
+
text = out
|
|
30
|
+
end
|
|
31
|
+
M._config = pandoc.json.decode(text, false)
|
|
32
|
+
return M._config
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
-- A map's keys, longest first, then bytewise. pairs() order changes from run
|
|
36
|
+
-- to run, so walking a map with it makes any choice between keys that match at
|
|
37
|
+
-- the same spot (an emoji with and without its U+FE0F selector) a coin flip.
|
|
38
|
+
-- Walking these instead makes the longest match win every time.
|
|
39
|
+
function M.sorted_keys(map)
|
|
40
|
+
local keys = {}
|
|
41
|
+
for k in pairs(map) do keys[#keys + 1] = k end
|
|
42
|
+
table.sort(keys, function(a, b)
|
|
43
|
+
if #a ~= #b then return #a > #b end
|
|
44
|
+
return a < b
|
|
45
|
+
end)
|
|
46
|
+
return keys
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
-- Does text contain any of keys (plain substrings, not Lua patterns)?
|
|
50
|
+
function M.contains_any(text, keys)
|
|
51
|
+
for _, k in ipairs(keys) do
|
|
52
|
+
if text:find(k, 1, true) then return true end
|
|
53
|
+
end
|
|
54
|
+
return false
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
-- Replace every occurrence of each key with map[key], as plain text: keys
|
|
58
|
+
-- aren't Lua patterns and values aren't %-escaped replacement strings.
|
|
59
|
+
function M.replace_all(text, map, keys)
|
|
60
|
+
for _, k in ipairs(keys or M.sorted_keys(map)) do
|
|
61
|
+
local out, pos = {}, 1
|
|
62
|
+
while true do
|
|
63
|
+
local s, e = text:find(k, pos, true)
|
|
64
|
+
if not s then break end
|
|
65
|
+
out[#out + 1] = text:sub(pos, s - 1)
|
|
66
|
+
out[#out + 1] = map[k]
|
|
67
|
+
pos = e + 1
|
|
68
|
+
end
|
|
69
|
+
if pos > 1 then
|
|
70
|
+
out[#out + 1] = text:sub(pos)
|
|
71
|
+
text = table.concat(out)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
return text
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
-- Split text at the image-map emoji, returning a pandoc.List of Str and
|
|
78
|
+
-- Image inlines. make_image(info) builds the Image for map[emoji]; clean(text),
|
|
79
|
+
-- if given, rewrites the text between emoji (empty results are dropped). At a
|
|
80
|
+
-- position where several keys match, the first in keys (the longest) wins.
|
|
81
|
+
function M.split_emoji(text, map, keys, make_image, clean)
|
|
82
|
+
clean = clean or function(s) return s end
|
|
83
|
+
local inlines = pandoc.List()
|
|
84
|
+
local function emit(s)
|
|
85
|
+
s = clean(s)
|
|
86
|
+
if #s > 0 then inlines:insert(pandoc.Str(s)) end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
while #text > 0 do
|
|
90
|
+
local first_pos, first_key
|
|
91
|
+
for _, k in ipairs(keys) do
|
|
92
|
+
local pos = text:find(k, 1, true)
|
|
93
|
+
if pos and (not first_pos or pos < first_pos) then
|
|
94
|
+
first_pos, first_key = pos, k
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
if not first_pos then
|
|
99
|
+
emit(text)
|
|
100
|
+
break
|
|
101
|
+
end
|
|
102
|
+
if first_pos > 1 then emit(text:sub(1, first_pos - 1)) end
|
|
103
|
+
inlines:insert(make_image(map[first_key]))
|
|
104
|
+
text = text:sub(first_pos + #first_key)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
return inlines
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
return M
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
-- Fills [...]{.revision} spans with the build stamp from the `date` metadata
|
|
2
|
+
-- ("<version> (<sha>)", set by book-kit), so the copyright page shows which
|
|
3
|
+
-- build a reader has without the build rewriting the source file.
|
|
4
|
+
|
|
5
|
+
local stamp
|
|
6
|
+
|
|
7
|
+
return {
|
|
8
|
+
{Meta = function(meta)
|
|
9
|
+
if meta.date then stamp = pandoc.utils.stringify(meta.date) end
|
|
10
|
+
end},
|
|
11
|
+
{Span = function(span)
|
|
12
|
+
if stamp and span.classes:includes("revision") then
|
|
13
|
+
span.content = pandoc.Inlines(stamp)
|
|
14
|
+
return span
|
|
15
|
+
end
|
|
16
|
+
end},
|
|
17
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
-- Lua filter to strip HTML comments (<!-- ... -->) from HTML/EPUB output.
|
|
2
|
+
--
|
|
3
|
+
-- Editorial notes and linter directives live in the Markdown source as HTML
|
|
4
|
+
-- comments -- textlint-disable/enable and markdownlint-disable pragmas, and
|
|
5
|
+
-- the occasional note-to-self.
|
|
6
|
+
-- They're useful in the source, but Pandoc passes them through to HTML/EPUB as
|
|
7
|
+
-- raw nodes, so they ship *inside* the distributed .xhtml/.html files. They're
|
|
8
|
+
-- invisible when rendered, but present in view-source and reachable by search
|
|
9
|
+
-- indexers -- not something we want in a published book.
|
|
10
|
+
--
|
|
11
|
+
-- This filter drops any raw-HTML node that is exactly a single comment. The
|
|
12
|
+
-- print PDF path already discards them (WeasyPrint), so this closes the gap for
|
|
13
|
+
-- the EPUB and HTML deliverables. Wired into the epub/kindle/html recipes in
|
|
14
|
+
-- the justfile.
|
|
15
|
+
|
|
16
|
+
local function is_html_comment(fmt, text)
|
|
17
|
+
if not (fmt == "html" or fmt == "html4" or fmt == "html5") then
|
|
18
|
+
return false
|
|
19
|
+
end
|
|
20
|
+
local trimmed = text:gsub("^%s+", ""):gsub("%s+$", "")
|
|
21
|
+
-- Must open with "<!--", close with "-->", and be a *single* comment: the
|
|
22
|
+
-- first "-->" has to be the final three characters, so a raw block mixing a
|
|
23
|
+
-- comment with real markup (e.g. `<!-- x --> <span>`) is left untouched.
|
|
24
|
+
if trimmed:sub(1, 4) ~= "<!--" then return false end
|
|
25
|
+
if trimmed:sub(-3) ~= "-->" then return false end
|
|
26
|
+
local first_close = trimmed:find("-->", 1, true)
|
|
27
|
+
return first_close == #trimmed - 2
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
function RawBlock(el)
|
|
31
|
+
if is_html_comment(el.format, el.text) then
|
|
32
|
+
return {}
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
function RawInline(el)
|
|
37
|
+
if is_html_comment(el.format, el.text) then
|
|
38
|
+
return {}
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
-- Lua filter to replace color emoji with Kindle-safe alternatives.
|
|
2
|
+
-- Color emoji don't render on Kindle e-ink (no bundled emoji font).
|
|
3
|
+
-- This filter runs ONLY for the Kindle build (`just kindle`).
|
|
4
|
+
--
|
|
5
|
+
-- Strategy:
|
|
6
|
+
-- - Emoji with B&W Unicode equivalents → use those (text replacement)
|
|
7
|
+
-- - Emoji used as subjects of discussion → inline Twemoji PNG images
|
|
8
|
+
-- - Any remaining unmapped emoji → stripped silently
|
|
9
|
+
-- - Keeps prose readable on e-ink without emoji support
|
|
10
|
+
|
|
11
|
+
-- B&W Unicode equivalents (render natively on Kindle; simple text swap).
|
|
12
|
+
-- NOTE: ❌/✅ are intentionally NOT here — their would-be text equivalents
|
|
13
|
+
-- (✗ U+2717 / ✓ U+2713) tofu (render as an empty box) on Kindle's fonts, so
|
|
14
|
+
-- they are handled as images in image_map below instead. Arrows (↑ ←) are
|
|
15
|
+
-- common glyphs that render reliably, so they stay as text.
|
|
16
|
+
local bw_map = {
|
|
17
|
+
["\u{1F446}"] = "\u{2191}", -- 👆 → ↑
|
|
18
|
+
["\u{1F448}"] = "\u{2190}", -- 👈 → ←
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
-- Image replacements: emoji → {cp = PNG name, alt = alt text}, from
|
|
22
|
+
-- emoji.images in book.config.yml, minus entries marked kindle: false. The
|
|
23
|
+
-- PNGs (Twemoji, 72×72, CC-BY 4.0) are whitened for e-ink into
|
|
24
|
+
-- emoji.kindle_dir. ❌/✅ are images rather than ✗/✓ text, since those tofu
|
|
25
|
+
-- on Kindle's fonts.
|
|
26
|
+
package.path = pandoc.path.directory(PANDOC_SCRIPT_FILE) .. "/lib/?.lua;" .. package.path
|
|
27
|
+
local book_kit = require("book_kit")
|
|
28
|
+
local image_map = {}
|
|
29
|
+
for emoji, entry in pairs(book_kit.config().emoji.images) do
|
|
30
|
+
if entry.kindle ~= false then image_map[emoji] = entry end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
-- Catch-all pattern for emoji Unicode ranges that aren't in the map.
|
|
34
|
+
-- Only targets Supplemental Plane emoji (U+1Fxxx) which are guaranteed
|
|
35
|
+
-- color emoji. BMP symbols (U+2xxx — arrows, dingbats, geometric shapes)
|
|
36
|
+
-- are left alone because many render fine in monochrome text fonts.
|
|
37
|
+
local emoji_ranges = {
|
|
38
|
+
{0x1F300, 0x1F5FF}, -- Misc Symbols & Pictographs
|
|
39
|
+
{0x1F600, 0x1F64F}, -- Emoticons
|
|
40
|
+
{0x1F680, 0x1F6FF}, -- Transport & Map
|
|
41
|
+
{0x1F700, 0x1F77F}, -- Alchemical Symbols
|
|
42
|
+
{0x1F900, 0x1F9FF}, -- Supplemental Symbols & Pictographs
|
|
43
|
+
{0x1FA00, 0x1FA6F}, -- Chess Symbols
|
|
44
|
+
{0x1FA70, 0x1FAFF}, -- Symbols & Pictographs Extended-A
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
local function is_emoji_codepoint(cp)
|
|
48
|
+
for _, range in ipairs(emoji_ranges) do
|
|
49
|
+
if cp >= range[1] and cp <= range[2] then
|
|
50
|
+
return true
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
return false
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
-- Build a UTF-8 pattern that matches any single emoji codepoint
|
|
57
|
+
-- not already handled by bw_map. Uses the codepoint ranges above.
|
|
58
|
+
local function strip_unmapped_emoji(text)
|
|
59
|
+
local result = {}
|
|
60
|
+
local i = 1
|
|
61
|
+
local len = #text
|
|
62
|
+
local stripped = false
|
|
63
|
+
|
|
64
|
+
while i <= len do
|
|
65
|
+
local byte = text:byte(i)
|
|
66
|
+
local cp, seq_len
|
|
67
|
+
|
|
68
|
+
-- Decode UTF-8 sequence
|
|
69
|
+
if byte < 0x80 then
|
|
70
|
+
cp, seq_len = byte, 1
|
|
71
|
+
elseif byte < 0xE0 then
|
|
72
|
+
cp = ((byte - 0xC0) * 64) + (text:byte(i+1) - 0x80)
|
|
73
|
+
seq_len = 2
|
|
74
|
+
elseif byte < 0xF0 then
|
|
75
|
+
cp = ((byte - 0xE0) * 4096) + ((text:byte(i+1) - 0x80) * 64) + (text:byte(i+2) - 0x80)
|
|
76
|
+
seq_len = 3
|
|
77
|
+
else
|
|
78
|
+
cp = ((byte - 0xF0) * 262144) + ((text:byte(i+1) - 0x80) * 4096) + ((text:byte(i+2) - 0x80) * 64) + (text:byte(i+3) - 0x80)
|
|
79
|
+
seq_len = 4
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
if is_emoji_codepoint(cp) then
|
|
83
|
+
stripped = true
|
|
84
|
+
-- Skip this character (don't add to result)
|
|
85
|
+
else
|
|
86
|
+
table.insert(result, text:sub(i, i + seq_len - 1))
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
i = i + seq_len
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
return table.concat(result), stripped
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
-- Inline style for emoji images (Kindle ignores external CSS on images)
|
|
96
|
+
local kindle_dir = book_kit.config().emoji.kindle_dir
|
|
97
|
+
|
|
98
|
+
local emoji_style = "height:1em;width:1em;vertical-align:middle;margin:0;padding:0;display:inline;background:transparent;background-color:transparent"
|
|
99
|
+
|
|
100
|
+
-- Build an inline Image element for an emoji
|
|
101
|
+
local function emoji_image(info)
|
|
102
|
+
return pandoc.Image(
|
|
103
|
+
{pandoc.Str(info.alt)},
|
|
104
|
+
kindle_dir .. info.cp .. ".png",
|
|
105
|
+
"",
|
|
106
|
+
pandoc.Attr("", {"emoji"}, {{"width", "16"}, {"height", "16"}, {"style", emoji_style}})
|
|
107
|
+
)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
local image_keys = book_kit.sorted_keys(image_map)
|
|
111
|
+
local bw_keys = book_kit.sorted_keys(bw_map)
|
|
112
|
+
|
|
113
|
+
-- strip_unmapped_emoji without its second return value.
|
|
114
|
+
local function strip_only(text)
|
|
115
|
+
return (strip_unmapped_emoji(text))
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
function Str(el)
|
|
119
|
+
local text = el.text
|
|
120
|
+
|
|
121
|
+
-- If text contains image-map emoji, split into Str + Image inlines, after
|
|
122
|
+
-- the B&W swaps; unmapped emoji are stripped from the text in between.
|
|
123
|
+
if book_kit.contains_any(text, image_keys) then
|
|
124
|
+
text = book_kit.replace_all(text, bw_map, bw_keys)
|
|
125
|
+
return book_kit.split_emoji(text, image_map, image_keys, emoji_image, strip_only)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
-- Otherwise, apply B&W text replacements and strip unmapped emoji
|
|
129
|
+
local replaced = book_kit.replace_all(text, bw_map, bw_keys)
|
|
130
|
+
local cleaned, stripped = strip_unmapped_emoji(replaced)
|
|
131
|
+
if stripped or replaced ~= text then
|
|
132
|
+
return pandoc.Str(cleaned)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
© 2023 Adobe (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe in the United States and/or other countries.
|
|
2
|
+
|
|
3
|
+
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
|
4
|
+
|
|
5
|
+
This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
-----------------------------------------------------------
|
|
9
|
+
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
|
10
|
+
-----------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
PREAMBLE
|
|
13
|
+
The goals of the Open Font License (OFL) are to stimulate worldwide
|
|
14
|
+
development of collaborative font projects, to support the font creation
|
|
15
|
+
efforts of academic and linguistic communities, and to provide a free and
|
|
16
|
+
open framework in which fonts may be shared and improved in partnership
|
|
17
|
+
with others.
|
|
18
|
+
|
|
19
|
+
The OFL allows the licensed fonts to be used, studied, modified and
|
|
20
|
+
redistributed freely as long as they are not sold by themselves. The
|
|
21
|
+
fonts, including any derivative works, can be bundled, embedded,
|
|
22
|
+
redistributed and/or sold with any software provided that any reserved
|
|
23
|
+
names are not used by derivative works. The fonts and derivatives,
|
|
24
|
+
however, cannot be released under any other type of license. The
|
|
25
|
+
requirement for fonts to remain under this license does not apply
|
|
26
|
+
to any document created using the fonts or their derivatives.
|
|
27
|
+
|
|
28
|
+
DEFINITIONS
|
|
29
|
+
"Font Software" refers to the set of files released by the Copyright
|
|
30
|
+
Holder(s) under this license and clearly marked as such. This may
|
|
31
|
+
include source files, build scripts and documentation.
|
|
32
|
+
|
|
33
|
+
"Reserved Font Name" refers to any names specified as such after the
|
|
34
|
+
copyright statement(s).
|
|
35
|
+
|
|
36
|
+
"Original Version" refers to the collection of Font Software components as
|
|
37
|
+
distributed by the Copyright Holder(s).
|
|
38
|
+
|
|
39
|
+
"Modified Version" refers to any derivative made by adding to, deleting,
|
|
40
|
+
or substituting -- in part or in whole -- any of the components of the
|
|
41
|
+
Original Version, by changing formats or by porting the Font Software to a
|
|
42
|
+
new environment.
|
|
43
|
+
|
|
44
|
+
"Author" refers to any designer, engineer, programmer, technical
|
|
45
|
+
writer or other person who contributed to the Font Software.
|
|
46
|
+
|
|
47
|
+
PERMISSION & CONDITIONS
|
|
48
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
49
|
+
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
|
50
|
+
redistribute, and sell modified and unmodified copies of the Font
|
|
51
|
+
Software, subject to the following conditions:
|
|
52
|
+
|
|
53
|
+
1) Neither the Font Software nor any of its individual components,
|
|
54
|
+
in Original or Modified Versions, may be sold by itself.
|
|
55
|
+
|
|
56
|
+
2) Original or Modified Versions of the Font Software may be bundled,
|
|
57
|
+
redistributed and/or sold with any software, provided that each copy
|
|
58
|
+
contains the above copyright notice and this license. These can be
|
|
59
|
+
included either as stand-alone text files, human-readable headers or
|
|
60
|
+
in the appropriate machine-readable metadata fields within text or
|
|
61
|
+
binary files as long as those fields can be easily viewed by the user.
|
|
62
|
+
|
|
63
|
+
3) No Modified Version of the Font Software may use the Reserved Font
|
|
64
|
+
Name(s) unless explicit written permission is granted by the corresponding
|
|
65
|
+
Copyright Holder. This restriction only applies to the primary font name as
|
|
66
|
+
presented to the users.
|
|
67
|
+
|
|
68
|
+
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
|
69
|
+
Software shall not be used to promote, endorse or advertise any
|
|
70
|
+
Modified Version, except to acknowledge the contribution(s) of the
|
|
71
|
+
Copyright Holder(s) and the Author(s) or with their explicit written
|
|
72
|
+
permission.
|
|
73
|
+
|
|
74
|
+
5) The Font Software, modified or unmodified, in part or in whole,
|
|
75
|
+
must be distributed entirely under this license, and must not be
|
|
76
|
+
distributed under any other license. The requirement for fonts to
|
|
77
|
+
remain under this license does not apply to any document created
|
|
78
|
+
using the Font Software.
|
|
79
|
+
|
|
80
|
+
TERMINATION
|
|
81
|
+
This license becomes null and void if any of the above conditions are
|
|
82
|
+
not met.
|
|
83
|
+
|
|
84
|
+
DISCLAIMER
|
|
85
|
+
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
86
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
|
87
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
|
88
|
+
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
|
89
|
+
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
90
|
+
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
|
91
|
+
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
92
|
+
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
|
93
|
+
OTHER DEALINGS IN THE FONT SOFTWARE.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
Copyright 2010-2024 Adobe (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe in the United States and/or other countries.
|
|
2
|
+
|
|
3
|
+
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
|
4
|
+
|
|
5
|
+
This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
-----------------------------------------------------------
|
|
9
|
+
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
|
10
|
+
-----------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
PREAMBLE
|
|
13
|
+
The goals of the Open Font License (OFL) are to stimulate worldwide
|
|
14
|
+
development of collaborative font projects, to support the font creation
|
|
15
|
+
efforts of academic and linguistic communities, and to provide a free and
|
|
16
|
+
open framework in which fonts may be shared and improved in partnership
|
|
17
|
+
with others.
|
|
18
|
+
|
|
19
|
+
The OFL allows the licensed fonts to be used, studied, modified and
|
|
20
|
+
redistributed freely as long as they are not sold by themselves. The
|
|
21
|
+
fonts, including any derivative works, can be bundled, embedded,
|
|
22
|
+
redistributed and/or sold with any software provided that any reserved
|
|
23
|
+
names are not used by derivative works. The fonts and derivatives,
|
|
24
|
+
however, cannot be released under any other type of license. The
|
|
25
|
+
requirement for fonts to remain under this license does not apply
|
|
26
|
+
to any document created using the fonts or their derivatives.
|
|
27
|
+
|
|
28
|
+
DEFINITIONS
|
|
29
|
+
"Font Software" refers to the set of files released by the Copyright
|
|
30
|
+
Holder(s) under this license and clearly marked as such. This may
|
|
31
|
+
include source files, build scripts and documentation.
|
|
32
|
+
|
|
33
|
+
"Reserved Font Name" refers to any names specified as such after the
|
|
34
|
+
copyright statement(s).
|
|
35
|
+
|
|
36
|
+
"Original Version" refers to the collection of Font Software components as
|
|
37
|
+
distributed by the Copyright Holder(s).
|
|
38
|
+
|
|
39
|
+
"Modified Version" refers to any derivative made by adding to, deleting,
|
|
40
|
+
or substituting -- in part or in whole -- any of the components of the
|
|
41
|
+
Original Version, by changing formats or by porting the Font Software to a
|
|
42
|
+
new environment.
|
|
43
|
+
|
|
44
|
+
"Author" refers to any designer, engineer, programmer, technical
|
|
45
|
+
writer or other person who contributed to the Font Software.
|
|
46
|
+
|
|
47
|
+
PERMISSION & CONDITIONS
|
|
48
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
49
|
+
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
|
50
|
+
redistribute, and sell modified and unmodified copies of the Font
|
|
51
|
+
Software, subject to the following conditions:
|
|
52
|
+
|
|
53
|
+
1) Neither the Font Software nor any of its individual components,
|
|
54
|
+
in Original or Modified Versions, may be sold by itself.
|
|
55
|
+
|
|
56
|
+
2) Original or Modified Versions of the Font Software may be bundled,
|
|
57
|
+
redistributed and/or sold with any software, provided that each copy
|
|
58
|
+
contains the above copyright notice and this license. These can be
|
|
59
|
+
included either as stand-alone text files, human-readable headers or
|
|
60
|
+
in the appropriate machine-readable metadata fields within text or
|
|
61
|
+
binary files as long as those fields can be easily viewed by the user.
|
|
62
|
+
|
|
63
|
+
3) No Modified Version of the Font Software may use the Reserved Font
|
|
64
|
+
Name(s) unless explicit written permission is granted by the corresponding
|
|
65
|
+
Copyright Holder. This restriction only applies to the primary font name as
|
|
66
|
+
presented to the users.
|
|
67
|
+
|
|
68
|
+
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
|
69
|
+
Software shall not be used to promote, endorse or advertise any
|
|
70
|
+
Modified Version, except to acknowledge the contribution(s) of the
|
|
71
|
+
Copyright Holder(s) and the Author(s) or with their explicit written
|
|
72
|
+
permission.
|
|
73
|
+
|
|
74
|
+
5) The Font Software, modified or unmodified, in part or in whole,
|
|
75
|
+
must be distributed entirely under this license, and must not be
|
|
76
|
+
distributed under any other license. The requirement for fonts to
|
|
77
|
+
remain under this license does not apply to any document created
|
|
78
|
+
using the Font Software.
|
|
79
|
+
|
|
80
|
+
TERMINATION
|
|
81
|
+
This license becomes null and void if any of the above conditions are
|
|
82
|
+
not met.
|
|
83
|
+
|
|
84
|
+
DISCLAIMER
|
|
85
|
+
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
86
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
|
87
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
|
88
|
+
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
|
89
|
+
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
90
|
+
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
|
91
|
+
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
92
|
+
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
|
93
|
+
OTHER DEALINGS IN THE FONT SOFTWARE.
|