hexapdf 0.47.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +50 -16
- 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 +1 -1
- 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/version.rb +1 -1
- 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 +7 -0
- data/test/hexapdf/test_writer.rb +8 -3
- 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
- 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: 2889ba1d03e2c351efd694b1583063023fff97c0da636ff5103f88538255735c
|
4
|
+
data.tar.gz: 6fb4727db05900e8fccba2ad4e093d1092e17305e5b5616ded97a76cf835673c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 00be8ed2c306a88e5bfc0eada97a7e6bf802ec269e832bb21b3521d4077b18ecad11946ddc6f8a6d575820e66339059e59ba2c4cdd2b74d6c7d6defd0f2f5256
|
7
|
+
data.tar.gz: 94c6a8178ead2a986921b72b07ef5dc388a5fa6a67945573eec921db30c9940d241d3f47591c8fbbf9bdcf313df0dda536f0fc78e0e946e27dfa3bc13dad9a28
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,37 @@
|
|
1
|
+
## 1.0.0 - 2024-10-26
|
2
|
+
|
3
|
+
### Added
|
4
|
+
|
5
|
+
* [HexaPDF::Task::MergeAcroForm] for merging AcroForm information for imported
|
6
|
+
pages
|
7
|
+
* [HexaPDF::Document#write_to_string] and [HexaPDF::Composer#write_to_string]
|
8
|
+
for easily writing a document to a String
|
9
|
+
* [HexaPDF::Font::CMap::Writer#create_cid_cmap] for creating a character code to
|
10
|
+
CID CMap file
|
11
|
+
|
12
|
+
### Changed
|
13
|
+
|
14
|
+
* [HexaPDF::Type::AcroForm::Form] text-like field creation methods to always set
|
15
|
+
a default appearance string and the quadding
|
16
|
+
* Convenience methods for accessing resources to not add the deprecated /ProcSet
|
17
|
+
entry by default
|
18
|
+
* [HexaPDF::DigitalSignature::CMSHandler] to add informational output regarding
|
19
|
+
the certificate chain on verification
|
20
|
+
* Validation of [HexaPDF::Type::FontType1] to ensure correct /Encoding value
|
21
|
+
|
22
|
+
### Fixed
|
23
|
+
|
24
|
+
* [HexaPDF::DigitalSignature::Signature#signed_data] to work for invalid offsets
|
25
|
+
* [HexaPDF::DigitalSignature::Signing::DefaultHandler] to update the document's
|
26
|
+
version to 2.0 when using PAdES
|
27
|
+
* Parsing of invalid `)` character in PDF objects and content streams
|
28
|
+
* Handling of files that contain stream length values that are indirect objects
|
29
|
+
that do not exist
|
30
|
+
* [HexaPDF::Font::TrueTypeWrapper] to correctly handle the situation when
|
31
|
+
multiple codepoints refer to the same glyph ID
|
32
|
+
* [HexaPDF::Type::Page#contents] to handle null values in /Contents array
|
33
|
+
|
34
|
+
|
1
35
|
## 0.47.0 - 2024-09-07
|
2
36
|
|
3
37
|
### Added
|
@@ -53,7 +87,7 @@
|
|
53
87
|
signatures
|
54
88
|
* [HexaPDF::DigitalSignature::CMSHandler#signing_time] to use time from an
|
55
89
|
embedded timestamp authority signature if possible
|
56
|
-
*
|
90
|
+
* HexaPDF::Layout::Box#fit to return success for boxes with content
|
57
91
|
width/height of zero
|
58
92
|
* [HexaPDF::Importer::copy] to optionally allow copying the catalog and page
|
59
93
|
tree nodes
|
@@ -61,7 +95,7 @@
|
|
61
95
|
### Fixed
|
62
96
|
|
63
97
|
* Setting of correct x-position in fit result for boxes with flow positioning
|
64
|
-
*
|
98
|
+
* HexaPDF::Layout::ListBox#fit to respect the set height
|
65
99
|
* CLI command `hexapdf inspect` to work in case of missing Unicde mappings
|
66
100
|
* [HexaPDF::Type::AcroForm::Form#delete_field] to correctly work for fields with
|
67
101
|
an embedded widget
|
@@ -80,7 +114,7 @@
|
|
80
114
|
|
81
115
|
### Changed
|
82
116
|
|
83
|
-
*
|
117
|
+
* HexaPDF::Layout::Box#fit to set width/height correctly for boxes with
|
84
118
|
position `:flow`
|
85
119
|
|
86
120
|
### Fixed
|
@@ -126,9 +160,9 @@
|
|
126
160
|
|
127
161
|
### Fixed
|
128
162
|
|
129
|
-
*
|
163
|
+
* HexaPDF::Layout::TextBox#fit to correctly calculate width in case of flowing
|
130
164
|
text around other boxes
|
131
|
-
*
|
165
|
+
* HexaPDF::Layout::TextBox#draw to correctly draw border, background... on
|
132
166
|
boxes using position 'flow'
|
133
167
|
* Comparison of Hash with [HexaPDF::Dictionary] objects by implementing
|
134
168
|
`#to_hash`
|
@@ -182,7 +216,7 @@
|
|
182
216
|
JavaScript action that formats the field's value
|
183
217
|
* [HexaPDF::Type::AcroForm::TextField#set_calculate_action] for setting a
|
184
218
|
JavaScript action that calculates the field's value
|
185
|
-
* [HexaPDF::Type::AcroForm#recalculate_fields] for recalculating fields
|
219
|
+
* [HexaPDF::Type::AcroForm::Form#recalculate_fields] for recalculating fields
|
186
220
|
|
187
221
|
### Changed
|
188
222
|
|
@@ -239,7 +273,7 @@
|
|
239
273
|
|
240
274
|
### Changed
|
241
275
|
|
242
|
-
*
|
276
|
+
* HexaPDF::Layout::Frame::FitResult#draw to provide better optional content
|
243
277
|
group names
|
244
278
|
|
245
279
|
### Fixed
|
@@ -318,8 +352,8 @@
|
|
318
352
|
|
319
353
|
### Changed
|
320
354
|
|
321
|
-
*
|
322
|
-
*
|
355
|
+
* HexaPDF::Layout::Frame::FitResult#draw to allow drawing at an offset
|
356
|
+
* HexaPDF::Layout::Box#fit to delegate the actual content fitting to the
|
323
357
|
`#fit_content` method
|
324
358
|
* [HexaPDF::Document::Layout#box] to allow using the block as drawing block for
|
325
359
|
the base box class
|
@@ -396,8 +430,8 @@
|
|
396
430
|
|
397
431
|
### Fixed
|
398
432
|
|
399
|
-
*
|
400
|
-
*
|
433
|
+
* HexaPDF::Layout::ColumnBox#fit to correctly take initial height into account
|
434
|
+
* HexaPDF::Layout::ColumnBox#fit to ensure correct results in case the
|
401
435
|
requested dimensions are larger than the current region
|
402
436
|
* [HexaPDF::Document::Layout#formatted_text_box] to correctly handle properties
|
403
437
|
* [HexaPDF::Layout::Frame#fit] to raise an error if an invalid value for the
|
@@ -443,7 +477,7 @@
|
|
443
477
|
context argument (a page or Form XObject instance)
|
444
478
|
* [HexaPDF::Layout::ListBox] to use its 'fill_color' style property for the item
|
445
479
|
marker color
|
446
|
-
*
|
480
|
+
* HexaPDF::Layout::Frame::FitResult#draw to use optional content groups for
|
447
481
|
debug output
|
448
482
|
|
449
483
|
### Fixed
|
@@ -452,7 +486,7 @@
|
|
452
486
|
default range starting at page 1
|
453
487
|
* [HexaPDF::Type::Page#flatten_annotations] to correctly handle scaled
|
454
488
|
appearances
|
455
|
-
* Using an unknown style name in [HexaPDF
|
489
|
+
* Using an unknown style name in [HexaPDF::Document::Layout] method by providing
|
456
490
|
a useful error message
|
457
491
|
* [HexaPDF::Layout::Box::new] to ensure that the properties attribute is always
|
458
492
|
a hash
|
@@ -513,7 +547,7 @@
|
|
513
547
|
final box positions into account
|
514
548
|
* [HexaPDF::Content::Canvas#text] to set the leading only when multiple lines
|
515
549
|
are drawn
|
516
|
-
*
|
550
|
+
* HexaPDF::Layout::TextBox#split to use float comparison
|
517
551
|
* Validation of standard encryption dictionary to auto-correct invalid /U and /O
|
518
552
|
fields in case they are padded with zeros
|
519
553
|
* [HexaPDF::Document#wrap] handling of sub-type mapping in case of missing type
|
@@ -930,7 +964,7 @@
|
|
930
964
|
* [HexaPDF::Layout::WidthFromPolygon] to work correctly in case of very small
|
931
965
|
floating point errors
|
932
966
|
* HexaPDF::Layout::TextFragment#inspect to work in case of interspersed numbers
|
933
|
-
*
|
967
|
+
* HexaPDF::Layout::TextBox#split to work for position :flow when box is wider
|
934
968
|
than the initial available width
|
935
969
|
* [HexaPDF::Layout::Frame#fit] to create minimally sized mask rectangles
|
936
970
|
* [HexaPDF::Content::GraphicObject::Geom2D] to close the path when drawing
|
@@ -1866,7 +1900,7 @@
|
|
1866
1900
|
objects
|
1867
1901
|
* [HexaPDF::Revision#each_modified_object] for iterating over all modified
|
1868
1902
|
objects of a revision
|
1869
|
-
* [HexaPDF::Layout::Box#split] and
|
1903
|
+
* [HexaPDF::Layout::Box#split] and HexaPDF::Layout::TextBox#split for
|
1870
1904
|
splitting a box into two parts
|
1871
1905
|
* [HexaPDF::Layout::Frame#full?] for testing whether the frame has any space
|
1872
1906
|
left
|
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