lib/hexapdf/document/layout.rb in hexapdf-0.45.0 vs lib/hexapdf/document/layout.rb in hexapdf-0.46.0

- old
+ new

@@ -75,13 +75,13 @@ # #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). + # * If no font is set on a style, the default font specified via the configuration option + # 'font.default' 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: @@ -149,11 +149,13 @@ end # :nodoc: def method_missing(name, *args, **kwargs, &block) if @layout.box_creation_method?(name) - @children << @layout.send(name, *args, **kwargs, &block) + box = @layout.send(name, *args, **kwargs, &block) + @children << box + box else super end end @@ -352,10 +354,11 @@ 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 + alias text text_box # 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 @@ -454,10 +457,11 @@ end end box_class_for_name(:text).new(items: data, width: width, height: height, properties: properties, style: box_style) end + alias formatted_text formatted_text_box # 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. @@ -475,10 +479,11 @@ 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 + alias image image_box # This helper class is used by Layout#table_box to allow specifying the keyword arguments used # when converting cell data to box instances. class CellArgumentCollector @@ -493,12 +498,29 @@ @argument_infos = [] @number_of_rows = number_of_rows @number_of_columns = number_of_columns end - # Stores the keyword arguments in +args+ for the given 0-based rows and columns which can - # either be a single number or a range of numbers. + # Stores the hash +args+ containing styling properties for the cells specified via the given + # 0-based rows and columns. + # + # Rows and columns can either be single numbers, ranges of numbers or stepped ranges (i.e. + # Enumerator::ArithmeticSequence instances). + # + # Examples: + # + # # Gray background for all cells + # args[] = {cell: {background_color: "gray"}} + # + # # Cell at (2, 3) gets a bigger font size + # args[2, 3] = {font_size: 50} + # + # # First column of every row has bold font + # args[0..-1, 0] = {font: 'Helvetica bold'} + # + # # Every second row has a blue background + # args[(0..-1).step(2)] = {cell: {background_color: "blue"}} def []=(rows = 0..-1, cols = 0..-1, args) rows = adjust_range(rows.kind_of?(Integer) ? rows..rows : rows, @number_of_rows) cols = adjust_range(cols.kind_of?(Integer) ? cols..cols : cols, @number_of_columns) @argument_infos << ArgumentInfo.new(rows, cols, args) end @@ -507,11 +529,11 @@ # # Earlier defined arguments are overridden by later ones, except for the +:cell+ key which # is merged. def retrieve_arguments_for(row, col) @argument_infos.each_with_object({}) do |arg_info, result| - next unless arg_info.rows.cover?(row) && arg_info.cols.cover?(col) + next unless arg_info.rows.include?(row) && arg_info.cols.include?(col) if arg_info.args[:cell] arg_info.args[:cell] = (result[:cell] || {}).merge(arg_info.args[:cell]) end result.update(arg_info.args) end @@ -520,11 +542,12 @@ private # Adjusts the +range+ so that both the begin and the end of the range are zero or positive # integers smaller than +max+. def adjust_range(range, max) - (range.begin % max)..(range.end % max) + r = (range.begin % max)..(range.end % max) + range.kind_of?(Range) ? r : r.step(range.step) end end # Creates a HexaPDF::Layout::TableBox for the given table data. @@ -538,11 +561,12 @@ # 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. + # cells themselves, irrespective of the type of content of the cells. See + # CellArgumentCollector#[]= for details. # # 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 @@ -584,10 +608,11 @@ end box_class_for_name(:table).new(cells: cells, column_widths: column_widths, header: header, footer: footer, cell_style: cell_style, width: width, height: height, properties: properties, style: style) end + alias table table_box LOREM_IPSUM = [ # :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 " \ @@ -603,26 +628,17 @@ # # The +text_box_properties+ arguments are passed as is to #text_box. 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 + alias lorem_ipsum lorem_ipsum_box - BOX_METHOD_NAMES = [:text, :formatted_text, :image, :table, :lorem_ipsum] #:nodoc: - - # 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. + # Allows creating boxes using more convenient method names: The name of a pre-defined box + # class like #column will invoke #box appropriately. Same if used with a '_box' suffix. 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) + if @document.config['layout.boxes.map'].key?(name_without_box) box(name_without_box, *args, **kwargs, &block) else super end end @@ -630,10 +646,12 @@ # :nodoc: def respond_to_missing?(name, _private) box_creation_method?(name) || super end + BOX_METHOD_NAMES = [:text, :formatted_text, :image, :table, :lorem_ipsum] #:nodoc: + # :nodoc: 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 @@ -646,28 +664,31 @@ @document.config.constantize('layout.boxes.map', name) do raise HexaPDF::Error, "Couldn't retrieve box class #{name} from configuration" end end - # Retrieves the appropriate HexaPDF::Layout::Style object based on the +style+ and +properties+ - # arguments. + # Retrieves the appropriate HexaPDF::Layout::Style object based on the +style+ and + # +properties+ arguments. # # The +style+ argument specifies the style to retrieve. It can either be a registered style # name (see #style), a hash with style properties or +nil+. In the latter case the registered # style :base is used # # If the +properties+ hash is not empty, the retrieved style is duplicated and the properties # hash is applied to it. # - # Finally, a default font (the one from the :base style or otherwise 'Times') is set if - # necessary to ensure that the style object works in all cases. + # Finally, a default font (the one from the :base style or otherwise the one set using the + # configuration option 'font.default') is set if necessary to ensure that the style object + # works in all cases. def retrieve_style(style, properties = nil) if style.kind_of?(Symbol) && !@styles.key?(style) raise HexaPDF::Error, "Style #{style} not defined" end style = HexaPDF::Layout::Style.create(@styles[style] || style || @styles[:base]) style = style.dup.update(**properties) unless properties.nil? || properties.empty? - style.font(@styles[:base].font? && @styles[:base].font || 'Times') unless style.font? + unless style.font? + style.font(@styles[:base].font? && @styles[:base].font || @document.config['font.default']) + end unless style.font.respond_to?(:pdf_object) name, options = *style.font style.font(@document.fonts.add(name, **(options || {}))) end style