lib/hexapdf/layout/box.rb in hexapdf-0.35.1 vs lib/hexapdf/layout/box.rb in hexapdf-0.36.0
- old
+ new
@@ -58,32 +58,63 @@
#
# Each subclass should only take keyword arguments on initialization so that the boxes can be
# instantiated from the common convenience method HexaPDF::Document::Layout#box. To use this
# facility subclasses need to be registered with the configuration option 'layout.boxes.map'.
#
- # The methods #fit, #supports_position_flow?, #split or #split_content, #empty?, and #draw or
- # #draw_content need to be customized according to the subclass's use case.
+ # The methods #supports_position_flow?, #empty?, #fit or #fit_content, #split or #split_content,
+ # and #draw or #draw_content need to be customized according to the subclass's use case (also
+ # see the documentation of the methods besides the informatione below):
#
- # #fit:: This method should return +true+ if fitting was successful. Additionally, the
- # @fit_successful instance variable needs to be set to the fit result as it is used in
- # #split.
- #
# #supports_position_flow?::
- # If the subclass supports the value :flow of the 'position' style property, this method
- # needs to be overridden to return +true+.
+ # If the subclass supports the value :flow of the 'position' style property, this method
+ # needs to be overridden to return +true+.
#
- # #split:: This method splits the content so that the current region is used as good as
- # possible. The default implementation should be fine for most use-cases, so only
- # #split_content needs to be implemented. The method #create_split_box should be used
- # for getting a basic cloned box.
+ # #empty?::
+ # This method should return +true+ if the subclass won't draw anything when #draw is called.
#
- # #empty?:: This method should return +true+ if the subclass won't draw anything when #draw is
- # called.
+ # #fit::
+ # This method should return +true+ if fitting was successful. Additionally, the
+ # @fit_successful instance variable needs to be set to the fit result as it is used in
+ # #split.
#
- # #draw:: This method draws the content and the default implementation already handles things
- # like drawing the border and background. Therefore it's best to implement #draw_content
- # which should just draw the content.
+ # The default implementation provides code common to most use-cases and delegates the
+ # specifics to the #fit_content method which needs to return +true+ if fitting was
+ # successful.
+ #
+ # #split::
+ # This method splits the content so that the current region is used as good as possible. The
+ # default implementation should be fine for most use-cases, so only #split_content needs to
+ # be implemented. The method #create_split_box should be used for getting a basic cloned
+ # box.
+ #
+ # #draw::
+ # This method draws the content and the default implementation already handles things like
+ # drawing the border and background. So it should not be overridden. The box specific
+ # drawing commands should be implemented in the #draw_content method.
+ #
+ # This base class provides various private helper methods for use in the above methods:
+ #
+ # +reserved_width+, +reserved_height+::
+ # Returns the width respectively the height of the reserved space inside the box that is
+ # used for the border and padding.
+ #
+ # +reserved_width_left+, +reserved_width_right+, +reserved_height_top+,
+ # +reserved_height_bottom+::
+ # Returns the reserved space inside the box at the specified edge (left, right, top,
+ # bottom).
+ #
+ # +update_content_width+, +update_content_height+::
+ # Takes a block that should return the content width respectively height and sets the box's
+ # width respectively height accordingly.
+ #
+ # +create_split_box+::
+ # Creates a new box based on this one and resets the internal data back to their original
+ # values.
+ #
+ # The keyword argument +split_box_value+ (defaults to +true+) is used to set the
+ # +@split_box+ variable to make the new box aware that it is a split box. This can be set to
+ # any other truthy value to convey more meaning.
class Box
include HexaPDF::Utils
# Creates a new Box object, using the provided block as drawing block (see ::new).
@@ -160,11 +191,12 @@
@draw_block = block
@fit_successful = false
@split_box = false
end
- # Returns +true+ if this is a split box, i.e. the rest of another box after it was split.
+ # Returns the set truthy value if this is a split box, i.e. the rest of another box after it
+ # was split.
def split_box?
@split_box
end
# Returns +false+ since a basic box doesn't support the 'position' style property value :flow.
@@ -182,22 +214,38 @@
def content_height
height = @height - reserved_height
height < 0 ? 0 : height
end
- # Fits the box into the Frame and returns +true+ if fitting was successful.
+ # Fits the box into the *frame* and returns +true+ if fitting was successful.
#
# The arguments +available_width+ and +available_height+ are the width and height of the
- # current region of the frame. The frame itself is provided as third argument.
+ # current region of the frame, adjusted for this box. The frame itself is provided as third
+ # argument.
#
- # The default implementation uses the available width and height for the box width and height
- # if they were initially set to 0. Otherwise the specified dimensions are used.
- def fit(available_width, available_height, _frame)
+ # The default implementation uses the given available width and height for the box width and
+ # height if they were initially set to 0. Otherwise the intially specified dimensions are
+ # used. Then the #fit_content method is called which allows sub-classes to fit their content.
+ #
+ # The following variables are set that may later be used during splitting or drawing:
+ #
+ # * (@fit_x, @fit_y): The lower-left corner of the content box where fitting was done. Can be
+ # used to adjust the drawing position in #draw/#draw_content if necessary.
+ # * @fit_successful: +true+ if fitting was successful.
+ def fit(available_width, available_height, frame)
@width = (@initial_width > 0 ? @initial_width : available_width)
@height = (@initial_height > 0 ? @initial_height : available_height)
@fit_successful = (float_compare(@width, available_width) <= 0 &&
float_compare(@height, available_height) <= 0)
+ return unless @fit_successful
+
+ @fit_successful = fit_content(available_width, available_height, frame)
+
+ @fit_x = frame.x + reserved_width_left
+ @fit_y = frame.y - @height + reserved_height_bottom
+
+ @fit_successful
end
# Tries to split the box into two, the first of which needs to fit into the current region of
# the frame, and returns the parts as array.
#
@@ -235,10 +283,12 @@
#
# The block specified when creating the box is invoked with the canvas and the box as
# arguments. Subclasses can specify an on-demand drawing method by setting the +@draw_block+
# instance variable to +nil+ or a valid block. This is useful to avoid unnecessary set-up
# operations when the block does nothing.
+ #
+ # Alternatively, if a #draw_content method is defined, this method is called.
def draw(canvas, x, y)
if (oc = properties['optional_content'])
canvas.optional_content(oc)
end
@@ -314,26 +364,51 @@
result += style.padding.bottom if style.padding?
result += style.border.width.bottom if style.border?
result
end
- # Draws the content of the box at position [x, y] which is the bottom-left corner of the
- # content box.
- def draw_content(canvas, x, y)
- if @draw_block
- canvas.translate(x, y) { @draw_block.call(canvas, self) }
- end
+ # Updates the width of the box using the content width returned by the block.
+ def update_content_width
+ return if @initial_width > 0
+ @width = yield + reserved_width
end
+ # Updates the height of the box using the content height returned by the block.
+ def update_content_height
+ return if @initial_height > 0
+ @height = yield + reserved_height
+ end
+
+ # Fits the content of the box and returns whether fitting was successful.
+ #
+ # This is just a stub implementation that returns +true+. Subclasses should override it to
+ # provide the box specific behaviour.
+ #
+ # See #fit for details.
+ def fit_content(available_width, available_height, frame)
+ true
+ end
+
# Splits the content of the box.
#
# This is just a stub implementation, returning [nil, self] since we can't know how to split
# the content when it didn't fit.
#
# Subclasses that support splitting content need to provide an appropriate implementation and
# use #create_split_box to create a cloned box to supply as the second argument.
def split_content(_available_width, _available_height, _frame)
[nil, self]
+ end
+
+ # Draws the content of the box at position [x, y] which is the bottom-left corner of the
+ # content box.
+ #
+ # This implementation uses the drawing block provided on initialization, if set, to draw the
+ # contents. Subclasses should override it to provide box specific behaviour.
+ def draw_content(canvas, x, y)
+ if @draw_block
+ canvas.translate(x, y) { @draw_block.call(canvas, self) }
+ end
end
# Creates a new box based on this one and resets the data back to their original values.
#
# The variable +@split_box+ is set to +split_box_value+ (defaults to +true+) to make the new