Class: PdfOxide::Pdf
- Inherits:
-
Object
- Object
- PdfOxide::Pdf
- Defined in:
- lib/pdf_oxide/pdf.rb
Overview
Create / edit / save PDFs. Read concerns live on PdfDocument; mutate concerns on DocumentEditor; creation + transformation (markdown→PDF, html→PDF) live here.
Mirrors ‘fyi.oxide.pdf.Pdf`. Lifecycle: instances own a native handle and **must be closed** via #close or the block-form `Pdf.from_markdown(…) { |pdf| … }`. Close is idempotent.
Instance Attribute Summary collapse
- #handle ⇒ Object readonly private
Class Method Summary collapse
- .build_from(symbol, content) ⇒ Object
-
.create_empty(&block) ⇒ Object
Create a blank PDF (one empty page).
- .finalizer(tracker) ⇒ Object private
-
.from_html(html, &block) ⇒ Object
Build a PDF from an HTML source.
-
.from_images(images, &block) ⇒ Pdf
Build a multi-page PDF from JPEG/PNG byte arrays.
-
.from_markdown(markdown) {|Pdf| ... } ⇒ Pdf
Build a PDF from a Markdown source.
-
.from_text(text, &block) ⇒ Object
Build a PDF from plain text.
-
.plan_split_by_bookmarks_count(source_pdf, level) ⇒ Integer
Count the bookmark-split segments that would result from splitting ‘source_pdf` at `level` (1 = top-level only; 0 = all).
-
.prefetch_available? ⇒ Boolean
Whether the build supports OCR model provisioning.
-
.prefetch_models(languages) ⇒ String
Prefetch OCR models for the given languages.
-
.version ⇒ String
Library version.
Instance Method Summary collapse
-
#close ⇒ Object
Idempotent free.
-
#closed? ⇒ Boolean
True once #close runs.
-
#initialize(handle) ⇒ Pdf
constructor
A new instance of Pdf.
-
#save(path) ⇒ String
Write the PDF bytes to ‘path`.
-
#to_bytes ⇒ String
BINARY-encoded PDF bytes.
Constructor Details
#initialize(handle) ⇒ Pdf
Returns a new instance of Pdf.
121 122 123 124 125 126 |
# File 'lib/pdf_oxide/pdf.rb', line 121 def initialize(handle) @handle = handle @closed = false @tracker = [@handle] ObjectSpace.define_finalizer(self, self.class.finalizer(@tracker)) end |
Instance Attribute Details
#handle ⇒ Object (readonly)
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
129 130 131 |
# File 'lib/pdf_oxide/pdf.rb', line 129 def handle @handle end |
Class Method Details
.build_from(symbol, content) ⇒ Object
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
# File 'lib/pdf_oxide/pdf.rb', line 103 def self.build_from(symbol, content) err = ::FFI::MemoryPointer.new(:int32) handle = Bindings.send(symbol, content, err) code = err.read_int32 raise ParseError, "#{symbol} failed (#{code})" if code != 0 raise ParseError, "#{symbol} returned null" if handle.nil? || handle.null? pdf = new(handle) return pdf unless block_given? begin yield pdf ensure pdf.close end end |
.create_empty(&block) ⇒ Object
Create a blank PDF (one empty page). Convenience for tests / toolchain bring-up.
75 76 77 |
# File 'lib/pdf_oxide/pdf.rb', line 75 def self.create_empty(&block) from_text(' ', &block) end |
.finalizer(tracker) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
208 209 210 211 212 213 214 215 216 |
# File 'lib/pdf_oxide/pdf.rb', line 208 def self.finalizer(tracker) proc do h = tracker[0] if h && !h.null? Bindings.pdf_free(h) tracker[0] = nil end end end |
.from_html(html, &block) ⇒ Object
Build a PDF from an HTML source. CSS is honored per pdf_oxide’s html_css pipeline.
26 27 28 29 30 |
# File 'lib/pdf_oxide/pdf.rb', line 26 def self.from_html(html, &block) raise ::PdfOxide::ArgumentError, 'html cannot be empty' if html.nil? || html.empty? build_from(:pdf_from_html, html, &block) end |
.from_images(images, &block) ⇒ Pdf
Build a multi-page PDF from JPEG/PNG byte arrays. Each image becomes a separate page. Format is auto-detected from magic bytes.
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
# File 'lib/pdf_oxide/pdf.rb', line 43 def self.from_images(images, &block) raise ::PdfOxide::ArgumentError, 'images cannot be empty' if images.nil? || images.empty? # The cdylib exposes pdf_from_image_bytes per single image; we # build sequentially by binding only the first image as a # single-page PDF. Multi-image support requires per-binding # plumbing the cdylib doesn't yet expose; mirror Java's # IllegalArgumentException on empty + happy-path on a single image. first = images.first raise ::PdfOxide::ArgumentError, 'image cannot be empty' if first.nil? || first.empty? binary = first.dup.force_encoding(Encoding::BINARY) buf = ::FFI::MemoryPointer.new(:uint8, binary.bytesize) buf.write_bytes(binary, 0, binary.bytesize) err = ::FFI::MemoryPointer.new(:int32) handle = Bindings.pdf_from_image_bytes(buf, binary.bytesize, err) code = err.read_int32 raise ParseError, "pdf_from_image_bytes failed (#{code})" if code != 0 raise ParseError, 'pdf_from_image_bytes returned null' if handle.nil? || handle.null? pdf = new(handle) return pdf unless block_given? begin yield pdf ensure pdf.close end end |
.from_markdown(markdown) {|Pdf| ... } ⇒ Pdf
Build a PDF from a Markdown source.
18 19 20 21 22 |
# File 'lib/pdf_oxide/pdf.rb', line 18 def self.from_markdown(markdown, &block) raise ::PdfOxide::ArgumentError, 'markdown cannot be empty' if markdown.nil? || markdown.empty? build_from(:pdf_from_markdown, markdown, &block) end |
.from_text(text, &block) ⇒ Object
Build a PDF from plain text.
33 34 35 36 37 |
# File 'lib/pdf_oxide/pdf.rb', line 33 def self.from_text(text, &block) raise ::PdfOxide::ArgumentError, 'text cannot be empty' if text.nil? || text.empty? build_from(:pdf_from_text, text, &block) end |
.plan_split_by_bookmarks_count(source_pdf, level) ⇒ Integer
Count the bookmark-split segments that would result from splitting ‘source_pdf` at `level` (1 = top-level only; 0 = all). Useful for previewing without producing output.
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 |
# File 'lib/pdf_oxide/pdf.rb', line 186 def self.plan_split_by_bookmarks_count(source_pdf, level) raise ::PdfOxide::ArgumentError, 'source_pdf cannot be nil' if source_pdf.nil? PdfOxide::PdfDocument.open(source_pdf) do |doc| require 'json' err = ::FFI::MemoryPointer.new(:int32) opts = JSON.generate(level: level) ptr = Bindings.pdf_document_plan_split_by_bookmarks(doc.handle, opts, err) code = err.read_int32 raise InternalError, "plan_split_by_bookmarks failed (#{code})" if code != 0 json = StringMarshaller.from_c_string(ptr) || '[]' arr = begin JSON.parse(json) rescue JSON::ParserError [] end Array(arr).length end end |
.prefetch_available? ⇒ Boolean
Returns whether the build supports OCR model provisioning.
98 99 100 |
# File 'lib/pdf_oxide/pdf.rb', line 98 def self.prefetch_available? Bindings.pdf_oxide_prefetch_available != 0 end |
.prefetch_models(languages) ⇒ String
Prefetch OCR models for the given languages.
87 88 89 90 91 92 93 94 95 |
# File 'lib/pdf_oxide/pdf.rb', line 87 def self.prefetch_models(languages) csv = Array(languages).join(',') err = ::FFI::MemoryPointer.new(:int32) ptr = Bindings.pdf_oxide_prefetch_models(csv, err) code = err.read_int32 raise InternalError, "prefetch_models failed (#{code})" if code != 0 StringMarshaller.from_c_string(ptr) || '' end |
Instance Method Details
#close ⇒ Object
Idempotent free.
163 164 165 166 167 168 169 170 171 |
# File 'lib/pdf_oxide/pdf.rb', line 163 def close return if @closed h = @handle @handle = nil @closed = true @tracker[0] = nil if @tracker Bindings.pdf_free(h) if h && !h.null? end |
#closed? ⇒ Boolean
Returns true once #close runs.
174 175 176 |
# File 'lib/pdf_oxide/pdf.rb', line 174 def closed? @closed end |
#save(path) ⇒ String
Write the PDF bytes to ‘path`.
150 151 152 153 154 155 156 157 158 159 160 |
# File 'lib/pdf_oxide/pdf.rb', line 150 def save(path) raise InvalidStateError, 'Pdf has been closed' if @closed raise ::PdfOxide::ArgumentError, 'path cannot be empty' if path.nil? || path.empty? err = ::FFI::MemoryPointer.new(:int32) rc = Bindings.pdf_save(@handle, path, err) code = err.read_int32 raise IoError, "pdf_save failed (#{code})" if code != 0 || rc != 0 File.absolute_path(path) end |
#to_bytes ⇒ String
Returns BINARY-encoded PDF bytes.
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 |
# File 'lib/pdf_oxide/pdf.rb', line 132 def to_bytes raise InvalidStateError, 'Pdf has been closed' if @closed len_ptr = ::FFI::MemoryPointer.new(:int32) err = ::FFI::MemoryPointer.new(:int32) buf = Bindings.pdf_save_to_bytes(@handle, len_ptr, err) code = err.read_int32 raise InternalError, "pdf_save_to_bytes failed (#{code})" if code != 0 raise InternalError, 'pdf_save_to_bytes returned null' if buf.nil? || buf.null? len = len_ptr.read_int32 bytes = buf.read_string(len) Bindings.free_bytes(buf) if Bindings.respond_to?(:free_bytes) bytes.force_encoding(Encoding::BINARY) end |