prawn 2.4.0 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
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