acro_that 1.0.0 → 1.0.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/Gemfile.lock +1 -1
- data/lib/acro_that/actions/add_field.rb +3 -1
- data/lib/acro_that/actions/update_field.rb +79 -24
- data/lib/acro_that/fields/base.rb +8 -2
- data/lib/acro_that/fields/checkbox.rb +41 -8
- data/lib/acro_that/fields/radio.rb +31 -9
- data/lib/acro_that/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 35e82f82af4a5ad469ce89111633daea2b86ca0f9a965282824c03bd5f9b8af6
|
|
4
|
+
data.tar.gz: 47e8c25c2f8d1a9243ed9c6e5c85ba6f65aa1cc52b8abe62c3fdaed3a72f8361
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d20594b2b72776e4eea071d87036e2151cfcbb5a36f09d1b11ecd4d055168a613ba1d8fcd308b1748706b30c4ea6cc84742e2f895a21f594ef6c9a79351f5cc1
|
|
7
|
+
data.tar.gz: fb36a88b91eced4f62b027f920e5233e626f4d469087fe7c46419091020324b9d92d0f5a70d55479064c93bd787513950b2ce84bbcb185834a706c7f83fa9555
|
data/Gemfile.lock
CHANGED
|
@@ -54,12 +54,14 @@ module AcroThat
|
|
|
54
54
|
def create_field_handler(type_input)
|
|
55
55
|
is_radio = [:radio, "radio"].include?(type_input)
|
|
56
56
|
group_id = @options[:group_id]
|
|
57
|
+
is_button = [:button, "button", "/Btn", "/btn"].include?(type_input)
|
|
57
58
|
|
|
58
59
|
if is_radio && group_id
|
|
59
60
|
AcroThat::Fields::Radio.new(@document, @name, @options.merge(metadata: @metadata))
|
|
60
61
|
elsif [:signature, "signature", "/Sig"].include?(type_input)
|
|
61
62
|
AcroThat::Fields::Signature.new(@document, @name, @options.merge(metadata: @metadata))
|
|
62
|
-
elsif [:checkbox, "checkbox"].include?(type_input)
|
|
63
|
+
elsif [:checkbox, "checkbox"].include?(type_input) || is_button
|
|
64
|
+
# :button type maps to /Btn which are checkboxes by default (unless radio flag is set)
|
|
63
65
|
AcroThat::Fields::Checkbox.new(@document, @name, @options.merge(metadata: @metadata))
|
|
64
66
|
else
|
|
65
67
|
# Default to text field
|
|
@@ -178,8 +178,34 @@ module AcroThat
|
|
|
178
178
|
ft_pattern = %r{/FT\s+/Btn}
|
|
179
179
|
is_button_field = ft_pattern.match(dict_body)
|
|
180
180
|
|
|
181
|
-
|
|
182
|
-
|
|
181
|
+
# Check if it's a radio button by checking field flags
|
|
182
|
+
# For widgets, check the parent field's flags since widgets don't have /Ff directly
|
|
183
|
+
is_radio = false
|
|
184
|
+
if is_button_field
|
|
185
|
+
field_flags_match = dict_body.match(%r{/Ff\s+(\d+)})
|
|
186
|
+
if field_flags_match
|
|
187
|
+
field_flags = field_flags_match[1].to_i
|
|
188
|
+
# Radio button flag is bit 15 = 32768
|
|
189
|
+
is_radio = field_flags.anybits?(32_768)
|
|
190
|
+
elsif dict_body.include?("/Parent")
|
|
191
|
+
# This is a widget - check parent field's flags
|
|
192
|
+
parent_tok = DictScan.value_token_after("/Parent", dict_body)
|
|
193
|
+
if parent_tok && parent_tok =~ /\A(\d+)\s+(\d+)\s+R/
|
|
194
|
+
parent_ref = [Integer(::Regexp.last_match(1)), Integer(::Regexp.last_match(2))]
|
|
195
|
+
parent_body = get_object_body_with_patch(parent_ref)
|
|
196
|
+
if parent_body
|
|
197
|
+
parent_flags_match = parent_body.match(%r{/Ff\s+(\d+)})
|
|
198
|
+
if parent_flags_match
|
|
199
|
+
parent_flags = parent_flags_match[1].to_i
|
|
200
|
+
is_radio = parent_flags.anybits?(32_768)
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
normalized_value = if is_button_field && !is_radio
|
|
208
|
+
# For checkboxes, normalize to "Yes" or "Off"
|
|
183
209
|
# Accept "Yes", "/Yes" (PDF name format), true (boolean), or "true" (string)
|
|
184
210
|
value_str = new_value.to_s
|
|
185
211
|
is_checked = ["Yes", "/Yes", "true"].include?(value_str) || new_value == true
|
|
@@ -189,7 +215,13 @@ module AcroThat
|
|
|
189
215
|
end
|
|
190
216
|
|
|
191
217
|
# Encode the normalized value
|
|
192
|
-
|
|
218
|
+
# For checkboxes, use PDF name format to match /AS appearance state format
|
|
219
|
+
# For radio buttons and other fields, use PDF string format
|
|
220
|
+
v_token = if is_button_field && !is_radio
|
|
221
|
+
DictScan.encode_pdf_name(normalized_value)
|
|
222
|
+
else
|
|
223
|
+
DictScan.encode_pdf_string(normalized_value)
|
|
224
|
+
end
|
|
193
225
|
|
|
194
226
|
# Find /V using pattern matching to ensure we get the complete key
|
|
195
227
|
v_key_pattern = %r{/V(?=[\s(<\[/])}
|
|
@@ -545,34 +577,57 @@ module AcroThat
|
|
|
545
577
|
end
|
|
546
578
|
|
|
547
579
|
def create_checkbox_yes_appearance(width, height)
|
|
548
|
-
|
|
549
|
-
# Box outline + checkmark
|
|
550
|
-
# Scale to match width and height
|
|
551
|
-
# Simple appearance: draw a box and a checkmark
|
|
552
|
-
# For simplicity, use PDF drawing operators
|
|
553
|
-
# Box: rectangle from (0,0) to (width, height)
|
|
554
|
-
# Checkmark: simple path drawing
|
|
555
|
-
|
|
556
|
-
# PDF content stream for checked checkbox
|
|
557
|
-
# Draw just the checkmark (no box border)
|
|
580
|
+
line_width = [width * 0.05, height * 0.05].min
|
|
558
581
|
border_width = [width * 0.08, height * 0.08].min
|
|
559
582
|
|
|
560
|
-
#
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
583
|
+
# Define checkmark in normalized coordinates (0-1 range) for consistent aspect ratio
|
|
584
|
+
# Checkmark shape: three points forming a checkmark
|
|
585
|
+
norm_x1 = 0.25
|
|
586
|
+
norm_y1 = 0.55
|
|
587
|
+
norm_x2 = 0.45
|
|
588
|
+
norm_y2 = 0.35
|
|
589
|
+
norm_x3 = 0.75
|
|
590
|
+
norm_y3 = 0.85
|
|
591
|
+
|
|
592
|
+
# Calculate scale to maximize size while maintaining aspect ratio
|
|
593
|
+
# Use the smaller dimension to ensure it fits
|
|
594
|
+
scale = [width, height].min * 0.85 # Use 85% of the smaller dimension
|
|
595
|
+
|
|
596
|
+
# Calculate checkmark dimensions
|
|
597
|
+
check_width = scale
|
|
598
|
+
check_height = scale
|
|
599
|
+
|
|
600
|
+
# Center the checkmark in the box
|
|
601
|
+
offset_x = (width - check_width) / 2
|
|
602
|
+
offset_y = (height - check_height) / 2
|
|
603
|
+
|
|
604
|
+
# Calculate actual coordinates
|
|
605
|
+
check_x1 = offset_x + norm_x1 * check_width
|
|
606
|
+
check_y1 = offset_y + norm_y1 * check_height
|
|
607
|
+
check_x2 = offset_x + norm_x2 * check_width
|
|
608
|
+
check_y2 = offset_y + norm_y2 * check_height
|
|
609
|
+
check_x3 = offset_x + norm_x3 * check_width
|
|
610
|
+
check_y3 = offset_y + norm_y3 * check_height
|
|
567
611
|
|
|
568
612
|
content_stream = "q\n"
|
|
569
|
-
|
|
570
|
-
content_stream += "
|
|
571
|
-
|
|
613
|
+
# Draw square border around field bounds
|
|
614
|
+
content_stream += "0 0 0 RG\n" # Black stroke color
|
|
615
|
+
content_stream += "#{line_width} w\n" # Line width
|
|
616
|
+
# Draw rectangle from (0,0) to (width, height)
|
|
617
|
+
content_stream += "0 0 m\n"
|
|
618
|
+
content_stream += "#{width} 0 l\n"
|
|
619
|
+
content_stream += "#{width} #{height} l\n"
|
|
620
|
+
content_stream += "0 #{height} l\n"
|
|
621
|
+
content_stream += "0 0 l\n"
|
|
622
|
+
content_stream += "S\n" # Stroke the border
|
|
623
|
+
|
|
624
|
+
# Draw checkmark
|
|
625
|
+
content_stream += "0 0 0 rg\n" # Black fill color
|
|
626
|
+
content_stream += "#{border_width} w\n" # Line width for checkmark
|
|
572
627
|
content_stream += "#{check_x1} #{check_y1} m\n"
|
|
573
628
|
content_stream += "#{check_x2} #{check_y2} l\n"
|
|
574
629
|
content_stream += "#{check_x3} #{check_y3} l\n"
|
|
575
|
-
content_stream += "S\n" # Stroke
|
|
630
|
+
content_stream += "S\n" # Stroke the checkmark
|
|
576
631
|
content_stream += "Q\n"
|
|
577
632
|
|
|
578
633
|
build_form_xobject(content_stream, width, height)
|
|
@@ -107,6 +107,7 @@ module AcroThat
|
|
|
107
107
|
end
|
|
108
108
|
|
|
109
109
|
# For radio buttons, /V should only be set if explicitly selected
|
|
110
|
+
# For checkboxes, /V should be a PDF name to match /AS format
|
|
110
111
|
# For other fields, encode as PDF string
|
|
111
112
|
if should_set_value && normalized_field_value && !normalized_field_value.to_s.empty?
|
|
112
113
|
# For radio buttons, only set /V if selected option is explicitly set to true
|
|
@@ -115,6 +116,10 @@ module AcroThat
|
|
|
115
116
|
if [true, "true"].include?(@options[:selected]) && normalized_field_value.to_s.start_with?("/")
|
|
116
117
|
dict += " /V #{normalized_field_value}\n"
|
|
117
118
|
end
|
|
119
|
+
elsif type == "/Btn"
|
|
120
|
+
# For checkboxes (button fields that aren't radio), encode value as PDF name
|
|
121
|
+
# to match the /AS appearance state format (/Yes or /Off)
|
|
122
|
+
dict += " /V #{DictScan.encode_pdf_name(normalized_field_value)}\n"
|
|
118
123
|
else
|
|
119
124
|
dict += " /V #{DictScan.encode_pdf_string(normalized_field_value)}\n"
|
|
120
125
|
end
|
|
@@ -156,10 +161,11 @@ module AcroThat
|
|
|
156
161
|
end
|
|
157
162
|
|
|
158
163
|
if type == "/Btn" && should_set_value
|
|
164
|
+
# For checkboxes, encode value as PDF name to match /AS appearance state format
|
|
159
165
|
value_str = value.to_s
|
|
160
166
|
is_checked = ["Yes", "/Yes", "true"].include?(value_str) || value == true
|
|
161
167
|
checkbox_value = is_checked ? "Yes" : "Off"
|
|
162
|
-
widget += " /V #{DictScan.
|
|
168
|
+
widget += " /V #{DictScan.encode_pdf_name(checkbox_value)}\n"
|
|
163
169
|
elsif should_set_value && value && !value.empty?
|
|
164
170
|
widget += " /V #{DictScan.encode_pdf_string(value)}\n"
|
|
165
171
|
end
|
|
@@ -193,7 +199,7 @@ module AcroThat
|
|
|
193
199
|
|
|
194
200
|
af_body = get_object_body_with_patch(af_ref)
|
|
195
201
|
# Use +"" instead of dup to create a mutable copy without keeping reference to original
|
|
196
|
-
patched = af_body
|
|
202
|
+
patched = af_body.to_s
|
|
197
203
|
|
|
198
204
|
# Step 1: Add field to /Fields array
|
|
199
205
|
fields_array_ref = DictScan.value_token_after("/Fields", patched)
|
|
@@ -44,7 +44,7 @@ module AcroThat
|
|
|
44
44
|
widget_ref = [widget_obj_num, 0]
|
|
45
45
|
original_widget_body = get_object_body_with_patch(widget_ref)
|
|
46
46
|
# Use +"" instead of dup to create a mutable copy without keeping reference to original
|
|
47
|
-
widget_body = original_widget_body
|
|
47
|
+
widget_body = original_widget_body.to_s
|
|
48
48
|
|
|
49
49
|
ap_dict = "<<\n /N <<\n /Yes #{yes_obj_num} 0 R\n /Off #{off_obj_num} 0 R\n >>\n>>"
|
|
50
50
|
|
|
@@ -58,12 +58,23 @@ module AcroThat
|
|
|
58
58
|
is_checked = value_str == "Yes" || value_str == "/Yes" || value_str == "true" || @field_value == true
|
|
59
59
|
normalized_value = is_checked ? "Yes" : "Off"
|
|
60
60
|
|
|
61
|
+
# Set /V to match /AS - both should be PDF names for checkboxes
|
|
62
|
+
v_value = DictScan.encode_pdf_name(normalized_value)
|
|
63
|
+
|
|
61
64
|
as_value = if normalized_value == "Yes"
|
|
62
65
|
"/Yes"
|
|
63
66
|
else
|
|
64
67
|
"/Off"
|
|
65
68
|
end
|
|
66
69
|
|
|
70
|
+
# Update /V to ensure it matches /AS format (both PDF names)
|
|
71
|
+
widget_body = if widget_body.include?("/V")
|
|
72
|
+
DictScan.replace_key_value(widget_body, "/V", v_value)
|
|
73
|
+
else
|
|
74
|
+
DictScan.upsert_key_value(widget_body, "/V", v_value)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Update /AS to match the normalized value
|
|
67
78
|
widget_body = if widget_body.include?("/AS")
|
|
68
79
|
DictScan.replace_key_value(widget_body, "/AS", as_value)
|
|
69
80
|
else
|
|
@@ -74,15 +85,37 @@ module AcroThat
|
|
|
74
85
|
end
|
|
75
86
|
|
|
76
87
|
def create_checkbox_yes_appearance(width, height)
|
|
77
|
-
border_width = [width * 0.08, height * 0.08].min
|
|
78
88
|
line_width = [width * 0.05, height * 0.05].min
|
|
89
|
+
border_width = [width * 0.08, height * 0.08].min
|
|
79
90
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
91
|
+
# Define checkmark in normalized coordinates (0-1 range) for consistent aspect ratio
|
|
92
|
+
# Checkmark shape: three points forming a checkmark
|
|
93
|
+
norm_x1 = 0.25
|
|
94
|
+
norm_y1 = 0.55
|
|
95
|
+
norm_x2 = 0.45
|
|
96
|
+
norm_y2 = 0.35
|
|
97
|
+
norm_x3 = 0.75
|
|
98
|
+
norm_y3 = 0.85
|
|
99
|
+
|
|
100
|
+
# Calculate scale to maximize size while maintaining aspect ratio
|
|
101
|
+
# Use the smaller dimension to ensure it fits
|
|
102
|
+
scale = [width, height].min * 0.85 # Use 85% of the smaller dimension
|
|
103
|
+
|
|
104
|
+
# Calculate checkmark dimensions
|
|
105
|
+
check_width = scale
|
|
106
|
+
check_height = scale
|
|
107
|
+
|
|
108
|
+
# Center the checkmark in the box
|
|
109
|
+
offset_x = (width - check_width) / 2
|
|
110
|
+
offset_y = (height - check_height) / 2
|
|
111
|
+
|
|
112
|
+
# Calculate actual coordinates
|
|
113
|
+
check_x1 = offset_x + norm_x1 * check_width
|
|
114
|
+
check_y1 = offset_y + norm_y1 * check_height
|
|
115
|
+
check_x2 = offset_x + norm_x2 * check_width
|
|
116
|
+
check_y2 = offset_y + norm_y2 * check_height
|
|
117
|
+
check_x3 = offset_x + norm_x3 * check_width
|
|
118
|
+
check_y3 = offset_y + norm_y3 * check_height
|
|
86
119
|
|
|
87
120
|
content_stream = "q\n"
|
|
88
121
|
# Draw square border around field bounds
|
|
@@ -74,7 +74,7 @@ module AcroThat
|
|
|
74
74
|
return unless original_widget_body
|
|
75
75
|
|
|
76
76
|
# Store original before modifying to avoid loading again
|
|
77
|
-
widget_body = original_widget_body
|
|
77
|
+
widget_body = original_widget_body.to_s
|
|
78
78
|
|
|
79
79
|
# Ensure we have a valid export value - if empty, generate a unique one
|
|
80
80
|
# Export value must be unique for each widget in the group for mutual exclusivity
|
|
@@ -124,7 +124,7 @@ module AcroThat
|
|
|
124
124
|
original_parent_body = get_object_body_with_patch(parent_ref)
|
|
125
125
|
if original_parent_body
|
|
126
126
|
# Store original before modifying
|
|
127
|
-
parent_body = original_parent_body
|
|
127
|
+
parent_body = original_parent_body.to_s
|
|
128
128
|
# Update parent's /V to match the selected button's export value
|
|
129
129
|
parent_body = if parent_body.include?("/V")
|
|
130
130
|
DictScan.replace_key_value(parent_body, "/V", export_name)
|
|
@@ -143,7 +143,7 @@ module AcroThat
|
|
|
143
143
|
return unless original_parent_body_for_ap
|
|
144
144
|
|
|
145
145
|
# Use a working copy for modification
|
|
146
|
-
parent_body_for_ap = original_parent_body_for_ap
|
|
146
|
+
parent_body_for_ap = original_parent_body_for_ap.to_s
|
|
147
147
|
parent_ap_tok = DictScan.value_token_after("/AP", parent_body_for_ap)
|
|
148
148
|
if parent_ap_tok && parent_ap_tok.start_with?("<<")
|
|
149
149
|
n_tok = DictScan.value_token_after("/N", parent_ap_tok)
|
|
@@ -165,12 +165,34 @@ module AcroThat
|
|
|
165
165
|
# Draw only the checkmark (no border)
|
|
166
166
|
border_width = [width * 0.08, height * 0.08].min
|
|
167
167
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
168
|
+
# Define checkmark in normalized coordinates (0-1 range) for consistent aspect ratio
|
|
169
|
+
# Checkmark shape: three points forming a checkmark
|
|
170
|
+
norm_x1 = 0.25
|
|
171
|
+
norm_y1 = 0.55
|
|
172
|
+
norm_x2 = 0.45
|
|
173
|
+
norm_y2 = 0.35
|
|
174
|
+
norm_x3 = 0.75
|
|
175
|
+
norm_y3 = 0.85
|
|
176
|
+
|
|
177
|
+
# Calculate scale to maximize size while maintaining aspect ratio
|
|
178
|
+
# Use the smaller dimension to ensure it fits
|
|
179
|
+
scale = [width, height].min * 0.85 # Use 85% of the smaller dimension
|
|
180
|
+
|
|
181
|
+
# Calculate checkmark dimensions
|
|
182
|
+
check_width = scale
|
|
183
|
+
check_height = scale
|
|
184
|
+
|
|
185
|
+
# Center the checkmark in the box
|
|
186
|
+
offset_x = (width - check_width) / 2
|
|
187
|
+
offset_y = (height - check_height) / 2
|
|
188
|
+
|
|
189
|
+
# Calculate actual coordinates
|
|
190
|
+
check_x1 = offset_x + norm_x1 * check_width
|
|
191
|
+
check_y1 = offset_y + norm_y1 * check_height
|
|
192
|
+
check_x2 = offset_x + norm_x2 * check_width
|
|
193
|
+
check_y2 = offset_y + norm_y2 * check_height
|
|
194
|
+
check_x3 = offset_x + norm_x3 * check_width
|
|
195
|
+
check_y3 = offset_y + norm_y3 * check_height
|
|
174
196
|
|
|
175
197
|
content_stream = "q\n"
|
|
176
198
|
# Draw checkmark only (no border)
|
data/lib/acro_that/version.rb
CHANGED