pdfdancer-client-typescript 1.0.1 → 1.0.2
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/.github/workflows/ci.yml +37 -0
- package/README.md +60 -64
- package/dist/__tests__/assertions.d.ts +2 -0
- package/dist/__tests__/assertions.d.ts.map +1 -0
- package/dist/__tests__/assertions.js +11 -0
- package/dist/__tests__/assertions.js.map +1 -0
- package/dist/client-v1.d.ts +3 -24
- package/dist/client-v1.d.ts.map +1 -1
- package/dist/client-v1.js +6 -23
- package/dist/client-v1.js.map +1 -1
- package/dist/exceptions.d.ts +0 -3
- package/dist/exceptions.d.ts.map +1 -1
- package/dist/exceptions.js +0 -3
- package/dist/exceptions.js.map +1 -1
- package/dist/models.d.ts +7 -8
- package/dist/models.d.ts.map +1 -1
- package/dist/models.js +28 -13
- package/dist/models.js.map +1 -1
- package/dist/paragraph-builder.d.ts +0 -8
- package/dist/paragraph-builder.d.ts.map +1 -1
- package/dist/paragraph-builder.js +0 -8
- package/dist/paragraph-builder.js.map +1 -1
- package/example.ts +6 -10
- package/fixtures/form-xobject-example.pdf +0 -0
- package/jest.config.js +25 -24
- package/package.json +1 -1
- package/src/__tests__/assertions.ts +12 -0
- package/src/__tests__/client-v1.test.ts +6 -6
- package/src/__tests__/e2e/form.test.ts +42 -44
- package/src/__tests__/e2e/image.test.ts +97 -101
- package/src/__tests__/e2e/line.test.ts +115 -127
- package/src/__tests__/e2e/page.test.ts +3 -6
- package/src/__tests__/e2e/paragraph.test.ts +187 -204
- package/src/__tests__/e2e/path.test.ts +12 -16
- package/src/client-v1.ts +575 -591
- package/src/exceptions.ts +27 -30
- package/src/models.ts +382 -355
- package/src/paragraph-builder.ts +0 -8
package/src/client-v1.ts
CHANGED
|
@@ -6,30 +6,30 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
FontNotFoundException,
|
|
10
|
+
HttpClientException,
|
|
11
|
+
PdfDancerException,
|
|
12
|
+
SessionException,
|
|
13
|
+
ValidationException
|
|
14
14
|
} from './exceptions';
|
|
15
15
|
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
16
|
+
AddRequest,
|
|
17
|
+
BoundingRect,
|
|
18
|
+
DeleteRequest,
|
|
19
|
+
FindRequest,
|
|
20
|
+
Font,
|
|
21
|
+
Image,
|
|
22
|
+
ModifyRequest,
|
|
23
|
+
ModifyTextRequest,
|
|
24
|
+
MoveRequest,
|
|
25
|
+
ObjectRef,
|
|
26
|
+
ObjectType,
|
|
27
|
+
Paragraph,
|
|
28
|
+
Position,
|
|
29
|
+
PositionMode,
|
|
30
|
+
ShapeType
|
|
31
31
|
} from './models';
|
|
32
|
-
import {
|
|
32
|
+
import {ParagraphBuilder} from './paragraph-builder';
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
35
|
* REST API client for interacting with the PDFDancer PDF manipulation service.
|
|
@@ -40,634 +40,618 @@ import { ParagraphBuilder } from './paragraph-builder';
|
|
|
40
40
|
* Mirrors the Python Client class functionality exactly.
|
|
41
41
|
*/
|
|
42
42
|
export class ClientV1 {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
43
|
+
private _token: string;
|
|
44
|
+
private _baseUrl: string;
|
|
45
|
+
private _readTimeout: number;
|
|
46
|
+
private _pdfBytes: Uint8Array;
|
|
47
|
+
private _sessionId!: string;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Creates a new client with PDF data.
|
|
51
|
+
* This constructor initializes the client, uploads the PDF data to create
|
|
52
|
+
* a new session, and prepares the client for PDF manipulation operations.
|
|
53
|
+
*/
|
|
54
|
+
constructor(
|
|
55
|
+
token: string,
|
|
56
|
+
pdfData: Uint8Array | File | ArrayBuffer,
|
|
57
|
+
baseUrl: string = "http://localhost:8080",
|
|
58
|
+
readTimeout: number = 30000
|
|
59
|
+
) {
|
|
60
|
+
// Strict validation like Python client
|
|
61
|
+
if (!token || !token.trim()) {
|
|
62
|
+
throw new ValidationException("Authentication token cannot be null or empty");
|
|
63
|
+
}
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
// Process PDF data with validation
|
|
70
|
-
this._pdfBytes = this._processPdfData(pdfData);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Initialize the client by creating a session.
|
|
75
|
-
* Must be called after constructor before using the client.
|
|
76
|
-
*/
|
|
77
|
-
async init(): Promise<void> {
|
|
78
|
-
this._sessionId = await this._createSession();
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Process PDF data from various input types with strict validation.
|
|
83
|
-
* Equivalent to readFile() method in Python client.
|
|
84
|
-
*/
|
|
85
|
-
private _processPdfData(pdfData: Uint8Array | File | ArrayBuffer): Uint8Array {
|
|
86
|
-
if (!pdfData) {
|
|
87
|
-
throw new ValidationException("PDF data cannot be null");
|
|
88
|
-
}
|
|
65
|
+
this._token = token.trim();
|
|
66
|
+
this._baseUrl = baseUrl.replace(/\/$/, ''); // Remove trailing slash
|
|
67
|
+
this._readTimeout = readTimeout;
|
|
89
68
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
if (pdfData.length === 0) {
|
|
93
|
-
throw new ValidationException("PDF data cannot be empty");
|
|
94
|
-
}
|
|
95
|
-
return pdfData;
|
|
96
|
-
} else if (pdfData instanceof ArrayBuffer) {
|
|
97
|
-
const uint8Array = new Uint8Array(pdfData);
|
|
98
|
-
if (uint8Array.length === 0) {
|
|
99
|
-
throw new ValidationException("PDF data cannot be empty");
|
|
100
|
-
}
|
|
101
|
-
return uint8Array;
|
|
102
|
-
} else if (pdfData instanceof File) {
|
|
103
|
-
// Note: File reading will be handled asynchronously in the session creation
|
|
104
|
-
return new Uint8Array(); // Placeholder, will be replaced in _createSession
|
|
105
|
-
} else {
|
|
106
|
-
throw new ValidationException(`Unsupported PDF data type: ${typeof pdfData}`);
|
|
107
|
-
}
|
|
108
|
-
} catch (error) {
|
|
109
|
-
if (error instanceof ValidationException) {
|
|
110
|
-
throw error;
|
|
111
|
-
}
|
|
112
|
-
throw new PdfDancerException(`Failed to process PDF data: ${error}`, error as Error);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Extract meaningful error messages from API response.
|
|
118
|
-
* Parses JSON error responses with _embedded.errors structure.
|
|
119
|
-
*/
|
|
120
|
-
private async _extractErrorMessage(response?: Response): Promise<string> {
|
|
121
|
-
if (!response) {
|
|
122
|
-
return "Unknown error";
|
|
69
|
+
// Process PDF data with validation
|
|
70
|
+
this._pdfBytes = this._processPdfData(pdfData);
|
|
123
71
|
}
|
|
124
72
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
const messages = errors
|
|
133
|
-
.filter(error => error.message)
|
|
134
|
-
.map(error => error.message);
|
|
135
|
-
|
|
136
|
-
if (messages.length > 0) {
|
|
137
|
-
return messages.join("; ");
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Check for top-level message
|
|
143
|
-
if (errorData.message) {
|
|
144
|
-
return errorData.message;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Fallback to response text or status
|
|
148
|
-
return await response.text() || `HTTP ${response.status}`;
|
|
149
|
-
} catch {
|
|
150
|
-
// If JSON parsing fails, return response text or status
|
|
151
|
-
try {
|
|
152
|
-
return await response.text() || `HTTP ${response.status}`;
|
|
153
|
-
} catch {
|
|
154
|
-
return `HTTP ${response.status}`;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Creates a new PDF processing session by uploading the PDF data.
|
|
161
|
-
* Equivalent to createSession() method in Python client.
|
|
162
|
-
*/
|
|
163
|
-
private async _createSession(): Promise<string> {
|
|
164
|
-
try {
|
|
165
|
-
const formData = new FormData();
|
|
166
|
-
|
|
167
|
-
// Handle File objects by reading their content
|
|
168
|
-
if (this._pdfBytes instanceof File) {
|
|
169
|
-
formData.append('pdf', this._pdfBytes, 'document.pdf');
|
|
170
|
-
} else {
|
|
171
|
-
const blob = new Blob([this._pdfBytes.buffer as ArrayBuffer], { type: 'application/pdf' });
|
|
172
|
-
formData.append('pdf', blob, 'document.pdf');
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
const response = await fetch(`${this._baseUrl}/session/create`, {
|
|
176
|
-
method: 'POST',
|
|
177
|
-
headers: {
|
|
178
|
-
'Authorization': `Bearer ${this._token}`
|
|
179
|
-
},
|
|
180
|
-
body: formData,
|
|
181
|
-
signal: this._readTimeout > 0 ? AbortSignal.timeout(this._readTimeout) : undefined
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
if (!response.ok) {
|
|
185
|
-
const errorMessage = await this._extractErrorMessage(response);
|
|
186
|
-
throw new HttpClientException(`Failed to create session: ${errorMessage}`, response);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
const sessionId = (await response.text()).trim();
|
|
190
|
-
|
|
191
|
-
if (!sessionId) {
|
|
192
|
-
throw new SessionException("Server returned empty session ID");
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
return sessionId;
|
|
196
|
-
} catch (error) {
|
|
197
|
-
if (error instanceof HttpClientException || error instanceof SessionException) {
|
|
198
|
-
throw error;
|
|
199
|
-
}
|
|
200
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
201
|
-
throw new HttpClientException(`Failed to create session: ${errorMessage}`, undefined, error as Error);
|
|
73
|
+
/**
|
|
74
|
+
* Initialize the client by creating a session.
|
|
75
|
+
* Must be called after constructor before using the client.
|
|
76
|
+
*/
|
|
77
|
+
private async init(): Promise<this> {
|
|
78
|
+
this._sessionId = await this._createSession();
|
|
79
|
+
return this;
|
|
202
80
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
* Equivalent to retrieve() method pattern in Python client.
|
|
208
|
-
*/
|
|
209
|
-
private async _makeRequest(
|
|
210
|
-
method: string,
|
|
211
|
-
path: string,
|
|
212
|
-
data?: Record<string, any>,
|
|
213
|
-
params?: Record<string, string>
|
|
214
|
-
): Promise<Response> {
|
|
215
|
-
const url = new URL(`${this._baseUrl}${path}`);
|
|
216
|
-
if (params) {
|
|
217
|
-
Object.entries(params).forEach(([key, value]) => {
|
|
218
|
-
url.searchParams.append(key, value);
|
|
219
|
-
});
|
|
81
|
+
|
|
82
|
+
static async create(token: string, pdfData: Uint8Array, baseUrl: string, timeout: number = 0): Promise<ClientV1> {
|
|
83
|
+
const client = new ClientV1(token, pdfData, baseUrl, timeout);
|
|
84
|
+
return await client.init();
|
|
220
85
|
}
|
|
221
86
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
method,
|
|
231
|
-
headers,
|
|
232
|
-
body: data ? JSON.stringify(data) : undefined,
|
|
233
|
-
signal: this._readTimeout > 0 ? AbortSignal.timeout(this._readTimeout) : undefined
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
// Handle FontNotFoundException specifically like Python client
|
|
237
|
-
if (response.status === 404) {
|
|
87
|
+
/**
|
|
88
|
+
* Process PDF data from various input types with strict validation.
|
|
89
|
+
*/
|
|
90
|
+
private _processPdfData(pdfData: Uint8Array | File | ArrayBuffer): Uint8Array {
|
|
91
|
+
if (!pdfData) {
|
|
92
|
+
throw new ValidationException("PDF data cannot be null");
|
|
93
|
+
}
|
|
94
|
+
|
|
238
95
|
try {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
throw new HttpClientException(`API request failed: ${errorMessage}`, undefined, error as Error);
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// Search Operations - matching Python client exactly
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* Searches for PDF objects matching the specified criteria.
|
|
270
|
-
* This method provides flexible search capabilities across all PDF content,
|
|
271
|
-
* allowing filtering by object type and position constraints.
|
|
272
|
-
*/
|
|
273
|
-
async find(objectType?: ObjectType, position?: Position): Promise<ObjectRef[]> {
|
|
274
|
-
const requestData = new FindRequest(objectType, position).toDict();
|
|
275
|
-
const response = await this._makeRequest('POST', '/pdf/find', requestData);
|
|
276
|
-
|
|
277
|
-
const objectsData = await response.json() as any[];
|
|
278
|
-
return objectsData.map((objData: any) => this._parseObjectRef(objData));
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* Searches for paragraph objects at the specified position.
|
|
283
|
-
* Equivalent to findParagraphs() in Python client.
|
|
284
|
-
*/
|
|
285
|
-
async findParagraphs(position?: Position): Promise<ObjectRef[]> {
|
|
286
|
-
return this.find(ObjectType.PARAGRAPH, position);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* Searches for image objects at the specified position.
|
|
291
|
-
* Equivalent to findImages() in Python client.
|
|
292
|
-
*/
|
|
293
|
-
async findImages(position?: Position): Promise<ObjectRef[]> {
|
|
294
|
-
return this.find(ObjectType.IMAGE, position);
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/**
|
|
298
|
-
* Searches for form field objects at the specified position.
|
|
299
|
-
* Equivalent to findForms() in Python client.
|
|
300
|
-
*/
|
|
301
|
-
async findForms(position?: Position): Promise<ObjectRef[]> {
|
|
302
|
-
return this.find(ObjectType.FORM, position);
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
/**
|
|
306
|
-
* Searches for vector path objects at the specified position.
|
|
307
|
-
* Equivalent to findPaths() in Python client.
|
|
308
|
-
*/
|
|
309
|
-
async findPaths(position?: Position): Promise<ObjectRef[]> {
|
|
310
|
-
return this.find(ObjectType.PATH, position);
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
/**
|
|
314
|
-
* Searches for text line objects at the specified position.
|
|
315
|
-
* Equivalent to findTextLines() in Python client.
|
|
316
|
-
*/
|
|
317
|
-
async findTextLines(position?: Position): Promise<ObjectRef[]> {
|
|
318
|
-
return this.find(ObjectType.TEXT_LINE, position);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
// Page Operations
|
|
322
|
-
|
|
323
|
-
/**
|
|
324
|
-
* Retrieves references to all pages in the PDF document.
|
|
325
|
-
* Equivalent to getPages() in Python client.
|
|
326
|
-
*/
|
|
327
|
-
async getPages(): Promise<ObjectRef[]> {
|
|
328
|
-
const response = await this._makeRequest('POST', '/pdf/page/find');
|
|
329
|
-
const pagesData = await response.json() as any[];
|
|
330
|
-
return pagesData.map((pageData: any) => this._parseObjectRef(pageData));
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
/**
|
|
334
|
-
* Retrieves a reference to a specific page by its page index.
|
|
335
|
-
* Equivalent to getPage() in Python client.
|
|
336
|
-
*/
|
|
337
|
-
async getPage(pageIndex: number): Promise<ObjectRef | null> {
|
|
338
|
-
if (pageIndex < 0) {
|
|
339
|
-
throw new ValidationException(`Page index must be >= 0, got ${pageIndex}`);
|
|
96
|
+
if (pdfData instanceof Uint8Array) {
|
|
97
|
+
if (pdfData.length === 0) {
|
|
98
|
+
throw new ValidationException("PDF data cannot be empty");
|
|
99
|
+
}
|
|
100
|
+
return pdfData;
|
|
101
|
+
} else if (pdfData instanceof ArrayBuffer) {
|
|
102
|
+
const uint8Array = new Uint8Array(pdfData);
|
|
103
|
+
if (uint8Array.length === 0) {
|
|
104
|
+
throw new ValidationException("PDF data cannot be empty");
|
|
105
|
+
}
|
|
106
|
+
return uint8Array;
|
|
107
|
+
} else if (pdfData instanceof File) {
|
|
108
|
+
// Note: File reading will be handled asynchronously in the session creation
|
|
109
|
+
return new Uint8Array(); // Placeholder, will be replaced in _createSession
|
|
110
|
+
} else {
|
|
111
|
+
throw new ValidationException(`Unsupported PDF data type: ${typeof pdfData}`);
|
|
112
|
+
}
|
|
113
|
+
} catch (error) {
|
|
114
|
+
if (error instanceof ValidationException) {
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
throw new PdfDancerException(`Failed to process PDF data: ${error}`, error as Error);
|
|
118
|
+
}
|
|
340
119
|
}
|
|
341
120
|
|
|
342
|
-
|
|
343
|
-
|
|
121
|
+
/**
|
|
122
|
+
* Extract meaningful error messages from API response.
|
|
123
|
+
* Parses JSON error responses with _embedded.errors structure.
|
|
124
|
+
*/
|
|
125
|
+
private async _extractErrorMessage(response?: Response): Promise<string> {
|
|
126
|
+
if (!response) {
|
|
127
|
+
return "Unknown error";
|
|
128
|
+
}
|
|
344
129
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
130
|
+
try {
|
|
131
|
+
const errorData = await response.json() as any;
|
|
132
|
+
|
|
133
|
+
// Check for embedded errors structure
|
|
134
|
+
if (errorData._embedded?.errors) {
|
|
135
|
+
const errors = errorData._embedded.errors;
|
|
136
|
+
if (Array.isArray(errors)) {
|
|
137
|
+
const messages = errors
|
|
138
|
+
.filter(error => error.message)
|
|
139
|
+
.map(error => error.message);
|
|
140
|
+
|
|
141
|
+
if (messages.length > 0) {
|
|
142
|
+
return messages.join("; ");
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Check for top-level message
|
|
148
|
+
if (errorData.message) {
|
|
149
|
+
return errorData.message;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Fallback to response text or status
|
|
153
|
+
return await response.text() || `HTTP ${response.status}`;
|
|
154
|
+
} catch {
|
|
155
|
+
// If JSON parsing fails, return response text or status
|
|
156
|
+
try {
|
|
157
|
+
return await response.text() || `HTTP ${response.status}`;
|
|
158
|
+
} catch {
|
|
159
|
+
return `HTTP ${response.status}`;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
348
162
|
}
|
|
349
163
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
164
|
+
/**
|
|
165
|
+
* Creates a new PDF processing session by uploading the PDF data.
|
|
166
|
+
*/
|
|
167
|
+
private async _createSession(): Promise<string> {
|
|
168
|
+
try {
|
|
169
|
+
const formData = new FormData();
|
|
170
|
+
|
|
171
|
+
// Handle File objects by reading their content
|
|
172
|
+
if (this._pdfBytes instanceof File) {
|
|
173
|
+
formData.append('pdf', this._pdfBytes, 'document.pdf');
|
|
174
|
+
} else {
|
|
175
|
+
const blob = new Blob([this._pdfBytes.buffer as ArrayBuffer], {type: 'application/pdf'});
|
|
176
|
+
formData.append('pdf', blob, 'document.pdf');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const response = await fetch(`${this._baseUrl}/session/create`, {
|
|
180
|
+
method: 'POST',
|
|
181
|
+
headers: {
|
|
182
|
+
'Authorization': `Bearer ${this._token}`
|
|
183
|
+
},
|
|
184
|
+
body: formData,
|
|
185
|
+
signal: this._readTimeout > 0 ? AbortSignal.timeout(this._readTimeout) : undefined
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
if (!response.ok) {
|
|
189
|
+
const errorMessage = await this._extractErrorMessage(response);
|
|
190
|
+
throw new HttpClientException(`Failed to create session: ${errorMessage}`, response);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const sessionId = (await response.text()).trim();
|
|
194
|
+
|
|
195
|
+
if (!sessionId) {
|
|
196
|
+
throw new SessionException("Server returned empty session ID");
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return sessionId;
|
|
200
|
+
} catch (error) {
|
|
201
|
+
if (error instanceof HttpClientException || error instanceof SessionException) {
|
|
202
|
+
throw error;
|
|
203
|
+
}
|
|
204
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
205
|
+
throw new HttpClientException(`Failed to create session: ${errorMessage}`, undefined, error as Error);
|
|
206
|
+
}
|
|
360
207
|
}
|
|
361
208
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
209
|
+
/**
|
|
210
|
+
* Make HTTP request with session headers and error handling.
|
|
211
|
+
*/
|
|
212
|
+
private async _makeRequest(
|
|
213
|
+
method: string,
|
|
214
|
+
path: string,
|
|
215
|
+
data?: Record<string, any>,
|
|
216
|
+
params?: Record<string, string>
|
|
217
|
+
): Promise<Response> {
|
|
218
|
+
const url = new URL(`${this._baseUrl}${path}`);
|
|
219
|
+
if (params) {
|
|
220
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
221
|
+
url.searchParams.append(key, value);
|
|
222
|
+
});
|
|
223
|
+
}
|
|
366
224
|
|
|
367
|
-
|
|
225
|
+
const headers: Record<string, string> = {
|
|
226
|
+
'Authorization': `Bearer ${this._token}`,
|
|
227
|
+
'X-Session-Id': this._sessionId,
|
|
228
|
+
'Content-Type': 'application/json'
|
|
229
|
+
};
|
|
368
230
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
231
|
+
try {
|
|
232
|
+
const response = await fetch(url.toString(), {
|
|
233
|
+
method,
|
|
234
|
+
headers,
|
|
235
|
+
body: data ? JSON.stringify(data) : undefined,
|
|
236
|
+
signal: this._readTimeout > 0 ? AbortSignal.timeout(this._readTimeout) : undefined
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// Handle FontNotFoundException specifically like Python client
|
|
240
|
+
if (response.status === 404) {
|
|
241
|
+
try {
|
|
242
|
+
const errorData = await response.json() as any;
|
|
243
|
+
if (errorData.error === 'FontNotFoundException') {
|
|
244
|
+
throw new FontNotFoundException(errorData.message || 'Font not found');
|
|
245
|
+
}
|
|
246
|
+
} catch (e) {
|
|
247
|
+
if (e instanceof FontNotFoundException) {
|
|
248
|
+
throw e;
|
|
249
|
+
}
|
|
250
|
+
// Continue with normal error handling if JSON parsing fails
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (!response.ok) {
|
|
255
|
+
const errorMessage = await this._extractErrorMessage(response);
|
|
256
|
+
throw new HttpClientException(`API request failed: ${errorMessage}`, response);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return response;
|
|
260
|
+
} catch (error) {
|
|
261
|
+
if (error instanceof FontNotFoundException || error instanceof HttpClientException) {
|
|
262
|
+
throw error;
|
|
263
|
+
}
|
|
264
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
265
|
+
throw new HttpClientException(`API request failed: ${errorMessage}`, undefined, error as Error);
|
|
266
|
+
}
|
|
376
267
|
}
|
|
377
268
|
|
|
378
|
-
|
|
379
|
-
const response = await this._makeRequest('DELETE', '/pdf/delete', requestData);
|
|
380
|
-
return await response.json() as boolean;
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
/**
|
|
384
|
-
* Moves a PDF object to a new position within the document.
|
|
385
|
-
* Equivalent to move() in Python client.
|
|
386
|
-
*/
|
|
387
|
-
async move(objectRef: ObjectRef, position: Position): Promise<boolean> {
|
|
388
|
-
if (!objectRef) {
|
|
389
|
-
throw new ValidationException("Object reference cannot be null");
|
|
390
|
-
}
|
|
391
|
-
if (!position) {
|
|
392
|
-
throw new ValidationException("Position cannot be null");
|
|
393
|
-
}
|
|
269
|
+
// Search Operations - matching Python client exactly
|
|
394
270
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
271
|
+
/**
|
|
272
|
+
* Searches for PDF objects matching the specified criteria.
|
|
273
|
+
* This method provides flexible search capabilities across all PDF content,
|
|
274
|
+
* allowing filtering by object type and position constraints.
|
|
275
|
+
*/
|
|
276
|
+
async find(objectType?: ObjectType, position?: Position): Promise<ObjectRef[]> {
|
|
277
|
+
const requestData = new FindRequest(objectType, position).toDict();
|
|
278
|
+
const response = await this._makeRequest('POST', '/pdf/find', requestData);
|
|
399
279
|
|
|
400
|
-
|
|
280
|
+
const objectsData = await response.json() as any[];
|
|
281
|
+
return objectsData.map((objData: any) => this._parseObjectRef(objData));
|
|
282
|
+
}
|
|
401
283
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
if (!image) {
|
|
408
|
-
throw new ValidationException("Image cannot be null");
|
|
284
|
+
/**
|
|
285
|
+
* Searches for paragraph objects at the specified position.
|
|
286
|
+
*/
|
|
287
|
+
async findParagraphs(position?: Position): Promise<ObjectRef[]> {
|
|
288
|
+
return this.find(ObjectType.PARAGRAPH, position);
|
|
409
289
|
}
|
|
410
290
|
|
|
411
|
-
|
|
412
|
-
|
|
291
|
+
/**
|
|
292
|
+
* Searches for image objects at the specified position.
|
|
293
|
+
*/
|
|
294
|
+
async findImages(position?: Position): Promise<ObjectRef[]> {
|
|
295
|
+
return this.find(ObjectType.IMAGE, position);
|
|
413
296
|
}
|
|
414
297
|
|
|
415
|
-
|
|
416
|
-
|
|
298
|
+
/**
|
|
299
|
+
* Searches for form field objects at the specified position.
|
|
300
|
+
*/
|
|
301
|
+
async findForms(position?: Position): Promise<ObjectRef[]> {
|
|
302
|
+
return this.find(ObjectType.FORM_X_OBJECT, position);
|
|
417
303
|
}
|
|
418
304
|
|
|
419
|
-
|
|
420
|
-
|
|
305
|
+
/**
|
|
306
|
+
* Searches for vector path objects at the specified position.
|
|
307
|
+
*/
|
|
308
|
+
async findPaths(position?: Position): Promise<ObjectRef[]> {
|
|
309
|
+
return this.find(ObjectType.PATH, position);
|
|
310
|
+
}
|
|
421
311
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
if (!paragraph) {
|
|
428
|
-
throw new ValidationException("Paragraph cannot be null");
|
|
312
|
+
/**
|
|
313
|
+
* Searches for text line objects at the specified position.
|
|
314
|
+
*/
|
|
315
|
+
async findTextLines(position?: Position): Promise<ObjectRef[]> {
|
|
316
|
+
return this.find(ObjectType.TEXT_LINE, position);
|
|
429
317
|
}
|
|
430
|
-
|
|
431
|
-
|
|
318
|
+
|
|
319
|
+
// Page Operations
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Retrieves references to all pages in the PDF document.
|
|
323
|
+
*/
|
|
324
|
+
async getPages(): Promise<ObjectRef[]> {
|
|
325
|
+
const response = await this._makeRequest('POST', '/pdf/page/find');
|
|
326
|
+
const pagesData = await response.json() as any[];
|
|
327
|
+
return pagesData.map((pageData: any) => this._parseObjectRef(pageData));
|
|
432
328
|
}
|
|
433
|
-
|
|
434
|
-
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Retrieves a reference to a specific page by its page index.
|
|
332
|
+
*/
|
|
333
|
+
async getPage(pageIndex: number): Promise<ObjectRef | null> {
|
|
334
|
+
if (pageIndex < 0) {
|
|
335
|
+
throw new ValidationException(`Page index must be >= 0, got ${pageIndex}`);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const params = {pageIndex: pageIndex.toString()};
|
|
339
|
+
const response = await this._makeRequest('POST', '/pdf/page/find', undefined, params);
|
|
340
|
+
|
|
341
|
+
const pagesData = await response.json() as any[];
|
|
342
|
+
if (!pagesData || pagesData.length === 0) {
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return this._parseObjectRef(pagesData[0]);
|
|
435
347
|
}
|
|
436
|
-
|
|
437
|
-
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Deletes a page from the PDF document.
|
|
351
|
+
*/
|
|
352
|
+
async deletePage(pageRef: ObjectRef): Promise<boolean> {
|
|
353
|
+
if (!pageRef) {
|
|
354
|
+
throw new ValidationException("Page reference cannot be null");
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const requestData = pageRef.toDict();
|
|
358
|
+
const response = await this._makeRequest('DELETE', '/pdf/page/delete', requestData);
|
|
359
|
+
return await response.json() as boolean;
|
|
438
360
|
}
|
|
439
361
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
// Modify Operations
|
|
454
|
-
|
|
455
|
-
/**
|
|
456
|
-
* Modifies a paragraph object or its text content.
|
|
457
|
-
* Equivalent to modifyParagraph() methods in Python client.
|
|
458
|
-
*/
|
|
459
|
-
async modifyParagraph(objectRef: ObjectRef, newParagraph: Paragraph | string): Promise<boolean> {
|
|
460
|
-
if (!objectRef) {
|
|
461
|
-
throw new ValidationException("Object reference cannot be null");
|
|
362
|
+
// Manipulation Operations
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Deletes the specified PDF object from the document.
|
|
366
|
+
*/
|
|
367
|
+
async delete(objectRef: ObjectRef): Promise<boolean> {
|
|
368
|
+
if (!objectRef) {
|
|
369
|
+
throw new ValidationException("Object reference cannot be null");
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const requestData = new DeleteRequest(objectRef).toDict();
|
|
373
|
+
const response = await this._makeRequest('DELETE', '/pdf/delete', requestData);
|
|
374
|
+
return await response.json() as boolean;
|
|
462
375
|
}
|
|
463
|
-
|
|
464
|
-
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Moves a PDF object to a new position within the document.
|
|
379
|
+
*/
|
|
380
|
+
async move(objectRef: ObjectRef, position: Position): Promise<boolean> {
|
|
381
|
+
if (!objectRef) {
|
|
382
|
+
throw new ValidationException("Object reference cannot be null");
|
|
383
|
+
}
|
|
384
|
+
if (!position) {
|
|
385
|
+
throw new ValidationException("Position cannot be null");
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const requestData = new MoveRequest(objectRef, position).toDict();
|
|
389
|
+
const response = await this._makeRequest('PUT', '/pdf/move', requestData);
|
|
390
|
+
return await response.json() as boolean;
|
|
465
391
|
}
|
|
466
392
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
393
|
+
// Add Operations
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Adds an image to the PDF document.
|
|
397
|
+
*/
|
|
398
|
+
async addImage(image: Image, position?: Position): Promise<boolean> {
|
|
399
|
+
if (!image) {
|
|
400
|
+
throw new ValidationException("Image cannot be null");
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (position) {
|
|
404
|
+
image.setPosition(position);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (!image.getPosition()) {
|
|
408
|
+
throw new ValidationException("Image position is null");
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return this._addObject(image);
|
|
477
412
|
}
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Adds a paragraph to the PDF document.
|
|
416
|
+
*/
|
|
417
|
+
async addParagraph(paragraph: Paragraph): Promise<boolean> {
|
|
418
|
+
if (!paragraph) {
|
|
419
|
+
throw new ValidationException("Paragraph cannot be null");
|
|
420
|
+
}
|
|
421
|
+
if (!paragraph.getPosition()) {
|
|
422
|
+
throw new ValidationException("Paragraph position is null");
|
|
423
|
+
}
|
|
424
|
+
if (paragraph.getPosition()!.pageIndex === undefined) {
|
|
425
|
+
throw new ValidationException("Paragraph position page index is null");
|
|
426
|
+
}
|
|
427
|
+
if (paragraph.getPosition()!.pageIndex! < 0) {
|
|
428
|
+
throw new ValidationException("Paragraph position page index is less than 0");
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
return this._addObject(paragraph);
|
|
487
432
|
}
|
|
488
|
-
|
|
489
|
-
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Internal method to add any PDF object.
|
|
436
|
+
*/
|
|
437
|
+
private async _addObject(pdfObject: Image | Paragraph): Promise<boolean> {
|
|
438
|
+
const requestData = new AddRequest(pdfObject).toDict();
|
|
439
|
+
const response = await this._makeRequest('POST', '/pdf/add', requestData);
|
|
440
|
+
return await response.json() as boolean;
|
|
490
441
|
}
|
|
491
442
|
|
|
492
|
-
|
|
493
|
-
const response = await this._makeRequest('PUT', '/pdf/text/line', requestData);
|
|
494
|
-
return await response.json() as boolean;
|
|
495
|
-
}
|
|
443
|
+
// Modify Operations
|
|
496
444
|
|
|
497
|
-
|
|
445
|
+
/**
|
|
446
|
+
* Modifies a paragraph object or its text content.
|
|
447
|
+
*/
|
|
448
|
+
async modifyParagraph(objectRef: ObjectRef, newParagraph: Paragraph | string): Promise<boolean> {
|
|
449
|
+
if (!objectRef) {
|
|
450
|
+
throw new ValidationException("Object reference cannot be null");
|
|
451
|
+
}
|
|
452
|
+
if (newParagraph === null || newParagraph === undefined) {
|
|
453
|
+
throw new ValidationException("New paragraph cannot be null");
|
|
454
|
+
}
|
|
498
455
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
456
|
+
if (typeof newParagraph === 'string') {
|
|
457
|
+
// Text modification
|
|
458
|
+
const requestData = new ModifyTextRequest(objectRef, newParagraph).toDict();
|
|
459
|
+
const response = await this._makeRequest('PUT', '/pdf/text/paragraph', requestData);
|
|
460
|
+
return await response.json() as boolean;
|
|
461
|
+
} else {
|
|
462
|
+
// Object modification
|
|
463
|
+
const requestData = new ModifyRequest(objectRef, newParagraph).toDict();
|
|
464
|
+
const response = await this._makeRequest('PUT', '/pdf/modify', requestData);
|
|
465
|
+
return await response.json() as boolean;
|
|
466
|
+
}
|
|
506
467
|
}
|
|
507
|
-
|
|
508
|
-
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Modifies a text line object.
|
|
471
|
+
*/
|
|
472
|
+
async modifyTextLine(objectRef: ObjectRef, newText: string): Promise<boolean> {
|
|
473
|
+
if (!objectRef) {
|
|
474
|
+
throw new ValidationException("Object reference cannot be null");
|
|
475
|
+
}
|
|
476
|
+
if (newText === null || newText === undefined) {
|
|
477
|
+
throw new ValidationException("New text cannot be null");
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
const requestData = new ModifyTextRequest(objectRef, newText).toDict();
|
|
481
|
+
const response = await this._makeRequest('PUT', '/pdf/text/line', requestData);
|
|
482
|
+
return await response.json() as boolean;
|
|
509
483
|
}
|
|
510
484
|
|
|
511
|
-
|
|
512
|
-
const response = await this._makeRequest('GET', '/font/find', undefined, params);
|
|
485
|
+
// Font Operations
|
|
513
486
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
487
|
+
/**
|
|
488
|
+
* Finds available fonts matching the specified name and size.
|
|
489
|
+
*/
|
|
490
|
+
async findFonts(fontName: string, fontSize: number): Promise<Font[]> {
|
|
491
|
+
if (!fontName || !fontName.trim()) {
|
|
492
|
+
throw new ValidationException("Font name cannot be null or empty");
|
|
493
|
+
}
|
|
494
|
+
if (fontSize <= 0) {
|
|
495
|
+
throw new ValidationException(`Font size must be positive, got ${fontSize}`);
|
|
496
|
+
}
|
|
517
497
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
* Equivalent to registerFont() in Python client.
|
|
521
|
-
*/
|
|
522
|
-
async registerFont(ttfFile: Uint8Array | File): Promise<string> {
|
|
523
|
-
if (!ttfFile) {
|
|
524
|
-
throw new ValidationException("TTF file cannot be null");
|
|
525
|
-
}
|
|
498
|
+
const params = {fontName: fontName.trim()};
|
|
499
|
+
const response = await this._makeRequest('GET', '/font/find', undefined, params);
|
|
526
500
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
let filename: string;
|
|
530
|
-
|
|
531
|
-
if (ttfFile instanceof Uint8Array) {
|
|
532
|
-
if (ttfFile.length === 0) {
|
|
533
|
-
throw new ValidationException("Font data cannot be empty");
|
|
534
|
-
}
|
|
535
|
-
fontData = ttfFile;
|
|
536
|
-
filename = 'font.ttf';
|
|
537
|
-
} else if (ttfFile instanceof File) {
|
|
538
|
-
if (ttfFile.size === 0) {
|
|
539
|
-
throw new ValidationException("Font file is empty");
|
|
540
|
-
}
|
|
541
|
-
fontData = new Uint8Array(await ttfFile.arrayBuffer());
|
|
542
|
-
filename = ttfFile.name;
|
|
543
|
-
} else {
|
|
544
|
-
throw new ValidationException(`Unsupported font file type: ${typeof ttfFile}`);
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
// Upload font file
|
|
548
|
-
const formData = new FormData();
|
|
549
|
-
const blob = new Blob([fontData.buffer as ArrayBuffer], { type: 'font/ttf' });
|
|
550
|
-
formData.append('ttfFile', blob, filename);
|
|
551
|
-
|
|
552
|
-
const response = await fetch(`${this._baseUrl}/font/register`, {
|
|
553
|
-
method: 'POST',
|
|
554
|
-
headers: {
|
|
555
|
-
'Authorization': `Bearer ${this._token}`,
|
|
556
|
-
'X-Session-Id': this._sessionId
|
|
557
|
-
},
|
|
558
|
-
body: formData,
|
|
559
|
-
signal: AbortSignal.timeout(30000)
|
|
560
|
-
});
|
|
561
|
-
|
|
562
|
-
if (!response.ok) {
|
|
563
|
-
const errorMessage = await this._extractErrorMessage(response);
|
|
564
|
-
throw new HttpClientException(`Font registration failed: ${errorMessage}`, response);
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
return (await response.text()).trim();
|
|
568
|
-
} catch (error) {
|
|
569
|
-
if (error instanceof ValidationException || error instanceof HttpClientException) {
|
|
570
|
-
throw error;
|
|
571
|
-
}
|
|
572
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
573
|
-
throw new PdfDancerException(`Failed to read font file: ${errorMessage}`, error as Error);
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
// Document Operations
|
|
578
|
-
|
|
579
|
-
/**
|
|
580
|
-
* Downloads the current state of the PDF document with all modifications applied.
|
|
581
|
-
* Equivalent to getPDFFile() in Python client.
|
|
582
|
-
*/
|
|
583
|
-
async getPdfFile(): Promise<Uint8Array> {
|
|
584
|
-
const response = await this._makeRequest('GET', `/session/${this._sessionId}/pdf`);
|
|
585
|
-
return new Uint8Array(await response.arrayBuffer());
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
/**
|
|
589
|
-
* Saves the current PDF to a file (browser environment).
|
|
590
|
-
* Downloads the PDF in the browser.
|
|
591
|
-
*/
|
|
592
|
-
async savePdf(filename: string = 'document.pdf'): Promise<void> {
|
|
593
|
-
if (!filename) {
|
|
594
|
-
throw new ValidationException("Filename cannot be null or empty");
|
|
501
|
+
const fontNames = await response.json() as string[];
|
|
502
|
+
return fontNames.map((name: string) => new Font(name, fontSize));
|
|
595
503
|
}
|
|
596
504
|
|
|
597
|
-
|
|
598
|
-
|
|
505
|
+
/**
|
|
506
|
+
* Registers a custom font for use in PDF operations.
|
|
507
|
+
*/
|
|
508
|
+
async registerFont(ttfFile: Uint8Array | File): Promise<string> {
|
|
509
|
+
if (!ttfFile) {
|
|
510
|
+
throw new ValidationException("TTF file cannot be null");
|
|
511
|
+
}
|
|
599
512
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
513
|
+
try {
|
|
514
|
+
let fontData: Uint8Array;
|
|
515
|
+
let filename: string;
|
|
516
|
+
|
|
517
|
+
if (ttfFile instanceof Uint8Array) {
|
|
518
|
+
if (ttfFile.length === 0) {
|
|
519
|
+
throw new ValidationException("Font data cannot be empty");
|
|
520
|
+
}
|
|
521
|
+
fontData = ttfFile;
|
|
522
|
+
filename = 'font.ttf';
|
|
523
|
+
} else if (ttfFile instanceof File) {
|
|
524
|
+
if (ttfFile.size === 0) {
|
|
525
|
+
throw new ValidationException("Font file is empty");
|
|
526
|
+
}
|
|
527
|
+
fontData = new Uint8Array(await ttfFile.arrayBuffer());
|
|
528
|
+
filename = ttfFile.name;
|
|
529
|
+
} else {
|
|
530
|
+
throw new ValidationException(`Unsupported font file type: ${typeof ttfFile}`);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// Upload font file
|
|
534
|
+
const formData = new FormData();
|
|
535
|
+
const blob = new Blob([fontData.buffer as ArrayBuffer], {type: 'font/ttf'});
|
|
536
|
+
formData.append('ttfFile', blob, filename);
|
|
537
|
+
|
|
538
|
+
const response = await fetch(`${this._baseUrl}/font/register`, {
|
|
539
|
+
method: 'POST',
|
|
540
|
+
headers: {
|
|
541
|
+
'Authorization': `Bearer ${this._token}`,
|
|
542
|
+
'X-Session-Id': this._sessionId
|
|
543
|
+
},
|
|
544
|
+
body: formData,
|
|
545
|
+
signal: AbortSignal.timeout(30000)
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
if (!response.ok) {
|
|
549
|
+
const errorMessage = await this._extractErrorMessage(response);
|
|
550
|
+
throw new HttpClientException(`Font registration failed: ${errorMessage}`, response);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
return (await response.text()).trim();
|
|
554
|
+
} catch (error) {
|
|
555
|
+
if (error instanceof ValidationException || error instanceof HttpClientException) {
|
|
556
|
+
throw error;
|
|
557
|
+
}
|
|
558
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
559
|
+
throw new PdfDancerException(`Failed to read font file: ${errorMessage}`, error as Error);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
603
562
|
|
|
604
|
-
|
|
605
|
-
link.href = url;
|
|
606
|
-
link.download = filename;
|
|
607
|
-
document.body.appendChild(link);
|
|
608
|
-
link.click();
|
|
609
|
-
document.body.removeChild(link);
|
|
563
|
+
// Document Operations
|
|
610
564
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
// Utility Methods
|
|
619
|
-
|
|
620
|
-
/**
|
|
621
|
-
* Parse JSON object data into ObjectRef instance.
|
|
622
|
-
*/
|
|
623
|
-
private _parseObjectRef(objData: any): ObjectRef {
|
|
624
|
-
const positionData = objData.position || {};
|
|
625
|
-
const position = positionData ? this._parsePosition(positionData) : new Position();
|
|
626
|
-
|
|
627
|
-
const objectType = ObjectType[objData.type as keyof typeof ObjectType];
|
|
628
|
-
|
|
629
|
-
return new ObjectRef(
|
|
630
|
-
objData.internalId,
|
|
631
|
-
position,
|
|
632
|
-
objectType
|
|
633
|
-
);
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
/**
|
|
637
|
-
* Parse JSON position data into Position instance.
|
|
638
|
-
*/
|
|
639
|
-
private _parsePosition(posData: any): Position {
|
|
640
|
-
const position = new Position();
|
|
641
|
-
position.pageIndex = posData.pageIndex;
|
|
642
|
-
position.textStartsWith = posData.textStartsWith;
|
|
643
|
-
|
|
644
|
-
if (posData.shape) {
|
|
645
|
-
position.shape = ShapeType[posData.shape as keyof typeof ShapeType];
|
|
565
|
+
/**
|
|
566
|
+
* Downloads the current state of the PDF document with all modifications applied.
|
|
567
|
+
*/
|
|
568
|
+
async getPdfFile(): Promise<Uint8Array> {
|
|
569
|
+
const response = await this._makeRequest('GET', `/session/${this._sessionId}/pdf`);
|
|
570
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
646
571
|
}
|
|
647
|
-
|
|
648
|
-
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Saves the current PDF to a file (browser environment).
|
|
575
|
+
* Downloads the PDF in the browser.
|
|
576
|
+
*/
|
|
577
|
+
async savePdf(filename: string = 'document.pdf'): Promise<void> {
|
|
578
|
+
if (!filename) {
|
|
579
|
+
throw new ValidationException("Filename cannot be null or empty");
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
try {
|
|
583
|
+
const pdfData = await this.getPdfFile();
|
|
584
|
+
|
|
585
|
+
// Create blob and download link
|
|
586
|
+
const blob = new Blob([pdfData.buffer as ArrayBuffer], {type: 'application/pdf'});
|
|
587
|
+
const url = URL.createObjectURL(blob);
|
|
588
|
+
|
|
589
|
+
const link = document.createElement('a');
|
|
590
|
+
link.href = url;
|
|
591
|
+
link.download = filename;
|
|
592
|
+
document.body.appendChild(link);
|
|
593
|
+
link.click();
|
|
594
|
+
document.body.removeChild(link);
|
|
595
|
+
|
|
596
|
+
URL.revokeObjectURL(url);
|
|
597
|
+
} catch (error) {
|
|
598
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
599
|
+
throw new PdfDancerException(`Failed to save PDF file: ${errorMessage}`, error as Error);
|
|
600
|
+
}
|
|
649
601
|
}
|
|
650
602
|
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
603
|
+
// Utility Methods
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* Parse JSON object data into ObjectRef instance.
|
|
607
|
+
*/
|
|
608
|
+
private _parseObjectRef(objData: any): ObjectRef {
|
|
609
|
+
const positionData = objData.position || {};
|
|
610
|
+
const position = positionData ? this._parsePosition(positionData) : new Position();
|
|
611
|
+
|
|
612
|
+
const objectType = ObjectType[objData.type as keyof typeof ObjectType];
|
|
613
|
+
|
|
614
|
+
return new ObjectRef(
|
|
615
|
+
objData.internalId,
|
|
616
|
+
position,
|
|
617
|
+
objectType
|
|
618
|
+
);
|
|
659
619
|
}
|
|
660
620
|
|
|
661
|
-
|
|
662
|
-
|
|
621
|
+
/**
|
|
622
|
+
* Parse JSON position data into Position instance.
|
|
623
|
+
*/
|
|
624
|
+
private _parsePosition(posData: any): Position {
|
|
625
|
+
const position = new Position();
|
|
626
|
+
position.pageIndex = posData.pageIndex;
|
|
627
|
+
position.textStartsWith = posData.textStartsWith;
|
|
628
|
+
|
|
629
|
+
if (posData.shape) {
|
|
630
|
+
position.shape = ShapeType[posData.shape as keyof typeof ShapeType];
|
|
631
|
+
}
|
|
632
|
+
if (posData.mode) {
|
|
633
|
+
position.mode = PositionMode[posData.mode as keyof typeof PositionMode];
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
if (posData.boundingRect) {
|
|
637
|
+
const rectData = posData.boundingRect;
|
|
638
|
+
position.boundingRect = new BoundingRect(
|
|
639
|
+
rectData.x,
|
|
640
|
+
rectData.y,
|
|
641
|
+
rectData.width,
|
|
642
|
+
rectData.height
|
|
643
|
+
);
|
|
644
|
+
}
|
|
663
645
|
|
|
664
|
-
|
|
646
|
+
return position;
|
|
647
|
+
}
|
|
665
648
|
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
}
|
|
649
|
+
// Builder Pattern Support
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* Creates a new ParagraphBuilder for fluent paragraph construction.
|
|
653
|
+
*/
|
|
654
|
+
paragraphBuilder(): ParagraphBuilder {
|
|
655
|
+
return new ParagraphBuilder(this);
|
|
656
|
+
}
|
|
657
|
+
}
|