@reekon-tools/boldr-utils 1.22.0 → 1.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/annotation/canvas/pdfCanvasPages.d.ts +25 -0
  2. package/dist/annotation/canvas/pdfCanvasPages.js +255 -0
  3. package/dist/annotation/data/AnnotationDataProvider.d.ts +6 -0
  4. package/dist/annotation/data/InMemoryAnnotationProvider.d.ts +6 -0
  5. package/dist/annotation/data/InMemoryAnnotationProvider.js +25 -0
  6. package/dist/annotation/data/hooks/usePdfCanvasDoc.d.ts +29 -0
  7. package/dist/annotation/data/hooks/usePdfCanvasDoc.js +344 -0
  8. package/dist/export/pdf/annots.d.ts +23 -0
  9. package/dist/export/pdf/annots.js +135 -0
  10. package/dist/export/pdf/decrypt.d.ts +21 -0
  11. package/dist/export/pdf/decrypt.js +121 -0
  12. package/dist/export/pdf/fonts.d.ts +3 -4
  13. package/dist/export/pdf/fonts.js +2 -14
  14. package/dist/export/pdf/index.d.ts +4 -2
  15. package/dist/export/pdf/index.js +3 -1
  16. package/dist/export/pdf/marks.d.ts +66 -5
  17. package/dist/export/pdf/marks.js +150 -7
  18. package/dist/export/pdf/pageMapping.d.ts +2 -0
  19. package/dist/export/pdf/pageMapping.js +10 -0
  20. package/dist/export/pdf/renderCanvasToPdf.d.ts +9 -1
  21. package/dist/export/pdf/renderCanvasToPdf.js +303 -120
  22. package/dist/export/pdf/resources.d.ts +35 -0
  23. package/dist/export/pdf/resources.js +95 -0
  24. package/dist/export/pdf/tiles.d.ts +5 -9
  25. package/dist/export/pdf/tiles.js +20 -20
  26. package/dist/export/pdf/types.d.ts +25 -1
  27. package/dist/exports.d.ts +3 -0
  28. package/dist/exports.js +3 -0
  29. package/dist/server.d.ts +2 -0
  30. package/dist/server.js +2 -0
  31. package/dist/types/firestore.d.ts +6 -0
  32. package/dist/types/firestore.js +5 -0
  33. package/dist/types/pdfCanvas.d.ts +38 -0
  34. package/dist/types/pdfCanvas.js +16 -0
  35. package/package.json +6 -1
@@ -10,7 +10,6 @@ export class PdfFonts {
10
10
  constructor(primary, fallback) {
11
11
  this.primary = primary;
12
12
  this.fallback = fallback;
13
- this.keys = new Map();
14
13
  }
15
14
  static async create(doc, primaryBytes) {
16
15
  doc.registerFontkit(fontkit);
@@ -52,23 +51,12 @@ export class PdfFonts {
52
51
  const p = this.pick(text);
53
52
  return p.text ? p.font.widthOfTextAtSize(p.text, size) : 0;
54
53
  }
55
- keyFor(page, font) {
56
- let keys = this.keys.get(page);
57
- if (!keys) {
58
- keys = {
59
- primary: page.node.newFontDictionary(this.primary.name, this.primary.ref),
60
- fallback: page.node.newFontDictionary(this.fallback.name, this.fallback.ref),
61
- };
62
- this.keys.set(page, keys);
63
- }
64
- return font === this.primary ? keys.primary : keys.fallback;
65
- }
66
54
  /**
67
55
  * Operators drawing one line of text in a y-DOWN space, with its baseline at
68
56
  * `yBaseline` and its left / centre / right edge at `x`. Returns [] when
69
57
  * nothing of the text is drawable.
70
58
  */
71
- textOps(page, text, size, color, x, yBaseline, options = {}) {
59
+ textOps(scope, text, size, color, x, yBaseline, options = {}) {
72
60
  const picked = this.pick(text);
73
61
  if (!picked.text || size <= 0)
74
62
  return [];
@@ -77,7 +65,7 @@ export class PdfFonts {
77
65
  const left = align === 'center' ? x - w / 2 : align === 'right' ? x - w : x;
78
66
  return withMatrix(uprightAt(left, yBaseline), drawText(picked.font.encodeText(picked.text), {
79
67
  color: parseHexColor(color).rgb,
80
- font: this.keyFor(page, picked.font),
68
+ font: scope.fontKey(picked.font),
81
69
  size,
82
70
  rotate: degrees(0),
83
71
  xSkew: degrees(0),
@@ -1,5 +1,7 @@
1
- export { renderCanvasToPdf } from './renderCanvasToPdf.js';
1
+ export { renderCanvasToPdf, renderPdfCanvasToPdf, } from './renderCanvasToPdf.js';
2
+ export { stripPdfEncryption, type StripPdfEncryptionOptions, type StripPdfEncryptionResult, } from './decrypt.js';
2
3
  export { docToPageMatrix, pageRectsOf, normalizeRotation, rotatedBoxSize, applyMatrix, type AffineMatrix, type PageRotation, type PdfBox, } from './pageMapping.js';
3
4
  export { resolveTileContent, measurementTileFillFor, FORMULA_COLUMN_TILE_FILL, PLANE_COMPUTED_TILE_FILL, type TileFileInfo, type TileResolutionContext, } from './tileContent.js';
4
5
  export { tileFootprint, type TileFootprint } from './tiles.js';
5
- export { PdfExportError, type EmbeddedImage, type PdfExportCalibration, type RenderCanvasToPdfInput, type RenderCanvasToPdfResult, type ResolvedTileContent, type MeasurementTileContent, type LiteralTileContent, type EmptyTileContent, type HeaderTileContent, type ObjectTileContent, type PlaneDimensionTileContent, type InstructionsTileContent, type SkippedItem, } from './types.js';
6
+ export { annotationCount } from './annots.js';
7
+ export { PdfExportError, type EmbeddedImage, type PdfExportCalibration, type RenderCanvasToPdfInput, type RenderPdfCanvasToPdfInput, type RenderCanvasToPdfResult, type ResolvedTileContent, type MeasurementTileContent, type LiteralTileContent, type EmptyTileContent, type HeaderTileContent, type ObjectTileContent, type PlaneDimensionTileContent, type InstructionsTileContent, type SkippedItem, } from './types.js';
@@ -1,5 +1,7 @@
1
- export { renderCanvasToPdf } from './renderCanvasToPdf.js';
1
+ export { renderCanvasToPdf, renderPdfCanvasToPdf, } from './renderCanvasToPdf.js';
2
+ export { stripPdfEncryption, } from './decrypt.js';
2
3
  export { docToPageMatrix, pageRectsOf, normalizeRotation, rotatedBoxSize, applyMatrix, } from './pageMapping.js';
3
4
  export { resolveTileContent, measurementTileFillFor, FORMULA_COLUMN_TILE_FILL, PLANE_COMPUTED_TILE_FILL, } from './tileContent.js';
4
5
  export { tileFootprint } from './tiles.js';
6
+ export { annotationCount } from './annots.js';
5
7
  export { PdfExportError, } from './types.js';
@@ -1,16 +1,73 @@
1
- import { type PDFName, type PDFOperator, type PDFPage } from 'pdf-lib';
2
- import type { AnnotationShape, AnnotationStroke, PlacedMeasurementRef } from '../../types/annotation.js';
1
+ import { type PDFOperator } from 'pdf-lib';
2
+ import type { AnnotationShape, AnnotationStroke, PlacedMeasurementRef, Vec2 } from '../../types/annotation.js';
3
3
  import { dashIntervals } from '../../annotation/canvas/strokeGeometry.js';
4
4
  import type { PdfFonts } from './fonts.js';
5
5
  import { type DocRect } from './pageMapping.js';
6
+ import type { ResourceScope } from './resources.js';
6
7
  import type { SkippedItem } from './types.js';
7
8
  export interface MarkContext {
8
- page: PDFPage;
9
+ /** Where fonts, images and opacity states get registered. */
10
+ scope: ResourceScope;
9
11
  fonts: PdfFonts;
10
- /** An ExtGState carrying `alpha` (CA/ca) for this page; undefined at 1. */
11
- alphaState: (alpha: number) => PDFName | undefined;
12
12
  skipped: SkippedItem[];
13
13
  }
14
+ /**
15
+ * What an element IS, for the annotation dictionary that wraps its
16
+ * appearance stream in annotation mode: the standard fields a viewer reads
17
+ * back (and regenerates from after an edit). Points and rects are in DOC
18
+ * space; the annotation builder maps them onto the page.
19
+ */
20
+ export type AnnotMeta = {
21
+ subtype: 'Ink';
22
+ color: string;
23
+ width: number;
24
+ alpha: number;
25
+ paths: Vec2[][];
26
+ } | {
27
+ subtype: 'Line';
28
+ color: string;
29
+ width: number;
30
+ alpha: number;
31
+ dash: boolean;
32
+ a: Vec2;
33
+ b: Vec2;
34
+ startArrow: boolean;
35
+ endArrow: boolean;
36
+ } | {
37
+ subtype: 'Square' | 'Circle';
38
+ color: string;
39
+ fill?: string;
40
+ width: number;
41
+ alpha: number;
42
+ dash: boolean;
43
+ rect: DocRect;
44
+ } | {
45
+ subtype: 'Polygon' | 'PolyLine';
46
+ color: string;
47
+ fill?: string;
48
+ width: number;
49
+ alpha: number;
50
+ dash: boolean;
51
+ vertices: Vec2[];
52
+ startArrow: boolean;
53
+ endArrow: boolean;
54
+ } | {
55
+ subtype: 'FreeText';
56
+ color: string;
57
+ fontSize: number;
58
+ text: string;
59
+ alpha: number;
60
+ } | {
61
+ subtype: 'Stamp';
62
+ contents?: string;
63
+ };
64
+ /** One drawable element: its operators, its doc-space bounds, its identity. */
65
+ export interface Mark {
66
+ id: string;
67
+ ops: PDFOperator[];
68
+ bounds: DocRect;
69
+ annot: AnnotMeta;
70
+ }
14
71
  export declare const strokeBounds: (s: AnnotationStroke) => DocRect | null;
15
72
  export declare const strokeOps: (s: AnnotationStroke, ctx: MarkContext) => PDFOperator[];
16
73
  export declare const shapeBounds: (shape: AnnotationShape) => DocRect | null;
@@ -18,3 +75,7 @@ export declare const shapeOps: (shape: AnnotationShape, ctx: MarkContext) => PDF
18
75
  export declare const measurementMarkBounds: (placed: PlacedMeasurementRef) => DocRect | null;
19
76
  export declare const measurementMarkOps: (placed: PlacedMeasurementRef, ctx: MarkContext) => PDFOperator[];
20
77
  export { dashIntervals };
78
+ export declare const strokeMark: (s: AnnotationStroke, ctx: MarkContext) => Mark | null;
79
+ export declare const shapeMark: (shape: AnnotationShape, ctx: MarkContext) => Mark | null;
80
+ /** The measurement annotation's line / rectangle (not its tile). */
81
+ export declare const measurementMark: (placed: PlacedMeasurementRef, ctx: MarkContext) => Mark | null;
@@ -66,7 +66,7 @@ export const strokeOps = (s, ctx) => {
66
66
  return [];
67
67
  const opacity = s.tool === 'highlighter' ? 0.3 : 1;
68
68
  const alpha = opacity * parseHexColor(s.color).alpha;
69
- const gs = ctx.alphaState(alpha);
69
+ const gs = ctx.scope.alphaState(alpha);
70
70
  const isDot = n === 4 && s.points[0] === s.points[2] && s.points[1] === s.points[3];
71
71
  if (isDot) {
72
72
  // Skia paints a zero-length round-capped contour as a dot; PDF only does
@@ -137,7 +137,7 @@ const textShapeOps = (shape, ctx) => {
137
137
  const colorStr = shape.style.stroke ?? '#000000';
138
138
  const decoration = shape.style.textDecoration;
139
139
  const glyphColor = decoration === 'highlight' ? highlightTextColor(colorStr) : colorStr;
140
- const gs = ctx.alphaState(parseHexColor(colorStr).alpha);
140
+ const gs = ctx.scope.alphaState(parseHexColor(colorStr).alpha);
141
141
  const lineHeight = TEXT_LINE_HEIGHT_FACTOR * fontSize;
142
142
  const ops = [];
143
143
  let lost = false;
@@ -151,7 +151,7 @@ const textShapeOps = (shape, ctx) => {
151
151
  if (decoration === 'highlight' && width > 0) {
152
152
  ops.push(pushGraphicsState(), ...fillStateOps(colorStr, gs), rectangle(origin.x, top, width, lineHeight), fill(), popGraphicsState());
153
153
  }
154
- ops.push(...ctx.fonts.textOps(ctx.page, line, fontSize, glyphColor, origin.x, baseline, {
154
+ ops.push(...ctx.fonts.textOps(ctx.scope, line, fontSize, glyphColor, origin.x, baseline, {
155
155
  graphicsState: gs,
156
156
  }));
157
157
  if ((decoration === 'underline' || decoration === 'squiggle') &&
@@ -177,7 +177,7 @@ export const shapeOps = (shape, ctx) => {
177
177
  return textShapeOps(shape, ctx);
178
178
  const strokeColor = style.stroke ?? '#000000';
179
179
  const strokeWidth = style.strokeWidth ?? 2;
180
- const gs = ctx.alphaState(parseHexColor(strokeColor).alpha);
180
+ const gs = ctx.scope.alphaState(parseHexColor(strokeColor).alpha);
181
181
  const closed = geometry.closed !== false;
182
182
  const pts = geometry.points;
183
183
  const strokeWith = (pathOps, join, cap) => [
@@ -197,7 +197,7 @@ export const shapeOps = (shape, ctx) => {
197
197
  const fillWith = (pathOps) => style.fill
198
198
  ? [
199
199
  pushGraphicsState(),
200
- ...fillStateOps(style.fill, ctx.alphaState(parseHexColor(style.fill).alpha)),
200
+ ...fillStateOps(style.fill, ctx.scope.alphaState(parseHexColor(style.fill).alpha)),
201
201
  ...pathOps,
202
202
  fill(),
203
203
  popGraphicsState(),
@@ -298,7 +298,7 @@ export const measurementMarkOps = (placed, ctx) => {
298
298
  width,
299
299
  join: 'miter',
300
300
  dash: !!placed.lineDash,
301
- graphicsState: ctx.alphaState(parseHexColor(color).alpha),
301
+ graphicsState: ctx.scope.alphaState(parseHexColor(color).alpha),
302
302
  }),
303
303
  ...rectPathOps(n.minX, n.minY, n.maxX - n.minX, n.maxY - n.minY),
304
304
  stroke(),
@@ -309,7 +309,7 @@ export const measurementMarkOps = (placed, ctx) => {
309
309
  const { a, b } = placed.line;
310
310
  const color = placed.lineColor ?? MEASUREMENT_LINE_COLOR;
311
311
  const width = placed.lineWidth ?? MEASUREMENT_LINE_WIDTH;
312
- const gs = ctx.alphaState(parseHexColor(color).alpha);
312
+ const gs = ctx.scope.alphaState(parseHexColor(color).alpha);
313
313
  const ops = [
314
314
  pushGraphicsState(),
315
315
  ...strokeStateOps({
@@ -334,3 +334,146 @@ export const measurementMarkOps = (placed, ctx) => {
334
334
  };
335
335
  // Re-exported so the renderer can size dash tests without a second import.
336
336
  export { dashIntervals };
337
+ /* ------------------------------------------------------------------ *
338
+ * Marks: ops + bounds + annotation metadata, per element
339
+ * ------------------------------------------------------------------ */
340
+ const strokePaths = (s) => {
341
+ const pts = [];
342
+ for (let i = 0; i + 1 < s.points.length; i += 2) {
343
+ pts.push({ x: s.points[i], y: s.points[i + 1] });
344
+ }
345
+ return pts;
346
+ };
347
+ export const strokeMark = (s, ctx) => {
348
+ const bounds = strokeBounds(s);
349
+ if (!bounds)
350
+ return null;
351
+ const opacity = s.tool === 'highlighter' ? 0.3 : 1;
352
+ return {
353
+ id: s.id,
354
+ ops: strokeOps(s, ctx),
355
+ bounds,
356
+ annot: {
357
+ subtype: 'Ink',
358
+ color: s.color,
359
+ width: s.width,
360
+ alpha: opacity * parseHexColor(s.color).alpha,
361
+ paths: [strokePaths(s)],
362
+ },
363
+ };
364
+ };
365
+ export const shapeMark = (shape, ctx) => {
366
+ const bounds = shapeBounds(shape);
367
+ if (!bounds)
368
+ return null;
369
+ const ops = shapeOps(shape, ctx);
370
+ const { kind, geometry, style } = shape;
371
+ const color = style.stroke ?? '#000000';
372
+ const width = style.strokeWidth ?? 2;
373
+ const alpha = parseHexColor(color).alpha;
374
+ const dash = !!style.dash;
375
+ const pts = geometry.points;
376
+ let annot;
377
+ switch (kind) {
378
+ case 'rect':
379
+ case 'ellipse': {
380
+ const b = visualShapeBounds(shape);
381
+ annot = {
382
+ subtype: kind === 'rect' ? 'Square' : 'Circle',
383
+ color,
384
+ fill: style.fill,
385
+ width,
386
+ alpha,
387
+ dash,
388
+ rect: boundsToRect(b),
389
+ };
390
+ break;
391
+ }
392
+ case 'line':
393
+ case 'arrow':
394
+ annot = {
395
+ subtype: 'Line',
396
+ color,
397
+ width,
398
+ alpha,
399
+ dash,
400
+ a: pts[0],
401
+ b: pts[1],
402
+ startArrow: style.startCap === 'arrow',
403
+ endArrow: kind === 'arrow' || style.cap === 'arrow',
404
+ };
405
+ break;
406
+ case 'polygon': {
407
+ const closed = geometry.closed !== false;
408
+ annot = {
409
+ subtype: closed ? 'Polygon' : 'PolyLine',
410
+ color,
411
+ fill: closed ? style.fill : undefined,
412
+ width,
413
+ alpha,
414
+ dash,
415
+ vertices: pts,
416
+ startArrow: !closed && style.startCap === 'arrow',
417
+ endArrow: !closed && style.cap === 'arrow',
418
+ };
419
+ break;
420
+ }
421
+ case 'text':
422
+ annot = {
423
+ subtype: 'FreeText',
424
+ color,
425
+ fontSize: style.fontSize ?? DEFAULT_TEXT_FONT_SIZE,
426
+ text: shape.text ?? '',
427
+ alpha,
428
+ };
429
+ break;
430
+ default:
431
+ return null;
432
+ }
433
+ return { id: shape.id, ops, bounds, annot };
434
+ };
435
+ /** The measurement annotation's line / rectangle (not its tile). */
436
+ export const measurementMark = (placed, ctx) => {
437
+ const bounds = measurementMarkBounds(placed);
438
+ if (!bounds)
439
+ return null;
440
+ const ops = measurementMarkOps(placed, ctx);
441
+ const color = placed.lineColor ?? MEASUREMENT_LINE_COLOR;
442
+ const alpha = parseHexColor(color).alpha;
443
+ const dash = !!placed.lineDash;
444
+ if (placementOf(placed) === 'rectangle' && placed.rect) {
445
+ const n = normalizeRect(placed.rect);
446
+ return {
447
+ id: `${placed.id}:rect`,
448
+ ops,
449
+ bounds,
450
+ annot: {
451
+ subtype: 'Square',
452
+ color,
453
+ width: placed.lineWidth ?? MEASUREMENT_RECT_WIDTH,
454
+ alpha,
455
+ dash,
456
+ rect: boundsToRect(n),
457
+ },
458
+ };
459
+ }
460
+ if (placementOf(placed) === 'line' && placed.line) {
461
+ return {
462
+ id: `${placed.id}:line`,
463
+ ops,
464
+ bounds,
465
+ annot: {
466
+ subtype: 'Line',
467
+ color,
468
+ width: placed.lineWidth ?? MEASUREMENT_LINE_WIDTH,
469
+ alpha,
470
+ dash,
471
+ a: placed.line.a,
472
+ b: placed.line.b,
473
+ startArrow: placed.lineStartCap === 'arrow',
474
+ endArrow: placed.lineCap === 'arrow',
475
+ },
476
+ };
477
+ }
478
+ return null;
479
+ };
@@ -39,6 +39,8 @@ export declare const applyMatrix: (m: AffineMatrix, x: number, y: number) => {
39
39
  x: number;
40
40
  y: number;
41
41
  };
42
+ /** The overlap of two rects, or null when they do not overlap. */
43
+ export declare const intersectRects: (a: DocRect, b: DocRect) => DocRect | null;
42
44
  export declare const rectsIntersect: (a: DocRect, b: DocRect) => boolean;
43
45
  /** Axis-aligned bounds → DocRect, inflated by `pad` on every side. */
44
46
  export declare const boundsToRect: (b: {
@@ -54,6 +54,16 @@ export const applyMatrix = (m, x, y) => ({
54
54
  x: m[0] * x + m[2] * y + m[4],
55
55
  y: m[1] * x + m[3] * y + m[5],
56
56
  });
57
+ /** The overlap of two rects, or null when they do not overlap. */
58
+ export const intersectRects = (a, b) => {
59
+ const x = Math.max(a.x, b.x);
60
+ const y = Math.max(a.y, b.y);
61
+ const right = Math.min(a.x + a.width, b.x + b.width);
62
+ const bottom = Math.min(a.y + a.height, b.y + b.height);
63
+ if (right <= x || bottom <= y)
64
+ return null;
65
+ return { x, y, width: right - x, height: bottom - y };
66
+ };
57
67
  export const rectsIntersect = (a, b) => a.x < b.x + b.width &&
58
68
  a.x + a.width > b.x &&
59
69
  a.y < b.y + b.height &&
@@ -1,2 +1,10 @@
1
- import { type RenderCanvasToPdfInput, type RenderCanvasToPdfResult } from './types.js';
1
+ import { type RenderCanvasToPdfInput, type RenderCanvasToPdfResult, type RenderPdfCanvasToPdfInput } from './types.js';
2
2
  export declare const renderCanvasToPdf: (input: RenderCanvasToPdfInput) => Promise<RenderCanvasToPdfResult>;
3
+ /**
4
+ * Export a PdfCanvas file. Each page's state is drawn in that page's own doc
5
+ * space (the DISPLAYED page: rotated extents, origin top-left). A user
6
+ * rotation is written into the page's /Rotate — so the recipient sees the
7
+ * page as the user did — and the page matrix undoes the combined rotation.
8
+ * Pages without a state are still rotated.
9
+ */
10
+ export declare const renderPdfCanvasToPdf: (input: RenderPdfCanvasToPdfInput) => Promise<RenderCanvasToPdfResult>;