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,72 @@
|
|
1
|
+
# ## Text Box Styling
|
2
|
+
#
|
3
|
+
# The text used as part of a [HexaPDF::Layout::TextBox] class can be styled
|
4
|
+
# using [HexaPDF::Layout::Style]. To do this [HexaPDF::Layout::TextFragment]
|
5
|
+
# objects have to be created with the needed styling and then added to a text
|
6
|
+
# box. In addition the style objects can be used for styling text boxes
|
7
|
+
# themselves.
|
8
|
+
#
|
9
|
+
# This example shows how to do this and shows off the various styling option.
|
10
|
+
#
|
11
|
+
# Usage:
|
12
|
+
# : `ruby text_box_styling.rb`
|
13
|
+
#
|
14
|
+
|
15
|
+
require 'hexapdf'
|
16
|
+
|
17
|
+
# Wraps the text in a TextFragment using the given style.
|
18
|
+
def fragment(text, style)
|
19
|
+
HexaPDF::Layout::TextFragment.new(items: style.font.decode_utf8(text),
|
20
|
+
style: style)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Draws the text box at the given [x, y] position onto the canvas.
|
24
|
+
def draw_box(box, canvas, x, y)
|
25
|
+
rest, height = box.fit
|
26
|
+
raise "Error" unless rest.empty?
|
27
|
+
box.draw(canvas, x, y)
|
28
|
+
y - height
|
29
|
+
end
|
30
|
+
|
31
|
+
sample_text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit,
|
32
|
+
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
|
33
|
+
enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
|
34
|
+
aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit
|
35
|
+
in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
|
36
|
+
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui
|
37
|
+
officia deserunt mollit anim id est laborum. ".tr("\n", ' ') * 3
|
38
|
+
|
39
|
+
doc = HexaPDF::Document.new
|
40
|
+
|
41
|
+
heading = HexaPDF::Layout::Style.new(font: doc.fonts.load("Helvetica", variant: :bold),
|
42
|
+
font_size: 16, align: :center)
|
43
|
+
body = HexaPDF::Layout::Style.new(font: doc.fonts.load("Times"),
|
44
|
+
font_size: 10, align: :justify,
|
45
|
+
text_indent: 20)
|
46
|
+
body.line_spacing(:proportional, 1.5)
|
47
|
+
standout = HexaPDF::Layout::Style.new(font: doc.fonts.load("Times", variant: :bold),
|
48
|
+
font_size: 14)
|
49
|
+
|
50
|
+
canvas = doc.pages.add.canvas
|
51
|
+
y_base = 800
|
52
|
+
left = 50
|
53
|
+
width = 500
|
54
|
+
|
55
|
+
box = HexaPDF::Layout::TextBox.new(items: [fragment("This is a header", heading)],
|
56
|
+
width: width, style: heading)
|
57
|
+
y_base = draw_box(box, canvas, left, y_base)
|
58
|
+
|
59
|
+
box = HexaPDF::Layout::TextBox.new(items: [fragment(sample_text, body)],
|
60
|
+
width: width, style: body)
|
61
|
+
y_base = draw_box(box, canvas, left, y_base - 20)
|
62
|
+
|
63
|
+
items = [
|
64
|
+
fragment(sample_text[0, 50], body), fragment(sample_text[50, 15], standout),
|
65
|
+
fragment(sample_text[65, 150], body), fragment(sample_text[215, 50], standout),
|
66
|
+
fragment(sample_text[265...800], body), fragment(sample_text[800, 40], standout),
|
67
|
+
fragment(sample_text[840..-1], body)
|
68
|
+
]
|
69
|
+
box = HexaPDF::Layout::TextBox.new(items: items, width: width, style: body)
|
70
|
+
draw_box(box, canvas, left, y_base - 20)
|
71
|
+
|
72
|
+
doc.write("text_box_styling.pdf", optimize: true)
|
data/examples/truetype.rb
CHANGED
@@ -11,13 +11,12 @@
|
|
11
11
|
# usual.
|
12
12
|
#
|
13
13
|
# Usage:
|
14
|
-
# : `ruby truetype.
|
14
|
+
# : `ruby truetype.rb [FONT_FILE]`
|
15
15
|
#
|
16
16
|
|
17
17
|
require 'hexapdf'
|
18
18
|
|
19
19
|
doc = HexaPDF::Document.new
|
20
|
-
doc.config['font.on_missing_glyph'] = ->(_, f) { f.missing_glyph_id }
|
21
20
|
doc.config['font.map'] = {
|
22
21
|
'myfont' => {none: ARGV.shift || File.join(__dir__, '../test/data/fonts/Ubuntu-Title.ttf')},
|
23
22
|
}
|
@@ -26,7 +25,7 @@ wrapper = doc.fonts.load('myfont')
|
|
26
25
|
max_gid = wrapper.wrapped_font[:maxp].num_glyphs
|
27
26
|
|
28
27
|
255.times do |page|
|
29
|
-
break unless page * 256 <
|
28
|
+
break unless page * 256 < max_gid
|
30
29
|
canvas = doc.pages.add.canvas
|
31
30
|
canvas.font("Helvetica", size: 10)
|
32
31
|
canvas.text("Font: #{wrapper.wrapped_font.full_name}", at: [50, 825])
|
@@ -37,7 +36,7 @@ max_gid = wrapper.wrapped_font[:maxp].num_glyphs
|
|
37
36
|
canvas.show_glyphs((0..15).map do |i|
|
38
37
|
gid = page * 256 + y * 16 + i
|
39
38
|
glyph = wrapper.glyph(gid)
|
40
|
-
gid
|
39
|
+
gid >= max_gid ? [] : [glyph, -(2000 - glyph.width)]
|
41
40
|
end.flatten!)
|
42
41
|
end
|
43
42
|
end
|
data/lib/hexapdf/cli/optimize.rb
CHANGED
@@ -87,10 +87,10 @@ module HexaPDF
|
|
87
87
|
end
|
88
88
|
doc.catalog[:Pages] = page_tree
|
89
89
|
|
90
|
-
doc.each(current: false) do |obj|
|
90
|
+
doc.each(current: false) do |obj, revision|
|
91
91
|
next unless obj.kind_of?(HexaPDF::Dictionary)
|
92
92
|
if (obj.type == :Pages || obj.type == :Page) && !retained.key?(obj.data)
|
93
|
-
|
93
|
+
revision.delete(obj)
|
94
94
|
end
|
95
95
|
end
|
96
96
|
end
|
@@ -31,6 +31,7 @@
|
|
31
31
|
# is created or manipulated using HexaPDF.
|
32
32
|
#++
|
33
33
|
|
34
|
+
require 'hexapdf/font/invalid_glyph'
|
34
35
|
require 'hexapdf/error'
|
35
36
|
|
36
37
|
module HexaPDF
|
@@ -159,11 +160,12 @@ module HexaPDF
|
|
159
160
|
# font.on_missing_glyph::
|
160
161
|
# Callback hook when an UTF-8 character cannot be mapped to a glyph of a font.
|
161
162
|
#
|
162
|
-
# The value needs to be an object that responds to \#call(
|
163
|
-
# +
|
164
|
-
#
|
163
|
+
# The value needs to be an object that responds to \#call(character, font_type, font) where
|
164
|
+
# +character+ is the Unicode character for the missing glyph and returns a substitute glyph to
|
165
|
+
# be used instead.
|
165
166
|
#
|
166
|
-
# The default implementation
|
167
|
+
# The default implementation returns an object of class HexaPDF::Font::InvalidGlyph which, when
|
168
|
+
# not removed before encoding, will raise an error.
|
167
169
|
#
|
168
170
|
# font.on_missing_unicode_mapping::
|
169
171
|
# Callback hook when a character code point cannot be converted to a Unicode character.
|
@@ -233,8 +235,8 @@ module HexaPDF
|
|
233
235
|
DefaultDocumentConfiguration =
|
234
236
|
Configuration.new('document.auto_decrypt' => true,
|
235
237
|
'font.map' => {},
|
236
|
-
'font.on_missing_glyph' => proc do |
|
237
|
-
|
238
|
+
'font.on_missing_glyph' => proc do |char, _type, font|
|
239
|
+
HexaPDF::Font::InvalidGlyph.new(font, char)
|
238
240
|
end,
|
239
241
|
'font.on_missing_unicode_mapping' => proc do |code_point, font|
|
240
242
|
raise HexaPDF::Error, "No Unicode mapping for code point #{code_point} " \
|
@@ -1322,10 +1322,13 @@ module HexaPDF
|
|
1322
1322
|
# canvas.word_spacing(amount) => canvas
|
1323
1323
|
# canvas.word_spacing(amount) { block } => canvas
|
1324
1324
|
#
|
1325
|
-
#
|
1326
|
-
# character is encountered in a text. For horizontal
|
1327
|
-
# distance between two words, whereas for vertical
|
1328
|
-
# distance.
|
1325
|
+
# If the font's PDF encoding supports this, the word spacing determines how much additional
|
1326
|
+
# space is added when the ASCII space character is encountered in a text. For horizontal
|
1327
|
+
# writing positive values increase the distance between two words, whereas for vertical
|
1328
|
+
# writing negative values increase the distance.
|
1329
|
+
#
|
1330
|
+
# Note that in HexaPDF only the standard 14 PDF Type1 fonts support this property! When using
|
1331
|
+
# any other font, for example a TrueType font, this property has no effect.
|
1329
1332
|
#
|
1330
1333
|
# Returns the current word spacing value (see Content::GraphicsState#word_spacing) when no
|
1331
1334
|
# argument is given. Otherwise sets the word spacing using the +amount+ argument and returns
|
@@ -1609,7 +1612,7 @@ module HexaPDF
|
|
1609
1612
|
# See: PDF1.7 s9.2.2
|
1610
1613
|
def font(name = nil, size: nil, **options)
|
1611
1614
|
if name
|
1612
|
-
@font = context.document.fonts.load(name, options)
|
1615
|
+
@font = (name.respond_to?(:dict) ? name : context.document.fonts.load(name, options))
|
1613
1616
|
if size
|
1614
1617
|
font_size(size)
|
1615
1618
|
else
|
@@ -119,10 +119,11 @@ module HexaPDF
|
|
119
119
|
def parse_number
|
120
120
|
if (val = @ss.scan(/[+-]?\d++(?!\.)/))
|
121
121
|
val.to_i
|
122
|
-
|
123
|
-
val = @ss.scan(/[+-]?(?:\d+\.\d*|\.\d+)/)
|
122
|
+
elsif (val = @ss.scan(/[+-]?(?:\d+\.\d*|\.\d+)/))
|
124
123
|
val << '0'.freeze if val.getbyte(-1) == 46 # dot '.'
|
125
124
|
Float(val)
|
125
|
+
else
|
126
|
+
parse_keyword
|
126
127
|
end
|
127
128
|
end
|
128
129
|
|
@@ -305,7 +305,7 @@ module HexaPDF
|
|
305
305
|
attr_reader :operators
|
306
306
|
|
307
307
|
# The resources dictionary used during processing.
|
308
|
-
|
308
|
+
attr_reader :resources
|
309
309
|
|
310
310
|
# The GraphicsState object containing the current graphics state.
|
311
311
|
#
|
@@ -336,8 +336,19 @@ module HexaPDF
|
|
336
336
|
def initialize(resources = nil)
|
337
337
|
@operators = Operator::DEFAULT_OPERATORS.dup
|
338
338
|
@graphics_state = GraphicsState.new
|
339
|
-
@resources = resources
|
340
339
|
@graphics_object = :none
|
340
|
+
@original_resources = nil
|
341
|
+
self.resources = resources
|
342
|
+
end
|
343
|
+
|
344
|
+
# Sets the resources dictionary used during processing.
|
345
|
+
#
|
346
|
+
# The first time resources are set, they are also stored as the "original" resources. This is
|
347
|
+
# needed because form XObject don't need to have a resources dictionary and can use the page's
|
348
|
+
# resources dictionary instead.
|
349
|
+
def resources=(res)
|
350
|
+
@original_resources = res if @original_resources.nil?
|
351
|
+
@resources = res
|
341
352
|
end
|
342
353
|
|
343
354
|
# Processes the operator with the given operands.
|
@@ -364,7 +375,7 @@ module HexaPDF
|
|
364
375
|
graphics_state.save
|
365
376
|
|
366
377
|
graphics_state.ctm.premultiply(*xobject[:Matrix]) if xobject.key?(:Matrix)
|
367
|
-
xobject.process_contents(self)
|
378
|
+
xobject.process_contents(self, original_resources: @original_resources)
|
368
379
|
|
369
380
|
graphics_state.restore
|
370
381
|
self.resources = res
|
data/lib/hexapdf/document.rb
CHANGED
@@ -67,7 +67,8 @@ module HexaPDF
|
|
67
67
|
if font
|
68
68
|
@loaded_fonts_cache[[name, options]] = font
|
69
69
|
else
|
70
|
-
raise HexaPDF::Error, "The requested font '#{name}'
|
70
|
+
raise HexaPDF::Error, "The requested font '#{name}' in variant '#{options[:variant]}' " \
|
71
|
+
"couldn't be found"
|
71
72
|
end
|
72
73
|
end
|
73
74
|
|
@@ -31,6 +31,8 @@
|
|
31
31
|
# is created or manipulated using HexaPDF.
|
32
32
|
#++
|
33
33
|
|
34
|
+
require 'hexapdf/error'
|
35
|
+
|
34
36
|
module HexaPDF
|
35
37
|
class Document
|
36
38
|
|
@@ -52,8 +54,29 @@ module HexaPDF
|
|
52
54
|
@document.catalog.pages
|
53
55
|
end
|
54
56
|
|
57
|
+
# :call-seq:
|
58
|
+
# pages.add -> new_page
|
59
|
+
# pages.add(media_box) -> new_page
|
60
|
+
# pages.add(page) -> page
|
61
|
+
#
|
55
62
|
# Adds the page or a new empty page at the end and returns it.
|
63
|
+
#
|
64
|
+
# If no argument is given, a new page with the default dimensions (see configuration option
|
65
|
+
# 'page.default_media_box') is used. If the single argument is an array with four numbers
|
66
|
+
# (specifying the media box) or a symbol (referencing a pre-defined media box, see
|
67
|
+
# HexaPDF::Type::Page::PAPER_SIZE), the new page will have these dimensions.
|
56
68
|
def add(page = nil)
|
69
|
+
case page
|
70
|
+
when Array
|
71
|
+
page = @document.add(Type: :Page, MediaBox: page)
|
72
|
+
when Symbol
|
73
|
+
if Type::Page::PAPER_SIZE.key?(page)
|
74
|
+
media_box = Type::Page::PAPER_SIZE[page].dup
|
75
|
+
page = @document.add(Type: :Page, MediaBox: media_box)
|
76
|
+
else
|
77
|
+
raise HexaPDF::Error, "Invalid page format specified: #{page}"
|
78
|
+
end
|
79
|
+
end
|
57
80
|
@document.catalog.pages.add_page(page)
|
58
81
|
end
|
59
82
|
|
@@ -0,0 +1,78 @@
|
|
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
|
+
module HexaPDF
|
35
|
+
module Font
|
36
|
+
|
37
|
+
# Represents an invalid glyph, i.e. a Unicode character that has no representation in the used
|
38
|
+
# font.
|
39
|
+
class InvalidGlyph
|
40
|
+
|
41
|
+
# The string that could not be represented as a glyph.
|
42
|
+
attr_reader :str
|
43
|
+
|
44
|
+
# Creates a new Glyph object.
|
45
|
+
def initialize(font, str)
|
46
|
+
@font = font
|
47
|
+
@str = str
|
48
|
+
end
|
49
|
+
|
50
|
+
# Returns the appropriate missing glyph id based on the used font.
|
51
|
+
def id
|
52
|
+
@font.missing_glyph_id
|
53
|
+
end
|
54
|
+
alias_method :name, :id
|
55
|
+
|
56
|
+
# Returns 0.
|
57
|
+
def x_min
|
58
|
+
0
|
59
|
+
end
|
60
|
+
alias_method :x_max, :x_min
|
61
|
+
alias_method :y_min, :x_min
|
62
|
+
alias_method :y_max, :x_min
|
63
|
+
alias_method :width, :x_min
|
64
|
+
|
65
|
+
# Word spacing is never applied for the invalid glyph, so +false+ is returned.
|
66
|
+
def apply_word_spacing?
|
67
|
+
false
|
68
|
+
end
|
69
|
+
|
70
|
+
#:nodoc:
|
71
|
+
def inspect
|
72
|
+
"#<#{self.class.name} font=#{@font.full_name.inspect} id=#{id} #{@str.inspect}>"
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
@@ -32,6 +32,7 @@
|
|
32
32
|
#++
|
33
33
|
|
34
34
|
require 'hexapdf/font/true_type/table'
|
35
|
+
require 'set'
|
35
36
|
|
36
37
|
module HexaPDF
|
37
38
|
module Font
|
@@ -45,8 +46,8 @@ module HexaPDF
|
|
45
46
|
# font.ttf.table_mapping::
|
46
47
|
# The default mapping from table tag as symbol to table class name.
|
47
48
|
#
|
48
|
-
# font.ttf.
|
49
|
-
# Action to take when encountering unknown
|
49
|
+
# font.ttf.unknown_format::
|
50
|
+
# Action to take when encountering unknown subtables. Can either be :ignore
|
50
51
|
# which ignores them or :raise which raises an error.
|
51
52
|
DEFAULT_CONFIG = {
|
52
53
|
'font.true_type.table_mapping' => {
|
@@ -60,8 +61,9 @@ module HexaPDF
|
|
60
61
|
post: 'HexaPDF::Font::TrueType::Table::Post',
|
61
62
|
glyf: 'HexaPDF::Font::TrueType::Table::Glyf',
|
62
63
|
'OS/2': 'HexaPDF::Font::TrueType::Table::OS2',
|
64
|
+
kern: 'HexaPDF::Font::TrueType::Table::Kern',
|
63
65
|
},
|
64
|
-
'font.true_type.
|
66
|
+
'font.true_type.unknown_format' => :ignore,
|
65
67
|
}
|
66
68
|
|
67
69
|
|
@@ -93,6 +95,15 @@ module HexaPDF
|
|
93
95
|
@directory ||= Table::Directory.new(self, io ? Table::Directory::SELF_ENTRY : nil)
|
94
96
|
end
|
95
97
|
|
98
|
+
# Returns a set of features this font supports.
|
99
|
+
#
|
100
|
+
# Features that may be available are for example :kern or :liga.
|
101
|
+
def features
|
102
|
+
@features ||= Set.new.tap do |set|
|
103
|
+
set << :kern if self[:kern]&.horizontal_kerning_subtable
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
96
107
|
# Returns the PostScript font name.
|
97
108
|
def font_name
|
98
109
|
self[:name][:postscript_name].preferred_record
|
@@ -53,6 +53,7 @@ module HexaPDF
|
|
53
53
|
autoload(:Post, 'hexapdf/font/true_type/table/post')
|
54
54
|
autoload(:Glyf, 'hexapdf/font/true_type/table/glyf')
|
55
55
|
autoload(:OS2, 'hexapdf/font/true_type/table/os2')
|
56
|
+
autoload(:Kern, 'hexapdf/font/true_type/table/kern')
|
56
57
|
|
57
58
|
|
58
59
|
# The time Epoch used in sfnt-formatted font files.
|
@@ -64,7 +64,7 @@ module HexaPDF
|
|
64
64
|
def parse_table #:nodoc:
|
65
65
|
@version, num_tables = read_formatted(4, 'n2')
|
66
66
|
@tables = []
|
67
|
-
handle_unknown = font.config['font.true_type.
|
67
|
+
handle_unknown = font.config['font.true_type.unknown_format']
|
68
68
|
|
69
69
|
num_tables.times { @tables << read_formatted(8, 'n2N') }
|
70
70
|
offset_map = {}
|
@@ -267,6 +267,7 @@ module HexaPDF
|
|
267
267
|
offset = id_range_offsets[index]
|
268
268
|
if offset != 0
|
269
269
|
glyph_id = glyph_indexes[offset - end_codes.length + (code - start_codes[index])]
|
270
|
+
glyph_id ||= 0 # Handle invalid subtable entries
|
270
271
|
glyph_id = (glyph_id + id_deltas[index]) % 65536 if glyph_id != 0
|
271
272
|
else
|
272
273
|
glyph_id = (code + id_deltas[index]) % 65536
|
@@ -82,6 +82,10 @@ module HexaPDF
|
|
82
82
|
@raw_data = raw_data
|
83
83
|
@number_of_contours, @x_min, @y_min, @x_max, @y_max = @raw_data.unpack('s>5')
|
84
84
|
@number_of_contours ||= 0
|
85
|
+
@x_min ||= 0
|
86
|
+
@y_min ||= 0
|
87
|
+
@x_max ||= 0
|
88
|
+
@y_max ||= 0
|
85
89
|
@components = nil
|
86
90
|
@component_offsets = nil
|
87
91
|
parse_compound_glyph if compound?
|