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
@@ -115,6 +115,13 @@ module CommonTokenizerTests
115
115
  assert_raises(HexaPDF::MalformedPDFError) { @tokenizer.next_token }
116
116
  end
117
117
 
118
+ it "next_token: returns a PDF keyword for a solitary plus sign" do
119
+ create_tokenizer("+")
120
+ token = @tokenizer.next_token
121
+ assert_equal("+", token)
122
+ assert(token.kind_of?(HexaPDF::Tokenizer::Token))
123
+ end
124
+
118
125
  it "next_object: works for all PDF object types, including array and dictionary" do
119
126
  create_tokenizer(<<-EOF.chomp.gsub(/^ {8}/, ''))
120
127
  true false null 123 34.5 (string) <4E6F76> /Name
@@ -28,4 +28,12 @@ module TestHelper
28
28
 
29
29
  end
30
30
 
31
+ # Asserts that the content string contains the operators.
32
+ def assert_operators(content, operators, only_names: false)
33
+ processor = TestHelper::OperatorRecorder.new
34
+ HexaPDF::Content::Parser.new.parse(content, processor)
35
+ result = processor.recorded_ops
36
+ result.map!(&:first) if only_names
37
+ assert_equal(operators, result)
38
+ end
31
39
  end
@@ -9,22 +9,12 @@ require 'hexapdf/content/parser'
9
9
 
10
10
  describe HexaPDF::Content::Canvas do
11
11
  before do
12
- @processor = TestHelper::OperatorRecorder.new
13
- @parser = HexaPDF::Content::Parser.new
14
-
15
12
  @doc = HexaPDF::Document.new
16
13
  @doc.config['graphic_object.arc.max_curves'] = 4
17
14
  @page = @doc.pages.add
18
15
  @canvas = @page.canvas
19
16
  end
20
17
 
21
- # Asserts that the content string contains the operators.
22
- def assert_operators(content, operators)
23
- @processor.recorded_ops.clear
24
- @parser.parse(content, @processor)
25
- assert_equal(operators, @processor.recorded_ops)
26
- end
27
-
28
18
  # Asserts that a specific operator is invoked when the block is executed.
29
19
  def assert_operator_invoked(op, *args)
30
20
  mock = Minitest::Mock.new
@@ -638,10 +628,9 @@ describe HexaPDF::Content::Canvas do
638
628
 
639
629
  it "serializes correctly" do
640
630
  @canvas.circle(0, 0, 1)
641
- @processor.recorded_ops.clear
642
- @parser.parse(@canvas.contents, @processor)
643
- assert_equal([:move_to, :curve_to, :curve_to, :curve_to, :curve_to, :close_subpath],
644
- @processor.recorded_ops.map(&:first))
631
+ assert_operators(@canvas.contents,
632
+ [:move_to, :curve_to, :curve_to, :curve_to, :curve_to, :close_subpath],
633
+ only_names: true)
645
634
  end
646
635
 
647
636
  it "returns the canvas object" do
@@ -658,10 +647,9 @@ describe HexaPDF::Content::Canvas do
658
647
 
659
648
  it "serializes correctly" do
660
649
  @canvas.ellipse(0, 0, a: 10, b: 5, inclination: 10)
661
- @processor.recorded_ops.clear
662
- @parser.parse(@canvas.contents, @processor)
663
- assert_equal([:move_to, :curve_to, :curve_to, :curve_to, :curve_to, :close_subpath],
664
- @processor.recorded_ops.map(&:first))
650
+ assert_operators(@canvas.contents,
651
+ [:move_to, :curve_to, :curve_to, :curve_to, :curve_to, :close_subpath],
652
+ only_names: true)
665
653
  end
666
654
 
667
655
  it "returns the canvas object" do
@@ -916,8 +904,7 @@ describe HexaPDF::Content::Canvas do
916
904
  @canvas.begin_text
917
905
  @canvas.begin_text
918
906
  @canvas.begin_text(force_new: true)
919
- @parser.parse(@canvas.contents, @processor)
920
- assert_equal([:begin_text, :end_text, :begin_text], @processor.recorded_ops.map(&:first))
907
+ assert_operators(@canvas.contents, [:begin_text, :end_text, :begin_text], only_names: true)
921
908
  end
922
909
 
923
910
  it "returns the canvas object" do
@@ -947,8 +934,7 @@ describe HexaPDF::Content::Canvas do
947
934
  @canvas.begin_text
948
935
  @canvas.end_text
949
936
  @canvas.end_text
950
- @parser.parse(@page.contents, @processor)
951
- assert_equal([:begin_text, :end_text], @processor.recorded_ops.map(&:first))
937
+ assert_operators(@page.contents, [:begin_text, :end_text], only_names: true)
952
938
  end
953
939
 
954
940
  it "returns the canvas object" do
@@ -1014,6 +1000,8 @@ describe HexaPDF::Content::Canvas do
1014
1000
  assert_nil(@canvas.font)
1015
1001
  @canvas.font("Times", size: 10)
1016
1002
  assert_same(@doc.fonts.load("Times"), @canvas.font)
1003
+ @canvas.font(@canvas.font)
1004
+ assert_same(@doc.fonts.load("Times"), @canvas.font)
1017
1005
  @canvas.font("Helvetica", size: 10)
1018
1006
  assert_operators(@canvas.contents, [[:set_font_and_size, [:F1, 10]],
1019
1007
  [:set_leading, [12.0]],
@@ -99,7 +99,10 @@ describe HexaPDF::Content::Processor do
99
99
  @processor.resources = resources = Object.new
100
100
  @processor.resources.define_singleton_method(:xobject) do |_name|
101
101
  obj = {Matrix: [2, 0, 0, 2, 10, 10], Subtype: :Form}
102
- obj.define_singleton_method(:process_contents) {|processor| processor.process(:w, [10])}
102
+ obj.define_singleton_method(:process_contents) do |processor, original_resources:|
103
+ test_case.assert_same(resources, original_resources)
104
+ processor.process(:w, [10])
105
+ end
103
106
  obj
104
107
  end
105
108
 
@@ -20,6 +20,22 @@ describe HexaPDF::Document::Pages do
20
20
  assert_equal([page], @doc.pages.root[:Kids])
21
21
  end
22
22
 
23
+ it "adds a new empty page with the given dimensions" do
24
+ page = @doc.pages.add([0, 0, 20, 20])
25
+ assert_same(page, @doc.pages[0])
26
+ assert_equal([0, 0, 20, 20], @doc.pages[0].box(:media).value)
27
+ end
28
+
29
+ it "adds a new empty page with the given page format" do
30
+ page = @doc.pages.add(:A4)
31
+ assert_same(page, @doc.pages[0])
32
+ assert_equal([0, 0, 595, 842], @doc.pages[0].box(:media).value)
33
+ end
34
+
35
+ it "fails if an unknown page format is given" do
36
+ assert_raises(HexaPDF::Error) { @doc.pages.add(:A953) }
37
+ end
38
+
23
39
  it "adds a given page to the end" do
24
40
  page = @doc.pages.add
25
41
  new_page = @doc.add(Type: :Page)
@@ -0,0 +1,34 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/font/invalid_glyph'
5
+
6
+ describe HexaPDF::Font::InvalidGlyph do
7
+ before do
8
+ font = Object.new
9
+ font.define_singleton_method(:missing_glyph_id) { 0 }
10
+ font.define_singleton_method(:full_name) { "Test Roman" }
11
+ @glyph = HexaPDF::Font::InvalidGlyph.new(font, "str")
12
+ end
13
+
14
+ it "returns the missing glyph id for id/name" do
15
+ assert_equal(0, @glyph.id)
16
+ assert_equal(0, @glyph.name)
17
+ end
18
+
19
+ it "returns 0 for all glyph dimensions" do
20
+ assert_equal(0, @glyph.x_min)
21
+ assert_equal(0, @glyph.x_max)
22
+ assert_equal(0, @glyph.y_min)
23
+ assert_equal(0, @glyph.y_max)
24
+ end
25
+
26
+ it "doesn't allow the application of word spacing" do
27
+ refute(@glyph.apply_word_spacing?)
28
+ end
29
+
30
+ it "can represent itself for debug purposes" do
31
+ assert_equal('#<HexaPDF::Font::InvalidGlyph font="Test Roman" id=0 "str">',
32
+ @glyph.inspect)
33
+ end
34
+ end
@@ -35,24 +35,34 @@ describe HexaPDF::Font::TrueTypeWrapper do
35
35
  @font_wrapper.decode_utf8("Test").map {|g| @cmap.gid_to_code(g.id)}.pack('U*'))
36
36
  end
37
37
 
38
- it "UTF-8 characters for which no glyph exists are mapped to the .notdef glyph" do
39
- gotten = nil
40
- @doc.config['font.on_missing_glyph'] = proc {|c| gotten = c; 0 }
41
- assert_equal([0], @font_wrapper.decode_utf8("😁").map(&:id))
42
- assert_equal(128_513, gotten)
38
+ it "invokes font.on_missing_glyph for UTF-8 characters for which no glyph exists" do
39
+ glyphs = @font_wrapper.decode_utf8("😁")
40
+ assert_equal(1, glyphs.length)
41
+ assert_kind_of(HexaPDF::Font::InvalidGlyph, glyphs.first)
42
+ assert_equal('' << 128_513, glyphs.first.str)
43
43
  end
44
44
  end
45
45
 
46
46
  describe "glyph" do
47
47
  it "returns the glyph object for the given id" do
48
- glyph = @font_wrapper.glyph(3)
49
- assert_equal(3, glyph.id)
50
- assert_equal(338, glyph.width)
51
- assert(glyph.space?)
48
+ glyph = @font_wrapper.glyph(17)
49
+ assert_equal(17, glyph.id)
50
+ assert_equal("0", glyph.str)
51
+ assert_equal(628, glyph.width)
52
+ assert_equal(47, glyph.x_min)
53
+ assert_equal(0, glyph.y_min)
54
+ assert_equal(584, glyph.x_max)
55
+ assert_equal(696, glyph.y_max)
56
+ refute(glyph.apply_word_spacing?)
57
+ assert_equal('#<HexaPDF::Font::TrueTypeWrapper::Glyph font="Ubuntu-Title" id=17 "0">',
58
+ glyph.inspect)
52
59
  end
53
60
 
54
61
  it "invokes font.on_missing_glyph for missing glyphs" do
55
- assert_raises(HexaPDF::Error) { @font_wrapper.glyph(9999) }
62
+ glyph = @font_wrapper.glyph(9999)
63
+ assert_kind_of(HexaPDF::Font::InvalidGlyph, glyph)
64
+ assert_equal(0, glyph.id)
65
+ assert_equal('' << 0xFFFD, glyph.str)
56
66
  end
57
67
  end
58
68
 
@@ -71,6 +81,10 @@ describe HexaPDF::Font::TrueTypeWrapper do
71
81
  code = @font_wrapper.encode(@font_wrapper.glyph(10))
72
82
  assert_equal([10].pack('n'), code)
73
83
  end
84
+
85
+ it "raises an error if an InvalidGlyph is encoded" do
86
+ assert_raises(HexaPDF::Error) { @font_wrapper.encode(@font_wrapper.glyph(9999)) }
87
+ end
74
88
  end
75
89
 
76
90
  describe "creates the necessary PDF dictionaries" do
@@ -89,7 +103,7 @@ describe HexaPDF::Font::TrueTypeWrapper do
89
103
  assert_equal(1, dict[:DescendantFonts].length)
90
104
  assert_equal(dict[:BaseFont], dict[:DescendantFonts][0][:BaseFont])
91
105
  assert_equal(HexaPDF::Font::CMap.create_to_unicode_cmap([[1, ' '.ord], [2, 'H'.ord]]),
92
- dict[:ToUnicode].stream)
106
+ dict[:ToUnicode].stream)
93
107
  assert_match(/\A[A-Z]{6}\+Ubuntu-Title\z/, dict[:BaseFont])
94
108
 
95
109
  # Checking CIDFont dictionary
@@ -1,12 +1,10 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
 
3
3
  require 'test_helper'
4
+ require_relative 'type1/common'
4
5
  require 'hexapdf/font/type1_wrapper'
5
6
  require 'hexapdf/document'
6
7
 
7
- FONT_TIMES = HexaPDF::Font::Type1::Font.from_afm(File.join(HexaPDF.data_dir, 'afm', "Times-Roman.afm"))
8
- FONT_SYMBOL = HexaPDF::Font::Type1::Font.from_afm(File.join(HexaPDF.data_dir, 'afm', "Symbol.afm"))
9
-
10
8
  describe HexaPDF::Font::Type1Wrapper do
11
9
  before do
12
10
  @doc = HexaPDF::Document.new
@@ -14,16 +12,20 @@ describe HexaPDF::Font::Type1Wrapper do
14
12
  @symbol_wrapper = HexaPDF::Font::Type1Wrapper.new(@doc, FONT_SYMBOL)
15
13
  end
16
14
 
15
+ it "returns 1 for the scaling factor" do
16
+ assert_equal(1, @times_wrapper.scaling_factor)
17
+ end
18
+
17
19
  describe "decode_utf8" do
18
20
  it "returns an array of glyph objects" do
19
21
  assert_equal([:T, :e, :s, :t], @times_wrapper.decode_utf8("Test").map(&:name))
20
22
  end
21
23
 
22
- it "UTF-8 characters for which no glyph name exist are mapped to themselves" do
23
- gotten = nil
24
- @doc.config['font.on_missing_glyph'] = proc {|c| gotten = c; :A }
25
- assert_equal([:A], @times_wrapper.decode_utf8("😁").map(&:name))
26
- assert_equal("😁", gotten)
24
+ it "UTF-8 characters for which no glyph name exists, are mapped to InvalidGlyph objects" do
25
+ glyphs = @times_wrapper.decode_utf8("😁")
26
+ assert_equal(1, glyphs.length)
27
+ assert_kind_of(HexaPDF::Font::InvalidGlyph, glyphs.first)
28
+ assert_equal('' << 128_513, glyphs.first.str)
27
29
  end
28
30
  end
29
31
 
@@ -31,12 +33,22 @@ describe HexaPDF::Font::Type1Wrapper do
31
33
  it "returns the glyph object for the given name" do
32
34
  glyph = @times_wrapper.glyph(:A)
33
35
  assert_equal(:A, glyph.name)
36
+ assert_equal("A", glyph.str)
34
37
  assert_equal(722, glyph.width)
35
- refute(glyph.space?)
38
+ assert_equal(15, glyph.x_min)
39
+ assert_equal(0, glyph.y_min)
40
+ assert_equal(706, glyph.x_max)
41
+ assert_equal(674, glyph.y_max)
42
+ refute(glyph.apply_word_spacing?)
43
+ assert_equal('#<HexaPDF::Font::Type1Wrapper::Glyph font="Times Roman" id=:A "A">',
44
+ glyph.inspect)
36
45
  end
37
46
 
38
47
  it "invokes font.on_missing_glyph for missing glyphs" do
39
- assert_raises(HexaPDF::Error) { @times_wrapper.glyph(:ffi) }
48
+ glyph = @times_wrapper.glyph(:ffi)
49
+ assert_kind_of(HexaPDF::Font::InvalidGlyph, glyph)
50
+ assert_equal(:'.notdef', glyph.name)
51
+ assert_equal('ffi', glyph.str)
40
52
  end
41
53
  end
42
54
 
@@ -49,6 +61,10 @@ describe HexaPDF::Font::Type1Wrapper do
49
61
  assert_equal(:WinAnsiEncoding, @times_wrapper.dict[:Encoding])
50
62
  end
51
63
 
64
+ it "fails if an InvalidGlyph is encoded" do
65
+ assert_raises(HexaPDF::Error) { @times_wrapper.encode(@times_wrapper.glyph(:ffi)) }
66
+ end
67
+
52
68
  it "fails if the encoding does not support the given glyph" do
53
69
  assert_raises(HexaPDF::Error) { @times_wrapper.encode(@times_wrapper.glyph(:uring)) }
54
70
  end
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'stringio'
5
+ require 'hexapdf/font/true_type'
6
+
7
+ module TestHelper
8
+
9
+ def set_up_stub_true_type_font(initial_data = ''.b, register_vars: true)
10
+ font = Object.new
11
+ font.define_singleton_method(:io) { @io ||= StringIO.new(initial_data) }
12
+ font.define_singleton_method(:config) { @config ||= {} }
13
+ entry = HexaPDF::Font::TrueType::Table::Directory::Entry.new('mock', 0, 0, initial_data.length)
14
+ @font, @entry = font, entry if register_vars
15
+ [font, entry]
16
+ end
17
+
18
+ def create_table(name, data = nil, standalone: false)
19
+ font, entry = !standalone ? [@font, @entry] : set_up_stub_true_type_font(register_vars: false)
20
+ if data
21
+ font.io.string = data
22
+ entry.length = font.io.length
23
+ end
24
+ HexaPDF::Font::TrueType::Table.const_get(name).new(font, entry)
25
+ end
26
+
27
+ end
@@ -1,53 +1,47 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
 
3
3
  require 'test_helper'
4
- require 'stringio'
4
+ require_relative 'common'
5
5
  require 'hexapdf/font/true_type/table/cmap'
6
6
 
7
7
  describe HexaPDF::Font::TrueType::Table::Cmap do
8
8
  before do
9
9
  f0 = [0, 262, 0].pack('n3') + (0..255).to_a.pack('C*')
10
- data = [0, 3].pack('n2') << [[0, 1, 28],
11
- [3, 1, 28 + f0.length],
12
- [1, 0, 28],
13
- ].map {|a| a.pack('n2N')}.join('') << \
14
- f0 << f0
15
- io = StringIO.new(data)
16
- config = @config = {}
17
- @file = Object.new
18
- @file.define_singleton_method(:io) { io }
19
- @file.define_singleton_method(:config) { config }
20
- @entry = HexaPDF::Font::TrueType::Table::Directory::Entry.new('cmap', 0, 0, io.length)
10
+ data = [0, 3].pack('n2') << [
11
+ [0, 1, 28],
12
+ [3, 1, 28 + f0.length],
13
+ [1, 0, 28],
14
+ ].map {|a| a.pack('n2N')}.join('') << f0 << f0
15
+ set_up_stub_true_type_font(data)
21
16
  end
22
17
 
23
18
  describe "initialize" do
24
19
  it "reads the data from the associated file" do
25
- table = HexaPDF::Font::TrueType::Table::Cmap.new(@file, @entry)
20
+ table = create_table(:Cmap)
26
21
  assert_equal(0, table.version)
27
22
  assert_equal(3, table.tables.length)
28
23
  end
29
24
 
30
25
  it "ignores unknown subtable when the config option is set to :ignore" do
31
- @file.io.string = [0, 1].pack('n2') << [3, 1, 12].pack('n2N') << "\x00\x03"
32
- table = HexaPDF::Font::TrueType::Table::Cmap.new(@file, @entry)
26
+ table = create_table(:Cmap, [0, 1].pack('n2') << [3, 1, 12].pack('n2N') << "\x00\x03")
33
27
  assert_equal(0, table.tables.length)
34
28
  end
35
29
 
36
30
  it "raises an error when an unsupported subtable is found and the option is set to :raise" do
37
- @file.io.string = [0, 1].pack('n2') << [3, 1, 12].pack('n2N') << "\x00\x03"
38
- @file.config['font.true_type.cmap.unknown_format'] = :raise
39
- assert_raises(HexaPDF::Error) { HexaPDF::Font::TrueType::Table::Cmap.new(@file, @entry) }
31
+ data = [0, 1].pack('n2') << [3, 1, 12].pack('n2N') << "\x00\x03"
32
+ @font.config['font.true_type.unknown_format'] = :raise
33
+ assert_raises(HexaPDF::Error) { create_table(:Cmap, data) }
40
34
  end
41
35
 
42
36
  it "loads data from subtables with identical offsets only once" do
43
- table = HexaPDF::Font::TrueType::Table::Cmap.new(@file, @entry)
37
+ table = create_table(:Cmap)
44
38
  assert_same(table.tables[0].gid_map, table.tables[2].gid_map)
45
39
  refute_same(table.tables[0].gid_map, table.tables[1].gid_map)
46
40
  end
47
41
  end
48
42
 
49
43
  it "returns the preferred table" do
50
- table = HexaPDF::Font::TrueType::Table::Cmap.new(@file, @entry)
44
+ table = create_table(:Cmap)
51
45
  assert_equal(table.tables[1], table.preferred_table)
52
46
  end
53
47
  end
@@ -90,6 +90,13 @@ describe HexaPDF::Font::TrueType::Table::CmapSubtable do
90
90
  assert_equal(65535, t.gid_to_code(84))
91
91
  end
92
92
 
93
+ it "works for format 4 with invalid 0xffff entry" do
94
+ f4 = [2, 0, 0, 0, 65535, 0, 65535, 0, 32767].pack('n*')
95
+ t = table([4, f4.length + 6, 0].pack('n3') << f4)
96
+ assert_nil(t[65535])
97
+ assert_equal(65535, t.gid_to_code(0))
98
+ end
99
+
93
100
  it "works for format 6" do
94
101
  t = table(([6, 30, 0, 1024, 10] + (1..10).to_a).pack('n*'))
95
102
  assert_nil(t[0])
@@ -1,12 +1,11 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
 
3
3
  require 'test_helper'
4
- require 'stringio'
4
+ require_relative 'common'
5
5
  require 'hexapdf/font/true_type/table/glyf'
6
6
 
7
7
  describe HexaPDF::Font::TrueType::Table::Glyf do
8
8
  before do
9
- @file = Object.new
10
9
  loca = Object.new
11
10
  loca.define_singleton_method(:offsets) { @offsets ||= [] }
12
11
  loca.define_singleton_method(:offset) {|i| @offsets[i]}
@@ -21,17 +20,20 @@ describe HexaPDF::Font::TrueType::Table::Glyf do
21
20
  data << [0b10100001, 4, 20, 30, 40, 50, 60, 70].pack('n2n2n4')
22
21
  data << [0b00000000, 1, 20, 30].pack('n2C2')
23
22
  loca.offsets << data.size
24
- @file.define_singleton_method(:io) { @io ||= StringIO.new(data) }
25
- @file.define_singleton_method(:[]) {|_arg| loca }
26
- @entry = HexaPDF::Font::TrueType::Table::Directory::Entry.new('glyf', 0, 0, @file.io.length)
23
+ set_up_stub_true_type_font(data)
24
+ @font.define_singleton_method(:[]) {|_arg| loca }
27
25
  end
28
26
 
29
27
  describe "initialize" do
30
28
  it "reads the data from the associated file" do
31
- table = HexaPDF::Font::TrueType::Table::Glyf.new(@file, @entry)
29
+ table = create_table(:Glyf)
32
30
  glyph = table[0]
33
31
  refute(glyph.compound?)
34
32
  assert_equal(0, glyph.number_of_contours)
33
+ assert_equal(0, glyph.x_min)
34
+ assert_equal(0, glyph.y_min)
35
+ assert_equal(0, glyph.x_max)
36
+ assert_equal(0, glyph.y_max)
35
37
 
36
38
  glyph = table[1]
37
39
  refute(glyph.compound?)