feat: 发布0.9.89版本

npr765
会PS的小码农 2 years ago
commit 0930c93ac5

@ -1,3 +1,41 @@
## [0.9.91](https://github.com/Hufe921/canvas-editor/compare/v0.9.90...v0.9.91) (2024-08-25)
### Bug Fixes
* format different types of line breaks #769 ([f65ff87](https://github.com/Hufe921/canvas-editor/commit/f65ff87e44728322417d235d2347ceca25cc6667)), closes [#769](https://github.com/Hufe921/canvas-editor/issues/769)
* format initial data boundary error #771 #784 ([f62a315](https://github.com/Hufe921/canvas-editor/commit/f62a315a7bd04e49e9e17bc7737ccd1f0e751d69)), closes [#771](https://github.com/Hufe921/canvas-editor/issues/771) [#784](https://github.com/Hufe921/canvas-editor/issues/784)
* set row margin boundary error ([5285170](https://github.com/Hufe921/canvas-editor/commit/528517094373b5fa9ca2145bb259889db865a588))
### Features
* add page border ([3f6bdf6](https://github.com/Hufe921/canvas-editor/commit/3f6bdf6fcb583ab370cb5abe3eafebf6100f415e))
* hit checkbox/radio control when click on label #651 ([31b76b6](https://github.com/Hufe921/canvas-editor/commit/31b76b6fb6640ea75383569cb16ad1b5a2ccf25f)), closes [#651](https://github.com/Hufe921/canvas-editor/issues/651)
## [0.9.90](https://github.com/Hufe921/canvas-editor/compare/v0.9.89...v0.9.90) (2024-08-18)
### Bug Fixes
* float image position when scaling the page #766 ([c249b9e](https://github.com/Hufe921/canvas-editor/commit/c249b9eec215fe573b325d1430212f5f01c32099)), closes [#766](https://github.com/Hufe921/canvas-editor/issues/766)
* get range paragraph info boundary error #758 ([4653fe7](https://github.com/Hufe921/canvas-editor/commit/4653fe7427f4a9ec40700f6a86ff284d1d46088a)), closes [#758](https://github.com/Hufe921/canvas-editor/issues/758)
* insert block element row flex error #754 ([136b1ff](https://github.com/Hufe921/canvas-editor/commit/136b1ffa55b7b0b78bf3d114400489e1bbab4f17)), closes [#754](https://github.com/Hufe921/canvas-editor/issues/754)
* paper printing size setting #760 ([7a6dd75](https://github.com/Hufe921/canvas-editor/commit/7a6dd753e59bde4cb581ac215b038dd1cd08c96f)), closes [#760](https://github.com/Hufe921/canvas-editor/issues/760)
* set editor mode option error #755 ([500cec3](https://github.com/Hufe921/canvas-editor/commit/500cec3e0b63e012f6572dcedb071befa114956e)), closes [#755](https://github.com/Hufe921/canvas-editor/issues/755)
* set row flex boundary error when deleting element ([2f272de](https://github.com/Hufe921/canvas-editor/commit/2f272dee58169607783e4cfd1347534f49db0834))
### Features
* add location property to executeLocationControl api #753 ([d1a1aaa](https://github.com/Hufe921/canvas-editor/commit/d1a1aaa6ae08d8395df1df664d47bfe4fe821869)), closes [#753](https://github.com/Hufe921/canvas-editor/issues/753)
* add radio and checkbox vertical align setting ([c375466](https://github.com/Hufe921/canvas-editor/commit/c3754663cee25003a7021a6079dd46e2a3a0abd8))
* get context content width ([187498e](https://github.com/Hufe921/canvas-editor/commit/187498ed3dfba4386375d812d41b31c057ac3af8))
## [0.9.89](https://github.com/Hufe921/canvas-editor/compare/v0.9.88...v0.9.89) (2024-08-09) ## [0.9.89](https://github.com/Hufe921/canvas-editor/compare/v0.9.88...v0.9.89) (2024-08-09)

@ -679,7 +679,7 @@ Feature: Change how image rows are displayed
Usage: Usage:
```javascript ```javascript
instance.command.executeSaveAsImageElement(element: IElement, display: ImageDisplay) instance.command.executeChangeImageDisplay(element: IElement, display: ImageDisplay)
``` ```
## executePageMode ## executePageMode
@ -961,3 +961,13 @@ Usage:
```javascript ```javascript
instance.command.executeInsertTitle(payload: IElement) instance.command.executeInsertTitle(payload: IElement)
``` ```
## executeFocus
Feature: focus
Usage:
```javascript
instance.command.executeFocus(payload?: IFocusOption)
```

@ -14,7 +14,7 @@ new Editor(container, IEditorData | IElement[], {
```typescript ```typescript
interface IEditorOption { interface IEditorOption {
mode?: EditorMode // Editor mode: Edit, Clean (Visual aids are not displayed, For example: page break), ReadOnly, Form (Only editable within the control), Print (Visual aids are not displayed, Unwritten content control). default: Edit mode?: EditorMode // Editor mode: Edit, Clean (Visual aids are not displayed, For example: page break), ReadOnly, Form (Only editable within the control), Print (Visual aids are not displayed, Unwritten content control), Design (Do not handle configurations such as non deletable and read-only). default: Edit
defaultType?: string // Default element type. default: TEXT defaultType?: string // Default element type. default: TEXT
defaultColor?: string // Default color. default: #000000 defaultColor?: string // Default color. default: #000000
defaultFont?: string // Default font. default: Microsoft YaHei defaultFont?: string // Default font. default: Microsoft YaHei
@ -60,8 +60,8 @@ interface IEditorOption {
wordBreak?: WordBreak // Word and punctuation breaks: No punctuation in the first line of the BREAK_WORD &The word is not split, and the line is folded after BREAK_ALL full according to the width of the character. default: BREAK_WORD wordBreak?: WordBreak // Word and punctuation breaks: No punctuation in the first line of the BREAK_WORD &The word is not split, and the line is folded after BREAK_ALL full according to the width of the character. default: BREAK_WORD
watermark?: IWatermark // Watermark{data:string; color?:string; opacity?:number; size?:number; font?:string;} watermark?: IWatermark // Watermark{data:string; color?:string; opacity?:number; size?:number; font?:string;}
control?: IControlOption // Control {placeholderColor?:string; bracketColor?:string; prefix?:string; postfix?:string; borderWidth?: number; borderColor?: string;} control?: IControlOption // Control {placeholderColor?:string; bracketColor?:string; prefix?:string; postfix?:string; borderWidth?: number; borderColor?: string;}
checkbox?: ICheckboxOption // Checkbox {width?:number; height?:number; gap?:number; lineWidth?:number; fillStyle?:string; strokeStyle?: string;} checkbox?: ICheckboxOption // Checkbox {width?:number; height?:number; gap?:number; lineWidth?:number; fillStyle?:string; strokeStyle?: string; verticalAlign?: VerticalAlign;}
radio?: IRadioOption // Radio {width?:number; height?:number; gap?:number; lineWidth?:number; fillStyle?:string; strokeStyle?: string;} radio?: IRadioOption // Radio {width?:number; height?:number; gap?:number; lineWidth?:number; fillStyle?:string; strokeStyle?: string; verticalAlign?: VerticalAlign;}
cursor?: ICursorOption // Cursor style. {width?: number; color?: string; dragWidth?: number; dragColor?: string;} cursor?: ICursorOption // Cursor style. {width?: number; color?: string; dragWidth?: number; dragColor?: string;}
title?: ITitleOption // Title configuration.{ defaultFirstSize?: number; defaultSecondSize?: number; defaultThirdSize?: number defaultFourthSize?: number; defaultFifthSize?: number; defaultSixthSize?: number;} title?: ITitleOption // Title configuration.{ defaultFirstSize?: number; defaultSecondSize?: number; defaultThirdSize?: number defaultFourthSize?: number; defaultFifthSize?: number; defaultSixthSize?: number;}
placeholder?: IPlaceholder // Placeholder text placeholder?: IPlaceholder // Placeholder text
@ -72,6 +72,7 @@ interface IEditorOption {
lineBreak?: ILineBreakOption // LineBreak option. {disabled?:boolean; color?:string; lineWidth?:number;} lineBreak?: ILineBreakOption // LineBreak option. {disabled?:boolean; color?:string; lineWidth?:number;}
separator?: ISeparatorOption // Separator option. {lineWidth?:number; strokeStyle?:string;} separator?: ISeparatorOption // Separator option. {lineWidth?:number; strokeStyle?:string;}
lineNumber?: ILineNumberOption // LineNumber option. {size?:number; font?:string; color?:string; disabled?:boolean; right?:number} lineNumber?: ILineNumberOption // LineNumber option. {size?:number; font?:string; color?:string; disabled?:boolean; right?:number}
pageBorder?: IPageBorderOption // PageBorder option. {color?:string; lineWidth:number; padding?:IPadding; disabled?:boolean;}
} }
``` ```
@ -159,3 +160,14 @@ interface ILineNumberOption {
type?: LineNumberType // Number type (renumber each page, consecutive numbering). default: continuity type?: LineNumberType // Number type (renumber each page, consecutive numbering). default: continuity
} }
``` ```
## PageBorder Configuration
```typescript
interface IPageBorderOption {
color?: string // color. default: #000000
lineWidth?: number // line width. default: 1
padding?: IPadding // padding. default: [0, 0, 0, 0]
disabled?: boolean // Whether to disable. default: true
}
```

@ -21,5 +21,5 @@ export function myPlugin(editor: Editor, options?: Option) {
## Use Plugin ## Use Plugin
```javascript ```javascript
instance.add(myPlugin, options?: Option) instance.use(myPlugin, options?: Option)
``` ```

@ -679,7 +679,7 @@ instance.command.executeSaveAsImageElement()
用法: 用法:
```javascript ```javascript
instance.command.executeSaveAsImageElement(element: IElement, display: ImageDisplay) instance.command.executeChangeImageDisplay(element: IElement, display: ImageDisplay)
``` ```
## executePageMode ## executePageMode
@ -961,3 +961,13 @@ instance.command.executeUpdateOptions(payload: IUpdateOption)
```javascript ```javascript
instance.command.executeInsertTitle(payload: IElement) instance.command.executeInsertTitle(payload: IElement)
``` ```
## executeFocus
功能:光标聚焦
用法:
```javascript
instance.command.executeFocus(payload?: IFocusOption)
```

@ -14,7 +14,7 @@ new Editor(container, IEditorData | IElement[], {
```typescript ```typescript
interface IEditorOption { interface IEditorOption {
mode?: EditorMode // 编辑器模式:编辑、清洁(不显示视觉辅助元素。如:分页符)、只读、表单(仅控件内可编辑)、打印(不显示辅助元素、未书写控件及前后括号)。默认:编辑 mode?: EditorMode // 编辑器模式:编辑、清洁(不显示视觉辅助元素。如:分页符)、只读、表单(仅控件内可编辑)、打印(不显示辅助元素、未书写控件及前后括号)、设计模式(不可删除、只读等配置不控制)。默认:编辑
defaultType?: string // 默认元素类型。默认TEXT defaultType?: string // 默认元素类型。默认TEXT
defaultColor?: string // 默认字体颜色。默认:#000000 defaultColor?: string // 默认字体颜色。默认:#000000
defaultFont?: string // 默认字体。默认Microsoft YaHei defaultFont?: string // 默认字体。默认Microsoft YaHei
@ -60,8 +60,8 @@ interface IEditorOption {
wordBreak?: WordBreak // 单词与标点断行BREAK_WORD首行不出现标点&单词不拆分、BREAK_ALL按字符宽度撑满后折行。默认BREAK_WORD wordBreak?: WordBreak // 单词与标点断行BREAK_WORD首行不出现标点&单词不拆分、BREAK_ALL按字符宽度撑满后折行。默认BREAK_WORD
watermark?: IWatermark // 水印信息。{data:string; color?:string; opacity?:number; size?:number; font?:string;} watermark?: IWatermark // 水印信息。{data:string; color?:string; opacity?:number; size?:number; font?:string;}
control?: IControlOption // 控件信息。 {placeholderColor?:string; bracketColor?:string; prefix?:string; postfix?:string; borderWidth?: number; borderColor?: string;} control?: IControlOption // 控件信息。 {placeholderColor?:string; bracketColor?:string; prefix?:string; postfix?:string; borderWidth?: number; borderColor?: string;}
checkbox?: ICheckboxOption // 复选框信息。{width?:number; height?:number; gap?:number; lineWidth?:number; fillStyle?:string; strokeStyle?: string;} checkbox?: ICheckboxOption // 复选框信息。{width?:number; height?:number; gap?:number; lineWidth?:number; fillStyle?:string; strokeStyle?: string; verticalAlign?: VerticalAlign;}
radio?: IRadioOption // 单选框信息。{width?:number; height?:number; gap?:number; lineWidth?:number; fillStyle?:string; strokeStyle?: string;} radio?: IRadioOption // 单选框信息。{width?:number; height?:number; gap?:number; lineWidth?:number; fillStyle?:string; strokeStyle?: string; verticalAlign?: VerticalAlign;}
cursor?: ICursorOption // 光标样式。{width?: number; color?: string; dragWidth?: number; dragColor?: string;} cursor?: ICursorOption // 光标样式。{width?: number; color?: string; dragWidth?: number; dragColor?: string;}
title?: ITitleOption // 标题配置。{ defaultFirstSize?: number; defaultSecondSize?: number; defaultThirdSize?: number defaultFourthSize?: number; defaultFifthSize?: number; defaultSixthSize?: number;} title?: ITitleOption // 标题配置。{ defaultFirstSize?: number; defaultSecondSize?: number; defaultThirdSize?: number defaultFourthSize?: number; defaultFifthSize?: number; defaultSixthSize?: number;}
placeholder?: IPlaceholder // 编辑器空白占位文本 placeholder?: IPlaceholder // 编辑器空白占位文本
@ -72,6 +72,7 @@ interface IEditorOption {
lineBreak?: ILineBreakOption // 换行符配置。{disabled?:boolean; color?:string; lineWidth?:number;} lineBreak?: ILineBreakOption // 换行符配置。{disabled?:boolean; color?:string; lineWidth?:number;}
separator?: ISeparatorOption // 分隔符配置。{lineWidth?:number; strokeStyle?:string;} separator?: ISeparatorOption // 分隔符配置。{lineWidth?:number; strokeStyle?:string;}
lineNumber?: ILineNumberOption // 行号配置。{size?:number; font?:string; color?:string; disabled?:boolean; right?:number} lineNumber?: ILineNumberOption // 行号配置。{size?:number; font?:string; color?:string; disabled?:boolean; right?:number}
pageBorder?: IPageBorderOption // 页面边框配置。{color?:string; lineWidth:number; padding?:IPadding; disabled?:boolean;}
} }
``` ```
@ -159,3 +160,14 @@ interface ILineNumberOption {
type?: LineNumberType // 编号类型(每页重新编号、连续编号)。默认:连续编号 type?: LineNumberType // 编号类型(每页重新编号、连续编号)。默认:连续编号
} }
``` ```
## 页面边框配置
```typescript
interface IPageBorderOption {
color?: string // 颜色。默认:#000000
lineWidth?: number // 宽度。默认1
padding?: IPadding // 距离正文内边距。默认:[0, 5, 0, 5]
disabled?: boolean // 是否禁用。默认true
}
```

@ -21,5 +21,5 @@ export function myPlugin(editor: Editor, options?: Option) {
## 使用插件 ## 使用插件
```javascript ```javascript
instance.add(myPlugin, options?: Option) instance.use(myPlugin, options?: Option)
``` ```

@ -365,7 +365,7 @@
<span>页面:<span class="page-no">1</span>/<span class="page-size">1</span></span> <span>页面:<span class="page-no">1</span>/<span class="page-size">1</span></span>
<span>字数:<span class="word-count">0</span></span> <span>字数:<span class="word-count">0</span></span>
</div> </div>
<div class="editor-mode" title="编辑模式(编辑、清洁、只读、表单)">编辑模式</div> <div class="editor-mode" title="编辑模式(编辑、清洁、只读、表单、设计)">编辑模式</div>
<div> <div>
<div class="page-scale-minus" title="缩小(Ctrl+-)"> <div class="page-scale-minus" title="缩小(Ctrl+-)">
<i></i> <i></i>

@ -1,11 +1,11 @@
{ {
"name": "@hufe921/canvas-editor", "name": "@liushuai05/canvas-editor",
"author": "Hufe", "author": "Hufe",
"license": "MIT", "license": "MIT",
"version": "0.9.89", "version": "0.9.89",
"description": "rich text editor by canvas/svg", "description": "rich text editor by canvas/svg",
"publishConfig": { "publishConfig": {
"registry": "https://registry.npmjs.org/", "registry": "https://registry.yarnpkg.com/",
"access": "public" "access": "public"
}, },
"files": [ "files": [

@ -95,6 +95,7 @@ export class Command {
public executeInsertControl: CommandAdapt['insertControl'] public executeInsertControl: CommandAdapt['insertControl']
public executeUpdateOptions: CommandAdapt['updateOptions'] public executeUpdateOptions: CommandAdapt['updateOptions']
public executeInsertTitle: CommandAdapt['insertTitle'] public executeInsertTitle: CommandAdapt['insertTitle']
public executeFocus: CommandAdapt['focus']
public getCatalog: CommandAdapt['getCatalog'] public getCatalog: CommandAdapt['getCatalog']
public getImage: CommandAdapt['getImage'] public getImage: CommandAdapt['getImage']
public getOptions: CommandAdapt['getOptions'] public getOptions: CommandAdapt['getOptions']
@ -214,6 +215,7 @@ export class Command {
this.executeSetZone = adapt.setZone.bind(adapt) this.executeSetZone = adapt.setZone.bind(adapt)
this.executeUpdateOptions = adapt.updateOptions.bind(adapt) this.executeUpdateOptions = adapt.updateOptions.bind(adapt)
this.executeInsertTitle = adapt.insertTitle.bind(adapt) this.executeInsertTitle = adapt.insertTitle.bind(adapt)
this.executeFocus = adapt.focus.bind(adapt)
// 获取 // 获取
this.getImage = adapt.getImage.bind(adapt) this.getImage = adapt.getImage.bind(adapt)
this.getOptions = adapt.getOptions.bind(adapt) this.getOptions = adapt.getOptions.bind(adapt)

@ -22,6 +22,7 @@ import {
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 { ListStyle, ListType } from '../../dataset/enum/List' import { ListStyle, ListType } from '../../dataset/enum/List'
import { MoveDirection } from '../../dataset/enum/Observer'
import { RowFlex } from '../../dataset/enum/Row' import { RowFlex } from '../../dataset/enum/Row'
import { TableBorder, TdBorder, TdSlash } from '../../dataset/enum/table/Table' import { TableBorder, TdBorder, TdSlash } from '../../dataset/enum/table/Table'
import { TitleLevel } from '../../dataset/enum/Title' import { TitleLevel } from '../../dataset/enum/Title'
@ -52,6 +53,7 @@ import {
IEditorOption, IEditorOption,
IEditorResult, IEditorResult,
IEditorText, IEditorText,
IFocusOption,
ISetValueOption, ISetValueOption,
IUpdateOption IUpdateOption
} from '../../interface/Editor' } from '../../interface/Editor'
@ -789,7 +791,7 @@ export class CommandAdapt {
const row = rowList[rowIndex] const row = rowList[rowIndex]
offsetX = row?.offsetX || 0 offsetX = row?.offsetX || 0
} }
const innerWidth = this.draw.getOriginalInnerWidth() - offsetX const innerWidth = this.draw.getContextInnerWidth() - offsetX
// colgroup // colgroup
const colgroup: IColgroup[] = [] const colgroup: IColgroup[] = []
const colWidth = innerWidth / col const colWidth = innerWidth / col
@ -810,7 +812,7 @@ export class CommandAdapt {
tdList.push({ tdList.push({
colspan: 1, colspan: 1,
rowspan: 1, rowspan: 1,
value: [{ value: ZERO, size: 16 }] value: []
}) })
} }
trList.push(tr) trList.push(tr)
@ -825,7 +827,9 @@ export class CommandAdapt {
formatElementList([element], { formatElementList([element], {
editorOptions: this.options editorOptions: this.options
}) })
formatElementContext(elementList, [element], startIndex) formatElementContext(elementList, [element], startIndex, {
editorOptions: this.options
})
const curIndex = startIndex + 1 const curIndex = startIndex + 1
this.draw.spliceElementList( this.draw.spliceElementList(
elementList, elementList,
@ -1586,7 +1590,9 @@ export class CommandAdapt {
})) }))
if (!newElementList) return if (!newElementList) return
const start = startIndex + 1 const start = startIndex + 1
formatElementContext(elementList, newElementList, startIndex) formatElementContext(elementList, newElementList, startIndex, {
editorOptions: this.options
})
this.draw.spliceElementList( this.draw.spliceElementList(
elementList, elementList,
start, start,
@ -1731,7 +1737,9 @@ export class CommandAdapt {
dashArray: payload dashArray: payload
} }
// 从行头增加分割线 // 从行头增加分割线
formatElementContext(elementList, [newElement], startIndex) formatElementContext(elementList, [newElement], startIndex, {
editorOptions: this.options
})
if (startIndex !== 0 && elementList[startIndex].value === ZERO) { if (startIndex !== 0 && elementList[startIndex].value === ZERO) {
this.draw.spliceElementList(elementList, startIndex, 1, newElement) this.draw.spliceElementList(elementList, startIndex, 1, newElement)
curIndex = startIndex - 1 curIndex = startIndex - 1
@ -2236,7 +2244,8 @@ export class CommandAdapt {
const { startIndex } = this.range.getRange() const { startIndex } = this.range.getRange()
const elementList = this.draw.getElementList() const elementList = this.draw.getElementList()
formatElementContext(elementList, cloneElementList, startIndex, { formatElementContext(elementList, cloneElementList, startIndex, {
isBreakWhenWrap: true isBreakWhenWrap: true,
editorOptions: this.options
}) })
this.draw.insertElementList(cloneElementList) this.draw.insertElementList(cloneElementList)
} }
@ -2726,4 +2735,23 @@ export class CommandAdapt {
// 插入标题 // 插入标题
this.draw.insertElementList([cloneElement]) this.draw.insertElementList([cloneElement])
} }
public focus(payload?: IFocusOption) {
const { position = LocationPosition.AFTER } = payload || {}
const curIndex =
position === LocationPosition.BEFORE
? 0
: this.draw.getOriginalMainElementList().length - 1
this.range.setRange(curIndex, curIndex)
this.draw.render({
curIndex,
isCompute: false,
isSubmitHistory: false
})
const positionList = this.draw.getPosition().getPositionList()
this.draw.getCursor().moveCursorToVisible({
cursorPosition: positionList[curIndex],
direction: MoveDirection.DOWN
})
}
} }

@ -103,6 +103,7 @@ import { LineBreakParticle } from './particle/LineBreakParticle'
import { MouseObserver } from '../observer/MouseObserver' import { MouseObserver } from '../observer/MouseObserver'
import { LineNumber } from './frame/LineNumber' import { LineNumber } from './frame/LineNumber'
import { ITd } from '../../interface/table/Td' import { ITd } from '../../interface/table/Td'
import { PageBorder } from './frame/PageBorder'
export class Draw { export class Draw {
private container: HTMLDivElement private container: HTMLDivElement
@ -157,6 +158,7 @@ export class Draw {
private listParticle: ListParticle private listParticle: ListParticle
private lineBreakParticle: LineBreakParticle private lineBreakParticle: LineBreakParticle
private control: Control private control: Control
private pageBorder: PageBorder
private workerManager: WorkerManager private workerManager: WorkerManager
private scrollObserver: ScrollObserver private scrollObserver: ScrollObserver
private selectionObserver: SelectionObserver private selectionObserver: SelectionObserver
@ -233,6 +235,7 @@ export class Draw {
this.listParticle = new ListParticle(this) this.listParticle = new ListParticle(this)
this.lineBreakParticle = new LineBreakParticle(this) this.lineBreakParticle = new LineBreakParticle(this)
this.control = new Control(this) this.control = new Control(this)
this.pageBorder = new PageBorder(this)
this.scrollObserver = new ScrollObserver(this) this.scrollObserver = new ScrollObserver(this)
this.selectionObserver = new SelectionObserver(this) this.selectionObserver = new SelectionObserver(this)
@ -312,6 +315,8 @@ export class Draw {
public isReadonly() { public isReadonly() {
switch (this.mode) { switch (this.mode) {
case EditorMode.DESIGN:
return false
case EditorMode.READONLY: case EditorMode.READONLY:
case EditorMode.PRINT: case EditorMode.PRINT:
return true return true
@ -323,6 +328,7 @@ export class Draw {
} }
public isDisabled() { public isDisabled() {
if (this.mode === EditorMode.DESIGN) return false
const { startIndex, endIndex } = this.range.getRange() const { startIndex, endIndex } = this.range.getRange()
const elementList = this.getElementList() const elementList = this.getElementList()
if (startIndex === endIndex) { if (startIndex === endIndex) {
@ -339,6 +345,10 @@ export class Draw {
) )
} }
public isDesignMode() {
return this.mode === EditorMode.DESIGN
}
public getOriginalWidth(): number { public getOriginalWidth(): number {
const { paperDirection, width, height } = this.options const { paperDirection, width, height } = this.options
return paperDirection === PaperDirection.VERTICAL ? width : height return paperDirection === PaperDirection.VERTICAL ? width : height
@ -391,6 +401,18 @@ export class Draw {
return width - margins[1] - margins[3] return width - margins[1] - margins[3]
} }
public getContextInnerWidth(): number {
const positionContext = this.position.getPositionContext()
if (positionContext.isTable) {
const { index, trIndex, tdIndex } = positionContext
const elementList = this.getOriginalElementList()
const td = elementList[index!].trList![trIndex!].tdList[tdIndex!]
const tdPadding = this.getTdPadding()
return td!.width! - tdPadding[1] - tdPadding[3]
}
return this.getOriginalInnerWidth()
}
public getMargins(): IMargin { public getMargins(): IMargin {
return <IMargin>this.getOriginalMargins().map(m => m * this.options.scale) return <IMargin>this.getOriginalMargins().map(m => m * this.options.scale)
} }
@ -684,6 +706,7 @@ export class Draw {
deleteCount: number, deleteCount: number,
...items: IElement[] ...items: IElement[]
) { ) {
const isDesignMode = this.isDesignMode()
if (deleteCount > 0) { if (deleteCount > 0) {
// 当最后元素与开始元素列表信息不一致时:清除当前列表信息 // 当最后元素与开始元素列表信息不一致时:清除当前列表信息
const endIndex = start + deleteCount const endIndex = start + deleteCount
@ -714,8 +737,9 @@ export class Draw {
while (deleteIndex >= start) { while (deleteIndex >= start) {
const deleteElement = elementList[deleteIndex] const deleteElement = elementList[deleteIndex]
if ( if (
deleteElement?.control?.deletable !== false && isDesignMode ||
deleteElement?.title?.deletable !== false (deleteElement?.control?.deletable !== false &&
deleteElement?.title?.deletable !== false)
) { ) {
elementList.splice(deleteIndex, 1) elementList.splice(deleteIndex, 1)
} }
@ -1069,7 +1093,8 @@ export class Draw {
pageComponentData.forEach(data => { pageComponentData.forEach(data => {
if (!data) return if (!data) return
formatElementList(data, { formatElementList(data, {
editorOptions: this.options editorOptions: this.options,
isForceCompensation: true
}) })
}) })
this.setEditorData({ this.setEditorData({
@ -1177,7 +1202,7 @@ export class Draw {
const { defaultBasicRowMarginHeight, defaultRowMargin, scale } = const { defaultBasicRowMarginHeight, defaultRowMargin, scale } =
this.options this.options
return ( return (
defaultBasicRowMarginHeight * (el.rowMargin || defaultRowMargin) * scale defaultBasicRowMarginHeight * (el.rowMargin ?? defaultRowMargin) * scale
) )
} }
@ -1217,7 +1242,7 @@ export class Draw {
const curRow: IRow = rowList[rowList.length - 1] const curRow: IRow = rowList[rowList.length - 1]
const element = elementList[i] const element = elementList[i]
const rowMargin = const rowMargin =
defaultBasicRowMarginHeight * (element.rowMargin || defaultRowMargin) defaultBasicRowMarginHeight * (element.rowMargin ?? defaultRowMargin)
const metrics: IElementMetrics = { const metrics: IElementMetrics = {
width: 0, width: 0,
height: 0, height: 0,
@ -2223,13 +2248,25 @@ export class Draw {
element.controlComponent === ControlComponent.CHECKBOX element.controlComponent === ControlComponent.CHECKBOX
) { ) {
this.textParticle.complete() this.textParticle.complete()
this.checkboxParticle.render(ctx, element, x, y + offsetY) this.checkboxParticle.render({
ctx,
x,
y: y + offsetY,
index: j,
row: curRow
})
} else if ( } else if (
element.type === ElementType.RADIO || element.type === ElementType.RADIO ||
element.controlComponent === ControlComponent.RADIO element.controlComponent === ControlComponent.RADIO
) { ) {
this.textParticle.complete() this.textParticle.complete()
this.radioParticle.render(ctx, element, x, y + offsetY) this.radioParticle.render({
ctx,
x,
y: y + offsetY,
index: j,
row: curRow
})
} else if (element.type === ElementType.TAB) { } else if (element.type === ElementType.TAB) {
this.textParticle.complete() this.textParticle.complete()
} else if ( } else if (
@ -2470,6 +2507,7 @@ export class Draw {
ctx: CanvasRenderingContext2D, ctx: CanvasRenderingContext2D,
payload: IDrawFloatPayload payload: IDrawFloatPayload
) { ) {
const { scale } = this.options
const floatPositionList = this.position.getFloatPositionList() const floatPositionList = this.position.getFloatPositionList()
const { imgDisplay, pageNo } = payload const { imgDisplay, pageNo } = payload
for (let e = 0; e < floatPositionList.length; e++) { for (let e = 0; e < floatPositionList.length; e++) {
@ -2486,8 +2524,8 @@ export class Draw {
this.imageParticle.render( this.imageParticle.render(
ctx, ctx,
element, element,
imgFloatPosition.x, imgFloatPosition.x * scale,
imgFloatPosition.y imgFloatPosition.y * scale
) )
} }
} }
@ -2507,8 +2545,15 @@ export class Draw {
private _drawPage(payload: IDrawPagePayload) { private _drawPage(payload: IDrawPagePayload) {
const { elementList, positionList, rowList, pageNo } = payload const { elementList, positionList, rowList, pageNo } = payload
const { inactiveAlpha, pageMode, header, footer, pageNumber, lineNumber } = const {
this.options inactiveAlpha,
pageMode,
header,
footer,
pageNumber,
lineNumber,
pageBorder
} = this.options
const innerWidth = this.getInnerWidth() const innerWidth = this.getInnerWidth()
const ctx = this.ctxList[pageNo] const ctx = this.ctxList[pageNo]
// 判断当前激活区域-非正文区域时元素透明度降低 // 判断当前激活区域-非正文区域时元素透明度降低
@ -2573,6 +2618,10 @@ export class Draw {
if (!lineNumber.disabled) { if (!lineNumber.disabled) {
this.lineNumber.render(ctx, pageNo) this.lineNumber.render(ctx, pageNo)
} }
// 绘制页面边框
if (!pageBorder.disabled) {
this.pageBorder.render(ctx)
}
} }
private _disconnectLazyRender() { private _disconnectLazyRender() {

@ -208,7 +208,7 @@ export class Control {
} }
public getIsDisabledControl(context: IControlContext = {}): boolean { public getIsDisabledControl(context: IControlContext = {}): boolean {
if (!this.activeControl) return false if (this.draw.isDesignMode() || !this.activeControl) return false
const { startIndex, endIndex } = context.range || this.range.getRange() const { startIndex, endIndex } = context.range || this.range.getRange()
if (startIndex === endIndex && ~startIndex && ~endIndex) { if (startIndex === endIndex && ~startIndex && ~endIndex) {
const elementList = context.elementList || this.getElementList() const elementList = context.elementList || this.getElementList()
@ -452,8 +452,11 @@ export class Control {
): number | null { ): number | null {
const elementList = context.elementList || this.getElementList() const elementList = context.elementList || this.getElementList()
const startElement = elementList[startIndex] const startElement = elementList[startIndex]
const { deletable = true } = startElement.control! // 设计模式不验证删除权限
if (!deletable) return null if (!this.draw.isDesignMode()) {
const { deletable = true } = startElement.control!
if (!deletable) return null
}
let leftIndex = -1 let leftIndex = -1
let rightIndex = -1 let rightIndex = -1
// 向左查找 // 向左查找
@ -538,7 +541,9 @@ export class Control {
controlComponent: ControlComponent.PLACEHOLDER, controlComponent: ControlComponent.PLACEHOLDER,
color: this.controlOptions.placeholderColor color: this.controlOptions.placeholderColor
} }
formatElementContext(elementList, [newElement], startIndex) formatElementContext(elementList, [newElement], startIndex, {
editorOptions: this.options
})
this.draw.spliceElementList( this.draw.spliceElementList(
elementList, elementList,
startIndex + p + 1, startIndex + p + 1,
@ -892,7 +897,8 @@ export class Control {
const elementList = zipElementList(pageComponentData[pageComponentKey]!) const elementList = zipElementList(pageComponentData[pageComponentKey]!)
pageComponentData[pageComponentKey] = elementList pageComponentData[pageComponentKey] = elementList
formatElementList(elementList, { formatElementList(elementList, {
editorOptions: this.options editorOptions: this.options,
isForceCompensation: true
}) })
} }
this.draw.setEditorData(pageComponentData) this.draw.setEditorData(pageComponentData)

@ -6,11 +6,13 @@ import {
import { ControlComponent } from '../../../../dataset/enum/Control' import { ControlComponent } from '../../../../dataset/enum/Control'
import { ElementType } from '../../../../dataset/enum/Element' import { ElementType } from '../../../../dataset/enum/Element'
import { KeyMap } from '../../../../dataset/enum/KeyMap' import { KeyMap } from '../../../../dataset/enum/KeyMap'
import { DeepRequired } from '../../../../interface/Common'
import { import {
IControlContext, IControlContext,
IControlInstance, IControlInstance,
IControlRuleOption IControlRuleOption
} from '../../../../interface/Control' } from '../../../../interface/Control'
import { IEditorOption } from '../../../../interface/Editor'
import { IElement } from '../../../../interface/Element' import { IElement } from '../../../../interface/Element'
import { omitObject, pickObject } from '../../../../utils' import { omitObject, pickObject } from '../../../../utils'
import { formatElementContext } from '../../../../utils/element' import { formatElementContext } from '../../../../utils/element'
@ -24,10 +26,12 @@ export class DateControl implements IControlInstance {
private control: Control private control: Control
private isPopup: boolean private isPopup: boolean
private datePicker: DatePicker | null private datePicker: DatePicker | null
private options: DeepRequired<IEditorOption>
constructor(element: IElement, control: Control) { constructor(element: IElement, control: Control) {
const draw = control.getDraw() const draw = control.getDraw()
this.draw = draw this.draw = draw
this.options = draw.getOptions()
this.element = element this.element = element
this.control = control this.control = control
this.isPopup = false this.isPopup = false
@ -138,7 +142,9 @@ export class DateControl implements IControlInstance {
...data[i], ...data[i],
controlComponent: ControlComponent.VALUE controlComponent: ControlComponent.VALUE
} }
formatElementContext(elementList, [newElement], startIndex) formatElementContext(elementList, [newElement], startIndex, {
editorOptions: this.options
})
draw.spliceElementList(elementList, start + i, 0, newElement) draw.spliceElementList(elementList, start + i, 0, newElement)
} }
return start + data.length - 1 return start + data.length - 1
@ -207,7 +213,9 @@ export class DateControl implements IControlInstance {
value: date[i], value: date[i],
controlComponent: ControlComponent.VALUE controlComponent: ControlComponent.VALUE
} }
formatElementContext(elementList, [newElement], prefixIndex) formatElementContext(elementList, [newElement], prefixIndex, {
editorOptions: this.options
})
draw.spliceElementList(elementList, start + i, 0, newElement) draw.spliceElementList(elementList, start + i, 0, newElement)
} }
// 重新渲染控件 // 重新渲染控件

@ -10,11 +10,13 @@ import { ControlComponent } from '../../../../dataset/enum/Control'
import { EditorComponent } from '../../../../dataset/enum/Editor' import { EditorComponent } from '../../../../dataset/enum/Editor'
import { ElementType } from '../../../../dataset/enum/Element' import { ElementType } from '../../../../dataset/enum/Element'
import { KeyMap } from '../../../../dataset/enum/KeyMap' import { KeyMap } from '../../../../dataset/enum/KeyMap'
import { DeepRequired } from '../../../../interface/Common'
import { import {
IControlContext, IControlContext,
IControlInstance, IControlInstance,
IControlRuleOption IControlRuleOption
} from '../../../../interface/Control' } from '../../../../interface/Control'
import { IEditorOption } from '../../../../interface/Editor'
import { IElement } from '../../../../interface/Element' import { IElement } from '../../../../interface/Element'
import { omitObject, pickObject, splitText } from '../../../../utils' import { omitObject, pickObject, splitText } from '../../../../utils'
import { formatElementContext } from '../../../../utils/element' import { formatElementContext } from '../../../../utils/element'
@ -25,8 +27,10 @@ export class SelectControl implements IControlInstance {
private control: Control private control: Control
private isPopup: boolean private isPopup: boolean
private selectDom: HTMLDivElement | null private selectDom: HTMLDivElement | null
private options: DeepRequired<IEditorOption>
constructor(element: IElement, control: Control) { constructor(element: IElement, control: Control) {
this.options = control.getDraw().getOptions()
this.element = element this.element = element
this.control = control this.control = control
this.isPopup = false this.isPopup = false
@ -270,7 +274,9 @@ export class SelectControl implements IControlInstance {
value: data[i], value: data[i],
controlComponent: ControlComponent.VALUE controlComponent: ControlComponent.VALUE
} }
formatElementContext(elementList, [newElement], prefixIndex) formatElementContext(elementList, [newElement], prefixIndex, {
editorOptions: this.options
})
draw.spliceElementList(elementList, start + i, 0, newElement) draw.spliceElementList(elementList, start + i, 0, newElement)
} }
// 设置状态 // 设置状态

@ -4,11 +4,13 @@ import {
} from '../../../../dataset/constant/Element' } from '../../../../dataset/constant/Element'
import { ControlComponent } from '../../../../dataset/enum/Control' import { ControlComponent } from '../../../../dataset/enum/Control'
import { KeyMap } from '../../../../dataset/enum/KeyMap' import { KeyMap } from '../../../../dataset/enum/KeyMap'
import { DeepRequired } from '../../../../interface/Common'
import { import {
IControlContext, IControlContext,
IControlInstance, IControlInstance,
IControlRuleOption IControlRuleOption
} from '../../../../interface/Control' } from '../../../../interface/Control'
import { IEditorOption } from '../../../../interface/Editor'
import { IElement } from '../../../../interface/Element' import { IElement } from '../../../../interface/Element'
import { omitObject, pickObject } from '../../../../utils' import { omitObject, pickObject } from '../../../../utils'
import { formatElementContext } from '../../../../utils/element' import { formatElementContext } from '../../../../utils/element'
@ -17,8 +19,10 @@ import { Control } from '../Control'
export class TextControl implements IControlInstance { export class TextControl implements IControlInstance {
private element: IElement private element: IElement
private control: Control private control: Control
private options: DeepRequired<IEditorOption>
constructor(element: IElement, control: Control) { constructor(element: IElement, control: Control) {
this.options = control.getDraw().getOptions()
this.element = element this.element = element
this.control = control this.control = control
} }
@ -114,7 +118,9 @@ export class TextControl implements IControlInstance {
...data[i], ...data[i],
controlComponent: ControlComponent.VALUE controlComponent: ControlComponent.VALUE
} }
formatElementContext(elementList, [newElement], startIndex) formatElementContext(elementList, [newElement], startIndex, {
editorOptions: this.options
})
draw.spliceElementList(elementList, start + i, 0, newElement) draw.spliceElementList(elementList, start + i, 0, newElement)
} }
return start + data.length - 1 return start + data.length - 1

@ -0,0 +1,47 @@
import { DeepRequired } from '../../../interface/Common'
import { IEditorOption } from '../../../interface/Editor'
import { Draw } from '../Draw'
import { Footer } from './Footer'
import { Header } from './Header'
export class PageBorder {
private draw: Draw
private header: Header
private footer: Footer
private options: DeepRequired<IEditorOption>
constructor(draw: Draw) {
this.draw = draw
this.header = draw.getHeader()
this.footer = draw.getFooter()
this.options = draw.getOptions()
}
public render(ctx: CanvasRenderingContext2D) {
const {
scale,
pageBorder: { color, lineWidth, padding }
} = this.options
ctx.save()
ctx.translate(0.5, 0.5)
ctx.strokeStyle = color
ctx.lineWidth = lineWidth * scale
const margins = this.draw.getMargins()
// x左边距 - 左距离正文距离
const x = margins[3] - padding[3] * scale
// y页眉上边距 + 页眉高度 - 上距离正文距离
const y = margins[0] + this.header.getExtraHeight() - padding[0] * scale
// width页面宽度 + 左右距离正文距离
const width = this.draw.getInnerWidth() + (padding[1] + padding[3]) * scale
// height页面高度 - 正文起始位置 - 页脚高度 - 下边距 - 下距离正文距离
const height =
this.draw.getHeight() -
y -
this.footer.getExtraHeight() -
margins[2] +
padding[2] * scale
ctx.rect(x, y, width, height)
ctx.stroke()
ctx.restore()
}
}

@ -84,7 +84,8 @@ export class Placeholder {
} }
] ]
formatElementList(this.elementList, { formatElementList(this.elementList, {
editorOptions: this.options editorOptions: this.options,
isForceCompensation: true
}) })
// 计算 // 计算
this._compute() this._compute()

@ -1,9 +1,19 @@
import { NBSP, ZERO } from '../../../dataset/constant/Common'
import { VerticalAlign } from '../../../dataset/enum/VerticalAlign'
import { DeepRequired } from '../../../interface/Common' import { DeepRequired } from '../../../interface/Common'
import { IEditorOption } from '../../../interface/Editor' import { IEditorOption } from '../../../interface/Editor'
import { IElement } from '../../../interface/Element' import { IElement } from '../../../interface/Element'
import { IRowElement } from '../../../interface/Row' import { IRow, IRowElement } from '../../../interface/Row'
import { Draw } from '../Draw' import { Draw } from '../Draw'
interface ICheckboxRenderOption {
ctx: CanvasRenderingContext2D
x: number
y: number
row: IRow
index: number
}
export class CheckboxParticle { export class CheckboxParticle {
private draw: Draw private draw: Draw
private options: DeepRequired<IEditorOption> private options: DeepRequired<IEditorOption>
@ -28,17 +38,41 @@ export class CheckboxParticle {
}) })
} }
public render( public render(payload: ICheckboxRenderOption) {
ctx: CanvasRenderingContext2D, const { ctx, x, index, row } = payload
element: IRowElement, let { y } = payload
x: number,
y: number
) {
const { const {
checkbox: { gap, lineWidth, fillStyle, strokeStyle }, checkbox: { gap, lineWidth, fillStyle, strokeStyle, verticalAlign },
scale scale
} = this.options } = this.options
const { metrics, checkbox } = element const { metrics, checkbox } = row.elementList[index]
// 垂直布局设置
if (
verticalAlign === VerticalAlign.TOP ||
verticalAlign === VerticalAlign.MIDDLE
) {
let nextIndex = index + 1
let nextElement: IRowElement | null = null
while (nextIndex < row.elementList.length) {
nextElement = row.elementList[nextIndex]
if (nextElement.value !== ZERO && nextElement.value !== NBSP) break
nextIndex++
}
// 以后一个非空格元素为基准
if (nextElement) {
const {
metrics: { boundingBoxAscent, boundingBoxDescent }
} = nextElement
const textHeight = boundingBoxAscent + boundingBoxDescent
if (textHeight > metrics.height) {
if (verticalAlign === VerticalAlign.TOP) {
y -= boundingBoxAscent - metrics.height
} else if (verticalAlign === VerticalAlign.MIDDLE) {
y -= (textHeight - metrics.height) / 2
}
}
}
}
// left top 四舍五入避免1像素问题 // left top 四舍五入避免1像素问题
const left = Math.round(x + gap * scale) const left = Math.round(x + gap * scale)
const top = Math.round(y - metrics.height + lineWidth) const top = Math.round(y - metrics.height + lineWidth)

@ -199,9 +199,16 @@ export class ListParticle {
height: height * scale height: height * scale
} }
} }
this.draw this.draw.getCheckboxParticle().render({
.getCheckboxParticle() ctx,
.render(ctx, checkboxRowElement, x - gap * scale, y) x: x - gap * scale,
y,
index: 0,
row: {
...row,
elementList: [checkboxRowElement, ...row.elementList]
}
})
} else { } else {
let text = '' let text = ''
if (startElement.listType === ListType.UL) { if (startElement.listType === ListType.UL) {

@ -1,9 +1,19 @@
import { NBSP, ZERO } from '../../../dataset/constant/Common'
import { VerticalAlign } from '../../../dataset/enum/VerticalAlign'
import { DeepRequired } from '../../../interface/Common' import { DeepRequired } from '../../../interface/Common'
import { IEditorOption } from '../../../interface/Editor' import { IEditorOption } from '../../../interface/Editor'
import { IElement } from '../../../interface/Element' import { IElement } from '../../../interface/Element'
import { IRowElement } from '../../../interface/Row' import { IRow, IRowElement } from '../../../interface/Row'
import { Draw } from '../Draw' import { Draw } from '../Draw'
interface IRadioRenderOption {
ctx: CanvasRenderingContext2D
x: number
y: number
row: IRow
index: number
}
export class RadioParticle { export class RadioParticle {
private draw: Draw private draw: Draw
private options: DeepRequired<IEditorOption> private options: DeepRequired<IEditorOption>
@ -28,17 +38,41 @@ export class RadioParticle {
}) })
} }
public render( public render(payload: IRadioRenderOption) {
ctx: CanvasRenderingContext2D, const { ctx, x, index, row } = payload
element: IRowElement, let { y } = payload
x: number,
y: number
) {
const { const {
radio: { gap, lineWidth, fillStyle, strokeStyle }, radio: { gap, lineWidth, fillStyle, strokeStyle, verticalAlign },
scale scale
} = this.options } = this.options
const { metrics, radio } = element const { metrics, radio } = row.elementList[index]
// 垂直布局设置
if (
verticalAlign === VerticalAlign.TOP ||
verticalAlign === VerticalAlign.MIDDLE
) {
let nextIndex = index + 1
let nextElement: IRowElement | null = null
while (nextIndex < row.elementList.length) {
nextElement = row.elementList[nextIndex]
if (nextElement.value !== ZERO && nextElement.value !== NBSP) break
nextIndex++
}
// 以后一个非空格元素为基准
if (nextElement) {
const {
metrics: { boundingBoxAscent, boundingBoxDescent }
} = nextElement
const textHeight = boundingBoxAscent + boundingBoxDescent
if (textHeight > metrics.height) {
if (verticalAlign === VerticalAlign.TOP) {
y -= boundingBoxAscent - metrics.height
} else if (verticalAlign === VerticalAlign.MIDDLE) {
y -= (textHeight - metrics.height) / 2
}
}
}
}
// left top 四舍五入避免1像素问题 // left top 四舍五入避免1像素问题
const left = Math.round(x + gap * scale) const left = Math.round(x + gap * scale)
const top = Math.round(y - metrics.height + lineWidth) const top = Math.round(y - metrics.height + lineWidth)

@ -1,4 +1,6 @@
import { ElementType } from '../../../../dataset/enum/Element' import { ElementType } from '../../../../dataset/enum/Element'
import { DeepRequired } from '../../../../interface/Common'
import { IEditorOption } from '../../../../interface/Editor'
import { IElement, IElementPosition } from '../../../../interface/Element' import { IElement, IElementPosition } from '../../../../interface/Element'
import { formatElementContext } from '../../../../utils/element' import { formatElementContext } from '../../../../utils/element'
import { RangeManager } from '../../../range/RangeManager' import { RangeManager } from '../../../range/RangeManager'
@ -9,9 +11,11 @@ export class DateParticle {
private draw: Draw private draw: Draw
private range: RangeManager private range: RangeManager
private datePicker: DatePicker private datePicker: DatePicker
private options: DeepRequired<IEditorOption>
constructor(draw: Draw) { constructor(draw: Draw) {
this.draw = draw this.draw = draw
this.options = draw.getOptions()
this.range = draw.getRange() this.range = draw.getRange()
this.datePicker = new DatePicker(draw, { this.datePicker = new DatePicker(draw, {
onSubmit: this._setValue.bind(this) onSubmit: this._setValue.bind(this)
@ -43,7 +47,9 @@ export class DateParticle {
} }
] ]
} }
formatElementContext(elementList, [dateElement], leftIndex) formatElementContext(elementList, [dateElement], leftIndex, {
editorOptions: this.options
})
this.draw.insertElementList([dateElement]) this.draw.insertElementList([dateElement])
} }

@ -30,11 +30,15 @@ export function input(data: string, host: CanvasEvent) {
const elementList = draw.getElementList() const elementList = draw.getElementList()
const copyElement = getAnchorElement(elementList, endIndex) const copyElement = getAnchorElement(elementList, endIndex)
if (!copyElement) return if (!copyElement) return
const isDesignMode = draw.isDesignMode()
const inputData: IElement[] = splitText(text).map(value => { const inputData: IElement[] = splitText(text).map(value => {
const newElement: IElement = { const newElement: IElement = {
value value
} }
if (!copyElement.title?.disabled && !copyElement.control?.disabled) { if (
isDesignMode ||
(!copyElement.title?.disabled && !copyElement.control?.disabled)
) {
const nextElement = elementList[endIndex + 1] const nextElement = elementList[endIndex + 1]
if ( if (
!copyElement.type || !copyElement.type ||
@ -69,7 +73,9 @@ export function input(data: string, host: CanvasEvent) {
if (startIndex !== endIndex) { if (startIndex !== endIndex) {
draw.spliceElementList(elementList, start, endIndex - startIndex) draw.spliceElementList(elementList, start, endIndex - startIndex)
} }
formatElementContext(elementList, inputData, startIndex) formatElementContext(elementList, inputData, startIndex, {
editorOptions: draw.getOptions()
})
draw.spliceElementList(elementList, start, 0, ...inputData) draw.spliceElementList(elementList, start, 0, ...inputData)
curIndex = startIndex + inputData.length curIndex = startIndex + inputData.length
} }

@ -40,7 +40,8 @@ export function enter(evt: KeyboardEvent, host: CanvasEvent) {
} }
// 格式化上下文 // 格式化上下文
formatElementContext(elementList, [enterText], startIndex, { formatElementContext(elementList, [enterText], startIndex, {
isBreakWhenWrap: true isBreakWhenWrap: true,
editorOptions: draw.getOptions()
}) })
// 标题结尾处回车无需格式化及样式复制 // 标题结尾处回车无需格式化及样式复制
if ( if (

@ -25,7 +25,9 @@ export function tab(evt: KeyboardEvent, host: CanvasEvent) {
const rangeManager = draw.getRange() const rangeManager = draw.getRange()
const { startIndex } = rangeManager.getRange() const { startIndex } = rangeManager.getRange()
const elementList = draw.getElementList() const elementList = draw.getElementList()
formatElementContext(elementList, [tabElement], startIndex) formatElementContext(elementList, [tabElement], startIndex, {
editorOptions: draw.getOptions()
})
draw.insertElementList([tabElement]) draw.insertElementList([tabElement])
} }
} }

@ -2,12 +2,16 @@ import { ImageDisplay } from '../../../dataset/enum/Common'
import { EditorMode } from '../../../dataset/enum/Editor' import { EditorMode } from '../../../dataset/enum/Editor'
import { ElementType } from '../../../dataset/enum/Element' import { ElementType } from '../../../dataset/enum/Element'
import { MouseEventButton } from '../../../dataset/enum/Event' import { MouseEventButton } from '../../../dataset/enum/Event'
import { ControlComponent } from '../../../dataset/enum/Control'
import { ControlType } from '../../../dataset/enum/Control'
import { IPreviewerDrawOption } from '../../../interface/Previewer' import { IPreviewerDrawOption } from '../../../interface/Previewer'
import { deepClone } from '../../../utils' import { deepClone } from '../../../utils'
import { isMod } from '../../../utils/hotkey' import { isMod } from '../../../utils/hotkey'
import { CheckboxControl } from '../../draw/control/checkbox/CheckboxControl' import { CheckboxControl } from '../../draw/control/checkbox/CheckboxControl'
import { RadioControl } from '../../draw/control/radio/RadioControl' import { RadioControl } from '../../draw/control/radio/RadioControl'
import { CanvasEvent } from '../CanvasEvent' import { CanvasEvent } from '../CanvasEvent'
import { IElement } from '../../../interface/Element'
import { Draw } from '../../draw/Draw'
export function setRangeCache(host: CanvasEvent) { export function setRangeCache(host: CanvasEvent) {
const draw = host.getDraw() const draw = host.getDraw()
@ -21,6 +25,42 @@ export function setRangeCache(host: CanvasEvent) {
host.cachePositionContext = position.getPositionContext() host.cachePositionContext = position.getPositionContext()
} }
export function hitCheckbox(element: IElement, draw: Draw) {
const { checkbox, control } = element
// 复选框不在控件内独立控制
if (!control) {
draw.getCheckboxParticle().setSelect(element)
} else {
const codes = control?.code ? control.code.split(',') : []
if (checkbox?.value) {
const codeIndex = codes.findIndex(c => c === checkbox.code)
codes.splice(codeIndex, 1)
} else {
if (checkbox?.code) {
codes.push(checkbox.code)
}
}
const activeControl = draw.getControl().getActiveControl()
if (activeControl instanceof CheckboxControl) {
activeControl.setSelect(codes)
}
}
}
export function hitRadio(element: IElement, draw: Draw) {
const { radio, control } = element
// 单选框不在控件内独立控制
if (!control) {
draw.getRadioParticle().setSelect(element)
} else {
const codes = radio?.code ? [radio.code] : []
const activeControl = draw.getControl().getActiveControl()
if (activeControl instanceof RadioControl) {
activeControl.setSelect(codes)
}
}
}
export function mousedown(evt: MouseEvent, host: CanvasEvent) { export function mousedown(evt: MouseEvent, host: CanvasEvent) {
if (evt.button === MouseEventButton.RIGHT) return if (evt.button === MouseEventButton.RIGHT) return
const draw = host.getDraw() const draw = host.getDraw()
@ -100,40 +140,27 @@ export function mousedown(evt: MouseEvent, host: CanvasEvent) {
rangeManager.setRange(startIndex, endIndex) rangeManager.setRange(startIndex, endIndex)
position.setCursorPosition(positionList[curIndex]) position.setCursorPosition(positionList[curIndex])
// 复选框 // 复选框
const isSetCheckbox = isDirectHitCheckbox && !isReadonly if (isDirectHitCheckbox && !isReadonly) {
// 单选框 hitCheckbox(curElement, draw)
const isSetRadio = isDirectHitRadio && !isReadonly } else if (isDirectHitRadio && !isReadonly) {
if (isSetCheckbox) { hitRadio(curElement, draw)
const { checkbox, control } = curElement } else if (
// 复选框不在控件内独立控制 curElement.controlComponent === ControlComponent.VALUE &&
if (!control) { (curElement.control?.type === ControlType.CHECKBOX ||
draw.getCheckboxParticle().setSelect(curElement) curElement.control?.type === ControlType.RADIO)
} else { ) {
const codes = control?.code ? control.code.split(',') : [] // 向左查找
if (checkbox?.value) { let preIndex = curIndex
const codeIndex = codes.findIndex(c => c === checkbox.code) while (preIndex > 0) {
codes.splice(codeIndex, 1) const preElement = elementList[preIndex]
} else { if (preElement.controlComponent === ControlComponent.CHECKBOX) {
if (checkbox?.code) { hitCheckbox(preElement, draw)
codes.push(checkbox.code) break
} } else if (preElement.controlComponent === ControlComponent.RADIO) {
} hitRadio(preElement, draw)
const activeControl = draw.getControl().getActiveControl() break
if (activeControl instanceof CheckboxControl) {
activeControl.setSelect(codes)
}
}
} else if (isSetRadio) {
const { control, radio } = curElement
// 单选框不在控件内独立控制
if (!control) {
draw.getRadioParticle().setSelect(curElement)
} else {
const codes = radio?.code ? [radio.code] : []
const activeControl = draw.getControl().getActiveControl()
if (activeControl instanceof RadioControl) {
activeControl.setSelect(codes)
} }
preIndex--
} }
} else { } else {
draw.render({ draw.render({

@ -176,7 +176,9 @@ export function mouseup(evt: MouseEvent, host: CanvasEvent) {
return newElement return newElement
} }
}) })
formatElementContext(elementList, replaceElementList, range.startIndex) formatElementContext(elementList, replaceElementList, range.startIndex, {
editorOptions: draw.getOptions()
})
// 缓存拖拽选区开始元素、位置、开始结束id // 缓存拖拽选区开始元素、位置、开始结束id
const cacheStartElement = cacheElementList[cacheStartIndex] const cacheStartElement = cacheElementList[cacheStartIndex]
const cacheStartPosition = cachePositionList[cacheStartIndex] const cacheStartPosition = cachePositionList[cacheStartIndex]

@ -50,7 +50,8 @@ export function pasteElement(host: CanvasEvent, elementList: IElement[]) {
} }
} }
formatElementContext(originalElementList, elementList, startIndex, { formatElementContext(originalElementList, elementList, startIndex, {
isBreakWhenWrap: true isBreakWhenWrap: true,
editorOptions: draw.getOptions()
}) })
} }
draw.insertElementList(elementList) draw.insertElementList(elementList)
@ -87,7 +88,9 @@ export function pasteImage(host: CanvasEvent, file: File | Blob) {
height: image.height height: image.height
} }
if (~startIndex) { if (~startIndex) {
formatElementContext(elementList, [imageElement], startIndex) formatElementContext(elementList, [imageElement], startIndex, {
editorOptions: draw.getOptions()
})
} }
draw.insertElementList([imageElement]) draw.insertElementList([imageElement])
} }

@ -430,7 +430,7 @@ export class RangeManager {
const color = curElement.color || null const color = curElement.color || null
const highlight = curElement.highlight || null const highlight = curElement.highlight || null
const rowFlex = curElement.rowFlex || null const rowFlex = curElement.rowFlex || null
const rowMargin = curElement.rowMargin || this.options.defaultRowMargin const rowMargin = curElement.rowMargin ?? this.options.defaultRowMargin
const dashArray = curElement.dashArray || [] const dashArray = curElement.dashArray || []
const level = curElement.level || null const level = curElement.level || null
const listType = curElement.listType || null const listType = curElement.listType || null

@ -1,4 +1,5 @@
import { ICheckboxOption } from '../../interface/Checkbox' import { ICheckboxOption } from '../../interface/Checkbox'
import { VerticalAlign } from '../enum/VerticalAlign'
export const defaultCheckboxOption: Readonly<Required<ICheckboxOption>> = { export const defaultCheckboxOption: Readonly<Required<ICheckboxOption>> = {
width: 14, width: 14,
@ -6,5 +7,6 @@ export const defaultCheckboxOption: Readonly<Required<ICheckboxOption>> = {
gap: 5, gap: 5,
lineWidth: 1, lineWidth: 1,
fillStyle: '#5175f4', fillStyle: '#5175f4',
strokeStyle: '#ffffff' strokeStyle: '#ffffff',
verticalAlign: VerticalAlign.BOTTOM
} }

@ -0,0 +1,8 @@
import { IPageBorderOption } from '../../interface/PageBorder'
export const defaultPageBorderOption: Readonly<Required<IPageBorderOption>> = {
color: '#000000',
lineWidth: 1,
padding: [0, 5, 0, 5],
disabled: true
}

@ -1,4 +1,5 @@
import { IRadioOption } from '../../interface/Radio' import { IRadioOption } from '../../interface/Radio'
import { VerticalAlign } from '../enum/VerticalAlign'
export const defaultRadioOption: Readonly<Required<IRadioOption>> = { export const defaultRadioOption: Readonly<Required<IRadioOption>> = {
width: 14, width: 14,
@ -6,5 +7,6 @@ export const defaultRadioOption: Readonly<Required<IRadioOption>> = {
gap: 5, gap: 5,
lineWidth: 1, lineWidth: 1,
fillStyle: '#5175f4', fillStyle: '#5175f4',
strokeStyle: '#000000' strokeStyle: '#000000',
verticalAlign: VerticalAlign.BOTTOM
} }

@ -19,7 +19,8 @@ export enum EditorMode {
CLEAN = 'clean', // 清洁模式(隐藏辅助元素) CLEAN = 'clean', // 清洁模式(隐藏辅助元素)
READONLY = 'readonly', // 只读模式(文档不可编辑) READONLY = 'readonly', // 只读模式(文档不可编辑)
FORM = 'form', // 表单模式(仅控件内可编辑) FORM = 'form', // 表单模式(仅控件内可编辑)
PRINT = 'print' // 打印模式(文档不可编辑、隐藏辅助元素、选区、未书写控件及边框) PRINT = 'print', // 打印模式(文档不可编辑、隐藏辅助元素、选区、未书写控件及边框)
DESIGN = 'design' // 设计模式(不可删除、只读等配置不控制)
} }
export enum EditorZone { export enum EditorZone {

@ -89,7 +89,8 @@ export default class Editor {
] ]
pageComponentData.forEach(elementList => { pageComponentData.forEach(elementList => {
formatElementList(elementList, { formatElementList(elementList, {
editorOptions editorOptions,
isForceCompensation: true
}) })
}) })
// 监听 // 监听

@ -1,3 +1,5 @@
import { VerticalAlign } from '../dataset/enum/VerticalAlign'
export interface ICheckbox { export interface ICheckbox {
value: boolean | null value: boolean | null
code?: string code?: string
@ -11,4 +13,5 @@ export interface ICheckboxOption {
lineWidth?: number lineWidth?: number
fillStyle?: string fillStyle?: string
strokeStyle?: string strokeStyle?: string
verticalAlign?: VerticalAlign
} }

@ -1,4 +1,4 @@
import { IElement } from '..' import { IElement, LocationPosition } from '..'
import { import {
EditorMode, EditorMode,
PageMode, PageMode,
@ -25,6 +25,7 @@ import { IZoneOption } from './Zone'
import { ISeparatorOption } from './Separator' import { ISeparatorOption } from './Separator'
import { ITableOption } from './table/Table' import { ITableOption } from './table/Table'
import { ILineNumberOption } from './LineNumber' import { ILineNumberOption } from './LineNumber'
import { IPageBorderOption } from './PageBorder'
export interface IEditorData { export interface IEditorData {
header?: IElement[] header?: IElement[]
@ -91,6 +92,7 @@ export interface IEditorOption {
lineBreak?: ILineBreakOption lineBreak?: ILineBreakOption
separator?: ISeparatorOption separator?: ISeparatorOption
lineNumber?: ILineNumberOption lineNumber?: ILineNumberOption
pageBorder?: IPageBorderOption
} }
export interface IEditorResult { export interface IEditorResult {
@ -123,3 +125,7 @@ export type IUpdateOption = Omit<
export interface ISetValueOption { export interface ISetValueOption {
isSetCursor?: boolean isSetCursor?: boolean
} }
export interface IFocusOption {
position?: LocationPosition
}

@ -0,0 +1,8 @@
import { IPadding } from './Common'
export interface IPageBorderOption {
color?: string
lineWidth?: number
padding?: IPadding
disabled?: boolean
}

@ -1,3 +1,5 @@
import { VerticalAlign } from '../dataset/enum/VerticalAlign'
export interface IRadio { export interface IRadio {
value: boolean | null value: boolean | null
code?: string code?: string
@ -11,4 +13,5 @@ export interface IRadioOption {
lineWidth?: number lineWidth?: number
fillStyle?: string fillStyle?: string
strokeStyle?: string strokeStyle?: string
verticalAlign?: VerticalAlign
} }

@ -9,6 +9,7 @@ import {
splitText splitText
} from '.' } from '.'
import { import {
EditorMode,
ElementType, ElementType,
IEditorOption, IEditorOption,
IElement, IElement,
@ -65,7 +66,8 @@ export function unzipElementList(elementList: IElement[]): IElement[] {
} }
interface IFormatElementListOption { interface IFormatElementListOption {
isHandleFirstElement?: boolean isHandleFirstElement?: boolean // 根据上下文确定首字符处理逻辑(处理首字符补偿)
isForceCompensation?: boolean // 强制补偿字符
editorOptions: DeepRequired<IEditorOption> editorOptions: DeepRequired<IEditorOption>
} }
@ -73,17 +75,19 @@ export function formatElementList(
elementList: IElement[], elementList: IElement[],
options: IFormatElementListOption options: IFormatElementListOption
) { ) {
const { isHandleFirstElement, editorOptions } = <IFormatElementListOption>{ const {
isHandleFirstElement: true, isHandleFirstElement = true,
...options isForceCompensation = false,
} editorOptions
} = options
const startElement = elementList[0] const startElement = elementList[0]
// 非首字符零宽节点文本元素则补偿-列表元素内部会补偿此处忽略 // 非首字符零宽节点文本元素则补偿-列表元素内部会补偿此处忽略
if ( if (
isHandleFirstElement && isForceCompensation ||
startElement?.type !== ElementType.LIST && (isHandleFirstElement &&
((startElement?.type && startElement.type !== ElementType.TEXT) || startElement?.type !== ElementType.LIST &&
!START_LINE_BREAK_REG.test(startElement?.value)) ((startElement?.type && startElement.type !== ElementType.TEXT) ||
!START_LINE_BREAK_REG.test(startElement?.value)))
) { ) {
elementList.unshift({ elementList.unshift({
value: ZERO value: ZERO
@ -100,7 +104,8 @@ export function formatElementList(
const valueList = el.valueList || [] const valueList = el.valueList || []
formatElementList(valueList, { formatElementList(valueList, {
...options, ...options,
isHandleFirstElement: false isHandleFirstElement: false,
isForceCompensation: false
}) })
// 追加节点 // 追加节点
if (valueList.length) { if (valueList.length) {
@ -134,7 +139,8 @@ export function formatElementList(
const valueList = el.valueList || [] const valueList = el.valueList || []
formatElementList(valueList, { formatElementList(valueList, {
...options, ...options,
isHandleFirstElement: true isHandleFirstElement: true,
isForceCompensation: false
}) })
// 追加节点 // 追加节点
if (valueList.length) { if (valueList.length) {
@ -170,7 +176,8 @@ export function formatElementList(
td.id = tdId td.id = tdId
formatElementList(td.value, { formatElementList(td.value, {
...options, ...options,
isHandleFirstElement: true isHandleFirstElement: true,
isForceCompensation: true
}) })
for (let v = 0; v < td.value.length; v++) { for (let v = 0; v < td.value.length; v++) {
const value = td.value[v] const value = td.value[v]
@ -385,7 +392,8 @@ export function formatElementList(
} }
formatElementList(valueList, { formatElementList(valueList, {
...options, ...options,
isHandleFirstElement: false isHandleFirstElement: false,
isForceCompensation: false
}) })
for (let v = 0; v < valueList.length; v++) { for (let v = 0; v < valueList.length; v++) {
const element = valueList[v] const element = valueList[v]
@ -451,7 +459,7 @@ export function formatElementList(
} }
el = elementList[i] el = elementList[i]
} }
if (el.value === '\n') { if (el.value === '\n' || el.value == '\r\n') {
el.value = ZERO el.value = ZERO
} }
if (el.type === ElementType.IMAGE || el.type === ElementType.BLOCK) { if (el.type === ElementType.IMAGE || el.type === ElementType.BLOCK) {
@ -886,7 +894,8 @@ export function getAnchorElement(
} }
export interface IFormatElementContextOption { export interface IFormatElementContextOption {
isBreakWhenWrap: boolean isBreakWhenWrap?: boolean
editorOptions?: DeepRequired<IEditorOption>
} }
export function formatElementContext( export function formatElementContext(
@ -897,11 +906,12 @@ export function formatElementContext(
) { ) {
let copyElement = getAnchorElement(sourceElementList, anchorIndex) let copyElement = getAnchorElement(sourceElementList, anchorIndex)
if (!copyElement) return if (!copyElement) return
// 标题元素禁用时不复制标题属性 const { isBreakWhenWrap = false, editorOptions } = options || {}
if (copyElement.title?.disabled) { const { mode } = editorOptions || {}
// 非设计模式时:标题元素禁用时不复制标题属性
if (mode !== EditorMode.DESIGN && copyElement.title?.disabled) {
copyElement = omitObject(copyElement, TITLE_CONTEXT_ATTR) copyElement = omitObject(copyElement, TITLE_CONTEXT_ATTR)
} }
const { isBreakWhenWrap = false } = options || {}
// 是否已经换行 // 是否已经换行
let isBreakWarped = false let isBreakWarped = false
for (let e = 0; e < formatElementList.length; e++) { for (let e = 0; e < formatElementList.length; e++) {
@ -930,7 +940,8 @@ export function formatElementContext(
formatElementContext( formatElementContext(
sourceElementList, sourceElementList,
targetElement.valueList, targetElement.valueList,
anchorIndex anchorIndex,
options
) )
} }
// 非块类元素,需处理行属性 // 非块类元素,需处理行属性

@ -37,6 +37,8 @@ import { ITitleOption } from '../interface/Title'
import { IWatermark } from '../interface/Watermark' import { IWatermark } from '../interface/Watermark'
import { IZoneOption } from '../interface/Zone' import { IZoneOption } from '../interface/Zone'
import { ILineNumberOption } from '../interface/LineNumber' import { ILineNumberOption } from '../interface/LineNumber'
import { IPageBorderOption } from '../interface/PageBorder'
import { defaultPageBorderOption } from '../dataset/constant/PageBorder'
import { import {
EditorMode, EditorMode,
PageMode, PageMode,
@ -120,6 +122,10 @@ export function mergeOption(
...defaultLineNumberOption, ...defaultLineNumberOption,
...options.lineNumber ...options.lineNumber
} }
const pageBorderOptions: Required<IPageBorderOption> = {
...defaultPageBorderOption,
...options.pageBorder
}
return { return {
mode: EditorMode.EDIT, mode: EditorMode.EDIT,
@ -180,6 +186,7 @@ export function mergeOption(
background: backgroundOptions, background: backgroundOptions,
lineBreak: lineBreakOptions, lineBreak: lineBreakOptions,
separator: separatorOptions, separator: separatorOptions,
lineNumber: lineNumberOptions lineNumber: lineNumberOptions,
pageBorder: pageBorderOptions
} }
} }

@ -1412,6 +1412,10 @@ window.onload = function () {
{ {
mode: EditorMode.PRINT, mode: EditorMode.PRINT,
name: '打印模式' name: '打印模式'
},
{
mode: EditorMode.DESIGN,
name: '设计模式'
} }
] ]
const modeElement = document.querySelector<HTMLDivElement>('.editor-mode')! const modeElement = document.querySelector<HTMLDivElement>('.editor-mode')!

Loading…
Cancel
Save