hexapdf 0.4.0 → 0.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
- 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
|