hexapdf 0.17.2 → 0.19.1
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 +67 -0
- data/lib/hexapdf/cli/command.rb +7 -1
- data/lib/hexapdf/content/canvas.rb +2 -2
- data/lib/hexapdf/content/color_space.rb +19 -0
- data/lib/hexapdf/content/graphic_object/arc.rb +2 -2
- data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +4 -4
- data/lib/hexapdf/content/graphic_object/solid_arc.rb +111 -10
- data/lib/hexapdf/content/graphics_state.rb +167 -25
- data/lib/hexapdf/dictionary.rb +1 -1
- data/lib/hexapdf/dictionary_fields.rb +1 -1
- data/lib/hexapdf/document/signatures.rb +221 -0
- data/lib/hexapdf/encryption/security_handler.rb +3 -1
- data/lib/hexapdf/layout/style.rb +2 -1
- data/lib/hexapdf/object.rb +18 -0
- data/lib/hexapdf/parser.rb +3 -0
- data/lib/hexapdf/serializer.rb +2 -0
- data/lib/hexapdf/task/optimize.rb +46 -3
- data/lib/hexapdf/type/acro_form/appearance_generator.rb +4 -4
- data/lib/hexapdf/type/acro_form/form.rb +39 -28
- data/lib/hexapdf/type/acro_form/variable_text_field.rb +56 -18
- data/lib/hexapdf/type/annotations/widget.rb +3 -15
- data/lib/hexapdf/type/font.rb +5 -0
- data/lib/hexapdf/type/font_type3.rb +20 -0
- data/lib/hexapdf/type/signature/adbe_pkcs7_detached.rb +125 -0
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/content/graphic_object/test_endpoint_arc.rb +16 -8
- data/test/hexapdf/content/test_color_space.rb +26 -0
- data/test/hexapdf/content/test_graphics_state.rb +9 -1
- data/test/hexapdf/content/test_operator.rb +8 -3
- data/test/hexapdf/encryption/test_security_handler.rb +3 -1
- data/test/hexapdf/layout/test_style.rb +11 -0
- data/test/hexapdf/task/test_optimize.rb +26 -0
- data/test/hexapdf/test_dictionary.rb +1 -0
- data/test/hexapdf/test_dictionary_fields.rb +3 -2
- data/test/hexapdf/test_object.rb +28 -0
- data/test/hexapdf/test_parser.rb +11 -0
- data/test/hexapdf/test_writer.rb +2 -2
- data/test/hexapdf/type/acro_form/test_appearance_generator.rb +8 -1
- data/test/hexapdf/type/acro_form/test_form.rb +15 -8
- data/test/hexapdf/type/acro_form/test_variable_text_field.rb +18 -8
- data/test/hexapdf/type/test_font.rb +4 -0
- data/test/hexapdf/type/test_font_type3.rb +16 -1
- metadata +4 -2
@@ -37,7 +37,7 @@
|
|
37
37
|
require 'hexapdf/dictionary'
|
38
38
|
require 'hexapdf/stream'
|
39
39
|
require 'hexapdf/error'
|
40
|
-
require 'hexapdf/content
|
40
|
+
require 'hexapdf/content'
|
41
41
|
|
42
42
|
module HexaPDF
|
43
43
|
module Type
|
@@ -61,14 +61,53 @@ module HexaPDF
|
|
61
61
|
|
62
62
|
UNSET_ARG = ::Object.new # :nodoc:
|
63
63
|
|
64
|
-
#
|
65
|
-
#
|
66
|
-
#
|
64
|
+
# Creates an AcroForm appearance string for the HexaPDF +document+ from the given arguments
|
65
|
+
# and returns it.
|
66
|
+
#
|
67
|
+
# +font+::
|
68
|
+
# The name of the font.
|
69
|
+
#
|
70
|
+
# +font_options+::
|
71
|
+
# Additional font options like :variant used when loading the font. See
|
72
|
+
# HexaPDF::Document::Fonts#add
|
73
|
+
#
|
74
|
+
# +font_size+::
|
75
|
+
# The font size. If this is set to 0, the font size is calculated using the height/width
|
76
|
+
# of the field.
|
77
|
+
#
|
78
|
+
# +font_color+::
|
79
|
+
# The font color. See HexaPDF::Content::ColorSpace.device_color_from_specification for
|
80
|
+
# allowed values.
|
81
|
+
def self.create_appearance_string(document, font: 'Helvetica', font_options: {},
|
82
|
+
font_size: 0, font_color: 0)
|
83
|
+
name = document.acro_form(create: true).default_resources.
|
84
|
+
add_font(document.fonts.add(font, **font_options).pdf_object)
|
85
|
+
font_color = HexaPDF::Content::ColorSpace.device_color_from_specification(font_color)
|
86
|
+
color_string = HexaPDF::Content::ColorSpace.serialize_device_color(font_color)
|
87
|
+
"#{color_string.chomp} /#{name} #{font_size} Tf"
|
88
|
+
end
|
89
|
+
|
90
|
+
# :call-seq:
|
91
|
+
# VariableTextField.parse_appearance_string(string) -> [font_name, font_size, font_color]
|
92
|
+
# VariableTextField.parse_appearance_string(string) {|obj, params| block } -> nil
|
93
|
+
#
|
94
|
+
# Parses the given appearance string.
|
95
|
+
#
|
96
|
+
# If no block is given, the appearance string is searched for font name, font size and font
|
97
|
+
# color all of which are returned. Otherwise the block is called with each found content
|
98
|
+
# stream operator and has to handle them itself.
|
67
99
|
def self.parse_appearance_string(appearance_string, &block) # :yield: obj, params
|
68
|
-
font_params = nil
|
69
|
-
block ||= lambda
|
100
|
+
font_params = [nil, nil, nil]
|
101
|
+
block ||= lambda do |obj, params|
|
102
|
+
case obj
|
103
|
+
when :Tf
|
104
|
+
font_params[0, 2] = params
|
105
|
+
when :rg, :g, :k
|
106
|
+
font_params[2] = HexaPDF::Content::ColorSpace.prenormalized_device_color(params)
|
107
|
+
end
|
108
|
+
end
|
70
109
|
HexaPDF::Content::Parser.parse(appearance_string.sub(/\/\//, '/'), &block)
|
71
|
-
font_params
|
110
|
+
block_given? ? nil : font_params
|
72
111
|
end
|
73
112
|
|
74
113
|
# :call-seq:
|
@@ -99,21 +138,20 @@ module HexaPDF
|
|
99
138
|
end
|
100
139
|
end
|
101
140
|
|
102
|
-
# Sets the default appearance string using the provided values
|
103
|
-
#
|
104
|
-
# The default argument values are a sane default. If +font_size+ is set to 0, the font size
|
105
|
-
# is calculated using the height/width of the field.
|
141
|
+
# Sets the default appearance string using the provided values or the default values which
|
142
|
+
# provide a sane default.
|
106
143
|
#
|
107
|
-
#
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
144
|
+
# See ::create_appearance_string for information on the arguments.
|
145
|
+
def set_default_appearance_string(font: 'Helvetica', font_options: {}, font_size: 0,
|
146
|
+
font_color: 0)
|
147
|
+
self[:DA] = self.class.create_appearance_string(document, font: font,
|
148
|
+
font_options: font_options,
|
149
|
+
font_size: font_size,
|
150
|
+
font_color: font_color)
|
113
151
|
end
|
114
152
|
|
115
153
|
# Parses the default appearance string and returns an array containing [font_name,
|
116
|
-
# font_size].
|
154
|
+
# font_size, font_color].
|
117
155
|
#
|
118
156
|
# The default appearance string is taken from the field or, if not set, the default
|
119
157
|
# appearance string of the form.
|
@@ -268,6 +268,7 @@ module HexaPDF
|
|
268
268
|
style ||= (field.check_box? ? :check : :cicrle)
|
269
269
|
size ||= 0
|
270
270
|
color = Content::ColorSpace.device_color_from_specification(color || 0)
|
271
|
+
serialized_color = Content::ColorSpace.serialize_device_color(color)
|
271
272
|
|
272
273
|
self[:MK] ||= {}
|
273
274
|
self[:MK][:CA] = case style
|
@@ -281,13 +282,6 @@ module HexaPDF
|
|
281
282
|
else
|
282
283
|
raise ArgumentError, "Unknown value #{style} for argument 'style'"
|
283
284
|
end
|
284
|
-
operator = case color.color_space.family
|
285
|
-
when :DeviceRGB then :rg
|
286
|
-
when :DeviceGray then :g
|
287
|
-
when :DeviceCMYK then :k
|
288
|
-
end
|
289
|
-
serialized_color = Content::Operator::DEFAULT_OPERATORS[operator].
|
290
|
-
serialize(HexaPDF::Serializer.new, *color.components)
|
291
285
|
self[:DA] = "/ZaDb #{size} Tf #{serialized_color}".strip
|
292
286
|
else
|
293
287
|
style = case self[:MK]&.[](:CA)
|
@@ -306,16 +300,10 @@ module HexaPDF
|
|
306
300
|
end
|
307
301
|
end
|
308
302
|
size = 0
|
309
|
-
color = [0]
|
303
|
+
color = HexaPDF::Content::ColorSpace.prenormalized_device_color([0])
|
310
304
|
if (da = self[:DA] || field[:DA])
|
311
|
-
HexaPDF::Type::AcroForm::VariableTextField.parse_appearance_string(da)
|
312
|
-
case obj
|
313
|
-
when :rg, :g, :k then color = params.dup
|
314
|
-
when :Tf then size = params[1]
|
315
|
-
end
|
316
|
-
end
|
305
|
+
_, size, color = HexaPDF::Type::AcroForm::VariableTextField.parse_appearance_string(da)
|
317
306
|
end
|
318
|
-
color = HexaPDF::Content::ColorSpace.prenormalized_device_color(color)
|
319
307
|
|
320
308
|
MarkerStyle.new(style, size, color)
|
321
309
|
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
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# -*- encoding: utf-8; frozen_string_literal: true -*-
|
2
|
+
#
|
3
|
+
#--
|
4
|
+
# This file is part of HexaPDF.
|
5
|
+
#
|
6
|
+
# HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
|
7
|
+
# Copyright (C) 2014-2021 Thomas Leitner
|
8
|
+
#
|
9
|
+
# HexaPDF is free software: you can redistribute it and/or modify it
|
10
|
+
# under the terms of the GNU Affero General Public License version 3 as
|
11
|
+
# published by the Free Software Foundation with the addition of the
|
12
|
+
# following permission added to Section 15 as permitted in Section 7(a):
|
13
|
+
# FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
|
14
|
+
# THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
|
15
|
+
# INFRINGEMENT OF THIRD PARTY RIGHTS.
|
16
|
+
#
|
17
|
+
# HexaPDF is distributed in the hope that it will be useful, but WITHOUT
|
18
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
20
|
+
# License for more details.
|
21
|
+
#
|
22
|
+
# You should have received a copy of the GNU Affero General Public License
|
23
|
+
# along with HexaPDF. If not, see <http://www.gnu.org/licenses/>.
|
24
|
+
#
|
25
|
+
# The interactive user interfaces in modified source and object code
|
26
|
+
# versions of HexaPDF must display Appropriate Legal Notices, as required
|
27
|
+
# under Section 5 of the GNU Affero General Public License version 3.
|
28
|
+
#
|
29
|
+
# In accordance with Section 7(b) of the GNU Affero General Public
|
30
|
+
# License, a covered work must retain the producer line in every PDF that
|
31
|
+
# is created or manipulated using HexaPDF.
|
32
|
+
#
|
33
|
+
# If the GNU Affero General Public License doesn't fit your need,
|
34
|
+
# commercial licenses are available at <https://gettalong.at/hexapdf/>.
|
35
|
+
#++
|
36
|
+
|
37
|
+
require 'openssl'
|
38
|
+
require 'hexapdf/type/signature'
|
39
|
+
|
40
|
+
module HexaPDF
|
41
|
+
module Type
|
42
|
+
class Signature
|
43
|
+
|
44
|
+
# The signature handler for the adbe.pkcs7.detached sub-filter.
|
45
|
+
class AdbePkcs7Detached < Handler
|
46
|
+
|
47
|
+
# Creates a new signature handler for the given signature dictionary.
|
48
|
+
def initialize(signature_dict)
|
49
|
+
super
|
50
|
+
@pkcs7 = OpenSSL::PKCS7.new(signature_dict.contents)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns the common name of the signer.
|
54
|
+
def signer_name
|
55
|
+
signer_certificate.subject.to_a.assoc("CN")&.[](1) || super
|
56
|
+
end
|
57
|
+
|
58
|
+
# Returns the time of signing.
|
59
|
+
def signing_time
|
60
|
+
signer_info.signed_time rescue super
|
61
|
+
end
|
62
|
+
|
63
|
+
# Returns the certificate chain.
|
64
|
+
def certificate_chain
|
65
|
+
@pkcs7.certificates
|
66
|
+
end
|
67
|
+
|
68
|
+
# Returns the signer certificate (an instance of OpenSSL::X509::Certificate).
|
69
|
+
def signer_certificate
|
70
|
+
info = signer_info
|
71
|
+
certificate_chain.find {|cert| cert.issuer == info.issuer && cert.serial == info.serial }
|
72
|
+
end
|
73
|
+
|
74
|
+
# Returns the signer information object (an instance of OpenSSL::PKCS7::SignerInfo).
|
75
|
+
def signer_info
|
76
|
+
@pkcs7.signers.first
|
77
|
+
end
|
78
|
+
|
79
|
+
# Verifies the signature using the provided OpenSSL::X509::Store object.
|
80
|
+
def verify(store, allow_self_signed: false)
|
81
|
+
result = VerificationResult.new
|
82
|
+
|
83
|
+
signer_info = self.signer_info
|
84
|
+
signer_certificate = self.signer_certificate
|
85
|
+
certificate_chain = self.certificate_chain
|
86
|
+
|
87
|
+
if certificate_chain.empty?
|
88
|
+
result.log(:error, "No certificates found in signature")
|
89
|
+
return result
|
90
|
+
end
|
91
|
+
|
92
|
+
if @pkcs7.signers.size != 1
|
93
|
+
result.log(:error, "Exactly one signer needed, found #{@pkcs7.signers.size}")
|
94
|
+
end
|
95
|
+
|
96
|
+
unless signer_certificate
|
97
|
+
result.log(:error, "Signer serial=#{signer_info.serial} issuer=#{signer_info.issuer} " \
|
98
|
+
"not found in certificates stored in PKCS7 object")
|
99
|
+
return result
|
100
|
+
end
|
101
|
+
|
102
|
+
key_usage = signer_certificate.extensions.find {|ext| ext.oid == 'keyUsage' }
|
103
|
+
unless key_usage && key_usage.value.split(', ').include?("Digital Signature")
|
104
|
+
result.log(:error, "Certificate key usage is missing 'Digital Signature'")
|
105
|
+
end
|
106
|
+
|
107
|
+
verify_signing_time(result)
|
108
|
+
|
109
|
+
store.verify_callback = store_verification_callback(result,
|
110
|
+
allow_self_signed: allow_self_signed)
|
111
|
+
if @pkcs7.verify(certificate_chain, store, signature_dict.signed_data,
|
112
|
+
OpenSSL::PKCS7::DETACHED | OpenSSL::PKCS7::BINARY)
|
113
|
+
result.log(:info, "Signature valid")
|
114
|
+
else
|
115
|
+
result.log(:error, "Signature verification failed")
|
116
|
+
end
|
117
|
+
|
118
|
+
result
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
data/lib/hexapdf/version.rb
CHANGED
@@ -55,21 +55,29 @@ describe HexaPDF::Content::GraphicObject::EndpointArc do
|
|
55
55
|
|
56
56
|
it "draws the arc onto the canvas" do
|
57
57
|
{
|
58
|
-
[false, false] => {cx: 100, cy: 50, start_angle: 180, end_angle: 270, clockwise: false},
|
59
|
-
[false, true] => {cx: 50, cy: 25, start_angle: 90, end_angle: 0, clockwise: true},
|
60
|
-
[true, false] => {cx: 50, cy: 25, start_angle: 90, end_angle: 360, clockwise: false},
|
61
|
-
[true, true] => {cx:
|
58
|
+
[false, false] => {cx: 100, cy: 50, a: 50, b: 25, start_angle: 180, end_angle: 270, clockwise: false},
|
59
|
+
[false, true] => {cx: 50, cy: 25, a: 50, b: 25, start_angle: 90, end_angle: 0, clockwise: true},
|
60
|
+
[true, false] => {cx: 50, cy: 25, a: 50, b: 25, start_angle: 90, end_angle: 360, clockwise: false},
|
61
|
+
[true, true] => {cx: 0, cy: 0, a: 40, b: 30, start_angle: 60, end_angle: 120},
|
62
62
|
}.each do |(large_arc, clockwise), data|
|
63
63
|
@page.delete(:Contents)
|
64
64
|
canvas = @page.canvas
|
65
|
-
canvas.
|
65
|
+
arc = canvas.graphic_object(:arc, **data)
|
66
|
+
canvas.draw(arc)
|
66
67
|
arc_data = @page.contents
|
67
68
|
|
68
69
|
canvas.contents.clear
|
69
70
|
assert(@page.contents.empty?)
|
70
|
-
canvas.move_to(
|
71
|
-
canvas.
|
72
|
-
|
71
|
+
canvas.move_to(*arc.start_point)
|
72
|
+
earc = canvas.graphic_object(:endpoint_arc, x: arc.end_point[0], y: arc.end_point[1],
|
73
|
+
a: data[:a], b: data[:b], inclination: data[:inclination] || 0,
|
74
|
+
large_arc: large_arc, clockwise: clockwise)
|
75
|
+
canvas.draw(earc)
|
76
|
+
narc = canvas.graphic_object(:arc, **earc.send(:compute_arc_values, *arc.start_point))
|
77
|
+
assert_in_delta(arc.start_point[0], narc.start_point[0], 0.0001)
|
78
|
+
assert_in_delta(arc.start_point[1], narc.start_point[1], 0.0001)
|
79
|
+
assert_in_delta(arc.end_point[0], narc.end_point[0], 0.0001)
|
80
|
+
assert_in_delta(arc.end_point[1], narc.end_point[1], 0.0001)
|
73
81
|
assert_equal(arc_data, @page.contents)
|
74
82
|
end
|
75
83
|
end
|
@@ -99,6 +99,32 @@ describe HexaPDF::Content::ColorSpace do
|
|
99
99
|
end
|
100
100
|
end
|
101
101
|
|
102
|
+
describe "self.serialize_device_color" do
|
103
|
+
it "works for device gray colors" do
|
104
|
+
color = @class.device_color_from_specification(0.5)
|
105
|
+
assert_equal("0.5 g\n", @class.serialize_device_color(color))
|
106
|
+
assert_equal("0.5 G\n", @class.serialize_device_color(color, type: :stroke))
|
107
|
+
end
|
108
|
+
|
109
|
+
it "works for device RGB colors" do
|
110
|
+
color = @class.device_color_from_specification("red")
|
111
|
+
assert_equal("1.0 0.0 0.0 rg\n", @class.serialize_device_color(color))
|
112
|
+
assert_equal("1.0 0.0 0.0 RG\n", @class.serialize_device_color(color, type: :stroke))
|
113
|
+
end
|
114
|
+
|
115
|
+
it "works for device CMYK colors" do
|
116
|
+
color = @class.device_color_from_specification([100, 100, 100, 0])
|
117
|
+
assert_equal("1.0 1.0 1.0 0.0 k\n", @class.serialize_device_color(color))
|
118
|
+
assert_equal("1.0 1.0 1.0 0.0 K\n", @class.serialize_device_color(color, type: :stroke))
|
119
|
+
end
|
120
|
+
|
121
|
+
it "fails if no device color is provided" do
|
122
|
+
assert_raises(ArgumentError) do
|
123
|
+
@class.serialize_device_color(@class::Universal.new([]).default_color)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
102
128
|
it "returns a device color object for prenormalized color values" do
|
103
129
|
assert_equal([5, 6, 7], @class.prenormalized_device_color([5, 6, 7]).components)
|
104
130
|
end
|
@@ -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)
|
@@ -107,9 +107,11 @@ describe HexaPDF::Encryption::SecurityHandler do
|
|
107
107
|
end
|
108
108
|
|
109
109
|
it "updates the trailer's /Encrypt entry to be wrapped by an encryption dictionary" do
|
110
|
-
@document.trailer[:Encrypt] = {Filter: :Test,
|
110
|
+
@document.trailer[:Encrypt] = {Filter: :Test,
|
111
|
+
V: HexaPDF::Object.new(1, oid: 1, document: @document)}
|
111
112
|
HexaPDF::Encryption::SecurityHandler.set_up_decryption(@document)
|
112
113
|
assert_kind_of(HexaPDF::Encryption::EncryptionDictionary, @document.trailer[:Encrypt])
|
114
|
+
assert_equal({Filter: :Test, V: 1}, @document.trailer[:Encrypt].value)
|
113
115
|
end
|
114
116
|
|
115
117
|
it "returns the frozen security handler" 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
|
@@ -135,12 +135,12 @@ describe HexaPDF::DictionaryFields do
|
|
135
135
|
end
|
136
136
|
|
137
137
|
def configuration
|
138
|
-
|
138
|
+
HexaPDF::Configuration.with_defaults
|
139
139
|
end
|
140
140
|
|
141
141
|
it "calls document.on_invalid_string if the provided string is invalid" do
|
142
142
|
str = "\xfe\xff\xD8\x00\x00s\x00t".b
|
143
|
-
assert_equal("
|
143
|
+
assert_equal("st", @field.convert(str, self))
|
144
144
|
end
|
145
145
|
end
|
146
146
|
|
@@ -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_object.rb
CHANGED
@@ -45,6 +45,34 @@ describe HexaPDF::Object do
|
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|
48
|
+
describe "class.make_direct" do
|
49
|
+
before do
|
50
|
+
@doc = HexaPDF::Document.new
|
51
|
+
end
|
52
|
+
|
53
|
+
it "doesn't touch wrapped direct objects" do
|
54
|
+
obj = HexaPDF::Object.new(5)
|
55
|
+
assert_same(obj, HexaPDF::Object.make_direct(obj))
|
56
|
+
end
|
57
|
+
|
58
|
+
it "works for simple values" do
|
59
|
+
obj = HexaPDF::Object.new(5, oid: 1, document: @doc)
|
60
|
+
assert_same(5, HexaPDF::Object.make_direct(obj))
|
61
|
+
end
|
62
|
+
|
63
|
+
it "works for hashes" do
|
64
|
+
obj = HexaPDF::Dictionary.new({a: 5, b: HexaPDF::Object.new(:a, oid: 3, document: @doc)},
|
65
|
+
oid: 1, document: @doc)
|
66
|
+
assert_equal({a: 5, b: :a}, HexaPDF::Object.make_direct(obj))
|
67
|
+
end
|
68
|
+
|
69
|
+
it "works for arrays" do
|
70
|
+
obj = HexaPDF::PDFArray.new([:b, HexaPDF::Object.new(:a, oid: 3, document: @doc)],
|
71
|
+
oid: 1, document: @doc)
|
72
|
+
assert_equal([:b, :a], HexaPDF::Object.make_direct(obj))
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
48
76
|
describe "initialize" do
|
49
77
|
it "uses a simple value as is" do
|
50
78
|
obj = HexaPDF::Object.new(5)
|
data/test/hexapdf/test_parser.rb
CHANGED
@@ -607,11 +607,22 @@ describe HexaPDF::Parser do
|
|
607
607
|
assert_equal(4, @parser.load_object(@xref).value)
|
608
608
|
end
|
609
609
|
|
610
|
+
it "handles an invalid object as first object" do
|
611
|
+
create_parser("2 0 obj\n(a(b\nendobj\n1 0 obj\n6\nendobj #)(\ntrailer\n<</Size 1>>")
|
612
|
+
assert_equal(6, @parser.load_object(@xref).value)
|
613
|
+
end
|
614
|
+
|
610
615
|
it "ignores invalid lines" do
|
611
616
|
create_parser("1 0 obj\n5\nendobj\nhello there\n1 0 obj\n6\nendobj\ntrailer\n<</Size 1>>")
|
612
617
|
assert_equal(6, @parser.load_object(@xref).value)
|
613
618
|
end
|
614
619
|
|
620
|
+
it "resets the header offset" do
|
621
|
+
create_parser("1 0 obj\n5\nendobj\ntrailer\n<</Size 1>>")
|
622
|
+
@parser.instance_variable_set(:@header_offset, 5)
|
623
|
+
assert_equal(5, @parser.load_object(@xref).value)
|
624
|
+
end
|
625
|
+
|
615
626
|
it "uses the last trailer" do
|
616
627
|
create_parser("trailer <</Size 1>>\ntrailer <</Size 2/Prev 342>>")
|
617
628
|
assert_equal({Size: 2}, @parser.reconstructed_revision.trailer.value)
|
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.1)>>
|
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.1)>>
|
76
76
|
endobj
|
77
77
|
2 0 obj
|
78
78
|
<</Length 10>>stream
|