module Markd::Pdf

Defined in:

hyphenation.cr
pdf.cr
pdf_renderer.cr
pdf_styles.cr

Constant Summary

DARK_CODE_THEME = "monokai"

Syntax highlighting theme used for the dark style when neither --code-theme nor -t is given: the light default would glow on a dark page.

DEFAULT_CODE_THEME = "friendly"

Syntax highlighting theme used when nothing better is known: a classic light style that reads well on the default light page.

DEFAULT_CSS = STYLES["default"]

The original name of the base stylesheet, kept for compatibility.

KDP_CSS = "h1 { page-break-before: right }"

The production layer kdp mode adds on top of the style: chapters (top-level headings) open on a right-hand page, with blank filler pages when the previous chapter ended on a verso one. User layers (--css) win over it, so retargeting or turning it off is a one-line override.

KDP_GUTTER_TABLE = [{24, 150, 9.525}, {151, 300, 12.7}, {301, 500, 15.875}, {501, 700, 19.05}, {701, 828, 22.225}]

KDP paperback gutter by page count (Amazon's table, in mm).

KDP_MIN_OUTSIDE_MARGIN_MM = 6.35

KDP's minimum margin on every side for no-bleed interiors, in mm (0.25 inch). All four sides are outside edges on one page parity: recto keeps the gutter on the left and the outside edge right, verso mirrors that.

MAX_IMAGE_BYTES = ((ENV["MARKPDF_MAX_IMAGE_MB"]?.try(&.to_i?) || 8) * 1024) * 1024

Remote image bodies larger than this are refused (MARKPDF_MAX_IMAGE_MB overrides the 8 MB default).

MAX_PAGE_DIMENSION = 5000.0
MAX_TOC_PASSES = 6

Renders until the TOC page numbers (and, when sizing the kdp gutter, the gutter) stop moving: each pass lays out the document with the current TOC block, then rebuilds that block from where the headings actually landed — the TOC's own length shifts every page after it, so one pass can never know its numbers. Two or three passes converge; the cap is a safety net for pathological documents. Yields the gutter to use this pass (-1 when the caller sizes margins itself) and the TOC block to render with (nil = none), and returns the final page count. Internal, but shared with the CLI, which owns its Renderer construction.

MIN_PAGE_DIMENSION = 10.0

Largest page dimension the PDF format allows (14400pt), and a floor small enough that a margin still leaves room to draw.

MINIMUM_HYPHENATED_LENGTH = 5

Words shorter than this are never worth hyphenating.

NO_HYPHENATION_ELEMENTS = {"pre", "code", "script", "style"}

Elements whose contents are never hyphenated: code, formulas and anything a renderer would not wrap word-by-word either.

PAGE_SIZES = {"a0" => {841.0, 1189.0}, "a1" => {594.0, 841.0}, "a2" => {420.0, 594.0}, "a3" => {297.0, 420.0}, "a4" => {210.0, 297.0}, "a5" => {148.0, 210.0}, "a6" => {105.0, 148.0}, "b0" => {1000.0, 1414.0}, "b1" => {707.0, 1000.0}, "b2" => {500.0, 707.0}, "b3" => {353.0, 500.0}, "b4" => {250.0, 353.0}, "b5" => {176.0, 250.0}, "b6" => {125.0, 176.0}, "letter" => {215.9, 279.4}, "legal" => {215.9, 355.6}}

Page sizes by name: the ISO 216 A and B series (portrait, in mm) plus the US sizes people keep asking for. Custom sizes pass a "WxH" string in millimeters instead of a name.

STYLE_DESCRIPTIONS = {"default" => "clean sans-serif print style", "book" => "serif, justified, indented — for long prose / e-readers", "dark" => "dark page, light text — for screen reading", "sepia" => "warm paper tones, serif — e-reader default look"}
STYLES = {"default" => "body { font-family: \"DejaVu Sans\", Helvetica, Arial, sans-serif; font-size: 11px; line-height: 1.5; color: #1a1a1a; margin: 0; padding: 0; }\nh1 { font-size: 25px; font-weight: bold; margin: 20px 0 12px 0; }\nh2 { font-size: 19px; font-weight: bold; margin: 18px 0 10px 0; }\nh3 { font-size: 15px; font-weight: bold; margin: 16px 0 8px 0; }\nh4 { font-size: 12px; font-weight: bold; margin: 14px 0 8px 0; }\nh5 { font-size: 11px; font-weight: bold; margin: 12px 0 6px 0; }\nh6 { font-size: 11px; font-weight: bold; color: #555555; margin: 12px 0 6px 0; }\np { margin: 0 0 9px 0; }\na { color: #0645ad; }\nstrong { font-weight: bold; }\nem { font-style: italic; }\ndel { text-decoration: line-through; }\nul, ol { margin: 0 0 9px 0; padding-left: 18px; }\nul ul, ol ol, ul ol, ol ul { margin: 0 0 9px 0; padding-left: 16px; }\nli { margin: 0 0 3px 0; }\nli.task-list-item { list-style-type: none; }\nblockquote { border-left: 4px solid #cccccc; margin: 9px 0 9px 0; padding: 2px 0 2px 12px; color: #444444; }\nblockquote p { margin: 0 0 6px 0; }\npre { font-family: \"DejaVu Sans Mono\", \"Liberation Mono\", Courier, monospace; font-size: 10px; background-color: #f6f6f6; border: 1px solid #e0e0e0;\n margin: 9px 0 9px 0; padding: 7px 9px 7px 9px; overflow: hidden;\n white-space: pre-wrap; }\ntable { overflow: hidden; }\ncode { font-family: \"DejaVu Sans Mono\", \"Liberation Mono\", Courier, monospace; font-size: 10px; background-color: #f2f2f2; padding: 0px 2px 0px 2px; }\npre code { background-color: transparent; padding: 0; }\ntable { border-spacing: 0; margin: 9px 0 9px 0; font-size: 10.5px; }\nth { font-weight: bold; border: 1px solid #cccccc; background-color: #f2f2f2; padding: 4px 8px 4px 8px; text-align: left; }\ntd { border: 1px solid #cccccc; padding: 4px 8px 4px 8px; }\nhr { border-bottom: 1px solid #bbbbbb; margin: 18px 0 18px 0; }\nimg { margin: 6px 0 6px 0; max-width: 100%; }\n.alert { border: none; border-left: 4px solid #666666; padding: 8px 12px 4px 12px; margin: 9px 0 9px 0; }\n.alert p { margin: 0 0 6px 0; }\n.alert-title { font-weight: bold; }\n.alert-note { border-left: 4px solid #2e6f9e; }\n.alert-tip { border-left: 4px solid #2e9e5b; }\n.alert-important { border-left: 4px solid #7e4fd0; }\n.alert-warning { border-left: 4px solid #d0962e; }\n.alert-caution { border-left: 4px solid #d0342c; }\n.footnotes { border-top: 1px solid #bbbbbb; margin-top: 18px; font-size: 10px; color: #444444; }\nu, ins { text-decoration: underline; }\nq { font-style: italic; }\nabbr { border-bottom: 1px dotted #888888; }\nmark { background-color: #fff3a3; }\nkbd { font-family: \"DejaVu Sans Mono\", \"Liberation Mono\", Courier, monospace; font-size: 10px; background-color: #f2f2f2; border: 1px solid #cccccc; padding: 0px 3px 0px 3px; }\n.math { font-family: \"DejaVu Serif\", Georgia, serif; font-style: italic; }\n.math.block { display: block; text-align: center; margin: 6px 0 6px 0; }\npre.math { text-align: left; background-color: transparent; border: none; line-height: 1.1; margin: 6px auto 6px auto; }\n#{TOC_CSS}", "book" => "body { font-family: \"DejaVu Serif\", Georgia, serif; font-size: 12px; line-height: 1.6; color: #1a1a1a; margin: 0; padding: 0; }\nh1, h2, h3 { text-align: center; }\nh1 { font-size: 24px; font-weight: bold; margin: 26px 0 18px 0; }\nh2 { font-size: 19px; font-weight: bold; margin: 20px 0 12px 0; }\nh3 { font-size: 15px; font-weight: bold; margin: 16px 0 8px 0; }\nh4 { font-size: 12px; font-weight: bold; margin: 14px 0 8px 0; }\nh5 { font-size: 11.5px; font-weight: bold; margin: 12px 0 6px 0; }\nh6 { font-size: 11.5px; font-style: italic; color: #555555; margin: 12px 0 6px 0; }\np { margin: 0; text-indent: 1.5em; text-align: justify; }\na { color: #0645ad; }\nstrong { font-weight: bold; }\nem { font-style: italic; }\ndel { text-decoration: line-through; }\nul, ol { margin: 0 0 9px 0; padding-left: 18px; }\nul ul, ol ol, ul ol, ol ul { margin: 0 0 9px 0; padding-left: 16px; }\nli { margin: 0 0 3px 0; text-align: justify; }\nli.task-list-item { list-style-type: none; }\nli p { margin: 0 0 3px 0; text-indent: 0; }\nblockquote { border-left: 4px solid #cccccc; margin: 9px 0 9px 0; padding: 2px 0 2px 12px; color: #444444; font-size: 11.5px; }\nblockquote p { margin: 0 0 6px 0; text-indent: 0; }\npre { font-family: \"DejaVu Sans Mono\", \"Liberation Mono\", Courier, monospace; font-size: 10px; background-color: #f6f6f6; border: none;\n margin: 9px 0 9px 0; padding: 7px 9px 7px 9px; overflow: hidden;\n white-space: pre-wrap; }\ntable { overflow: hidden; }\ncode { font-family: \"DejaVu Sans Mono\", \"Liberation Mono\", Courier, monospace; font-size: 10px; background-color: #f2f2f2; padding: 0px 2px 0px 2px; }\npre code { background-color: transparent; padding: 0; }\ntable { border-spacing: 0; margin: 9px 0 9px 0; font-size: 10.5px; }\nth { font-weight: bold; border: 1px solid #cccccc; background-color: #f2f2f2; padding: 4px 8px 4px 8px; text-align: left; }\ntd { border: 1px solid #cccccc; padding: 4px 8px 4px 8px; }\nhr { border-bottom: 1px solid #bbbbbb; margin: 18px 0 18px 0; }\nimg { margin: 6px 0 6px 0; max-width: 100%; }\n.alert { border: none; border-left: 4px solid #666666; padding: 8px 12px 4px 12px; margin: 9px 0 9px 0; font-size: 11.5px; }\n.alert p { margin: 0 0 6px 0; text-indent: 0; }\n.alert-title { font-weight: bold; }\n.alert-note { border-left: 4px solid #2e6f9e; }\n.alert-tip { border-left: 4px solid #2e9e5b; }\n.alert-important { border-left: 4px solid #7e4fd0; }\n.alert-warning { border-left: 4px solid #d0962e; }\n.alert-caution { border-left: 4px solid #d0342c; }\n.footnotes { border-top: 1px solid #bbbbbb; margin-top: 18px; font-size: 10px; color: #444444; }\nu, ins { text-decoration: underline; }\nq { font-style: italic; }\nabbr { border-bottom: 1px dotted #888888; }\nmark { background-color: #fff3a3; }\nkbd { font-family: \"DejaVu Sans Mono\", \"Liberation Mono\", Courier, monospace; font-size: 10px; background-color: #f2f2f2; border: 1px solid #cccccc; padding: 0px 3px 0px 3px; }\n.math { font-family: \"DejaVu Serif\", Georgia, serif; font-style: italic; }\n.math.block { display: block; text-align: center; margin: 6px 0 6px 0; }\npre.math { text-align: left; background-color: transparent; border: none; line-height: 1.1; margin: 6px auto 6px auto; }\n#{TOC_CSS}", "dark" => "body { font-family: \"DejaVu Sans\", Helvetica, Arial, sans-serif; font-size: 11px; line-height: 1.5; color: #e8e8e8; background-color: #121212; margin: 0; padding: 0; }\nh1 { font-size: 25px; font-weight: bold; margin: 20px 0 12px 0; }\nh2 { font-size: 19px; font-weight: bold; margin: 18px 0 10px 0; }\nh3 { font-size: 15px; font-weight: bold; margin: 16px 0 8px 0; }\nh4 { font-size: 12px; font-weight: bold; margin: 14px 0 8px 0; }\nh5 { font-size: 11px; font-weight: bold; margin: 12px 0 6px 0; }\nh6 { font-size: 11px; font-weight: bold; color: #aaaaaa; margin: 12px 0 6px 0; }\np { margin: 0 0 9px 0; }\na { color: #8ab4f8; }\nstrong { font-weight: bold; }\nem { font-style: italic; }\ndel { text-decoration: line-through; }\nul, ol { margin: 0 0 9px 0; padding-left: 18px; }\nul ul, ol ol, ul ol, ol ul { margin: 0 0 9px 0; padding-left: 16px; }\nli { margin: 0 0 3px 0; }\nli.task-list-item { list-style-type: none; }\nblockquote { border-left: 4px solid #444444; margin: 9px 0 9px 0; padding: 2px 0 2px 12px; color: #bbbbbb; }\nblockquote p { margin: 0 0 6px 0; }\npre { font-family: \"DejaVu Sans Mono\", \"Liberation Mono\", Courier, monospace; font-size: 10px; background-color: #1e1e1e; border: 1px solid #333333;\n margin: 9px 0 9px 0; padding: 7px 9px 7px 9px; overflow: hidden;\n white-space: pre-wrap; }\ntable { overflow: hidden; }\ncode { font-family: \"DejaVu Sans Mono\", \"Liberation Mono\", Courier, monospace; font-size: 10px; background-color: #1e1e1e; padding: 0px 2px 0px 2px; }\npre code { background-color: transparent; padding: 0; }\ntable { border-spacing: 0; margin: 9px 0 9px 0; font-size: 10.5px; }\nth { font-weight: bold; border: 1px solid #444444; background-color: #1e1e1e; padding: 4px 8px 4px 8px; text-align: left; }\ntd { border: 1px solid #444444; padding: 4px 8px 4px 8px; }\nhr { border-bottom: 1px solid #444444; margin: 18px 0 18px 0; }\nimg { margin: 6px 0 6px 0; max-width: 100%; }\n.alert { border: none; border-left: 4px solid #888888; padding: 8px 12px 4px 12px; margin: 9px 0 9px 0; }\n.alert p { margin: 0 0 6px 0; }\n.alert-title { font-weight: bold; }\n.alert-note { border-left: 4px solid #6fa8dc; }\n.alert-tip { border-left: 4px solid #6fbf8b; }\n.alert-important { border-left: 4px solid #a98fe0; }\n.alert-warning { border-left: 4px solid #e0b05e; }\n.alert-caution { border-left: 4px solid #e0645a; }\n.footnotes { border-top: 1px solid #444444; margin-top: 18px; font-size: 10px; color: #bbbbbb; }\nu, ins { text-decoration: underline; }\nq { font-style: italic; }\nabbr { border-bottom: 1px dotted #888888; }\nmark { background-color: #6b5b1e; }\nkbd { font-family: \"DejaVu Sans Mono\", \"Liberation Mono\", Courier, monospace; font-size: 10px; background-color: #1e1e1e; border: 1px solid #444444; padding: 0px 3px 0px 3px; }\n.math { font-family: \"DejaVu Serif\", Georgia, serif; font-style: italic; }\n.math.block { display: block; text-align: center; margin: 6px 0 6px 0; }\npre.math { text-align: left; background-color: transparent; border: none; line-height: 1.1; margin: 6px auto 6px auto; }\n#{TOC_CSS}", "sepia" => "body { font-family: \"DejaVu Serif\", Georgia, serif; font-size: 12px; line-height: 1.6; color: #5b4636; background-color: #f4ecd8; margin: 0; padding: 0; }\nh1 { font-size: 24px; font-weight: bold; margin: 20px 0 12px 0; }\nh2 { font-size: 19px; font-weight: bold; margin: 18px 0 10px 0; }\nh3 { font-size: 15px; font-weight: bold; margin: 16px 0 8px 0; }\nh4 { font-size: 12px; font-weight: bold; margin: 14px 0 8px 0; }\nh5 { font-size: 11.5px; font-weight: bold; margin: 12px 0 6px 0; }\nh6 { font-size: 11.5px; font-style: italic; color: #8a7a5a; margin: 12px 0 6px 0; }\np { margin: 0 0 9px 0; }\na { color: #7a5c2e; }\nstrong { font-weight: bold; }\nem { font-style: italic; }\ndel { text-decoration: line-through; }\nul, ol { margin: 0 0 9px 0; padding-left: 18px; }\nul ul, ol ol, ul ol, ol ul { margin: 0 0 9px 0; padding-left: 16px; }\nli { margin: 0 0 3px 0; }\nli.task-list-item { list-style-type: none; }\nblockquote { border-left: 4px solid #d3c3a0; margin: 9px 0 9px 0; padding: 2px 0 2px 12px; color: #6e5744; }\nblockquote p { margin: 0 0 6px 0; }\npre { font-family: \"DejaVu Sans Mono\", \"Liberation Mono\", Courier, monospace; font-size: 10px; background-color: #ece1c6; border: 1px solid #d3c3a0;\n margin: 9px 0 9px 0; padding: 7px 9px 7px 9px; overflow: hidden;\n white-space: pre-wrap; }\ntable { overflow: hidden; }\ncode { font-family: \"DejaVu Sans Mono\", \"Liberation Mono\", Courier, monospace; font-size: 10px; background-color: #ece1c6; padding: 0px 2px 0px 2px; }\npre code { background-color: transparent; padding: 0; }\ntable { border-spacing: 0; margin: 9px 0 9px 0; font-size: 10.5px; }\nth { font-weight: bold; border: 1px solid #d3c3a0; background-color: #ece1c6; padding: 4px 8px 4px 8px; text-align: left; }\ntd { border: 1px solid #d3c3a0; padding: 4px 8px 4px 8px; }\nhr { border-bottom: 1px solid #d3c3a0; margin: 18px 0 18px 0; }\nimg { margin: 6px 0 6px 0; max-width: 100%; }\n.alert { border: none; border-left: 4px solid #8a7a5a; padding: 8px 12px 4px 12px; margin: 9px 0 9px 0; }\n.alert p { margin: 0 0 6px 0; }\n.alert-title { font-weight: bold; }\n.alert-note { border-left: 4px solid #4e6f8e; }\n.alert-tip { border-left: 4px solid #4e8e5e; }\n.alert-important { border-left: 4px solid #7a5fa8; }\n.alert-warning { border-left: 4px solid #a8823e; }\n.alert-caution { border-left: 4px solid #a84e42; }\n.footnotes { border-top: 1px solid #d3c3a0; margin-top: 18px; font-size: 10px; color: #6e5744; }\nu, ins { text-decoration: underline; }\nq { font-style: italic; }\nabbr { border-bottom: 1px dotted #8a7a5a; }\nmark { background-color: #ecd98b; }\nkbd { font-family: \"DejaVu Sans Mono\", \"Liberation Mono\", Courier, monospace; font-size: 10px; background-color: #ece1c6; border: 1px solid #d3c3a0; padding: 0px 3px 0px 3px; }\n.math { font-family: \"DejaVu Serif\", Georgia, serif; font-style: italic; }\n.math.block { display: block; text-align: center; margin: 6px 0 6px 0; }\npre.math { text-align: left; background-color: transparent; border: none; line-height: 1.1; margin: 6px auto 6px auto; }\n#{TOC_CSS}"}
TOC_CSS = ".toc-title { font-size: 1.7em; font-weight: bold; margin: 0 0 1em 0; }\n.toc-entry { margin: 0 0 2px 0; }\n.toc-entry a { color: inherit; text-decoration: none; }\n.toc-page { float: right; }\n.toc-level-2 { margin-left: 1.5em; }\n.toc-level-3 { margin-left: 3em; }\n.toc-level-4 { margin-left: 4.5em; }\n.toc-level-5 { margin-left: 6em; }\n.toc-level-6 { margin-left: 7.5em; }"

Table of contents layout, interpolated into every style so they keep covering the same property set: entries are blocks with a right-floating page number (litehtml has no ::after or leader(), so no dot leaders), and colors inherit so one block fits all four looks. No page-break rule here on purpose: the shim's page-break properties inherit down the tree, and a break on the nav would give every entry its own page — the break to the body rides an empty div inject_toc adds instead.

Class Method Summary