feat:edit page header

pr675
Hufe921 3 years ago
parent da2dfd3a16
commit 6082ab26d1

@ -62,8 +62,9 @@ export class Cursor {
// 设置光标代理 // 设置光标代理
const height = this.draw.getHeight() const height = this.draw.getHeight()
const pageGap = this.draw.getPageGap() const pageGap = this.draw.getPageGap()
const { metrics, coordinate: { leftTop, rightTop }, ascent, pageNo } = cursorPosition const { metrics, coordinate: { leftTop, rightTop }, ascent } = cursorPosition
const preY = pageNo * (height + pageGap) const curPageNo = this.draw.getPageNo()
const preY = curPageNo * (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

@ -1,9 +1,9 @@
import { version } from '../../../../package.json' import { version } from '../../../../package.json'
import { ZERO } from '../../dataset/constant/Common' import { ZERO } from '../../dataset/constant/Common'
import { RowFlex } from '../../dataset/enum/Row' import { RowFlex } from '../../dataset/enum/Row'
import { IDrawOption, IDrawRowPayload, IPainterOptions } from '../../interface/Draw' import { IDrawOption, IDrawPagePayload, IDrawRowPayload, IPainterOptions } from '../../interface/Draw'
import { IEditorDrawData, IEditorOption, IEditorResult } from '../../interface/Editor' import { IEditorData, IEditorOption, IEditorResult } from '../../interface/Editor'
import { IElement, IElementMetrics, IElementPosition, IElementFillRect, IElementStyle } from '../../interface/Element' import { IElement, IElementMetrics, IElementFillRect, IElementStyle } from '../../interface/Element'
import { IRow, IRowElement } from '../../interface/Row' import { IRow, IRowElement } from '../../interface/Row'
import { deepClone, getUUID, nextTick } from '../../utils' import { deepClone, getUUID, nextTick } from '../../utils'
import { Cursor } from '../cursor/Cursor' import { Cursor } from '../cursor/Cursor'
@ -35,7 +35,7 @@ import { SubscriptParticle } from './particle/Subscript'
import { SeparatorParticle } from './particle/Separator' import { SeparatorParticle } from './particle/Separator'
import { PageBreakParticle } from './particle/PageBreak' import { PageBreakParticle } from './particle/PageBreak'
import { Watermark } from './frame/Watermark' import { Watermark } from './frame/Watermark'
import { EditorComponent, EditorMode, PageMode, PaperDirection } from '../../dataset/enum/Editor' import { EditorComponent, EditorMode, EditorZone, PageMode, PaperDirection } from '../../dataset/enum/Editor'
import { Control } from './control/Control' import { Control } from './control/Control'
import { zipElementList } from '../../utils/element' import { zipElementList } from '../../utils/element'
import { CheckboxParticle } from './particle/CheckboxParticle' import { CheckboxParticle } from './particle/CheckboxParticle'
@ -50,6 +50,7 @@ import { BlockParticle } from './particle/block/BlockParticle'
import { EDITOR_COMPONENT, EDITOR_PREFIX } from '../../dataset/constant/Editor' import { EDITOR_COMPONENT, EDITOR_PREFIX } from '../../dataset/constant/Editor'
import { I18n } from '../i18n/I18n' import { I18n } from '../i18n/I18n'
import { ImageObserver } from '../observer/ImageObserver' import { ImageObserver } from '../observer/ImageObserver'
import { Zone } from '../zone/Zone'
export class Draw { export class Draw {
@ -61,6 +62,7 @@ export class Draw {
private mode: EditorMode private mode: EditorMode
private options: DeepRequired<IEditorOption> private options: DeepRequired<IEditorOption>
private position: Position private position: Position
private zone: Zone
private headerElementList: IElement[] private headerElementList: IElement[]
private elementList: IElement[] private elementList: IElement[]
private footerElementList: IElement[] private footerElementList: IElement[]
@ -112,7 +114,7 @@ export class Draw {
constructor( constructor(
rootContainer: HTMLElement, rootContainer: HTMLElement,
options: DeepRequired<IEditorOption>, options: DeepRequired<IEditorOption>,
data: IEditorDrawData, data: IEditorData,
listener: Listener listener: Listener
) { ) {
this.container = this._wrapContainer(rootContainer) this.container = this._wrapContainer(rootContainer)
@ -133,6 +135,7 @@ export class Draw {
this.i18n = new I18n() this.i18n = new I18n()
this.historyManager = new HistoryManager() this.historyManager = new HistoryManager()
this.position = new Position(this) this.position = new Position(this)
this.zone = new Zone()
this.range = new RangeManager(this) this.range = new RangeManager(this)
this.margin = new Margin(this) this.margin = new Margin(this)
this.background = new Background(this) this.background = new Background(this)
@ -212,6 +215,12 @@ export class Draw {
return Math.floor(this.getOriginalHeight() * this.options.scale) return Math.floor(this.getOriginalHeight() * this.options.scale)
} }
public getOriginalMainHeight(): number {
const mainHeight = this.getOriginalHeight()
const extraHeight = this.header.getExtraHeight()
return mainHeight - extraHeight
}
public getCanvasWidth(pageNo = -1): number { public getCanvasWidth(pageNo = -1): number {
const page = this.getPage(pageNo) const page = this.getPage(pageNo)
return page.width return page.width
@ -343,6 +352,10 @@ export class Draw {
return this.position return this.position
} }
public getZone(): Zone {
return this.zone
}
public getRange(): RangeManager { public getRange(): RangeManager {
return this.range return this.range
} }
@ -351,12 +364,35 @@ export class Draw {
return this.headerElementList return this.headerElementList
} }
public getElementList(): IElement[] { public getTableElementList(sourceElementList: IElement[]): IElement[] {
const positionContext = this.position.getPositionContext() const positionContext = this.position.getPositionContext()
if (positionContext.isTable) {
const { index, trIndex, tdIndex } = positionContext const { index, trIndex, tdIndex } = positionContext
return this.elementList[index!].trList![trIndex!].tdList[tdIndex!].value return sourceElementList[index!].trList![trIndex!].tdList[tdIndex!].value
} }
public getElementList(): IElement[] {
const positionContext = this.position.getPositionContext()
const elementList = this.getOriginalElementList()
return positionContext.isTable
? this.getTableElementList(elementList)
: elementList
}
public getMainElementList(): IElement[] {
const positionContext = this.position.getPositionContext()
return positionContext.isTable
? this.getTableElementList(this.elementList)
: this.elementList
}
public getOriginalElementList() {
const zoneManager = this.getZone()
return zoneManager.isHeaderActive()
? this.header.getElementList()
: this.elementList
}
public getOriginalMainElementList(): IElement[] {
return this.elementList return this.elementList
} }
@ -406,10 +442,6 @@ export class Draw {
} }
} }
public getOriginalElementList() {
return this.elementList
}
public getCanvasEvent(): CanvasEvent { public getCanvasEvent(): CanvasEvent {
return this.canvasEvent return this.canvasEvent
} }
@ -434,6 +466,10 @@ export class Draw {
return this.tableTool return this.tableTool
} }
public getHeader(): Header {
return this.header
}
public getHyperlinkParticle(): HyperlinkParticle { public getHyperlinkParticle(): HyperlinkParticle {
return this.hyperlinkParticle return this.hyperlinkParticle
} }
@ -610,7 +646,7 @@ export class Draw {
// 配置 // 配置
const { width, height, margins, watermark } = this.options const { width, height, margins, watermark } = this.options
// 数据 // 数据
const data: IEditorDrawData = { const data: IEditorData = {
header: zipElementList(this.headerElementList), header: zipElementList(this.headerElementList),
main: zipElementList(this.elementList) main: zipElementList(this.elementList)
} }
@ -768,7 +804,7 @@ export class Draw {
metrics.boundingBoxAscent = 0 metrics.boundingBoxAscent = 0
// 表格分页处理(拆分表格) // 表格分页处理(拆分表格)
const margins = this.getMargins() const margins = this.getMargins()
const height = this.getHeight() const height = this.getOriginalMainHeight()
const marginHeight = margins[0] + margins[2] const marginHeight = margins[0] + margins[2]
let curPagePreHeight = marginHeight let curPagePreHeight = marginHeight
for (let r = 0; r < rowList.length; r++) { for (let r = 0; r < rowList.length; r++) {
@ -826,13 +862,13 @@ export class Draw {
} else if (element.type === ElementType.SEPARATOR) { } else if (element.type === ElementType.SEPARATOR) {
element.width = innerWidth element.width = innerWidth
metrics.width = innerWidth metrics.width = innerWidth
metrics.height = this.options.defaultSize metrics.height = defaultSize
metrics.boundingBoxAscent = -rowMargin metrics.boundingBoxAscent = -rowMargin
metrics.boundingBoxDescent = -rowMargin metrics.boundingBoxDescent = -rowMargin
} else if (element.type === ElementType.PAGE_BREAK) { } else if (element.type === ElementType.PAGE_BREAK) {
element.width = innerWidth element.width = innerWidth
metrics.width = innerWidth metrics.width = innerWidth
metrics.height = this.options.defaultSize metrics.height = defaultSize
} else if ( } else if (
element.type === ElementType.CHECKBOX || element.type === ElementType.CHECKBOX ||
element.controlComponent === ControlComponent.CHECKBOX element.controlComponent === ControlComponent.CHECKBOX
@ -860,7 +896,7 @@ export class Draw {
metrics.boundingBoxAscent = 0 metrics.boundingBoxAscent = 0
} else { } else {
// 设置上下标真实字体尺寸 // 设置上下标真实字体尺寸
const size = element.size || this.options.defaultSize const size = element.size || defaultSize
if (element.type === ElementType.SUPERSCRIPT || element.type === ElementType.SUBSCRIPT) { if (element.type === ElementType.SUPERSCRIPT || element.type === ElementType.SUBSCRIPT) {
element.actualSize = Math.ceil(size * 0.6) element.actualSize = Math.ceil(size * 0.6)
} }
@ -934,7 +970,7 @@ export class Draw {
private _computePageList(): IRow[][] { private _computePageList(): IRow[][] {
const pageRowList: IRow[][] = [[]] const pageRowList: IRow[][] = [[]]
const { pageMode } = this.options const { pageMode } = this.options
const height = this.getHeight() const height = this.getOriginalMainHeight()
const margins = this.getMargins() const margins = this.getMargins()
const marginHeight = margins[0] + margins[2] const marginHeight = margins[0] + margins[2]
let pageHeight = marginHeight let pageHeight = marginHeight
@ -979,7 +1015,7 @@ export class Draw {
} }
public drawRow(ctx: CanvasRenderingContext2D, payload: IDrawRowPayload) { public drawRow(ctx: CanvasRenderingContext2D, payload: IDrawRowPayload) {
const { rowList, pageNo, positionList, startIndex } = payload const { rowList, pageNo, elementList, positionList, startIndex, zone } = payload
const { scale, tdPadding } = this.options const { scale, tdPadding } = this.options
const { isCrossRowCol, tableId } = this.range.getRange() const { isCrossRowCol, tableId } = this.range.getRange()
let index = startIndex let index = startIndex
@ -1077,11 +1113,11 @@ export class Draw {
this.highlight.render(ctx) this.highlight.render(ctx)
} }
// 选区记录 // 选区记录
const { startIndex, endIndex } = this.range.getRange() const { zone: currentZone, startIndex, endIndex } = this.range.getRange()
if (startIndex !== endIndex && startIndex <= index && index <= endIndex) { if (currentZone === zone && startIndex !== endIndex && startIndex <= index && index <= endIndex) {
// 从行尾开始-绘制最小宽度 // 从行尾开始-绘制最小宽度
if (startIndex === index) { if (startIndex === index) {
const nextElement = this.elementList[startIndex + 1] const nextElement = elementList[startIndex + 1]
if (nextElement && nextElement.value === ZERO) { if (nextElement && nextElement.value === ZERO) {
rangeRecord.x = x + metrics.width rangeRecord.x = x + metrics.width
rangeRecord.y = y rangeRecord.y = y
@ -1119,11 +1155,13 @@ export class Draw {
for (let d = 0; d < tr.tdList!.length; d++) { for (let d = 0; d < tr.tdList!.length; d++) {
const td = tr.tdList[d] const td = tr.tdList[d]
this.drawRow(ctx, { this.drawRow(ctx, {
elementList: td.value,
positionList: td.positionList!, positionList: td.positionList!,
rowList: td.rowList!, rowList: td.rowList!,
pageNo, pageNo,
startIndex: 0, startIndex: 0,
innerWidth: (td.width! - tdGap) * scale innerWidth: (td.width! - tdGap) * scale,
zone
}) })
} }
} }
@ -1150,10 +1188,13 @@ export class Draw {
this.blockParticle.clear() this.blockParticle.clear()
} }
private _drawPage(positionList: IElementPosition[], rowList: IRow[], pageNo: number) { private _drawPage(payload: IDrawPagePayload) {
const { pageMode } = this.options const { elementList, positionList, rowList, pageNo } = payload
const { inactiveAlpha, pageMode } = this.options
const innerWidth = this.getInnerWidth() const innerWidth = this.getInnerWidth()
const ctx = this.ctxList[pageNo] const ctx = this.ctxList[pageNo]
// 判断当前激活区域-激活页眉时主题元素透明度降低
ctx.globalAlpha = this.zone.isHeaderActive() ? inactiveAlpha : 1
this._clearPage(pageNo) this._clearPage(pageNo)
// 绘制背景 // 绘制背景
this.background.render(ctx) this.background.render(ctx)
@ -1162,14 +1203,16 @@ export class Draw {
// 渲染元素 // 渲染元素
const index = rowList[0].startIndex const index = rowList[0].startIndex
this.drawRow(ctx, { this.drawRow(ctx, {
elementList,
positionList, positionList,
rowList, rowList,
pageNo, pageNo,
startIndex: index, startIndex: index,
innerWidth innerWidth,
zone: EditorZone.MAIN
}) })
// 绘制页眉 // 绘制页眉
this.header.render(ctx) this.header.render(ctx, pageNo)
// 绘制页码 // 绘制页码
this.pageNumber.render(ctx, pageNo) this.pageNumber.render(ctx, pageNo)
// 搜索匹配绘制 // 搜索匹配绘制
@ -1183,14 +1226,20 @@ export class Draw {
} }
private _lazyRender() { private _lazyRender() {
const positionList = this.position.getOriginalPositionList() const positionList = this.position.getOriginalMainPositionList()
const elementList = this.getOriginalMainElementList()
this.lazyRenderIntersectionObserver?.disconnect() this.lazyRenderIntersectionObserver?.disconnect()
this.lazyRenderIntersectionObserver = new IntersectionObserver((entries) => { this.lazyRenderIntersectionObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => { entries.forEach(entry => {
if (entry.isIntersecting) { if (entry.isIntersecting) {
const index = Number((<HTMLCanvasElement>entry.target).dataset.index) const index = Number((<HTMLCanvasElement>entry.target).dataset.index)
this.header.render(this.ctxList[index]) this.header.render(this.ctxList[index], index)
this._drawPage(positionList, this.pageRowList[index], index) this._drawPage({
elementList,
positionList,
rowList: this.pageRowList[index],
pageNo: index
})
} }
}) })
}) })
@ -1200,9 +1249,15 @@ export class Draw {
} }
private _immediateRender() { private _immediateRender() {
const positionList = this.position.getOriginalPositionList() const positionList = this.position.getOriginalMainPositionList()
const elementList = this.getOriginalMainElementList()
for (let i = 0; i < this.pageRowList.length; i++) { for (let i = 0; i < this.pageRowList.length; i++) {
this._drawPage(positionList, this.pageRowList[i], i) this._drawPage({
elementList,
positionList,
rowList: this.pageRowList[i],
pageNo: i
})
} }
} }
@ -1236,7 +1291,6 @@ export class Draw {
this.imageObserver.clearAll() this.imageObserver.clearAll()
this.cursor.recoveryCursor() this.cursor.recoveryCursor()
// 创建纸张 // 创建纸张
const positionList = this.position.getOriginalPositionList()
for (let i = 0; i < this.pageRowList.length; i++) { for (let i = 0; i < this.pageRowList.length; i++) {
if (!this.pageList[i]) { if (!this.pageList[i]) {
this._createPage(i) this._createPage(i)
@ -1260,20 +1314,19 @@ export class Draw {
} }
// 光标重绘 // 光标重绘
if (isSetCursor) { if (isSetCursor) {
const positionList = this.position.getPositionList()
const positionContext = this.position.getPositionContext() const positionContext = this.position.getPositionContext()
if (positionContext.isTable) { if (positionContext.isTable) {
const { index, trIndex, tdIndex } = positionContext const { index, trIndex, tdIndex } = positionContext
const tablePositionList = this.elementList[index!].trList?.[trIndex!].tdList[tdIndex!].positionList const elementList = this.getElementList()
const tablePositionList = elementList[index!].trList?.[trIndex!].tdList[tdIndex!].positionList
if (curIndex === undefined && tablePositionList) { if (curIndex === undefined && tablePositionList) {
curIndex = tablePositionList.length - 1 curIndex = tablePositionList.length - 1
} }
const tablePosition = tablePositionList?.[curIndex!] const tablePosition = tablePositionList?.[curIndex!]
this.position.setCursorPosition(tablePosition || null) this.position.setCursorPosition(tablePosition || null)
} else { } else {
if (curIndex === undefined) { this.position.setCursorPosition(curIndex !== undefined ? positionList[curIndex] : null)
curIndex = positionList.length - 1
}
this.position.setCursorPosition(positionList[curIndex!] || null)
} }
this.cursor.drawCursor() this.cursor.drawCursor()
} }
@ -1281,12 +1334,16 @@ export class Draw {
if (isSubmitHistory) { if (isSubmitHistory) {
const self = this const self = this
const oldElementList = deepClone(this.elementList) const oldElementList = deepClone(this.elementList)
const oldHeaderElementList = deepClone(this.header.getElementList())
const { startIndex, endIndex } = this.range.getRange() const { startIndex, endIndex } = this.range.getRange()
const pageNo = this.pageNo const pageNo = this.pageNo
const oldPositionContext = deepClone(this.position.getPositionContext()) const oldPositionContext = deepClone(this.position.getPositionContext())
const zone = this.zone.getZone()
this.historyManager.execute(function () { this.historyManager.execute(function () {
self.zone.setZone(zone)
self.setPageNo(pageNo) self.setPageNo(pageNo)
self.position.setPositionContext(oldPositionContext) self.position.setPositionContext(oldPositionContext)
self.header.setElementList(oldHeaderElementList)
self.elementList = deepClone(oldElementList) self.elementList = deepClone(oldElementList)
self.range.setRange(startIndex, endIndex) self.range.setRange(startIndex, endIndex)
self.render({ curIndex, isSubmitHistory: false }) self.render({ curIndex, isSubmitHistory: false })

@ -1,3 +1,5 @@
import { maxHeightRadioMapping } from '../../../dataset/constant/Header'
import { EditorZone } from '../../../dataset/enum/Editor'
import { DeepRequired } from '../../../interface/Common' import { DeepRequired } from '../../../interface/Common'
import { IEditorOption } from '../../../interface/Editor' import { IEditorOption } from '../../../interface/Editor'
import { IElement, IElementPosition } from '../../../interface/Element' import { IElement, IElementPosition } from '../../../interface/Element'
@ -25,6 +27,18 @@ export class Header {
this.positionList = [] this.positionList = []
} }
public setElementList(elementList: IElement[]) {
this.elementList = elementList
}
public getElementList(): IElement[] {
return this.elementList
}
public getPositionList(): IElementPosition[] {
return this.positionList
}
public compute() { public compute() {
this._recovery() this._recovery()
this._computeRowList() this._computeRowList()
@ -46,7 +60,7 @@ export class Header {
const innerWidth = this.draw.getInnerWidth() const innerWidth = this.draw.getInnerWidth()
const margins = this.draw.getMargins() const margins = this.draw.getMargins()
const startX = margins[3] const startX = margins[3]
const startY = margins[0] + top const startY = top
this.position.computePageRowPosition({ this.position.computePageRowPosition({
positionList: this.positionList, positionList: this.positionList,
rowList: this.rowList, rowList: this.rowList,
@ -58,14 +72,53 @@ export class Header {
}) })
} }
public render(ctx: CanvasRenderingContext2D) { public getMaxHeight(): number {
const { header: { maxHeightRadio }, height } = this.options
return height * maxHeightRadioMapping[maxHeightRadio]
}
public getHeight(): number {
const maxHeight = this.getMaxHeight()
const rowHeight = this.getRowHeight()
return rowHeight > maxHeight ? maxHeight : rowHeight
}
public getRowHeight(): number {
return this.rowList.reduce((pre, cur) => pre + cur.height, 0)
}
public getExtraHeight(): number {
const { header: { top: headerTop } } = this.options
// 页眉上边距 + 实际高 - 页面上边距
const rowHeight = this.getRowHeight()
const margins = this.draw.getOriginalMargins()
const extraHeight = headerTop + rowHeight - margins[0]
return extraHeight <= 0 ? 0 : extraHeight
}
public render(ctx: CanvasRenderingContext2D, pageNo: number) {
ctx.globalAlpha = 1
const innerWidth = this.draw.getInnerWidth() const innerWidth = this.draw.getInnerWidth()
const maxHeight = this.getMaxHeight()
// 超出最大高度不渲染
const rowList: IRow[] = []
let curRowHeight = 0
for (let r = 0; r < this.rowList.length; r++) {
const row = this.rowList[r]
if (curRowHeight + row.height > maxHeight) {
break
}
rowList.push(row)
curRowHeight += row.height
}
this.draw.drawRow(ctx, { this.draw.drawRow(ctx, {
elementList: this.elementList,
positionList: this.positionList, positionList: this.positionList,
rowList: this.rowList, rowList,
pageNo: 0, pageNo,
startIndex: 0, startIndex: 0,
innerWidth innerWidth,
zone: EditorZone.HEADER
}) })
} }

@ -147,8 +147,8 @@ export class CanvasEvent {
keydown(evt, this) keydown(evt, this)
} }
public dblclick() { public dblclick(evt: MouseEvent) {
click.dblclick(this) click.dblclick(this, evt)
} }
public threeClick() { public threeClick() {

@ -2,9 +2,25 @@ import { ZERO } from '../../../dataset/constant/Common'
import { LETTER_REG, NUMBER_LIKE_REG } from '../../../dataset/constant/Regular' import { LETTER_REG, NUMBER_LIKE_REG } from '../../../dataset/constant/Regular'
import { CanvasEvent } from '../CanvasEvent' import { CanvasEvent } from '../CanvasEvent'
function dblclick(host: CanvasEvent) { function dblclick(host: CanvasEvent, evt: MouseEvent) {
// 切换区域
const draw = host.getDraw() const draw = host.getDraw()
const position = draw.getPosition() const position = draw.getPosition()
const positionContext = position.getPositionByXY({
x: evt.offsetX,
y: evt.offsetY
})
if (!~positionContext.index && positionContext.zone) {
const zoneManager = draw.getZone()
zoneManager.setZone(positionContext.zone)
draw.render({
isSubmitHistory: false,
isSetCursor: false,
isCompute: false
})
return
}
// 自动扩选文字
const cursorPosition = position.getCursorPosition() const cursorPosition = position.getCursorPosition()
if (!cursorPosition) return if (!cursorPosition) return
const { value, index } = cursorPosition const { value, index } = cursorPosition

@ -21,10 +21,12 @@ function dragover(evt: DragEvent | MouseEvent, host: CanvasEvent) {
draw.setPageNo(Number(pageIndex)) draw.setPageNo(Number(pageIndex))
} }
const position = draw.getPosition() const position = draw.getPosition()
const { isTable, tdValueIndex, index } = position.adjustPositionContext({ const positionContext = position.adjustPositionContext({
x: evt.offsetX, x: evt.offsetX,
y: evt.offsetY y: evt.offsetY
}) })
if (!positionContext) return
const { isTable, tdValueIndex, index } = positionContext
// 设置选区及光标位置 // 设置选区及光标位置
const positionList = position.getPositionList() const positionList = position.getPositionList()
const curIndex = isTable ? tdValueIndex! : index const curIndex = isTable ? tdValueIndex! : index

@ -36,6 +36,7 @@ export function mousedown(evt: MouseEvent, host: CanvasEvent) {
x: evt.offsetX, x: evt.offsetX,
y: evt.offsetY y: evt.offsetY
}) })
if (!positionResult) return
const { const {
index, index,
isDirectHit, isDirectHit,

@ -32,6 +32,7 @@ export function mousemove(evt: MouseEvent, host: CanvasEvent) {
x: evt.offsetX, x: evt.offsetX,
y: evt.offsetY y: evt.offsetY
}) })
if (!~positionResult.index) return
const { const {
index, index,
isTable, isTable,

@ -3,9 +3,10 @@ import { ZERO } from '../../dataset/constant/Common'
import { ControlComponent, ImageDisplay } from '../../dataset/enum/Control' import { ControlComponent, ImageDisplay } from '../../dataset/enum/Control'
import { IComputePageRowPositionPayload, IComputePageRowPositionResult } from '../../interface/Position' import { IComputePageRowPositionPayload, IComputePageRowPositionResult } from '../../interface/Position'
import { IEditorOption } from '../../interface/Editor' import { IEditorOption } from '../../interface/Editor'
import { IElementPosition } from '../../interface/Element' import { IElement, IElementPosition } from '../../interface/Element'
import { ICurrentPosition, IGetPositionByXYPayload, IPositionContext } from '../../interface/Position' import { ICurrentPosition, IGetPositionByXYPayload, IPositionContext } from '../../interface/Position'
import { Draw } from '../draw/Draw' import { Draw } from '../draw/Draw'
import { EditorZone } from '../../dataset/enum/Editor'
export class Position { export class Position {
@ -28,17 +29,32 @@ export class Position {
this.options = draw.getOptions() this.options = draw.getOptions()
} }
public getOriginalPositionList(): IElementPosition[] { public getTablePositionList(sourceElementList: IElement[]): IElementPosition[] {
return this.positionList const { index, trIndex, tdIndex } = this.positionContext
return sourceElementList[index!].trList![trIndex!].tdList[tdIndex!].positionList || []
} }
public getPositionList(): IElementPosition[] { public getPositionList(): IElementPosition[] {
const { isTable } = this.positionContext const elementList = this.draw.getElementList()
if (isTable) { return this.positionContext.isTable
const { index, trIndex, tdIndex } = this.positionContext ? this.getTablePositionList(elementList)
const elementList = this.draw.getOriginalElementList() : this.getOriginalPositionList()
return elementList[index!].trList![trIndex!].tdList[tdIndex!].positionList || [] }
public getMainPositionList(): IElementPosition[] {
const elementList = this.draw.getMainElementList()
return this.positionContext.isTable
? this.getTablePositionList(elementList)
: this.positionList
}
public getOriginalPositionList(): IElementPosition[] {
const zoneManager = this.draw.getZone()
const header = this.draw.getHeader()
return zoneManager.isHeaderActive() ? header.getPositionList() : this.positionList
} }
public getOriginalMainPositionList(): IElementPosition[] {
return this.positionList return this.positionList
} }
@ -130,7 +146,10 @@ export class Position {
const pageRowList = this.draw.getPageRowList() const pageRowList = this.draw.getPageRowList()
const margins = this.draw.getMargins() const margins = this.draw.getMargins()
const startX = margins[3] const startX = margins[3]
const startY = margins[0] // 起始位置受页眉影响
const header = this.draw.getHeader()
const extraHeight = header.getExtraHeight()
const startY = margins[0] + extraHeight
for (let i = 0; i < pageRowList.length; i++) { for (let i = 0; i < pageRowList.length; i++) {
const rowList = pageRowList[i] const rowList = pageRowList[i]
const startIndex = rowList[0].startIndex const startIndex = rowList[0].startIndex
@ -169,12 +188,14 @@ export class Position {
elementList = this.draw.getOriginalElementList() elementList = this.draw.getOriginalElementList()
} }
if (!positionList) { if (!positionList) {
positionList = this.positionList positionList = this.getOriginalPositionList()
} }
const zoneManager = this.draw.getZone()
const curPageNo = this.draw.getPageNo() const curPageNo = this.draw.getPageNo()
const positionNo = zoneManager.isMainActive() ? curPageNo : 0
for (let j = 0; j < positionList.length; j++) { for (let j = 0; j < positionList.length; j++) {
const { index, pageNo, coordinate: { leftTop, rightTop, leftBottom } } = positionList[j] const { index, pageNo, coordinate: { leftTop, rightTop, leftBottom } } = positionList[j]
if (curPageNo !== pageNo) continue if (positionNo !== pageNo) continue
// 命中元素 // 命中元素
if (leftTop[0] <= x && rightTop[0] >= x && leftTop[1] <= y && leftBottom[1] >= y) { if (leftTop[0] <= x && rightTop[0] >= x && leftTop[1] <= y && leftBottom[1] >= y) {
let curPositionIndex = j let curPositionIndex = j
@ -267,15 +288,15 @@ export class Position {
} }
} }
// 判断所属行是否存在元素 // 判断所属行是否存在元素
const firstLetterList = positionList.filter(p => p.isLastLetter && p.pageNo === curPageNo) const firstLetterList = positionList.filter(p => p.isLastLetter && p.pageNo === positionNo)
for (let j = 0; j < firstLetterList.length; j++) { for (let j = 0; j < firstLetterList.length; j++) {
const { index, pageNo, coordinate: { leftTop, leftBottom } } = firstLetterList[j] const { index, pageNo, coordinate: { leftTop, leftBottom } } = firstLetterList[j]
if (curPageNo !== pageNo) continue if (positionNo !== 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]
// 是否在头部 // 是否在头部
if (isHead) { if (isHead) {
const headIndex = positionList.findIndex(p => p.pageNo === curPageNo && p.rowNo === firstLetterList[j].rowNo) const headIndex = positionList.findIndex(p => p.pageNo === positionNo && p.rowNo === firstLetterList[j].rowNo)
curPositionIndex = ~headIndex ? headIndex - 1 : index curPositionIndex = ~headIndex ? headIndex - 1 : index
} else { } else {
curPositionIndex = index curPositionIndex = index
@ -285,8 +306,28 @@ export class Position {
} }
} }
if (!isLastArea) { if (!isLastArea) {
// 判断所属位置是否属于header区域当前位置小于第一行的上边距
if (zoneManager.isMainActive()) {
if (y < firstLetterList[0].coordinate.leftTop[1]) {
return {
index: -1,
zone: EditorZone.HEADER
}
}
}
// 判断所属位置是否属于main区域当前位置大于第一行的上边距
if (zoneManager.isHeaderActive()) {
if (y > firstLetterList[0].coordinate.leftTop[1]) {
return {
index: -1,
zone: EditorZone.MAIN
}
}
}
// 当前页最后一行 // 当前页最后一行
return { index: firstLetterList[firstLetterList.length - 1]?.index || positionList.length - 1 } return {
index: firstLetterList[firstLetterList.length - 1]?.index || positionList.length - 1,
}
} }
return { return {
index: curPositionIndex, index: curPositionIndex,
@ -294,13 +335,10 @@ export class Position {
} }
} }
public adjustPositionContext(payload: Pick<IGetPositionByXYPayload, 'x' | 'y'>): ICurrentPosition { public adjustPositionContext(payload: IGetPositionByXYPayload): ICurrentPosition | null {
const isReadonly = this.draw.isReadonly() const isReadonly = this.draw.isReadonly()
const { x, y } = payload const positionResult = this.getPositionByXY(payload)
const positionResult = this.getPositionByXY({ if (!~positionResult.index) return null
x,
y
})
// 移动控件内光标 // 移动控件内光标
if (positionResult.isControl && !isReadonly) { if (positionResult.isControl && !isReadonly) {
const { const {

@ -90,6 +90,7 @@ export class RangeManager {
this.range.startTrIndex = startTrIndex this.range.startTrIndex = startTrIndex
this.range.endTrIndex = endTrIndex this.range.endTrIndex = endTrIndex
this.range.isCrossRowCol = !!(startTdIndex || endTdIndex || startTrIndex || endTrIndex) this.range.isCrossRowCol = !!(startTdIndex || endTdIndex || startTrIndex || endTrIndex)
this.range.zone = this.draw.getZone().getZone()
// 激活控件 // 激活控件
const control = this.draw.getControl() const control = this.draw.getControl()
if (~startIndex && ~endIndex) { if (~startIndex && ~endIndex) {

@ -0,0 +1,27 @@
import { EditorZone } from '../../dataset/enum/Editor'
export class Zone {
private currentZone: EditorZone
constructor() {
this.currentZone = EditorZone.MAIN
}
public isHeaderActive(): boolean {
return this.getZone() === EditorZone.HEADER
}
public isMainActive(): boolean {
return this.getZone() === EditorZone.MAIN
}
public getZone(): EditorZone {
return this.currentZone
}
public setZone(payload: EditorZone) {
this.currentZone = payload
}
}

@ -2,6 +2,12 @@ import { IHeader } from '../../interface/Header'
import { HeaderMaxHeightRatio } from '../enum/Header' import { HeaderMaxHeightRatio } from '../enum/Header'
export const defaultHeaderOption: Readonly<Required<IHeader>> = { export const defaultHeaderOption: Readonly<Required<IHeader>> = {
top: -50, top: 30,
maxHeightRadio: HeaderMaxHeightRatio.HALF maxHeightRadio: HeaderMaxHeightRatio.HALF
} }
export const maxHeightRadioMapping: Record<HeaderMaxHeightRatio, number> = {
[HeaderMaxHeightRatio.HALF]: 1 / 2,
[HeaderMaxHeightRatio.ONE_THIRD]: 1 / 3,
[HeaderMaxHeightRatio.QUARTER]: 1 / 4
}

@ -39,7 +39,7 @@ export default class Editor {
public register: Register public register: Register
public destroy: Function public destroy: Function
constructor(container: HTMLDivElement, data: IEditorData, options: IEditorOption = {}) { constructor(container: HTMLDivElement, data: IEditorData | IElement[], options: IEditorOption = {}) {
const headerOptions: Required<IHeader> = { const headerOptions: Required<IHeader> = {
...defaultHeaderOption, ...defaultHeaderOption,
...options.header ...options.header
@ -96,6 +96,7 @@ export default class Editor {
defaultHyperlinkColor: '#0000FF', defaultHyperlinkColor: '#0000FF',
headerTop: 50, headerTop: 50,
paperDirection: PaperDirection.VERTICAL, paperDirection: PaperDirection.VERTICAL,
inactiveAlpha: 0.6,
...options, ...options,
header: headerOptions, header: headerOptions,
watermark: waterMarkOptions, watermark: waterMarkOptions,

@ -1,4 +1,5 @@
import { IElementPosition } from './Element' import { EditorZone } from '../dataset/enum/Editor'
import { IElement, IElementPosition } from './Element'
import { IRow } from './Row' import { IRow } from './Row'
export interface IDrawOption { export interface IDrawOption {
@ -16,11 +17,20 @@ export interface IDrawImagePayload {
} }
export interface IDrawRowPayload { export interface IDrawRowPayload {
elementList: IElement[];
positionList: IElementPosition[]; positionList: IElementPosition[];
rowList: IRow[]; rowList: IRow[];
pageNo: number; pageNo: number;
startIndex: number; startIndex: number;
innerWidth: number; innerWidth: number;
zone: EditorZone;
}
export interface IDrawPagePayload {
elementList: IElement[];
positionList: IElementPosition[];
rowList: IRow[];
pageNo: number;
} }
export interface IPainterOptions { export interface IPainterOptions {

@ -7,14 +7,12 @@ import { IHeader } from './Header'
import { IMargin } from './Margin' import { IMargin } from './Margin'
import { IWatermark } from './Watermark' import { IWatermark } from './Watermark'
export interface IEditorDrawData { export interface IEditorData {
header?: IElement[]; header?: IElement[];
main: IElement[]; main: IElement[];
footer?: IElement[]; footer?: IElement[];
} }
export type IEditorData = IEditorDrawData | IElement[]
export interface IEditorOption { export interface IEditorOption {
mode?: EditorMode; mode?: EditorMode;
defaultType?: string; defaultType?: string;
@ -50,6 +48,7 @@ export interface IEditorOption {
defaultHyperlinkColor?: string; defaultHyperlinkColor?: string;
headerTop?: number; headerTop?: number;
paperDirection?: PaperDirection; paperDirection?: PaperDirection;
inactiveAlpha?: number;
header?: IHeader; header?: IHeader;
watermark?: IWatermark; watermark?: IWatermark;
control?: IControlOption; control?: IControlOption;

@ -1,4 +1,5 @@
import { IElement } from '..' import { IElement } from '..'
import { EditorZone } from '../dataset/enum/Editor'
import { IElementPosition } from './Element' import { IElementPosition } from './Element'
import { IRow } from './Row' import { IRow } from './Row'
import { ITd } from './table/Td' import { ITd } from './table/Td'
@ -16,6 +17,7 @@ export interface ICurrentPosition {
tdId?: string; tdId?: string;
trId?: string; trId?: string;
tableId?: string; tableId?: string;
zone?: EditorZone;
} }
export interface IGetPositionByXYPayload { export interface IGetPositionByXYPayload {

@ -1,3 +1,5 @@
import { EditorZone } from '../dataset/enum/Editor'
export interface IRange { export interface IRange {
startIndex: number; startIndex: number;
endIndex: number; endIndex: number;
@ -7,6 +9,7 @@ export interface IRange {
endTdIndex?: number; endTdIndex?: number;
startTrIndex?: number; startTrIndex?: number;
endTrIndex?: number; endTrIndex?: number;
zone?: EditorZone;
} }
export type RangeRowMap = Map<number, Set<number>> export type RangeRowMap = Map<number, Set<number>>

@ -15,10 +15,16 @@ window.onload = function () {
container, container,
{ {
header: [{ header: [{
value: '人民医院门诊', value: '第一人民医院',
size: 14, size: 32,
color: '#AAAAAA',
rowFlex: RowFlex.CENTER rowFlex: RowFlex.CENTER
}, {
value: '\n门诊病历',
size: 18,
rowFlex: RowFlex.CENTER
}, {
value: '\n',
type: ElementType.SEPARATOR
}], }],
main: <IElement[]>data main: <IElement[]>data
}, },

@ -1,13 +1,6 @@
import { ControlType, ElementType, IEditorOption, IElement, RowFlex } from './editor' import { ControlType, ElementType, IEditorOption, IElement } from './editor'
const text = `人民医院门诊病历\n主诉\n发热三天咳嗽五天。\n现病史\n患者于三天前无明显诱因感冒后发现面部水肿无皮疹尿量减少出现乏力在外治疗无好转现来我院就诊。\n既往史\n有糖尿病10年有高血压2年有传染性疾病1年。报告其他既往疾病。\n流行病史\n否认14天内接触过确诊患者、疑似患者、无症状感染者及其密切接触者否认14天内去过以下场所水产、肉类批发市场农贸市场集市大型超市夜市否认14天内与以下场所工作人员密切接触水产、肉类批发市场农贸市场集市大型超市否认14天内周围如家庭、办公室有2例以上聚集性发病否认14天内接触过有发热或呼吸道症状的人员否认14天内自身有发热或呼吸道症状否认14天内接触过纳入隔离观察的人员及其他可能与新冠肺炎关联的情形陪同家属无以上情况。\n体格检查\nT39.5℃P80bpmR20次/分BP120/80mmHg\n辅助检查\n2020年6月10日普放血细胞比容36.50%偏低4050单核细胞绝对值0.75*10/L偏高参考值0.10.6\n门诊诊断\n1.高血压\n2.糖尿病\n3.病毒性感冒\n4.过敏性鼻炎\n5.过敏性鼻息肉\n处置治疗\n1.超声引导下甲状腺细针穿刺术;\n2.乙型肝炎表面抗体测定;\n3.膜式病变细胞采集术、后颈皮下肤层;\n电子签名【】\n其他记录` const text = `主诉:\n发热三天咳嗽五天。\n现病史\n患者于三天前无明显诱因感冒后发现面部水肿无皮疹尿量减少出现乏力在外治疗无好转现来我院就诊。\n既往史\n有糖尿病10年有高血压2年有传染性疾病1年。报告其他既往疾病。\n流行病史\n否认14天内接触过确诊患者、疑似患者、无症状感染者及其密切接触者否认14天内去过以下场所水产、肉类批发市场农贸市场集市大型超市夜市否认14天内与以下场所工作人员密切接触水产、肉类批发市场农贸市场集市大型超市否认14天内周围如家庭、办公室有2例以上聚集性发病否认14天内接触过有发热或呼吸道症状的人员否认14天内自身有发热或呼吸道症状否认14天内接触过纳入隔离观察的人员及其他可能与新冠肺炎关联的情形陪同家属无以上情况。\n体格检查\nT39.5℃P80bpmR20次/分BP120/80mmHg\n辅助检查\n2020年6月10日普放血细胞比容36.50%偏低4050单核细胞绝对值0.75*10/L偏高参考值0.10.6\n门诊诊断\n1.高血压\n2.糖尿病\n3.病毒性感冒\n4.过敏性鼻炎\n5.过敏性鼻息肉\n处置治疗\n1.超声引导下甲状腺细针穿刺术;\n2.乙型肝炎表面抗体测定;\n3.膜式病变细胞采集术、后颈皮下肤层;\n电子签名【】\n其他记录`
// 模拟行居中
const centerText = ['人民医院门诊病历']
const centerIndex: number[] = centerText.map(c => {
const i = text.indexOf(c)
return ~i ? Array(c.length).fill(i).map((_, j) => i + j) : []
}).flat()
// 模拟加粗字 // 模拟加粗字
const boldText = ['主诉:', '现病史:', '既往史:', '流行病史:', '体格检查:', '辅助检查:', '门诊诊断:', '处置治疗:', '电子签名:', '其他记录:'] const boldText = ['主诉:', '现病史:', '既往史:', '流行病史:', '体格检查:', '辅助检查:', '门诊诊断:', '处置治疗:', '电子签名:', '其他记录:']
@ -32,13 +25,6 @@ const highlightIndex: number[] = highlightText.map(b => {
// 组合纯文本数据 // 组合纯文本数据
const elementList: IElement[] = text.split('').map((value, index) => { const elementList: IElement[] = text.split('').map((value, index) => {
if (centerIndex.includes(index)) {
return {
value,
size: 32,
rowFlex: RowFlex.CENTER
}
}
if (boldIndex.includes(index)) { if (boldIndex.includes(index)) {
return { return {
value, value,
@ -65,14 +51,8 @@ const elementList: IElement[] = text.split('').map((value, index) => {
} }
}) })
// 模拟分隔符
elementList.splice(8, 0, {
value: '\n',
type: ElementType.SEPARATOR
})
// 模拟文本控件 // 模拟文本控件
elementList.splice(24, 0, { elementList.splice(14, 0, {
type: ElementType.CONTROL, type: ElementType.CONTROL,
value: '', value: '',
control: { control: {
@ -85,7 +65,7 @@ elementList.splice(24, 0, {
}) })
// 模拟下拉控件 // 模拟下拉控件
elementList.splice(112, 0, { elementList.splice(102, 0, {
type: ElementType.CONTROL, type: ElementType.CONTROL,
value: '', value: '',
control: { control: {
@ -109,7 +89,7 @@ elementList.splice(112, 0, {
}) })
// 模拟超链接 // 模拟超链接
elementList.splice(138, 0, { elementList.splice(128, 0, {
type: ElementType.HYPERLINK, type: ElementType.HYPERLINK,
value: '', value: '',
valueList: [{ valueList: [{
@ -129,20 +109,20 @@ elementList.splice(138, 0, {
}) })
// 模拟下标 // 模拟下标
elementList.splice(371, 0, { elementList.splice(361, 0, {
value: '∆', value: '∆',
color: '#FF0000', color: '#FF0000',
type: ElementType.SUBSCRIPT type: ElementType.SUBSCRIPT
}) })
// 模拟上标 // 模拟上标
elementList.splice(459, 0, { elementList.splice(449, 0, {
value: '9', value: '9',
type: ElementType.SUPERSCRIPT type: ElementType.SUPERSCRIPT
}) })
// 模拟图片 // 模拟图片
elementList.splice(585, 0, { elementList.splice(575, 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,

Loading…
Cancel
Save