Skip to content

Commit

Permalink
feat: image element floating #363
Browse files Browse the repository at this point in the history
  • Loading branch information
Hufe921 committed Feb 24, 2024
1 parent 5b52bb8 commit b357a57
Show file tree
Hide file tree
Showing 23 changed files with 503 additions and 78 deletions.
6 changes: 4 additions & 2 deletions docs/en/guide/contextmenu-internal.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@
- Change the picture
- Save as picture
- Text wrapping
- Embedded type
- Upper and lower surrounding
- Embedded
- Upper and lower surrounding
- Float above text
- Float below text

## Table

Expand Down
2 changes: 2 additions & 0 deletions docs/guide/contextmenu-internal.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
- 文字环绕
- 嵌入型
- 上下型环绕
- 浮于文字上方
- 衬于文字下方

## 表格

Expand Down
6 changes: 6 additions & 0 deletions src/editor/assets/css/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,10 @@
to {
opacity: 1
}
}

.ce-float-image {
position: absolute;
opacity: 0.5;
pointer-events: none;
}
1 change: 1 addition & 0 deletions src/editor/assets/css/resizer/resizer.css
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
height: 20px;
white-space: nowrap;
position: absolute;
z-index: 9;
top: -30px;
left: 0;
opacity: .9;
Expand Down
19 changes: 18 additions & 1 deletion src/editor/core/command/CommandAdapt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { NBSP, WRAP, ZERO } from '../../dataset/constant/Common'
import { EDITOR_ELEMENT_STYLE_ATTR } from '../../dataset/constant/Element'
import { titleSizeMapping } from '../../dataset/constant/Title'
import { defaultWatermarkOption } from '../../dataset/constant/Watermark'
import { ControlComponent, ImageDisplay } from '../../dataset/enum/Control'
import { ImageDisplay } from '../../dataset/enum/Common'
import { ControlComponent } from '../../dataset/enum/Control'
import {
EditorContext,
EditorMode,
Expand Down Expand Up @@ -1944,6 +1945,22 @@ export class CommandAdapt {
public changeImageDisplay(element: IElement, display: ImageDisplay) {
if (element.imgDisplay === display) return
element.imgDisplay = display
if (
display === ImageDisplay.FLOAT_TOP ||
display === ImageDisplay.FLOAT_BOTTOM
) {
const positionList = this.position.getPositionList()
const { startIndex } = this.range.getRange()
const {
coordinate: { leftTop }
} = positionList[startIndex]
element.imgFloatPosition = {
x: leftTop[0],
y: leftTop[1]
}
} else {
delete element.imgFloatPosition
}
this.draw.getPreviewer().clearResizer()
this.draw.render({
isSetCursor: false
Expand Down
34 changes: 32 additions & 2 deletions src/editor/core/contextmenu/menus/imageMenus.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
import { INTERNAL_CONTEXT_MENU_KEY } from '../../../dataset/constant/ContextMenu'
import { ImageDisplay } from '../../../dataset/enum/Control'
import { ImageDisplay } from '../../../dataset/enum/Common'
import { ElementType } from '../../../dataset/enum/Element'
import {
IContextMenuContext,
IRegisterContextMenu
} from '../../../interface/contextmenu/ContextMenu'
import { Command } from '../../command/Command'
const {
IMAGE: { CHANGE, SAVE_AS, TEXT_WRAP, TEXT_WRAP_EMBED, TEXT_WRAP_UP_DOWN }
IMAGE: {
CHANGE,
SAVE_AS,
TEXT_WRAP,
TEXT_WRAP_EMBED,
TEXT_WRAP_UP_DOWN,
TEXT_WRAP_FLOAT_TOP,
TEXT_WRAP_FLOAT_BOTTOM
}
} = INTERNAL_CONTEXT_MENU_KEY

export const imageMenus: IRegisterContextMenu[] = [
Expand Down Expand Up @@ -86,6 +94,28 @@ export const imageMenus: IRegisterContextMenu[] = [
ImageDisplay.INLINE
)
}
},
{
key: TEXT_WRAP_FLOAT_TOP,
i18nPath: 'contextmenu.image.textWrapType.floatTop',
when: () => true,
callback: (command: Command, context: IContextMenuContext) => {
command.executeChangeImageDisplay(
context.startElement!,
ImageDisplay.FLOAT_TOP
)
}
},
{
key: TEXT_WRAP_FLOAT_BOTTOM,
i18nPath: 'contextmenu.image.textWrapType.floatBottom',
when: () => true,
callback: (command: Command, context: IContextMenuContext) => {
command.executeChangeImageDisplay(
context.startElement!,
ImageDisplay.FLOAT_BOTTOM
)
}
}
]
}
Expand Down
101 changes: 77 additions & 24 deletions src/editor/core/draw/Draw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ZERO } from '../../dataset/constant/Common'
import { RowFlex } from '../../dataset/enum/Row'
import {
IAppendElementListOption,
IDrawFloatPayload,
IDrawOption,
IDrawPagePayload,
IDrawRowPayload,
Expand Down Expand Up @@ -66,8 +67,7 @@ import { CheckboxParticle } from './particle/CheckboxParticle'
import { DeepRequired, IPadding } from '../../interface/Common'
import {
ControlComponent,
ControlIndentation,
ImageDisplay
ControlIndentation
} from '../../dataset/enum/Control'
import { formatElementList } from '../../utils/element'
import { WorkerManager } from '../worker/WorkerManager'
Expand All @@ -87,6 +87,7 @@ import { EventBus } from '../event/eventbus/EventBus'
import { EventBusMap } from '../../interface/EventBus'
import { Group } from './interactive/Group'
import { Override } from '../override/Override'
import { ImageDisplay } from '../../dataset/enum/Common'

export class Draw {
private container: HTMLDivElement
Expand Down Expand Up @@ -1106,28 +1107,39 @@ export class Draw {
element.type === ElementType.IMAGE ||
element.type === ElementType.LATEX
) {
const elementWidth = element.width! * scale
const elementHeight = element.height! * scale
// 图片超出尺寸后自适应
const curRowWidth =
element.imgDisplay === ImageDisplay.INLINE ? 0 : curRow.width
if (curRowWidth + elementWidth > availableWidth) {
// 计算剩余大小
const surplusWidth = availableWidth - curRowWidth
const adaptiveWidth =
surplusWidth > 0
? surplusWidth
: Math.min(elementWidth, availableWidth)
const adaptiveHeight = (elementHeight * adaptiveWidth) / elementWidth
element.width = adaptiveWidth / scale
element.height = adaptiveHeight / scale
metrics.width = adaptiveWidth
metrics.height = adaptiveHeight
metrics.boundingBoxDescent = adaptiveHeight
// 浮动图片无需计算数据
if (
element.imgDisplay === ImageDisplay.FLOAT_TOP ||
element.imgDisplay === ImageDisplay.FLOAT_BOTTOM
) {
metrics.width = 0
metrics.height = 0
metrics.boundingBoxDescent = 0
} else {
metrics.width = elementWidth
metrics.height = elementHeight
metrics.boundingBoxDescent = elementHeight
const elementWidth = element.width! * scale
const elementHeight = element.height! * scale
// 图片超出尺寸后自适应
const curRowWidth =
element.imgDisplay === ImageDisplay.INLINE ? 0 : curRow.width
if (curRowWidth + elementWidth > availableWidth) {
// 计算剩余大小
const surplusWidth = availableWidth - curRowWidth
const adaptiveWidth =
surplusWidth > 0
? surplusWidth
: Math.min(elementWidth, availableWidth)
const adaptiveHeight =
(elementHeight * adaptiveWidth) / elementWidth
element.width = adaptiveWidth / scale
element.height = adaptiveHeight / scale
metrics.width = adaptiveWidth
metrics.height = adaptiveHeight
metrics.boundingBoxDescent = adaptiveHeight
} else {
metrics.width = elementWidth
metrics.height = elementHeight
metrics.boundingBoxDescent = elementHeight
}
}
metrics.boundingBoxAscent = 0
} else if (element.type === ElementType.TABLE) {
Expand Down Expand Up @@ -1607,7 +1619,13 @@ export class Draw {
// 元素绘制
if (element.type === ElementType.IMAGE) {
this._drawRichText(ctx)
this.imageParticle.render(ctx, element, x, y + offsetY)
// 浮动图片单独绘制
if (
element.imgDisplay !== ImageDisplay.FLOAT_TOP &&
element.imgDisplay !== ImageDisplay.FLOAT_BOTTOM
) {
this.imageParticle.render(ctx, element, x, y + offsetY)
}
} else if (element.type === ElementType.LATEX) {
this._drawRichText(ctx)
this.laTexParticle.render(ctx, element, x, y + offsetY)
Expand Down Expand Up @@ -1816,6 +1834,31 @@ export class Draw {
}
}

private _drawFloat(
ctx: CanvasRenderingContext2D,
payload: IDrawFloatPayload
) {
const floatPositionList = this.position.getFloatPositionList()
const { imgDisplay, pageNo } = payload
for (let e = 0; e < floatPositionList.length; e++) {
const floatPosition = floatPositionList[e]
const element = floatPosition.element
if (
pageNo === floatPosition.pageNo &&
element.imgDisplay === imgDisplay &&
element.type === ElementType.IMAGE
) {
const imgFloatPosition = element.imgFloatPosition!
this.imageParticle.render(
ctx,
element,
imgFloatPosition.x,
imgFloatPosition.y
)
}
}
}

private _clearPage(pageNo: number) {
const ctx = this.ctxList[pageNo]
const pageDom = this.pageList[pageNo]
Expand All @@ -1837,6 +1880,11 @@ export class Draw {
if (this.mode !== EditorMode.PRINT) {
this.margin.render(ctx, pageNo)
}
// 渲染衬于文字下方元素
this._drawFloat(ctx, {
pageNo,
imgDisplay: ImageDisplay.FLOAT_BOTTOM
})
// 控件高亮
this.control.renderHighlightList(ctx, pageNo)
// 渲染元素
Expand Down Expand Up @@ -1864,6 +1912,11 @@ export class Draw {
this.footer.render(ctx, pageNo)
}
}
// 渲染浮于文字上方元素
this._drawFloat(ctx, {
pageNo,
imgDisplay: ImageDisplay.FLOAT_TOP
})
// 搜索匹配绘制
if (this.search.getSearchKeyword()) {
this.search.render(ctx, pageNo)
Expand Down
64 changes: 63 additions & 1 deletion src/editor/core/draw/particle/ImageParticle.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { EDITOR_PREFIX } from '../../../dataset/constant/Editor'
import { ImageDisplay } from '../../../dataset/enum/Common'
import { IEditorOption } from '../../../interface/Editor'
import { IElement } from '../../../interface/Element'
import { convertStringToBase64 } from '../../../utils'
Expand All @@ -7,11 +9,62 @@ export class ImageParticle {
private draw: Draw
protected options: Required<IEditorOption>
protected imageCache: Map<string, HTMLImageElement>
private container: HTMLDivElement
private floatImageContainer: HTMLDivElement | null
private floatImage: HTMLImageElement | null

constructor(draw: Draw) {
this.draw = draw
this.options = draw.getOptions()
this.container = draw.getContainer()
this.imageCache = new Map()
this.floatImageContainer = null
this.floatImage = null
}

public createFloatImage(element: IElement) {
const { scale } = this.options
// 复用浮动元素
let floatImageContainer = this.floatImageContainer
let floatImage = this.floatImage
if (!floatImageContainer) {
floatImageContainer = document.createElement('div')
floatImageContainer.classList.add(`${EDITOR_PREFIX}-float-image`)
this.container.append(floatImageContainer)
this.floatImageContainer = floatImageContainer
}
if (!floatImage) {
floatImage = document.createElement('img')
floatImageContainer.append(floatImage)
this.floatImage = floatImage
}
floatImageContainer.style.display = 'none'
floatImage.style.width = `${element.width! * scale}px`
floatImage.style.height = `${element.height! * scale}px`
// 浮动图片初始信息
const height = this.draw.getHeight()
const pageGap = this.draw.getPageGap()
const preY = this.draw.getPageNo() * (height + pageGap)
const imgFloatPosition = element.imgFloatPosition!
floatImageContainer.style.left = `${imgFloatPosition.x}px`
floatImageContainer.style.top = `${preY + imgFloatPosition.y}px`
floatImage.src = element.value
}

public dragFloatImage(movementX: number, movementY: number) {
if (!this.floatImageContainer) return
this.floatImageContainer.style.display = 'block'
// 之前的坐标加移动长度
const x = parseFloat(this.floatImageContainer.style.left) + movementX
const y = parseFloat(this.floatImageContainer.style.top) + movementY
this.floatImageContainer.style.left = `${x}px`
this.floatImageContainer.style.top = `${y}px`
}

public destroyFloatImage() {
if (this.floatImageContainer) {
this.floatImageContainer.style.display = 'none'
}
}

protected addImageObserver(promise: Promise<unknown>) {
Expand Down Expand Up @@ -58,9 +111,18 @@ export class ImageParticle {
img.setAttribute('crossOrigin', 'Anonymous')
img.src = element.value
img.onload = () => {
ctx.drawImage(img, x, y, width, height)
this.imageCache.set(element.id!, img)
resolve(element)
// 衬于文字下方图片需要重新首先绘制
if (element.imgDisplay === ImageDisplay.FLOAT_BOTTOM) {
this.draw.render({
isCompute: false,
isSetCursor: false,
isSubmitHistory: false
})
} else {
ctx.drawImage(img, x, y, width, height)
}
}
img.onerror = error => {
const fallbackImage = this.getFallbackImage(width, height)
Expand Down
Loading

0 comments on commit b357a57

Please sign in to comment.