pdfrb 0.7.21 → 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 +4 -4
- data/docs/SEMVER.md +60 -0
- data/docs/USAGE.md +173 -0
- data/lib/pdfrb/version.rb +1 -1
- metadata +4 -3
- data/pdfrb.gemspec +0 -45
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 06bcf184cd873b50571015f1848d43e3692c041d98bd278237b86d06f79d14c0
|
|
4
|
+
data.tar.gz: 768be65b94d3a987e83278429a765563974ee95cd7125ca05d9a0b9a303074c8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
+
```
|
data/lib/pdfrb/version.rb
CHANGED
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.
|
|
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-
|
|
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
|
|
@@ -1218,7 +1220,6 @@ files:
|
|
|
1218
1220
|
- lib/pdfrb/xmp/packet.rb
|
|
1219
1221
|
- lib/pdfrb/xmp/schemas.rb
|
|
1220
1222
|
- lib/pdfrb/xref_section.rb
|
|
1221
|
-
- pdfrb.gemspec
|
|
1222
1223
|
- script/apply_fixes.sh
|
|
1223
1224
|
- script/make_jpeg_fixture.rb
|
|
1224
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
|