prawn 2.4.0 → 2.5.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 (203) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/lib/prawn/document/bounding_box.rb +213 -141
  4. data/lib/prawn/document/column_box.rb +61 -26
  5. data/lib/prawn/document/internals.rb +25 -16
  6. data/lib/prawn/document/span.rb +20 -18
  7. data/lib/prawn/document.rb +257 -171
  8. data/lib/prawn/encoding.rb +2 -5
  9. data/lib/prawn/errors.rb +23 -34
  10. data/lib/prawn/font.rb +248 -135
  11. data/lib/prawn/font_metric_cache.rb +11 -10
  12. data/lib/prawn/fonts/afm.rb +85 -45
  13. data/lib/prawn/fonts/dfont.rb +7 -1
  14. data/lib/prawn/fonts/otf.rb +4 -1
  15. data/lib/prawn/fonts/to_unicode_cmap.rb +151 -0
  16. data/lib/prawn/fonts/ttc.rb +7 -2
  17. data/lib/prawn/fonts/ttf.rb +305 -93
  18. data/lib/prawn/fonts.rb +14 -0
  19. data/lib/prawn/graphics/blend_mode.rb +25 -28
  20. data/lib/prawn/graphics/cap_style.rb +9 -12
  21. data/lib/prawn/graphics/color.rb +57 -34
  22. data/lib/prawn/graphics/dash.rb +45 -42
  23. data/lib/prawn/graphics/join_style.rb +17 -11
  24. data/lib/prawn/graphics/patterns.rb +190 -69
  25. data/lib/prawn/graphics/transformation.rb +48 -41
  26. data/lib/prawn/graphics/transparency.rb +16 -40
  27. data/lib/prawn/graphics.rb +363 -253
  28. data/lib/prawn/grid.rb +184 -57
  29. data/lib/prawn/image_handler.rb +27 -10
  30. data/lib/prawn/images/image.rb +8 -10
  31. data/lib/prawn/images/jpg.rb +42 -19
  32. data/lib/prawn/images/png.rb +92 -41
  33. data/lib/prawn/images.rb +44 -57
  34. data/lib/prawn/measurement_extensions.rb +39 -8
  35. data/lib/prawn/measurements.rb +60 -5
  36. data/lib/prawn/outline.rb +114 -108
  37. data/lib/prawn/repeater.rb +51 -35
  38. data/lib/prawn/security/arcfour.rb +4 -4
  39. data/lib/prawn/security.rb +75 -70
  40. data/lib/prawn/soft_mask.rb +42 -30
  41. data/lib/prawn/stamp.rb +38 -42
  42. data/lib/prawn/text/box.rb +146 -96
  43. data/lib/prawn/text/formatted/arranger.rb +87 -26
  44. data/lib/prawn/text/formatted/box.rb +221 -150
  45. data/lib/prawn/text/formatted/fragment.rb +130 -14
  46. data/lib/prawn/text/formatted/line_wrap.rb +33 -24
  47. data/lib/prawn/text/formatted/parser.rb +112 -72
  48. data/lib/prawn/text/formatted/wrap.rb +12 -17
  49. data/lib/prawn/text/formatted.rb +75 -0
  50. data/lib/prawn/text.rb +441 -196
  51. data/lib/prawn/transformation_stack.rb +29 -10
  52. data/lib/prawn/utilities.rb +13 -13
  53. data/lib/prawn/version.rb +2 -1
  54. data/lib/prawn/view.rb +68 -53
  55. data/lib/prawn.rb +23 -18
  56. data.tar.gz.sig +0 -0
  57. metadata +54 -177
  58. metadata.gz.sig +0 -0
  59. data/.yardopts +0 -10
  60. data/Gemfile +0 -5
  61. data/Rakefile +0 -25
  62. data/manual/absolute_position.pdf +0 -0
  63. data/manual/basic_concepts/adding_pages.rb +0 -26
  64. data/manual/basic_concepts/basic_concepts.rb +0 -43
  65. data/manual/basic_concepts/creation.rb +0 -38
  66. data/manual/basic_concepts/cursor.rb +0 -32
  67. data/manual/basic_concepts/measurement.rb +0 -24
  68. data/manual/basic_concepts/origin.rb +0 -37
  69. data/manual/basic_concepts/other_cursor_helpers.rb +0 -39
  70. data/manual/basic_concepts/view.rb +0 -48
  71. data/manual/bounding_box/bounding_box.rb +0 -41
  72. data/manual/bounding_box/bounds.rb +0 -48
  73. data/manual/bounding_box/canvas.rb +0 -23
  74. data/manual/bounding_box/creation.rb +0 -22
  75. data/manual/bounding_box/indentation.rb +0 -45
  76. data/manual/bounding_box/nesting.rb +0 -52
  77. data/manual/bounding_box/russian_boxes.rb +0 -40
  78. data/manual/bounding_box/stretchy.rb +0 -29
  79. data/manual/contents.rb +0 -35
  80. data/manual/cover.rb +0 -43
  81. data/manual/document_and_page_options/background.rb +0 -29
  82. data/manual/document_and_page_options/document_and_page_options.rb +0 -34
  83. data/manual/document_and_page_options/metadata.rb +0 -25
  84. data/manual/document_and_page_options/page_margins.rb +0 -36
  85. data/manual/document_and_page_options/page_size.rb +0 -34
  86. data/manual/document_and_page_options/print_scaling.rb +0 -23
  87. data/manual/example_helper.rb +0 -8
  88. data/manual/graphics/blend_mode.rb +0 -52
  89. data/manual/graphics/circle_and_ellipse.rb +0 -21
  90. data/manual/graphics/color.rb +0 -22
  91. data/manual/graphics/common_lines.rb +0 -29
  92. data/manual/graphics/fill_and_stroke.rb +0 -41
  93. data/manual/graphics/fill_rules.rb +0 -38
  94. data/manual/graphics/gradients.rb +0 -43
  95. data/manual/graphics/graphics.rb +0 -64
  96. data/manual/graphics/helper.rb +0 -34
  97. data/manual/graphics/line_width.rb +0 -36
  98. data/manual/graphics/lines_and_curves.rb +0 -40
  99. data/manual/graphics/polygon.rb +0 -27
  100. data/manual/graphics/rectangle.rb +0 -20
  101. data/manual/graphics/rotate.rb +0 -25
  102. data/manual/graphics/scale.rb +0 -42
  103. data/manual/graphics/soft_masks.rb +0 -44
  104. data/manual/graphics/stroke_cap.rb +0 -30
  105. data/manual/graphics/stroke_dash.rb +0 -47
  106. data/manual/graphics/stroke_join.rb +0 -29
  107. data/manual/graphics/translate.rb +0 -29
  108. data/manual/graphics/transparency.rb +0 -33
  109. data/manual/how_to_read_this_manual.rb +0 -39
  110. data/manual/images/absolute_position.rb +0 -22
  111. data/manual/images/fit.rb +0 -20
  112. data/manual/images/horizontal.rb +0 -24
  113. data/manual/images/images.rb +0 -41
  114. data/manual/images/plain_image.rb +0 -17
  115. data/manual/images/scale.rb +0 -21
  116. data/manual/images/vertical.rb +0 -30
  117. data/manual/images/width_and_height.rb +0 -24
  118. data/manual/layout/boxes.rb +0 -26
  119. data/manual/layout/content.rb +0 -24
  120. data/manual/layout/layout.rb +0 -27
  121. data/manual/layout/simple_grid.rb +0 -22
  122. data/manual/outline/add_subsection_to.rb +0 -60
  123. data/manual/outline/insert_section_after.rb +0 -46
  124. data/manual/outline/outline.rb +0 -33
  125. data/manual/outline/sections_and_pages.rb +0 -66
  126. data/manual/repeatable_content/alternate_page_numbering.rb +0 -36
  127. data/manual/repeatable_content/page_numbering.rb +0 -55
  128. data/manual/repeatable_content/repeatable_content.rb +0 -35
  129. data/manual/repeatable_content/repeater.rb +0 -54
  130. data/manual/repeatable_content/stamp.rb +0 -40
  131. data/manual/security/encryption.rb +0 -28
  132. data/manual/security/permissions.rb +0 -43
  133. data/manual/security/security.rb +0 -28
  134. data/manual/table.rb +0 -16
  135. data/manual/text/alignment.rb +0 -43
  136. data/manual/text/color.rb +0 -24
  137. data/manual/text/column_box.rb +0 -30
  138. data/manual/text/fallback_fonts.rb +0 -41
  139. data/manual/text/font.rb +0 -40
  140. data/manual/text/font_size.rb +0 -44
  141. data/manual/text/font_style.rb +0 -25
  142. data/manual/text/formatted_callbacks.rb +0 -70
  143. data/manual/text/formatted_text.rb +0 -61
  144. data/manual/text/free_flowing_text.rb +0 -50
  145. data/manual/text/inline.rb +0 -40
  146. data/manual/text/kerning_and_character_spacing.rb +0 -38
  147. data/manual/text/leading.rb +0 -24
  148. data/manual/text/line_wrapping.rb +0 -60
  149. data/manual/text/paragraph_indentation.rb +0 -31
  150. data/manual/text/positioned_text.rb +0 -37
  151. data/manual/text/registering_families.rb +0 -51
  152. data/manual/text/rendering_and_color.rb +0 -36
  153. data/manual/text/right_to_left_text.rb +0 -54
  154. data/manual/text/rotation.rb +0 -52
  155. data/manual/text/single_usage.rb +0 -36
  156. data/manual/text/text.rb +0 -75
  157. data/manual/text/text_box_excess.rb +0 -35
  158. data/manual/text/text_box_extensions.rb +0 -48
  159. data/manual/text/text_box_overflow.rb +0 -51
  160. data/manual/text/utf8.rb +0 -27
  161. data/manual/text/win_ansi_charset.rb +0 -62
  162. data/prawn.gemspec +0 -51
  163. data/spec/data/curves.pdf +0 -66
  164. data/spec/extensions/encoding_helpers.rb +0 -11
  165. data/spec/prawn/document/bounding_box_spec.rb +0 -550
  166. data/spec/prawn/document/column_box_spec.rb +0 -75
  167. data/spec/prawn/document/security_spec.rb +0 -176
  168. data/spec/prawn/document_annotations_spec.rb +0 -76
  169. data/spec/prawn/document_destinations_spec.rb +0 -15
  170. data/spec/prawn/document_grid_spec.rb +0 -99
  171. data/spec/prawn/document_reference_spec.rb +0 -27
  172. data/spec/prawn/document_span_spec.rb +0 -44
  173. data/spec/prawn/document_spec.rb +0 -805
  174. data/spec/prawn/font_metric_cache_spec.rb +0 -54
  175. data/spec/prawn/font_spec.rb +0 -544
  176. data/spec/prawn/graphics/blend_mode_spec.rb +0 -63
  177. data/spec/prawn/graphics/transparency_spec.rb +0 -81
  178. data/spec/prawn/graphics_spec.rb +0 -872
  179. data/spec/prawn/graphics_stroke_styles_spec.rb +0 -229
  180. data/spec/prawn/image_handler_spec.rb +0 -53
  181. data/spec/prawn/images/jpg_spec.rb +0 -20
  182. data/spec/prawn/images/png_spec.rb +0 -283
  183. data/spec/prawn/images_spec.rb +0 -229
  184. data/spec/prawn/measurements_extensions_spec.rb +0 -24
  185. data/spec/prawn/outline_spec.rb +0 -512
  186. data/spec/prawn/repeater_spec.rb +0 -166
  187. data/spec/prawn/soft_mask_spec.rb +0 -74
  188. data/spec/prawn/stamp_spec.rb +0 -173
  189. data/spec/prawn/text/box_spec.rb +0 -1110
  190. data/spec/prawn/text/formatted/arranger_spec.rb +0 -466
  191. data/spec/prawn/text/formatted/box_spec.rb +0 -849
  192. data/spec/prawn/text/formatted/fragment_spec.rb +0 -343
  193. data/spec/prawn/text/formatted/line_wrap_spec.rb +0 -495
  194. data/spec/prawn/text/formatted/parser_spec.rb +0 -697
  195. data/spec/prawn/text_draw_text_spec.rb +0 -150
  196. data/spec/prawn/text_rendering_mode_spec.rb +0 -48
  197. data/spec/prawn/text_spacing_spec.rb +0 -95
  198. data/spec/prawn/text_spec.rb +0 -603
  199. data/spec/prawn/text_with_inline_formatting_spec.rb +0 -35
  200. data/spec/prawn/transformation_stack_spec.rb +0 -66
  201. data/spec/prawn/view_spec.rb +0 -63
  202. data/spec/prawn_manual_spec.rb +0 -35
  203. data/spec/spec_helper.rb +0 -48
@@ -9,45 +9,138 @@
9
9
 
10
10
  require 'ttfunk'
11
11
  require 'ttfunk/subset_collection'
12
+ require_relative 'to_unicode_cmap'
12
13
 
13
14
  module Prawn
14
15
  module Fonts
15
- # @private
16
+ # TrueType font.
17
+ #
18
+ # @note You shouldn't use this class directly.
16
19
  class TTF < Font
20
+ # TrueType font error.
17
21
  class Error < StandardError
22
+ # @private
18
23
  DEFAULT_MESSAGE = 'TTF font error'
24
+
25
+ # @private
19
26
  MESSAGE_WITH_FONT = 'TTF font error in font %<font>s'
20
27
 
21
28
  def initialize(message = DEFAULT_MESSAGE, font: nil)
22
29
  if font && message == DEFAULT_MESSAGE
23
- super format(MESSAGE_WITH_FONT, font: font)
30
+ super(format(MESSAGE_WITH_FONT, font: font))
24
31
  else
25
- super message
32
+ super(message)
26
33
  end
27
34
  end
28
35
  end
29
36
 
37
+ # Signals absence of a Unicode character map in the font.
30
38
  class NoUnicodeCMap < Error
39
+ # @private
31
40
  DEFAULT_MESSAGE = 'No unicode cmap found in font'
41
+
42
+ # @private
32
43
  MESSAGE_WITH_FONT = 'No unicode cmap found in font %<font>s'
33
44
  end
34
45
 
46
+ # Signals absense of a PostScript font name.
35
47
  class NoPostscriptName < Error
48
+ # @private
36
49
  DEFAULT_MESSAGE = 'Can not detect a postscript name'
50
+
51
+ # @private
37
52
  MESSAGE_WITH_FONT = 'Can not detect a postscript name in font %<font>s'
38
53
  end
39
54
 
40
- attr_reader :ttf, :subsets
55
+ # TTFunk font.
56
+ # @return [TTFunk::File]
57
+ attr_reader :ttf
58
+ attr_reader :subsets
41
59
 
60
+ # Does this font support Unicode?
61
+ #
62
+ # @return [true]
42
63
  def unicode?
43
64
  true
44
65
  end
45
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]
46
134
  def initialize(document, name, options = {})
47
135
  super
48
136
 
49
137
  @ttf = read_ttf_file
50
- @subsets = TTFunk::SubsetCollection.new(@ttf)
138
+ @subsets =
139
+ if full_font_embedding
140
+ FullFontSubsetsCollection.new(@ttf)
141
+ else
142
+ TTFunk::SubsetCollection.new(@ttf)
143
+ end
51
144
  @italic_angle = nil
52
145
 
53
146
  @attributes = {}
@@ -60,46 +153,54 @@ module Prawn
60
153
  @line_gap = Integer(@ttf.line_gap * scale_factor)
61
154
  end
62
155
 
63
- # NOTE: +string+ must be UTF8-encoded.
64
- def compute_width_of(string, options = {}) #:nodoc:
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 = {})
65
165
  scale = (options[:size] || size) / 1000.0
66
166
  if options[:kerning]
67
- kern(string).reduce(0) do |s, r|
167
+ kern(string).reduce(0) { |s, r|
68
168
  if r.is_a?(Numeric)
69
169
  s - r
70
170
  else
71
171
  r.reduce(s) { |a, e| a + character_width_by_code(e) }
72
172
  end
73
- end * scale
173
+ } * scale
74
174
  else
75
- string.codepoints.reduce(0) do |s, r|
175
+ string.codepoints.reduce(0) { |s, r|
76
176
  s + character_width_by_code(r)
77
- end * scale
177
+ } * scale
78
178
  end
79
179
  end
80
180
 
81
- # The font bbox, as an array of integers
181
+ # The font bbox.
82
182
  #
183
+ # @return [Array(Number, Number, Number, Number)]
83
184
  def bbox
84
185
  @bbox ||= @ttf.bbox.map { |i| Integer(i * scale_factor) }
85
186
  end
86
187
 
87
- # Returns true if the font has kerning data, false otherwise
188
+ # Does this font contain kerning data.
88
189
  #
89
- # rubocop: disable Naming/PredicateName
90
- def has_kerning_data?
190
+ # @return [Boolean]
191
+ def has_kerning_data? # rubocop: disable Naming/PredicateName
91
192
  @has_kerning_data
92
193
  end
93
- # rubocop: enable Naming/PredicateName
94
194
 
95
- # Perform any changes to the string that need to happen
96
- # before it is rendered to the canvas. Returns an array of
97
- # subset "chunks", where the even-numbered indices are the
98
- # font subset number, and the following entry element is
99
- # either a string or an array (for kerned text).
100
- #
101
- # The +text+ parameter must be UTF8-encoded.
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).
102
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)>]
103
204
  def encode_text(text, options = {})
104
205
  text = text.chomp
105
206
 
@@ -133,15 +234,23 @@ module Prawn
133
234
  end
134
235
  end
135
236
 
237
+ # Base name of the font.
238
+ #
239
+ # @return [String]
136
240
  def basename
137
241
  @basename ||= @ttf.name.postscript_name
138
242
  end
139
243
 
140
- # not sure how to compute this for true-type fonts...
244
+ # @devnote not sure how to compute this for true-type fonts...
245
+ #
246
+ # @private
247
+ # @return [Number]
141
248
  def stem_v
142
249
  0
143
250
  end
144
251
 
252
+ # @private
253
+ # @return [Number]
145
254
  def italic_angle
146
255
  return @italic_angle if @italic_angle
147
256
 
@@ -150,7 +259,7 @@ module Prawn
150
259
  hi = raw >> 16
151
260
  low = raw & 0xFF
152
261
  hi = -((hi ^ 0xFFFF) + 1) if hi & 0x8000 != 0
153
- @italic_angle = "#{hi}.#{low}".to_f
262
+ @italic_angle = Float("#{hi}.#{low}")
154
263
  else
155
264
  @italic_angle = 0
156
265
  end
@@ -158,32 +267,44 @@ module Prawn
158
267
  @italic_angle
159
268
  end
160
269
 
270
+ # @private
271
+ # @return [Number]
161
272
  def cap_height
162
273
  @cap_height ||=
163
274
  begin
164
- height = @ttf.os2.exists? && @ttf.os2.cap_height || 0
275
+ height = (@ttf.os2.exists? && @ttf.os2.cap_height) || 0
165
276
  height.zero? ? @ascender : height
166
277
  end
167
278
  end
168
279
 
280
+ # @private
281
+ # @return [number]
169
282
  def x_height
170
283
  # FIXME: seems like if os2 table doesn't exist, we could
171
284
  # just find the height of the lower-case 'x' glyph?
172
- @ttf.os2.exists? && @ttf.os2.x_height || 0
285
+ (@ttf.os2.exists? && @ttf.os2.x_height) || 0
173
286
  end
174
287
 
288
+ # @private
289
+ # @return [Number]
175
290
  def family_class
176
- @family_class ||= (@ttf.os2.exists? && @ttf.os2.family_class || 0) >> 8
291
+ @family_class ||= ((@ttf.os2.exists? && @ttf.os2.family_class) || 0) >> 8
177
292
  end
178
293
 
294
+ # @private
295
+ # @return [Boolean]
179
296
  def serif?
180
297
  @serif ||= [1, 2, 3, 4, 5, 7].include?(family_class)
181
298
  end
182
299
 
300
+ # @private
301
+ # @return [Boolean]
183
302
  def script?
184
303
  @script ||= family_class == 10
185
304
  end
186
305
 
306
+ # @private
307
+ # @return [Integer]
187
308
  def pdf_flags
188
309
  @pdf_flags ||=
189
310
  begin
@@ -197,27 +318,39 @@ module Prawn
197
318
  end
198
319
  end
199
320
 
321
+ # Normlize text to a compatible encoding.
322
+ #
323
+ # @param text [String]
324
+ # @return [String]
200
325
  def normalize_encoding(text)
201
326
  text.encode(::Encoding::UTF_8)
202
- rescue StandardError => e
203
- puts e
327
+ rescue StandardError
204
328
  raise Prawn::Errors::IncompatibleStringEncoding,
205
329
  "Encoding #{text.encoding} can not be transparently converted to UTF-8. " \
206
- 'Please ensure the encoding of the string you are attempting ' \
207
- 'to use is set correctly'
330
+ 'Please ensure the encoding of the string you are attempting to use is set correctly'
208
331
  end
209
332
 
333
+ # Encode text to UTF-8.
334
+ #
335
+ # @param text [String]
336
+ # @return [String]
210
337
  def to_utf8(text)
211
338
  text.encode('UTF-8')
212
339
  end
213
340
 
341
+ # Does this font has a glyph for the character?
342
+ #
343
+ # @param char [String]
344
+ # @return [Boolean]
214
345
  def glyph_present?(char)
215
346
  code = char.codepoints.first
216
347
  cmap[code].positive?
217
348
  end
218
349
 
219
- # Returns the number of characters in +str+ (a UTF-8-encoded string).
350
+ # Returns the number of characters in `str` (a UTF-8-encoded string).
220
351
  #
352
+ # @param str [String]
353
+ # @return [Integer]
221
354
  def character_count(str)
222
355
  str.length
223
356
  end
@@ -289,12 +422,26 @@ module Prawn
289
422
  end
290
423
 
291
424
  def embed(reference, subset)
292
- font_content = @subsets[subset].encode
425
+ if full_font_embedding
426
+ embed_full_font(reference)
427
+ else
428
+ embed_subset(reference, subset)
429
+ end
430
+ end
293
431
 
294
- # FIXME: we need postscript_name and glyph widths from the font
295
- # subset. Perhaps this could be done by querying the subset,
296
- # rather than by parsing the font that the subset produces?
297
- font = TTFunk::File.new(font_content)
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
298
445
 
299
446
  # empirically, it looks like Adobe Reader will not display fonts
300
447
  # if their font name is more than 33 bytes long. Strange. But true.
@@ -302,14 +449,14 @@ module Prawn
302
449
 
303
450
  raise NoPostscriptName.new(font: font) if basename.nil?
304
451
 
305
- fontfile = @document.ref!(Length1: font_content.size)
306
- fontfile.stream << font_content
307
- fontfile.stream.compress!
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?
308
456
 
309
457
  descriptor = @document.ref!(
310
458
  Type: :FontDescriptor,
311
459
  FontName: basename.to_sym,
312
- FontFile2: fontfile,
313
460
  FontBBox: bbox,
314
461
  Flags: pdf_flags,
315
462
  StemV: stem_v,
@@ -317,13 +464,23 @@ module Prawn
317
464
  Ascent: @ascender,
318
465
  Descent: @descender,
319
466
  CapHeight: cap_height,
320
- XHeight: x_height
467
+ XHeight: x_height,
321
468
  )
322
469
 
470
+ first_char, last_char = unicode_mapping.keys.minmax
323
471
  hmtx = font.horizontal_metrics
324
- widths = font.cmap.tables.first.code_map.map do |gid|
325
- Integer(hmtx.widths[gid] * scale_factor)
326
- end[32..-1]
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
+ }
327
484
 
328
485
  # It would be nice to have Encoding set for the macroman subsets,
329
486
  # and only do a ToUnicode cmap for non-encoded unicode subsets.
@@ -335,65 +492,120 @@ module Prawn
335
492
  # For now, it's simplest to just create a unicode cmap for every font.
336
493
  # It offends my inner purist, but it'll do.
337
494
 
338
- map = @subsets[subset].to_unicode_map
495
+ to_unicode = @document.ref!({})
496
+ to_unicode << ToUnicodeCMap.new(unicode_mapping).generate
497
+ to_unicode.stream.compress! if @document.compression_enabled?
339
498
 
340
- ranges = [[]]
341
- map.keys.sort.reduce('') do |_s, code|
342
- ranges << [] if ranges.last.length >= 100
343
- unicode = map[code]
344
- ranges.last << format(
345
- '<%<code>02x><%<unicode>04x>',
346
- code: code,
347
- unicode: unicode
348
- )
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)
349
516
  end
517
+ end
350
518
 
351
- range_blocks =
352
- ranges.reduce(+'') do |s, list|
353
- s << format(
354
- "%<lenght>d beginbfchar\n%<list>s\nendbfchar\n",
355
- lenght: list.length,
356
- list: list.join("\n")
357
- )
358
- end
519
+ def embed_full_font(reference)
520
+ embed_composite_font(reference, @ttf)
521
+ end
359
522
 
360
- to_unicode_cmap = UNICODE_CMAP_TEMPLATE % range_blocks.strip
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
361
527
 
362
- cmap = @document.ref!({})
363
- cmap << to_unicode_cmap
364
- cmap.stream.compress!
528
+ true_type = font_type(font) == :true_type
529
+ open_type = font_type(font) == :open_type
365
530
 
366
- reference.data.update(
367
- Subtype: :TrueType,
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,
368
572
  BaseFont: basename.to_sym,
573
+ CIDSystemInfo: {
574
+ Registry: 'Adobe',
575
+ Ordering: 'Identity',
576
+ Supplement: 0,
577
+ },
369
578
  FontDescriptor: descriptor,
370
- FirstChar: 32,
371
- LastChar: 255,
372
- Widths: @document.ref!(widths),
373
- ToUnicode: cmap
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,
374
597
  )
375
598
  end
376
599
 
377
- UNICODE_CMAP_TEMPLATE = <<-STR.strip.gsub(/^\s*/, '')
378
- /CIDInit /ProcSet findresource begin
379
- 12 dict begin
380
- begincmap
381
- /CIDSystemInfo <<
382
- /Registry (Adobe)
383
- /Ordering (UCS)
384
- /Supplement 0
385
- >> def
386
- /CMapName /Adobe-Identity-UCS def
387
- /CMapType 2 def
388
- 1 begincodespacerange
389
- <00><ff>
390
- endcodespacerange
391
- %s
392
- endcmap
393
- CMapName currentdict /CMap defineresource pop
394
- end
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
395
607
  end
396
- STR
608
+ end
397
609
 
398
610
  def read_ttf_file
399
611
  TTFunk::File.open(@name)
@@ -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'
@@ -1,41 +1,38 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # blend_mode.rb : Implements blend modes
4
- #
5
- # Contributed by John Ford. October, 2015
6
- #
7
- # This is free software. Please see the LICENSE and COPYING files for details.
8
- #
9
-
10
3
  module Prawn
11
4
  module Graphics
12
- # The Prawn::BlendMode module is used to change the way
13
- # two layers are blended together.
14
- #
15
- # Passing an array of blend modes is allowed. PDF viewers should
16
- # blend layers based on the first recognized blend mode.
17
- #
18
- # Valid blend modes in v1.4 of the PDF spec include :Normal, :Multiply,
19
- # :Screen, :Overlay, :Darken, :Lighten, :ColorDodge, :ColorBurn, :HardLight,
20
- # :SoftLight, :Difference, :Exclusion, :Hue, :Saturation, :Color, and
21
- # :Luminosity.
22
- #
23
- # Example:
24
- # pdf.fill_color('0000ff')
25
- # pdf.fill_rectangle([x, y+25], 50, 50)
26
- # pdf.blend_mode(:Multiply) do
27
- # pdf.fill_color('ff0000')
28
- # pdf.fill_circle([x, y], 25)
29
- # end
30
- #
5
+ # The {Prawn::BlendMode} module is used to change the way two graphic
6
+ # objects are blended together.
31
7
  module BlendMode
32
8
  # @group Stable API
33
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]
34
31
  def blend_mode(blend_mode = :Normal)
35
32
  renderer.min_version(1.4)
36
33
 
37
34
  save_graphics_state if block_given?
38
- renderer.add_content "/#{blend_mode_dictionary_name(blend_mode)} gs"
35
+ renderer.add_content("/#{blend_mode_dictionary_name(blend_mode)} gs")
39
36
  if block_given?
40
37
  yield
41
38
  restore_graphics_state
@@ -54,7 +51,7 @@ module Prawn
54
51
 
55
52
  dictionary = blend_mode_dictionary_registry[dictionary_name] ||= ref!(
56
53
  Type: :ExtGState,
57
- BM: blend_mode
54
+ BM: blend_mode,
58
55
  )
59
56
 
60
57
  page.ext_gstates[dictionary_name] = dictionary