<commit_before>// Contains most of the display server. Deals with input and output management.
// Doesn't do much without clients to drive it.

(function(exports) {
    "use strict";

    // Several APIs take a bag of properties. If a property is missing
    // from the bag, it's assumed to mean "keep the same value as before".
    // Thus, a value is updated if it's not undefined and not the same
    // as the existing value.
    function valueUpdated(newValue, existingValue) {
        return newValue !== undefined && newValue !== existingValue;
    }

    // A canvas to save data on during resizes.
    var tmpCanvas = document.createElement('canvas');
    var tmpCtx = tmpCanvas.getContext('2d');

    // A Pixmap is essentially a wrapper around a <canvas>, as
    // that's the closest we have to an actual pixel buffer in
    // HTML5.
    var Pixmap = new Class({
        initialize: function() {
            this.canvas = document.createElement('canvas');
            this._ctx = this.canvas.getContext('2d');
            this._refCount = 0;
        },
        _destroy: function() {
            this.canvas.width = 0;
            this.canvas.height = 0;
        },
        ref: function() {
            this._refCount++;
            return this;
        },
        unref: function() {
            if (--this._refCount == 0)
                this._destroy();
        },
        drawTo: function(func) {
            this._ctx.beginPath();
            this._ctx.save();
            func(this._ctx);
            this._ctx.restore();
        },
        resize: function(width, height) {
            if (this.canvas.width == width && this.canvas.height == height)
                return;

            // Save the old pixmap contents
            tmpCanvas.width = this.canvas.width;
            tmpCanvas.height = this.canvas.height;
            tmpCtx.drawImage(this.canvas, 0, 0, this.canvas.width, this.canvas.height);

            // Resize the pixmap canvas
            this.canvas.width = width;
            this.canvas.height = height;

            // And then draw the old contents back on
            this._ctx.drawImage(tmpCanvas, 0, 0, tmpCanvas.width, tmpCanvas.height);

            // And then, to save memory, dump the old image contents
            tmpCanvas.width = 0;
            tmpCanvas.height = 0;
        }
    });

    // The "wire" object for a Pixmap. Pixmaps are used internally in the
    // X server (for e.g. the root window contents), even when they don't
    // have XIDs and aren't exposed on the wire.
    //
    // Thus, a ServerPixmap manages the wire form of a "Pixmap", which means
    // that it keeps track of XIDs, and also exposes helper methods that
    // implement the "Drawable" interface.
    var ServerPixmap = new Class({
        initialize: function(server, pixmap) {
            this._server = server;
            this.xid = this._server.registerXidObject(this);
            this._pixmap = pixmap.ref();
            this._server.pixmapCreated(this);
        },
        destroy: function() {
            this._pixmap.unref();
            this._server.pixmapDestroyed(this);
            this._server.xidDestroyed(this.xid);
        },
        canDraw: function() {
            return true;
        },
        drawTo: function(func) {
            this._pixmap.drawTo(func);
            this._server.pixmapUpdated(this);
        },
        getGeometry: function() {
            return { width: this._pixmap.canvas.width,
                     height: this._pixmap.canvas.height };
        },

        getImage: function() {
            return this._pixmap.canvas;
        },
        getPattern: function() {
            return tmpCtx.createPattern(this._pixmap.canvas, 'repeat');
        },
    });

    // A draw tree manages a subtree of windows where the root owns
    // its own backing pixmap. By default, the only draw tree is
    // the one for the root window.
    //
    // When a window is redirected, it gets a new draw tree where
    // it is the root. Subwindows, instead of painting to the front
    // buffer pixmap, paint to this redirected pixmap instead using
    // the standard clipping semantics.
    var ServerWindowDrawTree = new Class({
        initialize: function(server, rootWindow) {
            this._server = server;
            this._rootWindow = rootWindow;

            this.pixmap = (new Pixmap()).ref();
            this.rootReconfigured();
        },
        destroy: function() {
            this.pixmap.unref();
            this.pixmap = null;
        },

        rootReconfigured: function() {
            this.pixmap.resize(this._rootWindow.width, this._rootWindow.height);
        },

        // A helper method to send an Expose to a window's effective region.
        exposeWindow: function(serverWindow, force, includeChildren) {
            if (!serverWindow.viewable && !force)
                return;

            var region = this.calculateVisibleRegionForWindow(serverWindow, includeChildren);
            this.exposeRegion(region);
            region.finalize();
        },

        // Given a region, walk through the window tree and try to send
        // Expose events to all the windows that are in that region.
        exposeRegion: function(exposedRegion) {
            // inRegion is a modification of inRegion that's translated into
            // serverWindow's parent's space, and clipped to the bounding
            // region of serverWindow's parent.
            var recursivelyExpose = function(serverWindow, inRegion) {
                // Skip mapped windows.
                if (!serverWindow.mapped)
                    return;
                // Skip redirected windows.
                if (serverWindow.drawTree != this)
                    return;

                // The obscuringRegion is the intersection of inRegion that
                // serverWindow will eventually draw to.
                var obscuringRegion = new Region();

                // Translate into serverWindow's space.
                inRegion.translate(-serverWindow.drawTreeX, -serverWindow.drawTreeY);

                // Clip inRegion to the bounding region of our region to get
                // the part of the exposed region that our window tree "owns".
                var bounding = serverWindow.getBoundingRegion();
                obscuringRegion.intersect(inRegion, bounding);
                bounding.finalize();

                // Either us or one of our inferiors is going to handle this
                // region, so subtract it out of inRegion first.
                inRegion.subtract(inRegion, obscuringRegion);

                // Our child windows need to be exposed first -- they'll
                // subtract out parts of inRegion that they're handling and
                // that we should not send exposes to.
                serverWindow.children.forEach(function(serverWindow) {
                    recursivelyExpose(serverWindow, obscuringRegion);
                });

                serverWindow.exposeRegion(obscuringRegion);
                obscuringRegion.finalize();

                // And translate back.
                inRegion.translate(serverWindow.drawTreeX, serverWindow.drawTreeY);
            }.bind(this);

            recursivelyExpose(this._rootWindow, exposedRegion);
        },

        // For a given window, return the region of the draw tree that the
        // window can display pixels on. In the Xorg server parlance, this
        // is known as a window's "clip list".
        //
        // It's the window's bounding region, minus the bounding region of
        // any windows that are occluding this one.
        //
        // If includeChildren is false, the returned region also excludes
        // parts of serverWindow that children are occluding.
        calculateVisibleRegionForWindow: function(serverWindow, includeChildren) {
            var region = serverWindow.calculateTransformedBoundingRegion();

            var subtractWindow = function(aboveWindow) {
                if (!aboveWindow.viewable)
                    return;
                if (aboveWindow.drawTree != this)
                    return;

                var transformedBoundingRegion = aboveWindow.calculateTransformedBoundingRegion();
                region.subtract(region, transformedBoundingRegion);
                transformedBoundingRegion.finalize();
            }.bind(this);

            if (!includeChildren)
                serverWindow.children.forEach(subtractWindow);

            while (serverWindow != null && serverWindow.drawTreeParent != null) {
                var parent = serverWindow.drawTreeParent;
                var idx = parent.children.indexOf(serverWindow);
                var windowsOnTop = parent.children.slice(0, idx);
                windowsOnTop.forEach(subtractWindow);
                serverWindow = parent;
            }
            return region;
        }
    });

    // Is this event something that's included in SubstructureRedirect?
    function isEventSubstructureRedirect(event) {
        switch (event.type) {
        case "MapRequest":
        case "ConfigureRequest":
            return true;
        }
        return false;
    }

    // Is this event something that's included in StructureNotify / SubstructureNotify?
    function isEventStructureNotify(event) {
        switch (event.type) {
        case "MapNotify":
        case "UnmapNotify":
        case "DestroyNotify":
        case "ConfigureNotify":
            return true;
        }
        return false;
    }

    // Is this event a pointer input event? Used for the purposes of
    // routing events when somebody has a pointer grab.
    function isEventPointerInputEvent(event) {
        switch (event.type) {
        case "Enter":
        case "Leave":
        case "ButtonPress":
        case "ButtonRelease":
        case "Motion":
            return true;
        }
        return false;
    }

    // The server-side form of a Window. There's no split like Pixmap, since
    // the X server doesn't maintain any windows that aren't also exposed on the
    // wire in some form.
    var ServerWindow = new Class({
        initialize: function(server, props) {
            this._server = server;
            this.xid = this._server.registerXidObject(this);

            this._backgroundColor = null;
            this._backgroundPixmap = null;
            this._backgroundPattern = null;

            this._unshapedBoundingRegion = new Region();
            this._shapedBoundingRegion = null;
            this._shapedInputRegion = null;

            this.drawTree = null;
            // The draw tree parent is the same as the window tree parent if
            // we share the same draw tree. If our window tree parent doesn't
            // share the same draw tree, then that means we are the root window
            // of our own draw tree.
            this.drawTreeParent = null;

            this._properties = {};
            this._passiveGrabs = {};

            // All child windows, sorted with the top-most window *first*.
            this.children = [];

            this.cursor = '';
            this.mapped = false;
            this.viewable = false;

            this._configureWindow(props);
        },
        finalize: function() {
            this._unshapedBoundingRegion.finalize();
            this._unshapedBoundingRegion = null;

            if (this._shapedBoundingRegion) {
                this._shapedBoundingRegion.finalize();
                this._shapedBoundingRegion = null;
            }
        },

        getBoundingRegion: function() {
            var boundingRegion = new Region();

            if (this._shapedBoundingRegion)
                boundingRegion.intersect(this._shapedBoundingRegion, this._unshapedBoundingRegion);
            else
                boundingRegion.copy(this._unshapedBoundingRegion);

            return boundingRegion;
        },
        calculateTransformedBoundingRegion: function() {
            var region = this.getBoundingRegion();
            var serverWindow = this;
            while (serverWindow != null) {
                var bounding = serverWindow.getBoundingRegion();
                region.intersect(region, bounding);
                region.translate(serverWindow.drawTreeX, serverWindow.drawTreeY);
                bounding.finalize();
                serverWindow = serverWindow.drawTreeParent;
            }
            return region;
        },

        _syncDrawTreePosition: function() {
            if (this.drawTreeParent == null) {
                this.drawTreeX = 0;
                this.drawTreeY = 0;
            } else {
                this.drawTreeX = this.x;
                this.drawTreeY = this.y;
            }
        },
        syncDrawTree: function() {
            var newDrawTree;

            if (this._redirectedDrawTree) {
                newDrawTree = this._redirectedDrawTree;
            } else if (this.windowTreeParent) {
                newDrawTree = this.windowTreeParent.drawTree;
            } else {
                // We are an unparented window or the root window;
                // we have no draw tree.
                newDrawTree = null;
            }

            if (newDrawTree == this.drawTree)
                return;

            this.drawTree = newDrawTree;

            if (this.windowTreeParent && newDrawTree == this.windowTreeParent.drawTree)
                this.drawTreeParent = this.windowTreeParent;
            else
                this.drawTreeParent = null;

            this._syncDrawTreePosition();

            this.children.forEach(function(child) {
                child.syncDrawTree();
            });
        },

        canDraw: function() {
            return this.viewable;
        },
        _getDrawOffset: function() {
            var x = 0, y = 0;
            var serverWindow = this;
            while (serverWindow != null) {
                x += serverWindow.drawTreeX;
                y += serverWindow.drawTreeY;
                serverWindow = serverWindow.drawTreeParent;
            }
            return { x: x, y: y };
        },
        _drawClippedToRegion: function(region, func) {
            this.drawTree.pixmap.drawTo(function(ctx) {
                CanvasUtil.pathFromRegion(ctx, region);
                ctx.clip();
                ctx.beginPath();
                func(ctx);
            }.bind(this));
        },
        _sendDamageEvents: function() {
            var serverWindow = this;
            while (serverWindow != null) {
                this._server.sendEvent({ type: "Damage", windowId: serverWindow.xid });
                serverWindow = serverWindow.drawTreeParent;
            }
        },
        drawTo: function(func) {
            var region = this.drawTree.calculateVisibleRegionForWindow(this, false);
            this._drawClippedToRegion(region, function(ctx) {
                var pos = this._getDrawOffset();
                ctx.translate(pos.x, pos.y);
                func(ctx);
            }.bind(this));
            region.finalize();

            this._sendDamageEvents();
        },
        _drawBackground: function(region) {
            if (!this._backgroundPattern)
                return;

            var pos = this._getDrawOffset();
            region.translate(pos.x, pos.y);
            this._drawClippedToRegion(region, function(ctx) {
                ctx.translate(pos.x, pos.y);
                ctx.fillStyle = this._backgroundPattern;
                ctx.fillRect(0, 0, this.width, this.height);
            }.bind(this));
        },
        exposeRegion: function(region) {
            if (region.is_empty())
                return;

            region.iter_rectangles(function(rect, count) {<commit_msg>server: Send the damage events in _drawClippedToRegion<commit_after>// Contains most of the display server. Deals with input and output management.
// Doesn't do much without clients to drive it.

(function(exports) {
    "use strict";

    // Several APIs take a bag of properties. If a property is missing
    // from the bag, it's assumed to mean "keep the same value as before".
    // Thus, a value is updated if it's not undefined and not the same
    // as the existing value.
    function valueUpdated(newValue, existingValue) {
        return newValue !== undefined && newValue !== existingValue;
    }

    // A canvas to save data on during resizes.
    var tmpCanvas = document.createElement('canvas');
    var tmpCtx = tmpCanvas.getContext('2d');

    // A Pixmap is essentially a wrapper around a <canvas>, as
    // that's the closest we have to an actual pixel buffer in
    // HTML5.
    var Pixmap = new Class({
        initialize: function() {
            this.canvas = document.createElement('canvas');
            this._ctx = this.canvas.getContext('2d');
            this._refCount = 0;
        },
        _destroy: function() {
            this.canvas.width = 0;
            this.canvas.height = 0;
        },
        ref: function() {
            this._refCount++;
            return this;
        },
        unref: function() {
            if (--this._refCount == 0)
                this._destroy();
        },
        drawTo: function(func) {
            this._ctx.beginPath();
            this._ctx.save();
            func(this._ctx);
            this._ctx.restore();
        },
        resize: function(width, height) {
            if (this.canvas.width == width && this.canvas.height == height)
                return;

            // Save the old pixmap contents
            tmpCanvas.width = this.canvas.width;
            tmpCanvas.height = this.canvas.height;
            tmpCtx.drawImage(this.canvas, 0, 0, this.canvas.width, this.canvas.height);

            // Resize the pixmap canvas
            this.canvas.width = width;
            this.canvas.height = height;

            // And then draw the old contents back on
            this._ctx.drawImage(tmpCanvas, 0, 0, tmpCanvas.width, tmpCanvas.height);

            // And then, to save memory, dump the old image contents
            tmpCanvas.width = 0;
            tmpCanvas.height = 0;
        }
    });

    // The "wire" object for a Pixmap. Pixmaps are used internally in the
    // X server (for e.g. the root window contents), even when they don't
    // have XIDs and aren't exposed on the wire.
    //
    // Thus, a ServerPixmap manages the wire form of a "Pixmap", which means
    // that it keeps track of XIDs, and also exposes helper methods that
    // implement the "Drawable" interface.
    var ServerPixmap = new Class({
        initialize: function(server, pixmap) {
            this._server = server;
            this.xid = this._server.registerXidObject(this);
            this._pixmap = pixmap.ref();
            this._server.pixmapCreated(this);
        },
        destroy: function() {
            this._pixmap.unref();
            this._server.pixmapDestroyed(this);
            this._server.xidDestroyed(this.xid);
        },
        canDraw: function() {
            return true;
        },
        drawTo: function(func) {
            this._pixmap.drawTo(func);
            this._server.pixmapUpdated(this);
        },
        getGeometry: function() {
            return { width: this._pixmap.canvas.width,
                     height: this._pixmap.canvas.height };
        },

        getImage: function() {
            return this._pixmap.canvas;
        },
        getPattern: function() {
            return tmpCtx.createPattern(this._pixmap.canvas, 'repeat');
        },
    });

    // A draw tree manages a subtree of windows where the root owns
    // its own backing pixmap. By default, the only draw tree is
    // the one for the root window.
    //
    // When a window is redirected, it gets a new draw tree where
    // it is the root. Subwindows, instead of painting to the front
    // buffer pixmap, paint to this redirected pixmap instead using
    // the standard clipping semantics.
    var ServerWindowDrawTree = new Class({
        initialize: function(server, rootWindow) {
            this._server = server;
            this._rootWindow = rootWindow;

            this.pixmap = (new Pixmap()).ref();
            this.rootReconfigured();
        },
        destroy: function() {
            this.pixmap.unref();
            this.pixmap = null;
        },

        rootReconfigured: function() {
            this.pixmap.resize(this._rootWindow.width, this._rootWindow.height);
        },

        // A helper method to send an Expose to a window's effective region.
        exposeWindow: function(serverWindow, force, includeChildren) {
            if (!serverWindow.viewable && !force)
                return;

            var region = this.calculateVisibleRegionForWindow(serverWindow, includeChildren);
            this.exposeRegion(region);
            region.finalize();
        },

        // Given a region, walk through the window tree and try to send
        // Expose events to all the windows that are in that region.
        exposeRegion: function(exposedRegion) {
            // inRegion is a modification of inRegion that's translated into
            // serverWindow's parent's space, and clipped to the bounding
            // region of serverWindow's parent.
            var recursivelyExpose = function(serverWindow, inRegion) {
                // Skip mapped windows.
                if (!serverWindow.mapped)
                    return;
                // Skip redirected windows.
                if (serverWindow.drawTree != this)
                    return;

                // The obscuringRegion is the intersection of inRegion that
                // serverWindow will eventually draw to.
                var obscuringRegion = new Region();

                // Translate into serverWindow's space.
                inRegion.translate(-serverWindow.drawTreeX, -serverWindow.drawTreeY);

                // Clip inRegion to the bounding region of our region to get
                // the part of the exposed region that our window tree "owns".
                var bounding = serverWindow.getBoundingRegion();
                obscuringRegion.intersect(inRegion, bounding);
                bounding.finalize();

                // Either us or one of our inferiors is going to handle this
                // region, so subtract it out of inRegion first.
                inRegion.subtract(inRegion, obscuringRegion);

                // Our child windows need to be exposed first -- they'll
                // subtract out parts of inRegion that they're handling and
                // that we should not send exposes to.
                serverWindow.children.forEach(function(serverWindow) {
                    recursivelyExpose(serverWindow, obscuringRegion);
                });

                serverWindow.exposeRegion(obscuringRegion);
                obscuringRegion.finalize();

                // And translate back.
                inRegion.translate(serverWindow.drawTreeX, serverWindow.drawTreeY);
            }.bind(this);

            recursivelyExpose(this._rootWindow, exposedRegion);
        },

        // For a given window, return the region of the draw tree that the
        // window can display pixels on. In the Xorg server parlance, this
        // is known as a window's "clip list".
        //
        // It's the window's bounding region, minus the bounding region of
        // any windows that are occluding this one.
        //
        // If includeChildren is false, the returned region also excludes
        // parts of serverWindow that children are occluding.
        calculateVisibleRegionForWindow: function(serverWindow, includeChildren) {
            var region = serverWindow.calculateTransformedBoundingRegion();

            var subtractWindow = function(aboveWindow) {
                if (!aboveWindow.viewable)
                    return;
                if (aboveWindow.drawTree != this)
                    return;

                var transformedBoundingRegion = aboveWindow.calculateTransformedBoundingRegion();
                region.subtract(region, transformedBoundingRegion);
                transformedBoundingRegion.finalize();
            }.bind(this);

            if (!includeChildren)
                serverWindow.children.forEach(subtractWindow);

            while (serverWindow != null && serverWindow.drawTreeParent != null) {
                var parent = serverWindow.drawTreeParent;
                var idx = parent.children.indexOf(serverWindow);
                var windowsOnTop = parent.children.slice(0, idx);
                windowsOnTop.forEach(subtractWindow);
                serverWindow = parent;
            }
            return region;
        }
    });

    // Is this event something that's included in SubstructureRedirect?
    function isEventSubstructureRedirect(event) {
        switch (event.type) {
        case "MapRequest":
        case "ConfigureRequest":
            return true;
        }
        return false;
    }

    // Is this event something that's included in StructureNotify / SubstructureNotify?
    function isEventStructureNotify(event) {
        switch (event.type) {
        case "MapNotify":
        case "UnmapNotify":
        case "DestroyNotify":
        case "ConfigureNotify":
            return true;
        }
        return false;
    }

    // Is this event a pointer input event? Used for the purposes of
    // routing events when somebody has a pointer grab.
    function isEventPointerInputEvent(event) {
        switch (event.type) {
        case "Enter":
        case "Leave":
        case "ButtonPress":
        case "ButtonRelease":
        case "Motion":
            return true;
        }
        return false;
    }

    // The server-side form of a Window. There's no split like Pixmap, since
    // the X server doesn't maintain any windows that aren't also exposed on the
    // wire in some form.
    var ServerWindow = new Class({
        initialize: function(server, props) {
            this._server = server;
            this.xid = this._server.registerXidObject(this);

            this._backgroundColor = null;
            this._backgroundPixmap = null;
            this._backgroundPattern = null;

            this._unshapedBoundingRegion = new Region();
            this._shapedBoundingRegion = null;
            this._shapedInputRegion = null;

            this.drawTree = null;
            // The draw tree parent is the same as the window tree parent if
            // we share the same draw tree. If our window tree parent doesn't
            // share the same draw tree, then that means we are the root window
            // of our own draw tree.
            this.drawTreeParent = null;

            this._properties = {};
            this._passiveGrabs = {};

            // All child windows, sorted with the top-most window *first*.
            this.children = [];

            this.cursor = '';
            this.mapped = false;
            this.viewable = false;

            this._configureWindow(props);
        },
        finalize: function() {
            this._unshapedBoundingRegion.finalize();
            this._unshapedBoundingRegion = null;

            if (this._shapedBoundingRegion) {
                this._shapedBoundingRegion.finalize();
                this._shapedBoundingRegion = null;
            }
        },

        getBoundingRegion: function() {
            var boundingRegion = new Region();

            if (this._shapedBoundingRegion)
                boundingRegion.intersect(this._shapedBoundingRegion, this._unshapedBoundingRegion);
            else
                boundingRegion.copy(this._unshapedBoundingRegion);

            return boundingRegion;
        },
        calculateTransformedBoundingRegion: function() {
            var region = this.getBoundingRegion();
            var serverWindow = this;
            while (serverWindow != null) {
                var bounding = serverWindow.getBoundingRegion();
                region.intersect(region, bounding);
                region.translate(serverWindow.drawTreeX, serverWindow.drawTreeY);
                bounding.finalize();
                serverWindow = serverWindow.drawTreeParent;
            }
            return region;
        },

        _syncDrawTreePosition: function() {
            if (this.drawTreeParent == null) {
                this.drawTreeX = 0;
                this.drawTreeY = 0;
            } else {
                this.drawTreeX = this.x;
                this.drawTreeY = this.y;
            }
        },
        syncDrawTree: function() {
            var newDrawTree;

            if (this._redirectedDrawTree) {
                newDrawTree = this._redirectedDrawTree;
            } else if (this.windowTreeParent) {
                newDrawTree = this.windowTreeParent.drawTree;
            } else {
                // We are an unparented window or the root window;
                // we have no draw tree.
                newDrawTree = null;
            }

            if (newDrawTree == this.drawTree)
                return;

            this.drawTree = newDrawTree;

            if (this.windowTreeParent && newDrawTree == this.windowTreeParent.drawTree)
                this.drawTreeParent = this.windowTreeParent;
            else
                this.drawTreeParent = null;

            this._syncDrawTreePosition();

            this.children.forEach(function(child) {
                child.syncDrawTree();
            });
        },

        canDraw: function() {
            return this.viewable;
        },
        _getDrawOffset: function() {
            var x = 0, y = 0;
            var serverWindow = this;
            while (serverWindow != null) {
                x += serverWindow.drawTreeX;
                y += serverWindow.drawTreeY;
                serverWindow = serverWindow.drawTreeParent;
            }
            return { x: x, y: y };
        },
        _drawClippedToRegion: function(region, func) {
            this.drawTree.pixmap.drawTo(function(ctx) {
                CanvasUtil.pathFromRegion(ctx, region);
                ctx.clip();
                ctx.beginPath();
                func(ctx);
            }.bind(this));

            this._sendDamageEvents();
        },
        _sendDamageEvents: function() {
            var serverWindow = this;
            while (serverWindow != null) {
                this._server.sendEvent({ type: "Damage", windowId: serverWindow.xid });
                serverWindow = serverWindow.drawTreeParent;
            }
        },
        drawTo: function(func) {
            var region = this.drawTree.calculateVisibleRegionForWindow(this, false);
            this._drawClippedToRegion(region, function(ctx) {
                var pos = this._getDrawOffset();
                ctx.translate(pos.x, pos.y);
                func(ctx);
            }.bind(this));
            region.finalize();
        },
        _drawBackground: function(region) {
            if (!this._backgroundPattern)
                return;

            var pos = this._getDrawOffset();
            region.translate(pos.x, pos.y);
            this._drawClippedToRegion(region, function(ctx) {
                ctx.translate(pos.x, pos.y);
                ctx.fillStyle = this._backgroundPattern;
                ctx.fillRect(0, 0, this.width, this.height);
            }.bind(this));
        },
        exposeRegion: function(region) {
            if (region.is_empty())
                return;

            region.iter_rectangles(function(rect, count) {
{'fraction_non_alphanumeric': 0.089729387561323, 'fraction_numerical': 0.001266023104921665, 'mean_word_length': 1.6810352142554095, 'pattern_counts': {'":': 22, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, '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': '1162803', 'n_tokens_mistral': 8057, 'n_tokens_neox': 7614, 'n_words': 2232}