@trackunit/shared-utils 0.0.89-alpha-13ecf77e3fd.0 → 0.0.89
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/index.cjs.js +263 -0
- package/index.esm.js +254 -1
- package/package.json +1 -1
- package/src/imageTools.d.ts +59 -0
- package/src/index.d.ts +2 -0
- package/src/svgTools.d.ts +36 -0
package/index.cjs.js
CHANGED
|
@@ -531,6 +531,128 @@ const toID = (uuid) => Number(uuid.replace(/-/g, "").replace(/^0+/, ""));
|
|
|
531
531
|
*/
|
|
532
532
|
const toIDs = (ids) => { var _a; return (_a = ids === null || ids === void 0 ? void 0 : ids.filter(id => !isNaN(toID(id))).map(id => toID(id))) !== null && _a !== void 0 ? _a : []; };
|
|
533
533
|
|
|
534
|
+
/**
|
|
535
|
+
* Converts an HTMLImageElement to a PNG image.
|
|
536
|
+
*
|
|
537
|
+
* @param options - image options
|
|
538
|
+
* @param {HTMLImageElement} options.image - The image element to convert.
|
|
539
|
+
* @param {ImageDimensions} options.dimensions - The dimensions of the image.
|
|
540
|
+
* @param {number} options.maxHeight - The maximum height of the converted PNG image.
|
|
541
|
+
* @param {number} options.maxWidth - The maximum width of the converted PNG image.
|
|
542
|
+
* @param {number} options.idealArea - The ideal area of the converted PNG image.
|
|
543
|
+
* @returns The converted PNG image as a data URL.
|
|
544
|
+
* @throws Error if unable to get the canvas context.
|
|
545
|
+
*/
|
|
546
|
+
const toPNG = ({ image, dimensions, maxHeight, maxWidth, idealArea, }) => {
|
|
547
|
+
var _a, _b;
|
|
548
|
+
let width = (_a = dimensions === null || dimensions === void 0 ? void 0 : dimensions.width) !== null && _a !== void 0 ? _a : image.width;
|
|
549
|
+
let height = (_b = dimensions === null || dimensions === void 0 ? void 0 : dimensions.height) !== null && _b !== void 0 ? _b : image.height;
|
|
550
|
+
const scale = calculateImageScaleRatio(width, height, maxWidth, maxHeight, idealArea);
|
|
551
|
+
width = Math.round(width * scale);
|
|
552
|
+
height = Math.round(height * scale);
|
|
553
|
+
const canvas = document.createElement("canvas");
|
|
554
|
+
canvas.width = width;
|
|
555
|
+
canvas.height = height;
|
|
556
|
+
const canvasCtx = canvas.getContext("2d");
|
|
557
|
+
if (!canvasCtx) {
|
|
558
|
+
throw new Error("Unable to get canvas context");
|
|
559
|
+
}
|
|
560
|
+
canvasCtx.drawImage(image, 0, 0, width, height);
|
|
561
|
+
return canvas.toDataURL("image/png");
|
|
562
|
+
};
|
|
563
|
+
/**
|
|
564
|
+
* Calculates the scale ratio for an image to fit inside given bounds
|
|
565
|
+
* Result is based on the idealArea more info: https://nicksherman.com/size-by-area/
|
|
566
|
+
*
|
|
567
|
+
* @param {number} width The width of the image
|
|
568
|
+
* @param {number} height The height of the image
|
|
569
|
+
* @param {number} maxWidth The maxWidth of the image
|
|
570
|
+
* @param {number} maxHeight The maxHeight of the image
|
|
571
|
+
* @param {number} idealArea The ideal area for the image to cover (Read more above)
|
|
572
|
+
*/
|
|
573
|
+
const calculateImageScaleRatio = (width, height, maxWidth, maxHeight, idealArea) => {
|
|
574
|
+
// Get the max width based on idealArea
|
|
575
|
+
const maxImageWidth = Math.round(width * Math.sqrt(idealArea / (width * height)));
|
|
576
|
+
const ratio = maxImageWidth / width;
|
|
577
|
+
// If the area scaling is largen than the ares, scale to fix
|
|
578
|
+
if (width * ratio > maxWidth || height * ratio > maxHeight) {
|
|
579
|
+
const scaleX = maxWidth / width;
|
|
580
|
+
const scaleY = maxHeight / height;
|
|
581
|
+
const scale = scaleX < scaleY ? scaleX : scaleY; // Use the lowest scale to make it fit and scale uniformly
|
|
582
|
+
return scale;
|
|
583
|
+
}
|
|
584
|
+
return maxImageWidth / width;
|
|
585
|
+
};
|
|
586
|
+
/**
|
|
587
|
+
* Preloads a data url as an image.
|
|
588
|
+
*
|
|
589
|
+
* @param {string} dataUrl - The data url to preload.
|
|
590
|
+
* @returns {Promise<HTMLImageElement>} A promise that resolves to the preloaded image.
|
|
591
|
+
*/
|
|
592
|
+
const preload = (dataUrl) => {
|
|
593
|
+
return new Promise((resolve, reject) => {
|
|
594
|
+
const image = new Image();
|
|
595
|
+
image.src = dataUrl;
|
|
596
|
+
image.onload = () => resolve(image);
|
|
597
|
+
image.onerror = () => reject();
|
|
598
|
+
});
|
|
599
|
+
};
|
|
600
|
+
/**
|
|
601
|
+
* Extracts the type of an image from a data URL
|
|
602
|
+
*
|
|
603
|
+
* @param {string} dataURL the data URL to extract the type from.
|
|
604
|
+
* @returns {"jpg" | "png" | "svg"} the type of the encoded image.
|
|
605
|
+
* @throws
|
|
606
|
+
*/
|
|
607
|
+
const getMimeTypeFromDataURL = (dataURL) => {
|
|
608
|
+
const mimeToTypeMap = {
|
|
609
|
+
"image/svg+xml": "svg",
|
|
610
|
+
"image/jpeg": "jpeg",
|
|
611
|
+
"image/jpg": "jpeg",
|
|
612
|
+
"image/png": "png",
|
|
613
|
+
};
|
|
614
|
+
const matches = dataURL.match(/^data:(.*?);base64,/);
|
|
615
|
+
if (!matches || !matches[1]) {
|
|
616
|
+
throw new Error("Invalid file");
|
|
617
|
+
}
|
|
618
|
+
const type = mimeToTypeMap[matches[1]];
|
|
619
|
+
if (!type) {
|
|
620
|
+
throw new Error("File is not a supported image");
|
|
621
|
+
}
|
|
622
|
+
return type;
|
|
623
|
+
};
|
|
624
|
+
/**
|
|
625
|
+
* Fetches and image from a URL and converts it to a base64 encoded data url
|
|
626
|
+
*
|
|
627
|
+
* @param {string} url - url pointing to an image resource
|
|
628
|
+
* @returns {Promise<string>} base64 encoded data url.
|
|
629
|
+
* @throws
|
|
630
|
+
*/
|
|
631
|
+
const fetchImageAsBase64 = async (url) => {
|
|
632
|
+
// Fetch the image from the URL
|
|
633
|
+
const response = await fetch(url);
|
|
634
|
+
if (!response.ok) {
|
|
635
|
+
throw new Error(`Failed to fetch image. Status: ${response.status}`);
|
|
636
|
+
}
|
|
637
|
+
// Convert the response to a Blob
|
|
638
|
+
const blob = await response.blob();
|
|
639
|
+
// Convert the Blob to a Base64 string
|
|
640
|
+
return new Promise((resolve, reject) => {
|
|
641
|
+
const reader = new FileReader();
|
|
642
|
+
reader.onloadend = () => {
|
|
643
|
+
if (typeof reader.result === "string") {
|
|
644
|
+
const base64String = reader.result;
|
|
645
|
+
resolve(base64String);
|
|
646
|
+
}
|
|
647
|
+
reject(new Error("Failed to convert Blob to Base64."));
|
|
648
|
+
};
|
|
649
|
+
reader.onerror = () => {
|
|
650
|
+
reject(new Error("Failed to convert Blob to Base64."));
|
|
651
|
+
};
|
|
652
|
+
reader.readAsDataURL(blob);
|
|
653
|
+
});
|
|
654
|
+
};
|
|
655
|
+
|
|
534
656
|
/**
|
|
535
657
|
* Deletes all undefined keys from an object.
|
|
536
658
|
*
|
|
@@ -982,6 +1104,137 @@ const stripHiddenCharacters = (input) => {
|
|
|
982
1104
|
return input.replace(hiddenRegex, "");
|
|
983
1105
|
};
|
|
984
1106
|
|
|
1107
|
+
/**
|
|
1108
|
+
* Convert the given value to HEX
|
|
1109
|
+
* If the value is not rgb, returns back the same value
|
|
1110
|
+
*/
|
|
1111
|
+
const rgb2hex = (rgb) => {
|
|
1112
|
+
var _a;
|
|
1113
|
+
const hexColor = (_a = rgb
|
|
1114
|
+
.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/)) === null || _a === void 0 ? void 0 : _a.slice(1).map(n => parseInt(n, 10).toString(16).padStart(2, "0")).join("");
|
|
1115
|
+
return hexColor ? `#${hexColor}` : rgb;
|
|
1116
|
+
};
|
|
1117
|
+
/**
|
|
1118
|
+
* Returns all svg related colors from a CSSStyleDeclaration
|
|
1119
|
+
*/
|
|
1120
|
+
const colorsFromStyleDeclaration = (styles) => {
|
|
1121
|
+
return [styles.fill, styles.stroke, styles.stopColor].filter(color => color);
|
|
1122
|
+
};
|
|
1123
|
+
/**
|
|
1124
|
+
* Loads the dimensions of an SVG string.
|
|
1125
|
+
*
|
|
1126
|
+
* @param base64EncodedSVG The SVG string to load dimensions from.
|
|
1127
|
+
* @returns A promise that resolves to an object containing the width and height of the SVG.
|
|
1128
|
+
* @throws {Error} If the SVG string is invalid or the dimensions cannot be determined.
|
|
1129
|
+
*/
|
|
1130
|
+
const loadSVGDimensions = (base64EncodedSVG) => {
|
|
1131
|
+
return new Promise((resolve, reject) => {
|
|
1132
|
+
// Create a temporary SVG element
|
|
1133
|
+
const svgAsString = atob(base64EncodedSVG.replace("data:image/svg+xml;base64,", ""));
|
|
1134
|
+
const parser = new DOMParser();
|
|
1135
|
+
const svgDoc = parser.parseFromString(svgAsString, "image/svg+xml");
|
|
1136
|
+
const svgElement = svgDoc.documentElement;
|
|
1137
|
+
// Check if parsing was successful
|
|
1138
|
+
if (svgElement.nodeName === "parsererror") {
|
|
1139
|
+
reject(new Error("Invalid SVG string"));
|
|
1140
|
+
return;
|
|
1141
|
+
}
|
|
1142
|
+
// Get dimensions
|
|
1143
|
+
const width = svgElement.width.baseVal.value;
|
|
1144
|
+
const height = svgElement.height.baseVal.value;
|
|
1145
|
+
// If width and height are not set explicitly, try to get from viewBox
|
|
1146
|
+
if (!width || !height) {
|
|
1147
|
+
const viewBox = svgElement.viewBox.baseVal;
|
|
1148
|
+
if (viewBox.width && viewBox.height) {
|
|
1149
|
+
resolve({
|
|
1150
|
+
width: viewBox.width,
|
|
1151
|
+
height: viewBox.height,
|
|
1152
|
+
});
|
|
1153
|
+
}
|
|
1154
|
+
else {
|
|
1155
|
+
reject(new Error("Unable to determine SVG dimensions"));
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
else {
|
|
1159
|
+
resolve({
|
|
1160
|
+
width,
|
|
1161
|
+
height,
|
|
1162
|
+
});
|
|
1163
|
+
}
|
|
1164
|
+
});
|
|
1165
|
+
};
|
|
1166
|
+
/**
|
|
1167
|
+
* Returns an array of all colors used in an svg
|
|
1168
|
+
*/
|
|
1169
|
+
const getAllColors = (base64EncodedSVG) => {
|
|
1170
|
+
const svgAsString = atob(base64EncodedSVG.replace("data:image/svg+xml;base64,", ""));
|
|
1171
|
+
const parser = new DOMParser();
|
|
1172
|
+
const svgImage = parser.parseFromString(svgAsString, "image/svg+xml");
|
|
1173
|
+
const colors = [];
|
|
1174
|
+
svgImage.querySelectorAll("[style]").forEach(element => {
|
|
1175
|
+
// Not all Elements have the style prop, this seems like a safe way to check and assert
|
|
1176
|
+
if (element instanceof HTMLElement) {
|
|
1177
|
+
const elementStyles = element.style;
|
|
1178
|
+
colors.push(...colorsFromStyleDeclaration(elementStyles));
|
|
1179
|
+
}
|
|
1180
|
+
});
|
|
1181
|
+
svgImage.querySelectorAll("style").forEach(element => {
|
|
1182
|
+
var _a;
|
|
1183
|
+
const cssRules = (_a = element.sheet) === null || _a === void 0 ? void 0 : _a.cssRules;
|
|
1184
|
+
if (cssRules === null || cssRules === void 0 ? void 0 : cssRules.length) {
|
|
1185
|
+
for (let i = 0; i < cssRules.length; i++) {
|
|
1186
|
+
// Checking if the style attribute exists
|
|
1187
|
+
const cssRule = cssRules[i]; // Replace type casting with type assertion
|
|
1188
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
1189
|
+
if (cssRule.style) {
|
|
1190
|
+
const cssRuleStyles = cssRule.style;
|
|
1191
|
+
colors.push(...colorsFromStyleDeclaration(cssRuleStyles));
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
});
|
|
1196
|
+
// Get fills
|
|
1197
|
+
svgImage.querySelectorAll("[fill]").forEach(element => {
|
|
1198
|
+
var _a;
|
|
1199
|
+
const color = (_a = element.attributes.getNamedItem("fill")) === null || _a === void 0 ? void 0 : _a.value;
|
|
1200
|
+
if (color) {
|
|
1201
|
+
colors.push(color);
|
|
1202
|
+
}
|
|
1203
|
+
});
|
|
1204
|
+
// Get stokes
|
|
1205
|
+
svgImage.querySelectorAll("[stroke]").forEach(element => {
|
|
1206
|
+
var _a;
|
|
1207
|
+
const color = (_a = element.attributes.getNamedItem("stroke")) === null || _a === void 0 ? void 0 : _a.value;
|
|
1208
|
+
if (color) {
|
|
1209
|
+
colors.push(color);
|
|
1210
|
+
}
|
|
1211
|
+
});
|
|
1212
|
+
// Get stop-color (for gradients)
|
|
1213
|
+
svgImage.querySelectorAll("[stop-color]").forEach(element => {
|
|
1214
|
+
var _a;
|
|
1215
|
+
const color = (_a = element.attributes.getNamedItem("stop-color")) === null || _a === void 0 ? void 0 : _a.value;
|
|
1216
|
+
if (color) {
|
|
1217
|
+
colors.push(color);
|
|
1218
|
+
}
|
|
1219
|
+
});
|
|
1220
|
+
return [...new Set(colors.map(color => rgb2hex(color)))];
|
|
1221
|
+
};
|
|
1222
|
+
/**
|
|
1223
|
+
* Converts a Base64 encoded SVG to a Scaled Base64 PNG.
|
|
1224
|
+
*
|
|
1225
|
+
* @param {string} base64EncodedSVG The Base64 encoded svg string to convert
|
|
1226
|
+
* @param {number} maxWidth The max width(px) of the output PNG
|
|
1227
|
+
* @param {number} maxHeight The max height(px) of the output PNG
|
|
1228
|
+
* @returns {Promise<string>} A Base64 encoded PNG, scaled to the given parameters
|
|
1229
|
+
*/
|
|
1230
|
+
const svgToPNG = async ({ base64EncodedSVG, maxWidth, maxHeight, idealArea, }) => {
|
|
1231
|
+
const [image, dimensions] = await Promise.all([
|
|
1232
|
+
preload(base64EncodedSVG),
|
|
1233
|
+
loadSVGDimensions(base64EncodedSVG),
|
|
1234
|
+
]);
|
|
1235
|
+
return toPNG({ image, dimensions, maxHeight, maxWidth, idealArea });
|
|
1236
|
+
};
|
|
1237
|
+
|
|
985
1238
|
/* eslint-disable local-rules/prefer-custom-object-keys */
|
|
986
1239
|
/* eslint-disable local-rules/prefer-custom-object-from-entries */
|
|
987
1240
|
/**
|
|
@@ -1065,7 +1318,9 @@ exports.alphabeticallySort = alphabeticallySort;
|
|
|
1065
1318
|
exports.arrayLengthCompare = arrayLengthCompare;
|
|
1066
1319
|
exports.arrayNotEmpty = arrayNotEmpty;
|
|
1067
1320
|
exports.booleanCompare = booleanCompare;
|
|
1321
|
+
exports.calculateImageScaleRatio = calculateImageScaleRatio;
|
|
1068
1322
|
exports.capitalize = capitalize;
|
|
1323
|
+
exports.colorsFromStyleDeclaration = colorsFromStyleDeclaration;
|
|
1069
1324
|
exports.convertBlobToBase64 = convertBlobToBase64;
|
|
1070
1325
|
exports.convertMetersToYards = convertMetersToYards;
|
|
1071
1326
|
exports.convertYardsToMeters = convertYardsToMeters;
|
|
@@ -1077,14 +1332,17 @@ exports.enumFromValue = enumFromValue;
|
|
|
1077
1332
|
exports.enumFromValueTypesafe = enumFromValueTypesafe;
|
|
1078
1333
|
exports.enumOrUndefinedFromValue = enumOrUndefinedFromValue;
|
|
1079
1334
|
exports.exhaustiveCheck = exhaustiveCheck;
|
|
1335
|
+
exports.fetchImageAsBase64 = fetchImageAsBase64;
|
|
1080
1336
|
exports.filterByMultiple = filterByMultiple;
|
|
1081
1337
|
exports.formatAddress = formatAddress;
|
|
1082
1338
|
exports.formatCoordinates = formatCoordinates;
|
|
1083
1339
|
exports.fuzzySearch = fuzzySearch;
|
|
1340
|
+
exports.getAllColors = getAllColors;
|
|
1084
1341
|
exports.getDifferenceBetweenDates = getDifferenceBetweenDates;
|
|
1085
1342
|
exports.getEndOfDay = getEndOfDay;
|
|
1086
1343
|
exports.getFirstLevelObjectPropertyDifferences = getFirstLevelObjectPropertyDifferences;
|
|
1087
1344
|
exports.getISOStringFromDate = getISOStringFromDate;
|
|
1345
|
+
exports.getMimeTypeFromDataURL = getMimeTypeFromDataURL;
|
|
1088
1346
|
exports.getResizedDimensions = getResizedDimensions;
|
|
1089
1347
|
exports.getStartOfDay = getStartOfDay;
|
|
1090
1348
|
exports.groupBy = groupBy;
|
|
@@ -1095,6 +1353,7 @@ exports.isArrayEqual = isArrayEqual;
|
|
|
1095
1353
|
exports.isSorted = isSorted;
|
|
1096
1354
|
exports.isUUID = isUUID;
|
|
1097
1355
|
exports.isValidImage = isValidImage;
|
|
1356
|
+
exports.loadSVGDimensions = loadSVGDimensions;
|
|
1098
1357
|
exports.nonNullable = nonNullable;
|
|
1099
1358
|
exports.numberCompare = numberCompare;
|
|
1100
1359
|
exports.numberCompareUnknownAfterHighest = numberCompareUnknownAfterHighest;
|
|
@@ -1104,17 +1363,21 @@ exports.objectFromEntries = objectFromEntries;
|
|
|
1104
1363
|
exports.objectKeys = objectKeys;
|
|
1105
1364
|
exports.objectValues = objectValues;
|
|
1106
1365
|
exports.pick = pick;
|
|
1366
|
+
exports.preload = preload;
|
|
1107
1367
|
exports.removeLeftPadding = removeLeftPadding;
|
|
1108
1368
|
exports.resizeBlob = resizeBlob;
|
|
1109
1369
|
exports.resizeImage = resizeImage;
|
|
1370
|
+
exports.rgb2hex = rgb2hex;
|
|
1110
1371
|
exports.size = size;
|
|
1111
1372
|
exports.stringCompare = stringCompare;
|
|
1112
1373
|
exports.stringCompareFromKey = stringCompareFromKey;
|
|
1113
1374
|
exports.stringNaturalCompare = stringNaturalCompare;
|
|
1114
1375
|
exports.stripHiddenCharacters = stripHiddenCharacters;
|
|
1376
|
+
exports.svgToPNG = svgToPNG;
|
|
1115
1377
|
exports.titleCase = titleCase;
|
|
1116
1378
|
exports.toID = toID;
|
|
1117
1379
|
exports.toIDs = toIDs;
|
|
1380
|
+
exports.toPNG = toPNG;
|
|
1118
1381
|
exports.toUUID = toUUID;
|
|
1119
1382
|
exports.trimIds = trimIds;
|
|
1120
1383
|
exports.trimPath = trimPath;
|
package/index.esm.js
CHANGED
|
@@ -529,6 +529,128 @@ const toID = (uuid) => Number(uuid.replace(/-/g, "").replace(/^0+/, ""));
|
|
|
529
529
|
*/
|
|
530
530
|
const toIDs = (ids) => { var _a; return (_a = ids === null || ids === void 0 ? void 0 : ids.filter(id => !isNaN(toID(id))).map(id => toID(id))) !== null && _a !== void 0 ? _a : []; };
|
|
531
531
|
|
|
532
|
+
/**
|
|
533
|
+
* Converts an HTMLImageElement to a PNG image.
|
|
534
|
+
*
|
|
535
|
+
* @param options - image options
|
|
536
|
+
* @param {HTMLImageElement} options.image - The image element to convert.
|
|
537
|
+
* @param {ImageDimensions} options.dimensions - The dimensions of the image.
|
|
538
|
+
* @param {number} options.maxHeight - The maximum height of the converted PNG image.
|
|
539
|
+
* @param {number} options.maxWidth - The maximum width of the converted PNG image.
|
|
540
|
+
* @param {number} options.idealArea - The ideal area of the converted PNG image.
|
|
541
|
+
* @returns The converted PNG image as a data URL.
|
|
542
|
+
* @throws Error if unable to get the canvas context.
|
|
543
|
+
*/
|
|
544
|
+
const toPNG = ({ image, dimensions, maxHeight, maxWidth, idealArea, }) => {
|
|
545
|
+
var _a, _b;
|
|
546
|
+
let width = (_a = dimensions === null || dimensions === void 0 ? void 0 : dimensions.width) !== null && _a !== void 0 ? _a : image.width;
|
|
547
|
+
let height = (_b = dimensions === null || dimensions === void 0 ? void 0 : dimensions.height) !== null && _b !== void 0 ? _b : image.height;
|
|
548
|
+
const scale = calculateImageScaleRatio(width, height, maxWidth, maxHeight, idealArea);
|
|
549
|
+
width = Math.round(width * scale);
|
|
550
|
+
height = Math.round(height * scale);
|
|
551
|
+
const canvas = document.createElement("canvas");
|
|
552
|
+
canvas.width = width;
|
|
553
|
+
canvas.height = height;
|
|
554
|
+
const canvasCtx = canvas.getContext("2d");
|
|
555
|
+
if (!canvasCtx) {
|
|
556
|
+
throw new Error("Unable to get canvas context");
|
|
557
|
+
}
|
|
558
|
+
canvasCtx.drawImage(image, 0, 0, width, height);
|
|
559
|
+
return canvas.toDataURL("image/png");
|
|
560
|
+
};
|
|
561
|
+
/**
|
|
562
|
+
* Calculates the scale ratio for an image to fit inside given bounds
|
|
563
|
+
* Result is based on the idealArea more info: https://nicksherman.com/size-by-area/
|
|
564
|
+
*
|
|
565
|
+
* @param {number} width The width of the image
|
|
566
|
+
* @param {number} height The height of the image
|
|
567
|
+
* @param {number} maxWidth The maxWidth of the image
|
|
568
|
+
* @param {number} maxHeight The maxHeight of the image
|
|
569
|
+
* @param {number} idealArea The ideal area for the image to cover (Read more above)
|
|
570
|
+
*/
|
|
571
|
+
const calculateImageScaleRatio = (width, height, maxWidth, maxHeight, idealArea) => {
|
|
572
|
+
// Get the max width based on idealArea
|
|
573
|
+
const maxImageWidth = Math.round(width * Math.sqrt(idealArea / (width * height)));
|
|
574
|
+
const ratio = maxImageWidth / width;
|
|
575
|
+
// If the area scaling is largen than the ares, scale to fix
|
|
576
|
+
if (width * ratio > maxWidth || height * ratio > maxHeight) {
|
|
577
|
+
const scaleX = maxWidth / width;
|
|
578
|
+
const scaleY = maxHeight / height;
|
|
579
|
+
const scale = scaleX < scaleY ? scaleX : scaleY; // Use the lowest scale to make it fit and scale uniformly
|
|
580
|
+
return scale;
|
|
581
|
+
}
|
|
582
|
+
return maxImageWidth / width;
|
|
583
|
+
};
|
|
584
|
+
/**
|
|
585
|
+
* Preloads a data url as an image.
|
|
586
|
+
*
|
|
587
|
+
* @param {string} dataUrl - The data url to preload.
|
|
588
|
+
* @returns {Promise<HTMLImageElement>} A promise that resolves to the preloaded image.
|
|
589
|
+
*/
|
|
590
|
+
const preload = (dataUrl) => {
|
|
591
|
+
return new Promise((resolve, reject) => {
|
|
592
|
+
const image = new Image();
|
|
593
|
+
image.src = dataUrl;
|
|
594
|
+
image.onload = () => resolve(image);
|
|
595
|
+
image.onerror = () => reject();
|
|
596
|
+
});
|
|
597
|
+
};
|
|
598
|
+
/**
|
|
599
|
+
* Extracts the type of an image from a data URL
|
|
600
|
+
*
|
|
601
|
+
* @param {string} dataURL the data URL to extract the type from.
|
|
602
|
+
* @returns {"jpg" | "png" | "svg"} the type of the encoded image.
|
|
603
|
+
* @throws
|
|
604
|
+
*/
|
|
605
|
+
const getMimeTypeFromDataURL = (dataURL) => {
|
|
606
|
+
const mimeToTypeMap = {
|
|
607
|
+
"image/svg+xml": "svg",
|
|
608
|
+
"image/jpeg": "jpeg",
|
|
609
|
+
"image/jpg": "jpeg",
|
|
610
|
+
"image/png": "png",
|
|
611
|
+
};
|
|
612
|
+
const matches = dataURL.match(/^data:(.*?);base64,/);
|
|
613
|
+
if (!matches || !matches[1]) {
|
|
614
|
+
throw new Error("Invalid file");
|
|
615
|
+
}
|
|
616
|
+
const type = mimeToTypeMap[matches[1]];
|
|
617
|
+
if (!type) {
|
|
618
|
+
throw new Error("File is not a supported image");
|
|
619
|
+
}
|
|
620
|
+
return type;
|
|
621
|
+
};
|
|
622
|
+
/**
|
|
623
|
+
* Fetches and image from a URL and converts it to a base64 encoded data url
|
|
624
|
+
*
|
|
625
|
+
* @param {string} url - url pointing to an image resource
|
|
626
|
+
* @returns {Promise<string>} base64 encoded data url.
|
|
627
|
+
* @throws
|
|
628
|
+
*/
|
|
629
|
+
const fetchImageAsBase64 = async (url) => {
|
|
630
|
+
// Fetch the image from the URL
|
|
631
|
+
const response = await fetch(url);
|
|
632
|
+
if (!response.ok) {
|
|
633
|
+
throw new Error(`Failed to fetch image. Status: ${response.status}`);
|
|
634
|
+
}
|
|
635
|
+
// Convert the response to a Blob
|
|
636
|
+
const blob = await response.blob();
|
|
637
|
+
// Convert the Blob to a Base64 string
|
|
638
|
+
return new Promise((resolve, reject) => {
|
|
639
|
+
const reader = new FileReader();
|
|
640
|
+
reader.onloadend = () => {
|
|
641
|
+
if (typeof reader.result === "string") {
|
|
642
|
+
const base64String = reader.result;
|
|
643
|
+
resolve(base64String);
|
|
644
|
+
}
|
|
645
|
+
reject(new Error("Failed to convert Blob to Base64."));
|
|
646
|
+
};
|
|
647
|
+
reader.onerror = () => {
|
|
648
|
+
reject(new Error("Failed to convert Blob to Base64."));
|
|
649
|
+
};
|
|
650
|
+
reader.readAsDataURL(blob);
|
|
651
|
+
});
|
|
652
|
+
};
|
|
653
|
+
|
|
532
654
|
/**
|
|
533
655
|
* Deletes all undefined keys from an object.
|
|
534
656
|
*
|
|
@@ -980,6 +1102,137 @@ const stripHiddenCharacters = (input) => {
|
|
|
980
1102
|
return input.replace(hiddenRegex, "");
|
|
981
1103
|
};
|
|
982
1104
|
|
|
1105
|
+
/**
|
|
1106
|
+
* Convert the given value to HEX
|
|
1107
|
+
* If the value is not rgb, returns back the same value
|
|
1108
|
+
*/
|
|
1109
|
+
const rgb2hex = (rgb) => {
|
|
1110
|
+
var _a;
|
|
1111
|
+
const hexColor = (_a = rgb
|
|
1112
|
+
.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/)) === null || _a === void 0 ? void 0 : _a.slice(1).map(n => parseInt(n, 10).toString(16).padStart(2, "0")).join("");
|
|
1113
|
+
return hexColor ? `#${hexColor}` : rgb;
|
|
1114
|
+
};
|
|
1115
|
+
/**
|
|
1116
|
+
* Returns all svg related colors from a CSSStyleDeclaration
|
|
1117
|
+
*/
|
|
1118
|
+
const colorsFromStyleDeclaration = (styles) => {
|
|
1119
|
+
return [styles.fill, styles.stroke, styles.stopColor].filter(color => color);
|
|
1120
|
+
};
|
|
1121
|
+
/**
|
|
1122
|
+
* Loads the dimensions of an SVG string.
|
|
1123
|
+
*
|
|
1124
|
+
* @param base64EncodedSVG The SVG string to load dimensions from.
|
|
1125
|
+
* @returns A promise that resolves to an object containing the width and height of the SVG.
|
|
1126
|
+
* @throws {Error} If the SVG string is invalid or the dimensions cannot be determined.
|
|
1127
|
+
*/
|
|
1128
|
+
const loadSVGDimensions = (base64EncodedSVG) => {
|
|
1129
|
+
return new Promise((resolve, reject) => {
|
|
1130
|
+
// Create a temporary SVG element
|
|
1131
|
+
const svgAsString = atob(base64EncodedSVG.replace("data:image/svg+xml;base64,", ""));
|
|
1132
|
+
const parser = new DOMParser();
|
|
1133
|
+
const svgDoc = parser.parseFromString(svgAsString, "image/svg+xml");
|
|
1134
|
+
const svgElement = svgDoc.documentElement;
|
|
1135
|
+
// Check if parsing was successful
|
|
1136
|
+
if (svgElement.nodeName === "parsererror") {
|
|
1137
|
+
reject(new Error("Invalid SVG string"));
|
|
1138
|
+
return;
|
|
1139
|
+
}
|
|
1140
|
+
// Get dimensions
|
|
1141
|
+
const width = svgElement.width.baseVal.value;
|
|
1142
|
+
const height = svgElement.height.baseVal.value;
|
|
1143
|
+
// If width and height are not set explicitly, try to get from viewBox
|
|
1144
|
+
if (!width || !height) {
|
|
1145
|
+
const viewBox = svgElement.viewBox.baseVal;
|
|
1146
|
+
if (viewBox.width && viewBox.height) {
|
|
1147
|
+
resolve({
|
|
1148
|
+
width: viewBox.width,
|
|
1149
|
+
height: viewBox.height,
|
|
1150
|
+
});
|
|
1151
|
+
}
|
|
1152
|
+
else {
|
|
1153
|
+
reject(new Error("Unable to determine SVG dimensions"));
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
else {
|
|
1157
|
+
resolve({
|
|
1158
|
+
width,
|
|
1159
|
+
height,
|
|
1160
|
+
});
|
|
1161
|
+
}
|
|
1162
|
+
});
|
|
1163
|
+
};
|
|
1164
|
+
/**
|
|
1165
|
+
* Returns an array of all colors used in an svg
|
|
1166
|
+
*/
|
|
1167
|
+
const getAllColors = (base64EncodedSVG) => {
|
|
1168
|
+
const svgAsString = atob(base64EncodedSVG.replace("data:image/svg+xml;base64,", ""));
|
|
1169
|
+
const parser = new DOMParser();
|
|
1170
|
+
const svgImage = parser.parseFromString(svgAsString, "image/svg+xml");
|
|
1171
|
+
const colors = [];
|
|
1172
|
+
svgImage.querySelectorAll("[style]").forEach(element => {
|
|
1173
|
+
// Not all Elements have the style prop, this seems like a safe way to check and assert
|
|
1174
|
+
if (element instanceof HTMLElement) {
|
|
1175
|
+
const elementStyles = element.style;
|
|
1176
|
+
colors.push(...colorsFromStyleDeclaration(elementStyles));
|
|
1177
|
+
}
|
|
1178
|
+
});
|
|
1179
|
+
svgImage.querySelectorAll("style").forEach(element => {
|
|
1180
|
+
var _a;
|
|
1181
|
+
const cssRules = (_a = element.sheet) === null || _a === void 0 ? void 0 : _a.cssRules;
|
|
1182
|
+
if (cssRules === null || cssRules === void 0 ? void 0 : cssRules.length) {
|
|
1183
|
+
for (let i = 0; i < cssRules.length; i++) {
|
|
1184
|
+
// Checking if the style attribute exists
|
|
1185
|
+
const cssRule = cssRules[i]; // Replace type casting with type assertion
|
|
1186
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
1187
|
+
if (cssRule.style) {
|
|
1188
|
+
const cssRuleStyles = cssRule.style;
|
|
1189
|
+
colors.push(...colorsFromStyleDeclaration(cssRuleStyles));
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
});
|
|
1194
|
+
// Get fills
|
|
1195
|
+
svgImage.querySelectorAll("[fill]").forEach(element => {
|
|
1196
|
+
var _a;
|
|
1197
|
+
const color = (_a = element.attributes.getNamedItem("fill")) === null || _a === void 0 ? void 0 : _a.value;
|
|
1198
|
+
if (color) {
|
|
1199
|
+
colors.push(color);
|
|
1200
|
+
}
|
|
1201
|
+
});
|
|
1202
|
+
// Get stokes
|
|
1203
|
+
svgImage.querySelectorAll("[stroke]").forEach(element => {
|
|
1204
|
+
var _a;
|
|
1205
|
+
const color = (_a = element.attributes.getNamedItem("stroke")) === null || _a === void 0 ? void 0 : _a.value;
|
|
1206
|
+
if (color) {
|
|
1207
|
+
colors.push(color);
|
|
1208
|
+
}
|
|
1209
|
+
});
|
|
1210
|
+
// Get stop-color (for gradients)
|
|
1211
|
+
svgImage.querySelectorAll("[stop-color]").forEach(element => {
|
|
1212
|
+
var _a;
|
|
1213
|
+
const color = (_a = element.attributes.getNamedItem("stop-color")) === null || _a === void 0 ? void 0 : _a.value;
|
|
1214
|
+
if (color) {
|
|
1215
|
+
colors.push(color);
|
|
1216
|
+
}
|
|
1217
|
+
});
|
|
1218
|
+
return [...new Set(colors.map(color => rgb2hex(color)))];
|
|
1219
|
+
};
|
|
1220
|
+
/**
|
|
1221
|
+
* Converts a Base64 encoded SVG to a Scaled Base64 PNG.
|
|
1222
|
+
*
|
|
1223
|
+
* @param {string} base64EncodedSVG The Base64 encoded svg string to convert
|
|
1224
|
+
* @param {number} maxWidth The max width(px) of the output PNG
|
|
1225
|
+
* @param {number} maxHeight The max height(px) of the output PNG
|
|
1226
|
+
* @returns {Promise<string>} A Base64 encoded PNG, scaled to the given parameters
|
|
1227
|
+
*/
|
|
1228
|
+
const svgToPNG = async ({ base64EncodedSVG, maxWidth, maxHeight, idealArea, }) => {
|
|
1229
|
+
const [image, dimensions] = await Promise.all([
|
|
1230
|
+
preload(base64EncodedSVG),
|
|
1231
|
+
loadSVGDimensions(base64EncodedSVG),
|
|
1232
|
+
]);
|
|
1233
|
+
return toPNG({ image, dimensions, maxHeight, maxWidth, idealArea });
|
|
1234
|
+
};
|
|
1235
|
+
|
|
983
1236
|
/* eslint-disable local-rules/prefer-custom-object-keys */
|
|
984
1237
|
/* eslint-disable local-rules/prefer-custom-object-from-entries */
|
|
985
1238
|
/**
|
|
@@ -1057,4 +1310,4 @@ const uuidv4 = () => {
|
|
|
1057
1310
|
*/
|
|
1058
1311
|
const uuidv5 = (name, namespace) => v5(name, namespace);
|
|
1059
1312
|
|
|
1060
|
-
export { DateTimeFormat, HoursAndMinutesFormat, align, alphabeticallySort, arrayLengthCompare, arrayNotEmpty, booleanCompare, capitalize, convertBlobToBase64, convertMetersToYards, convertYardsToMeters, dateCompare, deleteUndefinedKeys, difference, doNothing, enumFromValue, enumFromValueTypesafe, enumOrUndefinedFromValue, exhaustiveCheck, filterByMultiple, formatAddress, formatCoordinates, fuzzySearch, getDifferenceBetweenDates, getEndOfDay, getFirstLevelObjectPropertyDifferences, getISOStringFromDate, getResizedDimensions, getStartOfDay, groupBy, groupTinyDataToOthers, hourIntervals, intersection, isArrayEqual, isSorted, isUUID, isValidImage, nonNullable, numberCompare, numberCompareUnknownAfterHighest, objNotEmpty, objectEntries, objectFromEntries, objectKeys, objectValues, pick, removeLeftPadding, resizeBlob, resizeImage, size, stringCompare, stringCompareFromKey, stringNaturalCompare, stripHiddenCharacters, titleCase, toID, toIDs, toUUID, trimIds, trimPath, truthy, unionArraysByKey, uuidv3, uuidv4, uuidv5 };
|
|
1313
|
+
export { DateTimeFormat, HoursAndMinutesFormat, align, alphabeticallySort, arrayLengthCompare, arrayNotEmpty, booleanCompare, calculateImageScaleRatio, capitalize, colorsFromStyleDeclaration, convertBlobToBase64, convertMetersToYards, convertYardsToMeters, dateCompare, deleteUndefinedKeys, difference, doNothing, enumFromValue, enumFromValueTypesafe, enumOrUndefinedFromValue, exhaustiveCheck, fetchImageAsBase64, filterByMultiple, formatAddress, formatCoordinates, fuzzySearch, getAllColors, getDifferenceBetweenDates, getEndOfDay, getFirstLevelObjectPropertyDifferences, getISOStringFromDate, getMimeTypeFromDataURL, getResizedDimensions, getStartOfDay, groupBy, groupTinyDataToOthers, hourIntervals, intersection, isArrayEqual, isSorted, isUUID, isValidImage, loadSVGDimensions, nonNullable, numberCompare, numberCompareUnknownAfterHighest, objNotEmpty, objectEntries, objectFromEntries, objectKeys, objectValues, pick, preload, removeLeftPadding, resizeBlob, resizeImage, rgb2hex, size, stringCompare, stringCompareFromKey, stringNaturalCompare, stripHiddenCharacters, svgToPNG, titleCase, toID, toIDs, toPNG, toUUID, trimIds, trimPath, truthy, unionArraysByKey, uuidv3, uuidv4, uuidv5 };
|
package/package.json
CHANGED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export interface ImageDimensions {
|
|
2
|
+
width: number;
|
|
3
|
+
height: number;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Converts an HTMLImageElement to a PNG image.
|
|
7
|
+
*
|
|
8
|
+
* @param options - image options
|
|
9
|
+
* @param {HTMLImageElement} options.image - The image element to convert.
|
|
10
|
+
* @param {ImageDimensions} options.dimensions - The dimensions of the image.
|
|
11
|
+
* @param {number} options.maxHeight - The maximum height of the converted PNG image.
|
|
12
|
+
* @param {number} options.maxWidth - The maximum width of the converted PNG image.
|
|
13
|
+
* @param {number} options.idealArea - The ideal area of the converted PNG image.
|
|
14
|
+
* @returns The converted PNG image as a data URL.
|
|
15
|
+
* @throws Error if unable to get the canvas context.
|
|
16
|
+
*/
|
|
17
|
+
export declare const toPNG: ({ image, dimensions, maxHeight, maxWidth, idealArea, }: {
|
|
18
|
+
image: HTMLImageElement;
|
|
19
|
+
dimensions?: ImageDimensions;
|
|
20
|
+
maxHeight: number;
|
|
21
|
+
maxWidth: number;
|
|
22
|
+
idealArea: number;
|
|
23
|
+
}) => string;
|
|
24
|
+
/**
|
|
25
|
+
* Calculates the scale ratio for an image to fit inside given bounds
|
|
26
|
+
* Result is based on the idealArea more info: https://nicksherman.com/size-by-area/
|
|
27
|
+
*
|
|
28
|
+
* @param {number} width The width of the image
|
|
29
|
+
* @param {number} height The height of the image
|
|
30
|
+
* @param {number} maxWidth The maxWidth of the image
|
|
31
|
+
* @param {number} maxHeight The maxHeight of the image
|
|
32
|
+
* @param {number} idealArea The ideal area for the image to cover (Read more above)
|
|
33
|
+
*/
|
|
34
|
+
export declare const calculateImageScaleRatio: (width: number, height: number, maxWidth: number, maxHeight: number, idealArea: number) => number;
|
|
35
|
+
/**
|
|
36
|
+
* Preloads a data url as an image.
|
|
37
|
+
*
|
|
38
|
+
* @param {string} dataUrl - The data url to preload.
|
|
39
|
+
* @returns {Promise<HTMLImageElement>} A promise that resolves to the preloaded image.
|
|
40
|
+
*/
|
|
41
|
+
export declare const preload: (dataUrl: string) => Promise<HTMLImageElement>;
|
|
42
|
+
type ImageType = "jpeg" | "png" | "svg";
|
|
43
|
+
/**
|
|
44
|
+
* Extracts the type of an image from a data URL
|
|
45
|
+
*
|
|
46
|
+
* @param {string} dataURL the data URL to extract the type from.
|
|
47
|
+
* @returns {"jpg" | "png" | "svg"} the type of the encoded image.
|
|
48
|
+
* @throws
|
|
49
|
+
*/
|
|
50
|
+
export declare const getMimeTypeFromDataURL: (dataURL: string) => ImageType;
|
|
51
|
+
/**
|
|
52
|
+
* Fetches and image from a URL and converts it to a base64 encoded data url
|
|
53
|
+
*
|
|
54
|
+
* @param {string} url - url pointing to an image resource
|
|
55
|
+
* @returns {Promise<string>} base64 encoded data url.
|
|
56
|
+
* @throws
|
|
57
|
+
*/
|
|
58
|
+
export declare const fetchImageAsBase64: (url: string) => Promise<string>;
|
|
59
|
+
export {};
|
package/src/index.d.ts
CHANGED
|
@@ -12,12 +12,14 @@ export * from "./filter";
|
|
|
12
12
|
export * from "./groupBy/groupBy";
|
|
13
13
|
export * from "./GroupingUtility";
|
|
14
14
|
export * from "./idUtils";
|
|
15
|
+
export * from "./imageTools";
|
|
15
16
|
export * from "./Maybe";
|
|
16
17
|
export * from "./objectUtils";
|
|
17
18
|
export * from "./pathUtils";
|
|
18
19
|
export * from "./pictureUtils/pictureUtils";
|
|
19
20
|
export * from "./sorting/sorting";
|
|
20
21
|
export * from "./stringUtils";
|
|
22
|
+
export * from "./svgTools";
|
|
21
23
|
export * from "./translationUtils";
|
|
22
24
|
export * from "./typeUtils";
|
|
23
25
|
export * from "./UnitOfMeasurementConverter";
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type ImageDimensions } from "./imageTools";
|
|
2
|
+
/**
|
|
3
|
+
* Convert the given value to HEX
|
|
4
|
+
* If the value is not rgb, returns back the same value
|
|
5
|
+
*/
|
|
6
|
+
export declare const rgb2hex: (rgb: string) => string;
|
|
7
|
+
/**
|
|
8
|
+
* Returns all svg related colors from a CSSStyleDeclaration
|
|
9
|
+
*/
|
|
10
|
+
export declare const colorsFromStyleDeclaration: (styles: CSSStyleDeclaration) => string[];
|
|
11
|
+
/**
|
|
12
|
+
* Loads the dimensions of an SVG string.
|
|
13
|
+
*
|
|
14
|
+
* @param base64EncodedSVG The SVG string to load dimensions from.
|
|
15
|
+
* @returns A promise that resolves to an object containing the width and height of the SVG.
|
|
16
|
+
* @throws {Error} If the SVG string is invalid or the dimensions cannot be determined.
|
|
17
|
+
*/
|
|
18
|
+
export declare const loadSVGDimensions: (base64EncodedSVG: string) => Promise<ImageDimensions>;
|
|
19
|
+
/**
|
|
20
|
+
* Returns an array of all colors used in an svg
|
|
21
|
+
*/
|
|
22
|
+
export declare const getAllColors: (base64EncodedSVG: string) => string[];
|
|
23
|
+
/**
|
|
24
|
+
* Converts a Base64 encoded SVG to a Scaled Base64 PNG.
|
|
25
|
+
*
|
|
26
|
+
* @param {string} base64EncodedSVG The Base64 encoded svg string to convert
|
|
27
|
+
* @param {number} maxWidth The max width(px) of the output PNG
|
|
28
|
+
* @param {number} maxHeight The max height(px) of the output PNG
|
|
29
|
+
* @returns {Promise<string>} A Base64 encoded PNG, scaled to the given parameters
|
|
30
|
+
*/
|
|
31
|
+
export declare const svgToPNG: ({ base64EncodedSVG, maxWidth, maxHeight, idealArea, }: {
|
|
32
|
+
base64EncodedSVG: string;
|
|
33
|
+
maxWidth: number;
|
|
34
|
+
maxHeight: number;
|
|
35
|
+
idealArea: number;
|
|
36
|
+
}) => Promise<string>;
|