Module: Asciidoctor::PDF::Rhrev::ChangeBars
- Included in:
- Converter
- Defined in:
- lib/asciidoctor/rhrev/change_bars.rb
Overview
Inks a vertical change bar in the page margin next to blocks marked with the current revision (revnumber 1.2 => attribute rhrev1-2). Enabled by the rhrev-change-bars document attribute.
Styling comes from the PDF theme by default (rhrev_change_bars category: color, width, offset, side), overridable per document via the rhrev-change-bars-color/-width/-offset/-side attributes.
Constant Summary collapse
- ARRANGED_BLOCK_CONTEXTS =
Contexts that render through this hook unconditionally in asciidoctor-pdf: examples and listings (original coverage), plus admonition, quote, verse, and sidebar, so widening this one check is the whole fix for their bar-inking. open is deliberately NOT here: asciidoctor-pdf's convert_open only calls arrange_block when the block has a title, an id, or the unbreakable option, a plain open block skips it and just traverses directly, so it needs its own manual bracket instead (see Converter#convert_open), the same shape as paragraphs and the three list types below. Tables and images never reach this hook with their own node either and are handled the same way.
[:example, :listing, :admonition, :quote, :verse, :sidebar].freeze
Instance Method Summary collapse
-
#arrange_block(node, &block) ⇒ Object
The extent yielded here is exact across page breaks.
-
#bracket_change_bar(node) ⇒ Object
Brackets a block conversion with a before/after page-cursor snapshot and inks the bar across whatever it spanned, for converters that don't route through arrange_block: paragraphs and the three list types (convert_paragraph, convert_ulist, convert_olist, convert_dlist).
- #change_bar?(node) ⇒ Boolean
-
#change_bar_settings ⇒ Object
Resolved lazily on first ink: the theme (and @media/@folio_placement) are not loaded yet when init_change_bars runs; upstream convert_document sets them up after rhrev's pre-super initialization.
-
#change_bar_x(pgnum, settings) ⇒ Object
The offset measures the gap between the text edge and the near edge of the bar.
-
#enter_change_bar_section(node) ⇒ Object
A section's bar covers only its own directly-owned content, not any nested child sections/chapters: as soon as a child section begins rendering, close out the bar of every still-open ancestor section right here, at the boundary between the parent's own content and its first child section.
- #exit_change_bar_section(node) ⇒ Object
- #init_change_bars(doc) ⇒ Object
-
#ink_change_bar(from, to) ⇒ Object
Paints the bar page by page; from/to are { page:, cursor: }.
- #ink_change_bar_for_extent(extent) ⇒ Object
- #ink_chapter_title(node, title, opts = {}) ⇒ Object
-
#ink_general_heading(node, title, opts = {}) ⇒ Object
The heading ink methods run after arrange_heading / start_new_chapter have settled the page, so this is the first accurate start position.
-
#ink_part_title(node, title, opts = {}) ⇒ Object
Upstream defines ink_part_title as an alias copy of ink_chapter_title, so it must be overridden separately.
- #record_change_bar_start(node) ⇒ Object
-
#record_section_heading_position(node) ⇒ Object
A section's own cursor position at heading-ink time, before its body (potentially spanning several pages) renders.
- #take_change_bar_start(node) ⇒ Object
- #take_section_heading_position(node) ⇒ Object
Instance Method Details
#arrange_block(node, &block) ⇒ Object
The extent yielded here is exact across page breaks.
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 |
# File 'lib/asciidoctor/rhrev/change_bars.rb', line 165 def arrange_block node, &block if @change_bar_attr && !scratch? && (ARRANGED_BLOCK_CONTEXTS.include? node.context) && ( node) super node do |extent| if scratch? # measurement pass: upstream instance_execs this wrapper on the scratch # document, so re-bind the original block to it the same way instance_exec(&block) else block.call extent extent end end else super end end |
#bracket_change_bar(node) ⇒ Object
Brackets a block conversion with a before/after page-cursor snapshot and inks the bar across whatever it spanned, for converters that don't route through arrange_block: paragraphs and the three list types (convert_paragraph, convert_ulist, convert_olist, convert_dlist). Same shape as convert_table/convert_image, pulled out as a shared helper since none of the four need any special- casing the others don't.
190 191 192 193 194 195 196 197 |
# File 'lib/asciidoctor/rhrev/change_bars.rb', line 190 def node if @change_bar_attr && !scratch? && ( node) = { page: page_number, cursor: cursor } end result = yield , { page: page_number, cursor: cursor } if result end |
#change_bar?(node) ⇒ Boolean
52 53 54 55 |
# File 'lib/asciidoctor/rhrev/change_bars.rb', line 52 def node @change_bar_attr && node.respond_to?(:attributes) && node.attributes && (node.attributes.key? @change_bar_attr) end |
#change_bar_settings ⇒ Object
Resolved lazily on first ink: the theme (and @media/@folio_placement) are not loaded yet when init_change_bars runs; upstream convert_document sets them up after rhrev's pre-super initialization. Precedence: document attribute > theme key > built-in default.
37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
# File 'lib/asciidoctor/rhrev/change_bars.rb', line 37 def @change_bar_settings ||= begin overrides = @change_bar_overrides || {} theme = @theme side = overrides[:side] || theme&. || (@media == 'prepress' ? 'outer' : 'right') { color: (overrides[:color] || theme&. || 'FF0000').to_s.delete_prefix('#').upcase, width: (overrides[:width] || theme&. || 2).to_f, offset: (overrides[:offset] || theme&. || 16).to_f, side: side.to_s, } end end |
#change_bar_x(pgnum, settings) ⇒ Object
The offset measures the gap between the text edge and the near edge of the bar. Sides recto/verso are determined by the physical page number (honoring pdf-folio-placement inversion), matching prepress physical folio placement.
114 115 116 117 118 119 120 121 122 123 |
# File 'lib/asciidoctor/rhrev/change_bars.rb', line 114 def pgnum, settings case settings[:side] when 'right' on_right = true when 'outer', 'inner' recto = (page_side pgnum, @folio_placement && @folio_placement[:inverted]) == :recto on_right = settings[:side] == 'outer' ? recto : !recto end on_right ? bounds.right + settings[:offset] : bounds.left - settings[:offset] - settings[:width] end |
#enter_change_bar_section(node) ⇒ Object
A section's bar covers only its own directly-owned content, not any nested child sections/chapters: as soon as a child section begins rendering, close out the bar of every still-open ancestor section right here, at the boundary between the parent's own content and its first child section. A section carrying the recursive option keeps its bar open across child boundaries; convert_section closes it after the whole section, children included, has rendered.
95 96 97 98 99 100 101 102 103 |
# File 'lib/asciidoctor/rhrev/change_bars.rb', line 95 def node return unless @change_bar_attr @change_bar_section_stack.each do |ancestor| next if ancestor.option? 'recursive' next unless (start_pos = ancestor) start_pos, { page: page_number, cursor: cursor } end @change_bar_section_stack.push node end |
#exit_change_bar_section(node) ⇒ Object
105 106 107 108 |
# File 'lib/asciidoctor/rhrev/change_bars.rb', line 105 def node return unless @change_bar_attr @change_bar_section_stack.pop end |
#init_change_bars(doc) ⇒ Object
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
# File 'lib/asciidoctor/rhrev/change_bars.rb', line 14 def doc @change_bar_attr = nil @change_bar_settings = nil return unless doc.attr? 'rhrev-change-bars' # Without a revnumber there is no "current" revision to match return unless (revnumber = doc.attr 'revnumber') @change_bar_attr = %(#{@revision_prefix}#{revnumber.to_s.tr '.', '-'}) # Capture attribute values only; holding the Document in an ivar here # would leak it into the scratch prototype, which must stay marshalable @change_bar_overrides = { color: (doc.attr 'rhrev-change-bars-color'), width: (doc.attr 'rhrev-change-bars-width'), offset: (doc.attr 'rhrev-change-bars-offset'), side: (doc.attr 'rhrev-change-bars-side'), } @change_bar_starts = {} @change_bar_section_stack = [] end |
#ink_change_bar(from, to) ⇒ Object
Paints the bar page by page; from/to are { page:, cursor: }. Called immediately after the block is rendered, so all spanned pages exist.
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 |
# File 'lib/asciidoctor/rhrev/change_bars.rb', line 127 def from, to return if scratch? || from.nil? || to.nil? return if to[:page] < from[:page] return if to[:page] == from[:page] && to[:cursor] >= from[:cursor] settings = float do (from[:page]..to[:page]).each do |pgnum| go_to_page pgnum unless page_number == pgnum top = pgnum == from[:page] ? from[:cursor] : bounds.top bottom = pgnum == to[:page] ? to[:cursor] : 0 next if (height = top - bottom) <= 0 bounding_box [( pgnum, settings), top], width: settings[:width], height: height do fill_bounds settings[:color] end end end end |
#ink_change_bar_for_extent(extent) ⇒ Object
145 146 147 148 149 |
# File 'lib/asciidoctor/rhrev/change_bars.rb', line 145 def extent return unless extent ({ page: extent.from.page, cursor: extent.from.cursor }, { page: extent.to.page, cursor: extent.to.cursor }) end |
#ink_chapter_title(node, title, opts = {}) ⇒ Object
207 208 209 210 211 |
# File 'lib/asciidoctor/rhrev/change_bars.rb', line 207 def ink_chapter_title node, title, opts = {} node if @change_bar_attr && !scratch? && ( node) record_section_heading_position node unless scratch? super end |
#ink_general_heading(node, title, opts = {}) ⇒ Object
The heading ink methods run after arrange_heading / start_new_chapter have settled the page, so this is the first accurate start position.
201 202 203 204 205 |
# File 'lib/asciidoctor/rhrev/change_bars.rb', line 201 def ink_general_heading node, title, opts = {} node if @change_bar_attr && !scratch? && ( node) record_section_heading_position node unless scratch? super end |
#ink_part_title(node, title, opts = {}) ⇒ Object
Upstream defines ink_part_title as an alias copy of ink_chapter_title, so it must be overridden separately
215 216 217 218 219 |
# File 'lib/asciidoctor/rhrev/change_bars.rb', line 215 def ink_part_title node, title, opts = {} node if @change_bar_attr && !scratch? && ( node) record_section_heading_position node unless scratch? super end |
#record_change_bar_start(node) ⇒ Object
57 58 59 |
# File 'lib/asciidoctor/rhrev/change_bars.rb', line 57 def node @change_bar_starts[node] = { page: page_number, cursor: cursor } end |
#record_section_heading_position(node) ⇒ Object
A section's own cursor position at heading-ink time, before its body (potentially spanning several pages) renders. Unconditional, not gated on change bars being enabled at all: Converter# convert_section's own revision-history dest needs this, pairing it with pdf-page-start (also captured at this same moment, upstream's own convert_section, right before the heading ink call this method wraps) so both fields describe the section's own start consistently. A page-spanning section's cursor read after the whole body renders belongs to wherever that body finished, not the section's own start page; two entries sharing the same start page then sort by a y value that was never on that page at all, reversing their true top-to-bottom order, confirmed directly against a real document.
78 79 80 81 82 |
# File 'lib/asciidoctor/rhrev/change_bars.rb', line 78 def record_section_heading_position node return unless node.context == :section @section_heading_positions ||= {} @section_heading_positions[node] = cursor end |
#take_change_bar_start(node) ⇒ Object
61 62 63 |
# File 'lib/asciidoctor/rhrev/change_bars.rb', line 61 def node @change_bar_starts&.delete node end |
#take_section_heading_position(node) ⇒ Object
84 85 86 |
# File 'lib/asciidoctor/rhrev/change_bars.rb', line 84 def take_section_heading_position node @section_heading_positions&.delete node end |