acro_that 0.1.2 → 0.1.4

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: 40016774a590828e59c4c9ea4331a8d170ccd536d7522cbdb193fefda3f28333
4
+ data.tar.gz: bade6f113359d96d1f7c1a85efd7033732350c0b19bf40dd412e104da359aa5a
5
5
  SHA512:
6
- metadata.gz: a545eea311d77e7b46459e710925f8ec7b61baebd846ec7f602f6a77ab9532bab87d37be4bba53b00b43a09281e79faba79fcb71e5371c36953e877d556dcdd8
7
- data.tar.gz: b9074d19a0cc330af44f4fd5609f49f8892e20deb95d522fa7ca5fb3099ca8c71f01cbc98ef7d3e6ed95620af090afc96035fbeaaf4a551ca6f9e4003c49f4a8
6
+ metadata.gz: 60e6c6afd93cfd8911c0b0a0d4b6b080cb2c8af85a104cf3fd09f8fc7f9ac642999a8d5694c374ad64b71d4aa769f6d8b390cc82829317abd64598aa42e7280f
7
+ data.tar.gz: 31762cf9f6f285edd78976692e9c5bab7a67cf6298a03c8d4208b02927639be29e0d1f4ca1f14c3f1c170b9f617404e0931dc8bbedd17de79fcee5c80035dd29
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
data/README.md CHANGED
@@ -234,6 +234,32 @@ fields.each do |field|
234
234
  end
235
235
  ```
236
236
 
237
+ #### `#list_pages`
238
+ Returns an array of `Page` objects representing all pages in the document. Each `Page` object provides page information and methods to add fields to that specific page.
239
+
240
+ ```ruby
241
+ pages = doc.list_pages
242
+ pages.each do |page|
243
+ puts "Page #{page.page_number}: #{page.width}x#{page.height}"
244
+ end
245
+
246
+ # Add fields to specific pages - the page is automatically set!
247
+ first_page = pages[0]
248
+ first_page.add_field("Name", x: 100, y: 700, width: 200, height: 20)
249
+
250
+ second_page = pages[1]
251
+ second_page.add_field("Email", x: 100, y: 650, width: 200, height: 20)
252
+ ```
253
+
254
+ **Page Object Methods:**
255
+ - `page.page_number` - Returns the page number (1-indexed)
256
+ - `page.width` - Page width in points
257
+ - `page.height` - Page height in points
258
+ - `page.ref` - Page object reference `[obj_num, gen_num]`
259
+ - `page.metadata` - Hash containing page metadata (rotation, boxes, etc.)
260
+ - `page.add_field(name, options)` - Add a field to this page (page number is automatically set)
261
+ - `page.to_h` - Convert to hash for backward compatibility
262
+
237
263
  #### `#add_field(name, options)`
238
264
  Adds a new form field to the document. Options include:
239
265
  - `value`: Default value for the field (String)
@@ -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|
@@ -207,13 +179,14 @@ module AcroThat
207
179
  contents_refs: contents_refs
208
180
  }
209
181
 
210
- pages << {
211
- page: index + 1, # Page number starting at 1
212
- width: width,
213
- height: height,
214
- ref: ref,
215
- metadata: metadata
216
- }
182
+ pages << Page.new(
183
+ index + 1, # Page number starting at 1
184
+ width,
185
+ height,
186
+ ref,
187
+ metadata,
188
+ self # Pass document reference
189
+ )
217
190
  end
218
191
 
219
192
  pages
@@ -554,8 +527,7 @@ module AcroThat
554
527
  end
555
528
 
556
529
  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}
530
+ next unless DictScan.is_page?(body)
559
531
 
560
532
  # Handle inline /Annots array
561
533
  if body =~ %r{/Annots\s*\[(.*?)\]}
@@ -706,7 +678,7 @@ module AcroThat
706
678
  kid_body = @resolver.object_body(kid_ref)
707
679
 
708
680
  # 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})
681
+ if kid_body && DictScan.is_page?(kid_body)
710
682
  page_objects << kid_ref unless page_objects.include?(kid_ref)
711
683
  elsif kid_body && kid_body.include?("/Type /Pages")
712
684
  # Recursively find pages in this Pages node
@@ -716,14 +688,52 @@ module AcroThat
716
688
  end
717
689
  end
718
690
 
719
- def find_page_number_for_ref(page_ref)
691
+ # Find all page objects in document order
692
+ # Returns an array of page references [obj_num, gen_num]
693
+ def find_all_pages
720
694
  page_objects = []
721
- @resolver.each_object do |ref, body|
722
- next unless body&.include?("/Type /Page")
723
695
 
724
- page_objects << ref
696
+ # First, try to get pages in document order via page tree
697
+ root_ref = @resolver.root_ref
698
+ if root_ref
699
+ catalog_body = @resolver.object_body(root_ref)
700
+ if catalog_body && catalog_body =~ %r{/Pages\s+(\d+)\s+(\d+)\s+R}
701
+ pages_ref = [Integer(::Regexp.last_match(1)), Integer(::Regexp.last_match(2))]
702
+ collect_pages_from_tree(pages_ref, page_objects)
703
+ end
704
+ end
705
+
706
+ # Fallback: collect all page objects if page tree didn't work
707
+ if page_objects.empty?
708
+ @resolver.each_object do |ref, body|
709
+ next unless body
710
+
711
+ next unless DictScan.is_page?(body)
712
+
713
+ page_objects << ref unless page_objects.include?(ref)
714
+ end
715
+
716
+ # Sort by object number as fallback
717
+ page_objects.sort_by! { |ref| ref[0] }
725
718
  end
726
719
 
720
+ page_objects
721
+ end
722
+
723
+ # Find a page by its page number (1-indexed)
724
+ # Returns [obj_num, gen_num] or nil if not found
725
+ def find_page_by_number(page_num)
726
+ page_objects = find_all_pages
727
+
728
+ return nil if page_objects.empty?
729
+ return page_objects[page_num - 1] if page_num.positive? && page_num <= page_objects.length
730
+
731
+ page_objects[0] # Default to first page if page_num is out of range
732
+ end
733
+
734
+ def find_page_number_for_ref(page_ref)
735
+ page_objects = find_all_pages
736
+
727
737
  return nil if page_objects.empty?
728
738
 
729
739
  page_index = page_objects.index(page_ref)
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AcroThat
4
+ # Represents a page in a PDF document
5
+ class Page
6
+ attr_reader :page, :width, :height, :ref, :metadata, :document
7
+
8
+ def initialize(page, width, height, ref, metadata, document)
9
+ @page = page # Page number (1-indexed)
10
+ @width = width
11
+ @height = height
12
+ @ref = ref # [obj_num, gen_num]
13
+ @metadata = metadata # Hash with :rotate, :media_box, :crop_box, etc.
14
+ @document = document
15
+ end
16
+
17
+ # Add a field to this page
18
+ # Options are the same as Document#add_field, but :page is automatically set
19
+ def add_field(name, options = {})
20
+ # Automatically set the page number to this page
21
+ options_with_page = options.merge(page: @page)
22
+ @document.add_field(name, options_with_page)
23
+ end
24
+
25
+ # Get the page number
26
+ def page_number
27
+ @page
28
+ end
29
+
30
+ # Get the page reference [obj_num, gen_num]
31
+ def page_ref
32
+ @ref
33
+ end
34
+
35
+ # Check if page has rotation
36
+ def rotated?
37
+ !@metadata[:rotate].nil? && @metadata[:rotate] != 0
38
+ end
39
+
40
+ # Get rotation angle (0, 90, 180, 270)
41
+ def rotation
42
+ @metadata[:rotate] || 0
43
+ end
44
+
45
+ # Get MediaBox dimensions
46
+ def media_box
47
+ @metadata[:media_box]
48
+ end
49
+
50
+ # Get CropBox dimensions
51
+ def crop_box
52
+ @metadata[:crop_box]
53
+ end
54
+
55
+ # Get ArtBox dimensions
56
+ def art_box
57
+ @metadata[:art_box]
58
+ end
59
+
60
+ # Get BleedBox dimensions
61
+ def bleed_box
62
+ @metadata[:bleed_box]
63
+ end
64
+
65
+ # Get TrimBox dimensions
66
+ def trim_box
67
+ @metadata[:trim_box]
68
+ end
69
+
70
+ # String representation for debugging
71
+ def to_s
72
+ dims = width && height ? " #{width}x#{height}" : ""
73
+ rot = rotated? ? " (rotated #{rotation}°)" : ""
74
+ "#<AcroThat::Page page=#{page}#{dims}#{rot} ref=#{ref.inspect}>"
75
+ end
76
+
77
+ alias inspect to_s
78
+
79
+ # Convert to hash for backward compatibility
80
+ def to_h
81
+ {
82
+ page: @page,
83
+ width: @width,
84
+ height: @height,
85
+ ref: @ref,
86
+ metadata: @metadata
87
+ }
88
+ end
89
+ end
90
+ end
91
+
@@ -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.4"
5
5
  end
data/lib/acro_that.rb CHANGED
@@ -12,6 +12,7 @@ require_relative "acro_that/objstm"
12
12
  require_relative "acro_that/pdf_writer"
13
13
  require_relative "acro_that/incremental_writer"
14
14
  require_relative "acro_that/field"
15
+ require_relative "acro_that/page"
15
16
  require_relative "acro_that/document"
16
17
 
17
18
  # Load actions
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.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Wynkoop
@@ -116,8 +116,10 @@ files:
116
116
  - lib/acro_that/incremental_writer.rb
117
117
  - lib/acro_that/object_resolver.rb
118
118
  - lib/acro_that/objstm.rb
119
+ - lib/acro_that/page.rb
119
120
  - lib/acro_that/pdf_writer.rb
120
121
  - lib/acro_that/version.rb
122
+ - publish
121
123
  homepage: https://github.com/wynk182/acro_that
122
124
  licenses:
123
125
  - MIT