易企秀海报导出图片

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

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください。
// ==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...
})();