feat:增加分页功能
This commit is contained in:
+1
-3
@@ -114,9 +114,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="editor" editor-component="main">
|
<div class="editor" editor-component="main"></div>
|
||||||
<canvas style="width: 794px;height: 1123px;"></canvas>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<script type="module" src="/src/main.ts"></script>
|
<script type="module" src="/src/main.ts"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { ZERO } from "../../dataset/constant/Common"
|
import { ZERO } from "../../dataset/constant/Common"
|
||||||
|
import { EDITOR_ELEMENT_STYLE } from "../../dataset/constant/Element"
|
||||||
import { ElementType } from "../../dataset/enum/Element"
|
import { ElementType } from "../../dataset/enum/Element"
|
||||||
import { ElementStyleKey } from "../../dataset/enum/ElementStyle"
|
import { ElementStyleKey } from "../../dataset/enum/ElementStyle"
|
||||||
import { RowFlex } from "../../dataset/enum/Row"
|
import { RowFlex } from "../../dataset/enum/Row"
|
||||||
@@ -41,7 +42,7 @@ export class CommandAdapt {
|
|||||||
if (!selection) return
|
if (!selection) return
|
||||||
const painterStyle: IElementStyle = {}
|
const painterStyle: IElementStyle = {}
|
||||||
selection.forEach(s => {
|
selection.forEach(s => {
|
||||||
const painterStyleKeys = ['bold', 'color', 'highlight', 'font', 'size', 'italic', 'underline', 'strikeout']
|
const painterStyleKeys = EDITOR_ELEMENT_STYLE
|
||||||
painterStyleKeys.forEach(p => {
|
painterStyleKeys.forEach(p => {
|
||||||
const key = p as keyof typeof ElementStyleKey
|
const key = p as keyof typeof ElementStyleKey
|
||||||
if (painterStyle[key] === undefined) {
|
if (painterStyle[key] === undefined) {
|
||||||
@@ -168,6 +169,7 @@ export class CommandAdapt {
|
|||||||
public rowFlex(payload: RowFlex) {
|
public rowFlex(payload: RowFlex) {
|
||||||
const { startIndex, endIndex } = this.range.getRange()
|
const { startIndex, endIndex } = this.range.getRange()
|
||||||
if (startIndex === 0 && endIndex === 0) return
|
if (startIndex === 0 && endIndex === 0) return
|
||||||
|
const pageNo = this.draw.getPageNo()
|
||||||
const positionList = this.position.getPositionList()
|
const positionList = this.position.getPositionList()
|
||||||
// 开始/结束行号
|
// 开始/结束行号
|
||||||
const startRowNo = positionList[startIndex].rowNo
|
const startRowNo = positionList[startIndex].rowNo
|
||||||
@@ -176,6 +178,7 @@ export class CommandAdapt {
|
|||||||
// 当前选区所在行
|
// 当前选区所在行
|
||||||
for (let p = 0; p < positionList.length; p++) {
|
for (let p = 0; p < positionList.length; p++) {
|
||||||
const postion = positionList[p]
|
const postion = positionList[p]
|
||||||
|
if (postion.pageNo !== pageNo) continue
|
||||||
if (postion.rowNo > endRowNo) break
|
if (postion.rowNo > endRowNo) break
|
||||||
if (postion.rowNo >= startRowNo && postion.rowNo <= endRowNo) {
|
if (postion.rowNo >= startRowNo && postion.rowNo <= endRowNo) {
|
||||||
elementList[p].rowFlex = payload
|
elementList[p].rowFlex = payload
|
||||||
@@ -190,6 +193,7 @@ export class CommandAdapt {
|
|||||||
public rowMargin(payload: number) {
|
public rowMargin(payload: number) {
|
||||||
const { startIndex, endIndex } = this.range.getRange()
|
const { startIndex, endIndex } = this.range.getRange()
|
||||||
if (startIndex === 0 && endIndex === 0) return
|
if (startIndex === 0 && endIndex === 0) return
|
||||||
|
const pageNo = this.draw.getPageNo()
|
||||||
const positionList = this.position.getPositionList()
|
const positionList = this.position.getPositionList()
|
||||||
// 开始/结束行号
|
// 开始/结束行号
|
||||||
const startRowNo = positionList[startIndex].rowNo
|
const startRowNo = positionList[startIndex].rowNo
|
||||||
@@ -198,6 +202,7 @@ export class CommandAdapt {
|
|||||||
// 当前选区所在行
|
// 当前选区所在行
|
||||||
for (let p = 0; p < positionList.length; p++) {
|
for (let p = 0; p < positionList.length; p++) {
|
||||||
const postion = positionList[p]
|
const postion = positionList[p]
|
||||||
|
if (postion.pageNo !== pageNo) continue
|
||||||
if (postion.rowNo > endRowNo) break
|
if (postion.rowNo > endRowNo) break
|
||||||
if (postion.rowNo >= startRowNo && postion.rowNo <= endRowNo) {
|
if (postion.rowNo >= startRowNo && postion.rowNo <= endRowNo) {
|
||||||
elementList[p].rowMargin = payload
|
elementList[p].rowMargin = payload
|
||||||
@@ -253,7 +258,8 @@ export class CommandAdapt {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public print() {
|
public print() {
|
||||||
return printImageBase64(this.draw.getDataURL())
|
const { width, height } = this.options
|
||||||
|
return printImageBase64(this.draw.getDataURL(), width, height)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,23 +1,27 @@
|
|||||||
import { CURSOR_AGENT_HEIGHT } from "../../dataset/constant/Cursor"
|
import { CURSOR_AGENT_HEIGHT } from "../../dataset/constant/Cursor"
|
||||||
|
import { IEditorOption } from "../../interface/Editor"
|
||||||
import { Draw } from "../draw/Draw"
|
import { Draw } from "../draw/Draw"
|
||||||
import { CanvasEvent } from "../event/CanvasEvent"
|
import { CanvasEvent } from "../event/CanvasEvent"
|
||||||
|
import { Position } from "../position/Position"
|
||||||
import { CursorAgent } from "./CursorAgent"
|
import { CursorAgent } from "./CursorAgent"
|
||||||
|
|
||||||
export class Cursor {
|
export class Cursor {
|
||||||
|
|
||||||
private canvas: HTMLCanvasElement
|
private container: HTMLDivElement
|
||||||
private draw: Draw
|
private options: Required<IEditorOption>
|
||||||
|
private position: Position
|
||||||
private cursorDom: HTMLDivElement
|
private cursorDom: HTMLDivElement
|
||||||
private cursorAgent: CursorAgent
|
private cursorAgent: CursorAgent
|
||||||
|
|
||||||
constructor(canvas: HTMLCanvasElement, draw: Draw, canvasEvent: CanvasEvent) {
|
constructor(draw: Draw, canvasEvent: CanvasEvent) {
|
||||||
this.canvas = canvas
|
this.container = draw.getContainer()
|
||||||
this.draw = draw
|
this.position = draw.getPosition()
|
||||||
|
this.options = draw.getOptions()
|
||||||
|
|
||||||
this.cursorDom = document.createElement('div')
|
this.cursorDom = document.createElement('div')
|
||||||
this.cursorDom.classList.add('cursor')
|
this.cursorDom.classList.add('cursor')
|
||||||
this.canvas.parentNode?.append(this.cursorDom)
|
this.container.append(this.cursorDom)
|
||||||
this.cursorAgent = new CursorAgent(canvas, canvasEvent)
|
this.cursorAgent = new CursorAgent(draw, canvasEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCursorDom(): HTMLDivElement {
|
public getCursorDom(): HTMLDivElement {
|
||||||
@@ -29,10 +33,12 @@ export class Cursor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public drawCursor() {
|
public drawCursor() {
|
||||||
const cursorPosition = this.draw.getPosition().getCursorPosition()
|
const cursorPosition = this.position.getCursorPosition()
|
||||||
if (!cursorPosition) return
|
if (!cursorPosition) return
|
||||||
// 设置光标代理
|
// 设置光标代理
|
||||||
const { metrics, coordinate: { leftTop, rightTop }, ascent } = cursorPosition
|
const { metrics, coordinate: { leftTop, rightTop }, ascent, pageNo } = cursorPosition
|
||||||
|
const { height, pageGap } = this.options
|
||||||
|
const preY = pageNo * (height + pageGap)
|
||||||
// 增加1/4字体大小
|
// 增加1/4字体大小
|
||||||
const offsetHeight = metrics.height / 4
|
const offsetHeight = metrics.height / 4
|
||||||
const cursorHeight = metrics.height + offsetHeight * 2
|
const cursorHeight = metrics.height + offsetHeight * 2
|
||||||
@@ -43,7 +49,7 @@ export class Cursor {
|
|||||||
})
|
})
|
||||||
// fillText位置 + 文字基线到底部距离 - 模拟光标偏移量
|
// fillText位置 + 文字基线到底部距离 - 模拟光标偏移量
|
||||||
const descent = metrics.boundingBoxDescent < 0 ? 0 : metrics.boundingBoxDescent
|
const descent = metrics.boundingBoxDescent < 0 ? 0 : metrics.boundingBoxDescent
|
||||||
const cursorTop = (leftTop[1] + ascent) + descent - (cursorHeight - offsetHeight)
|
const cursorTop = (leftTop[1] + ascent) + descent - (cursorHeight - offsetHeight) + preY
|
||||||
const curosrleft = rightTop[0]
|
const curosrleft = rightTop[0]
|
||||||
agentCursorDom.style.left = `${curosrleft}px`
|
agentCursorDom.style.left = `${curosrleft}px`
|
||||||
agentCursorDom.style.top = `${cursorTop + cursorHeight - CURSOR_AGENT_HEIGHT}px`
|
agentCursorDom.style.top = `${cursorTop + cursorHeight - CURSOR_AGENT_HEIGHT}px`
|
||||||
|
|||||||
@@ -1,21 +1,22 @@
|
|||||||
import { debounce } from "../../utils"
|
import { debounce } from "../../utils"
|
||||||
|
import { Draw } from "../draw/Draw"
|
||||||
import { CanvasEvent } from "../event/CanvasEvent"
|
import { CanvasEvent } from "../event/CanvasEvent"
|
||||||
|
|
||||||
export class CursorAgent {
|
export class CursorAgent {
|
||||||
|
|
||||||
private canvas: HTMLCanvasElement
|
private container: HTMLDivElement
|
||||||
private agentCursorDom: HTMLTextAreaElement
|
private agentCursorDom: HTMLTextAreaElement
|
||||||
private canvasEvent: CanvasEvent
|
private canvasEvent: CanvasEvent
|
||||||
|
|
||||||
constructor(canvas: HTMLCanvasElement, canvasEvent: CanvasEvent) {
|
constructor(draw: Draw, canvasEvent: CanvasEvent) {
|
||||||
this.canvas = canvas
|
this.container = draw.getContainer()
|
||||||
this.canvasEvent = canvasEvent
|
this.canvasEvent = canvasEvent
|
||||||
// 代理光标绘制
|
// 代理光标绘制
|
||||||
const agentCursorDom = document.createElement('textarea')
|
const agentCursorDom = document.createElement('textarea')
|
||||||
agentCursorDom.autocomplete = 'off'
|
agentCursorDom.autocomplete = 'off'
|
||||||
agentCursorDom.classList.add('inputarea')
|
agentCursorDom.classList.add('inputarea')
|
||||||
agentCursorDom.innerText = ''
|
agentCursorDom.innerText = ''
|
||||||
this.canvas.parentNode?.append(agentCursorDom)
|
this.container.append(agentCursorDom)
|
||||||
this.agentCursorDom = agentCursorDom
|
this.agentCursorDom = agentCursorDom
|
||||||
// 事件
|
// 事件
|
||||||
agentCursorDom.onkeydown = (evt: KeyboardEvent) => this._keyDown(evt)
|
agentCursorDom.onkeydown = (evt: KeyboardEvent) => this._keyDown(evt)
|
||||||
|
|||||||
+172
-74
@@ -24,8 +24,11 @@ import { TextParticle } from "./particle/TextParticle"
|
|||||||
|
|
||||||
export class Draw {
|
export class Draw {
|
||||||
|
|
||||||
private canvas: HTMLCanvasElement
|
private container: HTMLDivElement
|
||||||
private ctx: CanvasRenderingContext2D
|
private pageContainer: HTMLDivElement
|
||||||
|
private pageList: HTMLCanvasElement[]
|
||||||
|
private ctxList: CanvasRenderingContext2D[]
|
||||||
|
private pageNo: number
|
||||||
private options: Required<IEditorOption>
|
private options: Required<IEditorOption>
|
||||||
private position: Position
|
private position: Position
|
||||||
private elementList: IElement[]
|
private elementList: IElement[]
|
||||||
@@ -48,41 +51,73 @@ export class Draw {
|
|||||||
private searchMatchList: number[][] | null
|
private searchMatchList: number[][] | null
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
canvas: HTMLCanvasElement,
|
container: HTMLDivElement,
|
||||||
ctx: CanvasRenderingContext2D,
|
|
||||||
options: Required<IEditorOption>,
|
options: Required<IEditorOption>,
|
||||||
elementList: IElement[],
|
elementList: IElement[],
|
||||||
listener: Listener
|
listener: Listener
|
||||||
) {
|
) {
|
||||||
this.canvas = canvas
|
this.container = container
|
||||||
this.ctx = ctx
|
this.pageList = []
|
||||||
|
this.ctxList = []
|
||||||
|
this.pageNo = 0
|
||||||
this.options = options
|
this.options = options
|
||||||
this.elementList = elementList
|
this.elementList = elementList
|
||||||
this.listener = listener
|
this.listener = listener
|
||||||
|
|
||||||
this.historyManager = new HistoryManager()
|
this.pageContainer = this._createPageContainer()
|
||||||
this.position = new Position(options, this)
|
this._createPage(0)
|
||||||
this.range = new RangeManager(ctx, options, this)
|
|
||||||
this.margin = new Margin(ctx, options)
|
|
||||||
this.background = new Background(ctx)
|
|
||||||
this.search = new Search(ctx, options, this)
|
|
||||||
this.underline = new Underline(ctx, options)
|
|
||||||
this.strikeout = new Strikeout(ctx, options)
|
|
||||||
this.highlight = new Highlight(ctx, options)
|
|
||||||
this.imageParticle = new ImageParticle(canvas, ctx, options, this)
|
|
||||||
this.textParticle = new TextParticle(ctx)
|
|
||||||
|
|
||||||
const canvasEvent = new CanvasEvent(canvas, this)
|
this.historyManager = new HistoryManager()
|
||||||
this.cursor = new Cursor(canvas, this, canvasEvent)
|
this.position = new Position(this)
|
||||||
|
this.range = new RangeManager(this)
|
||||||
|
this.margin = new Margin(this)
|
||||||
|
this.background = new Background()
|
||||||
|
this.search = new Search(this)
|
||||||
|
this.underline = new Underline(this)
|
||||||
|
this.strikeout = new Strikeout(this)
|
||||||
|
this.highlight = new Highlight(this)
|
||||||
|
this.imageParticle = new ImageParticle(this)
|
||||||
|
this.textParticle = new TextParticle(this)
|
||||||
|
|
||||||
|
const canvasEvent = new CanvasEvent(this)
|
||||||
|
this.cursor = new Cursor(this, canvasEvent)
|
||||||
canvasEvent.register()
|
canvasEvent.register()
|
||||||
const globalEvent = new GlobalEvent(canvas, this, canvasEvent)
|
const globalEvent = new GlobalEvent(this, canvasEvent)
|
||||||
globalEvent.register()
|
globalEvent.register()
|
||||||
|
|
||||||
this.rowList = []
|
this.rowList = []
|
||||||
this.painterStyle = null
|
this.painterStyle = null
|
||||||
this.searchMatchList = null
|
this.searchMatchList = null
|
||||||
|
|
||||||
this._setDefaultRange()
|
this.render({ isSetCursor: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
public getContainer(): HTMLDivElement {
|
||||||
|
return this.container
|
||||||
|
}
|
||||||
|
|
||||||
|
public getPageContainer(): HTMLDivElement {
|
||||||
|
return this.pageContainer
|
||||||
|
}
|
||||||
|
|
||||||
|
public getPageNo(): number {
|
||||||
|
return this.pageNo
|
||||||
|
}
|
||||||
|
|
||||||
|
public setPageNo(payload: number) {
|
||||||
|
this.pageNo = payload
|
||||||
|
}
|
||||||
|
|
||||||
|
public getPage(): HTMLCanvasElement {
|
||||||
|
return this.pageList[this.pageNo]
|
||||||
|
}
|
||||||
|
|
||||||
|
public getPageList(): HTMLCanvasElement[] {
|
||||||
|
return this.pageList
|
||||||
|
}
|
||||||
|
|
||||||
|
public getCtx(): CanvasRenderingContext2D {
|
||||||
|
return this.ctxList[this.pageNo]
|
||||||
}
|
}
|
||||||
|
|
||||||
public getOptions(): Required<IEditorOption> {
|
public getOptions(): Required<IEditorOption> {
|
||||||
@@ -121,8 +156,8 @@ export class Draw {
|
|||||||
return this.rowList.length
|
return this.rowList.length
|
||||||
}
|
}
|
||||||
|
|
||||||
public getDataURL(): string {
|
public getDataURL(): string[] {
|
||||||
return this.canvas.toDataURL()
|
return this.pageList.map(c => c.toDataURL())
|
||||||
}
|
}
|
||||||
|
|
||||||
public getPainterStyle(): IElementStyle | null {
|
public getPainterStyle(): IElementStyle | null {
|
||||||
@@ -132,11 +167,11 @@ export class Draw {
|
|||||||
public setPainterStyle(payload: IElementStyle | null) {
|
public setPainterStyle(payload: IElementStyle | null) {
|
||||||
this.painterStyle = payload
|
this.painterStyle = payload
|
||||||
if (this.getPainterStyle()) {
|
if (this.getPainterStyle()) {
|
||||||
this.canvas.style.cursor = 'copy'
|
this.pageList.forEach(c => c.style.cursor = 'copy')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getSearchMathch(): number[][] | null {
|
public getSearchMatch(): number[][] | null {
|
||||||
return this.searchMatchList
|
return this.searchMatchList
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,7 +179,7 @@ export class Draw {
|
|||||||
this.searchMatchList = payload
|
this.searchMatchList = payload
|
||||||
}
|
}
|
||||||
|
|
||||||
private _setDefaultRange() {
|
public setDefaultRange() {
|
||||||
if (!this.elementList.length) return
|
if (!this.elementList.length) return
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const curIndex = this.elementList.length - 1
|
const curIndex = this.elementList.length - 1
|
||||||
@@ -153,15 +188,43 @@ export class Draw {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _createPageContainer(): HTMLDivElement {
|
||||||
|
// 容器宽度需跟随纸张宽度
|
||||||
|
this.container.style.width = `${this.options.width}px`
|
||||||
|
const pageContainer = document.createElement('div')
|
||||||
|
pageContainer.classList.add('page-container')
|
||||||
|
this.container.append(pageContainer)
|
||||||
|
return pageContainer
|
||||||
|
}
|
||||||
|
|
||||||
|
private _createPage(pageNo: number) {
|
||||||
|
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.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.style.cursor = 'text'
|
||||||
|
const ctx = canvas.getContext('2d')!
|
||||||
|
ctx.scale(dpr, dpr)
|
||||||
|
// 缓存上下文
|
||||||
|
this.pageList.push(canvas)
|
||||||
|
this.ctxList.push(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
private _getFont(el: IElement): string {
|
private _getFont(el: IElement): string {
|
||||||
const { defaultSize, defaultFont } = this.options
|
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}px ${el.font || defaultFont}`
|
||||||
}
|
}
|
||||||
|
|
||||||
private _computeRowList() {
|
private _computeRowList() {
|
||||||
const { defaultSize } = this.options
|
const { defaultSize, width } = this.options
|
||||||
const canvasRect = this.canvas.getBoundingClientRect()
|
const canvas = document.createElement('canvas')
|
||||||
const { width } = canvasRect
|
const ctx = canvas.getContext('2d') as CanvasRenderingContext2D
|
||||||
const { margins, defaultRowMargin, defaultBasicRowMarginHeight } = this.options
|
const { margins, defaultRowMargin, defaultBasicRowMarginHeight } = this.options
|
||||||
const leftTopPoint: [number, number] = [margins[3], margins[0]]
|
const leftTopPoint: [number, number] = [margins[3], margins[0]]
|
||||||
const rightTopPoint: [number, number] = [width - margins[1], margins[0]]
|
const rightTopPoint: [number, number] = [width - margins[1], margins[0]]
|
||||||
@@ -176,7 +239,6 @@ export class Draw {
|
|||||||
rowFlex: this.elementList?.[1]?.rowFlex
|
rowFlex: this.elementList?.[1]?.rowFlex
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
this.ctx.save()
|
|
||||||
for (let i = 0; i < this.elementList.length; i++) {
|
for (let i = 0; i < this.elementList.length; i++) {
|
||||||
const curRow: IRow = rowList[rowList.length - 1]
|
const curRow: IRow = rowList[rowList.length - 1]
|
||||||
const element = this.elementList[i]
|
const element = this.elementList[i]
|
||||||
@@ -201,8 +263,8 @@ export class Draw {
|
|||||||
metrics.boundingBoxDescent = element.height!
|
metrics.boundingBoxDescent = element.height!
|
||||||
} else {
|
} else {
|
||||||
metrics.height = element.size || this.options.defaultSize
|
metrics.height = element.size || this.options.defaultSize
|
||||||
this.ctx.font = this._getFont(element)
|
ctx.font = this._getFont(element)
|
||||||
const fontMetrics = this.ctx.measureText(element.value)
|
const fontMetrics = ctx.measureText(element.value)
|
||||||
metrics.width = fontMetrics.width
|
metrics.width = fontMetrics.width
|
||||||
metrics.boundingBoxAscent = element.value === ZERO ? defaultSize : fontMetrics.actualBoundingBoxAscent
|
metrics.boundingBoxAscent = element.value === ZERO ? defaultSize : fontMetrics.actualBoundingBoxAscent
|
||||||
metrics.boundingBoxDescent = fontMetrics.actualBoundingBoxDescent
|
metrics.boundingBoxDescent = fontMetrics.actualBoundingBoxDescent
|
||||||
@@ -213,7 +275,7 @@ export class Draw {
|
|||||||
const rowElement: IRowElement = {
|
const rowElement: IRowElement = {
|
||||||
...element,
|
...element,
|
||||||
metrics,
|
metrics,
|
||||||
style: this.ctx.font
|
style: ctx.font
|
||||||
}
|
}
|
||||||
// 超过限定宽度
|
// 超过限定宽度
|
||||||
if (curRow.width + metrics.width > innerWidth || (i !== 0 && element.value === ZERO)) {
|
if (curRow.width + metrics.width > innerWidth || (i !== 0 && element.value === ZERO)) {
|
||||||
@@ -237,50 +299,30 @@ export class Draw {
|
|||||||
curRow.elementList.push(rowElement)
|
curRow.elementList.push(rowElement)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.ctx.restore()
|
|
||||||
this.rowList = rowList
|
this.rowList = rowList
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(payload?: IDrawOption) {
|
private _drawElement(positionList: IElementPosition[], rowList: IRow[], pageNo: number) {
|
||||||
let {
|
|
||||||
curIndex,
|
|
||||||
isSubmitHistory = true,
|
|
||||||
isSetCursor = true,
|
|
||||||
isComputeRowList = true
|
|
||||||
} = payload || {}
|
|
||||||
// 计算行信息
|
|
||||||
const { margins } = this.options
|
const { margins } = this.options
|
||||||
if (isComputeRowList) {
|
const canvas = this.pageList[pageNo]
|
||||||
this._computeRowList()
|
const ctx = this.ctxList[pageNo]
|
||||||
// 计算高度是否超出
|
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
||||||
const rowHeight = this.rowList.reduce((pre, cur) => cur.height + pre, 0)
|
|
||||||
if (rowHeight > this.canvas.height - margins[0] - margins[2]) {
|
|
||||||
const height = Math.ceil(rowHeight + margins[0] + margins[2])
|
|
||||||
this.canvas.height = height
|
|
||||||
this.canvas.style.height = `${height}px`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 清除光标等副作用
|
|
||||||
this.cursor.recoveryCursor()
|
|
||||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
|
|
||||||
this.position.setPositionList([])
|
|
||||||
const positionList = this.position.getPositionList()
|
|
||||||
// 基础信息
|
// 基础信息
|
||||||
const canvasRect = this.canvas.getBoundingClientRect()
|
const canvasRect = canvas.getBoundingClientRect()
|
||||||
// 绘制背景
|
// 绘制背景
|
||||||
this.background.render(canvasRect)
|
this.background.render(ctx, canvasRect)
|
||||||
// 绘制页边距
|
// 绘制页边距
|
||||||
const leftTopPoint: [number, number] = [margins[3], margins[0]]
|
const leftTopPoint: [number, number] = [margins[3], margins[0]]
|
||||||
this.margin.render(canvasRect)
|
this.margin.render(ctx, canvasRect)
|
||||||
// 渲染元素
|
// 渲染元素
|
||||||
let x = leftTopPoint[0]
|
let x = leftTopPoint[0]
|
||||||
let y = leftTopPoint[1]
|
let y = leftTopPoint[1]
|
||||||
let index = 0
|
let index = positionList.length
|
||||||
for (let i = 0; i < this.rowList.length; i++) {
|
for (let i = 0; i < rowList.length; i++) {
|
||||||
const curRow = this.rowList[i]
|
const curRow = rowList[i]
|
||||||
// 计算行偏移量(行居左、居中、居右)
|
// 计算行偏移量(行居左、居中、居右)
|
||||||
if (curRow.rowFlex && curRow.rowFlex !== RowFlex.LEFT) {
|
if (curRow.rowFlex && curRow.rowFlex !== RowFlex.LEFT) {
|
||||||
const canvasInnerWidth = this.canvas.width - margins[1] - margins[3]
|
const canvasInnerWidth = canvas.width - margins[1] - margins[3]
|
||||||
if (curRow.rowFlex === RowFlex.CENTER) {
|
if (curRow.rowFlex === RowFlex.CENTER) {
|
||||||
x += (canvasInnerWidth - curRow.width) / 2
|
x += (canvasInnerWidth - curRow.width) / 2
|
||||||
} else {
|
} else {
|
||||||
@@ -294,6 +336,7 @@ export class Draw {
|
|||||||
? curRow.ascent - element.height!
|
? curRow.ascent - element.height!
|
||||||
: curRow.ascent
|
: curRow.ascent
|
||||||
const positionItem: IElementPosition = {
|
const positionItem: IElementPosition = {
|
||||||
|
pageNo,
|
||||||
index,
|
index,
|
||||||
value: element.value,
|
value: element.value,
|
||||||
rowNo: i,
|
rowNo: i,
|
||||||
@@ -311,27 +354,27 @@ export class Draw {
|
|||||||
positionList.push(positionItem)
|
positionList.push(positionItem)
|
||||||
// 下划线绘制
|
// 下划线绘制
|
||||||
if (element.underline) {
|
if (element.underline) {
|
||||||
this.underline.render(x, y + curRow.height, metrics.width)
|
this.underline.render(ctx, x, y + curRow.height, metrics.width)
|
||||||
}
|
}
|
||||||
// 删除线绘制
|
// 删除线绘制
|
||||||
if (element.strikeout) {
|
if (element.strikeout) {
|
||||||
this.strikeout.render(x, y + curRow.height / 2, metrics.width)
|
this.strikeout.render(ctx, x, y + curRow.height / 2, metrics.width)
|
||||||
}
|
}
|
||||||
// 元素高亮
|
// 元素高亮
|
||||||
if (element.highlight) {
|
if (element.highlight) {
|
||||||
this.highlight.render(element.highlight, x, y, metrics.width, curRow.height)
|
this.highlight.render(ctx, element.highlight, x, y, metrics.width, curRow.height)
|
||||||
}
|
}
|
||||||
// 元素绘制
|
// 元素绘制
|
||||||
if (element.type === ElementType.IMAGE) {
|
if (element.type === ElementType.IMAGE) {
|
||||||
this.textParticle.complete()
|
this.textParticle.complete()
|
||||||
this.imageParticle.render(element, x, y + offsetY)
|
this.imageParticle.render(ctx, element, x, y + offsetY)
|
||||||
} else {
|
} else {
|
||||||
this.textParticle.record(element, x, y + offsetY)
|
this.textParticle.record(ctx, element, x, y + offsetY)
|
||||||
}
|
}
|
||||||
// 选区绘制
|
// 选区绘制
|
||||||
const { startIndex, endIndex } = this.range.getRange()
|
const { startIndex, endIndex } = this.range.getRange()
|
||||||
if (startIndex !== endIndex && startIndex < index && index <= endIndex) {
|
if (startIndex !== endIndex && startIndex < index && index <= endIndex) {
|
||||||
this.range.render(x, y, metrics.width, curRow.height)
|
this.range.render(ctx, x, y, metrics.width, curRow.height)
|
||||||
}
|
}
|
||||||
index++
|
index++
|
||||||
x += metrics.width
|
x += metrics.width
|
||||||
@@ -342,13 +385,66 @@ export class Draw {
|
|||||||
}
|
}
|
||||||
// 搜索匹配绘制
|
// 搜索匹配绘制
|
||||||
if (this.searchMatchList) {
|
if (this.searchMatchList) {
|
||||||
this.search.render()
|
this.search.render(ctx, pageNo)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public render(payload?: IDrawOption) {
|
||||||
|
let {
|
||||||
|
curIndex,
|
||||||
|
isSubmitHistory = true,
|
||||||
|
isSetCursor = true,
|
||||||
|
isComputeRowList = true
|
||||||
|
} = payload || {}
|
||||||
|
// 计算行信息
|
||||||
|
if (isComputeRowList) {
|
||||||
|
this._computeRowList()
|
||||||
|
}
|
||||||
|
// 清除光标等副作用
|
||||||
|
this.cursor.recoveryCursor()
|
||||||
|
this.position.setPositionList([])
|
||||||
|
const positionList = this.position.getPositionList()
|
||||||
|
// 按页渲染
|
||||||
|
const { margins } = this.options
|
||||||
|
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) {
|
||||||
|
pageHeight = marginHeight + row.height
|
||||||
|
pageRowList.push([row])
|
||||||
|
pageNo++
|
||||||
|
} else {
|
||||||
|
pageHeight += row.height
|
||||||
|
pageRowList[pageNo].push(row)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 绘制元素
|
||||||
|
for (let i = 0; i < pageRowList.length; i++) {
|
||||||
|
if (!this.pageList[i]) {
|
||||||
|
this._createPage(i)
|
||||||
|
}
|
||||||
|
const rowList = pageRowList[i]
|
||||||
|
this._drawElement(positionList, rowList, i)
|
||||||
|
}
|
||||||
|
// 移除多余页
|
||||||
|
setTimeout(() => {
|
||||||
|
const curPageCount = pageRowList.length
|
||||||
|
const prePageCount = this.pageList.length
|
||||||
|
if (prePageCount > curPageCount) {
|
||||||
|
const deleteCount = prePageCount - curPageCount
|
||||||
|
this.ctxList.splice(curPageCount, deleteCount)
|
||||||
|
this.pageList.splice(curPageCount, deleteCount)
|
||||||
|
.forEach(page => page.remove())
|
||||||
|
}
|
||||||
|
})
|
||||||
// 光标重绘
|
// 光标重绘
|
||||||
if (curIndex === undefined) {
|
|
||||||
curIndex = positionList.length - 1
|
|
||||||
}
|
|
||||||
if (isSetCursor) {
|
if (isSetCursor) {
|
||||||
|
if (curIndex === undefined) {
|
||||||
|
curIndex = positionList.length - 1
|
||||||
|
}
|
||||||
this.position.setCursorPosition(positionList[curIndex!] || null)
|
this.position.setCursorPosition(positionList[curIndex!] || null)
|
||||||
this.cursor.drawCursor()
|
this.cursor.drawCursor()
|
||||||
}
|
}
|
||||||
@@ -357,7 +453,9 @@ export class Draw {
|
|||||||
const self = this
|
const self = this
|
||||||
const oldElementList = deepClone(this.elementList)
|
const oldElementList = deepClone(this.elementList)
|
||||||
const { startIndex, endIndex } = this.range.getRange()
|
const { startIndex, endIndex } = this.range.getRange()
|
||||||
|
const pageNo = this.pageNo
|
||||||
this.historyManager.execute(function () {
|
this.historyManager.execute(function () {
|
||||||
|
self.setPageNo(pageNo)
|
||||||
self.range.setRange(startIndex, endIndex)
|
self.range.setRange(startIndex, endIndex)
|
||||||
self.elementList = deepClone(oldElementList)
|
self.elementList = deepClone(oldElementList)
|
||||||
self.render({ curIndex, isSubmitHistory: false })
|
self.render({ curIndex, isSubmitHistory: false })
|
||||||
|
|||||||
@@ -1,17 +1,11 @@
|
|||||||
export class Background {
|
export class Background {
|
||||||
|
|
||||||
private ctx: CanvasRenderingContext2D
|
public render(ctx: CanvasRenderingContext2D, canvasRect: DOMRect) {
|
||||||
|
|
||||||
constructor(ctx: CanvasRenderingContext2D) {
|
|
||||||
this.ctx = ctx
|
|
||||||
}
|
|
||||||
|
|
||||||
public render(canvasRect: DOMRect) {
|
|
||||||
const { width, height } = canvasRect
|
const { width, height } = canvasRect
|
||||||
this.ctx.save()
|
ctx.save()
|
||||||
this.ctx.fillStyle = '#ffffff'
|
ctx.fillStyle = '#ffffff'
|
||||||
this.ctx.fillRect(0, 0, width, height)
|
ctx.fillRect(0, 0, width, height)
|
||||||
this.ctx.restore()
|
ctx.restore()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,43 +1,42 @@
|
|||||||
import { IEditorOption } from "../../../interface/Editor"
|
import { IEditorOption } from "../../../interface/Editor"
|
||||||
|
import { Draw } from "../Draw"
|
||||||
|
|
||||||
export class Margin {
|
export class Margin {
|
||||||
|
|
||||||
private ctx: CanvasRenderingContext2D
|
|
||||||
private options: Required<IEditorOption>
|
private options: Required<IEditorOption>
|
||||||
|
|
||||||
constructor(ctx: CanvasRenderingContext2D, options: Required<IEditorOption>) {
|
constructor(draw: Draw) {
|
||||||
this.ctx = ctx
|
this.options = draw.getOptions()
|
||||||
this.options = options
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(canvasRect: DOMRect) {
|
public render(ctx: CanvasRenderingContext2D, canvasRect: DOMRect) {
|
||||||
const { width, height } = canvasRect
|
const { width, height } = canvasRect
|
||||||
const { marginIndicatorColor, marginIndicatorSize, margins } = this.options
|
const { marginIndicatorColor, marginIndicatorSize, margins } = this.options
|
||||||
this.ctx.save()
|
ctx.save()
|
||||||
this.ctx.strokeStyle = marginIndicatorColor
|
ctx.strokeStyle = marginIndicatorColor
|
||||||
this.ctx.beginPath()
|
ctx.beginPath()
|
||||||
const leftTopPoint: [number, number] = [margins[3], margins[0]]
|
const leftTopPoint: [number, number] = [margins[3], margins[0]]
|
||||||
const rightTopPoint: [number, number] = [width - margins[1], margins[0]]
|
const rightTopPoint: [number, number] = [width - margins[1], margins[0]]
|
||||||
const leftBottomPoint: [number, number] = [margins[3], height - margins[2]]
|
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]]
|
||||||
// 上左
|
// 上左
|
||||||
this.ctx.moveTo(leftTopPoint[0] - marginIndicatorSize, leftTopPoint[1])
|
ctx.moveTo(leftTopPoint[0] - marginIndicatorSize, leftTopPoint[1])
|
||||||
this.ctx.lineTo(...leftTopPoint)
|
ctx.lineTo(...leftTopPoint)
|
||||||
this.ctx.lineTo(leftTopPoint[0], leftTopPoint[1] - marginIndicatorSize)
|
ctx.lineTo(leftTopPoint[0], leftTopPoint[1] - marginIndicatorSize)
|
||||||
// 上右
|
// 上右
|
||||||
this.ctx.moveTo(rightTopPoint[0] + marginIndicatorSize, rightTopPoint[1])
|
ctx.moveTo(rightTopPoint[0] + marginIndicatorSize, rightTopPoint[1])
|
||||||
this.ctx.lineTo(...rightTopPoint)
|
ctx.lineTo(...rightTopPoint)
|
||||||
this.ctx.lineTo(rightTopPoint[0], rightTopPoint[1] - marginIndicatorSize)
|
ctx.lineTo(rightTopPoint[0], rightTopPoint[1] - marginIndicatorSize)
|
||||||
// 下左
|
// 下左
|
||||||
this.ctx.moveTo(leftBottomPoint[0] - marginIndicatorSize, leftBottomPoint[1])
|
ctx.moveTo(leftBottomPoint[0] - marginIndicatorSize, leftBottomPoint[1])
|
||||||
this.ctx.lineTo(...leftBottomPoint)
|
ctx.lineTo(...leftBottomPoint)
|
||||||
this.ctx.lineTo(leftBottomPoint[0], leftBottomPoint[1] + marginIndicatorSize)
|
ctx.lineTo(leftBottomPoint[0], leftBottomPoint[1] + marginIndicatorSize)
|
||||||
// 下右
|
// 下右
|
||||||
this.ctx.moveTo(rightBottomPoint[0] + marginIndicatorSize, rightBottomPoint[1])
|
ctx.moveTo(rightBottomPoint[0] + marginIndicatorSize, rightBottomPoint[1])
|
||||||
this.ctx.lineTo(...rightBottomPoint)
|
ctx.lineTo(...rightBottomPoint)
|
||||||
this.ctx.lineTo(rightBottomPoint[0], rightBottomPoint[1] + marginIndicatorSize)
|
ctx.lineTo(rightBottomPoint[0], rightBottomPoint[1] + marginIndicatorSize)
|
||||||
this.ctx.stroke()
|
ctx.stroke()
|
||||||
this.ctx.restore()
|
ctx.restore()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -4,36 +4,36 @@ import { Draw } from "../Draw"
|
|||||||
|
|
||||||
export class Search {
|
export class Search {
|
||||||
|
|
||||||
private ctx: CanvasRenderingContext2D
|
|
||||||
private options: Required<IEditorOption>
|
|
||||||
private draw: Draw
|
private draw: Draw
|
||||||
|
private options: Required<IEditorOption>
|
||||||
private position: Position
|
private position: Position
|
||||||
|
|
||||||
constructor(ctx: CanvasRenderingContext2D, options: Required<IEditorOption>, draw: Draw) {
|
constructor(draw: Draw) {
|
||||||
this.ctx = ctx
|
|
||||||
this.options = options
|
|
||||||
this.draw = draw
|
this.draw = draw
|
||||||
|
this.options = draw.getOptions()
|
||||||
this.position = draw.getPosition()
|
this.position = draw.getPosition()
|
||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render(ctx: CanvasRenderingContext2D, pageIndex: number) {
|
||||||
const searchMatch = this.draw.getSearchMathch()
|
const searchMatch = this.draw.getSearchMatch()
|
||||||
if (!searchMatch || !searchMatch.length) return
|
if (!searchMatch || !searchMatch.length) return
|
||||||
const searchMatchList = searchMatch.flat()
|
const searchMatchList = searchMatch.flat()
|
||||||
const positionList = this.position.getPositionList()
|
const positionList = this.position.getPositionList()
|
||||||
this.ctx.save()
|
ctx.save()
|
||||||
this.ctx.globalAlpha = this.options.searchMatchAlpha
|
ctx.globalAlpha = this.options.searchMatchAlpha
|
||||||
this.ctx.fillStyle = this.options.searchMatchColor
|
ctx.fillStyle = this.options.searchMatchColor
|
||||||
searchMatchList.forEach(s => {
|
searchMatchList.forEach(s => {
|
||||||
const position = positionList[s]
|
const position = positionList[s]
|
||||||
const { leftTop, leftBottom, rightTop } = position.coordinate
|
if (!position) return
|
||||||
|
const { coordinate: { leftTop, leftBottom, rightTop }, pageNo } = position
|
||||||
|
if (pageNo !== pageIndex) return
|
||||||
const x = leftTop[0]
|
const x = leftTop[0]
|
||||||
const y = leftTop[1]
|
const y = leftTop[1]
|
||||||
const width = rightTop[0] - leftTop[0]
|
const width = rightTop[0] - leftTop[0]
|
||||||
const height = leftBottom[1] - leftTop[1]
|
const height = leftBottom[1] - leftTop[1]
|
||||||
this.ctx.fillRect(x, y, width, height)
|
ctx.fillRect(x, y, width, height)
|
||||||
})
|
})
|
||||||
this.ctx.restore()
|
ctx.restore()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -5,8 +5,8 @@ import { Draw } from "../Draw"
|
|||||||
|
|
||||||
export class ImageParticle {
|
export class ImageParticle {
|
||||||
|
|
||||||
|
private container: HTMLDivElement
|
||||||
private canvas: HTMLCanvasElement
|
private canvas: HTMLCanvasElement
|
||||||
private ctx: CanvasRenderingContext2D
|
|
||||||
private draw: Draw
|
private draw: Draw
|
||||||
private options: Required<IEditorOption>
|
private options: Required<IEditorOption>
|
||||||
private curElement: IElement | null
|
private curElement: IElement | null
|
||||||
@@ -22,11 +22,11 @@ export class ImageParticle {
|
|||||||
private mousedownY: number
|
private mousedownY: number
|
||||||
private curHandleIndex: number
|
private curHandleIndex: number
|
||||||
|
|
||||||
constructor(canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D, options: Required<IEditorOption>, draw: Draw) {
|
constructor(draw: Draw) {
|
||||||
this.canvas = canvas
|
this.container = draw.getContainer()
|
||||||
this.ctx = ctx
|
this.canvas = draw.getPage()
|
||||||
this.draw = draw
|
this.draw = draw
|
||||||
this.options = options
|
this.options = draw.getOptions()
|
||||||
this.curElement = null
|
this.curElement = null
|
||||||
this.curPosition = null
|
this.curPosition = null
|
||||||
this.imageCache = new Map()
|
this.imageCache = new Map()
|
||||||
@@ -58,19 +58,21 @@ export class ImageParticle {
|
|||||||
resizerSelection.append(handleDom)
|
resizerSelection.append(handleDom)
|
||||||
resizerHandleList.push(handleDom)
|
resizerHandleList.push(handleDom)
|
||||||
}
|
}
|
||||||
this.canvas.parentNode!.append(resizerSelection)
|
this.container.append(resizerSelection)
|
||||||
// 拖拽镜像
|
// 拖拽镜像
|
||||||
const resizerImageContainer = document.createElement('div')
|
const resizerImageContainer = document.createElement('div')
|
||||||
resizerImageContainer.classList.add('resizer-image')
|
resizerImageContainer.classList.add('resizer-image')
|
||||||
resizerImageContainer.style.display = 'none'
|
resizerImageContainer.style.display = 'none'
|
||||||
const resizerImage = document.createElement('img')
|
const resizerImage = document.createElement('img')
|
||||||
resizerImageContainer.append(resizerImage)
|
resizerImageContainer.append(resizerImage)
|
||||||
this.canvas.parentNode!.append(resizerImageContainer)
|
this.container.append(resizerImageContainer)
|
||||||
return { resizerSelection, resizerHandleList, resizerImageContainer, resizerImage }
|
return { resizerSelection, resizerHandleList, resizerImageContainer, resizerImage }
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleMousedown(evt: MouseEvent) {
|
private _handleMousedown(evt: MouseEvent) {
|
||||||
|
this.canvas = this.draw.getPage()
|
||||||
if (!this.curPosition || !this.curElement) return
|
if (!this.curPosition || !this.curElement) return
|
||||||
|
const { height, pageGap } = this.options
|
||||||
this.mousedownX = evt.x
|
this.mousedownX = evt.x
|
||||||
this.mousedownY = evt.y
|
this.mousedownY = evt.y
|
||||||
const target = evt.target as HTMLDivElement
|
const target = evt.target as HTMLDivElement
|
||||||
@@ -83,8 +85,9 @@ export class ImageParticle {
|
|||||||
this.resizerImage.src = this.curElement?.value!
|
this.resizerImage.src = this.curElement?.value!
|
||||||
this.resizerImageContainer.style.display = 'block'
|
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.left = `${left}px`
|
||||||
this.resizerImageContainer.style.top = `${top}px`
|
this.resizerImageContainer.style.top = `${top + prePageHeight}px`
|
||||||
this.resizerImage.style.width = `${this.curElement.width}px`
|
this.resizerImage.style.width = `${this.curElement.width}px`
|
||||||
this.resizerImage.style.height = `${this.curElement.height}px`
|
this.resizerImage.style.height = `${this.curElement.height}px`
|
||||||
// 追加全局事件
|
// 追加全局事件
|
||||||
@@ -159,9 +162,10 @@ export class ImageParticle {
|
|||||||
const width = element.width!
|
const width = element.width!
|
||||||
const height = element.height!
|
const height = element.height!
|
||||||
const handleSize = this.options.resizerSize
|
const handleSize = this.options.resizerSize
|
||||||
|
const preY = this.draw.getPageNo() * (this.options.height + this.options.pageGap)
|
||||||
// 边框
|
// 边框
|
||||||
this.resizerSelection.style.left = `${left}px`
|
this.resizerSelection.style.left = `${left}px`
|
||||||
this.resizerSelection.style.top = `${top}px`
|
this.resizerSelection.style.top = `${top + preY}px`
|
||||||
this.resizerSelection.style.width = `${element.width}px`
|
this.resizerSelection.style.width = `${element.width}px`
|
||||||
this.resizerSelection.style.height = `${element.height}px`
|
this.resizerSelection.style.height = `${element.height}px`
|
||||||
// handle
|
// handle
|
||||||
@@ -190,17 +194,17 @@ export class ImageParticle {
|
|||||||
this.resizerSelection.style.display = 'none'
|
this.resizerSelection.style.display = 'none'
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(element: IElement, x: number, y: number) {
|
public render(ctx: CanvasRenderingContext2D, element: IElement, x: number, y: number) {
|
||||||
const width = element.width!
|
const width = element.width!
|
||||||
const height = element.height!
|
const height = element.height!
|
||||||
if (this.imageCache.has(element.id!)) {
|
if (this.imageCache.has(element.id!)) {
|
||||||
const img = this.imageCache.get(element.id!)!
|
const img = this.imageCache.get(element.id!)!
|
||||||
this.ctx.drawImage(img, x, y, width, height)
|
ctx.drawImage(img, x, y, width, height)
|
||||||
} else {
|
} else {
|
||||||
const img = new Image()
|
const img = new Image()
|
||||||
img.src = element.value
|
img.src = element.value
|
||||||
img.onload = () => {
|
img.onload = () => {
|
||||||
this.ctx.drawImage(img, x, y, width, height)
|
ctx.drawImage(img, x, y, width, height)
|
||||||
this.imageCache.set(element.id!, img)
|
this.imageCache.set(element.id!, img)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { IRowElement } from "../../../interface/Row"
|
import { IRowElement } from "../../../interface/Row"
|
||||||
|
import { Draw } from "../Draw"
|
||||||
|
|
||||||
export class TextParticle {
|
export class TextParticle {
|
||||||
|
|
||||||
@@ -9,8 +10,8 @@ export class TextParticle {
|
|||||||
private curStyle: string
|
private curStyle: string
|
||||||
private curColor?: string
|
private curColor?: string
|
||||||
|
|
||||||
constructor(ctx: CanvasRenderingContext2D) {
|
constructor(draw: Draw) {
|
||||||
this.ctx = ctx
|
this.ctx = draw.getCtx()
|
||||||
this.curX = -1
|
this.curX = -1
|
||||||
this.curY = -1
|
this.curY = -1
|
||||||
this.text = ''
|
this.text = ''
|
||||||
@@ -22,7 +23,8 @@ export class TextParticle {
|
|||||||
this.text = ''
|
this.text = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
public record(element: IRowElement, x: number, y: number) {
|
public record(ctx: CanvasRenderingContext2D, element: IRowElement, x: number, y: number) {
|
||||||
|
this.ctx = ctx
|
||||||
// 主动完成的重设起始点
|
// 主动完成的重设起始点
|
||||||
if (!this.text) {
|
if (!this.text) {
|
||||||
this._setCurXY(x, y)
|
this._setCurXY(x, y)
|
||||||
|
|||||||
@@ -1,22 +1,21 @@
|
|||||||
import { IEditorOption } from "../../../interface/Editor"
|
import { IEditorOption } from "../../../interface/Editor"
|
||||||
|
import { Draw } from "../Draw"
|
||||||
|
|
||||||
export class Highlight {
|
export class Highlight {
|
||||||
|
|
||||||
private ctx: CanvasRenderingContext2D
|
|
||||||
private options: Required<IEditorOption>
|
private options: Required<IEditorOption>
|
||||||
|
|
||||||
constructor(ctx: CanvasRenderingContext2D, options: Required<IEditorOption>) {
|
constructor(draw: Draw) {
|
||||||
this.ctx = ctx
|
this.options = draw.getOptions()
|
||||||
this.options = options
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(color: string, x: number, y: number, width: number, height: number) {
|
public render(ctx: CanvasRenderingContext2D, color: string, x: number, y: number, width: number, height: number) {
|
||||||
const { highlightAlpha } = this.options
|
const { highlightAlpha } = this.options
|
||||||
this.ctx.save()
|
ctx.save()
|
||||||
this.ctx.globalAlpha = highlightAlpha
|
ctx.globalAlpha = highlightAlpha
|
||||||
this.ctx.fillStyle = color
|
ctx.fillStyle = color
|
||||||
this.ctx.fillRect(x, y, width, height)
|
ctx.fillRect(x, y, width, height)
|
||||||
this.ctx.restore()
|
ctx.restore()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,24 +1,23 @@
|
|||||||
import { IEditorOption } from "../../../interface/Editor"
|
import { IEditorOption } from "../../../interface/Editor"
|
||||||
|
import { Draw } from "../Draw"
|
||||||
|
|
||||||
export class Strikeout {
|
export class Strikeout {
|
||||||
|
|
||||||
private ctx: CanvasRenderingContext2D
|
|
||||||
private options: Required<IEditorOption>
|
private options: Required<IEditorOption>
|
||||||
|
|
||||||
constructor(ctx: CanvasRenderingContext2D, options: Required<IEditorOption>) {
|
constructor(draw: Draw) {
|
||||||
this.ctx = ctx
|
this.options = draw.getOptions()
|
||||||
this.options = options
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(x: number, y: number, width: number) {
|
public render(ctx: CanvasRenderingContext2D, x: number, y: number, width: number) {
|
||||||
const { strikeoutColor } = this.options
|
const { strikeoutColor } = this.options
|
||||||
this.ctx.save()
|
ctx.save()
|
||||||
this.ctx.strokeStyle = strikeoutColor
|
ctx.strokeStyle = strikeoutColor
|
||||||
this.ctx.beginPath()
|
ctx.beginPath()
|
||||||
this.ctx.moveTo(x, y)
|
ctx.moveTo(x, y)
|
||||||
this.ctx.lineTo(x + width, y)
|
ctx.lineTo(x + width, y)
|
||||||
this.ctx.stroke()
|
ctx.stroke()
|
||||||
this.ctx.restore()
|
ctx.restore()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,24 +1,23 @@
|
|||||||
import { IEditorOption } from "../../../interface/Editor"
|
import { IEditorOption } from "../../../interface/Editor"
|
||||||
|
import { Draw } from "../Draw"
|
||||||
|
|
||||||
export class Underline {
|
export class Underline {
|
||||||
|
|
||||||
private ctx: CanvasRenderingContext2D
|
|
||||||
private options: Required<IEditorOption>
|
private options: Required<IEditorOption>
|
||||||
|
|
||||||
constructor(ctx: CanvasRenderingContext2D, options: Required<IEditorOption>) {
|
constructor(draw: Draw) {
|
||||||
this.ctx = ctx
|
this.options = draw.getOptions()
|
||||||
this.options = options
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(x: number, y: number, width: number) {
|
public render(ctx: CanvasRenderingContext2D, x: number, y: number, width: number) {
|
||||||
const { underlineColor } = this.options
|
const { underlineColor } = this.options
|
||||||
this.ctx.save()
|
ctx.save()
|
||||||
this.ctx.strokeStyle = underlineColor
|
ctx.strokeStyle = underlineColor
|
||||||
this.ctx.beginPath()
|
ctx.beginPath()
|
||||||
this.ctx.moveTo(x, y)
|
ctx.moveTo(x, y)
|
||||||
this.ctx.lineTo(x + width, y)
|
ctx.lineTo(x + width, y)
|
||||||
this.ctx.stroke()
|
ctx.stroke()
|
||||||
this.ctx.restore()
|
ctx.restore()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -17,19 +17,21 @@ export class CanvasEvent {
|
|||||||
private mouseDownStartIndex: number
|
private mouseDownStartIndex: number
|
||||||
|
|
||||||
private draw: Draw
|
private draw: Draw
|
||||||
private canvas: HTMLCanvasElement
|
private pageContainer: HTMLDivElement
|
||||||
|
private pageList: HTMLCanvasElement[]
|
||||||
private position: Position
|
private position: Position
|
||||||
private range: RangeManager
|
private range: RangeManager
|
||||||
private cursor: Cursor | null
|
private cursor: Cursor | null
|
||||||
private historyManager: HistoryManager
|
private historyManager: HistoryManager
|
||||||
private imageParticle: ImageParticle
|
private imageParticle: ImageParticle
|
||||||
|
|
||||||
constructor(canvas: HTMLCanvasElement, draw: Draw) {
|
constructor(draw: Draw) {
|
||||||
this.isAllowDrag = false
|
this.isAllowDrag = false
|
||||||
this.isCompositing = false
|
this.isCompositing = false
|
||||||
this.mouseDownStartIndex = 0
|
this.mouseDownStartIndex = 0
|
||||||
|
|
||||||
this.canvas = canvas
|
this.pageContainer = draw.getPageContainer()
|
||||||
|
this.pageList = draw.getPageList()
|
||||||
this.draw = draw
|
this.draw = draw
|
||||||
this.cursor = null
|
this.cursor = null
|
||||||
this.position = this.draw.getPosition()
|
this.position = this.draw.getPosition()
|
||||||
@@ -41,15 +43,17 @@ export class CanvasEvent {
|
|||||||
public register() {
|
public register() {
|
||||||
// 延迟加载
|
// 延迟加载
|
||||||
this.cursor = this.draw.getCursor()
|
this.cursor = this.draw.getCursor()
|
||||||
this.canvas.addEventListener('mousedown', this.mousedown.bind(this))
|
this.pageContainer.addEventListener('mousedown', this.mousedown.bind(this))
|
||||||
this.canvas.addEventListener('mouseleave', this.mouseleave.bind(this))
|
this.pageContainer.addEventListener('mouseleave', this.mouseleave.bind(this))
|
||||||
this.canvas.addEventListener('mousemove', this.mousemove.bind(this))
|
this.pageContainer.addEventListener('mousemove', this.mousemove.bind(this))
|
||||||
}
|
}
|
||||||
|
|
||||||
public setIsAllowDrag(payload: boolean) {
|
public setIsAllowDrag(payload: boolean) {
|
||||||
this.isAllowDrag = payload
|
this.isAllowDrag = payload
|
||||||
if (payload === false) {
|
if (payload === false) {
|
||||||
this.canvas.style.cursor = 'text'
|
this.pageList.forEach(p => {
|
||||||
|
p.style.cursor = 'text'
|
||||||
|
})
|
||||||
// 应用格式刷样式
|
// 应用格式刷样式
|
||||||
const painterStyle = this.draw.getPainterStyle()
|
const painterStyle = this.draw.getPainterStyle()
|
||||||
if (!painterStyle) return
|
if (!painterStyle) return
|
||||||
@@ -69,15 +73,21 @@ export class CanvasEvent {
|
|||||||
|
|
||||||
public mousemove(evt: MouseEvent) {
|
public mousemove(evt: MouseEvent) {
|
||||||
if (!this.isAllowDrag) return
|
if (!this.isAllowDrag) return
|
||||||
|
const target = evt.target as HTMLDivElement
|
||||||
|
const pageIndex = target.dataset.index
|
||||||
|
// 设置pageNo
|
||||||
|
if (pageIndex) {
|
||||||
|
this.draw.setPageNo(Number(pageIndex))
|
||||||
|
}
|
||||||
// 结束位置
|
// 结束位置
|
||||||
const { index: endIndex } = this.draw.getPosition().getPositionByXY(evt.offsetX, evt.offsetY)
|
const { index: endIndex } = this.position.getPositionByXY(evt.offsetX, evt.offsetY)
|
||||||
let end = ~endIndex ? endIndex : 0
|
let end = ~endIndex ? endIndex : 0
|
||||||
// 开始位置
|
// 开始位置
|
||||||
let start = this.mouseDownStartIndex
|
let start = this.mouseDownStartIndex
|
||||||
if (start > end) {
|
if (start > end) {
|
||||||
[start, end] = [end, start]
|
[start, end] = [end, start]
|
||||||
}
|
}
|
||||||
this.draw.getRange().setRange(start, end)
|
this.range.setRange(start, end)
|
||||||
if (start === end) return
|
if (start === end) return
|
||||||
// 绘制
|
// 绘制
|
||||||
this.draw.render({
|
this.draw.render({
|
||||||
@@ -88,8 +98,14 @@ export class CanvasEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public mousedown(evt: MouseEvent) {
|
public mousedown(evt: MouseEvent) {
|
||||||
|
const target = evt.target as HTMLDivElement
|
||||||
|
const pageIndex = target.dataset.index
|
||||||
|
// 设置pageNo
|
||||||
|
if (pageIndex) {
|
||||||
|
this.draw.setPageNo(Number(pageIndex))
|
||||||
|
}
|
||||||
this.isAllowDrag = true
|
this.isAllowDrag = true
|
||||||
const { index, isDirectHit, isImage } = this.draw.getPosition().getPositionByXY(evt.offsetX, evt.offsetY)
|
const { index, isDirectHit, isImage } = this.position.getPositionByXY(evt.offsetX, evt.offsetY)
|
||||||
// 记录选区开始位置
|
// 记录选区开始位置
|
||||||
this.mouseDownStartIndex = index
|
this.mouseDownStartIndex = index
|
||||||
// 绘制
|
// 绘制
|
||||||
@@ -114,7 +130,7 @@ export class CanvasEvent {
|
|||||||
|
|
||||||
public mouseleave(evt: MouseEvent) {
|
public mouseleave(evt: MouseEvent) {
|
||||||
// 是否还在canvas内部
|
// 是否还在canvas内部
|
||||||
const { x, y, width, height } = this.canvas.getBoundingClientRect()
|
const { x, y, width, height } = this.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
|
||||||
this.setIsAllowDrag(false)
|
this.setIsAllowDrag(false)
|
||||||
}
|
}
|
||||||
@@ -141,6 +157,15 @@ export class CanvasEvent {
|
|||||||
const curIndex = isCollspace ? index - 1 : startIndex
|
const curIndex = isCollspace ? index - 1 : startIndex
|
||||||
this.range.setRange(curIndex, curIndex)
|
this.range.setRange(curIndex, curIndex)
|
||||||
this.draw.render({ curIndex })
|
this.draw.render({ curIndex })
|
||||||
|
} else if (evt.key === KeyMap.Delete) {
|
||||||
|
if (!isCollspace) {
|
||||||
|
elementList.splice(startIndex + 1, endIndex - startIndex)
|
||||||
|
} else {
|
||||||
|
elementList.splice(index + 1, 1)
|
||||||
|
}
|
||||||
|
const curIndex = isCollspace ? index : startIndex
|
||||||
|
this.range.setRange(curIndex, curIndex)
|
||||||
|
this.draw.render({ curIndex })
|
||||||
} else if (evt.key === KeyMap.Enter) {
|
} else if (evt.key === KeyMap.Enter) {
|
||||||
const enterText: IElement = {
|
const enterText: IElement = {
|
||||||
value: ZERO
|
value: ZERO
|
||||||
|
|||||||
@@ -8,16 +8,16 @@ import { CanvasEvent } from "./CanvasEvent"
|
|||||||
|
|
||||||
export class GlobalEvent {
|
export class GlobalEvent {
|
||||||
|
|
||||||
private canvas: HTMLCanvasElement
|
|
||||||
private draw: Draw
|
private draw: Draw
|
||||||
|
private canvas: HTMLCanvasElement
|
||||||
private cursor: Cursor | null
|
private cursor: Cursor | null
|
||||||
private canvasEvent: CanvasEvent
|
private canvasEvent: CanvasEvent
|
||||||
private range: RangeManager
|
private range: RangeManager
|
||||||
private imageParticle: ImageParticle
|
private imageParticle: ImageParticle
|
||||||
|
|
||||||
constructor(canvas: HTMLCanvasElement, draw: Draw, canvasEvent: CanvasEvent) {
|
constructor(draw: Draw, canvasEvent: CanvasEvent) {
|
||||||
this.canvas = canvas
|
|
||||||
this.draw = draw
|
this.draw = draw
|
||||||
|
this.canvas = draw.getPage()
|
||||||
this.canvasEvent = canvasEvent
|
this.canvasEvent = canvasEvent
|
||||||
this.cursor = null
|
this.cursor = null
|
||||||
this.range = draw.getRange()
|
this.range = draw.getRange()
|
||||||
|
|||||||
@@ -14,13 +14,13 @@ export class Position {
|
|||||||
private draw: Draw
|
private draw: Draw
|
||||||
private options: Required<IEditorOption>
|
private options: Required<IEditorOption>
|
||||||
|
|
||||||
constructor(options: Required<IEditorOption>, draw: Draw) {
|
constructor(draw: Draw) {
|
||||||
this.positionList = []
|
this.positionList = []
|
||||||
this.elementList = []
|
this.elementList = []
|
||||||
this.cursorPosition = null
|
this.cursorPosition = null
|
||||||
|
|
||||||
this.options = options
|
|
||||||
this.draw = draw
|
this.draw = draw
|
||||||
|
this.options = draw.getOptions()
|
||||||
}
|
}
|
||||||
|
|
||||||
public getPositionList(): IElementPosition[] {
|
public getPositionList(): IElementPosition[] {
|
||||||
@@ -41,8 +41,10 @@ export class Position {
|
|||||||
|
|
||||||
public getPositionByXY(x: number, y: number): ICurrentPosition {
|
public getPositionByXY(x: number, y: number): ICurrentPosition {
|
||||||
this.elementList = this.draw.getElementList()
|
this.elementList = this.draw.getElementList()
|
||||||
|
const curPageNo = this.draw.getPageNo()
|
||||||
for (let j = 0; j < this.positionList.length; j++) {
|
for (let j = 0; j < this.positionList.length; j++) {
|
||||||
const { index, coordinate: { leftTop, rightTop, leftBottom } } = this.positionList[j];
|
const { index, pageNo, coordinate: { leftTop, rightTop, leftBottom } } = this.positionList[j]
|
||||||
|
if (curPageNo !== 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 curPostionIndex = j
|
let curPostionIndex = j
|
||||||
@@ -65,9 +67,10 @@ export class Position {
|
|||||||
let isLastArea = false
|
let isLastArea = false
|
||||||
let curPostionIndex = -1
|
let curPostionIndex = -1
|
||||||
// 判断所属行是否存在元素
|
// 判断所属行是否存在元素
|
||||||
const firstLetterList = this.positionList.filter(p => p.isLastLetter)
|
const firstLetterList = this.positionList.filter(p => p.isLastLetter && p.pageNo === curPageNo)
|
||||||
for (let j = 0; j < firstLetterList.length; j++) {
|
for (let j = 0; j < firstLetterList.length; j++) {
|
||||||
const { index, coordinate: { leftTop, leftBottom } } = firstLetterList[j]
|
const { index, pageNo, coordinate: { leftTop, leftBottom } } = firstLetterList[j]
|
||||||
|
if (curPageNo !== pageNo) continue
|
||||||
if (y > leftTop[1] && y <= leftBottom[1]) {
|
if (y > leftTop[1] && y <= leftBottom[1]) {
|
||||||
const isHead = x < this.options.margins[3]
|
const isHead = x < this.options.margins[3]
|
||||||
// 是否在头部
|
// 是否在头部
|
||||||
@@ -82,7 +85,8 @@ export class Position {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!isLastArea) {
|
if (!isLastArea) {
|
||||||
return { index: this.positionList.length - 1 }
|
// 当前页最后一行
|
||||||
|
return { index: firstLetterList[firstLetterList.length - 1]?.index || this.positionList.length - 1 }
|
||||||
}
|
}
|
||||||
return { index: curPostionIndex }
|
return { index: curPostionIndex }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,17 +7,15 @@ import { Listener } from "../listener/Listener"
|
|||||||
|
|
||||||
export class RangeManager {
|
export class RangeManager {
|
||||||
|
|
||||||
private ctx: CanvasRenderingContext2D
|
private draw: Draw
|
||||||
private options: Required<IEditorOption>
|
private options: Required<IEditorOption>
|
||||||
private range: IRange
|
private range: IRange
|
||||||
private draw: Draw
|
|
||||||
private listener: Listener
|
private listener: Listener
|
||||||
private historyManager: HistoryManager
|
private historyManager: HistoryManager
|
||||||
|
|
||||||
constructor(ctx: CanvasRenderingContext2D, options: Required<IEditorOption>, draw: Draw) {
|
constructor(draw: Draw) {
|
||||||
this.ctx = ctx
|
|
||||||
this.options = options
|
|
||||||
this.draw = draw
|
this.draw = draw
|
||||||
|
this.options = draw.getOptions()
|
||||||
this.listener = draw.getListener()
|
this.listener = draw.getListener()
|
||||||
this.historyManager = draw.getHistoryManager()
|
this.historyManager = draw.getHistoryManager()
|
||||||
this.range = {
|
this.range = {
|
||||||
@@ -104,12 +102,12 @@ export class RangeManager {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(x: number, y: number, width: number, height: number) {
|
public render(ctx: CanvasRenderingContext2D, x: number, y: number, width: number, height: number) {
|
||||||
this.ctx.save()
|
ctx.save()
|
||||||
this.ctx.globalAlpha = this.options.rangeAlpha
|
ctx.globalAlpha = this.options.rangeAlpha
|
||||||
this.ctx.fillStyle = this.options.rangeColor
|
ctx.fillStyle = this.options.rangeColor
|
||||||
this.ctx.fillRect(x, y, width, height)
|
ctx.fillRect(x, y, width, height)
|
||||||
this.ctx.restore()
|
ctx.restore()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export const EDITOR_ELEMENT_STYLE = ['bold', 'color', 'highlight', 'font', 'size', 'italic', 'underline', 'strikeout']
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
export enum KeyMap {
|
export enum KeyMap {
|
||||||
|
Delete = 'Delete',
|
||||||
Backspace = 'Backspace',
|
Backspace = 'Backspace',
|
||||||
Enter = "Enter",
|
Enter = "Enter",
|
||||||
Left = "ArrowLeft",
|
Left = "ArrowLeft",
|
||||||
|
|||||||
+5
-9
@@ -15,13 +15,16 @@ export default class Editor {
|
|||||||
public command: Command
|
public command: Command
|
||||||
public listener: Listener
|
public listener: Listener
|
||||||
|
|
||||||
constructor(canvas: HTMLCanvasElement, elementList: IElement[], options: IEditorOption = {}) {
|
constructor(container: HTMLDivElement, elementList: IElement[], options: IEditorOption = {}) {
|
||||||
const editorOptions: Required<IEditorOption> = {
|
const editorOptions: Required<IEditorOption> = {
|
||||||
defaultType: 'TEXT',
|
defaultType: 'TEXT',
|
||||||
defaultFont: 'Yahei',
|
defaultFont: 'Yahei',
|
||||||
defaultSize: 16,
|
defaultSize: 16,
|
||||||
defaultRowMargin: 1,
|
defaultRowMargin: 1,
|
||||||
defaultBasicRowMarginHeight: 8,
|
defaultBasicRowMarginHeight: 8,
|
||||||
|
width: 794,
|
||||||
|
height: 1123,
|
||||||
|
pageGap: 20,
|
||||||
underlineColor: '#000000',
|
underlineColor: '#000000',
|
||||||
strikeoutColor: '#FF0000',
|
strikeoutColor: '#FF0000',
|
||||||
rangeAlpha: 0.6,
|
rangeAlpha: 0.6,
|
||||||
@@ -36,12 +39,6 @@ export default class Editor {
|
|||||||
margins: [100, 120, 100, 120],
|
margins: [100, 120, 100, 120],
|
||||||
...options
|
...options
|
||||||
}
|
}
|
||||||
const ctx = canvas.getContext('2d') as CanvasRenderingContext2D
|
|
||||||
const dpr = window.devicePixelRatio
|
|
||||||
canvas.width = parseInt(canvas.style.width) * dpr
|
|
||||||
canvas.height = parseInt(canvas.style.height) * dpr
|
|
||||||
canvas.style.cursor = 'text'
|
|
||||||
ctx.scale(dpr, dpr)
|
|
||||||
if (elementList[0]?.value !== ZERO) {
|
if (elementList[0]?.value !== ZERO) {
|
||||||
elementList.unshift({
|
elementList.unshift({
|
||||||
value: ZERO
|
value: ZERO
|
||||||
@@ -59,8 +56,7 @@ export default class Editor {
|
|||||||
// 监听
|
// 监听
|
||||||
this.listener = new Listener()
|
this.listener = new Listener()
|
||||||
// 启动
|
// 启动
|
||||||
const draw = new Draw(canvas, ctx, editorOptions, elementList, this.listener)
|
const draw = new Draw(container, editorOptions, elementList, this.listener)
|
||||||
draw.render()
|
|
||||||
// 命令
|
// 命令
|
||||||
this.command = new Command(new CommandAdapt(draw))
|
this.command = new Command(new CommandAdapt(draw))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ export interface IEditorOption {
|
|||||||
defaultSize?: number;
|
defaultSize?: number;
|
||||||
defaultBasicRowMarginHeight?: number;
|
defaultBasicRowMarginHeight?: number;
|
||||||
defaultRowMargin?: number;
|
defaultRowMargin?: number;
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
pageGap?: number;
|
||||||
underlineColor?: string;
|
underlineColor?: string;
|
||||||
strikeoutColor?: string;
|
strikeoutColor?: string;
|
||||||
rangeColor?: string;
|
rangeColor?: string;
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ export interface IElementBasic {
|
|||||||
export type IElement = IElementBasic & IElementStyle
|
export type IElement = IElementBasic & IElementStyle
|
||||||
|
|
||||||
export interface IElementPosition {
|
export interface IElementPosition {
|
||||||
|
pageNo: number;
|
||||||
index: number;
|
index: number;
|
||||||
value: string,
|
value: string,
|
||||||
rowNo: number;
|
rowNo: number;
|
||||||
|
|||||||
@@ -15,4 +15,4 @@ export interface IRangeStype {
|
|||||||
rowMargin: number
|
rowMargin: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IRangeStyleChange = (payload: IRangeStype) => void;
|
export type IRangeStyleChange = (payload: IRangeStype) => void
|
||||||
|
|||||||
@@ -1,17 +1,21 @@
|
|||||||
export function printImageBase64(base64: string) {
|
export function printImageBase64(base64List: string[], width: number, height: number) {
|
||||||
const iframe = document.createElement('iframe')
|
const iframe = document.createElement('iframe')
|
||||||
document.body.append(iframe)
|
document.body.append(iframe)
|
||||||
const doc = iframe.contentWindow!.document
|
const doc = iframe.contentWindow!.document
|
||||||
doc.open()
|
doc.open()
|
||||||
const image = doc.createElement('img')
|
const container = document.createElement('div')
|
||||||
image.style.width = '794px'
|
base64List.forEach(base64 => {
|
||||||
image.style.height = '1123px'
|
const image = document.createElement('img')
|
||||||
image.src = base64
|
image.style.width = `${width}px`
|
||||||
|
image.style.height = `${height}px`
|
||||||
|
image.src = base64
|
||||||
|
container.append(image)
|
||||||
|
})
|
||||||
const style = document.createElement('style')
|
const style = document.createElement('style')
|
||||||
const stylesheet = `*{margin:0;padding:0;}`
|
const stylesheet = `*{margin:0;padding:0;}`
|
||||||
style.append(document.createTextNode(stylesheet))
|
style.append(document.createTextNode(stylesheet))
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
doc.write(`${style.outerHTML}${image.outerHTML}`)
|
doc.write(`${style.outerHTML}${container.innerHTML}`)
|
||||||
iframe.contentWindow?.print()
|
iframe.contentWindow?.print()
|
||||||
doc.close()
|
doc.close()
|
||||||
iframe.remove()
|
iframe.remove()
|
||||||
|
|||||||
+5
-6
@@ -3,9 +3,7 @@ import Editor, { ElementType, IElement, RowFlex } from './editor'
|
|||||||
|
|
||||||
window.onload = function () {
|
window.onload = function () {
|
||||||
|
|
||||||
const canvas = document.querySelector<HTMLCanvasElement>('canvas')
|
const text = `人民医院门诊病历\n主诉:\n发热三天,咳嗽五天。\n现病史:\n患者于三天前无明显诱因,感冒后发现面部水肿,无皮疹,尿量减少,出现乏力,在外治疗无好转,现来我院就诊。\n既往史:\n有糖尿病10年,有高血压2年,有传染性疾病1年。没有报告其他既往疾病。\n流行病史:\n否认14天内接触过新冠肺炎确诊患者、疑似患者、无症状感染者及其密切接触者;否认14天内去过以下场所:水产、肉类批发市场,农贸市场,集市,大型超市,夜市;否认14天内与以下场所工作人员密切接触:水产、肉类批发市场,农贸市场,集市,大型超市;否认14天内周围(如家庭、办公室)有2例以上聚集性发病;否认14天内接触过有发热或呼吸道症状的人员;否认14天内自身有发热或呼吸道症状;否认14天内接触过纳入隔离观察的人员及其他可能与新冠肺炎关联的情形;陪同家属{有无选择代码}有以上情况。\n体格检查:\nT:36.5℃,P:80bpm,R:20次/分,BP:120/80mmHg;\n辅助检查:\n2020年6月10日,普放:血细胞比容36.50%(偏低)40~50;单核细胞绝对值0.75*10^9/L(偏高)参考值:0.1~0.6;\n门诊诊断:\n1.高血压\n2.糖尿病\n3.病毒性感冒\n4.过敏性鼻炎\n5.过敏性鼻息肉\n处置治疗:\n1.超声引导下甲状腺细针穿刺术;\n2.乙型肝炎表面抗体测定;\n3.膜式病变细胞采集术、后颈皮下肤层;\n电子签名:【】`
|
||||||
if (!canvas) return
|
|
||||||
const text = `人民医院门诊病历\n主诉:\n发热三天,咳嗽五天。\n现病史:\n发病前14天内有病历报告社区的旅行时或居住史;发病前14天内与新型冠状病毒感染的患者或无症状感染者有接触史;发病前14天内解除过来自病历报告社区的发热或有呼吸道症状的患者;聚集性发病,2周内在小范围如家庭、办公室、学校班级等场所,出现2例及以上发热或呼吸道症状的病例。\n既往史:\n有糖尿病10年,有高血压2年,有传染性疾病1年。\n体格检查:\nT:36.5℃,P:80bpm,R:20次/分,BP:120/80mmHg;\n辅助检查:\n2020年6月10日,普放:血细胞比容36.50%(偏低)40~50;单核细胞绝对值0.75*10^9/L(偏高)参考值:0.1~0.6;\n门诊诊断:\n1.高血压\n处置治疗:\n1.超声引导下甲状腺细针穿刺术;\n2.乙型肝炎表面抗体测定;\n3.膜式病变细胞采集术、后颈皮下肤层;\n电子签名:【】`
|
|
||||||
// 模拟行居中
|
// 模拟行居中
|
||||||
const centerText = ['人民医院门诊病历']
|
const centerText = ['人民医院门诊病历']
|
||||||
const centerIndex: number[] = centerText.map(c => {
|
const centerIndex: number[] = centerText.map(c => {
|
||||||
@@ -13,7 +11,7 @@ window.onload = function () {
|
|||||||
return ~i ? Array(c.length).fill(i).map((_, j) => i + j) : []
|
return ~i ? Array(c.length).fill(i).map((_, j) => i + j) : []
|
||||||
}).flat()
|
}).flat()
|
||||||
// 模拟加粗字
|
// 模拟加粗字
|
||||||
const boldText = ['主诉:', '现病史:', '既往史:', '体格检查:', '辅助检查:', '门诊诊断:', '处置治疗:', '电子签名:']
|
const boldText = ['主诉:', '现病史:', '既往史:', '流行病史:', '体格检查:', '辅助检查:', '门诊诊断:', '处置治疗:', '电子签名:']
|
||||||
const boldIndex: number[] = boldText.map(b => {
|
const boldIndex: number[] = boldText.map(b => {
|
||||||
const i = text.indexOf(b)
|
const i = text.indexOf(b)
|
||||||
return ~i ? Array(b.length).fill(i).map((_, j) => i + j) : []
|
return ~i ? Array(b.length).fill(i).map((_, j) => i + j) : []
|
||||||
@@ -64,7 +62,7 @@ window.onload = function () {
|
|||||||
size: 16
|
size: 16
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
data.splice(390, 0, {
|
data.splice(595, 0, {
|
||||||
value: `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFkAAAAgCAYAAAB5JtSmAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAQ0SURBVGhD7dhrUSNBFAVgvKACEVjAAhJQgAIUYAABGEAABvgfAdn6UnWou01PppOZhIXNj1P9vo9zH5PK1Waz2V5wWlxIPgMuJJ8Bi0h+fn7eXl9fb29ubrYPDw/dO/8DHh8fu/vB4kym4Orqaofb29vund8OSSbhemewSrugBMnG3vlvw9vb265yn56edmtz/t/f33+5C8MkixQSZSsl9UzLOHUmcwTYAN/Rpl5eXnY+pnIB0Xd3d7s5m3rvDsrkCGszNiQ7r/tr4v39fSc/uipOqRcqufTHBiO78GGdzG5xcLtIFmVde7L9NsvXRo9s84+Pj+79pUAwn5GcD1wIz5r+fYGeJdnjGiF9hwL7iWAcfX19/evtKVHJXrtN8Rf4A3TVczqhrut5i1mSZQgnIriSWtdzP2N+EvIhi3/GWqHWtWXuy2IYbheiKarJZIZknkxyrryc2Utrgal+9S8iScUXIx/3kcxfe/jotcuDezLFlIbARDrzHpytXdKnQr4xyc74Vu9YV5Ih2Q/tT7mDSEYw5ZU4wu3nJx64k/1z9umlUG0hah/JSbC6Jzi5exDJWoTHERoBxu8uf/pT1j3HDkUIJitjbRfRA/iwVzlgy1RCfSF5ili9xj7BUWKs9wJZ3MpditYu+lsc+/PRx53cVF9Pdg/syE9Hb6cS75PkmhUEUFofmTvLGEXKimHueJP9Y3swWQwGLUiA9xEbHKuvgs4pPe1+1myTAKlw81buJ8kigjAXKauXPLQPhEYgJSEYsgdTUR0BmTVgc6C359wcvKGnBrGO8dO5VlD1ZZ519nrBHvrwKVMCas9hgL0YUI2wV98fC4FqCWizzXyqF44A0ZKLHkilgvPs1zbiTuZIdZ414KvqGCKZYx4zple+MSrrJVncAyL02/TOqncJwVMglx5zI4QDZ5WPvBGEcNP+7TlEcqJIAQFGsIdQjmZt7MlYA5yiI3pOQTCQXUm2TuVmXgmewxDJQDgl6deJJoU5y7p9uwZagmu1mCvbNoOOBfkhOf6lRZjzPb8qRjBMMiUhM9GNMZQq5/oRXBP7Mlj/i12A7EMIaJGqDcl8I79+/N1xTvdINQ2TDAQSvI9Md479vdqCHKSFQKAfEmgBqCTDkjaSgOZXQkg2jy1ti0xApnBQJo/0obQRipeQXbN3CmxKGQch5xgki4Efghl/kFqzPD//2DnXIodIRpaoETaXxcmwGNO7N4I2Oyuc6b+xK/tL9IH3kY/E+r1JdST4yM+7VUiuJbuPZHBeHZcNvXtziMMV9mRuvUOX8Vg9IFjRx9dUYM3s2oJyNx9ahFfSWwyRHKHG3nmL2q/mojyFVAWnEdi2Hg7OBXwUCCKr1QEtoe0+/9jI3xqIiuF2QRD0zqcwpfQnge9TVSI4tWrNe79shj98F0xDC0N4bTUVF5LPgAvJJ8dm+wcP2iJuZNdC5QAAAABJRU5ErkJggg==`,
|
value: `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFkAAAAgCAYAAAB5JtSmAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAQ0SURBVGhD7dhrUSNBFAVgvKACEVjAAhJQgAIUYAABGEAABvgfAdn6UnWou01PppOZhIXNj1P9vo9zH5PK1Waz2V5wWlxIPgMuJJ8Bi0h+fn7eXl9fb29ubrYPDw/dO/8DHh8fu/vB4kym4Orqaofb29vund8OSSbhemewSrugBMnG3vlvw9vb265yn56edmtz/t/f33+5C8MkixQSZSsl9UzLOHUmcwTYAN/Rpl5eXnY+pnIB0Xd3d7s5m3rvDsrkCGszNiQ7r/tr4v39fSc/uipOqRcqufTHBiO78GGdzG5xcLtIFmVde7L9NsvXRo9s84+Pj+79pUAwn5GcD1wIz5r+fYGeJdnjGiF9hwL7iWAcfX19/evtKVHJXrtN8Rf4A3TVczqhrut5i1mSZQgnIriSWtdzP2N+EvIhi3/GWqHWtWXuy2IYbheiKarJZIZknkxyrryc2Utrgal+9S8iScUXIx/3kcxfe/jotcuDezLFlIbARDrzHpytXdKnQr4xyc74Vu9YV5Ih2Q/tT7mDSEYw5ZU4wu3nJx64k/1z9umlUG0hah/JSbC6Jzi5exDJWoTHERoBxu8uf/pT1j3HDkUIJitjbRfRA/iwVzlgy1RCfSF5ili9xj7BUWKs9wJZ3MpditYu+lsc+/PRx53cVF9Pdg/syE9Hb6cS75PkmhUEUFofmTvLGEXKimHueJP9Y3swWQwGLUiA9xEbHKuvgs4pPe1+1myTAKlw81buJ8kigjAXKauXPLQPhEYgJSEYsgdTUR0BmTVgc6C359wcvKGnBrGO8dO5VlD1ZZ519nrBHvrwKVMCas9hgL0YUI2wV98fC4FqCWizzXyqF44A0ZKLHkilgvPs1zbiTuZIdZ414KvqGCKZYx4zple+MSrrJVncAyL02/TOqncJwVMglx5zI4QDZ5WPvBGEcNP+7TlEcqJIAQFGsIdQjmZt7MlYA5yiI3pOQTCQXUm2TuVmXgmewxDJQDgl6deJJoU5y7p9uwZagmu1mCvbNoOOBfkhOf6lRZjzPb8qRjBMMiUhM9GNMZQq5/oRXBP7Mlj/i12A7EMIaJGqDcl8I79+/N1xTvdINQ2TDAQSvI9Md479vdqCHKSFQKAfEmgBqCTDkjaSgOZXQkg2jy1ti0xApnBQJo/0obQRipeQXbN3CmxKGQch5xgki4Efghl/kFqzPD//2DnXIodIRpaoETaXxcmwGNO7N4I2Oyuc6b+xK/tL9IH3kY/E+r1JdST4yM+7VUiuJbuPZHBeHZcNvXtziMMV9mRuvUOX8Vg9IFjRx9dUYM3s2oJyNx9ahFfSWwyRHKHG3nmL2q/mojyFVAWnEdi2Hg7OBXwUCCKr1QEtoe0+/9jI3xqIiuF2QRD0zqcwpfQnge9TVSI4tWrNe79shj98F0xDC0N4bTUVF5LPgAvJJ8dm+wcP2iJuZNdC5QAAAABJRU5ErkJggg==`,
|
||||||
width: 89,
|
width: 89,
|
||||||
height: 32,
|
height: 32,
|
||||||
@@ -72,7 +70,8 @@ window.onload = function () {
|
|||||||
type: ElementType.IMAGE
|
type: ElementType.IMAGE
|
||||||
})
|
})
|
||||||
// 初始化编辑器
|
// 初始化编辑器
|
||||||
const instance = new Editor(canvas, data, {
|
const container = document.querySelector<HTMLDivElement>('.editor')!
|
||||||
|
const instance = new Editor(container, data, {
|
||||||
margins: [100, 120, 100, 120]
|
margins: [100, 120, 100, 120]
|
||||||
})
|
})
|
||||||
console.log('编辑器实例: ', instance)
|
console.log('编辑器实例: ', instance)
|
||||||
|
|||||||
+10
-2
@@ -249,6 +249,8 @@ ul {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.menu-item .menu-item__search__collapse {
|
.menu-item .menu-item__search__collapse {
|
||||||
|
width: 215px;
|
||||||
|
height: 36px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
display: none;
|
display: none;
|
||||||
@@ -298,10 +300,16 @@ ul {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.editor {
|
.editor {
|
||||||
width: 794px;
|
|
||||||
height: 1123px;
|
|
||||||
margin: 80px auto;
|
margin: 80px auto;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-container {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-container canvas {
|
||||||
|
display: block;
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
box-shadow: rgb(158 161 165 / 40%) 0px 2px 12px 0px;
|
box-shadow: rgb(158 161 165 / 40%) 0px 2px 12px 0px;
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user