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,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?
|