pdf-wrapper 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG ADDED
@@ -0,0 +1,2 @@
1
+ v0.0.1 (9th January 2008)
2
+ - Initial, pre-alpha, eats small children for breakfast release
data/DESIGN ADDED
@@ -0,0 +1,29 @@
1
+ *************************************
2
+ * Overarching Design Principles
3
+ *************************************
4
+ - low level canvas drawing API
5
+ - add text
6
+ - add shapes / images
7
+
8
+ - higher level "widget" API
9
+ - text boxes
10
+ - form helpers ( check boxes, etc)
11
+ - water mark
12
+ - repeating elements (page numbers, headers/footers, etc)
13
+ - image boxes
14
+ - lists
15
+ - tables (port simple table?)
16
+
17
+ *************************************
18
+ * Thoughts on the table API
19
+ *************************************
20
+
21
+ FPDF::Table
22
+ - .table(data = [], columns = [])
23
+ - http://source.mihelac.org/2006/6/19/creating-pdf-documents-with-tables-in-ruby-rails#comments
24
+
25
+ PDF::SimpleTable
26
+ - .table(data = [{}], columns = []
27
+
28
+ XHTML?
29
+ - .table(data = String, opts = {})
data/README ADDED
@@ -0,0 +1,73 @@
1
+ = Overview
2
+
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
+ At this stage the API is *roughly* following that of PDF::Writer, but i've made
12
+ tweaks in some places and added some new methods. This is a work in progress so
13
+ many features of PDF::Writer aren't available yet.
14
+
15
+ A key motivation for writing this library is cairo's support for Unicode in PDFs.
16
+ All text functions in this library support UTF8 input, although as a native
17
+ English speaker I've only tested this a little, so any feedback is welcome.
18
+
19
+ There also seems to be a lack of English documentation available for the ruby
20
+ bindings to cairo/pango, so I'm aiming to document the code as much as possible
21
+ to provide worked examples for others. I'm learning as I go though, so if regular
22
+ users of either library spot techniques that fail best practice, please let me know.
23
+
24
+ It's early days, so the API is far from stable and I'm hesitant to write extensive
25
+ documentation just yet. It's the price you pay for being an early adopter. The
26
+ examples/ dir should have a range of sample code, and I'll try to keep it up to
27
+ date.
28
+
29
+ I welcome all feedback, feature requests, patches and suggestions. In
30
+ particular, what high level widgets would you like to see? What do you use when
31
+ building reports and documents in GUI programs?
32
+
33
+ = Installation
34
+
35
+ The recommended installation method is via Rubygems.
36
+
37
+ gem install pdf-wrapper
38
+
39
+ = Author
40
+
41
+ James Healy <jimmy@deefa.com>
42
+
43
+ = License
44
+
45
+ * GPL version 2 or the Ruby License
46
+ * Ruby: http://www.ruby-lang.org/en/LICENSE.txt
47
+
48
+ = Dependencies
49
+
50
+ * ruby/cairo
51
+ * ruby/pango (optional, required to add text)
52
+ * ruby/rsvg2 (optional, required for SVG support)
53
+
54
+ These are all ruby bindings to C libraries. On Debian/Ubuntu based systems
55
+ (which I develop on) you can get them by running:
56
+
57
+ aptitude install libcairo-ruby libpango1-ruby librsvg2-ruby
58
+
59
+ For users of other systems, I'd love to receive info on how you set these bindings up.
60
+
61
+ ruby/cairo is also available as a gem (cairo), which may be installable if you have a copy
62
+ of the cairo source available on your system.
63
+
64
+ = Compatibility
65
+
66
+ JRuby users, you're probably out of luck.
67
+
68
+ Rubinius users, I have no idea.
69
+
70
+ Ruby1.9 users, the current release of ruby/cairo (1.5.0) doesn't work with 1.9,
71
+ but the version in SVN does. Hopefully it will be released soon. The version in
72
+ Debian has been patched to work with 1.9 already. PDF::Wrapper itself is 1.9
73
+ compatible.
data/Rakefile ADDED
@@ -0,0 +1,76 @@
1
+ require "rubygems"
2
+ require 'rake'
3
+ require 'rake/clean'
4
+ require 'rake/rdoctask'
5
+ require 'rake/testtask'
6
+ require "rake/gempackagetask"
7
+ require 'spec/rake/spectask'
8
+
9
+ PKG_VERSION = "0.0.1"
10
+ PKG_NAME = "pdf-wrapper"
11
+ PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
12
+
13
+ desc "Default Task"
14
+ task :default => [ :spec ]
15
+
16
+ # run all rspecs
17
+ desc "Run all rspec files"
18
+ Spec::Rake::SpecTask.new("spec") do |t|
19
+ t.spec_files = FileList['specs/**/*.rb']
20
+ t.rcov = true
21
+ t.rcov_dir = (ENV['CC_BUILD_ARTIFACTS'] || 'doc') + "/rcov"
22
+ t.rcov_opts = ["--exclude","spec.*\.rb","--exclude",".*cairo.*","--exclude",".*rcov.*","--exclude",".*rspec.*","--exclude",".*df-reader.*"]
23
+ end
24
+
25
+ # generate specdocs
26
+ desc "Generate Specdocs"
27
+ Spec::Rake::SpecTask.new("specdocs") do |t|
28
+ t.spec_files = FileList['specs/**/*.rb']
29
+ t.spec_opts = ["--format", "rdoc"]
30
+ t.out = (ENV['CC_BUILD_ARTIFACTS'] || 'doc') + '/specdoc.rd'
31
+ end
32
+
33
+ # generate failing spec report
34
+ desc "Generate failing spec report"
35
+ Spec::Rake::SpecTask.new("spec_report") do |t|
36
+ t.spec_files = FileList['specs/**/*.rb']
37
+ t.spec_opts = ["--format", "html", "--diff"]
38
+ t.out = (ENV['CC_BUILD_ARTIFACTS'] || 'doc') + '/spec_report.html'
39
+ t.fail_on_error = false
40
+ end
41
+
42
+ # Genereate the RDoc documentation
43
+ desc "Create documentation"
44
+ Rake::RDocTask.new("doc") do |rdoc|
45
+ rdoc.title = "pdf-wrapper"
46
+ rdoc.rdoc_dir = (ENV['CC_BUILD_ARTIFACTS'] || 'doc') + '/rdoc'
47
+ rdoc.rdoc_files.include('README')
48
+ rdoc.rdoc_files.include('CHANGELOG')
49
+ rdoc.rdoc_files.include('DESIGN')
50
+ rdoc.rdoc_files.include('lib/**/*.rb')
51
+ rdoc.options << "--inline-source"
52
+ end
53
+
54
+ # a gemspec for packaging this library
55
+ spec = Gem::Specification.new do |spec|
56
+ spec.name = PKG_NAME
57
+ spec.version = PKG_VERSION
58
+ spec.platform = Gem::Platform::RUBY
59
+ spec.summary = "A PDF generating library built on top of cairo"
60
+ spec.files = Dir.glob("{examples,lib}/**/**/*") + ["Rakefile"]
61
+ spec.require_path = "lib"
62
+ spec.has_rdoc = true
63
+ spec.extra_rdoc_files = %w{README DESIGN CHANGELOG}
64
+ spec.rdoc_options << '--title' << 'PDF::Wrapper Documentation' << '--main' << 'README' << '-q'
65
+ spec.author = "James Healy"
66
+ spec.email = "jimmy@deefa.com"
67
+ spec.rubyforge_project = "pdf-wrapper"
68
+ spec.description = "A PDF writing library that uses the cairo and pango libraries to do the heavy lifting."
69
+ end
70
+
71
+ # package the library into a gem
72
+ desc "Generate a gem for pdf-wrapper"
73
+ Rake::GemPackageTask.new(spec) do |pkg|
74
+ pkg.need_zip = true
75
+ pkg.need_tar = true
76
+ end
data/examples/cell.rb ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift(File.dirname(__FILE__) + "/../lib")
4
+
5
+ require 'pdf/wrapper'
6
+
7
+ pdf = PDF::Wrapper.new(:paper => :A4)
8
+ 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.render_to_file("wrapper-cell.pdf")
Binary file
Binary file
data/examples/image.rb ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift(File.dirname(__FILE__) + "/../lib")
4
+
5
+ require 'pdf/wrapper'
6
+
7
+ pdf = PDF::Wrapper.new(:paper => :A4)
8
+ pdf.default_font("Sans Serif")
9
+ pdf.default_color(:black)
10
+ pdf.text("PDF::Wrapper Supports Images", :alignment => :center)
11
+ pdf.image(File.dirname(__FILE__) + "/google.png", :left => 100, :top => 250)
12
+ pdf.render_to_file("image.pdf")
@@ -0,0 +1,66 @@
1
+ %PDF-1.4
2
+ %����
3
+ 2 0 obj
4
+ << /Length 3 0 R
5
+ /Filter /FlateDecode
6
+ /Type /XObject
7
+ /Subtype /Form
8
+ /BBox [ 0 0 595.28 841.89 ]
9
+ >>
10
+ stream
11
+ x��R�N�0 �W�?�(v�� !qX8"(�V��a��O�<�yQ��ӎ�c� ��G�G8~�/<����-p����;��%0�s�6��u�`S0g���>)+�^�Ey�1�(�S߽�}GO���ɑ|��J�;z w�A�`��8ܶ>�(Wق�i�ɱ�jhēd�Ӄ3�5d�)�wIs���X�q�Rr���~0��l��S�,��K�m��>A�mZ���pPb3�0VS`8�X%I���"�BR�ز��dj���^A*�%�H�Z���^���z�)��v�����3];�'a��q�hz����-b���͚‹C^��N
12
+ endstream
13
+ endobj
14
+ 3 0 obj
15
+ 342
16
+ endobj
17
+ 4 0 obj
18
+ << /Type /Page
19
+ /Parent 1 0 R
20
+ /MediaBox [ 0 0 595.28 841.89 ]
21
+ /Contents [ 2 0 R ]
22
+ /Group <<
23
+ /Type /Group
24
+ /S /Transparency
25
+ /CS /DeviceRGB
26
+ >>
27
+ >>
28
+ endobj
29
+ 1 0 obj
30
+ << /Type /Pages
31
+ /Kids [ 4 0 R ]
32
+ /Count 1
33
+ /Resources <<
34
+ /ExtGState <<
35
+ /a0 << /CA 1 /ca 1 >>
36
+ >>
37
+ >>
38
+ >>
39
+ endobj
40
+ 5 0 obj
41
+ << /Creator (cairo 1.4.12 (http://cairographics.org))
42
+ /Producer (cairo 1.4.12 (http://cairographics.org))
43
+ >>
44
+ endobj
45
+ 6 0 obj
46
+ << /Type /Catalog
47
+ /Pages 1 0 R
48
+ >>
49
+ endobj
50
+ xref
51
+ 0 7
52
+ 0000000000 65535 f
53
+ 0000000739 00000 n
54
+ 0000000017 00000 n
55
+ 0000000512 00000 n
56
+ 0000000537 00000 n
57
+ 0000000898 00000 n
58
+ 0000001030 00000 n
59
+ trailer
60
+ << /Size 7
61
+ /Root 6 0 R
62
+ /Info 5 0 R
63
+ >>
64
+ startxref
65
+ 1087
66
+ %%EOF
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift(File.dirname(__FILE__) + "/../lib")
4
+
5
+ require 'pdf/wrapper'
6
+
7
+ pdf = PDF::Wrapper.new(:paper => :A4)
8
+ pdf.rectangle(30,30,100,100, :fill_color => :red)
9
+ pdf.circle(100,300,30)
10
+ pdf.line(100, 350, 400, 150)
11
+ pdf.rounded_rectangle(300,300, 200, 200, 10, :fill_color => :green)
12
+ pdf.render_to_file("shapes.pdf")
data/examples/table.rb ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift(File.dirname(__FILE__) + "/../lib")
4
+
5
+ require 'pdf/wrapper'
6
+
7
+ pdf = PDF::Wrapper.new(:paper => :A4)
8
+ pdf.text "Chunky Bacon!!"
9
+ data = [%w{one two three four}]
10
+
11
+ data << ["This is some longer text to ensure that the cell wraps",2,3,4]
12
+
13
+ (1..100).each do
14
+ data << %w{1 2 3 4}
15
+ end
16
+ pdf.table(data, :font_size => 10)
17
+ pdf.render_to_file("table.pdf")
data/examples/utf8.rb ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift(File.dirname(__FILE__) + "/../lib")
4
+
5
+ require 'pdf/wrapper'
6
+
7
+ pdf = PDF::Wrapper.new(:paper => :A4)
8
+ pdf.default_font("Sans Serif")
9
+ pdf.default_color(:black)
10
+ pdf.text File.read(File.dirname(__FILE__) + "/../specs/data/utf8.txt"), :font => "Monospace", :font_size => 8
11
+ pdf.render_to_file("wrapper.pdf")
data/lib/pdf/core.rb ADDED
@@ -0,0 +1,8 @@
1
+ class Hash
2
+ # raise an error if this hash has any keys that aren't in the supplied list
3
+ # - borrowed from activesupport
4
+ def assert_valid_keys(*valid_keys)
5
+ unknown_keys = keys - [valid_keys].flatten
6
+ raise(ArgumentError, "Unknown key(s): #{unknown_keys.join(", ")}") unless unknown_keys.empty?
7
+ end
8
+ end
@@ -0,0 +1,839 @@
1
+ # -* coding: UTF-8 -*-
2
+
3
+ require 'stringio'
4
+ require 'pdf/core'
5
+
6
+ # try to load cairo from the standard places, but don't worry if it fails,
7
+ # we'll try to find it via rubygems
8
+ begin
9
+ require 'cairo'
10
+ rescue LoadError
11
+ begin
12
+ require 'rubygems'
13
+ gem 'cairo', '>=1.5'
14
+ require 'cairo'
15
+ rescue Gem::LoadError
16
+ raise LoadError, "Could not find the ruby cairo bindings in the standard locations or via rubygems. Check to ensure they're installed correctly"
17
+ rescue LoadError
18
+ raise LoadError, "Could not load rubygems"
19
+ end
20
+ end
21
+
22
+ module PDF
23
+ # Create PDF files by using the cairo and pango libraries.
24
+ #
25
+ # Rendering to a file:
26
+ #
27
+ # require 'pdfwrapper'
28
+ # pdf = PDF::Wrapper.new(:paper => :A4)
29
+ # pdf.text "Hello World"
30
+ # pdf.render_to_file("wrapper.pdf")
31
+ #
32
+ # Rendering to a string:
33
+ #
34
+ # require 'pdfwrapper'
35
+ # pdf = PDF::Wrapper.new(:paper => :A4)
36
+ # pdf.text "Hello World", :font_size => 16
37
+ # puts pdf.render
38
+ #
39
+ # Changing the default font:
40
+ #
41
+ # require 'pdfwrapper'
42
+ # pdf = PDF::Wrapper.new(:paper => :A4)
43
+ # pdf.default_font("Monospace")
44
+ # pdf.text "Hello World", :font => "Sans Serif", :font_size => 18
45
+ # pdf.text "Pretend this is a code sample"
46
+ # puts pdf.render
47
+ class Wrapper
48
+
49
+ attr_reader :margin_left, :margin_right, :margin_top, :margin_bottom
50
+ attr_reader :page_width, :page_height
51
+
52
+ # borrowed from PDF::Writer
53
+ PAGE_SIZES = { # :value {...}:
54
+ #:4A0 => [4767.87, 6740.79], :2A0 => [3370.39, 4767.87],
55
+ :A0 => [2383.94, 3370.39], :A1 => [1683.78, 2383.94],
56
+ :A2 => [1190.55, 1683.78], :A3 => [841.89, 1190.55],
57
+ :A4 => [595.28, 841.89], :A5 => [419.53, 595.28],
58
+ :A6 => [297.64, 419.53], :A7 => [209.76, 297.64],
59
+ :A8 => [147.40, 209.76], :A9 => [104.88, 147.40],
60
+ :A10 => [73.70, 104.88], :B0 => [2834.65, 4008.19],
61
+ :B1 => [2004.09, 2834.65], :B2 => [1417.32, 2004.09],
62
+ :B3 => [1000.63, 1417.32], :B4 => [708.66, 1000.63],
63
+ :B5 => [498.90, 708.66], :B6 => [354.33, 498.90],
64
+ :B7 => [249.45, 354.33], :B8 => [175.75, 249.45],
65
+ :B9 => [124.72, 175.75], :B10 => [87.87, 124.72],
66
+ :C0 => [2599.37, 3676.54], :C1 => [1836.85, 2599.37],
67
+ :C2 => [1298.27, 1836.85], :C3 => [918.43, 1298.27],
68
+ :C4 => [649.13, 918.43], :C5 => [459.21, 649.13],
69
+ :C6 => [323.15, 459.21], :C7 => [229.61, 323.15],
70
+ :C8 => [161.57, 229.61], :C9 => [113.39, 161.57],
71
+ :C10 => [79.37, 113.39], :RA0 => [2437.80, 3458.27],
72
+ :RA1 => [1729.13, 2437.80], :RA2 => [1218.90, 1729.13],
73
+ :RA3 => [864.57, 1218.90], :RA4 => [609.45, 864.57],
74
+ :SRA0 => [2551.18, 3628.35], :SRA1 => [1814.17, 2551.18],
75
+ :SRA2 => [1275.59, 1814.17], :SRA3 => [907.09, 1275.59],
76
+ :SRA4 => [637.80, 907.09], :LETTER => [612.00, 792.00],
77
+ :LEGAL => [612.00, 1008.00], :FOLIO => [612.00, 936.00],
78
+ :EXECUTIVE => [521.86, 756.00]
79
+ }
80
+
81
+ # create a new PDF::Wrapper class to compose a PDF document
82
+ # Options:
83
+ # <tt>:paper</tt>:: The paper size to use (default :A4)
84
+ # <tt>:orientation</tt>:: :portrait (default) or :landscape
85
+ # <tt>:background_colour</tt>:: The background colour to use (default :white)
86
+ def initialize(opts={})
87
+ options = {:paper => :A4,
88
+ :orientation => :portrait,
89
+ :background_colour => :white
90
+ }
91
+ options.merge!(opts)
92
+
93
+ # test for invalid options
94
+ raise ArgumentError, "Invalid paper option" unless PAGE_SIZES.include?(options[:paper])
95
+
96
+ # set page dimensions
97
+ if options[:orientation].eql?(:portrait)
98
+ @page_width = PAGE_SIZES[options[:paper]][0]
99
+ @page_height = PAGE_SIZES[options[:paper]][1]
100
+ elsif options[:orientation].eql?(:landscape)
101
+ @page_width = PAGE_SIZES[options[:paper]][1]
102
+ @page_height = PAGE_SIZES[options[:paper]][0]
103
+ else
104
+ raise ArgumentError, "Invalid orientation"
105
+ end
106
+
107
+ # set page margins and dimensions of usable canvas
108
+ # TODO: add options for customising the margins. ATM they're always 5% of the page dimensions
109
+ @margin_left = (@page_width * 0.05).ceil
110
+ @margin_right = (@page_width * 0.05).ceil
111
+ @margin_top = (@page_height * 0.05).ceil
112
+ @margin_bottom = (@page_height * 0.05).ceil
113
+
114
+ # initialize some cairo objects to draw on
115
+ @output = StringIO.new
116
+ @surface = Cairo::PDFSurface.new(@output, @page_width, @page_height)
117
+ @context = Cairo::Context.new(@surface)
118
+
119
+ # set the background colour
120
+ set_color(options[:background_colour])
121
+ @context.paint
122
+
123
+ # set a default drawing colour and font style
124
+ default_color(:black)
125
+ default_font("Sans Serif")
126
+ default_font_size(16)
127
+
128
+ # move the cursor to the top left of the usable canvas
129
+ reset_cursor
130
+ end
131
+
132
+ #####################################################
133
+ # Functions relating to calculating various page dimensions
134
+ #####################################################
135
+
136
+ # Returns the x value of the left margin
137
+ # The top left corner of the page is (0,0)
138
+ def absolute_left_margin
139
+ @margin_left
140
+ end
141
+
142
+ # Returns the x value of the right margin
143
+ # The top left corner of the page is (0,0)
144
+ def absolute_right_margin
145
+ @page_width - @margin_right
146
+ end
147
+
148
+ # Returns the y value of the top margin
149
+ # The top left corner of the page is (0,0)
150
+ def absolute_top_margin
151
+ @margin_top
152
+ end
153
+
154
+ # Returns the y value of the bottom margin
155
+ # The top left corner of the page is (0,0)
156
+ def absolute_bottom_margin
157
+ @page_height - @margin_bottom
158
+ end
159
+
160
+ # Returns the x at the middle of the page
161
+ def absolute_x_middle
162
+ @page_width / 2
163
+ end
164
+
165
+ # Returns the y at the middle of the page
166
+ def absolute_y_middle
167
+ @page_height / 2
168
+ end
169
+
170
+ # Returns the width of the usable part of the page (between the side margins)
171
+ def body_width
172
+ @page_width - @margin_left - @margin_right
173
+ end
174
+
175
+ # Returns the height of the usable part of the page (between the top and bottom margins)
176
+ def body_height
177
+ @page_height - @margin_top - @margin_bottom
178
+ end
179
+
180
+ # Returns the x coordinate of the middle part of the usable space between the margins
181
+ def margin_x_middle
182
+ @margin_left + (body_width / 2)
183
+ end
184
+
185
+ # Returns the y coordinate of the middle part of the usable space between the margins
186
+ def margin_y_middle
187
+ @margin_top + (body_height / 2)
188
+ end
189
+
190
+ # return the current position of the cursor
191
+ # returns 2 values - x,y
192
+ def current_point
193
+ return @context.current_point
194
+ end
195
+
196
+ # return the number of points from starty to the bottom border
197
+ def points_to_bottom_margin(starty)
198
+ absolute_bottom_margin - starty
199
+ end
200
+
201
+ # return the number of points from startx to the right border
202
+ def points_to_right_margin(startx)
203
+ absolute_right_margin - startx
204
+ end
205
+
206
+ #####################################################
207
+ # Functions relating to working with text
208
+ #####################################################
209
+
210
+ # change the default font size
211
+ def default_font_size(size)
212
+ #@context.set_font_size(size.to_i)
213
+ @default_font_size = size.to_i unless size.nil?
214
+ end
215
+ alias default_font_size= default_font_size
216
+ alias font_size default_font_size # PDF::Writer compatibility
217
+
218
+ # change the default font to write with
219
+ def default_font(fontname, style = nil, weight = nil)
220
+ #@context.select_font_face(fontname, slant, bold)
221
+ @default_font = fontname
222
+ @default_font_style = style unless style.nil?
223
+ @default_font_weight = weight unless weight.nil?
224
+ end
225
+ alias default_font= default_font
226
+ alias select_font default_font # PDF::Writer compatibility
227
+
228
+ # change the default colour used to draw on the canvas
229
+ #
230
+ # Parameters:
231
+ # <tt>c</tt>:: either a colour symbol recognised by rcairo (:red, :blue, :black, etc) or
232
+ # an array with 3-4 integer elements. The first 3 numbers are red, green and
233
+ # blue (0-255). The optional 4th number is the alpha channel and should be
234
+ # between 0 and 1. See the API docs at http://cairo.rubyforge.org/ for a list
235
+ # of predefined colours
236
+ def default_color(c)
237
+ validate_color(c)
238
+ @default_color = c
239
+ end
240
+ alias default_color= default_color
241
+ alias stroke_color default_color # PDF::Writer compatibility
242
+
243
+ # add text to the page, bounded by a box with dimensions HxW, with it's top left corner
244
+ # at x,y. Any text that doesn't fit it the box will be silently dropped.
245
+ #
246
+ # In addition to the standard text style options (see the documentation for text()), cell() supports
247
+ # the following options:
248
+ #
249
+ # <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.
250
+ # <tt>:border_width</tt>:: How wide should the border be?
251
+ # <tt>:border_color</tt>:: What color should the border be?
252
+ # <tt>:bgcolor</tt>:: A background color for the cell. Defaults to none.
253
+ def cell(str, x, y, w, h, opts={})
254
+ # TODO: add support for pango markup (see http://ruby-gnome2.sourceforge.jp/hiki.cgi?pango-markup)
255
+ # TODO: add a wrap option so wrapping can be disabled
256
+ # TODO: raise an error if any unrecognised options were supplied
257
+ # TODO: add padding between border and text
258
+ # TODO: how do we handle a single word that is too long for the width?
259
+
260
+ options = default_text_options
261
+ options.merge!({:border => "tblr", :border_width => 1, :border_color => :black, :bgcolor => nil})
262
+ options.merge!(opts)
263
+
264
+ options[:width] = w
265
+ options[:border] = "" unless options[:border]
266
+ options[:border].downcase!
267
+
268
+ # TODO: raise an exception if the box coords or dimensions will place it off the canvas
269
+ rectangle(x,y,w,h, :color => options[:bgcolor], :fill_color => options[:bgcolor]) if options[:bgcolor]
270
+
271
+ layout = build_pango_layout(str.to_s, options)
272
+
273
+ set_color(options[:color])
274
+
275
+ # draw the context on our cairo layout
276
+ render_layout(layout, x, y, h, :auto_new_page => false)
277
+
278
+ # draw a border around the cell
279
+ # TODO: obey options[:border_width]
280
+ # TODO: obey options[:border_color]
281
+ line(x,y,x+w,y) if options[:border].include?("t")
282
+ line(x,y+h,x+w,y+h) if options[:border].include?("b")
283
+ line(x,y,x,y+h) if options[:border].include?("l")
284
+ line(x+w,y,x+w,y+h) if options[:border].include?("r")
285
+ end
286
+
287
+ # draws a basic table onto the page
288
+ # data - a 2d array with the data for the columns. The first row will be treated as the headings
289
+ #
290
+ # In addition to the standard text style options (see the documentation for text()), cell() supports
291
+ # the following options:
292
+ #
293
+ # <tt>:left</tt>:: The x co-ordinate of the left-hand side of the table. Defaults to the left margin
294
+ # <tt>:top</tt>:: The y co-ordinate of the top of the text. Defaults to the top margin
295
+ # <tt>:width</tt>:: The width of the table. Defaults to the width of the page body
296
+ def table(data, opts = {})
297
+ # TODO: instead of accepting the data, use a XHTML table string?
298
+ # TODO: handle overflowing to a new page
299
+ # TODO: add a way to display borders
300
+ # TODO: raise an error if any unrecognised options were supplied
301
+
302
+ x, y = current_point
303
+ options = default_text_options.merge!({:left => x,
304
+ :top => y
305
+ })
306
+ options.merge!(opts)
307
+ options[:width] = body_width - options[:left] unless options[:width]
308
+
309
+ # move to the start of our table (the top left)
310
+ x = options[:left]
311
+ y = options[:top]
312
+ move_to(x,y)
313
+
314
+ # all columns will have the same width at this stage
315
+ cell_width = options[:width] / data.first.size
316
+
317
+ # draw the header cells
318
+ y = draw_table_row(data.shift, cell_width, options)
319
+ x = options[:left]
320
+ move_to(x,y)
321
+
322
+ # draw the data cells
323
+ data.each do |row|
324
+ y = draw_table_row(row, cell_width, options)
325
+ x = options[:left]
326
+ move_to(x,y)
327
+ end
328
+ end
329
+
330
+ # Write text to the page
331
+ #
332
+ # By default the text will be rendered using all the space within the margins and using
333
+ # the default font styling set by default_font(), default_font_size, etc
334
+ #
335
+ # There is no way to place a bottom bound (or height) onto the text. Text will wrap as
336
+ # necessary and take all the room it needs. For finer grained control of text boxes, see the
337
+ # cell method.
338
+ #
339
+ # To override all these defaults, use the options hash
340
+ #
341
+ # Positioning Options:
342
+ #
343
+ # <tt>:left</tt>:: The x co-ordinate of the left-hand side of the text.
344
+ # <tt>:top</tt>:: The y co-ordinate of the top of the text.
345
+ # <tt>:width</tt>:: The width of the text to wrap at
346
+ #
347
+ # Text Style Options:
348
+ #
349
+ # <tt>:font</tt>:: The font family to use as a string
350
+ # <tt>:font_size</tt>:: The size of the font in points
351
+ # <tt>:alignment</tt>:: Align the text along the left, right or centre. Use :left, :right, :center
352
+ # <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
353
+ # <tt>:spacing</tt>:: Space between lines in PDF points
354
+ def text(str, opts={})
355
+ # TODO: add support for pango markup (see http://ruby-gnome2.sourceforge.jp/hiki.cgi?pango-markup)
356
+ # TODO: add a wrap option so wrapping can be disabled
357
+ # TODO: raise an error if any unrecognised options were supplied
358
+ #
359
+ # the non pango way to add text to the cairo context, not particularly useful for
360
+ # PDF generation as it doesn't support wrapping text or other advanced layout features
361
+ # and I really don't feel like re-implementing all that
362
+ # @context.show_text(str)
363
+
364
+ # the "pango way"
365
+ x, y = current_point
366
+ options = default_text_options.merge!({:left => x, :top => y})
367
+ options.merge!(opts)
368
+
369
+ # if the user hasn't specified a width, make the text wrap on the right margin
370
+ options[:width] = absolute_right_margin - options[:left] if options[:width].nil?
371
+
372
+ layout = build_pango_layout(str.to_s, options)
373
+
374
+ set_color(options[:color])
375
+
376
+ # draw the context on our cairo layout
377
+ y = render_layout(layout, options[:left], options[:top], points_to_bottom_margin(options[:top]), :auto_new_page => true)
378
+
379
+ move_to(options[:left], y + 5)
380
+ end
381
+
382
+ # Returns the amount of vertical space needed to display the supplied text at the requested width
383
+ # opts is an options hash that specifies various attributes of the text. See the text function for more information.
384
+ # TODO: raise an error if any unrecognised options were supplied
385
+ def text_height(str, width, opts = {})
386
+ options = default_text_options.merge!(opts)
387
+ options[:width] = width || body_width
388
+
389
+ layout = build_pango_layout(str.to_s, options)
390
+ width, height = layout.size
391
+
392
+ return height / Pango::SCALE
393
+ end
394
+
395
+ #####################################################
396
+ # Functions relating to working with graphics
397
+ #####################################################
398
+
399
+ # draw a circle with radius r and a centre point at (x,y).
400
+ # Parameters:
401
+ # <tt>:x</tt>:: The x co-ordinate of the circle centre.
402
+ # <tt>:y</tt>:: The y co-ordinate of the circle centre.
403
+ # <tt>:r</tt>:: The radius of the circle
404
+ #
405
+ # Options:
406
+ # <tt>:color</tt>:: The colour of the circle outline
407
+ # <tt>:fill_color</tt>:: The colour to fill the circle with. Defaults to nil (no fill)
408
+ def circle(x, y, r, opts = {})
409
+ # TODO: raise an error if any unrecognised options were supplied
410
+ options = {:color => @default_color,
411
+ :fill_color => nil
412
+ }
413
+ options.merge!(opts)
414
+
415
+ move_to(x + r, y)
416
+
417
+ # if the rectangle should be filled in
418
+ if options[:fill_color]
419
+ set_color(options[:fill_color])
420
+ @context.circle(x, y, r).fill
421
+ end
422
+
423
+ set_color(options[:color])
424
+ @context.circle(x, y, r).stroke
425
+ move_to(x + r, y + r)
426
+ end
427
+
428
+ # draw a line from x1,y1 to x2,y2
429
+ #
430
+ # Options:
431
+ # <tt>:color</tt>:: The colour of the line
432
+ def line(x0, y0, x1, y1, opts = {})
433
+ # TODO: raise an error if any unrecognised options were supplied
434
+ options = {:color => @default_color }
435
+ options.merge!(opts)
436
+
437
+ set_color(options[:color])
438
+
439
+ move_to(x0,y0)
440
+ @context.line_to(x1,y1).stroke
441
+ move_to(x1,y1)
442
+ end
443
+
444
+ # draw a rectangle starting at x,y with w,h dimensions.
445
+ # Parameters:
446
+ # <tt>:x</tt>:: The x co-ordinate of the top left of the rectangle.
447
+ # <tt>:y</tt>:: The y co-ordinate of the top left of the rectangle.
448
+ # <tt>:w</tt>:: The width of the rectangle
449
+ # <tt>:h</tt>:: The height of the rectangle
450
+ #
451
+ # Options:
452
+ # <tt>:color</tt>:: The colour of the rectangle outline
453
+ # <tt>:fill_color</tt>:: The colour to fill the rectangle with. Defaults to nil (no fill)
454
+ def rectangle(x, y, w, h, opts = {})
455
+ # TODO: raise an error if any unrecognised options were supplied
456
+ options = {:color => @default_color,
457
+ :fill_color => nil
458
+ }
459
+ options.merge!(opts)
460
+
461
+ # if the rectangle should be filled in
462
+ if options[:fill_color]
463
+ set_color(options[:fill_color])
464
+ @context.rectangle(x, y, w, h).fill
465
+ end
466
+
467
+ set_color(options[:color])
468
+ @context.rectangle(x, y, w, h).stroke
469
+
470
+ move_to(x+w, y+h)
471
+ end
472
+
473
+ # draw a rounded rectangle starting at x,y with w,h dimensions.
474
+ # Parameters:
475
+ # <tt>:x</tt>:: The x co-ordinate of the top left of the rectangle.
476
+ # <tt>:y</tt>:: The y co-ordinate of the top left of the rectangle.
477
+ # <tt>:w</tt>:: The width of the rectangle
478
+ # <tt>:h</tt>:: The height of the rectangle
479
+ # <tt>:r</tt>:: The size of the rounded corners
480
+ #
481
+ # Options:
482
+ # <tt>:color</tt>:: The colour of the rectangle outline
483
+ # <tt>:fill_color</tt>:: The colour to fill the rectangle with. Defaults to nil (no fill)
484
+ def rounded_rectangle(x, y, w, h, r, opts = {})
485
+ # TODO: raise an error if any unrecognised options were supplied
486
+ options = {:color => @default_color,
487
+ :fill_color => nil
488
+ }
489
+ options.merge!(opts)
490
+
491
+ raise ArgumentError, "Argument r must be less than both w and h arguments" if r >= w || r >= h
492
+
493
+ # if the rectangle should be filled in
494
+ if options[:fill_color]
495
+ set_color(options[:fill_color])
496
+ @context.rounded_rectangle(x, y, w, h, r).fill
497
+ end
498
+
499
+ set_color(options[:color])
500
+ @context.rounded_rectangle(x, y, w, h, r).stroke
501
+
502
+ move_to(x+w, y+h)
503
+ end
504
+
505
+ #####################################################
506
+ # Functions relating to working with images
507
+ #####################################################
508
+
509
+ # add an image to the page
510
+ # at this stage the file must be a PNG or SVG
511
+ # supported options:
512
+ # <tt>:left</tt>:: The x co-ordinate of the left-hand side of the image.
513
+ # <tt>:top</tt>:: The y co-ordinate of the top of the image.
514
+ # <tt>:height</tt>:: The height of the image
515
+ # <tt>:width</tt>:: The width of the image
516
+ #
517
+ # left and top default to the current cursor location
518
+ # width and height default to the size of the imported image
519
+ def image(filename, opts = {})
520
+ # TODO: maybe split this up into separate functions for each image type
521
+ # TODO: add some options for things like justification, scaling and padding
522
+ # TODO: png images currently can't be resized
523
+ # TODO: raise an error if any unrecognised options were supplied
524
+ raise ArgumentError, "file #{filename} not found" unless File.file?(filename)
525
+
526
+ filetype = detect_image_type(filename)
527
+
528
+ if filetype.eql?(:png)
529
+ img_surface = Cairo::ImageSurface.from_png(filename)
530
+ x, y = current_point
531
+ @context.set_source(img_surface, opts[:left] || x, opts[:top] || y)
532
+ @context.paint
533
+ elsif filetype.eql?(:svg)
534
+ # thanks to Nathan Stitt for help with this section
535
+ load_librsvg
536
+ @context.save
537
+
538
+ # import it
539
+ handle = RSVG::Handle.new_from_file(filename)
540
+
541
+ # size the SVG
542
+ if opts[:height] && opts[:width]
543
+ handle.set_size_callback do |h,w|
544
+ [ opts[:width], opts[:height] ]
545
+ end
546
+ end
547
+
548
+ # place the image on our main context
549
+ x, y = current_point
550
+ @context.translate( opts[:left] || x, opts[:top] || y )
551
+ @context.render_rsvg_handle(handle)
552
+ @context.restore
553
+ else
554
+ raise ArgumentError, "Unrecognised image format"
555
+ end
556
+ end
557
+
558
+ #####################################################
559
+ # Functions relating to generating the final document
560
+ #####################################################
561
+
562
+ # render the PDF and return it as a string
563
+ def render
564
+ # finalise the document, then convert the StringIO object it was rendered to
565
+ # into a string
566
+ @context.show_page
567
+ @context.target.finish
568
+ return @output.string
569
+ end
570
+
571
+ # save the rendered PDF to a file
572
+ def render_to_file(filename)
573
+ # finalise the document
574
+ @context.show_page
575
+ @context.target.finish
576
+
577
+ # write each line from the StringIO object it was rendered to into the
578
+ # requested file
579
+ File.open(filename, "w") do |of|
580
+ @output.rewind
581
+ @output.each_line { |line| of.write(line) }
582
+ end
583
+ end
584
+
585
+ #####################################################
586
+ # Misc Functions
587
+ #####################################################
588
+
589
+ # move the cursor to an arbitary position on the current page
590
+ def move_to(x,y)
591
+ raise ArgumentError, 'x cannot be larger than the width of the page' if x > page_width
592
+ raise ArgumentError, 'y cannot be larger than the height of the page' if y > page_height
593
+ @context.move_to(x,y)
594
+ end
595
+
596
+ # reset the cursor by moving it to the top left of the useable section of the page
597
+ def reset_cursor
598
+ @context.move_to(margin_left,margin_top)
599
+ end
600
+
601
+ # move to the next page
602
+ def start_new_page
603
+ @context.show_page
604
+ reset_cursor
605
+ end
606
+
607
+ private
608
+
609
+ def build_pango_layout(str, opts = {})
610
+ options = {:left => @margin_left,
611
+ :top => @margin_top,
612
+ :font => @default_font,
613
+ :font_size => @default_font_size,
614
+ :color => @default_color,
615
+ :alignment => :left,
616
+ :justify => false,
617
+ :spacing => 0
618
+ }
619
+ options.merge!(opts)
620
+
621
+ # if the user hasn't specified a width, make the text wrap on the right margin
622
+ options[:width] = absolute_right_margin - options[:left] if options[:width].nil?
623
+
624
+ # even though this is a private function, raise this error to force calling functions
625
+ # to decide how they want to handle converting non-strings into strings for rendering
626
+ raise ArgumentError, 'build_pango_layout must be passed a string' unless str.kind_of?(String)
627
+
628
+ # if we're running under a M17n aware VM, ensure the string provided is UTF-8
629
+ if RUBY_VERSION >= "1.9"
630
+ begin
631
+ str = str.encode("UTF-8")
632
+ rescue
633
+ raise ArgumentError, 'Strings must be supplied with a UTF-8 encoding, or an encoding that can be converted to UTF-8'
634
+ end
635
+ end
636
+
637
+ # The pango way:
638
+ load_libpango
639
+
640
+ # create a new Pango layout that our text will be added to
641
+ layout = @context.create_pango_layout
642
+ layout.text = str.to_s
643
+ layout.width = options[:width] * Pango::SCALE
644
+ layout.spacing = options[:spacing] * Pango::SCALE
645
+
646
+ # set the alignment of the text in the layout
647
+ if options[:alignment].eql?(:left)
648
+ layout.alignment = Pango::Layout::ALIGN_LEFT
649
+ elsif options[:alignment].eql?(:right)
650
+ layout.alignment = Pango::Layout::ALIGN_RIGHT
651
+ elsif options[:alignment].eql?(:center) || options[:alignment].eql?(:centre)
652
+ layout.alignment = Pango::Layout::ALIGN_CENTER
653
+ else
654
+ raise ArgumentError, "Invalid alignment requested"
655
+ end
656
+
657
+ # justify the text if need be - only works in pango >= 1.17
658
+ layout.justify = true if options[:justify]
659
+
660
+ # setup the font that will be used to render the text
661
+ fdesc = Pango::FontDescription.new(options[:font])
662
+ fdesc.set_size(options[:font_size] * Pango::SCALE)
663
+ layout.font_description = fdesc
664
+ @context.update_pango_layout(layout)
665
+ return layout
666
+ end
667
+
668
+ def default_text_options
669
+ { :font => @default_font,
670
+ :font_size => @default_font_size,
671
+ :color => @default_color,
672
+ :alignment => :left,
673
+ :justify => false,
674
+ :spacing => 0
675
+ }
676
+ end
677
+
678
+ def detect_image_type(filename)
679
+
680
+ # read the first Kb from the file to attempt file type detection
681
+ f = File.new(filename)
682
+ bytes = f.read(1024)
683
+
684
+ # if the file is a PNG
685
+ if bytes[1,3].eql?("PNG")
686
+ return :png
687
+ elsif bytes.include?("<svg")
688
+ return :svg
689
+ else
690
+ return nil
691
+ end
692
+ end
693
+
694
+ # adds a single table row to the canvas. Top left of the row will be at the current x,y
695
+ # co-ordinates, so make sure they're set correctly before calling this function
696
+ #
697
+ # strings - array of strings. Each element of the array is a cell
698
+ # column_widths - the width of each column. At this stage it should be an int. All columns are the same width
699
+ # options - any options relating to text style to use. font, font_size, alignment, etc. See text() for more info.
700
+ #
701
+ # Returns the y co-ordinates of the bottom edge of the row, ready for the next row
702
+ def draw_table_row(strings, column_widths, options)
703
+ row_height = 0
704
+ x, y = current_point
705
+
706
+ # we run all this code twice. The first time is a dry run to calculate the
707
+ # height of the largest cell, which determines the overall height of the row.
708
+ # The second run through we actually draw each cell onto the canvas
709
+ [:dry, :paint].each do |action|
710
+
711
+ strings.each do |head|
712
+ # TODO: provide a way for these to be overridden on a per cell basis
713
+ opts = {
714
+ :font => options[:font],
715
+ :font_size => options[:font_size],
716
+ :color => options[:color],
717
+ :alignment => options[:alignment],
718
+ :justify => options[:justify],
719
+ :spacing => options[:spacing]
720
+ }
721
+
722
+ if action == :dry
723
+ # calc the cell height, and set row_height if this cell is the biggest in the row
724
+ cell_height = text_height(head, column_widths, opts)
725
+ row_height = cell_height if cell_height > row_height
726
+ else
727
+ # start a new page if necesary
728
+ if row_height > (absolute_bottom_margin - y)
729
+ start_new_page
730
+ y = margin_top
731
+ end
732
+
733
+ # add our cell, then advance x to the left edge of the next cell
734
+ self.cell(head, x, y, column_widths, row_height, opts)
735
+ x += column_widths
736
+ end
737
+
738
+ end
739
+ end
740
+
741
+ return y + row_height
742
+ end
743
+
744
+ # load libpango if it isn't already loaded.
745
+ # This will add some methods to the cairo Context class in addition to providing
746
+ # its own classes and constants. A small amount of documentation is available at
747
+ # http://ruby-gnome2.sourceforge.jp/fr/hiki.cgi?Cairo%3A%3AContext#Pango+related+APIs
748
+ def load_libpango
749
+ begin
750
+ require 'pango' unless ::Object.const_defined?(:Pango)
751
+ rescue LoadError
752
+ raise LoadError, 'Ruby/Pango library not found. Visit http://ruby-gnome2.sourceforge.jp/'
753
+ end
754
+ end
755
+
756
+ # load librsvg if it isn't already loaded
757
+ # This will add an additional method to the Cairo::Context class
758
+ # that allows an existing SVG to be drawn directly onto it
759
+ # There's a *little* bit of documentation at:
760
+ # http://ruby-gnome2.sourceforge.jp/fr/hiki.cgi?Cairo%3A%3AContext#render_rsvg_handle
761
+ def load_librsvg
762
+ begin
763
+ require 'rsvg2' unless ::Object.const_defined?(:RSVG)
764
+ rescue LoadError
765
+ raise LoadError, 'Ruby/RSVG library not found. Visit http://ruby-gnome2.sourceforge.jp/'
766
+ end
767
+ end
768
+
769
+ # renders a pango layout onto our main context
770
+ # based on a function of the same name found in the text2.rb sample file
771
+ # distributed with rcairo - it's still black magic to me and has a few edge
772
+ # cases where it doesn't work too well. Needs to be improved.
773
+ def render_layout(layout, x, y, h, opts = {})
774
+ options = {:auto_new_page => true }
775
+ options.merge!(opts)
776
+
777
+ limit_y = y + h
778
+
779
+ iter = layout.iter
780
+ prev_baseline = iter.baseline / Pango::SCALE
781
+ begin
782
+ line = iter.line
783
+ ink_rect, logical_rect = iter.line_extents
784
+ y_begin, y_end = iter.line_yrange
785
+ if limit_y < (y + y_end / Pango::SCALE)
786
+ if options[:auto_new_page]
787
+ start_new_page
788
+ y = margin_top - prev_baseline
789
+ else
790
+ break
791
+ end
792
+ end
793
+ width, height = layout.size
794
+ baseline = iter.baseline / Pango::SCALE
795
+ move_to(x + logical_rect.x / Pango::SCALE, y + baseline)
796
+ @context.show_pango_layout_line(line)
797
+ prev_baseline = baseline
798
+ end while iter.next_line!
799
+ return y + baseline
800
+ end
801
+
802
+ # set the current drawing colour
803
+ #
804
+ # for info on what is valid, see the comments for default_color
805
+ def set_color(c)
806
+ # catch and reraise an exception to keep stack traces readable and clear
807
+ validate_color(c)
808
+
809
+ if c.kind_of?(Array)
810
+ @context.set_source_color(*c)
811
+ else
812
+ @context.set_source_color(c)
813
+ end
814
+ end
815
+
816
+ # test to see if the specified colour is a a valid cairo color
817
+ #
818
+ # for info on what is valid, see the comments for default_color
819
+ def validate_color(c)
820
+ @context.save
821
+ begin
822
+ if c.kind_of?(Array)
823
+ # if the colour is being specified manually, there must be 3 or 4 elements
824
+ raise ArgumentError if c.size != 3 && c.size != 4
825
+ @context.set_source_color(c)
826
+ else
827
+ @context.set_source_color(c)
828
+ end
829
+ @default_color = c
830
+ rescue ArgumentError
831
+ c.kind_of?(Array) ? str = "[#{c.join(",")}]" : str = c.to_s
832
+ raise ArgumentError, "#{str} is not a valid color definition"
833
+ ensure
834
+ @context.restore
835
+ end
836
+ return true
837
+ end
838
+ end
839
+ end
metadata ADDED
@@ -0,0 +1,73 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pdf-wrapper
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - James Healy
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-01-09 00:00:00 +11:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: A PDF writing library that uses the cairo and pango libraries to do the heavy lifting.
17
+ email: jimmy@deefa.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README
24
+ - DESIGN
25
+ - CHANGELOG
26
+ files:
27
+ - examples/google.png
28
+ - examples/cell.rb
29
+ - examples/image.rb
30
+ - examples/table.rb
31
+ - examples/shapes.rb
32
+ - examples/shapes.pdf
33
+ - examples/image.pdf
34
+ - examples/utf8.rb
35
+ - lib/pdf
36
+ - lib/pdf/wrapper.rb
37
+ - lib/pdf/core.rb
38
+ - Rakefile
39
+ - README
40
+ - DESIGN
41
+ - CHANGELOG
42
+ has_rdoc: true
43
+ homepage:
44
+ post_install_message:
45
+ rdoc_options:
46
+ - --title
47
+ - PDF::Wrapper Documentation
48
+ - --main
49
+ - README
50
+ - -q
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: "0"
58
+ version:
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: "0"
64
+ version:
65
+ requirements: []
66
+
67
+ rubyforge_project: pdf-wrapper
68
+ rubygems_version: 1.0.1
69
+ signing_key:
70
+ specification_version: 2
71
+ summary: A PDF generating library built on top of cairo
72
+ test_files: []
73
+