imprimatur 0.4.0 → 0.6.0

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.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +28 -0
  3. data/Cargo.lock +472 -5
  4. data/Cargo.toml +16 -1
  5. data/README.md +59 -7
  6. data/ext/imprimatur_native/cli/Cargo.toml +1 -1
  7. data/ext/imprimatur_native/cli/src/app.rs +3 -1
  8. data/ext/imprimatur_native/core/Cargo.toml +29 -13
  9. data/ext/imprimatur_native/core/README.md +13 -1
  10. data/ext/imprimatur_native/core/src/bin/imprimatur-render-worker.rs +10 -0
  11. data/ext/imprimatur_native/core/src/bytes.rs +16 -0
  12. data/ext/imprimatur_native/core/src/doc/picture.rs +3 -7
  13. data/ext/imprimatur_native/core/src/doc/size.rs +14 -0
  14. data/ext/imprimatur_native/core/src/doc/symbol.rs +3 -5
  15. data/ext/imprimatur_native/core/src/doc.rs +24 -0
  16. data/ext/imprimatur_native/core/src/docx.rs +6 -5
  17. data/ext/imprimatur_native/core/src/error.rs +6 -0
  18. data/ext/imprimatur_native/core/src/font.rs +12 -0
  19. data/ext/imprimatur_native/core/src/lib.rs +6 -0
  20. data/ext/imprimatur_native/core/src/pdf/layout.rs +13 -5
  21. data/ext/imprimatur_native/core/src/pdf/objects.rs +1 -1
  22. data/ext/imprimatur_native/core/src/pdf/truetype/descriptor.rs +1 -0
  23. data/ext/imprimatur_native/core/src/read/budget.rs +10 -5
  24. data/ext/imprimatur_native/core/src/read/content.rs +16 -0
  25. data/ext/imprimatur_native/core/src/read/image/dimensions.rs +2 -2
  26. data/ext/imprimatur_native/core/src/read/ole.rs +1 -8
  27. data/ext/imprimatur_native/core/src/read/pdf/bounded.rs +190 -0
  28. data/ext/imprimatur_native/core/src/read/pdf/content/state.rs +50 -0
  29. data/ext/imprimatur_native/core/src/read/pdf/content.rs +43 -54
  30. data/ext/imprimatur_native/core/src/read/pdf/document/pages.rs +140 -0
  31. data/ext/imprimatur_native/core/src/read/pdf/document.rs +20 -110
  32. data/ext/imprimatur_native/core/src/read/pdf/filter.rs +25 -13
  33. data/ext/imprimatur_native/core/src/read/pdf/font.rs +4 -3
  34. data/ext/imprimatur_native/core/src/read/pdf/isolation/protocol.rs +155 -0
  35. data/ext/imprimatur_native/core/src/read/pdf/isolation/system.rs +71 -0
  36. data/ext/imprimatur_native/core/src/read/pdf/isolation/worker.rs +43 -0
  37. data/ext/imprimatur_native/core/src/read/pdf/isolation.rs +166 -0
  38. data/ext/imprimatur_native/core/src/read/pdf/render.rs +206 -0
  39. data/ext/imprimatur_native/core/src/read/pdf.rs +16 -8
  40. data/ext/imprimatur_native/core/src/read/xls/strings.rs +2 -1
  41. data/ext/imprimatur_native/core/src/read/xls.rs +1 -8
  42. data/ext/imprimatur_native/core/src/read.rs +6 -5
  43. data/ext/imprimatur_native/core/src/test_support/font.rs +37 -0
  44. data/ext/imprimatur_native/core/src/xlsx/validation.rs +28 -18
  45. data/ext/imprimatur_native/core/src/zip/reader.rs +1 -8
  46. data/ext/imprimatur_native/extconf.rb +18 -0
  47. data/ext/imprimatur_native/ffi/Cargo.toml +2 -2
  48. data/ext/imprimatur_native/ffi/src/document.rs +6 -0
  49. data/ext/imprimatur_native/ffi/src/lib.rs +7 -0
  50. data/ext/imprimatur_native/ffi/src/pdf.rs +73 -0
  51. data/ext/imprimatur_native/ffi/src/read.rs +18 -12
  52. data/lib/imprimatur/document.rb +4 -2
  53. data/lib/imprimatur/native.rb +7 -0
  54. data/lib/imprimatur/pdf_reader.rb +27 -0
  55. data/lib/imprimatur/reader.rb +48 -11
  56. data/lib/imprimatur/spreadsheet.rb +1 -1
  57. data/lib/imprimatur/version.rb +1 -1
  58. data/lib/imprimatur.rb +28 -3
  59. metadata +19 -4
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "imprimatur_native"
3
- version = "0.4.0"
3
+ version = "0.6.0"
4
4
  description = "Ruby bindings for the imprimatur crate"
5
5
  publish = false
6
6
  edition.workspace = true
@@ -16,7 +16,7 @@ workspace = true
16
16
 
17
17
  [dependencies]
18
18
  # Gems ship precompiled, so they carry every feature of the crate.
19
- imprimatur = { workspace = true, features = ["archive", "fonts", "json"] }
19
+ imprimatur = { workspace = true, features = ["archive", "fonts", "json", "isolation"] }
20
20
  serde_json.workspace = true
21
21
  magnus.workspace = true
22
22
  rb-sys.workspace = true
@@ -66,6 +66,11 @@ impl NativeDocument {
66
66
  Ok(())
67
67
  }
68
68
 
69
+ fn set_font_size(ruby: &Ruby, rb_self: &Self, points: f64) -> Result<(), Error> {
70
+ borrow(ruby, &rb_self.0, "document")?.set_font_size(points).or_raise(ruby)?;
71
+ Ok(())
72
+ }
73
+
69
74
  /// A PNG or JPEG from its bytes, `width` and `height` in points.
70
75
  fn picture(
71
76
  ruby: &Ruby,
@@ -201,6 +206,7 @@ pub fn define(ruby: &Ruby, native: RModule) -> Result<(), Error> {
201
206
  class.define_method("set_page", method!(NativeDocument::set_page, 2))?;
202
207
  class.define_method("set_page_points", method!(NativeDocument::set_page_points, 3))?;
203
208
  class.define_method("set_margin", method!(NativeDocument::set_margin, 1))?;
209
+ class.define_method("set_font_size", method!(NativeDocument::set_font_size, 1))?;
204
210
  class.define_method("picture", method!(NativeDocument::picture, 4))?;
205
211
  class.define_method("barcode", method!(NativeDocument::barcode, 4))?;
206
212
  class.define_method("heading", method!(NativeDocument::heading, 2))?;
@@ -8,6 +8,7 @@ mod barcode;
8
8
  mod blocking;
9
9
  mod convert;
10
10
  mod document;
11
+ mod pdf;
11
12
  mod read;
12
13
  mod sheets;
13
14
  mod style;
@@ -277,6 +278,9 @@ fn argument_error(ruby: &Ruby, message: impl Into<std::borrow::Cow<'static, str>
277
278
  /// Raises `Imprimatur::Error`, or the subclass of it the error has, all
278
279
  /// defined in `lib/imprimatur/error.rb`.
279
280
  fn domain_error(ruby: &Ruby, error: &imprimatur::Error) -> Error {
281
+ if let imprimatur::Error::InvalidPdfOption(message) = error {
282
+ return argument_error(ruby, *message);
283
+ }
280
284
  let name = match error {
281
285
  imprimatur::Error::NoTextLayer => "NoTextLayer",
282
286
  imprimatur::Error::PasswordRequired => "PasswordRequired",
@@ -322,6 +326,9 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
322
326
  native.define_singleton_method("markdown", function!(read::markdown_native, 4))?;
323
327
  native.define_singleton_method("content", function!(read::content_native, 5))?;
324
328
  native.define_singleton_method("images", function!(read::images_native, 4))?;
329
+ native.define_singleton_method("pdf_info", function!(pdf::info, 2))?;
330
+ native.define_singleton_method("pdf_pages", function!(pdf::pages, 3))?;
331
+ native.define_singleton_method("render_pdf_page", function!(pdf::render, 5))?;
325
332
  native.define_singleton_method("sheets", function!(sheets::sheets_native, 5))?;
326
333
  let zip_writer = native.define_class("ZipWriter", ruby.class_object())?;
327
334
  zip_writer.define_singleton_method("new", function!(archive::ZipWriter::new, 1))?;
@@ -0,0 +1,73 @@
1
+ //! PDF-specific entry points; all document work stays in the Rust core.
2
+
3
+ use imprimatur::read::{Limits, pdf};
4
+ use magnus::{Error, RString, Ruby};
5
+ use serde_json::json;
6
+ use std::path::PathBuf;
7
+
8
+ use crate::read::without_gvl;
9
+
10
+ type Keywords = (Option<usize>, Option<u64>, Option<f64>);
11
+
12
+ pub fn info(ruby: &Ruby, document: RString, keywords: Keywords) -> Result<RString, Error> {
13
+ let value = without_gvl(ruby, document, keywords, |data, limits| {
14
+ let info = pdf::info(data, limits)?;
15
+ Ok(json!({ "page_count": info.page_count, "encrypted": info.encrypted }).to_string())
16
+ })?;
17
+ Ok(ruby.str_new(&value))
18
+ }
19
+
20
+ pub fn pages(
21
+ ruby: &Ruby,
22
+ document: RString,
23
+ keywords: Keywords,
24
+ options: (u32, u32, usize, usize, usize),
25
+ ) -> Result<RString, Error> {
26
+ let (first_page, last_page, max_pages, max_page_bytes, max_output_bytes) = options;
27
+ let options =
28
+ pdf::ReadOptions { first_page, last_page, max_pages, max_page_bytes, max_output_bytes };
29
+ let value = without_gvl(ruby, document, keywords, |data, limits| {
30
+ let result = pdf::pages(data, &options, limits)?;
31
+ let pages: Vec<_> = result
32
+ .pages
33
+ .into_iter()
34
+ .map(|page| {
35
+ json!({
36
+ "page": page.number,
37
+ "status": page.status.as_str(),
38
+ "text": page.text,
39
+ "images": page.images,
40
+ "truncated": page.truncated,
41
+ "reason": page.reason,
42
+ })
43
+ })
44
+ .collect();
45
+ Ok(json!({
46
+ "page_count": result.info.page_count,
47
+ "encrypted": result.info.encrypted,
48
+ "pages": pages,
49
+ })
50
+ .to_string())
51
+ })?;
52
+ Ok(ruby.str_new(&value))
53
+ }
54
+
55
+ pub fn render(
56
+ ruby: &Ruby,
57
+ document: RString,
58
+ keywords: Keywords,
59
+ options: (u32, u16, u64, usize, bool),
60
+ worker: String,
61
+ memory_bytes: u64,
62
+ ) -> Result<RString, Error> {
63
+ let (page, max_side, max_pixels, max_output_bytes, grayscale) = options;
64
+ let worker = PathBuf::from(worker);
65
+ let options = pdf::RenderOptions { max_side, max_pixels, max_output_bytes, grayscale };
66
+ let process =
67
+ pdf::isolation::ProcessLimits { memory_bytes, ..pdf::isolation::ProcessLimits::default() };
68
+ let data = without_gvl(ruby, document, keywords, |data: &[u8], limits: &Limits| {
69
+ pdf::isolation::render_page_isolated(&worker, data, page, &options, limits, &process)
70
+ .map(|image| image.data)
71
+ })?;
72
+ Ok(ruby.str_from_slice(&data))
73
+ }
@@ -16,14 +16,11 @@ use serde_json::Value as Json;
16
16
  use crate::blocking::MAX_ATTEMPTS;
17
17
  use crate::{argument_error, domain_error};
18
18
 
19
- /// A read of a document's bytes within limits, run without the GVL.
20
- pub type Job<T> = fn(&[u8], &Limits) -> imprimatur::Result<T>;
21
-
22
19
  /// What crosses into the thread without the GVL, and comes back.
23
- struct Read<T> {
20
+ struct Read<T, F> {
24
21
  document: Vec<u8>,
25
22
  limits: Limits,
26
- job: Job<T>,
23
+ job: F,
27
24
  result: Option<imprimatur::Result<T>>,
28
25
  }
29
26
 
@@ -107,17 +104,20 @@ fn limits(
107
104
  }
108
105
 
109
106
  /// Runs `job` on the document's bytes on this thread, the GVL released.
110
- pub fn without_gvl<T>(
107
+ pub fn without_gvl<T, F>(
111
108
  ruby: &Ruby,
112
109
  document: RString,
113
110
  keywords: (Option<usize>, Option<u64>, Option<f64>),
114
- job: Job<T>,
115
- ) -> Result<T, Error> {
111
+ job: F,
112
+ ) -> Result<T, Error>
113
+ where
114
+ F: Fn(&[u8], &Limits) -> imprimatur::Result<T>,
115
+ {
116
116
  let document = crate::bytes(document);
117
117
  let cancel = Arc::new(AtomicBool::new(false));
118
118
  let limits = limits(ruby, &cancel, keywords)?;
119
119
  let mut read = Read { document, limits, job, result: None };
120
- let read_pointer: *mut Read<T> = &raw mut read;
120
+ let read_pointer: *mut Read<T, F> = &raw mut read;
121
121
  let flag_pointer = Arc::as_ptr(&cancel).cast_mut();
122
122
  for _ in 0..MAX_ATTEMPTS {
123
123
  // SAFETY: `read` and `cancel` outlive the call, which returns only
@@ -127,12 +127,15 @@ pub fn without_gvl<T>(
127
127
  #[allow(unsafe_code)]
128
128
  unsafe {
129
129
  rb_sys::rb_thread_call_without_gvl2(
130
- Some(run::<T>),
130
+ Some(run::<T, F>),
131
131
  read_pointer.cast(),
132
132
  Some(stop),
133
133
  flag_pointer.cast(),
134
134
  );
135
135
  }
136
+ // In particular, SIGCHLD wakes an isolated render on normal completion.
137
+ // Deliver real Ruby interrupts even when the native job succeeded.
138
+ ruby.thread_check_ints()?;
136
139
  match read.result.take() {
137
140
  Some(Ok(value)) => return Ok(value),
138
141
  // Cut short by Ruby: raise what Ruby has to raise (Thread#raise,
@@ -153,10 +156,13 @@ pub fn without_gvl<T>(
153
156
  /// The read itself, on the thread without the GVL. A panic would abort
154
157
  /// the process at this C boundary; it becomes an error instead.
155
158
  #[allow(unsafe_code)]
156
- unsafe extern "C" fn run<T>(data: *mut c_void) -> *mut c_void {
159
+ unsafe extern "C" fn run<T, F>(data: *mut c_void) -> *mut c_void
160
+ where
161
+ F: Fn(&[u8], &Limits) -> imprimatur::Result<T>,
162
+ {
157
163
  // SAFETY: `data` is the `Read<T>` without_gvl passed, alive and not
158
164
  // otherwise borrowed until this returns.
159
- let read = unsafe { &mut *data.cast::<Read<T>>() };
165
+ let read = unsafe { &mut *data.cast::<Read<T, F>>() };
160
166
  let result = catch_unwind(AssertUnwindSafe(|| (read.job)(&read.document, &read.limits)));
161
167
  read.result = Some(result.unwrap_or_else(|_| {
162
168
  Err(imprimatur::Error::Malformed {
@@ -21,10 +21,11 @@ module Imprimatur
21
21
  # @param page_size [:a4, :letter, Array(Numeric, Numeric)] or its width
22
22
  # and height in points, 36 to 1584 a side
23
23
  # @param margin [Numeric, nil] points on every side, one inch (72) unless set
24
+ # @param font_size [Numeric, nil] body text in points, 1 to 1638 in steps of 0.5; 11 unless set
24
25
  # @param page_numbers [Boolean] number pages "1 / N" in the footer
25
26
  # @raise [ArgumentError] an unknown page size
26
- # @raise [Imprimatur::Error] a page size or margin out of range
27
- def initialize(page_size: :a4, margin: nil, page_numbers: false)
27
+ # @raise [Imprimatur::Error] a page size, margin or font size out of range
28
+ def initialize(page_size: :a4, margin: nil, font_size: nil, page_numbers: false)
28
29
  @native = Native::Document.new
29
30
  numbers = page_numbers ? true : false
30
31
  if page_size.is_a?(Array)
@@ -34,6 +35,7 @@ module Imprimatur
34
35
  @native.set_page(page_size.to_s, numbers)
35
36
  end
36
37
  @native.set_margin(Float(margin)) if margin
38
+ @native.set_font_size(Float(font_size)) if font_size
37
39
  end
38
40
 
39
41
  # A PNG or JPEG in the flow, at +width+ or +height+ points (the other
@@ -15,3 +15,10 @@ rescue LoadError => e
15
15
  raise LoadError, "imprimatur: the native extension did not load (#{e.message}). " \
16
16
  'Install a platform gem, or build it with `rake compile` (toolchain: https://rustup.rs).'
17
17
  end
18
+
19
+ module Imprimatur
20
+ # Always use the companion of the loaded extension, never PATH.
21
+ RENDER_WORKER = File.join(File.dirname($LOADED_FEATURES.find do |path|
22
+ path.end_with?("/imprimatur_native.#{RbConfig::CONFIG['DLEXT']}")
23
+ end), 'imprimatur-render-worker').freeze
24
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Imprimatur
4
+ # Keyword handling only; parsing, limits and rendering live in the native core.
5
+ module PdfReader
6
+ def self.info(document, max_bytes: nil, **limits)
7
+ JSON.parse(Native.pdf_info(Reader.read(document, max_bytes), Reader.limits(max_bytes:, **limits)),
8
+ symbolize_names: true)
9
+ end
10
+
11
+ def self.pages(document, first_page: 1, last_page: 0xFFFFFFFF, max_pages: 500,
12
+ max_page_bytes: 256 * 1024, max_output_bytes: 8 * 1024 * 1024,
13
+ max_bytes: nil, **limits)
14
+ options = [first_page, last_page, max_pages, max_page_bytes, max_output_bytes]
15
+ JSON.parse(Native.pdf_pages(Reader.read(document, max_bytes), Reader.limits(max_bytes:, **limits), options),
16
+ symbolize_names: true)
17
+ end
18
+
19
+ def self.render(document, page:, max_side: 1568, max_pixels: 4_000_000,
20
+ max_output_bytes: 8 * 1024 * 1024, grayscale: true,
21
+ memory_bytes: 1024 * 1024 * 1024, max_bytes: nil, **limits)
22
+ Native.render_pdf_page(Reader.read(document, max_bytes), Reader.limits(max_bytes:, **limits),
23
+ [page, max_side, max_pixels, max_output_bytes, grayscale],
24
+ RENDER_WORKER, memory_bytes)
25
+ end
26
+ end
27
+ end
@@ -12,22 +12,29 @@ module Imprimatur
12
12
  # Documents in: Markdown, their structure, their images. See
13
13
  # {Imprimatur.markdown}, {Imprimatur.content} and {Imprimatur.images}.
14
14
  module Reader
15
- # File signatures a document's bytes start with; a String that starts
16
- # with neither is taken for a path.
15
+ # File signatures a document's bytes start with; a String that is not
16
+ # binary and starts with neither is taken for a path.
17
17
  SIGNATURES = ["%PDF-".b, "PK\x03\x04".b, "PK\x05\x06".b, "\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1".b].freeze
18
18
  private_constant :SIGNATURES
19
19
 
20
+ # The largest document read in unless told, as the native core's own limit.
21
+ MAX_BYTES = 512 * 1024 * 1024
22
+ private_constant :MAX_BYTES
23
+
20
24
  # @see Imprimatur.markdown
21
- def self.markdown(document, **) = Native.markdown(read(document), *limits(**))
25
+ def self.markdown(document, max_bytes: nil, **)
26
+ Native.markdown(read(document, max_bytes), *limits(max_bytes:, **))
27
+ end
22
28
 
23
29
  # @see Imprimatur.content
24
- def self.content(document, styles: false, **)
25
- JSON.parse(Native.content(read(document), *limits(**), styles ? true : false), symbolize_names: true)
30
+ def self.content(document, styles: false, max_bytes: nil, **)
31
+ bytes = read(document, max_bytes)
32
+ JSON.parse(Native.content(bytes, *limits(max_bytes:, **), styles ? true : false), symbolize_names: true)
26
33
  end
27
34
 
28
35
  # @see Imprimatur.images
29
- def self.images(document, **)
30
- Native.images(read(document), *limits(**)).map do |data, description|
36
+ def self.images(document, max_bytes: nil, **)
37
+ Native.images(read(document, max_bytes), *limits(max_bytes:, **)).map do |data, description|
31
38
  found = JSON.parse(description, symbolize_names: true)
32
39
  Image.new(data:, **Image.members.drop(1).to_h { [it, found[it]] })
33
40
  end
@@ -38,22 +45,52 @@ module Imprimatur
38
45
  [max_bytes, max_inflated, timeout && Float(timeout)]
39
46
  end
40
47
 
48
+ # A path or an IO is read no further than +max_bytes+ (one byte past it
49
+ # tells it is passed): an endless stream or a huge file stops there.
50
+ #
41
51
  # @api private
42
- def self.read(document)
52
+ def self.read(document, max_bytes = nil)
53
+ limit = max_bytes.is_a?(Integer) && max_bytes >= 0 ? max_bytes : MAX_BYTES
43
54
  case document
44
- when Pathname then File.binread(document)
45
- when String then bytes?(document) ? document : File.binread(document)
55
+ when Pathname then file(document, limit)
56
+ when String then bytes?(document) ? document : file(document, limit)
46
57
  else
47
58
  raise TypeError, "expected a path, bytes or an IO, not #{document.class}" unless document.respond_to?(:read)
48
59
 
49
- document.read.to_s.b
60
+ within(document.read(limit + 1).to_s.b, limit)
50
61
  end
51
62
  end
52
63
 
64
+ # A binary String (what +File.binread+, a binary IO or a download give)
65
+ # is bytes, whatever it holds: uploaded bytes never name a file to read.
66
+ #
53
67
  # @api private
54
68
  def self.bytes?(string)
69
+ return true if string.encoding == Encoding::BINARY
70
+
55
71
  head = string.byteslice(0, 1024).b
56
72
  SIGNATURES.any? { head.start_with?(it) } || head.include?("%PDF-") || head.include?("\0")
57
73
  end
74
+
75
+ # A regular file's bytes. Opened without blocking and checked through
76
+ # the open file, so a pipe cannot hang the read, a device such as
77
+ # +/dev/zero+ is refused, and the file cannot be swapped in between.
78
+ #
79
+ # @api private
80
+ def self.file(path, limit)
81
+ File.open(path, File::RDONLY | File::NONBLOCK | File::BINARY) do |io|
82
+ stat = io.stat
83
+ raise UnsupportedDocument, "cannot read this document: #{path} is not a regular file" unless stat.file?
84
+
85
+ within(stat.size > limit ? '' : io.read(limit + 1).to_s, limit, stat.size)
86
+ end
87
+ end
88
+
89
+ # @api private
90
+ def self.within(bytes, limit, size = bytes.bytesize)
91
+ raise LimitExceeded, 'the document exceeds the input size limit' if [size, bytes.bytesize].max > limit
92
+
93
+ bytes
94
+ end
58
95
  end
59
96
  end
@@ -123,7 +123,7 @@ module Imprimatur
123
123
  # @raise [UnsupportedDocument] a PDF, a Word document or another format
124
124
  # @raise [Imprimatur::Error] a damaged workbook, or past a limit
125
125
  def self.open(document, styles: false, **limits)
126
- new(Native.sheets(Reader.read(document), *Reader.limits(**limits), styles ? true : false))
126
+ new(Native.sheets(Reader.read(document, limits[:max_bytes]), *Reader.limits(**limits), styles ? true : false))
127
127
  end
128
128
 
129
129
  # @return [Array<Sheet>] in the workbook's order, hidden ones included
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Imprimatur
4
- VERSION = '0.4.0'
4
+ VERSION = '0.6.0'
5
5
  end
data/lib/imprimatur.rb CHANGED
@@ -10,6 +10,7 @@ require_relative 'imprimatur/barcode'
10
10
  require_relative 'imprimatur/xlsx'
11
11
  require_relative 'imprimatur/document'
12
12
  require_relative 'imprimatur/reader'
13
+ require_relative 'imprimatur/pdf_reader'
13
14
  require_relative 'imprimatur/spreadsheet'
14
15
  require_relative 'imprimatur/zip'
15
16
 
@@ -45,7 +46,7 @@ module Imprimatur
45
46
  # Builds a {Document} and renders it as PDF. With a path, saves it there
46
47
  # and returns the path; without, returns the bytes.
47
48
  #
48
- # @param options [Hash] +page_size:+ and +page_numbers:+, see {Document#initialize}
49
+ # @param options [Hash] +page_size:+, +margin:+, +font_size:+ and +page_numbers:+, see {Document#initialize}
49
50
  # @yieldparam document [Document]
50
51
  def pdf(path = nil, **options, &) = Document.build(:pdf, path, **options, &)
51
52
 
@@ -66,7 +67,8 @@ module Imprimatur
66
67
  # Imprimatur.markdown(File.open('sales.xlsx'))
67
68
  #
68
69
  # @param document [String, Pathname, #read] a path, the document's bytes
69
- # (told apart by their signature), or an IO
70
+ # (a binary String always is; another String only with a document's
71
+ # signature), or an IO. A path must name a regular file.
70
72
  # @param timeout [Numeric, nil] seconds the read may take (60), +nil+ for none
71
73
  # @param max_bytes [Integer, nil] the largest document accepted (512 MiB)
72
74
  # @param max_inflated [Integer, nil] decompressed bytes allowed in all (1 GiB)
@@ -91,7 +93,11 @@ module Imprimatur
91
93
  # sheet = Imprimatur.content('sales.xlsx')[:sheets].first
92
94
  # sheet[:rows][1][:cells] # => [{cell: "A2", type: "text", value: "North"}, ...]
93
95
  #
94
- # Takes the arguments of {.markdown}, and raises as it does. With
96
+ # Unlike {.markdown}, a PDF without text still returns every page, with
97
+ # empty +:blocks+ and its image count, rather than raising {NoTextLayer}.
98
+ # This keeps scanned and blank pages visible to OCR callers.
99
+ #
100
+ # Takes the arguments of {.markdown}, and raises as it does otherwise. With
95
101
  # +styles: true+ each sheet also lists its formatted cells' +:styles+:
96
102
  # font, size, bold, italic, underline, ARGB color and fill, alignment,
97
103
  # wrap, border and number format.
@@ -99,6 +105,25 @@ module Imprimatur
99
105
  # @return [Hash] with symbol keys; +:format+ is +"pdf"+, +"xlsx"+ or +"docx"+
100
106
  def content(document, ...) = Reader.content(document, ...)
101
107
 
108
+ # PDF metadata without reading page content.
109
+ # @return [Hash] +page_count:+ and +encrypted:+
110
+ def pdf_info(document, ...) = PdfReader.info(document, ...)
111
+
112
+ # A bounded, one-based page selection. Each page has its status, text,
113
+ # image count (nil when unknown), truncation flag and failure reason.
114
+ # Takes +first_page:+, +last_page:+, +max_pages:+ (500),
115
+ # +max_page_bytes:+ (256 KiB), +max_output_bytes:+ (8 MiB), and read limits.
116
+ # @return [Hash] +page_count:+, +encrypted:+ and +pages:+
117
+ def pdf_pages(document, ...) = PdfReader.pages(document, ...)
118
+
119
+ # A full page as an opaque PNG, not just its embedded images.
120
+ # Takes +page:+ (one-based), +max_side:+ (1568), +max_pixels:+ (4 million),
121
+ # +max_output_bytes:+ (8 MiB), +grayscale:+ (true), and read limits.
122
+ # Rendering checks time/cancellation before and after Hayro, not during it.
123
+ # Untrusted documents still need an externally limited worker process.
124
+ # @return [String] PNG bytes
125
+ def render_pdf_page(document, ...) = PdfReader.render(document, ...)
126
+
102
127
  # A workbook's sheets and their values, read from the same documents
103
128
  # {.markdown} takes (+.xlsx+, +.xls+, +.ods+); see {Spreadsheet}.
104
129
  #
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: imprimatur
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Abdelkader Boudih
@@ -24,9 +24,10 @@ dependencies:
24
24
  - !ruby/object:Gem::Version
25
25
  version: 0.9.130
26
26
  description: 'A Ruby DSL over a native core that writes Excel workbooks, and PDF and
27
- Word documents from one model, with its own XML, zip and PDF writers: no third-party
28
- format stack. It reads PDF, XLSX, XLS, ODS and DOCX back: as Markdown, as typed
29
- structure, and their images. Precompiled for Linux, macOS and FreeBSD; CRuby only.'
27
+ Word documents from one model, with its own XML, zip and PDF readers and writers.
28
+ It reads PDF, XLSX, XLS, ODS and DOCX back: as Markdown, as typed structure, and
29
+ their images. Full-page PDF rendering uses the pure-Rust Hayro renderer. Precompiled
30
+ for Linux, macOS and FreeBSD; CRuby only.'
30
31
  email:
31
32
  - terminale@gmail.com
32
33
  executables: []
@@ -58,9 +59,12 @@ files:
58
59
  - ext/imprimatur_native/core/src/barcode/ean.rs
59
60
  - ext/imprimatur_native/core/src/barcode/gs1.rs
60
61
  - ext/imprimatur_native/core/src/barcode/matrix.rs
62
+ - ext/imprimatur_native/core/src/bin/imprimatur-render-worker.rs
63
+ - ext/imprimatur_native/core/src/bytes.rs
61
64
  - ext/imprimatur_native/core/src/doc.rs
62
65
  - ext/imprimatur_native/core/src/doc/paragraph.rs
63
66
  - ext/imprimatur_native/core/src/doc/picture.rs
67
+ - ext/imprimatur_native/core/src/doc/size.rs
64
68
  - ext/imprimatur_native/core/src/doc/symbol.rs
65
69
  - ext/imprimatur_native/core/src/doc/table.rs
66
70
  - ext/imprimatur_native/core/src/docx.rs
@@ -94,22 +98,30 @@ files:
94
98
  - ext/imprimatur_native/core/src/read/ole.rs
95
99
  - ext/imprimatur_native/core/src/read/package.rs
96
100
  - ext/imprimatur_native/core/src/read/pdf.rs
101
+ - ext/imprimatur_native/core/src/read/pdf/bounded.rs
97
102
  - ext/imprimatur_native/core/src/read/pdf/cmap.rs
98
103
  - ext/imprimatur_native/core/src/read/pdf/content.rs
99
104
  - ext/imprimatur_native/core/src/read/pdf/content/geometry.rs
100
105
  - ext/imprimatur_native/core/src/read/pdf/content/operands.rs
106
+ - ext/imprimatur_native/core/src/read/pdf/content/state.rs
101
107
  - ext/imprimatur_native/core/src/read/pdf/crypt.rs
102
108
  - ext/imprimatur_native/core/src/read/pdf/document.rs
109
+ - ext/imprimatur_native/core/src/read/pdf/document/pages.rs
103
110
  - ext/imprimatur_native/core/src/read/pdf/filter.rs
104
111
  - ext/imprimatur_native/core/src/read/pdf/font.rs
105
112
  - ext/imprimatur_native/core/src/read/pdf/glyphs.rs
106
113
  - ext/imprimatur_native/core/src/read/pdf/glyphs.txt
107
114
  - ext/imprimatur_native/core/src/read/pdf/images.rs
115
+ - ext/imprimatur_native/core/src/read/pdf/isolation.rs
116
+ - ext/imprimatur_native/core/src/read/pdf/isolation/protocol.rs
117
+ - ext/imprimatur_native/core/src/read/pdf/isolation/system.rs
118
+ - ext/imprimatur_native/core/src/read/pdf/isolation/worker.rs
108
119
  - ext/imprimatur_native/core/src/read/pdf/layout.rs
109
120
  - ext/imprimatur_native/core/src/read/pdf/layout/bidi.rs
110
121
  - ext/imprimatur_native/core/src/read/pdf/lexer.rs
111
122
  - ext/imprimatur_native/core/src/read/pdf/object.rs
112
123
  - ext/imprimatur_native/core/src/read/pdf/object/name.rs
124
+ - ext/imprimatur_native/core/src/read/pdf/render.rs
113
125
  - ext/imprimatur_native/core/src/read/pdf/xref.rs
114
126
  - ext/imprimatur_native/core/src/read/span.rs
115
127
  - ext/imprimatur_native/core/src/read/xls.rs
@@ -118,6 +130,7 @@ files:
118
130
  - ext/imprimatur_native/core/src/read/xlsx/formats.rs
119
131
  - ext/imprimatur_native/core/src/script.rs
120
132
  - ext/imprimatur_native/core/src/style.rs
133
+ - ext/imprimatur_native/core/src/test_support/font.rs
121
134
  - ext/imprimatur_native/core/src/xlsx.rs
122
135
  - ext/imprimatur_native/core/src/xlsx/autofit.rs
123
136
  - ext/imprimatur_native/core/src/xlsx/cell.rs
@@ -142,6 +155,7 @@ files:
142
155
  - ext/imprimatur_native/ffi/src/convert.rs
143
156
  - ext/imprimatur_native/ffi/src/document.rs
144
157
  - ext/imprimatur_native/ffi/src/lib.rs
158
+ - ext/imprimatur_native/ffi/src/pdf.rs
145
159
  - ext/imprimatur_native/ffi/src/read.rs
146
160
  - ext/imprimatur_native/ffi/src/sheets.rs
147
161
  - ext/imprimatur_native/ffi/src/style.rs
@@ -152,6 +166,7 @@ files:
152
166
  - lib/imprimatur/error.rb
153
167
  - lib/imprimatur/formula.rb
154
168
  - lib/imprimatur/native.rb
169
+ - lib/imprimatur/pdf_reader.rb
155
170
  - lib/imprimatur/reader.rb
156
171
  - lib/imprimatur/spreadsheet.rb
157
172
  - lib/imprimatur/style.rb