feat:render header element
This commit is contained in:
@@ -2,7 +2,7 @@ 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, IDrawRowPayload, IPainterOptions } from '../../interface/Draw'
|
||||||
import { IEditorOption, IEditorResult } from '../../interface/Editor'
|
import { IEditorDrawData, IEditorOption, IEditorResult } from '../../interface/Editor'
|
||||||
import { IElement, IElementMetrics, IElementPosition, IElementFillRect, IElementStyle } from '../../interface/Element'
|
import { IElement, IElementMetrics, IElementPosition, 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'
|
||||||
@@ -61,7 +61,9 @@ export class Draw {
|
|||||||
private mode: EditorMode
|
private mode: EditorMode
|
||||||
private options: DeepRequired<IEditorOption>
|
private options: DeepRequired<IEditorOption>
|
||||||
private position: Position
|
private position: Position
|
||||||
|
private headerElementList: IElement[]
|
||||||
private elementList: IElement[]
|
private elementList: IElement[]
|
||||||
|
private footerElementList: IElement[]
|
||||||
private listener: Listener
|
private listener: Listener
|
||||||
|
|
||||||
private i18n: I18n
|
private i18n: I18n
|
||||||
@@ -110,7 +112,7 @@ export class Draw {
|
|||||||
constructor(
|
constructor(
|
||||||
rootContainer: HTMLElement,
|
rootContainer: HTMLElement,
|
||||||
options: DeepRequired<IEditorOption>,
|
options: DeepRequired<IEditorOption>,
|
||||||
elementList: IElement[],
|
data: IEditorDrawData,
|
||||||
listener: Listener
|
listener: Listener
|
||||||
) {
|
) {
|
||||||
this.container = this._wrapContainer(rootContainer)
|
this.container = this._wrapContainer(rootContainer)
|
||||||
@@ -119,7 +121,9 @@ export class Draw {
|
|||||||
this.pageNo = 0
|
this.pageNo = 0
|
||||||
this.mode = options.mode
|
this.mode = options.mode
|
||||||
this.options = options
|
this.options = options
|
||||||
this.elementList = elementList
|
this.headerElementList = data.header || []
|
||||||
|
this.elementList = data.main
|
||||||
|
this.footerElementList = data.footer || []
|
||||||
this.listener = listener
|
this.listener = listener
|
||||||
|
|
||||||
this._formatContainer()
|
this._formatContainer()
|
||||||
@@ -343,6 +347,10 @@ export class Draw {
|
|||||||
return this.range
|
return this.range
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getHeaderElementList(): IElement[] {
|
||||||
|
return this.headerElementList
|
||||||
|
}
|
||||||
|
|
||||||
public getElementList(): IElement[] {
|
public getElementList(): IElement[] {
|
||||||
const positionContext = this.position.getPositionContext()
|
const positionContext = this.position.getPositionContext()
|
||||||
if (positionContext.isTable) {
|
if (positionContext.isTable) {
|
||||||
@@ -352,6 +360,10 @@ export class Draw {
|
|||||||
return this.elementList
|
return this.elementList
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getFooterElementList(): IElement[] {
|
||||||
|
return this.footerElementList
|
||||||
|
}
|
||||||
|
|
||||||
public insertElementList(payload: IElement[]) {
|
public insertElementList(payload: IElement[]) {
|
||||||
if (!payload.length) return
|
if (!payload.length) return
|
||||||
const isPartRangeInControlOutside = this.control.isPartRangeInControlOutside()
|
const isPartRangeInControlOutside = this.control.isPartRangeInControlOutside()
|
||||||
@@ -596,15 +608,17 @@ export class Draw {
|
|||||||
|
|
||||||
public getValue(): IEditorResult {
|
public getValue(): IEditorResult {
|
||||||
// 配置
|
// 配置
|
||||||
const { width, height, margins, watermark, header } = this.options
|
const { width, height, margins, watermark } = this.options
|
||||||
// 数据
|
// 数据
|
||||||
const data = zipElementList(this.elementList)
|
const data: IEditorDrawData = {
|
||||||
|
header: zipElementList(this.headerElementList),
|
||||||
|
main: zipElementList(this.elementList)
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
version,
|
version,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
margins,
|
margins,
|
||||||
header: header.data ? header : undefined,
|
|
||||||
watermark: watermark.data ? watermark : undefined,
|
watermark: watermark.data ? watermark : undefined,
|
||||||
data
|
data
|
||||||
}
|
}
|
||||||
@@ -670,7 +684,7 @@ export class Draw {
|
|||||||
return `${el.italic ? 'italic ' : ''}${el.bold ? 'bold ' : ''}${size * scale}px ${font}`
|
return `${el.italic ? 'italic ' : ''}${el.bold ? 'bold ' : ''}${size * scale}px ${font}`
|
||||||
}
|
}
|
||||||
|
|
||||||
private _computeRowList(innerWidth: number, elementList: IElement[]) {
|
public computeRowList(innerWidth: number, elementList: IElement[]) {
|
||||||
const { defaultSize, defaultRowMargin, scale, tdPadding, defaultTabWidth } = this.options
|
const { defaultSize, defaultRowMargin, scale, tdPadding, defaultTabWidth } = this.options
|
||||||
const defaultBasicRowMarginHeight = this.getDefaultBasicRowMarginHeight()
|
const defaultBasicRowMarginHeight = this.getDefaultBasicRowMarginHeight()
|
||||||
const canvas = document.createElement('canvas')
|
const canvas = document.createElement('canvas')
|
||||||
@@ -726,7 +740,7 @@ export class Draw {
|
|||||||
let maxTrHeight = 0
|
let maxTrHeight = 0
|
||||||
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]
|
||||||
const rowList = this._computeRowList((td.width! - tdGap) * scale, td.value)
|
const rowList = this.computeRowList((td.width! - tdGap) * scale, td.value)
|
||||||
const rowHeight = rowList.reduce((pre, cur) => pre + cur.height, 0)
|
const rowHeight = rowList.reduce((pre, cur) => pre + cur.height, 0)
|
||||||
td.rowList = rowList
|
td.rowList = rowList
|
||||||
// 移除缩放导致的行高变化-渲染时会进行缩放调整
|
// 移除缩放导致的行高变化-渲染时会进行缩放调整
|
||||||
@@ -964,7 +978,7 @@ export class Draw {
|
|||||||
this.textParticle.complete()
|
this.textParticle.complete()
|
||||||
}
|
}
|
||||||
|
|
||||||
private _drawRow(ctx: CanvasRenderingContext2D, payload: IDrawRowPayload) {
|
public drawRow(ctx: CanvasRenderingContext2D, payload: IDrawRowPayload) {
|
||||||
const { rowList, pageNo, positionList, startIndex } = payload
|
const { rowList, pageNo, positionList, startIndex } = payload
|
||||||
const { scale, tdPadding } = this.options
|
const { scale, tdPadding } = this.options
|
||||||
const { isCrossRowCol, tableId } = this.range.getRange()
|
const { isCrossRowCol, tableId } = this.range.getRange()
|
||||||
@@ -1104,7 +1118,7 @@ export class Draw {
|
|||||||
const tr = element.trList![t]
|
const tr = element.trList![t]
|
||||||
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, {
|
||||||
positionList: td.positionList!,
|
positionList: td.positionList!,
|
||||||
rowList: td.rowList!,
|
rowList: td.rowList!,
|
||||||
pageNo,
|
pageNo,
|
||||||
@@ -1147,7 +1161,7 @@ export class Draw {
|
|||||||
this.margin.render(ctx, pageNo)
|
this.margin.render(ctx, pageNo)
|
||||||
// 渲染元素
|
// 渲染元素
|
||||||
const index = rowList[0].startIndex
|
const index = rowList[0].startIndex
|
||||||
this._drawRow(ctx, {
|
this.drawRow(ctx, {
|
||||||
positionList,
|
positionList,
|
||||||
rowList,
|
rowList,
|
||||||
pageNo,
|
pageNo,
|
||||||
@@ -1175,6 +1189,7 @@ export class Draw {
|
|||||||
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._drawPage(positionList, this.pageRowList[index], index)
|
this._drawPage(positionList, this.pageRowList[index], index)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -1203,8 +1218,10 @@ export class Draw {
|
|||||||
const innerWidth = this.getInnerWidth()
|
const innerWidth = this.getInnerWidth()
|
||||||
// 计算文档信息
|
// 计算文档信息
|
||||||
if (isCompute) {
|
if (isCompute) {
|
||||||
|
// 页眉信息
|
||||||
|
this.header.compute()
|
||||||
// 行信息
|
// 行信息
|
||||||
this.rowList = this._computeRowList(innerWidth, this.elementList)
|
this.rowList = this.computeRowList(innerWidth, this.elementList)
|
||||||
// 页面信息
|
// 页面信息
|
||||||
this.pageRowList = this._computePageList()
|
this.pageRowList = this._computePageList()
|
||||||
// 位置信息
|
// 位置信息
|
||||||
|
|||||||
@@ -1,31 +1,72 @@
|
|||||||
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 { IRow } from '../../../interface/Row'
|
||||||
|
import { Position } from '../../position/Position'
|
||||||
import { Draw } from '../Draw'
|
import { Draw } from '../Draw'
|
||||||
|
|
||||||
export class Header {
|
export class Header {
|
||||||
|
|
||||||
private draw: Draw
|
private draw: Draw
|
||||||
|
private position: Position
|
||||||
private options: DeepRequired<IEditorOption>
|
private options: DeepRequired<IEditorOption>
|
||||||
|
|
||||||
|
private elementList: IElement[]
|
||||||
|
private rowList: IRow[]
|
||||||
|
private positionList: IElementPosition[]
|
||||||
|
|
||||||
constructor(draw: Draw) {
|
constructor(draw: Draw) {
|
||||||
this.draw = draw
|
this.draw = draw
|
||||||
this.options = <DeepRequired<IEditorOption>>draw.getOptions()
|
this.position = draw.getPosition()
|
||||||
|
this.options = draw.getOptions()
|
||||||
|
|
||||||
|
this.elementList = draw.getHeaderElementList()
|
||||||
|
this.rowList = []
|
||||||
|
this.positionList = []
|
||||||
|
}
|
||||||
|
|
||||||
|
public compute() {
|
||||||
|
this._recovery()
|
||||||
|
this._computeRowList()
|
||||||
|
this._computePositionList()
|
||||||
|
}
|
||||||
|
|
||||||
|
private _recovery() {
|
||||||
|
this.rowList = []
|
||||||
|
this.positionList = []
|
||||||
|
}
|
||||||
|
|
||||||
|
private _computeRowList() {
|
||||||
|
const innerWidth = this.draw.getInnerWidth()
|
||||||
|
this.rowList = this.draw.computeRowList(innerWidth, this.elementList)
|
||||||
|
}
|
||||||
|
|
||||||
|
private _computePositionList() {
|
||||||
|
const { header: { top } } = this.options
|
||||||
|
const innerWidth = this.draw.getInnerWidth()
|
||||||
|
const margins = this.draw.getMargins()
|
||||||
|
const startX = margins[3]
|
||||||
|
const startY = margins[0] + top
|
||||||
|
this.position.computePageRowPosition({
|
||||||
|
positionList: this.positionList,
|
||||||
|
rowList: this.rowList,
|
||||||
|
pageNo: 0,
|
||||||
|
startIndex: 0,
|
||||||
|
startX,
|
||||||
|
startY,
|
||||||
|
innerWidth
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(ctx: CanvasRenderingContext2D) {
|
public render(ctx: CanvasRenderingContext2D) {
|
||||||
const { header: { data, size, color, font }, scale } = this.options
|
const innerWidth = this.draw.getInnerWidth()
|
||||||
if (!data) return
|
this.draw.drawRow(ctx, {
|
||||||
const width = this.draw.getWidth()
|
positionList: this.positionList,
|
||||||
const top = this.draw.getHeaderTop()
|
rowList: this.rowList,
|
||||||
ctx.save()
|
pageNo: 0,
|
||||||
ctx.fillStyle = color
|
startIndex: 0,
|
||||||
ctx.font = `${size! * scale}px ${font}`
|
innerWidth
|
||||||
// 文字长度
|
})
|
||||||
const textWidth = ctx.measureText(`${data}`).width
|
|
||||||
// 偏移量
|
|
||||||
const left = (width - textWidth) / 2
|
|
||||||
ctx.fillText(`${data}`, left < 0 ? 0 : left, top)
|
|
||||||
ctx.restore()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -46,7 +46,7 @@ export class Position {
|
|||||||
this.positionList = payload
|
this.positionList = payload
|
||||||
}
|
}
|
||||||
|
|
||||||
private computePageRowPosition(payload: IComputePageRowPositionPayload): IComputePageRowPositionResult {
|
public computePageRowPosition(payload: IComputePageRowPositionPayload): IComputePageRowPositionResult {
|
||||||
const { positionList, rowList, pageNo, startX, startY, startIndex, innerWidth } = payload
|
const { positionList, rowList, pageNo, startX, startY, startIndex, innerWidth } = payload
|
||||||
const { scale, tdPadding } = this.options
|
const { scale, tdPadding } = this.options
|
||||||
let x = startX
|
let x = startX
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { IHeader } from '../../interface/Header'
|
import { IHeader } from '../../interface/Header'
|
||||||
|
import { HeaderMaxHeightRatio } from '../enum/Header'
|
||||||
|
|
||||||
export const defaultHeaderOption: Readonly<Required<IHeader>> = {
|
export const defaultHeaderOption: Readonly<Required<IHeader>> = {
|
||||||
data: '',
|
top: -50,
|
||||||
color: '#AAAAAA',
|
maxHeightRadio: HeaderMaxHeightRatio.HALF
|
||||||
size: 14,
|
|
||||||
font: 'Yahei'
|
|
||||||
}
|
}
|
||||||
@@ -18,6 +18,12 @@ export enum EditorMode {
|
|||||||
READONLY = 'readonly'
|
READONLY = 'readonly'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum EditorZone {
|
||||||
|
HEADER = 'header',
|
||||||
|
MAIN = 'main',
|
||||||
|
FOOTER = 'footer'
|
||||||
|
}
|
||||||
|
|
||||||
export enum PageMode {
|
export enum PageMode {
|
||||||
PAGING = 'paging',
|
PAGING = 'paging',
|
||||||
CONTINUITY = 'continuity'
|
CONTINUITY = 'continuity'
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export enum HeaderMaxHeightRatio {
|
||||||
|
HALF = 'half',
|
||||||
|
ONE_THIRD = 'one-third',
|
||||||
|
QUARTER = 'quarter'
|
||||||
|
}
|
||||||
+24
-4
@@ -1,5 +1,5 @@
|
|||||||
import './assets/css/index.css'
|
import './assets/css/index.css'
|
||||||
import { IEditorOption, IEditorResult } from './interface/Editor'
|
import { IEditorData, IEditorOption, IEditorResult } from './interface/Editor'
|
||||||
import { IElement } from './interface/Element'
|
import { IElement } from './interface/Element'
|
||||||
import { Draw } from './core/draw/Draw'
|
import { Draw } from './core/draw/Draw'
|
||||||
import { Command } from './core/command/Command'
|
import { Command } from './core/command/Command'
|
||||||
@@ -39,7 +39,7 @@ export default class Editor {
|
|||||||
public register: Register
|
public register: Register
|
||||||
public destroy: Function
|
public destroy: Function
|
||||||
|
|
||||||
constructor(container: HTMLDivElement, elementList: IElement[], options: IEditorOption = {}) {
|
constructor(container: HTMLDivElement, data: IEditorData, options: IEditorOption = {}) {
|
||||||
const headerOptions: Required<IHeader> = {
|
const headerOptions: Required<IHeader> = {
|
||||||
...defaultHeaderOption,
|
...defaultHeaderOption,
|
||||||
...options.header
|
...options.header
|
||||||
@@ -103,13 +103,33 @@ export default class Editor {
|
|||||||
checkbox: checkboxOptions,
|
checkbox: checkboxOptions,
|
||||||
cursor: cursorOptions
|
cursor: cursorOptions
|
||||||
}
|
}
|
||||||
formatElementList(elementList, {
|
// 数据处理
|
||||||
|
let headerElementList: IElement[] = []
|
||||||
|
let mainElementList: IElement[] = []
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
mainElementList = data
|
||||||
|
} else {
|
||||||
|
headerElementList = data.header || []
|
||||||
|
mainElementList = data.main
|
||||||
|
}
|
||||||
|
formatElementList(headerElementList, {
|
||||||
|
editorOptions
|
||||||
|
})
|
||||||
|
formatElementList(mainElementList, {
|
||||||
editorOptions
|
editorOptions
|
||||||
})
|
})
|
||||||
// 监听
|
// 监听
|
||||||
this.listener = new Listener()
|
this.listener = new Listener()
|
||||||
// 启动
|
// 启动
|
||||||
const draw = new Draw(container, editorOptions, elementList, this.listener)
|
const draw = new Draw(
|
||||||
|
container,
|
||||||
|
editorOptions,
|
||||||
|
{
|
||||||
|
header: headerElementList,
|
||||||
|
main: mainElementList
|
||||||
|
},
|
||||||
|
this.listener
|
||||||
|
)
|
||||||
// 命令
|
// 命令
|
||||||
this.command = new Command(new CommandAdapt(draw))
|
this.command = new Command(new CommandAdapt(draw))
|
||||||
// 菜单
|
// 菜单
|
||||||
|
|||||||
@@ -7,6 +7,14 @@ import { IHeader } from './Header'
|
|||||||
import { IMargin } from './Margin'
|
import { IMargin } from './Margin'
|
||||||
import { IWatermark } from './Watermark'
|
import { IWatermark } from './Watermark'
|
||||||
|
|
||||||
|
export interface IEditorDrawData {
|
||||||
|
header?: IElement[];
|
||||||
|
main: IElement[];
|
||||||
|
footer?: IElement[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IEditorData = IEditorDrawData | IElement[]
|
||||||
|
|
||||||
export interface IEditorOption {
|
export interface IEditorOption {
|
||||||
mode?: EditorMode;
|
mode?: EditorMode;
|
||||||
defaultType?: string;
|
defaultType?: string;
|
||||||
@@ -54,7 +62,6 @@ export interface IEditorResult {
|
|||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
margins: IMargin;
|
margins: IMargin;
|
||||||
header?: IHeader;
|
|
||||||
watermark?: IWatermark;
|
watermark?: IWatermark;
|
||||||
data: IElement[];
|
data: IEditorData;
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
|
import { HeaderMaxHeightRatio } from '../dataset/enum/Header'
|
||||||
|
|
||||||
export interface IHeader {
|
export interface IHeader {
|
||||||
data: string;
|
top?: number;
|
||||||
color?: string;
|
maxHeightRadio?: HeaderMaxHeightRatio;
|
||||||
size?: number;
|
|
||||||
font?: string;
|
|
||||||
}
|
}
|
||||||
+14
-2
@@ -1,7 +1,7 @@
|
|||||||
import { data, options } from './mock'
|
import { data, options } from './mock'
|
||||||
import './style.css'
|
import './style.css'
|
||||||
import prism from 'prismjs'
|
import prism from 'prismjs'
|
||||||
import Editor, { BlockType, Command, ControlType, EditorMode, ElementType, IBlock, IElement, KeyMap, PageMode, PaperDirection } from './editor'
|
import Editor, { BlockType, Command, ControlType, EditorMode, ElementType, IBlock, IElement, KeyMap, PageMode, PaperDirection, RowFlex } from './editor'
|
||||||
import { Dialog } from './components/dialog/Dialog'
|
import { Dialog } from './components/dialog/Dialog'
|
||||||
import { formatPrismToken } from './utils/prism'
|
import { formatPrismToken } from './utils/prism'
|
||||||
import { Signature } from './components/signature/Signature'
|
import { Signature } from './components/signature/Signature'
|
||||||
@@ -11,7 +11,19 @@ window.onload = function () {
|
|||||||
|
|
||||||
// 1. 初始化编辑器
|
// 1. 初始化编辑器
|
||||||
const container = document.querySelector<HTMLDivElement>('.editor')!
|
const container = document.querySelector<HTMLDivElement>('.editor')!
|
||||||
const instance = new Editor(container, <IElement[]>data, options)
|
const instance = new Editor(
|
||||||
|
container,
|
||||||
|
{
|
||||||
|
header: [{
|
||||||
|
value: '人民医院门诊',
|
||||||
|
size: 14,
|
||||||
|
color: '#AAAAAA',
|
||||||
|
rowFlex: RowFlex.CENTER
|
||||||
|
}],
|
||||||
|
main: <IElement[]>data
|
||||||
|
},
|
||||||
|
options
|
||||||
|
)
|
||||||
console.log('实例: ', instance)
|
console.log('实例: ', instance)
|
||||||
// cypress使用
|
// cypress使用
|
||||||
Reflect.set(window, 'editor', instance)
|
Reflect.set(window, 'editor', instance)
|
||||||
|
|||||||
@@ -312,9 +312,6 @@ export const data: IElement[] = elementList
|
|||||||
|
|
||||||
export const options: IEditorOption = {
|
export const options: IEditorOption = {
|
||||||
margins: [100, 120, 100, 120],
|
margins: [100, 120, 100, 120],
|
||||||
header: {
|
|
||||||
data: '人民医院门诊'
|
|
||||||
},
|
|
||||||
watermark: {
|
watermark: {
|
||||||
data: 'CANVAS-EDITOR',
|
data: 'CANVAS-EDITOR',
|
||||||
size: 120
|
size: 120
|
||||||
|
|||||||
Reference in New Issue
Block a user