mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 17:27:08 +08:00
This commit is contained in:
1 parent
d7e2456f67
commit
105590d6d7
3 files changed
+164
-48
No files matched your search
@@ -171,11 +171,6 @@ a:focus-visible {
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.editor-group:has(#shapeFill) .editor-field {
|
|
||||||
grid-template-columns: 2em 100px;
|
|
||||||
justify-content: end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.editor-group:has(#textFont) {
|
.editor-group:has(#textFont) {
|
||||||
grid-template-columns: 100px 54px;
|
grid-template-columns: 100px 54px;
|
||||||
}
|
}
|
||||||
@@ -1277,7 +1272,8 @@ button.outline-link:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.edit-object:hover,
|
.edit-object:hover,
|
||||||
.edit-object.selected {
|
.edit-object.selected,
|
||||||
|
.edit-selection {
|
||||||
outline: calc(1px / var(--surface-scale)) solid var(--accent);
|
outline: calc(1px / var(--surface-scale)) solid var(--accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1301,7 +1297,8 @@ button.outline-link:hover {
|
|||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.selected .edit-handle {
|
.selected .edit-handle,
|
||||||
|
.edit-selection .edit-handle {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1309,6 +1306,11 @@ button.outline-link:hover {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.edit-selection .edit-handle {
|
||||||
|
pointer-events: auto;
|
||||||
|
touch-action: none;
|
||||||
|
}
|
||||||
|
|
||||||
.edit-handle::before {
|
.edit-handle::before {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: calc(-8px / var(--surface-scale));
|
inset: calc(-8px / var(--surface-scale));
|
||||||
|
|||||||
+55
-25
@@ -233,6 +233,7 @@ const fontManager = new FontManager({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const canvasEditor = new CanvasEditor(el.viewerPanel, {
|
const canvasEditor = new CanvasEditor(el.viewerPanel, {
|
||||||
|
fontControls: [el.textFont, el.textFontAdd],
|
||||||
busy: () => document.body.hasAttribute("aria-busy"),
|
busy: () => document.body.hasAttribute("aria-busy"),
|
||||||
rotation: () => state.rotation,
|
rotation: () => state.rotation,
|
||||||
onSelect: updateObjectControls,
|
onSelect: updateObjectControls,
|
||||||
@@ -247,14 +248,15 @@ const canvasEditor = new CanvasEditor(el.viewerPanel, {
|
|||||||
onDraw: (index, shape, box, style) => changeDocument("ofdgoInsertShape", null, index, shape,
|
onDraw: (index, shape, box, style) => changeDocument("ofdgoInsertShape", null, index, shape,
|
||||||
box.x, box.y, box.width, box.height, style.fill, style.fillColor, style.stroke, style.strokeColor, style.lineWidth),
|
box.x, box.y, box.width, box.height, style.fill, style.fillColor, style.stroke, style.strokeColor, style.lineWidth),
|
||||||
onDrawText: beginCanvasText,
|
onDrawText: beginCanvasText,
|
||||||
onCommitText: (item, value) => item.draft
|
onCommitText: (item, value, fontData) => item.draft
|
||||||
? changeDocument("ofdgoInsertText", null, item.index, value, item.fontData, item.x, item.y, item.size, item.color, item.width, item.wrap, item.align, item.paragraphHeight)
|
? changeDocument("ofdgoInsertText", null, item.index, value, fontData || item.fontData, item.x, item.y, item.size, item.color, item.width, item.wrap, item.align, item.paragraphHeight)
|
||||||
: changeDocument("ofdgoUpdateText", item, value, null, item.size, null),
|
: changeDocument("ofdgoUpdateText", item, value, fontData || null, item.size, null),
|
||||||
onTextChange: () => {
|
onTextChange: () => {
|
||||||
const editing = canvasEditor.input;
|
const editing = canvasEditor.input;
|
||||||
setDirty(state.editing && (state.editorInfo?.revision !== state.savedRevision
|
setDirty(state.editing && (state.editorInfo?.revision !== state.savedRevision
|
||||||
|| Boolean(editing && editing.input.value !== editing.item.text)));
|
|| Boolean(editing && (editing.input.value !== editing.item.text || editing.fontData))));
|
||||||
if (!editing) {
|
if (!editing) {
|
||||||
|
updateTextFonts(canvasEditor.selected, true);
|
||||||
syncSelection();
|
syncSelection();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -304,19 +306,7 @@ el.textColor.addEventListener("change", () => changeTextStyle(true));
|
|||||||
editorClick(el.textWrap, () => changeParagraph(!currentTextStyle().wrap));
|
editorClick(el.textWrap, () => changeParagraph(!currentTextStyle().wrap));
|
||||||
el.textAlign.addEventListener("change", () => changeParagraph());
|
el.textAlign.addEventListener("change", () => changeParagraph());
|
||||||
el.textLineHeight.addEventListener("change", () => changeParagraph());
|
el.textLineHeight.addEventListener("change", () => changeParagraph());
|
||||||
el.textFont.addEventListener("change", async () => {
|
el.textFont.addEventListener("change", changeTextFont);
|
||||||
const item = canvasEditor.selected;
|
|
||||||
const font = state.textFonts[Number(el.textFont.value)];
|
|
||||||
if (!state.ready || document.body.hasAttribute("aria-busy") || !font || item?.draft) return;
|
|
||||||
if (item?.type === "TextObject") {
|
|
||||||
if (!font.embedded) {
|
|
||||||
await changeDocument("ofdgoUpdateText", item, item.text, fontManager.read(font), item.size, null);
|
|
||||||
updateTextFonts(canvasEditor.selected, true);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
state.textDefaults.fontChoice = font;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
el.shapeWidth.addEventListener("focus", () => { el.shapeWidth.defaultValue = el.shapeWidth.value; });
|
el.shapeWidth.addEventListener("focus", () => { el.shapeWidth.defaultValue = el.shapeWidth.value; });
|
||||||
for (const input of [el.textSize, el.shapeWidth, el.textLineHeight]) {
|
for (const input of [el.textSize, el.shapeWidth, el.textLineHeight]) {
|
||||||
input.addEventListener("keydown", (event) => {
|
input.addEventListener("keydown", (event) => {
|
||||||
@@ -418,10 +408,7 @@ editorClick(el.insertImageButton, () => openInsertPanel());
|
|||||||
el.insertCancel.addEventListener("click", () => el.insertPanel.close());
|
el.insertCancel.addEventListener("click", () => el.insertPanel.close());
|
||||||
el.insertPanel.addEventListener("close", () => { state.insertObject = null; });
|
el.insertPanel.addEventListener("close", () => { state.insertObject = null; });
|
||||||
el.insertForm.addEventListener("submit", insertObject);
|
el.insertForm.addEventListener("submit", insertObject);
|
||||||
el.textFontAdd.addEventListener("click", () => {
|
el.textFontAdd.addEventListener("click", () => openFontFile(el.fontInput));
|
||||||
openFontFile(el.fontInput);
|
|
||||||
canvasEditor.commitText();
|
|
||||||
});
|
|
||||||
el.saveButton.addEventListener("click", () => exportFile(true, null, "ofd"));
|
el.saveButton.addEventListener("click", () => exportFile(true, null, "ofd"));
|
||||||
el.togglePagesButton.addEventListener("click", () => toggleSidebar("pages"));
|
el.togglePagesButton.addEventListener("click", () => toggleSidebar("pages"));
|
||||||
el.toggleMetaButton.addEventListener("click", () => toggleSidebar("meta"));
|
el.toggleMetaButton.addEventListener("click", () => toggleSidebar("meta"));
|
||||||
@@ -851,13 +838,51 @@ function updateTextFonts(item, refresh = false) {
|
|||||||
if (refresh || state.textFontID !== id) {
|
if (refresh || state.textFontID !== id) {
|
||||||
state.textFontID = id;
|
state.textFontID = id;
|
||||||
state.textFonts = editorFonts(id ? item : null);
|
state.textFonts = editorFonts(id ? item : null);
|
||||||
const selected = id ? state.textFonts[0] : state.textDefaults.fontChoice;
|
const selected = canvasEditor.input?.fontChoice || (id ? state.textFonts[0] : state.textDefaults.fontChoice);
|
||||||
if (!id && selected?.embedded) state.textFonts.unshift(selected);
|
if (selected && (canvasEditor.input || selected.embedded)
|
||||||
|
&& !state.textFonts.some(font => (font.id || font.postscriptName) === (selected.id || selected.postscriptName))) {
|
||||||
|
state.textFonts.unshift(selected);
|
||||||
|
}
|
||||||
setFontOptions(el.textFont, state.textFonts, selected || state.textFonts[0]);
|
setFontOptions(el.textFont, state.textFonts, selected || state.textFonts[0]);
|
||||||
if (!id) state.textDefaults.fontChoice = state.textFonts[Number(el.textFont.value)];
|
if (!id) state.textDefaults.fontChoice = state.textFonts[Number(el.textFont.value)];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// changeTextFont preserves uncommitted text while loading a replacement font.
|
||||||
|
async function changeTextFont() {
|
||||||
|
const item = canvasEditor.selected;
|
||||||
|
const font = state.textFonts[Number(el.textFont.value)];
|
||||||
|
if (!state.ready || document.body.hasAttribute("aria-busy") || !font) return;
|
||||||
|
const editing = canvasEditor.input;
|
||||||
|
if (editing) {
|
||||||
|
const openSeq = state.openSeq;
|
||||||
|
setBusy(true);
|
||||||
|
try {
|
||||||
|
const data = font.embedded ? (await callWASM("ofdgoEditorFont", font.id.slice(9))).bytes : await fontManager.read(font);
|
||||||
|
const face = new FontFace("ofdgo-edit-font", data);
|
||||||
|
await face.load();
|
||||||
|
if (openSeq !== state.openSeq || canvasEditor.input !== editing) return;
|
||||||
|
editing.fontChoice = font;
|
||||||
|
if (item.draft) state.textDefaults.fontChoice = font;
|
||||||
|
canvasEditor.setTextFont(face, data);
|
||||||
|
} catch (err) {
|
||||||
|
if (openSeq === state.openSeq) {
|
||||||
|
updateTextFonts(canvasEditor.selected, true);
|
||||||
|
showError(err, false);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (openSeq === state.openSeq) setBusy(false);
|
||||||
|
}
|
||||||
|
} else if (item?.type === "TextObject") {
|
||||||
|
if (!font.embedded) {
|
||||||
|
await changeDocument("ofdgoUpdateText", item, item.text, fontManager.read(font), item.size, null);
|
||||||
|
updateTextFonts(canvasEditor.selected, true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
state.textDefaults.fontChoice = font;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function insertObject(event) {
|
async function insertObject(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (!state.editing || document.body.hasAttribute("aria-busy") || el.insertSubmit.disabled) {
|
if (!state.editing || document.body.hasAttribute("aria-busy") || el.insertSubmit.disabled) {
|
||||||
@@ -1779,6 +1804,10 @@ async function applyFontChange(options = {}) {
|
|||||||
if (!state.doc) {
|
if (!state.doc) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (state.editing) {
|
||||||
|
setStatus(pageStatus(state.pageIndex, state.doc.pageCount));
|
||||||
|
return;
|
||||||
|
}
|
||||||
await openDocument({
|
await openDocument({
|
||||||
pageIndex: state.pageIndex,
|
pageIndex: state.pageIndex,
|
||||||
fitMode: state.fitMode,
|
fitMode: state.fitMode,
|
||||||
@@ -4135,8 +4164,9 @@ function updateObjectControls(item, reset = false) {
|
|||||||
el.editObjectButton.disabled = disabled || Boolean(item.items) || item.type === "PathObject";
|
el.editObjectButton.disabled = disabled || Boolean(item.items) || item.type === "PathObject";
|
||||||
el.multiSelectButton.disabled = !state.editing || !canvasEditor.enabled || !state.ready || state.exporting;
|
el.multiSelectButton.disabled = !state.editing || !canvasEditor.enabled || !state.ready || state.exporting;
|
||||||
const text = item?.type === "TextObject" ? item : state.textDefaults;
|
const text = item?.type === "TextObject" ? item : state.textDefaults;
|
||||||
el.textFont.disabled = el.textSize.disabled = el.textColor.disabled = !state.editing || !state.ready || state.exporting || Boolean(item?.draft || item?.items);
|
el.textFont.disabled = !state.editing || !state.ready || state.exporting || Boolean(item?.items);
|
||||||
el.textFontAdd.disabled = !state.editing || !state.ready || state.exporting || Boolean(item?.draft);
|
el.textSize.disabled = el.textColor.disabled = el.textFont.disabled || Boolean(item?.draft);
|
||||||
|
el.textFontAdd.disabled = !state.editing || !state.ready || state.exporting;
|
||||||
el.textAlign.disabled = el.textWrap.disabled = el.textLineHeight.disabled = el.textSize.disabled;
|
el.textAlign.disabled = el.textWrap.disabled = el.textLineHeight.disabled = el.textSize.disabled;
|
||||||
el.textAlign.value = text.align || "left";
|
el.textAlign.value = text.align || "left";
|
||||||
el.textWrap.setAttribute("aria-pressed", String(Boolean(text.wrap)));
|
el.textWrap.setAttribute("aria-pressed", String(Boolean(text.wrap)));
|
||||||
|
|||||||
+100
-16
@@ -28,6 +28,29 @@ export function selectionBounds(items) {
|
|||||||
height: Math.max(...items.map(item => item.y + item.height)) - y };
|
height: Math.max(...items.map(item => item.y + item.height)) - y };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// alignmentGuides marks the nearest edges or centers without changing object coordinates.
|
||||||
|
function alignmentGuides(box, targets, tolerance) {
|
||||||
|
const lines = [];
|
||||||
|
for (const [axis, size, cross, span] of [["x", "width", "y", "height"], ["y", "height", "x", "width"]]) {
|
||||||
|
let distance = Infinity, line;
|
||||||
|
for (const target of targets) {
|
||||||
|
for (const anchor of [0, 0.5, 1]) {
|
||||||
|
const position = target[axis] + target[size] * anchor;
|
||||||
|
for (const edge of [0, 0.5, 1]) {
|
||||||
|
const delta = Math.abs(box[axis] + box[size] * edge - position);
|
||||||
|
if (delta > tolerance[axis] || delta >= distance) continue;
|
||||||
|
distance = delta;
|
||||||
|
const from = Math.min(box[cross], target[cross]);
|
||||||
|
const to = Math.max(box[cross] + box[span], target[cross] + target[span]);
|
||||||
|
line = axis === "x" ? `M${position} ${from}V${to}` : `M${from} ${position}H${to}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (line) lines.push(line);
|
||||||
|
}
|
||||||
|
return lines.join("");
|
||||||
|
}
|
||||||
|
|
||||||
// constrainedPoint preserves shape constraints without changing off-page geometry.
|
// constrainedPoint preserves shape constraints without changing off-page geometry.
|
||||||
export function constrainedPoint(from, to, shape, shift) {
|
export function constrainedPoint(from, to, shape, shift) {
|
||||||
let dx = to.x - from.x, dy = to.y - from.y;
|
let dx = to.x - from.x, dy = to.y - from.y;
|
||||||
@@ -170,11 +193,14 @@ export class CanvasEditor {
|
|||||||
|
|
||||||
place(item, change = { x: 0, y: 0, scale: 1 }) {
|
place(item, change = { x: 0, y: 0, scale: 1 }) {
|
||||||
if (item.items) {
|
if (item.items) {
|
||||||
for (const member of item.items) this.place(member, change);
|
for (const member of item.items) this.place(member, {
|
||||||
return;
|
x: change.x + (member.x - item.x) * (change.scale - 1),
|
||||||
|
y: change.y + (member.y - item.y) * (change.scale - 1), scale: change.scale,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (item.shape) {
|
if (item.shape) {
|
||||||
this.placeShape(item, { x: item.geometry.x + change.x, y: item.geometry.y + change.y,
|
this.placeShape(item, { x: item.geometry.x + change.x + (item.geometry.x - item.x) * (change.scale - 1),
|
||||||
|
y: item.geometry.y + change.y + (item.geometry.y - item.y) * (change.scale - 1),
|
||||||
width: item.geometry.width * change.scale, height: item.geometry.height * change.scale });
|
width: item.geometry.width * change.scale, height: item.geometry.height * change.scale });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -207,12 +233,27 @@ export class CanvasEditor {
|
|||||||
|
|
||||||
// setSelection updates object highlights and exposes one same-page selection to the toolbar.
|
// setSelection updates object highlights and exposes one same-page selection to the toolbar.
|
||||||
setSelection(items) {
|
setSelection(items) {
|
||||||
|
if (this.selected?.items) this.selected.node.remove();
|
||||||
for (const item of this.items()) {
|
for (const item of this.items()) {
|
||||||
item.node.classList.remove("selected", "multi-selected");
|
item.node.classList.remove("selected", "multi-selected");
|
||||||
item.node.setAttribute("aria-pressed", "false");
|
item.node.setAttribute("aria-pressed", "false");
|
||||||
}
|
}
|
||||||
this.selected = items.length < 2 ? items[0] || null : { ...selectionBounds(items), items,
|
this.selected = items.length < 2 ? items[0] || null : { ...selectionBounds(items), items,
|
||||||
id: items.map(item => item.id), index: items[0].index, page: items[0].page, surface: items[0].surface };
|
id: items.map(item => item.id), index: items[0].index, page: items[0].page, surface: items[0].surface };
|
||||||
|
if (items.length > 1) {
|
||||||
|
const node = document.createElement("div");
|
||||||
|
node.className = "edit-layer edit-selection";
|
||||||
|
for (const corner of ["nw", "ne", "sw", "se"]) {
|
||||||
|
const handle = document.createElement("span");
|
||||||
|
handle.className = `edit-handle edit-${corner}`;
|
||||||
|
handle.dataset.corner = corner;
|
||||||
|
node.append(handle);
|
||||||
|
}
|
||||||
|
this.selected.node = node;
|
||||||
|
this.nodes.set(node, this.selected);
|
||||||
|
this.place(this.selected);
|
||||||
|
this.selected.surface.append(node);
|
||||||
|
}
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
item.node.classList.add("selected");
|
item.node.classList.add("selected");
|
||||||
item.node.classList.toggle("multi-selected", items.length > 1);
|
item.node.classList.toggle("multi-selected", items.length > 1);
|
||||||
@@ -249,7 +290,7 @@ export class CanvasEditor {
|
|||||||
this.startShape(event);
|
this.startShape(event);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const node = event.target.closest(".edit-object");
|
const node = event.target.closest(".edit-object, .edit-selection");
|
||||||
if (node) {
|
if (node) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
@@ -264,16 +305,22 @@ export class CanvasEditor {
|
|||||||
this.select(target, true);
|
this.select(target, true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!this.items().includes(target)) this.select(target);
|
if (target !== this.selected && !this.items().includes(target)) this.select(target);
|
||||||
if (!node || this.options.busy()) {
|
if (!node || this.options.busy()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const item = this.selected;
|
const item = this.selected;
|
||||||
this.drag = {
|
this.drag = {
|
||||||
item, pointerID: event.pointerId, corner: item.items ? "" : event.target.dataset.corner || "",
|
item, pointerID: event.pointerId, corner: !item.items || target === item ? event.target.dataset.corner || "" : "",
|
||||||
rect: item.surface.getBoundingClientRect(), rotation: this.options.rotation(),
|
rect: item.surface.getBoundingClientRect(), rotation: this.options.rotation(),
|
||||||
clientX: event.clientX, clientY: event.clientY,
|
clientX: event.clientX, clientY: event.clientY,
|
||||||
};
|
};
|
||||||
|
if (!this.drag.corner) {
|
||||||
|
const selected = this.items();
|
||||||
|
this.drag.targets = [...item.surface.querySelectorAll(".edit-object")]
|
||||||
|
.map(node => this.nodes.get(node)).filter(member => !selected.includes(member));
|
||||||
|
this.drag.targets.push({ x: 0, y: 0, ...item.page });
|
||||||
|
}
|
||||||
if (item.shape === "line" && this.drag.corner) {
|
if (item.shape === "line" && this.drag.corner) {
|
||||||
Object.assign(this.drag, this.createPreview(item.surface, item.page, item.shape, {
|
Object.assign(this.drag, this.createPreview(item.surface, item.page, item.shape, {
|
||||||
fill: false, stroke: true, strokeColor: "var(--accent)", lineWidth: item.lineWidth,
|
fill: false, stroke: true, strokeColor: "var(--accent)", lineWidth: item.lineWidth,
|
||||||
@@ -315,6 +362,24 @@ export class CanvasEditor {
|
|||||||
}
|
}
|
||||||
drag.change = objectTransform(drag.item, to.x - from.x, to.y - from.y, drag.corner);
|
drag.change = objectTransform(drag.item, to.x - from.x, to.y - from.y, drag.corner);
|
||||||
this.place(drag.item, drag.change);
|
this.place(drag.item, drag.change);
|
||||||
|
if (!drag.corner) this.showGuides(drag);
|
||||||
|
}
|
||||||
|
|
||||||
|
// showGuides keeps the visual threshold constant through page zoom and rotation.
|
||||||
|
showGuides(drag) {
|
||||||
|
const { item, rect, rotation, change } = drag;
|
||||||
|
const path = alignmentGuides({ ...item, x: item.x + change.x, y: item.y + change.y }, drag.targets, {
|
||||||
|
x: 4 * item.page.width / (rotation % 180 ? rect.height : rect.width),
|
||||||
|
y: 4 * item.page.height / (rotation % 180 ? rect.width : rect.height),
|
||||||
|
});
|
||||||
|
if (!drag.guides && path) {
|
||||||
|
drag.guides = this.createPreview(item.surface, item.page, "path", {
|
||||||
|
fill: false, stroke: true, strokeColor: "var(--accent)", lineWidth: 1,
|
||||||
|
});
|
||||||
|
drag.guides.node.setAttribute("vector-effect", "non-scaling-stroke");
|
||||||
|
drag.guides.node.setAttribute("stroke-dasharray", "4 3");
|
||||||
|
}
|
||||||
|
drag.guides?.node.setAttribute("d", path);
|
||||||
}
|
}
|
||||||
|
|
||||||
end(event) {
|
end(event) {
|
||||||
@@ -374,6 +439,7 @@ export class CanvasEditor {
|
|||||||
this.drag = null;
|
this.drag = null;
|
||||||
if (drag) {
|
if (drag) {
|
||||||
drag.preview?.remove();
|
drag.preview?.remove();
|
||||||
|
drag.guides?.preview.remove();
|
||||||
if (drag.marquee) this.setSelection(drag.before);
|
if (drag.marquee) this.setSelection(drag.before);
|
||||||
if (drag.item) {
|
if (drag.item) {
|
||||||
this.place(drag.item);
|
this.place(drag.item);
|
||||||
@@ -554,17 +620,15 @@ export class CanvasEditor {
|
|||||||
lineHeight: item.paragraphHeight || item.lineHeight ? `${(item.paragraphHeight || item.lineHeight) * PX_PER_MM}px` : "normal", color: item.color,
|
lineHeight: item.paragraphHeight || item.lineHeight ? `${(item.paragraphHeight || item.lineHeight) * PX_PER_MM}px` : "normal", color: item.color,
|
||||||
textAlign: item.align || "left",
|
textAlign: item.align || "left",
|
||||||
});
|
});
|
||||||
this.input = { input, item, face };
|
this.input = { input, item, face, fontChoice: item.fontChoice };
|
||||||
item.surface.append(input);
|
item.surface.append(input);
|
||||||
const resize = () => {
|
|
||||||
input.style.height = "0px";
|
|
||||||
input.style.height = `${input.scrollHeight + 2}px`;
|
|
||||||
};
|
|
||||||
input.addEventListener("input", () => {
|
input.addEventListener("input", () => {
|
||||||
resize();
|
this.resizeText();
|
||||||
this.options.onTextChange();
|
this.options.onTextChange();
|
||||||
});
|
});
|
||||||
input.addEventListener("blur", () => this.commitText());
|
input.addEventListener("blur", (event) => {
|
||||||
|
if (!this.options.fontControls?.includes(event.relatedTarget)) this.commitText();
|
||||||
|
});
|
||||||
input.addEventListener("keydown", (event) => {
|
input.addEventListener("keydown", (event) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
if (event.isComposing || input.readOnly) {
|
if (event.isComposing || input.readOnly) {
|
||||||
@@ -579,21 +643,41 @@ export class CanvasEditor {
|
|||||||
this.commitText();
|
this.commitText();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
resize();
|
this.resizeText();
|
||||||
input.focus({ preventScroll: true });
|
input.focus({ preventScroll: true });
|
||||||
if (!item.draft) input.select();
|
if (!item.draft) input.select();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// resizeText follows the draft's current font and content without changing document geometry.
|
||||||
|
resizeText() {
|
||||||
|
const { input } = this.input;
|
||||||
|
input.style.height = "0px";
|
||||||
|
input.style.height = `${input.scrollHeight + 2}px`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// setTextFont changes only the inline buffer until text and font are committed together.
|
||||||
|
setTextFont(face, data) {
|
||||||
|
const editing = this.input;
|
||||||
|
document.fonts.add(face);
|
||||||
|
document.fonts.delete(editing.face);
|
||||||
|
editing.face = face;
|
||||||
|
editing.fontData = data;
|
||||||
|
editing.input.style.fontFamily = `"${face.family}"`;
|
||||||
|
this.resizeText();
|
||||||
|
this.options.onTextChange();
|
||||||
|
editing.input.focus({ preventScroll: true });
|
||||||
|
}
|
||||||
|
|
||||||
async commitText() {
|
async commitText() {
|
||||||
const editing = this.input;
|
const editing = this.input;
|
||||||
if (!editing) return true;
|
if (!editing) return true;
|
||||||
if (editing.input.readOnly) return false;
|
if (editing.input.readOnly) return false;
|
||||||
if (editing.input.value === editing.item.text || editing.item.draft && !editing.input.value.trim()) {
|
if (editing.input.value === editing.item.text && !editing.fontData || editing.item.draft && !editing.input.value.trim()) {
|
||||||
this.closeText();
|
this.closeText();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
editing.input.readOnly = true;
|
editing.input.readOnly = true;
|
||||||
const saved = await this.options.onCommitText(editing.item, editing.input.value);
|
const saved = await this.options.onCommitText(editing.item, editing.input.value, editing.fontData);
|
||||||
if (this.input !== editing) {
|
if (this.input !== editing) {
|
||||||
return Boolean(saved);
|
return Boolean(saved);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user