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

Instance Method Details

#arrange_block(node, &block) ⇒ Object

The extent yielded here is exact across page breaks.



142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/asciidoctor/rhrev/change_bars.rb', line 142

def arrange_block node, &block
  if @change_bar_attr && !scratch? &&
      (ARRANGED_BLOCK_CONTEXTS.include? node.context) && (change_bar? 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
        ink_change_bar_for_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.



167
168
169
170
171
172
173
174
# File 'lib/asciidoctor/rhrev/change_bars.rb', line 167

def bracket_change_bar node
  if @change_bar_attr && !scratch? && (change_bar? node)
    bar_from = { page: page_number, cursor: cursor }
  end
  result = yield
  ink_change_bar bar_from, { page: page_number, cursor: cursor } if bar_from
  result
end

#change_bar?(node) ⇒ Boolean

Returns:

  • (Boolean)


52
53
54
55
# File 'lib/asciidoctor/rhrev/change_bars.rb', line 52

def change_bar? node
  @change_bar_attr && node.respond_to?(:attributes) &&
    node.attributes && (node.attributes.key? @change_bar_attr)
end

#change_bar_settingsObject

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
  @change_bar_settings ||= begin
    overrides = @change_bar_overrides || {}
    theme = @theme
    side = overrides[:side] || theme&.rhrev_change_bars_side ||
      (@media == 'prepress' ? 'outer' : 'right')
    {
      color: (overrides[:color] || theme&.rhrev_change_bars_color || 'FF0000').to_s.delete_prefix('#').upcase,
      width: (overrides[:width] || theme&.rhrev_change_bars_width || 2).to_f,
      offset: (overrides[:offset] || theme&.rhrev_change_bars_offset || 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.



91
92
93
94
95
96
97
98
99
100
# File 'lib/asciidoctor/rhrev/change_bars.rb', line 91

def change_bar_x 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.



72
73
74
75
76
77
78
79
80
# File 'lib/asciidoctor/rhrev/change_bars.rb', line 72

def enter_change_bar_section node
  return unless @change_bar_attr
  @change_bar_section_stack.each do |ancestor|
    next if ancestor.option? 'recursive'
    next unless (start_pos = take_change_bar_start ancestor)
    ink_change_bar start_pos, { page: page_number, cursor: cursor }
  end
  @change_bar_section_stack.push node
end

#exit_change_bar_section(node) ⇒ Object



82
83
84
85
# File 'lib/asciidoctor/rhrev/change_bars.rb', line 82

def exit_change_bar_section 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 init_change_bars 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.



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/asciidoctor/rhrev/change_bars.rb', line 104

def ink_change_bar 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 = change_bar_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 [(change_bar_x pgnum, settings), top], width: settings[:width], height: height do
        fill_bounds settings[:color]
      end
    end
  end
end

#ink_change_bar_for_extent(extent) ⇒ Object



122
123
124
125
126
# File 'lib/asciidoctor/rhrev/change_bars.rb', line 122

def ink_change_bar_for_extent extent
  return unless extent
  ink_change_bar({ page: extent.from.page, cursor: extent.from.cursor },
    { page: extent.to.page, cursor: extent.to.cursor })
end

#ink_chapter_title(node, title, opts = {}) ⇒ Object



183
184
185
186
# File 'lib/asciidoctor/rhrev/change_bars.rb', line 183

def ink_chapter_title node, title, opts = {}
  record_change_bar_start node if @change_bar_attr && !scratch? && (change_bar? node)
  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.



178
179
180
181
# File 'lib/asciidoctor/rhrev/change_bars.rb', line 178

def ink_general_heading node, title, opts = {}
  record_change_bar_start node if @change_bar_attr && !scratch? && (change_bar? node)
  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



190
191
192
193
# File 'lib/asciidoctor/rhrev/change_bars.rb', line 190

def ink_part_title node, title, opts = {}
  record_change_bar_start node if @change_bar_attr && !scratch? && (change_bar? node)
  super
end

#record_change_bar_start(node) ⇒ Object



57
58
59
# File 'lib/asciidoctor/rhrev/change_bars.rb', line 57

def record_change_bar_start node
  @change_bar_starts[node] = { page: page_number, cursor: cursor }
end

#take_change_bar_start(node) ⇒ Object



61
62
63
# File 'lib/asciidoctor/rhrev/change_bars.rb', line 61

def take_change_bar_start node
  @change_bar_starts&.delete node
end