refactor: add prettier and format

pr675
Hufe921 3 years ago
parent 9c2bd33b7a
commit d464c50435

@ -0,0 +1,8 @@
{
"semi": false,
"singleQuote": true,
"printWidth": 80,
"trailingComma": "none",
"arrowParens": "avoid",
"endOfLine": "lf"
}

@ -10,9 +10,11 @@
"CRDT", "CRDT",
"deletable", "deletable",
"dppx", "dppx",
"esbenp",
"inputarea", "inputarea",
"linebreak", "linebreak",
"noopener", "noopener",
"Parens",
"prismjs", "prismjs",
"resizer", "resizer",
"richtext", "richtext",
@ -34,5 +36,14 @@
"typescript.tsdk": "node_modules/typescript/lib", "typescript.tsdk": "node_modules/typescript/lib",
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll.eslint": true "source.fixAll.eslint": true
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
} }
} }

@ -2,36 +2,39 @@ import { EditorComponent, EDITOR_COMPONENT } from '../../editor'
import './dialog.css' import './dialog.css'
export interface IDialogData { export interface IDialogData {
type: string; type: string
label?: string; label?: string
name: string; name: string
value?: string; value?: string
options?: { label: string; value: string; }[]; options?: { label: string; value: string }[]
placeholder?: string; placeholder?: string
width?: number; width?: number
height?: number; height?: number
required?: boolean; required?: boolean
} }
export interface IDialogConfirm { export interface IDialogConfirm {
name: string; name: string
value: string; value: string
} }
export interface IDialogOptions { export interface IDialogOptions {
onClose?: () => void; onClose?: () => void
onCancel?: () => void; onCancel?: () => void
onConfirm?: (payload: IDialogConfirm[]) => void; onConfirm?: (payload: IDialogConfirm[]) => void
title: string; title: string
data: IDialogData[]; data: IDialogData[]
} }
export class Dialog { export class Dialog {
private options: IDialogOptions private options: IDialogOptions
private mask: HTMLDivElement | null private mask: HTMLDivElement | null
private container: HTMLDivElement | null private container: HTMLDivElement | null
private inputList: (HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement)[] private inputList: (
| HTMLInputElement
| HTMLTextAreaElement
| HTMLSelectElement
)[]
constructor(options: IDialogOptions) { constructor(options: IDialogOptions) {
this.options = options this.options = options
@ -90,7 +93,10 @@ export class Dialog {
} }
} }
// 选项输入框 // 选项输入框
let optionInput: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement let optionInput:
| HTMLInputElement
| HTMLTextAreaElement
| HTMLSelectElement
if (option.type === 'select') { if (option.type === 'select') {
optionInput = document.createElement('select') optionInput = document.createElement('select')
option.options?.forEach(item => { option.options?.forEach(item => {
@ -162,5 +168,4 @@ export class Dialog {
this.mask?.remove() this.mask?.remove()
this.container?.remove() this.container?.remove()
} }
} }

@ -2,17 +2,17 @@ import { EditorComponent, EDITOR_COMPONENT } from '../../editor'
import './signature.css' import './signature.css'
export interface ISignatureResult { export interface ISignatureResult {
value: string; value: string
width: number; width: number
height: number; height: number
} }
export interface ISignatureOptions { export interface ISignatureOptions {
width?: number; width?: number
height?: number; height?: number
onClose?: () => void; onClose?: () => void
onCancel?: () => void; onCancel?: () => void
onConfirm?: (payload: ISignatureResult | null) => void; onConfirm?: (payload: ISignatureResult | null) => void
} }
export class Signature { export class Signature {
@ -43,7 +43,8 @@ export class Signature {
this.dpr = window.devicePixelRatio this.dpr = window.devicePixelRatio
this.canvasWidth = (options.width || this.DEFAULT_WIDTH) * this.dpr this.canvasWidth = (options.width || this.DEFAULT_WIDTH) * this.dpr
this.canvasHeight = (options.height || this.DEFAULT_HEIGHT) * this.dpr this.canvasHeight = (options.height || this.DEFAULT_HEIGHT) * this.dpr
const { mask, container, trashContainer, undoContainer, canvas } = this._render() const { mask, container, trashContainer, undoContainer, canvas } =
this._render()
this.mask = mask this.mask = mask
this.container = container this.container = container
this.trashContainer = trashContainer this.trashContainer = trashContainer
@ -213,7 +214,8 @@ export class Signature {
const targetLineWidth = Math.min(5, Math.max(1, 5 - speed * SPEED_FACTOR)) const targetLineWidth = Math.min(5, Math.max(1, 5 - speed * SPEED_FACTOR))
// 平滑过渡算法20%的变化比例调整线条粗细系数0.2 // 平滑过渡算法20%的变化比例调整线条粗细系数0.2
const SMOOTH_FACTOR = 0.2 const SMOOTH_FACTOR = 0.2
this.ctx.lineWidth = this.ctx.lineWidth * (1 - SMOOTH_FACTOR) + targetLineWidth * SMOOTH_FACTOR this.ctx.lineWidth =
this.ctx.lineWidth * (1 - SMOOTH_FACTOR) + targetLineWidth * SMOOTH_FACTOR
// 绘制 // 绘制
const { offsetX, offsetY } = evt const { offsetX, offsetY } = evt
this.ctx.beginPath() this.ctx.beginPath()
@ -231,7 +233,12 @@ export class Signature {
private _stopDraw() { private _stopDraw() {
this.isDrawing = false this.isDrawing = false
if (this.isDrawn) { if (this.isDrawn) {
const imageData = this.ctx.getImageData(0, 0, this.canvasWidth, this.canvasHeight) const imageData = this.ctx.getImageData(
0,
0,
this.canvasWidth,
this.canvasHeight
)
const self = this const self = this
this._saveUndoFn(function () { this._saveUndoFn(function () {
self.ctx.clearRect(0, 0, self.canvasWidth, self.canvasHeight) self.ctx.clearRect(0, 0, self.canvasWidth, self.canvasHeight)
@ -299,5 +306,4 @@ export class Signature {
this.mask.remove() this.mask.remove()
this.container.remove() this.container.remove()
} }
} }

@ -2,7 +2,6 @@ import { CommandAdapt } from './CommandAdapt'
// 通过CommandAdapt中转避免直接暴露编辑器上下文 // 通过CommandAdapt中转避免直接暴露编辑器上下文
export class Command { export class Command {
public executeMode: CommandAdapt['mode'] public executeMode: CommandAdapt['mode']
public executeCut: CommandAdapt['cut'] public executeCut: CommandAdapt['cut']
public executeCopy: CommandAdapt['copy'] public executeCopy: CommandAdapt['copy']
@ -130,7 +129,8 @@ export class Command {
this.executeCancelMergeTableCell = adapt.cancelMergeTableCell.bind(adapt) this.executeCancelMergeTableCell = adapt.cancelMergeTableCell.bind(adapt)
this.executeTableTdVerticalAlign = adapt.tableTdVerticalAlign.bind(adapt) this.executeTableTdVerticalAlign = adapt.tableTdVerticalAlign.bind(adapt)
this.executeTableBorderType = adapt.tableBorderType.bind(adapt) this.executeTableBorderType = adapt.tableBorderType.bind(adapt)
this.executeTableTdBackgroundColor = adapt.tableTdBackgroundColor.bind(adapt) this.executeTableTdBackgroundColor =
adapt.tableTdBackgroundColor.bind(adapt)
this.executeImage = adapt.image.bind(adapt) this.executeImage = adapt.image.bind(adapt)
this.executeHyperlink = adapt.hyperlink.bind(adapt) this.executeHyperlink = adapt.hyperlink.bind(adapt)
this.executeDeleteHyperlink = adapt.deleteHyperlink.bind(adapt) this.executeDeleteHyperlink = adapt.deleteHyperlink.bind(adapt)
@ -174,5 +174,4 @@ export class Command {
this.getPaperMargin = adapt.getPaperMargin.bind(adapt) this.getPaperMargin = adapt.getPaperMargin.bind(adapt)
this.getSearchNavigateInfo = adapt.getSearchNavigateInfo.bind(adapt) this.getSearchNavigateInfo = adapt.getSearchNavigateInfo.bind(adapt)
} }
} }

@ -3,7 +3,12 @@ import { EDITOR_ELEMENT_STYLE_ATTR } from '../../dataset/constant/Element'
import { titleSizeMapping } from '../../dataset/constant/Title' import { titleSizeMapping } from '../../dataset/constant/Title'
import { defaultWatermarkOption } from '../../dataset/constant/Watermark' import { defaultWatermarkOption } from '../../dataset/constant/Watermark'
import { ControlComponent, ImageDisplay } from '../../dataset/enum/Control' import { ControlComponent, ImageDisplay } from '../../dataset/enum/Control'
import { EditorContext, EditorMode, PageMode, PaperDirection } from '../../dataset/enum/Editor' import {
EditorContext,
EditorMode,
PageMode,
PaperDirection
} from '../../dataset/enum/Editor'
import { ElementType } from '../../dataset/enum/Element' import { ElementType } from '../../dataset/enum/Element'
import { ElementStyleKey } from '../../dataset/enum/ElementStyle' import { ElementStyleKey } from '../../dataset/enum/ElementStyle'
import { ListStyle, ListType } from '../../dataset/enum/List' import { ListStyle, ListType } from '../../dataset/enum/List'
@ -12,8 +17,17 @@ import { TableBorder } from '../../dataset/enum/table/Table'
import { TitleLevel } from '../../dataset/enum/Title' import { TitleLevel } from '../../dataset/enum/Title'
import { VerticalAlign } from '../../dataset/enum/VerticalAlign' import { VerticalAlign } from '../../dataset/enum/VerticalAlign'
import { ICatalog } from '../../interface/Catalog' import { ICatalog } from '../../interface/Catalog'
import { IAppendElementListOption, IDrawImagePayload, IGetValueOption, IPainterOption } from '../../interface/Draw' import {
import { IEditorData, IEditorOption, IEditorResult } from '../../interface/Editor' IAppendElementListOption,
IDrawImagePayload,
IGetValueOption,
IPainterOption
} from '../../interface/Draw'
import {
IEditorData,
IEditorOption,
IEditorResult
} from '../../interface/Editor'
import { IElement, IElementStyle } from '../../interface/Element' import { IElement, IElementStyle } from '../../interface/Element'
import { IMargin } from '../../interface/Margin' import { IMargin } from '../../interface/Margin'
import { RangeContext } from '../../interface/Range' import { RangeContext } from '../../interface/Range'
@ -22,7 +36,12 @@ import { ITd } from '../../interface/table/Td'
import { ITr } from '../../interface/table/Tr' import { ITr } from '../../interface/table/Tr'
import { IWatermark } from '../../interface/Watermark' import { IWatermark } from '../../interface/Watermark'
import { deepClone, downloadFile, getUUID } from '../../utils' import { deepClone, downloadFile, getUUID } from '../../utils'
import { formatElementContext, formatElementList, isTextLikeElement, pickElementAttr } from '../../utils/element' import {
formatElementContext,
formatElementList,
isTextLikeElement,
pickElementAttr
} from '../../utils/element'
import { printImageBase64 } from '../../utils/print' import { printImageBase64 } from '../../utils/print'
import { Control } from '../draw/control/Control' import { Control } from '../draw/control/Control'
import { Draw } from '../draw/Draw' import { Draw } from '../draw/Draw'
@ -36,7 +55,6 @@ import { RangeManager } from '../range/RangeManager'
import { WorkerManager } from '../worker/WorkerManager' import { WorkerManager } from '../worker/WorkerManager'
export class CommandAdapt { export class CommandAdapt {
private draw: Draw private draw: Draw
private range: RangeManager private range: RangeManager
private position: Position private position: Position
@ -103,11 +121,19 @@ export class CommandAdapt {
const { startIndex, endIndex } = this.range.getRange() const { startIndex, endIndex } = this.range.getRange()
const isCollapsed = startIndex === endIndex const isCollapsed = startIndex === endIndex
// 首字符禁止删除 // 首字符禁止删除
if (isCollapsed && elementList[startIndex].value === ZERO && startIndex === 0) { if (
isCollapsed &&
elementList[startIndex].value === ZERO &&
startIndex === 0
) {
return return
} }
if (!isCollapsed) { if (!isCollapsed) {
this.draw.spliceElementList(elementList, startIndex + 1, endIndex - startIndex) this.draw.spliceElementList(
elementList,
startIndex + 1,
endIndex - startIndex
)
} else { } else {
this.draw.spliceElementList(elementList, startIndex, 1) this.draw.spliceElementList(elementList, startIndex, 1)
} }
@ -198,7 +224,12 @@ export class CommandAdapt {
if (!selection || !selection.length) return if (!selection || !selection.length) return
let isExistUpdate = false let isExistUpdate = false
selection.forEach(el => { selection.forEach(el => {
if ((!el.size && payload === defaultSize) || (el.size && el.size === payload)) return if (
(!el.size && payload === defaultSize) ||
(el.size && el.size === payload)
) {
return
}
el.size = payload el.size = payload
isExistUpdate = true isExistUpdate = true
}) })
@ -310,7 +341,9 @@ export class CommandAdapt {
if (activeControl) return if (activeControl) return
const selection = this.range.getSelection() const selection = this.range.getSelection()
if (!selection) return if (!selection) return
const superscriptIndex = selection.findIndex(s => s.type === ElementType.SUPERSCRIPT) const superscriptIndex = selection.findIndex(
s => s.type === ElementType.SUPERSCRIPT
)
selection.forEach(el => { selection.forEach(el => {
// 取消上标 // 取消上标
if (~superscriptIndex) { if (~superscriptIndex) {
@ -320,7 +353,11 @@ export class CommandAdapt {
} }
} else { } else {
// 设置上标 // 设置上标
if (!el.type || el.type === ElementType.TEXT || el.type === ElementType.SUBSCRIPT) { if (
!el.type ||
el.type === ElementType.TEXT ||
el.type === ElementType.SUBSCRIPT
) {
el.type = ElementType.SUPERSCRIPT el.type = ElementType.SUPERSCRIPT
} }
} }
@ -335,7 +372,9 @@ export class CommandAdapt {
if (activeControl) return if (activeControl) return
const selection = this.range.getSelection() const selection = this.range.getSelection()
if (!selection) return if (!selection) return
const subscriptIndex = selection.findIndex(s => s.type === ElementType.SUBSCRIPT) const subscriptIndex = selection.findIndex(
s => s.type === ElementType.SUBSCRIPT
)
selection.forEach(el => { selection.forEach(el => {
// 取消下标 // 取消下标
if (~subscriptIndex) { if (~subscriptIndex) {
@ -345,7 +384,11 @@ export class CommandAdapt {
} }
} else { } else {
// 设置下标 // 设置下标
if (!el.type || el.type === ElementType.TEXT || el.type === ElementType.SUPERSCRIPT) { if (
!el.type ||
el.type === ElementType.TEXT ||
el.type === ElementType.SUPERSCRIPT
) {
el.type = ElementType.SUBSCRIPT el.type = ElementType.SUBSCRIPT
} }
} }
@ -388,9 +431,10 @@ export class CommandAdapt {
if (!~startIndex && !~endIndex) return if (!~startIndex && !~endIndex) return
const elementList = this.draw.getElementList() const elementList = this.draw.getElementList()
// 需要改变的元素列表 // 需要改变的元素列表
const changeElementList = startIndex === endIndex const changeElementList =
? this.range.getRangeElementList() startIndex === endIndex
: elementList.slice(startIndex + 1, endIndex + 1) ? this.range.getRangeElementList()
: elementList.slice(startIndex + 1, endIndex + 1)
if (!changeElementList || !changeElementList.length) return if (!changeElementList || !changeElementList.length) return
// 设置值 // 设置值
const titleId = getUUID() const titleId = getUUID()
@ -428,7 +472,9 @@ export class CommandAdapt {
const changeElementList = this.range.getRangeElementList() const changeElementList = this.range.getRangeElementList()
if (!changeElementList || !changeElementList.length) return if (!changeElementList || !changeElementList.length) return
// 如果包含列表则设置为取消列表 // 如果包含列表则设置为取消列表
const isUnsetList = changeElementList.find(el => el.listType === listType && el.listStyle === listStyle) const isUnsetList = changeElementList.find(
el => el.listType === listType && el.listStyle === listStyle
)
// 设置值 // 设置值
const listId = getUUID() const listId = getUUID()
changeElementList.forEach(el => { changeElementList.forEach(el => {
@ -601,13 +647,15 @@ export class CommandAdapt {
id: newTdId, id: newTdId,
rowspan: 1, rowspan: 1,
colspan: curTd.colspan, colspan: curTd.colspan,
value: [{ value: [
value: ZERO, {
size: 16, value: ZERO,
tableId, size: 16,
trId: newTrId, tableId,
tdId: newTdId trId: newTrId,
}] tdId: newTdId
}
]
}) })
} }
curTrList.splice(trIndex!, 0, newTr) curTrList.splice(trIndex!, 0, newTr)
@ -637,9 +685,8 @@ export class CommandAdapt {
const element = originalElementList[index!] const element = originalElementList[index!]
const curTrList = element.trList! const curTrList = element.trList!
const curTr = curTrList[trIndex!] const curTr = curTrList[trIndex!]
const anchorTr = curTrList.length - 1 === trIndex const anchorTr =
? curTr curTrList.length - 1 === trIndex ? curTr : curTrList[trIndex! + 1]
: curTrList[trIndex! + 1]
// 之前/当前行跨行的增加跨行数 // 之前/当前行跨行的增加跨行数
if (anchorTr.tdList.length < element.colgroup!.length) { if (anchorTr.tdList.length < element.colgroup!.length) {
const curTrNo = anchorTr.tdList[0].rowIndex! const curTrNo = anchorTr.tdList[0].rowIndex!
@ -667,13 +714,15 @@ export class CommandAdapt {
id: newTdId, id: newTdId,
rowspan: 1, rowspan: 1,
colspan: curTd.colspan, colspan: curTd.colspan,
value: [{ value: [
value: ZERO, {
size: 16, value: ZERO,
tableId, size: 16,
trId: newTrId, tableId,
tdId: newTdId trId: newTrId,
}] tdId: newTdId
}
]
}) })
} }
curTrList.splice(trIndex! + 1, 0, newTr) curTrList.splice(trIndex! + 1, 0, newTr)
@ -711,13 +760,15 @@ export class CommandAdapt {
id: tdId, id: tdId,
rowspan: 1, rowspan: 1,
colspan: 1, colspan: 1,
value: [{ value: [
value: ZERO, {
size: 16, value: ZERO,
tableId, size: 16,
trId: tr.id, tableId,
tdId trId: tr.id,
}] tdId
}
]
}) })
} }
// 重新计算宽度 // 重新计算宽度
@ -768,13 +819,15 @@ export class CommandAdapt {
id: tdId, id: tdId,
rowspan: 1, rowspan: 1,
colspan: 1, colspan: 1,
value: [{ value: [
value: ZERO, {
size: 16, value: ZERO,
tableId, size: 16,
trId: tr.id, tableId,
tdId trId: tr.id,
}] tdId
}
]
}) })
} }
// 重新计算宽度 // 重新计算宽度
@ -834,13 +887,15 @@ export class CommandAdapt {
id: tdId, id: tdId,
rowspan: 1, rowspan: 1,
colspan: 1, colspan: 1,
value: [{ value: [
value: ZERO, {
size: 16, value: ZERO,
tableId: element.id, size: 16,
trId: tr.id, tableId: element.id,
tdId trId: tr.id,
}] tdId
}
]
}) })
start += 1 start += 1
} }
@ -885,7 +940,10 @@ export class CommandAdapt {
if (td.colspan > 1) { if (td.colspan > 1) {
const tdColIndex = td.colIndex! const tdColIndex = td.colIndex!
// 交叉减去一列 // 交叉减去一列
if (tdColIndex <= curColIndex && tdColIndex + td.colspan - 1 >= curColIndex) { if (
tdColIndex <= curColIndex &&
tdColIndex + td.colspan - 1 >= curColIndex
) {
td.colspan -= 1 td.colspan -= 1
} }
} }
@ -940,7 +998,13 @@ export class CommandAdapt {
if (isReadonly) return if (isReadonly) return
const positionContext = this.position.getPositionContext() const positionContext = this.position.getPositionContext()
if (!positionContext.isTable) return if (!positionContext.isTable) return
const { isCrossRowCol, startTdIndex, endTdIndex, startTrIndex, endTrIndex } = this.range.getRange() const {
isCrossRowCol,
startTdIndex,
endTdIndex,
startTrIndex,
endTrIndex
} = this.range.getRange()
if (!isCrossRowCol) return if (!isCrossRowCol) return
const { index } = positionContext const { index } = positionContext
const originalElementList = this.draw.getOriginalElementList() const originalElementList = this.draw.getOriginalElementList()
@ -966,8 +1030,10 @@ export class CommandAdapt {
const tdColIndex = td.colIndex! const tdColIndex = td.colIndex!
const tdRowIndex = td.rowIndex! const tdRowIndex = td.rowIndex!
if ( if (
tdColIndex >= startColIndex && tdColIndex <= endColIndex tdColIndex >= startColIndex &&
&& tdRowIndex >= startRowIndex && tdRowIndex <= endRowIndex tdColIndex <= endColIndex &&
tdRowIndex >= startRowIndex &&
tdRowIndex <= endRowIndex
) { ) {
tdList.push(td) tdList.push(td)
} }
@ -994,7 +1060,12 @@ export class CommandAdapt {
const tdEndX = tdStartX + td.width! const tdEndX = tdStartX + td.width!
const tdEndY = tdStartY + td.height! const tdEndY = tdStartY + td.height!
// 存在不符合项 // 存在不符合项
if (startX > tdStartX || startY > tdStartY || endX < tdEndX || endY < tdEndY) { if (
startX > tdStartX ||
startY > tdStartY ||
endX < tdEndX ||
endY < tdEndY
) {
return return
} }
} }
@ -1064,13 +1135,15 @@ export class CommandAdapt {
id: tdId, id: tdId,
rowspan: 1, rowspan: 1,
colspan: 1, colspan: 1,
value: [{ value: [
value: ZERO, {
size: 16, value: ZERO,
tableId: element.id, size: 16,
trId: curTr.id, tableId: element.id,
tdId trId: curTr.id,
}] tdId
}
]
}) })
} }
curTd.colspan = 1 curTd.colspan = 1
@ -1085,13 +1158,15 @@ export class CommandAdapt {
id: tdId, id: tdId,
rowspan: 1, rowspan: 1,
colspan: 1, colspan: 1,
value: [{ value: [
value: ZERO, {
size: 16, value: ZERO,
tableId: element.id, size: 16,
trId: tr.id, tableId: element.id,
tdId trId: tr.id,
}] tdId
}
]
}) })
} }
} }
@ -1241,7 +1316,11 @@ export class CommandAdapt {
const elementList = this.draw.getElementList() const elementList = this.draw.getElementList()
const [leftIndex, rightIndex] = hyperRange const [leftIndex, rightIndex] = hyperRange
// 删除元素 // 删除元素
this.draw.spliceElementList(elementList, leftIndex, rightIndex - leftIndex + 1) this.draw.spliceElementList(
elementList,
leftIndex,
rightIndex - leftIndex + 1
)
this.draw.getHyperlinkParticle().clearHyperlinkPopup() this.draw.getHyperlinkParticle().clearHyperlinkPopup()
// 重置画布 // 重置画布
const newIndex = leftIndex - 1 const newIndex = leftIndex - 1
@ -1306,7 +1385,12 @@ export class CommandAdapt {
// 光标存在分割线,则判断为修改线段逻辑 // 光标存在分割线,则判断为修改线段逻辑
const endElement = elementList[endIndex + 1] const endElement = elementList[endIndex + 1]
if (endElement && endElement.type === ElementType.SEPARATOR) { if (endElement && endElement.type === ElementType.SEPARATOR) {
if (endElement.dashArray && endElement.dashArray.join() === payload.join()) return if (
endElement.dashArray &&
endElement.dashArray.join() === payload.join()
) {
return
}
curIndex = endIndex curIndex = endIndex
endElement.dashArray = payload endElement.dashArray = payload
} else { } else {
@ -1334,10 +1418,12 @@ export class CommandAdapt {
if (isReadonly) return if (isReadonly) return
const activeControl = this.control.getActiveControl() const activeControl = this.control.getActiveControl()
if (activeControl) return if (activeControl) return
this.insertElementList([{ this.insertElementList([
type: ElementType.PAGE_BREAK, {
value: WRAP type: ElementType.PAGE_BREAK,
}]) value: WRAP
}
])
} }
public addWatermark(payload: IWatermark) { public addWatermark(payload: IWatermark) {
@ -1458,7 +1544,8 @@ export class CommandAdapt {
} }
curTdId = tdId! curTdId = tdId!
const curTableIndex = tableIndex! + pageDiffCount const curTableIndex = tableIndex! + pageDiffCount
const tableElementList = elementList[curTableIndex].trList![trIndex!].tdList[tdIndex!].value const tableElementList =
elementList[curTableIndex].trList![trIndex!].tdList[tdIndex!].value
// 表格内元素 // 表格内元素
const curIndex = index + tableDiffCount const curIndex = index + tableDiffCount
const tableElement = tableElementList[curIndex] const tableElement = tableElementList[curIndex]
@ -1483,8 +1570,8 @@ export class CommandAdapt {
const curIndex = match.index + pageDiffCount const curIndex = match.index + pageDiffCount
const element = elementList[curIndex] const element = elementList[curIndex]
if ( if (
element.type === ElementType.CONTROL element.type === ElementType.CONTROL &&
&& element.controlComponent !== ControlComponent.VALUE element.controlComponent !== ControlComponent.VALUE
) { ) {
continue continue
} }
@ -1517,7 +1604,8 @@ export class CommandAdapt {
const firstIndex = firstMatch.index + (payload.length - 1) const firstIndex = firstMatch.index + (payload.length - 1)
if (firstMatch.type === EditorContext.TABLE) { if (firstMatch.type === EditorContext.TABLE) {
const { tableIndex, trIndex, tdIndex, index } = firstMatch const { tableIndex, trIndex, tdIndex, index } = firstMatch
const element = elementList[tableIndex!].trList![trIndex!].tdList[tdIndex!].value[index] const element =
elementList[tableIndex!].trList![trIndex!].tdList[tdIndex!].value[index]
this.position.setPositionContext({ this.position.setPositionContext({
isTable: true, isTable: true,
index: tableIndex, index: tableIndex,
@ -1607,7 +1695,9 @@ export class CommandAdapt {
const isCollapsed = startIndex === endIndex const isCollapsed = startIndex === endIndex
// 元素信息 // 元素信息
const elementList = this.draw.getElementList() const elementList = this.draw.getElementList()
const startElement = pickElementAttr(elementList[isCollapsed ? startIndex : startIndex + 1]) const startElement = pickElementAttr(
elementList[isCollapsed ? startIndex : startIndex + 1]
)
const endElement = pickElementAttr(elementList[endIndex]) const endElement = pickElementAttr(elementList[endIndex])
// 页码信息 // 页码信息
const positionList = this.position.getPositionList() const positionList = this.position.getPositionList()
@ -1676,7 +1766,10 @@ export class CommandAdapt {
this.draw.insertElementList(payload) this.draw.insertElementList(payload)
} }
public appendElementList(elementList: IElement[], options?: IAppendElementListOption) { public appendElementList(
elementList: IElement[],
options?: IAppendElementListOption
) {
if (!elementList.length) return if (!elementList.length) return
const isReadonly = this.draw.isReadonly() const isReadonly = this.draw.isReadonly()
if (isReadonly) return if (isReadonly) return
@ -1716,7 +1809,10 @@ export class CommandAdapt {
let newIndex = -1 let newIndex = -1
for (let e = 0; e < elementList.length; e++) { for (let e = 0; e < elementList.length; e++) {
const element = elementList[e] const element = elementList[e]
if (element.titleId === titleId && elementList[e + 1]?.titleId !== titleId) { if (
element.titleId === titleId &&
elementList[e + 1]?.titleId !== titleId
) {
newIndex = e newIndex = e
break break
} }
@ -1757,5 +1853,4 @@ export class CommandAdapt {
}) })
} }
} }
} }

@ -1,7 +1,10 @@
import { NAME_PLACEHOLDER } from '../../dataset/constant/ContextMenu' import { NAME_PLACEHOLDER } from '../../dataset/constant/ContextMenu'
import { EDITOR_COMPONENT, EDITOR_PREFIX } from '../../dataset/constant/Editor' import { EDITOR_COMPONENT, EDITOR_PREFIX } from '../../dataset/constant/Editor'
import { EditorComponent } from '../../dataset/enum/Editor' import { EditorComponent } from '../../dataset/enum/Editor'
import { IContextMenuContext, IRegisterContextMenu } from '../../interface/contextmenu/ContextMenu' import {
IContextMenuContext,
IRegisterContextMenu
} from '../../interface/contextmenu/ContextMenu'
import { findParent } from '../../utils' import { findParent } from '../../utils'
import { Command } from '../command/Command' import { Command } from '../command/Command'
import { Draw } from '../draw/Draw' import { Draw } from '../draw/Draw'
@ -15,14 +18,13 @@ import { imageMenus } from './menus/imageMenus'
import { tableMenus } from './menus/tableMenus' import { tableMenus } from './menus/tableMenus'
interface IRenderPayload { interface IRenderPayload {
contextMenuList: IRegisterContextMenu[]; contextMenuList: IRegisterContextMenu[]
left: number; left: number
top: number; top: number
parentMenuContainer?: HTMLDivElement; parentMenuContainer?: HTMLDivElement
} }
export class ContextMenu { export class ContextMenu {
private draw: Draw private draw: Draw
private command: Command private command: Command
private range: RangeManager private range: RangeManager
@ -63,7 +65,10 @@ export class ContextMenu {
} }
public removeEvent() { public removeEvent() {
this.container.removeEventListener('contextmenu', this._proxyContextMenuEvent) this.container.removeEventListener(
'contextmenu',
this._proxyContextMenuEvent
)
document.removeEventListener('mousedown', this._handleEffect) document.removeEventListener('mousedown', this._handleEffect)
} }
@ -88,7 +93,7 @@ export class ContextMenu {
this._render({ this._render({
contextMenuList: renderList, contextMenuList: renderList,
left: evt.x, left: evt.x,
top: evt.y, top: evt.y
}) })
} }
evt.preventDefault() evt.preventDefault()
@ -99,8 +104,10 @@ export class ContextMenu {
// 点击非右键菜单内 // 点击非右键菜单内
const contextMenuDom = findParent( const contextMenuDom = findParent(
evt.target as Element, evt.target as Element,
(node: Node & Element) => !!node && node.nodeType === 1 (node: Node & Element) =>
&& node.getAttribute(EDITOR_COMPONENT) === EditorComponent.CONTEXTMENU, !!node &&
node.nodeType === 1 &&
node.getAttribute(EDITOR_COMPONENT) === EditorComponent.CONTEXTMENU,
true true
) )
if (!contextMenuDom) { if (!contextMenuDom) {
@ -112,7 +119,11 @@ export class ContextMenu {
private _getContext(): IContextMenuContext { private _getContext(): IContextMenuContext {
// 是否是只读模式 // 是否是只读模式
const isReadonly = this.draw.isReadonly() const isReadonly = this.draw.isReadonly()
const { isCrossRowCol: crossRowCol, startIndex, endIndex } = this.range.getRange() const {
isCrossRowCol: crossRowCol,
startIndex,
endIndex
} = this.range.getRange()
// 是否存在焦点 // 是否存在焦点
const editorTextFocus = !!(~startIndex || ~endIndex) const editorTextFocus = !!(~startIndex || ~endIndex)
// 是否存在选区 // 是否存在选区
@ -141,7 +152,10 @@ export class ContextMenu {
private _createContextMenuContainer(): HTMLDivElement { private _createContextMenuContainer(): HTMLDivElement {
const contextMenuContainer = document.createElement('div') const contextMenuContainer = document.createElement('div')
contextMenuContainer.classList.add(`${EDITOR_PREFIX}-contextmenu-container`) contextMenuContainer.classList.add(`${EDITOR_PREFIX}-contextmenu-container`)
contextMenuContainer.setAttribute(EDITOR_COMPONENT, EditorComponent.CONTEXTMENU) contextMenuContainer.setAttribute(
EDITOR_COMPONENT,
EditorComponent.CONTEXTMENU
)
this.container.append(contextMenuContainer) this.container.append(contextMenuContainer)
return contextMenuContainer return contextMenuContainer
} }
@ -155,7 +169,10 @@ export class ContextMenu {
let childMenuContainer: HTMLDivElement | null = null let childMenuContainer: HTMLDivElement | null = null
// 父菜单添加子菜单映射关系 // 父菜单添加子菜单映射关系
if (parentMenuContainer) { if (parentMenuContainer) {
this.contextMenuRelationShip.set(parentMenuContainer, contextMenuContainer) this.contextMenuRelationShip.set(
parentMenuContainer,
contextMenuContainer
)
} }
for (let c = 0; c < contextMenuList.length; c++) { for (let c = 0; c < contextMenuList.length; c++) {
const menu = contextMenuList[c] const menu = contextMenuList[c]
@ -186,9 +203,12 @@ export class ContextMenu {
parentMenuContainer: contextMenuContainer parentMenuContainer: contextMenuContainer
}) })
} }
menuItem.onmouseleave = (evt) => { menuItem.onmouseleave = evt => {
// 移动到子菜单选项选中状态不变化 // 移动到子菜单选项选中状态不变化
if (!childMenuContainer || !childMenuContainer.contains(evt.relatedTarget as Node)) { if (
!childMenuContainer ||
!childMenuContainer.contains(evt.relatedTarget as Node)
) {
this._setHoverStatus(menuItem, false) this._setHoverStatus(menuItem, false)
} }
} }
@ -235,9 +255,8 @@ export class ContextMenu {
const innerWidth = window.innerWidth const innerWidth = window.innerWidth
const contextMenuWidth = contextMenuContainer.getBoundingClientRect().width const contextMenuWidth = contextMenuContainer.getBoundingClientRect().width
// 右侧空间不足时,以菜单右上角作为起始点 // 右侧空间不足时,以菜单右上角作为起始点
const adjustLeft = left + contextMenuWidth > innerWidth const adjustLeft =
? left - contextMenuWidth left + contextMenuWidth > innerWidth ? left - contextMenuWidth : left
: left
contextMenuContainer.style.left = `${adjustLeft}px` contextMenuContainer.style.left = `${adjustLeft}px`
contextMenuContainer.style.top = `${top}px` contextMenuContainer.style.top = `${top}px`
this.contextMenuContainerList.push(contextMenuContainer) this.contextMenuContainerList.push(contextMenuContainer)
@ -255,7 +274,8 @@ export class ContextMenu {
private _setHoverStatus(payload: HTMLDivElement, status: boolean) { private _setHoverStatus(payload: HTMLDivElement, status: boolean) {
if (status) { if (status) {
payload.parentNode?.querySelectorAll(`${EDITOR_PREFIX}-contextmenu-item`) payload.parentNode
?.querySelectorAll(`${EDITOR_PREFIX}-contextmenu-item`)
.forEach(child => child.classList.remove('hover')) .forEach(child => child.classList.remove('hover'))
payload.classList.add('hover') payload.classList.add('hover')
} else { } else {
@ -287,5 +307,4 @@ export class ContextMenu {
this.contextMenuContainerList = [] this.contextMenuContainerList = []
this.contextMenuRelationShip.clear() this.contextMenuRelationShip.clear()
} }
} }

@ -5,8 +5,12 @@ import { Command } from '../../command/Command'
export const controlMenus: IRegisterContextMenu[] = [ export const controlMenus: IRegisterContextMenu[] = [
{ {
i18nPath: 'contextmenu.control.delete', i18nPath: 'contextmenu.control.delete',
when: (payload) => { when: payload => {
return !payload.isReadonly && !payload.editorHasSelection && payload.startElement?.type === ElementType.CONTROL return (
!payload.isReadonly &&
!payload.editorHasSelection &&
payload.startElement?.type === ElementType.CONTROL
)
}, },
callback: (command: Command) => { callback: (command: Command) => {
command.executeRemoveControl() command.executeRemoveControl()

@ -6,7 +6,7 @@ export const globalMenus: IRegisterContextMenu[] = [
{ {
i18nPath: 'contextmenu.global.cut', i18nPath: 'contextmenu.global.cut',
shortCut: `${isApple ? '⌘' : 'Ctrl'} + X`, shortCut: `${isApple ? '⌘' : 'Ctrl'} + X`,
when: (payload) => { when: payload => {
return !payload.isReadonly return !payload.isReadonly
}, },
callback: (command: Command) => { callback: (command: Command) => {
@ -16,7 +16,7 @@ export const globalMenus: IRegisterContextMenu[] = [
{ {
i18nPath: 'contextmenu.global.copy', i18nPath: 'contextmenu.global.copy',
shortCut: `${isApple ? '⌘' : 'Ctrl'} + C`, shortCut: `${isApple ? '⌘' : 'Ctrl'} + C`,
when: (payload) => { when: payload => {
return payload.editorHasSelection return payload.editorHasSelection
}, },
callback: (command: Command) => { callback: (command: Command) => {
@ -26,7 +26,7 @@ export const globalMenus: IRegisterContextMenu[] = [
{ {
i18nPath: 'contextmenu.global.paste', i18nPath: 'contextmenu.global.paste',
shortCut: `${isApple ? '⌘' : 'Ctrl'} + V`, shortCut: `${isApple ? '⌘' : 'Ctrl'} + V`,
when: (payload) => { when: payload => {
return !payload.isReadonly && payload.editorTextFocus return !payload.isReadonly && payload.editorTextFocus
}, },
callback: (command: Command) => { callback: (command: Command) => {
@ -36,7 +36,7 @@ export const globalMenus: IRegisterContextMenu[] = [
{ {
i18nPath: 'contextmenu.global.selectAll', i18nPath: 'contextmenu.global.selectAll',
shortCut: `${isApple ? '⌘' : 'Ctrl'} + A`, shortCut: `${isApple ? '⌘' : 'Ctrl'} + A`,
when: (payload) => { when: payload => {
return payload.editorTextFocus return payload.editorTextFocus
}, },
callback: (command: Command) => { callback: (command: Command) => {

@ -1,12 +1,18 @@
import { ElementType } from '../../../dataset/enum/Element' import { ElementType } from '../../../dataset/enum/Element'
import { IContextMenuContext, IRegisterContextMenu } from '../../../interface/contextmenu/ContextMenu' import {
IContextMenuContext,
IRegisterContextMenu
} from '../../../interface/contextmenu/ContextMenu'
import { Command } from '../../command/Command' import { Command } from '../../command/Command'
export const hyperlinkMenus: IRegisterContextMenu[] = [ export const hyperlinkMenus: IRegisterContextMenu[] = [
{ {
i18nPath: 'contextmenu.hyperlink.delete', i18nPath: 'contextmenu.hyperlink.delete',
when: (payload) => { when: payload => {
return !payload.isReadonly && payload.startElement?.type === ElementType.HYPERLINK return (
!payload.isReadonly &&
payload.startElement?.type === ElementType.HYPERLINK
)
}, },
callback: (command: Command) => { callback: (command: Command) => {
command.executeDeleteHyperlink() command.executeDeleteHyperlink()
@ -14,8 +20,11 @@ export const hyperlinkMenus: IRegisterContextMenu[] = [
}, },
{ {
i18nPath: 'contextmenu.hyperlink.cancel', i18nPath: 'contextmenu.hyperlink.cancel',
when: (payload) => { when: payload => {
return !payload.isReadonly && payload.startElement?.type === ElementType.HYPERLINK return (
!payload.isReadonly &&
payload.startElement?.type === ElementType.HYPERLINK
)
}, },
callback: (command: Command) => { callback: (command: Command) => {
command.executeCancelHyperlink() command.executeCancelHyperlink()
@ -23,8 +32,11 @@ export const hyperlinkMenus: IRegisterContextMenu[] = [
}, },
{ {
i18nPath: 'contextmenu.hyperlink.edit', i18nPath: 'contextmenu.hyperlink.edit',
when: (payload) => { when: payload => {
return !payload.isReadonly && payload.startElement?.type === ElementType.HYPERLINK return (
!payload.isReadonly &&
payload.startElement?.type === ElementType.HYPERLINK
)
}, },
callback: (command: Command, context: IContextMenuContext) => { callback: (command: Command, context: IContextMenuContext) => {
const url = window.prompt('编辑链接', context.startElement?.url) const url = window.prompt('编辑链接', context.startElement?.url)

@ -1,14 +1,21 @@
import { ImageDisplay } from '../../../dataset/enum/Control' import { ImageDisplay } from '../../../dataset/enum/Control'
import { ElementType } from '../../../dataset/enum/Element' import { ElementType } from '../../../dataset/enum/Element'
import { IContextMenuContext, IRegisterContextMenu } from '../../../interface/contextmenu/ContextMenu' import {
IContextMenuContext,
IRegisterContextMenu
} from '../../../interface/contextmenu/ContextMenu'
import { Command } from '../../command/Command' import { Command } from '../../command/Command'
export const imageMenus: IRegisterContextMenu[] = [ export const imageMenus: IRegisterContextMenu[] = [
{ {
i18nPath: 'contextmenu.image.change', i18nPath: 'contextmenu.image.change',
icon: 'image-change', icon: 'image-change',
when: (payload) => { when: payload => {
return !payload.isReadonly && !payload.editorHasSelection && payload.startElement?.type === ElementType.IMAGE return (
!payload.isReadonly &&
!payload.editorHasSelection &&
payload.startElement?.type === ElementType.IMAGE
)
}, },
callback: (command: Command) => { callback: (command: Command) => {
// 创建代理元素 // 创建代理元素
@ -31,8 +38,11 @@ export const imageMenus: IRegisterContextMenu[] = [
{ {
i18nPath: 'contextmenu.image.saveAs', i18nPath: 'contextmenu.image.saveAs',
icon: 'image', icon: 'image',
when: (payload) => { when: payload => {
return !payload.editorHasSelection && payload.startElement?.type === ElementType.IMAGE return (
!payload.editorHasSelection &&
payload.startElement?.type === ElementType.IMAGE
)
}, },
callback: (command: Command) => { callback: (command: Command) => {
command.executeSaveAsImageElement() command.executeSaveAsImageElement()
@ -40,23 +50,32 @@ export const imageMenus: IRegisterContextMenu[] = [
}, },
{ {
i18nPath: 'contextmenu.image.textWrap', i18nPath: 'contextmenu.image.textWrap',
when: (payload) => { when: payload => {
return !payload.isReadonly && !payload.editorHasSelection && payload.startElement?.type === ElementType.IMAGE return (
!payload.isReadonly &&
!payload.editorHasSelection &&
payload.startElement?.type === ElementType.IMAGE
)
}, },
childMenus: [ childMenus: [
{ {
i18nPath: 'contextmenu.image.textWrapType.embed', i18nPath: 'contextmenu.image.textWrapType.embed',
when: () => true, when: () => true,
callback: (command: Command, context: IContextMenuContext) => { callback: (command: Command, context: IContextMenuContext) => {
command.executeChangeImageDisplay(context.startElement!, ImageDisplay.BLOCK) command.executeChangeImageDisplay(
context.startElement!,
ImageDisplay.BLOCK
)
} }
}, },
{ {
i18nPath: 'contextmenu.image.textWrapType.upDown', i18nPath: 'contextmenu.image.textWrapType.upDown',
when: () => true, when: () => true,
callback: (command: Command, context: IContextMenuContext) => { callback: (command: Command, context: IContextMenuContext) => {
command.executeChangeImageDisplay(context.startElement!, ImageDisplay.INLINE) command.executeChangeImageDisplay(
context.startElement!,
ImageDisplay.INLINE
)
} }
} }
] ]

@ -10,7 +10,7 @@ export const tableMenus: IRegisterContextMenu[] = [
{ {
i18nPath: 'contextmenu.table.border', i18nPath: 'contextmenu.table.border',
icon: 'border-all', icon: 'border-all',
when: (payload) => { when: payload => {
return !payload.isReadonly && payload.isInTable return !payload.isReadonly && payload.isInTable
}, },
childMenus: [ childMenus: [
@ -43,7 +43,7 @@ export const tableMenus: IRegisterContextMenu[] = [
{ {
i18nPath: 'contextmenu.table.verticalAlign', i18nPath: 'contextmenu.table.verticalAlign',
icon: 'vertical-align', icon: 'vertical-align',
when: (payload) => { when: payload => {
return !payload.isReadonly && payload.isInTable return !payload.isReadonly && payload.isInTable
}, },
childMenus: [ childMenus: [
@ -76,7 +76,7 @@ export const tableMenus: IRegisterContextMenu[] = [
{ {
i18nPath: 'contextmenu.table.insertRowCol', i18nPath: 'contextmenu.table.insertRowCol',
icon: 'insert-row-col', icon: 'insert-row-col',
when: (payload) => { when: payload => {
return !payload.isReadonly && payload.isInTable return !payload.isReadonly && payload.isInTable
}, },
childMenus: [ childMenus: [
@ -117,7 +117,7 @@ export const tableMenus: IRegisterContextMenu[] = [
{ {
i18nPath: 'contextmenu.table.deleteRowCol', i18nPath: 'contextmenu.table.deleteRowCol',
icon: 'delete-row-col', icon: 'delete-row-col',
when: (payload) => { when: payload => {
return !payload.isReadonly && payload.isInTable return !payload.isReadonly && payload.isInTable
}, },
childMenus: [ childMenus: [
@ -150,7 +150,7 @@ export const tableMenus: IRegisterContextMenu[] = [
{ {
i18nPath: 'contextmenu.table.mergeCell', i18nPath: 'contextmenu.table.mergeCell',
icon: 'merge-cell', icon: 'merge-cell',
when: (payload) => { when: payload => {
return !payload.isReadonly && payload.isCrossRowCol return !payload.isReadonly && payload.isCrossRowCol
}, },
callback: (command: Command) => { callback: (command: Command) => {
@ -160,7 +160,7 @@ export const tableMenus: IRegisterContextMenu[] = [
{ {
i18nPath: 'contextmenu.table.mergeCancelCell', i18nPath: 'contextmenu.table.mergeCancelCell',
icon: 'merge-cancel-cell', icon: 'merge-cancel-cell',
when: (payload) => { when: payload => {
return !payload.isReadonly && payload.isInTable return !payload.isReadonly && payload.isInTable
}, },
callback: (command: Command) => { callback: (command: Command) => {

@ -8,15 +8,13 @@ import { CanvasEvent } from '../event/CanvasEvent'
import { Position } from '../position/Position' import { Position } from '../position/Position'
import { CursorAgent } from './CursorAgent' import { CursorAgent } from './CursorAgent'
export type IDrawCursorOption = ICursorOption & export type IDrawCursorOption = ICursorOption & {
{ isShow?: boolean
isShow?: boolean; isBlink?: boolean
isBlink?: boolean; isFocus?: boolean
isFocus?: boolean;
} }
export class Cursor { export class Cursor {
private readonly ANIMATION_CLASS = `${EDITOR_PREFIX}-cursor--animation` private readonly ANIMATION_CLASS = `${EDITOR_PREFIX}-cursor--animation`
private draw: Draw private draw: Draw
@ -56,8 +54,8 @@ export class Cursor {
return this.getAgentDom().value return this.getAgentDom().value
} }
public clearAgentDomValue(): string { public clearAgentDomValue() {
return this.getAgentDom().value = '' this.getAgentDom().value = ''
} }
private _blinkStart() { private _blinkStart() {
@ -97,9 +95,16 @@ export class Cursor {
// 设置光标代理 // 设置光标代理
const height = this.draw.getHeight() const height = this.draw.getHeight()
const pageGap = this.draw.getPageGap() const pageGap = this.draw.getPageGap()
const { metrics, coordinate: { leftTop, rightTop }, ascent, pageNo } = cursorPosition const {
metrics,
coordinate: { leftTop, rightTop },
ascent,
pageNo
} = cursorPosition
const zoneManager = this.draw.getZone() const zoneManager = this.draw.getZone()
const curPageNo = zoneManager.isMainActive() ? pageNo : this.draw.getPageNo() const curPageNo = zoneManager.isMainActive()
? pageNo
: this.draw.getPageNo()
const preY = curPageNo * (height + pageGap) const preY = curPageNo * (height + pageGap)
// 增加1/4字体大小 // 增加1/4字体大小
const offsetHeight = metrics.height / 4 const offsetHeight = metrics.height / 4
@ -112,11 +117,15 @@ export class Cursor {
}) })
} }
// fillText位置 + 文字基线到底部距离 - 模拟光标偏移量 // fillText位置 + 文字基线到底部距离 - 模拟光标偏移量
const descent = metrics.boundingBoxDescent < 0 ? 0 : metrics.boundingBoxDescent const descent =
const cursorTop = (leftTop[1] + ascent) + descent - (cursorHeight - offsetHeight) + preY metrics.boundingBoxDescent < 0 ? 0 : metrics.boundingBoxDescent
const cursorTop =
leftTop[1] + ascent + descent - (cursorHeight - offsetHeight) + preY
const cursorLeft = rightTop[0] const cursorLeft = rightTop[0]
agentCursorDom.style.left = `${cursorLeft}px` agentCursorDom.style.left = `${cursorLeft}px`
agentCursorDom.style.top = `${cursorTop + cursorHeight - CURSOR_AGENT_HEIGHT * scale}px` agentCursorDom.style.top = `${
cursorTop + cursorHeight - CURSOR_AGENT_HEIGHT * scale
}px`
// 模拟光标显示 // 模拟光标显示
if (!isShow) return if (!isShow) return
const isReadonly = this.draw.isReadonly() const isReadonly = this.draw.isReadonly()
@ -137,5 +146,4 @@ export class Cursor {
this.cursorDom.style.display = 'none' this.cursorDom.style.display = 'none'
this._clearBlinkTimeout() this._clearBlinkTimeout()
} }
} }

@ -10,7 +10,6 @@ import { Draw } from '../draw/Draw'
import { CanvasEvent } from '../event/CanvasEvent' import { CanvasEvent } from '../event/CanvasEvent'
export class CursorAgent { export class CursorAgent {
private draw: Draw private draw: Draw
private container: HTMLDivElement private container: HTMLDivElement
private agentCursorDom: HTMLTextAreaElement private agentCursorDom: HTMLTextAreaElement
@ -31,8 +30,14 @@ export class CursorAgent {
agentCursorDom.onkeydown = (evt: KeyboardEvent) => this._keyDown(evt) agentCursorDom.onkeydown = (evt: KeyboardEvent) => this._keyDown(evt)
agentCursorDom.oninput = debounce(this._input.bind(this), 0) agentCursorDom.oninput = debounce(this._input.bind(this), 0)
agentCursorDom.onpaste = (evt: ClipboardEvent) => this._paste(evt) agentCursorDom.onpaste = (evt: ClipboardEvent) => this._paste(evt)
agentCursorDom.addEventListener('compositionstart', this._compositionstart.bind(this)) agentCursorDom.addEventListener(
agentCursorDom.addEventListener('compositionend', this._compositionend.bind(this)) 'compositionstart',
this._compositionstart.bind(this)
)
agentCursorDom.addEventListener(
'compositionend',
this._compositionend.bind(this)
)
} }
public getAgentCursorDom(): HTMLTextAreaElement { public getAgentCursorDom(): HTMLTextAreaElement {
@ -84,13 +89,17 @@ export class CursorAgent {
let start = 0 let start = 0
while (start < pasteElementList.length) { while (start < pasteElementList.length) {
const pasteElement = pasteElementList[start] const pasteElement = pasteElementList[start]
if (anchorElement.titleId && /^\n/.test(pasteElement.value)) break if (anchorElement.titleId && /^\n/.test(pasteElement.value)) {
break
}
if (VIRTUAL_ELEMENT_TYPE.includes(pasteElement.type!)) { if (VIRTUAL_ELEMENT_TYPE.includes(pasteElement.type!)) {
pasteElementList.splice(start, 1) pasteElementList.splice(start, 1)
if (pasteElement.valueList) { if (pasteElement.valueList) {
for (let v = 0; v < pasteElement.valueList.length; v++) { for (let v = 0; v < pasteElement.valueList.length; v++) {
const element = pasteElement.valueList[v] const element = pasteElement.valueList[v]
if (element.value === ZERO || element.value === '\n') continue if (element.value === ZERO || element.value === '\n') {
continue
}
pasteElementList.splice(start, 0, element) pasteElementList.splice(start, 0, element)
start++ start++
} }
@ -145,5 +154,4 @@ export class CursorAgent {
private _compositionend(evt: CompositionEvent) { private _compositionend(evt: CompositionEvent) {
this.canvasEvent.compositionend(evt) this.canvasEvent.compositionend(evt)
} }
} }

@ -1,9 +1,25 @@
import { version } from '../../../../package.json' import { version } from '../../../../package.json'
import { ZERO } from '../../dataset/constant/Common' import { ZERO } from '../../dataset/constant/Common'
import { RowFlex } from '../../dataset/enum/Row' import { RowFlex } from '../../dataset/enum/Row'
import { IAppendElementListOption, IDrawOption, IDrawPagePayload, IDrawRowPayload, IGetValueOption, IPainterOption } from '../../interface/Draw' import {
import { IEditorData, IEditorOption, IEditorResult } from '../../interface/Editor' IAppendElementListOption,
import { IElement, IElementMetrics, IElementFillRect, IElementStyle } from '../../interface/Element' IDrawOption,
IDrawPagePayload,
IDrawRowPayload,
IGetValueOption,
IPainterOption
} from '../../interface/Draw'
import {
IEditorData,
IEditorOption,
IEditorResult
} from '../../interface/Editor'
import {
IElement,
IElementMetrics,
IElementFillRect,
IElementStyle
} from '../../interface/Element'
import { IRow, IRowElement } from '../../interface/Row' import { IRow, IRowElement } from '../../interface/Row'
import { deepClone, getUUID, nextTick } from '../../utils' import { deepClone, getUUID, nextTick } from '../../utils'
import { Cursor } from '../cursor/Cursor' import { Cursor } from '../cursor/Cursor'
@ -35,7 +51,14 @@ import { SubscriptParticle } from './particle/Subscript'
import { SeparatorParticle } from './particle/Separator' import { SeparatorParticle } from './particle/Separator'
import { PageBreakParticle } from './particle/PageBreak' import { PageBreakParticle } from './particle/PageBreak'
import { Watermark } from './frame/Watermark' import { Watermark } from './frame/Watermark'
import { EditorComponent, EditorMode, EditorZone, PageMode, PaperDirection, WordBreak } from '../../dataset/enum/Editor' import {
EditorComponent,
EditorMode,
EditorZone,
PageMode,
PaperDirection,
WordBreak
} from '../../dataset/enum/Editor'
import { Control } from './control/Control' import { Control } from './control/Control'
import { zipElementList } from '../../utils/element' import { zipElementList } from '../../utils/element'
import { CheckboxParticle } from './particle/CheckboxParticle' import { CheckboxParticle } from './particle/CheckboxParticle'
@ -58,7 +81,6 @@ import { Placeholder } from './frame/Placeholder'
import { WORD_LIKE_REG } from '../../dataset/constant/Regular' import { WORD_LIKE_REG } from '../../dataset/constant/Regular'
export class Draw { export class Draw {
private container: HTMLDivElement private container: HTMLDivElement
private pageContainer: HTMLDivElement private pageContainer: HTMLDivElement
private pageList: HTMLCanvasElement[] private pageList: HTMLCanvasElement[]
@ -276,7 +298,10 @@ export class Draw {
} }
public getPageNumberBottom(): number { public getPageNumberBottom(): number {
const { pageNumber: { bottom }, scale } = this.options const {
pageNumber: { bottom },
scale
} = this.options
return bottom * scale return bottom * scale
} }
@ -444,7 +469,8 @@ export class Draw {
public insertElementList(payload: IElement[]) { public insertElementList(payload: IElement[]) {
if (!payload.length) return if (!payload.length) return
const isPartRangeInControlOutside = this.control.isPartRangeInControlOutside() const isPartRangeInControlOutside =
this.control.isPartRangeInControlOutside()
if (isPartRangeInControlOutside) return if (isPartRangeInControlOutside) return
const { startIndex, endIndex } = this.range.getRange() const { startIndex, endIndex } = this.range.getRange()
if (!~startIndex && !~endIndex) return if (!~startIndex && !~endIndex) return
@ -475,7 +501,10 @@ export class Draw {
} }
} }
public appendElementList(elementList: IElement[], options: IAppendElementListOption = {}) { public appendElementList(
elementList: IElement[],
options: IAppendElementListOption = {}
) {
if (!elementList.length) return if (!elementList.length) return
formatElementList(elementList, { formatElementList(elementList, {
isHandleFirstElement: false, isHandleFirstElement: false,
@ -496,17 +525,30 @@ export class Draw {
}) })
} }
public spliceElementList(elementList: IElement[], start: number, deleteCount: number, ...items: IElement[]) { public spliceElementList(
elementList: IElement[],
start: number,
deleteCount: number,
...items: IElement[]
) {
if (deleteCount > 0) { if (deleteCount > 0) {
// 当最后元素与开始元素列表信息不一致时:清除当前列表信息 // 当最后元素与开始元素列表信息不一致时:清除当前列表信息
const endIndex = start + deleteCount const endIndex = start + deleteCount
const endElement = elementList[endIndex] const endElement = elementList[endIndex]
const endElementListId = endElement?.listId const endElementListId = endElement?.listId
if (endElementListId && elementList[start - 1]?.listId !== endElementListId) { if (
endElementListId &&
elementList[start - 1]?.listId !== endElementListId
) {
let startIndex = endIndex let startIndex = endIndex
while (startIndex < elementList.length) { while (startIndex < elementList.length) {
const curElement = elementList[startIndex] const curElement = elementList[startIndex]
if (curElement.listId !== endElementListId || curElement.value === ZERO) break if (
curElement.listId !== endElementListId ||
curElement.value === ZERO
) {
break
}
delete curElement.listId delete curElement.listId
delete curElement.listType delete curElement.listType
delete curElement.listStyle delete curElement.listStyle
@ -597,18 +639,23 @@ export class Draw {
} }
public getPainterStyle(): IElementStyle | null { public getPainterStyle(): IElementStyle | null {
return this.painterStyle && Object.keys(this.painterStyle).length ? this.painterStyle : null return this.painterStyle && Object.keys(this.painterStyle).length
? this.painterStyle
: null
} }
public getPainterOptions(): IPainterOption | null { public getPainterOptions(): IPainterOption | null {
return this.painterOptions return this.painterOptions
} }
public setPainterStyle(payload: IElementStyle | null, options?: IPainterOption) { public setPainterStyle(
payload: IElementStyle | null,
options?: IPainterOption
) {
this.painterStyle = payload this.painterStyle = payload
this.painterOptions = options || null this.painterOptions = options || null
if (this.getPainterStyle()) { if (this.getPainterStyle()) {
this.pageList.forEach(c => c.style.cursor = 'copy') this.pageList.forEach(c => (c.style.cursor = 'copy'))
} }
} }
@ -747,8 +794,14 @@ export class Draw {
// 数据 // 数据
const { pageNo } = options const { pageNo } = options
let mainElementList = this.elementList let mainElementList = this.elementList
if (Number.isInteger(pageNo) && pageNo! >= 0 && pageNo! < this.pageRowList.length) { if (
mainElementList = this.pageRowList[pageNo!].flatMap(row => row.elementList) Number.isInteger(pageNo) &&
pageNo! >= 0 &&
pageNo! < this.pageRowList.length
) {
mainElementList = this.pageRowList[pageNo!].flatMap(
row => row.elementList
)
} }
const data: IEditorData = { const data: IEditorData = {
header: zipElementList(this.headerElementList), header: zipElementList(this.headerElementList),
@ -850,11 +903,14 @@ export class Draw {
const { defaultSize, defaultFont } = this.options const { defaultSize, defaultFont } = this.options
const font = el.font || defaultFont const font = el.font || defaultFont
const size = el.actualSize || el.size || defaultSize const size = el.actualSize || el.size || defaultSize
return `${el.italic ? 'italic ' : ''}${el.bold ? 'bold ' : ''}${size * scale}px ${font}` return `${el.italic ? 'italic ' : ''}${el.bold ? 'bold ' : ''}${
size * scale
}px ${font}`
} }
public computeRowList(innerWidth: number, elementList: IElement[]) { public computeRowList(innerWidth: number, elementList: IElement[]) {
const { defaultSize, defaultRowMargin, scale, tdPadding, defaultTabWidth } = this.options const { defaultSize, defaultRowMargin, scale, tdPadding, defaultTabWidth } =
this.options
const defaultBasicRowMarginHeight = this.getDefaultBasicRowMarginHeight() const defaultBasicRowMarginHeight = this.getDefaultBasicRowMarginHeight()
const canvas = document.createElement('canvas') const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d') as CanvasRenderingContext2D const ctx = canvas.getContext('2d') as CanvasRenderingContext2D
@ -877,7 +933,8 @@ export class Draw {
for (let i = 0; i < elementList.length; i++) { for (let i = 0; i < elementList.length; i++) {
const curRow: IRow = rowList[rowList.length - 1] const curRow: IRow = rowList[rowList.length - 1]
const element = elementList[i] const element = elementList[i]
const rowMargin = defaultBasicRowMarginHeight * (element.rowMargin || defaultRowMargin) const rowMargin =
defaultBasicRowMarginHeight * (element.rowMargin || defaultRowMargin)
const metrics: IElementMetrics = { const metrics: IElementMetrics = {
width: 0, width: 0,
height: 0, height: 0,
@ -887,17 +944,24 @@ export class Draw {
// 实际可用宽度 // 实际可用宽度
const offsetX = element.listId ? listStyleMap.get(element.listId) || 0 : 0 const offsetX = element.listId ? listStyleMap.get(element.listId) || 0 : 0
const availableWidth = innerWidth - offsetX const availableWidth = innerWidth - offsetX
if (element.type === ElementType.IMAGE || element.type === ElementType.LATEX) { if (
element.type === ElementType.IMAGE ||
element.type === ElementType.LATEX
) {
const elementWidth = element.width! * scale const elementWidth = element.width! * scale
const elementHeight = element.height! * scale const elementHeight = element.height! * scale
// 图片超出尺寸后自适应 // 图片超出尺寸后自适应
const curRowWidth = element.imgDisplay === ImageDisplay.INLINE ? 0 : curRow.width const curRowWidth =
element.imgDisplay === ImageDisplay.INLINE ? 0 : curRow.width
if (curRowWidth + elementWidth > availableWidth) { if (curRowWidth + elementWidth > availableWidth) {
// 计算剩余大小 // 计算剩余大小
const surplusWidth = availableWidth - curRowWidth const surplusWidth = availableWidth - curRowWidth
const adaptiveWidth = surplusWidth > 0 ? surplusWidth : Math.min(elementWidth, availableWidth) const adaptiveWidth =
surplusWidth > 0
? surplusWidth
: Math.min(elementWidth, availableWidth)
element.width = adaptiveWidth element.width = adaptiveWidth
element.height = elementHeight * adaptiveWidth / elementWidth element.height = (elementHeight * adaptiveWidth) / elementWidth
metrics.width = element.width metrics.width = element.width
metrics.height = element.height metrics.height = element.height
metrics.boundingBoxDescent = element.height metrics.boundingBoxDescent = element.height
@ -917,7 +981,10 @@ export class Draw {
const tr = trList[t] const tr = trList[t]
for (let d = 0; d < tr.tdList.length; d++) { for (let d = 0; d < tr.tdList.length; d++) {
const td = tr.tdList[d] const td = tr.tdList[d]
const rowList = this.computeRowList((td.width! - tdGap) * scale, td.value) const rowList = this.computeRowList(
(td.width! - tdGap) * scale,
td.value
)
const rowHeight = rowList.reduce((pre, cur) => pre + cur.height, 0) const rowHeight = rowList.reduce((pre, cur) => pre + cur.height, 0)
td.rowList = rowList td.rowList = rowList
// 移除缩放导致的行高变化-渲染时会进行缩放调整 // 移除缩放导致的行高变化-渲染时会进行缩放调整
@ -957,9 +1024,10 @@ export class Draw {
const curTdHeight = td.mainHeight! const curTdHeight = td.mainHeight!
const curTdMinHeight = td.realMinHeight! const curTdMinHeight = td.realMinHeight!
// 获取最大可减少高度 // 获取最大可减少高度
const curReduceHeight = curTdHeight < curTdMinHeight const curReduceHeight =
? curTdRealHeight - curTdMinHeight curTdHeight < curTdMinHeight
: curTdRealHeight - curTdHeight ? curTdRealHeight - curTdMinHeight
: curTdRealHeight - curTdHeight
if (!~reduceHeight || curReduceHeight < reduceHeight) { if (!~reduceHeight || curReduceHeight < reduceHeight) {
reduceHeight = curReduceHeight reduceHeight = curReduceHeight
} }
@ -976,7 +1044,10 @@ export class Draw {
this.tableParticle.computeRowColInfo(element) this.tableParticle.computeRowColInfo(element)
// 计算出表格高度 // 计算出表格高度
const tableHeight = trList.reduce((pre, cur) => pre + cur.height, 0) const tableHeight = trList.reduce((pre, cur) => pre + cur.height, 0)
const tableWidth = element.colgroup!.reduce((pre, cur) => pre + cur.width, 0) const tableWidth = element.colgroup!.reduce(
(pre, cur) => pre + cur.width,
0
)
element.width = tableWidth element.width = tableWidth
element.height = tableHeight element.height = tableHeight
const elementWidth = tableWidth * scale const elementWidth = tableWidth * scale
@ -991,7 +1062,10 @@ export class Draw {
let curPagePreHeight = marginHeight let curPagePreHeight = marginHeight
for (let r = 0; r < rowList.length; r++) { for (let r = 0; r < rowList.length; r++) {
const row = rowList[r] const row = rowList[r]
if (row.height + curPagePreHeight > height || rowList[r - 1]?.isPageBreak) { if (
row.height + curPagePreHeight > height ||
rowList[r - 1]?.isPageBreak
) {
curPagePreHeight = marginHeight + row.height curPagePreHeight = marginHeight + row.height
} else { } else {
curPagePreHeight += row.height curPagePreHeight += row.height
@ -1009,7 +1083,10 @@ export class Draw {
for (let r = 0; r < trList.length; r++) { for (let r = 0; r < trList.length; r++) {
const tr = trList[r] const tr = trList[r]
const trHeight = tr.height * scale const trHeight = tr.height * scale
if (curPagePreHeight + rowMarginHeight + preTrHeight + trHeight > height) { if (
curPagePreHeight + rowMarginHeight + preTrHeight + trHeight >
height
) {
// 是否跨列 // 是否跨列
if (element.colgroup?.length !== tr.tdList.length) { if (element.colgroup?.length !== tr.tdList.length) {
deleteCount = 0 deleteCount = 0
@ -1024,7 +1101,10 @@ export class Draw {
} }
if (deleteCount) { if (deleteCount) {
const cloneTrList = trList.splice(deleteStart, deleteCount) const cloneTrList = trList.splice(deleteStart, deleteCount)
const cloneTrHeight = cloneTrList.reduce((pre, cur) => pre + cur.height, 0) const cloneTrHeight = cloneTrList.reduce(
(pre, cur) => pre + cur.height,
0
)
element.height -= cloneTrHeight element.height -= cloneTrHeight
metrics.height -= cloneTrHeight metrics.height -= cloneTrHeight
metrics.boundingBoxDescent -= cloneTrHeight metrics.boundingBoxDescent -= cloneTrHeight
@ -1035,7 +1115,10 @@ export class Draw {
this.spliceElementList(elementList, i + 1, 0, cloneElement) this.spliceElementList(elementList, i + 1, 0, cloneElement)
// 换页的是当前行则改变上下文 // 换页的是当前行则改变上下文
const positionContext = this.position.getPositionContext() const positionContext = this.position.getPositionContext()
if (positionContext.isTable && positionContext.trIndex === deleteStart) { if (
positionContext.isTable &&
positionContext.trIndex === deleteStart
) {
positionContext.index! += 1 positionContext.index! += 1
positionContext.trIndex = 0 positionContext.trIndex = 0
this.position.setPositionContext(positionContext) this.position.setPositionContext(positionContext)
@ -1079,7 +1162,10 @@ export class Draw {
} else { } else {
// 设置上下标真实字体尺寸 // 设置上下标真实字体尺寸
const size = element.size || defaultSize const size = element.size || defaultSize
if (element.type === ElementType.SUPERSCRIPT || element.type === ElementType.SUBSCRIPT) { if (
element.type === ElementType.SUPERSCRIPT ||
element.type === ElementType.SUBSCRIPT
) {
element.actualSize = Math.ceil(size * 0.6) element.actualSize = Math.ceil(size * 0.6)
} }
metrics.height = (element.actualSize || size) * scale metrics.height = (element.actualSize || size) * scale
@ -1089,19 +1175,29 @@ export class Draw {
if (element.letterSpacing) { if (element.letterSpacing) {
metrics.width += element.letterSpacing * scale metrics.width += element.letterSpacing * scale
} }
metrics.boundingBoxAscent = (element.value === ZERO ? defaultSize : fontMetrics.actualBoundingBoxAscent) * scale metrics.boundingBoxAscent =
metrics.boundingBoxDescent = fontMetrics.actualBoundingBoxDescent * scale (element.value === ZERO
? defaultSize
: fontMetrics.actualBoundingBoxAscent) * scale
metrics.boundingBoxDescent =
fontMetrics.actualBoundingBoxDescent * scale
if (element.type === ElementType.SUPERSCRIPT) { if (element.type === ElementType.SUPERSCRIPT) {
metrics.boundingBoxAscent += metrics.height / 2 metrics.boundingBoxAscent += metrics.height / 2
} else if (element.type === ElementType.SUBSCRIPT) { } else if (element.type === ElementType.SUBSCRIPT) {
metrics.boundingBoxDescent += metrics.height / 2 metrics.boundingBoxDescent += metrics.height / 2
} }
} }
const ascent = (element.imgDisplay !== ImageDisplay.INLINE && element.type === ElementType.IMAGE) || const ascent =
(element.imgDisplay !== ImageDisplay.INLINE &&
element.type === ElementType.IMAGE) ||
element.type === ElementType.LATEX element.type === ElementType.LATEX
? metrics.height + rowMargin ? metrics.height + rowMargin
: metrics.boundingBoxAscent + rowMargin : metrics.boundingBoxAscent + rowMargin
const height = rowMargin + metrics.boundingBoxAscent + metrics.boundingBoxDescent + rowMargin const height =
rowMargin +
metrics.boundingBoxAscent +
metrics.boundingBoxDescent +
rowMargin
const rowElement: IRowElement = Object.assign(element, { const rowElement: IRowElement = Object.assign(element, {
metrics, metrics,
style: this._getFont(element, scale) style: this._getFont(element, scale)
@ -1119,12 +1215,19 @@ export class Draw {
// 英文单词 // 英文单词
const word = `${preElement?.value || ''}${element.value}` const word = `${preElement?.value || ''}${element.value}`
if (WORD_LIKE_REG.test(word)) { if (WORD_LIKE_REG.test(word)) {
const { width, endElement } = this.textParticle.measureWord(ctx, elementList, i) const { width, endElement } = this.textParticle.measureWord(
ctx,
elementList,
i
)
curRowWidth += width curRowWidth += width
nextElement = endElement nextElement = endElement
} }
// 标点符号 // 标点符号
curRowWidth += this.textParticle.measurePunctuationWidth(ctx, nextElement) curRowWidth += this.textParticle.measurePunctuationWidth(
ctx,
nextElement
)
} }
} }
// 列表信息 // 列表信息
@ -1137,15 +1240,15 @@ export class Draw {
} }
listId = element.listId listId = element.listId
if ( if (
element.type === ElementType.TABLE element.type === ElementType.TABLE ||
|| preElement?.type === ElementType.TABLE preElement?.type === ElementType.TABLE ||
|| preElement?.type === ElementType.BLOCK preElement?.type === ElementType.BLOCK ||
|| element.type === ElementType.BLOCK element.type === ElementType.BLOCK ||
|| preElement?.imgDisplay === ImageDisplay.INLINE preElement?.imgDisplay === ImageDisplay.INLINE ||
|| element.imgDisplay === ImageDisplay.INLINE element.imgDisplay === ImageDisplay.INLINE ||
|| curRowWidth > availableWidth curRowWidth > availableWidth ||
|| (i !== 0 && element.value === ZERO) (i !== 0 && element.value === ZERO) ||
|| (preElement?.listId !== element.listId) preElement?.listId !== element.listId
) { ) {
// 减小行元素前第一行空行行高 // 减小行元素前第一行空行行高
if ( if (
@ -1156,8 +1259,12 @@ export class Draw {
curRow.height = defaultBasicRowMarginHeight curRow.height = defaultBasicRowMarginHeight
} }
// 两端对齐 // 两端对齐
if (preElement?.rowFlex === RowFlex.ALIGNMENT && curRowWidth > availableWidth) { if (
const gap = (availableWidth - curRow.width) / curRow.elementList.length preElement?.rowFlex === RowFlex.ALIGNMENT &&
curRowWidth > availableWidth
) {
const gap =
(availableWidth - curRow.width) / curRow.elementList.length
for (let e = 0; e < curRow.elementList.length; e++) { for (let e = 0; e < curRow.elementList.length; e++) {
const el = curRow.elementList[e] const el = curRow.elementList[e]
el.metrics.width += gap el.metrics.width += gap
@ -1217,7 +1324,10 @@ export class Draw {
} else { } else {
for (let i = 0; i < this.rowList.length; i++) { for (let i = 0; i < this.rowList.length; i++) {
const row = this.rowList[i] const row = this.rowList[i]
if (row.height + pageHeight > height || this.rowList[i - 1]?.isPageBreak) { if (
row.height + pageHeight > height ||
this.rowList[i - 1]?.isPageBreak
) {
pageHeight = marginHeight + row.height pageHeight = marginHeight + row.height
pageRowList.push([row]) pageRowList.push([row])
pageNo++ pageNo++
@ -1238,7 +1348,8 @@ export class Draw {
} }
public drawRow(ctx: CanvasRenderingContext2D, payload: IDrawRowPayload) { public drawRow(ctx: CanvasRenderingContext2D, payload: IDrawRowPayload) {
const { rowList, pageNo, elementList, positionList, startIndex, zone } = payload const { rowList, pageNo, elementList, positionList, startIndex, zone } =
payload
const { scale, tdPadding } = this.options const { scale, tdPadding } = this.options
const { isCrossRowCol, tableId } = this.range.getRange() const { isCrossRowCol, tableId } = this.range.getRange()
let index = startIndex let index = startIndex
@ -1266,10 +1377,21 @@ export class Draw {
// 元素高亮记录 // 元素高亮记录
if (element.highlight) { if (element.highlight) {
// 高亮元素相连需立即绘制,并记录下一元素坐标 // 高亮元素相连需立即绘制,并记录下一元素坐标
if (preElement && preElement.highlight && preElement.highlight !== element.highlight) { if (
preElement &&
preElement.highlight &&
preElement.highlight !== element.highlight
) {
this.highlight.render(ctx) this.highlight.render(ctx)
} }
this.highlight.recordFillInfo(ctx, x, y, metrics.width, curRow.height, element.highlight) this.highlight.recordFillInfo(
ctx,
x,
y,
metrics.width,
curRow.height,
element.highlight
)
} else if (preElement?.highlight) { } else if (preElement?.highlight) {
this.highlight.render(ctx) this.highlight.render(ctx)
} }
@ -1325,19 +1447,40 @@ export class Draw {
} }
// 下划线记录 // 下划线记录
if (element.underline) { if (element.underline) {
this.underline.recordFillInfo(ctx, x, y + curRow.height, metrics.width, 0, element.color) this.underline.recordFillInfo(
ctx,
x,
y + curRow.height,
metrics.width,
0,
element.color
)
} else if (preElement?.underline) { } else if (preElement?.underline) {
this.underline.render(ctx) this.underline.render(ctx)
} }
// 删除线记录 // 删除线记录
if (element.strikeout) { if (element.strikeout) {
this.strikeout.recordFillInfo(ctx, x, y + curRow.height / 2, metrics.width) this.strikeout.recordFillInfo(
ctx,
x,
y + curRow.height / 2,
metrics.width
)
} else if (preElement?.strikeout) { } else if (preElement?.strikeout) {
this.strikeout.render(ctx) this.strikeout.render(ctx)
} }
// 选区记录 // 选区记录
const { zone: currentZone, startIndex, endIndex } = this.range.getRange() const {
if (currentZone === zone && startIndex !== endIndex && startIndex <= index && index <= endIndex) { zone: currentZone,
startIndex,
endIndex
} = this.range.getRange()
if (
currentZone === zone &&
startIndex !== endIndex &&
startIndex <= index &&
index <= endIndex
) {
// 从行尾开始-绘制最小宽度 // 从行尾开始-绘制最小宽度
if (startIndex === index) { if (startIndex === index) {
const nextElement = elementList[startIndex + 1] const nextElement = elementList[startIndex + 1]
@ -1351,8 +1494,8 @@ export class Draw {
const positionContext = this.position.getPositionContext() const positionContext = this.position.getPositionContext()
// 表格需限定上下文 // 表格需限定上下文
if ( if (
(!positionContext.isTable && !element.tdId) (!positionContext.isTable && !element.tdId) ||
|| positionContext.tdId === element.tdId positionContext.tdId === element.tdId
) { ) {
let rangeWidth = metrics.width let rangeWidth = metrics.width
// 最小选区宽度 // 最小选区宽度
@ -1392,7 +1535,11 @@ export class Draw {
} }
// 绘制列表样式 // 绘制列表样式
if (curRow.isList) { if (curRow.isList) {
this.listParticle.drawListStyle(ctx, curRow, positionList[curRow.startIndex]) this.listParticle.drawListStyle(
ctx,
curRow,
positionList[curRow.startIndex]
)
} }
// 绘制富文本及文字 // 绘制富文本及文字
this._drawRichText(ctx) this._drawRichText(ctx)
@ -1401,8 +1548,16 @@ export class Draw {
const { x, y, width, height } = rangeRecord const { x, y, width, height } = rangeRecord
this.range.render(ctx, x, y, width, height) this.range.render(ctx, x, y, width, height)
} }
if (isCrossRowCol && tableRangeElement && tableRangeElement.id === tableId) { if (
const { coordinate: { leftTop: [x, y] } } = positionList[curRow.startIndex] isCrossRowCol &&
tableRangeElement &&
tableRangeElement.id === tableId
) {
const {
coordinate: {
leftTop: [x, y]
}
} = positionList[curRow.startIndex]
this.tableParticle.drawRange(ctx, tableRangeElement, x, y) this.tableParticle.drawRange(ctx, tableRangeElement, x, y)
} }
} }
@ -1474,7 +1629,7 @@ export class Draw {
const positionList = this.position.getOriginalMainPositionList() const positionList = this.position.getOriginalMainPositionList()
const elementList = this.getOriginalMainElementList() const elementList = this.getOriginalMainElementList()
this._disconnectLazyRender() this._disconnectLazyRender()
this.lazyRenderIntersectionObserver = new IntersectionObserver((entries) => { this.lazyRenderIntersectionObserver = new IntersectionObserver(entries => {
entries.forEach(entry => { entries.forEach(entry => {
if (entry.isIntersecting) { if (entry.isIntersecting) {
const index = Number((<HTMLCanvasElement>entry.target).dataset.index) const index = Number((<HTMLCanvasElement>entry.target).dataset.index)
@ -1555,7 +1710,8 @@ export class Draw {
if (prePageCount > curPageCount) { if (prePageCount > curPageCount) {
const deleteCount = prePageCount - curPageCount const deleteCount = prePageCount - curPageCount
this.ctxList.splice(curPageCount, deleteCount) this.ctxList.splice(curPageCount, deleteCount)
this.pageList.splice(curPageCount, deleteCount) this.pageList
.splice(curPageCount, deleteCount)
.forEach(page => page.remove()) .forEach(page => page.remove())
} }
// 绘制元素 // 绘制元素
@ -1572,14 +1728,17 @@ export class Draw {
if (positionContext.isTable) { if (positionContext.isTable) {
const { index, trIndex, tdIndex } = positionContext const { index, trIndex, tdIndex } = positionContext
const elementList = this.getOriginalElementList() const elementList = this.getOriginalElementList()
const tablePositionList = elementList[index!].trList?.[trIndex!].tdList[tdIndex!].positionList const tablePositionList =
elementList[index!].trList?.[trIndex!].tdList[tdIndex!].positionList
if (curIndex === undefined && tablePositionList) { if (curIndex === undefined && tablePositionList) {
curIndex = tablePositionList.length - 1 curIndex = tablePositionList.length - 1
} }
const tablePosition = tablePositionList?.[curIndex!] const tablePosition = tablePositionList?.[curIndex!]
this.position.setCursorPosition(tablePosition || null) this.position.setCursorPosition(tablePosition || null)
} else { } else {
this.position.setCursorPosition(curIndex !== undefined ? positionList[curIndex] : null) this.position.setCursorPosition(
curIndex !== undefined ? positionList[curIndex] : null
)
} }
this.cursor.drawCursor() this.cursor.drawCursor()
} }
@ -1631,5 +1790,4 @@ export class Draw {
this.scrollObserver.removeEvent() this.scrollObserver.removeEvent()
this.selectionObserver.removeEvent() this.selectionObserver.removeEvent()
} }
} }

@ -1,10 +1,19 @@
import { ControlComponent, ControlType } from '../../../dataset/enum/Control' import { ControlComponent, ControlType } from '../../../dataset/enum/Control'
import { ElementType } from '../../../dataset/enum/Element' import { ElementType } from '../../../dataset/enum/Element'
import { IControl, IControlInitOption, IControlInstance, IControlOption } from '../../../interface/Control' import {
IControl,
IControlInitOption,
IControlInstance,
IControlOption
} from '../../../interface/Control'
import { IElement, IElementPosition } from '../../../interface/Element' import { IElement, IElementPosition } from '../../../interface/Element'
import { IRange } from '../../../interface/Range' import { IRange } from '../../../interface/Range'
import { deepClone, splitText } from '../../../utils' import { deepClone, splitText } from '../../../utils'
import { formatElementContext, pickElementAttr, zipElementList } from '../../../utils/element' import {
formatElementContext,
pickElementAttr,
zipElementList
} from '../../../utils/element'
import { Listener } from '../../listener/Listener' import { Listener } from '../../listener/Listener'
import { RangeManager } from '../../range/RangeManager' import { RangeManager } from '../../range/RangeManager'
import { Draw } from '../Draw' import { Draw } from '../Draw'
@ -13,11 +22,10 @@ import { SelectControl } from './select/SelectControl'
import { TextControl } from './text/TextControl' import { TextControl } from './text/TextControl'
interface IMoveCursorResult { interface IMoveCursorResult {
newIndex: number; newIndex: number
newElement: IElement; newElement: IElement
} }
export class Control { export class Control {
private draw: Draw private draw: Draw
private range: RangeManager private range: RangeManager
private listener: Listener private listener: Listener
@ -44,8 +52,9 @@ export class Control {
const startElement = elementList[startIndex] const startElement = elementList[startIndex]
const endElement = elementList[endIndex] const endElement = elementList[endIndex]
if ( if (
(startElement.type === ElementType.CONTROL || endElement.type === ElementType.CONTROL) (startElement.type === ElementType.CONTROL ||
&& startElement.controlId !== endElement.controlId endElement.type === ElementType.CONTROL) &&
startElement.controlId !== endElement.controlId
) { ) {
return true return true
} }
@ -195,8 +204,8 @@ export class Control {
while (startIndex < elementList.length) { while (startIndex < elementList.length) {
const nextElement = elementList[startIndex] const nextElement = elementList[startIndex]
if ( if (
nextElement.controlId !== element.controlId nextElement.controlId !== element.controlId ||
|| nextElement.controlComponent !== ControlComponent.PREFIX nextElement.controlComponent !== ControlComponent.PREFIX
) { ) {
return { return {
newIndex: startIndex - 1, newIndex: startIndex - 1,
@ -211,8 +220,8 @@ export class Control {
while (startIndex > 0) { while (startIndex > 0) {
const preElement = elementList[startIndex] const preElement = elementList[startIndex]
if ( if (
preElement.controlId !== element.controlId preElement.controlId !== element.controlId ||
|| preElement.controlComponent === ControlComponent.PREFIX preElement.controlComponent === ControlComponent.PREFIX
) { ) {
return { return {
newIndex: startIndex, newIndex: startIndex,
@ -260,7 +269,11 @@ export class Control {
if (!~leftIndex && !~rightIndex) return startIndex if (!~leftIndex && !~rightIndex) return startIndex
leftIndex = ~leftIndex ? leftIndex : 0 leftIndex = ~leftIndex ? leftIndex : 0
// 删除元素 // 删除元素
this.draw.spliceElementList(elementList, leftIndex + 1, rightIndex - leftIndex) this.draw.spliceElementList(
elementList,
leftIndex + 1,
rightIndex - leftIndex
)
return leftIndex return leftIndex
} }
@ -302,7 +315,12 @@ export class Control {
color: this.options.placeholderColor color: this.options.placeholderColor
} }
formatElementContext(elementList, [newElement], startIndex) formatElementContext(elementList, [newElement], startIndex)
this.draw.spliceElementList(elementList, startIndex + p + 1, 0, newElement) this.draw.spliceElementList(
elementList,
startIndex + p + 1,
0,
newElement
)
} }
} }
@ -326,5 +344,4 @@ export class Control {
} }
return this.activeControl.cut() return this.activeControl.cut()
} }
} }

@ -5,7 +5,6 @@ import { IElement } from '../../../../interface/Element'
import { Control } from '../Control' import { Control } from '../Control'
export class CheckboxControl implements IControlInstance { export class CheckboxControl implements IControlInstance {
private element: IElement private element: IElement
private control: Control private control: Control
@ -124,5 +123,4 @@ export class CheckboxControl implements IControlInstance {
public cut(): number { public cut(): number {
return -1 return -1
} }
} }

@ -1,4 +1,7 @@
import { EDITOR_COMPONENT, EDITOR_PREFIX } from '../../../../dataset/constant/Editor' import {
EDITOR_COMPONENT,
EDITOR_PREFIX
} from '../../../../dataset/constant/Editor'
import { ControlComponent } from '../../../../dataset/enum/Control' import { ControlComponent } from '../../../../dataset/enum/Control'
import { EditorComponent } from '../../../../dataset/enum/Editor' import { EditorComponent } from '../../../../dataset/enum/Editor'
import { KeyMap } from '../../../../dataset/enum/KeyMap' import { KeyMap } from '../../../../dataset/enum/KeyMap'
@ -9,7 +12,6 @@ import { formatElementContext } from '../../../../utils/element'
import { Control } from '../Control' import { Control } from '../Control'
export class SelectControl implements IControlInstance { export class SelectControl implements IControlInstance {
private element: IElement private element: IElement
private control: Control private control: Control
private isPopup: boolean private isPopup: boolean
@ -105,8 +107,9 @@ export class SelectControl implements IControlInstance {
return this.clearSelect() return this.clearSelect()
} else { } else {
const endNextElement = elementList[endIndex + 1] const endNextElement = elementList[endIndex + 1]
if ((startElement.controlComponent === ControlComponent.PREFIX && if (
endNextElement.controlComponent === ControlComponent.PLACEHOLDER) || (startElement.controlComponent === ControlComponent.PREFIX &&
endNextElement.controlComponent === ControlComponent.PLACEHOLDER) ||
endNextElement.controlComponent === ControlComponent.POSTFIX || endNextElement.controlComponent === ControlComponent.POSTFIX ||
startElement.controlComponent === ControlComponent.PLACEHOLDER startElement.controlComponent === ControlComponent.PLACEHOLDER
) { ) {
@ -232,7 +235,12 @@ export class SelectControl implements IControlInstance {
} }
selectPopupContainer.append(ul) selectPopupContainer.append(ul)
// 定位 // 定位
const { coordinate: { leftTop: [left, top] }, lineHeight } = position const {
coordinate: {
leftTop: [left, top]
},
lineHeight
} = position
const preY = this.control.getPreY() const preY = this.control.getPreY()
selectPopupContainer.style.left = `${left}px` selectPopupContainer.style.left = `${left}px`
selectPopupContainer.style.top = `${top + preY + lineHeight}px` selectPopupContainer.style.top = `${top + preY + lineHeight}px`
@ -246,7 +254,9 @@ export class SelectControl implements IControlInstance {
if (this.isPopup) return if (this.isPopup) return
const { startIndex } = this.control.getRange() const { startIndex } = this.control.getRange()
const elementList = this.control.getElementList() const elementList = this.control.getElementList()
if (elementList[startIndex + 1]?.controlId !== this.element.controlId) return if (elementList[startIndex + 1]?.controlId !== this.element.controlId) {
return
}
this._createSelectPopupDom() this._createSelectPopupDom()
this.isPopup = true this.isPopup = true
} }
@ -256,5 +266,4 @@ export class SelectControl implements IControlInstance {
this.selectDom?.remove() this.selectDom?.remove()
this.isPopup = false this.isPopup = false
} }
} }

@ -6,7 +6,6 @@ import { formatElementContext } from '../../../../utils/element'
import { Control } from '../Control' import { Control } from '../Control'
export class TextControl implements IControlInstance { export class TextControl implements IControlInstance {
private element: IElement private element: IElement
private control: Control private control: Control
@ -99,7 +98,11 @@ export class TextControl implements IControlInstance {
if (evt.key === KeyMap.Backspace) { if (evt.key === KeyMap.Backspace) {
// 移除选区元素 // 移除选区元素
if (startIndex !== endIndex) { if (startIndex !== endIndex) {
draw.spliceElementList(elementList, startIndex + 1, endIndex - startIndex) draw.spliceElementList(
elementList,
startIndex + 1,
endIndex - startIndex
)
const value = this.getValue() const value = this.getValue()
if (!value.length) { if (!value.length) {
this.control.addPlaceholder(startIndex) this.control.addPlaceholder(startIndex)
@ -126,7 +129,11 @@ export class TextControl implements IControlInstance {
} else if (evt.key === KeyMap.Delete) { } else if (evt.key === KeyMap.Delete) {
// 移除选区元素 // 移除选区元素
if (startIndex !== endIndex) { if (startIndex !== endIndex) {
draw.spliceElementList(elementList, startIndex + 1, endIndex - startIndex) draw.spliceElementList(
elementList,
startIndex + 1,
endIndex - startIndex
)
const value = this.getValue() const value = this.getValue()
if (!value.length) { if (!value.length) {
this.control.addPlaceholder(startIndex) this.control.addPlaceholder(startIndex)
@ -134,8 +141,9 @@ export class TextControl implements IControlInstance {
return startIndex return startIndex
} else { } else {
const endNextElement = elementList[endIndex + 1] const endNextElement = elementList[endIndex + 1]
if ((startElement.controlComponent === ControlComponent.PREFIX && if (
endNextElement.controlComponent === ControlComponent.PLACEHOLDER) || (startElement.controlComponent === ControlComponent.PREFIX &&
endNextElement.controlComponent === ControlComponent.PLACEHOLDER) ||
endNextElement.controlComponent === ControlComponent.POSTFIX || endNextElement.controlComponent === ControlComponent.POSTFIX ||
startElement.controlComponent === ControlComponent.PLACEHOLDER startElement.controlComponent === ControlComponent.PLACEHOLDER
) { ) {
@ -170,5 +178,4 @@ export class TextControl implements IControlInstance {
} }
return startIndex return startIndex
} }
} }

@ -1,7 +1,6 @@
import { Draw } from '../Draw' import { Draw } from '../Draw'
export class Background { export class Background {
private draw: Draw private draw: Draw
constructor(draw: Draw) { constructor(draw: Draw) {
@ -16,5 +15,4 @@ export class Background {
ctx.fillRect(0, 0, width, height) ctx.fillRect(0, 0, width, height)
ctx.restore() ctx.restore()
} }
} }

@ -8,7 +8,6 @@ import { Position } from '../../position/Position'
import { Draw } from '../Draw' import { Draw } from '../Draw'
export class Footer { export class Footer {
private draw: Draw private draw: Draw
private position: Position private position: Position
private options: DeepRequired<IEditorOption> private options: DeepRequired<IEditorOption>
@ -81,13 +80,18 @@ export class Footer {
} }
public getFooterBottom(): number { public getFooterBottom(): number {
const { footer: { bottom, disabled }, scale } = this.options const {
footer: { bottom, disabled },
scale
} = this.options
if (disabled) return 0 if (disabled) return 0
return Math.floor(bottom * scale) return Math.floor(bottom * scale)
} }
public getMaxHeight(): number { public getMaxHeight(): number {
const { footer: { maxHeightRadio } } = this.options const {
footer: { maxHeightRadio }
} = this.options
const height = this.draw.getHeight() const height = this.draw.getHeight()
return Math.floor(height * maxHeightRadioMapping[maxHeightRadio]) return Math.floor(height * maxHeightRadioMapping[maxHeightRadio])
} }
@ -136,5 +140,4 @@ export class Footer {
zone: EditorZone.FOOTER zone: EditorZone.FOOTER
}) })
} }
} }

@ -8,7 +8,6 @@ import { Position } from '../../position/Position'
import { Draw } from '../Draw' import { Draw } from '../Draw'
export class Header { export class Header {
private draw: Draw private draw: Draw
private position: Position private position: Position
private options: DeepRequired<IEditorOption> private options: DeepRequired<IEditorOption>
@ -78,13 +77,18 @@ export class Header {
} }
public getHeaderTop(): number { public getHeaderTop(): number {
const { header: { top, disabled }, scale } = this.options const {
header: { top, disabled },
scale
} = this.options
if (disabled) return 0 if (disabled) return 0
return Math.floor(top * scale) return Math.floor(top * scale)
} }
public getMaxHeight(): number { public getMaxHeight(): number {
const { header: { maxHeightRadio } } = this.options const {
header: { maxHeightRadio }
} = this.options
const height = this.draw.getHeight() const height = this.draw.getHeight()
return Math.floor(height * maxHeightRadioMapping[maxHeightRadio]) return Math.floor(height * maxHeightRadioMapping[maxHeightRadio])
} }
@ -133,5 +137,4 @@ export class Header {
zone: EditorZone.HEADER zone: EditorZone.HEADER
}) })
} }
} }

@ -3,7 +3,6 @@ import { IEditorOption } from '../../../interface/Editor'
import { Draw } from '../Draw' import { Draw } from '../Draw'
export class Margin { export class Margin {
private draw: Draw private draw: Draw
private options: Required<IEditorOption> private options: Required<IEditorOption>
@ -15,9 +14,10 @@ export class Margin {
public render(ctx: CanvasRenderingContext2D, pageNo: number) { public render(ctx: CanvasRenderingContext2D, pageNo: number) {
const { marginIndicatorColor, pageMode } = this.options const { marginIndicatorColor, pageMode } = this.options
const width = this.draw.getWidth() const width = this.draw.getWidth()
const height = pageMode === PageMode.CONTINUITY const height =
? this.draw.getCanvasHeight(pageNo) pageMode === PageMode.CONTINUITY
: this.draw.getHeight() ? this.draw.getCanvasHeight(pageNo)
: this.draw.getHeight()
const margins = this.draw.getMargins() const margins = this.draw.getMargins()
const marginIndicatorSize = this.draw.getMarginIndicatorSize() const marginIndicatorSize = this.draw.getMarginIndicatorSize()
ctx.save() ctx.save()
@ -27,7 +27,10 @@ export class Margin {
const leftTopPoint: [number, number] = [margins[3], margins[0]] const leftTopPoint: [number, number] = [margins[3], margins[0]]
const rightTopPoint: [number, number] = [width - margins[1], margins[0]] const rightTopPoint: [number, number] = [width - margins[1], margins[0]]
const leftBottomPoint: [number, number] = [margins[3], height - margins[2]] const leftBottomPoint: [number, number] = [margins[3], height - margins[2]]
const rightBottomPoint: [number, number] = [width - margins[1], height - margins[2]] const rightBottomPoint: [number, number] = [
width - margins[1],
height - margins[2]
]
// 上左 // 上左
ctx.moveTo(leftTopPoint[0] - marginIndicatorSize, leftTopPoint[1]) ctx.moveTo(leftTopPoint[0] - marginIndicatorSize, leftTopPoint[1])
ctx.lineTo(...leftTopPoint) ctx.lineTo(...leftTopPoint)
@ -47,5 +50,4 @@ export class Margin {
ctx.stroke() ctx.stroke()
ctx.restore() ctx.restore()
} }
} }

@ -8,7 +8,6 @@ import { convertNumberToChinese } from '../../../utils'
import { Draw } from '../Draw' import { Draw } from '../Draw'
export class PageNumber { export class PageNumber {
private draw: Draw private draw: Draw
private options: DeepRequired<IEditorOption> private options: DeepRequired<IEditorOption>
@ -21,7 +20,16 @@ export class PageNumber {
const { const {
scale, scale,
pageMode, pageMode,
pageNumber: { size, font, color, rowFlex, numberType, format, startPageNo, fromPageNo } pageNumber: {
size,
font,
color,
rowFlex,
numberType,
format,
startPageNo,
fromPageNo
}
} = this.options } = this.options
if (pageNo < fromPageNo) return if (pageNo < fromPageNo) return
// 处理页码格式 // 处理页码格式
@ -29,20 +37,27 @@ export class PageNumber {
const pageNoReg = new RegExp(FORMAT_PLACEHOLDER.PAGE_NO) const pageNoReg = new RegExp(FORMAT_PLACEHOLDER.PAGE_NO)
if (pageNoReg.test(text)) { if (pageNoReg.test(text)) {
const realPageNo = pageNo + startPageNo - fromPageNo const realPageNo = pageNo + startPageNo - fromPageNo
const pageNoText = numberType === NumberType.CHINESE ? convertNumberToChinese(realPageNo) : `${realPageNo}` const pageNoText =
numberType === NumberType.CHINESE
? convertNumberToChinese(realPageNo)
: `${realPageNo}`
text = text.replace(pageNoReg, pageNoText) text = text.replace(pageNoReg, pageNoText)
} }
const pageCountReg = new RegExp(FORMAT_PLACEHOLDER.PAGE_COUNT) const pageCountReg = new RegExp(FORMAT_PLACEHOLDER.PAGE_COUNT)
if (pageCountReg.test(text)) { if (pageCountReg.test(text)) {
const pageCount = this.draw.getPageCount() - fromPageNo const pageCount = this.draw.getPageCount() - fromPageNo
const pageCountText = numberType === NumberType.CHINESE ? convertNumberToChinese(pageCount) : `${pageCount}` const pageCountText =
numberType === NumberType.CHINESE
? convertNumberToChinese(pageCount)
: `${pageCount}`
text = text.replace(pageCountReg, pageCountText) text = text.replace(pageCountReg, pageCountText)
} }
const width = this.draw.getWidth() const width = this.draw.getWidth()
// 计算y位置 // 计算y位置
const height = pageMode === PageMode.CONTINUITY const height =
? this.draw.getCanvasHeight(pageNo) pageMode === PageMode.CONTINUITY
: this.draw.getHeight() ? this.draw.getCanvasHeight(pageNo)
: this.draw.getHeight()
const pageNumberBottom = this.draw.getPageNumberBottom() const pageNumberBottom = this.draw.getPageNumberBottom()
const y = height - pageNumberBottom const y = height - pageNumberBottom
ctx.save() ctx.save()
@ -62,5 +77,4 @@ export class PageNumber {
ctx.fillText(text, x, y) ctx.fillText(text, x, y)
ctx.restore() ctx.restore()
} }
} }

@ -7,7 +7,6 @@ import { Position } from '../../position/Position'
import { Draw } from '../Draw' import { Draw } from '../Draw'
export class Placeholder { export class Placeholder {
private draw: Draw private draw: Draw
private position: Position private position: Position
private options: DeepRequired<IEditorOption> private options: DeepRequired<IEditorOption>
@ -61,16 +60,20 @@ export class Placeholder {
} }
public render(ctx: CanvasRenderingContext2D) { public render(ctx: CanvasRenderingContext2D) {
const { placeholder: { data, font, size, color, opacity } } = this.options const {
placeholder: { data, font, size, color, opacity }
} = this.options
if (!data) return if (!data) return
this._recovery() this._recovery()
// 构建元素列表并格式化 // 构建元素列表并格式化
this.elementList = [{ this.elementList = [
value: data, {
font, value: data,
size, font,
color size,
}] color
}
]
formatElementList(this.elementList, { formatElementList(this.elementList, {
editorOptions: this.options editorOptions: this.options
}) })
@ -90,5 +93,4 @@ export class Placeholder {
}) })
ctx.restore() ctx.restore()
} }
} }

@ -3,7 +3,6 @@ import { DeepRequired } from '../../../interface/Common'
import { Draw } from '../Draw' import { Draw } from '../Draw'
export class Watermark { export class Watermark {
private draw: Draw private draw: Draw
private options: DeepRequired<IEditorOption> private options: DeepRequired<IEditorOption>
@ -13,7 +12,10 @@ export class Watermark {
} }
public render(ctx: CanvasRenderingContext2D) { public render(ctx: CanvasRenderingContext2D) {
const { watermark: { data, opacity, font, size, color }, scale } = this.options const {
watermark: { data, opacity, font, size, color },
scale
} = this.options
const width = this.draw.getWidth() const width = this.draw.getWidth()
const height = this.draw.getHeight() const height = this.draw.getHeight()
const x = width / 2 const x = width / 2
@ -25,9 +27,12 @@ export class Watermark {
// 移动到中心位置再旋转 // 移动到中心位置再旋转
const measureText = ctx.measureText(data) const measureText = ctx.measureText(data)
ctx.translate(x, y) ctx.translate(x, y)
ctx.rotate(-45 * Math.PI / 180) ctx.rotate((-45 * Math.PI) / 180)
ctx.fillText(data, - measureText.width / 2, measureText.actualBoundingBoxAscent - size / 2) ctx.fillText(
data,
-measureText.width / 2,
measureText.actualBoundingBoxAscent - size / 2
)
ctx.restore() ctx.restore()
} }
} }

@ -11,12 +11,11 @@ import { Position } from '../../position/Position'
import { Draw } from '../Draw' import { Draw } from '../Draw'
export interface INavigateInfo { export interface INavigateInfo {
index: number; index: number
count: number; count: number
} }
export class Search { export class Search {
private draw: Draw private draw: Draw
private options: Required<IEditorOption> private options: Required<IEditorOption>
private position: Position private position: Position
@ -49,7 +48,8 @@ export class Search {
} else { } else {
let index = this.searchNavigateIndex - 1 let index = this.searchNavigateIndex - 1
let isExistPre = false let isExistPre = false
const searchNavigateId = this.searchMatchList[this.searchNavigateIndex].groupId const searchNavigateId =
this.searchMatchList[this.searchNavigateIndex].groupId
while (index >= 0) { while (index >= 0) {
const match = this.searchMatchList[index] const match = this.searchMatchList[index]
if (searchNavigateId !== match.groupId) { if (searchNavigateId !== match.groupId) {
@ -60,9 +60,11 @@ export class Search {
index-- index--
} }
if (!isExistPre) { if (!isExistPre) {
const lastSearchMatch = this.searchMatchList[this.searchMatchList.length - 1] const lastSearchMatch =
this.searchMatchList[this.searchMatchList.length - 1]
if (lastSearchMatch.groupId === searchNavigateId) return null if (lastSearchMatch.groupId === searchNavigateId) return null
this.searchNavigateIndex = this.searchMatchList.length - 1 - (this.searchKeyword.length - 1) this.searchNavigateIndex =
this.searchMatchList.length - 1 - (this.searchKeyword.length - 1)
} }
} }
return this.searchNavigateIndex return this.searchNavigateIndex
@ -75,7 +77,8 @@ export class Search {
} else { } else {
let index = this.searchNavigateIndex + 1 let index = this.searchNavigateIndex + 1
let isExistNext = false let isExistNext = false
const searchNavigateId = this.searchMatchList[this.searchNavigateIndex].groupId const searchNavigateId =
this.searchMatchList[this.searchNavigateIndex].groupId
while (index < this.searchMatchList.length) { while (index < this.searchMatchList.length) {
const match = this.searchMatchList[index] const match = this.searchMatchList[index]
if (searchNavigateId !== match.groupId) { if (searchNavigateId !== match.groupId) {
@ -95,7 +98,10 @@ export class Search {
} }
public searchNavigateScrollIntoView(position: IElementPosition) { public searchNavigateScrollIntoView(position: IElementPosition) {
const { coordinate: { leftTop, leftBottom, rightTop }, pageNo } = position const {
coordinate: { leftTop, leftBottom, rightTop },
pageNo
} = position
const height = this.draw.getHeight() const height = this.draw.getHeight()
const pageGap = this.draw.getPageGap() const pageGap = this.draw.getPageGap()
const preY = pageNo * (height + pageGap) const preY = pageNo * (height + pageGap)
@ -105,7 +111,9 @@ export class Search {
// 扩大搜索词尺寸,使可视范围更广 // 扩大搜索词尺寸,使可视范围更广
const ANCHOR_OVERFLOW_SIZE = 50 const ANCHOR_OVERFLOW_SIZE = 50
anchor.style.width = `${rightTop[0] - leftTop[0] + ANCHOR_OVERFLOW_SIZE}px` anchor.style.width = `${rightTop[0] - leftTop[0] + ANCHOR_OVERFLOW_SIZE}px`
anchor.style.height = `${leftBottom[1] - leftTop[1] + ANCHOR_OVERFLOW_SIZE}px` anchor.style.height = `${
leftBottom[1] - leftTop[1] + ANCHOR_OVERFLOW_SIZE
}px`
anchor.style.left = `${leftTop[0]}px` anchor.style.left = `${leftTop[0]}px`
anchor.style.top = `${leftTop[1] + preY}px` anchor.style.top = `${leftTop[1] + preY}px`
this.draw.getContainer().append(anchor) this.draw.getContainer().append(anchor)
@ -127,9 +135,10 @@ export class Search {
public getSearchNavigateInfo(): null | INavigateInfo { public getSearchNavigateInfo(): null | INavigateInfo {
if (!this.searchKeyword || !this.searchMatchList.length) return null if (!this.searchKeyword || !this.searchMatchList.length) return null
const index = this.searchNavigateIndex !== null const index =
? (this.searchNavigateIndex / this.searchKeyword.length) + 1 this.searchNavigateIndex !== null
: 0 ? this.searchNavigateIndex / this.searchKeyword.length + 1
: 0
let count = 0 let count = 0
let groupId = null let groupId = null
for (let s = 0; s < this.searchMatchList.length; s++) { for (let s = 0; s < this.searchMatchList.length; s++) {
@ -148,7 +157,11 @@ export class Search {
const keyword = payload.toLocaleLowerCase() const keyword = payload.toLocaleLowerCase()
const searchMatchList: ISearchResult[] = [] const searchMatchList: ISearchResult[] = []
// 分组 // 分组
const elementListGroup: { type: EditorContext, elementList: IElement[], index: number }[] = [] const elementListGroup: {
type: EditorContext
elementList: IElement[]
index: number
}[] = []
const originalElementList = this.draw.getOriginalElementList() const originalElementList = this.draw.getOriginalElementList()
const originalElementListLength = originalElementList.length const originalElementListLength = originalElementList.length
// 查找表格所在位置 // 查找表格所在位置
@ -162,7 +175,9 @@ export class Search {
let i = 0 let i = 0
let elementIndex = 0 let elementIndex = 0
while (elementIndex < originalElementListLength - 1) { while (elementIndex < originalElementListLength - 1) {
const endIndex = tableIndexList.length ? tableIndexList[i] : originalElementListLength const endIndex = tableIndexList.length
? tableIndexList[i]
: originalElementListLength
const pageElement = originalElementList.slice(elementIndex, endIndex) const pageElement = originalElementList.slice(elementIndex, endIndex)
if (pageElement.length) { if (pageElement.length) {
elementListGroup.push({ elementListGroup.push({
@ -183,12 +198,21 @@ export class Search {
i++ i++
} }
// 搜索文本 // 搜索文本
function searchClosure(payload: string | null, type: EditorContext, elementList: IElement[], restArgs?: ISearchResultRestArgs) { function searchClosure(
payload: string | null,
type: EditorContext,
elementList: IElement[],
restArgs?: ISearchResultRestArgs
) {
if (!payload) return if (!payload) return
const text = elementList const text = elementList
.map(e => !e.type || (TEXTLIKE_ELEMENT_TYPE.includes(e.type) && e.controlComponent !== ControlComponent.CHECKBOX) .map(e =>
? e.value !e.type ||
: ZERO) (TEXTLIKE_ELEMENT_TYPE.includes(e.type) &&
e.controlComponent !== ControlComponent.CHECKBOX)
? e.value
: ZERO
)
.filter(Boolean) .filter(Boolean)
.join('') .join('')
.toLocaleLowerCase() .toLocaleLowerCase()
@ -239,8 +263,15 @@ export class Search {
} }
public render(ctx: CanvasRenderingContext2D, pageIndex: number) { public render(ctx: CanvasRenderingContext2D, pageIndex: number) {
if (!this.searchMatchList || !this.searchMatchList.length || !this.searchKeyword) return if (
const { searchMatchAlpha, searchMatchColor, searchNavigateMatchColor } = this.options !this.searchMatchList ||
!this.searchMatchList.length ||
!this.searchKeyword
) {
return
}
const { searchMatchAlpha, searchMatchColor, searchNavigateMatchColor } =
this.options
const positionList = this.position.getOriginalPositionList() const positionList = this.position.getOriginalPositionList()
const elementList = this.draw.getOriginalElementList() const elementList = this.draw.getOriginalElementList()
ctx.save() ctx.save()
@ -250,12 +281,17 @@ export class Search {
let position: IElementPosition | null = null let position: IElementPosition | null = null
if (searchMatch.type === EditorContext.TABLE) { if (searchMatch.type === EditorContext.TABLE) {
const { tableIndex, trIndex, tdIndex, index } = searchMatch const { tableIndex, trIndex, tdIndex, index } = searchMatch
position = elementList[tableIndex!]?.trList![trIndex!].tdList[tdIndex!]?.positionList![index] position =
elementList[tableIndex!]?.trList![trIndex!].tdList[tdIndex!]
?.positionList![index]
} else { } else {
position = positionList[searchMatch.index] position = positionList[searchMatch.index]
} }
if (!position) continue if (!position) continue
const { coordinate: { leftTop, leftBottom, rightTop }, pageNo } = position const {
coordinate: { leftTop, leftBottom, rightTop },
pageNo
} = position
if (pageNo !== pageIndex) continue if (pageNo !== pageIndex) continue
// 高亮并定位当前搜索词 // 高亮并定位当前搜索词
const searchMatchIndexList = this.getSearchNavigateIndexList() const searchMatchIndexList = this.getSearchNavigateIndexList()
@ -277,5 +313,4 @@ export class Search {
} }
ctx.restore() ctx.restore()
} }
} }

@ -4,15 +4,22 @@ import { IRowElement } from '../../../interface/Row'
import { Draw } from '../Draw' import { Draw } from '../Draw'
export class CheckboxParticle { export class CheckboxParticle {
private options: DeepRequired<IEditorOption> private options: DeepRequired<IEditorOption>
constructor(draw: Draw) { constructor(draw: Draw) {
this.options = draw.getOptions() this.options = draw.getOptions()
} }
public render(ctx: CanvasRenderingContext2D, element: IRowElement, x: number, y: number) { public render(
const { checkbox: { gap, lineWidth, fillStyle, fontStyle }, scale } = this.options ctx: CanvasRenderingContext2D,
element: IRowElement,
x: number,
y: number
) {
const {
checkbox: { gap, lineWidth, fillStyle, fontStyle },
scale
} = this.options
const { metrics, checkbox } = element const { metrics, checkbox } = element
// left top 四舍五入避免1像素问题 // left top 四舍五入避免1像素问题
const left = Math.round(x + gap) const left = Math.round(x + gap)
@ -50,5 +57,4 @@ export class CheckboxParticle {
ctx.closePath() ctx.closePath()
ctx.restore() ctx.restore()
} }
} }

@ -6,7 +6,6 @@ import { IRowElement } from '../../../interface/Row'
import { Draw } from '../Draw' import { Draw } from '../Draw'
export class HyperlinkParticle { export class HyperlinkParticle {
private draw: Draw private draw: Draw
private options: Required<IEditorOption> private options: Required<IEditorOption>
private container: HTMLDivElement private container: HTMLDivElement
@ -17,7 +16,8 @@ export class HyperlinkParticle {
this.draw = draw this.draw = draw
this.options = draw.getOptions() this.options = draw.getOptions()
this.container = draw.getContainer() this.container = draw.getContainer()
const { hyperlinkPopupContainer, hyperlinkDom } = this._createHyperlinkPopupDom() const { hyperlinkPopupContainer, hyperlinkDom } =
this._createHyperlinkPopupDom()
this.hyperlinkDom = hyperlinkDom this.hyperlinkDom = hyperlinkDom
this.hyperlinkPopupContainer = hyperlinkPopupContainer this.hyperlinkPopupContainer = hyperlinkPopupContainer
} }
@ -34,7 +34,12 @@ export class HyperlinkParticle {
} }
public drawHyperlinkPopup(element: IElement, position: IElementPosition) { public drawHyperlinkPopup(element: IElement, position: IElementPosition) {
const { coordinate: { leftTop: [left, top] }, lineHeight } = position const {
coordinate: {
leftTop: [left, top]
},
lineHeight
} = position
const height = this.draw.getHeight() const height = this.draw.getHeight()
const pageGap = this.draw.getPageGap() const pageGap = this.draw.getPageGap()
const preY = this.draw.getPageNo() * (height + pageGap) const preY = this.draw.getPageNo() * (height + pageGap)
@ -60,7 +65,12 @@ export class HyperlinkParticle {
} }
} }
public render(ctx: CanvasRenderingContext2D, element: IRowElement, x: number, y: number) { public render(
ctx: CanvasRenderingContext2D,
element: IRowElement,
x: number,
y: number
) {
ctx.save() ctx.save()
ctx.font = element.style ctx.font = element.style
if (!element.color) { if (!element.color) {
@ -73,5 +83,4 @@ export class HyperlinkParticle {
ctx.fillText(element.value, x, y) ctx.fillText(element.value, x, y)
ctx.restore() ctx.restore()
} }
} }

@ -4,7 +4,6 @@ import { convertStringToBase64 } from '../../../utils'
import { Draw } from '../Draw' import { Draw } from '../Draw'
export class ImageParticle { export class ImageParticle {
private draw: Draw private draw: Draw
protected options: Required<IEditorOption> protected options: Required<IEditorOption>
protected imageCache: Map<string, HTMLImageElement> protected imageCache: Map<string, HTMLImageElement>
@ -26,18 +25,27 @@ export class ImageParticle {
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}"> const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">
<rect width="${width}" height="${height}" fill="url(#mosaic)" /> <rect width="${width}" height="${height}" fill="url(#mosaic)" />
<defs> <defs>
<pattern id="mosaic" x="${x}" y="${y}" width="${tileSize * 2}" height="${tileSize * 2}" patternUnits="userSpaceOnUse"> <pattern id="mosaic" x="${x}" y="${y}" width="${
tileSize * 2
}" height="${tileSize * 2}" patternUnits="userSpaceOnUse">
<rect width="${tileSize}" height="${tileSize}" fill="#cccccc" /> <rect width="${tileSize}" height="${tileSize}" fill="#cccccc" />
<rect width="${tileSize}" height="${tileSize}" fill="#cccccc" transform="translate(${tileSize}, ${tileSize})" /> <rect width="${tileSize}" height="${tileSize}" fill="#cccccc" transform="translate(${tileSize}, ${tileSize})" />
</pattern> </pattern>
</defs> </defs>
</svg>` </svg>`
const fallbackImage = new Image() const fallbackImage = new Image()
fallbackImage.src = `data:image/svg+xml;base64,${convertStringToBase64(svg)}` fallbackImage.src = `data:image/svg+xml;base64,${convertStringToBase64(
svg
)}`
return fallbackImage return fallbackImage
} }
public render(ctx: CanvasRenderingContext2D, element: IElement, x: number, y: number) { public render(
ctx: CanvasRenderingContext2D,
element: IElement,
x: number,
y: number
) {
const { scale } = this.options const { scale } = this.options
const width = element.width! * scale const width = element.width! * scale
const height = element.height! * scale const height = element.height! * scale
@ -54,7 +62,7 @@ export class ImageParticle {
this.imageCache.set(element.id!, img) this.imageCache.set(element.id!, img)
resolve(element) resolve(element)
} }
img.onerror = (error) => { img.onerror = error => {
const fallbackImage = this.getFallbackImage(width, height) const fallbackImage = this.getFallbackImage(width, height)
fallbackImage.onload = () => { fallbackImage.onload = () => {
ctx.drawImage(fallbackImage, x, y, width, height) ctx.drawImage(fallbackImage, x, y, width, height)
@ -66,5 +74,4 @@ export class ImageParticle {
this.addImageObserver(imageLoadPromise) this.addImageObserver(imageLoadPromise)
} }
} }
} }

@ -9,7 +9,6 @@ import { IRow } from '../../../interface/Row'
import { Draw } from '../Draw' import { Draw } from '../Draw'
export class ListParticle { export class ListParticle {
private options: DeepRequired<IEditorOption> private options: DeepRequired<IEditorOption>
// 非递增样式直接返回默认值 // 非递增样式直接返回默认值
@ -21,7 +20,10 @@ export class ListParticle {
this.options = draw.getOptions() this.options = draw.getOptions()
} }
public computeListStyle(ctx: CanvasRenderingContext2D, elementList: IElement[]): Map<string, number> { public computeListStyle(
ctx: CanvasRenderingContext2D,
elementList: IElement[]
): Map<string, number> {
const listStyleMap = new Map<string, number>() const listStyleMap = new Map<string, number>()
let start = 0 let start = 0
let curListId = elementList[start].listId let curListId = elementList[start].listId
@ -51,11 +53,17 @@ export class ListParticle {
return listStyleMap return listStyleMap
} }
public getListStyleWidth(ctx: CanvasRenderingContext2D, listElementList: IElement[]): number { public getListStyleWidth(
ctx: CanvasRenderingContext2D,
listElementList: IElement[]
): number {
const { scale } = this.options const { scale } = this.options
const startElement = listElementList[0] const startElement = listElementList[0]
// 非递增样式返回固定值 // 非递增样式返回固定值
if (startElement.listStyle && startElement.listStyle !== ListStyle.DECIMAL) { if (
startElement.listStyle &&
startElement.listStyle !== ListStyle.DECIMAL
) {
return this.UN_COUNT_STYLE_WIDTH * scale return this.UN_COUNT_STYLE_WIDTH * scale
} }
// 计算列表数量 // 计算列表数量
@ -67,23 +75,35 @@ export class ListParticle {
}, 0) }, 0)
if (!count) return 0 if (!count) return 0
// 以递增样式最大宽度为准 // 以递增样式最大宽度为准
const text = `${this.MEASURE_BASE_TEXT.repeat(String(count).length)}${KeyMap.PERIOD}` const text = `${this.MEASURE_BASE_TEXT.repeat(String(count).length)}${
KeyMap.PERIOD
}`
const textMetrics = ctx.measureText(text) const textMetrics = ctx.measureText(text)
return Math.ceil((textMetrics.width + this.LIST_GAP) * scale) return Math.ceil((textMetrics.width + this.LIST_GAP) * scale)
} }
public drawListStyle(ctx: CanvasRenderingContext2D, row: IRow, position: IElementPosition) { public drawListStyle(
ctx: CanvasRenderingContext2D,
row: IRow,
position: IElementPosition
) {
const { elementList, offsetX, listIndex, ascent } = row const { elementList, offsetX, listIndex, ascent } = row
const startElement = elementList[0] const startElement = elementList[0]
if (startElement.value !== ZERO || startElement.listWrap) return if (startElement.value !== ZERO || startElement.listWrap) return
let text = '' let text = ''
if (startElement.listType === ListType.UL) { if (startElement.listType === ListType.UL) {
text = ulStyleMapping[<UlStyle><unknown>startElement.listStyle] || ulStyleMapping[UlStyle.DISC] text =
ulStyleMapping[<UlStyle>(<unknown>startElement.listStyle)] ||
ulStyleMapping[UlStyle.DISC]
} else { } else {
text = `${listIndex! + 1}${KeyMap.PERIOD}` text = `${listIndex! + 1}${KeyMap.PERIOD}`
} }
if (!text) return if (!text) return
const { coordinate: { leftTop: [startX, startY] } } = position const {
coordinate: {
leftTop: [startX, startY]
}
} = position
const x = startX - offsetX! const x = startX - offsetX!
const y = startY + ascent const y = startY + ascent
const { defaultFont, defaultSize, scale } = this.options const { defaultFont, defaultSize, scale } = this.options
@ -92,5 +112,4 @@ export class ListParticle {
ctx.fillText(text, x, y) ctx.fillText(text, x, y)
ctx.restore() ctx.restore()
} }
} }

@ -3,7 +3,6 @@ import { IRowElement } from '../../../interface/Row'
import { Draw } from '../Draw' import { Draw } from '../Draw'
export class PageBreakParticle { export class PageBreakParticle {
static readonly font: string = 'Yahei' static readonly font: string = 'Yahei'
static readonly fontSize: number = 12 static readonly fontSize: number = 12
static readonly displayName: string = '分页符' static readonly displayName: string = '分页符'
@ -17,12 +16,18 @@ export class PageBreakParticle {
this.options = draw.getOptions() this.options = draw.getOptions()
} }
public render(ctx: CanvasRenderingContext2D, element: IRowElement, x: number, y: number) { public render(
ctx: CanvasRenderingContext2D,
element: IRowElement,
x: number,
y: number
) {
const { font, fontSize, displayName, lineDash } = PageBreakParticle const { font, fontSize, displayName, lineDash } = PageBreakParticle
const { scale, defaultRowMargin } = this.options const { scale, defaultRowMargin } = this.options
const size = fontSize * scale const size = fontSize * scale
const elementWidth = element.width! const elementWidth = element.width!
const offsetY = this.draw.getDefaultBasicRowMarginHeight() * defaultRowMargin const offsetY =
this.draw.getDefaultBasicRowMarginHeight() * defaultRowMargin
ctx.save() ctx.save()
ctx.font = `${size}px ${font}` ctx.font = `${size}px ${font}`
const textMeasure = ctx.measureText(displayName) const textMeasure = ctx.measureText(displayName)
@ -37,8 +42,11 @@ export class PageBreakParticle {
ctx.lineTo(x + elementWidth, y) ctx.lineTo(x + elementWidth, y)
ctx.stroke() ctx.stroke()
// 文字 // 文字
ctx.fillText(displayName, x + halfX, y + textMeasure.actualBoundingBoxAscent - size / 2) ctx.fillText(
displayName,
x + halfX,
y + textMeasure.actualBoundingBoxAscent - size / 2
)
ctx.restore() ctx.restore()
} }
} }

@ -1,7 +1,11 @@
import { IRowElement } from '../../../interface/Row' import { IRowElement } from '../../../interface/Row'
export class SeparatorParticle { export class SeparatorParticle {
public render(
public render(ctx: CanvasRenderingContext2D, element: IRowElement, x: number, y: number) { ctx: CanvasRenderingContext2D,
element: IRowElement,
x: number,
y: number
) {
ctx.save() ctx.save()
if (element.color) { if (element.color) {
ctx.strokeStyle = element.color ctx.strokeStyle = element.color
@ -16,5 +20,4 @@ export class SeparatorParticle {
ctx.stroke() ctx.stroke()
ctx.restore() ctx.restore()
} }
} }

@ -1,8 +1,12 @@
import { IRowElement } from '../../../interface/Row' import { IRowElement } from '../../../interface/Row'
export class SubscriptParticle { export class SubscriptParticle {
public render(
public render(ctx: CanvasRenderingContext2D, element: IRowElement, x: number, y: number) { ctx: CanvasRenderingContext2D,
element: IRowElement,
x: number,
y: number
) {
ctx.save() ctx.save()
ctx.font = element.style ctx.font = element.style
if (element.color) { if (element.color) {
@ -11,5 +15,4 @@ export class SubscriptParticle {
ctx.fillText(element.value, x, y + element.metrics.height / 2) ctx.fillText(element.value, x, y + element.metrics.height / 2)
ctx.restore() ctx.restore()
} }
} }

@ -1,8 +1,12 @@
import { IRowElement } from '../../../interface/Row' import { IRowElement } from '../../../interface/Row'
export class SuperscriptParticle { export class SuperscriptParticle {
public render(
public render(ctx: CanvasRenderingContext2D, element: IRowElement, x: number, y: number) { ctx: CanvasRenderingContext2D,
element: IRowElement,
x: number,
y: number
) {
ctx.save() ctx.save()
ctx.font = element.style ctx.font = element.style
if (element.color) { if (element.color) {
@ -11,5 +15,4 @@ export class SuperscriptParticle {
ctx.fillText(element.value, x, y - element.metrics.height / 2) ctx.fillText(element.value, x, y - element.metrics.height / 2)
ctx.restore() ctx.restore()
} }
} }

@ -5,12 +5,11 @@ import { IRowElement } from '../../../interface/Row'
import { Draw } from '../Draw' import { Draw } from '../Draw'
export interface IMeasureWordResult { export interface IMeasureWordResult {
width: number; width: number
endElement: IElement; endElement: IElement
} }
export class TextParticle { export class TextParticle {
private ctx: CanvasRenderingContext2D private ctx: CanvasRenderingContext2D
private curX: number private curX: number
private curY: number private curY: number
@ -28,7 +27,11 @@ export class TextParticle {
this.cacheMeasureText = new Map() this.cacheMeasureText = new Map()
} }
public measureWord(ctx: CanvasRenderingContext2D, elementList: IElement[], curIndex: number): IMeasureWordResult { public measureWord(
ctx: CanvasRenderingContext2D,
elementList: IElement[],
curIndex: number
): IMeasureWordResult {
let width = 0 let width = 0
let endElement: IElement = elementList[curIndex] let endElement: IElement = elementList[curIndex]
let i = curIndex let i = curIndex
@ -47,12 +50,18 @@ export class TextParticle {
} }
} }
public measurePunctuationWidth(ctx: CanvasRenderingContext2D, element: IElement): number { public measurePunctuationWidth(
ctx: CanvasRenderingContext2D,
element: IElement
): number {
if (!element || !PUNCTUATION_LIST.includes(element.value)) return 0 if (!element || !PUNCTUATION_LIST.includes(element.value)) return 0
return this.measureText(ctx, element).width return this.measureText(ctx, element).width
} }
public measureText(ctx: CanvasRenderingContext2D, element: IElement): TextMetrics { public measureText(
ctx: CanvasRenderingContext2D,
element: IElement
): TextMetrics {
const id = `${element.value}${ctx.font}` const id = `${element.value}${ctx.font}`
const cacheTextMetrics = this.cacheMeasureText.get(id) const cacheTextMetrics = this.cacheMeasureText.get(id)
if (cacheTextMetrics) { if (cacheTextMetrics) {
@ -68,7 +77,12 @@ export class TextParticle {
this.text = '' this.text = ''
} }
public record(ctx: CanvasRenderingContext2D, element: IRowElement, x: number, y: number) { public record(
ctx: CanvasRenderingContext2D,
element: IRowElement,
x: number,
y: number
) {
this.ctx = ctx this.ctx = ctx
// 主动完成的重设起始点 // 主动完成的重设起始点
if (!this.text) { if (!this.text) {
@ -77,7 +91,7 @@ export class TextParticle {
// 样式发生改变 // 样式发生改变
if ( if (
(this.curStyle && element.style !== this.curStyle) || (this.curStyle && element.style !== this.curStyle) ||
(element.color !== this.curColor) element.color !== this.curColor
) { ) {
this.complete() this.complete()
this._setCurXY(x, y) this._setCurXY(x, y)
@ -102,5 +116,4 @@ export class TextParticle {
this.ctx.fillText(this.text, this.curX, this.curY) this.ctx.fillText(this.text, this.curX, this.curY)
this.ctx.restore() this.ctx.restore()
} }
} }

@ -5,7 +5,6 @@ import { Draw } from '../../Draw'
import { BaseBlock } from './modules/BaseBlock' import { BaseBlock } from './modules/BaseBlock'
export class BlockParticle { export class BlockParticle {
private draw: Draw private draw: Draw
private container: HTMLDivElement private container: HTMLDivElement
private blockContainer: HTMLDivElement private blockContainer: HTMLDivElement
@ -64,5 +63,4 @@ export class BlockParticle {
} }
}) })
} }
} }

@ -7,7 +7,6 @@ import { IFrameBlock } from './IFrameBlock'
import { VideoBlock } from './VideoBlock' import { VideoBlock } from './VideoBlock'
export class BaseBlock { export class BaseBlock {
private draw: Draw private draw: Draw
private element: IRowElement private element: IRowElement
private block: IFrameBlock | VideoBlock | null private block: IFrameBlock | VideoBlock | null
@ -60,5 +59,4 @@ export class BaseBlock {
public remove() { public remove() {
this.blockItem.remove() this.blockItem.remove()
} }
} }

@ -1,7 +1,6 @@
import { IRowElement } from '../../../../../interface/Row' import { IRowElement } from '../../../../../interface/Row'
export class IFrameBlock { export class IFrameBlock {
private static readonly sandbox = [ private static readonly sandbox = [
'allow-forms', 'allow-forms',
'allow-scripts', 'allow-scripts',
@ -24,5 +23,4 @@ export class IFrameBlock {
iframe.src = block.iframeBlock?.src || '' iframe.src = block.iframeBlock?.src || ''
blockItemContainer.append(iframe) blockItemContainer.append(iframe)
} }
} }

@ -1,7 +1,6 @@
import { IRowElement } from '../../../../../interface/Row' import { IRowElement } from '../../../../../interface/Row'
export class VideoBlock { export class VideoBlock {
private element: IRowElement private element: IRowElement
constructor(element: IRowElement) { constructor(element: IRowElement) {
@ -19,5 +18,4 @@ export class VideoBlock {
video.crossOrigin = 'anonymous' video.crossOrigin = 'anonymous'
blockItemContainer.append(video) blockItemContainer.append(video)
} }
} }

@ -7,7 +7,6 @@ import { Draw } from '../../Draw'
import { DatePicker } from './DatePicker' import { DatePicker } from './DatePicker'
export class DateParticle { export class DateParticle {
private draw: Draw private draw: Draw
private range: RangeManager private range: RangeManager
private datePicker: DatePicker private datePicker: DatePicker
@ -32,7 +31,7 @@ export class DateParticle {
wed: t('datePicker.weeks.wed'), wed: t('datePicker.weeks.wed'),
thu: t('datePicker.weeks.thu'), thu: t('datePicker.weeks.thu'),
fri: t('datePicker.weeks.fri'), fri: t('datePicker.weeks.fri'),
sat: t('datePicker.weeks.sat'), sat: t('datePicker.weeks.sat')
}, },
year: t('datePicker.year'), year: t('datePicker.year'),
month: t('datePicker.month'), month: t('datePicker.month'),
@ -51,16 +50,22 @@ export class DateParticle {
const elementList = this.draw.getElementList() const elementList = this.draw.getElementList()
const startElement = elementList[leftIndex + 1] const startElement = elementList[leftIndex + 1]
// 删除旧时间 // 删除旧时间
this.draw.spliceElementList(elementList, leftIndex + 1, rightIndex - leftIndex) this.draw.spliceElementList(
elementList,
leftIndex + 1,
rightIndex - leftIndex
)
this.range.setRange(leftIndex, leftIndex) this.range.setRange(leftIndex, leftIndex)
// 插入新时间 // 插入新时间
const dateElement: IElement = { const dateElement: IElement = {
type: ElementType.DATE, type: ElementType.DATE,
value: '', value: '',
dateFormat: startElement.dateFormat, dateFormat: startElement.dateFormat,
valueList: [{ valueList: [
value: date {
}] value: date
}
]
} }
formatElementContext(elementList, [dateElement], leftIndex) formatElementContext(elementList, [dateElement], leftIndex)
this.draw.insertElementList([dateElement]) this.draw.insertElementList([dateElement])
@ -113,7 +118,10 @@ export class DateParticle {
const elementList = this.draw.getElementList() const elementList = this.draw.getElementList()
const range = this.getDateElementRange() const range = this.getDateElementRange()
const value = range const value = range
? elementList.slice(range[0] + 1, range[1] + 1).map(el => el.value).join('') ? elementList
.slice(range[0] + 1, range[1] + 1)
.map(el => el.value)
.join('')
: '' : ''
this.datePicker.render({ this.datePicker.render({
value, value,
@ -123,7 +131,12 @@ export class DateParticle {
}) })
} }
public render(ctx: CanvasRenderingContext2D, element: IRowElement, x: number, y: number) { public render(
ctx: CanvasRenderingContext2D,
element: IRowElement,
x: number,
y: number
) {
ctx.save() ctx.save()
ctx.font = element.style ctx.font = element.style
if (element.color) { if (element.color) {
@ -132,5 +145,4 @@ export class DateParticle {
ctx.fillText(element.value, x, y) ctx.fillText(element.value, x, y)
ctx.restore() ctx.restore()
} }
} }

@ -3,66 +3,65 @@ import { IElement, IElementPosition } from '../../../../interface/Element'
import { datePicker } from '../../../i18n/lang/zh-CN.json' import { datePicker } from '../../../i18n/lang/zh-CN.json'
export interface IDatePickerLang { export interface IDatePickerLang {
now: string; now: string
confirm: string; confirm: string
return: string; return: string
timeSelect: string; timeSelect: string
weeks: { weeks: {
sun: string; sun: string
mon: string; mon: string
tue: string; tue: string
wed: string; wed: string
thu: string; thu: string
fri: string; fri: string
sat: string; sat: string
}; }
year: string; year: string
month: string; month: string
hour: string; hour: string
minute: string; minute: string
second: string; second: string
} }
export interface IDatePickerOption { export interface IDatePickerOption {
mountDom?: HTMLElement; mountDom?: HTMLElement
onSubmit?: (date: string) => any; onSubmit?: (date: string) => any
getLang?: () => IDatePickerLang getLang?: () => IDatePickerLang
} }
interface IDatePickerDom { interface IDatePickerDom {
container: HTMLDivElement; container: HTMLDivElement
dateWrap: HTMLDivElement; dateWrap: HTMLDivElement
datePickerWeek: HTMLDivElement; datePickerWeek: HTMLDivElement
timeWrap: HTMLUListElement; timeWrap: HTMLUListElement
title: { title: {
preYear: HTMLSpanElement; preYear: HTMLSpanElement
preMonth: HTMLSpanElement; preMonth: HTMLSpanElement
now: HTMLSpanElement; now: HTMLSpanElement
nextMonth: HTMLSpanElement; nextMonth: HTMLSpanElement
nextYear: HTMLSpanElement; nextYear: HTMLSpanElement
}; }
day: HTMLDivElement; day: HTMLDivElement
time: { time: {
hour: HTMLOListElement; hour: HTMLOListElement
minute: HTMLOListElement; minute: HTMLOListElement
second: HTMLOListElement; second: HTMLOListElement
}; }
menu: { menu: {
time: HTMLButtonElement; time: HTMLButtonElement
now: HTMLButtonElement; now: HTMLButtonElement
submit: HTMLButtonElement; submit: HTMLButtonElement
}; }
} }
interface IRenderOption { interface IRenderOption {
value: string; value: string
element: IElement; element: IElement
position: IElementPosition; position: IElementPosition
startTop?: number; startTop?: number
} }
export class DatePicker { export class DatePicker {
private options: IDatePickerOption private options: IDatePickerOption
private now: Date private now: Date
private dom: IDatePickerDom private dom: IDatePickerDom
@ -115,7 +114,9 @@ export class DatePicker {
// week-星期显示 // week-星期显示
const datePickerWeek = document.createElement('div') const datePickerWeek = document.createElement('div')
datePickerWeek.classList.add(`${EDITOR_PREFIX}-date-week`) datePickerWeek.classList.add(`${EDITOR_PREFIX}-date-week`)
const { weeks: { sun, mon, tue, wed, thu, fri, sat } } = this.lang const {
weeks: { sun, mon, tue, wed, thu, fri, sat }
} = this.lang
const weekList = [sun, mon, tue, wed, thu, fri, sat] const weekList = [sun, mon, tue, wed, thu, fri, sat]
weekList.forEach(week => { weekList.forEach(week => {
const weekDom = document.createElement('span') const weekDom = document.createElement('span')
@ -232,7 +233,7 @@ export class DatePicker {
this.dispose() this.dispose()
this._submit() this._submit()
} }
this.dom.time.hour.onclick = (evt) => { this.dom.time.hour.onclick = evt => {
if (!this.pickDate) return if (!this.pickDate) return
const li = <HTMLLIElement>evt.target const li = <HTMLLIElement>evt.target
const id = li.dataset.id const id = li.dataset.id
@ -240,7 +241,7 @@ export class DatePicker {
this.pickDate.setHours(Number(id)) this.pickDate.setHours(Number(id))
this._setTimePick(false) this._setTimePick(false)
} }
this.dom.time.minute.onclick = (evt) => { this.dom.time.minute.onclick = evt => {
if (!this.pickDate) return if (!this.pickDate) return
const li = <HTMLLIElement>evt.target const li = <HTMLLIElement>evt.target
const id = li.dataset.id const id = li.dataset.id
@ -248,7 +249,7 @@ export class DatePicker {
this.pickDate.setMinutes(Number(id)) this.pickDate.setMinutes(Number(id))
this._setTimePick(false) this._setTimePick(false)
} }
this.dom.time.second.onclick = (evt) => { this.dom.time.second.onclick = evt => {
if (!this.pickDate) return if (!this.pickDate) return
const li = <HTMLLIElement>evt.target const li = <HTMLLIElement>evt.target
const id = li.dataset.id const id = li.dataset.id
@ -293,16 +294,23 @@ export class DatePicker {
this.dom.menu.time.innerText = this.lang.timeSelect this.dom.menu.time.innerText = this.lang.timeSelect
this.dom.menu.now.innerText = this.lang.now this.dom.menu.now.innerText = this.lang.now
this.dom.menu.submit.innerText = this.lang.confirm this.dom.menu.submit.innerText = this.lang.confirm
const { weeks: { sun, mon, tue, wed, thu, fri, sat } } = this.lang const {
weeks: { sun, mon, tue, wed, thu, fri, sat }
} = this.lang
const weekList = [sun, mon, tue, wed, thu, fri, sat] const weekList = [sun, mon, tue, wed, thu, fri, sat]
this.dom.datePickerWeek.childNodes.forEach((child, i) => { this.dom.datePickerWeek.childNodes.forEach((child, i) => {
(<HTMLSpanElement>child).innerText = weekList[i] const childElement = <HTMLSpanElement>child
childElement.innerText = weekList[i]
}) })
const hourTitle = (<HTMLSpanElement>this.dom.time.hour.previousElementSibling) const hourTitle = <HTMLSpanElement>this.dom.time.hour.previousElementSibling
hourTitle.innerText = this.lang.hour hourTitle.innerText = this.lang.hour
const minuteTitle = (<HTMLSpanElement>this.dom.time.minute.previousElementSibling) const minuteTitle = <HTMLSpanElement>(
this.dom.time.minute.previousElementSibling
)
minuteTitle.innerText = this.lang.minute minuteTitle.innerText = this.lang.minute
const secondTitle = (<HTMLSpanElement>this.dom.time.second.previousElementSibling) const secondTitle = <HTMLSpanElement>(
this.dom.time.second.previousElementSibling
)
secondTitle.innerText = this.lang.second secondTitle.innerText = this.lang.second
} }
@ -324,7 +332,9 @@ export class DatePicker {
// 当前年月日 // 当前年月日
const year = this.now.getFullYear() const year = this.now.getFullYear()
const month = this.now.getMonth() + 1 const month = this.now.getMonth() + 1
this.dom.title.now.innerText = `${year}${this.lang.year} ${String(month).padStart(2, '0')}${this.lang.month}` this.dom.title.now.innerText = `${year}${this.lang.year} ${String(
month
).padStart(2, '0')}${this.lang.month}`
// 日期补差 // 日期补差
const curDate = new Date(year, month, 0) // 当月日期 const curDate = new Date(year, month, 0) // 当月日期
const curDay = curDate.getDate() // 当月总天数 const curDay = curDate.getDate() // 当月总天数
@ -353,11 +363,16 @@ export class DatePicker {
if (localYear === year && localMonth === month && localDay === i) { if (localYear === year && localMonth === month && localDay === i) {
dayDom.classList.add('active') dayDom.classList.add('active')
} }
if (this.pickDate && pickYear === year && pickMonth === month && pickDay === i) { if (
this.pickDate &&
pickYear === year &&
pickMonth === month &&
pickDay === i
) {
dayDom.classList.add('select') dayDom.classList.add('select')
} }
dayDom.innerText = `${i}` dayDom.innerText = `${i}`
dayDom.onclick = (evt) => { dayDom.onclick = evt => {
const newMonth = month - 1 const newMonth = month - 1
this.now = new Date(year, newMonth, i) this.now = new Date(year, newMonth, i)
this._setDatePick(year, newMonth, i) this._setDatePick(year, newMonth, i)
@ -405,14 +420,23 @@ export class DatePicker {
const hour = this.pickDate?.getHours() || 0 const hour = this.pickDate?.getHours() || 0
const minute = this.pickDate?.getMinutes() || 0 const minute = this.pickDate?.getMinutes() || 0
const second = this.pickDate?.getSeconds() || 0 const second = this.pickDate?.getSeconds() || 0
const { hour: hourDom, minute: minuteDom, second: secondDom } = this.dom.time const {
hour: hourDom,
minute: minuteDom,
second: secondDom
} = this.dom.time
const timeDomList = [hourDom, minuteDom, secondDom] const timeDomList = [hourDom, minuteDom, secondDom]
// 清空 // 清空
timeDomList.forEach(timeDom => { timeDomList.forEach(timeDom => {
timeDom.querySelectorAll('li') timeDom
.querySelectorAll('li')
.forEach(li => li.classList.remove('active')) .forEach(li => li.classList.remove('active'))
}) })
const pickList: [HTMLOListElement, number][] = [[hourDom, hour], [minuteDom, minute], [secondDom, second]] const pickList: [HTMLOListElement, number][] = [
[hourDom, hour],
[minuteDom, minute],
[secondDom, second]
]
pickList.forEach(([dom, time]) => { pickList.forEach(([dom, time]) => {
const pickDom = dom.querySelector<HTMLLIElement>(`[data-id='${time}']`)! const pickDom = dom.querySelector<HTMLLIElement>(`[data-id='${time}']`)!
pickDom.classList.add('active') pickDom.classList.add('active')
@ -433,7 +457,9 @@ export class DatePicker {
offsetParents.push(pointer) offsetParents.push(pointer)
pointer = <HTMLElement>pointer.offsetParent pointer = <HTMLElement>pointer.offsetParent
} }
const top = selected.offsetTop + offsetParents.reduce((prev, curr) => (prev + curr.offsetTop), 0) const top =
selected.offsetTop +
offsetParents.reduce((prev, curr) => prev + curr.offsetTop, 0)
const bottom = top + selected.offsetHeight const bottom = top + selected.offsetHeight
const viewRectTop = container.scrollTop const viewRectTop = container.scrollTop
const viewRectBottom = viewRectTop + container.clientHeight const viewRectBottom = viewRectTop + container.clientHeight
@ -502,8 +528,8 @@ export class DatePicker {
dateString = dateString.replace( dateString = dateString.replace(
reg[1], reg[1],
reg[1].length === 1 reg[1].length === 1
? (dateOption[key]) ? dateOption[key]
: (dateOption[key].padStart(reg[1].length, '0')) : dateOption[key].padStart(reg[1].length, '0')
) )
} }
} }
@ -527,5 +553,4 @@ export class DatePicker {
public dispose() { public dispose() {
this._toggleVisible(false) this._toggleVisible(false)
} }
} }

@ -3,7 +3,6 @@ import { ImageParticle } from '../ImageParticle'
import { LaTexSVG, LaTexUtils } from './utils/LaTexUtils' import { LaTexSVG, LaTexUtils } from './utils/LaTexUtils'
export class LaTexParticle extends ImageParticle { export class LaTexParticle extends ImageParticle {
public static convertLaTextToSVG(laTex: string): LaTexSVG { public static convertLaTextToSVG(laTex: string): LaTexSVG {
return new LaTexUtils(laTex).svg({ return new LaTexUtils(laTex).svg({
SCALE_X: 10, SCALE_X: 10,
@ -13,7 +12,12 @@ export class LaTexParticle extends ImageParticle {
}) })
} }
public render(ctx: CanvasRenderingContext2D, element: IElement, x: number, y: number) { public render(
ctx: CanvasRenderingContext2D,
element: IElement,
x: number,
y: number
) {
const { scale } = this.options const { scale } = this.options
const width = element.width! * scale const width = element.width! * scale
const height = element.height! * scale const height = element.height! * scale
@ -29,13 +33,11 @@ export class LaTexParticle extends ImageParticle {
this.imageCache.set(element.value, img) this.imageCache.set(element.value, img)
resolve(element) resolve(element)
} }
img.onerror = (error) => { img.onerror = error => {
reject(error) reject(error)
} }
}) })
this.addImageObserver(laTexLoadPromise) this.addImageObserver(laTexLoadPromise)
} }
} }
} }

@ -6,7 +6,7 @@ const CONFIG: Record<string, number> = {
SQRT_MAG_SCALE: 0.5, SQRT_MAG_SCALE: 0.5,
FRAC_SCALE: 0.85, FRAC_SCALE: 0.85,
LINE_SPACING: 0.5, LINE_SPACING: 0.5,
FRAC_SPACING: 0.4, FRAC_SPACING: 0.4
} }
function tokenize(str: string): string[] { function tokenize(str: string): string[] {
@ -49,27 +49,40 @@ function tokenize(str: string): string[] {
} }
interface Bbox { interface Bbox {
x: number, y: number, w: number, h: number x: number
y: number
w: number
h: number
} }
interface Expr { interface Expr {
type: string; type: string
text: string; text: string
mode: string; mode: string
chld: Expr[]; chld: Expr[]
bbox: Bbox; bbox: Bbox
} }
function parseAtom(x: string): Expr { function parseAtom(x: string): Expr {
// @ts-ignore return {
return { type: SYMB[x] ? 'symb' : 'char', mode: 'math', text: x, chld: [], bbox: null } type: SYMB[x] ? 'symb' : 'char',
mode: 'math',
text: x,
chld: [],
// @ts-ignore
bbox: null
}
} }
function parse(tokens: string[]): Expr { function parse(tokens: string[]): Expr {
let i = 0 let i = 0
let expr: Expr = { let expr: Expr = {
type: 'node',
text: '',
mode: 'math',
chld: [],
// @ts-ignore // @ts-ignore
type: 'node', text: '', mode: 'math', chld: [], bbox: null bbox: null
} }
function takeOpt(): Expr | null { function takeOpt(): Expr | null {
@ -133,8 +146,12 @@ function parse(tokens: string[]): Expr {
for (i = 0; i < tokens.length; i++) { for (i = 0; i < tokens.length; i++) {
const s: Symb = SYMB[tokens[i]] const s: Symb = SYMB[tokens[i]]
const e: Expr = { const e: Expr = {
type: '',
text: tokens[i],
mode: 'math',
chld: [],
// @ts-ignore // @ts-ignore
type: '', text: tokens[i], mode: 'math', chld: [], bbox: null bbox: null
} }
if (s) { if (s) {
if (s.arity) { if (s.arity) {
@ -190,7 +207,14 @@ function environments(exprs: Expr[]) {
} }
} }
function transform(expr: Expr, sclx: number, scly: number, x: number, y: number, notFirst?: boolean) { function transform(
expr: Expr,
sclx: number,
scly: number,
x: number,
y: number,
notFirst?: boolean
) {
if (scly == null) { if (scly == null) {
scly = sclx scly = sclx
} }
@ -209,8 +233,10 @@ function transform(expr: Expr, sclx: number, scly: number, x: number, y: number,
} }
function computeBbox(exprs: Expr[]): Bbox { function computeBbox(exprs: Expr[]): Bbox {
let xmin = Infinity; let xmax = -Infinity let xmin = Infinity
let ymin = Infinity; let ymax = -Infinity let xmax = -Infinity
let ymin = Infinity
let ymax = -Infinity
for (let i = 0; i < exprs.length; i++) { for (let i = 0; i < exprs.length; i++) {
if (!exprs[i].bbox) { if (!exprs[i].bbox) {
continue continue
@ -238,8 +264,11 @@ function group(exprs: Expr[]): Expr {
exprs[i].bbox.y -= bbox.y exprs[i].bbox.y -= bbox.y
} }
const expr: Expr = { const expr: Expr = {
type: 'node', text: '', mode: 'math', type: 'node',
chld: exprs, bbox text: '',
mode: 'math',
chld: exprs,
bbox
} }
return expr return expr
} }
@ -249,7 +278,10 @@ function align(exprs: Expr[], alignment = 'center'): void {
if (exprs[i].text == '^' || exprs[i].text == '\'') { if (exprs[i].text == '^' || exprs[i].text == '\'') {
let h = 0 let h = 0
let j = i let j = i
while (j > 0 && (exprs[j].text == '^' || exprs[j].text == '_' || exprs[j].text == '\'')) { while (
j > 0 &&
(exprs[j].text == '^' || exprs[j].text == '_' || exprs[j].text == '\'')
) {
j-- j--
} }
h = exprs[j].bbox.y h = exprs[j].bbox.y
@ -269,7 +301,10 @@ function align(exprs: Expr[], alignment = 'center'): void {
} else if (exprs[i].text == '_') { } else if (exprs[i].text == '_') {
let h = 1 let h = 1
let j = i let j = i
while (j > 0 && (exprs[j].text == '^' || exprs[j].text == '_' || exprs[j].text == '\'')) { while (
j > 0 &&
(exprs[j].text == '^' || exprs[j].text == '_' || exprs[j].text == '\'')
) {
j-- j--
} }
h = exprs[j].bbox.y + exprs[j].bbox.h h = exprs[j].bbox.y + exprs[j].bbox.h
@ -284,12 +319,18 @@ function align(exprs: Expr[], alignment = 'center'): void {
} }
} }
} }
function searchHigh(i: number, l: string, r: string, dir: number, lvl0: number): number[] { function searchHigh(
i: number,
l: string,
r: string,
dir: number,
lvl0: number
): number[] {
let j = i let j = i
let lvl = lvl0 let lvl = lvl0
let ymin = Infinity let ymin = Infinity
let ymax = -Infinity let ymax = -Infinity
while ((dir > 0) ? (j < exprs.length) : (j >= 0)) { while (dir > 0 ? j < exprs.length : j >= 0) {
if (exprs[j].text == l) { if (exprs[j].text == l) {
lvl++ lvl++
} else if (exprs[j].text == r) { } else if (exprs[j].text == r) {
@ -312,13 +353,13 @@ function align(exprs: Expr[], alignment = 'center'): void {
const [ymin, ymax] = searchHigh(i, '\\left', '\\right', 1, 0) const [ymin, ymax] = searchHigh(i, '\\left', '\\right', 1, 0)
if (ymin != Infinity && ymax != -Infinity) { if (ymin != Infinity && ymax != -Infinity) {
exprs[i].bbox.y = ymin exprs[i].bbox.y = ymin
transform(exprs[i], 1, ((ymax - ymin) / exprs[i].bbox.h), 0, 0) transform(exprs[i], 1, (ymax - ymin) / exprs[i].bbox.h, 0, 0)
} }
} else if (exprs[i].text == '\\right') { } else if (exprs[i].text == '\\right') {
const [ymin, ymax] = searchHigh(i, '\\right', '\\left', -1, 0) const [ymin, ymax] = searchHigh(i, '\\right', '\\left', -1, 0)
if (ymin != Infinity && ymax != -Infinity) { if (ymin != Infinity && ymax != -Infinity) {
exprs[i].bbox.y = ymin exprs[i].bbox.y = ymin
transform(exprs[i], 1, ((ymax - ymin) / exprs[i].bbox.h), 0, 0) transform(exprs[i], 1, (ymax - ymin) / exprs[i].bbox.h, 0, 0)
} }
} else if (exprs[i].text == '\\middle') { } else if (exprs[i].text == '\\middle') {
const [lmin, lmax] = searchHigh(i, '\\right', '\\left', -1, 1) const [lmin, lmax] = searchHigh(i, '\\right', '\\left', -1, 1)
@ -327,12 +368,12 @@ function align(exprs: Expr[], alignment = 'center'): void {
const ymax = Math.max(lmax, rmax) const ymax = Math.max(lmax, rmax)
if (ymin != Infinity && ymax != -Infinity) { if (ymin != Infinity && ymax != -Infinity) {
exprs[i].bbox.y = ymin exprs[i].bbox.y = ymin
transform(exprs[i], 1, ((ymax - ymin) / exprs[i].bbox.h), 0, 0) transform(exprs[i], 1, (ymax - ymin) / exprs[i].bbox.h, 0, 0)
} }
} }
} }
if (!exprs.some((x) => (x.text == '&' || x.text == '\\\\'))) { if (!exprs.some(x => x.text == '&' || x.text == '\\\\')) {
return return
} }
@ -408,28 +449,27 @@ function align(exprs: Expr[], alignment = 'center'): void {
ybds[i][1] += shft ybds[i][1] += shft
} }
exprs.splice(0, exprs.length) exprs.splice(0, exprs.length)
for (let i = 0; i < erows.length; i++) { for (let i = 0; i < erows.length; i++) {
let dx = 0 let dx = 0
for (let j = 0; j < erows[i].length; j++) { for (let j = 0; j < erows[i].length; j++) {
const e: Expr = erows[i][j] const e: Expr = erows[i][j]
if (!e) { if (!e) {
dx += (colws[j]) dx += colws[j]
continue continue
} }
e.bbox.x += dx e.bbox.x += dx
dx += (colws[j] - e.bbox.w) dx += colws[j] - e.bbox.w
// e.bbox.w = colws[j]; // e.bbox.w = colws[j];
if (alignment == 'center') { if (alignment == 'center') {
e.bbox.x += (colws[j] - e.bbox.w) / 2 e.bbox.x += (colws[j] - e.bbox.w) / 2
} else if (alignment == 'left') { } else if (alignment == 'left') {
//ok //ok
} else if (alignment == 'right') { } else if (alignment == 'right') {
e.bbox.x += (colws[j] - e.bbox.w) e.bbox.x += colws[j] - e.bbox.w
} else if (alignment == 'equation') { } else if (alignment == 'equation') {
if (j != erows[i].length - 1) { if (j != erows[i].length - 1) {
e.bbox.x += (colws[j] - e.bbox.w) e.bbox.x += colws[j] - e.bbox.w
} }
} }
exprs.push(e) exprs.push(e)
@ -438,28 +478,29 @@ function align(exprs: Expr[], alignment = 'center'): void {
} }
function plan(expr: Expr, mode = 'math'): void { function plan(expr: Expr, mode = 'math'): void {
const tmd: string = { const tmd: string =
'\\text': 'text', {
'\\mathnormal': 'math', '\\text': 'text',
'\\mathrm': 'rm', '\\mathnormal': 'math',
'\\mathit': 'it', '\\mathrm': 'rm',
'\\mathbf': 'bf', '\\mathit': 'it',
'\\mathsf': 'sf', '\\mathbf': 'bf',
'\\mathtt': 'tt', '\\mathsf': 'sf',
'\\mathfrak': 'frak', '\\mathtt': 'tt',
'\\mathcal': 'cal', '\\mathfrak': 'frak',
'\\mathbb': 'bb', '\\mathcal': 'cal',
'\\mathscr': 'scr', '\\mathbb': 'bb',
'\\rm': 'rm', '\\mathscr': 'scr',
'\\it': 'it', '\\rm': 'rm',
'\\bf': 'bf', '\\it': 'it',
'\\sf': 'tt', '\\bf': 'bf',
'\\tt': 'tt', '\\sf': 'tt',
'\\frak': 'frak', '\\tt': 'tt',
'\\cal': 'cal', '\\frak': 'frak',
'\\bb': 'bb', '\\cal': 'cal',
'\\scr': 'scr', '\\bb': 'bb',
}[expr.text] ?? mode '\\scr': 'scr'
}[expr.text] ?? mode
if (!expr.chld.length) { if (!expr.chld.length) {
if (SYMB[expr.text]) { if (SYMB[expr.text]) {
if (SYMB[expr.text].flags.big) { if (SYMB[expr.text].flags.big) {
@ -517,9 +558,20 @@ function plan(expr: Expr, mode = 'math'): void {
const mw: number = Math.max(a.bbox.w, b.bbox.w) * s const mw: number = Math.max(a.bbox.w, b.bbox.w) * s
// @ts-ignore // @ts-ignore
transform(a, s, null, (mw - a.bbox.w * s) / 2, 0) transform(a, s, null, (mw - a.bbox.w * s) / 2, 0)
// @ts-ignore transform(
transform(b, s, null, (mw - b.bbox.w * s) / 2, a.bbox.h + CONFIG.FRAC_SPACING) b,
expr.bbox = { x: 0, y: -a.bbox.h + 1 - CONFIG.FRAC_SPACING / 2, w: mw, h: a.bbox.h + b.bbox.h + CONFIG.FRAC_SPACING } s,
// @ts-ignore
null,
(mw - b.bbox.w * s) / 2,
a.bbox.h + CONFIG.FRAC_SPACING
)
expr.bbox = {
x: 0,
y: -a.bbox.h + 1 - CONFIG.FRAC_SPACING / 2,
w: mw,
h: a.bbox.h + b.bbox.h + CONFIG.FRAC_SPACING
}
} else if (expr.text == '\\binom') { } else if (expr.text == '\\binom') {
const a: Expr = expr.chld[0] const a: Expr = expr.chld[0]
const b: Expr = expr.chld[1] const b: Expr = expr.chld[1]
@ -548,20 +600,22 @@ function plan(expr: Expr, mode = 'math'): void {
} }
// @ts-ignore // @ts-ignore
transform(e, 1, null, 1 + pl, 0.5) transform(e, 1, null, 1 + pl, 0.5)
expr.bbox = { x: 0, y: 2 - e.bbox.h - 0.5, w: e.bbox.w + 1 + pl, h: e.bbox.h + 0.5 } expr.bbox = {
x: 0,
y: 2 - e.bbox.h - 0.5,
w: e.bbox.w + 1 + pl,
h: e.bbox.h + 0.5
}
} else if (SYMB[expr.text] && SYMB[expr.text].flags.hat) { } else if (SYMB[expr.text] && SYMB[expr.text].flags.hat) {
const e: Expr = expr.chld[0] const e: Expr = expr.chld[0]
plan(e) plan(e)
const y0 = e.bbox.y - 0.5 const y0 = e.bbox.y - 0.5
e.bbox.y = 0.5 e.bbox.y = 0.5
expr.bbox = { x: 0, y: y0, w: e.bbox.w, h: e.bbox.h + 0.5 } expr.bbox = { x: 0, y: y0, w: e.bbox.w, h: e.bbox.h + 0.5 }
} else if (SYMB[expr.text] && SYMB[expr.text].flags.mat) { } else if (SYMB[expr.text] && SYMB[expr.text].flags.mat) {
const e: Expr = expr.chld[0] const e: Expr = expr.chld[0]
plan(e) plan(e)
expr.bbox = { x: 0, y: 0, w: e.bbox.w, h: e.bbox.h + 0.5 } expr.bbox = { x: 0, y: 0, w: e.bbox.w, h: e.bbox.h + 0.5 }
} else { } else {
let dx = 0 let dx = 0
let dy = 0 let dy = 0
@ -569,13 +623,14 @@ function plan(expr: Expr, mode = 'math'): void {
for (let i = 0; i < expr.chld.length; i++) { for (let i = 0; i < expr.chld.length; i++) {
const c: Expr = expr.chld[i] const c: Expr = expr.chld[i]
// @ts-ignore // @ts-ignore
const spac: number = { const spac: number =
'\\quad': 2, {
'\\,': 2 * 3 / 18, '\\quad': 2,
'\\:': 2 * 4 / 18, '\\,': (2 * 3) / 18,
'\\;': 2 * 5 / 18, '\\:': (2 * 4) / 18,
'\\!': 2 * (-3) / 18, '\\;': (2 * 5) / 18,
}[c.text] ?? null '\\!': (2 * -3) / 18
}[c.text] ?? null
if (c.text == '\\\\') { if (c.text == '\\\\') {
dy += mh dy += mh
@ -593,10 +648,16 @@ function plan(expr: Expr, mode = 'math'): void {
transform(c, 1, null, dx, dy) transform(c, 1, null, dx, dy)
if (c.text == '^' || c.text == '_' || c.text == '\'') { if (c.text == '^' || c.text == '_' || c.text == '\'') {
let j: number = i let j: number = i
while (j > 0 && (expr.chld[j].text == '^' || expr.chld[j].text == '_' || expr.chld[j].text == '\'')) { while (
j > 0 &&
(expr.chld[j].text == '^' ||
expr.chld[j].text == '_' ||
expr.chld[j].text == '\'')
) {
j-- j--
} }
const wasBig = SYMB[expr.chld[j].text] && SYMB[expr.chld[j].text].flags.big const wasBig =
SYMB[expr.chld[j].text] && SYMB[expr.chld[j].text].flags.big
if (c.text == '\'') { if (c.text == '\'') {
let k = j + 1 let k = j + 1
let nth = 0 let nth = 0
@ -606,13 +667,21 @@ function plan(expr: Expr, mode = 'math'): void {
} }
k++ k++
} }
c.bbox.x = expr.chld[j].bbox.x + expr.chld[j].bbox.w + c.bbox.w * nth c.bbox.x =
expr.chld[j].bbox.x + expr.chld[j].bbox.w + c.bbox.w * nth
dx = Math.max(dx, c.bbox.x + c.bbox.w) dx = Math.max(dx, c.bbox.x + c.bbox.w)
} else { } else {
if (wasBig) { if (wasBig) {
const ex = expr.chld[j].bbox.x + (expr.chld[j].bbox.w - c.bbox.w * CONFIG.SUB_SUP_SCALE) / 2 const ex =
expr.chld[j].bbox.x +
(expr.chld[j].bbox.w - c.bbox.w * CONFIG.SUB_SUP_SCALE) / 2
c.bbox.x = ex c.bbox.x = ex
dx = Math.max(dx, expr.chld[j].bbox.x + expr.chld[j].bbox.w + (c.bbox.w * CONFIG.SUB_SUP_SCALE - expr.chld[j].bbox.w) / 2) dx = Math.max(
dx,
expr.chld[j].bbox.x +
expr.chld[j].bbox.w +
(c.bbox.w * CONFIG.SUB_SUP_SCALE - expr.chld[j].bbox.w) / 2
)
} else { } else {
c.bbox.x = expr.chld[j].bbox.x + expr.chld[j].bbox.w c.bbox.x = expr.chld[j].bbox.x + expr.chld[j].bbox.w
dx = Math.max(dx, c.bbox.x + c.bbox.w * CONFIG.SUB_SUP_SCALE) dx = Math.max(dx, c.bbox.x + c.bbox.w * CONFIG.SUB_SUP_SCALE)
@ -629,19 +698,20 @@ function plan(expr: Expr, mode = 'math'): void {
} }
dy += mh dy += mh
const m2s: Record<string, string[]> = { const m2s: Record<string, string[]> = {
'bmatrix': ['[', ']'], bmatrix: ['[', ']'],
'pmatrix': ['(', ')'], pmatrix: ['(', ')'],
'Bmatrix': ['\\{', '\\}'], Bmatrix: ['\\{', '\\}'],
'cases': ['\\{'] cases: ['\\{']
} }
const alt: string = { const alt: string =
'bmatrix': 'center', {
'pmatrix': 'center', bmatrix: 'center',
'Bmatrix': 'center', pmatrix: 'center',
'cases': 'left', Bmatrix: 'center',
'matrix': 'center', cases: 'left',
'aligned': 'equation', matrix: 'center',
}[expr.text] ?? 'left' aligned: 'equation'
}[expr.text] ?? 'left'
const hasLp = !!m2s[expr.text] const hasLp = !!m2s[expr.text]
const hasRp = !!m2s[expr.text] && m2s[expr.text].length > 1 const hasRp = !!m2s[expr.text] && m2s[expr.text].length > 1
@ -657,16 +727,29 @@ function plan(expr: Expr, mode = 'math'): void {
// @ts-ignore // @ts-ignore
transform(expr.chld[i], 1, null, -bb.x + (hasLp ? 1.5 : 0), -bb.y) transform(expr.chld[i], 1, null, -bb.x + (hasLp ? 1.5 : 0), -bb.y)
} }
expr.bbox = { x: 0, y: 0, w: bb.w + 1.5 * Number(hasLp) + 1.5 * Number(hasRp), h: bb.h } expr.bbox = {
x: 0,
y: 0,
w: bb.w + 1.5 * Number(hasLp) + 1.5 * Number(hasRp),
h: bb.h
}
if (hasLp) { if (hasLp) {
expr.chld.unshift({ expr.chld.unshift({
type: 'symb', text: m2s[expr.text][0], mode: expr.mode, chld: [], bbox: { x: 0, y: 0, w: 1, h: bb.h } type: 'symb',
text: m2s[expr.text][0],
mode: expr.mode,
chld: [],
bbox: { x: 0, y: 0, w: 1, h: bb.h }
}) })
} }
if (hasRp) { if (hasRp) {
expr.chld.push({ expr.chld.push({
type: 'symb', text: m2s[expr.text][1], mode: expr.mode, chld: [], bbox: { x: bb.w + 2, y: 0, w: 1, h: bb.h } type: 'symb',
text: m2s[expr.text][1],
mode: expr.mode,
chld: [],
bbox: { x: bb.w + 2, y: 0, w: 1, h: bb.h }
}) })
} }
if (hasLp || hasRp || expr.text == 'matrix') { if (hasLp || hasRp || expr.text == 'matrix') {
@ -684,7 +767,8 @@ function flatten(expr: Expr) {
dx += expr.bbox.x dx += expr.bbox.x
dy += expr.bbox.y dy += expr.bbox.y
if (expr.text == '\\frac') { if (expr.text == '\\frac') {
const h: number = expr.chld[1].bbox.y - (expr.chld[0].bbox.y + expr.chld[0].bbox.h) const h: number =
expr.chld[1].bbox.y - (expr.chld[0].bbox.y + expr.chld[0].bbox.h)
const e: Expr = { const e: Expr = {
type: 'symb', type: 'symb',
mode: expr.mode, mode: expr.mode,
@ -693,13 +777,17 @@ function flatten(expr: Expr) {
x: dx, x: dx,
y: dy + (expr.chld[1].bbox.y - h / 2) - h / 2, y: dy + (expr.chld[1].bbox.y - h / 2) - h / 2,
w: expr.bbox.w, w: expr.bbox.w,
h: h, h: h
}, chld: [], },
chld: []
} }
ff.push(e) ff.push(e)
} else if (expr.text == '\\sqrt') { } else if (expr.text == '\\sqrt') {
const h: number = expr.chld[0].bbox.y const h: number = expr.chld[0].bbox.y
const xx: number = Math.max(0, expr.chld[0].bbox.x - expr.chld[0].bbox.h / 2) const xx: number = Math.max(
0,
expr.chld[0].bbox.x - expr.chld[0].bbox.h / 2
)
const e: Expr = { const e: Expr = {
type: 'symb', type: 'symb',
mode: expr.mode, mode: expr.mode,
@ -708,8 +796,9 @@ function flatten(expr: Expr) {
x: dx + xx, x: dx + xx,
y: dy + h / 2, y: dy + h / 2,
w: expr.chld[0].bbox.x - xx, w: expr.chld[0].bbox.x - xx,
h: expr.bbox.h - h / 2, h: expr.bbox.h - h / 2
}, chld: [], },
chld: []
} }
ff.push(e) ff.push(e)
ff.push({ ff.push({
@ -720,8 +809,9 @@ function flatten(expr: Expr) {
x: dx + expr.chld[0].bbox.x, x: dx + expr.chld[0].bbox.x,
y: dy, y: dy,
w: expr.bbox.w - expr.chld[0].bbox.x, w: expr.bbox.w - expr.chld[0].bbox.x,
h: h, h: h
}, chld: [], },
chld: []
}) })
} else if (expr.text == '\\binom') { } else if (expr.text == '\\binom') {
const w = Math.min(expr.chld[0].bbox.x, expr.chld[1].bbox.x) const w = Math.min(expr.chld[0].bbox.x, expr.chld[1].bbox.x)
@ -733,8 +823,9 @@ function flatten(expr: Expr) {
x: dx, x: dx,
y: dy, y: dy,
w: w, w: w,
h: expr.bbox.h, h: expr.bbox.h
}, chld: [], },
chld: []
} }
ff.push(e) ff.push(e)
ff.push({ ff.push({
@ -745,8 +836,9 @@ function flatten(expr: Expr) {
x: dx + expr.bbox.w - w, x: dx + expr.bbox.w - w,
y: dy, y: dy,
w: w, w: w,
h: expr.bbox.h, h: expr.bbox.h
}, chld: [], },
chld: []
}) })
} else if (SYMB[expr.text] && SYMB[expr.text].flags.hat) { } else if (SYMB[expr.text] && SYMB[expr.text].flags.hat) {
const h: number = expr.chld[0].bbox.y const h: number = expr.chld[0].bbox.y
@ -758,8 +850,9 @@ function flatten(expr: Expr) {
x: dx, x: dx,
y: dy, y: dy,
w: expr.bbox.w, w: expr.bbox.w,
h: h, h: h
}, chld: [], },
chld: []
} }
ff.push(e) ff.push(e)
} else if (SYMB[expr.text] && SYMB[expr.text].flags.mat) { } else if (SYMB[expr.text] && SYMB[expr.text].flags.mat) {
@ -772,21 +865,23 @@ function flatten(expr: Expr) {
x: dx, x: dx,
y: dy + h, y: dy + h,
w: expr.bbox.w, w: expr.bbox.w,
h: expr.bbox.h - h, h: expr.bbox.h - h
}, chld: [], },
chld: []
} }
ff.push(e) ff.push(e)
} else if (expr.type != 'node' } else if (expr.type != 'node' && expr.text != '^' && expr.text != '_') {
&& expr.text != '^'
&& expr.text != '_'
) {
const e: Expr = { const e: Expr = {
type: expr.type == 'func' ? 'symb' : expr.type, text: expr.text, mode: expr.mode, bbox: { type: expr.type == 'func' ? 'symb' : expr.type,
text: expr.text,
mode: expr.mode,
bbox: {
x: dx, x: dx,
y: dy, y: dy,
w: expr.bbox.w, w: expr.bbox.w,
h: expr.bbox.h h: expr.bbox.h
}, chld: [], },
chld: []
} }
ff.push(e) ff.push(e)
} }
@ -809,8 +904,12 @@ function render(expr: Expr): number[][][] {
const e: Expr = expr.chld[i] const e: Expr = expr.chld[i]
let s = e.bbox.h / 2 let s = e.bbox.h / 2
let isSmallHat = false let isSmallHat = false
if (SYMB[e.text] && SYMB[e.text].flags.hat && if (
!SYMB[e.text].flags.xfl && !SYMB[e.text].flags.yfl) { SYMB[e.text] &&
SYMB[e.text].flags.hat &&
!SYMB[e.text].flags.xfl &&
!SYMB[e.text].flags.yfl
) {
s *= 4 s *= 4
isSmallHat = true isSmallHat = true
} }
@ -824,25 +923,24 @@ function render(expr: Expr): number[][][] {
let y = d.polylines[j][k][1] let y = d.polylines[j][k][1]
if (SYMB[e.text].flags.xfl) { if (SYMB[e.text].flags.xfl) {
x = (x - d.xmin) / Math.max(d.xmax - d.xmin, 1) * e.bbox.w x = ((x - d.xmin) / Math.max(d.xmax - d.xmin, 1)) * e.bbox.w
x += e.bbox.x x += e.bbox.x
} else if (d.w / 16 * s > e.bbox.w) { } else if ((d.w / 16) * s > e.bbox.w) {
x = x / Math.max(d.w, 1) * e.bbox.w x = (x / Math.max(d.w, 1)) * e.bbox.w
x += e.bbox.x x += e.bbox.x
} else { } else {
x = (x / 16) * s
x = x / 16 * s const p = (e.bbox.w - (d.w / 16) * s) / 2
const p = (e.bbox.w - d.w / 16 * s) / 2
x += e.bbox.x + p x += e.bbox.x + p
} }
if (SYMB[e.text].flags.yfl) { if (SYMB[e.text].flags.yfl) {
y = (y - d.ymin) / Math.max(d.ymax - d.ymin, 1) * e.bbox.h y = ((y - d.ymin) / Math.max(d.ymax - d.ymin, 1)) * e.bbox.h
y += e.bbox.y y += e.bbox.y
} else { } else {
y = y / 16 * s y = (y / 16) * s
if (isSmallHat) { if (isSmallHat) {
const p = (d.ymax + d.ymin) / 2 const p = (d.ymax + d.ymin) / 2
y -= p / 16 * s y -= (p / 16) * s
} }
y += e.bbox.y + e.bbox.h / 2 y += e.bbox.y + e.bbox.h / 2
} }
@ -850,7 +948,7 @@ function render(expr: Expr): number[][][] {
} }
o.push(l) o.push(l)
} }
} else if ((SYMB[e.text] && SYMB[e.text].flags.txt) || (e.type == 'char')) { } else if ((SYMB[e.text] && SYMB[e.text].flags.txt) || e.type == 'char') {
let x0 = e.bbox.x let x0 = e.bbox.x
const isVerb = !!(SYMB[e.text] && SYMB[e.text].flags.txt) const isVerb = !!(SYMB[e.text] && SYMB[e.text].flags.txt)
for (let n = Number(isVerb); n < e.text.length; n++) { for (let n = Number(isVerb); n < e.text.length; n++) {
@ -874,7 +972,6 @@ function render(expr: Expr): number[][][] {
} else { } else {
x += (16 - d.w) / 2 / 16 x += (16 - d.w) / 2 / 16
} }
} }
x += x0 x += x0
y += e.bbox.y + e.bbox.h / 2 y += e.bbox.y + e.bbox.h / 2
@ -885,7 +982,7 @@ function render(expr: Expr): number[][][] {
if (e.mode == 'tt') { if (e.mode == 'tt') {
x0 += s x0 += s
} else { } else {
x0 += d.w / 16 * s x0 += (d.w / 16) * s
} }
} }
} }
@ -894,16 +991,16 @@ function render(expr: Expr): number[][][] {
} }
interface ExportOpt { interface ExportOpt {
MIN_CHAR_H?: number; MIN_CHAR_H?: number
MAX_W?: number; MAX_W?: number
MAX_H?: number; MAX_H?: number
MARGIN_X?: number; MARGIN_X?: number
MARGIN_Y?: number; MARGIN_Y?: number
SCALE_X?: number; SCALE_X?: number
SCALE_Y?: number; SCALE_Y?: number
STROKE_W?: number; STROKE_W?: number
FG_COLOR?: string; FG_COLOR?: string
BG_COLOR?: string; BG_COLOR?: string
} }
function nf(x: number): number { function nf(x: number): number {
@ -911,16 +1008,16 @@ function nf(x: number): number {
} }
export interface LaTexSVG { export interface LaTexSVG {
svg: string; svg: string
width: number; width: number
height: number; height: number
} }
export class LaTexUtils { export class LaTexUtils {
_latex: string; _latex: string
_tree: Expr; _tree: Expr
_tokens: string[]; _tokens: string[]
_polylines: number[][][]; _polylines: number[][][]
constructor(latex: string) { constructor(latex: string) {
this._latex = latex this._latex = latex
@ -943,9 +1040,11 @@ export class LaTexUtils {
let mh = 0 let mh = 0
for (let i = 0; i < this._tree.chld.length; i++) { for (let i = 0; i < this._tree.chld.length; i++) {
const c: Expr = this._tree.chld[i] const c: Expr = this._tree.chld[i]
if (c.type == 'char' || if (
c.type == 'char' ||
(SYMB[c.text] && (SYMB[c.text] &&
(SYMB[c.text].flags.txt || !Object.keys(SYMB[c.text].flags).length))) { (SYMB[c.text].flags.txt || !Object.keys(SYMB[c.text].flags).length))
) {
mh = Math.min(c.bbox.h, mh) mh = Math.min(c.bbox.h, mh)
} }
} }
@ -956,12 +1055,12 @@ export class LaTexUtils {
if (opt.MAX_W != undefined) { if (opt.MAX_W != undefined) {
const s0 = sclx const s0 = sclx
sclx = Math.min(sclx, opt.MAX_W / this._tree.bbox.w) sclx = Math.min(sclx, opt.MAX_W / this._tree.bbox.w)
scly *= (sclx / s0) scly *= sclx / s0
} }
if (opt.MAX_H != undefined) { if (opt.MAX_H != undefined) {
const s0 = scly const s0 = scly
scly = Math.min(scly, opt.MAX_H / this._tree.bbox.h) scly = Math.min(scly, opt.MAX_H / this._tree.bbox.h)
sclx *= (scly / s0) sclx *= scly / s0
} }
const px: number = opt.MARGIN_X ?? sclx const px: number = opt.MARGIN_X ?? sclx
const py: number = opt.MARGIN_Y ?? scly const py: number = opt.MARGIN_Y ?? scly
@ -989,7 +1088,7 @@ export class LaTexUtils {
for (let i = 0; i < this._polylines.length; i++) { for (let i = 0; i < this._polylines.length; i++) {
for (let j = 0; j < this._polylines[i].length; j++) { for (let j = 0; j < this._polylines[i].length; j++) {
const [x, y] = this._polylines[i][j] const [x, y] = this._polylines[i][j]
d += (!j) ? 'M' : 'L' d += !j ? 'M' : 'L'
d += `${nf(px + x * sclx)} ${nf(py + y * scly)}` d += `${nf(px + x * sclx)} ${nf(py + y * scly)}`
} }
} }
@ -1004,18 +1103,22 @@ export class LaTexUtils {
let o = `<svg let o = `<svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="${w}" height="${h}" width="${w}" height="${h}"
fill="none" stroke="${opt.FG_COLOR ?? 'black'}" stroke-width="${opt.STROKE_W ?? 1}" fill="none" stroke="${opt.FG_COLOR ?? 'black'}" stroke-width="${
opt.STROKE_W ?? 1
}"
stroke-linecap="round" stroke-linejoin="round" stroke-linecap="round" stroke-linejoin="round"
>` >`
if (opt.BG_COLOR) { if (opt.BG_COLOR) {
o += `<rect x="${0}" y="${0}" width="${w}" height="${h}" fill="${opt.BG_COLOR}" stroke="none"></rect>` o += `<rect x="${0}" y="${0}" width="${w}" height="${h}" fill="${
opt.BG_COLOR
}" stroke="none"></rect>`
} }
o += `<path d="` o += `<path d="`
for (let i = 0; i < this._polylines.length; i++) { for (let i = 0; i < this._polylines.length; i++) {
o += 'M' o += 'M'
for (let j = 0; j < this._polylines[i].length; j++) { for (let j = 0; j < this._polylines[i].length; j++) {
const [x, y] = this._polylines[i][j] const [x, y] = this._polylines[i][j]
o += (nf(px + x * sclx)) + ' ' + (nf(py + y * scly)) + ' ' o += nf(px + x * sclx) + ' ' + nf(py + y * scly) + ' '
} }
} }
o += `"/>` o += `"/>`
@ -1040,10 +1143,14 @@ export class LaTexUtils {
let pdf = '' let pdf = ''
let count = 4 let count = 4
for (let i = 0; i < this._polylines.length; i++) { for (let i = 0; i < this._polylines.length; i++) {
pdf += `${count} 0 obj \n<< /Length 0 >>\n stream\n 1 j 1 J ${opt.STROKE_W ?? 1} w\n` pdf += `${count} 0 obj \n<< /Length 0 >>\n stream\n 1 j 1 J ${
opt.STROKE_W ?? 1
} w\n`
for (let j = 0; j < this._polylines[i].length; j++) { for (let j = 0; j < this._polylines[i].length; j++) {
const [x, y] = this._polylines[i][j] const [x, y] = this._polylines[i][j]
pdf += `${nf(px + x * sclx)} ${nf(height - (py + y * scly))} ${j ? 'l' : 'm'} ` pdf += `${nf(px + x * sclx)} ${nf(height - (py + y * scly))} ${
j ? 'l' : 'm'
} `
} }
pdf += '\nS\nendstream\nendobj\n' pdf += '\nS\nendstream\nendobj\n'
head += `${count} 0 R ` head += `${count} 0 R `
@ -1077,6 +1184,13 @@ export class LaTexUtils {
} }
} }
const _impl: Record<string, Function> = { tokenize, parse, environments, plan, flatten, render } const _impl: Record<string, Function> = {
tokenize,
parse,
environments,
plan,
flatten,
render
}
export { CONFIG, _impl } export { CONFIG, _impl }

@ -1,10 +1,10 @@
interface HersheyEntry { interface HersheyEntry {
w: number; w: number
xmin: number; xmin: number
xmax: number; xmax: number
ymin: number; ymin: number
ymax: number; ymax: number
polylines: Array<Array<Array<number>>>; polylines: Array<Array<Array<number>>>
} }
const ordR = 'R'.charCodeAt(0) const ordR = 'R'.charCodeAt(0)
@ -53,7 +53,7 @@ function compile(i: number): void {
xmax: zmax, xmax: zmax,
ymin: ymin, ymin: ymin,
ymax: ymax, ymax: ymax,
polylines: polylines, polylines: polylines
} }
} }
const data: Record<number, HersheyEntry> = {} const data: Record<number, HersheyEntry> = {}
@ -1628,5 +1628,5 @@ const raw: Record<number, string> = {
3923: ' 59E_HMIOIXM[OYQX RINJOJXMZ RHMJNKOKWNYOY RRMPOQPQXU[WY[W RRPSORNQORPRXUZ RRMTOSPSWVYWY RZM\\O[P[W RZP[OZNYOZPZW RZMXOYPYX', 3923: ' 59E_HMIOIXM[OYQX RINJOJXMZ RHMJNKOKWNYOY RRMPOQPQXU[WY[W RRPSORNQORPRXUZ RRMTOSPSWVYWY RZM\\O[P[W RZP[OZNYOZPZW RZMXOYPYX',
3924: ' 39I[LONPUZV[XY RMNOOUYWZ RLONMONVXXY RXMVMVOXOXMVOSS RQUNYL[N[NYLYL[ RNTQT RSTVT', 3924: ' 39I[LONPUZV[XY RMNOOUYWZ RLONMONVXXY RXMVMVOXOXMVOSS RQUNYL[N[NYLYL[ RNTQT RSTVT',
3925: ' 49I[KOLOMPMYP[UY RMNNONYPZ RKOMMOOOXRZ RVMXOWPW]V_U`SaQaO`MaObQa RVPWOVNUOVPV^U_ RPaNa RVMTOUPU^T`Sa', 3925: ' 49I[KOLOMPMYP[UY RMNNONYPZ RKOMMOOOXRZ RVMXOWPW]V_U`SaQaO`MaObQa RVPWOVNUOVPV^U_ RPaNa RVMTOUPU^T`Sa',
3926: ' 43L[RNOPOORNTMWOWSRU RTNVOVS RRNUPUSTT RRUWWW]V_U`SaQaO`MaObQa RVWV^U_ RPaNa RTVUWU^T`Sa', 3926: ' 43L[RNOPOORNTMWOWSRU RTNVOVS RRNUPUSTT RRUWWW]V_U`SaQaO`MaObQa RVWV^U_ RPaNa RTVUWU^T`Sa'
} }

@ -3,17 +3,21 @@ https://oeis.org/wiki/List_of_LaTeX_mathematical_symbols
https://en.wikibooks.org/wiki/LaTeX/Mathematics https://en.wikibooks.org/wiki/LaTeX/Mathematics
*/ */
export interface Symb { export interface Symb {
glyph: number; glyph: number
arity?: number; arity?: number
flags: Record<string, boolean>; flags: Record<string, boolean>
} }
const SYMB: Record<string, Symb> = { const SYMB: Record<string, Symb> = {
'\\frac': { glyph: 0, arity: 2, flags: {} }, '\\frac': { glyph: 0, arity: 2, flags: {} },
'\\binom': { glyph: 0, arity: 2, flags: {} }, '\\binom': { glyph: 0, arity: 2, flags: {} },
'\\sqrt': { glyph: 2267, arity: 1, flags: { opt: true, xfl: true, yfl: true } }, '\\sqrt': {
glyph: 2267,
arity: 1,
flags: { opt: true, xfl: true, yfl: true }
},
'^': { glyph: 0, arity: 1, flags: {} }, '^': { glyph: 0, arity: 1, flags: {} },
'_': { glyph: 0, arity: 1, flags: {} }, _: { glyph: 0, arity: 1, flags: {} },
'(': { glyph: 2221, arity: 0, flags: { yfl: true } }, '(': { glyph: 2221, arity: 0, flags: { yfl: true } },
')': { glyph: 2222, arity: 0, flags: { yfl: true } }, ')': { glyph: 2222, arity: 0, flags: { yfl: true } },
'[': { glyph: 2223, arity: 0, flags: { yfl: true } }, '[': { glyph: 2223, arity: 0, flags: { yfl: true } },
@ -56,12 +60,28 @@ const SYMB: Record<string, Symb> = {
'\\ell': { glyph: 662, arity: 0, flags: {} }, '\\ell': { glyph: 662, arity: 0, flags: {} },
/*accents*/ /*accents*/
'\\vec': { glyph: 2261, arity: 1, flags: { hat: true, xfl: true, yfl: true } }, '\\vec': {
'\\overrightarrow': { glyph: 2261, arity: 1, flags: { hat: true, xfl: true, yfl: true } }, glyph: 2261,
'\\overleftarrow': { glyph: 2263, arity: 1, flags: { hat: true, xfl: true, yfl: true } }, arity: 1,
flags: { hat: true, xfl: true, yfl: true }
},
'\\overrightarrow': {
glyph: 2261,
arity: 1,
flags: { hat: true, xfl: true, yfl: true }
},
'\\overleftarrow': {
glyph: 2263,
arity: 1,
flags: { hat: true, xfl: true, yfl: true }
},
'\\bar': { glyph: 2231, arity: 1, flags: { hat: true, xfl: true } }, '\\bar': { glyph: 2231, arity: 1, flags: { hat: true, xfl: true } },
'\\overline': { glyph: 2231, arity: 1, flags: { hat: true, xfl: true } }, '\\overline': { glyph: 2231, arity: 1, flags: { hat: true, xfl: true } },
'\\widehat': { glyph: 2247, arity: 1, flags: { hat: true, xfl: true, yfl: true } }, '\\widehat': {
glyph: 2247,
arity: 1,
flags: { hat: true, xfl: true, yfl: true }
},
'\\hat': { glyph: 2247, arity: 1, flags: { hat: true } }, '\\hat': { glyph: 2247, arity: 1, flags: { hat: true } },
'\\acute': { glyph: 2248, arity: 1, flags: { hat: true } }, '\\acute': { glyph: 2248, arity: 1, flags: { hat: true } },
'\\grave': { glyph: 2249, arity: 1, flags: { hat: true } }, '\\grave': { glyph: 2249, arity: 1, flags: { hat: true } },
@ -219,7 +239,7 @@ const SYMB: Record<string, Symb> = {
'\\Phi': { glyph: 2047, flags: {} }, '\\Phi': { glyph: 2047, flags: {} },
'\\Chi': { glyph: 2048, flags: {} }, '\\Chi': { glyph: 2048, flags: {} },
'\\Psi': { glyph: 2049, flags: {} }, '\\Psi': { glyph: 2049, flags: {} },
'\\Omega': { glyph: 2050, flags: {} }, '\\Omega': { glyph: 2050, flags: {} }
} }
export { SYMB } export { SYMB }
@ -293,6 +313,6 @@ export function asciiMap(x: string, mode = 'math'): number {
'>': 2242, '>': 2242,
'~': 2246, '~': 2246,
'@': 2273, '@': 2273,
'\\': 804, '\\': 804
}[x] }[x]
} }

@ -1,12 +1,14 @@
import { EDITOR_PREFIX } from '../../../../dataset/constant/Editor' import { EDITOR_PREFIX } from '../../../../dataset/constant/Editor'
import { IEditorOption } from '../../../../interface/Editor' import { IEditorOption } from '../../../../interface/Editor'
import { IElement, IElementPosition } from '../../../../interface/Element' import { IElement, IElementPosition } from '../../../../interface/Element'
import { IPreviewerCreateResult, IPreviewerDrawOption } from '../../../../interface/Previewer' import {
IPreviewerCreateResult,
IPreviewerDrawOption
} from '../../../../interface/Previewer'
import { downloadFile } from '../../../../utils' import { downloadFile } from '../../../../utils'
import { Draw } from '../../Draw' import { Draw } from '../../Draw'
export class Previewer { export class Previewer {
private container: HTMLDivElement private container: HTMLDivElement
private canvas: HTMLCanvasElement private canvas: HTMLCanvasElement
private draw: Draw private draw: Draw
@ -39,7 +41,12 @@ export class Previewer {
this.previewerDrawOption = {} this.previewerDrawOption = {}
this.curPosition = null this.curPosition = null
// 图片尺寸缩放 // 图片尺寸缩放
const { resizerSelection, resizerHandleList, resizerImageContainer, resizerImage } = this._createResizerDom() const {
resizerSelection,
resizerHandleList,
resizerImageContainer,
resizerImage
} = this._createResizerDom()
this.resizerSelection = resizerSelection this.resizerSelection = resizerSelection
this.resizerHandleList = resizerHandleList this.resizerHandleList = resizerHandleList
this.resizerImageContainer = resizerImageContainer this.resizerImageContainer = resizerImageContainer
@ -79,7 +86,12 @@ export class Previewer {
const resizerImage = document.createElement('img') const resizerImage = document.createElement('img')
resizerImageContainer.append(resizerImage) resizerImageContainer.append(resizerImage)
this.container.append(resizerImageContainer) this.container.append(resizerImageContainer)
return { resizerSelection, resizerHandleList, resizerImageContainer, resizerImage } return {
resizerSelection,
resizerHandleList,
resizerImageContainer,
resizerImage
}
} }
private _keydown = () => { private _keydown = () => {
@ -107,7 +119,11 @@ export class Previewer {
// 拖拽图片镜像 // 拖拽图片镜像
this.resizerImage.src = this.curElementSrc this.resizerImage.src = this.curElementSrc
this.resizerImageContainer.style.display = 'block' this.resizerImageContainer.style.display = 'block'
const { coordinate: { leftTop: [left, top] } } = this.curPosition const {
coordinate: {
leftTop: [left, top]
}
} = this.curPosition
const prePageHeight = this.draw.getPageNo() * (height + pageGap) const prePageHeight = this.draw.getPageNo() * (height + pageGap)
this.resizerImageContainer.style.left = `${left}px` this.resizerImageContainer.style.left = `${left}px`
this.resizerImageContainer.style.top = `${top + prePageHeight}px` this.resizerImageContainer.style.top = `${top + prePageHeight}px`
@ -116,22 +132,30 @@ export class Previewer {
// 追加全局事件 // 追加全局事件
const mousemoveFn = this._mousemove.bind(this) const mousemoveFn = this._mousemove.bind(this)
document.addEventListener('mousemove', mousemoveFn) document.addEventListener('mousemove', mousemoveFn)
document.addEventListener('mouseup', () => { document.addEventListener(
// 改变尺寸 'mouseup',
if (this.curElement && this.curPosition) { () => {
this.curElement.width = this.width // 改变尺寸
this.curElement.height = this.height if (this.curElement && this.curPosition) {
this.draw.render({ isSetCursor: false }) this.curElement.width = this.width
this.drawResizer(this.curElement, this.curPosition, this.previewerDrawOption) this.curElement.height = this.height
this.draw.render({ isSetCursor: false })
this.drawResizer(
this.curElement,
this.curPosition,
this.previewerDrawOption
)
}
// 还原副作用
this.resizerImageContainer.style.display = 'none'
document.removeEventListener('mousemove', mousemoveFn)
document.body.style.cursor = ''
this.canvas.style.cursor = 'text'
},
{
once: true
} }
// 还原副作用 )
this.resizerImageContainer.style.display = 'none'
document.removeEventListener('mousemove', mousemoveFn)
document.body.style.cursor = ''
this.canvas.style.cursor = 'text'
}, {
once: true
})
evt.preventDefault() evt.preventDefault()
} }
@ -254,7 +278,7 @@ export class Previewer {
let startX = 0 let startX = 0
let startY = 0 let startY = 0
let isAllowDrag = false let isAllowDrag = false
img.onmousedown = (evt) => { img.onmousedown = evt => {
isAllowDrag = true isAllowDrag = true
startX = evt.x startX = evt.x
startY = evt.y startY = evt.y
@ -262,8 +286,8 @@ export class Previewer {
} }
previewerContainer.onmousemove = (evt: MouseEvent) => { previewerContainer.onmousemove = (evt: MouseEvent) => {
if (!isAllowDrag) return if (!isAllowDrag) return
x += (evt.x - startX) x += evt.x - startX
y += (evt.y - startY) y += evt.y - startY
startX = evt.x startX = evt.x
startY = evt.y startY = evt.y
this._setPreviewerTransform(scaleSize, rotateSize, x, y) this._setPreviewerTransform(scaleSize, rotateSize, x, y)
@ -272,7 +296,7 @@ export class Previewer {
isAllowDrag = false isAllowDrag = false
previewerContainer.style.cursor = 'auto' previewerContainer.style.cursor = 'auto'
} }
previewerContainer.onwheel = (evt) => { previewerContainer.onwheel = evt => {
evt.preventDefault() evt.preventDefault()
if (evt.deltaY < 0) { if (evt.deltaY < 0) {
// 放大 // 放大
@ -286,11 +310,18 @@ export class Previewer {
} }
} }
public _setPreviewerTransform(scale: number, rotate: number, x: number, y: number) { public _setPreviewerTransform(
scale: number,
rotate: number,
x: number,
y: number
) {
if (!this.previewerImage) return if (!this.previewerImage) return
this.previewerImage.style.left = `${x}px` this.previewerImage.style.left = `${x}px`
this.previewerImage.style.top = `${y}px` this.previewerImage.style.top = `${y}px`
this.previewerImage.style.transform = `scale(${scale}) rotate(${rotate * 90}deg)` this.previewerImage.style.transform = `scale(${scale}) rotate(${
rotate * 90
}deg)`
} }
private _clearPreviewer() { private _clearPreviewer() {
@ -299,10 +330,19 @@ export class Previewer {
document.body.style.overflow = 'auto' document.body.style.overflow = 'auto'
} }
public drawResizer(element: IElement, position: IElementPosition, options: IPreviewerDrawOption = {}) { public drawResizer(
element: IElement,
position: IElementPosition,
options: IPreviewerDrawOption = {}
) {
this.previewerDrawOption = options this.previewerDrawOption = options
const { scale } = this.options const { scale } = this.options
const { coordinate: { leftTop: [left, top] }, ascent } = position const {
coordinate: {
leftTop: [left, top]
},
ascent
} = position
const elementWidth = element.width! * scale const elementWidth = element.width! * scale
const elementHeight = element.height! * scale const elementHeight = element.height! * scale
const height = this.draw.getHeight() const height = this.draw.getHeight()
@ -316,14 +356,16 @@ export class Previewer {
this.resizerSelection.style.height = `${elementHeight}px` this.resizerSelection.style.height = `${elementHeight}px`
// handle // handle
for (let i = 0; i < 8; i++) { for (let i = 0; i < 8; i++) {
const left = i === 0 || i === 6 || i === 7 const left =
? -handleSize i === 0 || i === 6 || i === 7
: i === 1 || i === 5 ? -handleSize
: i === 1 || i === 5
? elementWidth / 2 ? elementWidth / 2
: elementWidth - handleSize : elementWidth - handleSize
const top = i === 0 || i === 1 || i === 2 const top =
? -handleSize i === 0 || i === 1 || i === 2
: i === 3 || i === 7 ? -handleSize
: i === 3 || i === 7
? elementHeight / 2 - handleSize ? elementHeight / 2 - handleSize
: elementHeight - handleSize : elementHeight - handleSize
this.resizerHandleList[i].style.left = `${left}px` this.resizerHandleList[i].style.left = `${left}px`
@ -342,5 +384,4 @@ export class Previewer {
this.resizerSelection.style.display = 'none' this.resizerSelection.style.display = 'none'
document.removeEventListener('keydown', this._keydown) document.removeEventListener('keydown', this._keydown)
} }
} }

@ -7,16 +7,15 @@ import { RangeManager } from '../../../range/RangeManager'
import { Draw } from '../../Draw' import { Draw } from '../../Draw'
interface IDrawTableBorderOption { interface IDrawTableBorderOption {
ctx: CanvasRenderingContext2D; ctx: CanvasRenderingContext2D
startX: number; startX: number
startY: number; startY: number
width: number; width: number
height: number; height: number
isDrawFullBorder?: boolean; isDrawFullBorder?: boolean
} }
export class TableParticle { export class TableParticle {
private draw: Draw private draw: Draw
private range: RangeManager private range: RangeManager
private options: Required<IEditorOption> private options: Required<IEditorOption>
@ -45,9 +44,17 @@ export class TableParticle {
} }
public getRangeRowCol(): ITd[][] | null { public getRangeRowCol(): ITd[][] | null {
const { isTable, index, trIndex, tdIndex } = this.draw.getPosition().getPositionContext() const { isTable, index, trIndex, tdIndex } = this.draw
.getPosition()
.getPositionContext()
if (!isTable) return null if (!isTable) return null
const { isCrossRowCol, startTdIndex, endTdIndex, startTrIndex, endTrIndex } = this.range.getRange() const {
isCrossRowCol,
startTdIndex,
endTdIndex,
startTrIndex,
endTrIndex
} = this.range.getRange()
const originalElementList = this.draw.getOriginalElementList() const originalElementList = this.draw.getOriginalElementList()
const element = originalElementList[index!] const element = originalElementList[index!]
const curTrList = element.trList! const curTrList = element.trList!
@ -59,6 +66,7 @@ export class TableParticle {
let endTd = curTrList[endTrIndex!].tdList[endTdIndex!] let endTd = curTrList[endTrIndex!].tdList[endTdIndex!]
// 交换起始位置 // 交换起始位置
if (startTd.x! > endTd.x! || startTd.y! > endTd.y!) { if (startTd.x! > endTd.x! || startTd.y! > endTd.y!) {
// prettier-ignore
[startTd, endTd] = [endTd, startTd] [startTd, endTd] = [endTd, startTd]
} }
const startColIndex = startTd.colIndex! const startColIndex = startTd.colIndex!
@ -75,8 +83,10 @@ export class TableParticle {
const tdColIndex = td.colIndex! const tdColIndex = td.colIndex!
const tdRowIndex = td.rowIndex! const tdRowIndex = td.rowIndex!
if ( if (
tdColIndex >= startColIndex && tdColIndex <= endColIndex tdColIndex >= startColIndex &&
&& tdRowIndex >= startRowIndex && tdRowIndex <= endRowIndex tdColIndex <= endColIndex &&
tdRowIndex >= startRowIndex &&
tdRowIndex <= endRowIndex
) { ) {
tdList.push(td) tdList.push(td)
} }
@ -105,7 +115,12 @@ export class TableParticle {
ctx.translate(-0.5, -0.5) ctx.translate(-0.5, -0.5)
} }
private _drawBorder(ctx: CanvasRenderingContext2D, element: IElement, startX: number, startY: number) { private _drawBorder(
ctx: CanvasRenderingContext2D,
element: IElement,
startX: number,
startY: number
) {
const { colgroup, trList, borderType } = element const { colgroup, trList, borderType } = element
if (!colgroup || !trList || borderType === TableBorder.EMPTY) return if (!colgroup || !trList || borderType === TableBorder.EMPTY) return
const { scale } = this.options const { scale } = this.options
@ -146,7 +161,12 @@ export class TableParticle {
ctx.restore() ctx.restore()
} }
private _drawBackgroundColor(ctx: CanvasRenderingContext2D, element: IElement, startX: number, startY: number) { private _drawBackgroundColor(
ctx: CanvasRenderingContext2D,
element: IElement,
startX: number,
startY: number
) {
const { trList } = element const { trList } = element
if (!trList) return if (!trList) return
const { scale } = this.options const { scale } = this.options
@ -259,11 +279,22 @@ export class TableParticle {
} }
} }
public drawRange(ctx: CanvasRenderingContext2D, element: IElement, startX: number, startY: number) { public drawRange(
ctx: CanvasRenderingContext2D,
element: IElement,
startX: number,
startY: number
) {
const { scale, rangeAlpha, rangeColor } = this.options const { scale, rangeAlpha, rangeColor } = this.options
const { type, trList } = element const { type, trList } = element
if (!trList || type !== ElementType.TABLE) return if (!trList || type !== ElementType.TABLE) return
const { isCrossRowCol, startTdIndex, endTdIndex, startTrIndex, endTrIndex } = this.range.getRange() const {
isCrossRowCol,
startTdIndex,
endTdIndex,
startTrIndex,
endTrIndex
} = this.range.getRange()
// 存在跨行/列 // 存在跨行/列
if (!isCrossRowCol) return if (!isCrossRowCol) return
let startTd = trList[startTrIndex!].tdList[startTdIndex!] let startTd = trList[startTrIndex!].tdList[startTdIndex!]
@ -284,8 +315,10 @@ export class TableParticle {
const tdColIndex = td.colIndex! const tdColIndex = td.colIndex!
const tdRowIndex = td.rowIndex! const tdRowIndex = td.rowIndex!
if ( if (
tdColIndex >= startColIndex && tdColIndex <= endColIndex tdColIndex >= startColIndex &&
&& tdRowIndex >= startRowIndex && tdRowIndex <= endRowIndex tdColIndex <= endColIndex &&
tdRowIndex >= startRowIndex &&
tdRowIndex <= endRowIndex
) { ) {
const x = td.x! * scale const x = td.x! * scale
const y = td.y! * scale const y = td.y! * scale
@ -300,9 +333,13 @@ export class TableParticle {
ctx.restore() ctx.restore()
} }
public render(ctx: CanvasRenderingContext2D, element: IElement, startX: number, startY: number) { public render(
ctx: CanvasRenderingContext2D,
element: IElement,
startX: number,
startY: number
) {
this._drawBackgroundColor(ctx, element, startX, startY) this._drawBackgroundColor(ctx, element, startX, startY)
this._drawBorder(ctx, element, startX, startY) this._drawBorder(ctx, element, startX, startY)
} }
} }

@ -6,14 +6,13 @@ import { Position } from '../../../position/Position'
import { Draw } from '../../Draw' import { Draw } from '../../Draw'
interface IAnchorMouseDown { interface IAnchorMouseDown {
evt: MouseEvent; evt: MouseEvent
order: TableOrder; order: TableOrder
index: number; index: number
element: IElement; element: IElement
} }
export class TableTool { export class TableTool {
// 单元格最小宽度 // 单元格最小宽度
private readonly MIN_TD_WIDTH = 20 private readonly MIN_TD_WIDTH = 20
// 行列工具相对表格偏移值 // 行列工具相对表格偏移值
@ -58,12 +57,11 @@ export class TableTool {
} }
public render() { public render() {
const { isTable, index, trIndex, tdIndex } = this.position.getPositionContext() const { isTable, index, trIndex, tdIndex } =
this.position.getPositionContext()
if (!isTable) return if (!isTable) return
// 销毁之前工具 // 销毁之前工具
this.dispose() this.dispose()
// 渲染所需数据 // 渲染所需数据
const { scale } = this.options const { scale } = this.options
const elementList = this.draw.getOriginalElementList() const elementList = this.draw.getOriginalElementList()
@ -71,7 +69,9 @@ export class TableTool {
const element = elementList[index!] const element = elementList[index!]
const position = positionList[index!] const position = positionList[index!]
const { colgroup, trList } = element const { colgroup, trList } = element
const { coordinate: { leftTop } } = position const {
coordinate: { leftTop }
} = position
const height = this.draw.getHeight() const height = this.draw.getHeight()
const pageGap = this.draw.getPageGap() const pageGap = this.draw.getPageGap()
const prePageHeight = this.draw.getPageNo() * (height + pageGap) const prePageHeight = this.draw.getPageNo() * (height + pageGap)
@ -80,12 +80,13 @@ export class TableTool {
const td = element.trList![trIndex!].tdList[tdIndex!] const td = element.trList![trIndex!].tdList[tdIndex!]
const rowIndex = td.rowIndex const rowIndex = td.rowIndex
const colIndex = td.colIndex const colIndex = td.colIndex
// 渲染行工具 // 渲染行工具
const rowHeightList = trList!.map(tr => tr.height) const rowHeightList = trList!.map(tr => tr.height)
const rowContainer = document.createElement('div') const rowContainer = document.createElement('div')
rowContainer.classList.add(`${EDITOR_PREFIX}-table-tool__row`) rowContainer.classList.add(`${EDITOR_PREFIX}-table-tool__row`)
rowContainer.style.transform = `translateX(-${this.ROW_COL_OFFSET * scale}px)` rowContainer.style.transform = `translateX(-${
this.ROW_COL_OFFSET * scale
}px)`
for (let r = 0; r < rowHeightList.length; r++) { for (let r = 0; r < rowHeightList.length; r++) {
const rowHeight = rowHeightList[r] * scale const rowHeight = rowHeightList[r] * scale
const rowItem = document.createElement('div') const rowItem = document.createElement('div')
@ -95,7 +96,7 @@ export class TableTool {
} }
const rowItemAnchor = document.createElement('div') const rowItemAnchor = document.createElement('div')
rowItemAnchor.classList.add(`${EDITOR_PREFIX}-table-tool__anchor`) rowItemAnchor.classList.add(`${EDITOR_PREFIX}-table-tool__anchor`)
rowItemAnchor.onmousedown = (evt) => { rowItemAnchor.onmousedown = evt => {
this._mousedown({ this._mousedown({
evt, evt,
element, element,
@ -116,7 +117,9 @@ export class TableTool {
const colWidthList = colgroup!.map(col => col.width) const colWidthList = colgroup!.map(col => col.width)
const colContainer = document.createElement('div') const colContainer = document.createElement('div')
colContainer.classList.add(`${EDITOR_PREFIX}-table-tool__col`) colContainer.classList.add(`${EDITOR_PREFIX}-table-tool__col`)
colContainer.style.transform = `translateY(-${this.ROW_COL_OFFSET * scale}px)` colContainer.style.transform = `translateY(-${
this.ROW_COL_OFFSET * scale
}px)`
for (let c = 0; c < colWidthList.length; c++) { for (let c = 0; c < colWidthList.length; c++) {
const colWidth = colWidthList[c] * scale const colWidth = colWidthList[c] * scale
const colItem = document.createElement('div') const colItem = document.createElement('div')
@ -126,7 +129,7 @@ export class TableTool {
} }
const colItemAnchor = document.createElement('div') const colItemAnchor = document.createElement('div')
colItemAnchor.classList.add(`${EDITOR_PREFIX}-table-tool__anchor`) colItemAnchor.classList.add(`${EDITOR_PREFIX}-table-tool__anchor`)
colItemAnchor.onmousedown = (evt) => { colItemAnchor.onmousedown = evt => {
this._mousedown({ this._mousedown({
evt, evt,
element, element,
@ -160,9 +163,11 @@ export class TableTool {
rowBorder.classList.add(`${EDITOR_PREFIX}-table-tool__border__row`) rowBorder.classList.add(`${EDITOR_PREFIX}-table-tool__border__row`)
rowBorder.style.width = `${td.width! * scale}px` rowBorder.style.width = `${td.width! * scale}px`
rowBorder.style.height = `${this.BORDER_VALUE}px` rowBorder.style.height = `${this.BORDER_VALUE}px`
rowBorder.style.top = `${(td.y! + td.height!) * scale - this.BORDER_VALUE / 2}px` rowBorder.style.top = `${
(td.y! + td.height!) * scale - this.BORDER_VALUE / 2
}px`
rowBorder.style.left = `${td.x! * scale}px` rowBorder.style.left = `${td.x! * scale}px`
rowBorder.onmousedown = (evt) => { rowBorder.onmousedown = evt => {
this._mousedown({ this._mousedown({
evt, evt,
element, element,
@ -176,8 +181,10 @@ export class TableTool {
colBorder.style.width = `${this.BORDER_VALUE}px` colBorder.style.width = `${this.BORDER_VALUE}px`
colBorder.style.height = `${td.height! * scale}px` colBorder.style.height = `${td.height! * scale}px`
colBorder.style.top = `${td.y! * scale}px` colBorder.style.top = `${td.y! * scale}px`
colBorder.style.left = `${(td.x! + td.width!) * scale - this.BORDER_VALUE / 2}px` colBorder.style.left = `${
colBorder.onmousedown = (evt) => { (td.x! + td.width!) * scale - this.BORDER_VALUE / 2
}px`
colBorder.onmousedown = evt => {
this._mousedown({ this._mousedown({
evt, evt,
element, element,
@ -239,82 +246,95 @@ export class TableTool {
} }
} }
document.addEventListener('mousemove', mousemoveFn) document.addEventListener('mousemove', mousemoveFn)
document.addEventListener('mouseup', () => { document.addEventListener(
let isChangeSize = false 'mouseup',
// 改变尺寸 () => {
if (order === TableOrder.ROW) { let isChangeSize = false
const tr = element.trList![index] // 改变尺寸
// 最大移动高度-向上移动超出最小高度限定,则减少移动量 if (order === TableOrder.ROW) {
const { defaultTrMinHeight } = this.options const tr = element.trList![index]
if (dy < 0 && tr.height + dy < defaultTrMinHeight) { // 最大移动高度-向上移动超出最小高度限定,则减少移动量
dy = defaultTrMinHeight - tr.height const { defaultTrMinHeight } = this.options
} if (dy < 0 && tr.height + dy < defaultTrMinHeight) {
if (dy) { dy = defaultTrMinHeight - tr.height
tr.height += dy
tr.minHeight = tr.height
isChangeSize = true
}
} else {
const { colgroup } = element
if (colgroup && dx) {
// 宽度分配
const innerWidth = this.draw.getInnerWidth()
const curColWidth = colgroup[index].width
// 最小移动距离计算-如果向左移动:使单元格小于最小宽度,则减少移动量
if (dx < 0 && curColWidth + dx < this.MIN_TD_WIDTH) {
dx = this.MIN_TD_WIDTH - curColWidth
} }
// 最大移动距离计算-如果向右移动:使后面一个单元格小于最小宽度,则减少移动量 if (dy) {
const nextColWidth = colgroup[index + 1]?.width tr.height += dy
if (dx > 0 && nextColWidth && nextColWidth - dx < this.MIN_TD_WIDTH) { tr.minHeight = tr.height
dx = nextColWidth - this.MIN_TD_WIDTH isChangeSize = true
} }
const moveColWidth = curColWidth + dx } else {
// 开始移动 const { colgroup } = element
let moveTableWidth = 0 if (colgroup && dx) {
for (let c = 0; c < colgroup.length; c++) { // 宽度分配
const group = colgroup[c] const innerWidth = this.draw.getInnerWidth()
// 下一列减去偏移量 const curColWidth = colgroup[index].width
if (c === index + 1) { // 最小移动距离计算-如果向左移动:使单元格小于最小宽度,则减少移动量
moveTableWidth -= dx if (dx < 0 && curColWidth + dx < this.MIN_TD_WIDTH) {
dx = this.MIN_TD_WIDTH - curColWidth
} }
// 当前列加上偏移量 // 最大移动距离计算-如果向右移动:使后面一个单元格小于最小宽度,则减少移动量
if (c === index) { const nextColWidth = colgroup[index + 1]?.width
moveTableWidth += moveColWidth if (
dx > 0 &&
nextColWidth &&
nextColWidth - dx < this.MIN_TD_WIDTH
) {
dx = nextColWidth - this.MIN_TD_WIDTH
} }
if (c !== index) { const moveColWidth = curColWidth + dx
moveTableWidth += group.width // 开始移动
let moveTableWidth = 0
for (let c = 0; c < colgroup.length; c++) {
const group = colgroup[c]
// 下一列减去偏移量
if (c === index + 1) {
moveTableWidth -= dx
}
// 当前列加上偏移量
if (c === index) {
moveTableWidth += moveColWidth
}
if (c !== index) {
moveTableWidth += group.width
}
} }
} if (moveTableWidth > innerWidth) {
if (moveTableWidth > innerWidth) { const tableWidth = element.width!
const tableWidth = element.width! dx = innerWidth - tableWidth
dx = innerWidth - tableWidth }
} if (dx) {
if (dx) { // 当前列增加,后列减少
// 当前列增加,后列减少 if (colgroup.length - 1 !== index) {
if (colgroup.length - 1 !== index) { colgroup[index + 1].width -= dx / scale
colgroup[index + 1].width -= dx / scale }
colgroup[index].width += dx / scale
isChangeSize = true
} }
colgroup[index].width += dx / scale
isChangeSize = true
} }
} }
if (isChangeSize) {
this.draw.render({ isSetCursor: false })
}
// 还原副作用
anchorLine.remove()
document.removeEventListener('mousemove', mousemoveFn)
document.body.style.cursor = ''
this.canvas.style.cursor = 'text'
},
{
once: true
} }
if (isChangeSize) { )
this.draw.render({ isSetCursor: false })
}
// 还原副作用
anchorLine.remove()
document.removeEventListener('mousemove', mousemoveFn)
document.body.style.cursor = ''
this.canvas.style.cursor = 'text'
}, {
once: true
})
evt.preventDefault() evt.preventDefault()
} }
private _mousemove(evt: MouseEvent, tableOrder: TableOrder, startX: number, startY: number): { dx: number; dy: number } | null { private _mousemove(
evt: MouseEvent,
tableOrder: TableOrder,
startX: number,
startY: number
): { dx: number; dy: number } | null {
if (!this.anchorLine) return null if (!this.anchorLine) return null
const dx = evt.x - this.mousedownX const dx = evt.x - this.mousedownX
const dy = evt.y - this.mousedownY const dy = evt.y - this.mousedownY
@ -326,5 +346,4 @@ export class TableTool {
evt.preventDefault() evt.preventDefault()
return { dx, dy } return { dx, dy }
} }
} }

@ -10,15 +10,23 @@ export abstract class AbstractRichText {
public clearFillInfo() { public clearFillInfo() {
this.fillColor = undefined this.fillColor = undefined
return this.fillRect = { this.fillRect = {
x: 0, x: 0,
y: 0, y: 0,
width: 0, width: 0,
height: 0 height: 0
} }
return this.fillRect
} }
public recordFillInfo(ctx: CanvasRenderingContext2D, x: number, y: number, width: number, height?: number, color?: string) { public recordFillInfo(
ctx: CanvasRenderingContext2D,
x: number,
y: number,
width: number,
height?: number,
color?: string
) {
const isFirstRecord = !this.fillRect.width const isFirstRecord = !this.fillRect.width
if (!isFirstRecord && this.fillColor && this.fillColor !== color) { if (!isFirstRecord && this.fillColor && this.fillColor !== color) {
this.render(ctx) this.render(ctx)
@ -37,5 +45,4 @@ export abstract class AbstractRichText {
} }
public abstract render(ctx: CanvasRenderingContext2D): void public abstract render(ctx: CanvasRenderingContext2D): void
} }

@ -3,7 +3,6 @@ import { IEditorOption } from '../../../interface/Editor'
import { Draw } from '../Draw' import { Draw } from '../Draw'
export class Highlight extends AbstractRichText { export class Highlight extends AbstractRichText {
private options: Required<IEditorOption> private options: Required<IEditorOption>
constructor(draw: Draw) { constructor(draw: Draw) {
@ -22,5 +21,4 @@ export class Highlight extends AbstractRichText {
ctx.restore() ctx.restore()
this.clearFillInfo() this.clearFillInfo()
} }
} }

@ -3,7 +3,6 @@ import { IEditorOption } from '../../../interface/Editor'
import { Draw } from '../Draw' import { Draw } from '../Draw'
export class Strikeout extends AbstractRichText { export class Strikeout extends AbstractRichText {
private options: Required<IEditorOption> private options: Required<IEditorOption>
constructor(draw: Draw) { constructor(draw: Draw) {
@ -25,5 +24,4 @@ export class Strikeout extends AbstractRichText {
ctx.restore() ctx.restore()
this.clearFillInfo() this.clearFillInfo()
} }
} }

@ -3,7 +3,6 @@ import { IEditorOption } from '../../../interface/Editor'
import { Draw } from '../Draw' import { Draw } from '../Draw'
export class Underline extends AbstractRichText { export class Underline extends AbstractRichText {
private options: Required<IEditorOption> private options: Required<IEditorOption>
constructor(draw: Draw) { constructor(draw: Draw) {
@ -25,5 +24,4 @@ export class Underline extends AbstractRichText {
ctx.restore() ctx.restore()
this.clearFillInfo() this.clearFillInfo()
} }
} }

@ -20,14 +20,13 @@ import composition from './handlers/composition'
import drag from './handlers/drag' import drag from './handlers/drag'
export interface ICompositionInfo { export interface ICompositionInfo {
elementList: IElement[]; elementList: IElement[]
startIndex: number; startIndex: number
endIndex: number; endIndex: number
value: string; value: string
} }
export class CanvasEvent { export class CanvasEvent {
public isAllowSelection: boolean public isAllowSelection: boolean
public isComposing: boolean public isComposing: boolean
public compositionInfo: ICompositionInfo | null public compositionInfo: ICompositionInfo | null
@ -70,7 +69,10 @@ export class CanvasEvent {
public register() { public register() {
this.pageContainer.addEventListener('mousedown', this.mousedown.bind(this)) this.pageContainer.addEventListener('mousedown', this.mousedown.bind(this))
this.pageContainer.addEventListener('mouseup', this.mouseup.bind(this)) this.pageContainer.addEventListener('mouseup', this.mouseup.bind(this))
this.pageContainer.addEventListener('mouseleave', this.mouseleave.bind(this)) this.pageContainer.addEventListener(
'mouseleave',
this.mouseleave.bind(this)
)
this.pageContainer.addEventListener('mousemove', this.mousemove.bind(this)) this.pageContainer.addEventListener('mousemove', this.mousemove.bind(this))
this.pageContainer.addEventListener('dblclick', this.dblclick.bind(this)) this.pageContainer.addEventListener('dblclick', this.dblclick.bind(this))
this.pageContainer.addEventListener('dragover', this.dragover.bind(this)) this.pageContainer.addEventListener('dragover', this.dragover.bind(this))
@ -182,5 +184,4 @@ export class CanvasEvent {
public dragover(evt: DragEvent | MouseEvent) { public dragover(evt: DragEvent | MouseEvent) {
drag.dragover(evt, this) drag.dragover(evt, this)
} }
} }

@ -12,7 +12,6 @@ import { RangeManager } from '../range/RangeManager'
import { CanvasEvent } from './CanvasEvent' import { CanvasEvent } from './CanvasEvent'
export class GlobalEvent { export class GlobalEvent {
private draw: Draw private draw: Draw
private canvas: HTMLCanvasElement private canvas: HTMLCanvasElement
private options: Required<IEditorOption> private options: Required<IEditorOption>
@ -38,7 +37,9 @@ export class GlobalEvent {
this.hyperlinkParticle = draw.getHyperlinkParticle() this.hyperlinkParticle = draw.getHyperlinkParticle()
this.dateParticle = draw.getDateParticle() this.dateParticle = draw.getDateParticle()
this.control = draw.getControl() this.control = draw.getControl()
this.dprMediaQueryList = window.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`) this.dprMediaQueryList = window.matchMedia(
`(resolution: ${window.devicePixelRatio}dppx)`
)
} }
public register() { public register() {
@ -62,7 +63,10 @@ export class GlobalEvent {
document.removeEventListener('click', this.recoverEffect) document.removeEventListener('click', this.recoverEffect)
document.removeEventListener('mouseup', this.setCanvasEventAbility) document.removeEventListener('mouseup', this.setCanvasEventAbility)
document.removeEventListener('wheel', this.setPageScale) document.removeEventListener('wheel', this.setPageScale)
document.removeEventListener('visibilitychange', this._handleVisibilityChange) document.removeEventListener(
'visibilitychange',
this._handleVisibilityChange
)
this.dprMediaQueryList.removeEventListener('change', this._handleDprChange) this.dprMediaQueryList.removeEventListener('change', this._handleDprChange)
} }
@ -78,7 +82,8 @@ export class GlobalEvent {
// 编辑器外部dom // 编辑器外部dom
const outerEditorDom = findParent( const outerEditorDom = findParent(
evt.target as Element, evt.target as Element,
(node: Node & Element) => !!node && node.nodeType === 1 && !!node.getAttribute(EDITOR_COMPONENT), (node: Node & Element) =>
!!node && node.nodeType === 1 && !!node.getAttribute(EDITOR_COMPONENT),
true true
) )
if (outerEditorDom) { if (outerEditorDom) {
@ -149,5 +154,4 @@ export class GlobalEvent {
private _handleDprChange = () => { private _handleDprChange = () => {
this.draw.setPageDevicePixel() this.draw.setPageDevicePixel()
} }
} }

@ -30,7 +30,10 @@ function dblclick(host: CanvasEvent, evt: MouseEvent) {
let upStartIndex = index - 1 let upStartIndex = index - 1
while (upStartIndex > 0) { while (upStartIndex > 0) {
const value = elementList[upStartIndex].value const value = elementList[upStartIndex].value
if ((isNumber && NUMBER_LIKE_REG.test(value)) || (!isNumber && LETTER_REG.test(value))) { if (
(isNumber && NUMBER_LIKE_REG.test(value)) ||
(!isNumber && LETTER_REG.test(value))
) {
upCount++ upCount++
upStartIndex-- upStartIndex--
} else { } else {
@ -41,7 +44,10 @@ function dblclick(host: CanvasEvent, evt: MouseEvent) {
let downStartIndex = index + 1 let downStartIndex = index + 1
while (downStartIndex < elementList.length) { while (downStartIndex < elementList.length) {
const value = elementList[downStartIndex].value const value = elementList[downStartIndex].value
if ((isNumber && NUMBER_LIKE_REG.test(value)) || (!isNumber && LETTER_REG.test(value))) { if (
(isNumber && NUMBER_LIKE_REG.test(value)) ||
(!isNumber && LETTER_REG.test(value))
) {
downCount++ downCount++
downStartIndex++ downStartIndex++
} else { } else {

@ -36,7 +36,9 @@ function dragover(evt: DragEvent | MouseEvent, host: CanvasEvent) {
position.setCursorPosition(positionList[curIndex]) position.setCursorPosition(positionList[curIndex])
} }
const cursor = draw.getCursor() const cursor = draw.getCursor()
const { cursor: { dragColor, dragWidth } } = draw.getOptions() const {
cursor: { dragColor, dragWidth }
} = draw.getOptions()
cursor.drawCursor({ cursor.drawCursor({
width: dragWidth, width: dragWidth,
color: dragColor, color: dragColor,

@ -42,12 +42,12 @@ export function input(data: string, host: CanvasEvent) {
} }
const nextElement = elementList[endIndex + 1] const nextElement = elementList[endIndex + 1]
if ( if (
copyElement.type === TEXT copyElement.type === TEXT ||
|| (!copyElement.type && copyElement.value !== ZERO) (!copyElement.type && copyElement.value !== ZERO) ||
|| (copyElement.type === HYPERLINK && nextElement?.type === HYPERLINK) (copyElement.type === HYPERLINK && nextElement?.type === HYPERLINK) ||
|| (copyElement.type === DATE && nextElement?.type === DATE) (copyElement.type === DATE && nextElement?.type === DATE) ||
|| (copyElement.type === SUBSCRIPT && nextElement?.type === SUBSCRIPT) (copyElement.type === SUBSCRIPT && nextElement?.type === SUBSCRIPT) ||
|| (copyElement.type === SUPERSCRIPT && nextElement?.type === SUPERSCRIPT) (copyElement.type === SUPERSCRIPT && nextElement?.type === SUPERSCRIPT)
) { ) {
EDITOR_ELEMENT_COPY_ATTR.forEach(attr => { EDITOR_ELEMENT_COPY_ATTR.forEach(attr => {
const value = copyElement[attr] as never const value = copyElement[attr] as never

@ -47,7 +47,11 @@ export function keydown(evt: KeyboardEvent, host: CanvasEvent) {
}) })
} }
if (!isCollapsed) { if (!isCollapsed) {
draw.spliceElementList(elementList, startIndex + 1, endIndex - startIndex) draw.spliceElementList(
elementList,
startIndex + 1,
endIndex - startIndex
)
} else { } else {
draw.spliceElementList(elementList, index, 1) draw.spliceElementList(elementList, index, 1)
} }
@ -64,7 +68,11 @@ export function keydown(evt: KeyboardEvent, host: CanvasEvent) {
curIndex = control.removeControl(endIndex + 1) curIndex = control.removeControl(endIndex + 1)
} else { } else {
if (!isCollapsed) { if (!isCollapsed) {
draw.spliceElementList(elementList, startIndex + 1, endIndex - startIndex) draw.spliceElementList(
elementList,
startIndex + 1,
endIndex - startIndex
)
} else { } else {
draw.spliceElementList(elementList, index + 1, 1) draw.spliceElementList(elementList, index + 1, 1)
} }
@ -84,7 +92,12 @@ export function keydown(evt: KeyboardEvent, host: CanvasEvent) {
enterText.listWrap = true enterText.listWrap = true
} }
// 标题结尾处回车无需格式化 // 标题结尾处回车无需格式化
if (!(endElement.titleId && endElement.titleId !== elementList[endIndex + 1]?.titleId)) { if (
!(
endElement.titleId &&
endElement.titleId !== elementList[endIndex + 1]?.titleId
)
) {
formatElementContext(elementList, [enterText], startIndex) formatElementContext(elementList, [enterText], startIndex)
} }
let curIndex: number let curIndex: number
@ -94,7 +107,12 @@ export function keydown(evt: KeyboardEvent, host: CanvasEvent) {
if (isCollapsed) { if (isCollapsed) {
draw.spliceElementList(elementList, index + 1, 0, enterText) draw.spliceElementList(elementList, index + 1, 0, enterText)
} else { } else {
draw.spliceElementList(elementList, startIndex + 1, endIndex - startIndex, enterText) draw.spliceElementList(
elementList,
startIndex + 1,
endIndex - startIndex,
enterText
)
} }
curIndex = index + 1 curIndex = index + 1
} }
@ -159,7 +177,12 @@ export function keydown(evt: KeyboardEvent, host: CanvasEvent) {
} }
} }
const maxElementListIndex = elementList.length - 1 const maxElementListIndex = elementList.length - 1
if (anchorStartIndex > maxElementListIndex || anchorEndIndex > maxElementListIndex) return if (
anchorStartIndex > maxElementListIndex ||
anchorEndIndex > maxElementListIndex
) {
return
}
rangeManager.setRange(anchorStartIndex, anchorEndIndex) rangeManager.setRange(anchorStartIndex, anchorEndIndex)
const isCollapsed = anchorStartIndex === anchorEndIndex const isCollapsed = anchorStartIndex === anchorEndIndex
draw.render({ draw.render({
@ -181,21 +204,31 @@ export function keydown(evt: KeyboardEvent, host: CanvasEvent) {
anchorPosition = positionList[startIndex] anchorPosition = positionList[startIndex]
} }
} }
const { rowNo, index, pageNo, coordinate: { leftTop, rightTop } } = anchorPosition const {
rowNo,
index,
pageNo,
coordinate: { leftTop, rightTop }
} = anchorPosition
if ((isUp && rowNo !== 0) || (!isUp && rowNo !== draw.getRowCount())) { if ((isUp && rowNo !== 0) || (!isUp && rowNo !== draw.getRowCount())) {
// 下一个光标点所在行位置集合 // 下一个光标点所在行位置集合
const probablePosition = isUp const probablePosition = isUp
? positionList.slice(0, index) ? positionList
.filter(p => p.rowNo === rowNo - 1 && pageNo === p.pageNo) .slice(0, index)
: positionList.slice(index, positionList.length - 1) .filter(p => p.rowNo === rowNo - 1 && pageNo === p.pageNo)
.filter(p => p.rowNo === rowNo + 1 && pageNo === p.pageNo) : positionList
.slice(index, positionList.length - 1)
.filter(p => p.rowNo === rowNo + 1 && pageNo === p.pageNo)
// 查找与当前位置元素点交叉最多的位置 // 查找与当前位置元素点交叉最多的位置
let maxIndex = 0 let maxIndex = 0
let maxDistance = 0 let maxDistance = 0
for (let p = 0; p < probablePosition.length; p++) { for (let p = 0; p < probablePosition.length; p++) {
const position = probablePosition[p] const position = probablePosition[p]
// 当前光标在前 // 当前光标在前
if (position.coordinate.leftTop[0] >= leftTop[0] && position.coordinate.leftTop[0] <= rightTop[0]) { if (
position.coordinate.leftTop[0] >= leftTop[0] &&
position.coordinate.leftTop[0] <= rightTop[0]
) {
const curDistance = rightTop[0] - position.coordinate.leftTop[0] const curDistance = rightTop[0] - position.coordinate.leftTop[0]
if (curDistance > maxDistance) { if (curDistance > maxDistance) {
maxIndex = position.index maxIndex = position.index
@ -204,7 +237,10 @@ export function keydown(evt: KeyboardEvent, host: CanvasEvent) {
} }
} }
// 当前光标在后 // 当前光标在后
else if (position.coordinate.leftTop[0] <= leftTop[0] && position.coordinate.rightTop[0] >= leftTop[0]) { else if (
position.coordinate.leftTop[0] <= leftTop[0] &&
position.coordinate.rightTop[0] >= leftTop[0]
) {
const curDistance = position.coordinate.rightTop[0] - leftTop[0] const curDistance = position.coordinate.rightTop[0] - leftTop[0]
if (curDistance > maxDistance) { if (curDistance > maxDistance) {
maxIndex = position.index maxIndex = position.index
@ -237,6 +273,7 @@ export function keydown(evt: KeyboardEvent, host: CanvasEvent) {
} }
} }
if (anchorStartIndex > anchorEndIndex) { if (anchorStartIndex > anchorEndIndex) {
// prettier-ignore
[anchorStartIndex, anchorEndIndex] = [anchorEndIndex, anchorStartIndex] [anchorStartIndex, anchorEndIndex] = [anchorEndIndex, anchorStartIndex]
} }
rangeManager.setRange(anchorStartIndex, anchorEndIndex) rangeManager.setRange(anchorStartIndex, anchorEndIndex)
@ -282,10 +319,12 @@ export function keydown(evt: KeyboardEvent, host: CanvasEvent) {
} }
evt.preventDefault() evt.preventDefault()
} else if (evt.key === KeyMap.TAB) { } else if (evt.key === KeyMap.TAB) {
draw.insertElementList([{ draw.insertElementList([
type: ElementType.TAB, {
value: '' type: ElementType.TAB,
}]) value: ''
}
])
evt.preventDefault() evt.preventDefault()
} }
} }

@ -15,7 +15,10 @@ export function mousedown(evt: MouseEvent, host: CanvasEvent) {
if (!host.isAllowDrag) { if (!host.isAllowDrag) {
const range = rangeManager.getRange() const range = rangeManager.getRange()
if (!isReadonly && range.startIndex !== range.endIndex) { if (!isReadonly && range.startIndex !== range.endIndex) {
const isPointInRange = rangeManager.getIsPointInRange(evt.offsetX, evt.offsetY) const isPointInRange = rangeManager.getIsPointInRange(
evt.offsetX,
evt.offsetY
)
if (isPointInRange) { if (isPointInRange) {
host.isAllowDrag = true host.isAllowDrag = true
host.cacheRange = deepClone(range) host.cacheRange = deepClone(range)
@ -37,14 +40,8 @@ export function mousedown(evt: MouseEvent, host: CanvasEvent) {
y: evt.offsetY y: evt.offsetY
}) })
if (!positionResult) return if (!positionResult) return
const { const { index, isDirectHit, isCheckbox, isImage, isTable, tdValueIndex } =
index, positionResult
isDirectHit,
isCheckbox,
isImage,
isTable,
tdValueIndex,
} = positionResult
// 记录选区开始位置 // 记录选区开始位置
host.mouseDownStartPosition = { host.mouseDownStartPosition = {
...positionResult, ...positionResult,
@ -88,13 +85,16 @@ export function mousedown(evt: MouseEvent, host: CanvasEvent) {
const previewer = draw.getPreviewer() const previewer = draw.getPreviewer()
previewer.clearResizer() previewer.clearResizer()
if (isDirectHitImage && !isReadonly) { if (isDirectHitImage && !isReadonly) {
previewer.drawResizer(curElement, positionList[curIndex], previewer.drawResizer(
curElement,
positionList[curIndex],
curElement.type === ElementType.LATEX curElement.type === ElementType.LATEX
? { ? {
mime: 'svg', mime: 'svg',
srcKey: 'laTexSVG' srcKey: 'laTexSVG'
} }
: {}) : {}
)
// 光标事件代理丢失,重新定位 // 光标事件代理丢失,重新定位
draw.getCursor().drawCursor({ draw.getCursor().drawCursor({
isShow: false isShow: false

@ -5,6 +5,8 @@ export function mouseleave(evt: MouseEvent, host: CanvasEvent) {
const draw = host.getDraw() const draw = host.getDraw()
const pageContainer = draw.getPageContainer() const pageContainer = draw.getPageContainer()
const { x, y, width, height } = pageContainer.getBoundingClientRect() const { x, y, width, height } = pageContainer.getBoundingClientRect()
if (evt.x >= x && evt.x <= x + width && evt.y >= y && evt.y <= y + height) return if (evt.x >= x && evt.x <= x + width && evt.y >= y && evt.y <= y + height) {
return
}
host.setIsAllowSelection(false) host.setIsAllowSelection(false)
} }

@ -10,8 +10,15 @@ export function mousemove(evt: MouseEvent, host: CanvasEvent) {
const { startIndex, endIndex } = host.cacheRange! const { startIndex, endIndex } = host.cacheRange!
const positionList = host.cachePositionList! const positionList = host.cachePositionList!
for (let p = startIndex + 1; p <= endIndex; p++) { for (let p = startIndex + 1; p <= endIndex; p++) {
const { coordinate: { leftTop, rightBottom } } = positionList[p] const {
if (x >= leftTop[0] && x <= rightBottom[0] && y >= leftTop[1] && y <= rightBottom[1]) { coordinate: { leftTop, rightBottom }
} = positionList[p]
if (
x >= leftTop[0] &&
x <= rightBottom[0] &&
y >= leftTop[1] &&
y <= rightBottom[1]
) {
return return
} }
} }
@ -33,14 +40,8 @@ export function mousemove(evt: MouseEvent, host: CanvasEvent) {
y: evt.offsetY y: evt.offsetY
}) })
if (!~positionResult.index) return if (!~positionResult.index) return
const { const { index, isTable, tdValueIndex, tdIndex, trIndex, tableId } =
index, positionResult
isTable,
tdValueIndex,
tdIndex,
trIndex,
tableId
} = positionResult
const { const {
index: startIndex, index: startIndex,
isTable: startIsTable, isTable: startIsTable,
@ -50,7 +51,11 @@ export function mousemove(evt: MouseEvent, host: CanvasEvent) {
const endIndex = isTable ? tdValueIndex! : index const endIndex = isTable ? tdValueIndex! : index
// 判断是否是表格跨行/列 // 判断是否是表格跨行/列
const rangeManager = draw.getRange() const rangeManager = draw.getRange()
if (isTable && startIsTable && (tdIndex !== startTdIndex || trIndex !== startTrIndex)) { if (
isTable &&
startIsTable &&
(tdIndex !== startTdIndex || trIndex !== startTrIndex)
) {
rangeManager.setRange( rangeManager.setRange(
endIndex, endIndex,
endIndex, endIndex,
@ -65,6 +70,7 @@ export function mousemove(evt: MouseEvent, host: CanvasEvent) {
// 开始位置 // 开始位置
let start = startIndex let start = startIndex
if (start > end) { if (start > end) {
// prettier-ignore
[start, end] = [end, start] [start, end] = [end, start]
} }
if (start === end) return if (start === end) return

@ -15,7 +15,7 @@ function createDragId(element: IElement): string {
} }
function getElementIndexByDragId(dragId: string, elementList: IElement[]) { function getElementIndexByDragId(dragId: string, elementList: IElement[]) {
return (<IDragElement[]>elementList).findIndex((el) => el.dragId === dragId) return (<IDragElement[]>elementList).findIndex(el => el.dragId === dragId)
} }
export function mouseup(evt: MouseEvent, host: CanvasEvent) { export function mouseup(evt: MouseEvent, host: CanvasEvent) {
@ -30,7 +30,10 @@ export function mouseup(evt: MouseEvent, host: CanvasEvent) {
const cachePositionList = host.cachePositionList! const cachePositionList = host.cachePositionList!
const range = rangeManager.getRange() const range = rangeManager.getRange()
// 是否需要拖拽-位置发生改变 // 是否需要拖拽-位置发生改变
if (range.startIndex >= cacheRange.startIndex && range.endIndex <= cacheRange.endIndex) { if (
range.startIndex >= cacheRange.startIndex &&
range.endIndex <= cacheRange.endIndex
) {
rangeManager.replaceRange({ rangeManager.replaceRange({
...cacheRange ...cacheRange
}) })
@ -42,34 +45,29 @@ export function mouseup(evt: MouseEvent, host: CanvasEvent) {
return return
} }
// 是否是不可拖拽的控件结构元素 // 是否是不可拖拽的控件结构元素
const dragElementList = cacheElementList.slice(cacheRange.startIndex + 1, cacheRange.endIndex + 1) const dragElementList = cacheElementList.slice(
const isContainControl = dragElementList.find(element => element.type === ElementType.CONTROL) cacheRange.startIndex + 1,
cacheRange.endIndex + 1
)
const isContainControl = dragElementList.find(
element => element.type === ElementType.CONTROL
)
if (isContainControl) { if (isContainControl) {
// 仅允许 (最前/后元素不是控件 || 在控件前后 || 文本控件且是值) 拖拽 // 仅允许 (最前/后元素不是控件 || 在控件前后 || 文本控件且是值) 拖拽
const cacheStartElement = cacheElementList[cacheRange.startIndex + 1] const cacheStartElement = cacheElementList[cacheRange.startIndex + 1]
const cacheEndElement = cacheElementList[cacheRange.endIndex] const cacheEndElement = cacheElementList[cacheRange.endIndex]
const isAllowDragControl = const isAllowDragControl =
( ((cacheStartElement.type !== ElementType.CONTROL ||
( cacheStartElement.controlComponent === ControlComponent.PREFIX) &&
cacheStartElement.type !== ElementType.CONTROL || (cacheEndElement.type !== ElementType.CONTROL ||
cacheStartElement.controlComponent === ControlComponent.PREFIX cacheEndElement.controlComponent === ControlComponent.POSTFIX)) ||
) && (cacheStartElement.controlId === cacheEndElement.controlId &&
(
cacheEndElement.type !== ElementType.CONTROL ||
cacheEndElement.controlComponent === ControlComponent.POSTFIX
)
) ||
(
cacheStartElement.controlId === cacheEndElement.controlId &&
cacheStartElement.controlComponent === ControlComponent.PREFIX && cacheStartElement.controlComponent === ControlComponent.PREFIX &&
cacheEndElement.controlComponent === ControlComponent.POSTFIX cacheEndElement.controlComponent === ControlComponent.POSTFIX) ||
) || (cacheStartElement.control?.type === ControlType.TEXT &&
(
cacheStartElement.control?.type === ControlType.TEXT &&
cacheStartElement.controlComponent === ControlComponent.VALUE && cacheStartElement.controlComponent === ControlComponent.VALUE &&
cacheEndElement.control?.type === ControlType.TEXT && cacheEndElement.control?.type === ControlType.TEXT &&
cacheEndElement.controlComponent === ControlComponent.VALUE cacheEndElement.controlComponent === ControlComponent.VALUE)
)
if (!isAllowDragControl) { if (!isAllowDragControl) {
draw.render({ draw.render({
curIndex: range.startIndex, curIndex: range.startIndex,
@ -83,7 +81,11 @@ export function mouseup(evt: MouseEvent, host: CanvasEvent) {
const editorOptions = draw.getOptions() const editorOptions = draw.getOptions()
const elementList = draw.getElementList() const elementList = draw.getElementList()
const replaceElementList = dragElementList.map(el => { const replaceElementList = dragElementList.map(el => {
if (!el.type || el.type === ElementType.TEXT || el.control?.type === ControlType.TEXT) { if (
!el.type ||
el.type === ElementType.TEXT ||
el.control?.type === ControlType.TEXT
) {
const newElement: IElement = { const newElement: IElement = {
value: el.value value: el.value
} }
@ -98,14 +100,16 @@ export function mouseup(evt: MouseEvent, host: CanvasEvent) {
const newElement = deepClone(el) const newElement = deepClone(el)
formatElementList([newElement], { formatElementList([newElement], {
isHandleFirstElement: false, isHandleFirstElement: false,
editorOptions, editorOptions
}) })
return newElement return newElement
} }
}) })
formatElementContext(elementList, replaceElementList, range.startIndex) formatElementContext(elementList, replaceElementList, range.startIndex)
// 缓存拖拽选区开始结束id // 缓存拖拽选区开始结束id
const cacheRangeStartId = createDragId(cacheElementList[cacheRange.startIndex]) const cacheRangeStartId = createDragId(
cacheElementList[cacheRange.startIndex]
)
const cacheRangeEndId = createDragId(cacheElementList[cacheRange.endIndex]) const cacheRangeEndId = createDragId(cacheElementList[cacheRange.endIndex])
// 设置拖拽值 // 设置拖拽值
const replaceLength = replaceElementList.length const replaceLength = replaceElementList.length
@ -113,11 +117,19 @@ export function mouseup(evt: MouseEvent, host: CanvasEvent) {
let rangeEnd = rangeStart + replaceLength let rangeEnd = rangeStart + replaceLength
const control = draw.getControl() const control = draw.getControl()
const activeControl = control.getActiveControl() const activeControl = control.getActiveControl()
if (activeControl && cacheElementList[rangeStart].controlComponent !== ControlComponent.POSTFIX) { if (
activeControl &&
cacheElementList[rangeStart].controlComponent !== ControlComponent.POSTFIX
) {
rangeEnd = activeControl.setValue(replaceElementList) rangeEnd = activeControl.setValue(replaceElementList)
rangeStart = rangeEnd - replaceLength rangeStart = rangeEnd - replaceLength
} else { } else {
draw.spliceElementList(elementList, rangeStart + 1, 0, ...replaceElementList) draw.spliceElementList(
elementList,
rangeStart + 1,
0,
...replaceElementList
)
} }
if (!~rangeEnd) { if (!~rangeEnd) {
draw.render({ draw.render({
@ -129,10 +141,19 @@ export function mouseup(evt: MouseEvent, host: CanvasEvent) {
const rangeStartId = createDragId(elementList[rangeStart]) const rangeStartId = createDragId(elementList[rangeStart])
const rangeEndId = createDragId(elementList[rangeEnd]) const rangeEndId = createDragId(elementList[rangeEnd])
// 删除原有拖拽元素 // 删除原有拖拽元素
const cacheRangeStartIndex = getElementIndexByDragId(cacheRangeStartId, cacheElementList) const cacheRangeStartIndex = getElementIndexByDragId(
const cacheRangeEndIndex = getElementIndexByDragId(cacheRangeEndId, cacheElementList) cacheRangeStartId,
cacheElementList
)
const cacheRangeEndIndex = getElementIndexByDragId(
cacheRangeEndId,
cacheElementList
)
const cacheEndElement = cacheElementList[cacheRangeEndIndex] const cacheEndElement = cacheElementList[cacheRangeEndIndex]
if (cacheEndElement.type === ElementType.CONTROL && cacheEndElement.controlComponent !== ControlComponent.POSTFIX) { if (
cacheEndElement.type === ElementType.CONTROL &&
cacheEndElement.controlComponent !== ControlComponent.POSTFIX
) {
rangeManager.replaceRange({ rangeManager.replaceRange({
...cacheRange, ...cacheRange,
startIndex: cacheRangeStartIndex, startIndex: cacheRangeStartIndex,
@ -140,7 +161,11 @@ export function mouseup(evt: MouseEvent, host: CanvasEvent) {
}) })
control.getActiveControl()?.cut() control.getActiveControl()?.cut()
} else { } else {
draw.spliceElementList(cacheElementList, cacheRangeStartIndex + 1, cacheRangeEndIndex - cacheRangeStartIndex) draw.spliceElementList(
cacheElementList,
cacheRangeStartIndex + 1,
cacheRangeEndIndex - cacheRangeStartIndex
)
} }
// 重设上下文 // 重设上下文
const startElement = elementList[range.startIndex] const startElement = elementList[range.startIndex]

@ -1,7 +1,6 @@
import { Draw } from '../draw/Draw' import { Draw } from '../draw/Draw'
export class HistoryManager { export class HistoryManager {
private undoStack: Array<Function> = [] private undoStack: Array<Function> = []
private redoStack: Array<Function> = [] private redoStack: Array<Function> = []
private maxRecordCount: number private maxRecordCount: number
@ -51,5 +50,4 @@ export class HistoryManager {
this.undoStack = [] this.undoStack = []
this.redoStack = [] this.redoStack = []
} }
} }

@ -5,7 +5,6 @@ import { mergeObject } from '../../utils'
import { DeepPartial } from '../../interface/Common' import { DeepPartial } from '../../interface/Common'
export class I18n { export class I18n {
private langMap: Map<string, ILang> = new Map([ private langMap: Map<string, ILang> = new Map([
['zhCN', zhCN], ['zhCN', zhCN],
['en', en] ['en', en]
@ -15,10 +14,7 @@ export class I18n {
public registerLangMap(locale: string, lang: DeepPartial<ILang>) { public registerLangMap(locale: string, lang: DeepPartial<ILang>) {
const sourceLang = this.langMap.get(locale) const sourceLang = this.langMap.get(locale)
this.langMap.set( this.langMap.set(locale, <ILang>mergeObject(sourceLang || zhCN, lang))
locale,
<ILang>mergeObject(sourceLang || zhCN, lang)
)
} }
public setLocale(locale: string) { public setLocale(locale: string) {
@ -44,5 +40,4 @@ export class I18n {
} }
return value return value
} }
} }

@ -12,7 +12,6 @@ import {
} from '../../interface/Listener' } from '../../interface/Listener'
export class Listener { export class Listener {
public rangeStyleChange: IRangeStyleChange | null public rangeStyleChange: IRangeStyleChange | null
public visiblePageNoListChange: IVisiblePageNoListChange | null public visiblePageNoListChange: IVisiblePageNoListChange | null
public intersectionPageNoChange: IIntersectionPageNoChange | null public intersectionPageNoChange: IIntersectionPageNoChange | null
@ -36,5 +35,4 @@ export class Listener {
this.pageModeChange = null this.pageModeChange = null
this.zoneChange = null this.zoneChange = null
} }
} }

@ -1,5 +1,4 @@
export class ImageObserver { export class ImageObserver {
private promiseList: Promise<unknown>[] private promiseList: Promise<unknown>[]
constructor() { constructor() {
@ -17,5 +16,4 @@ export class ImageObserver {
public allSettled() { public allSettled() {
return Promise.allSettled(this.promiseList) return Promise.allSettled(this.promiseList)
} }
} }

@ -2,16 +2,15 @@ import { debounce } from '../../utils'
import { Draw } from '../draw/Draw' import { Draw } from '../draw/Draw'
export interface IElementVisibleInfo { export interface IElementVisibleInfo {
intersectionHeight: number; intersectionHeight: number
} }
export interface IPageVisibleInfo { export interface IPageVisibleInfo {
intersectionPageNo: number; intersectionPageNo: number
visiblePageNoList: number[]; visiblePageNoList: number[]
} }
export class ScrollObserver { export class ScrollObserver {
private draw: Draw private draw: Draw
constructor(draw: Draw) { constructor(draw: Draw) {
@ -35,8 +34,12 @@ export class ScrollObserver {
public getElementVisibleInfo(element: Element): IElementVisibleInfo { public getElementVisibleInfo(element: Element): IElementVisibleInfo {
const rect = element.getBoundingClientRect() const rect = element.getBoundingClientRect()
const viewHeight = Math.max(document.documentElement.clientHeight, window.innerHeight) const viewHeight = Math.max(
const visibleHeight = Math.min(rect.bottom, viewHeight) - Math.max(rect.top, 0) document.documentElement.clientHeight,
window.innerHeight
)
const visibleHeight =
Math.min(rect.bottom, viewHeight) - Math.max(rect.top, 0)
return { return {
intersectionHeight: visibleHeight > 0 ? visibleHeight : 0 intersectionHeight: visibleHeight > 0 ? visibleHeight : 0
} }
@ -71,5 +74,4 @@ export class ScrollObserver {
this.draw.setIntersectionPageNo(intersectionPageNo) this.draw.setIntersectionPageNo(intersectionPageNo)
this.draw.setVisiblePageNoList(visiblePageNoList) this.draw.setVisiblePageNoList(visiblePageNoList)
}, 150) }, 150)
} }

@ -3,11 +3,15 @@ import { Draw } from '../draw/Draw'
import { RangeManager } from '../range/RangeManager' import { RangeManager } from '../range/RangeManager'
export class SelectionObserver { export class SelectionObserver {
// 每次滚动长度 // 每次滚动长度
private readonly step: number = 5 private readonly step: number = 5
// 触发滚动阀值 // 触发滚动阀值
private readonly thresholdPoints: [top: number, down: number, left: number, right: number] = [70, 40, 10, 20] private readonly thresholdPoints: [
top: number,
down: number,
left: number,
right: number
] = [70, 40, 10, 20]
private rangeManager: RangeManager private rangeManager: RangeManager
private requestAnimationFrameId: number | null private requestAnimationFrameId: number | null
@ -74,7 +78,9 @@ export class SelectionObserver {
} else { } else {
window.scrollTo(x + this.step, y) window.scrollTo(x + this.step, y)
} }
this.requestAnimationFrameId = window.requestAnimationFrame(this._move.bind(this, direction)) this.requestAnimationFrameId = window.requestAnimationFrame(
this._move.bind(this, direction)
)
} }
private _startMove(direction: MoveDirection) { private _startMove(direction: MoveDirection) {
@ -90,5 +96,4 @@ export class SelectionObserver {
this.isMoving = false this.isMoving = false
} }
} }
} }

@ -2,15 +2,16 @@ import Editor from '../..'
import { PluginFunction } from '../../interface/Plugin' import { PluginFunction } from '../../interface/Plugin'
export class Plugin { export class Plugin {
private editor: Editor private editor: Editor
constructor(editor: Editor) { constructor(editor: Editor) {
this.editor = editor this.editor = editor
} }
public use<Options>(pluginFunction: PluginFunction<Options>, options?: Options) { public use<Options>(
pluginFunction: PluginFunction<Options>,
options?: Options
) {
pluginFunction(this.editor, options) pluginFunction(this.editor, options)
} }
} }

@ -1,15 +1,21 @@
import { ElementType, RowFlex, VerticalAlign } from '../..' import { ElementType, RowFlex, VerticalAlign } from '../..'
import { ZERO } from '../../dataset/constant/Common' import { ZERO } from '../../dataset/constant/Common'
import { ControlComponent, ImageDisplay } from '../../dataset/enum/Control' import { ControlComponent, ImageDisplay } from '../../dataset/enum/Control'
import { IComputePageRowPositionPayload, IComputePageRowPositionResult } from '../../interface/Position' import {
IComputePageRowPositionPayload,
IComputePageRowPositionResult
} from '../../interface/Position'
import { IEditorOption } from '../../interface/Editor' import { IEditorOption } from '../../interface/Editor'
import { IElement, IElementPosition } from '../../interface/Element' import { IElement, IElementPosition } from '../../interface/Element'
import { ICurrentPosition, IGetPositionByXYPayload, IPositionContext } from '../../interface/Position' import {
ICurrentPosition,
IGetPositionByXYPayload,
IPositionContext
} from '../../interface/Position'
import { Draw } from '../draw/Draw' import { Draw } from '../draw/Draw'
import { EditorZone } from '../../dataset/enum/Editor' import { EditorZone } from '../../dataset/enum/Editor'
export class Position { export class Position {
private cursorPosition: IElementPosition | null private cursorPosition: IElementPosition | null
private positionContext: IPositionContext private positionContext: IPositionContext
private positionList: IElementPosition[] private positionList: IElementPosition[]
@ -29,9 +35,14 @@ export class Position {
this.options = draw.getOptions() this.options = draw.getOptions()
} }
public getTablePositionList(sourceElementList: IElement[]): IElementPosition[] { public getTablePositionList(
sourceElementList: IElement[]
): IElementPosition[] {
const { index, trIndex, tdIndex } = this.positionContext const { index, trIndex, tdIndex } = this.positionContext
return sourceElementList[index!].trList![trIndex!].tdList[tdIndex!].positionList || [] return (
sourceElementList[index!].trList![trIndex!].tdList[tdIndex!]
.positionList || []
)
} }
public getPositionList(): IElementPosition[] { public getPositionList(): IElementPosition[] {
@ -67,8 +78,19 @@ export class Position {
this.positionList = payload this.positionList = payload
} }
public computePageRowPosition(payload: IComputePageRowPositionPayload): IComputePageRowPositionResult { public computePageRowPosition(
const { positionList, rowList, pageNo, startX, startY, startRowIndex, startIndex, innerWidth } = payload payload: IComputePageRowPositionPayload
): IComputePageRowPositionResult {
const {
positionList,
rowList,
pageNo,
startX,
startY,
startRowIndex,
startIndex,
innerWidth
} = payload
const { scale, tdPadding } = this.options const { scale, tdPadding } = this.options
let x = startX let x = startX
let y = startY let y = startY
@ -92,8 +114,9 @@ export class Position {
const element = curRow.elementList[j] const element = curRow.elementList[j]
const metrics = element.metrics const metrics = element.metrics
const offsetY = const offsetY =
(element.imgDisplay !== ImageDisplay.INLINE && element.type === ElementType.IMAGE) (element.imgDisplay !== ImageDisplay.INLINE &&
|| element.type === ElementType.LATEX element.type === ElementType.IMAGE) ||
element.type === ElementType.LATEX
? curRow.ascent - metrics.height ? curRow.ascent - metrics.height
: curRow.ascent : curRow.ascent
const positionItem: IElementPosition = { const positionItem: IElementPosition = {
@ -137,15 +160,23 @@ export class Position {
}) })
// 垂直对齐方式 // 垂直对齐方式
if ( if (
td.verticalAlign === VerticalAlign.MIDDLE td.verticalAlign === VerticalAlign.MIDDLE ||
|| td.verticalAlign === VerticalAlign.BOTTOM td.verticalAlign === VerticalAlign.BOTTOM
) { ) {
const rowsHeight = rowList.reduce((pre, cur) => pre + cur.height, 0) const rowsHeight = rowList.reduce(
(pre, cur) => pre + cur.height,
0
)
const blankHeight = (td.height! - tdGap) * scale - rowsHeight const blankHeight = (td.height! - tdGap) * scale - rowsHeight
const offsetHeight = td.verticalAlign === VerticalAlign.MIDDLE ? blankHeight / 2 : blankHeight const offsetHeight =
td.verticalAlign === VerticalAlign.MIDDLE
? blankHeight / 2
: blankHeight
if (Math.floor(offsetHeight) > 0) { if (Math.floor(offsetHeight) > 0) {
td.positionList.forEach(tdPosition => { td.positionList.forEach(tdPosition => {
const { coordinate: { leftTop, leftBottom, rightBottom, rightTop } } = tdPosition const {
coordinate: { leftTop, leftBottom, rightBottom, rightTop }
} = tdPosition
leftTop[1] += offsetHeight leftTop[1] += offsetHeight
leftBottom[1] += offsetHeight leftBottom[1] += offsetHeight
rightBottom[1] += offsetHeight rightBottom[1] += offsetHeight
@ -228,10 +259,19 @@ export class Position {
const isMainActive = zoneManager.isMainActive() const isMainActive = zoneManager.isMainActive()
const positionNo = isMainActive ? curPageNo : 0 const positionNo = isMainActive ? curPageNo : 0
for (let j = 0; j < positionList.length; j++) { for (let j = 0; j < positionList.length; j++) {
const { index, pageNo, coordinate: { leftTop, rightTop, leftBottom } } = positionList[j] const {
index,
pageNo,
coordinate: { leftTop, rightTop, leftBottom }
} = positionList[j]
if (positionNo !== pageNo) continue if (positionNo !== pageNo) continue
// 命中元素 // 命中元素
if (leftTop[0] <= x && rightTop[0] >= x && leftTop[1] <= y && leftBottom[1] >= y) { if (
leftTop[0] <= x &&
rightTop[0] >= x &&
leftTop[1] <= y &&
leftBottom[1] >= y
) {
let curPositionIndex = j let curPositionIndex = j
const element = elementList[j] const element = elementList[j]
// 表格被命中 // 表格被命中
@ -254,8 +294,10 @@ export class Position {
const tdValueElement = td.value[tdValueIndex] const tdValueElement = td.value[tdValueIndex]
return { return {
index, index,
isCheckbox: tdValueElement.type === ElementType.CHECKBOX || isCheckbox:
tdValueElement.controlComponent === ControlComponent.CHECKBOX, tdValueElement.type === ElementType.CHECKBOX ||
tdValueElement.controlComponent ===
ControlComponent.CHECKBOX,
isControl: tdValueElement.type === ElementType.CONTROL, isControl: tdValueElement.type === ElementType.CONTROL,
isImage: tablePosition.isImage, isImage: tablePosition.isImage,
isDirectHit: tablePosition.isDirectHit, isDirectHit: tablePosition.isDirectHit,
@ -272,7 +314,10 @@ export class Position {
} }
} }
// 图片区域均为命中 // 图片区域均为命中
if (element.type === ElementType.IMAGE || element.type === ElementType.LATEX) { if (
element.type === ElementType.IMAGE ||
element.type === ElementType.LATEX
) {
return { return {
index: curPositionIndex, index: curPositionIndex,
isDirectHit: true, isDirectHit: true,
@ -298,7 +343,7 @@ export class Position {
} }
return { return {
index: curPositionIndex, index: curPositionIndex,
isControl: element.type === ElementType.CONTROL, isControl: element.type === ElementType.CONTROL
} }
} }
} }
@ -323,18 +368,28 @@ export class Position {
} }
} }
// 判断所属行是否存在元素 // 判断所属行是否存在元素
const lastLetterList = positionList.filter(p => p.isLastLetter && p.pageNo === positionNo) const lastLetterList = positionList.filter(
p => p.isLastLetter && p.pageNo === positionNo
)
for (let j = 0; j < lastLetterList.length; j++) { for (let j = 0; j < lastLetterList.length; j++) {
const { index, pageNo, coordinate: { leftTop, leftBottom } } = lastLetterList[j] const {
index,
pageNo,
coordinate: { leftTop, leftBottom }
} = lastLetterList[j]
if (positionNo !== pageNo) continue if (positionNo !== pageNo) continue
if (y > leftTop[1] && y <= leftBottom[1]) { if (y > leftTop[1] && y <= leftBottom[1]) {
const isHead = x < this.options.margins[3] const isHead = x < this.options.margins[3]
// 是否在头部 // 是否在头部
if (isHead) { if (isHead) {
const headIndex = positionList.findIndex(p => p.pageNo === positionNo && p.rowNo === lastLetterList[j].rowNo) const headIndex = positionList.findIndex(
p => p.pageNo === positionNo && p.rowNo === lastLetterList[j].rowNo
)
// 头部元素为空元素时无需选中 // 头部元素为空元素时无需选中
curPositionIndex = ~headIndex curPositionIndex = ~headIndex
? positionList[headIndex].value === ZERO ? headIndex : headIndex - 1 ? positionList[headIndex].value === ZERO
? headIndex
: headIndex - 1
: index : index
} else { } else {
curPositionIndex = index curPositionIndex = index
@ -350,7 +405,8 @@ export class Position {
// 页脚上部距离页面顶部距离 // 页脚上部距离页面顶部距离
const footer = this.draw.getFooter() const footer = this.draw.getFooter()
const pageHeight = this.draw.getHeight() const pageHeight = this.draw.getHeight()
const footerTopY = pageHeight - (footer.getFooterBottom() + footer.getHeight()) const footerTopY =
pageHeight - (footer.getFooterBottom() + footer.getHeight())
// 判断所属位置是否属于页眉页脚区域 // 判断所属位置是否属于页眉页脚区域
if (isMainActive) { if (isMainActive) {
// 页眉:当前位置小于页眉底部位置 // 页眉:当前位置小于页眉底部位置
@ -378,7 +434,9 @@ export class Position {
} }
// 当前页最后一行 // 当前页最后一行
return { return {
index: lastLetterList[lastLetterList.length - 1]?.index || positionList.length - 1, index:
lastLetterList[lastLetterList.length - 1]?.index ||
positionList.length - 1
} }
} }
return { return {
@ -387,19 +445,15 @@ export class Position {
} }
} }
public adjustPositionContext(payload: IGetPositionByXYPayload): ICurrentPosition | null { public adjustPositionContext(
payload: IGetPositionByXYPayload
): ICurrentPosition | null {
const isReadonly = this.draw.isReadonly() const isReadonly = this.draw.isReadonly()
const positionResult = this.getPositionByXY(payload) const positionResult = this.getPositionByXY(payload)
if (!~positionResult.index) return null if (!~positionResult.index) return null
// 移动控件内光标 // 移动控件内光标
if (positionResult.isControl && !isReadonly) { if (positionResult.isControl && !isReadonly) {
const { const { index, isTable, trIndex, tdIndex, tdValueIndex } = positionResult
index,
isTable,
trIndex,
tdIndex,
tdValueIndex
} = positionResult
const control = this.draw.getControl() const control = this.draw.getControl()
const { newIndex } = control.moveCursor({ const { newIndex } = control.moveCursor({
index, index,
@ -439,5 +493,4 @@ export class Position {
}) })
return positionResult return positionResult
} }
} }

@ -12,7 +12,6 @@ import { Listener } from '../listener/Listener'
import { Position } from '../position/Position' import { Position } from '../position/Position'
export class RangeManager { export class RangeManager {
private draw: Draw private draw: Draw
private options: Required<IEditorOption> private options: Required<IEditorOption>
private range: IRange private range: IRange
@ -55,7 +54,9 @@ export class RangeManager {
public getTextLikeSelection(): IElement[] | null { public getTextLikeSelection(): IElement[] | null {
const selection = this.getSelection() const selection = this.getSelection()
if (!selection) return null if (!selection) return null
return selection.filter(s => !s.type || TEXTLIKE_ELEMENT_TYPE.includes(s.type)) return selection.filter(
s => !s.type || TEXTLIKE_ELEMENT_TYPE.includes(s.type)
)
} }
// 获取光标所选位置行信息 // 获取光标所选位置行信息
@ -100,7 +101,9 @@ export class RangeManager {
if ( if (
positionList[start]?.value === ZERO || positionList[start]?.value === ZERO ||
elementList[start].titleId !== elementList[start - 1]?.titleId elementList[start].titleId !== elementList[start - 1]?.titleId
) break ) {
break
}
start-- start--
} }
// 中间选择 // 中间选择
@ -125,7 +128,9 @@ export class RangeManager {
if ( if (
positionList[end].value === ZERO || positionList[end].value === ZERO ||
elementList[end].titleId !== elementList[end + 1]?.titleId elementList[end].titleId !== elementList[end + 1]?.titleId
) break ) {
break
}
const { pageNo, rowNo } = positionList[end] const { pageNo, rowNo } = positionList[end]
let rowArray = rangeRow.get(pageNo) let rowArray = rangeRow.get(pageNo)
if (!rowArray) { if (!rowArray) {
@ -172,8 +177,15 @@ export class RangeManager {
const { startIndex, endIndex } = this.range const { startIndex, endIndex } = this.range
const positionList = this.position.getPositionList() const positionList = this.position.getPositionList()
for (let p = startIndex + 1; p <= endIndex; p++) { for (let p = startIndex + 1; p <= endIndex; p++) {
const { coordinate: { leftTop, rightBottom } } = positionList[p] const {
if (x >= leftTop[0] && x <= rightBottom[0] && y >= leftTop[1] && y <= rightBottom[1]) { coordinate: { leftTop, rightBottom }
} = positionList[p]
if (
x >= leftTop[0] &&
x <= rightBottom[0] &&
y >= leftTop[1] &&
y <= rightBottom[1]
) {
return true return true
} }
} }
@ -196,7 +208,12 @@ export class RangeManager {
this.range.endTdIndex = endTdIndex this.range.endTdIndex = endTdIndex
this.range.startTrIndex = startTrIndex this.range.startTrIndex = startTrIndex
this.range.endTrIndex = endTrIndex this.range.endTrIndex = endTrIndex
this.range.isCrossRowCol = !!(startTdIndex || endTdIndex || startTrIndex || endTrIndex) this.range.isCrossRowCol = !!(
startTdIndex ||
endTdIndex ||
startTrIndex ||
endTrIndex
)
this.range.zone = this.draw.getZone().getZone() this.range.zone = this.draw.getZone().getZone()
// 激活控件 // 激活控件
const control = this.draw.getControl() const control = this.draw.getControl()
@ -349,8 +366,8 @@ export class RangeManager {
while (index > 0) { while (index > 0) {
const preElement = elementList[index] const preElement = elementList[index]
if ( if (
preElement.controlId !== endElement.controlId preElement.controlId !== endElement.controlId ||
|| preElement.controlComponent === ControlComponent.PREFIX preElement.controlComponent === ControlComponent.PREFIX
) { ) {
range.startIndex = index range.startIndex = index
range.endIndex = index range.endIndex = index
@ -365,12 +382,14 @@ export class RangeManager {
while (index < elementList.length) { while (index < elementList.length) {
const nextElement = elementList[index] const nextElement = elementList[index]
if ( if (
nextElement.controlId !== startElement.controlId nextElement.controlId !== startElement.controlId ||
|| nextElement.controlComponent === ControlComponent.VALUE nextElement.controlComponent === ControlComponent.VALUE
) { ) {
range.startIndex = index - 1 range.startIndex = index - 1
break break
} else if (nextElement.controlComponent === ControlComponent.PLACEHOLDER) { } else if (
nextElement.controlComponent === ControlComponent.PLACEHOLDER
) {
range.startIndex = index - 1 range.startIndex = index - 1
range.endIndex = index - 1 range.endIndex = index - 1
return return
@ -384,12 +403,14 @@ export class RangeManager {
while (index > 0) { while (index > 0) {
const preElement = elementList[index] const preElement = elementList[index]
if ( if (
preElement.controlId !== startElement.controlId preElement.controlId !== startElement.controlId ||
|| preElement.controlComponent === ControlComponent.VALUE preElement.controlComponent === ControlComponent.VALUE
) { ) {
range.startIndex = index range.startIndex = index
break break
} else if (preElement.controlComponent === ControlComponent.PLACEHOLDER) { } else if (
preElement.controlComponent === ControlComponent.PLACEHOLDER
) {
range.startIndex = index range.startIndex = index
range.endIndex = index range.endIndex = index
return return
@ -400,7 +421,13 @@ export class RangeManager {
} }
} }
public render(ctx: CanvasRenderingContext2D, x: number, y: number, width: number, height: number) { public render(
ctx: CanvasRenderingContext2D,
x: number,
y: number,
width: number,
height: number
) {
ctx.save() ctx.save()
ctx.globalAlpha = this.options.rangeAlpha ctx.globalAlpha = this.options.rangeAlpha
ctx.fillStyle = this.options.rangeColor ctx.fillStyle = this.options.rangeColor
@ -411,9 +438,9 @@ export class RangeManager {
public toString(): string { public toString(): string {
const selection = this.getSelection() const selection = this.getSelection()
if (!selection) return '' if (!selection) return ''
return selection.map(s => s.value) return selection
.map(s => s.value)
.join('') .join('')
.replace(new RegExp(ZERO, 'g'), '') .replace(new RegExp(ZERO, 'g'), '')
} }
} }

@ -7,13 +7,12 @@ import { ILang } from '../../interface/i18n/I18n'
import { DeepPartial } from '../../interface/Common' import { DeepPartial } from '../../interface/Common'
interface IRegisterPayload { interface IRegisterPayload {
contextMenu: ContextMenu; contextMenu: ContextMenu
shortcut: Shortcut; shortcut: Shortcut
i18n: I18n; i18n: I18n
} }
export class Register { export class Register {
public contextMenuList: (payload: IRegisterContextMenu[]) => void public contextMenuList: (payload: IRegisterContextMenu[]) => void
public shortcutList: (payload: IRegisterShortcut[]) => void public shortcutList: (payload: IRegisterShortcut[]) => void
public langMap: (locale: string, lang: DeepPartial<ILang>) => void public langMap: (locale: string, lang: DeepPartial<ILang>) => void
@ -24,5 +23,4 @@ export class Register {
this.shortcutList = shortcut.registerShortcutList.bind(shortcut) this.shortcutList = shortcut.registerShortcutList.bind(shortcut)
this.langMap = i18n.registerLangMap.bind(i18n) this.langMap = i18n.registerLangMap.bind(i18n)
} }
} }

@ -7,7 +7,6 @@ import { titleKeys } from './keys/titleKeys'
import { listKeys } from './keys/listKeys' import { listKeys } from './keys/listKeys'
export class Shortcut { export class Shortcut {
private command: Command private command: Command
private globalShortcutList: IRegisterShortcut[] private globalShortcutList: IRegisterShortcut[]
private agentShortcutList: IRegisterShortcut[] private agentShortcutList: IRegisterShortcut[]
@ -17,11 +16,7 @@ export class Shortcut {
this.globalShortcutList = [] this.globalShortcutList = []
this.agentShortcutList = [] this.agentShortcutList = []
// 内部快捷键 // 内部快捷键
this._addShortcutList([ this._addShortcutList([...richtextKeys, ...titleKeys, ...listKeys])
...richtextKeys,
...titleKeys,
...listKeys
])
// 全局快捷键 // 全局快捷键
this._addEvent() this._addEvent()
// 编辑器快捷键 // 编辑器快捷键
@ -66,11 +61,10 @@ export class Shortcut {
for (let s = 0; s < shortCutList.length; s++) { for (let s = 0; s < shortCutList.length; s++) {
const shortCut = shortCutList[s] const shortCut = shortCutList[s]
if ( if (
( (shortCut.mod
shortCut.mod ? isMod(evt) === !!shortCut.mod
? isMod(evt) === !!shortCut.mod : evt.ctrlKey === !!shortCut.ctrl &&
: evt.ctrlKey === !!shortCut.ctrl && evt.metaKey === !!shortCut.meta evt.metaKey === !!shortCut.meta) &&
) &&
evt.shiftKey === !!shortCut.shift && evt.shiftKey === !!shortCut.shift &&
evt.altKey === !!shortCut.alt && evt.altKey === !!shortCut.alt &&
evt.key === shortCut.key evt.key === shortCut.key
@ -81,5 +75,4 @@ export class Shortcut {
} }
} }
} }
} }

@ -10,42 +10,48 @@ export const titleKeys: IRegisterShortcut[] = [
callback: (command: Command) => { callback: (command: Command) => {
command.executeTitle(null) command.executeTitle(null)
} }
}, { },
{
key: KeyMap.ONE, key: KeyMap.ONE,
alt: true, alt: true,
ctrl: true, ctrl: true,
callback: (command: Command) => { callback: (command: Command) => {
command.executeTitle(TitleLevel.FIRST) command.executeTitle(TitleLevel.FIRST)
} }
}, { },
{
key: KeyMap.TWO, key: KeyMap.TWO,
alt: true, alt: true,
ctrl: true, ctrl: true,
callback: (command: Command) => { callback: (command: Command) => {
command.executeTitle(TitleLevel.SECOND) command.executeTitle(TitleLevel.SECOND)
} }
}, { },
{
key: KeyMap.THREE, key: KeyMap.THREE,
alt: true, alt: true,
ctrl: true, ctrl: true,
callback: (command: Command) => { callback: (command: Command) => {
command.executeTitle(TitleLevel.THIRD) command.executeTitle(TitleLevel.THIRD)
} }
}, { },
{
key: KeyMap.FOUR, key: KeyMap.FOUR,
alt: true, alt: true,
ctrl: true, ctrl: true,
callback: (command: Command) => { callback: (command: Command) => {
command.executeTitle(TitleLevel.FOURTH) command.executeTitle(TitleLevel.FOURTH)
} }
}, { },
{
key: KeyMap.FIVE, key: KeyMap.FIVE,
alt: true, alt: true,
ctrl: true, ctrl: true,
callback: (command: Command) => { callback: (command: Command) => {
command.executeTitle(TitleLevel.FIFTH) command.executeTitle(TitleLevel.FIFTH)
} }
}, { },
{
key: KeyMap.SIX, key: KeyMap.SIX,
alt: true, alt: true,
ctrl: true, ctrl: true,

@ -3,9 +3,7 @@ import WordCountWorker from './works/wordCount?worker&inline'
import CatalogWorker from './works/catalog?worker&inline' import CatalogWorker from './works/catalog?worker&inline'
import { ICatalog } from '../../interface/Catalog' import { ICatalog } from '../../interface/Catalog'
export class WorkerManager { export class WorkerManager {
private draw: Draw private draw: Draw
private wordCountWorker: Worker private wordCountWorker: Worker
private catalogWorker: Worker private catalogWorker: Worker
@ -18,11 +16,11 @@ export class WorkerManager {
public getWordCount(): Promise<number> { public getWordCount(): Promise<number> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.wordCountWorker.onmessage = (evt) => { this.wordCountWorker.onmessage = evt => {
resolve(evt.data) resolve(evt.data)
} }
this.wordCountWorker.onerror = (evt) => { this.wordCountWorker.onerror = evt => {
reject(evt) reject(evt)
} }
@ -33,11 +31,11 @@ export class WorkerManager {
public getCatalog(): Promise<ICatalog | null> { public getCatalog(): Promise<ICatalog | null> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.catalogWorker.onmessage = (evt) => { this.catalogWorker.onmessage = evt => {
resolve(evt.data) resolve(evt.data)
} }
this.catalogWorker.onerror = (evt) => { this.catalogWorker.onerror = evt => {
reject(evt) reject(evt)
} }
@ -45,5 +43,4 @@ export class WorkerManager {
this.catalogWorker.postMessage(elementList) this.catalogWorker.postMessage(elementList)
}) })
} }
} }

@ -50,7 +50,8 @@ function getCatalog(elementList: IElement[]): ICatalog | null {
valueList.push(titleE) valueList.push(titleE)
t++ t++
} }
titleElement.value = valueList.map(s => s.value) titleElement.value = valueList
.map(s => s.value)
.join('') .join('')
.replace(new RegExp(ZERO, 'g'), '') .replace(new RegExp(ZERO, 'g'), '')
titleElementList.push(titleElement) titleElementList.push(titleElement)
@ -60,7 +61,8 @@ function getCatalog(elementList: IElement[]): ICatalog | null {
if (!titleElementList.length) return null if (!titleElementList.length) return null
// 查找到比最新元素大的标题时终止 // 查找到比最新元素大的标题时终止
const recursiveInsert = (title: IElement, catalogItem: ICatalogItem) => { const recursiveInsert = (title: IElement, catalogItem: ICatalogItem) => {
const subCatalogItem = catalogItem.subCatalog[catalogItem.subCatalog.length - 1] const subCatalogItem =
catalogItem.subCatalog[catalogItem.subCatalog.length - 1]
const catalogItemLevel = titleOrderNumberMapping[subCatalogItem?.level] const catalogItemLevel = titleOrderNumberMapping[subCatalogItem?.level]
const titleLevel = titleOrderNumberMapping[title.level!] const titleLevel = titleOrderNumberMapping[title.level!]
if (subCatalogItem && titleLevel > catalogItemLevel) { if (subCatalogItem && titleLevel > catalogItemLevel) {
@ -97,7 +99,7 @@ function getCatalog(elementList: IElement[]): ICatalog | null {
return catalog return catalog
} }
onmessage = (evt) => { onmessage = evt => {
const elementList = <IElement[]>evt.data const elementList = <IElement[]>evt.data
const catalog = getCatalog(elementList) const catalog = getCatalog(elementList)
postMessage(catalog) postMessage(catalog)

@ -115,7 +115,7 @@ function groupText(text: string): string[] {
return characterList return characterList
} }
onmessage = (evt) => { onmessage = evt => {
const elementList = <IElement[]>evt.data const elementList = <IElement[]>evt.data
// 提取文本 // 提取文本
const originText = pickText(elementList) const originText = pickText(elementList)

@ -6,7 +6,6 @@ import { Draw } from '../draw/Draw'
import { I18n } from '../i18n/I18n' import { I18n } from '../i18n/I18n'
export class Zone { export class Zone {
private readonly INDICATOR_PADDING = 2 private readonly INDICATOR_PADDING = 2
private readonly INDICATOR_TITLE_TRANSLATE = [20, 5] private readonly INDICATOR_TITLE_TRANSLATE = [20, 5]
@ -99,9 +98,13 @@ export class Zone {
: startY - this.INDICATOR_PADDING : startY - this.INDICATOR_PADDING
// 标题 // 标题
const indicatorTitle = document.createElement('div') const indicatorTitle = document.createElement('div')
indicatorTitle.innerText = this.i18n.t(`frame.${isHeaderActive ? 'header' : 'footer'}`) indicatorTitle.innerText = this.i18n.t(
`frame.${isHeaderActive ? 'header' : 'footer'}`
)
indicatorTitle.style.top = `${indicatorBottomY}px` indicatorTitle.style.top = `${indicatorBottomY}px`
indicatorTitle.style.transform = `translate(${offsetX * scale}px, ${offsetY * scale}px) scale(${scale})` indicatorTitle.style.transform = `translate(${offsetX * scale}px, ${
offsetY * scale
}px) scale(${scale})`
this.indicatorContainer.append(indicatorTitle) this.indicatorContainer.append(indicatorTitle)
// 上边线 // 上边线
@ -141,5 +144,4 @@ export class Zone {
this.indicatorContainer?.remove() this.indicatorContainer?.remove()
this.indicatorContainer = null this.indicatorContainer = null
} }
} }

@ -4,7 +4,22 @@ export const ZERO = '\u200B'
export const WRAP = '\n' export const WRAP = '\n'
export const HORIZON_TAB = '\t' export const HORIZON_TAB = '\t'
export const NBSP = '\u0020' export const NBSP = '\u0020'
export const PUNCTUATION_LIST = ['·', '、', ':', '', ',', '', '.', '。', ';', '', '?', '', '!', ''] export const PUNCTUATION_LIST = [
'·',
'、',
':',
'',
',',
'',
'.',
'。',
';',
'',
'?',
'',
'!',
''
]
export const maxHeightRadioMapping: Record<MaxHeightRatio, number> = { export const maxHeightRadioMapping: Record<MaxHeightRatio, number> = {
[MaxHeightRatio.HALF]: 1 / 2, [MaxHeightRatio.HALF]: 1 / 2,

@ -65,10 +65,7 @@ export const TABLE_CONTEXT_ATTR: Array<keyof IElement> = [
'tableId' 'tableId'
] ]
export const TITLE_CONTEXT_ATTR: Array<keyof IElement> = [ export const TITLE_CONTEXT_ATTR: Array<keyof IElement> = ['level', 'titleId']
'level',
'titleId',
]
export const LIST_CONTEXT_ATTR: Array<keyof IElement> = [ export const LIST_CONTEXT_ATTR: Array<keyof IElement> = [
'listId', 'listId',
@ -98,12 +95,7 @@ export const INLINE_ELEMENT_TYPE: ElementType[] = [
ElementType.TABLE ElementType.TABLE
] ]
export const INLINE_NODE_NAME: string[] = [ export const INLINE_NODE_NAME: string[] = ['HR', 'TABLE', 'UL', 'OL']
'HR',
'TABLE',
'UL',
'OL'
]
export const VIRTUAL_ELEMENT_TYPE: ElementType[] = [ export const VIRTUAL_ELEMENT_TYPE: ElementType[] = [
ElementType.TITLE, ElementType.TITLE,

@ -29,10 +29,10 @@ export const titleOrderNumberMapping: Record<TitleLevel, number> = {
} }
export const titleNodeNameMapping: Record<string, TitleLevel> = { export const titleNodeNameMapping: Record<string, TitleLevel> = {
'H1': TitleLevel.FIRST, H1: TitleLevel.FIRST,
'H2': TitleLevel.SECOND, H2: TitleLevel.SECOND,
'H3': TitleLevel.THIRD, H3: TitleLevel.THIRD,
'H4': TitleLevel.FOURTH, H4: TitleLevel.FOURTH,
'H5': TitleLevel.FIFTH, H5: TitleLevel.FIFTH,
'H6': TitleLevel.SIXTH H6: TitleLevel.SIXTH
} }

@ -6,7 +6,7 @@ export enum ListType {
export enum UlStyle { export enum UlStyle {
DISC = 'disc', // 实心圆点 DISC = 'disc', // 实心圆点
CIRCLE = 'circle', // 空心圆点 CIRCLE = 'circle', // 空心圆点
SQUARE = 'square', // 实心方块 SQUARE = 'square' // 实心方块
} }
export enum OlStyle { export enum OlStyle {

@ -10,8 +10,18 @@ import { ElementType } from './dataset/enum/Element'
import { formatElementList } from './utils/element' import { formatElementList } from './utils/element'
import { Register } from './core/register/Register' import { Register } from './core/register/Register'
import { ContextMenu } from './core/contextmenu/ContextMenu' import { ContextMenu } from './core/contextmenu/ContextMenu'
import { IContextMenuContext, IRegisterContextMenu } from './interface/contextmenu/ContextMenu' import {
import { EditorComponent, EditorZone, EditorMode, PageMode, PaperDirection, WordBreak } from './dataset/enum/Editor' IContextMenuContext,
IRegisterContextMenu
} from './interface/contextmenu/ContextMenu'
import {
EditorComponent,
EditorZone,
EditorMode,
PageMode,
PaperDirection,
WordBreak
} from './dataset/enum/Editor'
import { EDITOR_COMPONENT } from './dataset/constant/Editor' import { EDITOR_COMPONENT } from './dataset/constant/Editor'
import { IHeader } from './interface/Header' import { IHeader } from './interface/Header'
import { IWatermark } from './interface/Watermark' import { IWatermark } from './interface/Watermark'
@ -49,14 +59,17 @@ import { Plugin } from './core/plugin/Plugin'
import { UsePlugin } from './interface/Plugin' import { UsePlugin } from './interface/Plugin'
export default class Editor { export default class Editor {
public command: Command public command: Command
public listener: Listener public listener: Listener
public register: Register public register: Register
public destroy: () => void public destroy: () => void
public use: UsePlugin public use: UsePlugin
constructor(container: HTMLDivElement, data: IEditorData | IElement[], options: IEditorOption = {}) { constructor(
container: HTMLDivElement,
data: IEditorData | IElement[],
options: IEditorOption = {}
) {
const headerOptions: Required<IHeader> = { const headerOptions: Required<IHeader> = {
...defaultHeaderOption, ...defaultHeaderOption,
...options.header ...options.header
@ -153,12 +166,16 @@ export default class Editor {
mainElementList = data.main mainElementList = data.main
footerElementList = data.footer || [] footerElementList = data.footer || []
} }
[headerElementList, mainElementList, footerElementList] const pageComponentData = [
.forEach(elementList => { headerElementList,
formatElementList(elementList, { mainElementList,
editorOptions footerElementList
}) ]
pageComponentData.forEach(elementList => {
formatElementList(elementList, {
editorOptions
}) })
})
// 监听 // 监听
this.listener = new Listener() this.listener = new Listener()
// 启动 // 启动
@ -194,7 +211,6 @@ export default class Editor {
const plugin = new Plugin(this) const plugin = new Plugin(this)
this.use = plugin.use.bind(plugin) this.use = plugin.use.bind(plugin)
} }
} }
// 对外对象 // 对外对象

@ -1,15 +1,15 @@
import { BlockType } from '../dataset/enum/Block' import { BlockType } from '../dataset/enum/Block'
export interface IIFrameBlock { export interface IIFrameBlock {
src: string; src: string
} }
export interface IVideoBlock { export interface IVideoBlock {
src: string; src: string
} }
export interface IBlock { export interface IBlock {
type: BlockType; type: BlockType
iframeBlock?: IIFrameBlock; iframeBlock?: IIFrameBlock
videoBlock?: IVideoBlock; videoBlock?: IVideoBlock
} }

@ -1,10 +1,10 @@
import { TitleLevel } from '../dataset/enum/Title' import { TitleLevel } from '../dataset/enum/Title'
export interface ICatalogItem { export interface ICatalogItem {
id: string; id: string
name: string; name: string
level: TitleLevel; level: TitleLevel
subCatalog: ICatalogItem[]; subCatalog: ICatalogItem[]
} }
export type ICatalog = ICatalogItem[] export type ICatalog = ICatalogItem[]

@ -1,14 +1,14 @@
export interface ICheckbox { export interface ICheckbox {
value: boolean | null; value: boolean | null
code?: string; code?: string
disabled?: boolean; disabled?: boolean
} }
export interface ICheckboxOption { export interface ICheckboxOption {
width?: number; width?: number
height?: number; height?: number
gap?: number; gap?: number
lineWidth?: number; lineWidth?: number
fillStyle?: string; fillStyle?: string
fontStyle?: string; fontStyle?: string
} }

@ -1,4 +1,11 @@
export type Primitive = string | number | boolean | bigint | symbol | undefined | null export type Primitive =
| string
| number
| boolean
| bigint
| symbol
| undefined
| null
export type Builtin = Primitive | Function | Date | Error | RegExp export type Builtin = Primitive | Function | Date | Error | RegExp
@ -25,5 +32,5 @@ export type DeepRequired<T> = T extends Error
: Required<T> : Required<T>
export type DeepPartial<T> = { export type DeepPartial<T> = {
[P in keyof T]?: DeepPartial<T[P]>; [P in keyof T]?: DeepPartial<T[P]>
} }

@ -3,64 +3,63 @@ import { ICheckbox } from './Checkbox'
import { IElement } from './Element' import { IElement } from './Element'
export interface IValueSet { export interface IValueSet {
value: string; value: string
code: string; code: string
} }
export interface IControlSelect { export interface IControlSelect {
code: string | null; code: string | null
valueSets: IValueSet[]; valueSets: IValueSet[]
} }
export interface IControlCheckbox { export interface IControlCheckbox {
code: string | null; code: string | null
min?: number; min?: number
max?: number; max?: number
valueSets: IValueSet[]; valueSets: IValueSet[]
checkbox?: ICheckbox; checkbox?: ICheckbox
} }
export interface IControlBasic { export interface IControlBasic {
type: ControlType; type: ControlType
value: IElement[] | null; value: IElement[] | null
placeholder?: string; placeholder?: string
conceptId?: string; conceptId?: string
prefix?: string; prefix?: string
postfix?: string; postfix?: string
} }
export type IControl = IControlBasic export type IControl = IControlBasic &
& Partial<IControlSelect> Partial<IControlSelect> &
& Partial<IControlCheckbox> Partial<IControlCheckbox>
export interface IControlOption { export interface IControlOption {
placeholderColor?: string; placeholderColor?: string
bracketColor?: string; bracketColor?: string
prefix?: string; prefix?: string
postfix?: string; postfix?: string
} }
export interface IControlInitOption { export interface IControlInitOption {
index: number; index: number
isTable?: boolean; isTable?: boolean
trIndex?: number; trIndex?: number
tdIndex?: number; tdIndex?: number
tdValueIndex?: number; tdValueIndex?: number
} }
export interface IControlInitResult { export interface IControlInitResult {
newIndex: number; newIndex: number
} }
export interface IControlInstance { export interface IControlInstance {
getElement(): IElement; getElement(): IElement
getValue(): IElement[]; getValue(): IElement[]
setValue(data: IElement[]): number; setValue(data: IElement[]): number
keydown(evt: KeyboardEvent): number; keydown(evt: KeyboardEvent): number
cut(): number; cut(): number
} }

@ -1,6 +1,6 @@
export interface ICursorOption { export interface ICursorOption {
width?: number; width?: number
color?: string; color?: string
dragWidth?: number; dragWidth?: number
dragColor?: string; dragColor?: string
} }

@ -3,44 +3,44 @@ import { IElement, IElementPosition } from './Element'
import { IRow } from './Row' import { IRow } from './Row'
export interface IDrawOption { export interface IDrawOption {
curIndex?: number; curIndex?: number
isSetCursor?: boolean; isSetCursor?: boolean
isSubmitHistory?: boolean; isSubmitHistory?: boolean
isCompute?: boolean; isCompute?: boolean
isLazy?: boolean; isLazy?: boolean
} }
export interface IDrawImagePayload { export interface IDrawImagePayload {
width: number; width: number
height: number; height: number
value: string; value: string
} }
export interface IDrawRowPayload { export interface IDrawRowPayload {
elementList: IElement[]; elementList: IElement[]
positionList: IElementPosition[]; positionList: IElementPosition[]
rowList: IRow[]; rowList: IRow[]
pageNo: number; pageNo: number
startIndex: number; startIndex: number
innerWidth: number; innerWidth: number
zone?: EditorZone; zone?: EditorZone
} }
export interface IDrawPagePayload { export interface IDrawPagePayload {
elementList: IElement[]; elementList: IElement[]
positionList: IElementPosition[]; positionList: IElementPosition[]
rowList: IRow[]; rowList: IRow[]
pageNo: number; pageNo: number
} }
export interface IPainterOption { export interface IPainterOption {
isDblclick: boolean; isDblclick: boolean
} }
export interface IGetValueOption { export interface IGetValueOption {
pageNo?: number; pageNo?: number
} }
export interface IAppendElementListOption { export interface IAppendElementListOption {
isPrepend?: boolean; isPrepend?: boolean
} }

@ -1,5 +1,10 @@
import { IElement } from '..' import { IElement } from '..'
import { EditorMode, PageMode, PaperDirection, WordBreak } from '../dataset/enum/Editor' import {
EditorMode,
PageMode,
PaperDirection,
WordBreak
} from '../dataset/enum/Editor'
import { ICheckboxOption } from './Checkbox' import { ICheckboxOption } from './Checkbox'
import { IControlOption } from './Control' import { IControlOption } from './Control'
import { ICursorOption } from './Cursor' import { ICursorOption } from './Cursor'
@ -12,64 +17,64 @@ import { ITitleOption } from './Title'
import { IWatermark } from './Watermark' import { IWatermark } from './Watermark'
export interface IEditorData { export interface IEditorData {
header?: IElement[]; header?: IElement[]
main: IElement[]; main: IElement[]
footer?: IElement[]; footer?: IElement[]
} }
export interface IEditorOption { export interface IEditorOption {
mode?: EditorMode; mode?: EditorMode
defaultType?: string; defaultType?: string
defaultFont?: string; defaultFont?: string
defaultSize?: number; defaultSize?: number
minSize?: number; minSize?: number
maxSize?: number; maxSize?: number
defaultBasicRowMarginHeight?: number; defaultBasicRowMarginHeight?: number
defaultRowMargin?: number; defaultRowMargin?: number
defaultTabWidth?: number; defaultTabWidth?: number
width?: number; width?: number
height?: number; height?: number
scale?: number; scale?: number
pageGap?: number; pageGap?: number
underlineColor?: string; underlineColor?: string
strikeoutColor?: string; strikeoutColor?: string
rangeColor?: string; rangeColor?: string
rangeAlpha?: number; rangeAlpha?: number
rangeMinWidth?: number; rangeMinWidth?: number
searchMatchColor?: string; searchMatchColor?: string
searchNavigateMatchColor?: string; searchNavigateMatchColor?: string
searchMatchAlpha?: number; searchMatchAlpha?: number
highlightAlpha?: number; highlightAlpha?: number
resizerColor?: string; resizerColor?: string
resizerSize?: number; resizerSize?: number
marginIndicatorSize?: number; marginIndicatorSize?: number
marginIndicatorColor?: string, marginIndicatorColor?: string
margins?: IMargin, margins?: IMargin
pageMode?: PageMode; pageMode?: PageMode
tdPadding?: number; tdPadding?: number
defaultTrMinHeight?: number; defaultTrMinHeight?: number
defaultColMinWidth?: number; defaultColMinWidth?: number
defaultHyperlinkColor?: string; defaultHyperlinkColor?: string
paperDirection?: PaperDirection; paperDirection?: PaperDirection
inactiveAlpha?: number; inactiveAlpha?: number
historyMaxRecordCount?: number; historyMaxRecordCount?: number
wordBreak?: WordBreak; wordBreak?: WordBreak
header?: IHeader; header?: IHeader
footer?: IFooter; footer?: IFooter
pageNumber?: IPageNumber; pageNumber?: IPageNumber
watermark?: IWatermark; watermark?: IWatermark
control?: IControlOption; control?: IControlOption
checkbox?: ICheckboxOption; checkbox?: ICheckboxOption
cursor?: ICursorOption; cursor?: ICursorOption
title?: ITitleOption; title?: ITitleOption
placeholder?: IPlaceholder; placeholder?: IPlaceholder
} }
export interface IEditorResult { export interface IEditorResult {
version: string; version: string
width: number; width: number
height: number; height: number
margins: IMargin; margins: IMargin
watermark?: IWatermark; watermark?: IWatermark
data: IEditorData; data: IEditorData
} }

@ -11,86 +11,86 @@ import { IColgroup } from './table/Colgroup'
import { ITr } from './table/Tr' import { ITr } from './table/Tr'
export interface IElementBasic { export interface IElementBasic {
id?: string; id?: string
type?: ElementType; type?: ElementType
value: string; value: string
} }
export interface IElementStyle { export interface IElementStyle {
font?: string; font?: string
size?: number; size?: number
width?: number; width?: number
height?: number; height?: number
bold?: boolean; bold?: boolean
color?: string; color?: string
highlight?: string; highlight?: string
italic?: boolean; italic?: boolean
underline?: boolean; underline?: boolean
strikeout?: boolean; strikeout?: boolean
rowFlex?: RowFlex; rowFlex?: RowFlex
rowMargin?: number; rowMargin?: number
letterSpacing?: number; letterSpacing?: number
} }
export interface ITitleElement { export interface ITitleElement {
valueList?: IElement[]; valueList?: IElement[]
level?: TitleLevel; level?: TitleLevel
titleId?: string; titleId?: string
} }
export interface IListElement { export interface IListElement {
valueList?: IElement[]; valueList?: IElement[]
listType?: ListType; listType?: ListType
listStyle?: ListStyle; listStyle?: ListStyle
listId?: string; listId?: string
listWrap?: boolean; listWrap?: boolean
} }
export interface ITableAttr { export interface ITableAttr {
colgroup?: IColgroup[]; colgroup?: IColgroup[]
trList?: ITr[]; trList?: ITr[]
borderType?: TableBorder; borderType?: TableBorder
} }
export interface ITableElement { export interface ITableElement {
tdId?: string; tdId?: string
trId?: string; trId?: string
tableId?: string; tableId?: string
} }
export type ITable = ITableAttr & ITableElement export type ITable = ITableAttr & ITableElement
export interface IHyperlinkElement { export interface IHyperlinkElement {
valueList?: IElement[]; valueList?: IElement[]
url?: string; url?: string
hyperlinkId?: string; hyperlinkId?: string
} }
export interface ISuperscriptSubscript { export interface ISuperscriptSubscript {
actualSize?: number; actualSize?: number
} }
export interface ISeparator { export interface ISeparator {
dashArray?: number[]; dashArray?: number[]
} }
export interface IControlElement { export interface IControlElement {
control?: IControl; control?: IControl
controlId?: string; controlId?: string
controlComponent?: ControlComponent; controlComponent?: ControlComponent
} }
export interface ICheckboxElement { export interface ICheckboxElement {
checkbox?: ICheckbox; checkbox?: ICheckbox
} }
export interface ILaTexElement { export interface ILaTexElement {
laTexSVG?: string; laTexSVG?: string
} }
export interface IDateElement { export interface IDateElement {
dateFormat?: string; dateFormat?: string
dateId?: string; dateId?: string
} }
export interface IImageElement { export interface IImageElement {
@ -98,52 +98,52 @@ export interface IImageElement {
} }
export interface IBlockElement { export interface IBlockElement {
block?: IBlock; block?: IBlock
} }
export type IElement = IElementBasic export type IElement = IElementBasic &
& IElementStyle IElementStyle &
& ITable ITable &
& IHyperlinkElement IHyperlinkElement &
& ISuperscriptSubscript ISuperscriptSubscript &
& ISeparator ISeparator &
& IControlElement IControlElement &
& ICheckboxElement ICheckboxElement &
& ILaTexElement ILaTexElement &
& IDateElement IDateElement &
& IImageElement IImageElement &
& IBlockElement IBlockElement &
& ITitleElement ITitleElement &
& IListElement IListElement
export interface IElementMetrics { export interface IElementMetrics {
width: number; width: number
height: number; height: number
boundingBoxAscent: number; boundingBoxAscent: number
boundingBoxDescent: number; boundingBoxDescent: number
} }
export interface IElementPosition { export interface IElementPosition {
pageNo: number; pageNo: number
index: number; index: number
value: string, value: string
rowIndex: number; rowIndex: number
rowNo: number; rowNo: number
ascent: number; ascent: number
lineHeight: number; lineHeight: number
metrics: IElementMetrics; metrics: IElementMetrics
isLastLetter: boolean, isLastLetter: boolean
coordinate: { coordinate: {
leftTop: number[]; leftTop: number[]
leftBottom: number[]; leftBottom: number[]
rightTop: number[]; rightTop: number[]
rightBottom: number[]; rightBottom: number[]
} }
} }
export interface IElementFillRect { export interface IElementFillRect {
x: number; x: number
y: number; y: number
width: number; width: number
height: number; height: number
} }

@ -1,7 +1,7 @@
import { MaxHeightRatio } from '../dataset/enum/Common' import { MaxHeightRatio } from '../dataset/enum/Common'
export interface IFooter { export interface IFooter {
bottom?: number; bottom?: number
maxHeightRadio?: MaxHeightRatio; maxHeightRadio?: MaxHeightRatio
disabled?: boolean; disabled?: boolean
} }

@ -1,7 +1,7 @@
import { MaxHeightRatio } from '../dataset/enum/Common' import { MaxHeightRatio } from '../dataset/enum/Common'
export interface IHeader { export interface IHeader {
top?: number; top?: number
maxHeightRadio?: MaxHeightRatio; maxHeightRadio?: MaxHeightRatio
disabled?: boolean; disabled?: boolean
} }

@ -1,27 +1,34 @@
import { EditorZone, ElementType, ListStyle, ListType, PageMode, TitleLevel } from '..' import {
EditorZone,
ElementType,
ListStyle,
ListType,
PageMode,
TitleLevel
} from '..'
import { RowFlex } from '../dataset/enum/Row' import { RowFlex } from '../dataset/enum/Row'
import { IControl } from './Control' import { IControl } from './Control'
import { IEditorResult } from './Editor' import { IEditorResult } from './Editor'
export interface IRangeStyle { export interface IRangeStyle {
type: ElementType | null; type: ElementType | null
undo: boolean; undo: boolean
redo: boolean; redo: boolean
painter: boolean; painter: boolean
font: string; font: string
size: number; size: number
bold: boolean; bold: boolean
italic: boolean; italic: boolean
underline: boolean; underline: boolean
strikeout: boolean; strikeout: boolean
color: string | null; color: string | null
highlight: string | null; highlight: string | null
rowFlex: RowFlex | null; rowFlex: RowFlex | null
rowMargin: number; rowMargin: number
dashArray: number[]; dashArray: number[]
level: TitleLevel | null; level: TitleLevel | null
listType: ListType | null; listType: ListType | null
listStyle: ListStyle | null; listStyle: ListStyle | null
} }
export type IRangeStyleChange = (payload: IRangeStyle) => void export type IRangeStyleChange = (payload: IRangeStyle) => void

@ -2,14 +2,14 @@ import { NumberType } from '../dataset/enum/Common'
import { RowFlex } from '../dataset/enum/Row' import { RowFlex } from '../dataset/enum/Row'
export interface IPageNumber { export interface IPageNumber {
bottom?: number; bottom?: number
size?: number; size?: number
font?: string; font?: string
color?: string; color?: string
rowFlex?: RowFlex; rowFlex?: RowFlex
format?: string; format?: string
numberType?: NumberType; numberType?: NumberType
disabled?: boolean; disabled?: boolean
startPageNo?: number; startPageNo?: number
fromPageNo?: number; fromPageNo?: number
} }

@ -1,7 +1,7 @@
export interface IPlaceholder { export interface IPlaceholder {
data: string; data: string
color?: string; color?: string
opacity?: number; opacity?: number
size?: number; size?: number
font?: string; font?: string
} }

@ -1,5 +1,8 @@
import Editor from '..' import Editor from '..'
export type PluginFunction<Options> = (editor: Editor, options?: Options) => any; export type PluginFunction<Options> = (editor: Editor, options?: Options) => any
export type UsePlugin = <Options>(pluginFunction: PluginFunction<Options>, options?: Options) => void; export type UsePlugin = <Options>(
pluginFunction: PluginFunction<Options>,
options?: Options
) => void

@ -5,56 +5,56 @@ import { IRow } from './Row'
import { ITd } from './table/Td' import { ITd } from './table/Td'
export interface ICurrentPosition { export interface ICurrentPosition {
index: number; index: number
isCheckbox?: boolean; isCheckbox?: boolean
isControl?: boolean; isControl?: boolean
isImage?: boolean; isImage?: boolean
isTable?: boolean; isTable?: boolean
isDirectHit?: boolean; isDirectHit?: boolean
trIndex?: number; trIndex?: number
tdIndex?: number; tdIndex?: number
tdValueIndex?: number; tdValueIndex?: number
tdId?: string; tdId?: string
trId?: string; trId?: string
tableId?: string; tableId?: string
zone?: EditorZone; zone?: EditorZone
} }
export interface IGetPositionByXYPayload { export interface IGetPositionByXYPayload {
x: number; x: number
y: number; y: number
isTable?: boolean; isTable?: boolean
td?: ITd; td?: ITd
tablePosition?: IElementPosition; tablePosition?: IElementPosition
elementList?: IElement[]; elementList?: IElement[]
positionList?: IElementPosition[]; positionList?: IElementPosition[]
} }
export interface IPositionContext { export interface IPositionContext {
isTable: boolean; isTable: boolean
isCheckbox?: boolean; isCheckbox?: boolean
isControl?: boolean; isControl?: boolean
index?: number; index?: number
trIndex?: number; trIndex?: number
tdIndex?: number; tdIndex?: number
tdId?: string; tdId?: string
trId?: string; trId?: string
tableId?: string; tableId?: string
} }
export interface IComputePageRowPositionPayload { export interface IComputePageRowPositionPayload {
positionList: IElementPosition[]; positionList: IElementPosition[]
rowList: IRow[]; rowList: IRow[]
pageNo: number; pageNo: number
startRowIndex: number; startRowIndex: number
startIndex: number; startIndex: number
startX: number; startX: number
startY: number; startY: number
innerWidth: number; innerWidth: number
} }
export interface IComputePageRowPositionResult { export interface IComputePageRowPositionResult {
x: number; x: number
y: number; y: number
index: number; index: number
} }

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save