@pdfme/schemas 6.1.13-dev.25 → 6.1.13-dev.26

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.
@@ -0,0 +1,31 @@
1
+ export type ImageFormat = 'jpeg' | 'png' | 'other';
2
+ export declare const detectImageFormat: (bytes: Uint8Array) => ImageFormat;
3
+ /** TIFF/IFD0: returns 1..8 or undefined. Never throws. */
4
+ export declare const parseTiffOrientation: (tiff: Uint8Array) => number | undefined;
5
+ /**
6
+ * JPEG: scan APP1 'Exif\0\0' segments; stop at SOS/EOI.
7
+ * Skips 0xFF fill/padding and standalone markers. If an Exif APP1 has no
8
+ * valid Orientation, keep scanning for a later one.
9
+ */
10
+ export declare const getJpegOrientation: (bytes: Uint8Array) => number | undefined;
11
+ /**
12
+ * PNG: scan chunks for eXIf (raw TIFF payload). Skip IDAT and other
13
+ * ancillary chunks; eXIf may appear after image data. Stop at IEND.
14
+ */
15
+ export declare const getPngOrientation: (bytes: Uint8Array) => number | undefined;
16
+ export declare const orientationSwapsDimensions: (orientation: number) => boolean;
17
+ /**
18
+ * Map source (x, y) into destination (dx, dy) for EXIF orientations 2–8.
19
+ * Orientations 5–8 swap width and height.
20
+ */
21
+ export declare const transformRgba: (src: Uint8Array, width: number, height: number, orientation: number) => {
22
+ data: Uint8Array;
23
+ width: number;
24
+ height: number;
25
+ };
26
+ /**
27
+ * One normalize path for JPEG and PNG. Tag absent/1 is a byte-identical
28
+ * passthrough. Tag 2–8 decodes, transforms pixels, and re-encodes without
29
+ * an orientation tag. May throw on decode/encode failure — callers fall back.
30
+ */
31
+ export declare const normalizeImageOrientation: (bytes: Uint8Array) => Uint8Array;
package/dist/index.js CHANGED
@@ -11,6 +11,8 @@ import { DEFAULT_FONT_NAME, ZOOM, b64toUint8Array, getDefaultFont, getFallbackFo
11
11
  import { Buffer as Buffer$1 } from "buffer";
12
12
  import { toRadians } from "@pdfme/pdf-lib";
13
13
  import { Barcode, Calendar, CalendarClock, ChevronDown, Circle, CircleDot, Clock, Image, List, Minus, QrCode, Route, Square, SquareCheck, Table, Type } from "lucide";
14
+ import jpegJsModule from "jpeg-js";
15
+ import UPNGModule from "@pdf-lib/upng";
14
16
  import DOMPurify from "dompurify";
15
17
  import bwipjs from "bwip-js";
16
18
  import AirDatepicker from "air-datepicker";
@@ -1406,88 +1408,208 @@ var listSchema = {
1406
1408
  icon: createSvgStr(List)
1407
1409
  };
1408
1410
  //#endregion
1409
- //#region src/graphics/imagehelper.ts
1410
- var decoder = new TextDecoder();
1411
- var toUTF8String = (input, start = 0, end = input.length) => decoder.decode(input.slice(start, end));
1412
- var toHexString = (input, start = 0, end = input.length) => input.slice(start, end).reduce((memo, i) => memo + ("0" + i.toString(16)).slice(-2), "");
1413
- var readUInt16BE = (input, offset = 0) => input[offset] * 256 + input[offset + 1];
1414
- var readUInt32BE = (input, offset = 0) => input[offset] * 2 ** 24 + input[offset + 1] * 2 ** 16 + input[offset + 2] * 256 + input[offset + 3];
1415
- var extractSize = (input, index) => {
1416
- return {
1417
- height: readUInt16BE(input, index),
1418
- width: readUInt16BE(input, index + 2)
1419
- };
1411
+ //#region src/graphics/orientation.ts
1412
+ var isJpegJsApi = (value) => !!value && typeof value === "object" && typeof value.decode === "function" && typeof value.encode === "function";
1413
+ var isUPNGApi = (value) => !!value && typeof value === "object" && typeof value.decode === "function" && typeof value.toRGBA8 === "function" && typeof value.encode === "function";
1414
+ var resolveExport = (value, guard, label) => {
1415
+ let current = value;
1416
+ while (current && typeof current === "object") {
1417
+ if (guard(current)) return current;
1418
+ current = current.default;
1419
+ }
1420
+ throw new TypeError(`Failed to resolve ${label} exports`);
1421
+ };
1422
+ var jpegJs = resolveExport(jpegJsModule, isJpegJsApi, "jpeg-js");
1423
+ var UPNG = resolveExport(UPNGModule, isUPNGApi, "@pdf-lib/upng");
1424
+ var JPEG_ENCODE_QUALITY = 90;
1425
+ var JPEG_MAX_MEMORY_MB = 512;
1426
+ var JPEG_SOI_0 = 255;
1427
+ var JPEG_SOI_1 = 216;
1428
+ var PNG_SIGNATURE = new Uint8Array([
1429
+ 137,
1430
+ 80,
1431
+ 78,
1432
+ 71,
1433
+ 13,
1434
+ 10,
1435
+ 26,
1436
+ 10
1437
+ ]);
1438
+ var toArrayBuffer = (bytes) => {
1439
+ const copy = new Uint8Array(bytes.byteLength);
1440
+ copy.set(bytes);
1441
+ return copy.buffer;
1442
+ };
1443
+ var isPngSignature = (bytes) => {
1444
+ if (bytes.length < PNG_SIGNATURE.length) return false;
1445
+ for (let i = 0; i < PNG_SIGNATURE.length; i++) if (bytes[i] !== PNG_SIGNATURE[i]) return false;
1446
+ return true;
1420
1447
  };
1421
- var validateInput = (input, index) => {
1422
- if (index > input.length) throw new TypeError("Corrupt JPG, exceeded buffer limits");
1423
- if (input[index] !== 255) throw new TypeError("Invalid JPG, marker table corrupted");
1448
+ var detectImageFormat = (bytes) => {
1449
+ if (bytes.length >= 2 && bytes[0] === JPEG_SOI_0 && bytes[1] === JPEG_SOI_1) return "jpeg";
1450
+ if (isPngSignature(bytes)) return "png";
1451
+ return "other";
1424
1452
  };
1425
- var JPG = {
1426
- validate: (input) => toHexString(input, 0, 2) === "ffd8",
1427
- calculate(input) {
1428
- input = input.slice(4);
1429
- let next;
1430
- while (input.length) {
1431
- const i = readUInt16BE(input, 0);
1432
- validateInput(input, i);
1433
- next = input[i + 1];
1434
- if (next === 192 || next === 193 || next === 194) return extractSize(input, i + 5);
1435
- input = input.slice(i + 2);
1453
+ /** TIFF/IFD0: returns 1..8 or undefined. Never throws. */
1454
+ var parseTiffOrientation = (tiff) => {
1455
+ try {
1456
+ if (tiff.length < 8) return void 0;
1457
+ const le = tiff[0] === 73 && tiff[1] === 73;
1458
+ if (!le && !(tiff[0] === 77 && tiff[1] === 77)) return void 0;
1459
+ const view = new DataView(tiff.buffer, tiff.byteOffset, tiff.byteLength);
1460
+ if (view.getUint16(2, le) !== 42) return void 0;
1461
+ const ifd = view.getUint32(4, le);
1462
+ if (ifd + 2 > tiff.length) return void 0;
1463
+ const n = view.getUint16(ifd, le);
1464
+ for (let i = 0; i < n; i++) {
1465
+ const entry = ifd + 2 + i * 12;
1466
+ if (entry + 12 > tiff.length) return void 0;
1467
+ if (view.getUint16(entry, le) !== 274) continue;
1468
+ if (view.getUint16(entry + 2, le) !== 3) return void 0;
1469
+ if (view.getUint32(entry + 4, le) !== 1) return void 0;
1470
+ const val = view.getUint16(entry + 8, le);
1471
+ return val >= 1 && val <= 8 ? val : void 0;
1436
1472
  }
1437
- throw new TypeError("Invalid JPG, no size found");
1473
+ return;
1474
+ } catch {
1475
+ return;
1438
1476
  }
1439
1477
  };
1440
- var pngSignature = "PNG\r\n\n";
1441
- var pngImageHeaderChunkName = "IHDR";
1442
- var pngFriedChunkName = "CgBI";
1443
- var typeHandlers = {
1444
- jpg: JPG,
1445
- png: {
1446
- validate(input) {
1447
- if (pngSignature === toUTF8String(input, 1, 8)) {
1448
- let chunkName = toUTF8String(input, 12, 16);
1449
- if (chunkName === pngFriedChunkName) chunkName = toUTF8String(input, 28, 32);
1450
- if (chunkName !== pngImageHeaderChunkName) throw new TypeError("Invalid PNG");
1451
- return true;
1478
+ var isExifApp1Payload = (segment) => segment.length >= 6 && segment[0] === 69 && segment[1] === 120 && segment[2] === 105 && segment[3] === 102 && segment[4] === 0 && segment[5] === 0;
1479
+ /** Standalone JPEG markers have no length field (TEM, RST0–7, SOI). */
1480
+ var isStandaloneJpegMarker = (marker) => marker === 1 || marker >= 208 && marker <= 216;
1481
+ /**
1482
+ * JPEG: scan APP1 'Exif\0\0' segments; stop at SOS/EOI.
1483
+ * Skips 0xFF fill/padding and standalone markers. If an Exif APP1 has no
1484
+ * valid Orientation, keep scanning for a later one.
1485
+ */
1486
+ var getJpegOrientation = (bytes) => {
1487
+ if (bytes.length < 2 || bytes[0] !== JPEG_SOI_0 || bytes[1] !== JPEG_SOI_1) return void 0;
1488
+ let pos = 2;
1489
+ while (pos < bytes.length) {
1490
+ if (bytes[pos] !== 255) return void 0;
1491
+ while (pos < bytes.length && bytes[pos] === 255) pos++;
1492
+ if (pos >= bytes.length) return void 0;
1493
+ const marker = bytes[pos++];
1494
+ if (isStandaloneJpegMarker(marker)) continue;
1495
+ if (marker === 218 || marker === 217) return void 0;
1496
+ if (pos + 2 > bytes.length) return void 0;
1497
+ const size = bytes[pos] << 8 | bytes[pos + 1];
1498
+ if (size < 2 || pos + size > bytes.length) return void 0;
1499
+ if (marker === 225 && size >= 8) {
1500
+ const segment = bytes.subarray(pos + 2, pos + size);
1501
+ if (isExifApp1Payload(segment)) {
1502
+ const orientation = parseTiffOrientation(segment.subarray(6));
1503
+ if (orientation !== void 0) return orientation;
1452
1504
  }
1453
- return false;
1454
- },
1455
- calculate(input) {
1456
- if (toUTF8String(input, 12, 16) === pngFriedChunkName) return {
1457
- height: readUInt32BE(input, 36),
1458
- width: readUInt32BE(input, 32)
1459
- };
1460
- return {
1461
- height: readUInt32BE(input, 20),
1462
- width: readUInt32BE(input, 16)
1463
- };
1464
1505
  }
1506
+ pos += size;
1465
1507
  }
1466
1508
  };
1467
- function detector(input) {
1468
- const firstBytes = {
1469
- 137: "png",
1470
- 255: "jpg"
1471
- };
1472
- const byte = input[0];
1473
- if (byte in firstBytes) {
1474
- const type = firstBytes[byte];
1475
- if (type && typeHandlers[type].validate(input)) return type;
1509
+ /**
1510
+ * PNG: scan chunks for eXIf (raw TIFF payload). Skip IDAT and other
1511
+ * ancillary chunks; eXIf may appear after image data. Stop at IEND.
1512
+ */
1513
+ var getPngOrientation = (bytes) => {
1514
+ if (!isPngSignature(bytes)) return void 0;
1515
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
1516
+ let pos = 8;
1517
+ while (pos + 12 <= bytes.length) {
1518
+ const length = view.getUint32(pos);
1519
+ if (pos + 12 + length > bytes.length) return void 0;
1520
+ const type = String.fromCharCode(bytes[pos + 4], bytes[pos + 5], bytes[pos + 6], bytes[pos + 7]);
1521
+ if (type === "eXIf") return parseTiffOrientation(bytes.subarray(pos + 8, pos + 8 + length));
1522
+ if (type === "IEND") return void 0;
1523
+ pos += 12 + length;
1476
1524
  }
1477
- return Object.keys(typeHandlers).find((key) => typeHandlers[key].validate(input));
1478
- }
1479
- var getImageDimension = (value) => {
1480
- const idx = value.indexOf(";base64,");
1481
- const imgBase64 = value.substring(idx + 8, value.length);
1482
- return imageSize(Buffer$1.from(imgBase64, "base64"));
1483
1525
  };
1484
- var imageSize = (imgBuffer) => {
1485
- const type = detector(imgBuffer);
1486
- if (typeof type !== "undefined" && type in typeHandlers) {
1487
- const size = typeHandlers[type].calculate(imgBuffer);
1488
- if (size !== void 0) return size;
1526
+ var orientationSwapsDimensions = (orientation) => orientation >= 5;
1527
+ /**
1528
+ * Map source (x, y) into destination (dx, dy) for EXIF orientations 2–8.
1529
+ * Orientations 5–8 swap width and height.
1530
+ */
1531
+ var transformRgba = (src, width, height, orientation) => {
1532
+ const swap = orientationSwapsDimensions(orientation);
1533
+ const destWidth = swap ? height : width;
1534
+ const destHeight = swap ? width : height;
1535
+ const dest = new Uint8Array(destWidth * destHeight * 4);
1536
+ for (let y = 0; y < height; y++) for (let x = 0; x < width; x++) {
1537
+ let dx = x;
1538
+ let dy = y;
1539
+ switch (orientation) {
1540
+ case 2:
1541
+ dx = width - 1 - x;
1542
+ dy = y;
1543
+ break;
1544
+ case 3:
1545
+ dx = width - 1 - x;
1546
+ dy = height - 1 - y;
1547
+ break;
1548
+ case 4:
1549
+ dx = x;
1550
+ dy = height - 1 - y;
1551
+ break;
1552
+ case 5:
1553
+ dx = y;
1554
+ dy = x;
1555
+ break;
1556
+ case 6:
1557
+ dx = height - 1 - y;
1558
+ dy = x;
1559
+ break;
1560
+ case 7:
1561
+ dx = height - 1 - y;
1562
+ dy = width - 1 - x;
1563
+ break;
1564
+ case 8:
1565
+ dx = y;
1566
+ dy = width - 1 - x;
1567
+ }
1568
+ const srcIndex = (y * width + x) * 4;
1569
+ const destIndex = (dy * destWidth + dx) * 4;
1570
+ dest[destIndex] = src[srcIndex];
1571
+ dest[destIndex + 1] = src[srcIndex + 1];
1572
+ dest[destIndex + 2] = src[srcIndex + 2];
1573
+ dest[destIndex + 3] = src[srcIndex + 3];
1489
1574
  }
1490
- throw new TypeError("[@pdfme/schemas/images] Unsupported file type: " + (type === void 0 ? "undefined" : type));
1575
+ return {
1576
+ data: dest,
1577
+ width: destWidth,
1578
+ height: destHeight
1579
+ };
1580
+ };
1581
+ var bakeJpegOrientation = (bytes) => {
1582
+ const orientation = getJpegOrientation(bytes);
1583
+ if (!orientation || orientation === 1) return bytes;
1584
+ const raw = jpegJs.decode(bytes, {
1585
+ useTArray: true,
1586
+ formatAsRGBA: true,
1587
+ maxMemoryUsageInMB: JPEG_MAX_MEMORY_MB
1588
+ });
1589
+ const transformed = transformRgba(raw.data, raw.width, raw.height, orientation);
1590
+ return new Uint8Array(jpegJs.encode({
1591
+ data: transformed.data,
1592
+ width: transformed.width,
1593
+ height: transformed.height
1594
+ }, JPEG_ENCODE_QUALITY).data);
1595
+ };
1596
+ var bakePngOrientation = (bytes) => {
1597
+ const orientation = getPngOrientation(bytes);
1598
+ if (!orientation || orientation === 1) return bytes;
1599
+ const img = UPNG.decode(toArrayBuffer(bytes));
1600
+ const transformed = transformRgba(new Uint8Array(UPNG.toRGBA8(img)[0]), img.width, img.height, orientation);
1601
+ return new Uint8Array(UPNG.encode([toArrayBuffer(transformed.data)], transformed.width, transformed.height, 0));
1602
+ };
1603
+ /**
1604
+ * One normalize path for JPEG and PNG. Tag absent/1 is a byte-identical
1605
+ * passthrough. Tag 2–8 decodes, transforms pixels, and re-encodes without
1606
+ * an orientation tag. May throw on decode/encode failure — callers fall back.
1607
+ */
1608
+ var normalizeImageOrientation = (bytes) => {
1609
+ const kind = detectImageFormat(bytes);
1610
+ if (kind === "jpeg") return bakeJpegOrientation(bytes);
1611
+ if (kind === "png") return bakePngOrientation(bytes);
1612
+ return bytes;
1491
1613
  };
1492
1614
  //#endregion
1493
1615
  //#region src/graphics/image.ts
@@ -1515,6 +1637,11 @@ var getCacheKey = (schema, input) => {
1515
1637
  const hex = (hash >>> 0).toString(16).padStart(8, "0");
1516
1638
  return `${schema.type}:${input.length}:${hex}`;
1517
1639
  };
1640
+ var dataUrlToBytes = (value) => {
1641
+ const idx = value.indexOf(";base64,");
1642
+ const base64 = idx >= 0 ? value.slice(idx + 8) : value;
1643
+ return new Uint8Array(Buffer$1.from(base64, "base64"));
1644
+ };
1518
1645
  var fullSize$1 = {
1519
1646
  width: "100%",
1520
1647
  height: "100%"
@@ -1527,16 +1654,24 @@ var imageSchema = {
1527
1654
  const inputImageCacheKey = getCacheKey(schema, value);
1528
1655
  let image = _cache.get(inputImageCacheKey);
1529
1656
  if (!image) {
1530
- image = await (value.startsWith("data:image/png;") ? pdfDoc.embedPng(value) : pdfDoc.embedJpg(value));
1657
+ const originalBytes = dataUrlToBytes(value);
1658
+ const kind = detectImageFormat(originalBytes);
1659
+ let bytes = originalBytes;
1660
+ try {
1661
+ if (kind === "jpeg" || kind === "png") bytes = normalizeImageOrientation(originalBytes);
1662
+ } catch (error) {
1663
+ console.warn("[@pdfme/schemas] EXIF orientation bake failed; embedding original bytes:", error);
1664
+ bytes = originalBytes;
1665
+ }
1666
+ image = await (kind === "png" ? pdfDoc.embedPng(bytes) : pdfDoc.embedJpg(bytes));
1531
1667
  _cache.set(inputImageCacheKey, image);
1532
1668
  }
1533
1669
  const _schema = {
1534
1670
  ...schema,
1535
1671
  position: { ...schema.position }
1536
1672
  };
1537
- const dimension = getImageDimension(value);
1538
- const imageWidth = px2mm(dimension.width);
1539
- const imageHeight = px2mm(dimension.height);
1673
+ const imageWidth = px2mm(image.width);
1674
+ const imageHeight = px2mm(image.height);
1540
1675
  const boxWidth = _schema.width;
1541
1676
  const boxHeight = _schema.height;
1542
1677
  const imageRatio = imageWidth / imageHeight;