prawn-table 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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