Module: Arrolio::GenericFlowBuilder::Figures
- Included in:
- Arrolio::GenericFlowBuilder
- Defined in:
- lib/arrolio/generic_flow_builder/figures.rb
Overview
Extracted emission seam: one module per content family, the same pattern as GenericAdapter's decomposition. The class keeps build(), dispatch, and shared helpers.
Constant Summary collapse
- INLINE_SVG_PREFIX =
'inline-svg:'
Instance Method Summary collapse
-
#figure_group_flowable(group, out) ⇒ Object
Figures are atomic: image + caption render as ONE flowable so the caption can never be orphaned onto the next page.
- #image_flowable(image) ⇒ Object
- #resolve_image_source(image) ⇒ Object
- #svg_dimension(source, name) ⇒ Object
- #with_block_gap(flowable, gap) ⇒ Object
- #write_inline_svg(prefixed) ⇒ Object
Instance Method Details
#figure_group_flowable(group, out) ⇒ Object
Figures are atomic: image + caption render as ONE flowable so the caption can never be orphaned onto the next page. Caption gap is flavor geometry (ref: image bottom to caption ~28pt; figure blocks separated by ~24pt).
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
# File 'lib/arrolio/generic_flow_builder/figures.rb', line 14 def figure_group_flowable(group, out) figure_config = @rules['figure'] || {} caption_gap = figure_config.fetch('caption_gap', 0.0).to_f block_gap = figure_config.fetch('block_gap', 0.0).to_f image = image_flowable(group.image) if group.image if group.caption caption_style = resolve(:figure_caption).with(margin_top: caption_gap) runs = group.caption.inline_runs.map do |run| InlineRun.new(run.text, style: caption_style) end caption = Flowables::TextFlowable.new(runs, style: caption_style) end return out << caption if image.nil? image = with_block_gap(image, block_gap) out << if caption Flowables::FigureFlowable.new(image, caption) else image end end |
#image_flowable(image) ⇒ Object
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
# File 'lib/arrolio/generic_flow_builder/figures.rb', line 49 def image_flowable(image) source = resolve_image_source(image) image_rules = @rules['image'] || {} default_width = (image_rules['default_natural_width'] || 400).to_f default_height = (image_rules['default_natural_height'] || 300).to_f max_width = (image_rules['max_display_width'] || 106).to_f natural_width = image.width || svg_dimension(source, 'width') || default_width natural_height = image.height || svg_dimension(source, 'height') || default_height display_width = [image.width || natural_width, max_width].min Flowables::ImageFlowable.new( source, natural_width: natural_width, natural_height: natural_height, display_width: display_width, alt: image.alt, style: resolve(image.style_id) ) end |
#resolve_image_source(image) ⇒ Object
68 69 70 71 72 73 |
# File 'lib/arrolio/generic_flow_builder/figures.rb', line 68 def resolve_image_source(image) return asset_resolver.resolve(image.src) unless image.src.is_a?(String) return write_inline_svg(image.src) if image.src.start_with?(INLINE_SVG_PREFIX) asset_resolver.resolve(image.src) end |
#svg_dimension(source, name) ⇒ Object
82 83 84 85 86 87 |
# File 'lib/arrolio/generic_flow_builder/figures.rb', line 82 def svg_dimension(source, name) return nil unless source && File.exist?(source) && source.match?(/\.svg\z/i) value = File.read(source)[/(?:#{name})=["']([\d.]+)/, 1] value && (value.to_f * SVG_PX_TO_PT) end |
#with_block_gap(flowable, gap) ⇒ Object
37 38 39 40 41 42 43 44 45 46 47 |
# File 'lib/arrolio/generic_flow_builder/figures.rb', line 37 def with_block_gap(flowable, gap) return flowable if gap.zero? style = flowable.style.with(margin_top: flowable.style.margin_top + gap) flowable.class.new(flowable.src, natural_width: flowable.natural_width, natural_height: flowable.natural_height, display_width: flowable.display_width, alt: flowable.alt, style: style) end |
#write_inline_svg(prefixed) ⇒ Object
75 76 77 78 79 80 |
# File 'lib/arrolio/generic_flow_builder/figures.rb', line 75 def write_inline_svg(prefixed) svg_xml = prefixed[INLINE_SVG_PREFIX.length..] path = File.join(Dir.mktmpdir('arrolio-svg'), 'figure.svg') File.write(path, svg_xml) path end |