mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 16:59:27 +08:00
This commit is contained in:
1 parent
c68bf1f3c9
commit
d7e2456f67
6 files changed
+339
-141
No files matched your search
@@ -83,11 +83,16 @@
|
||||
<option value="middle">垂直</option>
|
||||
<option value="bottom">底部</option>
|
||||
</select>
|
||||
<select id="objectDistribute" class="order-select" aria-label="对象分布" disabled>
|
||||
<option value="" hidden>分布</option>
|
||||
<option value="horizontal">水平</option>
|
||||
<option value="vertical">垂直</option>
|
||||
</select>
|
||||
<span>对象</span>
|
||||
</div>
|
||||
<div class="editor-group">
|
||||
<div class="text-font"><select id="textFont" aria-label="字体" title="字体" disabled></select><button id="textFontAdd" class="icon-button" type="button" title="添加字体" aria-label="添加字体">+</button></div>
|
||||
<div class="form-unit text-size"><input id="textSize" type="number" min="1" step="any" required aria-label="字号" disabled><span>pt</span></div>
|
||||
<div class="editor-number"><input id="textSize" type="number" min="1" step="any" required aria-label="字号" disabled><span>pt</span></div>
|
||||
<input id="textColor" class="color-input" type="color" value="#000000" aria-label="文字颜色" title="文字颜色" disabled>
|
||||
<span>文字</span>
|
||||
</div>
|
||||
@@ -99,7 +104,7 @@
|
||||
<option value="justify">两端</option>
|
||||
</select>
|
||||
<button id="textWrap" class="button" type="button" aria-label="自动折行" aria-pressed="false" disabled>折行</button>
|
||||
<label class="form-unit paragraph-height"><span>行距</span><input id="textLineHeight" type="number" min="0" step="any" placeholder="自动" aria-label="行距" title="行距,0为自动" disabled><span>pt</span></label>
|
||||
<label class="editor-field"><span>行距</span><span class="editor-number"><input id="textLineHeight" type="number" min="0" step="any" placeholder="自动" aria-label="行距" title="行距,0为自动" disabled><span>pt</span></span></label>
|
||||
<span>段落</span>
|
||||
</div>
|
||||
<div class="editor-group">
|
||||
@@ -113,7 +118,7 @@
|
||||
<div class="editor-group">
|
||||
<div class="shape-paint"><label><input id="shapeFill" type="checkbox">填充</label><input id="shapeFillColor" class="color-input" type="color" value="#dcefeb" aria-label="填充颜色" title="填充颜色"></div>
|
||||
<div class="shape-paint"><label><input id="shapeStroke" type="checkbox" checked>描边</label><input id="shapeStrokeColor" class="color-input" type="color" value="#000000" aria-label="描边颜色" title="描边颜色"></div>
|
||||
<div class="form-unit text-size"><input id="shapeWidth" type="number" value="1" min="0" step="any" required aria-label="线宽" title="线宽"><span>pt</span></div>
|
||||
<label class="editor-field"><span>线宽</span><span class="editor-number"><input id="shapeWidth" type="number" value="1" min="0" step="any" required aria-label="线宽" title="线宽"><span>pt</span></span></label>
|
||||
<span>图形</span>
|
||||
</div>
|
||||
<div class="editor-group">
|
||||
|
||||
+63
-20
@@ -121,15 +121,68 @@ a:focus-visible {
|
||||
grid-template-columns: repeat(4, max-content);
|
||||
}
|
||||
|
||||
.form-unit.paragraph-height {
|
||||
.editor-field {
|
||||
display: grid;
|
||||
align-items: center;
|
||||
grid-column: 1 / -1;
|
||||
grid-template-columns: auto 58px auto;
|
||||
gap: 4px;
|
||||
grid-template-columns: 2em minmax(0, 1fr);
|
||||
gap: 6px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.paragraph-height input {
|
||||
width: 58px;
|
||||
.editor-number {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
height: var(--control-height);
|
||||
border: 1px solid var(--line);
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.editor-number:focus-within {
|
||||
outline: 2px solid var(--focus);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.editor-number input {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
padding: 0 6px;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
background: transparent;
|
||||
color: #1f2428;
|
||||
text-align: right;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.editor-number > span {
|
||||
padding-right: 6px;
|
||||
color: var(--text-muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.editor-number:has(input:disabled) {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.editor-number input:disabled {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.editor-group:has(#shapeFill) .editor-field {
|
||||
grid-template-columns: 2em 100px;
|
||||
justify-content: end;
|
||||
}
|
||||
|
||||
.editor-group:has(#textFont) {
|
||||
grid-template-columns: 100px 54px;
|
||||
}
|
||||
|
||||
.editor-paragraph {
|
||||
width: 172px;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.editor-file #saveButton {
|
||||
@@ -353,17 +406,14 @@ button:not(:disabled):hover {
|
||||
}
|
||||
|
||||
.page-control input,
|
||||
.paragraph-height input,
|
||||
.text-size input {
|
||||
.editor-number input {
|
||||
appearance: textfield;
|
||||
}
|
||||
|
||||
.page-control input::-webkit-inner-spin-button,
|
||||
.page-control input::-webkit-outer-spin-button,
|
||||
.paragraph-height input::-webkit-inner-spin-button,
|
||||
.paragraph-height input::-webkit-outer-spin-button,
|
||||
.text-size input::-webkit-inner-spin-button,
|
||||
.text-size input::-webkit-outer-spin-button {
|
||||
.editor-number input::-webkit-inner-spin-button,
|
||||
.editor-number input::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
@@ -468,9 +518,7 @@ button:not(:disabled):hover {
|
||||
.form-row input[type="number"],
|
||||
.form-row input[type="file"],
|
||||
.form-row select,
|
||||
.form-row textarea,
|
||||
.paragraph-height input,
|
||||
.text-size input {
|
||||
.form-row textarea {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
height: var(--control-height);
|
||||
@@ -489,11 +537,6 @@ button:not(:disabled):hover {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.form-unit.text-size {
|
||||
grid-template-columns: calc(2em + 22px) auto;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.color-input {
|
||||
width: calc(2em + 22px);
|
||||
height: var(--control-height);
|
||||
@@ -1201,7 +1244,7 @@ button.outline-link:hover {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.editing-draw .page-surface {
|
||||
.editing-draw {
|
||||
cursor: crosshair;
|
||||
touch-action: none;
|
||||
user-select: none;
|
||||
|
||||
+23
-14
@@ -1,4 +1,4 @@
|
||||
import { CanvasEditor, objectTransform } from "./ofdgo_edit.js";
|
||||
import { CanvasEditor } from "./ofdgo_edit.js";
|
||||
import { FontManager } from "./ofdgo_font.js";
|
||||
|
||||
const MM_TO_PX = 96 / 25.4;
|
||||
@@ -92,6 +92,7 @@ const el = {
|
||||
copyObjectButton: document.querySelector("#copyObjectButton"),
|
||||
objectOrder: document.querySelector("#objectOrder"),
|
||||
objectAlign: document.querySelector("#objectAlign"),
|
||||
objectDistribute: document.querySelector("#objectDistribute"),
|
||||
multiSelectButton: document.querySelector("#multiSelectButton"),
|
||||
textAlign: document.querySelector("#textAlign"),
|
||||
textWrap: document.querySelector("#textWrap"),
|
||||
@@ -341,11 +342,7 @@ editorClick(el.copyObjectButton, () => {
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
let change = objectTransform(item, item.page, 3, 3);
|
||||
if (!change.x && !change.y) {
|
||||
change = objectTransform(item, item.page, -3, -3);
|
||||
}
|
||||
return changeDocument("ofdgoCopyObject", item, change.x, change.y);
|
||||
return changeDocument("ofdgoCopyObjects", { ...item, id: canvasEditor.items().map(member => member.id) }, 3, 3);
|
||||
});
|
||||
el.objectOrder.addEventListener("change", () => {
|
||||
const action = el.objectOrder.value;
|
||||
@@ -354,8 +351,7 @@ el.objectOrder.addEventListener("change", () => {
|
||||
if (!item || !action) {
|
||||
return;
|
||||
}
|
||||
const targets = { up: item.order + 1, down: item.order - 1, top: item.count - 1, bottom: 0 };
|
||||
return changeDocument("ofdgoMoveObject", item, targets[action]);
|
||||
return changeDocument("ofdgoOrderObjects", { ...item, id: canvasEditor.items().map(member => member.id) }, action);
|
||||
});
|
||||
el.objectAlign.addEventListener("change", () => {
|
||||
const alignment = el.objectAlign.value;
|
||||
@@ -364,6 +360,13 @@ el.objectAlign.addEventListener("change", () => {
|
||||
return changeDocument(canvasEditor.selected.items ? "ofdgoAlignObjects" : "ofdgoAlignObject", canvasEditor.selected, alignment);
|
||||
}
|
||||
});
|
||||
el.objectDistribute.addEventListener("change", () => {
|
||||
const axis = el.objectDistribute.value;
|
||||
el.objectDistribute.value = "";
|
||||
if (axis && canvasEditor.items().length >= 3) {
|
||||
return changeDocument("ofdgoDistributeObjects", canvasEditor.selected, axis);
|
||||
}
|
||||
});
|
||||
editorClick(el.undoButton, () => changeDocument("ofdgoUndo"));
|
||||
editorClick(el.redoButton, () => changeDocument("ofdgoRedo"));
|
||||
for (const [button, tool] of [[el.drawLineButton, "line"], [el.drawRectangleButton, "rectangle"], [el.drawEllipseButton, "ellipse"]]) {
|
||||
@@ -885,7 +888,7 @@ async function insertObject(event) {
|
||||
return;
|
||||
}
|
||||
setEditorInfo(doc);
|
||||
canvasEditor.selectLast = !item;
|
||||
canvasEditor.pendingSelection = item ? null : { index };
|
||||
state.selectObjects = true;
|
||||
setPan(false);
|
||||
openSeq = ++state.openSeq;
|
||||
@@ -1031,8 +1034,8 @@ async function changeDocument(name, item, ...args) {
|
||||
if (!item || name === "ofdgoDeleteObject" || name === "ofdgoDeleteObjects") {
|
||||
canvasEditor.clear();
|
||||
}
|
||||
if (name === "ofdgoCopyObject" || name === "ofdgoInsertShape" || name === "ofdgoInsertText") {
|
||||
canvasEditor.selectLast = true;
|
||||
if (name === "ofdgoCopyObjects" || name === "ofdgoInsertShape" || name === "ofdgoInsertText") {
|
||||
canvasEditor.pendingSelection = { index: item?.index ?? args[0], ids: doc.selectedIDs };
|
||||
}
|
||||
openSeq = ++state.openSeq;
|
||||
if (item || name === "ofdgoInsertShape" || name === "ofdgoInsertText") {
|
||||
@@ -4127,7 +4130,8 @@ function updateEditorTools() {
|
||||
function updateObjectControls(item, reset = false) {
|
||||
const disabled = !item || Boolean(item.draft) || !state.ready || state.exporting;
|
||||
el.deleteObjectButton.disabled = el.objectAlign.disabled = disabled;
|
||||
el.copyObjectButton.disabled = disabled || Boolean(item?.items);
|
||||
el.copyObjectButton.disabled = disabled;
|
||||
el.objectDistribute.disabled = disabled || !item.items || item.items.length < 3;
|
||||
el.editObjectButton.disabled = disabled || Boolean(item.items) || item.type === "PathObject";
|
||||
el.multiSelectButton.disabled = !state.editing || !canvasEditor.enabled || !state.ready || state.exporting;
|
||||
const text = item?.type === "TextObject" ? item : state.textDefaults;
|
||||
@@ -4158,9 +4162,14 @@ function updateObjectControls(item, reset = false) {
|
||||
}
|
||||
}
|
||||
updateDrawingControls();
|
||||
el.objectOrder.disabled = disabled || Boolean(item.items) || item.count <= 1;
|
||||
const members = item?.items || (item ? [item] : []);
|
||||
const orders = members.map(member => member.order).sort((a, b) => a - b);
|
||||
const count = members[0]?.count || 0;
|
||||
const canRaise = orders.some((order, i) => order !== count - orders.length + i);
|
||||
const canLower = orders.some((order, i) => order !== i);
|
||||
el.objectOrder.disabled = disabled || count <= orders.length;
|
||||
for (const option of el.objectOrder.options) {
|
||||
option.disabled = disabled || (["up", "top"].includes(option.value) ? item.order === item.count - 1 : item.order === 0);
|
||||
option.disabled = disabled || (["up", "top"].includes(option.value) ? !canRaise : !canLower);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+41
-45
@@ -8,25 +8,16 @@ export function pagePoint(x, y, rect, page, rotation) {
|
||||
return { x: point[0] * page.width, y: point[1] * page.height };
|
||||
}
|
||||
|
||||
export function objectTransform(box, page, dx, dy, corner = "") {
|
||||
export function objectTransform(box, dx, dy, corner = "") {
|
||||
if (!corner) {
|
||||
return {
|
||||
x: Math.max(-box.x, Math.min(dx, Math.max(-box.x, page.width - box.x - box.width))),
|
||||
y: Math.max(-box.y, Math.min(dy, Math.max(-box.y, page.height - box.y - box.height))),
|
||||
scale: 1,
|
||||
};
|
||||
return { x: dx, y: dy, scale: 1 };
|
||||
}
|
||||
const left = corner.includes("w");
|
||||
const top = corner.includes("n");
|
||||
const x = left ? -dx : dx;
|
||||
const y = top ? -dy : dy;
|
||||
const limit = Math.min(
|
||||
(left ? box.x + box.width : page.width - box.x) / box.width,
|
||||
(top ? box.y + box.height : page.height - box.y) / box.height,
|
||||
);
|
||||
const minimum = Math.min(1, 1 / Math.min(box.width, box.height));
|
||||
const scale = Math.max(Math.min(minimum, limit), Math.min(limit,
|
||||
1 + (x * box.width + y * box.height) / (box.width ** 2 + box.height ** 2)));
|
||||
const scale = Math.max(minimum, 1 + (x * box.width + y * box.height) / (box.width ** 2 + box.height ** 2));
|
||||
return { x: left ? box.width * (1 - scale) : 0, y: top ? box.height * (1 - scale) : 0, scale };
|
||||
}
|
||||
|
||||
@@ -37,8 +28,8 @@ export function selectionBounds(items) {
|
||||
height: Math.max(...items.map(item => item.y + item.height)) - y };
|
||||
}
|
||||
|
||||
// constrainedPoint applies drawing constraints before clipping the endpoint to the page.
|
||||
export function constrainedPoint(from, to, page, shape, shift) {
|
||||
// constrainedPoint preserves shape constraints without changing off-page geometry.
|
||||
export function constrainedPoint(from, to, shape, shift) {
|
||||
let dx = to.x - from.x, dy = to.y - from.y;
|
||||
if (shift && shape === "line") {
|
||||
const directions = [[1, 0], [1, 1], [0, 1], [-1, 1], [-1, 0], [-1, -1], [0, -1], [1, -1]];
|
||||
@@ -51,37 +42,31 @@ export function constrainedPoint(from, to, page, shape, shift) {
|
||||
dx = (dx < 0 ? -1 : 1) * size;
|
||||
dy = (dy < 0 ? -1 : 1) * size;
|
||||
}
|
||||
if (!shift) {
|
||||
return { x: Math.max(0, Math.min(to.x, page.width)), y: Math.max(0, Math.min(to.y, page.height)) };
|
||||
}
|
||||
const limit = Math.min(1,
|
||||
dx ? (dx > 0 ? page.width - from.x : -from.x) / dx : 1,
|
||||
dy ? (dy > 0 ? page.height - from.y : -from.y) / dy : 1);
|
||||
return { x: from.x + dx * limit, y: from.y + dy * limit };
|
||||
return { x: from.x + dx, y: from.y + dy };
|
||||
}
|
||||
|
||||
// reshapeBox moves line endpoints or resizes basic shapes without scaling their stroke.
|
||||
export function reshapeBox(item, dx, dy, handle, shift) {
|
||||
const box = item.geometry, page = item.page;
|
||||
const box = item.geometry;
|
||||
if (!dx && !dy && !shift) return { ...box };
|
||||
if (item.shape === "line") {
|
||||
const start = { x: box.x, y: box.y }, end = { x: box.x + box.width, y: box.y + box.height };
|
||||
const fixed = handle === "start" ? end : start, moving = handle === "start" ? start : end;
|
||||
const point = constrainedPoint(fixed, { x: moving.x + dx, y: moving.y + dy }, page, "line", shift);
|
||||
const point = constrainedPoint(fixed, { x: moving.x + dx, y: moving.y + dy }, "line", shift);
|
||||
const from = handle === "start" ? point : fixed, to = handle === "start" ? fixed : point;
|
||||
return from.x === to.x && from.y === to.y ? null
|
||||
: { x: from.x, y: from.y, width: to.x - from.x, height: to.y - from.y };
|
||||
}
|
||||
if (shift && handle.length === 2) {
|
||||
const change = objectTransform(box, page, dx, dy, handle);
|
||||
const change = objectTransform(box, dx, dy, handle);
|
||||
return { x: box.x + change.x, y: box.y + change.y, width: box.width * change.scale, height: box.height * change.scale };
|
||||
}
|
||||
let x = box.x, y = box.y, right = x + box.width, bottom = y + box.height;
|
||||
const minWidth = Math.min(1, box.width), minHeight = Math.min(1, box.height);
|
||||
if (handle.includes("w")) x = Math.max(0, Math.min(x + dx, right - minWidth));
|
||||
if (handle.includes("e")) right = Math.min(page.width, Math.max(right + dx, x + minWidth));
|
||||
if (handle.includes("n")) y = Math.max(0, Math.min(y + dy, bottom - minHeight));
|
||||
if (handle.includes("s")) bottom = Math.min(page.height, Math.max(bottom + dy, y + minHeight));
|
||||
if (handle.includes("w")) x = Math.min(x + dx, right - minWidth);
|
||||
if (handle.includes("e")) right = Math.max(right + dx, x + minWidth);
|
||||
if (handle.includes("n")) y = Math.min(y + dy, bottom - minHeight);
|
||||
if (handle.includes("s")) bottom = Math.max(bottom + dy, y + minHeight);
|
||||
return { x, y, width: right - x, height: bottom - y };
|
||||
}
|
||||
|
||||
@@ -144,7 +129,9 @@ export class CanvasEditor {
|
||||
}
|
||||
|
||||
mount(index, page, surface) {
|
||||
const selected = this.items().filter(item => item.index === index).map(item => item.id);
|
||||
const pending = this.pendingSelection?.index === index;
|
||||
const selected = pending ? this.pendingSelection.ids || page.objects.slice(-1).map(item => item.id)
|
||||
: this.items().filter(item => item.index === index).map(item => item.id);
|
||||
const selection = [];
|
||||
this.pages.set(surface, { index, width: page.width, height: page.height });
|
||||
const layer = document.createElement("div");
|
||||
@@ -178,10 +165,7 @@ export class CanvasEditor {
|
||||
}
|
||||
surface.append(layer);
|
||||
if (selected.length) this.setSelection(selection);
|
||||
if (this.selectLast && layer.lastElementChild) {
|
||||
this.selectLast = false;
|
||||
this.select(this.nodes.get(layer.lastElementChild));
|
||||
}
|
||||
if (pending) this.pendingSelection = null;
|
||||
}
|
||||
|
||||
place(item, change = { x: 0, y: 0, scale: 1 }) {
|
||||
@@ -249,7 +233,7 @@ export class CanvasEditor {
|
||||
clear() {
|
||||
this.closeText();
|
||||
this.cancel();
|
||||
this.selectLast = false;
|
||||
this.pendingSelection = null;
|
||||
this.select(null);
|
||||
}
|
||||
|
||||
@@ -319,7 +303,7 @@ export class CanvasEditor {
|
||||
const from = pagePoint(drag.clientX, drag.clientY, drag.rect, drag.item.page, drag.rotation);
|
||||
const to = pagePoint(event.clientX, event.clientY, drag.rect, drag.item.page, drag.rotation);
|
||||
if (drag.item.type === "TextObject" && (drag.corner === "w" || drag.corner === "e")) {
|
||||
drag.box = reshapeBox({ geometry: drag.item, page: drag.item.page }, to.x - from.x, 0, drag.corner, false);
|
||||
drag.box = reshapeBox({ geometry: drag.item }, to.x - from.x, 0, drag.corner, false);
|
||||
Object.assign(drag.item.node.style, { left: `${drag.box.x * PX_PER_MM}px`, width: `${drag.box.width * PX_PER_MM}px` });
|
||||
return;
|
||||
}
|
||||
@@ -329,7 +313,7 @@ export class CanvasEditor {
|
||||
if (drag.node) paintShape(drag.node, drag.item.shape, drag.box || drag.item.geometry);
|
||||
return;
|
||||
}
|
||||
drag.change = objectTransform(drag.item, drag.item.page, 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);
|
||||
}
|
||||
|
||||
@@ -421,7 +405,7 @@ export class CanvasEditor {
|
||||
// moveMarquee includes intersecting objects while preserving an additive selection.
|
||||
moveMarquee(event, drag) {
|
||||
const from = pagePoint(drag.clientX, drag.clientY, drag.rect, drag.page, drag.rotation);
|
||||
const to = constrainedPoint(from, pagePoint(event.clientX, event.clientY, drag.rect, drag.page, drag.rotation), drag.page, "rectangle", false);
|
||||
const to = pagePoint(event.clientX, event.clientY, drag.rect, drag.page, drag.rotation);
|
||||
const box = { x: Math.min(from.x, to.x), y: Math.min(from.y, to.y), width: Math.abs(to.x-from.x), height: Math.abs(to.y-from.y) };
|
||||
paintShape(drag.node, "rectangle", box);
|
||||
if (Math.hypot(event.clientX-drag.clientX, event.clientY-drag.clientY) < 3) return;
|
||||
@@ -436,7 +420,7 @@ export class CanvasEditor {
|
||||
}
|
||||
|
||||
startShape(event) {
|
||||
const surface = event.target.closest(".page-surface");
|
||||
const surface = event.target.closest(".page-surface") || (this.tool !== "text" && this.nearestSurface(event));
|
||||
const page = this.pages.get(surface);
|
||||
if (!page) {
|
||||
return;
|
||||
@@ -455,6 +439,22 @@ export class CanvasEditor {
|
||||
this.viewer.setPointerCapture(event.pointerId);
|
||||
}
|
||||
|
||||
// nearestSurface assigns a pasteboard gesture to one mounted page for its entire duration.
|
||||
nearestSurface(event) {
|
||||
let nearest = null, distance = Infinity;
|
||||
for (const surface of this.viewer.querySelectorAll(".page-surface")) {
|
||||
if (!this.pages.has(surface)) continue;
|
||||
const rect = surface.getBoundingClientRect();
|
||||
const next = Math.hypot(Math.max(rect.left - event.clientX, 0, event.clientX - rect.right),
|
||||
Math.max(rect.top - event.clientY, 0, event.clientY - rect.bottom));
|
||||
if (next < distance) {
|
||||
nearest = surface;
|
||||
distance = next;
|
||||
}
|
||||
}
|
||||
return nearest;
|
||||
}
|
||||
|
||||
// createPreview provides a temporary SVG overlay without rerendering the page during a drag.
|
||||
createPreview(surface, page, shape, style) {
|
||||
const preview = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
||||
@@ -472,10 +472,10 @@ export class CanvasEditor {
|
||||
moveShape(event, drag) {
|
||||
const point = (x, y) => {
|
||||
const p = pagePoint(x, y, drag.rect, drag.page, drag.rotation);
|
||||
return { x: Math.max(0, Math.min(p.x, drag.page.width)), y: Math.max(0, Math.min(p.y, drag.page.height)) };
|
||||
return drag.shape === "text" ? { x: Math.max(0, Math.min(p.x, drag.page.width)), y: Math.max(0, Math.min(p.y, drag.page.height)) } : p;
|
||||
};
|
||||
const from = point(drag.clientX, drag.clientY);
|
||||
const to = constrainedPoint(from, pagePoint(event.clientX, event.clientY, drag.rect, drag.page, drag.rotation), drag.page, drag.shape, event.shiftKey && drag.shape !== "text");
|
||||
const to = constrainedPoint(from, point(event.clientX, event.clientY), drag.shape, event.shiftKey && drag.shape !== "text");
|
||||
const x = Math.min(from.x, to.x), y = Math.min(from.y, to.y);
|
||||
const width = Math.abs(to.x - from.x), height = drag.shape === "text" ? Math.max(5, Math.abs(to.y - from.y)) : Math.abs(to.y - from.y);
|
||||
const valid = Math.hypot(event.clientX - drag.clientX, event.clientY - drag.clientY) >= 3
|
||||
@@ -531,11 +531,7 @@ export class CanvasEditor {
|
||||
const [x, y] = directions[event.key];
|
||||
const [dx, dy] = [[x, y], [y, -x], [-x, -y], [-y, x]][this.options.rotation() / 90];
|
||||
const step = event.shiftKey ? 10 : 1;
|
||||
const change = objectTransform(this.selected, this.selected.page,
|
||||
dx * step, dy * step);
|
||||
if (change.x || change.y) {
|
||||
this.options.onTransform(this.selected, change);
|
||||
}
|
||||
this.options.onTransform(this.selected, { x: dx * step, y: dy * step, scale: 1 });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user