html2canvas-pro 2.2.2 → 2.2.4

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.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * html2canvas-pro 2.2.2 <https://yorickshan.github.io/html2canvas-pro/>
2
+ * html2canvas-pro 2.2.4 <https://yorickshan.github.io/html2canvas-pro/>
3
3
  * Copyright (c) 2024-present yorickshan and html2canvas-pro contributors
4
4
  * Released under MIT License
5
5
  */
@@ -3896,6 +3896,7 @@ const backgroundImage = {
3896
3896
  initialValue: "none",
3897
3897
  type: 1,
3898
3898
  prefix: false,
3899
+ skipCache: true,
3899
3900
  parse: (context, tokens) => {
3900
3901
  if (tokens.length === 0) return [];
3901
3902
  const first = tokens[0];
@@ -4338,6 +4339,7 @@ const listStyleImage = {
4338
4339
  initialValue: "none",
4339
4340
  type: 0,
4340
4341
  prefix: false,
4342
+ skipCache: true,
4341
4343
  parse: (context, token) => {
4342
4344
  if (token.type === 20 && token.value === "none") return null;
4343
4345
  return image.parse(context, token);
@@ -5358,6 +5360,7 @@ const borderImageSource = {
5358
5360
  initialValue: "none",
5359
5361
  prefix: false,
5360
5362
  type: 1,
5363
+ skipCache: true,
5361
5364
  parse: (context, tokens) => {
5362
5365
  if (tokens.length === 0) return null;
5363
5366
  const filtered = tokens.filter((t) => nonFunctionArgSeparator(t) && isSupportedImage(t));
@@ -6294,7 +6297,7 @@ const parse = (context, descriptor, style) => {
6294
6297
  valueCache = /* @__PURE__ */ new Map();
6295
6298
  parseCache.set(descriptor, valueCache);
6296
6299
  }
6297
- if (!(descriptor.type === 3 && descriptor.format === "image")) {
6300
+ if (!(descriptor.skipCache || descriptor.type === 3 && descriptor.format === "image")) {
6298
6301
  if (valueCache.size >= 200) {
6299
6302
  const oldestKey = valueCache.keys().next().value;
6300
6303
  valueCache.delete(oldestKey);
@@ -8440,20 +8443,124 @@ var BlendEffect = class {
8440
8443
  this.compositeOperation = MIX_BLEND_MODE_TO_COMPOSITE[mixBlendMode];
8441
8444
  }
8442
8445
  };
8443
- var FilterEffect = class {
8446
+ var FilterEffect = class FilterEffect {
8444
8447
  constructor(filterString) {
8445
8448
  this.type = 5;
8446
8449
  this.target = 6;
8447
- const dropShadowMatch = filterString.match(/drop-shadow\(\s*([\d.-]+)(px)?\s+([\d.-]+)(px)?\s+([\d.-]+)(px)?\s+(.+?)\s*\)/);
8448
- if (dropShadowMatch) {
8449
- this.shadow = {
8450
- offsetX: parseFloat(dropShadowMatch[1]),
8451
- offsetY: parseFloat(dropShadowMatch[3]),
8452
- blur: parseFloat(dropShadowMatch[5]),
8453
- color: dropShadowMatch[7].trim()
8454
- };
8455
- this.safeFilterString = filterString.replace(/drop-shadow\([^)]+\)\s*/g, "").trim();
8456
- } else this.safeFilterString = filterString;
8450
+ const parsed = FilterEffect.parseDropShadow(filterString);
8451
+ this.shadow = parsed.shadow;
8452
+ this.safeFilterString = parsed.safeFilterString;
8453
+ }
8454
+ /**
8455
+ * Parse a CSS filter string, extracting drop-shadow() parameters for
8456
+ * rendering via ctx.shadow* (which avoids canvas taint), and producing
8457
+ * a safe filter string with all drop-shadow() calls removed.
8458
+ *
8459
+ * Uses paren-depth tracking to correctly handle nested function
8460
+ * arguments — e.g. rgba() / hsla() inside drop-shadow() — which
8461
+ * the previous regex-based approach ([^)]+) could not handle.
8462
+ *
8463
+ * Per CSS spec the shadow value is: <color>? && <length>{2,3}
8464
+ * (components can appear in any order).
8465
+ */
8466
+ static parseDropShadow(filterString) {
8467
+ if (!filterString) return { safeFilterString: "" };
8468
+ const matches = FilterEffect.findDropShadows(filterString);
8469
+ if (matches.length === 0) return { safeFilterString: filterString };
8470
+ const shadow = FilterEffect.parseDropShadowBody(matches[0].body);
8471
+ let result = "";
8472
+ let lastEnd = 0;
8473
+ for (const m of matches) {
8474
+ result += filterString.slice(lastEnd, m.start);
8475
+ lastEnd = m.end;
8476
+ }
8477
+ result += filterString.slice(lastEnd);
8478
+ return {
8479
+ shadow,
8480
+ safeFilterString: result.replace(/\s+/g, " ").trim()
8481
+ };
8482
+ }
8483
+ /**
8484
+ * Find all drop-shadow() function calls in a CSS filter string.
8485
+ * Uses paren-depth tracking to correctly skip over nested
8486
+ * parentheses (e.g. rgba(0, 0, 0, 0.15)).
8487
+ */
8488
+ static findDropShadows(str) {
8489
+ const results = [];
8490
+ const re = /drop-shadow\(/gi;
8491
+ let m;
8492
+ while ((m = re.exec(str)) !== null) {
8493
+ const openParen = m.index + 12 - 1;
8494
+ const start = m.index;
8495
+ let depth = 1;
8496
+ let pos = openParen + 1;
8497
+ while (pos < str.length && depth > 0) {
8498
+ const ch = str[pos];
8499
+ if (ch === "(") depth++;
8500
+ else if (ch === ")") depth--;
8501
+ pos++;
8502
+ }
8503
+ if (depth !== 0) break;
8504
+ const body = str.slice(openParen + 1, pos - 1);
8505
+ results.push({
8506
+ body,
8507
+ start,
8508
+ end: pos
8509
+ });
8510
+ }
8511
+ return results;
8512
+ }
8513
+ /**
8514
+ * Parse the body of a single drop-shadow() function.
8515
+ * Body format (order-independent): <offsetX> <offsetY> [<blur>] [<color>?]
8516
+ */
8517
+ static parseDropShadowBody(body) {
8518
+ const tokens = FilterEffect.tokenizeFilterArgs(body.trim());
8519
+ const lengths = [];
8520
+ let color;
8521
+ for (const token of tokens) if (FilterEffect.isCSSLength(token)) lengths.push(parseFloat(token));
8522
+ else if (token) color = token;
8523
+ if (lengths.length < 2 || lengths.length > 3) return;
8524
+ return {
8525
+ offsetX: lengths[0],
8526
+ offsetY: lengths[1],
8527
+ blur: lengths[2] ?? 0,
8528
+ color: color ?? "rgba(0,0,0,1)"
8529
+ };
8530
+ }
8531
+ /**
8532
+ * Split whitespace-separated tokens while keeping parenthesised
8533
+ * expressions intact.
8534
+ *
8535
+ * e.g. "rgba(0, 0, 0, 0.15) 0px 1px 2px"
8536
+ * → ["rgba(0, 0, 0, 0.15)", "0px", "1px", "2px"]
8537
+ */
8538
+ static tokenizeFilterArgs(str) {
8539
+ const tokens = [];
8540
+ let current = "";
8541
+ let depth = 0;
8542
+ for (let i = 0; i < str.length; i++) {
8543
+ const ch = str[i];
8544
+ if (ch === "(") {
8545
+ depth++;
8546
+ current += ch;
8547
+ } else if (ch === ")") {
8548
+ depth--;
8549
+ current += ch;
8550
+ } else if (/\s/.test(ch)) {
8551
+ if (depth > 0) current += ch;
8552
+ else if (current) {
8553
+ tokens.push(current);
8554
+ current = "";
8555
+ }
8556
+ } else current += ch;
8557
+ }
8558
+ if (current) tokens.push(current);
8559
+ return tokens;
8560
+ }
8561
+ /** True when token looks like a CSS length: number with optional unit. */
8562
+ static isCSSLength(token) {
8563
+ return /^-?[\d.]+(px|em|rem|pt|cm|mm|in|pc|ex|ch|vw|vh|vmin|vmax|%)?$/i.test(token);
8457
8564
  }
8458
8565
  };
8459
8566
  const isTransformEffect = (effect) => effect.type === 0;