bidi2pdf 0.1.9 → 0.1.10
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/CHANGELOG.md +59 -3
- data/README.md +146 -7
- data/lib/bidi2pdf/bidi/auth_interceptor.rb +3 -0
- data/lib/bidi2pdf/bidi/browser_tab.rb +38 -8
- data/lib/bidi2pdf/bidi/commands/cdp_get_session.rb +21 -0
- data/lib/bidi2pdf/bidi/commands/page_print.rb +101 -0
- data/lib/bidi2pdf/bidi/commands/print_parameters_validator.rb +4 -1
- data/lib/bidi2pdf/bidi/commands.rb +2 -0
- data/lib/bidi2pdf/bidi/connection_manager.rb +3 -0
- data/lib/bidi2pdf/bidi/session.rb +33 -2
- data/lib/bidi2pdf/cli.rb +9 -2
- data/lib/bidi2pdf/test_helpers/configuration.rb +67 -0
- data/lib/bidi2pdf/test_helpers/images/extractor.rb +99 -0
- data/lib/bidi2pdf/test_helpers/images/image_similarity_checker.rb +50 -0
- data/lib/bidi2pdf/test_helpers/images/tiff_helper.rb +204 -0
- data/lib/bidi2pdf/test_helpers/images.rb +12 -0
- data/lib/bidi2pdf/test_helpers/matchers/contains_pdf_image.rb +29 -0
- data/lib/bidi2pdf/test_helpers/pdf_file_helper.rb +39 -0
- data/lib/bidi2pdf/test_helpers/spec_paths_helper.rb +60 -0
- data/lib/bidi2pdf/test_helpers/testcontainers/chromedriver_test_helper.rb +1 -1
- data/lib/bidi2pdf/test_helpers/testcontainers/testcontainers_refinement.rb +1 -1
- data/lib/bidi2pdf/test_helpers.rb +7 -0
- data/lib/bidi2pdf/version.rb +1 -1
- metadata +49 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 134f35256652da3e5e6c8e383e97c98779a1e2665798dbfa5d133bc85130b8cc
|
4
|
+
data.tar.gz: dd106af3e757d26ba3d935bbe54d0249402280a84f1b1a8e27855d19b4cd155c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 15d9df827cefb697a44f78945f5fc20866319c7fc6a403f5ad58817f239deca9857fc0923e0a7faf582dac6d7f0770236310cca8bd58aab138ab6499749c6d37
|
7
|
+
data.tar.gz: '09b043e8da1b79a4cc95c708c32afa78b00fa3f6edbedd2e0dacfdd562aff1a385be50f36cc78951a771b1b63c775eac11980dec07d1998476b9b247d6114377'
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
<!-- generated by git-cliff start -->
|
2
|
-
|
3
2
|
# Changelog
|
4
3
|
|
5
4
|
All notable changes to this project will be documented in this file.
|
@@ -7,10 +6,65 @@ All notable changes to this project will be documented in this file.
|
|
7
6
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
8
7
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
9
8
|
|
10
|
-
[unreleased]: https://github.com
|
9
|
+
[unreleased]: https://github.com///compare/v0.1.10..HEAD
|
11
10
|
|
12
11
|
<!-- generated by git-cliff end -->
|
13
12
|
|
13
|
+
## [0.1.10] - 2025-06-18
|
14
|
+
|
15
|
+
### 💄 Style
|
16
|
+
|
17
|
+
- Improve readability of conditional statements
|
18
|
+
|
19
|
+
### 📝 Docs
|
20
|
+
|
21
|
+
- Update README with Quick Start section
|
22
|
+
- Add Table of Contents and architecture diagram
|
23
|
+
- Add high level overview diagram to README
|
24
|
+
- Update example URLs in README for clarity
|
25
|
+
|
26
|
+
### 🔄 Changed
|
27
|
+
|
28
|
+
- Merge pull request #19 from dieter-medium/enhance-docs
|
29
|
+
- Merge pull request #18 from dieter-medium/enhance-docs
|
30
|
+
- Merge pull request #13 from dieter-medium/dependabot/bundler/main/base64-0.3.0
|
31
|
+
- Merge branch 'main' into dependabot/bundler/main/base64-0.3.0
|
32
|
+
- Merge pull request #14 from dieter-medium/dependabot/bundler/main/rake-13.3.0
|
33
|
+
- Merge branch 'main' into dependabot/bundler/main/rake-13.3.0
|
34
|
+
- Merge pull request #12 from dieter-medium/dependabot/bundler/main/rspec-3.13.1
|
35
|
+
- Merge pull request #11 from dieter-medium/dependabot/bundler/main/rubocop-1.75.7
|
36
|
+
- Merge branch 'main' into dependabot/bundler/main/rubocop-1.75.7
|
37
|
+
- Merge pull request #10 from dieter-medium/dependabot/bundler/main/json-2.12.2
|
38
|
+
- Merge branch 'main' into dependabot/bundler/main/json-2.12.2
|
39
|
+
- Merge pull request #9 from dieter-medium/dependabot/bundler/main/diff-lcs-1.6.2
|
40
|
+
- Merge pull request #7 from dieter-medium/dependabot/bundler/main/rubocop-1.75.6
|
41
|
+
- Merge pull request #8 from dieter-medium/dependabot/bundler/main/json-2.12.0
|
42
|
+
- Merge pull request #6 from dieter-medium/dependabot/bundler/main/rbs-3.9.4
|
43
|
+
- Merge pull request #5 from dieter-medium/dependabot/bundler/main/chromedriver-binary-0.1.3
|
44
|
+
- Merge pull request #4 from dieter-medium/add-more-default-chrome-args
|
45
|
+
- Merge pull request #3 from dieter-medium/make-configuration-rw
|
46
|
+
- Merge pull request #2 from dieter-medium/add-test-helpers
|
47
|
+
- Merge pull request #1 from dieter-medium/testing-vips
|
48
|
+
|
49
|
+
### 🔧 Build
|
50
|
+
|
51
|
+
- Bump rspec from 3.13.0 to 3.13.1
|
52
|
+
- Bump rake from 13.2.1 to 13.3.0
|
53
|
+
- Update base64 requirement from ~> 0.2.0 to >= 0.2, < 0.4
|
54
|
+
- Bump rubocop from 1.75.3 to 1.76.2
|
55
|
+
- Bump diff-lcs from 1.6.1 to 1.6.2
|
56
|
+
- Bump json from 2.10.2 to 2.12.2
|
57
|
+
- Bump rbs from 3.9.2 to 3.9.4
|
58
|
+
- Bump chromedriver-binary from 0.1.2 to 0.1.3
|
59
|
+
|
60
|
+
### 🚀 Added
|
61
|
+
|
62
|
+
- Add CDP session handling for PDF generation
|
63
|
+
- Expand default Chrome arguments for sessions
|
64
|
+
- Add configuration setter for TestHelpers
|
65
|
+
- Add test helpers for PDF testing and handling
|
66
|
+
- Add image extraction and similarity checking
|
67
|
+
|
14
68
|
## [0.1.9] - 2025-05-04
|
15
69
|
|
16
70
|
### 🎨 Refactored
|
@@ -200,8 +254,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
200
254
|
|
201
255
|
- Initial release
|
202
256
|
|
257
|
+
## Changelog
|
203
258
|
|
204
|
-
- [unreleased](https://github.com/dieter-medium/bidi2pdf/compare/v0.1.
|
259
|
+
- [unreleased](https://github.com/dieter-medium/bidi2pdf/compare/v0.1.10..HEAD)
|
260
|
+
- [0.1.10](https://github.com/dieter-medium/bidi2pdf/compare/v0.1.9..V0.1.10)
|
205
261
|
- [0.1.9](https://github.com/dieter-medium/bidi2pdf/compare/v0.1.8..v0.1.9)
|
206
262
|
- [0.1.8](https://github.com/dieter-medium/bidi2pdf/compare/v0.1.7..v0.1.8)
|
207
263
|
- [0.1.7](https://github.com/dieter-medium/bidi2pdf/compare/v0.1.6..v0.1.7)
|
data/README.md
CHANGED
@@ -14,6 +14,23 @@ Bidi2pdf gives you **precision, flexibility, and full control**.
|
|
14
14
|
|
15
15
|
---
|
16
16
|
|
17
|
+
## 📚 Table of Contents
|
18
|
+
|
19
|
+
1. [Key Features](#key-features)
|
20
|
+
2. [Quick Start](#quick-start)
|
21
|
+
3. [Why BiDi?](#why-bidi-instead-of-cdp)
|
22
|
+
4. [Installation](#installation)
|
23
|
+
5. [CLI Usage](#cli-usage)
|
24
|
+
6. [Library API](#library-api)
|
25
|
+
7. [Architecture](#architecture)
|
26
|
+
8. [Docker](#docker)
|
27
|
+
9. [Configuration Options](#configuration-options)
|
28
|
+
10. [Rails Integration](#rails-integration)
|
29
|
+
11. [Test Helpers](#test-helpers)
|
30
|
+
12. [Development](#development)
|
31
|
+
13. [Contributing](#contributing)
|
32
|
+
14. [License](#license)
|
33
|
+
|
17
34
|
## ✨ Key Features
|
18
35
|
|
19
36
|
✅ **One-liner CLI** – From URL to PDF in a single command
|
@@ -27,6 +44,25 @@ Bidi2pdf gives you **precision, flexibility, and full control**.
|
|
27
44
|
|
28
45
|
---
|
29
46
|
|
47
|
+
## ⚡ Quick Start
|
48
|
+
|
49
|
+
Get up and running in three easy steps:
|
50
|
+
|
51
|
+
```bash
|
52
|
+
# 1. Install the gem (system-wide)
|
53
|
+
gem install bidi2pdf
|
54
|
+
|
55
|
+
# 2. Render any page to PDF
|
56
|
+
bidi2pdf render --url https://example.com --output example.pdf
|
57
|
+
|
58
|
+
# 3. Open the PDF (macOS shown; use xdg-open on Linux)
|
59
|
+
open example.pdf
|
60
|
+
```
|
61
|
+
|
62
|
+
> **Bundler users** – Add it to your project with `bundle add bidi2pdf`.
|
63
|
+
|
64
|
+
---
|
65
|
+
|
30
66
|
## 🚀 Installation
|
31
67
|
|
32
68
|
### Bundler
|
@@ -54,14 +90,14 @@ gem install bidi2pdf
|
|
54
90
|
### Command-line
|
55
91
|
|
56
92
|
```bash
|
57
|
-
bidi2pdf render --url https://example.com --output example.pdf
|
93
|
+
bidi2pdf render --url https://example.com/invoice/14432423 --output example.pdf
|
58
94
|
```
|
59
95
|
|
60
96
|
### Advanced CLI Options
|
61
97
|
|
62
98
|
```bash
|
63
99
|
bidi2pdf render \
|
64
|
-
--url https://example.com \
|
100
|
+
--url https://example.com/invoice/14432423 \
|
65
101
|
--output example.pdf \
|
66
102
|
--cookie session=abc123 \
|
67
103
|
--header X-API-KEY=token \
|
@@ -81,7 +117,7 @@ bidi2pdf render \
|
|
81
117
|
require 'bidi2pdf'
|
82
118
|
|
83
119
|
launcher = Bidi2pdf::Launcher.new(
|
84
|
-
url: 'https://example.com',
|
120
|
+
url: 'https://example.com/invoice/14432423',
|
85
121
|
output: 'example.pdf',
|
86
122
|
cookies: { 'session' => 'abc123' },
|
87
123
|
headers: { 'X-API-KEY' => 'token' },
|
@@ -99,7 +135,7 @@ launcher.launch
|
|
99
135
|
require "bidi2pdf"
|
100
136
|
|
101
137
|
Bidi2pdf::DSL.with_tab(headless: true) do |tab|
|
102
|
-
tab.navigate_to("https://example.com")
|
138
|
+
tab.navigate_to("https://example.com/invoice/14432423")
|
103
139
|
tab.wait_until_network_idle
|
104
140
|
tab.print("example.pdf")
|
105
141
|
end
|
@@ -145,7 +181,7 @@ tab.basic_auth(url_patterns: [{ type: "pattern", protocol: "https", hostname: "e
|
|
145
181
|
username: "username", password: "secret")
|
146
182
|
|
147
183
|
# 4. Render PDF
|
148
|
-
tab.navigate_to "https://example.com"
|
184
|
+
tab.navigate_to "https://example.com/invoice/14432423"
|
149
185
|
|
150
186
|
# Alternative: send html code to the browser
|
151
187
|
# tab.render_html_content("<html>...</html>")
|
@@ -176,6 +212,48 @@ session.close
|
|
176
212
|
|
177
213
|
---
|
178
214
|
|
215
|
+
## 🌐 Architecture
|
216
|
+
|
217
|
+
```mermaid
|
218
|
+
%%{ init: {
|
219
|
+
"theme": "base",
|
220
|
+
"themeVariables": {
|
221
|
+
"primaryColor": "#E0E7FF",
|
222
|
+
"secondaryColor":"#FEF9C3",
|
223
|
+
"edgeLabelBackground":"#FFFFFF",
|
224
|
+
"fontSize":"14px",
|
225
|
+
"nodeBorderRadius":"6"
|
226
|
+
}
|
227
|
+
}
|
228
|
+
}%%
|
229
|
+
flowchart LR
|
230
|
+
%% ----- Ruby side ---------
|
231
|
+
A["fa:fa-gem Ruby Application"]
|
232
|
+
B["fa:fa-gem bidi2pdf<br/>Library"]
|
233
|
+
%% ----Chrome environment -----------
|
234
|
+
subgraph C["fa:fa-chrome Chrome Environment"]
|
235
|
+
direction TB
|
236
|
+
C1["fa:fa-chrome Local Chrome<br/>(sub-process)"]
|
237
|
+
C2["fa:fa-docker Docker Chrome<br/>(remote)"]
|
238
|
+
end
|
239
|
+
|
240
|
+
D[[PDF File]]
|
241
|
+
%% ---- Data / control flows ------
|
242
|
+
A -- " HTML / URL + JS / CSS " --> B
|
243
|
+
B -- " WebDriver BiDi " --> C1
|
244
|
+
B -- " WebDriver BiDi " --> C2
|
245
|
+
C1 -- " PDF bytes " --> B
|
246
|
+
C2 -- " PDF bytes " --> B
|
247
|
+
B -- " PDF " --> D
|
248
|
+
%% --- Optional extra styling classes (for future tweaks) ---
|
249
|
+
classDef ruby fill:#E0E7FF,stroke:#6366F1,color:#1E1B4B;
|
250
|
+
classDef chrome fill:#FEF9C3,stroke:#F59E0B,color:#78350F;
|
251
|
+
class A,B ruby;
|
252
|
+
class C1,C2 chrome;
|
253
|
+
```
|
254
|
+
|
255
|
+
---
|
256
|
+
|
179
257
|
## 🐳 Docker Support
|
180
258
|
|
181
259
|
### 🛠️ Build & Run Locally
|
@@ -191,7 +269,7 @@ docker build -t bidi2pdf -f docker/Dockerfile .
|
|
191
269
|
docker run -it --rm \
|
192
270
|
-v ./output:/reports \
|
193
271
|
bidi2pdf \
|
194
|
-
bidi2pdf render --url=https://example.com --output /reports/example.pdf
|
272
|
+
bidi2pdf render --url=https://example.com/invoice/14432423 --output /reports/example.pdf
|
195
273
|
|
196
274
|
```
|
197
275
|
|
@@ -203,7 +281,7 @@ Grab it directly from [Docker Hub](https://hub.docker.com/r/dieters877565/bidi2p
|
|
203
281
|
docker run -it --rm \
|
204
282
|
-v ./output:/reports \
|
205
283
|
dieters877565/bidi2pdf:main-slim \
|
206
|
-
bidi2pdf render --url=https://example.com --output /reports/example.pdf
|
284
|
+
bidi2pdf render --url=https://example.com/invoice/14432423 --output /reports/example.pdf
|
207
285
|
```
|
208
286
|
|
209
287
|
✅ Tip: Mount your local directory (e.g. ./output) to /reports in the container to easily access the generated PDFs.
|
@@ -271,6 +349,67 @@ visit: [https://github.com/dieter-medium/bidi2pdf-rails](https://github.com/diet
|
|
271
349
|
|
272
350
|
---
|
273
351
|
|
352
|
+
## 🧪 Test Helpers
|
353
|
+
|
354
|
+
Bidi2pdf provides a suite of RSpec helpers (activated with `pdf: true`) to
|
355
|
+
simplify PDF-related testing:
|
356
|
+
|
357
|
+
### SpecPathsHelper
|
358
|
+
|
359
|
+
– `spec_dir` → returns your spec directory
|
360
|
+
– `tmp_dir` → returns your tmp directory
|
361
|
+
– `tmp_file(*parts)` → builds a tmp file path
|
362
|
+
– `random_tmp_dir(*dirs, prefix:)` → builds a random tmp directory
|
363
|
+
|
364
|
+
- `fixture_file(*parts)` → returns the path to a fixture file
|
365
|
+
|
366
|
+
### PdfFileHelper
|
367
|
+
|
368
|
+
– `with_pdf_debug(pdf_data) { |data| … }` → on failure, writes PDF to disk
|
369
|
+
– `store_pdf_file(pdf_data, filename_prefix = "test")` → saves PDF and returns path
|
370
|
+
|
371
|
+
### Rspec Matchers
|
372
|
+
|
373
|
+
- `have_pdf_page_count` → checks if the PDF has a specific number of pages
|
374
|
+
- `match_pdf_text` → checks if the PDF equals a specific text, after stripping whitespace and normalizing characters
|
375
|
+
- `contains_pdf_text` → checks if the PDF contains a specific text, after stripping whitespace and normalizing
|
376
|
+
characters, supporting regex
|
377
|
+
- `contains_pdf_image` → checks if the PDF contains a specific image
|
378
|
+
|
379
|
+
### ChromedriverContainer
|
380
|
+
|
381
|
+
`require "bidi2pdf/test_helpers/testcontainers"` you can use the `chromedriver_container` helper to
|
382
|
+
start a ChromeDriver container for your tests. This is useful if you don't want to run ChromeDriver locally
|
383
|
+
or if you want to ensure a clean environment for your tests.
|
384
|
+
|
385
|
+
This also provides the helper methods:
|
386
|
+
|
387
|
+
- `session_url` → returns the session URL for the ChromeDriver container
|
388
|
+
- `chromedriver_container` → returns the Testcontainers container object
|
389
|
+
- `create_session` -> creates a `Bidi2pdf::Bidi::Session` object for the ChromeDriver container
|
390
|
+
|
391
|
+
With the environment variable `DISABLE_CHROME_SANDBOX` set to `true`, the container will run Chrome without
|
392
|
+
the sandbox. This is useful for CI environments where the sandbox may cause issues.
|
393
|
+
|
394
|
+
#### Example
|
395
|
+
|
396
|
+
```ruby
|
397
|
+
require "bidi2pdf/test_helpers"
|
398
|
+
require "bidi2pdf/test_helpers/images" # <= for image matching, requires lib-vips
|
399
|
+
require "bidi2pdf/test_helpers/testcontainers" # <= requires testcontainers gem
|
400
|
+
|
401
|
+
RSpec.describe "PDF generation", :pdf, :chromedriver do
|
402
|
+
it "generates a PDF with the correct content" do
|
403
|
+
pdf_data = generate_pdf("https://example.com/invoice/14432423")
|
404
|
+
expect(pdf_data).to have_pdf_page_count(1)
|
405
|
+
expect(pdf_data).to match_pdf_text("Hello, world!")
|
406
|
+
expect(pdf_data).to contain_pdf_image(fixture_file("logo.png"))
|
407
|
+
end
|
408
|
+
end
|
409
|
+
```
|
410
|
+
|
411
|
+
---
|
412
|
+
|
274
413
|
## 🛠 Development
|
275
414
|
|
276
415
|
```bash
|
@@ -39,6 +39,7 @@ module Bidi2pdf
|
|
39
39
|
|
40
40
|
private
|
41
41
|
|
42
|
+
# rubocop:disable Naming/PredicateMethod
|
42
43
|
def handled_bad_credentials(navigation_id, network_id, url)
|
43
44
|
return false unless network_ids.include?(network_id)
|
44
45
|
|
@@ -55,6 +56,8 @@ module Bidi2pdf
|
|
55
56
|
|
56
57
|
true
|
57
58
|
end
|
59
|
+
|
60
|
+
# rubocop:enable Naming/PredicateMethod
|
58
61
|
end
|
59
62
|
end
|
60
63
|
end
|
@@ -371,13 +371,13 @@ module Bidi2pdf
|
|
371
371
|
# rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity
|
372
372
|
def print(outputfile = nil, print_options: { background: true }, &block)
|
373
373
|
Bidi2pdf.notification_service.instrument("print.bidi2pdf") do |instrumentation_payload|
|
374
|
-
cmd =
|
374
|
+
cmd, extractor = build_command_and_extractor(print_options)
|
375
375
|
|
376
376
|
instrumentation_payload[:cmd] = cmd
|
377
377
|
|
378
378
|
client.send_cmd_and_wait(cmd) do |response|
|
379
379
|
if response["result"]
|
380
|
-
pdf_base64 = response
|
380
|
+
pdf_base64 = extractor.call response
|
381
381
|
|
382
382
|
instrumentation_payload[:pdf_base64] = pdf_base64
|
383
383
|
|
@@ -404,6 +404,36 @@ module Bidi2pdf
|
|
404
404
|
|
405
405
|
private
|
406
406
|
|
407
|
+
def build_command_and_extractor(print_options)
|
408
|
+
cmd_type = (print_options.delete(:cmd_type) || :bidi).to_sym
|
409
|
+
|
410
|
+
if cmd_type == :bidi
|
411
|
+
cmd = Bidi2pdf::Bidi::Commands::BrowsingContextPrint.new(
|
412
|
+
context: browsing_context_id,
|
413
|
+
print_options: print_options
|
414
|
+
)
|
415
|
+
extractor = ->(response) { response.dig("result", "data") }
|
416
|
+
else
|
417
|
+
cmd = Bidi2pdf::Bidi::Commands::PagePrint.new(
|
418
|
+
cdp_session: cdp_session,
|
419
|
+
print_options: print_options
|
420
|
+
)
|
421
|
+
extractor = ->(response) { response.dig("result", "result", "data") }
|
422
|
+
end
|
423
|
+
|
424
|
+
[cmd, extractor]
|
425
|
+
end
|
426
|
+
|
427
|
+
def cdp_session
|
428
|
+
@cdp_session ||= begin
|
429
|
+
cmd = Bidi2pdf::Bidi::Commands::CdpGetSession.new context: browsing_context_id
|
430
|
+
client.send_cmd_and_wait(cmd) do |response|
|
431
|
+
Bidi2pdf.logger.debug "CDP session: #{response.inspect}"
|
432
|
+
response["result"]["session"]
|
433
|
+
end
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
407
437
|
def navigate_with_listeners(url)
|
408
438
|
register_event_listeners
|
409
439
|
|
@@ -479,7 +509,7 @@ module Bidi2pdf
|
|
479
509
|
const script = document.createElement('script');
|
480
510
|
script.type = 'text/javascript';
|
481
511
|
|
482
|
-
#{
|
512
|
+
#{"script.text = #{content.to_json};" if content}
|
483
513
|
|
484
514
|
script.addEventListener(
|
485
515
|
'error',
|
@@ -489,12 +519,12 @@ module Bidi2pdf
|
|
489
519
|
{once: true},
|
490
520
|
);
|
491
521
|
|
492
|
-
#{
|
522
|
+
#{"script.id = '#{id}';" if id}
|
493
523
|
#{js_src_part}
|
494
524
|
|
495
525
|
document.head.appendChild(script);
|
496
526
|
|
497
|
-
#{
|
527
|
+
#{"resolve(script);" unless url}
|
498
528
|
});
|
499
529
|
JS
|
500
530
|
end
|
@@ -515,7 +545,7 @@ module Bidi2pdf
|
|
515
545
|
link.type = 'text/css';
|
516
546
|
link.href = '#{url}';
|
517
547
|
#{" "}
|
518
|
-
#{
|
548
|
+
#{"link.id = '#{id}';" if id}
|
519
549
|
#{" "}
|
520
550
|
link.addEventListener(
|
521
551
|
'load',
|
@@ -544,9 +574,9 @@ module Bidi2pdf
|
|
544
574
|
const style = document.createElement('style');
|
545
575
|
style.type = 'text/css';
|
546
576
|
#{" "}
|
547
|
-
#{
|
577
|
+
#{"style.id = '#{id}';" if id}
|
548
578
|
#{" "}
|
549
|
-
#{
|
579
|
+
#{"style.textContent = #{content.to_json};" if content}
|
550
580
|
#{" "}
|
551
581
|
document.head.appendChild(style);
|
552
582
|
resolve(style);
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bidi2pdf
|
4
|
+
module Bidi
|
5
|
+
module Commands
|
6
|
+
class CdpGetSession
|
7
|
+
include Base
|
8
|
+
|
9
|
+
def initialize(context:)
|
10
|
+
@context = context
|
11
|
+
end
|
12
|
+
|
13
|
+
def params = { context: @context }
|
14
|
+
|
15
|
+
def method_name
|
16
|
+
"goog:cdp.getSession"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "print_parameters_validator"
|
4
|
+
|
5
|
+
module Bidi2pdf
|
6
|
+
module Bidi
|
7
|
+
module Commands
|
8
|
+
class PagePrint
|
9
|
+
include Base
|
10
|
+
|
11
|
+
def initialize(cdp_session:, print_options:)
|
12
|
+
@cdp_session = cdp_session
|
13
|
+
@print_options = print_options || { background: true }
|
14
|
+
|
15
|
+
PrintParametersValidator.validate!(@print_options)
|
16
|
+
|
17
|
+
return unless @print_options[:page]&.key?(:format)
|
18
|
+
|
19
|
+
@print_options[:page] = Bidi2pdf.translate_paper_format @print_options[:page][:format]
|
20
|
+
end
|
21
|
+
|
22
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
23
|
+
def params
|
24
|
+
{
|
25
|
+
# https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-printToPDF
|
26
|
+
method: "Page.printToPDF",
|
27
|
+
session: @cdp_session,
|
28
|
+
params: {
|
29
|
+
"printBackground" => @print_options[:background],
|
30
|
+
|
31
|
+
"marginTop" => cm_to_inch(@print_options.dig(:margin, :top) || 0),
|
32
|
+
"marginBottom" => cm_to_inch(@print_options.dig(:margin, :bottom) || 0),
|
33
|
+
"marginLeft" => cm_to_inch(@print_options.dig(:margin, :left) || 0),
|
34
|
+
"marginRight" => cm_to_inch(@print_options.dig(:margin, :right) || 0),
|
35
|
+
"landscape" => (@print_options[:orientation] || "portrait").to_sym == :landscape,
|
36
|
+
|
37
|
+
"paperWidth" => cm_to_inch(@print_options.dig(:page, :width)),
|
38
|
+
"paperHeight" => cm_to_inch(@print_options.dig(:page, :height)),
|
39
|
+
"pageRanges" => page_ranges_to_string(@print_options[:pageRanges]),
|
40
|
+
"scale" => @print_options[:scale] || 1.0,
|
41
|
+
|
42
|
+
"displayHeaderFooter" => @print_options[:display_header_footer],
|
43
|
+
"headerTemplate" => @print_options[:header_template] || "",
|
44
|
+
"footerTemplate" => @print_options[:footer_template] || "",
|
45
|
+
|
46
|
+
"preferCSSPageSize" => @print_options.fetch(:prefer_css_page_size, true),
|
47
|
+
|
48
|
+
"generateTaggedPDF" => @print_options.fetch(:generate_tagged_pdf, false),
|
49
|
+
"generateDocumentOutline" => @print_options.fetch(:generate_document_outline, false),
|
50
|
+
|
51
|
+
transferMode: "ReturnAsBase64"
|
52
|
+
}.compact
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
57
|
+
|
58
|
+
def method_name
|
59
|
+
"goog:cdp.sendCommand"
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
# rubocop:disable Naming/MethodParameterName
|
65
|
+
def cm_to_inch(cm)
|
66
|
+
return nil if cm.nil?
|
67
|
+
|
68
|
+
cm.to_f / 2.54
|
69
|
+
end
|
70
|
+
|
71
|
+
# rubocop:enable Naming/MethodParameterName
|
72
|
+
|
73
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
74
|
+
def page_ranges_to_string(input)
|
75
|
+
return nil if input.nil? || input.empty?
|
76
|
+
|
77
|
+
segments = input.map do |entry|
|
78
|
+
case entry
|
79
|
+
when Integer
|
80
|
+
entry.to_s
|
81
|
+
when String
|
82
|
+
raise ArgumentError, "Invalid page entry: #{entry.inspect}" unless entry =~ /\A\d+(-\d+)?\z/
|
83
|
+
|
84
|
+
entry
|
85
|
+
else
|
86
|
+
raise ArgumentError, "Unsupported page entry type: #{entry.class}"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# dedupe, sort by numeric start, and join
|
91
|
+
segments
|
92
|
+
.uniq
|
93
|
+
.sort_by { |seg| seg.split("-", 2).first.to_i }
|
94
|
+
.join(",")
|
95
|
+
end
|
96
|
+
|
97
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -37,6 +37,7 @@ module Bidi2pdf
|
|
37
37
|
@params = params
|
38
38
|
end
|
39
39
|
|
40
|
+
# rubocop:disable Naming/PredicateMethod
|
40
41
|
def validate!
|
41
42
|
raise ArgumentError, "params must be a Hash" unless @params.is_a?(Hash)
|
42
43
|
|
@@ -51,6 +52,8 @@ module Bidi2pdf
|
|
51
52
|
true
|
52
53
|
end
|
53
54
|
|
55
|
+
# rubocop:enable Naming/PredicateMethod
|
56
|
+
|
54
57
|
private
|
55
58
|
|
56
59
|
def validate_boolean(key)
|
@@ -79,7 +82,7 @@ module Bidi2pdf
|
|
79
82
|
def validate_page_ranges
|
80
83
|
return unless @params.key?(:pageRanges)
|
81
84
|
unless @params[:pageRanges].is_a?(Array) &&
|
82
|
-
|
85
|
+
@params[:pageRanges].all? { |v| v.is_a?(Integer) || v.is_a?(String) }
|
83
86
|
raise ArgumentError, ":pageRanges must be an array of integers or strings"
|
84
87
|
end
|
85
88
|
end
|
@@ -18,6 +18,8 @@ module Bidi2pdf
|
|
18
18
|
require_relative "commands/browsing_context_close"
|
19
19
|
require_relative "commands/browsing_context_navigate"
|
20
20
|
require_relative "commands/browsing_context_print"
|
21
|
+
require_relative "commands/cdp_get_session"
|
22
|
+
require_relative "commands/page_print"
|
21
23
|
require_relative "commands/session_subscribe"
|
22
24
|
require_relative "commands/session_end"
|
23
25
|
require_relative "commands/cancel_auth"
|
@@ -17,6 +17,7 @@ module Bidi2pdf
|
|
17
17
|
@connection_latch.count_down
|
18
18
|
end
|
19
19
|
|
20
|
+
# rubocop:disable Naming/PredicateMethod
|
20
21
|
def wait_until_open(timeout:)
|
21
22
|
return true if @connected
|
22
23
|
|
@@ -26,6 +27,8 @@ module Bidi2pdf
|
|
26
27
|
|
27
28
|
true
|
28
29
|
end
|
30
|
+
|
31
|
+
# rubocop:enable Naming/PredicateMethod
|
29
32
|
end
|
30
33
|
end
|
31
34
|
end
|
@@ -32,7 +32,38 @@ module Bidi2pdf
|
|
32
32
|
SUBSCRIBE_EVENTS = %w[script].freeze
|
33
33
|
|
34
34
|
# Default Chrome arguments for the session.
|
35
|
-
DEFAULT_CHROME_ARGS =
|
35
|
+
DEFAULT_CHROME_ARGS = [
|
36
|
+
"--allow-pre-commit-input", # Allow pre-commit input for form fields
|
37
|
+
"--disable-dev-shm-usage", # Disable /dev/shm usage; use /tmp instead
|
38
|
+
"--disable-gpu", # Disable GPU hardware acceleration; force software rendering
|
39
|
+
"--disable-popup-blocking", # Allow all pop-ups; bypass built-in popup blocker
|
40
|
+
"--disable-hang-monitor", # Disable “Page Unresponsive” / “Aw, Snap!” dialogs on hangs
|
41
|
+
"--disable-background-networking", # Turn off speculative/periodic network requests (DNS prefetch, Safe Browsing updates, etc.)
|
42
|
+
"--disable-background-timer-throttling", # Prevent JS timers from being throttled in background tabs
|
43
|
+
"--disable-client-side-phishing-detection", # Disable built-in phishing checks; rely only on server-side detection
|
44
|
+
"--disable-component-extensions-with-background-pages", # Block component extensions that run persistent background pages (PDF viewer, Translate, etc.)
|
45
|
+
"--disable-crash-reporter", # Disable crash-report uploads and UI
|
46
|
+
"--disable-default-apps", # Stop installation of Chrome’s default apps on a fresh profile
|
47
|
+
"--disable-infobars", # Suppress “Chrome is being controlled by automated test software” infobar (and similar)
|
48
|
+
"--disable-ipc-flooding-protection", # Turn off defenses against too-many IPC messages from renderers
|
49
|
+
"--disable-prompt-on-repost", # Skip “Confirm Form Resubmission” dialogs on page reloads after POST
|
50
|
+
"--disable-renderer-backgrounding", # Keep background tab renderers at full priority
|
51
|
+
"--disable-search-engine-choice-screen", # Skip first-run search engine selection UI
|
52
|
+
"--disable-sync", # Turn off all Google account sync (bookmarks, passwords, etc.)
|
53
|
+
"--enable-automation", # Expose WebDriver hooks (navigator.webdriver) for automation frameworks
|
54
|
+
"--export-tagged-pdf", # When printing to PDF, include tagged structure for accessibility
|
55
|
+
"--force-color-profile=srgb", # Force rendering to use the sRGB color profile
|
56
|
+
"--generate-pdf-document-outline", # Auto-generate PDF bookmarks/outlines from HTML headings, not supported by chrome/chromium https://issues.chromium.org/issues/41387522#comment48
|
57
|
+
"--metrics-recording-only", # Collect UMA metrics locally but never upload them
|
58
|
+
"--no-first-run", # Skip the “Welcome” or “What’s New” screens on fresh profiles
|
59
|
+
"--password-store=basic", # Use Chrome’s basic (in-profile) password storage vs. OS vault
|
60
|
+
"--use-mock-keychain", # On macOS, use a fake keychain for testing (don’t touch the real one)
|
61
|
+
"--disable-backgrounding-occluded-windows", # Prevent fully-occluded windows from being treated as background
|
62
|
+
"--disable-breakpad", # Disable the Breakpad crash-reporting library entirely
|
63
|
+
"--enable-features=PdfOopif", # Enable out-of-process iframe (OOPIF) architecture for PDF rendering
|
64
|
+
"--disable-features=Translate,AcceptCHFrame,MediaRouter,OptimizationHints,ProcessPerSiteUpToMainFrameThreshold,IsolateSandboxedIframes",
|
65
|
+
"--disable-extensions about:blank"
|
66
|
+
].freeze
|
36
67
|
|
37
68
|
# @return [URI] The URI of the session.
|
38
69
|
attr_reader :session_uri
|
@@ -209,7 +240,7 @@ module Bidi2pdf
|
|
209
240
|
# @return [Hash] The session request payload.
|
210
241
|
def session_request
|
211
242
|
session_chrome_args = chrome_args.dup
|
212
|
-
session_chrome_args << "--headless" if @headless
|
243
|
+
session_chrome_args << "--headless=new" if @headless
|
213
244
|
|
214
245
|
{
|
215
246
|
"capabilities" => {
|