Greasy Fork

Greasy Fork is available in English.

SVG Extractor with Preview and Download (GlyphWiki Special)

提取页面中的所有 SVG,提供预览并选择下载 SVG 或 PNG(PNG 尺寸根据 SVG 长宽比例调整),增加预览区关闭功能,支持外部 SVG 文件(需服务器配置支持),特别处理 GlyphWiki 的 SVG 移除网格和矩形边界

当前为 2025-01-14 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         SVG Extractor with Preview and Download (GlyphWiki Special)
// @license MIT
// @namespace    http://tampermonkey.net/
// @version      1.8
// @description  提取页面中的所有 SVG,提供预览并选择下载 SVG 或 PNG(PNG 尺寸根据 SVG 长宽比例调整),增加预览区关闭功能,支持外部 SVG 文件(需服务器配置支持),特别处理 GlyphWiki 的 SVG 移除网格和矩形边界
// @author       般若
// @match        *://*/*
// @grant        none
// @require      https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js
// ==/UserScript==

(function() {
    'use strict';

    // 创建预览容器
    const previewContainer = document.createElement('div');
    previewContainer.style.position = 'fixed';
    previewContainer.style.bottom = '20px';
    previewContainer.style.right = '20px';
    previewContainer.style.zIndex = '10000';
    previewContainer.style.backgroundColor = 'white';
    previewContainer.style.padding = '10px';
    previewContainer.style.border = '1px solid #ccc';
    previewContainer.style.borderRadius = '5px';
    previewContainer.style.boxShadow = '0 0 10px rgba(0, 0, 0, 0.1)';
    previewContainer.style.maxHeight = '80vh';
    previewContainer.style.overflowY = 'auto';
    document.body.appendChild(previewContainer);

    // 创建关闭按钮
    const closeButton = document.createElement('button');
    closeButton.innerText = '×';
    closeButton.style.position = 'absolute';
    closeButton.style.top = '5px';
    closeButton.style.right = '5px';
    closeButton.style.backgroundColor = 'transparent';
    closeButton.style.border = 'none';
    closeButton.style.fontSize = '16px';
    closeButton.style.cursor = 'pointer';
    closeButton.addEventListener('click', () => {
        previewContainer.style.display = 'none';
    });
    previewContainer.appendChild(closeButton);

    // 创建提取按钮
    const extractButton = document.createElement('button');
    extractButton.innerText = '提取 SVG';
    extractButton.style.position = 'fixed';
    extractButton.style.bottom = '20px';
    extractButton.style.left = '20px';
    extractButton.style.zIndex = '10000';
    extractButton.style.padding = '10px';
    extractButton.style.backgroundColor = '#007bff';
    extractButton.style.color = 'white';
    extractButton.style.border = 'none';
    extractButton.style.borderRadius = '5px';
    extractButton.style.cursor = 'pointer';
    document.body.appendChild(extractButton);

    // 创建关闭预览区按钮
    const closePreviewButton = document.createElement('button');
    closePreviewButton.innerText = '关闭预览区';
    closePreviewButton.style.position = 'fixed';
    closePreviewButton.style.bottom = '60px';
    closePreviewButton.style.left = '20px';
    closePreviewButton.style.zIndex = '10000';
    closePreviewButton.style.padding = '10px';
    closePreviewButton.style.backgroundColor = '#dc3545';
    closePreviewButton.style.color = 'white';
    closePreviewButton.style.border = 'none';
    closePreviewButton.style.borderRadius = '5px';
    closePreviewButton.style.cursor = 'pointer';
    closePreviewButton.addEventListener('click', () => {
        previewContainer.style.display = 'none';
    });
    document.body.appendChild(closePreviewButton);

    // 移除 GlyphWiki SVG 的网格和矩形边界
    function removeGlyphWikiBackground(svg) {
        if (window.location.hostname === 'glyphwiki.org') {
            const rects = svg.querySelectorAll('rect.glyph-boundary, rect.glyph-guide');
            rects.forEach(rect => rect.remove());

            const gridLines = svg.querySelectorAll('g.grid-lines');
            gridLines.forEach(grid => grid.remove());

            // 修改 viewBox
            svg.setAttribute('viewBox', '-20 -20 240 240');
        }
    }

    // 加载外部 SVG 文件并将其转换为内联 SVG
    async function loadAndInlineExternalSVGs() {
        const imgElements = Array.from(document.querySelectorAll('img[src$=".svg"]'));
        for (const img of imgElements) {
            try {
                const response = await fetch(img.src);
                const svgText = await response.text();
                const parser = new DOMParser();
                const svgDoc = parser.parseFromString(svgText, 'image/svg+xml');
                const svgElement = svgDoc.querySelector('svg');
                if (svgElement) {
                    img.replaceWith(svgElement);
                    removeGlyphWikiBackground(svgElement); // 移除 GlyphWiki 背景
                }
            } catch (error) {
                console.error('Failed to load and inline SVG:', error);
            }
        }
    }

    // 提取页面中的所有 SVG 元素
    function extractSVGs() {
        const svgs = Array.from(document.querySelectorAll('svg')).map((svg, index) => {
            removeGlyphWikiBackground(svg); // 移除 GlyphWiki 背景
            const serializer = new XMLSerializer();
            const svgString = serializer.serializeToString(svg);
            const blob = new Blob([svgString], { type: 'image/svg+xml' });
            const url = URL.createObjectURL(blob);
            return { url, name: `svg-${index + 1}.svg`, element: svg };
        });

        // 特别处理字统网动态更新的 SVG
        const dynamicSvgs = Array.from(document.querySelectorAll('svg[id="zusvgimgkage"], svg[style*="position:absolute;left:0;top:0;"]')).map((svg, index) => {
            const serializer = new XMLSerializer();
            const svgString = serializer.serializeToString(svg);
            const blob = new Blob([svgString], { type: 'image/svg+xml' });
            const url = URL.createObjectURL(blob);
            return { url, name: `dynamic-svg-${index + 1}.svg`, element: svg };
        });

        return svgs.concat(dynamicSvgs);
    }

    // 显示 SVG 预览
    function showSVGPreviews(svgs) {
        previewContainer.innerHTML = '<h3 style="margin: 0 0 10px;">SVG 预览</h3>';
        previewContainer.appendChild(closeButton); // 重新添加关闭按钮
        svgs.forEach((svg, index) => {
            const previewItem = document.createElement('div');
            previewItem.style.marginBottom = '10px';
            previewItem.style.borderBottom = '1px solid #eee';
            previewItem.style.paddingBottom = '10px';

            // 显示 SVG 预览
            const previewImage = document.createElement('img');
            previewImage.src = svg.url;
            previewImage.style.maxWidth = '200px';
            previewImage.style.maxHeight = '100px';
            previewImage.style.display = 'block';
            previewImage.style.marginBottom = '5px';
            previewItem.appendChild(previewImage);

            // 下载 SVG 按钮
            const downloadSvgButton = document.createElement('button');
            downloadSvgButton.innerText = '下载 SVG';
            downloadSvgButton.style.marginRight = '5px';
            downloadSvgButton.style.padding = '5px 10px';
            downloadSvgButton.style.backgroundColor = '#28a745';
            downloadSvgButton.style.color = 'white';
            downloadSvgButton.style.border = 'none';
            downloadSvgButton.style.borderRadius = '5px';
            downloadSvgButton.style.cursor = 'pointer';
            downloadSvgButton.addEventListener('click', () => {
                const link = document.createElement('a');
                link.href = svg.url;
                link.download = svg.name;
                document.body.appendChild(link);
                link.click();
                document.body.removeChild(link);
                URL.revokeObjectURL(svg.url);
            });
            previewItem.appendChild(downloadSvgButton);

            // 下载 PNG 按钮(透明背景)
            const downloadPngTransparentButton = document.createElement('button');
            downloadPngTransparentButton.innerText = '下载 PNG (透明)';
            downloadPngTransparentButton.style.marginRight = '5px';
            downloadPngTransparentButton.style.padding = '5px 10px';
            downloadPngTransparentButton.style.backgroundColor = '#17a2b8';
            downloadPngTransparentButton.style.color = 'white';
            downloadPngTransparentButton.style.border = 'none';
            downloadPngTransparentButton.style.borderRadius = '5px';
            downloadPngTransparentButton.style.cursor = 'pointer';
            downloadPngTransparentButton.addEventListener('click', () => {
                downloadPNG(svg, index, false);
            });
            previewItem.appendChild(downloadPngTransparentButton);

            // 下载 PNG 按钮(白色背景)
            const downloadPngWhiteButton = document.createElement('button');
            downloadPngWhiteButton.innerText = '下载 PNG (白色背景)';
            downloadPngWhiteButton.style.padding = '5px 10px';
            downloadPngWhiteButton.style.backgroundColor = '#ffc107';
            downloadPngWhiteButton.style.color = 'black';
            downloadPngWhiteButton.style.border = 'none';
            downloadPngWhiteButton.style.borderRadius = '5px';
            downloadPngWhiteButton.style.cursor = 'pointer';
            downloadPngWhiteButton.addEventListener('click', () => {
                downloadPNG(svg, index, true);
            });
            previewItem.appendChild(downloadPngWhiteButton);

            previewContainer.appendChild(previewItem);
        });
    }

    // 下载 PNG 函数
    function downloadPNG(svg, index, isWhiteBackground) {
        const img = new Image();
        img.src = svg.url;
        img.onload = () => {
            const canvas = document.createElement('canvas');
            const svgElement = svg.element;

            // 获取 SVG 的实际尺寸
            const svgWidth = svgElement.width.baseVal.value || svgElement.getBoundingClientRect().width;
            const svgHeight = svgElement.height.baseVal.value || svgElement.getBoundingClientRect().height;

            // 如果 SVG 的长宽不相等,则 PNG 尺寸为原 SVG 的 4 倍长 x 4 倍宽
            if (svgWidth !== svgHeight) {
                canvas.width = svgWidth * 4;
                canvas.height = svgHeight * 4;
            } else {
                canvas.width = 500;
                canvas.height = 500;
            }

            const ctx = canvas.getContext('2d');

            // 如果选择白色背景,填充白色
            if (isWhiteBackground) {
                ctx.fillStyle = 'white';
                ctx.fillRect(0, 0, canvas.width, canvas.height);
            }

            // 计算缩放比例
            const scale = Math.min(canvas.width / svgWidth, canvas.height / svgHeight);
            const scaledWidth = svgWidth * scale;
            const scaledHeight = svgHeight * scale;

            // 居中绘制
            const offsetX = (canvas.width - scaledWidth) / 2;
            const offsetY = (canvas.height - scaledHeight) / 2;

            ctx.drawImage(img, offsetX, offsetY, scaledWidth, scaledHeight);

            canvas.toBlob(blob => {
                const url = URL.createObjectURL(blob);
                const link = document.createElement('a');
                link.href = url;
                link.download = `png-${index + 1}-${isWhiteBackground ? 'white' : 'transparent'}.png`;
                document.body.appendChild(link);
                link.click();
                document.body.removeChild(link);
                URL.revokeObjectURL(url);
            }, 'image/png');
        };
    }

    // 点击按钮提取并显示 SVG 预览
    extractButton.addEventListener('click', async () => {
        await loadAndInlineExternalSVGs(); // 加载并内联外部 SVG
        const svgs = extractSVGs();
        if (svgs.length > 0) {
            previewContainer.style.display = 'block'; // 显示预览区
            showSVGPreviews(svgs);
        } else {
            previewContainer.innerHTML = '<p>未找到 SVG 元素。</p>';
        }
    });
})();