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.
Files changed (139) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +72 -0
  3. package/README.md +251 -2
  4. package/bin/book-kit.js +115 -0
  5. package/commands/_emoji_io.py +32 -0
  6. package/commands/audit-chapter-lengths.js +244 -0
  7. package/commands/check-epub-size.js +68 -0
  8. package/commands/check-pdf-page-count.js +162 -0
  9. package/commands/composite-kindle-emoji.py +85 -0
  10. package/commands/composite-print-emoji.py +98 -0
  11. package/commands/conventions.js +76 -0
  12. package/commands/convert-pdfx.sh +194 -0
  13. package/commands/download-icc-profile.sh +105 -0
  14. package/commands/fonts.py +212 -0
  15. package/commands/generate-reference-docx.py +344 -0
  16. package/commands/kindle-fonts.js +173 -0
  17. package/commands/links-to-footnotes.js +366 -0
  18. package/commands/markdownlint.js +25 -0
  19. package/commands/minify-css.js +43 -0
  20. package/commands/optimize-images.js +73 -0
  21. package/commands/parity.sh +150 -0
  22. package/commands/postprocess-epub.js +541 -0
  23. package/commands/prep-print-src.js +77 -0
  24. package/commands/prose.js +38 -0
  25. package/commands/release-files.js +89 -0
  26. package/commands/reorder-print-frontmatter.js +79 -0
  27. package/commands/strip-page-rules.js +1168 -0
  28. package/commands/strip-property-rules.js +93 -0
  29. package/commands/update-key-takeaways.js +314 -0
  30. package/commands/validate.js +283 -0
  31. package/configs/markdownlint-cli2.jsonc +98 -0
  32. package/configs/textlintrc.json +22 -0
  33. package/css/theme/01-tokens.css +83 -0
  34. package/css/theme/02-fonts.css +79 -0
  35. package/css/theme/03-prose.css +53 -0
  36. package/css/theme/04-base.css +226 -0
  37. package/css/theme/05-components.css +546 -0
  38. package/css/theme/06-utilities.css +40 -0
  39. package/css/theme/07-ereader.css +245 -0
  40. package/css/theme/08-epub.css +865 -0
  41. package/css/theme/09-back-matter.css +47 -0
  42. package/css/theme/10-dark.css +302 -0
  43. package/css/theme/11-print.css +1229 -0
  44. package/css/theme.css +38 -0
  45. package/defaults/book.config.yml +318 -0
  46. package/defaults/pandoc-base.yml +19 -0
  47. package/defaults/pandoc-docx.yml +6 -0
  48. package/defaults/pandoc-epub-fonts.yml +6 -0
  49. package/defaults/pandoc-epub.yml +15 -0
  50. package/defaults/pandoc-kindle.yml +6 -0
  51. package/emoji/1f382.png +0 -0
  52. package/emoji/1f389.png +0 -0
  53. package/emoji/1f440.png +0 -0
  54. package/emoji/1f44d.png +0 -0
  55. package/emoji/1f44f.png +0 -0
  56. package/emoji/1f454.png +0 -0
  57. package/emoji/1f4a1.png +0 -0
  58. package/emoji/1f4bb.png +0 -0
  59. package/emoji/1f602.png +0 -0
  60. package/emoji/1f603.png +0 -0
  61. package/emoji/1f604.png +0 -0
  62. package/emoji/1f60a.png +0 -0
  63. package/emoji/1f60f.png +0 -0
  64. package/emoji/1f64c.png +0 -0
  65. package/emoji/1f64f.png +0 -0
  66. package/emoji/1f914.png +0 -0
  67. package/emoji/1f923.png +0 -0
  68. package/emoji/2714.png +0 -0
  69. package/emoji/274c.png +0 -0
  70. package/filters/add-div-titles.lua +196 -0
  71. package/filters/arc-notice.lua +36 -0
  72. package/filters/body-emoji-images.lua +48 -0
  73. package/filters/callout-classes.lua +40 -0
  74. package/filters/lib/book_kit.lua +110 -0
  75. package/filters/revision.lua +17 -0
  76. package/filters/strip-comments.lua +40 -0
  77. package/filters/strip-emoji-kindle.lua +134 -0
  78. package/fonts/LICENSE-source-code-pro.md +93 -0
  79. package/fonts/LICENSE-source-sans.md +93 -0
  80. package/fonts/LICENSE-source-serif.md +93 -0
  81. package/fonts/recipe.json +41 -0
  82. package/just/book-kit.just +229 -0
  83. package/lib/acronyms.js +47 -0
  84. package/lib/callouts.js +17 -0
  85. package/lib/config.js +211 -0
  86. package/lib/is-main.js +21 -0
  87. package/lib/lint.js +148 -0
  88. package/lib/pandoc.js +148 -0
  89. package/lib/paragraphs.js +173 -0
  90. package/lib/revision.js +35 -0
  91. package/lib/structure.js +39 -0
  92. package/lib/textlint-github-formatter.js +58 -0
  93. package/lib/validate.js +202 -0
  94. package/package.json +98 -4
  95. package/presets/business-nonfiction/CONVENTIONS.md +35 -0
  96. package/presets/business-nonfiction/preset.yml +128 -0
  97. package/profiles/pdfx-def.ps +42 -0
  98. package/requirements.txt +14 -0
  99. package/rules/no-ai-like-patterns.js +209 -0
  100. package/rules/no-assumptive.js +76 -0
  101. package/rules/no-bare-acronyms.js +129 -0
  102. package/rules/no-cliches.js +544 -0
  103. package/rules/no-cultural-idioms.js +422 -0
  104. package/rules/no-vague-intensifiers.js +94 -0
  105. package/templates/template-epub.html +79 -0
  106. package/templates/template.html +73 -0
  107. package/toolchain.json +42 -0
  108. package/validators/acronym-expansion.js +94 -0
  109. package/validators/bold-keywords.js +56 -0
  110. package/validators/callout-balance.js +74 -0
  111. package/validators/callout-divs.js +66 -0
  112. package/validators/callout-labels.js +102 -0
  113. package/validators/chapter-lengths.js +14 -0
  114. package/validators/chapter.js +57 -0
  115. package/validators/cross-references.js +45 -0
  116. package/validators/doubled-words.js +76 -0
  117. package/validators/duplicate-headings.js +52 -0
  118. package/validators/em-dashes.js +73 -0
  119. package/validators/escaped-smallcaps.js +46 -0
  120. package/validators/footnotes.js +78 -0
  121. package/validators/heading-hierarchy.js +58 -0
  122. package/validators/hypothetical-hooks.js +103 -0
  123. package/validators/index-integrity.js +28 -0
  124. package/validators/index.js +73 -0
  125. package/validators/list-punctuation.js +123 -0
  126. package/validators/min-opener-length.js +87 -0
  127. package/validators/no-bare-url-link-text.js +38 -0
  128. package/validators/no-todo-markers.js +49 -0
  129. package/validators/no-transition-todos.js +34 -0
  130. package/validators/number-range-dashes.js +72 -0
  131. package/validators/paragraph-length.js +62 -0
  132. package/validators/quote-punctuation.js +85 -0
  133. package/validators/section-intro-final-lead.js +90 -0
  134. package/validators/section-intro-has-lead.js +41 -0
  135. package/validators/section-intro-no-tldr.js +37 -0
  136. package/validators/section-intros.js +33 -0
  137. package/validators/sentence-starters.js +132 -0
  138. package/validators/smallcaps.js +67 -0
  139. 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.