class HexaPDF::Composer

See: HexaPDF::Document::Layout, HexaPDF::Layout::Frame, HexaPDF::Layout::Box
end
pdf.text(“Hello World”, valign: :center)
pdf.style(:base, font_size: 20, align: :center)
HexaPDF::Composer.create(‘out.pdf’, page_size: :A6, margin: 36) do |pdf|
#>pdf-full
== Example
before and restore it afterwards.
transformations of the canvas cannot always be undone). So it is best to save the graphics state
later box drawing operations since the graphics state cannot completely be reset (e.g.
When using #canvas and modifying the graphics state, care has to be taken to avoid problems with
current page.
through #canvas which provides direct access to the HexaPDF::Content::Canvas object of the
available space. This information can be used, for example, for custom drawing operations
The #x and #y methods provide the point where the next box would be drawn if it fits the
calling the #new_page method, optionally providing a page style.
on the new page, or it is drawn completely on the new page. A new page can also be created by
created. The box is either split into two boxes where one fits on the first page and the other
If the frame of a page is full and a box doesn’t fit anymore, a new page is automatically
HexaPDF::Document::Layout methods and drawn on the page via the frame.
page. Behind the scenes HexaPDF::Layout::Box (and subclass) objects are created using the
Once the Composer object is created, its methods can be used to draw text, images, … on the
creation when creating a Composer instance.
style, see #page_style. Use the skip_page_creation argument to avoid the initial page
except the margin area. How the frame gets created can be customized by defining a custom page
layout tasks, like positioning of text, images, and so on. By default, it covers the whole page
HexaPDF::Layout::Frame object. The frame is used by the various methods for general document
On creation a HexaPDF::Document object is created as well the first page and an accompanying
::create.
First, a new Composer objects needs to be created, either using ::new or the utility method
== Usage
provide a convenient interface for working with them.
HexaPDF::Layout::Frame and HexaPDF::Layout::Box objects underneath and binds them together to
The composer class can be used to create PDF documents from scratch. It uses

def self.create(output, **options, &block)

end
...
HexaPDF::Composer.create('out.pdf', margin: 36) do |pdf|

Example:

passed to ::new.
Creates a new PDF document and writes it to +output+. The argument +options+ and +block+ are
def self.create(output, **options, &block)
  new(**options, &block).write(output)
end

def box(name, width: 0, height: 0, style: nil, **box_options, &block)

See: HexaPDF::Document::Layout#box

composer.box(:image, image: composer.document.images.add(machu_picchu))
#>pdf-composer

Examples:

method for details on the arguments.
It uses HexaPDF::Document::Layout#box behind the scenes to create the named box. See that

Draws the named box at the current position (see #x and #y).
def box(name, width: 0, height: 0, style: nil, **box_options, &block)
  draw_box(@document.layout.box(name, width: width, height: height, style: style, **box_options, &block))
end

def create_stamp(width, height) # :yield: canvas

:yield: canvas
composer.image(stamp, width: 50)
composer.image(stamp, width: 20, height: 20)
end
rectangle(10, 10, 30, 30).fill_stroke
canvas.fill_color("hp-blue").line_width(5).
stamp = composer.create_stamp(50, 50) do |canvas|
#>pdf-composer

Examples:

page.box.width/height might be good choices).
The width and the height of the stamp need to be set (frame.width/height or

or on multiple pages.
Creates a stamp (Form XObject) which can be used like an image multiple times on a single page
def create_stamp(width, height) # :yield: canvas
  stamp = @document.add({Type: :XObject, Subtype: :Form, BBox: [0, 0, width, height]})
  yield(stamp.canvas) if block_given?
  stamp
end

def draw_box(box)

created for the rest of the box.
If none or only some parts of the box fit into the current frame, one or more new pages are

again.
it still doesn't fit, a new region of the frame is determined and then the process starts
The box is drawn into the current frame if possible. If it doesn't fit, the box is split. If

Draws the given HexaPDF::Layout::Box and returns the last drawn box.
def draw_box(box)
  drawn_on_page = true
  while true
    result = @frame.fit(box)
    if result.success?
      @frame.draw(@canvas, result)
      break
    elsif @frame.full?
      new_page
      drawn_on_page = false
    else
      draw_box, box = @frame.split(result)
      if draw_box
        @frame.draw(@canvas, result)
        drawn_on_page = true
      elsif !@frame.find_next_region
        unless drawn_on_page
          raise HexaPDF::Error, "Box doesn't fit on empty page"
        end
        new_page
        drawn_on_page = false
      end
    end
  end
  box
end

def formatted_text(data, width: 0, height: 0, style: nil, box_style: nil, **style_properties)

See: #text, HexaPDF::Layout::TextBox, HexaPDF::Layout::TextFragment

valign: :bottom, block: block}])
composer.formatted_text(["Some ", {box: :list, width: 50,
block = lambda {|list| list.text("First item"); list.text("Second item") }
composer.formatted_text(["Some ", {text: "string", style: {font_size: 20}}])
fill_color: 'hp-blue', text: "Example"}])
composer.formatted_text(["Some ", {link: "https://example.com",
composer.formatted_text(["Some ", {text: "string", fill_color: "hp-orange"}])
composer.formatted_text(["Some string"])
#>pdf-composer

Examples:

arguments.
HexaPDF::Layout::TextBox that does the actual work. See that method for details on the
It uses HexaPDF::Document::Layout#formatted_text_box behind the scenes to create the

interspersing with inline boxes.
Draws text like #text but allows parts of the text to be formatted differently and
def formatted_text(data, width: 0, height: 0, style: nil, box_style: nil, **style_properties)
  draw_box(@document.layout.formatted_text_box(data, width: width, height: height, style: style,
                                               box_style: box_style, **style_properties))
end

def image(file, width: 0, height: 0, style: nil, **style_properties)

See: HexaPDF::Layout::ImageBox

composer.image(machu_picchu, height: 30)
composer.image(machu_picchu, border: {width: 3})
#>pdf-composer

Examples:

arguments.
HexaPDF::Layout::ImageBox that does the actual work. See that method for details on the
It uses HexaPDF::Document::Layout#image_box behind the scenes to create the

Draws the given image at the current position (see #x and #y).
def image(file, width: 0, height: 0, style: nil, **style_properties)
  draw_box(@document.layout.image_box(file, width: width, height: height,
                                      style: style, **style_properties))
end

def initialize(skip_page_creation: false, page_size: :A4, page_orientation: :portrait,

end
# ...
composer.new_page
end
style.frame = style.create_frame(canvas.context, 36)
composer.page_style(:default) do |canvas, style|
HexaPDF::Composer.new(skip_page_creation: true) do |composer|

end
#...
HexaPDF::Composer.new(page_size: :Letter, margin: 72) do |composer|

composer = HexaPDF::Composer.new
# Uses the default values

Example:

Only used if +skip_page_creation+ is +false+.

The margin to use. See HexaPDF::Layout::Style::Quad#set for possible values.
margin::

Only used if +skip_page_creation+ is +false+.

is one of the predefined page sizes.
Specifies the orientation of the page, either +:portrait+ or +:landscape+, if +page_size+
page_orientation::

Only used if +skip_page_creation+ is +false+.

urx, ury] specifying a custom page size.
Can be any valid predefined page size (see Type::Page::PAPER_SIZE) or an array [llx, lly,
page_size::

method to create the initial page/frame.
method needs to be used to define a page style which is then used with the #new_page
created. This is useful when the first page needs a custom page style. The #page_style
Otherwise, i.e. when this argument is +true+, no initial page or default page style is

initial page/frame is created using this page style.
and +margin+ are used to create a page style with the name :default. Additionally, an
If this argument is +false+ (the default), the arguments +page_size+, +page_orientation+
skip_page_creation::

Creates a new Composer object and optionally yields it to the given block.
def initialize(skip_page_creation: false, page_size: :A4, page_orientation: :portrait,
               margin: 36) #:yields: composer
  @document = HexaPDF::Document.new
  @page_styles = {}
  @next_page_style = :default
  unless skip_page_creation
    page_style(:default, page_size: page_size, orientation: page_orientation) do |canvas, style|
      style.frame = style.create_frame(canvas.context, margin)
    end
    new_page
  end
  yield(self) if block_given?
end

def method_missing(name, *args, **kwargs, &block)

See: HexaPDF::Document::Layout#box

end
end
list.formatted_text([{text: name.to_s, fill_color: "hp-blue-dark"}, "\n#{klass}"])
composer.document.config['layout.boxes.map'].each do |name, klass|
composer.list(item_spacing: 2) do |list|
composer.lorem_ipsum(sentences: 1, margin: [0, 0, 5])
#>pdf-composer

Examples:

This includes all named boxes defined in the 'layout.boxes.map' configuration option.

Draws any custom box that can be created using HexaPDF::Document::Layout.
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 new_page(style = @next_page_style)

composer.new_page # uses the :content style, next style again :content
composer.new_page(:cover) # uses the :cover style, set next style to :content

composer.page_style(:content, page_size: :A4)
composer.page_style(:cover, page_size: :A4, next_style: :content)
# Define two page styles

Examples:

page style, that page style is used again.
pages (see Layout::PageStyle#next_style). If this information is not provided by the applied
The applied page style determines the page style that should be used for the following new

@next_page_style).
If not provided, the currently set page style is used (:default is the initial value for
The page style (see #page_style) to use for the new page can be set via the +style+ argument.

Creates a new page, making it the current one.
def new_page(style = @next_page_style)
  page_style = @page_styles.fetch(style) do |key|
    raise ArgumentError, "Page style #{key} has not been defined"
  end
  @page = @document.pages.add(page_style.create_page(@document))
  @canvas = @page.canvas
  @frame = page_style.frame
  @next_page_style = page_style.next_style || style
end

def page_style(name, **attributes, &block)

See: HexaPDF::Layout::PageStyle

end
style.frame = style.create_frame(canvas.context, 36)
end
fill
canvas.rectangle(0, 0, page_box.width, page_box.height).
canvas.fill_color("green") do
page_box = canvas.context.box
composer.page_style(:cover, page_size: :A4) do |canvas, style|
composer.page_style(:default)

Example:

is provided, it is used to define the page template.
those attribute values is created, stored under +name+ and returned. Additionally, if a block
If one or more page style attributes are given, a new HexaPDF::Layout::PageStyle object with

+nil+ is returned.
If no attributes are given, the page style +name+ is returned. In case it does not exist,

Creates and/or returns the page style +name+.

composer.page_style(name, **attributes, &template_block) -> page_style
composer.page_style(name) -> page_style
:call-seq:
def page_style(name, **attributes, &block)
  if attributes.empty? && block.nil?
    @page_styles[name]
  else
    @page_styles[name] = HexaPDF::Layout::PageStyle.new(**attributes, &block)
  end
end

def respond_to_missing?(name, _private) # :nodoc:

:nodoc:
def respond_to_missing?(name, _private) # :nodoc:
  @document.layout.box_creation_method?(name) || super
end

def style(name, base: :base, **properties)

See: HexaPDF::Layout::Style

composer.style(:header1, base: :header, font_size: 30)
composer.style(:header, font: 'Helvetica', fill_color: "008")
composer.style(:base, font_size: 12, leading: 1.2)

Example:

that method.
See HexaPDF::Document::Layout#style for details; this method is just a thin wrapper around

If neither +base+ nor any style properties are specified, the style +name+ is just returned.

values and returns it.
Creates or updates the HexaPDF::Layout::Style object called +name+ with the given property

composer.style(name, base: :base, **properties) -> style
composer.style(name) -> style
:call-seq:
def style(name, base: :base, **properties)
  @document.layout.style(name, base: base, **properties)
end

def text(str, width: 0, height: 0, style: nil, box_style: nil, **style_properties)

See: #formatted_text, HexaPDF::Layout::TextBox, HexaPDF::Layout::TextFragment

})
underlays: [->(c, b) { c.rectangle(0, 0, b.content_width, b.content_height).fill }]
composer.text("Different box style", fill_color: 'white', box_style: {
composer.text("Another test", font_size: 15, fill_color: "hp-blue")
composer.text("Now " * 7, width: 100)
composer.text("Test it now " * 15)
#>pdf-composer

Examples:

See HexaPDF::Document::Layout#text_box for details on the arguments.

HexaPDF::Layout::TextBox that does the actual work.
#formatted_text. It uses HexaPDF::Document::Layout#text_box behind the scenes to create the
This method is of the two main methods for creating text boxes, the other being

one or more new pages are created automatically.
next best position is used. If the text doesn't fit onto the current page or only partially,
The text will be positioned at the current position (see #x and #y) if possible. Otherwise the

Draws the given text at the current position into the current frame.
def text(str, width: 0, height: 0, style: nil, box_style: nil, **style_properties)
  draw_box(@document.layout.text_box(str, width: width, height: height, style: style,
                                     box_style: box_style, **style_properties))
end

def write(output, optimize: true, **options)

See HexaPDF::Document#write for details.

Writes the created PDF document to the given output.
def write(output, optimize: true, **options)
  @document.write(output, optimize: optimize, **options)
end

def x

circle(composer.x, composer.y, 5).stroke
circle(composer.x, composer.y, 0.5).fill.
composer.canvas.stroke_color("hp-blue").
composer.text("Hello", position: :float)
#>pdf-composer

Example:

The x-position inside the current frame where the next box (provided it fits) will be placed.
def x
  @frame.x
end

def y

circle(composer.x, composer.y, 5).stroke
circle(composer.x, composer.y, 0.5).fill.
composer.canvas.stroke_color("hp-blue").
composer.text("Hello", position: :float)
#>pdf-composer

Example:

The y-position inside the current frame.where the next box (provided it fits) will be placed.
def y
  @frame.y
end