Class: HexaPDF::Composer
- Inherits:
-
Object
- Object
- HexaPDF::Composer
- Defined in:
- lib/hexapdf/composer.rb
Overview
The composer class can be used to create PDF documents from scratch. It uses HexaPDF::Layout::Frame and HexaPDF::Layout::Box objects underneath and binds them together to provide a convenient interface for working with them.
Usage
First, a new Composer objects needs to be created, either using ::new or the utility method ::create.
On creation a HexaPDF::Document object is created as well the first page and an accompanying HexaPDF::Layout::Frame object. The frame is used by the various methods for general document layout tasks, like positioning of text, images, and so on. By default, it covers the whole page except the margin area. How the frame gets created can be customized by overriding the #create_frame method.
Once the Composer object is created, its methods can be used to draw text, images, … on the page. Behind the scenes HexaPDF::Layout::Box (and subclass) objects are created and drawn on the page via the frame.
All drawing methods accept HexaPDF::Layout::Style objects or names for style objects (defined via #style). The HexaPDF::Layout::Style#font is handled specially:
-
If no font is set on a style, the font “Times” is automatically set because otherwise there would be problems with text drawing operations (font is the only style property that has no valid default value).
-
Standard style objects only allow font wrapper objects to be set via the HexaPDF::Layout::Style#font method. Composer makes usage easier by allowing strings or an array [name, options_hash] to be used, like with e.g Content::Canvas. So using Helvetica as font, one could just do this by saying
style.font = 'Helvetica'And if Helvetica bold should be used it would be
style.font = ['Helvetica', variant: :bold]
If the frame of a page is full and a box doesn't fit anymore, a new page is automatically created. The box is either split into two boxes where one fits on the first page and the other on the new page, or it is drawn completely on the new page. A new page can also be created by calling the #new_page method.
The #x and #y methods provide the point where the next box would be drawn if it fits the available space. This information can be used, for example, for custom drawing operations through #canvas which provides direct access to the HexaPDF::Content::Canvas object of the current page.
When using #canvas and modifying the graphics state, care has to be taken to avoid problems with later box drawing operations since the graphics state cannot completely be reset (e.g. transformations of the canvas cannot always be undone). So it is best to save the graphics state before and restore it afterwards.
Example
HexaPDF::Composer.create('output.pdf', margin: 36) do |pdf|
pdf.base_style.font_size(20).align(:center)
pdf.text("Hello World", valign: :center)
end
Instance Attribute Summary collapse
-
#canvas ⇒ Object
readonly
The Content::Canvas of the current page.
-
#document ⇒ Object
readonly
The PDF document that is created.
-
#frame ⇒ Object
readonly
The HexaPDF::Layout::Frame for automatic box placement.
-
#page ⇒ Object
readonly
The current page (a HexaPDF::Type::Page object).
Class Method Summary collapse
-
.create(output, **options, &block) ⇒ Object
Creates a new PDF document and writes it to
output.
Instance Method Summary collapse
-
#draw_box(box) ⇒ Object
Draws the given HexaPDF::Layout::Box.
-
#formatted_text(data, width: 0, height: 0, style: nil, box_style: nil, **style_properties) ⇒ Object
Draws text like #text but allows parts of the text to be formatted differently.
-
#image(file, width: 0, height: 0, style: nil, **style_properties) ⇒ Object
Draws the given image at the current position.
-
#initialize(page_size: :A4, page_orientation: :portrait, margin: 36) {|_self| ... } ⇒ Composer
constructor
Creates a new Composer object and optionally yields it to the given block.
-
#new_page(page_size: nil, page_orientation: nil, margin: nil) ⇒ Object
Creates a new page, making it the current one.
-
#style(name, base: :base, **properties) ⇒ Object
:call-seq: composer.style(:header) -> style composer.style(:header, base: :base, **properties) -> style.
-
#text(str, width: 0, height: 0, style: nil, box_style: nil, **style_properties) ⇒ Object
Draws the given text at the current position into the current frame.
-
#write(output, optimize: true, **options) ⇒ Object
Writes the PDF document to the given output.
-
#x ⇒ Object
The x-position of the cursor inside the current frame.
-
#y ⇒ Object
The y-position of the cursor inside the current frame.
Constructor Details
#initialize(page_size: :A4, page_orientation: :portrait, margin: 36) {|_self| ... } ⇒ Composer
Creates a new Composer object and optionally yields it to the given block.
- page_size
-
Can be any valid predefined page size (see Type::Page::PAPER_SIZE) or an array [llx, lly, urx, ury] specifying a custom page size.
- page_orientation
-
Specifies the orientation of the page, either
:portraitor:landscape. Only used ifpage_sizeis one of the predefined page sizes. - margin
-
The margin to use. See HexaPDF::Layout::Style::Quad#set for possible values.
Example:
composer = HexaPDF::Composer.new # uses the default values
HexaPDF::Composer.new(page_size: :Letter, margin: 72) do |composer|
#...
end
144 145 146 147 148 149 150 151 152 153 |
# File 'lib/hexapdf/composer.rb', line 144 def initialize(page_size: :A4, page_orientation: :portrait, margin: 36) #:yields: composer @document = HexaPDF::Document.new @page_size = page_size @page_orientation = page_orientation @margin = Layout::Style::Quad.new(margin) @styles = {base: Layout::Style.new} new_page yield(self) if block_given? end |
Instance Attribute Details
#canvas ⇒ Object (readonly)
The Content::Canvas of the current page. Can be used to perform arbitrary drawing operations.
120 121 122 |
# File 'lib/hexapdf/composer.rb', line 120 def canvas @canvas end |
#document ⇒ Object (readonly)
The PDF document that is created.
114 115 116 |
# File 'lib/hexapdf/composer.rb', line 114 def document @document end |
#frame ⇒ Object (readonly)
The HexaPDF::Layout::Frame for automatic box placement.
123 124 125 |
# File 'lib/hexapdf/composer.rb', line 123 def frame @frame end |
#page ⇒ Object (readonly)
The current page (a HexaPDF::Type::Page object).
117 118 119 |
# File 'lib/hexapdf/composer.rb', line 117 def page @page end |
Class Method Details
.create(output, **options, &block) ⇒ Object
Creates a new PDF document and writes it to output. The options are passed to ::new.
Example:
HexaPDF::Composer.create('output.pdf', margin: 36) do |pdf|
...
end
109 110 111 |
# File 'lib/hexapdf/composer.rb', line 109 def self.create(output, **, &block) new(**, &block).write(output) end |
Instance Method Details
#draw_box(box) ⇒ Object
Draws the given HexaPDF::Layout::Box.
The box is drawn into the current frame if possible. 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 current frame, one or more new pages are created for the rest of the box.
339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 |
# File 'lib/hexapdf/composer.rb', line 339 def draw_box(box) drawn_on_page = true while true if @frame.fit(box) @frame.draw(@canvas, box) break elsif @frame.full? new_page drawn_on_page = false else draw_box, box = @frame.split(box) if draw_box @frame.draw(@canvas, draw_box) 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 end |
#formatted_text(data, width: 0, height: 0, style: nil, box_style: nil, **style_properties) ⇒ Object
Draws text like #text but allows parts of the text to be formatted differently.
The argument data needs to be an array of String and/or Hash objects:
-
A String object is treated like data.
-
Hashes can contain any style properties and the following special keys:
- text
-
The text to be formatted.
- link
-
A URL that should be linked to. If no text is provided but a link, the link is used as text.
- style
-
The style to be use as basis instead of the style created from the
styleandstyle_propertiesarguments. See HexaPDF::Layout::Style::create for allowed values.
If any style properties are set, the used style is copied and the additional properties applied.
See #text for details on width, height, style, style_properties and box_style.
Examples:
#>pdf-composer
composer.formatted_text(["Some string"])
composer.formatted_text(["Some ", {text: "string", fill_color: 128}])
composer.formatted_text(["Some ", {link: "https://example.com",
fill_color: 'blue', text: "Example"}])
composer.formatted_text(["Some ", {text: "string", style: {font_size: 20}}])
See: #text, HexaPDF::Layout::TextBox, HexaPDF::Layout::TextFragment
298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 |
# File 'lib/hexapdf/composer.rb', line 298 def formatted_text(data, width: 0, height: 0, style: nil, box_style: nil, **style_properties) style = retrieve_style(style, style_properties) box_style = (box_style ? retrieve_style(box_style) : style) data.map! do |hash| if hash.kind_of?(String) Layout::TextFragment.create(hash, style) else link = hash.delete(:link) (hash[:overlays] ||= []) << [:link, {uri: link}] if link text = hash.delete(:text) || link || "" Layout::TextFragment.create(text, retrieve_style(hash.delete(:style) || style, hash)) end end draw_box(Layout::TextBox.new(data, width: width, height: height, style: box_style)) end |
#image(file, width: 0, height: 0, style: nil, **style_properties) ⇒ Object
Draws the given image at the current position.
The file argument can be anything that is accepted by HexaPDF::Document::Images#add.
See #text for details on width, height, style and style_properties.
Examples:
#>pdf-composer
composer.image(machu_picchu, border: {width: 3})
composer.image(machu_picchu, height: 30)
325 326 327 328 329 |
# File 'lib/hexapdf/composer.rb', line 325 def image(file, width: 0, height: 0, style: nil, **style_properties) style = retrieve_style(style, style_properties) image = document.images.add(file) draw_box(Layout::ImageBox.new(image, width: width, height: height, style: style)) end |
#new_page(page_size: nil, page_orientation: nil, margin: nil) ⇒ Object
Creates a new page, making it the current one.
If any of page_size, page_orientation or margin are set, they will be used instead of the default values and will become the default values.
Examples:
composer.new_page # uses the default values
composer.new_page(page_size: :A5, margin: [72, 36])
164 165 166 167 168 169 170 171 172 |
# File 'lib/hexapdf/composer.rb', line 164 def new_page(page_size: nil, page_orientation: nil, margin: nil) @page_size = page_size if page_size @page_orientation = page_orientation if page_orientation @margin = Layout::Style::Quad.new(margin) if margin @page = @document.pages.add(@page_size, orientation: @page_orientation) @canvas = @page.canvas create_frame end |
#style(name, base: :base, **properties) ⇒ Object
:call-seq:
composer.style(:header) -> style
composer.style(:header, base: :base, **properties) -> style
Creates or updates the HexaPDF::Layout::Style object called name with the given property values and returns it. Such a style can then be used by name in the various box drawing methods, e.g. #text or #image.
If neither base nor any style properties are specified, the style name is just returned.
If the style name does not exist yet and the argument base specifies the name of another style, that style is duplicated and used as basis for the style.
The special name :base should be used for setting the base style which is used when no specific style is set. It is best to fully initialize the base style before creating any other styles.
Note that the style property 'font' is handled specially by Composer, see the class documentation for details.
Example:
composer.style(:base, font_size: 12, leading: 1.2)
composer.style(:header, font: 'Helvetica', fill_color: "008")
composer.style(:header1, base: :header, font_size: 30)
See: HexaPDF::Layout::Style
218 219 220 221 222 |
# File 'lib/hexapdf/composer.rb', line 218 def style(name, base: :base, **properties) style = @styles[name] ||= (@styles.key?(base) ? @styles[base].dup : Layout::Style.new) style.update(**properties) unless properties.empty? style end |
#text(str, width: 0, height: 0, style: nil, box_style: nil, **style_properties) ⇒ Object
Draws the given text at the current position into the current frame.
This method is the main method for displaying text on a PDF page. It uses a HexaPDF::Layout::TextBox behind the scenes to do the actual work.
The text will be positioned at the current position if possible. Otherwise the next best position is used. If the text doesn't fit onto the current page or only partially, new pages are created automatically.
width,height-
The arguments
widthandheightare used as constraints and are respected when fitting the box. The default value of 0 means that no constraints are set. style,style_properties-
The box and the text are styled using the given
style. This can either be a style name set via #style or anything HexaPDF::Layout::Style::create accepts. If any additionalstyle_propertiesare specified, the style is duplicated and the additional styles are applied. box_style-
Sometimes it is necessary for the box to have a different style than the text, e.g. when using overlays. In such a case use
box_stylefor specifiying the style of the box (a style name set via #style or anything HexaPDF::Layout::Style::create accepts). Thestyletogether with thestyle_propertieswill be used for the text style.
Examples:
#>pdf-composer
composer.text("Test " * 15)
composer.text("Now " * 7, width: 100)
composer.text("Another test", font_size: 15, fill_color: "green")
composer.text("Different box style", fill_color: 'white', box_style: {
underlays: [->(c, b) { c.rectangle(0, 0, b.content_width, b.content_height).fill }]
})
See HexaPDF::HexaPDF::Layout::TextBox for details.
260 261 262 263 264 265 |
# File 'lib/hexapdf/composer.rb', line 260 def text(str, width: 0, height: 0, style: nil, box_style: nil, **style_properties) style = retrieve_style(style, style_properties) box_style = (box_style ? retrieve_style(box_style) : style) draw_box(Layout::TextBox.new([Layout::TextFragment.create(str, style)], width: width, height: height, style: box_style)) end |
#write(output, optimize: true, **options) ⇒ Object
Writes the PDF document to the given output.
See Document#write for details.
187 188 189 |
# File 'lib/hexapdf/composer.rb', line 187 def write(output, optimize: true, **) @document.write(output, optimize: optimize, **) end |
#x ⇒ Object
The x-position of the cursor inside the current frame.
175 176 177 |
# File 'lib/hexapdf/composer.rb', line 175 def x @frame.x end |
#y ⇒ Object
The y-position of the cursor inside the current frame.
180 181 182 |
# File 'lib/hexapdf/composer.rb', line 180 def y @frame.y end |