feat: add text decoration property

pr675
Hufe921 2 years ago
parent dc2804c492
commit f1570f2180

@ -244,7 +244,7 @@ Feature: Underline
Usage:
```javascript
instance.command.executeUnderline()
instance.command.executeUnderline(textDecoration?: ITextDecoration)
```
## executeStrikeout

@ -41,6 +41,9 @@ interface IElement {
};
rowMargin?: number;
letterSpacing?: number;
textDecoration?: {
style?: TextDecorationStyle;
};
// groupIds
groupIds?: string[];
// table

@ -244,7 +244,7 @@ instance.command.executeItalic()
用法:
```javascript
instance.command.executeUnderline()
instance.command.executeUnderline(textDecoration?: ITextDecoration)
```
## executeStrikeout

@ -41,6 +41,9 @@ interface IElement {
};
rowMargin?: number;
letterSpacing?: number;
textDecoration?: {
style?: TextDecorationStyle;
};
// 组信息-可用于批注等其他成组使用场景
groupIds?: string[];
// 表格

@ -51,8 +51,9 @@ import { IRange, RangeContext, RangeRect } from '../../interface/Range'
import { IColgroup } from '../../interface/table/Colgroup'
import { ITd } from '../../interface/table/Td'
import { ITr } from '../../interface/table/Tr'
import { ITextDecoration } from '../../interface/Text'
import { IWatermark } from '../../interface/Watermark'
import { deepClone, downloadFile, getUUID } from '../../utils'
import { deepClone, downloadFile, getUUID, isObjectEqual } from '../../utils'
import {
createDomFromElementList,
formatElementContext,
@ -486,15 +487,29 @@ export class CommandAdapt {
}
}
public underline() {
public underline(textDecoration?: ITextDecoration) {
const isDisabled =
this.draw.isReadonly() || this.control.isDisabledControl()
if (isDisabled) return
const selection = this.range.getSelectionElementList()
if (selection?.length) {
const noUnderlineIndex = selection.findIndex(s => !s.underline)
// 没有设置下划线、当前与之前有一个设置不存在、文本装饰不一致时重设下划线
const isSetUnderline = selection.some(
s =>
!s.underline ||
(!textDecoration && s.textDecoration) ||
(textDecoration && !s.textDecoration) ||
(textDecoration &&
s.textDecoration &&
!isObjectEqual(s.textDecoration, textDecoration))
)
selection.forEach(el => {
el.underline = !!~noUnderlineIndex
el.underline = isSetUnderline
if (isSetUnderline && textDecoration) {
el.textDecoration = textDecoration
} else {
delete el.textDecoration
}
})
this.draw.render({
isSetCursor: false,

@ -1716,7 +1716,8 @@ export class Draw {
y + curRow.height - rowMargin + offsetY,
metrics.width + offsetX,
0,
color
color,
element.textDecoration?.style
)
} else if (preElement?.underline || preElement?.control?.underline) {
this.underline.render(ctx)

@ -1,8 +1,10 @@
import { TextDecorationStyle } from '../../../dataset/enum/Text'
import { IElementFillRect } from '../../../interface/Element'
export abstract class AbstractRichText {
public fillRect: IElementFillRect
public fillColor?: string
protected fillRect: IElementFillRect
protected fillColor?: string
protected fillDecorationStyle?: TextDecorationStyle
constructor() {
this.fillRect = this.clearFillInfo()
@ -10,6 +12,7 @@ export abstract class AbstractRichText {
public clearFillInfo() {
this.fillColor = undefined
this.fillDecorationStyle = undefined
this.fillRect = {
x: 0,
y: 0,
@ -25,15 +28,19 @@ export abstract class AbstractRichText {
y: number,
width: number,
height?: number,
color?: string
color?: string,
decorationStyle?: TextDecorationStyle
) {
const isFirstRecord = !this.fillRect.width
// 颜色不同时立即绘制
if (!isFirstRecord && this.fillColor !== color) {
if (
!isFirstRecord &&
(this.fillColor !== color || this.fillDecorationStyle !== decorationStyle)
) {
this.render(ctx)
this.clearFillInfo()
// 重新记录
this.recordFillInfo(ctx, x, y, width, height, color)
this.recordFillInfo(ctx, x, y, width, height, color, decorationStyle)
return
}
if (isFirstRecord) {
@ -45,6 +52,7 @@ export abstract class AbstractRichText {
}
this.fillRect.width += width
this.fillColor = color
this.fillDecorationStyle = decorationStyle
}
public abstract render(ctx: CanvasRenderingContext2D): void

@ -1,6 +1,7 @@
import { AbstractRichText } from './AbstractRichText'
import { IEditorOption } from '../../../interface/Editor'
import { Draw } from '../Draw'
import { DashType, TextDecorationStyle } from '../../../dataset/enum/Text'
export class Underline extends AbstractRichText {
private options: Required<IEditorOption>
@ -10,17 +11,95 @@ export class Underline extends AbstractRichText {
this.options = draw.getOptions()
}
// 下划线
private _drawLine(
ctx: CanvasRenderingContext2D,
startX: number,
startY: number,
width: number,
dashType?: DashType
) {
const endX = startX + width
ctx.beginPath()
switch (dashType) {
case DashType.DASHED:
// 长虚线- - - - - -
ctx.setLineDash([3, 1])
break
case DashType.DOTTED:
// 点虚线 . . . . . .
ctx.setLineDash([1, 1])
break
}
ctx.moveTo(startX, startY)
ctx.lineTo(endX, startY)
ctx.stroke()
}
// 双实线
private _drawDouble(
ctx: CanvasRenderingContext2D,
startX: number,
startY: number,
width: number
) {
const SPACING = 3 // 双实线间距
const endX = startX + width
const endY = startY + SPACING * this.options.scale
ctx.beginPath()
ctx.moveTo(startX, startY)
ctx.lineTo(endX, startY)
ctx.stroke()
ctx.beginPath()
ctx.moveTo(startX, endY)
ctx.lineTo(endX, endY)
ctx.stroke()
}
// 波浪线
private _drawWave(
ctx: CanvasRenderingContext2D,
startX: number,
startY: number,
width: number
) {
const { scale } = this.options
const AMPLITUDE = 1.2 * scale // 振幅
const FREQUENCY = 1 / scale // 频率
const adjustY = startY + 2 * AMPLITUDE // 增加2倍振幅
ctx.beginPath()
for (let x = 0; x < width; x++) {
const y = AMPLITUDE * Math.sin(FREQUENCY * x)
ctx.lineTo(startX + x, adjustY + y)
}
ctx.stroke()
}
public render(ctx: CanvasRenderingContext2D) {
if (!this.fillRect.width) return
const { underlineColor } = this.options
const { underlineColor, scale } = this.options
const { x, y, width } = this.fillRect
ctx.save()
ctx.strokeStyle = this.fillColor || underlineColor
ctx.lineWidth = scale
const adjustY = Math.floor(y + 2 * ctx.lineWidth) + 0.5 // +0.5从1处渲染避免线宽度等于3
ctx.beginPath()
ctx.moveTo(x, adjustY)
ctx.lineTo(x + width, adjustY)
ctx.stroke()
switch (this.fillDecorationStyle) {
case TextDecorationStyle.WAVY:
this._drawWave(ctx, x, adjustY, width)
break
case TextDecorationStyle.DOUBLE:
this._drawDouble(ctx, x, adjustY, width)
break
case TextDecorationStyle.DASHED:
this._drawLine(ctx, x, adjustY, width, DashType.DASHED)
break
case TextDecorationStyle.DOTTED:
this._drawLine(ctx, x, adjustY, width, DashType.DOTTED)
break
default:
this._drawLine(ctx, x, adjustY, width)
break
}
ctx.restore()
this.clearFillInfo()
}

@ -10,7 +10,8 @@ export const EDITOR_ELEMENT_STYLE_ATTR: Array<keyof IElement> = [
'size',
'italic',
'underline',
'strikeout'
'strikeout',
'textDecoration'
]
export const EDITOR_ROW_ATTR: Array<keyof IElement> = ['rowFlex', 'rowMargin']
@ -32,7 +33,8 @@ export const EDITOR_ELEMENT_COPY_ATTR: Array<keyof IElement> = [
'dateFormat',
'groupIds',
'rowFlex',
'rowMargin'
'rowMargin',
'textDecoration'
]
export const EDITOR_ELEMENT_ZIP_ATTR: Array<keyof IElement> = [
@ -66,7 +68,8 @@ export const EDITOR_ELEMENT_ZIP_ATTR: Array<keyof IElement> = [
'groupIds',
'conceptId',
'imgDisplay',
'imgFloatPosition'
'imgFloatPosition',
'textDecoration'
]
export const TABLE_TD_ZIP_ATTR: Array<keyof ITd> = [

@ -0,0 +1,13 @@
export enum TextDecorationStyle {
SOLID = 'solid',
DOUBLE = 'double',
DASHED = 'dashed',
DOTTED = 'dotted',
WAVY = 'wavy'
}
export enum DashType {
SOLID = 'solid',
DASHED = 'dashed',
DOTTED = 'dotted'
}

@ -75,6 +75,7 @@ import { defaultZoneOption } from './dataset/constant/Zone'
import { IBackgroundOption } from './interface/Background'
import { defaultBackground } from './dataset/constant/Background'
import { BackgroundRepeat, BackgroundSize } from './dataset/enum/Background'
import { TextDecorationStyle } from './dataset/enum/Text'
export default class Editor {
public command: Command
@ -299,7 +300,8 @@ export {
WordBreak,
ControlIndentation,
BackgroundRepeat,
BackgroundSize
BackgroundSize,
TextDecorationStyle
}
// 对外类型

@ -8,6 +8,7 @@ import { TableBorder } from '../dataset/enum/table/Table'
import { IBlock } from './Block'
import { ICheckbox } from './Checkbox'
import { IControl } from './Control'
import { ITextDecoration } from './Text'
import { IColgroup } from './table/Colgroup'
import { ITr } from './table/Tr'
@ -31,6 +32,7 @@ export interface IElementStyle {
rowFlex?: RowFlex
rowMargin?: number
letterSpacing?: number
textDecoration?: ITextDecoration
}
export interface IElementGroup {

@ -0,0 +1,5 @@
import { TextDecorationStyle } from '../dataset/enum/Text'
export interface ITextDecoration {
style?: TextDecorationStyle
}

@ -285,3 +285,13 @@ export function isArrayEqual(arr1: unknown[], arr2: unknown[]): boolean {
}
return !arr1.some(item => !arr2.includes(item))
}
export function isObjectEqual(obj1: unknown, obj2: unknown): boolean {
if (!isObject(obj1) || !isObject(obj2)) return false
const obj1Keys = Object.keys(obj1)
const obj2Keys = Object.keys(obj2)
if (obj1Keys.length !== obj2Keys.length) {
return false
}
return !obj1Keys.some(key => obj2[key] !== obj1[key])
}

Loading…
Cancel
Save