rustpdf 0.4.6-x86_64-linux → 0.4.8-x86_64-linux

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: af49bee0cf9eba571ac93944eb7af3bf9a37f10fbefa223e2a3a65ab3631b6c5
4
- data.tar.gz: d7f93ac16ea468d072531ceeb7da1e29ecc48ac3e5c42865d26bd4421ce420e6
3
+ metadata.gz: 891a60b238e8fbe4f141d0252c180360841e60cdf54b3d83e3e6a81fafcca973
4
+ data.tar.gz: fbcdf3fac905a97922ca51881d7b187a8ea8bdaea121c97bfd230febff282fed
5
5
  SHA512:
6
- metadata.gz: ff093097b85d89788189953ca39e7646e7f60965b668678f7e4ca14e37fe8a1887d2832d37bec48140a47a2dec10c70f440f630ee2fb5cab1eb8c41eb5d6f8a1
7
- data.tar.gz: ca2523be2593cdf116728a03940c73a62707baf7404e4e153da397cbb3f2c2b0cadd7e5865dd533d1edd99e794d792d1bce8833fb1521f1901513f1a5d18b3d2
6
+ metadata.gz: 0a87dc46b070cb6e73d6f54d364b4765bc59b698370da60c5ca3e3a9b32eae1e8237a45f865fb5c7bee6e1b1bcc79d2cc731e996e9078e750ec31a11678c7f4f
7
+ data.tar.gz: 9f25f278475388e7c76f6a34cb424d07f7a4f3a9207c572137b82c16ffd1589a8de3909537fc2d342e85e2eac699ef82839af2b834a499b2d2ea3bc342f6288d
data/README.md CHANGED
@@ -1,21 +1,16 @@
1
- # rustpdf (Ruby binding)
2
-
3
- Idiomatic Ruby binding for the `rust-pdf` core over its C ABI (`libpdf_ffi`),
4
- using the built-in **Fiddle** standard library — no native gem to compile. It
5
- covers the whole product surface: vector graphics, embedded/subsetted fonts and
6
- text, wrapping paragraphs, images, **PDF/A** (levels 1b–3a),
7
- **tagged/accessible** output, embedded-file attachments, **AcroForm** fields,
8
- manipulation (merge/split/rotate/optimize/incremental update), **text
9
- extraction**, **page rendering** (page to PNG image), **encryption** (RC4 / AES-128 / AES-256) and **digital signatures**
10
- (PKCS#7 / PAdES) — plus **feature licensing**.
11
-
12
- Files (module `RustPdf`):
13
-
14
- * `lib/rustpdf.rb` — module functions (`version`, `activate_license`,
15
- `extract_text`, `sign`, `timestamp`, `add_dss`), enums, error, helpers;
16
- * `lib/rustpdf/native.rb` — the Fiddle signature table + loader;
17
- * `lib/rustpdf/document.rb`, `editable_doc.rb` — the `Document` / `EditableDoc`
18
- classes.
1
+ # RustPdf for Ruby
2
+
3
+ Generate, edit, sign and process PDFs from Ruby: vector graphics, embedded fonts and Unicode text, wrapping paragraphs, images, **PDF/A** (1b-4f), **tagged/accessible** output, attachments, **AcroForm** fields, page manipulation (merge/split/stamp), watermarks, true **redaction**, **AES-256** encryption, **digital signatures (PAdES)** with HSM/deferred signing, timestamps/LTV, text extraction and search, and page **rendering to PNG**. Pure Ruby gem (stdlib Fiddle), no native extension to compile.
4
+
5
+ ## Documentation
6
+
7
+ - **Full API reference:** https://rustpdf.dev/docs/ruby
8
+ - **Interactive positioning guide** (coordinates, anchors, rotation): https://rustpdf.dev/positioning
9
+ - All product guides (PDF/A, signatures, encryption, redaction, rendering): https://rustpdf.dev/docs/
10
+
11
+ The public API is `RustPdf::Document` (create PDFs), `RustPdf::EditableDoc`
12
+ (load and edit existing PDFs) and module functions (`version`,
13
+ `activate_license`, `extract_text`, `sign`, `timestamp`, `add_dss`).
19
14
 
20
15
  ## Loading the native library
21
16
 
@@ -163,41 +163,130 @@ module RustPdf
163
163
  found != 0
164
164
  end
165
165
 
166
- # Draw a line of positioned +text+ with baseline at (+x+, +y+) on page
167
- # +page_index+ (0-based), using standard Helvetica at +size+ points in RGB
166
+ # Register a TrueType/OpenType font (from a file path) for text stamping;
167
+ # returns a font id usable with the +font_id+ keyword of #place_text /
168
+ # #masked_text / #place_paragraph. The font is embedded as a subset —
169
+ # stamped text renders with the real font's glyphs and metrics.
170
+ def add_font_file(path)
171
+ RustPdf.out_int { |buf| Native.call("pdf_editable_add_font_file", ptr, path, buf) }
172
+ end
173
+
174
+ # Register a stamping font from raw TrueType/OpenType bytes. See
175
+ # #add_font_file.
176
+ def add_font(data)
177
+ RustPdf.out_int { |buf| Native.call("pdf_editable_add_font", ptr, data, data.bytesize, buf) }
178
+ end
179
+
180
+ # Draw a line of positioned +text+ anchored at (+x+, +y+) on page
181
+ # +page_index+ (0-based), using standard Helvetica (or an embedded font —
182
+ # pass +font_id+ from #add_font_file / #add_font) at +size+ points in RGB
168
183
  # +color+ (each 0..1, default black). +rotation_deg+ rotates the text
169
184
  # counter-clockwise about its anchor (match the page rotation to follow a
170
185
  # rotated page). +align+ (RustPdf::Align, default LEFT) shifts the start
171
186
  # point along the baseline direction by the text width for RIGHT/CENTER
172
- # alignment. Coordinates are in the page's VISIBLE space (origin lower-left,
173
- # y up), regardless of the page's /Rotate. Returns whether the page existed.
187
+ # alignment. +anchor+ (RustPdf::VerticalAnchor) says what +y+ means:
188
+ # BASELINE (default, historical behavior), TOP (text hangs from +y+
189
+ # baseline lands ascent x size below it, legacy fixed-position layout), BOTTOM
190
+ # (descender line rests on +y+), or LINE_TOP / LINE_BOTTOM (legacy layout engines line
191
+ # box). Coordinates are in the page's VISIBLE space (origin lower-left,
192
+ # y up) unless #stamp_space= chose MEDIA. Returns whether the page (and
193
+ # font) existed.
174
194
  def place_text(page_index, x, y, text, size = 12.0, color = [0.0, 0.0, 0.0], rotation_deg = 0.0,
175
- align: Align::LEFT)
195
+ align: Align::LEFT, font_id: -1, anchor: VerticalAnchor::BASELINE)
176
196
  r, g, b = color
177
197
  found = RustPdf.out_int do |buf|
178
- Native.call("pdf_editable_place_text_aligned", ptr, page_index, x.to_f, y.to_f, text,
179
- size.to_f, r.to_f, g.to_f, b.to_f, rotation_deg.to_f, align, buf)
198
+ Native.call("pdf_editable_place_text_anchored", ptr, page_index, x.to_f, y.to_f, text,
199
+ size.to_f, r.to_f, g.to_f, b.to_f, rotation_deg.to_f, align, anchor, font_id, buf)
180
200
  end
181
201
  found != 0
182
202
  end
183
203
 
204
+ # Choose the coordinate space of the positioned stamping primitives
205
+ # (#fill_rect, #place_text, #masked_text, #place_paragraph, #draw_image)
206
+ # for subsequent calls. StampSpace::VISIBLE (default) keeps the historical
207
+ # behavior — coordinates in the page's displayed space, compensating
208
+ # /Rotate. StampSpace::MEDIA interprets coordinates and +rotation_deg+ in
209
+ # the raw PDF user space (legacy layout semantics), never composing with the
210
+ # page's /Rotate — use it to reproduce legacy-engine placement on rotated/scanned
211
+ # pages. Watermarks and redaction are unaffected.
212
+ def stamp_space=(space)
213
+ RustPdf.check(Native.call("pdf_editable_set_stamp_space", ptr, space))
214
+ @stamp_space = space
215
+ end
216
+
217
+ # The current stamping coordinate space (see #stamp_space=).
218
+ def stamp_space
219
+ @stamp_space || StampSpace::VISIBLE
220
+ end
221
+
222
+ # Chainable form of #stamp_space=.
223
+ def set_stamp_space(space)
224
+ self.stamp_space = space
225
+ self
226
+ end
227
+
228
+ # Stamp a paragraph with automatic word wrapping on page +page_index+:
229
+ # +text+ is broken into lines that fit +width+ points (greedy, by word;
230
+ # "\n" forces a break) and drawn downward from (+x+, +y+). +anchor+
231
+ # (RustPdf::VerticalAnchor) says what +y+ means for the block: TOP
232
+ # (default) — top of the box, first baseline ascent x size below +y+
233
+ # (legacy fixed-position layout); BASELINE — the first line's baseline; BOTTOM /
234
+ # LINE_BOTTOM — bottom-pinned: the block's bottom rests on +y+ and grows
235
+ # upward by its real content height. +align+ lays lines out inside
236
+ # +[x, x+width]+ (JUSTIFY stretches the word gaps of every line but the
237
+ # last of each paragraph). +max_height+ (points, nil = unlimited) truncates
238
+ # overflowing lines (a ceiling for the bottom-pinned anchors — the last
239
+ # lines stay pinned). +line_height+ scales the default 1.2 x size leading.
240
+ # Pass +font_id+ from #add_font_file / #add_font to wrap and draw with an
241
+ # embedded font (its real metrics drive the break points); -1 uses the
242
+ # built-in Helvetica. +rotation_deg+ rotates the laid-out block
243
+ # counter-clockwise about the anchor. Returns whether the page (and font)
244
+ # existed and the box was valid.
245
+ def place_paragraph(page_index, x, y, width, text, size: 12.0, color: [0.0, 0.0, 0.0],
246
+ align: Align::LEFT, font_id: -1, max_height: nil, line_height: 1.0,
247
+ anchor: VerticalAnchor::TOP, rotation_deg: 0.0)
248
+ found, = paragraph_anchored(page_index, x, y, width, text, size, color, align,
249
+ font_id, max_height, line_height, anchor, rotation_deg)
250
+ found
251
+ end
252
+
253
+ # Like #place_paragraph but returns a Hash with the number of +:lines+
254
+ # drawn and the consumed block +:height+ in points (top of the first drawn
255
+ # line's box to the bottom of the last one's; 0 when nothing fit) — stack
256
+ # blocks without re-measuring.
257
+ def place_paragraph_measured(page_index, x, y, width, text, size: 12.0, color: [0.0, 0.0, 0.0],
258
+ align: Align::LEFT, font_id: -1, max_height: nil, line_height: 1.0,
259
+ anchor: VerticalAnchor::TOP, rotation_deg: 0.0)
260
+ _, lines, height = paragraph_anchored(page_index, x, y, width, text, size, color, align,
261
+ font_id, max_height, line_height, anchor, rotation_deg)
262
+ { lines: lines, height: height }
263
+ end
264
+
184
265
  # Mask a placeholder: fill an opaque background box +[x, y, x+width,
185
266
  # y+height]+ in +bg_color+ (each 0..1, default white), then write +text+
186
- # over it using standard Helvetica at +size+ points in +text_color+ (each
267
+ # over it using standard Helvetica (or an embedded font — pass +font_id+
268
+ # from #add_font_file / #add_font) at +size+ points in +text_color+ (each
187
269
  # 0..1, default black), horizontally aligned per +align+ (RustPdf::Align,
188
- # default LEFT) and vertically centered within the box. Saves hand-computing
189
- # the baseline when stamping a real value over a placeholder. Coordinates are
190
- # in the page's VISIBLE space (origin lower-left, y up). Returns whether the
191
- # page existed.
270
+ # default LEFT). +valign+ (RustPdf::VerticalAlign) controls the vertical
271
+ # alignment of the line inside the box: MIDDLE (default, historical
272
+ # cap-height centering), TOP (line hangs from the top edge legacy PDF libraries
273
+ # top line-alignment semantics) or BOTTOM (descender line rests on the
274
+ # bottom edge). +padding+ is the horizontal edge inset (points) for
275
+ # LEFT/RIGHT alignment: text starts at +x + padding+ (or ends at
276
+ # +x + width - padding+); nil keeps the historical
277
+ # min(0.15 x size, width / 4), 0 starts flush with the box edge like
278
+ # rectangle-based DrawString APIs. Coordinates are in the page's VISIBLE space
279
+ # (origin lower-left, y up). Returns whether the page (and font) existed.
192
280
  def masked_text(page_index, x, y, width, height, text, size = 12.0,
193
281
  text_color = [0.0, 0.0, 0.0], bg_color = [1.0, 1.0, 1.0],
194
- align: Align::LEFT)
282
+ align: Align::LEFT, font_id: -1, valign: VerticalAlign::MIDDLE, padding: nil)
195
283
  tr, tg, tb = text_color
196
284
  br, bg, bb = bg_color
197
285
  found = RustPdf.out_int do |buf|
198
- Native.call("pdf_editable_masked_text", ptr, page_index, x.to_f, y.to_f,
286
+ Native.call("pdf_editable_masked_text_pad", ptr, page_index, x.to_f, y.to_f,
199
287
  width.to_f, height.to_f, text, size.to_f,
200
- tr.to_f, tg.to_f, tb.to_f, br.to_f, bg.to_f, bb.to_f, align, buf)
288
+ tr.to_f, tg.to_f, tb.to_f, br.to_f, bg.to_f, bb.to_f, align, valign,
289
+ (padding || -1.0).to_f, font_id, buf)
201
290
  end
202
291
  found != 0
203
292
  end
@@ -205,13 +294,18 @@ module RustPdf
205
294
  # Stamp an +image+ (PNG or JPEG bytes; the core dispatches on the signature)
206
295
  # onto page +page_index+ (0-based), with its lower-left corner at (+x+, +y+),
207
296
  # scaled to +width+ x +height+ points and rotated +rotation_deg+ degrees
208
- # counter-clockwise about that corner. Coordinates are in the page's VISIBLE
209
- # space (origin lower-left, y up), regardless of the page's /Rotate. Returns
210
- # whether the page existed.
211
- def draw_image(page_index, image, x, y, width, height, rotation_deg = 0.0)
297
+ # counter-clockwise. +anchor+ (RustPdf::ImageAnchor) controls how a rotated
298
+ # image is anchored: CORNER (default) rotates the image about its own
299
+ # lower-left corner at (x, y); BOUNDING_BOX lands the rotated image's
300
+ # bounding box with its lower-left at (x, y) (bounding-box layout semantics
301
+ # e.g. a 90-degree image occupies [x, x+height] x [y, y+width]).
302
+ # Coordinates are in the page's VISIBLE space (origin lower-left, y up),
303
+ # regardless of the page's /Rotate. Returns whether the page existed.
304
+ def draw_image(page_index, image, x, y, width, height, rotation_deg = 0.0,
305
+ anchor: ImageAnchor::CORNER)
212
306
  found = RustPdf.out_int do |buf|
213
- Native.call("pdf_editable_draw_image", ptr, page_index, image, image.bytesize,
214
- x.to_f, y.to_f, width.to_f, height.to_f, rotation_deg.to_f, buf)
307
+ Native.call("pdf_editable_draw_image_anchored", ptr, page_index, image, image.bytesize,
308
+ x.to_f, y.to_f, width.to_f, height.to_f, rotation_deg.to_f, anchor, buf)
215
309
  end
216
310
  found != 0
217
311
  end
@@ -289,6 +383,24 @@ module RustPdf
289
383
 
290
384
  private
291
385
 
386
+ # Shared core of #place_paragraph / #place_paragraph_measured. Returns
387
+ # [found (bool), lines (Integer), height (Float)].
388
+ def paragraph_anchored(page_index, x, y, width, text, size, color, align,
389
+ font_id, max_height, line_height, anchor, rotation_deg)
390
+ r, g, b = color
391
+ height_buf = Fiddle::Pointer.malloc(Fiddle::SIZEOF_DOUBLE, Fiddle::RUBY_FREE)
392
+ lines_buf = Fiddle::Pointer.malloc(Native::SIZEOF_INT, Fiddle::RUBY_FREE)
393
+ found_buf = Fiddle::Pointer.malloc(Native::SIZEOF_INT, Fiddle::RUBY_FREE)
394
+ RustPdf.check(Native.call("pdf_editable_place_paragraph_anchored", ptr, page_index,
395
+ x.to_f, y.to_f, width.to_f, text, size.to_f,
396
+ r.to_f, g.to_f, b.to_f, align, anchor, font_id,
397
+ (max_height || 0.0).to_f, line_height.to_f, rotation_deg.to_f,
398
+ height_buf, lines_buf, found_buf))
399
+ [found_buf[0, Native::SIZEOF_INT].unpack1("i!") != 0,
400
+ lines_buf[0, Native::SIZEOF_INT].unpack1("i!"),
401
+ height_buf[0, Fiddle::SIZEOF_DOUBLE].unpack1("d")]
402
+ end
403
+
292
404
  def ptr
293
405
  raise Error, "operation on a closed EditableDoc" if @ptr.nil? || @ptr.null?
294
406
 
@@ -135,6 +135,17 @@ module RustPdf
135
135
  # Issue #50: stamp a PNG/JPEG image onto an existing page. The image bytes
136
136
  # cross as the (uint8_t* data, uintptr_t len) pair, like pdf_editable_load.
137
137
  "pdf_editable_draw_image" => [[VP, I, VP, SZ, D, D, D, D, D, VP], I],
138
+
139
+ # Stamping fonts + anchored stamping primitives. font_id -1 = built-in
140
+ # Helvetica; anchors/valign/space/image-anchor cross as small ints.
141
+ "pdf_editable_add_font_file" => [[VP, VP, VP], I],
142
+ "pdf_editable_add_font" => [[VP, VP, SZ, VP], I],
143
+ "pdf_editable_place_text_anchored" => [[VP, I, D, D, VP, D, D, D, D, D, I, I, I, VP], I],
144
+ "pdf_editable_masked_text_pad" => [[VP, I, D, D, D, D, VP, D, D, D, D, D, D, D, I, I, D, I, VP], I],
145
+ "pdf_editable_place_paragraph_anchored" =>
146
+ [[VP, I, D, D, D, VP, D, D, D, D, I, I, I, D, D, D, VP, VP, VP], I],
147
+ "pdf_editable_set_stamp_space" => [[VP, I], I],
148
+ "pdf_editable_draw_image_anchored" => [[VP, I, VP, SZ, D, D, D, D, D, I, VP], I],
138
149
  }.freeze
139
150
 
140
151
  def lib
data/lib/rustpdf.rb CHANGED
@@ -40,6 +40,48 @@ module RustPdf
40
40
  JUSTIFY = 3
41
41
  end
42
42
 
43
+ # What +y+ means for stamped text (EditableDoc#place_text /
44
+ # #place_paragraph). BASELINE is the historical place_text behavior; TOP
45
+ # hangs the text from +y+ (baseline lands ascent x size below it, legacy layout engines
46
+ # fixed-position layout semantics); BOTTOM rests the descender line on +y+.
47
+ # LINE_TOP / LINE_BOTTOM anchor via the layout line box (its height is also
48
+ # the leading basis, so a single line and a wrapped block agree vertically).
49
+ module VerticalAnchor
50
+ BASELINE = 0
51
+ TOP = 1
52
+ BOTTOM = 2
53
+ LINE_TOP = 3
54
+ LINE_BOTTOM = 4
55
+ end
56
+
57
+ # Vertical alignment of the line inside a masked_text box. MIDDLE is the
58
+ # historical cap-height centering; TOP hangs the line from the top edge
59
+ # (top line-alignment in rectangle-based text APIs semantics); BOTTOM rests the descender
60
+ # line on the bottom edge.
61
+ module VerticalAlign
62
+ TOP = 0
63
+ MIDDLE = 1
64
+ BOTTOM = 2
65
+ end
66
+
67
+ # Coordinate space of the positioned stamping primitives (EditableDoc).
68
+ # VISIBLE (default) compensates the page /Rotate so coordinates match what
69
+ # a viewer displays; MEDIA is the raw PDF user space (legacy layout engines
70
+ # fixed-position layout/rotation semantics — no /Rotate composition).
71
+ module StampSpace
72
+ VISIBLE = 0
73
+ MEDIA = 1
74
+ end
75
+
76
+ # How a rotated EditableDoc#draw_image is anchored. CORNER (default) rotates
77
+ # the image about its own lower-left corner at (x, y); BOUNDING_BOX lands
78
+ # the rotated image's bounding box with its lower-left at (x, y) (legacy layout engines
79
+ # layout semantics).
80
+ module ImageAnchor
81
+ CORNER = 0
82
+ BOUNDING_BOX = 1
83
+ end
84
+
43
85
  # Embedded-file relationship (PDF/A-3).
44
86
  module Relationship
45
87
  SOURCE = 0
Binary file
metadata CHANGED
@@ -1,17 +1,18 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rustpdf
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.6
4
+ version: 0.4.8
5
5
  platform: x86_64-linux
6
6
  authors:
7
7
  - rust-pdf
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-06-30 00:00:00.000000000 Z
11
+ date: 2026-07-02 00:00:00.000000000 Z
12
12
  dependencies: []
13
- description: Idiomatic Ruby binding over the rust-pdf C ABI (libpdf_ffi) using the
14
- built-in Fiddle stdlib.
13
+ description: 'Generate, edit, sign and process PDFs in Ruby: PDF/A, AES-256 encryption,
14
+ digital signatures (PAdES), forms, stamping, redaction, text extraction and page
15
+ rendering. Docs: https://rustpdf.dev/docs/ruby'
15
16
  email:
16
17
  executables: []
17
18
  extensions: []
@@ -45,6 +46,5 @@ requirements: []
45
46
  rubygems_version: 3.5.22
46
47
  signing_key:
47
48
  specification_version: 4
48
- summary: Ruby binding for the rust-pdf core (generate, manipulate, sign and validate
49
- PDFs).
49
+ summary: 'Generate, edit, sign and process PDFs in Ruby. Docs: https://rustpdf.dev/docs/ruby'
50
50
  test_files: []