@emasoft/svg-matrix 1.1.0 → 1.2.0
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/bin/svg-matrix.js +7 -6
- package/bin/svgm.js +109 -40
- package/dist/svg-matrix.min.js +7 -7
- package/dist/svg-toolbox.min.js +148 -228
- package/dist/svgm.min.js +152 -232
- package/dist/version.json +5 -5
- package/package.json +1 -1
- package/scripts/postinstall.js +72 -41
- package/scripts/test-postinstall.js +18 -16
- package/scripts/version-sync.js +78 -60
- package/src/animation-optimization.js +190 -98
- package/src/animation-references.js +11 -3
- package/src/arc-length.js +23 -20
- package/src/bezier-analysis.js +9 -13
- package/src/bezier-intersections.js +18 -4
- package/src/browser-verify.js +35 -8
- package/src/clip-path-resolver.js +285 -114
- package/src/convert-path-data.js +20 -8
- package/src/css-specificity.js +33 -9
- package/src/douglas-peucker.js +272 -141
- package/src/geometry-to-path.js +79 -22
- package/src/gjk-collision.js +287 -126
- package/src/index.js +56 -21
- package/src/inkscape-support.js +122 -101
- package/src/logger.js +43 -27
- package/src/marker-resolver.js +201 -121
- package/src/mask-resolver.js +231 -98
- package/src/matrix.js +9 -5
- package/src/mesh-gradient.js +22 -14
- package/src/off-canvas-detection.js +53 -17
- package/src/path-optimization.js +356 -171
- package/src/path-simplification.js +671 -256
- package/src/pattern-resolver.js +1 -3
- package/src/polygon-clip.js +396 -78
- package/src/svg-boolean-ops.js +90 -23
- package/src/svg-collections.js +1546 -667
- package/src/svg-flatten.js +152 -38
- package/src/svg-matrix-lib.js +2 -2
- package/src/svg-parser.js +5 -1
- package/src/svg-rendering-context.js +3 -1
- package/src/svg-toolbox-lib.js +2 -2
- package/src/svg-toolbox.js +99 -457
- package/src/svg-validation-data.js +513 -345
- package/src/svg2-polyfills.js +156 -93
- package/src/svgm-lib.js +8 -4
- package/src/transform-optimization.js +168 -51
- package/src/transforms2d.js +73 -40
- package/src/transforms3d.js +34 -27
- package/src/use-symbol-resolver.js +175 -76
- package/src/vector.js +80 -44
- package/src/vendor/inkscape-hatch-polyfill.js +143 -108
- package/src/vendor/inkscape-hatch-polyfill.min.js +291 -1
- package/src/vendor/inkscape-mesh-polyfill.js +953 -766
- package/src/vendor/inkscape-mesh-polyfill.min.js +896 -1
- package/src/verification.js +3 -4
package/src/svg-toolbox.js
CHANGED
|
@@ -648,8 +648,10 @@ const resetLevenshteinCache = () => levenshteinCache.clear();
|
|
|
648
648
|
*/
|
|
649
649
|
const findClosestMatch = (name, validSet, maxDistance = 2) => {
|
|
650
650
|
if (!name || typeof name !== "string") return null;
|
|
651
|
-
if (!validSet || !(validSet instanceof Set) || validSet.size === 0)
|
|
652
|
-
|
|
651
|
+
if (!validSet || !(validSet instanceof Set) || validSet.size === 0)
|
|
652
|
+
return null;
|
|
653
|
+
const validMaxDistance =
|
|
654
|
+
typeof maxDistance !== "number" || maxDistance < 0 ? 2 : maxDistance;
|
|
653
655
|
const lower = name.toLowerCase();
|
|
654
656
|
let closest = null;
|
|
655
657
|
let minDist = validMaxDistance + 1;
|
|
@@ -1345,7 +1347,9 @@ export const cleanupIds = createOperation((doc, options = {}) => {
|
|
|
1345
1347
|
try {
|
|
1346
1348
|
return decodeURI(id);
|
|
1347
1349
|
} catch (e) {
|
|
1348
|
-
//
|
|
1350
|
+
// LEGITIMATE FALLBACK: Malformed percent-encoding (e.g., "%zz") should not crash.
|
|
1351
|
+
// Return original ID which may still be valid as a literal string reference.
|
|
1352
|
+
// Real-world SVGs often contain malformed IDs that work because they're used literally.
|
|
1349
1353
|
if (process.env.DEBUG) console.warn(`[svg-toolbox] ${e.message}`);
|
|
1350
1354
|
return id;
|
|
1351
1355
|
}
|
|
@@ -9667,7 +9671,9 @@ export const generateFullCompatibilityMatrix = createOperation(
|
|
|
9667
9671
|
const docUrl = new URL(doc.documentURI || doc.baseURI || "");
|
|
9668
9672
|
isCrossOrigin = imgUrl.origin !== docUrl.origin;
|
|
9669
9673
|
} catch (e) {
|
|
9670
|
-
//
|
|
9674
|
+
// LEGITIMATE FALLBACK: URL parsing may fail for relative paths or malformed URLs.
|
|
9675
|
+
// Defaulting to cross-origin=true is the SECURE choice (more restrictive).
|
|
9676
|
+
// This is feature detection behavior, not error-hiding.
|
|
9671
9677
|
if (process.env.DEBUG) console.warn(`[svg-toolbox] ${e.message}`);
|
|
9672
9678
|
isCrossOrigin = true;
|
|
9673
9679
|
}
|
|
@@ -11683,374 +11689,6 @@ export function printHierarchicalMatrix(matrixData, options = {}) {
|
|
|
11683
11689
|
return matrixData;
|
|
11684
11690
|
}
|
|
11685
11691
|
|
|
11686
|
-
/**
|
|
11687
|
-
* Generate an SVG visualization of the compatibility matrix (LEGACY - simple version).
|
|
11688
|
-
* @deprecated Use generateDetailedCompatibilityMatrixSVG for full hierarchical output
|
|
11689
|
-
*/
|
|
11690
|
-
export function generateCompatibilityMatrixSVG_legacy(
|
|
11691
|
-
matrixData,
|
|
11692
|
-
options = {},
|
|
11693
|
-
) {
|
|
11694
|
-
const cellWidth = options.cellWidth || 60;
|
|
11695
|
-
const cellHeight = options.cellHeight || 45;
|
|
11696
|
-
const fontFamily =
|
|
11697
|
-
options.fontFamily || "system-ui, -apple-system, sans-serif";
|
|
11698
|
-
const interactive = options.interactive !== false;
|
|
11699
|
-
|
|
11700
|
-
// Axis definitions (same as printHierarchicalMatrix)
|
|
11701
|
-
const COL_L1_SYNTAX = [
|
|
11702
|
-
{ id: "direct", label: "DIRECT", desc: "SVG file" },
|
|
11703
|
-
{ id: "img", label: "IMG", desc: "<img>" },
|
|
11704
|
-
{ id: "object", label: "OBJECT", desc: "<object>" },
|
|
11705
|
-
{ id: "embed", label: "EMBED", desc: "<embed>" },
|
|
11706
|
-
{ id: "inline", label: "INLINE", desc: "<svg>" },
|
|
11707
|
-
{ id: "css", label: "CSS", desc: "bg-image" },
|
|
11708
|
-
];
|
|
11709
|
-
|
|
11710
|
-
const COL_L2_STORAGE = [
|
|
11711
|
-
{ id: "embedded", label: "EMB", desc: "Embedded (data-uri)" },
|
|
11712
|
-
{ id: "local", label: "LOC", desc: "Local path" },
|
|
11713
|
-
{ id: "remote", label: "REM", desc: "Remote URL" },
|
|
11714
|
-
];
|
|
11715
|
-
|
|
11716
|
-
const ROW_L1_DISPLAY = [
|
|
11717
|
-
{ id: "browser", label: "BROWSER", desc: "Desktop browsers" },
|
|
11718
|
-
{ id: "mobile", label: "MOBILE", desc: "Mobile WebView" },
|
|
11719
|
-
{ id: "renderer", label: "RENDER", desc: "Server-side render" },
|
|
11720
|
-
];
|
|
11721
|
-
|
|
11722
|
-
const ROW_L2_LOAD = [
|
|
11723
|
-
{ id: "file", label: "file://", desc: "Local filesystem" },
|
|
11724
|
-
{ id: "http", label: "http(s)://", desc: "Web server" },
|
|
11725
|
-
{ id: "data", label: "data:", desc: "Data URI" },
|
|
11726
|
-
];
|
|
11727
|
-
|
|
11728
|
-
const ROW_L3_PROCESSING = [
|
|
11729
|
-
{ id: "script", label: "+JS", desc: "With JavaScript" },
|
|
11730
|
-
{ id: "noscript", label: "-JS", desc: "No JavaScript" },
|
|
11731
|
-
];
|
|
11732
|
-
|
|
11733
|
-
const BROWSERS_LOCAL = [
|
|
11734
|
-
{ id: "chrome", code: "C", name: "Chrome", color: "#4285F4" },
|
|
11735
|
-
{ id: "firefox", code: "F", name: "Firefox", color: "#FF7139" },
|
|
11736
|
-
{ id: "safari", code: "S", name: "Safari", color: "#006CFF" },
|
|
11737
|
-
{ id: "edge", code: "E", name: "Edge", color: "#0078D7" },
|
|
11738
|
-
{ id: "ie11", code: "I", name: "IE11", color: "#1EBBEE" },
|
|
11739
|
-
{ id: "ios", code: "i", name: "iOS", color: "#999" },
|
|
11740
|
-
{ id: "android", code: "A", name: "Android", color: "#3DDC84" },
|
|
11741
|
-
];
|
|
11742
|
-
|
|
11743
|
-
const FUNCS = [
|
|
11744
|
-
{ id: "display", code: "D", name: "Display", color: "#2196F3" },
|
|
11745
|
-
{ id: "animation", code: "A", name: "Animation", color: "#9C27B0" },
|
|
11746
|
-
{ id: "scripting", code: "J", name: "JavaScript", color: "#FF9800" },
|
|
11747
|
-
{ id: "events", code: "E", name: "Events", color: "#E91E63" },
|
|
11748
|
-
{ id: "media", code: "M", name: "Media", color: "#00BCD4" },
|
|
11749
|
-
{ id: "html", code: "H", name: "foreignObject", color: "#795548" },
|
|
11750
|
-
{ id: "images", code: "I", name: "Images", color: "#8BC34A" },
|
|
11751
|
-
{ id: "fonts", code: "T", name: "Fonts", color: "#607D8B" },
|
|
11752
|
-
{ id: "sprites", code: "U", name: "use/sprites", color: "#FF5722" },
|
|
11753
|
-
{ id: "links", code: "L", name: "Links", color: "#3F51B5" },
|
|
11754
|
-
];
|
|
11755
|
-
|
|
11756
|
-
// Calculate dimensions
|
|
11757
|
-
const rowHeaderWidth = 140;
|
|
11758
|
-
const colHeaderHeight = 80;
|
|
11759
|
-
const totalCols = COL_L1_SYNTAX.length * COL_L2_STORAGE.length;
|
|
11760
|
-
const totalRows =
|
|
11761
|
-
ROW_L1_DISPLAY.length * ROW_L2_LOAD.length * ROW_L3_PROCESSING.length;
|
|
11762
|
-
const svgWidth = rowHeaderWidth + totalCols * cellWidth + 40;
|
|
11763
|
-
const svgHeight = colHeaderHeight + totalRows * cellHeight + 200; // Extra for legend
|
|
11764
|
-
|
|
11765
|
-
// Color helpers
|
|
11766
|
-
const statusColors = {
|
|
11767
|
-
ok: "#4CAF50",
|
|
11768
|
-
warn: "#FF9800",
|
|
11769
|
-
block: "#F44336",
|
|
11770
|
-
};
|
|
11771
|
-
|
|
11772
|
-
/**
|
|
11773
|
-
* Evaluate a cell for the legacy SVG visualization (simplified version).
|
|
11774
|
-
* @param {string} display - Display environment ID
|
|
11775
|
-
* @param {string} load - Load protocol ID
|
|
11776
|
-
* @param {string} storage - Storage type ID
|
|
11777
|
-
* @param {string} syntax - Embedding syntax ID
|
|
11778
|
-
* @param {string} processing - Processing context ID
|
|
11779
|
-
* @returns {Object} Simplified cell evaluation result
|
|
11780
|
-
*/
|
|
11781
|
-
const evaluateCell = (display, load, storage, syntax, processing) => {
|
|
11782
|
-
const result = { browserStatus: {}, funcStatus: {}, overallStatus: "ok" };
|
|
11783
|
-
|
|
11784
|
-
// Initialize
|
|
11785
|
-
for (const b of BROWSERS_LOCAL) {
|
|
11786
|
-
result.browserStatus[b.id] = {};
|
|
11787
|
-
for (const f of FUNCS) result.browserStatus[b.id][f.id] = "ok";
|
|
11788
|
-
}
|
|
11789
|
-
for (const f of FUNCS) result.funcStatus[f.id] = "ok";
|
|
11790
|
-
|
|
11791
|
-
// Apply rules
|
|
11792
|
-
if (syntax === "img" || syntax === "css") {
|
|
11793
|
-
["animation", "scripting", "events", "html", "media", "links"].forEach(
|
|
11794
|
-
(f) => {
|
|
11795
|
-
result.funcStatus[f] = "block";
|
|
11796
|
-
BROWSERS_LOCAL.forEach((b) => {
|
|
11797
|
-
result.browserStatus[b.id][f] = "block";
|
|
11798
|
-
});
|
|
11799
|
-
},
|
|
11800
|
-
);
|
|
11801
|
-
}
|
|
11802
|
-
|
|
11803
|
-
if (display === "renderer") {
|
|
11804
|
-
["animation", "scripting", "events", "media", "html"].forEach((f) => {
|
|
11805
|
-
result.funcStatus[f] = "block";
|
|
11806
|
-
BROWSERS_LOCAL.forEach((b) => {
|
|
11807
|
-
result.browserStatus[b.id][f] = "block";
|
|
11808
|
-
});
|
|
11809
|
-
});
|
|
11810
|
-
}
|
|
11811
|
-
|
|
11812
|
-
if (processing === "noscript") {
|
|
11813
|
-
result.funcStatus["scripting"] = "block";
|
|
11814
|
-
result.funcStatus["events"] = "warn";
|
|
11815
|
-
BROWSERS_LOCAL.forEach((b) => {
|
|
11816
|
-
result.browserStatus[b.id]["scripting"] = "block";
|
|
11817
|
-
result.browserStatus[b.id]["events"] = "warn";
|
|
11818
|
-
});
|
|
11819
|
-
}
|
|
11820
|
-
|
|
11821
|
-
// IE11 specific
|
|
11822
|
-
result.browserStatus["ie11"]["animation"] = "block";
|
|
11823
|
-
result.browserStatus["ie11"]["html"] = "block";
|
|
11824
|
-
|
|
11825
|
-
// Media autoplay
|
|
11826
|
-
if (matrixData.detectedCapabilities?.some((c) => c.startsWith("AUDIO_"))) {
|
|
11827
|
-
BROWSERS_LOCAL.forEach((b) => {
|
|
11828
|
-
if (result.browserStatus[b.id]["media"] !== "block") {
|
|
11829
|
-
result.browserStatus[b.id]["media"] = "warn";
|
|
11830
|
-
}
|
|
11831
|
-
});
|
|
11832
|
-
result.browserStatus["ios"]["media"] = "block";
|
|
11833
|
-
result.funcStatus["media"] = "warn";
|
|
11834
|
-
}
|
|
11835
|
-
|
|
11836
|
-
// Remote storage
|
|
11837
|
-
if (storage === "remote") {
|
|
11838
|
-
["images", "fonts", "sprites"].forEach((f) => {
|
|
11839
|
-
if (result.funcStatus[f] !== "block") result.funcStatus[f] = "warn";
|
|
11840
|
-
});
|
|
11841
|
-
}
|
|
11842
|
-
|
|
11843
|
-
// Note: SMIL is fully supported in all modern browsers (except IE11)
|
|
11844
|
-
// Google retracted the deprecation notice after community feedback
|
|
11845
|
-
|
|
11846
|
-
// Calculate overall
|
|
11847
|
-
let hasBlock = false,
|
|
11848
|
-
hasWarn = false;
|
|
11849
|
-
Object.values(result.funcStatus).forEach((s) => {
|
|
11850
|
-
if (s === "block") hasBlock = true;
|
|
11851
|
-
else if (s === "warn") hasWarn = true;
|
|
11852
|
-
});
|
|
11853
|
-
result.overallStatus = hasBlock ? "block" : hasWarn ? "warn" : "ok";
|
|
11854
|
-
|
|
11855
|
-
return result;
|
|
11856
|
-
};
|
|
11857
|
-
|
|
11858
|
-
// Build SVG
|
|
11859
|
-
let svg = `<?xml version="1.0" encoding="UTF-8"?>
|
|
11860
|
-
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
11861
|
-
viewBox="0 0 ${svgWidth} ${svgHeight}" preserveAspectRatio="xMinYMin meet" height="${svgHeight}" width="100%">
|
|
11862
|
-
<title>SVG Compatibility Matrix</title>
|
|
11863
|
-
<desc>Browser and functionality compatibility for different SVG embedding methods</desc>
|
|
11864
|
-
|
|
11865
|
-
<defs>
|
|
11866
|
-
<style>
|
|
11867
|
-
.title { font: bold 18px ${fontFamily}; fill: #333; }
|
|
11868
|
-
.subtitle { font: 12px ${fontFamily}; fill: #666; }
|
|
11869
|
-
.col-header { font: bold 11px ${fontFamily}; fill: #333; }
|
|
11870
|
-
.col-subheader { font: 10px ${fontFamily}; fill: #555; }
|
|
11871
|
-
.row-header { font: bold 10px ${fontFamily}; fill: #333; }
|
|
11872
|
-
.row-subheader { font: 9px ${fontFamily}; fill: #555; }
|
|
11873
|
-
.cell-text { font: 8px ${fontFamily}; fill: #333; }
|
|
11874
|
-
.cell-ok { fill: #E8F5E9; }
|
|
11875
|
-
.cell-warn { fill: #FFF3E0; }
|
|
11876
|
-
.cell-block { fill: #FFEBEE; }
|
|
11877
|
-
.legend-text { font: 10px ${fontFamily}; fill: #333; }
|
|
11878
|
-
.grid-line { stroke: #DDD; stroke-width: 1; }
|
|
11879
|
-
.grid-line-bold { stroke: #999; stroke-width: 2; }
|
|
11880
|
-
${
|
|
11881
|
-
interactive
|
|
11882
|
-
? `
|
|
11883
|
-
.cell-group:hover rect { stroke: #333; stroke-width: 2; }
|
|
11884
|
-
.tooltip { pointer-events: none; opacity: 0; transition: opacity 0.2s; }
|
|
11885
|
-
.cell-group:hover .tooltip { opacity: 1; }
|
|
11886
|
-
`
|
|
11887
|
-
: ""
|
|
11888
|
-
}
|
|
11889
|
-
</style>
|
|
11890
|
-
</defs>
|
|
11891
|
-
|
|
11892
|
-
<!-- Title -->
|
|
11893
|
-
<text x="${svgWidth / 2}" y="25" text-anchor="middle" class="title">SVG Compatibility Matrix</text>
|
|
11894
|
-
<text x="${svgWidth / 2}" y="42" text-anchor="middle" class="subtitle">
|
|
11895
|
-
${totalCols} columns × ${totalRows} rows = ${totalCols * totalRows} combinations
|
|
11896
|
-
</text>
|
|
11897
|
-
|
|
11898
|
-
<g transform="translate(20, 55)">
|
|
11899
|
-
`;
|
|
11900
|
-
|
|
11901
|
-
// Column headers - Level 1 (SYNTAX)
|
|
11902
|
-
let colX = rowHeaderWidth;
|
|
11903
|
-
for (const syntax of COL_L1_SYNTAX) {
|
|
11904
|
-
const groupWidth = COL_L2_STORAGE.length * cellWidth;
|
|
11905
|
-
svg += ` <rect x="${colX}" y="0" width="${groupWidth}" height="25" fill="#E3F2FD" stroke="#90CAF9"/>
|
|
11906
|
-
<text x="${colX + groupWidth / 2}" y="17" text-anchor="middle" class="col-header">${syntax.label}</text>
|
|
11907
|
-
`;
|
|
11908
|
-
// Level 2 (STORAGE)
|
|
11909
|
-
for (let i = 0; i < COL_L2_STORAGE.length; i++) {
|
|
11910
|
-
const storage = COL_L2_STORAGE[i];
|
|
11911
|
-
const x = colX + i * cellWidth;
|
|
11912
|
-
svg += ` <rect x="${x}" y="25" width="${cellWidth}" height="20" fill="#F5F5F5" stroke="#DDD"/>
|
|
11913
|
-
<text x="${x + cellWidth / 2}" y="39" text-anchor="middle" class="col-subheader">${storage.label}</text>
|
|
11914
|
-
`;
|
|
11915
|
-
}
|
|
11916
|
-
colX += groupWidth;
|
|
11917
|
-
}
|
|
11918
|
-
|
|
11919
|
-
// Row headers and cells
|
|
11920
|
-
let rowY = colHeaderHeight - 35;
|
|
11921
|
-
for (const displayEnv of ROW_L1_DISPLAY) {
|
|
11922
|
-
// Display header
|
|
11923
|
-
const displayRows = ROW_L2_LOAD.length * ROW_L3_PROCESSING.length;
|
|
11924
|
-
const displayHeight = displayRows * cellHeight;
|
|
11925
|
-
svg += ` <rect x="0" y="${rowY}" width="50" height="${displayHeight}" fill="#E8EAF6" stroke="#9FA8DA"/>
|
|
11926
|
-
<text x="25" y="${rowY + displayHeight / 2}" text-anchor="middle" dominant-baseline="middle" class="row-header" transform="rotate(-90, 25, ${rowY + displayHeight / 2})">${displayEnv.label}</text>
|
|
11927
|
-
`;
|
|
11928
|
-
|
|
11929
|
-
for (const loadProto of ROW_L2_LOAD) {
|
|
11930
|
-
for (const procCtx of ROW_L3_PROCESSING) {
|
|
11931
|
-
// Row label
|
|
11932
|
-
svg += ` <rect x="50" y="${rowY}" width="90" height="${cellHeight}" fill="#FAFAFA" stroke="#EEE"/>
|
|
11933
|
-
<text x="55" y="${rowY + cellHeight / 2 - 6}" class="row-subheader">${loadProto.label}</text>
|
|
11934
|
-
<text x="55" y="${rowY + cellHeight / 2 + 8}" class="row-subheader">${procCtx.label}</text>
|
|
11935
|
-
`;
|
|
11936
|
-
|
|
11937
|
-
// Cells
|
|
11938
|
-
colX = rowHeaderWidth;
|
|
11939
|
-
for (const syntax of COL_L1_SYNTAX) {
|
|
11940
|
-
for (const storage of COL_L2_STORAGE) {
|
|
11941
|
-
const cellResult = evaluateCell(
|
|
11942
|
-
displayEnv.id,
|
|
11943
|
-
loadProto.id,
|
|
11944
|
-
storage.id,
|
|
11945
|
-
syntax.id,
|
|
11946
|
-
procCtx.id,
|
|
11947
|
-
);
|
|
11948
|
-
const cellClass = `cell-${cellResult.overallStatus}`;
|
|
11949
|
-
|
|
11950
|
-
// Build browser status string
|
|
11951
|
-
let browserStr = "";
|
|
11952
|
-
for (const b of BROWSERS_LOCAL) {
|
|
11953
|
-
let bStatus = "ok";
|
|
11954
|
-
for (const f of FUNCS) {
|
|
11955
|
-
const s = cellResult.browserStatus[b.id][f.id];
|
|
11956
|
-
if (s === "block") {
|
|
11957
|
-
bStatus = "block";
|
|
11958
|
-
break;
|
|
11959
|
-
} else if (s === "warn" && bStatus !== "block")
|
|
11960
|
-
bStatus = "warn";
|
|
11961
|
-
}
|
|
11962
|
-
const symbol =
|
|
11963
|
-
bStatus === "ok" ? "✓" : bStatus === "warn" ? "⚠" : "✗";
|
|
11964
|
-
browserStr += `${b.code}${symbol}`;
|
|
11965
|
-
}
|
|
11966
|
-
|
|
11967
|
-
// Build func status string
|
|
11968
|
-
let funcStr = "";
|
|
11969
|
-
for (const f of FUNCS) {
|
|
11970
|
-
const s = cellResult.funcStatus[f.id];
|
|
11971
|
-
if (s !== "ok") {
|
|
11972
|
-
funcStr += `${f.code}${s === "warn" ? "⚠" : "✗"}`;
|
|
11973
|
-
}
|
|
11974
|
-
}
|
|
11975
|
-
if (!funcStr) funcStr = "OK";
|
|
11976
|
-
|
|
11977
|
-
svg += ` <g class="cell-group">
|
|
11978
|
-
<rect x="${colX}" y="${rowY}" width="${cellWidth}" height="${cellHeight}" class="${cellClass}" stroke="#DDD"/>
|
|
11979
|
-
<text x="${colX + cellWidth / 2}" y="${rowY + 15}" text-anchor="middle" class="cell-text">${browserStr.substring(0, 10)}</text>
|
|
11980
|
-
<text x="${colX + cellWidth / 2}" y="${rowY + 28}" text-anchor="middle" class="cell-text">${browserStr.substring(10)}</text>
|
|
11981
|
-
<text x="${colX + cellWidth / 2}" y="${rowY + 40}" text-anchor="middle" class="cell-text" fill="${statusColors[cellResult.overallStatus]}">${funcStr.substring(0, 8)}</text>
|
|
11982
|
-
`;
|
|
11983
|
-
if (interactive) {
|
|
11984
|
-
// Tooltip
|
|
11985
|
-
svg += ` <g class="tooltip" transform="translate(${colX + cellWidth / 2}, ${rowY - 5})">
|
|
11986
|
-
<rect x="-60" y="-45" width="120" height="40" fill="#333" rx="4"/>
|
|
11987
|
-
<text x="0" y="-30" text-anchor="middle" fill="#FFF" font-size="9">${syntax.label}/${storage.label}</text>
|
|
11988
|
-
<text x="0" y="-17" text-anchor="middle" fill="#FFF" font-size="8">${displayEnv.label} ${loadProto.label} ${procCtx.label}</text>
|
|
11989
|
-
</g>
|
|
11990
|
-
`;
|
|
11991
|
-
}
|
|
11992
|
-
svg += ` </g>
|
|
11993
|
-
`;
|
|
11994
|
-
colX += cellWidth;
|
|
11995
|
-
}
|
|
11996
|
-
}
|
|
11997
|
-
rowY += cellHeight;
|
|
11998
|
-
}
|
|
11999
|
-
}
|
|
12000
|
-
}
|
|
12001
|
-
|
|
12002
|
-
// Legend
|
|
12003
|
-
const legendY = colHeaderHeight + totalRows * cellHeight + 20;
|
|
12004
|
-
svg += `
|
|
12005
|
-
<!-- Legend -->
|
|
12006
|
-
<g transform="translate(0, ${legendY})">
|
|
12007
|
-
<text x="10" y="15" class="col-header">LEGEND</text>
|
|
12008
|
-
|
|
12009
|
-
<!-- Status colors -->
|
|
12010
|
-
<rect x="10" y="25" width="20" height="15" class="cell-ok" stroke="#4CAF50"/>
|
|
12011
|
-
<text x="35" y="37" class="legend-text">OK - All features work</text>
|
|
12012
|
-
|
|
12013
|
-
<rect x="160" y="25" width="20" height="15" class="cell-warn" stroke="#FF9800"/>
|
|
12014
|
-
<text x="185" y="37" class="legend-text">Warning - Partial support</text>
|
|
12015
|
-
|
|
12016
|
-
<rect x="340" y="25" width="20" height="15" class="cell-block" stroke="#F44336"/>
|
|
12017
|
-
<text x="365" y="37" class="legend-text">Blocked - Not supported</text>
|
|
12018
|
-
|
|
12019
|
-
<!-- Browser codes -->
|
|
12020
|
-
<text x="10" y="60" class="col-header">BROWSERS:</text>
|
|
12021
|
-
`;
|
|
12022
|
-
|
|
12023
|
-
let legendX = 90;
|
|
12024
|
-
for (const b of BROWSERS_LOCAL) {
|
|
12025
|
-
svg += ` <text x="${legendX}" y="60" class="legend-text"><tspan fill="${b.color}" font-weight="bold">${b.code}</tspan>=${b.name}</text>
|
|
12026
|
-
`;
|
|
12027
|
-
legendX += 85;
|
|
12028
|
-
}
|
|
12029
|
-
|
|
12030
|
-
svg += `
|
|
12031
|
-
<!-- Functionality codes -->
|
|
12032
|
-
<text x="10" y="80" class="col-header">FUNCTIONS:</text>
|
|
12033
|
-
`;
|
|
12034
|
-
|
|
12035
|
-
legendX = 90;
|
|
12036
|
-
let legendRow = 0;
|
|
12037
|
-
for (const f of FUNCS) {
|
|
12038
|
-
if (legendX > 600) {
|
|
12039
|
-
legendX = 90;
|
|
12040
|
-
legendRow++;
|
|
12041
|
-
}
|
|
12042
|
-
svg += ` <text x="${legendX}" y="${80 + legendRow * 18}" class="legend-text"><tspan fill="${f.color}" font-weight="bold">${f.code}</tspan>=${f.name}</text>
|
|
12043
|
-
`;
|
|
12044
|
-
legendX += 100;
|
|
12045
|
-
}
|
|
12046
|
-
|
|
12047
|
-
svg += ` </g>
|
|
12048
|
-
</g>
|
|
12049
|
-
</svg>`;
|
|
12050
|
-
|
|
12051
|
-
return svg;
|
|
12052
|
-
}
|
|
12053
|
-
|
|
12054
11692
|
/**
|
|
12055
11693
|
* Generate a detailed, hierarchical SVG visualization of the compatibility matrix.
|
|
12056
11694
|
* Features:
|
|
@@ -19515,7 +19153,8 @@ export const embedExternalDependencies = createOperation(
|
|
|
19515
19153
|
// BUG FIX: Verify svg element exists before manipulating
|
|
19516
19154
|
if (!svg) {
|
|
19517
19155
|
warnings.push("Cannot add defs: document has no root SVG element");
|
|
19518
|
-
if (onProgress)
|
|
19156
|
+
if (onProgress)
|
|
19157
|
+
onProgress("externalSVGs", useArray.length, useArray.length);
|
|
19519
19158
|
} else {
|
|
19520
19159
|
defs = new SVGElement("defs", {}, []);
|
|
19521
19160
|
// Insert defs as first child - handle null firstChild properly
|
|
@@ -19532,104 +19171,105 @@ export const embedExternalDependencies = createOperation(
|
|
|
19532
19171
|
|
|
19533
19172
|
if (defs) {
|
|
19534
19173
|
for (let i = 0; i < useArray.length; i++) {
|
|
19535
|
-
|
|
19536
|
-
|
|
19537
|
-
|
|
19538
|
-
|
|
19539
|
-
if (!href || !isExternalHref(href)) continue;
|
|
19174
|
+
const useEl = useArray[i];
|
|
19175
|
+
const href =
|
|
19176
|
+
useEl.getAttribute("href") || useEl.getAttribute("xlink:href");
|
|
19540
19177
|
|
|
19541
|
-
|
|
19542
|
-
if (!path) continue;
|
|
19178
|
+
if (!href || !isExternalHref(href)) continue;
|
|
19543
19179
|
|
|
19544
|
-
|
|
19545
|
-
|
|
19546
|
-
if (visitedURLs.has(resolvedPath)) {
|
|
19547
|
-
warnings.push(`Circular reference detected: ${resolvedPath}`);
|
|
19548
|
-
continue;
|
|
19549
|
-
}
|
|
19550
|
-
visitedURLs.add(resolvedPath);
|
|
19551
|
-
|
|
19552
|
-
try {
|
|
19553
|
-
const { content } = await fetchResource(
|
|
19554
|
-
resolvedPath,
|
|
19555
|
-
"text",
|
|
19556
|
-
timeout,
|
|
19557
|
-
);
|
|
19558
|
-
const externalDoc = parseSVG(content);
|
|
19180
|
+
const { path, fragment } = parseExternalSVGRef(href);
|
|
19181
|
+
if (!path) continue;
|
|
19559
19182
|
|
|
19560
|
-
|
|
19561
|
-
|
|
19183
|
+
// Check circular reference
|
|
19184
|
+
const resolvedPath = resolveURL(path, basePath);
|
|
19185
|
+
if (visitedURLs.has(resolvedPath)) {
|
|
19186
|
+
warnings.push(`Circular reference detected: ${resolvedPath}`);
|
|
19562
19187
|
continue;
|
|
19563
19188
|
}
|
|
19189
|
+
visitedURLs.add(resolvedPath);
|
|
19564
19190
|
|
|
19565
|
-
|
|
19566
|
-
|
|
19567
|
-
|
|
19568
|
-
|
|
19569
|
-
|
|
19570
|
-
|
|
19571
|
-
|
|
19572
|
-
...options,
|
|
19573
|
-
basePath: resolvedPath,
|
|
19574
|
-
maxRecursionDepth: maxRecursionDepth - 1,
|
|
19575
|
-
// BUG FIX: Pass visitedURLs to detect cross-document circular references (A → B → A)
|
|
19576
|
-
_visitedURLs: visitedURLs,
|
|
19577
|
-
});
|
|
19578
|
-
}
|
|
19191
|
+
try {
|
|
19192
|
+
const { content } = await fetchResource(
|
|
19193
|
+
resolvedPath,
|
|
19194
|
+
"text",
|
|
19195
|
+
timeout,
|
|
19196
|
+
);
|
|
19197
|
+
const externalDoc = parseSVG(content);
|
|
19579
19198
|
|
|
19580
|
-
|
|
19581
|
-
|
|
19199
|
+
if (!externalDoc) {
|
|
19200
|
+
handleMissingResource(path, new Error("Failed to parse SVG"));
|
|
19201
|
+
continue;
|
|
19202
|
+
}
|
|
19582
19203
|
|
|
19583
|
-
|
|
19584
|
-
|
|
19585
|
-
|
|
19586
|
-
|
|
19587
|
-
|
|
19588
|
-
|
|
19589
|
-
|
|
19590
|
-
|
|
19591
|
-
|
|
19204
|
+
// Extract vendor namespaces from external SVG for later preservation
|
|
19205
|
+
const externalSvgRoot = externalDoc.documentElement || externalDoc;
|
|
19206
|
+
extractVendorNamespaces(externalSvgRoot);
|
|
19207
|
+
|
|
19208
|
+
// Recursively process external SVG if enabled
|
|
19209
|
+
if (recursive && maxRecursionDepth > 0) {
|
|
19210
|
+
await embedExternalDependencies(externalDoc, {
|
|
19211
|
+
...options,
|
|
19212
|
+
basePath: resolvedPath,
|
|
19213
|
+
maxRecursionDepth: maxRecursionDepth - 1,
|
|
19214
|
+
// BUG FIX: Pass visitedURLs to detect cross-document circular references (A → B → A)
|
|
19215
|
+
_visitedURLs: visitedURLs,
|
|
19216
|
+
});
|
|
19217
|
+
}
|
|
19218
|
+
|
|
19219
|
+
extCounter++;
|
|
19220
|
+
const uniquePrefix = `${idPrefix}${extCounter}_`;
|
|
19221
|
+
|
|
19222
|
+
if (externalSVGMode === "extract" && fragment) {
|
|
19223
|
+
// Extract just the referenced element
|
|
19224
|
+
const targetEl = externalDoc.getElementById(fragment);
|
|
19225
|
+
if (targetEl) {
|
|
19226
|
+
// Clone and relocate IDs
|
|
19227
|
+
const cloned = targetEl.clone
|
|
19228
|
+
? targetEl.clone()
|
|
19229
|
+
: JSON.parse(JSON.stringify(targetEl));
|
|
19230
|
+
relocateIds(cloned, uniquePrefix);
|
|
19231
|
+
|
|
19232
|
+
// Add to defs
|
|
19233
|
+
defs.children.push(cloned);
|
|
19234
|
+
|
|
19235
|
+
// Update use href to local reference
|
|
19236
|
+
const newId = uniquePrefix + fragment;
|
|
19237
|
+
if (useEl.getAttribute("href"))
|
|
19238
|
+
useEl.setAttribute("href", "#" + newId);
|
|
19239
|
+
if (useEl.getAttribute("xlink:href"))
|
|
19240
|
+
useEl.setAttribute("xlink:href", "#" + newId);
|
|
19241
|
+
} else {
|
|
19242
|
+
handleMissingResource(
|
|
19243
|
+
href,
|
|
19244
|
+
new Error(`Fragment #${fragment} not found in ${path}`),
|
|
19245
|
+
);
|
|
19246
|
+
}
|
|
19247
|
+
} else {
|
|
19248
|
+
// Embed entire external SVG
|
|
19249
|
+
const externalSvg = externalDoc.documentElement || externalDoc;
|
|
19250
|
+
relocateIds(externalSvg, uniquePrefix);
|
|
19251
|
+
|
|
19252
|
+
// Set an ID on the external SVG
|
|
19253
|
+
const svgId = uniquePrefix + "svg";
|
|
19254
|
+
externalSvg.setAttribute("id", svgId);
|
|
19592
19255
|
|
|
19593
19256
|
// Add to defs
|
|
19594
|
-
defs.children.push(
|
|
19257
|
+
defs.children.push(externalSvg);
|
|
19595
19258
|
|
|
19596
|
-
// Update use href
|
|
19597
|
-
const
|
|
19259
|
+
// Update use href
|
|
19260
|
+
const newHref = fragment
|
|
19261
|
+
? "#" + uniquePrefix + fragment
|
|
19262
|
+
: "#" + svgId;
|
|
19598
19263
|
if (useEl.getAttribute("href"))
|
|
19599
|
-
useEl.setAttribute("href",
|
|
19264
|
+
useEl.setAttribute("href", newHref);
|
|
19600
19265
|
if (useEl.getAttribute("xlink:href"))
|
|
19601
|
-
useEl.setAttribute("xlink:href",
|
|
19602
|
-
} else {
|
|
19603
|
-
handleMissingResource(
|
|
19604
|
-
href,
|
|
19605
|
-
new Error(`Fragment #${fragment} not found in ${path}`),
|
|
19606
|
-
);
|
|
19266
|
+
useEl.setAttribute("xlink:href", newHref);
|
|
19607
19267
|
}
|
|
19608
|
-
}
|
|
19609
|
-
|
|
19610
|
-
const externalSvg = externalDoc.documentElement || externalDoc;
|
|
19611
|
-
relocateIds(externalSvg, uniquePrefix);
|
|
19612
|
-
|
|
19613
|
-
// Set an ID on the external SVG
|
|
19614
|
-
const svgId = uniquePrefix + "svg";
|
|
19615
|
-
externalSvg.setAttribute("id", svgId);
|
|
19616
|
-
|
|
19617
|
-
// Add to defs
|
|
19618
|
-
defs.children.push(externalSvg);
|
|
19619
|
-
|
|
19620
|
-
// Update use href
|
|
19621
|
-
const newHref = fragment
|
|
19622
|
-
? "#" + uniquePrefix + fragment
|
|
19623
|
-
: "#" + svgId;
|
|
19624
|
-
if (useEl.getAttribute("href")) useEl.setAttribute("href", newHref);
|
|
19625
|
-
if (useEl.getAttribute("xlink:href"))
|
|
19626
|
-
useEl.setAttribute("xlink:href", newHref);
|
|
19268
|
+
} catch (e) {
|
|
19269
|
+
handleMissingResource(path, e);
|
|
19627
19270
|
}
|
|
19628
|
-
} catch (e) {
|
|
19629
|
-
handleMissingResource(path, e);
|
|
19630
|
-
}
|
|
19631
19271
|
|
|
19632
|
-
|
|
19272
|
+
if (onProgress) onProgress("externalSVGs", i + 1, useArray.length);
|
|
19633
19273
|
}
|
|
19634
19274
|
}
|
|
19635
19275
|
}
|
|
@@ -20062,11 +19702,13 @@ function decodeDataUri(parsed) {
|
|
|
20062
19702
|
return Buffer.from(parsed.data, "base64");
|
|
20063
19703
|
}
|
|
20064
19704
|
// URL-encoded or plain text
|
|
20065
|
-
//
|
|
19705
|
+
// LEGITIMATE FALLBACK: Handle malformed percent-encoded sequences.
|
|
19706
|
+
// Real-world data URIs may have invalid percent-encoding but the raw data is still usable.
|
|
19707
|
+
// Returning as-is allows the caller to attempt to use the data rather than crashing.
|
|
20066
19708
|
try {
|
|
20067
19709
|
return decodeURIComponent(parsed.data);
|
|
20068
19710
|
} catch (e) {
|
|
20069
|
-
//
|
|
19711
|
+
// Malformed %XX sequences - return raw data which may still be valid for the use case
|
|
20070
19712
|
if (process.env.DEBUG) console.warn(`[svg-toolbox] ${e.message}`);
|
|
20071
19713
|
return parsed.data;
|
|
20072
19714
|
}
|