@tstdl/base 0.93.128 → 0.93.129
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/api/client/tests/api-client.test.js +5 -5
- package/api/types.d.ts +1 -1
- package/api/types.js +2 -9
- package/document-management/server/services/document-file.service.js +10 -9
- package/document-management/server/services/document-management-ancillary.service.d.ts +12 -1
- package/document-management/server/services/document-management-ancillary.service.js +9 -0
- package/file/server/temporary-file.d.ts +2 -1
- package/file/server/temporary-file.js +5 -1
- package/object-storage/s3/s3.object-storage.js +6 -6
- package/package.json +1 -1
- package/pdf/utils.d.ts +24 -3
- package/pdf/utils.js +89 -30
- package/process/spawn.d.ts +1 -1
- package/renderer/typst.d.ts +5 -0
- package/renderer/typst.js +9 -5
|
@@ -156,9 +156,9 @@ describe('ApiClient', () => {
|
|
|
156
156
|
expect(mockHttpClient.rawRequest.mock.calls.at(-1)[0].credentials).toBe('include');
|
|
157
157
|
await client.withBustCache();
|
|
158
158
|
expect(mockHttpClient.rawRequest.mock.calls.at(-1)[0].context[bustCache]).toBe(true);
|
|
159
|
-
expect(Client.getEndpointResource('withCredentials')).toContain('
|
|
160
|
-
expect(Client.getEndpointUrl('withCredentials').href).toBe('http://baseurl/api/v1/features
|
|
161
|
-
expect(client.getEndpointUrl('withCredentials').href).toBe('http://localhost/api/v1/features
|
|
159
|
+
expect(Client.getEndpointResource('withCredentials')).not.toContain('withCredentials');
|
|
160
|
+
expect(Client.getEndpointUrl('withCredentials').href).toBe('http://baseurl/api/v1/features');
|
|
161
|
+
expect(client.getEndpointUrl('withCredentials').href).toBe('http://localhost/api/v1/features');
|
|
162
162
|
});
|
|
163
163
|
it('should handle Server Sent Events and DataStream', async () => {
|
|
164
164
|
const apiDefinition = defineApi({
|
|
@@ -185,10 +185,10 @@ describe('ApiClient', () => {
|
|
|
185
185
|
const client = new Client(mockHttpClient);
|
|
186
186
|
const sse = await client.events();
|
|
187
187
|
expect(sse).toBeInstanceOf(ServerSentEvents);
|
|
188
|
-
expect(EventSourceMock).toHaveBeenCalledWith(expect.stringContaining('sse
|
|
188
|
+
expect(EventSourceMock).toHaveBeenCalledWith(expect.stringContaining('sse'), expect.any(Object));
|
|
189
189
|
const stream = await client.stream();
|
|
190
190
|
expect(stream).toBeInstanceOf(Object); // It's an Observable
|
|
191
|
-
expect(EventSourceMock).toHaveBeenCalledWith(expect.stringContaining('sse
|
|
191
|
+
expect(EventSourceMock).toHaveBeenCalledWith(expect.stringContaining('sse'), expect.any(Object));
|
|
192
192
|
vi.unstubAllGlobals();
|
|
193
193
|
});
|
|
194
194
|
});
|
package/api/types.d.ts
CHANGED
package/api/types.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { objectEntries } from '../utils/object/object.js';
|
|
2
|
-
import {
|
|
3
|
-
import { isFunction, isUndefined } from '../utils/type-guards.js';
|
|
2
|
+
import { isFunction } from '../utils/type-guards.js';
|
|
4
3
|
import { resolveValueOrProvider } from '../utils/value-or-provider.js';
|
|
5
4
|
export function defineApi(definition) {
|
|
6
5
|
return definition;
|
|
@@ -16,11 +15,5 @@ export function normalizedApiDefinitionEndpoints(apiDefinitionEndpoints) {
|
|
|
16
15
|
return Object.fromEntries(entries);
|
|
17
16
|
}
|
|
18
17
|
export function normalizedApiDefinitionEndpointsEntries(apiDefinition) {
|
|
19
|
-
return objectEntries(apiDefinition).map(([key, def]) =>
|
|
20
|
-
const endpoint = resolveValueOrProvider(def);
|
|
21
|
-
if (isUndefined(endpoint.resource)) {
|
|
22
|
-
endpoint.resource = hyphenate(key);
|
|
23
|
-
}
|
|
24
|
-
return [key, endpoint];
|
|
25
|
-
});
|
|
18
|
+
return objectEntries(apiDefinition).map(([key, def]) => [key, resolveValueOrProvider(def)]);
|
|
26
19
|
}
|
|
@@ -6,7 +6,6 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
6
6
|
};
|
|
7
7
|
import sharp, {} from 'sharp';
|
|
8
8
|
import { match } from 'ts-pattern';
|
|
9
|
-
import { injectGenkit } from '../../../ai/genkit/index.js';
|
|
10
9
|
import { ForbiddenError } from '../../../errors/forbidden.error.js';
|
|
11
10
|
import { NotImplementedError } from '../../../errors/not-implemented.error.js';
|
|
12
11
|
import { getMimeType, getMimeTypeExtensions, mimeTypes } from '../../../file/index.js';
|
|
@@ -20,14 +19,15 @@ import { digest } from '../../../utils/cryptography.js';
|
|
|
20
19
|
import { currentTimestamp } from '../../../utils/date-time.js';
|
|
21
20
|
import { getRandomString } from '../../../utils/random.js';
|
|
22
21
|
import { readableStreamFromPromise, readBinaryStream } from '../../../utils/stream/index.js';
|
|
23
|
-
import { isNotReadableStream, isNotUint8Array, isUint8Array } from '../../../utils/type-guards.js';
|
|
22
|
+
import { isNotReadableStream, isNotUint8Array, isString, isUint8Array } from '../../../utils/type-guards.js';
|
|
24
23
|
import { millisecondsPerMinute } from '../../../utils/units.js';
|
|
25
24
|
import { Document } from '../../models/index.js';
|
|
26
25
|
import { DocumentManagementConfiguration } from '../module.js';
|
|
26
|
+
import { DocumentManagementAncillaryService } from './document-management-ancillary.service.js';
|
|
27
27
|
import { DocumentManagementSingleton } from './singleton.js';
|
|
28
28
|
let DocumentFileService = class DocumentFileService extends Transactional {
|
|
29
29
|
#configuration = inject(DocumentManagementConfiguration);
|
|
30
|
-
#
|
|
30
|
+
#documentManagementAncillaryService = inject(DocumentManagementAncillaryService);
|
|
31
31
|
#fileObjectStorage = inject(ObjectStorage, this.#configuration.fileObjectStorageModule);
|
|
32
32
|
#filePreviewObjectStorage = inject(ObjectStorage, this.#configuration.filePreviewObjectStorageModule);
|
|
33
33
|
#fileUploadObjectStorage = inject(ObjectStorage, { module: this.#configuration.fileUploadObjectStorageModule /* , configuration: { lifecycle: { expiration: { after: 5 * secondsPerMinute } } } */ });
|
|
@@ -86,7 +86,10 @@ let DocumentFileService = class DocumentFileService extends Transactional {
|
|
|
86
86
|
return this.#fileObjectStorage.getContentStream(objectKey);
|
|
87
87
|
}
|
|
88
88
|
async getContentUrl(document, download = false) {
|
|
89
|
-
|
|
89
|
+
const resolvedFilename = await this.#documentManagementAncillaryService.getDocumentFilename(document);
|
|
90
|
+
const fileExtension = getMimeTypeExtensions(document.mimeType)[0] ?? 'bin';
|
|
91
|
+
const filename = isString(resolvedFilename) ? `${resolvedFilename}.${fileExtension}` : resolvedFilename.fullname;
|
|
92
|
+
return await this.getDocumentFileContentObjectUrl(document, filename, download);
|
|
90
93
|
}
|
|
91
94
|
/** Gets the underlying object storage object for the document file */
|
|
92
95
|
async getObject(document) {
|
|
@@ -132,14 +135,12 @@ let DocumentFileService = class DocumentFileService extends Transactional {
|
|
|
132
135
|
await this.#filePreviewObjectStorage.uploadObject(key, image, { contentLength: image.length, contentType: 'image/webp' });
|
|
133
136
|
}
|
|
134
137
|
}
|
|
135
|
-
async getDocumentFileContentObjectUrl(document,
|
|
138
|
+
async getDocumentFileContentObjectUrl(document, filename, download) {
|
|
136
139
|
const key = getObjectKey(document.id);
|
|
137
|
-
const fileExtension = getMimeTypeExtensions(document.mimeType)[0] ?? 'bin';
|
|
138
140
|
const disposition = download ? 'attachment' : 'inline';
|
|
139
|
-
const filename = `${title}.${fileExtension}`;
|
|
140
141
|
return await this.#fileObjectStorage.getDownloadUrl(key, currentTimestamp() + (5 * millisecondsPerMinute), {
|
|
141
|
-
'
|
|
142
|
-
'
|
|
142
|
+
'Content-Type': document.mimeType,
|
|
143
|
+
'Content-Disposition': `${disposition}; filename = "${encodeURIComponent(filename)}"`,
|
|
143
144
|
});
|
|
144
145
|
}
|
|
145
146
|
};
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
import type { DocumentCollection } from '../../models/index.js';
|
|
1
|
+
import type { Document, DocumentCollection } from '../../models/index.js';
|
|
2
2
|
import type { DocumentCollectionMetadata } from '../../service-models/index.js';
|
|
3
|
+
export type DocumentFileNameResult = string | {
|
|
4
|
+
/** The full name of the document, including extension. */
|
|
5
|
+
fullname: string;
|
|
6
|
+
};
|
|
3
7
|
export declare abstract class DocumentManagementAncillaryService {
|
|
4
8
|
/**
|
|
5
9
|
* Resolves application-specific metadata for a list of document collections.
|
|
@@ -7,4 +11,11 @@ export declare abstract class DocumentManagementAncillaryService {
|
|
|
7
11
|
* @returns A promise that resolves to an array of DocumentCollectionMetadata, corresponding to the input collections.
|
|
8
12
|
*/
|
|
9
13
|
abstract resolveMetadata(tenantId: string, collections: DocumentCollection[]): DocumentCollectionMetadata[] | Promise<DocumentCollectionMetadata[]>;
|
|
14
|
+
/**
|
|
15
|
+
* Resolve the file name for a document, which will be used when downloading the document file. The file extension is automatically determined based on the document's MIME type, so the returned file name should not include the extension.
|
|
16
|
+
* @param document The Document entity for which to resolve the file name.
|
|
17
|
+
* @param title An optional title provided by the api call.
|
|
18
|
+
* @returns The resolved file name for the document, without the file extension.
|
|
19
|
+
*/
|
|
20
|
+
getDocumentFilename(document: Document): DocumentFileNameResult | Promise<DocumentFileNameResult>;
|
|
10
21
|
}
|
|
@@ -1,2 +1,11 @@
|
|
|
1
1
|
export class DocumentManagementAncillaryService {
|
|
2
|
+
/**
|
|
3
|
+
* Resolve the file name for a document, which will be used when downloading the document file. The file extension is automatically determined based on the document's MIME type, so the returned file name should not include the extension.
|
|
4
|
+
* @param document The Document entity for which to resolve the file name.
|
|
5
|
+
* @param title An optional title provided by the api call.
|
|
6
|
+
* @returns The resolved file name for the document, without the file extension.
|
|
7
|
+
*/
|
|
8
|
+
getDocumentFilename(document) {
|
|
9
|
+
return document.title ?? document.originalFileName ?? document.id;
|
|
10
|
+
}
|
|
2
11
|
}
|
|
@@ -12,7 +12,7 @@ export declare class TemporaryFile implements AsyncDisposable {
|
|
|
12
12
|
/**
|
|
13
13
|
* Prevents the temporary file from being deleted on disposal.
|
|
14
14
|
*/
|
|
15
|
-
keep():
|
|
15
|
+
keep(): this;
|
|
16
16
|
read(): Promise<Uint8Array>;
|
|
17
17
|
readText(): Promise<string>;
|
|
18
18
|
readStream(): ReadableStream<Uint8Array>;
|
|
@@ -25,5 +25,6 @@ export declare class TemporaryFile implements AsyncDisposable {
|
|
|
25
25
|
moveTo(path: string, keep?: boolean): Promise<void>;
|
|
26
26
|
delete(): Promise<void>;
|
|
27
27
|
size(): Promise<number>;
|
|
28
|
+
dispose(): Promise<void>;
|
|
28
29
|
[Symbol.asyncDispose](): Promise<void>;
|
|
29
30
|
}
|
|
@@ -36,6 +36,7 @@ export class TemporaryFile {
|
|
|
36
36
|
*/
|
|
37
37
|
keep() {
|
|
38
38
|
this.#keep = true;
|
|
39
|
+
return this;
|
|
39
40
|
}
|
|
40
41
|
async read() {
|
|
41
42
|
return await readFile(this.#path);
|
|
@@ -69,7 +70,7 @@ export class TemporaryFile {
|
|
|
69
70
|
const result = await stat(this.#path);
|
|
70
71
|
return result.size;
|
|
71
72
|
}
|
|
72
|
-
async
|
|
73
|
+
async dispose() {
|
|
73
74
|
if (this.#keep) {
|
|
74
75
|
return;
|
|
75
76
|
}
|
|
@@ -78,4 +79,7 @@ export class TemporaryFile {
|
|
|
78
79
|
}
|
|
79
80
|
catch { }
|
|
80
81
|
}
|
|
82
|
+
async [Symbol.asyncDispose]() {
|
|
83
|
+
await this.dispose();
|
|
84
|
+
}
|
|
81
85
|
}
|
|
@@ -240,16 +240,16 @@ let S3ObjectStorage = S3ObjectStorage_1 = class S3ObjectStorage extends ObjectSt
|
|
|
240
240
|
async getDownloadUrl(key, expirationTimestamp, responseHeaders) {
|
|
241
241
|
const bucketKey = this.getBucketKey(key);
|
|
242
242
|
const expiration = getExpiration(expirationTimestamp);
|
|
243
|
-
const expiresHeader = responseHeaders?.['Expires'];
|
|
243
|
+
const expiresHeader = responseHeaders?.['Expires'] ?? responseHeaders?.['Response-Expires'];
|
|
244
244
|
const mappedExpiresHeader = isDefined(expiresHeader) ? (isDate(expiresHeader) ? expiresHeader : new Date(expiresHeader)) : undefined;
|
|
245
245
|
return await getSignedUrl(this.client, new GetObjectCommand({
|
|
246
246
|
Bucket: this.bucket,
|
|
247
247
|
Key: bucketKey,
|
|
248
|
-
ResponseContentType: responseHeaders?.['Content-Type'],
|
|
249
|
-
ResponseContentDisposition: responseHeaders?.['Content-Disposition'],
|
|
250
|
-
ResponseCacheControl: responseHeaders?.['Cache-Control'],
|
|
251
|
-
ResponseContentLanguage: responseHeaders?.['Content-Language'],
|
|
252
|
-
ResponseContentEncoding: responseHeaders?.['Content-Encoding'],
|
|
248
|
+
ResponseContentType: responseHeaders?.['Content-Type'] ?? responseHeaders?.['Response-Content-Type'],
|
|
249
|
+
ResponseContentDisposition: responseHeaders?.['Content-Disposition'] ?? responseHeaders?.['Response-Content-Disposition'],
|
|
250
|
+
ResponseCacheControl: responseHeaders?.['Cache-Control'] ?? responseHeaders?.['Response-Cache-Control'],
|
|
251
|
+
ResponseContentLanguage: responseHeaders?.['Content-Language'] ?? responseHeaders?.['Response-Content-Language'],
|
|
252
|
+
ResponseContentEncoding: responseHeaders?.['Content-Encoding'] ?? responseHeaders?.['Response-Content-Encoding'],
|
|
253
253
|
ResponseExpires: mappedExpiresHeader,
|
|
254
254
|
}), { expiresIn: expiration });
|
|
255
255
|
}
|
package/package.json
CHANGED
package/pdf/utils.d.ts
CHANGED
|
@@ -1,6 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
3
|
-
|
|
1
|
+
import { TemporaryFile } from '../file/server/temporary-file.js';
|
|
2
|
+
export type PdfInput = string | {
|
|
3
|
+
file: string;
|
|
4
|
+
} | Uint8Array | ReadableStream<Uint8Array>;
|
|
5
|
+
export type PdfInputWithPageRange = string | {
|
|
6
|
+
file: string;
|
|
7
|
+
} | Uint8Array | ReadableStream<Uint8Array> | {
|
|
8
|
+
input: PdfInput;
|
|
9
|
+
pages: PageRange[];
|
|
10
|
+
};
|
|
11
|
+
export type PageRange = number | {
|
|
12
|
+
from?: number;
|
|
13
|
+
to?: number;
|
|
14
|
+
};
|
|
15
|
+
export declare function getPdfPageCount(file: PdfInput): Promise<number>;
|
|
16
|
+
export declare function extractPdfPages(file: PdfInput, pages: PageRange[]): Promise<TemporaryFile>;
|
|
17
|
+
export declare function mergePdfs(sourceFiles: PdfInputWithPageRange[]): Promise<TemporaryFile>;
|
|
18
|
+
export declare function overlayPdfs(base: PdfInput, overlay: PdfInput, options?: {
|
|
19
|
+
repeat?: boolean;
|
|
20
|
+
}): Promise<TemporaryFile>;
|
|
4
21
|
/**
|
|
5
22
|
* Convert a PDF page to an image.
|
|
6
23
|
* @param file The PDF file to convert.
|
|
@@ -10,3 +27,7 @@ export declare function mergePdfsStream(pdfs: (string | Uint8Array | ReadableStr
|
|
|
10
27
|
* @returns The converted image as a byte array.
|
|
11
28
|
*/
|
|
12
29
|
export declare function pdfToImage(file: string | Uint8Array | ReadableStream<Uint8Array>, page: number, size: number, format: 'png' | 'jpeg' | 'tiff' | 'ps' | 'eps' | 'pdf' | 'svg'): Promise<Uint8Array>;
|
|
30
|
+
export declare function isInputWithPageRange(input: PdfInputWithPageRange): input is {
|
|
31
|
+
input: PdfInput;
|
|
32
|
+
pages: PageRange[];
|
|
33
|
+
};
|
package/pdf/utils.js
CHANGED
|
@@ -51,15 +51,15 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
|
|
|
51
51
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
52
52
|
});
|
|
53
53
|
import { TemporaryFile } from '../file/server/temporary-file.js';
|
|
54
|
-
import { spawnCommand } from '../process/spawn.js';
|
|
55
|
-
import {
|
|
54
|
+
import { spawnCommand, spawnWaitReadCommand } from '../process/spawn.js';
|
|
55
|
+
import { hasOwnProperty } from '../utils/object/object.js';
|
|
56
|
+
import { isNotString, isNumber, isObject, isReadableStream, isString, isUint8Array, isUndefined } from '../utils/type-guards.js';
|
|
56
57
|
export async function getPdfPageCount(file) {
|
|
57
58
|
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
58
59
|
try {
|
|
59
|
-
const
|
|
60
|
-
const
|
|
61
|
-
const
|
|
62
|
-
const process = await spawnCommand('qpdf', ['--show-npages', path]);
|
|
60
|
+
const stack = __addDisposableResource(env_1, new AsyncDisposableStack(), true);
|
|
61
|
+
const [sourceFile] = await getPdfSourceFiles([file], stack);
|
|
62
|
+
const process = await spawnCommand('qpdf', ['--show-npages', sourceFile]);
|
|
63
63
|
const { code } = await process.wait();
|
|
64
64
|
if (code != 0) {
|
|
65
65
|
const errorOutput = await process.readError();
|
|
@@ -78,14 +78,19 @@ export async function getPdfPageCount(file) {
|
|
|
78
78
|
await result_1;
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
|
-
export async function
|
|
81
|
+
export async function extractPdfPages(file, pages) {
|
|
82
82
|
const env_2 = { stack: [], error: void 0, hasError: false };
|
|
83
83
|
try {
|
|
84
84
|
const stack = __addDisposableResource(env_2, new AsyncDisposableStack(), true);
|
|
85
|
-
const
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
85
|
+
const [sourceFile] = await getPdfSourceFiles([file], stack);
|
|
86
|
+
const resultFile = TemporaryFile.create();
|
|
87
|
+
const pagesString = toQpdfPageRangeString(pages);
|
|
88
|
+
const processResult = await spawnWaitReadCommand('string', 'qpdf', ['--empty', '--pages', sourceFile, pagesString, '--', resultFile.path]);
|
|
89
|
+
if (processResult.code != 0) {
|
|
90
|
+
await resultFile.dispose();
|
|
91
|
+
throw new Error(processResult.error.trim());
|
|
92
|
+
}
|
|
93
|
+
return resultFile;
|
|
89
94
|
}
|
|
90
95
|
catch (e_2) {
|
|
91
96
|
env_2.error = e_2;
|
|
@@ -97,14 +102,26 @@ export async function mergePdfs(pdfs) {
|
|
|
97
102
|
await result_2;
|
|
98
103
|
}
|
|
99
104
|
}
|
|
100
|
-
export async function
|
|
105
|
+
export async function mergePdfs(sourceFiles) {
|
|
101
106
|
const env_3 = { stack: [], error: void 0, hasError: false };
|
|
102
107
|
try {
|
|
103
108
|
const stack = __addDisposableResource(env_3, new AsyncDisposableStack(), true);
|
|
104
109
|
const resultFile = __addDisposableResource(env_3, TemporaryFile.create(), true);
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
110
|
+
const sourceFilePaths = await getPdfSourceFiles(sourceFiles, stack);
|
|
111
|
+
const pages = sourceFiles.map((source) => isInputWithPageRange(source) ? source.pages : undefined);
|
|
112
|
+
const sourceArguments = sourceFilePaths.flatMap((path, index) => {
|
|
113
|
+
const pageRanges = pages[index];
|
|
114
|
+
if (isUndefined(pageRanges)) {
|
|
115
|
+
return [path];
|
|
116
|
+
}
|
|
117
|
+
const pagesString = toQpdfPageRangeString(pageRanges);
|
|
118
|
+
return [path, pagesString];
|
|
119
|
+
});
|
|
120
|
+
const processResult = await spawnWaitReadCommand('string', 'qpdf', ['--empty', '--pages', ...sourceArguments, '--', resultFile.path]);
|
|
121
|
+
if (processResult.code != 0) {
|
|
122
|
+
throw new Error(processResult.error);
|
|
123
|
+
}
|
|
124
|
+
return resultFile;
|
|
108
125
|
}
|
|
109
126
|
catch (e_3) {
|
|
110
127
|
env_3.error = e_3;
|
|
@@ -116,22 +133,32 @@ export async function mergePdfsStream(pdfs) {
|
|
|
116
133
|
await result_3;
|
|
117
134
|
}
|
|
118
135
|
}
|
|
119
|
-
async function
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
136
|
+
export async function overlayPdfs(base, overlay, options) {
|
|
137
|
+
const env_4 = { stack: [], error: void 0, hasError: false };
|
|
138
|
+
try {
|
|
139
|
+
const stack = __addDisposableResource(env_4, new AsyncDisposableStack(), true);
|
|
140
|
+
const [baseFilePath, overlayFilePath] = await getPdfSourceFiles([base, overlay], stack);
|
|
141
|
+
const resultFile = TemporaryFile.create();
|
|
142
|
+
stack.use(resultFile);
|
|
143
|
+
const args = [baseFilePath, '--overlay', overlayFilePath];
|
|
144
|
+
if (options?.repeat) {
|
|
145
|
+
args.push('--repeat=1');
|
|
123
146
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
147
|
+
args.push('--', resultFile.path);
|
|
148
|
+
const processResult = await spawnWaitReadCommand('string', 'qpdf', args);
|
|
149
|
+
if (processResult.code != 0) {
|
|
150
|
+
throw new Error(processResult.error);
|
|
151
|
+
}
|
|
152
|
+
return resultFile;
|
|
153
|
+
}
|
|
154
|
+
catch (e_4) {
|
|
155
|
+
env_4.error = e_4;
|
|
156
|
+
env_4.hasError = true;
|
|
157
|
+
}
|
|
158
|
+
finally {
|
|
159
|
+
const result_4 = __disposeResources(env_4);
|
|
160
|
+
if (result_4)
|
|
161
|
+
await result_4;
|
|
135
162
|
}
|
|
136
163
|
}
|
|
137
164
|
/**
|
|
@@ -151,3 +178,35 @@ export async function pdfToImage(file, page, size, format) {
|
|
|
151
178
|
}
|
|
152
179
|
return await process.readOutputBytes();
|
|
153
180
|
}
|
|
181
|
+
function toQpdfPageRangeString(pages) {
|
|
182
|
+
return pages.map((page) => {
|
|
183
|
+
if (isNumber(page)) {
|
|
184
|
+
return String(page);
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
const from = page.from ?? '1';
|
|
188
|
+
const to = page.to ?? 'z';
|
|
189
|
+
return `${from}-${to}`;
|
|
190
|
+
}
|
|
191
|
+
}).join(',');
|
|
192
|
+
}
|
|
193
|
+
async function getPdfSourceFiles(input, stack) {
|
|
194
|
+
return await Promise.all(input.map(async (pdf) => {
|
|
195
|
+
if (isUint8Array(pdf) || isReadableStream(pdf)) {
|
|
196
|
+
const tmpFile = await TemporaryFile.from(pdf);
|
|
197
|
+
stack.use(tmpFile);
|
|
198
|
+
return tmpFile.path;
|
|
199
|
+
}
|
|
200
|
+
if (isString(pdf)) {
|
|
201
|
+
return pdf;
|
|
202
|
+
}
|
|
203
|
+
if (isInputWithPageRange(pdf)) {
|
|
204
|
+
const [path] = await getPdfSourceFiles([pdf.input], stack);
|
|
205
|
+
return path;
|
|
206
|
+
}
|
|
207
|
+
return pdf.file;
|
|
208
|
+
}));
|
|
209
|
+
}
|
|
210
|
+
export function isInputWithPageRange(input) {
|
|
211
|
+
return isObject(input) && hasOwnProperty(input, 'input') && hasOwnProperty(input, 'pages');
|
|
212
|
+
}
|
package/process/spawn.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ export type SpawnOptions = {
|
|
|
18
18
|
workingDirectory?: string;
|
|
19
19
|
environment?: Record<string, string>;
|
|
20
20
|
};
|
|
21
|
-
export type SpawnCommandResult = TransformStream<Uint8Array, Uint8Array
|
|
21
|
+
export type SpawnCommandResult = TransformStream<Uint8Array, Uint8Array<ArrayBuffer>> & {
|
|
22
22
|
process: ChildProcessWithoutNullStreams;
|
|
23
23
|
stderr: ReadableStream<Uint8Array>;
|
|
24
24
|
write(chunk: ReadableStream<Uint8Array> | Uint8Array | string, options?: StreamPipeOptions): Promise<void>;
|
package/renderer/typst.d.ts
CHANGED
|
@@ -43,6 +43,11 @@ export type TypstRenderOptions = {
|
|
|
43
43
|
* One (or multiple comma-separated) PDF standards that Typst will enforce conformance with.
|
|
44
44
|
*/
|
|
45
45
|
pdfStandard?: LiteralUnion<'1.4' | '1.5' | '1.6' | '1.7' | '2.0' | 'a-1b' | 'a-1a' | 'a-2b' | 'a-2u' | 'a-2a' | 'a-3b' | 'a-3u' | 'a-3a' | 'a-4' | 'a-4f' | 'a-4e' | 'ua-1', string>;
|
|
46
|
+
/**
|
|
47
|
+
* A reference file to use when rendering to docx format. This allows customizing styles, fonts, etc. in the generated docx file. The file should be a valid docx file that serves as a template for the output.
|
|
48
|
+
* The reference file can be generated with `pandoc --print-default-data-file reference.docx`
|
|
49
|
+
*/
|
|
50
|
+
docxReferenceFile?: string;
|
|
46
51
|
};
|
|
47
52
|
/**
|
|
48
53
|
* Renders Typst source code to a file in the specified format.
|
package/renderer/typst.js
CHANGED
|
@@ -14,10 +14,9 @@ import { isDefined, isNumber, isString } from '../utils/type-guards.js';
|
|
|
14
14
|
export async function renderTypst(source, options) {
|
|
15
15
|
const format = options?.format ?? 'pdf';
|
|
16
16
|
const command = (format == 'docx') ? 'pandoc' : 'typst';
|
|
17
|
-
let args =
|
|
18
|
-
? ['--from', 'typst', '--to', 'docx', '--output']
|
|
19
|
-
: ['compile', '--format', format];
|
|
17
|
+
let args = [];
|
|
20
18
|
if (command == 'typst') {
|
|
19
|
+
args = ['compile', '--format', format];
|
|
21
20
|
if (isDefined(options?.root)) {
|
|
22
21
|
args.push('--root', options.root);
|
|
23
22
|
}
|
|
@@ -45,14 +44,19 @@ export async function renderTypst(source, options) {
|
|
|
45
44
|
args.push('--pages', pageParts.join(','));
|
|
46
45
|
}
|
|
47
46
|
}
|
|
48
|
-
|
|
47
|
+
else if (command == 'pandoc') {
|
|
48
|
+
args = ['--from', 'typst', '--to', 'docx', '--output', '-'];
|
|
49
|
+
if (isDefined(options?.docxReferenceFile)) {
|
|
50
|
+
args.push('--reference-doc', options.docxReferenceFile);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
49
53
|
const process = await spawnCommand(command, args);
|
|
50
54
|
const [{ code, output, error }] = await Promise.all([
|
|
51
55
|
process.waitRead('binary', { throwOnNonZeroExitCode: false }),
|
|
52
56
|
process.write(source),
|
|
53
57
|
]);
|
|
54
58
|
const errorString = decodeText(error);
|
|
55
|
-
if (code
|
|
59
|
+
if (code != 0) {
|
|
56
60
|
throw new Error(`
|
|
57
61
|
Typst compilation failed with exit code ${code}.\n
|
|
58
62
|
Error Output:\n${errorString}
|