All checks were successful
CI Pipeline / build (pull_request) Successful in 14s
210 lines
5.2 KiB
Ruby
210 lines
5.2 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module Notare
|
|
module Builder
|
|
def p(text = nil, style: nil, &block)
|
|
para = Nodes::Paragraph.new(style: resolve_style(style))
|
|
if block
|
|
with_target(para, &block)
|
|
elsif text
|
|
para.add_run(Nodes::Run.new(text, **current_formatting))
|
|
end
|
|
@nodes << para
|
|
end
|
|
|
|
def text(value, style: nil)
|
|
formatting = current_formatting.merge(style: resolve_style(style))
|
|
@current_target.add_run(Nodes::Run.new(value, **formatting))
|
|
end
|
|
|
|
# Heading shortcuts
|
|
def h1(text = nil, &block)
|
|
p(text, style: :heading1, &block)
|
|
end
|
|
|
|
def h2(text = nil, &block)
|
|
p(text, style: :heading2, &block)
|
|
end
|
|
|
|
def h3(text = nil, &block)
|
|
p(text, style: :heading3, &block)
|
|
end
|
|
|
|
def h4(text = nil, &block)
|
|
p(text, style: :heading4, &block)
|
|
end
|
|
|
|
def h5(text = nil, &block)
|
|
p(text, style: :heading5, &block)
|
|
end
|
|
|
|
def h6(text = nil, &block)
|
|
p(text, style: :heading6, &block)
|
|
end
|
|
|
|
def image(path, width: nil, height: nil)
|
|
validate_image_path!(path)
|
|
img = register_image(path, width: width, height: height)
|
|
@current_target.add_run(img)
|
|
end
|
|
|
|
def b(&block)
|
|
with_format(:bold, &block)
|
|
end
|
|
|
|
def i(&block)
|
|
with_format(:italic, &block)
|
|
end
|
|
|
|
def u(&block)
|
|
with_format(:underline, &block)
|
|
end
|
|
|
|
def s(&block)
|
|
with_format(:strike, &block)
|
|
end
|
|
|
|
def br
|
|
@current_target.add_run(Nodes::Break.new(type: :line))
|
|
end
|
|
|
|
def page_break
|
|
@nodes << Nodes::Break.new(type: :page)
|
|
end
|
|
|
|
def link(url, text = nil, &block)
|
|
hyperlink = register_hyperlink(url)
|
|
if block
|
|
with_target(hyperlink, &block)
|
|
elsif text
|
|
hyperlink.add_run(Nodes::Run.new(text, underline: true, color: "0000FF"))
|
|
else
|
|
hyperlink.add_run(Nodes::Run.new(url, underline: true, color: "0000FF"))
|
|
end
|
|
@current_target.add_run(hyperlink)
|
|
end
|
|
|
|
def ul(&block)
|
|
list(:bullet, &block)
|
|
end
|
|
|
|
def ol(&block)
|
|
list(:number, &block)
|
|
end
|
|
|
|
def li(text = nil, &block)
|
|
current_type = @list_type_stack.last
|
|
item = Nodes::ListItem.new([], list_type: current_type, num_id: @current_list.num_id, level: @list_level)
|
|
item.add_run(Nodes::Run.new(text, **current_formatting)) if text
|
|
with_target(item, &block) if block
|
|
@current_list.add_item(item)
|
|
end
|
|
|
|
def table(style: nil, &block)
|
|
tbl = Nodes::Table.new(style: resolve_table_style(style))
|
|
previous_table = @current_table
|
|
@current_table = tbl
|
|
block.call
|
|
@current_table = previous_table
|
|
@nodes << tbl
|
|
end
|
|
|
|
def tr(&block)
|
|
row = Nodes::TableRow.new
|
|
previous_row = @current_row
|
|
@current_row = row
|
|
block.call
|
|
@current_row = previous_row
|
|
@current_table.add_row(row)
|
|
end
|
|
|
|
def td(text = nil, &block)
|
|
cell = Nodes::TableCell.new
|
|
if block
|
|
with_target(cell, &block)
|
|
elsif text
|
|
cell.add_run(Nodes::Run.new(text, **current_formatting))
|
|
end
|
|
@current_row.add_cell(cell)
|
|
end
|
|
|
|
private
|
|
|
|
def list(type, &block)
|
|
@num_id_counter ||= 0
|
|
@list_level ||= 0
|
|
@list_type_stack ||= []
|
|
|
|
previous_list = @current_list
|
|
nested = !previous_list.nil?
|
|
|
|
if nested
|
|
# Nested list: reuse parent list, push new type, increment level
|
|
@list_level += 1
|
|
@list_type_stack.push(type)
|
|
block.call
|
|
@list_type_stack.pop
|
|
@list_level -= 1
|
|
else
|
|
# Top-level list: new List node
|
|
@num_id_counter += 1
|
|
mark_has_lists!
|
|
list_node = Nodes::List.new(type: type, num_id: @num_id_counter)
|
|
@list_type_stack.push(type)
|
|
@current_list = list_node
|
|
block.call
|
|
@current_list = previous_list
|
|
@list_type_stack.pop
|
|
@nodes << list_node
|
|
end
|
|
end
|
|
|
|
def with_format(format, &block)
|
|
@format_stack ||= []
|
|
@format_stack.push(format)
|
|
block.call
|
|
@format_stack.pop
|
|
end
|
|
|
|
def with_target(target, &block)
|
|
previous_target = @current_target
|
|
@current_target = target
|
|
block.call
|
|
@current_target = previous_target
|
|
end
|
|
|
|
def current_formatting
|
|
@format_stack ||= []
|
|
{
|
|
bold: @format_stack.include?(:bold),
|
|
italic: @format_stack.include?(:italic),
|
|
underline: @format_stack.include?(:underline),
|
|
strike: @format_stack.include?(:strike)
|
|
}
|
|
end
|
|
|
|
def validate_image_path!(path)
|
|
raise ArgumentError, "Image file not found: #{path}" unless File.exist?(path)
|
|
|
|
ext = File.extname(path).downcase
|
|
return if %w[.png .jpg .jpeg].include?(ext)
|
|
|
|
raise ArgumentError, "Unsupported image format: #{ext}. Use PNG or JPEG."
|
|
end
|
|
|
|
def resolve_style(style_or_name)
|
|
return nil if style_or_name.nil?
|
|
return style_or_name if style_or_name.is_a?(Style)
|
|
|
|
style(style_or_name) || raise(ArgumentError, "Unknown style: #{style_or_name}")
|
|
end
|
|
|
|
def resolve_table_style(style_or_name)
|
|
return nil if style_or_name.nil?
|
|
return style_or_name if style_or_name.is_a?(TableStyle)
|
|
|
|
table_style(style_or_name) || raise(ArgumentError, "Unknown table style: #{style_or_name}")
|
|
end
|
|
end
|
|
end
|