@fieldnotes/core 0.7.1 → 0.8.1
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/dist/index.cjs +263 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -2
- package/dist/index.d.ts +11 -2
- package/dist/index.js +262 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -60,6 +60,7 @@ __export(index_exports, {
|
|
|
60
60
|
createShape: () => createShape,
|
|
61
61
|
createStroke: () => createStroke,
|
|
62
62
|
createText: () => createText,
|
|
63
|
+
exportImage: () => exportImage,
|
|
63
64
|
exportState: () => exportState,
|
|
64
65
|
findBindTarget: () => findBindTarget,
|
|
65
66
|
findBoundArrows: () => findBoundArrows,
|
|
@@ -1828,6 +1829,257 @@ function createText(input) {
|
|
|
1828
1829
|
};
|
|
1829
1830
|
}
|
|
1830
1831
|
|
|
1832
|
+
// src/canvas/export-image.ts
|
|
1833
|
+
function getStrokeBounds(el) {
|
|
1834
|
+
if (el.type !== "stroke") return null;
|
|
1835
|
+
if (el.points.length === 0) return null;
|
|
1836
|
+
let minX = Infinity;
|
|
1837
|
+
let minY = Infinity;
|
|
1838
|
+
let maxX = -Infinity;
|
|
1839
|
+
let maxY = -Infinity;
|
|
1840
|
+
for (const p of el.points) {
|
|
1841
|
+
const px = el.position.x + p.x;
|
|
1842
|
+
const py = el.position.y + p.y;
|
|
1843
|
+
minX = Math.min(minX, px);
|
|
1844
|
+
minY = Math.min(minY, py);
|
|
1845
|
+
maxX = Math.max(maxX, px);
|
|
1846
|
+
maxY = Math.max(maxY, py);
|
|
1847
|
+
}
|
|
1848
|
+
const pad = el.width / 2;
|
|
1849
|
+
return {
|
|
1850
|
+
x: minX - pad,
|
|
1851
|
+
y: minY - pad,
|
|
1852
|
+
w: maxX - minX + el.width,
|
|
1853
|
+
h: maxY - minY + el.width
|
|
1854
|
+
};
|
|
1855
|
+
}
|
|
1856
|
+
function getElementRect(el) {
|
|
1857
|
+
switch (el.type) {
|
|
1858
|
+
case "stroke":
|
|
1859
|
+
return getStrokeBounds(el);
|
|
1860
|
+
case "arrow": {
|
|
1861
|
+
const b = getArrowBounds(el.from, el.to, el.bend);
|
|
1862
|
+
const pad = el.width / 2 + 14;
|
|
1863
|
+
return { x: b.x - pad, y: b.y - pad, w: b.w + pad * 2, h: b.h + pad * 2 };
|
|
1864
|
+
}
|
|
1865
|
+
case "grid":
|
|
1866
|
+
return null;
|
|
1867
|
+
case "note":
|
|
1868
|
+
case "image":
|
|
1869
|
+
case "html":
|
|
1870
|
+
case "text":
|
|
1871
|
+
case "shape":
|
|
1872
|
+
if ("size" in el) {
|
|
1873
|
+
return { x: el.position.x, y: el.position.y, w: el.size.w, h: el.size.h };
|
|
1874
|
+
}
|
|
1875
|
+
return null;
|
|
1876
|
+
default:
|
|
1877
|
+
return null;
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
function computeBounds(elements, padding) {
|
|
1881
|
+
let minX = Infinity;
|
|
1882
|
+
let minY = Infinity;
|
|
1883
|
+
let maxX = -Infinity;
|
|
1884
|
+
let maxY = -Infinity;
|
|
1885
|
+
let found = false;
|
|
1886
|
+
for (const el of elements) {
|
|
1887
|
+
const rect = getElementRect(el);
|
|
1888
|
+
if (!rect) continue;
|
|
1889
|
+
found = true;
|
|
1890
|
+
minX = Math.min(minX, rect.x);
|
|
1891
|
+
minY = Math.min(minY, rect.y);
|
|
1892
|
+
maxX = Math.max(maxX, rect.x + rect.w);
|
|
1893
|
+
maxY = Math.max(maxY, rect.y + rect.h);
|
|
1894
|
+
}
|
|
1895
|
+
if (!found) return null;
|
|
1896
|
+
return {
|
|
1897
|
+
x: minX - padding,
|
|
1898
|
+
y: minY - padding,
|
|
1899
|
+
w: maxX - minX + padding * 2,
|
|
1900
|
+
h: maxY - minY + padding * 2
|
|
1901
|
+
};
|
|
1902
|
+
}
|
|
1903
|
+
function renderNoteOnCanvas(ctx, note) {
|
|
1904
|
+
const { x, y } = note.position;
|
|
1905
|
+
const { w, h } = note.size;
|
|
1906
|
+
const r = 4;
|
|
1907
|
+
const pad = 8;
|
|
1908
|
+
ctx.save();
|
|
1909
|
+
ctx.fillStyle = note.backgroundColor;
|
|
1910
|
+
ctx.beginPath();
|
|
1911
|
+
ctx.moveTo(x + r, y);
|
|
1912
|
+
ctx.lineTo(x + w - r, y);
|
|
1913
|
+
ctx.arcTo(x + w, y, x + w, y + r, r);
|
|
1914
|
+
ctx.lineTo(x + w, y + h - r);
|
|
1915
|
+
ctx.arcTo(x + w, y + h, x + w - r, y + h, r);
|
|
1916
|
+
ctx.lineTo(x + r, y + h);
|
|
1917
|
+
ctx.arcTo(x, y + h, x, y + h - r, r);
|
|
1918
|
+
ctx.lineTo(x, y + r);
|
|
1919
|
+
ctx.arcTo(x, y, x + r, y, r);
|
|
1920
|
+
ctx.closePath();
|
|
1921
|
+
ctx.fill();
|
|
1922
|
+
if (note.text) {
|
|
1923
|
+
ctx.fillStyle = note.textColor;
|
|
1924
|
+
ctx.font = "14px system-ui, sans-serif";
|
|
1925
|
+
ctx.textBaseline = "top";
|
|
1926
|
+
wrapText(ctx, note.text, x + pad, y + pad, w - pad * 2, 18);
|
|
1927
|
+
}
|
|
1928
|
+
ctx.restore();
|
|
1929
|
+
}
|
|
1930
|
+
function renderTextOnCanvas(ctx, text) {
|
|
1931
|
+
if (!text.text) return;
|
|
1932
|
+
ctx.save();
|
|
1933
|
+
ctx.fillStyle = text.color;
|
|
1934
|
+
ctx.font = `${text.fontSize}px system-ui, sans-serif`;
|
|
1935
|
+
ctx.textBaseline = "top";
|
|
1936
|
+
ctx.textAlign = text.textAlign;
|
|
1937
|
+
const pad = 2;
|
|
1938
|
+
let textX = text.position.x + pad;
|
|
1939
|
+
if (text.textAlign === "center") {
|
|
1940
|
+
textX = text.position.x + text.size.w / 2;
|
|
1941
|
+
} else if (text.textAlign === "right") {
|
|
1942
|
+
textX = text.position.x + text.size.w - pad;
|
|
1943
|
+
}
|
|
1944
|
+
const lineHeight = text.fontSize * 1.4;
|
|
1945
|
+
const lines = text.text.split("\n");
|
|
1946
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1947
|
+
const line = lines[i];
|
|
1948
|
+
if (line !== void 0) {
|
|
1949
|
+
ctx.fillText(line, textX, text.position.y + pad + i * lineHeight);
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
ctx.restore();
|
|
1953
|
+
}
|
|
1954
|
+
function wrapText(ctx, text, x, y, maxWidth, lineHeight) {
|
|
1955
|
+
const words = text.split(" ");
|
|
1956
|
+
let line = "";
|
|
1957
|
+
let offsetY = 0;
|
|
1958
|
+
for (const word of words) {
|
|
1959
|
+
const testLine = line ? `${line} ${word}` : word;
|
|
1960
|
+
const metrics = ctx.measureText(testLine);
|
|
1961
|
+
if (metrics.width > maxWidth && line) {
|
|
1962
|
+
ctx.fillText(line, x, y + offsetY);
|
|
1963
|
+
line = word;
|
|
1964
|
+
offsetY += lineHeight;
|
|
1965
|
+
} else {
|
|
1966
|
+
line = testLine;
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
if (line) {
|
|
1970
|
+
ctx.fillText(line, x, y + offsetY);
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
function renderGridForBounds(ctx, grid, bounds) {
|
|
1974
|
+
const visibleBounds = {
|
|
1975
|
+
minX: bounds.x,
|
|
1976
|
+
minY: bounds.y,
|
|
1977
|
+
maxX: bounds.x + bounds.w,
|
|
1978
|
+
maxY: bounds.y + bounds.h
|
|
1979
|
+
};
|
|
1980
|
+
if (grid.gridType === "hex") {
|
|
1981
|
+
renderHexGrid(
|
|
1982
|
+
ctx,
|
|
1983
|
+
visibleBounds,
|
|
1984
|
+
grid.cellSize,
|
|
1985
|
+
grid.hexOrientation,
|
|
1986
|
+
grid.strokeColor,
|
|
1987
|
+
grid.strokeWidth,
|
|
1988
|
+
grid.opacity
|
|
1989
|
+
);
|
|
1990
|
+
} else {
|
|
1991
|
+
renderSquareGrid(
|
|
1992
|
+
ctx,
|
|
1993
|
+
visibleBounds,
|
|
1994
|
+
grid.cellSize,
|
|
1995
|
+
grid.strokeColor,
|
|
1996
|
+
grid.strokeWidth,
|
|
1997
|
+
grid.opacity
|
|
1998
|
+
);
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
2001
|
+
function loadImages(elements) {
|
|
2002
|
+
const imageElements = elements.filter(
|
|
2003
|
+
(el) => el.type === "image" && "src" in el
|
|
2004
|
+
);
|
|
2005
|
+
const cache = /* @__PURE__ */ new Map();
|
|
2006
|
+
if (imageElements.length === 0) return Promise.resolve(cache);
|
|
2007
|
+
return new Promise((resolve) => {
|
|
2008
|
+
let remaining = imageElements.length;
|
|
2009
|
+
const done = () => {
|
|
2010
|
+
remaining--;
|
|
2011
|
+
if (remaining <= 0) resolve(cache);
|
|
2012
|
+
};
|
|
2013
|
+
for (const el of imageElements) {
|
|
2014
|
+
const img = new Image();
|
|
2015
|
+
img.crossOrigin = "anonymous";
|
|
2016
|
+
img.onload = () => {
|
|
2017
|
+
cache.set(el.id, img);
|
|
2018
|
+
done();
|
|
2019
|
+
};
|
|
2020
|
+
img.onerror = done;
|
|
2021
|
+
img.src = el.src;
|
|
2022
|
+
}
|
|
2023
|
+
});
|
|
2024
|
+
}
|
|
2025
|
+
async function exportImage(store, options = {}, layerManager) {
|
|
2026
|
+
const scale = options.scale ?? 2;
|
|
2027
|
+
const padding = options.padding ?? 0;
|
|
2028
|
+
const background = options.background ?? "#ffffff";
|
|
2029
|
+
const filter = options.filter;
|
|
2030
|
+
const allElements = store.getAll();
|
|
2031
|
+
let visibleElements = layerManager ? allElements.filter((el) => layerManager.isLayerVisible(el.layerId)) : allElements;
|
|
2032
|
+
if (filter) {
|
|
2033
|
+
visibleElements = visibleElements.filter(filter);
|
|
2034
|
+
}
|
|
2035
|
+
const bounds = computeBounds(visibleElements, padding);
|
|
2036
|
+
if (!bounds) return null;
|
|
2037
|
+
const imageCache = await loadImages(visibleElements);
|
|
2038
|
+
const canvas = document.createElement("canvas");
|
|
2039
|
+
canvas.width = Math.ceil(bounds.w * scale);
|
|
2040
|
+
canvas.height = Math.ceil(bounds.h * scale);
|
|
2041
|
+
const ctx = canvas.getContext("2d");
|
|
2042
|
+
if (!ctx) return null;
|
|
2043
|
+
ctx.scale(scale, scale);
|
|
2044
|
+
ctx.translate(-bounds.x, -bounds.y);
|
|
2045
|
+
ctx.fillStyle = background;
|
|
2046
|
+
ctx.fillRect(bounds.x, bounds.y, bounds.w, bounds.h);
|
|
2047
|
+
const renderer = new ElementRenderer();
|
|
2048
|
+
renderer.setStore(store);
|
|
2049
|
+
const grids = [];
|
|
2050
|
+
for (const el of visibleElements) {
|
|
2051
|
+
if (el.type === "grid") {
|
|
2052
|
+
grids.push(el);
|
|
2053
|
+
continue;
|
|
2054
|
+
}
|
|
2055
|
+
if (el.type === "note") {
|
|
2056
|
+
renderNoteOnCanvas(ctx, el);
|
|
2057
|
+
continue;
|
|
2058
|
+
}
|
|
2059
|
+
if (el.type === "text") {
|
|
2060
|
+
renderTextOnCanvas(ctx, el);
|
|
2061
|
+
continue;
|
|
2062
|
+
}
|
|
2063
|
+
if (el.type === "html") {
|
|
2064
|
+
continue;
|
|
2065
|
+
}
|
|
2066
|
+
if (el.type === "image") {
|
|
2067
|
+
const img = imageCache.get(el.id);
|
|
2068
|
+
if (img) {
|
|
2069
|
+
ctx.drawImage(img, el.position.x, el.position.y, el.size.w, el.size.h);
|
|
2070
|
+
}
|
|
2071
|
+
continue;
|
|
2072
|
+
}
|
|
2073
|
+
renderer.renderCanvasElement(ctx, el);
|
|
2074
|
+
}
|
|
2075
|
+
for (const grid of grids) {
|
|
2076
|
+
renderGridForBounds(ctx, grid, bounds);
|
|
2077
|
+
}
|
|
2078
|
+
return new Promise((resolve) => {
|
|
2079
|
+
canvas.toBlob((blob) => resolve(blob), "image/png");
|
|
2080
|
+
});
|
|
2081
|
+
}
|
|
2082
|
+
|
|
1831
2083
|
// src/layers/layer-manager.ts
|
|
1832
2084
|
var LayerManager = class {
|
|
1833
2085
|
constructor(store) {
|
|
@@ -2089,6 +2341,9 @@ var Viewport = class {
|
|
|
2089
2341
|
exportJSON() {
|
|
2090
2342
|
return JSON.stringify(this.exportState());
|
|
2091
2343
|
}
|
|
2344
|
+
async exportImage(options) {
|
|
2345
|
+
return exportImage(this.store, options, this.layerManager);
|
|
2346
|
+
}
|
|
2092
2347
|
loadState(state) {
|
|
2093
2348
|
this.historyRecorder.pause();
|
|
2094
2349
|
this.noteEditor.destroy(this.store);
|
|
@@ -2130,7 +2385,12 @@ var Viewport = class {
|
|
|
2130
2385
|
}
|
|
2131
2386
|
addHtmlElement(dom, position, size = { w: 200, h: 150 }) {
|
|
2132
2387
|
const domId = dom.id || void 0;
|
|
2133
|
-
const el = createHtmlElement({
|
|
2388
|
+
const el = createHtmlElement({
|
|
2389
|
+
position,
|
|
2390
|
+
size,
|
|
2391
|
+
domId,
|
|
2392
|
+
layerId: this.layerManager.activeLayerId
|
|
2393
|
+
});
|
|
2134
2394
|
this.htmlContent.set(el.id, dom);
|
|
2135
2395
|
this.historyRecorder.begin();
|
|
2136
2396
|
this.store.add(el);
|
|
@@ -3667,7 +3927,7 @@ var UpdateLayerCommand = class {
|
|
|
3667
3927
|
};
|
|
3668
3928
|
|
|
3669
3929
|
// src/index.ts
|
|
3670
|
-
var VERSION = "0.
|
|
3930
|
+
var VERSION = "0.8.1";
|
|
3671
3931
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3672
3932
|
0 && (module.exports = {
|
|
3673
3933
|
AddElementCommand,
|
|
@@ -3710,6 +3970,7 @@ var VERSION = "0.7.0";
|
|
|
3710
3970
|
createShape,
|
|
3711
3971
|
createStroke,
|
|
3712
3972
|
createText,
|
|
3973
|
+
exportImage,
|
|
3713
3974
|
exportState,
|
|
3714
3975
|
findBindTarget,
|
|
3715
3976
|
findBoundArrows,
|