hexapdf 0.40.0 → 0.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +45 -0
- data/examples/019-acro_form.rb +12 -23
- data/examples/027-composer_optional_content.rb +1 -1
- data/examples/030-pdfa.rb +6 -6
- data/examples/031-acro_form_java_script.rb +101 -0
- data/lib/hexapdf/cli/command.rb +11 -0
- data/lib/hexapdf/cli/form.rb +38 -9
- data/lib/hexapdf/cli/info.rb +4 -0
- data/lib/hexapdf/configuration.rb +10 -0
- data/lib/hexapdf/content/canvas.rb +2 -0
- data/lib/hexapdf/document/layout.rb +8 -1
- data/lib/hexapdf/encryption/aes.rb +13 -6
- data/lib/hexapdf/encryption/security_handler.rb +6 -4
- data/lib/hexapdf/font/cmap/parser.rb +1 -5
- data/lib/hexapdf/font/cmap.rb +22 -3
- data/lib/hexapdf/font_loader/from_configuration.rb +1 -1
- data/lib/hexapdf/font_loader/variant_from_name.rb +72 -0
- data/lib/hexapdf/font_loader.rb +1 -0
- data/lib/hexapdf/layout/style.rb +5 -4
- data/lib/hexapdf/type/acro_form/appearance_generator.rb +11 -76
- data/lib/hexapdf/type/acro_form/field.rb +14 -0
- data/lib/hexapdf/type/acro_form/form.rb +25 -8
- data/lib/hexapdf/type/acro_form/java_script_actions.rb +498 -0
- data/lib/hexapdf/type/acro_form/text_field.rb +78 -0
- data/lib/hexapdf/type/acro_form.rb +1 -0
- data/lib/hexapdf/type/annotations/widget.rb +1 -1
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/encryption/test_aes.rb +18 -8
- data/test/hexapdf/encryption/test_security_handler.rb +17 -0
- data/test/hexapdf/encryption/test_standard_security_handler.rb +1 -1
- data/test/hexapdf/font/cmap/test_parser.rb +5 -3
- data/test/hexapdf/font/test_cmap.rb +8 -0
- data/test/hexapdf/font_loader/test_from_configuration.rb +4 -0
- data/test/hexapdf/font_loader/test_variant_from_name.rb +34 -0
- data/test/hexapdf/type/acro_form/test_appearance_generator.rb +31 -47
- data/test/hexapdf/type/acro_form/test_field.rb +11 -0
- data/test/hexapdf/type/acro_form/test_form.rb +33 -0
- data/test/hexapdf/type/acro_form/test_java_script_actions.rb +226 -0
- data/test/hexapdf/type/acro_form/test_text_field.rb +44 -0
- metadata +7 -2
data/lib/hexapdf/font/cmap.rb
CHANGED
|
@@ -100,8 +100,10 @@ module HexaPDF
|
|
|
100
100
|
# The writing mode of the CMap: 0 for horizontal, 1 for vertical writing.
|
|
101
101
|
attr_accessor :wmode
|
|
102
102
|
|
|
103
|
-
attr_reader :codespace_ranges, :cid_mapping, :cid_range_mappings, :unicode_mapping
|
|
104
|
-
|
|
103
|
+
attr_reader :codespace_ranges, :cid_mapping, :cid_range_mappings, :unicode_mapping,
|
|
104
|
+
:unicode_range_mappings # :nodoc:
|
|
105
|
+
protected :codespace_ranges, :cid_mapping, :cid_range_mappings, :unicode_mapping,
|
|
106
|
+
:unicode_range_mappings
|
|
105
107
|
|
|
106
108
|
# Creates a new CMap object.
|
|
107
109
|
def initialize
|
|
@@ -109,6 +111,7 @@ module HexaPDF
|
|
|
109
111
|
@cid_mapping = {}
|
|
110
112
|
@cid_range_mappings = []
|
|
111
113
|
@unicode_mapping = {}
|
|
114
|
+
@unicode_range_mappings = []
|
|
112
115
|
end
|
|
113
116
|
|
|
114
117
|
# Add all mappings from the given CMap to this CMap.
|
|
@@ -117,6 +120,7 @@ module HexaPDF
|
|
|
117
120
|
@cid_mapping.merge!(cmap.cid_mapping)
|
|
118
121
|
@cid_range_mappings.concat(cmap.cid_range_mappings)
|
|
119
122
|
@unicode_mapping.merge!(cmap.unicode_mapping)
|
|
123
|
+
@unicode_range_mappings.concat(cmap.unicode_range_mappings)
|
|
120
124
|
end
|
|
121
125
|
|
|
122
126
|
# Add a codespace range using an array of ranges for the individual bytes.
|
|
@@ -193,10 +197,25 @@ module HexaPDF
|
|
|
193
197
|
@unicode_mapping[code] = string
|
|
194
198
|
end
|
|
195
199
|
|
|
200
|
+
# Adds a mapping from a range of character codes to strings starting with the given 16-bit
|
|
201
|
+
# integer values (representing the raw UTF-16BE characters).
|
|
202
|
+
def add_unicode_range_mapping(start_code, end_code, start_values)
|
|
203
|
+
@unicode_range_mappings << [start_code..end_code, start_values]
|
|
204
|
+
end
|
|
205
|
+
|
|
196
206
|
# Returns the Unicode string in UTF-8 encoding for the given character code, or +nil+ if no
|
|
197
207
|
# mapping was found.
|
|
198
208
|
def to_unicode(code)
|
|
199
|
-
unicode_mapping
|
|
209
|
+
@unicode_mapping.fetch(code) do
|
|
210
|
+
@unicode_range_mappings.reverse_each do |range, start_values|
|
|
211
|
+
if range.cover?(code)
|
|
212
|
+
str = start_values[0..-2].append(start_values[-1] + code - range.first).
|
|
213
|
+
pack('n*').encode(::Encoding::UTF_8, ::Encoding::UTF_16BE)
|
|
214
|
+
return @unicode_mapping[code] = str
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
nil
|
|
218
|
+
end
|
|
200
219
|
end
|
|
201
220
|
|
|
202
221
|
end
|
|
@@ -62,7 +62,7 @@ module HexaPDF
|
|
|
62
62
|
# Specifies whether the font should be subset if possible.
|
|
63
63
|
#
|
|
64
64
|
# This method uses the FromFile font loader behind the scenes.
|
|
65
|
-
def self.call(document, name, variant: :none, subset: true)
|
|
65
|
+
def self.call(document, name, variant: :none, subset: true, **)
|
|
66
66
|
file = document.config['font.map'].dig(name, variant)
|
|
67
67
|
return nil if file.nil?
|
|
68
68
|
|
|
@@ -0,0 +1,72 @@
|
|
|
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-2024 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 'hexapdf/font/true_type_wrapper'
|
|
38
|
+
require 'hexapdf/font_loader/from_file'
|
|
39
|
+
|
|
40
|
+
module HexaPDF
|
|
41
|
+
module FontLoader
|
|
42
|
+
|
|
43
|
+
# This module translates font names like 'Helvetica bold' into the arguments 'Helvetica' and
|
|
44
|
+
# {variant: :bold}.
|
|
45
|
+
#
|
|
46
|
+
# This eases the usage of font names where specifying a font variant is not straight-forward.
|
|
47
|
+
# The actual loading of the font is deferred to Document::Fonts#add.
|
|
48
|
+
#
|
|
49
|
+
# Note that this should be the last entry in the list of font loaders to ensure correct
|
|
50
|
+
# operation.
|
|
51
|
+
module VariantFromName
|
|
52
|
+
|
|
53
|
+
# Returns a font wrapper for the given font by splitting the font name into the font name part
|
|
54
|
+
# and variant selector part. If the the resulting font cannot be resolved, +nil+ is returned.
|
|
55
|
+
#
|
|
56
|
+
# A font name should have the form 'Fontname selector' where selector can be 'bold', 'italic'
|
|
57
|
+
# or 'bold_italic', for example 'Helvetica bold'.
|
|
58
|
+
#
|
|
59
|
+
# Note that a supplied :variant keyword argument is ignored!
|
|
60
|
+
def self.call(document, name, recursive_invocation: false, **options)
|
|
61
|
+
return if recursive_invocation
|
|
62
|
+
name, variant = name.split(/ (?=(?:bold|italic|bold_italic)\z)/, 2)
|
|
63
|
+
return if variant.nil?
|
|
64
|
+
|
|
65
|
+
options[:variant] = variant.to_sym
|
|
66
|
+
document.fonts.add(name, **options, recursive_invocation: true) rescue nil
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
end
|
|
72
|
+
end
|
data/lib/hexapdf/font_loader.rb
CHANGED
|
@@ -88,6 +88,7 @@ module HexaPDF
|
|
|
88
88
|
autoload(:Standard14, 'hexapdf/font_loader/standard14')
|
|
89
89
|
autoload(:FromConfiguration, 'hexapdf/font_loader/from_configuration')
|
|
90
90
|
autoload(:FromFile, 'hexapdf/font_loader/from_file')
|
|
91
|
+
autoload(:VariantFromName, 'hexapdf/font_loader/variant_from_name')
|
|
91
92
|
|
|
92
93
|
end
|
|
93
94
|
|
data/lib/hexapdf/layout/style.rb
CHANGED
|
@@ -614,7 +614,7 @@ module HexaPDF
|
|
|
614
614
|
# The font to be used, must be set to a valid font wrapper object before it can be used.
|
|
615
615
|
#
|
|
616
616
|
# HexaPDF::Composer handles this property specially in that it resolves a set string or array
|
|
617
|
-
# to a font wrapper object before doing else with the style object.
|
|
617
|
+
# to a font wrapper object before doing anything else with the style object.
|
|
618
618
|
#
|
|
619
619
|
# This is the only style property without a default value!
|
|
620
620
|
#
|
|
@@ -624,11 +624,12 @@ module HexaPDF
|
|
|
624
624
|
#
|
|
625
625
|
# #>pdf-composer100
|
|
626
626
|
# composer.text("Helvetica", font: composer.document.fonts.add("Helvetica"))
|
|
627
|
-
# composer.text("Courier", font: "Courier")
|
|
627
|
+
# composer.text("Courier", font: "Courier")
|
|
628
628
|
#
|
|
629
629
|
# helvetica_bold = composer.document.fonts.add("Helvetica", variant: :bold)
|
|
630
630
|
# composer.text("Helvetica Bold", font: helvetica_bold)
|
|
631
|
-
# composer.text("Courier Bold", font:
|
|
631
|
+
# composer.text("Courier Bold", font: "Courier bold")
|
|
632
|
+
# composer.text("Courier Bold also", font: ["Courier", variant: :bold])
|
|
632
633
|
|
|
633
634
|
##
|
|
634
635
|
# :method: font_size
|
|
@@ -1413,7 +1414,7 @@ module HexaPDF
|
|
|
1413
1414
|
# #>pdf-composer100
|
|
1414
1415
|
# composer.text("This is some longer text that does appear in two lines.")
|
|
1415
1416
|
# composer.text("This is some longer text that does not appear in two lines.",
|
|
1416
|
-
# height: 15,
|
|
1417
|
+
# height: 15, overflow: :truncate)
|
|
1417
1418
|
|
|
1418
1419
|
[
|
|
1419
1420
|
[:font, "raise HexaPDF::Error, 'No font set'"],
|
|
@@ -39,6 +39,7 @@ require 'hexapdf/error'
|
|
|
39
39
|
require 'hexapdf/layout/style'
|
|
40
40
|
require 'hexapdf/layout/text_fragment'
|
|
41
41
|
require 'hexapdf/layout/text_layouter'
|
|
42
|
+
require 'hexapdf/type/acro_form/java_script_actions'
|
|
42
43
|
|
|
43
44
|
module HexaPDF
|
|
44
45
|
module Type
|
|
@@ -129,14 +130,20 @@ module HexaPDF
|
|
|
129
130
|
# widget.background_color(1)
|
|
130
131
|
# widget.marker_style(style: :circle, size: 0, color: 0)
|
|
131
132
|
def create_check_box_appearances
|
|
132
|
-
|
|
133
|
-
|
|
133
|
+
normal_appearance = @widget.appearance_dict&.normal_appearance
|
|
134
|
+
if !normal_appearance.kind_of?(HexaPDF::Dictionary) || normal_appearance.kind_of?(HexaPDF::Stream)
|
|
135
|
+
(@widget[:AP] ||= {})[:N] = {Off: nil}
|
|
136
|
+
normal_appearance = @widget[:AP][:N]
|
|
137
|
+
normal_appearance[@field[:V] == :Off ? :Yes : @field[:V]] = nil
|
|
138
|
+
end
|
|
139
|
+
on_name = (normal_appearance.value.keys - [:Off]).first
|
|
134
140
|
unless on_name
|
|
135
141
|
raise HexaPDF::Error, "Widget of button field doesn't define name for on state"
|
|
136
142
|
end
|
|
137
143
|
|
|
138
144
|
@widget[:AS] = (@field[:V] == on_name ? on_name : :Off)
|
|
139
145
|
@widget.flag(:print)
|
|
146
|
+
@widget.unflag(:hidden)
|
|
140
147
|
|
|
141
148
|
border_style = @widget.border_style
|
|
142
149
|
marker_style = @widget.marker_style
|
|
@@ -206,6 +213,7 @@ module HexaPDF
|
|
|
206
213
|
|
|
207
214
|
@widget[:AS] = :N
|
|
208
215
|
@widget.flag(:print)
|
|
216
|
+
@widget.unflag(:hidden)
|
|
209
217
|
rect = @widget[:Rect]
|
|
210
218
|
rect.width = @document.config['acro_form.text_field.default_width'] if rect.width == 0
|
|
211
219
|
if rect.height == 0
|
|
@@ -358,7 +366,7 @@ module HexaPDF
|
|
|
358
366
|
|
|
359
367
|
# Draws a single line of text inside the widget's rectangle.
|
|
360
368
|
def draw_single_line_text(canvas, width, height, style, padding)
|
|
361
|
-
value, text_color =
|
|
369
|
+
value, text_color = JavaScriptActions.apply_format(@field.field_value, @field[:AA]&.[](:F))
|
|
362
370
|
style.fill_color = text_color if text_color
|
|
363
371
|
calculate_and_apply_font_size(value, style, width, height, padding)
|
|
364
372
|
line = HexaPDF::Layout::Line.new(@document.layout.text_fragments(value, style: style))
|
|
@@ -500,79 +508,6 @@ module HexaPDF
|
|
|
500
508
|
style.clear_cache
|
|
501
509
|
end
|
|
502
510
|
|
|
503
|
-
# Handles Javascript formatting routines for single-line text fields.
|
|
504
|
-
#
|
|
505
|
-
# Returns [value, nil_or_text_color] where value is the new, potentially adjusted field
|
|
506
|
-
# value and the second argument is either +nil+ or the color that should be used for the
|
|
507
|
-
# text value.
|
|
508
|
-
def apply_javascript_formatting(value)
|
|
509
|
-
format_action = @widget[:AA]&.[](:F)
|
|
510
|
-
return [value, nil] unless format_action && format_action[:S] == :JavaScript
|
|
511
|
-
if (match = AF_NUMBER_FORMAT_RE.match(format_action[:JS]))
|
|
512
|
-
apply_af_number_format(value, match)
|
|
513
|
-
else
|
|
514
|
-
[value, nil]
|
|
515
|
-
end
|
|
516
|
-
end
|
|
517
|
-
|
|
518
|
-
# Regular expression for matching the AFNumber_Format Javascript method.
|
|
519
|
-
AF_NUMBER_FORMAT_RE = /
|
|
520
|
-
\AAFNumber_Format\(
|
|
521
|
-
\s*(?<ndec>\d+)\s*,
|
|
522
|
-
\s*(?<sep_style>[0-3])\s*,
|
|
523
|
-
\s*(?<neg_style>[0-3])\s*,
|
|
524
|
-
\s*0\s*,
|
|
525
|
-
\s*(?<currency_string>".*?")\s*,
|
|
526
|
-
\s*(?<prepend>false|true)\s*
|
|
527
|
-
\);\z
|
|
528
|
-
/x
|
|
529
|
-
|
|
530
|
-
# Implements the Javascript AFNumber_Format method.
|
|
531
|
-
#
|
|
532
|
-
# See:
|
|
533
|
-
# - https://experienceleague.adobe.com/docs/experience-manager-learn/assets/FormsAPIReference.pdf
|
|
534
|
-
# - https://opensource.adobe.com/dc-acrobat-sdk-docs/library/jsapiref/JS_API_AcroJS.html#printf
|
|
535
|
-
def apply_af_number_format(value, match)
|
|
536
|
-
value = value.to_f
|
|
537
|
-
format = "%.#{match[:ndec]}f"
|
|
538
|
-
text_color = 'black'
|
|
539
|
-
|
|
540
|
-
currency_string = JSON.parse(match[:currency_string])
|
|
541
|
-
format = (match[:prepend] == 'true' ? currency_string + format : format + currency_string)
|
|
542
|
-
|
|
543
|
-
if value < 0
|
|
544
|
-
value = value.abs
|
|
545
|
-
case match[:neg_style]
|
|
546
|
-
when '0' # MinusBlack
|
|
547
|
-
format = "-#{format}"
|
|
548
|
-
when '1' # Red
|
|
549
|
-
text_color = 'red'
|
|
550
|
-
when '2' # ParensBlack
|
|
551
|
-
format = "(#{format})"
|
|
552
|
-
when '3' # ParensRed
|
|
553
|
-
format = "(#{format})"
|
|
554
|
-
text_color = 'red'
|
|
555
|
-
end
|
|
556
|
-
end
|
|
557
|
-
|
|
558
|
-
result = sprintf(format, value)
|
|
559
|
-
|
|
560
|
-
# sep_style: 0=12,345.67, 1=12345.67, 2=12.345,67, 3=12345,67
|
|
561
|
-
before_decimal_point, after_decimal_point = result.split('.')
|
|
562
|
-
if match[:sep_style] == '0' || match[:sep_style] == '2'
|
|
563
|
-
separator = (match[:sep_style] == '0' ? ',' : '.')
|
|
564
|
-
before_decimal_point.gsub!(/\B(?=(\d\d\d)+(?:[^\d]|\z))/, separator)
|
|
565
|
-
end
|
|
566
|
-
result = if after_decimal_point
|
|
567
|
-
decimal_point = (match[:sep_style] =~ /[01]/ ? '.' : ',')
|
|
568
|
-
"#{before_decimal_point}#{decimal_point}#{after_decimal_point}"
|
|
569
|
-
else
|
|
570
|
-
before_decimal_point
|
|
571
|
-
end
|
|
572
|
-
|
|
573
|
-
[result, text_color]
|
|
574
|
-
end
|
|
575
|
-
|
|
576
511
|
end
|
|
577
512
|
|
|
578
513
|
end
|
|
@@ -155,6 +155,12 @@ module HexaPDF
|
|
|
155
155
|
field.value[name].nil? ? nil : field[name]
|
|
156
156
|
end
|
|
157
157
|
|
|
158
|
+
# Wraps the given +field+ object inside the correct field class and returns the wrapped
|
|
159
|
+
# object.
|
|
160
|
+
def self.wrap(document, field)
|
|
161
|
+
document.wrap(field, type: :XXAcroFormField, subtype: inherited_value(field, :FT))
|
|
162
|
+
end
|
|
163
|
+
|
|
158
164
|
# Form fields must always be indirect objects.
|
|
159
165
|
def must_be_indirect?
|
|
160
166
|
true
|
|
@@ -236,6 +242,14 @@ module HexaPDF
|
|
|
236
242
|
kids.nil? || kids.empty? || kids.none? {|kid| kid.key?(:T) }
|
|
237
243
|
end
|
|
238
244
|
|
|
245
|
+
# Returns self.
|
|
246
|
+
#
|
|
247
|
+
# This method is only here to make it easier to get the form field when the object may
|
|
248
|
+
# either be a form field or a field widget.
|
|
249
|
+
def form_field
|
|
250
|
+
self
|
|
251
|
+
end
|
|
252
|
+
|
|
239
253
|
# Returns +true+ if the field contains an embedded widget.
|
|
240
254
|
def embedded_widget?
|
|
241
255
|
key?(:Subtype)
|
|
@@ -99,11 +99,11 @@ module HexaPDF
|
|
|
99
99
|
document.pages.each do |page|
|
|
100
100
|
page.each_annotation do |annot|
|
|
101
101
|
if !annot.key?(:Parent) && annot.key?(:FT)
|
|
102
|
-
result <<
|
|
102
|
+
result << Field.wrap(document, annot)
|
|
103
103
|
elsif annot.key?(:Parent)
|
|
104
104
|
field = annot[:Parent]
|
|
105
105
|
field = field[:Parent] while field[:Parent]
|
|
106
|
-
result <<
|
|
106
|
+
result << Field.wrap(document, field)
|
|
107
107
|
end
|
|
108
108
|
end
|
|
109
109
|
end
|
|
@@ -129,8 +129,7 @@ module HexaPDF
|
|
|
129
129
|
array.each_with_index do |field, index|
|
|
130
130
|
next if field.nil?
|
|
131
131
|
unless field.respond_to?(:type) && field.type == :XXAcroFormField
|
|
132
|
-
array[index] = field =
|
|
133
|
-
subtype: Field.inherited_value(field, :FT))
|
|
132
|
+
array[index] = field = Field.wrap(document, field)
|
|
134
133
|
end
|
|
135
134
|
if field.terminal_field?
|
|
136
135
|
yield(field)
|
|
@@ -153,8 +152,7 @@ module HexaPDF
|
|
|
153
152
|
name.split('.').each do |part|
|
|
154
153
|
field = nil
|
|
155
154
|
fields&.each do |f|
|
|
156
|
-
f =
|
|
157
|
-
subtype: Field.inherited_value(f, :FT))
|
|
155
|
+
f = Field.wrap(document, f)
|
|
158
156
|
next unless f[:T] == part
|
|
159
157
|
field = f
|
|
160
158
|
fields = field[:Kids] unless field.terminal_field?
|
|
@@ -420,6 +418,26 @@ module HexaPDF
|
|
|
420
418
|
not_flattened
|
|
421
419
|
end
|
|
422
420
|
|
|
421
|
+
# Recalculates all form fields that have a calculate action applied (which are all fields
|
|
422
|
+
# listed in the /CO entry).
|
|
423
|
+
#
|
|
424
|
+
# If HexaPDF doesn't support a calculation method or an error occurs during calculation, the
|
|
425
|
+
# field value is not updated.
|
|
426
|
+
#
|
|
427
|
+
# Note that calculations are *not* done automatically when a form field's value changes
|
|
428
|
+
# since it would lead to possibly many calls to this actions. So first fill in all field
|
|
429
|
+
# values and then call this method.
|
|
430
|
+
#
|
|
431
|
+
# See: JavaScriptActions
|
|
432
|
+
def recalculate_fields
|
|
433
|
+
self[:CO]&.each do |field|
|
|
434
|
+
field = Field.wrap(document, field)
|
|
435
|
+
next unless field && (calculation_action = field[:AA]&.[](:C))
|
|
436
|
+
result = JavaScriptActions.calculate(self, calculation_action)
|
|
437
|
+
field.form_field.field_value = result if result
|
|
438
|
+
end
|
|
439
|
+
end
|
|
440
|
+
|
|
423
441
|
private
|
|
424
442
|
|
|
425
443
|
# Creates a new field with the full name +name+ and the field type +type+.
|
|
@@ -468,8 +486,7 @@ module HexaPDF
|
|
|
468
486
|
end
|
|
469
487
|
next false unless field.key?(:T) # Skip widgets
|
|
470
488
|
|
|
471
|
-
field =
|
|
472
|
-
subtype: Field.inherited_value(field, :FT))
|
|
489
|
+
field = Field.wrap(document, field)
|
|
473
490
|
reject = false
|
|
474
491
|
if field[:Parent] != parent
|
|
475
492
|
yield("Parent entry of field (#{field.oid},#{field.gen}) invalid", true)
|