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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +80 -0
- data/COPYING +2 -0
- data/GPLv2 +339 -0
- data/GPLv3 +674 -0
- data/LICENSE +65 -0
- data/LICENSE-prawn-svg +21 -0
- data/README.md +148 -0
- data/data/fonts/Courier-Bold.afm +342 -0
- data/data/fonts/Courier-BoldOblique.afm +342 -0
- data/data/fonts/Courier-Oblique.afm +342 -0
- data/data/fonts/Courier.afm +342 -0
- data/data/fonts/Helvetica-Bold.afm +2827 -0
- data/data/fonts/Helvetica-BoldOblique.afm +2827 -0
- data/data/fonts/Helvetica-Oblique.afm +3051 -0
- data/data/fonts/Helvetica.afm +3051 -0
- data/data/fonts/MustRead.html +19 -0
- data/data/fonts/Symbol.afm +213 -0
- data/data/fonts/Times-Bold.afm +2588 -0
- data/data/fonts/Times-BoldItalic.afm +2384 -0
- data/data/fonts/Times-Italic.afm +2667 -0
- data/data/fonts/Times-Roman.afm +2419 -0
- data/data/fonts/ZapfDingbats.afm +225 -0
- data/lib/gamba/annotations.rb +21 -0
- data/lib/gamba/combine.rb +307 -0
- data/lib/gamba/document/bounding_box.rb +193 -0
- data/lib/gamba/document/column_box.rb +91 -0
- data/lib/gamba/document/internals.rb +264 -0
- data/lib/gamba/document/span.rb +25 -0
- data/lib/gamba/document.rb +268 -0
- data/lib/gamba/encoding.rb +13 -0
- data/lib/gamba/errors.rb +39 -0
- data/lib/gamba/font.rb +228 -0
- data/lib/gamba/fonts/afm.rb +171 -0
- data/lib/gamba/fonts/otf.rb +50 -0
- data/lib/gamba/fonts/ttf.rb +413 -0
- data/lib/gamba/graphic_state.rb +84 -0
- data/lib/gamba/graphics/color.rb +174 -0
- data/lib/gamba/graphics/patterns.rb +100 -0
- data/lib/gamba/graphics/style.rb +159 -0
- data/lib/gamba/graphics.rb +212 -0
- data/lib/gamba/grid.rb +216 -0
- data/lib/gamba/images/image.rb +271 -0
- data/lib/gamba/images/png_decoder.rb +65 -0
- data/lib/gamba/images.rb +115 -0
- data/lib/gamba/measurement_extensions.rb +40 -0
- data/lib/gamba/measurements.rb +62 -0
- data/lib/gamba/outline.rb +148 -0
- data/lib/gamba/page.rb +125 -0
- data/lib/gamba/page_geometry.rb +27 -0
- data/lib/gamba/pdf/decryption.rb +165 -0
- data/lib/gamba/pdf/outline_node.rb +53 -0
- data/lib/gamba/pdf/reader.rb +616 -0
- data/lib/gamba/pdf/reference.rb +31 -0
- data/lib/gamba/pdf/writer.rb +105 -0
- data/lib/gamba/pdf.rb +87 -0
- data/lib/gamba/pdf_a/srgb_profile.rb +61 -0
- data/lib/gamba/pdf_a.rb +132 -0
- data/lib/gamba/repeater.rb +79 -0
- data/lib/gamba/security.rb +111 -0
- data/lib/gamba/stamp.rb +76 -0
- data/lib/gamba/table/cell/kinds.rb +246 -0
- data/lib/gamba/table/cell.rb +331 -0
- data/lib/gamba/table/cells.rb +208 -0
- data/lib/gamba/table.rb +298 -0
- data/lib/gamba/text/box.rb +16 -0
- data/lib/gamba/text/formatted/arranger.rb +217 -0
- data/lib/gamba/text/formatted/box.rb +312 -0
- data/lib/gamba/text/formatted/fragment.rb +205 -0
- data/lib/gamba/text/formatted/line_wrap.rb +205 -0
- data/lib/gamba/text/formatted/parser.rb +150 -0
- data/lib/gamba/text/formatted/wrap.rb +103 -0
- data/lib/gamba/text.rb +310 -0
- data/lib/gamba/transformation_stack.rb +43 -0
- data/lib/gamba/true_type/cff_subset.rb +157 -0
- data/lib/gamba/true_type/file.rb +372 -0
- data/lib/gamba/true_type/open_type_layout.rb +189 -0
- data/lib/gamba/true_type/simple_subsets.rb +108 -0
- data/lib/gamba/true_type/subset.rb +206 -0
- data/lib/gamba/version.rb +10 -0
- data/lib/gamba/view.rb +28 -0
- data/lib/gamba.rb +124 -0
- data/lib/pdf/core.rb +4 -0
- data/lib/prawn/measurement_extensions.rb +4 -0
- data/lib/prawn/svg/attributes/clip_path.rb +46 -0
- data/lib/prawn/svg/attributes/mask.rb +16 -0
- data/lib/prawn/svg/attributes/opacity.rb +30 -0
- data/lib/prawn/svg/attributes/space.rb +10 -0
- data/lib/prawn/svg/attributes/stroke.rb +50 -0
- data/lib/prawn/svg/attributes/transform.rb +10 -0
- data/lib/prawn/svg/attributes.rb +6 -0
- data/lib/prawn/svg/calculators/arc_to_bezier_curve.rb +57 -0
- data/lib/prawn/svg/calculators/aspect_ratio.rb +60 -0
- data/lib/prawn/svg/calculators/document_sizing.rb +103 -0
- data/lib/prawn/svg/calculators/path_length.rb +168 -0
- data/lib/prawn/svg/calculators/pixels.rb +57 -0
- data/lib/prawn/svg/color.rb +240 -0
- data/lib/prawn/svg/css/font_face_parser.rb +72 -0
- data/lib/prawn/svg/css/font_family_parser.rb +39 -0
- data/lib/prawn/svg/css/font_parser.rb +46 -0
- data/lib/prawn/svg/css/selector_parser.rb +180 -0
- data/lib/prawn/svg/css/stylesheets.rb +233 -0
- data/lib/prawn/svg/css/values_parser.rb +68 -0
- data/lib/prawn/svg/document.rb +169 -0
- data/lib/prawn/svg/elements/anchor.rb +10 -0
- data/lib/prawn/svg/elements/base.rb +315 -0
- data/lib/prawn/svg/elements/bbox_scaling.rb +78 -0
- data/lib/prawn/svg/elements/call_duplicator.rb +37 -0
- data/lib/prawn/svg/elements/circle.rb +19 -0
- data/lib/prawn/svg/elements/clip_path.rb +26 -0
- data/lib/prawn/svg/elements/container.rb +10 -0
- data/lib/prawn/svg/elements/direct_render_base.rb +28 -0
- data/lib/prawn/svg/elements/ellipse.rb +20 -0
- data/lib/prawn/svg/elements/gradient.rb +183 -0
- data/lib/prawn/svg/elements/ignored.rb +5 -0
- data/lib/prawn/svg/elements/image.rb +178 -0
- data/lib/prawn/svg/elements/line.rb +27 -0
- data/lib/prawn/svg/elements/marker.rb +72 -0
- data/lib/prawn/svg/elements/mask.rb +65 -0
- data/lib/prawn/svg/elements/path.rb +294 -0
- data/lib/prawn/svg/elements/pattern.rb +150 -0
- data/lib/prawn/svg/elements/polygon.rb +25 -0
- data/lib/prawn/svg/elements/polyline.rb +23 -0
- data/lib/prawn/svg/elements/rect.rb +64 -0
- data/lib/prawn/svg/elements/root.rb +22 -0
- data/lib/prawn/svg/elements/switch.rb +92 -0
- data/lib/prawn/svg/elements/text.rb +99 -0
- data/lib/prawn/svg/elements/text_component.rb +352 -0
- data/lib/prawn/svg/elements/text_node.rb +308 -0
- data/lib/prawn/svg/elements/text_path.rb +153 -0
- data/lib/prawn/svg/elements/use.rb +142 -0
- data/lib/prawn/svg/elements/viewport.rb +29 -0
- data/lib/prawn/svg/elements.rb +46 -0
- data/lib/prawn/svg/extension.rb +25 -0
- data/lib/prawn/svg/font.rb +20 -0
- data/lib/prawn/svg/font_metrics.rb +144 -0
- data/lib/prawn/svg/font_registry.rb +315 -0
- data/lib/prawn/svg/funciri.rb +14 -0
- data/lib/prawn/svg/gradient_renderer.rb +332 -0
- data/lib/prawn/svg/gradients.rb +53 -0
- data/lib/prawn/svg/interface.rb +72 -0
- data/lib/prawn/svg/length.rb +43 -0
- data/lib/prawn/svg/link_renderer.rb +40 -0
- data/lib/prawn/svg/loaders/data.rb +17 -0
- data/lib/prawn/svg/loaders/file.rb +111 -0
- data/lib/prawn/svg/loaders/web.rb +24 -0
- data/lib/prawn/svg/paint.rb +67 -0
- data/lib/prawn/svg/pathable.rb +153 -0
- data/lib/prawn/svg/pattern_renderer.rb +112 -0
- data/lib/prawn/svg/pdfmatrix.rb +39 -0
- data/lib/prawn/svg/percentage.rb +24 -0
- data/lib/prawn/svg/properties.rb +329 -0
- data/lib/prawn/svg/renderer.rb +249 -0
- data/lib/prawn/svg/state.rb +24 -0
- data/lib/prawn/svg/transform_parser.rb +78 -0
- data/lib/prawn/svg/ttc.rb +35 -0
- data/lib/prawn/svg/ttf.rb +95 -0
- data/lib/prawn/svg/url_loader.rb +46 -0
- data/lib/prawn/svg/version.rb +5 -0
- data/lib/prawn/table.rb +4 -0
- data/lib/prawn-svg.rb +52 -0
- data/lib/prawn.rb +3 -0
- 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
|