pdfrb 0.7.42 → 0.7.44

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: ab4ad0a762217c8fdfc88589f78152f7ec382f5192cec734457cdba1c538139a
4
- data.tar.gz: 1850f2ae9b0fa99006c5fa0fbdc4d290929c72aa5b5f990e30275eab8c19a66b
3
+ metadata.gz: 836c9a2bfe781c6816a25f2db87e99c3b30265fcfb80f116f1b54ccfe9ce149d
4
+ data.tar.gz: 2a7b2a9f1d607b1dd29fd6aca4a2b9a977b315e98834026974ae73d384a62718
5
5
  SHA512:
6
- metadata.gz: 37b7a245a79cbf85cbaf8e82b353da55c01b688453589ce7b9bcd4e58a6a8acd988702e5a2af564a0a1516e5f554af32b36c8ca2b7db98a8ef03572b26b49f80
7
- data.tar.gz: d3430314391486f081de9a2723857f0c266dff32c3e88487f6e409d6d74a16fdc348003d736b7af202de2761c3608dc48808832f08ed2c45c0b8ff245dd49493
6
+ metadata.gz: dc2be0c8bcf1f7a6a77f3db649dba782cc789e532a17afb2fd6152b5fa8109e2c0d6410a01f8df0c820e53fa3439035ff3db24fde8e889a32b6bc880a93cda25
7
+ data.tar.gz: ad3a5ae6eb6dc87577477bba0a7b4ccb6e75e5c914dee698ee4bc661f2df96b363172ef6aded34815b5965ec3f01edf0e6c710b70ba4a4ab95510acc6a0a2b2d
data/docs/USAGE.md CHANGED
@@ -137,9 +137,18 @@ File.binwrite("linearized.pdf", io.string)
137
137
  ## Encryption
138
138
 
139
139
  ```ruby
140
- doc = Pdfrb.open("input.pdf")
140
+ doc = Pdfrb::Document.new
141
+ doc.pages.add
142
+ doc.encrypt!(user_password: "secret", owner_password: "owner", bits: 128)
141
143
  doc.write("encrypted.pdf")
142
- # Encryption configuration via document.config
144
+
145
+ # Opening an encrypted document:
146
+ doc = Pdfrb.open("encrypted.pdf",
147
+ config: { "encryption.password" => "secret" })
148
+
149
+ # Removing encryption from a parsed document:
150
+ doc.decrypt!
151
+ doc.write("decrypted.pdf")
143
152
  ```
144
153
 
145
154
  ## Merging PDFs
@@ -64,7 +64,11 @@ module Pdfrb
64
64
  self
65
65
  end
66
66
 
67
- def line(x1, y1, x2, y2)
67
+ def line(x1 = nil, y1 = nil, x2 = nil, y2 = nil, from: nil, to: nil)
68
+ if from && to
69
+ x1, y1 = from
70
+ x2, y2 = to
71
+ end
68
72
  move_to(x1, y1).line_to(x2, y2)
69
73
  end
70
74
 
@@ -189,8 +193,14 @@ module Pdfrb
189
193
  self
190
194
  end
191
195
 
192
- def rectangle(x, y, width, height)
193
- emit_op(Pdfrb::Content::Operator::Rectangle, x, y, width, height)
196
+ def rectangle(x = nil, y = nil, w = nil, h = nil,
197
+ point: nil, width: nil, height: nil)
198
+ x, y = point if point
199
+ w = width if w.nil?
200
+ h = height if h.nil?
201
+ raise ArgumentError, "rectangle needs x, y, width and height" if [x, y, w, h].any?(&:nil?)
202
+
203
+ emit_op(Pdfrb::Content::Operator::Rectangle, x, y, w, h)
194
204
  self
195
205
  end
196
206
 
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ module Content
5
+ # A BI ... ID ... EI inline image (s8.9.7). Carries the expanded
6
+ # header keys (abbreviations resolved by the parser), the raw
7
+ # encoded byte payload, and decodes on demand via the filter
8
+ # pipeline.
9
+ class InlineImage
10
+ # Filter-name abbreviations permitted in inline images
11
+ # (s8.9.7 Table 89).
12
+ FILTER_ABBREVIATIONS = {
13
+ AHx: :ASCIIHexDecode,
14
+ A85: :ASCII85Decode,
15
+ Fl: :FlateDecode,
16
+ LZW: :LZWDecode,
17
+ CCF: :CCITTFaxDecode,
18
+ DCT: :DCTDecode,
19
+ RL: :RunLengthDecode,
20
+ }.freeze
21
+
22
+ # Color-space-name abbreviations (s8.9.7 Table 89).
23
+ COLOR_SPACE_ABBREVIATIONS = {
24
+ G: :DeviceGray,
25
+ RGB: :DeviceRGB,
26
+ CMYK: :DeviceCMYK,
27
+ I: :Indexed,
28
+ }.freeze
29
+
30
+ attr_reader :header, :data
31
+
32
+ def initialize(header: {}, data: "")
33
+ @header = header.dup.freeze
34
+ @data = (data || "".b).dup.freeze
35
+ end
36
+
37
+ def width; header[:Width]; end
38
+ def height; header[:Height]; end
39
+ def bits_per_component; header[:BitsPerComponent]; end
40
+
41
+ def image_mask?
42
+ [true, 1].include?(header[:ImageMask])
43
+ end
44
+
45
+ def decode; header[:Decode]; end
46
+ def interpolate?; header[:Interpolate] == true; end
47
+
48
+ # /ColorSpace with the inline abbreviation expanded
49
+ # (/G -> /DeviceGray etc.).
50
+ def color_space
51
+ cs = header[:ColorSpace]
52
+ return nil unless cs.is_a?(::Symbol)
53
+
54
+ COLOR_SPACE_ABBREVIATIONS.fetch(cs, cs)
55
+ end
56
+
57
+ # Decoded filter chain as an array of Symbols; nil when the
58
+ # image data is stored raw.
59
+ def filters
60
+ f = header[:Filter]
61
+ return [] if f.nil?
62
+
63
+ arr = f.is_a?(::Array) ? f : [f]
64
+ arr.map { |name| FILTER_ABBREVIATIONS.fetch(name.to_sym, name.to_sym) }
65
+ end
66
+
67
+ # Apply the filter chain to the raw payload. Returns the raw
68
+ # data when no filter is declared.
69
+ def decoded_data
70
+ list = filters
71
+ return @data if list.empty?
72
+
73
+ Pdfrb::Filter.apply(
74
+ @data, filters: list, parms: Array(header[:DecodeParms]),
75
+ direction: :decode
76
+ )
77
+ end
78
+
79
+ # Number of colour components implied by /ColorSpace.
80
+ def components
81
+ case color_space
82
+ when :DeviceGray, :Indexed then 1
83
+ when :DeviceRGB then 3
84
+ when :DeviceCMYK then 4
85
+ end
86
+ end
87
+
88
+ # Expected decoded byte count: rows * width * components *
89
+ # bits, packed per row.
90
+ def expected_decoded_size
91
+ return nil if width.nil? || height.nil? || bits_per_component.nil?
92
+ return nil if image_mask?
93
+
94
+ comps = components
95
+ return nil if comps.nil?
96
+
97
+ bits = width * comps * bits_per_component
98
+ bytes_per_row = (bits + 7) / 8
99
+ bytes_per_row * height
100
+ end
101
+ end
102
+ end
103
+ end
@@ -12,6 +12,12 @@ module Pdfrb
12
12
  module Operator
13
13
  @registry = {}
14
14
 
15
+ # Referenced directly by the Parser for BI ... ID ... EI
16
+ # sequences; the remaining operator classes load via
17
+ # eager_load! / Operator[].
18
+ autoload :BeginInlineImage, "pdfrb/content/operators/inline_image"
19
+ autoload :EndInlineImage, "pdfrb/content/operators/inline_image"
20
+
15
21
  class << self
16
22
  attr_reader :registry
17
23
 
@@ -29,8 +29,8 @@ module Pdfrb
29
29
  def invoke(processor, image = nil)
30
30
  processor.inline_image(image) if image
31
31
  end
32
- register
33
32
  end
33
+ register
34
34
  end
35
35
 
36
36
  class EndInlineImage < Base
@@ -38,8 +38,8 @@ module Pdfrb
38
38
  def name; "EI"; end
39
39
 
40
40
  def invoke(_processor, *_operands); end
41
- register
42
41
  end
42
+ register
43
43
  end
44
44
  end
45
45
  end
@@ -16,7 +16,6 @@ module Pdfrb
16
16
  autoload :GraphicsStateParams, "pdfrb/content/operators/graphics_state_params"
17
17
  autoload :MarkedContent, "pdfrb/content/operators/marked_content"
18
18
  autoload :Clipping, "pdfrb/content/operators/clipping"
19
- autoload :InlineImage, "pdfrb/content/operators/inline_image"
20
19
  end
21
20
  end
22
21
  end
@@ -82,7 +82,7 @@ module Pdfrb
82
82
  # keyword from the data. Read raw bytes until "\nEI" or
83
83
  # " EI" terminator.
84
84
  data = read_inline_image_data
85
- { header: header, data: data }
85
+ Pdfrb::Content::InlineImage.new(header: header, data: data)
86
86
  end
87
87
 
88
88
  # Map inline-image abbreviation keys to full PDF names per
@@ -78,9 +78,9 @@ module Pdfrb
78
78
  def marked_content_point(_tag, _properties = nil); end
79
79
 
80
80
  # Called when a BI/ID/EI inline image is encountered in the
81
- # content stream. +image+ is a Hash with :header (the image
82
- # dict keys, expanded from abbreviations) and :data (the raw
83
- # byte payload). Subclasses override to render or extract.
81
+ # content stream. +image+ is a Content::InlineImage with the
82
+ # expanded header keys, the raw payload, and decoded_data /
83
+ # geometry accessors. Subclasses override to render or extract.
84
84
  def inline_image(_image); end
85
85
  end
86
86
  end
data/lib/pdfrb/content.rb CHANGED
@@ -5,6 +5,7 @@ module Pdfrb
5
5
  autoload :GraphicsState, "pdfrb/content/graphics_state"
6
6
  autoload :Operator, "pdfrb/content/operator"
7
7
  autoload :Parser, "pdfrb/content/parser"
8
+ autoload :InlineImage, "pdfrb/content/inline_image"
8
9
  autoload :Processor, "pdfrb/content/processor"
9
10
  autoload :ColorSpace, "pdfrb/content/color_space"
10
11
  autoload :TransformationMatrix, "pdfrb/content/transformation_matrix"
@@ -166,6 +166,14 @@ module Pdfrb
166
166
 
167
167
  def encryption; @encryption ||= Document::Encryption.new(self); end
168
168
 
169
+ def encrypt!(**)
170
+ encryption.encrypt!(**)
171
+ end
172
+
173
+ def decrypt!
174
+ encryption.decrypt!
175
+ end
176
+
169
177
  def info; @info ||= Document::Info.new(self); end
170
178
 
171
179
  def display; @display ||= Document::Display.new(self); end
@@ -306,6 +314,19 @@ module Pdfrb
306
314
 
307
315
  @xref, @trailer_dict = load_xref_and_trailer(io, sxref)
308
316
  @object_reader = Pdfrb::Source::ObjectReader.new(self, @xref) if @xref
317
+ seed_next_oid
318
+ end
319
+
320
+ # New/modified objects must not collide with objects loaded from
321
+ # the xref: continue allocating past the highest known oid (or
322
+ # the trailer /Size, whichever is larger).
323
+ def seed_next_oid
324
+ return if @xref.nil?
325
+
326
+ max_oid = @xref.entries.keys.max || 0
327
+ size = @trailer_dict && @trailer_dict[:Size]
328
+ size = size.to_i if size.respond_to?(:to_i)
329
+ @next_oid = [max_oid + 1, size.to_i, @next_oid].max
309
330
  end
310
331
 
311
332
  def load_xref_and_trailer(io, sxref)
@@ -31,6 +31,18 @@ module Pdfrb
31
31
  block_given? ? document : results
32
32
  end
33
33
 
34
+ # Extract the text of a single page (see #call for the
35
+ # resolution strategy).
36
+ def call_single_page(page)
37
+ extractor = TextCollector.new(page.document, page)
38
+ begin
39
+ extractor.process(page.decoded_content)
40
+ rescue StandardError
41
+ # Malformed content stream — best effort, skip.
42
+ end
43
+ extractor.text
44
+ end
45
+
34
46
  # Internal: collects bytes from +Tj+ / +TJ+ / +'+ / +"'.
35
47
  # Tracks text-matrix y to insert line breaks at vertical moves.
36
48
  # Resolves glyph codes to Unicode via the active font's
data/lib/pdfrb/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pdfrb
4
- VERSION = "0.7.42"
4
+ VERSION = "0.7.44"
5
5
  end
data/lib/pdfrb.rb CHANGED
@@ -58,6 +58,20 @@ module Pdfrb
58
58
  autoload :CLI, "pdfrb/cli"
59
59
 
60
60
  require "logger"
61
+ require "stringio"
62
+
63
+ class << self
64
+ # Open a PDF file (path or block over the file handle).
65
+ def open(path, **, &)
66
+ Document.open(path, **, &)
67
+ end
68
+
69
+ # Parse PDF bytes/IO into a Document.
70
+ def parse(io, **)
71
+ io = StringIO.new(io.b) if io.is_a?(String)
72
+ Document.new(io: io, **)
73
+ end
74
+ end
61
75
 
62
76
  def self.logger
63
77
  @logger ||= Logger.new($stderr, level: Logger::WARN)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pdfrb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.42
4
+ version: 0.7.44
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-08-25 00:00:00.000000000 Z
11
+ date: 2026-08-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -782,6 +782,7 @@ files:
782
782
  - lib/pdfrb/content/graphic_object/rectangle.rb
783
783
  - lib/pdfrb/content/graphics_state.rb
784
784
  - lib/pdfrb/content/hidden_text_detector.rb
785
+ - lib/pdfrb/content/inline_image.rb
785
786
  - lib/pdfrb/content/operator.rb
786
787
  - lib/pdfrb/content/operators.rb
787
788
  - lib/pdfrb/content/operators/clipping.rb