Skip to content

Commit

Permalink
[Editor] Simplify the draw layer code
Browse files Browse the repository at this point in the history
and tweak a bit the highlight one (e.g. it's useless to have 64 bits floating point numbers
when 32 bits ones are enough).

It's a required step for the refactoring of the ink tool (in order to use the draw layer).
It avoids to call several functions acting on the same SVG element.
  • Loading branch information
calixteman committed Nov 21, 2024
1 parent 07765e9 commit 3c343ac
Show file tree
Hide file tree
Showing 7 changed files with 323 additions and 235 deletions.
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

0 comments on commit 3c343ac

Please sign in to comment.