From ab7c5788c874e8d212b3b4dd3a61d0889900a71a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E4=BA=91=E9=A3=9E?= Date: Fri, 1 Jul 2022 16:07:26 +0800 Subject: [PATCH] feat:add laTeX previewer --- src/editor/core/draw/Draw.ts | 13 +- .../draw/control/checkbox/CheckboxControl.ts | 2 +- .../core/draw/control/select/SelectControl.ts | 2 +- .../core/draw/control/text/TextControl.ts | 2 +- .../core/draw/particle/ImageParticle.ts | 321 +---------------- .../core/draw/particle/latex/LaTexParticle.ts | 4 +- .../draw/particle/latex/utils/LaTexUtils.ts | 4 +- .../core/draw/particle/previewer/Previewer.ts | 335 ++++++++++++++++++ src/editor/core/event/CanvasEvent.ts | 20 +- src/editor/core/event/GlobalEvent.ts | 8 +- src/editor/interface/Draw.ts | 7 - src/editor/interface/Previewer.ts | 13 + src/editor/utils/element.ts | 8 + 13 files changed, 388 insertions(+), 351 deletions(-) create mode 100644 src/editor/core/draw/particle/previewer/Previewer.ts create mode 100644 src/editor/interface/Previewer.ts diff --git a/src/editor/core/draw/Draw.ts b/src/editor/core/draw/Draw.ts index e750a34..a75ec1e 100644 --- a/src/editor/core/draw/Draw.ts +++ b/src/editor/core/draw/Draw.ts @@ -43,6 +43,7 @@ import { DeepRequired } from '../../interface/Common' import { ControlComponent } from '../../dataset/enum/Control' import { formatElementList } from '../../utils/element' import { WorkerManager } from '../worker/WorkerManager' +import { Previewer } from './particle/previewer/Previewer' export class Draw { @@ -67,6 +68,7 @@ export class Draw { private strikeout: Strikeout private highlight: Highlight private historyManager: HistoryManager + private previewer: Previewer private imageParticle: ImageParticle private laTexParticle: LaTexParticle private textParticle: TextParticle @@ -118,6 +120,7 @@ export class Draw { this.underline = new Underline(this) this.strikeout = new Strikeout(this) this.highlight = new Highlight(this) + this.previewer = new Previewer(this) this.imageParticle = new ImageParticle(this) this.laTexParticle = new LaTexParticle(this) this.textParticle = new TextParticle(this) @@ -354,6 +357,10 @@ export class Draw { return this.cursor } + public getPreviewer(): Previewer { + return this.previewer + } + public getImageParticle(): ImageParticle { return this.imageParticle } @@ -532,12 +539,6 @@ export class Draw { boundingBoxDescent: 0 } if (element.type === ElementType.IMAGE || element.type === ElementType.LATEX) { - if (element.type === ElementType.LATEX) { - const { svg, width, height } = this.laTexParticle.convertLaTextToSVG(element.value) - element.width = width - element.height = height - element.laTexSVG = svg - } const elementWidth = element.width! * scale const elementHeight = element.height! * scale // 图片超出尺寸后自适应 diff --git a/src/editor/core/draw/control/checkbox/CheckboxControl.ts b/src/editor/core/draw/control/checkbox/CheckboxControl.ts index fc1139b..07855e8 100644 --- a/src/editor/core/draw/control/checkbox/CheckboxControl.ts +++ b/src/editor/core/draw/control/checkbox/CheckboxControl.ts @@ -1,5 +1,5 @@ import { ControlComponent } from '../../../../dataset/enum/Control' -import { KeyMap } from '../../../../dataset/enum/Keymap' +import { KeyMap } from '../../../../dataset/enum/KeyMap' import { IControlInstance } from '../../../../interface/Control' import { IElement } from '../../../../interface/Element' import { Control } from '../Control' diff --git a/src/editor/core/draw/control/select/SelectControl.ts b/src/editor/core/draw/control/select/SelectControl.ts index 82e31a5..c204e82 100644 --- a/src/editor/core/draw/control/select/SelectControl.ts +++ b/src/editor/core/draw/control/select/SelectControl.ts @@ -1,7 +1,7 @@ import { EDITOR_COMPONENT } from '../../../../dataset/constant/Editor' import { ControlComponent } from '../../../../dataset/enum/Control' import { EditorComponent } from '../../../../dataset/enum/Editor' -import { KeyMap } from '../../../../dataset/enum/Keymap' +import { KeyMap } from '../../../../dataset/enum/KeyMap' import { IControlInstance } from '../../../../interface/Control' import { IElement } from '../../../../interface/Element' import { splitText } from '../../../../utils' diff --git a/src/editor/core/draw/control/text/TextControl.ts b/src/editor/core/draw/control/text/TextControl.ts index dccf8e7..ddb90a6 100644 --- a/src/editor/core/draw/control/text/TextControl.ts +++ b/src/editor/core/draw/control/text/TextControl.ts @@ -1,5 +1,5 @@ import { ControlComponent } from '../../../../dataset/enum/Control' -import { KeyMap } from '../../../../dataset/enum/Keymap' +import { KeyMap } from '../../../../dataset/enum/KeyMap' import { IControlInstance } from '../../../../interface/Control' import { IElement } from '../../../../interface/Element' import { Control } from '../Control' diff --git a/src/editor/core/draw/particle/ImageParticle.ts b/src/editor/core/draw/particle/ImageParticle.ts index d6c802e..9cca801 100644 --- a/src/editor/core/draw/particle/ImageParticle.ts +++ b/src/editor/core/draw/particle/ImageParticle.ts @@ -1,334 +1,15 @@ -import { IImageParticleCreateResult } from '../../../interface/Draw' import { IEditorOption } from '../../../interface/Editor' -import { IElement, IElementPosition } from '../../../interface/Element' -import { downloadFile } from '../../../utils' +import { IElement } from '../../../interface/Element' import { Draw } from '../Draw' export class ImageParticle { - protected container: HTMLDivElement - protected canvas: HTMLCanvasElement - protected draw: Draw protected options: Required - protected curElement: IElement | null - protected curPosition: IElementPosition | null protected imageCache: Map - // 拖拽改变尺寸 - protected resizerSelection: HTMLDivElement - protected resizerHandleList: HTMLDivElement[] - protected resizerImageContainer: HTMLDivElement - protected resizerImage: HTMLImageElement - protected width: number - protected height: number - protected mousedownX: number - protected mousedownY: number - protected curHandleIndex: number - // 预览选区 - protected previewerContainer: HTMLDivElement | null - protected previewerImage: HTMLImageElement | null constructor(draw: Draw) { - this.container = draw.getContainer() - this.canvas = draw.getPage() - this.draw = draw this.options = draw.getOptions() - this.curElement = null - this.curPosition = null this.imageCache = new Map() - // 图片尺寸缩放 - const { resizerSelection, resizerHandleList, resizerImageContainer, resizerImage } = this._createResizerDom() - this.resizerSelection = resizerSelection - this.resizerHandleList = resizerHandleList - this.resizerImageContainer = resizerImageContainer - this.resizerImage = resizerImage - this.width = 0 - this.height = 0 - this.mousedownX = 0 - this.mousedownY = 0 - this.curHandleIndex = 0 // 默认右下角 - // 图片预览 - resizerSelection.ondblclick = this._dblclick.bind(this) - this.previewerContainer = null - this.previewerImage = null - } - - private _createResizerDom(): IImageParticleCreateResult { - // 拖拽边框 - const resizerSelection = document.createElement('div') - resizerSelection.classList.add('resizer-selection') - resizerSelection.style.display = 'none' - resizerSelection.style.borderColor = this.options.resizerColor - const resizerHandleList: HTMLDivElement[] = [] - for (let i = 0; i < 8; i++) { - const handleDom = document.createElement('div') - handleDom.style.background = this.options.resizerColor - handleDom.classList.add(`handle-${i}`) - handleDom.setAttribute('data-index', String(i)) - handleDom.onmousedown = this._mousedown.bind(this) - resizerSelection.append(handleDom) - resizerHandleList.push(handleDom) - } - this.container.append(resizerSelection) - // 拖拽镜像 - const resizerImageContainer = document.createElement('div') - resizerImageContainer.classList.add('resizer-image') - resizerImageContainer.style.display = 'none' - const resizerImage = document.createElement('img') - resizerImageContainer.append(resizerImage) - this.container.append(resizerImageContainer) - return { resizerSelection, resizerHandleList, resizerImageContainer, resizerImage } - } - - private _mousedown(evt: MouseEvent) { - this.canvas = this.draw.getPage() - if (!this.curPosition || !this.curElement) return - const { scale } = this.options - const height = this.draw.getHeight() - const pageGap = this.draw.getPageGap() - this.mousedownX = evt.x - this.mousedownY = evt.y - const target = evt.target as HTMLDivElement - this.curHandleIndex = Number(target.dataset.index) - // 改变光标 - const cursor = window.getComputedStyle(target).cursor - document.body.style.cursor = cursor - this.canvas.style.cursor = cursor - // 拖拽图片镜像 - this.resizerImage.src = this.curElement?.value || '' - this.resizerImageContainer.style.display = 'block' - 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` - this.resizerImage.style.width = `${this.curElement.width! * scale}px` - this.resizerImage.style.height = `${this.curElement.height! * scale}px` - // 追加全局事件 - 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.resizerImageContainer.style.display = 'none' - document.removeEventListener('mousemove', mousemoveFn) - document.body.style.cursor = '' - this.canvas.style.cursor = 'text' - }, { - once: true - }) - evt.preventDefault() - } - - private _mousemove(evt: MouseEvent) { - if (!this.curElement) return - const { scale } = this.options - let dx = 0 - let dy = 0 - switch (this.curHandleIndex) { - case 0: - dx = this.mousedownX - evt.x - dy = this.mousedownY - evt.y - break - case 1: - dy = this.mousedownY - evt.y - break - case 2: - dx = evt.x - this.mousedownX - dy = this.mousedownY - evt.y - break - case 3: - dx = evt.x - this.mousedownX - break - case 5: - dy = evt.y - this.mousedownY - break - case 6: - dx = this.mousedownX - evt.x - dy = evt.y - this.mousedownY - break - case 7: - dx = this.mousedownX - evt.x - break - default: - dx = evt.x - this.mousedownX - dy = evt.y - this.mousedownY - break - } - this.width = this.curElement.width! + dx - this.height = this.curElement.height! + dy - this.resizerImage.style.width = `${this.width * scale}px` - this.resizerImage.style.height = `${this.height * scale}px` - evt.preventDefault() - } - - private _dblclick() { - this._drawPreviewer() - document.body.style.overflow = 'hidden' - } - - private _drawPreviewer() { - const previewerContainer = document.createElement('div') - previewerContainer.classList.add('image-previewer') - // 关闭按钮 - const closeBtn = document.createElement('i') - closeBtn.classList.add('image-close') - closeBtn.onclick = () => { - this._clearPreviewer() - } - previewerContainer.append(closeBtn) - // 图片 - const imgContainer = document.createElement('div') - imgContainer.classList.add('image-container') - const img = document.createElement('img') - img.src = this.curElement?.value || '' - img.draggable = false - imgContainer.append(img) - this.previewerImage = img - previewerContainer.append(imgContainer) - // 操作栏 - let x = 0 - let y = 0 - let scaleSize = 1 - let rotateSize = 0 - const menuContainer = document.createElement('div') - menuContainer.classList.add('image-menu') - const zoomIn = document.createElement('i') - zoomIn.classList.add('zoom-in') - zoomIn.onclick = () => { - scaleSize += 0.1 - this._setPreviewerTransform(scaleSize, rotateSize, x, y) - } - menuContainer.append(zoomIn) - const zoomOut = document.createElement('i') - zoomOut.onclick = () => { - if (scaleSize - 0.1 <= 0.1) return - scaleSize -= 0.1 - this._setPreviewerTransform(scaleSize, rotateSize, x, y) - } - zoomOut.classList.add('zoom-out') - menuContainer.append(zoomOut) - const rotate = document.createElement('i') - rotate.classList.add('rotate') - rotate.onclick = () => { - rotateSize += 1 - this._setPreviewerTransform(scaleSize, rotateSize, x, y) - } - menuContainer.append(rotate) - const originalSize = document.createElement('i') - originalSize.classList.add('original-size') - originalSize.onclick = () => { - x = 0 - y = 0 - scaleSize = 1 - rotateSize = 0 - this._setPreviewerTransform(scaleSize, rotateSize, x, y) - } - menuContainer.append(originalSize) - const imageDownload = document.createElement('i') - imageDownload.classList.add('image-download') - imageDownload.onclick = () => { - downloadFile(img.src, `${this.curElement?.id}.png`) - } - menuContainer.append(imageDownload) - previewerContainer.append(menuContainer) - this.previewerContainer = previewerContainer - document.body.append(previewerContainer) - // 拖拽调整位置 - let startX = 0 - let startY = 0 - let isAllowDrag = false - img.onmousedown = (evt) => { - isAllowDrag = true - startX = evt.x - startY = evt.y - previewerContainer.style.cursor = 'move' - } - previewerContainer.onmousemove = (evt: MouseEvent) => { - if (!isAllowDrag) return - x += (evt.x - startX) - y += (evt.y - startY) - startX = evt.x - startY = evt.y - this._setPreviewerTransform(scaleSize, rotateSize, x, y) - } - previewerContainer.onmouseup = () => { - isAllowDrag = false - previewerContainer.style.cursor = 'auto' - } - previewerContainer.onwheel = (evt) => { - evt.preventDefault() - if (evt.deltaY < 0) { - // 放大 - scaleSize += 0.1 - } else { - // 缩小 - if (scaleSize - 0.1 <= 0.1) return - scaleSize -= 0.1 - } - this._setPreviewerTransform(scaleSize, rotateSize, x, y) - } - } - - 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)` - } - - private _clearPreviewer() { - this.previewerContainer?.remove() - this.previewerContainer = null - document.body.style.overflow = 'auto' - } - - public getImageCache(): Map { - return this.imageCache - } - - public drawResizer(element: IElement, position: IElementPosition) { - const { scale } = this.options - const { coordinate: { leftTop: [left, top] } } = position - const elementWidth = element.width! * scale - const elementHeight = element.height! * scale - const height = this.draw.getHeight() - const pageGap = this.draw.getPageGap() - const handleSize = this.options.resizerSize - const preY = this.draw.getPageNo() * (height + pageGap) - // 边框 - this.resizerSelection.style.left = `${left}px` - this.resizerSelection.style.top = `${top + preY}px` - this.resizerSelection.style.width = `${elementWidth}px` - 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 - ? elementWidth / 2 - : elementWidth - handleSize - 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` - this.resizerHandleList[i].style.top = `${top}px` - } - this.resizerSelection.style.display = 'block' - this.curElement = element - this.curPosition = position - this.width = this.curElement.width! * scale - this.height = this.curElement.height! * scale - } - - public clearResizer() { - this.resizerSelection.style.display = 'none' } public render(ctx: CanvasRenderingContext2D, element: IElement, x: number, y: number) { diff --git a/src/editor/core/draw/particle/latex/LaTexParticle.ts b/src/editor/core/draw/particle/latex/LaTexParticle.ts index 9af54bd..4bfdad7 100644 --- a/src/editor/core/draw/particle/latex/LaTexParticle.ts +++ b/src/editor/core/draw/particle/latex/LaTexParticle.ts @@ -1,10 +1,10 @@ import { IElement } from '../../../../interface/Element' import { ImageParticle } from '../ImageParticle' -import { LaTexSVG, LaTexUtils } from './utils/LaTexUtils' +import { LaTexSVG, LaTexUtils } from './utils/LaTeXUtils' export class LaTexParticle extends ImageParticle { - public convertLaTextToSVG(laTex: string): LaTexSVG { + public static convertLaTextToSVG(laTex: string): LaTexSVG { return new LaTexUtils(laTex).svg({ SCALE_X: 10, SCALE_Y: 10, diff --git a/src/editor/core/draw/particle/latex/utils/LaTexUtils.ts b/src/editor/core/draw/particle/latex/utils/LaTexUtils.ts index 4d7f615..59053b3 100644 --- a/src/editor/core/draw/particle/latex/utils/LaTexUtils.ts +++ b/src/editor/core/draw/particle/latex/utils/LaTexUtils.ts @@ -1022,8 +1022,8 @@ export class LaTexUtils { o += `` return { svg: `data:image/svg+xml;base64,${window.btoa(o)}`, - width: w, - height: h + width: Math.ceil(w), + height: Math.ceil(h) } } diff --git a/src/editor/core/draw/particle/previewer/Previewer.ts b/src/editor/core/draw/particle/previewer/Previewer.ts new file mode 100644 index 0000000..ed45db1 --- /dev/null +++ b/src/editor/core/draw/particle/previewer/Previewer.ts @@ -0,0 +1,335 @@ +import { IEditorOption } from '../../../../interface/Editor' +import { IElement, IElementPosition } from '../../../../interface/Element' +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 + private options: Required + private curElement: IElement | null + private curElementSrc: string + private previewerDrawOption: IPreviewerDrawOption + private curPosition: IElementPosition | null + // 拖拽改变尺寸 + private resizerSelection: HTMLDivElement + private resizerHandleList: HTMLDivElement[] + private resizerImageContainer: HTMLDivElement + private resizerImage: HTMLImageElement + private width: number + private height: number + private mousedownX: number + private mousedownY: number + private curHandleIndex: number + // 预览选区 + private previewerContainer: HTMLDivElement | null + private previewerImage: HTMLImageElement | null + + constructor(draw: Draw) { + this.container = draw.getContainer() + this.canvas = draw.getPage() + this.draw = draw + this.options = draw.getOptions() + this.curElement = null + this.curElementSrc = '' + this.previewerDrawOption = {} + this.curPosition = null + // 图片尺寸缩放 + const { resizerSelection, resizerHandleList, resizerImageContainer, resizerImage } = this._createResizerDom() + this.resizerSelection = resizerSelection + this.resizerHandleList = resizerHandleList + this.resizerImageContainer = resizerImageContainer + this.resizerImage = resizerImage + this.width = 0 + this.height = 0 + this.mousedownX = 0 + this.mousedownY = 0 + this.curHandleIndex = 0 // 默认右下角 + // 图片预览 + resizerSelection.ondblclick = this._dblclick.bind(this) + this.previewerContainer = null + this.previewerImage = null + } + + private _createResizerDom(): IPreviewerCreateResult { + // 拖拽边框 + const resizerSelection = document.createElement('div') + resizerSelection.classList.add('resizer-selection') + resizerSelection.style.display = 'none' + resizerSelection.style.borderColor = this.options.resizerColor + const resizerHandleList: HTMLDivElement[] = [] + for (let i = 0; i < 8; i++) { + const handleDom = document.createElement('div') + handleDom.style.background = this.options.resizerColor + handleDom.classList.add(`handle-${i}`) + handleDom.setAttribute('data-index', String(i)) + handleDom.onmousedown = this._mousedown.bind(this) + resizerSelection.append(handleDom) + resizerHandleList.push(handleDom) + } + this.container.append(resizerSelection) + // 拖拽镜像 + const resizerImageContainer = document.createElement('div') + resizerImageContainer.classList.add('resizer-image') + resizerImageContainer.style.display = 'none' + const resizerImage = document.createElement('img') + resizerImageContainer.append(resizerImage) + this.container.append(resizerImageContainer) + return { resizerSelection, resizerHandleList, resizerImageContainer, resizerImage } + } + + private _mousedown(evt: MouseEvent) { + this.canvas = this.draw.getPage() + if (!this.curPosition || !this.curElement) return + const { scale } = this.options + const height = this.draw.getHeight() + const pageGap = this.draw.getPageGap() + this.mousedownX = evt.x + this.mousedownY = evt.y + const target = evt.target as HTMLDivElement + this.curHandleIndex = Number(target.dataset.index) + // 改变光标 + const cursor = window.getComputedStyle(target).cursor + document.body.style.cursor = cursor + this.canvas.style.cursor = cursor + // 拖拽图片镜像 + this.resizerImage.src = this.curElementSrc + this.resizerImageContainer.style.display = 'block' + 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` + this.resizerImage.style.width = `${this.curElement.width! * scale}px` + this.resizerImage.style.height = `${this.curElement.height! * scale}px` + // 追加全局事件 + 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) + } + // 还原副作用 + this.resizerImageContainer.style.display = 'none' + document.removeEventListener('mousemove', mousemoveFn) + document.body.style.cursor = '' + this.canvas.style.cursor = 'text' + }, { + once: true + }) + evt.preventDefault() + } + + private _mousemove(evt: MouseEvent) { + if (!this.curElement) return + const { scale } = this.options + let dx = 0 + let dy = 0 + switch (this.curHandleIndex) { + case 0: + dx = this.mousedownX - evt.x + dy = this.mousedownY - evt.y + break + case 1: + dy = this.mousedownY - evt.y + break + case 2: + dx = evt.x - this.mousedownX + dy = this.mousedownY - evt.y + break + case 3: + dx = evt.x - this.mousedownX + break + case 5: + dy = evt.y - this.mousedownY + break + case 6: + dx = this.mousedownX - evt.x + dy = evt.y - this.mousedownY + break + case 7: + dx = this.mousedownX - evt.x + break + default: + dx = evt.x - this.mousedownX + dy = evt.y - this.mousedownY + break + } + this.width = this.curElement.width! + dx + this.height = this.curElement.height! + dy + this.resizerImage.style.width = `${this.width * scale}px` + this.resizerImage.style.height = `${this.height * scale}px` + evt.preventDefault() + } + + private _dblclick() { + this._drawPreviewer() + document.body.style.overflow = 'hidden' + } + + private _drawPreviewer() { + const previewerContainer = document.createElement('div') + previewerContainer.classList.add('image-previewer') + // 关闭按钮 + const closeBtn = document.createElement('i') + closeBtn.classList.add('image-close') + closeBtn.onclick = () => { + this._clearPreviewer() + } + previewerContainer.append(closeBtn) + // 图片 + const imgContainer = document.createElement('div') + imgContainer.classList.add('image-container') + const img = document.createElement('img') + img.src = this.curElementSrc + img.draggable = false + imgContainer.append(img) + this.previewerImage = img + previewerContainer.append(imgContainer) + // 操作栏 + let x = 0 + let y = 0 + let scaleSize = 1 + let rotateSize = 0 + const menuContainer = document.createElement('div') + menuContainer.classList.add('image-menu') + const zoomIn = document.createElement('i') + zoomIn.classList.add('zoom-in') + zoomIn.onclick = () => { + scaleSize += 0.1 + this._setPreviewerTransform(scaleSize, rotateSize, x, y) + } + menuContainer.append(zoomIn) + const zoomOut = document.createElement('i') + zoomOut.onclick = () => { + if (scaleSize - 0.1 <= 0.1) return + scaleSize -= 0.1 + this._setPreviewerTransform(scaleSize, rotateSize, x, y) + } + zoomOut.classList.add('zoom-out') + menuContainer.append(zoomOut) + const rotate = document.createElement('i') + rotate.classList.add('rotate') + rotate.onclick = () => { + rotateSize += 1 + this._setPreviewerTransform(scaleSize, rotateSize, x, y) + } + menuContainer.append(rotate) + const originalSize = document.createElement('i') + originalSize.classList.add('original-size') + originalSize.onclick = () => { + x = 0 + y = 0 + scaleSize = 1 + rotateSize = 0 + this._setPreviewerTransform(scaleSize, rotateSize, x, y) + } + menuContainer.append(originalSize) + const imageDownload = document.createElement('i') + imageDownload.classList.add('image-download') + imageDownload.onclick = () => { + const { mime } = this.previewerDrawOption + downloadFile(img.src, `${this.curElement?.id}.${mime || 'png'}`) + } + menuContainer.append(imageDownload) + previewerContainer.append(menuContainer) + this.previewerContainer = previewerContainer + document.body.append(previewerContainer) + // 拖拽调整位置 + let startX = 0 + let startY = 0 + let isAllowDrag = false + img.onmousedown = (evt) => { + isAllowDrag = true + startX = evt.x + startY = evt.y + previewerContainer.style.cursor = 'move' + } + previewerContainer.onmousemove = (evt: MouseEvent) => { + if (!isAllowDrag) return + x += (evt.x - startX) + y += (evt.y - startY) + startX = evt.x + startY = evt.y + this._setPreviewerTransform(scaleSize, rotateSize, x, y) + } + previewerContainer.onmouseup = () => { + isAllowDrag = false + previewerContainer.style.cursor = 'auto' + } + previewerContainer.onwheel = (evt) => { + evt.preventDefault() + if (evt.deltaY < 0) { + // 放大 + scaleSize += 0.1 + } else { + // 缩小 + if (scaleSize - 0.1 <= 0.1) return + scaleSize -= 0.1 + } + this._setPreviewerTransform(scaleSize, rotateSize, x, y) + } + } + + 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)` + } + + private _clearPreviewer() { + this.previewerContainer?.remove() + this.previewerContainer = null + document.body.style.overflow = 'auto' + } + + public drawResizer(element: IElement, position: IElementPosition, options: IPreviewerDrawOption = {}) { + this.previewerDrawOption = options + const { scale } = this.options + const { coordinate: { leftTop: [left, top] } } = position + const elementWidth = element.width! * scale + const elementHeight = element.height! * scale + const height = this.draw.getHeight() + const pageGap = this.draw.getPageGap() + const handleSize = this.options.resizerSize + const preY = this.draw.getPageNo() * (height + pageGap) + // 边框 + this.resizerSelection.style.left = `${left}px` + this.resizerSelection.style.top = `${top + preY}px` + this.resizerSelection.style.width = `${elementWidth}px` + 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 + ? elementWidth / 2 + : elementWidth - handleSize + 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` + this.resizerHandleList[i].style.top = `${top}px` + } + this.resizerSelection.style.display = 'block' + this.curElement = element + this.curElementSrc = element[options.srcKey || 'value'] || '' + this.curPosition = position + this.width = this.curElement.width! * scale + this.height = this.curElement.height! * scale + } + + public clearResizer() { + this.resizerSelection.style.display = 'none' + } + +} \ No newline at end of file diff --git a/src/editor/core/event/CanvasEvent.ts b/src/editor/core/event/CanvasEvent.ts index 091f51f..1949fb3 100644 --- a/src/editor/core/event/CanvasEvent.ts +++ b/src/editor/core/event/CanvasEvent.ts @@ -3,14 +3,13 @@ import { ZERO } from '../../dataset/constant/Common' import { EDITOR_ELEMENT_COPY_ATTR } from '../../dataset/constant/Element' import { ElementStyleKey } from '../../dataset/enum/ElementStyle' import { MouseEventButton } from '../../dataset/enum/Event' -import { KeyMap } from '../../dataset/enum/Keymap' +import { KeyMap } from '../../dataset/enum/KeyMap' import { IElement } from '../../interface/Element' import { ICurrentPosition } from '../../interface/Position' import { writeElementList } from '../../utils/clipboard' import { Cursor } from '../cursor/Cursor' import { Draw } from '../draw/Draw' import { HyperlinkParticle } from '../draw/particle/HyperlinkParticle' -import { ImageParticle } from '../draw/particle/ImageParticle' import { TableTool } from '../draw/particle/table/TableTool' import { HistoryManager } from '../history/HistoryManager' import { Listener } from '../listener/Listener' @@ -20,6 +19,7 @@ import { LETTER_REG, NUMBER_LIKE_REG } from '../../dataset/constant/Regular' import { Control } from '../draw/control/Control' import { CheckboxControl } from '../draw/control/checkbox/CheckboxControl' import { splitText } from '../../utils' +import { Previewer } from '../draw/particle/previewer/Previewer' export class CanvasEvent { @@ -34,7 +34,7 @@ export class CanvasEvent { private range: RangeManager private cursor: Cursor | null private historyManager: HistoryManager - private imageParticle: ImageParticle + private previewer: Previewer private tableTool: TableTool private hyperlinkParticle: HyperlinkParticle private listener: Listener @@ -52,7 +52,7 @@ export class CanvasEvent { this.position = this.draw.getPosition() this.range = this.draw.getRange() this.historyManager = this.draw.getHistoryManager() - this.imageParticle = this.draw.getImageParticle() + this.previewer = this.draw.getPreviewer() this.tableTool = this.draw.getTableTool() this.hyperlinkParticle = this.draw.getHyperlinkParticle() this.listener = this.draw.getListener() @@ -258,10 +258,16 @@ export class CanvasEvent { isComputeRowList: false }) } - // 图片尺寸拖拽组件 - this.imageParticle.clearResizer() + // 预览工具组件 + this.previewer.clearResizer() if (isDirectHitImage && !isReadonly) { - this.imageParticle.drawResizer(curElement, positionList[curIndex]) + this.previewer.drawResizer(curElement, positionList[curIndex], + curElement.type === ElementType.LATEX + ? { + mime: 'svg', + srcKey: 'laTexSVG' + } + : {}) } // 表格工具组件 this.tableTool.dispose() diff --git a/src/editor/core/event/GlobalEvent.ts b/src/editor/core/event/GlobalEvent.ts index c4077e9..eacda88 100644 --- a/src/editor/core/event/GlobalEvent.ts +++ b/src/editor/core/event/GlobalEvent.ts @@ -5,7 +5,7 @@ import { Cursor } from '../cursor/Cursor' import { Control } from '../draw/control/Control' import { Draw } from '../draw/Draw' import { HyperlinkParticle } from '../draw/particle/HyperlinkParticle' -import { ImageParticle } from '../draw/particle/ImageParticle' +import { Previewer } from '../draw/particle/previewer/Previewer' import { TableTool } from '../draw/particle/table/TableTool' import { RangeManager } from '../range/RangeManager' import { CanvasEvent } from './CanvasEvent' @@ -18,7 +18,7 @@ export class GlobalEvent { private cursor: Cursor | null private canvasEvent: CanvasEvent private range: RangeManager - private imageParticle: ImageParticle + private previewer: Previewer private tableTool: TableTool private hyperlinkParticle: HyperlinkParticle private control: Control @@ -30,7 +30,7 @@ export class GlobalEvent { this.canvasEvent = canvasEvent this.cursor = null this.range = draw.getRange() - this.imageParticle = draw.getImageParticle() + this.previewer = draw.getPreviewer() this.tableTool = draw.getTableTool() this.hyperlinkParticle = draw.getHyperlinkParticle() this.control = draw.getControl() @@ -74,7 +74,7 @@ export class GlobalEvent { this.cursor.recoveryCursor() this.range.recoveryRangeStyle() this.range.setRange(-1, -1) - this.imageParticle.clearResizer() + this.previewer.clearResizer() this.tableTool.dispose() this.hyperlinkParticle.clearHyperlinkPopup() this.control.destroyControl() diff --git a/src/editor/interface/Draw.ts b/src/editor/interface/Draw.ts index 4583173..082ecc3 100644 --- a/src/editor/interface/Draw.ts +++ b/src/editor/interface/Draw.ts @@ -14,13 +14,6 @@ export interface IDrawImagePayload { value: string; } -export interface IImageParticleCreateResult { - resizerSelection: HTMLDivElement; - resizerHandleList: HTMLDivElement[]; - resizerImageContainer: HTMLDivElement; - resizerImage: HTMLImageElement; -} - export interface IDrawRowPayload { positionList: IElementPosition[]; rowList: IRow[]; diff --git a/src/editor/interface/Previewer.ts b/src/editor/interface/Previewer.ts new file mode 100644 index 0000000..af69bc3 --- /dev/null +++ b/src/editor/interface/Previewer.ts @@ -0,0 +1,13 @@ +import { IElement } from './Element' + +export interface IPreviewerCreateResult { + resizerSelection: HTMLDivElement; + resizerHandleList: HTMLDivElement[]; + resizerImageContainer: HTMLDivElement; + resizerImage: HTMLImageElement; +} + +export interface IPreviewerDrawOption { + mime?: 'png' | 'jpg' | 'jpeg' | 'svg'; + srcKey?: keyof Pick; +} \ No newline at end of file diff --git a/src/editor/utils/element.ts b/src/editor/utils/element.ts index 996003c..cc53a44 100644 --- a/src/editor/utils/element.ts +++ b/src/editor/utils/element.ts @@ -1,5 +1,6 @@ import { deepClone, getUUID, splitText } from '.' import { ElementType, IEditorOption, IElement } from '..' +import { LaTexParticle } from '../core/draw/particle/latex/LaTexParticle' import { defaultCheckboxOption } from '../dataset/constant/Checkbox' import { ZERO } from '../dataset/constant/Common' import { defaultControlOption } from '../dataset/constant/Control' @@ -214,6 +215,13 @@ export function formatElementList(elementList: IElement[], options: IFormatEleme if (el.type === ElementType.IMAGE) { el.id = getUUID() } + if (el.type === ElementType.LATEX) { + const { svg, width, height } = LaTexParticle.convertLaTextToSVG(el.value) + el.width = el.width || width + el.height = el.height || height + el.laTexSVG = svg + el.id = getUUID() + } i++ } }