hexapdf 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +46 -0
- data/CONTRIBUTERS +1 -1
- data/README.md +5 -5
- data/VERSION +1 -1
- data/examples/emoji-smile.png +0 -0
- data/examples/emoji-wink.png +0 -0
- data/examples/graphics.rb +9 -8
- data/examples/standard_pdf_fonts.rb +2 -1
- data/examples/text_box_alignment.rb +47 -0
- data/examples/text_box_inline_boxes.rb +56 -0
- data/examples/text_box_line_wrapping.rb +57 -0
- data/examples/text_box_shapes.rb +166 -0
- data/examples/text_box_styling.rb +72 -0
- data/examples/truetype.rb +3 -4
- data/lib/hexapdf/cli/optimize.rb +2 -2
- data/lib/hexapdf/configuration.rb +8 -6
- data/lib/hexapdf/content/canvas.rb +8 -5
- data/lib/hexapdf/content/parser.rb +3 -2
- data/lib/hexapdf/content/processor.rb +14 -3
- data/lib/hexapdf/document.rb +1 -0
- data/lib/hexapdf/document/fonts.rb +2 -1
- data/lib/hexapdf/document/pages.rb +23 -0
- data/lib/hexapdf/font/invalid_glyph.rb +78 -0
- data/lib/hexapdf/font/true_type/font.rb +14 -3
- data/lib/hexapdf/font/true_type/table.rb +1 -0
- data/lib/hexapdf/font/true_type/table/cmap.rb +1 -1
- data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +1 -0
- data/lib/hexapdf/font/true_type/table/glyf.rb +4 -0
- data/lib/hexapdf/font/true_type/table/kern.rb +170 -0
- data/lib/hexapdf/font/true_type/table/post.rb +5 -1
- data/lib/hexapdf/font/true_type_wrapper.rb +71 -24
- data/lib/hexapdf/font/type1/afm_parser.rb +3 -2
- data/lib/hexapdf/font/type1/character_metrics.rb +0 -9
- data/lib/hexapdf/font/type1/font.rb +11 -0
- data/lib/hexapdf/font/type1/font_metrics.rb +6 -1
- data/lib/hexapdf/font/type1_wrapper.rb +51 -7
- data/lib/hexapdf/font_loader/standard14.rb +1 -1
- data/lib/hexapdf/layout.rb +51 -0
- data/lib/hexapdf/layout/inline_box.rb +95 -0
- data/lib/hexapdf/layout/line_fragment.rb +333 -0
- data/lib/hexapdf/layout/numeric_refinements.rb +56 -0
- data/lib/hexapdf/layout/style.rb +365 -0
- data/lib/hexapdf/layout/text_box.rb +727 -0
- data/lib/hexapdf/layout/text_fragment.rb +206 -0
- data/lib/hexapdf/layout/text_shaper.rb +155 -0
- data/lib/hexapdf/task.rb +0 -1
- data/lib/hexapdf/task/dereference.rb +1 -1
- data/lib/hexapdf/tokenizer.rb +3 -2
- data/lib/hexapdf/type/font_descriptor.rb +2 -1
- data/lib/hexapdf/type/font_type0.rb +3 -1
- data/lib/hexapdf/type/form.rb +12 -4
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/common_tokenizer_tests.rb +7 -0
- data/test/hexapdf/content/common.rb +8 -0
- data/test/hexapdf/content/test_canvas.rb +10 -22
- data/test/hexapdf/content/test_processor.rb +4 -1
- data/test/hexapdf/document/test_pages.rb +16 -0
- data/test/hexapdf/font/test_invalid_glyph.rb +34 -0
- data/test/hexapdf/font/test_true_type_wrapper.rb +25 -11
- data/test/hexapdf/font/test_type1_wrapper.rb +26 -10
- data/test/hexapdf/font/true_type/table/common.rb +27 -0
- data/test/hexapdf/font/true_type/table/test_cmap.rb +14 -20
- data/test/hexapdf/font/true_type/table/test_cmap_subtable.rb +7 -0
- data/test/hexapdf/font/true_type/table/test_glyf.rb +8 -6
- data/test/hexapdf/font/true_type/table/test_head.rb +9 -13
- data/test/hexapdf/font/true_type/table/test_hhea.rb +16 -23
- data/test/hexapdf/font/true_type/table/test_hmtx.rb +4 -7
- data/test/hexapdf/font/true_type/table/test_kern.rb +61 -0
- data/test/hexapdf/font/true_type/table/test_loca.rb +7 -13
- data/test/hexapdf/font/true_type/table/test_maxp.rb +4 -9
- data/test/hexapdf/font/true_type/table/test_name.rb +14 -17
- data/test/hexapdf/font/true_type/table/test_os2.rb +3 -5
- data/test/hexapdf/font/true_type/table/test_post.rb +21 -19
- data/test/hexapdf/font/true_type/test_font.rb +4 -0
- data/test/hexapdf/font/type1/common.rb +6 -0
- data/test/hexapdf/font/type1/test_afm_parser.rb +9 -0
- data/test/hexapdf/font/type1/test_font.rb +6 -0
- data/test/hexapdf/layout/test_inline_box.rb +40 -0
- data/test/hexapdf/layout/test_line_fragment.rb +206 -0
- data/test/hexapdf/layout/test_style.rb +143 -0
- data/test/hexapdf/layout/test_text_box.rb +640 -0
- data/test/hexapdf/layout/test_text_fragment.rb +208 -0
- data/test/hexapdf/layout/test_text_shaper.rb +64 -0
- data/test/hexapdf/task/test_dereference.rb +1 -0
- data/test/hexapdf/test_writer.rb +2 -2
- data/test/hexapdf/type/test_font_descriptor.rb +4 -2
- data/test/hexapdf/type/test_font_type0.rb +7 -0
- data/test/hexapdf/type/test_form.rb +12 -0
- metadata +29 -2
@@ -0,0 +1,170 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
#
|
3
|
+
#--
|
4
|
+
# This file is part of HexaPDF.
|
5
|
+
#
|
6
|
+
# HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
|
7
|
+
# Copyright (C) 2014-2017 Thomas Leitner
|
8
|
+
#
|
9
|
+
# HexaPDF is free software: you can redistribute it and/or modify it
|
10
|
+
# under the terms of the GNU Affero General Public License version 3 as
|
11
|
+
# published by the Free Software Foundation with the addition of the
|
12
|
+
# following permission added to Section 15 as permitted in Section 7(a):
|
13
|
+
# FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
|
14
|
+
# THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
|
15
|
+
# INFRINGEMENT OF THIRD PARTY RIGHTS.
|
16
|
+
#
|
17
|
+
# HexaPDF is distributed in the hope that it will be useful, but WITHOUT
|
18
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
20
|
+
# License for more details.
|
21
|
+
#
|
22
|
+
# You should have received a copy of the GNU Affero General Public License
|
23
|
+
# along with HexaPDF. If not, see <http://www.gnu.org/licenses/>.
|
24
|
+
#
|
25
|
+
# The interactive user interfaces in modified source and object code
|
26
|
+
# versions of HexaPDF must display Appropriate Legal Notices, as required
|
27
|
+
# under Section 5 of the GNU Affero General Public License version 3.
|
28
|
+
#
|
29
|
+
# In accordance with Section 7(b) of the GNU Affero General Public
|
30
|
+
# License, a covered work must retain the producer line in every PDF that
|
31
|
+
# is created or manipulated using HexaPDF.
|
32
|
+
#++
|
33
|
+
|
34
|
+
require 'hexapdf/font/true_type/table'
|
35
|
+
|
36
|
+
module HexaPDF
|
37
|
+
module Font
|
38
|
+
module TrueType
|
39
|
+
class Table
|
40
|
+
|
41
|
+
# The 'kern' table contains kerning values, i.e. values to control inter-character spacing.
|
42
|
+
#
|
43
|
+
# Restrictions:
|
44
|
+
#
|
45
|
+
# * Only subtable format 0 is supported, all other subtables are ignored.
|
46
|
+
#
|
47
|
+
# See: https://www.microsoft.com/typography/otspec/kern.htm
|
48
|
+
class Kern < Table
|
49
|
+
|
50
|
+
# A kerning subtable containing the actual information to do kerning.
|
51
|
+
class Subtable
|
52
|
+
|
53
|
+
# Creates a new subtable.
|
54
|
+
def initialize(pairs:, horizontal:, minimum_values:, cross_stream:)
|
55
|
+
@pairs = pairs
|
56
|
+
@horizontal = horizontal
|
57
|
+
@minimum_values = minimum_values
|
58
|
+
@cross_stream = cross_stream
|
59
|
+
end
|
60
|
+
|
61
|
+
# Returns the kerning value between the two glyphs, or +nil+ if there is no kerning
|
62
|
+
# value.
|
63
|
+
def kern(left, right)
|
64
|
+
@pairs.fetch(left, nil)&.fetch(right, nil)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Returns +true+ if this subtable is used for horizontal kerning.
|
68
|
+
def horizontal?
|
69
|
+
@horizontal
|
70
|
+
end
|
71
|
+
|
72
|
+
# Returns +true+ if this subtable contains minimum values and not kerning values.
|
73
|
+
def minimum_values?
|
74
|
+
@minimum_values
|
75
|
+
end
|
76
|
+
|
77
|
+
# Returns +true+ if this subtable contains cross-stream values, i.e. values that are
|
78
|
+
# applied perpendicular to the writing direction.
|
79
|
+
def cross_stream?
|
80
|
+
@cross_stream
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
# The version of the table.
|
86
|
+
attr_accessor :version
|
87
|
+
|
88
|
+
# The available subtables, all instances of Subtable.
|
89
|
+
attr_reader :subtables
|
90
|
+
|
91
|
+
# Returns the first subtable that supports horizontal non-cross-stream kerning, or +nil+
|
92
|
+
# if no such subtable exists.
|
93
|
+
def horizontal_kerning_subtable
|
94
|
+
@horizontal_kerning_subtable
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
def parse_table #:nodoc:
|
100
|
+
@version, nr_of_subtables = read_formatted(4, 'nn')
|
101
|
+
subtable_parsing_method = :parse_subtable0
|
102
|
+
if @version == 1
|
103
|
+
@version = Rational(@version << 16 + nr_of_subtables, 65536)
|
104
|
+
nr_of_subtables = read_formatted(4, 'N').first
|
105
|
+
subtable_parsing_method = :parse_subtable1
|
106
|
+
end
|
107
|
+
|
108
|
+
@subtables = []
|
109
|
+
send(subtable_parsing_method, nr_of_subtables) do |length, format, options|
|
110
|
+
if format == 0
|
111
|
+
pairs = Format0.parse(io, length)
|
112
|
+
@subtables << Subtable.new(pairs: pairs, **options)
|
113
|
+
elsif font.config['font.true_type.unknown_format'] == :raise
|
114
|
+
raise HexaPDF::Error, "Unsupported kern subtable format: #{format}"
|
115
|
+
else
|
116
|
+
io.pos += length
|
117
|
+
end
|
118
|
+
end
|
119
|
+
@horizontal_kerning_subtable = @subtables.find do |t|
|
120
|
+
t.horizontal? && !t.minimum_values? && !t.cross_stream?
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Parses subtables for kern table version 0.
|
125
|
+
def parse_subtable0(nr_of_subtables)
|
126
|
+
nr_of_subtables.times do
|
127
|
+
length, format, coverage = read_formatted(6, 'x2nCC')
|
128
|
+
options = {horizontal: (coverage[0] == 1),
|
129
|
+
minimum_values: (coverage[1] == 1),
|
130
|
+
cross_stream: (coverage[2] == 1)}
|
131
|
+
yield(length - 6, format, options)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# Parses subtables for kern table version 1.
|
136
|
+
def parse_subtable1(nr_of_subtables)
|
137
|
+
nr_of_subtables.times do
|
138
|
+
length, coverage, format = read_formatted(8, 'NCC')
|
139
|
+
options = {horizontal: (coverage[7] == 0),
|
140
|
+
minimum_values: false,
|
141
|
+
cross_stream: (coverage[6] == 1)}
|
142
|
+
yield(length - 8, format, options)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
# 'kern' subtable format 0
|
147
|
+
module Format0
|
148
|
+
|
149
|
+
# :call-seq:
|
150
|
+
# Format0.parse(io, length) -> pairs
|
151
|
+
#
|
152
|
+
# Parses the format 0 subtable and returns a hash of the form
|
153
|
+
# {left_char: {right_char: kern_value}}
|
154
|
+
def self.parse(io, _length)
|
155
|
+
number_of_pairs = io.read(8).unpack('n').first
|
156
|
+
pairs = Hash.new {|h, k| h[k] = {}}
|
157
|
+
io.read(number_of_pairs * 6).unpack('n*').each_slice(3) do |left, right, value|
|
158
|
+
pairs[left][right] = (value < 0x8000 ? value : -(value ^ 0xffff) - 1)
|
159
|
+
end
|
160
|
+
pairs
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
@@ -102,7 +102,11 @@ module HexaPDF
|
|
102
102
|
when 3 then Format3.parse(io, sub_table_length)
|
103
103
|
when 4 then Format4.parse(io, sub_table_length)
|
104
104
|
else
|
105
|
-
|
105
|
+
if font.config['font.true_type.unknown_format'] == :raise
|
106
|
+
raise HexaPDF::Error, "Unsupported post table format: #{@format}"
|
107
|
+
else
|
108
|
+
[]
|
109
|
+
end
|
106
110
|
end
|
107
111
|
end
|
108
112
|
|
@@ -33,6 +33,7 @@
|
|
33
33
|
|
34
34
|
require 'hexapdf/font/true_type'
|
35
35
|
require 'hexapdf/font/cmap'
|
36
|
+
require 'hexapdf/font/invalid_glyph'
|
36
37
|
require 'hexapdf/error'
|
37
38
|
|
38
39
|
module HexaPDF
|
@@ -58,10 +59,34 @@ module HexaPDF
|
|
58
59
|
# The glyph ID.
|
59
60
|
attr_reader :id
|
60
61
|
|
62
|
+
# The string representation of the glyph.
|
63
|
+
attr_reader :str
|
64
|
+
|
61
65
|
# Creates a new Glyph object.
|
62
|
-
def initialize(font, id)
|
66
|
+
def initialize(font, id, str)
|
63
67
|
@font = font
|
64
68
|
@id = id
|
69
|
+
@str = str
|
70
|
+
end
|
71
|
+
|
72
|
+
# Returns the glyph's minimum x coordinate.
|
73
|
+
def x_min
|
74
|
+
@x_min ||= @font[:glyf][id].x_min * 1000.0 / @font[:head].units_per_em
|
75
|
+
end
|
76
|
+
|
77
|
+
# Returns the glyph's maximum x coordinate.
|
78
|
+
def x_max
|
79
|
+
@x_max ||= @font[:glyf][id].x_max * 1000.0 / @font[:head].units_per_em
|
80
|
+
end
|
81
|
+
|
82
|
+
# Returns the glyph's minimum y coordinate.
|
83
|
+
def y_min
|
84
|
+
@y_min ||= @font[:glyf][id].y_min * 1000.0 / @font[:head].units_per_em
|
85
|
+
end
|
86
|
+
|
87
|
+
# Returns the glyph's maximum y coordinate.
|
88
|
+
def y_max
|
89
|
+
@y_max ||= @font[:glyf][id].y_max * 1000.0 / @font[:head].units_per_em
|
65
90
|
end
|
66
91
|
|
67
92
|
# Returns the width of the glyph.
|
@@ -69,11 +94,15 @@ module HexaPDF
|
|
69
94
|
@width ||= @font[:hmtx][id].advance_width * 1000.0 / @font[:head].units_per_em
|
70
95
|
end
|
71
96
|
|
72
|
-
# Returns +
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
97
|
+
# Returns +false+ since the word spacing parameter is never applied for multibyte font
|
98
|
+
# encodings where each glyph is encoded using two bytes.
|
99
|
+
def apply_word_spacing?
|
100
|
+
false
|
101
|
+
end
|
102
|
+
|
103
|
+
#:nodoc:
|
104
|
+
def inspect
|
105
|
+
"#<#{self.class.name} font=#{@font.full_name.inspect} id=#{id} #{str.inspect}>"
|
77
106
|
end
|
78
107
|
|
79
108
|
end
|
@@ -108,6 +137,16 @@ module HexaPDF
|
|
108
137
|
@encoded_glyphs = {}
|
109
138
|
end
|
110
139
|
|
140
|
+
# Returns the type of the font, i.e. :TrueType.
|
141
|
+
def font_type
|
142
|
+
:TrueType
|
143
|
+
end
|
144
|
+
|
145
|
+
# Returns the scaling factor for converting font units into PDF units.
|
146
|
+
def scaling_factor
|
147
|
+
@scaling_factor ||= 1000.0 / @wrapped_font[:head].units_per_em
|
148
|
+
end
|
149
|
+
|
111
150
|
# Returns +true+ if the wrapped TrueType font will be subset.
|
112
151
|
def subset?
|
113
152
|
!@subsetter.nil?
|
@@ -115,14 +154,18 @@ module HexaPDF
|
|
115
154
|
|
116
155
|
# Returns a Glyph object for the given glyph ID.
|
117
156
|
#
|
157
|
+
# The optional argument +str+ should be the string representation of the glyph. Only use it if
|
158
|
+
# it is known,
|
159
|
+
#
|
118
160
|
# Note: Although this method is public, it should normally not be used by application code!
|
119
|
-
def glyph(id)
|
161
|
+
def glyph(id, str = nil)
|
120
162
|
@id_to_glyph[id] ||=
|
121
163
|
begin
|
122
|
-
if id
|
123
|
-
id
|
164
|
+
if id >= 0 && id < @wrapped_font[:maxp].num_glyphs
|
165
|
+
Glyph.new(@wrapped_font, id, str || ('' << (@cmap.gid_to_code(id) || 0xFFFD)))
|
166
|
+
else
|
167
|
+
@document.config['font.on_missing_glyph'].call("\u{FFFD}", font_type, @wrapped_font)
|
124
168
|
end
|
125
|
-
Glyph.new(@wrapped_font, id)
|
126
169
|
end
|
127
170
|
end
|
128
171
|
|
@@ -131,8 +174,11 @@ module HexaPDF
|
|
131
174
|
str.each_codepoint.map do |c|
|
132
175
|
@codepoint_to_glyph[c] ||=
|
133
176
|
begin
|
134
|
-
gid = @cmap[c]
|
135
|
-
|
177
|
+
if (gid = @cmap[c])
|
178
|
+
glyph(gid, '' << c)
|
179
|
+
else
|
180
|
+
@document.config['font.on_missing_glyph'].call('' << c, font_type, @wrapped_font)
|
181
|
+
end
|
136
182
|
end
|
137
183
|
end
|
138
184
|
end
|
@@ -141,6 +187,9 @@ module HexaPDF
|
|
141
187
|
def encode(glyph)
|
142
188
|
@encoded_glyphs[glyph] ||=
|
143
189
|
begin
|
190
|
+
if glyph.kind_of?(InvalidGlyph)
|
191
|
+
raise HexaPDF::Error, "Glyph for #{glyph.str.inspect} missing"
|
192
|
+
end
|
144
193
|
if @subsetter
|
145
194
|
[@subsetter.use_glyph(glyph.id)].pack('n')
|
146
195
|
else
|
@@ -158,32 +207,30 @@ module HexaPDF
|
|
158
207
|
#
|
159
208
|
# See: #complete_font_dict
|
160
209
|
def build_font_dict
|
161
|
-
scaling = 1000.0 / @wrapped_font[:head].units_per_em
|
162
|
-
|
163
210
|
fd = @document.add(Type: :FontDescriptor,
|
164
211
|
FontName: @wrapped_font.font_name.intern,
|
165
212
|
FontWeight: @wrapped_font.weight,
|
166
213
|
Flags: 0,
|
167
|
-
FontBBox: @wrapped_font.bounding_box.map {|m| m *
|
214
|
+
FontBBox: @wrapped_font.bounding_box.map {|m| m * scaling_factor},
|
168
215
|
ItalicAngle: @wrapped_font.italic_angle || 0,
|
169
|
-
Ascent: @wrapped_font.ascender *
|
170
|
-
Descent: @wrapped_font.descender *
|
216
|
+
Ascent: @wrapped_font.ascender * scaling_factor,
|
217
|
+
Descent: @wrapped_font.descender * scaling_factor,
|
171
218
|
StemV: @wrapped_font.dominant_vertical_stem_width)
|
172
219
|
if @wrapped_font[:'OS/2'].version >= 2
|
173
|
-
fd[:CapHeight] = @wrapped_font.cap_height *
|
174
|
-
fd[:XHeight] = @wrapped_font.x_height *
|
220
|
+
fd[:CapHeight] = @wrapped_font.cap_height * scaling_factor
|
221
|
+
fd[:XHeight] = @wrapped_font.x_height * scaling_factor
|
175
222
|
else # estimate values
|
176
223
|
# Estimate as per https://www.microsoft.com/typography/otspec/os2.htm#ch
|
177
224
|
fd[:CapHeight] = if @cmap[0x0048] # H
|
178
|
-
@wrapped_font[:glyf][@cmap[0x0048]].y_max *
|
225
|
+
@wrapped_font[:glyf][@cmap[0x0048]].y_max * scaling_factor
|
179
226
|
else
|
180
|
-
@wrapped_font.ascender * 0.8 *
|
227
|
+
@wrapped_font.ascender * 0.8 * scaling_factor
|
181
228
|
end
|
182
229
|
# Estimate as per https://www.microsoft.com/typography/otspec/os2.htm#xh
|
183
230
|
fd[:XHeight] = if @cmap[0x0078] # x
|
184
|
-
@wrapped_font[:glyf][@cmap[0x0078]].y_max *
|
231
|
+
@wrapped_font[:glyf][@cmap[0x0078]].y_max * scaling_factor
|
185
232
|
else
|
186
|
-
@wrapped_font.ascender * 0.5 *
|
233
|
+
@wrapped_font.ascender * 0.5 * scaling_factor
|
187
234
|
end
|
188
235
|
end
|
189
236
|
|
@@ -247,7 +294,7 @@ module HexaPDF
|
|
247
294
|
|
248
295
|
# Adds the /DW and /W fields to the CIDFont dictionary.
|
249
296
|
def complete_width_information
|
250
|
-
default_width = glyph(3).width.to_i
|
297
|
+
default_width = glyph(3, " ").width.to_i
|
251
298
|
widths = @encoded_glyphs.keys.reject {|g| g.width == default_width}.map! do |glyph|
|
252
299
|
[(@subsetter ? @subsetter.subset_glyph_id(glyph.id) : glyph.id), glyph.width]
|
253
300
|
end.sort!
|
@@ -153,8 +153,9 @@ module HexaPDF
|
|
153
153
|
char.name = $3.to_sym
|
154
154
|
char.bbox = [$4.to_i, $5.to_i, $6.to_i, $7.to_i]
|
155
155
|
if $8
|
156
|
+
@metrics.ligature_pairs[char.name] = {}
|
156
157
|
$8.scan(/L (\S+) (\S+)/).each do |name, ligature|
|
157
|
-
char.
|
158
|
+
@metrics.ligature_pairs[char.name][name.to_sym] = ligature.to_sym
|
158
159
|
end
|
159
160
|
end
|
160
161
|
end
|
@@ -170,7 +171,7 @@ module HexaPDF
|
|
170
171
|
parse_integer.times do
|
171
172
|
read_line
|
172
173
|
if @line =~ /KPX (\S+) (\S+) (\S+)/
|
173
|
-
@metrics.kerning_pairs[$1][$2] = $3.to_i
|
174
|
+
(@metrics.kerning_pairs[$1.to_sym] ||= {})[$2.to_sym] = $3.to_i
|
174
175
|
end
|
175
176
|
end
|
176
177
|
end
|
@@ -51,15 +51,6 @@ module HexaPDF
|
|
51
51
|
# the lower-left corner and the x- and y-coordinates of the upper-right corner.
|
52
52
|
attr_accessor :bbox
|
53
53
|
|
54
|
-
# Mapping of possible ligatures. This character combined with the character specified by a
|
55
|
-
# key forms the ligature character stored as value of that key. Both keys and values are
|
56
|
-
# character names.
|
57
|
-
attr_accessor :ligatures
|
58
|
-
|
59
|
-
def initialize #:nodoc:
|
60
|
-
@ligatures = {}
|
61
|
-
end
|
62
|
-
|
63
54
|
end
|
64
55
|
|
65
56
|
end
|
@@ -32,6 +32,7 @@
|
|
32
32
|
#++
|
33
33
|
|
34
34
|
require 'forwardable'
|
35
|
+
require 'set'
|
35
36
|
require 'hexapdf/font/type1'
|
36
37
|
require 'hexapdf/font/encoding'
|
37
38
|
|
@@ -117,6 +118,16 @@ module HexaPDF
|
|
117
118
|
:'.notdef'
|
118
119
|
end
|
119
120
|
|
121
|
+
# Returns a set of features this font supports.
|
122
|
+
#
|
123
|
+
# For Type1 fonts, the features that may be available :kern and :liga.
|
124
|
+
def features
|
125
|
+
@features ||= Set.new.tap do |set|
|
126
|
+
set << :kern unless metrics.kerning_pairs.empty?
|
127
|
+
set << :liga unless metrics.ligature_pairs.empty?
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
120
131
|
end
|
121
132
|
|
122
133
|
end
|
@@ -105,9 +105,14 @@ module HexaPDF
|
|
105
105
|
# mapping from the second character name to the kerning amount.
|
106
106
|
attr_accessor :kerning_pairs
|
107
107
|
|
108
|
+
# Nested mapping of ligature pairs, ie. each key is a character name and each value is a
|
109
|
+
# mapping from the second character name to the ligature name.
|
110
|
+
attr_accessor :ligature_pairs
|
111
|
+
|
108
112
|
def initialize #:nodoc:
|
109
113
|
@character_metrics = {}
|
110
|
-
@kerning_pairs =
|
114
|
+
@kerning_pairs = {}
|
115
|
+
@ligature_pairs = {}
|
111
116
|
end
|
112
117
|
|
113
118
|
WEIGHT_NAME_TO_NUMBER = {'Bold' => 700, 'Medium' => 500, 'Roman' => 400}.freeze #:nodoc:
|
@@ -49,10 +49,34 @@ module HexaPDF
|
|
49
49
|
attr_reader :name
|
50
50
|
alias_method :id, :name
|
51
51
|
|
52
|
+
# The string representation of the glyph.
|
53
|
+
attr_reader :str
|
54
|
+
|
52
55
|
# Creates a new Glyph object.
|
53
|
-
def initialize(font, name)
|
56
|
+
def initialize(font, name, str)
|
54
57
|
@font = font
|
55
58
|
@name = name
|
59
|
+
@str = str
|
60
|
+
end
|
61
|
+
|
62
|
+
# Returns the glyph's minimum x coordinate.
|
63
|
+
def x_min
|
64
|
+
@font.metrics.character_metrics[name].bbox[0]
|
65
|
+
end
|
66
|
+
|
67
|
+
# Returns the glyph's maximum x coordinate.
|
68
|
+
def x_max
|
69
|
+
@font.metrics.character_metrics[name].bbox[2]
|
70
|
+
end
|
71
|
+
|
72
|
+
# Returns the glyph's minimum y coordinate.
|
73
|
+
def y_min
|
74
|
+
@font.metrics.character_metrics[name].bbox[1]
|
75
|
+
end
|
76
|
+
|
77
|
+
# Returns the glyph's maximum y coordinate.
|
78
|
+
def y_max
|
79
|
+
@font.metrics.character_metrics[name].bbox[3]
|
56
80
|
end
|
57
81
|
|
58
82
|
# Returns the width of the glyph.
|
@@ -60,11 +84,16 @@ module HexaPDF
|
|
60
84
|
@width ||= @font.width(name)
|
61
85
|
end
|
62
86
|
|
63
|
-
# Returns +true+ if the
|
64
|
-
def
|
87
|
+
# Returns +true+ if the word spacing parameter needs to be applied for the glyph.
|
88
|
+
def apply_word_spacing?
|
65
89
|
@name == :space
|
66
90
|
end
|
67
91
|
|
92
|
+
#:nodoc:
|
93
|
+
def inspect
|
94
|
+
"#<#{self.class.name} font=#{@font.full_name.inspect} id=#{name.inspect} #{str.inspect}>"
|
95
|
+
end
|
96
|
+
|
68
97
|
end
|
69
98
|
|
70
99
|
private_constant :Glyph
|
@@ -101,14 +130,26 @@ module HexaPDF
|
|
101
130
|
@encoded_glyphs = {}
|
102
131
|
end
|
103
132
|
|
133
|
+
# Returns the type of the font, i.e. :Type1.
|
134
|
+
def font_type
|
135
|
+
:Type1
|
136
|
+
end
|
137
|
+
|
138
|
+
# Returns 1 since all Type1 fonts use 1000 units for the em-square.
|
139
|
+
def scaling_factor
|
140
|
+
1
|
141
|
+
end
|
142
|
+
|
104
143
|
# Returns a Glyph object for the given glyph name.
|
105
144
|
def glyph(name)
|
106
145
|
@name_to_glyph[name] ||=
|
107
146
|
begin
|
108
|
-
|
109
|
-
|
147
|
+
str = Encoding::GlyphList.name_to_unicode(name, @zapf_dingbats_opt)
|
148
|
+
if @wrapped_font.metrics.character_metrics.key?(name)
|
149
|
+
Glyph.new(@wrapped_font, name, str)
|
150
|
+
else
|
151
|
+
@document.config['font.on_missing_glyph'].call(str, font_type, @wrapped_font)
|
110
152
|
end
|
111
|
-
Glyph.new(@wrapped_font, name)
|
112
153
|
end
|
113
154
|
end
|
114
155
|
|
@@ -118,7 +159,7 @@ module HexaPDF
|
|
118
159
|
@codepoint_to_glyph[c] ||=
|
119
160
|
begin
|
120
161
|
name = Encoding::GlyphList.unicode_to_name('' << c, @zapf_dingbats_opt)
|
121
|
-
name =
|
162
|
+
name = "u" << c.to_s(16).rjust(6, '0') if name == :'.notdef'
|
122
163
|
glyph(name)
|
123
164
|
end
|
124
165
|
end
|
@@ -128,6 +169,9 @@ module HexaPDF
|
|
128
169
|
def encode(glyph)
|
129
170
|
@encoded_glyphs[glyph.name] ||=
|
130
171
|
begin
|
172
|
+
if glyph.name == @wrapped_font.missing_glyph_id
|
173
|
+
raise HexaPDF::Error, "Glyph for #{glyph.str.inspect} missing"
|
174
|
+
end
|
131
175
|
code = @encoding.code_to_name.key(glyph.name)
|
132
176
|
if code
|
133
177
|
code.chr.freeze
|