Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
3 changes: 3 additions & 0 deletions src/ContinuousEnginePool.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export const ContinuousEnginePool = memo(forwardRef<
onPencilDoubleTap,
registerPerPageSlot,
shouldCaptureBeforeReassign,
capturePreviewOnReassign = true,
onSlotCaptureBeforeUnmount,
}, ref) {
const slotRefs = useRef<Array<PooledCanvasSlotHandle | null>>([]);
Expand Down Expand Up @@ -294,6 +295,7 @@ export const ContinuousEnginePool = memo(forwardRef<
onPencilDoubleTap={onPencilDoubleTap}
registerRef={registerPerPageSlot}
shouldCaptureBeforeReassign={shouldCaptureBeforeReassign}
capturePreviewOnReassign={capturePreviewOnReassign}
onCaptureBeforeReassign={onSlotCaptureBeforeUnmount}
/>
))}
Expand All @@ -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
));
22 changes: 22 additions & 0 deletions src/__tests__/ContinuousEnginePool.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
14 changes: 10 additions & 4 deletions src/continuous-engine-pool/PooledCanvasSlot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export const PooledCanvasSlot = memo(forwardRef<PooledCanvasSlotHandle, PooledCa
onPencilDoubleTap,
registerRef,
shouldCaptureBeforeReassign,
capturePreviewOnReassign,
onCaptureBeforeReassign,
}, ref) {
const slotViewRef = useRef<View | null>(null);
Expand All @@ -91,13 +92,15 @@ export const PooledCanvasSlot = memo(forwardRef<PooledCanvasSlotHandle, PooledCa
const forwardedReadyRef = useRef(false);
const benchmarkRecordingActiveRef = useRef(false);
const shouldCaptureBeforeReassignRef = useRef(shouldCaptureBeforeReassign);
const capturePreviewOnReassignRef = useRef(capturePreviewOnReassign);
const captureCallbackRef = useRef(onCaptureBeforeReassign);
const onCanvasReadyRef = useRef(onCanvasReady);
const onSlotLoadedRef = useRef(onSlotLoaded);
const onSelectionChangeRef = useRef(onSelectionChange);
const getToolStateRef = useRef(getToolState);

shouldCaptureBeforeReassignRef.current = shouldCaptureBeforeReassign;
capturePreviewOnReassignRef.current = capturePreviewOnReassign;
captureCallbackRef.current = onCaptureBeforeReassign;
onCanvasReadyRef.current = onCanvasReady;
onSlotLoadedRef.current = onSlotLoaded;
Expand Down Expand Up @@ -289,10 +292,12 @@ export const PooledCanvasSlot = memo(forwardRef<PooledCanvasSlotHandle, PooledCa
const data = await sourceRef.getBase64Data();
if (data) {
let previewUri: string | null = null;
try {
previewUri = await sourceRef.getBase64PngData(PAGE_PREVIEW_CAPTURE_SCALE);
} catch {
previewUri = null;
if (capturePreviewOnReassignRef.current) {
try {
previewUri = await sourceRef.getBase64PngData(PAGE_PREVIEW_CAPTURE_SCALE);
} catch {
previewUri = null;
}
}
captureCallbackRef.current(pageId, data, previewUri || undefined);
}
Expand Down Expand Up @@ -683,6 +688,7 @@ export const PooledCanvasSlot = memo(forwardRef<PooledCanvasSlotHandle, PooledCa
prev.onPencilDoubleTap === next.onPencilDoubleTap &&
prev.registerRef === next.registerRef &&
prev.shouldCaptureBeforeReassign === next.shouldCaptureBeforeReassign &&
prev.capturePreviewOnReassign === next.capturePreviewOnReassign &&
prev.onCaptureBeforeReassign === next.onCaptureBeforeReassign
));

Expand Down
8 changes: 8 additions & 0 deletions src/continuous-engine-pool/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,13 @@ export type ContinuousEnginePoolProps = {
sourceRef?: ContinuousEnginePoolSlotRef,
) => 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,
Expand Down Expand Up @@ -145,6 +152,7 @@ export type PooledCanvasSlotProps = {
sourceRef?: ContinuousEnginePoolSlotRef,
) => void;
shouldCaptureBeforeReassign: (pageId: string) => boolean;
capturePreviewOnReassign: boolean;
onCaptureBeforeReassign: (
pageId: string,
data: string,
Expand Down
Loading