receipts 2.4.0 → 3.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.
@@ -0,0 +1,387 @@
1
+ require "set"
2
+
3
+ module Receipts
4
+ module PDF
5
+ class UnsupportedFontError < StandardError; end
6
+
7
+ # Reads the tables of a TrueType (.ttf) font needed to measure text and
8
+ # embed a subset of the font in a PDF.
9
+ class TrueType
10
+ REQUIRED_TABLES = %w[head hhea maxp hmtx cmap loca glyf].freeze
11
+
12
+ # Hinting tables copied unchanged into subsets when present
13
+ HINTING_TABLES = ["cvt ", "fpgm", "prep"].freeze
14
+
15
+ CACHE = {}
16
+ CACHE_MUTEX = Mutex.new
17
+ CACHE_SIZE = 16
18
+
19
+ attr_reader :units_per_em, :bbox, :ascender, :descender, :line_gap, :num_glyphs, :cmap,
20
+ :italic_angle, :underline_position, :underline_thickness, :strikeout_position, :strikeout_size,
21
+ :cap_height, :x_height, :weight, :postscript_name
22
+
23
+ # Recently used fonts are cached by path since they're reused across documents,
24
+ # and parsed again if the file changes
25
+ def self.load(path)
26
+ path = File.expand_path(path.to_s)
27
+ mtime = File.mtime(path)
28
+
29
+ CACHE_MUTEX.synchronize do
30
+ cached_mtime, font = CACHE[path]
31
+ unless cached_mtime == mtime
32
+ font = new(File.binread(path))
33
+ CACHE.delete(path)
34
+ CACHE[path] = [mtime, font]
35
+ CACHE.shift while CACHE.size > CACHE_SIZE
36
+ end
37
+ font
38
+ end
39
+ end
40
+
41
+ def initialize(data)
42
+ @data = data.b
43
+
44
+ case @data.byteslice(0, 4)
45
+ when "\x00\x01\x00\x00".b, "true"
46
+ nil
47
+ when "OTTO"
48
+ raise UnsupportedFontError, "OpenType fonts with CFF outlines are not supported, use a TrueType (.ttf) font"
49
+ when "ttcf"
50
+ raise UnsupportedFontError, "TrueType collections (.ttc) are not supported, use a single TrueType (.ttf) font"
51
+ else
52
+ raise UnsupportedFontError, "not a TrueType font"
53
+ end
54
+
55
+ @tables = {}
56
+ u16(4).times do |i|
57
+ record = 12 + i * 16
58
+ @tables[@data.byteslice(record, 4)] = [u32(record + 8), u32(record + 12)]
59
+ end
60
+
61
+ REQUIRED_TABLES.each do |tag|
62
+ raise UnsupportedFontError, "font is missing the #{tag} table" unless @tables[tag]
63
+ end
64
+
65
+ parse_head
66
+ parse_hhea
67
+ parse_maxp
68
+ parse_hmtx
69
+ parse_loca
70
+ parse_cmap
71
+ parse_post
72
+ parse_os2
73
+ parse_name
74
+ end
75
+
76
+ def glyph_id(codepoint)
77
+ @cmap.fetch(codepoint, 0)
78
+ end
79
+
80
+ def advance(gid)
81
+ @advances[gid] || @advances.last
82
+ end
83
+
84
+ def fixed_pitch?
85
+ @fixed_pitch
86
+ end
87
+
88
+ # Builds a font containing only the given glyphs (plus composite glyph parts),
89
+ # renumbered compactly. Returns the font data and a Hash of original to new glyph IDs.
90
+ def subset(gids)
91
+ glyphs = glyph_closure(Set.new(gids) << 0).sort
92
+ mapping = glyphs.each_with_index.to_h
93
+ count = glyphs.size
94
+
95
+ glyf = String.new(encoding: Encoding::BINARY)
96
+ loca = []
97
+ glyphs.each do |gid|
98
+ loca << glyf.bytesize
99
+ data = remap_components(glyph_data(gid), mapping)
100
+ glyf << data << padding(data)
101
+ end
102
+ loca << glyf.bytesize
103
+
104
+ head = table_data("head").dup
105
+ head[8, 4] = [0].pack("N") # checkSumAdjustment, set below
106
+ head[50, 2] = [1].pack("n") # indexToLocFormat: long offsets
107
+
108
+ hhea = table_data("hhea").dup
109
+ hhea[34, 2] = [count].pack("n") # numberOfHMetrics
110
+
111
+ maxp = table_data("maxp").dup
112
+ maxp[4, 2] = [count].pack("n") # numGlyphs
113
+
114
+ hmtx = glyphs.flat_map { |gid| [advance(gid), left_side_bearing(gid)] }.pack("ns>" * count)
115
+
116
+ tables = {"head" => head, "hhea" => hhea, "maxp" => maxp, "hmtx" => hmtx, "loca" => loca.pack("N*"), "glyf" => glyf}
117
+ HINTING_TABLES.each { |tag| tables[tag] = table_data(tag) if @tables[tag] }
118
+
119
+ if @tables["post"]
120
+ # Version 3.0 post table: metrics only, no glyph names
121
+ post = table_data("post").byteslice(0, 32).dup
122
+ post[0, 4] = [0x00030000].pack("N")
123
+ tables["post"] = post
124
+ end
125
+
126
+ [build_font(tables), mapping]
127
+ end
128
+
129
+ private
130
+
131
+ def u16(offset)
132
+ @data.byteslice(offset, 2).unpack1("n")
133
+ end
134
+
135
+ def i16(offset)
136
+ @data.byteslice(offset, 2).unpack1("s>")
137
+ end
138
+
139
+ def u32(offset)
140
+ @data.byteslice(offset, 4).unpack1("N")
141
+ end
142
+
143
+ def table_offset(tag)
144
+ @tables[tag]&.first
145
+ end
146
+
147
+ def table_data(tag)
148
+ offset, length = @tables.fetch(tag)
149
+ @data.byteslice(offset, length)
150
+ end
151
+
152
+ def padding(data)
153
+ "\0".b * (-data.bytesize % 4)
154
+ end
155
+
156
+ def parse_head
157
+ t = table_offset("head")
158
+ @units_per_em = u16(t + 18)
159
+ @bbox = [i16(t + 36), i16(t + 38), i16(t + 40), i16(t + 42)]
160
+ @index_to_loc_format = i16(t + 50)
161
+ end
162
+
163
+ def parse_hhea
164
+ t = table_offset("hhea")
165
+ @ascender = i16(t + 4)
166
+ @descender = i16(t + 6)
167
+ @line_gap = i16(t + 8)
168
+ @number_of_hmetrics = u16(t + 34)
169
+ end
170
+
171
+ def parse_maxp
172
+ @num_glyphs = u16(table_offset("maxp") + 4)
173
+ end
174
+
175
+ def parse_hmtx
176
+ @hmtx_offset = table_offset("hmtx")
177
+ @advances = @data.byteslice(@hmtx_offset, @number_of_hmetrics * 4).unpack("n*").each_slice(2).map(&:first)
178
+ end
179
+
180
+ def left_side_bearing(gid)
181
+ if gid < @number_of_hmetrics
182
+ i16(@hmtx_offset + gid * 4 + 2)
183
+ else
184
+ i16(@hmtx_offset + @number_of_hmetrics * 4 + (gid - @number_of_hmetrics) * 2)
185
+ end
186
+ end
187
+
188
+ def parse_loca
189
+ t = table_offset("loca")
190
+ @loca = if @index_to_loc_format.zero?
191
+ @data.byteslice(t, (@num_glyphs + 1) * 2).unpack("n*").map { |offset| offset * 2 }
192
+ else
193
+ @data.byteslice(t, (@num_glyphs + 1) * 4).unpack("N*")
194
+ end
195
+ @glyf_offset = table_offset("glyf")
196
+ end
197
+
198
+ def glyph_data(gid)
199
+ return "".b if gid >= @num_glyphs
200
+ @data.byteslice(@glyf_offset + @loca[gid], @loca[gid + 1] - @loca[gid])
201
+ end
202
+
203
+ def glyph_closure(gids)
204
+ glyphs = Set.new
205
+ queue = gids.to_a
206
+ until queue.empty?
207
+ gid = queue.pop
208
+ next if gid >= @num_glyphs || !glyphs.add?(gid)
209
+ each_component(glyph_data(gid)) { |_, component| queue << component }
210
+ end
211
+ glyphs
212
+ end
213
+
214
+ # Points composite glyphs at the renumbered IDs of their components
215
+ def remap_components(data, mapping)
216
+ copy = nil
217
+ each_component(data) do |pos, component|
218
+ copy ||= data.dup
219
+ copy[pos, 2] = [mapping.fetch(component, 0)].pack("n")
220
+ end
221
+ copy || data
222
+ end
223
+
224
+ # Composite glyphs reference other glyphs. Yields the byte offset of each
225
+ # component's glyph ID and the ID.
226
+ def each_component(data)
227
+ return if data.bytesize < 10 || data.unpack1("s>") >= 0
228
+
229
+ pos = 10
230
+ loop do
231
+ flags, component = data.byteslice(pos, 4).unpack("nn")
232
+ yield pos + 2, component
233
+ pos += 4
234
+ pos += (flags & 0x0001).zero? ? 2 : 4 # ARG_1_AND_2_ARE_WORDS
235
+ if flags & 0x0008 != 0 # WE_HAVE_A_SCALE
236
+ pos += 2
237
+ elsif flags & 0x0040 != 0 # WE_HAVE_AN_X_AND_Y_SCALE
238
+ pos += 4
239
+ elsif flags & 0x0080 != 0 # WE_HAVE_A_TWO_BY_TWO
240
+ pos += 8
241
+ end
242
+ break if (flags & 0x0020).zero? # MORE_COMPONENTS
243
+ end
244
+ end
245
+
246
+ def parse_cmap
247
+ t = table_offset("cmap")
248
+ subtables = Array.new(u16(t + 2)) do |i|
249
+ record = t + 4 + i * 8
250
+ offset = t + u32(record + 4)
251
+ [u16(record), u16(record + 2), offset, u16(offset)]
252
+ end
253
+
254
+ # Prefer full Unicode (format 12) over the Basic Multilingual Plane only (format 4)
255
+ best = subtables.find { |platform, encoding, _, format| platform == 3 && encoding == 10 && format == 12 } ||
256
+ subtables.find { |platform, _, _, format| platform == 0 && format == 12 } ||
257
+ subtables.find { |platform, encoding, _, format| platform == 3 && encoding == 1 && format == 4 } ||
258
+ subtables.find { |platform, _, _, format| platform == 0 && format == 4 }
259
+ raise UnsupportedFontError, "font has no Unicode character map" unless best
260
+
261
+ @cmap = (best[3] == 12) ? parse_cmap_format12(best[2]) : parse_cmap_format4(best[2])
262
+ end
263
+
264
+ def parse_cmap_format4(offset)
265
+ map = {}
266
+ seg_count = u16(offset + 6) / 2
267
+ ends = offset + 14
268
+ starts = ends + seg_count * 2 + 2
269
+ deltas = starts + seg_count * 2
270
+ range_offsets = deltas + seg_count * 2
271
+
272
+ seg_count.times do |i|
273
+ first = u16(starts + i * 2)
274
+ last = u16(ends + i * 2)
275
+ delta = u16(deltas + i * 2)
276
+ range_offset_pos = range_offsets + i * 2
277
+ range_offset = u16(range_offset_pos)
278
+
279
+ (first..last).each do |codepoint|
280
+ next if codepoint == 0xFFFF
281
+ gid = if range_offset.zero?
282
+ (codepoint + delta) & 0xFFFF
283
+ else
284
+ glyph = u16(range_offset_pos + range_offset + (codepoint - first) * 2)
285
+ glyph.zero? ? 0 : (glyph + delta) & 0xFFFF
286
+ end
287
+ map[codepoint] = gid unless gid.zero?
288
+ end
289
+ end
290
+ map
291
+ end
292
+
293
+ def parse_cmap_format12(offset)
294
+ map = {}
295
+ u32(offset + 12).times do |i|
296
+ group = offset + 16 + i * 12
297
+ first = u32(group)
298
+ last = u32(group + 4)
299
+ gid = u32(group + 8)
300
+ (first..last).each { |codepoint| map[codepoint] = gid + codepoint - first }
301
+ end
302
+ map
303
+ end
304
+
305
+ def parse_post
306
+ if (t = table_offset("post"))
307
+ @italic_angle = i16(t + 4) + u16(t + 6) / 65536.0
308
+ @underline_position = i16(t + 8)
309
+ @underline_thickness = i16(t + 10)
310
+ @fixed_pitch = u32(t + 12) != 0
311
+ else
312
+ @italic_angle = 0
313
+ @underline_position = -@units_per_em / 10
314
+ @underline_thickness = @units_per_em / 20
315
+ @fixed_pitch = false
316
+ end
317
+ end
318
+
319
+ def parse_os2
320
+ if (t = table_offset("OS/2"))
321
+ version = u16(t)
322
+ @weight = u16(t + 4)
323
+ @strikeout_size = i16(t + 26)
324
+ @strikeout_position = i16(t + 28)
325
+ if version >= 2
326
+ @x_height = i16(t + 86)
327
+ @cap_height = i16(t + 88)
328
+ end
329
+ end
330
+
331
+ @weight ||= 400
332
+ @strikeout_size ||= @underline_thickness
333
+ @strikeout_position ||= (@ascender * 0.25).round
334
+ @x_height ||= (@ascender * 0.5).round
335
+ @cap_height ||= (@ascender * 0.7).round
336
+ end
337
+
338
+ def parse_name
339
+ if (t = table_offset("name"))
340
+ strings = t + u16(t + 4)
341
+ u16(t + 2).times do |i|
342
+ record = t + 6 + i * 12
343
+ next unless u16(record + 6) == 6 # PostScript name
344
+
345
+ platform = u16(record)
346
+ raw = @data.byteslice(strings + u16(record + 10), u16(record + 8)).dup
347
+ name = if platform == 1
348
+ raw.force_encoding(Encoding::ISO_8859_1).encode(Encoding::UTF_8)
349
+ else
350
+ raw.force_encoding(Encoding::UTF_16BE).encode(Encoding::UTF_8, invalid: :replace, undef: :replace)
351
+ end
352
+ @postscript_name = name.delete("^!-~").delete("[](){}<>/%#")
353
+ break unless @postscript_name.empty?
354
+ end
355
+ end
356
+
357
+ @postscript_name = "Font" if @postscript_name.nil? || @postscript_name.empty?
358
+ end
359
+
360
+ def build_font(tables)
361
+ tags = tables.keys.sort
362
+ entry_selector = Math.log2(tags.size).floor
363
+ search_range = 2**entry_selector * 16
364
+
365
+ directory = [0x00010000, tags.size, search_range, entry_selector, tags.size * 16 - search_range].pack("Nnnnn")
366
+ body = String.new(encoding: Encoding::BINARY)
367
+ body_offset = 12 + tags.size * 16
368
+ head_offset = nil
369
+
370
+ tags.each do |tag|
371
+ data = tables[tag]
372
+ head_offset = body_offset + body.bytesize if tag == "head"
373
+ directory << [tag, checksum(data), body_offset + body.bytesize, data.bytesize].pack("a4NNN")
374
+ body << data << padding(data)
375
+ end
376
+
377
+ font = directory << body
378
+ font[head_offset + 8, 4] = [(0xB1B0AFBA - checksum(font)) & 0xFFFFFFFF].pack("N")
379
+ font
380
+ end
381
+
382
+ def checksum(data)
383
+ (data + padding(data)).unpack("N*").sum & 0xFFFFFFFF
384
+ end
385
+ end
386
+ end
387
+ end
@@ -0,0 +1,119 @@
1
+ require "zlib"
2
+ require "digest/md5"
3
+
4
+ module Receipts
5
+ module PDF
6
+ # An indirect object reference: `12 0 R`
7
+ Reference = Struct.new(:id)
8
+
9
+ # A string written in hex form: `<48656C6C6F>`
10
+ HexString = Struct.new(:bytes)
11
+
12
+ class Stream
13
+ attr_reader :dictionary, :data
14
+
15
+ # Data is Flate compressed unless the dictionary already declares a filter
16
+ def initialize(data, dictionary = {})
17
+ data = data.b
18
+ dictionary = dictionary.dup
19
+
20
+ unless dictionary.key?(:Filter)
21
+ data = Zlib::Deflate.deflate(data)
22
+ dictionary[:Filter] = :FlateDecode
23
+ end
24
+
25
+ dictionary[:Length] = data.bytesize
26
+ @data = data
27
+ @dictionary = dictionary
28
+ end
29
+ end
30
+
31
+ # Serializes Ruby values into PDF syntax
32
+ module Serializer
33
+ NAME_ESCAPE = /[^\x21-\x7E]|[#%()\/<>\[\]{}]/n
34
+
35
+ module_function
36
+
37
+ def dump(value)
38
+ case value
39
+ when Reference then "#{value.id} 0 R"
40
+ when Symbol then name(value)
41
+ when Hash then "<<" + value.map { |k, v| "#{name(k)} #{dump(v)}" }.join(" ") + ">>"
42
+ when Array then "[" + value.map { |v| dump(v) }.join(" ") + "]"
43
+ when HexString then "<#{value.bytes.unpack1("H*")}>"
44
+ when String then literal(value)
45
+ when Integer then value.to_s
46
+ when Float then number(value)
47
+ when true, false then value.to_s
48
+ when nil then "null"
49
+ else raise ArgumentError, "cannot serialize #{value.class} to PDF"
50
+ end
51
+ end
52
+
53
+ def name(value)
54
+ "/" + value.to_s.b.gsub(NAME_ESCAPE) { |c| format("#%02X", c.ord) }
55
+ end
56
+
57
+ def literal(value)
58
+ "(" + value.b.gsub(/[\\()\r]/n) { |c| (c == "\r") ? "\\r" : "\\#{c}" } + ")"
59
+ end
60
+
61
+ def number(value)
62
+ return value.to_s if value.is_a?(Integer)
63
+ str = format("%.4f", value).sub(/\.?0+\z/, "")
64
+ (str == "-0") ? "0" : str
65
+ end
66
+
67
+ # Text strings (document info, etc) must be PDFDocEncoding or UTF-16BE with a BOM
68
+ def text_string(value)
69
+ value = value.to_s
70
+ value.ascii_only? ? value.b : "\xFE\xFF".b + value.encode("UTF-16BE").b
71
+ end
72
+ end
73
+
74
+ # Assembles numbered objects into a PDF file with a cross-reference table
75
+ class Writer
76
+ def initialize
77
+ @objects = []
78
+ end
79
+
80
+ def reserve
81
+ @objects << nil
82
+ Reference.new(@objects.size)
83
+ end
84
+
85
+ def set(ref, value)
86
+ @objects[ref.id - 1] = value
87
+ ref
88
+ end
89
+
90
+ def add(value)
91
+ set(reserve, value)
92
+ end
93
+
94
+ def render(root:, info:)
95
+ out = "%PDF-1.7\n%\xE2\xE3\xCF\xD3\n".b
96
+
97
+ offsets = @objects.each_with_index.map do |object, index|
98
+ offset = out.bytesize
99
+ out << "#{index + 1} 0 obj\n"
100
+ if object.is_a?(Stream)
101
+ out << Serializer.dump(object.dictionary) << "\nstream\n" << object.data << "\nendstream"
102
+ else
103
+ out << Serializer.dump(object)
104
+ end
105
+ out << "\nendobj\n"
106
+ offset
107
+ end
108
+
109
+ xref = out.bytesize
110
+ id = HexString.new(Digest::MD5.digest(out))
111
+
112
+ out << "xref\n0 #{@objects.size + 1}\n0000000000 65535 f \n"
113
+ offsets.each { |offset| out << format("%010d 00000 n \n", offset) }
114
+ out << "trailer\n" << Serializer.dump(Size: @objects.size + 1, Root: root, Info: info, ID: [id, id])
115
+ out << "\nstartxref\n#{xref}\n%%EOF\n"
116
+ end
117
+ end
118
+ end
119
+ end
@@ -1,3 +1,3 @@
1
1
  module Receipts
2
- VERSION = "2.4.0"
2
+ VERSION = "3.0.0"
3
3
  end
data/lib/receipts.rb CHANGED
@@ -1,7 +1,14 @@
1
1
  require "receipts/version"
2
2
  require "open-uri"
3
- require "prawn"
4
- require "prawn/table"
3
+ require "receipts/pdf/geometry"
4
+ require "receipts/pdf/writer"
5
+ require "receipts/pdf/true_type"
6
+ require "receipts/pdf/font"
7
+ require "receipts/pdf/image"
8
+ require "receipts/pdf/inline_format"
9
+ require "receipts/pdf/text_layout"
10
+ require "receipts/pdf/table"
11
+ require "receipts/pdf/document"
5
12
 
6
13
  module Receipts
7
14
  autoload :Base, "receipts/base"
metadata CHANGED
@@ -1,49 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: receipts
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.4.0
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Oliver
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2024-03-26 00:00:00.000000000 Z
12
- dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: prawn
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: 1.3.0
20
- - - "<"
21
- - !ruby/object:Gem::Version
22
- version: 3.0.0
23
- type: :runtime
24
- prerelease: false
25
- version_requirements: !ruby/object:Gem::Requirement
26
- requirements:
27
- - - ">="
28
- - !ruby/object:Gem::Version
29
- version: 1.3.0
30
- - - "<"
31
- - !ruby/object:Gem::Version
32
- version: 3.0.0
33
- - !ruby/object:Gem::Dependency
34
- name: prawn-table
35
- requirement: !ruby/object:Gem::Requirement
36
- requirements:
37
- - - "~>"
38
- - !ruby/object:Gem::Version
39
- version: 0.2.1
40
- type: :runtime
41
- prerelease: false
42
- version_requirements: !ruby/object:Gem::Requirement
43
- requirements:
44
- - - "~>"
45
- - !ruby/object:Gem::Version
46
- version: 0.2.1
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
47
12
  description: Receipts for your Rails application that works with any payment provider.
48
13
  email:
49
14
  - excid3@gmail.com
@@ -51,6 +16,7 @@ executables: []
51
16
  extensions: []
52
17
  extra_rdoc_files: []
53
18
  files:
19
+ - ".standard.yml"
54
20
  - CHANGELOG.md
55
21
  - Gemfile
56
22
  - LICENSE.txt
@@ -63,11 +29,22 @@ files:
63
29
  - examples/statement.pdf
64
30
  - lib/receipts.rb
65
31
  - lib/receipts/base.rb
32
+ - lib/receipts/fonts/Inter-Bold.ttf
33
+ - lib/receipts/fonts/Inter-Regular.ttf
34
+ - lib/receipts/fonts/OFL.txt
66
35
  - lib/receipts/invoice.rb
36
+ - lib/receipts/pdf/document.rb
37
+ - lib/receipts/pdf/font.rb
38
+ - lib/receipts/pdf/geometry.rb
39
+ - lib/receipts/pdf/image.rb
40
+ - lib/receipts/pdf/inline_format.rb
41
+ - lib/receipts/pdf/table.rb
42
+ - lib/receipts/pdf/text_layout.rb
43
+ - lib/receipts/pdf/true_type.rb
44
+ - lib/receipts/pdf/writer.rb
67
45
  - lib/receipts/receipt.rb
68
46
  - lib/receipts/statement.rb
69
47
  - lib/receipts/version.rb
70
- - receipts.gemspec
71
48
  homepage: https://github.com/excid3/receipts
72
49
  licenses:
73
50
  - MIT
@@ -75,7 +52,6 @@ metadata:
75
52
  homepage_uri: https://github.com/excid3/receipts
76
53
  source_code_uri: https://github.com/excid3/receipts
77
54
  changelog_uri: https://github.com/excid3/receipts/blob/master/CHANGELOG.md
78
- post_install_message:
79
55
  rdoc_options: []
80
56
  require_paths:
81
57
  - lib
@@ -83,15 +59,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
83
59
  requirements:
84
60
  - - ">="
85
61
  - !ruby/object:Gem::Version
86
- version: '0'
62
+ version: '2.7'
87
63
  required_rubygems_version: !ruby/object:Gem::Requirement
88
64
  requirements:
89
65
  - - ">="
90
66
  - !ruby/object:Gem::Version
91
67
  version: '0'
92
68
  requirements: []
93
- rubygems_version: 3.5.4
94
- signing_key:
69
+ rubygems_version: 4.0.21
95
70
  specification_version: 4
96
71
  summary: Receipts for your Rails application that works with any payment provider.
97
72
  test_files: []
data/receipts.gemspec DELETED
@@ -1,33 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "lib/receipts/version"
4
-
5
- Gem::Specification.new do |spec|
6
- spec.name = "receipts"
7
- spec.version = Receipts::VERSION
8
- spec.authors = ["Chris Oliver"]
9
- spec.email = ["excid3@gmail.com"]
10
-
11
- spec.summary = "Receipts for your Rails application that works with any payment provider."
12
- spec.description = "Receipts for your Rails application that works with any payment provider."
13
- spec.homepage = "https://github.com/excid3/receipts"
14
- spec.license = "MIT"
15
-
16
- spec.metadata["homepage_uri"] = spec.homepage
17
- spec.metadata["source_code_uri"] = spec.homepage
18
- spec.metadata["changelog_uri"] = "https://github.com/excid3/receipts/blob/master/CHANGELOG.md"
19
-
20
- # Specify which files should be added to the gem when it is released.
21
- # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
- spec.files = Dir.chdir(File.expand_path(__dir__)) do
23
- `git ls-files -z`.split("\x0").reject do |f|
24
- (f == __FILE__) || f.match(%r{\A(?:(?:test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
25
- end
26
- end
27
- spec.bindir = "exe"
28
- spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
29
- spec.require_paths = ["lib"]
30
-
31
- spec.add_dependency "prawn", ">= 1.3.0", "< 3.0.0"
32
- spec.add_dependency "prawn-table", "~> 0.2.1"
33
- end