diff --git a/src/display/draw_layer.js b/src/display/draw_layer.js index 1f43e2857..3db845d01 100644 --- a/src/display/draw_layer.js +++ b/src/display/draw_layer.js @@ -69,22 +69,7 @@ class DrawLayer { return svg; } - highlight({ outlines, box }, color, opacity) { - const id = this.#id++; - const root = this.#createSVG(box); - root.classList.add("highlight"); - 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", - DrawLayer.#extractPathFromHighlightOutlines(outlines) - ); - - // Create the clipping path for the editor div. + #createClipPath(defs, pathId) { const clipPath = DrawLayer._svgFactory.createElement("clipPath"); defs.append(clipPath); const clipPathId = `clip_${pathId}`; @@ -95,6 +80,24 @@ class DrawLayer { clipPathUse.setAttribute("href", `#${pathId}`); clipPathUse.classList.add("clip"); + return clipPathId; + } + + highlight(outlines, color, opacity) { + const id = this.#id++; + const root = this.#createSVG(outlines.box); + root.classList.add("highlight"); + 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()); + + // Create the clipping path for the editor div. + const clipPathId = this.#createClipPath(defs, pathId); + const use = DrawLayer._svgFactory.createElement("use"); root.append(use); root.setAttribute("fill", color); @@ -106,13 +109,13 @@ class DrawLayer { return { id, clipPathId: `url(#${clipPathId})` }; } - highlightOutline({ outlines, box }) { + highlightOutline(outlines) { // 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(box); + const root = this.#createSVG(outlines.box); root.classList.add("highlightOutline"); const defs = DrawLayer._svgFactory.createElement("defs"); root.append(defs); @@ -120,10 +123,7 @@ class DrawLayer { defs.append(path); const pathId = `path_p${this.pageIndex}_${id}`; path.setAttribute("id", pathId); - path.setAttribute( - "d", - DrawLayer.#extractPathFromHighlightOutlines(outlines) - ); + path.setAttribute("d", outlines.toSVGPath()); path.setAttribute("vector-effect", "non-scaling-stroke"); const use1 = DrawLayer._svgFactory.createElement("use"); @@ -139,27 +139,6 @@ class DrawLayer { return id; } - static #extractPathFromHighlightOutlines(polygons) { - const buffer = []; - for (const polygon of polygons) { - let [prevX, prevY] = polygon; - buffer.push(`M${prevX} ${prevY}`); - for (let i = 2; i < polygon.length; i += 2) { - const x = polygon[i]; - const y = polygon[i + 1]; - if (x === prevX) { - buffer.push(`V${y}`); - prevY = y; - } else if (y === prevY) { - buffer.push(`H${x}`); - prevX = x; - } - } - buffer.push("Z"); - } - return buffer.join(" "); - } - updateBox(id, box) { DrawLayer.#setBox(this.#mapping.get(id), box); } diff --git a/src/display/editor/outliner.js b/src/display/editor/outliner.js index 9a40cd4ce..077fd71c8 100644 --- a/src/display/editor/outliner.js +++ b/src/display/editor/outliner.js @@ -170,7 +170,7 @@ class Outliner { } outline.push(lastPointX, lastPointY); } - return { outlines, box: this.#box }; + return new HighlightOutline(outlines, this.#box); } #binarySearch(y) { @@ -259,4 +259,51 @@ class Outliner { } } +class Outline { + toSVGPath() { + throw new Error("Abstract method `toSVGPath` must be implemented."); + } + + get box() { + throw new Error("Abstract getter `box` must be implemented."); + } +} + +class HighlightOutline extends Outline { + #box; + + #outlines; + + constructor(outlines, box) { + super(); + this.#outlines = outlines; + this.#box = box; + } + + toSVGPath() { + const buffer = []; + for (const polygon of this.#outlines) { + let [prevX, prevY] = polygon; + buffer.push(`M${prevX} ${prevY}`); + for (let i = 2; i < polygon.length; i += 2) { + const x = polygon[i]; + const y = polygon[i + 1]; + if (x === prevX) { + buffer.push(`V${y}`); + prevY = y; + } else if (y === prevY) { + buffer.push(`H${x}`); + prevX = x; + } + } + buffer.push("Z"); + } + return buffer.join(" "); + } + + get box() { + return this.#box; + } +} + export { Outliner };