<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

import {CameraState} from "../model/CameraState";

// Constant: the Sozi namespace
var SVG_NS = "http://www.w3.org/2000/svg";

export var Camera = Object.create(CameraState);

Camera.init = function (viewport, layer) {
    CameraState.init.call(this, viewport.svgRoot);

    this.viewport = viewport;
    this.layer = layer;
    this.selected = true;

    // The clipping rectangle of this camera
    this.svgClipRect = document.createElementNS(SVG_NS, "rect");

    if (viewport.editMode) {
        // In edit mode, we set up a mask outside the clipping rectangle.
        // This value allows to set the opacity of the mask.
        this.maskValue = 0;

        var svgMask = document.createElementNS(SVG_NS, "mask");
        var svgMaskId = viewport.makeUniqueId("sozi-mask-");
        svgMask.setAttribute("id", svgMaskId);
        viewport.svgRoot.appendChild(svgMask);

        this.svgMaskRect = document.createElementNS(SVG_NS, "rect");
        svgMask.appendChild(this.svgMaskRect);

        this.svgClipRect.setAttribute("fill", "white");
        svgMask.appendChild(this.svgClipRect);

        // We also define two rectangles that will show the outline
        // of the clipped region in alternating white and black dashes.
        this.svgClipOutlineRect1 = document.createElementNS(SVG_NS, "rect");
        this.svgClipOutlineRect1.setAttribute("stroke", "black");
        this.svgClipOutlineRect1.setAttribute("fill", "none");
        viewport.svgRoot.appendChild(this.svgClipOutlineRect1);

        this.svgClipOutlineRect2 = document.createElementNS(SVG_NS, "rect");
        this.svgClipOutlineRect2.setAttribute("stroke", "white");
        this.svgClipOutlineRect2.setAttribute("fill", "none");
        this.svgClipOutlineRect2.setAttribute("stroke-dasharray", "2,2");
        viewport.svgRoot.appendChild(this.svgClipOutlineRect2);

        this.concealClipping();
    }
    else {
        // When playing the presentation, we use the default SVG
        // clipping technique.
        var svgClipPath = document.createElementNS(SVG_NS, "clipPath");
        var svgClipPathId = viewport.makeUniqueId("sozi-clip-path-");
        svgClipPath.setAttribute("id", svgClipPathId);
        svgClipPath.appendChild(this.svgClipRect);
        viewport.svgRoot.appendChild(svgClipPath);
    }

    // The groups that will support transformations
    this.svgTransformGroups = layer.svgNodes.map(svgNode => {
        // The group that will support the clipping operation
        var svgClippedGroup = document.createElementNS(SVG_NS, "g");
        viewport.svgRoot.insertBefore(svgClippedGroup, svgNode);

        if (viewport.editMode) {
            svgClippedGroup.setAttribute("mask", "url(#" + svgMaskId + ")");
        }
        else {
            svgClippedGroup.setAttribute("clip-path", "url(#" + svgClipPathId + ")");
        }

        var svgGroup = document.createElementNS(SVG_NS, "g");
        svgGroup.appendChild(svgNode);
        svgClippedGroup.appendChild(svgGroup);
        return svgGroup;
    });

    return this;
};

Camera.revealClipping = function () {
    this.maskValue = 64;
    this.svgClipOutlineRect1.style.display = "inline";
    this.svgClipOutlineRect2.style.display = "inline";
};

Camera.concealClipping = function () {
    this.maskValue = 0;
    this.svgClipOutlineRect1.style.display = "none";
    this.svgClipOutlineRect2.style.display = "none";
};

Object.defineProperty(Camera, "scale", {
    get() {
        return Math.min(this.viewport.width / this.width, this.viewport.height / this.height);
    }
});

Camera.rotate = function (angle) {
    this.restoreAspectRatio();
    return this.setAngle(this.angle + angle).update();
};

Camera.zoom = function (factor, x, y) {
    this.width /= factor;
    this.height /= factor;
    this.restoreAspectRatio();
    return this.translate(
        (1 - factor) * (x - this.viewport.width  / 2),
        (1 - factor) * (y - this.viewport.height / 2)
    );
};

Camera.translate = function (deltaX, deltaY) {
    var scale = this.scale;
    var angleRad = this.angle * Math.PI / 180;
    var si = Math.sin(angleRad);
    var co = Math.cos(angleRad);
    this.cx -= (deltaX * co - deltaY * si) / scale;
    this.cy -= (deltaX * si + deltaY * co) / scale;
    this.restoreAspectRatio();
    return this.update();
};

Camera.clip = function (x0, y0, x1, y1) {
    this.clipped = true;
    var scale = this.scale;
    var clipWidth = Math.abs(x1 - x0) + 1;
    var clipHeight = Math.abs(y1 - y0) + 1;
    this.clipXOffset = (Math.min(x0, x1) - (this.viewport.width  - clipWidth)  / 2) * this.width  / clipWidth;
    this.clipYOffset = (Math.min(y0, y1) - (this.viewport.height - clipHeight) / 2) * this.height / clipHeight;
    this.clipWidthFactor  = clipWidth  / this.width  / scale;
    this.clipHeightFactor = clipHeight / this.height / scale;
    return this.update();
};

Camera.restoreAspectRatio = function () {
    var viewportRatio = this.viewport.width / this.viewport.height;
    var camRatio = this.width / this.height;
    var ratio = viewportRatio / camRatio;
    if (ratio > 1) {
        this.width *= ratio;
        if (this.clipped) {
            this.clipWidthFactor /= ratio;
        }
    }
    else {
        this.height /= ratio;
        if (this.clipped) {
            this.clipHeightFactor *= ratio;
        }
    }
};

Camera.getCandidateReferenceElement = function () {
    // getIntersectionList is not supported in Gecko
    if (!this.svgRoot.getIntersectionList) {
        return this.svgRoot;
    }

    // Get all elements that intersect with the viewport.
    var viewportRect = this.svgRoot.createSVGRect();
    viewportRect.x = 0;
    viewportRect.y = 0;
    viewportRect.width = this.viewport.width;
    viewportRect.height = this.viewport.height;
    var viewportArea = this.viewport.width * this.viewport.height;

    var intersectionList = this.svgRoot.getIntersectionList(viewportRect, this.layer.svgNodes[0]);
<commit_msg>Fixes #320 - Return root element as candidate reference element in auto layer<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

import {CameraState} from "../model/CameraState";

// Constant: the Sozi namespace
var SVG_NS = "http://www.w3.org/2000/svg";

export var Camera = Object.create(CameraState);

Camera.init = function (viewport, layer) {
    CameraState.init.call(this, viewport.svgRoot);

    this.viewport = viewport;
    this.layer = layer;
    this.selected = true;

    // The clipping rectangle of this camera
    this.svgClipRect = document.createElementNS(SVG_NS, "rect");

    if (viewport.editMode) {
        // In edit mode, we set up a mask outside the clipping rectangle.
        // This value allows to set the opacity of the mask.
        this.maskValue = 0;

        var svgMask = document.createElementNS(SVG_NS, "mask");
        var svgMaskId = viewport.makeUniqueId("sozi-mask-");
        svgMask.setAttribute("id", svgMaskId);
        viewport.svgRoot.appendChild(svgMask);

        this.svgMaskRect = document.createElementNS(SVG_NS, "rect");
        svgMask.appendChild(this.svgMaskRect);

        this.svgClipRect.setAttribute("fill", "white");
        svgMask.appendChild(this.svgClipRect);

        // We also define two rectangles that will show the outline
        // of the clipped region in alternating white and black dashes.
        this.svgClipOutlineRect1 = document.createElementNS(SVG_NS, "rect");
        this.svgClipOutlineRect1.setAttribute("stroke", "black");
        this.svgClipOutlineRect1.setAttribute("fill", "none");
        viewport.svgRoot.appendChild(this.svgClipOutlineRect1);

        this.svgClipOutlineRect2 = document.createElementNS(SVG_NS, "rect");
        this.svgClipOutlineRect2.setAttribute("stroke", "white");
        this.svgClipOutlineRect2.setAttribute("fill", "none");
        this.svgClipOutlineRect2.setAttribute("stroke-dasharray", "2,2");
        viewport.svgRoot.appendChild(this.svgClipOutlineRect2);

        this.concealClipping();
    }
    else {
        // When playing the presentation, we use the default SVG
        // clipping technique.
        var svgClipPath = document.createElementNS(SVG_NS, "clipPath");
        var svgClipPathId = viewport.makeUniqueId("sozi-clip-path-");
        svgClipPath.setAttribute("id", svgClipPathId);
        svgClipPath.appendChild(this.svgClipRect);
        viewport.svgRoot.appendChild(svgClipPath);
    }

    // The groups that will support transformations
    this.svgTransformGroups = layer.svgNodes.map(svgNode => {
        // The group that will support the clipping operation
        var svgClippedGroup = document.createElementNS(SVG_NS, "g");
        viewport.svgRoot.insertBefore(svgClippedGroup, svgNode);

        if (viewport.editMode) {
            svgClippedGroup.setAttribute("mask", "url(#" + svgMaskId + ")");
        }
        else {
            svgClippedGroup.setAttribute("clip-path", "url(#" + svgClipPathId + ")");
        }

        var svgGroup = document.createElementNS(SVG_NS, "g");
        svgGroup.appendChild(svgNode);
        svgClippedGroup.appendChild(svgGroup);
        return svgGroup;
    });

    return this;
};

Camera.revealClipping = function () {
    this.maskValue = 64;
    this.svgClipOutlineRect1.style.display = "inline";
    this.svgClipOutlineRect2.style.display = "inline";
};

Camera.concealClipping = function () {
    this.maskValue = 0;
    this.svgClipOutlineRect1.style.display = "none";
    this.svgClipOutlineRect2.style.display = "none";
};

Object.defineProperty(Camera, "scale", {
    get() {
        return Math.min(this.viewport.width / this.width, this.viewport.height / this.height);
    }
});

Camera.rotate = function (angle) {
    this.restoreAspectRatio();
    return this.setAngle(this.angle + angle).update();
};

Camera.zoom = function (factor, x, y) {
    this.width /= factor;
    this.height /= factor;
    this.restoreAspectRatio();
    return this.translate(
        (1 - factor) * (x - this.viewport.width  / 2),
        (1 - factor) * (y - this.viewport.height / 2)
    );
};

Camera.translate = function (deltaX, deltaY) {
    var scale = this.scale;
    var angleRad = this.angle * Math.PI / 180;
    var si = Math.sin(angleRad);
    var co = Math.cos(angleRad);
    this.cx -= (deltaX * co - deltaY * si) / scale;
    this.cy -= (deltaX * si + deltaY * co) / scale;
    this.restoreAspectRatio();
    return this.update();
};

Camera.clip = function (x0, y0, x1, y1) {
    this.clipped = true;
    var scale = this.scale;
    var clipWidth = Math.abs(x1 - x0) + 1;
    var clipHeight = Math.abs(y1 - y0) + 1;
    this.clipXOffset = (Math.min(x0, x1) - (this.viewport.width  - clipWidth)  / 2) * this.width  / clipWidth;
    this.clipYOffset = (Math.min(y0, y1) - (this.viewport.height - clipHeight) / 2) * this.height / clipHeight;
    this.clipWidthFactor  = clipWidth  / this.width  / scale;
    this.clipHeightFactor = clipHeight / this.height / scale;
    return this.update();
};

Camera.restoreAspectRatio = function () {
    var viewportRatio = this.viewport.width / this.viewport.height;
    var camRatio = this.width / this.height;
    var ratio = viewportRatio / camRatio;
    if (ratio > 1) {
        this.width *= ratio;
        if (this.clipped) {
            this.clipWidthFactor /= ratio;
        }
    }
    else {
        this.height /= ratio;
        if (this.clipped) {
            this.clipHeightFactor *= ratio;
        }
    }
};

Camera.getCandidateReferenceElement = function () {
    // getIntersectionList is not supported in Gecko
    if (!this.svgRoot.getIntersectionList || !this.layer.svgNodes.length) {
        return this.svgRoot;
    }

    // Get all elements that intersect with the viewport.
    var viewportRect = this.svgRoot.createSVGRect();
    viewportRect.x = 0;
    viewportRect.y = 0;
    viewportRect.width = this.viewport.width;
    viewportRect.height = this.viewport.height;
    var viewportArea = this.viewport.width * this.viewport.height;

    var intersectionList = this.svgRoot.getIntersectionList(viewportRect, this.layer.svgNodes[0]);

{'fraction_non_alphanumeric': 0.12525058134872905, 'fraction_numerical': 0.009061021570042498, 'mean_word_length': 2.857717290442314, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, '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': '4183932', 'n_tokens_mistral': 3963, 'n_tokens_neox': 3872, 'n_words': 854}