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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +28 -0
- data/Cargo.lock +472 -5
- data/Cargo.toml +16 -1
- data/README.md +59 -7
- data/ext/imprimatur_native/cli/Cargo.toml +1 -1
- data/ext/imprimatur_native/cli/src/app.rs +3 -1
- data/ext/imprimatur_native/core/Cargo.toml +29 -13
- data/ext/imprimatur_native/core/README.md +13 -1
- data/ext/imprimatur_native/core/src/bin/imprimatur-render-worker.rs +10 -0
- data/ext/imprimatur_native/core/src/bytes.rs +16 -0
- data/ext/imprimatur_native/core/src/doc/picture.rs +3 -7
- data/ext/imprimatur_native/core/src/doc/size.rs +14 -0
- data/ext/imprimatur_native/core/src/doc/symbol.rs +3 -5
- data/ext/imprimatur_native/core/src/doc.rs +24 -0
- data/ext/imprimatur_native/core/src/docx.rs +6 -5
- data/ext/imprimatur_native/core/src/error.rs +6 -0
- data/ext/imprimatur_native/core/src/font.rs +12 -0
- data/ext/imprimatur_native/core/src/lib.rs +6 -0
- data/ext/imprimatur_native/core/src/pdf/layout.rs +13 -5
- data/ext/imprimatur_native/core/src/pdf/objects.rs +1 -1
- data/ext/imprimatur_native/core/src/pdf/truetype/descriptor.rs +1 -0
- data/ext/imprimatur_native/core/src/read/budget.rs +10 -5
- data/ext/imprimatur_native/core/src/read/content.rs +16 -0
- data/ext/imprimatur_native/core/src/read/image/dimensions.rs +2 -2
- data/ext/imprimatur_native/core/src/read/ole.rs +1 -8
- data/ext/imprimatur_native/core/src/read/pdf/bounded.rs +190 -0
- data/ext/imprimatur_native/core/src/read/pdf/content/state.rs +50 -0
- data/ext/imprimatur_native/core/src/read/pdf/content.rs +43 -54
- data/ext/imprimatur_native/core/src/read/pdf/document/pages.rs +140 -0
- data/ext/imprimatur_native/core/src/read/pdf/document.rs +20 -110
- data/ext/imprimatur_native/core/src/read/pdf/filter.rs +25 -13
- data/ext/imprimatur_native/core/src/read/pdf/font.rs +4 -3
- data/ext/imprimatur_native/core/src/read/pdf/isolation/protocol.rs +155 -0
- data/ext/imprimatur_native/core/src/read/pdf/isolation/system.rs +71 -0
- data/ext/imprimatur_native/core/src/read/pdf/isolation/worker.rs +43 -0
- data/ext/imprimatur_native/core/src/read/pdf/isolation.rs +166 -0
- data/ext/imprimatur_native/core/src/read/pdf/render.rs +206 -0
- data/ext/imprimatur_native/core/src/read/pdf.rs +16 -8
- data/ext/imprimatur_native/core/src/read/xls/strings.rs +2 -1
- data/ext/imprimatur_native/core/src/read/xls.rs +1 -8
- data/ext/imprimatur_native/core/src/read.rs +6 -5
- data/ext/imprimatur_native/core/src/test_support/font.rs +37 -0
- data/ext/imprimatur_native/core/src/xlsx/validation.rs +28 -18
- data/ext/imprimatur_native/core/src/zip/reader.rs +1 -8
- data/ext/imprimatur_native/extconf.rb +18 -0
- data/ext/imprimatur_native/ffi/Cargo.toml +2 -2
- data/ext/imprimatur_native/ffi/src/document.rs +6 -0
- data/ext/imprimatur_native/ffi/src/lib.rs +7 -0
- data/ext/imprimatur_native/ffi/src/pdf.rs +73 -0
- data/ext/imprimatur_native/ffi/src/read.rs +18 -12
- data/lib/imprimatur/document.rb +4 -2
- data/lib/imprimatur/native.rb +7 -0
- data/lib/imprimatur/pdf_reader.rb +27 -0
- data/lib/imprimatur/reader.rb +48 -11
- data/lib/imprimatur/spreadsheet.rb +1 -1
- data/lib/imprimatur/version.rb +1 -1
- data/lib/imprimatur.rb +28 -3
- metadata +19 -4
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "imprimatur_native"
|
|
3
|
-
version = "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:
|
|
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:
|
|
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 {
|
data/lib/imprimatur/document.rb
CHANGED
|
@@ -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
|
|
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
|
data/lib/imprimatur/native.rb
CHANGED
|
@@ -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
|
data/lib/imprimatur/reader.rb
CHANGED
|
@@ -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
|
|
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,
|
|
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
|
-
|
|
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
|
|
45
|
-
when String then bytes?(document) ? 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
|
data/lib/imprimatur/version.rb
CHANGED
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
|
-
# (
|
|
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
|
-
#
|
|
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
|
+
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
|
|
28
|
-
|
|
29
|
-
|
|
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
|