prawn-table 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a4a6491060408a154f0b6b70579d776f97624436
4
- data.tar.gz: d470ea52f9d97111fe2176dbe30382b4ecd9335f
3
+ metadata.gz: 1a9ab85b9c6e1e5faa063d37a21fa0dadf061a1a
4
+ data.tar.gz: 47f2bf92a9b72a9e7ee01a9761ed5ac4962661d0
5
5
  SHA512:
6
- metadata.gz: b710e1ae453b7008b313dc1a44b6e53f8930573727a4442fde65b3600fc7b40e867f9a21c345c97004ce9586ba376bec41d09eb5f744934ad2e1491e271fd4df
7
- data.tar.gz: 6fdb44072676f245e942f33c8d0c9b2fd8b47d99aa8dceef9866ed0ba2ee49d65dcbb6d788ed228789f628e6fe36544db08fb6a59ff43cc9853abab56f55588d
6
+ metadata.gz: 17c4dbc381202ee3d65eb782c62a3428cde3a76d0fbc1058077d6e15edeb99a27c5fbe01dea896023f89e1dc4b0d18518b4efc3eca2294c0db013cb456347255
7
+ data.tar.gz: 7fc28e1c4be55935e8a802643d41c46670403befc0cabe51b92adf28b6bd7a0ef98744ef2b71dfd2b85ad05be8d8d0fc8737dff34767bfdc4f80f942c1b49fbd
@@ -137,7 +137,6 @@ module Prawn
137
137
  @pdf = document
138
138
  @cells = make_cells(data)
139
139
  @header = false
140
- @epsilon = 1e-9
141
140
  options.each { |k, v| send("#{k}=", v) }
142
141
 
143
142
  if block
@@ -258,17 +257,10 @@ module Prawn
258
257
  #
259
258
  def draw
260
259
  with_position do
261
- # The cell y-positions are based on an infinitely long canvas. The offset
262
- # keeps track of how much we have to add to the original, theoretical
263
- # y-position to get to the actual position on the current page.
264
- offset = @pdf.y
265
-
266
260
  # Reference bounds are the non-stretchy bounds used to decide when to
267
261
  # flow to a new column / page.
268
262
  ref_bounds = @pdf.reference_bounds
269
263
 
270
- last_y = @pdf.y
271
-
272
264
  # Determine whether we're at the top of the current bounds (margin box or
273
265
  # bounding box). If we're at the top, we couldn't gain any more room by
274
266
  # breaking to the next page -- this means, in particular, that if the
@@ -279,122 +271,46 @@ module Prawn
279
271
  # Note that we use the actual bounds, not the reference bounds. This is
280
272
  # because even if we are in a stretchy bounding box, flowing to the next
281
273
  # page will not buy us any space if we are at the top.
282
- if @pdf.y > @pdf.bounds.height + @pdf.bounds.absolute_bottom - 0.001
283
- # we're at the top of our bounds
284
- started_new_page_at_row = 0
285
- else
286
- started_new_page_at_row = -1
287
-
288
- # If there isn't enough room left on the page to fit the first data row
289
- # (excluding the header), start the table on the next page.
290
- needed_height = row(0).height
291
- if @header
292
- if @header.is_a? Integer
293
- needed_height += row(1..@header).height
294
- else
295
- needed_height += row(1).height
296
- end
297
- end
298
- if needed_height > @pdf.y - ref_bounds.absolute_bottom
299
- @pdf.bounds.move_past_bottom
300
- offset = @pdf.y
301
- started_new_page_at_row = 0
302
- end
303
- end
274
+ #
275
+ # initial_row_on_initial_page may return 0 (already at the top OR created
276
+ # a new page) or -1 (enough space)
277
+ started_new_page_at_row = initial_row_on_initial_page
278
+
279
+ # The cell y-positions are based on an infinitely long canvas. The offset
280
+ # keeps track of how much we have to add to the original, theoretical
281
+ # y-position to get to the actual position on the current page.
282
+ offset = @pdf.y
304
283
 
305
284
  # Duplicate each cell of the header row into @header_row so it can be
306
285
  # modified in before_rendering_page callbacks.
307
- if @header
308
- @header_row = Cells.new
309
- if @header.is_a? Integer
310
- @header.times do |r|
311
- row(r).each { |cell| @header_row[cell.row, cell.column] = cell.dup }
312
- end
313
- else
314
- row(0).each { |cell| @header_row[cell.row, cell.column] = cell.dup }
315
- end
316
- end
286
+ @header_row = header_rows if @header
317
287
 
318
288
  # Track cells to be drawn on this page. They will all be drawn when this
319
289
  # page is finished.
320
290
  cells_this_page = []
321
291
 
322
292
  @cells.each do |cell|
323
- # we only need to run this test on the first cell in a row
324
- # check if the rows height fits on the page
325
- # check if the row is not the first on that page (wouldn't make sense to go to next page in this case)
326
- if cell.column == 0 &&
327
- row(cell.row).height_with_span > (cell.y + offset) - ref_bounds.absolute_bottom &&
328
- cell.row > started_new_page_at_row
329
- # Ink all cells on the current page
330
- if defined?(@before_rendering_page) && @before_rendering_page
331
- c = Cells.new(cells_this_page.map { |ci, _| ci })
332
- @before_rendering_page.call(c)
333
- end
334
- if @header_row.nil? || cells_this_page.size > @header_row.size
335
- Cell.draw_cells(cells_this_page)
336
- end
337
- cells_this_page = []
338
-
339
- # start a new page or column
340
- @pdf.bounds.move_past_bottom
341
- x_offset = @pdf.bounds.left_side - @pdf.bounds.absolute_left
342
- if cell.row > 0 && @header
343
- if @header.is_a? Integer
344
- header_height = 0
345
- y_coord = @pdf.cursor
346
- @header.times do |h|
347
- additional_header_height = add_header(cells_this_page, x_offset, y_coord-header_height, cell.row-1, h)
348
- header_height += additional_header_height
349
- end
350
- else
351
- header_height = add_header(cells_this_page, x_offset, @pdf.cursor, cell.row-1)
352
- end
353
- else
354
- header_height = 0
355
- end
356
- offset = @pdf.y - cell.y - header_height
293
+ if start_new_page?(cell, offset, ref_bounds)
294
+ # draw cells on the current page and then start a new one
295
+ # this will also add a header to the new page if a header is set
296
+ # reset array of cells for the new page
297
+ cells_this_page, offset = ink_and_draw_cells_and_start_new_page(cells_this_page, cell)
298
+
299
+ # remember the current row for background coloring
357
300
  started_new_page_at_row = cell.row
358
301
  end
359
302
 
360
- # Don't modify cell.x / cell.y here, as we want to reuse the original
361
- # values when re-inking the table. #draw should be able to be called
362
- # multiple times.
363
- x, y = cell.x, cell.y
364
- y += offset
365
-
366
- # Translate coordinates to the bounds we are in, since drawing is
367
- # relative to the cursor, not ref_bounds.
368
- x += @pdf.bounds.left_side - @pdf.bounds.absolute_left
369
- y -= @pdf.bounds.absolute_bottom
370
-
371
303
  # Set background color, if any.
372
- if defined?(@row_colors) && @row_colors && (!@header || cell.row > 0)
373
- # Ensure coloring restarts on every page (to make sure the header
374
- # and first row of a page are not colored the same way).
375
- if @header.is_a? Integer
376
- rows = @header
377
- elsif @header
378
- rows = 1
379
- else
380
- rows = 0
381
- end
382
- index = cell.row - [started_new_page_at_row, rows].max
383
-
384
- cell.background_color ||= @row_colors[index % @row_colors.length]
385
- end
304
+ cell = set_background_color(cell, started_new_page_at_row)
386
305
 
387
- cells_this_page << [cell, [x, y]]
388
- last_y = y
306
+ # add the current cell to the cells array for the current page
307
+ cells_this_page << [cell, [cell.relative_x, cell.relative_y(offset)]]
389
308
  end
309
+
390
310
  # Draw the last page of cells
391
- if defined?(@before_rendering_page) && @before_rendering_page
392
- c = Cells.new(cells_this_page.map { |ci, _| ci })
393
- @before_rendering_page.call(c)
394
- end
395
- Cell.draw_cells(cells_this_page)
311
+ ink_and_draw_cells(cells_this_page)
396
312
 
397
- @pdf.move_cursor_to(last_y - @cells.last.height)
313
+ @pdf.move_cursor_to(@cells.last.relative_y(offset) - @cells.last.height)
398
314
  end
399
315
  end
400
316
 
@@ -409,19 +325,19 @@ module Prawn
409
325
  #
410
326
  def column_widths
411
327
  @column_widths ||= begin
412
- if width - cells.min_width < -epsilon
328
+ if width - cells.min_width < -Prawn::FLOAT_PRECISION
413
329
  raise Errors::CannotFit,
414
330
  "Table's width was set too small to contain its contents " +
415
331
  "(min width #{cells.min_width}, requested #{width})"
416
332
  end
417
333
 
418
- if width - cells.max_width > epsilon
334
+ if width - cells.max_width > Prawn::FLOAT_PRECISION
419
335
  raise Errors::CannotFit,
420
336
  "Table's width was set larger than its contents' maximum width " +
421
337
  "(max width #{cells.max_width}, requested #{width})"
422
338
  end
423
339
 
424
- if width - natural_width < -epsilon
340
+ if width - natural_width < -Prawn::FLOAT_PRECISION
425
341
  # Shrink the table to fit the requested width.
426
342
  f = (width - cells.min_width).to_f / (natural_width - cells.min_width)
427
343
 
@@ -429,7 +345,7 @@ module Prawn
429
345
  min, nat = column(c).min_width, natural_column_widths[c]
430
346
  (f * (nat - min)) + min
431
347
  end
432
- elsif width - natural_width > epsilon
348
+ elsif width - natural_width > Prawn::FLOAT_PRECISION
433
349
  # Expand the table to fit the requested width.
434
350
  f = (width - cells.width).to_f / (cells.max_width - cells.width)
435
351
 
@@ -464,6 +380,123 @@ module Prawn
464
380
  end
465
381
 
466
382
  protected
383
+
384
+ # sets the background color (if necessary) for the given cell
385
+ def set_background_color(cell, started_new_page_at_row)
386
+ if defined?(@row_colors) && @row_colors && (!@header || cell.row > 0)
387
+ # Ensure coloring restarts on every page (to make sure the header
388
+ # and first row of a page are not colored the same way).
389
+ rows = number_of_header_rows
390
+
391
+ index = cell.row - [started_new_page_at_row, rows].max
392
+
393
+ cell.background_color ||= @row_colors[index % @row_colors.length]
394
+ end
395
+ cell
396
+ end
397
+
398
+ # number of rows of the header
399
+ # @return [Integer] the number of rows of the header
400
+ def number_of_header_rows
401
+ # header may be set to any integer value -> number of rows
402
+ if @header.is_a? Integer
403
+ return @header
404
+ # header may be set to true -> first row is repeated
405
+ elsif @header
406
+ return 1
407
+ end
408
+ # defaults to 0 header rows
409
+ 0
410
+ end
411
+
412
+ # should we start a new page? (does the current row fail to fit on this page)
413
+ def start_new_page?(cell, offset, ref_bounds)
414
+ # we only need to run this test on the first cell in a row
415
+ # check if the rows height fails to fit on the page
416
+ # check if the row is not the first on that page (wouldn't make sense to go to next page in this case)
417
+ (cell.column == 0 && cell.row > 0 &&
418
+ !row(cell.row).fits_on_current_page?(offset, ref_bounds))
419
+ end
420
+
421
+ # ink cells and then draw them
422
+ def ink_and_draw_cells(cells_this_page, draw_cells = true)
423
+ ink_cells(cells_this_page)
424
+ Cell.draw_cells(cells_this_page) if draw_cells
425
+ end
426
+
427
+ # ink and draw cells, then start a new page
428
+ def ink_and_draw_cells_and_start_new_page(cells_this_page, cell)
429
+ # don't draw only a header
430
+ draw_cells = (@header_row.nil? || cells_this_page.size > @header_row.size)
431
+
432
+ ink_and_draw_cells(cells_this_page, draw_cells)
433
+
434
+ # start a new page or column
435
+ @pdf.bounds.move_past_bottom
436
+
437
+ offset = (@pdf.y - cell.y)
438
+
439
+ cells_next_page = []
440
+
441
+ header_height = add_header(cell.row, cells_next_page)
442
+
443
+ # account for header height in newly generated offset
444
+ offset -= header_height
445
+
446
+ # reset cells_this_page in calling function and return new offset
447
+ return cells_next_page, offset
448
+ end
449
+
450
+ # Ink all cells on the current page
451
+ def ink_cells(cells_this_page)
452
+ if defined?(@before_rendering_page) && @before_rendering_page
453
+ c = Cells.new(cells_this_page.map { |ci, _| ci })
454
+ @before_rendering_page.call(c)
455
+ end
456
+ end
457
+
458
+ # Determine whether we're at the top of the current bounds (margin box or
459
+ # bounding box). If we're at the top, we couldn't gain any more room by
460
+ # breaking to the next page -- this means, in particular, that if the
461
+ # first row is taller than the margin box, we will only move to the next
462
+ # page if we're below the top. Some floating-point tolerance is added to
463
+ # the calculation.
464
+ #
465
+ # Note that we use the actual bounds, not the reference bounds. This is
466
+ # because even if we are in a stretchy bounding box, flowing to the next
467
+ # page will not buy us any space if we are at the top.
468
+ # @return [Integer] 0 (already at the top OR created a new page) or -1 (enough space)
469
+ def initial_row_on_initial_page
470
+ # we're at the top of our bounds
471
+ return 0 if fits_on_page?(@pdf.bounds.height)
472
+
473
+ needed_height = row(0..number_of_header_rows).height
474
+
475
+ # have we got enough room to fit the first row (including header row(s))
476
+ return -1 if fits_on_page?(needed_height)
477
+
478
+ # If there isn't enough room left on the page to fit the first data row
479
+ # (including the header), start the table on the next page.
480
+ @pdf.bounds.move_past_bottom
481
+
482
+ # we are at the top of a new page
483
+ 0
484
+ end
485
+
486
+ # do we have enough room to fit a given height on to the current page?
487
+ def fits_on_page?(needed_height)
488
+ needed_height < @pdf.y - (@pdf.bounds.absolute_bottom - Prawn::FLOAT_PRECISION)
489
+ end
490
+
491
+ # return the header rows
492
+ # @api private
493
+ def header_rows
494
+ header_rows = Cells.new
495
+ number_of_header_rows.times do |r|
496
+ row(r).each { |cell| header_rows[cell.row, cell.column] = cell.dup }
497
+ end
498
+ header_rows
499
+ end
467
500
 
468
501
  # Converts the array of cellable objects given into instances of
469
502
  # Prawn::Table::Cell, and sets up their in-table properties so that they
@@ -528,6 +561,19 @@ module Prawn
528
561
  cells
529
562
  end
530
563
 
564
+ def add_header(row_number, cells_this_page)
565
+ x_offset = @pdf.bounds.left_side - @pdf.bounds.absolute_left
566
+ header_height = 0
567
+ if row_number > 0 && @header
568
+ y_coord = @pdf.cursor
569
+ number_of_header_rows.times do |h|
570
+ additional_header_height = add_one_header_row(cells_this_page, x_offset, y_coord-header_height, row_number-1, h)
571
+ header_height += additional_header_height
572
+ end
573
+ end
574
+ header_height
575
+ end
576
+
531
577
  # Add the header row(s) to the given array of cells at the given y-position.
532
578
  # Number the row with the given +row+ index, so that the header appears (in
533
579
  # any Cells built for this page) immediately prior to the first data row on
@@ -535,12 +581,20 @@ module Prawn
535
581
  #
536
582
  # Return the height of the header.
537
583
  #
538
- def add_header(page_of_cells, x_offset, y, row, row_of_header=nil)
584
+ def add_one_header_row(page_of_cells, x_offset, y, row, row_of_header=nil)
539
585
  rows_to_operate_on = @header_row
540
586
  rows_to_operate_on = @header_row.rows(row_of_header) if row_of_header
541
587
  rows_to_operate_on.each do |cell|
542
588
  cell.row = row
543
- cell.dummy_cells.each {|c| c.row = row + c.row }
589
+ cell.dummy_cells.each {|c|
590
+ if cell.rowspan > 1
591
+ # be sure to account for cells that span multiple rows
592
+ # in this case you need multiple row numbers
593
+ c.row += row
594
+ else
595
+ c.row = row
596
+ end
597
+ }
544
598
  page_of_cells << [cell, [cell.x + x_offset, y]]
545
599
  end
546
600
  rows_to_operate_on.height
@@ -634,11 +688,6 @@ module Prawn
634
688
  @pdf.y = final_y
635
689
  end
636
690
 
637
- private
638
-
639
- def epsilon
640
- @epsilon
641
- end
642
691
  end
643
692
  end
644
693
 
@@ -444,6 +444,12 @@ module Prawn
444
444
  @point[0] = val
445
445
  end
446
446
 
447
+ def relative_x
448
+ # Translate coordinates to the bounds we are in, since drawing is
449
+ # relative to the cursor, not ref_bounds.
450
+ x + @pdf.bounds.left_side - @pdf.bounds.absolute_left
451
+ end
452
+
447
453
  # y-position of the cell within the parent bounds.
448
454
  #
449
455
  def y
@@ -456,6 +462,10 @@ module Prawn
456
462
  @point[1] = val
457
463
  end
458
464
 
465
+ def relative_y(offset = 0)
466
+ y + offset - @pdf.bounds.absolute_bottom
467
+ end
468
+
459
469
  # Sets padding on this cell. The argument can be one of:
460
470
  #
461
471
  # * an integer (sets all padding)
@@ -37,6 +37,10 @@ module Prawn
37
37
  #
38
38
  class Cells < Array
39
39
 
40
+ def fits_on_current_page?(offset, ref_bounds)
41
+ height_with_span < (self[0,0].y + offset) - ref_bounds.absolute_bottom
42
+ end
43
+
40
44
  # @group Experimental API
41
45
 
42
46
  # Limits selection to the given row or rows. +row_spec+ can be anything
@@ -153,15 +157,7 @@ module Prawn
153
157
  # Returns the total width of all columns in the selected set.
154
158
  #
155
159
  def width
156
- widths = {}
157
- each do |cell|
158
- per_cell_width = cell.width_ignoring_span.to_f / cell.colspan
159
- cell.colspan.times do |n|
160
- widths[cell.column+n] = [widths[cell.column+n], per_cell_width].
161
- compact.max
162
- end
163
- end
164
- widths.values.inject(0, &:+)
160
+ ColumnWidthCalculator.new(self).natural_widths.inject(0, &:+)
165
161
  end
166
162
 
167
163
  # Returns minimum width required to contain cells in the set.
@@ -69,6 +69,14 @@ describe "Prawn::Table" do
69
69
  output = PDF::Inspector::Page.analyze(pdf.render)
70
70
  output.pages[0][:strings][0..4].should == output.pages[1][:strings][0..4]
71
71
  end
72
+
73
+ it "should respect an explicit set table with", :issue => 6 do
74
+ data = [[{ :content => "Current Supplier: BLINKY LIGHTS COMPANY", :colspan => 4 }],
75
+ ["Current Supplier: BLINKY LIGHTS COMPANY", "611 kWh X $.090041", "$", "55.02"]]
76
+ pdf = Prawn::Document.new
77
+ table = Prawn::Table.new data, pdf, :width => pdf.bounds.width
78
+ table.column_widths.inject{|sum,x| sum + x }.should == pdf.bounds.width
79
+ end
72
80
  end
73
81
 
74
82
  describe "Text may be longer than the available space in a row on a single page" do
@@ -1274,6 +1282,32 @@ describe "Prawn::Table" do
1274
1282
 
1275
1283
  end
1276
1284
 
1285
+ it "Prints table on one page when using subtable with colspan > 1", :unresolved, issue: 10 do
1286
+ pdf = Prawn::Document.new(margin: [ 30, 71, 55, 71])
1287
+
1288
+ lines = "one\ntwo\nthree\nfour"
1289
+
1290
+ sub_table_lines = lines.split("\n").map do |line|
1291
+ if line == "one"
1292
+ [ { content: "#{line}", colspan: 2, size: 11} ]
1293
+ else
1294
+ [ { content: "\u2022"}, { content: "#{line}"} ]
1295
+ end
1296
+ end
1297
+
1298
+ sub_table = pdf.make_table(sub_table_lines,
1299
+ cell_style: { border_color: '00ff00'})
1300
+
1301
+ #outer table
1302
+ pdf.table [[
1303
+ { content: "Placeholder text", width: 200 },
1304
+ { content: sub_table }
1305
+ ]], width: 515, cell_style: { border_width: 1, border_color: 'ff0000' }
1306
+
1307
+ pdf.render
1308
+ pdf.page_count.should == 1
1309
+ end
1310
+
1277
1311
  describe "An invalid table" do
1278
1312
 
1279
1313
  before(:each) do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: prawn-table
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gregory Brown
@@ -13,7 +13,7 @@ authors:
13
13
  autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
- date: 2014-07-27 00:00:00.000000000 Z
16
+ date: 2014-08-29 00:00:00.000000000 Z
17
17
  dependencies:
18
18
  - !ruby/object:Gem::Dependency
19
19
  name: prawn