pdfrb 0.7.20 → 0.7.22

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: a799578418324e198b38930d7dce57663503478adb1b529c608d58183f84f183
4
- data.tar.gz: 94b49f8a02ce9845ad283afa39ec4224ce0ecef9240919c21909e38f18f52ccd
3
+ metadata.gz: 06bcf184cd873b50571015f1848d43e3692c041d98bd278237b86d06f79d14c0
4
+ data.tar.gz: 768be65b94d3a987e83278429a765563974ee95cd7125ca05d9a0b9a303074c8
5
5
  SHA512:
6
- metadata.gz: 1d2bdf98564008508094b7c6165160541419331edd629f2dd836dc4632b1f99830e0b8d3b9490cd3395ebc5e0674c40f8ce99bf42b079ff9f8ab7c3b13e8180a
7
- data.tar.gz: 984567fb69359980624147608deeeed46c9a345a0ec4f97158924629d1bf78f8c3a6be5dba5cfc1c6a36d930865a55d33a5c5d0fa17922fb4c16726d1142f037
6
+ metadata.gz: 912a23d9b0d4e2b122ea255214dc4d443ee0233d592ed483cea6a12c5ff60ec52db59a183edb8790b8b4acc50badfef728da2de56028fef8df5c58fd507e0d04
7
+ data.tar.gz: ec728839ee2d5db65428b681aa0bc41dd7613569e3baca4adea7e197e5388f64440342547ef2e538b2568a3bab3bce12f0ec584a6165b867f36bf20c676941b6
data/docs/SEMVER.md ADDED
@@ -0,0 +1,60 @@
1
+ # Pdfrb Semver Policy
2
+
3
+ ## Version Scheme
4
+
5
+ Pdfrb follows [Semantic Versioning 2.0.0](https://semver.org/).
6
+
7
+ - **MAJOR** (X.0.0): Breaking API changes. Existing code that depends
8
+ on pdfrb may need updates.
9
+ - **MINOR** (0.X.0): New features, backward-compatible. Existing code
10
+ continues to work.
11
+ - **PATCH** (0.0.X): Bug fixes, performance improvements, doc updates.
12
+ No new features, no breaking changes.
13
+
14
+ ## Current Phase: 0.x (pre-1.0)
15
+
16
+ During 0.x development, the API is not yet frozen. Minor version bumps
17
+ MAY include breaking changes, but they will be documented in
18
+ CHANGELOG.md with migration notes.
19
+
20
+ The goal is to reach 1.0 once:
21
+ - The public API is stable (Document, Writer, Canvas, Conformance).
22
+ - Round-trip is proven against a broad fixture corpus.
23
+ - All P0 TODOs are complete.
24
+ - veraPDF cross-check passes on PDF/A output.
25
+
26
+ ## Deprecation Policy
27
+
28
+ 1. A deprecated API is marked with YARD `@deprecated` tags and emits a
29
+ `Warning.warn` on first use.
30
+ 2. The deprecation period lasts at least one minor version.
31
+ 3. Removal happens in the next major version bump.
32
+
33
+ ## Release Checklist
34
+
35
+ - [ ] All specs pass: `bundle exec rake`
36
+ - [ ] Rubocop clean: `bundle exec rubocop`
37
+ - [ ] Coverage maintained or improved: `COVERAGE=1 bundle exec rspec`
38
+ - [ ] CHANGELOG.md updated with all changes
39
+ - [ ] Version bumped in `lib/pdfrb/version.rb`
40
+ - [ ] Git tag created: `vX.Y.Z`
41
+ - [ ] Gem pushed: `gem build && gem push`
42
+
43
+ ## Public API Stability Contract
44
+
45
+ The following are considered **stable** (subject to semver guarantees):
46
+
47
+ - `Pdfrb::Document` — top-level facade (new, open, write, pages, fonts, etc.)
48
+ - `Pdfrb::Writer` — serialization
49
+ - `Pdfrb::Serializer` — COS-to-bytes
50
+ - `Pdfrb::Canvas` — content-stream drawing
51
+ - `Pdfrb::Compare` — semantic diff
52
+ - `Pdfrb::Conformance` — PDF/A and PDF/UA validators
53
+ - `Pdfrb::DigitalSignature` — signing and verification
54
+ - `Pdfrb::Error` hierarchy
55
+
56
+ The following are **internal** (may change without notice):
57
+
58
+ - `Pdfrb::Source::*` — tokenizer/parser internals
59
+ - `Pdfrb::Model::Cos::*` — COS implementation details
60
+ - `Pdfrb::Arlington::*` — predicate evaluation internals
data/docs/USAGE.md ADDED
@@ -0,0 +1,173 @@
1
+ # Pdfrb Usage Guide
2
+
3
+ A cookbook of common PDF tasks using pdfrb.
4
+
5
+ ## Creating a PDF
6
+
7
+ ```ruby
8
+ require "pdfrb"
9
+
10
+ doc = Pdfrb::Document.new
11
+ font = doc.fonts.add("Helvetica")
12
+ page = doc.pages.add
13
+ page.canvas.text("Hello, World!", at: [72, 720], font: font, size: 24)
14
+
15
+ doc.write("hello.pdf")
16
+ ```
17
+
18
+ ## Reading a PDF
19
+
20
+ ```ruby
21
+ doc = Pdfrb.open("input.pdf")
22
+ puts "Pages: #{doc.pages.count}"
23
+ doc.pages.each do |page|
24
+ text = Pdfrb::Task::ExtractText.call_single_page(page)
25
+ puts text
26
+ end
27
+ ```
28
+
29
+ ## Drawing on a Canvas
30
+
31
+ ```ruby
32
+ doc = Pdfrb::Document.new
33
+ page = doc.pages.add
34
+ font = doc.fonts.add("Helvetica")
35
+
36
+ page.canvas.tap do |c|
37
+ c.text("Title", at: [72, 720], font: font, size: 18)
38
+ c.rectangle(point: [72, 700], width: 200, height: 50)
39
+ c.stroke
40
+ c.line(from: [72, 600], to: [300, 600])
41
+ c.stroke
42
+ end
43
+
44
+ doc.write("drawing.pdf")
45
+ ```
46
+
47
+ ## Embedding an ICC Profile
48
+
49
+ ```ruby
50
+ icc_bytes = File.binread("sRGB.icc")
51
+ cs = doc.colors.embed_icc_profile(icc_bytes)
52
+ page = doc.pages.first
53
+ cs_name = doc.colors.register(page, cs)
54
+ # Use cs_name in content stream: "/CS1 cs 0.5 0.3 0.2 scn"
55
+ ```
56
+
57
+ ## Tagged PDF (Accessibility)
58
+
59
+ ```ruby
60
+ doc.structure.enable!
61
+ doc.catalog.value[:Lang] = "en-US"
62
+
63
+ doc_elem = doc.structure.add_element(:Document)
64
+ doc.structure.add_child(doc_elem, :H1, title: "Introduction")
65
+ doc.structure.add_child(doc_elem, :P)
66
+ ```
67
+
68
+ ## Optional Content Groups (Layers)
69
+
70
+ ```ruby
71
+ doc.layers.add("Background Art", default_on: false)
72
+ doc.layers.add("Annotations")
73
+ doc.layers.sync!
74
+ ```
75
+
76
+ ## Interactive Forms (AcroForm)
77
+
78
+ ```ruby
79
+ page = doc.pages.add
80
+ doc.form.add_text_field("username", page: page, rect: [50, 700, 250, 720])
81
+ doc.form.add_checkbox("agree", page: page, rect: [50, 650, 65, 665], checked: true)
82
+ doc.form.add_combo("country", page: page, rect: [50, 600, 200, 620],
83
+ options: ["US", "UK", "JP"], value: "US")
84
+ ```
85
+
86
+ ## Digital Signatures
87
+
88
+ ```ruby
89
+ cert = OpenSSL::X509::Certificate.new(File.read("cert.pem"))
90
+ key = OpenSSL::PKey::RSA.new(File.read("key.pem"))
91
+
92
+ signed = Pdfrb::DigitalSignature::Signing.sign(doc, cert: cert, key: key,
93
+ reason: "Approval")
94
+ File.binwrite("signed.pdf", signed)
95
+
96
+ # Verify
97
+ results = Pdfrb::DigitalSignature::Verification.verify(signed, trusted_certs: [cert])
98
+ puts "Valid: #{results.first.valid?}"
99
+ ```
100
+
101
+ ## Semantic Comparison (Diff)
102
+
103
+ ```ruby
104
+ left = File.binread("v1.pdf")
105
+ right = File.binread("v2.pdf")
106
+ report = Pdfrb::Compare.compare(left, right)
107
+
108
+ puts "Pages: #{report.page_count_delta}"
109
+ puts "Fonts added: #{report.font_diff[:added]}"
110
+ puts "Equivalent: #{report.equivalent?}"
111
+ ```
112
+
113
+ ## Conformance Validation
114
+
115
+ ```ruby
116
+ doc = Pdfrb.open("archival.pdf")
117
+
118
+ # PDF/A
119
+ result = Pdfrb::Conformance::PdfA.validate(doc, level: :a2b)
120
+ puts "PDF/A-2b: #{result.passed? ? 'PASS' : 'FAIL'}"
121
+ result.errors.each { |e| puts " ERROR: #{e.message}" }
122
+
123
+ # PDF/UA
124
+ result = Pdfrb::Conformance::PdfUA.validate(doc)
125
+ puts "PDF/UA: #{result.passed? ? 'PASS' : 'FAIL'}"
126
+ ```
127
+
128
+ ## Linearization (Fast Web View)
129
+
130
+ ```ruby
131
+ doc = Pdfrb.open("large.pdf")
132
+ io = StringIO.new
133
+ Pdfrb::Linearization::Writer.new(doc).write(io)
134
+ File.binwrite("linearized.pdf", io.string)
135
+ ```
136
+
137
+ ## Encryption
138
+
139
+ ```ruby
140
+ doc = Pdfrb.open("input.pdf")
141
+ doc.write("encrypted.pdf")
142
+ # Encryption configuration via document.config
143
+ ```
144
+
145
+ ## Merging PDFs
146
+
147
+ ```ruby
148
+ target = Pdfrb.open("base.pdf")
149
+ source = Pdfrb.open("appendix.pdf")
150
+ Pdfrb::Task::Merge.call(target, source)
151
+ target.write("merged.pdf")
152
+ ```
153
+
154
+ ## Extracting Images
155
+
156
+ ```ruby
157
+ doc = Pdfrb.open("input.pdf")
158
+ images = Pdfrb::Task::ExtractImages.call(doc)
159
+ images.each_with_index do |img, i|
160
+ File.binwrite("image_#{i}.#{img[:format]}", img[:data])
161
+ end
162
+ ```
163
+
164
+ ## CLI
165
+
166
+ ```sh
167
+ pdfrb info input.pdf
168
+ pdfrb extract-text input.pdf
169
+ pdfrb merge a.pdf b.pdf -o merged.pdf
170
+ pdfrb diff v1.pdf v2.pdf
171
+ pdfrb encrypt input.pdf -o encrypted.pdf
172
+ pdfrb optimize input.pdf -o optimized.pdf
173
+ ```
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ module Model
5
+ module Type
6
+ # Additional Actions on a page object (ISO 32000-2 §12.6.3.16,
7
+ # PDF 1.2+). /O fires on page open, /C on page close.
8
+ class AddActionPageObject < Pdfrb::Model::Cos::Dictionary
9
+ arlington_object "AddActionPageObject"
10
+
11
+ # /O — action(s) on page open.
12
+ def on_open(document = nil)
13
+ resolve_action(:O, document)
14
+ end
15
+
16
+ # /C — action(s) on page close.
17
+ def on_close(document = nil)
18
+ resolve_action(:C, document)
19
+ end
20
+
21
+ private
22
+
23
+ def resolve_action(key, document)
24
+ ref = value[key]
25
+ return nil unless ref && document
26
+
27
+ ref.is_a?(Pdfrb::Model::Reference) ? document.object(ref) : ref
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -3,13 +3,14 @@
3
3
  module Pdfrb
4
4
  module Model
5
5
  module Type
6
- # BorderEffect (s12.5.4.2). Cloudy / inset border effects for
7
- # annotations.
8
- class BorderEffect < Cos::Dictionary
6
+ # BorderEffect (ISO 32000-2 §12.5.4.2, PDF 1.5+). Cloudy /
7
+ # inset border effects for annotations, via the /BE dict.
8
+ class BorderEffect < Pdfrb::Model::Cos::Dictionary
9
+ arlington_object "BorderEffect"
9
10
  register_type :BorderEffect
10
11
 
11
12
  def type; self[:Type]; end
12
- def style; self[:S]&.to_sym; end
13
+ def style; (self[:S] || :S).to_sym; end
13
14
  def intensity; self[:I] || 0; end
14
15
 
15
16
  def cloudy?
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ module Model
5
+ module Type
6
+ # BorderStyle (ISO 32000-2 §12.5.4, PDF 1.2+). The /BS dict on
7
+ # an annotation, controlling line width, style, and dash
8
+ # pattern.
9
+ class BorderStyle < Pdfrb::Model::Cos::Dictionary
10
+ arlington_object "BorderStyle"
11
+
12
+ # /W — optional, border width in points (default 1).
13
+ def width
14
+ value[:W] || 1
15
+ end
16
+
17
+ # /S — optional, border style name (default :S).
18
+ # S = solid, D = dashed, B = beveled, I = inset, U = underline.
19
+ def style
20
+ (value[:S] || :S).to_sym
21
+ end
22
+
23
+ # /D — optional, dash pattern array (only when style == :D).
24
+ def dash
25
+ value[:D]
26
+ end
27
+
28
+ def solid?; style == :S; end
29
+ def dashed?; style == :D; end
30
+ def beveled?; style == :B; end
31
+ def inset?; style == :I; end
32
+ def underline?; style == :U; end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -7,6 +7,7 @@ module Pdfrb
7
7
  # its Character ID system: registry, ordering, supplement.
8
8
  # Required for proper glyph interpretation.
9
9
  class CIDSystemInfo < Pdfrb::Model::Cos::Dictionary
10
+ arlington_object "CIDSystemInfo"
10
11
  def registry; self[:Registry]; end
11
12
  def ordering; self[:Ordering]; end
12
13
  def supplement; self[:Supplement]; end
@@ -6,6 +6,7 @@ module Pdfrb
6
6
  # Movie dictionary (s13.4). Deprecated since PDF 2.0 but still
7
7
  # appears in legacy PDFs. Use Screen + Rendition actions instead.
8
8
  class Movie < Pdfrb::Model::Cos::Dictionary
9
+ arlington_object "Movie"
9
10
  def type; self[:Type]; end
10
11
  def file_spec; self[:F]; end
11
12
  def aspect; self[:Aspect]; end
@@ -7,6 +7,7 @@ module Pdfrb
7
7
  # /R = sampling rate, /C = channels, /B = bits/sample, /E =
8
8
  # encoding (Raw, Signed, MuLaw, ALaw).
9
9
  class Sound < Pdfrb::Model::Cos::Stream
10
+ arlington_object "SoundObject"
10
11
  def type; self[:Type]; end
11
12
  def sampling_rate; self[:R]; end
12
13
  def channels; self[:C] || 1; end
@@ -4,6 +4,7 @@ module Pdfrb
4
4
  module Model
5
5
  module Type
6
6
  class WidgetAnnotation < Annotation
7
+ arlington_object "AnnotWidget"
7
8
  def field; self[:Parent]; end
8
9
  def appearance_stream; self[:AP]; end
9
10
  def highlight_mode; self[:H]; end
@@ -401,6 +401,13 @@ module Pdfrb
401
401
  # Document Part hierarchy (ISO 16612-2 PDF/VT).
402
402
  autoload :DPartRoot, "pdfrb/model/type/d_part"
403
403
  autoload :DPart, "pdfrb/model/type/d_part"
404
+
405
+ # Additional actions on page objects (s12.6.3.16).
406
+ autoload :AddActionPageObject, "pdfrb/model/type/add_action_page_object"
407
+
408
+ # Border style (s12.5.4). BorderEffect lives in its own file.
409
+ autoload :BorderStyle, "pdfrb/model/type/border_style"
410
+ autoload :BorderEffect, "pdfrb/model/type/border_effect"
404
411
  end
405
412
  end
406
413
  end
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.20"
4
+ VERSION = "0.7.22"
5
5
  end
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.20
4
+ version: 0.7.22
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-17 00:00:00.000000000 Z
11
+ date: 2026-08-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -701,6 +701,8 @@ files:
701
701
  - data/pdfrb/glyphlist.txt
702
702
  - data/pdfrb/layout/hyphenation_en.txt
703
703
  - data/pdfrb/zapfdingbats.txt
704
+ - docs/SEMVER.md
705
+ - docs/USAGE.md
704
706
  - exe/pdfrb
705
707
  - lib/pdfrb.rb
706
708
  - lib/pdfrb/action.rb
@@ -975,6 +977,7 @@ files:
975
977
  - lib/pdfrb/model/type/action_trans.rb
976
978
  - lib/pdfrb/model/type/action_uri.rb
977
979
  - lib/pdfrb/model/type/add_action_catalog.rb
980
+ - lib/pdfrb/model/type/add_action_page_object.rb
978
981
  - lib/pdfrb/model/type/af_embedded_file.rb
979
982
  - lib/pdfrb/model/type/af_file_specification.rb
980
983
  - lib/pdfrb/model/type/alternate_image.rb
@@ -985,6 +988,7 @@ files:
985
988
  - lib/pdfrb/model/type/appearance_trap_net.rb
986
989
  - lib/pdfrb/model/type/bead.rb
987
990
  - lib/pdfrb/model/type/border_effect.rb
991
+ - lib/pdfrb/model/type/border_style.rb
988
992
  - lib/pdfrb/model/type/border_styling.rb
989
993
  - lib/pdfrb/model/type/box_color_info.rb
990
994
  - lib/pdfrb/model/type/box_style.rb
@@ -1216,7 +1220,6 @@ files:
1216
1220
  - lib/pdfrb/xmp/packet.rb
1217
1221
  - lib/pdfrb/xmp/schemas.rb
1218
1222
  - lib/pdfrb/xref_section.rb
1219
- - pdfrb.gemspec
1220
1223
  - script/apply_fixes.sh
1221
1224
  - script/make_jpeg_fixture.rb
1222
1225
  - script/make_png_fixture.rb
data/pdfrb.gemspec DELETED
@@ -1,45 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "lib/pdfrb/version"
4
-
5
- Gem::Specification.new do |spec|
6
- spec.name = "pdfrb"
7
- spec.version = Pdfrb::VERSION
8
- spec.authors = ["Ribose Inc."]
9
- spec.email = ["open.source@ribose.com"]
10
-
11
- spec.summary = "Pure-Ruby PDF parser, Arlington-model-driven domain model, and serializer"
12
- spec.description = <<~HEREDOC
13
- Pdfrb is a pure-Ruby PDF library: a byte-level reader, an
14
- Arlington-model-driven typed domain model, and a serializer. The
15
- PDF object model is sourced directly from the vendored Arlington
16
- PDF Model TSVs (machine-readable ISO 32000-2:2020), so field
17
- metadata, version predicates, and validators stay aligned with
18
- the spec by data, not by hand-coded mimicry.
19
-
20
- Two-direction contract: "PDF file <=> Model" and "API Builder
21
- Input => Model". Mirrors the layered design of the sibling
22
- postscript gem.
23
- HEREDOC
24
-
25
- spec.homepage = "https://github.com/claricle/pdfrb"
26
- spec.license = "BSD-2-Clause"
27
- spec.required_ruby_version = ">= 3.2.0"
28
-
29
- spec.metadata["homepage_uri"] = spec.homepage
30
- spec.metadata["source_code_uri"] = "https://github.com/claricle/pdfrb"
31
- spec.metadata["changelog_uri"] = "https://github.com/claricle/pdfrb/blob/main/CHANGELOG.md"
32
- spec.metadata["bug_tracker_uri"] = "https://github.com/claricle/pdfrb/issues"
33
- spec.metadata["rubygems_mfa_required"] = "true"
34
-
35
- spec.files = Dir.chdir(__dir__) do
36
- `git ls-files -z`.split("\x0").reject do |f|
37
- (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
38
- end
39
- end
40
- spec.bindir = "exe"
41
- spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
42
- spec.require_paths = ["lib"]
43
-
44
- spec.add_dependency "thor"
45
- end