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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c25e9ad58be5a660ea6abb5a56896fef953d67578425b01a9de86f1cc1ae8a1d
4
- data.tar.gz: 45133e292e45343903ee53b44f709149bea0353d9aa7e2a2a9f3c6fa676da1dd
3
+ metadata.gz: 35e82f82af4a5ad469ce89111633daea2b86ca0f9a965282824c03bd5f9b8af6
4
+ data.tar.gz: 47e8c25c2f8d1a9243ed9c6e5c85ba6f65aa1cc52b8abe62c3fdaed3a72f8361
5
5
  SHA512:
6
- metadata.gz: 7762f8c137e7b29c6cdbc15c8ce3fd9387b96dfc4a22a64ee171fc0c9c8d7f67951b42fb491cb283e160aced6a37d0e6eaa30599fa63d0876a77a9be8506d493
7
- data.tar.gz: 7a92be45bc9810a3e085c67fe0ee7b0a245925179ec345bbf0f3cb56cc4b2195bc6faca01f27a64c86646c7ef5c91dd1c6f732140905a6d208c4e8c476ff0458
6
+ metadata.gz: d20594b2b72776e4eea071d87036e2151cfcbb5a36f09d1b11ecd4d055168a613ba1d8fcd308b1748706b30c4ea6cc84742e2f895a21f594ef6c9a79351f5cc1
7
+ data.tar.gz: fb36a88b91eced4f62b027f920e5233e626f4d469087fe7c46419091020324b9d92d0f5a70d55479064c93bd787513950b2ce84bbcb185834a706c7f83fa9555
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- acro_that (0.1.8)
4
+ acro_that (1.0.0)
5
5
  chunky_png (~> 1.4)
6
6
 
7
7
  GEM
@@ -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
- normalized_value = if is_button_field
182
- # For checkboxes/radio buttons, normalize to "Yes" or "Off"
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
- v_token = DictScan.encode_pdf_string(normalized_value)
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
- # Create a form XObject that draws a checked checkbox
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
- # Calculate checkmark path
561
- check_x1 = width * 0.25
562
- check_y1 = height * 0.45
563
- check_x2 = width * 0.45
564
- check_y2 = height * 0.25
565
- check_x3 = width * 0.75
566
- check_y3 = height * 0.75
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
- content_stream += "0 0 0 rg\n" # Black color (darker)
570
- content_stream += "#{border_width} w\n" # Line width
571
- # Draw checkmark only (no box border)
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.encode_pdf_string(checkbox_value)}\n"
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
- check_x1 = width * 0.25
81
- check_y1 = height * 0.45
82
- check_x2 = width * 0.45
83
- check_y2 = height * 0.25
84
- check_x3 = width * 0.75
85
- check_y3 = height * 0.75
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
- check_x1 = width * 0.25
169
- check_y1 = height * 0.45
170
- check_x2 = width * 0.45
171
- check_y2 = height * 0.25
172
- check_x3 = width * 0.75
173
- check_y3 = height * 0.75
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)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AcroThat
4
- VERSION = "1.0.0"
4
+ VERSION = "1.0.1"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: acro_that
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Wynkoop