mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 16:44:23 +08:00
This commit is contained in:
1 parent
f924f008a3
commit
299ff8d002
13 files changed
+1418
-71
No files matched your search
+21
-2
@@ -123,7 +123,7 @@
|
||||
<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>
|
||||
<button id="textWrap" class="button" type="button" aria-label="自动折行" aria-pressed="false" disabled>折行</button>
|
||||
<label class="editor-field"><span>字距</span><span class="editor-number"><input id="textSpacing" type="number" step="any" placeholder="0" aria-label="字距" title="字距" disabled><span>pt</span></span></label>
|
||||
<span>段落</span>
|
||||
<button id="paragraphButton" class="editor-group-action" type="button" title="段落缩进" aria-label="段落缩进" aria-haspopup="dialog" aria-controls="paragraphPanel" disabled>段落</button>
|
||||
</div>
|
||||
<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>
|
||||
@@ -155,7 +155,7 @@
|
||||
<span>变换</span>
|
||||
</div>
|
||||
<div class="editor-group">
|
||||
<button id="addPageButton" class="button" type="button" aria-label="新增页面">新增</button>
|
||||
<select id="addPageButton" aria-label="新增页面" title="新增页面"><option value="" hidden>新增</option><option value="blank">空白</option><option value="import">插页</option></select>
|
||||
<button id="copyPageButton" class="button" type="button" aria-label="复制页面">复制</button>
|
||||
<button id="deletePageButton" class="button" type="button" aria-label="删除页面">删除</button>
|
||||
<button id="movePagePrevButton" class="button" type="button" aria-label="页面前移">前移</button>
|
||||
@@ -187,6 +187,25 @@
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<dialog id="importPanel" class="form-dialog" aria-label="插入页面">
|
||||
<form id="importForm">
|
||||
<div class="form-row"><label for="importFile">文档</label><input id="importFile" type="file" accept=".ofd" required autofocus></div>
|
||||
<div class="form-row"><label for="importRange">范围</label><select id="importRange"><option value="all">全部</option><option value="custom">指定</option></select></div>
|
||||
<div id="importPagesRow" class="form-row" hidden><label for="importPages">页码</label><input id="importPages" type="text" autocomplete="off" spellcheck="false" disabled></div>
|
||||
<div class="form-row"><label for="importPosition">位置</label><select id="importPosition"><option value="before">页前</option><option value="after" selected>页后</option><option value="first">首部</option><option value="last">末尾</option></select></div>
|
||||
<div class="form-actions"><div id="importStatus" class="form-status" role="status"></div><button id="importCancel" class="button" type="button">取消</button><button id="importSubmit" class="button" type="submit" disabled>插入</button></div>
|
||||
</form>
|
||||
</dialog>
|
||||
<dialog id="paragraphPanel" class="form-dialog" aria-label="段落缩进">
|
||||
<form id="paragraphForm">
|
||||
<div class="form-row"><label for="firstLineIndent">首行</label><div class="form-unit"><input id="firstLineIndent" type="number" step="any" required autofocus><span>mm</span></div></div>
|
||||
<div class="form-pair">
|
||||
<div class="form-row"><label for="leftIndent">左侧</label><div class="form-unit"><input id="leftIndent" type="number" step="any" required><span>mm</span></div></div>
|
||||
<div class="form-row"><label for="rightIndent">右侧</label><div class="form-unit"><input id="rightIndent" type="number" step="any" required><span>mm</span></div></div>
|
||||
</div>
|
||||
<div class="form-actions"><div id="paragraphStatus" class="form-status" role="status"></div><button id="paragraphCancel" class="button" type="button">取消</button><button class="button" type="submit">确定</button></div>
|
||||
</form>
|
||||
</dialog>
|
||||
<dialog id="pagePanel" class="form-dialog" aria-label="页面设置">
|
||||
<form id="pageForm">
|
||||
<div class="form-row">
|
||||
|
||||
+22
-1
@@ -334,7 +334,8 @@ a:focus-visible {
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
.editor-group > span {
|
||||
.editor-group > span,
|
||||
.editor-group-action {
|
||||
grid-column: 1 / -1;
|
||||
grid-row: 3;
|
||||
align-self: end;
|
||||
@@ -343,6 +344,26 @@ a:focus-visible {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.editor-group-action {
|
||||
justify-self: center;
|
||||
padding: 0 8px;
|
||||
height: 16px;
|
||||
min-height: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
line-height: 16px;
|
||||
text-decoration: underline dotted;
|
||||
text-underline-offset: 3px;
|
||||
}
|
||||
|
||||
.mixed-color::-webkit-color-swatch {
|
||||
background: repeating-linear-gradient(135deg, #fff 0 4px, #aeb7bc 4px 8px);
|
||||
}
|
||||
|
||||
.mixed-color::-moz-color-swatch {
|
||||
background: repeating-linear-gradient(135deg, #fff 0 4px, #aeb7bc 4px 8px);
|
||||
}
|
||||
|
||||
.app-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
+160
-23
@@ -1,4 +1,4 @@
|
||||
import { CanvasEditor, canEditObject, missingGlyphMessage, objectEditReason } from "./ofdgo_edit.js";
|
||||
import { CanvasEditor, canEditObject, missingGlyphMessage, objectEditReason, selectedText } from "./ofdgo_edit.js";
|
||||
import { FontManager, FontPicker } from "./ofdgo_font.js";
|
||||
|
||||
const MM_TO_PX = 96 / 25.4;
|
||||
@@ -38,6 +38,7 @@ const state = {
|
||||
editorInfo: null,
|
||||
savedRevision: null,
|
||||
insertObject: null,
|
||||
importPageCount: 0,
|
||||
objectClipboard: null,
|
||||
textFonts: [],
|
||||
textFontID: null,
|
||||
@@ -114,6 +115,24 @@ const el = {
|
||||
textFontToggle: document.querySelector("#textFontToggle"),
|
||||
textFontList: document.querySelector("#textFontList"),
|
||||
textFontAdd: document.querySelector("#textFontAdd"),
|
||||
paragraphButton: document.querySelector("#paragraphButton"),
|
||||
paragraphPanel: document.querySelector("#paragraphPanel"),
|
||||
paragraphForm: document.querySelector("#paragraphForm"),
|
||||
paragraphCancel: document.querySelector("#paragraphCancel"),
|
||||
paragraphStatus: document.querySelector("#paragraphStatus"),
|
||||
leftIndent: document.querySelector("#leftIndent"),
|
||||
rightIndent: document.querySelector("#rightIndent"),
|
||||
firstLineIndent: document.querySelector("#firstLineIndent"),
|
||||
importPanel: document.querySelector("#importPanel"),
|
||||
importForm: document.querySelector("#importForm"),
|
||||
importFile: document.querySelector("#importFile"),
|
||||
importRange: document.querySelector("#importRange"),
|
||||
importPagesRow: document.querySelector("#importPagesRow"),
|
||||
importPages: document.querySelector("#importPages"),
|
||||
importPosition: document.querySelector("#importPosition"),
|
||||
importStatus: document.querySelector("#importStatus"),
|
||||
importCancel: document.querySelector("#importCancel"),
|
||||
importSubmit: document.querySelector("#importSubmit"),
|
||||
textSize: document.querySelector("#textSize"),
|
||||
textColor: document.querySelector("#textColor"),
|
||||
drawLineButton: document.querySelector("#drawLineButton"),
|
||||
@@ -270,7 +289,7 @@ const canvasEditor = new CanvasEditor(el.viewerPanel, {
|
||||
onTransform: (item, change) => changeDocument(item.items ? "ofdgoTransformObjects" : "ofdgoTransformObject", item,
|
||||
change.x + item.x * (1 - change.scale), change.y + item.y * (1 - change.scale), change.scale),
|
||||
onReshape: (item, box) => changeDocument("ofdgoReshapeObject", item, box.x, box.y, box.width, box.height),
|
||||
onTextWidth: (item, box) => changeDocument("ofdgoLayoutText", item, box.x - item.x, box.width, true, item.align || "", item.paragraphHeight || 0, item.letterSpacing || 0),
|
||||
onTextWidth: (item, box) => changeDocument("ofdgoLayoutText", item, box.x - item.x, box.width, true, item.align || "", item.paragraphHeight || 0, item.letterSpacing || 0, ...textIndents(item)),
|
||||
onCrop: (item, box) => changeDocument("ofdgoCropImage", item, box.x, box.y, box.width, box.height),
|
||||
onCropChange: () => {
|
||||
updatePendingChanges();
|
||||
@@ -294,7 +313,7 @@ const canvasEditor = new CanvasEditor(el.viewerPanel, {
|
||||
box.x, box.y, box.width, box.height, style.fill, style.fillColor, style.stroke, style.strokeColor, style.lineWidth),
|
||||
onDrawText: beginCanvasText,
|
||||
onCommitText: (item, value, fontData) => item.draft
|
||||
? 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, item.letterSpacing)
|
||||
? 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, item.letterSpacing, ...textIndents(item))
|
||||
: changeDocument("ofdgoUpdateText", item, value, fontData || null, item.size, null),
|
||||
onTextChange: () => {
|
||||
const editing = canvasEditor.input;
|
||||
@@ -357,6 +376,28 @@ el.textColor.addEventListener("change", () => changeTextStyle(true));
|
||||
editorClick(el.textWrap, () => changeParagraph(!currentTextStyle().wrap));
|
||||
el.textAlign.addEventListener("change", () => changeParagraph());
|
||||
el.textLineHeight.addEventListener("change", () => changeParagraph());
|
||||
editorClick(el.paragraphButton, () => {
|
||||
const item = currentTextStyle();
|
||||
for (const key of ["leftIndent", "rightIndent", "firstLineIndent"]) el[key].value = item[key] || 0;
|
||||
el.paragraphStatus.textContent = "";
|
||||
el.paragraphPanel.showModal();
|
||||
});
|
||||
el.paragraphCancel.addEventListener("click", () => el.paragraphPanel.close());
|
||||
el.paragraphForm.addEventListener("submit", async event => {
|
||||
event.preventDefault();
|
||||
if (document.body.hasAttribute("aria-busy") || !el.paragraphForm.reportValidity()) return;
|
||||
const item = currentTextStyle();
|
||||
const indents = Object.fromEntries(["leftIndent", "rightIndent", "firstLineIndent"].map(key => [key, Number(el[key].value)]));
|
||||
if (item === state.textDefaults) {
|
||||
Object.assign(item, indents);
|
||||
} else {
|
||||
if (!confirmTextReflow(item)) return;
|
||||
const saved = await changeDocument("ofdgoLayoutText", item, null, null, Boolean(item.wrap), item.align || "left",
|
||||
item.paragraphHeight || 0, item.letterSpacing || 0, ...textIndents(indents));
|
||||
if (!saved) return;
|
||||
}
|
||||
el.paragraphPanel.close();
|
||||
});
|
||||
el.textSpacing.addEventListener("change", () => changeParagraph());
|
||||
el.shapeWidth.addEventListener("focus", () => { el.shapeWidth.defaultValue = el.shapeWidth.value; });
|
||||
for (const input of [el.textSize, el.shapeWidth, el.textLineHeight, el.textSpacing]) {
|
||||
@@ -455,10 +496,36 @@ for (const [button, tool] of [[el.drawLineButton, "line"], [el.drawRectangleButt
|
||||
for (const input of [el.shapeFill, el.shapeFillColor, el.shapeStroke, el.shapeStrokeColor, el.shapeWidth]) {
|
||||
input.addEventListener("change", changeShapeStyle);
|
||||
}
|
||||
editorClick(el.addPageButton, () => {
|
||||
el.addPageButton.addEventListener("change", async () => {
|
||||
const action = el.addPageButton.value;
|
||||
el.addPageButton.value = "";
|
||||
if (canvasEditor.input && !await canvasEditor.commitText()) return;
|
||||
if (canvasEditor.crop && !await canvasEditor.commitCrop()) return;
|
||||
if (action === "import") { openImportPanel(); return; }
|
||||
const page = currentPageInfo();
|
||||
return changeDocument("ofdgoChangePage", null, "add", state.pageIndex, page.width, page.height);
|
||||
});
|
||||
el.importCancel.addEventListener("click", () => el.importPanel.close());
|
||||
el.importPanel.addEventListener("close", () => {
|
||||
state.importPageCount = 0;
|
||||
el.importForm.reset();
|
||||
if (state.ready) callWASM("ofdgoLoadImport", null).catch(err => showError(err, false));
|
||||
});
|
||||
el.importFile.addEventListener("change", loadImportFile);
|
||||
el.importRange.addEventListener("change", () => {
|
||||
const custom = el.importRange.value === "custom";
|
||||
el.importPagesRow.hidden = !custom;
|
||||
el.importPages.disabled = !custom;
|
||||
el.importPages.required = custom;
|
||||
if (custom) el.importPages.focus();
|
||||
});
|
||||
el.importForm.addEventListener("submit", async event => {
|
||||
event.preventDefault();
|
||||
if (document.body.hasAttribute("aria-busy") || !state.importPageCount || !el.importForm.reportValidity()) return;
|
||||
const position = { before:state.pageIndex, after:state.pageIndex+1, first:0, last:state.doc.pageCount }[el.importPosition.value];
|
||||
const saved = await changeDocument("ofdgoImportPages", null, el.importRange.value === "custom" ? el.importPages.value : "", position);
|
||||
if (saved) el.importPanel.close();
|
||||
});
|
||||
editorClick(el.copyPageButton, () => changeDocument("ofdgoChangePage", null, "copy", state.pageIndex));
|
||||
editorClick(el.deletePageButton, () => changeDocument("ofdgoChangePage", null, "delete", state.pageIndex));
|
||||
editorClick(el.movePagePrevButton, () => changeDocument("ofdgoChangePage", null, "move", state.pageIndex, state.pageIndex - 1));
|
||||
@@ -712,7 +779,45 @@ function handleKeyDown(event) {
|
||||
}
|
||||
|
||||
function formDialogOpen() {
|
||||
return el.exportPanel.open || el.createPanel.open || el.insertPanel.open || el.pagePanel.open;
|
||||
return el.exportPanel.open || el.createPanel.open || el.insertPanel.open || el.pagePanel.open || el.paragraphPanel.open || el.importPanel.open;
|
||||
}
|
||||
|
||||
function openImportPanel() {
|
||||
if (!state.editing || document.body.hasAttribute("aria-busy")) return;
|
||||
state.importPageCount = 0;
|
||||
el.importForm.reset();
|
||||
el.importStatus.textContent = "";
|
||||
el.importSubmit.disabled = true;
|
||||
el.importPagesRow.hidden = true;
|
||||
el.importPages.disabled = true;
|
||||
el.importPages.required = false;
|
||||
el.importPanel.showModal();
|
||||
}
|
||||
|
||||
async function loadImportFile() {
|
||||
const file = el.importFile.files[0];
|
||||
const openSeq = state.openSeq;
|
||||
state.importPageCount = 0;
|
||||
el.importSubmit.disabled = true;
|
||||
el.importStatus.textContent = "";
|
||||
setBusy(true);
|
||||
try {
|
||||
const data = file ? new Uint8Array(await file.arrayBuffer()) : null;
|
||||
if (openSeq !== state.openSeq) return;
|
||||
const info = await callWASM("ofdgoLoadImport", data);
|
||||
if (openSeq !== state.openSeq || !el.importPanel.open) {
|
||||
await callWASM("ofdgoLoadImport", null);
|
||||
return;
|
||||
}
|
||||
state.importPageCount = info?.pageCount || 0;
|
||||
el.importPages.title = state.importPageCount ? `页码 1-${state.importPageCount}` : "页码";
|
||||
el.importStatus.textContent = info?.signed ? "保留签章外观,原签名不再有效" : "";
|
||||
el.importSubmit.disabled = !state.importPageCount;
|
||||
} catch (err) {
|
||||
if (openSeq === state.openSeq && el.importPanel.open) el.importStatus.textContent = err.message;
|
||||
} finally {
|
||||
if (openSeq === state.openSeq) setBusy(false);
|
||||
}
|
||||
}
|
||||
|
||||
function openPagePanel() {
|
||||
@@ -943,7 +1048,11 @@ function openInsertPanel(item = null) {
|
||||
}
|
||||
|
||||
function currentTextStyle() {
|
||||
return canvasEditor.selected?.type === "TextObject" ? canvasEditor.selected : state.textDefaults;
|
||||
return selectedText(canvasEditor.selected) || state.textDefaults;
|
||||
}
|
||||
|
||||
function textIndents(item) {
|
||||
return [item.leftIndent || 0, item.rightIndent || 0, item.firstLineIndent || 0];
|
||||
}
|
||||
|
||||
async function toggleTextTool() {
|
||||
@@ -958,10 +1067,11 @@ async function toggleTextTool() {
|
||||
updateTextFonts(canvasEditor.selected, true);
|
||||
}
|
||||
if (openSeq !== state.openSeq) return;
|
||||
const style = canEditObject(canvasEditor.selected, "layoutKnown") ? currentTextStyle() : state.textDefaults;
|
||||
const style = !canvasEditor.selected?.items && canEditObject(canvasEditor.selected, "layoutKnown") ? currentTextStyle() : state.textDefaults;
|
||||
const font = state.textFonts[Number(fontPicker.value)];
|
||||
state.textDefaults = { type: "TextObject", size: style.size, color: style.color, wrap: style.wrap,
|
||||
align: style.align || "left", paragraphHeight: style.paragraphHeight || 0, letterSpacing: style.letterSpacing || 0,
|
||||
leftIndent: style.leftIndent || 0, rightIndent: style.rightIndent || 0, firstLineIndent: style.firstLineIndent || 0,
|
||||
fontChoice: font && !font.disabled ? font : state.textDefaults.fontChoice };
|
||||
state.selectObjects = true;
|
||||
setPan(false);
|
||||
@@ -1045,22 +1155,25 @@ async function readTextFont(font) {
|
||||
}
|
||||
|
||||
function updateTextFonts(item, refresh = false) {
|
||||
item = selectedText(item);
|
||||
const id = item?.type === "TextObject" && !item.draft ? item.font : null;
|
||||
if (refresh || state.textFontID !== id) {
|
||||
state.textFontID = id;
|
||||
const key = item?.items ? `selection:${item.items.map(member => member.id).join(",")}:${id || ""}` : id;
|
||||
if (refresh || state.textFontID !== key) {
|
||||
state.textFontID = key;
|
||||
state.textFonts = editorFonts(id ? item : null);
|
||||
const selected = canvasEditor.input?.fontChoice || (id ? state.textFonts[0] : state.textDefaults.fontChoice);
|
||||
if (selected && (canvasEditor.input || selected.embedded)
|
||||
&& !state.textFonts.some(font => (font.id || font.postscriptName) === (selected.id || selected.postscriptName))) {
|
||||
state.textFonts.unshift(selected);
|
||||
}
|
||||
fontPicker.setFonts(state.textFonts, selected || state.textFonts[0]);
|
||||
if (!id) state.textDefaults.fontChoice = state.textFonts[Number(fontPicker.value)];
|
||||
fontPicker.setFonts(state.textFonts, item?.items && !id ? null : selected || state.textFonts[0]);
|
||||
el.textFont.placeholder = item?.items && !id ? "混合" : "";
|
||||
if (!id && !item?.items) state.textDefaults.fontChoice = state.textFonts[Number(fontPicker.value)];
|
||||
}
|
||||
}
|
||||
|
||||
async function changeTextFont() {
|
||||
const item = canvasEditor.selected;
|
||||
const item = selectedText(canvasEditor.selected);
|
||||
const font = state.textFonts[Number(fontPicker.value)];
|
||||
if (!state.ready || document.body.hasAttribute("aria-busy") || !font || font.disabled) return;
|
||||
const editing = canvasEditor.input;
|
||||
@@ -1069,6 +1182,7 @@ async function changeTextFont() {
|
||||
setBusy(true);
|
||||
try {
|
||||
const data = await readTextFont(font);
|
||||
await callWASM("ofdgoCheckTextFont", data, editing.input.value);
|
||||
const face = new FontFace("ofdgo-edit-font", data);
|
||||
await face.load();
|
||||
if (openSeq !== state.openSeq || canvasEditor.input !== editing) return;
|
||||
@@ -1084,8 +1198,8 @@ async function changeTextFont() {
|
||||
if (openSeq === state.openSeq) setBusy(false);
|
||||
}
|
||||
} else if (item?.type === "TextObject") {
|
||||
if (!font.embedded && canEditObject(item, "replaceFont")) {
|
||||
await changeDocument("ofdgoUpdateText", item, canEditObject(item, "layoutKnown") ? item.text : null, readTextFont(font), item.size, null);
|
||||
if (canEditObject(item, "replaceFont") && (!font.embedded || item.font !== font.id.slice(9))) {
|
||||
await changeDocument("ofdgoStyleText", { ...item, id: (item.items || [item]).map(member => member.id) }, readTextFont(font), 0, null);
|
||||
updateTextFonts(canvasEditor.selected, true);
|
||||
}
|
||||
} else {
|
||||
@@ -1174,7 +1288,7 @@ async function changeParagraph(wrap = currentTextStyle().wrap) {
|
||||
updateObjectControls(item, true);
|
||||
return;
|
||||
}
|
||||
await changeDocument("ofdgoLayoutText", item, null, null, Boolean(wrap), el.textAlign.value, height, spacing);
|
||||
await changeDocument("ofdgoLayoutText", item, null, null, Boolean(wrap), el.textAlign.value, height, spacing, ...textIndents(item));
|
||||
updateObjectControls(canvasEditor.selected, true);
|
||||
}
|
||||
|
||||
@@ -1190,6 +1304,15 @@ async function changeTextStyle(color) {
|
||||
}
|
||||
const size = color || Number(el.textSize.value) === Number(el.textSize.defaultValue) ? item.size : Number(el.textSize.value) * 25.4 / 72;
|
||||
const fill = color && el.textColor.value !== item.color ? el.textColor.value : null;
|
||||
if (item.items) {
|
||||
if (color ? !canEditObject(item, "update") : !canEditObject(item, "layoutKnown") || !canEditObject(item, "reflow")) return;
|
||||
if (!color && !el.textSize.value) return;
|
||||
if (size !== item.size || fill !== null) {
|
||||
await changeDocument("ofdgoStyleText", { ...item, id: item.items.map(member => member.id) }, null, color ? 0 : size, fill);
|
||||
}
|
||||
updateObjectControls(canvasEditor.selected, true);
|
||||
return;
|
||||
}
|
||||
if (item === state.textDefaults) {
|
||||
item.size = size;
|
||||
if (fill !== null) item.color = fill;
|
||||
@@ -1314,6 +1437,10 @@ async function changeDocument(name, item, ...args) {
|
||||
if (openSeq === state.openSeq) {
|
||||
if (el.pagePanel.open) {
|
||||
el.pageStatus.textContent = err.message;
|
||||
} else if (el.paragraphPanel.open) {
|
||||
el.paragraphStatus.textContent = err.message;
|
||||
} else if (el.importPanel.open) {
|
||||
el.importStatus.textContent = err.message;
|
||||
} else {
|
||||
showError(err, false);
|
||||
}
|
||||
@@ -1825,6 +1952,8 @@ async function openOFD(file) {
|
||||
el.createPanel.close();
|
||||
el.insertPanel.close();
|
||||
el.pagePanel.close();
|
||||
el.paragraphPanel.close();
|
||||
el.importPanel.close();
|
||||
updateControls();
|
||||
await openDocument({ pageIndex: 0, resetScroll: true, openSeq });
|
||||
} catch (err) {
|
||||
@@ -3049,10 +3178,10 @@ async function pasteEditorContent(event) {
|
||||
el.textFontAdd.focus();
|
||||
return;
|
||||
}
|
||||
const style = currentTextStyle();
|
||||
const style = canvasEditor.selected?.items ? state.textDefaults : currentTextStyle();
|
||||
const data = readTextFont(font);
|
||||
await changeDocument("ofdgoInsertText", null, index, value, data, x, y, style.size, style.color,
|
||||
page.width - x * 2, style.wrap, style.align || "left", style.paragraphHeight || 0, style.letterSpacing || 0);
|
||||
page.width - x * 2, style.wrap, style.align || "left", style.paragraphHeight || 0, style.letterSpacing || 0, ...textIndents(style));
|
||||
}
|
||||
|
||||
function copySelection(event) {
|
||||
@@ -4512,13 +4641,15 @@ function updateObjectControls(item, reset = false) {
|
||||
el.editObjectButton.disabled = disabled || Boolean(item.items) || item.type === "PathObject"
|
||||
|| !canEditObject(item, item.type === "TextObject" ? "reflow" : "update");
|
||||
el.multiSelectButton.disabled = !state.editing || !canvasEditor.enabled || !state.ready || state.exporting;
|
||||
const text = item?.type === "TextObject" ? item : state.textDefaults;
|
||||
const textDisabled = !state.editing || !state.ready || state.exporting || Boolean(item?.items);
|
||||
const selection = selectedText(item);
|
||||
const text = selection || state.textDefaults;
|
||||
const textDisabled = !state.editing || !state.ready || state.exporting || Boolean(item?.items && !selection);
|
||||
fontPicker.setDisabled(textDisabled || text !== state.textDefaults && !item.draft && !canEditObject(item, "replaceFont"));
|
||||
el.textSize.disabled = textDisabled || Boolean(item?.draft) || text !== state.textDefaults && !canEditObject(item, "reflow");
|
||||
el.textSize.disabled = textDisabled || Boolean(item?.draft) || text !== state.textDefaults && (!canEditObject(item, "reflow") || Boolean(item?.items) && !canEditObject(item, "layoutKnown"));
|
||||
el.textColor.disabled = textDisabled || Boolean(item?.draft) || text !== state.textDefaults && !canEditObject(item, "update");
|
||||
el.textFontAdd.disabled = !state.editing || !state.ready || state.exporting;
|
||||
el.textAlign.disabled = el.textWrap.disabled = el.textLineHeight.disabled = el.textSpacing.disabled = el.textSize.disabled;
|
||||
el.textAlign.disabled = el.textWrap.disabled = el.textLineHeight.disabled = el.textSpacing.disabled = el.textSize.disabled || Boolean(item?.items);
|
||||
el.paragraphButton.disabled = el.textAlign.disabled;
|
||||
const layoutKnown = text === state.textDefaults || Boolean(item?.draft) || canEditObject(item, "layoutKnown");
|
||||
el.textAlign.value = layoutKnown ? text.align || "left" : "";
|
||||
el.textLineHeight.placeholder = layoutKnown ? "自动" : "原文";
|
||||
@@ -4532,10 +4663,13 @@ function updateObjectControls(item, reset = false) {
|
||||
}
|
||||
updateTextFonts(item);
|
||||
if (reset || el.textSize.disabled || document.activeElement !== el.textSize) {
|
||||
el.textSize.value = document.activeElement === el.textSize ? textPoints(text.size) : Math.round(textPoints(text.size));
|
||||
el.textSize.value = text.size === undefined ? "" : document.activeElement === el.textSize ? textPoints(text.size) : Math.round(textPoints(text.size));
|
||||
el.textSize.defaultValue = el.textSize.value;
|
||||
}
|
||||
el.textSize.title = `字号 ${textPoints(text.size)} pt`;
|
||||
el.textSize.placeholder = text.size === undefined ? "混合" : "";
|
||||
el.textSize.title = text.size === undefined ? "字号:混合" : `字号 ${textPoints(text.size)} pt`;
|
||||
el.textColor.classList.toggle("mixed-color", Boolean(selection?.items && !text.color));
|
||||
el.textColor.title = selection?.items && !text.color ? "文字颜色:混合" : "文字颜色";
|
||||
if (reset || el.textColor.disabled || document.activeElement !== el.textColor) {
|
||||
el.textColor.value = text.color || "#000000";
|
||||
}
|
||||
@@ -4589,6 +4723,7 @@ async function callWASM(name, ...args) {
|
||||
args[0] = args[0].slice();
|
||||
transfer.push(args[0].buffer);
|
||||
}
|
||||
if (name === "ofdgoLoadImport" && args[0]) transfer.push(args[0].buffer);
|
||||
wasmWorker.postMessage({ id, name, args }, transfer);
|
||||
} catch (err) {
|
||||
wasmRequests.delete(id);
|
||||
@@ -4610,6 +4745,8 @@ function setBusy(busy, text = "", percent = 0, status = "") {
|
||||
el.createForm.inert = busy;
|
||||
el.insertForm.inert = busy;
|
||||
el.pageForm.inert = busy;
|
||||
el.paragraphForm.inert = busy;
|
||||
el.importForm.inert = busy;
|
||||
el.editorTools.inert = busy;
|
||||
el.fontList.inert = busy;
|
||||
if (!busy) {
|
||||
|
||||
@@ -131,22 +131,29 @@ export function missingGlyphMessage(diagnostic) {
|
||||
}
|
||||
|
||||
export function objectEditReason(item) {
|
||||
const labels = { fontUnavailable: "字体不可用", unsupportedColor: "颜色样式暂不支持", unsupportedStyle: "绘制参数暂不支持",
|
||||
unsupportedContainer: "容器特性暂不支持", invalidObject: "对象数据异常" };
|
||||
const reasons = (item?.items || (item ? [item] : [])).map(object => {
|
||||
const reason = object.capabilities?.reason || "";
|
||||
if (!reason) return "";
|
||||
let message = "对象特性暂不支持";
|
||||
let message = labels[object.capabilities.reasonCode] || "对象特性暂不支持";
|
||||
if (object.capabilities.missingGlyphs) message = missingGlyphMessage(object.capabilities.missingGlyphs);
|
||||
else if (reason.includes("embedded font")) message = "缺少内嵌字体";
|
||||
else if (reason.includes("color") || reason.includes("RGB")) message = "颜色样式暂不支持";
|
||||
else if (reason.includes("draw parameter")) message = "绘制参数暂不支持";
|
||||
else if (reason.includes("layer")) message = "图层特性暂不支持";
|
||||
else if (reason.includes("container")) message = "容器特性暂不支持";
|
||||
else if (reason.includes("invalid")) message = "对象数据异常";
|
||||
return `${object.capabilities.transform ? "部分操作受限" : "暂不可编辑"}:${message}`;
|
||||
});
|
||||
return [...new Set(reasons.filter(Boolean))].join(";");
|
||||
}
|
||||
|
||||
export function selectedText(item) {
|
||||
if (!item?.items) return item?.type === "TextObject" ? item : null;
|
||||
if (!item.items.every(member => member.type === "TextObject")) return null;
|
||||
const text = { ...item, type: "TextObject" };
|
||||
for (const key of ["font", "fontName", "size", "color"]) {
|
||||
const value = item.items[0][key];
|
||||
text[key] = item.items.every(member => member[key] === value) ? value : undefined;
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
export class CanvasEditor {
|
||||
constructor(viewer, options) {
|
||||
this.viewer = viewer;
|
||||
@@ -803,6 +810,9 @@ export class CanvasEditor {
|
||||
lineHeight: item.paragraphHeight || item.lineHeight ? `${(item.paragraphHeight || item.lineHeight) * PX_PER_MM}px` : "normal", color: item.color,
|
||||
textAlign: item.align || "left",
|
||||
letterSpacing: `${(item.letterSpacing || 0) * PX_PER_MM}px`,
|
||||
paddingLeft: `${2 + Math.max(0, item.leftIndent || 0) * PX_PER_MM}px`,
|
||||
paddingRight: `${2 + Math.max(0, item.rightIndent || 0) * PX_PER_MM}px`,
|
||||
textIndent: `${(item.firstLineIndent || 0) * PX_PER_MM}px`,
|
||||
});
|
||||
if (item.textFrame) {
|
||||
const { width, height, matrix: [a, b, c, d, e, f] } = item.textFrame;
|
||||
|
||||
@@ -32,7 +32,7 @@ export class FontPicker {
|
||||
this.names.set(name, (this.names.get(name) || 0) + 1);
|
||||
}
|
||||
const key = font => font?.id || font?.postscriptName;
|
||||
this.value = fonts.length ? String(Math.max(0, fonts.findIndex(font => key(font) === key(selected)))) : "";
|
||||
this.value = !fonts.length ? "" : selected === null ? "-1" : String(Math.max(0, fonts.findIndex(font => key(font) === key(selected))));
|
||||
this.restore();
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user