diff --git a/docs/en/guide/option.md b/docs/en/guide/option.md index 06bfe11..d8f8087 100644 --- a/docs/en/guide/option.md +++ b/docs/en/guide/option.md @@ -60,8 +60,8 @@ interface IEditorOption { wordBreak?: WordBreak // Word and punctuation breaks: No punctuation in the first line of the BREAK_WORD &The word is not split, and the line is folded after BREAK_ALL full according to the width of the character. default: BREAK_WORD 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;} - checkbox?: ICheckboxOption // Checkbox {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;} + 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; verticalAlign?: VerticalAlign;} 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;} placeholder?: IPlaceholder // Placeholder text diff --git a/docs/guide/option.md b/docs/guide/option.md index 8fdc3c3..9736b42 100644 --- a/docs/guide/option.md +++ b/docs/guide/option.md @@ -60,8 +60,8 @@ interface IEditorOption { wordBreak?: WordBreak // 单词与标点断行:BREAK_WORD首行不出现标点&单词不拆分、BREAK_ALL按字符宽度撑满后折行。默认:BREAK_WORD 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;} - checkbox?: ICheckboxOption // 复选框信息。{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;} + 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; verticalAlign?: VerticalAlign;} cursor?: ICursorOption // 光标样式。{width?: number; color?: string; dragWidth?: number; dragColor?: string;} title?: ITitleOption // 标题配置。{ defaultFirstSize?: number; defaultSecondSize?: number; defaultThirdSize?: number defaultFourthSize?: number; defaultFifthSize?: number; defaultSixthSize?: number;} placeholder?: IPlaceholder // 编辑器空白占位文本 diff --git a/src/editor/core/draw/Draw.ts b/src/editor/core/draw/Draw.ts index f0ca513..556208e 100644 --- a/src/editor/core/draw/Draw.ts +++ b/src/editor/core/draw/Draw.ts @@ -1912,13 +1912,25 @@ export class Draw { element.controlComponent === ControlComponent.CHECKBOX ) { 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 ( element.type === ElementType.RADIO || element.controlComponent === ControlComponent.RADIO ) { 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) { this.textParticle.complete() } else if ( diff --git a/src/editor/core/draw/particle/CheckboxParticle.ts b/src/editor/core/draw/particle/CheckboxParticle.ts index 1a5817b..1ec7faf 100644 --- a/src/editor/core/draw/particle/CheckboxParticle.ts +++ b/src/editor/core/draw/particle/CheckboxParticle.ts @@ -1,9 +1,19 @@ +import { NBSP, ZERO } from '../../../dataset/constant/Common' +import { VerticalAlign } from '../../../dataset/enum/VerticalAlign' import { DeepRequired } from '../../../interface/Common' import { IEditorOption } from '../../../interface/Editor' import { IElement } from '../../../interface/Element' -import { IRowElement } from '../../../interface/Row' +import { IRow, IRowElement } from '../../../interface/Row' import { Draw } from '../Draw' +interface ICheckboxRenderOption { + ctx: CanvasRenderingContext2D + x: number + y: number + row: IRow + index: number +} + export class CheckboxParticle { private draw: Draw private options: DeepRequired @@ -28,17 +38,41 @@ export class CheckboxParticle { }) } - public render( - ctx: CanvasRenderingContext2D, - element: IRowElement, - x: number, - y: number - ) { + public render(payload: ICheckboxRenderOption) { + const { ctx, x, index, row } = payload + let { y } = payload const { - checkbox: { gap, lineWidth, fillStyle, strokeStyle }, + checkbox: { gap, lineWidth, fillStyle, strokeStyle, verticalAlign }, scale } = 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像素问题 const left = Math.round(x + gap * scale) const top = Math.round(y - metrics.height + lineWidth) diff --git a/src/editor/core/draw/particle/ListParticle.ts b/src/editor/core/draw/particle/ListParticle.ts index 1548979..f4e1c49 100644 --- a/src/editor/core/draw/particle/ListParticle.ts +++ b/src/editor/core/draw/particle/ListParticle.ts @@ -199,9 +199,16 @@ export class ListParticle { height: height * scale } } - this.draw - .getCheckboxParticle() - .render(ctx, checkboxRowElement, x - gap * scale, y) + this.draw.getCheckboxParticle().render({ + ctx, + x: x - gap * scale, + y, + index: 0, + row: { + ...row, + elementList: [checkboxRowElement, ...row.elementList] + } + }) } else { let text = '' if (startElement.listType === ListType.UL) { diff --git a/src/editor/core/draw/particle/RadioParticle.ts b/src/editor/core/draw/particle/RadioParticle.ts index a6a2e25..1df91f3 100644 --- a/src/editor/core/draw/particle/RadioParticle.ts +++ b/src/editor/core/draw/particle/RadioParticle.ts @@ -1,9 +1,19 @@ +import { NBSP, ZERO } from '../../../dataset/constant/Common' +import { VerticalAlign } from '../../../dataset/enum/VerticalAlign' import { DeepRequired } from '../../../interface/Common' import { IEditorOption } from '../../../interface/Editor' import { IElement } from '../../../interface/Element' -import { IRowElement } from '../../../interface/Row' +import { IRow, IRowElement } from '../../../interface/Row' import { Draw } from '../Draw' +interface IRadioRenderOption { + ctx: CanvasRenderingContext2D + x: number + y: number + row: IRow + index: number +} + export class RadioParticle { private draw: Draw private options: DeepRequired @@ -28,17 +38,41 @@ export class RadioParticle { }) } - public render( - ctx: CanvasRenderingContext2D, - element: IRowElement, - x: number, - y: number - ) { + public render(payload: IRadioRenderOption) { + const { ctx, x, index, row } = payload + let { y } = payload const { - radio: { gap, lineWidth, fillStyle, strokeStyle }, + radio: { gap, lineWidth, fillStyle, strokeStyle, verticalAlign }, scale } = 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像素问题 const left = Math.round(x + gap * scale) const top = Math.round(y - metrics.height + lineWidth) diff --git a/src/editor/dataset/constant/Checkbox.ts b/src/editor/dataset/constant/Checkbox.ts index 3153027..5ff7b04 100644 --- a/src/editor/dataset/constant/Checkbox.ts +++ b/src/editor/dataset/constant/Checkbox.ts @@ -1,4 +1,5 @@ import { ICheckboxOption } from '../../interface/Checkbox' +import { VerticalAlign } from '../enum/VerticalAlign' export const defaultCheckboxOption: Readonly> = { width: 14, @@ -6,5 +7,6 @@ export const defaultCheckboxOption: Readonly> = { gap: 5, lineWidth: 1, fillStyle: '#5175f4', - strokeStyle: '#ffffff' + strokeStyle: '#ffffff', + verticalAlign: VerticalAlign.BOTTOM } diff --git a/src/editor/dataset/constant/Radio.ts b/src/editor/dataset/constant/Radio.ts index 118d783..a16c0e9 100644 --- a/src/editor/dataset/constant/Radio.ts +++ b/src/editor/dataset/constant/Radio.ts @@ -1,4 +1,5 @@ import { IRadioOption } from '../../interface/Radio' +import { VerticalAlign } from '../enum/VerticalAlign' export const defaultRadioOption: Readonly> = { width: 14, @@ -6,5 +7,6 @@ export const defaultRadioOption: Readonly> = { gap: 5, lineWidth: 1, fillStyle: '#5175f4', - strokeStyle: '#000000' + strokeStyle: '#000000', + verticalAlign: VerticalAlign.BOTTOM } diff --git a/src/editor/interface/Checkbox.ts b/src/editor/interface/Checkbox.ts index 13ad547..456bd24 100644 --- a/src/editor/interface/Checkbox.ts +++ b/src/editor/interface/Checkbox.ts @@ -1,3 +1,5 @@ +import { VerticalAlign } from '../dataset/enum/VerticalAlign' + export interface ICheckbox { value: boolean | null code?: string @@ -11,4 +13,5 @@ export interface ICheckboxOption { lineWidth?: number fillStyle?: string strokeStyle?: string + verticalAlign?: VerticalAlign } diff --git a/src/editor/interface/Radio.ts b/src/editor/interface/Radio.ts index 1f4fc61..95a42dc 100644 --- a/src/editor/interface/Radio.ts +++ b/src/editor/interface/Radio.ts @@ -1,3 +1,5 @@ +import { VerticalAlign } from '../dataset/enum/VerticalAlign' + export interface IRadio { value: boolean | null code?: string @@ -11,4 +13,5 @@ export interface IRadioOption { lineWidth?: number fillStyle?: string strokeStyle?: string + verticalAlign?: VerticalAlign }