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
@@ -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?)