bare-x-to-pdf 0.3.0 → 0.4.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.
- package/README.md +40 -5
- package/binding.c +236 -40
- package/cmake/rust.cmake +2 -2
- package/core/src/doc/mod.rs +29 -16
- package/core/src/docx/mod.rs +3 -5
- package/core/src/docx/numbering.rs +8 -4
- package/core/src/lib.rs +13 -9
- package/core/src/limits.rs +42 -0
- package/core/src/markdown/mod.rs +6 -0
- package/core/src/odt/geometry.rs +21 -0
- package/core/src/odt/mod.rs +13 -9
- package/core/src/odt/odp.rs +3 -5
- package/core/src/odt/styles.rs +2 -2
- package/core/src/pptx/mod.rs +23 -8
- package/core/src/pptx/text.rs +2 -2
- package/core/src/typst_backend/emit.rs +42 -19
- package/core/src/typst_backend/fonts.rs +1 -1
- package/core/src/typst_backend/images.rs +76 -0
- package/core/src/typst_backend/mod.rs +48 -6
- package/core/src/xlsx/biff.rs +15 -4
- package/core/src/xlsx/biff_art.rs +8 -2
- package/core/src/xlsx/biff_chart.rs +10 -1
- package/core/src/xlsx/formula.rs +30 -3
- package/core/src/xlsx/mod.rs +90 -24
- package/core/src/xlsx/ods.rs +15 -12
- package/core/src/xml.rs +6 -1
- package/index.d.ts +3 -1
- package/index.js +130 -38
- package/package.json +16 -13
package/README.md
CHANGED
|
@@ -47,17 +47,20 @@ npm install --os=ios --cpu=arm64
|
|
|
47
47
|
```js
|
|
48
48
|
const converter = require('bare-x-to-pdf')
|
|
49
49
|
|
|
50
|
-
const pdf = converter.convert(docxBytes, { to: 'pdf' })
|
|
50
|
+
const pdf = await converter.convert(docxBytes, { to: 'pdf' })
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
Input is a `Uint8Array` or `ArrayBuffer` and the result is a `Buffer`.
|
|
54
|
-
|
|
53
|
+
Input is a `Uint8Array` or `ArrayBuffer` and the result is a `Buffer`. `convert()` runs on
|
|
54
|
+
the libuv thread pool and does not block the JavaScript thread; `convertSync()` runs on the
|
|
55
|
+
calling thread.
|
|
55
56
|
|
|
56
57
|
## API
|
|
57
58
|
|
|
58
|
-
### `convert(input, options)`
|
|
59
|
+
### `const pdf = await convert(input, options)`
|
|
59
60
|
|
|
60
|
-
Convert `input` and
|
|
61
|
+
Convert `input` off the JavaScript thread and resolve with a `Buffer`. The input is copied,
|
|
62
|
+
so it may be changed or transferred once the call returns. Invalid options and conversion
|
|
63
|
+
errors reject the promise.
|
|
61
64
|
|
|
62
65
|
- `options.to` — target format, `'pdf'`. Required.
|
|
63
66
|
- `options.from` — source format; sniffed from the bytes when omitted. Markdown has no
|
|
@@ -65,6 +68,11 @@ Convert `input` and return a `Buffer`.
|
|
|
65
68
|
URIs, since the input is bytes with no directory to resolve relative paths against.
|
|
66
69
|
- `options.fontsDir` — a directory of fonts to use instead of the bundled set.
|
|
67
70
|
|
|
71
|
+
### `const pdf = convertSync(input, options)`
|
|
72
|
+
|
|
73
|
+
Convert `input` on the calling thread and return a `Buffer`. Takes the same options as
|
|
74
|
+
`convert()`.
|
|
75
|
+
|
|
68
76
|
### `detect(input)`
|
|
69
77
|
|
|
70
78
|
Sniff the format from the bytes and return `'docx'`, `'pptx'`, `'xlsx'`, `'doc'`, `'xls'`,
|
|
@@ -84,6 +92,33 @@ Return every supported pair as `{ from, to }`.
|
|
|
84
92
|
|
|
85
93
|
Path to the bundled fonts directory.
|
|
86
94
|
|
|
95
|
+
## Limits
|
|
96
|
+
|
|
97
|
+
Documents are often untrusted, so the engine caps what a small file can make it allocate or
|
|
98
|
+
lay out. A document past a hard limit is rejected with an error. One past a soft limit still
|
|
99
|
+
converts, with the value clamped.
|
|
100
|
+
|
|
101
|
+
Rejected:
|
|
102
|
+
|
|
103
|
+
- An archive part larger than 128 MB uncompressed.
|
|
104
|
+
- XML nested more than 512 elements deep.
|
|
105
|
+
- A spreadsheet that would print more than 30,000 cells.
|
|
106
|
+
|
|
107
|
+
Clamped:
|
|
108
|
+
|
|
109
|
+
- Table spans: 1–1,000 columns.
|
|
110
|
+
- Text columns: 1–64.
|
|
111
|
+
- Pages: 72–14,400 pt on each side, with margins scaled to fit.
|
|
112
|
+
- Lengths: NaN and infinity become 0; any other length is held within ±1,000,000 pt.
|
|
113
|
+
- List counters: above 32,767, letter and Roman labels fall back to decimal.
|
|
114
|
+
- Charts: up to 100,000 points per series (in .xls, 32,000 per series and 256,000 per chart).
|
|
115
|
+
- Formulas: nesting depth up to 256.
|
|
116
|
+
- Images: more than 50 megapixels, or ones that cannot be decoded, are drawn as blank
|
|
117
|
+
space.
|
|
118
|
+
- Tables nested more than 12 deep: the inner tables are flattened into paragraphs.
|
|
119
|
+
|
|
120
|
+
Spreadsheet pages with no content are not printed, as in LibreOffice Calc.
|
|
121
|
+
|
|
87
122
|
## Development
|
|
88
123
|
|
|
89
124
|
The platform packages in `npm/` are npm workspaces, and each declares its own `os` and `cpu`
|
package/binding.c
CHANGED
|
@@ -6,13 +6,39 @@
|
|
|
6
6
|
#include <stdlib.h>
|
|
7
7
|
#include <string.h>
|
|
8
8
|
#include <utf.h>
|
|
9
|
+
#include <uv.h>
|
|
9
10
|
|
|
10
11
|
#include "simple_converter.h"
|
|
11
12
|
|
|
13
|
+
typedef struct {
|
|
14
|
+
const uint8_t *input;
|
|
15
|
+
size_t input_len;
|
|
16
|
+
uint8_t *owned_input;
|
|
17
|
+
char *from;
|
|
18
|
+
char *to;
|
|
19
|
+
char *fonts_dir;
|
|
20
|
+
sc_buffer_t out;
|
|
21
|
+
sc_buffer_t error;
|
|
22
|
+
int status;
|
|
23
|
+
} simple_converter_job_t;
|
|
24
|
+
|
|
25
|
+
typedef struct {
|
|
26
|
+
uv_work_t work;
|
|
27
|
+
js_env_t *env;
|
|
28
|
+
js_deferred_t *deferred;
|
|
29
|
+
js_deferred_teardown_t *teardown;
|
|
30
|
+
bool exiting;
|
|
31
|
+
simple_converter_job_t job;
|
|
32
|
+
} simple_converter_req_t;
|
|
33
|
+
|
|
12
34
|
static bool
|
|
13
35
|
simple_converter__string(js_env_t *env, js_value_t *value, char **result) {
|
|
14
36
|
int err;
|
|
15
37
|
|
|
38
|
+
bool is_string;
|
|
39
|
+
err = js_is_string(env, value, &is_string);
|
|
40
|
+
if (err != 0 || !is_string) return false;
|
|
41
|
+
|
|
16
42
|
size_t len = 0;
|
|
17
43
|
err = js_get_value_string_utf8(env, value, NULL, 0, &len);
|
|
18
44
|
if (err != 0) return false;
|
|
@@ -27,14 +53,32 @@ simple_converter__string(js_env_t *env, js_value_t *value, char **result) {
|
|
|
27
53
|
}
|
|
28
54
|
|
|
29
55
|
str[len] = '\0';
|
|
56
|
+
|
|
57
|
+
if (strlen(str) != len) {
|
|
58
|
+
free(str);
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
|
|
30
62
|
*result = str;
|
|
31
63
|
return true;
|
|
32
64
|
}
|
|
33
65
|
|
|
34
|
-
static
|
|
35
|
-
|
|
66
|
+
static void
|
|
67
|
+
simple_converter__job_release(simple_converter_job_t *job) {
|
|
68
|
+
free(job->owned_input);
|
|
69
|
+
free(job->from);
|
|
70
|
+
free(job->to);
|
|
71
|
+
free(job->fonts_dir);
|
|
72
|
+
sc_free(&job->out);
|
|
73
|
+
sc_free(&job->error);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
static bool
|
|
77
|
+
simple_converter__job_init(js_env_t *env, js_callback_info_t *info, bool copy_input, simple_converter_job_t *job) {
|
|
36
78
|
int err;
|
|
37
79
|
|
|
80
|
+
memset(job, 0, sizeof(*job));
|
|
81
|
+
|
|
38
82
|
size_t argc = 4;
|
|
39
83
|
js_value_t *argv[4];
|
|
40
84
|
|
|
@@ -44,68 +88,219 @@ simple_converter_convert(js_env_t *env, js_callback_info_t *info) {
|
|
|
44
88
|
if (argc < 4) {
|
|
45
89
|
err = js_throw_error(env, NULL, "convert expects (bytes, from, to, fontsDir)");
|
|
46
90
|
assert(err == 0);
|
|
47
|
-
return
|
|
91
|
+
return false;
|
|
48
92
|
}
|
|
49
93
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
err
|
|
53
|
-
|
|
94
|
+
bool is_typedarray;
|
|
95
|
+
err = js_is_typedarray(env, argv[0], &is_typedarray);
|
|
96
|
+
assert(err == 0);
|
|
97
|
+
|
|
98
|
+
js_typedarray_type_t type = js_int8array;
|
|
99
|
+
uint8_t *input = NULL;
|
|
100
|
+
size_t input_len = 0;
|
|
101
|
+
if (is_typedarray) {
|
|
102
|
+
err = js_get_typedarray_info(env, argv[0], &type, (void **) &input, &input_len, NULL, NULL);
|
|
103
|
+
}
|
|
104
|
+
if (!is_typedarray || err != 0 || type != js_uint8array) {
|
|
54
105
|
err = js_throw_error(env, NULL, "input must be a Uint8Array");
|
|
55
106
|
assert(err == 0);
|
|
56
|
-
return
|
|
107
|
+
return false;
|
|
57
108
|
}
|
|
58
109
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
bool ok = simple_converter__string(env, argv[1], &from) &&
|
|
64
|
-
simple_converter__string(env, argv[2], &to) &&
|
|
65
|
-
simple_converter__string(env, argv[3], &fonts_dir);
|
|
110
|
+
bool ok = simple_converter__string(env, argv[1], &job->from) &&
|
|
111
|
+
simple_converter__string(env, argv[2], &job->to) &&
|
|
112
|
+
simple_converter__string(env, argv[3], &job->fonts_dir);
|
|
66
113
|
|
|
67
114
|
if (!ok) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
free(fonts_dir);
|
|
71
|
-
err = js_throw_error(env, NULL, "from, to and fontsDir must be strings");
|
|
115
|
+
simple_converter__job_release(job);
|
|
116
|
+
err = js_throw_error(env, NULL, "from, to and fontsDir must be strings without NUL characters");
|
|
72
117
|
assert(err == 0);
|
|
73
|
-
return
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
job->input = input;
|
|
122
|
+
job->input_len = input_len;
|
|
123
|
+
|
|
124
|
+
if (copy_input && input_len > 0) {
|
|
125
|
+
job->owned_input = malloc(input_len);
|
|
126
|
+
if (job->owned_input == NULL) {
|
|
127
|
+
simple_converter__job_release(job);
|
|
128
|
+
err = js_throw_error(env, NULL, "out of memory");
|
|
129
|
+
assert(err == 0);
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
memcpy(job->owned_input, input, input_len);
|
|
133
|
+
job->input = job->owned_input;
|
|
74
134
|
}
|
|
75
135
|
|
|
76
|
-
|
|
77
|
-
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
static void
|
|
140
|
+
simple_converter__run(simple_converter_job_t *job) {
|
|
141
|
+
job->status = sc_convert(job->input, job->input_len, job->from, job->to, job->fonts_dir, &job->out, &job->error);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
static void
|
|
145
|
+
simple_converter__finalize_output(js_env_t *env, void *data, void *finalize_hint) {
|
|
146
|
+
sc_buffer_t *buffer = finalize_hint;
|
|
147
|
+
sc_free(buffer);
|
|
148
|
+
free(buffer);
|
|
149
|
+
}
|
|
78
150
|
|
|
79
|
-
|
|
151
|
+
static js_value_t *
|
|
152
|
+
simple_converter__output(js_env_t *env, simple_converter_job_t *job) {
|
|
153
|
+
int err;
|
|
80
154
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
155
|
+
js_value_t *result;
|
|
156
|
+
|
|
157
|
+
sc_buffer_t *owned = job->out.len > 0 ? malloc(sizeof(sc_buffer_t)) : NULL;
|
|
84
158
|
|
|
85
|
-
if (
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
159
|
+
if (owned != NULL) {
|
|
160
|
+
*owned = job->out;
|
|
161
|
+
err = js_create_external_arraybuffer(env, owned->data, owned->len, simple_converter__finalize_output, owned, &result);
|
|
162
|
+
if (err == 0) {
|
|
163
|
+
job->out.data = NULL;
|
|
164
|
+
job->out.len = 0;
|
|
165
|
+
return result;
|
|
90
166
|
}
|
|
91
|
-
|
|
92
|
-
err = js_throw_error(env, NULL, message ? message : "conversion failed");
|
|
93
|
-
assert(err == 0);
|
|
94
|
-
free(message);
|
|
95
|
-
return NULL;
|
|
167
|
+
free(owned);
|
|
96
168
|
}
|
|
97
169
|
|
|
98
|
-
js_value_t *result;
|
|
99
170
|
void *data;
|
|
100
|
-
err = js_create_arraybuffer(env, out.len, &data, &result);
|
|
171
|
+
err = js_create_arraybuffer(env, job->out.len, &data, &result);
|
|
172
|
+
assert(err == 0);
|
|
173
|
+
|
|
174
|
+
if (job->out.len > 0) memcpy(data, job->out.data, job->out.len);
|
|
175
|
+
|
|
176
|
+
return result;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
static js_value_t *
|
|
180
|
+
simple_converter__error(js_env_t *env, simple_converter_job_t *job) {
|
|
181
|
+
int err;
|
|
182
|
+
|
|
183
|
+
js_value_t *message;
|
|
184
|
+
if (job->error.len > 0) {
|
|
185
|
+
err = js_create_string_utf8(env, (utf8_t *) job->error.data, job->error.len, &message);
|
|
186
|
+
} else {
|
|
187
|
+
err = js_create_string_utf8(env, (utf8_t *) "conversion failed", (size_t) -1, &message);
|
|
188
|
+
}
|
|
189
|
+
assert(err == 0);
|
|
190
|
+
|
|
191
|
+
js_value_t *error;
|
|
192
|
+
err = js_create_error(env, NULL, message, &error);
|
|
101
193
|
assert(err == 0);
|
|
102
194
|
|
|
103
|
-
|
|
104
|
-
|
|
195
|
+
return error;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
static js_value_t *
|
|
199
|
+
simple_converter_convert_sync(js_env_t *env, js_callback_info_t *info) {
|
|
200
|
+
int err;
|
|
201
|
+
|
|
202
|
+
simple_converter_job_t job;
|
|
203
|
+
if (!simple_converter__job_init(env, info, false, &job)) return NULL;
|
|
204
|
+
|
|
205
|
+
simple_converter__run(&job);
|
|
206
|
+
|
|
207
|
+
js_value_t *result = NULL;
|
|
208
|
+
|
|
209
|
+
if (job.status == 0) {
|
|
210
|
+
result = simple_converter__output(env, &job);
|
|
211
|
+
} else {
|
|
212
|
+
err = js_throw(env, simple_converter__error(env, &job));
|
|
213
|
+
assert(err == 0);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
simple_converter__job_release(&job);
|
|
105
217
|
|
|
106
218
|
return result;
|
|
107
219
|
}
|
|
108
220
|
|
|
221
|
+
static void
|
|
222
|
+
simple_converter__on_work(uv_work_t *work) {
|
|
223
|
+
simple_converter_req_t *req = (simple_converter_req_t *) work;
|
|
224
|
+
|
|
225
|
+
simple_converter__run(&req->job);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
static void
|
|
229
|
+
simple_converter__on_after_work(uv_work_t *work, int status) {
|
|
230
|
+
int err;
|
|
231
|
+
|
|
232
|
+
simple_converter_req_t *req = (simple_converter_req_t *) work;
|
|
233
|
+
|
|
234
|
+
js_env_t *env = req->env;
|
|
235
|
+
|
|
236
|
+
if (!req->exiting) {
|
|
237
|
+
js_handle_scope_t *scope;
|
|
238
|
+
err = js_open_handle_scope(env, &scope);
|
|
239
|
+
assert(err == 0);
|
|
240
|
+
|
|
241
|
+
if (status == 0 && req->job.status == 0) {
|
|
242
|
+
err = js_resolve_deferred(env, req->deferred, simple_converter__output(env, &req->job));
|
|
243
|
+
} else {
|
|
244
|
+
err = js_reject_deferred(env, req->deferred, simple_converter__error(env, &req->job));
|
|
245
|
+
}
|
|
246
|
+
assert(err == 0);
|
|
247
|
+
|
|
248
|
+
err = js_close_handle_scope(env, scope);
|
|
249
|
+
assert(err == 0);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
err = js_finish_deferred_teardown_callback(req->teardown);
|
|
253
|
+
assert(err == 0);
|
|
254
|
+
|
|
255
|
+
simple_converter__job_release(&req->job);
|
|
256
|
+
free(req);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
static void
|
|
260
|
+
simple_converter__on_teardown(js_deferred_teardown_t *teardown, void *data) {
|
|
261
|
+
simple_converter_req_t *req = data;
|
|
262
|
+
|
|
263
|
+
req->exiting = true;
|
|
264
|
+
|
|
265
|
+
uv_cancel((uv_req_t *) &req->work);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
static js_value_t *
|
|
269
|
+
simple_converter_convert(js_env_t *env, js_callback_info_t *info) {
|
|
270
|
+
int err;
|
|
271
|
+
|
|
272
|
+
simple_converter_req_t *req = malloc(sizeof(simple_converter_req_t));
|
|
273
|
+
if (req == NULL) {
|
|
274
|
+
err = js_throw_error(env, NULL, "out of memory");
|
|
275
|
+
assert(err == 0);
|
|
276
|
+
return NULL;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (!simple_converter__job_init(env, info, true, &req->job)) {
|
|
280
|
+
free(req);
|
|
281
|
+
return NULL;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
req->env = env;
|
|
285
|
+
req->exiting = false;
|
|
286
|
+
|
|
287
|
+
js_value_t *promise;
|
|
288
|
+
err = js_create_promise(env, &req->deferred, &promise);
|
|
289
|
+
assert(err == 0);
|
|
290
|
+
|
|
291
|
+
uv_loop_t *loop;
|
|
292
|
+
err = js_get_env_loop(env, &loop);
|
|
293
|
+
assert(err == 0);
|
|
294
|
+
|
|
295
|
+
err = uv_queue_work(loop, &req->work, simple_converter__on_work, simple_converter__on_after_work);
|
|
296
|
+
assert(err == 0);
|
|
297
|
+
|
|
298
|
+
err = js_add_deferred_teardown_callback(env, simple_converter__on_teardown, req, &req->teardown);
|
|
299
|
+
assert(err == 0);
|
|
300
|
+
|
|
301
|
+
return promise;
|
|
302
|
+
}
|
|
303
|
+
|
|
109
304
|
static js_value_t *
|
|
110
305
|
simple_converter_exports(js_env_t *env, js_value_t *exports) {
|
|
111
306
|
int err;
|
|
@@ -120,6 +315,7 @@ simple_converter_exports(js_env_t *env, js_value_t *exports) {
|
|
|
120
315
|
}
|
|
121
316
|
|
|
122
317
|
V("convert", simple_converter_convert)
|
|
318
|
+
V("convertSync", simple_converter_convert_sync)
|
|
123
319
|
#undef V
|
|
124
320
|
|
|
125
321
|
return exports;
|
package/cmake/rust.cmake
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
include_guard(GLOBAL)
|
|
2
2
|
|
|
3
|
-
set(
|
|
3
|
+
set(CORROSION_COMMIT "1499b14e4906a2890f5cee1547c8848db261753d" CACHE STRING "Corrosion commit (v0.6.1)")
|
|
4
4
|
|
|
5
5
|
bare_platform(_sys)
|
|
6
6
|
bare_arch(_arch)
|
|
@@ -49,7 +49,7 @@ endif()
|
|
|
49
49
|
|
|
50
50
|
set(Rust_CARGO_TARGET "${_triple}" CACHE STRING "Rust target triple")
|
|
51
51
|
|
|
52
|
-
fetch_package("github:corrosion-rs/corrosion#${
|
|
52
|
+
fetch_package("github:corrosion-rs/corrosion#${CORROSION_COMMIT}")
|
|
53
53
|
|
|
54
54
|
corrosion_import_crate(
|
|
55
55
|
MANIFEST_PATH "${CMAKE_CURRENT_SOURCE_DIR}/core/Cargo.toml"
|
package/core/src/doc/mod.rs
CHANGED
|
@@ -34,6 +34,8 @@ pub fn read(bytes: &[u8]) -> Result<Document, Error> {
|
|
|
34
34
|
chpx_fkps: file.fkps(file.fib.fc_lcb(12))?,
|
|
35
35
|
papx_fkps: file.fkps(file.fib.fc_lcb(13))?,
|
|
36
36
|
counters: std::cell::RefCell::new(HashMap::new()),
|
|
37
|
+
extracted: std::cell::Cell::new(0),
|
|
38
|
+
image_bytes: std::cell::Cell::new(0),
|
|
37
39
|
};
|
|
38
40
|
|
|
39
41
|
let main_end = file.fib.ccp_text;
|
|
@@ -369,7 +371,7 @@ impl File {
|
|
|
369
371
|
let (fc_clx, lcb_clx) = self.fib.fc_lcb(33);
|
|
370
372
|
let clx = self
|
|
371
373
|
.table
|
|
372
|
-
.get(fc_clx as usize..(
|
|
374
|
+
.get(fc_clx as usize..fc_clx.saturating_add(lcb_clx) as usize)
|
|
373
375
|
.ok_or_else(|| Error::new("piece table is out of range"))?;
|
|
374
376
|
let mut pos = 0;
|
|
375
377
|
while pos < clx.len() {
|
|
@@ -380,7 +382,7 @@ impl File {
|
|
|
380
382
|
}
|
|
381
383
|
2 => {
|
|
382
384
|
let lcb = u32_at(clx, pos + 1) as usize;
|
|
383
|
-
let plc =
|
|
385
|
+
let plc = clx.get(pos + 5..pos.saturating_add(5).saturating_add(lcb).min(clx.len())).unwrap_or(&[]);
|
|
384
386
|
let n = (plc.len().saturating_sub(4)) / 12;
|
|
385
387
|
let mut pieces = Vec::with_capacity(n);
|
|
386
388
|
for i in 0..n {
|
|
@@ -402,7 +404,7 @@ impl File {
|
|
|
402
404
|
|
|
403
405
|
fn fonts(&self) -> Vec<String> {
|
|
404
406
|
let (fc, lcb) = self.fib.fc_lcb(15);
|
|
405
|
-
let Some(sttb) = self.table.get(fc as usize..(
|
|
407
|
+
let Some(sttb) = self.table.get(fc as usize..fc.saturating_add(lcb) as usize) else { return Vec::new() };
|
|
406
408
|
let mut fonts = Vec::new();
|
|
407
409
|
let mut pos = 0;
|
|
408
410
|
if u16_at(sttb, 0) == 0xFFFF {
|
|
@@ -441,10 +443,10 @@ impl File {
|
|
|
441
443
|
let (fc, lcb) = self.fib.fc_lcb(1);
|
|
442
444
|
let stsh = self
|
|
443
445
|
.table
|
|
444
|
-
.get(fc as usize..(
|
|
446
|
+
.get(fc as usize..fc.saturating_add(lcb) as usize)
|
|
445
447
|
.ok_or_else(|| Error::new("stylesheet is out of range"))?;
|
|
446
448
|
let cb_stshi = u16_at(stsh, 0) as usize;
|
|
447
|
-
let stshi =
|
|
449
|
+
let stshi = stsh.get(2..(2 + cb_stshi).min(stsh.len())).unwrap_or(&[]);
|
|
448
450
|
let cstd = u16_at(stshi, 0) as usize;
|
|
449
451
|
let cb_std_base = u16_at(stshi, 2) as usize;
|
|
450
452
|
let default_ftc = u16_at(stshi, 12) as usize;
|
|
@@ -634,7 +636,7 @@ impl File {
|
|
|
634
636
|
}
|
|
635
637
|
|
|
636
638
|
fn parse_fkp(page: &[u8]) -> Fkp {
|
|
637
|
-
let crun = page
|
|
639
|
+
let crun = (page.get(511).copied().unwrap_or(0) as usize).min(101);
|
|
638
640
|
let mut fcs = Vec::with_capacity(crun + 1);
|
|
639
641
|
for i in 0..=crun {
|
|
640
642
|
fcs.push(u32_at(page, i * 4));
|
|
@@ -643,15 +645,15 @@ fn parse_fkp(page: &[u8]) -> Fkp {
|
|
|
643
645
|
let mut entries = Vec::with_capacity(crun);
|
|
644
646
|
let mut istds = Vec::with_capacity(crun);
|
|
645
647
|
for i in 0..crun {
|
|
646
|
-
let offset_byte = page
|
|
648
|
+
let offset_byte = page.get(base + i).copied().unwrap_or(0);
|
|
647
649
|
if offset_byte == 0 {
|
|
648
650
|
entries.push(Vec::new());
|
|
649
651
|
istds.push(0);
|
|
650
652
|
continue;
|
|
651
653
|
}
|
|
652
654
|
let at = offset_byte as usize * 2;
|
|
653
|
-
let cb = page
|
|
654
|
-
let grpprl =
|
|
655
|
+
let cb = page.get(at).copied().unwrap_or(0) as usize;
|
|
656
|
+
let grpprl = page.get(at + 1..(at + 1 + cb).min(page.len())).unwrap_or(&[]);
|
|
655
657
|
entries.push(grpprl.to_vec());
|
|
656
658
|
istds.push(0);
|
|
657
659
|
}
|
|
@@ -659,7 +661,7 @@ fn parse_fkp(page: &[u8]) -> Fkp {
|
|
|
659
661
|
}
|
|
660
662
|
|
|
661
663
|
fn parse_papx_fkp(page: &[u8]) -> Fkp {
|
|
662
|
-
let cpara = page
|
|
664
|
+
let cpara = (page.get(511).copied().unwrap_or(0) as usize).min(29);
|
|
663
665
|
let mut fcs = Vec::with_capacity(cpara + 1);
|
|
664
666
|
for i in 0..=cpara {
|
|
665
667
|
fcs.push(u32_at(page, i * 4));
|
|
@@ -668,22 +670,22 @@ fn parse_papx_fkp(page: &[u8]) -> Fkp {
|
|
|
668
670
|
let mut entries = Vec::with_capacity(cpara);
|
|
669
671
|
let mut istds = Vec::with_capacity(cpara);
|
|
670
672
|
for i in 0..cpara {
|
|
671
|
-
let bx = page
|
|
673
|
+
let bx = page.get(base + i * 13).copied().unwrap_or(0);
|
|
672
674
|
if bx == 0 {
|
|
673
675
|
entries.push(Vec::new());
|
|
674
676
|
istds.push(0);
|
|
675
677
|
continue;
|
|
676
678
|
}
|
|
677
679
|
let at = bx as usize * 2;
|
|
678
|
-
let mut cb = page
|
|
680
|
+
let mut cb = page.get(at).copied().unwrap_or(0) as usize;
|
|
679
681
|
let mut start = at + 1;
|
|
680
682
|
if cb == 0 {
|
|
681
|
-
cb = page
|
|
683
|
+
cb = page.get(at + 1).copied().unwrap_or(0) as usize * 2;
|
|
682
684
|
start = at + 2;
|
|
683
685
|
} else {
|
|
684
686
|
cb = cb * 2 - 1;
|
|
685
687
|
}
|
|
686
|
-
let body =
|
|
688
|
+
let body = page.get(start..(start + cb).min(page.len())).unwrap_or(&[]);
|
|
687
689
|
let istd = u16_at(body, 0);
|
|
688
690
|
entries.push(body.get(2..).map(|b| b.to_vec()).unwrap_or_default());
|
|
689
691
|
istds.push(istd);
|
|
@@ -729,6 +731,8 @@ struct Reader<'a> {
|
|
|
729
731
|
chpx_fkps: Vec<(u32, u32, Fkp)>,
|
|
730
732
|
papx_fkps: Vec<(u32, u32, Fkp)>,
|
|
731
733
|
counters: std::cell::RefCell<HashMap<u32, Vec<i64>>>,
|
|
734
|
+
extracted: std::cell::Cell<usize>,
|
|
735
|
+
image_bytes: std::cell::Cell<usize>,
|
|
732
736
|
}
|
|
733
737
|
|
|
734
738
|
#[derive(Debug, Clone)]
|
|
@@ -745,7 +749,11 @@ impl Reader<'_> {
|
|
|
745
749
|
|
|
746
750
|
fn text(&self, start: u32, end: u32) -> Vec<Char> {
|
|
747
751
|
let mut out = Vec::new();
|
|
752
|
+
let budget = self.file.word.len().saturating_mul(4);
|
|
748
753
|
for piece in self.pieces {
|
|
754
|
+
if self.extracted.get() + out.len() > budget {
|
|
755
|
+
break;
|
|
756
|
+
}
|
|
749
757
|
if piece.cp_end <= start || piece.cp_start >= end {
|
|
750
758
|
continue;
|
|
751
759
|
}
|
|
@@ -780,6 +788,7 @@ impl Reader<'_> {
|
|
|
780
788
|
}
|
|
781
789
|
}
|
|
782
790
|
}
|
|
791
|
+
self.extracted.set(self.extracted.get() + out.len());
|
|
783
792
|
out
|
|
784
793
|
}
|
|
785
794
|
|
|
@@ -1209,9 +1218,9 @@ impl Reader<'_> {
|
|
|
1209
1218
|
let values = counters.entry(lsid).or_insert_with(|| vec![0; 9]);
|
|
1210
1219
|
let ilvl = ilvl.min(8) as usize;
|
|
1211
1220
|
if values[ilvl] == 0 {
|
|
1212
|
-
values[ilvl] = level.start
|
|
1221
|
+
values[ilvl] = i64::from(level.start).saturating_sub(1);
|
|
1213
1222
|
}
|
|
1214
|
-
values[ilvl]
|
|
1223
|
+
values[ilvl] = values[ilvl].saturating_add(1);
|
|
1215
1224
|
for deeper in ilvl + 1..9 {
|
|
1216
1225
|
values[deeper] = 0;
|
|
1217
1226
|
}
|
|
@@ -1258,6 +1267,10 @@ impl Reader<'_> {
|
|
|
1258
1267
|
}
|
|
1259
1268
|
let body = data.get(at + cb_header..(at + lcb).min(data.len()))?;
|
|
1260
1269
|
let content = find_blip(body)
|
|
1270
|
+
.filter(|bytes| {
|
|
1271
|
+
self.image_bytes.set(self.image_bytes.get() + bytes.len());
|
|
1272
|
+
self.image_bytes.get() <= crate::limits::MAX_IMAGE_BYTES
|
|
1273
|
+
})
|
|
1261
1274
|
.and_then(|bytes| ImageFormat::sniff(bytes).map(|format| ImageData { data: bytes.to_vec(), format }))
|
|
1262
1275
|
.map(DrawingContent::Image)
|
|
1263
1276
|
.unwrap_or(DrawingContent::Placeholder(None));
|
package/core/src/docx/mod.rs
CHANGED
|
@@ -193,7 +193,7 @@ fn build_section<R: Read + std::io::Seek>(
|
|
|
193
193
|
..Section::default()
|
|
194
194
|
};
|
|
195
195
|
if let Some(cols) = sect.child("cols") {
|
|
196
|
-
section.columns = cols.attr("num").and_then(|n| n.parse().ok()).unwrap_or(1).
|
|
196
|
+
section.columns = cols.attr("num").and_then(|n| n.parse().ok()).unwrap_or(1).clamp(1, crate::limits::MAX_TEXT_COLUMNS);
|
|
197
197
|
if let Some(space) = cols.attr("space").and_then(twips) {
|
|
198
198
|
section.column_gap = space;
|
|
199
199
|
}
|
|
@@ -267,8 +267,7 @@ fn entry<R: Read + std::io::Seek>(
|
|
|
267
267
|
) -> Result<Option<Vec<u8>>, Error> {
|
|
268
268
|
match archive.by_name(name) {
|
|
269
269
|
Ok(mut file) => {
|
|
270
|
-
let
|
|
271
|
-
file.read_to_end(&mut data)?;
|
|
270
|
+
let data = crate::limits::read_entry(&mut file)?;
|
|
272
271
|
Ok(Some(data))
|
|
273
272
|
}
|
|
274
273
|
Err(zip::result::ZipError::FileNotFound) => Ok(None),
|
|
@@ -810,8 +809,7 @@ impl Reader<'_> {
|
|
|
810
809
|
.child("gridSpan")
|
|
811
810
|
.and_then(|s| s.attr("val"))
|
|
812
811
|
.and_then(|v| v.parse().ok())
|
|
813
|
-
.
|
|
814
|
-
.max(1);
|
|
812
|
+
.map_or(1, crate::limits::span);
|
|
815
813
|
cell.width = pr
|
|
816
814
|
.child("tcW")
|
|
817
815
|
.filter(|w| w.attr("type") != Some("pct"))
|
|
@@ -147,18 +147,18 @@ impl Counters {
|
|
|
147
147
|
let values = self
|
|
148
148
|
.values
|
|
149
149
|
.entry(key)
|
|
150
|
-
.or_insert_with(|| all.iter().map(|l| l.start
|
|
150
|
+
.or_insert_with(|| all.iter().map(|l| l.start.saturating_sub(1)).collect());
|
|
151
151
|
|
|
152
152
|
let first_use = self.started.insert((num_id.to_string(), ilvl));
|
|
153
153
|
if first_use {
|
|
154
154
|
if let Some(start) = instance.overrides.get(&ilvl).and_then(|o| o.start) {
|
|
155
|
-
values[ilvl] = start
|
|
155
|
+
values[ilvl] = start.saturating_sub(1);
|
|
156
156
|
}
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
-
values[ilvl]
|
|
159
|
+
values[ilvl] = values[ilvl].saturating_add(1);
|
|
160
160
|
for deeper in ilvl + 1..values.len() {
|
|
161
|
-
values[deeper] = all[deeper].start
|
|
161
|
+
values[deeper] = all[deeper].start.saturating_sub(1);
|
|
162
162
|
}
|
|
163
163
|
|
|
164
164
|
let text = if level.format == "bullet" {
|
|
@@ -203,6 +203,10 @@ fn bullet_text(text: &str) -> String {
|
|
|
203
203
|
}
|
|
204
204
|
|
|
205
205
|
pub fn format_number(value: i64, format: &str) -> String {
|
|
206
|
+
let format = match format {
|
|
207
|
+
"lowerLetter" | "upperLetter" | "lowerRoman" | "upperRoman" if value > crate::limits::MAX_LIST_VALUE => "decimal",
|
|
208
|
+
other => other,
|
|
209
|
+
};
|
|
206
210
|
match format {
|
|
207
211
|
"none" => String::new(),
|
|
208
212
|
"lowerLetter" => letters(value).to_lowercase(),
|