mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-30 01:27:26 +08:00
This commit is contained in:
1 parent
06b5df9173
commit
697a059162
18 files changed
+694
-130
No files matched your search
@@ -183,6 +183,12 @@
|
||||
<form id="objectStyleForm">
|
||||
<div class="form-row"><span>组合</span><div class="form-format"><button id="groupObjectsButton" class="button" type="button">组合</button><button id="ungroupObjectButton" class="button" type="button">解组</button></div></div>
|
||||
<div class="form-row"><span>格式</span><div class="form-format"><button id="copyStyleButton" class="button" type="button" aria-label="复制格式">复制</button><button id="pasteStyleButton" class="button" type="button" aria-label="粘贴格式">粘贴</button></div></div>
|
||||
<details id="objectLinkFields" hidden>
|
||||
<summary>链接</summary>
|
||||
<div class="form-row"><label for="objectLinkKind">目标</label><select id="objectLinkKind"><option value="none">不设</option><option value="uri">网址</option><option value="page">页面</option><option value="keep">原有</option></select></div>
|
||||
<div class="form-row" id="objectLinkAddressRow" hidden><label for="objectLinkAddress">地址</label><input id="objectLinkAddress" type="text"></div>
|
||||
<div class="form-row" id="objectLinkPageRow" hidden><label for="objectLinkPage">页码</label><input id="objectLinkPage" type="number" min="1" step="1"></div>
|
||||
</details>
|
||||
<div class="form-row"><label for="objectOpacity">透明</label><div class="form-unit"><input id="objectOpacity" type="number" min="0" max="100" step="any" aria-label="透明度" autofocus><span>%</span></div></div>
|
||||
<details id="objectBorderFields" hidden>
|
||||
<summary>边框</summary>
|
||||
@@ -342,6 +348,14 @@
|
||||
<dialog id="objectPicker" class="form-dialog" aria-label="选取对象">
|
||||
<form method="dialog"><div id="objectPickerList" class="object-picker-list"></div><div class="form-actions"><button class="button" type="submit">关闭</button></div></form>
|
||||
</dialog>
|
||||
<dialog id="sourceTextPanel" class="form-dialog" aria-label="保留排版">
|
||||
<form id="sourceTextForm">
|
||||
<h2 class="form-title">保留排版</h2>
|
||||
<div class="form-row"><label for="sourceTextValue">内容</label><textarea id="sourceTextValue" rows="3" dir="auto" aria-label="文字内容" required></textarea></div>
|
||||
<div id="sourceTextPreview" aria-label="文字预览"></div>
|
||||
<div class="form-actions"><span id="sourceTextStatus" class="form-status" role="status"></span><button id="sourceTextCancel" class="button" type="button">取消</button><button class="button primary" type="submit">确定</button></div>
|
||||
</form>
|
||||
</dialog>
|
||||
<dialog id="annotationNote" class="form-dialog" aria-label="注解">
|
||||
<form id="annotationForm" method="dialog">
|
||||
<div id="annotationDetail" class="annotation-detail"></div>
|
||||
|
||||
@@ -692,6 +692,27 @@ button:not(:disabled):hover {
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
#sourceTextPanel {
|
||||
width: 440px;
|
||||
}
|
||||
|
||||
#sourceTextValue {
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
#sourceTextPreview {
|
||||
height: 160px;
|
||||
margin-top: 12px;
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#sourceTextPreview svg {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.security-dialog {
|
||||
width: 360px;
|
||||
}
|
||||
|
||||
+90
-3
@@ -284,10 +284,22 @@ const el = {
|
||||
objectHeight: document.querySelector("#objectHeight"),
|
||||
objectAspect: document.querySelector("#objectAspect"),
|
||||
objectStylePanel: document.querySelector("#objectStylePanel"),
|
||||
sourceTextPanel: document.querySelector("#sourceTextPanel"),
|
||||
sourceTextForm: document.querySelector("#sourceTextForm"),
|
||||
sourceTextValue: document.querySelector("#sourceTextValue"),
|
||||
sourceTextPreview: document.querySelector("#sourceTextPreview"),
|
||||
sourceTextStatus: document.querySelector("#sourceTextStatus"),
|
||||
sourceTextCancel: document.querySelector("#sourceTextCancel"),
|
||||
objectStyleForm: document.querySelector("#objectStyleForm"),
|
||||
groupObjectsButton: document.querySelector("#groupObjectsButton"),
|
||||
ungroupObjectButton: document.querySelector("#ungroupObjectButton"),
|
||||
objectOpacity: document.querySelector("#objectOpacity"),
|
||||
objectLinkFields: document.querySelector("#objectLinkFields"),
|
||||
objectLinkKind: document.querySelector("#objectLinkKind"),
|
||||
objectLinkAddress: document.querySelector("#objectLinkAddress"),
|
||||
objectLinkPage: document.querySelector("#objectLinkPage"),
|
||||
objectLinkAddressRow: document.querySelector("#objectLinkAddressRow"),
|
||||
objectLinkPageRow: document.querySelector("#objectLinkPageRow"),
|
||||
objectGradientFields: document.querySelector("#objectGradientFields"),
|
||||
gradientTarget: document.querySelector("#gradientTarget"),
|
||||
objectBorderFields: document.querySelector("#objectBorderFields"),
|
||||
@@ -997,6 +1009,23 @@ el.objectBoundsForm.addEventListener("submit", async event => {
|
||||
if (saved) el.objectBoundsPanel.close();
|
||||
});
|
||||
el.objectStyleCancel.addEventListener("click", () => el.objectStylePanel.close());
|
||||
el.sourceTextCancel.addEventListener("click", () => el.sourceTextPanel.close());
|
||||
el.sourceTextValue.addEventListener("input", () => {
|
||||
clearTimeout(state.sourceTextTimer);
|
||||
state.sourceTextTimer = setTimeout(previewPositionedText, 150);
|
||||
});
|
||||
el.sourceTextPanel.addEventListener("close", () => {
|
||||
clearTimeout(state.sourceTextTimer);
|
||||
state.sourceText = null;
|
||||
el.sourceTextPreview.replaceChildren();
|
||||
});
|
||||
el.sourceTextForm.addEventListener("submit", async event => {
|
||||
event.preventDefault();
|
||||
const item = state.sourceText?.item;
|
||||
if (!item) return;
|
||||
if (await changeDocument("ofdgoUpdateText", item, el.sourceTextValue.value, null, item.size, null)) el.sourceTextPanel.close();
|
||||
});
|
||||
el.objectLinkKind.addEventListener("change", updateObjectLinkFields);
|
||||
el.gradientKind.addEventListener("change", () => {
|
||||
el.gradientControls.hidden = el.gradientKind.value === "keep";
|
||||
el.gradientAngle.disabled = el.gradientKind.value !== "linear";
|
||||
@@ -1410,7 +1439,7 @@ function handleKeyDown(event) {
|
||||
|
||||
function formDialogOpen() {
|
||||
return el.exportPanel.open || el.createPanel.open || el.insertPanel.open || el.pagePanel.open || el.paragraphPanel.open || el.importPanel.open || el.infoPanel.open
|
||||
|| el.batchPagesPanel.open || el.objectStylePanel.open || el.outlinePanel.open || el.objectBoundsPanel.open || el.annotationNote.open || el.annotationCreate.open || el.objectPicker.open;
|
||||
|| el.batchPagesPanel.open || el.objectStylePanel.open || el.sourceTextPanel.open || el.outlinePanel.open || el.objectBoundsPanel.open || el.annotationNote.open || el.annotationCreate.open || el.objectPicker.open;
|
||||
}
|
||||
|
||||
function setObjectDimension(input, value) {
|
||||
@@ -1458,6 +1487,16 @@ function openObjectStyle() {
|
||||
el.copyStyleButton.disabled = items.length !== 1 || !canCopyStyle(items[0]);
|
||||
el.pasteStyleButton.disabled = !state.styleClipboard || !items.every(item => item.type === state.styleClipboard && canCopyStyle(item));
|
||||
const shared = (key, fallback) => items.every(item => (item[key] ?? fallback) === (items[0][key] ?? fallback)) ? items[0][key] ?? fallback : null;
|
||||
el.objectLinkFields.hidden = items.length !== 1 || items[0].scoped;
|
||||
const link = items[0].link ? JSON.parse(items[0].link) : null;
|
||||
const targetPage = link?.Goto?.Dest ? state.doc.pages.findIndex(page => page.id === link.Goto.Dest.PageID) : -1;
|
||||
el.objectLinkKind.value = items[0].linkCount > 1 ? "keep" : link?.URI ? "uri" : targetPage >= 0 ? "page" : link ? "keep" : "none";
|
||||
el.objectLinkKind.disabled = items[0].linkCount > 1;
|
||||
el.objectLinkAddress.value = link?.URI?.URI || "";
|
||||
el.objectLinkPage.value = String(targetPage >= 0 ? targetPage + 1 : 1);
|
||||
el.objectLinkPage.max = String(state.doc?.pages.length || 1);
|
||||
state.objectLinkOriginal = { kind: el.objectLinkKind.value, address: el.objectLinkAddress.value, page: el.objectLinkPage.value, link };
|
||||
updateObjectLinkFields();
|
||||
el.objectBorderFields.hidden = items.length !== 1 || items[0].scoped || items[0].type !== "ImageObject";
|
||||
const border = items[0].borderStyle ? JSON.parse(items[0].borderStyle) : null;
|
||||
el.borderEnabled.checked = Boolean(border);
|
||||
@@ -1517,6 +1556,14 @@ function openObjectStyle() {
|
||||
|
||||
function readObjectStyle() {
|
||||
const style = {}, changed = key => el[key].value !== state.styleOriginal[key];
|
||||
if (!el.objectLinkFields.hidden) {
|
||||
const original = state.objectLinkOriginal, kind = el.objectLinkKind.value;
|
||||
if (kind !== "keep" && (kind !== original.kind || kind === "uri" && el.objectLinkAddress.value !== original.address || kind === "page" && el.objectLinkPage.value !== original.page)) {
|
||||
style.link = kind === "none" ? null : JSON.stringify(kind === "uri"
|
||||
? { URI: { ...original.link?.URI, URI: el.objectLinkAddress.value.trim() } }
|
||||
: { Dest: { ...(original.link?.Goto?.Dest || { Type: "Fit" }), PageID: state.doc.pages[Number(el.objectLinkPage.value) - 1].id } });
|
||||
}
|
||||
}
|
||||
if (!el.objectBorderFields.hidden) {
|
||||
const border = {};
|
||||
if (el.borderEnabled.checked !== state.borderOriginal.Enabled) border.Enabled = el.borderEnabled.checked;
|
||||
@@ -1587,6 +1634,16 @@ function readObjectStyle() {
|
||||
return style;
|
||||
}
|
||||
|
||||
function updateObjectLinkFields() {
|
||||
const kind = el.objectLinkKind.value, visible = !el.objectLinkFields.hidden;
|
||||
el.objectLinkAddressRow.hidden = !visible || kind !== "uri";
|
||||
el.objectLinkPageRow.hidden = !visible || kind !== "page";
|
||||
el.objectLinkAddress.disabled = el.objectLinkAddressRow.hidden;
|
||||
el.objectLinkPage.disabled = el.objectLinkPageRow.hidden;
|
||||
el.objectLinkAddress.required = visible && kind === "uri";
|
||||
el.objectLinkPage.required = visible && kind === "page";
|
||||
}
|
||||
|
||||
function gradientForm() {
|
||||
return {
|
||||
kind: el.gradientKind.value, angle: el.gradientAngle.value,
|
||||
@@ -2084,7 +2141,18 @@ async function editCanvasObject(item) {
|
||||
return;
|
||||
}
|
||||
if (item.type === "TextObject") {
|
||||
if (!canEditObject(item, "textContent")) return;
|
||||
if (!canEditObject(item, "textContent")) {
|
||||
if (!item.scoped && canEditObject(item, "rewriteText")) {
|
||||
state.sourceText = { item, openSeq: state.openSeq };
|
||||
el.sourceTextValue.value = item.text;
|
||||
el.sourceTextStatus.textContent = "";
|
||||
el.sourceTextPreview.replaceChildren();
|
||||
el.sourceTextPanel.showModal();
|
||||
el.sourceTextValue.focus();
|
||||
await previewPositionedText();
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else if (!canEditObject(item, item.type === "ImageObject" ? "replaceImage" : "update")) return;
|
||||
if (item.type === "PathObject") {
|
||||
el.shapeWidth.focus();
|
||||
@@ -2114,6 +2182,22 @@ async function editCanvasObject(item) {
|
||||
}
|
||||
}
|
||||
|
||||
async function previewPositionedText() {
|
||||
const request = state.sourceText, value = el.sourceTextValue.value;
|
||||
if (!request || !el.sourceTextPanel.open) return;
|
||||
try {
|
||||
const svg = await callWASM("ofdgoPreviewPositionedText", request.item.index, request.item.id, value);
|
||||
if (state.sourceText !== request || request.openSeq !== state.openSeq || el.sourceTextValue.value !== value) return;
|
||||
el.sourceTextPreview.replaceChildren(parseSVG(svg));
|
||||
el.sourceTextStatus.textContent = "";
|
||||
} catch (error) {
|
||||
if (state.sourceText === request && request.openSeq === state.openSeq && el.sourceTextValue.value === value) {
|
||||
el.sourceTextPreview.replaceChildren();
|
||||
el.sourceTextStatus.textContent = error.message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function mountEditorObjects(index, page, surface) {
|
||||
const scope = state.composite?.index === index ? state.composite : null;
|
||||
canvasEditor.mount(index, { ...page, objects: scope ? scope.objects : page.objects.filter(item => state.renderAnnotations || item.type !== "Annotation") }, surface);
|
||||
@@ -2714,6 +2798,8 @@ async function changeDocument(name, item, ...args) {
|
||||
el.batchPagesStatus.textContent = err.message;
|
||||
} else if (el.objectStylePanel.open) {
|
||||
el.objectStyleStatus.textContent = err.message;
|
||||
} else if (el.sourceTextPanel.open) {
|
||||
el.sourceTextStatus.textContent = err.message;
|
||||
} else if (el.objectBoundsPanel.open) {
|
||||
el.objectBoundsStatus.textContent = err.message;
|
||||
} else if (el.outlinePanel.open) {
|
||||
@@ -3275,6 +3361,7 @@ async function openOFD(file) {
|
||||
el.importPanel.close();
|
||||
el.batchPagesPanel.close();
|
||||
el.objectStylePanel.close();
|
||||
el.sourceTextPanel.close();
|
||||
el.objectBoundsPanel.close();
|
||||
el.outlinePanel.close();
|
||||
updateControls();
|
||||
@@ -7077,7 +7164,7 @@ function updateObjectControls(item, reset = false) {
|
||||
el.resetCropButton.disabled = canEditObject(item, "resetCrop") ? disabled : el.cropImageButton.disabled || !cropping && (item.scoped ? !item.cropped : !item.imageBounds || ["x", "y", "width", "height"].every(key => Math.abs(item[key] - item.imageBounds[key]) < 1e-9));
|
||||
el.objectDistribute.disabled = el.objectAlign.disabled || !item.items || item.items.length < 3;
|
||||
el.editObjectButton.disabled = disabled || Boolean(item.items) || !canEditObject(item, "enter") && (item.type === "PathObject"
|
||||
|| !canEditObject(item, item.type === "TextObject" ? "textContent" : item.type === "ImageObject" ? "replaceImage" : "update"));
|
||||
|| !(item.type === "TextObject" && !item.scoped && canEditObject(item, "rewriteText")) && !canEditObject(item, item.type === "TextObject" ? "textContent" : item.type === "ImageObject" ? "replaceImage" : "update"));
|
||||
el.editObjectButton.textContent = canEditObject(item, "enter") ? "进入" : item?.type === "ImageObject" ? "替换" : "修改";
|
||||
el.editObjectButton.title = canEditObject(item, "enter") ? item?.type === "Annotation" ? "进入注解" : "进入组合" : item?.type === "ImageObject" ? "替换图片" : item?.type === "Annotation" ? "修改注解" : "修改对象";
|
||||
el.editObjectButton.setAttribute("aria-label", el.editObjectButton.title);
|
||||
|
||||
@@ -1186,10 +1186,12 @@ export class CanvasEditor {
|
||||
input.setAttribute("aria-label", "编辑文字");
|
||||
input.setAttribute("role", "textbox");
|
||||
input.setAttribute("aria-multiline", "true");
|
||||
input.setAttribute("dir", "auto");
|
||||
input.contentEditable = "true";
|
||||
input.spellcheck = false;
|
||||
for (const text of item.text.split("\n")) {
|
||||
const paragraph = document.createElement("div");
|
||||
paragraph.setAttribute("dir", "auto");
|
||||
if (text) paragraph.textContent = text;
|
||||
else paragraph.append(document.createElement("br"));
|
||||
input.append(paragraph);
|
||||
@@ -1206,6 +1208,7 @@ export class CanvasEditor {
|
||||
minHeight: autoSize ? "0px" : `${frame.height * PX_PER_MM + 4}px`,
|
||||
transform: `matrix(${a},${b},${c},${d},${(e + a * left) * PX_PER_MM},${(f + b * left) * PX_PER_MM})`,
|
||||
whiteSpace: item.wrap ? "pre-wrap" : "pre",
|
||||
unicodeBidi: "plaintext",
|
||||
fontFamily: `"${face.family}"`, fontSize: `${item.size * PX_PER_MM}px`,
|
||||
lineHeight: item.paragraphHeight || item.lineHeight ? `${(item.paragraphHeight || item.lineHeight) * PX_PER_MM}px` : "normal", color: item.color,
|
||||
textAlign: item.align || "left",
|
||||
|
||||
Reference in new issue
Block a user