feat: add background image option

pr675
Hufe921 2 years ago
parent 3253f3708e
commit eadf7f6e49

@ -27,7 +27,6 @@ interface IEditorOption {
height?: number // Paper height. default: 1123 height?: number // Paper height. default: 1123
scale?: number // scaling. default: 1 scale?: number // scaling. default: 1
pageGap?: number // Paper spacing. default: 20 pageGap?: number // Paper spacing. default: 20
backgroundColor?: string // Paper background color. default: #FFFFFF
underlineColor?: string // Underline color. default: #000000 underlineColor?: string // Underline color. default: #000000
strikeoutColor?: string // Strikeout color. default: #FF0000 strikeoutColor?: string // Strikeout color. default: #FF0000
rangeColor?: string // Range color. default: #AECBFA rangeColor?: string // Range color. default: #AECBFA
@ -68,6 +67,7 @@ interface IEditorOption {
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'}
} }
``` ```

@ -27,7 +27,6 @@ interface IEditorOption {
height?: number // 纸张高度。默认1123 height?: number // 纸张高度。默认1123
scale?: number // 缩放比例。默认1 scale?: number // 缩放比例。默认1
pageGap?: number // 纸张间隔。默认20 pageGap?: number // 纸张间隔。默认20
backgroundColor?: string // 纸张背景色。默认:#FFFFFF
underlineColor?: string // 下划线颜色。默认:#000000 underlineColor?: string // 下划线颜色。默认:#000000
strikeoutColor?: string // 删除线颜色。默认:#FF0000 strikeoutColor?: string // 删除线颜色。默认:#FF0000
rangeColor?: string // 选区颜色。默认:#AECBFA rangeColor?: string // 选区颜色。默认:#AECBFA
@ -68,6 +67,7 @@ interface IEditorOption {
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'}
} }
``` ```

@ -1,3 +1,7 @@
import {
BackgroundRepeat,
BackgroundSize
} from '../../../dataset/enum/Background'
import { DeepRequired } from '../../../interface/Common' import { DeepRequired } from '../../../interface/Common'
import { IEditorOption } from '../../../interface/Editor' import { IEditorOption } from '../../../interface/Editor'
import { Draw } from '../Draw' import { Draw } from '../Draw'
@ -5,19 +9,104 @@ import { Draw } from '../Draw'
export class Background { export class Background {
private draw: Draw private draw: Draw
private options: DeepRequired<IEditorOption> private options: DeepRequired<IEditorOption>
private imageCache: Map<string, HTMLImageElement>
constructor(draw: Draw) { constructor(draw: Draw) {
this.draw = draw this.draw = draw
this.options = draw.getOptions() this.options = draw.getOptions()
this.imageCache = new Map()
} }
public render(ctx: CanvasRenderingContext2D, pageNo: number) { private _renderBackgroundColor(
const { backgroundColor } = this.options ctx: CanvasRenderingContext2D,
const width = this.draw.getCanvasWidth(pageNo) color: string,
const height = this.draw.getCanvasHeight(pageNo) width: number,
height: number
) {
ctx.save() ctx.save()
ctx.fillStyle = backgroundColor ctx.fillStyle = color
ctx.fillRect(0, 0, width, height) ctx.fillRect(0, 0, width, height)
ctx.restore() ctx.restore()
} }
private _drawImage(
ctx: CanvasRenderingContext2D,
imageElement: HTMLImageElement,
width: number,
height: number
) {
const { background, scale } = this.options
// contain
if (background.size === BackgroundSize.CONTAIN) {
const imageWidth = imageElement.width * scale
const imageHeight = imageElement.height * scale
if (
!background.repeat ||
background.repeat === BackgroundRepeat.NO_REPEAT
) {
ctx.drawImage(imageElement, 0, 0, imageWidth, imageHeight)
} else {
let startX = 0
let startY = 0
const repeatXCount =
background.repeat === BackgroundRepeat.REPEAT ||
background.repeat === BackgroundRepeat.REPEAT_X
? Math.ceil((width * scale) / imageWidth)
: 1
const repeatYCount =
background.repeat === BackgroundRepeat.REPEAT ||
background.repeat === BackgroundRepeat.REPEAT_Y
? Math.ceil((height * scale) / imageHeight)
: 1
for (let x = 0; x < repeatXCount; x++) {
for (let y = 0; y < repeatYCount; y++) {
ctx.drawImage(imageElement, startX, startY, imageWidth, imageHeight)
startY += imageHeight
}
startY = 0
startX += imageWidth
}
}
} else {
// cover
ctx.drawImage(imageElement, 0, 0, width * scale, height * scale)
}
}
private _renderBackgroundImage(
ctx: CanvasRenderingContext2D,
width: number,
height: number
) {
const { background } = this.options
const imageElementCache = this.imageCache.get(background.image)
if (imageElementCache) {
this._drawImage(ctx, imageElementCache, width, height)
} else {
const img = new Image()
img.setAttribute('crossOrigin', 'Anonymous')
img.src = background.image
img.onload = () => {
this.imageCache.set(background.image, img)
this._drawImage(ctx, img, width, height)
// 避免层级上浮,触发编辑器二次渲染
this.draw.render({
isCompute: false,
isSubmitHistory: false
})
}
}
}
public render(ctx: CanvasRenderingContext2D, pageNo: number) {
const { background } = this.options
if (background.image) {
const { width, height } = this.options
this._renderBackgroundImage(ctx, width, height)
} else {
const width = this.draw.getCanvasWidth(pageNo)
const height = this.draw.getCanvasHeight(pageNo)
this._renderBackgroundColor(ctx, background.color, width, height)
}
}
} }

@ -0,0 +1,9 @@
import { IBackgroundOption } from '../../interface/Background'
import { BackgroundRepeat, BackgroundSize } from '../enum/Background'
export const defaultBackground: Readonly<Required<IBackgroundOption>> = {
color: '#FFFFFF',
image: '',
size: BackgroundSize.COVER,
repeat: BackgroundRepeat.NO_REPEAT
}

@ -0,0 +1,11 @@
export enum BackgroundSize {
CONTAIN = 'contain',
COVER = 'cover'
}
export enum BackgroundRepeat {
REPEAT = 'repeat',
NO_REPEAT = 'no-repeat',
REPEAT_X = 'repeat-x',
REPEAT_Y = 'repeat-y'
}

@ -75,6 +75,9 @@ import { IRange } from './interface/Range'
import { deepClone, splitText } from './utils' import { deepClone, splitText } from './utils'
import { IZoneOption } from './interface/Zone' import { IZoneOption } from './interface/Zone'
import { defaultZoneOption } from './dataset/constant/Zone' import { defaultZoneOption } from './dataset/constant/Zone'
import { IBackgroundOption } from './interface/Background'
import { defaultBackground } from './dataset/constant/Background'
import { BackgroundRepeat, BackgroundSize } from './dataset/enum/Background'
export default class Editor { export default class Editor {
public command: Command public command: Command
@ -138,6 +141,10 @@ export default class Editor {
...defaultZoneOption, ...defaultZoneOption,
...options.zone ...options.zone
} }
const backgroundOptions: Required<IBackgroundOption> = {
...defaultBackground,
...options.background
}
const editorOptions: DeepRequired<IEditorOption> = { const editorOptions: DeepRequired<IEditorOption> = {
mode: EditorMode.EDIT, mode: EditorMode.EDIT,
@ -153,7 +160,6 @@ export default class Editor {
height: 1123, height: 1123,
scale: 1, scale: 1,
pageGap: 20, pageGap: 20,
backgroundColor: '#FFFFFF',
underlineColor: '#000000', underlineColor: '#000000',
strikeoutColor: '#FF0000', strikeoutColor: '#FF0000',
rangeAlpha: 0.6, rangeAlpha: 0.6,
@ -194,7 +200,8 @@ export default class Editor {
placeholder: placeholderOptions, placeholder: placeholderOptions,
group: groupOptions, group: groupOptions,
pageBreak: pageBreakOptions, pageBreak: pageBreakOptions,
zone: zoneOptions zone: zoneOptions,
background: backgroundOptions
} }
// 数据处理 // 数据处理
data = deepClone(data) data = deepClone(data)
@ -292,7 +299,9 @@ export {
ListType, ListType,
ListStyle, ListStyle,
WordBreak, WordBreak,
ControlIndentation ControlIndentation,
BackgroundRepeat,
BackgroundSize
} }
// 对外类型 // 对外类型

@ -0,0 +1,8 @@
import { BackgroundRepeat, BackgroundSize } from '../dataset/enum/Background'
export interface IBackgroundOption {
color?: string
image?: string
size?: BackgroundSize
repeat?: BackgroundRepeat
}

@ -5,6 +5,7 @@ import {
PaperDirection, PaperDirection,
WordBreak WordBreak
} from '../dataset/enum/Editor' } from '../dataset/enum/Editor'
import { IBackgroundOption } from './Background'
import { ICheckboxOption } from './Checkbox' import { ICheckboxOption } from './Checkbox'
import { IPadding } from './Common' import { IPadding } from './Common'
import { IControlOption } from './Control' import { IControlOption } from './Control'
@ -40,7 +41,6 @@ export interface IEditorOption {
height?: number height?: number
scale?: number scale?: number
pageGap?: number pageGap?: number
backgroundColor?: string
underlineColor?: string underlineColor?: string
strikeoutColor?: string strikeoutColor?: string
rangeColor?: string rangeColor?: string
@ -81,6 +81,7 @@ export interface IEditorOption {
group?: IGroup group?: IGroup
pageBreak?: IPageBreak pageBreak?: IPageBreak
zone?: IZoneOption zone?: IZoneOption
background?: IBackgroundOption
} }
export interface IEditorResult { export interface IEditorResult {

Loading…
Cancel
Save