Module: Idml::Render::Footnote
- Defined in:
- lib/idml/render/footnote.rb
Overview
Footnote semantics: story-scoped numbering, body-text marker synthesis, footnote-paragraph extraction, and the geometry of the bottom-of-frame footnote area (height reservation + separator rule). Emission of footnote text lines stays in TextFrameRenderer — it owns all canvas text drawing — while this module owns everything footnote-specific (MECE).
Numbering is sequential per story, honoring FootnoteOption's StartAt / Prefix / Suffix when the package declares them. Document-wide continuous numbering and per-section restart are not modeled.
Defined Under Namespace
Classes: Counter, Entry, PositionedRun
Constant Summary collapse
- DEFAULT_RULE_WEIGHT =
0.5
- DEFAULT_RULE_GAP =
3.0- MARKER_TEXT_SEPARATOR =
" "
Class Method Summary collapse
- .counter_for(option) ⇒ Object
-
.emit_separator(canvas, frame, y, option) ⇒ Object
Emits the separator rule at the top of the footnote area.
-
.extract(element, number, condition_filter: nil, style_lookup: nil, option: nil) ⇒ Object
Extracts the footnote's paragraphs via StyleResolver and prefixes the first paragraph's first run with the marker text ("1 ", "n2." …) so the footnote reads as numbered.
-
.layout_entries(entries, frame, font, top_y, option = nil) ⇒ Object
Lays out all entries' paragraphs top-down starting at
top_y. -
.marker_run(number, base, paragraphs, option) ⇒ Object
Builds the superscript marker run emitted into the body text where the footnote anchors.
-
.marker_text(number, option) ⇒ Object
Marker text shown in the body text (and prefixed to the footnote's first paragraph): Prefix + number + Suffix.
-
.option(package) ⇒ Object
Returns the package's FootnoteOption (Preferences.xml), or nil when the package doesn't declare one.
-
.reserved_height(entries, font, frame, option = nil) ⇒ Object
Height to reserve at the frame bottom for the entries: separator gap + the measured paragraph stack.
-
.rule_gap(option) ⇒ Object
Vertical distance between the footnote area's top edge and the first footnote line — room for the separator rule.
Class Method Details
.counter_for(option) ⇒ Object
51 52 53 |
# File 'lib/idml/render/footnote.rb', line 51 def self.counter_for(option) Counter.new(option&.start_at) end |
.emit_separator(canvas, frame, y, option) ⇒ Object
Emits the separator rule at the top of the footnote area. Honors FootnoteOption RuleOn / RuleLineWeight / RuleWidth / RuleLeftIndent; defaults approximate InDesign (0.5pt rule spanning the column).
190 191 192 193 194 195 196 197 198 |
# File 'lib/idml/render/footnote.rb', line 190 def self.emit_separator(canvas, frame, y, option) return if option&.rule_on == false thickness = separator_weight(option) return unless thickness.positive? stroke_rule(canvas, separator_left(frame, option), y, separator_width(frame, option), thickness) end |
.extract(element, number, condition_filter: nil, style_lookup: nil, option: nil) ⇒ Object
Extracts the footnote's paragraphs via StyleResolver and prefixes the first paragraph's first run with the marker text ("1 ", "n2." …) so the footnote reads as numbered.
80 81 82 83 84 85 86 87 88 89 |
# File 'lib/idml/render/footnote.rb', line 80 def self.extract(element, number, condition_filter: nil, style_lookup: nil, option: nil) paragraphs = StyleResolver.extract_container_paragraphs( element, condition_filter: condition_filter, style_lookup: style_lookup ) marker = marker_text(number, option) prepend_marker(paragraphs, marker) unless marker.empty? paragraphs end |
.layout_entries(entries, frame, font, top_y, option = nil) ⇒ Object
Lays out all entries' paragraphs top-down starting at
top_y. Returns [positioned_runs, bottom_y] where
bottom_y is the cursor after the last line — the same walk
drives both height measurement and rendering, so the
reserved area always matches what gets drawn.
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
# File 'lib/idml/render/footnote.rb', line 105 def self.layout_entries(entries, frame, font, top_y, option = nil) positioned = [] cursor = top_y wrap_width = TextEngine::VerticalLayout.wrap_width(frame) entries.each_with_index do |entry, index| cursor -= (option&.space_between || 0).to_f if index.positive? entry.paragraphs.each do |paragraph| cursor = layout_paragraph( paragraph, frame, font, wrap_width, cursor, positioned ) end end [positioned, cursor] end |
.marker_run(number, base, paragraphs, option) ⇒ Object
Builds the superscript marker run emitted into the body text where the footnote anchors. Inherits the base run's character styling so the marker matches its context; when the owning CSR has no text run, falls back to defaults.
65 66 67 68 69 70 71 72 73 74 75 |
# File 'lib/idml/render/footnote.rb', line 65 def self.marker_run(number, base, paragraphs, option) marker = base || StyleResolver::StyledRun.new( point_size: StyleResolver::DEFAULT_POINT_SIZE, ) marker = marker.dup marker.text = marker_text(number, option) marker.position = "Superscript" marker.footnote_number = number marker.footnote_paragraphs = paragraphs marker end |
.marker_text(number, option) ⇒ Object
Marker text shown in the body text (and prefixed to the footnote's first paragraph): Prefix + number + Suffix.
57 58 59 |
# File 'lib/idml/render/footnote.rb', line 57 def self.marker_text(number, option) "#{option&.prefix}#{number}#{option&.suffix}" end |
.option(package) ⇒ Object
Returns the package's FootnoteOption (Preferences.xml), or nil when the package doesn't declare one.
46 47 48 49 |
# File 'lib/idml/render/footnote.rb', line 46 def self.option(package) preferences = package&.preferences preferences&.footnote_option&.first end |
.reserved_height(entries, font, frame, option = nil) ⇒ Object
Height to reserve at the frame bottom for the entries: separator gap + the measured paragraph stack.
171 172 173 174 175 176 177 178 |
# File 'lib/idml/render/footnote.rb', line 171 def self.reserved_height(entries, font, frame, option = nil) return 0.0 if entries.empty? content_bottom = TextEngine::VerticalLayout.bottom_limit(frame) _, bottom_y = layout_entries(entries, frame, font, content_bottom, option) content_bottom - bottom_y + rule_gap(option) end |
.rule_gap(option) ⇒ Object
Vertical distance between the footnote area's top edge and the first footnote line — room for the separator rule.
182 183 184 |
# File 'lib/idml/render/footnote.rb', line 182 def self.rule_gap(option) option&.spacer || DEFAULT_RULE_GAP end |