hexapdf 0.47.0 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +65 -16
- data/lib/hexapdf/cli.rb +14 -1
- data/lib/hexapdf/composer.rb +7 -0
- data/lib/hexapdf/configuration.rb +2 -0
- data/lib/hexapdf/content/parser.rb +3 -1
- data/lib/hexapdf/digital_signature/cms_handler.rb +13 -0
- data/lib/hexapdf/digital_signature/signature.rb +1 -1
- data/lib/hexapdf/digital_signature/signing/default_handler.rb +1 -0
- data/lib/hexapdf/document.rb +14 -3
- data/lib/hexapdf/font/cmap/writer.rb +58 -4
- data/lib/hexapdf/font/cmap.rb +7 -0
- data/lib/hexapdf/font/true_type_wrapper.rb +41 -16
- data/lib/hexapdf/layout/text_fragment.rb +2 -1
- data/lib/hexapdf/object.rb +1 -1
- data/lib/hexapdf/parser.rb +6 -2
- data/lib/hexapdf/reference.rb +1 -1
- data/lib/hexapdf/task/merge_acro_form.rb +164 -0
- data/lib/hexapdf/task.rb +1 -0
- data/lib/hexapdf/tokenizer.rb +2 -0
- data/lib/hexapdf/type/acro_form/form.rb +14 -27
- data/lib/hexapdf/type/acro_form/signature_field.rb +16 -6
- data/lib/hexapdf/type/acro_form/variable_text_field.rb +1 -1
- data/lib/hexapdf/type/actions/go_to.rb +1 -0
- data/lib/hexapdf/type/actions/go_to_r.rb +1 -0
- data/lib/hexapdf/type/actions/launch.rb +5 -1
- data/lib/hexapdf/type/annotation.rb +6 -1
- data/lib/hexapdf/type/annotations/markup_annotation.rb +14 -1
- data/lib/hexapdf/type/catalog.rb +3 -0
- data/lib/hexapdf/type/cid_font.rb +4 -1
- data/lib/hexapdf/type/file_specification.rb +17 -14
- data/lib/hexapdf/type/font_descriptor.rb +4 -3
- data/lib/hexapdf/type/font_simple.rb +3 -1
- data/lib/hexapdf/type/font_true_type.rb +2 -0
- data/lib/hexapdf/type/font_type0.rb +1 -1
- data/lib/hexapdf/type/font_type1.rb +7 -0
- data/lib/hexapdf/type/font_type3.rb +0 -1
- data/lib/hexapdf/type/form.rb +5 -2
- data/lib/hexapdf/type/graphics_state_parameter.rb +7 -4
- data/lib/hexapdf/type/image.rb +8 -4
- data/lib/hexapdf/type/info.rb +2 -2
- data/lib/hexapdf/type/mark_information.rb +2 -2
- data/lib/hexapdf/type/optional_content_configuration.rb +1 -1
- data/lib/hexapdf/type/optional_content_membership.rb +1 -1
- data/lib/hexapdf/type/page.rb +5 -3
- data/lib/hexapdf/type/resources.rb +6 -6
- data/lib/hexapdf/type/viewer_preferences.rb +4 -3
- data/lib/hexapdf/utils/sorted_tree_node.rb +12 -2
- data/lib/hexapdf/version.rb +1 -1
- data/lib/hexapdf/writer.rb +1 -0
- data/lib/hexapdf/xref_section.rb +20 -4
- data/test/hexapdf/common_tokenizer_tests.rb +5 -0
- data/test/hexapdf/digital_signature/signing/test_default_handler.rb +6 -0
- data/test/hexapdf/digital_signature/test_cms_handler.rb +12 -7
- data/test/hexapdf/digital_signature/test_signature.rb +7 -0
- data/test/hexapdf/digital_signature/test_signatures.rb +8 -3
- data/test/hexapdf/font/cmap/test_writer.rb +73 -16
- data/test/hexapdf/font/test_true_type_wrapper.rb +17 -3
- data/test/hexapdf/layout/test_list_box.rb +7 -7
- data/test/hexapdf/layout/test_text_fragment.rb +3 -3
- data/test/hexapdf/layout/test_text_layouter.rb +4 -2
- data/test/hexapdf/task/test_merge_acro_form.rb +104 -0
- data/test/hexapdf/test_composer.rb +8 -0
- data/test/hexapdf/test_document.rb +9 -0
- data/test/hexapdf/test_parser.rb +23 -6
- data/test/hexapdf/test_writer.rb +10 -5
- data/test/hexapdf/test_xref_section.rb +15 -0
- data/test/hexapdf/type/acro_form/test_appearance_generator.rb +18 -18
- data/test/hexapdf/type/acro_form/test_form.rb +7 -3
- data/test/hexapdf/type/actions/test_launch.rb +6 -2
- data/test/hexapdf/type/test_font_type1.rb +5 -0
- data/test/hexapdf/type/test_form.rb +1 -1
- data/test/hexapdf/type/test_page.rb +7 -1
- data/test/hexapdf/utils/test_sorted_tree_node.rb +7 -6
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 61b0fb56c6522f2af82eb8ffb10570c45bb11460cf4c048c1bdfe8d9daf71afe
|
4
|
+
data.tar.gz: 91cb053019c367825ac0799a84e4ddad837fe283a6ab2bc6df16ee9ed9f2456d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9a71ee1e9307f0ef67c9dec108c7f68db45166a62f9b6ec60915ce2c089cf0e9ec5bfcd8d74e8b31b63238a09c820a0798689a84e5ea0b1577e2492e5a1d425e
|
7
|
+
data.tar.gz: b20043cead03f7fc7fe527fdbcb3674ab2d1da06b546bac9c1549b6eb6d143232453132709d93ae008d78a83bff36cf85fd0dbc0938da848e7847a1830e6011e
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,52 @@
|
|
1
|
+
## 1.0.1 - 2024-11-04
|
2
|
+
|
3
|
+
### Changed
|
4
|
+
|
5
|
+
* Informational output on errors when running CLI commands to provide more
|
6
|
+
details
|
7
|
+
|
8
|
+
### Fixed
|
9
|
+
|
10
|
+
* Parsing of indirect objects the value of which is an indirect reference
|
11
|
+
* Writing of the initial cross-reference section to ensure a single subsection
|
12
|
+
* [HexaPDF::Utils::SortedTreeNode] to wrap all /Kids entries with the correct
|
13
|
+
type class
|
14
|
+
|
15
|
+
|
16
|
+
## 1.0.0 - 2024-10-26
|
17
|
+
|
18
|
+
### Added
|
19
|
+
|
20
|
+
* [HexaPDF::Task::MergeAcroForm] for merging AcroForm information for imported
|
21
|
+
pages
|
22
|
+
* [HexaPDF::Document#write_to_string] and [HexaPDF::Composer#write_to_string]
|
23
|
+
for easily writing a document to a String
|
24
|
+
* [HexaPDF::Font::CMap::Writer#create_cid_cmap] for creating a character code to
|
25
|
+
CID CMap file
|
26
|
+
|
27
|
+
### Changed
|
28
|
+
|
29
|
+
* [HexaPDF::Type::AcroForm::Form] text-like field creation methods to always set
|
30
|
+
a default appearance string and the quadding
|
31
|
+
* Convenience methods for accessing resources to not add the deprecated /ProcSet
|
32
|
+
entry by default
|
33
|
+
* [HexaPDF::DigitalSignature::CMSHandler] to add informational output regarding
|
34
|
+
the certificate chain on verification
|
35
|
+
* Validation of [HexaPDF::Type::FontType1] to ensure correct /Encoding value
|
36
|
+
|
37
|
+
### Fixed
|
38
|
+
|
39
|
+
* [HexaPDF::DigitalSignature::Signature#signed_data] to work for invalid offsets
|
40
|
+
* [HexaPDF::DigitalSignature::Signing::DefaultHandler] to update the document's
|
41
|
+
version to 2.0 when using PAdES
|
42
|
+
* Parsing of invalid `)` character in PDF objects and content streams
|
43
|
+
* Handling of files that contain stream length values that are indirect objects
|
44
|
+
that do not exist
|
45
|
+
* [HexaPDF::Font::TrueTypeWrapper] to correctly handle the situation when
|
46
|
+
multiple codepoints refer to the same glyph ID
|
47
|
+
* [HexaPDF::Type::Page#contents] to handle null values in /Contents array
|
48
|
+
|
49
|
+
|
1
50
|
## 0.47.0 - 2024-09-07
|
2
51
|
|
3
52
|
### Added
|
@@ -53,7 +102,7 @@
|
|
53
102
|
signatures
|
54
103
|
* [HexaPDF::DigitalSignature::CMSHandler#signing_time] to use time from an
|
55
104
|
embedded timestamp authority signature if possible
|
56
|
-
*
|
105
|
+
* HexaPDF::Layout::Box#fit to return success for boxes with content
|
57
106
|
width/height of zero
|
58
107
|
* [HexaPDF::Importer::copy] to optionally allow copying the catalog and page
|
59
108
|
tree nodes
|
@@ -61,7 +110,7 @@
|
|
61
110
|
### Fixed
|
62
111
|
|
63
112
|
* Setting of correct x-position in fit result for boxes with flow positioning
|
64
|
-
*
|
113
|
+
* HexaPDF::Layout::ListBox#fit to respect the set height
|
65
114
|
* CLI command `hexapdf inspect` to work in case of missing Unicde mappings
|
66
115
|
* [HexaPDF::Type::AcroForm::Form#delete_field] to correctly work for fields with
|
67
116
|
an embedded widget
|
@@ -80,7 +129,7 @@
|
|
80
129
|
|
81
130
|
### Changed
|
82
131
|
|
83
|
-
*
|
132
|
+
* HexaPDF::Layout::Box#fit to set width/height correctly for boxes with
|
84
133
|
position `:flow`
|
85
134
|
|
86
135
|
### Fixed
|
@@ -126,9 +175,9 @@
|
|
126
175
|
|
127
176
|
### Fixed
|
128
177
|
|
129
|
-
*
|
178
|
+
* HexaPDF::Layout::TextBox#fit to correctly calculate width in case of flowing
|
130
179
|
text around other boxes
|
131
|
-
*
|
180
|
+
* HexaPDF::Layout::TextBox#draw to correctly draw border, background... on
|
132
181
|
boxes using position 'flow'
|
133
182
|
* Comparison of Hash with [HexaPDF::Dictionary] objects by implementing
|
134
183
|
`#to_hash`
|
@@ -182,7 +231,7 @@
|
|
182
231
|
JavaScript action that formats the field's value
|
183
232
|
* [HexaPDF::Type::AcroForm::TextField#set_calculate_action] for setting a
|
184
233
|
JavaScript action that calculates the field's value
|
185
|
-
* [HexaPDF::Type::AcroForm#recalculate_fields] for recalculating fields
|
234
|
+
* [HexaPDF::Type::AcroForm::Form#recalculate_fields] for recalculating fields
|
186
235
|
|
187
236
|
### Changed
|
188
237
|
|
@@ -239,7 +288,7 @@
|
|
239
288
|
|
240
289
|
### Changed
|
241
290
|
|
242
|
-
*
|
291
|
+
* HexaPDF::Layout::Frame::FitResult#draw to provide better optional content
|
243
292
|
group names
|
244
293
|
|
245
294
|
### Fixed
|
@@ -318,8 +367,8 @@
|
|
318
367
|
|
319
368
|
### Changed
|
320
369
|
|
321
|
-
*
|
322
|
-
*
|
370
|
+
* HexaPDF::Layout::Frame::FitResult#draw to allow drawing at an offset
|
371
|
+
* HexaPDF::Layout::Box#fit to delegate the actual content fitting to the
|
323
372
|
`#fit_content` method
|
324
373
|
* [HexaPDF::Document::Layout#box] to allow using the block as drawing block for
|
325
374
|
the base box class
|
@@ -396,8 +445,8 @@
|
|
396
445
|
|
397
446
|
### Fixed
|
398
447
|
|
399
|
-
*
|
400
|
-
*
|
448
|
+
* HexaPDF::Layout::ColumnBox#fit to correctly take initial height into account
|
449
|
+
* HexaPDF::Layout::ColumnBox#fit to ensure correct results in case the
|
401
450
|
requested dimensions are larger than the current region
|
402
451
|
* [HexaPDF::Document::Layout#formatted_text_box] to correctly handle properties
|
403
452
|
* [HexaPDF::Layout::Frame#fit] to raise an error if an invalid value for the
|
@@ -443,7 +492,7 @@
|
|
443
492
|
context argument (a page or Form XObject instance)
|
444
493
|
* [HexaPDF::Layout::ListBox] to use its 'fill_color' style property for the item
|
445
494
|
marker color
|
446
|
-
*
|
495
|
+
* HexaPDF::Layout::Frame::FitResult#draw to use optional content groups for
|
447
496
|
debug output
|
448
497
|
|
449
498
|
### Fixed
|
@@ -452,7 +501,7 @@
|
|
452
501
|
default range starting at page 1
|
453
502
|
* [HexaPDF::Type::Page#flatten_annotations] to correctly handle scaled
|
454
503
|
appearances
|
455
|
-
* Using an unknown style name in [HexaPDF
|
504
|
+
* Using an unknown style name in [HexaPDF::Document::Layout] method by providing
|
456
505
|
a useful error message
|
457
506
|
* [HexaPDF::Layout::Box::new] to ensure that the properties attribute is always
|
458
507
|
a hash
|
@@ -513,7 +562,7 @@
|
|
513
562
|
final box positions into account
|
514
563
|
* [HexaPDF::Content::Canvas#text] to set the leading only when multiple lines
|
515
564
|
are drawn
|
516
|
-
*
|
565
|
+
* HexaPDF::Layout::TextBox#split to use float comparison
|
517
566
|
* Validation of standard encryption dictionary to auto-correct invalid /U and /O
|
518
567
|
fields in case they are padded with zeros
|
519
568
|
* [HexaPDF::Document#wrap] handling of sub-type mapping in case of missing type
|
@@ -930,7 +979,7 @@
|
|
930
979
|
* [HexaPDF::Layout::WidthFromPolygon] to work correctly in case of very small
|
931
980
|
floating point errors
|
932
981
|
* HexaPDF::Layout::TextFragment#inspect to work in case of interspersed numbers
|
933
|
-
*
|
982
|
+
* HexaPDF::Layout::TextBox#split to work for position :flow when box is wider
|
934
983
|
than the initial available width
|
935
984
|
* [HexaPDF::Layout::Frame#fit] to create minimally sized mask rectangles
|
936
985
|
* [HexaPDF::Content::GraphicObject::Geom2D] to close the path when drawing
|
@@ -1866,7 +1915,7 @@
|
|
1866
1915
|
objects
|
1867
1916
|
* [HexaPDF::Revision#each_modified_object] for iterating over all modified
|
1868
1917
|
objects of a revision
|
1869
|
-
* [HexaPDF::Layout::Box#split] and
|
1918
|
+
* [HexaPDF::Layout::Box#split] and HexaPDF::Layout::TextBox#split for
|
1870
1919
|
splitting a box into two parts
|
1871
1920
|
* [HexaPDF::Layout::Frame#full?] for testing whether the frame has any space
|
1872
1921
|
left
|
data/lib/hexapdf/cli.rb
CHANGED
@@ -64,8 +64,21 @@ module HexaPDF
|
|
64
64
|
rescue StandardError => e
|
65
65
|
$stderr.puts "Problem encountered: #{e.message}"
|
66
66
|
unless e.kind_of?(HexaPDF::Error)
|
67
|
+
$stderr.puts "Backtrace (last 10 lines):"
|
68
|
+
$stderr.puts e.backtrace[0, 10]
|
69
|
+
$stderr.puts
|
67
70
|
$stderr.puts "--> The problem might indicate a faulty PDF or a bug in HexaPDF."
|
68
|
-
$stderr.puts "--> Please report this at
|
71
|
+
$stderr.puts "--> Please report this at"
|
72
|
+
$stderr.puts "-->"
|
73
|
+
$stderr.puts "--> https://github.com/gettalong/hexapdf/issues"
|
74
|
+
$stderr.puts "-->"
|
75
|
+
$stderr.puts "--> and include the information above as well as the output of running"
|
76
|
+
$stderr.puts "--> the following command on the input PDF:"
|
77
|
+
$stderr.puts "-->"
|
78
|
+
$stderr.puts "--> hexapdf info --check INPUT.PDF"
|
79
|
+
$stderr.puts "-->"
|
80
|
+
$stderr.puts "--> If possible, please also provide the input PDF."
|
81
|
+
$stderr.puts "--> Thanks!"
|
69
82
|
end
|
70
83
|
exit(1)
|
71
84
|
end
|
data/lib/hexapdf/composer.rb
CHANGED
@@ -231,6 +231,13 @@ module HexaPDF
|
|
231
231
|
@document.write(output, optimize: optimize, **options)
|
232
232
|
end
|
233
233
|
|
234
|
+
# Writes the created PDF document to a string and returns that string.
|
235
|
+
#
|
236
|
+
# See HexaPDF::Document#write for details.
|
237
|
+
def write_to_string(optimize: true, **options)
|
238
|
+
@document.write_to_string(optimize: optimize, **options)
|
239
|
+
end
|
240
|
+
|
234
241
|
# :call-seq:
|
235
242
|
# composer.style(name) -> style
|
236
243
|
# composer.style(name, base: :base, **properties) -> style
|
@@ -598,6 +598,7 @@ module HexaPDF
|
|
598
598
|
optimize: 'HexaPDF::Task::Optimize',
|
599
599
|
dereference: 'HexaPDF::Task::Dereference',
|
600
600
|
pdfa: 'HexaPDF::Task::PDFA',
|
601
|
+
merge_acro_form: 'HexaPDF::Task::MergeAcroForm',
|
601
602
|
})
|
602
603
|
|
603
604
|
# The global configuration object, providing the following options:
|
@@ -720,6 +721,7 @@ module HexaPDF
|
|
720
721
|
Metadata: 'HexaPDF::Type::Metadata',
|
721
722
|
OutputIntent: 'HexaPDF::Type::OutputIntent',
|
722
723
|
XXDestOutputProfileRef: 'HexaPDF::Type::OutputIntent::DestOutputProfileRef',
|
724
|
+
ExData: 'HexaPDF::Type::Annotations::MarkupAnnotation::ExData',
|
723
725
|
},
|
724
726
|
'object.subtype_map' => {
|
725
727
|
nil => {
|
@@ -112,7 +112,9 @@ module HexaPDF
|
|
112
112
|
elsif byte == 93 # ]
|
113
113
|
@ss.pos += 1
|
114
114
|
TOKEN_ARRAY_END
|
115
|
-
elsif byte ==
|
115
|
+
elsif byte == 41 # )
|
116
|
+
raise HexaPDF::MalformedPDFError.new("Delimiter ')' found at invalid position", pos: pos)
|
117
|
+
elsif byte == 123 || byte == 125 # { } )
|
116
118
|
Token.new(@ss.get_byte)
|
117
119
|
elsif byte == 37 # %
|
118
120
|
unless @ss.skip_until(/(?=[\r\n])/)
|
@@ -155,6 +155,19 @@ module HexaPDF
|
|
155
155
|
result.log(:error, "Signature verification failed")
|
156
156
|
end
|
157
157
|
|
158
|
+
certs = [signer_certificate]
|
159
|
+
cur_cert = certs.first
|
160
|
+
while true
|
161
|
+
cur_cert = certificate_chain.find {|cert| cert.subject == cur_cert.issuer }
|
162
|
+
if cur_cert && !certs.include?(cur_cert)
|
163
|
+
certs << cur_cert
|
164
|
+
else
|
165
|
+
break
|
166
|
+
end
|
167
|
+
end
|
168
|
+
cert_subjects = certs.map {|cert| cert.subject.to_a.assoc("CN")&.[](1) }
|
169
|
+
result.log(:info, "Certificate chain: #{cert_subjects.join(" -> ")}")
|
170
|
+
|
158
171
|
result
|
159
172
|
end
|
160
173
|
|
@@ -289,6 +289,7 @@ module HexaPDF
|
|
289
289
|
signature[:Location] = location if location
|
290
290
|
signature[:ContactInfo] = contact_info if contact_info
|
291
291
|
signature[:Prop_Build] = {App: {Name: :HexaPDF, REx: HexaPDF::VERSION}}
|
292
|
+
signature.document.version = '2.0' if signature_type == :pades
|
292
293
|
|
293
294
|
if doc_mdp_permissions
|
294
295
|
doc = signature.document
|
data/lib/hexapdf/document.rb
CHANGED
@@ -724,10 +724,12 @@ module HexaPDF
|
|
724
724
|
end
|
725
725
|
|
726
726
|
# :call-seq:
|
727
|
-
# doc.write(filename, incremental: false, validate: true, update_fields: true, optimize: false)
|
728
|
-
# doc.write(io, incremental: false, validate: true, update_fields: true, optimize: false)
|
727
|
+
# doc.write(filename, incremental: false, validate: true, update_fields: true, optimize: false) -> [start_xref, section]
|
728
|
+
# doc.write(io, incremental: false, validate: true, update_fields: true, optimize: false) -> [start_xref, section]
|
729
729
|
#
|
730
|
-
# Writes the document to the given file (in case +io+ is a String) or IO stream.
|
730
|
+
# Writes the document to the given file (in case +io+ is a String) or IO stream. Returns the
|
731
|
+
# file position of the start of the last cross-reference section and the last XRefSection object
|
732
|
+
# written.
|
731
733
|
#
|
732
734
|
# Before the document is written, it is validated using #validate and an error is raised if the
|
733
735
|
# document is not valid. However, this step can be skipped if needed.
|
@@ -784,6 +786,15 @@ module HexaPDF
|
|
784
786
|
end
|
785
787
|
end
|
786
788
|
|
789
|
+
# Writes the document to a string and returns the string.
|
790
|
+
#
|
791
|
+
# See #write for further information and details on the available arguments.
|
792
|
+
def write_to_string(**args)
|
793
|
+
io = StringIO.new(''.b)
|
794
|
+
write(io)
|
795
|
+
io.string
|
796
|
+
end
|
797
|
+
|
787
798
|
def inspect #:nodoc:
|
788
799
|
"<#{self.class.name}:#{object_id}>"
|
789
800
|
end
|
@@ -40,9 +40,7 @@ module HexaPDF
|
|
40
40
|
module Font
|
41
41
|
class CMap
|
42
42
|
|
43
|
-
# Creates a CMap file.
|
44
|
-
#
|
45
|
-
# Currently only ToUnicode CMaps are supported.
|
43
|
+
# Creates a CMap file, either a ToUnicode CMap or a CID CMap.
|
46
44
|
class Writer
|
47
45
|
|
48
46
|
# Maximum number of entries in one section.
|
@@ -74,6 +72,28 @@ module HexaPDF
|
|
74
72
|
to_unicode_template % result.chop!
|
75
73
|
end
|
76
74
|
|
75
|
+
# Returns a CID CMap for the given input code to CID mapping which needs to be sorted by
|
76
|
+
# input codes.
|
77
|
+
#
|
78
|
+
# Note that the returned CMap always uses a 16-bit input code space!
|
79
|
+
def create_cid_cmap(mapping)
|
80
|
+
return cid_template % '' if mapping.empty?
|
81
|
+
|
82
|
+
chars, ranges = compute_section_entries(mapping)
|
83
|
+
|
84
|
+
result = create_sections("cidchar", chars.size / 2) do |index|
|
85
|
+
index *= 2
|
86
|
+
sprintf("<%04X>", chars[index]) << " #{chars[index + 1]}\n"
|
87
|
+
end
|
88
|
+
|
89
|
+
result << create_sections("cidrange", ranges.size / 3) do |index|
|
90
|
+
index *= 3
|
91
|
+
sprintf("<%04X><%04X>", ranges[index], ranges[index + 1]) << " #{ranges[index + 2]}\n"
|
92
|
+
end
|
93
|
+
|
94
|
+
cid_template % result.chop!
|
95
|
+
end
|
96
|
+
|
77
97
|
private
|
78
98
|
|
79
99
|
# Computes the entries for the "char" and "range" sections based on the given mapping.
|
@@ -146,7 +166,7 @@ module HexaPDF
|
|
146
166
|
result
|
147
167
|
end
|
148
168
|
|
149
|
-
# Returns the
|
169
|
+
# Returns the template for a ToUnicode CMap.
|
150
170
|
def to_unicode_template
|
151
171
|
<<~TEMPLATE
|
152
172
|
/CIDInit /ProcSet findresource begin
|
@@ -170,6 +190,40 @@ module HexaPDF
|
|
170
190
|
TEMPLATE
|
171
191
|
end
|
172
192
|
|
193
|
+
# Returns the template for a CID CMap.
|
194
|
+
def cid_template
|
195
|
+
<<~TEMPLATE
|
196
|
+
%%!PS-Adobe-3.0 Resource-CMap
|
197
|
+
%%%%DocumentNeededResources: ProcSet (CIDInit)
|
198
|
+
%%%%IncludeResource: ProcSet (CIDInit)
|
199
|
+
%%%%BeginResource: CMap (Custom)
|
200
|
+
%%%%Title: (Custom Adobe Identity 0)
|
201
|
+
%%%%Version: 1
|
202
|
+
/CIDInit /ProcSet findresource begin
|
203
|
+
12 dict begin
|
204
|
+
begincmap
|
205
|
+
/CIDSystemInfo 3 dict dup begin
|
206
|
+
/Registry (Adobe) def
|
207
|
+
/Ordering (Identity) def
|
208
|
+
/Supplement 0 def
|
209
|
+
end def
|
210
|
+
/CMapName /Custom def
|
211
|
+
/CMapType 1 def
|
212
|
+
/CMapVersion 1 def
|
213
|
+
/WMode 0 def
|
214
|
+
1 begincodespacerange
|
215
|
+
<0000> <FFFF>
|
216
|
+
endcodespacerange
|
217
|
+
%s
|
218
|
+
endcmap
|
219
|
+
CMapName currentdict /CMap defineresource pop
|
220
|
+
end
|
221
|
+
end
|
222
|
+
%%%%EndResource
|
223
|
+
%%%%EOF
|
224
|
+
TEMPLATE
|
225
|
+
end
|
226
|
+
|
173
227
|
end
|
174
228
|
|
175
229
|
end
|
data/lib/hexapdf/font/cmap.rb
CHANGED
@@ -85,6 +85,13 @@ module HexaPDF
|
|
85
85
|
Writer.new.create_to_unicode_cmap(mapping)
|
86
86
|
end
|
87
87
|
|
88
|
+
# Returns a string containing a CID CMap that represents the given code to CID mapping.
|
89
|
+
#
|
90
|
+
# See: Writer#create_cid_cmap
|
91
|
+
def self.create_cid_cmap(mapping)
|
92
|
+
Writer.new.create_cid_cmap(mapping)
|
93
|
+
end
|
94
|
+
|
88
95
|
# The registry part of the CMap version.
|
89
96
|
attr_accessor :registry
|
90
97
|
|
@@ -57,6 +57,10 @@ module HexaPDF
|
|
57
57
|
class TrueTypeWrapper
|
58
58
|
|
59
59
|
# Represents a single glyph of the wrapped font.
|
60
|
+
#
|
61
|
+
# Since some characters/strings may be mapped to the same glyph id by the font's builtin cmap
|
62
|
+
# table, it is possible that different Glyph instances with the same #id but different #str
|
63
|
+
# exist.
|
60
64
|
class Glyph
|
61
65
|
|
62
66
|
# The associated TrueTypeWrapper object.
|
@@ -152,6 +156,7 @@ module HexaPDF
|
|
152
156
|
@id_to_glyph = {}
|
153
157
|
@codepoint_to_glyph = {}
|
154
158
|
@encoded_glyphs = {}
|
159
|
+
@last_char_code = 0
|
155
160
|
end
|
156
161
|
|
157
162
|
# Returns the type of the font, i.e. :TrueType.
|
@@ -179,14 +184,15 @@ module HexaPDF
|
|
179
184
|
!@subsetter.nil?
|
180
185
|
end
|
181
186
|
|
182
|
-
# Returns a Glyph object for the given glyph ID.
|
187
|
+
# Returns a Glyph object for the given glyph ID and +str+ pair.
|
183
188
|
#
|
184
|
-
# The optional argument +str+ should be the string representation of the glyph.
|
185
|
-
#
|
189
|
+
# The optional argument +str+ should be the string representation of the glyph. It is possible
|
190
|
+
# that multiple strings map to the same glyph (e.g. hyphen and soft-hyphen could be
|
191
|
+
# represented by the same glyph).
|
186
192
|
#
|
187
193
|
# Note: Although this method is public, it should normally not be used by application code!
|
188
194
|
def glyph(id, str = nil)
|
189
|
-
@id_to_glyph[id] ||=
|
195
|
+
@id_to_glyph[[id, str]] ||=
|
190
196
|
if id >= 0 && id < @wrapped_font[:maxp].num_glyphs
|
191
197
|
Glyph.new(self, id, str || (+'' << (@cmap.gid_to_code(id) || 0xFFFD)))
|
192
198
|
else
|
@@ -228,14 +234,12 @@ module HexaPDF
|
|
228
234
|
|
229
235
|
# Encodes the glyph and returns the code string.
|
230
236
|
def encode(glyph)
|
231
|
-
(@encoded_glyphs[glyph
|
237
|
+
(@encoded_glyphs[glyph] ||=
|
232
238
|
begin
|
233
239
|
raise HexaPDF::MissingGlyphError.new(glyph) if glyph.kind_of?(InvalidGlyph)
|
234
|
-
if @subsetter
|
235
|
-
|
236
|
-
|
237
|
-
[[glyph.id].pack('n'), glyph]
|
238
|
-
end
|
240
|
+
@subsetter.use_glyph(glyph.id) if @subsetter
|
241
|
+
@last_char_code += 1
|
242
|
+
[[@last_char_code].pack('n'), @last_char_code]
|
239
243
|
end)[0]
|
240
244
|
end
|
241
245
|
|
@@ -286,7 +290,7 @@ module HexaPDF
|
|
286
290
|
Supplement: 0},
|
287
291
|
CIDToGIDMap: :Identity})
|
288
292
|
dict = document.add({Type: :Font, Subtype: :Type0, BaseFont: cid_font[:BaseFont],
|
289
|
-
|
293
|
+
DescendantFonts: [cid_font]})
|
290
294
|
dict.font_wrapper = self
|
291
295
|
|
292
296
|
document.register_listener(:complete_objects) do
|
@@ -294,6 +298,7 @@ module HexaPDF
|
|
294
298
|
embed_font(dict, document)
|
295
299
|
complete_width_information(dict)
|
296
300
|
create_to_unicode_cmap(dict, document)
|
301
|
+
add_encoding_information_cmap(dict, document)
|
297
302
|
end
|
298
303
|
|
299
304
|
dict
|
@@ -306,7 +311,7 @@ module HexaPDF
|
|
306
311
|
return unless @subsetter
|
307
312
|
|
308
313
|
tag = +''
|
309
|
-
data = @encoded_glyphs.each_with_object(''.b) {|(
|
314
|
+
data = @encoded_glyphs.each_with_object(''.b) {|(g, v), s| s << g.id.to_s << v[0] }
|
310
315
|
hash = Digest::MD5.hexdigest(data << @wrapped_font.font_name).to_i(16)
|
311
316
|
while hash != 0 && tag.length < 6
|
312
317
|
hash, mod = hash.divmod(UPPERCASE_LETTERS.length)
|
@@ -336,8 +341,8 @@ module HexaPDF
|
|
336
341
|
# Adds the /DW and /W fields to the CIDFont dictionary.
|
337
342
|
def complete_width_information(dict)
|
338
343
|
default_width = glyph(3, " ").width.to_i
|
339
|
-
widths = @encoded_glyphs.reject {|
|
340
|
-
[(@subsetter ? @subsetter.subset_glyph_id(id) : id),
|
344
|
+
widths = @encoded_glyphs.reject {|g, _| g.width == default_width }.map do |g, _|
|
345
|
+
[(@subsetter ? @subsetter.subset_glyph_id(g.id) : g.id), g.width]
|
341
346
|
end.sort!
|
342
347
|
dict[:DescendantFonts].first.set_widths(widths, default_width: default_width)
|
343
348
|
end
|
@@ -346,9 +351,10 @@ module HexaPDF
|
|
346
351
|
# correctly.
|
347
352
|
def create_to_unicode_cmap(dict, document)
|
348
353
|
stream = HexaPDF::StreamData.new do
|
349
|
-
mapping = @encoded_glyphs.
|
354
|
+
mapping = @encoded_glyphs.map do |glyph, (_, char_code)|
|
350
355
|
# Using 0xFFFD as mentioned in Adobe #5411, last line before section 1.5
|
351
|
-
|
356
|
+
# TODO: glyph.str assumed to consist of single char, No support for multiple chars
|
357
|
+
[char_code, glyph.str.ord || 0xFFFD]
|
352
358
|
end.sort_by!(&:first)
|
353
359
|
HexaPDF::Font::CMap.create_to_unicode_cmap(mapping)
|
354
360
|
end
|
@@ -357,6 +363,25 @@ module HexaPDF
|
|
357
363
|
dict[:ToUnicode] = stream_obj
|
358
364
|
end
|
359
365
|
|
366
|
+
# Adds the /Encoding entry to the +dict+.
|
367
|
+
#
|
368
|
+
# This can either be the identity mapping or, if some Unicode codepoints are mapped to the
|
369
|
+
# same glyph, a custom CMap.
|
370
|
+
def add_encoding_information_cmap(dict, document)
|
371
|
+
mapping = @encoded_glyphs.map do |glyph, (_, char_code)|
|
372
|
+
# Using 0xFFFD as mentioned in Adobe #5411, last line before section 1.5
|
373
|
+
[char_code, (@subsetter ? @subsetter.subset_glyph_id(glyph.id) : glyph.id)]
|
374
|
+
end.sort_by!(&:first)
|
375
|
+
if mapping.all? {|char_code, cid| char_code == cid }
|
376
|
+
dict[:Encoding] = :'Identity-H'
|
377
|
+
else
|
378
|
+
stream = HexaPDF::StreamData.new { HexaPDF::Font::CMap.create_cid_cmap(mapping) }
|
379
|
+
stream_obj = document.add({}, stream: stream)
|
380
|
+
stream_obj.set_filter(:FlateDecode)
|
381
|
+
dict[:Encoding] = stream_obj
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
360
385
|
end
|
361
386
|
|
362
387
|
end
|
@@ -235,6 +235,7 @@ module HexaPDF
|
|
235
235
|
end
|
236
236
|
end
|
237
237
|
|
238
|
+
in_text_object = (canvas.graphics_object == :text)
|
238
239
|
canvas.begin_text
|
239
240
|
tlm = canvas.graphics_state.tlm
|
240
241
|
tx = x - tlm.e
|
@@ -248,7 +249,7 @@ module HexaPDF
|
|
248
249
|
elsif ty.abs < PRECISION
|
249
250
|
canvas.move_text_cursor(offset: [tx, 0], absolute: false)
|
250
251
|
else
|
251
|
-
canvas.move_text_cursor(offset: [x, y])
|
252
|
+
canvas.move_text_cursor(offset: [x, y], absolute: in_text_object)
|
252
253
|
end
|
253
254
|
canvas.show_glyphs_only(items)
|
254
255
|
|
data/lib/hexapdf/object.rb
CHANGED
data/lib/hexapdf/parser.rb
CHANGED
@@ -116,7 +116,11 @@ module HexaPDF
|
|
116
116
|
"the values (#{xref_entry.oid},#{xref_entry.gen}) from the xref")
|
117
117
|
end
|
118
118
|
|
119
|
-
|
119
|
+
if obj.kind_of?(Reference)
|
120
|
+
@document.deref(obj)
|
121
|
+
else
|
122
|
+
@document.wrap(obj, oid: oid, gen: gen, stream: stream)
|
123
|
+
end
|
120
124
|
rescue HexaPDF::MalformedPDFError
|
121
125
|
reconstructed_revision.object(xref_entry) ||
|
122
126
|
@document.wrap(nil, oid: xref_entry.oid, gen: xref_entry.gen)
|
@@ -184,7 +188,7 @@ module HexaPDF
|
|
184
188
|
length = if object[:Length].kind_of?(Integer)
|
185
189
|
object[:Length]
|
186
190
|
elsif object[:Length].kind_of?(Reference)
|
187
|
-
@document.deref(object[:Length])
|
191
|
+
@document.deref(object[:Length])&.value || 0
|
188
192
|
else
|
189
193
|
0
|
190
194
|
end
|