Class: HexaPDF::Document::Layout
- Inherits:
-
Object
- Object
- HexaPDF::Document::Layout
- Defined in:
- lib/hexapdf/document/layout.rb
Overview
This class provides methods for working with classes in the HexaPDF::Layout module.
Often times the layout related classes are used through HexaPDF::Composer which makes it easy to create documents. However, sometimes one wants to have a bit more control or do something special and use the HexaPDF::Layout classes directly. This is possible but it is better to use those classes through an instance of this class because it makes it more convenient and ties everything together. Incidentally, HexaPDF::Composer relies on this class for a good part of its work.
Boxes
The main focus of the class is on providing convenience methods for creating box objects. The most often used box classes like HexaPDF::Layout::TextBox or HexaPDF::Layout::ImageBox can be created through dedicated methods:
-
#text_box
-
#formatted_text_box
-
#image_box
-
#lorem_ipsum_box
Other, more general boxes don’t have their own method but can be created through the general #box method. This method uses the ‘layout.boxes.map’ configuration option.
Additionally, the _box
suffix can be omitted, so calling #text, #formatted_text and #image also works. Furthermore, all box names defined in the ‘layout.boxes.map’ configuration option can be used as method names (with or without a _box
suffix) and will invoke #box, i.e. #column and #column_box will also work.
Box Styles
All box creation methods accept Layout::Style objects or names for style objects (defined via #style). This allows one to predefine certain styles (like first level heading, second level heading, paragraph, …) and consistently use them throughout the document creation process.
One style property, 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 Layout::Style#font method. This class makes usage easier by allowing strings or an array [name, options_hash] to be used, like with e.g Content::Canvas#font. So to use Helvetica as font, one could just do:
style.font = 'Helvetica'
And if Helvetica in its bold variant should be used it would be:
style.font = ['Helvetica', variant: :bold]
Defined Under Namespace
Classes: CellArgumentCollector, ChildrenCollector
Constant Summary collapse
- LOREM_IPSUM =
:nodoc:
[ # :nodoc: "Lorem ipsum dolor sit amet, con\u{00AD}sectetur adipis\u{00AD}cing elit, sed " \ "do eiusmod tempor incididunt ut labore et dolore magna aliqua.", "Ut enim ad minim veniam, quis nostrud exer\u{00AD}citation ullamco laboris nisi ut " \ "aliquip ex ea commodo consequat.", "Duis aute irure dolor in reprehen\u{00AD}derit in voluptate velit esse cillum dolore " \ "eu fugiat nulla pariatur.", "Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt " \ "mollit anim id est laborum.", ]
- BOX_METHOD_NAMES =
:nodoc:
[:text, :formatted_text, :image, :table, :lorem_ipsum]
Instance Attribute Summary collapse
-
#styles ⇒ Object
readonly
The mapping of style name (a Symbol) to Layout::Style instance.
Instance Method Summary collapse
-
#box(name, width: 0, height: 0, style: nil, **box_options, &block) ⇒ Object
Creates the named box and returns it.
-
#box_creation_method?(name) ⇒ Boolean
:nodoc:.
-
#formatted_text_box(data, width: 0, height: 0, style: nil, properties: nil, box_style: nil, **style_properties) ⇒ Object
Creates a HexaPDF::Layout::TextBox like #text_box but allows parts of the text to be formatted differently.
-
#image_box(file, width: 0, height: 0, properties: nil, style: nil, **style_properties) ⇒ Object
Creates a HexaPDF::Layout::ImageBox for the given image.
-
#initialize(document) ⇒ Layout
constructor
Creates a new Layout object for the given PDF document.
-
#inline_box(box_or_name, *args, valign: :baseline, **kwargs, &block) ⇒ Object
Creates an inline box for use together with text fragments.
-
#lorem_ipsum_box(sentences: 4, count: 1, **text_box_properties) ⇒ Object
Uses #text_box to create
count
paragraphs withsentences
number of sentences (1 to 4) of lorem ipsum text. -
#method_missing(name, *args, **kwargs, &block) ⇒ Object
Allows creating boxes using more convenient method names:.
-
#respond_to_missing?(name, _private) ⇒ Boolean
:nodoc:.
-
#style(name, base: :base, **properties) ⇒ Object
:call-seq: layout.style(name) -> style layout.style(name, base: :base, **properties) -> style.
-
#table_box(data, column_widths: nil, header: nil, footer: nil, cell_style: nil, width: 0, height: 0, style: nil, properties: nil, **style_properties) {|collector| ... } ⇒ Object
Creates a HexaPDF::Layout::TableBox for the given table data.
-
#text_box(text, width: 0, height: 0, style: nil, properties: nil, box_style: nil, **style_properties) ⇒ Object
Creates a HexaPDF::Layout::TextBox for the given text.
-
#text_fragments(text, style: nil, properties: nil, **style_properties) ⇒ Object
Creates an array of HexaPDF::Layout::TextFragment objects for the given
text
.
Constructor Details
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(name, *args, **kwargs, &block) ⇒ Object
Allows creating boxes using more convenient method names:
-
#text for #text_box
-
#formatted_text for #formatted_text_box
-
#image for #image_box
-
#lorem_ipsum for #lorem_ipsum_box
-
The name of a pre-defined box class like #column will invoke #box appropriately. Same if used with a ‘_box’ suffix.
587 588 589 590 591 592 593 594 595 596 |
# File 'lib/hexapdf/document/layout.rb', line 587 def method_missing(name, *args, **kwargs, &block) name_without_box = name.to_s.sub(/_box$/, '').intern if BOX_METHOD_NAMES.include?(name) send("#{name}_box", *args, **kwargs, &block) elsif @document.config['layout.boxes.map'].key?(name_without_box) box(name_without_box, *args, **kwargs, &block) else super end end |
Instance Attribute Details
#styles ⇒ Object (readonly)
The mapping of style name (a Symbol) to Layout::Style instance.
172 173 174 |
# File 'lib/hexapdf/document/layout.rb', line 172 def styles @styles end |
Instance Method Details
#box(name, width: 0, height: 0, style: nil, **box_options, &block) ⇒ Object
Creates the named box and returns it.
The name
argument refers to the registered name of the box class that is looked up in the ‘layout.boxes.map’ configuration option. The box_options
are passed as-is to the initialization method of that box class
If a block is provided, a ChildrenCollector is yielded and the collected children are passed to the box initialization method via the :children keyword argument.
See #text_box for details on width
, height
and style
(note that there is no style_properties
argument).
Example:
layout.box(:column, columns: 2, gap: 15) # => column_box_instance
layout.box(:column) do |column| # column box with one child
column.lorem_ipsum
end
255 256 257 258 259 260 261 |
# File 'lib/hexapdf/document/layout.rb', line 255 def box(name, width: 0, height: 0, style: nil, **, &block) if block_given? && !.key?(:children) [:children] = ChildrenCollector.collect(self, &block) end box_class_for_name(name).new(width: width, height: height, style: retrieve_style(style), **) end |
#box_creation_method?(name) ⇒ Boolean
:nodoc:
604 605 606 607 608 |
# File 'lib/hexapdf/document/layout.rb', line 604 def box_creation_method?(name) name = name.to_s.sub(/_box$/, '').intern BOX_METHOD_NAMES.include?(name) || @document.config['layout.boxes.map'].key?(name) || name == :box end |
#formatted_text_box(data, width: 0, height: 0, style: nil, properties: nil, box_style: nil, **style_properties) ⇒ Object
Creates a HexaPDF::Layout::TextBox like #text_box but allows parts of the text to be formatted differently.
The argument data
needs to be an array of String, HexaPDF::Layout::InlineBox and/or Hash objects and is transformed so that it is suitable as argument for the text box initialization method.
-
A String object is treated like data.
-
A HexaPDF::Layout::InlineBox is used without modification.
-
Hashes can contain any style properties and the following special keys:
- text
-
The text to be formatted. If this is set and :box is not, the hash will be transformed into text fragments.
- link
-
A URL that should be linked to. If no text is provided but a link, the link is used for the text. If this is set and :box is not, the hash will be transformed into text fragments with an appropriate link overlay.
- style
-
The style to use as base style instead of the style created from the
style
andstyle_properties
arguments. This can either be a style name set via #style or anything HexaPDF::Layout::Style::create allows.If any style properties are set, the used style is duplicated and the additional properties applied.
The final style is used for a created text fragment.
- properties
-
The custom properties that should be set on the created text fragments.
- box
-
An inline box to be used. If this is set, the hash will be transformed into an inline box.
The value must be one or more (as an array) positional arguments to be used with the #inline_box method. The rest of the hash keys are passed as keyword arguments to #inline_box except for :block which would be passed as the block.
See #text_box for details on width
, height
, style
, style_properties
, properties
and box_style
.
Examples:
# Text without special styling
layout.formatted_text_box(["Some string"])
# A predefined inline box
ibox = layout.inline_box(:text, 'Hello')
layout.formatted_text_box([ibox])
# Text with styling properties
layout.formatted_text_box([{text: "string", fill_color: 128}])
# Text referencing a base style
layout.formatted_text_box([{text: "string", style: :bold}])
# Text with a link
layout.formatted_text_box([{link: "https://example.com",
fill_color: 'blue', text: "Example"}])
# Inline boxes created from the given data
layout.formatted_text_box([{box: [:text, "string"], valign: :top}])
block = lambda {|list| list.text("First item"); list.text("Second item") }
layout.formatted_text_box(["Some ", {box: :list, item_spacing: 10, block: block}])
# Combining the above variants
layout.formatted_text_box(["Hello", {box: [:text, 'World!']}, "Here comes a ",
{link: 'https://example.com', text: 'link'}, '!',
{text: 'And more!', style: :bold, font_size: 20}])
See: #text_box, #inline_box, HexaPDF::Layout::TextBox, HexaPDF::Layout::TextFragment, HexaPDF::Layout::InlineBox
402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 |
# File 'lib/hexapdf/document/layout.rb', line 402 def formatted_text_box(data, width: 0, height: 0, style: nil, properties: nil, box_style: nil, **style_properties) style = retrieve_style(style, style_properties) box_style = (box_style ? retrieve_style(box_style) : style) data = data.inject([]) do |result, item| case item when String result.concat(text_fragments(item, style: style)) when Hash if (args = item.delete(:box)) block = item.delete(:block) result << inline_box(*args, **item, &block) else link = item.delete(:link) (item[:overlays] ||= []) << [:link, {uri: link}] if link text = item.delete(:text) || link || "" item_properties = item.delete(:properties) frag_style = retrieve_style(item.delete(:style) || style, item) result.concat(text_fragments(text, style: frag_style, properties: item_properties)) end when HexaPDF::Layout::InlineBox result << item else raise ArgumentError, "Invalid item of class #{item.class} in data array" end end box_class_for_name(:text).new(items: data, width: width, height: height, properties: properties, style: box_style) end |
#image_box(file, width: 0, height: 0, properties: nil, style: nil, **style_properties) ⇒ Object
Creates a HexaPDF::Layout::ImageBox for the given image.
The file
argument can be anything that is accepted by HexaPDF::Document::Images#add or a HexaPDF::Type::Form object.
See #text_box for details on width
, height
, style
, style_properties
and properties
.
Examples:
layout.image_box(machu_picchu, border: {width: 3})
layout.image_box(machu_picchu, height: 30)
See: HexaPDF::Layout::ImageBox
446 447 448 449 450 451 |
# File 'lib/hexapdf/document/layout.rb', line 446 def image_box(file, width: 0, height: 0, properties: nil, style: nil, **style_properties) style = retrieve_style(style, style_properties) image = file.kind_of?(HexaPDF::Stream) ? file : @document.images.add(file) box_class_for_name(:image).new(image: image, width: width, height: height, properties: properties, style: style) end |
#inline_box(box_or_name, *args, valign: :baseline, **kwargs, &block) ⇒ Object
Creates an inline box for use together with text fragments.
The valign
argument ist used to specify the vertical alignment of the box within the text line. See HexaPDF::Layout::Line for details.
If a box instance is provided as first argument, it is used. Otherwise the first argument has to be the name of a box creation method and args
, kwargs
and block
are passed to it.
Example:
layout.inline_box(:text, "Hallo")
layout.inline_box(:list) {|list| list.text("Hallo") }
228 229 230 231 232 233 234 235 |
# File 'lib/hexapdf/document/layout.rb', line 228 def inline_box(box_or_name, *args, valign: :baseline, **kwargs, &block) box = if box_or_name.kind_of?(HexaPDF::Layout::Box) box_or_name else send(box_or_name, *args, **kwargs, &block) end HexaPDF::Layout::InlineBox.new(box, valign: valign) end |
#lorem_ipsum_box(sentences: 4, count: 1, **text_box_properties) ⇒ Object
Uses #text_box to create count
paragraphs with sentences
number of sentences (1 to 4) of lorem ipsum text.
The text_box_properties
arguments are passed as is to #text_box.
573 574 575 |
# File 'lib/hexapdf/document/layout.rb', line 573 def lorem_ipsum_box(sentences: 4, count: 1, **text_box_properties) text_box(([LOREM_IPSUM[0, sentences].join(" ")] * count).join("\n\n"), **text_box_properties) end |
#respond_to_missing?(name, _private) ⇒ Boolean
:nodoc:
599 600 601 |
# File 'lib/hexapdf/document/layout.rb', line 599 def respond_to_missing?(name, _private) box_creation_method?(name) || super end |
#style(name, base: :base, **properties) ⇒ Object
:call-seq:
layout.style(name) -> style
layout.style(name, base: :base, **properties) -> style
Creates or updates the Layout::Style object called name
with the given property values and returns it.
If neither base
nor any style properties are specified, the style name
is just returned.
This method allows convenient access to the stored styles and to update them. Such styles can then be used by name in the various box creation methods, e.g. #text_box or #image_box.
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. This also means that the referenced base
style needs be defined first!
The special name :base should be used for setting the base style which is used for the base
argument when no specific style is specified.
Note that the style property ‘font’ is handled specially, see the class documentation for details.
Example:
layout.style(:base, font_size: 12, leading: 1.2)
layout.style(:header, font: 'Helvetica', fill_color: "008")
layout.style(:header1, base: :header, font_size: 30)
See: HexaPDF::Layout::Style
209 210 211 212 213 |
# File 'lib/hexapdf/document/layout.rb', line 209 def style(name, base: :base, **properties) style = @styles[name] ||= (@styles.key?(base) ? @styles[base].dup : HexaPDF::Layout::Style.new) style.update(**properties) unless properties.empty? style end |
#table_box(data, column_widths: nil, header: nil, footer: nil, cell_style: nil, width: 0, height: 0, style: nil, properties: nil, **style_properties) {|collector| ... } ⇒ Object
Creates a HexaPDF::Layout::TableBox for the given table data.
This method is a small wrapper around the actual class and mainly facilitates transforming the contents of the data
into the box instances needed by the table box implementation.
In addition to everything the table box implementation allows for data
, it is also possible to specify strings as cell contents. Those strings will be converted to text boxes by using the #text_box method. Note that this functionality is not available for the header and footer!
Additional arguments for the #text_box invocations can be specified using the optional block that yields a CellArgumentCollector instance. This allows customization of the text boxes. By specifying the special key :cell
it is also possible to assign style properties to the cells themselves.
See HexaPDF::Layout::TableBox::new for details on column_widths
, header
, footer
, and cell_style
.
See #text_box for details on width
, height
, style
, style_properties
and properties
.
Examples:
layout.table_box([[layout.text('A'), layout.text('B')],
[layout.image(image_path), layout.text('D')]]
layout.table_box([['A', 'B'], [layout.image(image_path), 'D]]) # same as above
layout.table_box([['A', 'B'], ['C', 'D]]) do |args|
# assign the predefined style :cell_text to all texts
args[] = {style: :cell_text}
# row 0 has a grey background and bold text
args[0] = {font: ['Helvetica', variant: :bold], cell: {background_color: 'eee'}}
# text in last column is right aligned
args[0..-1, -1] = {text_align: :right}
end
See: HexaPDF::Layout::TableBox
535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 |
# File 'lib/hexapdf/document/layout.rb', line 535 def table_box(data, column_widths: nil, header: nil, footer: nil, cell_style: nil, width: 0, height: 0, style: nil, properties: nil, **style_properties) style = retrieve_style(style, style_properties) cells = HexaPDF::Layout::TableBox::Cells.new(data, cell_style: cell_style) collector = CellArgumentCollector.new(cells.number_of_rows, cells.number_of_columns) yield(collector) if block_given? cells.style do |cell| args = collector.retrieve_arguments_for(cell.row, cell.column) cstyle = args.delete(:cell) result = case cell.children when Array, HexaPDF::Layout::Box cell.children else text_box(cell.children.to_s, **args) end cell.children = result cell.style.update(**cstyle) if cstyle end box_class_for_name(:table).new(cells: cells, column_widths: column_widths, header: header, footer: , cell_style: cell_style, width: width, height: height, properties: properties, style: style) end |
#text_box(text, width: 0, height: 0, style: nil, properties: nil, box_style: nil, **style_properties) ⇒ Object
Creates a HexaPDF::Layout::TextBox for the given text.
This method is of the two main methods for creating text boxes, the other being #formatted_text_box.
width
,height
-
The arguments
width
andheight
are 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 Layout::Style::create accepts. If any additionalstyle_properties
are specified, the style is duplicated and the additional styles are applied. properties
-
This can be used to set custom properties on the created text box. See Layout::Box#properties for details and usage.
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_style
for specifiying the style of the box (a style name set via #style or anything Layout::Style::create accepts).The
style
together with thestyle_properties
will be used for the text style.
Examples:
layout.text_box("Test is on " * 15)
layout.text_box("Now " * 7, width: 100)
layout.text_box("Another test", font_size: 15, fill_color: "hp-blue")
layout.text_box("Different box style", fill_color: 'white', box_style: {
underlays: [->(c, b) { c.rectangle(0, 0, b.content_width, b.content_height).fill }]
})
See: #formatted_text_box, HexaPDF::Layout::TextBox, HexaPDF::Layout::TextFragment
321 322 323 324 325 326 327 328 |
# File 'lib/hexapdf/document/layout.rb', line 321 def text_box(text, width: 0, height: 0, style: nil, properties: nil, box_style: nil, **style_properties) style = retrieve_style(style, style_properties) box_style = (box_style ? retrieve_style(box_style) : style) box_class_for_name(:text).new(items: text_fragments(text, style: style), width: width, height: height, properties: properties, style: box_style) end |
#text_fragments(text, style: nil, properties: nil, **style_properties) ⇒ Object
Creates an array of HexaPDF::Layout::TextFragment objects for the given text
.
This method uses the configuration option ‘font.on_invalid_glyph’ to map Unicode characters without a valid glyph in the given font to zero, one or more glyphs in a fallback font.
style
,style_properties
-
The text is styled using the given
style
. This can either be a style name set via #style or anything Layout::Style::create accepts. If any additionalstyle_properties
are specified, the style is duplicated and the additional styles are applied. properties
-
This can be used to set custom properties on the created text fragments. See Layout::Box#properties for details and usage.
276 277 278 279 280 281 282 283 |
# File 'lib/hexapdf/document/layout.rb', line 276 def text_fragments(text, style: nil, properties: nil, **style_properties) style = retrieve_style(style, style_properties) fragments = HexaPDF::Layout::TextFragment.create_with_fallback_glyphs( text, style, &@document.config['font.on_invalid_glyph'] ) fragments.each {|f| f.properties.update(properties) } if properties fragments end |