@tbib-image-viewer/core 0.1.1 → 0.1.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/dist/index.d.mts CHANGED
@@ -145,9 +145,14 @@ declare class AnnotationStore {
145
145
  * at original native resolution.
146
146
  */
147
147
  declare function renderToOffscreenCanvas(imageSource: string | HTMLImageElement, strokes: AnnotationStroke[], options?: ExportOptions): Promise<HTMLCanvasElement>;
148
+ declare function renderToOffscreenCanvasWithCleanup(imageSource: string | HTMLImageElement, strokes: AnnotationStroke[], options?: ExportOptions): Promise<{
149
+ canvas: HTMLCanvasElement;
150
+ cleanup?: () => void;
151
+ }>;
148
152
  /**
149
153
  * Triggers a browser download of the image (with or without annotations).
154
+ * Guarantees zero unhandled SecurityError on tainted canvases with automatic fallback.
150
155
  */
151
156
  declare function downloadAnnotatedImage(imageSource: string | HTMLImageElement, strokes: AnnotationStroke[], options?: ExportOptions): Promise<void>;
152
157
 
153
- export { AnnotationStore, type AnnotationStroke, DEFAULT_TRANSFORM, type DrawTool, type ExportFormat, type ExportOptions, type GalleryItem, type HistoryState, type ImageDimensions, type InteractionMode, MAX_SCALE, MIN_SCALE, type NormalizedPoint, type ScreenPoint, type SlideAnnotations, type TransformState, clampScale, distanceSq, downloadAnnotatedImage, getFittedImageBounds, hexToRgba, isPointNearStroke, normalizedImageToScreen, pointsToSvgPath, renderToOffscreenCanvas, screenToNormalizedImage };
158
+ export { AnnotationStore, type AnnotationStroke, DEFAULT_TRANSFORM, type DrawTool, type ExportFormat, type ExportOptions, type GalleryItem, type HistoryState, type ImageDimensions, type InteractionMode, MAX_SCALE, MIN_SCALE, type NormalizedPoint, type ScreenPoint, type SlideAnnotations, type TransformState, clampScale, distanceSq, downloadAnnotatedImage, getFittedImageBounds, hexToRgba, isPointNearStroke, normalizedImageToScreen, pointsToSvgPath, renderToOffscreenCanvas, renderToOffscreenCanvasWithCleanup, screenToNormalizedImage };
package/dist/index.d.ts CHANGED
@@ -145,9 +145,14 @@ declare class AnnotationStore {
145
145
  * at original native resolution.
146
146
  */
147
147
  declare function renderToOffscreenCanvas(imageSource: string | HTMLImageElement, strokes: AnnotationStroke[], options?: ExportOptions): Promise<HTMLCanvasElement>;
148
+ declare function renderToOffscreenCanvasWithCleanup(imageSource: string | HTMLImageElement, strokes: AnnotationStroke[], options?: ExportOptions): Promise<{
149
+ canvas: HTMLCanvasElement;
150
+ cleanup?: () => void;
151
+ }>;
148
152
  /**
149
153
  * Triggers a browser download of the image (with or without annotations).
154
+ * Guarantees zero unhandled SecurityError on tainted canvases with automatic fallback.
150
155
  */
151
156
  declare function downloadAnnotatedImage(imageSource: string | HTMLImageElement, strokes: AnnotationStroke[], options?: ExportOptions): Promise<void>;
152
157
 
153
- export { AnnotationStore, type AnnotationStroke, DEFAULT_TRANSFORM, type DrawTool, type ExportFormat, type ExportOptions, type GalleryItem, type HistoryState, type ImageDimensions, type InteractionMode, MAX_SCALE, MIN_SCALE, type NormalizedPoint, type ScreenPoint, type SlideAnnotations, type TransformState, clampScale, distanceSq, downloadAnnotatedImage, getFittedImageBounds, hexToRgba, isPointNearStroke, normalizedImageToScreen, pointsToSvgPath, renderToOffscreenCanvas, screenToNormalizedImage };
158
+ export { AnnotationStore, type AnnotationStroke, DEFAULT_TRANSFORM, type DrawTool, type ExportFormat, type ExportOptions, type GalleryItem, type HistoryState, type ImageDimensions, type InteractionMode, MAX_SCALE, MIN_SCALE, type NormalizedPoint, type ScreenPoint, type SlideAnnotations, type TransformState, clampScale, distanceSq, downloadAnnotatedImage, getFittedImageBounds, hexToRgba, isPointNearStroke, normalizedImageToScreen, pointsToSvgPath, renderToOffscreenCanvas, renderToOffscreenCanvasWithCleanup, screenToNormalizedImage };
package/dist/index.js CHANGED
@@ -276,23 +276,62 @@ var AnnotationStore = class {
276
276
  };
277
277
 
278
278
  // src/export/canvas.ts
279
- async function renderToOffscreenCanvas(imageSource, strokes, options = {}) {
280
- const { includeAnnotations = true, backgroundColor } = options;
281
- let img;
282
- if (typeof imageSource === "string") {
283
- img = new Image();
279
+ async function loadCleanImage(imageSource) {
280
+ const src = typeof imageSource === "string" ? imageSource : imageSource.src;
281
+ try {
282
+ const res = await fetch(src, { mode: "cors" });
283
+ if (res.ok) {
284
+ const blob = await res.blob();
285
+ const blobUrl = URL.createObjectURL(blob);
286
+ const img = new Image();
287
+ await new Promise((resolve, reject) => {
288
+ img.onload = () => resolve();
289
+ img.onerror = reject;
290
+ img.src = blobUrl;
291
+ });
292
+ return {
293
+ img,
294
+ cleanup: () => URL.revokeObjectURL(blobUrl)
295
+ };
296
+ }
297
+ } catch {
298
+ }
299
+ try {
300
+ const img = new Image();
284
301
  img.crossOrigin = "anonymous";
302
+ const sep = src.includes("?") ? "&" : "?";
303
+ const corsSrc = `${src}${sep}_export=${Date.now()}`;
285
304
  await new Promise((resolve, reject) => {
286
305
  img.onload = () => resolve();
287
- img.onerror = (e) => reject(new Error(`Failed to load image from source: ${imageSource}`));
288
- img.src = imageSource;
306
+ img.onerror = reject;
307
+ img.src = corsSrc;
289
308
  });
290
- } else {
291
- img = imageSource;
309
+ return { img };
310
+ } catch {
311
+ }
312
+ if (typeof imageSource !== "string" && imageSource.naturalWidth > 0) {
313
+ return { img: imageSource };
292
314
  }
315
+ const fallbackImg = new Image();
316
+ await new Promise((resolve, reject) => {
317
+ fallbackImg.onload = () => resolve();
318
+ fallbackImg.onerror = reject;
319
+ fallbackImg.src = src;
320
+ });
321
+ return { img: fallbackImg };
322
+ }
323
+ async function renderToOffscreenCanvas(imageSource, strokes, options = {}) {
324
+ const { canvas, cleanup } = await renderToOffscreenCanvasWithCleanup(imageSource, strokes, options);
325
+ cleanup?.();
326
+ return canvas;
327
+ }
328
+ async function renderToOffscreenCanvasWithCleanup(imageSource, strokes, options = {}) {
329
+ const { includeAnnotations = true, backgroundColor } = options;
330
+ const { img, cleanup } = await loadCleanImage(imageSource);
293
331
  const naturalWidth = img.naturalWidth || img.width;
294
332
  const naturalHeight = img.naturalHeight || img.height;
295
333
  if (naturalWidth === 0 || naturalHeight === 0) {
334
+ cleanup?.();
296
335
  throw new Error("Invalid image dimensions: width or height is 0");
297
336
  }
298
337
  const canvas = document.createElement("canvas");
@@ -300,6 +339,7 @@ async function renderToOffscreenCanvas(imageSource, strokes, options = {}) {
300
339
  canvas.height = naturalHeight;
301
340
  const ctx = canvas.getContext("2d");
302
341
  if (!ctx) {
342
+ cleanup?.();
303
343
  throw new Error("Unable to obtain 2D canvas context for export");
304
344
  }
305
345
  if (backgroundColor) {
@@ -346,7 +386,7 @@ async function renderToOffscreenCanvas(imageSource, strokes, options = {}) {
346
386
  ctx.restore();
347
387
  }
348
388
  }
349
- return canvas;
389
+ return { canvas, cleanup };
350
390
  }
351
391
  async function downloadAnnotatedImage(imageSource, strokes, options = {}) {
352
392
  const {
@@ -354,38 +394,45 @@ async function downloadAnnotatedImage(imageSource, strokes, options = {}) {
354
394
  quality = 0.95,
355
395
  fileName = `tbib-annotated-image-${Date.now()}.${format === "image/jpeg" ? "jpg" : format === "image/webp" ? "webp" : "png"}`
356
396
  } = options;
357
- try {
358
- const canvas = await renderToOffscreenCanvas(imageSource, strokes, options);
359
- return new Promise((resolve, reject) => {
360
- canvas.toBlob(
361
- (blob) => {
362
- if (!blob) {
363
- reject(new Error("Failed to generate image blob from canvas"));
364
- return;
365
- }
366
- const url = URL.createObjectURL(blob);
367
- const link = document.createElement("a");
368
- link.href = url;
369
- link.download = fileName;
370
- document.body.appendChild(link);
371
- link.click();
372
- document.body.removeChild(link);
373
- setTimeout(() => URL.revokeObjectURL(url), 1e3);
374
- resolve();
375
- },
376
- format,
377
- quality
378
- );
379
- });
380
- } catch (err) {
381
- console.warn("Offscreen canvas render encountered an issue, falling back to direct asset download:", err);
382
- const src = typeof imageSource === "string" ? imageSource : imageSource.src;
397
+ const src = typeof imageSource === "string" ? imageSource : imageSource.src;
398
+ const directDownload = (downloadUrl, name) => {
383
399
  const link = document.createElement("a");
384
- link.href = src;
385
- link.download = fileName;
400
+ link.href = downloadUrl;
401
+ link.download = name;
386
402
  document.body.appendChild(link);
387
403
  link.click();
388
404
  document.body.removeChild(link);
405
+ };
406
+ try {
407
+ const { canvas, cleanup } = await renderToOffscreenCanvasWithCleanup(imageSource, strokes, options);
408
+ await new Promise((resolve) => {
409
+ try {
410
+ canvas.toBlob(
411
+ (blob) => {
412
+ cleanup?.();
413
+ if (!blob) {
414
+ directDownload(src, fileName);
415
+ resolve();
416
+ return;
417
+ }
418
+ const url = URL.createObjectURL(blob);
419
+ directDownload(url, fileName);
420
+ setTimeout(() => URL.revokeObjectURL(url), 1e3);
421
+ resolve();
422
+ },
423
+ format,
424
+ quality
425
+ );
426
+ } catch (taintedErr) {
427
+ cleanup?.();
428
+ console.warn("Canvas was tainted by cross-origin security rules, downloading direct asset:", taintedErr);
429
+ directDownload(src, fileName);
430
+ resolve();
431
+ }
432
+ });
433
+ } catch (err) {
434
+ console.warn("Offscreen canvas render encountered an issue, falling back to direct asset download:", err);
435
+ directDownload(src, fileName);
389
436
  }
390
437
  }
391
438
 
@@ -402,6 +449,7 @@ exports.isPointNearStroke = isPointNearStroke;
402
449
  exports.normalizedImageToScreen = normalizedImageToScreen;
403
450
  exports.pointsToSvgPath = pointsToSvgPath;
404
451
  exports.renderToOffscreenCanvas = renderToOffscreenCanvas;
452
+ exports.renderToOffscreenCanvasWithCleanup = renderToOffscreenCanvasWithCleanup;
405
453
  exports.screenToNormalizedImage = screenToNormalizedImage;
406
454
  //# sourceMappingURL=index.js.map
407
455
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/engine/transform.ts","../src/annotations/smooth.ts","../src/annotations/store.ts","../src/export/canvas.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { ImageDimensions, NormalizedPoint, ScreenPoint, TransformState } from \"../types\";\n\nexport const DEFAULT_TRANSFORM: TransformState = {\n scale: 1,\n translateX: 0,\n translateY: 0,\n rotate: 0,\n};\n\nexport const MIN_SCALE = 0.5;\nexport const MAX_SCALE = 8.0;\n\n/**\n * Clamps scale between min and max allowed bounds.\n */\nexport function clampScale(scale: number, min = MIN_SCALE, max = MAX_SCALE): number {\n return Math.min(Math.max(scale, min), max);\n}\n\n/**\n * Converts a screen/viewport coordinate (e.g. mouse or touch event)\n * into a normalized image coordinate (0 to 1) based on the image bounding box\n * and current scale/pan/rotation transform.\n */\nexport function screenToNormalizedImage(\n screenPt: ScreenPoint,\n containerRect: DOMRect,\n imageDimensions: ImageDimensions,\n transform: TransformState\n): NormalizedPoint | null {\n if (imageDimensions.width <= 0 || imageDimensions.height <= 0) return null;\n\n // 1. Point relative to container center\n const centerX = containerRect.left + containerRect.width / 2;\n const centerY = containerRect.top + containerRect.height / 2;\n\n let dx = screenPt.x - centerX - transform.translateX;\n let dy = screenPt.y - centerY - transform.translateY;\n\n // 2. Undo rotation\n const rad = (-transform.rotate * Math.PI) / 180;\n const cos = Math.cos(rad);\n const sin = Math.sin(rad);\n const unrotatedX = dx * cos - dy * sin;\n const unrotatedY = dx * sin + dy * cos;\n\n // 3. Undo scale\n const scaledX = unrotatedX / transform.scale;\n const scaledY = unrotatedY / transform.scale;\n\n // 4. Compute displayed image fitted bounds at scale = 1\n const aspect = imageDimensions.width / imageDimensions.height;\n const containerAspect = containerRect.width / containerRect.height;\n\n let fittedW: number;\n let fittedH: number;\n\n if (containerAspect > aspect) {\n fittedH = containerRect.height;\n fittedW = fittedH * aspect;\n } else {\n fittedW = containerRect.width;\n fittedH = fittedW / aspect;\n }\n\n // 5. Convert to normalized 0..1 coordinates relative to fitted image box\n const normX = (scaledX + fittedW / 2) / fittedW;\n const normY = (scaledY + fittedH / 2) / fittedH;\n\n return {\n x: Math.max(0, Math.min(1, normX)),\n y: Math.max(0, Math.min(1, normY)),\n };\n}\n\n/**\n * Converts a normalized image coordinate (0 to 1) to screen/pixel coordinates\n * inside the given container.\n */\nexport function normalizedImageToScreen(\n normPt: NormalizedPoint,\n containerRect: DOMRect,\n imageDimensions: ImageDimensions,\n transform: TransformState\n): ScreenPoint {\n const aspect = imageDimensions.width / imageDimensions.height;\n const containerAspect = containerRect.width / containerRect.height;\n\n let fittedW: number;\n let fittedH: number;\n\n if (containerAspect > aspect) {\n fittedH = containerRect.height;\n fittedW = fittedH * aspect;\n } else {\n fittedW = containerRect.width;\n fittedH = fittedW / aspect;\n }\n\n // Local unscaled, unrotated position relative to center\n const localX = normPt.x * fittedW - fittedW / 2;\n const localY = normPt.y * fittedH - fittedH / 2;\n\n // Apply scale\n const scaledX = localX * transform.scale;\n const scaledY = localY * transform.scale;\n\n // Apply rotation\n const rad = (transform.rotate * Math.PI) / 180;\n const cos = Math.cos(rad);\n const sin = Math.sin(rad);\n const rotatedX = scaledX * cos - scaledY * sin;\n const rotatedY = scaledX * sin + scaledY * cos;\n\n // Apply translation and container center\n const centerX = containerRect.left + containerRect.width / 2;\n const centerY = containerRect.top + containerRect.height / 2;\n\n return {\n x: centerX + transform.translateX + rotatedX,\n y: centerY + transform.translateY + rotatedY,\n };\n}\n\n/**\n * Computes fitted size of an image inside container maintaining aspect ratio.\n */\nexport function getFittedImageBounds(\n containerW: number,\n containerH: number,\n imgW: number,\n imgH: number\n): { width: number; height: number } {\n if (imgW <= 0 || imgH <= 0 || containerW <= 0 || containerH <= 0) {\n return { width: 0, height: 0 };\n }\n const aspect = imgW / imgH;\n const containerAspect = containerW / containerH;\n\n if (containerAspect > aspect) {\n const height = containerH;\n return { width: height * aspect, height };\n } else {\n const width = containerW;\n return { width, height: width / aspect };\n }\n}\n","import type { NormalizedPoint } from \"../types\";\n\n/**\n * Converts a list of normalized points into a smooth SVG path definition\n * using quadratic Bézier midpoint interpolation.\n */\nexport function pointsToSvgPath(\n points: NormalizedPoint[],\n width: number,\n height: number\n): string {\n if (points.length === 0) return \"\";\n if (points.length === 1) {\n const p = points[0];\n const px = p.x * width;\n const py = p.y * height;\n return `M ${px} ${py} L ${px + 0.1} ${py + 0.1}`;\n }\n\n const p0 = points[0];\n let d = `M ${p0.x * width} ${p0.y * height}`;\n\n for (let i = 1; i < points.length - 1; i++) {\n const current = points[i];\n const next = points[i + 1];\n\n const cx = (current.x * width + next.x * width) / 2;\n const cy = (current.y * height + next.y * height) / 2;\n\n d += ` Q ${current.x * width} ${current.y * height}, ${cx} ${cy}`;\n }\n\n const last = points[points.length - 1];\n d += ` L ${last.x * width} ${last.y * height}`;\n\n return d;\n}\n\n/**\n * Calculates squared Euclidean distance between two points in normalized coordinates.\n */\nexport function distanceSq(p1: NormalizedPoint, p2: NormalizedPoint): number {\n const dx = p1.x - p2.x;\n const dy = p1.y - p2.y;\n return dx * dx + dy * dy;\n}\n\n/**\n * Checks if a test point in normalized coordinates is close to any point in the stroke\n * within an interaction threshold (used for eraser hit detection).\n */\nexport function isPointNearStroke(\n testPt: NormalizedPoint,\n points: NormalizedPoint[],\n thresholdNorm: number\n): boolean {\n const thresholdSq = thresholdNorm * thresholdNorm;\n for (let i = 0; i < points.length; i++) {\n if (distanceSq(testPt, points[i]) <= thresholdSq) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Converts hex color (#rrggbb or #rgb) to an rgba string with alpha.\n */\nexport function hexToRgba(hex: string, alpha = 1): string {\n if (hex.startsWith(\"rgba\")) return hex;\n const clean = hex.trim().replace(\"#\", \"\");\n if (clean.length === 3) {\n const r = parseInt(clean[0] + clean[0], 16);\n const g = parseInt(clean[1] + clean[1], 16);\n const b = parseInt(clean[2] + clean[2], 16);\n return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n }\n if (clean.length === 6) {\n const r = parseInt(clean.substring(0, 2), 16);\n const g = parseInt(clean.substring(2, 4), 16);\n const b = parseInt(clean.substring(4, 6), 16);\n return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n }\n return hex;\n}\n","import type { AnnotationStroke, SlideAnnotations } from \"../types\";\n\nexport interface HistoryState {\n past: AnnotationStroke[][];\n present: AnnotationStroke[];\n future: AnnotationStroke[][];\n}\n\nexport class AnnotationStore {\n private slides = new Map<string, HistoryState>();\n private listeners = new Set<(slideId: string, strokes: AnnotationStroke[]) => void>();\n\n constructor(initialData?: SlideAnnotations[]) {\n if (initialData) {\n for (const item of initialData) {\n this.slides.set(item.slideId, {\n past: [],\n present: [...item.strokes],\n future: [],\n });\n }\n }\n }\n\n private getOrCreate(slideId: string): HistoryState {\n let state = this.slides.get(slideId);\n if (!state) {\n state = {\n past: [],\n present: [],\n future: [],\n };\n this.slides.set(slideId, state);\n }\n return state;\n }\n\n public getStrokes(slideId: string): AnnotationStroke[] {\n return this.getOrCreate(slideId).present;\n }\n\n public canUndo(slideId: string): boolean {\n return this.getOrCreate(slideId).past.length > 0;\n }\n\n public canRedo(slideId: string): boolean {\n return this.getOrCreate(slideId).future.length > 0;\n }\n\n public addStroke(slideId: string, stroke: AnnotationStroke): void {\n const state = this.getOrCreate(slideId);\n state.past.push([...state.present]);\n state.present = [...state.present, stroke];\n state.future = []; // Clear redo tree on new action\n this.notify(slideId);\n }\n\n public removeStroke(slideId: string, strokeId: string): void {\n const state = this.getOrCreate(slideId);\n const filtered = state.present.filter((s) => s.id !== strokeId);\n if (filtered.length !== state.present.length) {\n state.past.push([...state.present]);\n state.present = filtered;\n state.future = [];\n this.notify(slideId);\n }\n }\n\n public removeStrokesNear(\n slideId: string,\n testPt: { x: number; y: number },\n thresholdNorm: number,\n isPointNearStrokeFn: (pt: { x: number; y: number }, pts: { x: number; y: number }[], thresh: number) => boolean\n ): boolean {\n const state = this.getOrCreate(slideId);\n const toKeep: AnnotationStroke[] = [];\n let removedAny = false;\n\n for (const stroke of state.present) {\n if (isPointNearStrokeFn(testPt, stroke.points, thresholdNorm)) {\n removedAny = true;\n } else {\n toKeep.push(stroke);\n }\n }\n\n if (removedAny) {\n state.past.push([...state.present]);\n state.present = toKeep;\n state.future = [];\n this.notify(slideId);\n return true;\n }\n return false;\n }\n\n public clear(slideId: string): void {\n const state = this.getOrCreate(slideId);\n if (state.present.length === 0) return;\n state.past.push([...state.present]);\n state.present = [];\n state.future = [];\n this.notify(slideId);\n }\n\n public undo(slideId: string): boolean {\n const state = this.getOrCreate(slideId);\n if (state.past.length === 0) return false;\n\n const previous = state.past.pop()!;\n state.future.unshift([...state.present]);\n state.present = previous;\n this.notify(slideId);\n return true;\n }\n\n public redo(slideId: string): boolean {\n const state = this.getOrCreate(slideId);\n if (state.future.length === 0) return false;\n\n const next = state.future.shift()!;\n state.past.push([...state.present]);\n state.present = next;\n this.notify(slideId);\n return true;\n }\n\n public subscribe(listener: (slideId: string, strokes: AnnotationStroke[]) => void): () => void {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n private notify(slideId: string): void {\n const strokes = this.getStrokes(slideId);\n for (const listener of this.listeners) {\n listener(slideId, strokes);\n }\n }\n\n public exportJSON(): SlideAnnotations[] {\n const result: SlideAnnotations[] = [];\n for (const [slideId, state] of this.slides.entries()) {\n if (state.present.length > 0) {\n result.push({\n slideId,\n strokes: [...state.present],\n });\n }\n }\n return result;\n }\n\n public importJSON(data: SlideAnnotations[]): void {\n for (const item of data) {\n this.slides.set(item.slideId, {\n past: [],\n present: [...item.strokes],\n future: [],\n });\n this.notify(item.slideId);\n }\n }\n}\n","import type { AnnotationStroke, ExportOptions } from \"../types\";\nimport { hexToRgba } from \"../annotations/smooth\";\n\n/**\n * Loads an image from a URL or source element and renders it\n * along with its annotations onto an off-screen HTMLCanvasElement\n * at original native resolution.\n */\nexport async function renderToOffscreenCanvas(\n imageSource: string | HTMLImageElement,\n strokes: AnnotationStroke[],\n options: ExportOptions = {}\n): Promise<HTMLCanvasElement> {\n const { includeAnnotations = true, backgroundColor } = options;\n\n // 1. Resolve source HTMLImageElement\n let img: HTMLImageElement;\n if (typeof imageSource === \"string\") {\n img = new Image();\n img.crossOrigin = \"anonymous\";\n await new Promise<void>((resolve, reject) => {\n img.onload = () => resolve();\n img.onerror = (e) => reject(new Error(`Failed to load image from source: ${imageSource}`));\n img.src = imageSource;\n });\n } else {\n img = imageSource;\n }\n\n const naturalWidth = img.naturalWidth || img.width;\n const naturalHeight = img.naturalHeight || img.height;\n\n if (naturalWidth === 0 || naturalHeight === 0) {\n throw new Error(\"Invalid image dimensions: width or height is 0\");\n }\n\n // 2. Setup high-res canvas matching native dimensions\n const canvas = document.createElement(\"canvas\");\n canvas.width = naturalWidth;\n canvas.height = naturalHeight;\n const ctx = canvas.getContext(\"2d\");\n\n if (!ctx) {\n throw new Error(\"Unable to obtain 2D canvas context for export\");\n }\n\n // 3. Optional background color\n if (backgroundColor) {\n ctx.fillStyle = backgroundColor;\n ctx.fillRect(0, 0, naturalWidth, naturalHeight);\n }\n\n // 4. Draw base image\n ctx.drawImage(img, 0, 0, naturalWidth, naturalHeight);\n\n // 5. Burn in annotations if requested\n if (includeAnnotations && strokes.length > 0) {\n for (const stroke of strokes) {\n if (stroke.points.length === 0) continue;\n\n ctx.save();\n ctx.beginPath();\n ctx.lineCap = \"round\";\n ctx.lineJoin = \"round\";\n\n // Scale stroke width relative to the maximum dimension of the image\n // Stroke width in model is relative to fitted display (e.g. 3px on 800px)\n // We scale it proportionally so it looks identical to what user saw\n const strokeScale = Math.max(naturalWidth, naturalHeight) / 1000;\n ctx.lineWidth = Math.max(1, stroke.width * strokeScale);\n\n if (stroke.tool === \"highlighter\") {\n ctx.globalAlpha = stroke.opacity || 0.35;\n ctx.strokeStyle = hexToRgba(stroke.color, 1);\n // Multiply blend looks most natural on medical illustrations & documents\n ctx.globalCompositeOperation = \"multiply\";\n } else {\n ctx.globalAlpha = stroke.opacity || 1.0;\n ctx.strokeStyle = stroke.color;\n ctx.globalCompositeOperation = \"source-over\";\n }\n\n const p0 = stroke.points[0];\n const startX = p0.x * naturalWidth;\n const startY = p0.y * naturalHeight;\n ctx.moveTo(startX, startY);\n\n if (stroke.points.length === 1) {\n ctx.lineTo(startX + 0.5, startY + 0.5);\n } else {\n for (let i = 1; i < stroke.points.length - 1; i++) {\n const pt = stroke.points[i];\n const next = stroke.points[i + 1];\n const cx = (pt.x * naturalWidth + next.x * naturalWidth) / 2;\n const cy = (pt.y * naturalHeight + next.y * naturalHeight) / 2;\n ctx.quadraticCurveTo(pt.x * naturalWidth, pt.y * naturalHeight, cx, cy);\n }\n const last = stroke.points[stroke.points.length - 1];\n ctx.lineTo(last.x * naturalWidth, last.y * naturalHeight);\n }\n\n ctx.stroke();\n ctx.restore();\n }\n }\n\n return canvas;\n}\n\n/**\n * Triggers a browser download of the image (with or without annotations).\n */\nexport async function downloadAnnotatedImage(\n imageSource: string | HTMLImageElement,\n strokes: AnnotationStroke[],\n options: ExportOptions = {}\n): Promise<void> {\n const {\n format = \"image/png\",\n quality = 0.95,\n fileName = `tbib-annotated-image-${Date.now()}.${format === \"image/jpeg\" ? \"jpg\" : format === \"image/webp\" ? \"webp\" : \"png\"}`,\n } = options;\n\n try {\n const canvas = await renderToOffscreenCanvas(imageSource, strokes, options);\n\n return new Promise<void>((resolve, reject) => {\n canvas.toBlob(\n (blob) => {\n if (!blob) {\n reject(new Error(\"Failed to generate image blob from canvas\"));\n return;\n }\n\n const url = URL.createObjectURL(blob);\n const link = document.createElement(\"a\");\n link.href = url;\n link.download = fileName;\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n\n setTimeout(() => URL.revokeObjectURL(url), 1000);\n resolve();\n },\n format,\n quality\n );\n });\n } catch (err) {\n console.warn(\"Offscreen canvas render encountered an issue, falling back to direct asset download:\", err);\n const src = typeof imageSource === \"string\" ? imageSource : imageSource.src;\n const link = document.createElement(\"a\");\n link.href = src;\n link.download = fileName;\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/engine/transform.ts","../src/annotations/smooth.ts","../src/annotations/store.ts","../src/export/canvas.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { ImageDimensions, NormalizedPoint, ScreenPoint, TransformState } from \"../types\";\n\nexport const DEFAULT_TRANSFORM: TransformState = {\n scale: 1,\n translateX: 0,\n translateY: 0,\n rotate: 0,\n};\n\nexport const MIN_SCALE = 0.5;\nexport const MAX_SCALE = 8.0;\n\n/**\n * Clamps scale between min and max allowed bounds.\n */\nexport function clampScale(scale: number, min = MIN_SCALE, max = MAX_SCALE): number {\n return Math.min(Math.max(scale, min), max);\n}\n\n/**\n * Converts a screen/viewport coordinate (e.g. mouse or touch event)\n * into a normalized image coordinate (0 to 1) based on the image bounding box\n * and current scale/pan/rotation transform.\n */\nexport function screenToNormalizedImage(\n screenPt: ScreenPoint,\n containerRect: DOMRect,\n imageDimensions: ImageDimensions,\n transform: TransformState\n): NormalizedPoint | null {\n if (imageDimensions.width <= 0 || imageDimensions.height <= 0) return null;\n\n // 1. Point relative to container center\n const centerX = containerRect.left + containerRect.width / 2;\n const centerY = containerRect.top + containerRect.height / 2;\n\n let dx = screenPt.x - centerX - transform.translateX;\n let dy = screenPt.y - centerY - transform.translateY;\n\n // 2. Undo rotation\n const rad = (-transform.rotate * Math.PI) / 180;\n const cos = Math.cos(rad);\n const sin = Math.sin(rad);\n const unrotatedX = dx * cos - dy * sin;\n const unrotatedY = dx * sin + dy * cos;\n\n // 3. Undo scale\n const scaledX = unrotatedX / transform.scale;\n const scaledY = unrotatedY / transform.scale;\n\n // 4. Compute displayed image fitted bounds at scale = 1\n const aspect = imageDimensions.width / imageDimensions.height;\n const containerAspect = containerRect.width / containerRect.height;\n\n let fittedW: number;\n let fittedH: number;\n\n if (containerAspect > aspect) {\n fittedH = containerRect.height;\n fittedW = fittedH * aspect;\n } else {\n fittedW = containerRect.width;\n fittedH = fittedW / aspect;\n }\n\n // 5. Convert to normalized 0..1 coordinates relative to fitted image box\n const normX = (scaledX + fittedW / 2) / fittedW;\n const normY = (scaledY + fittedH / 2) / fittedH;\n\n return {\n x: Math.max(0, Math.min(1, normX)),\n y: Math.max(0, Math.min(1, normY)),\n };\n}\n\n/**\n * Converts a normalized image coordinate (0 to 1) to screen/pixel coordinates\n * inside the given container.\n */\nexport function normalizedImageToScreen(\n normPt: NormalizedPoint,\n containerRect: DOMRect,\n imageDimensions: ImageDimensions,\n transform: TransformState\n): ScreenPoint {\n const aspect = imageDimensions.width / imageDimensions.height;\n const containerAspect = containerRect.width / containerRect.height;\n\n let fittedW: number;\n let fittedH: number;\n\n if (containerAspect > aspect) {\n fittedH = containerRect.height;\n fittedW = fittedH * aspect;\n } else {\n fittedW = containerRect.width;\n fittedH = fittedW / aspect;\n }\n\n // Local unscaled, unrotated position relative to center\n const localX = normPt.x * fittedW - fittedW / 2;\n const localY = normPt.y * fittedH - fittedH / 2;\n\n // Apply scale\n const scaledX = localX * transform.scale;\n const scaledY = localY * transform.scale;\n\n // Apply rotation\n const rad = (transform.rotate * Math.PI) / 180;\n const cos = Math.cos(rad);\n const sin = Math.sin(rad);\n const rotatedX = scaledX * cos - scaledY * sin;\n const rotatedY = scaledX * sin + scaledY * cos;\n\n // Apply translation and container center\n const centerX = containerRect.left + containerRect.width / 2;\n const centerY = containerRect.top + containerRect.height / 2;\n\n return {\n x: centerX + transform.translateX + rotatedX,\n y: centerY + transform.translateY + rotatedY,\n };\n}\n\n/**\n * Computes fitted size of an image inside container maintaining aspect ratio.\n */\nexport function getFittedImageBounds(\n containerW: number,\n containerH: number,\n imgW: number,\n imgH: number\n): { width: number; height: number } {\n if (imgW <= 0 || imgH <= 0 || containerW <= 0 || containerH <= 0) {\n return { width: 0, height: 0 };\n }\n const aspect = imgW / imgH;\n const containerAspect = containerW / containerH;\n\n if (containerAspect > aspect) {\n const height = containerH;\n return { width: height * aspect, height };\n } else {\n const width = containerW;\n return { width, height: width / aspect };\n }\n}\n","import type { NormalizedPoint } from \"../types\";\n\n/**\n * Converts a list of normalized points into a smooth SVG path definition\n * using quadratic Bézier midpoint interpolation.\n */\nexport function pointsToSvgPath(\n points: NormalizedPoint[],\n width: number,\n height: number\n): string {\n if (points.length === 0) return \"\";\n if (points.length === 1) {\n const p = points[0];\n const px = p.x * width;\n const py = p.y * height;\n return `M ${px} ${py} L ${px + 0.1} ${py + 0.1}`;\n }\n\n const p0 = points[0];\n let d = `M ${p0.x * width} ${p0.y * height}`;\n\n for (let i = 1; i < points.length - 1; i++) {\n const current = points[i];\n const next = points[i + 1];\n\n const cx = (current.x * width + next.x * width) / 2;\n const cy = (current.y * height + next.y * height) / 2;\n\n d += ` Q ${current.x * width} ${current.y * height}, ${cx} ${cy}`;\n }\n\n const last = points[points.length - 1];\n d += ` L ${last.x * width} ${last.y * height}`;\n\n return d;\n}\n\n/**\n * Calculates squared Euclidean distance between two points in normalized coordinates.\n */\nexport function distanceSq(p1: NormalizedPoint, p2: NormalizedPoint): number {\n const dx = p1.x - p2.x;\n const dy = p1.y - p2.y;\n return dx * dx + dy * dy;\n}\n\n/**\n * Checks if a test point in normalized coordinates is close to any point in the stroke\n * within an interaction threshold (used for eraser hit detection).\n */\nexport function isPointNearStroke(\n testPt: NormalizedPoint,\n points: NormalizedPoint[],\n thresholdNorm: number\n): boolean {\n const thresholdSq = thresholdNorm * thresholdNorm;\n for (let i = 0; i < points.length; i++) {\n if (distanceSq(testPt, points[i]) <= thresholdSq) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Converts hex color (#rrggbb or #rgb) to an rgba string with alpha.\n */\nexport function hexToRgba(hex: string, alpha = 1): string {\n if (hex.startsWith(\"rgba\")) return hex;\n const clean = hex.trim().replace(\"#\", \"\");\n if (clean.length === 3) {\n const r = parseInt(clean[0] + clean[0], 16);\n const g = parseInt(clean[1] + clean[1], 16);\n const b = parseInt(clean[2] + clean[2], 16);\n return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n }\n if (clean.length === 6) {\n const r = parseInt(clean.substring(0, 2), 16);\n const g = parseInt(clean.substring(2, 4), 16);\n const b = parseInt(clean.substring(4, 6), 16);\n return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n }\n return hex;\n}\n","import type { AnnotationStroke, SlideAnnotations } from \"../types\";\n\nexport interface HistoryState {\n past: AnnotationStroke[][];\n present: AnnotationStroke[];\n future: AnnotationStroke[][];\n}\n\nexport class AnnotationStore {\n private slides = new Map<string, HistoryState>();\n private listeners = new Set<(slideId: string, strokes: AnnotationStroke[]) => void>();\n\n constructor(initialData?: SlideAnnotations[]) {\n if (initialData) {\n for (const item of initialData) {\n this.slides.set(item.slideId, {\n past: [],\n present: [...item.strokes],\n future: [],\n });\n }\n }\n }\n\n private getOrCreate(slideId: string): HistoryState {\n let state = this.slides.get(slideId);\n if (!state) {\n state = {\n past: [],\n present: [],\n future: [],\n };\n this.slides.set(slideId, state);\n }\n return state;\n }\n\n public getStrokes(slideId: string): AnnotationStroke[] {\n return this.getOrCreate(slideId).present;\n }\n\n public canUndo(slideId: string): boolean {\n return this.getOrCreate(slideId).past.length > 0;\n }\n\n public canRedo(slideId: string): boolean {\n return this.getOrCreate(slideId).future.length > 0;\n }\n\n public addStroke(slideId: string, stroke: AnnotationStroke): void {\n const state = this.getOrCreate(slideId);\n state.past.push([...state.present]);\n state.present = [...state.present, stroke];\n state.future = []; // Clear redo tree on new action\n this.notify(slideId);\n }\n\n public removeStroke(slideId: string, strokeId: string): void {\n const state = this.getOrCreate(slideId);\n const filtered = state.present.filter((s) => s.id !== strokeId);\n if (filtered.length !== state.present.length) {\n state.past.push([...state.present]);\n state.present = filtered;\n state.future = [];\n this.notify(slideId);\n }\n }\n\n public removeStrokesNear(\n slideId: string,\n testPt: { x: number; y: number },\n thresholdNorm: number,\n isPointNearStrokeFn: (pt: { x: number; y: number }, pts: { x: number; y: number }[], thresh: number) => boolean\n ): boolean {\n const state = this.getOrCreate(slideId);\n const toKeep: AnnotationStroke[] = [];\n let removedAny = false;\n\n for (const stroke of state.present) {\n if (isPointNearStrokeFn(testPt, stroke.points, thresholdNorm)) {\n removedAny = true;\n } else {\n toKeep.push(stroke);\n }\n }\n\n if (removedAny) {\n state.past.push([...state.present]);\n state.present = toKeep;\n state.future = [];\n this.notify(slideId);\n return true;\n }\n return false;\n }\n\n public clear(slideId: string): void {\n const state = this.getOrCreate(slideId);\n if (state.present.length === 0) return;\n state.past.push([...state.present]);\n state.present = [];\n state.future = [];\n this.notify(slideId);\n }\n\n public undo(slideId: string): boolean {\n const state = this.getOrCreate(slideId);\n if (state.past.length === 0) return false;\n\n const previous = state.past.pop()!;\n state.future.unshift([...state.present]);\n state.present = previous;\n this.notify(slideId);\n return true;\n }\n\n public redo(slideId: string): boolean {\n const state = this.getOrCreate(slideId);\n if (state.future.length === 0) return false;\n\n const next = state.future.shift()!;\n state.past.push([...state.present]);\n state.present = next;\n this.notify(slideId);\n return true;\n }\n\n public subscribe(listener: (slideId: string, strokes: AnnotationStroke[]) => void): () => void {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n private notify(slideId: string): void {\n const strokes = this.getStrokes(slideId);\n for (const listener of this.listeners) {\n listener(slideId, strokes);\n }\n }\n\n public exportJSON(): SlideAnnotations[] {\n const result: SlideAnnotations[] = [];\n for (const [slideId, state] of this.slides.entries()) {\n if (state.present.length > 0) {\n result.push({\n slideId,\n strokes: [...state.present],\n });\n }\n }\n return result;\n }\n\n public importJSON(data: SlideAnnotations[]): void {\n for (const item of data) {\n this.slides.set(item.slideId, {\n past: [],\n present: [...item.strokes],\n future: [],\n });\n this.notify(item.slideId);\n }\n }\n}\n","import type { AnnotationStroke, ExportOptions } from \"../types\";\nimport { hexToRgba } from \"../annotations/smooth\";\n\n/**\n * Loads an image from a source URL or HTMLImageElement cleanly without\n * tainting the canvas, using fetch->Blob or crossOrigin cache-busting.\n */\nasync function loadCleanImage(\n imageSource: string | HTMLImageElement\n): Promise<{ img: HTMLImageElement; cleanup?: () => void }> {\n const src = typeof imageSource === \"string\" ? imageSource : imageSource.src;\n\n // Strategy 1: Fetch as Blob URL (Guarantees same-origin blob: URL, zero canvas tainting)\n try {\n const res = await fetch(src, { mode: \"cors\" });\n if (res.ok) {\n const blob = await res.blob();\n const blobUrl = URL.createObjectURL(blob);\n const img = new Image();\n await new Promise<void>((resolve, reject) => {\n img.onload = () => resolve();\n img.onerror = reject;\n img.src = blobUrl;\n });\n return {\n img,\n cleanup: () => URL.revokeObjectURL(blobUrl),\n };\n }\n } catch {\n // Strategy 1 failed (e.g. strict origin restrictions on fetch)\n }\n\n // Strategy 2: In-memory Image with crossOrigin = \"anonymous\" and cache buster\n try {\n const img = new Image();\n img.crossOrigin = \"anonymous\";\n const sep = src.includes(\"?\") ? \"&\" : \"?\";\n const corsSrc = `${src}${sep}_export=${Date.now()}`;\n await new Promise<void>((resolve, reject) => {\n img.onload = () => resolve();\n img.onerror = reject;\n img.src = corsSrc;\n });\n return { img };\n } catch {\n // Strategy 2 failed\n }\n\n // Strategy 3: Fall back to existing image element or non-CORS element\n if (typeof imageSource !== \"string\" && imageSource.naturalWidth > 0) {\n return { img: imageSource };\n }\n\n const fallbackImg = new Image();\n await new Promise<void>((resolve, reject) => {\n fallbackImg.onload = () => resolve();\n fallbackImg.onerror = reject;\n fallbackImg.src = src;\n });\n return { img: fallbackImg };\n}\n\n/**\n * Loads an image from a URL or source element and renders it\n * along with its annotations onto an off-screen HTMLCanvasElement\n * at original native resolution.\n */\nexport async function renderToOffscreenCanvas(\n imageSource: string | HTMLImageElement,\n strokes: AnnotationStroke[],\n options: ExportOptions = {}\n): Promise<HTMLCanvasElement> {\n const { canvas, cleanup } = await renderToOffscreenCanvasWithCleanup(imageSource, strokes, options);\n cleanup?.();\n return canvas;\n}\n\nexport async function renderToOffscreenCanvasWithCleanup(\n imageSource: string | HTMLImageElement,\n strokes: AnnotationStroke[],\n options: ExportOptions = {}\n): Promise<{ canvas: HTMLCanvasElement; cleanup?: () => void }> {\n const { includeAnnotations = true, backgroundColor } = options;\n\n // 1. Resolve clean HTMLImageElement\n const { img, cleanup } = await loadCleanImage(imageSource);\n\n const naturalWidth = img.naturalWidth || img.width;\n const naturalHeight = img.naturalHeight || img.height;\n\n if (naturalWidth === 0 || naturalHeight === 0) {\n cleanup?.();\n throw new Error(\"Invalid image dimensions: width or height is 0\");\n }\n\n // 2. Setup high-res canvas matching native dimensions\n const canvas = document.createElement(\"canvas\");\n canvas.width = naturalWidth;\n canvas.height = naturalHeight;\n const ctx = canvas.getContext(\"2d\");\n\n if (!ctx) {\n cleanup?.();\n throw new Error(\"Unable to obtain 2D canvas context for export\");\n }\n\n // 3. Optional background color\n if (backgroundColor) {\n ctx.fillStyle = backgroundColor;\n ctx.fillRect(0, 0, naturalWidth, naturalHeight);\n }\n\n // 4. Draw base image\n ctx.drawImage(img, 0, 0, naturalWidth, naturalHeight);\n\n // 5. Burn in annotations if requested\n if (includeAnnotations && strokes.length > 0) {\n for (const stroke of strokes) {\n if (stroke.points.length === 0) continue;\n\n ctx.save();\n ctx.beginPath();\n ctx.lineCap = \"round\";\n ctx.lineJoin = \"round\";\n\n // Scale stroke width relative to the maximum dimension of the image\n const strokeScale = Math.max(naturalWidth, naturalHeight) / 1000;\n ctx.lineWidth = Math.max(1, stroke.width * strokeScale);\n\n if (stroke.tool === \"highlighter\") {\n ctx.globalAlpha = stroke.opacity || 0.35;\n ctx.strokeStyle = hexToRgba(stroke.color, 1);\n ctx.globalCompositeOperation = \"multiply\";\n } else {\n ctx.globalAlpha = stroke.opacity || 1.0;\n ctx.strokeStyle = stroke.color;\n ctx.globalCompositeOperation = \"source-over\";\n }\n\n const p0 = stroke.points[0];\n const startX = p0.x * naturalWidth;\n const startY = p0.y * naturalHeight;\n ctx.moveTo(startX, startY);\n\n if (stroke.points.length === 1) {\n ctx.lineTo(startX + 0.5, startY + 0.5);\n } else {\n for (let i = 1; i < stroke.points.length - 1; i++) {\n const pt = stroke.points[i];\n const next = stroke.points[i + 1];\n const cx = (pt.x * naturalWidth + next.x * naturalWidth) / 2;\n const cy = (pt.y * naturalHeight + next.y * naturalHeight) / 2;\n ctx.quadraticCurveTo(pt.x * naturalWidth, pt.y * naturalHeight, cx, cy);\n }\n const last = stroke.points[stroke.points.length - 1];\n ctx.lineTo(last.x * naturalWidth, last.y * naturalHeight);\n }\n\n ctx.stroke();\n ctx.restore();\n }\n }\n\n return { canvas, cleanup };\n}\n\n/**\n * Triggers a browser download of the image (with or without annotations).\n * Guarantees zero unhandled SecurityError on tainted canvases with automatic fallback.\n */\nexport async function downloadAnnotatedImage(\n imageSource: string | HTMLImageElement,\n strokes: AnnotationStroke[],\n options: ExportOptions = {}\n): Promise<void> {\n const {\n format = \"image/png\",\n quality = 0.95,\n fileName = `tbib-annotated-image-${Date.now()}.${format === \"image/jpeg\" ? \"jpg\" : format === \"image/webp\" ? \"webp\" : \"png\"}`,\n } = options;\n\n const src = typeof imageSource === \"string\" ? imageSource : imageSource.src;\n\n const directDownload = (downloadUrl: string, name: string) => {\n const link = document.createElement(\"a\");\n link.href = downloadUrl;\n link.download = name;\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n };\n\n try {\n const { canvas, cleanup } = await renderToOffscreenCanvasWithCleanup(imageSource, strokes, options);\n\n await new Promise<void>((resolve) => {\n try {\n canvas.toBlob(\n (blob) => {\n cleanup?.();\n if (!blob) {\n directDownload(src, fileName);\n resolve();\n return;\n }\n\n const url = URL.createObjectURL(blob);\n directDownload(url, fileName);\n setTimeout(() => URL.revokeObjectURL(url), 1000);\n resolve();\n },\n format,\n quality\n );\n } catch (taintedErr) {\n cleanup?.();\n console.warn(\"Canvas was tainted by cross-origin security rules, downloading direct asset:\", taintedErr);\n directDownload(src, fileName);\n resolve();\n }\n });\n } catch (err) {\n console.warn(\"Offscreen canvas render encountered an issue, falling back to direct asset download:\", err);\n directDownload(src, fileName);\n }\n}\n"]}
package/dist/index.mjs CHANGED
@@ -274,23 +274,62 @@ var AnnotationStore = class {
274
274
  };
275
275
 
276
276
  // src/export/canvas.ts
277
- async function renderToOffscreenCanvas(imageSource, strokes, options = {}) {
278
- const { includeAnnotations = true, backgroundColor } = options;
279
- let img;
280
- if (typeof imageSource === "string") {
281
- img = new Image();
277
+ async function loadCleanImage(imageSource) {
278
+ const src = typeof imageSource === "string" ? imageSource : imageSource.src;
279
+ try {
280
+ const res = await fetch(src, { mode: "cors" });
281
+ if (res.ok) {
282
+ const blob = await res.blob();
283
+ const blobUrl = URL.createObjectURL(blob);
284
+ const img = new Image();
285
+ await new Promise((resolve, reject) => {
286
+ img.onload = () => resolve();
287
+ img.onerror = reject;
288
+ img.src = blobUrl;
289
+ });
290
+ return {
291
+ img,
292
+ cleanup: () => URL.revokeObjectURL(blobUrl)
293
+ };
294
+ }
295
+ } catch {
296
+ }
297
+ try {
298
+ const img = new Image();
282
299
  img.crossOrigin = "anonymous";
300
+ const sep = src.includes("?") ? "&" : "?";
301
+ const corsSrc = `${src}${sep}_export=${Date.now()}`;
283
302
  await new Promise((resolve, reject) => {
284
303
  img.onload = () => resolve();
285
- img.onerror = (e) => reject(new Error(`Failed to load image from source: ${imageSource}`));
286
- img.src = imageSource;
304
+ img.onerror = reject;
305
+ img.src = corsSrc;
287
306
  });
288
- } else {
289
- img = imageSource;
307
+ return { img };
308
+ } catch {
309
+ }
310
+ if (typeof imageSource !== "string" && imageSource.naturalWidth > 0) {
311
+ return { img: imageSource };
290
312
  }
313
+ const fallbackImg = new Image();
314
+ await new Promise((resolve, reject) => {
315
+ fallbackImg.onload = () => resolve();
316
+ fallbackImg.onerror = reject;
317
+ fallbackImg.src = src;
318
+ });
319
+ return { img: fallbackImg };
320
+ }
321
+ async function renderToOffscreenCanvas(imageSource, strokes, options = {}) {
322
+ const { canvas, cleanup } = await renderToOffscreenCanvasWithCleanup(imageSource, strokes, options);
323
+ cleanup?.();
324
+ return canvas;
325
+ }
326
+ async function renderToOffscreenCanvasWithCleanup(imageSource, strokes, options = {}) {
327
+ const { includeAnnotations = true, backgroundColor } = options;
328
+ const { img, cleanup } = await loadCleanImage(imageSource);
291
329
  const naturalWidth = img.naturalWidth || img.width;
292
330
  const naturalHeight = img.naturalHeight || img.height;
293
331
  if (naturalWidth === 0 || naturalHeight === 0) {
332
+ cleanup?.();
294
333
  throw new Error("Invalid image dimensions: width or height is 0");
295
334
  }
296
335
  const canvas = document.createElement("canvas");
@@ -298,6 +337,7 @@ async function renderToOffscreenCanvas(imageSource, strokes, options = {}) {
298
337
  canvas.height = naturalHeight;
299
338
  const ctx = canvas.getContext("2d");
300
339
  if (!ctx) {
340
+ cleanup?.();
301
341
  throw new Error("Unable to obtain 2D canvas context for export");
302
342
  }
303
343
  if (backgroundColor) {
@@ -344,7 +384,7 @@ async function renderToOffscreenCanvas(imageSource, strokes, options = {}) {
344
384
  ctx.restore();
345
385
  }
346
386
  }
347
- return canvas;
387
+ return { canvas, cleanup };
348
388
  }
349
389
  async function downloadAnnotatedImage(imageSource, strokes, options = {}) {
350
390
  const {
@@ -352,41 +392,48 @@ async function downloadAnnotatedImage(imageSource, strokes, options = {}) {
352
392
  quality = 0.95,
353
393
  fileName = `tbib-annotated-image-${Date.now()}.${format === "image/jpeg" ? "jpg" : format === "image/webp" ? "webp" : "png"}`
354
394
  } = options;
355
- try {
356
- const canvas = await renderToOffscreenCanvas(imageSource, strokes, options);
357
- return new Promise((resolve, reject) => {
358
- canvas.toBlob(
359
- (blob) => {
360
- if (!blob) {
361
- reject(new Error("Failed to generate image blob from canvas"));
362
- return;
363
- }
364
- const url = URL.createObjectURL(blob);
365
- const link = document.createElement("a");
366
- link.href = url;
367
- link.download = fileName;
368
- document.body.appendChild(link);
369
- link.click();
370
- document.body.removeChild(link);
371
- setTimeout(() => URL.revokeObjectURL(url), 1e3);
372
- resolve();
373
- },
374
- format,
375
- quality
376
- );
377
- });
378
- } catch (err) {
379
- console.warn("Offscreen canvas render encountered an issue, falling back to direct asset download:", err);
380
- const src = typeof imageSource === "string" ? imageSource : imageSource.src;
395
+ const src = typeof imageSource === "string" ? imageSource : imageSource.src;
396
+ const directDownload = (downloadUrl, name) => {
381
397
  const link = document.createElement("a");
382
- link.href = src;
383
- link.download = fileName;
398
+ link.href = downloadUrl;
399
+ link.download = name;
384
400
  document.body.appendChild(link);
385
401
  link.click();
386
402
  document.body.removeChild(link);
403
+ };
404
+ try {
405
+ const { canvas, cleanup } = await renderToOffscreenCanvasWithCleanup(imageSource, strokes, options);
406
+ await new Promise((resolve) => {
407
+ try {
408
+ canvas.toBlob(
409
+ (blob) => {
410
+ cleanup?.();
411
+ if (!blob) {
412
+ directDownload(src, fileName);
413
+ resolve();
414
+ return;
415
+ }
416
+ const url = URL.createObjectURL(blob);
417
+ directDownload(url, fileName);
418
+ setTimeout(() => URL.revokeObjectURL(url), 1e3);
419
+ resolve();
420
+ },
421
+ format,
422
+ quality
423
+ );
424
+ } catch (taintedErr) {
425
+ cleanup?.();
426
+ console.warn("Canvas was tainted by cross-origin security rules, downloading direct asset:", taintedErr);
427
+ directDownload(src, fileName);
428
+ resolve();
429
+ }
430
+ });
431
+ } catch (err) {
432
+ console.warn("Offscreen canvas render encountered an issue, falling back to direct asset download:", err);
433
+ directDownload(src, fileName);
387
434
  }
388
435
  }
389
436
 
390
- export { AnnotationStore, DEFAULT_TRANSFORM, MAX_SCALE, MIN_SCALE, clampScale, distanceSq, downloadAnnotatedImage, getFittedImageBounds, hexToRgba, isPointNearStroke, normalizedImageToScreen, pointsToSvgPath, renderToOffscreenCanvas, screenToNormalizedImage };
437
+ export { AnnotationStore, DEFAULT_TRANSFORM, MAX_SCALE, MIN_SCALE, clampScale, distanceSq, downloadAnnotatedImage, getFittedImageBounds, hexToRgba, isPointNearStroke, normalizedImageToScreen, pointsToSvgPath, renderToOffscreenCanvas, renderToOffscreenCanvasWithCleanup, screenToNormalizedImage };
391
438
  //# sourceMappingURL=index.mjs.map
392
439
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/engine/transform.ts","../src/annotations/smooth.ts","../src/annotations/store.ts","../src/export/canvas.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["import type { ImageDimensions, NormalizedPoint, ScreenPoint, TransformState } from \"../types\";\n\nexport const DEFAULT_TRANSFORM: TransformState = {\n scale: 1,\n translateX: 0,\n translateY: 0,\n rotate: 0,\n};\n\nexport const MIN_SCALE = 0.5;\nexport const MAX_SCALE = 8.0;\n\n/**\n * Clamps scale between min and max allowed bounds.\n */\nexport function clampScale(scale: number, min = MIN_SCALE, max = MAX_SCALE): number {\n return Math.min(Math.max(scale, min), max);\n}\n\n/**\n * Converts a screen/viewport coordinate (e.g. mouse or touch event)\n * into a normalized image coordinate (0 to 1) based on the image bounding box\n * and current scale/pan/rotation transform.\n */\nexport function screenToNormalizedImage(\n screenPt: ScreenPoint,\n containerRect: DOMRect,\n imageDimensions: ImageDimensions,\n transform: TransformState\n): NormalizedPoint | null {\n if (imageDimensions.width <= 0 || imageDimensions.height <= 0) return null;\n\n // 1. Point relative to container center\n const centerX = containerRect.left + containerRect.width / 2;\n const centerY = containerRect.top + containerRect.height / 2;\n\n let dx = screenPt.x - centerX - transform.translateX;\n let dy = screenPt.y - centerY - transform.translateY;\n\n // 2. Undo rotation\n const rad = (-transform.rotate * Math.PI) / 180;\n const cos = Math.cos(rad);\n const sin = Math.sin(rad);\n const unrotatedX = dx * cos - dy * sin;\n const unrotatedY = dx * sin + dy * cos;\n\n // 3. Undo scale\n const scaledX = unrotatedX / transform.scale;\n const scaledY = unrotatedY / transform.scale;\n\n // 4. Compute displayed image fitted bounds at scale = 1\n const aspect = imageDimensions.width / imageDimensions.height;\n const containerAspect = containerRect.width / containerRect.height;\n\n let fittedW: number;\n let fittedH: number;\n\n if (containerAspect > aspect) {\n fittedH = containerRect.height;\n fittedW = fittedH * aspect;\n } else {\n fittedW = containerRect.width;\n fittedH = fittedW / aspect;\n }\n\n // 5. Convert to normalized 0..1 coordinates relative to fitted image box\n const normX = (scaledX + fittedW / 2) / fittedW;\n const normY = (scaledY + fittedH / 2) / fittedH;\n\n return {\n x: Math.max(0, Math.min(1, normX)),\n y: Math.max(0, Math.min(1, normY)),\n };\n}\n\n/**\n * Converts a normalized image coordinate (0 to 1) to screen/pixel coordinates\n * inside the given container.\n */\nexport function normalizedImageToScreen(\n normPt: NormalizedPoint,\n containerRect: DOMRect,\n imageDimensions: ImageDimensions,\n transform: TransformState\n): ScreenPoint {\n const aspect = imageDimensions.width / imageDimensions.height;\n const containerAspect = containerRect.width / containerRect.height;\n\n let fittedW: number;\n let fittedH: number;\n\n if (containerAspect > aspect) {\n fittedH = containerRect.height;\n fittedW = fittedH * aspect;\n } else {\n fittedW = containerRect.width;\n fittedH = fittedW / aspect;\n }\n\n // Local unscaled, unrotated position relative to center\n const localX = normPt.x * fittedW - fittedW / 2;\n const localY = normPt.y * fittedH - fittedH / 2;\n\n // Apply scale\n const scaledX = localX * transform.scale;\n const scaledY = localY * transform.scale;\n\n // Apply rotation\n const rad = (transform.rotate * Math.PI) / 180;\n const cos = Math.cos(rad);\n const sin = Math.sin(rad);\n const rotatedX = scaledX * cos - scaledY * sin;\n const rotatedY = scaledX * sin + scaledY * cos;\n\n // Apply translation and container center\n const centerX = containerRect.left + containerRect.width / 2;\n const centerY = containerRect.top + containerRect.height / 2;\n\n return {\n x: centerX + transform.translateX + rotatedX,\n y: centerY + transform.translateY + rotatedY,\n };\n}\n\n/**\n * Computes fitted size of an image inside container maintaining aspect ratio.\n */\nexport function getFittedImageBounds(\n containerW: number,\n containerH: number,\n imgW: number,\n imgH: number\n): { width: number; height: number } {\n if (imgW <= 0 || imgH <= 0 || containerW <= 0 || containerH <= 0) {\n return { width: 0, height: 0 };\n }\n const aspect = imgW / imgH;\n const containerAspect = containerW / containerH;\n\n if (containerAspect > aspect) {\n const height = containerH;\n return { width: height * aspect, height };\n } else {\n const width = containerW;\n return { width, height: width / aspect };\n }\n}\n","import type { NormalizedPoint } from \"../types\";\n\n/**\n * Converts a list of normalized points into a smooth SVG path definition\n * using quadratic Bézier midpoint interpolation.\n */\nexport function pointsToSvgPath(\n points: NormalizedPoint[],\n width: number,\n height: number\n): string {\n if (points.length === 0) return \"\";\n if (points.length === 1) {\n const p = points[0];\n const px = p.x * width;\n const py = p.y * height;\n return `M ${px} ${py} L ${px + 0.1} ${py + 0.1}`;\n }\n\n const p0 = points[0];\n let d = `M ${p0.x * width} ${p0.y * height}`;\n\n for (let i = 1; i < points.length - 1; i++) {\n const current = points[i];\n const next = points[i + 1];\n\n const cx = (current.x * width + next.x * width) / 2;\n const cy = (current.y * height + next.y * height) / 2;\n\n d += ` Q ${current.x * width} ${current.y * height}, ${cx} ${cy}`;\n }\n\n const last = points[points.length - 1];\n d += ` L ${last.x * width} ${last.y * height}`;\n\n return d;\n}\n\n/**\n * Calculates squared Euclidean distance between two points in normalized coordinates.\n */\nexport function distanceSq(p1: NormalizedPoint, p2: NormalizedPoint): number {\n const dx = p1.x - p2.x;\n const dy = p1.y - p2.y;\n return dx * dx + dy * dy;\n}\n\n/**\n * Checks if a test point in normalized coordinates is close to any point in the stroke\n * within an interaction threshold (used for eraser hit detection).\n */\nexport function isPointNearStroke(\n testPt: NormalizedPoint,\n points: NormalizedPoint[],\n thresholdNorm: number\n): boolean {\n const thresholdSq = thresholdNorm * thresholdNorm;\n for (let i = 0; i < points.length; i++) {\n if (distanceSq(testPt, points[i]) <= thresholdSq) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Converts hex color (#rrggbb or #rgb) to an rgba string with alpha.\n */\nexport function hexToRgba(hex: string, alpha = 1): string {\n if (hex.startsWith(\"rgba\")) return hex;\n const clean = hex.trim().replace(\"#\", \"\");\n if (clean.length === 3) {\n const r = parseInt(clean[0] + clean[0], 16);\n const g = parseInt(clean[1] + clean[1], 16);\n const b = parseInt(clean[2] + clean[2], 16);\n return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n }\n if (clean.length === 6) {\n const r = parseInt(clean.substring(0, 2), 16);\n const g = parseInt(clean.substring(2, 4), 16);\n const b = parseInt(clean.substring(4, 6), 16);\n return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n }\n return hex;\n}\n","import type { AnnotationStroke, SlideAnnotations } from \"../types\";\n\nexport interface HistoryState {\n past: AnnotationStroke[][];\n present: AnnotationStroke[];\n future: AnnotationStroke[][];\n}\n\nexport class AnnotationStore {\n private slides = new Map<string, HistoryState>();\n private listeners = new Set<(slideId: string, strokes: AnnotationStroke[]) => void>();\n\n constructor(initialData?: SlideAnnotations[]) {\n if (initialData) {\n for (const item of initialData) {\n this.slides.set(item.slideId, {\n past: [],\n present: [...item.strokes],\n future: [],\n });\n }\n }\n }\n\n private getOrCreate(slideId: string): HistoryState {\n let state = this.slides.get(slideId);\n if (!state) {\n state = {\n past: [],\n present: [],\n future: [],\n };\n this.slides.set(slideId, state);\n }\n return state;\n }\n\n public getStrokes(slideId: string): AnnotationStroke[] {\n return this.getOrCreate(slideId).present;\n }\n\n public canUndo(slideId: string): boolean {\n return this.getOrCreate(slideId).past.length > 0;\n }\n\n public canRedo(slideId: string): boolean {\n return this.getOrCreate(slideId).future.length > 0;\n }\n\n public addStroke(slideId: string, stroke: AnnotationStroke): void {\n const state = this.getOrCreate(slideId);\n state.past.push([...state.present]);\n state.present = [...state.present, stroke];\n state.future = []; // Clear redo tree on new action\n this.notify(slideId);\n }\n\n public removeStroke(slideId: string, strokeId: string): void {\n const state = this.getOrCreate(slideId);\n const filtered = state.present.filter((s) => s.id !== strokeId);\n if (filtered.length !== state.present.length) {\n state.past.push([...state.present]);\n state.present = filtered;\n state.future = [];\n this.notify(slideId);\n }\n }\n\n public removeStrokesNear(\n slideId: string,\n testPt: { x: number; y: number },\n thresholdNorm: number,\n isPointNearStrokeFn: (pt: { x: number; y: number }, pts: { x: number; y: number }[], thresh: number) => boolean\n ): boolean {\n const state = this.getOrCreate(slideId);\n const toKeep: AnnotationStroke[] = [];\n let removedAny = false;\n\n for (const stroke of state.present) {\n if (isPointNearStrokeFn(testPt, stroke.points, thresholdNorm)) {\n removedAny = true;\n } else {\n toKeep.push(stroke);\n }\n }\n\n if (removedAny) {\n state.past.push([...state.present]);\n state.present = toKeep;\n state.future = [];\n this.notify(slideId);\n return true;\n }\n return false;\n }\n\n public clear(slideId: string): void {\n const state = this.getOrCreate(slideId);\n if (state.present.length === 0) return;\n state.past.push([...state.present]);\n state.present = [];\n state.future = [];\n this.notify(slideId);\n }\n\n public undo(slideId: string): boolean {\n const state = this.getOrCreate(slideId);\n if (state.past.length === 0) return false;\n\n const previous = state.past.pop()!;\n state.future.unshift([...state.present]);\n state.present = previous;\n this.notify(slideId);\n return true;\n }\n\n public redo(slideId: string): boolean {\n const state = this.getOrCreate(slideId);\n if (state.future.length === 0) return false;\n\n const next = state.future.shift()!;\n state.past.push([...state.present]);\n state.present = next;\n this.notify(slideId);\n return true;\n }\n\n public subscribe(listener: (slideId: string, strokes: AnnotationStroke[]) => void): () => void {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n private notify(slideId: string): void {\n const strokes = this.getStrokes(slideId);\n for (const listener of this.listeners) {\n listener(slideId, strokes);\n }\n }\n\n public exportJSON(): SlideAnnotations[] {\n const result: SlideAnnotations[] = [];\n for (const [slideId, state] of this.slides.entries()) {\n if (state.present.length > 0) {\n result.push({\n slideId,\n strokes: [...state.present],\n });\n }\n }\n return result;\n }\n\n public importJSON(data: SlideAnnotations[]): void {\n for (const item of data) {\n this.slides.set(item.slideId, {\n past: [],\n present: [...item.strokes],\n future: [],\n });\n this.notify(item.slideId);\n }\n }\n}\n","import type { AnnotationStroke, ExportOptions } from \"../types\";\nimport { hexToRgba } from \"../annotations/smooth\";\n\n/**\n * Loads an image from a URL or source element and renders it\n * along with its annotations onto an off-screen HTMLCanvasElement\n * at original native resolution.\n */\nexport async function renderToOffscreenCanvas(\n imageSource: string | HTMLImageElement,\n strokes: AnnotationStroke[],\n options: ExportOptions = {}\n): Promise<HTMLCanvasElement> {\n const { includeAnnotations = true, backgroundColor } = options;\n\n // 1. Resolve source HTMLImageElement\n let img: HTMLImageElement;\n if (typeof imageSource === \"string\") {\n img = new Image();\n img.crossOrigin = \"anonymous\";\n await new Promise<void>((resolve, reject) => {\n img.onload = () => resolve();\n img.onerror = (e) => reject(new Error(`Failed to load image from source: ${imageSource}`));\n img.src = imageSource;\n });\n } else {\n img = imageSource;\n }\n\n const naturalWidth = img.naturalWidth || img.width;\n const naturalHeight = img.naturalHeight || img.height;\n\n if (naturalWidth === 0 || naturalHeight === 0) {\n throw new Error(\"Invalid image dimensions: width or height is 0\");\n }\n\n // 2. Setup high-res canvas matching native dimensions\n const canvas = document.createElement(\"canvas\");\n canvas.width = naturalWidth;\n canvas.height = naturalHeight;\n const ctx = canvas.getContext(\"2d\");\n\n if (!ctx) {\n throw new Error(\"Unable to obtain 2D canvas context for export\");\n }\n\n // 3. Optional background color\n if (backgroundColor) {\n ctx.fillStyle = backgroundColor;\n ctx.fillRect(0, 0, naturalWidth, naturalHeight);\n }\n\n // 4. Draw base image\n ctx.drawImage(img, 0, 0, naturalWidth, naturalHeight);\n\n // 5. Burn in annotations if requested\n if (includeAnnotations && strokes.length > 0) {\n for (const stroke of strokes) {\n if (stroke.points.length === 0) continue;\n\n ctx.save();\n ctx.beginPath();\n ctx.lineCap = \"round\";\n ctx.lineJoin = \"round\";\n\n // Scale stroke width relative to the maximum dimension of the image\n // Stroke width in model is relative to fitted display (e.g. 3px on 800px)\n // We scale it proportionally so it looks identical to what user saw\n const strokeScale = Math.max(naturalWidth, naturalHeight) / 1000;\n ctx.lineWidth = Math.max(1, stroke.width * strokeScale);\n\n if (stroke.tool === \"highlighter\") {\n ctx.globalAlpha = stroke.opacity || 0.35;\n ctx.strokeStyle = hexToRgba(stroke.color, 1);\n // Multiply blend looks most natural on medical illustrations & documents\n ctx.globalCompositeOperation = \"multiply\";\n } else {\n ctx.globalAlpha = stroke.opacity || 1.0;\n ctx.strokeStyle = stroke.color;\n ctx.globalCompositeOperation = \"source-over\";\n }\n\n const p0 = stroke.points[0];\n const startX = p0.x * naturalWidth;\n const startY = p0.y * naturalHeight;\n ctx.moveTo(startX, startY);\n\n if (stroke.points.length === 1) {\n ctx.lineTo(startX + 0.5, startY + 0.5);\n } else {\n for (let i = 1; i < stroke.points.length - 1; i++) {\n const pt = stroke.points[i];\n const next = stroke.points[i + 1];\n const cx = (pt.x * naturalWidth + next.x * naturalWidth) / 2;\n const cy = (pt.y * naturalHeight + next.y * naturalHeight) / 2;\n ctx.quadraticCurveTo(pt.x * naturalWidth, pt.y * naturalHeight, cx, cy);\n }\n const last = stroke.points[stroke.points.length - 1];\n ctx.lineTo(last.x * naturalWidth, last.y * naturalHeight);\n }\n\n ctx.stroke();\n ctx.restore();\n }\n }\n\n return canvas;\n}\n\n/**\n * Triggers a browser download of the image (with or without annotations).\n */\nexport async function downloadAnnotatedImage(\n imageSource: string | HTMLImageElement,\n strokes: AnnotationStroke[],\n options: ExportOptions = {}\n): Promise<void> {\n const {\n format = \"image/png\",\n quality = 0.95,\n fileName = `tbib-annotated-image-${Date.now()}.${format === \"image/jpeg\" ? \"jpg\" : format === \"image/webp\" ? \"webp\" : \"png\"}`,\n } = options;\n\n try {\n const canvas = await renderToOffscreenCanvas(imageSource, strokes, options);\n\n return new Promise<void>((resolve, reject) => {\n canvas.toBlob(\n (blob) => {\n if (!blob) {\n reject(new Error(\"Failed to generate image blob from canvas\"));\n return;\n }\n\n const url = URL.createObjectURL(blob);\n const link = document.createElement(\"a\");\n link.href = url;\n link.download = fileName;\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n\n setTimeout(() => URL.revokeObjectURL(url), 1000);\n resolve();\n },\n format,\n quality\n );\n });\n } catch (err) {\n console.warn(\"Offscreen canvas render encountered an issue, falling back to direct asset download:\", err);\n const src = typeof imageSource === \"string\" ? imageSource : imageSource.src;\n const link = document.createElement(\"a\");\n link.href = src;\n link.download = fileName;\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/engine/transform.ts","../src/annotations/smooth.ts","../src/annotations/store.ts","../src/export/canvas.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["import type { ImageDimensions, NormalizedPoint, ScreenPoint, TransformState } from \"../types\";\n\nexport const DEFAULT_TRANSFORM: TransformState = {\n scale: 1,\n translateX: 0,\n translateY: 0,\n rotate: 0,\n};\n\nexport const MIN_SCALE = 0.5;\nexport const MAX_SCALE = 8.0;\n\n/**\n * Clamps scale between min and max allowed bounds.\n */\nexport function clampScale(scale: number, min = MIN_SCALE, max = MAX_SCALE): number {\n return Math.min(Math.max(scale, min), max);\n}\n\n/**\n * Converts a screen/viewport coordinate (e.g. mouse or touch event)\n * into a normalized image coordinate (0 to 1) based on the image bounding box\n * and current scale/pan/rotation transform.\n */\nexport function screenToNormalizedImage(\n screenPt: ScreenPoint,\n containerRect: DOMRect,\n imageDimensions: ImageDimensions,\n transform: TransformState\n): NormalizedPoint | null {\n if (imageDimensions.width <= 0 || imageDimensions.height <= 0) return null;\n\n // 1. Point relative to container center\n const centerX = containerRect.left + containerRect.width / 2;\n const centerY = containerRect.top + containerRect.height / 2;\n\n let dx = screenPt.x - centerX - transform.translateX;\n let dy = screenPt.y - centerY - transform.translateY;\n\n // 2. Undo rotation\n const rad = (-transform.rotate * Math.PI) / 180;\n const cos = Math.cos(rad);\n const sin = Math.sin(rad);\n const unrotatedX = dx * cos - dy * sin;\n const unrotatedY = dx * sin + dy * cos;\n\n // 3. Undo scale\n const scaledX = unrotatedX / transform.scale;\n const scaledY = unrotatedY / transform.scale;\n\n // 4. Compute displayed image fitted bounds at scale = 1\n const aspect = imageDimensions.width / imageDimensions.height;\n const containerAspect = containerRect.width / containerRect.height;\n\n let fittedW: number;\n let fittedH: number;\n\n if (containerAspect > aspect) {\n fittedH = containerRect.height;\n fittedW = fittedH * aspect;\n } else {\n fittedW = containerRect.width;\n fittedH = fittedW / aspect;\n }\n\n // 5. Convert to normalized 0..1 coordinates relative to fitted image box\n const normX = (scaledX + fittedW / 2) / fittedW;\n const normY = (scaledY + fittedH / 2) / fittedH;\n\n return {\n x: Math.max(0, Math.min(1, normX)),\n y: Math.max(0, Math.min(1, normY)),\n };\n}\n\n/**\n * Converts a normalized image coordinate (0 to 1) to screen/pixel coordinates\n * inside the given container.\n */\nexport function normalizedImageToScreen(\n normPt: NormalizedPoint,\n containerRect: DOMRect,\n imageDimensions: ImageDimensions,\n transform: TransformState\n): ScreenPoint {\n const aspect = imageDimensions.width / imageDimensions.height;\n const containerAspect = containerRect.width / containerRect.height;\n\n let fittedW: number;\n let fittedH: number;\n\n if (containerAspect > aspect) {\n fittedH = containerRect.height;\n fittedW = fittedH * aspect;\n } else {\n fittedW = containerRect.width;\n fittedH = fittedW / aspect;\n }\n\n // Local unscaled, unrotated position relative to center\n const localX = normPt.x * fittedW - fittedW / 2;\n const localY = normPt.y * fittedH - fittedH / 2;\n\n // Apply scale\n const scaledX = localX * transform.scale;\n const scaledY = localY * transform.scale;\n\n // Apply rotation\n const rad = (transform.rotate * Math.PI) / 180;\n const cos = Math.cos(rad);\n const sin = Math.sin(rad);\n const rotatedX = scaledX * cos - scaledY * sin;\n const rotatedY = scaledX * sin + scaledY * cos;\n\n // Apply translation and container center\n const centerX = containerRect.left + containerRect.width / 2;\n const centerY = containerRect.top + containerRect.height / 2;\n\n return {\n x: centerX + transform.translateX + rotatedX,\n y: centerY + transform.translateY + rotatedY,\n };\n}\n\n/**\n * Computes fitted size of an image inside container maintaining aspect ratio.\n */\nexport function getFittedImageBounds(\n containerW: number,\n containerH: number,\n imgW: number,\n imgH: number\n): { width: number; height: number } {\n if (imgW <= 0 || imgH <= 0 || containerW <= 0 || containerH <= 0) {\n return { width: 0, height: 0 };\n }\n const aspect = imgW / imgH;\n const containerAspect = containerW / containerH;\n\n if (containerAspect > aspect) {\n const height = containerH;\n return { width: height * aspect, height };\n } else {\n const width = containerW;\n return { width, height: width / aspect };\n }\n}\n","import type { NormalizedPoint } from \"../types\";\n\n/**\n * Converts a list of normalized points into a smooth SVG path definition\n * using quadratic Bézier midpoint interpolation.\n */\nexport function pointsToSvgPath(\n points: NormalizedPoint[],\n width: number,\n height: number\n): string {\n if (points.length === 0) return \"\";\n if (points.length === 1) {\n const p = points[0];\n const px = p.x * width;\n const py = p.y * height;\n return `M ${px} ${py} L ${px + 0.1} ${py + 0.1}`;\n }\n\n const p0 = points[0];\n let d = `M ${p0.x * width} ${p0.y * height}`;\n\n for (let i = 1; i < points.length - 1; i++) {\n const current = points[i];\n const next = points[i + 1];\n\n const cx = (current.x * width + next.x * width) / 2;\n const cy = (current.y * height + next.y * height) / 2;\n\n d += ` Q ${current.x * width} ${current.y * height}, ${cx} ${cy}`;\n }\n\n const last = points[points.length - 1];\n d += ` L ${last.x * width} ${last.y * height}`;\n\n return d;\n}\n\n/**\n * Calculates squared Euclidean distance between two points in normalized coordinates.\n */\nexport function distanceSq(p1: NormalizedPoint, p2: NormalizedPoint): number {\n const dx = p1.x - p2.x;\n const dy = p1.y - p2.y;\n return dx * dx + dy * dy;\n}\n\n/**\n * Checks if a test point in normalized coordinates is close to any point in the stroke\n * within an interaction threshold (used for eraser hit detection).\n */\nexport function isPointNearStroke(\n testPt: NormalizedPoint,\n points: NormalizedPoint[],\n thresholdNorm: number\n): boolean {\n const thresholdSq = thresholdNorm * thresholdNorm;\n for (let i = 0; i < points.length; i++) {\n if (distanceSq(testPt, points[i]) <= thresholdSq) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Converts hex color (#rrggbb or #rgb) to an rgba string with alpha.\n */\nexport function hexToRgba(hex: string, alpha = 1): string {\n if (hex.startsWith(\"rgba\")) return hex;\n const clean = hex.trim().replace(\"#\", \"\");\n if (clean.length === 3) {\n const r = parseInt(clean[0] + clean[0], 16);\n const g = parseInt(clean[1] + clean[1], 16);\n const b = parseInt(clean[2] + clean[2], 16);\n return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n }\n if (clean.length === 6) {\n const r = parseInt(clean.substring(0, 2), 16);\n const g = parseInt(clean.substring(2, 4), 16);\n const b = parseInt(clean.substring(4, 6), 16);\n return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n }\n return hex;\n}\n","import type { AnnotationStroke, SlideAnnotations } from \"../types\";\n\nexport interface HistoryState {\n past: AnnotationStroke[][];\n present: AnnotationStroke[];\n future: AnnotationStroke[][];\n}\n\nexport class AnnotationStore {\n private slides = new Map<string, HistoryState>();\n private listeners = new Set<(slideId: string, strokes: AnnotationStroke[]) => void>();\n\n constructor(initialData?: SlideAnnotations[]) {\n if (initialData) {\n for (const item of initialData) {\n this.slides.set(item.slideId, {\n past: [],\n present: [...item.strokes],\n future: [],\n });\n }\n }\n }\n\n private getOrCreate(slideId: string): HistoryState {\n let state = this.slides.get(slideId);\n if (!state) {\n state = {\n past: [],\n present: [],\n future: [],\n };\n this.slides.set(slideId, state);\n }\n return state;\n }\n\n public getStrokes(slideId: string): AnnotationStroke[] {\n return this.getOrCreate(slideId).present;\n }\n\n public canUndo(slideId: string): boolean {\n return this.getOrCreate(slideId).past.length > 0;\n }\n\n public canRedo(slideId: string): boolean {\n return this.getOrCreate(slideId).future.length > 0;\n }\n\n public addStroke(slideId: string, stroke: AnnotationStroke): void {\n const state = this.getOrCreate(slideId);\n state.past.push([...state.present]);\n state.present = [...state.present, stroke];\n state.future = []; // Clear redo tree on new action\n this.notify(slideId);\n }\n\n public removeStroke(slideId: string, strokeId: string): void {\n const state = this.getOrCreate(slideId);\n const filtered = state.present.filter((s) => s.id !== strokeId);\n if (filtered.length !== state.present.length) {\n state.past.push([...state.present]);\n state.present = filtered;\n state.future = [];\n this.notify(slideId);\n }\n }\n\n public removeStrokesNear(\n slideId: string,\n testPt: { x: number; y: number },\n thresholdNorm: number,\n isPointNearStrokeFn: (pt: { x: number; y: number }, pts: { x: number; y: number }[], thresh: number) => boolean\n ): boolean {\n const state = this.getOrCreate(slideId);\n const toKeep: AnnotationStroke[] = [];\n let removedAny = false;\n\n for (const stroke of state.present) {\n if (isPointNearStrokeFn(testPt, stroke.points, thresholdNorm)) {\n removedAny = true;\n } else {\n toKeep.push(stroke);\n }\n }\n\n if (removedAny) {\n state.past.push([...state.present]);\n state.present = toKeep;\n state.future = [];\n this.notify(slideId);\n return true;\n }\n return false;\n }\n\n public clear(slideId: string): void {\n const state = this.getOrCreate(slideId);\n if (state.present.length === 0) return;\n state.past.push([...state.present]);\n state.present = [];\n state.future = [];\n this.notify(slideId);\n }\n\n public undo(slideId: string): boolean {\n const state = this.getOrCreate(slideId);\n if (state.past.length === 0) return false;\n\n const previous = state.past.pop()!;\n state.future.unshift([...state.present]);\n state.present = previous;\n this.notify(slideId);\n return true;\n }\n\n public redo(slideId: string): boolean {\n const state = this.getOrCreate(slideId);\n if (state.future.length === 0) return false;\n\n const next = state.future.shift()!;\n state.past.push([...state.present]);\n state.present = next;\n this.notify(slideId);\n return true;\n }\n\n public subscribe(listener: (slideId: string, strokes: AnnotationStroke[]) => void): () => void {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n private notify(slideId: string): void {\n const strokes = this.getStrokes(slideId);\n for (const listener of this.listeners) {\n listener(slideId, strokes);\n }\n }\n\n public exportJSON(): SlideAnnotations[] {\n const result: SlideAnnotations[] = [];\n for (const [slideId, state] of this.slides.entries()) {\n if (state.present.length > 0) {\n result.push({\n slideId,\n strokes: [...state.present],\n });\n }\n }\n return result;\n }\n\n public importJSON(data: SlideAnnotations[]): void {\n for (const item of data) {\n this.slides.set(item.slideId, {\n past: [],\n present: [...item.strokes],\n future: [],\n });\n this.notify(item.slideId);\n }\n }\n}\n","import type { AnnotationStroke, ExportOptions } from \"../types\";\nimport { hexToRgba } from \"../annotations/smooth\";\n\n/**\n * Loads an image from a source URL or HTMLImageElement cleanly without\n * tainting the canvas, using fetch->Blob or crossOrigin cache-busting.\n */\nasync function loadCleanImage(\n imageSource: string | HTMLImageElement\n): Promise<{ img: HTMLImageElement; cleanup?: () => void }> {\n const src = typeof imageSource === \"string\" ? imageSource : imageSource.src;\n\n // Strategy 1: Fetch as Blob URL (Guarantees same-origin blob: URL, zero canvas tainting)\n try {\n const res = await fetch(src, { mode: \"cors\" });\n if (res.ok) {\n const blob = await res.blob();\n const blobUrl = URL.createObjectURL(blob);\n const img = new Image();\n await new Promise<void>((resolve, reject) => {\n img.onload = () => resolve();\n img.onerror = reject;\n img.src = blobUrl;\n });\n return {\n img,\n cleanup: () => URL.revokeObjectURL(blobUrl),\n };\n }\n } catch {\n // Strategy 1 failed (e.g. strict origin restrictions on fetch)\n }\n\n // Strategy 2: In-memory Image with crossOrigin = \"anonymous\" and cache buster\n try {\n const img = new Image();\n img.crossOrigin = \"anonymous\";\n const sep = src.includes(\"?\") ? \"&\" : \"?\";\n const corsSrc = `${src}${sep}_export=${Date.now()}`;\n await new Promise<void>((resolve, reject) => {\n img.onload = () => resolve();\n img.onerror = reject;\n img.src = corsSrc;\n });\n return { img };\n } catch {\n // Strategy 2 failed\n }\n\n // Strategy 3: Fall back to existing image element or non-CORS element\n if (typeof imageSource !== \"string\" && imageSource.naturalWidth > 0) {\n return { img: imageSource };\n }\n\n const fallbackImg = new Image();\n await new Promise<void>((resolve, reject) => {\n fallbackImg.onload = () => resolve();\n fallbackImg.onerror = reject;\n fallbackImg.src = src;\n });\n return { img: fallbackImg };\n}\n\n/**\n * Loads an image from a URL or source element and renders it\n * along with its annotations onto an off-screen HTMLCanvasElement\n * at original native resolution.\n */\nexport async function renderToOffscreenCanvas(\n imageSource: string | HTMLImageElement,\n strokes: AnnotationStroke[],\n options: ExportOptions = {}\n): Promise<HTMLCanvasElement> {\n const { canvas, cleanup } = await renderToOffscreenCanvasWithCleanup(imageSource, strokes, options);\n cleanup?.();\n return canvas;\n}\n\nexport async function renderToOffscreenCanvasWithCleanup(\n imageSource: string | HTMLImageElement,\n strokes: AnnotationStroke[],\n options: ExportOptions = {}\n): Promise<{ canvas: HTMLCanvasElement; cleanup?: () => void }> {\n const { includeAnnotations = true, backgroundColor } = options;\n\n // 1. Resolve clean HTMLImageElement\n const { img, cleanup } = await loadCleanImage(imageSource);\n\n const naturalWidth = img.naturalWidth || img.width;\n const naturalHeight = img.naturalHeight || img.height;\n\n if (naturalWidth === 0 || naturalHeight === 0) {\n cleanup?.();\n throw new Error(\"Invalid image dimensions: width or height is 0\");\n }\n\n // 2. Setup high-res canvas matching native dimensions\n const canvas = document.createElement(\"canvas\");\n canvas.width = naturalWidth;\n canvas.height = naturalHeight;\n const ctx = canvas.getContext(\"2d\");\n\n if (!ctx) {\n cleanup?.();\n throw new Error(\"Unable to obtain 2D canvas context for export\");\n }\n\n // 3. Optional background color\n if (backgroundColor) {\n ctx.fillStyle = backgroundColor;\n ctx.fillRect(0, 0, naturalWidth, naturalHeight);\n }\n\n // 4. Draw base image\n ctx.drawImage(img, 0, 0, naturalWidth, naturalHeight);\n\n // 5. Burn in annotations if requested\n if (includeAnnotations && strokes.length > 0) {\n for (const stroke of strokes) {\n if (stroke.points.length === 0) continue;\n\n ctx.save();\n ctx.beginPath();\n ctx.lineCap = \"round\";\n ctx.lineJoin = \"round\";\n\n // Scale stroke width relative to the maximum dimension of the image\n const strokeScale = Math.max(naturalWidth, naturalHeight) / 1000;\n ctx.lineWidth = Math.max(1, stroke.width * strokeScale);\n\n if (stroke.tool === \"highlighter\") {\n ctx.globalAlpha = stroke.opacity || 0.35;\n ctx.strokeStyle = hexToRgba(stroke.color, 1);\n ctx.globalCompositeOperation = \"multiply\";\n } else {\n ctx.globalAlpha = stroke.opacity || 1.0;\n ctx.strokeStyle = stroke.color;\n ctx.globalCompositeOperation = \"source-over\";\n }\n\n const p0 = stroke.points[0];\n const startX = p0.x * naturalWidth;\n const startY = p0.y * naturalHeight;\n ctx.moveTo(startX, startY);\n\n if (stroke.points.length === 1) {\n ctx.lineTo(startX + 0.5, startY + 0.5);\n } else {\n for (let i = 1; i < stroke.points.length - 1; i++) {\n const pt = stroke.points[i];\n const next = stroke.points[i + 1];\n const cx = (pt.x * naturalWidth + next.x * naturalWidth) / 2;\n const cy = (pt.y * naturalHeight + next.y * naturalHeight) / 2;\n ctx.quadraticCurveTo(pt.x * naturalWidth, pt.y * naturalHeight, cx, cy);\n }\n const last = stroke.points[stroke.points.length - 1];\n ctx.lineTo(last.x * naturalWidth, last.y * naturalHeight);\n }\n\n ctx.stroke();\n ctx.restore();\n }\n }\n\n return { canvas, cleanup };\n}\n\n/**\n * Triggers a browser download of the image (with or without annotations).\n * Guarantees zero unhandled SecurityError on tainted canvases with automatic fallback.\n */\nexport async function downloadAnnotatedImage(\n imageSource: string | HTMLImageElement,\n strokes: AnnotationStroke[],\n options: ExportOptions = {}\n): Promise<void> {\n const {\n format = \"image/png\",\n quality = 0.95,\n fileName = `tbib-annotated-image-${Date.now()}.${format === \"image/jpeg\" ? \"jpg\" : format === \"image/webp\" ? \"webp\" : \"png\"}`,\n } = options;\n\n const src = typeof imageSource === \"string\" ? imageSource : imageSource.src;\n\n const directDownload = (downloadUrl: string, name: string) => {\n const link = document.createElement(\"a\");\n link.href = downloadUrl;\n link.download = name;\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n };\n\n try {\n const { canvas, cleanup } = await renderToOffscreenCanvasWithCleanup(imageSource, strokes, options);\n\n await new Promise<void>((resolve) => {\n try {\n canvas.toBlob(\n (blob) => {\n cleanup?.();\n if (!blob) {\n directDownload(src, fileName);\n resolve();\n return;\n }\n\n const url = URL.createObjectURL(blob);\n directDownload(url, fileName);\n setTimeout(() => URL.revokeObjectURL(url), 1000);\n resolve();\n },\n format,\n quality\n );\n } catch (taintedErr) {\n cleanup?.();\n console.warn(\"Canvas was tainted by cross-origin security rules, downloading direct asset:\", taintedErr);\n directDownload(src, fileName);\n resolve();\n }\n });\n } catch (err) {\n console.warn(\"Offscreen canvas render encountered an issue, falling back to direct asset download:\", err);\n directDownload(src, fileName);\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tbib-image-viewer/core",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Framework-agnostic coordinate math, annotation engine, stroke smoothing, and native-res canvas export for TBiB Image Viewer",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",