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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/lib/prawn/document/bounding_box.rb +213 -141
- data/lib/prawn/document/column_box.rb +61 -26
- data/lib/prawn/document/internals.rb +25 -16
- data/lib/prawn/document/span.rb +20 -18
- data/lib/prawn/document.rb +257 -171
- data/lib/prawn/encoding.rb +2 -5
- data/lib/prawn/errors.rb +23 -34
- data/lib/prawn/font.rb +248 -135
- data/lib/prawn/font_metric_cache.rb +11 -10
- data/lib/prawn/fonts/afm.rb +85 -45
- data/lib/prawn/fonts/dfont.rb +7 -1
- data/lib/prawn/fonts/otf.rb +4 -1
- data/lib/prawn/fonts/to_unicode_cmap.rb +151 -0
- data/lib/prawn/fonts/ttc.rb +7 -2
- data/lib/prawn/fonts/ttf.rb +305 -93
- data/lib/prawn/fonts.rb +14 -0
- data/lib/prawn/graphics/blend_mode.rb +25 -28
- data/lib/prawn/graphics/cap_style.rb +9 -12
- data/lib/prawn/graphics/color.rb +57 -34
- data/lib/prawn/graphics/dash.rb +45 -42
- data/lib/prawn/graphics/join_style.rb +17 -11
- data/lib/prawn/graphics/patterns.rb +190 -69
- data/lib/prawn/graphics/transformation.rb +48 -41
- data/lib/prawn/graphics/transparency.rb +16 -40
- data/lib/prawn/graphics.rb +363 -253
- data/lib/prawn/grid.rb +184 -57
- data/lib/prawn/image_handler.rb +27 -10
- data/lib/prawn/images/image.rb +8 -10
- data/lib/prawn/images/jpg.rb +42 -19
- data/lib/prawn/images/png.rb +92 -41
- data/lib/prawn/images.rb +44 -57
- data/lib/prawn/measurement_extensions.rb +39 -8
- data/lib/prawn/measurements.rb +60 -5
- data/lib/prawn/outline.rb +114 -108
- data/lib/prawn/repeater.rb +51 -35
- data/lib/prawn/security/arcfour.rb +4 -4
- data/lib/prawn/security.rb +75 -70
- data/lib/prawn/soft_mask.rb +42 -30
- data/lib/prawn/stamp.rb +38 -42
- data/lib/prawn/text/box.rb +146 -96
- data/lib/prawn/text/formatted/arranger.rb +87 -26
- data/lib/prawn/text/formatted/box.rb +221 -150
- data/lib/prawn/text/formatted/fragment.rb +130 -14
- data/lib/prawn/text/formatted/line_wrap.rb +33 -24
- data/lib/prawn/text/formatted/parser.rb +112 -72
- data/lib/prawn/text/formatted/wrap.rb +12 -17
- data/lib/prawn/text/formatted.rb +75 -0
- data/lib/prawn/text.rb +441 -196
- data/lib/prawn/transformation_stack.rb +29 -10
- data/lib/prawn/utilities.rb +13 -13
- data/lib/prawn/version.rb +2 -1
- data/lib/prawn/view.rb +68 -53
- data/lib/prawn.rb +23 -18
- data.tar.gz.sig +0 -0
- metadata +54 -177
- metadata.gz.sig +0 -0
- data/.yardopts +0 -10
- data/Gemfile +0 -5
- data/Rakefile +0 -25
- data/manual/absolute_position.pdf +0 -0
- data/manual/basic_concepts/adding_pages.rb +0 -26
- data/manual/basic_concepts/basic_concepts.rb +0 -43
- data/manual/basic_concepts/creation.rb +0 -38
- data/manual/basic_concepts/cursor.rb +0 -32
- data/manual/basic_concepts/measurement.rb +0 -24
- data/manual/basic_concepts/origin.rb +0 -37
- data/manual/basic_concepts/other_cursor_helpers.rb +0 -39
- data/manual/basic_concepts/view.rb +0 -48
- data/manual/bounding_box/bounding_box.rb +0 -41
- data/manual/bounding_box/bounds.rb +0 -48
- data/manual/bounding_box/canvas.rb +0 -23
- data/manual/bounding_box/creation.rb +0 -22
- data/manual/bounding_box/indentation.rb +0 -45
- data/manual/bounding_box/nesting.rb +0 -52
- data/manual/bounding_box/russian_boxes.rb +0 -40
- data/manual/bounding_box/stretchy.rb +0 -29
- data/manual/contents.rb +0 -35
- data/manual/cover.rb +0 -43
- data/manual/document_and_page_options/background.rb +0 -29
- data/manual/document_and_page_options/document_and_page_options.rb +0 -34
- data/manual/document_and_page_options/metadata.rb +0 -25
- data/manual/document_and_page_options/page_margins.rb +0 -36
- data/manual/document_and_page_options/page_size.rb +0 -34
- data/manual/document_and_page_options/print_scaling.rb +0 -23
- data/manual/example_helper.rb +0 -8
- data/manual/graphics/blend_mode.rb +0 -52
- data/manual/graphics/circle_and_ellipse.rb +0 -21
- data/manual/graphics/color.rb +0 -22
- data/manual/graphics/common_lines.rb +0 -29
- data/manual/graphics/fill_and_stroke.rb +0 -41
- data/manual/graphics/fill_rules.rb +0 -38
- data/manual/graphics/gradients.rb +0 -43
- data/manual/graphics/graphics.rb +0 -64
- data/manual/graphics/helper.rb +0 -34
- data/manual/graphics/line_width.rb +0 -36
- data/manual/graphics/lines_and_curves.rb +0 -40
- data/manual/graphics/polygon.rb +0 -27
- data/manual/graphics/rectangle.rb +0 -20
- data/manual/graphics/rotate.rb +0 -25
- data/manual/graphics/scale.rb +0 -42
- data/manual/graphics/soft_masks.rb +0 -44
- data/manual/graphics/stroke_cap.rb +0 -30
- data/manual/graphics/stroke_dash.rb +0 -47
- data/manual/graphics/stroke_join.rb +0 -29
- data/manual/graphics/translate.rb +0 -29
- data/manual/graphics/transparency.rb +0 -33
- data/manual/how_to_read_this_manual.rb +0 -39
- data/manual/images/absolute_position.rb +0 -22
- data/manual/images/fit.rb +0 -20
- data/manual/images/horizontal.rb +0 -24
- data/manual/images/images.rb +0 -41
- data/manual/images/plain_image.rb +0 -17
- data/manual/images/scale.rb +0 -21
- data/manual/images/vertical.rb +0 -30
- data/manual/images/width_and_height.rb +0 -24
- data/manual/layout/boxes.rb +0 -26
- data/manual/layout/content.rb +0 -24
- data/manual/layout/layout.rb +0 -27
- data/manual/layout/simple_grid.rb +0 -22
- data/manual/outline/add_subsection_to.rb +0 -60
- data/manual/outline/insert_section_after.rb +0 -46
- data/manual/outline/outline.rb +0 -33
- data/manual/outline/sections_and_pages.rb +0 -66
- data/manual/repeatable_content/alternate_page_numbering.rb +0 -36
- data/manual/repeatable_content/page_numbering.rb +0 -55
- data/manual/repeatable_content/repeatable_content.rb +0 -35
- data/manual/repeatable_content/repeater.rb +0 -54
- data/manual/repeatable_content/stamp.rb +0 -40
- data/manual/security/encryption.rb +0 -28
- data/manual/security/permissions.rb +0 -43
- data/manual/security/security.rb +0 -28
- data/manual/table.rb +0 -16
- data/manual/text/alignment.rb +0 -43
- data/manual/text/color.rb +0 -24
- data/manual/text/column_box.rb +0 -30
- data/manual/text/fallback_fonts.rb +0 -41
- data/manual/text/font.rb +0 -40
- data/manual/text/font_size.rb +0 -44
- data/manual/text/font_style.rb +0 -25
- data/manual/text/formatted_callbacks.rb +0 -70
- data/manual/text/formatted_text.rb +0 -61
- data/manual/text/free_flowing_text.rb +0 -50
- data/manual/text/inline.rb +0 -40
- data/manual/text/kerning_and_character_spacing.rb +0 -38
- data/manual/text/leading.rb +0 -24
- data/manual/text/line_wrapping.rb +0 -60
- data/manual/text/paragraph_indentation.rb +0 -31
- data/manual/text/positioned_text.rb +0 -37
- data/manual/text/registering_families.rb +0 -51
- data/manual/text/rendering_and_color.rb +0 -36
- data/manual/text/right_to_left_text.rb +0 -54
- data/manual/text/rotation.rb +0 -52
- data/manual/text/single_usage.rb +0 -36
- data/manual/text/text.rb +0 -75
- data/manual/text/text_box_excess.rb +0 -35
- data/manual/text/text_box_extensions.rb +0 -48
- data/manual/text/text_box_overflow.rb +0 -51
- data/manual/text/utf8.rb +0 -27
- data/manual/text/win_ansi_charset.rb +0 -62
- data/prawn.gemspec +0 -51
- data/spec/data/curves.pdf +0 -66
- data/spec/extensions/encoding_helpers.rb +0 -11
- data/spec/prawn/document/bounding_box_spec.rb +0 -550
- data/spec/prawn/document/column_box_spec.rb +0 -75
- data/spec/prawn/document/security_spec.rb +0 -176
- data/spec/prawn/document_annotations_spec.rb +0 -76
- data/spec/prawn/document_destinations_spec.rb +0 -15
- data/spec/prawn/document_grid_spec.rb +0 -99
- data/spec/prawn/document_reference_spec.rb +0 -27
- data/spec/prawn/document_span_spec.rb +0 -44
- data/spec/prawn/document_spec.rb +0 -805
- data/spec/prawn/font_metric_cache_spec.rb +0 -54
- data/spec/prawn/font_spec.rb +0 -544
- data/spec/prawn/graphics/blend_mode_spec.rb +0 -63
- data/spec/prawn/graphics/transparency_spec.rb +0 -81
- data/spec/prawn/graphics_spec.rb +0 -872
- data/spec/prawn/graphics_stroke_styles_spec.rb +0 -229
- data/spec/prawn/image_handler_spec.rb +0 -53
- data/spec/prawn/images/jpg_spec.rb +0 -20
- data/spec/prawn/images/png_spec.rb +0 -283
- data/spec/prawn/images_spec.rb +0 -229
- data/spec/prawn/measurements_extensions_spec.rb +0 -24
- data/spec/prawn/outline_spec.rb +0 -512
- data/spec/prawn/repeater_spec.rb +0 -166
- data/spec/prawn/soft_mask_spec.rb +0 -74
- data/spec/prawn/stamp_spec.rb +0 -173
- data/spec/prawn/text/box_spec.rb +0 -1110
- data/spec/prawn/text/formatted/arranger_spec.rb +0 -466
- data/spec/prawn/text/formatted/box_spec.rb +0 -849
- data/spec/prawn/text/formatted/fragment_spec.rb +0 -343
- data/spec/prawn/text/formatted/line_wrap_spec.rb +0 -495
- data/spec/prawn/text/formatted/parser_spec.rb +0 -697
- data/spec/prawn/text_draw_text_spec.rb +0 -150
- data/spec/prawn/text_rendering_mode_spec.rb +0 -48
- data/spec/prawn/text_spacing_spec.rb +0 -95
- data/spec/prawn/text_spec.rb +0 -603
- data/spec/prawn/text_with_inline_formatting_spec.rb +0 -35
- data/spec/prawn/transformation_stack_spec.rb +0 -66
- data/spec/prawn/view_spec.rb +0 -63
- data/spec/prawn_manual_spec.rb +0 -35
- data/spec/spec_helper.rb +0 -48
data/lib/prawn/fonts/ttf.rb
CHANGED
@@ -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
|
-
#
|
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
|
30
|
+
super(format(MESSAGE_WITH_FONT, font: font))
|
24
31
|
else
|
25
|
-
super
|
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
|
-
|
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 =
|
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
|
-
#
|
64
|
-
|
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)
|
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
|
-
|
173
|
+
} * scale
|
74
174
|
else
|
75
|
-
string.codepoints.reduce(0)
|
175
|
+
string.codepoints.reduce(0) { |s, r|
|
76
176
|
s + character_width_by_code(r)
|
77
|
-
|
177
|
+
} * scale
|
78
178
|
end
|
79
179
|
end
|
80
180
|
|
81
|
-
# The font bbox
|
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
|
-
#
|
188
|
+
# Does this font contain kerning data.
|
88
189
|
#
|
89
|
-
#
|
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
|
-
#
|
97
|
-
#
|
98
|
-
#
|
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}"
|
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
|
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
|
-
|
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
|
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
|
-
|
425
|
+
if full_font_embedding
|
426
|
+
embed_full_font(reference)
|
427
|
+
else
|
428
|
+
embed_subset(reference, subset)
|
429
|
+
end
|
430
|
+
end
|
293
431
|
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
font
|
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!(
|
306
|
-
fontfile.
|
307
|
-
fontfile.stream.
|
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 =
|
325
|
-
|
326
|
-
|
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
|
-
|
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
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
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
|
-
|
352
|
-
|
353
|
-
|
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
|
-
|
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
|
-
|
363
|
-
|
364
|
-
cmap.stream.compress!
|
528
|
+
true_type = font_type(font) == :true_type
|
529
|
+
open_type = font_type(font) == :open_type
|
365
530
|
|
366
|
-
|
367
|
-
|
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
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
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
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
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
|
-
|
608
|
+
end
|
397
609
|
|
398
610
|
def read_ttf_file
|
399
611
|
TTFunk::File.open(@name)
|
data/lib/prawn/fonts.rb
ADDED
@@ -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
|
-
#
|
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
|
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
|