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.

Instance Method Summary collapse

Instance Method Details

#arrange_block(node, &block) ⇒ Object

Examples and listings are arranged blocks: the extent yielded here is exact across page breaks. Tables and images never reach this hook with their own node, so they are handled in the converter overrides instead.



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/asciidoctor/rhrev/change_bars.rb', line 128

def arrange_block node, &block
  if @change_bar_attr && !scratch? &&
      (node.context == :example || node.context == :listing) && (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

#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.



88
89
90
91
92
93
94
95
96
97
# File 'lib/asciidoctor/rhrev/change_bars.rb', line 88

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.



70
71
72
73
74
75
76
77
# File 'lib/asciidoctor/rhrev/change_bars.rb', line 70

def enter_change_bar_section node
  return unless @change_bar_attr
  @change_bar_section_stack.each do |ancestor|
    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



79
80
81
82
# File 'lib/asciidoctor/rhrev/change_bars.rb', line 79

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.



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

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



119
120
121
122
123
# File 'lib/asciidoctor/rhrev/change_bars.rb', line 119

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



153
154
155
156
# File 'lib/asciidoctor/rhrev/change_bars.rb', line 153

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.



148
149
150
151
# File 'lib/asciidoctor/rhrev/change_bars.rb', line 148

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



160
161
162
163
# File 'lib/asciidoctor/rhrev/change_bars.rb', line 160

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