anki/ts/reviewer/image_occlusion.ts
Austin S eebd143a9f
Fix null error on window resizing (#2501)
* Fix null error on window resizing

* Be more specific in type cast (dae)
2023-05-17 16:52:38 +10:00

210 lines
7.1 KiB
TypeScript

// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import * as tr from "@tslib/ftl";
window.addEventListener("load", () => {
window.addEventListener("resize", setupImageCloze);
});
export function setupImageCloze(): void {
window.requestAnimationFrame(setupImageClozeInner);
}
function setupImageClozeInner(): void {
const canvas = document.querySelector("canvas") as HTMLCanvasElement | null;
if (canvas == null) {
return;
}
canvas.style.maxWidth = "100%";
canvas.style.maxHeight = "95vh";
const ctx: CanvasRenderingContext2D = canvas.getContext("2d")!;
const container = document.getElementById("container") as HTMLDivElement;
const image = document.getElementById("img") as HTMLImageElement;
if (image == null) {
container.innerText = tr.notetypeErrorNoImageToShow();
return;
}
const size = limitSize({ width: image.naturalWidth, height: image.naturalHeight });
canvas.width = size.width;
canvas.height = size.height;
// set height for div container (used 'relative' in css)
container.style.height = `${image.height}px`;
// setup button for toggle image occlusion
const button = document.getElementById("toggle");
if (button) {
button.addEventListener("click", toggleMasks);
}
drawShapes(ctx);
}
function drawShapes(ctx: CanvasRenderingContext2D): void {
const activeCloze = document.querySelectorAll(".cloze");
const inActiveCloze = document.querySelectorAll(".cloze-inactive");
const shapeProperty = getShapeProperty();
for (const clz of activeCloze) {
const cloze = (<HTMLDivElement> clz);
const shape = cloze.dataset.shape!;
const fill = shapeProperty.activeShapeColor;
draw(ctx, cloze, shape, fill, shapeProperty.activeBorder);
}
for (const clz of inActiveCloze) {
const cloze = (<HTMLDivElement> clz);
const shape = cloze.dataset.shape!;
const fill = shapeProperty.inActiveShapeColor;
const hideinactive = cloze.dataset.hideinactive == "true";
if (!hideinactive) {
draw(ctx, cloze, shape, fill, shapeProperty.inActiveBorder);
}
}
}
function draw(
ctx: CanvasRenderingContext2D,
cloze: HTMLDivElement,
shape: string,
color: string,
border: { width: number; color: string },
): void {
ctx.fillStyle = color;
const posLeft = parseFloat(cloze.dataset.left!);
const posTop = parseFloat(cloze.dataset.top!);
const width = parseFloat(cloze.dataset.width!);
const height = parseFloat(cloze.dataset.height!);
switch (shape) {
case "rect":
{
ctx.strokeStyle = border.color;
ctx.lineWidth = border.width;
ctx.fillRect(posLeft, posTop, width, height);
ctx.strokeRect(posLeft, posTop, width, height);
}
break;
case "ellipse":
{
const rx = parseFloat(cloze.dataset.rx!);
const ry = parseFloat(cloze.dataset.ry!);
const newLeft = posLeft + rx;
const newTop = posTop + ry;
ctx.beginPath();
ctx.strokeStyle = border.color;
ctx.lineWidth = border.width;
ctx.ellipse(newLeft, newTop, rx, ry, 0, 0, Math.PI * 2, false);
ctx.closePath();
ctx.fill();
ctx.stroke();
}
break;
case "polygon":
{
const points = JSON.parse(cloze.dataset.points!);
ctx.beginPath();
ctx.strokeStyle = border.color;
ctx.lineWidth = border.width;
ctx.moveTo(points[0][0], points[0][1]);
for (let i = 1; i < points.length; i++) {
ctx.lineTo(points[i][0], points[i][1]);
}
ctx.closePath();
ctx.fill();
ctx.stroke();
}
break;
default:
break;
}
}
// following function copy+pasted from mask-editor.ts,
// so update both, if it changes
function limitSize(size: { width: number; height: number }): { width: number; height: number; scalar: number } {
const maximumPixels = 1000000;
const { width, height } = size;
const requiredPixels = width * height;
if (requiredPixels <= maximumPixels) return { width, height, scalar: 1 };
const scalar = Math.sqrt(maximumPixels) / Math.sqrt(requiredPixels);
return {
width: Math.floor(width * scalar),
height: Math.floor(height * scalar),
scalar: scalar,
};
}
function getShapeProperty(): {
activeShapeColor: string;
inActiveShapeColor: string;
activeBorder: { width: number; color: string };
inActiveBorder: { width: number; color: string };
} {
const canvas = document.getElementById("canvas");
const computedStyle = window.getComputedStyle(canvas!);
// it may throw error if the css variable is not defined
try {
// shape color
const activeShapeColor = computedStyle.getPropertyValue("--active-shape-color");
const inActiveShapeColor = computedStyle.getPropertyValue("--inactive-shape-color");
// inactive shape border
const inActiveShapeBorder = computedStyle.getPropertyValue("--inactive-shape-border");
const inActiveBorder = inActiveShapeBorder.split(" ").filter((x) => x);
const inActiveShapeBorderWidth = parseFloat(inActiveBorder[0]);
const inActiveShapeBorderColor = inActiveBorder[1];
// active shape border
const activeShapeBorder = computedStyle.getPropertyValue("--active-shape-border");
const activeBorder = activeShapeBorder.split(" ").filter((x) => x);
const activeShapeBorderWidth = parseFloat(activeBorder[0]);
const activeShapeBorderColor = activeBorder[1];
return {
activeShapeColor: activeShapeColor ? activeShapeColor : "#ff8e8e",
inActiveShapeColor: inActiveShapeColor ? inActiveShapeColor : "#ffeba2",
activeBorder: {
width: activeShapeBorderWidth ? activeShapeBorderWidth : 1,
color: activeShapeBorderColor ? activeShapeBorderColor : "#212121",
},
inActiveBorder: {
width: inActiveShapeBorderWidth ? inActiveShapeBorderWidth : 1,
color: inActiveShapeBorderColor ? inActiveShapeBorderColor : "#212121",
},
};
} catch {
// return default values
return {
activeShapeColor: "#ff8e8e",
inActiveShapeColor: "#ffeba2",
activeBorder: {
width: 1,
color: "#212121",
},
inActiveBorder: {
width: 1,
color: "#212121",
},
};
}
}
const toggleMasks = (): void => {
const canvas = document.getElementById("canvas") as HTMLCanvasElement;
const display = canvas.style.display;
if (display === "none") {
canvas.style.display = "unset";
} else {
canvas.style.display = "none";
}
};