docpdf 0.1.4 → 0.1.5
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 +10 -0
- data/README.md +8 -3
- data/lib/docpdf/adapters/converters/prawn.rb +15 -2
- data/lib/docpdf/adapters/stampers/combine_pdf.rb +3 -1
- data/lib/docpdf/adapters/stampers/hexapdf.rb +2 -1
- data/lib/docpdf/configuration.rb +2 -0
- data/lib/docpdf/converter_resolver.rb +34 -4
- data/lib/docpdf/prawn_errors.rb +14 -0
- data/lib/docpdf/version.rb +1 -1
- data/lib/docpdf/watermarker.rb +2 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 43cbb73b198ee9a4c207afbdf359ffa4ce79b2c66e372169049fe622cce4f562
|
|
4
|
+
data.tar.gz: f08f6d9e7fd5b82a9b69f4e5402b94c4ab8a627c7468ad36373d6842458f2299
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3612856e07798bbee41cd5a2dea00dd21ea67aadd2b4c8412fe2afd4c13dc9f91a334833373ba8f099b7bae1392e6756736da848085a2f2e3a3a04ef962568f0
|
|
7
|
+
data.tar.gz: 53b3025fab9d38dc1bf0d8b960b42088d49b73e4a05f79b6025b18bb9f912bde8da2d0dfacf571bc2b11f12e6c47b9266ef3f30d637e4c1fe1ae59ec937699e4
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.1.5] - 2026-09-18
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- `config.converter` selects a preferred converter adapter, mirroring `config.stamper`. It only claims the mime types that adapter registered for, so everything else still resolves in registration order and `config.converter = :hexapdf` does not disturb Word or image conversion.
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
- Watermarking a structurally valid PDF with an empty page tree raised `NoMethodError` instead of a `DocPDF::ConversionError`. Both stampers and the per-page watermark path now raise `ConversionError` with a clear message.
|
|
10
|
+
- Plain text containing U+2028 LINE SEPARATOR or U+2029 PARAGRAPH SEPARATOR crashed the Prawn converter, since Windows-1252 has no room for them. Both are now translated to a newline, which is what they mean, so text pasted out of word processors and web pages converts cleanly.
|
|
11
|
+
- Adapters no longer leak third-party exceptions to callers rescuing `DocPDF::Error`. The Prawn converter wraps every `Prawn::Errors` class plus `Encoding::UndefinedConversionError`, and both stampers wrap a missing watermark image file.
|
|
12
|
+
|
|
3
13
|
## [0.1.4] - 2026-04-08
|
|
4
14
|
|
|
5
15
|
### Fixed
|
data/README.md
CHANGED
|
@@ -197,6 +197,11 @@ DocPDF.configure do |config|
|
|
|
197
197
|
# Stamper adapter for watermarking (default: nil, auto-detects hexapdf then combine_pdf)
|
|
198
198
|
config.stamper = :hexapdf # or :combine_pdf
|
|
199
199
|
|
|
200
|
+
# Preferred converter adapter (default: nil, uses registration order)
|
|
201
|
+
# Only applies to the MIME types that adapter registered for; everything else
|
|
202
|
+
# still resolves normally, so this will not affect Word or image conversion.
|
|
203
|
+
config.converter = :hexapdf # or :prawn, :rmagick, :mini_magick, ...
|
|
204
|
+
|
|
200
205
|
# Page size (default: "LETTER")
|
|
201
206
|
config.page_size = "A4"
|
|
202
207
|
|
|
@@ -220,7 +225,7 @@ end
|
|
|
220
225
|
|
|
221
226
|
Converter adapters (for format-to-PDF conversion) are auto-detected based on MIME type and gem availability. The first available adapter wins, in registration order:
|
|
222
227
|
|
|
223
|
-
- **text/plain**: Prawn, then HexaPDF
|
|
228
|
+
- **text/plain**: Prawn, then HexaPDF (set `config.converter` to reverse this)
|
|
224
229
|
- **image/\***: RMagick, then MiniMagick
|
|
225
230
|
- **Office formats**: LibreOffice (always available if installed)
|
|
226
231
|
- **application/pdf**: Passthrough (returned unchanged)
|
|
@@ -235,8 +240,8 @@ DocPDF has two types of adapters:
|
|
|
235
240
|
| Adapter | Gem | Formats |
|
|
236
241
|
|---------|-----|---------|
|
|
237
242
|
| Soffice | None (system) | Word, Excel, PowerPoint, ODF, CSV, HTML, RTF |
|
|
238
|
-
| Prawn | `prawn` | Plain text |
|
|
239
|
-
| HexaPDF | `hexapdf` | Plain text |
|
|
243
|
+
| Prawn | `prawn` | Plain text (wraps long lines; built-in fonts are Windows-1252 only) |
|
|
244
|
+
| HexaPDF | `hexapdf` | Plain text (wider character support; does not wrap long lines) |
|
|
240
245
|
| RMagick | `rmagick` | JPEG, PNG, HEIC, WebP |
|
|
241
246
|
| MiniMagick | `mini_magick` | JPEG, PNG, HEIC, WebP |
|
|
242
247
|
| Passthrough | None | PDF (returned unchanged) |
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
require "prawn"
|
|
2
2
|
require_relative "base"
|
|
3
|
+
require_relative "../../prawn_errors"
|
|
3
4
|
|
|
4
5
|
module DocPDF
|
|
5
6
|
module Adapters
|
|
@@ -7,18 +8,30 @@ module DocPDF
|
|
|
7
8
|
class Prawn < Base
|
|
8
9
|
MIME_TYPES = %w[text/plain].freeze
|
|
9
10
|
|
|
11
|
+
# U+2028 LINE SEPARATOR and U+2029 PARAGRAPH SEPARATOR mean "break the
|
|
12
|
+
# line", but Windows-1252 has no room for them, so Prawn's built-in fonts
|
|
13
|
+
# reject them. Text pasted out of word processors and web pages carries
|
|
14
|
+
# them routinely, so translate to a newline instead of failing on it.
|
|
15
|
+
LINE_SEPARATORS = /[
]/
|
|
16
|
+
|
|
10
17
|
class << self
|
|
11
18
|
def convert(data, _source_filename)
|
|
12
19
|
config = DocPDF.configuration
|
|
13
20
|
opts = config.text_options
|
|
14
|
-
content = data
|
|
21
|
+
content = normalize(data)
|
|
15
22
|
pdf = ::Prawn::Document.new(page_size: config.page_size, margin: opts[:margins])
|
|
16
23
|
pdf.font(opts[:font], size: opts[:font_size])
|
|
17
24
|
pdf.text content, color: opts[:color]
|
|
18
25
|
pdf.render
|
|
19
|
-
rescue
|
|
26
|
+
rescue *PRAWN_ERRORS, ::Encoding::UndefinedConversionError => e
|
|
20
27
|
raise ConversionError, "Prawn failed to render text to PDF: #{e.message}"
|
|
21
28
|
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def normalize(data)
|
|
33
|
+
data.dup.force_encoding("UTF-8").gsub(LINE_SEPARATORS, "\n")
|
|
34
|
+
end
|
|
22
35
|
end
|
|
23
36
|
end
|
|
24
37
|
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
require "combine_pdf"
|
|
2
2
|
require "prawn"
|
|
3
3
|
require_relative "base"
|
|
4
|
+
require_relative "../../prawn_errors"
|
|
4
5
|
|
|
5
6
|
module DocPDF
|
|
6
7
|
module Adapters
|
|
@@ -10,6 +11,7 @@ module DocPDF
|
|
|
10
11
|
def stamp(data, stamps, page_indices: nil)
|
|
11
12
|
source = ::CombinePDF.parse(data)
|
|
12
13
|
source_page = source.pages.first
|
|
14
|
+
raise ConversionError, "Cannot stamp a PDF with no pages" unless source_page
|
|
13
15
|
page_w = source_page[:MediaBox][2].to_f
|
|
14
16
|
page_h = source_page[:MediaBox][3].to_f
|
|
15
17
|
|
|
@@ -20,7 +22,7 @@ module DocPDF
|
|
|
20
22
|
page << stamp_page if page_indices.nil? || page_indices.include?(idx)
|
|
21
23
|
end
|
|
22
24
|
source.to_pdf
|
|
23
|
-
rescue ::CombinePDF::ParsingError => e
|
|
25
|
+
rescue ::CombinePDF::ParsingError, *PRAWN_ERRORS, Errno::ENOENT => e
|
|
24
26
|
raise ConversionError, "CombinePDF failed to stamp PDF: #{e.message}"
|
|
25
27
|
end
|
|
26
28
|
|
|
@@ -9,6 +9,7 @@ module DocPDF
|
|
|
9
9
|
def stamp(data, stamps, page_indices: nil)
|
|
10
10
|
doc = HexaPDF::Document.new(io: StringIO.new(data))
|
|
11
11
|
source_page = doc.pages[0]
|
|
12
|
+
raise ConversionError, "Cannot stamp a PDF with no pages" unless source_page
|
|
12
13
|
page_w = source_page.box.width
|
|
13
14
|
page_h = source_page.box.height
|
|
14
15
|
|
|
@@ -21,7 +22,7 @@ module DocPDF
|
|
|
21
22
|
end
|
|
22
23
|
|
|
23
24
|
write_to_string(doc)
|
|
24
|
-
rescue HexaPDF::Error => e
|
|
25
|
+
rescue HexaPDF::Error, Errno::ENOENT => e
|
|
25
26
|
raise ConversionError, "HexaPDF failed to stamp PDF: #{e.message}"
|
|
26
27
|
end
|
|
27
28
|
|
data/lib/docpdf/configuration.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
module DocPDF
|
|
2
2
|
class Configuration
|
|
3
3
|
attr_accessor :soffice_path,
|
|
4
|
+
:converter,
|
|
4
5
|
:stamper,
|
|
5
6
|
:page_size,
|
|
6
7
|
:text_options,
|
|
@@ -22,6 +23,7 @@ module DocPDF
|
|
|
22
23
|
|
|
23
24
|
def initialize
|
|
24
25
|
@soffice_path = "soffice"
|
|
26
|
+
@converter = nil
|
|
25
27
|
@stamper = nil
|
|
26
28
|
@page_size = "LETTER"
|
|
27
29
|
@text_options = TEXT_DEFAULTS.dup
|
|
@@ -8,12 +8,44 @@ module DocPDF
|
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
def resolve(mime_type)
|
|
11
|
+
configured = DocPDF.configuration.converter
|
|
12
|
+
return resolve_configured(configured, mime_type) if configured
|
|
13
|
+
|
|
14
|
+
resolve_registered(mime_type)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def load_adapter(entry)
|
|
20
|
+
require entry[:require_name] if entry[:require_name]
|
|
21
|
+
entry[:loader].call
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# A configured converter only claims the mime types it registered for, so
|
|
25
|
+
# anything else still falls through to the normal registration order. That
|
|
26
|
+
# keeps config.converter = :hexapdf from breaking Word conversion.
|
|
27
|
+
def resolve_configured(name, mime_type)
|
|
28
|
+
entry = @adapters.find { |e| e[:name] == name }
|
|
29
|
+
unless entry
|
|
30
|
+
valid_names = @adapters.map { |e| e[:name].inspect }.join(", ")
|
|
31
|
+
raise AdapterNotFoundError, "Unknown converter: #{name.inspect}. Valid converters are: #{valid_names}."
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
return resolve_registered(mime_type) unless entry[:mime_types].include?(mime_type)
|
|
35
|
+
|
|
36
|
+
begin
|
|
37
|
+
load_adapter(entry)
|
|
38
|
+
rescue LoadError => e
|
|
39
|
+
raise AdapterNotFoundError, "Converter #{name.inspect} requires gems that are not installed: #{e.message}"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def resolve_registered(mime_type)
|
|
11
44
|
missing_gems = []
|
|
12
45
|
|
|
13
46
|
@adapters.each do |entry|
|
|
14
47
|
next unless entry[:mime_types].include?(mime_type)
|
|
15
|
-
|
|
16
|
-
return entry[:loader].call
|
|
48
|
+
return load_adapter(entry)
|
|
17
49
|
rescue LoadError
|
|
18
50
|
missing_gems << entry[:require_name]
|
|
19
51
|
next
|
|
@@ -27,8 +59,6 @@ module DocPDF
|
|
|
27
59
|
resolve_fallback
|
|
28
60
|
end
|
|
29
61
|
|
|
30
|
-
private
|
|
31
|
-
|
|
32
62
|
def resolve_fallback
|
|
33
63
|
entry = @adapters.find { |e| e[:name] == :fallback }
|
|
34
64
|
raise AdapterNotFoundError, "No converter found and no fallback registered." unless entry
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
require "prawn"
|
|
2
|
+
|
|
3
|
+
module DocPDF
|
|
4
|
+
# Prawn defines every error class directly under StandardError with no shared
|
|
5
|
+
# ancestor, so there is no namespace to rescue. Collecting them lets adapters
|
|
6
|
+
# wrap the whole family in DocPDF::ConversionError rather than leaking Prawn's
|
|
7
|
+
# exceptions to callers who only rescue DocPDF::Error.
|
|
8
|
+
#
|
|
9
|
+
# Only require this from files that already require "prawn"; prawn is optional.
|
|
10
|
+
PRAWN_ERRORS = ::Prawn::Errors.constants
|
|
11
|
+
.map { |name| ::Prawn::Errors.const_get(name) }
|
|
12
|
+
.select { |const| const.is_a?(Class) && const <= StandardError }
|
|
13
|
+
.freeze
|
|
14
|
+
end
|
data/lib/docpdf/version.rb
CHANGED
data/lib/docpdf/watermarker.rb
CHANGED
|
@@ -138,6 +138,8 @@ module DocPDF
|
|
|
138
138
|
|
|
139
139
|
def stamp_per_page(stamper)
|
|
140
140
|
page_count = pdf_page_count
|
|
141
|
+
raise ConversionError, "Cannot stamp a PDF with no pages" if page_count.zero?
|
|
142
|
+
|
|
141
143
|
result = @pdf_bytes
|
|
142
144
|
|
|
143
145
|
build_page_stamp_map(page_count).each do |page_indices, stamps_for_pages|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: docpdf
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Velocity Labs, LLC
|
|
@@ -40,6 +40,7 @@ files:
|
|
|
40
40
|
- lib/docpdf/errors.rb
|
|
41
41
|
- lib/docpdf/input_normalizer.rb
|
|
42
42
|
- lib/docpdf/mime_detector.rb
|
|
43
|
+
- lib/docpdf/prawn_errors.rb
|
|
43
44
|
- lib/docpdf/result.rb
|
|
44
45
|
- lib/docpdf/stamper_resolver.rb
|
|
45
46
|
- lib/docpdf/version.rb
|