Class: Asciidoctor::BeautifyUri::PdfConverter
- Inherits:
-
Object
- Object
- Asciidoctor::BeautifyUri::PdfConverter
- Defined in:
- lib/asciidoctor/beautify_uri/pdf_converter.rb
Overview
Draws the PDF card directly with Prawn — see pdf_card.rb for why this replaced an earlier version that composed the card as a real AsciiDoc table and let asciidoctor-pdf's own table converter render it. Every position here (thumbnail, title, subtitle, link line, frame) is one this method chooses explicitly, in the same coordinate space, rather than routed through a table cell's own layout — that's what fixes the thumbnail/title top-alignment (both start at the same y, full stop) and what makes rounding the thumbnail's corners a plain Prawn clip path instead of an SVG-wrapping detour with its own scale math to get wrong.
Vertical rhythm is anchored to real font metrics, not an arbitrary line pitch: each line of text is placed by its baseline (draw_text, not text_box — see the note below), lines are spaced a full font.height apart (ascender+descender+line gap — a font's own natural single-line height, not asciidoctor-pdf's tighter body-text pitch, which is tuned for paragraphs of running text, not a standalone few-line card) plus theme-configurable slack, the brand icon spans cap-height to baseline of the link line it sits next to, and the thumbnail spans the title's cap-height down to the link line's baseline — i.e. it lines up with the visible top and bottom of the text stack, not the extra headroom/footroom a font reserves for ascenders and descenders that never actually appear in this text.
Every visual knob (spacing, border, corner radii, font styles) reads
from a uri_card theme namespace with a || DEFAULT fallback, the
same convention asciidoctor-lists-extended uses for its own list-of
keys — see doc/modules/ROOT/pages/pdf-theming.adoc for the full list.
An unset theme behaves exactly as if these were still hardcoded
constants. The one exception is a provider-mandated thumbnail corner
radius (e.g. Spotify's design guidelines) — a real brand requirement,
not a preference, so it overrides the theme key entirely; see
pdf_card.rb and providers/spotify.rb.
Constant Summary collapse
- DEFAULT_MARGIN_INNER_PT =
8- DEFAULT_ICON_GAP_PT =
4- DEFAULT_BORDER_COLOR =
'B3B3B3'- DEFAULT_BORDER_WIDTH_PT =
0.5- DEFAULT_BORDER_RADIUS_PT =
6- DEFAULT_THUMBNAIL_BORDER_RADIUS_PT =
4- DEFAULT_LINE_SPACING =
font.height (ascender+descender+line gap) is a font's own natural single-line pitch — already looser than asciidoctor-pdf's own tight body-text line height, but still reads as cramped for a card that's nothing but a few short stacked lines rather than running prose.
1.35- DEFAULT_TITLE_FONT_STYLE =
:bold- DEFAULT_SUBTITLE_FONT_STYLE =
:normal- DEFAULT_LINK_FONT_STYLE =
:bold- ELLIPSIS =
'…'
Instance Method Summary collapse
Instance Method Details
#convert_uri_card(node) ⇒ Object
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 |
# File 'lib/asciidoctor/beautify_uri/pdf_converter.rb', line 57 def convert_uri_card node add_dest_for_block node if node.id title = node.attr 'uri-card-title' subtitle = node.attr 'uri-card-subtitle' link_text = node.attr 'uri-card-link-text' target_url = node.attr 'uri-card-target-url' icon_path = node.attr 'uri-card-icon-path' thumb_url = node.attr 'uri-card-thumb-url' lines = subtitle ? 3 : 2 margin_inner = theme_num 'margin_inner', DEFAULT_MARGIN_INNER_PT icon_gap = theme_num 'icon_gap', DEFAULT_ICON_GAP_PT title_style = theme_style 'title_font_style', DEFAULT_TITLE_FONT_STYLE subtitle_style = theme_style 'subtitle_font_style', DEFAULT_SUBTITLE_FONT_STYLE link_style = theme_style 'link_font_style', DEFAULT_LINK_FONT_STYLE line_h = font.height * (theme_num 'line_spacing', DEFAULT_LINE_SPACING) title_cap_h = nil with_style(title_style) { title_cap_h = cap_height_pt } link_cap_h = nil with_style(link_style) { link_cap_h = cap_height_pt } # No photographic thumbnail (thumbnail=false, or the provider simply # didn't resolve one): the provider's own brand mark fills that # column at full size instead of sitting small next to the link # text, so the card never has an empty thumbnail column with no # visual identity at all. The small link-line icon is then # redundant — the same mark already anchors the card much larger — # and dropped. has_photo = !thumb_url.nil? link_icon = has_photo ? (build_icon icon_path, link_cap_h) : nil link_icon_w = link_icon ? link_icon.document.sizing.output_width : 0 # The visible span of the meta stack: the title's own cap-height # (its capital letters' true top) down to the link line's baseline — # not (lines * line_h), which would also count the title's ascender # headroom above cap-height and the link line's descender footroom # below its baseline, neither of which this stack actually uses. text_h = title_cap_h + (line_h * (lines - 1)) logo_icon = has_photo ? nil : (build_icon icon_path, text_h) thumb_w = if has_photo thumbnail_width node, text_h elsif logo_icon logo_icon.document.sizing.output_width else 0 end # The printable area this card must never exceed — at a large # enough base font size (or an unusually long title/subtitle/link), # text sized only against its own natural width can run past the # page margin entirely rather than merely looking a little cramped. # Designed to comfortably fit a 25-character playlist/album name, an # 18-character artist name, and a 23-character track name (Spotify's # own typical metadata lengths) well within this budget at ordinary # font sizes; fit_text below is the actual backstop for whatever # exceeds it regardless of why. max_content_w = bounds.width - (margin_inner * 2) thumb_w = [thumb_w, max_content_w * 0.5].min if thumb_w.positive? max_text_w = thumb_w.positive? ? max_content_w - thumb_w - margin_inner : max_content_w max_link_text_w = [max_text_w - link_icon_w - icon_gap, 1].max title_w = nil with_style(title_style) { title = fit_text title, max_text_w; title_w = width_of title } link_w = nil with_style(link_style) { link_text = fit_text link_text, max_link_text_w; link_w = width_of link_text } # with_style (like Prawn's own font(name) { block }) returns the font # object, not the block's result — assign via an outer local instead # of the method's return value. subtitle_w = 0.0 with_style(subtitle_style) { subtitle = fit_text subtitle, max_text_w; subtitle_w = width_of subtitle } if subtitle text_col_w = [title_w, link_w + link_icon_w + icon_gap, subtitle_w].max # One margin_inner value does double duty, matching how it reads # visually: the gap from the frame to the thumbnail (or to the text # column, without one) is the same gap as from the thumbnail to the # text column — a single consistent inner margin, not two knobs that # happen to usually agree. content_w = thumb_w.positive? ? thumb_w + margin_inner + text_col_w : text_col_w card_w = [content_w + (margin_inner * 2), bounds.width].min card_h = text_h + (margin_inner * 2) advance_page if card_h > cursor && !at_page_top? top = cursor left = bounds.left content_left = left + margin_inner # The line this card's own cap-height/thumbnail-top reference is # measured from — see text_h above. content_top = top - margin_inner draw_frame [left, top], card_w, card_h text_left = content_left if thumb_w.positive? if has_photo draw_thumbnail node, thumb_url, target_url, [content_left, content_top], thumb_w, text_h else draw_logo logo_icon, target_url, [content_left, content_top], thumb_w, text_h end text_left += thumb_w + margin_inner end title_baseline = content_top - title_cap_h link_baseline = title_baseline - (line_h * (lines - 1)) # draw_text (baseline-anchored), not text_box: text_box's own # line-fit check needs a touch more box height than a bare line # metric to accept a single line at all (confirmed empirically — a # box exactly one nominal line tall silently dropped the whole line # as unrendered "leftover" instead of drawing it), and every line # here is already sized (and, if need be, fit_text-truncated) to fit # within text_col_w, so there is nothing left for a box's # wrap/truncate machinery to do anyway. with_style(title_style) { draw_text title, at: [text_left, title_baseline] } with_style(subtitle_style) { draw_text subtitle, at: [text_left, title_baseline - line_h] } if subtitle draw_link_line link_icon, link_icon_w, icon_gap, link_text, link_style, target_url, text_left, link_baseline, text_col_w, link_cap_h move_cursor_to top - card_h theme_margin :block, :bottom, (next_enclosed_block node) end |