pdf-wrapper 0.0.7 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,14 @@
1
+ v0.1.0 (28th May 2008)
2
+ - added PDF::Wrapper#translate and PDF::Wrapper#scale
3
+ - fixed a bug that caused some text to be rendered off the page when wrapping onto 3rd and
4
+ subsequent pages
5
+ - Improved table support using a new class PDF::Wrapper::Table
6
+ - Added a :center option to Wrapper#image
7
+ - Added a :markup option to text related functions. Pango markup can be used to tweak
8
+ text style (bold, italics, super script, etc)
9
+ - Replaced PDF::Wrapper#rounded_rectangle with the :radius option to PDF::Wrapper#rectangle
10
+ - Allow cells to have a border with rounded corners
11
+
1
12
  v0.0.7 (30th April 2008)
2
13
  - Added support for creating new pages from a template file (pdf/png/jpg/gif/svg/probably more)
3
14
  - Convert the params to start_new_page to be an options hash
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.0.7"
9
+ PKG_VERSION = "0.1.0"
10
10
  PKG_NAME = "pdf-wrapper"
11
11
  PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
12
12
 
@@ -16,11 +16,11 @@ task :default => [ :spec ]
16
16
  # run all rspecs
17
17
  desc "Run all rspec files"
18
18
  Spec::Rake::SpecTask.new("spec") do |t|
19
- #t.spec_files = FileList['specs/**/*_spec.rb']
20
- t.spec_files = ['specs/load_spec.rb','specs/image_spec.rb','specs/shapes_spec.rb','specs/text_spec.rb','specs/wrapper_spec.rb']
19
+ # spec files listed explicitly so that load_spec is the first one run
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
21
  t.rcov = true
22
22
  t.rcov_dir = (ENV['CC_BUILD_ARTIFACTS'] || 'doc') + "/rcov"
23
- t.rcov_opts = ["--exclude","spec.*\.rb","--exclude",".*cairo.*","--exclude",".*rcov.*","--exclude",".*rspec.*","--exclude",".*pdf-reader.*"]
23
+ t.rcov_opts = ["--exclude","spec.*\.rb","--exclude",".*cairo.*","--exclude",".*rcov.*","--exclude",".*rspec.*","--exclude",".*pdf-reader.*", "--exclude",".*gems.*"]
24
24
  end
25
25
 
26
26
  # generate specdocs
data/examples/cell.rb CHANGED
@@ -6,5 +6,5 @@ $:.unshift(File.dirname(__FILE__) + "/../lib")
6
6
  require 'pdf/wrapper'
7
7
 
8
8
  pdf = PDF::Wrapper.new(:paper => :A4)
9
- pdf.cell("Given an index within a layout, determines the positions that of the strong and weak cursors if the insertion point is at that index. The position of each cursor is stored as a zero-width rectangle. The strong cursor location is the location where characters of the directionality equal to the base direction of the layout are inserted. The weak cursor location is the location where characters of the directionality opposite to the base direction of the layout are inserted.", 100, 100, 100, 200, {:border => "", :color => :white, :bgcolor => :black})
9
+ pdf.cell("Given an index within a layout, determines the positions that of the strong and weak cursors if the insertion point is at that index. The position of each cursor is stored as a zero-width rectangle. The strong cursor location is the location where characters of the directionality equal to the base direction of the layout are inserted. The weak cursor location is the location where characters of the directionality opposite to the base direction of the layout are inserted.", 100, 100, 100, 200, {:border => "", :color => :black, :radius => 10})
10
10
  pdf.render_to_file("wrapper-cell.pdf")
data/examples/image.rb CHANGED
@@ -6,8 +6,8 @@ $:.unshift(File.dirname(__FILE__) + "/../lib")
6
6
  require 'pdf/wrapper'
7
7
 
8
8
  pdf = PDF::Wrapper.new(:paper => :A4)
9
- pdf.default_font("Sans Serif")
10
- pdf.default_color(:black)
9
+ pdf.font("Sans Serif")
10
+ pdf.color(:black)
11
11
  pdf.text("PDF::Wrapper Supports Images", :alignment => :center)
12
12
  pdf.image(File.dirname(__FILE__) + "/../specs/data/zits.gif")
13
13
  pdf.image(File.dirname(__FILE__) + "/../specs/data/google.png", :left => 100, :top => 350)
@@ -16,6 +16,6 @@ pdf.start_new_page
16
16
  pdf.image(File.dirname(__FILE__) + "/../specs/data/orc.svg", :left => pdf.margin_left, :top => pdf.margin_top, :width => pdf.body_width, :height => pdf.body_height)
17
17
  pdf.start_new_page
18
18
  pdf.image(File.dirname(__FILE__) + "/../specs/data/utf8-long.pdf", :left => pdf.margin_left, :top => pdf.margin_top, :width => pdf.body_width/2, :height => pdf.body_height/2)
19
- pdf.default_color(:red)
19
+ pdf.color(:red)
20
20
 
21
21
  pdf.render_to_file("image.pdf")
@@ -0,0 +1,12 @@
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.color(:black)
11
+ pdf.text "<i>James Healy</i>", :font => "Monospace", :font_size => 16, :alignment => :center, :markup => :pango
12
+ pdf.render_to_file("markup.pdf")
@@ -20,7 +20,7 @@ pdf.repeating_element(:odd) do
20
20
  end
21
21
 
22
22
  pdf.repeating_element([1,2]) do
23
- pdf.rounded_rectangle(100, 100, 100, 100, 10)
23
+ pdf.rectangle(pdf.absolute_x_middle, pdf.absolute_y_middle, 100, 100, :radius => 5)
24
24
  end
25
25
 
26
26
  pdf.repeating_element(3) do
@@ -0,0 +1,38 @@
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")
@@ -0,0 +1,30 @@
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")
@@ -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.image(File.dirname(__FILE__) + "/../specs/data/zits.gif", :top => 100, :height => 200, :width => 200, :proportional => true, :center => true)
10
+ pdf.rectangle(pdf.margin_left, 100, 200, 200)
11
+ pdf.image(File.dirname(__FILE__) + "/../specs/data/windmill.jpg", :top => 400, :height => 200, :width => 200, :proportional => true, :center => true)
12
+ pdf.rectangle(pdf.margin_left, 400, 200, 200)
13
+
14
+ pdf.render_to_file("scaled_image.pdf")
data/examples/shapes.rb CHANGED
@@ -9,5 +9,5 @@ pdf = PDF::Wrapper.new(:paper => :A4)
9
9
  pdf.rectangle(30,30,100,100, :fill_color => :red)
10
10
  pdf.circle(100,300,30)
11
11
  pdf.line(100, 350, 400, 150)
12
- pdf.rounded_rectangle(300,300, 200, 200, 10, :fill_color => :green)
12
+ pdf.rectangle(300,300, 200, 200, :fill_color => :green, :radius => 10)
13
13
  pdf.render_to_file("shapes.pdf")
data/examples/table.rb CHANGED
@@ -6,13 +6,34 @@ $:.unshift(File.dirname(__FILE__) + "/../lib")
6
6
  require 'pdf/wrapper'
7
7
 
8
8
  pdf = PDF::Wrapper.new(:paper => :A4)
9
- pdf.text "Chunky Bacon!!"
10
- data = [%w{one two three four}]
9
+ pdf.text File.read(File.dirname(__FILE__) + "/../specs/data/utf8.txt").strip, :alignment => :centre
10
+ pdf.pad 5
11
+ headers = %w{one two three four}
11
12
 
12
- data << ["This is some longer text to ensure that the cell wraps",2,3,4]
13
+ data = []
14
+ data << ["This is some longer text to ensure that the cell wraps","oh noes! the cols can't get the width they desire",3,4]
15
+ data << ["This is some longer text to ensure that the cell wraps","oh noes! the cols can't get the width they desire",3,4]
16
+
17
+ data << [[], "j", "a", "m"]
13
18
 
14
19
  (1..100).each do
15
20
  data << %w{1 2 3 4}
16
21
  end
17
- pdf.table(data, :font_size => 10)
22
+
23
+ table = PDF::Wrapper::Table.new do |t|
24
+ t.data = data
25
+ t.headers = headers
26
+ t.table_options :font_size => 10
27
+ t.header_options :color => :white, :fill_color => :black
28
+ t.row_options 6, {:border => "t"}
29
+ t.row_options :even, {:fill_color => :gray}
30
+ t.col_options 0, {:border => "tb"}
31
+ t.col_options 1, {:alignment => :centre}
32
+ t.col_options 2, {:alignment => :centre}
33
+ t.col_options 3, {:alignment => :centre, :border => "tb"}
34
+ t.col_options :even, {:fill_color => :blue}
35
+ t.cell_options 3, 3, {:fill_color => :green}
36
+ end
37
+
38
+ pdf.table(table)
18
39
  pdf.render_to_file("table.pdf")
@@ -0,0 +1,21 @@
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
+
10
+ def captioned_image(filename, caption, x, y)
11
+ @pdf.translate(x, y) do
12
+ @pdf.image(filename, :top => 0, :left => 0, :height => 100, :width => 100, :proportional => true)
13
+ @pdf.text("Image Caption", :top => 110, :left => 0)
14
+ end
15
+ end
16
+
17
+ captioned_image(File.dirname(__FILE__) + "/../specs/data/orc.svg", "One", 100, 100)
18
+ captioned_image(File.dirname(__FILE__) + "/../specs/data/orc.svg", "Two", 250, 300)
19
+ captioned_image(File.dirname(__FILE__) + "/../specs/data/orc.svg", "Three", 400, 500)
20
+
21
+ @pdf.render_to_file("translate.pdf")
@@ -6,7 +6,6 @@ $:.unshift(File.dirname(__FILE__) + "/../lib")
6
6
  require 'pdf/wrapper'
7
7
 
8
8
  pdf = PDF::Wrapper.new(:paper => :A4)
9
- pdf.default_font("Sans Serif")
10
- pdf.default_color(:black)
9
+ pdf.font("Sans Serif")
11
10
  pdf.text File.read(File.dirname(__FILE__) + "/../specs/data/utf8-long.txt"), :font => "Monospace", :font_size => 8
12
11
  pdf.render_to_file("utf8-long.pdf")
data/examples/utf8.rb CHANGED
@@ -6,7 +6,6 @@ $:.unshift(File.dirname(__FILE__) + "/../lib")
6
6
  require 'pdf/wrapper'
7
7
 
8
8
  pdf = PDF::Wrapper.new(:paper => :A4)
9
- pdf.default_font("Sans Serif")
10
- pdf.default_color(:black)
9
+ pdf.font("Sans Serif")
11
10
  pdf.text File.read(File.dirname(__FILE__) + "/../specs/data/utf8.txt"), :font => "Monospace", :font_size => 8, :alignment => :center
12
11
  pdf.render_to_file("wrapper.pdf")
data/lib/pdf/core.rb CHANGED
@@ -7,4 +7,27 @@ class Hash
7
7
  unknown_keys = keys - [valid_keys].flatten
8
8
  raise(ArgumentError, "Unknown key(s): #{unknown_keys.join(", ")}") unless unknown_keys.empty?
9
9
  end
10
+
11
+ def only(*keys)
12
+ keys.flatten!
13
+ self.dup.reject { |k,v|
14
+ !keys.include? k.to_sym
15
+ }
16
+ end
17
+ end
18
+
19
+ unless [].respond_to?(:sum)
20
+
21
+ module Enumerable
22
+ # borrowed from active support. No need to pull that entire beast in as a dependency
23
+ def sum(identity = 0, &block)
24
+ return identity unless size > 0
25
+
26
+ if block_given?
27
+ map(&block).sum
28
+ else
29
+ inject { |sum, element| sum + element }
30
+ end
31
+ end
32
+ end
10
33
  end
data/lib/pdf/wrapper.rb CHANGED
@@ -3,6 +3,12 @@
3
3
  require 'stringio'
4
4
  require 'pdf/core'
5
5
 
6
+ require File.dirname(__FILE__) + "/wrapper/graphics"
7
+ require File.dirname(__FILE__) + "/wrapper/images"
8
+ require File.dirname(__FILE__) + "/wrapper/loading"
9
+ require File.dirname(__FILE__) + "/wrapper/table"
10
+ require File.dirname(__FILE__) + "/wrapper/text"
11
+
6
12
  # try to load cairo from the standard places, but don't worry if it fails,
7
13
  # we'll try to find it via rubygems
8
14
  begin
@@ -40,14 +46,13 @@ module PDF
40
46
  #
41
47
  # require 'pdf/wrapper'
42
48
  # pdf = PDF::Wrapper.new(:paper => :A4)
43
- # pdf.default_font("Monospace")
49
+ # pdf.font("Monospace")
44
50
  # pdf.text "Hello World", :font => "Sans Serif", :font_size => 18
45
51
  # pdf.text "Pretend this is a code sample"
46
52
  # puts pdf.render
47
53
  class Wrapper
48
54
 
49
- attr_reader :margin_left, :margin_right, :margin_top, :margin_bottom
50
- attr_reader :page_width, :page_height, :page
55
+ attr_reader :page
51
56
 
52
57
  # borrowed from PDF::Writer
53
58
  PAGE_SIZES = { # :value {...}:
@@ -87,10 +92,20 @@ module PDF
87
92
  # <tt>:margin_bottom</tt>:: The size of the default bottom margin (default 5% of page)
88
93
  # <tt>:margin_left</tt>:: The size of the default left margin (default 5% of page)
89
94
  # <tt>:margin_right</tt>:: The size of the default right margin (default 5% of page)
90
- # <tt>:template</tt>:: The path to an image file. If specified, the first page of the document will use the specified image as a template.
95
+ # <tt>:template</tt>:: The path to an image file. If specified, the first page of the document will use the specified image as a template.
91
96
  # The page will be sized to match the template size. The use templates on subsequent pages, see the options for
92
97
  # start_new_page.
93
98
  def initialize(opts={})
99
+ # TODO: Investigate ways of using the cairo transform/translate/scale functionality to
100
+ # reduce the amount of irritating co-ordinate maths the user of PDF::Wrapper (ie. me!)
101
+ # is required to do.
102
+ # - translate the pdf body width so that it's 1.0 wide and 1.0 high?
103
+ # TODO: find a way to add metadata (title, author, subject, etc) to the output file
104
+ # currently no way to specify this in cairo.
105
+ # tentatively scheduled for cairo 1.8 - see:
106
+ # - http://cairographics.org/roadmap/
107
+ # - http://lists.cairographics.org/archives/cairo/2007-September/011441.html
108
+ # - http://lists.freedesktop.org/archives/cairo/2006-April/006809.html
94
109
 
95
110
  # ensure we have recentish cairo bindings
96
111
  raise "Ruby Cairo bindings version #{Cairo::BINDINGS_VERSION.join(".")} is too low. At least 1.5 is required" if Cairo::BINDINGS_VERSION.to_s < "150"
@@ -129,14 +144,14 @@ module PDF
129
144
  @context = Cairo::Context.new(@surface)
130
145
 
131
146
  # set the background colour
132
- set_color(options[:background_color])
147
+ color(options[:background_color])
133
148
  @context.paint
134
149
 
135
150
  # set a default drawing colour and font style
136
- default_color(:black)
137
- default_line_width(2.0)
138
- default_font("Sans Serif")
139
- default_font_size(16)
151
+ color(:black)
152
+ line_width(0.5)
153
+ font("Sans Serif")
154
+ font_size(16)
140
155
 
141
156
  # maintain a count of pages and array of repeating elements to add to each page
142
157
  @page = 1
@@ -160,499 +175,165 @@ module PDF
160
175
  # Returns the x value of the left margin
161
176
  # The top left corner of the page is (0,0)
162
177
  def absolute_left_margin
163
- @margin_left
178
+ margin_left
164
179
  end
165
180
 
166
181
  # Returns the x value of the right margin
167
182
  # The top left corner of the page is (0,0)
168
183
  def absolute_right_margin
169
- @page_width - @margin_right
184
+ page_width - margin_right
170
185
  end
171
186
 
172
187
  # Returns the y value of the top margin
173
188
  # The top left corner of the page is (0,0)
174
189
  def absolute_top_margin
175
- @margin_top
190
+ margin_top
176
191
  end
177
192
 
178
193
  # Returns the y value of the bottom margin
179
194
  # The top left corner of the page is (0,0)
180
195
  def absolute_bottom_margin
181
- @page_height - @margin_bottom
196
+ page_height - margin_bottom
182
197
  end
183
198
 
184
199
  # Returns the x at the middle of the page
185
200
  def absolute_x_middle
186
- @page_width / 2
201
+ page_width / 2
187
202
  end
188
203
 
189
204
  # Returns the y at the middle of the page
190
205
  def absolute_y_middle
191
- @page_height / 2
206
+ page_height / 2
192
207
  end
193
208
 
194
209
  # Returns the width of the usable part of the page (between the side margins)
195
210
  def body_width
196
- @page_width - @margin_left - @margin_right
211
+ device_x_to_user_x(@page_width - @margin_left - @margin_right)
197
212
  end
198
213
 
199
214
  # Returns the height of the usable part of the page (between the top and bottom margins)
200
215
  def body_height
201
- @page_height - @margin_top - @margin_bottom
216
+ #@context.device_to_user(@page_width - @margin_left - @margin_right, @page_height - @margin_top - @margin_bottom).last
217
+ device_y_to_user_y(@page_height - @margin_top - @margin_bottom)
202
218
  end
203
219
 
204
220
  # Returns the x coordinate of the middle part of the usable space between the margins
205
- def margin_x_middle
206
- @margin_left + (body_width / 2)
221
+ def body_x_middle
222
+ margin_left + (body_width / 2)
207
223
  end
208
224
 
209
225
  # Returns the y coordinate of the middle part of the usable space between the margins
210
- def margin_y_middle
211
- @margin_top + (body_height / 2)
226
+ def body_y_middle
227
+ margin_top + (body_height / 2)
228
+ end
229
+
230
+ def page_height
231
+ device_y_to_user_y(@page_height)
232
+ end
233
+
234
+ def page_width
235
+ device_x_to_user_x(@page_width)
212
236
  end
213
237
 
214
238
  # return the current position of the cursor
215
239
  # returns 2 values - x,y
216
240
  def current_point
217
- return @context.current_point
241
+ @context.current_point
218
242
  end
219
243
 
220
- # return the number of points from starty to the bottom border
221
- def points_to_bottom_margin(starty)
222
- absolute_bottom_margin - starty
244
+ def margin_bottom
245
+ device_y_to_user_y(@margin_bottom).to_i
223
246
  end
224
247
 
225
- # return the number of points from startx to the right border
226
- def points_to_right_margin(startx)
227
- absolute_right_margin - startx
248
+ def margin_left
249
+ device_x_to_user_x(@margin_left).to_i
228
250
  end
229
251
 
230
- #####################################################
231
- # Functions relating to working with text
232
- #####################################################
233
-
234
- # change the default font size
235
- def default_font_size(size)
236
- #@context.set_font_size(size.to_i)
237
- @default_font_size = size.to_i unless size.nil?
252
+ def margin_right
253
+ device_x_to_user_x(@margin_right).to_i
238
254
  end
239
- alias default_font_size= default_font_size
240
- alias font_size default_font_size # PDF::Writer compatibility
241
-
242
- # change the default font to write with
243
- def default_font(fontname, style = nil, weight = nil)
244
- #@context.select_font_face(fontname, slant, bold)
245
- @default_font = fontname
246
- @default_font_style = style unless style.nil?
247
- @default_font_weight = weight unless weight.nil?
248
- end
249
- alias default_font= default_font
250
- alias select_font default_font # PDF::Writer compatibility
251
255
 
252
- # change the default colour used to draw on the canvas
253
- #
254
- # Parameters:
255
- # <tt>c</tt>:: either a colour symbol recognised by rcairo (:red, :blue, :black, etc) or
256
- # an array with 3-4 integer elements. The first 3 numbers are red, green and
257
- # blue (0-255). The optional 4th number is the alpha channel and should be
258
- # between 0 and 1. See the API docs at http://cairo.rubyforge.org/ for a list
259
- # of predefined colours
260
- def default_color(c)
261
- c = translate_color(c)
262
- validate_color(c)
263
- @default_color = c
256
+ def margin_top
257
+ device_y_to_user_y(@margin_top).to_i
264
258
  end
265
- alias default_color= default_color
266
- alias stroke_color default_color # PDF::Writer compatibility
267
259
 
268
- # change the default line width used to draw stroke on the canvas
269
- #
270
- # Parameters:
271
- # <tt>f</tt>:: float value of stroke width from 0.01 to 255
272
- def default_line_width(f)
273
- @default_line_width = f
260
+ # return the number of points from starty to the bottom border
261
+ def points_to_bottom_margin(starty)
262
+ absolute_bottom_margin - starty
274
263
  end
275
- alias default_line_width= default_line_width
276
264
 
277
- # add text to the page, bounded by a box with dimensions HxW, with it's top left corner
278
- # at x,y. Any text that doesn't fit it the box will be silently dropped.
279
- #
280
- # In addition to the standard text style options (see the documentation for text()), cell() supports
281
- # the following options:
282
- #
283
- # <tt>:border</tt>:: Which sides of the cell should have a border? A string with any combination the letters tblr (top, bottom, left, right). Nil for no border, defaults to all sides.
284
- # <tt>:border_width</tt>:: How wide should the border be?
285
- # <tt>:border_color</tt>:: What color should the border be?
286
- # <tt>:fill_color</tt>:: A background color for the cell. Defaults to none.
287
- # <tt>:padding</tt>:: The number of points to leave between the inside of the border and text. Defaults to 3.
288
- def cell(str, x, y, w, h, opts={})
289
- # TODO: add support for pango markup (see http://ruby-gnome2.sourceforge.jp/hiki.cgi?pango-markup)
290
- # TODO: add a wrap option so wrapping can be disabled
291
- # TODO: handle a single word that is too long for the width
292
- # TODO: add an option to draw a border with rounded corners
293
-
294
- options = default_text_options
295
- options.merge!({:border => "tblr", :border_width => @default_line_width, :border_color => :black, :fill_color => nil, :padding => 3})
296
- options.merge!(opts)
297
- options.assert_valid_keys(default_text_options.keys + [:width, :border, :border_width, :border_color, :fill_color, :padding])
298
-
299
- # apply padding
300
- textw = w - (options[:padding] * 2)
301
- texth = h - (options[:padding] * 2)
302
- textx = x + options[:padding]
303
- texty = y + options[:padding]
304
-
305
- options[:border] = "" unless options[:border]
306
- options[:border].downcase!
307
-
308
- # save the cursor position so we can restore it at the end
309
- origx, origy = current_point
310
-
311
- # TODO: raise an exception if the box coords or dimensions will place it off the canvas
312
- rectangle(x,y,w,h, :color => options[:fill_color], :fill_color => options[:fill_color]) if options[:fill_color]
313
- layout = build_pango_layout(str.to_s, textw, options)
314
-
315
- set_color(options[:color])
316
-
317
- # draw the context on our cairo layout
318
- render_layout(layout, textx, texty, texth, :auto_new_page => false)
319
-
320
- # draw a border around the cell
321
- line(x,y,x+w,y, :color => options[:border_color], :line_width => options[:border_width]) if options[:border].include?("t")
322
- line(x,y+h,x+w,y+h, :color => options[:border_color], :line_width => options[:border_width]) if options[:border].include?("b")
323
- line(x,y,x,y+h, :color => options[:border_color], :line_width => options[:border_width]) if options[:border].include?("l")
324
- line(x+w,y,x+w,y+h, :color => options[:border_color], :line_width => options[:border_width]) if options[:border].include?("r")
325
-
326
- # restore the cursor position
327
- move_to(origx, origy)
265
+ # return the number of points from startx to the right border
266
+ def points_to_right_margin(startx)
267
+ absolute_right_margin - startx
328
268
  end
329
269
 
330
- # draws a basic table onto the page
270
+ # Set a new location to be the origin (0,0). This is useful for repetitive tasks
271
+ # where objects need to be added to the canvas at regular offsets, and can save
272
+ # a significant amount of irritating co-ordinate maths.
331
273
  #
332
- # <tt>data</tt>:: a 2d array with the data for the columns. The first row will be treated as the headings
274
+ # As an example, consider the following code fragment. If you have a series of images
275
+ # to arrange on a page with identical sizes, translate can help keep the code clean
276
+ # and readable by reducing (or removing completely) the need to perform a series of
277
+ # basic sums to calculate the correct offsets, etc.
333
278
  #
334
- # In addition to the standard text style options (see the documentation for text), table supports
335
- # the following options:
279
+ # def captioned_image(filename, caption, x, y)
280
+ # @pdf.translate(x, y) do
281
+ # @pdf.image(filename, :top => 0, :left => 0, :height => 100, :width => 100, :proportional => true)
282
+ # @pdf.text(caption, :top => 110, :left => 0, :width => 100)
283
+ # end
284
+ # end
336
285
  #
337
- # <tt>:left</tt>:: The x co-ordinate of the left-hand side of the table. Defaults to the left margin
338
- # <tt>:top</tt>:: The y co-ordinate of the top of the text. Defaults to the top margin
339
- # <tt>:width</tt>:: The width of the table. Defaults to the width of the page body
340
- def table(data, opts = {})
341
- # TODO: instead of accepting the data, use a XHTML table string?
342
- # TODO: handle overflowing to a new page
343
- # TODO: add a way to display borders
344
-
345
- x, y = current_point
346
- options = default_text_options.merge!({:left => x,
347
- :top => y
348
- })
349
- options.merge!(opts)
350
- options.assert_valid_keys(default_text_options.keys + default_positioning_options.keys)
351
- options[:width] = body_width - options[:left] unless options[:width]
352
-
353
- # move to the start of our table (the top left)
354
- x = options[:left]
355
- y = options[:top]
356
- move_to(x,y)
357
-
358
- # all columns will have the same width at this stage
359
- cell_width = options[:width] / data.first.size
360
-
361
- # draw the header cells
362
- y = draw_table_row(data.shift, cell_width, options)
363
- x = options[:left]
364
- move_to(x,y)
365
-
366
- # draw the data cells
367
- data.each do |row|
368
- y = draw_table_row(row, cell_width, options)
369
- x = options[:left]
370
- move_to(x,y)
286
+ # captioned_image("orc.svg", "Orc", 100, 100)
287
+ # captioned_image("hobbit.svg", "Hobbit", 100, 400)
288
+ # captioned_image("elf.svg", "Elf", 100, 400)
289
+ def translate(x, y, &block)
290
+ @context.save do
291
+ @context.translate(x, y)
292
+ yield
371
293
  end
372
294
  end
373
295
 
374
- # Write text to the page
375
- #
376
- # By default the text will be rendered using all the space within the margins and using
377
- # the default font styling set by default_font(), default_font_size, etc
378
- #
379
- # There is no way to place a bottom bound (or height) onto the text. Text will wrap as
380
- # necessary and take all the room it needs. For finer grained control of text boxes, see the
381
- # cell method.
382
- #
383
- # To override all these defaults, use the options hash
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
384
298
  #
385
- # Positioning Options:
299
+ # Divide everything by 2
386
300
  #
387
- # <tt>:left</tt>:: The x co-ordinate of the left-hand side of the text.
388
- # <tt>:top</tt>:: The y co-ordinate of the top of the text.
389
- # <tt>:width</tt>:: The width of the text to wrap at
301
+ # pdf.scale(0.5, 0.5) do
302
+ # ...
303
+ # end
390
304
  #
391
- # Text Style Options:
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)
392
307
  #
393
- # <tt>:font</tt>:: The font family to use as a string
394
- # <tt>:font_size</tt>:: The size of the font in points
395
- # <tt>:alignment</tt>:: Align the text along the left, right or centre. Use :left, :right, :center
396
- # <tt>:justify</tt>:: Justify the text so it exapnds to fill the entire width of each line. Note that this only works in pango >= 1.17
397
- # <tt>:spacing</tt>:: Space between lines in PDF points
398
- def text(str, opts={})
399
- # TODO: add support for pango markup (see http://ruby-gnome2.sourceforge.jp/hiki.cgi?pango-markup)
400
- # TODO: add converters from various markup languages to pango markup. (bluecloth, redcloth, markdown, textile, etc)
401
- # TODO: add a wrap option so wrapping can be disabled
402
- #
403
- # the non pango way to add text to the cairo context, not particularly useful for
404
- # PDF generation as it doesn't support wrapping text or other advanced layout features
405
- # and I really don't feel like re-implementing all that
406
- # @context.show_text(str)
407
-
408
- # the "pango way"
409
- x, y = current_point
410
- options = default_text_options.merge!({:left => x, :top => y})
411
- options.merge!(opts)
412
- options.assert_valid_keys(default_text_options.keys + default_positioning_options.keys)
413
-
414
- # if the user hasn't specified a width, make the text wrap on the right margin
415
- options[:width] = absolute_right_margin - options[:left] if options[:width].nil?
416
-
417
- layout = build_pango_layout(str.to_s, options[:width], options)
418
-
419
- set_color(options[:color])
420
-
421
- # draw the context on our cairo layout
422
- y = render_layout(layout, options[:left], options[:top], points_to_bottom_margin(options[:top]), :auto_new_page => true)
423
-
424
- move_to(options[:left], y + 5)
425
- end
426
-
427
- # Returns the amount of vertical space needed to display the supplied text at the requested width
428
- # opts is an options hash that specifies various attributes of the text. See the text function for more information.
429
- def text_height(str, width, opts = {})
430
- options = default_text_options.merge!(opts)
431
- options[:width] = width || body_width
432
- options.assert_valid_keys(default_text_options.keys + default_positioning_options.keys)
433
-
434
- layout = build_pango_layout(str.to_s, options[:width], options)
435
- width, height = layout.size
436
-
437
- return height / Pango::SCALE
438
- end
439
-
440
- # Returns the amount of horizontal space needed to display the supplied text with the requested options
441
- # opts is an options hash that specifies various attributes of the text. See the text function for more information.
442
- # The text is assumed to not wrap.
443
- def text_width(str, opts = {})
444
- options = default_text_options.merge!(opts)
445
- options.assert_valid_keys(default_text_options.keys)
446
-
447
- layout = build_pango_layout(str.to_s, -1, options)
448
- width, height = layout.size
449
-
450
- return width / Pango::SCALE
451
- end
452
-
453
- #####################################################
454
- # Functions relating to working with graphics
455
- #####################################################
456
-
457
- # draw a circle with radius r and a centre point at (x,y).
458
- # Parameters:
459
- # <tt>:x</tt>:: The x co-ordinate of the circle centre.
460
- # <tt>:y</tt>:: The y co-ordinate of the circle centre.
461
- # <tt>:r</tt>:: The radius of the circle
308
+ # pdf.scale(pdf.page_width.to_f, pdf.page_height.to_f) do
309
+ # ...
310
+ # end
462
311
  #
463
- # Options:
464
- # <tt>:color</tt>:: The colour of the circle outline
465
- # <tt>:line_width</tt>:: The width of outline. Defaults to 2.0
466
- # <tt>:fill_color</tt>:: The colour to fill the circle with. Defaults to nil (no fill)
467
- def circle(x, y, r, opts = {})
468
- options = {:color => @default_color,
469
- :line_width => @default_line_width,
470
- :fill_color => nil
471
- }
472
- options.merge!(opts)
473
- options.assert_valid_keys(:color, :line_width, :fill_color)
474
-
475
- # save the cursor position so we can restore it at the end
476
- origx, origy = current_point
477
-
478
- move_to(x + r, y)
312
+ def scale(w, h, &block)
313
+ @context.save do
314
+ @context.scale(w, h)
479
315
 
480
- # if the circle should be filled in
481
- if options[:fill_color]
482
- set_color(options[:fill_color])
483
- @context.circle(x, y, r).fill
316
+ # set the line width again so that it's set relative to the current
317
+ # scale factor
318
+ line_width @line_width
319
+ yield
484
320
  end
485
-
486
- set_color(options[:color])
487
- @context.set_line_width(options[:line_width])
488
- @context.circle(x, y, r).stroke
489
-
490
- # restore the cursor position
491
- move_to(origx, origy)
492
321
  end
493
322
 
494
- # draw a line from x1,y1 to x2,y2
495
- #
496
- # Options:
497
- # <tt>:color</tt>:: The colour of the line
498
- # <tt>:line_width</tt>:: The width of line. Defaults its 2.0
499
- def line(x0, y0, x1, y1, opts = {})
500
- options = {:color => @default_color, :line_width => @default_line_width }
501
- options.merge!(opts)
502
- options.assert_valid_keys(:color, :line_width)
503
-
504
- # save the cursor position so we can restore it at the end
505
- origx, origy = current_point
506
-
507
- set_color(options[:color])
508
- @context.set_line_width(options[:line_width])
509
- move_to(x0,y0)
510
- @context.line_to(x1,y1).stroke
511
-
512
- # restore the cursor position
513
- move_to(origx, origy)
514
- end
515
-
516
- # Adds a cubic Bezier spline to the path from the (x0, y0) to position (x3, y3)
517
- # in user-space coordinates, using (x1, y1) and (x2, y2) as the control points.
518
- # Options:
519
- # <tt>:color</tt>:: The colour of the line
520
- # <tt>:line_width</tt>:: The width of line. Defaults to 2.0
521
- def curve(x0, y0, x1, y1, x2, y2, x3, y3, opts = {})
522
- options = {:color => @default_color, :line_width => @default_line_width }
523
- options.merge!(opts)
524
- options.assert_valid_keys(:color, :line_width)
525
- origx, origy = current_point
526
-
527
- set_color(options[:color])
528
- @context.set_line_width(options[:line_width])
529
- move_to(x0,y0)
530
- @context.curve_to(x1, y1, x2, y2, x3, y3).stroke
531
-
532
- # restore the cursor position
533
- move_to(origx, origy)
534
- end
535
-
536
-
537
- # draw a rectangle starting at x,y with w,h dimensions.
538
- # Parameters:
539
- # <tt>:x</tt>:: The x co-ordinate of the top left of the rectangle.
540
- # <tt>:y</tt>:: The y co-ordinate of the top left of the rectangle.
541
- # <tt>:w</tt>:: The width of the rectangle
542
- # <tt>:h</tt>:: The height of the rectangle
323
+ # change the default colour used to draw on the canvas
543
324
  #
544
- # Options:
545
- # <tt>:color</tt>:: The colour of the rectangle outline
546
- # <tt>:line_width</tt>:: The width of outline. Defaults to 2.0
547
- # <tt>:fill_color</tt>:: The colour to fill the rectangle with. Defaults to nil (no fill)
548
- def rectangle(x, y, w, h, opts = {})
549
- options = {:color => @default_color,
550
- :line_width => @default_line_width,
551
- :fill_color => nil
552
- }
553
- options.merge!(opts)
554
- options.assert_valid_keys(:color, :line_width, :fill_color)
555
-
556
- # save the cursor position so we can restore it at the end
557
- origx, origy = current_point
558
-
559
- # if the rectangle should be filled in
560
- if options[:fill_color]
561
- set_color(options[:fill_color])
562
- @context.rectangle(x, y, w, h).fill
563
- end
564
-
565
- set_color(options[:color])
566
- @context.set_line_width(options[:line_width])
567
- @context.rectangle(x, y, w, h).stroke
568
-
569
- # restore the cursor position
570
- move_to(origx, origy)
571
- end
572
-
573
- # draw a rounded rectangle starting at x,y with w,h dimensions.
574
325
  # Parameters:
575
- # <tt>:x</tt>:: The x co-ordinate of the top left of the rectangle.
576
- # <tt>:y</tt>:: The y co-ordinate of the top left of the rectangle.
577
- # <tt>:w</tt>:: The width of the rectangle
578
- # <tt>:h</tt>:: The height of the rectangle
579
- # <tt>:r</tt>:: The size of the rounded corners
580
- #
581
- # Options:
582
- # <tt>:color</tt>:: The colour of the rectangle outline
583
- # <tt>:line_width</tt>:: The width of outline. Defaults to 2.0
584
- # <tt>:fill_color</tt>:: The colour to fill the rectangle with. Defaults to nil (no fill)
585
- def rounded_rectangle(x, y, w, h, r, opts = {})
586
- options = {:color => @default_color,
587
- :line_width => @default_line_width,
588
- :fill_color => nil
589
- }
590
- options.merge!(opts)
591
- options.assert_valid_keys(:color, :fill_color, :line_width)
592
-
593
- raise ArgumentError, "Argument r must be less than both w and h arguments" if r >= w || r >= h
594
-
595
- # save the cursor position so we can restore it at the end
596
- origx, origy = current_point
597
-
598
- # if the rectangle should be filled in
599
- if options[:fill_color]
600
- set_color(options[:fill_color])
601
- @context.rounded_rectangle(x, y, w, h, r).fill
602
- end
603
-
604
- set_color(options[:color])
605
- @context.set_line_width(options[:line_width])
606
- @context.rounded_rectangle(x, y, w, h, r).stroke
607
-
608
- # restore the cursor position
609
- move_to(origx, origy)
610
- end
611
-
612
- #####################################################
613
- # Functions relating to working with images
614
- #####################################################
615
-
616
- # add an image to the page - a wide range of image formats are supported,
617
- # including svg, jpg, png and gif. PDF images are also supported - an attempt
618
- # to add a multipage PDF will result in only the first page appearing in the
619
- # new document.
620
- #
621
- # supported options:
622
- # <tt>:left</tt>:: The x co-ordinate of the left-hand side of the image.
623
- # <tt>:top</tt>:: The y co-ordinate of the top of the image.
624
- # <tt>:height</tt>:: The height of the image
625
- # <tt>:width</tt>:: The width of the image
626
- # <tt>:proportional</tt>:: Boolean. Maintain image proportions when scaling. Defaults to false.
627
- # <tt>:padding</tt>:: Add some padding between the image and the specified box.
628
- #
629
- # left and top default to the current cursor location
630
- # width and height default to the size of the imported image
631
- # padding defaults to 0
632
- def image(filename, opts = {})
633
- # TODO: add some options for justification and padding
634
- raise ArgumentError, "file #{filename} not found" unless File.file?(filename)
635
- opts.assert_valid_keys(default_positioning_options.keys + [:padding, :proportional])
636
-
637
- if opts[:padding]
638
- opts[:left] += opts[:padding].to_i if opts[:left]
639
- opts[:top] += opts[:padding].to_i if opts[:top]
640
- opts[:width] -= opts[:padding].to_i * 2 if opts[:width]
641
- opts[:height] -= opts[:padding].to_i * 2 if opts[:height]
642
- end
643
-
644
- case detect_image_type(filename)
645
- when :pdf then draw_pdf filename, opts
646
- when :png then draw_png filename, opts
647
- when :svg then draw_svg filename, opts
648
- else
649
- begin
650
- draw_pixbuf filename, opts
651
- rescue Gdk::PixbufError
652
- raise ArgumentError, "Unrecognised image format (#{filename})"
653
- end
654
- end
326
+ # <tt>c</tt>:: either a colour symbol recognised by rcairo (:red, :blue, :black, etc) or
327
+ # an array with 3-4 integer elements. The first 3 numbers are red, green and
328
+ # blue (0-255). The optional 4th number is the alpha channel and should be
329
+ # between 0 and 1. See the API docs at http://cairo.rubyforge.org/ for a list
330
+ # of predefined colours
331
+ def color(c)
332
+ c = translate_color(c)
333
+ validate_color(c)
334
+ @context.set_source_rgba(*c)
655
335
  end
336
+ alias color= color
656
337
 
657
338
  #####################################################
658
339
  # Functions relating to generating the final document
@@ -693,8 +374,6 @@ module PDF
693
374
 
694
375
  # move the cursor to an arbitary position on the current page
695
376
  def move_to(x,y)
696
- raise ArgumentError, 'x cannot be larger than the width of the page' if x > page_width
697
- raise ArgumentError, 'y cannot be larger than the height of the page' if y > page_height
698
377
  @context.move_to(x,y)
699
378
  end
700
379
 
@@ -761,61 +440,6 @@ module PDF
761
440
 
762
441
  private
763
442
 
764
- def build_pango_layout(str, w, opts = {})
765
- options = default_text_options.merge!(opts)
766
-
767
- # if the user hasn't specified a width, make the layout as wide as the page body
768
- w = body_width if w.nil?
769
-
770
- # even though this is a private function, raise this error to force calling functions
771
- # to decide how they want to handle converting non-strings into strings for rendering
772
- raise ArgumentError, 'build_pango_layout must be passed a string' unless str.kind_of?(String)
773
-
774
- # if we're running under a M17n aware VM, ensure the string provided is UTF-8 or can be
775
- # converted to UTF-8
776
- if RUBY_VERSION >= "1.9"
777
- begin
778
- str = str.encode("UTF-8")
779
- rescue
780
- raise ArgumentError, 'Strings must be supplied with a UTF-8 encoding, or an encoding that can be converted to UTF-8'
781
- end
782
- end
783
-
784
- # The pango way:
785
- load_libpango
786
-
787
- # create a new Pango layout that our text will be added to
788
- layout = @context.create_pango_layout
789
- layout.text = str.to_s
790
- if w == -1
791
- layout.width = -1
792
- else
793
- layout.width = w * Pango::SCALE
794
- end
795
- layout.spacing = options[:spacing] * Pango::SCALE
796
-
797
- # set the alignment of the text in the layout
798
- if options[:alignment].eql?(:left)
799
- layout.alignment = Pango::Layout::ALIGN_LEFT
800
- elsif options[:alignment].eql?(:right)
801
- layout.alignment = Pango::Layout::ALIGN_RIGHT
802
- elsif options[:alignment].eql?(:center) || options[:alignment].eql?(:centre)
803
- layout.alignment = Pango::Layout::ALIGN_CENTER
804
- else
805
- raise ArgumentError, "Invalid alignment requested"
806
- end
807
-
808
- # justify the text if need be - only works in pango >= 1.17
809
- layout.justify = true if options[:justify]
810
-
811
- # setup the font that will be used to render the text
812
- fdesc = Pango::FontDescription.new(options[:font])
813
- fdesc.set_size(options[:font_size] * Pango::SCALE)
814
- layout.font_description = fdesc
815
- @context.update_pango_layout(layout)
816
- return layout
817
- end
818
-
819
443
  # runs the code in block, passing it a hash of options that might be
820
444
  # required
821
445
  def call_repeating_element(spec, block)
@@ -844,299 +468,51 @@ module PDF
844
468
  }
845
469
  end
846
470
 
847
- def default_text_options
848
- { :font => @default_font,
849
- :font_size => @default_font_size,
850
- :color => @default_color,
851
- :alignment => :left,
852
- :justify => false,
853
- :spacing => 0
854
- }
855
- end
856
-
857
- def detect_image_type(filename)
858
- # read the first Kb from the file to attempt file type detection
859
- f = File.new(filename)
860
- bytes = f.read(1024)
861
-
862
- # if the file is a PNG
863
- if bytes[1,3].eql?("PNG")
864
- return :png
865
- elsif bytes[0,3].eql?("GIF")
866
- return :gif
867
- elsif bytes[0,4].eql?("%PDF")
868
- return :pdf
869
- elsif bytes.include?("<svg")
870
- return :svg
871
- elsif bytes.include?("Exif") || bytes.include?("JFIF")
872
- return :jpg
873
- else
874
- return nil
875
- end
876
- end
877
-
878
- def calc_image_dimensions(desired_w, desired_h, actual_w, actual_h, scale = false)
879
- if scale
880
- wp = desired_w / actual_w.to_f
881
- hp = desired_h / actual_h.to_f
882
-
883
- if wp < hp
884
- width = actual_w * wp
885
- height = actual_h * wp
886
- else
887
- width = actual_w * hp
888
- height = actual_h * hp
889
- end
890
- else
891
- width = desired_w || actual_w
892
- height = desired_h || actual_h
893
- end
894
- return width.to_f, height.to_f
895
- end
896
-
897
- def draw_pdf(filename, opts = {})
898
- # based on a similar function in rabbit. Thanks Kou.
899
- load_libpoppler
900
- x, y = current_point
901
- page = Poppler::Document.new(filename).get_page(1)
902
- w, h = page.size
903
- width, height = calc_image_dimensions(opts[:width], opts[:height], w, h, opts[:proportional])
904
- @context.save do
905
- @context.translate(opts[:left] || x, opts[:top] || y)
906
- @context.scale(width / w, height / h)
907
- @context.render_poppler_page(page)
908
- end
909
- move_to(opts[:left] || x, (opts[:top] || y) + height)
910
- end
911
-
912
- def draw_pixbuf(filename, opts = {})
913
- # based on a similar function in rabbit. Thanks Kou.
914
- load_libpixbuf
915
- x, y = current_point
916
- pixbuf = Gdk::Pixbuf.new(filename)
917
- width, height = calc_image_dimensions(opts[:width], opts[:height], pixbuf.width, pixbuf.height, opts[:proportional])
918
- @context.save do
919
- @context.translate(opts[:left] || x, opts[:top] || y)
920
- @context.scale(width / pixbuf.width, height / pixbuf.height)
921
- @context.set_source_pixbuf(pixbuf, 0, 0)
922
- @context.paint
923
- end
924
- move_to(opts[:left] || x, (opts[:top] || y) + height)
925
- end
926
-
927
- def draw_png(filename, opts = {})
928
- # based on a similar function in rabbit. Thanks Kou.
929
- x, y = current_point
930
- img_surface = Cairo::ImageSurface.from_png(filename)
931
- width, height = calc_image_dimensions(opts[:width], opts[:height], img_surface.width, img_surface.height, opts[:proportional])
932
- @context.save do
933
- @context.translate(opts[:left] || x, opts[:top] || y)
934
- @context.scale(width / img_surface.width, height / img_surface.height)
935
- @context.set_source(img_surface, 0, 0)
936
- @context.paint
937
- end
938
- move_to(opts[:left] || x, (opts[:top] || y) + height)
471
+ # save and restore the cursor position around a block
472
+ def save_coords(&block)
473
+ origx, origy = current_point
474
+ yield
475
+ move_to(origx, origy)
939
476
  end
940
477
 
941
- def draw_svg(filename, opts = {})
942
- # based on a similar function in rabbit. Thanks Kou.
943
- load_librsvg
944
- x, y = current_point
945
- handle = RSVG::Handle.new_from_file(filename)
946
- width, height = calc_image_dimensions(opts[:width], opts[:height], handle.width, handle.height, opts[:proportional])
478
+ # save and restore the cursor position and graphics state around a block
479
+ def save_coords_and_state(&block)
480
+ origx, origy = current_point
947
481
  @context.save do
948
- @context.translate(opts[:left] || x, opts[:top] || y)
949
- @context.scale(width / handle.width, height / handle.height)
950
- @context.render_rsvg_handle(handle)
951
- #@context.paint
952
- end
953
- move_to(opts[:left] || x, (opts[:top] || y) + height)
954
- end
955
-
956
- # adds a single table row to the canvas. Top left of the row will be at the current x,y
957
- # co-ordinates, so make sure they're set correctly before calling this function
958
- #
959
- # strings - array of strings. Each element of the array is a cell
960
- # column_widths - the width of each column. At this stage it should be an int. All columns are the same width
961
- # options - any options relating to text style to use. font, font_size, alignment, etc. See text() for more info.
962
- #
963
- # Returns the y co-ordinates of the bottom edge of the row, ready for the next row
964
- def draw_table_row(strings, column_widths, options)
965
- row_height = 0
966
- x, y = current_point
967
-
968
- # we run all this code twice. The first time is a dry run to calculate the
969
- # height of the largest cell, which determines the overall height of the row.
970
- # The second run through we actually draw each cell onto the canvas
971
- [:dry, :paint].each do |action|
972
-
973
- strings.each do |head|
974
- # TODO: provide a way for these to be overridden on a per cell basis
975
- opts = {
976
- :font => options[:font],
977
- :font_size => options[:font_size],
978
- :color => options[:color],
979
- :alignment => options[:alignment],
980
- :justify => options[:justify],
981
- :spacing => options[:spacing]
982
- }
983
-
984
- if action == :dry
985
- # calc the cell height, and set row_height if this cell is the biggest in the row
986
- cell_height = text_height(head, column_widths, opts)
987
- row_height = cell_height if cell_height > row_height
988
- else
989
- # start a new page if necesary
990
- if row_height > (absolute_bottom_margin - y)
991
- start_new_page
992
- y = margin_top
993
- end
994
-
995
- # add our cell, then advance x to the left edge of the next cell
996
- self.cell(head, x, y, column_widths, row_height, opts)
997
- x += column_widths
998
- end
999
-
1000
- end
1001
- end
1002
-
1003
- return y + row_height
1004
- end
1005
-
1006
- def image_dimensions(filename)
1007
- raise ArgumentError, "file #{filename} not found" unless File.file?(filename)
1008
-
1009
- case detect_image_type(filename)
1010
- when :pdf then
1011
- load_libpoppler
1012
- page = Poppler::Document.new(filename).get_page(1)
1013
- return page.size
1014
- when :png then
1015
- img_surface = Cairo::ImageSurface.from_png(filename)
1016
- return img_surface.width, img_surface.height
1017
- when :svg then
1018
- load_librsvg
1019
- handle = RSVG::Handle.new_from_file(filename)
1020
- return handle.width, handle.height
1021
- else
1022
- load_libpixbuf
1023
- begin
1024
- pixbuf = Gdk::Pixbuf.new(filename)
1025
- return pixbuf.width, pixbuf.height
1026
- rescue Gdk::PixbufError
1027
- raise ArgumentError, "Unrecognised image format (#{filename})"
1028
- end
482
+ yield
1029
483
  end
484
+ move_to(origx, origy)
1030
485
  end
1031
486
 
1032
- # load libpango if it isn't already loaded.
1033
- # This will add some methods to the cairo Context class in addition to providing
1034
- # its own classes and constants. A small amount of documentation is available at
1035
- # http://ruby-gnome2.sourceforge.jp/fr/hiki.cgi?Cairo%3A%3AContext#Pango+related+APIs
1036
- def load_libpango
1037
- begin
1038
- require 'pango' unless @context.respond_to? :create_pango_layout
1039
- rescue LoadError
1040
- raise LoadError, 'Ruby/Pango library not found. Visit http://ruby-gnome2.sourceforge.jp/'
1041
- end
487
+ def translate_color(c)
488
+ # the follow line converts a color definition from various formats (hex, symbol, etc)
489
+ # into a 4 item array. This is normally handled within cairo itself, however when
490
+ # Cairo and Poppler are both loaded, it breaks.
491
+ Cairo::Color.parse(c).to_rgb.to_a
1042
492
  end
1043
493
 
1044
- # load lib gdkpixbuf if it isn't already loaded.
1045
- # This will add some methods to the cairo Context class in addition to providing
1046
- # its own classes and constants.
1047
- def load_libpixbuf
1048
- begin
1049
- require 'gdk_pixbuf2' unless @context.respond_to? :set_source_pixbuf
1050
- rescue LoadError
1051
- raise LoadError, 'Ruby/GdkPixbuf library not found. Visit http://ruby-gnome2.sourceforge.jp/'
1052
- end
494
+ def user_to_device_dist(x,y)
495
+ @context.user_to_device_distance(x, y)
1053
496
  end
1054
497
 
1055
- # load lib poppler if it isn't already loaded.
1056
- # This will add some methods to the cairo Context class in addition to providing
1057
- # its own classes and constants.
1058
- def load_libpoppler
1059
- begin
1060
- require 'poppler' unless @context.respond_to? :render_poppler_page
1061
- rescue LoadError
1062
- raise LoadError, 'Ruby/Poppler library not found. Visit http://ruby-gnome2.sourceforge.jp/'
1063
- end
498
+ def user_x_to_device_x(x)
499
+ @context.user_to_device(x, 0).first.abs
1064
500
  end
1065
501
 
1066
- # load librsvg if it isn't already loaded
1067
- # This will add an additional method to the Cairo::Context class
1068
- # that allows an existing SVG to be drawn directly onto it
1069
- # There's a *little* bit of documentation at:
1070
- # http://ruby-gnome2.sourceforge.jp/fr/hiki.cgi?Cairo%3A%3AContext#render_rsvg_handle
1071
- def load_librsvg
1072
- begin
1073
- require 'rsvg2' unless @context.respond_to? :render_svg_handle
1074
- rescue LoadError
1075
- raise LoadError, 'Ruby/RSVG library not found. Visit http://ruby-gnome2.sourceforge.jp/'
1076
- end
502
+ def user_y_to_device_y(y)
503
+ @context.user_to_device(0, y).last.abs
1077
504
  end
1078
505
 
1079
- # renders a pango layout onto our main context
1080
- # based on a function of the same name found in the text2.rb sample file
1081
- # distributed with rcairo - it's still black magic to me and has a few edge
1082
- # cases where it doesn't work too well. Needs to be improved.
1083
- def render_layout(layout, x, y, h, opts = {})
1084
- # we can't use context.show_pango_layout, as that won't start
1085
- # a new page if the layout hits the bottom margin. Instead,
1086
- # we iterate over each line of text in the layout and add it to
1087
- # the canvas, page breaking as necessary
1088
- options = {:auto_new_page => true }
1089
- options.merge!(opts)
1090
-
1091
- offset = 0
1092
- baseline = 0
1093
-
1094
- iter = layout.iter
1095
- loop do
1096
- line = iter.line
1097
- ink_rect, logical_rect = iter.line_extents
1098
- if y + (baseline - offset) >= (y + h)
1099
- # our text is using the maximum amount of vertical space we want it to
1100
- if options[:auto_new_page]
1101
- # create a new page and we can continue adding text
1102
- offset += baseline
1103
- start_new_page
1104
- else
1105
- # the user doesn't want us to continue on the next page, so
1106
- # stop adding lines to the canvas
1107
- break
1108
- end
1109
- end
1110
-
1111
- # move to the start of the next line
1112
- #move_to(x, y)
1113
- baseline = iter.baseline / Pango::SCALE
1114
- @context.move_to(x + logical_rect.x / Pango::SCALE, y + baseline - offset)
1115
-
1116
- # draw the line on the canvas
1117
- @context.show_pango_layout_line(line)
1118
-
1119
- break unless iter.next_line!
1120
- end
1121
-
1122
- # return the y co-ord we finished on
1123
- return y + baseline - offset
506
+ def device_to_user_dist(x, y)
507
+ @context.device_to_user_distance(x, y)
1124
508
  end
1125
509
 
1126
- def translate_color(c)
1127
- # the follow line converts a color definition from various formats (hex, symbol, etc)
1128
- # into a 4 item array. This is normally handled within cairo itself, however when
1129
- # Cairo and Poppler are both loaded, it breaks.
1130
- Cairo::Color.parse(c).to_rgb.to_a
510
+ def device_x_to_user_x(x)
511
+ @context.device_to_user(x, 0).first.abs
1131
512
  end
1132
513
 
1133
- # set the current drawing colour
1134
- #
1135
- # for info on what is valid, see the comments for default_color
1136
- def set_color(c)
1137
- c = translate_color(c)
1138
- validate_color(c)
1139
- @context.set_source_rgba(*c)
514
+ def device_y_to_user_y(y)
515
+ @context.device_to_user(0, y).last.abs
1140
516
  end
1141
517
 
1142
518
  # test to see if the specified colour is a a valid cairo color