UNPKG

r2-navigator-js

Version:

Readium 2 'navigator' for NodeJS (TypeScript)

2,197 lines 105 kB
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.recreateAllHighlightsDebounced = exports.setDrawMargin = exports.HIGHLIGHT_GROUP_PAGEBREAK = exports.HIGHLIGHT_GROUP_TTS = exports.ENABLE_PAGEBREAK_MARGIN_TEXT_EXPERIMENT = exports.ENABLE_CSS_HIGHLIGHTS = exports.ENABLE_FLOATING_UI = void 0;
exports.getBoundingClientRectOfDocumentBody = getBoundingClientRectOfDocumentBody;
exports.hideAllhighlights = hideAllhighlights;
exports.destroyAllhighlights = destroyAllhighlights;
exports.destroyHighlight = destroyHighlight;
exports.destroyHighlightsGroup = destroyHighlightsGroup;
exports.recreateAllHighlightsRaw = recreateAllHighlightsRaw;
exports.recreateAllHighlights = recreateAllHighlights;
exports.createHighlights = createHighlights;
exports.createHighlight = createHighlight;
const crypto = require("crypto");
const debounce = require("debounce");
const electron_1 = require("electron");
const events_1 = require("../../common/events");
const highlight_1 = require("../../common/highlight");
const readium_css_inject_1 = require("../../common/readium-css-inject");
const rect_utils_1 = require("../common/rect-utils");
const readium_css_1 = require("./readium-css");
const selection_1 = require("./selection");
const styles_1 = require("../../common/styles");
const readium_css_2 = require("./readium-css");
const core_1 = require("@flatten-js/core");
const { unify, subtract } = core_1.BooleanOperations;
const dom_1 = require("@floating-ui/dom");
const IS_DEV = (process.env.NODE_ENV === "development" || process.env.NODE_ENV === "dev");
window.DEBUG_RECTS = IS_DEV && rect_utils_1.VERBOSE;
exports.ENABLE_FLOATING_UI = true;
exports.ENABLE_CSS_HIGHLIGHTS = true && !!CSS.highlights;
exports.ENABLE_PAGEBREAK_MARGIN_TEXT_EXPERIMENT = false;
let lastMouseDownX = -1;
let lastMouseDownY = -1;
let bodyEventListenersSet = false;
let _highlightsContainer;
let _timeoutMouseMove;
const TIMEOUT_MOUSE_MS = 200;
const cleanupPolygon = (polygonAccumulator, off) => {
    const DEBUG_RECTS = window.DEBUG_RECTS;
    const minLength = Math.abs(off) + 1;
    let nSegments = 0;
    let nArcs = 0;
    let total = 0;
    if (DEBUG_RECTS) {
        console.log("--====}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}");
    }
    for (const e of polygonAccumulator.edges) {
        const edge = e;
        if (edge.isSegment) {
            nSegments++;
            const segment = edge.shape;
            const l = segment.length;
            if (core_1.Utils.LE(l, minLength)) {
                total++;
                if (DEBUG_RECTS) {
                    console.log("--POLYGON SEGMENT small LENGTH: " + l + "(" + off + ")");
                }
            }
            else {
                if (DEBUG_RECTS) {
                    console.log("--POLYGON SEGMENT ok LENGTH: " + l + "(" + off + ")");
                }
            }
        }
        else if (edge.isArc) {
            nArcs++;
            if (DEBUG_RECTS) {
                console.log("--POLYGON ARC");
            }
        }
    }
    if (DEBUG_RECTS) {
        console.log("--====");
        console.log("--==== POLYGON SEGMENT small TOTAL 1: " + total);
        console.log("--==== POLYGON SEGMENT small SEGMENTS 1: " + nSegments);
        console.log("--==== POLYGON SEGMENT small ARCS 1: " + nArcs);
    }
    total = 0;
    nSegments = 0;
    nArcs = 0;
    if (DEBUG_RECTS) {
        console.log("--====}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}");
    }
    for (const f of polygonAccumulator.faces) {
        const face = f;
        for (const e of face.edges) {
            const edge = e;
            if (edge.isSegment) {
                nSegments++;
                const segment = edge.shape;
                const l = segment.length;
                if (core_1.Utils.LE(l, minLength)) {
                    total++;
                    if (DEBUG_RECTS) {
                        console.log("--POLYGON SEGMENT small LENGTH: " + l + "(" + off + ")");
                    }
                }
                else {
                    if (DEBUG_RECTS) {
                        console.log("--POLYGON SEGMENT ok LENGTH: " + l + "(" + off + ")");
                    }
                }
            }
            else if (edge.isArc) {
                nArcs++;
                if (DEBUG_RECTS) {
                    console.log("--POLYGON ARC");
                }
            }
        }
    }
    if (DEBUG_RECTS) {
        console.log("--====");
        console.log("--==== POLYGON SEGMENT small TOTAL 2: " + total);
        console.log("--==== POLYGON SEGMENT small SEGMENTS 2: " + nSegments);
        console.log("--==== POLYGON SEGMENT small ARCS 2: " + nArcs);
    }
    total = 0;
    nSegments = 0;
    nArcs = 0;
    if (DEBUG_RECTS) {
        console.log("--====}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}");
    }
    for (const f of polygonAccumulator.faces) {
        const face = f;
        let edge = face.first;
        while (edge) {
            if (edge.isSegment) {
                nSegments++;
                const segment = edge.shape;
                const l = segment.length;
                if (core_1.Utils.LE(l, minLength)) {
                    total++;
                    if (DEBUG_RECTS) {
                        console.log("--POLYGON SEGMENT small LENGTH: " + l + "(" + off + ")");
                    }
                }
                else {
                    if (DEBUG_RECTS) {
                        console.log("--POLYGON SEGMENT ok LENGTH: " + l + "(" + off + ")");
                    }
                }
            }
            else if (edge.isArc) {
                nArcs++;
                if (DEBUG_RECTS) {
                    console.log("--POLYGON ARC");
                }
            }
            if (edge == face.last) {
                break;
            }
            edge = edge.next;
        }
    }
    if (DEBUG_RECTS) {
        console.log("--====");
        console.log("--==== POLYGON SEGMENT small TOTAL 3: " + total);
        console.log("--==== POLYGON SEGMENT small SEGMENTS 3: " + nSegments);
        console.log("--==== POLYGON SEGMENT small ARCS 3: " + nArcs);
        console.log("--====}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}");
    }
    const faces = Array.from(polygonAccumulator.faces);
    for (const f of faces) {
        const face = f;
        if (DEBUG_RECTS) {
            console.log("~~~~ POLY FACE");
        }
        const edges = Array.from(face.edges);
        const edgeShapes = edges.map((edge) => edge.shape);
        let chainedEdgeShapes = [];
        while (edgeShapes.length) {
            if (DEBUG_RECTS) {
                console.log("~~~~ POLY EDGE SHAPE");
            }
            if (!chainedEdgeShapes.length) {
                const last = edgeShapes.pop();
                chainedEdgeShapes.push(last);
                continue;
            }
            const lastInChain = chainedEdgeShapes[chainedEdgeShapes.length - 1];
            const lastInChainStartPoint = lastInChain.breakToFunctional ? lastInChain.start : lastInChain.start;
            const lastInChainEndPoint = lastInChain.breakToFunctional ? lastInChain.end : lastInChain.end;
            const shapesBefore = [];
            const shapesAfter = [];
            for (const edgeShape of edgeShapes) {
                const edgeShapeStartPoint = edgeShape.breakToFunctional ? edgeShape.start : edgeShape.start;
                const edgeShapeEndPoint = edgeShape.breakToFunctional ? edgeShape.end : edgeShape.end;
                if (core_1.Utils.EQ(lastInChainStartPoint.x, edgeShapeEndPoint.x) && core_1.Utils.EQ(lastInChainStartPoint.y, edgeShapeEndPoint.y)) {
                    shapesBefore.push(edgeShape);
                }
                if (core_1.Utils.EQ(lastInChainEndPoint.x, edgeShapeStartPoint.x) && core_1.Utils.EQ(lastInChainEndPoint.y, edgeShapeStartPoint.y)) {
                    shapesAfter.push(edgeShape);
                }
            }
            if (shapesBefore.length > 1 || shapesAfter.length > 1 || shapesAfter.length === 0) {
                if (DEBUG_RECTS) {
                    console.log("~~~~ POLY SHAPES BEFORE/AFTER ABORT: " + shapesBefore.length + " ... " + shapesAfter.length);
                }
                chainedEdgeShapes = [];
                break;
            }
            const startPoint = shapesAfter[0].breakToFunctional ? shapesAfter[0].start : shapesAfter[0].start;
            const endPoint = shapesAfter[0].breakToFunctional ? shapesAfter[0].end : shapesAfter[0].end;
            if (DEBUG_RECTS) {
                console.log("*** SEGMENT/ARC --- START: (" + startPoint.x + ", " + startPoint.y + ") END: (" + endPoint.x + ", " + endPoint.y + ")");
            }
            edgeShapes.splice(edgeShapes.indexOf(shapesAfter[0]), 1);
            chainedEdgeShapes.push(shapesAfter[0]);
            if (chainedEdgeShapes.length === edges.length) {
                const edgeShapeEndPoint = shapesAfter[0].breakToFunctional ? shapesAfter[0].end : shapesAfter[0].end;
                const firstInChainStartPoint = chainedEdgeShapes[0].breakToFunctional ? chainedEdgeShapes[0].start : chainedEdgeShapes[0].start;
                if (!core_1.Utils.EQ(firstInChainStartPoint.x, edgeShapeEndPoint.x) || !core_1.Utils.EQ(firstInChainStartPoint.y, edgeShapeEndPoint.y)) {
                    if (DEBUG_RECTS) {
                        console.log("~~~~ POLY SHAPES TAIL/HEAD ABORT");
                    }
                    chainedEdgeShapes = [];
                    break;
                }
            }
        }
        let previousSegment;
        let previousSmallSegment;
        const newEdgeShapes = [];
        let hasChanged = false;
        for (const edgeShape of chainedEdgeShapes) {
            if (!edgeShape.breakToFunctional) {
                const segment = edgeShape;
                const l = segment.length;
                if (DEBUG_RECTS) {
                    console.log("--POLYGON SLOPES: " + (previousSegment === null || previousSegment === void 0 ? void 0 : previousSegment.slope) + " vs. " + segment.slope);
                }
                if (previousSegment && core_1.Utils.EQ(previousSegment.slope, segment.slope)) {
                    if (DEBUG_RECTS) {
                        console.log("--POLYGON SLOPE EQUAL ... merge :)");
                    }
                    hasChanged = true;
                    newEdgeShapes.pop();
                    const seg = new core_1.Segment(new core_1.Point(previousSegment.start.x, previousSegment.start.y), new core_1.Point(segment.end.x, segment.end.y));
                    newEdgeShapes.push(seg);
                    previousSmallSegment = undefined;
                    previousSegment = seg;
                    if (chainedEdgeShapes.indexOf(edgeShape) === chainedEdgeShapes.length - 1 && !newEdgeShapes[0].breakToFunctional && core_1.Utils.EQ(newEdgeShapes[0].slope, seg.slope)) {
                        if (DEBUG_RECTS) {
                            console.log("--POLYGON SLOPE EQUAL (tail/head link) 1... merge :)");
                        }
                        hasChanged = true;
                        newEdgeShapes.splice(0, 1);
                        const seg2 = new core_1.Segment(new core_1.Point(newEdgeShapes[0].start.x, newEdgeShapes[0].start.y), new core_1.Point(seg.end.x, seg.end.y));
                        newEdgeShapes.push(seg2);
                        previousSmallSegment = undefined;
                        previousSegment = seg2;
                    }
                }
                else if (newEdgeShapes.length && chainedEdgeShapes.indexOf(edgeShape) === chainedEdgeShapes.length - 1 && !newEdgeShapes[0].breakToFunctional && core_1.Utils.EQ(newEdgeShapes[0].slope, segment.slope)) {
                    if (DEBUG_RECTS) {
                        console.log("--POLYGON SLOPE EQUAL (tail/head link) 2... merge :)");
                    }
                    hasChanged = true;
                    newEdgeShapes.splice(0, 1);
                    const seg = new core_1.Segment(new core_1.Point(newEdgeShapes[0].start.x, newEdgeShapes[0].start.y), new core_1.Point(segment.end.x, segment.end.y));
                    newEdgeShapes.push(seg);
                    previousSmallSegment = undefined;
                    previousSegment = seg;
                }
                else if (core_1.Utils.LE(l, minLength)) {
                    if (DEBUG_RECTS) {
                        console.log("--POLYGON SEGMENT small LENGTH: " + l + "(" + off + ")");
                    }
                    if (previousSmallSegment) {
                        if (DEBUG_RECTS) {
                            console.log("-->>>> POLYGON SEGMENT small will merge :) ...");
                        }
                        hasChanged = true;
                        newEdgeShapes.pop();
                        const seg = new core_1.Segment(new core_1.Point(previousSmallSegment.start.x, previousSmallSegment.start.y), new core_1.Point(segment.end.x, segment.end.y));
                        newEdgeShapes.push(seg);
                        previousSmallSegment = undefined;
                        previousSegment = seg;
                    }
                    else if (newEdgeShapes.length && chainedEdgeShapes.indexOf(edgeShape) === chainedEdgeShapes.length - 1 && !newEdgeShapes[0].breakToFunctional && core_1.Utils.LE(newEdgeShapes[0].length, minLength)) {
                        if (DEBUG_RECTS) {
                            console.log("-->>>> POLYGON SEGMENT small (tail/head link) will merge :) ...");
                        }
                        hasChanged = true;
                        newEdgeShapes.splice(0, 1);
                        const seg = new core_1.Segment(new core_1.Point(newEdgeShapes[0].start.x, newEdgeShapes[0].start.y), new core_1.Point(segment.end.x, segment.end.y));
                        ;
                        newEdgeShapes.push(seg);
                        previousSmallSegment = undefined;
                        previousSegment = seg;
                    }
                    else {
                        newEdgeShapes.push(segment);
                        previousSmallSegment = segment;
                        previousSegment = segment;
                    }
                }
                else {
                    if (DEBUG_RECTS) {
                        console.log("--POLYGON SEGMENT ok LENGTH: " + l + "(" + off + ")");
                    }
                    previousSmallSegment = undefined;
                    newEdgeShapes.push(segment);
                    previousSegment = segment;
                }
            }
            else {
                if (DEBUG_RECTS) {
                    console.log("--POLYGON ARC");
                }
                previousSmallSegment = undefined;
                previousSegment = undefined;
                newEdgeShapes.push(edgeShape);
            }
        }
        if (hasChanged) {
            if (DEBUG_RECTS) {
                console.log("-->>>> POLYGON face changed :)");
            }
            polygonAccumulator.deleteFace(face);
            polygonAccumulator.addFace(newEdgeShapes);
        }
    }
};
const addEdgePoints = (polygon, offset) => {
    const boxes = [];
    for (const f of polygon.faces) {
        const face = f;
        for (const edge of face.edges) {
            if (edge.isSegment) {
                const segment = edge.shape;
                const bStart = new core_1.Box(segment.start.x - offset, segment.start.y - offset, segment.start.x + offset * 2, segment.start.y + offset * 2);
                boxes.push(bStart);
                const bEnd = new core_1.Box(segment.end.x - offset, segment.end.y - offset, segment.end.x + offset * 2, segment.end.y + offset * 2);
                boxes.push(bEnd);
            }
            else {
                const arc = edge.shape;
                const bStart = new core_1.Box(arc.start.x - offset, arc.start.y - offset, arc.start.x + offset * 2, arc.start.y + offset * 2);
                boxes.push(bStart);
                const bEnd = new core_1.Box(arc.end.x - offset, arc.end.y - offset, arc.end.x + offset * 2, arc.end.y + offset * 2);
                boxes.push(bEnd);
            }
        }
    }
    for (const box of boxes) {
        polygon.addFace(box);
    }
};
const BASE_ORIENTATION = core_1.ORIENTATION.CCW;
const USE_SEGMENT_JOINS_NOT_ARCS = false;
function arcSE(center, start, end, counterClockwise) {
    const DEBUG_RECTS = window.DEBUG_RECTS;
    const startAngle = Number((new core_1.Vector(center, start).slope).toPrecision(12));
    let endAngle = Number((new core_1.Vector(center, end).slope).toPrecision(12));
    if (core_1.Utils.EQ(startAngle, endAngle)) {
        if (DEBUG_RECTS) {
            console.log("--POLYGON ARC ORIENTATION CCW/CW inverse");
        }
        endAngle += 2 * Math.PI;
        counterClockwise = !counterClockwise;
    }
    const r = Number((new core_1.Vector(center, start).length).toPrecision(12));
    ;
    return new core_1.Arc(center, r, startAngle, endAngle, counterClockwise);
}
function offset_(polygon, off, useSegmentJoinsNotArcs) {
    const DEBUG_RECTS = window.DEBUG_RECTS;
    const postponeFinalUnify = off > 0;
    let polygonAccumulator = postponeFinalUnify ? undefined : polygon.clone();
    for (const f of polygon.faces) {
        const face = f;
        for (const edge of face.edges) {
            if (edge.isSegment) {
                const polygonEdge = new core_1.Polygon();
                const segment = edge.shape;
                const v_seg = new core_1.Vector(segment.end.x - segment.start.x, segment.end.y - segment.start.y);
                const v_seg_unit = v_seg.normalize();
                const absOffset = Math.abs(off);
                const v_left = v_seg_unit.rotate90CCW().multiply(absOffset);
                const v_right = v_seg_unit.rotate90CW().multiply(absOffset);
                const seg_left = segment.translate(v_left).reverse();
                const seg_right = segment.translate(v_right);
                const seg_left_ = new core_1.Segment(new core_1.Point(Number((seg_left.start.x).toPrecision(12)), Number((seg_left.start.y).toPrecision(12))), new core_1.Point(Number((seg_left.end.x).toPrecision(12)), Number((seg_left.end.y).toPrecision(12))));
                const seg_right_ = new core_1.Segment(new core_1.Point(Number((seg_right.start.x).toPrecision(12)), Number((seg_right.start.y).toPrecision(12))), new core_1.Point(Number((seg_right.end.x).toPrecision(12)), Number((seg_right.end.y).toPrecision(12))));
                const orientation = BASE_ORIENTATION === core_1.ORIENTATION.CCW ? core_1.CCW : core_1.CW;
                const cap1 = arcSE(segment.start, seg_left_.end, seg_right_.start, orientation);
                const cap2 = arcSE(segment.end, seg_right_.end, seg_left_.start, orientation);
                const cap1_ = useSegmentJoinsNotArcs
                    ?
                        new core_1.Segment(seg_left_.end, seg_right_.start)
                    :
                        cap1;
                const cap2_ = useSegmentJoinsNotArcs
                    ?
                        new core_1.Segment(seg_right_.end, seg_left_.start)
                    :
                        cap2;
                const face = polygonEdge.addFace([
                    seg_left_,
                    cap1_,
                    seg_right_,
                    cap2_,
                ]);
                if (face.orientation() !== BASE_ORIENTATION) {
                    if (DEBUG_RECTS) {
                        console.log("--POLYGON FACE ORIENTATION CCW/CW reverse() 1");
                    }
                    face.reverse();
                }
                if (!(polygonAccumulator || polygonEdge).faces.size) {
                    if (DEBUG_RECTS) {
                        console.log("--################# POLYGON BEFORE unify/substract HAS NO FACES!! " + (polygonAccumulator || polygonEdge).faces.size);
                    }
                }
                if (off > 0) {
                    polygonAccumulator = polygonAccumulator ? unify(polygonAccumulator, polygonEdge) : polygonEdge;
                }
                else {
                    polygonAccumulator = polygonAccumulator ? subtract(polygonAccumulator, polygonEdge) : polygonEdge;
                }
                if (!(polygonAccumulator || polygonEdge).faces.size) {
                    if (DEBUG_RECTS) {
                        console.log("--################# POLYGON AFTER unify/substract HAS NO FACES!! " + (polygonAccumulator || polygonEdge).faces.size);
                    }
                    if (!useSegmentJoinsNotArcs) {
                        if (DEBUG_RECTS) {
                            console.log("--##### POLYGON AFTER unify/substract try again without arc, only segment joiners ...");
                        }
                        return offset_(polygon, off, true);
                    }
                }
                else {
                    if (DEBUG_RECTS) {
                        console.log("--################# POLYGON AFTER unify/substract FACES: " + (polygonAccumulator || polygonEdge).edges.size + " /// " + (polygonAccumulator || polygonEdge).faces.size);
                    }
                }
                for (const f of polygonAccumulator.faces) {
                    const face = f;
                    if (face.edges.length < 4) {
                        if (DEBUG_RECTS) {
                            console.log("-------- POLYGON FACE EDGES not at least 4??!");
                        }
                        if (!useSegmentJoinsNotArcs) {
                            if (DEBUG_RECTS) {
                                console.log("--##### POLYGON AFTER unify/substract try again without arc, only segment joiners ...");
                            }
                            return offset_(polygon, off, true);
                        }
                    }
                    if (face.orientation() !== BASE_ORIENTATION) {
                        if (DEBUG_RECTS) {
                            console.log("-------- POLYGON FACE ORIENTATION");
                        }
                    }
                }
            }
            else {
                console.log("!!!!!!!! POLYGON ARC??!");
                return polygon;
            }
        }
    }
    Array.from((polygonAccumulator ? polygonAccumulator : polygon).faces).forEach((face) => {
        if (face.orientation() !== BASE_ORIENTATION) {
            if (DEBUG_RECTS) {
                console.log("--HIGH WEBVIEW-- removing polygon orientation face / inner hole (offset poly 1))");
            }
            if (polygonAccumulator) {
                polygonAccumulator.deleteFace(face);
            }
        }
    });
    if (polygonAccumulator && postponeFinalUnify) {
        polygonAccumulator = unify(polygonAccumulator, polygon);
    }
    Array.from((polygonAccumulator ? polygonAccumulator : polygon).faces).forEach((face) => {
        if (face.orientation() !== BASE_ORIENTATION) {
            if (DEBUG_RECTS) {
                console.log("--HIGH WEBVIEW-- removing polygon orientation face / inner hole (offset poly 2))");
            }
            if (polygonAccumulator) {
                polygonAccumulator.deleteFace(face);
            }
        }
    });
    if (polygonAccumulator) {
        if (!polygonAccumulator.faces.size) {
            if (DEBUG_RECTS) {
                console.log("--################# POLYGON INTERMEDIARY HAS NO FACES!! " + polygonAccumulator.faces.size);
            }
        }
        cleanupPolygon(polygonAccumulator, off);
    }
    let resPoly = polygonAccumulator ? polygonAccumulator : polygon;
    if (!resPoly.faces.size) {
        if (DEBUG_RECTS) {
            console.log("--################# POLYGON INTERMEDIARY HAS NO FACES!! " + resPoly.faces.size);
        }
        if (polygonAccumulator) {
            if (DEBUG_RECTS) {
                console.log("--################# FALLBACK TO SINGLE FACE POLY (BEFORE SUBSTRACT/UNIFY): " + polygon.faces.size);
            }
            resPoly = polygon;
        }
    }
    return resPoly;
}
function offset(originaPolygon, off, useSegmentJoinsNotArcs = USE_SEGMENT_JOINS_NOT_ARCS) {
    const DEBUG_RECTS = window.DEBUG_RECTS;
    off = Number((off).toPrecision(12));
    if (core_1.Utils.EQ_0(off)) {
        return originaPolygon;
    }
    const singleFacePolygons = [];
    for (const f of originaPolygon.faces) {
        const face = f;
        const poly = new core_1.Polygon();
        poly.addFace(face.edges.map((edge) => edge.shape));
        singleFacePolygons.push(poly);
    }
    const singlePolygon = new core_1.Polygon();
    for (const polygon of singleFacePolygons) {
        const resPoly = offset_(polygon, off, useSegmentJoinsNotArcs);
        for (const f of resPoly.faces) {
            const face = f;
            singlePolygon.addFace(face.edges.map(((edge) => edge.shape)));
        }
    }
    if (!singlePolygon.faces.size) {
        if (DEBUG_RECTS) {
            console.log("--##### POLYGON OFFSET HAS NO FACES!! " + singlePolygon.faces.size);
        }
        if (!useSegmentJoinsNotArcs) {
            if (DEBUG_RECTS) {
                console.log("--##### POLYGON OFFSET try again without arc, only segment joiners ...");
            }
            return offset(originaPolygon, off, true);
        }
    }
    return singlePolygon;
}
const DEFAULT_BACKGROUND_COLOR = {
    blue: 0,
    green: 0,
    red: 255,
};
const _highlights = [];
exports.HIGHLIGHT_GROUP_TTS = "tts";
exports.HIGHLIGHT_GROUP_PAGEBREAK = "pagebreak";
let _drawMargin = false;
const drawMargin = (h) => {
    if (h.group === exports.HIGHLIGHT_GROUP_TTS) {
        return false;
    }
    if (h.drawType === highlight_1.HighlightDrawTypeOpacityMask || h.drawType === highlight_1.HighlightDrawTypeOpacityMaskRuler || h.drawType === highlight_1.HighlightDrawTypeMarginBookmark) {
        return true;
    }
    if (h.group === exports.HIGHLIGHT_GROUP_PAGEBREAK) {
        return true;
    }
    if (Array.isArray(_drawMargin)) {
        if (h.group) {
            return _drawMargin.includes(h.group);
        }
        return false;
    }
    return _drawMargin;
};
const setDrawMargin = (win, drawMargin) => {
    _drawMargin = drawMargin;
    if (IS_DEV) {
        console.log("--HIGH WEBVIEW-- _drawMargin: " + JSON.stringify(_drawMargin, null, 4));
    }
    recreateAllHighlightsRaw(win);
};
exports.setDrawMargin = setDrawMargin;
const SVG_XML_NAMESPACE = "http://www.w3.org/2000/svg";
function getBoundingClientRectOfDocumentBody(win) {
    return win.document.body.getBoundingClientRect();
}
function processMouseEvent(win, ev) {
    var _a;
    if (_timeoutMouseMove) {
        clearTimeout(_timeoutMouseMove);
        _timeoutMouseMove = undefined;
    }
    if (!_highlightsContainer) {
        return;
    }
    const isMouseMove = ev.type === "mousemove";
    if (isMouseMove) {
        if (ev.buttons > 0) {
            return;
        }
        if (!_highlights.length) {
            return;
        }
    }
    const documant = win.document;
    const scrollElement = (0, readium_css_1.getScrollingElement)(documant);
    const x = ev.clientX;
    const y = ev.clientY;
    const paginated = (0, readium_css_inject_1.isPaginated)(documant);
    const bodyRect = getBoundingClientRectOfDocumentBody(win);
    const xOffset = paginated ? (-scrollElement.scrollLeft) : bodyRect.left;
    const yOffset = paginated ? (-scrollElement.scrollTop) : bodyRect.top;
    const scale = 1 / ((win.READIUM2 && win.READIUM2.isFixedLayout) ? win.READIUM2.fxlViewportScale : 1);
    let hit = false;
    let foundHighlight;
    let foundElement;
    for (let i = _highlights.length - 1; i >= 0; i--) {
        const highlight = _highlights[i];
        const doDrawMargin = drawMargin(highlight);
        let highlightParent = documant.getElementById(`${highlight.id}`);
        if (!highlightParent) {
            highlightParent = _highlightsContainer.querySelector(`#${highlight.id}`);
        }
        if (!highlightParent) {
            continue;
        }
        let highlightFragment = highlightParent.firstElementChild;
        while (highlightFragment) {
            if (highlightFragment.namespaceURI === SVG_XML_NAMESPACE) {
                const svg = highlightFragment;
                hit = (!doDrawMargin || svg.classList.contains(styles_1.CLASS_HIGHLIGHT_CONTOUR_MARGIN)) && svg.polygon.contains(new core_1.Point((x - xOffset) * scale, (y - yOffset) * scale));
                if (hit) {
                    break;
                }
            }
            highlightFragment = highlightFragment.nextElementSibling;
        }
        if (hit) {
            foundHighlight = highlight;
            foundElement = highlightParent;
            break;
        }
    }
    let highlightContainer = _highlightsContainer.firstElementChild;
    while (highlightContainer) {
        if (!foundElement || foundElement !== highlightContainer) {
            highlightContainer.classList.remove(styles_1.CLASS_HIGHLIGHT_HOVER);
        }
        highlightContainer = highlightContainer.nextElementSibling;
    }
    if (!hit) {
        const _highlightsFloatingUI = win.document.getElementById(styles_1.ID_HIGHLIGHTS_FLOATING);
        if (_highlightsFloatingUI && _highlightsFloatingUI.style.display !== "none") {
            _highlightsFloatingUI.style.display = "none";
        }
        documant.documentElement.classList.remove(styles_1.CLASS_HIGHLIGHT_CURSOR2);
        return;
    }
    if (foundElement && foundHighlight && foundHighlight.pointerInteraction) {
        if (isMouseMove) {
            foundElement.classList.add(styles_1.CLASS_HIGHLIGHT_HOVER);
            if (foundHighlight.group !== exports.HIGHLIGHT_GROUP_PAGEBREAK) {
                documant.documentElement.classList.add(styles_1.CLASS_HIGHLIGHT_CURSOR2);
            }
            const text = ((_a = foundHighlight.textPopup) === null || _a === void 0 ? void 0 : _a.text) ? foundHighlight.textPopup.text : undefined;
            if (text && _highlightsContainer) {
                _timeoutMouseMove = win.setTimeout(() => {
                    var _a, _b;
                    _timeoutMouseMove = undefined;
                    if (!_highlightsContainer) {
                        return;
                    }
                    const _highlightsFloatingUI = win.document.getElementById(styles_1.ID_HIGHLIGHTS_FLOATING);
                    if (!_highlightsFloatingUI) {
                        return;
                    }
                    const _highlightsFloatingUI_ARROW = _highlightsFloatingUI.firstElementChild;
                    if (!_highlightsFloatingUI_ARROW) {
                        return;
                    }
                    const _highlightsFloatingUI_TEXT = _highlightsFloatingUI_ARROW.nextElementSibling;
                    if (!_highlightsFloatingUI_TEXT) {
                        return;
                    }
                    const doDrawArrow = foundHighlight.drawType !== highlight_1.HighlightDrawTypeMarginBookmark;
                    _highlightsFloatingUI_ARROW.style.display = doDrawArrow ? "block" : "none";
                    const dir = ((_a = foundHighlight.textPopup) === null || _a === void 0 ? void 0 : _a.dir) ? foundHighlight.textPopup.dir : "ltr";
                    const lang = ((_b = foundHighlight.textPopup) === null || _b === void 0 ? void 0 : _b.lang) ? foundHighlight.textPopup.lang : "en";
                    const zoom = foundElement.__inverseZoom || 1;
                    if (dir) {
                        _highlightsFloatingUI_TEXT.setAttribute("dir", dir);
                    }
                    else {
                        _highlightsFloatingUI_TEXT.removeAttribute("dir");
                    }
                    if (lang) {
                        _highlightsFloatingUI_TEXT.setAttribute("lang", lang);
                        _highlightsFloatingUI_TEXT.setAttributeNS("http://www.w3.org/XML/1998/", "lang", lang);
                    }
                    else {
                        _highlightsFloatingUI_TEXT.removeAttribute("lang");
                        _highlightsFloatingUI_TEXT.removeAttributeNS("http://www.w3.org/XML/1998/", "lang");
                    }
                    _highlightsFloatingUI_TEXT.style.writingMode = "horizontal-tb";
                    _highlightsFloatingUI_TEXT.textContent = text;
                    if (!exports.ENABLE_FLOATING_UI) {
                        const xx = (x - xOffset) * scale;
                        const yy = (y - yOffset) * scale;
                        Object.assign(_highlightsFloatingUI.style, {
                            display: "block",
                            left: `${xx * zoom}px`,
                            top: `${yy * zoom}px`,
                        });
                    }
                    else {
                        Object.assign(_highlightsFloatingUI.style, {
                            display: "block",
                            left: "0px",
                            top: "-999999px",
                            opacity: "0",
                        });
                        const doDrawMargin = drawMargin(foundHighlight);
                        let anchor = null;
                        const all = foundElement.querySelectorAll("svg.R2_CLASS_HIGHLIGHT_CONTOUR > path");
                        if ((all === null || all === void 0 ? void 0 : all.length) > 0) {
                            anchor = all[(all === null || all === void 0 ? void 0 : all.length) - 1];
                        }
                        if (!anchor && doDrawMargin) {
                            anchor = foundElement.querySelector("svg.R2_CLASS_HIGHLIGHT_CONTOUR_MARGIN > path");
                        }
                        if (anchor) {
                            const paginated = (0, readium_css_inject_1.isPaginated)(documant);
                            const virtualElement = {
                                getBoundingClientRect() {
                                    return {
                                        width: 0,
                                        height: 0,
                                        x: x,
                                        y: y,
                                        top: y,
                                        left: x,
                                        right: x,
                                        bottom: y,
                                    };
                                },
                            };
                            let _highlightsFloatingUI_;
                            if (paginated) {
                                const css = win.getComputedStyle(_highlightsFloatingUI);
                                let width = parseFloat(css.width) || 0;
                                let height = parseFloat(css.height) || 0;
                                const offsetWidth = _highlightsFloatingUI.offsetWidth;
                                const offsetHeight = _highlightsFloatingUI.offsetHeight;
                                const shouldFallback = Math.round(width) !== offsetWidth || Math.round(height) !== offsetHeight;
                                if (shouldFallback) {
                                    width = offsetWidth;
                                    height = offsetHeight;
                                }
                                _highlightsFloatingUI_ = documant.createElementNS(SVG_XML_NAMESPACE, "svg");
                                _highlightsFloatingUI_.setAttribute("id", styles_1.ID_HIGHLIGHTS_FLOATING + "_");
                                Object.assign(_highlightsFloatingUI_.style, {
                                    width: (width / zoom) + "px",
                                    height: (height / zoom) + "px",
                                });
                                _highlightsContainer.append(_highlightsFloatingUI_);
                            }
                            const arrowLen = doDrawArrow ? _highlightsFloatingUI_ARROW.offsetWidth : 0;
                            const floatingOffset = doDrawArrow ? (Math.sqrt(2 * arrowLen ** 2) / 2) : 0;
                            (0, dom_1.computePosition)(anchor || virtualElement, paginated ? _highlightsFloatingUI_ : _highlightsFloatingUI, {
                                strategy: paginated ? "fixed" : "absolute",
                                placement: "bottom",
                                middleware: [
                                    (0, dom_1.offset)(floatingOffset),
                                    (0, dom_1.flip)(),
                                    (0, dom_1.shift)({ padding: 4 }),
                                    doDrawArrow ? (0, dom_1.arrow)({ padding: 8, element: _highlightsFloatingUI_ARROW }) : undefined,
                                ].filter((v) => !!v),
                            })
                                .then(({ x: fuix, y: fuiy, middlewareData, placement }) => {
                                if (doDrawArrow && middlewareData.arrow && _highlightsFloatingUI_ARROW) {
                                    const side = placement.split("-")[0];
                                    const staticSide = {
                                        top: "bottom",
                                        right: "left",
                                        bottom: "top",
                                        left: "right",
                                    }[side];
                                    console.log("middlewareData.arrow", middlewareData.arrow.x, middlewareData.arrow.y);
                                    const { x: xarrow, y: yarrow } = middlewareData.arrow;
                                    if (xarrow != null || yarrow != null) {
                                        Object.assign(_highlightsFloatingUI_ARROW.style, {
                                            left: xarrow != null ? `${xarrow}px` : "",
                                            top: yarrow != null ? `${yarrow}px` : "",
                                            right: "",
                                            bottom: "",
                                            [staticSide]: `${-arrowLen / 2}px`,
                                            transform: staticSide === "top" ? "rotate(45deg)" : "rotate(225deg)",
                                        });
                                    }
                                }
                                const xx = paginated ? (fuix - xOffset) * zoom : fuix;
                                const yy = paginated ? (fuiy - yOffset) * zoom : fuiy;
                                if (_highlightsFloatingUI) {
                                    Object.assign(_highlightsFloatingUI.style, {
                                        display: "block",
                                        left: `${xx}px`,
                                        top: `${yy}px`,
                                        opacity: "1",
                                    });
                                }
                                if (_highlightsFloatingUI_) {
                                    _highlightsFloatingUI_.remove();
                                }
                            });
                        }
                        else {
                            const xx = (x - xOffset) * scale;
                            const yy = (y - yOffset) * scale;
                            Object.assign(_highlightsFloatingUI.style, {
                                display: "block",
                                left: `${xx * zoom}px`,
                                top: `${yy * zoom}px`,
                                opacity: "1",
                            });
                        }
                    }
                }, TIMEOUT_MOUSE_MS);
            }
        }
        else if ((ev.type === "mouseup" || ev.type === "click") && foundHighlight.group !== exports.HIGHLIGHT_GROUP_PAGEBREAK) {
            documant.documentElement.classList.remove(styles_1.CLASS_HIGHLIGHT_CURSOR2);
            const _highlightsFloatingUI = win.document.getElementById(styles_1.ID_HIGHLIGHTS_FLOATING);
            if (_highlightsFloatingUI && _highlightsFloatingUI.style.display !== "none") {
                _highlightsFloatingUI.style.display = "none";
            }
            ev.preventDefault();
            ev.stopPropagation();
            const payload = {
                highlight: foundHighlight,
                event: {
                    type: ev.type,
                    button: ev.button,
                    alt: ev.altKey,
                    shift: ev.shiftKey,
                    ctrl: ev.ctrlKey,
                    meta: ev.metaKey,
                    x: ev.clientX,
                    y: ev.clientY,
                },
            };
            electron_1.ipcRenderer.sendToHost(events_1.R2_EVENT_HIGHLIGHT_CLICK, payload);
        }
    }
    else {
        const _highlightsFloatingUI = win.document.getElementById(styles_1.ID_HIGHLIGHTS_FLOATING);
        if (_highlightsFloatingUI && _highlightsFloatingUI.style.display !== "none") {
            _highlightsFloatingUI.style.display = "none";
        }
    }
}
const computeInverseZoom = (bodyComputedStyle, rootComputedStyle) => {
    let zoomStr = rootComputedStyle.zoom;
    if (!zoomStr || zoomStr === "1") {
        zoomStr = bodyComputedStyle.zoom;
    }
    if (zoomStr) {
        const zoomFactor = parseFloat(zoomStr);
        if (zoomFactor !== 0) {
            const inverseZoom = 1 / zoomFactor;
            return inverseZoom;
        }
    }
    return 1;
};
function ensureHighlightsContainer(win, _bodyComputedStyle, _rootComputedStyle) {
    const documant = win.document;
    if (!_highlightsContainer) {
        if (!bodyEventListenersSet) {
            bodyEventListenersSet = true;
            documant.body.addEventListener("mousedown", (ev) => {
                lastMouseDownX = ev.clientX;
                lastMouseDownY = ev.clientY;
            }, false);
            documant.body.addEventListener("mouseup", (ev) => {
                if ((Math.abs(lastMouseDownX - ev.clientX) < 3) &&
                    (Math.abs(lastMouseDownY - ev.clientY) < 3)) {
                    processMouseEvent(win, ev);
                }
            }, false);
            documant.body.addEventListener("mousemove", (ev) => {
                processMouseEvent(win, ev);
            }, false);
        }
        const _highlightsContainer_ = documant.createElement("div");
        _highlightsContainer_.setAttribute("aria-hidden", "true");
        _highlightsContainer_.setAttribute("id", styles_1.ID_HIGHLIGHTS_CONTAINER);
        _highlightsContainer_.setAttribute("class", styles_1.CLASS_HIGHLIGHT_COMMON);
        _highlightsContainer_.setAttribute("style", `width: ${win.READIUM2.isFixedLayout ? "-webkit-fill-available" : "auto"} !important; ` +
            `height: ${win.READIUM2.isFixedLayout ? "-webkit-fill-available" : "auto"} !important; `);
        const _highlightsFloatingUI = documant.createElement("div");
        _highlightsFloatingUI.setAttribute("id", styles_1.ID_HIGHLIGHTS_FLOATING);
        const _highlightsFloatingUI_ARROW = documant.createElement("div");
        _highlightsFloatingUI.append(_highlightsFloatingUI_ARROW);
        const _highlightsFloatingUI_TEXT = documant.createElement("div");
        _highlightsFloatingUI.append(_highlightsFloatingUI_TEXT);
        _highlightsContainer_.append(_highlightsFloatingUI);
        documant.body.append(_highlightsContainer_);
        _highlightsContainer = _highlightsContainer_;
    }
    return _highlightsContainer;
}
function hideAllhighlights(_documant) {
    if (IS_DEV) {
        console.log("--HIGH WEBVIEW-- hideAllhighlights: " + _highlights.length);
    }
    if (exports.ENABLE_CSS_HIGHLIGHTS) {
        CSS.highlights.clear();
    }
    if (_highlightsContainer) {
        _highlightsContainer.remove();
        _highlightsContainer = undefined;
    }
}
function destroyAllhighlights(documant) {
    if (IS_DEV) {
        console.log("--HIGH WEBVIEW-- destroyAllhighlights: " + _highlights.length);
    }
    hideAllhighlights(documant);
    _highlights.splice(0, _highlights.length);
}
function destroyHighlight(documant, id) {
    if (IS_DEV) {
        console.log("--HIGH WEBVIEW-- destroyHighlight: " + id + " ... " + _highlights.length);
    }
    let i = -1;
    const highlight = _highlights.find((h, j) => {
        i = j;
        return h.id === id;
    });
    if (highlight && i >= 0 && i < _highlights.length) {
        _highlights.splice(i, 1);
    }
    const highlightContainer = documant.getElementById(id);
    if (highlightContainer) {
        highlightContainer.remove();
    }
    if (exports.ENABLE_CSS_HIGHLIGHTS && highlight && highlight.rangeCssHighlight) {
        const [_strRGB, cssHighlightID] = computeCssHighlightRGBID(highlight);
        const cssHighlight = CSS.highlights.get(cssHighlightID);
        if (cssHighlight && cssHighlight.has(highlight.rangeCssHighlight)) {
            cssHighlight.delete(highlight.rangeCssHighlight);
        }
    }
}
function destroyHighlightsGroup(documant, group) {
    if (IS_DEV) {
        console.log("--HIGH WEBVIEW-- destroyHighlightsGroup: " + group + " ... " + _highlights.length);
    }
    while (true) {
        let i = -1;
        const highlight = _highlights.find((h, j) => {
            i = j;
            return h.group === group;
        });
        if (highlight) {
            if (i >= 0 && i < _highlights.length) {
                _highlights.splice(i, 1);
            }
            const highlightContainer = documant.getElementById(highlight.id);
            if (highlightContainer) {
                highlightContainer.remove();
            }
            if (exports.ENABLE_CSS_HIGHLIGHTS && highlight.rangeCssHighlight) {
                const [_strRGB, cssHighlightID] = computeCssHighlightRGBID(highlight);
                const cssHighlight = CSS.highlights.get(cssHighlightID);
                if (cssHighlight && cssHighlight.has(highlight.rangeCssHighlight)) {
                    cssHighlight.delete(highlight.rangeCssHighlight);
                }
            }
        }
        else {
            break;
        }
    }
}
function recreateAllHighlightsRaw(win, highlights) {
    if (IS_DEV) {
        console.log("--HIGH WEBVIEW-- recreateAllHighlightsRaw: " + _highlights.length + " ==> " + (highlights === null || highlights === void 0 ? void 0 : highlights.length));
    }
    const documant = win.document;
    if (highlights === null || highlights === void 0 ? void 0 : highlights.length) {
        if (_highlights.length) {
            if (IS_DEV) {
                console.log("--HIGH WEBVIEW-- recreateAllHighlightsRaw DESTROY OLD BEFORE RESTORE BACKUP: " + _highlights.length + " ==> " + highlights.length);
            }
            destroyAllhighlights(documant);
        }
        if (IS_DEV) {
            console.log("--HIGH WEBVIEW-- recreateAllHighlightsRaw RESTORE BACKUP: " + _highlights.length + " ==> " + highlights.length);
        }
        _highlights.push(...highlights);
    }
    if (!_highlights.length) {
        return;
    }
    if (!documant.body) {
        if (IS_DEV) {
            console.log("--HIGH WEBVIEW-- NO BODY?! (retrying...): " + _highlights.length);
        }
        (0, exports.recreateAllHighlightsDebounced)(win);
        return;
    }
    hideAllhighlights(documant);
    const bodyRect = getBoundingClientRectOfDocumentBody(win);
    const rootComputedStyle = win.getComputedStyle(documant.documentElement);
    const bodyComputedStyle = win.getComputedStyle(documant.body);
    const docFrag = documant.createDocumentFragment();
    for (const highlight of _highlights) {
        const r = adjustRangeInfo(win, highlight.range, highlight.selectionInfo);
        if (r) {
            highlight.range = r;
        }
        else if (r === null) {
        }
        else if (typeof r === "undefined") {
            continue;
        }
        let div;
        try {
            div = createHighlightDom(win, highlight, bodyRect, bodyComputedStyle, rootComputedStyle);
        }
        catch (err) {
            console.log("createHighlightDom ERROR:");
            console.log(err);
        }
        if (div) {
            docFrag.append(div);
        }
    }
    if (IS_DEV) {
        console.log("--HIGH WEBVIEW-- createHighlightDom DONE: " + _highlights.length);
    }
    const highlightsContainer = ensureHighlightsContainer(win, bodyComputedStyle, rootComputedStyle);
    highlightsContainer.append(docFrag);
}
exports.recreateAllHighlightsDebounced = debounce((win) => {
    if (IS_DEV) {
        console.log("--HIGH WEBVIEW-- recreateAllHighlightsDebounced: " + _highlights.length);
    }
    recreateAllHighlightsRaw(win);
}, 500);
function recreateAllHighlights(win) {
    if (IS_DEV) {
        console.log("--HIGH WEBVIEW-- recreateAllHighlights: " + _highlights.length);
    }
    hideAllhighlights(win.document);
    (0, exports.recreateAllHighlightsDebounced)(win);
}
function createHighlights(win, highDefs, pointerInteraction) {
    if (IS_DEV) {
        console.log("--HIGH WEBVIEW-- createHighlights: " + highDefs.length + " ... " + _highlights.length);
    }
    const documant = win.document;
    const highlights = [];
    const bodyRect = getBoundingClientRectOfDocumentBody(win);
    const rootComputedStyle = win.getComputedStyle(documant.documentElement);
    const bodyComputedStyle = win.getComputedStyle(documant.body);
    const docFrag = documant.createDocumentFragment();
    for (const highDef of highDefs) {
        if (!highDef.selectionInfo && !highDef.range) {
            highlights.push(null);
            continue;
        }
        const hh = createHighlight(win, highDef.selectionInfo, highDef.range, highDef.color, pointerInteraction, highDef.drawType, highDef.expand, highDef.group, highDef.marginText, highDef.textPopup, bodyRect, bodyComputedStyle, rootComputedStyle);
        if (hh) {
            highlights.push(hh[0]);
            if (hh[1]) {
                docFrag.append(hh[1]);
            }
        }
    }
    const highlightsContainer = ensureHighlightsContainer(win, bodyComputedStyle, rootComputedStyle);
    highlightsContainer.append(docFrag);
    return highlights;
}
const computeCFI = (node) => {
    if (node.nodeType !== Node.ELEMENT_NODE) {
        if (node.parentNode) {
            return computeCFI(node.parentNode);
        }
        return undefined;
    }
    let cfi = "";
    let currentElement = node;
    while (currentElement.parentNode && currentElement.parentNode.nodeType === Node.ELEMENT_NODE) {
        const currentElementParentChildren = currentElement.parentNode.children;
        let currentElementIndex = -1;
        for (let i = 0; i < currentElementParentChildren.length; i++) {
            if (currentElement === currentElementParentChildren[i]) {
                currentElementIndex = i;
                break;
            }
        }
        if (currentElementIndex >= 0) {
            const cfiIndex = (currentElementIndex + 1) * 2;
            cfi = cfiIndex +
                (currentElement.id ? ("[" + currentElement.id + "]") : "") +
                (cfi.length ? ("/" + cfi) : "");
        }
        currentElement = currentElement.parentNode;
    }
    return "/" + cfi;
};
const adjustRangeInfo = (win, range, selectionInfo) => {
    if ((!range || !range.startContainer) &&
        selectionInfo &&
        selectionInfo.rangeInfo.startContainerElementCssSelector === selectionInfo.rangeInfo.endContainerElementCssSelector &&
        selectionInfo.rangeInfo.startContainerChildTextNodeIndex === -1 &&
        selectionInfo.rangeInfo.startOffset === -1 &&
        selectionInfo.rangeInfo.endOffset === -1) {
        console.log("createHighlight EMPTY selectionInfo", JSON.stringify(selectionInfo, null, 4));
        const el = win.document.querySelector(selectionInfo.rangeInfo.startContainerElementCssSelector);
        if (el) {
            console.log("createHighlight EMPTY selectionInfo: ELEMENT match", selectionInfo.rangeInfo.startContainerElementCssSelector);
            let _firstTextNode;
            const scanTextNodes = (elem) => {
                var _a, _b;
                const lower = elem.tagName.toLowerCase();
                if (elem.getAttribute("id") === styles_1.ID_HIGHLIGHTS_CONTAINER ||
                    lower === "audio" || lower === "img" || lower === "script" || lower === "noscript") {
                    return;
                }
                for (let i = 0; i < elem.childNodes.length; i++) {
                    const childNode = elem.childNodes[i];
                    if (childNode.nodeType === 1) {
                        if (!_firstTextNode) {
                            scanTextNodes(childNode);
                        }
                    }
                    else if (childNode.nodeType === 3 && (((_a = childNode.nodeValue) === null || _a === void 0 ? void 0 : _a.length) || 0) > 0) {
                        let text = (_b = childNode.nodeValue) === null || _b === void 0 ? void 0 : _b.replace(/\s\s+/g, " ");
                        if (text) {
                            text = text.trim();
                        }
                        if (text && !_firstTextNode) {
                            _firstTextNode = childNode;
                        }
                    }
                }
            };
            scanTextNodes(el);
            if (_firstTextNode) {
                console.log("createHighlight EMPTY selectionInfo: FIRST TEXT NODE found", _firstTextNode.nodeValue);
                range = new Range();
                range.selectNodeContents(_firstTextNode);
                range.setStart(range.startContainer, range.startOffset);
                range.setEnd(range.endContainer, range.startOffset + 1);
                return range;
            }
            else {
                if (el === win.document.documentElement || el === win.document.body) {
                    console.log("createHighlight EMPTY selectionInfo: FIRST TEXT NODE not found, fallback ELEMENT inside HTML BODY", el.nodeName);
                    let _firstLeafElement;
                    const scanElementNodes = (elem) => {
                        for (let i = 0; i < elem.childNodes.length; i++) {
                            const childNode = elem.childNodes[i];
                            if (childNode.nodeType === 1) {
                                if (!_firstLeafElement && !childNode.childNodes.length) {
                                    _firstLeafElement = childNode;
                                }
                                if (!_firstLeafElement) {
                                    scanElementNodes(childNode);
                                }
                            }
                        }
                    };
                    scanElementNodes(el);
                    range = new Range();
                    if (!win && _firstLeafElement) {
                        console.log("createHighlight EMPTY selectionInfo: fallback ELEMENT first leaf: ", _firstLeafElement.nodeName);
                        range.selectNode(_firstLeafElement);
                    }
                    else {
                        console.log("createHighlight EMPTY selectionInfo: fallback fail: ", el.nodeName);
                        range.selectNodeContents(el);
                    }
                    return range;
                }
                else {
                    console.log("createHighlight EMPTY selectionInfo: FIRST TEXT NODE not found, fallback ELEMENT", el.nodeName);
                    range = new Range();
                    range.selectNode(el);
                    return range;
                }
            }
        }
        else {
            console.log("createHighlight EMPTY selectionInfo: ELEMENT NOT match", selectionInfo.rangeInfo.startContainerElementCssSelector);
            return undefined;
        }
    }
    return null;
};
function createHighlight(win, selectionInfo, range, color, pointerInteraction, drawType, expand, group, marginText, textPopup, bodyRect, bodyComputedStyle, rootComputedStyle) {
    const r = adjustRangeInfo(win, range, selectionInfo);
    if (r) {
        range = r;
    }
    else if (r === null) {
    }
    else if (typeof r === "undefined") {
        return undefined;
    }
    const uniqueStr = selectionInfo ? `${selectionInfo.rangeInfo.startContainerElementCssSelector}${selectionInfo.rangeInfo.startContainerChildTextNodeIndex}${selectionInfo.rangeInfo.startOffset}${selectionInfo.rangeInfo.endContainerElementCssSelector}${selectionInfo.rangeInfo.endContainerChildTextNodeIndex}${selectionInfo.rangeInfo.endOffset}` : range ? `${range.startOffset}-${range.endOffset}-${computeCFI(range.startContainer)}-${computeCFI(range.endContainer)}` : "_RANGE_";
    const checkSum = crypto.createHash("sha1");
    checkSum.update(uniqueStr);
    const shaHex = checkSum.digest("hex");
    const idBase = "R2_HIGHLIGHT_" + shaHex;
    let id = idBase;
    let idIdx = 0;
    while (_highlights.find((h) => h.id === id) ||
        win.document.getElementById(id)) {
        if (IS_DEV) {
            console.log("HIGHLIGHT ID already exists, increment: " + uniqueStr + " ==> " + id);
        }
        id = `${idBase}_${idIdx++}`;
    }
    const highlight = {
        color: color ? color : DEFAULT_BACKGROUND_COLOR,
        drawType,
        expand,
        id,
        pointerInteraction,
        selectionInfo,
        range,
        group,
        marginText,
        textPopup,
    };
    _highlights.push(highlight);
    let div;
    try {
        div = createHighlightDom(win, highlight, bodyRect, bodyComputedStyle, rootComputedStyle);
    }
    catch (err) {
        console.log("createHighlightDom ERROR:");
        console.log(err);
    }
    return [highlight, div || null];
}
const computeCssHighlightRGBID = (highlight) => {
    const drawUnderline = highlight.drawType === highlight_1.HighlightDrawTypeUnderline;
    const drawStrikeThrough = highlight.drawType === highlight_1.HighlightDrawTypeStrikethrough;
    const strRGB = `R${highlight.color.red}G${highlight.color.green}B${highlight.color.blue}${drawUnderline ? "_" : drawStrikeThrough ? "__" : ""}`;
    const cssHighlightID = `highlight_${strRGB}`;
    return [strRGB, cssHighlightID];
};
const calcRGB = (rgb) => {
    return (rgb <= 0.03928) ? rgb / 12.92 : ((rgb + 0.055) / 1.055) ** 2.4;
};
const computeHighContrastForegroundColourForBackground = (color) => {
    let foregroundColour = "#ffffff";
    const red = calcRGB(color.red);
    const green = calcRGB(color.green);
    const blue = calcRGB(color.blue);
    const luminance = 0.2126 * red + 0.7152 * green + 0.0722 * blue;
    const pickBlack = (luminance + 0.05) / 0.05;
    const pickWhite = 1.05 / (luminance + 0.05);
    if (pickBlack > pickWhite) {
        foregroundColour = "#000000";
    }
    return foregroundColour;
};
const JAPANESE_RUBY_TO_SKIP = ["rt", "rp"];
function createHighlightDom(win, highlight, bodyRect, bodyComputedStyle, rootComputedStyle) {
    var _a, _b, _c;
    const DEBUG_RECTS = window.DEBUG_RECTS;
    const documant = win.document;
    const scrollElement = (0, readium_css_1.getScrollingElement)(documant);
    const range = highlight.range ? highlight.range : highlight.selectionInfo ? (0, selection_1.convertRangeInfo)(documant, highlight.selectionInfo.rangeInfo) : undefined;
    if (!range) {
        return null;
    }
    let rangeHasSVG = false;
    let parent = range.startContainer;
    while (parent) {
        if (parent.nodeType === Node.ELEMENT_NODE) {
            const ns = parent.namespaceURI;
            if (ns && ns.includes("svg")) {
                rangeHasSVG = true;
                break;
            }
        }
        parent = parent.parentNode;
    }
    if (!rangeHasSVG) {
        parent = range.endContainer;
        while (parent) {
            if (parent.nodeType === Node.ELEMENT_NODE) {
                const ns = parent.namespaceURI;
                if (ns && ns.includes("svg")) {
                    rangeHasSVG = true;
                    break;
                }
            }
            parent = parent.parentNode;
        }
    }
    const drawBackground = !highlight.drawType || highlight.drawType === highlight_1.HighlightDrawTypeBackground;
    const drawUnderline = highlight.drawType === highlight_1.HighlightDrawTypeUnderline;
    const drawStrikeThrough = highlight.drawType === highlight_1.HighlightDrawTypeStrikethrough;
    const drawOutline = highlight.drawType === highlight_1.HighlightDrawTypeOutline;
    const drawOpacityMask = highlight.drawType === highlight_1.HighlightDrawTypeOpacityMask;
    const drawOpacityMaskRuler = highlight.drawType === highlight_1.HighlightDrawTypeOpacityMaskRuler;
    const drawMarginBookmark = highlight.drawType === highlight_1.HighlightDrawTypeMarginBookmark;
    const paginated = (0, readium_css_inject_1.isPaginated)(documant);
    const rtl = (0, readium_css_2.isRTL)();
    const isVWM = (0, readium_css_1.isVerticalWritingMode)();
    const doDrawMargin = drawMargin(highlight);
    const inverseZoom = computeInverseZoom(bodyComputedStyle, rootComputedStyle);
    const underlineThickness = 3 / inverseZoom;
    const strikeThroughLineThickness = 3 / inverseZoom;
    if (exports.ENABLE_CSS_HIGHLIGHTS && !doDrawMargin && !rangeHasSVG && (drawBackground || (drawUnderline && !isVWM) || (drawStrikeThrough && !isVWM))) {
        highlight.rangeCssHighlight = range;
        const [strRGB, cssHighlightID] = computeCssHighlightRGBID(highlight);
        const styleElement = win.document.getElementById("Readium2-" + strRGB);
        if (!styleElement) {
            const foregroundColour = computeHighContrastForegroundColourForBackground(highlight.color);
            (0, readium_css_inject_1.appendCSSInline)(win.document, strRGB, drawUnderline || drawStrikeThrough
                ?
                    `
::highlight(${cssHighlightID}) {
    text-decoration-color: rgb(${highlight.color.red}, ${highlight.color.green}, ${highlight.color.blue});
    text-decoration-style: solid;
    text-decoration-thickness: ${0.16 / inverseZoom}em;
    text-decoration-line: ${drawUnderline ? "underline" : "line-through"};
}
`
                :
                    `
::highlight(${cssHighlightID}) {
    background-color: rgb(${highlight.color.red}, ${highlight.color.green}, ${highlight.color.blue});
    color: ${foregroundColour};
}
@supports (color: contrast-color(red)) {

    ::highlight(${cssHighlightID}) {
        background-color: rgb(${highlight.color.red}, ${highlight.color.green}, ${highlight.color.blue});

        color: contrast-color(rgb(${highlight.color.red}, ${highlight.color.green}, ${highlight.color.blue}));
        text-shadow: none;
    }
}
`);
        }
        let cssHighlight = CSS.highlights.get(cssHighlightID);
        if (!cssHighlight) {
            cssHighlight = new Highlight();
            CSS.highlights.set(cssHighlightID, cssHighlight);
        }
        cssHighlight.add(highlight.rangeCssHighlight);
    }
    const highlightParent = documant.createElement("div");
    highlightParent.setAttribute("id", highlight.id);
    highlightParent.setAttribute("class", `${styles_1.CLASS_HIGHLIGHT_CONTAINER} ${styles_1.CLASS_HIGHLIGHT_COMMON}`);
    highlightParent.setAttribute("data-type", `${highlight.drawType || highlight_1.HighlightDrawTypeBackground}`);
    if (highlight.group) {
        highlightParent.setAttribute("data-group", highlight.group);
    }
    if (doDrawMargin) {
        highlightParent.classList.add(styles_1.CLASS_HIGHLIGHT_MARGIN);
    }
    highlightParent.__inverseZoom = inverseZoom;
    if (drawBackground) {
        highlightParent.classList.add(styles_1.CLASS_HIGHLIGHT_BEHIND);
    }
    if ((drawOpacityMask || drawOpacityMaskRuler)) {
        highlightParent.classList.add(styles_1.CLASS_HIGHLIGHT_MASK);
    }
    if (!highlight.pointerInteraction &&
        (highlight.rangeCssHighlight)) {
        return highlightParent;
    }
    const xOffset = paginated ? (-scrollElement.scrollLeft) : bodyRect.left;
    const yOffset = paginated ? (-scrollElement.scrollTop) : bodyRect.top;
    const scale = 1 / ((win.READIUM2 && win.READIUM2.isFixedLayout) ? win.READIUM2.fxlViewportScale : 1);
    const doNotMergeHorizontallyAlignedRects = drawUnderline || drawStrikeThrough;
    let clientRects;
    const rangeClientRects = (0, rect_utils_1.DOMRectListToArray)(range.getClientRects());
    if (doNotMergeHorizontallyAlignedRects) {
        const textClientRects = (0, rect_utils_1.getTextClientRects)(range, JAPANESE_RUBY_TO_SKIP);
        const textReducedClientRects = (0, rect_utils_1.getClientRectsNoOverlap)(textClientRects, true, isVWM, highlight.expand ? highlight.expand : 0);
        clientRects = (DEBUG_RECTS && drawStrikeThrough) ? textClientRects : textReducedClientRects;
    }
    else {
        if (drawMarginBookmark &&
            rangeClientRects.length === 2 &&
            Math.floor(rangeClientRects[0].width) === 0 &&
            Math.floor(rangeClientRects[1].width) === 0) {
            rangeClientRects[0].width = 2;
            rangeClientRects[0].left -= 1;
            rangeClientRects[0].right += 1;
            rangeClientRects[1].width = 2;
            rangeClientRects[1].left -= 1;
            rangeClientRects[1].right += 1;
        }
        clientRects = (0, rect_utils_1.getClientRectsNoOverlap)(rangeClientRects, false, isVWM, highlight.expand ? highlight.expand : 0);
    }
    const gap = 2;
    const gapX = ((drawOutline || drawBackground) ? 4 : 0);
    const boxesNoGapExpanded = [];
    const boxesGapExpanded = [];
    for (const clientRect of clientRects) {
        const rect = {
            height: clientRect.height,
            left: clientRect.left - xOffset,
            top: clientRect.top - yOffset,
            width: clientRect.width,
        };
        const w = rect.width * scale;
        const h = rect.height * scale;
        const x = rect.left * scale;
        const y = rect.top * scale;
        boxesGapExpanded.push(new core_1.Box(Number((x - gap).toPrecision(12)), Number((y - gap).toPrecision(12)), Number((x + w + gap).toPrecision(12)), Number((y + h + gap).toPrecision(12))));
        if (drawStrikeThrough) {
            const thickness = DEBUG_RECTS ? (isVWM ? rect.width : rect.height) : strikeThroughLineThickness;
            const ww = (isVWM ? thickness : rect.width) * scale;
            const hh = (isVWM ? rect.height : thickness) * scale;
            const xx = (isVWM
                ?
                    (DEBUG_RECTS
                        ?
                            rect.left
                        :
                            (rect.left + (rect.width / 2) - (thickness / 2)))
                :
                    rect.left) * scale;
            const yy = (isVWM
                ?
                    rect.top
                :
                    (DEBUG_RECTS
                        ?
                            rect.top
                        :
                            (rect.top + (rect.height / 2) - (thickness / 2)))) * scale;
            boxesNoGapExpanded.push(new core_1.Box(Number((xx - gapX).toPrecision(12)), Number((yy - gapX).toPrecision(12)), Number((xx + ww + gapX).toPrecision(12)), Number((yy + hh + gapX).toPrecision(12))));
        }
        else {
            const thickness = DEBUG_RECTS ? (isVWM ? rect.width : rect.height) : underlineThickness;
            if (drawUnderline) {
                const ww = (isVWM ? thickness : rect.width) * scale;
                const hh = (isVWM ? rect.height : thickness) * scale;
                const xx = (isVWM
                    ?
                        (DEBUG_RECTS
                            ?
                                rect.left
                            :
                                (rect.left - (thickness + thickness / 2)))
                    :
                        rect.left) * scale;
                const yy = (isVWM
                    ?
                        rect.top
                    :
                        (DEBUG_RECTS
                            ?
                                rect.top
                            :
                                (rect.top + rect.height - (thickness / 2)))) * scale;
                boxesNoGapExpanded.push(new core_1.Box(Number((xx - gapX).toPrecision(12)), Number((yy - gapX).toPrecision(12)), Number((xx + ww + gapX).toPrecision(12)), Number((yy + hh + gapX).toPrecision(12))));
            }
            else {
                boxesNoGapExpanded.push(new core_1.Box(Number((x - gapX).toPrecision(12)), Number((y - gapX).toPrecision(12)), Number((x + w + gapX).toPrecision(12)), Number((y + h + gapX).toPrecision(12))));
            }
        }
    }
    const polygonCountourUnionPoly = boxesGapExpanded.reduce((previousPolygon, currentBox) => {
        const p = new core_1.Polygon();
        const f = p.addFace(currentBox);
        if (f.orientation() !== BASE_ORIENTATION) {
            console.log("--POLYGON FACE ORIENTATION CCW/CW reverse() 2");
            f.reverse();
        }
        return unify(previousPolygon, p);
    }, new core_1.Polygon());
    Array.from(polygonCountourUnionPoly.faces).forEach((face) => {
        if (face.orientation() !== BASE_ORIENTATION) {
            if (DEBUG_RECTS) {
                console.log("--HIGH WEBVIEW-- removing polygon orientation face / inner hole (contour))");
            }
            polygonCountourUnionPoly.deleteFace(face);
        }
    });
    cleanupPolygon(polygonCountourUnionPoly, gap);
    const bodyPaddingLeft = parseInt(bodyComputedStyle.paddingLeft, 10) / inverseZoom;
    const bodyPaddingRight = parseInt(bodyComputedStyle.paddingRight, 10) / inverseZoom;
    const bodyWidth = parseInt(bodyComputedStyle.width, 10) / inverseZoom;
    const bodyHeight = parseInt(bodyComputedStyle.height, 10) / inverseZoom;
    const paginatedTwo = paginated && (0, readium_css_1.isTwoPageSpread)();
    const paginatedWidth = scrollElement.clientWidth / (paginatedTwo ? 2 : 1);
    const paginatedGap = (paginatedWidth - bodyWidth) / 2;
    const paginatedOffset = paginatedGap + bodyPaddingLeft;
    const useFastBoundingRect = true;
    if (drawOpacityMask || drawOpacityMaskRuler) {
        let boundingRectMaskBase;
        const polygonMaskBaseRects = [];
        const bodyRect_ = {
            left: win.READIUM2.isFixedLayout
                ?
                    0
                :
                    (rtl
                        ?
                            (paginated
                                ?
                                    -(paginatedGap + paginatedGap + bodyRect.width - (paginatedWidth * (paginatedTwo ? 2 : 1)))
                                :
                                    0)
                        :
                            (paginated
                                ?
                                    0
                                :
                                    0)),
            top: win.READIUM2.isFixedLayout ? 0 : rtl ? 0 : 0,
            width: win.READIUM2.isFixedLayout
                ?
                    bodyRect.width * scale
                :
                    (rtl
                        ?
                            (paginated
                                ?
                                    bodyRect.width + paginatedGap + paginatedGap
                                :
                                    bodyRect.width)
                        :
                            (paginated
                                ?
                                    bodyRect.width + paginatedGap + paginatedGap
                                :
                                    bodyRect.width)),
            height: win.READIUM2.isFixedLayout
                ?
                    bodyRect.height * scale
                :
                    bodyRect.height,
            right: 0,
            bottom: 0,
        };
        bodyRect_.right = bodyRect_.left + bodyRect_.width;
        bodyRect_.bottom = bodyRect_.top + bodyRect_.height;
        boundingRectMaskBase = boundingRectMaskBase ? (0, rect_utils_1.getBoundingRect)(boundingRectMaskBase, bodyRect_) : bodyRect_;
        polygonMaskBaseRects.push(bodyRect_);
        let polygonMaskBaseUnionPoly;
        if (paginated) {
            const tolerance = 1;
            const groups = [];
            for (const r of polygonMaskBaseRects) {
                const group = groups.find((g) => {
                    return !(r.left < (g.x - tolerance) || r.left > (g.x + tolerance));
                });
                if (!group) {
                    groups.push({
                        x: r.left,
                        boxes: [r],
                    });
                }
                else {
                    (_a = group.boxes) === null || _a === void 0 ? void 0 : _a.push(r);
                }
            }
            boundingRectMaskBase = groups.map((g) => {
                return g.boxes.reduce((prev, cur) => {
                    if (prev === cur) {
                        return cur;
                    }
                    return (0, rect_utils_1.getBoundingRect)(prev, cur);
                }, g.boxes[0]);
            });
            if (boundingRectMaskBase.length === 1) {
                boundingRectMaskBase = boundingRectMaskBase[0];
            }
        }
        if (useFastBoundingRect) {
            if (boundingRectMaskBase) {
                polygonMaskBaseUnionPoly = new core_1.Polygon();
                if (Array.isArray(boundingRectMaskBase)) {
                    for (const b of boundingRectMaskBase) {
                        const f = polygonMaskBaseUnionPoly.addFace(new core_1.Box(b.left, b.top, b.right, b.bottom));
                        if (f.orientation() !== BASE_ORIENTATION) {
                            console.log("--POLYGON FACE ORIENTATION CCW/CW reverse() 6");
                            f.reverse();
                        }
                    }
                }
                else {
                    const f = polygonMaskBaseUnionPoly.addFace(new core_1.Box(boundingRectMaskBase.left, boundingRectMaskBase.top, boundingRectMaskBase.right, boundingRectMaskBase.bottom));
                    if (f.orientation() !== BASE_ORIENTATION) {
                        console.log("--POLYGON FACE ORIENTATION CCW/CW reverse() 7");
                        f.reverse();
                    }
                }
            }
            else {
                const poly = new core_1.Polygon();
                for (const r of polygonMaskBaseRects) {
                    const f = poly.addFace(new core_1.Box(r.left, r.top, r.right, r.bottom));
                    if (f.orientation() !== BASE_ORIENTATION) {
                        console.log("--POLYGON FACE ORIENTATION CCW/CW reverse() 8");
                        f.reverse();
                    }
                }
                polygonMaskBaseUnionPoly = new core_1.Polygon();
                const f = polygonMaskBaseUnionPoly.addFace(poly.box);
                if (f.orientation() !== BASE_ORIENTATION) {
                    console.log("--POLYGON FACE ORIENTATION CCW/CW reverse() 9");
                    f.reverse();
                }
            }
        }
        else {
            polygonMaskBaseUnionPoly = polygonMaskBaseRects.reduce((previousPolygon, r) => {
                const b = new core_1.Box(r.left, r.top, r.right, r.bottom);
                const p = new core_1.Polygon();
                const f = p.addFace(b);
                if (f.orientation() !== BASE_ORIENTATION) {
                    console.log("--POLYGON FACE ORIENTATION CCW/CW reverse() 10");
                    f.reverse();
                }
                return unify(previousPolygon, p);
            }, new core_1.Polygon());
        }
        if (drawOpacityMaskRuler) {
            try {
                polygonMaskBaseUnionPoly = offset(polygonMaskBaseUnionPoly, 20, true);
            }
            catch (e) {
                console.log(e);
            }
        }
        let polygonMaskUnionPoly;
        if (drawOpacityMaskRuler) {
            let boundingRectMask;
            const polygonMaskRects = [];
            for (const f of polygonCountourUnionPoly.faces) {
                const face = f;
                const b = face.box;
                const left = isVWM
                    ?
                        b.xmin
                    :
                        paginated
                            ?
                                ((rtl
                                    ?
                                        paginatedGap + bodyPaddingLeft
                                    :
                                        paginatedGap + bodyPaddingLeft)
                                    + Math.floor(b.xmin / paginatedWidth) * paginatedWidth)
                            :
                                (rtl
                                    ?
                                        0
                                    :
                                        win.READIUM2.isFixedLayout
                                            ?
                                                0
                                            :
                                                0);
                const top = isVWM
                    ?
                        0
                    :
                        b.ymin;
                const width = isVWM
                    ?
                        b.width
                    :
                        paginated
                            ?
                                (rtl
                                    ?
                                        bodyWidth - bodyPaddingLeft - bodyPaddingRight
                                    :
                                        bodyWidth - bodyPaddingLeft - bodyPaddingRight)
                            :
                                bodyWidth;
                const height = isVWM
                    ?
                        bodyHeight
                    :
                        b.height;
                const extra = 0;
                const r = {
                    left: left - (isVWM ? extra : 0),
                    top: top - (isVWM ? 0 : extra),
                    right: left + width + (isVWM ? extra : 0),
                    bottom: top + height + (isVWM ? 0 : extra),
                    width: width + extra * 2,
                    height: height + extra * 2,
                };
                boundingRectMask = boundingRectMask ? (0, rect_utils_1.getBoundingRect)(boundingRectMask, r) : r;
                polygonMaskRects.push(r);
            }
            if (paginated) {
                const tolerance = 1;
                const groups = [];
                for (const r of polygonMaskRects) {
                    const group = groups.find((g) => {
                        return !(r.left < (g.x - tolerance) || r.left > (g.x + tolerance));
                    });
                    if (!group) {
                        groups.push({
                            x: r.left,
                            boxes: [r],
                        });
                    }
                    else {
                        (_b = group.boxes) === null || _b === void 0 ? void 0 : _b.push(r);
                    }
                }
                boundingRectMask = groups.map((g) => {
                    return g.boxes.reduce((prev, cur) => {
                        if (prev === cur) {
                            return cur;
                        }
                        return (0, rect_utils_1.getBoundingRect)(prev, cur);
                    }, g.boxes[0]);
                });
                if (boundingRectMask.length === 1) {
                    boundingRectMask = boundingRectMask[0];
                }
            }
            if (useFastBoundingRect) {
                if (boundingRectMask) {
                    polygonMaskUnionPoly = new core_1.Polygon();
                    if (Array.isArray(boundingRectMask)) {
                        for (const b of boundingRectMask) {
                            const f = polygonMaskUnionPoly.addFace(new core_1.Box(b.left, b.top, b.right, b.bottom));
                            if (f.orientation() !== BASE_ORIENTATION) {
                                console.log("--POLYGON FACE ORIENTATION CCW/CW reverse() 6");
                                f.reverse();
                            }
                        }
                    }
                    else {
                        const f = polygonMaskUnionPoly.addFace(new core_1.Box(boundingRectMask.left, boundingRectMask.top, boundingRectMask.right, boundingRectMask.bottom));
                        if (f.orientation() !== BASE_ORIENTATION) {
                            console.log("--POLYGON FACE ORIENTATION CCW/CW reverse() 7");
                            f.reverse();
                        }
                    }
                }
                else {
                    const poly = new core_1.Polygon();
                    for (const r of polygonMaskRects) {
                        const f = poly.addFace(new core_1.Box(r.left, r.top, r.right, r.bottom));
                        if (f.orientation() !== BASE_ORIENTATION) {
                            console.log("--POLYGON FACE ORIENTATION CCW/CW reverse() 8");
                            f.reverse();
                        }
                    }
                    polygonMaskUnionPoly = new core_1.Polygon();
                    const f = polygonMaskUnionPoly.addFace(poly.box);
                    if (f.orientation() !== BASE_ORIENTATION) {
                        console.log("--POLYGON FACE ORIENTATION CCW/CW reverse() 9");
                        f.reverse();
                    }
                }
            }
            else {
                polygonMaskUnionPoly = polygonMaskRects.reduce((previousPolygon, r) => {
                    const b = new core_1.Box(r.left, r.top, r.right, r.bottom);
                    const p = new core_1.Polygon();
                    const f = p.addFace(b);
                    if (f.orientation() !== BASE_ORIENTATION) {
                        console.log("--POLYGON FACE ORIENTATION CCW/CW reverse() 10");
                        f.reverse();
                    }
                    return unify(previousPolygon, p);
                }, new core_1.Polygon());
            }
            try {
                polygonMaskUnionPoly = offset(polygonMaskUnionPoly, 10, false);
            }
            catch (e) {
                console.log(e);
            }
        }
        const polyToDraw = polygonMaskUnionPoly ?
            subtract(polygonMaskBaseUnionPoly, polygonMaskUnionPoly) :
            subtract(polygonMaskBaseUnionPoly, polygonCountourUnionPoly);
        const highlightMaskSVG = documant.createElementNS(SVG_XML_NAMESPACE, "svg");
        highlightMaskSVG.setAttribute("class", `${styles_1.CLASS_HIGHLIGHT_COMMON_SVG} ${styles_1.CLASS_HIGHLIGHT_SVG} ${styles_1.CLASS_HIGHLIGHT_CONTOUR}`);
        highlightMaskSVG.polygon = polyToDraw;
        let rsBackground = bodyComputedStyle.getPropertyValue("--RS__backgroundColor");
        if (!rsBackground) {
            rsBackground = rootComputedStyle.getPropertyValue("--RS__backgroundColor");
        }
        if (rsBackground === "transparent") {
            rsBackground = "";
        }
        let rsForeground = bodyComputedStyle.getPropertyValue("--RS__textColor");
        if (!rsForeground) {
            rsForeground = rootComputedStyle.getPropertyValue("--RS__textColor");
        }
        const svgPathMask = highlightMaskSVG.polygon.scale(inverseZoom, inverseZoom).svg({
            fillRule: "evenodd",
            fill: rsBackground ? rsBackground : "white",
            fillOpacity: 0.9,
            stroke: drawOpacityMaskRuler ? (rsForeground ? rsForeground : "black") : "transparent",
            strokeWidth: drawOpacityMaskRuler ? 1.5 : 0,
            className: undefined,
        });
        highlightMaskSVG.innerHTML = svgPathMask;
        highlightParent.append(highlightMaskSVG);
    }
    if (!highlight.pointerInteraction &&
        (((drawOpacityMask || drawOpacityMaskRuler) && highlight.group === exports.HIGHLIGHT_GROUP_TTS))) {
        return highlightParent;
    }
    if (!drawOpacityMask && !drawOpacityMaskRuler && !drawMarginBookmark) {
        let polygonSurface;
        if (highlight.rangeCssHighlight) {
            polygonSurface = undefined;
        }
        else if (doNotMergeHorizontallyAlignedRects) {
            const singleSVGPath = !DEBUG_RECTS;
            if (singleSVGPath) {
                polygonSurface = new core_1.Polygon();
                for (const box of boxesNoGapExpanded) {
                    const f = polygonSurface.addFace(box);
                    if (f.orientation() !== BASE_ORIENTATION) {
                        console.log("--POLYGON FACE ORIENTATION CCW/CW reverse() 3");
                        f.reverse();
                    }
                }
            }
            else {
                polygonSurface = [];
                for (const box of boxesNoGapExpanded) {
                    const poly = new core_1.Polygon();
                    const f = poly.addFace(box);
                    if (f.orientation() !== BASE_ORIENTATION) {
                        console.log("--POLYGON FACE ORIENTATION CCW/CW reverse() 4");
                        f.reverse();
                    }
                    polygonSurface.push(poly);
                }
            }
        }
        else {
            polygonSurface = boxesNoGapExpanded.reduce((previousPolygon, currentBox) => {
                const p = new core_1.Polygon();
                const f = p.addFace(currentBox);
                if (f.orientation() !== BASE_ORIENTATION) {
                    console.log("--POLYGON FACE ORIENTATION CCW/CW reverse() 5");
                    f.reverse();
                }
                return unify(previousPolygon, p);
            }, new core_1.Polygon());
            Array.from(polygonSurface.faces).forEach((face) => {
                if (face.orientation() !== BASE_ORIENTATION) {
                    if (DEBUG_RECTS) {
                        console.log("--HIGH WEBVIEW-- removing polygon orientation face / inner hole (surface))");
                    }
                    polygonSurface.deleteFace(face);
                }
            });
            if (drawOutline || drawBackground || drawOpacityMask || drawOpacityMaskRuler) {
                if (DEBUG_RECTS) {
                    console.log("--==========--==========--==========--==========--==========--==========");
                    console.log("--POLY FACES BEFORE ...");
                }
                for (const f of polygonSurface.faces) {
                    const face = f;
                    if (DEBUG_RECTS) {
                        console.log("--................--................--................");
                        console.log("--POLY FACE: " + (face.orientation() === core_1.ORIENTATION.CCW ? "CCW" : face.orientation() === core_1.ORIENTATION.CW ? "CW" : "ORIENTATION.NOT_ORIENTABLE"));
                    }
                    for (const edge of face.edges) {
                        if (DEBUG_RECTS) {
                            console.log("--POLY EDGE");
                        }
                        if (edge.isSegment) {
                            if (DEBUG_RECTS) {
                                console.log("--POLY SEGMENT...");
                            }
                            const segment = edge.shape;
                            const pointStart = segment.start;
                            const pointEnd = segment.end;
                            if (DEBUG_RECTS) {
                                console.log("--POLY SEGMENT START x, y: " + pointStart.x + ", " + pointStart.y);
                                console.log("--POLY SEGMENT END x, y: " + pointEnd.x + ", " + pointEnd.y);
                            }
                        }
                        else if (edge.isArc) {
                            if (DEBUG_RECTS) {
                                console.log("--POLY ARC...");
                            }
                            const arc = edge.shape;
                            if (DEBUG_RECTS) {
                                console.log("--POLY ARC: " + arc.start.x + ", " + arc.start.y);
                                console.log("--POLY ARC: " + arc.end.x + ", " + arc.end.y);
                                console.log("--POLY ARC: " + arc.length + " / " + arc.sweep);
                            }
                        }
                    }
                }
                try {
                    polygonSurface = offset(polygonSurface, -(gap + gap / 2));
                }
                catch (e) {
                    console.log(e);
                }
                if (DEBUG_RECTS) {
                    console.log("--==========--==========--==========--==========--==========--==========");
                    console.log("--POLY FACES AFTER ...");
                }
                for (const f of polygonSurface.faces) {
                    const face = f;
                    if (DEBUG_RECTS) {
                        console.log("--................--................--................");
                        console.log("--POLY FACE: " + (face.orientation() === core_1.ORIENTATION.CCW ? "CCW" : face.orientation() === core_1.ORIENTATION.CW ? "CW" : "ORIENTATION.NOT_ORIENTABLE"));
                    }
                    for (const edge of face.edges) {
                        if (DEBUG_RECTS) {
                            console.log("--POLY EDGE");
                        }
                        if (edge.isSegment) {
                            if (DEBUG_RECTS) {
                                console.log("--POLY SEGMENT...");
                            }
                            const segment = edge.shape;
                            const pointStart = segment.start;
                            const pointEnd = segment.end;
                            if (DEBUG_RECTS) {
                                console.log("--POLY SEGMENT START x, y: " + pointStart.x + ", " + pointStart.y);
                                console.log("--POLY SEGMENT END x, y: " + pointEnd.x + ", " + pointEnd.y);
                            }
                        }
                        else if (edge.isArc) {
                            if (DEBUG_RECTS) {
                                console.log("--POLY ARC...");
                            }
                            const arc = edge.shape;
                            if (DEBUG_RECTS) {
                                console.log("--POLY ARC: " + arc.start.x + ", " + arc.start.y);
                                console.log("--POLY ARC: " + arc.end.x + ", " + arc.end.y);
                                console.log("--POLY ARC: " + arc.length + " / " + arc.sweep);
                            }
                        }
                    }
                }
            }
        }
        if (DEBUG_RECTS) {
            addEdgePoints(polygonCountourUnionPoly, 1);
            if (!polygonSurface) {
            }
            else if (Array.isArray(polygonSurface)) {
                for (const poly of polygonSurface) {
                    addEdgePoints(poly, 1);
                }
            }
            else {
                addEdgePoints(polygonSurface, 1);
            }
        }
        const highlightAreaSVG = documant.createElementNS(SVG_XML_NAMESPACE, "svg");
        highlightAreaSVG.setAttribute("class", `${styles_1.CLASS_HIGHLIGHT_COMMON} ${styles_1.CLASS_HIGHLIGHT_CONTOUR}`);
        highlightAreaSVG.polygon = polygonCountourUnionPoly;
        const outlineThickness = 2;
        highlightAreaSVG.innerHTML =
            (polygonSurface ?
                (Array.isArray(polygonSurface)
                    ?
                        polygonSurface.reduce((prevSVGPath, currentPolygon) => {
                            return prevSVGPath + currentPolygon.scale(inverseZoom, inverseZoom).svg({
                                fill: DEBUG_RECTS ? "pink" : (drawOutline || highlight.rangeCssHighlight) ? "transparent" : `rgb(${highlight.color.red}, ${highlight.color.green}, ${highlight.color.blue})`,
                                fillRule: "evenodd",
                                stroke: DEBUG_RECTS ? "magenta" : drawOutline ? `rgb(${highlight.color.red}, ${highlight.color.green}, ${highlight.color.blue})` : "transparent",
                                strokeWidth: DEBUG_RECTS ? 1 : drawOutline ? outlineThickness : 0,
                                fillOpacity: 1,
                                className: undefined,
                            });
                        }, "")
                    :
                        polygonSurface.scale(inverseZoom, inverseZoom).svg({
                            fill: DEBUG_RECTS ? "yellow" : (drawOutline || highlight.rangeCssHighlight) ? "transparent" : `rgb(${highlight.color.red}, ${highlight.color.green}, ${highlight.color.blue})`,
                            fillRule: "evenodd",
                            stroke: DEBUG_RECTS ? "green" : drawOutline ? `rgb(${highlight.color.red}, ${highlight.color.green}, ${highlight.color.blue})` : "transparent",
                            strokeWidth: DEBUG_RECTS ? 1 : drawOutline ? outlineThickness : 0,
                            fillOpacity: 1,
                            className: undefined,
                        })) : "")
                +
                    polygonCountourUnionPoly.scale(inverseZoom, inverseZoom).svg({
                        fill: "transparent",
                        fillRule: "evenodd",
                        stroke: DEBUG_RECTS ? "red" : "transparent",
                        strokeWidth: DEBUG_RECTS ? 1 : 1,
                        fillOpacity: 1,
                        className: undefined,
                    });
        highlightParent.append(highlightAreaSVG);
    }
    if (doDrawMargin && highlight.pointerInteraction) {
        const MARGIN_MARKER_THICKNESS = 14 * (win.READIUM2.isFixedLayout ? 1 : (1 / inverseZoom));
        const MARGIN_MARKER_OFFSET = 6 * (win.READIUM2.isFixedLayout ? 1 : (1 / inverseZoom));
        let boundingRectCountourMargin;
        const polygonCountourMarginRects = [];
        for (const f of polygonCountourUnionPoly.faces) {
            const face = f;
            const b = face.box;
            const left = isVWM
                ?
                    b.xmin
                :
                    paginated
                        ?
                            ((rtl
                                ?
                                    MARGIN_MARKER_OFFSET - paginatedOffset + paginatedWidth
                                :
                                    paginatedOffset - MARGIN_MARKER_OFFSET - MARGIN_MARKER_THICKNESS)
                                +
                                    Math.floor((b.xmin) / paginatedWidth) * paginatedWidth)
                        :
                            (rtl
                                ?
                                    MARGIN_MARKER_OFFSET + bodyRect.width - bodyPaddingRight
                                :
                                    win.READIUM2.isFixedLayout
                                        ?
                                            MARGIN_MARKER_OFFSET
                                        :
                                            bodyPaddingLeft - MARGIN_MARKER_THICKNESS - MARGIN_MARKER_OFFSET);
            const top = isVWM
                ?
                    parseInt(bodyComputedStyle.paddingTop, 10) - MARGIN_MARKER_THICKNESS - MARGIN_MARKER_OFFSET
                :
                    b.ymin;
            const width = isVWM ? b.width : MARGIN_MARKER_THICKNESS;
            const height = isVWM ? MARGIN_MARKER_THICKNESS : b.height;
            const extra = 0;
            const r = {
                left: left - (isVWM ? extra : 0),
                top: top - (isVWM ? 0 : extra),
                right: left + width + (isVWM ? extra : 0),
                bottom: top + height + (isVWM ? 0 : extra),
                width: width + extra * 2,
                height: height + extra * 2,
            };
            boundingRectCountourMargin = boundingRectCountourMargin ? (0, rect_utils_1.getBoundingRect)(boundingRectCountourMargin, r) : r;
            polygonCountourMarginRects.push(r);
        }
        let polygonMarginUnionPoly;
        if (paginated) {
            const tolerance = 1;
            const groups = [];
            for (const r of polygonCountourMarginRects) {
                const group = groups.find((g) => {
                    return !(r.left < (g.x - tolerance) || r.left > (g.x + tolerance));
                });
                if (!group) {
                    groups.push({
                        x: r.left,
                        boxes: [r],
                    });
                }
                else {
                    (_c = group.boxes) === null || _c === void 0 ? void 0 : _c.push(r);
                }
            }
            boundingRectCountourMargin = groups.map((g) => {
                return g.boxes.reduce((prev, cur) => {
                    if (prev === cur) {
                        return cur;
                    }
                    return (0, rect_utils_1.getBoundingRect)(prev, cur);
                }, g.boxes[0]);
            });
            if (boundingRectCountourMargin.length === 1) {
                boundingRectCountourMargin = boundingRectCountourMargin[0];
            }
        }
        if (useFastBoundingRect) {
            if (boundingRectCountourMargin) {
                polygonMarginUnionPoly = new core_1.Polygon();
                if (Array.isArray(boundingRectCountourMargin)) {
                    for (const b of boundingRectCountourMargin) {
                        const f = polygonMarginUnionPoly.addFace(new core_1.Box(b.left, b.top, b.right, b.bottom));
                        if (f.orientation() !== BASE_ORIENTATION) {
                            console.log("--POLYGON FACE ORIENTATION CCW/CW reverse() 6");
                            f.reverse();
                        }
                    }
                }
                else {
                    const f = polygonMarginUnionPoly.addFace(new core_1.Box(boundingRectCountourMargin.left, boundingRectCountourMargin.top, boundingRectCountourMargin.right, boundingRectCountourMargin.bottom));
                    if (f.orientation() !== BASE_ORIENTATION) {
                        console.log("--POLYGON FACE ORIENTATION CCW/CW reverse() 7");
                        f.reverse();
                    }
                }
            }
            else {
                const poly = new core_1.Polygon();
                for (const r of polygonCountourMarginRects) {
                    const f = poly.addFace(new core_1.Box(r.left, r.top, r.right, r.bottom));
                    if (f.orientation() !== BASE_ORIENTATION) {
                        console.log("--POLYGON FACE ORIENTATION CCW/CW reverse() 8");
                        f.reverse();
                    }
                }
                polygonMarginUnionPoly = new core_1.Polygon();
                const f = polygonMarginUnionPoly.addFace(poly.box);
                if (f.orientation() !== BASE_ORIENTATION) {
                    console.log("--POLYGON FACE ORIENTATION CCW/CW reverse() 9");
                    f.reverse();
                }
            }
        }
        else {
            polygonMarginUnionPoly = polygonCountourMarginRects.reduce((previousPolygon, r) => {
                const b = new core_1.Box(r.left, r.top, r.right, r.bottom);
                const p = new core_1.Polygon();
                const f = p.addFace(b);
                if (f.orientation() !== BASE_ORIENTATION) {
                    console.log("--POLYGON FACE ORIENTATION CCW/CW reverse() 10");
                    f.reverse();
                }
                return unify(previousPolygon, p);
            }, new core_1.Polygon());
        }
        if (drawMarginBookmark) {
            const ratio = 3;
            const delta = MARGIN_MARKER_THICKNESS / ratio;
            const polygonMarginUnionPoly_ = polygonMarginUnionPoly.clone();
            try {
                const bbox = polygonMarginUnionPoly.box;
                const vec = new core_1.Vector(bbox.center, new core_1.Point(0, 0));
                polygonMarginUnionPoly = polygonMarginUnionPoly.translate(vec);
                polygonMarginUnionPoly = polygonMarginUnionPoly.scale(1 / ratio, 1 / ratio);
                polygonMarginUnionPoly = polygonMarginUnionPoly.translate(vec.invert());
                polygonMarginUnionPoly = offset(polygonMarginUnionPoly, delta, false);
                const p = new core_1.Polygon();
                const triangleInset = MARGIN_MARKER_THICKNESS / 2.5;
                const f = p.addFace([
                    new core_1.Segment(new core_1.Point(polygonMarginUnionPoly.box.xmin, polygonMarginUnionPoly.box.ymax), new core_1.Point(polygonMarginUnionPoly.box.xmax, polygonMarginUnionPoly.box.ymax)),
                    new core_1.Segment(new core_1.Point(polygonMarginUnionPoly.box.xmax, polygonMarginUnionPoly.box.ymax), new core_1.Point(polygonMarginUnionPoly.box.xmin + polygonMarginUnionPoly.box.width / 2, polygonMarginUnionPoly.box.ymax - triangleInset)),
                    new core_1.Segment(new core_1.Point(polygonMarginUnionPoly.box.xmin + polygonMarginUnionPoly.box.width / 2, polygonMarginUnionPoly.box.ymax - triangleInset), new core_1.Point(polygonMarginUnionPoly.box.xmin, polygonMarginUnionPoly.box.ymax)),
                ]);
                if (f.orientation() !== BASE_ORIENTATION) {
                    console.log("--xPOLYGON FACE ORIENTATION CCW/CW reverse() 10");
                    f.reverse();
                }
                polygonMarginUnionPoly = subtract(polygonMarginUnionPoly, p);
            }
            catch (e) {
                console.log(e);
                polygonMarginUnionPoly = polygonMarginUnionPoly_;
            }
        }
        const highlightMarginSVG = documant.createElementNS(SVG_XML_NAMESPACE, "svg");
        highlightMarginSVG.setAttribute("class", `${styles_1.CLASS_HIGHLIGHT_COMMON} ${styles_1.CLASS_HIGHLIGHT_CONTOUR_MARGIN}`);
        highlightMarginSVG.polygon = polygonMarginUnionPoly;
        const svgPath = polygonMarginUnionPoly.scale(inverseZoom, inverseZoom).svg({
            fillRule: "evenodd",
            fill: `rgb(${highlight.color.red}, ${highlight.color.green}, ${highlight.color.blue})`,
            stroke: "transparent",
            strokeWidth: 0,
            fillOpacity: 1,
            className: undefined,
        });
        if (exports.ENABLE_PAGEBREAK_MARGIN_TEXT_EXPERIMENT) {
            let svg = svgPath;
            highlight.marginText = "Long test 1.";
            if (highlight.marginText) {
                const m = svg.match(/d="\s*M([0-9]+\.?[0-9]*),([0-9]+\.?[0-9]*)/);
                if (m && m[1] && m[2]) {
                    const r2SvgHighlightsTextFilterID = `r2SvgFilterR${highlight.color.red}G${highlight.color.green}B${highlight.color.blue}`;
                    const el = highlightParent.querySelector(`#${r2SvgHighlightsTextFilterID}`);
                    const filter = el ? "" : `<defs><filter x="0" y="0" width="1" height="1" id="${r2SvgHighlightsTextFilterID}"><feFlood flood-color="rgb(${highlight.color.red}, ${highlight.color.green}, ${highlight.color.blue})" result="bg" /><feMerge><feMergeNode in="bg"/><feMergeNode in="SourceGraphic"/></feMerge></filter></defs>`;
                    svg = `${filter}${svgPath}<text x="${m[1]}" y="${m[2]}" class="${styles_1.CLASS_HIGHLIGHT_CONTOUR_MARGIN}_" font-size="stroke:red; fill: magenta;" filter="url(#${r2SvgHighlightsTextFilterID})">${highlight.marginText}</text>`;
                }
            }
            highlightMarginSVG.innerHTML = svg;
        }
        else {
            highlightMarginSVG.innerHTML = svgPath;
        }
        highlightParent.append(highlightMarginSVG);
    }
    return highlightParent;
}
//# sourceMappingURL=highlight.js.map