<commit_before>
namespace pxt.blocks.layout {
    export interface FlowOptions {
        ratio?: number;
        useViewWidth?: boolean;
    }

    export function patchBlocksFromOldWorkspace(blockInfo: ts.pxtc.BlocksInfo, oldWs: Blockly.Workspace, newXml: string): string {
        const newWs = pxt.blocks.loadWorkspaceXml(newXml, true);
        // position blocks
        alignBlocks(blockInfo, oldWs, newWs);
        // inject disabled blocks
        return injectDisabledBlocks(oldWs, newWs);
    }

    function injectDisabledBlocks(oldWs: Blockly.Workspace, newWs: Blockly.Workspace): string {
        const oldDom = Blockly.Xml.workspaceToDom(oldWs, true);
        const newDom = Blockly.Xml.workspaceToDom(newWs, true);
        Util.toArray(oldDom.childNodes)
            .filter(n => n.nodeType == Node.ELEMENT_NODE && n.localName == "block" && (<Element>n).getAttribute("disabled") == "true")
            .forEach(n => newDom.appendChild(newDom.ownerDocument.importNode(n, true)));
        const updatedXml = Blockly.Xml.domToText(newDom);
        return updatedXml;
    }

    function alignBlocks(blockInfo: ts.pxtc.BlocksInfo, oldWs: Blockly.Workspace, newWs: Blockly.Workspace) {
        let env: pxt.blocks.Environment;
        let newBlocks: pxt.Map<Blockly.Block[]>; // support for multiple events with similar name
        oldWs.getTopBlocks(false).filter(ob => !ob.disabled)
            .forEach(ob => {
                const otp = ob.xy_;
                if (otp && otp.x != 0 && otp.y != 0) {
                    if (!env) {
                        env = pxt.blocks.mkEnv(oldWs, blockInfo, true);
                        newBlocks = {};
                        newWs.getTopBlocks(false).forEach(b => {
                            const nkey = pxt.blocks.callKey(env, b);
                            const nbs = newBlocks[nkey] || [];
                            nbs.push(b);
                            newBlocks[nkey] = nbs;
                        });
                    }
                    const oldKey = pxt.blocks.callKey(env, ob);
                    const newBlock = (newBlocks[oldKey] || []).shift();
                    if (newBlock)
                        newBlock.xy_ = otp.clone();
                }
            })
    }

    declare function unescape(escapeUri: string): string;

    export function verticalAlign(ws: Blockly.Workspace, emPixels: number) {
        let y = 0
        let comments = ws.getTopComments(true);
        comments.forEach(comment => {
            comment.moveBy(0, y)
            y += comment.getHeightWidth().height
            y += emPixels; //buffer
        })
        let blocks = ws.getTopBlocks(true);
        blocks.forEach(block => {
            block.moveBy(0, y)
            y += block.getHeightWidth().height
            y += emPixels; //buffer
        })
    };

    export function flow(ws: Blockly.Workspace, opts?: FlowOptions) {
        if (opts) {
            if (opts.useViewWidth) {
                const metrics = ws.getMetrics();

                // Only use the width if in portrait, otherwise the blocks are too spread out
                if (metrics.viewHeight > metrics.viewWidth) {
                    flowBlocks(ws.getTopComments(true), ws.getTopBlocks(true), undefined, metrics.viewWidth)
                    return;
                }
            }
            flowBlocks(ws.getTopComments(true), ws.getTopBlocks(true), opts.ratio);
        }
        else {
            flowBlocks(ws.getTopComments(true), ws.getTopBlocks(true));
        }
    }

    export function screenshotEnabled(): boolean {
        return !BrowserUtils.isIE()
            && !BrowserUtils.isUwpEdge(); // TODO figure out why screenshots are not working in UWP; disable for now
    }

    export function screenshotAsync(ws: Blockly.Workspace): Promise<string> {
        return toPngAsync(ws);
    }

    export function toPngAsync(ws: Blockly.Workspace): Promise<string> {
        return toSvgAsync(ws)
            .then(sg => {
                if (!sg) return Promise.resolve<string>(undefined);
                return toPngAsyncInternal(sg.width, sg.height, 4, sg.xml);
            });
    }

    export function svgToPngAsync(svg: SVGElement, x: number, y: number, width: number, height: number, pixelDensity: number): Promise<string> {
        return blocklyToSvgAsync(svg, x, y, width, height)
            .then(sg => {
                if (!sg) return Promise.resolve<string>(undefined);
                return toPngAsyncInternal(sg.width, sg.height, pixelDensity, sg.xml);
            });
    }

    function toPngAsyncInternal(width: number, height: number, pixelDensity: number, data: string): Promise<string> {
        return new Promise<string>((resolve, reject) => {
            const cvs = document.createElement("canvas") as HTMLCanvasElement;
            const ctx = cvs.getContext("2d");
            const img = new Image;

            cvs.width = width * pixelDensity;
            cvs.height = height * pixelDensity;
            img.onload = function () {
                ctx.drawImage(img, 0, 0, width, height, 0, 0, cvs.width, cvs.height);
                const canvasdata = cvs.toDataURL("image/png");
                resolve(canvasdata);
            };
            img.onerror = ev => {
                pxt.reportError("blocks", "blocks screenshot failed");
                resolve(undefined)
            }
            img.src = data;
        })
    }

    const XLINK_NAMESPACE = "http://www.w3.org/1999/xlink";

    export function toSvgAsync(ws: Blockly.Workspace): Promise<{
        width: number; height: number; xml: string;
    }> {
        if (!ws)
            return Promise.resolve<{ width: number; height: number; xml: string; }>(undefined);

        const bbox = (document.getElementsByClassName("blocklyBlockCanvas")[0] as any).getBBox();
        let sg = (ws as any).svgBlockCanvas_.cloneNode(true) as SVGGElement;


        return blocklyToSvgAsync(sg, bbox.x, bbox.y, bbox.width, bbox.height);
    }

    export function serializeNode(sg: Node): string {
        return serializeSvgString(new XMLSerializer().serializeToString(sg));
    }

    export function serializeSvgString(xmlString: string): string {
        return xmlString
            .replace(new RegExp('&nbsp;','g'), '&#160;'); // Replace &nbsp; with &#160; as a workaround for having nbsp missing from SVG xml
    }

    export interface BlockSvg {
        width: number; height: number; svg: string; xml: string; css: string;
    }

    export function blocklyToSvgAsync(sg: SVGElement, x: number, y: number, width: number, height: number): Promise<BlockSvg> {
        if (!sg.childNodes[0])
            return Promise.resolve<BlockSvg>(undefined);

        sg.removeAttribute("width");
        sg.removeAttribute("height");
        sg.removeAttribute("transform");

        const xmlString = serializeNode(sg)
            .replace(/^\s*<svg[^>]+>/i, '')
            .replace(/<\/svg>\s*$/i, '') // strip out svg tag
        const svgXml = `<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="${XLINK_NAMESPACE}" width="${width}" height="${height}" viewBox="${x} ${y} ${width} ${height}">${xmlString}</svg>`;
        const xsg = new DOMParser().parseFromString(svgXml, "image/svg+xml");<commit_msg>reduce screenshot size until reasonably small image is generated (#4675)<commit_after>
namespace pxt.blocks.layout {
    export interface FlowOptions {
        ratio?: number;
        useViewWidth?: boolean;
    }

    export function patchBlocksFromOldWorkspace(blockInfo: ts.pxtc.BlocksInfo, oldWs: Blockly.Workspace, newXml: string): string {
        const newWs = pxt.blocks.loadWorkspaceXml(newXml, true);
        // position blocks
        alignBlocks(blockInfo, oldWs, newWs);
        // inject disabled blocks
        return injectDisabledBlocks(oldWs, newWs);
    }

    function injectDisabledBlocks(oldWs: Blockly.Workspace, newWs: Blockly.Workspace): string {
        const oldDom = Blockly.Xml.workspaceToDom(oldWs, true);
        const newDom = Blockly.Xml.workspaceToDom(newWs, true);
        Util.toArray(oldDom.childNodes)
            .filter(n => n.nodeType == Node.ELEMENT_NODE && n.localName == "block" && (<Element>n).getAttribute("disabled") == "true")
            .forEach(n => newDom.appendChild(newDom.ownerDocument.importNode(n, true)));
        const updatedXml = Blockly.Xml.domToText(newDom);
        return updatedXml;
    }

    function alignBlocks(blockInfo: ts.pxtc.BlocksInfo, oldWs: Blockly.Workspace, newWs: Blockly.Workspace) {
        let env: pxt.blocks.Environment;
        let newBlocks: pxt.Map<Blockly.Block[]>; // support for multiple events with similar name
        oldWs.getTopBlocks(false).filter(ob => !ob.disabled)
            .forEach(ob => {
                const otp = ob.xy_;
                if (otp && otp.x != 0 && otp.y != 0) {
                    if (!env) {
                        env = pxt.blocks.mkEnv(oldWs, blockInfo, true);
                        newBlocks = {};
                        newWs.getTopBlocks(false).forEach(b => {
                            const nkey = pxt.blocks.callKey(env, b);
                            const nbs = newBlocks[nkey] || [];
                            nbs.push(b);
                            newBlocks[nkey] = nbs;
                        });
                    }
                    const oldKey = pxt.blocks.callKey(env, ob);
                    const newBlock = (newBlocks[oldKey] || []).shift();
                    if (newBlock)
                        newBlock.xy_ = otp.clone();
                }
            })
    }

    declare function unescape(escapeUri: string): string;

    export function verticalAlign(ws: Blockly.Workspace, emPixels: number) {
        let y = 0
        let comments = ws.getTopComments(true);
        comments.forEach(comment => {
            comment.moveBy(0, y)
            y += comment.getHeightWidth().height
            y += emPixels; //buffer
        })
        let blocks = ws.getTopBlocks(true);
        blocks.forEach(block => {
            block.moveBy(0, y)
            y += block.getHeightWidth().height
            y += emPixels; //buffer
        })
    };

    export function flow(ws: Blockly.Workspace, opts?: FlowOptions) {
        if (opts) {
            if (opts.useViewWidth) {
                const metrics = ws.getMetrics();

                // Only use the width if in portrait, otherwise the blocks are too spread out
                if (metrics.viewHeight > metrics.viewWidth) {
                    flowBlocks(ws.getTopComments(true), ws.getTopBlocks(true), undefined, metrics.viewWidth)
                    return;
                }
            }
            flowBlocks(ws.getTopComments(true), ws.getTopBlocks(true), opts.ratio);
        }
        else {
            flowBlocks(ws.getTopComments(true), ws.getTopBlocks(true));
        }
    }

    export function screenshotEnabled(): boolean {
        return !BrowserUtils.isIE()
            && !BrowserUtils.isUwpEdge(); // TODO figure out why screenshots are not working in UWP; disable for now
    }

    export function screenshotAsync(ws: Blockly.Workspace): Promise<string> {
        return toPngAsync(ws);
    }

    export function toPngAsync(ws: Blockly.Workspace): Promise<string> {
        return toSvgAsync(ws)
            .then(sg => {
                if (!sg) return Promise.resolve<string>(undefined);
                return toPngAsyncInternal(sg.width, sg.height, 4, sg.xml);
            });
    }

    export function svgToPngAsync(svg: SVGElement, x: number, y: number, width: number, height: number, pixelDensity: number): Promise<string> {
        return blocklyToSvgAsync(svg, x, y, width, height)
            .then(sg => {
                if (!sg) return Promise.resolve<string>(undefined);
                return toPngAsyncInternal(sg.width, sg.height, pixelDensity, sg.xml);
            });
    }

    const MAX_SCREENSHOT_SIZE = 1e6; // max 1Mb
    function toPngAsyncInternal(width: number, height: number, pixelDensity: number, data: string): Promise<string> {
        return new Promise<string>((resolve, reject) => {
            const cvs = document.createElement("canvas") as HTMLCanvasElement;
            const ctx = cvs.getContext("2d");
            const img = new Image;

            cvs.width = width * pixelDensity;
            cvs.height = height * pixelDensity;
            img.onload = function () {
                ctx.drawImage(img, 0, 0, width, height, 0, 0, cvs.width, cvs.height);
                let canvasdata = cvs.toDataURL("image/png");
                // if the generated image is too big, shrink image
                while (canvasdata.length > MAX_SCREENSHOT_SIZE) {
                    cvs.width = (cvs.width / 2) >> 0;
                    cvs.height = (cvs.height / 2) >> 0;
                    pxt.log(`screenshot size ${canvasdata.length}b, shrinking to ${cvs.width}x${cvs.height}`)
                    ctx.drawImage(img, 0, 0, width, height, 0, 0, cvs.width, cvs.height);
                    canvasdata = cvs.toDataURL("image/png");
                }
                resolve(canvasdata);
            };
            img.onerror = ev => {
                pxt.reportError("blocks", "blocks screenshot failed");
                resolve(undefined)
            }
            img.src = data;
        })
    }

    const XLINK_NAMESPACE = "http://www.w3.org/1999/xlink";

    export function toSvgAsync(ws: Blockly.Workspace): Promise<{
        width: number; height: number; xml: string;
    }> {
        if (!ws)
            return Promise.resolve<{ width: number; height: number; xml: string; }>(undefined);

        const bbox = (document.getElementsByClassName("blocklyBlockCanvas")[0] as any).getBBox();
        let sg = (ws as any).svgBlockCanvas_.cloneNode(true) as SVGGElement;


        return blocklyToSvgAsync(sg, bbox.x, bbox.y, bbox.width, bbox.height);
    }

    export function serializeNode(sg: Node): string {
        return serializeSvgString(new XMLSerializer().serializeToString(sg));
    }

    export function serializeSvgString(xmlString: string): string {
        return xmlString
            .replace(new RegExp('&nbsp;', 'g'), '&#160;'); // Replace &nbsp; with &#160; as a workaround for having nbsp missing from SVG xml
    }

    export interface BlockSvg {
        width: number; height: number; svg: string; xml: string; css: string;
    }

    export function blocklyToSvgAsync(sg: SVGElement, x: number, y: number, width: number, height: number): Promise<BlockSvg> {
        if (!sg.childNodes[0])
            return Promise.resolve<BlockSvg>(undefined);

        sg.removeAttribute("width");
        sg.removeAttribute("height");
        sg.removeAttribute("transform");

        const xmlString = serializeNode(sg)
            .replace(/^\s*<svg[^>]+>/i, '')
            .replace(/<\/svg>\s*$/i, '') // strip out svg tag
        const svgXml = `<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="${XLINK_NAMESPACE}" width="${width}" height="${height}" viewBox="${x} ${y} ${width} ${height}">${xmlString}</svg>`;
        const xsg = new DOMParser().parseFromString(svgXml, "image/svg+xml");
{'fraction_non_alphanumeric': 0.12547728768926925, 'fraction_numerical': 0.005069124423963134, 'mean_word_length': 2.08760162601626, 'pattern_counts': {'":': 0, '<': 37, '<?xml version=': 0, '>': 68, 'https://': 0, 'lorem ipsum': 0, 'www.': 4, 'xml': 26}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4090927', 'n_tokens_mistral': 4460, 'n_tokens_neox': 4255, 'n_words': 987}