acro_that 0.1.2 → 0.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: aa3b37611a5ca19d2cb0fa1c86d3821fb139b6f65ce9d3cd314d913b6e9e5db5
4
- data.tar.gz: a4d07aaa7f268eb151b324b03e9575f563880b2cf97fd0172edc0e96c35e6eef
3
+ metadata.gz: 56f6be44d023bdedaf254cc097d18791f284793d0c79c07847fd462c0643cc91
4
+ data.tar.gz: b649d290433fac6a6113a74ca47bfde60e31687f6e93b85e30c32053ee416b3a
5
5
  SHA512:
6
- metadata.gz: a545eea311d77e7b46459e710925f8ec7b61baebd846ec7f602f6a77ab9532bab87d37be4bba53b00b43a09281e79faba79fcb71e5371c36953e877d556dcdd8
7
- data.tar.gz: b9074d19a0cc330af44f4fd5609f49f8892e20deb95d522fa7ca5fb3099ca8c71f01cbc98ef7d3e6ed95620af090afc96035fbeaaf4a551ca6f9e4003c49f4a8
6
+ metadata.gz: b27c5ec88a2cfdec49750d9d3316979c90c0815461eb6281b4cf602dc6533c8761c8fba7fb49407256b9ade87f2f3731e8f9d121c4ef089d08be2901107cc295
7
+ data.tar.gz: f1b41cef40049564af8f081547460d72e787a721f676db4c80cd74eb21844b48364ac3b871e6ad2839505908d9e5be3b8d643c16812bd91ae6d0e858bcfb51eb
data/CHANGELOG.md CHANGED
@@ -5,6 +5,22 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.1.3] - 2025-01-XX
9
+
10
+ ### Fixed
11
+ - Fixed bug where fields added to multi-page PDFs were all placed on the same page. Fields now correctly appear on their specified pages when using the `page` option in `add_field`.
12
+
13
+ ### Changed
14
+ - Refactored page-finding logic to eliminate code duplication across `Document` and `AddField` classes
15
+ - Unified page discovery through `Document#find_all_pages` and `Document#find_page_by_number` methods
16
+ - Updated all page detection patterns to use centralized `DictScan.is_page?` utility method
17
+
18
+ ### Added
19
+ - `DictScan.is_page?` utility method for consistent page object detection across the codebase
20
+ - `Document#find_all_pages` private method for unified page discovery in document order
21
+ - `Document#find_page_by_number` private method for finding pages by page number
22
+ - Exposed `find_page_by_number` through `Base` module for use in action classes
23
+
8
24
  ## [0.1.1] - 2025-10-31
9
25
 
10
26
  ### Added
@@ -153,61 +153,8 @@ module AcroThat
153
153
  end
154
154
 
155
155
  def find_page_ref(page_num)
156
- page_objects = []
157
- resolver.each_object do |ref, body|
158
- next unless body
159
-
160
- # Check for /Type /Page (actual page, not /Type/Pages)
161
- # Must match /Type /Page or /Type/Page but NOT /Type/Pages
162
- is_page = body.include?("/Type /Page") ||
163
- (body =~ %r{/Type\s*/Page(?!s)\b})
164
- next unless is_page
165
-
166
- page_objects << ref
167
- end
168
-
169
- # If still no pages found, try to find them via the page tree
170
- if page_objects.empty?
171
- # Find the document catalog's /Pages entry
172
- root_ref = resolver.root_ref
173
- if root_ref
174
- catalog_body = resolver.object_body(root_ref)
175
- if catalog_body && catalog_body =~ %r{/Pages\s+(\d+)\s+(\d+)\s+R}
176
- pages_ref = [Integer(::Regexp.last_match(1)), Integer(::Regexp.last_match(2))]
177
- pages_body = resolver.object_body(pages_ref)
178
-
179
- # Extract /Kids array from Pages object
180
- if pages_body && pages_body =~ %r{/Kids\s*\[(.*?)\]}m
181
- kids_array = ::Regexp.last_match(1)
182
- # Extract all object references from Kids array
183
- kids_array.scan(/(\d+)\s+(\d+)\s+R/) do |num_str, gen_str|
184
- kid_ref = [num_str.to_i, gen_str.to_i]
185
- kid_body = resolver.object_body(kid_ref)
186
- # Check if this kid is a page (not /Type/Pages)
187
- if kid_body && (kid_body.include?("/Type /Page") || kid_body =~ %r{/Type\s*/Page(?!s)\b})
188
- page_objects << kid_ref
189
- elsif kid_body && kid_body.include?("/Type /Pages")
190
- # Recursively find pages in this Pages node
191
- if kid_body =~ %r{/Kids\s*\[(.*?)\]}m
192
- kid_body[::Regexp.last_match(0)..].scan(/(\d+)\s+(\d+)\s+R/) do |n, g|
193
- grandkid_ref = [n.to_i, g.to_i]
194
- grandkid_body = resolver.object_body(grandkid_ref)
195
- if grandkid_body && (grandkid_body.include?("/Type /Page") || grandkid_body =~ %r{/Type\s*/Page(?!s)\b})
196
- page_objects << grandkid_ref
197
- end
198
- end
199
- end
200
- end
201
- end
202
- end
203
- end
204
- end
205
- end
206
-
207
- return page_objects[0] if page_objects.empty?
208
- return page_objects[page_num - 1] if page_num.positive? && page_num <= page_objects.length
209
-
210
- page_objects[0]
156
+ # Use Document's unified page-finding method
157
+ find_page_by_number(page_num)
211
158
  end
212
159
 
213
160
  def add_widget_to_page(widget_obj_num, page_num)
@@ -39,6 +39,10 @@ module AcroThat
39
39
  def acroform_ref
40
40
  @document.send(:acroform_ref)
41
41
  end
42
+
43
+ def find_page_by_number(page_num)
44
+ @document.send(:find_page_by_number, page_num)
45
+ end
42
46
  end
43
47
  end
44
48
  end
@@ -107,11 +107,7 @@ module AcroThat
107
107
  page_objects = []
108
108
  resolver.each_object do |ref, body|
109
109
  next unless body
110
-
111
- is_page = body.include?("/Type /Page") ||
112
- body.include?("/Type/Page") ||
113
- (body.include?("/Type") && body.include?("/Page") && body =~ %r{/Type\s*/Page})
114
- next unless is_page
110
+ next unless DictScan.is_page?(body)
115
111
 
116
112
  page_objects << ref
117
113
  end
@@ -316,6 +316,13 @@ module AcroThat
316
316
  body.include?("/Subtype") && body.include?("/Widget") && body =~ %r{/Subtype\s*/Widget}
317
317
  end
318
318
 
319
+ # Check if a body represents a page object (not /Type/Pages)
320
+ def is_page?(body)
321
+ return false unless body
322
+
323
+ body.include?("/Type /Page") || body =~ %r{/Type\s*/Page(?!s)\b}
324
+ end
325
+
319
326
  # Check if a field is multiline by checking /Ff flag bit 12 (0x1000)
320
327
  def is_multiline_field?(dict_body)
321
328
  return false unless dict_body
@@ -74,35 +74,7 @@ module AcroThat
74
74
  # Return an array of page information (page number, width, height, ref, metadata)
75
75
  def list_pages
76
76
  pages = []
77
- page_objects = []
78
-
79
- # Try to get pages in document order via page tree first
80
- root_ref = @resolver.root_ref
81
- if root_ref
82
- catalog_body = @resolver.object_body(root_ref)
83
- if catalog_body && catalog_body =~ %r{/Pages\s+(\d+)\s+(\d+)\s+R}
84
- pages_ref = [Integer(::Regexp.last_match(1)), Integer(::Regexp.last_match(2))]
85
-
86
- # Recursively collect pages from page tree
87
- collect_pages_from_tree(pages_ref, page_objects)
88
- end
89
- end
90
-
91
- # Fallback: collect all page objects if page tree didn't work
92
- if page_objects.empty?
93
- @resolver.each_object do |ref, body|
94
- next unless body
95
-
96
- # Match /Type /Page or /Type/Page but NOT /Type/Pages
97
- is_page = body.include?("/Type /Page") || body =~ %r{/Type\s*/Page(?!s)\b}
98
- next unless is_page
99
-
100
- page_objects << ref unless page_objects.include?(ref)
101
- end
102
-
103
- # Sort by object number as fallback
104
- page_objects.sort_by! { |ref| ref[0] }
105
- end
77
+ page_objects = find_all_pages
106
78
 
107
79
  # Second pass: extract information from each page
108
80
  page_objects.each_with_index do |ref, index|
@@ -554,8 +526,7 @@ module AcroThat
554
526
  end
555
527
 
556
528
  body = obj[:body]
557
- # Match /Type /Page or /Type/Page but NOT /Type/Pages
558
- next unless body&.include?("/Type /Page") || body =~ %r{/Type\s*/Page(?!s)\b}
529
+ next unless DictScan.is_page?(body)
559
530
 
560
531
  # Handle inline /Annots array
561
532
  if body =~ %r{/Annots\s*\[(.*?)\]}
@@ -706,7 +677,7 @@ module AcroThat
706
677
  kid_body = @resolver.object_body(kid_ref)
707
678
 
708
679
  # Check if this kid is a page (not /Type/Pages)
709
- if kid_body && (kid_body.include?("/Type /Page") || kid_body =~ %r{/Type\s*/Page(?!s)\b})
680
+ if kid_body && DictScan.is_page?(kid_body)
710
681
  page_objects << kid_ref unless page_objects.include?(kid_ref)
711
682
  elsif kid_body && kid_body.include?("/Type /Pages")
712
683
  # Recursively find pages in this Pages node
@@ -716,14 +687,52 @@ module AcroThat
716
687
  end
717
688
  end
718
689
 
719
- def find_page_number_for_ref(page_ref)
690
+ # Find all page objects in document order
691
+ # Returns an array of page references [obj_num, gen_num]
692
+ def find_all_pages
720
693
  page_objects = []
721
- @resolver.each_object do |ref, body|
722
- next unless body&.include?("/Type /Page")
723
694
 
724
- page_objects << ref
695
+ # First, try to get pages in document order via page tree
696
+ root_ref = @resolver.root_ref
697
+ if root_ref
698
+ catalog_body = @resolver.object_body(root_ref)
699
+ if catalog_body && catalog_body =~ %r{/Pages\s+(\d+)\s+(\d+)\s+R}
700
+ pages_ref = [Integer(::Regexp.last_match(1)), Integer(::Regexp.last_match(2))]
701
+ collect_pages_from_tree(pages_ref, page_objects)
702
+ end
725
703
  end
726
704
 
705
+ # Fallback: collect all page objects if page tree didn't work
706
+ if page_objects.empty?
707
+ @resolver.each_object do |ref, body|
708
+ next unless body
709
+
710
+ next unless DictScan.is_page?(body)
711
+
712
+ page_objects << ref unless page_objects.include?(ref)
713
+ end
714
+
715
+ # Sort by object number as fallback
716
+ page_objects.sort_by! { |ref| ref[0] }
717
+ end
718
+
719
+ page_objects
720
+ end
721
+
722
+ # Find a page by its page number (1-indexed)
723
+ # Returns [obj_num, gen_num] or nil if not found
724
+ def find_page_by_number(page_num)
725
+ page_objects = find_all_pages
726
+
727
+ return nil if page_objects.empty?
728
+ return page_objects[page_num - 1] if page_num.positive? && page_num <= page_objects.length
729
+
730
+ page_objects[0] # Default to first page if page_num is out of range
731
+ end
732
+
733
+ def find_page_number_for_ref(page_ref)
734
+ page_objects = find_all_pages
735
+
727
736
  return nil if page_objects.empty?
728
737
 
729
738
  page_index = page_objects.index(page_ref)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AcroThat
4
- VERSION = "0.1.2"
4
+ VERSION = "0.1.3"
5
5
  end
data/publish ADDED
@@ -0,0 +1,183 @@
1
+ #!/bin/bash
2
+
3
+ set -e # Exit on any error
4
+
5
+ VERSION_FILE="lib/acro_that/version.rb"
6
+ GEMSPEC_FILE="acro_that.gemspec"
7
+ GEM_NAME="acro_that"
8
+
9
+ # Function to show usage
10
+ usage() {
11
+ cat << EOF
12
+ Usage: $0 [OPTIONS]
13
+
14
+ Publish acro_that gem to RubyGems.
15
+
16
+ OPTIONS:
17
+ -b, --bump TYPE Bump version before publishing (major|minor|patch)
18
+ -k, --key KEY RubyGems API key name (optional, uses credentials if not provided)
19
+ -h, --help Show this help message
20
+
21
+ EXAMPLES:
22
+ $0 # Publish current version without bumping
23
+ $0 -b patch # Bump patch version (0.1.2 -> 0.1.3)
24
+ $0 -b minor # Bump minor version (0.1.2 -> 0.2.0)
25
+ $0 -b major # Bump major version (0.1.2 -> 1.0.0)
26
+ $0 -b patch -k mykey # Bump patch and use specific API key
27
+
28
+ EOF
29
+ exit 1
30
+ }
31
+
32
+ # Function to get current version
33
+ get_current_version() {
34
+ if [[ ! -f "$VERSION_FILE" ]]; then
35
+ echo "Error: $VERSION_FILE not found!" >&2
36
+ exit 1
37
+ fi
38
+
39
+ # Extract version using awk (works reliably on both macOS and Linux)
40
+ awk -F'"' '/VERSION =/ {print $2}' "$VERSION_FILE"
41
+ }
42
+
43
+ # Function to bump version
44
+ bump_version() {
45
+ local bump_type=$1
46
+ local current_version=$(get_current_version)
47
+
48
+ IFS='.' read -ra VERSION_PARTS <<< "$current_version"
49
+ local major=${VERSION_PARTS[0]:-0}
50
+ local minor=${VERSION_PARTS[1]:-0}
51
+ local patch=${VERSION_PARTS[2]:-0}
52
+
53
+ case "$bump_type" in
54
+ major)
55
+ major=$((major + 1))
56
+ minor=0
57
+ patch=0
58
+ ;;
59
+ minor)
60
+ minor=$((minor + 1))
61
+ patch=0
62
+ ;;
63
+ patch)
64
+ patch=$((patch + 1))
65
+ ;;
66
+ *)
67
+ echo "Error: Invalid bump type: $bump_type" >&2
68
+ echo "Must be one of: major, minor, patch" >&2
69
+ exit 1
70
+ ;;
71
+ esac
72
+
73
+ local new_version="${major}.${minor}.${patch}"
74
+
75
+ # Update version in version.rb
76
+ if [[ "$OSTYPE" == "darwin"* ]]; then
77
+ # macOS uses BSD sed
78
+ sed -i '' "s/VERSION = \".*\"/VERSION = \"$new_version\"/" "$VERSION_FILE"
79
+ else
80
+ # Linux uses GNU sed
81
+ sed -i "s/VERSION = \".*\"/VERSION = \"$new_version\"/" "$VERSION_FILE"
82
+ fi
83
+
84
+ echo "$new_version"
85
+ }
86
+
87
+ # Parse arguments
88
+ BUMP_TYPE=""
89
+ API_KEY=""
90
+
91
+ while [[ $# -gt 0 ]]; do
92
+ case $1 in
93
+ -b|--bump)
94
+ BUMP_TYPE="$2"
95
+ shift 2
96
+ ;;
97
+ -k|--key)
98
+ API_KEY="$2"
99
+ shift 2
100
+ ;;
101
+ -h|--help)
102
+ usage
103
+ ;;
104
+ *)
105
+ echo "Error: Unknown option: $1" >&2
106
+ usage
107
+ ;;
108
+ esac
109
+ done
110
+
111
+ # Validate bump type if provided
112
+ if [[ -n "$BUMP_TYPE" ]]; then
113
+ if [[ ! "$BUMP_TYPE" =~ ^(major|minor|patch)$ ]]; then
114
+ echo "Error: Invalid bump type: $BUMP_TYPE" >&2
115
+ echo "Must be one of: major, minor, patch" >&2
116
+ exit 1
117
+ fi
118
+ fi
119
+
120
+ # Get version (after potential bump)
121
+ if [[ -n "$BUMP_TYPE" ]]; then
122
+ echo "Bumping $BUMP_TYPE version..."
123
+ VERSION=$(bump_version "$BUMP_TYPE")
124
+ echo "New version: $VERSION"
125
+ else
126
+ VERSION=$(get_current_version)
127
+ echo "Using current version: $VERSION"
128
+ fi
129
+
130
+ # Build the gem
131
+ echo "Building gem..."
132
+ GEM_FILE="${GEM_NAME}-${VERSION}.gem"
133
+ gem build "$GEMSPEC_FILE"
134
+
135
+ if [[ ! -f "$GEM_FILE" ]]; then
136
+ echo "Error: Failed to build gem file: $GEM_FILE" >&2
137
+ exit 1
138
+ fi
139
+
140
+ echo "Gem built successfully: $GEM_FILE"
141
+
142
+ # Push to RubyGems
143
+ echo "Pushing to RubyGems..."
144
+ if [[ -n "$API_KEY" ]]; then
145
+ gem push "$GEM_FILE" --key "$API_KEY"
146
+ else
147
+ gem push "$GEM_FILE"
148
+ fi
149
+
150
+ if [[ $? -ne 0 ]]; then
151
+ echo "Error: Failed to push gem to RubyGems" >&2
152
+ exit 1
153
+ fi
154
+
155
+ echo "Gem pushed to RubyGems successfully"
156
+
157
+ # Commit and push to git (only if version was bumped or if there are changes)
158
+ if [[ -n "$BUMP_TYPE" ]] || ! git diff --quiet "$VERSION_FILE"; then
159
+ echo "Committing version change..."
160
+ git add "$VERSION_FILE"
161
+ git commit -m "v${VERSION}"
162
+
163
+ echo "Creating and pushing tag..."
164
+ git tag -a "v${VERSION}" -m "Release version ${VERSION}"
165
+
166
+ echo "Pushing to origin..."
167
+ git push origin main
168
+ git push origin "v${VERSION}"
169
+
170
+ echo "Git operations completed successfully"
171
+ else
172
+ echo "No version changes to commit"
173
+ fi
174
+
175
+ echo ""
176
+ echo "✅ Successfully published ${GEM_NAME} v${VERSION}!"
177
+ echo " - Gem built: ${GEM_FILE}"
178
+ echo " - Pushed to RubyGems"
179
+ if [[ -n "$BUMP_TYPE" ]]; then
180
+ echo " - Version bumped and committed"
181
+ echo " - Tagged as v${VERSION}"
182
+ fi
183
+
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: 0.1.2
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Wynkoop
@@ -118,6 +118,7 @@ files:
118
118
  - lib/acro_that/objstm.rb
119
119
  - lib/acro_that/pdf_writer.rb
120
120
  - lib/acro_that/version.rb
121
+ - publish
121
122
  homepage: https://github.com/wynk182/acro_that
122
123
  licenses:
123
124
  - MIT