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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/lib/prawn/document/bounding_box.rb +223 -143
- data/lib/prawn/document/column_box.rb +61 -26
- data/lib/prawn/document/internals.rb +25 -16
- data/lib/prawn/document/span.rb +21 -18
- data/lib/prawn/document.rb +273 -182
- data/lib/prawn/encoding.rb +2 -5
- data/lib/prawn/errors.rb +23 -34
- data/lib/prawn/font.rb +254 -139
- data/lib/prawn/font_metric_cache.rb +18 -16
- data/lib/prawn/fonts/afm.rb +99 -57
- 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 +345 -107
- 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 +75 -50
- data/lib/prawn/graphics/dash.rb +45 -42
- data/lib/prawn/graphics/join_style.rb +18 -12
- data/lib/prawn/graphics/patterns.rb +239 -110
- data/lib/prawn/graphics/transformation.rb +51 -44
- data/lib/prawn/graphics/transparency.rb +16 -40
- data/lib/prawn/graphics.rb +370 -260
- data/lib/prawn/grid.rb +219 -57
- data/lib/prawn/image_handler.rb +27 -10
- data/lib/prawn/images/image.rb +8 -10
- data/lib/prawn/images/jpg.rb +46 -20
- data/lib/prawn/images/png.rb +94 -42
- data/lib/prawn/images.rb +70 -81
- data/lib/prawn/measurement_extensions.rb +39 -8
- data/lib/prawn/measurements.rb +60 -5
- data/lib/prawn/outline.rb +120 -113
- data/lib/prawn/repeater.rb +52 -36
- data/lib/prawn/security/arcfour.rb +4 -4
- data/lib/prawn/security.rb +106 -98
- data/lib/prawn/soft_mask.rb +42 -30
- data/lib/prawn/stamp.rb +38 -42
- data/lib/prawn/text/box.rb +156 -105
- data/lib/prawn/text/formatted/arranger.rb +121 -41
- data/lib/prawn/text/formatted/box.rb +239 -163
- data/lib/prawn/text/formatted/fragment.rb +130 -14
- data/lib/prawn/text/formatted/line_wrap.rb +49 -38
- data/lib/prawn/text/formatted/parser.rb +116 -74
- data/lib/prawn/text/formatted/wrap.rb +25 -26
- data/lib/prawn/text/formatted.rb +75 -0
- data/lib/prawn/text.rb +456 -211
- 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 +69 -54
- data/lib/prawn.rb +24 -18
- data.tar.gz.sig +0 -0
- metadata +55 -262
- metadata.gz.sig +3 -4
- data/.yardopts +0 -10
- data/Gemfile +0 -5
- data/Rakefile +0 -54
- 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 -25
- 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 -22
- 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 -37
- data/manual/graphics/gradients.rb +0 -43
- data/manual/graphics/graphics.rb +0 -64
- data/manual/graphics/helper.rb +0 -27
- 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 -28
- 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 -27
- 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 -41
- 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 -22
- data/manual/text/formatted_callbacks.rb +0 -65
- data/manual/text/formatted_text.rb +0 -58
- 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 -32
- 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 -47
- 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 -49
- data/manual/text/utf8.rb +0 -27
- data/manual/text/win_ansi_charset.rb +0 -62
- data/prawn.gemspec +0 -57
- data/spec/data/curves.pdf +0 -66
- data/spec/extensions/encoding_helpers.rb +0 -11
- data/spec/prawn/document/bounding_box_spec.rb +0 -546
- 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 -36
- data/spec/prawn/document_spec.rb +0 -802
- data/spec/prawn/font_metric_cache_spec.rb +0 -54
- data/spec/prawn/font_spec.rb +0 -542
- 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 -837
- 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 -224
- data/spec/prawn/measurements_extensions_spec.rb +0 -24
- data/spec/prawn/outline_spec.rb +0 -412
- data/spec/prawn/repeater_spec.rb +0 -165
- data/spec/prawn/soft_mask_spec.rb +0 -74
- data/spec/prawn/stamp_spec.rb +0 -172
- data/spec/prawn/text/box_spec.rb +0 -1112
- data/spec/prawn/text/formatted/arranger_spec.rb +0 -466
- data/spec/prawn/text/formatted/box_spec.rb +0 -846
- data/spec/prawn/text/formatted/fragment_spec.rb +0 -343
- data/spec/prawn/text/formatted/line_wrap_spec.rb +0 -494
- data/spec/prawn/text/formatted/parser_spec.rb +0 -697
- data/spec/prawn/text_draw_text_spec.rb +0 -149
- 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
@@ -1,19 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# font_metric_cache.rb : The Prawn font class
|
4
|
-
#
|
5
|
-
# Copyright Dec 2012, Kenneth Kalmer. All Rights Reserved.
|
6
|
-
#
|
7
|
-
# This is free software. Please see the LICENSE and COPYING files for details.
|
8
|
-
#
|
9
|
-
|
10
3
|
module Prawn
|
11
|
-
# Cache used internally by Prawn::Document instances to calculate the width
|
4
|
+
# Cache used internally by {Prawn::Document} instances to calculate the width
|
12
5
|
# of various strings for layout purposes.
|
13
6
|
#
|
14
7
|
# @private
|
15
8
|
class FontMetricCache
|
16
|
-
CacheEntry = Struct.new(:font, :options, :string)
|
9
|
+
CacheEntry = Struct.new(:font, :font_size, :options, :string)
|
17
10
|
|
18
11
|
def initialize(document)
|
19
12
|
@document = document
|
@@ -21,17 +14,26 @@ module Prawn
|
|
21
14
|
@cache = {}
|
22
15
|
end
|
23
16
|
|
17
|
+
# Get width of string.
|
18
|
+
#
|
19
|
+
# @param string [String]
|
20
|
+
# @param options [Hash{Symbol => any}]
|
21
|
+
# @option options :style [Symbol]
|
22
|
+
# @option options :size [Number]
|
23
|
+
# @option options :kerning [Boolean] (false)
|
24
|
+
# @return [Number]
|
24
25
|
def width_of(string, options)
|
25
|
-
f =
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
26
|
+
f =
|
27
|
+
if options[:style]
|
28
|
+
# override style with :style => :bold
|
29
|
+
@document.find_font(@document.font.family, style: options[:style])
|
30
|
+
else
|
31
|
+
@document.font
|
32
|
+
end
|
31
33
|
|
32
34
|
encoded_string = f.normalize_encoding(string)
|
33
35
|
|
34
|
-
key = CacheEntry.new(f, options, encoded_string)
|
36
|
+
key = CacheEntry.new(f, @document.font_size, options, encoded_string)
|
35
37
|
|
36
38
|
@cache[key] ||= f.compute_width_of(encoded_string, options)
|
37
39
|
|
data/lib/prawn/fonts/afm.rb
CHANGED
@@ -1,24 +1,31 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# Implements AFM font support for Prawn
|
4
|
-
#
|
5
|
-
# Copyright May 2008, Gregory Brown / James Healy. All Rights Reserved.
|
6
|
-
#
|
7
|
-
# This is free software. Please see the LICENSE and COPYING files for details.
|
8
|
-
|
9
3
|
require_relative '../encoding'
|
10
4
|
|
11
5
|
module Prawn
|
12
6
|
module Fonts
|
13
|
-
#
|
14
|
-
|
7
|
+
# AFM font. AFM stands for Adobe Font Metrics. It's not a complete font, it
|
8
|
+
# doesn't provide actual glyph outlines. It only contains glyph metrics to
|
9
|
+
# make text layout possible. AFM is used for PDF built-in fonts. Those
|
10
|
+
# fonts are supposed to be present on the target system making it possible
|
11
|
+
# to save a little bit of space by not embedding the fonts. A file that uses
|
12
|
+
# these fonts can not be read on a system that doesn't have these fonts
|
13
|
+
# installed.
|
14
|
+
#
|
15
|
+
# @note You shouldn't use this class directly.
|
15
16
|
class AFM < Font
|
16
17
|
class << self
|
18
|
+
# Prawn would warn you if you're using non-ASCII glyphs with AFM fonts
|
19
|
+
# as not all implementations provide those glyphs. This attribute
|
20
|
+
# suppresses that warning.
|
21
|
+
#
|
22
|
+
# @return [Boolean] (false)
|
17
23
|
attr_accessor :hide_m17n_warning
|
18
24
|
end
|
19
25
|
|
20
26
|
self.hide_m17n_warning = false
|
21
27
|
|
28
|
+
# List of PDF built-in fonts.
|
22
29
|
BUILT_INS = %w[
|
23
30
|
Courier Helvetica Times-Roman Symbol ZapfDingbats
|
24
31
|
Courier-Bold Courier-Oblique Courier-BoldOblique
|
@@ -26,31 +33,46 @@ module Prawn
|
|
26
33
|
Helvetica-Bold Helvetica-Oblique Helvetica-BoldOblique
|
27
34
|
].freeze
|
28
35
|
|
36
|
+
# Does this font support Unicode?
|
37
|
+
#
|
38
|
+
# @return [false]
|
29
39
|
def unicode?
|
30
40
|
false
|
31
41
|
end
|
32
42
|
|
43
|
+
# Paths to look for AFM files at.
|
44
|
+
#
|
45
|
+
# @return [Array<String>]
|
33
46
|
def self.metrics_path
|
34
|
-
@metrics_path ||=
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
47
|
+
@metrics_path ||=
|
48
|
+
if ENV['METRICS']
|
49
|
+
ENV['METRICS'].split(':')
|
50
|
+
else
|
51
|
+
[
|
52
|
+
'.', '/usr/lib/afm',
|
53
|
+
'/usr/local/lib/afm',
|
54
|
+
'/usr/openwin/lib/fonts/afm',
|
55
|
+
"#{Prawn::DATADIR}/fonts",
|
56
|
+
]
|
57
|
+
end
|
44
58
|
end
|
45
59
|
|
46
|
-
|
60
|
+
# @private
|
61
|
+
attr_reader :attributes
|
47
62
|
|
48
|
-
#
|
63
|
+
# Parsed AFM data cache.
|
64
|
+
#
|
65
|
+
# @return [SynchronizedCache]
|
49
66
|
def self.font_data
|
50
67
|
@font_data ||= SynchronizedCache.new
|
51
68
|
end
|
52
69
|
|
53
|
-
|
70
|
+
# @param document [Prawn::Document]
|
71
|
+
# @param name [String]
|
72
|
+
# @param options [Hash]
|
73
|
+
# @option options :family [String]
|
74
|
+
# @option options :style [Symbol]
|
75
|
+
def initialize(document, name, options = {})
|
54
76
|
name ||= options[:family]
|
55
77
|
unless BUILT_INS.include?(name)
|
56
78
|
raise Prawn::Errors::UnknownFont,
|
@@ -61,7 +83,7 @@ module Prawn
|
|
61
83
|
|
62
84
|
file_name = @name.dup
|
63
85
|
file_name << '.afm' unless /\.afm$/.match?(file_name)
|
64
|
-
file_name = file_name[0] == '/'
|
86
|
+
file_name = find_font(file_name) unless file_name[0] == '/'
|
65
87
|
|
66
88
|
font_data = self.class.font_data[file_name] ||= parse_afm(file_name)
|
67
89
|
@glyph_widths = font_data[:glyph_widths]
|
@@ -71,42 +93,51 @@ module Prawn
|
|
71
93
|
@kern_pair_table = font_data[:kern_pair_table]
|
72
94
|
@attributes = font_data[:attributes]
|
73
95
|
|
74
|
-
@ascender = @attributes
|
75
|
-
@descender = @attributes
|
96
|
+
@ascender = Integer(@attributes.fetch('ascender', '0'), 10)
|
97
|
+
@descender = Integer(@attributes.fetch('descender', '0'), 10)
|
76
98
|
@line_gap = Float(bbox[3] - bbox[1]) - (@ascender - @descender)
|
77
99
|
end
|
78
100
|
|
79
|
-
# The font bbox
|
101
|
+
# The font bbox.
|
80
102
|
#
|
103
|
+
# @return [Array(Number, Number, Number, Number)]
|
81
104
|
def bbox
|
82
105
|
@bbox ||= @attributes['fontbbox'].split(/\s+/).map { |e| Integer(e) }
|
83
106
|
end
|
84
107
|
|
85
|
-
#
|
86
|
-
|
108
|
+
# Compute width of a string at the specified size, optionally with kerning
|
109
|
+
# applied.
|
110
|
+
#
|
111
|
+
# @param string [String] *must* be encoded as WinAnsi
|
112
|
+
# @param options [Hash{Symbol => any}]
|
113
|
+
# @option options :size [Number]
|
114
|
+
# @option options :kerning [Boolean] (false)
|
115
|
+
# @return [Number]
|
116
|
+
def compute_width_of(string, options = {})
|
87
117
|
scale = (options[:size] || size) / 1000.0
|
88
118
|
|
89
119
|
if options[:kerning]
|
90
120
|
strings, numbers = kern(string).partition { |e| e.is_a?(String) }
|
91
|
-
total_kerning_offset = numbers.
|
121
|
+
total_kerning_offset = numbers.sum
|
92
122
|
(unscaled_width_of(strings.join) - total_kerning_offset) * scale
|
93
123
|
else
|
94
124
|
unscaled_width_of(string) * scale
|
95
125
|
end
|
96
126
|
end
|
97
127
|
|
98
|
-
#
|
128
|
+
# Does this font contain kerning data.
|
99
129
|
#
|
100
|
-
#
|
101
|
-
def has_kerning_data?
|
130
|
+
# @return [Boolean]
|
131
|
+
def has_kerning_data? # rubocop: disable Naming/PredicateName
|
102
132
|
@kern_pairs.any?
|
103
133
|
end
|
104
|
-
# rubocop: enable Naming/PredicateName
|
105
134
|
|
106
|
-
#
|
135
|
+
# Built-in fonts only work with WinAnsi encoding, so translate the
|
107
136
|
# string. Changes the encoding in-place, so the argument itself
|
108
137
|
# is replaced with a string in WinAnsi encoding.
|
109
138
|
#
|
139
|
+
# @param text [String]
|
140
|
+
# @return [String]
|
110
141
|
def normalize_encoding(text)
|
111
142
|
text.encode('windows-1252')
|
112
143
|
rescue ::Encoding::InvalidByteSequenceError,
|
@@ -114,17 +145,23 @@ module Prawn
|
|
114
145
|
|
115
146
|
raise Prawn::Errors::IncompatibleStringEncoding,
|
116
147
|
"Your document includes text that's not compatible with the " \
|
117
|
-
|
118
|
-
|
119
|
-
|
148
|
+
"Windows-1252 character set.\n" \
|
149
|
+
'If you need full UTF-8 support, use external fonts instead of ' \
|
150
|
+
"PDF's built-in fonts.\n"
|
120
151
|
end
|
121
152
|
|
153
|
+
# Encode text to UTF-8.
|
154
|
+
#
|
155
|
+
# @param text [String]
|
156
|
+
# @return [String]
|
122
157
|
def to_utf8(text)
|
123
158
|
text.encode('UTF-8')
|
124
159
|
end
|
125
160
|
|
126
|
-
# Returns the number of characters in
|
161
|
+
# Returns the number of characters in `str` (a WinAnsi-encoded string).
|
127
162
|
#
|
163
|
+
# @param str [String]
|
164
|
+
# @return [Integer]
|
128
165
|
def character_count(str)
|
129
166
|
str.length
|
130
167
|
end
|
@@ -136,15 +173,23 @@ module Prawn
|
|
136
173
|
# is either a string or an array (for kerned text).
|
137
174
|
#
|
138
175
|
# For Adobe fonts, there is only ever a single subset, so
|
139
|
-
# the first element of the array is
|
176
|
+
# the first element of the array is `0`, and the second is
|
140
177
|
# the string itself (or an array, if kerning is performed).
|
141
178
|
#
|
142
|
-
# The
|
179
|
+
# The `text` argument must be in WinAnsi encoding (cp1252).
|
143
180
|
#
|
181
|
+
# @param text [String]
|
182
|
+
# @param options [Hash{Symbol => any}]
|
183
|
+
# @option options :kerning [Boolean]
|
184
|
+
# @return [Array<Array(0, (String, Array)>]
|
144
185
|
def encode_text(text, options = {})
|
145
186
|
[[0, options[:kerning] ? kern(text) : text]]
|
146
187
|
end
|
147
188
|
|
189
|
+
# Does this font has a glyph for the character?
|
190
|
+
#
|
191
|
+
# @param char [String]
|
192
|
+
# @return [Boolean]
|
148
193
|
def glyph_present?(char)
|
149
194
|
!normalize_encoding(char).nil?
|
150
195
|
rescue Prawn::Errors::IncompatibleStringEncoding
|
@@ -157,7 +202,7 @@ module Prawn
|
|
157
202
|
font_dict = {
|
158
203
|
Type: :Font,
|
159
204
|
Subtype: :Type1,
|
160
|
-
BaseFont: name.to_sym
|
205
|
+
BaseFont: name.to_sym,
|
161
206
|
}
|
162
207
|
|
163
208
|
# Symbolic AFM fonts (Symbol, ZapfDingbats) have their own encodings
|
@@ -171,12 +216,12 @@ module Prawn
|
|
171
216
|
end
|
172
217
|
|
173
218
|
def find_font(file)
|
174
|
-
self.class.metrics_path.find { |f| File.exist?
|
219
|
+
self.class.metrics_path.find { |f| File.exist?("#{f}/#{file}") } +
|
175
220
|
"/#{file}"
|
176
221
|
rescue NoMethodError
|
177
222
|
raise Prawn::Errors::UnknownFont,
|
178
223
|
"Couldn't find the font: #{file} in any of:\n" +
|
179
|
-
|
224
|
+
self.class.metrics_path.join("\n")
|
180
225
|
end
|
181
226
|
|
182
227
|
def parse_afm(file_name)
|
@@ -184,14 +229,14 @@ module Prawn
|
|
184
229
|
glyph_widths: {},
|
185
230
|
bounding_boxes: {},
|
186
231
|
kern_pairs: {},
|
187
|
-
attributes: {}
|
232
|
+
attributes: {},
|
188
233
|
}
|
189
234
|
section = []
|
190
235
|
|
191
236
|
File.foreach(file_name) do |line|
|
192
237
|
case line
|
193
238
|
when /^Start(\w+)/
|
194
|
-
section.push
|
239
|
+
section.push(Regexp.last_match(1))
|
195
240
|
next
|
196
241
|
when /^End(\w+)/
|
197
242
|
section.pop
|
@@ -203,13 +248,13 @@ module Prawn
|
|
203
248
|
next unless /^CH?\s/.match?(line)
|
204
249
|
|
205
250
|
name = line[/\bN\s+(\.?\w+)\s*;/, 1]
|
206
|
-
data[:glyph_widths][name] = line[/\bWX\s+(\d+)\s*;/, 1]
|
251
|
+
data[:glyph_widths][name] = Integer(line[/\bWX\s+(\d+)\s*;/, 1], 10)
|
207
252
|
data[:bounding_boxes][name] = line[/\bB\s+([^;]+);/, 1].to_s.rstrip
|
208
253
|
when %w[FontMetrics KernData KernPairs]
|
209
254
|
next unless line =~ /^KPX\s+(\.?\w+)\s+(\.?\w+)\s+(-?\d+)/
|
210
255
|
|
211
256
|
data[:kern_pairs][[Regexp.last_match(1), Regexp.last_match(2)]] =
|
212
|
-
Regexp.last_match(3)
|
257
|
+
Integer(Regexp.last_match(3), 10)
|
213
258
|
when %w[FontMetrics KernData TrackKern],
|
214
259
|
%w[FontMetrics Composites]
|
215
260
|
next
|
@@ -220,15 +265,12 @@ module Prawn
|
|
220
265
|
|
221
266
|
# process data parsed from AFM file to build tables which
|
222
267
|
# will be used when measuring and kerning text
|
223
|
-
data[:glyph_table] =
|
224
|
-
|
225
|
-
|
268
|
+
data[:glyph_table] =
|
269
|
+
(0..255).map { |i|
|
270
|
+
data[:glyph_widths].fetch(Encoding::WinAnsi::CHARACTERS[i], 0)
|
271
|
+
}
|
226
272
|
|
227
|
-
character_hash =
|
228
|
-
Encoding::WinAnsi::CHARACTERS.zip(
|
229
|
-
(0..Encoding::WinAnsi::CHARACTERS.size).to_a
|
230
|
-
)
|
231
|
-
]
|
273
|
+
character_hash = Encoding::WinAnsi::CHARACTERS.zip((0..Encoding::WinAnsi::CHARACTERS.size).to_a).to_h
|
232
274
|
data[:kern_pair_table] =
|
233
275
|
data[:kern_pairs].each_with_object({}) do |p, h|
|
234
276
|
h[p[0].map { |n| character_hash[n] }] = p[1]
|
@@ -252,7 +294,7 @@ module Prawn
|
|
252
294
|
end
|
253
295
|
|
254
296
|
# converts a string into an array with spacing offsets
|
255
|
-
#
|
297
|
+
# between characters that need to be kerned
|
256
298
|
#
|
257
299
|
# String *must* be encoded as WinAnsi
|
258
300
|
#
|
@@ -271,7 +313,7 @@ module Prawn
|
|
271
313
|
end
|
272
314
|
|
273
315
|
kerned.map do |e|
|
274
|
-
e = e.
|
316
|
+
e = e.pack('C*') if e.is_a?(Array)
|
275
317
|
if e.respond_to?(:force_encoding)
|
276
318
|
e.force_encoding(::Encoding::Windows_1252)
|
277
319
|
else
|
@@ -281,7 +323,7 @@ module Prawn
|
|
281
323
|
end
|
282
324
|
|
283
325
|
def unscaled_width_of(string)
|
284
|
-
string.bytes.
|
326
|
+
string.bytes.reduce(0) do |s, r|
|
285
327
|
s + @glyph_table[r]
|
286
328
|
end
|
287
329
|
end
|
data/lib/prawn/fonts/dfont.rb
CHANGED
@@ -4,7 +4,9 @@ require_relative 'ttf'
|
|
4
4
|
|
5
5
|
module Prawn
|
6
6
|
module Fonts
|
7
|
-
#
|
7
|
+
# DFONT font. DFONT is a bunch of TrueType fonts in a single file.
|
8
|
+
#
|
9
|
+
# @note You shouldn't use this class directly.
|
8
10
|
class DFont < TTF
|
9
11
|
# Returns a list of the names of all named fonts in the given dfont file.
|
10
12
|
# Note that fonts are not required to be named in a dfont file, so the
|
@@ -12,6 +14,8 @@ module Prawn
|
|
12
14
|
# the list is returned in no particular order, so the first font in the
|
13
15
|
# list is not necessarily the font at index 0 in the file.
|
14
16
|
#
|
17
|
+
# @param file [String]
|
18
|
+
# @return [Array<String>]
|
15
19
|
def self.named_fonts(file)
|
16
20
|
TTFunk::ResourceFile.open(file) do |f|
|
17
21
|
return f.resources_for('sfnt')
|
@@ -20,6 +24,8 @@ module Prawn
|
|
20
24
|
|
21
25
|
# Returns the number of fonts contained in the dfont file.
|
22
26
|
#
|
27
|
+
# @param file [String]
|
28
|
+
# @return [Integer]
|
23
29
|
def self.font_count(file)
|
24
30
|
TTFunk::ResourceFile.open(file) do |f|
|
25
31
|
return f.map['sfnt'][:list].length
|
data/lib/prawn/fonts/otf.rb
CHANGED
@@ -4,7 +4,10 @@ require_relative 'ttf'
|
|
4
4
|
|
5
5
|
module Prawn
|
6
6
|
module Fonts
|
7
|
-
#
|
7
|
+
# OpenType font. This class is used mostly to distinguish OTF from TTF.
|
8
|
+
# All functionality is in the {Fonts::TTF} class.
|
9
|
+
#
|
10
|
+
# @note You shouldn't use this class directly.
|
8
11
|
class OTF < TTF
|
9
12
|
end
|
10
13
|
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Prawn
|
4
|
+
module Fonts
|
5
|
+
# This class generates ToUnicode CMap for embedde TrueType/OpenType fonts.
|
6
|
+
# It's a separate format and is somewhat complicated so it has its own
|
7
|
+
# place.
|
8
|
+
#
|
9
|
+
# @private
|
10
|
+
class ToUnicodeCMap
|
11
|
+
# mapping is expected to be a hash with keys being character codes (in
|
12
|
+
# broad sense, as used in the showing operation strings) and values being
|
13
|
+
# Unicode code points.
|
14
|
+
def initialize(mapping, code_space_size = nil)
|
15
|
+
@mapping = mapping
|
16
|
+
@code_space_size = code_space_size
|
17
|
+
end
|
18
|
+
|
19
|
+
# Generate CMap.
|
20
|
+
#
|
21
|
+
# @return [String]
|
22
|
+
def generate
|
23
|
+
chunks = []
|
24
|
+
|
25
|
+
# Header
|
26
|
+
chunks << <<~HEADER.chomp
|
27
|
+
/CIDInit /ProcSet findresource begin
|
28
|
+
12 dict begin
|
29
|
+
begincmap
|
30
|
+
/CIDSystemInfo 3 dict dup begin
|
31
|
+
/Registry (Adobe) def
|
32
|
+
/Ordering (UCS) def
|
33
|
+
/Supplement 0 def
|
34
|
+
end def
|
35
|
+
/CMapName /Adobe-Identity-UCS def
|
36
|
+
/CMapType 2 def
|
37
|
+
HEADER
|
38
|
+
|
39
|
+
max_glyph_index = mapping.keys.max
|
40
|
+
# Range
|
41
|
+
code_space_size = (max_glyph_index.bit_length / 8.0).ceil
|
42
|
+
|
43
|
+
used_code_space_size = @code_space_size || code_space_size
|
44
|
+
|
45
|
+
# In CMap codespaces are not sequentional, they're ranges in
|
46
|
+
# a multi-dimentional space. Each byte is considered separately. So we
|
47
|
+
# have to maximally extend the lower bytes in order to allow for
|
48
|
+
# continuos mapping.
|
49
|
+
# We only keep the highest byte because usually it's lower than
|
50
|
+
# maximally allowed and we don't want to cover that unused space.
|
51
|
+
code_space_max = max_glyph_index | ('ff' * (code_space_size - 1)).to_i(16)
|
52
|
+
|
53
|
+
chunks << '1 begincodespacerange'
|
54
|
+
chunks << format("<%0#{used_code_space_size * 2}X><%0#{used_code_space_size * 2}X>", 0, code_space_max)
|
55
|
+
chunks << 'endcodespacerange'
|
56
|
+
|
57
|
+
# Mapping
|
58
|
+
all_spans = mapping_spans(mapping.reject { |gid, cid| gid.zero? || (0xd800..0xdfff).cover?(cid) })
|
59
|
+
|
60
|
+
short_spans, long_spans = all_spans.partition { |span| span[0] == :short }
|
61
|
+
|
62
|
+
long_spans
|
63
|
+
.each_slice(100) do |spans|
|
64
|
+
chunks << "#{spans.length} beginbfrange"
|
65
|
+
|
66
|
+
spans.each do |type, span|
|
67
|
+
# rubocop: disable Lint/FormatParameterMismatch # false positive
|
68
|
+
case type
|
69
|
+
when :fully_sorted
|
70
|
+
chunks << format(
|
71
|
+
"<%0#{code_space_size * 2}X><%0#{code_space_size * 2}X><%s>",
|
72
|
+
span.first[0],
|
73
|
+
span.last[0],
|
74
|
+
span.first[1].chr(::Encoding::UTF_16BE).unpack1('H*'),
|
75
|
+
)
|
76
|
+
when :index_sorted
|
77
|
+
chunks << format(
|
78
|
+
"<%0#{code_space_size * 2}X><%0#{code_space_size * 2}X>[%s]",
|
79
|
+
span.first[0],
|
80
|
+
span.last[0],
|
81
|
+
span.map { |_, cid| "<#{cid.chr(::Encoding::UTF_16BE).unpack1('H*')}>" }.join(''),
|
82
|
+
)
|
83
|
+
end
|
84
|
+
# rubocop: enable Lint/FormatParameterMismatch
|
85
|
+
end
|
86
|
+
|
87
|
+
chunks << 'endbfrange'
|
88
|
+
end
|
89
|
+
|
90
|
+
short_spans
|
91
|
+
.map { |_type, slice| slice.flatten(1) }
|
92
|
+
.each_slice(100) do |mapping|
|
93
|
+
chunks << "#{mapping.length} beginbfchar"
|
94
|
+
chunks.concat(
|
95
|
+
mapping.map { |(gid, cid)|
|
96
|
+
# rubocop: disable Lint/FormatParameterMismatch # false positive
|
97
|
+
format(
|
98
|
+
"<%0#{code_space_size * 2}X><%s>",
|
99
|
+
gid,
|
100
|
+
cid.chr(::Encoding::UTF_16BE).unpack1('H*'),
|
101
|
+
)
|
102
|
+
# rubocop: enable Lint/FormatParameterMismatch
|
103
|
+
},
|
104
|
+
)
|
105
|
+
chunks << 'endbfchar'
|
106
|
+
end
|
107
|
+
|
108
|
+
# Footer
|
109
|
+
chunks << <<~FOOTER.chomp
|
110
|
+
endcmap
|
111
|
+
CMapName currentdict /CMap defineresource pop
|
112
|
+
end
|
113
|
+
end
|
114
|
+
FOOTER
|
115
|
+
|
116
|
+
chunks.join("\n")
|
117
|
+
end
|
118
|
+
|
119
|
+
private
|
120
|
+
|
121
|
+
attr_reader :mapping
|
122
|
+
|
123
|
+
attr_reader :cmap
|
124
|
+
attr_reader :code_space_size
|
125
|
+
attr_reader :code_space_max
|
126
|
+
|
127
|
+
def mapping_spans(mapping)
|
128
|
+
mapping
|
129
|
+
.sort
|
130
|
+
.slice_when { |a, b| (b[0] - a[0]) != 1 } # Slice at key discontinuity
|
131
|
+
.flat_map { |slice|
|
132
|
+
if slice.length == 1
|
133
|
+
[[:short, slice]]
|
134
|
+
else
|
135
|
+
continuous_slices, discontinuous_slices =
|
136
|
+
slice
|
137
|
+
.slice_when { |a, b| b[1] - a[1] != 1 } # Slice at value discontinuity
|
138
|
+
.partition { |subslice| subslice.length > 1 }
|
139
|
+
|
140
|
+
discontinuous_slices
|
141
|
+
.flatten(1) # Join together
|
142
|
+
.slice_when { |a, b| (b[0] - a[0]) != 1 } # Slice at key discontinuity, again
|
143
|
+
.map { |span| span.length > 1 ? [:index_sorted, span] : [:short, slice] } +
|
144
|
+
continuous_slices.map { |span| [:fully_sorted, span] }
|
145
|
+
end
|
146
|
+
} # rubocop: disable Style/MultilineBlockChain
|
147
|
+
.sort_by { |span| span[1][0][0] } # Sort span start key
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
data/lib/prawn/fonts/ttc.rb
CHANGED
@@ -4,11 +4,16 @@ require_relative 'ttf'
|
|
4
4
|
|
5
5
|
module Prawn
|
6
6
|
module Fonts
|
7
|
-
#
|
7
|
+
# TrueType Collection font. It's an SFNT-based format that contains a bunch
|
8
|
+
# of TrueType fonts in a single file.
|
9
|
+
#
|
10
|
+
# @note You shouldn't use this class directly.
|
8
11
|
class TTC < TTF
|
9
12
|
# Returns a list of the names of all named fonts in the given ttc file.
|
10
13
|
# They are returned in order of their appearance in the file.
|
11
14
|
#
|
15
|
+
# @param file [String]
|
16
|
+
# @return [Array<String>]
|
12
17
|
def self.font_names(file)
|
13
18
|
TTFunk::Collection.open(file) do |ttc|
|
14
19
|
ttc.map { |font| font.name.font_name.first }
|
@@ -20,7 +25,7 @@ module Prawn
|
|
20
25
|
def read_ttf_file
|
21
26
|
TTFunk::File.from_ttc(
|
22
27
|
@name,
|
23
|
-
font_option_to_index(@name, @options[:font])
|
28
|
+
font_option_to_index(@name, @options[:font]),
|
24
29
|
)
|
25
30
|
end
|
26
31
|
|