diff --git a/CHANGELOG.md b/CHANGELOG.md index 1412925..18938ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to `@mathnotes/mobile-ink` will be documented here. +## [0.3.7] - 2026-10-02 + +- Added `capturePreviewOnReassign` to `ContinuousEnginePool` (default `true`). Hosts that don't use page previews can set it to `false` to skip the native render and PNG encode that runs for every captured page when the pool shifts. Page data is still captured. + ## [0.3.6] - 2026-10-02 - iOS: selection hit-testing, the selection toolbar and `onInkSelectionChange` now share one bounds helper that rejects non-finite geometry; the event sends `null` bounds instead of NaN. The selection toolbar ignores selection bounds that are not finite. Inverted or overflowing bounds previously produced a NaN frame and terminated the app with `CALayerInvalidGeometry` (seen in production after a pooled page was cleared with a live selection, which 0.3.5 also fixes at the source). diff --git a/package-lock.json b/package-lock.json index 2c80b20..5ebfa15 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@mathnotes/mobile-ink", - "version": "0.3.6", + "version": "0.3.7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@mathnotes/mobile-ink", - "version": "0.3.6", + "version": "0.3.7", "license": "Apache-2.0", "devDependencies": { "@babel/core": "^7.25.2", diff --git a/package.json b/package.json index f9ec2e3..a8cfc04 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@mathnotes/mobile-ink", - "version": "0.3.6", + "version": "0.3.7", "description": "Production-grade React Native ink engine with native Skia drawing and continuous canvas primitives.", "license": "Apache-2.0", "author": "BuilderPro LLC", diff --git a/src/ContinuousEnginePool.tsx b/src/ContinuousEnginePool.tsx index a3b5c67..52795ac 100644 --- a/src/ContinuousEnginePool.tsx +++ b/src/ContinuousEnginePool.tsx @@ -54,6 +54,7 @@ export const ContinuousEnginePool = memo(forwardRef< onPencilDoubleTap, registerPerPageSlot, shouldCaptureBeforeReassign, + capturePreviewOnReassign = true, onSlotCaptureBeforeUnmount, }, ref) { const slotRefs = useRef>([]); @@ -294,6 +295,7 @@ export const ContinuousEnginePool = memo(forwardRef< onPencilDoubleTap={onPencilDoubleTap} registerRef={registerPerPageSlot} shouldCaptureBeforeReassign={shouldCaptureBeforeReassign} + capturePreviewOnReassign={capturePreviewOnReassign} onCaptureBeforeReassign={onSlotCaptureBeforeUnmount} /> ))} @@ -318,5 +320,6 @@ export const ContinuousEnginePool = memo(forwardRef< prev.onPencilDoubleTap === next.onPencilDoubleTap && prev.registerPerPageSlot === next.registerPerPageSlot && prev.shouldCaptureBeforeReassign === next.shouldCaptureBeforeReassign && + prev.capturePreviewOnReassign === next.capturePreviewOnReassign && prev.onSlotCaptureBeforeUnmount === next.onSlotCaptureBeforeUnmount )); diff --git a/src/__tests__/ContinuousEnginePool.test.tsx b/src/__tests__/ContinuousEnginePool.test.tsx index 5db70d6..221276d 100644 --- a/src/__tests__/ContinuousEnginePool.test.tsx +++ b/src/__tests__/ContinuousEnginePool.test.tsx @@ -190,6 +190,28 @@ describe("ContinuousEnginePool", () => { expect(mockReleaseEngine).toHaveBeenCalledTimes(3); }); + it("captures page data without rendering a preview when previews are off", async () => { + const pages = [page(0), page(1), page(2), page(3)]; + const onSlotCaptureBeforeUnmount = jest.fn(); + const { poolRef } = renderPool({ + shouldCaptureBeforeReassign: jest.fn((pageId: string) => pageId === "page-0"), + capturePreviewOnReassign: false, + onSlotCaptureBeforeUnmount, + }); + await act(async () => {}); + mockGetBase64PngData.mockClear(); + + await assignPages(poolRef, buildAssignments(pages.slice(0, 3), 0)); + await assignPages(poolRef, buildAssignments(pages.slice(1, 4), 1)); + + expect(onSlotCaptureBeforeUnmount).toHaveBeenCalledWith( + "page-0", + '{"pages":{"0":"persisted"}}', + undefined, + ); + expect(mockGetBase64PngData).not.toHaveBeenCalled(); + }); + it("defaults pooled native canvases to the Ganesh backend", async () => { renderPool(); diff --git a/src/continuous-engine-pool/PooledCanvasSlot.tsx b/src/continuous-engine-pool/PooledCanvasSlot.tsx index 45f9a8c..d4054da 100644 --- a/src/continuous-engine-pool/PooledCanvasSlot.tsx +++ b/src/continuous-engine-pool/PooledCanvasSlot.tsx @@ -73,6 +73,7 @@ export const PooledCanvasSlot = memo(forwardRef(null); @@ -91,6 +92,7 @@ export const PooledCanvasSlot = memo(forwardRef void; shouldCaptureBeforeReassign: (pageId: string) => boolean; + /** + * Also render a PNG preview of a page captured before its slot is + * reassigned (passed as `previewUri` to onSlotCaptureBeforeUnmount). + * Defaults to true. Set false when previews are not used, to skip a + * native render and PNG encode on every page shift. + */ + capturePreviewOnReassign?: boolean; onSlotCaptureBeforeUnmount: ( pageId: string, data: string, @@ -145,6 +152,7 @@ export type PooledCanvasSlotProps = { sourceRef?: ContinuousEnginePoolSlotRef, ) => void; shouldCaptureBeforeReassign: (pageId: string) => boolean; + capturePreviewOnReassign: boolean; onCaptureBeforeReassign: ( pageId: string, data: string,