docpdf 0.1.5 → 0.1.6

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: 43cbb73b198ee9a4c207afbdf359ffa4ce79b2c66e372169049fe622cce4f562
4
- data.tar.gz: f08f6d9e7fd5b82a9b69f4e5402b94c4ab8a627c7468ad36373d6842458f2299
3
+ metadata.gz: b68eda5d7355d1d3a227ef71f44d41f95dc7ccbfe7b08af17739e629c9ab26dc
4
+ data.tar.gz: 23e0ab911eb89df49eeac3634b50ef6d8bf8f7b74a8861067e2a0e44f3946e82
5
5
  SHA512:
6
- metadata.gz: 3612856e07798bbee41cd5a2dea00dd21ea67aadd2b4c8412fe2afd4c13dc9f91a334833373ba8f099b7bae1392e6756736da848085a2f2e3a3a04ef962568f0
7
- data.tar.gz: 53b3025fab9d38dc1bf0d8b960b42088d49b73e4a05f79b6025b18bb9f912bde8da2d0dfacf571bc2b11f12e6c47b9266ef3f30d637e4c1fe1ae59ec937699e4
6
+ metadata.gz: 14aff5944d6ac087f241b078c557e480974f322ca2cbcf3c9d08136e6b37f6598ca1bbd17e14267b060632e83e54491dbb5dc6a42b15768eb64883bbe3fd7838
7
+ data.tar.gz: cb43492ce6e3b7257e3c6cc01281904c509dcb473f77e932361ae986f0f39cda9af3bc0a4080b2e8c7d24e2c13adf6d4a8b93230ed455eef6b6ed74ede98e301
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.6] - 2026-09-18
4
+
5
+ ### Added
6
+ - `font_file` registers a TrueType font for text conversion (`config.text_options`) and text watermarks (`config.watermark_options`, or per stamp). PDF's built-in fonts are limited to Windows-1252, so Cyrillic, Greek and other non-Latin text previously raised `ConversionError` with no way around it. Takes a path for the regular weight or a hash of `normal`/`bold`/`italic`/`bold_italic`, and is supported by both text converters and both stampers.
7
+
3
8
  ## [0.1.5] - 2026-09-18
4
9
 
5
10
  ### Added
data/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
  Convert documents from any common format to PDF, with optional watermarking. Zero hard dependencies; bring your own PDF library.
10
10
 
11
11
  [![CI](https://github.com/velocity-labs/docpdf/actions/workflows/ci.yml/badge.svg)](https://github.com/velocity-labs/docpdf/actions/workflows/ci.yml)
12
- [![Gem Version](https://badge.fury.io/rb/docpdf.svg)](https://rubygems.org/gems/docpdf)
12
+ [![Gem Version](https://img.shields.io/gem/v/docpdf?label=gem%20version&color=D3AF37&cacheSeconds=3600)](https://rubygems.org/gems/docpdf)
13
13
 
14
14
  <br clear="left">
15
15
 
@@ -208,6 +208,7 @@ DocPDF.configure do |config|
208
208
  # Plain text file conversion defaults
209
209
  config.text_options = {
210
210
  font: "Helvetica", # default: "Courier"
211
+ font_file: nil, # default: nil (see "Non-Latin text" below)
211
212
  font_size: 12, # default: 10
212
213
  margins: [72, 72, 72, 72], # default: [50, 50, 50, 50] (points: top, right, bottom, left)
213
214
  color: "000000", # default: "333333" (hex)
@@ -216,6 +217,7 @@ DocPDF.configure do |config|
216
217
  # Text watermark defaults (per-stamp options override these)
217
218
  config.watermark_options = {
218
219
  font: "Times", # default: "Helvetica"
220
+ font_file: nil, # default: nil (see "Non-Latin text" below)
219
221
  font_size: 96, # default: 72
220
222
  color: "FF0000", # default: "AAAAAA" (hex)
221
223
  rotation: 30, # default: 45 (degrees counter-clockwise)
@@ -231,6 +233,43 @@ Converter adapters (for format-to-PDF conversion) are auto-detected based on MIM
231
233
  - **application/pdf**: Passthrough (returned unchanged)
232
234
  - **Unknown formats**: Fallback (tries LibreOffice, then returns raw data)
233
235
 
236
+ ### Non-Latin text
237
+
238
+ PDF's built-in fonts only cover the Windows-1252 character set, so text outside it
239
+ (Cyrillic, Greek, CJK, emoji, and many accented forms) cannot be rendered and raises
240
+ `DocPDF::ConversionError`. Point `font_file` at a TrueType font to lift that limit:
241
+
242
+ ```ruby
243
+ DocPDF.configure do |config|
244
+ config.text_options = config.text_options.merge(
245
+ font: "DejaVuSans",
246
+ font_file: "/path/to/DejaVuSans.ttf"
247
+ )
248
+ end
249
+
250
+ DocPDF.convert("resume.txt") # renders Cyrillic, Greek, and more
251
+ ```
252
+
253
+ `font_file` takes a path for the regular weight, or a hash to register several styles:
254
+
255
+ ```ruby
256
+ font_file: {
257
+ normal: "/fonts/DejaVuSans.ttf",
258
+ bold: "/fonts/DejaVuSans-Bold.ttf",
259
+ italic: "/fonts/DejaVuSans-Oblique.ttf",
260
+ bold_italic: "/fonts/DejaVuSans-BoldOblique.ttf"
261
+ }
262
+ ```
263
+
264
+ The same option works for text watermarks, via `config.watermark_options` or per stamp:
265
+
266
+ ```ruby
267
+ DocPDF.watermark("doc.pdf", { text: "ЧЕРНОВИК", font: "DejaVuSans", font_file: "/fonts/DejaVuSans.ttf" })
268
+ ```
269
+
270
+ A font only renders the characters it actually contains. DejaVu covers Latin, Cyrillic
271
+ and Greek but not CJK, so pick a font that covers the scripts you expect.
272
+
234
273
  ## Adapters
235
274
 
236
275
  DocPDF has two types of adapters:
@@ -1,5 +1,6 @@
1
1
  require "hexapdf"
2
2
  require_relative "base"
3
+ require_relative "../../font_files"
3
4
 
4
5
  module DocPDF
5
6
  module Adapters
@@ -22,6 +23,7 @@ module DocPDF
22
23
  page_size = normalize_page_size(config.page_size)
23
24
 
24
25
  doc = HexaPDF::Document.new
26
+ register_font(doc, opts[:font], opts[:font_file])
25
27
  page = doc.pages.add(page_size)
26
28
  canvas = page.canvas
27
29
 
@@ -51,6 +53,13 @@ module DocPDF
51
53
 
52
54
  private
53
55
 
56
+ def register_font(doc, name, font_file)
57
+ map = FontFiles.for_hexapdf(font_file)
58
+ return unless map
59
+
60
+ doc.config["font.map"] = (doc.config["font.map"] || {}).merge(name => map)
61
+ end
62
+
54
63
  def normalize_page_size(size)
55
64
  return size if size.is_a?(Symbol)
56
65
 
@@ -1,6 +1,7 @@
1
1
  require "prawn"
2
2
  require_relative "base"
3
3
  require_relative "../../prawn_errors"
4
+ require_relative "../../font_files"
4
5
 
5
6
  module DocPDF
6
7
  module Adapters
@@ -20,6 +21,7 @@ module DocPDF
20
21
  opts = config.text_options
21
22
  content = normalize(data)
22
23
  pdf = ::Prawn::Document.new(page_size: config.page_size, margin: opts[:margins])
24
+ register_font(pdf, opts[:font], opts[:font_file])
23
25
  pdf.font(opts[:font], size: opts[:font_size])
24
26
  pdf.text content, color: opts[:color]
25
27
  pdf.render
@@ -29,6 +31,13 @@ module DocPDF
29
31
 
30
32
  private
31
33
 
34
+ def register_font(pdf, name, font_file)
35
+ families = FontFiles.for_prawn(font_file)
36
+ return unless families
37
+
38
+ pdf.font_families.update(name => families)
39
+ end
40
+
32
41
  def normalize(data)
33
42
  data.dup.force_encoding("UTF-8").gsub(LINE_SEPARATORS, "\n")
34
43
  end
@@ -2,6 +2,7 @@ require "combine_pdf"
2
2
  require "prawn"
3
3
  require_relative "base"
4
4
  require_relative "../../prawn_errors"
5
+ require_relative "../../font_files"
5
6
 
6
7
  module DocPDF
7
8
  module Adapters
@@ -62,6 +63,13 @@ module DocPDF
62
63
  [img_w, img_h]
63
64
  end
64
65
 
66
+ def register_font(pdf, name, font_file)
67
+ families = FontFiles.for_prawn(font_file)
68
+ return unless families
69
+
70
+ pdf.font_families.update(name => families)
71
+ end
72
+
65
73
  def render_image_stamp(pdf, stamp)
66
74
  page_w = pdf.bounds.width
67
75
  page_h = pdf.bounds.height
@@ -88,6 +96,7 @@ module DocPDF
88
96
  page_w = pdf.bounds.width
89
97
  page_h = pdf.bounds.height
90
98
 
99
+ register_font(pdf, stamp[:font], stamp[:font_file])
91
100
  pdf.font(stamp[:font])
92
101
  font_size = fit_font_size(stamp[:font_size], stamp[:text], pdf, page_w, page_h)
93
102
  text_w = pdf.width_of(stamp[:text], size: font_size)
@@ -1,5 +1,6 @@
1
1
  require "hexapdf"
2
2
  require_relative "base"
3
+ require_relative "../../font_files"
3
4
 
4
5
  module DocPDF
5
6
  module Adapters
@@ -88,6 +89,7 @@ module DocPDF
88
89
  page_w = page.box.width
89
90
  page_h = page.box.height
90
91
 
92
+ register_font(doc, stamp[:font], stamp[:font_file])
91
93
  font = doc.fonts.add(stamp[:font])
92
94
  glyph_units = font.decode_utf8(stamp[:text]).sum { |g| g.width }
93
95
  font_size = fit_font_size(stamp[:font_size], glyph_units, page_w, page_h)
@@ -116,6 +118,13 @@ module DocPDF
116
118
  end
117
119
  end
118
120
 
121
+ def register_font(doc, name, font_file)
122
+ map = FontFiles.for_hexapdf(font_file)
123
+ return unless map
124
+
125
+ doc.config["font.map"] = (doc.config["font.map"] || {}).merge(name => map)
126
+ end
127
+
119
128
  def write_to_string(doc)
120
129
  io = StringIO.new
121
130
  doc.write(io)
@@ -9,6 +9,7 @@ module DocPDF
9
9
 
10
10
  TEXT_DEFAULTS = {
11
11
  font: "Courier",
12
+ font_file: nil,
12
13
  font_size: 10,
13
14
  margins: [50, 50, 50, 50],
14
15
  color: "333333",
@@ -16,6 +17,7 @@ module DocPDF
16
17
 
17
18
  WATERMARK_DEFAULTS = {
18
19
  font: "Helvetica",
20
+ font_file: nil,
19
21
  font_size: 72,
20
22
  color: "AAAAAA",
21
23
  rotation: 45,
@@ -0,0 +1,35 @@
1
+ module DocPDF
2
+ # Prawn's built-in AFM fonts only cover Windows-1252, so any text outside it
3
+ # fails to render. Pointing an adapter at a TrueType file lifts that limit.
4
+ # Each backend wants the styles keyed differently, so normalize once here.
5
+ module FontFiles
6
+ STYLES = %i[normal bold italic bold_italic].freeze
7
+
8
+ # HexaPDF calls the unstyled variant :none where Prawn calls it :normal.
9
+ HEXAPDF_STYLES = { normal: :none, bold: :bold, italic: :italic, bold_italic: :bold_italic }.freeze
10
+
11
+ class << self
12
+ # Accepts a path for the regular weight, or a hash of styles:
13
+ # "DejaVuSans.ttf"
14
+ # { normal: "...", bold: "...", italic: "...", bold_italic: "..." }
15
+ def normalize(font_file)
16
+ return if font_file.nil?
17
+ return { normal: font_file.to_s } if font_file.is_a?(String) || font_file.is_a?(Pathname)
18
+
19
+ styles = font_file.to_h.transform_keys(&:to_sym).slice(*STYLES)
20
+ styles.empty? ? nil : styles.transform_values(&:to_s)
21
+ end
22
+
23
+ def for_hexapdf(font_file)
24
+ styles = normalize(font_file)
25
+ return unless styles
26
+
27
+ styles.each_with_object({}) { |(style, path), map| map[HEXAPDF_STYLES.fetch(style)] = path }
28
+ end
29
+
30
+ def for_prawn(font_file)
31
+ normalize(font_file)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -1,3 +1,3 @@
1
1
  module DocPDF
2
- VERSION = "0.1.5"
2
+ VERSION = "0.1.6"
3
3
  end
@@ -46,7 +46,7 @@ module DocPDF
46
46
  merged = STAMP_DEFAULTS.merge(stamp)
47
47
  if merged[:text]
48
48
  wm = DocPDF.configuration.watermark_options
49
- { font: wm[:font], font_size: wm[:font_size], color: wm[:color], rotation: wm[:rotation] }.merge(merged)
49
+ { font: wm[:font], font_file: wm[:font_file], font_size: wm[:font_size], color: wm[:color], rotation: wm[:rotation] }.merge(merged)
50
50
  else
51
51
  merged
52
52
  end
data/lib/docpdf.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require "docpdf/version"
2
2
  require "docpdf/configuration"
3
3
  require "docpdf/errors"
4
+ require "docpdf/font_files"
4
5
  require "docpdf/result"
5
6
  require "docpdf/input_normalizer"
6
7
  require "docpdf/mime_detector"
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.5
4
+ version: 0.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Velocity Labs, LLC
@@ -38,6 +38,7 @@ files:
38
38
  - lib/docpdf/converter.rb
39
39
  - lib/docpdf/converter_resolver.rb
40
40
  - lib/docpdf/errors.rb
41
+ - lib/docpdf/font_files.rb
41
42
  - lib/docpdf/input_normalizer.rb
42
43
  - lib/docpdf/mime_detector.rb
43
44
  - lib/docpdf/prawn_errors.rb