File: grid.rb

package info (click to toggle)
ruby-prawn 1.0.0~rc1%2Bdfsg1-3
  • links: PTS, VCS
  • area: main
  • in suites: wheezy
  • size: 4,248 kB
  • sloc: ruby: 17,499; sh: 44; makefile: 17
file content (259 lines) | stat: -rw-r--r-- 6,199 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
module Prawn
  class Document
    
    # 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
    #
    def define_grid(options = {})
      @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 box 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] ||= if args.empty?
        @grid
      else
        g1, g2 = args
        if(g1.class == Array && g2.class == Array && 
          g1.length == 2 && g2.length == 2)
          multi_box(single_box(*g1), single_box(*g2))
        else
          single_box(g1, g2)
        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.
    class Grid
      attr_reader :pdf, :columns, :rows, :gutter, :row_gutter, :column_gutter
      def initialize(pdf, options = {}) # :nodoc:
        valid_options = [:columns, :rows, :gutter, :row_gutter, :column_gutter]
        Prawn.verify_options valid_options, options
      
        @pdf = pdf
        @columns = options[:columns]
        @rows = options[:rows]
        set_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")
        self.rows.times do |i|
          self.columns.times do |j|
            pdf.grid(i,j).show(color)
          end
        end
      end

      private
      
      def subdivide(total, num, gutter)
        (total.to_f - (gutter * (num - 1).to_f)) / num.to_f
      end
      
      def set_gutter(options)
        if options.has_key?(:gutter)
          @gutter = options[:gutter].to_f
          @row_gutter, @column_gutter = @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.
    #
    class Box #:nodoc:
      attr_reader :pdf
    
      def initialize(pdf, i, j)
        @pdf = pdf
        @i = i
        @j = j
      end
    
      # Mostly diagnostic method that outputs the name of a box as 
      # col_num, row_num
      #
      def name
        "#{@i.to_s},#{@j.to_s}"
      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) * @j.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) * @i.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")
        self.bounding_box do
          original_stroke_color = pdf.stroke_color

          pdf.stroke_color = grid_color
          pdf.text self.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.
    class MultiBox < Box #:nodoc:
      def initialize(pdf, b1, b2)
        @pdf = pdf
        @bs = [b1, b2]
      end
    
      def name
        @bs.map {|b| b.name}.join(":")
      end
    
      def total_height
        @bs[0].total_height
      end

      def width
        right_box.right - left_box.left
      end
    
      def height
        top_box.top - bottom_box.bottom
      end
    
      def gutter
        @bs[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 ||= @bs.min {|a,b| a.left <=> b.left}
      end
    
      def right_box
        @right_box ||= @bs.max {|a,b| a.right <=> b.right}
      end
    
      def top_box
        @top_box ||= @bs.max {|a,b| a.top <=> b.top}
      end
    
      def bottom_box
        @bottom_box ||= @bs.min {|a,b| a.bottom <=> b.bottom}
      end
    end
  
    private
    def single_box(i, j)
      Box.new(self, i, j)
    end
  
    def multi_box(b1, b2)
      MultiBox.new(self, b1, b2)
    end
  end
end