fix: IME position error #675

pr675
会PS的小码农 2 years ago
commit 20df644163

@ -51,6 +51,12 @@ body:
label: What is actually happening? label: What is actually happening?
validations: validations:
required: true required: true
- type: textarea
id: editor-value
attributes:
label: Editor Value
description: Output of `instance.command.getValue()`
render: json
- type: textarea - type: textarea
id: system-info id: system-info
attributes: attributes:

@ -1,3 +1,107 @@
## [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)
### Bug Fixes
* three click selection paragraph boundary error #742 ([9dd192f](https://github.com/Hufe921/canvas-editor/commit/9dd192ffd26f9f271efa06c81b6bd5e32557d872)), closes [#742](https://github.com/Hufe921/canvas-editor/issues/742)
### Documentation
* update plugin markdown ([c2d3e94](https://github.com/Hufe921/canvas-editor/commit/c2d3e941b19a6bc766b0bfa8dac7911578170b6c))
### Features
* add id property to contextMenu context #737 ([997ecc0](https://github.com/Hufe921/canvas-editor/commit/997ecc03e89afeaaf6b903f9f253c5c5090b3f78)), closes [#737](https://github.com/Hufe921/canvas-editor/issues/737)
* add line number option #734 ([d89218a](https://github.com/Hufe921/canvas-editor/commit/d89218a916fce5a7b7b6bf27fa4663ecc4d15cf6)), closes [#734](https://github.com/Hufe921/canvas-editor/issues/734)
* control related apis support the control id property ([dd1b53e](https://github.com/Hufe921/canvas-editor/commit/dd1b53ee6297bc72b0064c09522b597118919c50))
* set range using the shift shortcut key #728 ([8878fd7](https://github.com/Hufe921/canvas-editor/commit/8878fd7734ae0a566f16ca2db14e64ded5a05f38)), closes [#728](https://github.com/Hufe921/canvas-editor/issues/728)
## [0.9.88](https://github.com/Hufe921/canvas-editor/compare/v0.9.87...v0.9.88) (2024-08-02)
### Bug Fixes
* float image position boundary error #716 ([f5113f5](https://github.com/Hufe921/canvas-editor/commit/f5113f539c5b224fdb9af8cde8e61f632dc7e49f)), closes [#716](https://github.com/Hufe921/canvas-editor/issues/716)
### Chores
* add touch support to signature component ([c3ef290](https://github.com/Hufe921/canvas-editor/commit/c3ef2907ae31be21cd3bc61a5600ad381230034f))
* update issue template ([eea301e](https://github.com/Hufe921/canvas-editor/commit/eea301eb11fdc3d5870da2408abccf4d96d8532f))
### Features
* add applyPageNumbers attribute to background option #729 ([8d112a8](https://github.com/Hufe921/canvas-editor/commit/8d112a8518656edd14201cb9bd16a417752fcdf9)), closes [#729](https://github.com/Hufe921/canvas-editor/issues/729)
* add cursor setting option to executeSetValue api #715 ([3235e5a](https://github.com/Hufe921/canvas-editor/commit/3235e5ae386bd1f48f5f64427e1eb0ed3782838d)), closes [#715](https://github.com/Hufe921/canvas-editor/issues/715)
* add title disabled property #680 ([87a8dbe](https://github.com/Hufe921/canvas-editor/commit/87a8dbea1596bcdee28edbb58b02fbe2a36e6c58)), closes [#680](https://github.com/Hufe921/canvas-editor/issues/680)
## [0.9.87](https://github.com/Hufe921/canvas-editor/compare/v0.9.86...v0.9.87) (2024-07-26)
### Bug Fixes
* format of checkbox and radio control value ([72686fd](https://github.com/Hufe921/canvas-editor/commit/72686fda1bf12353699fd57273493d8a9b4caee4))
* highlight checkbox and radio control #707 ([d939aa3](https://github.com/Hufe921/canvas-editor/commit/d939aa35ba7b19e5d1b5c4121fb873b2c579cc55)), closes [#707](https://github.com/Hufe921/canvas-editor/issues/707)
* set control highlight limit component type ([e14fbd6](https://github.com/Hufe921/canvas-editor/commit/e14fbd622d1076cba5f34bd4fa29530af4f70261))
* update punctuation width when scaling the page #712 ([83cb479](https://github.com/Hufe921/canvas-editor/commit/83cb47913195c43cde2b3ae344ff1d89a223e6a6)), closes [#712](https://github.com/Hufe921/canvas-editor/issues/712)
* word breaking when scaling the page #666 ([2bd1f34](https://github.com/Hufe921/canvas-editor/commit/2bd1f34c15196968f69d4711613f1c81f1dade68)), closes [#666](https://github.com/Hufe921/canvas-editor/issues/666)
### Features
* add custom field to getValue api #699 ([67c63f8](https://github.com/Hufe921/canvas-editor/commit/67c63f856f2c0e3e8c0644e39694357639c18d7e)), closes [#699](https://github.com/Hufe921/canvas-editor/issues/699)
* delete cell contents when selecting rows and columns #706 ([ccd0627](https://github.com/Hufe921/canvas-editor/commit/ccd0627a6fad975acb43e9f16dda3fa13972c908)), closes [#706](https://github.com/Hufe921/canvas-editor/issues/706)
* optimize text selection at the beginning of a line #695 ([97ac2da](https://github.com/Hufe921/canvas-editor/commit/97ac2daaf8f0688b181cb8baea9ba74ae1664361)), closes [#695](https://github.com/Hufe921/canvas-editor/issues/695)
* set control properties in read-only mode #679 ([26a3468](https://github.com/Hufe921/canvas-editor/commit/26a3468f66d67bf8249cdc1a679c740d7cf1a9c9)), closes [#679](https://github.com/Hufe921/canvas-editor/issues/679)
* set the container scrollbar to automatically scroll #711 ([b226566](https://github.com/Hufe921/canvas-editor/commit/b226566e2bf6f1b4bc3ae11577c7a96ae3cbf2d0)), closes [#711](https://github.com/Hufe921/canvas-editor/issues/711)
## [0.9.86](https://github.com/Hufe921/canvas-editor/compare/v0.9.85...v0.9.86) (2024-07-13) ## [0.9.86](https://github.com/Hufe921/canvas-editor/compare/v0.9.85...v0.9.86) (2024-07-13)

@ -21,6 +21,10 @@ export default defineConfig({
{ {
text: 'Demo', text: 'Demo',
link: 'https://hufe.club/canvas-editor' link: 'https://hufe.club/canvas-editor'
},
{
text: '官方插件',
link: '/guide/plugin-internal.html'
} }
], ],
sidebar: [ sidebar: [
@ -71,7 +75,10 @@ export default defineConfig({
}, },
{ {
text: '插件', text: '插件',
items: [{ text: '自定义插件', link: '/guide/plugin' }] items: [
{ text: '自定义插件', link: '/guide/plugin-custom' },
{ text: '官方插件', link: '/guide/plugin-internal' }
]
} }
], ],
socialLinks: [ socialLinks: [
@ -104,6 +111,10 @@ export default defineConfig({
{ {
text: 'Demo', text: 'Demo',
link: 'https://hufe.club/canvas-editor' link: 'https://hufe.club/canvas-editor'
},
{
text: 'Official plugin',
link: '/en/guide/plugin-internal.html'
} }
], ],
sidebar: [ sidebar: [
@ -154,7 +165,10 @@ export default defineConfig({
}, },
{ {
text: 'Plugin', text: 'Plugin',
items: [{ text: 'plugin', link: '/en/guide/plugin' }] items: [
{ text: 'custom', link: '/en/guide/plugin-custom' },
{ text: 'official', link: '/en/guide/plugin-internal' }
]
} }
] ]
} }

@ -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
@ -789,7 +789,7 @@ Feature: Set the editor data
Usage: Usage:
```javascript ```javascript
instance.command.executeSetValue(payload: Partial<IEditorData>) instance.command.executeSetValue(payload: Partial<IEditorData>, options?: ISetValueOption)
``` ```
## executeRemoveControl ## executeRemoveControl
@ -929,7 +929,7 @@ Feature: Positioning and activating control
Usage: Usage:
```javascript ```javascript
instance.command.executeLocationControl(controlId: string) instance.command.executeLocationControl(controlId: string, options?: ILocationControlOption)
``` ```
## executeInsertControl ## executeInsertControl
@ -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)
```

@ -60,17 +60,19 @@ 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
group?: IGroup // Group option. {opacity?:number; backgroundColor?:string; activeOpacity?:number; activeBackgroundColor?:string; disabled?:boolean} group?: IGroup // Group option. {opacity?:number; backgroundColor?:string; activeOpacity?:number; activeBackgroundColor?:string; disabled?:boolean}
pageBreak?: IPageBreak // PageBreak option。{font?:string; fontSize?:number; lineDash?:number[];} pageBreak?: IPageBreak // PageBreak option。{font?:string; fontSize?:number; lineDash?:number[];}
zone?: IZoneOption // Zone option。{tipDisabled?:boolean;} zone?: IZoneOption // Zone option。{tipDisabled?:boolean;}
background?: IBackgroundOption // Background option. {color?:string; image?:string; size?:BackgroundSize; repeat?:BackgroundRepeat;}。default: {color: '#FFFFFF'} background?: IBackgroundOption // Background option. {color?:string; image?:string; size?:BackgroundSize; repeat?:BackgroundRepeat; applyPageNumbers?:number[]}。default: {color: '#FFFFFF'}
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}
pageBorder?: IPageBorderOption // PageBorder option. {color?:string; lineWidth:number; padding?:IPadding; disabled?:boolean;}
} }
``` ```
@ -109,11 +111,11 @@ interface IFooter {
```typescript ```typescript
interface IPageNumber { interface IPageNumber {
bottom?: number // The size from the bottom of the page.default: 60 bottom?: number // The size from the bottom of the page.default: 60
size?: number // font size.default: 12 size?: number // font size. default: 12
font?: string // font.default: Microsoft YaHei font?: string // font. default: Microsoft YaHei
color?: string // font color.default: #000000 color?: string // font color. default: #000000
rowFlex?: RowFlex // Line alignment.default: CENTER rowFlex?: RowFlex // Line alignment. default: CENTER
format?: string // Page number format.default: {pageNo}。example{pageNo}/{pageCount} format?: string // Page number format. default: {pageNo}。example{pageNo}/{pageCount}
numberType?: NumberType // The numeric type. default: ARABIC numberType?: NumberType // The numeric type. default: ARABIC
disabled?: boolean // Whether to disable disabled?: boolean // Whether to disable
startPageNo?: number // Start page number.default: 1 startPageNo?: number // Start page number.default: 1
@ -127,10 +129,10 @@ interface IPageNumber {
```typescript ```typescript
interface IWatermark { interface IWatermark {
data: string // text. data: string // text.
color?: string // color.default: #AEB5C0 color?: string // color. default: #AEB5C0
opacity?: number // transparency.default: 0.3 opacity?: number // transparency. default: 0.3
size?: number // font size.default: 200 size?: number // font size. default: 200
font?: string // font.default: Microsoft YaHei font?: string // font. default: Microsoft YaHei
} }
``` ```
@ -139,9 +141,33 @@ interface IWatermark {
```typescript ```typescript
interface IPlaceholder { interface IPlaceholder {
data: string // text. data: string // text.
color?: string // color.default: #DCDFE6 color?: string // color. default: #DCDFE6
opacity?: number // transparency.default: 1 opacity?: number // transparency. default: 1
size?: number // font size.default: 16 size?: number // font size. default: 16
font?: string // font.default: Microsoft YaHei font?: string // font. default: Microsoft YaHei
}
```
## LineNumber Configuration
```typescript
interface ILineNumberOption {
size?: number // font size. default: 12
font?: string // font. default: Microsoft YaHei
color?: string // color. default: #000000
disabled?: boolean // Whether to disable. default: false
right?: number // Distance from the main text. default: 20
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
} }
``` ```

@ -1,8 +1,7 @@
# Custom Plugin # Custom Plugin
::: tip ::: tip
1. Currently, only methods can be added and modified to the editor instance, and more functions will be extended in the future Official plugin: https://github.com/Hufe921/canvas-editor-plugin
2. Official maintenance plugin: https://github.com/Hufe921/canvas-editor-plugin
::: :::
## Write a Plugin ## Write a Plugin
@ -14,11 +13,13 @@ export function myPlugin(editor: Editor, options?: Option) {
// 2. addsee moresrc/plugins/markdown // 2. addsee moresrc/plugins/markdown
editor.command.addFunction = () => {} editor.command.addFunction = () => {}
// 3. listener, eventbus, shortcut, contextmenu, override...
} }
``` ```
## Use Plugin ## Use Plugin
```javascript ```javascript
instance.add(myPlugin, options?: Option) instance.use(myPlugin, options?: Option)
``` ```

@ -0,0 +1,109 @@
# Official plugin
::: tip
Official plugin: https://github.com/Hufe921/canvas-editor-plugin
:::
## Barcode1d
```javascript
import Editor from "@hufe921/canvas-editor"
import barcode1DPlugin from "@hufe921/canvas-editor-plugin-barcode1d"
const instance = new Editor()
instance.use(barcode1DPlugin)
instance.executeInsertBarcode1D(
content: string,
width: number,
height: number,
options?: JsBarcode.Options
)
```
## Barcode2d
```javascript
import Editor from "@hufe921/canvas-editor"
import barcode2DPlugin from "@hufe921/canvas-editor-plugin-barcode2d"
const instance = new Editor()
instance.use(barcode2DPlugin, options?: IBarcode2DOption)
instance.executeInsertBarcode2D(
content: string,
width: number,
height: number,
hints?: Map<EncodeHintType, any>
)
```
## Code block
```javascript
import Editor from "@hufe921/canvas-editor"
import codeblockPlugin from "@hufe921/canvas-editor-plugin-codeblock"
const instance = new Editor()
instance.use(codeblockPlugin)
instance.executeInsertCodeblock(content: string)
```
## Word
```javascript
import Editor from '@hufe921/canvas-editor'
import docxPlugin from '@hufe921/canvas-editor-plugin-docx'
const instance = new Editor()
instance.use(docxPlugin)
command.executeImportDocx({
arrayBuffer: buffer
})
instance.executeExportDocx({
fileName: string
})
```
## Excel
```javascript
import Editor from '@hufe921/canvas-editor'
import excelPlugin from '@hufe921/canvas-editor-plugin-excel'
const instance = new Editor()
instance.use(excelPlugin)
command.executeImportExcel({
arrayBuffer: buffer
})
```
## Floating toolbar
```javascript
import Editor from '@hufe921/canvas-editor'
import floatingToolbarPlugin from '@hufe921/canvas-editor-plugin-floating-toolbar'
const instance = new Editor()
instance.use(floatingToolbarPlugin)
```
## Diagram
```javascript
import Editor from '@hufe921/canvas-editor'
import diagramPlugin from '@hufe921/canvas-editor-plugin-diagram'
const instance = new Editor()
instance.use(diagramPlugin)
command.executeLoadDiagram({
lang?: Lang
data?: string
onDestroy?: (message?: any) => void
})
```

@ -157,6 +157,7 @@ interface IElement {
title?: { title?: {
conceptId?: string; conceptId?: string;
deletable?: boolean; deletable?: boolean;
disabled?: boolean;
}; };
// list // list
listType?: ListType; listType?: ListType;

@ -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
@ -789,7 +789,7 @@ instance.command.executeUpdateElementById(payload: IUpdateElementByIdOption)
用法: 用法:
```javascript ```javascript
instance.command.executeSetValue(payload: Partial<IEditorData>) instance.command.executeSetValue(payload: Partial<IEditorData>, options?: ISetValueOption)
``` ```
## executeRemoveControl ## executeRemoveControl
@ -929,7 +929,7 @@ instance.command.executeSetControlHighlight(payload: ISetControlHighlightOption)
用法: 用法:
```javascript ```javascript
instance.command.executeLocationControl(controlId: string) instance.command.executeLocationControl(controlId: string, options?: ILocationControlOption)
``` ```
## executeInsertControl ## executeInsertControl
@ -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)
```

@ -60,17 +60,19 @@ 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 // 编辑器空白占位文本
group?: IGroup // 成组配置。{opacity?:number; backgroundColor?:string; activeOpacity?:number; activeBackgroundColor?:string; disabled?:boolean} group?: IGroup // 成组配置。{opacity?:number; backgroundColor?:string; activeOpacity?:number; activeBackgroundColor?:string; disabled?:boolean}
pageBreak?: IPageBreak // 分页符配置。{font?:string; fontSize?:number; lineDash?:number[];} pageBreak?: IPageBreak // 分页符配置。{font?:string; fontSize?:number; lineDash?:number[];}
zone?: IZoneOption // 编辑器区域配置。{tipDisabled?:boolean;} zone?: IZoneOption // 编辑器区域配置。{tipDisabled?:boolean;}
background?: IBackgroundOption // 背景配置。{color?:string; image?:string; size?:BackgroundSize; repeat?:BackgroundRepeat;}。默认:{color: '#FFFFFF'} background?: IBackgroundOption // 背景配置。{color?:string; image?:string; size?:BackgroundSize; repeat?:BackgroundRepeat; applyPageNumbers?:number[]}。默认:{color: '#FFFFFF'}
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}
pageBorder?: IPageBorderOption // 页面边框配置。{color?:string; lineWidth:number; padding?:IPadding; disabled?:boolean;}
} }
``` ```
@ -145,3 +147,27 @@ interface IPlaceholder {
font?: string // 字体。默认Microsoft YaHei font?: string // 字体。默认Microsoft YaHei
} }
``` ```
## 行号配置
```typescript
interface ILineNumberOption {
size?: number // 字体大小。默认12
font?: string // 字体。默认Microsoft YaHei
color?: string // 颜色。默认:#000000
disabled?: boolean // 是否禁用。默认true
right?: number // 距离正文距离。默认20
type?: LineNumberType // 编号类型(每页重新编号、连续编号)。默认:连续编号
}
```
## 页面边框配置
```typescript
interface IPageBorderOption {
color?: string // 颜色。默认:#000000
lineWidth?: number // 宽度。默认1
padding?: IPadding // 距离正文内边距。默认:[0, 5, 0, 5]
disabled?: boolean // 是否禁用。默认true
}
```

@ -1,8 +1,7 @@
# 自定义插件 # 自定义插件
::: tip ::: tip
1. 目前仅支持对编辑器实例进行方法的增加及修改,后续扩展更多功能 官方维护插件仓库https://github.com/Hufe921/canvas-editor-plugin
2. 官方维护插件https://github.com/Hufe921/canvas-editor-plugin
::: :::
## 开发插件 ## 开发插件
@ -14,11 +13,13 @@ export function myPlugin(editor: Editor, options?: Option) {
// 2. 增加方法详见src/plugins/markdown // 2. 增加方法详见src/plugins/markdown
editor.command.addFunction = () => {} editor.command.addFunction = () => {}
// 3. 事件监听、快捷键、右键菜单、重写方法等组合处理
} }
``` ```
## 使用插件 ## 使用插件
```javascript ```javascript
instance.add(myPlugin, options?: Option) instance.use(myPlugin, options?: Option)
``` ```

@ -0,0 +1,109 @@
# 官方插件
::: tip
官方维护插件仓库https://github.com/Hufe921/canvas-editor-plugin
:::
## 条形码
```javascript
import Editor from "@hufe921/canvas-editor"
import barcode1DPlugin from "@hufe921/canvas-editor-plugin-barcode1d"
const instance = new Editor()
instance.use(barcode1DPlugin)
instance.executeInsertBarcode1D(
content: string,
width: number,
height: number,
options?: JsBarcode.Options
)
```
## 二维码
```javascript
import Editor from "@hufe921/canvas-editor"
import barcode2DPlugin from "@hufe921/canvas-editor-plugin-barcode2d"
const instance = new Editor()
instance.use(barcode2DPlugin, options?: IBarcode2DOption)
instance.executeInsertBarcode2D(
content: string,
width: number,
height: number,
hints?: Map<EncodeHintType, any>
)
```
## 代码块
```javascript
import Editor from "@hufe921/canvas-editor"
import codeblockPlugin from "@hufe921/canvas-editor-plugin-codeblock"
const instance = new Editor()
instance.use(codeblockPlugin)
instance.executeInsertCodeblock(content: string)
```
## Word
```javascript
import Editor from '@hufe921/canvas-editor'
import docxPlugin from '@hufe921/canvas-editor-plugin-docx'
const instance = new Editor()
instance.use(docxPlugin)
command.executeImportDocx({
arrayBuffer: buffer
})
instance.executeExportDocx({
fileName: string
})
```
## Excel
```javascript
import Editor from '@hufe921/canvas-editor'
import excelPlugin from '@hufe921/canvas-editor-plugin-excel'
const instance = new Editor()
instance.use(excelPlugin)
command.executeImportExcel({
arrayBuffer: buffer
})
```
## 悬浮工具
```javascript
import Editor from '@hufe921/canvas-editor'
import floatingToolbarPlugin from '@hufe921/canvas-editor-plugin-floating-toolbar'
const instance = new Editor()
instance.use(floatingToolbarPlugin)
```
## 流程图
```javascript
import Editor from '@hufe921/canvas-editor'
import diagramPlugin from '@hufe921/canvas-editor-plugin-diagram'
const instance = new Editor()
instance.use(diagramPlugin)
command.executeLoadDiagram({
lang?: Lang
data?: string
onDestroy?: (message?: any) => void
})
```

@ -157,6 +157,7 @@ interface IElement {
title?: { title?: {
conceptId?: string; conceptId?: string;
deletable?: boolean; deletable?: boolean;
disabled?: boolean;
}; };
// 列表 // 列表
listType?: ListType; listType?: ListType;

@ -2,7 +2,7 @@
"name": "@hufe921/canvas-editor", "name": "@hufe921/canvas-editor",
"author": "Hufe", "author": "Hufe",
"license": "MIT", "license": "MIT",
"version": "0.9.86", "version": "0.9.91",
"description": "rich text editor by canvas/svg", "description": "rich text editor by canvas/svg",
"publishConfig": { "publishConfig": {
"registry": "https://registry.npmjs.org/", "registry": "https://registry.npmjs.org/",

@ -55,6 +55,10 @@ export class Signature {
this.ctx.lineCap = 'round' this.ctx.lineCap = 'round'
this._bindEvent() this._bindEvent()
this._clearUndoFn() this._clearUndoFn()
// this is necessary so that the screen does not move when moving - it is removed when closing the modal
document.documentElement.classList.add('overflow-hidden')
document.body.classList.add('overflow-hidden')
this.container.classList.add('overflow-hidden')
} }
private _render() { private _render() {
@ -166,6 +170,9 @@ export class Signature {
this.canvas.onmousedown = this._startDraw.bind(this) this.canvas.onmousedown = this._startDraw.bind(this)
this.canvas.onmousemove = this._draw.bind(this) this.canvas.onmousemove = this._draw.bind(this)
this.container.onmouseup = this._stopDraw.bind(this) this.container.onmouseup = this._stopDraw.bind(this)
this.container.ontouchmove = this.registerTouchmove.bind(this)
this.container.ontouchstart = this.registerTouchstart.bind(this)
this.container.ontouchend = this.registerTouchend.bind(this)
} }
private _undo() { private _undo() {
@ -302,8 +309,32 @@ export class Signature {
} }
} }
private registerTouchmove(evt: TouchEvent) {
this.registerTouchEvent(evt, 'mousemove')
}
private registerTouchstart(evt: TouchEvent) {
this.registerTouchEvent(evt, 'mousedown')
}
private registerTouchend() {
const me = new MouseEvent('mouseup', {})
this.canvas.dispatchEvent(me)
}
private registerTouchEvent(evt: TouchEvent, eventName: string) {
const touch = evt.touches[0]
const me = new MouseEvent(eventName, {
clientX: touch.clientX,
clientY: touch.clientY
})
this.canvas.dispatchEvent(me)
}
private _dispose() { private _dispose() {
this.mask.remove() this.mask.remove()
this.container.remove() this.container.remove()
document.documentElement.classList.remove('overflow-hidden')
document.body.classList.remove('overflow-hidden')
} }
} }

@ -126,3 +126,7 @@
background: #5b9cf3; background: #5b9cf3;
border-color: #5b9cf3; border-color: #5b9cf3;
} }
.overflow-hidden {
overflow: hidden !important;
}

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

@ -10,7 +10,7 @@ import {
titleSizeMapping titleSizeMapping
} from '../../dataset/constant/Title' } from '../../dataset/constant/Title'
import { defaultWatermarkOption } from '../../dataset/constant/Watermark' import { defaultWatermarkOption } from '../../dataset/constant/Watermark'
import { ImageDisplay } from '../../dataset/enum/Common' import { ImageDisplay, LocationPosition } from '../../dataset/enum/Common'
import { ControlComponent } from '../../dataset/enum/Control' import { ControlComponent } from '../../dataset/enum/Control'
import { import {
EditorContext, EditorContext,
@ -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'
@ -31,6 +32,7 @@ import { DeepRequired } from '../../interface/Common'
import { import {
IGetControlValueOption, IGetControlValueOption,
IGetControlValueResult, IGetControlValueResult,
ILocationControlOption,
ISetControlExtensionOption, ISetControlExtensionOption,
ISetControlHighlightOption, ISetControlHighlightOption,
ISetControlProperties, ISetControlProperties,
@ -51,6 +53,8 @@ import {
IEditorOption, IEditorOption,
IEditorResult, IEditorResult,
IEditorText, IEditorText,
IFocusOption,
ISetValueOption,
IUpdateOption IUpdateOption
} from '../../interface/Editor' } from '../../interface/Editor'
import { import {
@ -299,15 +303,13 @@ export class CommandAdapt {
} }
public applyPainterStyle() { public applyPainterStyle() {
const isDisabled = const isDisabled = this.draw.isReadonly() || this.draw.isDisabled()
this.draw.isReadonly() || this.control.getIsDisabledControl()
if (isDisabled) return if (isDisabled) return
this.canvasEvent.applyPainterStyle() this.canvasEvent.applyPainterStyle()
} }
public format() { public format() {
const isDisabled = const isDisabled = this.draw.isReadonly() || this.draw.isDisabled()
this.draw.isReadonly() || this.control.getIsDisabledControl()
if (isDisabled) return if (isDisabled) return
const selection = this.range.getSelectionElementList() const selection = this.range.getSelectionElementList()
// 选区设置或设置换行处样式 // 选区设置或设置换行处样式
@ -335,8 +337,7 @@ export class CommandAdapt {
} }
public font(payload: string) { public font(payload: string) {
const isDisabled = const isDisabled = this.draw.isReadonly() || this.draw.isDisabled()
this.draw.isReadonly() || this.control.getIsDisabledControl()
if (isDisabled) return if (isDisabled) return
const selection = this.range.getSelectionElementList() const selection = this.range.getSelectionElementList()
if (selection?.length) { if (selection?.length) {
@ -358,8 +359,7 @@ export class CommandAdapt {
public size(payload: number) { public size(payload: number) {
const { minSize, maxSize, defaultSize } = this.options const { minSize, maxSize, defaultSize } = this.options
if (payload < minSize || payload > maxSize) return if (payload < minSize || payload > maxSize) return
const isDisabled = const isDisabled = this.draw.isReadonly() || this.draw.isDisabled()
this.draw.isReadonly() || this.control.getIsDisabledControl()
if (isDisabled) return if (isDisabled) return
// 选区设置或设置换行处样式 // 选区设置或设置换行处样式
let renderOption: IDrawOption = {} let renderOption: IDrawOption = {}
@ -395,8 +395,7 @@ export class CommandAdapt {
} }
public sizeAdd() { public sizeAdd() {
const isDisabled = const isDisabled = this.draw.isReadonly() || this.draw.isDisabled()
this.draw.isReadonly() || this.control.getIsDisabledControl()
if (isDisabled) return if (isDisabled) return
const selection = this.range.getTextLikeSelectionElementList() const selection = this.range.getTextLikeSelectionElementList()
// 选区设置或设置换行处样式 // 选区设置或设置换行处样式
@ -435,8 +434,7 @@ export class CommandAdapt {
} }
public sizeMinus() { public sizeMinus() {
const isDisabled = const isDisabled = this.draw.isReadonly() || this.draw.isDisabled()
this.draw.isReadonly() || this.control.getIsDisabledControl()
if (isDisabled) return if (isDisabled) return
const selection = this.range.getTextLikeSelectionElementList() const selection = this.range.getTextLikeSelectionElementList()
// 选区设置或设置换行处样式 // 选区设置或设置换行处样式
@ -475,8 +473,7 @@ export class CommandAdapt {
} }
public bold() { public bold() {
const isDisabled = const isDisabled = this.draw.isReadonly() || this.draw.isDisabled()
this.draw.isReadonly() || this.control.getIsDisabledControl()
if (isDisabled) return if (isDisabled) return
const selection = this.range.getSelectionElementList() const selection = this.range.getSelectionElementList()
if (selection?.length) { if (selection?.length) {
@ -497,8 +494,7 @@ export class CommandAdapt {
} }
public italic() { public italic() {
const isDisabled = const isDisabled = this.draw.isReadonly() || this.draw.isDisabled()
this.draw.isReadonly() || this.control.getIsDisabledControl()
if (isDisabled) return if (isDisabled) return
const selection = this.range.getSelectionElementList() const selection = this.range.getSelectionElementList()
if (selection?.length) { if (selection?.length) {
@ -519,8 +515,7 @@ export class CommandAdapt {
} }
public underline(textDecoration?: ITextDecoration) { public underline(textDecoration?: ITextDecoration) {
const isDisabled = const isDisabled = this.draw.isReadonly() || this.draw.isDisabled()
this.draw.isReadonly() || this.control.getIsDisabledControl()
if (isDisabled) return if (isDisabled) return
const selection = this.range.getSelectionElementList() const selection = this.range.getSelectionElementList()
if (selection?.length) { if (selection?.length) {
@ -558,8 +553,7 @@ export class CommandAdapt {
} }
public strikeout() { public strikeout() {
const isDisabled = const isDisabled = this.draw.isReadonly() || this.draw.isDisabled()
this.draw.isReadonly() || this.control.getIsDisabledControl()
if (isDisabled) return if (isDisabled) return
const selection = this.range.getSelectionElementList() const selection = this.range.getSelectionElementList()
if (selection?.length) { if (selection?.length) {
@ -583,8 +577,7 @@ export class CommandAdapt {
} }
public superscript() { public superscript() {
const isDisabled = const isDisabled = this.draw.isReadonly() || this.draw.isDisabled()
this.draw.isReadonly() || this.control.getIsDisabledControl()
if (isDisabled) return if (isDisabled) return
const selection = this.range.getSelectionElementList() const selection = this.range.getSelectionElementList()
if (!selection) return if (!selection) return
@ -613,8 +606,7 @@ export class CommandAdapt {
} }
public subscript() { public subscript() {
const isDisabled = const isDisabled = this.draw.isReadonly() || this.draw.isDisabled()
this.draw.isReadonly() || this.control.getIsDisabledControl()
if (isDisabled) return if (isDisabled) return
const selection = this.range.getSelectionElementList() const selection = this.range.getSelectionElementList()
if (!selection) return if (!selection) return
@ -643,8 +635,7 @@ export class CommandAdapt {
} }
public color(payload: string | null) { public color(payload: string | null) {
const isDisabled = const isDisabled = this.draw.isReadonly() || this.draw.isDisabled()
this.draw.isReadonly() || this.control.getIsDisabledControl()
if (isDisabled) return if (isDisabled) return
const selection = this.range.getSelectionElementList() const selection = this.range.getSelectionElementList()
if (selection?.length) { if (selection?.length) {
@ -675,8 +666,7 @@ export class CommandAdapt {
} }
public highlight(payload: string | null) { public highlight(payload: string | null) {
const isDisabled = const isDisabled = this.draw.isReadonly() || this.draw.isDisabled()
this.draw.isReadonly() || this.control.getIsDisabledControl()
if (isDisabled) return if (isDisabled) return
const selection = this.range.getSelectionElementList() const selection = this.range.getSelectionElementList()
if (selection?.length) { if (selection?.length) {
@ -801,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
@ -822,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)
@ -1801,8 +1791,7 @@ export class CommandAdapt {
} }
public image(payload: IDrawImagePayload) { public image(payload: IDrawImagePayload) {
const isDisabled = const isDisabled = this.draw.isReadonly() || this.draw.isDisabled()
this.draw.isReadonly() || this.control.getIsDisabledControl()
if (isDisabled) return if (isDisabled) return
const { startIndex, endIndex } = this.range.getRange() const { startIndex, endIndex } = this.range.getRange()
if (!~startIndex && !~endIndex) return if (!~startIndex && !~endIndex) return
@ -1962,12 +1951,11 @@ export class CommandAdapt {
} }
public async print() { public async print() {
const { scale, printPixelRatio, paperDirection } = this.options const { scale, printPixelRatio, paperDirection, width, height } =
this.options
if (scale !== 1) { if (scale !== 1) {
this.draw.setPageScale(1) this.draw.setPageScale(1)
} }
const width = this.draw.getOriginalWidth()
const height = this.draw.getOriginalHeight()
const base64List = await this.draw.getDataURL({ const base64List = await this.draw.getDataURL({
pixelRatio: printPixelRatio, pixelRatio: printPixelRatio,
mode: EditorMode.PRINT mode: EditorMode.PRINT
@ -2301,8 +2289,8 @@ export class CommandAdapt {
} }
} }
public setValue(payload: Partial<IEditorData>) { public setValue(payload: Partial<IEditorData>, options?: ISetValueOption) {
this.draw.setValue(payload) this.draw.setValue(payload, options)
} }
public removeControl() { public removeControl() {
@ -2449,19 +2437,19 @@ export class CommandAdapt {
public getControlValue( public getControlValue(
payload: IGetControlValueOption payload: IGetControlValueOption
): IGetControlValueResult | null { ): IGetControlValueResult | null {
return this.draw.getControl().getValueByConceptId(payload) return this.draw.getControl().getValueById(payload)
} }
public setControlValue(payload: ISetControlValueOption) { public setControlValue(payload: ISetControlValueOption) {
this.draw.getControl().setValueByConceptId(payload) this.draw.getControl().setValueById(payload)
} }
public setControlExtension(payload: ISetControlExtensionOption) { public setControlExtension(payload: ISetControlExtensionOption) {
this.draw.getControl().setExtensionByConceptId(payload) this.draw.getControl().setExtensionById(payload)
} }
public setControlProperties(payload: ISetControlProperties) { public setControlProperties(payload: ISetControlProperties) {
this.draw.getControl().setPropertiesByConceptId(payload) this.draw.getControl().setPropertiesById(payload)
} }
public setControlHighlight(payload: ISetControlHighlightOption) { public setControlHighlight(payload: ISetControlHighlightOption) {
@ -2483,7 +2471,8 @@ export class CommandAdapt {
return this.draw.getControl().getList() return this.draw.getControl().getList()
} }
public locationControl(controlId: string) { public locationControl(controlId: string, options?: ILocationControlOption) {
const isLocationAfter = options?.position === LocationPosition.AFTER
function location( function location(
elementList: IElement[], elementList: IElement[],
zone: EditorZone zone: EditorZone
@ -2517,7 +2506,16 @@ export class CommandAdapt {
} }
} }
if (element?.controlId !== controlId) continue if (element?.controlId !== controlId) continue
const curIndex = i - 1 let curIndex = i - 1
if (isLocationAfter) {
curIndex -= 1
if (
element.controlComponent !== ControlComponent.PLACEHOLDER &&
element.controlComponent !== ControlComponent.POSTFIX
) {
continue
}
}
return { return {
zone, zone,
range: { range: {
@ -2730,4 +2728,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
})
}
} }

@ -159,7 +159,9 @@ export class ContextMenu {
const originalElementList = this.draw.getOriginalElementList() const originalElementList = this.draw.getOriginalElementList()
const originTableElement = originalElementList[index!] || null const originTableElement = originalElementList[index!] || null
if (originTableElement) { if (originTableElement) {
tableElement = zipElementList([originTableElement])[0] tableElement = zipElementList([originTableElement], {
extraPickAttrs: ['id']
})[0]
} }
} }
// 是否存在跨行/列 // 是否存在跨行/列

@ -15,7 +15,8 @@ import {
import { import {
IEditorData, IEditorData,
IEditorOption, IEditorOption,
IEditorResult IEditorResult,
ISetValueOption
} from '../../interface/Editor' } from '../../interface/Editor'
import { import {
IElement, IElement,
@ -100,6 +101,8 @@ import { ImageDisplay } from '../../dataset/enum/Common'
import { PUNCTUATION_REG } from '../../dataset/constant/Regular' import { PUNCTUATION_REG } from '../../dataset/constant/Regular'
import { LineBreakParticle } from './particle/LineBreakParticle' import { LineBreakParticle } from './particle/LineBreakParticle'
import { MouseObserver } from '../observer/MouseObserver' import { MouseObserver } from '../observer/MouseObserver'
import { LineNumber } from './frame/LineNumber'
import { PageBorder } from './frame/PageBorder'
export class Draw { export class Draw {
private container: HTMLDivElement private container: HTMLDivElement
@ -137,6 +140,7 @@ export class Draw {
private tableParticle: TableParticle private tableParticle: TableParticle
private tableTool: TableTool private tableTool: TableTool
private pageNumber: PageNumber private pageNumber: PageNumber
private lineNumber: LineNumber
private waterMark: Watermark private waterMark: Watermark
private placeholder: Placeholder private placeholder: Placeholder
private header: Header private header: Header
@ -153,6 +157,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
@ -212,6 +217,7 @@ export class Draw {
this.tableParticle = new TableParticle(this) this.tableParticle = new TableParticle(this)
this.tableTool = new TableTool(this) this.tableTool = new TableTool(this)
this.pageNumber = new PageNumber(this) this.pageNumber = new PageNumber(this)
this.lineNumber = new LineNumber(this)
this.waterMark = new Watermark(this) this.waterMark = new Watermark(this)
this.placeholder = new Placeholder(this) this.placeholder = new Placeholder(this)
this.header = new Header(this, data.header) this.header = new Header(this, data.header)
@ -228,6 +234,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)
@ -298,6 +305,7 @@ export class Draw {
this.clearSideEffect() this.clearSideEffect()
this.range.clearRange() this.range.clearRange()
this.mode = payload this.mode = payload
this.options.mode = payload
this.render({ this.render({
isSetCursor: false, isSetCursor: false,
isSubmitHistory: false isSubmitHistory: false
@ -316,6 +324,23 @@ export class Draw {
} }
} }
public isDisabled() {
const { startIndex, endIndex } = this.range.getRange()
const elementList = this.getElementList()
if (startIndex === endIndex) {
const startElement = elementList[startIndex]
const nextElement = elementList[startIndex + 1]
return !!(
(startElement?.title?.disabled && nextElement?.title?.disabled) ||
(startElement?.control?.disabled && nextElement?.control?.disabled)
)
}
const selectionElementList = elementList.slice(startIndex + 1, endIndex + 1)
return selectionElementList.some(
element => element.title?.disabled || element.control?.disabled
)
}
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
@ -368,6 +393,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)
} }
@ -531,6 +568,10 @@ export class Draw {
return this.lineBreakParticle return this.lineBreakParticle
} }
public getTextParticle(): TextParticle {
return this.textParticle
}
public getHeaderElementList(): IElement[] { public getHeaderElementList(): IElement[] {
return this.header.getElementList() return this.header.getElementList()
} }
@ -1034,14 +1075,16 @@ export class Draw {
} }
} }
public setValue(payload: Partial<IEditorData>) { public setValue(payload: Partial<IEditorData>, options?: ISetValueOption) {
const { header, main, footer } = deepClone(payload) const { header, main, footer } = deepClone(payload)
if (!header && !main && !footer) return if (!header && !main && !footer) return
const { isSetCursor = false } = options || {}
const pageComponentData = [header, main, footer] const pageComponentData = [header, main, footer]
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({
@ -1051,8 +1094,17 @@ export class Draw {
}) })
// 渲染&计算&清空历史记录 // 渲染&计算&清空历史记录
this.historyManager.recovery() this.historyManager.recovery()
const curIndex = isSetCursor
? main?.length
? main.length - 1
: 0
: undefined
if (curIndex !== undefined) {
this.range.setRange(curIndex, curIndex)
}
this.render({ this.render({
isSetCursor: false, curIndex,
isSetCursor,
isFirstRender: true isFirstRender: true
}) })
} }
@ -1140,7 +1192,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
) )
} }
@ -1166,6 +1218,7 @@ export class Draw {
ascent: 0, ascent: 0,
elementList: [], elementList: [],
startIndex: 0, startIndex: 0,
rowIndex: 0,
rowFlex: elementList?.[0]?.rowFlex || elementList?.[1]?.rowFlex rowFlex: elementList?.[0]?.rowFlex || elementList?.[1]?.rowFlex
}) })
} }
@ -1178,7 +1231,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,
@ -1682,16 +1735,18 @@ export class Draw {
i i
) )
// 单词宽度大于行可用宽度,无需折行 // 单词宽度大于行可用宽度,无需折行
if (width <= availableWidth) { const wordWidth = width * scale
curRowWidth += width if (wordWidth <= availableWidth) {
curRowWidth += wordWidth
nextElement = endElement nextElement = endElement
} }
} }
// 标点符号 // 标点符号
curRowWidth += this.textParticle.measurePunctuationWidth( const punctuationWidth = this.textParticle.measurePunctuationWidth(
ctx, ctx,
nextElement nextElement
) )
curRowWidth += punctuationWidth * scale
} }
} }
// 列表信息 // 列表信息
@ -1724,6 +1779,7 @@ export class Draw {
startIndex: i, startIndex: i,
elementList: [rowElement], elementList: [rowElement],
ascent, ascent,
rowIndex: curRow.rowIndex + 1,
rowFlex: elementList[i]?.rowFlex || elementList[i + 1]?.rowFlex, rowFlex: elementList[i]?.rowFlex || elementList[i + 1]?.rowFlex,
isPageBreak: element.type === ElementType.PAGE_BREAK isPageBreak: element.type === ElementType.PAGE_BREAK
} }
@ -1981,13 +2037,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 (
@ -2228,6 +2296,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++) {
@ -2244,8 +2313,8 @@ export class Draw {
this.imageParticle.render( this.imageParticle.render(
ctx, ctx,
element, element,
imgFloatPosition.x, imgFloatPosition.x * scale,
imgFloatPosition.y imgFloatPosition.y * scale
) )
} }
} }
@ -2265,7 +2334,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 } = this.options const {
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]
// 判断当前激活区域-非正文区域时元素透明度降低 // 判断当前激活区域-非正文区域时元素透明度降低
@ -2326,6 +2403,14 @@ export class Draw {
if (this.elementList.length <= 1 && !this.elementList[0]?.listId) { if (this.elementList.length <= 1 && !this.elementList[0]?.listId) {
this.placeholder.render(ctx) this.placeholder.render(ctx)
} }
// 渲染行数
if (!lineNumber.disabled) {
this.lineNumber.render(ctx, pageNo)
}
// 绘制页面边框
if (!pageBorder.disabled) {
this.pageBorder.render(ctx)
}
} }
private _disconnectLazyRender() { private _disconnectLazyRender() {

@ -207,8 +207,17 @@ export class Control {
return prefixCount === postfixCount return prefixCount === postfixCount
} }
public getIsDisabledControl(): boolean { public getIsDisabledControl(context: IControlContext = {}): boolean {
return !!this.activeControl?.getElement().control?.disabled if (!this.activeControl) return false
const { startIndex, endIndex } = context.range || this.range.getRange()
if (startIndex === endIndex && ~startIndex && ~endIndex) {
const elementList = context.elementList || this.getElementList()
const startElement = elementList[startIndex]
if (startElement.controlComponent === ControlComponent.POSTFIX) {
return false
}
}
return !!this.activeControl.getElement()?.control?.disabled
} }
public getContainer(): HTMLDivElement { public getContainer(): HTMLDivElement {
@ -560,11 +569,10 @@ export class Control {
return this.activeControl.cut() return this.activeControl.cut()
} }
public getValueByConceptId( public getValueById(payload: IGetControlValueOption): IGetControlValueResult {
payload: IGetControlValueOption const { id, conceptId } = payload
): IGetControlValueResult {
const { conceptId } = payload
const result: IGetControlValueResult = [] const result: IGetControlValueResult = []
if (!id && !conceptId) return result
const getValue = (elementList: IElement[], zone: EditorZone) => { const getValue = (elementList: IElement[], zone: EditorZone) => {
let i = 0 let i = 0
while (i < elementList.length) { while (i < elementList.length) {
@ -581,8 +589,14 @@ export class Control {
} }
} }
} }
if (element?.control?.conceptId !== conceptId) continue if (
const { type, code, valueSets } = element.control! !element.control ||
(id && element.controlId !== id) ||
(conceptId && element.control.conceptId !== conceptId)
) {
continue
}
const { type, code, valueSets } = element.control
let j = i let j = i
let textControlValue = '' let textControlValue = ''
while (j < elementList.length) { while (j < elementList.length) {
@ -646,9 +660,10 @@ export class Control {
return result return result
} }
public setValueByConceptId(payload: ISetControlValueOption) { public setValueById(payload: ISetControlValueOption) {
let isExistSet = false let isExistSet = false
const { conceptId, value } = payload const { id, conceptId, value } = payload
if (!id && !conceptId) return
// 设置值 // 设置值
const setValue = (elementList: IElement[]) => { const setValue = (elementList: IElement[]) => {
let i = 0 let i = 0
@ -666,7 +681,13 @@ export class Control {
} }
} }
} }
if (element?.control?.conceptId !== conceptId) continue if (
!element.control ||
(id && element.controlId !== id) ||
(conceptId && element.control.conceptId !== conceptId)
) {
continue
}
isExistSet = true isExistSet = true
const { type } = element.control! const { type } = element.control!
// 当前控件结束索引 // 当前控件结束索引
@ -712,7 +733,7 @@ export class Control {
} else if (type === ControlType.CHECKBOX) { } else if (type === ControlType.CHECKBOX) {
const checkbox = new CheckboxControl(element, this) const checkbox = new CheckboxControl(element, this)
this.activeControl = checkbox this.activeControl = checkbox
const codes = value?.split(',') || [] const codes = value ? value.split(',') : []
checkbox.setSelect(codes, controlContext, controlRule) checkbox.setSelect(codes, controlContext, controlRule)
} else if (type === ControlType.RADIO) { } else if (type === ControlType.RADIO) {
const radio = new RadioControl(element, this) const radio = new RadioControl(element, this)
@ -758,8 +779,9 @@ export class Control {
} }
} }
public setExtensionByConceptId(payload: ISetControlExtensionOption) { public setExtensionById(payload: ISetControlExtensionOption) {
const { conceptId, extension } = payload const { id, conceptId, extension } = payload
if (!id && !conceptId) return
const setExtension = (elementList: IElement[]) => { const setExtension = (elementList: IElement[]) => {
let i = 0 let i = 0
while (i < elementList.length) { while (i < elementList.length) {
@ -776,7 +798,13 @@ export class Control {
} }
} }
} }
if (element?.control?.conceptId !== conceptId) continue if (
!element.control ||
(id && element.controlId !== id) ||
(conceptId && element.control.conceptId !== conceptId)
) {
continue
}
element.control.extension = extension element.control.extension = extension
// 修改后控件结束索引 // 修改后控件结束索引
let newEndIndex = i let newEndIndex = i
@ -798,8 +826,9 @@ export class Control {
} }
} }
public setPropertiesByConceptId(payload: ISetControlProperties) { public setPropertiesById(payload: ISetControlProperties) {
const { conceptId, properties } = payload const { id, conceptId, properties } = payload
if (!id && !conceptId) return
let isExistUpdate = false let isExistUpdate = false
function setProperties(elementList: IElement[]) { function setProperties(elementList: IElement[]) {
let i = 0 let i = 0
@ -816,7 +845,13 @@ export class Control {
} }
} }
} }
if (element?.control?.conceptId !== conceptId) continue if (
!element.control ||
(id && element.controlId !== id) ||
(conceptId && element.control.conceptId !== conceptId)
) {
continue
}
isExistUpdate = true isExistUpdate = true
element.control = { element.control = {
...element.control, ...element.control,
@ -857,7 +892,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)

@ -77,7 +77,10 @@ export class CheckboxControl implements IControlInstance {
options: IControlRuleOption = {} options: IControlRuleOption = {}
) { ) {
// 校验是否可以设置 // 校验是否可以设置
if (!options.isIgnoreDisabledRule && this.control.getIsDisabledControl()) { if (
!options.isIgnoreDisabledRule &&
this.control.getIsDisabledControl(context)
) {
return return
} }
const { control } = this.element const { control } = this.element

@ -99,7 +99,10 @@ export class DateControl implements IControlInstance {
options: IControlRuleOption = {} options: IControlRuleOption = {}
): number { ): number {
// 校验是否可以设置 // 校验是否可以设置
if (!options.isIgnoreDisabledRule && this.control.getIsDisabledControl()) { if (
!options.isIgnoreDisabledRule &&
this.control.getIsDisabledControl(context)
) {
return -1 return -1
} }
const elementList = context.elementList || this.control.getElementList() const elementList = context.elementList || this.control.getElementList()
@ -147,7 +150,7 @@ export class DateControl implements IControlInstance {
): number { ): number {
const { isIgnoreDisabledRule = false, isAddPlaceholder = true } = options const { isIgnoreDisabledRule = false, isAddPlaceholder = true } = options
// 校验是否可以设置 // 校验是否可以设置
if (!isIgnoreDisabledRule && this.control.getIsDisabledControl()) { if (!isIgnoreDisabledRule && this.control.getIsDisabledControl(context)) {
return -1 return -1
} }
const range = this.getValueRange(context) const range = this.getValueRange(context)
@ -171,7 +174,10 @@ export class DateControl implements IControlInstance {
options: IControlRuleOption = {} options: IControlRuleOption = {}
) { ) {
// 校验是否可以设置 // 校验是否可以设置
if (!options.isIgnoreDisabledRule && this.control.getIsDisabledControl()) { if (
!options.isIgnoreDisabledRule &&
this.control.getIsDisabledControl(context)
) {
return return
} }
const elementList = context.elementList || this.control.getElementList() const elementList = context.elementList || this.control.getElementList()

@ -1,3 +1,5 @@
import { ZERO } from '../../../../dataset/constant/Common'
import { ControlComponent } from '../../../../dataset/enum/Control'
import { ElementType } from '../../../../dataset/enum/Element' import { ElementType } from '../../../../dataset/enum/Element'
import { DeepRequired } from '../../../../interface/Common' import { DeepRequired } from '../../../../interface/Common'
import { import {
@ -69,10 +71,13 @@ export class ControlSearch {
} }
} }
} }
const controlConceptId = element?.control?.conceptId const currentControl = element?.control
if (!controlConceptId) continue if (!currentControl) continue
const highlightIndex = this.highlightList.findIndex( const highlightIndex = this.highlightList.findIndex(
highlight => highlight.conceptId === controlConceptId highlight =>
highlight.id === element.controlId ||
(currentControl.conceptId &&
currentControl.conceptId === highlight.conceptId)
) )
if (!~highlightIndex) continue if (!~highlightIndex) continue
// 搜索后控件结束索引 // 搜索后控件结束索引
@ -85,7 +90,13 @@ export class ControlSearch {
} }
i = newEndIndex i = newEndIndex
// 高亮信息 // 高亮信息
const controlElementList = elementList.slice(startIndex, newEndIndex) const controlElementList = elementList
.slice(startIndex, newEndIndex)
.map(element =>
element.controlComponent === ControlComponent.VALUE
? element
: { value: ZERO }
)
const highlight = this.highlightList[highlightIndex] const highlight = this.highlightList[highlightIndex]
const { ruleList } = highlight const { ruleList } = highlight
for (let r = 0; r < ruleList.length; r++) { for (let r = 0; r < ruleList.length; r++) {

@ -12,7 +12,10 @@ export class RadioControl extends CheckboxControl {
options: IControlRuleOption = {} options: IControlRuleOption = {}
) { ) {
// 校验是否可以设置 // 校验是否可以设置
if (!options.isIgnoreDisabledRule && this.control.getIsDisabledControl()) { if (
!options.isIgnoreDisabledRule &&
this.control.getIsDisabledControl(context)
) {
return return
} }
const { control } = this.element const { control } = this.element

@ -163,7 +163,7 @@ export class SelectControl implements IControlInstance {
): number { ): number {
const { isIgnoreDisabledRule = false, isAddPlaceholder = true } = options const { isIgnoreDisabledRule = false, isAddPlaceholder = true } = options
// 校验是否可以设置 // 校验是否可以设置
if (!isIgnoreDisabledRule && this.control.getIsDisabledControl()) { if (!isIgnoreDisabledRule && this.control.getIsDisabledControl(context)) {
return -1 return -1
} }
const elementList = context.elementList || this.control.getElementList() const elementList = context.elementList || this.control.getElementList()
@ -215,7 +215,10 @@ export class SelectControl implements IControlInstance {
options: IControlRuleOption = {} options: IControlRuleOption = {}
) { ) {
// 校验是否可以设置 // 校验是否可以设置
if (!options.isIgnoreDisabledRule && this.control.getIsDisabledControl()) { if (
!options.isIgnoreDisabledRule &&
this.control.getIsDisabledControl(context)
) {
return return
} }
const elementList = context.elementList || this.control.getElementList() const elementList = context.elementList || this.control.getElementList()

@ -75,7 +75,10 @@ export class TextControl implements IControlInstance {
options: IControlRuleOption = {} options: IControlRuleOption = {}
): number { ): number {
// 校验是否可以设置 // 校验是否可以设置
if (!options.isIgnoreDisabledRule && this.control.getIsDisabledControl()) { if (
!options.isIgnoreDisabledRule &&
this.control.getIsDisabledControl(context)
) {
return -1 return -1
} }
const elementList = context.elementList || this.control.getElementList() const elementList = context.elementList || this.control.getElementList()
@ -122,7 +125,10 @@ export class TextControl implements IControlInstance {
options: IControlRuleOption = {} options: IControlRuleOption = {}
): number { ): number {
// 校验是否可以设置 // 校验是否可以设置
if (!options.isIgnoreDisabledRule && this.control.getIsDisabledControl()) { if (
!options.isIgnoreDisabledRule &&
this.control.getIsDisabledControl(context)
) {
return -1 return -1
} }
const elementList = context.elementList || this.control.getElementList() const elementList = context.elementList || this.control.getElementList()

@ -99,14 +99,19 @@ export class Background {
} }
public render(ctx: CanvasRenderingContext2D, pageNo: number) { public render(ctx: CanvasRenderingContext2D, pageNo: number) {
const { background } = this.options const {
if (background.image) { background: { image, color, applyPageNumbers }
} = this.options
if (
image &&
(!applyPageNumbers?.length || applyPageNumbers.includes(pageNo))
) {
const { width, height } = this.options const { width, height } = this.options
this._renderBackgroundImage(ctx, width, height) this._renderBackgroundImage(ctx, width, height)
} else { } else {
const width = this.draw.getCanvasWidth(pageNo) const width = this.draw.getCanvasWidth(pageNo)
const height = this.draw.getCanvasHeight(pageNo) const height = this.draw.getCanvasHeight(pageNo)
this._renderBackgroundColor(ctx, background.color, width, height) this._renderBackgroundColor(ctx, color, width, height)
} }
} }
} }

@ -0,0 +1,43 @@
import { LineNumberType } from '../../../dataset/enum/LineNumber'
import { DeepRequired } from '../../../interface/Common'
import { IEditorOption } from '../../../interface/Editor'
import { Draw } from '../Draw'
export class LineNumber {
private draw: Draw
private options: DeepRequired<IEditorOption>
constructor(draw: Draw) {
this.draw = draw
this.options = draw.getOptions()
}
public render(ctx: CanvasRenderingContext2D, pageNo: number) {
const {
scale,
lineNumber: { color, size, font, right, type }
} = this.options
const textParticle = this.draw.getTextParticle()
const margins = this.draw.getMargins()
const positionList = this.draw.getPosition().getOriginalMainPositionList()
const pageRowList = this.draw.getPageRowList()
const rowList = pageRowList[pageNo]
ctx.save()
ctx.fillStyle = color
ctx.font = `${size * scale}px ${font}`
for (let i = 0; i < rowList.length; i++) {
const row = rowList[i]
const {
coordinate: { leftBottom }
} = positionList[row.startIndex]
const seq = type === LineNumberType.PAGE ? i + 1 : row.rowIndex + 1
const textMetrics = textParticle.measureText(ctx, {
value: `${seq}`
})
const x = margins[3] - (textMetrics.width + right) * scale
const y = leftBottom[1] - textMetrics.actualBoundingBoxAscent * scale
ctx.fillText(`${seq}`, x, y)
}
ctx.restore()
}
}

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

@ -106,6 +106,8 @@ export class CanvasEvent {
public applyPainterStyle() { public applyPainterStyle() {
const painterStyle = this.draw.getPainterStyle() const painterStyle = this.draw.getPainterStyle()
if (!painterStyle) return if (!painterStyle) return
const isDisabled = this.draw.isReadonly() || this.draw.isDisabled()
if (isDisabled) return
const selection = this.range.getSelection() const selection = this.range.getSelection()
if (!selection) return if (!selection) return
const painterStyleKeys = Object.keys(painterStyle) const painterStyleKeys = Object.keys(painterStyle)

@ -195,7 +195,10 @@ function threeClick(host: CanvasEvent) {
} }
if (newStartIndex < 0) return if (newStartIndex < 0) return
let newEndIndex = index + downCount + 1 let newEndIndex = index + downCount + 1
if (elementList[newEndIndex]?.value === ZERO) { if (
elementList[newEndIndex]?.value === ZERO ||
newEndIndex > elementList.length - 1
) {
newEndIndex -= 1 newEndIndex -= 1
} }
rangeManager.setRange(newStartIndex, newEndIndex) rangeManager.setRange(newStartIndex, newEndIndex)

@ -8,7 +8,7 @@ import { CanvasEvent } from '../CanvasEvent'
export function input(data: string, host: CanvasEvent) { export function input(data: string, host: CanvasEvent) {
const draw = host.getDraw() const draw = host.getDraw()
if (draw.isReadonly()) return if (draw.isReadonly() || draw.isDisabled()) return
const position = draw.getPosition() const position = draw.getPosition()
const cursorPosition = position.getCursorPosition() const cursorPosition = position.getCursorPosition()
if (!data || !cursorPosition) return if (!data || !cursorPosition) return
@ -34,26 +34,28 @@ export function input(data: string, host: CanvasEvent) {
const newElement: IElement = { const newElement: IElement = {
value value
} }
const nextElement = elementList[endIndex + 1] if (!copyElement.title?.disabled && !copyElement.control?.disabled) {
if ( const nextElement = elementList[endIndex + 1]
!copyElement.type || if (
copyElement.type === TEXT || !copyElement.type ||
(copyElement.type === HYPERLINK && nextElement?.type === HYPERLINK) || copyElement.type === TEXT ||
(copyElement.type === DATE && nextElement?.type === DATE) || (copyElement.type === HYPERLINK && nextElement?.type === HYPERLINK) ||
(copyElement.type === SUBSCRIPT && nextElement?.type === SUBSCRIPT) || (copyElement.type === DATE && nextElement?.type === DATE) ||
(copyElement.type === SUPERSCRIPT && nextElement?.type === SUPERSCRIPT) (copyElement.type === SUBSCRIPT && nextElement?.type === SUBSCRIPT) ||
) { (copyElement.type === SUPERSCRIPT && nextElement?.type === SUPERSCRIPT)
EDITOR_ELEMENT_COPY_ATTR.forEach(attr => { ) {
// 在分组外无需复制分组信息 EDITOR_ELEMENT_COPY_ATTR.forEach(attr => {
if (attr === 'groupIds' && !nextElement?.groupIds) return // 在分组外无需复制分组信息
const value = copyElement[attr] as never if (attr === 'groupIds' && !nextElement?.groupIds) return
if (value !== undefined) { const value = copyElement[attr] as never
newElement[attr] = value if (value !== undefined) {
} newElement[attr] = value
}) }
} })
if (isComposing) { }
newElement.underline = true if (isComposing) {
newElement.underline = true
}
} }
return newElement return newElement
}) })

@ -7,9 +7,30 @@ export function backspace(evt: KeyboardEvent, host: CanvasEvent) {
// 可输入性验证 // 可输入性验证
const rangeManager = draw.getRange() const rangeManager = draw.getRange()
if (!rangeManager.getIsCanInput()) return if (!rangeManager.getIsCanInput()) return
const { startIndex, endIndex, isCrossRowCol } = rangeManager.getRange()
const control = draw.getControl() const control = draw.getControl()
let curIndex: number | null let curIndex: number | null
if (control.getActiveControl() && control.getIsRangeCanCaptureEvent()) { if (isCrossRowCol) {
// 表格跨行列选中时清空单元格内容
const rowCol = draw.getTableParticle().getRangeRowCol()
if (!rowCol) return
let isDeleted = false
for (let r = 0; r < rowCol.length; r++) {
const row = rowCol[r]
for (let c = 0; c < row.length; c++) {
const col = row[c]
if (col.value.length > 1) {
draw.spliceElementList(col.value, 1, col.value.length - 1)
isDeleted = true
}
}
}
// 删除成功后定位
curIndex = isDeleted ? 0 : null
} else if (
control.getActiveControl() &&
control.getIsRangeCanCaptureEvent()
) {
// 光标在控件内 // 光标在控件内
curIndex = control.keydown(evt) curIndex = control.keydown(evt)
} else { } else {
@ -18,7 +39,6 @@ export function backspace(evt: KeyboardEvent, host: CanvasEvent) {
const cursorPosition = position.getCursorPosition() const cursorPosition = position.getCursorPosition()
if (!cursorPosition) return if (!cursorPosition) return
const { index } = cursorPosition const { index } = cursorPosition
const { startIndex, endIndex } = rangeManager.getRange()
const isCollapsed = rangeManager.getIsCollapsed() const isCollapsed = rangeManager.getIsCollapsed()
const elementList = draw.getElementList() const elementList = draw.getElementList()
// 判断是否允许删除 // 判断是否允许删除
@ -33,16 +53,16 @@ export function backspace(evt: KeyboardEvent, host: CanvasEvent) {
return return
} }
} }
// 清空当前行对齐方式 // 替换当前行对齐方式
const startElement = elementList[startIndex] const startElement = elementList[startIndex]
if (isCollapsed && startElement.rowFlex && startElement.value === ZERO) { if (isCollapsed && startElement.rowFlex && startElement.value === ZERO) {
const rowList = draw.getRowList() const rowFlexElementList = rangeManager.getRangeRowElementList()
const positionList = position.getPositionList() if (rowFlexElementList) {
const rowNo = positionList[startIndex].rowNo const preElement = elementList[startIndex - 1]
const rowFlexElementList = rowList[rowNo].elementList rowFlexElementList.forEach(element => {
rowFlexElementList.forEach(element => { element.rowFlex = preElement?.rowFlex
delete element.rowFlex })
}) }
} }
if (!isCollapsed) { if (!isCollapsed) {
draw.spliceElementList(elementList, startIndex + 1, endIndex - startIndex) draw.spliceElementList(elementList, startIndex + 1, endIndex - startIndex)
@ -51,8 +71,17 @@ export function backspace(evt: KeyboardEvent, host: CanvasEvent) {
} }
curIndex = isCollapsed ? index - 1 : startIndex curIndex = isCollapsed ? index - 1 : startIndex
} }
if (curIndex === null) return
draw.getGlobalEvent().setCanvasEventAbility() draw.getGlobalEvent().setCanvasEventAbility()
rangeManager.setRange(curIndex, curIndex) if (curIndex === null) {
draw.render({ curIndex }) rangeManager.setRange(startIndex, startIndex)
draw.render({
curIndex: startIndex,
isSubmitHistory: false
})
} else {
rangeManager.setRange(curIndex, curIndex)
draw.render({
curIndex
})
}
} }

@ -6,11 +6,28 @@ export function del(evt: KeyboardEvent, host: CanvasEvent) {
// 可输入性验证 // 可输入性验证
const rangeManager = draw.getRange() const rangeManager = draw.getRange()
if (!rangeManager.getIsCanInput()) return if (!rangeManager.getIsCanInput()) return
const { startIndex, endIndex } = rangeManager.getRange() const { startIndex, endIndex, isCrossRowCol } = rangeManager.getRange()
const elementList = draw.getElementList() const elementList = draw.getElementList()
const control = draw.getControl() const control = draw.getControl()
let curIndex: number | null let curIndex: number | null
if (control.getActiveControl() && control.getIsRangeWithinControl()) { if (isCrossRowCol) {
// 表格跨行列选中时清空单元格内容
const rowCol = draw.getTableParticle().getRangeRowCol()
if (!rowCol) return
let isDeleted = false
for (let r = 0; r < rowCol.length; r++) {
const row = rowCol[r]
for (let c = 0; c < row.length; c++) {
const col = row[c]
if (col.value.length > 1) {
draw.spliceElementList(col.value, 1, col.value.length - 1)
isDeleted = true
}
}
}
// 删除成功后定位
curIndex = isDeleted ? 0 : null
} else if (control.getActiveControl() && control.getIsRangeWithinControl()) {
// 光标在控件内 // 光标在控件内
curIndex = control.keydown(evt) curIndex = control.keydown(evt)
} else if (elementList[endIndex + 1]?.controlId) { } else if (elementList[endIndex + 1]?.controlId) {
@ -42,8 +59,17 @@ export function del(evt: KeyboardEvent, host: CanvasEvent) {
curIndex = isCollapsed ? index : startIndex curIndex = isCollapsed ? index : startIndex
} }
} }
if (curIndex === null) return
draw.getGlobalEvent().setCanvasEventAbility() draw.getGlobalEvent().setCanvasEventAbility()
rangeManager.setRange(curIndex, curIndex) if (curIndex === null) {
draw.render({ curIndex }) rangeManager.setRange(startIndex, startIndex)
draw.render({
curIndex: startIndex,
isSubmitHistory: false
})
} else {
rangeManager.setRange(curIndex, curIndex)
draw.render({
curIndex
})
}
} }

@ -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()
@ -48,6 +88,8 @@ export function mousedown(evt: MouseEvent, host: CanvasEvent) {
draw.setPageNo(Number(pageIndex)) draw.setPageNo(Number(pageIndex))
} }
host.isAllowSelection = true host.isAllowSelection = true
// 缓存旧上下文信息
const oldPositionContext = deepClone(position.getPositionContext())
const positionResult = position.adjustPositionContext({ const positionResult = position.adjustPositionContext({
x: evt.offsetX, x: evt.offsetX,
y: evt.offsetY y: evt.offsetY
@ -79,43 +121,46 @@ export function mousedown(evt: MouseEvent, host: CanvasEvent) {
const isDirectHitCheckbox = !!(isDirectHit && isCheckbox) const isDirectHitCheckbox = !!(isDirectHit && isCheckbox)
const isDirectHitRadio = !!(isDirectHit && isRadio) const isDirectHitRadio = !!(isDirectHit && isRadio)
if (~index) { if (~index) {
rangeManager.setRange(curIndex, curIndex) let startIndex = curIndex
position.setCursorPosition(positionList[curIndex]) let endIndex = curIndex
// 复选框 // shift激活时进行选区处理
const isSetCheckbox = isDirectHitCheckbox && !isReadonly if (evt.shiftKey) {
// 单选框 const { startIndex: oldStartIndex } = rangeManager.getRange()
const isSetRadio = isDirectHitRadio && !isReadonly if (~oldStartIndex) {
if (isSetCheckbox) { const newPositionContext = position.getPositionContext()
const { checkbox, control } = curElement if (newPositionContext.tdId === oldPositionContext.tdId) {
// 复选框不在控件内独立控制 if (curIndex > oldStartIndex) {
if (!control) { startIndex = oldStartIndex
draw.getCheckboxParticle().setSelect(curElement) } else {
} else { endIndex = oldStartIndex
const codes = 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)
}
} }
} else if (isSetRadio) { }
const { control, radio } = curElement rangeManager.setRange(startIndex, endIndex)
// 单选框不在控件内独立控制 position.setCursorPosition(positionList[curIndex])
if (!control) { // 复选框
draw.getRadioParticle().setSelect(curElement) if (isDirectHitCheckbox && !isReadonly) {
} else { hitCheckbox(curElement, draw)
const codes = radio?.code ? [radio.code] : [] } else if (isDirectHitRadio && !isReadonly) {
const activeControl = draw.getControl().getActiveControl() hitRadio(curElement, draw)
if (activeControl instanceof RadioControl) { } else if (
activeControl.setSelect(codes) curElement.controlComponent === ControlComponent.VALUE &&
(curElement.control?.type === ControlType.CHECKBOX ||
curElement.control?.type === ControlType.RADIO)
) {
// 向左查找
let preIndex = curIndex
while (preIndex > 0) {
const preElement = elementList[preIndex]
if (preElement.controlComponent === ControlComponent.CHECKBOX) {
hitCheckbox(preElement, draw)
break
} else if (preElement.controlComponent === ControlComponent.RADIO) {
hitRadio(preElement, draw)
break
} }
preIndex--
} }
} else { } else {
draw.render({ draw.render({

@ -48,7 +48,10 @@ export function mouseup(evt: MouseEvent, host: CanvasEvent) {
// 判断是否允许拖放 // 判断是否允许拖放
if (host.isAllowDrop) { if (host.isAllowDrop) {
const draw = host.getDraw() const draw = host.getDraw()
if (draw.isReadonly()) return if (draw.isReadonly() || draw.isDisabled()) {
host.mousedown(evt)
return
}
const position = draw.getPosition() const position = draw.getPosition()
const positionList = position.getPositionList() const positionList = position.getPositionList()
const positionContext = position.getPositionContext() const positionContext = position.getPositionContext()

@ -17,8 +17,7 @@ import { IOverrideResult } from '../../override/Override'
export function pasteElement(host: CanvasEvent, elementList: IElement[]) { export function pasteElement(host: CanvasEvent, elementList: IElement[]) {
const draw = host.getDraw() const draw = host.getDraw()
const isReadonly = draw.isReadonly() if (draw.isReadonly() || draw.isDisabled()) return
if (isReadonly) return
const rangeManager = draw.getRange() const rangeManager = draw.getRange()
const { startIndex } = rangeManager.getRange() const { startIndex } = rangeManager.getRange()
const originalElementList = draw.getElementList() const originalElementList = draw.getElementList()
@ -59,8 +58,7 @@ export function pasteElement(host: CanvasEvent, elementList: IElement[]) {
export function pasteHTML(host: CanvasEvent, htmlText: string) { export function pasteHTML(host: CanvasEvent, htmlText: string) {
const draw = host.getDraw() const draw = host.getDraw()
const isReadonly = draw.isReadonly() if (draw.isReadonly() || draw.isDisabled()) return
if (isReadonly) return
const elementList = getElementListByHTML(htmlText, { const elementList = getElementListByHTML(htmlText, {
innerWidth: draw.getOriginalInnerWidth() innerWidth: draw.getOriginalInnerWidth()
}) })
@ -69,8 +67,7 @@ export function pasteHTML(host: CanvasEvent, htmlText: string) {
export function pasteImage(host: CanvasEvent, file: File | Blob) { export function pasteImage(host: CanvasEvent, file: File | Blob) {
const draw = host.getDraw() const draw = host.getDraw()
const isReadonly = draw.isReadonly() if (draw.isReadonly() || draw.isDisabled()) return
if (isReadonly) return
const rangeManager = draw.getRange() const rangeManager = draw.getRange()
const { startIndex } = rangeManager.getRange() const { startIndex } = rangeManager.getRange()
const elementList = draw.getElementList() const elementList = draw.getElementList()
@ -99,8 +96,7 @@ export function pasteImage(host: CanvasEvent, file: File | Blob) {
export function pasteByEvent(host: CanvasEvent, evt: ClipboardEvent) { export function pasteByEvent(host: CanvasEvent, evt: ClipboardEvent) {
const draw = host.getDraw() const draw = host.getDraw()
const isReadonly = draw.isReadonly() if (draw.isReadonly() || draw.isDisabled()) return
if (isReadonly) return
const clipboardData = evt.clipboardData const clipboardData = evt.clipboardData
if (!clipboardData) return if (!clipboardData) return
// 自定义粘贴事件 // 自定义粘贴事件
@ -157,8 +153,7 @@ export function pasteByEvent(host: CanvasEvent, evt: ClipboardEvent) {
export async function pasteByApi(host: CanvasEvent, options?: IPasteOption) { export async function pasteByApi(host: CanvasEvent, options?: IPasteOption) {
const draw = host.getDraw() const draw = host.getDraw()
const isReadonly = draw.isReadonly() if (draw.isReadonly() || draw.isDisabled()) return
if (isReadonly) return
// 自定义粘贴事件 // 自定义粘贴事件
const { paste } = draw.getOverride() const { paste } = draw.getOverride()
if (paste) { if (paste) {

@ -13,34 +13,65 @@ export class SelectionObserver {
right: number right: number
] = [70, 40, 10, 20] ] = [70, 40, 10, 20]
private selectionContainer: Element | Document
private rangeManager: RangeManager private rangeManager: RangeManager
private requestAnimationFrameId: number | null private requestAnimationFrameId: number | null
private isMousedown: boolean private isMousedown: boolean
private isMoving: boolean private isMoving: boolean
private clientWidth: number
private clientHeight: number
private containerRect: DOMRect | null
constructor(draw: Draw) { constructor(draw: Draw) {
this.rangeManager = draw.getRange()
// 优先使用配置的滚动容器dom
const { scrollContainerSelector } = draw.getOptions()
this.selectionContainer = scrollContainerSelector
? document.querySelector(scrollContainerSelector) || document
: document
this.requestAnimationFrameId = null this.requestAnimationFrameId = null
this.isMousedown = false this.isMousedown = false
this.isMoving = false this.isMoving = false
this.rangeManager = draw.getRange() // 缓存尺寸
this.clientWidth = 0
this.clientHeight = 0
this.containerRect = null
// 添加监听
this._addEvent() this._addEvent()
} }
private _addEvent() { private _addEvent() {
document.addEventListener('mousedown', this._mousedown) const container = <Document>this.selectionContainer
document.addEventListener('mousemove', this._mousemove) container.addEventListener('mousedown', this._mousedown)
document.addEventListener('mouseup', this._mouseup) container.addEventListener('mousemove', this._mousemove)
container.addEventListener('mouseup', this._mouseup)
document.addEventListener('mouseleave', this._mouseup)
} }
public removeEvent() { public removeEvent() {
document.removeEventListener('mousedown', this._mousedown) const container = <Document>this.selectionContainer
document.removeEventListener('mousemove', this._mousemove) container.removeEventListener('mousedown', this._mousedown)
document.removeEventListener('mouseup', this._mouseup) container.removeEventListener('mousemove', this._mousemove)
container.removeEventListener('mouseup', this._mouseup)
document.removeEventListener('mouseleave', this._mouseup)
} }
private _mousedown = () => { private _mousedown = () => {
this.isMousedown = true this.isMousedown = true
// 更新容器宽高
this.clientWidth =
this.selectionContainer instanceof Document
? document.documentElement.clientWidth
: this.selectionContainer.clientWidth
this.clientHeight =
this.selectionContainer instanceof Document
? document.documentElement.clientHeight
: this.selectionContainer.clientHeight
// 更新容器位置信息
if (!(this.selectionContainer instanceof Document)) {
const rect = this.selectionContainer.getBoundingClientRect()
this.containerRect = rect
}
} }
private _mouseup = () => { private _mouseup = () => {
@ -50,16 +81,18 @@ export class SelectionObserver {
private _mousemove = (evt: MouseEvent) => { private _mousemove = (evt: MouseEvent) => {
if (!this.isMousedown || this.rangeManager.getIsCollapsed()) return if (!this.isMousedown || this.rangeManager.getIsCollapsed()) return
const { x, y } = evt let { x, y } = evt
const clientWidth = document.documentElement.clientWidth if (this.containerRect) {
const clientHeight = document.documentElement.clientHeight x = x - this.containerRect.x
y = y - this.containerRect.y
}
if (y < this.thresholdPoints[0]) { if (y < this.thresholdPoints[0]) {
this._startMove(MoveDirection.UP) this._startMove(MoveDirection.UP)
} else if (clientHeight - y <= this.thresholdPoints[1]) { } else if (this.clientHeight - y <= this.thresholdPoints[1]) {
this._startMove(MoveDirection.DOWN) this._startMove(MoveDirection.DOWN)
} else if (x < this.thresholdPoints[2]) { } else if (x < this.thresholdPoints[2]) {
this._startMove(MoveDirection.LEFT) this._startMove(MoveDirection.LEFT)
} else if (clientWidth - x < this.thresholdPoints[3]) { } else if (this.clientWidth - x < this.thresholdPoints[3]) {
this._startMove(MoveDirection.RIGHT) this._startMove(MoveDirection.RIGHT)
} else { } else {
this._stopMove() this._stopMove()
@ -67,16 +100,27 @@ export class SelectionObserver {
} }
private _move(direction: MoveDirection) { private _move(direction: MoveDirection) {
const x = window.scrollX // Document使用window
const y = window.scrollY const container =
this.selectionContainer instanceof Document
? window
: this.selectionContainer
const x =
this.selectionContainer instanceof Document
? window.scrollX
: (<Element>container).scrollLeft
const y =
this.selectionContainer instanceof Document
? window.scrollY
: (<Element>container).scrollTop
if (direction === MoveDirection.DOWN) { if (direction === MoveDirection.DOWN) {
window.scrollTo(x, y + this.step) container.scrollTo(x, y + this.step)
} else if (direction === MoveDirection.UP) { } else if (direction === MoveDirection.UP) {
window.scrollTo(x, y - this.step) container.scrollTo(x, y - this.step)
} else if (direction === MoveDirection.LEFT) { } else if (direction === MoveDirection.LEFT) {
window.scrollTo(x - this.step, y) container.scrollTo(x - this.step, y)
} else { } else {
window.scrollTo(x + this.step, y) container.scrollTo(x + this.step, y)
} }
this.requestAnimationFrameId = window.requestAnimationFrame( this.requestAnimationFrameId = window.requestAnimationFrame(
this._move.bind(this, direction) this._move.bind(this, direction)

@ -643,6 +643,7 @@ export class Position {
payload: IGetFloatPositionByXYPayload payload: IGetFloatPositionByXYPayload
): ICurrentPosition | void { ): ICurrentPosition | void {
const { x, y } = payload const { x, y } = payload
const currentPageNo = payload.pageNo ?? this.draw.getPageNo()
const currentZone = this.draw.getZone().getZone() const currentZone = this.draw.getZone().getZone()
for (let f = 0; f < this.floatPositionList.length; f++) { for (let f = 0; f < this.floatPositionList.length; f++) {
const { const {
@ -653,9 +654,11 @@ export class Position {
trIndex, trIndex,
tdIndex, tdIndex,
tdValueIndex, tdValueIndex,
zone: floatElementZone zone: floatElementZone,
pageNo
} = this.floatPositionList[f] } = this.floatPositionList[f]
if ( if (
currentPageNo === pageNo &&
element.type === ElementType.IMAGE && element.type === ElementType.IMAGE &&
element.imgDisplay === payload.imgDisplay && element.imgDisplay === payload.imgDisplay &&
(!floatElementZone || floatElementZone === currentZone) (!floatElementZone || floatElementZone === currentZone)

@ -171,8 +171,9 @@ export class RangeManager {
} }
start-- start--
} }
const isCollapsed = startIndex === endIndex
// 中间选择 // 中间选择
if (startIndex !== endIndex) { if (!isCollapsed) {
let middle = startIndex + 1 let middle = startIndex + 1
while (middle < endIndex) { while (middle < endIndex) {
const { pageNo, rowNo } = positionList[middle] const { pageNo, rowNo } = positionList[middle]
@ -189,6 +190,10 @@ export class RangeManager {
} }
// 向下查找 // 向下查找
let end = endIndex let end = endIndex
// 闭合选区&&首字符为换行符时继续向下查找
if (isCollapsed && elementList[startIndex].value === ZERO) {
end += 1
}
while (end < positionList.length) { while (end < positionList.length) {
const element = elementList[end] const element = elementList[end]
const nextElement = elementList[end + 1] const nextElement = elementList[end + 1]
@ -425,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

@ -5,5 +5,6 @@ export const defaultBackground: Readonly<Required<IBackgroundOption>> = {
color: '#FFFFFF', color: '#FFFFFF',
image: '', image: '',
size: BackgroundSize.COVER, size: BackgroundSize.COVER,
repeat: BackgroundRepeat.NO_REPEAT repeat: BackgroundRepeat.NO_REPEAT,
applyPageNumbers: []
} }

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

@ -120,8 +120,7 @@ export const CONTROL_STYLE_ATTR: Array<keyof IControlStyle> = [
export const EDITOR_ELEMENT_CONTEXT_ATTR: Array<keyof IElement> = [ export const EDITOR_ELEMENT_CONTEXT_ATTR: Array<keyof IElement> = [
...TABLE_CONTEXT_ATTR, ...TABLE_CONTEXT_ATTR,
...TITLE_CONTEXT_ATTR, ...TITLE_CONTEXT_ATTR,
...LIST_CONTEXT_ATTR, ...LIST_CONTEXT_ATTR
...EDITOR_ROW_ATTR
] ]
export const TEXTLIKE_ELEMENT_TYPE: ElementType[] = [ export const TEXTLIKE_ELEMENT_TYPE: ElementType[] = [

@ -0,0 +1,11 @@
import { ILineNumberOption } from '../../interface/LineNumber'
import { LineNumberType } from '../enum/LineNumber'
export const defaultLineNumberOption: Readonly<Required<ILineNumberOption>> = {
size: 12,
font: 'Microsoft YaHei',
color: '#000000',
disabled: true,
right: 20,
type: LineNumberType.CONTINUITY
}

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

@ -15,3 +15,8 @@ export enum ImageDisplay {
FLOAT_TOP = 'float-top', FLOAT_TOP = 'float-top',
FLOAT_BOTTOM = 'float-bottom' FLOAT_BOTTOM = 'float-bottom'
} }
export enum LocationPosition {
BEFORE = 'before',
AFTER = 'after'
}

@ -0,0 +1,4 @@
export enum LineNumberType {
PAGE = 'page',
CONTINUITY = 'continuity'
}

@ -6,7 +6,7 @@ import { Command } from './core/command/Command'
import { CommandAdapt } from './core/command/CommandAdapt' import { CommandAdapt } from './core/command/CommandAdapt'
import { Listener } from './core/listener/Listener' import { Listener } from './core/listener/Listener'
import { RowFlex } from './dataset/enum/Row' import { RowFlex } from './dataset/enum/Row'
import { ImageDisplay } from './dataset/enum/Common' import { ImageDisplay, LocationPosition } from './dataset/enum/Common'
import { ElementType } from './dataset/enum/Element' import { ElementType } from './dataset/enum/Element'
import { formatElementList } from './utils/element' import { formatElementList } from './utils/element'
import { Register } from './core/register/Register' import { Register } from './core/register/Register'
@ -52,6 +52,7 @@ import { deepClone, splitText } from './utils'
import { BackgroundRepeat, BackgroundSize } from './dataset/enum/Background' import { BackgroundRepeat, BackgroundSize } from './dataset/enum/Background'
import { TextDecorationStyle } from './dataset/enum/Text' import { TextDecorationStyle } from './dataset/enum/Text'
import { mergeOption } from './utils/option' import { mergeOption } from './utils/option'
import { LineNumberType } from './dataset/enum/LineNumber'
export default class Editor { export default class Editor {
public command: Command public command: Command
@ -88,7 +89,8 @@ export default class Editor {
] ]
pageComponentData.forEach(elementList => { pageComponentData.forEach(elementList => {
formatElementList(elementList, { formatElementList(elementList, {
editorOptions editorOptions,
isForceCompensation: true
}) })
}) })
// 监听 // 监听
@ -169,7 +171,9 @@ export {
ControlIndentation, ControlIndentation,
BackgroundRepeat, BackgroundRepeat,
BackgroundSize, BackgroundSize,
TextDecorationStyle TextDecorationStyle,
LineNumberType,
LocationPosition
} }
// 对外类型 // 对外类型

@ -5,4 +5,5 @@ export interface IBackgroundOption {
image?: string image?: string
size?: BackgroundSize size?: BackgroundSize
repeat?: BackgroundRepeat repeat?: BackgroundRepeat
applyPageNumbers?: number[]
} }

@ -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,3 +1,4 @@
import { LocationPosition } from '../dataset/enum/Common'
import { ControlType, ControlIndentation } from '../dataset/enum/Control' import { ControlType, ControlIndentation } from '../dataset/enum/Control'
import { EditorZone } from '../dataset/enum/Editor' import { EditorZone } from '../dataset/enum/Editor'
import { MoveDirection } from '../dataset/enum/Observer' import { MoveDirection } from '../dataset/enum/Observer'
@ -40,7 +41,8 @@ export interface IControlHighlightRule {
export interface IControlHighlight { export interface IControlHighlight {
ruleList: IControlHighlightRule[] ruleList: IControlHighlightRule[]
conceptId: string id?: string
conceptId?: string
} }
export interface IControlRule { export interface IControlRule {
@ -124,7 +126,8 @@ export interface IControlRuleOption {
} }
export interface IGetControlValueOption { export interface IGetControlValueOption {
conceptId: string id?: string
conceptId?: string
} }
export type IGetControlValueResult = (Omit<IControl, 'value'> & { export type IGetControlValueResult = (Omit<IControl, 'value'> & {
@ -134,19 +137,22 @@ export type IGetControlValueResult = (Omit<IControl, 'value'> & {
})[] })[]
export interface ISetControlValueOption { export interface ISetControlValueOption {
conceptId: string id?: string
conceptId?: string
value: string value: string
} }
export interface ISetControlExtensionOption { export interface ISetControlExtensionOption {
conceptId: string id?: string
conceptId?: string
extension: unknown extension: unknown
} }
export type ISetControlHighlightOption = IControlHighlight[] export type ISetControlHighlightOption = IControlHighlight[]
export type ISetControlProperties = { export type ISetControlProperties = {
conceptId: string id?: string
conceptId?: string
properties: Partial<Omit<IControl, 'value'>> properties: Partial<Omit<IControl, 'value'>>
} }
@ -163,3 +169,7 @@ export interface INextControlContext {
export interface IInitNextControlOption { export interface IInitNextControlOption {
direction?: MoveDirection direction?: MoveDirection
} }
export interface ILocationControlOption {
position: LocationPosition
}

@ -1,4 +1,4 @@
import { IElement } from '..' import { IElement, LocationPosition } from '..'
import { import {
EditorMode, EditorMode,
PageMode, PageMode,
@ -24,6 +24,8 @@ import { IWatermark } from './Watermark'
import { IZoneOption } from './Zone' 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 { IPageBorderOption } from './PageBorder'
export interface IEditorData { export interface IEditorData {
header?: IElement[] header?: IElement[]
@ -89,6 +91,8 @@ export interface IEditorOption {
background?: IBackgroundOption background?: IBackgroundOption
lineBreak?: ILineBreakOption lineBreak?: ILineBreakOption
separator?: ISeparatorOption separator?: ISeparatorOption
lineNumber?: ILineNumberOption
pageBorder?: IPageBorderOption
} }
export interface IEditorResult { export interface IEditorResult {
@ -117,3 +121,11 @@ export type IUpdateOption = Omit<
| 'historyMaxRecordCount' | 'historyMaxRecordCount'
| 'scrollContainerSelector' | 'scrollContainerSelector'
> >
export interface ISetValueOption {
isSetCursor?: boolean
}
export interface IFocusOption {
position?: LocationPosition
}

@ -177,5 +177,5 @@ export interface IElementFillRect {
export interface IUpdateElementByIdOption { export interface IUpdateElementByIdOption {
id: string id: string
properties: Omit<IElement, 'id'> properties: Omit<Partial<IElement>, 'id'>
} }

@ -0,0 +1,10 @@
import { LineNumberType } from '../dataset/enum/LineNumber'
export interface ILineNumberOption {
size?: number
font?: string
color?: string
disabled?: boolean
right?: number
type?: LineNumberType
}

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

@ -19,4 +19,5 @@ export interface IRow {
offsetX?: number offsetX?: number
elementList: IRowElement[] elementList: IRowElement[]
isWidthNotEnough?: boolean isWidthNotEnough?: boolean
rowIndex: number
} }

@ -14,6 +14,7 @@ export type ITitleOption = ITitleSizeOption & {}
export interface ITitleRule { export interface ITitleRule {
deletable?: boolean deletable?: boolean
disabled?: boolean
} }
export type ITitle = ITitleRule & { export type ITitle = ITitleRule & {

@ -4,6 +4,7 @@ import {
deepCloneOmitKeys, deepCloneOmitKeys,
getUUID, getUUID,
isArrayEqual, isArrayEqual,
omitObject,
pickObject, pickObject,
splitText splitText
} from '.' } from '.'
@ -29,7 +30,8 @@ import {
INLINE_NODE_NAME, INLINE_NODE_NAME,
TABLE_CONTEXT_ATTR, TABLE_CONTEXT_ATTR,
TABLE_TD_ZIP_ATTR, TABLE_TD_ZIP_ATTR,
TEXTLIKE_ELEMENT_TYPE TEXTLIKE_ELEMENT_TYPE,
TITLE_CONTEXT_ATTR
} from '../dataset/constant/Element' } from '../dataset/constant/Element'
import { import {
listStyleCSSMapping, listStyleCSSMapping,
@ -63,7 +65,8 @@ export function unzipElementList(elementList: IElement[]): IElement[] {
} }
interface IFormatElementListOption { interface IFormatElementListOption {
isHandleFirstElement?: boolean isHandleFirstElement?: boolean // 根据上下文确定首字符处理逻辑(处理首字符补偿)
isForceCompensation?: boolean // 强制补偿字符
editorOptions: DeepRequired<IEditorOption> editorOptions: DeepRequired<IEditorOption>
} }
@ -71,17 +74,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
@ -98,7 +103,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) {
@ -132,7 +138,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) {
@ -168,7 +175,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]
@ -234,7 +242,10 @@ export function formatElementList(
// 移除父节点 // 移除父节点
elementList.splice(i, 1) elementList.splice(i, 1)
// 控件上下文提取(压缩后的控件上下文无法提取) // 控件上下文提取(压缩后的控件上下文无法提取)
const controlContext = pickObject(el, EDITOR_ELEMENT_CONTEXT_ATTR) const controlContext = pickObject(el, [
...EDITOR_ELEMENT_CONTEXT_ATTR,
...EDITOR_ROW_ATTR
])
// 控件设置的默认样式(以前缀为基准) // 控件设置的默认样式(以前缀为基准)
const controlDefaultStyle = pickObject( const controlDefaultStyle = pickObject(
<IElement>(<unknown>el.control), <IElement>(<unknown>el.control),
@ -285,6 +296,7 @@ export function formatElementList(
// checkbox组件 // checkbox组件
elementList.splice(i, 0, { elementList.splice(i, 0, {
...controlContext, ...controlContext,
...controlDefaultStyle,
controlId, controlId,
value: '', value: '',
type: el.type, type: el.type,
@ -332,6 +344,7 @@ export function formatElementList(
// radio组件 // radio组件
elementList.splice(i, 0, { elementList.splice(i, 0, {
...controlContext, ...controlContext,
...controlDefaultStyle,
controlId, controlId,
value: '', value: '',
type: el.type, type: el.type,
@ -378,7 +391,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]
@ -444,7 +458,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) {
@ -816,8 +830,12 @@ export function formatElementContext(
anchorIndex: number, anchorIndex: number,
options?: IFormatElementContextOption options?: IFormatElementContextOption
) { ) {
const copyElement = getAnchorElement(sourceElementList, anchorIndex) let copyElement = getAnchorElement(sourceElementList, anchorIndex)
if (!copyElement) return if (!copyElement) return
// 标题元素禁用时不复制标题属性
if (copyElement.title?.disabled) {
copyElement = omitObject(copyElement, TITLE_CONTEXT_ATTR)
}
const { isBreakWhenWrap = false } = options || {} const { isBreakWhenWrap = false } = options || {}
// 是否已经换行 // 是否已经换行
let isBreakWarped = false let isBreakWarped = false
@ -837,9 +855,9 @@ export function formatElementContext(
(!copyElement.listId && targetElement.type === ElementType.LIST) (!copyElement.listId && targetElement.type === ElementType.LIST)
) { ) {
const cloneAttr = [...TABLE_CONTEXT_ATTR, ...EDITOR_ROW_ATTR] const cloneAttr = [...TABLE_CONTEXT_ATTR, ...EDITOR_ROW_ATTR]
cloneProperty<IElement>(cloneAttr, copyElement, targetElement) cloneProperty<IElement>(cloneAttr, copyElement!, targetElement)
targetElement.valueList?.forEach(valueItem => { targetElement.valueList?.forEach(valueItem => {
cloneProperty<IElement>(cloneAttr, copyElement, valueItem) cloneProperty<IElement>(cloneAttr, copyElement!, valueItem)
}) })
continue continue
} }
@ -850,11 +868,12 @@ export function formatElementContext(
anchorIndex anchorIndex
) )
} }
cloneProperty<IElement>( // 非块类元素,需处理行属性
EDITOR_ELEMENT_CONTEXT_ATTR, const cloneAttr = [...EDITOR_ELEMENT_CONTEXT_ATTR]
copyElement, if (!getIsBlockElement(targetElement)) {
targetElement cloneAttr.push(...EDITOR_ROW_ATTR)
) }
cloneProperty<IElement>(cloneAttr, copyElement, targetElement)
} }
} }

@ -16,6 +16,7 @@ import { defaultTableOption } from '../dataset/constant/Table'
import { defaultTitleOption } from '../dataset/constant/Title' import { defaultTitleOption } from '../dataset/constant/Title'
import { defaultWatermarkOption } from '../dataset/constant/Watermark' import { defaultWatermarkOption } from '../dataset/constant/Watermark'
import { defaultZoneOption } from '../dataset/constant/Zone' import { defaultZoneOption } from '../dataset/constant/Zone'
import { defaultLineNumberOption } from '../dataset/constant/LineNumber'
import { IBackgroundOption } from '../interface/Background' import { IBackgroundOption } from '../interface/Background'
import { ICheckboxOption } from '../interface/Checkbox' import { ICheckboxOption } from '../interface/Checkbox'
import { DeepRequired } from '../interface/Common' import { DeepRequired } from '../interface/Common'
@ -35,6 +36,9 @@ import { ITableOption } from '../interface/table/Table'
import { ITitleOption } from '../interface/Title' 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 { IPageBorderOption } from '../interface/PageBorder'
import { defaultPageBorderOption } from '../dataset/constant/PageBorder'
import { import {
EditorMode, EditorMode,
PageMode, PageMode,
@ -114,6 +118,14 @@ export function mergeOption(
...defaultSeparatorOption, ...defaultSeparatorOption,
...options.separator ...options.separator
} }
const lineNumberOptions: Required<ILineNumberOption> = {
...defaultLineNumberOption,
...options.lineNumber
}
const pageBorderOptions: Required<IPageBorderOption> = {
...defaultPageBorderOption,
...options.pageBorder
}
return { return {
mode: EditorMode.EDIT, mode: EditorMode.EDIT,
@ -173,6 +185,8 @@ export function mergeOption(
zone: zoneOptions, zone: zoneOptions,
background: backgroundOptions, background: backgroundOptions,
lineBreak: lineBreakOptions, lineBreak: lineBreakOptions,
separator: separatorOptions separator: separatorOptions,
lineNumber: lineNumberOptions,
pageBorder: pageBorderOptions
} }
} }

@ -1,5 +1,35 @@
import { PaperDirection } from '../dataset/enum/Editor' import { PaperDirection } from '../dataset/enum/Editor'
function convertPxToPaperSize(width: number, height: number) {
if (width === 1125 && height === 1593) {
return {
size: 'a3',
width: '297mm',
height: '420mm'
}
}
if (width === 794 && height === 1123) {
return {
size: 'a4',
width: '210mm',
height: '297mm'
}
}
if (width === 565 && height === 796) {
return {
size: 'a5',
width: '148mm',
height: '210mm'
}
}
// 其他默认不转换
return {
size: '',
width: `${width}px`,
height: `${height}px`
}
}
export interface IPrintImageBase64Option { export interface IPrintImageBase64Option {
width: number width: number
height: number height: number
@ -24,10 +54,17 @@ export function printImageBase64(
const doc = contentWindow.document const doc = contentWindow.document
doc.open() doc.open()
const container = document.createElement('div') const container = document.createElement('div')
const paperSize = convertPxToPaperSize(width, height)
base64List.forEach(base64 => { base64List.forEach(base64 => {
const image = document.createElement('img') const image = document.createElement('img')
image.style.width = `${width}px` image.style.width =
image.style.height = `${height}px` direction === PaperDirection.HORIZONTAL
? paperSize.height
: paperSize.width
image.style.height =
direction === PaperDirection.HORIZONTAL
? paperSize.width
: paperSize.height
image.src = base64 image.src = base64
container.append(image) container.append(image)
}) })
@ -39,7 +76,9 @@ export function printImageBase64(
} }
@page { @page {
margin: 0; margin: 0;
size: ${direction === PaperDirection.HORIZONTAL ? `landscape` : `portrait`}; size: ${paperSize.size} ${
direction === PaperDirection.HORIZONTAL ? `landscape` : `portrait`
};
}` }`
style.append(document.createTextNode(stylesheet)) style.append(document.createTextNode(stylesheet))
setTimeout(() => { setTimeout(() => {

Loading…
Cancel
Save