giga-fast-kit 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. checksums.yaml +7 -0
  2. data/giga-fast-kit.gemspec +12 -0
  3. data/prawn-2.5.0/COPYING +2 -0
  4. data/prawn-2.5.0/GPLv2 +339 -0
  5. data/prawn-2.5.0/GPLv3 +674 -0
  6. data/prawn-2.5.0/LICENSE +56 -0
  7. data/prawn-2.5.0/data/fonts/Courier-Bold.afm +342 -0
  8. data/prawn-2.5.0/data/fonts/Courier-BoldOblique.afm +342 -0
  9. data/prawn-2.5.0/data/fonts/Courier-Oblique.afm +342 -0
  10. data/prawn-2.5.0/data/fonts/Courier.afm +342 -0
  11. data/prawn-2.5.0/data/fonts/Helvetica-Bold.afm +2827 -0
  12. data/prawn-2.5.0/data/fonts/Helvetica-BoldOblique.afm +2827 -0
  13. data/prawn-2.5.0/data/fonts/Helvetica-Oblique.afm +3051 -0
  14. data/prawn-2.5.0/data/fonts/Helvetica.afm +3051 -0
  15. data/prawn-2.5.0/data/fonts/MustRead.html +19 -0
  16. data/prawn-2.5.0/data/fonts/Symbol.afm +213 -0
  17. data/prawn-2.5.0/data/fonts/Times-Bold.afm +2588 -0
  18. data/prawn-2.5.0/data/fonts/Times-BoldItalic.afm +2384 -0
  19. data/prawn-2.5.0/data/fonts/Times-Italic.afm +2667 -0
  20. data/prawn-2.5.0/data/fonts/Times-Roman.afm +2419 -0
  21. data/prawn-2.5.0/data/fonts/ZapfDingbats.afm +225 -0
  22. data/prawn-2.5.0/lib/prawn/document/bounding_box.rb +625 -0
  23. data/prawn-2.5.0/lib/prawn/document/column_box.rb +177 -0
  24. data/prawn-2.5.0/lib/prawn/document/internals.rb +76 -0
  25. data/prawn-2.5.0/lib/prawn/document/span.rb +65 -0
  26. data/prawn-2.5.0/lib/prawn/document.rb +846 -0
  27. data/prawn-2.5.0/lib/prawn/encoding.rb +85 -0
  28. data/prawn-2.5.0/lib/prawn/errors.rb +73 -0
  29. data/prawn-2.5.0/lib/prawn/font.rb +567 -0
  30. data/prawn-2.5.0/lib/prawn/font_metric_cache.rb +50 -0
  31. data/prawn-2.5.0/lib/prawn/fonts/afm.rb +332 -0
  32. data/prawn-2.5.0/lib/prawn/fonts/dfont.rb +42 -0
  33. data/prawn-2.5.0/lib/prawn/fonts/otf.rb +14 -0
  34. data/prawn-2.5.0/lib/prawn/fonts/to_unicode_cmap.rb +151 -0
  35. data/prawn-2.5.0/lib/prawn/fonts/ttc.rb +41 -0
  36. data/prawn-2.5.0/lib/prawn/fonts/ttf.rb +615 -0
  37. data/prawn-2.5.0/lib/prawn/fonts.rb +14 -0
  38. data/prawn-2.5.0/lib/prawn/graphics/blend_mode.rb +62 -0
  39. data/prawn-2.5.0/lib/prawn/graphics/cap_style.rb +44 -0
  40. data/prawn-2.5.0/lib/prawn/graphics/color.rb +257 -0
  41. data/prawn-2.5.0/lib/prawn/graphics/dash.rb +123 -0
  42. data/prawn-2.5.0/lib/prawn/graphics/join_style.rb +60 -0
  43. data/prawn-2.5.0/lib/prawn/graphics/patterns.rb +389 -0
  44. data/prawn-2.5.0/lib/prawn/graphics/transformation.rb +172 -0
  45. data/prawn-2.5.0/lib/prawn/graphics/transparency.rb +79 -0
  46. data/prawn-2.5.0/lib/prawn/graphics.rb +791 -0
  47. data/prawn-2.5.0/lib/prawn/grid.rb +449 -0
  48. data/prawn-2.5.0/lib/prawn/image_handler.rb +61 -0
  49. data/prawn-2.5.0/lib/prawn/images/image.rb +48 -0
  50. data/prawn-2.5.0/lib/prawn/images/jpg.rb +121 -0
  51. data/prawn-2.5.0/lib/prawn/images/png.rb +383 -0
  52. data/prawn-2.5.0/lib/prawn/images.rb +195 -0
  53. data/prawn-2.5.0/lib/prawn/measurement_extensions.rb +82 -0
  54. data/prawn-2.5.0/lib/prawn/measurements.rb +129 -0
  55. data/prawn-2.5.0/lib/prawn/outline.rb +316 -0
  56. data/prawn-2.5.0/lib/prawn/repeater.rb +140 -0
  57. data/prawn-2.5.0/lib/prawn/security/arcfour.rb +54 -0
  58. data/prawn-2.5.0/lib/prawn/security.rb +308 -0
  59. data/prawn-2.5.0/lib/prawn/soft_mask.rb +105 -0
  60. data/prawn-2.5.0/lib/prawn/stamp.rb +147 -0
  61. data/prawn-2.5.0/lib/prawn/text/box.rb +192 -0
  62. data/prawn-2.5.0/lib/prawn/text/formatted/arranger.rb +396 -0
  63. data/prawn-2.5.0/lib/prawn/text/formatted/box.rb +725 -0
  64. data/prawn-2.5.0/lib/prawn/text/formatted/fragment.rb +373 -0
  65. data/prawn-2.5.0/lib/prawn/text/formatted/line_wrap.rb +335 -0
  66. data/prawn-2.5.0/lib/prawn/text/formatted/parser.rb +284 -0
  67. data/prawn-2.5.0/lib/prawn/text/formatted/wrap.rb +166 -0
  68. data/prawn-2.5.0/lib/prawn/text/formatted.rb +82 -0
  69. data/prawn-2.5.0/lib/prawn/text.rb +701 -0
  70. data/prawn-2.5.0/lib/prawn/transformation_stack.rb +63 -0
  71. data/prawn-2.5.0/lib/prawn/utilities.rb +34 -0
  72. data/prawn-2.5.0/lib/prawn/version.rb +6 -0
  73. data/prawn-2.5.0/lib/prawn/view.rb +116 -0
  74. data/prawn-2.5.0/lib/prawn.rb +83 -0
  75. metadata +114 -0
@@ -0,0 +1,846 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'stringio'
4
+
5
+ require_relative 'document/bounding_box'
6
+ require_relative 'document/column_box'
7
+ require_relative 'document/internals'
8
+ require_relative 'document/span'
9
+
10
+ module Prawn
11
+ # The `Prawn::Document` class is how you start creating a PDF document.
12
+ #
13
+ # There are three basic ways you can instantiate PDF Documents in Prawn, they
14
+ # are through assignment, implicit block or explicit block. Below is an
15
+ # example of each type, each example does exactly the same thing, makes a PDF
16
+ # document with all the defaults and puts in the default font "Hello There"
17
+ # and then saves it to the current directory as _example.pdf_.
18
+ #
19
+ # For example, assignment can be like this:
20
+ #
21
+ # ```ruby
22
+ # pdf = Prawn::Document.new
23
+ # pdf.text "Hello There"
24
+ # pdf.render_file "example.pdf"
25
+ # ```
26
+ #
27
+ # Or you can do an implied block form:
28
+ #
29
+ # ```ruby
30
+ # Prawn::Document.generate "example.pdf" do
31
+ # text "Hello There"
32
+ # end
33
+ # ```
34
+ #
35
+ # Or if you need to access a variable outside the scope of the block, the
36
+ # explicit block form:
37
+ #
38
+ # ```ruby
39
+ # words = "Hello There"
40
+ # Prawn::Document.generate "example.pdf" do |pdf|
41
+ # pdf.text words
42
+ # end
43
+ # ```
44
+ #
45
+ # Usually, the block forms are used when you are simply creating a PDF
46
+ # document that you want to immediately save or render out.
47
+ #
48
+ # See the {#initialize new} and {.generate generate} methods for further
49
+ # details on the above.
50
+ class Document
51
+ include Prawn::Document::Internals
52
+ include PDF::Core::Annotations
53
+ include PDF::Core::Destinations
54
+ include Prawn::Document::Security
55
+ include Prawn::Text
56
+ include Prawn::Graphics
57
+ include Prawn::Images
58
+ include Prawn::Stamp
59
+ include Prawn::SoftMask
60
+ include Prawn::TransformationStack
61
+
62
+ alias inspect to_s
63
+
64
+ # @group Extension API
65
+
66
+ # NOTE: We probably need to rethink the options validation system, but this
67
+ # constant temporarily allows for extensions to modify the list.
68
+
69
+ # List of recognised options.
70
+ VALID_OPTIONS = %i[
71
+ page_size page_layout margin left_margin
72
+ right_margin top_margin bottom_margin skip_page_creation
73
+ compress background info
74
+ text_formatter print_scaling
75
+ ].freeze
76
+
77
+ # Any module added to this array will be included into instances of
78
+ # {Prawn::Document} at the per-object level. These will also be inherited
79
+ # by any subclasses.
80
+ #
81
+ # @example
82
+ # module MyFancyModule
83
+ # def party!
84
+ # text "It's a big party!"
85
+ # end
86
+ # end
87
+ #
88
+ # Prawn::Document.extensions << MyFancyModule
89
+ #
90
+ # Prawn::Document.generate("foo.pdf") do
91
+ # party!
92
+ # end
93
+ #
94
+ # @return [Array<Module>]
95
+ def self.extensions
96
+ @extensions ||= []
97
+ end
98
+
99
+ # @private
100
+ def self.inherited(base)
101
+ super
102
+ extensions.each { |e| base.extensions << e }
103
+ end
104
+
105
+ # @group Stable Attributes
106
+
107
+ # Current margin box.
108
+ # @return [Prawn::Document::BoundingBox]
109
+ attr_accessor :margin_box
110
+
111
+ # Current page margins.
112
+ # @return [{:left, :top, :right, :bottom => Number}]
113
+ attr_reader :margins
114
+
115
+ # Absolute cursor position.
116
+ # @return [Number]
117
+ attr_reader :y
118
+
119
+ # Current page number.
120
+ # @return [Integer]
121
+ attr_accessor :page_number
122
+
123
+ # @group Extension Attributes
124
+
125
+ # Current text formatter. By default it's {Text::Formatted::Parser}
126
+ # @return [Object]
127
+ attr_accessor :text_formatter
128
+
129
+ # @group Stable API
130
+
131
+ # Creates and renders a PDF document.
132
+ #
133
+ # When using the implicit block form, Prawn will evaluate the block
134
+ # within an instance of {Prawn::Document}, simplifying your syntax.
135
+ # However, please note that you will not be able to reference variables
136
+ # from the enclosing scope within this block.
137
+ #
138
+ # ```ruby
139
+ # # Using implicit block form and rendering to a file
140
+ # Prawn::Document.generate "example.pdf" do
141
+ # # self here is set to the newly instantiated Prawn::Document
142
+ # # and so any variables in the outside scope are unavailable
143
+ # font "Times-Roman"
144
+ # draw_text "Hello World", at: [200,720], size: 32
145
+ # end
146
+ # ```
147
+ #
148
+ # If you need to access your local and instance variables, use the explicit
149
+ # block form shown below. In this case, Prawn yields an instance of
150
+ # {Prawn::Document} and the block is an ordinary closure:
151
+ #
152
+ # ```ruby
153
+ # # Using explicit block form and rendering to a file
154
+ # content = "Hello World"
155
+ # Prawn::Document.generate "example.pdf" do |pdf|
156
+ # # self here is left alone
157
+ # pdf.font "Times-Roman"
158
+ # pdf.draw_text content, at: [200,720], size: 32
159
+ # end
160
+ # ```
161
+ def self.generate(filename, options = {}, &block)
162
+ pdf = new(options, &block)
163
+ pdf.render_file(filename)
164
+ end
165
+
166
+ # Creates a new PDF Document.
167
+ #
168
+ # Setting e.g. the `:margin` to 100 points and the `:left_margin` to 50 will
169
+ # result in margins of 100 points on every side except for the left, where
170
+ # it will be 50.
171
+ #
172
+ # The `:margin` can also be an array much like CSS shorthand:
173
+ #
174
+ # ```ruby
175
+ # # Top and bottom are 20, left and right are 100.
176
+ # margin: [20, 100]
177
+ # # Top is 50, left and right are 100, bottom is 20.
178
+ # margin: [50, 100, 20]
179
+ # # Top is 10, right is 20, bottom is 30, left is 40.
180
+ # margin: [10, 20, 30, 40]
181
+ # ```
182
+ #
183
+ # Additionally, `:page_size` can be specified as a simple two value array
184
+ # giving the width and height of the document you need in PDF Points.
185
+ #
186
+ # @example New document, US Letter paper, portrait orientation
187
+ # pdf = Prawn::Document.new
188
+ #
189
+ # @example New document, A4 paper, landscaped
190
+ # pdf = Prawn::Document.new(page_size: "A4", page_layout: :landscape)
191
+ #
192
+ # @example New document, Custom size
193
+ # pdf = Prawn::Document.new(page_size: [200, 300])
194
+ #
195
+ # @example New document, with background
196
+ # pdf = Prawn::Document.new(
197
+ # background: "#{Prawn::DATADIR}/images/pigs.jpg"
198
+ # )
199
+ #
200
+ # @param options [Hash{Symbol => any}]
201
+ # @option options :page_size [String, Array(Number, Number)] (LETTER)
202
+ # One of the `PDF::Core::PageGeometry` sizes.
203
+ # @option options :page_layout [:portrait, :landscape]
204
+ # Page orientation.
205
+ # @option options :margin [Number, Array<Number>] ([32])
206
+ # Sets the margin on all sides in points.
207
+ # @option options :left_margin [Number] (32)
208
+ # Sets the left margin in points.
209
+ # @option options :right_margin [Number] (32)
210
+ # Sets the right margin in points.
211
+ # @option options :top_margin [Number] (32)
212
+ # Sets the top margin in points.
213
+ # @option options :bottom_margin [Number] (32)
214
+ # Sets the bottom margin in points.
215
+ # @option options :skip_page_creation [Boolean] (false)
216
+ # Creates a document without starting the first page.
217
+ # @option options :compress [Boolean] (false)
218
+ # Compresses content streams before rendering them.
219
+ # @option options :background [String?] (nil)
220
+ # An image path to be used as background on all pages.
221
+ # @option options :background_scale [Number?] (1)
222
+ # Background image scale.
223
+ # @option options :info [Hash{Symbol => any}?] (nil)
224
+ # Generic hash allowing for custom metadata properties.
225
+ # @option options :text_formatter [Object] (Prawn::Text::Formatted::Parser)
226
+ # The text formatter to use for `:inline_format`ted text.
227
+ def initialize(options = {}, &block)
228
+ options = options.dup
229
+
230
+ Prawn.verify_options(VALID_OPTIONS, options)
231
+
232
+ # need to fix, as the refactoring breaks this
233
+ # raise NotImplementedError if options[:skip_page_creation]
234
+
235
+ self.class.extensions.reverse_each { |e| extend(e) }
236
+ self.state = PDF::Core::DocumentState.new(options)
237
+ state.populate_pages_from_store(self)
238
+ renderer.min_version(state.store.min_version) if state.store.min_version
239
+
240
+ renderer.min_version(1.6) if options[:print_scaling] == :none
241
+
242
+ @background = options[:background]
243
+ @background_scale = options[:background_scale] || 1
244
+ @font_size = 12
245
+
246
+ @bounding_box = nil
247
+ @margin_box = nil
248
+
249
+ @page_number = 0
250
+
251
+ @text_formatter = options.delete(:text_formatter) ||
252
+ Text::Formatted::Parser
253
+
254
+ options[:size] = options.delete(:page_size)
255
+ options[:layout] = options.delete(:page_layout)
256
+
257
+ initialize_first_page(options)
258
+
259
+ @bounding_box = @margin_box
260
+
261
+ if block
262
+ block.arity < 1 ? instance_eval(&block) : block[self]
263
+ end
264
+ end
265
+
266
+ # @group Stable API
267
+
268
+ # Creates and advances to a new page in the document.
269
+ #
270
+ # Page size, margins, and layout can also be set when generating a
271
+ # new page. These values will become the new defaults for page creation.
272
+ #
273
+ # @example
274
+ # pdf.start_new_page #=> Starts new page keeping current values
275
+ # pdf.start_new_page(size: "LEGAL", :layout => :landscape)
276
+ # pdf.start_new_page(left_margin: 50, right_margin: 50)
277
+ # pdf.start_new_page(margin: 100)
278
+ #
279
+ # @param options [Hash]
280
+ # @option options :margins [Hash{:left, :right, :top, :bottom => Number}, nil]
281
+ # ({ left: 0, right: 0, top: 0, bottom: 0 }) Page margins
282
+ # @option options :crop [Hash{:left, :right, :top, :bottom => Number}, nil] (PDF::Core::Page::ZERO_INDENTS)
283
+ # Page crop box
284
+ # @option options :bleed [Hash{:left, :right, :top, :bottom => Number}, nil] (PDF::Core::Page::ZERO_INDENTS)
285
+ # Page bleed box
286
+ # @option options :trims [Hash{:left, :right, :top, :bottom => Number}, nil] (PDF::Core::Page::ZERO_INDENTS)
287
+ # Page trim box
288
+ # @option options :art_indents [Hash{:left, :right, :top, :bottom => Number}, nil] (PDF::Core::Page::ZERO_INDENTS)
289
+ # Page art box indents.
290
+ # @option options :graphic_state [PDF::Core::GraphicState, nil] (nil)
291
+ # Initial graphic state
292
+ # @option options :size [String, Array<Number>, nil] ('LETTER')
293
+ # Page size. A string identifies a named page size defined in
294
+ # `PDF::Core::PageGeometry`. An array must be a two element array
295
+ # specifying width and height in points.
296
+ # @option options :layout [:portrait, :landscape, nil] (:portrait)
297
+ # Page orientation.
298
+ # @return [void]
299
+ def start_new_page(options = {})
300
+ last_page = state.page
301
+ if last_page
302
+ last_page_size = last_page.size
303
+ last_page_layout = last_page.layout
304
+ last_page_margins = last_page.margins.dup
305
+ end
306
+
307
+ page_options = {
308
+ size: options[:size] || last_page_size,
309
+ layout: options[:layout] || last_page_layout,
310
+ margins: last_page_margins,
311
+ }
312
+ if last_page
313
+ if last_page.graphic_state
314
+ new_graphic_state = last_page.graphic_state.dup
315
+ end
316
+
317
+ # erase the color space so that it gets reset on new page for fussy
318
+ # pdf-readers
319
+ new_graphic_state&.color_space = {}
320
+
321
+ page_options[:graphic_state] = new_graphic_state
322
+ end
323
+
324
+ state.page = PDF::Core::Page.new(self, page_options)
325
+
326
+ apply_margin_options(options)
327
+ generate_margin_box
328
+
329
+ # Reset the bounding box if the new page has different size or layout
330
+ if last_page && (last_page.size != state.page.size ||
331
+ last_page.layout != state.page.layout)
332
+ @bounding_box = @margin_box
333
+ end
334
+
335
+ use_graphic_settings
336
+
337
+ unless options[:orphan]
338
+ state.insert_page(state.page, @page_number)
339
+ @page_number += 1
340
+
341
+ if @background
342
+ canvas do
343
+ image(@background, scale: @background_scale, at: bounds.top_left)
344
+ end
345
+ end
346
+ @y = @bounding_box.absolute_top
347
+
348
+ float do
349
+ state.on_page_create_action(self)
350
+ end
351
+ end
352
+ end
353
+
354
+ # Remove page of the document by index.
355
+ #
356
+ # @example
357
+ # pdf = Prawn::Document.new
358
+ # pdf.page_count #=> 1
359
+ # 3.times { pdf.start_new_page }
360
+ # pdf.page_count #=> 4
361
+ # pdf.delete_page(-1)
362
+ # pdf.page_count #=> 3
363
+ #
364
+ # @param index [Integer]
365
+ # @return [Boolean]
366
+ def delete_page(index)
367
+ return false if index.abs > (state.pages.count - 1)
368
+
369
+ state.pages.delete_at(index)
370
+
371
+ state.store.pages.data[:Kids].delete_at(index)
372
+ state.store.pages.data[:Count] -= 1
373
+ @page_number -= 1
374
+ true
375
+ end
376
+
377
+ # Number of pages in the document.
378
+ #
379
+ # @example
380
+ # pdf = Prawn::Document.new
381
+ # pdf.page_count #=> 1
382
+ # 3.times { pdf.start_new_page }
383
+ # pdf.page_count #=> 4
384
+ #
385
+ # @return [Integer]
386
+ def page_count
387
+ state.page_count
388
+ end
389
+
390
+ # Re-opens the page with the given (1-based) page number so that you can
391
+ # draw on it.
392
+ #
393
+ # @param page_number [Integer]
394
+ # @return [void]
395
+ def go_to_page(page_number)
396
+ @page_number = page_number
397
+ state.page = state.pages[page_number - 1]
398
+ generate_margin_box
399
+ @y = @bounding_box.absolute_top
400
+ end
401
+
402
+ # Set cursor absolute position.
403
+ #
404
+ # @param new_y [Number]
405
+ # @return [new_y]
406
+ def y=(new_y)
407
+ @y = new_y
408
+ bounds.update_height
409
+ end
410
+
411
+ # The current y drawing position relative to the innermost bounding box,
412
+ # or to the page margins at the top level.
413
+ #
414
+ # @return [Number]
415
+ def cursor
416
+ y - bounds.absolute_bottom
417
+ end
418
+
419
+ # Moves to the specified y position in relative terms to the bottom margin.
420
+ #
421
+ # @param new_y [Number]
422
+ # @return [void]
423
+ def move_cursor_to(new_y)
424
+ self.y = new_y + bounds.absolute_bottom
425
+ end
426
+
427
+ # Executes a block and then restores the original y position. If new pages
428
+ # were created during this block, it will teleport back to the original
429
+ # page when done.
430
+ #
431
+ # @example
432
+ # pdf.text "A"
433
+ #
434
+ # pdf.float do
435
+ # pdf.move_down 100
436
+ # pdf.text "C"
437
+ # end
438
+ #
439
+ # pdf.text "B"
440
+ #
441
+ # @return [void]
442
+ def float
443
+ original_page = page_number
444
+ original_y = y
445
+ yield
446
+ go_to_page(original_page) unless page_number == original_page
447
+ self.y = original_y
448
+ end
449
+
450
+ # Renders the PDF document to string.
451
+ # Pass an open file descriptor to render to file.
452
+ #
453
+ # @overload render(output = nil)
454
+ # @param output [#<<]
455
+ # @return [String]
456
+ def render(*arguments)
457
+ (1..page_count).each do |i|
458
+ go_to_page(i)
459
+ repeaters.each { |r| r.run(i) }
460
+ end
461
+
462
+ renderer.render(*arguments)
463
+ end
464
+
465
+ # Renders the PDF document to file.
466
+ #
467
+ # @example
468
+ # pdf.render_file "foo.pdf"
469
+ #
470
+ # @param filename [String]
471
+ # @return [void]
472
+ def render_file(filename)
473
+ File.open(filename, 'wb') { |f| render(f) }
474
+ end
475
+
476
+ # The bounds method returns the current bounding box you are currently in,
477
+ # which is by default the box represented by the margin box on the
478
+ # document itself. When called from within a created `bounding_box`
479
+ # block, the box defined by that call will be returned instead of the
480
+ # document margin box.
481
+ #
482
+ # Another important point about bounding boxes is that all x and
483
+ # y measurements within a bounding box code block are relative to the bottom
484
+ # left corner of the bounding box.
485
+ #
486
+ # @example
487
+ # Prawn::Document.new do
488
+ # # In the default "margin box" of a Prawn document of 0.5in along each
489
+ # # edge
490
+ #
491
+ # # Draw a border around the page (the manual way)
492
+ # stroke do
493
+ # line(bounds.bottom_left, bounds.bottom_right)
494
+ # line(bounds.bottom_right, bounds.top_right)
495
+ # line(bounds.top_right, bounds.top_left)
496
+ # line(bounds.top_left, bounds.bottom_left)
497
+ # end
498
+ #
499
+ # # Draw a border around the page (the easy way)
500
+ # stroke_bounds
501
+ # end
502
+ #
503
+ # @return [Prawn::Document::BoundingBox]
504
+ def bounds
505
+ @bounding_box
506
+ end
507
+
508
+ # Returns the innermost non-stretchy bounding box.
509
+ #
510
+ # @private
511
+ # @return [Prawn::Document::BoundingBox]
512
+ def reference_bounds
513
+ @bounding_box.reference_bounds
514
+ end
515
+
516
+ # Sets {Document#bounds} to the {BoundingBox} provided. See {#bounds} for
517
+ # a brief description of what a bounding box is. This function is useful if
518
+ # you really need to change the bounding box manually, but usually, just
519
+ # entering and exiting bounding box code blocks is good enough.
520
+ #
521
+ # @param bounding_box [Prawn::Document::BoundingBox]
522
+ # @return [bounding_box]
523
+ def bounds=(bounding_box)
524
+ @bounding_box = bounding_box
525
+ end
526
+
527
+ # Moves up the document by n points relative to the current position inside
528
+ # the current bounding box.
529
+ #
530
+ # @param amount [Number]
531
+ # @return [void]
532
+ def move_up(amount)
533
+ self.y += amount
534
+ end
535
+
536
+ # Moves down the document by n points relative to the current position
537
+ # inside the current bounding box.
538
+ #
539
+ # @param amount [Number]
540
+ # @return [void]
541
+ def move_down(amount)
542
+ self.y -= amount
543
+ end
544
+
545
+ # Moves down the document and then executes a block.
546
+ #
547
+ # @example
548
+ # pdf.text "some text"
549
+ # pdf.pad_top(100) do
550
+ # pdf.text "This is 100 points below the previous line of text"
551
+ # end
552
+ # pdf.text "This text appears right below the previous line of text"
553
+ #
554
+ # @param y [Number]
555
+ # @return [void]
556
+ # @yield
557
+ def pad_top(y)
558
+ move_down(y)
559
+ yield
560
+ end
561
+
562
+ # Executes a block then moves down the document
563
+ #
564
+ # @example
565
+ # pdf.text "some text"
566
+ # pdf.pad_bottom(100) do
567
+ # pdf.text "This text appears right below the previous line of text"
568
+ # end
569
+ # pdf.text "This is 100 points below the previous line of text"
570
+ #
571
+ # @param y [Number]
572
+ # @return [void]
573
+ # @yield
574
+ def pad_bottom(y)
575
+ yield
576
+ move_down(y)
577
+ end
578
+
579
+ # Moves down the document by y, executes a block, then moves down the
580
+ # document by y again.
581
+ #
582
+ # @example
583
+ # pdf.text "some text"
584
+ # pdf.pad(100) do
585
+ # pdf.text "This is 100 points below the previous line of text"
586
+ # end
587
+ # pdf.text "This is 100 points below the previous line of text"
588
+ #
589
+ # @param y [Number]
590
+ # @return [void]
591
+ # @yield
592
+ def pad(y)
593
+ move_down(y)
594
+ yield
595
+ move_down(y)
596
+ end
597
+
598
+ # Indents the specified number of PDF points for the duration of the block
599
+ #
600
+ # @example
601
+ # pdf.text "some text"
602
+ # pdf.indent(20) do
603
+ # pdf.text "This is indented 20 points"
604
+ # end
605
+ # pdf.text "This starts 20 points left of the above line " +
606
+ # "and is flush with the first line"
607
+ # pdf.indent 20, 20 do
608
+ # pdf.text "This line is indented on both sides."
609
+ # end
610
+ #
611
+ # @param left [Number]
612
+ # @param right [Number]
613
+ # @yield
614
+ # @return [void]
615
+ def indent(left, right = 0, &block)
616
+ bounds.indent(left, right, &block)
617
+ end
618
+
619
+ # Places a text box on specified pages for page numbering. This should be
620
+ # called towards the end of document creation, after all your content is
621
+ # already in place. In your template string, `<page>` refers to the current
622
+ # page, and `<total>` refers to the total amount of pages in the document.
623
+ # Page numbering should occur at the end of your {Prawn::Document.generate}
624
+ # block because the method iterates through existing pages after they are
625
+ # created.
626
+ #
627
+ # Please refer to {Prawn::Text#text_box} for additional options concerning
628
+ # text formatting and placement.
629
+ #
630
+ # @example Print page numbers on every page except for the first. Start counting from five.
631
+ # Prawn::Document.generate("page_with_numbering.pdf") do
632
+ # number_pages "<page> in a total of <total>", {
633
+ # start_count_at: 5,
634
+ # page_filter: lambda { |pg| pg != 1 },
635
+ # at: [bounds.right - 50, 0],
636
+ # align: :right,
637
+ # size: 14
638
+ # }
639
+ # end
640
+ #
641
+ # @param string [String] Template string for page number wording.
642
+ # Should include `<page>` and, optionally, `<total>`.
643
+ # @param options [Hash{Symbol => any}] A hash for page numbering and text box options.
644
+ # @option options :page_filter []
645
+ # A filter to specify which pages to place page numbers on. Refer to the method {#page_match?}
646
+ # @option options :start_count_at [Integer]
647
+ # The starting count to increment pages from.
648
+ # @option options :total_pages [Integer]
649
+ # If provided, will replace `<total>` with the value given. Useful to
650
+ # override the total number of pages when using the start_count_at option.
651
+ # @option options :color [String, Array<Number>] Text fill color.
652
+ def number_pages(string, options = {})
653
+ opts = options.dup
654
+ start_count_at = opts.delete(:start_count_at)
655
+
656
+ page_filter =
657
+ if opts.key?(:page_filter)
658
+ opts.delete(:page_filter)
659
+ else
660
+ :all
661
+ end
662
+
663
+ total_pages = opts.delete(:total_pages)
664
+ txtcolor = opts.delete(:color)
665
+ # An explicit height so that we can draw page numbers in the margins
666
+ opts[:height] = 50 unless opts.key?(:height)
667
+
668
+ start_count = false
669
+ pseudopage = 0
670
+ (1..page_count).each do |p|
671
+ unless start_count
672
+ pseudopage =
673
+ case start_count_at
674
+ when String
675
+ Integer(start_count_at, 10)
676
+ when (1..)
677
+ Integer(start_count_at)
678
+ else
679
+ 1
680
+ end
681
+ end
682
+ if page_match?(page_filter, p)
683
+ go_to_page(p)
684
+ # have to use fill_color here otherwise text reverts back to default
685
+ # fill color
686
+ fill_color(txtcolor) unless txtcolor.nil?
687
+ total_pages = page_count if total_pages.nil?
688
+ str = string.gsub('<page>', pseudopage.to_s)
689
+ .gsub('<total>', total_pages.to_s)
690
+ text_box(str, opts)
691
+ start_count = true # increment page count as soon as first match found
692
+ end
693
+ pseudopage += 1 if start_count
694
+ end
695
+ end
696
+
697
+ # @group Experimental API
698
+
699
+ # @private
700
+ def group(*_arguments)
701
+ raise NotImplementedError,
702
+ 'Document#group has been disabled because its implementation ' \
703
+ 'lead to corrupted documents whenever a page boundary was ' \
704
+ 'crossed. We will try to work on reimplementing it in a ' \
705
+ 'future release'
706
+ end
707
+
708
+ # @private
709
+ def transaction
710
+ raise NotImplementedError,
711
+ 'Document#transaction has been disabled because its implementation ' \
712
+ 'lead to corrupted documents whenever a page boundary was ' \
713
+ 'crossed. We will try to work on reimplementing it in a ' \
714
+ 'future release'
715
+ end
716
+
717
+ # Provides a way to execute a block of code repeatedly based on a
718
+ # page_filter.
719
+ #
720
+ # Available page filters are:
721
+ # :all repeats on every page
722
+ # :odd repeats on odd pages
723
+ #
724
+ # @param page_filter [:all, :odd, :even, Array<Number>, Range, Proc]
725
+ # * `:all`: repeats on every page
726
+ # * `:odd`: repeats on odd pages
727
+ # * `:even`: repeats on even pages
728
+ # * array: repeats on every page listed in the array
729
+ # * range: repeats on every page included in the range
730
+ # * lambda: yields page number and repeats for true return values
731
+ # @param page_number [Integer]
732
+ # @return [Boolean]
733
+ def page_match?(page_filter, page_number)
734
+ case page_filter
735
+ when :all
736
+ true
737
+ when :odd
738
+ page_number.odd?
739
+ when :even
740
+ page_number.even?
741
+ when Range, Array
742
+ page_filter.include?(page_number)
743
+ when Proc
744
+ page_filter.call(page_number)
745
+ end
746
+ end
747
+
748
+ # @private
749
+ def mask(*fields)
750
+ # Stores the current state of the named attributes, executes the block,
751
+ # and then restores the original values after the block has executed.
752
+ # -- I will remove the nodoc if/when this feature is a little less hacky
753
+ stored = {}
754
+ fields.each { |f| stored[f] = public_send(f) }
755
+ yield
756
+ fields.each { |f| public_send(:"#{f}=", stored[f]) }
757
+ end
758
+
759
+ # @group Extension API
760
+
761
+ # Initializes the first page in a new document.
762
+ # This methods allows customisation of this process in extensions such as
763
+ # Prawn::Template.
764
+ #
765
+ # @param options [Hash]
766
+ # @return [void]
767
+ def initialize_first_page(options)
768
+ if options[:skip_page_creation]
769
+ start_new_page(options.merge(orphan: true))
770
+ else
771
+ start_new_page(options)
772
+ end
773
+ end
774
+
775
+ ## Internals. Don't depend on them!
776
+
777
+ # @private
778
+ attr_accessor :state
779
+
780
+ # @private
781
+ def page
782
+ state.page
783
+ end
784
+
785
+ private
786
+
787
+ # setting override_settings to true ensures that a new graphic state does
788
+ # not end up using previous settings.
789
+ def use_graphic_settings(override_settings = false)
790
+ set_fill_color if current_fill_color != '000000' || override_settings
791
+ set_stroke_color if current_stroke_color != '000000' || override_settings
792
+ write_line_width if line_width != 1 || override_settings
793
+ write_stroke_cap_style if cap_style != :butt || override_settings
794
+ write_stroke_join_style if join_style != :miter || override_settings
795
+ write_stroke_dash if dashed? || override_settings
796
+ end
797
+
798
+ def generate_margin_box
799
+ old_margin_box = @margin_box
800
+ page = state.page
801
+
802
+ @margin_box = BoundingBox.new(
803
+ self,
804
+ nil, # margin box has no parent
805
+ [page.margins[:left], page.dimensions[-1] - page.margins[:top]],
806
+ width: page.dimensions[-2] -
807
+ (page.margins[:left] + page.margins[:right]),
808
+ height: page.dimensions[-1] -
809
+ (page.margins[:top] + page.margins[:bottom]),
810
+ )
811
+
812
+ # This check maintains indentation settings across page breaks
813
+ if old_margin_box
814
+ @margin_box.add_left_padding(old_margin_box.total_left_padding)
815
+ @margin_box.add_right_padding(old_margin_box.total_right_padding)
816
+ end
817
+
818
+ # we must update bounding box if not flowing from the previous page
819
+ #
820
+ @bounding_box = @margin_box unless @bounding_box&.parent
821
+ end
822
+
823
+ def apply_margin_options(options)
824
+ sides = %i[top right bottom left]
825
+ margin = Array(options[:margin])
826
+
827
+ # Treat :margin as CSS shorthand with 1-4 values.
828
+ positions = {
829
+ 4 => [0, 1, 2, 3],
830
+ 3 => [0, 1, 2, 1],
831
+ 2 => [0, 1, 0, 1],
832
+ 1 => [0, 0, 0, 0],
833
+ 0 => [],
834
+ }[margin.length]
835
+
836
+ sides.zip(positions).each do |side, pos|
837
+ new_margin = options[:"#{side}_margin"] || (margin[pos] if pos)
838
+ state.page.margins[side] = new_margin if new_margin
839
+ end
840
+ end
841
+
842
+ def font_metric_cache # :nodoc:
843
+ @font_metric_cache ||= FontMetricCache.new(self)
844
+ end
845
+ end
846
+ end