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,193 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Prawn
4
+ class Document
5
+ def bounding_box(point, *arguments, &block)
6
+ init_bounding_box(block) do |parent_box|
7
+ @bounding_box = BoundingBox.new(self, parent_box, map_to_absolute(point), *arguments)
8
+ end
9
+ end
10
+
11
+ def canvas(&block)
12
+ init_bounding_box(block, hold_position: true) do
13
+ _left, _bottom, page_width, page_height = page.dimensions
14
+ @bounding_box = BoundingBox.new(self, nil, [0, page_height], width: page_width, height: page_height)
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def init_bounding_box(user_block, options = {})
21
+ raise ArgumentError, "bounding boxes require a block to be drawn within the box" unless user_block
22
+
23
+ parent_box = @bounding_box
24
+ original_y = y
25
+ yield parent_box
26
+ self.y = @bounding_box.absolute_top
27
+ user_block.call
28
+ self.y = original_y if y == @bounding_box.absolute_top
29
+ self.y = @bounding_box.absolute_bottom unless options[:hold_position] || @bounding_box.stretchy?
30
+ created_box = @bounding_box
31
+ @bounding_box = parent_box
32
+ created_box
33
+ end
34
+
35
+ # A rectangle the document draws into, positioned from its top left corner; without a height, it stretches
36
+ # down to wherever the cursor ends.
37
+ class BoundingBox
38
+ class NoReferenceBounds < StandardError
39
+ def initialize(message = "Can't find reference bounds: my parent is unset")
40
+ super
41
+ end
42
+ end
43
+
44
+ attr_reader :document, :parent, :total_left_padding, :total_right_padding, :width
45
+
46
+ def initialize(document, parent, point, options = {})
47
+ raise ArgumentError, "BoundingBox needs the :width option to be set" unless options[:width]
48
+
49
+ @document = document
50
+ @parent = parent
51
+ @x, @y = point
52
+ @width = options[:width]
53
+ @height = options[:height]
54
+ @total_left_padding = 0
55
+ @total_right_padding = 0
56
+ @stretched_height = nil
57
+ end
58
+
59
+ def anchor
60
+ [@x, @y - height]
61
+ end
62
+
63
+ def indent(left_padding, right_padding = 0)
64
+ add_left_padding(left_padding)
65
+ add_right_padding(right_padding)
66
+ yield
67
+ ensure
68
+ @document.bounds.subtract_left_padding(left_padding)
69
+ @document.bounds.subtract_right_padding(right_padding)
70
+ end
71
+
72
+ def add_left_padding(left_padding)
73
+ @total_left_padding += left_padding
74
+ @x += left_padding
75
+ @width -= left_padding
76
+ end
77
+
78
+ def subtract_left_padding(left_padding)
79
+ @total_left_padding -= left_padding
80
+ @x -= left_padding
81
+ @width += left_padding
82
+ end
83
+
84
+ def add_right_padding(right_padding)
85
+ @total_right_padding += right_padding
86
+ @width -= right_padding
87
+ end
88
+
89
+ def subtract_right_padding(right_padding)
90
+ @total_right_padding -= right_padding
91
+ @width += right_padding
92
+ end
93
+
94
+ def left
95
+ 0
96
+ end
97
+
98
+ def right
99
+ @width
100
+ end
101
+
102
+ def top
103
+ height
104
+ end
105
+
106
+ def bottom
107
+ 0
108
+ end
109
+
110
+ def top_left
111
+ [left, top]
112
+ end
113
+
114
+ def top_right
115
+ [right, top]
116
+ end
117
+
118
+ def bottom_right
119
+ [right, bottom]
120
+ end
121
+
122
+ def bottom_left
123
+ [left, bottom]
124
+ end
125
+
126
+ def absolute_left
127
+ @x
128
+ end
129
+
130
+ def absolute_right
131
+ @x + width
132
+ end
133
+
134
+ def absolute_top
135
+ @y
136
+ end
137
+
138
+ def absolute_bottom
139
+ @y - height
140
+ end
141
+
142
+ def absolute_top_left
143
+ [absolute_left, absolute_top]
144
+ end
145
+
146
+ def absolute_top_right
147
+ [absolute_right, absolute_top]
148
+ end
149
+
150
+ def absolute_bottom_left
151
+ [absolute_left, absolute_bottom]
152
+ end
153
+
154
+ def absolute_bottom_right
155
+ [absolute_right, absolute_bottom]
156
+ end
157
+
158
+ def height
159
+ return @height if @height
160
+
161
+ @stretched_height = [absolute_top - @document.y, Float(@stretched_height || 0.0)].max
162
+ end
163
+ alias_method :update_height, :height
164
+
165
+ def left_side
166
+ absolute_left
167
+ end
168
+
169
+ def right_side
170
+ absolute_right
171
+ end
172
+
173
+ def move_past_bottom
174
+ if @document.page_number == @document.page_count
175
+ @document.start_new_page
176
+ else
177
+ @document.__send__(:flow_onto_page, @document.page_number + 1)
178
+ end
179
+ end
180
+
181
+ def stretchy?
182
+ !@height
183
+ end
184
+
185
+ def reference_bounds
186
+ return self unless stretchy?
187
+ raise NoReferenceBounds unless @parent
188
+
189
+ @parent.reference_bounds
190
+ end
191
+ end
192
+ end
193
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Prawn
4
+ class Document
5
+ def column_box(*arguments, &block)
6
+ init_column_box(block) do |parent_box|
7
+ map_to_absolute!(arguments[0])
8
+ @bounding_box = ColumnBox.new(self, parent_box, *arguments)
9
+ end
10
+ end
11
+
12
+ private
13
+
14
+ def init_column_box(user_block, options = {})
15
+ parent_box = @bounding_box
16
+ yield parent_box
17
+ self.y = @bounding_box.absolute_top
18
+ user_block.call
19
+ self.y = @bounding_box.absolute_bottom unless options[:hold_position]
20
+ @bounding_box = parent_box
21
+ end
22
+
23
+ # A bounding box whose text flows down one column, then the next, then onto a new page.
24
+ class ColumnBox < BoundingBox
25
+ DEFAULT_COLUMN_COUNT = 3
26
+
27
+ def initialize(document, parent, point, options = {})
28
+ super
29
+ @columns = options[:columns] || DEFAULT_COLUMN_COUNT
30
+ @spacer = options[:spacer] || @document.font_size
31
+ @current_column = 0
32
+ @reflow_margins = options[:reflow_margins]
33
+ end
34
+
35
+ def bare_column_width
36
+ (@width - (@spacer * (@columns - 1))) / @columns
37
+ end
38
+
39
+ def width
40
+ bare_column_width - (@total_left_padding + @total_right_padding)
41
+ end
42
+
43
+ def width_of_column
44
+ bare_column_width + @spacer
45
+ end
46
+
47
+ def left_side
48
+ absolute_left + (width_of_column * @current_column)
49
+ end
50
+
51
+ def left
52
+ width_of_column * @current_column
53
+ end
54
+
55
+ def right_side
56
+ absolute_right - (width_of_column * (@columns - (1 + @current_column)))
57
+ end
58
+
59
+ def right
60
+ left + width
61
+ end
62
+
63
+ def move_past_bottom
64
+ @current_column = (@current_column + 1) % @columns
65
+ @document.y = @y
66
+ return unless @current_column.zero?
67
+
68
+ @y = @parent.absolute_top if @reflow_margins
69
+ @document.start_new_page
70
+ end
71
+
72
+ def add_left_padding(left_padding)
73
+ @total_left_padding += left_padding
74
+ @x += left_padding
75
+ end
76
+
77
+ def subtract_left_padding(left_padding)
78
+ @total_left_padding -= left_padding
79
+ @x -= left_padding
80
+ end
81
+
82
+ def add_right_padding(right_padding)
83
+ @total_right_padding += right_padding
84
+ end
85
+
86
+ def subtract_right_padding(right_padding)
87
+ @total_right_padding -= right_padding
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,264 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Prawn
4
+ class Document
5
+ # The document's objects, pages and graphic state stack: what Prawn splits between PDF::Core's renderer and its
6
+ # document state.
7
+ module Internals
8
+ # A save not yet restored: the state in effect before it (nil on an empty stack, the default one), the
9
+ # ExtGState its block sets, {name:, reference:}, if any, and whether it was restored since.
10
+ OpenSave = Struct.new(:previous, :ext_gstate, :restored)
11
+ DEFAULT_GRAPHIC_STATE = PDF::Core::GraphicState.new.freeze
12
+ # The settings a block ending on a later page writes back, each with its writer, in the order Prawn writes them
13
+ # on a new page, then the blend mode.
14
+ GRAPHIC_SETTING_WRITERS = {fill_paint: :write_fill_paint, stroke_paint: :write_stroke_paint, line_width: :write_line_width,
15
+ cap_style: :write_stroke_cap_style, join_style: :write_stroke_join_style, dash: :write_stroke_dash,
16
+ blend_mode: :write_blend_mode}.freeze
17
+ # What these writers read from the state.
18
+ RESUMED_STATE = %i[fill_color fill_pattern stroke_color stroke_pattern line_width cap_style join_style dash blend_mode].freeze
19
+
20
+ attr_reader :page, :pages, :pages_reference
21
+
22
+ def ref(data)
23
+ ref!(data).identifier
24
+ end
25
+
26
+ def ref!(data, stream = nil)
27
+ reference = PDF::Core::Reference.new(@objects.size + 1, data, stream)
28
+ @objects << reference
29
+ reference
30
+ end
31
+
32
+ def deref(object)
33
+ object.is_a?(PDF::Core::Reference) ? object.data : object
34
+ end
35
+
36
+ def add_content(string)
37
+ open_graphics_state if page.stack.empty?
38
+ page.content_stream << string << "\n"
39
+ end
40
+
41
+ def names
42
+ @root.data[:Names] ||= ref!(Type: :Names)
43
+ end
44
+
45
+ def outlines_reference
46
+ @root.data[:Outlines] ||= ref!(PDF::Core::OutlineRoot.new)
47
+ end
48
+
49
+ def names?
50
+ @root.data.key?(:Names)
51
+ end
52
+
53
+ def before_render(&block)
54
+ @before_render_callbacks << block
55
+ end
56
+
57
+ def on_page_create(&block)
58
+ @on_page_create = block
59
+ end
60
+
61
+ def compression_enabled?
62
+ @compress
63
+ end
64
+
65
+ def min_version(version)
66
+ @version = version if version > @version
67
+ end
68
+
69
+ # Like Prawn, reading the state of a page whose stack was emptied opens a new one.
70
+ def graphic_state
71
+ open_graphics_state if page.stack.empty?
72
+ page.stack.last
73
+ end
74
+
75
+ def save_graphics_state(graphic_state = nil)
76
+ save_transformation_stack
77
+ previous = graphic_state ? page.stack.last : self.graphic_state
78
+ open_save = OpenSave.new(previous, nil, false)
79
+ @unrestored_graphics_states << open_save
80
+ state = PDF::Core::GraphicState.new(graphic_state || previous)
81
+ state.save = open_save
82
+ # A state given, as a dynamic repeater gives the one it was created in, still inherits the page's text state.
83
+ state.copy_text_state(previous) if graphic_state
84
+ page.stack.push(state)
85
+ add_content("q")
86
+ return unless block_given?
87
+
88
+ yield
89
+ restore_graphics_state
90
+ end
91
+
92
+ # A page break leaves the blocks still open with states saved on an earlier page, which closed them when it
93
+ # ended: like Prawn, their restore closes the state open on the new page (or one reopened for a block with an
94
+ # ExtGState), and where none is open it has nothing left to close. Only a restore matching no save at all, on an
95
+ # empty stack, raises.
96
+ def restore_graphics_state
97
+ restore_transformation_stack
98
+ open_save = @unrestored_graphics_states.pop
99
+ open_save&.restored = true
100
+ if page.stack.empty?
101
+ raise Errors::EmptyGraphicStateStack, "\n You have reached the end of the graphic state stack" unless open_save
102
+ else
103
+ add_content("Q")
104
+ page.stack.pop
105
+ end
106
+ return unless open_save && !page.stack.last.equal?(open_save.previous)
107
+
108
+ resume_graphic_settings(open_save.previous)
109
+ resume_text_state
110
+ end
111
+
112
+ def trailer
113
+ @trailer
114
+ end
115
+
116
+ # What Prawn splits into a renderer and a state is this document itself: code written for Prawn calls both.
117
+ def renderer
118
+ self
119
+ end
120
+
121
+ def state
122
+ self
123
+ end
124
+
125
+ def info
126
+ @info.data
127
+ end
128
+
129
+ private
130
+
131
+ # The states left open on a page by blocks that ended on another one: content drawn there again is drawn outside
132
+ # them, as it would have been had the blocks ended there, in a base state with the settings in effect before them.
133
+ def close_restored_graphics_states
134
+ closed = nil
135
+ while (state = page.stack.last)&.save&.restored
136
+ closed = state
137
+ page.content_stream << "Q\n"
138
+ page.stack.pop
139
+ end
140
+ return unless closed
141
+
142
+ graphic_state
143
+ resume_graphic_settings(closed.save.previous)
144
+ end
145
+
146
+ def open_graphics_state
147
+ page.stack.push(PDF::Core::GraphicState.new)
148
+ page.content_stream << "q\n"
149
+ end
150
+
151
+ # The blocks still open saved their states on the page just closed: the page's own state is the outermost one's.
152
+ # When one sets an ExtGState (opacity, blend mode), the new page reopens them in order, so each block's restore
153
+ # closes its own state; otherwise the page stays as Prawn writes it.
154
+ def reopen_ext_gstate_blocks
155
+ return if @unrestored_graphics_states.empty?
156
+
157
+ outermost, *inner = @unrestored_graphics_states
158
+ graphic_state.save = outermost
159
+ return if @unrestored_graphics_states.none?(&:ext_gstate)
160
+
161
+ write_ext_gstate(outermost.ext_gstate) if outermost.ext_gstate
162
+ inner.each { reopen_graphics_state(_1) }
163
+ end
164
+
165
+ # Content flowing onto a page that exists, through go_to_page: the page keeps its own state, which holds what it
166
+ # drew already. When a block still open that began elsewhere sets an ExtGState, each such block gets a state of
167
+ # its own on top, in order, so only what they add here is drawn in them, and each block's restore closes its own.
168
+ def reopen_ext_gstate_blocks_on_existing_page
169
+ return if @unrestored_graphics_states.none?(&:ext_gstate)
170
+
171
+ saves_on_page = page.stack.filter_map(&:save)
172
+ saved_elsewhere = @unrestored_graphics_states.reject { |open_save| saves_on_page.any? { _1.equal?(open_save) } }
173
+ saved_elsewhere.each { reopen_graphics_state(_1) } if saved_elsewhere.any?(&:ext_gstate)
174
+ end
175
+
176
+ # Content flowing onto a page that exists goes on with the settings in effect, as on a new page. go_to_page alone
177
+ # keeps the page's own settings, which page numbers and repeaters draw with, as in Prawn.
178
+ def flow_onto_page(page_number)
179
+ carried = page.stack.last
180
+ go_to_page(page_number)
181
+ resume_graphic_settings(carried)
182
+ end
183
+
184
+ def reopen_graphics_state(open_save)
185
+ page.stack.push(PDF::Core::GraphicState.new(graphic_state))
186
+ page.stack.last.save = open_save
187
+ add_content("q")
188
+ write_ext_gstate(open_save.ext_gstate) if open_save.ext_gstate
189
+ end
190
+
191
+ def ext_gstate_block(ext_gstate)
192
+ save_graphics_state
193
+ @unrestored_graphics_states.last.ext_gstate = ext_gstate
194
+ write_ext_gstate(ext_gstate)
195
+ yield
196
+ restore_graphics_state
197
+ end
198
+
199
+ def write_ext_gstate(ext_gstate)
200
+ page.ext_gstates[ext_gstate[:name]] = ext_gstate[:reference]
201
+ add_content("/#{ext_gstate[:name]} gs")
202
+ graphic_state.blend_mode = (ext_gstate[:name] == "BMNormal") ? nil : ext_gstate if ext_gstate[:reference].data.key?(:BM)
203
+ end
204
+
205
+ def write_blend_mode
206
+ write_ext_gstate(graphic_state.blend_mode || blend_mode_ext_gstate(:Normal))
207
+ end
208
+
209
+ # A block that saved its state on an earlier page ends on this one, where the state it returns to is the new
210
+ # page's (the default one once that is closed), not the one in effect before the block: Prawn draws what follows
211
+ # in black. Write back the settings that differ, as Prawn writes them on a new page.
212
+ def resume_graphic_settings(previous)
213
+ previous ||= DEFAULT_GRAPHIC_STATE
214
+ current = page.stack.last || DEFAULT_GRAPHIC_STATE
215
+ differing = GRAPHIC_SETTING_WRITERS.reject { |setting, _writer| current.public_send(setting) == previous.public_send(setting) }
216
+ return if differing.empty?
217
+
218
+ state = graphic_state
219
+ RESUMED_STATE.each { |setting| state.public_send(:"#{setting}=", previous.public_send(setting).dup) }
220
+ differing.each_value { |writer| send(writer) }
221
+ end
222
+
223
+ def initialize_internals(options)
224
+ @objects = []
225
+ @info = ref!({Creator: "Gamba", Producer: "Gamba"}.merge(options[:info] || {}))
226
+ @root = ref!(Type: :Catalog)
227
+ @pages_reference = ref!(Type: :Pages, Count: 0, Kids: [])
228
+ @root.data[:Pages] = @pages_reference
229
+ @root.data[:ViewerPreferences] = {PrintScaling: :None} if options[:print_scaling] == :none
230
+ @version = 1.3
231
+ # One entry per save not yet restored, document-wide.
232
+ @unrestored_graphics_states = []
233
+ min_version(1.6) if options[:print_scaling] == :none
234
+ @pages = []
235
+ @page = nil
236
+ @trailer = options.fetch(:trailer, {})
237
+ @compress = options.fetch(:compress, false)
238
+ @before_render_callbacks = []
239
+ @on_page_create = nil
240
+ end
241
+
242
+ def insert_page(page, page_number)
243
+ pages.insert(page_number, page)
244
+ pages_reference.data[:Kids].insert(page_number, page.dictionary)
245
+ pages_reference.data[:Count] += 1
246
+ end
247
+
248
+ def render_document
249
+ pages.each_with_index do |page, index|
250
+ go_to_page(index + 1)
251
+ until page.stack.empty?
252
+ page.content_stream << "Q\n"
253
+ page.stack.pop
254
+ end
255
+ page.finalize
256
+ end
257
+ @before_render_callbacks.each { _1.call(self) }
258
+ finalize_pdf_a if pdf_a?
259
+ PDF::Core::Writer.call(objects: @objects, root: @root, info: @info, version: @version, trailer: @trailer, encryption: @encryption,
260
+ identified: pdf_a?)
261
+ end
262
+ end
263
+ end
264
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Prawn
4
+ class Document
5
+ # A column of the given width across pages, positioned within the margins, starting at the cursor.
6
+ def span(width, options = {})
7
+ Prawn.verify_options([:position], options)
8
+ original_position = y
9
+ left_boundary =
10
+ case options.fetch(:position, :left)
11
+ when :left then margin_box.absolute_left
12
+ when :center then margin_box.absolute_left + (margin_box.width / 2.0) - (width / 2.0)
13
+ when :right then margin_box.absolute_right - width
14
+ when Numeric then margin_box.absolute_left + options[:position]
15
+ else raise ArgumentError, "Invalid option for :position"
16
+ end
17
+ canvas do
18
+ bounding_box([left_boundary, margin_box.absolute_top], width:) do
19
+ self.y = original_position
20
+ yield
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end