pdf-wrapper 0.1.0 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,18 @@
1
+ v0.1.3 (24th July 2008)
2
+ - Require the gem version of cairo
3
+ - >= 1.5. However I recommend >= 1.6.3 to avoid FATAL error under 1.8.7
4
+ - Removed Wrapper#scale. Was causing more trouble than it's worth. May re-implement later.
5
+ - Allow the render functions to be called multiple times
6
+ - Added Wrapper#finished?
7
+ - rename Wrapper#render_to_file to Wrapper#render_file.
8
+ - old method still exists with a deprecation warning
9
+ - add :rotate option to Wrapper#image (thanks Lourens Naudé)
10
+ - borrowed some convenience methods for co-ordinate manipulation from Prawn
11
+ - Wrapper#translate now resets the point to 0,0 before yielding
12
+
13
+ v0.1.1 (Unreleased)
14
+ - use a proxy object when building repeating objects to prevent a new page being started
15
+
1
16
  v0.1.0 (28th May 2008)
2
17
  - added PDF::Wrapper#translate and PDF::Wrapper#scale
3
18
  - fixed a bug that caused some text to be rendered off the page when wrapping onto 3rd and
@@ -1,27 +1,30 @@
1
1
  = Overview
2
2
 
3
3
  PDF::Wrapper is a PDF generation library that uses the cairo and pango
4
- libraries to do the heavy lifting. I've essentially just wrapped these general
5
- purpose graphics libraries with some sugar that makes them a little easier to
6
- use for making PDFs. The idea is to lever the low level tools in those libraries
7
- (drawing shapes, laying out text, importing raster images, etc) to build some
8
- higher level tools - tables, text boxes, borders, lists, repeating elements
9
- (headers/footers), etc.
10
-
11
- The API started off *roughly* following that of PDF::Writer, but i've made
12
- tweaks and changes along the way and it's diverging. This is a work in progress
13
- so many features of PDF::Writer aren't available yet.
4
+ native libraries to do the heavy lifting. It essentially just wraps these
5
+ general purpose graphics libraries with some sugar that makes them a little
6
+ easier to use for making PDFs. The idea is to lever the low level tools in
7
+ those libraries (drawing shapes, laying out text, importing raster images, etc)
8
+ to build some higher level tools - tables, text boxes, borders, lists,
9
+ repeating elements (headers/footers), etc.
10
+
11
+ The API started off *roughly* following that of PDF::Writer, but it has since
12
+ diverged significantly. I've spent some time contributing to a pure Ruby PDF
13
+ generation library (Prawn[http://github.com/sandal/prawn/tree]) and its elegant
14
+ and simple API is having a strong effect on the direction I've been taking
15
+ PDF::Wrapper.
14
16
 
15
17
  A key motivation for writing this library is cairo's support for Unicode in PDFs.
16
- All text functions in this library support UTF-8 input, although as a native
17
- English speaker I've only tested this a little, so any feedback is welcome.
18
+ All text functions in this library require UTF-8 input, although as a native
19
+ English speaker I've only tested non ASCII text a little, so any feedback is
20
+ welcome.
18
21
 
19
- There also seems to be a lack of English documentation available for the ruby
22
+ There also seems to be a lack of English documentation available for the ruby
20
23
  bindings to cairo/pango, so I'm aiming to document the code as much as possible
21
24
  to provide worked examples for others. I'm learning as I go though, so if regular
22
25
  users of either library spot techniques that fail best practice, please let me know.
23
26
 
24
- It's early days, so the API is far from stable and I'm hesitant to write extensive
27
+ It's early days, so the API is far from stable and I'm hesitant to write extensive
25
28
  documentation just yet. It's the price you pay for being an early adopter. The
26
29
  examples/ dir should have a range of sample code, and I'll try to keep it up to
27
30
  date.
@@ -44,7 +47,7 @@ James Healy <jimmy@deefa.com>
44
47
 
45
48
  * GPL version 2 or the Ruby License
46
49
  * Ruby: http://www.ruby-lang.org/en/LICENSE.txt
47
-
50
+
48
51
  = Dependencies
49
52
 
50
53
  * ruby/cairo[http://cairographics.org/rcairo/]
@@ -65,7 +68,9 @@ of the cairo source available on your system.
65
68
 
66
69
  = Compatibility
67
70
 
68
- JRuby users, you're probably out of luck.
71
+ JRuby users, you're currently out of luck. In theory it should be possible to use the Java bindings
72
+ to the native libraries we need, but as I'm not a JRuby user, it's not an itch
73
+ I've been motivated to scratch.
69
74
 
70
75
  Rubinius users, I have no idea.
71
76
 
data/Rakefile CHANGED
@@ -6,7 +6,7 @@ require 'rake/testtask'
6
6
  require "rake/gempackagetask"
7
7
  require 'spec/rake/spectask'
8
8
 
9
- PKG_VERSION = "0.1.0"
9
+ PKG_VERSION = "0.1.3"
10
10
  PKG_NAME = "pdf-wrapper"
11
11
  PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
12
12
 
@@ -18,6 +18,7 @@ desc "Run all rspec files"
18
18
  Spec::Rake::SpecTask.new("spec") do |t|
19
19
  # spec files listed explicitly so that load_spec is the first one run
20
20
  t.spec_files = ['specs/load_spec.rb','specs/image_spec.rb','specs/graphics_spec.rb','specs/tables_spec.rb','specs/text_spec.rb','specs/wrapper_spec.rb']
21
+ t.spec_opts = ['-c']
21
22
  t.rcov = true
22
23
  t.rcov_dir = (ENV['CC_BUILD_ARTIFACTS'] || 'doc') + "/rcov"
23
24
  t.rcov_opts = ["--exclude","spec.*\.rb","--exclude",".*cairo.*","--exclude",".*rcov.*","--exclude",".*rspec.*","--exclude",".*pdf-reader.*", "--exclude",".*gems.*"]
@@ -45,7 +46,7 @@ desc "Create documentation"
45
46
  Rake::RDocTask.new("doc") do |rdoc|
46
47
  rdoc.title = "pdf-wrapper"
47
48
  rdoc.rdoc_dir = (ENV['CC_BUILD_ARTIFACTS'] || 'doc') + '/rdoc'
48
- rdoc.rdoc_files.include('README')
49
+ rdoc.rdoc_files.include('README.rdoc')
49
50
  rdoc.rdoc_files.include('CHANGELOG')
50
51
  rdoc.rdoc_files.include('TODO')
51
52
  rdoc.rdoc_files.include('lib/**/*.rb')
@@ -67,8 +68,9 @@ spec = Gem::Specification.new do |spec|
67
68
  spec.files = Dir.glob("{examples,lib,specs}/**/**/*") + ["Rakefile"]
68
69
  spec.require_path = "lib"
69
70
  spec.has_rdoc = true
70
- spec.extra_rdoc_files = %w{README CHANGELOG TODO}
71
- spec.rdoc_options << '--title' << 'PDF::Wrapper Documentation' << '--main' << 'README' << '-q'
71
+ spec.extra_rdoc_files = %w{README.rdoc CHANGELOG TODO}
72
+ spec.rdoc_options << '--title' << 'PDF::Wrapper Documentation' << '--main' << 'README.rdoc' << '-q'
73
+ spec.add_dependency 'cairo', '>=1.5.0'
72
74
  spec.author = "James Healy"
73
75
  spec.homepage = "http://pdf-wrapper.rubyforge.org/"
74
76
  spec.email = "jimmy@deefa.com"
data/examples/text.rb ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ # coding: utf-8
3
+
4
+ $:.unshift(File.dirname(__FILE__) + "/../lib")
5
+
6
+ require 'pdf/wrapper'
7
+
8
+ pdf = PDF::Wrapper.new(:paper => :A4)
9
+ pdf.font("Sans Serif")
10
+ pdf.text "Line 1", :font_size => 8
11
+ pdf.text "Line 2", :font_size => 8
12
+ pdf.text "Line 3", :font_size => 8
13
+ pdf.text "Line 4\nLine 5", :font_size => 8
14
+ pdf.render_to_file("text.pdf")
data/lib/pdf/errors.rb ADDED
@@ -0,0 +1 @@
1
+ class InvalidOperationError < RuntimeError; end;
data/lib/pdf/wrapper.rb CHANGED
@@ -2,28 +2,18 @@
2
2
 
3
3
  require 'stringio'
4
4
  require 'pdf/core'
5
+ require 'pdf/errors'
5
6
 
6
7
  require File.dirname(__FILE__) + "/wrapper/graphics"
7
8
  require File.dirname(__FILE__) + "/wrapper/images"
8
9
  require File.dirname(__FILE__) + "/wrapper/loading"
9
10
  require File.dirname(__FILE__) + "/wrapper/table"
10
11
  require File.dirname(__FILE__) + "/wrapper/text"
12
+ require File.dirname(__FILE__) + "/wrapper/page"
11
13
 
12
- # try to load cairo from the standard places, but don't worry if it fails,
13
- # we'll try to find it via rubygems
14
- begin
15
- require 'cairo'
16
- rescue LoadError
17
- begin
18
- require 'rubygems'
19
- gem 'cairo', '>=1.5'
20
- require 'cairo'
21
- rescue Gem::LoadError
22
- raise LoadError, "Could not find the ruby cairo bindings in the standard locations or via rubygems. Check to ensure they're installed correctly"
23
- rescue LoadError
24
- raise LoadError, "Could not load rubygems"
25
- end
26
- end
14
+ require 'rubygems'
15
+ gem 'cairo', '>=1.5'
16
+ require 'cairo'
27
17
 
28
18
  module PDF
29
19
  # Create PDF files by using the cairo and pango libraries.
@@ -289,33 +279,7 @@ module PDF
289
279
  def translate(x, y, &block)
290
280
  @context.save do
291
281
  @context.translate(x, y)
292
- yield
293
- end
294
- end
295
-
296
- # all code wrapped in the block passed to this function will have co-ordinates
297
- # and distances (width/height) multiplied by these values before being used
298
- #
299
- # Divide everything by 2
300
- #
301
- # pdf.scale(0.5, 0.5) do
302
- # ...
303
- # end
304
- #
305
- # Make the page 1.0 wide and 1.0 tall, so co-ordinates and distances
306
- # can be specified as percentages (0.5 == 50%, etc)
307
- #
308
- # pdf.scale(pdf.page_width.to_f, pdf.page_height.to_f) do
309
- # ...
310
- # end
311
- #
312
- def scale(w, h, &block)
313
- @context.save do
314
- @context.scale(w, h)
315
-
316
- # set the line width again so that it's set relative to the current
317
- # scale factor
318
- line_width @line_width
282
+ move_to(0,0)
319
283
  yield
320
284
  end
321
285
  end
@@ -343,16 +307,19 @@ module PDF
343
307
  def render
344
308
  # finalise the document, then convert the StringIO object it was rendered to
345
309
  # into a string
346
- @context.show_page
347
- @context.target.finish
310
+ finish
348
311
  return @output.string
349
312
  end
350
313
 
314
+ def render_to_file(filename) #nodoc
315
+ # TODO: remove this at some point
316
+ warn "WARNING: render_to_file() is deprecated, please use render_file()"
317
+ render_file filename
318
+ end
319
+
351
320
  # save the rendered PDF to a file
352
- def render_to_file(filename)
353
- # finalise the document
354
- @context.show_page
355
- @context.target.finish
321
+ def render_file(filename)
322
+ finish
356
323
 
357
324
  # write each line from the StringIO object it was rendered to into the
358
325
  # requested file
@@ -366,10 +333,110 @@ module PDF
366
333
  # Misc Functions
367
334
  #####################################################
368
335
 
369
- def pad(n)
336
+ # move down the canvas by n points
337
+ # returns the new y position
338
+ #
339
+ def move_down(n)
340
+ x, y = current_point
341
+ newy = y + n
342
+ move_to(x, newy)
343
+ newy
344
+ end
345
+
346
+ # move up the canvas by n points
347
+ # returns the new y position
348
+ #
349
+ def move_up(n)
350
+ x, y = current_point
351
+ newy = y - n
352
+ move_to(x, newy)
353
+ newy
354
+ end
355
+
356
+ # move left across the canvas by n points
357
+ # returns the new x position
358
+ #
359
+ def move_left(n)
360
+ x, y = current_point
361
+ newx = x - n
362
+ move_to(newx, y)
363
+ newx
364
+ end
365
+
366
+ # move right across the canvas by n points
367
+ # returns the new x position
368
+ #
369
+ def move_right(n)
370
370
  x, y = current_point
371
- move_to(x, y + n)
372
- y + n
371
+ newx = x + n
372
+ move_to(newx, y)
373
+ newx
374
+ end
375
+
376
+ # Moves down the document and then executes a block.
377
+ #
378
+ # pdf.text "some text"
379
+ # pdf.pad_top(100) do
380
+ # pdf.text "This is 100 points below the previous line of text"
381
+ # end
382
+ # pdf.text "This text appears right below the previous line of text"
383
+ #
384
+ def pad_top(n)
385
+ move_down n
386
+ yield
387
+ end
388
+
389
+ # Executes a block then moves down the document
390
+ #
391
+ # pdf.text "some text"
392
+ # pdf.pad_bottom(100) do
393
+ # pdf.text "This text appears right below the previous line of text"
394
+ # end
395
+ # pdf.text "This is 100 points below the previous line of text"
396
+ #
397
+ def pad_bottom(n)
398
+ yield
399
+ move_down n
400
+ end
401
+
402
+ # Moves down the document by y, executes a block, then moves down the
403
+ # document by y again.
404
+ #
405
+ # pdf.text "some text"
406
+ # pdf.pad(100) do
407
+ # pdf.text "This is 100 points below the previous line of text"
408
+ # end
409
+ # pdf.text "This is 100 points below the previous line of text"
410
+ #
411
+ def pad(n)
412
+ if block_given?
413
+ move_down n
414
+ yield
415
+ move_down n
416
+ else
417
+ move_down n
418
+ end
419
+ end
420
+
421
+ # Moves right across the document by n, executes a block, then moves back
422
+ # left by the same amount
423
+ #
424
+ # pdf.text "some text"
425
+ # pdf.indent(50) do
426
+ # pdf.text "This starts 50 points right the previous line of text"
427
+ # end
428
+ # pdf.text "This starts in line with the first line of text"
429
+ #
430
+ # If no block is provided, operates just like move_right.
431
+ #
432
+ def indent(n)
433
+ if block_given?
434
+ move_right n
435
+ yield
436
+ move_left n
437
+ else
438
+ move_right n
439
+ end
373
440
  end
374
441
 
375
442
  # move the cursor to an arbitary position on the current page
@@ -382,21 +449,35 @@ module PDF
382
449
  @context.move_to(margin_left,margin_top)
383
450
  end
384
451
 
452
+ # returns true if the PDF has already been rendered, false if it hasn't.
453
+ # Due to limitations of the underlying libraries, content cannot be
454
+ # added to a PDF once it has been rendered.
455
+ #
456
+ def finished?
457
+ @output.seek(@output.size - 6)
458
+ bytes = @output.read(6)
459
+ bytes == "%%EOF\n" ? true : false
460
+ end
461
+
385
462
  # add the same elements to multiple pages. Useful for adding items like headers, footers and
386
463
  # watermarks.
387
464
  #
465
+ # There is a single block parameter that is a proxy to the current PDF::Wrapper object that
466
+ # disallows start_new_page calls. Every other method from PDF::Wrapper is considered valid.
467
+ #
388
468
  # arguments:
389
469
  # <tt>spec</tt>:: Which pages to add the items to. :all, :odd, :even, a range, an Array of numbers or an number
390
470
  #
391
471
  # To add text to every page that mentions the page number
392
- # pdf.repeating_element(:all) do
393
- # pdf.text("Page #{pdf.page}!", :left => pdf.margin_left, :top => pdf.margin_top, :font_size => 18)
472
+ # pdf.repeating_element(:all) do |page|
473
+ # page.text("Page #{page.page}!", :left => page.margin_left, :top => page.margin_top, :font_size => 18)
394
474
  # end
395
475
  #
396
476
  # To add a circle to the middle of every page
397
- # pdf.repeating_element(:all) do
398
- # pdf.circle(pdf.absolute_x_middle, pdf.absolute_y_middle, 100)
477
+ # pdf.repeating_element(:all) do |page|
478
+ # page.circle(page.absolute_x_middle, page.absolute_y_middle, 100)
399
479
  # end
480
+ #
400
481
  def repeating_element(spec = :all, &block)
401
482
  call_repeating_element(spec, block)
402
483
 
@@ -409,6 +490,7 @@ module PDF
409
490
  # options:
410
491
  # <tt>:pageno</tt>:: If specified, the current page number will be set to that. By default, the page number will just increment.
411
492
  # <tt>:template</tt>:: The path to an image file. If specified, the new page will use the specified image as a template. The page will be sized to match the template size
493
+ #
412
494
  def start_new_page(opts = {})
413
495
  opts.assert_valid_keys(:pageno, :template)
414
496
 
@@ -440,10 +522,17 @@ module PDF
440
522
 
441
523
  private
442
524
 
525
+ def finish
526
+ # finalise the document
527
+ @context.show_page
528
+ @context.target.finish
529
+ rescue Cairo::SurfaceFinishedError
530
+ # do nothing, we're happy that the surfaced has been finished
531
+ end
532
+
443
533
  # runs the code in block, passing it a hash of options that might be
444
534
  # required
445
535
  def call_repeating_element(spec, block)
446
- # TODO: disallow start_new_page when adding a repeating element
447
536
  if spec == :all ||
448
537
  (spec == :even && (page % 2) == 0) ||
449
538
  (spec == :odd && (page % 2) == 1) ||
@@ -453,7 +542,7 @@ module PDF
453
542
 
454
543
  @context.save do
455
544
  # add it to the current page
456
- block.call
545
+ block.call PDF::Wrapper::Page.new(self)
457
546
  end
458
547
  end
459
548
  end
@@ -491,10 +580,6 @@ module PDF
491
580
  Cairo::Color.parse(c).to_rgb.to_a
492
581
  end
493
582
 
494
- def user_to_device_dist(x,y)
495
- @context.user_to_device_distance(x, y)
496
- end
497
-
498
583
  def user_x_to_device_x(x)
499
584
  @context.user_to_device(x, 0).first.abs
500
585
  end
@@ -503,10 +588,6 @@ module PDF
503
588
  @context.user_to_device(0, y).last.abs
504
589
  end
505
590
 
506
- def device_to_user_dist(x, y)
507
- @context.device_to_user_distance(x, y)
508
- end
509
-
510
591
  def device_x_to_user_x(x)
511
592
  @context.device_to_user(x, 0).first.abs
512
593
  end
@@ -13,6 +13,8 @@ module PDF
13
13
  # <tt>:proportional</tt>:: Boolean. Maintain image proportions when scaling. Defaults to false.
14
14
  # <tt>:padding</tt>:: Add some padding between the image and the specified box.
15
15
  # <tt>:center</tt>:: If the image is scaled, it will be centered horizontally and vertically
16
+ # <tt>:rotate</tt>:: The desired rotation. One of :counterclockwise, :upsidedown, :clockwise.
17
+ # Doesn't work with PNG, PDF or SVG files.
16
18
  #
17
19
  # left and top default to the current cursor location
18
20
  # width and height default to the size of the imported image
@@ -20,7 +22,7 @@ module PDF
20
22
  def image(filename, opts = {})
21
23
  # TODO: add some options for justification and padding
22
24
  raise ArgumentError, "file #{filename} not found" unless File.file?(filename)
23
- opts.assert_valid_keys(default_positioning_options.keys + [:padding, :proportional, :center])
25
+ opts.assert_valid_keys(default_positioning_options.keys + [:padding, :proportional, :center, :rotate])
24
26
 
25
27
  if opts[:padding]
26
28
  opts[:left] += opts[:padding].to_i if opts[:left]
@@ -132,6 +134,9 @@ module PDF
132
134
  load_libpixbuf
133
135
  x, y = current_point
134
136
  pixbuf = Gdk::Pixbuf.new(filename)
137
+ if opts[:rotate]
138
+ pixbuf = pixbuf.rotate( rotation_constant( opts[:rotate] ) )
139
+ end
135
140
  width, height = calc_image_dimensions(opts[:width], opts[:height], pixbuf.width, pixbuf.height, opts[:proportional])
136
141
  x, y = calc_image_coords(opts[:left] || x, opts[:top] || y, opts[:width] || pixbuf.width, opts[:height] || pixbuf.height, width, height, opts[:center])
137
142
  @context.save do
@@ -145,6 +150,10 @@ module PDF
145
150
  raise ArgumentError, "Unrecognised image format (#{filename})"
146
151
  end
147
152
 
153
+ def rotation_constant( rotation )
154
+ Gdk::Pixbuf.const_get "ROTATE_#{rotation.to_s.upcase}"
155
+ end
156
+
148
157
  def draw_png(filename, opts = {})
149
158
  # based on a similar function in rabbit. Thanks Kou.
150
159
  x, y = current_point
@@ -0,0 +1,20 @@
1
+ module PDF
2
+ class Wrapper
3
+
4
+ # a proxy to a PDF::Wrapper object that disallows new pages
5
+ class Page
6
+
7
+ def initialize(pdf)
8
+ @pdf = pdf
9
+ end
10
+
11
+ def method_missing(method, *args, &block)
12
+ if method.to_sym == :start_new_page
13
+ raise InvalidOperationError, 'start_new_page is not allowed in the current context'
14
+ else
15
+ @pdf.__send__(method, *args, &block)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -16,6 +16,11 @@ module PDF
16
16
  # <tt>:top</tt>:: The y co-ordinate of the top of the text. Defaults to the current cursor location
17
17
  # <tt>:width</tt>:: The width of the table. Defaults to the distance from the left of the table to the right margin
18
18
  def table(data, opts = {})
19
+ # TODO: add support for a table footer
20
+ # - repeating each page, or just at the bottom?
21
+ # - if it repeats, should it be different on each page? ie. a sum of that pages rows, etc.
22
+ # TODO: add support for manually specifying 1 or more column widths
23
+ # TODO: maybe support for multiple data sets with group headers/footers. useful for subtotals, etc
19
24
 
20
25
  x, y = current_point
21
26
  options = {:left => x, :top => y }
@@ -163,7 +168,7 @@ module PDF
163
168
  # For example:
164
169
  #
165
170
  # table = Table.new do |t|
166
- # t.headings = ["Words", "Numbers"]
171
+ # t.headers = ["Words", "Numbers"]
167
172
  # t.data = [['one', 1],
168
173
  # ['two', 2],
169
174
  # ['three',3]]
@@ -186,12 +191,6 @@ module PDF
186
191
  attr_reader :cells, :headers
187
192
  attr_accessor :width, :show_headers
188
193
 
189
- # Create a new table object.
190
- #
191
- # data should be a 2d array
192
- #
193
- # [[ "one", "two"],
194
- # [ "one", "two"]]
195
194
  #
196
195
  # headers should be a single array
197
196
  #
@@ -209,7 +208,14 @@ module PDF
209
208
  self
210
209
  end
211
210
 
211
+ # Specify the tables data.
212
+ #
213
+ # The single argument should be a 2d array like:
214
+ #
215
+ # [[ "one", "two"],
216
+ # [ "one", "two"]]
212
217
  def data=(d)
218
+ # TODO: raise an exception of the data rows aren't all the same size
213
219
  # TODO: ensure d is array-like
214
220
  @cells = d.collect do |row|
215
221
  row.collect do |str|
@@ -218,14 +224,21 @@ module PDF
218
224
  end
219
225
  end
220
226
 
227
+ # Specify the tables optional column headers.
228
+ #
229
+ # The single argument should be an array like:
230
+ #
231
+ # [ "col one", "col two"]
221
232
  def headers=(h)
233
+ # TODO: raise an exception of the size of the array does not match the size
234
+ # of the data row arrays
222
235
  # TODO: ensure h is array-like
223
236
  @headers = h.collect do |str|
224
237
  Wrapper::Cell.new(str)
225
238
  end
226
239
  end
227
240
 
228
- # access a particular cell
241
+ # access a particular cell at location x, y
229
242
  def cell(col_idx, row_idx)
230
243
  @cells[row_idx, col_idx]
231
244
  end
@@ -28,9 +28,10 @@ module PDF
28
28
  def cell(str, x, y, w, h, opts={})
29
29
  # TODO: add a wrap option so wrapping can be disabled
30
30
  # TODO: add an option for vertical alignment
31
+ # TODO: allow cell contents to be defined as a block, like link_to in EDGE rails
31
32
 
32
33
  options = default_text_options
33
- options.merge!({:border => "tblr", :border_width => @default_line_width, :border_color => :black, :fill_color => nil, :padding => device_to_user_dist(3,0).first, :radius => nil})
34
+ options.merge!({:border => "tblr", :border_width => @default_line_width, :border_color => :black, :fill_color => nil, :padding => 3, :radius => nil})
34
35
  options.merge!(opts)
35
36
  options.assert_valid_keys(default_text_options.keys + [:width, :border, :border_width, :border_color, :fill_color, :padding, :radius])
36
37
 
@@ -142,7 +143,7 @@ module PDF
142
143
  # draw the context on our cairo layout
143
144
  y = render_layout(layout, options[:left], options[:top], points_to_bottom_margin(options[:top]), :auto_new_page => true)
144
145
 
145
- move_to(options[:left], y + device_y_to_user_y(5))
146
+ move_to(options[:left], y)
146
147
  end
147
148
 
148
149
  # Returns the amount of vertical space needed to display the supplied text at the requested width
@@ -233,7 +234,7 @@ module PDF
233
234
  layout.width = w * Pango::SCALE
234
235
  end
235
236
  # spacing is specified in user points
236
- layout.spacing = device_y_to_user_y(options[:spacing] * Pango::SCALE)
237
+ layout.spacing = options[:spacing] * Pango::SCALE
237
238
 
238
239
  # set the alignment of the text in the layout
239
240
  if options[:alignment].eql?(:left)
@@ -263,7 +264,7 @@ module PDF
263
264
  # setup the font that will be used to render the text
264
265
  fdesc = Pango::FontDescription.new(options[:font])
265
266
  # font size should be specified in device points for simplicity's sake.
266
- fdesc.size = device_y_to_user_y(options[:font_size] * Pango::SCALE)
267
+ fdesc.size = options[:font_size] * Pango::SCALE
267
268
  layout.font_description = fdesc
268
269
  @context.update_pango_layout(layout)
269
270
 
@@ -296,6 +297,7 @@ module PDF
296
297
 
297
298
  offset = 0
298
299
  baseline = 0
300
+ spacing = layout.spacing / Pango::SCALE
299
301
 
300
302
  iter = layout.iter
301
303
  loop do
@@ -320,7 +322,7 @@ module PDF
320
322
  end
321
323
 
322
324
  # move to the start of this line
323
- @context.move_to(x + linex, y + baseline - offset)
325
+ @context.move_to(x + linex, y + baseline - offset + spacing)
324
326
 
325
327
  # draw the line on the canvas
326
328
  @context.show_pango_layout_line(line)
@@ -328,10 +330,11 @@ module PDF
328
330
  break unless iter.next_line!
329
331
  end
330
332
 
333
+ width, height = layout.size
334
+
331
335
  # return the y co-ord we finished on
332
- return device_y_to_user_y(y + baseline - offset)
336
+ return y + (height / Pango::SCALE) - offset + spacing
333
337
  end
334
338
 
335
-
336
339
  end
337
340
  end
data/specs/image_spec.rb CHANGED
@@ -29,5 +29,13 @@ context "The PDF::Wrapper class" do
29
29
  pdf.calc_image_dimensions(300, 250, 200, 200, true).should eql([250.0,250.0])
30
30
  end
31
31
 
32
+ specify "should be able to draw rotated images correctly" do
33
+ pdf = PDF::Wrapper.new
34
+ pdf.image(File.dirname(__FILE__) + "/data/shipsail.jpg", :rotate => :clockwise)
35
+ pdf.image(File.dirname(__FILE__) + "/data/shipsail.jpg", :rotate => :counterclockwise)
36
+ pdf.image(File.dirname(__FILE__) + "/data/shipsail.jpg", :rotate => :upsidedown)
37
+ pdf.image(File.dirname(__FILE__) + "/data/shipsail.jpg", :rotate => :none)
38
+ end
39
+
32
40
  specify "should be able to draw an image with padding correctly"
33
41
  end
data/specs/spec_helper.rb CHANGED
@@ -6,7 +6,7 @@ require 'pdf/wrapper'
6
6
  require 'tempfile'
7
7
  require 'rubygems'
8
8
 
9
- gem "pdf-reader", ">=0.6.1"
9
+ gem "pdf-reader", ">=0.7.3"
10
10
 
11
11
  require 'pdf/reader'
12
12
 
@@ -27,7 +27,6 @@ class PDF::Wrapper
27
27
  public :load_libpoppler
28
28
  public :user_x_to_device_x
29
29
  public :user_y_to_device_y
30
- public :user_to_device_dist
31
30
  public :device_x_to_user_x
32
31
  public :device_y_to_user_y
33
32
  public :validate_color
@@ -35,15 +34,15 @@ end
35
34
 
36
35
  # a helper class for counting the number of pages in a PDF
37
36
  class PageReceiver
38
- attr_accessor :page_count
37
+ attr_accessor :pages
39
38
 
40
39
  def initialize
41
40
  @page_count = 0
42
41
  end
43
42
 
44
43
  # Called when page parsing ends
45
- def end_page
46
- @page_count += 1
44
+ def page_count(arg)
45
+ @pages = arg
47
46
  end
48
47
  end
49
48
 
@@ -92,16 +92,6 @@ context "The PDF::Wrapper class" do
92
92
  y.to_i.should eql(100)
93
93
  end
94
94
 
95
- specify "should be able to move the cursor to any arbitary point on the canvas when scaled" do
96
- pdf = PDF::Wrapper.new
97
- pdf.scale(pdf.page_width, pdf.page_height) do
98
- pdf.move_to(0.5, 0.5)
99
- x,y = pdf.current_point
100
- sprintf("%0.1f",x).should eql("0.5")
101
- sprintf("%0.1f",y).should eql("0.5")
102
- end
103
- end
104
-
105
95
  specify "should be able to shift the y position of the cursor using pad" do
106
96
  pdf = PDF::Wrapper.new
107
97
  pdf.move_to(100,100)
@@ -123,7 +113,7 @@ context "The PDF::Wrapper class" do
123
113
  # verify the output
124
114
  receiver = PageReceiver.new
125
115
  reader = PDF::Reader.string(pdf.render, receiver)
126
- receiver.page_count.should eql(2)
116
+ receiver.pages.should eql(2)
127
117
  end
128
118
 
129
119
 
@@ -150,7 +140,7 @@ context "The PDF::Wrapper class" do
150
140
  # write the PDF to a temp file
151
141
  tmp = Tempfile.open("siftr")
152
142
  tmp.close
153
- pdf.render_to_file(tmp.path)
143
+ pdf.render_file(tmp.path)
154
144
 
155
145
  # ensure an actual PDF was written out
156
146
  File.open(tmp.path, "r") do |f|
@@ -175,7 +165,7 @@ context "The PDF::Wrapper class" do
175
165
  test_str = "repeating"
176
166
 
177
167
  pdf = PDF::Wrapper.new
178
- pdf.repeating_element(:all) { pdf.text test_str }
168
+ pdf.repeating_element(:all) { |page| page.text test_str }
179
169
 
180
170
  pdf.start_new_page
181
171
  pdf.start_new_page
@@ -195,7 +185,7 @@ context "The PDF::Wrapper class" do
195
185
  test_str = "repeating"
196
186
 
197
187
  pdf = PDF::Wrapper.new
198
- pdf.repeating_element(:odd) { pdf.text test_str }
188
+ pdf.repeating_element(:odd) { |page| page.text test_str }
199
189
 
200
190
  pdf.start_new_page
201
191
  pdf.start_new_page
@@ -215,7 +205,7 @@ context "The PDF::Wrapper class" do
215
205
  test_str = "repeating"
216
206
 
217
207
  pdf = PDF::Wrapper.new
218
- pdf.repeating_element(:even) { pdf.text test_str }
208
+ pdf.repeating_element(:even) { |page| page.text test_str }
219
209
 
220
210
  pdf.start_new_page
221
211
  pdf.start_new_page
@@ -235,7 +225,7 @@ context "The PDF::Wrapper class" do
235
225
  test_str = "repeating"
236
226
 
237
227
  pdf = PDF::Wrapper.new
238
- pdf.repeating_element((2..3)) { pdf.text test_str }
228
+ pdf.repeating_element((2..3)) { |page| page.text test_str }
239
229
 
240
230
  pdf.start_new_page
241
231
  pdf.start_new_page
@@ -255,7 +245,7 @@ context "The PDF::Wrapper class" do
255
245
  test_str = "repeating"
256
246
 
257
247
  pdf = PDF::Wrapper.new
258
- pdf.repeating_element(2) { pdf.text test_str }
248
+ pdf.repeating_element(2) { |page| page.text test_str }
259
249
 
260
250
  pdf.start_new_page
261
251
  pdf.start_new_page
@@ -275,7 +265,7 @@ context "The PDF::Wrapper class" do
275
265
  test_str = "repeating"
276
266
 
277
267
  pdf = PDF::Wrapper.new
278
- pdf.repeating_element([1,3,4]) { pdf.text test_str }
268
+ pdf.repeating_element([1,3,4]) { |page| page.text test_str }
279
269
 
280
270
  pdf.start_new_page
281
271
  pdf.start_new_page
@@ -292,6 +282,19 @@ context "The PDF::Wrapper class" do
292
282
  end
293
283
 
294
284
  specify "should not change the state of the cairo canvas or PDF::Writer defaults (fonts, colors, etc) when adding repeating elements"
285
+
286
+ specify "should not allow a new page to be started while adding repeating elements" do
287
+ test_str = "repeating"
288
+
289
+ pdf = PDF::Wrapper.new
290
+ lambda do
291
+ pdf.repeating_element([1,3,4]) do |page|
292
+ page.text test_str
293
+ page.start_new_page
294
+ end
295
+ end.should raise_error(InvalidOperationError)
296
+
297
+ end
295
298
 
296
299
  specify "should leave the cursor on the bottom left corner of an object when using functions with optional positioning [func(data, opts)]" do
297
300
  pdf = PDF::Wrapper.new
@@ -406,29 +409,6 @@ context "The PDF::Wrapper class" do
406
409
  receiver.pages[1].should eql([0.0, 0.0, 734.0, 772.0])
407
410
  end
408
411
 
409
- specify "should return correct page dimensions when scaled" do
410
- pdf = PDF::Wrapper.new(:paper => :A4)
411
-
412
- pdf.scale(595.28, 841.89) do
413
- pdf.page_width.should eql(1.0)
414
- pdf.page_height.should eql(1.0)
415
- pdf.absolute_x_middle.should eql(0.5)
416
- pdf.absolute_y_middle.should eql(0.5)
417
- end
418
- end
419
-
420
- specify "should return correct body dimensions when scaled" do
421
- pdf = PDF::Wrapper.new(:paper => :A4, :margin_top => 10, :margin_right => 10, :margin_bottom => 10, :margin_left => 10)
422
-
423
- # scale so the dimensions of the body are 1,1
424
- pdf.scale(595.28 - 20, 841.89 - 20) do
425
- pdf.body_width.should eql(1.0)
426
- pdf.body_height.should eql(1.0)
427
- pdf.body_x_middle.should eql(0.5)
428
- pdf.body_y_middle.should eql(0.5)
429
- end
430
- end
431
-
432
412
  specify "should correctly convert a user x co-ordinate to device" do
433
413
  pdf = PDF::Wrapper.new(:paper => :A4, :margin_left => 40)
434
414
 
@@ -439,11 +419,6 @@ context "The PDF::Wrapper class" do
439
419
  # a user x co-ord of 10 is now equal to a device co-ord of 50
440
420
  pdf.user_x_to_device_x(10).should eql(50.0)
441
421
  end
442
-
443
- # scale so the dimensions of the page are 1,1
444
- pdf.scale(pdf.page_width, pdf.page_height) do
445
- pdf.user_x_to_device_x(0.5).should eql(595.28/2)
446
- end
447
422
  end
448
423
 
449
424
  specify "should correctly convert a user y co-ordinate to device" do
@@ -456,11 +431,6 @@ context "The PDF::Wrapper class" do
456
431
  # a user y co-ord of 10 is now equal to a device co-ord of 50
457
432
  pdf.user_y_to_device_y(10).should eql(50.0)
458
433
  end
459
-
460
- # scale so the dimensions of the page are 1,1
461
- pdf.scale(pdf.page_width, pdf.page_height) do
462
- pdf.user_y_to_device_y(0.5).should eql(841.89/2)
463
- end
464
434
  end
465
435
 
466
436
  specify "should correctly convert a device x co-ordinate to user" do
@@ -472,11 +442,6 @@ context "The PDF::Wrapper class" do
472
442
  pdf.translate(pdf.margin_left, pdf.margin_top) do
473
443
  pdf.device_x_to_user_x(50).should eql(10.0)
474
444
  end
475
-
476
- # scale so the dimensions of the page are 1,1
477
- pdf.scale(pdf.page_width, pdf.page_height) do
478
- pdf.device_x_to_user_x(595.28/2).should eql(0.5)
479
- end
480
445
  end
481
446
 
482
447
  specify "should correctly convert a device y co-ordinate to user" do
@@ -488,29 +453,20 @@ context "The PDF::Wrapper class" do
488
453
  pdf.translate(pdf.margin_left, pdf.margin_top) do
489
454
  pdf.device_y_to_user_y(50).should eql(10.0)
490
455
  end
491
-
492
- # scale so the dimensions of the page are 1,1
493
- pdf.scale(pdf.page_width, pdf.page_height) do
494
- pdf.device_y_to_user_y(841.89/2).should eql(0.5)
495
- end
496
456
  end
497
457
 
498
- specify "should correctly convert a user distance to distance" do
499
- pdf = PDF::Wrapper.new(:paper => :A4, :margin_top => 40)
500
-
501
- pdf.user_to_device_dist(10,0).should eql([10.0,0.0])
502
- pdf.user_to_device_dist(0,10).should eql([0.0,10.0])
503
-
504
- # translate so that 0,0 is at the page body corner
505
- pdf.translate(pdf.margin_left, pdf.margin_top) do
506
- pdf.user_to_device_dist(10,0).should eql([10.0,0.0])
507
- pdf.user_to_device_dist(0,10).should eql([0.0,10.0])
508
- end
458
+ specify "should allow Wrapper#render to be called multiple times" do
459
+ pdf = PDF::Wrapper.new
460
+ pdf.text "Hi!"
461
+ pdf.render.should be_a_kind_of(String)
462
+ pdf.render.should be_a_kind_of(String)
463
+ end
509
464
 
510
- # scale so the dimensions of the page are 1,1
511
- pdf.scale(pdf.page_width, pdf.page_height) do
512
- pdf.user_to_device_dist(0.5,0).should eql([595.28/2,0.0])
513
- pdf.user_to_device_dist(0,0.5).should eql([0.0,841.89/2])
514
- end
465
+ specify "should be aware of when the underlying PDFSurface has been finished" do
466
+ pdf = PDF::Wrapper.new
467
+ pdf.text "Hi!"
468
+ pdf.finished?.should be_false
469
+ pdf.render
470
+ pdf.finished?.should be_true
515
471
  end
516
472
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pdf-wrapper
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - James Healy
@@ -9,10 +9,19 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-05-28 00:00:00 +10:00
12
+ date: 2008-07-28 00:00:00 +10:00
13
13
  default_executable:
14
- dependencies: []
15
-
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: cairo
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.5.0
24
+ version:
16
25
  description: A unicode aware PDF writing library that uses the ruby bindings to various c libraries ( like cairo, pango, poppler and rsvg ) to do the heavy lifting.
17
26
  email: jimmy@deefa.com
18
27
  executables: []
@@ -20,7 +29,7 @@ executables: []
20
29
  extensions: []
21
30
 
22
31
  extra_rdoc_files:
23
- - README
32
+ - README.rdoc
24
33
  - CHANGELOG
25
34
  - TODO
26
35
  files:
@@ -28,21 +37,22 @@ files:
28
37
  - examples/image.rb
29
38
  - examples/markup.rb
30
39
  - examples/repeating.rb
31
- - examples/scaled.rb
32
- - examples/scaled_cells.rb
33
- - examples/scaled_image.rb
40
+ - examples/utf8-long.rb
34
41
  - examples/shapes.rb
35
42
  - examples/table.rb
43
+ - examples/scaled_image.rb
36
44
  - examples/translate.rb
37
- - examples/utf8-long.rb
38
45
  - examples/utf8.rb
46
+ - examples/text.rb
39
47
  - lib/pdf
40
48
  - lib/pdf/core.rb
41
49
  - lib/pdf/wrapper.rb
50
+ - lib/pdf/errors.rb
42
51
  - lib/pdf/wrapper
43
52
  - lib/pdf/wrapper/graphics.rb
44
53
  - lib/pdf/wrapper/images.rb
45
54
  - lib/pdf/wrapper/loading.rb
55
+ - lib/pdf/wrapper/page.rb
46
56
  - lib/pdf/wrapper/table.rb
47
57
  - lib/pdf/wrapper/text.rb
48
58
  - specs/data
@@ -57,15 +67,15 @@ files:
57
67
  - specs/data/utf8.txt
58
68
  - specs/data/windmill.jpg
59
69
  - specs/data/zits.gif
60
- - specs/graphics_spec.rb
70
+ - specs/spec_helper.rb
61
71
  - specs/image_spec.rb
62
72
  - specs/load_spec.rb
63
- - specs/spec_helper.rb
64
73
  - specs/tables_spec.rb
65
74
  - specs/text_spec.rb
66
75
  - specs/wrapper_spec.rb
76
+ - specs/graphics_spec.rb
67
77
  - Rakefile
68
- - README
78
+ - README.rdoc
69
79
  - CHANGELOG
70
80
  - TODO
71
81
  has_rdoc: true
@@ -75,7 +85,7 @@ rdoc_options:
75
85
  - --title
76
86
  - PDF::Wrapper Documentation
77
87
  - --main
78
- - README
88
+ - README.rdoc
79
89
  - -q
80
90
  require_paths:
81
91
  - lib
@@ -94,7 +104,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
94
104
  requirements: []
95
105
 
96
106
  rubyforge_project: pdf-wrapper
97
- rubygems_version: 1.1.1
107
+ rubygems_version: 1.2.0
98
108
  signing_key:
99
109
  specification_version: 2
100
110
  summary: A PDF generating library built on top of cairo
data/examples/scaled.rb DELETED
@@ -1,38 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # coding: utf-8
3
-
4
- $:.unshift(File.dirname(__FILE__) + "/../lib")
5
-
6
- require 'pdf/wrapper'
7
-
8
- pdf = PDF::Wrapper.new(:paper => :A4)
9
- pdf.line_width = 2
10
-
11
- # grid lines
12
- pdf.line(50, 0, 50, pdf.page_height)
13
- pdf.line(100, 0, 100, pdf.page_height)
14
- pdf.line(150, 0, 150, pdf.page_height)
15
- pdf.line(200, 0, 200, pdf.page_height)
16
- pdf.line(0, 50, pdf.page_width, 50)
17
- pdf.line(0, 100, pdf.page_width, 100)
18
- pdf.line(0, 150, pdf.page_width, 150)
19
- pdf.line(0, 200, pdf.page_width, 200)
20
-
21
- # non scaled
22
- pdf.rectangle(100,100,100,100, :fill_color => :green)
23
-
24
- # scaled
25
- pdf.scale(pdf.page_width.to_f, pdf.page_height.to_f) do
26
- # top left corner 10% of the page width from the left and top of the page.
27
- # width 10% of the page width
28
- # height 10% of the page height
29
- # - obviously will not be square on a A4 page
30
- pdf.rectangle(0.1,0.1,0.1,0.1, :fill_color => :red)
31
- pdf.text("boo!", :top => 0.5, :left => 0.2, :width => 0.6)
32
- end
33
-
34
- #pdf.text("boo2!", :top => 500, :left => 100)
35
-
36
-
37
- # show results
38
- pdf.render_to_file("scaled.pdf")
@@ -1,30 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # coding: utf-8
3
-
4
- $:.unshift(File.dirname(__FILE__) + "/../lib")
5
-
6
- require 'pdf/wrapper'
7
-
8
- pdf = PDF::Wrapper.new(:paper => :A4)
9
- pdf.font("Sans Serif")
10
- pdf.line_width(0.1)
11
- # naglowek
12
- pdf.translate(pdf.absolute_left_margin, pdf.absolute_top_margin) do
13
- pdf.scale(pdf.body_width, pdf.body_width) do
14
- pdf.cell("Exorigo", 0, 0, 0.2, 0.05, :alignment => :center, :font_size => 15)
15
- pdf.cell("Zamówienie nr PO/0000/01/01/2008", 0.2, 0, 0.8, 0.05, :fill_color => :gray, :alignment => :center)
16
- pdf.cell("imię i naziwsko osoby zamawiającej. NALEŻY PODAĆ NA FAKTURZE", 0, 0.05, 0.55, 0.025, :font_size => 6)
17
- pdf.cell("data zamówienia", 0.55, 0.05, 0.15, 0.025, :font_size => 6)
18
- pdf.cell("POWYŻSZY NUMER ZAMÓWIENIA NALEŻY PODAĆ NA FAKTURZE", 0.7, 0.05, 0.3, 0.05, :font_size => 6, :alignment => :center, :spacing => 4)
19
- pdf.cell("Jan Kowalski", 0, 0.075, 0.55, 0.025, :font => "Sans Serif bold", :font_size => 8)
20
- pdf.cell("22.01.2008", 0.55, 0.075, 0.15, 0.025, :font => "Sans Serif bold", :font_size => 8)
21
- end
22
- end
23
- # dane dostawcy
24
-
25
- # szczegoly zamowienia
26
-
27
- # autoryzacja
28
-
29
- # adres
30
- pdf.render_to_file("scaled-cells.pdf")