Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CONTRIBUTORS
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ Dongjin Ouyang <[email protected]>
Sawan Sunar <[email protected]>
hideo aoyama <https://github.com/boukendesho>
Ross Brown <[email protected]>
🦙 <github.com/iamllama>
🦙 <[email protected]>
Lukas Sommer <[email protected]>
Luca Auer <[email protected]>
Niclas Heinz <[email protected]>
Expand Down
5 changes: 5 additions & 0 deletions rslib/src/image_occlusion/imageocclusion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ pub fn get_image_cloze_data(text: &str) -> String {
result.push_str(&format!("data-top=\"{}\" ", property.value));
}
}
"angle" => {
if !property.value.is_empty() {
result.push_str(&format!("data-angle=\"{}\" ", property.value));
}
}
"width" => {
if !is_empty_or_zero(&property.value) {
result.push_str(&format!("data-width=\"{}\" ", property.value));
Expand Down
7 changes: 5 additions & 2 deletions ts/routes/image-occlusion/mask-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,15 +99,18 @@ function initCanvas(): fabric.Canvas {
// Disable uniform scaling
canvas.uniformScaling = false;
canvas.uniScaleKey = "none";
// disable rotation globally
delete fabric.Object.prototype.controls.mtr;
// disable object caching
fabric.Object.prototype.objectCaching = false;
// add a border to corner to handle blend of control
fabric.Object.prototype.transparentCorners = false;
fabric.Object.prototype.cornerStyle = "circle";
fabric.Object.prototype.cornerStrokeColor = "#000000";
fabric.Object.prototype.padding = 8;
// disable rotation when selecting
canvas.on("selection:created", () => {
const g = canvas.getActiveObject();
if (g && g instanceof fabric.Group) { g.setControlsVisibility({ mtr: false }); }
});
canvas.on("object:modified", (evt) => {
if (evt.target instanceof fabric.Polygon) {
modifiedPolygon(canvas, evt.target);
Expand Down
26 changes: 24 additions & 2 deletions ts/routes/image-occlusion/review.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,15 +263,29 @@ function drawShape({
ctx.fillStyle = fill;
ctx.strokeStyle = stroke;
ctx.lineWidth = strokeWidth;
const angle = ((shape.angle ?? 0) * Math.PI) / 180;
if (shape instanceof Rectangle) {
if (angle) {
ctx.save();
ctx.translate(shape.left, shape.top);
ctx.rotate(angle);
ctx.translate(-shape.left, -shape.top);
}
ctx.fillRect(shape.left, shape.top, shape.width, shape.height);
// ctx stroke methods will draw a visible stroke, even if the width is 0
if (strokeWidth) {
ctx.strokeRect(shape.left, shape.top, shape.width, shape.height);
}
if (angle) { ctx.restore(); }
} else if (shape instanceof Ellipse) {
const adjustedLeft = shape.left + shape.rx;
const adjustedTop = shape.top + shape.ry;
if (angle) {
ctx.save();
ctx.translate(shape.left, shape.top);
ctx.rotate(angle);
ctx.translate(-shape.left, -shape.top);
}
ctx.beginPath();
ctx.ellipse(
adjustedLeft,
Expand All @@ -288,6 +302,7 @@ function drawShape({
if (strokeWidth) {
ctx.stroke();
}
if (angle) { ctx.restore(); }
} else if (shape instanceof Polygon) {
const offset = getPolygonOffset(shape);
ctx.save();
Expand Down Expand Up @@ -329,10 +344,17 @@ function drawShape({
}
totalHeight += lineHeight;
}
const left = shape.left / shape.scaleX;
const top = shape.top / shape.scaleY;
if (angle) {
ctx.translate(left, top);
ctx.rotate(angle);
ctx.translate(-left, -top);
}
ctx.fillStyle = TEXT_BACKGROUND_COLOR;
ctx.fillRect(
shape.left / shape.scaleX,
shape.top / shape.scaleY,
left,
top,
maxWidth + TEXT_PADDING,
totalHeight + TEXT_PADDING,
);
Expand Down
11 changes: 8 additions & 3 deletions ts/routes/image-occlusion/shapes/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { fabric } from "fabric";

import { SHAPE_MASK_COLOR } from "../tools/lib";
import type { ConstructorParams, Size } from "../types";
import { floatToDisplay } from "./floats";
import { angleToStored, floatToDisplay } from "./lib";
import { xFromNormalized, xToNormalized, yFromNormalized, yToNormalized } from "./position";

export type ShapeOrShapes = Shape | Shape[];
Expand All @@ -18,6 +18,7 @@ export type ShapeOrShapes = Shape | Shape[];
export class Shape {
left: number;
top: number;
angle?: number; // polygons don't use it
fill: string;
/** Whether occlusions from other cloze numbers should be shown on the
* question side. Used only in reviewer code.
Expand All @@ -27,11 +28,12 @@ export class Shape {
ordinal: number | undefined;

constructor(
{ left = 0, top = 0, fill = SHAPE_MASK_COLOR, occludeInactive, ordinal = undefined }: ConstructorParams<Shape> =
{},
{ left = 0, top = 0, angle = 0, fill = SHAPE_MASK_COLOR, occludeInactive, ordinal = undefined }:
ConstructorParams<Shape> = {},
) {
this.left = left;
this.top = top;
this.angle = angle;
this.fill = fill;
this.occludeInactive = occludeInactive;
this.ordinal = ordinal;
Expand All @@ -41,9 +43,11 @@ export class Shape {
* text.
*/
toDataForCloze(): ShapeDataForCloze {
const angle = angleToStored(this.angle);
return {
left: floatToDisplay(this.left),
top: floatToDisplay(this.top),
...(!angle ? {} : { angle: angle.toString() }),
...(this.fill === SHAPE_MASK_COLOR ? {} : { fill: this.fill }),
};
}
Expand Down Expand Up @@ -85,6 +89,7 @@ export class Shape {
export interface ShapeDataForCloze {
left: string;
top: string;
angle?: string;
fill?: string;
oi?: string;
}
2 changes: 1 addition & 1 deletion ts/routes/image-occlusion/shapes/ellipse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { fabric } from "fabric";
import type { ConstructorParams, Size } from "../types";
import type { ShapeDataForCloze } from "./base";
import { Shape } from "./base";
import { floatToDisplay } from "./floats";
import { floatToDisplay } from "./lib";
import { xFromNormalized, xToNormalized, yFromNormalized, yToNormalized } from "./position";

export class Ellipse extends Shape {
Expand Down
3 changes: 3 additions & 0 deletions ts/routes/image-occlusion/shapes/from-cloze.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type { GetImageOcclusionNoteResponse_ImageOcclusion } from "@generated/an

import type { Shape, ShapeOrShapes } from "./base";
import { Ellipse } from "./ellipse";
import { storedToAngle } from "./lib";
import { Point, Polygon } from "./polygon";
import { Rectangle } from "./rectangle";
import { Text } from "./text";
Expand Down Expand Up @@ -75,6 +76,7 @@ function extractShapeFromRenderedCloze(cloze: HTMLDivElement): Shape | null {
text: cloze.dataset.text,
scale: cloze.dataset.scale,
fs: cloze.dataset.fontSize,
angle: cloze.dataset.angle,
};
return buildShape(type, props);
}
Expand All @@ -92,6 +94,7 @@ function buildShape(type: ShapeType, props: Record<string, any>): Shape {
props.top = parseFloat(
Number.isNaN(Number(props.top)) ? ".0000" : props.top,
);
props.angle = storedToAngle(props.angle) ?? 0;
switch (type) {
case "rect": {
return new Rectangle({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,15 @@ export function floatToDisplay(number: number): string {
}
return number.toFixed(4).replace(/^0+|0+$/g, "");
}

const ANGLE_STEPS = 10000;

export function angleToStored(angle: any): number | null {
const angleDeg = Number(angle) % 360;
return Number.isNaN(angleDeg) ? null : Math.round((angleDeg / 360) * ANGLE_STEPS);
}

export function storedToAngle(x: any): number | null {
const angleSteps = Number(x) % ANGLE_STEPS;
return Number.isNaN(angleSteps) ? null : (angleSteps / ANGLE_STEPS) * 360;
}
2 changes: 1 addition & 1 deletion ts/routes/image-occlusion/shapes/polygon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { fabric } from "fabric";
import type { ConstructorParams, Size } from "../types";
import type { ShapeDataForCloze } from "./base";
import { Shape } from "./base";
import { floatToDisplay } from "./floats";
import { floatToDisplay } from "./lib";
import { xFromNormalized, xToNormalized, yFromNormalized, yToNormalized } from "./position";

export class Polygon extends Shape {
Expand Down
2 changes: 1 addition & 1 deletion ts/routes/image-occlusion/shapes/rectangle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { fabric } from "fabric";
import type { ConstructorParams, Size } from "../types";
import type { ShapeDataForCloze } from "./base";
import { Shape } from "./base";
import { floatToDisplay } from "./floats";
import { floatToDisplay } from "./lib";
import { xFromNormalized, xToNormalized, yFromNormalized, yToNormalized } from "./position";

export class Rectangle extends Shape {
Expand Down
2 changes: 1 addition & 1 deletion ts/routes/image-occlusion/shapes/text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { TEXT_BACKGROUND_COLOR, TEXT_COLOR, TEXT_FONT_FAMILY, TEXT_FONT_SIZE, TE
import type { ConstructorParams, Size } from "../types";
import type { ShapeDataForCloze } from "./base";
import { Shape } from "./base";
import { floatToDisplay } from "./floats";
import { floatToDisplay } from "./lib";

export class Text extends Shape {
text: string;
Expand Down
39 changes: 22 additions & 17 deletions ts/routes/image-occlusion/tools/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export const groupShapes = (canvas: fabric.Canvas): void => {

activeObject.toGroup().set({
opacity: get(opacityStateStore) ? 0.4 : 1,
});
}).setControlsVisibility({ mtr: false });

redraw(canvas);
};
Expand Down Expand Up @@ -228,18 +228,21 @@ const setShapePosition = (
boundingBox: fabric.Rect,
object: fabric.Object,
): void => {
if (object.left! < 0) {
object.set({ left: 0 });
const { left, top, width, height } = object.getBoundingRect(true);

if (left < 0) {
object.set({ left: Math.max(object.left! - left, 0) });
}
if (object.top! < 0) {
object.set({ top: 0 });
if (top < 0) {
object.set({ top: Math.max(object.top! - top, 0) });
}
if (object.left! + object.width! * object.scaleX! + object.strokeWidth! > boundingBox.width!) {
object.set({ left: boundingBox.width! - object.width! * object.scaleX! });
if (left > boundingBox.width!) {
object.set({ left: object.left! - left - width + boundingBox.width! });
}
if (object.top! + object.height! * object.scaleY! + object.strokeWidth! > boundingBox.height!) {
object.set({ top: boundingBox.height! - object.height! * object.scaleY! });
if (top > boundingBox.height!) {
object.set({ top: object.top! - top - height + boundingBox.height! });
}

object.setCoords();
};

Expand Down Expand Up @@ -277,23 +280,25 @@ export const makeShapesRemainInCanvas = (canvas: fabric.Canvas, boundingBox: fab
canvas.on("object:moving", function(e) {
const obj = e.target!;

const objWidth = obj.getScaledWidth();
const objHeight = obj.getScaledHeight();
const { left: objBbLeft, top: objBbTop, width: objBbWidth, height: objBbHeight } = obj.getBoundingRect(
true,
true,
);

if (objWidth > boundingBox.width! || objHeight > boundingBox.height!) {
if (objBbWidth > boundingBox.width! || objBbHeight > boundingBox.height!) {
return;
}

const top = obj.top!;
const left = obj.left!;

const topBound = boundingBox.top!;
const bottomBound = topBound + boundingBox.height! + 5;
const leftBound = boundingBox.left!;
const rightBound = leftBound + boundingBox.width! + 5;

obj.left = Math.min(Math.max(left, leftBound), rightBound - objWidth);
obj.top = Math.min(Math.max(top, topBound), bottomBound - objHeight);
const newBbLeft = Math.min(Math.max(objBbLeft, leftBound), rightBound - objBbWidth);
const newBbTop = Math.min(Math.max(objBbTop, topBound), bottomBound - objBbHeight);

obj.left = obj.left! + newBbLeft - objBbLeft;
obj.top = obj.top! + newBbTop - objBbTop;
});
};

Expand Down