From 665ba8134ccd6d3aea48d5c7c005abec830fe1cc Mon Sep 17 00:00:00 2001 From: Calixte Denizet Date: Thu, 21 Nov 2024 15:14:17 +0100 Subject: [PATCH] [Editor] Simplify the draw layer code 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. --- src/display/draw_layer.js | 112 ++++++----- src/display/editor/drawers/freedraw.js | 76 ++++---- src/display/editor/drawers/highlight.js | 37 ++-- src/display/editor/drawers/outline.js | 63 +++++- src/display/editor/highlight.js | 246 ++++++++++++++++-------- test/driver.js | 33 +++- web/draw_layer_builder.css | 31 +-- 7 files changed, 367 insertions(+), 231 deletions(-) diff --git a/src/display/draw_layer.js b/src/display/draw_layer.js index 4e649f93e90853..3e3df99b19a80a 100644 --- a/src/display/draw_layer.js +++ b/src/display/draw_layer.js @@ -55,7 +55,7 @@ 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}%`; @@ -63,11 +63,10 @@ class DrawLayer { 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; } @@ -86,10 +85,19 @@ 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); @@ -97,45 +105,42 @@ class DrawLayer { 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(mustRemoveSelfIntersections, properties) { // 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}`; @@ -166,59 +171,50 @@ 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()); - } - - 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); + this.updateProperties(id, properties); } - removeClass(id, className) { - this.#mapping.get(id).classList.remove(className); + 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); + } } - getSVGRoot(id) { - return this.#mapping.get(id); + updateParent(id, layer) { + if (layer === this) { + return; + } + const root = this.#mapping.get(id); + layer.#parent.append(root); + this.#mapping.delete(id); + layer.#mapping.set(id, root); } remove(id) { diff --git a/src/display/editor/drawers/freedraw.js b/src/display/editor/drawers/freedraw.js index 38ded7ff81e7f1..30f73d90cd4784 100644 --- a/src/display/editor/drawers/freedraw.js +++ b/src/display/editor/drawers/freedraw.js @@ -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; @@ -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; @@ -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; @@ -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, @@ -460,7 +460,7 @@ class FreeDrawOutliner { class FreeDrawOutline extends Outline { #box; - #bbox = null; + #bbox = new Float32Array(4); #innerMargin; @@ -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; @@ -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]; @@ -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() { @@ -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; @@ -654,10 +650,6 @@ class FreeDrawOutline extends Outline { } return outliner.getOutlines(); } - - get mustRemoveSelfIntersections() { - return true; - } } export { FreeDrawOutline, FreeDrawOutliner }; diff --git a/src/display/editor/drawers/highlight.js b/src/display/editor/drawers/highlight.js index 2e01e5c2742194..a6389284a2766a 100644 --- a/src/display/editor/drawers/highlight.js +++ b/src/display/editor/drawers/highlight.js @@ -19,6 +19,8 @@ import { Outline } from "./outline.js"; class HighlightOutliner { #box; + #lastPoint; + #verticalEdges = []; #intervals = []; @@ -77,13 +79,13 @@ class HighlightOutliner { edge[2] = (y2 - shiftedMinY) / bboxHeight; } - this.#box = { - x: shiftedMinX, - y: shiftedMinY, - width: bboxWidth, - height: bboxHeight, - lastPoint, - }; + this.#box = new Float32Array([ + shiftedMinX, + shiftedMinY, + bboxWidth, + bboxHeight, + ]); + this.#lastPoint = lastPoint; } getOutlines() { @@ -173,7 +175,7 @@ class HighlightOutliner { } outline.push(lastPointX, lastPointY); } - return new HighlightOutline(outlines, this.#box); + return new HighlightOutline(outlines, this.#box, this.#lastPoint); } #binarySearch(y) { @@ -267,10 +269,11 @@ class HighlightOutline extends Outline { #outlines; - constructor(outlines, box) { + constructor(outlines, box, lastPoint) { super(); this.#outlines = outlines; this.#box = box; + this.lastPoint = lastPoint; } toSVGPath() { @@ -319,10 +322,6 @@ class HighlightOutline extends Outline { return this.#box; } - get classNamesForDrawing() { - return ["highlight"]; - } - get classNamesForOutlining() { return ["highlightOutline"]; } @@ -339,21 +338,9 @@ class FreeHighlightOutliner extends FreeDrawOutliner { isLTR ); } - - get classNamesForDrawing() { - return ["highlight", "free"]; - } } class FreeHighlightOutline extends FreeDrawOutline { - get classNamesForDrawing() { - return ["highlight", "free"]; - } - - get classNamesForOutlining() { - return ["highlightOutline", "free"]; - } - newOutliner(point, box, scaleFactor, thickness, isLTR, innerMargin = 0) { return new FreeHighlightOutliner( point, diff --git a/src/display/editor/drawers/outline.js b/src/display/editor/drawers/outline.js index d272526f3a553c..bc860cec5ad8b6 100644 --- a/src/display/editor/drawers/outline.js +++ b/src/display/editor/drawers/outline.js @@ -16,6 +16,8 @@ import { unreachable } from "../../../shared/util.js"; class Outline { + static PRECISION = 1e-4; + /** * @returns {string} The SVG path of the outline. */ @@ -35,20 +37,61 @@ class Outline { unreachable("Abstract method `serialize` must be implemented."); } - // eslint-disable-next-line getter-return - get classNamesForDrawing() { - unreachable("Abstract getter `classNamesForDrawing` must be implemented."); + static _rescale(src, tx, ty, sx, sy, dest) { + dest ||= new Float32Array(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; } - // eslint-disable-next-line getter-return - get classNamesForOutlining() { - unreachable( - "Abstract getter `classNamesForOutlining` must be implemented." - ); + static _rescaleAndSwap(src, tx, ty, sx, sy, dest) { + dest ||= new Float32Array(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; + } + + static _translate(src, tx, ty, dest) { + dest ||= new Float32Array(src.length); + for (let i = 0, ii = src.length; i < ii; i += 2) { + dest[i] = tx + src[i]; + dest[i + 1] = ty + src[i + 1]; + } + return dest; + } + + static svgRound(x) { + return Math.round(x * 10000); + } + + static _normalizePoint(x, y, parentWidth, parentHeight, rotation) { + switch (rotation) { + case 90: + return [1 - y / parentWidth, x / parentHeight]; + case 180: + return [1 - x / parentWidth, 1 - y / parentHeight]; + case 270: + return [y / parentWidth, 1 - x / parentHeight]; + default: + return [x / parentWidth, y / parentHeight]; + } } - get mustRemoveSelfIntersections() { - return false; + static _normalizePagePoint(x, y, rotation) { + switch (rotation) { + case 90: + return [1 - y, x]; + case 180: + return [1 - x, 1 - y]; + case 270: + return [y, 1 - x]; + default: + return [x, y]; + } } } diff --git a/src/display/editor/highlight.js b/src/display/editor/highlight.js index 345b6b0d43cc9a..33da6c126dd233 100644 --- a/src/display/editor/highlight.js +++ b/src/display/editor/highlight.js @@ -157,12 +157,7 @@ class HighlightEditor extends AnnotationEditor { /* borderWidth = */ 0.001 ); this.#highlightOutlines = outliner.getOutlines(); - ({ - x: this.x, - y: this.y, - width: this.width, - height: this.height, - } = this.#highlightOutlines.box); + [this.x, this.y, this.width, this.height] = this.#highlightOutlines.box; const outlinerForOutline = new HighlightOutliner( this.#boxes, @@ -173,7 +168,7 @@ class HighlightEditor extends AnnotationEditor { this.#focusOutlines = outlinerForOutline.getOutlines(); // The last point is in the pages coordinate system. - const { lastPoint } = this.#focusOutlines.box; + const { lastPoint } = this.#focusOutlines; this.#lastPoint = [ (lastPoint[0] - this.x) / this.width, (lastPoint[1] - this.y) / this.height, @@ -195,26 +190,44 @@ class HighlightEditor extends AnnotationEditor { this.#clipPathId = clipPathId; // We need to redraw the highlight because we change the coordinates to be // in the box coordinate system. - this.parent.drawLayer.finalizeLine(highlightId, highlightOutlines); - this.#outlineId = this.parent.drawLayer.drawOutline(this.#focusOutlines); + this.parent.drawLayer.finalizeDraw(highlightId, { + bbox: highlightOutlines.box, + path: { + d: highlightOutlines.toSVGPath(), + }, + }); + this.#outlineId = this.parent.drawLayer.drawOutline( + /* mustRemoveSelfIntersections = */ true, + { + rootClass: { + highlightOutline: true, + free: true, + }, + bbox: this.#focusOutlines.box, + path: { + d: this.#focusOutlines.toSVGPath(), + }, + } + ); } else if (this.parent) { const angle = this.parent.viewport.rotation; - this.parent.drawLayer.updateLine(this.#id, highlightOutlines); - this.parent.drawLayer.updateBox( - this.#id, - HighlightEditor.#rotateBbox( + this.parent.drawLayer.updateProperties(this.#id, { + bbox: HighlightEditor.#rotateBbox( this.#highlightOutlines.box, (angle - this.rotation + 360) % 360 - ) - ); - - this.parent.drawLayer.updateLine(this.#outlineId, this.#focusOutlines); - this.parent.drawLayer.updateBox( - this.#outlineId, - HighlightEditor.#rotateBbox(this.#focusOutlines.box, angle) - ); + ), + path: { + d: highlightOutlines.toSVGPath(), + }, + }); + this.parent.drawLayer.updateProperties(this.#outlineId, { + bbox: HighlightEditor.#rotateBbox(this.#focusOutlines.box, angle), + path: { + d: this.#focusOutlines.toSVGPath(), + }, + }); } - const { x, y, width, height } = highlightOutlines.box; + const [x, y, width, height] = highlightOutlines.box; switch (this.rotation) { case 0: this.x = x; @@ -246,7 +259,7 @@ class HighlightEditor extends AnnotationEditor { } } - const { lastPoint } = this.#focusOutlines.box; + const { lastPoint } = this.#focusOutlines; this.#lastPoint = [(lastPoint[0] - x) / width, (lastPoint[1] - y) / height]; } @@ -324,10 +337,14 @@ class HighlightEditor extends AnnotationEditor { #updateColor(color) { const setColorAndOpacity = (col, opa) => { this.color = col; - this.parent?.drawLayer.changeColor(this.#id, col); - this.#colorPicker?.updateColor(col); this.#opacity = opa; - this.parent?.drawLayer.changeOpacity(this.#id, opa); + this.parent?.drawLayer.updateProperties(this.#id, { + root: { + fill: col, + "fill-opacity": opa, + }, + }); + this.#colorPicker?.updateColor(col); }; const savedColor = this.color; const savedOpacity = this.#opacity; @@ -503,46 +520,53 @@ class HighlightEditor extends AnnotationEditor { return; } ({ id: this.#id, clipPathId: this.#clipPathId } = parent.drawLayer.draw( - this.#highlightOutlines, - this.color, - this.#opacity + { + bbox: this.#highlightOutlines.box, + root: { + viewBox: "0 0 1 1", + fill: this.color, + "fill-opacity": this.#opacity, + }, + rootClass: { + highlight: true, + free: this.#isFreeHighlight, + }, + path: { + d: this.#highlightOutlines.toSVGPath(), + }, + }, + /* isPathUpdatable = */ false, + /* hasClip = */ true )); - this.#outlineId = parent.drawLayer.drawOutline(this.#focusOutlines); + this.#outlineId = parent.drawLayer.drawOutline( + /* mustRemoveSelfIntersections = */ this.#isFreeHighlight, + { + rootClass: { + highlightOutline: true, + free: this.#isFreeHighlight, + }, + bbox: this.#focusOutlines.box, + path: { + d: this.#focusOutlines.toSVGPath(), + }, + } + ); + if (this.#highlightDiv) { this.#highlightDiv.style.clipPath = this.#clipPathId; } } - static #rotateBbox({ x, y, width, height }, angle) { + static #rotateBbox([x, y, width, height], angle) { switch (angle) { case 90: - return { - x: 1 - y - height, - y: x, - width: height, - height: width, - }; + return [1 - y - height, x, height, width]; case 180: - return { - x: 1 - x - width, - y: 1 - y - height, - width, - height, - }; + return [1 - x - width, 1 - y - height, width, height]; case 270: - return { - x: y, - y: 1 - x - width, - width: height, - height: width, - }; + return [y, 1 - x - width, height, width]; } - return { - x, - y, - width, - height, - }; + return [x, y, width, height]; } /** @inheritdoc */ @@ -555,15 +579,23 @@ class HighlightEditor extends AnnotationEditor { box = HighlightEditor.#rotateBbox(this.#highlightOutlines.box, angle); } else { // An highlight annotation is always drawn horizontally. - box = HighlightEditor.#rotateBbox(this, angle); - } - drawLayer.rotate(this.#id, angle); - drawLayer.rotate(this.#outlineId, angle); - drawLayer.updateBox(this.#id, box); - drawLayer.updateBox( - this.#outlineId, - HighlightEditor.#rotateBbox(this.#focusOutlines.box, angle) - ); + box = HighlightEditor.#rotateBbox( + [this.x, this.y, this.width, this.height], + angle + ); + } + drawLayer.updateProperties(this.#id, { + bbox: box, + root: { + "data-main-rotation": angle, + }, + }); + drawLayer.updateProperties(this.#outlineId, { + bbox: HighlightEditor.#rotateBbox(this.#focusOutlines.box, angle), + root: { + "data-main-rotation": angle, + }, + }); } /** @inheritdoc */ @@ -600,13 +632,21 @@ class HighlightEditor extends AnnotationEditor { pointerover() { if (!this.isSelected) { - this.parent.drawLayer.addClass(this.#outlineId, "hovered"); + this.parent?.drawLayer.updateProperties(this.#outlineId, { + rootClass: { + hovered: true, + }, + }); } } pointerleave() { if (!this.isSelected) { - this.parent.drawLayer.removeClass(this.#outlineId, "hovered"); + this.parent?.drawLayer.updateProperties(this.#outlineId, { + rootClass: { + hovered: false, + }, + }); } } @@ -646,8 +686,12 @@ class HighlightEditor extends AnnotationEditor { if (!this.#outlineId) { return; } - this.parent?.drawLayer.removeClass(this.#outlineId, "hovered"); - this.parent?.drawLayer.addClass(this.#outlineId, "selected"); + this.parent?.drawLayer.updateProperties(this.#outlineId, { + rootClass: { + hovered: false, + selected: true, + }, + }); } /** @inheritdoc */ @@ -656,7 +700,11 @@ class HighlightEditor extends AnnotationEditor { if (!this.#outlineId) { return; } - this.parent?.drawLayer.removeClass(this.#outlineId, "selected"); + this.parent?.drawLayer.updateProperties(this.#outlineId, { + rootClass: { + selected: false, + }, + }); if (!this.#isFreeHighlight) { this.#setCaret(/* start = */ false); } @@ -671,8 +719,16 @@ class HighlightEditor extends AnnotationEditor { show(visible = this._isVisible) { super.show(visible); if (this.parent) { - this.parent.drawLayer.show(this.#id, visible); - this.parent.drawLayer.show(this.#outlineId, visible); + this.parent.drawLayer.updateProperties(this.#id, { + rootClass: { + hidden: !visible, + }, + }); + this.parent.drawLayer.updateProperties(this.#outlineId, { + rootClass: { + hidden: !visible, + }, + }); } } @@ -755,17 +811,34 @@ class HighlightEditor extends AnnotationEditor { ); ({ id: this._freeHighlightId, clipPathId: this._freeHighlightClipId } = parent.drawLayer.draw( - this._freeHighlight, - this._defaultColor, - this._defaultOpacity, - /* isPathUpdatable = */ true + { + bbox: [0, 0, 1, 1], + root: { + viewBox: "0 0 1 1", + fill: this._defaultColor, + "fill-opacity": this._defaultOpacity, + }, + rootClass: { + highlight: true, + free: true, + }, + path: { + d: this._freeHighlight.toSVGPath(), + }, + }, + /* isPathUpdatable = */ true, + /* hasClip = */ true )); } static #highlightMove(parent, event) { if (this._freeHighlight.add(event)) { // Redraw only if the point has been added. - parent.drawLayer.updatePath(this._freeHighlightId, this._freeHighlight); + parent.drawLayer.updateProperties(this._freeHighlightId, { + path: { + d: this._freeHighlight.toSVGPath(), + }, + }); } } @@ -886,10 +959,23 @@ class HighlightEditor extends AnnotationEditor { outliner.add(point); } const { id, clipPathId } = parent.drawLayer.draw( - outliner, - editor.color, - editor._defaultOpacity, - /* isPathUpdatable = */ true + { + bbox: [0, 0, 1, 1], + root: { + viewBox: "0 0 1 1", + fill: editor.color, + "fill-opacity": editor._defaultOpacity, + }, + rootClass: { + highlight: true, + free: true, + }, + path: { + d: outliner.toSVGPath(), + }, + }, + /* isPathUpdatable = */ true, + /* hasClip = */ true ); editor.#createFreeOutlines({ highlightOutlines: outliner.getOutlines(), diff --git a/test/driver.js b/test/driver.js index c99b82744fe226..35252f1d8700ec 100644 --- a/test/driver.js +++ b/test/driver.js @@ -381,8 +381,37 @@ class Rasterize { ); const drawLayer = new DrawLayer({ pageIndex: 0 }); drawLayer.setParent(div); - drawLayer.draw(outliner.getOutlines(), "orange", 0.4); - drawLayer.drawOutline(outlinerForOutline.getOutlines()); + const outlines = outliner.getOutlines(); + drawLayer.draw( + { + bbox: outlines.box, + root: { + viewBox: "0 0 1 1", + fill: "orange", + "fill-opacity": 0.4, + }, + rootClass: { + highlight: true, + free: false, + }, + path: { + d: outlines.toSVGPath(), + }, + }, + /* isPathUpdatable = */ false, + /* hasClip = */ true + ); + const focusLine = outlinerForOutline.getOutlines(); + drawLayer.drawOutline(/* mustRemoveSelfIntersections = */ false, { + rootClass: { + highlightOutline: true, + free: false, + }, + bbox: focusLine.box, + path: { + d: focusLine.toSVGPath(), + }, + }); svg.append(foreignObject); diff --git a/web/draw_layer_builder.css b/web/draw_layer_builder.css index 2adeebc8112439..e4b6ae385adfbd 100644 --- a/web/draw_layer_builder.css +++ b/web/draw_layer_builder.css @@ -17,24 +17,27 @@ svg { transform: none; - &[data-main-rotation="90"] { - mask, - use:not(.clip, .mask) { - transform: matrix(0, 1, -1, 0, 1, 0); + &.highlight, + &.highlightOutline { + &[data-main-rotation="90"] { + mask, + use:not(.clip, .mask) { + transform: matrix(0, 1, -1, 0, 1, 0); + } } - } - &[data-main-rotation="180"] { - mask, - use:not(.clip, .mask) { - transform: matrix(-1, 0, 0, -1, 1, 1); + &[data-main-rotation="180"] { + mask, + use:not(.clip, .mask) { + transform: matrix(-1, 0, 0, -1, 1, 1); + } } - } - &[data-main-rotation="270"] { - mask, - use:not(.clip, .mask) { - transform: matrix(0, -1, 1, 0, 0, 1); + &[data-main-rotation="270"] { + mask, + use:not(.clip, .mask) { + transform: matrix(0, -1, 1, 0, 0, 1); + } } }