mack-pdf_writer 0.8.1 → 0.8.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. data/lib/gems/archive-tar-minitar-0.5.2/bin/minitar +27 -0
  2. data/lib/gems/archive-tar-minitar-0.5.2/lib/archive/tar/minitar/command.rb +814 -0
  3. data/lib/gems/archive-tar-minitar-0.5.2/lib/archive/tar/minitar.rb +979 -0
  4. data/lib/gems/color-1.4.0/lib/color/cmyk.rb +281 -0
  5. data/lib/gems/color-1.4.0/lib/color/css.rb +30 -0
  6. data/lib/gems/color-1.4.0/lib/color/grayscale.rb +214 -0
  7. data/lib/gems/color-1.4.0/lib/color/hsl.rb +223 -0
  8. data/lib/gems/color-1.4.0/lib/color/palette/adobecolor.rb +274 -0
  9. data/lib/gems/color-1.4.0/lib/color/palette/gimp.rb +118 -0
  10. data/lib/gems/color-1.4.0/lib/color/palette/monocontrast.rb +182 -0
  11. data/lib/gems/color-1.4.0/lib/color/palette.rb +18 -0
  12. data/lib/gems/color-1.4.0/lib/color/rgb/metallic.rb +45 -0
  13. data/lib/gems/color-1.4.0/lib/color/rgb-colors.rb +357 -0
  14. data/lib/gems/color-1.4.0/lib/color/rgb.rb +455 -0
  15. data/lib/gems/color-1.4.0/lib/color/yiq.rb +86 -0
  16. data/lib/gems/color-1.4.0/lib/color.rb +147 -0
  17. data/lib/gems/pdf-writer-1.1.8/bin/techbook +24 -0
  18. data/lib/gems/pdf-writer-1.1.8/lib/pdf/charts/stddev.rb +430 -0
  19. data/lib/gems/pdf-writer-1.1.8/lib/pdf/charts.rb +13 -0
  20. data/lib/gems/pdf-writer-1.1.8/lib/pdf/math.rb +108 -0
  21. data/lib/gems/pdf-writer-1.1.8/lib/pdf/quickref.rb +332 -0
  22. data/lib/gems/pdf-writer-1.1.8/lib/pdf/simpletable.rb +947 -0
  23. data/lib/gems/pdf-writer-1.1.8/lib/pdf/techbook.rb +901 -0
  24. data/lib/gems/pdf-writer-1.1.8/lib/pdf/writer/arc4.rb +63 -0
  25. data/lib/gems/pdf-writer-1.1.8/lib/pdf/writer/fontmetrics.rb +203 -0
  26. data/lib/gems/pdf-writer-1.1.8/lib/pdf/writer/fonts/Courier-Bold.afm +342 -0
  27. data/lib/gems/pdf-writer-1.1.8/lib/pdf/writer/fonts/Courier-BoldOblique.afm +342 -0
  28. data/lib/gems/pdf-writer-1.1.8/lib/pdf/writer/fonts/Courier-Oblique.afm +342 -0
  29. data/lib/gems/pdf-writer-1.1.8/lib/pdf/writer/fonts/Courier.afm +342 -0
  30. data/lib/gems/pdf-writer-1.1.8/lib/pdf/writer/fonts/Helvetica-Bold.afm +2827 -0
  31. data/lib/gems/pdf-writer-1.1.8/lib/pdf/writer/fonts/Helvetica-BoldOblique.afm +2827 -0
  32. data/lib/gems/pdf-writer-1.1.8/lib/pdf/writer/fonts/Helvetica-Oblique.afm +3051 -0
  33. data/lib/gems/pdf-writer-1.1.8/lib/pdf/writer/fonts/Helvetica.afm +3051 -0
  34. data/lib/gems/pdf-writer-1.1.8/lib/pdf/writer/fonts/MustRead.html +19 -0
  35. data/lib/gems/pdf-writer-1.1.8/lib/pdf/writer/fonts/Symbol.afm +213 -0
  36. data/lib/gems/pdf-writer-1.1.8/lib/pdf/writer/fonts/Times-Bold.afm +2588 -0
  37. data/lib/gems/pdf-writer-1.1.8/lib/pdf/writer/fonts/Times-BoldItalic.afm +2384 -0
  38. data/lib/gems/pdf-writer-1.1.8/lib/pdf/writer/fonts/Times-Italic.afm +2667 -0
  39. data/lib/gems/pdf-writer-1.1.8/lib/pdf/writer/fonts/Times-Roman.afm +2419 -0
  40. data/lib/gems/pdf-writer-1.1.8/lib/pdf/writer/fonts/ZapfDingbats.afm +225 -0
  41. data/lib/gems/pdf-writer-1.1.8/lib/pdf/writer/graphics/imageinfo.rb +365 -0
  42. data/lib/gems/pdf-writer-1.1.8/lib/pdf/writer/graphics.rb +813 -0
  43. data/lib/gems/pdf-writer-1.1.8/lib/pdf/writer/lang/en.rb +99 -0
  44. data/lib/gems/pdf-writer-1.1.8/lib/pdf/writer/lang.rb +43 -0
  45. data/lib/gems/pdf-writer-1.1.8/lib/pdf/writer/object/action.rb +35 -0
  46. data/lib/gems/pdf-writer-1.1.8/lib/pdf/writer/object/annotation.rb +42 -0
  47. data/lib/gems/pdf-writer-1.1.8/lib/pdf/writer/object/catalog.rb +39 -0
  48. data/lib/gems/pdf-writer-1.1.8/lib/pdf/writer/object/contents.rb +65 -0
  49. data/lib/gems/pdf-writer-1.1.8/lib/pdf/writer/object/destination.rb +40 -0
  50. data/lib/gems/pdf-writer-1.1.8/lib/pdf/writer/object/encryption.rb +53 -0
  51. data/lib/gems/pdf-writer-1.1.8/lib/pdf/writer/object/font.rb +72 -0
  52. data/lib/gems/pdf-writer-1.1.8/lib/pdf/writer/object/fontdescriptor.rb +34 -0
  53. data/lib/gems/pdf-writer-1.1.8/lib/pdf/writer/object/fontencoding.rb +40 -0
  54. data/lib/gems/pdf-writer-1.1.8/lib/pdf/writer/object/image.rb +304 -0
  55. data/lib/gems/pdf-writer-1.1.8/lib/pdf/writer/object/info.rb +51 -0
  56. data/lib/gems/pdf-writer-1.1.8/lib/pdf/writer/object/outline.rb +30 -0
  57. data/lib/gems/pdf-writer-1.1.8/lib/pdf/writer/object/outlines.rb +30 -0
  58. data/lib/gems/pdf-writer-1.1.8/lib/pdf/writer/object/page.rb +195 -0
  59. data/lib/gems/pdf-writer-1.1.8/lib/pdf/writer/object/pages.rb +115 -0
  60. data/lib/gems/pdf-writer-1.1.8/lib/pdf/writer/object/procset.rb +46 -0
  61. data/lib/gems/pdf-writer-1.1.8/lib/pdf/writer/object/viewerpreferences.rb +74 -0
  62. data/lib/gems/pdf-writer-1.1.8/lib/pdf/writer/object.rb +23 -0
  63. data/lib/gems/pdf-writer-1.1.8/lib/pdf/writer/ohash.rb +58 -0
  64. data/lib/gems/pdf-writer-1.1.8/lib/pdf/writer/oreader.rb +25 -0
  65. data/lib/gems/pdf-writer-1.1.8/lib/pdf/writer/state.rb +48 -0
  66. data/lib/gems/pdf-writer-1.1.8/lib/pdf/writer/strokestyle.rb +138 -0
  67. data/lib/gems/pdf-writer-1.1.8/lib/pdf/writer.rb +2729 -0
  68. data/lib/gems/transaction-simple-1.4.0/lib/transaction/simple/group.rb +146 -0
  69. data/lib/gems/transaction-simple-1.4.0/lib/transaction/simple/threadsafe/group.rb +36 -0
  70. data/lib/gems/transaction-simple-1.4.0/lib/transaction/simple/threadsafe.rb +68 -0
  71. data/lib/gems/transaction-simple-1.4.0/lib/transaction/simple.rb +486 -0
  72. data/lib/gems.rb +13 -0
  73. data/lib/mack-pdf_writer.rb +2 -0
  74. metadata +111 -16
@@ -0,0 +1,2729 @@
1
+ #--
2
+ # PDF::Writer for Ruby.
3
+ # http://rubyforge.org/projects/ruby-pdf/
4
+ # Copyright 2003 - 2005 Austin Ziegler.
5
+ #
6
+ # Licensed under a MIT-style licence. See LICENCE in the main distribution
7
+ # for full licensing information.
8
+ #
9
+ # $Id: writer.rb 202 2008-03-16 23:30:11Z sandal $
10
+ #++
11
+ require 'thread'
12
+ require 'open-uri'
13
+
14
+ require 'transaction/simple'
15
+ require 'color'
16
+
17
+ # A class to provide the core functionality to create a PDF document
18
+ # without any requirement for additional modules.
19
+ module PDF
20
+ class Writer
21
+ # The version of PDF::Writer.
22
+ VERSION = '1.1.8'
23
+
24
+ # Escape the text so that it's safe for insertion into the PDF
25
+ # document.
26
+ def self.escape(text)
27
+ text.gsub(/\\/, '\\\\\\\\').
28
+ gsub(/\(/, '\\(').
29
+ gsub(/\)/, '\\)').
30
+ gsub(/&lt;/, '<').
31
+ gsub(/&gt;/, '>').
32
+ gsub(/&amp;/, '&')
33
+ end
34
+ end
35
+ end
36
+
37
+ require 'pdf/math'
38
+ require 'pdf/writer/lang'
39
+ require 'pdf/writer/lang/en'
40
+
41
+ begin
42
+ require 'zlib'
43
+ PDF::Writer::Compression = true
44
+ rescue LoadError
45
+ warn PDF::Writer::Lang[:no_zlib_no_compress]
46
+ PDF::Writer::Compression = false
47
+ end
48
+
49
+ require 'pdf/writer/arc4'
50
+ require 'pdf/writer/fontmetrics'
51
+ require 'pdf/writer/object'
52
+ require 'pdf/writer/object/action'
53
+ require 'pdf/writer/object/annotation'
54
+ require 'pdf/writer/object/catalog'
55
+ require 'pdf/writer/object/contents'
56
+ require 'pdf/writer/object/destination'
57
+ require 'pdf/writer/object/encryption'
58
+ require 'pdf/writer/object/font'
59
+ require 'pdf/writer/object/fontdescriptor'
60
+ require 'pdf/writer/object/fontencoding'
61
+ require 'pdf/writer/object/image'
62
+ require 'pdf/writer/object/info'
63
+ require 'pdf/writer/object/outlines'
64
+ require 'pdf/writer/object/outline'
65
+ require 'pdf/writer/object/page'
66
+ require 'pdf/writer/object/pages'
67
+ require 'pdf/writer/object/procset'
68
+ require 'pdf/writer/object/viewerpreferences'
69
+
70
+ require 'pdf/writer/ohash'
71
+ require 'pdf/writer/strokestyle'
72
+ require 'pdf/writer/graphics'
73
+ require 'pdf/writer/graphics/imageinfo'
74
+ require 'pdf/writer/state'
75
+
76
+ class PDF::Writer
77
+ # The system font path. The sytem font path will be determined
78
+ # differently for each operating system.
79
+ #
80
+ # Win32:: Uses ENV['SystemRoot']/Fonts as the system font path. There is
81
+ # an extension that will handle this better, but until and
82
+ # unless it is distributed with the standard Ruby Windows
83
+ # installer, PDF::Writer will not depend upon it.
84
+ # OS X:: The fonts are found in /System/Library/Fonts.
85
+ # Linux:: The font path list will be found (usually) in
86
+ # /etc/fonts/fonts.conf or /usr/etc/fonts/fonts.conf. This XML
87
+ # file will be parsed (using REXML) to provide the value for
88
+ # FONT_PATH.
89
+ FONT_PATH = []
90
+
91
+ class << self
92
+ require 'rexml/document'
93
+ # Parse the fonts.conf XML file.
94
+ def parse_fonts_conf(filename)
95
+ doc = REXML::Document.new(File.open(filename, "rb")).root rescue nil
96
+
97
+ if doc
98
+ path = REXML::XPath.match(doc, '//dir').map do |el|
99
+ el.text.gsub($/, '')
100
+ end
101
+ doc = nil
102
+ else
103
+ path = []
104
+ end
105
+ path
106
+ end
107
+ private :parse_fonts_conf
108
+ end
109
+
110
+ case RUBY_PLATFORM
111
+ when /mswin32/o
112
+ # Windows font path. This is not the most reliable method.
113
+ FONT_PATH << File.join(ENV['SystemRoot'], 'Fonts')
114
+ when /darwin/o
115
+ # Macintosh font path.
116
+ FONT_PATH << '/System/Library/Fonts'
117
+ else
118
+ FONT_PATH.push(*parse_fonts_conf('/etc/fonts/fonts.conf'))
119
+ FONT_PATH.push(*parse_fonts_conf('//usr/etc/fonts/fonts.conf'))
120
+ end
121
+
122
+ FONT_PATH.uniq!
123
+
124
+ include PDF::Writer::Graphics
125
+
126
+ # Contains all of the PDF objects, ready for final assembly. This is of
127
+ # no interest to external consumers.
128
+ attr_reader :objects #:nodoc:
129
+
130
+ # The ARC4 encryption object. This is of no interest to external
131
+ # consumers.
132
+ attr_reader :arc4 #:nodoc:
133
+ # The string that will be used to encrypt this PDF document.
134
+ attr_accessor :encryption_key
135
+
136
+ # The number of PDF objects in the document
137
+ def size
138
+ @objects.size
139
+ end
140
+
141
+ # Generate an ID for a new PDF object.
142
+ def generate_id
143
+ @mutex.synchronize { @current_id += 1 }
144
+ end
145
+ private :generate_id
146
+
147
+ # Generate a new font ID.
148
+ def generate_font_id
149
+ @mutex.synchronize { @current_font_id += 1 }
150
+ end
151
+ private :generate_font_id
152
+
153
+ class << self
154
+ # Create the document with prepress options. Uses the same options as
155
+ # PDF::Writer.new (<tt>:paper</tt>, <tt>:orientation</tt>, and
156
+ # <tt>:version</tt>). It also supports the following options:
157
+ #
158
+ # <tt>:left_margin</tt>:: The left margin.
159
+ # <tt>:right_margin</tt>:: The right margin.
160
+ # <tt>:top_margin</tt>:: The top margin.
161
+ # <tt>:bottom_margin</tt>:: The bottom margin.
162
+ # <tt>:bleed_size</tt>:: The size of the bleed area in points.
163
+ # Default 12.
164
+ # <tt>:mark_length</tt>:: The length of the prepress marks in
165
+ # points. Default 18.
166
+ #
167
+ # The prepress marks are added to the loose objects and will appear on
168
+ # all pages.
169
+ def prepress(options = { })
170
+ pdf = self.new(options)
171
+
172
+ bleed_size = options[:bleed_size] || 12
173
+ mark_length = options[:mark_length] || 18
174
+
175
+ pdf.left_margin = options[:left_margin] if options[:left_margin]
176
+ pdf.right_margin = options[:right_margin] if options[:right_margin]
177
+ pdf.top_margin = options[:top_margin] if options[:top_margin]
178
+ pdf.bottom_margin = options[:bottom_margin] if options[:bottom_margin]
179
+
180
+ # This is in an "odd" order because the y-coordinate system in PDF
181
+ # is from bottom to top.
182
+ tx0 = pdf.pages.media_box[0] + pdf.left_margin
183
+ ty0 = pdf.pages.media_box[3] - pdf.top_margin
184
+ tx1 = pdf.pages.media_box[2] - pdf.right_margin
185
+ ty1 = pdf.pages.media_box[1] + pdf.bottom_margin
186
+
187
+ bx0 = tx0 - bleed_size
188
+ by0 = ty0 - bleed_size
189
+ bx1 = tx1 + bleed_size
190
+ by1 = ty1 + bleed_size
191
+
192
+ pdf.pages.trim_box = [ tx0, ty0, tx1, ty1 ]
193
+ pdf.pages.bleed_box = [ bx0, by0, bx1, by1 ]
194
+
195
+ all = pdf.open_object
196
+ pdf.save_state
197
+ kk = Color::CMYK.new(0, 0, 0, 100)
198
+ pdf.stroke_color! kk
199
+ pdf.fill_color! kk
200
+ pdf.stroke_style! StrokeStyle.new(0.3)
201
+
202
+ pdf.prepress_clip_mark(tx1, ty0, 0, mark_length, bleed_size) # Upper Right
203
+ pdf.prepress_clip_mark(tx0, ty0, 90, mark_length, bleed_size) # Upper Left
204
+ pdf.prepress_clip_mark(tx0, ty1, 180, mark_length, bleed_size) # Lower Left
205
+ pdf.prepress_clip_mark(tx1, ty1, -90, mark_length, bleed_size) # Lower Right
206
+
207
+ mid_x = pdf.pages.media_box[2] / 2.0
208
+ mid_y = pdf.pages.media_box[3] / 2.0
209
+
210
+ pdf.prepress_center_mark(mid_x, ty0, 0, mark_length, bleed_size) # Centre Top
211
+ pdf.prepress_center_mark(tx0, mid_y, 90, mark_length, bleed_size) # Centre Left
212
+ pdf.prepress_center_mark(mid_x, ty1, 180, mark_length, bleed_size) # Centre Bottom
213
+ pdf.prepress_center_mark(tx1, mid_y, -90, mark_length, bleed_size) # Centre Right
214
+
215
+ pdf.restore_state
216
+ pdf.close_object
217
+ pdf.add_object(all, :all)
218
+
219
+ yield pdf if block_given?
220
+
221
+ pdf
222
+ end
223
+
224
+ # Convert a measurement in centimetres to points, which are the
225
+ # default PDF userspace units.
226
+ def cm2pts(x)
227
+ (x / 2.54) * 72
228
+ end
229
+
230
+ # Convert a measurement in millimetres to points, which are the
231
+ # default PDF userspace units.
232
+ def mm2pts(x)
233
+ (x / 25.4) * 72
234
+ end
235
+
236
+ # Convert a measurement in inches to points, which are the default PDF
237
+ # userspace units.
238
+ def in2pts(x)
239
+ x * 72
240
+ end
241
+ end
242
+
243
+ # Convert a measurement in centimetres to points, which are the default
244
+ # PDF userspace units.
245
+ def cm2pts(x)
246
+ PDF::Writer.cm2pts(x)
247
+ end
248
+
249
+ # Convert a measurement in millimetres to points, which are the default
250
+ # PDF userspace units.
251
+ def mm2pts(x)
252
+ PDF::Writer.mm2pts(x)
253
+ end
254
+
255
+ # Convert a measurement in inches to points, which are the default PDF
256
+ # userspace units.
257
+ def in2pts(x)
258
+ PDF::Writer.in2pts(x)
259
+ end
260
+
261
+ # Standard page size names. One of these may be provided to
262
+ # PDF::Writer.new as the <tt>:paper</tt> parameter.
263
+ #
264
+ # Page sizes supported are:
265
+ #
266
+ # * 4A0, 2A0
267
+ # * A0, A1 A2, A3, A4, A5, A6, A7, A8, A9, A10
268
+ # * B0, B1, B2, B3, B4, B5, B6, B7, B8, B9, B10
269
+ # * C0, C1, C2, C3, C4, C5, C6, C7, C8, C9, C10
270
+ # * RA0, RA1, RA2, RA3, RA4
271
+ # * SRA0, SRA1, SRA2, SRA3, SRA4
272
+ # * LETTER
273
+ # * LEGAL
274
+ # * FOLIO
275
+ # * EXECUTIVE
276
+ PAGE_SIZES = { # :value {...}:
277
+ "4A0" => [0, 0, 4767.87, 6740.79], "2A0" => [0, 0, 3370.39, 4767.87],
278
+ "A0" => [0, 0, 2383.94, 3370.39], "A1" => [0, 0, 1683.78, 2383.94],
279
+ "A2" => [0, 0, 1190.55, 1683.78], "A3" => [0, 0, 841.89, 1190.55],
280
+ "A4" => [0, 0, 595.28, 841.89], "A5" => [0, 0, 419.53, 595.28],
281
+ "A6" => [0, 0, 297.64, 419.53], "A7" => [0, 0, 209.76, 297.64],
282
+ "A8" => [0, 0, 147.40, 209.76], "A9" => [0, 0, 104.88, 147.40],
283
+ "A10" => [0, 0, 73.70, 104.88], "B0" => [0, 0, 2834.65, 4008.19],
284
+ "B1" => [0, 0, 2004.09, 2834.65], "B2" => [0, 0, 1417.32, 2004.09],
285
+ "B3" => [0, 0, 1000.63, 1417.32], "B4" => [0, 0, 708.66, 1000.63],
286
+ "B5" => [0, 0, 498.90, 708.66], "B6" => [0, 0, 354.33, 498.90],
287
+ "B7" => [0, 0, 249.45, 354.33], "B8" => [0, 0, 175.75, 249.45],
288
+ "B9" => [0, 0, 124.72, 175.75], "B10" => [0, 0, 87.87, 124.72],
289
+ "C0" => [0, 0, 2599.37, 3676.54], "C1" => [0, 0, 1836.85, 2599.37],
290
+ "C2" => [0, 0, 1298.27, 1836.85], "C3" => [0, 0, 918.43, 1298.27],
291
+ "C4" => [0, 0, 649.13, 918.43], "C5" => [0, 0, 459.21, 649.13],
292
+ "C6" => [0, 0, 323.15, 459.21], "C7" => [0, 0, 229.61, 323.15],
293
+ "C8" => [0, 0, 161.57, 229.61], "C9" => [0, 0, 113.39, 161.57],
294
+ "C10" => [0, 0, 79.37, 113.39], "RA0" => [0, 0, 2437.80, 3458.27],
295
+ "RA1" => [0, 0, 1729.13, 2437.80], "RA2" => [0, 0, 1218.90, 1729.13],
296
+ "RA3" => [0, 0, 864.57, 1218.90], "RA4" => [0, 0, 609.45, 864.57],
297
+ "SRA0" => [0, 0, 2551.18, 3628.35], "SRA1" => [0, 0, 1814.17, 2551.18],
298
+ "SRA2" => [0, 0, 1275.59, 1814.17], "SRA3" => [0, 0, 907.09, 1275.59],
299
+ "SRA4" => [0, 0, 637.80, 907.09], "LETTER" => [0, 0, 612.00, 792.00],
300
+ "LEGAL" => [0, 0, 612.00, 1008.00], "FOLIO" => [0, 0, 612.00, 936.00],
301
+ "EXECUTIVE" => [0, 0, 521.86, 756.00]
302
+ }
303
+
304
+ # Creates a new PDF document as a writing canvas. It accepts three named
305
+ # parameters:
306
+ #
307
+ # <tt>:paper</tt>:: Specifies the size of the default page in
308
+ # PDF::Writer. This may be a four-element array
309
+ # of coordinates specifying the lower-left
310
+ # <tt>(xll, yll)</tt> and upper-right <tt>(xur,
311
+ # yur)</tt> corners, a two-element array of
312
+ # width and height in centimetres, or a page
313
+ # name as defined in PAGE_SIZES.
314
+ # <tt>:orientation</tt>:: The orientation of the page, either long
315
+ # (:portrait) or wide (:landscape). This may be
316
+ # used to swap the width and the height of the
317
+ # page.
318
+ # <tt>:version</tt>:: The feature set available to the document is
319
+ # limited by the PDF version. Setting this
320
+ # version restricts the feature set available to
321
+ # PDF::Writer. PDF::Writer currently supports
322
+ # PDF version 1.3 features and does not yet
323
+ # support advanced features from PDF 1.4, 1.5,
324
+ # or 1.6.
325
+ def initialize(options = {})
326
+ paper = options[:paper] || "LETTER"
327
+ orientation = options[:orientation] || :portrait
328
+ version = options[:version] || PDF_VERSION_13
329
+
330
+ @mutex = Mutex.new
331
+ @current_id = @current_font_id = 0
332
+
333
+ # Start the document
334
+ @objects = []
335
+ @callbacks = []
336
+ @font_families = {}
337
+ @fonts = {}
338
+ @stack = []
339
+ @state_stack = StateStack.new
340
+ @loose_objects = []
341
+ @current_text_state = ""
342
+ @options = {}
343
+ @destinations = {}
344
+ @add_loose_objects = {}
345
+ @images = []
346
+ @word_space_adjust = nil
347
+ @current_stroke_style = PDF::Writer::StrokeStyle.new(1)
348
+ @page_numbering = nil
349
+ @arc4 = nil
350
+ @encryption = nil
351
+ @file_identifier = nil
352
+
353
+ @columns = {}
354
+ @columns_on = false
355
+ @insert_mode = nil
356
+
357
+ @catalog = PDF::Writer::Object::Catalog.new(self)
358
+ @outlines = PDF::Writer::Object::Outlines.new(self)
359
+ @pages = PDF::Writer::Object::Pages.new(self)
360
+
361
+ @current_node = @pages
362
+ @procset = PDF::Writer::Object::Procset.new(self)
363
+ @info = PDF::Writer::Object::Info.new(self)
364
+ @page = PDF::Writer::Object::Page.new(self)
365
+ @current_text_render_style = 0
366
+ @first_page = @page
367
+
368
+ @version = version
369
+
370
+ # Initialize the default font families.
371
+ init_font_families
372
+
373
+ @font_size = 10
374
+ @pageset = [@pages.first_page]
375
+
376
+ if paper.kind_of?(Array)
377
+ if paper.size == 4
378
+ size = paper # Coordinate Array
379
+ else
380
+ size = [0, 0, PDF::Writer.cm2pts(paper[0]), PDF::Writer.cm2pts(paper[1])]
381
+ # Paper size in centimeters has been passed
382
+ end
383
+ else
384
+ size = PAGE_SIZES[paper.upcase].dup
385
+ end
386
+ size[3], size[2] = size[2], size[3] if orientation == :landscape
387
+
388
+ @pages.media_box = size
389
+
390
+ @page_width = size[2] - size[0]
391
+ @page_height = size[3] - size[1]
392
+ @y = @page_height
393
+
394
+ # Also set the margins to some reasonable defaults -- 1.27 cm, 36pt,
395
+ # or 0.5 inches.
396
+ margins_pt(36)
397
+
398
+ # Set the current writing position to the top of the first page
399
+ @y = absolute_top_margin
400
+ # Get the ID of the page that was created during the instantiation
401
+ # process.
402
+
403
+ fill_color! Color::RGB::Black
404
+ stroke_color! Color::RGB::Black
405
+
406
+ yield self if block_given?
407
+ end
408
+
409
+ PDF_VERSION_13 = '1.3'
410
+ PDF_VERSION_14 = '1.4'
411
+ PDF_VERSION_15 = '1.5'
412
+ PDF_VERSION_16 = '1.6'
413
+
414
+ # The version of PDF to which this document conforms. Should be one of
415
+ # PDF_VERSION_13, PDF_VERSION_14, PDF_VERSION_15, or PDF_VERSION_16.
416
+ attr_reader :version
417
+ # The document catalog object (PDF::Writer::Object::Catalog). The
418
+ # options in the catalog should be set with PDF::Writer#open_here,
419
+ # PDF::Writer#viewer_preferences, and PDF::Writer#page_mode.
420
+ #
421
+ # This is of little interest to external clients.
422
+ attr_accessor :catalog #:nodoc:
423
+ # The PDF::Writer::Object::Pages object. This is of little interest to
424
+ # external clients.
425
+ attr_accessor :pages #:nodoc:
426
+
427
+ # The PDF::Writer::Object::Procset object. This is of little interest to
428
+ # external clients.
429
+ attr_accessor :procset #:nodoc:
430
+ # Sets the document to compressed (+true+) or uncompressed (+false+).
431
+ # Defaults to uncompressed. This can ONLY be set once and should be set
432
+ # as early as possible in the document creation process.
433
+ attr_accessor :compressed
434
+ def compressed=(cc) #:nodoc:
435
+ @compressed = cc if @compressed.nil?
436
+ end
437
+ # Returns +true+ if the document is compressed.
438
+ def compressed?
439
+ @compressed == true
440
+ end
441
+ # The set of known labelled destinations. All destinations are of class
442
+ # PDF::Writer::Object::Destination. This is of little interest to
443
+ # external clients.
444
+ attr_reader :destinations #:nodoc:
445
+ # The PDF::Writer::Object::Info info object. This is used to provide
446
+ # certain metadata.
447
+ attr_reader :info
448
+ # The current page for writing. This is of little interest to external
449
+ # clients.
450
+ attr_accessor :current_page #:nodoc:
451
+ # Returns the current contents object to which raw PDF instructions may
452
+ # be written.
453
+ attr_reader :current_contents
454
+ # The PDF::Writer::Object::Outlines object. This is currently used very
455
+ # little. This is of little interest to external clients.
456
+ attr_reader :outlines #:nodoc:
457
+
458
+ # The complete set of page objects. This is of little interest to
459
+ # external consumers.
460
+ attr_reader :pageset #:nodoc:
461
+
462
+ attr_accessor :left_margin
463
+ attr_accessor :right_margin
464
+ attr_accessor :top_margin
465
+ attr_accessor :bottom_margin
466
+ attr_reader :page_width
467
+ attr_reader :page_height
468
+
469
+ # The absolute x position of the left margin.
470
+ attr_reader :absolute_left_margin
471
+ def absolute_left_margin #:nodoc:
472
+ @left_margin
473
+ end
474
+ # The absolute x position of the right margin.
475
+ attr_reader :absolute_right_margin
476
+ def absolute_right_margin #:nodoc:
477
+ @page_width - @right_margin
478
+ end
479
+ # Returns the absolute y position of the top margin.
480
+ attr_reader :absolute_top_margin
481
+ def absolute_top_margin #:nodoc:
482
+ @page_height - @top_margin
483
+ end
484
+ # Returns the absolute y position of the bottom margin.
485
+ attr_reader :absolute_bottom_margin
486
+ def absolute_bottom_margin #:nodoc:
487
+ @bottom_margin
488
+ end
489
+
490
+ # The height of the margin area.
491
+ attr_reader :margin_height
492
+ def margin_height #:nodoc:
493
+ absolute_top_margin - absolute_bottom_margin
494
+ end
495
+ # The width of the margin area.
496
+ attr_reader :margin_width
497
+ def margin_width #:nodoc:
498
+ absolute_right_margin - absolute_left_margin
499
+ end
500
+ # The absolute x middle position.
501
+ attr_reader :absolute_x_middle
502
+ def absolute_x_middle #:nodoc:
503
+ @page_width / 2.0
504
+ end
505
+ # The absolute y middle position.
506
+ attr_reader :absolute_y_middle
507
+ def absolute_y_middle #:nodoc:
508
+ @page_height / 2.0
509
+ end
510
+ # The middle of the writing area between the left and right margins.
511
+ attr_reader :margin_x_middle
512
+ def margin_x_middle #:nodoc:
513
+ (absolute_right_margin + absolute_left_margin) / 2.0
514
+ end
515
+ # The middle of the writing area between the top and bottom margins.
516
+ attr_reader :margin_y_middle
517
+ def margin_y_middle #:nodoc:
518
+ (absolute_top_margin + absolute_bottom_margin) / 2.0
519
+ end
520
+
521
+ # The vertical position of the writing point. The vertical position is
522
+ # constrained between the top and bottom margins. Any attempt to set it
523
+ # outside of those margins will cause the y pointer to be placed
524
+ # absolutely at the margins.
525
+ attr_accessor :y
526
+ def y=(yy) #:nodoc:
527
+ @y = yy
528
+ @y = absolute_top_margin if @y > absolute_top_margin
529
+ @y = @bottom_margin if @y < @bottom_margin
530
+ end
531
+
532
+ # The vertical position of the writing point. If the vertical position
533
+ # is outside of the bottom margin, a new page will be created.
534
+ attr_accessor :pointer
535
+ def pointer=(y) #:nodoc:
536
+ @y = y
537
+ start_new_page if @y < @bottom_margin
538
+ end
539
+
540
+ # Used to change the vertical position of the writing point. The pointer
541
+ # is moved *down* the page by +dy+ (that is, #y is reduced by +dy+), so
542
+ # if the pointer is to be moved up, a negative number must be used.
543
+ # Moving up the page will not move to the previous page because of
544
+ # limitations in the way that PDF::Writer works. The writing point will
545
+ # be limited to the top margin position.
546
+ #
547
+ # If +make_space+ is true and a new page is forced, then the pointer
548
+ # will be moved down on the new page. This will allow space to be
549
+ # reserved for graphics.
550
+ def move_pointer(dy, make_space = false)
551
+ @y -= dy
552
+ if @y < @bottom_margin
553
+ start_new_page
554
+ @y -= dy if make_space
555
+ elsif @y > absolute_top_margin
556
+ @y = absolute_top_margin
557
+ end
558
+ end
559
+
560
+ # Define the margins in millimetres.
561
+ def margins_mm(top, left = top, bottom = top, right = left)
562
+ margins_pt(mm2pts(top), mm2pts(left), mm2pts(bottom), mm2pts(right))
563
+ end
564
+
565
+ # Define the margins in centimetres.
566
+ def margins_cm(top, left = top, bottom = top, right = left)
567
+ margins_pt(cm2pts(top), cm2pts(left), cm2pts(bottom), cm2pts(right))
568
+ end
569
+
570
+ # Define the margins in inches.
571
+ def margins_in(top, left = top, bottom = top, right = left)
572
+ margins_pt(in2pts(top), in2pts(left), in2pts(bottom), in2pts(right))
573
+ end
574
+
575
+ # Define the margins in points. This will move the #y pointer
576
+ #
577
+ # # T L B R
578
+ # pdf.margins_pt(36) # 36 36 36 36
579
+ # pdf.margins_pt(36, 54) # 36 54 36 54
580
+ # pdf.margins_pt(36, 54, 72) # 36 54 72 54
581
+ # pdf.margins_pt(36, 54, 72, 90) # 36 54 72 90
582
+ def margins_pt(top, left = top, bottom = top, right = left)
583
+ # Set the margins to new values
584
+ @top_margin = top
585
+ @bottom_margin = bottom
586
+ @left_margin = left
587
+ @right_margin = right
588
+ # Check to see if this means that the current writing position is
589
+ # outside the writable area
590
+ if @y > (@page_height - top)
591
+ # Move y down
592
+ @y = @page_height - top
593
+ end
594
+
595
+ start_new_page if @y < bottom # Make a new page
596
+ end
597
+
598
+ # Allows the user to find out what the ID is of the first page that was
599
+ # created during startup - useful if they wish to add something to it
600
+ # later.
601
+ attr_reader :first_page
602
+
603
+ # Add a new translation table for a font family. A font family will be
604
+ # used to associate a single name and font styles with multiple fonts.
605
+ # A style will be identified with a single-character style identifier or
606
+ # a series of style identifiers. The only styles currently recognised
607
+ # are:
608
+ #
609
+ # +b+:: Bold (or heavy) fonts. Examples: Helvetica-Bold, Courier-Bold,
610
+ # Times-Bold.
611
+ # +i+:: Italic (or oblique) fonts. Examples: Helvetica-Oblique,
612
+ # Courier-Oblique, Times-Italic.
613
+ # +bi+:: Bold italic fonts. Examples Helvetica-BoldOblique,
614
+ # Courier-BoldOblique, Times-BoldItalic.
615
+ # +ib+:: Italic bold fonts. Generally defined the same as +bi+ font
616
+ # styles. Examples: Helvetica-BoldOblique, Courier-BoldOblique,
617
+ # Times-BoldItalic.
618
+ #
619
+ # Each font family key is the base name for the font.
620
+ attr_reader :font_families
621
+
622
+ # Initialize the font families for the default fonts.
623
+ def init_font_families
624
+ # Set the known family groups. These font families will be used to
625
+ # enable bold and italic markers to be included within text
626
+ # streams. HTML forms will be used... <b></b> <i></i>
627
+ @font_families["Helvetica"] =
628
+ {
629
+ "b" => 'Helvetica-Bold',
630
+ "i" => 'Helvetica-Oblique',
631
+ "bi" => 'Helvetica-BoldOblique',
632
+ "ib" => 'Helvetica-BoldOblique'
633
+ }
634
+ @font_families['Courier'] =
635
+ {
636
+ "b" => 'Courier-Bold',
637
+ "i" => 'Courier-Oblique',
638
+ "bi" => 'Courier-BoldOblique',
639
+ "ib" => 'Courier-BoldOblique'
640
+ }
641
+ @font_families['Times-Roman'] =
642
+ {
643
+ "b" => 'Times-Bold',
644
+ "i" => 'Times-Italic',
645
+ "bi" => 'Times-BoldItalic',
646
+ "ib" => 'Times-BoldItalic'
647
+ }
648
+ end
649
+ private :init_font_families
650
+
651
+ # Sets the trim box area.
652
+ def trim_box(x0, y0, x1, y1)
653
+ @pages.trim_box = [ x0, y0, x1, y1 ]
654
+ end
655
+
656
+ # Sets the bleed box area.
657
+ def bleed_box(x0, y0, x1, y1)
658
+ @pages.bleed_box = [ x0, y0, x1, y1 ]
659
+ end
660
+
661
+ # set the viewer preferences of the document, it is up to the browser to
662
+ # obey these.
663
+ def viewer_preferences(label, value = 0)
664
+ @catalog.viewer_preferences ||= PDF::Writer::Object::ViewerPreferences.new(self)
665
+
666
+ # This will only work if the label is one of the valid ones.
667
+ if label.kind_of?(Hash)
668
+ label.each { |kk, vv| @catalog.viewer_preferences.__send__("#{kk.downcase}=".intern, vv) }
669
+ else
670
+ @catalog.viewer_preferences.__send__("#{label.downcase}=".intern, value)
671
+ end
672
+ end
673
+
674
+ # Add a link in the document to an external URL.
675
+ def add_link(uri, x0, y0, x1, y1)
676
+ PDF::Writer::Object::Annotation.new(self, :link, [x0, y0, x1, y1], uri)
677
+ end
678
+
679
+ # Add a link in the document to an internal destination (ie. within the
680
+ # document)
681
+ def add_internal_link(label, x0, y0, x1, y1)
682
+ PDF::Writer::Object::Annotation.new(self, :ilink, [x0, y0, x1, y1], label)
683
+ end
684
+
685
+ # Add an outline item (Bookmark).
686
+ def add_outline_item(label, title = label)
687
+ PDF::Writer::Object::Outline.new(self, label, title)
688
+ end
689
+
690
+ # Standard encryption/DRM options.
691
+ ENCRYPT_OPTIONS = { #:nodoc:
692
+ :print => 4,
693
+ :modify => 8,
694
+ :copy => 16,
695
+ :add => 32
696
+ }
697
+
698
+ # should be used for internal checks, not implemented as yet
699
+ def check_all_here
700
+ end
701
+
702
+ # Return the PDF stream as a string.
703
+ def render(debug = false)
704
+ add_page_numbers
705
+ @compression = false if $DEBUG or debug
706
+ @arc4.init(@encryption_key) unless @arc4.nil?
707
+
708
+ check_all_here
709
+
710
+ xref = []
711
+
712
+ content = "%PDF-#{@version}\n%âãÏÓ\n"
713
+ pos = content.size
714
+
715
+ objects.each do |oo|
716
+ cont = oo.to_s
717
+ content << cont
718
+ xref << pos
719
+ pos += cont.size
720
+ end
721
+
722
+ # pos += 1 # Newline character before XREF
723
+
724
+ content << "\nxref\n0 #{xref.size + 1}\n0000000000 65535 f \n"
725
+ xref.each { |xx| content << "#{'%010d' % [xx]} 00000 n \n" }
726
+ content << "\ntrailer\n"
727
+ content << " << /Size #{xref.size + 1}\n"
728
+ content << " /Root 1 0 R\n /Info #{@info.oid} 0 R\n"
729
+ # If encryption has been applied to this document, then add the marker
730
+ # for this dictionary
731
+ if @arc4 and @encryption
732
+ content << "/Encrypt #{@encryption.oid} 0 R\n"
733
+ end
734
+
735
+ if @file_identifier
736
+ content << "/ID[<#{@file_identifier}><#{@file_identifier}>]\n"
737
+ end
738
+ content << " >>\nstartxref\n#{pos}\n%%EOF\n"
739
+ content
740
+ end
741
+ alias :to_s :render
742
+
743
+ # Loads the font metrics. This is now thread-safe.
744
+ def load_font_metrics(font)
745
+ metrics = PDF::Writer::FontMetrics.open(font)
746
+ @mutex.synchronize do
747
+ @fonts[font] = metrics
748
+ @fonts[font].font_num = @fonts.size
749
+ end
750
+ metrics
751
+ end
752
+ private :load_font_metrics
753
+
754
+ def find_font(fontname)
755
+ name = File.basename(fontname, ".afm")
756
+ @objects.detect do |oo|
757
+ oo.kind_of?(PDF::Writer::Object::Font) and /#{oo.basefont}$/ =~ name
758
+ end
759
+ end
760
+ private :find_font
761
+
762
+ def font_file(fontfile)
763
+ path = "#{fontfile}.pfb"
764
+ return path if File.exists?(path)
765
+ path = "#{fontfile}.ttf"
766
+ return path if File.exists?(path)
767
+ nil
768
+ end
769
+ private :font_file
770
+
771
+ def load_font(font, encoding = nil)
772
+ metrics = load_font_metrics(font)
773
+
774
+ name = File.basename(font).gsub(/\.afm$/o, "")
775
+
776
+ encoding_diff = nil
777
+ case encoding
778
+ when Hash
779
+ encoding_name = encoding[:encoding]
780
+ encoding_diff = encoding[:differences]
781
+ encoding = PDF::Writer::Object::FontEncoding.new(self, encoding_name, encoding_diff)
782
+ when NilClass
783
+ encoding_name = encoding = 'WinAnsiEncoding'
784
+ else
785
+ encoding_name = encoding
786
+ end
787
+
788
+ wfo = PDF::Writer::Object::Font.new(self, name, encoding)
789
+
790
+ # We have an Adobe Font Metrics (.afm) file. We need to find the
791
+ # associated Type1 (.pfb) or TrueType (.ttf) files (we do not yet
792
+ # support OpenType fonts); we need to load it into a
793
+ # PDF::Writer::Object and put the references into the metrics object.
794
+ base = metrics.path.sub(/\.afm$/o, "")
795
+ fontfile = font_file(base)
796
+ unless fontfile
797
+ base = File.basename(base)
798
+ FONT_PATH.each do |path|
799
+ fontfile = font_file(File.join(path, base))
800
+ break if fontfile
801
+ end
802
+ end
803
+
804
+ if font =~ /afm/o and fontfile
805
+ # Find the array of font widths, and put that into an object.
806
+ first_char = -1
807
+ last_char = 0
808
+
809
+ widths = {}
810
+ metrics.c.each_value do |details|
811
+ num = details["C"]
812
+
813
+ if num >= 0
814
+ # warn "Multiple definitions of #{num}" if widths.has_key?(num)
815
+ widths[num] = details['WX']
816
+ first_char = num if num < first_char or first_char < 0
817
+ last_char = num if num > last_char
818
+ end
819
+ end
820
+
821
+ # Adjust the widths for the differences array.
822
+ if encoding_diff
823
+ encoding_diff.each do |cnum, cname|
824
+ (cnum - last_char).times { widths << 0 } if cnum > last_char
825
+ last_char = cnum
826
+ widths[cnum - first_char] = metrics.c[cname]['WX'] if metrics.c[cname]
827
+ end
828
+ end
829
+
830
+ raise RuntimeError, 'Font metrics file (.afm) invalid - no charcters described' if first_char == -1 and last_char == 0
831
+
832
+ widthid = PDF::Writer::Object::Contents.new(self, :raw)
833
+ widthid << "["
834
+ (first_char .. last_char).each do |ii|
835
+ if widths.has_key?(ii)
836
+ widthid << " #{widths[ii].to_i}"
837
+ else
838
+ widthid << " 0"
839
+ end
840
+ end
841
+ widthid << "]"
842
+
843
+ # Load the pfb file, and put that into an object too. Note that PDF
844
+ # supports only binary format Type1 font files and TrueType font
845
+ # files. There is a simple utility to convert Type1 from pfa to pfb.
846
+ data = File.open(fontfile, "rb") { |ff| ff.read }
847
+
848
+ # Create the font descriptor.
849
+ fdsc = PDF::Writer::Object::FontDescriptor.new(self)
850
+ # Raw contents causes problems with Acrobat Reader.
851
+ pfbc = PDF::Writer::Object::Contents.new(self)
852
+
853
+ # Determine flags (more than a little flakey, hopefully will not
854
+ # matter much).
855
+ flags = 0
856
+ if encoding == "none"
857
+ flags += 2 ** 2
858
+ else
859
+ flags += 2 ** 6 if metrics.italicangle.nonzero?
860
+ flags += 2 ** 0 if metrics.isfixedpitch == "true"
861
+ flags += 2 ** 5 # Assume a non-symbolic font
862
+ end
863
+
864
+ # 1: FixedPitch: All glyphs have the same width (as opposed to
865
+ # proportional or variable-pitch fonts, which have
866
+ # different widths).
867
+ # 2: Serif: Glyphs have serifs, which are short strokes drawn
868
+ # at an angle on the top and bottom of glyph stems.
869
+ # (Sans serif fonts do not have serifs.)
870
+ # 3: Symbolic Font contains glyphs outside the Adobe standard
871
+ # Latin character set. This flag and the Nonsymbolic
872
+ # flag cannot both be set or both be clear (see
873
+ # below).
874
+ # 4: Script: Glyphs resemble cursive handwriting.
875
+ # 6: Nonsymbolic: Font uses the Adobe standard Latin character set
876
+ # or a subset of it (see below).
877
+ # 7: Italic: Glyphs have dominant vertical strokes that are
878
+ # slanted.
879
+ # 17: AllCap: Font contains no lowercase letters; typically used
880
+ # for display purposes, such as for titles or
881
+ # headlines.
882
+ # 18: SmallCap: Font contains both uppercase and lowercase
883
+ # letters. The uppercase letters are similar to
884
+ # those in the regular version of the same typeface
885
+ # family. The glyphs for the lowercase letters have
886
+ # the same shapes as the corresponding uppercase
887
+ # letters, but they are sized and their proportions
888
+ # adjusted so that they have the same size and
889
+ # stroke weight as lowercase glyphs in the same
890
+ # typeface family.
891
+ # 19: ForceBold: See below.
892
+
893
+ list = {
894
+ 'Ascent' => 'Ascender',
895
+ 'CapHeight' => 'CapHeight',
896
+ 'Descent' => 'Descender',
897
+ 'FontBBox' => 'FontBBox',
898
+ 'ItalicAngle' => 'ItalicAngle'
899
+ }
900
+ fdopt = {
901
+ 'Flags' => flags,
902
+ 'FontName' => metrics.fontname,
903
+ 'StemV' => 100 # Don't know what the value for this should be!
904
+ }
905
+
906
+ list.each do |kk, vv|
907
+ zz = metrics.__send__(vv.downcase.intern)
908
+ fdopt[kk] = zz if zz
909
+ end
910
+
911
+ # Determine the cruicial lengths within this file
912
+ if fontfile =~ /\.pfb$/o
913
+ fdopt['FontFile'] = pfbc.oid
914
+ i1 = data.index('eexec') + 6
915
+ i2 = data.index('00000000') - i1
916
+ i3 = data.size - i2 - i1
917
+ pfbc.add('Length1' => i1, 'Length2' => i2, 'Length3' => i3)
918
+ elsif fontfile =~ /\.ttf$/o
919
+ fdopt['FontFile2'] = pfbc.oid
920
+ pfbc.add('Length1' => data.size)
921
+ end
922
+
923
+ fdsc.options = fdopt
924
+ # Embed the font program
925
+ pfbc << data
926
+
927
+ # Tell the font object about all this new stuff
928
+ tmp = {
929
+ 'BaseFont' => metrics.fontname,
930
+ 'Widths' => widthid.oid,
931
+ 'FirstChar' => first_char,
932
+ 'LastChar' => last_char,
933
+ 'FontDescriptor' => fdsc.oid
934
+ }
935
+ tmp['SubType'] = 'TrueType' if fontfile =~ /\.ttf/
936
+
937
+ tmp.each { |kk, vv| wfo.__send__("#{kk.downcase}=".intern, vv) }
938
+ end
939
+
940
+ # Also set the differences here. Note that this means that these will
941
+ # take effect only the first time that a font is selected, else they
942
+ # are ignored.
943
+ metrics.differences = encoding_diff unless encoding_diff.nil?
944
+ metrics.encoding = encoding_name
945
+ metrics
946
+ end
947
+ private :load_font
948
+
949
+ # If the named +font+ is not loaded, then load it and make the required
950
+ # PDF objects to represent the font. If the font is already loaded, then
951
+ # make it the current font.
952
+ #
953
+ # The parameter +encoding+ applies only when the font is first being
954
+ # loaded; it may not be applied later. It may either be an encoding name
955
+ # or a hash. The Hash must contain two keys:
956
+ #
957
+ # <tt>:encoding</tt>:: The name of the encoding. Either *none*,
958
+ # *WinAnsiEncoding*, *MacRomanEncoding*, or
959
+ # *MacExpertEncoding*. For symbolic fonts, an
960
+ # encoding of *none* is recommended with a
961
+ # differences Hash.
962
+ # <tt>:differences</tt>:: This Hash value is a mapping between character
963
+ # byte values (0 .. 255) and character names
964
+ # from the AFM file for the font.
965
+ #
966
+ # The standard PDF encodings are detailed fully in the PDF Reference
967
+ # version 1.6, Appendix D.
968
+ #
969
+ # Note that WinAnsiEncoding is not the same as Windows code page 1252
970
+ # (roughly equivalent to latin-1), Most characters map, but not all. The
971
+ # encoding value currently defaults to WinAnsiEncoding.
972
+ #
973
+ # If the font's "natural" encoding is desired, then it is necessary to
974
+ # specify the +encoding+ parameter as <tt>{ :encoding => nil }</tt>.
975
+ def select_font(font, encoding = nil)
976
+ load_font(font, encoding) unless @fonts[font]
977
+
978
+ @current_base_font = font
979
+ current_font!
980
+ @current_base_font
981
+ end
982
+
983
+ # Selects the current font based on defined font families and the
984
+ # current text state. As noted in #font_families, a "bi" font can be
985
+ # defined differently than an "ib" font. It should not be possible to
986
+ # have a "bb" text state, but if one were to show up, an entry for the
987
+ # #font_families would have to be defined to select anything other than
988
+ # the default font. This function is to be called whenever the current
989
+ # text state is changed; it will update the current font to whatever the
990
+ # appropriate font defined in the font family.
991
+ #
992
+ # When the user calls #select_font, both the current base font and the
993
+ # current font will be reset; this function only changes the current
994
+ # font, not the current base font.
995
+ #
996
+ # This will probably not be needed by end users.
997
+ def current_font!
998
+ select_font("Helvetica") unless @current_base_font
999
+
1000
+ font = File.basename(@current_base_font)
1001
+ if @font_families[font] and @font_families[font][@current_text_state]
1002
+ # Then we are in some state or another and this font has a family,
1003
+ # and the current setting exists within it select the font, then
1004
+ # return it.
1005
+ if File.dirname(@current_base_font) != '.'
1006
+ nf = File.join(File.dirname(@current_base_font), @font_families[font][@current_text_state])
1007
+ else
1008
+ nf = @font_families[font][@current_text_state]
1009
+ end
1010
+
1011
+ unless @fonts[nf]
1012
+ enc = {
1013
+ :encoding => @fonts[font].encoding,
1014
+ :differences => @fonts[font].differences
1015
+ }
1016
+ load_font(nf, enc)
1017
+ end
1018
+ @current_font = nf
1019
+ else
1020
+ @current_font = @current_base_font
1021
+ end
1022
+ end
1023
+
1024
+ attr_reader :current_font
1025
+ attr_reader :current_base_font
1026
+ attr_accessor :font_size
1027
+
1028
+ # add content to the currently active object
1029
+ def add_content(cc)
1030
+ @current_contents << cc
1031
+ end
1032
+
1033
+ # Return the height in units of the current font in the given size. Uses
1034
+ # the current #font_size if size is not provided.
1035
+ def font_height(size = nil)
1036
+ size = @font_size if size.nil? or size <= 0
1037
+
1038
+ select_font("Helvetica") if @fonts.empty?
1039
+ hh = @fonts[@current_font].fontbbox[3].to_f - @fonts[@current_font].fontbbox[1].to_f
1040
+ (size * hh / 1000.0)
1041
+ end
1042
+
1043
+ # Return the font descender, this will normally return a negative
1044
+ # number. If you add this number to the baseline, you get the level of
1045
+ # the bottom of the font it is in the PDF user units. Uses the current
1046
+ # #font_size if size is not provided.
1047
+ def font_descender(size = nil)
1048
+ size = @font_size if size.nil? or size <= 0
1049
+
1050
+ select_font("Helvetica") if @fonts.empty?
1051
+ hi = @fonts[@current_font].fontbbox[1].to_f
1052
+ (size * hi / 1000.0)
1053
+ end
1054
+
1055
+ # Given a start position and information about how text is to be laid
1056
+ # out, calculate where on the page the text will end.
1057
+ def text_end_position(x, y, angle, size, wa, text)
1058
+ width = text_width(text, size)
1059
+ width += wa * (text.count(" "))
1060
+ rad = PDF::Math.deg2rad(angle)
1061
+ [Math.cos(rad) * width + x, ((-Math.sin(rad)) * width + y)]
1062
+ end
1063
+ private :text_end_position
1064
+
1065
+ # Wrapper function for #text_tags
1066
+ def quick_text_tags(text, ii, font_change)
1067
+ ret = text_tags(text, ii, font_change)
1068
+ [ret[0], ret[1], ret[2]]
1069
+ end
1070
+ private :quick_text_tags
1071
+
1072
+ # Matches tags.
1073
+ MATCH_TAG_REPLACE_RE = %r{^r:(\w+)(?: (.*?))? */} #:nodoc:
1074
+ MATCH_TAG_DRAW_ONE_RE = %r{^C:(\w+)(?: (.*?))? */} #:nodoc:
1075
+ MATCH_TAG_DRAW_PAIR_RE = %r{^c:(\w+)(?: (.*))? *} #:nodoc:
1076
+
1077
+ # Checks if +text+ contains a control tag at +pos+. Control tags are
1078
+ # XML-like tags that contain tag information.
1079
+ #
1080
+ # === Supported Tag Formats
1081
+ # <tt>&lt;b></tt>:: Adds +b+ to the end of the current
1082
+ # text state. If this is the closing
1083
+ # tag, <tt>&lt;/b></tt>, +b+ is removed
1084
+ # from the end of the current text
1085
+ # state.
1086
+ # <tt>&lt;i></tt>:: Adds +i+ to the end of the current
1087
+ # text state. If this is the closing
1088
+ # tag, <tt>&lt;/i</tt>, +i+ is removed
1089
+ # from the end of the current text
1090
+ # state.
1091
+ # <tt>&lt;r:TAG[ PARAMS]/></tt>:: Calls a stand-alone replace callback
1092
+ # method of the form tag_TAG_replace.
1093
+ # PARAMS must be separated from the TAG
1094
+ # name by a single space. The PARAMS, if
1095
+ # present, are passed to the replace
1096
+ # callback unmodified, whose
1097
+ # responsibility it is to interpret the
1098
+ # parameters. The replace callback is
1099
+ # expected to return text that will be
1100
+ # used in the place of the tag.
1101
+ # #text_tags is called again immediately
1102
+ # so that if the replacement text has
1103
+ # tags, they will be dealt with
1104
+ # properly.
1105
+ # <tt>&lt;C:TAG[ PARAMS]/></tt>:: Calls a stand-alone drawing callback
1106
+ # method. The method will be provided an
1107
+ # information hash (see below for the
1108
+ # data provided). It is expected to use
1109
+ # this information to perform whatever
1110
+ # drawing tasks are needed to perform
1111
+ # its task.
1112
+ # <tt>&lt;c:TAG[ PARAMS]></tt>:: Calls a paired drawing callback
1113
+ # method. The method will be provided an
1114
+ # information hash (see below for the
1115
+ # data provided). It is expected to use
1116
+ # this information to perform whatever
1117
+ # drawing tasks are needed to perform
1118
+ # its task. It must have a corresponding
1119
+ # &lt;/c:TAG> closing tag. Paired
1120
+ # callback behaviours will be preserved
1121
+ # over page breaks and line changes.
1122
+ #
1123
+ # Drawing callback tags will be provided an information hash that tells
1124
+ # the callback method where it must perform its drawing tasks.
1125
+ #
1126
+ # === Drawing Callback Parameters
1127
+ # <tt>:x</tt>:: The current X position of the text.
1128
+ # <tt>:y</tt>:: The current y position of the text.
1129
+ # <tt>:angle</tt>:: The current text drawing angle.
1130
+ # <tt>:params</tt>:: Any parameters that may be important to the
1131
+ # callback. This value is only guaranteed to have
1132
+ # meaning when a stand-alone callback is made or the
1133
+ # opening tag is processed.
1134
+ # <tt>:status</tt>:: :start, :end, :start_line, :end_line
1135
+ # <tt>:cbid</tt>:: The identifier of this callback. This may be
1136
+ # used as a key into a different variable where
1137
+ # state may be kept.
1138
+ # <tt>:callback</tt>:: The name of the callback function. Only set for
1139
+ # stand-alone or opening callback tags.
1140
+ # <tt>:height</tt>:: The font height.
1141
+ # <tt>:descender</tt>:: The font descender size.
1142
+ #
1143
+ # ==== <tt>:status</tt> Values and Meanings
1144
+ # <tt>:start</tt>:: The callback has been started. This applies
1145
+ # either when the callback is a stand-alone
1146
+ # callback (<tt>&lt;C:TAG/></tt>) or the opening
1147
+ # tag of a paired tag (<tt>&lt;c:TAG></tt>).
1148
+ # <tt>:end</tt>:: The callback has been manually terminated with
1149
+ # a closing tag (<tt>&lt;/c:TAG></tt>).
1150
+ # <tt>:start_line</tt>:: Called when a new line is to be drawn. This
1151
+ # allows the callback to perform any updates
1152
+ # necessary to permit paired callbacks to cross
1153
+ # line boundaries. This will usually involve
1154
+ # updating x, y positions.
1155
+ # <tt>:end_line</tt>:: Called when the end of a line is reached. This
1156
+ # permits the callback to perform any drawing
1157
+ # necessary to permit paired callbacks to cross
1158
+ # line boundaries.
1159
+ #
1160
+ # Drawing callback methods may return a hash of the <tt>:x</tt> and
1161
+ # <tt>:y</tt> position that the drawing pointer should take after the
1162
+ # callback is complete.
1163
+ #
1164
+ # === Known Callback Tags
1165
+ # <tt>&lt;c:alink URI></tt>:: makes an external link around text
1166
+ # between the opening and closing tags of
1167
+ # this callback. The URI may be any URL,
1168
+ # including http://, ftp://, and mailto:,
1169
+ # as long as there is a URL handler
1170
+ # registered. URI is of the form
1171
+ # uri="URI".
1172
+ # <tt>&lt;c:ilink DEST></tt>:: makes an internal link within the
1173
+ # document. The DEST must refer to a known
1174
+ # named destination within the document.
1175
+ # DEST is of the form dest="DEST".
1176
+ # <tt>&lt;c:uline></tt>:: underlines the specified text.
1177
+ # <tt>&lt;C:bullet></tt>:: Draws a solid bullet at the tag
1178
+ # position.
1179
+ # <tt>&lt;C:disc></tt>:: Draws a disc bullet at the tag position.
1180
+ def text_tags(text, pos, font_change, final = false, x = 0, y = 0, size = 0, angle = 0, word_space_adjust = 0)
1181
+ tag_size = 0
1182
+
1183
+ tag_match = %r!^<(/)?([^>]+)>!.match(text[pos..-1])
1184
+
1185
+ if tag_match
1186
+ closed, tag_name = tag_match.captures
1187
+ cts = @current_text_state # Alias for shorter lines.
1188
+ tag_size = tag_name.size + 2 + (closed ? 1 : 0)
1189
+
1190
+ case tag_name
1191
+ when %r{^(?:b|strong)$}o
1192
+ if closed
1193
+ cts.slice!(-1, 1) if ?b == cts[-1]
1194
+ else
1195
+ cts << ?b
1196
+ end
1197
+ when %r{^(?:i|em)$}o
1198
+ if closed
1199
+ cts.slice!(-1, 1) if ?i == cts[-1]
1200
+ else
1201
+ cts << ?i
1202
+ end
1203
+ when %r{^r:}o
1204
+ _match = MATCH_TAG_REPLACE_RE.match(tag_name)
1205
+ if _match.nil?
1206
+ warn PDF::Writer::Lang[:callback_warning] % [ 'r:', tag_name ]
1207
+ tag_size = 0
1208
+ else
1209
+ func = _match.captures[0]
1210
+ params = parse_tag_params(_match.captures[1] || "")
1211
+ tag = TAGS[:replace][func]
1212
+
1213
+ if tag
1214
+ text[pos, tag_size] = tag[self, params]
1215
+ tag_size, text, font_change, x, y = text_tags(text, pos,
1216
+ font_change,
1217
+ final, x, y, size,
1218
+ angle,
1219
+ word_space_adjust)
1220
+ else
1221
+ warn PDF::Writer::Lang[:callback_warning] % [ 'r:', func ]
1222
+ tag_size = 0
1223
+ end
1224
+ end
1225
+ when %r{^C:}o
1226
+ _match = MATCH_TAG_DRAW_ONE_RE.match(tag_name)
1227
+ if _match.nil?
1228
+ warn PDF::Writer::Lang[:callback_warning] % [ 'C:', tag_name ]
1229
+ tag_size = 0
1230
+ else
1231
+ func = _match.captures[0]
1232
+ params = parse_tag_params(_match.captures[1] || "")
1233
+ tag = TAGS[:single][func]
1234
+
1235
+ if tag
1236
+ font_change = false
1237
+
1238
+ if final
1239
+ # Only call the function if this is the "final" call. Assess
1240
+ # the text position. Calculate the text width to this point.
1241
+ x, y = text_end_position(x, y, angle, size, word_space_adjust,
1242
+ text[0, pos])
1243
+ info = {
1244
+ :x => x,
1245
+ :y => y,
1246
+ :angle => angle,
1247
+ :params => params,
1248
+ :status => :start,
1249
+ :cbid => @callbacks.size + 1,
1250
+ :callback => func,
1251
+ :height => font_height(size),
1252
+ :descender => font_descender(size)
1253
+ }
1254
+
1255
+ ret = tag[self, info]
1256
+ if ret.kind_of?(Hash)
1257
+ ret.each do |rk, rv|
1258
+ x = rv if rk == :x
1259
+ y = rv if rk == :y
1260
+ font_change = rv if rk == :font_change
1261
+ end
1262
+ end
1263
+ end
1264
+ else
1265
+ warn PDF::Writer::Lang[:callback_Warning] % [ 'C:', func ]
1266
+ tag_size = 0
1267
+ end
1268
+ end
1269
+ when %r{^c:}o
1270
+ _match = MATCH_TAG_DRAW_PAIR_RE.match(tag_name)
1271
+
1272
+ if _match.nil?
1273
+ warn PDF::Writer::Lang[:callback_warning] % [ 'c:', tag_name ]
1274
+ tag_size = 0
1275
+ else
1276
+ func = _match.captures[0]
1277
+ params = parse_tag_params(_match.captures[1] || "")
1278
+ tag = TAGS[:pair][func]
1279
+
1280
+ if tag
1281
+ font_change = false
1282
+
1283
+ if final
1284
+ # Only call the function if this is the "final" call. Assess
1285
+ # the text position. Calculate the text width to this point.
1286
+ x, y = text_end_position(x, y, angle, size, word_space_adjust,
1287
+ text[0, pos])
1288
+ info = {
1289
+ :x => x,
1290
+ :y => y,
1291
+ :angle => angle,
1292
+ :params => params,
1293
+ }
1294
+
1295
+ if closed
1296
+ info[:status] = :end
1297
+ info[:cbid] = @callbacks.size
1298
+
1299
+ ret = tag[self, info]
1300
+
1301
+ if ret.kind_of?(Hash)
1302
+ ret.each do |rk, rv|
1303
+ x = rv if rk == :x
1304
+ y = rv if rk == :y
1305
+ font_change = rv if rk == :font_change
1306
+ end
1307
+ end
1308
+
1309
+ @callbacks.pop
1310
+ else
1311
+ info[:status] = :start
1312
+ info[:cbid] = @callbacks.size + 1
1313
+ info[:tag] = tag
1314
+ info[:callback] = func
1315
+ info[:height] = font_height(size)
1316
+ info[:descender] = font_descender(size)
1317
+
1318
+ @callbacks << info
1319
+
1320
+ ret = tag[self, info]
1321
+
1322
+ if ret.kind_of?(Hash)
1323
+ ret.each do |rk, rv|
1324
+ x = rv if rk == :x
1325
+ y = rv if rk == :y
1326
+ font_change = rv if rk == :font_change
1327
+ end
1328
+ end
1329
+ end
1330
+ end
1331
+ else
1332
+ warn PDF::Writer::Lang[:callback_warning] % [ 'c:', func ]
1333
+ tag_size = 0
1334
+ end
1335
+ end
1336
+ else
1337
+ tag_size = 0
1338
+ end
1339
+ end
1340
+ [ tag_size, text, font_change, x, y ]
1341
+ end
1342
+ private :text_tags
1343
+
1344
+ TAG_PARAM_RE = %r{(\w+)=(?:"([^"]+)"|'([^']+)'|(\w+))} #:nodoc:
1345
+
1346
+ def parse_tag_params(params)
1347
+ params ||= ""
1348
+ ph = {}
1349
+ params.scan(TAG_PARAM_RE) do |param|
1350
+ ph[param[0]] = param[1] || param[2] || param[3]
1351
+ end
1352
+ ph
1353
+ end
1354
+ private :parse_tag_params
1355
+
1356
+ # Add +text+ to the document at <tt>(x, y)</tt> location at +size+ and
1357
+ # +angle+. The +word_space_adjust+ parameter is an internal parameter
1358
+ # that should not be used.
1359
+ #
1360
+ # As of PDF::Writer 1.1, +size+ and +text+ have been reversed and +size+
1361
+ # is now optional, defaulting to the current #font_size if unset.
1362
+ def add_text(x, y, text, size = nil, angle = 0, word_space_adjust = 0)
1363
+ if text.kind_of?(Numeric) and size.kind_of?(String)
1364
+ text, size = size, text
1365
+ warn PDF::Writer::Lang[:add_text_parameters_reversed] % caller[0]
1366
+ end
1367
+
1368
+ if size.nil? or size <= 0
1369
+ size = @font_size
1370
+ end
1371
+
1372
+ select_font("Helvetica") if @fonts.empty?
1373
+
1374
+ text = text.to_s
1375
+
1376
+ # If there are any open callbacks, then they should be called, to show
1377
+ # the start of the line
1378
+ @callbacks.reverse_each do |ii|
1379
+ info = ii.dup
1380
+ info[:x] = x
1381
+ info[:y] = y
1382
+ info[:angle] = angle
1383
+ info[:status] = :start_line
1384
+
1385
+ info[:tag][self, info]
1386
+ end
1387
+ if angle == 0
1388
+ add_content("\nBT %.3f %.3f Td" % [x, y])
1389
+ else
1390
+ rad = PDF::Math.deg2rad(angle)
1391
+ tt = "\nBT %.3f %.3f %.3f %.3f %.3f %.3f Tm"
1392
+ tt = tt % [ Math.cos(rad), Math.sin(rad), -Math.sin(rad), Math.cos(rad), x, y ]
1393
+ add_content(tt)
1394
+ end
1395
+
1396
+ if (word_space_adjust != 0) or not ((@word_space_adjust.nil?) and (@word_space_adjust != word_space_adjust))
1397
+ @word_space_adjust = word_space_adjust
1398
+ add_content(" %.3f Tw" % word_space_adjust)
1399
+ end
1400
+
1401
+ pos = -1
1402
+ start = 0
1403
+ loop do
1404
+ pos += 1
1405
+ break if pos == text.size
1406
+ font_change = true
1407
+ tag_size, text, font_change = quick_text_tags(text, pos, font_change)
1408
+
1409
+ if tag_size != 0
1410
+ if pos > start
1411
+ part = text[start, pos - start]
1412
+ tt = " /F#{find_font(@current_font).font_id}"
1413
+ tt << " %.1f Tf %d Tr" % [ size, @current_text_render_style ]
1414
+ tt << " (#{PDF::Writer.escape(part)}) Tj"
1415
+ add_content(tt)
1416
+ end
1417
+
1418
+ if font_change
1419
+ current_font!
1420
+ else
1421
+ add_content(" ET")
1422
+ xp = x
1423
+ yp = y
1424
+ tag_size, text, font_change, xp, yp = text_tags(text, pos, font_change, true, xp, yp, size, angle, word_space_adjust)
1425
+
1426
+ # Restart the text object
1427
+ if angle.zero?
1428
+ add_content("\nBT %.3f %.3f Td" % [xp, yp])
1429
+ else
1430
+ rad = PDF::Math.deg2rad(angle)
1431
+ tt = "\nBT %.3f %.3f %.3f %.3f %.3f %.3f Tm"
1432
+ tt = tt % [ Math.cos(rad), Math.sin(rad), -Math.sin(rad), Math.cos(rad), xp, yp ]
1433
+ add_content(tt)
1434
+ end
1435
+
1436
+ if (word_space_adjust != 0) or (word_space_adjust != @word_space_adjust)
1437
+ @word_space_adjust = word_space_adjust
1438
+ add_content(" %.3f Tw" % [word_space_adjust])
1439
+ end
1440
+ end
1441
+
1442
+ pos += tag_size - 1
1443
+ start = pos + 1
1444
+ end
1445
+ end
1446
+
1447
+ if start < text.size
1448
+ part = text[start..-1]
1449
+
1450
+ tt = " /F#{find_font(@current_font).font_id}"
1451
+ tt << " %.1f Tf %d Tr" % [ size, @current_text_render_style ]
1452
+ tt << " (#{PDF::Writer.escape(part)}) Tj"
1453
+ add_content(tt)
1454
+ end
1455
+ add_content(" ET")
1456
+
1457
+ # XXX: Experimental fix.
1458
+ @callbacks.reverse_each do |ii|
1459
+ info = ii.dup
1460
+ info[:x] = x
1461
+ info[:y] = y
1462
+ info[:angle] = angle
1463
+ info[:status] = :end_line
1464
+ info[:tag][self, info]
1465
+ end
1466
+ end
1467
+
1468
+ def char_width(font, char)
1469
+ char = char[0] unless @fonts[font].c[char]
1470
+
1471
+ if @fonts[font].differences and @fonts[font].c[char].nil?
1472
+ name = @fonts[font].differences[char] || 'M'
1473
+ width = @fonts[font].c[name]['WX'] if @fonts[font].c[name]['WX']
1474
+ elsif @fonts[font].c[char]
1475
+ width = @fonts[font].c[char]['WX']
1476
+ else
1477
+ width = @fonts[font].c['M']['WX']
1478
+ end
1479
+ width
1480
+ end
1481
+ private :char_width
1482
+
1483
+ # Calculate how wide a given text string will be on a page, at a given
1484
+ # size. This may be called externally, but is alse used by #text_width.
1485
+ # If +size+ is not specified, PDF::Writer will use the current
1486
+ # #font_size.
1487
+ #
1488
+ # The argument list is reversed from earlier versions.
1489
+ def text_line_width(text, size = nil)
1490
+ if text.kind_of?(Numeric) and size.kind_of?(String)
1491
+ text, size = size, text
1492
+ warn PDF::Writer::Lang[:text_width_parameters_reversed] % caller[0]
1493
+ end
1494
+
1495
+ if size.nil? or size <= 0
1496
+ size = @font_size
1497
+ end
1498
+
1499
+ # This function should not change any of the settings, though it will
1500
+ # need to track any tag which change during calculation, so copy them
1501
+ # at the start and put them back at the end.
1502
+ t_CTS = @current_text_state.dup
1503
+
1504
+ select_font("Helvetica") if @fonts.empty?
1505
+ # converts a number or a float to a string so it can get the width
1506
+ tt = text.to_s
1507
+ # hmm, this is where it all starts to get tricky - use the font
1508
+ # information to calculate the width of each character, add them up
1509
+ # and convert to user units
1510
+ width = 0
1511
+ font = @current_font
1512
+
1513
+ pos = -1
1514
+ loop do
1515
+ pos += 1
1516
+ break if pos == tt.size
1517
+ font_change = true
1518
+ tag_size, text, font_change = quick_text_tags(text, pos, font_change)
1519
+ if tag_size != 0
1520
+ if font_change
1521
+ current_font!
1522
+ font = @current_font
1523
+ end
1524
+ pos += tag_size - 1
1525
+ else
1526
+ if "&lt;" == tt[pos, 4]
1527
+ width += char_width(font, '<')
1528
+ pos += 3
1529
+ elsif "&gt;" == tt[pos, 4]
1530
+ width += char_width(font, '>')
1531
+ pos += 3
1532
+ elsif "&amp;" == tt[pos, 5]
1533
+ width += char_width(font, '&')
1534
+ pos += 4
1535
+ else
1536
+ width += char_width(font, tt[pos, 1])
1537
+ end
1538
+ end
1539
+ end
1540
+
1541
+ @current_text_state = t_CTS.dup
1542
+ current_font!
1543
+
1544
+ (width * size / 1000.0)
1545
+ end
1546
+
1547
+ # Calculate how wide a given text string will be on a page, at a given
1548
+ # size. If +size+ is not specified, PDF::Writer will use the current
1549
+ # #font_size. The difference between this method and #text_line_width is
1550
+ # that this method will iterate over lines separated with newline
1551
+ # characters.
1552
+ #
1553
+ # The argument list is reversed from earlier versions.
1554
+ def text_width(text, size = nil)
1555
+ if text.kind_of?(Numeric) and size.kind_of?(String)
1556
+ text, size = size, text
1557
+ warn PDF::Writer::Lang[:text_width_parameters_reversed] % caller[0]
1558
+ end
1559
+
1560
+ if size.nil? or size <= 0
1561
+ size = @font_size
1562
+ end
1563
+
1564
+ max = 0
1565
+
1566
+ text.to_s.each do |line|
1567
+ width = text_line_width(line, size)
1568
+ max = width if width > max
1569
+ end
1570
+ max
1571
+ end
1572
+
1573
+ # Partially calculate the values necessary to sort out the justification
1574
+ # of text.
1575
+ def adjust_wrapped_text(text, actual, width, x, just)
1576
+ adjust = 0
1577
+
1578
+ case just
1579
+ when :left
1580
+ nil
1581
+ when :right
1582
+ x += (width - actual)
1583
+ when :center
1584
+ x += (width - actual) / 2.0
1585
+ when :full
1586
+ spaces = text.count(" ")
1587
+ adjust = (width - actual) / spaces.to_f if spaces > 0
1588
+ end
1589
+
1590
+ [x, adjust]
1591
+ end
1592
+ private :adjust_wrapped_text
1593
+
1594
+ # Add text to the page, but ensure that it fits within a certain width.
1595
+ # If it does not fit then put in as much as possible, breaking at word
1596
+ # boundaries; return the remainder. +justification+ and +angle+ can also
1597
+ # be specified for the text.
1598
+ #
1599
+ # This will display the text; if it goes beyond the width +width+, it
1600
+ # will backttrack to the previous space or hyphen and return the
1601
+ # remainder of the text.
1602
+ #
1603
+ # +justification+:: :left, :right, :center, or :full
1604
+ def add_text_wrap(x, y, width, text, size = nil, justification = :left, angle = 0, test = false)
1605
+ if text.kind_of?(Numeric) and size.kind_of?(String)
1606
+ text, size = size, text
1607
+ warn PDF::Writer::Lang[:add_textw_parameters_reversed] % caller[0]
1608
+ end
1609
+
1610
+ if size.nil? or size <= 0
1611
+ size = @font_size
1612
+ end
1613
+
1614
+ # Need to store the initial text state, as this will change during the
1615
+ # width calculation, but will need to be re-set before printing, so
1616
+ # that the chars work out right
1617
+ t_CTS = @current_text_state.dup
1618
+
1619
+ select_font("Helvetica") if @fonts.empty?
1620
+ return "" if width <= 0
1621
+
1622
+ w = brk = brkw = 0
1623
+ font = @current_font
1624
+ tw = width / size.to_f * 1000
1625
+
1626
+ pos = -1
1627
+ loop do
1628
+ pos += 1
1629
+ break if pos == text.size
1630
+ font_change = true
1631
+ tag_size, text, font_change = quick_text_tags(text, pos, font_change)
1632
+ if tag_size != 0
1633
+ if font_change
1634
+ current_font!
1635
+ font = @current_font
1636
+ end
1637
+ pos += (tag_size - 1)
1638
+ else
1639
+ w += char_width(font, text[pos, 1])
1640
+
1641
+ if w > tw # We need to truncate this line
1642
+ if brk > 0 # There is somewhere to break the line.
1643
+ if text[brk] == " "
1644
+ tmp = text[0, brk]
1645
+ else
1646
+ tmp = text[0, brk + 1]
1647
+ end
1648
+ x, adjust = adjust_wrapped_text(tmp, brkw, width, x, justification)
1649
+
1650
+ # Reset the text state
1651
+ @current_text_state = t_CTS.dup
1652
+ current_font!
1653
+ add_text(x, y, tmp, size, angle, adjust) unless test
1654
+ return text[brk + 1..-1]
1655
+ else # just break before the current character
1656
+ tmp = text[0, pos]
1657
+ # tmpw = (w - char_width(font, text[pos, 1])) * size / 1000.0
1658
+ x, adjust = adjust_wrapped_text(tmp, brkw, width, x, justification)
1659
+
1660
+ # Reset the text state
1661
+ @current_text_state = t_CTS.dup
1662
+ current_font!
1663
+ add_text(x, y, tmp, size, angle, adjust) unless test
1664
+ return text[pos..-1]
1665
+ end
1666
+ end
1667
+
1668
+ if text[pos] == ?-
1669
+ brk = pos
1670
+ brkw = w * size / 1000.0
1671
+ end
1672
+
1673
+ if text[pos, 1] == " "
1674
+ brk = pos
1675
+ ctmp = text[pos]
1676
+ ctmp = @fonts[font].differences[ctmp] unless @fonts[font].differences.nil?
1677
+ z = @fonts[font].c[tmp].nil? ? 0 : @fonts[font].c[tmp]['WX']
1678
+ brkw = (w - z) * size / 1000.0
1679
+ end
1680
+ end
1681
+ end
1682
+
1683
+ # There was no need to break this line.
1684
+ justification = :left if justification == :full
1685
+ tmpw = (w * size) / 1000.0
1686
+ x, adjust = adjust_wrapped_text(text, tmpw, width, x, justification)
1687
+ # reset the text state
1688
+ @current_text_state = t_CTS.dup
1689
+ current_font!
1690
+ add_text(x, y, text, size, angle, adjust) unless test
1691
+ return ""
1692
+ end
1693
+
1694
+ # Saves the state.
1695
+ def save_state
1696
+ PDF::Writer::State.new do |state|
1697
+ state.fill_color = @current_fill_color
1698
+ state.stroke_color = @current_stroke_color
1699
+ state.text_render_style = @current_text_render_style
1700
+ state.stroke_style = @current_stroke_style
1701
+ @state_stack.push state
1702
+ end
1703
+ add_content("\nq")
1704
+ end
1705
+
1706
+ # This will be called at a new page to return the state to what it was
1707
+ # on the end of the previous page, before the stack was closed down.
1708
+ # This is to get around not being able to have open 'q' across pages.
1709
+ def reset_state_at_page_start
1710
+ @state_stack.each do |state|
1711
+ fill_color! state.fill_color
1712
+ stroke_color! state.stroke_color
1713
+ text_render_style! state.text_render_style
1714
+ stroke_style! state.stroke_style
1715
+ add_content("\nq")
1716
+ end
1717
+ end
1718
+ private :reset_state_at_page_start
1719
+
1720
+ # Restore a previously saved state.
1721
+ def restore_state
1722
+ unless @state_stack.empty?
1723
+ state = @state_stack.pop
1724
+ @current_fill_color = state.fill_color
1725
+ @current_stroke_color = state.stroke_color
1726
+ @current_text_render_style = state.text_render_style
1727
+ @current_stroke_style = state.stroke_style
1728
+ stroke_style!
1729
+ end
1730
+ add_content("\nQ")
1731
+ end
1732
+
1733
+ # Restore the state at the end of a page.
1734
+ def reset_state_at_page_finish
1735
+ add_content("\nQ" * @state_stack.size)
1736
+ end
1737
+ private :reset_state_at_page_finish
1738
+
1739
+ # Make a loose object. The output will go into this object, until it is
1740
+ # closed, then will revert to the current one. This object will not
1741
+ # appear until it is included within a page. The function will return
1742
+ # the object reference.
1743
+ def open_object
1744
+ @stack << { :contents => @current_contents, :page => @current_page }
1745
+ @current_contents = PDF::Writer::Object::Contents.new(self)
1746
+ @loose_objects << @current_contents
1747
+ yield @current_contents if block_given?
1748
+ @current_contents
1749
+ end
1750
+
1751
+ # Opens an existing object for editing.
1752
+ def reopen_object(id)
1753
+ @stack << { :contents => @current_contents, :page => @current_page }
1754
+ @current_contents = id
1755
+ # if this object is the primary contents for a page, then set the
1756
+ # current page to its parent
1757
+ @current_page = @current_contents.on_page unless @current_contents.on_page.nil?
1758
+ @current_contents
1759
+ end
1760
+
1761
+ # Close an object for writing.
1762
+ def close_object
1763
+ unless @stack.empty?
1764
+ obj = @stack.pop
1765
+ @current_contents = obj[:contents]
1766
+ @current_page = obj[:page]
1767
+ end
1768
+ end
1769
+
1770
+ # Stop an object from appearing on pages from this point on.
1771
+ def stop_object(id)
1772
+ obj = @loose_objects.detect { |ii| ii.oid == id.oid }
1773
+ @add_loose_objects[obj] = nil
1774
+ end
1775
+
1776
+ # After an object has been created, it will only show if it has been
1777
+ # added, using this method.
1778
+ def add_object(id, where = :this_page)
1779
+ obj = @loose_objects.detect { |ii| ii == id }
1780
+
1781
+ if obj and @current_contents != obj
1782
+ case where
1783
+ when :all_pages, :this_page
1784
+ @add_loose_objects[obj] = where if where == :all_pages
1785
+ @current_contents.on_page.contents << obj if @current_contents.on_page
1786
+ when :even_pages
1787
+ @add_loose_objects[obj] = where
1788
+ page = @current_contents.on_page
1789
+ add_object(id) if (page.info.page_number % 2) == 0
1790
+ when :odd_pages
1791
+ @add_loose_objects[obj] = where
1792
+ page = @current_contents.on_page
1793
+ add_object(id) if (page.info.page_number % 2) == 1
1794
+ when :all_following_pages
1795
+ @add_loose_objects[obj] = :all_pages
1796
+ when :following_even_pages
1797
+ @add_loose_objects[obj] = :even_pages
1798
+ when :following_odd_pages
1799
+ @add_loose_objects[obj] = :odd_pages
1800
+ end
1801
+ end
1802
+ end
1803
+
1804
+ # Add content to the documents info object.
1805
+ def add_info(label, value = 0)
1806
+ # This will only work if the label is one of the valid ones. Modify
1807
+ # this so that arrays can be passed as well. If @label is an array
1808
+ # then assume that it is key => value pairs else assume that they are
1809
+ # both scalar, anything else will probably error.
1810
+ if label.kind_of?(Hash)
1811
+ label.each { |kk, vv| @info.__send__(kk.downcase.intern, vv) }
1812
+ else
1813
+ @info.__send__(label.downcase.intern, value)
1814
+ end
1815
+ end
1816
+
1817
+ # Specify the Destination object where the document should open when it
1818
+ # first starts. +style+ must be one of the values detailed for
1819
+ # #destinations. The value of +style+ affects the interpretation of
1820
+ # +params+. Uses the current page as the starting location.
1821
+ def open_here(style, *params)
1822
+ open_at(@current_page, style, *params)
1823
+ end
1824
+
1825
+ # Specify the Destination object where the document should open when it
1826
+ # first starts. +style+ must be one of the following values. The value
1827
+ # of +style+ affects the interpretation of +params+. Uses +page+ as the
1828
+ # starting location.
1829
+ def open_at(page, style, *params)
1830
+ d = PDF::Writer::Object::Destination.new(self, page, style, *params)
1831
+ @catalog.open_here = d
1832
+ end
1833
+
1834
+ # Create a labelled destination within the document. The label is the
1835
+ # name which will be used for <c:ilink> destinations.
1836
+ #
1837
+ # XYZ:: The viewport will be opened at position <tt>(left, top)</tt>
1838
+ # with +zoom+ percentage. +params+ must have three values
1839
+ # representing +left+, +top+, and +zoom+, respectively. If the
1840
+ # values are "null", the current parameter values are unchanged.
1841
+ # Fit:: Fit the page to the viewport (horizontal and vertical).
1842
+ # +params+ will be ignored.
1843
+ # FitH:: Fit the page horizontally to the viewport. The top of the
1844
+ # viewport is set to the first value in +params+.
1845
+ # FitV:: Fit the page vertically to the viewport. The left of the
1846
+ # viewport is set to the first value in +params+.
1847
+ # FitR:: Fits the page to the provided rectangle. +params+ must have
1848
+ # four values representing the +left+, +bottom+, +right+, and
1849
+ # +top+ positions, respectively.
1850
+ # FitB:: Fits the page to the bounding box of the page. +params+ is
1851
+ # ignored.
1852
+ # FitBH:: Fits the page horizontally to the bounding box of the page.
1853
+ # The top position is defined by the first value in +params+.
1854
+ # FitBV:: Fits the page vertically to the bounding box of the page. The
1855
+ # left position is defined by the first value in +params+.
1856
+ def add_destination(label, style, *params)
1857
+ @destinations[label] = PDF::Writer::Object::Destination.new(self, @current_page, style, *params)
1858
+ end
1859
+
1860
+ # Set the page mode of the catalog. Must be one of the following:
1861
+ # UseNone:: Neither document outline nor thumbnail images are
1862
+ # visible.
1863
+ # UseOutlines:: Document outline visible.
1864
+ # UseThumbs:: Thumbnail images visible.
1865
+ # FullScreen:: Full-screen mode, with no menu bar, window controls, or
1866
+ # any other window visible.
1867
+ # UseOC:: Optional content group panel is visible.
1868
+ #
1869
+ def page_mode=(mode)
1870
+ @catalog.page_mode = value
1871
+ end
1872
+
1873
+ include Transaction::Simple
1874
+
1875
+ # The width of the currently active column. This will return zero (0) if
1876
+ # columns are off.
1877
+ attr_reader :column_width
1878
+ def column_width #:nodoc:
1879
+ return 0 unless @columns_on
1880
+ @columns[:width]
1881
+ end
1882
+ # The gutter between columns. This will return zero (0) if columns are
1883
+ # off.
1884
+ attr_reader :column_gutter
1885
+ def column_gutter #:nodoc:
1886
+ return 0 unless @columns_on
1887
+ @columns[:gutter]
1888
+ end
1889
+ # The current column number. Returns zero (0) if columns are off.
1890
+ attr_reader :column_number
1891
+ def column_number #:nodoc:
1892
+ return 0 unless @columns_on
1893
+ @columns[:current]
1894
+ end
1895
+ # The total number of columns. Returns zero (0) if columns are off.
1896
+ attr_reader :column_count
1897
+ def column_count #:nodoc:
1898
+ return 0 unless @columns_on
1899
+ @columns[:size]
1900
+ end
1901
+ # Indicates if columns are currently on.
1902
+ def columns?
1903
+ @columns_on
1904
+ end
1905
+
1906
+ # Starts multi-column output. Creates +size+ number of columns with a
1907
+ # +gutter+ PDF unit space between each column.
1908
+ #
1909
+ # If columns are already started, this will return +false+.
1910
+ def start_columns(size = 2, gutter = 10)
1911
+ # Start from the current y-position; make the set number of columns.
1912
+ return false if @columns_on
1913
+
1914
+ @columns = {
1915
+ :current => 1,
1916
+ :bot_y => @y
1917
+ }
1918
+ @columns_on = true
1919
+ # store the current margins
1920
+ @columns[:left] = @left_margin
1921
+ @columns[:right] = @right_margin
1922
+ @columns[:top] = @top_margin
1923
+ @columns[:bottom] = @bottom_margin
1924
+ # Reset the margins to suit the new columns. Safe enough to assume the
1925
+ # first column here, but start from the current y-position.
1926
+ @top_margin = @page_height - @y
1927
+ @columns[:size] = size || 2
1928
+ @columns[:gutter] = gutter || 10
1929
+ w = absolute_right_margin - absolute_left_margin
1930
+ @columns[:width] = (w - ((size - 1) * gutter)) / size.to_f
1931
+ @right_margin = @page_width - (@left_margin + @columns[:width])
1932
+ end
1933
+
1934
+ def restore_margins_after_columns
1935
+ @left_margin = @columns[:left]
1936
+ @right_margin = @columns[:right]
1937
+ @top_margin = @columns[:top]
1938
+ @bottom_margin = @columns[:bottom]
1939
+ end
1940
+ private :restore_margins_after_columns
1941
+
1942
+ # Turns off multi-column output. If we are in the first column, or the
1943
+ # lowest point at which columns were written is higher than the bottom
1944
+ # of the page, then the writing pointer will be placed at the lowest
1945
+ # point. Otherwise, a new page will be started.
1946
+ def stop_columns
1947
+ return false unless @columns_on
1948
+ @columns_on = false
1949
+
1950
+ @columns[:bot_y] = @y if @y < @columns[:bot_y]
1951
+
1952
+ if (@columns[:bot_y] > @bottom_margin) or @column_number == 1
1953
+ @y = @columns[:bot_y]
1954
+ else
1955
+ start_new_page
1956
+ end
1957
+ restore_margins_after_columns
1958
+ @columns = {}
1959
+ true
1960
+ end
1961
+
1962
+ # Changes page insert mode. May be called as follows:
1963
+ #
1964
+ # pdf.insert_mode # => current insert mode
1965
+ # # The following four affect the insert mode without changing the
1966
+ # # insert page or insert position.
1967
+ # pdf.insert_mode(:on) # enables insert mode
1968
+ # pdf.insert_mode(true) # enables insert mode
1969
+ # pdf.insert_mode(:off) # disables insert mode
1970
+ # pdf.insert_mode(false) # disables insert mode
1971
+ #
1972
+ # # Changes the insert mode, the insert page, and the insert
1973
+ # # position at the same time.
1974
+ # opts = {
1975
+ # :on => true,
1976
+ # :page => :last,
1977
+ # :position => :before
1978
+ # }
1979
+ # pdf.insert_mode(opts)
1980
+ def insert_mode(options = {})
1981
+ case options
1982
+ when :on, true
1983
+ @insert_mode = true
1984
+ when :off, false
1985
+ @insert_mode = false
1986
+ else
1987
+ return @insert_mode unless options
1988
+
1989
+ @insert_mode = options[:on] unless options[:on].nil?
1990
+
1991
+ unless options[:page].nil?
1992
+ if @pageset[options[:page]].nil? or options[:page] == :last
1993
+ @insert_page = @pageset[-1]
1994
+ else
1995
+ @insert_page = @pageset[options[:page]]
1996
+ end
1997
+ end
1998
+
1999
+ @insert_position = options[:position] if options[:position]
2000
+ end
2001
+ end
2002
+ # Returns or changes the insert page property.
2003
+ #
2004
+ # pdf.insert_page # => current insert page
2005
+ # pdf.insert_page(35) # insert at page 35
2006
+ # pdf.insert_page(:last) # insert at the last page
2007
+ def insert_page(page = nil)
2008
+ return @insert_page unless page
2009
+ if page == :last
2010
+ @insert_page = @pageset[-1]
2011
+ else
2012
+ @insert_page = @pageset[page]
2013
+ end
2014
+ end
2015
+ # Changes the #insert_page property to append to the page set.
2016
+ def append_page
2017
+ insert_mode(:last)
2018
+ end
2019
+ # Returns or changes the insert position to be before or after the
2020
+ # specified page.
2021
+ #
2022
+ # pdf.insert_position # => current insert position
2023
+ # pdf.insert_position(:before) # insert before #insert_page
2024
+ # pdf.insert_position(:after) # insert before #insert_page
2025
+ def insert_position(position = nil)
2026
+ return @insert_position unless position
2027
+ @insert_position = position
2028
+ end
2029
+
2030
+ # Creates a new page. If multi-column output is turned on, this will
2031
+ # change the column to the next greater or create a new page as
2032
+ # necessary. If +force+ is true, then a new page will be created even if
2033
+ # multi-column output is on.
2034
+ def start_new_page(force = false)
2035
+ page_required = true
2036
+
2037
+ if @columns_on
2038
+ # Check if this is just going to a new column. Increment the column
2039
+ # number.
2040
+ @columns[:current] += 1
2041
+
2042
+ if @columns[:current] <= @columns[:size] and not force
2043
+ page_required = false
2044
+ @columns[:bot_y] = @y if @y < @columns[:bot_y]
2045
+ else
2046
+ @columns[:current] = 1
2047
+ @top_margin = @columns[:top]
2048
+ @columns[:bot_y] = absolute_top_margin
2049
+ end
2050
+
2051
+ w = @columns[:width]
2052
+ g = @columns[:gutter]
2053
+ n = @columns[:current] - 1
2054
+ @left_margin = @columns[:left] + n * (g + w)
2055
+ @right_margin = @page_width - (@left_margin + w)
2056
+ end
2057
+
2058
+ if page_required or force
2059
+ # make a new page, setting the writing point back to the top.
2060
+ @y = absolute_top_margin
2061
+ # make the new page with a call to the basic class
2062
+ if @insert_mode
2063
+ id = new_page(true, @insert_page, @insert_position)
2064
+ @pageset << id
2065
+ # Manipulate the insert options so that inserted pages follow each
2066
+ # other
2067
+ @insert_page = id
2068
+ @insert_position = :after
2069
+ else
2070
+ @pageset << new_page
2071
+ end
2072
+
2073
+ else
2074
+ @y = absolute_top_margin
2075
+ end
2076
+ @pageset
2077
+ end
2078
+
2079
+ # Add a new page to the document. This also makes the new page the
2080
+ # current active object. This allows for mandatory page creation
2081
+ # regardless of multi-column output.
2082
+ #
2083
+ # For most purposes, #start_new_page is preferred.
2084
+ def new_page(insert = false, page = nil, pos = :after)
2085
+ reset_state_at_page_finish
2086
+
2087
+ if insert
2088
+ # The id from the PDF::Writer class is the id of the contents of the
2089
+ # page, not the page object itself. Query that object to find the
2090
+ # parent.
2091
+ _new_page = PDF::Writer::Object::Page.new(self, { :rpage => page, :pos => pos })
2092
+ else
2093
+ _new_page = PDF::Writer::Object::Page.new(self)
2094
+ end
2095
+
2096
+ reset_state_at_page_start
2097
+
2098
+ # If there has been a stroke or fill color set, transfer them.
2099
+ fill_color!
2100
+ stroke_color!
2101
+ stroke_style!
2102
+
2103
+ # the call to the page object set @current_contents to the present page,
2104
+ # so this can be returned as the page id
2105
+ # @current_contents
2106
+ _new_page
2107
+ end
2108
+
2109
+ # Returns the current generic page number. This is based exclusively on
2110
+ # the size of the page set.
2111
+ def current_page_number
2112
+ @pageset.size
2113
+ end
2114
+
2115
+ # Put page numbers on the pages from the current page. Place them
2116
+ # relative to the coordinates <tt>(x, y)</tt> with the text horizontally
2117
+ # relative according to +pos+, which may be <tt>:left</tt>,
2118
+ # <tt>:right</tt>, or <tt>:center</tt>. The page numbers will be written
2119
+ # on each page using +pattern+.
2120
+ #
2121
+ # When +pattern+ is rendered, <PAGENUM> will be replaced with the
2122
+ # current page number; <TOTALPAGENUM> will be replaced with the total
2123
+ # number of pages in the page numbering scheme. The default +pattern+ is
2124
+ # "<PAGENUM> of <TOTALPAGENUM>".
2125
+ #
2126
+ #
2127
+ # Each time page numbers are started, a new page number scheme will be
2128
+ # started. The scheme number will be returned.
2129
+ def start_page_numbering(x, y, size, pos = nil, pattern = nil, starting = nil)
2130
+ pos ||= :left
2131
+ pattern ||= "<PAGENUM> of <TOTALPAGENUM>"
2132
+ starting ||= 1
2133
+
2134
+ @page_numbering ||= []
2135
+ @page_numbering << (o = {})
2136
+
2137
+ page = @pageset.size - 1
2138
+ o[page] = {
2139
+ :x => x,
2140
+ :y => y,
2141
+ :pos => pos,
2142
+ :pattern => pattern,
2143
+ :starting => starting,
2144
+ :size => size,
2145
+ :start => true
2146
+ }
2147
+ @page_numbering.index(o)
2148
+ end
2149
+
2150
+ # Given a particular generic page number +page_num+ (numbered
2151
+ # sequentially from the beginning of the page set), return the page
2152
+ # number under a particular page numbering +scheme+ (defaults to the
2153
+ # first scheme turned on). Returns +nil+ if page numbering is not turned
2154
+ # on or if the page is not under the current numbering scheme.
2155
+ #
2156
+ # This method has been dprecated.
2157
+ def which_page_number(page_num, scheme = 0)
2158
+ return nil unless @page_numbering
2159
+
2160
+ num = nil
2161
+ start = start_num = 1
2162
+
2163
+ @page_numbering[scheme].each do |kk, vv|
2164
+ if kk <= page_num
2165
+ if vv.kind_of?(Hash)
2166
+ unless vv[:starting].nil?
2167
+ start = vv[:starting]
2168
+ start_num = kk
2169
+ num = page_num - start_num + start
2170
+ end
2171
+ else
2172
+ num = nil
2173
+ end
2174
+ end
2175
+ end
2176
+ num
2177
+ end
2178
+
2179
+ # Stop page numbering. Returns +false+ if page numbering is off.
2180
+ #
2181
+ # If +stop_total+ is true, then then the totaling of pages for this page
2182
+ # numbering +scheme+ will be stopped as well. If +stop_at+ is
2183
+ # <tt>:current</tt>, then the page numbering will stop at this page;
2184
+ # otherwise, it will stop at the next page.
2185
+ #
2186
+ # This method has been dprecated.
2187
+ def stop_page_numbering(stop_total = false, stop_at = :current, scheme = 0)
2188
+ return false unless @page_numbering
2189
+
2190
+ page = @pageset.size - 1
2191
+
2192
+ @page_numbering[scheme][page] ||= {}
2193
+ o = @page_numbering[scheme][page]
2194
+
2195
+ case [ stop_total, stop_at == :current ]
2196
+ when [ true, true ]
2197
+ o[:stop] = :stop_total
2198
+ when [ true, false ]
2199
+ o[:stop] = :stop_total_next
2200
+ when [ false, true ]
2201
+ o[:stop] = :stop_next
2202
+ else
2203
+ o[:stop] = :stop
2204
+ end
2205
+ end
2206
+
2207
+ def page_number_search(condition, scheme)
2208
+ res = nil
2209
+ scheme.each { |page, value| res = page if value[:stop] == condition }
2210
+ res
2211
+ end
2212
+ private :page_number_search
2213
+
2214
+ def add_page_numbers
2215
+ # This will go through the @page_numbering array and add the page
2216
+ # numbers are required.
2217
+ if @page_numbering
2218
+ page_count = @pageset.size
2219
+ pn_tmp = @page_numbering.dup
2220
+
2221
+ # Go through each of the page numbering schemes.
2222
+ pn_tmp.each do |scheme|
2223
+ # First, find the total pages for this schemes.
2224
+ page = page_number_search(:stop_total, scheme)
2225
+
2226
+ if page
2227
+ total_pages = page
2228
+ else
2229
+ page = page_number_search(:stop_total_next, scheme)
2230
+ if page
2231
+ total_pages = page
2232
+ else
2233
+ total_pages = page_count - 1
2234
+ end
2235
+ end
2236
+
2237
+ status = nil
2238
+ delta = pattern = pos = x = y = size = nil
2239
+ pattern = pos = x = y = size = nil
2240
+
2241
+ @pageset.each_with_index do |page, index|
2242
+ next if status.nil? and scheme[index].nil?
2243
+
2244
+ info = scheme[index]
2245
+ if info
2246
+ if info[:start]
2247
+ status = true
2248
+ if info[:starting]
2249
+ delta = info[:starting] - index
2250
+ else
2251
+ delta = index
2252
+ end
2253
+
2254
+ pattern = info[:pattern]
2255
+ pos = info[:pos]
2256
+ x = info[:x]
2257
+ y = info[:y]
2258
+ size = info[:size]
2259
+
2260
+ # Check for the special case of page numbering starting and
2261
+ # stopping on the same page.
2262
+ status = :stop_next if info[:stop]
2263
+ elsif [:stop, :stop_total].include?(info[:stop])
2264
+ status = :stop_now
2265
+ elsif status == true and [:stop_next, :stop_total_next].include?(info[:stop])
2266
+ status = :stop_next
2267
+ end
2268
+ end
2269
+
2270
+ if status
2271
+ # Add the page numbering to this page
2272
+ num = index + delta.to_i
2273
+ total = total_pages + num - index
2274
+ patt = pattern.gsub(/<PAGENUM>/, num.to_s).gsub(/<TOTALPAGENUM>/, total.to_s)
2275
+ reopen_object(page.contents.first)
2276
+
2277
+ case pos
2278
+ when :left # Write the page number from x.
2279
+ w = 0
2280
+ when :right # Write the page number to x.
2281
+ w = text_width(patt, size)
2282
+ when :center # Write the page number around x.
2283
+ w = text_width(patt, size) / 2.0
2284
+ end
2285
+ add_text(x - w, y, patt, size)
2286
+ close_object
2287
+ status = nil if [ :stop_now, :stop_next ].include?(status)
2288
+ end
2289
+ end
2290
+ end
2291
+ end
2292
+ end
2293
+ private :add_page_numbers
2294
+
2295
+ def preprocess_text(text)
2296
+ text
2297
+ end
2298
+ private :preprocess_text
2299
+
2300
+ # This will add a string of +text+ to the document, starting at the
2301
+ # current drawing position. It will wrap to keep within the margins,
2302
+ # including optional offsets from the left and the right. The text will
2303
+ # go to the start of the next line when a return code "\n" is found.
2304
+ #
2305
+ # Possible +options+ are:
2306
+ # <tt>:font_size</tt>:: The font size to be used. If not
2307
+ # specified, is either the last font size or
2308
+ # the default font size of 12 points.
2309
+ # Setting this value *changes* the current
2310
+ # #font_size.
2311
+ # <tt>:left</tt>:: number, gap to leave from the left margin
2312
+ # <tt>:right</tt>:: number, gap to leave from the right margin
2313
+ # <tt>:absolute_left</tt>:: number, absolute left position (overrides
2314
+ # <tt>:left</tt>)
2315
+ # <tt>:absolute_right</tt>:: number, absolute right position (overrides
2316
+ # <tt>:right</tt>)
2317
+ # <tt>:justification</tt>:: <tt>:left</tt>, <tt>:right</tt>,
2318
+ # <tt>:center</tt>, <tt>:full</tt>
2319
+ # <tt>:leading</tt>:: number, defines the total height taken by
2320
+ # the line, independent of the font height.
2321
+ # <tt>:spacing</tt>:: a Floating point number, though usually
2322
+ # set to one of 1, 1.5, 2 (line spacing as
2323
+ # used in word processing)
2324
+ #
2325
+ # Only one of <tt>:leading</tt> or <tt>:spacing</tt> should be specified
2326
+ # (leading overrides spacing).
2327
+ #
2328
+ # If the <tt>:test</tt> option is +true+, then this should just check to
2329
+ # see if the text is flowing onto a new page or not; returns +true+ or
2330
+ # +false+. Note that the new page test is only sensitive to exceeding
2331
+ # the bottom margin of the page. It is not known whether the writing of
2332
+ # the text will require a new physical page or whether it will require a
2333
+ # new column.
2334
+ def text(text, options = {})
2335
+ # Apply the filtering which will make underlining (and other items)
2336
+ # function.
2337
+ text = preprocess_text(text)
2338
+
2339
+ options ||= {}
2340
+
2341
+ new_page_required = false
2342
+ __y = @y
2343
+
2344
+ if options[:absolute_left]
2345
+ left = options[:absolute_left]
2346
+ else
2347
+ left = @left_margin
2348
+ left += options[:left] if options[:left]
2349
+ end
2350
+
2351
+ if options[:absolute_right]
2352
+ right = options[:absolute_right]
2353
+ else
2354
+ right = absolute_right_margin
2355
+ right -= options[:right] if options[:right]
2356
+ end
2357
+
2358
+ size = options[:font_size] || 0
2359
+ if size <= 0
2360
+ size = @font_size
2361
+ else
2362
+ @font_size = size
2363
+ end
2364
+
2365
+ just = options[:justification] || :left
2366
+
2367
+ if options[:leading] # leading instead of spacing
2368
+ height = options[:leading]
2369
+ elsif options[:spacing]
2370
+ height = options[:spacing] * font_height(size)
2371
+ else
2372
+ height = font_height(size)
2373
+ end
2374
+
2375
+ text.each do |line|
2376
+ start = true
2377
+ loop do # while not line.empty? or start
2378
+ break if (line.nil? or line.empty?) and not start
2379
+
2380
+ start = false
2381
+
2382
+ @y -= height
2383
+
2384
+ if @y < @bottom_margin
2385
+ if options[:test]
2386
+ new_page_required = true
2387
+ else
2388
+ # and then re-calc the left and right, in case they have
2389
+ # changed due to columns
2390
+ start_new_page
2391
+ @y -= height
2392
+
2393
+ if options[:absolute_left]
2394
+ left = options[:absolute_left]
2395
+ else
2396
+ left = @left_margin
2397
+ left += options[:left] if options[:left]
2398
+ end
2399
+
2400
+ if options[:absolute_right]
2401
+ right = options[:absolute_right]
2402
+ else
2403
+ right = absolute_right_margin
2404
+ right -= options[:right] if options[:right]
2405
+ end
2406
+ end
2407
+ end
2408
+
2409
+ line = add_text_wrap(left, @y, right - left, line, size, just, 0, options[:test])
2410
+ end
2411
+ end
2412
+
2413
+ if options[:test]
2414
+ @y = __y
2415
+ new_page_required
2416
+ else
2417
+ @y
2418
+ end
2419
+ end
2420
+
2421
+ def prepress_clip_mark(x, y, angle, mark_length = 18, bleed_size = 12) #:nodoc:
2422
+ save_state
2423
+ translate_axis(x, y)
2424
+ rotate_axis(angle)
2425
+ line(0, bleed_size, 0, bleed_size + mark_length).stroke
2426
+ line(bleed_size, 0, bleed_size + mark_length, 0).stroke
2427
+ restore_state
2428
+ end
2429
+
2430
+ def prepress_center_mark(x, y, angle, mark_length = 18, bleed_size = 12) #:nodoc:
2431
+ save_state
2432
+ translate_axis(x, y)
2433
+ rotate_axis(angle)
2434
+ half_mark = mark_length / 2.0
2435
+ c_x = 0
2436
+ c_y = bleed_size + half_mark
2437
+ line((c_x - half_mark), c_y, (c_x + half_mark), c_y).stroke
2438
+ line(c_x, (c_y - half_mark), c_x, (c_y + half_mark)).stroke
2439
+ rad = (mark_length * 0.50) / 2.0
2440
+ circle_at(c_x, c_y, rad).stroke
2441
+ restore_state
2442
+ end
2443
+
2444
+ # Returns the estimated number of lines remaining given the default or
2445
+ # specified font size.
2446
+ def lines_remaining(font_size = nil)
2447
+ font_size ||= @font_size
2448
+ remaining = @y - @bottom_margin
2449
+ remaining / font_height(font_size).to_f
2450
+ end
2451
+
2452
+ # Callback tag relationships. All relationships are of the form
2453
+ # "tagname" => CallbackClass.
2454
+ #
2455
+ # There are three types of tag callbacks:
2456
+ # <tt>:pair</tt>:: Paired callbacks, e.g., <c:alink></c:alink>.
2457
+ # <tt>:single</tt>:: Single-tag callbacks, e.g., <C:bullet>.
2458
+ # <tt>:replace</tt>:: Single-tag replacement callbacks, e.g., <r:xref>.
2459
+ TAGS = {
2460
+ :pair => { },
2461
+ :single => { },
2462
+ :replace => { }
2463
+ }
2464
+ TAGS.freeze
2465
+
2466
+ # A callback to support the formation of clickable links to external
2467
+ # locations.
2468
+ class TagAlink
2469
+ # The default anchored link style.
2470
+ DEFAULT_STYLE = {
2471
+ :color => Color::RGB::Blue,
2472
+ :text_color => Color::RGB::Blue,
2473
+ :draw_line => true,
2474
+ :line_style => { :dash => PDF::Writer::StrokeStyle::SOLID_LINE },
2475
+ :factor => 0.05
2476
+ }
2477
+
2478
+ class << self
2479
+ # Sets the style for <c:alink> callback underlines that follow. This
2480
+ # is expected to be a hash with the following keys:
2481
+ #
2482
+ # <tt>:color</tt>:: The colour to be applied to the link
2483
+ # underline. Default is Color::RGB::Blue.
2484
+ # <tt>:text_color</tt>:: The colour to be applied to the link text.
2485
+ # Default is Color::RGB::Blue.
2486
+ # <tt>:factor</tt>:: The size of the line, as a multiple of the
2487
+ # text height. Default is 0.05.
2488
+ # <tt>:draw_line</tt>:: Whether to draw the underline as part of
2489
+ # the link or not. Default is +true+.
2490
+ # <tt>:line_style</tt>:: The style modification hash supplied to
2491
+ # PDF::Writer::StrokeStyle.new. The default
2492
+ # is a solid line with normal cap, join, and
2493
+ # miter limit values.
2494
+ #
2495
+ # Set this to +nil+ to get the default style.
2496
+ attr_accessor :style
2497
+
2498
+ def [](pdf, info)
2499
+ @style ||= DEFAULT_STYLE.dup
2500
+
2501
+ case info[:status]
2502
+ when :start, :start_line
2503
+ # The beginning of the link. This should contain the URI for the
2504
+ # link as the :params entry, and will also contain the value of
2505
+ # :cbid.
2506
+ @links ||= {}
2507
+
2508
+ @links[info[:cbid]] = {
2509
+ :x => info[:x],
2510
+ :y => info[:y],
2511
+ :angle => info[:angle],
2512
+ :descender => info[:descender],
2513
+ :height => info[:height],
2514
+ :uri => info[:params]["uri"]
2515
+ }
2516
+
2517
+ pdf.save_state
2518
+ pdf.fill_color @style[:text_color] if @style[:text_color]
2519
+ if @style[:draw_line]
2520
+ pdf.stroke_color @style[:color] if @style[:color]
2521
+ sz = info[:height] * @style[:factor]
2522
+ pdf.stroke_style! StrokeStyle.new(sz, @style[:line_style])
2523
+ end
2524
+ when :end, :end_line
2525
+ # The end of the link. Assume that it is the most recent opening
2526
+ # which has closed.
2527
+ start = @links[info[:cbid]]
2528
+ # Add underlining.
2529
+ theta = PDF::Math.deg2rad(start[:angle] - 90.0)
2530
+ if @style[:draw_line]
2531
+ drop = start[:height] * @style[:factor] * 1.5
2532
+ drop_x = Math.cos(theta) * drop
2533
+ drop_y = -Math.sin(theta) * drop
2534
+ pdf.move_to(start[:x] - drop_x, start[:y] - drop_y)
2535
+ pdf.line_to(info[:x] - drop_x, info[:y] - drop_y).stroke
2536
+ end
2537
+ pdf.add_link(start[:uri], start[:x], start[:y] +
2538
+ start[:descender], info[:x], start[:y] +
2539
+ start[:descender] + start[:height])
2540
+ pdf.restore_state
2541
+ end
2542
+ end
2543
+ end
2544
+ end
2545
+ TAGS[:pair]["alink"] = TagAlink
2546
+
2547
+ # A callback for creating and managing links internal to the document.
2548
+ class TagIlink
2549
+ def self.[](pdf, info)
2550
+ case info[:status]
2551
+ when :start, :start_line
2552
+ @links ||= {}
2553
+ @links[info[:cbid]] = {
2554
+ :x => info[:x],
2555
+ :y => info[:y],
2556
+ :angle => info[:angle],
2557
+ :descender => info[:descender],
2558
+ :height => info[:height],
2559
+ :uri => info[:params]["dest"]
2560
+ }
2561
+ when :end, :end_line
2562
+ # The end of the link. Assume that it is the most recent opening
2563
+ # which has closed.
2564
+ start = @links[info[:cbid]]
2565
+ pdf.add_internal_link(start[:uri], start[:x],
2566
+ start[:y] + start[:descender], info[:x],
2567
+ start[:y] + start[:descender] +
2568
+ start[:height])
2569
+ end
2570
+ end
2571
+ end
2572
+ TAGS[:pair]["ilink"] = TagIlink
2573
+
2574
+ # A callback to support underlining.
2575
+ class TagUline
2576
+ # The default underline style.
2577
+ DEFAULT_STYLE = {
2578
+ :color => nil,
2579
+ :line_style => { :dash => PDF::Writer::StrokeStyle::SOLID_LINE },
2580
+ :factor => 0.05
2581
+ }
2582
+
2583
+ class << self
2584
+ # Sets the style for <c:uline> callback underlines that follow. This
2585
+ # is expected to be a hash with the following keys:
2586
+ #
2587
+ # <tt>:factor</tt>:: The size of the line, as a multiple of the
2588
+ # text height. Default is 0.05.
2589
+ #
2590
+ # Set this to +nil+ to get the default style.
2591
+ attr_accessor :style
2592
+
2593
+ def [](pdf, info)
2594
+ @style ||= DEFAULT_STYLE.dup
2595
+
2596
+ case info[:status]
2597
+ when :start, :start_line
2598
+ @links ||= {}
2599
+
2600
+ @links[info[:cbid]] = {
2601
+ :x => info[:x],
2602
+ :y => info[:y],
2603
+ :angle => info[:angle],
2604
+ :descender => info[:descender],
2605
+ :height => info[:height],
2606
+ :uri => nil
2607
+ }
2608
+
2609
+ pdf.save_state
2610
+ pdf.stroke_color @style[:color] if @style[:color]
2611
+ sz = info[:height] * @style[:factor]
2612
+ pdf.stroke_style! StrokeStyle.new(sz, @style[:line_style])
2613
+ when :end, :end_line
2614
+ start = @links[info[:cbid]]
2615
+ theta = PDF::Math.deg2rad(start[:angle] - 90.0)
2616
+ drop = start[:height] * @style[:factor] * 1.5
2617
+ drop_x = Math.cos(theta) * drop
2618
+ drop_y = -Math.sin(theta) * drop
2619
+ pdf.move_to(start[:x] - drop_x, start[:y] - drop_y)
2620
+ pdf.line_to(info[:x] - drop_x, info[:y] - drop_y).stroke
2621
+ pdf.restore_state
2622
+ end
2623
+ end
2624
+ end
2625
+ end
2626
+ TAGS[:pair]["uline"] = TagUline
2627
+
2628
+ # A callback function to support drawing of a solid bullet style. Use
2629
+ # with <C:bullet>.
2630
+ class TagBullet
2631
+ # The default bullet color.
2632
+ DEFAULT_COLOR = Color::RGB::Black
2633
+
2634
+ class << self
2635
+ # Sets the style for <C:bullet> callback bullets that follow.
2636
+ # Default is Color::RGB::Black.
2637
+ #
2638
+ # Set this to +nil+ to get the default colour.
2639
+ attr_accessor :color
2640
+ def [](pdf, info)
2641
+ @color ||= DEFAULT_COLOR
2642
+
2643
+ desc = info[:descender].abs
2644
+ xpos = info[:x] - (desc * 2.00)
2645
+ ypos = info[:y] + (desc * 1.05)
2646
+
2647
+ pdf.save_state
2648
+ ss = StrokeStyle.new(desc)
2649
+ ss.cap = :butt
2650
+ ss.join = :miter
2651
+ pdf.stroke_style! ss
2652
+ pdf.stroke_color @color
2653
+ pdf.circle_at(xpos, ypos, 1).stroke
2654
+ pdf.restore_state
2655
+ end
2656
+ end
2657
+ end
2658
+ TAGS[:single]["bullet"] = TagBullet
2659
+
2660
+ # A callback function to support drawing of a disc bullet style.
2661
+ class TagDisc
2662
+ # The default disc bullet foreground.
2663
+ DEFAULT_FOREGROUND = Color::RGB::Black
2664
+ # The default disc bullet background.
2665
+ DEFAULT_BACKGROUND = Color::RGB::White
2666
+ class << self
2667
+ # The foreground color for <C:disc> bullets. Default is
2668
+ # Color::RGB::Black.
2669
+ #
2670
+ # Set to +nil+ to get the default color.
2671
+ attr_accessor :foreground
2672
+ # The background color for <C:disc> bullets. Default is
2673
+ # Color::RGB::White.
2674
+ #
2675
+ # Set to +nil+ to get the default color.
2676
+ attr_accessor :background
2677
+ def [](pdf, info)
2678
+ @foreground ||= DEFAULT_FOREGROUND
2679
+ @background ||= DEFAULT_BACKGROUND
2680
+
2681
+ desc = info[:descender].abs
2682
+ xpos = info[:x] - (desc * 2.00)
2683
+ ypos = info[:y] + (desc * 1.05)
2684
+
2685
+ ss = StrokeStyle.new(desc)
2686
+ ss.cap = :butt
2687
+ ss.join = :miter
2688
+ pdf.stroke_style! ss
2689
+ pdf.stroke_color @foreground
2690
+ pdf.circle_at(xpos, ypos, 1).stroke
2691
+ pdf.stroke_color @background
2692
+ pdf.circle_at(xpos, ypos, 0.5).stroke
2693
+ end
2694
+ end
2695
+ end
2696
+ TAGS[:single]["disc"] = TagDisc
2697
+
2698
+ # Opens a new PDF object for operating against. Returns the object's
2699
+ # identifier. To close the object, you'll need to do:
2700
+ # ob = open_new_object # Opens the object
2701
+ # # do stuff here
2702
+ # close_object # Closes the PDF document
2703
+ # # do stuff here
2704
+ # reopen_object(ob) # Reopens the custom object.
2705
+ # close_object # Closes it.
2706
+ # restore_state # Returns full control to the PDF document.
2707
+ #
2708
+ # ... I think. I haven't examined the full details to be sure of what
2709
+ # this is doing, but the code works.
2710
+ def open_new_object
2711
+ save_state
2712
+ oid = open_object
2713
+ close_object
2714
+ add_object(oid)
2715
+ reopen_object(oid)
2716
+ oid
2717
+ end
2718
+
2719
+ # Save the PDF as a file to disk.
2720
+ def save_as(name)
2721
+ File.open(name, "wb") { |f| f.write self.render }
2722
+ end
2723
+
2724
+ # memory improvement for transaction-simple
2725
+ def _post_transaction_rewind
2726
+ @objects.each { |e| e.instance_variable_set(:@parent,self) }
2727
+ end
2728
+
2729
+ end