From 95341ed81fa0410c7b035089534c6fdf6f6dbab4 Mon Sep 17 00:00:00 2001 From: Albrecht Degering Date: Wed, 22 Jul 2026 19:54:51 +0200 Subject: [PATCH] fix: aggregate large ink groups safely --- src/onenote/one/content.ts | 28 +++++++++++++++++----------- tests/content-model.test.ts | 21 ++++++++++++++++++++- 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/src/onenote/one/content.ts b/src/onenote/one/content.ts index bb33091..5705528 100644 --- a/src/onenote/one/content.ts +++ b/src/onenote/one/content.ts @@ -623,7 +623,7 @@ function parseInk( id: exGuidKey(object.id), strokes: [], children, - boundingBox: unionBoundingBoxes( + boundingBox: unionInkBoundingBoxes( children.map((child) => child.boundingBox) ), layout: parseLayout(object), @@ -915,18 +915,24 @@ function boundingBoxFromStrokes( : undefined; } -function unionBoundingBoxes( +/** @internal Exported so the bounded aggregation path can be stress-tested. */ +export function unionInkBoundingBoxes( boxes: readonly (OneNoteInkBoundingBox | undefined)[] ): OneNoteInkBoundingBox | undefined { - const present = boxes.filter( - (box): box is OneNoteInkBoundingBox => box !== undefined - ); - if (present.length === 0) return undefined; - const x = Math.min(...present.map((box) => box.x)); - const y = Math.min(...present.map((box) => box.y)); - const x2 = Math.max(...present.map((box) => box.x + box.width)); - const y2 = Math.max(...present.map((box) => box.y + box.height)); - return { x, y, width: x2 - x, height: y2 - y }; + let x = Infinity; + let y = Infinity; + let x2 = -Infinity; + let y2 = -Infinity; + for (const box of boxes) { + if (!box) continue; + x = Math.min(x, box.x); + y = Math.min(y, box.y); + x2 = Math.max(x2, box.x + box.width); + y2 = Math.max(y2, box.y + box.height); + } + return Number.isFinite(x) + ? { x, y, width: x2 - x, height: y2 - y } + : undefined; } function parseLayout(object: StoreObject): OneNoteLayout { diff --git a/tests/content-model.test.ts b/tests/content-model.test.ts index b5eaf82..b693738 100644 --- a/tests/content-model.test.ts +++ b/tests/content-model.test.ts @@ -15,7 +15,10 @@ import { type OneNoteTextBlock, } from '../src/onenote/index.js'; import type { ParserDiagnostic } from '../src/onenote/model/diagnostics.js'; -import { parseContentObjects } from '../src/onenote/one/content.js'; +import { + parseContentObjects, + unionInkBoundingBoxes, +} from '../src/onenote/one/content.js'; import { JCID, PROPERTY } from '../src/onenote/one/constants.js'; import type { ExGuid, @@ -209,6 +212,22 @@ describe('OneNote rich-content model', () => { expect(parsedPoints).toBeLessThanOrEqual(100); }); + it('combines a large number of child ink bounds without argument spreading', () => { + const boxes = Array.from({ length: 200_000 }, (_, index) => ({ + x: index, + y: -index, + width: 2, + height: 3, + })); + + expect(unionInkBoundingBoxes([undefined, ...boxes])).toEqual({ + x: 0, + y: -199_999, + width: 200_001, + height: 200_002, + }); + }); + it('bounds retained rich text across content blocks', () => { const guid = '{33333333-3333-3333-3333-333333333333}'; const firstId = id(guid, 1);