giga-fast-kit 0.0.1

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 (75) hide show
  1. checksums.yaml +7 -0
  2. data/giga-fast-kit.gemspec +12 -0
  3. data/prawn-2.5.0/COPYING +2 -0
  4. data/prawn-2.5.0/GPLv2 +339 -0
  5. data/prawn-2.5.0/GPLv3 +674 -0
  6. data/prawn-2.5.0/LICENSE +56 -0
  7. data/prawn-2.5.0/data/fonts/Courier-Bold.afm +342 -0
  8. data/prawn-2.5.0/data/fonts/Courier-BoldOblique.afm +342 -0
  9. data/prawn-2.5.0/data/fonts/Courier-Oblique.afm +342 -0
  10. data/prawn-2.5.0/data/fonts/Courier.afm +342 -0
  11. data/prawn-2.5.0/data/fonts/Helvetica-Bold.afm +2827 -0
  12. data/prawn-2.5.0/data/fonts/Helvetica-BoldOblique.afm +2827 -0
  13. data/prawn-2.5.0/data/fonts/Helvetica-Oblique.afm +3051 -0
  14. data/prawn-2.5.0/data/fonts/Helvetica.afm +3051 -0
  15. data/prawn-2.5.0/data/fonts/MustRead.html +19 -0
  16. data/prawn-2.5.0/data/fonts/Symbol.afm +213 -0
  17. data/prawn-2.5.0/data/fonts/Times-Bold.afm +2588 -0
  18. data/prawn-2.5.0/data/fonts/Times-BoldItalic.afm +2384 -0
  19. data/prawn-2.5.0/data/fonts/Times-Italic.afm +2667 -0
  20. data/prawn-2.5.0/data/fonts/Times-Roman.afm +2419 -0
  21. data/prawn-2.5.0/data/fonts/ZapfDingbats.afm +225 -0
  22. data/prawn-2.5.0/lib/prawn/document/bounding_box.rb +625 -0
  23. data/prawn-2.5.0/lib/prawn/document/column_box.rb +177 -0
  24. data/prawn-2.5.0/lib/prawn/document/internals.rb +76 -0
  25. data/prawn-2.5.0/lib/prawn/document/span.rb +65 -0
  26. data/prawn-2.5.0/lib/prawn/document.rb +846 -0
  27. data/prawn-2.5.0/lib/prawn/encoding.rb +85 -0
  28. data/prawn-2.5.0/lib/prawn/errors.rb +73 -0
  29. data/prawn-2.5.0/lib/prawn/font.rb +567 -0
  30. data/prawn-2.5.0/lib/prawn/font_metric_cache.rb +50 -0
  31. data/prawn-2.5.0/lib/prawn/fonts/afm.rb +332 -0
  32. data/prawn-2.5.0/lib/prawn/fonts/dfont.rb +42 -0
  33. data/prawn-2.5.0/lib/prawn/fonts/otf.rb +14 -0
  34. data/prawn-2.5.0/lib/prawn/fonts/to_unicode_cmap.rb +151 -0
  35. data/prawn-2.5.0/lib/prawn/fonts/ttc.rb +41 -0
  36. data/prawn-2.5.0/lib/prawn/fonts/ttf.rb +615 -0
  37. data/prawn-2.5.0/lib/prawn/fonts.rb +14 -0
  38. data/prawn-2.5.0/lib/prawn/graphics/blend_mode.rb +62 -0
  39. data/prawn-2.5.0/lib/prawn/graphics/cap_style.rb +44 -0
  40. data/prawn-2.5.0/lib/prawn/graphics/color.rb +257 -0
  41. data/prawn-2.5.0/lib/prawn/graphics/dash.rb +123 -0
  42. data/prawn-2.5.0/lib/prawn/graphics/join_style.rb +60 -0
  43. data/prawn-2.5.0/lib/prawn/graphics/patterns.rb +389 -0
  44. data/prawn-2.5.0/lib/prawn/graphics/transformation.rb +172 -0
  45. data/prawn-2.5.0/lib/prawn/graphics/transparency.rb +79 -0
  46. data/prawn-2.5.0/lib/prawn/graphics.rb +791 -0
  47. data/prawn-2.5.0/lib/prawn/grid.rb +449 -0
  48. data/prawn-2.5.0/lib/prawn/image_handler.rb +61 -0
  49. data/prawn-2.5.0/lib/prawn/images/image.rb +48 -0
  50. data/prawn-2.5.0/lib/prawn/images/jpg.rb +121 -0
  51. data/prawn-2.5.0/lib/prawn/images/png.rb +383 -0
  52. data/prawn-2.5.0/lib/prawn/images.rb +195 -0
  53. data/prawn-2.5.0/lib/prawn/measurement_extensions.rb +82 -0
  54. data/prawn-2.5.0/lib/prawn/measurements.rb +129 -0
  55. data/prawn-2.5.0/lib/prawn/outline.rb +316 -0
  56. data/prawn-2.5.0/lib/prawn/repeater.rb +140 -0
  57. data/prawn-2.5.0/lib/prawn/security/arcfour.rb +54 -0
  58. data/prawn-2.5.0/lib/prawn/security.rb +308 -0
  59. data/prawn-2.5.0/lib/prawn/soft_mask.rb +105 -0
  60. data/prawn-2.5.0/lib/prawn/stamp.rb +147 -0
  61. data/prawn-2.5.0/lib/prawn/text/box.rb +192 -0
  62. data/prawn-2.5.0/lib/prawn/text/formatted/arranger.rb +396 -0
  63. data/prawn-2.5.0/lib/prawn/text/formatted/box.rb +725 -0
  64. data/prawn-2.5.0/lib/prawn/text/formatted/fragment.rb +373 -0
  65. data/prawn-2.5.0/lib/prawn/text/formatted/line_wrap.rb +335 -0
  66. data/prawn-2.5.0/lib/prawn/text/formatted/parser.rb +284 -0
  67. data/prawn-2.5.0/lib/prawn/text/formatted/wrap.rb +166 -0
  68. data/prawn-2.5.0/lib/prawn/text/formatted.rb +82 -0
  69. data/prawn-2.5.0/lib/prawn/text.rb +701 -0
  70. data/prawn-2.5.0/lib/prawn/transformation_stack.rb +63 -0
  71. data/prawn-2.5.0/lib/prawn/utilities.rb +34 -0
  72. data/prawn-2.5.0/lib/prawn/version.rb +6 -0
  73. data/prawn-2.5.0/lib/prawn/view.rb +116 -0
  74. data/prawn-2.5.0/lib/prawn.rb +83 -0
  75. metadata +114 -0
@@ -0,0 +1,615 @@
1
+ # frozen_string_literal: true
2
+
3
+ # prawn/font/ttf.rb : Implements AFM font support for Prawn
4
+ #
5
+ # Copyright May 2008, Gregory Brown / James Healy / Jamis Buck
6
+ # All Rights Reserved.
7
+ #
8
+ # This is free software. Please see the LICENSE and COPYING files for details.
9
+
10
+ require 'ttfunk'
11
+ require 'ttfunk/subset_collection'
12
+ require_relative 'to_unicode_cmap'
13
+
14
+ module Prawn
15
+ module Fonts
16
+ # TrueType font.
17
+ #
18
+ # @note You shouldn't use this class directly.
19
+ class TTF < Font
20
+ # TrueType font error.
21
+ class Error < StandardError
22
+ # @private
23
+ DEFAULT_MESSAGE = 'TTF font error'
24
+
25
+ # @private
26
+ MESSAGE_WITH_FONT = 'TTF font error in font %<font>s'
27
+
28
+ def initialize(message = DEFAULT_MESSAGE, font: nil)
29
+ if font && message == DEFAULT_MESSAGE
30
+ super(format(MESSAGE_WITH_FONT, font: font))
31
+ else
32
+ super(message)
33
+ end
34
+ end
35
+ end
36
+
37
+ # Signals absence of a Unicode character map in the font.
38
+ class NoUnicodeCMap < Error
39
+ # @private
40
+ DEFAULT_MESSAGE = 'No unicode cmap found in font'
41
+
42
+ # @private
43
+ MESSAGE_WITH_FONT = 'No unicode cmap found in font %<font>s'
44
+ end
45
+
46
+ # Signals absense of a PostScript font name.
47
+ class NoPostscriptName < Error
48
+ # @private
49
+ DEFAULT_MESSAGE = 'Can not detect a postscript name'
50
+
51
+ # @private
52
+ MESSAGE_WITH_FONT = 'Can not detect a postscript name in font %<font>s'
53
+ end
54
+
55
+ # TTFunk font.
56
+ # @return [TTFunk::File]
57
+ attr_reader :ttf
58
+ attr_reader :subsets
59
+
60
+ # Does this font support Unicode?
61
+ #
62
+ # @return [true]
63
+ def unicode?
64
+ true
65
+ end
66
+
67
+ # An adapter for subset collection to represent a full font.
68
+ #
69
+ # @private
70
+ class FullFontSubsetsCollection
71
+ FULL_FONT = Object.new.tap do |obj|
72
+ obj.singleton_class.define_method(:inspect) do
73
+ super().insert(-2, ' FULL_FONT')
74
+ end
75
+ end.freeze
76
+
77
+ def initialize(original)
78
+ @original = original
79
+
80
+ (@cmap ||= original.cmap.unicode.first) || raise(NoUnicodeCMap.new(font: name))
81
+
82
+ @code_space_size =
83
+ case cmap.code_map.keys.max
84
+ when 0..0xff then 1
85
+ when 0x100..0xffff then 2
86
+ when 0x10000..0xffffff then 3
87
+ else
88
+ 4
89
+ end
90
+
91
+ # Codespaces are not sequentional, they're ranges in
92
+ # a multi-dimentional space. Each byte is considered separately. So we
93
+ # have to maximally extend the lower two bytes in order to allow for
94
+ # continuos Unicode mapping.
95
+ # We only keep the highest byte because Unicode only goes to 1FFFFF
96
+ # and fonts usually cover even less of the space. We don't want to
97
+ # list all those unmapped charac codes here.
98
+ @code_space_max = cmap.code_map.keys.max | ('ff' * (code_space_size - 1)).to_i(16)
99
+ end
100
+
101
+ # Encode characters.
102
+ #
103
+ # @return [Array<Array(FULL_FONT, String)>]
104
+ def encode(characters)
105
+ [
106
+ [
107
+ FULL_FONT,
108
+ characters.map { |c|
109
+ check_bounds!(c)
110
+ [cmap[c]].pack('n')
111
+ }.join(''),
112
+ ],
113
+ ]
114
+ end
115
+
116
+ private
117
+
118
+ attr_reader :cmap
119
+ attr_reader :code_space_size
120
+ attr_reader :code_space_max
121
+
122
+ def check_bounds!(num)
123
+ if num > code_space_max
124
+ raise Error, "CID (#{num}) exceedes code space size"
125
+ end
126
+ end
127
+ end
128
+
129
+ # @param document [Prawn::Document]
130
+ # @param name [String] font file path
131
+ # @param options [Hash]
132
+ # @option options :family [String]
133
+ # @option options :style [Symbol]
134
+ def initialize(document, name, options = {})
135
+ super
136
+
137
+ @ttf = read_ttf_file
138
+ @subsets =
139
+ if full_font_embedding
140
+ FullFontSubsetsCollection.new(@ttf)
141
+ else
142
+ TTFunk::SubsetCollection.new(@ttf)
143
+ end
144
+ @italic_angle = nil
145
+
146
+ @attributes = {}
147
+ @bounding_boxes = {}
148
+ @char_widths = {}
149
+ @has_kerning_data = @ttf.kerning.exists? && @ttf.kerning.tables.any?
150
+
151
+ @ascender = Integer(@ttf.ascent * scale_factor)
152
+ @descender = Integer(@ttf.descent * scale_factor)
153
+ @line_gap = Integer(@ttf.line_gap * scale_factor)
154
+ end
155
+
156
+ # Compute width of a string at the specified size, optionally with kerning
157
+ # applied.
158
+ #
159
+ # @param string [String] *must* be encoded as UTF-8
160
+ # @param options [Hash{Symbol => any}]
161
+ # @option options :size [Number]
162
+ # @option options :kerning [Boolean] (false)
163
+ # @return [Number]
164
+ def compute_width_of(string, options = {})
165
+ scale = (options[:size] || size) / 1000.0
166
+ if options[:kerning]
167
+ kern(string).reduce(0) { |s, r|
168
+ if r.is_a?(Numeric)
169
+ s - r
170
+ else
171
+ r.reduce(s) { |a, e| a + character_width_by_code(e) }
172
+ end
173
+ } * scale
174
+ else
175
+ string.codepoints.reduce(0) { |s, r|
176
+ s + character_width_by_code(r)
177
+ } * scale
178
+ end
179
+ end
180
+
181
+ # The font bbox.
182
+ #
183
+ # @return [Array(Number, Number, Number, Number)]
184
+ def bbox
185
+ @bbox ||= @ttf.bbox.map { |i| Integer(i * scale_factor) }
186
+ end
187
+
188
+ # Does this font contain kerning data.
189
+ #
190
+ # @return [Boolean]
191
+ def has_kerning_data? # rubocop: disable Naming/PredicateName
192
+ @has_kerning_data
193
+ end
194
+
195
+ # Perform any changes to the string that need to happen before it is
196
+ # rendered to the canvas. Returns an array of subset "chunks", where the
197
+ # even-numbered indices are the font subset number, and the following
198
+ # entry element is either a string or an array (for kerned text).
199
+ #
200
+ # @param text [String] must be in UTF-8 encoding
201
+ # @param options [Hash{Symbol => any}]
202
+ # @option options :kerning [Boolean]
203
+ # @return [Array<Array(0, (String, Array)>]
204
+ def encode_text(text, options = {})
205
+ text = text.chomp
206
+
207
+ if options[:kerning]
208
+ last_subset = nil
209
+ kern(text).reduce([]) do |result, element|
210
+ if element.is_a?(Numeric)
211
+ unless result.last[1].is_a?(Array)
212
+ result.last[1] = [result.last[1]]
213
+ end
214
+ result.last[1] << element
215
+ result
216
+ else
217
+ encoded = @subsets.encode(element)
218
+
219
+ if encoded.first[0] == last_subset
220
+ result.last[1] << encoded.first[1]
221
+ encoded.shift
222
+ end
223
+
224
+ if encoded.any?
225
+ last_subset = encoded.last[0]
226
+ result + encoded
227
+ else
228
+ result
229
+ end
230
+ end
231
+ end
232
+ else
233
+ @subsets.encode(text.unpack('U*'))
234
+ end
235
+ end
236
+
237
+ # Base name of the font.
238
+ #
239
+ # @return [String]
240
+ def basename
241
+ @basename ||= @ttf.name.postscript_name
242
+ end
243
+
244
+ # @devnote not sure how to compute this for true-type fonts...
245
+ #
246
+ # @private
247
+ # @return [Number]
248
+ def stem_v
249
+ 0
250
+ end
251
+
252
+ # @private
253
+ # @return [Number]
254
+ def italic_angle
255
+ return @italic_angle if @italic_angle
256
+
257
+ if @ttf.postscript.exists?
258
+ raw = @ttf.postscript.italic_angle
259
+ hi = raw >> 16
260
+ low = raw & 0xFF
261
+ hi = -((hi ^ 0xFFFF) + 1) if hi & 0x8000 != 0
262
+ @italic_angle = Float("#{hi}.#{low}")
263
+ else
264
+ @italic_angle = 0
265
+ end
266
+
267
+ @italic_angle
268
+ end
269
+
270
+ # @private
271
+ # @return [Number]
272
+ def cap_height
273
+ @cap_height ||=
274
+ begin
275
+ height = (@ttf.os2.exists? && @ttf.os2.cap_height) || 0
276
+ height.zero? ? @ascender : height
277
+ end
278
+ end
279
+
280
+ # @private
281
+ # @return [number]
282
+ def x_height
283
+ # FIXME: seems like if os2 table doesn't exist, we could
284
+ # just find the height of the lower-case 'x' glyph?
285
+ (@ttf.os2.exists? && @ttf.os2.x_height) || 0
286
+ end
287
+
288
+ # @private
289
+ # @return [Number]
290
+ def family_class
291
+ @family_class ||= ((@ttf.os2.exists? && @ttf.os2.family_class) || 0) >> 8
292
+ end
293
+
294
+ # @private
295
+ # @return [Boolean]
296
+ def serif?
297
+ @serif ||= [1, 2, 3, 4, 5, 7].include?(family_class)
298
+ end
299
+
300
+ # @private
301
+ # @return [Boolean]
302
+ def script?
303
+ @script ||= family_class == 10
304
+ end
305
+
306
+ # @private
307
+ # @return [Integer]
308
+ def pdf_flags
309
+ @pdf_flags ||=
310
+ begin
311
+ flags = 0
312
+ flags |= 0x0001 if @ttf.postscript.fixed_pitch?
313
+ flags |= 0x0002 if serif?
314
+ flags |= 0x0008 if script?
315
+ flags |= 0x0040 if italic_angle != 0
316
+ # Assume the font contains at least some non-latin characters
317
+ flags | 0x0004
318
+ end
319
+ end
320
+
321
+ # Normlize text to a compatible encoding.
322
+ #
323
+ # @param text [String]
324
+ # @return [String]
325
+ def normalize_encoding(text)
326
+ text.encode(::Encoding::UTF_8)
327
+ rescue StandardError
328
+ raise Prawn::Errors::IncompatibleStringEncoding,
329
+ "Encoding #{text.encoding} can not be transparently converted to UTF-8. " \
330
+ 'Please ensure the encoding of the string you are attempting to use is set correctly'
331
+ end
332
+
333
+ # Encode text to UTF-8.
334
+ #
335
+ # @param text [String]
336
+ # @return [String]
337
+ def to_utf8(text)
338
+ text.encode('UTF-8')
339
+ end
340
+
341
+ # Does this font has a glyph for the character?
342
+ #
343
+ # @param char [String]
344
+ # @return [Boolean]
345
+ def glyph_present?(char)
346
+ code = char.codepoints.first
347
+ cmap[code].positive?
348
+ end
349
+
350
+ # Returns the number of characters in `str` (a UTF-8-encoded string).
351
+ #
352
+ # @param str [String]
353
+ # @return [Integer]
354
+ def character_count(str)
355
+ str.length
356
+ end
357
+
358
+ private
359
+
360
+ def cmap
361
+ (@cmap ||= @ttf.cmap.unicode.first) || raise(NoUnicodeCMap.new(font: name))
362
+ end
363
+
364
+ # +string+ must be UTF8-encoded.
365
+ #
366
+ # Returns an array. If an element is a numeric, it represents the
367
+ # kern amount to inject at that position. Otherwise, the element
368
+ # is an array of UTF-16 characters.
369
+ def kern(string)
370
+ a = []
371
+
372
+ string.each_codepoint do |r|
373
+ if a.empty?
374
+ a << [r]
375
+ elsif (kern = kern_pairs_table[[cmap[a.last.last], cmap[r]]])
376
+ kern *= scale_factor
377
+ a << -kern << [r]
378
+ else
379
+ a.last << r
380
+ end
381
+ end
382
+
383
+ a
384
+ end
385
+
386
+ def kern_pairs_table
387
+ @kern_pairs_table ||=
388
+ if has_kerning_data?
389
+ @ttf.kerning.tables.first.pairs
390
+ else
391
+ {}
392
+ end
393
+ end
394
+
395
+ def hmtx
396
+ @hmtx ||= @ttf.horizontal_metrics
397
+ end
398
+
399
+ def character_width_by_code(code)
400
+ return 0 unless cmap[code]
401
+
402
+ # Some TTF fonts have nonzero widths for \n (UTF-8 / ASCII code: 10).
403
+ # Patch around this as we'll never be drawing a newline with a width.
404
+ return 0.0 if code == 10
405
+
406
+ @char_widths[code] ||= Integer(hmtx.widths[cmap[code]] * scale_factor)
407
+ end
408
+
409
+ def scale_factor
410
+ @scale_factor ||= 1000.0 / @ttf.header.units_per_em
411
+ end
412
+
413
+ def register(subset)
414
+ temp_name = @ttf.name.postscript_name.delete("\0").to_sym
415
+ ref = @document.ref!(Type: :Font, BaseFont: temp_name)
416
+
417
+ # Embed the font metrics in the document after everything has been
418
+ # drawn, just before the document is emitted.
419
+ @document.renderer.before_render { |_doc| embed(ref, subset) }
420
+
421
+ ref
422
+ end
423
+
424
+ def embed(reference, subset)
425
+ if full_font_embedding
426
+ embed_full_font(reference)
427
+ else
428
+ embed_subset(reference, subset)
429
+ end
430
+ end
431
+
432
+ def embed_subset(reference, subset)
433
+ font = TTFunk::File.new(@subsets[subset].encode)
434
+ unicode_mapping = @subsets[subset].to_unicode_map
435
+ embed_simple_font(reference, font, unicode_mapping)
436
+ end
437
+
438
+ def embed_simple_font(reference, font, unicode_mapping)
439
+ if font_type(font) == :unknown
440
+ raise Error, %(Simple font embedding is not uspported for font "#{font.name}.")
441
+ end
442
+
443
+ true_type = font_type(font) == :true_type
444
+ open_type = font_type(font) == :open_type
445
+
446
+ # empirically, it looks like Adobe Reader will not display fonts
447
+ # if their font name is more than 33 bytes long. Strange. But true.
448
+ basename = font.name.postscript_name[0, 33].delete("\0")
449
+
450
+ raise NoPostscriptName.new(font: font) if basename.nil?
451
+
452
+ fontfile = @document.ref!({})
453
+ fontfile.data[:Length1] = font.contents.size
454
+ fontfile.stream << font.contents.string
455
+ fontfile.stream.compress! if @document.compression_enabled?
456
+
457
+ descriptor = @document.ref!(
458
+ Type: :FontDescriptor,
459
+ FontName: basename.to_sym,
460
+ FontBBox: bbox,
461
+ Flags: pdf_flags,
462
+ StemV: stem_v,
463
+ ItalicAngle: italic_angle,
464
+ Ascent: @ascender,
465
+ Descent: @descender,
466
+ CapHeight: cap_height,
467
+ XHeight: x_height,
468
+ )
469
+
470
+ first_char, last_char = unicode_mapping.keys.minmax
471
+ hmtx = font.horizontal_metrics
472
+ widths =
473
+ (first_char..last_char).map { |code|
474
+ if unicode_mapping.key?(code)
475
+ gid = font.cmap.tables.first.code_map[code]
476
+ Integer(hmtx.widths[gid] * scale_factor)
477
+ else
478
+ # These characters are not in the document so we don't ever use
479
+ # these values but we need to encode them so let's use as little
480
+ # sapce as possible.
481
+ 0
482
+ end
483
+ }
484
+
485
+ # It would be nice to have Encoding set for the macroman subsets,
486
+ # and only do a ToUnicode cmap for non-encoded unicode subsets.
487
+ # However, apparently Adobe Reader won't render MacRoman encoded
488
+ # subsets if original font contains unicode characters. (It has to
489
+ # be some flag or something that ttfunk is simply copying over...
490
+ # but I can't figure out which flag that is.)
491
+ #
492
+ # For now, it's simplest to just create a unicode cmap for every font.
493
+ # It offends my inner purist, but it'll do.
494
+
495
+ to_unicode = @document.ref!({})
496
+ to_unicode << ToUnicodeCMap.new(unicode_mapping).generate
497
+ to_unicode.stream.compress! if @document.compression_enabled?
498
+
499
+ reference.data.update(
500
+ BaseFont: basename.to_sym,
501
+ FontDescriptor: descriptor,
502
+ FirstChar: first_char,
503
+ LastChar: last_char,
504
+ Widths: @document.ref!(widths),
505
+ ToUnicode: to_unicode,
506
+ )
507
+
508
+ if true_type
509
+ reference.data.update(Subtype: :TrueType)
510
+ descriptor.data.update(FontFile2: fontfile)
511
+ elsif open_type
512
+ @document.renderer.min_version(1.6)
513
+ reference.data.update(Subtype: :Type1)
514
+ descriptor.data.update(FontFile3: fontfile)
515
+ fontfile.data.update(Subtype: :OpenType)
516
+ end
517
+ end
518
+
519
+ def embed_full_font(reference)
520
+ embed_composite_font(reference, @ttf)
521
+ end
522
+
523
+ def embed_composite_font(reference, font)
524
+ if font_type(font) == :unknown
525
+ raise Error, %(Composite font embedding is not uspported for font "#{font.name}.")
526
+ end
527
+
528
+ true_type = font_type(font) == :true_type
529
+ open_type = font_type(font) == :open_type
530
+
531
+ fontfile = @document.ref!({})
532
+ fontfile.data[:Length1] = font.contents.size if true_type
533
+ fontfile.data[:Subtype] = :CIDFontType0C if open_type
534
+ fontfile.stream << font.contents.string
535
+ fontfile.stream.compress! if @document.compression_enabled?
536
+
537
+ # empirically, it looks like Adobe Reader will not display fonts
538
+ # if their font name is more than 33 bytes long. Strange. But true.
539
+ basename = font.name.postscript_name[0, 33].delete("\0")
540
+
541
+ descriptor = @document.ref!(
542
+ Type: :FontDescriptor,
543
+ FontName: basename.to_sym,
544
+ FontBBox: bbox,
545
+ Flags: pdf_flags,
546
+ StemV: stem_v,
547
+ ItalicAngle: italic_angle,
548
+ Ascent: @ascender,
549
+ Descent: @descender,
550
+ CapHeight: cap_height,
551
+ XHeight: x_height,
552
+ )
553
+ descriptor.data[:FontFile2] = fontfile if true_type
554
+ descriptor.data[:FontFile3] = fontfile if open_type
555
+
556
+ to_unicode = @document.ref!({})
557
+ to_unicode << ToUnicodeCMap.new(
558
+ font.cmap.unicode.first
559
+ .code_map
560
+ .reject { |cid, gid| gid.zero? || (0xd800..0xdfff).cover?(cid) }
561
+ .invert
562
+ .sort.to_h,
563
+ 2, # Identity-H is a 2-byte encoding
564
+ ).generate
565
+ to_unicode.stream.compress! if @document.compression_enabled?
566
+
567
+ widths =
568
+ font.horizontal_metrics.widths.map { |w| (w * scale_factor).round }
569
+
570
+ child_font = @document.ref!(
571
+ Type: :Font,
572
+ BaseFont: basename.to_sym,
573
+ CIDSystemInfo: {
574
+ Registry: 'Adobe',
575
+ Ordering: 'Identity',
576
+ Supplement: 0,
577
+ },
578
+ FontDescriptor: descriptor,
579
+ W: [0, widths],
580
+ )
581
+ if true_type
582
+ child_font.data.update(
583
+ Subtype: :CIDFontType2,
584
+ CIDToGIDMap: :Identity,
585
+ )
586
+ end
587
+ if open_type
588
+ child_font.data[:Subtype] = :CIDFontType0
589
+ end
590
+
591
+ reference.data.update(
592
+ Subtype: :Type0,
593
+ BaseFont: basename.to_sym,
594
+ Encoding: :'Identity-H',
595
+ DescendantFonts: [child_font],
596
+ ToUnicode: to_unicode,
597
+ )
598
+ end
599
+
600
+ def font_type(font)
601
+ if font.directory.tables.key?('glyf')
602
+ :true_type
603
+ elsif font.directory.tables.key?('CFF ')
604
+ :open_type
605
+ else
606
+ :unknown
607
+ end
608
+ end
609
+
610
+ def read_ttf_file
611
+ TTFunk::File.open(@name)
612
+ end
613
+ end
614
+ end
615
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Prawn
4
+ # Namespace for different fonts.
5
+ module Fonts
6
+ end
7
+ end
8
+
9
+ require_relative 'font'
10
+ require_relative 'fonts/afm'
11
+ require_relative 'fonts/ttf'
12
+ require_relative 'fonts/dfont'
13
+ require_relative 'fonts/otf'
14
+ require_relative 'fonts/ttc'
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Prawn
4
+ module Graphics
5
+ # The {Prawn::BlendMode} module is used to change the way two graphic
6
+ # objects are blended together.
7
+ module BlendMode
8
+ # @group Stable API
9
+
10
+ # Set blend mode. If a block is passed blend mode is restored afterwards.
11
+ #
12
+ # Passing an array of blend modes is allowed. PDF viewers should blend
13
+ # layers based on the first recognized blend mode.
14
+ #
15
+ # Valid blend modes since PDF 1.4 include `:Normal`, `:Multiply`, `:Screen`,
16
+ # `:Overlay`, `:Darken`, `:Lighten`, `:ColorDodge`, `:ColorBurn`,
17
+ # `:HardLight`, `:SoftLight`, `:Difference`, `:Exclusion`, `:Hue`,
18
+ # `:Saturation`, `:Color`, and `:Luminosity`.
19
+ #
20
+ # @example
21
+ # pdf.fill_color('0000ff')
22
+ # pdf.fill_rectangle([x, y + 25], 50, 50)
23
+ # pdf.blend_mode(:Multiply) do
24
+ # pdf.fill_color('ff0000')
25
+ # pdf.fill_circle([x, y], 25)
26
+ # end
27
+ #
28
+ # @param blend_mode [Symbol, Array<Symbol>]
29
+ # @yield
30
+ # @return [void]
31
+ def blend_mode(blend_mode = :Normal)
32
+ renderer.min_version(1.4)
33
+
34
+ save_graphics_state if block_given?
35
+ renderer.add_content("/#{blend_mode_dictionary_name(blend_mode)} gs")
36
+ if block_given?
37
+ yield
38
+ restore_graphics_state
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def blend_mode_dictionary_registry
45
+ @blend_mode_dictionary_registry ||= {}
46
+ end
47
+
48
+ def blend_mode_dictionary_name(blend_mode)
49
+ key = Array(blend_mode).join('')
50
+ dictionary_name = "BM#{key}"
51
+
52
+ dictionary = blend_mode_dictionary_registry[dictionary_name] ||= ref!(
53
+ Type: :ExtGState,
54
+ BM: blend_mode,
55
+ )
56
+
57
+ page.ext_gstates[dictionary_name] = dictionary
58
+ dictionary_name
59
+ end
60
+ end
61
+ end
62
+ end