refactor: add prettier and format
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+229
-71
@@ -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
|
||||
}
|
||||
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
|
||||
}
|
||||
colgroup[index].width += dx / scale
|
||||
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
|
||||
}
|
||||
// 最大移动距离计算-如果向右移动:使后面一个单元格小于最小宽度,则减少移动量
|
||||
const nextColWidth = colgroup[index + 1]?.width
|
||||
if (
|
||||
dx > 0 &&
|
||||
nextColWidth &&
|
||||
nextColWidth - dx < this.MIN_TD_WIDTH
|
||||
) {
|
||||
dx = nextColWidth - this.MIN_TD_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
|
||||
}
|
||||
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'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export enum BlockType {
|
||||
IFRAME = 'iframe',
|
||||
VIDEO = 'video'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,4 +7,4 @@ export enum MaxHeightRatio {
|
||||
export enum NumberType {
|
||||
ARABIC = 'arabic',
|
||||
CHINESE = 'chinese'
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user