Greasy Fork is available in English.

易企秀海报导出图片

易企秀超高清截图。百分百正确去水印,导出原图脚本需要请私聊。此脚本为基础版,支持部分海报正确导出 去水印原图。

// ==UserScript==
// @name         易企秀海报导出图片
// @namespace    http://tampermonkey.eqxposter.1.0.baseVresion.net/
// @version      1.0
// @description  易企秀超高清截图。百分百正确去水印,导出原图脚本需要请私聊。此脚本为基础版,支持部分海报正确导出 去水印原图。
// @author       zouys
// @match        https://www.eqxiu.com/h2/create/*
// @icon         https://img.douyucdn.cn/data/yuba/default/2021/08/26/202108260113528146305214128.jpg?i=3729ce896e75556d73b47749933df87293
// @run-at       document-start
// @grant        GM_xmlhttpRequest
// @connect      font.eqh5.com
// @connect      asset.eqh5.com
// ==/UserScript==
/**
 目前思路:
 通过canvas画板,按照html中的设定样式挨个画出,导出为图片
 图片通过接口抓取地址,
 带字体文本,通过svg保存为图片(编码实现)

 理论上可行,可设置图片分辨率
 根据下载图片的分辨率,理论上存在最大分辨率

 */
(function() {
    'use strict';

    if(typeof GM_xmlhttpRequest=='undefined'){
        console.error('GM_xmlhttpRequest not support!')
        return ;
    }
    let btn=document.createElement('button')
    btn.style.width='250px;'
    btn.style.height='50px;'
    btn.style.position='absolute'
    btn.style.left='50%'
    btn.style.top='70px'
    btn.style.zindex='999999'
    btn.style.backgroundColor='yellow'
    btn.onclick=()=>{domain()}
    btn.innerText='一键导出图片'
    document.body.appendChild(btn);


    //捕获请求获得的模版数据
    let templateData;
    //模版图片数据,用于获取无水印图片
    let templatePicListData;
    //画布
    let canvas;
    //画布上下文
    let ctx;
    //缩放值
    let scale;
    /**
     * 通过queue保存画布操作
     * 队列
     */
    let drawQueue = [];
    //字体
    let fontMap=new Map();
    //字体中文别名
    let fontNickNameMap=new Map();
    //字体对应url
    let fontUrlMap=new Map();
    //特殊字体用字,用于获取差异字体文件
    let specialFontMap=new Map();

    // 创建Canvas元素
    canvas = document.createElement('canvas');
    // Canvas上下文
    ctx = canvas.getContext('2d');
    /**
     * 将绘制操作添加到队列中
     * @param drawFunction 函数,队列中保存的函数
     */
    function addToDrawQueue(drawFunction) {
        drawQueue.push(drawFunction);
    }
    /**
     * 从队列中取出绘制操作并执行,默认会取出队列
     * @param execute 默认为true,从队列头部取出操作
     * @returns {Promise<void>}
     */
    async function runDrawQueue(execute = true) {
        await new Promise(function (resolve, reject) {
            if (drawQueue.length > 0 && execute) {
                let drawFunction = drawQueue.shift();
                drawFunction();
                resolve();
            } else {
                console.warn('drawQueue is empty');
                reject()
            }
        })
    }
    /**
     * 根据url,提升画质
     *     *https://asset.eqh5.com//store/bade83874ac8f0d467738052ff2a5ef6.jpg?
     *     * imageMogr2/auto-orient
     *     * /quality/100
     *     * /cut/976x2048x194x0
     * @param imageUrl 图片地址
     * @param quality 质量,1-100,越大越清晰
     * @returns {*|string} 返回加工后的图片地址
     */
    let upgradeImage=(imageUrl,quality)=>{
        if(imageUrl.includes('quality/')){
            let stringArr=imageUrl.split('quality/');
            return stringArr[0]+'quality/'+quality+(stringArr[1].length>3?stringArr[1].substring(stringArr[1].indexOf('/')):'');
        }else
            return imageUrl
    }
    /**
     * 将画布转为图片
     * @param canvas 已完成操作的画布
     */
    let canvasToImage=(canvas)=>{
        // 获取转换后的图片数据
        const imageData = canvas.toDataURL('image/jpeg',1);

        let downloadLink = document.createElement('a');
        downloadLink.href = imageData;
        downloadLink.download = 'test.jpeg';
        downloadLink.click();
        Vue.loading.close()
    }
    /**
     * 图片加载完成后,进行画布操作
     * @param image 图片
     * @param canvas 画布,弱耦合,获取画布宽高
     * @param ctx 画布上下文,用于画操作
     * @param dx 在画布中开画的起始x轴坐标
     * @param dy 在画布中开画的起始y轴坐标
     * @param dWidth 默认为画布宽带,规定所画图片的宽带,等比例缩放。
     * @param dHeight 默认为画布高度,规定所画图片的高度,等比例缩放。
     * @returns {Promise<void>} 可同步等待完成
     */
    let imageLoad=async (image,canvas,ctx,dx,dy,dWidth,dHeight)=>{
        console.log(image.msg,`,加载成功:像素:${image.naturalWidth}*${image.naturalHeight}`);
        //console.log('图片高度:',image.naturalHeight,'px');
        //console.log('图片宽度:',image.naturalWidth,'px');
        //console.log(image);

        //画出背景图,保存ctx,独立每次操作
        //ctx.save();
        //ctx.globalAlpha = 0.5;
        await ctx.drawImage(image,dx,dy,dWidth || canvas.width,dHeight || canvas.height);
        //ctx.globalAlpha = 1;
        //ctx.restore();
        //await canvasToImage(canvas);
    }

    /**
     * 队列处理,逐个出队
     */
    function drawLoop(){
        runDrawQueue().then()
        while(drawQueue.length>0){
            runDrawQueue().then();
        }
        try {
            canvasToImage(canvas);
            console.log("图片导出成功!")
        }catch (e) {
            console.warn("图片最后导出异常!",e)

        }
    }
    function getDateString() {
        let currentDate = new Date();
        let year = currentDate.getFullYear();
        let month = ('0' + (currentDate.getMonth() + 1)).slice(-2); // 月份从0开始,所以要加1,然后确保两位数
        let day = ('0' + currentDate.getDate()).slice(-2); // 获取日期并确保两位数
        let hours = ('0' + currentDate.getHours()).slice(-2); // 获取小时并确保两位数
        let minutes = ('0' + currentDate.getMinutes()).slice(-2); // 获取分钟并确保两位数
        let seconds = ('0' + currentDate.getSeconds()).slice(-2); // 获取秒并确保两位数
        let daysOfWeek = ['Sunday周日', 'Monday周一', 'Tuesday周二', 'Wednesday周三', 'Thursday周四', 'Friday周五', 'Saturday周六'];
        let dayOfWeek = daysOfWeek[currentDate.getDay()];

        // 构建时间字符串,格式为 YYYY-MM-DD HH:MM:SS DayOfWeek
        let currentDateString = year + '年-' + month + '月-' + day + '日 ' + hours + '时:' + minutes + '分:' + seconds + '秒 ' + dayOfWeek;
        return (currentDateString);
    }
    //劫持函数
    function addXMLRequestCallback(callback) {
        // oldSend 旧函数 i 循环
        var oldSend, i;
        //判断是否有callbacks变量
        if (XMLHttpRequest.callbacks) {
            //判断XMLHttpRequest对象下是否存在回调列表,存在就push一个回调的函数
            XMLHttpRequest.callbacks.push(callback);
        } else {
            //如果不存在则在xmlhttprequest函数下创建一个回调列表/callback数组
            XMLHttpRequest.callbacks = [callback];
            // 保存 XMLHttpRequest 的send函数
            oldSend = XMLHttpRequest.prototype.send;
            //获取旧xml的send函数,并对其进行劫持(替换)  function()则为替换的函数
            XMLHttpRequest.prototype.send = function () {
                // 把callback列表上的所有函数取出来
                for (i = 0; i < XMLHttpRequest.callbacks.length; i++) {
                    // 把this传入进去
                    XMLHttpRequest.callbacks[i](this);
                }
                //循环回调xml内的回调函数
                // 调用旧的send函数 并传入this 和 参数
                oldSend.apply(this, arguments);
            };
        }
    }
    //传入回调 接收xhr变量
    addXMLRequestCallback(function (xhr) {
        //调用劫持函数,填入一个function的回调函数
        //回调函数监听了对xhr调用了监听load状态,并且在触发的时候再次调用一个function,进行一些数据的劫持以及修改
        xhr.addEventListener("load", function () {
            // 输出xhr所有相关信息
            if (xhr.readyState === 4 && xhr.status === 200) {
                //  如果xhr请求成功 则返回请求路径
                //console.log("函数1", xhr.responseURL);
                // 捕获模版元素 数据 用于获取特殊字体
                if(xhr.responseURL.includes('https://p1.eqxiu.com/m/print/page/save') || xhr.responseURL.includes('https://p1.eqxiu.com/m/print/page/batch/save')){
                    console.log(xhr.response)
                    if(JSON.parse(xhr.response).obj){
                        templateData=JSON.parse(xhr.response).obj.elements;
                        console.log("捕获请求数据:",templateData);
                    }
                }
                // 捕获 模板 字体数据或 所用图片列表
                else if (xhr.responseURL.includes("https://emw-api.eqxiu.com/copyright/list-style")){
                    //console.log(JSON.parse(xhr.response))
                    if(JSON.parse(xhr.response).obj.picList){
                        templatePicListData=JSON.parse(xhr.response).obj.picList;
                        console.log("捕获请求图片列表:",templatePicListData)
                    }
                }
            }
        });

    });
    let getSpecialFont=async (eleList) => {
        try {
            if (eleList) {
                //获取模版用字,特殊字体字
                //叠加获取字体,style和list-style不可信
                for (const ele of eleList) {
                    //console.log(ele)
                    if (ele.property.content && (ele.property.fontFamilyName !== "默认字体")) {
                        //获取特殊字
                        let fontFamily;
                        let fontFamilyName = ele.property.fontFamilyName;

                        if (ele.property.contentStyle && ele.property.contentStyle.fontFamily) {
                            fontFamily = ele.property.contentStyle.fontFamily;

                        } else {
                            //根据fontFamilyName来获取对应的font
                            if(fontNickNameMap.size>0){
                                for(let [key, value] of fontNickNameMap.entries()) {
                                    if (value === fontFamilyName) {
                                        fontFamily = key;
                                        break;
                                    }
                                }
                            }else{
                                console.warn("字体别名查询异常!");
                            }
                        }
                        if(ele.css && ele.css.fontFamily){
                            fontFamily = ele.css.fontFamily;
                        }
                        //去除所有空格
                        let fontContent = ele.property.content.replace(/\s/g, '');
                        //检查是否为时间格式的
                        //showLayerLabel='日期...'
                        if(ele.property.showLayerLabel && ele.property.showLayerLabel.includes("日期")) {
                            fontContent = getDateString().replace(/\s/g, '');
                        }
                        console.log("fontContent", fontContent);
                        //保存多种字体用字
                        specialFontMap.set(fontFamily, specialFontMap.get(fontFamily) ? (specialFontMap.get(fontFamily) + fontContent) : fontContent);
                        //增加字体
                        if(!fontUrlMap.has(fontFamily)){
                            fontUrlMap.set(fontFamily, `https://font.eqh5.com/store/fonts/${fontFamily}.woff`);
                        }
                    }
                }
                console.log("特殊字体字:", specialFontMap)
                //文件差异化装载到map中
                try {
                    if(fontUrlMap.size>0){
                        for(let [key,value] of fontUrlMap.entries()) {
                            //console.log(value)
                            if(!fontMap.has(key)){
                                //去重
                                value=value+'?text='+encodeURIComponent(Array.from(new Set(specialFontMap.get(key))).join(''));
                                //获取字体数据
                                fontMap.set(key, await requestFontDate(value));
                                console.log(`字体:${key},别名${fontNickNameMap.get(key)},文件已差异化加载到map`)
                            }
                        }
                        //console.log("fontMap:",fontMap)
                    }else {
                        console.log("本模版使用默认字体!")
                    }
                }catch (e) {
                    console.error("字体map装载出错",e)
                }

            }

        } catch (e) {
            console.warn("模版特殊用字获取失败!导出图片可能为默认字体!");
            console.warn(e);
        }
    }
    /**
     * 根据dom中的style装载字体map,不装载默认字体
     * */
    let initFont=async (defaultFont) => {
        try {
            let styleNodeCollection = document.getElementsByTagName('style');
            let arr = Array.from(styleNodeCollection);
            for (let index = 0; index < arr.length; index++) {
                //序列化
                let styleNode = new XMLSerializer().serializeToString(arr[index]);
                //console.log("styleNode:",styleNode)
                let fontMatches = styleNode.match(/@font-face\s*{[^}]*}/g)
                //console.log(fontMatches)
                if (fontMatches && fontMatches.length > 0) {
                    for(let matchString of fontMatches) {
                        let fontString = matchString;
                        let font;
                        if(fontString.split('font-family:"')[1] && fontString.split('font-family:"')[1].split('";')[0]){
                            font = fontString.split('font-family:"')[1].split('";')[0]
                            console.log("检测到字体:",font);
                        }else {
                            continue;
                        }

                        if (font && !fontUrlMap.has(font) && font!==defaultFont && font!=='element-icons') {
                            //获取这个字体的url
                            //https://font.eqh5.com/store/fonts/ZYADMGWSBZ.woff?text=%E7%88%B1%E7%9A%84%E7%9B%AE%E5%85%89%E6%97%A0%E6%89%80%E4%B8%8D%E5%9C%A8
                            let url = fontString.split('url(')[1].split(')')[0]
                            //纯净url,组装获取所需要的字
                            //url=url.split('?')[0]+'?'+`text=${encodeURIComponent(specialFontMap.get(font))}`;
                            //字体别名存储
                            try {
                                fontNickNameMap.set(font, decodeURIComponent(url.split('text=')[1]))
                            } catch (e) {
                                console.warn("字体别名存储失败!导出字体可能失效!")
                            }
                            //纯净url
                            url=url.split('?text=')[0];
                            console.log(`字体:${font},别名${fontNickNameMap.get(font)} ,对应url:${url}`)

                            fontUrlMap.set(font, url)

                        } else {
                            //console.error(`字体:${font}`)
                        }
                    }
                } else {
                    //console.warn("未找到:@font-face 字段")
                }
            }
            console.log("字体urlMap",fontUrlMap);

        } catch (e) {
            console.error("字体初始化异常!", e)
        }
    }
    /**
     *
     * */
    let getDiv2Svg=(div)=>{
        let mySvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        let divRect=window.getComputedStyle(div);
        div.setAttribute("xmlns",'http://www.w3.org/1999/xhtml');
        mySvg.setAttribute('width', divRect.width+'');
        mySvg.setAttribute('height', divRect.height+'');
        // 创建一个 foreignObject 元素,用于包装原始 div 元素
        let foreignObject = document.createElementNS('http://www.w3.org/2000/svg', 'foreignObject');
        foreignObject.setAttribute('width', '100%');
        foreignObject.setAttribute('height', '100%');
        foreignObject.appendChild(div.cloneNode(true));
        //包含特殊字体处理
        if(divRect.fontFamily && divRect.fontFamily!=="SourceHanSansCN_Normal"){
            mySvg=fontStyleInnerSvg(mySvg,divRect.fontFamily);
        }
        mySvg.appendChild(foreignObject);
        let mySvgData = new XMLSerializer().serializeToString(mySvg);
        return "data:image/svg+xml,"+mySvgData;
    }
    async function domain() {
        console.log('do it')
        console.log('do it')
        /*以下需要等待懒加载全部加载成功后调用*/
        Vue.loading.open('导出中...')
        /*eqc-editor  根元素*/
        //获取根节点
        const editorRoot = document.querySelector('.eqc-editor');
        //获取根节点宽高信息
        const editorRootRect = editorRoot.getBoundingClientRect();
        //获取编辑器中所有子节点,包括水印
        //const childDivALL = editorRoot.children;
        /*根据编辑器 高宽 ,设置画布,最后导出为图片*/

        canvas.width = editorRootRect.width;
        canvas.height = editorRootRect.height;
        //画出背景图  eqc-background
        //无水印获取图片
        //获取背景图div
        try{
            let background_div_0 = editorRoot.children[0].children[1].children[0];
            let background_url;
            if((new XMLSerializer().serializeToString(background_div_0)).includes('background')){
                let myBackground_url = window.getComputedStyle(background_div_0).backgroundImage.split('"')[1].split('"')[0]
                //  图片画质提升
                background_url = await upgradeImage(myBackground_url, 100);
                //console.log("背景图画质提升后url:",background_url);
            }else {
                //转为svg
                try {
                    background_div_0=editorRoot.children[0];
                    background_url=getDiv2Svg(background_div_0);

                }catch (e) {
                    console.warn("背景图转为svg失败!导出图片可能会有缺失!")
                }
            }
            if(!background_url){
                console.warn("背景图url解析失败!")
            }else {
                //同步加载背景图片,并存入队列,防止错位
                let image;
                await new Promise((resolve, reject) => {
                    try {
                        let image = new Image();
                        //防止跨域问题
                        image.setAttribute("crossOrigin", 'anonymous');
                        image.src = background_url;
                        image.msg = '背景图片';
                        resolve(image)
                    } catch (e) {
                        reject(e);
                        console.error('Failed to load image:', e)
                    }
                }).then((v)=>{
                    image=v;
                },r=>{
                    console.log(r)
                    image.msg="null image!"
                })
                console.log(image.msg,",入队!")
                //console.log(image)
                addToDrawQueue(() => {
                    imageLoad(image, canvas, ctx, 0, 0,null,null);
                })
            }
        }catch (e) {
            console.log(e)
        }
        //初始化字体map,排除默认字体
        await initFont("SourceHanSansCN_Normal");
        /**
         * 获取特殊字体
         * */
        await getSpecialFont(templateData);


        /*获取 eqc-elements */
        let eqcEle = document.querySelector('.eqc-elements');
        //获取缩放值
        scale = parseFloat(eqcEle.style.transform.split('(')[1].split(')')[0]);

        //编辑器中的div集合,包裹svg或者图片  .h2-core-check-ele
        const divCollect = document.querySelector('.eqc-elements').querySelectorAll('div')
        let domObjectArr=[];
        await divCollect.forEach((value)=>{
            if(value.style.zIndex){
                domObjectArr.push({
                    dom: value,
                    zIndex: value.style.zIndex
                });
            }
        })
        domObjectArr.sort((a, b) => {
            // 将zIndex转换为数字进行比较
            return parseInt(a.zIndex) - parseInt(b.zIndex);
        });
        console.log("dom元素数组:",domObjectArr)
        //遍历集合,确定其中是svg还是图片,得按顺序入队使用for
        let loopArr=domObjectArr
            for (let key = 0; key < loopArr.length; key++) {
                //console.log("value:", value)
                let value=loopArr[key];
                let svg = value.dom.querySelector('svg');
                let img=value.dom.querySelector('img');
                let div=value.dom.querySelector('div');
                //svg,画到canvas
                if (svg) {
                    //svg中包含image标签的情况
                    let svgImage=svg.querySelectorAll('image');
                    if(svgImage.length > 0){
                        // console.log("svgImage[0]:",svgImage[0]);
                        // console.log(svgImage[0].href.baseVal)
                        svgImage[0].src= await new Promise((resolve, reject) => {
                            // 创建一个 Image 对象
                            const img = new Image();

                            // 允许加载跨域图像
                            img.crossOrigin = "Anonymous";
                            img.src=svgImage[0].href.baseVal
                            // 图像加载完成后的处理
                            img.onload = function () {
                                // 创建一个 Canvas 元素
                                const canvas = document.createElement('canvas');
                                const ctx = canvas.getContext('2d');

                                // 设置 Canvas 的宽度和高度
                                canvas.width = img.width;
                                canvas.height = img.height;

                                // 将图像绘制到 Canvas 上
                                ctx.drawImage(img, 0, 0);

                                // 获取 Canvas 上的图像数据
                                const imageData = canvas.toDataURL('image/png'); // 使用 'image/png' 格式

                                // 解析 Base64 数据并返回
                                resolve(imageData);
                            };

                            // 加载失败的处理
                            img.onerror = function (e) {
                                reject(e);
                            };}
                            )
                        //console.log("svgImage[0].src",svgImage[0].src);
                        // 获取 Canvas 上的图像数据
                         // 使用 'image/png' 格式
                        //svgImage[0].src = myCanvas.toDataURL('image/png');
                    }
                    let svgData = new XMLSerializer().serializeToString(svg);
                    let valueData=new XMLSerializer().serializeToString(value.dom);
                    /* SVG父节点中包含旋转样式的处理*/
                    if(valueData.includes('rotate')){
                        let rect=window.getComputedStyle(value.dom);
                        let matrix=rect.getPropertyValue('transform');
                        let angle=0;
                        if(matrix!==''){
                            let values = matrix.split('(')[1].split(')')[0].split(',');
                            let a = parseFloat(values[0]);
                            let b = parseFloat(values[1]);
                            angle = Math.round(Math.atan2(b, a) * (180/Math.PI));
                            let mySvg=svg.cloneNode(true);
                            mySvg.setAttribute('transform',`rotate(${angle})`);
                            svgData=new XMLSerializer().serializeToString(mySvg);
                        }else {
                            console.warn('图片没有旋转!但进入了旋转适应函数!')
                            return
                        }
                    }
                    //console.log(svgData)
                    /*svg对应字体获取*/
                    //获取字体名
                    if(svgData.includes("font-family"))
                    {
                        let font=svgData.split('font-family:')[1].split(';')[0]
                        try {
                            console.log(`检测到字体:${font}`)
                            if(font!=="SourceHanSansCN_Normal"&& fontMap.has(font)){
                                svgData=new XMLSerializer().serializeToString(fontStyleInnerSvg(svg,font));
                            }
                        }catch (e) {
                            console.warn("字体加载出错!",e)
                        }
                    }
                    //console.log(`svg:${svgData}`)
                    // 将SVG绘制到Canvas上
                    let blob = await new Blob([svgData], {type: 'image/svg+xml;base64,'});
                    //console.log("src:",URL.createObjectURL(blob))
                    await getImage2Queue(URL.createObjectURL(blob),key,svg,value,"svg")

                }
                //img,画到canvas
                else if(img){
                    //console.log("图片地址:",img.src)
                    //水印检查
                    img.src=checkPicWaterMaskUrl(img.src)
                    //质量提升
                    img.src=upgradeImage(img.src,100);
                    await getImage2Queue(img.src,key,img,value,"img");
                }
                else if(div){
                     //有多种情况
                    if(div.children.length>0){
                        //包含的是其他标签
                        //canvas
                        let flag=div.children[0];
                        if(flag instanceof HTMLCanvasElement){
                            //canvas->image->function
                            let canvas = flag;
                            let ctx = canvas.getContext('2d');

                            // 将 Canvas 的背景颜色设置为透明
                            canvas.style.backgroundColor = "transparent";

                            // 获取 Canvas 的宽度和高度
                            let width = canvas.width;
                            let height = canvas.height;

                            // 创建一个新的 Canvas 元素
                            let newCanvas = document.createElement('canvas');
                            let newCtx = newCanvas.getContext('2d');

                            // 设置新 Canvas 的宽度和高度
                            newCanvas.width = width;
                            newCanvas.height = height;

                            // 清空新 Canvas
                            newCtx.clearRect(0, 0, width, height);

                            // 获取 Canvas 上的非透明内容
                            let imageData = ctx.getImageData(0, 0, width, height);

                            // 在新 Canvas 上绘制非透明内容
                            newCtx.putImageData(imageData, 0, 0);

                            // 将新 Canvas 的背景颜色设置为透明
                            newCanvas.style.backgroundColor = "transparent";

                            // 将新 Canvas 转换为图片
                            let url = newCanvas.toDataURL('image/jpeg', 1);
                            await getImage2Queue(url, key, flag, value, "div has canvas");
                            continue;
                        }
                    }
                    // 获取 style 属性值
                    let styleAttr = div.getAttribute('style');
                    // 通过正则表达式匹配背景图片地址
                    let backgroundImageURL = styleAttr.match(/background:\s*url\(['"]?([^'"]*)['"]?\)/);

                    // 提取背景图片地址
                    if (backgroundImageURL && backgroundImageURL.length > 1) {
                        /**
                         * div中包含背景图片的情况
                         * @type {string}
                         */
                        try{
                            let imageURL = backgroundImageURL[1];
                            /**
                             * 检查图片是否带水印   捕获请求
                             * https://emw-api.eqxiu.com/copyright/list-style
                             * https://p1.eqxiu.com/m/print/findProductByIdsWithOrderCheck
                             * */
                            //水印检查
                            imageURL=checkPicWaterMaskUrl(imageURL)
                            //console.log("水印检查后image:",imageURL)
                            //质量提升
                            imageURL=upgradeImage(imageURL,100);
                            //console.log("div has image src:",imageURL);
                            await getImage2Queue(imageURL,key,div,value,"div to image");
                        }catch (e) {
                            console.warn("div背景图片url导出失败!导出图片可能会有缺失!",e)
                        }

                    } else {
                        //console.warn('未找到div内嵌图片地址。');
                        /**
                         * 纯div,div中包含文字
                         */
                        try {
                            let mySvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
                            let divRect=window.getComputedStyle(div);
                            let divData=new XMLSerializer().serializeToString(value.dom);
                            if(divData.includes('rotate')){
                                /* 父节点中包含旋转样式的处理*/
                                let rect=window.getComputedStyle(value.dom);
                                let matrix=rect.getPropertyValue('transform');
                                let angle=0;
                                if(matrix!==''){
                                    let values = matrix.split('(')[1].split(')')[0].split(',');
                                    let a = parseFloat(values[0]);
                                    let b = parseFloat(values[1]);
                                    angle = Math.round(Math.atan2(b, a) * (180/Math.PI));
                                    mySvg.setAttribute('transform',`rotate(${angle})`);
                                    //svgData=new XMLSerializer().serializeToString(svg);
                                }else {
                                    console.warn('图片没有旋转!但进入了旋转适应函数!')
                                    return
                                }

                            }
                            div.setAttribute("xmlns",'http://www.w3.org/1999/xhtml');
                            mySvg.setAttribute('width', divRect.width+'');
                            mySvg.setAttribute('height', divRect.height+'');
                            // 创建一个 foreignObject 元素,用于包装原始 div 元素
                            let foreignObject = document.createElementNS('http://www.w3.org/2000/svg', 'foreignObject');
                            foreignObject.setAttribute('width', '100%');
                            foreignObject.setAttribute('height', '100%');
                            foreignObject.appendChild(div.cloneNode(true));
                            //包含特殊字体处理
                            if(divRect.fontFamily && divRect.fontFamily!=="SourceHanSansCN_Normal"){
                                mySvg=fontStyleInnerSvg(mySvg,divRect.fontFamily);
                            }
                            mySvg.appendChild(foreignObject);
                            let mySvgData = new XMLSerializer().serializeToString(mySvg);
                            // let image=new Image();
                            // image.src="data:image/svg+xml,"+mySvgData;

                            // 将SVG绘制到Canvas上
                            //let myBlob = await new Blob([mySvgData], {type: 'image/svg+xml;charset=utf-8;base64,'});
                            //console.log("div to svg src:","data:image/svg+xml,"+mySvgData);

                            //这里采用数据uri,绕过浏览器检查,使用blob会导致画布被污染。
                            await getImage2Queue("data:image/svg+xml,"+mySvgData,key,div,value,"div to svg")
                        }catch (e) {
                            console.warn("div文字转为svg失败!导出图片可能会有缺失!")
                        }
                    }


                }
            }


        console.log("queue is :",drawQueue);
        //开始执行队列操作
        drawLoop();

    }
    /**
     * @param svgNode svg节点
     * @param font 字体
     * @returns String 序列化svg标签字符串
     * */
    let fontStyleInnerSvg=(svgNode,font)=>{
        /**
         * 尝试将字体嵌入svg标签中
         * */
        try {
            // 创建 <defs> 元素
            let defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
            let style = document.createElementNS("http://www.w3.org/2000/svg", "style");
            style.innerHTML = `
                                @font-face {
                                  font-family: ${font};
                                  src: url(${fontMap.get(font)});
                                  }
                            `;
            /*src: url('${fontMap.get(font)}');
            * src: url(https://asset.eqh5.com/store/0e46c94fe4785f70847594775d3e8cc3.woff format('woff'));
            * */
            defs.appendChild(style);
            // svg.appendChild(defs);
            svgNode.insertBefore(defs, svgNode.childNodes[0]);
            return svgNode;
        }catch (e) {
            console.warn("字体内嵌时错误",e)
        }
    }
    /**
     *
     * @param src 图片src
     * @returns String 重塑的无水印URL或原始URL
     */
    let checkPicWaterMaskUrl=(src)=>{
        let fullPath=src;
        src=fullPath.split('.com/')[1];
        let endPath='';
        if(src.split('?')[1]){
            endPath=src.split('?')[1];
            src=src.split('?')[0];
        }
        //console.log("fullPath,src,endPath",fullPath,src,endPath);
        let temp=src;
        if(templatePicListData.length>0){
            for (const value of templatePicListData) {
                //console.log("picList src:",src)
                //console.log("picList watermarkPath:",value.productTypeMap.watermarkPath)
                if(value.productTypeMap.watermarkPath && src===value.productTypeMap.watermarkPath){
                    src=value.productTypeMap.path!==src?value.productTypeMap.path:(src);
                    if(src===temp){
                        console.warn("图片未去水印!导出图片可能有问题!");
                    }else {
                        console.log("内嵌图已去水印!src:",src);
                    }

                }
            }
        }
        //console.log("检查后src:",realUrl)
        return fullPath.split('.com/')[0] + '.com/' + src + (endPath === '' ? '' : '?' + endPath);
    }


    /**
     *
     * @param url 图片src
     * @param key 顺序标识
     * @param self 自身节点
     * @param parent 父节点,用于获取宽高及起始坐标
     * @param source 来源 div svg img 标签
     * @returns {Promise<void>} 需同步等待操作完成
     */
    let getImage2Queue=async (url,key,self,parent,source,)=> {

        try {
            //console.log("self:",self)
            let imgChild = new Image();
            imgChild.setAttribute("crossOrigin", 'Anonymous');
            imgChild.msg = `子图片[${key}](源:${source})`;
            url=source !== "svg" ? url : upgradeImage(url, 100);
            /*try {
                //图片旋转检查
                if(new XMLSerializer().serializeToString(parent).includes("rotate")){
                    imgChild.src=await getRotatedImage(parent, url);
                }else {
                    imgChild.src = url;
                    console.log('图片不旋转');
                }
            }catch (e) {

            }*/
            imgChild.src = url;
            // 使用 Promise 包装图片加载过程
            await new Promise((resolve, reject) => {
                imgChild.onload = () => resolve();
                imgChild.onerror = reject;
            });
            //console.log(imgChild)
            //画的起始位置,(left,top) * scale
            //通过父元素div获取

            let rect = window.getComputedStyle(parent.dom);
            let rectSelf=window.getComputedStyle(self);
            let left = parseFloat(rect.left) * scale ;
            let top = parseFloat(rect.top) * scale ;
            //console.log("onload:", left, top)
            //  svg的宽高,(width,height) * scale
            //  实际宽高考虑内边距   padding 本元素内边距
            //  ((width,height) - 2*padding) * scale
            let svgWidth = (parseFloat(rect.width) - 2*parseFloat(rectSelf.padding)) * scale;
            let svgHeight = (parseFloat(rect.height) - 2*parseFloat(rectSelf.padding)) * scale;
            console.log(`子svg:层级:${parent.zIndex},开始位置xy和图片宽高(${left},${top},${svgWidth},${svgHeight})`)
            console.log(imgChild.msg, ",入队!")
            addToDrawQueue(() => {
                imageLoad(imgChild, canvas, ctx, left, top, svgWidth, svgHeight).then();
            })

        } catch (e) {
            console.warn("图片宽高设置失败!导出图片可能不正确!",e)
        }
    }

    /**
     * @param nodeParent 父节点
     * @param imageSrc 图片索引
     * @returns image 图片对象
     * */
    let getRotatedImage=async  (nodeParent,imageSrc)=>{
        //使用缓存canvas生成图片后在旋转
        let cacheCanvas=document.createElement("canvas");

        let cacheCtx=cacheCanvas.getContext("2d");
        let image = new Image();
        let rect=window.getComputedStyle(nodeParent);
        cacheCanvas.width=parseFloat(rect.width);
        cacheCanvas.height=parseFloat(rect.height);
        let matrix=rect.getPropertyValue('transform');
        let angle=0;
        if(matrix!==''){
            let values = matrix.split('(')[1].split(')')[0].split(',');
            let a = parseFloat(values[0]);
            let b = parseFloat(values[1]);
            angle = Math.round(Math.atan2(b, a) * (180/Math.PI));
        }else {
            console.warn('图片没有旋转!但进入了旋转适应函数!')
            return
        }
        if(angle!==0){
            //构造图片,并画到临时canvas上
            image.onload=()=>{
                cacheCtx.drawImage(image,0,0,cacheCanvas.width,cacheCanvas.height);
                //旋转角度
                if(angle!==0){
                    cacheCtx.rotate(angle)
                }else {
                    console.warn('未检测到旋转角度!')
                }
                console.log('旋转后的图片:',cacheCanvas.toDataURL('image/jpeg',1));
                return cacheCanvas.toDataURL('image/jpeg',1);
            }

            image.src=imageSrc;
        }

    }
    async function requestFontDate(url) {
        return await new Promise((resolve) => {
            GM_xmlhttpRequest({
                'url': url,
                method: 'GET',
                headers: {
                    "accept": "*/*",
                    "accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
                    "priority": "u=0",
                    "sec-ch-ua": "\"Chromium\";v=\"124\", \"Microsoft Edge\";v=\"124\", \"Not-A.Brand\";v=\"99\"",
                    "sec-ch-ua-mobile": "?0",
                    "sec-ch-ua-platform": "\"Windows\"",
                    "sec-fetch-dest": "font",
                    "sec-fetch-mode": "cors",
                    "sec-fetch-site": "cross-site",
                    "referrer": "https://www.eqxiu.com/",
                    "referrerPolicy": "strict-origin-when-cross-origin",
                    "mode": "cors",
                    "credentials": "omit",
                    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0"
                },
                responseType: 'blob',
                onload: async function (res) {
                    if (res.status===200){
                        console.log(`${url}请求成功!`,res)
                        var byteString = res.response;
                        var arrayBuffer = new Uint8Array(byteString.length);
                        for (var i = 0; i < byteString.length; i++) {
                            arrayBuffer[i] = byteString.charCodeAt(i);
                        }
                       /*if(Base64){
                       }else{
                           console.warn("base64 not load")
                       }*/
                        var blob = new Blob([byteString], { type: 'application/font-woff;charset=utf-8' });
                        var file = new File([blob], 'font.woff', { type: 'application/font-woff;charset=utf-8' });
                        var reader = new FileReader();
                        reader.onload = function(event) {
                            var base64String = event.target.result;
                            //console.log(base64String); // 输出 Base64 编码的字符串
                            resolve(base64String)
                        };
                        reader.readAsDataURL(file);
                    }else{
                        console.log("请求失败!")
                    }
                },
                onerror: function (err) {
                    console.warn('请求错误!' +err.message)
                }
            });
        })
    }


/*let requestFontDate=async (url)=>{
        try{

            let response=await fetch(url, {
                "headers": {
                    "accept": "*!/!*",
                    "accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
                    "priority": "u=0",
                    "sec-ch-ua": "\"Chromium\";v=\"124\", \"Microsoft Edge\";v=\"124\", \"Not-A.Brand\";v=\"99\"",
                    "sec-ch-ua-mobile": "?0",
                    "sec-ch-ua-platform": "\"Windows\"",
                    "sec-fetch-dest": "font",
                    "sec-fetch-mode": "cors",
                    "sec-fetch-site": "cross-site"
                },
                "referrer": "https://www.eqxiu.com/",
                "referrerPolicy": "no-referrer-when-downgrade",
                "body": null,
                "method": "GET",
                "mode": "cors",
                "credentials": "omit"
            })
            if (response.status===200){
                let blob=await response.blob()
                let reader =await new FileReader()
                return new Promise((resolve, reject) => {
                    reader.onload = () => {
                        resolve(reader.result);
                    };
                    reader.onerror = () => {
                        reject(reader.error);
                    };
                    reader.readAsDataURL(blob);
                });
            }else{
                console.log("请求失败!")
            }
        }catch (e) {
            console.warn("请求失败!",e)
        }
    }*/
    /**********************************************************************************/
        /*
        html2canvas(document.getElementsByClassName('eqc-editor')[0]).then(canvas => {
            // 创建一个图片元素
            var img = canvas.toDataURL("image/png");

            // 可以选择将图片添加到页面中
            //var image = document.createElement('img');
            //image.src = img;
            //document.body.appendChild(image);

            // 也可以选择下载图片
            var downloadLink = document.createElement('a');
            downloadLink.href = img;
            downloadLink.download = 'html-snapshot.png';
            downloadLink.click();
        });
         }*/
        /**********************************************************************************/
        /**********************************************************************************/
        /* */
        /**********************************************************************************/
        /* div  to  svg*/
        /**********************************************************************************/
        /* convertToSVG()
         function convertToSVG() {
             const div = document.querySelectorAll('.h2-core-check-ele')[0];
             const svg = convertToSVGElement(div);
             svg.id='mysvg';
             document.body.appendChild(svg);
             svgToImage('mysvg');
         }

         function convertToSVGElement(htmlElement) {
             const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
             const html = new XMLSerializer().serializeToString(htmlElement.cloneNode(true));
             svg.innerHTML = `<foreignObject width="100%" height="100%">${html}</foreignObject>`;
             return svg;
         }*/
        /**********************************************************************************/




    // Your code here...
})();