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.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +46 -0
  3. data/CONTRIBUTERS +1 -1
  4. data/README.md +5 -5
  5. data/VERSION +1 -1
  6. data/examples/emoji-smile.png +0 -0
  7. data/examples/emoji-wink.png +0 -0
  8. data/examples/graphics.rb +9 -8
  9. data/examples/standard_pdf_fonts.rb +2 -1
  10. data/examples/text_box_alignment.rb +47 -0
  11. data/examples/text_box_inline_boxes.rb +56 -0
  12. data/examples/text_box_line_wrapping.rb +57 -0
  13. data/examples/text_box_shapes.rb +166 -0
  14. data/examples/text_box_styling.rb +72 -0
  15. data/examples/truetype.rb +3 -4
  16. data/lib/hexapdf/cli/optimize.rb +2 -2
  17. data/lib/hexapdf/configuration.rb +8 -6
  18. data/lib/hexapdf/content/canvas.rb +8 -5
  19. data/lib/hexapdf/content/parser.rb +3 -2
  20. data/lib/hexapdf/content/processor.rb +14 -3
  21. data/lib/hexapdf/document.rb +1 -0
  22. data/lib/hexapdf/document/fonts.rb +2 -1
  23. data/lib/hexapdf/document/pages.rb +23 -0
  24. data/lib/hexapdf/font/invalid_glyph.rb +78 -0
  25. data/lib/hexapdf/font/true_type/font.rb +14 -3
  26. data/lib/hexapdf/font/true_type/table.rb +1 -0
  27. data/lib/hexapdf/font/true_type/table/cmap.rb +1 -1
  28. data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +1 -0
  29. data/lib/hexapdf/font/true_type/table/glyf.rb +4 -0
  30. data/lib/hexapdf/font/true_type/table/kern.rb +170 -0
  31. data/lib/hexapdf/font/true_type/table/post.rb +5 -1
  32. data/lib/hexapdf/font/true_type_wrapper.rb +71 -24
  33. data/lib/hexapdf/font/type1/afm_parser.rb +3 -2
  34. data/lib/hexapdf/font/type1/character_metrics.rb +0 -9
  35. data/lib/hexapdf/font/type1/font.rb +11 -0
  36. data/lib/hexapdf/font/type1/font_metrics.rb +6 -1
  37. data/lib/hexapdf/font/type1_wrapper.rb +51 -7
  38. data/lib/hexapdf/font_loader/standard14.rb +1 -1
  39. data/lib/hexapdf/layout.rb +51 -0
  40. data/lib/hexapdf/layout/inline_box.rb +95 -0
  41. data/lib/hexapdf/layout/line_fragment.rb +333 -0
  42. data/lib/hexapdf/layout/numeric_refinements.rb +56 -0
  43. data/lib/hexapdf/layout/style.rb +365 -0
  44. data/lib/hexapdf/layout/text_box.rb +727 -0
  45. data/lib/hexapdf/layout/text_fragment.rb +206 -0
  46. data/lib/hexapdf/layout/text_shaper.rb +155 -0
  47. data/lib/hexapdf/task.rb +0 -1
  48. data/lib/hexapdf/task/dereference.rb +1 -1
  49. data/lib/hexapdf/tokenizer.rb +3 -2
  50. data/lib/hexapdf/type/font_descriptor.rb +2 -1
  51. data/lib/hexapdf/type/font_type0.rb +3 -1
  52. data/lib/hexapdf/type/form.rb +12 -4
  53. data/lib/hexapdf/version.rb +1 -1
  54. data/test/hexapdf/common_tokenizer_tests.rb +7 -0
  55. data/test/hexapdf/content/common.rb +8 -0
  56. data/test/hexapdf/content/test_canvas.rb +10 -22
  57. data/test/hexapdf/content/test_processor.rb +4 -1
  58. data/test/hexapdf/document/test_pages.rb +16 -0
  59. data/test/hexapdf/font/test_invalid_glyph.rb +34 -0
  60. data/test/hexapdf/font/test_true_type_wrapper.rb +25 -11
  61. data/test/hexapdf/font/test_type1_wrapper.rb +26 -10
  62. data/test/hexapdf/font/true_type/table/common.rb +27 -0
  63. data/test/hexapdf/font/true_type/table/test_cmap.rb +14 -20
  64. data/test/hexapdf/font/true_type/table/test_cmap_subtable.rb +7 -0
  65. data/test/hexapdf/font/true_type/table/test_glyf.rb +8 -6
  66. data/test/hexapdf/font/true_type/table/test_head.rb +9 -13
  67. data/test/hexapdf/font/true_type/table/test_hhea.rb +16 -23
  68. data/test/hexapdf/font/true_type/table/test_hmtx.rb +4 -7
  69. data/test/hexapdf/font/true_type/table/test_kern.rb +61 -0
  70. data/test/hexapdf/font/true_type/table/test_loca.rb +7 -13
  71. data/test/hexapdf/font/true_type/table/test_maxp.rb +4 -9
  72. data/test/hexapdf/font/true_type/table/test_name.rb +14 -17
  73. data/test/hexapdf/font/true_type/table/test_os2.rb +3 -5
  74. data/test/hexapdf/font/true_type/table/test_post.rb +21 -19
  75. data/test/hexapdf/font/true_type/test_font.rb +4 -0
  76. data/test/hexapdf/font/type1/common.rb +6 -0
  77. data/test/hexapdf/font/type1/test_afm_parser.rb +9 -0
  78. data/test/hexapdf/font/type1/test_font.rb +6 -0
  79. data/test/hexapdf/layout/test_inline_box.rb +40 -0
  80. data/test/hexapdf/layout/test_line_fragment.rb +206 -0
  81. data/test/hexapdf/layout/test_style.rb +143 -0
  82. data/test/hexapdf/layout/test_text_box.rb +640 -0
  83. data/test/hexapdf/layout/test_text_fragment.rb +208 -0
  84. data/test/hexapdf/layout/test_text_shaper.rb +64 -0
  85. data/test/hexapdf/task/test_dereference.rb +1 -0
  86. data/test/hexapdf/test_writer.rb +2 -2
  87. data/test/hexapdf/type/test_font_descriptor.rb +4 -2
  88. data/test/hexapdf/type/test_font_type0.rb +7 -0
  89. data/test/hexapdf/type/test_form.rb +12 -0
  90. 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
- raise HexaPDF::Error, "Unsupported post table format: #{@format}"
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 +true+ if the glyph represents the space character.
73
- def space?
74
- # Accoding to http://scripts.sil.org/iws-chapter08 and
75
- # https://www.microsoft.com/typography/otspec/recom.htm
76
- @id == 3
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 < 0 || id >= @wrapped_font[:maxp].num_glyphs
123
- id = @document.config['font.on_missing_glyph'].call(0xFFFD, @wrapped_font)
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] || @document.config['font.on_missing_glyph'].call(c, @wrapped_font)
135
- glyph(gid)
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 * scaling},
214
+ FontBBox: @wrapped_font.bounding_box.map {|m| m * scaling_factor},
168
215
  ItalicAngle: @wrapped_font.italic_angle || 0,
169
- Ascent: @wrapped_font.ascender * scaling,
170
- Descent: @wrapped_font.descender * scaling,
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 * scaling
174
- fd[:XHeight] = @wrapped_font.x_height * scaling
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 * scaling
225
+ @wrapped_font[:glyf][@cmap[0x0048]].y_max * scaling_factor
179
226
  else
180
- @wrapped_font.ascender * 0.8 * scaling
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 * scaling
231
+ @wrapped_font[:glyf][@cmap[0x0078]].y_max * scaling_factor
185
232
  else
186
- @wrapped_font.ascender * 0.5 * scaling
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.ligatures[name] = ligature
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 = Hash.new {|h, k| h[k] = {}}
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 glyph represents the space character.
64
- def space?
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
- unless @wrapped_font.metrics.character_metrics.key?(name)
109
- name = @document.config['font.on_missing_glyph'].call(name, @wrapped_font)
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 = '' << c if name == :'.notdef'
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