From 747e9ba68d84b6d08441910a16052c593e1b9dbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E4=BA=91=E9=A3=9E?= Date: Mon, 29 Nov 2021 20:47:04 +0800 Subject: [PATCH] feat:add page zoom --- index.html | 10 +- .../{page-add.svg => page-scale-add.svg} | 0 .../{page-minus.svg => page-scale-minus.svg} | 0 src/editor/core/command/Command.ts | 13 ++ src/editor/core/command/CommandAdapt.ts | 16 +++ src/editor/core/cursor/Cursor.ts | 8 +- src/editor/core/draw/Draw.ts | 117 +++++++++++++----- src/editor/core/draw/frame/Margin.ts | 9 +- src/editor/core/draw/frame/PageNumber.ts | 9 +- .../core/draw/particle/ImageParticle.ts | 43 ++++--- src/editor/core/listener/Listener.ts | 3 + src/editor/index.ts | 3 + src/editor/interface/Editor.ts | 3 + src/editor/interface/Listener.ts | 2 + src/main.ts | 12 ++ src/style.css | 11 +- 16 files changed, 198 insertions(+), 61 deletions(-) rename src/assets/images/{page-add.svg => page-scale-add.svg} (100%) rename src/assets/images/{page-minus.svg => page-scale-minus.svg} (100%) diff --git a/index.html b/index.html index 059867e..8b89f41 100644 --- a/index.html +++ b/index.html @@ -121,12 +121,12 @@ 页面:1/1
-
- +
+
- 100% -
- + 100% +
+
diff --git a/src/assets/images/page-add.svg b/src/assets/images/page-scale-add.svg similarity index 100% rename from src/assets/images/page-add.svg rename to src/assets/images/page-scale-add.svg diff --git a/src/assets/images/page-minus.svg b/src/assets/images/page-scale-minus.svg similarity index 100% rename from src/assets/images/page-minus.svg rename to src/assets/images/page-scale-minus.svg diff --git a/src/editor/core/command/Command.ts b/src/editor/core/command/Command.ts index acc8535..88bcfb5 100644 --- a/src/editor/core/command/Command.ts +++ b/src/editor/core/command/Command.ts @@ -24,6 +24,8 @@ export class Command { private static image: Function private static search: Function private static print: Function + private static pageScaleMinus: Function + private static pageScaleAdd: Function constructor(adapt: CommandAdapt) { Command.undo = adapt.undo.bind(adapt) @@ -46,6 +48,8 @@ export class Command { Command.image = adapt.image.bind(adapt) Command.search = adapt.search.bind(adapt) Command.print = adapt.print.bind(adapt) + Command.pageScaleMinus = adapt.pageScaleMinus.bind(adapt) + Command.pageScaleAdd = adapt.pageScaleAdd.bind(adapt) } // 撤销、重做、格式刷、清除格式 @@ -131,4 +135,13 @@ export class Command { return Command.print() } + // 页面缩放 + public executePageScaleMinus() { + return Command.pageScaleMinus() + } + + public executePageScaleAdd() { + return Command.pageScaleAdd() + } + } \ No newline at end of file diff --git a/src/editor/core/command/CommandAdapt.ts b/src/editor/core/command/CommandAdapt.ts index 23c013d..45c6102 100644 --- a/src/editor/core/command/CommandAdapt.ts +++ b/src/editor/core/command/CommandAdapt.ts @@ -262,4 +262,20 @@ export class CommandAdapt { return printImageBase64(this.draw.getDataURL(), width, height) } + public pageScaleMinus() { + const { scale } = this.options + const nextScale = scale * 10 - 1 + if (nextScale >= 5) { + this.draw.setPageScale(nextScale / 10) + } + } + + public pageScaleAdd() { + const { scale } = this.options + const nextScale = scale * 10 + 1 + if (nextScale <= 30) { + this.draw.setPageScale(nextScale / 10) + } + } + } \ No newline at end of file diff --git a/src/editor/core/cursor/Cursor.ts b/src/editor/core/cursor/Cursor.ts index 457fe6d..058c3e3 100644 --- a/src/editor/core/cursor/Cursor.ts +++ b/src/editor/core/cursor/Cursor.ts @@ -7,6 +7,7 @@ import { CursorAgent } from "./CursorAgent" export class Cursor { + private draw: Draw private container: HTMLDivElement private options: Required private position: Position @@ -14,6 +15,7 @@ export class Cursor { private cursorAgent: CursorAgent constructor(draw: Draw, canvasEvent: CanvasEvent) { + this.draw = draw this.container = draw.getContainer() this.position = draw.getPosition() this.options = draw.getOptions() @@ -36,8 +38,10 @@ export class Cursor { const cursorPosition = this.position.getCursorPosition() if (!cursorPosition) return // 设置光标代理 + const { scale } = this.options + const height = this.draw.getHeight() + const pageGap = this.draw.getPageGap() const { metrics, coordinate: { leftTop, rightTop }, ascent, pageNo } = cursorPosition - const { height, pageGap } = this.options const preY = pageNo * (height + pageGap) // 增加1/4字体大小 const offsetHeight = metrics.height / 4 @@ -52,7 +56,7 @@ export class Cursor { const cursorTop = (leftTop[1] + ascent) + descent - (cursorHeight - offsetHeight) + preY const curosrleft = rightTop[0] agentCursorDom.style.left = `${curosrleft}px` - agentCursorDom.style.top = `${cursorTop + cursorHeight - CURSOR_AGENT_HEIGHT}px` + agentCursorDom.style.top = `${cursorTop + cursorHeight - CURSOR_AGENT_HEIGHT * scale}px` // 模拟光标显示 this.cursorDom.style.left = `${curosrleft}px` this.cursorDom.style.top = `${cursorTop}px` diff --git a/src/editor/core/draw/Draw.ts b/src/editor/core/draw/Draw.ts index 341ebfa..8580bc8 100644 --- a/src/editor/core/draw/Draw.ts +++ b/src/editor/core/draw/Draw.ts @@ -49,7 +49,6 @@ export class Draw { private textParticle: TextParticle private pageNumber: PageNumber - private innerWidth: number private rowList: IRow[] private painterStyle: IElementStyle | null private searchMatchList: number[][] | null @@ -93,8 +92,6 @@ export class Draw { const globalEvent = new GlobalEvent(this, canvasEvent) globalEvent.register() - const { width, margins } = options - this.innerWidth = width - margins[1] - margins[3] this.rowList = [] this.painterStyle = null this.searchMatchList = null @@ -104,6 +101,40 @@ export class Draw { this.render({ isSetCursor: false }) } + public getWidth(): number { + return Math.floor(this.options.width * this.options.scale) + } + + public getHeight(): number { + return Math.floor(this.options.height * this.options.scale) + } + + public getInnerWidth(): number { + const width = this.getWidth() + const margins = this.getMargins() + return width - margins[1] - margins[3] + } + + public getMargins(): number[] { + return this.options.margins.map(m => m * this.options.scale) + } + + public getPageGap(): number { + return this.options.pageGap * this.options.scale + } + + public getPageNumberBottom(): number { + return this.options.pageNumberBottom * this.options.scale + } + + public getMarginIndicatorSize(): number { + return this.options.marginIndicatorSize * this.options.scale + } + + public getDefaultBasicRowMarginHeight(): number { + return this.options.defaultBasicRowMarginHeight * this.options.scale + } + public getContainer(): HTMLDivElement { return this.container } @@ -222,9 +253,27 @@ export class Draw { }) } + public setPageScale(payload: number) { + this.options.scale = payload + const width = this.getWidth() + const height = this.getHeight() + this.container.style.width = `${width}px` + this.pageList.forEach(p => { + p.width = width + p.height = height + p.style.width = `${width}px` + p.style.height = `${height}px` + p.style.marginBottom = `${this.getPageGap()}px` + }) + this.render({ isSubmitHistory: false, isSetCursor: false }) + if (this.listener.pageScaleChange) { + this.listener.pageScaleChange(payload) + } + } + private _createPageContainer(): HTMLDivElement { // 容器宽度需跟随纸张宽度 - this.container.style.width = `${this.options.width}px` + this.container.style.width = `${this.getWidth()}px` const pageContainer = document.createElement('div') pageContainer.classList.add('page-container') this.container.append(pageContainer) @@ -232,16 +281,18 @@ export class Draw { } private _createPage(pageNo: number) { + const width = this.getWidth() + const height = this.getHeight() const canvas = document.createElement('canvas') - canvas.style.width = `${this.options.width}px` - canvas.style.height = `${this.options.height}px` - canvas.style.marginBottom = `${this.options.pageGap}px` + canvas.style.width = `${width}px` + canvas.style.height = `${height}px` + canvas.style.marginBottom = `${this.getPageGap()}px` canvas.setAttribute('data-index', String(pageNo)) this.pageContainer.append(canvas) // 调整分辨率 const dpr = window.devicePixelRatio - canvas.width = parseInt(canvas.style.width) * dpr - canvas.height = parseInt(canvas.style.height) * dpr + canvas.width = width * dpr + canvas.height = height * dpr canvas.style.cursor = 'text' const ctx = canvas.getContext('2d')! ctx.scale(dpr, dpr) @@ -250,16 +301,17 @@ export class Draw { this.ctxList.push(ctx) } - private _getFont(el: IElement): string { + private _getFont(el: IElement, scale: number = 1): string { const { defaultSize, defaultFont } = this.options - return `${el.italic ? 'italic ' : ''}${el.bold ? 'bold ' : ''}${el.size || defaultSize}px ${el.font || defaultFont}` + return `${el.italic ? 'italic ' : ''}${el.bold ? 'bold ' : ''}${(el.size || defaultSize) * scale}px ${el.font || defaultFont}` } private _computeRowList() { - const { defaultSize, defaultRowMargin, defaultBasicRowMarginHeight } = this.options + const { defaultSize, defaultRowMargin, scale } = this.options + const innerWidth = this.getInnerWidth() + const defaultBasicRowMarginHeight = this.getDefaultBasicRowMarginHeight() const canvas = document.createElement('canvas') const ctx = canvas.getContext('2d') as CanvasRenderingContext2D - const innerWidth = this.innerWidth const rowList: IRow[] = [] if (this.elementList.length) { rowList.push({ @@ -281,24 +333,30 @@ export class Draw { boundingBoxDescent: 0 } if (element.type === ElementType.IMAGE) { - metrics.height = element.height! + const elementWidth = element.width! * scale + const elementHeight = element.height! * scale // 图片超出尺寸后自适应 - if (curRow.width + element.width! > innerWidth) { + if (curRow.width + elementWidth > innerWidth) { // 计算剩余大小 const surplusWidth = innerWidth - curRow.width element.width = surplusWidth - element.height = element.height! * surplusWidth / element.width + element.height = elementHeight * surplusWidth / elementWidth + metrics.width = element.width + metrics.height = element.height + metrics.boundingBoxDescent = element.height + } else { + metrics.width = elementWidth + metrics.height = elementHeight + metrics.boundingBoxDescent = elementHeight } - metrics.width = element.width! metrics.boundingBoxAscent = 0 - metrics.boundingBoxDescent = element.height! } else { - metrics.height = element.size || this.options.defaultSize + metrics.height = (element.size || this.options.defaultSize) * scale ctx.font = this._getFont(element) const fontMetrics = this.textParticle.measureText(ctx, element) - metrics.width = fontMetrics.width - metrics.boundingBoxAscent = element.value === ZERO ? defaultSize : fontMetrics.actualBoundingBoxAscent - metrics.boundingBoxDescent = fontMetrics.actualBoundingBoxDescent + metrics.width = fontMetrics.width * scale + metrics.boundingBoxAscent = (element.value === ZERO ? defaultSize : fontMetrics.actualBoundingBoxAscent) * scale + metrics.boundingBoxDescent = fontMetrics.actualBoundingBoxDescent * scale } const ascent = metrics.boundingBoxAscent + rowMargin const descent = metrics.boundingBoxDescent + rowMargin @@ -306,7 +364,7 @@ export class Draw { const rowElement: IRowElement = { ...element, metrics, - style: ctx.font + style: this._getFont(element, scale) } // 超过限定宽度 if (curRow.width + metrics.width > innerWidth || (i !== 0 && element.value === ZERO)) { @@ -322,7 +380,7 @@ export class Draw { if (curRow.height < height) { curRow.height = height if (element.type === ElementType.IMAGE) { - curRow.ascent = element.height! + curRow.ascent = metrics.height } else { curRow.ascent = ascent } @@ -334,7 +392,9 @@ export class Draw { } private _drawElement(positionList: IElementPosition[], rowList: IRow[], pageNo: number) { - const { margins, width, height } = this.options + const width = this.getWidth() + const height = this.getHeight() + const margins = this.getMargins() const ctx = this.ctxList[pageNo] ctx.clearRect(0, 0, width, height) // 绘制背景 @@ -361,7 +421,7 @@ export class Draw { const element = curRow.elementList[j] const metrics = element.metrics const offsetY = element.type === ElementType.IMAGE - ? curRow.ascent - element.height! + ? curRow.ascent - metrics.height : curRow.ascent const positionItem: IElementPosition = { pageNo, @@ -430,6 +490,7 @@ export class Draw { isSetCursor = true, isComputeRowList = true } = payload || {} + const height = this.getHeight() // 计算行信息 if (isComputeRowList) { this._computeRowList() @@ -439,14 +500,14 @@ export class Draw { this.position.setPositionList([]) const positionList = this.position.getPositionList() // 按页渲染 - const { margins } = this.options + const margins = this.getMargins() const marginHeight = margins[0] + margins[2] let pageHeight = marginHeight let pageNo = 0 let pageRowList: IRow[][] = [[]] for (let i = 0; i < this.rowList.length; i++) { const row = this.rowList[i] - if (row.height + pageHeight > this.options.height) { + if (row.height + pageHeight > height) { pageHeight = marginHeight + row.height pageRowList.push([row]) pageNo++ diff --git a/src/editor/core/draw/frame/Margin.ts b/src/editor/core/draw/frame/Margin.ts index c361fbc..ff2953c 100644 --- a/src/editor/core/draw/frame/Margin.ts +++ b/src/editor/core/draw/frame/Margin.ts @@ -3,15 +3,20 @@ import { Draw } from "../Draw" export class Margin { + private draw: Draw private options: Required constructor(draw: Draw) { + this.draw = draw this.options = draw.getOptions() } public render(ctx: CanvasRenderingContext2D) { - const { width, height } = this.options - const { marginIndicatorColor, marginIndicatorSize, margins } = this.options + const { marginIndicatorColor } = this.options + const width = this.draw.getWidth() + const height = this.draw.getHeight() + const margins = this.draw.getMargins() + const marginIndicatorSize = this.draw.getMarginIndicatorSize() ctx.save() ctx.strokeStyle = marginIndicatorColor ctx.beginPath() diff --git a/src/editor/core/draw/frame/PageNumber.ts b/src/editor/core/draw/frame/PageNumber.ts index 4cda3f7..d6ca451 100644 --- a/src/editor/core/draw/frame/PageNumber.ts +++ b/src/editor/core/draw/frame/PageNumber.ts @@ -3,17 +3,22 @@ import { Draw } from "../Draw" export class PageNumber { + private draw: Draw private options: Required constructor(draw: Draw) { + this.draw = draw this.options = draw.getOptions() } public render(ctx: CanvasRenderingContext2D, pageNo: number) { - const { pageNumberBottom, width, height } = this.options + const { pageNumberSize, pageNumberFont, scale } = this.options + const width = this.draw.getWidth() + const height = this.draw.getHeight() + const pageNumberBottom = this.draw.getPageNumberBottom() ctx.save() ctx.fillStyle = '#00000' - ctx.font = '12px' + ctx.font = `${pageNumberSize * scale}px ${pageNumberFont}` ctx.fillText(`${pageNo + 1}`, width / 2, height - pageNumberBottom) ctx.restore() } diff --git a/src/editor/core/draw/particle/ImageParticle.ts b/src/editor/core/draw/particle/ImageParticle.ts index 39f939b..3225684 100644 --- a/src/editor/core/draw/particle/ImageParticle.ts +++ b/src/editor/core/draw/particle/ImageParticle.ts @@ -72,7 +72,9 @@ export class ImageParticle { private _handleMousedown(evt: MouseEvent) { this.canvas = this.draw.getPage() if (!this.curPosition || !this.curElement) return - const { height, pageGap } = this.options + 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 @@ -88,8 +90,8 @@ export class ImageParticle { 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}px` - this.resizerImage.style.height = `${this.curElement.height}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) @@ -114,6 +116,7 @@ export class ImageParticle { private _mousemove(evt: MouseEvent) { if (!this.curElement) return + const { scale } = this.options let dx = 0 let dy = 0 switch (this.curHandleIndex) { @@ -148,8 +151,8 @@ export class ImageParticle { } this.width = this.curElement.width! + dx this.height = this.curElement.height! + dy - this.resizerImage.style.width = `${this.width}px` - this.resizerImage.style.height = `${this.height}px` + this.resizerImage.style.width = `${this.width * scale}px` + this.resizerImage.style.height = `${this.height * scale}px` evt.preventDefault() } @@ -158,36 +161,39 @@ export class ImageParticle { } public drawResizer(element: IElement, position: IElementPosition) { + const { scale } = this.options const { coordinate: { leftTop: [left, top] } } = position - const width = element.width! - const height = element.height! + 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() * (this.options.height + this.options.pageGap) + const preY = this.draw.getPageNo() * (height + pageGap) // 边框 this.resizerSelection.style.left = `${left}px` this.resizerSelection.style.top = `${top + preY}px` - this.resizerSelection.style.width = `${element.width}px` - this.resizerSelection.style.height = `${element.height}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 - ? width / 2 - : width - handleSize + ? elementWidth / 2 + : elementWidth - handleSize const top = i === 0 || i === 1 || i === 2 ? -handleSize : i === 3 || i === 7 - ? height / 2 - handleSize - : height - handleSize + ? 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! - this.height = this.curElement.height! + this.width = this.curElement.width! * scale + this.height = this.curElement.height! * scale } public clearResizer() { @@ -195,8 +201,9 @@ export class ImageParticle { } public render(ctx: CanvasRenderingContext2D, element: IElement, x: number, y: number) { - const width = element.width! - const height = element.height! + const { scale } = this.options + const width = element.width! * scale + const height = element.height! * scale if (this.imageCache.has(element.id!)) { const img = this.imageCache.get(element.id!)! ctx.drawImage(img, x, y, width, height) diff --git a/src/editor/core/listener/Listener.ts b/src/editor/core/listener/Listener.ts index a4bb989..23d257d 100644 --- a/src/editor/core/listener/Listener.ts +++ b/src/editor/core/listener/Listener.ts @@ -1,5 +1,6 @@ import { IIntersectionPageNoChange, + IPageScaleChange, IPageSizeChange, IRangeStyleChange, IVisiblePageNoListChange @@ -11,12 +12,14 @@ export class Listener { public visiblePageNoListChange: IVisiblePageNoListChange | null public intersectionPageNoChange: IIntersectionPageNoChange | null public pageSizeChange: IPageSizeChange | null + public pageScaleChange: IPageScaleChange | null constructor() { this.rangeStyleChange = null this.visiblePageNoListChange = null this.intersectionPageNoChange = null this.pageSizeChange = null + this.pageScaleChange = null } } \ No newline at end of file diff --git a/src/editor/index.ts b/src/editor/index.ts index 001fd6d..a748ab6 100644 --- a/src/editor/index.ts +++ b/src/editor/index.ts @@ -24,8 +24,11 @@ export default class Editor { defaultBasicRowMarginHeight: 8, width: 794, height: 1123, + scale: 1, pageGap: 20, pageNumberBottom: 60, + pageNumberSize: 12, + pageNumberFont: 'Yahei', underlineColor: '#000000', strikeoutColor: '#FF0000', rangeAlpha: 0.6, diff --git a/src/editor/interface/Editor.ts b/src/editor/interface/Editor.ts index e02b47c..872d3bf 100644 --- a/src/editor/interface/Editor.ts +++ b/src/editor/interface/Editor.ts @@ -6,8 +6,11 @@ export interface IEditorOption { defaultRowMargin?: number; width?: number; height?: number; + scale?: number; pageGap?: number; pageNumberBottom?: number; + pageNumberSize?: number; + pageNumberFont?: string; underlineColor?: string; strikeoutColor?: string; rangeColor?: string; diff --git a/src/editor/interface/Listener.ts b/src/editor/interface/Listener.ts index f0ca881..8f16c30 100644 --- a/src/editor/interface/Listener.ts +++ b/src/editor/interface/Listener.ts @@ -22,3 +22,5 @@ export type IVisiblePageNoListChange = (payload: number[]) => void export type IIntersectionPageNoChange = (payload: number) => void export type IPageSizeChange = (payload: number) => void + +export type IPageScaleChange = (payload: number) => void diff --git a/src/main.ts b/src/main.ts index ca5fbc9..e3ee5f0 100644 --- a/src/main.ts +++ b/src/main.ts @@ -231,6 +231,14 @@ window.onload = function () { console.log('print') instance.command.executePrint() } + document.querySelector('.page-scale-minus')!.onclick = function () { + console.log('page-scale-minus') + instance.command.executePageScaleMinus() + } + document.querySelector('.page-scale-add')!.onclick = function () { + console.log('page-scale-add') + instance.command.executePageScaleAdd() + } // 内部事件监听 instance.listener.rangeStyleChange = function (payload) { @@ -295,4 +303,8 @@ window.onload = function () { document.querySelector('.page-no')!.innerText = `${payload + 1}` } + instance.listener.pageScaleChange = function (payload) { + document.querySelector('.page-scale-percentage')!.innerText = `${Math.floor(payload * 100)}%` + } + } \ No newline at end of file diff --git a/src/style.css b/src/style.css index 047d19a..328832e 100644 --- a/src/style.css +++ b/src/style.css @@ -395,11 +395,14 @@ ul { cursor: pointer; } -.footer>div:last-child .page-minus { - background-image: url('./assets/images/page-minus.svg'); +.footer .page-scale-minus i { + background-image: url('./assets/images/page-scale-minus.svg'); +} +.footer .page-scale-add i { + background-image: url('./assets/images/page-scale-add.svg'); } -.footer>div:last-child .page-add { - background-image: url('./assets/images/page-add.svg'); +.footer .page-scale-percentage { + user-select: none; } \ No newline at end of file