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",
"deletable",
"dppx",
"esbenp",
"inputarea",
"linebreak",
"noopener",
"Parens",
"prismjs",
"resizer",
"richtext",
@ -34,5 +36,14 @@
"typescript.tsdk": "node_modules/typescript/lib",
"editor.codeActionsOnSave": {
"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'
export interface IDialogData {
type: string;
label?: string;
name: string;
value?: string;
options?: { label: string; value: string; }[];
placeholder?: string;
width?: number;
height?: number;
required?: boolean;
type: string
label?: string
name: string
value?: string
options?: { label: string; value: string }[]
placeholder?: string
width?: number
height?: number
required?: boolean
}
export interface IDialogConfirm {
name: string;
value: string;
name: string
value: string
}
export interface IDialogOptions {
onClose?: () => void;
onCancel?: () => void;
onConfirm?: (payload: IDialogConfirm[]) => void;
title: string;
data: IDialogData[];
onClose?: () => void
onCancel?: () => void
onConfirm?: (payload: IDialogConfirm[]) => void
title: string
data: IDialogData[]
}
export class Dialog {
private options: IDialogOptions
private mask: HTMLDivElement | null
private container: HTMLDivElement | null
private inputList: (HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement)[]
private inputList: (
| HTMLInputElement
| HTMLTextAreaElement
| HTMLSelectElement
)[]
constructor(options: IDialogOptions) {
this.options = options
@ -90,7 +93,10 @@ export class Dialog {
}
}
// 选项输入框
let optionInput: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement
let optionInput:
| HTMLInputElement
| HTMLTextAreaElement
| HTMLSelectElement
if (option.type === 'select') {
optionInput = document.createElement('select')
option.options?.forEach(item => {
@ -162,5 +168,4 @@ export class Dialog {
this.mask?.remove()
this.container?.remove()
}
}
}

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

@ -2,7 +2,6 @@ import { CommandAdapt } from './CommandAdapt'
// 通过CommandAdapt中转避免直接暴露编辑器上下文
export class Command {
public executeMode: CommandAdapt['mode']
public executeCut: CommandAdapt['cut']
public executeCopy: CommandAdapt['copy']
@ -130,7 +129,8 @@ export class Command {
this.executeCancelMergeTableCell = adapt.cancelMergeTableCell.bind(adapt)
this.executeTableTdVerticalAlign = adapt.tableTdVerticalAlign.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.executeHyperlink = adapt.hyperlink.bind(adapt)
this.executeDeleteHyperlink = adapt.deleteHyperlink.bind(adapt)
@ -174,5 +174,4 @@ export class Command {
this.getPaperMargin = adapt.getPaperMargin.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 { defaultWatermarkOption } from '../../dataset/constant/Watermark'
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 { ElementStyleKey } from '../../dataset/enum/ElementStyle'
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 { VerticalAlign } from '../../dataset/enum/VerticalAlign'
import { ICatalog } from '../../interface/Catalog'
import { IAppendElementListOption, IDrawImagePayload, IGetValueOption, IPainterOption } from '../../interface/Draw'
import { IEditorData, IEditorOption, IEditorResult } from '../../interface/Editor'
import {
IAppendElementListOption,
IDrawImagePayload,
IGetValueOption,
IPainterOption
} from '../../interface/Draw'
import {
IEditorData,
IEditorOption,
IEditorResult
} from '../../interface/Editor'
import { IElement, IElementStyle } from '../../interface/Element'
import { IMargin } from '../../interface/Margin'
import { RangeContext } from '../../interface/Range'
@ -22,7 +36,12 @@ import { ITd } from '../../interface/table/Td'
import { ITr } from '../../interface/table/Tr'
import { IWatermark } from '../../interface/Watermark'
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 { Control } from '../draw/control/Control'
import { Draw } from '../draw/Draw'
@ -36,7 +55,6 @@ import { RangeManager } from '../range/RangeManager'
import { WorkerManager } from '../worker/WorkerManager'
export class CommandAdapt {
private draw: Draw
private range: RangeManager
private position: Position
@ -103,11 +121,19 @@ export class CommandAdapt {
const { startIndex, endIndex } = this.range.getRange()
const isCollapsed = startIndex === endIndex
// 首字符禁止删除
if (isCollapsed && elementList[startIndex].value === ZERO && startIndex === 0) {
if (
isCollapsed &&
elementList[startIndex].value === ZERO &&
startIndex === 0
) {
return
}
if (!isCollapsed) {
this.draw.spliceElementList(elementList, startIndex + 1, endIndex - startIndex)
this.draw.spliceElementList(
elementList,
startIndex + 1,
endIndex - startIndex
)
} else {
this.draw.spliceElementList(elementList, startIndex, 1)
}
@ -198,7 +224,12 @@ export class CommandAdapt {
if (!selection || !selection.length) return
let isExistUpdate = false
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
isExistUpdate = true
})
@ -310,7 +341,9 @@ export class CommandAdapt {
if (activeControl) return
const selection = this.range.getSelection()
if (!selection) return
const superscriptIndex = selection.findIndex(s => s.type === ElementType.SUPERSCRIPT)
const superscriptIndex = selection.findIndex(
s => s.type === ElementType.SUPERSCRIPT
)
selection.forEach(el => {
// 取消上标
if (~superscriptIndex) {
@ -320,7 +353,11 @@ export class CommandAdapt {
}
} 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
}
}
@ -335,7 +372,9 @@ export class CommandAdapt {
if (activeControl) return
const selection = this.range.getSelection()
if (!selection) return
const subscriptIndex = selection.findIndex(s => s.type === ElementType.SUBSCRIPT)
const subscriptIndex = selection.findIndex(
s => s.type === ElementType.SUBSCRIPT
)
selection.forEach(el => {
// 取消下标
if (~subscriptIndex) {
@ -345,7 +384,11 @@ export class CommandAdapt {
}
} 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
}
}
@ -388,9 +431,10 @@ export class CommandAdapt {
if (!~startIndex && !~endIndex) return
const elementList = this.draw.getElementList()
// 需要改变的元素列表
const changeElementList = startIndex === endIndex
? this.range.getRangeElementList()
: elementList.slice(startIndex + 1, endIndex + 1)
const changeElementList =
startIndex === endIndex
? this.range.getRangeElementList()
: elementList.slice(startIndex + 1, endIndex + 1)
if (!changeElementList || !changeElementList.length) return
// 设置值
const titleId = getUUID()
@ -428,7 +472,9 @@ export class CommandAdapt {
const changeElementList = this.range.getRangeElementList()
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()
changeElementList.forEach(el => {
@ -601,13 +647,15 @@ export class CommandAdapt {
id: newTdId,
rowspan: 1,
colspan: curTd.colspan,
value: [{
value: ZERO,
size: 16,
tableId,
trId: newTrId,
tdId: newTdId
}]
value: [
{
value: ZERO,
size: 16,
tableId,
trId: newTrId,
tdId: newTdId
}
]
})
}
curTrList.splice(trIndex!, 0, newTr)
@ -637,9 +685,8 @@ export class CommandAdapt {
const element = originalElementList[index!]
const curTrList = element.trList!
const curTr = curTrList[trIndex!]
const anchorTr = curTrList.length - 1 === trIndex
? curTr
: curTrList[trIndex! + 1]
const anchorTr =
curTrList.length - 1 === trIndex ? curTr : curTrList[trIndex! + 1]
// 之前/当前行跨行的增加跨行数
if (anchorTr.tdList.length < element.colgroup!.length) {
const curTrNo = anchorTr.tdList[0].rowIndex!
@ -667,13 +714,15 @@ export class CommandAdapt {
id: newTdId,
rowspan: 1,
colspan: curTd.colspan,
value: [{
value: ZERO,
size: 16,
tableId,
trId: newTrId,
tdId: newTdId
}]
value: [
{
value: ZERO,
size: 16,
tableId,
trId: newTrId,
tdId: newTdId
}
]
})
}
curTrList.splice(trIndex! + 1, 0, newTr)
@ -711,13 +760,15 @@ export class CommandAdapt {
id: tdId,
rowspan: 1,
colspan: 1,
value: [{
value: ZERO,
size: 16,
tableId,
trId: tr.id,
tdId
}]
value: [
{
value: ZERO,
size: 16,
tableId,
trId: tr.id,
tdId
}
]
})
}
// 重新计算宽度
@ -768,13 +819,15 @@ export class CommandAdapt {
id: tdId,
rowspan: 1,
colspan: 1,
value: [{
value: ZERO,
size: 16,
tableId,
trId: tr.id,
tdId
}]
value: [
{
value: ZERO,
size: 16,
tableId,
trId: tr.id,
tdId
}
]
})
}
// 重新计算宽度
@ -834,13 +887,15 @@ export class CommandAdapt {
id: tdId,
rowspan: 1,
colspan: 1,
value: [{
value: ZERO,
size: 16,
tableId: element.id,
trId: tr.id,
tdId
}]
value: [
{
value: ZERO,
size: 16,
tableId: element.id,
trId: tr.id,
tdId
}
]
})
start += 1
}
@ -885,7 +940,10 @@ export class CommandAdapt {
if (td.colspan > 1) {
const tdColIndex = td.colIndex!
// 交叉减去一列
if (tdColIndex <= curColIndex && tdColIndex + td.colspan - 1 >= curColIndex) {
if (
tdColIndex <= curColIndex &&
tdColIndex + td.colspan - 1 >= curColIndex
) {
td.colspan -= 1
}
}
@ -940,7 +998,13 @@ export class CommandAdapt {
if (isReadonly) return
const positionContext = this.position.getPositionContext()
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
const { index } = positionContext
const originalElementList = this.draw.getOriginalElementList()
@ -966,8 +1030,10 @@ export class CommandAdapt {
const tdColIndex = td.colIndex!
const tdRowIndex = td.rowIndex!
if (
tdColIndex >= startColIndex && tdColIndex <= endColIndex
&& tdRowIndex >= startRowIndex && tdRowIndex <= endRowIndex
tdColIndex >= startColIndex &&
tdColIndex <= endColIndex &&
tdRowIndex >= startRowIndex &&
tdRowIndex <= endRowIndex
) {
tdList.push(td)
}
@ -994,7 +1060,12 @@ export class CommandAdapt {
const tdEndX = tdStartX + td.width!
const tdEndY = tdStartY + td.height!
// 存在不符合项
if (startX > tdStartX || startY > tdStartY || endX < tdEndX || endY < tdEndY) {
if (
startX > tdStartX ||
startY > tdStartY ||
endX < tdEndX ||
endY < tdEndY
) {
return
}
}
@ -1064,13 +1135,15 @@ export class CommandAdapt {
id: tdId,
rowspan: 1,
colspan: 1,
value: [{
value: ZERO,
size: 16,
tableId: element.id,
trId: curTr.id,
tdId
}]
value: [
{
value: ZERO,
size: 16,
tableId: element.id,
trId: curTr.id,
tdId
}
]
})
}
curTd.colspan = 1
@ -1085,13 +1158,15 @@ export class CommandAdapt {
id: tdId,
rowspan: 1,
colspan: 1,
value: [{
value: ZERO,
size: 16,
tableId: element.id,
trId: tr.id,
tdId
}]
value: [
{
value: ZERO,
size: 16,
tableId: element.id,
trId: tr.id,
tdId
}
]
})
}
}
@ -1241,7 +1316,11 @@ export class CommandAdapt {
const elementList = this.draw.getElementList()
const [leftIndex, rightIndex] = hyperRange
// 删除元素
this.draw.spliceElementList(elementList, leftIndex, rightIndex - leftIndex + 1)
this.draw.spliceElementList(
elementList,
leftIndex,
rightIndex - leftIndex + 1
)
this.draw.getHyperlinkParticle().clearHyperlinkPopup()
// 重置画布
const newIndex = leftIndex - 1
@ -1306,7 +1385,12 @@ export class CommandAdapt {
// 光标存在分割线,则判断为修改线段逻辑
const endElement = elementList[endIndex + 1]
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
endElement.dashArray = payload
} else {
@ -1334,10 +1418,12 @@ export class CommandAdapt {
if (isReadonly) return
const activeControl = this.control.getActiveControl()
if (activeControl) return
this.insertElementList([{
type: ElementType.PAGE_BREAK,
value: WRAP
}])
this.insertElementList([
{
type: ElementType.PAGE_BREAK,
value: WRAP
}
])
}
public addWatermark(payload: IWatermark) {
@ -1458,7 +1544,8 @@ export class CommandAdapt {
}
curTdId = tdId!
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 tableElement = tableElementList[curIndex]
@ -1483,8 +1570,8 @@ export class CommandAdapt {
const curIndex = match.index + pageDiffCount
const element = elementList[curIndex]
if (
element.type === ElementType.CONTROL
&& element.controlComponent !== ControlComponent.VALUE
element.type === ElementType.CONTROL &&
element.controlComponent !== ControlComponent.VALUE
) {
continue
}
@ -1517,7 +1604,8 @@ export class CommandAdapt {
const firstIndex = firstMatch.index + (payload.length - 1)
if (firstMatch.type === EditorContext.TABLE) {
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({
isTable: true,
index: tableIndex,
@ -1607,7 +1695,9 @@ export class CommandAdapt {
const isCollapsed = startIndex === endIndex
// 元素信息
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 positionList = this.position.getPositionList()
@ -1676,7 +1766,10 @@ export class CommandAdapt {
this.draw.insertElementList(payload)
}
public appendElementList(elementList: IElement[], options?: IAppendElementListOption) {
public appendElementList(
elementList: IElement[],
options?: IAppendElementListOption
) {
if (!elementList.length) return
const isReadonly = this.draw.isReadonly()
if (isReadonly) return
@ -1716,7 +1809,10 @@ export class CommandAdapt {
let newIndex = -1
for (let e = 0; e < elementList.length; 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
break
}
@ -1757,5 +1853,4 @@ export class CommandAdapt {
})
}
}
}
}

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

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

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

@ -1,12 +1,18 @@
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'
export const hyperlinkMenus: IRegisterContextMenu[] = [
{
i18nPath: 'contextmenu.hyperlink.delete',
when: (payload) => {
return !payload.isReadonly && payload.startElement?.type === ElementType.HYPERLINK
when: payload => {
return (
!payload.isReadonly &&
payload.startElement?.type === ElementType.HYPERLINK
)
},
callback: (command: Command) => {
command.executeDeleteHyperlink()
@ -14,8 +20,11 @@ export const hyperlinkMenus: IRegisterContextMenu[] = [
},
{
i18nPath: 'contextmenu.hyperlink.cancel',
when: (payload) => {
return !payload.isReadonly && payload.startElement?.type === ElementType.HYPERLINK
when: payload => {
return (
!payload.isReadonly &&
payload.startElement?.type === ElementType.HYPERLINK
)
},
callback: (command: Command) => {
command.executeCancelHyperlink()
@ -23,8 +32,11 @@ export const hyperlinkMenus: IRegisterContextMenu[] = [
},
{
i18nPath: 'contextmenu.hyperlink.edit',
when: (payload) => {
return !payload.isReadonly && payload.startElement?.type === ElementType.HYPERLINK
when: payload => {
return (
!payload.isReadonly &&
payload.startElement?.type === ElementType.HYPERLINK
)
},
callback: (command: Command, context: IContextMenuContext) => {
const url = window.prompt('编辑链接', context.startElement?.url)
@ -33,4 +45,4 @@ export const hyperlinkMenus: IRegisterContextMenu[] = [
}
}
}
]
]

@ -1,14 +1,21 @@
import { ImageDisplay } from '../../../dataset/enum/Control'
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'
export const imageMenus: IRegisterContextMenu[] = [
{
i18nPath: 'contextmenu.image.change',
icon: 'image-change',
when: (payload) => {
return !payload.isReadonly && !payload.editorHasSelection && payload.startElement?.type === ElementType.IMAGE
when: payload => {
return (
!payload.isReadonly &&
!payload.editorHasSelection &&
payload.startElement?.type === ElementType.IMAGE
)
},
callback: (command: Command) => {
// 创建代理元素
@ -31,8 +38,11 @@ export const imageMenus: IRegisterContextMenu[] = [
{
i18nPath: 'contextmenu.image.saveAs',
icon: 'image',
when: (payload) => {
return !payload.editorHasSelection && payload.startElement?.type === ElementType.IMAGE
when: payload => {
return (
!payload.editorHasSelection &&
payload.startElement?.type === ElementType.IMAGE
)
},
callback: (command: Command) => {
command.executeSaveAsImageElement()
@ -40,25 +50,34 @@ export const imageMenus: IRegisterContextMenu[] = [
},
{
i18nPath: 'contextmenu.image.textWrap',
when: (payload) => {
return !payload.isReadonly && !payload.editorHasSelection && payload.startElement?.type === ElementType.IMAGE
when: payload => {
return (
!payload.isReadonly &&
!payload.editorHasSelection &&
payload.startElement?.type === ElementType.IMAGE
)
},
childMenus: [
{
i18nPath: 'contextmenu.image.textWrapType.embed',
when: () => true,
callback: (command: Command, context: IContextMenuContext) => {
command.executeChangeImageDisplay(context.startElement!, ImageDisplay.BLOCK)
command.executeChangeImageDisplay(
context.startElement!,
ImageDisplay.BLOCK
)
}
},
{
i18nPath: 'contextmenu.image.textWrapType.upDown',
when: () => true,
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',
icon: 'border-all',
when: (payload) => {
when: payload => {
return !payload.isReadonly && payload.isInTable
},
childMenus: [
@ -43,7 +43,7 @@ export const tableMenus: IRegisterContextMenu[] = [
{
i18nPath: 'contextmenu.table.verticalAlign',
icon: 'vertical-align',
when: (payload) => {
when: payload => {
return !payload.isReadonly && payload.isInTable
},
childMenus: [
@ -76,7 +76,7 @@ export const tableMenus: IRegisterContextMenu[] = [
{
i18nPath: 'contextmenu.table.insertRowCol',
icon: 'insert-row-col',
when: (payload) => {
when: payload => {
return !payload.isReadonly && payload.isInTable
},
childMenus: [
@ -117,7 +117,7 @@ export const tableMenus: IRegisterContextMenu[] = [
{
i18nPath: 'contextmenu.table.deleteRowCol',
icon: 'delete-row-col',
when: (payload) => {
when: payload => {
return !payload.isReadonly && payload.isInTable
},
childMenus: [
@ -150,7 +150,7 @@ export const tableMenus: IRegisterContextMenu[] = [
{
i18nPath: 'contextmenu.table.mergeCell',
icon: 'merge-cell',
when: (payload) => {
when: payload => {
return !payload.isReadonly && payload.isCrossRowCol
},
callback: (command: Command) => {
@ -160,11 +160,11 @@ export const tableMenus: IRegisterContextMenu[] = [
{
i18nPath: 'contextmenu.table.mergeCancelCell',
icon: 'merge-cancel-cell',
when: (payload) => {
when: payload => {
return !payload.isReadonly && payload.isInTable
},
callback: (command: Command) => {
command.executeCancelMergeTableCell()
}
}
]
]

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

@ -10,7 +10,6 @@ import { Draw } from '../draw/Draw'
import { CanvasEvent } from '../event/CanvasEvent'
export class CursorAgent {
private draw: Draw
private container: HTMLDivElement
private agentCursorDom: HTMLTextAreaElement
@ -31,8 +30,14 @@ export class CursorAgent {
agentCursorDom.onkeydown = (evt: KeyboardEvent) => this._keyDown(evt)
agentCursorDom.oninput = debounce(this._input.bind(this), 0)
agentCursorDom.onpaste = (evt: ClipboardEvent) => this._paste(evt)
agentCursorDom.addEventListener('compositionstart', this._compositionstart.bind(this))
agentCursorDom.addEventListener('compositionend', this._compositionend.bind(this))
agentCursorDom.addEventListener(
'compositionstart',
this._compositionstart.bind(this)
)
agentCursorDom.addEventListener(
'compositionend',
this._compositionend.bind(this)
)
}
public getAgentCursorDom(): HTMLTextAreaElement {
@ -84,13 +89,17 @@ export class CursorAgent {
let start = 0
while (start < pasteElementList.length) {
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!)) {
pasteElementList.splice(start, 1)
if (pasteElement.valueList) {
for (let v = 0; v < pasteElement.valueList.length; 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)
start++
}
@ -145,5 +154,4 @@ export class CursorAgent {
private _compositionend(evt: CompositionEvent) {
this.canvasEvent.compositionend(evt)
}
}
}

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -6,7 +6,6 @@ import { IRowElement } from '../../../interface/Row'
import { Draw } from '../Draw'
export class HyperlinkParticle {
private draw: Draw
private options: Required<IEditorOption>
private container: HTMLDivElement
@ -17,7 +16,8 @@ export class HyperlinkParticle {
this.draw = draw
this.options = draw.getOptions()
this.container = draw.getContainer()
const { hyperlinkPopupContainer, hyperlinkDom } = this._createHyperlinkPopupDom()
const { hyperlinkPopupContainer, hyperlinkDom } =
this._createHyperlinkPopupDom()
this.hyperlinkDom = hyperlinkDom
this.hyperlinkPopupContainer = hyperlinkPopupContainer
}
@ -34,7 +34,12 @@ export class HyperlinkParticle {
}
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 pageGap = this.draw.getPageGap()
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.font = element.style
if (!element.color) {
@ -73,5 +83,4 @@ export class HyperlinkParticle {
ctx.fillText(element.value, x, y)
ctx.restore()
}
}
}

@ -4,7 +4,6 @@ import { convertStringToBase64 } from '../../../utils'
import { Draw } from '../Draw'
export class ImageParticle {
private draw: Draw
protected options: Required<IEditorOption>
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}">
<rect width="${width}" height="${height}" fill="url(#mosaic)" />
<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" transform="translate(${tileSize}, ${tileSize})" />
</pattern>
</defs>
</svg>`
const fallbackImage = new Image()
fallbackImage.src = `data:image/svg+xml;base64,${convertStringToBase64(svg)}`
fallbackImage.src = `data:image/svg+xml;base64,${convertStringToBase64(
svg
)}`
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 width = element.width! * scale
const height = element.height! * scale
@ -54,7 +62,7 @@ export class ImageParticle {
this.imageCache.set(element.id!, img)
resolve(element)
}
img.onerror = (error) => {
img.onerror = error => {
const fallbackImage = this.getFallbackImage(width, height)
fallbackImage.onload = () => {
ctx.drawImage(fallbackImage, x, y, width, height)
@ -66,5 +74,4 @@ export class ImageParticle {
this.addImageObserver(imageLoadPromise)
}
}
}
}

@ -9,7 +9,6 @@ import { IRow } from '../../../interface/Row'
import { Draw } from '../Draw'
export class ListParticle {
private options: DeepRequired<IEditorOption>
// 非递增样式直接返回默认值
@ -21,7 +20,10 @@ export class ListParticle {
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>()
let start = 0
let curListId = elementList[start].listId
@ -51,11 +53,17 @@ export class ListParticle {
return listStyleMap
}
public getListStyleWidth(ctx: CanvasRenderingContext2D, listElementList: IElement[]): number {
public getListStyleWidth(
ctx: CanvasRenderingContext2D,
listElementList: IElement[]
): number {
const { scale } = this.options
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
}
// 计算列表数量
@ -67,23 +75,35 @@ export class ListParticle {
}, 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)
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 startElement = elementList[0]
if (startElement.value !== ZERO || startElement.listWrap) return
let text = ''
if (startElement.listType === ListType.UL) {
text = ulStyleMapping[<UlStyle><unknown>startElement.listStyle] || ulStyleMapping[UlStyle.DISC]
text =
ulStyleMapping[<UlStyle>(<unknown>startElement.listStyle)] ||
ulStyleMapping[UlStyle.DISC]
} else {
text = `${listIndex! + 1}${KeyMap.PERIOD}`
}
if (!text) return
const { coordinate: { leftTop: [startX, startY] } } = position
const {
coordinate: {
leftTop: [startX, startY]
}
} = position
const x = startX - offsetX!
const y = startY + ascent
const { defaultFont, defaultSize, scale } = this.options
@ -92,5 +112,4 @@ export class ListParticle {
ctx.fillText(text, x, y)
ctx.restore()
}
}
}

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

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

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

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

@ -5,12 +5,11 @@ import { IRowElement } from '../../../interface/Row'
import { Draw } from '../Draw'
export interface IMeasureWordResult {
width: number;
endElement: IElement;
width: number
endElement: IElement
}
export class TextParticle {
private ctx: CanvasRenderingContext2D
private curX: number
private curY: number
@ -28,7 +27,11 @@ export class TextParticle {
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 endElement: IElement = elementList[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
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 cacheTextMetrics = this.cacheMeasureText.get(id)
if (cacheTextMetrics) {
@ -68,7 +77,12 @@ export class TextParticle {
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
// 主动完成的重设起始点
if (!this.text) {
@ -77,7 +91,7 @@ export class TextParticle {
// 样式发生改变
if (
(this.curStyle && element.style !== this.curStyle) ||
(element.color !== this.curColor)
element.color !== this.curColor
) {
this.complete()
this._setCurXY(x, y)
@ -102,5 +116,4 @@ export class TextParticle {
this.ctx.fillText(this.text, this.curX, this.curY)
this.ctx.restore()
}
}
}

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

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

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

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

@ -7,7 +7,6 @@ import { Draw } from '../../Draw'
import { DatePicker } from './DatePicker'
export class DateParticle {
private draw: Draw
private range: RangeManager
private datePicker: DatePicker
@ -32,7 +31,7 @@ export class DateParticle {
wed: t('datePicker.weeks.wed'),
thu: t('datePicker.weeks.thu'),
fri: t('datePicker.weeks.fri'),
sat: t('datePicker.weeks.sat'),
sat: t('datePicker.weeks.sat')
},
year: t('datePicker.year'),
month: t('datePicker.month'),
@ -51,16 +50,22 @@ export class DateParticle {
const elementList = this.draw.getElementList()
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)
// 插入新时间
const dateElement: IElement = {
type: ElementType.DATE,
value: '',
dateFormat: startElement.dateFormat,
valueList: [{
value: date
}]
valueList: [
{
value: date
}
]
}
formatElementContext(elementList, [dateElement], leftIndex)
this.draw.insertElementList([dateElement])
@ -113,7 +118,10 @@ export class DateParticle {
const elementList = this.draw.getElementList()
const range = this.getDateElementRange()
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({
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.font = element.style
if (element.color) {
@ -132,5 +145,4 @@ export class DateParticle {
ctx.fillText(element.value, x, y)
ctx.restore()
}
}
}

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

@ -3,7 +3,6 @@ import { ImageParticle } from '../ImageParticle'
import { LaTexSVG, LaTexUtils } from './utils/LaTexUtils'
export class LaTexParticle extends ImageParticle {
public static convertLaTextToSVG(laTex: string): LaTexSVG {
return new LaTexUtils(laTex).svg({
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 width = element.width! * scale
const height = element.height! * scale
@ -29,13 +33,11 @@ export class LaTexParticle extends ImageParticle {
this.imageCache.set(element.value, img)
resolve(element)
}
img.onerror = (error) => {
img.onerror = error => {
reject(error)
}
})
this.addImageObserver(laTexLoadPromise)
}
}
}

@ -6,7 +6,7 @@ const CONFIG: Record<string, number> = {
SQRT_MAG_SCALE: 0.5,
FRAC_SCALE: 0.85,
LINE_SPACING: 0.5,
FRAC_SPACING: 0.4,
FRAC_SPACING: 0.4
}
function tokenize(str: string): string[] {
@ -49,27 +49,40 @@ function tokenize(str: string): string[] {
}
interface Bbox {
x: number, y: number, w: number, h: number
x: number
y: number
w: number
h: number
}
interface Expr {
type: string;
text: string;
mode: string;
chld: Expr[];
bbox: Bbox;
type: string
text: string
mode: string
chld: Expr[]
bbox: Bbox
}
function parseAtom(x: string): Expr {
// @ts-ignore
return { type: SYMB[x] ? 'symb' : 'char', mode: 'math', text: x, chld: [], bbox: null }
return {
type: SYMB[x] ? 'symb' : 'char',
mode: 'math',
text: x,
chld: [],
// @ts-ignore
bbox: null
}
}
function parse(tokens: string[]): Expr {
let i = 0
let expr: Expr = {
type: 'node',
text: '',
mode: 'math',
chld: [],
// @ts-ignore
type: 'node', text: '', mode: 'math', chld: [], bbox: null
bbox: null
}
function takeOpt(): Expr | null {
@ -133,8 +146,12 @@ function parse(tokens: string[]): Expr {
for (i = 0; i < tokens.length; i++) {
const s: Symb = SYMB[tokens[i]]
const e: Expr = {
type: '',
text: tokens[i],
mode: 'math',
chld: [],
// @ts-ignore
type: '', text: tokens[i], mode: 'math', chld: [], bbox: null
bbox: null
}
if (s) {
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) {
scly = sclx
}
@ -209,8 +233,10 @@ function transform(expr: Expr, sclx: number, scly: number, x: number, y: number,
}
function computeBbox(exprs: Expr[]): Bbox {
let xmin = Infinity; let xmax = -Infinity
let ymin = Infinity; let ymax = -Infinity
let xmin = Infinity
let xmax = -Infinity
let ymin = Infinity
let ymax = -Infinity
for (let i = 0; i < exprs.length; i++) {
if (!exprs[i].bbox) {
continue
@ -238,8 +264,11 @@ function group(exprs: Expr[]): Expr {
exprs[i].bbox.y -= bbox.y
}
const expr: Expr = {
type: 'node', text: '', mode: 'math',
chld: exprs, bbox
type: 'node',
text: '',
mode: 'math',
chld: exprs,
bbox
}
return expr
}
@ -249,7 +278,10 @@ function align(exprs: Expr[], alignment = 'center'): void {
if (exprs[i].text == '^' || exprs[i].text == '\'') {
let h = 0
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--
}
h = exprs[j].bbox.y
@ -269,7 +301,10 @@ function align(exprs: Expr[], alignment = 'center'): void {
} else if (exprs[i].text == '_') {
let h = 1
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--
}
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 lvl = lvl0
let ymin = 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) {
lvl++
} 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)
if (ymin != Infinity && ymax != -Infinity) {
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') {
const [ymin, ymax] = searchHigh(i, '\\right', '\\left', -1, 0)
if (ymin != Infinity && ymax != -Infinity) {
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') {
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)
if (ymin != Infinity && ymax != -Infinity) {
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
}
@ -408,28 +449,27 @@ function align(exprs: Expr[], alignment = 'center'): void {
ybds[i][1] += shft
}
exprs.splice(0, exprs.length)
for (let i = 0; i < erows.length; i++) {
let dx = 0
for (let j = 0; j < erows[i].length; j++) {
const e: Expr = erows[i][j]
if (!e) {
dx += (colws[j])
dx += colws[j]
continue
}
e.bbox.x += dx
dx += (colws[j] - e.bbox.w)
dx += colws[j] - e.bbox.w
// e.bbox.w = colws[j];
if (alignment == 'center') {
e.bbox.x += (colws[j] - e.bbox.w) / 2
} else if (alignment == 'left') {
//ok
} else if (alignment == 'right') {
e.bbox.x += (colws[j] - e.bbox.w)
e.bbox.x += colws[j] - e.bbox.w
} else if (alignment == 'equation') {
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)
@ -438,28 +478,29 @@ function align(exprs: Expr[], alignment = 'center'): void {
}
function plan(expr: Expr, mode = 'math'): void {
const tmd: string = {
'\\text': 'text',
'\\mathnormal': 'math',
'\\mathrm': 'rm',
'\\mathit': 'it',
'\\mathbf': 'bf',
'\\mathsf': 'sf',
'\\mathtt': 'tt',
'\\mathfrak': 'frak',
'\\mathcal': 'cal',
'\\mathbb': 'bb',
'\\mathscr': 'scr',
'\\rm': 'rm',
'\\it': 'it',
'\\bf': 'bf',
'\\sf': 'tt',
'\\tt': 'tt',
'\\frak': 'frak',
'\\cal': 'cal',
'\\bb': 'bb',
'\\scr': 'scr',
}[expr.text] ?? mode
const tmd: string =
{
'\\text': 'text',
'\\mathnormal': 'math',
'\\mathrm': 'rm',
'\\mathit': 'it',
'\\mathbf': 'bf',
'\\mathsf': 'sf',
'\\mathtt': 'tt',
'\\mathfrak': 'frak',
'\\mathcal': 'cal',
'\\mathbb': 'bb',
'\\mathscr': 'scr',
'\\rm': 'rm',
'\\it': 'it',
'\\bf': 'bf',
'\\sf': 'tt',
'\\tt': 'tt',
'\\frak': 'frak',
'\\cal': 'cal',
'\\bb': 'bb',
'\\scr': 'scr'
}[expr.text] ?? mode
if (!expr.chld.length) {
if (SYMB[expr.text]) {
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
// @ts-ignore
transform(a, s, null, (mw - a.bbox.w * s) / 2, 0)
// @ts-ignore
transform(b, s, 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 }
transform(
b,
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') {
const a: Expr = expr.chld[0]
const b: Expr = expr.chld[1]
@ -548,20 +600,22 @@ function plan(expr: Expr, mode = 'math'): void {
}
// @ts-ignore
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) {
const e: Expr = expr.chld[0]
plan(e)
const y0 = 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 }
} else if (SYMB[expr.text] && SYMB[expr.text].flags.mat) {
const e: Expr = expr.chld[0]
plan(e)
expr.bbox = { x: 0, y: 0, w: e.bbox.w, h: e.bbox.h + 0.5 }
} else {
let dx = 0
let dy = 0
@ -569,13 +623,14 @@ function plan(expr: Expr, mode = 'math'): void {
for (let i = 0; i < expr.chld.length; i++) {
const c: Expr = expr.chld[i]
// @ts-ignore
const spac: number = {
'\\quad': 2,
'\\,': 2 * 3 / 18,
'\\:': 2 * 4 / 18,
'\\;': 2 * 5 / 18,
'\\!': 2 * (-3) / 18,
}[c.text] ?? null
const spac: number =
{
'\\quad': 2,
'\\,': (2 * 3) / 18,
'\\:': (2 * 4) / 18,
'\\;': (2 * 5) / 18,
'\\!': (2 * -3) / 18
}[c.text] ?? null
if (c.text == '\\\\') {
dy += mh
@ -593,10 +648,16 @@ function plan(expr: Expr, mode = 'math'): void {
transform(c, 1, null, dx, dy)
if (c.text == '^' || c.text == '_' || c.text == '\'') {
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--
}
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 == '\'') {
let k = j + 1
let nth = 0
@ -606,13 +667,21 @@ function plan(expr: Expr, mode = 'math'): void {
}
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)
} else {
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
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 {
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)
@ -629,19 +698,20 @@ function plan(expr: Expr, mode = 'math'): void {
}
dy += mh
const m2s: Record<string, string[]> = {
'bmatrix': ['[', ']'],
'pmatrix': ['(', ')'],
'Bmatrix': ['\\{', '\\}'],
'cases': ['\\{']
bmatrix: ['[', ']'],
pmatrix: ['(', ')'],
Bmatrix: ['\\{', '\\}'],
cases: ['\\{']
}
const alt: string = {
'bmatrix': 'center',
'pmatrix': 'center',
'Bmatrix': 'center',
'cases': 'left',
'matrix': 'center',
'aligned': 'equation',
}[expr.text] ?? 'left'
const alt: string =
{
bmatrix: 'center',
pmatrix: 'center',
Bmatrix: 'center',
cases: 'left',
matrix: 'center',
aligned: 'equation'
}[expr.text] ?? 'left'
const hasLp = !!m2s[expr.text]
const hasRp = !!m2s[expr.text] && m2s[expr.text].length > 1
@ -657,16 +727,29 @@ function plan(expr: Expr, mode = 'math'): void {
// @ts-ignore
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) {
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) {
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') {
@ -684,7 +767,8 @@ function flatten(expr: Expr) {
dx += expr.bbox.x
dy += expr.bbox.y
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 = {
type: 'symb',
mode: expr.mode,
@ -693,13 +777,17 @@ function flatten(expr: Expr) {
x: dx,
y: dy + (expr.chld[1].bbox.y - h / 2) - h / 2,
w: expr.bbox.w,
h: h,
}, chld: [],
h: h
},
chld: []
}
ff.push(e)
} else if (expr.text == '\\sqrt') {
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 = {
type: 'symb',
mode: expr.mode,
@ -708,8 +796,9 @@ function flatten(expr: Expr) {
x: dx + xx,
y: dy + h / 2,
w: expr.chld[0].bbox.x - xx,
h: expr.bbox.h - h / 2,
}, chld: [],
h: expr.bbox.h - h / 2
},
chld: []
}
ff.push(e)
ff.push({
@ -720,8 +809,9 @@ function flatten(expr: Expr) {
x: dx + expr.chld[0].bbox.x,
y: dy,
w: expr.bbox.w - expr.chld[0].bbox.x,
h: h,
}, chld: [],
h: h
},
chld: []
})
} else if (expr.text == '\\binom') {
const w = Math.min(expr.chld[0].bbox.x, expr.chld[1].bbox.x)
@ -733,8 +823,9 @@ function flatten(expr: Expr) {
x: dx,
y: dy,
w: w,
h: expr.bbox.h,
}, chld: [],
h: expr.bbox.h
},
chld: []
}
ff.push(e)
ff.push({
@ -745,8 +836,9 @@ function flatten(expr: Expr) {
x: dx + expr.bbox.w - w,
y: dy,
w: w,
h: expr.bbox.h,
}, chld: [],
h: expr.bbox.h
},
chld: []
})
} else if (SYMB[expr.text] && SYMB[expr.text].flags.hat) {
const h: number = expr.chld[0].bbox.y
@ -758,8 +850,9 @@ function flatten(expr: Expr) {
x: dx,
y: dy,
w: expr.bbox.w,
h: h,
}, chld: [],
h: h
},
chld: []
}
ff.push(e)
} else if (SYMB[expr.text] && SYMB[expr.text].flags.mat) {
@ -772,21 +865,23 @@ function flatten(expr: Expr) {
x: dx,
y: dy + h,
w: expr.bbox.w,
h: expr.bbox.h - h,
}, chld: [],
h: expr.bbox.h - h
},
chld: []
}
ff.push(e)
} else if (expr.type != 'node'
&& expr.text != '^'
&& expr.text != '_'
) {
} else if (expr.type != 'node' && expr.text != '^' && expr.text != '_') {
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,
y: dy,
w: expr.bbox.w,
h: expr.bbox.h
}, chld: [],
},
chld: []
}
ff.push(e)
}
@ -809,8 +904,12 @@ function render(expr: Expr): number[][][] {
const e: Expr = expr.chld[i]
let s = e.bbox.h / 2
let isSmallHat = false
if (SYMB[e.text] && SYMB[e.text].flags.hat &&
!SYMB[e.text].flags.xfl && !SYMB[e.text].flags.yfl) {
if (
SYMB[e.text] &&
SYMB[e.text].flags.hat &&
!SYMB[e.text].flags.xfl &&
!SYMB[e.text].flags.yfl
) {
s *= 4
isSmallHat = true
}
@ -824,25 +923,24 @@ function render(expr: Expr): number[][][] {
let y = d.polylines[j][k][1]
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
} else if (d.w / 16 * s > e.bbox.w) {
x = x / Math.max(d.w, 1) * e.bbox.w
} else if ((d.w / 16) * s > e.bbox.w) {
x = (x / Math.max(d.w, 1)) * e.bbox.w
x += e.bbox.x
} else {
x = x / 16 * s
const p = (e.bbox.w - d.w / 16 * s) / 2
x = (x / 16) * s
const p = (e.bbox.w - (d.w / 16) * s) / 2
x += e.bbox.x + p
}
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
} else {
y = y / 16 * s
y = (y / 16) * s
if (isSmallHat) {
const p = (d.ymax + d.ymin) / 2
y -= p / 16 * s
y -= (p / 16) * s
}
y += e.bbox.y + e.bbox.h / 2
}
@ -850,7 +948,7 @@ function render(expr: Expr): number[][][] {
}
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
const isVerb = !!(SYMB[e.text] && SYMB[e.text].flags.txt)
for (let n = Number(isVerb); n < e.text.length; n++) {
@ -874,7 +972,6 @@ function render(expr: Expr): number[][][] {
} else {
x += (16 - d.w) / 2 / 16
}
}
x += x0
y += e.bbox.y + e.bbox.h / 2
@ -885,7 +982,7 @@ function render(expr: Expr): number[][][] {
if (e.mode == 'tt') {
x0 += s
} else {
x0 += d.w / 16 * s
x0 += (d.w / 16) * s
}
}
}
@ -894,16 +991,16 @@ function render(expr: Expr): number[][][] {
}
interface ExportOpt {
MIN_CHAR_H?: number;
MAX_W?: number;
MAX_H?: number;
MARGIN_X?: number;
MARGIN_Y?: number;
SCALE_X?: number;
SCALE_Y?: number;
STROKE_W?: number;
FG_COLOR?: string;
BG_COLOR?: string;
MIN_CHAR_H?: number
MAX_W?: number
MAX_H?: number
MARGIN_X?: number
MARGIN_Y?: number
SCALE_X?: number
SCALE_Y?: number
STROKE_W?: number
FG_COLOR?: string
BG_COLOR?: string
}
function nf(x: number): number {
@ -911,16 +1008,16 @@ function nf(x: number): number {
}
export interface LaTexSVG {
svg: string;
width: number;
height: number;
svg: string
width: number
height: number
}
export class LaTexUtils {
_latex: string;
_tree: Expr;
_tokens: string[];
_polylines: number[][][];
_latex: string
_tree: Expr
_tokens: string[]
_polylines: number[][][]
constructor(latex: string) {
this._latex = latex
@ -943,9 +1040,11 @@ export class LaTexUtils {
let mh = 0
for (let i = 0; i < this._tree.chld.length; i++) {
const c: Expr = this._tree.chld[i]
if (c.type == 'char' ||
if (
c.type == 'char' ||
(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)
}
}
@ -956,12 +1055,12 @@ export class LaTexUtils {
if (opt.MAX_W != undefined) {
const s0 = sclx
sclx = Math.min(sclx, opt.MAX_W / this._tree.bbox.w)
scly *= (sclx / s0)
scly *= sclx / s0
}
if (opt.MAX_H != undefined) {
const s0 = scly
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 py: number = opt.MARGIN_Y ?? scly
@ -989,7 +1088,7 @@ export class LaTexUtils {
for (let i = 0; i < this._polylines.length; i++) {
for (let j = 0; j < this._polylines[i].length; 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)}`
}
}
@ -1004,18 +1103,22 @@ export class LaTexUtils {
let o = `<svg
xmlns="http://www.w3.org/2000/svg"
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"
>`
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="`
for (let i = 0; i < this._polylines.length; i++) {
o += 'M'
for (let j = 0; j < this._polylines[i].length; 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 += `"/>`
@ -1040,10 +1143,14 @@ export class LaTexUtils {
let pdf = ''
let count = 4
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++) {
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'
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 {
w: number;
xmin: number;
xmax: number;
ymin: number;
ymax: number;
polylines: Array<Array<Array<number>>>;
w: number
xmin: number
xmax: number
ymin: number
ymax: number
polylines: Array<Array<Array<number>>>
}
const ordR = 'R'.charCodeAt(0)
@ -53,7 +53,7 @@ function compile(i: number): void {
xmax: zmax,
ymin: ymin,
ymax: ymax,
polylines: polylines,
polylines: polylines
}
}
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',
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',
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
*/
export interface Symb {
glyph: number;
arity?: number;
flags: Record<string, boolean>;
glyph: number
arity?: number
flags: Record<string, boolean>
}
const SYMB: Record<string, Symb> = {
'\\frac': { 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: 2221, arity: 0, flags: { yfl: true } },
')': { glyph: 2222, 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: {} },
/*accents*/
'\\vec': { glyph: 2261, 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 } },
'\\vec': {
glyph: 2261,
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 } },
'\\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 } },
'\\acute': { glyph: 2248, 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: {} },
'\\Chi': { glyph: 2048, flags: {} },
'\\Psi': { glyph: 2049, flags: {} },
'\\Omega': { glyph: 2050, flags: {} },
'\\Omega': { glyph: 2050, flags: {} }
}
export { SYMB }
@ -293,6 +313,6 @@ export function asciiMap(x: string, mode = 'math'): number {
'>': 2242,
'~': 2246,
'@': 2273,
'\\': 804,
'\\': 804
}[x]
}

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

@ -7,16 +7,15 @@ import { RangeManager } from '../../../range/RangeManager'
import { Draw } from '../../Draw'
interface IDrawTableBorderOption {
ctx: CanvasRenderingContext2D;
startX: number;
startY: number;
width: number;
height: number;
isDrawFullBorder?: boolean;
ctx: CanvasRenderingContext2D
startX: number
startY: number
width: number
height: number
isDrawFullBorder?: boolean
}
export class TableParticle {
private draw: Draw
private range: RangeManager
private options: Required<IEditorOption>
@ -45,9 +44,17 @@ export class TableParticle {
}
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
const { isCrossRowCol, startTdIndex, endTdIndex, startTrIndex, endTrIndex } = this.range.getRange()
const {
isCrossRowCol,
startTdIndex,
endTdIndex,
startTrIndex,
endTrIndex
} = this.range.getRange()
const originalElementList = this.draw.getOriginalElementList()
const element = originalElementList[index!]
const curTrList = element.trList!
@ -59,6 +66,7 @@ export class TableParticle {
let endTd = curTrList[endTrIndex!].tdList[endTdIndex!]
// 交换起始位置
if (startTd.x! > endTd.x! || startTd.y! > endTd.y!) {
// prettier-ignore
[startTd, endTd] = [endTd, startTd]
}
const startColIndex = startTd.colIndex!
@ -75,8 +83,10 @@ export class TableParticle {
const tdColIndex = td.colIndex!
const tdRowIndex = td.rowIndex!
if (
tdColIndex >= startColIndex && tdColIndex <= endColIndex
&& tdRowIndex >= startRowIndex && tdRowIndex <= endRowIndex
tdColIndex >= startColIndex &&
tdColIndex <= endColIndex &&
tdRowIndex >= startRowIndex &&
tdRowIndex <= endRowIndex
) {
tdList.push(td)
}
@ -105,7 +115,12 @@ export class TableParticle {
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
if (!colgroup || !trList || borderType === TableBorder.EMPTY) return
const { scale } = this.options
@ -146,7 +161,12 @@ export class TableParticle {
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
if (!trList) return
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 { type, trList } = element
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
let startTd = trList[startTrIndex!].tdList[startTdIndex!]
@ -284,8 +315,10 @@ export class TableParticle {
const tdColIndex = td.colIndex!
const tdRowIndex = td.rowIndex!
if (
tdColIndex >= startColIndex && tdColIndex <= endColIndex
&& tdRowIndex >= startRowIndex && tdRowIndex <= endRowIndex
tdColIndex >= startColIndex &&
tdColIndex <= endColIndex &&
tdRowIndex >= startRowIndex &&
tdRowIndex <= endRowIndex
) {
const x = td.x! * scale
const y = td.y! * scale
@ -300,9 +333,13 @@ export class TableParticle {
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._drawBorder(ctx, element, startX, startY)
}
}
}

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

@ -10,15 +10,23 @@ export abstract class AbstractRichText {
public clearFillInfo() {
this.fillColor = undefined
return this.fillRect = {
this.fillRect = {
x: 0,
y: 0,
width: 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
if (!isFirstRecord && this.fillColor && this.fillColor !== color) {
this.render(ctx)
@ -37,5 +45,4 @@ export abstract class AbstractRichText {
}
public abstract render(ctx: CanvasRenderingContext2D): void
}
}

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

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

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

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

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

@ -30,7 +30,10 @@ function dblclick(host: CanvasEvent, evt: MouseEvent) {
let upStartIndex = index - 1
while (upStartIndex > 0) {
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++
upStartIndex--
} else {
@ -41,7 +44,10 @@ function dblclick(host: CanvasEvent, evt: MouseEvent) {
let downStartIndex = index + 1
while (downStartIndex < elementList.length) {
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++
downStartIndex++
} else {
@ -106,4 +112,4 @@ function threeClick(host: CanvasEvent) {
export default {
dblclick,
threeClick
}
}

@ -34,4 +34,4 @@ function compositionend(host: CanvasEvent, evt: CompositionEvent) {
export default {
compositionstart,
compositionend
}
}

@ -10,4 +10,4 @@ export function copy(host: CanvasEvent) {
const elementList = draw.getElementList()
writeElementList(elementList.slice(startIndex + 1, endIndex + 1), options)
}
}
}

@ -46,4 +46,4 @@ export function cut(host: CanvasEvent) {
}
rangeManager.setRange(curIndex, curIndex)
draw.render({ curIndex })
}
}

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

@ -6,4 +6,4 @@ export function drop(evt: DragEvent, host: CanvasEvent) {
if (data) {
host.input(data)
}
}
}

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

@ -47,7 +47,11 @@ export function keydown(evt: KeyboardEvent, host: CanvasEvent) {
})
}
if (!isCollapsed) {
draw.spliceElementList(elementList, startIndex + 1, endIndex - startIndex)
draw.spliceElementList(
elementList,
startIndex + 1,
endIndex - startIndex
)
} else {
draw.spliceElementList(elementList, index, 1)
}
@ -64,7 +68,11 @@ export function keydown(evt: KeyboardEvent, host: CanvasEvent) {
curIndex = control.removeControl(endIndex + 1)
} else {
if (!isCollapsed) {
draw.spliceElementList(elementList, startIndex + 1, endIndex - startIndex)
draw.spliceElementList(
elementList,
startIndex + 1,
endIndex - startIndex
)
} else {
draw.spliceElementList(elementList, index + 1, 1)
}
@ -84,7 +92,12 @@ export function keydown(evt: KeyboardEvent, host: CanvasEvent) {
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)
}
let curIndex: number
@ -94,7 +107,12 @@ export function keydown(evt: KeyboardEvent, host: CanvasEvent) {
if (isCollapsed) {
draw.spliceElementList(elementList, index + 1, 0, enterText)
} else {
draw.spliceElementList(elementList, startIndex + 1, endIndex - startIndex, enterText)
draw.spliceElementList(
elementList,
startIndex + 1,
endIndex - startIndex,
enterText
)
}
curIndex = index + 1
}
@ -159,7 +177,12 @@ export function keydown(evt: KeyboardEvent, host: CanvasEvent) {
}
}
const maxElementListIndex = elementList.length - 1
if (anchorStartIndex > maxElementListIndex || anchorEndIndex > maxElementListIndex) return
if (
anchorStartIndex > maxElementListIndex ||
anchorEndIndex > maxElementListIndex
) {
return
}
rangeManager.setRange(anchorStartIndex, anchorEndIndex)
const isCollapsed = anchorStartIndex === anchorEndIndex
draw.render({
@ -181,21 +204,31 @@ export function keydown(evt: KeyboardEvent, host: CanvasEvent) {
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())) {
// 下一个光标点所在行位置集合
const probablePosition = isUp
? positionList.slice(0, index)
.filter(p => p.rowNo === rowNo - 1 && pageNo === p.pageNo)
: positionList.slice(index, positionList.length - 1)
.filter(p => p.rowNo === rowNo + 1 && pageNo === p.pageNo)
? positionList
.slice(0, index)
.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 maxDistance = 0
for (let p = 0; p < probablePosition.length; 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]
if (curDistance > maxDistance) {
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]
if (curDistance > maxDistance) {
maxIndex = position.index
@ -237,6 +273,7 @@ export function keydown(evt: KeyboardEvent, host: CanvasEvent) {
}
}
if (anchorStartIndex > anchorEndIndex) {
// prettier-ignore
[anchorStartIndex, anchorEndIndex] = [anchorEndIndex, anchorStartIndex]
}
rangeManager.setRange(anchorStartIndex, anchorEndIndex)
@ -282,10 +319,12 @@ export function keydown(evt: KeyboardEvent, host: CanvasEvent) {
}
evt.preventDefault()
} else if (evt.key === KeyMap.TAB) {
draw.insertElementList([{
type: ElementType.TAB,
value: ''
}])
draw.insertElementList([
{
type: ElementType.TAB,
value: ''
}
])
evt.preventDefault()
}
}
}

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

@ -5,6 +5,8 @@ export function mouseleave(evt: MouseEvent, host: CanvasEvent) {
const draw = host.getDraw()
const pageContainer = draw.getPageContainer()
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)
}
}

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

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

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

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

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

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

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

@ -3,11 +3,15 @@ import { Draw } from '../draw/Draw'
import { RangeManager } from '../range/RangeManager'
export class SelectionObserver {
// 每次滚动长度
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 requestAnimationFrameId: number | null
@ -74,7 +78,9 @@ export class SelectionObserver {
} else {
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) {
@ -90,5 +96,4 @@ export class SelectionObserver {
this.isMoving = false
}
}
}
}

@ -2,15 +2,16 @@ import Editor from '../..'
import { PluginFunction } from '../../interface/Plugin'
export class Plugin {
private editor: Editor
constructor(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)
}
}
}

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

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

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

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

@ -19,4 +19,4 @@ export const listKeys: IRegisterShortcut[] = [
command.executeList(ListType.OL)
}
}
]
]

@ -91,4 +91,4 @@ export const richtextKeys: IRegisterShortcut[] = [
command.executeRowFlex(RowFlex.ALIGNMENT)
}
}
]
]

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

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

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

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

@ -6,7 +6,6 @@ import { Draw } from '../draw/Draw'
import { I18n } from '../i18n/I18n'
export class Zone {
private readonly INDICATOR_PADDING = 2
private readonly INDICATOR_TITLE_TRANSLATE = [20, 5]
@ -99,9 +98,13 @@ export class Zone {
: startY - this.INDICATOR_PADDING
// 标题
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.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)
// 上边线
@ -141,5 +144,4 @@ export class Zone {
this.indicatorContainer?.remove()
this.indicatorContainer = null
}
}
}

@ -7,4 +7,4 @@ export const defaultCheckboxOption: Readonly<Required<ICheckboxOption>> = {
lineWidth: 1,
fillStyle: '#5175f4',
fontStyle: '#ffffff'
}
}

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

@ -1,3 +1,3 @@
export const NAME_PLACEHOLDER = {
SELECTED_TEXT: '%s'
}
}

@ -5,4 +5,4 @@ export const defaultControlOption: Readonly<Required<IControlOption>> = {
bracketColor: '#000000',
prefix: '{',
postfix: '}'
}
}

@ -7,4 +7,4 @@ export const defaultCursorOption: Readonly<Required<ICursorOption>> = {
color: '#000000',
dragWidth: 2,
dragColor: '#0000FF'
}
}

@ -1,2 +1,2 @@
export const EDITOR_COMPONENT = 'editor-component'
export const EDITOR_PREFIX = 'ce'
export const EDITOR_PREFIX = 'ce'

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

@ -5,4 +5,4 @@ export const defaultFooterOption: Readonly<Required<IFooter>> = {
bottom: 30,
maxHeightRadio: MaxHeightRatio.HALF,
disabled: false
}
}

@ -5,4 +5,4 @@ export const defaultHeaderOption: Readonly<Required<IHeader>> = {
top: 30,
maxHeightRadio: MaxHeightRatio.HALF,
disabled: false
}
}

@ -16,4 +16,4 @@ export const listStyleCSSMapping: Record<ListStyle, string> = {
[ListStyle.CIRCLE]: 'circle',
[ListStyle.SQUARE]: 'square',
[ListStyle.DECIMAL]: 'decimal'
}
}

@ -18,4 +18,4 @@ export const defaultPageNumberOption: Readonly<Required<IPageNumber>> = {
disabled: false,
startPageNo: 1,
fromPageNo: 0
}
}

@ -6,4 +6,4 @@ export const defaultPlaceholderOption: Readonly<Required<IPlaceholder>> = {
opacity: 1,
size: 16,
font: 'Yahei'
}
}

@ -2,4 +2,4 @@ export const NUMBER_REG = /[0-9]/
export const LETTER_REG = /[a-zA-Z]/
export const NUMBER_LIKE_REG = /[0-9.]/
export const CHINESE_REG = /[\u4e00-\u9fa5]/
export const WORD_LIKE_REG = /[^a-zA-Z][a-zA-Z]/
export const WORD_LIKE_REG = /[^a-zA-Z][a-zA-Z]/

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

@ -6,4 +6,4 @@ export const defaultWatermarkOption: Readonly<Required<IWatermark>> = {
opacity: 0.3,
size: 200,
font: 'Yahei'
}
}

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

Loading…
Cancel
Save