File: grid.rb

package info (click to toggle)
ruby-prawn 2.3.0%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 4,380 kB
  • sloc: ruby: 15,820; sh: 43; makefile: 20
file content (287 lines) | stat: -rw-r--r-- 6,718 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
# frozen_string_literal: true

# grid.rb: Provides a basic grid layout system for Prawn
#
# Contributed by Andrew O'Brien in March 2009
#
# This is free software. Please see the LICENSE and COPYING files for details.

module Prawn
  class Document
    # @group Experimental API

    # Defines the grid system for a particular document.  Takes the number of
    # rows and columns and the width to use for the gutter as the
    # keys :rows, :columns, :gutter, :row_gutter, :column_gutter
    #
    # Note that a completely new grid object is built each time define_grid()
    # is called. This means that all subsequent calls to grid() will use
    # the newly defined Grid object -- grids are not nestable like
    # bounding boxes are.

    def define_grid(options = {})
      @boxes = nil
      @grid = Grid.new(self, options)
    end

    # A method that can either be used to access a particular grid on the page
    # or work with the grid system directly.
    #
    #   @pdf.grid                 # Get the Grid directly
    #   @pdf.grid([0,1])          # Get the GridBox at [0,1]
    #   @pdf.grid([0,1], [1,2])   # Get a multi-box spanning from [0,1] to [1,2]
    #
    def grid(*args)
      @boxes ||= {}
      @boxes[args] ||=
        begin
          if args.empty?
            @grid
          else
            g1, g2 = args

            if g1.is_a?(Array) && g2.is_a?(Array) &&
                g1.length == 2 && g2.length == 2
              multi_box(single_box(*g1), single_box(*g2))
            else
              single_box(g1, g2)
            end
          end
        end
    end

    # A Grid represents the entire grid system of a Page and calculates
    # the column width and row height of the base box.
    #
    # @group Experimental API
    class Grid
      attr_reader :pdf, :columns, :rows, :gutter, :row_gutter, :column_gutter
      def initialize(pdf, options = {}) # :nodoc:
        valid_options = %i[columns rows gutter row_gutter column_gutter]
        Prawn.verify_options valid_options, options

        @pdf = pdf
        @columns = options[:columns]
        @rows = options[:rows]
        apply_gutter(options)
      end

      # Calculates the base width of boxes.
      def column_width
        @column_width ||= subdivide(pdf.bounds.width, columns, column_gutter)
      end

      # Calculates the base height of boxes.
      def row_height
        @row_height ||= subdivide(pdf.bounds.height, rows, row_gutter)
      end

      # Diagnostic tool to show all of the grids.  Defaults to gray.
      def show_all(color = 'CCCCCC')
        rows.times do |row|
          columns.times do |column|
            pdf.grid(row, column).show(color)
          end
        end
      end

      private

      def subdivide(total, num, gutter)
        (total.to_f - (gutter * (num - 1).to_f)) / num.to_f
      end

      def apply_gutter(options)
        if options.key?(:gutter)
          @gutter = options[:gutter].to_f
          @row_gutter = @gutter
          @column_gutter = @gutter
        else
          @row_gutter = options[:row_gutter].to_f
          @column_gutter = options[:column_gutter].to_f
          @gutter = 0
        end
      end
    end

    # A Box is a class that represents a bounded area of a page.
    # A Grid object has methods that allow easy access to the coordinates of
    # its corners, which can be plugged into most existing prawnmethods.
    #
    # @group Experimental API
    class GridBox
      attr_reader :pdf

      def initialize(pdf, rows, columns)
        @pdf = pdf
        @rows = rows
        @columns = columns
      end

      # Mostly diagnostic method that outputs the name of a box as
      # col_num, row_num
      #
      def name
        "#{@rows},#{@columns}"
      end

      # :nodoc
      def total_height
        pdf.bounds.height.to_f
      end

      # Width of a box
      def width
        grid.column_width.to_f
      end

      # Height of a box
      def height
        grid.row_height.to_f
      end

      # Width of the gutter
      def gutter
        grid.gutter.to_f
      end

      # x-coordinate of left side
      def left
        @left ||= (width + grid.column_gutter) * @columns.to_f
      end

      # x-coordinate of right side
      def right
        @right ||= left + width
      end

      # y-coordinate of the top
      def top
        @top ||= total_height - ((height + grid.row_gutter) * @rows.to_f)
      end

      # y-coordinate of the bottom
      def bottom
        @bottom ||= top - height
      end

      # x,y coordinates of top left corner
      def top_left
        [left, top]
      end

      # x,y coordinates of top right corner
      def top_right
        [right, top]
      end

      # x,y coordinates of bottom left corner
      def bottom_left
        [left, bottom]
      end

      # x,y coordinates of bottom right corner
      def bottom_right
        [right, bottom]
      end

      # Creates a standard bounding box based on the grid box.
      def bounding_box(&blk)
        pdf.bounding_box(top_left, width: width, height: height, &blk)
      end

      # Diagnostic method
      def show(grid_color = 'CCCCCC')
        bounding_box do
          original_stroke_color = pdf.stroke_color

          pdf.stroke_color = grid_color
          pdf.text name
          pdf.stroke_bounds

          pdf.stroke_color = original_stroke_color
        end
      end

      private

      def grid
        pdf.grid
      end
    end

    # A MultiBox is specified by 2 Boxes and spans the areas between.
    #
    # @group Experimental API
    class MultiBox < GridBox
      def initialize(pdf, box1, box2)
        @pdf = pdf
        @boxes = [box1, box2]
      end

      def name
        @boxes.map(&:name).join(':')
      end

      def total_height
        @boxes[0].total_height
      end

      def width
        right_box.right - left_box.left
      end

      def height
        top_box.top - bottom_box.bottom
      end

      def gutter
        @boxes[0].gutter
      end

      def left
        left_box.left
      end

      def right
        right_box.right
      end

      def top
        top_box.top
      end

      def bottom
        bottom_box.bottom
      end

      private

      def left_box
        @left_box ||= @boxes.min_by(&:left)
      end

      def right_box
        @right_box ||= @boxes.max_by(&:right)
      end

      def top_box
        @top_box ||= @boxes.max_by(&:top)
      end

      def bottom_box
        @bottom_box ||= @boxes.min_by(&:bottom)
      end
    end

    private

    def single_box(rows, columns)
      GridBox.new(self, rows, columns)
    end

    def multi_box(box1, box2)
      MultiBox.new(self, box1, box2)
    end
  end
end