feat:add page zoom

pr675
黄云飞 4 years ago
parent 26b165a8e3
commit 747e9ba68d

@ -121,12 +121,12 @@
<span>页面:<span class="page-no">1</span>/<span class="page-size">1</span></span>
</div>
<div>
<div>
<i class="page-minus"></i>
<div class="page-scale-minus">
<i></i>
</div>
<span>100%</span>
<div>
<i class="page-add"></i>
<span class="page-scale-percentage">100%</span>
<div class="page-scale-add">
<i></i>
</div>
</div>
</div>

Before

Width:  |  Height:  |  Size: 165 B

After

Width:  |  Height:  |  Size: 165 B

Before

Width:  |  Height:  |  Size: 148 B

After

Width:  |  Height:  |  Size: 148 B

@ -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()
}
}

@ -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)
}
}
}

@ -7,6 +7,7 @@ import { CursorAgent } from "./CursorAgent"
export class Cursor {
private draw: Draw
private container: HTMLDivElement
private options: Required<IEditorOption>
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`

@ -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++

@ -3,15 +3,20 @@ import { Draw } from "../Draw"
export class Margin {
private draw: Draw
private options: Required<IEditorOption>
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()

@ -3,17 +3,22 @@ import { Draw } from "../Draw"
export class PageNumber {
private draw: Draw
private options: Required<IEditorOption>
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()
}

@ -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)

@ -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
}
}

@ -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,

@ -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;

@ -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

@ -231,6 +231,14 @@ window.onload = function () {
console.log('print')
instance.command.executePrint()
}
document.querySelector<HTMLDivElement>('.page-scale-minus')!.onclick = function () {
console.log('page-scale-minus')
instance.command.executePageScaleMinus()
}
document.querySelector<HTMLDivElement>('.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<HTMLSpanElement>('.page-no')!.innerText = `${payload + 1}`
}
instance.listener.pageScaleChange = function (payload) {
document.querySelector<HTMLSpanElement>('.page-scale-percentage')!.innerText = `${Math.floor(payload * 100)}%`
}
}

@ -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;
}
Loading…
Cancel
Save