Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Editor] Simplify the draw layer code #19085

Merged
merged 1 commit into from
Nov 22, 2024
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
106 changes: 46 additions & 60 deletions src/display/draw_layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,19 +55,18 @@ class DrawLayer {
return shadow(this, "_svgFactory", new DOMSVGFactory());
}

static #setBox(element, { x = 0, y = 0, width = 1, height = 1 } = {}) {
static #setBox(element, [x, y, width, height]) {
const { style } = element;
style.top = `${100 * y}%`;
style.left = `${100 * x}%`;
style.width = `${100 * width}%`;
style.height = `${100 * height}%`;
}

#createSVG(box) {
#createSVG() {
const svg = DrawLayer._svgFactory.create(1, 1, /* skipDimensions = */ true);
this.#parent.append(svg);
svg.setAttribute("aria-hidden", true);
DrawLayer.#setBox(svg, box);

return svg;
}
Expand All @@ -86,56 +85,62 @@ class DrawLayer {
return clipPathId;
}

draw(outlines, color, opacity, isPathUpdatable = false) {
#updateProperties(element, properties) {
for (const [key, value] of Object.entries(properties)) {
if (value === null) {
element.removeAttribute(key);
} else {
element.setAttribute(key, value);
}
}
}

draw(properties, isPathUpdatable = false, hasClip = false) {
const id = this.#id++;
const root = this.#createSVG(outlines.box);
root.classList.add(...outlines.classNamesForDrawing);
const root = this.#createSVG();

const defs = DrawLayer._svgFactory.createElement("defs");
root.append(defs);
const path = DrawLayer._svgFactory.createElement("path");
defs.append(path);
const pathId = `path_p${this.pageIndex}_${id}`;
path.setAttribute("id", pathId);
path.setAttribute("d", outlines.toSVGPath());
path.setAttribute("vector-effect", "non-scaling-stroke");

if (isPathUpdatable) {
this.#toUpdate.set(id, path);
}

// Create the clipping path for the editor div.
const clipPathId = this.#createClipPath(defs, pathId);
const clipPathId = hasClip ? this.#createClipPath(defs, pathId) : null;

const use = DrawLayer._svgFactory.createElement("use");
root.append(use);
root.setAttribute("fill", color);
root.setAttribute("fill-opacity", opacity);
use.setAttribute("href", `#${pathId}`);
this.updateProperties(root, properties);

this.#mapping.set(id, root);

return { id, clipPathId: `url(#${clipPathId})` };
}

drawOutline(outlines) {
drawOutline(properties, mustRemoveSelfIntersections) {
// We cannot draw the outline directly in the SVG for highlights because
// it composes with its parent with mix-blend-mode: multiply.
// But the outline has a different mix-blend-mode, so we need to draw it in
// its own SVG.
const id = this.#id++;
const root = this.#createSVG(outlines.box);
root.classList.add(...outlines.classNamesForOutlining);
const root = this.#createSVG();
const defs = DrawLayer._svgFactory.createElement("defs");
root.append(defs);
const path = DrawLayer._svgFactory.createElement("path");
defs.append(path);
const pathId = `path_p${this.pageIndex}_${id}`;
path.setAttribute("id", pathId);
path.setAttribute("d", outlines.toSVGPath());
path.setAttribute("vector-effect", "non-scaling-stroke");

let maskId;
if (outlines.mustRemoveSelfIntersections) {
if (mustRemoveSelfIntersections) {
const mask = DrawLayer._svgFactory.createElement("mask");
defs.append(mask);
maskId = `mask_p${this.pageIndex}_${id}`;
Expand Down Expand Up @@ -166,59 +171,40 @@ class DrawLayer {
use1.classList.add("mainOutline");
use2.classList.add("secondaryOutline");

this.updateProperties(root, properties);

this.#mapping.set(id, root);

return id;
}

finalizeLine(id, line) {
const path = this.#toUpdate.get(id);
finalizeDraw(id, properties) {
this.#toUpdate.delete(id);
this.updateBox(id, line.box);
path.setAttribute("d", line.toSVGPath());
}

updateLine(id, line) {
const root = this.#mapping.get(id);
const defs = root.firstChild;
const path = defs.firstChild;
path.setAttribute("d", line.toSVGPath());
this.updateProperties(id, properties);
}

updatePath(id, line) {
this.#toUpdate.get(id).setAttribute("d", line.toSVGPath());
}

updateBox(id, box) {
DrawLayer.#setBox(this.#mapping.get(id), box);
}

show(id, visible) {
this.#mapping.get(id).classList.toggle("hidden", !visible);
}

rotate(id, angle) {
this.#mapping.get(id).setAttribute("data-main-rotation", angle);
}

changeColor(id, color) {
this.#mapping.get(id).setAttribute("fill", color);
}

changeOpacity(id, opacity) {
this.#mapping.get(id).setAttribute("fill-opacity", opacity);
}

addClass(id, className) {
this.#mapping.get(id).classList.add(className);
}

removeClass(id, className) {
this.#mapping.get(id).classList.remove(className);
}

getSVGRoot(id) {
return this.#mapping.get(id);
updateProperties(elementOrId, { root, bbox, rootClass, path }) {
const element =
typeof elementOrId === "number"
? this.#mapping.get(elementOrId)
: elementOrId;
if (root) {
this.#updateProperties(element, root);
}
if (bbox) {
DrawLayer.#setBox(element, bbox);
}
if (rootClass) {
const { classList } = element;
for (const [className, value] of Object.entries(rootClass)) {
classList.toggle(className, value);
}
}
if (path) {
const defs = element.firstChild;
const pathElement = defs.firstChild;
this.#updateProperties(pathElement, path);
}
}

remove(id) {
Expand Down
76 changes: 34 additions & 42 deletions src/display/editor/drawers/freedraw.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class FreeDrawOutliner {
// We track the last 3 points in order to be able to:
// - compute the normal of the line,
// - compute the control points of the quadratic Bézier curve.
#last = new Float64Array(18);
#last = new Float32Array(18);

#lastX;

Expand Down Expand Up @@ -302,7 +302,7 @@ class FreeDrawOutliner {
const last = this.#last;
const [layerX, layerY, layerWidth, layerHeight] = this.#box;

const points = new Float64Array((this.#points?.length ?? 0) + 2);
const points = new Float32Array((this.#points?.length ?? 0) + 2);
for (let i = 0, ii = points.length - 2; i < ii; i += 2) {
points[i] = (this.#points[i] - layerX) / layerWidth;
points[i + 1] = (this.#points[i + 1] - layerY) / layerHeight;
Expand All @@ -315,7 +315,7 @@ class FreeDrawOutliner {
return this.#getOutlineTwoPoints(points);
}

const outline = new Float64Array(
const outline = new Float32Array(
this.#top.length + 24 + this.#bottom.length
);
let N = top.length;
Expand Down Expand Up @@ -360,7 +360,7 @@ class FreeDrawOutliner {
const [layerX, layerY, layerWidth, layerHeight] = this.#box;
const [lastTopX, lastTopY, lastBottomX, lastBottomY] =
this.#getLastCoords();
const outline = new Float64Array(36);
const outline = new Float32Array(36);
outline.set(
[
NaN,
Expand Down Expand Up @@ -460,7 +460,7 @@ class FreeDrawOutliner {
class FreeDrawOutline extends Outline {
#box;

#bbox = null;
#bbox = new Float32Array(4);

#innerMargin;

Expand All @@ -480,9 +480,10 @@ class FreeDrawOutline extends Outline {
this.#scaleFactor = scaleFactor;
this.#innerMargin = innerMargin;
this.#isLTR = isLTR;
this.lastPoint = [NaN, NaN];
this.#computeMinMax(isLTR);

const { x, y, width, height } = this.#bbox;
const [x, y, width, height] = this.#bbox;
for (let i = 0, ii = outline.length; i < ii; i += 2) {
outline[i] = (outline[i] - x) / width;
outline[i + 1] = (outline[i + 1] - y) / height;
Expand Down Expand Up @@ -517,49 +518,43 @@ class FreeDrawOutline extends Outline {
let points;
switch (rotation) {
case 0:
outline = this.#rescale(this.#outline, blX, trY, width, -height);
points = this.#rescale(this.#points, blX, trY, width, -height);
outline = Outline._rescale(this.#outline, blX, trY, width, -height);
points = Outline._rescale(this.#points, blX, trY, width, -height);
break;
case 90:
outline = this.#rescaleAndSwap(this.#outline, blX, blY, width, height);
points = this.#rescaleAndSwap(this.#points, blX, blY, width, height);
outline = Outline._rescaleAndSwap(
this.#outline,
blX,
blY,
width,
height
);
points = Outline._rescaleAndSwap(this.#points, blX, blY, width, height);
break;
case 180:
outline = this.#rescale(this.#outline, trX, blY, -width, height);
points = this.#rescale(this.#points, trX, blY, -width, height);
outline = Outline._rescale(this.#outline, trX, blY, -width, height);
points = Outline._rescale(this.#points, trX, blY, -width, height);
break;
case 270:
outline = this.#rescaleAndSwap(
outline = Outline._rescaleAndSwap(
this.#outline,
trX,
trY,
-width,
-height
);
points = this.#rescaleAndSwap(this.#points, trX, trY, -width, -height);
points = Outline._rescaleAndSwap(
this.#points,
trX,
trY,
-width,
-height
);
break;
}
return { outline: Array.from(outline), points: [Array.from(points)] };
}

#rescale(src, tx, ty, sx, sy) {
const dest = new Float64Array(src.length);
for (let i = 0, ii = src.length; i < ii; i += 2) {
dest[i] = tx + src[i] * sx;
dest[i + 1] = ty + src[i + 1] * sy;
}
return dest;
}

#rescaleAndSwap(src, tx, ty, sx, sy) {
const dest = new Float64Array(src.length);
for (let i = 0, ii = src.length; i < ii; i += 2) {
dest[i] = tx + src[i + 1] * sx;
dest[i + 1] = ty + src[i] * sy;
}
return dest;
}

#computeMinMax(isLTR) {
const outline = this.#outline;
let lastX = outline[4];
Expand Down Expand Up @@ -605,11 +600,12 @@ class FreeDrawOutline extends Outline {
lastY = outline[i + 5];
}

const x = minX - this.#innerMargin,
y = minY - this.#innerMargin,
width = maxX - minX + 2 * this.#innerMargin,
height = maxY - minY + 2 * this.#innerMargin;
this.#bbox = { x, y, width, height, lastPoint: [lastPointX, lastPointY] };
const bbox = this.#bbox;
bbox[0] = minX - this.#innerMargin;
bbox[1] = minY - this.#innerMargin;
bbox[2] = maxX - minX + 2 * this.#innerMargin;
bbox[3] = maxY - minY + 2 * this.#innerMargin;
this.lastPoint = [lastPointX, lastPointY];
}

get box() {
Expand All @@ -629,7 +625,7 @@ class FreeDrawOutline extends Outline {

getNewOutline(thickness, innerMargin) {
// Build the outline of the highlight to use as the focus outline.
const { x, y, width, height } = this.#bbox;
const [x, y, width, height] = this.#bbox;
const [layerX, layerY, layerWidth, layerHeight] = this.#box;
const sx = width * layerWidth;
const sy = height * layerHeight;
Expand All @@ -654,10 +650,6 @@ class FreeDrawOutline extends Outline {
}
return outliner.getOutlines();
}

get mustRemoveSelfIntersections() {
return true;
}
}

export { FreeDrawOutline, FreeDrawOutliner };
Loading