gamba 2.0.0

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 (163) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +80 -0
  3. data/COPYING +2 -0
  4. data/GPLv2 +339 -0
  5. data/GPLv3 +674 -0
  6. data/LICENSE +65 -0
  7. data/LICENSE-prawn-svg +21 -0
  8. data/README.md +148 -0
  9. data/data/fonts/Courier-Bold.afm +342 -0
  10. data/data/fonts/Courier-BoldOblique.afm +342 -0
  11. data/data/fonts/Courier-Oblique.afm +342 -0
  12. data/data/fonts/Courier.afm +342 -0
  13. data/data/fonts/Helvetica-Bold.afm +2827 -0
  14. data/data/fonts/Helvetica-BoldOblique.afm +2827 -0
  15. data/data/fonts/Helvetica-Oblique.afm +3051 -0
  16. data/data/fonts/Helvetica.afm +3051 -0
  17. data/data/fonts/MustRead.html +19 -0
  18. data/data/fonts/Symbol.afm +213 -0
  19. data/data/fonts/Times-Bold.afm +2588 -0
  20. data/data/fonts/Times-BoldItalic.afm +2384 -0
  21. data/data/fonts/Times-Italic.afm +2667 -0
  22. data/data/fonts/Times-Roman.afm +2419 -0
  23. data/data/fonts/ZapfDingbats.afm +225 -0
  24. data/lib/gamba/annotations.rb +21 -0
  25. data/lib/gamba/combine.rb +307 -0
  26. data/lib/gamba/document/bounding_box.rb +193 -0
  27. data/lib/gamba/document/column_box.rb +91 -0
  28. data/lib/gamba/document/internals.rb +264 -0
  29. data/lib/gamba/document/span.rb +25 -0
  30. data/lib/gamba/document.rb +268 -0
  31. data/lib/gamba/encoding.rb +13 -0
  32. data/lib/gamba/errors.rb +39 -0
  33. data/lib/gamba/font.rb +228 -0
  34. data/lib/gamba/fonts/afm.rb +171 -0
  35. data/lib/gamba/fonts/otf.rb +50 -0
  36. data/lib/gamba/fonts/ttf.rb +413 -0
  37. data/lib/gamba/graphic_state.rb +84 -0
  38. data/lib/gamba/graphics/color.rb +174 -0
  39. data/lib/gamba/graphics/patterns.rb +100 -0
  40. data/lib/gamba/graphics/style.rb +159 -0
  41. data/lib/gamba/graphics.rb +212 -0
  42. data/lib/gamba/grid.rb +216 -0
  43. data/lib/gamba/images/image.rb +271 -0
  44. data/lib/gamba/images/png_decoder.rb +65 -0
  45. data/lib/gamba/images.rb +115 -0
  46. data/lib/gamba/measurement_extensions.rb +40 -0
  47. data/lib/gamba/measurements.rb +62 -0
  48. data/lib/gamba/outline.rb +148 -0
  49. data/lib/gamba/page.rb +125 -0
  50. data/lib/gamba/page_geometry.rb +27 -0
  51. data/lib/gamba/pdf/decryption.rb +165 -0
  52. data/lib/gamba/pdf/outline_node.rb +53 -0
  53. data/lib/gamba/pdf/reader.rb +616 -0
  54. data/lib/gamba/pdf/reference.rb +31 -0
  55. data/lib/gamba/pdf/writer.rb +105 -0
  56. data/lib/gamba/pdf.rb +87 -0
  57. data/lib/gamba/pdf_a/srgb_profile.rb +61 -0
  58. data/lib/gamba/pdf_a.rb +132 -0
  59. data/lib/gamba/repeater.rb +79 -0
  60. data/lib/gamba/security.rb +111 -0
  61. data/lib/gamba/stamp.rb +76 -0
  62. data/lib/gamba/table/cell/kinds.rb +246 -0
  63. data/lib/gamba/table/cell.rb +331 -0
  64. data/lib/gamba/table/cells.rb +208 -0
  65. data/lib/gamba/table.rb +298 -0
  66. data/lib/gamba/text/box.rb +16 -0
  67. data/lib/gamba/text/formatted/arranger.rb +217 -0
  68. data/lib/gamba/text/formatted/box.rb +312 -0
  69. data/lib/gamba/text/formatted/fragment.rb +205 -0
  70. data/lib/gamba/text/formatted/line_wrap.rb +205 -0
  71. data/lib/gamba/text/formatted/parser.rb +150 -0
  72. data/lib/gamba/text/formatted/wrap.rb +103 -0
  73. data/lib/gamba/text.rb +310 -0
  74. data/lib/gamba/transformation_stack.rb +43 -0
  75. data/lib/gamba/true_type/cff_subset.rb +157 -0
  76. data/lib/gamba/true_type/file.rb +372 -0
  77. data/lib/gamba/true_type/open_type_layout.rb +189 -0
  78. data/lib/gamba/true_type/simple_subsets.rb +108 -0
  79. data/lib/gamba/true_type/subset.rb +206 -0
  80. data/lib/gamba/version.rb +10 -0
  81. data/lib/gamba/view.rb +28 -0
  82. data/lib/gamba.rb +124 -0
  83. data/lib/pdf/core.rb +4 -0
  84. data/lib/prawn/measurement_extensions.rb +4 -0
  85. data/lib/prawn/svg/attributes/clip_path.rb +46 -0
  86. data/lib/prawn/svg/attributes/mask.rb +16 -0
  87. data/lib/prawn/svg/attributes/opacity.rb +30 -0
  88. data/lib/prawn/svg/attributes/space.rb +10 -0
  89. data/lib/prawn/svg/attributes/stroke.rb +50 -0
  90. data/lib/prawn/svg/attributes/transform.rb +10 -0
  91. data/lib/prawn/svg/attributes.rb +6 -0
  92. data/lib/prawn/svg/calculators/arc_to_bezier_curve.rb +57 -0
  93. data/lib/prawn/svg/calculators/aspect_ratio.rb +60 -0
  94. data/lib/prawn/svg/calculators/document_sizing.rb +103 -0
  95. data/lib/prawn/svg/calculators/path_length.rb +168 -0
  96. data/lib/prawn/svg/calculators/pixels.rb +57 -0
  97. data/lib/prawn/svg/color.rb +240 -0
  98. data/lib/prawn/svg/css/font_face_parser.rb +72 -0
  99. data/lib/prawn/svg/css/font_family_parser.rb +39 -0
  100. data/lib/prawn/svg/css/font_parser.rb +46 -0
  101. data/lib/prawn/svg/css/selector_parser.rb +180 -0
  102. data/lib/prawn/svg/css/stylesheets.rb +233 -0
  103. data/lib/prawn/svg/css/values_parser.rb +68 -0
  104. data/lib/prawn/svg/document.rb +169 -0
  105. data/lib/prawn/svg/elements/anchor.rb +10 -0
  106. data/lib/prawn/svg/elements/base.rb +315 -0
  107. data/lib/prawn/svg/elements/bbox_scaling.rb +78 -0
  108. data/lib/prawn/svg/elements/call_duplicator.rb +37 -0
  109. data/lib/prawn/svg/elements/circle.rb +19 -0
  110. data/lib/prawn/svg/elements/clip_path.rb +26 -0
  111. data/lib/prawn/svg/elements/container.rb +10 -0
  112. data/lib/prawn/svg/elements/direct_render_base.rb +28 -0
  113. data/lib/prawn/svg/elements/ellipse.rb +20 -0
  114. data/lib/prawn/svg/elements/gradient.rb +183 -0
  115. data/lib/prawn/svg/elements/ignored.rb +5 -0
  116. data/lib/prawn/svg/elements/image.rb +178 -0
  117. data/lib/prawn/svg/elements/line.rb +27 -0
  118. data/lib/prawn/svg/elements/marker.rb +72 -0
  119. data/lib/prawn/svg/elements/mask.rb +65 -0
  120. data/lib/prawn/svg/elements/path.rb +294 -0
  121. data/lib/prawn/svg/elements/pattern.rb +150 -0
  122. data/lib/prawn/svg/elements/polygon.rb +25 -0
  123. data/lib/prawn/svg/elements/polyline.rb +23 -0
  124. data/lib/prawn/svg/elements/rect.rb +64 -0
  125. data/lib/prawn/svg/elements/root.rb +22 -0
  126. data/lib/prawn/svg/elements/switch.rb +92 -0
  127. data/lib/prawn/svg/elements/text.rb +99 -0
  128. data/lib/prawn/svg/elements/text_component.rb +352 -0
  129. data/lib/prawn/svg/elements/text_node.rb +308 -0
  130. data/lib/prawn/svg/elements/text_path.rb +153 -0
  131. data/lib/prawn/svg/elements/use.rb +142 -0
  132. data/lib/prawn/svg/elements/viewport.rb +29 -0
  133. data/lib/prawn/svg/elements.rb +46 -0
  134. data/lib/prawn/svg/extension.rb +25 -0
  135. data/lib/prawn/svg/font.rb +20 -0
  136. data/lib/prawn/svg/font_metrics.rb +144 -0
  137. data/lib/prawn/svg/font_registry.rb +315 -0
  138. data/lib/prawn/svg/funciri.rb +14 -0
  139. data/lib/prawn/svg/gradient_renderer.rb +332 -0
  140. data/lib/prawn/svg/gradients.rb +53 -0
  141. data/lib/prawn/svg/interface.rb +72 -0
  142. data/lib/prawn/svg/length.rb +43 -0
  143. data/lib/prawn/svg/link_renderer.rb +40 -0
  144. data/lib/prawn/svg/loaders/data.rb +17 -0
  145. data/lib/prawn/svg/loaders/file.rb +111 -0
  146. data/lib/prawn/svg/loaders/web.rb +24 -0
  147. data/lib/prawn/svg/paint.rb +67 -0
  148. data/lib/prawn/svg/pathable.rb +153 -0
  149. data/lib/prawn/svg/pattern_renderer.rb +112 -0
  150. data/lib/prawn/svg/pdfmatrix.rb +39 -0
  151. data/lib/prawn/svg/percentage.rb +24 -0
  152. data/lib/prawn/svg/properties.rb +329 -0
  153. data/lib/prawn/svg/renderer.rb +249 -0
  154. data/lib/prawn/svg/state.rb +24 -0
  155. data/lib/prawn/svg/transform_parser.rb +78 -0
  156. data/lib/prawn/svg/ttc.rb +35 -0
  157. data/lib/prawn/svg/ttf.rb +95 -0
  158. data/lib/prawn/svg/url_loader.rb +46 -0
  159. data/lib/prawn/svg/version.rb +5 -0
  160. data/lib/prawn/table.rb +4 -0
  161. data/lib/prawn-svg.rb +52 -0
  162. data/lib/prawn.rb +3 -0
  163. metadata +263 -0
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Prawn
4
+ module Images
5
+ # Undoes PNG's per-row filters (PNG specification §9), giving back raw samples: needed to split an alpha
6
+ # channel or a palette's transparency into a separate soft mask.
7
+ module PngDecoder
8
+ NONE = 0
9
+ SUB = 1
10
+ UP = 2
11
+ AVERAGE = 3
12
+ PAETH = 4
13
+
14
+ module_function
15
+
16
+ # Rows of unfiltered bytes, from the inflated data of a non-interlaced image.
17
+ def unfilter(data, row_bytes, bytes_per_pixel)
18
+ previous = ("\x00" * row_bytes).b
19
+ row_count = data.bytesize / (row_bytes + 1)
20
+ Array.new(row_count) do |row_index|
21
+ start = row_index * (row_bytes + 1)
22
+ filter = data.getbyte(start)
23
+ row = data.byteslice(start + 1, row_bytes).b
24
+ unfilter_row!(row, previous, filter, bytes_per_pixel)
25
+ previous = row
26
+ end
27
+ end
28
+
29
+ def unfilter_row!(row, previous, filter, bytes_per_pixel)
30
+ case filter
31
+ when NONE then nil
32
+ when SUB
33
+ (bytes_per_pixel...row.bytesize).each { |index| row.setbyte(index, (row.getbyte(index) + row.getbyte(index - bytes_per_pixel)) & 0xFF) }
34
+ when UP
35
+ row.bytesize.times { |index| row.setbyte(index, (row.getbyte(index) + previous.getbyte(index)) & 0xFF) }
36
+ when AVERAGE
37
+ row.bytesize.times do |index|
38
+ left = (index >= bytes_per_pixel) ? row.getbyte(index - bytes_per_pixel) : 0
39
+ row.setbyte(index, (row.getbyte(index) + ((left + previous.getbyte(index)) >> 1)) & 0xFF)
40
+ end
41
+ when PAETH
42
+ row.bytesize.times do |index|
43
+ left = (index >= bytes_per_pixel) ? row.getbyte(index - bytes_per_pixel) : 0
44
+ up = previous.getbyte(index)
45
+ up_left = (index >= bytes_per_pixel) ? previous.getbyte(index - bytes_per_pixel) : 0
46
+ row.setbyte(index, (row.getbyte(index) + paeth(left, up, up_left)) & 0xFF)
47
+ end
48
+ else raise Errors::UnsupportedImageType, "PNG uses an unknown filter type #{filter}"
49
+ end
50
+ row
51
+ end
52
+
53
+ def paeth(left, up, up_left)
54
+ estimate = left + up - up_left
55
+ distance_left = (estimate - left).abs
56
+ distance_up = (estimate - up).abs
57
+ distance_up_left = (estimate - up_left).abs
58
+ if distance_left <= distance_up && distance_left <= distance_up_left then left
59
+ elsif distance_up <= distance_up_left then up
60
+ else up_left
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest/sha1"
4
+ require "pathname"
5
+
6
+ module Prawn
7
+ def self.image_handler
8
+ @image_handler ||= ImageHandler.new
9
+ end
10
+
11
+ # The image formats Gamba knows, tried in order on an image's first bytes.
12
+ class ImageHandler
13
+ def initialize
14
+ @handlers = []
15
+ end
16
+
17
+ def register(handler)
18
+ @handlers.delete(handler)
19
+ @handlers.push(handler)
20
+ end
21
+
22
+ def register!(handler)
23
+ @handlers.delete(handler)
24
+ @handlers.unshift(handler)
25
+ end
26
+
27
+ def unregister(handler)
28
+ @handlers.delete(handler)
29
+ end
30
+
31
+ def find(image_blob)
32
+ @handlers.find { _1.can_render?(image_blob) } || raise(Errors::UnsupportedImageType, "image file is an unrecognised format")
33
+ end
34
+ end
35
+
36
+ module Images
37
+ def image(file, options = {})
38
+ Prawn.verify_options(%i[at position vposition height width scale fit], options)
39
+ pdf_object, info = build_image_object(file)
40
+ embed_image(pdf_object, info, options)
41
+ info
42
+ end
43
+
44
+ # The same image bytes are embedded once per document, however many times they are placed.
45
+ def build_image_object(file)
46
+ image_content = verify_and_read_image(file)
47
+ entry = image_registry[Digest::SHA1.hexdigest(image_content)] ||= begin
48
+ info = Prawn.image_handler.find(image_content).new(image_content)
49
+ min_version(info.min_pdf_version) if info.respond_to?(:min_pdf_version)
50
+ {obj: info.build_pdf_object(self), info:}
51
+ end
52
+ [entry[:obj], entry[:info]]
53
+ end
54
+
55
+ def embed_image(pdf_object, info, options)
56
+ width, height = info.calc_image_dimensions(options)
57
+ if options[:at]
58
+ x, y = map_to_absolute(options[:at])
59
+ else
60
+ x, y = image_position(width, height, options)
61
+ move_text_position(height)
62
+ end
63
+ label = "I#{@image_counter = (@image_counter || 0) + 1}"
64
+ page.xobjects[label] = pdf_object
65
+ add_content("\nq\n#{PDF::Core.real_params([width, 0, 0, height, x, y - height])} cm\n/#{label} Do\nQ")
66
+ end
67
+
68
+ private
69
+
70
+ def verify_and_read_image(io_or_path)
71
+ if io_or_path.respond_to?(:rewind)
72
+ io_or_path.rewind
73
+ io_or_path.binmode if io_or_path.respond_to?(:binmode)
74
+ return io_or_path.read
75
+ end
76
+ path = Pathname.new(io_or_path)
77
+ raise ArgumentError, "#{path} not found" unless path.file?
78
+
79
+ path.binread
80
+ end
81
+
82
+ def image_position(width, height, options)
83
+ options[:position] ||= :left
84
+ y =
85
+ case options[:vposition]
86
+ when :top then bounds.absolute_top
87
+ when :center then bounds.absolute_top - ((bounds.height - height) / 2.0)
88
+ when :bottom then bounds.absolute_bottom + height
89
+ when Numeric then bounds.absolute_top - options[:vposition]
90
+ else determine_y_with_page_flow(height)
91
+ end
92
+ x =
93
+ case options[:position]
94
+ when :left then bounds.left_side
95
+ when :center then bounds.left_side + ((bounds.width - width) / 2.0)
96
+ when :right then bounds.right_side - width
97
+ when Numeric then options[:position] + bounds.left_side
98
+ end
99
+ [x, y]
100
+ end
101
+
102
+ def determine_y_with_page_flow(height)
103
+ bounds.move_past_bottom if overruns_page?(height)
104
+ y
105
+ end
106
+
107
+ def overruns_page?(height)
108
+ (y - height) < reference_bounds.absolute_bottom
109
+ end
110
+
111
+ def image_registry
112
+ @image_registry ||= {}
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "measurements"
4
+
5
+ # 10.mm, 2.cm, 1.in…: lengths in PDF points.
6
+ class Numeric
7
+ include Prawn::Measurements
8
+
9
+ def mm
10
+ mm2pt(self)
11
+ end
12
+
13
+ def cm
14
+ cm2pt(self)
15
+ end
16
+
17
+ def dm
18
+ dm2pt(self)
19
+ end
20
+
21
+ def m
22
+ m2pt(self)
23
+ end
24
+
25
+ def in
26
+ in2pt(self)
27
+ end
28
+
29
+ def yd
30
+ yd2pt(self)
31
+ end
32
+
33
+ def ft
34
+ ft2pt(self)
35
+ end
36
+
37
+ def pt
38
+ pt2pt(self)
39
+ end
40
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Prawn
4
+ # Conversions between metric and imperial units and PDF points (1/72 inch).
5
+ module Measurements
6
+ def cm2mm(cm)
7
+ cm * 10
8
+ end
9
+
10
+ def dm2mm(dm)
11
+ dm * 100
12
+ end
13
+
14
+ def m2mm(m)
15
+ m * 1000
16
+ end
17
+
18
+ def ft2in(ft)
19
+ ft * 12
20
+ end
21
+
22
+ def yd2in(yd)
23
+ yd * 36
24
+ end
25
+
26
+ def pt2pt(pt)
27
+ pt
28
+ end
29
+
30
+ def in2pt(inch)
31
+ inch * 72
32
+ end
33
+
34
+ def ft2pt(ft)
35
+ in2pt(ft2in(ft))
36
+ end
37
+
38
+ def yd2pt(yd)
39
+ in2pt(yd2in(yd))
40
+ end
41
+
42
+ def mm2pt(mm)
43
+ mm * (72 / 25.4)
44
+ end
45
+
46
+ def cm2pt(cm)
47
+ mm2pt(cm2mm(cm))
48
+ end
49
+
50
+ def dm2pt(dm)
51
+ mm2pt(dm2mm(dm))
52
+ end
53
+
54
+ def m2pt(m)
55
+ mm2pt(m2mm(m))
56
+ end
57
+
58
+ def pt2mm(pt)
59
+ pt * 1 / mm2pt(1)
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Prawn
4
+ class Document
5
+ def outline
6
+ @outline ||= Outline.new(self)
7
+ end
8
+ end
9
+
10
+ # The document outline (bookmarks): sections holding pages and subsections, each pointing at a page.
11
+ class Outline
12
+ attr_accessor :parent, :prev, :document, :items
13
+
14
+ def initialize(document)
15
+ @document = document
16
+ @parent = root
17
+ @prev = nil
18
+ @items = {}
19
+ end
20
+
21
+ def page_number
22
+ @document.page_number
23
+ end
24
+
25
+ def define(&block)
26
+ instance_eval(&block) if block
27
+ end
28
+ alias_method :update, :define
29
+
30
+ def add_subsection_to(title, position = :last, &)
31
+ @parent = items[title] || raise(Errors::UnknownOutlineTitle, "\n No outline item with title: '#{title}' exists in the outline tree")
32
+ @prev = (position == :first) ? nil : @parent.data.last
33
+ insert_section((position == :first) ? @parent.data.first : nil, &)
34
+ end
35
+
36
+ def insert_section_after(title, &)
37
+ @prev = items[title] || raise(Errors::UnknownOutlineTitle, "\n No outline item with title: '#{title}' exists in the outline tree")
38
+ @parent = @prev.data.parent
39
+ insert_section(@prev.data.next, &)
40
+ end
41
+
42
+ def section(title, options = {}, &)
43
+ add_outline_item(title, options, &)
44
+ end
45
+
46
+ def page(options = {})
47
+ raise Errors::RequiredOption, "\nTitle is a required option for page" unless options[:title]
48
+
49
+ add_outline_item(options[:title], options)
50
+ end
51
+
52
+ private
53
+
54
+ def root
55
+ document.outlines_reference
56
+ end
57
+
58
+ def add_outline_item(title, options, &block)
59
+ outline_item = create_outline_item(title, options)
60
+ prev.data.next = outline_item if prev
61
+ parent.data.first = outline_item unless prev
62
+ parent.data.last = outline_item
63
+ increase_count
64
+ self.prev = block ? nil : outline_item
65
+ self.parent = outline_item if block
66
+ yield if block
67
+ return unless parent == outline_item
68
+
69
+ self.prev = outline_item
70
+ self.parent = outline_item.data.parent
71
+ end
72
+
73
+ def create_outline_item(title, options)
74
+ outline_item = PDF::Core::OutlineItem.new(title, parent, options)
75
+ case options[:destination]
76
+ when Integer then outline_item.dest = [document.pages[options[:destination] - 1].dictionary, :Fit]
77
+ when Array then outline_item.dest = options[:destination]
78
+ end
79
+ outline_item.prev = prev if @prev
80
+ items[title] = document.ref!(outline_item)
81
+ end
82
+
83
+ def increase_count
84
+ counting_parent = parent
85
+ while counting_parent
86
+ counting_parent.data.count += 1
87
+ counting_parent = (counting_parent == root) ? nil : counting_parent.data.parent
88
+ end
89
+ end
90
+
91
+ def insert_section(next_item)
92
+ last = @parent.data.last
93
+ yield if block_given?
94
+ if next_item
95
+ next_item.data.prev = @prev
96
+ @prev.data.next = next_item
97
+ @parent.data.last = last
98
+ end
99
+ @parent = root
100
+ @prev = root.data.last
101
+ end
102
+ end
103
+ end
104
+
105
+ module PDF::Core
106
+ module Destinations
107
+ def dests
108
+ names.data[:Dests] ||= ref!(PDF::Core::NameTree.new)
109
+ end
110
+
111
+ def add_dest(name, reference)
112
+ reference = ref!(reference) unless reference.is_a?(PDF::Core::Reference)
113
+ dests.data.add(name, reference)
114
+ end
115
+
116
+ def dest_xyz(left, top, zoom = nil, dest_page = page)
117
+ [dest_page.dictionary, :XYZ, left, top, zoom]
118
+ end
119
+
120
+ def dest_fit(dest_page = page)
121
+ [dest_page.dictionary, :Fit]
122
+ end
123
+
124
+ def dest_fit_horizontally(top, dest_page = page)
125
+ [dest_page.dictionary, :FitH, top]
126
+ end
127
+
128
+ def dest_fit_vertically(left, dest_page = page)
129
+ [dest_page.dictionary, :FitV, left]
130
+ end
131
+
132
+ def dest_fit_rect(left, bottom, right, top, dest_page = page)
133
+ [dest_page.dictionary, :FitR, left, bottom, right, top]
134
+ end
135
+
136
+ def dest_fit_bounds(dest_page = page)
137
+ [dest_page.dictionary, :FitB]
138
+ end
139
+
140
+ def dest_fit_bounds_horizontally(top, dest_page = page)
141
+ [dest_page.dictionary, :FitBH, top]
142
+ end
143
+
144
+ def dest_fit_bounds_vertically(left, dest_page = page)
145
+ [dest_page.dictionary, :FitBV, left]
146
+ end
147
+ end
148
+ end
data/lib/gamba/page.rb ADDED
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PDF::Core
4
+ # One page: its geometry, the content stream being written, its resources and the stack of graphic states.
5
+ class Page
6
+ DEFAULT_MARGINS = {left: 36, right: 36, top: 36, bottom: 36}.freeze
7
+ NO_INDENTS = {left: 0, bottom: 0, right: 0, top: 0}.freeze
8
+
9
+ attr_accessor :margins, :crops, :bleeds, :trims, :art_indents, :stack
10
+ attr_reader :document, :content
11
+
12
+ def initialize(document, options = {})
13
+ @document = document
14
+ @margins = options[:margins] || DEFAULT_MARGINS.dup
15
+ @crops = options[:crops] || NO_INDENTS
16
+ @bleeds = options[:bleeds] || NO_INDENTS
17
+ @trims = options[:trims] || NO_INDENTS
18
+ @art_indents = options[:art_indents] || NO_INDENTS
19
+ @stack = [GraphicState.new(options[:graphic_state])]
20
+ @size = options[:size] || "LETTER"
21
+ @layout = options[:layout] || :portrait
22
+ @content = document.ref!({}, (+"q\n").b)
23
+ @dictionary = document.ref!(Type: :Page, Parent: document.pages_reference, MediaBox: dimensions, Contents: @content)
24
+ @stamp_stream = nil
25
+ @stamp_dictionary = nil
26
+ end
27
+
28
+ def graphic_state
29
+ stack.last
30
+ end
31
+
32
+ def size
33
+ @size || dimensions[2, 2]
34
+ end
35
+
36
+ def layout
37
+ @layout || ((dimensions[3] > dimensions[2]) ? :portrait : :landscape)
38
+ end
39
+
40
+ def content_stream
41
+ @stamp_stream || content.stream
42
+ end
43
+
44
+ def dictionary
45
+ @stamp_dictionary || @dictionary
46
+ end
47
+
48
+ # Writes what the block draws into a form XObject instead of the page, starting from a copy of the current
49
+ # graphic settings.
50
+ def stamp_stream(reference)
51
+ @stamp_dictionary = reference
52
+ @stamp_stream = reference.stream
53
+ depth = stack.size
54
+ document.save_graphics_state
55
+ document.__send__(:freeze_stamp_graphics)
56
+ yield if block_given?
57
+ document.restore_graphics_state until stack.size == depth
58
+ @stamp_stream = nil
59
+ @stamp_dictionary = nil
60
+ end
61
+
62
+ def in_stamp_stream?
63
+ !@stamp_stream.nil?
64
+ end
65
+
66
+ def resources
67
+ dictionary.data[:Resources] ||= {}
68
+ end
69
+
70
+ def fonts
71
+ resources[:Font] ||= {}
72
+ end
73
+
74
+ def xobjects
75
+ resources[:XObject] ||= {}
76
+ end
77
+
78
+ def ext_gstates
79
+ resources[:ExtGState] ||= {}
80
+ end
81
+
82
+ def dimensions
83
+ sides = PageGeometry::SIZES[@size] || @size
84
+ sides =
85
+ case @layout
86
+ when :portrait then sides
87
+ when :landscape then sides.reverse
88
+ else raise Prawn::Errors::InvalidPageLayout, "Layout must be either :portrait or :landscape"
89
+ end
90
+ [0, 0, *sides]
91
+ end
92
+
93
+ def crop_box
94
+ boxed(crops)
95
+ end
96
+
97
+ def bleed_box
98
+ boxed(bleeds)
99
+ end
100
+
101
+ def trim_box
102
+ boxed(trims)
103
+ end
104
+
105
+ def art_box
106
+ boxed(art_indents)
107
+ end
108
+
109
+ # Resources are required even when empty; every box defaults to the media box, so only those that differ are
110
+ # written.
111
+ def finalize
112
+ resources
113
+ {CropBox: crop_box, BleedBox: bleed_box, TrimBox: trim_box, ArtBox: art_box}.each do |name, box|
114
+ dictionary.data[name] = box unless box == dimensions
115
+ end
116
+ end
117
+
118
+ private
119
+
120
+ def boxed(indents)
121
+ left, bottom, right, top = dimensions
122
+ [left + indents[:left], bottom + indents[:bottom], right - indents[:right], top - indents[:top]]
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PDF::Core
4
+ module PageGeometry
5
+ POINTS_PER_INCH = 72.0
6
+ MILLIMETERS_PER_INCH = 25.4
7
+
8
+ # ISO 216 (A, B), ISO 217 (RA, SRA) and ISO 269 (C) sizes in millimeters, then North American sizes in inches.
9
+ MILLIMETERS_BY_SIZE = {
10
+ "4A0" => [1682, 2378], "2A0" => [1189, 1682],
11
+ "A0" => [841, 1189], "A1" => [594, 841], "A2" => [420, 594], "A3" => [297, 420], "A4" => [210, 297],
12
+ "A5" => [148, 210], "A6" => [105, 148], "A7" => [74, 105], "A8" => [52, 74], "A9" => [37, 52], "A10" => [26, 37],
13
+ "B0" => [1000, 1414], "B1" => [707, 1000], "B2" => [500, 707], "B3" => [353, 500], "B4" => [250, 353],
14
+ "B5" => [176, 250], "B6" => [125, 176], "B7" => [88, 125], "B8" => [62, 88], "B9" => [44, 62], "B10" => [31, 44],
15
+ "C0" => [917, 1297], "C1" => [648, 917], "C2" => [458, 648], "C3" => [324, 458], "C4" => [229, 324],
16
+ "C5" => [162, 229], "C6" => [114, 162], "C7" => [81, 114], "C8" => [57, 81], "C9" => [40, 57], "C10" => [28, 40],
17
+ "RA0" => [860, 1220], "RA1" => [610, 860], "RA2" => [430, 610], "RA3" => [305, 430], "RA4" => [215, 305],
18
+ "SRA0" => [900, 1280], "SRA1" => [640, 900], "SRA2" => [450, 640], "SRA3" => [320, 450], "SRA4" => [225, 320],
19
+ "EXECUTIVE" => [184.1, 266.7]
20
+ }.freeze
21
+ INCHES_BY_SIZE = {"FOLIO" => [8.5, 13], "LEGAL" => [8.5, 14], "LETTER" => [8.5, 11], "TABLOID" => [11, 17]}.freeze
22
+
23
+ SIZES = MILLIMETERS_BY_SIZE.transform_values { |sides| sides.map { |millimeters| (millimeters * POINTS_PER_INCH / MILLIMETERS_PER_INCH).round(2) } }
24
+ .merge(INCHES_BY_SIZE.transform_values { |sides| sides.map { |inches| inches * POINTS_PER_INCH } })
25
+ .freeze
26
+ end
27
+ end