# -*- encoding: utf-8; frozen_string_literal: true -*-
#
#--
# This file is part of HexaPDF.
#
# HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
# Copyright (C) 2014-2024 Thomas Leitner
#
# HexaPDF is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License version 3 as
# published by the Free Software Foundation with the addition of the
# following permission added to Section 15 as permitted in Section 7(a):
# FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
# THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
# INFRINGEMENT OF THIRD PARTY RIGHTS.
#
# HexaPDF is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
# License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with HexaPDF. If not, see .
#
# The interactive user interfaces in modified source and object code
# versions of HexaPDF must display Appropriate Legal Notices, as required
# under Section 5 of the GNU Affero General Public License version 3.
#
# In accordance with Section 7(b) of the GNU Affero General Public
# License, a covered work must retain the producer line in every PDF that
# is created or manipulated using HexaPDF.
#
# If the GNU Affero General Public License doesn't fit your need,
# commercial licenses are available at .
#++
require 'hexapdf/layout'
module HexaPDF
module Content
# The CanvasComposer class allows using the document layout functionality for a single canvas.
# It works in a similar manner as the HexaPDF::Composer class.
#
# See: HexaPDF::Composer, HexaPDF::Document::Layout
class CanvasComposer
# The associated canvas.
attr_reader :canvas
# The associated HexaPDF::Document instance.
attr_reader :document
# The HexaPDF::Layout::Frame instance into which the boxes are laid out.
attr_reader :frame
# Creates a new CanvasComposer instance for the given +canvas+.
#
# The +margin+ can be any value allowed by HexaPDF::Layout::Style::Quad#set and defines the
# margin that should not be used during composition. For the remaining area of the canvas a
# frame object will be created.
def initialize(canvas, margin: 0)
@canvas = canvas
@document = @canvas.context.document
box = @canvas.context.box
margin = Layout::Style::Quad.new(margin)
@frame = Layout::Frame.new(box.left + margin.left,
box.bottom + margin.bottom,
box.width - margin.left - margin.right,
box.height - margin.bottom - margin.top,
context: @canvas.context)
end
# Invokes HexaPDF::Document::Layout#style with the given arguments to create/update and return
# a style object.
def style(name, base: :base, **properties)
@document.layout.style(name, base: base, **properties)
end
# Draws the given HexaPDF::Layout::Box and returns the last drawn box.
#
# The box is drawn into the frame. If it doesn't fit, the box is split. If it still doesn't
# fit, a new region of the frame is determined and then the process starts again.
#
# If none or only some parts of the box fit into the frame, an exception is thrown.
def draw_box(box)
while true
result = @frame.fit(box)
if result.success?
@frame.draw(@canvas, result)
break
elsif @frame.full?
raise HexaPDF::Error, "Frame for canvas composer is full and box doesn't fit anymore"
else
draw_box, box = @frame.split(result)
if draw_box
@frame.draw(@canvas, result)
elsif !@frame.find_next_region
raise HexaPDF::Error, "Frame for canvas composer is full and box doesn't fit anymore"
end
end
end
box
end
# Draws any box that can be created using HexaPDF::Document::Layout.
#
# This includes all named boxes defined in the 'layout.boxes.map' configuration option.
#
# Examples:
#
# #>pdf
# canvas.composer(margin: 10) do |composer|
# composer.text("Some text", position: :float)
# composer.image(machu_picchu, height: 30, align: :right)
# composer.lorem_ipsum(sentences: 1, margin: [0, 0, 5])
# composer.list(item_spacing: 2) do |list|
# composer.document.config['layout.boxes.map'].each do |name, klass|
# list.formatted_text([{text: name.to_s, fill_color: "hp-blue-dark"},
# {text: "\n#{klass}"}], font_size: 6)
# end
# end
# end
#
# See: HexaPDF::Document::Layout#box
def method_missing(name, *args, **kwargs, &block)
if @document.layout.box_creation_method?(name)
draw_box(@document.layout.send(name, *args, **kwargs, &block))
else
super
end
end
def respond_to_missing?(name, _private) # :nodoc:
@document.layout.box_creation_method?(name) || super
end
end
end
end