diff --git a/docs/en/guide/option.md b/docs/en/guide/option.md index 845d617..eedf745 100644 --- a/docs/en/guide/option.md +++ b/docs/en/guide/option.md @@ -69,6 +69,7 @@ interface IEditorOption { pageBreak?: IPageBreak // PageBreak option。{font?:string; fontSize?:number; lineDash?:number[];} zone?: IZoneOption // Zone option。{tipDisabled?:boolean;} background?: IBackgroundOption // Background option. {color?:string; image?:string; size?:BackgroundSize; repeat?:BackgroundRepeat;}。default: {color: '#FFFFFF'} + lineBreak?: ILineBreakOption // LineBreak option. {disabled?:boolean; color?:string; lineWidth?:number;} } ``` diff --git a/docs/guide/option.md b/docs/guide/option.md index 449075d..c81a1fe 100644 --- a/docs/guide/option.md +++ b/docs/guide/option.md @@ -69,6 +69,7 @@ interface IEditorOption { pageBreak?: IPageBreak // 分页符配置。{font?:string; fontSize?:number; lineDash?:number[];} zone?: IZoneOption // 编辑器区域配置。{tipDisabled?:boolean;} background?: IBackgroundOption // 背景配置。{color?:string; image?:string; size?:BackgroundSize; repeat?:BackgroundRepeat;}。默认:{color: '#FFFFFF'} + lineBreak?: ILineBreakOption // 换行符配置。{disabled?:boolean; color?:string; lineWidth?:number;} } ``` diff --git a/src/editor/core/draw/Draw.ts b/src/editor/core/draw/Draw.ts index 93eabf1..68f34e1 100644 --- a/src/editor/core/draw/Draw.ts +++ b/src/editor/core/draw/Draw.ts @@ -93,6 +93,7 @@ import { Group } from './interactive/Group' import { Override } from '../override/Override' import { ImageDisplay } from '../../dataset/enum/Common' import { PUNCTUATION_REG } from '../../dataset/constant/Regular' +import { LineBreakParticle } from './particle/LineBreakParticle' export class Draw { private container: HTMLDivElement @@ -143,6 +144,7 @@ export class Draw { private checkboxParticle: CheckboxParticle private blockParticle: BlockParticle private listParticle: ListParticle + private lineBreakParticle: LineBreakParticle private control: Control private workerManager: WorkerManager private scrollObserver: ScrollObserver @@ -216,6 +218,7 @@ export class Draw { this.checkboxParticle = new CheckboxParticle(this) this.blockParticle = new BlockParticle(this) this.listParticle = new ListParticle(this) + this.lineBreakParticle = new LineBreakParticle(this) this.control = new Control(this) this.scrollObserver = new ScrollObserver(this) @@ -510,6 +513,10 @@ export class Draw { return this.range } + public getLineBreakParticle(): LineBreakParticle { + return this.lineBreakParticle + } + public getHeaderElementList(): IElement[] { return this.header.getElementList() } @@ -1531,7 +1538,8 @@ export class Draw { } } listId = element.listId - if ( + // 是否强制换行 + const isForceBreak = element.type === ElementType.SEPARATOR || element.type === ElementType.TABLE || preElement?.type === ElementType.TABLE || @@ -1539,10 +1547,13 @@ export class Draw { element.type === ElementType.BLOCK || preElement?.imgDisplay === ImageDisplay.INLINE || element.imgDisplay === ImageDisplay.INLINE || - curRowWidth > availableWidth || - (i !== 0 && element.value === ZERO) || - preElement?.listId !== element.listId - ) { + preElement?.listId !== element.listId || + (i !== 0 && element.value === ZERO) + // 是否宽度不足导致换行 + const isWidthNotEnough = curRowWidth > availableWidth + if (isForceBreak || isWidthNotEnough) { + // 换行原因:宽度不足 + curRow.isWidthNotEnough = isWidthNotEnough && !isForceBreak // 减小行元素前第一行空行行高 if ( curRow.startIndex === 0 && @@ -1702,10 +1713,17 @@ export class Draw { // 优先绘制高亮元素 this._drawHighlight(ctx, payload) // 绘制元素、下划线、删除线、选区 - const { rowList, pageNo, elementList, positionList, startIndex, zone } = - payload + const { scale, tdPadding, group, lineBreak } = this.options + const { + rowList, + pageNo, + elementList, + positionList, + startIndex, + zone, + isDrawLineBreak = !lineBreak.disabled + } = payload const isPrintMode = this.mode === EditorMode.PRINT - const { scale, tdPadding, group } = this.options const { isCrossRowCol, tableId } = this.range.getRange() let index = startIndex for (let i = 0; i < rowList.length; i++) { @@ -1807,6 +1825,14 @@ export class Draw { this.textParticle.complete() } } + // 换行符绘制 + if ( + isDrawLineBreak && + !curRow.isWidthNotEnough && + j === curRow.elementList.length - 1 + ) { + this.lineBreakParticle.render(ctx, element, x, y + curRow.height / 2) + } // 边框绘制(目前仅支持控件) if (element.control?.border) { // 不同控件边框立刻绘制 @@ -1961,7 +1987,8 @@ export class Draw { pageNo, startIndex: 0, innerWidth: (td.width! - tdPaddingWidth) * scale, - zone + zone, + isDrawLineBreak }) } } diff --git a/src/editor/core/draw/frame/Placeholder.ts b/src/editor/core/draw/frame/Placeholder.ts index e90fffe..a6e4eb1 100644 --- a/src/editor/core/draw/frame/Placeholder.ts +++ b/src/editor/core/draw/frame/Placeholder.ts @@ -5,6 +5,7 @@ import { IRow } from '../../../interface/Row' import { formatElementList } from '../../../utils/element' import { Position } from '../../position/Position' import { Draw } from '../Draw' +import { LineBreakParticle } from '../particle/LineBreakParticle' export class Placeholder { private draw: Draw @@ -45,10 +46,15 @@ export class Placeholder { } private _computePositionList() { + const { lineBreak, scale } = this.options const headerExtraHeight = this.draw.getHeader().getExtraHeight() const innerWidth = this.draw.getInnerWidth() const margins = this.draw.getMargins() - const startX = margins[3] + let startX = margins[3] + // 换行符绘制开启时,移动起始位置 + if (!lineBreak.disabled) { + startX += (LineBreakParticle.WIDTH + LineBreakParticle.GAP) * scale + } const startY = margins[0] + headerExtraHeight this.position.computePageRowPosition({ positionList: this.positionList, @@ -92,7 +98,8 @@ export class Placeholder { rowList: this.rowList, pageNo: 0, startIndex: 0, - innerWidth + innerWidth, + isDrawLineBreak: false }) ctx.restore() } diff --git a/src/editor/core/draw/particle/LineBreakParticle.ts b/src/editor/core/draw/particle/LineBreakParticle.ts new file mode 100644 index 0000000..f6ce167 --- /dev/null +++ b/src/editor/core/draw/particle/LineBreakParticle.ts @@ -0,0 +1,55 @@ +import { DeepRequired } from '../../../interface/Common' +import { IEditorOption } from '../../../interface/Editor' +import { IRowElement } from '../../../interface/Row' +import { Draw } from '../Draw' + +export class LineBreakParticle { + private options: DeepRequired + public static readonly WIDTH = 12 + public static readonly HEIGHT = 9 + public static readonly GAP = 3 // 距离左边间隙 + + constructor(draw: Draw) { + this.options = draw.getOptions() + } + + public render( + ctx: CanvasRenderingContext2D, + element: IRowElement, + x: number, + y: number + ) { + const { + scale, + lineBreak: { color, lineWidth } + } = this.options + ctx.save() + ctx.beginPath() + // 换行符尺寸设置为9像素 + const top = y - (LineBreakParticle.HEIGHT * scale) / 2 + const left = x + element.metrics.width + // 移动位置并设置缩放 + ctx.translate(left, top) + ctx.scale(scale, scale) + // 样式设置 + ctx.strokeStyle = color + ctx.lineWidth = lineWidth + ctx.lineCap = 'round' + ctx.lineJoin = 'round' + ctx.beginPath() + // 回车折线 + ctx.moveTo(8, 0) + ctx.lineTo(12, 0) + ctx.lineTo(12, 6) + ctx.lineTo(3, 6) + // 箭头向上 + ctx.moveTo(3, 6) + ctx.lineTo(6, 3) + // 箭头向下 + ctx.moveTo(3, 6) + ctx.lineTo(6, 9) + ctx.stroke() + ctx.closePath() + ctx.restore() + } +} diff --git a/src/editor/dataset/constant/LineBreak.ts b/src/editor/dataset/constant/LineBreak.ts new file mode 100644 index 0000000..520f751 --- /dev/null +++ b/src/editor/dataset/constant/LineBreak.ts @@ -0,0 +1,7 @@ +import { ILineBreakOption } from '../../interface/LineBreak' + +export const defaultLineBreak: Readonly> = { + disabled: true, + color: '#CCCCCC', + lineWidth: 1.5 +} diff --git a/src/editor/index.ts b/src/editor/index.ts index 6acc307..a3bf4e5 100644 --- a/src/editor/index.ts +++ b/src/editor/index.ts @@ -76,6 +76,8 @@ import { IBackgroundOption } from './interface/Background' import { defaultBackground } from './dataset/constant/Background' import { BackgroundRepeat, BackgroundSize } from './dataset/enum/Background' import { TextDecorationStyle } from './dataset/enum/Text' +import { ILineBreakOption } from './interface/LineBreak' +import { defaultLineBreak } from './dataset/constant/LineBreak' export default class Editor { public command: Command @@ -143,6 +145,10 @@ export default class Editor { ...defaultBackground, ...options.background } + const lineBreakOptions: Required = { + ...defaultLineBreak, + ...options.lineBreak + } const editorOptions: DeepRequired = { mode: EditorMode.EDIT, @@ -200,7 +206,8 @@ export default class Editor { group: groupOptions, pageBreak: pageBreakOptions, zone: zoneOptions, - background: backgroundOptions + background: backgroundOptions, + lineBreak: lineBreakOptions } // 数据处理 data = deepClone(data) diff --git a/src/editor/interface/Draw.ts b/src/editor/interface/Draw.ts index 7cc97a6..62c6eca 100644 --- a/src/editor/interface/Draw.ts +++ b/src/editor/interface/Draw.ts @@ -32,6 +32,7 @@ export interface IDrawRowPayload { startIndex: number innerWidth: number zone?: EditorZone + isDrawLineBreak?: boolean } export interface IDrawFloatPayload { diff --git a/src/editor/interface/Editor.ts b/src/editor/interface/Editor.ts index ced4b5b..3f2f440 100644 --- a/src/editor/interface/Editor.ts +++ b/src/editor/interface/Editor.ts @@ -13,6 +13,7 @@ import { ICursorOption } from './Cursor' import { IFooter } from './Footer' import { IGroup } from './Group' import { IHeader } from './Header' +import { ILineBreakOption } from './LineBreak' import { IMargin } from './Margin' import { IPageBreak } from './PageBreak' import { IPageNumber } from './PageNumber' @@ -83,6 +84,7 @@ export interface IEditorOption { pageBreak?: IPageBreak zone?: IZoneOption background?: IBackgroundOption + lineBreak?: ILineBreakOption } export interface IEditorResult { diff --git a/src/editor/interface/LineBreak.ts b/src/editor/interface/LineBreak.ts new file mode 100644 index 0000000..16625df --- /dev/null +++ b/src/editor/interface/LineBreak.ts @@ -0,0 +1,5 @@ +export interface ILineBreakOption { + disabled?: boolean + color?: string + lineWidth?: number +} diff --git a/src/editor/interface/Row.ts b/src/editor/interface/Row.ts index 1c6ecac..c8bc6f6 100644 --- a/src/editor/interface/Row.ts +++ b/src/editor/interface/Row.ts @@ -18,4 +18,5 @@ export interface IRow { listIndex?: number offsetX?: number elementList: IRowElement[] + isWidthNotEnough?: boolean }