hexapdf 0.2.0 → 0.3.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 (173) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +33 -1
  3. data/CONTRIBUTERS +1 -1
  4. data/LICENSE +1 -1
  5. data/Rakefile +1 -1
  6. data/VERSION +1 -1
  7. data/lib/hexapdf.rb +1 -1
  8. data/lib/hexapdf/cli.rb +19 -52
  9. data/lib/hexapdf/cli/command.rb +251 -0
  10. data/lib/hexapdf/cli/{extract.rb → files.rb} +19 -23
  11. data/lib/hexapdf/cli/images.rb +147 -0
  12. data/lib/hexapdf/cli/info.rb +5 -5
  13. data/lib/hexapdf/cli/inspect.rb +13 -12
  14. data/lib/hexapdf/cli/merge.rb +200 -0
  15. data/lib/hexapdf/cli/modify.rb +39 -242
  16. data/lib/hexapdf/cli/optimize.rb +104 -0
  17. data/lib/hexapdf/configuration.rb +1 -1
  18. data/lib/hexapdf/content.rb +1 -1
  19. data/lib/hexapdf/content/canvas.rb +1 -1
  20. data/lib/hexapdf/content/color_space.rb +1 -1
  21. data/lib/hexapdf/content/graphic_object.rb +1 -1
  22. data/lib/hexapdf/content/graphic_object/arc.rb +1 -1
  23. data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +1 -1
  24. data/lib/hexapdf/content/graphic_object/solid_arc.rb +1 -1
  25. data/lib/hexapdf/content/graphics_state.rb +1 -1
  26. data/lib/hexapdf/content/operator.rb +1 -1
  27. data/lib/hexapdf/content/parser.rb +16 -15
  28. data/lib/hexapdf/content/processor.rb +1 -1
  29. data/lib/hexapdf/content/transformation_matrix.rb +1 -1
  30. data/lib/hexapdf/data_dir.rb +1 -1
  31. data/lib/hexapdf/dictionary.rb +1 -1
  32. data/lib/hexapdf/dictionary_fields.rb +1 -1
  33. data/lib/hexapdf/document.rb +1 -1
  34. data/lib/hexapdf/document/files.rb +1 -1
  35. data/lib/hexapdf/document/fonts.rb +1 -1
  36. data/lib/hexapdf/document/images.rb +1 -1
  37. data/lib/hexapdf/document/pages.rb +1 -1
  38. data/lib/hexapdf/encryption.rb +1 -1
  39. data/lib/hexapdf/encryption/aes.rb +1 -1
  40. data/lib/hexapdf/encryption/arc4.rb +1 -1
  41. data/lib/hexapdf/encryption/fast_aes.rb +1 -1
  42. data/lib/hexapdf/encryption/fast_arc4.rb +1 -1
  43. data/lib/hexapdf/encryption/identity.rb +1 -1
  44. data/lib/hexapdf/encryption/ruby_aes.rb +1 -1
  45. data/lib/hexapdf/encryption/ruby_arc4.rb +1 -1
  46. data/lib/hexapdf/encryption/security_handler.rb +1 -1
  47. data/lib/hexapdf/encryption/standard_security_handler.rb +1 -1
  48. data/lib/hexapdf/error.rb +1 -1
  49. data/lib/hexapdf/filter.rb +1 -1
  50. data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
  51. data/lib/hexapdf/filter/ascii_hex_decode.rb +1 -1
  52. data/lib/hexapdf/filter/dct_decode.rb +1 -1
  53. data/lib/hexapdf/filter/encryption.rb +1 -1
  54. data/lib/hexapdf/filter/flate_decode.rb +1 -1
  55. data/lib/hexapdf/filter/jpx_decode.rb +1 -1
  56. data/lib/hexapdf/filter/lzw_decode.rb +2 -3
  57. data/lib/hexapdf/filter/predictor.rb +11 -11
  58. data/lib/hexapdf/filter/run_length_decode.rb +1 -1
  59. data/lib/hexapdf/font/cmap.rb +1 -1
  60. data/lib/hexapdf/font/cmap/parser.rb +1 -1
  61. data/lib/hexapdf/font/cmap/writer.rb +1 -1
  62. data/lib/hexapdf/font/encoding.rb +1 -1
  63. data/lib/hexapdf/font/encoding/base.rb +1 -1
  64. data/lib/hexapdf/font/encoding/difference_encoding.rb +1 -1
  65. data/lib/hexapdf/font/encoding/glyph_list.rb +1 -1
  66. data/lib/hexapdf/font/encoding/mac_expert_encoding.rb +1 -1
  67. data/lib/hexapdf/font/encoding/mac_roman_encoding.rb +1 -1
  68. data/lib/hexapdf/font/encoding/standard_encoding.rb +1 -1
  69. data/lib/hexapdf/font/encoding/symbol_encoding.rb +1 -1
  70. data/lib/hexapdf/font/encoding/win_ansi_encoding.rb +1 -1
  71. data/lib/hexapdf/font/encoding/zapf_dingbats_encoding.rb +1 -1
  72. data/lib/hexapdf/font/true_type.rb +2 -1
  73. data/lib/hexapdf/font/true_type/font.rb +1 -1
  74. data/lib/hexapdf/font/true_type/subsetter.rb +186 -0
  75. data/lib/hexapdf/font/true_type/table.rb +8 -4
  76. data/lib/hexapdf/font/true_type/table/cmap.rb +1 -1
  77. data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +1 -1
  78. data/lib/hexapdf/font/true_type/table/directory.rb +1 -1
  79. data/lib/hexapdf/font/true_type/table/glyf.rb +6 -2
  80. data/lib/hexapdf/font/true_type/table/head.rb +2 -2
  81. data/lib/hexapdf/font/true_type/table/hhea.rb +1 -1
  82. data/lib/hexapdf/font/true_type/table/hmtx.rb +1 -1
  83. data/lib/hexapdf/font/true_type/table/loca.rb +1 -1
  84. data/lib/hexapdf/font/true_type/table/maxp.rb +1 -1
  85. data/lib/hexapdf/font/true_type/table/name.rb +1 -1
  86. data/lib/hexapdf/font/true_type/table/os2.rb +1 -1
  87. data/lib/hexapdf/font/true_type/table/post.rb +1 -1
  88. data/lib/hexapdf/font/true_type_wrapper.rb +56 -8
  89. data/lib/hexapdf/font/type1.rb +1 -1
  90. data/lib/hexapdf/font/type1/afm_parser.rb +1 -1
  91. data/lib/hexapdf/font/type1/character_metrics.rb +1 -1
  92. data/lib/hexapdf/font/type1/font.rb +1 -1
  93. data/lib/hexapdf/font/type1/font_metrics.rb +1 -1
  94. data/lib/hexapdf/font/type1/pfb_parser.rb +1 -1
  95. data/lib/hexapdf/font/type1_wrapper.rb +1 -1
  96. data/lib/hexapdf/font_loader.rb +1 -1
  97. data/lib/hexapdf/font_loader/from_configuration.rb +6 -3
  98. data/lib/hexapdf/font_loader/standard14.rb +1 -1
  99. data/lib/hexapdf/image_loader.rb +1 -1
  100. data/lib/hexapdf/image_loader/jpeg.rb +1 -1
  101. data/lib/hexapdf/image_loader/pdf.rb +1 -1
  102. data/lib/hexapdf/image_loader/png.rb +1 -1
  103. data/lib/hexapdf/importer.rb +1 -1
  104. data/lib/hexapdf/name_tree_node.rb +1 -1
  105. data/lib/hexapdf/number_tree_node.rb +1 -1
  106. data/lib/hexapdf/object.rb +1 -1
  107. data/lib/hexapdf/parser.rb +1 -1
  108. data/lib/hexapdf/rectangle.rb +1 -1
  109. data/lib/hexapdf/reference.rb +1 -1
  110. data/lib/hexapdf/revision.rb +1 -1
  111. data/lib/hexapdf/revisions.rb +13 -15
  112. data/lib/hexapdf/serializer.rb +7 -3
  113. data/lib/hexapdf/stream.rb +1 -1
  114. data/lib/hexapdf/task.rb +1 -1
  115. data/lib/hexapdf/task/dereference.rb +1 -1
  116. data/lib/hexapdf/task/optimize.rb +1 -1
  117. data/lib/hexapdf/tokenizer.rb +12 -12
  118. data/lib/hexapdf/type.rb +1 -1
  119. data/lib/hexapdf/type/catalog.rb +1 -1
  120. data/lib/hexapdf/type/embedded_file.rb +1 -1
  121. data/lib/hexapdf/type/file_specification.rb +1 -1
  122. data/lib/hexapdf/type/font.rb +1 -1
  123. data/lib/hexapdf/type/font_descriptor.rb +1 -1
  124. data/lib/hexapdf/type/font_simple.rb +1 -1
  125. data/lib/hexapdf/type/font_true_type.rb +1 -1
  126. data/lib/hexapdf/type/font_type1.rb +1 -1
  127. data/lib/hexapdf/type/form.rb +1 -1
  128. data/lib/hexapdf/type/graphics_state_parameter.rb +1 -1
  129. data/lib/hexapdf/type/image.rb +187 -1
  130. data/lib/hexapdf/type/info.rb +1 -1
  131. data/lib/hexapdf/type/names.rb +1 -1
  132. data/lib/hexapdf/type/object_stream.rb +1 -1
  133. data/lib/hexapdf/type/page.rb +1 -1
  134. data/lib/hexapdf/type/page_tree_node.rb +6 -1
  135. data/lib/hexapdf/type/resources.rb +1 -1
  136. data/lib/hexapdf/type/trailer.rb +2 -2
  137. data/lib/hexapdf/type/viewer_preferences.rb +1 -1
  138. data/lib/hexapdf/type/xref_stream.rb +22 -18
  139. data/lib/hexapdf/utils/bit_field.rb +1 -1
  140. data/lib/hexapdf/utils/bit_stream.rb +16 -32
  141. data/lib/hexapdf/utils/lru_cache.rb +1 -1
  142. data/lib/hexapdf/utils/math_helpers.rb +1 -1
  143. data/lib/hexapdf/utils/object_hash.rb +1 -1
  144. data/lib/hexapdf/utils/pdf_doc_encoding.rb +1 -1
  145. data/lib/hexapdf/utils/sorted_tree_node.rb +1 -1
  146. data/lib/hexapdf/version.rb +2 -2
  147. data/lib/hexapdf/writer.rb +2 -1
  148. data/lib/hexapdf/xref_section.rb +6 -1
  149. data/man/man1/hexapdf.1 +194 -115
  150. data/test/data/images/greyscale-1bit.png +0 -0
  151. data/test/data/images/greyscale-2bit.png +0 -0
  152. data/test/data/images/greyscale-8bit.png +0 -0
  153. data/test/data/images/indexed-alpha-4bit.png +0 -0
  154. data/test/data/images/truecolour-8bit.png +0 -0
  155. data/test/hexapdf/content/test_operator.rb +8 -8
  156. data/test/hexapdf/content/test_processor.rb +1 -1
  157. data/test/hexapdf/encryption/test_security_handler.rb +1 -1
  158. data/test/hexapdf/font/test_true_type_wrapper.rb +89 -48
  159. data/test/hexapdf/font/true_type/table/test_glyf.rb +1 -0
  160. data/test/hexapdf/font/true_type/test_subsetter.rb +70 -0
  161. data/test/hexapdf/font/true_type/test_table.rb +16 -0
  162. data/test/hexapdf/font_loader/test_from_configuration.rb +7 -0
  163. data/test/hexapdf/test_document.rb +1 -1
  164. data/test/hexapdf/test_object.rb +1 -1
  165. data/test/hexapdf/test_revisions.rb +34 -8
  166. data/test/hexapdf/test_serializer.rb +3 -0
  167. data/test/hexapdf/test_writer.rb +11 -2
  168. data/test/hexapdf/test_xref_section.rb +15 -0
  169. data/test/hexapdf/type/test_image.rb +234 -0
  170. data/test/hexapdf/type/test_object_stream.rb +2 -2
  171. data/test/hexapdf/type/test_trailer.rb +4 -0
  172. data/test/hexapdf/utils/test_bit_stream.rb +69 -0
  173. metadata +14 -6
@@ -521,8 +521,8 @@ describe_operator :MoveTextAndSetLeading, :TD do
521
521
  @processor.operators[:Td] = td
522
522
 
523
523
  invoke(1.56, 1.78)
524
- tl.verify
525
- td.verify
524
+ assert(tl.verify)
525
+ assert(td.verify)
526
526
  end
527
527
 
528
528
  it "serializes correctly" do
@@ -554,7 +554,7 @@ describe_operator :MoveTextNextLine, :'T*' do
554
554
 
555
555
  @processor.graphics_state.leading = 1.78
556
556
  invoke
557
- td.verify
557
+ assert(td.verify)
558
558
  end
559
559
  end
560
560
 
@@ -576,8 +576,8 @@ describe_operator :MoveTextNextLineAndShowText, :"'" do
576
576
  @processor.operators[:Tj] = tj
577
577
 
578
578
  invoke(text)
579
- tstar.verify
580
- tj.verify
579
+ assert(tstar.verify)
580
+ assert(tj.verify)
581
581
  end
582
582
 
583
583
  it "serializes correctly" do
@@ -602,9 +602,9 @@ describe_operator :SetSpacingMoveTextNextLineAndShowText, :'"' do
602
602
  @processor.operators[:"'"] = tapos
603
603
 
604
604
  invoke(word_spacing, char_spacing, text)
605
- tw.verify
606
- tc.verify
607
- tapos.verify
605
+ assert(tw.verify)
606
+ assert(tc.verify)
607
+ assert(tapos.verify)
608
608
  end
609
609
 
610
610
  it "serializes correctly" do
@@ -76,7 +76,7 @@ describe HexaPDF::Content::Processor do
76
76
  op.expect(:invoke, nil, [@processor, :arg])
77
77
  @processor.operators[:test] = op
78
78
  @processor.process(:test, [:arg])
79
- op.verify
79
+ assert(op.verify)
80
80
  end
81
81
 
82
82
  it "invokes the mapped message name" do
@@ -138,7 +138,7 @@ describe HexaPDF::Encryption::SecurityHandler do
138
138
  [[40, nil], [48, 48], [128, 128], [256, nil]].each do |key_length, result|
139
139
  algorithm = (key_length == 256 ? :aes : :arc4)
140
140
  @handler.set_up_encryption(key_length: key_length, algorithm: algorithm)
141
- assert_equal(result, @handler.dict[:Length])
141
+ assert(result == @handler.dict[:Length])
142
142
  end
143
143
  end
144
144
 
@@ -7,8 +7,8 @@ require 'hexapdf/document'
7
7
  describe HexaPDF::Font::TrueTypeWrapper do
8
8
  before do
9
9
  @doc = HexaPDF::Document.new
10
- font_file = File.join(TEST_DATA_DIR, "fonts", "Ubuntu-Title.ttf")
11
- @font = HexaPDF::Font::TrueType::Font.new(File.open(font_file))
10
+ @font_file = File.join(TEST_DATA_DIR, "fonts", "Ubuntu-Title.ttf")
11
+ @font = HexaPDF::Font::TrueType::Font.new(File.open(@font_file))
12
12
  @cmap = @font[:cmap].preferred_table
13
13
  @font_wrapper = HexaPDF::Font::TrueTypeWrapper.new(@doc, @font)
14
14
  end
@@ -24,6 +24,11 @@ describe HexaPDF::Font::TrueTypeWrapper do
24
24
  end
25
25
  end
26
26
 
27
+ it "can be asked whether font wil be subset" do
28
+ assert(@font_wrapper.subset?)
29
+ refute(HexaPDF::Font::TrueTypeWrapper.new(@doc, @font, subset: false).subset?)
30
+ end
31
+
27
32
  describe "decode_utf8" do
28
33
  it "returns an array of glyph objects" do
29
34
  assert_equal("Test",
@@ -34,7 +39,7 @@ describe HexaPDF::Font::TrueTypeWrapper do
34
39
  gotten = nil
35
40
  @doc.config['font.on_missing_glyph'] = proc {|c| gotten = c; 0 }
36
41
  assert_equal([0], @font_wrapper.decode_utf8("😁").map(&:id))
37
- assert_equal(128513, gotten)
42
+ assert_equal(128_513, gotten)
38
43
  end
39
44
  end
40
45
 
@@ -52,59 +57,95 @@ describe HexaPDF::Font::TrueTypeWrapper do
52
57
  end
53
58
 
54
59
  describe "encode" do
55
- it "returns the PDF font dictionary and the encoded glyph" do
56
- dict = @font_wrapper.dict
60
+ it "returns the encoded glyph ID for fonts that are subset" do
61
+ code = @font_wrapper.encode(@font_wrapper.glyph(3))
62
+ assert_equal([1].pack('n'), code)
63
+ code = @font_wrapper.encode(@font_wrapper.glyph(10))
64
+ assert_equal([2].pack('n'), code)
65
+ end
57
66
 
67
+ it "returns the encoded glyph ID for fonts that are not subset" do
68
+ @font_wrapper = HexaPDF::Font::TrueTypeWrapper.new(@doc, @font, subset: false)
58
69
  code = @font_wrapper.encode(@font_wrapper.glyph(3))
59
70
  assert_equal([3].pack('n'), code)
60
- glyph = @font_wrapper.decode_utf8('H').first
61
- code = @font_wrapper.encode(glyph)
62
- assert_equal([glyph.id].pack('n'), code)
63
-
64
- @doc.dispatch_message(:complete_objects)
71
+ code = @font_wrapper.encode(@font_wrapper.glyph(10))
72
+ assert_equal([10].pack('n'), code)
73
+ end
74
+ end
65
75
 
66
- # Checking Type 0 font dictionary
67
- assert_equal(:Font, dict[:Type])
68
- assert_equal(:Type0, dict[:Subtype])
69
- assert_equal(:'Identity-H', dict[:Encoding])
70
- assert_equal(1, dict[:DescendantFonts].length)
71
- assert_equal(dict[:BaseFont], dict[:DescendantFonts][0][:BaseFont])
72
- assert_equal(HexaPDF::Font::CMap.create_to_unicode_cmap([[3, ' '.ord], [glyph.id, 'H'.ord]]),
73
- dict[:ToUnicode].stream)
74
-
75
- # Checking CIDFont dictionary
76
- cidfont = dict[:DescendantFonts][0]
77
- assert_equal(:Font, cidfont[:Type])
78
- assert_equal(:CIDFontType2, cidfont[:Subtype])
79
- assert_equal({Registry: "Adobe", Ordering: "Identity", Supplement: 0}, cidfont[:CIDSystemInfo])
80
- assert_equal(:Identity, cidfont[:CIDToGIDMap])
81
- assert_equal(@font_wrapper.glyph(3).width, cidfont[:DW])
82
- assert_equal([glyph.id, [glyph.width]], cidfont[:W])
83
-
84
- # Checking font descriptor
85
- fd = cidfont[:FontDescriptor]
86
- assert_equal(dict[:BaseFont], fd[:FontName])
87
- assert(fd.flagged?(:symbolic))
88
- assert(fd.key?(:FontFile2))
89
- assert(fd.validate)
90
-
91
- @cmap.stub(:[], nil) do
92
- @font[:'OS/2'].typo_ascender = 1000
93
- font_wrapper = HexaPDF::Font::TrueTypeWrapper.new(@doc, @font)
94
- font_wrapper.encode(glyph)
95
- fd = font_wrapper.dict[:DescendantFonts][0][:FontDescriptor]
96
- assert_equal(800, fd[:CapHeight])
97
- assert_equal(500, fd[:XHeight])
98
- end
99
-
100
- @font[:'OS/2'].version = 2
101
- @font[:'OS/2'].x_height = 500 * @font[:head].units_per_em / 1000
102
- @font[:'OS/2'].cap_height = 1000 * @font[:head].units_per_em / 1000
76
+ it "creates the necessary PDF dictionaries" do
77
+ @font_wrapper.encode(@font_wrapper.glyph(3))
78
+ glyph = @font_wrapper.decode_utf8('H').first
79
+ @font_wrapper.encode(glyph)
80
+ @doc.dispatch_message(:complete_objects)
81
+
82
+ dict = @font_wrapper.dict
83
+
84
+ # Checking Type 0 font dictionary
85
+ assert_equal(:Font, dict[:Type])
86
+ assert_equal(:Type0, dict[:Subtype])
87
+ assert_equal(:'Identity-H', dict[:Encoding])
88
+ assert_equal(1, dict[:DescendantFonts].length)
89
+ assert_equal(dict[:BaseFont], dict[:DescendantFonts][0][:BaseFont])
90
+ assert_equal(HexaPDF::Font::CMap.create_to_unicode_cmap([[3, ' '.ord], [glyph.id, 'H'.ord]]),
91
+ dict[:ToUnicode].stream)
92
+ assert_match(/\A[A-Z]{6}\+Ubuntu-Title\z/, dict[:BaseFont])
93
+
94
+ # Checking CIDFont dictionary
95
+ cidfont = dict[:DescendantFonts][0]
96
+ assert_equal(:Font, cidfont[:Type])
97
+ assert_equal(:CIDFontType2, cidfont[:Subtype])
98
+ assert_equal({Registry: "Adobe", Ordering: "Identity", Supplement: 0}, cidfont[:CIDSystemInfo])
99
+ assert_equal(:Identity, cidfont[:CIDToGIDMap])
100
+ assert_equal(@font_wrapper.glyph(3).width, cidfont[:DW])
101
+ assert_equal([glyph.id, [glyph.width]], cidfont[:W])
102
+
103
+ # Checking font descriptor
104
+ fd = cidfont[:FontDescriptor]
105
+ assert_equal(dict[:BaseFont], fd[:FontName])
106
+ assert(fd.flagged?(:symbolic))
107
+ assert(fd.key?(:FontFile2))
108
+ assert(fd.validate)
109
+
110
+ # Two special cases for determining cap height and x-height
111
+ @cmap.stub(:[], nil) do
112
+ @font[:'OS/2'].typo_ascender = 1000
103
113
  font_wrapper = HexaPDF::Font::TrueTypeWrapper.new(@doc, @font)
104
114
  font_wrapper.encode(glyph)
105
115
  fd = font_wrapper.dict[:DescendantFonts][0][:FontDescriptor]
106
- assert_equal(1000, fd[:CapHeight])
116
+ assert_equal(800, fd[:CapHeight])
107
117
  assert_equal(500, fd[:XHeight])
108
118
  end
119
+
120
+ @font[:'OS/2'].version = 2
121
+ @font[:'OS/2'].x_height = 500 * @font[:head].units_per_em / 1000
122
+ @font[:'OS/2'].cap_height = 1000 * @font[:head].units_per_em / 1000
123
+ font_wrapper = HexaPDF::Font::TrueTypeWrapper.new(@doc, @font)
124
+ font_wrapper.encode(glyph)
125
+ fd = font_wrapper.dict[:DescendantFonts][0][:FontDescriptor]
126
+ assert_equal(1000, fd[:CapHeight])
127
+ assert_equal(500, fd[:XHeight])
128
+ end
129
+
130
+ describe "font file embedding" do
131
+ it "embeds subset fonts" do
132
+ @font_wrapper.encode(@font_wrapper.glyph(10))
133
+ @doc.dispatch_message(:complete_objects)
134
+
135
+ font_data = @font_wrapper.dict[:DescendantFonts][0][:FontDescriptor][:FontFile2].stream
136
+ font = HexaPDF::Font::TrueType::Font.new(StringIO.new(font_data))
137
+ assert_equal(@font[:glyf][0].raw_data, font[:glyf][0].raw_data)
138
+ assert_equal(@font[:glyf][10].raw_data, font[:glyf][1].raw_data)
139
+ end
140
+
141
+ it "embeds full fonts" do
142
+ @font_wrapper = HexaPDF::Font::TrueTypeWrapper.new(@doc, @font, subset: false)
143
+ @doc.dispatch_message(:complete_objects)
144
+
145
+ assert_equal(File.size(@font_file),
146
+ @font_wrapper.dict[:DescendantFonts][0][:FontDescriptor][:FontFile2][:Length1])
147
+ assert_equal(File.binread(@font_file),
148
+ @font_wrapper.dict[:DescendantFonts][0][:FontDescriptor][:FontFile2].stream)
149
+ end
109
150
  end
110
151
  end
@@ -50,6 +50,7 @@ describe HexaPDF::Font::TrueType::Table::Glyf do
50
50
  assert_equal(-100, glyph.x_max)
51
51
  assert_equal(-150, glyph.y_max)
52
52
  assert_equal([1, 2, 3, 4, 1], glyph.components)
53
+ assert_equal([12, 18, 28, 40, 56], glyph.component_offsets)
53
54
  end
54
55
  end
55
56
  end
@@ -0,0 +1,70 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'stringio'
5
+ require 'hexapdf/font/true_type'
6
+
7
+ describe HexaPDF::Font::TrueType::Subsetter do
8
+ before do
9
+ font_file = File.join(TEST_DATA_DIR, "fonts", "Ubuntu-Title.ttf")
10
+ @font = HexaPDF::Font::TrueType::Font.new(File.open(font_file))
11
+ @subsetter = HexaPDF::Font::TrueType::Subsetter.new(@font)
12
+ end
13
+
14
+ after do
15
+ @font.io.close
16
+ end
17
+
18
+ it "adds glyphs to the subset" do
19
+ assert_equal(1, @subsetter.use_glyph(5))
20
+ assert_equal(2, @subsetter.use_glyph(6))
21
+ assert_equal(1, @subsetter.use_glyph(5))
22
+ end
23
+
24
+ it "creates the subset font file" do
25
+ gid = @font[:cmap].preferred_table[0x41]
26
+ @subsetter.use_glyph(gid)
27
+ subset = HexaPDF::Font::TrueType::Font.new(StringIO.new(@subsetter.build_font))
28
+
29
+ assert(subset[:head])
30
+ assert(subset[:head].checksum_valid?)
31
+ assert(subset[:hhea])
32
+ assert(subset[:hhea].checksum_valid?)
33
+ assert(subset[:glyf])
34
+ assert(subset[:glyf].checksum_valid?)
35
+ assert(subset[:loca])
36
+ assert(subset[:loca].checksum_valid?)
37
+ assert(subset[:maxp])
38
+ assert(subset[:maxp].checksum_valid?)
39
+ assert(subset[:hmtx])
40
+ assert(subset[:hmtx].checksum_valid?)
41
+
42
+ assert(Time.now - subset[:head].modified < 10)
43
+ assert_equal(2, subset[:maxp].num_glyphs)
44
+ assert_equal(2, subset[:hhea].num_of_long_hor_metrics)
45
+ assert_equal(3, subset[:loca].offsets.length)
46
+
47
+ assert_equal(subset[:hmtx][0], @font[:hmtx][0])
48
+ assert_equal(subset[:hmtx][1], @font[:hmtx][gid])
49
+
50
+ assert_equal(subset[:glyf][1].raw_data, @font[:glyf][gid].raw_data)
51
+ end
52
+
53
+ it "correctly subsets compound glyphs" do
54
+ font_file = "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"
55
+ skip unless File.exist?(font_file)
56
+
57
+ begin
58
+ @font = HexaPDF::Font::TrueType::Font.new(File.open(font_file))
59
+ @subsetter = HexaPDF::Font::TrueType::Subsetter.new(@font)
60
+
61
+ @subsetter.use_glyph(@font[:cmap].preferred_table['À'.ord])
62
+ subset = HexaPDF::Font::TrueType::Font.new(StringIO.new(@subsetter.build_font))
63
+
64
+ assert_equal(4, subset[:maxp].num_glyphs)
65
+ assert_equal([2, 3], subset[:glyf][1].components)
66
+ ensure
67
+ @font.io.close
68
+ end
69
+ end
70
+ end
@@ -29,4 +29,20 @@ describe HexaPDF::Font::TrueType::Table do
29
29
  assert(table.checksum_valid?)
30
30
  end
31
31
  end
32
+
33
+ describe "read_fixed" do
34
+ it "works for unsigned values" do
35
+ @file.io.string = [1, 20480].pack('nn')
36
+ @entry.length = @file.io.string.length
37
+ table = TestHelper::TrueTypeTestTable.new(@file, @entry)
38
+ assert_equal(1 + Rational(20480, 65536), table.send(:read_fixed))
39
+ end
40
+
41
+ it "works for signed values" do
42
+ @file.io.string = [-1, 20480].pack('nn')
43
+ @entry.length = @file.io.string.length
44
+ table = TestHelper::TrueTypeTestTable.new(@file, @entry)
45
+ assert_equal(-1 + Rational(20480, 65536), table.send(:read_fixed))
46
+ end
47
+ end
32
48
  end
@@ -17,6 +17,13 @@ describe HexaPDF::FontLoader::FromConfiguration do
17
17
  assert_equal("Ubuntu-Title", wrapper.wrapped_font.font_name)
18
18
  end
19
19
 
20
+ it "passes the subset value to the wrapper" do
21
+ wrapper = @klass.call(@doc, "font")
22
+ assert(wrapper.subset?)
23
+ wrapper = @klass.call(@doc, "font", subset: false)
24
+ refute(wrapper.subset?)
25
+ end
26
+
20
27
  it "fails if the font file cannot be read" do
21
28
  @doc.config['font.map']['font'][:none] << "unknown"
22
29
  assert_raises(HexaPDF::Error) { @klass.call(@doc, "font") }
@@ -392,7 +392,7 @@ EOF
392
392
  objs = [[10, 20, 30], [200, nil]]
393
393
  data = @io_doc.revisions.map.with_index {|rev, i| objs[i].map {|o| [o, rev]}}.reverse.flatten
394
394
  @io_doc.each(current: false) do |obj, rev|
395
- assert_equal(data.shift, obj.value)
395
+ assert(data.shift == obj.value)
396
396
  assert_equal(data.shift, rev)
397
397
  end
398
398
  end
@@ -9,7 +9,7 @@ describe HexaPDF::Object do
9
9
  it "handles not-duplicatable classes" do
10
10
  assert_equal(5, HexaPDF::Object.deep_copy(5))
11
11
  assert_equal(5.5, HexaPDF::Object.deep_copy(5.5))
12
- assert_equal(nil, HexaPDF::Object.deep_copy(nil))
12
+ assert_nil(HexaPDF::Object.deep_copy(nil))
13
13
  assert_equal(true, HexaPDF::Object.deep_copy(true))
14
14
  assert_equal(false, HexaPDF::Object.deep_copy(false))
15
15
  assert_equal(:Name, HexaPDF::Object.deep_copy(:Name))
@@ -28,17 +28,42 @@ startxref
28
28
  47
29
29
  %%EOF
30
30
 
31
+ 2 0 obj
32
+ 300
33
+ endobj
34
+
35
+ 3 0 obj
36
+ << /Type /XRef /Size 4 /Index [2 1] /W [1 1 1] /Filter /ASCIIHexDecode /Length 6
37
+ >>stream
38
+ 019E00
39
+ endstream
40
+ endobj
41
+
31
42
  2 0 obj
32
43
  200
33
44
  endobj
34
45
 
46
+ xref
47
+ 2 2
48
+ 0000000301 00000 n
49
+ 0000000178 00000 n
50
+ trailer
51
+ << /Size 4 /Prev 47 >>
52
+ startxref
53
+ 321
54
+ %%EOF
55
+
56
+ 2 0 obj
57
+ 400
58
+ endobj
59
+
35
60
  xref
36
61
  2 1
37
- 0000000158 00000 n
62
+ 0000000422 00000 n
38
63
  trailer
39
- << /Size 3 /Prev 47 >>
64
+ << /Size 4 /Prev 321 /XRefStm 178 >>
40
65
  startxref
41
- 178
66
+ 442
42
67
  %%EOF
43
68
  EOF
44
69
  @doc = HexaPDF::Document.new(io: @io)
@@ -48,7 +73,7 @@ EOF
48
73
  describe "add" do
49
74
  it "adds an empty revision as the current revision" do
50
75
  rev = @revisions.add
51
- assert_equal({Size: 3}, rev.trailer.value)
76
+ assert_equal({Size: 4}, rev.trailer.value)
52
77
  assert_equal(rev, @revisions.current)
53
78
  end
54
79
  end
@@ -74,27 +99,28 @@ EOF
74
99
  describe "merge" do
75
100
  it "does nothing when only one revision is specified" do
76
101
  @revisions.merge(1..1)
77
- assert_equal(2, @revisions.each.to_a.size)
102
+ assert_equal(3, @revisions.each.to_a.size)
78
103
  end
79
104
 
80
105
  it "merges the higher into the the lower revision" do
81
106
  @revisions.merge
82
107
  assert_equal(1, @revisions.each.to_a.size)
83
- assert_equal([10, 200], @revisions.current.each.to_a.sort.map(&:value))
108
+ assert_equal([10, 400, @doc.object(3).value], @revisions.current.each.to_a.sort.map(&:value))
84
109
  end
85
110
 
86
111
  it "handles objects correctly that are in multiple revisions" do
87
112
  @revisions.current.add(@revisions[0].object(1))
88
113
  @revisions.merge
89
114
  assert_equal(1, @revisions.each.to_a.size)
90
- assert_equal([10, 200], @revisions.current.each.to_a.sort.map(&:value))
115
+ assert_equal([10, 400, @doc.object(3).value], @revisions.current.each.to_a.sort.map(&:value))
91
116
  end
92
117
  end
93
118
 
94
119
  describe "initialize" do
95
120
  it "automatically loads all revisions from the underlying IO object" do
96
121
  assert_equal(20, @revisions.revision(0).object(2).value)
97
- assert_equal(200, @revisions[1].object(2).value)
122
+ assert_equal(300, @revisions[1].object(2).value)
123
+ assert_equal(400, @revisions[2].object(2).value)
98
124
  end
99
125
  end
100
126
  end