Class: RustPdf::EditableDoc

Inherits:
Object
  • Object
show all
Defined in:
lib/rustpdf/editable_doc.rb

Overview

An existing PDF loaded for manipulation. Call #close (or rely on GC) to free.

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(ptr) ⇒ EditableDoc

Returns a new instance of EditableDoc.



20
21
22
# File 'lib/rustpdf/editable_doc.rb', line 20

def initialize(ptr)
  @ptr = ptr
end

Class Method Details

.load(data, password: nil) ⇒ Object

Load a PDF from bytes (optionally with a password).

Raises:



5
6
7
8
9
10
11
12
13
14
# File 'lib/rustpdf/editable_doc.rb', line 5

def self.load(data, password: nil)
  ptr = if password
          Native.call("pdf_editable_load_password", data, data.bytesize, password)
        else
          Native.call("pdf_editable_load", data, data.bytesize)
        end
  raise Error, RustPdf.last_error if ptr.null?

  new(ptr)
end

.load_file(path, password: nil) ⇒ Object



16
17
18
# File 'lib/rustpdf/editable_doc.rb', line 16

def self.load_file(path, password: nil)
  load(File.binread(path), password: password)
end

Instance Method Details

#add_font(data) ⇒ Object

Register a stamping font from raw TrueType/OpenType bytes. See #add_font_file.



176
177
178
# File 'lib/rustpdf/editable_doc.rb', line 176

def add_font(data)
  RustPdf.out_int { |buf| Native.call("pdf_editable_add_font", ptr, data, data.bytesize, buf) }
end

#add_font_file(path) ⇒ Object

Register a TrueType/OpenType font (from a file path) for text stamping; returns a font id usable with the font_id keyword of #place_text / #masked_text / #place_paragraph. The font is embedded as a subset — stamped text renders with the real font's glyphs and metrics.



170
171
172
# File 'lib/rustpdf/editable_doc.rb', line 170

def add_font_file(path)
  RustPdf.out_int { |buf| Native.call("pdf_editable_add_font_file", ptr, path, buf) }
end

#closeObject



25
26
27
28
29
30
# File 'lib/rustpdf/editable_doc.rb', line 25

def close
  return if @ptr.nil? || @ptr.null?

  Native.call("pdf_editable_free", @ptr)
  @ptr = nil
end

#compact(on = true) ⇒ Object



357
358
359
360
# File 'lib/rustpdf/editable_doc.rb', line 357

def compact(on = true)
  RustPdf.check(Native.call("pdf_editable_compact", ptr, on ? 1 : 0))
  self
end

#convert_to_pdfa(level = Pdfa::A2B) ⇒ Object

Convert the document to PDF/A (B-levels only: A1B=0, A2B=1, A3B=3).



324
325
326
327
# File 'lib/rustpdf/editable_doc.rb', line 324

def convert_to_pdfa(level = Pdfa::A2B)
  RustPdf.check(Native.call("pdf_editable_convert_to_pdfa", ptr, level))
  self
end

#delete_page(index) ⇒ Object



46
47
48
49
# File 'lib/rustpdf/editable_doc.rb', line 46

def delete_page(index)
  RustPdf.check(Native.call("pdf_editable_delete_page", ptr, index))
  self
end

#draw_image(page_index, image, x, y, width, height, rotation_deg = 0.0, anchor: ImageAnchor::CORNER) ⇒ Object

Stamp an image (PNG or JPEG bytes; the core dispatches on the signature) onto page page_index (0-based), with its lower-left corner at (+x+, y), scaled to width x height points and rotated rotation_deg degrees counter-clockwise. anchor (RustPdf::ImageAnchor) controls how a rotated image is anchored: CORNER (default) rotates the image about its own lower-left corner at (x, y); BOUNDING_BOX lands the rotated image's bounding box with its lower-left at (x, y) (bounding-box layout semantics — e.g. a 90-degree image occupies [x, x+height] x [y, y+width]). Coordinates are in the page's VISIBLE space (origin lower-left, y up), regardless of the page's /Rotate. Returns whether the page existed.



304
305
306
307
308
309
310
311
# File 'lib/rustpdf/editable_doc.rb', line 304

def draw_image(page_index, image, x, y, width, height, rotation_deg = 0.0,
               anchor: ImageAnchor::CORNER)
  found = RustPdf.out_int do |buf|
    Native.call("pdf_editable_draw_image_anchored", ptr, page_index, image, image.bytesize,
                x.to_f, y.to_f, width.to_f, height.to_f, rotation_deg.to_f, anchor, buf)
  end
  found != 0
end

#encrypt(method: Cipher::AES256, user: "", owner: "", read_only: false) ⇒ Object

Encrypt on save (requires a license).



363
364
365
366
# File 'lib/rustpdf/editable_doc.rb', line 363

def encrypt(method: Cipher::AES256, user: "", owner: "", read_only: false)
  RustPdf.check(Native.call("pdf_editable_encrypt", ptr, method, user, owner, read_only ? 1 : 0))
  self
end

#extract_pages(indices) ⇒ Object



57
58
59
60
61
62
# File 'lib/rustpdf/editable_doc.rb', line 57

def extract_pages(indices)
  buf = indices.pack("J*")
  out = Fiddle::Pointer.malloc(Native::SIZEOF_SZ, Fiddle::RUBY_FREE)
  RustPdf.check(Native.call("pdf_editable_extract_pages", ptr, buf, indices.size, out))
  EditableDoc.send(:new, out.ptr)
end

#field_namesObject

Returns the list of AcroForm field names.



123
124
125
126
127
128
# File 'lib/rustpdf/editable_doc.rb', line 123

def field_names
  p = ptr
  text = RustPdf.take_bytes { |pp, pn| Native.call("pdf_editable_field_names", p, pp, pn) }
                .force_encoding(Encoding::UTF_8)
  text.split("\n").reject(&:empty?)
end

#fill_rect(page_index, x, y, width, height, color = [1.0, 1.0, 1.0], opacity = 1.0) ⇒ Object

Paint a filled rectangle at (+x+, y) sized width x height on page page_index (0-based), in RGB color (each 0..1, default opaque white) at opacity (0..1). Coordinates are in the page's VISIBLE space (origin lower-left, y up), regardless of the page's /Rotate. The common use is masking a placeholder with an opaque white box. Returns whether the page existed.



157
158
159
160
161
162
163
164
# File 'lib/rustpdf/editable_doc.rb', line 157

def fill_rect(page_index, x, y, width, height, color = [1.0, 1.0, 1.0], opacity = 1.0)
  r, g, b = color
  found = RustPdf.out_int do |buf|
    Native.call("pdf_editable_fill_rect", ptr, page_index, x.to_f, y.to_f,
                width.to_f, height.to_f, r.to_f, g.to_f, b.to_f, opacity.to_f, buf)
  end
  found != 0
end

#fill_text_field(name, value) ⇒ Object

Returns whether the field existed.



91
92
93
94
# File 'lib/rustpdf/editable_doc.rb', line 91

def fill_text_field(name, value)
  found = RustPdf.out_int { |buf| Native.call("pdf_editable_fill_text_field", ptr, name, value, buf) }
  found != 0
end

#flatten_formsObject

Flatten all AcroForm fields into page content (removes interactivity).



117
118
119
120
# File 'lib/rustpdf/editable_doc.rb', line 117

def flatten_forms
  RustPdf.check(Native.call("pdf_editable_flatten_forms", ptr))
  self
end

#get_info(key) ⇒ Object



74
75
76
77
78
# File 'lib/rustpdf/editable_doc.rb', line 74

def get_info(key)
  p = ptr
  RustPdf.take_bytes { |pp, pn| Native.call("pdf_editable_get_info", p, key, pp, pn) }
         .force_encoding(Encoding::UTF_8)
end

#info=(pair) ⇒ Object



64
65
66
67
# File 'lib/rustpdf/editable_doc.rb', line 64

def info=(pair)
  key, value = pair
  RustPdf.check(Native.call("pdf_editable_set_info", ptr, key, value))
end

#masked_text(page_index, x, y, width, height, text, size = 12.0, text_color = [0.0, 0.0, 0.0], bg_color = [1.0, 1.0, 1.0], align: Align::LEFT, font_id: -1,, valign: VerticalAlign::MIDDLE, padding: nil) ⇒ Object

Mask a placeholder: fill an opaque background box +[x, y, x+width, y+height]+ in bg_color (each 0..1, default white), then write text over it using standard Helvetica (or an embedded font — pass font_id from #add_font_file / #add_font) at size points in text_color (each 0..1, default black), horizontally aligned per align (RustPdf::Align, default LEFT). valign (RustPdf::VerticalAlign) controls the vertical alignment of the line inside the box: MIDDLE (default, historical cap-height centering), TOP (line hangs from the top edge — legacy PDF libraries top line-alignment semantics) or BOTTOM (descender line rests on the bottom edge). padding is the horizontal edge inset (points) for LEFT/RIGHT alignment: text starts at +x + padding+ (or ends at +x + width - padding+); nil keeps the historical min(0.15 x size, width / 4), 0 starts flush with the box edge like rectangle-based DrawString APIs. Coordinates are in the page's VISIBLE space (origin lower-left, y up). Returns whether the page (and font) existed.



280
281
282
283
284
285
286
287
288
289
290
291
292
# File 'lib/rustpdf/editable_doc.rb', line 280

def masked_text(page_index, x, y, width, height, text, size = 12.0,
                text_color = [0.0, 0.0, 0.0], bg_color = [1.0, 1.0, 1.0],
                align: Align::LEFT, font_id: -1, valign: VerticalAlign::MIDDLE, padding: nil)
  tr, tg, tb = text_color
  br, bg, bb = bg_color
  found = RustPdf.out_int do |buf|
    Native.call("pdf_editable_masked_text_pad", ptr, page_index, x.to_f, y.to_f,
                width.to_f, height.to_f, text, size.to_f,
                tr.to_f, tg.to_f, tb.to_f, br.to_f, bg.to_f, bb.to_f, align, valign,
                (padding || -1.0).to_f, font_id, buf)
  end
  found != 0
end

#merge(other) ⇒ Object



36
37
38
39
# File 'lib/rustpdf/editable_doc.rb', line 36

def merge(other)
  RustPdf.check(Native.call("pdf_editable_merge", ptr, other.send(:ptr)))
  self
end

#normalize(version = Version::V1_7) ⇒ Object

Normalize to a plain PDF at version (strip PDF/A + set version). Codes as in #set_version.



347
348
349
350
# File 'lib/rustpdf/editable_doc.rb', line 347

def normalize(version = Version::V1_7)
  RustPdf.check(Native.call("pdf_editable_normalize", ptr, version))
  self
end

#optimizeObject



352
353
354
355
# File 'lib/rustpdf/editable_doc.rb', line 352

def optimize
  RustPdf.check(Native.call("pdf_editable_optimize", ptr))
  self
end

#overlay_page(index, content) ⇒ Object



85
86
87
88
# File 'lib/rustpdf/editable_doc.rb', line 85

def overlay_page(index, content)
  RustPdf.check(Native.call("pdf_editable_overlay_page", ptr, index, content, content.bytesize))
  self
end

#page_countObject



32
33
34
# File 'lib/rustpdf/editable_doc.rb', line 32

def page_count
  Native.call("pdf_editable_page_count", ptr)
end

#place_paragraph(page_index, x, y, width, text, size: 12.0, color: [0.0, 0.0, 0.0], align: Align::LEFT, font_id: -1,, max_height: nil, line_height: 1.0, anchor: VerticalAnchor::TOP, rotation_deg: 0.0) ⇒ Object

Stamp a paragraph with automatic word wrapping on page page_index: text is broken into lines that fit width points (greedy, by word; "\n" forces a break) and drawn downward from (+x+, y). anchor (RustPdf::VerticalAnchor) says what y means for the block: TOP (default) — top of the box, first baseline ascent x size below y (legacy fixed-position layout); BASELINE — the first line's baseline; BOTTOM / LINE_BOTTOM — bottom-pinned: the block's bottom rests on y and grows upward by its real content height. align lays lines out inside +[x, x+width]+ (JUSTIFY stretches the word gaps of every line but the last of each paragraph). max_height (points, nil = unlimited) truncates overflowing lines (a ceiling for the bottom-pinned anchors — the last lines stay pinned). line_height scales the default 1.2 x size leading. Pass font_id from #add_font_file / #add_font to wrap and draw with an embedded font (its real metrics drive the break points); -1 uses the built-in Helvetica. rotation_deg rotates the laid-out block counter-clockwise about the anchor. Returns whether the page (and font) existed and the box was valid.



245
246
247
248
249
250
251
# File 'lib/rustpdf/editable_doc.rb', line 245

def place_paragraph(page_index, x, y, width, text, size: 12.0, color: [0.0, 0.0, 0.0],
                    align: Align::LEFT, font_id: -1, max_height: nil, line_height: 1.0,
                    anchor: VerticalAnchor::TOP, rotation_deg: 0.0)
  found, = paragraph_anchored(page_index, x, y, width, text, size, color, align,
                              font_id, max_height, line_height, anchor, rotation_deg)
  found
end

#place_paragraph_measured(page_index, x, y, width, text, size: 12.0, color: [0.0, 0.0, 0.0], align: Align::LEFT, font_id: -1,, max_height: nil, line_height: 1.0, anchor: VerticalAnchor::TOP, rotation_deg: 0.0) ⇒ Object

Like #place_paragraph but returns a Hash with the number of :lines drawn and the consumed block :height in points (top of the first drawn line's box to the bottom of the last one's; 0 when nothing fit) — stack blocks without re-measuring.



257
258
259
260
261
262
263
# File 'lib/rustpdf/editable_doc.rb', line 257

def place_paragraph_measured(page_index, x, y, width, text, size: 12.0, color: [0.0, 0.0, 0.0],
                             align: Align::LEFT, font_id: -1, max_height: nil, line_height: 1.0,
                             anchor: VerticalAnchor::TOP, rotation_deg: 0.0)
  _, lines, height = paragraph_anchored(page_index, x, y, width, text, size, color, align,
                                        font_id, max_height, line_height, anchor, rotation_deg)
  { lines: lines, height: height }
end

#place_text(page_index, x, y, text, size = 12.0, color = [0.0, 0.0, 0.0], rotation_deg = 0.0, align: Align::LEFT, font_id: -1,, anchor: VerticalAnchor::BASELINE) ⇒ Object

Draw a line of positioned text anchored at (+x+, y) on page page_index (0-based), using standard Helvetica (or an embedded font — pass font_id from #add_font_file / #add_font) at size points in RGB color (each 0..1, default black). rotation_deg rotates the text counter-clockwise about its anchor (match the page rotation to follow a rotated page). align (RustPdf::Align, default LEFT) shifts the start point along the baseline direction by the text width for RIGHT/CENTER alignment. anchor (RustPdf::VerticalAnchor) says what y means: BASELINE (default, historical behavior), TOP (text hangs from y — baseline lands ascent x size below it, legacy fixed-position layout), BOTTOM (descender line rests on y), or LINE_TOP / LINE_BOTTOM (legacy layout engines line box). Coordinates are in the page's VISIBLE space (origin lower-left, y up) unless #stamp_space= chose MEDIA. Returns whether the page (and font) existed.



194
195
196
197
198
199
200
201
202
# File 'lib/rustpdf/editable_doc.rb', line 194

def place_text(page_index, x, y, text, size = 12.0, color = [0.0, 0.0, 0.0], rotation_deg = 0.0,
               align: Align::LEFT, font_id: -1, anchor: VerticalAnchor::BASELINE)
  r, g, b = color
  found = RustPdf.out_int do |buf|
    Native.call("pdf_editable_place_text_anchored", ptr, page_index, x.to_f, y.to_f, text,
                size.to_f, r.to_f, g.to_f, b.to_f, rotation_deg.to_f, align, anchor, font_id, buf)
  end
  found != 0
end

#redact(page_index, rects) ⇒ Object

Black out rectangles on a page. rects = [[x0,y0,x1,y1], ...]. Returns whether the page existed.



317
318
319
320
321
# File 'lib/rustpdf/editable_doc.rb', line 317

def redact(page_index, rects)
  flat = rects.flatten.pack("d*")
  found = RustPdf.out_int { |buf| Native.call("pdf_editable_redact", ptr, page_index, flat, rects.size, buf) }
  found != 0
end

#reorder_pages(order) ⇒ Object



51
52
53
54
55
# File 'lib/rustpdf/editable_doc.rb', line 51

def reorder_pages(order)
  buf = order.pack("J*")
  RustPdf.check(Native.call("pdf_editable_reorder_pages", ptr, buf, order.size))
  self
end

#rotate_page(index, degrees) ⇒ Object



41
42
43
44
# File 'lib/rustpdf/editable_doc.rb', line 41

def rotate_page(index, degrees)
  RustPdf.check(Native.call("pdf_editable_rotate_page", ptr, index, degrees))
  self
end

#save(path) ⇒ Object



380
381
382
# File 'lib/rustpdf/editable_doc.rb', line 380

def save(path)
  RustPdf.check(Native.call("pdf_editable_save", ptr, path))
end

#set_checkbox(name, checked = true) ⇒ Object

Set a checkbox by field name. Returns whether the field existed.



99
100
101
102
# File 'lib/rustpdf/editable_doc.rb', line 99

def set_checkbox(name, checked = true)
  found = RustPdf.out_int { |buf| Native.call("pdf_editable_set_checkbox", ptr, name, checked ? 1 : 0, buf) }
  found != 0
end

#set_choice(name, value) ⇒ Object

Set a choice (dropdown/list) value by field name. Returns whether found.



111
112
113
114
# File 'lib/rustpdf/editable_doc.rb', line 111

def set_choice(name, value)
  found = RustPdf.out_int { |buf| Native.call("pdf_editable_set_choice", ptr, name, value, buf) }
  found != 0
end

#set_info(key, value) ⇒ Object



69
70
71
72
# File 'lib/rustpdf/editable_doc.rb', line 69

def set_info(key, value)
  RustPdf.check(Native.call("pdf_editable_set_info", ptr, key, value))
  self
end

#set_radio(name, export_value) ⇒ Object

Select a radio button by field name + export value. Returns whether found.



105
106
107
108
# File 'lib/rustpdf/editable_doc.rb', line 105

def set_radio(name, export_value)
  found = RustPdf.out_int { |buf| Native.call("pdf_editable_set_radio", ptr, name, export_value, buf) }
  found != 0
end

#set_stamp_space(space) ⇒ Object

Chainable form of #stamp_space=.



223
224
225
226
# File 'lib/rustpdf/editable_doc.rb', line 223

def set_stamp_space(space)
  self.stamp_space = space
  self
end

#set_version(version) ⇒ Object

Set the output PDF version (downgrade/normalize). version uses the RustPdf::Version codes (V1_4=0, V1_5=1, V1_7=2, V2_0=3).



333
334
335
336
# File 'lib/rustpdf/editable_doc.rb', line 333

def set_version(version)
  RustPdf.check(Native.call("pdf_editable_set_version", ptr, version))
  self
end

#set_xmp(xml) ⇒ Object



80
81
82
83
# File 'lib/rustpdf/editable_doc.rb', line 80

def set_xmp(xml)
  RustPdf.check(Native.call("pdf_editable_set_xmp", ptr, xml, xml.bytesize))
  self
end

#stamp_spaceObject

The current stamping coordinate space (see #stamp_space=).



218
219
220
# File 'lib/rustpdf/editable_doc.rb', line 218

def stamp_space
  @stamp_space || StampSpace::VISIBLE
end

#stamp_space=(space) ⇒ Object

Choose the coordinate space of the positioned stamping primitives (#fill_rect, #place_text, #masked_text, #place_paragraph, #draw_image) for subsequent calls. StampSpace::VISIBLE (default) keeps the historical behavior — coordinates in the page's displayed space, compensating /Rotate. StampSpace::MEDIA interprets coordinates and rotation_deg in the raw PDF user space (legacy layout semantics), never composing with the page's /Rotate — use it to reproduce legacy-engine placement on rotated/scanned pages. Watermarks and redaction are unaffected.



212
213
214
215
# File 'lib/rustpdf/editable_doc.rb', line 212

def stamp_space=(space)
  RustPdf.check(Native.call("pdf_editable_set_stamp_space", ptr, space))
  @stamp_space = space
end

#strip_pdfaObject

Strip PDF/A conformance (OutputIntents, XMP pdfaid, /Version) so the file is a plain PDF.



340
341
342
343
# File 'lib/rustpdf/editable_doc.rb', line 340

def strip_pdfa
  RustPdf.check(Native.call("pdf_editable_strip_pdfa", ptr))
  self
end

#to_bytesObject



368
369
370
371
# File 'lib/rustpdf/editable_doc.rb', line 368

def to_bytes
  p = ptr
  RustPdf.take_bytes { |pp, pn| Native.call("pdf_editable_to_bytes", p, pp, pn) }
end

#to_bytes_incremental(original) ⇒ Object



373
374
375
376
377
378
# File 'lib/rustpdf/editable_doc.rb', line 373

def to_bytes_incremental(original)
  p = ptr
  RustPdf.take_bytes do |pp, pn|
    Native.call("pdf_editable_to_bytes_incremental", p, original, original.bytesize, pp, pn)
  end
end

#watermark_image_file(path, width, height, opacity: 0.30, rotation_deg: 0.0) ⇒ Object

Stamp an image watermark (from a file) across every page, rotated rotation_deg degrees counter-clockwise.



143
144
145
146
147
# File 'lib/rustpdf/editable_doc.rb', line 143

def watermark_image_file(path, width, height, opacity: 0.30, rotation_deg: 0.0)
  RustPdf.check(Native.call("pdf_editable_watermark_image_file", ptr, path, width, height,
                            opacity, rotation_deg))
  self
end

#watermark_text(text, size: 64.0, color: [0.5, 0.5, 0.5], opacity: 0.30, rotation_deg: 45.0, opaque_background: false) ⇒ Object

Stamp a diagonal text watermark across every page. When opaque_background is true, the text is drawn over an opaque white box (otherwise it is blended into the page content).



133
134
135
136
137
138
139
# File 'lib/rustpdf/editable_doc.rb', line 133

def watermark_text(text, size: 64.0, color: [0.5, 0.5, 0.5], opacity: 0.30,
                   rotation_deg: 45.0, opaque_background: false)
  r, g, b = color
  RustPdf.check(Native.call("pdf_editable_watermark_text", ptr, text, size, r, g, b,
                            opacity, rotation_deg, opaque_background ? 1 : 0))
  self
end