html2canvas-pro 2.2.3 → 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.3 <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
  */
@@ -8443,20 +8443,124 @@ var BlendEffect = class {
8443
8443
  this.compositeOperation = MIX_BLEND_MODE_TO_COMPOSITE[mixBlendMode];
8444
8444
  }
8445
8445
  };
8446
- var FilterEffect = class {
8446
+ var FilterEffect = class FilterEffect {
8447
8447
  constructor(filterString) {
8448
8448
  this.type = 5;
8449
8449
  this.target = 6;
8450
- const dropShadowMatch = filterString.match(/drop-shadow\(\s*([\d.-]+)(px)?\s+([\d.-]+)(px)?\s+([\d.-]+)(px)?\s+(.+?)\s*\)/);
8451
- if (dropShadowMatch) {
8452
- this.shadow = {
8453
- offsetX: parseFloat(dropShadowMatch[1]),
8454
- offsetY: parseFloat(dropShadowMatch[3]),
8455
- blur: parseFloat(dropShadowMatch[5]),
8456
- color: dropShadowMatch[7].trim()
8457
- };
8458
- this.safeFilterString = filterString.replace(/drop-shadow\([^)]+\)\s*/g, "").trim();
8459
- } 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);
8460
8564
  }
8461
8565
  };
8462
8566
  const isTransformEffect = (effect) => effect.type === 0;