inkRect.mjs

import { BOX_PAD, BOX_STROKE } from './composeBox.mjs'


/**
 * 文字墨跡之紅框留白:紅框幾何為目標 ±6 且 5px 描邊內縮,目標矩形緊貼文字墨跡時框內僅剩約 1px(紅框壓字)。
 * 墨跡外擴 INK_PAD(4px)後框內留白約 5px,與自帶內距之按鈕相當(2026-09-28)。
 * itemsUnionBox 之 fit 以同一常數外擴 DOM 文字墨跡;本函數供「無 DOM 之緊貼墨跡矩形」(canvas 圖例列、翻頁箭頭、圖例項等,
 * 由圖表程式庫回報之視窗座標矩形)使用,交給 captureStableWithBox 作為矩形目標。
 * neighbors(2026-09-28):同一列之其他項目矩形(例:其他圖例項);外擴後框線不越過與鄰項間隙之正中,不壓到鄰項(業主:亂框亂壓遮蔽有效資訊即缺陷)。
 * 矩形須為「實際墨跡」:圖表程式庫回報之文字/項目矩形常比畫出之字形寬數 px,以之取中點框線會偏向鄰項——先經 canvasInkRects 收斂;
 * 鄰項須含畫面上有墨跡之全部項目(被捲動窗裁掉一部分者亦算,canvasInkRects 只回其可見部分),不可只取完整顯示者
 * (w-web-perm 統計圖例項殷鑑:兩者皆未做時,框線距自身文字 7px、貼下一項圖示 0px)。
 *
 * @param {Object} rect 輸入視窗座標矩形物件{x,y,width,height}
 * @param {Number|Object} [opt=INK_PAD] 輸入外擴像素數字,或設定物件{pad,neighbors}
 * @param {Number} [opt.pad=INK_PAD] 輸入外擴像素,預設INK_PAD(4)
 * @param {Array} [opt.neighbors=[]] 輸入鄰項矩形陣列(同為{x,y,width,height}),預設[]
 * @returns {Object} 回傳外擴後之矩形物件{x,y,width,height}
 * @example
 *
 * import inkRect from 'w-package-tools-e2e/src/inkRect.mjs'
 *
 * let buf = await captureStableWithBox(page, inkRect(legend.rect))
 * let buf2 = await captureStableWithBox(page, inkRect(item.rect, { neighbors: others.map((o) => o.rect) }))
 *
 */
export const INK_PAD = 4


function inkRect(rect, opt = INK_PAD) {
    let isRect = (r) => !!r && ['x', 'y', 'width', 'height'].every((k) => typeof r[k] === 'number')
    if (!isRect(rect)) {
        throw new Error('inkRect: rect 須為 {x,y,width,height} 之數值矩形')
    }
    let { pad = INK_PAD, neighbors = [] } = (typeof opt === 'number') ? { pad: opt } : (opt || {})
    if (typeof pad !== 'number' || !(pad >= 0)) {
        throw new Error('inkRect: pad 須為非負數')
    }
    let left = rect.x - pad
    let top = rect.y - pad
    let right = rect.x + rect.width + pad
    let bottom = rect.y + rect.height + pad
    //框線中心在目標邊外 edge 處;與鄰項間隙之正中為界
    let edge = BOX_PAD - BOX_STROKE / 2
    let r0 = { l: rect.x, t: rect.y, r: rect.x + rect.width, b: rect.y + rect.height }
    for (let n of neighbors) {
        if (!isRect(n)) {
            continue
        }
        let nb = { l: n.x, t: n.y, r: n.x + n.width, b: n.y + n.height }
        let vOverlap = nb.b > r0.t && nb.t < r0.b
        let hOverlap = nb.r > r0.l && nb.l < r0.r
        if (vOverlap && nb.l >= r0.r - 1) {
            right = Math.min(right, (r0.r + nb.l) / 2 - edge)
        }
        else if (vOverlap && nb.r <= r0.l + 1) {
            left = Math.max(left, (nb.r + r0.l) / 2 + edge)
        }
        else if (hOverlap && nb.t >= r0.b - 1) {
            bottom = Math.min(bottom, (r0.b + nb.t) / 2 - edge)
        }
        else if (hOverlap && nb.b <= r0.t + 1) {
            top = Math.max(top, (nb.b + r0.t) / 2 + edge)
        }
    }
    if (right - left < 1 || bottom - top < 1) {
        return { x: rect.x - pad, y: rect.y - pad, width: rect.width + pad * 2, height: rect.height + pad * 2 }
    }
    return { x: left, y: top, width: right - left, height: bottom - top }
}


export default inkRect