pdfsink 0.1.2 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0406a9057adb9c3470182b98d6600e8bf239227e792e4dd1f9560542ab5056bb
4
- data.tar.gz: e048fcdd3eaf050bcd91cd289930822ff17a28c6603b45e22c5a857982374a6f
3
+ metadata.gz: 6097674e65f4c20f1f715625ff7e45ba1de03af6c5a42e019fc52b4c7b471897
4
+ data.tar.gz: 6eaa5deed917aa12a71f1dce9918b814da3c2078eb2bcea530a82e8d62e21400
5
5
  SHA512:
6
- metadata.gz: '098ac8d04c6585bd7355beb2a5aec26d6d4e625e49248baa00907161ce1ddb870be0012ede8e67bf327afb2dcf3a03dacab1ee32076dea1ecb737176d1c32f28'
7
- data.tar.gz: 9679286de91e56311407459e52f3214171b7977ec06ba377c943f5b901b1f1973bc7ac7bd7d95cd3ae34c6694cad227f326be5f76f592de8346294ca54bd5571
6
+ metadata.gz: fbaf27f8a1e2fd4edb52c244b6e8a98f66eb35e71d1652bbaea5ff420f29fd32b7280defdb6290e40a9fec418882a8600ef152bcde7995055ebc071463f05c4e
7
+ data.tar.gz: 36566d61641837c8a85bfd23f468f184ab3bcd6e7e9e5e1a6ee5f0ebc30453b607423d8c4baf283b5057e024cc39c1753e101284c796f85ce367357c24f8652d
data/README.md CHANGED
@@ -106,10 +106,30 @@ page.search(/total:\s*\$\d+/i)
106
106
  # => [{"text" => "Total: $420", "x0" => ..., ...}]
107
107
  ```
108
108
 
109
+ ### Read every page in one run
110
+
111
+ Each page method runs the pdfsink-rs binary for that page only. To read
112
+ every page, `load_all` runs it once for the whole document:
113
+
114
+ ```ruby
115
+ doc = Pdfsink.open("report.pdf")
116
+ doc.load_all # one run; returns doc.pages
117
+ doc.pages.each do |page|
118
+ page.extract_text # no further runs
119
+ page.tables # no further runs (same strategy as load_all)
120
+ end
121
+
122
+ doc.load_all(strategy: :text) # load tables found with another strategy
123
+ ```
124
+
125
+ Use the page methods when you need a few pages, and `load_all` when you
126
+ need all of them. `load_all` loads text and tables only; `extract_words`,
127
+ `objects`, `links` and `search` still run the binary for their page.
128
+
109
129
  ### Binary version
110
130
 
111
131
  ```ruby
112
- Pdfsink.version # => "0.2.9"
132
+ Pdfsink.version # => "0.2.20+aim.1"
113
133
  ```
114
134
 
115
135
  ## Rails Integration
data/lib/pdfsink/cli.rb CHANGED
@@ -30,7 +30,7 @@ module Pdfsink
30
30
  # Override the resolved binary path (mainly for tests).
31
31
  attr_writer :binary
32
32
 
33
- # The pdfsink-rs version string, e.g. "pdfsink-rs 0.2.9".
33
+ # The pdfsink-rs version string, e.g. "0.2.20+aim.1".
34
34
  #
35
35
  # The CLI has no version subcommand, so this reports the crate version
36
36
  # the gem was built against.
@@ -51,11 +51,14 @@ module Pdfsink
51
51
 
52
52
  # Extracted text for a single page.
53
53
  #
54
+ # The binary always writes UTF-8. Open3 tags its output with the process
55
+ # locale, which is wrong under a C locale, so the tag is set here.
56
+ #
54
57
  # @param path [String]
55
58
  # @param page [Integer] 1-based page number
56
- # @return [String]
59
+ # @return [String] UTF-8
57
60
  def text(path, page)
58
- run("text", path, page.to_s)
61
+ run("text", path, page.to_s).force_encoding(Encoding::UTF_8)
59
62
  end
60
63
 
61
64
  # Words with positions for a single page.
@@ -94,6 +97,19 @@ module Pdfsink
94
97
  run_json("table", path, page.to_s, strategy)
95
98
  end
96
99
 
100
+ # Document metadata plus the text and largest table of every page, in
101
+ # one run. Same payload as {info}, with "text" and "table" added to
102
+ # each entry in "pages". "text" has no trailing newline, unlike {text}.
103
+ #
104
+ # @param path [String]
105
+ # @param strategy [String] one of "lines", "lines_strict", "text", "explicit"
106
+ # @return [Hash]
107
+ # @raise [ParseError] if the binary prints nothing (too old to have +pages+)
108
+ def pages(path, strategy)
109
+ run_json("pages", path, strategy) or
110
+ raise ParseError, "pdfsink-rs pages printed nothing; the binary may be older than #{PDFSINK_RS_VERSION}"
111
+ end
112
+
97
113
  private
98
114
 
99
115
  def find_binary
@@ -5,7 +5,8 @@ module Pdfsink
5
5
  #
6
6
  # Opening is cheap: the path is validated and the document's +info+ payload
7
7
  # (page count and per-page metadata) is fetched lazily on first access.
8
- # {Page} objects are created on demand and memoized.
8
+ # {Page} objects are created on demand and memoized. {#load_all} reads
9
+ # every page's text and table in one run.
9
10
  #
10
11
  # @example
11
12
  # doc = Pdfsink::Document.open("report.pdf")
@@ -77,6 +78,30 @@ module Pdfsink
77
78
  pages.each(&block)
78
79
  end
79
80
 
81
+ # Read the text and largest table of every page in one run of the
82
+ # binary. Afterwards {Page#extract_text}, and {Page#tables} with the same
83
+ # strategy, return without running it again. Use this when you need
84
+ # every page; for a few pages, the per-page methods are faster.
85
+ #
86
+ # Each call parses the whole document again, so a second strategy needs a
87
+ # second call. {Page#extract_words}, {Page#objects}, {Page#links} and
88
+ # {Page#search} are not preloaded.
89
+ #
90
+ # @param strategy [Symbol, String, nil] table-detection strategy
91
+ # @return [Array<Page>]
92
+ def load_all(strategy: nil)
93
+ strategy = TableStrategy.resolve(strategy)
94
+ payload = Cli.pages(path, strategy)
95
+ entries = payload["pages"]
96
+
97
+ @info = payload.merge("pages" => entries.map { |entry| entry.except("text", "table") })
98
+ entries.each.with_index(1) do |entry, number|
99
+ # The text command prints a trailing newline; pages does not.
100
+ page(number).preload(text: "#{entry['text']}\n", strategy: strategy, table: entry["table"])
101
+ end
102
+ pages
103
+ end
104
+
80
105
  def inspect
81
106
  "#<Pdfsink::Document path=#{path.inspect} pages=#{page_count}>"
82
107
  end
data/lib/pdfsink/page.rb CHANGED
@@ -3,10 +3,11 @@
3
3
  module Pdfsink
4
4
  # A single page of a {Document}.
5
5
  #
6
- # Each accessor shells out to the pdfsink-rs binary for that page; results
7
- # are cached so repeated reads don't re-spawn the process. Page-level
8
- # metadata (dimensions, rotation, bbox, object counts) comes from the
9
- # document's +info+ payload and needs no extra spawn.
6
+ # Each accessor runs the pdfsink-rs binary for this page only; results are
7
+ # cached so repeated reads don't run it again. {Document#load_all} fills
8
+ # the text and tables of every page in one run. Page-level metadata
9
+ # (dimensions, rotation, bbox, object counts) comes from the document's
10
+ # +info+ payload and needs no extra run.
10
11
  #
11
12
  # @example
12
13
  # doc = Pdfsink::Document.open("report.pdf")
@@ -25,6 +26,7 @@ module Pdfsink
25
26
  @document = document
26
27
  @number = number
27
28
  @meta = meta
29
+ @tables = {}
28
30
  end
29
31
 
30
32
  # @return [Float] page width in PDF points
@@ -83,7 +85,22 @@ module Pdfsink
83
85
  # @param strategy [Symbol, String, nil] table-detection strategy
84
86
  # @return [Array<Array>, nil] rows of cells
85
87
  def tables(strategy: nil)
86
- Cli.table(path, number, TableStrategy.resolve(strategy))
88
+ strategy = TableStrategy.resolve(strategy)
89
+ return @tables[strategy] if @tables.key?(strategy)
90
+
91
+ @tables[strategy] = Cli.table(path, number, strategy)
92
+ end
93
+
94
+ # Store text and a table read by {Document#load_all}.
95
+ #
96
+ # @api private
97
+ # @param text [String]
98
+ # @param strategy [String] a resolved table strategy
99
+ # @param table [Array<Array>, nil]
100
+ # @return [void]
101
+ def preload(text:, strategy:, table:)
102
+ @extract_text = text
103
+ @tables[strategy] = table
87
104
  end
88
105
 
89
106
  def inspect
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pdfsink
4
- VERSION = "0.1.2"
4
+ VERSION = "0.2.0"
5
5
 
6
6
  # Version of the pdfsink-rs crate this gem builds and wraps. Built from the
7
7
  # matching git tag (v#{PDFSINK_RS_VERSION}), not crates.io -- the crate carries
8
8
  # a [patch.crates-io] fix that cargo strips on publish.
9
- PDFSINK_RS_VERSION = "0.2.12"
9
+ PDFSINK_RS_VERSION = "0.2.20+aim.1"
10
10
  end
data/lib/pdfsink.rb CHANGED
@@ -73,7 +73,7 @@ module Pdfsink
73
73
 
74
74
  # The version of the underlying pdfsink-rs binary the gem was built with.
75
75
  #
76
- # @return [String] e.g. "0.2.9"
76
+ # @return [String] e.g. "0.2.20+aim.1"
77
77
  def version
78
78
  Cli.version
79
79
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pdfsink
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Accountaim
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-07-17 00:00:00.000000000 Z
11
+ date: 2026-10-24 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |
14
14
  A Ruby gem that wraps the pdfsink-rs CLI, a fast pure-Rust PDF extraction