draft_forge 0.3.0 → 0.4.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 +4 -4
- data/README.md +10 -0
- data/app/jobs/draft_forge/export_pdf_job.rb +3 -45
- data/app/services/draft_forge/pdf_renderer.rb +61 -0
- data/lib/draft_forge/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 00ddff91bfa7f755bd68bf5f4a205892f5ec5bb270b98120b0dc0d11b0bc3e2b
|
4
|
+
data.tar.gz: 2a953c25d5b6cbdb75d48483670e7437849751856c031fd7a6abb08f1e3f71e7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: afcfcf35621e4e60f95d6432a04bdd22b255db91936875aacc9ae75cbee90e128138fe50a3398e4f11ee1e95f0624419dd44272fac8b2ea9340380ad8b667d0c
|
7
|
+
data.tar.gz: d6c0634575431f10a6fc2a1679208f1cf2c306ad9963e2ab643cf8d47b9c89a06ae8b75ba3542889a6b9c91dd1db026592eae2d3f5e70ddad5f73fbcd25e22da
|
data/README.md
CHANGED
@@ -45,6 +45,12 @@ DraftForge::FetchExport.call(export.id)
|
|
45
45
|
# => { id: 1, status: "queued" }
|
46
46
|
```
|
47
47
|
|
48
|
+
```ruby
|
49
|
+
# Render HTML directly to a PDF without queuing
|
50
|
+
file = DraftForge::PdfRenderer.call("<p>Hello</p>")
|
51
|
+
file.close!
|
52
|
+
```
|
53
|
+
|
48
54
|
## Configuration
|
49
55
|
|
50
56
|
`DraftForge` exposes simple configuration hooks for PDF rendering and HTML
|
@@ -64,6 +70,10 @@ DraftForge.configure do |config|
|
|
64
70
|
end
|
65
71
|
```
|
66
72
|
|
73
|
+
## Performance
|
74
|
+
|
75
|
+
`ExportPdfJob` streams generated PDFs to a temporary file before attaching, keeping memory usage low even for very large, 100+ page exports.
|
76
|
+
|
67
77
|
## Testing
|
68
78
|
|
69
79
|
Run the RSpec suite from this directory to verify changes:
|
@@ -1,5 +1,4 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
require 'grover'
|
3
2
|
|
4
3
|
module DraftForge
|
5
4
|
class ExportPdfJob < ActiveJob::Base
|
@@ -9,56 +8,15 @@ module DraftForge
|
|
9
8
|
export = Export.find(export_id)
|
10
9
|
export.update!(status: :processing)
|
11
10
|
|
12
|
-
|
13
|
-
html = wrap_html(safe_html)
|
14
|
-
|
15
|
-
pdf = Grover.new(html, DraftForge.pdf_options).to_pdf
|
11
|
+
tempfile = PdfRenderer.call(raw_html)
|
16
12
|
|
17
13
|
filename = export.requested_filename.presence || "document.pdf"
|
18
|
-
export.pdf.attach(io:
|
14
|
+
export.pdf.attach(io: tempfile, filename: filename, content_type: 'application/pdf')
|
15
|
+
tempfile.close!
|
19
16
|
export.update!(status: :complete)
|
20
17
|
rescue => e
|
21
18
|
Rails.logger.error("[DraftForge] Export failed: #{e.class}: #{e.message}")
|
22
19
|
export.update!(status: :failed) rescue nil
|
23
20
|
end
|
24
|
-
|
25
|
-
private
|
26
|
-
|
27
|
-
def wrap_html(body)
|
28
|
-
options = DraftForge.pdf_options
|
29
|
-
size = options[:format] || 'A4'
|
30
|
-
margin = options[:margin] || {}
|
31
|
-
top = margin[:top] || '20mm'
|
32
|
-
right = margin[:right] || '15mm'
|
33
|
-
bottom = margin[:bottom] || '20mm'
|
34
|
-
left = margin[:left] || '15mm'
|
35
|
-
|
36
|
-
<<~HTML
|
37
|
-
<!doctype html>
|
38
|
-
<html>
|
39
|
-
<head>
|
40
|
-
<meta charset="utf-8" />
|
41
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
42
|
-
<style>
|
43
|
-
@page { size: #{size}; margin: #{top} #{right} #{bottom} #{left}; }
|
44
|
-
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Helvetica Neue', Arial, 'Noto Sans', 'Liberation Sans', sans-serif; font-size: 12px; line-height: 1.45; color: #111; }
|
45
|
-
h1, h2, h3 { margin: 0 0 8px; }
|
46
|
-
h1 { font-size: 22px; }
|
47
|
-
h2 { font-size: 18px; }
|
48
|
-
h3 { font-size: 15px; }
|
49
|
-
p { margin: 0 0 8px; }
|
50
|
-
table { width: 100%; border-collapse: collapse; margin: 8px 0; }
|
51
|
-
th, td { border: 1px solid #ccc; padding: 6px 8px; vertical-align: top; }
|
52
|
-
img { max-width: 100%; height: auto; }
|
53
|
-
.page-break { page-break-after: always; }
|
54
|
-
.checklist li { list-style: none; }
|
55
|
-
</style>
|
56
|
-
</head>
|
57
|
-
<body>
|
58
|
-
#{body}
|
59
|
-
</body>
|
60
|
-
</html>
|
61
|
-
HTML
|
62
|
-
end
|
63
21
|
end
|
64
22
|
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'grover'
|
4
|
+
|
5
|
+
module DraftForge
|
6
|
+
class PdfRenderer
|
7
|
+
def self.call(raw_html)
|
8
|
+
safe_html = HtmlSanitizer.call(raw_html)
|
9
|
+
html = wrap_html(safe_html)
|
10
|
+
|
11
|
+
tempfile = Tempfile.new(["draft_forge", ".pdf"])
|
12
|
+
tempfile.binmode
|
13
|
+
begin
|
14
|
+
Grover.new(html, **DraftForge.pdf_options).to_pdf(path: tempfile.path)
|
15
|
+
tempfile.rewind
|
16
|
+
tempfile
|
17
|
+
rescue
|
18
|
+
tempfile.close!
|
19
|
+
raise
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.wrap_html(body)
|
24
|
+
options = DraftForge.pdf_options
|
25
|
+
size = options[:format] || 'A4'
|
26
|
+
margin = options[:margin] || {}
|
27
|
+
top = margin[:top] || '20mm'
|
28
|
+
right = margin[:right] || '15mm'
|
29
|
+
bottom = margin[:bottom] || '20mm'
|
30
|
+
left = margin[:left] || '15mm'
|
31
|
+
|
32
|
+
<<~HTML
|
33
|
+
<!doctype html>
|
34
|
+
<html>
|
35
|
+
<head>
|
36
|
+
<meta charset="utf-8" />
|
37
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
38
|
+
<style>
|
39
|
+
@page { size: #{size}; margin: #{top} #{right} #{bottom} #{left}; }
|
40
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Helvetica Neue', Arial, 'Noto Sans', 'Liberation Sans', sans-serif; font-size: 12px; line-height: 1.45; color: #111; }
|
41
|
+
h1, h2, h3 { margin: 0 0 8px; }
|
42
|
+
h1 { font-size: 22px; }
|
43
|
+
h2 { font-size: 18px; }
|
44
|
+
h3 { font-size: 15px; }
|
45
|
+
p { margin: 0 0 8px; }
|
46
|
+
table { width: 100%; border-collapse: collapse; margin: 8px 0; }
|
47
|
+
th, td { border: 1px solid #ccc; padding: 6px 8px; vertical-align: top; }
|
48
|
+
img { max-width: 100%; height: auto; }
|
49
|
+
.page-break { page-break-after: always; }
|
50
|
+
.checklist li { list-style: none; }
|
51
|
+
</style>
|
52
|
+
</head>
|
53
|
+
<body>
|
54
|
+
#{body}
|
55
|
+
</body>
|
56
|
+
</html>
|
57
|
+
HTML
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
data/lib/draft_forge/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: draft_forge
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mike Attara
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-08-
|
11
|
+
date: 2025-08-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -77,6 +77,7 @@ files:
|
|
77
77
|
- app/services/draft_forge/editor_js_renderer.rb
|
78
78
|
- app/services/draft_forge/fetch_export.rb
|
79
79
|
- app/services/draft_forge/html_sanitizer.rb
|
80
|
+
- app/services/draft_forge/pdf_renderer.rb
|
80
81
|
- config/routes.rb
|
81
82
|
- lib/draft_forge.rb
|
82
83
|
- lib/draft_forge/engine.rb
|