hexapdf 0.18.0 → 0.19.3
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +46 -2
- data/lib/hexapdf/cli/command.rb +7 -1
- data/lib/hexapdf/content/canvas.rb +2 -2
- data/lib/hexapdf/content/graphics_state.rb +167 -25
- data/lib/hexapdf/dictionary_fields.rb +1 -1
- data/lib/hexapdf/encryption/standard_security_handler.rb +1 -2
- data/lib/hexapdf/layout/style.rb +2 -1
- data/lib/hexapdf/parser.rb +21 -9
- data/lib/hexapdf/task/optimize.rb +46 -3
- data/lib/hexapdf/type/font.rb +5 -0
- data/lib/hexapdf/type/font_type3.rb +20 -0
- data/lib/hexapdf/version.rb +1 -1
- data/lib/hexapdf/writer.rb +8 -2
- data/test/hexapdf/content/test_graphics_state.rb +9 -1
- data/test/hexapdf/content/test_operator.rb +8 -3
- data/test/hexapdf/encryption/test_standard_security_handler.rb +8 -6
- data/test/hexapdf/layout/test_style.rb +11 -0
- data/test/hexapdf/task/test_optimize.rb +26 -0
- data/test/hexapdf/test_dictionary_fields.rb +1 -0
- data/test/hexapdf/test_parser.rb +14 -0
- data/test/hexapdf/test_writer.rb +42 -13
- data/test/hexapdf/type/test_font.rb +4 -0
- data/test/hexapdf/type/test_font_type3.rb +16 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 85c063a63af9729acc10a54ef53fba69d73b75f1c06ff0e6de246df920e17dd9
|
4
|
+
data.tar.gz: dcea10d0ccfe66282c92e6bb41c1d57927d525e1618753d598a910546c8a8e82
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4f1d468375c4ce336e55a09d897de9a8c95babcfa1b4441cc04a9dc712435c64906016647baa030f5695f9434d18214c30bca746f245b53a69321139f63256b9
|
7
|
+
data.tar.gz: 1f1895ca7ad46bae1bf33790d4c8daa7f72adfcc00cc26e1611b7ece64d7a5a76e7f1e06be4022bb58f5dbd84154e9846cb4feb94f727c9abe99f5b7c8b87fcf
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,47 @@
|
|
1
|
+
## 0.19.3 - 2021-12-14
|
2
|
+
|
3
|
+
### Fixed
|
4
|
+
|
5
|
+
* Handling of invalid files where the "startxref" keyword and its value are on
|
6
|
+
the same line
|
7
|
+
|
8
|
+
|
9
|
+
## 0.19.2 - 2021-12-14
|
10
|
+
|
11
|
+
### Fixed
|
12
|
+
|
13
|
+
* Set the trailer's ID field to an array of two empty strings when decrypting in
|
14
|
+
case it is missing
|
15
|
+
* Incremental writing when one of the existing revisions contains a
|
16
|
+
cross-reference stream
|
17
|
+
|
18
|
+
|
19
|
+
## 0.19.1 - 2021-12-12
|
20
|
+
|
21
|
+
### Added
|
22
|
+
|
23
|
+
* [HexaPDF::Type::FontType3#bounding_box] to fix content stream processing error
|
24
|
+
|
25
|
+
### Fixed
|
26
|
+
|
27
|
+
* Calculation of scaled font size for [HexaPDF::Content::GraphicsState] and
|
28
|
+
[HexaPDF::Layout::Style] when Type3 fonts are used
|
29
|
+
|
30
|
+
|
31
|
+
## 0.19.0 - 2021-11-24
|
32
|
+
|
33
|
+
### Added
|
34
|
+
|
35
|
+
* Page resource pruning to the optimization task
|
36
|
+
* An option for page resources pruning to the optimization options of the
|
37
|
+
`hexapdf` command
|
38
|
+
|
39
|
+
### Fixed
|
40
|
+
|
41
|
+
* Handling of invalid date strings with a minute time zone offset greater than
|
42
|
+
59
|
43
|
+
|
44
|
+
|
1
45
|
## 0.18.0 - 2021-11-04
|
2
46
|
|
3
47
|
### Added
|
@@ -6,7 +50,7 @@
|
|
6
50
|
device colors in parts other than the canvas
|
7
51
|
* [HexaPDF::Type::AcroForm::VariableTextField::create_appearance_string] for
|
8
52
|
centralized creation of appearance strings
|
9
|
-
* [HexaPDF::Object
|
53
|
+
* [HexaPDF::Object::make_direct] for making objects and all parts of them direct
|
10
54
|
instead of indirect
|
11
55
|
|
12
56
|
### Changed
|
@@ -26,7 +70,7 @@
|
|
26
70
|
dictionary are indirect objects
|
27
71
|
* [HexaPDF::Content::GraphicObject::EndpointArc] to correctly determine the
|
28
72
|
start and end points
|
29
|
-
*
|
73
|
+
* HexaPDF::Dictionary#perform_validation to correctly handle objects that
|
30
74
|
should not be indirect objects
|
31
75
|
|
32
76
|
|
data/lib/hexapdf/cli/command.rb
CHANGED
@@ -66,6 +66,7 @@ module HexaPDF
|
|
66
66
|
@out_options.xref_streams = :preserve
|
67
67
|
@out_options.streams = :preserve
|
68
68
|
@out_options.optimize_fonts = false
|
69
|
+
@out_options.prune_page_resources = false
|
69
70
|
|
70
71
|
@out_options.encryption = :preserve
|
71
72
|
@out_options.enc_user_pwd = @out_options.enc_owner_pwd = nil
|
@@ -169,6 +170,10 @@ module HexaPDF
|
|
169
170
|
"time; default: #{@out_options.compress_pages})") do |c|
|
170
171
|
@out_options.compress_pages = c
|
171
172
|
end
|
173
|
+
options.on("--[no-]prune-page-resources", "Prunes unused objects from the page resources " \
|
174
|
+
"(may take a long time; default: #{@out_options.prune_page_resources})") do |c|
|
175
|
+
@out_options.prune_page_resources = c
|
176
|
+
end
|
172
177
|
options.on("--[no-]optimize-fonts", "Optimize embedded font files; " \
|
173
178
|
"default: #{@out_options.optimize_fonts})") do |o|
|
174
179
|
@out_options.optimize_fonts = o
|
@@ -236,7 +241,8 @@ module HexaPDF
|
|
236
241
|
doc.task(:optimize, compact: @out_options.compact,
|
237
242
|
object_streams: @out_options.object_streams,
|
238
243
|
xref_streams: @out_options.xref_streams,
|
239
|
-
compress_pages: @out_options.compress_pages
|
244
|
+
compress_pages: @out_options.compress_pages,
|
245
|
+
prune_page_resources: @out_options.prune_page_resources)
|
240
246
|
if @out_options.streams != :preserve || @out_options.optimize_fonts
|
241
247
|
doc.each(only_current: false) do |obj|
|
242
248
|
optimize_stream(obj)
|
@@ -589,7 +589,7 @@ module HexaPDF
|
|
589
589
|
#
|
590
590
|
# The line cap style specifies how the ends of stroked open paths should look like.
|
591
591
|
#
|
592
|
-
# The +style+ parameter can be one of:
|
592
|
+
# The +style+ parameter can be one of (also see LineCapStyle):
|
593
593
|
#
|
594
594
|
# :butt or 0::
|
595
595
|
# Stroke is squared off at the endpoint of a path.
|
@@ -641,7 +641,7 @@ module HexaPDF
|
|
641
641
|
#
|
642
642
|
# The line join style specifies the shape that is used at the corners of stroked paths.
|
643
643
|
#
|
644
|
-
# The +style+ parameter can be one of:
|
644
|
+
# The +style+ parameter can be one of (also see LineJoinStyle):
|
645
645
|
#
|
646
646
|
# :miter or 0::
|
647
647
|
# The outer lines of the two segments continue until the meet at an angle.
|
@@ -73,7 +73,7 @@ module HexaPDF
|
|
73
73
|
end
|
74
74
|
|
75
75
|
# Defines all available line cap styles as constants. Each line cap style is an instance of
|
76
|
-
# NamedValue. For use with Content::
|
76
|
+
# NamedValue, see ::normalize. For use with e.g. Content::Canvas#line_cap_style.
|
77
77
|
#
|
78
78
|
# See: PDF1.7 s8.4.3.3
|
79
79
|
module LineCapStyle
|
@@ -95,18 +95,39 @@ module HexaPDF
|
|
95
95
|
end
|
96
96
|
|
97
97
|
# Stroke is squared off at the endpoint of a path.
|
98
|
+
#
|
99
|
+
# Specify as 0 or :butt.
|
100
|
+
#
|
101
|
+
# #>pdf-small-hide
|
102
|
+
# canvas.line_cap_style(:butt)
|
103
|
+
# canvas.line_width(10).line(50, 20, 50, 80).stroke
|
104
|
+
# canvas.stroke_color("white").line_width(1).line(50, 20, 50, 80).stroke
|
98
105
|
BUTT_CAP = NamedValue.new(:butt, 0)
|
99
106
|
|
100
107
|
# A semicircular arc is drawn at the endpoint of a path.
|
108
|
+
#
|
109
|
+
# Specify as 1 or :round.
|
110
|
+
#
|
111
|
+
# #>pdf-small-hide
|
112
|
+
# canvas.line_cap_style(:round)
|
113
|
+
# canvas.line_width(10).line(50, 20, 50, 80).stroke
|
114
|
+
# canvas.stroke_color("white").line_width(1).line(50, 20, 50, 80).stroke
|
101
115
|
ROUND_CAP = NamedValue.new(:round, 1)
|
102
116
|
|
103
117
|
# The stroke continues half the line width beyond the endpoint of a path.
|
118
|
+
#
|
119
|
+
# Specify as 2 or :projecting_square.
|
120
|
+
#
|
121
|
+
# #>pdf-small-hide
|
122
|
+
# canvas.line_cap_style(:projecting_square)
|
123
|
+
# canvas.line_width(10).line(50, 20, 50, 80).stroke
|
124
|
+
# canvas.stroke_color("white").line_width(1).line(50, 20, 50, 80).stroke
|
104
125
|
PROJECTING_SQUARE_CAP = NamedValue.new(:projecting_square, 2)
|
105
126
|
|
106
127
|
end
|
107
128
|
|
108
129
|
# Defines all available line join styles as constants. Each line join style is an instance of
|
109
|
-
# NamedValue
|
130
|
+
# NamedValue, see ::normalize For use with e.g. Content::Canvas#line_join_style.
|
110
131
|
#
|
111
132
|
# See: PDF1.7 s8.4.3.4
|
112
133
|
module LineJoinStyle
|
@@ -127,20 +148,47 @@ module HexaPDF
|
|
127
148
|
end
|
128
149
|
end
|
129
150
|
|
130
|
-
# The outer lines of the two segments continue until
|
151
|
+
# The outer lines of the two segments continue until they meet at an angle.
|
152
|
+
#
|
153
|
+
# Specify as 0 or :miter.
|
154
|
+
#
|
155
|
+
# #>pdf-small-hide
|
156
|
+
# canvas.line_join_style(:miter)
|
157
|
+
# canvas.line_width(10).
|
158
|
+
# polyline(20, 20, 50, 80, 80, 20).stroke
|
159
|
+
# canvas.stroke_color("white").line_width(1).line_join_style(:bevel).
|
160
|
+
# polyline(20, 20, 50, 80, 80, 20).stroke
|
131
161
|
MITER_JOIN = NamedValue.new(:miter, 0)
|
132
162
|
|
133
163
|
# An arc of a circle is drawn around the point where the segments meet.
|
164
|
+
#
|
165
|
+
# Specify as 1 or :round.
|
166
|
+
#
|
167
|
+
# #>pdf-small-hide
|
168
|
+
# canvas.line_join_style(:round)
|
169
|
+
# canvas.line_width(10).
|
170
|
+
# polyline(20, 20, 50, 80, 80, 20).stroke
|
171
|
+
# canvas.stroke_color("white").line_width(1).line_join_style(:bevel).
|
172
|
+
# polyline(20, 20, 50, 80, 80, 20).stroke
|
134
173
|
ROUND_JOIN = NamedValue.new(:round, 1)
|
135
174
|
|
136
|
-
# The two segments are finished with butt caps and the space between the ends is filled with
|
137
|
-
#
|
175
|
+
# The two segments are finished with butt caps and the space between the ends is filled with a
|
176
|
+
# triangle.
|
177
|
+
#
|
178
|
+
# Specify as 2 or :bevel.
|
179
|
+
#
|
180
|
+
# #>pdf-small-hide
|
181
|
+
# canvas.line_join_style(:bevel)
|
182
|
+
# canvas.line_width(10).
|
183
|
+
# polyline(20, 20, 50, 80, 80, 20).stroke
|
184
|
+
# canvas.stroke_color("white").line_width(1).line_join_style(:bevel).
|
185
|
+
# polyline(20, 20, 50, 80, 80, 20).stroke
|
138
186
|
BEVEL_JOIN = NamedValue.new(:bevel, 2)
|
139
187
|
|
140
188
|
end
|
141
189
|
|
142
|
-
# The line dash pattern defines how a line should be dashed. For use with
|
143
|
-
# Content::
|
190
|
+
# The line dash pattern defines how a line should be dashed. For use with e.g.
|
191
|
+
# Content::Canvas#line_dash_pattern.
|
144
192
|
#
|
145
193
|
# A dash pattern consists of two parts: the dash array and the dash phase. The dash array
|
146
194
|
# defines the length of alternating dashes and gaps (important: starting with dashes). And the
|
@@ -159,6 +207,12 @@ module HexaPDF
|
|
159
207
|
# See: PDF1.7 s8.4.3.6
|
160
208
|
class LineDashPattern
|
161
209
|
|
210
|
+
# :call-seq:
|
211
|
+
# LineDashPattern.normalize(line_dash_pattern) -> line_dash_pattern
|
212
|
+
# LineDashPattern.normalize(array, phase = 0) -> LineDashPattern.new(array, phase)
|
213
|
+
# LineDashPattern.normalize(number, phase = 0) -> LineDashPattern.new([number], phase)
|
214
|
+
# LineDashPattern.normalize(0) -> LineDashPattern.new
|
215
|
+
#
|
162
216
|
# Returns the arguments normalized to a valid LineDashPattern instance.
|
163
217
|
#
|
164
218
|
# If +array+ is 0, the default line dash pattern representing a solid line will be used. If it
|
@@ -206,8 +260,8 @@ module HexaPDF
|
|
206
260
|
|
207
261
|
end
|
208
262
|
|
209
|
-
# Defines all available rendering intents as constants. For use with
|
210
|
-
# Content::
|
263
|
+
# Defines all available rendering intents as constants. For use with e.g.
|
264
|
+
# Content::Canvas#rendering_intent.
|
211
265
|
#
|
212
266
|
# See: PDF1.7 s8.6.5.8
|
213
267
|
module RenderingIntent
|
@@ -241,7 +295,7 @@ module HexaPDF
|
|
241
295
|
end
|
242
296
|
|
243
297
|
# Defines all available text rendering modes as constants. Each text rendering mode is an
|
244
|
-
# instance of NamedValue. For use with Content::
|
298
|
+
# instance of NamedValue. For use with e.g. Content::Canvas#text_rendering_mode.
|
245
299
|
#
|
246
300
|
# See: PDF1.7 s9.3.6
|
247
301
|
module TextRenderingMode
|
@@ -272,28 +326,97 @@ module HexaPDF
|
|
272
326
|
end
|
273
327
|
end
|
274
328
|
|
275
|
-
# Fill text
|
329
|
+
# Fill text.
|
330
|
+
#
|
331
|
+
# Specify as 0 or :fill.
|
332
|
+
#
|
333
|
+
# #>pdf-small-hide
|
334
|
+
# canvas.font("Helvetica", size: 13)
|
335
|
+
# canvas.stroke_color("green").line_width(0.5)
|
336
|
+
# canvas.text_rendering_mode(:fill)
|
337
|
+
# canvas.text("#{canvas.text_rendering_mode.name}", at: [10, 50])
|
276
338
|
FILL = NamedValue.new(:fill, 0)
|
277
339
|
|
278
|
-
# Stroke text
|
340
|
+
# Stroke text.
|
341
|
+
#
|
342
|
+
# Specify as 1 or :stroke.
|
343
|
+
#
|
344
|
+
# #>pdf-small-hide
|
345
|
+
# canvas.font("Helvetica", size: 13)
|
346
|
+
# canvas.stroke_color("green").line_width(0.5)
|
347
|
+
# canvas.text_rendering_mode(:stroke)
|
348
|
+
# canvas.text("#{canvas.text_rendering_mode.name}", at: [10, 50])
|
279
349
|
STROKE = NamedValue.new(:stroke, 1)
|
280
350
|
|
281
|
-
# Fill, then stroke text
|
351
|
+
# Fill, then stroke text.
|
352
|
+
#
|
353
|
+
# Specify as 2 or :fill_stroke.
|
354
|
+
#
|
355
|
+
# #>pdf-small-hide
|
356
|
+
# canvas.font("Helvetica", size: 13)
|
357
|
+
# canvas.stroke_color("green").line_width(0.5)
|
358
|
+
# canvas.text_rendering_mode(:fill_stroke)
|
359
|
+
# canvas.text("#{canvas.text_rendering_mode.name}", at: [10, 50])
|
282
360
|
FILL_STROKE = NamedValue.new(:fill_stroke, 2)
|
283
361
|
|
284
|
-
# Neither fill nor stroke text (invisible)
|
362
|
+
# Neither fill nor stroke text (invisible).
|
363
|
+
#
|
364
|
+
# Specify as 3 or :invisible.
|
365
|
+
#
|
366
|
+
# #>pdf-small-hide
|
367
|
+
# canvas.font("Helvetica", size: 13)
|
368
|
+
# canvas.stroke_color("green").line_width(0.5)
|
369
|
+
# canvas.text_rendering_mode(:invisible)
|
370
|
+
# canvas.text("#{canvas.text_rendering_mode.name}", at: [10, 50])
|
371
|
+
# canvas.stroke_color("red").line_width(20).line(30, 20, 30, 80).stroke
|
285
372
|
INVISIBLE = NamedValue.new(:invisible, 3)
|
286
373
|
|
287
|
-
# Fill text and add to path for clipping
|
374
|
+
# Fill text and add to path for clipping.
|
375
|
+
#
|
376
|
+
# Specify as 4 or :fill_clip.
|
377
|
+
#
|
378
|
+
# #>pdf-small-hide
|
379
|
+
# canvas.font("Helvetica", size: 13)
|
380
|
+
# canvas.stroke_color("green").line_width(0.5)
|
381
|
+
# canvas.text_rendering_mode(:fill_clip)
|
382
|
+
# canvas.text("#{canvas.text_rendering_mode.name}", at: [10, 50])
|
383
|
+
# canvas.stroke_color("red").line_width(20).line(30, 20, 30, 80).stroke
|
288
384
|
FILL_CLIP = NamedValue.new(:fill_clip, 4)
|
289
385
|
|
290
|
-
# Stroke text and add to path for clipping
|
386
|
+
# Stroke text and add to path for clipping.
|
387
|
+
#
|
388
|
+
# Specify as 5 or :stroke_clip.
|
389
|
+
#
|
390
|
+
# #>pdf-small-hide
|
391
|
+
# canvas.font("Helvetica", size: 13)
|
392
|
+
# canvas.stroke_color("green").line_width(0.5)
|
393
|
+
# canvas.text_rendering_mode(:stroke_clip)
|
394
|
+
# canvas.text("#{canvas.text_rendering_mode.name}", at: [10, 50])
|
395
|
+
# canvas.stroke_color("red").line_width(20).line(30, 20, 30, 80).stroke
|
291
396
|
STROKE_CLIP = NamedValue.new(:stroke_clip, 5)
|
292
397
|
|
293
|
-
# Fill, then stroke text and add to path for clipping
|
398
|
+
# Fill, then stroke text and add to path for clipping.
|
399
|
+
#
|
400
|
+
# Specify as 6 or :fill_stroke_clip.
|
401
|
+
#
|
402
|
+
# #>pdf-small-hide
|
403
|
+
# canvas.font("Helvetica", size: 13)
|
404
|
+
# canvas.stroke_color("green").line_width(0.5)
|
405
|
+
# canvas.text_rendering_mode(:fill_stroke_clip)
|
406
|
+
# canvas.text("#{canvas.text_rendering_mode.name}", at: [10, 50])
|
407
|
+
# canvas.stroke_color("red").line_width(20).line(30, 20, 30, 80).stroke
|
294
408
|
FILL_STROKE_CLIP = NamedValue.new(:fill_stroke_clip, 6)
|
295
409
|
|
296
|
-
# Add text to path for clipping
|
410
|
+
# Add text to path for clipping.
|
411
|
+
#
|
412
|
+
# Specify as 7 or :clip.
|
413
|
+
#
|
414
|
+
# #>pdf-small-hide
|
415
|
+
# canvas.font("Helvetica", size: 13)
|
416
|
+
# canvas.stroke_color("green").line_width(0.5)
|
417
|
+
# canvas.text_rendering_mode(:clip)
|
418
|
+
# canvas.text("#{canvas.text_rendering_mode.name}", at: [10, 50])
|
419
|
+
# canvas.stroke_color("red").line_width(20).line(30, 20, 30, 80).stroke
|
297
420
|
CLIP = NamedValue.new(:clip, 7)
|
298
421
|
|
299
422
|
end
|
@@ -389,7 +512,7 @@ module HexaPDF
|
|
389
512
|
attr_accessor :leading
|
390
513
|
|
391
514
|
# The font for the text.
|
392
|
-
|
515
|
+
attr_reader :font
|
393
516
|
|
394
517
|
# The font size.
|
395
518
|
attr_reader :font_size
|
@@ -415,23 +538,25 @@ module HexaPDF
|
|
415
538
|
|
416
539
|
# The scaled character spacing used in glyph displacement calculations.
|
417
540
|
#
|
418
|
-
# This returns the
|
541
|
+
# This returns the character spacing multiplied by #scaled_horizontal_scaling.
|
419
542
|
#
|
420
543
|
# See PDF1.7 s9.4.4
|
421
544
|
attr_reader :scaled_character_spacing
|
422
545
|
|
423
546
|
# The scaled word spacing used in glyph displacement calculations.
|
424
547
|
#
|
425
|
-
# This returns the
|
548
|
+
# This returns the word spacing multiplied by #scaled_horizontal_scaling.
|
426
549
|
#
|
427
550
|
# See PDF1.7 s9.4.4
|
428
551
|
attr_reader :scaled_word_spacing
|
429
552
|
|
430
553
|
# The scaled font size used in glyph displacement calculations.
|
431
554
|
#
|
432
|
-
# This returns the
|
555
|
+
# This returns the font size multiplied by the scaling factor from glyph space to text space
|
556
|
+
# (0.001 for all fonts except Type3 fonts or the scaling specified in /FontMatrix for Type3
|
557
|
+
# fonts) and multiplied by #scaled_horizontal_scaling.
|
433
558
|
#
|
434
|
-
# See PDF1.7 s9.4.4
|
559
|
+
# See PDF1.7 s9.4.4, HexaPDF::Type::FontType3
|
435
560
|
attr_reader :scaled_font_size
|
436
561
|
|
437
562
|
# The scaled horizontal scaling used in glyph displacement calculations.
|
@@ -542,6 +667,15 @@ module HexaPDF
|
|
542
667
|
self.fill_color = color_space.default_color
|
543
668
|
end
|
544
669
|
|
670
|
+
##
|
671
|
+
# :attr_writer: font
|
672
|
+
#
|
673
|
+
# Sets the font and updates the glyph space to text space scaling.
|
674
|
+
def font=(font)
|
675
|
+
@font = font
|
676
|
+
update_scaled_font_size
|
677
|
+
end
|
678
|
+
|
545
679
|
##
|
546
680
|
# :attr_writer: character_spacing
|
547
681
|
#
|
@@ -566,7 +700,7 @@ module HexaPDF
|
|
566
700
|
# Sets the font size and updates the scaled font size.
|
567
701
|
def font_size=(size)
|
568
702
|
@font_size = size
|
569
|
-
|
703
|
+
update_scaled_font_size
|
570
704
|
end
|
571
705
|
|
572
706
|
##
|
@@ -579,7 +713,15 @@ module HexaPDF
|
|
579
713
|
@scaled_horizontal_scaling = scaling / 100.0
|
580
714
|
@scaled_character_spacing = @character_spacing * @scaled_horizontal_scaling
|
581
715
|
@scaled_word_spacing = @word_spacing * @scaled_horizontal_scaling
|
582
|
-
|
716
|
+
update_scaled_font_size
|
717
|
+
end
|
718
|
+
|
719
|
+
private
|
720
|
+
|
721
|
+
# Updates the cached value for the scaled font size.
|
722
|
+
def update_scaled_font_size
|
723
|
+
@scaled_font_size = @font_size * (@font&.glyph_scaling_factor || 0.001) *
|
724
|
+
@scaled_horizontal_scaling
|
583
725
|
end
|
584
726
|
|
585
727
|
end
|
@@ -293,7 +293,7 @@ module HexaPDF
|
|
293
293
|
end
|
294
294
|
|
295
295
|
# :nodoc:
|
296
|
-
DATE_RE = /\AD:(\d{4})(\d\d)?(\d\d)?(\d\d)?(\d\d)?(\d\d)?([Z+-])?(?:(\d\d)(?:'|'(\d
|
296
|
+
DATE_RE = /\AD:(\d{4})(\d\d)?(\d\d)?(\d\d)?(\d\d)?(\d\d)?([Z+-])?(?:(\d\d)(?:'|'([0-5]\d)'?|\z)?)?\z/n
|
297
297
|
|
298
298
|
# Checks if the given object is a string and converts into a Time object if possible.
|
299
299
|
# Otherwise returns +nil+.
|
@@ -328,8 +328,7 @@ module HexaPDF
|
|
328
328
|
raise(HexaPDF::UnsupportedEncryptionError,
|
329
329
|
"Invalid /R value for standard security handler")
|
330
330
|
elsif dict[:R] <= 4 && !document.trailer[:ID].kind_of?(PDFArray)
|
331
|
-
|
332
|
-
"Document ID for needed for decryption")
|
331
|
+
document.trailer[:ID] = ['', '']
|
333
332
|
end
|
334
333
|
@trailer_id_hash = trailer_id_hash
|
335
334
|
|
data/lib/hexapdf/layout/style.rb
CHANGED
@@ -1069,7 +1069,8 @@ module HexaPDF
|
|
1069
1069
|
|
1070
1070
|
# The font size scaled appropriately.
|
1071
1071
|
def scaled_font_size
|
1072
|
-
@scaled_font_size ||= calculated_font_size
|
1072
|
+
@scaled_font_size ||= calculated_font_size * font.pdf_object.glyph_scaling_factor *
|
1073
|
+
scaled_horizontal_scaling
|
1073
1074
|
end
|
1074
1075
|
|
1075
1076
|
# The character spacing scaled appropriately.
|
data/lib/hexapdf/parser.rb
CHANGED
@@ -62,9 +62,15 @@ module HexaPDF
|
|
62
62
|
@object_stream_data = {}
|
63
63
|
@reconstructed_revision = nil
|
64
64
|
@in_reconstruct_revision = false
|
65
|
+
@contains_xref_streams = false
|
65
66
|
retrieve_pdf_header_offset_and_version
|
66
67
|
end
|
67
68
|
|
69
|
+
# Returns +true+ if the PDF file contains cross-reference streams.
|
70
|
+
def contains_xref_streams?
|
71
|
+
@contains_xref_streams
|
72
|
+
end
|
73
|
+
|
68
74
|
# Loads the indirect (potentially compressed) object specified by the given cross-reference
|
69
75
|
# entry.
|
70
76
|
#
|
@@ -230,6 +236,7 @@ module HexaPDF
|
|
230
236
|
maybe_raise("Cross-reference stream doesn't contain entry for itself", pos: pos)
|
231
237
|
xref_section.add_in_use_entry(obj.oid, obj.gen, pos)
|
232
238
|
end
|
239
|
+
@contains_xref_streams = true
|
233
240
|
end
|
234
241
|
xref_section.delete(0)
|
235
242
|
[xref_section, trailer]
|
@@ -335,7 +342,8 @@ module HexaPDF
|
|
335
342
|
step_size = 1024
|
336
343
|
pos = @io.pos
|
337
344
|
eof_not_found = pos == 0
|
338
|
-
startxref_missing = false
|
345
|
+
startxref_missing = startxref_mangled = false
|
346
|
+
startxref_offset = nil
|
339
347
|
|
340
348
|
while pos != 0
|
341
349
|
@io.pos = [pos - step_size, 0].max
|
@@ -343,27 +351,31 @@ module HexaPDF
|
|
343
351
|
lines = @io.read(step_size + 40).split(/[\r\n]+/)
|
344
352
|
|
345
353
|
eof_index = lines.rindex {|l| l.strip == '%%EOF' }
|
346
|
-
|
354
|
+
if !eof_index
|
347
355
|
eof_not_found = true
|
348
|
-
|
349
|
-
|
350
|
-
|
356
|
+
elsif lines[eof_index - 1].strip =~ /\Astartxref\s(\d+)\z/
|
357
|
+
startxref_offset = $1.to_i
|
358
|
+
startxref_mangled = true
|
359
|
+
break # we found it even if it the syntax is not entirely correct
|
360
|
+
elsif eof_index < 2 || lines[eof_index - 2].strip != "startxref"
|
351
361
|
startxref_missing = true
|
352
|
-
|
362
|
+
else
|
363
|
+
startxref_offset = lines[eof_index - 1].to_i
|
364
|
+
break # we found it
|
353
365
|
end
|
354
|
-
|
355
|
-
break # we found the startxref offset
|
356
366
|
end
|
357
367
|
|
358
368
|
if eof_not_found
|
359
369
|
maybe_raise("PDF file trailer with end-of-file marker not found", pos: pos,
|
360
370
|
force: !eof_index)
|
371
|
+
elsif startxref_mangled
|
372
|
+
maybe_raise("PDF file trailer keyword startxref on same line as value", pos: pos)
|
361
373
|
elsif startxref_missing
|
362
374
|
maybe_raise("PDF file trailer is missing startxref keyword", pos: pos,
|
363
375
|
force: eof_index < 2 || lines[eof_index - 2].strip != "startxref")
|
364
376
|
end
|
365
377
|
|
366
|
-
@startxref_offset =
|
378
|
+
@startxref_offset = startxref_offset
|
367
379
|
end
|
368
380
|
|
369
381
|
# Returns the reconstructed revision.
|
@@ -72,8 +72,19 @@ module HexaPDF
|
|
72
72
|
# Compresses the content streams of all pages if set to +true+. Note that this can take a
|
73
73
|
# *very* long time because each content stream has to be unfiltered, parsed, serialized
|
74
74
|
# and then filtered again.
|
75
|
+
#
|
76
|
+
# prune_page_resources::
|
77
|
+
# Removes all unused XObjects from the resources dictionaries of all pages. It is
|
78
|
+
# recommended to also set the +compact+ argument because otherwise the unused XObjects won't
|
79
|
+
# be deleted from the document.
|
80
|
+
#
|
81
|
+
# This is sometimes necessary after importing pages from other PDF files that use a single
|
82
|
+
# resources dictionary for all pages.
|
75
83
|
def self.call(doc, compact: false, object_streams: :preserve, xref_streams: :preserve,
|
76
|
-
compress_pages: false)
|
84
|
+
compress_pages: false, prune_page_resources: false)
|
85
|
+
used_refs = compress_pages(doc) if compress_pages
|
86
|
+
prune_page_resources(doc, used_refs) if prune_page_resources
|
87
|
+
|
77
88
|
if compact
|
78
89
|
compact(doc, object_streams, xref_streams)
|
79
90
|
elsif object_streams != :preserve
|
@@ -83,8 +94,6 @@ module HexaPDF
|
|
83
94
|
else
|
84
95
|
doc.each(only_current: false, &method(:delete_fields_with_defaults))
|
85
96
|
end
|
86
|
-
|
87
|
-
compress_pages(doc) if compress_pages
|
88
97
|
end
|
89
98
|
|
90
99
|
# Compacts the document by merging all revisions into one, deleting null and unused entries
|
@@ -214,12 +223,41 @@ module HexaPDF
|
|
214
223
|
|
215
224
|
# Compresses the contents of all pages by parsing and then serializing again. The HexaPDF
|
216
225
|
# serializer is already optimized for small output size so nothing else needs to be done.
|
226
|
+
#
|
227
|
+
# Returns a hash of the form key=>true where the keys are the used XObjects (for use with
|
228
|
+
# #prune_page_resources).
|
217
229
|
def self.compress_pages(doc)
|
230
|
+
used_refs = {}
|
218
231
|
doc.pages.each do |page|
|
219
232
|
processor = SerializationProcessor.new
|
220
233
|
HexaPDF::Content::Parser.parse(page.contents, processor)
|
221
234
|
page.contents = processor.result
|
222
235
|
page[:Contents].set_filter(:FlateDecode)
|
236
|
+
xobjects = page.resources[:XObject]
|
237
|
+
processor.used_references.each {|ref| used_refs[xobjects[ref]] = true }
|
238
|
+
end
|
239
|
+
used_refs
|
240
|
+
end
|
241
|
+
|
242
|
+
# Deletes all XObject entries from the resources dictionaries of all pages whose names do not
|
243
|
+
# match the keys in +used_refs+.
|
244
|
+
def self.prune_page_resources(doc, used_refs)
|
245
|
+
unless used_refs
|
246
|
+
used_refs = {}
|
247
|
+
doc.pages.each do |page|
|
248
|
+
xobjects = page.resources[:XObject]
|
249
|
+
HexaPDF::Content::Parser.parse(page.contents) do |op, operands|
|
250
|
+
used_refs[xobjects[operands[0]]] = true if op == :Do
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
doc.pages.each do |page|
|
256
|
+
xobjects = page.resources[:XObject]
|
257
|
+
xobjects.each do |key, obj|
|
258
|
+
next if used_refs[obj]
|
259
|
+
xobjects.delete(key)
|
260
|
+
end
|
223
261
|
end
|
224
262
|
end
|
225
263
|
|
@@ -228,14 +266,19 @@ module HexaPDF
|
|
228
266
|
|
229
267
|
attr_reader :result #:nodoc:
|
230
268
|
|
269
|
+
# Contains all found references
|
270
|
+
attr_reader :used_references
|
271
|
+
|
231
272
|
def initialize #:nodoc:
|
232
273
|
@result = ''.b
|
233
274
|
@serializer = HexaPDF::Serializer.new
|
275
|
+
@used_references = []
|
234
276
|
end
|
235
277
|
|
236
278
|
def process(op, operands) #:nodoc:
|
237
279
|
@result << HexaPDF::Content::Operator::DEFAULT_OPERATORS[op].
|
238
280
|
serialize(@serializer, *operands)
|
281
|
+
@used_references << operands[0] if op == :Do
|
239
282
|
end
|
240
283
|
|
241
284
|
end
|
data/lib/hexapdf/type/font.rb
CHANGED
@@ -41,6 +41,10 @@ module HexaPDF
|
|
41
41
|
|
42
42
|
# Represents a Type 3 font.
|
43
43
|
#
|
44
|
+
# Note: We assume the /FontMatrix is only used for scaling, i.e. of the form [x 0 0 +/-x 0 0].
|
45
|
+
# If it is of a different form, things won't work correctly. This will be handled once such a
|
46
|
+
# case is found.
|
47
|
+
#
|
44
48
|
# See: PDF1.7 s9.6.5
|
45
49
|
class FontType3 < FontSimple
|
46
50
|
|
@@ -51,6 +55,22 @@ module HexaPDF
|
|
51
55
|
define_field :CharProcs, type: Dictionary, required: true
|
52
56
|
define_field :Resources, type: Dictionary, version: '1.2'
|
53
57
|
|
58
|
+
# Returns the bounding box of the font.
|
59
|
+
def bounding_box
|
60
|
+
matrix = self[:FontMatrix]
|
61
|
+
bbox = self[:FontBBox].value
|
62
|
+
if matrix[3] < 0 # Some writers invert the y-axis
|
63
|
+
bbox = bbox.dup
|
64
|
+
bbox[1], bbox[3] = -bbox[3], -bbox[1]
|
65
|
+
end
|
66
|
+
bbox
|
67
|
+
end
|
68
|
+
|
69
|
+
# Returns the glyph scaling factor for transforming from glyph space to text space.
|
70
|
+
def glyph_scaling_factor
|
71
|
+
self[:FontMatrix][0]
|
72
|
+
end
|
73
|
+
|
54
74
|
private
|
55
75
|
|
56
76
|
def perform_validation
|
data/lib/hexapdf/version.rb
CHANGED
data/lib/hexapdf/writer.rb
CHANGED
@@ -66,6 +66,8 @@ module HexaPDF
|
|
66
66
|
@serializer = Serializer.new
|
67
67
|
@serializer.encrypter = @document.encrypted? ? @document.security_handler : nil
|
68
68
|
@rev_size = 0
|
69
|
+
|
70
|
+
@use_xref_streams = false
|
69
71
|
end
|
70
72
|
|
71
73
|
# Writes the document to the IO object.
|
@@ -87,6 +89,7 @@ module HexaPDF
|
|
87
89
|
IO.copy_stream(@document.revisions.parser.io, @io)
|
88
90
|
|
89
91
|
@rev_size = @document.revisions.current.next_free_oid
|
92
|
+
@use_xref_streams = @document.revisions.parser.contains_xref_streams?
|
90
93
|
|
91
94
|
revision = Revision.new(@document.revisions.current.trailer)
|
92
95
|
@document.revisions.each do |rev|
|
@@ -170,10 +173,13 @@ module HexaPDF
|
|
170
173
|
end
|
171
174
|
end
|
172
175
|
|
173
|
-
if !object_streams.empty? && xref_stream.nil?
|
174
|
-
|
176
|
+
if (!object_streams.empty? || @use_xref_streams) && xref_stream.nil?
|
177
|
+
xref_stream = @document.wrap({Type: :XRef}, oid: rev.next_free_oid)
|
178
|
+
rev.add(xref_stream)
|
175
179
|
end
|
176
180
|
|
181
|
+
@use_xref_streams = true if xref_stream
|
182
|
+
|
177
183
|
[xref_stream, object_streams]
|
178
184
|
end
|
179
185
|
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'test_helper'
|
4
4
|
require 'hexapdf/content/graphics_state'
|
5
|
+
require 'ostruct'
|
5
6
|
|
6
7
|
# Dummy class used as wrapper so that constant lookup works correctly
|
7
8
|
class GraphicsStateWrapper < Minitest::Spec
|
@@ -146,6 +147,13 @@ class GraphicsStateWrapper < Minitest::Spec
|
|
146
147
|
it "fails when restoring the graphics state if the stack is empty" do
|
147
148
|
assert_raises(HexaPDF::Error) { @gs.restore }
|
148
149
|
end
|
149
|
-
end
|
150
150
|
|
151
|
+
it "uses the correct glyph to text space scaling" do
|
152
|
+
font = OpenStruct.new
|
153
|
+
font.glyph_scaling_factor = 0.002
|
154
|
+
@gs.font = font
|
155
|
+
@gs.font_size = 10
|
156
|
+
assert_equal(0.02, @gs.scaled_font_size)
|
157
|
+
end
|
158
|
+
end
|
151
159
|
end
|
@@ -4,6 +4,7 @@ require 'test_helper'
|
|
4
4
|
require 'hexapdf/content/operator'
|
5
5
|
require 'hexapdf/content/processor'
|
6
6
|
require 'hexapdf/serializer'
|
7
|
+
require 'ostruct'
|
7
8
|
|
8
9
|
describe HexaPDF::Content::Operator::BaseOperator do
|
9
10
|
before do
|
@@ -190,9 +191,11 @@ end
|
|
190
191
|
|
191
192
|
describe_operator :SetGraphicsStateParameters, :gs do
|
192
193
|
it "applies parameters from an ExtGState dictionary" do
|
194
|
+
font = OpenStruct.new
|
195
|
+
font.glyph_scaling_factor = 0.01
|
193
196
|
@processor.resources[:ExtGState] = {Name: {LW: 10, LC: 2, LJ: 2, ML: 2, D: [[3, 5], 2],
|
194
197
|
RI: 2, SA: true, BM: :Multiply, CA: 0.5, ca: 0.5,
|
195
|
-
AIS: true, TK: false, Font: [
|
198
|
+
AIS: true, TK: false, Font: [font, 10]}}
|
196
199
|
@processor.resources.define_singleton_method(:document) do
|
197
200
|
Object.new.tap {|obj| obj.define_singleton_method(:deref) {|o| o } }
|
198
201
|
end
|
@@ -210,7 +213,7 @@ describe_operator :SetGraphicsStateParameters, :gs do
|
|
210
213
|
assert_equal(0.5, gs.stroke_alpha)
|
211
214
|
assert_equal(0.5, gs.fill_alpha)
|
212
215
|
assert(gs.alpha_source)
|
213
|
-
assert_equal(
|
216
|
+
assert_equal(font, gs.font)
|
214
217
|
assert_equal(10, gs.font_size)
|
215
218
|
refute(gs.text_knockout)
|
216
219
|
end
|
@@ -448,7 +451,9 @@ describe_operator :SetFontAndSize, :Tf do
|
|
448
451
|
self[:Font] && self[:Font][name]
|
449
452
|
end
|
450
453
|
|
451
|
-
|
454
|
+
font = OpenStruct.new
|
455
|
+
font.glyph_scaling_factor = 0.01
|
456
|
+
@processor.resources[:Font] = {F1: font}
|
452
457
|
invoke(:F1, 10)
|
453
458
|
assert_equal(@processor.resources.font(:F1), @processor.graphics_state.font)
|
454
459
|
assert_equal(10, @processor.graphics_state.font_size)
|
@@ -229,19 +229,21 @@ describe HexaPDF::Encryption::StandardSecurityHandler do
|
|
229
229
|
assert_match(/Invalid \/R/i, exp.message)
|
230
230
|
end
|
231
231
|
|
232
|
-
it "fails if the
|
232
|
+
it "fails if the supplied password is invalid" do
|
233
233
|
exp = assert_raises(HexaPDF::EncryptionError) do
|
234
|
-
@handler.set_up_decryption({Filter: :Standard, V: 2, R:
|
234
|
+
@handler.set_up_decryption({Filter: :Standard, V: 2, R: 6, U: 'a' * 48, O: 'a' * 48,
|
235
|
+
UE: 'a' * 32, OE: 'a' * 32})
|
235
236
|
end
|
236
|
-
assert_match(/
|
237
|
+
assert_match(/Invalid password/i, exp.message)
|
237
238
|
end
|
238
239
|
|
239
|
-
it "
|
240
|
+
it "assigns empty strings to the trailer's ID field if it is missing" do
|
241
|
+
refute(@document.trailer.key?(:ID))
|
240
242
|
exp = assert_raises(HexaPDF::EncryptionError) do
|
241
|
-
@handler.set_up_decryption({Filter: :Standard, V:
|
242
|
-
UE: 'a' * 32, OE: 'a' * 32})
|
243
|
+
@handler.set_up_decryption({Filter: :Standard, V: 1, R: 2, U: 'a' * 48, O: 'a' * 48, P: 15})
|
243
244
|
end
|
244
245
|
assert_match(/Invalid password/i, exp.message)
|
246
|
+
assert_equal(['', ''], @document.trailer[:ID].value)
|
245
247
|
end
|
246
248
|
|
247
249
|
describe "/Perms field checking" do
|
@@ -597,6 +597,11 @@ end
|
|
597
597
|
describe HexaPDF::Layout::Style do
|
598
598
|
before do
|
599
599
|
@style = HexaPDF::Layout::Style.new
|
600
|
+
@style.font = Object.new.tap do |obj|
|
601
|
+
obj.define_singleton_method(:pdf_object) do
|
602
|
+
Object.new.tap {|pdf| pdf.define_singleton_method(:glyph_scaling_factor) { 0.001 } }
|
603
|
+
end
|
604
|
+
end
|
600
605
|
end
|
601
606
|
|
602
607
|
it "can assign values on initialization" do
|
@@ -644,6 +649,7 @@ describe HexaPDF::Layout::Style do
|
|
644
649
|
end
|
645
650
|
|
646
651
|
it "has several simple and dynamically generated properties with default values" do
|
652
|
+
@style = HexaPDF::Layout::Style.new
|
647
653
|
assert_raises(HexaPDF::Error) { @style.font }
|
648
654
|
assert_equal(10, @style.font_size)
|
649
655
|
assert_equal(0, @style.character_spacing)
|
@@ -725,6 +731,11 @@ describe HexaPDF::Layout::Style do
|
|
725
731
|
font = Object.new
|
726
732
|
font.define_singleton_method(:scaling_factor) { 1 }
|
727
733
|
font.define_singleton_method(:wrapped_font) { wrapped_font }
|
734
|
+
font.define_singleton_method(:pdf_object) do
|
735
|
+
obj = Object.new
|
736
|
+
obj.define_singleton_method(:glyph_scaling_factor) { 0.001 }
|
737
|
+
obj
|
738
|
+
end
|
728
739
|
@style.font = font
|
729
740
|
end
|
730
741
|
|
@@ -159,4 +159,30 @@ describe HexaPDF::Task::Optimize do
|
|
159
159
|
assert_equal("10 10 m\nq\nQ\nBI\n/Name 5 ID\ndataEI\n", page.contents)
|
160
160
|
end
|
161
161
|
end
|
162
|
+
|
163
|
+
describe "prune_page_resources" do
|
164
|
+
it "removes all unused XObject references" do
|
165
|
+
[false, true].each do |compress_pages|
|
166
|
+
page1 = @doc.pages.add
|
167
|
+
page1.resources[:XObject] = {}
|
168
|
+
page1.resources[:XObject][:test] = @doc.add({})
|
169
|
+
page1.resources[:XObject][:used_on_page2] = @doc.add({})
|
170
|
+
page1.resources[:XObject][:unused] = @doc.add({})
|
171
|
+
page1.contents = "/test Do"
|
172
|
+
page2 = @doc.pages.add
|
173
|
+
page2.resources[:XObject] = {}
|
174
|
+
page2.resources[:XObject][:used_on2] = page1.resources[:XObject][:used_on_page2]
|
175
|
+
page2.resources[:XObject][:also_unused] = page1.resources[:XObject][:unused]
|
176
|
+
page2.contents = "/used_on2 Do"
|
177
|
+
|
178
|
+
@doc.task(:optimize, prune_page_resources: true, compress_pages: compress_pages)
|
179
|
+
|
180
|
+
assert(page1.resources[:XObject].key?(:test))
|
181
|
+
assert(page1.resources[:XObject].key?(:used_on_page2))
|
182
|
+
refute(page1.resources[:XObject].key?(:unused))
|
183
|
+
assert(page2.resources[:XObject].key?(:used_on2))
|
184
|
+
refute(page2.resources[:XObject].key?(:also_unused))
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
162
188
|
end
|
@@ -173,6 +173,7 @@ describe HexaPDF::DictionaryFields do
|
|
173
173
|
|
174
174
|
it "allows conversion to a Time object from a binary string" do
|
175
175
|
refute(@field.convert('test'.b, self))
|
176
|
+
refute(@field.convert('D:01211016165909+00\'64'.b, self))
|
176
177
|
|
177
178
|
[
|
178
179
|
["D:1998", [1998, 01, 01, 00, 00, 00, "-00:00"]],
|
data/test/hexapdf/test_parser.rb
CHANGED
@@ -338,6 +338,11 @@ describe HexaPDF::Parser do
|
|
338
338
|
assert_equal(5, @parser.startxref_offset)
|
339
339
|
end
|
340
340
|
|
341
|
+
it "handles the case of startxref and its value being on the same line" do
|
342
|
+
create_parser("startxref 5\n%%EOF")
|
343
|
+
assert_equal(5, @parser.startxref_offset)
|
344
|
+
end
|
345
|
+
|
341
346
|
it "fails even in big files when nothing is found" do
|
342
347
|
create_parser("\nhallo" * 5000)
|
343
348
|
exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.startxref_offset }
|
@@ -366,6 +371,13 @@ describe HexaPDF::Parser do
|
|
366
371
|
exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.startxref_offset }
|
367
372
|
assert_match(/end-of-file marker not found/, exp.message)
|
368
373
|
end
|
374
|
+
|
375
|
+
it "fails on strict parsing if the startxref is on the same line as its value" do
|
376
|
+
@document.config['parser.on_correctable_error'] = proc { true }
|
377
|
+
create_parser("startxref 5\n%%EOF")
|
378
|
+
exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.startxref_offset }
|
379
|
+
assert_match(/startxref on same line/, exp.message)
|
380
|
+
end
|
369
381
|
end
|
370
382
|
|
371
383
|
describe "file_header_version" do
|
@@ -531,12 +543,14 @@ describe HexaPDF::Parser do
|
|
531
543
|
xref_section, trailer = @parser.load_revision(@parser.startxref_offset)
|
532
544
|
assert_equal({Test: 'now'}, trailer)
|
533
545
|
assert(xref_section[1].in_use?)
|
546
|
+
refute(@parser.contains_xref_streams?)
|
534
547
|
end
|
535
548
|
|
536
549
|
it "works for a cross-reference stream" do
|
537
550
|
xref_section, trailer = @parser.load_revision(212)
|
538
551
|
assert_equal({Size: 2}, trailer)
|
539
552
|
assert(xref_section[1].in_use?)
|
553
|
+
assert(@parser.contains_xref_streams?)
|
540
554
|
end
|
541
555
|
|
542
556
|
it "fails if another object is found instead of a cross-reference stream" do
|
data/test/hexapdf/test_writer.rb
CHANGED
@@ -40,7 +40,7 @@ describe HexaPDF::Writer do
|
|
40
40
|
219
|
41
41
|
%%EOF
|
42
42
|
3 0 obj
|
43
|
-
<</Producer(HexaPDF version 0.
|
43
|
+
<</Producer(HexaPDF version 0.19.3)>>
|
44
44
|
endobj
|
45
45
|
xref
|
46
46
|
3 1
|
@@ -72,7 +72,7 @@ describe HexaPDF::Writer do
|
|
72
72
|
141
|
73
73
|
%%EOF
|
74
74
|
6 0 obj
|
75
|
-
<</Producer(HexaPDF version 0.
|
75
|
+
<</Producer(HexaPDF version 0.19.3)>>
|
76
76
|
endobj
|
77
77
|
2 0 obj
|
78
78
|
<</Length 10>>stream
|
@@ -103,21 +103,50 @@ describe HexaPDF::Writer do
|
|
103
103
|
assert_document_conversion(@compressed_input_io)
|
104
104
|
end
|
105
105
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
106
|
+
describe "write_incremental" do
|
107
|
+
it "writes a document in incremental mode" do
|
108
|
+
doc = HexaPDF::Document.new(io: @std_input_io)
|
109
|
+
doc.pages.add
|
110
|
+
output_io = StringIO.new
|
111
|
+
HexaPDF::Writer.write(doc, output_io, incremental: true)
|
112
|
+
assert_equal(output_io.string[0, @std_input_io.string.length], @std_input_io.string)
|
113
|
+
doc = HexaPDF::Document.new(io: output_io)
|
114
|
+
assert_equal(4, doc.revisions.size)
|
115
|
+
assert_equal(2, doc.revisions.current.each.to_a.size)
|
116
|
+
end
|
117
|
+
|
118
|
+
it "uses an xref stream if the document already contains at least one" do
|
119
|
+
doc = HexaPDF::Document.new(io: @compressed_input_io)
|
120
|
+
doc.pages.add
|
121
|
+
output_io = StringIO.new
|
122
|
+
HexaPDF::Writer.write(doc, output_io, incremental: true)
|
123
|
+
refute_match(/^trailer/, output_io.string)
|
124
|
+
end
|
115
125
|
end
|
116
126
|
|
117
|
-
it "
|
127
|
+
it "creates an xref stream if no xref stream is in a revision but object streams are" do
|
118
128
|
document = HexaPDF::Document.new
|
119
129
|
document.add({Type: :ObjStm})
|
120
|
-
|
130
|
+
HexaPDF::Writer.new(document, StringIO.new).write
|
131
|
+
assert(:XRef, document.object(2).type)
|
132
|
+
end
|
133
|
+
|
134
|
+
it "creates an xref stream if a previous revision had one" do
|
135
|
+
document = HexaPDF::Document.new
|
136
|
+
document.pages.add
|
137
|
+
document.revisions.add
|
138
|
+
document.pages.add
|
139
|
+
document.add({Type: :ObjStm})
|
140
|
+
document.revisions.add
|
141
|
+
document.pages.add
|
142
|
+
io = StringIO.new
|
143
|
+
HexaPDF::Writer.new(document, io).write
|
144
|
+
|
145
|
+
document = HexaPDF::Document.new(io: io)
|
146
|
+
assert_equal(3, document.revisions.count)
|
147
|
+
assert(document.revisions[0].none? {|obj| obj.type == :XRef })
|
148
|
+
assert(document.revisions[1].one? {|obj| obj.type == :XRef })
|
149
|
+
assert(document.revisions[2].one? {|obj| obj.type == :XRef })
|
121
150
|
end
|
122
151
|
|
123
152
|
it "raises an error if the class is misused and an xref section contains invalid entries" do
|
@@ -9,10 +9,25 @@ describe HexaPDF::Type::FontType3 do
|
|
9
9
|
@doc = HexaPDF::Document.new
|
10
10
|
@font = @doc.add({Type: :Font, Subtype: :Type3, Encoding: :WinAnsiEncoding,
|
11
11
|
FirstChar: 32, LastChar: 34, Widths: [600, 0, 700],
|
12
|
-
FontBBox: [0,
|
12
|
+
FontBBox: [0, 100, 100, 0], FontMatrix: [0.002, 0, 0, 0.002, 0, 0],
|
13
13
|
CharProcs: {}})
|
14
14
|
end
|
15
15
|
|
16
|
+
describe "bounding_box" do
|
17
|
+
it "returns the font's bounding box" do
|
18
|
+
assert_equal([0, 0, 100, 100], @font.bounding_box)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "inverts the y-values if necessary based on /FontMatrix" do
|
22
|
+
@font[:FontMatrix][3] *= -1
|
23
|
+
assert_equal([0, -100, 100, 0], @font.bounding_box)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
it "returns the glyph scaling factor" do
|
28
|
+
assert_equal(0.002, @font.glyph_scaling_factor)
|
29
|
+
end
|
30
|
+
|
16
31
|
describe "validation" do
|
17
32
|
it "works for valid objects" do
|
18
33
|
assert(@font.validate)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hexapdf
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.19.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Thomas Leitner
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-12-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: cmdparse
|