Greasy Fork

来自缓存

Greasy Fork is available in English.

带预览和下载功能的 SVG 提取器(GlyphWiki 特别版)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         带预览和下载功能的 SVG 提取器(GlyphWiki 特别版)
// @license      MIT
// @namespace    http://tampermonkey.net/
// @version      2.1
// @description  提取页面中的所有 SVG 和 <symbol> 标签,提供预览并选择下载 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);

    function removeGlyphWikiBackground(svg) {
        const allowedDomains = ['glyphwiki.org', 'zhs.glyphwiki.org', 'zht.glyphwiki.org']; // 添加你需要的网址
        if (allowedDomains.includes(window.location.hostname)) {
            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', '0 0 200 200');
            svg.setAttribute('width', '100%');
            svg.setAttribute('height', '100%');
        }
    }

    // 加载外部 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);
    }

    // 提取页面中的所有 <symbol> 标签并生成 SVG 文件
    function extractSymbols() {
        const symbols = document.querySelectorAll('symbol');
        const symbolSvgs = [];

        symbols.forEach(symbol => {
            // 创建一个新的 SVG 元素
            const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
            svg.setAttribute('viewBox', symbol.getAttribute('viewBox'));
            svg.setAttribute('width', '100');
            svg.setAttribute('height', '100');
            //svg.style.border = '1px solid #ccc';
            //svg.style.marginBottom = '10px';

            // 将 <symbol> 的内容复制到新的 SVG 中
            Array.from(symbol.children).forEach(child => {
                svg.appendChild(child.cloneNode(true));
            });

            // 将 SVG 转换为字符串
            const serializer = new XMLSerializer();
            const svgString = serializer.serializeToString(svg);

            // 创建一个下载链接
            const blob = new Blob([svgString], { type: 'image/svg+xml' });
            const url = URL.createObjectURL(blob);
            symbolSvgs.push({ url, name: `${symbol.id}.svg`, element: svg });
        });

        return symbolSvgs;
    }

    // 显示 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 = 2500;
                canvas.height = 2500;
            }

            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();
        const symbolSvgs = extractSymbols();
        const allSvgs = svgs.concat(symbolSvgs);
        if (allSvgs.length > 0) {
            previewContainer.style.display = 'block'; // 显示预览区
            showSVGPreviews(allSvgs);
        } else {
            previewContainer.innerHTML = '<p>未找到 SVG 元素。</p>';
        }
    });
})();