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,206 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gamba
4
+ module TrueType
5
+ # A new TrueType font holding only the glyphs a document uses, numbered in the order it first used them
6
+ # (glyph 0, .notdef, stays first). Composite glyphs bring their components along.
7
+ class Subset
8
+ # Tables ISO 32000-1 §9.9 requires in an embedded TrueType font, plus OS/2 and post for strict readers, and
9
+ # gasp, which tells rasterizers like Poppler at which sizes to hint and smooth glyphs.
10
+ COPIED_TABLES = %w[cvt\ fpgm prep OS/2 gasp].freeze
11
+ # Composite glyph flags (OpenType 'glyf' table).
12
+ ARGUMENTS_ARE_WORDS = 0x0001
13
+ HAS_SCALE = 0x0008
14
+ MORE_COMPONENTS = 0x0020
15
+ HAS_X_AND_Y_SCALE = 0x0040
16
+ HAS_TWO_BY_TWO = 0x0080
17
+ CHECKSUM_MAGIC = 0xB1B0AFBA
18
+
19
+ def initialize(file)
20
+ @file = file
21
+ @new_glyph_by_original = {0 => 0}
22
+ end
23
+
24
+ def glyph_for(original_glyph)
25
+ @new_glyph_by_original[original_glyph] ||= @new_glyph_by_original.size
26
+ end
27
+
28
+ def original_glyphs
29
+ @new_glyph_by_original.keys
30
+ end
31
+
32
+ # Numbers the components of composite glyphs, level by level, in the order of their composites' indices.
33
+ def add_components(originals)
34
+ level = originals
35
+ loop do
36
+ components = level.flat_map { component_glyphs(@file.glyph_data(_1)) }
37
+ break if components.empty?
38
+
39
+ components.each { glyph_for(_1) }
40
+ level = components
41
+ end
42
+ end
43
+
44
+ def bytes(extra_tables = {})
45
+ glyphs = glyph_data_including_components
46
+ offsets = [0]
47
+ glyf = glyphs.each_with_object(+"".b) do |data, table|
48
+ table << data << ("\x00" * ((4 - (data.bytesize % 4)) % 4))
49
+ offsets << table.bytesize
50
+ end
51
+ glyph_count = glyphs.size
52
+ widths = @file.advance_widths
53
+ left_side_bearings = original_glyphs.map { left_side_bearing(_1) }
54
+
55
+ bounds = glyphs.filter_map { |glyph| glyph.unpack("s>5") if glyph.bytesize >= 10 }
56
+ advances = original_glyphs.map { widths[_1] }
57
+ tables = {
58
+ "head" => head_table(bounds),
59
+ "hhea" => horizontal_header(bounds, advances, left_side_bearings, glyph_count),
60
+ "maxp" => maximum_profile(bounds, glyphs, glyph_count),
61
+ "hmtx" => horizontal_metrics(original_glyphs, widths, left_side_bearings),
62
+ "loca" => offsets.pack("N*"),
63
+ "glyf" => glyf,
64
+ "post" => post_table
65
+ }
66
+ COPIED_TABLES.each { |tag| tables[tag] = @file.table(tag) if @file.table?(tag) }
67
+ Subset.assemble(tables.merge(extra_tables))
68
+ end
69
+
70
+ # Version 3, no glyph names: the 32-byte header of the font's own post table, zeros where it has none or a
71
+ # shorter one, which the file accepts since the fields it reads stop at byte 16.
72
+ def post_table
73
+ post = (@file.table("post") || "".b).byteslice(0, 32).ljust(32, "\x00".b)
74
+ post[0, 4] = "\x00\x03\x00\x00".b
75
+ post
76
+ end
77
+
78
+ # An sfnt file of these tables, with its directory and checksums (OpenType specification, "Font file").
79
+ def self.assemble(tables, sfnt_version: 0x00010000)
80
+ tags = tables.keys.sort
81
+ table_count = tags.size
82
+ search_range = (2**Math.log2(table_count).floor) * 16
83
+ directory = [sfnt_version, table_count, search_range, Math.log2(search_range / 16).to_i, (table_count * 16) - search_range].pack("Nn4")
84
+ body = +"".b
85
+ offset = 12 + (table_count * 16)
86
+ tags.each do |tag|
87
+ data = tables[tag]
88
+ directory << [tag, Subset.checksum(data), offset + body.bytesize, data.bytesize].pack("a4N3")
89
+ body << data << ("\x00" * ((4 - (data.bytesize % 4)) % 4))
90
+ end
91
+ font = directory << body
92
+ head_offset = 12 + (table_count * 16) + tags.take_while { _1 != "head" }.sum { |tag| (tables[tag].bytesize + 3) & ~3 }
93
+ font[head_offset + 8, 4] = [(CHECKSUM_MAGIC - Subset.checksum(font)) & 0xFFFFFFFF].pack("N")
94
+ font
95
+ end
96
+
97
+ def self.checksum(data)
98
+ padded = data + ("\x00" * ((4 - (data.bytesize % 4)) % 4))
99
+ padded.unpack("N*").sum & 0xFFFFFFFF
100
+ end
101
+
102
+ private
103
+
104
+ def glyph_data_including_components
105
+ glyphs = []
106
+ index = 0
107
+ while index < original_glyphs.size
108
+ glyphs << remap_components(@file.glyph_data(original_glyphs[index]))
109
+ index += 1
110
+ end
111
+ glyphs
112
+ end
113
+
114
+ def component_glyphs(data)
115
+ return [] if data.bytesize < 10 || data.unpack1("s>") >= 0
116
+
117
+ glyphs = []
118
+ position = 10
119
+ loop do
120
+ break if position + 4 > data.bytesize
121
+
122
+ flags, original = data.unpack("@#{position} n2")
123
+ glyphs << original if original < @file.glyph_count
124
+ position += 4 + ((flags & ARGUMENTS_ARE_WORDS).zero? ? 2 : 4)
125
+ position += 2 if (flags & HAS_SCALE).nonzero?
126
+ position += 4 if (flags & HAS_X_AND_Y_SCALE).nonzero?
127
+ position += 8 if (flags & HAS_TWO_BY_TWO).nonzero?
128
+ break if (flags & MORE_COMPONENTS).zero?
129
+ end
130
+ glyphs
131
+ end
132
+
133
+ def remap_components(data)
134
+ return data if data.bytesize < 10 || data.unpack1("s>") >= 0
135
+
136
+ data = data.dup
137
+ position = 10
138
+ loop do
139
+ raise Error, "Truncated composite glyph" if position + 4 > data.bytesize
140
+
141
+ flags, original = data.unpack("@#{position} n2")
142
+ raise Error, "Composite glyph component #{original} beyond the font's #{@file.glyph_count} glyphs" if original >= @file.glyph_count
143
+
144
+ data[position + 2, 2] = [glyph_for(original)].pack("n")
145
+ position += 4 + ((flags & ARGUMENTS_ARE_WORDS).zero? ? 2 : 4)
146
+ position += 2 if (flags & HAS_SCALE).nonzero?
147
+ position += 4 if (flags & HAS_X_AND_Y_SCALE).nonzero?
148
+ position += 8 if (flags & HAS_TWO_BY_TWO).nonzero?
149
+ break if (flags & MORE_COMPONENTS).zero?
150
+ end
151
+ data
152
+ end
153
+
154
+ # Each glyph's advance width then left side bearing, packed: one array, no pairs to flatten.
155
+ def horizontal_metrics(original_glyphs, widths, left_side_bearings)
156
+ metrics = []
157
+ original_glyphs.each_with_index { |glyph, index| metrics.push(widths[glyph], left_side_bearings[index]) }
158
+ metrics.pack("n*")
159
+ end
160
+
161
+ def left_side_bearing(glyph)
162
+ metric_count = @file.table("hhea").unpack1("@34 n")
163
+ hmtx = @file.table("hmtx")
164
+ raw = (glyph < metric_count) ? hmtx.unpack1("@#{(glyph * 4) + 2} n") : hmtx.unpack1("@#{(metric_count * 4) + ((glyph - metric_count) * 2)} n")
165
+ raw.to_i
166
+ end
167
+
168
+ # The subset's own extremes, as the OpenType specification defines them: readers like Poppler size their
169
+ # glyph caches from the font's bounding box.
170
+ def head_table(bounds)
171
+ head = @file.table("head").dup
172
+ head[8, 4] = "\x00\x00\x00\x00".b
173
+ head[36, 8] = [bounds.map { _1[1] }.min || 0, bounds.map { _1[2] }.min || 0, bounds.map { _1[3] }.max || 0, bounds.map { _1[4] }.max || 0].pack("s>4")
174
+ head[50, 2] = [1].pack("n")
175
+ head
176
+ end
177
+
178
+ def horizontal_header(bounds, advances, left_side_bearings, glyph_count)
179
+ hhea = @file.table("hhea").dup
180
+ right_side_bearings = bounds.zip(advances).filter_map { |bound, advance| advance - (bound[3] - bound[1]) - bound[1] if bound && advance }
181
+ extents = bounds.map { _1[3] }
182
+ hhea[10, 8] = [advances.compact.max || 0, left_side_bearings.min || 0, right_side_bearings.min || 0, extents.max || 0].pack("n s>3")
183
+ hhea[34, 2] = [glyph_count].pack("n")
184
+ hhea
185
+ end
186
+
187
+ # Largest simple glyph, in points and contours; composite figures stay those of the whole font.
188
+ def maximum_profile(bounds, glyphs, glyph_count)
189
+ maxp = @file.table("maxp").dup
190
+ maxp[4, 2] = [glyph_count].pack("n")
191
+ return maxp if maxp.bytesize < 10
192
+
193
+ simple = glyphs.select { |glyph| glyph.bytesize >= 10 && glyph.unpack1("s>") >= 0 }
194
+ contours = simple.map { _1.unpack1("s>") }
195
+ points = simple.zip(contours).map! do |glyph, count|
196
+ next 0 if count.zero?
197
+ raise Error, "Truncated simple glyph" if glyph.bytesize < 10 + (count * 2)
198
+
199
+ glyph.unpack1("@#{10 + ((count - 1) * 2)} n") + 1
200
+ end
201
+ maxp[6, 4] = [points.max || 0, contours.max || 0].pack("n2")
202
+ maxp
203
+ end
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Prawn
4
+ # Prawn extensions check Prawn::VERSION: we report the version of the API we mirror.
5
+ VERSION = "2.5.0"
6
+ end
7
+
8
+ module Gamba
9
+ VERSION = "2.0.0"
10
+ end
data/lib/gamba/view.rb ADDED
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Prawn
4
+ # Mixed into a class, turns it into a document: Prawn's methods are called on an implicit Prawn::Document.
5
+ module View
6
+ def document
7
+ @document ||= Document.new
8
+ end
9
+
10
+ def method_missing(method_name, ...)
11
+ return super unless document.respond_to?(method_name)
12
+
13
+ document.public_send(method_name, ...)
14
+ end
15
+
16
+ def respond_to_missing?(method_name, include_all = false)
17
+ document.respond_to?(method_name) || super
18
+ end
19
+
20
+ def update(&)
21
+ instance_eval(&)
22
+ end
23
+
24
+ def save_as(filename)
25
+ document.render_file(filename)
26
+ end
27
+ end
28
+ end
data/lib/gamba.rb ADDED
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ if Gem.loaded_specs.key?("prawn")
4
+ raise LoadError, "Gamba replaces the prawn gem, which is in your bundle too: remove prawn (and prawn-table, " \
5
+ "prawn-svg) from your Gemfile"
6
+ end
7
+
8
+ require_relative "gamba/version"
9
+
10
+ module Prawn
11
+ BASEDIR = File.expand_path("..", __dir__)
12
+ DATADIR = File.join(BASEDIR, "data")
13
+ FLOAT_PRECISION = 1.0e-9
14
+
15
+ class << self
16
+ attr_accessor :debug
17
+
18
+ def verify_options(accepted, actual)
19
+ return unless debug || $DEBUG
20
+
21
+ unknown = actual.keys - Array(accepted)
22
+ raise Errors::UnknownOption, "\nDetected unknown option(s): #{unknown.inspect}\nAccepted options are: #{Array(accepted).inspect}" unless unknown.empty?
23
+
24
+ yield if block_given?
25
+ end
26
+ end
27
+ end
28
+
29
+ require_relative "gamba/errors"
30
+ require_relative "gamba/pdf"
31
+ require_relative "gamba/pdf/reference"
32
+ require_relative "gamba/pdf/writer"
33
+ require_relative "gamba/pdf/outline_node"
34
+ require_relative "gamba/page_geometry"
35
+ require_relative "gamba/encoding"
36
+ require_relative "gamba/graphic_state"
37
+ require_relative "gamba/page"
38
+ require_relative "gamba/transformation_stack"
39
+ require_relative "gamba/graphics/color"
40
+ require_relative "gamba/graphics/style"
41
+ require_relative "gamba/graphics/patterns"
42
+ require_relative "gamba/graphics"
43
+ require_relative "gamba/text"
44
+ require_relative "gamba/text/formatted/parser"
45
+ require_relative "gamba/text/formatted/fragment"
46
+ require_relative "gamba/text/formatted/arranger"
47
+ require_relative "gamba/text/formatted/line_wrap"
48
+ require_relative "gamba/text/formatted/wrap"
49
+ require_relative "gamba/text/formatted/box"
50
+ require_relative "gamba/text/box"
51
+ require_relative "gamba/annotations"
52
+ require_relative "gamba/stamp"
53
+ require_relative "gamba/measurements"
54
+ require_relative "gamba/view"
55
+ require_relative "gamba/images"
56
+ require_relative "gamba/images/png_decoder"
57
+ require_relative "gamba/images/image"
58
+ require_relative "gamba/repeater"
59
+ require_relative "gamba/grid"
60
+ require_relative "gamba/outline"
61
+ require_relative "gamba/table"
62
+ require_relative "gamba/table/cells"
63
+ require_relative "gamba/table/cell"
64
+ require_relative "gamba/table/cell/kinds"
65
+ require_relative "gamba/security"
66
+ require_relative "gamba/pdf_a"
67
+ require_relative "gamba/pdf_a/srgb_profile"
68
+ require_relative "gamba/document/internals"
69
+ require_relative "gamba/document/bounding_box"
70
+ require_relative "gamba/document/span"
71
+ require_relative "gamba/document/column_box"
72
+ require_relative "gamba/document"
73
+ require_relative "gamba/font"
74
+ require_relative "gamba/fonts/afm"
75
+
76
+ # TrueType fonts load on first use: a document of standard fonts never needs them.
77
+ module Gamba
78
+ module TrueType
79
+ autoload :Error, ::File.expand_path("gamba/true_type/file", __dir__)
80
+ autoload :File, ::File.expand_path("gamba/true_type/file", __dir__)
81
+ autoload :Subset, ::File.expand_path("gamba/true_type/subset", __dir__)
82
+ autoload :SimpleSubsets, ::File.expand_path("gamba/true_type/simple_subsets", __dir__)
83
+ autoload :CffSubset, ::File.expand_path("gamba/true_type/cff_subset", __dir__)
84
+ autoload :OpenTypeLayout, ::File.expand_path("gamba/true_type/open_type_layout", __dir__)
85
+ end
86
+ end
87
+
88
+ module Prawn
89
+ module Fonts
90
+ %i[TTF TTC DFont].each { autoload _1, ::File.expand_path("gamba/fonts/ttf", __dir__) }
91
+ autoload :OTF, ::File.expand_path("gamba/fonts/otf", __dir__)
92
+ end
93
+
94
+ class Font
95
+ %i[TTF TTC DFont].each { autoload _1, ::File.expand_path("gamba/fonts/ttf", __dir__) }
96
+ end
97
+ end
98
+
99
+ module Gamba
100
+ autoload :Combine, ::File.expand_path("gamba/combine", __dir__)
101
+
102
+ # The pages of other PDF files, one after the other in a new one:
103
+ # Gamba.combine([label, label], page_size: [288, 432]). See Gamba::Combine#initialize for the options.
104
+ def self.combine(sources, page_size: nil, orientation: nil)
105
+ Combine.new(page_size:, orientation:).call(sources)
106
+ end
107
+
108
+ # Loads what Gamba otherwise loads on first use, SVG support included: call it before a server forks its
109
+ # workers, so they share that memory instead of each loading its own copy.
110
+ def self.eager_load!
111
+ [TrueType::File, TrueType::Subset, Prawn::Fonts::TTF, Combine, PDF::Core::Reader]
112
+ require "prawn-svg"
113
+ end
114
+ end
115
+
116
+ module PDF
117
+ module Core
118
+ autoload :Reader, ::File.expand_path("gamba/pdf/reader", __dir__)
119
+ autoload :Decryption, ::File.expand_path("gamba/pdf/decryption", __dir__)
120
+ end
121
+ end
122
+
123
+ Prawn.image_handler.register(Prawn::Images::JPG)
124
+ Prawn.image_handler.register(Prawn::Images::PNG)
data/lib/pdf/core.rb ADDED
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Gamba replaces pdf-core too: this file keeps `require "pdf/core"` working.
4
+ require "gamba"
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "prawn"
4
+ require "gamba/measurement_extensions"
@@ -0,0 +1,46 @@
1
+ module Prawn::SVG::Attributes::ClipPath
2
+ def parse_clip_path_attribute_and_call
3
+ return unless (clip_path = properties.clip_path)
4
+ return if clip_path == "none"
5
+
6
+ clip_path_element = extract_element_from_url_id_reference(clip_path, "clipPath")
7
+
8
+ if clip_path_element.nil?
9
+ document.warnings << "Could not resolve clip-path URI to a clipPath element"
10
+ else
11
+ clip_calls = clip_path_element.build_clip_calls(self)
12
+ return if clip_calls.nil?
13
+
14
+ add_call_and_enter "save_graphics_state"
15
+
16
+ if clip_path_contains_text?(clip_calls)
17
+ # Text in clip paths is implemented using a PDF soft mask. The text
18
+ # renders in white fill inside the mask to define the visible region.
19
+ # This correctly unions multiple text elements, unlike PDF text
20
+ # rendering mode 7 which intersects separate text objects.
21
+ @calls << ["soft_mask", [], {}, clip_calls]
22
+ else
23
+ @calls.concat clip_calls
24
+
25
+ # SVG's clip-rule applies per-element (determining each shape's interior),
26
+ # then elements are unioned. PDF's W* applies even-odd to the entire combined
27
+ # path, which incorrectly creates holes between separate shapes. Only use W*
28
+ # when there's a single child element, where self-intersection matters.
29
+ child_elements = clip_path_element.svg_child_elements
30
+ if child_elements.length == 1 && clip_path_element.computed_properties.clip_rule == "evenodd"
31
+ add_call "clip", clip_rule: :even_odd
32
+ else
33
+ add_call "clip"
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def clip_path_contains_text?(calls)
42
+ calls.any? do |call, _arguments, _kwarguments, children|
43
+ call == "svg:render" || clip_path_contains_text?(children)
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,16 @@
1
+ module Prawn::SVG::Attributes::Mask
2
+ def parse_mask_attribute_and_call
3
+ return unless (mask = properties.mask)
4
+ return if mask == "none"
5
+
6
+ mask_element = extract_element_from_url_id_reference(mask, "mask")
7
+
8
+ if mask_element.nil?
9
+ document.warnings << "Could not resolve mask URI to a mask element"
10
+ else
11
+ add_call_and_enter "save_graphics_state"
12
+ mask_calls = mask_element.build_mask_calls(self)
13
+ @calls << ["soft_mask", [], {}, mask_calls]
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,30 @@
1
+ module Prawn::SVG::Attributes::Opacity
2
+ def parse_opacity_attributes_and_call
3
+ # The opacity property is not inherited, but the opacity applies to all children underneath the element.
4
+ #
5
+ # Having an opacity property on a parent, and again on a child, multiplies the parent and child's opacities
6
+ # when drawing the children.
7
+ #
8
+ # The fill-opacity and stroke-opacity properties are inherited, but children which have a different value
9
+ # are displayed at that opacity rather than multiplying the parent's fill/stroke opacity with the child's.
10
+ #
11
+ # opacity and fill/stroke opacity can both be applied to the same element, and they multiply together.
12
+
13
+ opacity = computed_properties.opacity&.to_f&.clamp(0, 1)
14
+ fill_opacity = computed_properties.fill_opacity.to_f.clamp(0, 1)
15
+ stroke_opacity = computed_properties.stroke_opacity.to_f.clamp(0, 1)
16
+
17
+ state.opacity *= opacity if opacity
18
+
19
+ fill_opacity = (fill_opacity || 1) * state.opacity
20
+ stroke_opacity = (stroke_opacity || 1) * state.opacity
21
+
22
+ fill_opacity = stroke_opacity = 0 if computed_properties.visibility != "visible"
23
+
24
+ if state.last_fill_opacity != fill_opacity || state.last_stroke_opacity != stroke_opacity
25
+ state.last_fill_opacity = fill_opacity
26
+ state.last_stroke_opacity = stroke_opacity
27
+ add_call_and_enter "transparent", fill_opacity, stroke_opacity
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,10 @@
1
+ module Prawn::SVG::Attributes::Space
2
+ def parse_xml_space_attribute
3
+ case attributes["xml:space"]
4
+ when "preserve"
5
+ state.preserve_space = true
6
+ when "default"
7
+ state.preserve_space = false
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,50 @@
1
+ module Prawn::SVG::Attributes::Stroke
2
+ CAP_STYLE_TRANSLATIONS = {"butt" => :butt, "round" => :round, "square" => :projecting_square}.freeze
3
+ JOIN_STYLE_TRANSLATIONS = {"miter" => :miter, "round" => :round, "bevel" => :bevel}.freeze
4
+
5
+ def parse_stroke_attributes_and_call
6
+ if (width_string = properties.stroke_width)
7
+ width = pixels(width_string)
8
+ state.stroke_width = width
9
+ add_call("line_width", width)
10
+ end
11
+
12
+ if (linecap = properties.stroke_linecap) && linecap != "inherit"
13
+ add_call("cap_style", CAP_STYLE_TRANSLATIONS.fetch(linecap, :butt))
14
+ end
15
+
16
+ if (linejoin = properties.stroke_linejoin) && linejoin != "inherit"
17
+ add_call("join_style", JOIN_STYLE_TRANSLATIONS.fetch(linejoin, :miter))
18
+ end
19
+
20
+ if (miterlimit = properties.stroke_miterlimit) && miterlimit != "inherit"
21
+ add_call("miter_limit", miterlimit)
22
+ end
23
+
24
+ if (dasharray = properties.stroke_dasharray)
25
+ case dasharray
26
+ when "inherit"
27
+ # don't do anything
28
+ when "none"
29
+ add_call("undash")
30
+ when Array
31
+ dasharray *= 2 if dasharray.length.odd?
32
+ values = dasharray.map { |value| pixels(value) }
33
+
34
+ if values.sum.zero?
35
+ add_call("undash")
36
+ else
37
+ options = {}
38
+ offset = computed_properties.stroke_dashoffset
39
+ if offset && offset != 0 && offset != "inherit"
40
+ phase = pixels(offset)
41
+ options[:phase] = phase unless phase.zero?
42
+ end
43
+ add_call("dash", values, **options)
44
+ end
45
+ else
46
+ raise "Unknown dasharray value: #{dasharray.inspect}"
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,10 @@
1
+ module Prawn::SVG::Attributes::Transform
2
+ def parse_transform_attribute_and_call
3
+ # Some elements do not support transforms
4
+ return unless transformable?
5
+ return unless (transform = attributes["transform"])
6
+
7
+ matrix = matrix_for_pdf(parse_transform_attribute(transform))
8
+ add_call_and_enter "transformation_matrix", *matrix unless matrix == [1, 0, 0, 1, 0, 0]
9
+ end
10
+ end
@@ -0,0 +1,6 @@
1
+ module Prawn::SVG::Attributes
2
+ end
3
+
4
+ %w[transform opacity clip_path mask stroke space].each do |name|
5
+ require "prawn/svg/attributes/#{name}"
6
+ end
@@ -0,0 +1,57 @@
1
+ module Prawn::SVG::Calculators
2
+ module ArcToBezierCurve
3
+ protected
4
+
5
+ # Convert the elliptical arc to a cubic bézier curve using this algorithm:
6
+ # http://www.spaceroots.org/documents/ellipse/elliptical-arc.pdf
7
+ #
8
+ # eta_1 and eta_2 are the eccentric anomaly angles (the parametric angles on the
9
+ # unit circle that map to points on the ellipse). theta is the ellipse rotation angle.
10
+ def calculate_bezier_curve_points_for_arc(cx, cy, a, b, eta_1, eta_2, theta)
11
+ e = lambda do |eta|
12
+ [
13
+ cx + (a * Math.cos(theta) * Math.cos(eta)) - (b * Math.sin(theta) * Math.sin(eta)),
14
+ cy + (a * Math.sin(theta) * Math.cos(eta)) + (b * Math.cos(theta) * Math.sin(eta))
15
+ ]
16
+ end
17
+
18
+ ep = lambda do |eta|
19
+ [
20
+ (-a * Math.cos(theta) * Math.sin(eta)) - (b * Math.sin(theta) * Math.cos(eta)),
21
+ (-a * Math.sin(theta) * Math.sin(eta)) + (b * Math.cos(theta) * Math.cos(eta))
22
+ ]
23
+ end
24
+
25
+ iterations = 1
26
+ d_eta = eta_2 - eta_1
27
+
28
+ while iterations < 1024
29
+ break if d_eta.abs <= Math::PI / 2.0
30
+
31
+ iterations *= 2
32
+ d_eta = (eta_2 - eta_1) / iterations
33
+ end
34
+
35
+ (0...iterations).collect do |iteration|
36
+ seg_eta_a = eta_1 + (iteration * d_eta)
37
+ seg_eta_b = eta_1 + ((iteration + 1) * d_eta)
38
+ seg_d_eta = seg_eta_b - seg_eta_a
39
+
40
+ alpha = Math.sin(seg_d_eta) * ((Math.sqrt(4 + (3 * (Math.tan(seg_d_eta / 2)**2))) - 1) / 3)
41
+
42
+ x1, y1 = e[seg_eta_a]
43
+ x2, y2 = e[seg_eta_b]
44
+
45
+ ep_eta1_x, ep_eta1_y = ep[seg_eta_a]
46
+ q1_x = x1 + (alpha * ep_eta1_x)
47
+ q1_y = y1 + (alpha * ep_eta1_y)
48
+
49
+ ep_eta2_x, ep_eta2_y = ep[seg_eta_b]
50
+ q2_x = x2 - (alpha * ep_eta2_x)
51
+ q2_y = y2 - (alpha * ep_eta2_y)
52
+
53
+ {p2: [x2, y2], q1: [q1_x, q1_y], q2: [q2_x, q2_y]}
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,60 @@
1
+ module Prawn::SVG::Calculators
2
+ class AspectRatio
3
+ attr_reader :align, :defer
4
+ attr_reader :width, :height, :x, :y
5
+
6
+ def initialize(value, container_dimensions, object_dimensions)
7
+ values = (value || "xMidYMid meet").split
8
+ @x = @y = 0
9
+
10
+ if values.first == "defer"
11
+ @defer = true
12
+ values.shift
13
+ end
14
+
15
+ @align, @meet_or_slice = values
16
+
17
+ w_container, h_container = container_dimensions
18
+ w_object, h_object = object_dimensions
19
+
20
+ container_ratio = w_container / h_container.to_f
21
+ object_ratio = w_object / h_object.to_f
22
+
23
+ if @align == "none"
24
+ @width, @height = container_dimensions
25
+ else
26
+ matches = @align.to_s.strip.match(/\Ax(Min|Mid|Max)Y(Min|Mid|Max)\z/i) || [nil, "Mid", "Mid"]
27
+
28
+ if (container_ratio > object_ratio) == slice?
29
+ @width = w_container
30
+ @height = w_container / object_ratio
31
+ @y = case matches[2].downcase
32
+ when "min" then 0
33
+ when "mid" then (h_container - (w_container / object_ratio)) / 2
34
+ when "max" then h_container - (w_container / object_ratio)
35
+ end
36
+ else
37
+ @width = h_container * object_ratio
38
+ @height = h_container
39
+ @x = case matches[1].downcase
40
+ when "min" then 0
41
+ when "mid" then (w_container - (h_container * object_ratio)) / 2
42
+ when "max" then w_container - (h_container * object_ratio)
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ def slice?
49
+ @meet_or_slice == "slice"
50
+ end
51
+
52
+ def meet?
53
+ @meet_or_slice != "slice"
54
+ end
55
+
56
+ def inspect
57
+ "[AspectRatio: #{@width},#{@height} offset #{@x},#{@y}]"
58
+ end
59
+ end
60
+ end