hexapdf 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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