prawn 2.3.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 +223 -143
  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 +21 -18
  7. data/lib/prawn/document.rb +273 -182
  8. data/lib/prawn/encoding.rb +2 -5
  9. data/lib/prawn/errors.rb +23 -34
  10. data/lib/prawn/font.rb +254 -139
  11. data/lib/prawn/font_metric_cache.rb +18 -16
  12. data/lib/prawn/fonts/afm.rb +99 -57
  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 +345 -107
  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 +75 -50
  22. data/lib/prawn/graphics/dash.rb +45 -42
  23. data/lib/prawn/graphics/join_style.rb +18 -12
  24. data/lib/prawn/graphics/patterns.rb +239 -110
  25. data/lib/prawn/graphics/transformation.rb +51 -44
  26. data/lib/prawn/graphics/transparency.rb +16 -40
  27. data/lib/prawn/graphics.rb +370 -260
  28. data/lib/prawn/grid.rb +219 -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 +46 -20
  32. data/lib/prawn/images/png.rb +94 -42
  33. data/lib/prawn/images.rb +70 -81
  34. data/lib/prawn/measurement_extensions.rb +39 -8
  35. data/lib/prawn/measurements.rb +60 -5
  36. data/lib/prawn/outline.rb +120 -113
  37. data/lib/prawn/repeater.rb +52 -36
  38. data/lib/prawn/security/arcfour.rb +4 -4
  39. data/lib/prawn/security.rb +106 -98
  40. data/lib/prawn/soft_mask.rb +42 -30
  41. data/lib/prawn/stamp.rb +38 -42
  42. data/lib/prawn/text/box.rb +156 -105
  43. data/lib/prawn/text/formatted/arranger.rb +121 -41
  44. data/lib/prawn/text/formatted/box.rb +239 -163
  45. data/lib/prawn/text/formatted/fragment.rb +130 -14
  46. data/lib/prawn/text/formatted/line_wrap.rb +49 -38
  47. data/lib/prawn/text/formatted/parser.rb +116 -74
  48. data/lib/prawn/text/formatted/wrap.rb +25 -26
  49. data/lib/prawn/text/formatted.rb +75 -0
  50. data/lib/prawn/text.rb +456 -211
  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 +69 -54
  55. data/lib/prawn.rb +24 -18
  56. data.tar.gz.sig +0 -0
  57. metadata +55 -262
  58. metadata.gz.sig +3 -4
  59. data/.yardopts +0 -10
  60. data/Gemfile +0 -5
  61. data/Rakefile +0 -54
  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 -25
  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 -22
  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 -37
  94. data/manual/graphics/gradients.rb +0 -43
  95. data/manual/graphics/graphics.rb +0 -64
  96. data/manual/graphics/helper.rb +0 -27
  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 -28
  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 -27
  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 -41
  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 -22
  142. data/manual/text/formatted_callbacks.rb +0 -65
  143. data/manual/text/formatted_text.rb +0 -58
  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 -32
  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 -47
  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 -49
  160. data/manual/text/utf8.rb +0 -27
  161. data/manual/text/win_ansi_charset.rb +0 -62
  162. data/prawn.gemspec +0 -57
  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 -546
  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 -36
  173. data/spec/prawn/document_spec.rb +0 -802
  174. data/spec/prawn/font_metric_cache_spec.rb +0 -54
  175. data/spec/prawn/font_spec.rb +0 -542
  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 -837
  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 -224
  184. data/spec/prawn/measurements_extensions_spec.rb +0 -24
  185. data/spec/prawn/outline_spec.rb +0 -412
  186. data/spec/prawn/repeater_spec.rb +0 -165
  187. data/spec/prawn/soft_mask_spec.rb +0 -74
  188. data/spec/prawn/stamp_spec.rb +0 -172
  189. data/spec/prawn/text/box_spec.rb +0 -1112
  190. data/spec/prawn/text/formatted/arranger_spec.rb +0 -466
  191. data/spec/prawn/text/formatted/box_spec.rb +0 -846
  192. data/spec/prawn/text/formatted/fragment_spec.rb +0 -343
  193. data/spec/prawn/text/formatted/line_wrap_spec.rb +0 -494
  194. data/spec/prawn/text/formatted/parser_spec.rb +0 -697
  195. data/spec/prawn/text_draw_text_spec.rb +0 -149
  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,22 +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
17
- attr_reader :ttf, :subsets
20
+ # TrueType font error.
21
+ class Error < StandardError
22
+ # @private
23
+ DEFAULT_MESSAGE = 'TTF font error'
18
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]
19
63
  def unicode?
20
64
  true
21
65
  end
22
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]
23
134
  def initialize(document, name, options = {})
24
135
  super
25
136
 
26
137
  @ttf = read_ttf_file
27
- @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
28
144
  @italic_angle = nil
29
145
 
30
146
  @attributes = {}
@@ -37,52 +153,60 @@ module Prawn
37
153
  @line_gap = Integer(@ttf.line_gap * scale_factor)
38
154
  end
39
155
 
40
- # NOTE: +string+ must be UTF8-encoded.
41
- 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 = {})
42
165
  scale = (options[:size] || size) / 1000.0
43
166
  if options[:kerning]
44
- kern(string).inject(0) do |s, r|
167
+ kern(string).reduce(0) { |s, r|
45
168
  if r.is_a?(Numeric)
46
169
  s - r
47
170
  else
48
- r.inject(s) { |a, e| a + character_width_by_code(e) }
171
+ r.reduce(s) { |a, e| a + character_width_by_code(e) }
49
172
  end
50
- end * scale
173
+ } * scale
51
174
  else
52
- string.codepoints.inject(0) do |s, r|
175
+ string.codepoints.reduce(0) { |s, r|
53
176
  s + character_width_by_code(r)
54
- end * scale
177
+ } * scale
55
178
  end
56
179
  end
57
180
 
58
- # The font bbox, as an array of integers
181
+ # The font bbox.
59
182
  #
183
+ # @return [Array(Number, Number, Number, Number)]
60
184
  def bbox
61
185
  @bbox ||= @ttf.bbox.map { |i| Integer(i * scale_factor) }
62
186
  end
63
187
 
64
- # Returns true if the font has kerning data, false otherwise
188
+ # Does this font contain kerning data.
65
189
  #
66
- # rubocop: disable Naming/PredicateName
67
- def has_kerning_data?
190
+ # @return [Boolean]
191
+ def has_kerning_data? # rubocop: disable Naming/PredicateName
68
192
  @has_kerning_data
69
193
  end
70
- # rubocop: enable Naming/PredicateName
71
194
 
72
- # Perform any changes to the string that need to happen
73
- # before it is rendered to the canvas. Returns an array of
74
- # subset "chunks", where the even-numbered indices are the
75
- # font subset number, and the following entry element is
76
- # either a string or an array (for kerned text).
77
- #
78
- # 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).
79
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)>]
80
204
  def encode_text(text, options = {})
81
205
  text = text.chomp
82
206
 
83
207
  if options[:kerning]
84
208
  last_subset = nil
85
- kern(text).inject([]) do |result, element|
209
+ kern(text).reduce([]) do |result, element|
86
210
  if element.is_a?(Numeric)
87
211
  unless result.last[1].is_a?(Array)
88
212
  result.last[1] = [result.last[1]]
@@ -110,15 +234,23 @@ module Prawn
110
234
  end
111
235
  end
112
236
 
237
+ # Base name of the font.
238
+ #
239
+ # @return [String]
113
240
  def basename
114
241
  @basename ||= @ttf.name.postscript_name
115
242
  end
116
243
 
117
- # 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]
118
248
  def stem_v
119
249
  0
120
250
  end
121
251
 
252
+ # @private
253
+ # @return [Number]
122
254
  def italic_angle
123
255
  return @italic_angle if @italic_angle
124
256
 
@@ -127,7 +259,7 @@ module Prawn
127
259
  hi = raw >> 16
128
260
  low = raw & 0xFF
129
261
  hi = -((hi ^ 0xFFFF) + 1) if hi & 0x8000 != 0
130
- @italic_angle = "#{hi}.#{low}".to_f
262
+ @italic_angle = Float("#{hi}.#{low}")
131
263
  else
132
264
  @italic_angle = 0
133
265
  end
@@ -135,64 +267,90 @@ module Prawn
135
267
  @italic_angle
136
268
  end
137
269
 
270
+ # @private
271
+ # @return [Number]
138
272
  def cap_height
139
- @cap_height ||= begin
140
- height = @ttf.os2.exists? && @ttf.os2.cap_height || 0
141
- height.zero? ? @ascender : height
142
- end
273
+ @cap_height ||=
274
+ begin
275
+ height = (@ttf.os2.exists? && @ttf.os2.cap_height) || 0
276
+ height.zero? ? @ascender : height
277
+ end
143
278
  end
144
279
 
280
+ # @private
281
+ # @return [number]
145
282
  def x_height
146
283
  # FIXME: seems like if os2 table doesn't exist, we could
147
284
  # just find the height of the lower-case 'x' glyph?
148
- @ttf.os2.exists? && @ttf.os2.x_height || 0
285
+ (@ttf.os2.exists? && @ttf.os2.x_height) || 0
149
286
  end
150
287
 
288
+ # @private
289
+ # @return [Number]
151
290
  def family_class
152
- @family_class ||= (@ttf.os2.exists? && @ttf.os2.family_class || 0) >> 8
291
+ @family_class ||= ((@ttf.os2.exists? && @ttf.os2.family_class) || 0) >> 8
153
292
  end
154
293
 
294
+ # @private
295
+ # @return [Boolean]
155
296
  def serif?
156
297
  @serif ||= [1, 2, 3, 4, 5, 7].include?(family_class)
157
298
  end
158
299
 
300
+ # @private
301
+ # @return [Boolean]
159
302
  def script?
160
303
  @script ||= family_class == 10
161
304
  end
162
305
 
306
+ # @private
307
+ # @return [Integer]
163
308
  def pdf_flags
164
- @pdf_flags ||= begin
165
- flags = 0
166
- flags |= 0x0001 if @ttf.postscript.fixed_pitch?
167
- flags |= 0x0002 if serif?
168
- flags |= 0x0008 if script?
169
- flags |= 0x0040 if italic_angle != 0
170
- # Assume the font contains at least some non-latin characters
171
- flags | 0x0004
172
- end
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
173
319
  end
174
320
 
321
+ # Normlize text to a compatible encoding.
322
+ #
323
+ # @param text [String]
324
+ # @return [String]
175
325
  def normalize_encoding(text)
176
326
  text.encode(::Encoding::UTF_8)
177
- rescue StandardError => e
178
- puts e
179
- raise Prawn::Errors::IncompatibleStringEncoding, 'Encoding ' \
180
- "#{text.encoding} can not be transparently converted to UTF-8. " \
181
- 'Please ensure the encoding of the string you are attempting ' \
182
- 'to use is set correctly'
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'
183
331
  end
184
332
 
333
+ # Encode text to UTF-8.
334
+ #
335
+ # @param text [String]
336
+ # @return [String]
185
337
  def to_utf8(text)
186
338
  text.encode('UTF-8')
187
339
  end
188
340
 
341
+ # Does this font has a glyph for the character?
342
+ #
343
+ # @param char [String]
344
+ # @return [Boolean]
189
345
  def glyph_present?(char)
190
346
  code = char.codepoints.first
191
347
  cmap[code].positive?
192
348
  end
193
349
 
194
- # 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).
195
351
  #
352
+ # @param str [String]
353
+ # @return [Integer]
196
354
  def character_count(str)
197
355
  str.length
198
356
  end
@@ -200,7 +358,7 @@ module Prawn
200
358
  private
201
359
 
202
360
  def cmap
203
- (@cmap ||= @ttf.cmap.unicode.first) || raise('no unicode cmap for font')
361
+ (@cmap ||= @ttf.cmap.unicode.first) || raise(NoUnicodeCMap.new(font: name))
204
362
  end
205
363
 
206
364
  # +string+ must be UTF8-encoded.
@@ -264,27 +422,41 @@ module Prawn
264
422
  end
265
423
 
266
424
  def embed(reference, subset)
267
- 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
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
268
442
 
269
- # FIXME: we need postscript_name and glyph widths from the font
270
- # subset. Perhaps this could be done by querying the subset,
271
- # rather than by parsing the font that the subset produces?
272
- font = TTFunk::File.new(font_content)
443
+ true_type = font_type(font) == :true_type
444
+ open_type = font_type(font) == :open_type
273
445
 
274
446
  # empirically, it looks like Adobe Reader will not display fonts
275
447
  # if their font name is more than 33 bytes long. Strange. But true.
276
448
  basename = font.name.postscript_name[0, 33].delete("\0")
277
449
 
278
- raise "Can't detect a postscript name for #{file}" if basename.nil?
450
+ raise NoPostscriptName.new(font: font) if basename.nil?
279
451
 
280
- fontfile = @document.ref!(Length1: font_content.size)
281
- fontfile.stream << font_content
282
- 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?
283
456
 
284
457
  descriptor = @document.ref!(
285
458
  Type: :FontDescriptor,
286
459
  FontName: basename.to_sym,
287
- FontFile2: fontfile,
288
460
  FontBBox: bbox,
289
461
  Flags: pdf_flags,
290
462
  StemV: stem_v,
@@ -292,13 +464,23 @@ module Prawn
292
464
  Ascent: @ascender,
293
465
  Descent: @descender,
294
466
  CapHeight: cap_height,
295
- XHeight: x_height
467
+ XHeight: x_height,
296
468
  )
297
469
 
470
+ first_char, last_char = unicode_mapping.keys.minmax
298
471
  hmtx = font.horizontal_metrics
299
- widths = font.cmap.tables.first.code_map.map do |gid|
300
- Integer(hmtx.widths[gid] * scale_factor)
301
- 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
+ }
302
484
 
303
485
  # It would be nice to have Encoding set for the macroman subsets,
304
486
  # and only do a ToUnicode cmap for non-encoded unicode subsets.
@@ -310,64 +492,120 @@ module Prawn
310
492
  # For now, it's simplest to just create a unicode cmap for every font.
311
493
  # It offends my inner purist, but it'll do.
312
494
 
313
- 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?
314
498
 
315
- ranges = [[]]
316
- map.keys.sort.inject('') do |_s, code|
317
- ranges << [] if ranges.last.length >= 100
318
- unicode = map[code]
319
- ranges.last << format(
320
- '<%<code>02x><%<unicode>04x>',
321
- code: code,
322
- unicode: unicode
323
- )
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)
324
516
  end
517
+ end
325
518
 
326
- range_blocks = ranges.inject(+'') do |s, list|
327
- s << format(
328
- "%<lenght>d beginbfchar\n%<list>s\nendbfchar\n",
329
- lenght: list.length,
330
- list: list.join("\n")
331
- )
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}.")
332
526
  end
333
527
 
334
- to_unicode_cmap = UNICODE_CMAP_TEMPLATE % range_blocks.strip
528
+ true_type = font_type(font) == :true_type
529
+ open_type = font_type(font) == :open_type
335
530
 
336
- cmap = @document.ref!({})
337
- cmap << to_unicode_cmap
338
- cmap.stream.compress!
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?
339
536
 
340
- reference.data.update(
341
- Subtype: :TrueType,
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,
342
572
  BaseFont: basename.to_sym,
573
+ CIDSystemInfo: {
574
+ Registry: 'Adobe',
575
+ Ordering: 'Identity',
576
+ Supplement: 0,
577
+ },
343
578
  FontDescriptor: descriptor,
344
- FirstChar: 32,
345
- LastChar: 255,
346
- Widths: @document.ref!(widths),
347
- 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,
348
597
  )
349
598
  end
350
599
 
351
- UNICODE_CMAP_TEMPLATE = <<-STR.strip.gsub(/^\s*/, '')
352
- /CIDInit /ProcSet findresource begin
353
- 12 dict begin
354
- begincmap
355
- /CIDSystemInfo <<
356
- /Registry (Adobe)
357
- /Ordering (UCS)
358
- /Supplement 0
359
- >> def
360
- /CMapName /Adobe-Identity-UCS def
361
- /CMapType 2 def
362
- 1 begincodespacerange
363
- <00><ff>
364
- endcodespacerange
365
- %s
366
- endcmap
367
- CMapName currentdict /CMap defineresource pop
368
- 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
369
607
  end
370
- STR
608
+ end
371
609
 
372
610
  def read_ttf_file
373
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'