canvu-react 0.3.12 → 0.3.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -1
- package/dist/index.cjs +135 -63
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +135 -63
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +194 -115
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +9 -1
- package/dist/react.d.ts +9 -1
- package/dist/react.js +194 -115
- package/dist/react.js.map +1 -1
- package/dist/realtime.cjs +30 -5
- package/dist/realtime.cjs.map +1 -1
- package/dist/realtime.js +30 -5
- package/dist/realtime.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -255,6 +255,11 @@ export function BoardImport() {
|
|
|
255
255
|
files: Array.from(files),
|
|
256
256
|
worldCenter: { x: 0, y: 0 },
|
|
257
257
|
assetStore,
|
|
258
|
+
pdfScale: 1.15,
|
|
259
|
+
pdfPageConcurrency: 2,
|
|
260
|
+
onItemsReady(nextItems) {
|
|
261
|
+
doc.onItemsChange([...doc.items, ...nextItems]);
|
|
262
|
+
},
|
|
258
263
|
decorateItem(item) {
|
|
259
264
|
return {
|
|
260
265
|
...item,
|
|
@@ -265,7 +270,6 @@ export function BoardImport() {
|
|
|
265
270
|
|
|
266
271
|
doc.onItemsChange([...doc.items, ...result.items]);
|
|
267
272
|
}
|
|
268
|
-
|
|
269
273
|
if (!doc.isHydrated) return null;
|
|
270
274
|
|
|
271
275
|
return (
|
|
@@ -300,6 +304,10 @@ export function BoardImport() {
|
|
|
300
304
|
}
|
|
301
305
|
```
|
|
302
306
|
|
|
307
|
+
canvu can now stream PDF pages into the canvas progressively through
|
|
308
|
+
`onItemsReady(...)`, skip PDF thumbnails during ingest, and use a lower initial
|
|
309
|
+
raster scale by default to improve time-to-first-render.
|
|
310
|
+
|
|
303
311
|
This helper is the same ingestion layer used internally by the native file
|
|
304
312
|
tool, so external imports do not need to reimplement PDF rasterization, local
|
|
305
313
|
blob persistence, or `pluginData` attachment.
|
package/dist/index.cjs
CHANGED
|
@@ -243,10 +243,32 @@ async function runWithConcurrency(items, concurrency, execute) {
|
|
|
243
243
|
async function loadPdfToStore(file, store, options) {
|
|
244
244
|
const scale = options?.scale ?? 1.5;
|
|
245
245
|
const pageConcurrency = options?.pageConcurrency ?? 2;
|
|
246
|
+
const storeThumbnails = options?.storeThumbnails ?? false;
|
|
246
247
|
const pdfjs = await getPdfJs();
|
|
247
248
|
const arrayBuffer = await file.arrayBuffer();
|
|
248
249
|
const pdf = await pdfjs.getDocument({ data: arrayBuffer }).promise;
|
|
249
250
|
const pageNumbers = normalizePdfPageNumbers(options?.pageNumbers, pdf.numPages);
|
|
251
|
+
const bufferedResults = /* @__PURE__ */ new Map();
|
|
252
|
+
let nextEmitIndex = 0;
|
|
253
|
+
let emitChain = Promise.resolve();
|
|
254
|
+
const queuePageEmission = async (pageResult) => {
|
|
255
|
+
bufferedResults.set(pageResult.pageNumber, pageResult);
|
|
256
|
+
const run = async () => {
|
|
257
|
+
while (nextEmitIndex < pageNumbers.length) {
|
|
258
|
+
const nextPageNumber = pageNumbers[nextEmitIndex];
|
|
259
|
+
if (nextPageNumber == null) break;
|
|
260
|
+
const bufferedResult = bufferedResults.get(nextPageNumber);
|
|
261
|
+
if (!bufferedResult) break;
|
|
262
|
+
bufferedResults.delete(nextPageNumber);
|
|
263
|
+
nextEmitIndex += 1;
|
|
264
|
+
await options?.onPageStored?.(bufferedResult);
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
const nextChain = emitChain.then(run, run);
|
|
268
|
+
emitChain = nextChain.catch(() => {
|
|
269
|
+
});
|
|
270
|
+
await nextChain;
|
|
271
|
+
};
|
|
250
272
|
return await runWithConcurrency(
|
|
251
273
|
pageNumbers,
|
|
252
274
|
pageConcurrency,
|
|
@@ -256,27 +278,31 @@ async function loadPdfToStore(file, store, options) {
|
|
|
256
278
|
const mime = "image/png";
|
|
257
279
|
const pageBlob = await canvasToBlob(canvas, mime);
|
|
258
280
|
const blobId = await store.storeOriginal(pageBlob);
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
tCtx
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
281
|
+
const thumbnailBlobId = storeThumbnails ? await (async () => {
|
|
282
|
+
const thumbScale = Math.min(1, 256 / Math.max(width, height));
|
|
283
|
+
const tw = Math.max(1, Math.round(width * thumbScale));
|
|
284
|
+
const th = Math.max(1, Math.round(height * thumbScale));
|
|
285
|
+
const thumbCanvas = document.createElement("canvas");
|
|
286
|
+
thumbCanvas.width = tw;
|
|
287
|
+
thumbCanvas.height = th;
|
|
288
|
+
const tCtx = thumbCanvas.getContext("2d");
|
|
289
|
+
if (tCtx) {
|
|
290
|
+
tCtx.imageSmoothingEnabled = true;
|
|
291
|
+
tCtx.imageSmoothingQuality = "high";
|
|
292
|
+
tCtx.drawImage(canvas, 0, 0, tw, th);
|
|
293
|
+
}
|
|
294
|
+
const thumbBlob = await canvasToBlob(thumbCanvas, mime);
|
|
295
|
+
return await store.storeThumbnail(thumbBlob);
|
|
296
|
+
})() : "";
|
|
297
|
+
const pageResult = {
|
|
274
298
|
blobId,
|
|
275
299
|
thumbnailBlobId,
|
|
276
300
|
width,
|
|
277
301
|
height,
|
|
278
302
|
pageNumber
|
|
279
303
|
};
|
|
304
|
+
await queuePageEmission(pageResult);
|
|
305
|
+
return pageResult;
|
|
280
306
|
}
|
|
281
307
|
);
|
|
282
308
|
}
|
|
@@ -1713,7 +1739,8 @@ function collectEraserTargetsAtWorldPoint(items, worldX, worldY, options) {
|
|
|
1713
1739
|
var HYDRATION_CACHE_NAME = "canvu-asset-hydration-v1";
|
|
1714
1740
|
var DEFAULT_PDF_SCALE = 1.15;
|
|
1715
1741
|
var DEFAULT_PDF_PAGE_CONCURRENCY = 2;
|
|
1716
|
-
var
|
|
1742
|
+
var hydrationSourcePromises = /* @__PURE__ */ new Map();
|
|
1743
|
+
var pdfSourceBlobPromises = /* @__PURE__ */ new Map();
|
|
1717
1744
|
function buildImageHydrationKey(assetId) {
|
|
1718
1745
|
return `image:${assetId}`;
|
|
1719
1746
|
}
|
|
@@ -1761,23 +1788,38 @@ async function fetchBlob(url) {
|
|
|
1761
1788
|
return null;
|
|
1762
1789
|
}
|
|
1763
1790
|
}
|
|
1764
|
-
async function
|
|
1791
|
+
async function getHydrationSource(key, preferCachedRasters, loader) {
|
|
1765
1792
|
const cached = preferCachedRasters ? await readCachedHydrationBlob(key) : null;
|
|
1766
|
-
if (cached)
|
|
1767
|
-
|
|
1793
|
+
if (cached) {
|
|
1794
|
+
return {
|
|
1795
|
+
blob: cached
|
|
1796
|
+
};
|
|
1797
|
+
}
|
|
1798
|
+
const inFlight = hydrationSourcePromises.get(key);
|
|
1768
1799
|
if (inFlight) return await inFlight;
|
|
1769
1800
|
const nextPromise = (async () => {
|
|
1770
|
-
const
|
|
1771
|
-
if (blob) {
|
|
1772
|
-
await writeCachedHydrationBlob(key, blob);
|
|
1801
|
+
const source = await loader();
|
|
1802
|
+
if (source?.blob) {
|
|
1803
|
+
await writeCachedHydrationBlob(key, source.blob);
|
|
1773
1804
|
}
|
|
1774
|
-
return
|
|
1805
|
+
return source;
|
|
1775
1806
|
})();
|
|
1776
|
-
|
|
1807
|
+
hydrationSourcePromises.set(key, nextPromise);
|
|
1808
|
+
try {
|
|
1809
|
+
return await nextPromise;
|
|
1810
|
+
} finally {
|
|
1811
|
+
hydrationSourcePromises.delete(key);
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
async function getPdfSourceBlob(url) {
|
|
1815
|
+
const inFlight = pdfSourceBlobPromises.get(url);
|
|
1816
|
+
if (inFlight) return await inFlight;
|
|
1817
|
+
const nextPromise = fetchBlob(url);
|
|
1818
|
+
pdfSourceBlobPromises.set(url, nextPromise);
|
|
1777
1819
|
try {
|
|
1778
1820
|
return await nextPromise;
|
|
1779
1821
|
} finally {
|
|
1780
|
-
|
|
1822
|
+
pdfSourceBlobPromises.delete(url);
|
|
1781
1823
|
}
|
|
1782
1824
|
}
|
|
1783
1825
|
function registerObjectUrl(objectUrls, blob) {
|
|
@@ -1831,18 +1873,22 @@ async function hydrateImageAssets(requests, resolvedAssetUrls, objectUrls, prefe
|
|
|
1831
1873
|
if (!resolvedAsset?.url) {
|
|
1832
1874
|
return [assetId, null];
|
|
1833
1875
|
}
|
|
1834
|
-
const
|
|
1876
|
+
const source = await getHydrationSource(
|
|
1835
1877
|
buildImageHydrationKey(assetId),
|
|
1836
1878
|
preferCachedRasters,
|
|
1837
|
-
async () =>
|
|
1879
|
+
async () => {
|
|
1880
|
+
const blob = await fetchBlob(resolvedAsset.url);
|
|
1881
|
+
if (!blob) return null;
|
|
1882
|
+
return { blob };
|
|
1883
|
+
}
|
|
1838
1884
|
);
|
|
1839
|
-
if (!blob) {
|
|
1885
|
+
if (!source?.blob) {
|
|
1840
1886
|
return [assetId, null];
|
|
1841
1887
|
}
|
|
1842
1888
|
return [
|
|
1843
1889
|
assetId,
|
|
1844
1890
|
{
|
|
1845
|
-
href: registerObjectUrl(objectUrls, blob)
|
|
1891
|
+
href: registerObjectUrl(objectUrls, source.blob)
|
|
1846
1892
|
}
|
|
1847
1893
|
];
|
|
1848
1894
|
})
|
|
@@ -1857,45 +1903,69 @@ async function hydratePdfAssets(requests, resolvedAssetUrls, objectUrls, options
|
|
|
1857
1903
|
if (!resolvedAsset?.url) {
|
|
1858
1904
|
continue;
|
|
1859
1905
|
}
|
|
1860
|
-
const
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1906
|
+
const pageKeys = group.pageNumbers.map((pageNumber) => ({
|
|
1907
|
+
pageNumber,
|
|
1908
|
+
cacheKey: buildPdfHydrationKey(group.assetId, pageNumber, group.scale)
|
|
1909
|
+
}));
|
|
1910
|
+
const pagesToRender = [];
|
|
1911
|
+
for (const { pageNumber, cacheKey } of pageKeys) {
|
|
1912
|
+
const cachedBlob = options.preferCachedRasters ? await readCachedHydrationBlob(cacheKey) : null;
|
|
1913
|
+
if (cachedBlob) {
|
|
1914
|
+
hydratedPages.set(cacheKey, {
|
|
1915
|
+
href: registerObjectUrl(objectUrls, cachedBlob)
|
|
1916
|
+
});
|
|
1866
1917
|
continue;
|
|
1867
1918
|
}
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
}
|
|
1871
|
-
}
|
|
1872
|
-
if (missingPages.length === 0) {
|
|
1873
|
-
continue;
|
|
1919
|
+
if (!hydrationSourcePromises.has(cacheKey)) {
|
|
1920
|
+
pagesToRender.push({ pageNumber, cacheKey });
|
|
1921
|
+
}
|
|
1874
1922
|
}
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1923
|
+
if (pagesToRender.length > 0) {
|
|
1924
|
+
const renderPromise = (async () => {
|
|
1925
|
+
const pdfBlob = await getPdfSourceBlob(resolvedAsset.url);
|
|
1926
|
+
if (!pdfBlob) {
|
|
1927
|
+
return /* @__PURE__ */ new Map();
|
|
1928
|
+
}
|
|
1929
|
+
const renderedPages = await loadPdfToStore(pdfBlob, options.imageStore, {
|
|
1930
|
+
scale: group.scale,
|
|
1931
|
+
pageNumbers: pagesToRender.map(({ pageNumber }) => pageNumber),
|
|
1932
|
+
pageConcurrency: options.pdfPageConcurrency
|
|
1933
|
+
});
|
|
1934
|
+
const renderedByPage = /* @__PURE__ */ new Map();
|
|
1935
|
+
for (const renderedPage of renderedPages) {
|
|
1936
|
+
const pageBlob = await options.imageStore.getOriginal(renderedPage.blobId);
|
|
1937
|
+
renderedByPage.set(
|
|
1938
|
+
renderedPage.pageNumber,
|
|
1939
|
+
pageBlob ? {
|
|
1940
|
+
blob: pageBlob,
|
|
1941
|
+
width: renderedPage.width,
|
|
1942
|
+
height: renderedPage.height
|
|
1943
|
+
} : null
|
|
1944
|
+
);
|
|
1945
|
+
}
|
|
1946
|
+
return renderedByPage;
|
|
1947
|
+
})();
|
|
1948
|
+
for (const { pageNumber, cacheKey } of pagesToRender) {
|
|
1949
|
+
const pagePromise = getHydrationSource(
|
|
1950
|
+
cacheKey,
|
|
1951
|
+
options.preferCachedRasters,
|
|
1952
|
+
async () => (await renderPromise).get(pageNumber) ?? null
|
|
1953
|
+
);
|
|
1954
|
+
hydrationSourcePromises.set(cacheKey, pagePromise);
|
|
1955
|
+
}
|
|
1878
1956
|
}
|
|
1879
|
-
const
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
const cacheKey = buildPdfHydrationKey(
|
|
1886
|
-
group.assetId,
|
|
1887
|
-
renderedPage.pageNumber,
|
|
1888
|
-
group.scale
|
|
1957
|
+
for (const { cacheKey } of pageKeys) {
|
|
1958
|
+
if (hydratedPages.has(cacheKey)) continue;
|
|
1959
|
+
const source = await getHydrationSource(
|
|
1960
|
+
cacheKey,
|
|
1961
|
+
options.preferCachedRasters,
|
|
1962
|
+
async () => null
|
|
1889
1963
|
);
|
|
1890
|
-
|
|
1891
|
-
if (!pageBlob) {
|
|
1892
|
-
continue;
|
|
1893
|
-
}
|
|
1894
|
-
await writeCachedHydrationBlob(cacheKey, pageBlob);
|
|
1964
|
+
if (!source?.blob) continue;
|
|
1895
1965
|
hydratedPages.set(cacheKey, {
|
|
1896
|
-
href: registerObjectUrl(objectUrls,
|
|
1897
|
-
width:
|
|
1898
|
-
height:
|
|
1966
|
+
href: registerObjectUrl(objectUrls, source.blob),
|
|
1967
|
+
width: source.width,
|
|
1968
|
+
height: source.height
|
|
1899
1969
|
});
|
|
1900
1970
|
}
|
|
1901
1971
|
}
|
|
@@ -1936,7 +2006,9 @@ async function hydrateSceneItemsWithAssets(items, assetStore, options = {}) {
|
|
|
1936
2006
|
objectUrls,
|
|
1937
2007
|
{
|
|
1938
2008
|
imageStore,
|
|
1939
|
-
pdfPageConcurrency
|
|
2009
|
+
pdfPageConcurrency,
|
|
2010
|
+
preferCachedRasters
|
|
2011
|
+
}
|
|
1940
2012
|
);
|
|
1941
2013
|
return {
|
|
1942
2014
|
items: items.map((item) => {
|