feat: image element floating #363

pr675
Hufe921 2 years ago
parent 5b52bb8794
commit b357a57dfd

@ -19,8 +19,10 @@
- Change the picture - Change the picture
- Save as picture - Save as picture
- Text wrapping - Text wrapping
- Embedded type - Embedded
- Upper and lower surrounding - Upper and lower surrounding
- Float above text
- Float below text
## Table ## Table

@ -21,6 +21,8 @@
- 文字环绕 - 文字环绕
- 嵌入型 - 嵌入型
- 上下型环绕 - 上下型环绕
- 浮于文字上方
- 衬于文字下方
## 表格 ## 表格

@ -68,4 +68,10 @@
to { to {
opacity: 1 opacity: 1
} }
}
.ce-float-image {
position: absolute;
opacity: 0.5;
pointer-events: none;
} }

@ -54,6 +54,7 @@
height: 20px; height: 20px;
white-space: nowrap; white-space: nowrap;
position: absolute; position: absolute;
z-index: 9;
top: -30px; top: -30px;
left: 0; left: 0;
opacity: .9; opacity: .9;

@ -2,7 +2,8 @@ import { NBSP, WRAP, ZERO } from '../../dataset/constant/Common'
import { EDITOR_ELEMENT_STYLE_ATTR } from '../../dataset/constant/Element' import { EDITOR_ELEMENT_STYLE_ATTR } from '../../dataset/constant/Element'
import { titleSizeMapping } from '../../dataset/constant/Title' import { titleSizeMapping } from '../../dataset/constant/Title'
import { defaultWatermarkOption } from '../../dataset/constant/Watermark' 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 { import {
EditorContext, EditorContext,
EditorMode, EditorMode,
@ -1944,6 +1945,22 @@ export class CommandAdapt {
public changeImageDisplay(element: IElement, display: ImageDisplay) { public changeImageDisplay(element: IElement, display: ImageDisplay) {
if (element.imgDisplay === display) return if (element.imgDisplay === display) return
element.imgDisplay = display 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.getPreviewer().clearResizer()
this.draw.render({ this.draw.render({
isSetCursor: false isSetCursor: false

@ -1,5 +1,5 @@
import { INTERNAL_CONTEXT_MENU_KEY } from '../../../dataset/constant/ContextMenu' 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 { ElementType } from '../../../dataset/enum/Element'
import { import {
IContextMenuContext, IContextMenuContext,
@ -7,7 +7,15 @@ import {
} from '../../../interface/contextmenu/ContextMenu' } from '../../../interface/contextmenu/ContextMenu'
import { Command } from '../../command/Command' import { Command } from '../../command/Command'
const { 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 } = INTERNAL_CONTEXT_MENU_KEY
export const imageMenus: IRegisterContextMenu[] = [ export const imageMenus: IRegisterContextMenu[] = [
@ -86,6 +94,28 @@ export const imageMenus: IRegisterContextMenu[] = [
ImageDisplay.INLINE 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
)
}
} }
] ]
} }

@ -3,6 +3,7 @@ import { ZERO } from '../../dataset/constant/Common'
import { RowFlex } from '../../dataset/enum/Row' import { RowFlex } from '../../dataset/enum/Row'
import { import {
IAppendElementListOption, IAppendElementListOption,
IDrawFloatPayload,
IDrawOption, IDrawOption,
IDrawPagePayload, IDrawPagePayload,
IDrawRowPayload, IDrawRowPayload,
@ -66,8 +67,7 @@ import { CheckboxParticle } from './particle/CheckboxParticle'
import { DeepRequired, IPadding } from '../../interface/Common' import { DeepRequired, IPadding } from '../../interface/Common'
import { import {
ControlComponent, ControlComponent,
ControlIndentation, ControlIndentation
ImageDisplay
} from '../../dataset/enum/Control' } from '../../dataset/enum/Control'
import { formatElementList } from '../../utils/element' import { formatElementList } from '../../utils/element'
import { WorkerManager } from '../worker/WorkerManager' import { WorkerManager } from '../worker/WorkerManager'
@ -87,6 +87,7 @@ import { EventBus } from '../event/eventbus/EventBus'
import { EventBusMap } from '../../interface/EventBus' import { EventBusMap } from '../../interface/EventBus'
import { Group } from './interactive/Group' import { Group } from './interactive/Group'
import { Override } from '../override/Override' import { Override } from '../override/Override'
import { ImageDisplay } from '../../dataset/enum/Common'
export class Draw { export class Draw {
private container: HTMLDivElement private container: HTMLDivElement
@ -1106,28 +1107,39 @@ export class Draw {
element.type === ElementType.IMAGE || element.type === ElementType.IMAGE ||
element.type === ElementType.LATEX element.type === ElementType.LATEX
) { ) {
const elementWidth = element.width! * scale // 浮动图片无需计算数据
const elementHeight = element.height! * scale if (
// 图片超出尺寸后自适应 element.imgDisplay === ImageDisplay.FLOAT_TOP ||
const curRowWidth = element.imgDisplay === ImageDisplay.FLOAT_BOTTOM
element.imgDisplay === ImageDisplay.INLINE ? 0 : curRow.width ) {
if (curRowWidth + elementWidth > availableWidth) { metrics.width = 0
// 计算剩余大小 metrics.height = 0
const surplusWidth = availableWidth - curRowWidth metrics.boundingBoxDescent = 0
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 { } else {
metrics.width = elementWidth const elementWidth = element.width! * scale
metrics.height = elementHeight const elementHeight = element.height! * scale
metrics.boundingBoxDescent = elementHeight // 图片超出尺寸后自适应
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 metrics.boundingBoxAscent = 0
} else if (element.type === ElementType.TABLE) { } else if (element.type === ElementType.TABLE) {
@ -1607,7 +1619,13 @@ export class Draw {
// 元素绘制 // 元素绘制
if (element.type === ElementType.IMAGE) { if (element.type === ElementType.IMAGE) {
this._drawRichText(ctx) 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) { } else if (element.type === ElementType.LATEX) {
this._drawRichText(ctx) this._drawRichText(ctx)
this.laTexParticle.render(ctx, element, x, y + offsetY) this.laTexParticle.render(ctx, element, x, y + offsetY)
@ -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) { private _clearPage(pageNo: number) {
const ctx = this.ctxList[pageNo] const ctx = this.ctxList[pageNo]
const pageDom = this.pageList[pageNo] const pageDom = this.pageList[pageNo]
@ -1837,6 +1880,11 @@ export class Draw {
if (this.mode !== EditorMode.PRINT) { if (this.mode !== EditorMode.PRINT) {
this.margin.render(ctx, pageNo) this.margin.render(ctx, pageNo)
} }
// 渲染衬于文字下方元素
this._drawFloat(ctx, {
pageNo,
imgDisplay: ImageDisplay.FLOAT_BOTTOM
})
// 控件高亮 // 控件高亮
this.control.renderHighlightList(ctx, pageNo) this.control.renderHighlightList(ctx, pageNo)
// 渲染元素 // 渲染元素
@ -1864,6 +1912,11 @@ export class Draw {
this.footer.render(ctx, pageNo) this.footer.render(ctx, pageNo)
} }
} }
// 渲染浮于文字上方元素
this._drawFloat(ctx, {
pageNo,
imgDisplay: ImageDisplay.FLOAT_TOP
})
// 搜索匹配绘制 // 搜索匹配绘制
if (this.search.getSearchKeyword()) { if (this.search.getSearchKeyword()) {
this.search.render(ctx, pageNo) this.search.render(ctx, pageNo)

@ -1,3 +1,5 @@
import { EDITOR_PREFIX } from '../../../dataset/constant/Editor'
import { ImageDisplay } from '../../../dataset/enum/Common'
import { IEditorOption } from '../../../interface/Editor' import { IEditorOption } from '../../../interface/Editor'
import { IElement } from '../../../interface/Element' import { IElement } from '../../../interface/Element'
import { convertStringToBase64 } from '../../../utils' import { convertStringToBase64 } from '../../../utils'
@ -7,11 +9,62 @@ export class ImageParticle {
private draw: Draw private draw: Draw
protected options: Required<IEditorOption> protected options: Required<IEditorOption>
protected imageCache: Map<string, HTMLImageElement> protected imageCache: Map<string, HTMLImageElement>
private container: HTMLDivElement
private floatImageContainer: HTMLDivElement | null
private floatImage: HTMLImageElement | null
constructor(draw: Draw) { constructor(draw: Draw) {
this.draw = draw this.draw = draw
this.options = draw.getOptions() this.options = draw.getOptions()
this.container = draw.getContainer()
this.imageCache = new Map() 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>) { protected addImageObserver(promise: Promise<unknown>) {
@ -58,9 +111,18 @@ export class ImageParticle {
img.setAttribute('crossOrigin', 'Anonymous') img.setAttribute('crossOrigin', 'Anonymous')
img.src = element.value img.src = element.value
img.onload = () => { img.onload = () => {
ctx.drawImage(img, x, y, width, height)
this.imageCache.set(element.id!, img) this.imageCache.set(element.id!, img)
resolve(element) 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 => { img.onerror = error => {
const fallbackImage = this.getFallbackImage(width, height) const fallbackImage = this.getFallbackImage(width, height)

@ -63,6 +63,32 @@ export class Previewer {
this.previewerImage = null this.previewerImage = null
} }
private _getElementPosition(
element: IElement,
position: IElementPosition | null = null
): { x: number; y: number } {
let x = 0
let y = 0
const height = this.draw.getHeight()
const pageGap = this.draw.getPageGap()
const preY = this.draw.getPageNo() * (height + pageGap)
// 优先使用浮动位置
if (element.imgFloatPosition) {
x = element.imgFloatPosition.x!
y = element.imgFloatPosition.y + preY
} else if (position) {
const {
coordinate: {
leftTop: [left, top]
},
ascent
} = position
x = left
y = top + preY + ascent
}
return { x, y }
}
private _createResizerDom(): IPreviewerCreateResult { private _createResizerDom(): IPreviewerCreateResult {
// 拖拽边框 // 拖拽边框
const resizerSelection = document.createElement('div') const resizerSelection = document.createElement('div')
@ -114,10 +140,8 @@ export class Previewer {
private _mousedown(evt: MouseEvent) { private _mousedown(evt: MouseEvent) {
this.canvas = this.draw.getPage() this.canvas = this.draw.getPage()
if (!this.curPosition || !this.curElement) return if (!this.curElement) return
const { scale } = this.options const { scale } = this.options
const height = this.draw.getHeight()
const pageGap = this.draw.getPageGap()
this.mousedownX = evt.x this.mousedownX = evt.x
this.mousedownY = evt.y this.mousedownY = evt.y
const target = evt.target as HTMLDivElement const target = evt.target as HTMLDivElement
@ -129,15 +153,13 @@ export class Previewer {
// 拖拽图片镜像 // 拖拽图片镜像
this.resizerImage.src = this.curElementSrc this.resizerImage.src = this.curElementSrc
this.resizerImageContainer.style.display = 'block' this.resizerImageContainer.style.display = 'block'
const { // 优先使用浮动位置信息
coordinate: { const { x: resizerLeft, y: resizerTop } = this._getElementPosition(
leftTop: [left, top] this.curElement,
}, this.curPosition
ascent )
} = this.curPosition this.resizerImageContainer.style.left = `${resizerLeft}px`
const prePageHeight = this.draw.getPageNo() * (height + pageGap) this.resizerImageContainer.style.top = `${resizerTop}px`
this.resizerImageContainer.style.left = `${left}px`
this.resizerImageContainer.style.top = `${top + prePageHeight + ascent}px`
this.resizerImage.style.width = `${this.curElement.width! * scale}px` this.resizerImage.style.width = `${this.curElement.width! * scale}px`
this.resizerImage.style.height = `${this.curElement.height! * scale}px` this.resizerImage.style.height = `${this.curElement.height! * scale}px`
// 追加全局事件 // 追加全局事件
@ -147,7 +169,7 @@ export class Previewer {
'mouseup', 'mouseup',
() => { () => {
// 改变尺寸 // 改变尺寸
if (this.curElement && this.curPosition) { if (this.curElement) {
this.curElement.width = this.width this.curElement.width = this.width
this.curElement.height = this.height this.curElement.height = this.height
this.draw.render({ isSetCursor: false }) this.draw.render({ isSetCursor: false })
@ -398,27 +420,22 @@ export class Previewer {
public drawResizer( public drawResizer(
element: IElement, element: IElement,
position: IElementPosition, position: IElementPosition | null = null,
options: IPreviewerDrawOption = {} options: IPreviewerDrawOption = {}
) { ) {
this.previewerDrawOption = options this.previewerDrawOption = options
const { scale } = this.options const { scale } = this.options
const {
coordinate: {
leftTop: [left, top]
},
ascent
} = position
const elementWidth = element.width! * scale const elementWidth = element.width! * scale
const elementHeight = element.height! * scale const elementHeight = element.height! * scale
const height = this.draw.getHeight()
const pageGap = this.draw.getPageGap()
const preY = this.draw.getPageNo() * (height + pageGap)
// 尺寸预览 // 尺寸预览
this._updateResizerSizeView(elementWidth, elementHeight) this._updateResizerSizeView(elementWidth, elementHeight)
// 边框 // 优先使用浮动位置信息
this.resizerSelection.style.left = `${left}px` const { x: resizerLeft, y: resizerTop } = this._getElementPosition(
this.resizerSelection.style.top = `${top + preY + ascent}px` element,
position
)
this.resizerSelection.style.left = `${resizerLeft}px`
this.resizerSelection.style.top = `${resizerTop}px`
// 更新预览包围框尺寸 // 更新预览包围框尺寸
this._updateResizerRect(elementWidth, elementHeight) this._updateResizerRect(elementWidth, elementHeight)
this.resizerSelection.style.display = 'block' this.resizerSelection.style.display = 'block'

@ -1,3 +1,4 @@
import { ImageDisplay } from '../../../dataset/enum/Common'
import { ElementType } from '../../../dataset/enum/Element' import { ElementType } from '../../../dataset/enum/Element'
import { MouseEventButton } from '../../../dataset/enum/Event' import { MouseEventButton } from '../../../dataset/enum/Event'
import { deepClone } from '../../../utils' import { deepClone } from '../../../utils'
@ -60,7 +61,9 @@ export function mousedown(evt: MouseEvent, host: CanvasEvent) {
// 记录选区开始位置 // 记录选区开始位置
host.mouseDownStartPosition = { host.mouseDownStartPosition = {
...positionResult, ...positionResult,
index: isTable ? tdValueIndex! : index index: isTable ? tdValueIndex! : index,
x: evt.offsetX,
y: evt.offsetY
} }
const elementList = draw.getElementList() const elementList = draw.getElementList()
const positionList = position.getPositionList() const positionList = position.getPositionList()
@ -129,6 +132,13 @@ export function mousedown(evt: MouseEvent, host: CanvasEvent) {
}) })
// 点击图片允许拖拽调整位置 // 点击图片允许拖拽调整位置
setRangeCache(host) setRangeCache(host)
// 浮动元素创建镜像图片
if (
curElement.imgDisplay === ImageDisplay.FLOAT_TOP ||
curElement.imgDisplay === ImageDisplay.FLOAT_BOTTOM
) {
draw.getImageParticle().createFloatImage(curElement)
}
} }
// 表格工具组件 // 表格工具组件
const tableTool = draw.getTableTool() const tableTool = draw.getTableTool()

@ -1,3 +1,5 @@
import { ImageDisplay } from '../../../dataset/enum/Common'
import { ElementType } from '../../../dataset/enum/Element'
import { CanvasEvent } from '../CanvasEvent' import { CanvasEvent } from '../CanvasEvent'
export function mousemove(evt: MouseEvent, host: CanvasEvent) { export function mousemove(evt: MouseEvent, host: CanvasEvent) {
@ -22,6 +24,19 @@ export function mousemove(evt: MouseEvent, host: CanvasEvent) {
return return
} }
} }
const cacheStartIndex = host.cacheRange?.startIndex
if (cacheStartIndex) {
// 浮动元素拖拽调整位置
const dragElement = host.cacheElementList![cacheStartIndex]
if (
dragElement?.type === ElementType.IMAGE &&
(dragElement.imgDisplay === ImageDisplay.FLOAT_TOP ||
dragElement.imgDisplay === ImageDisplay.FLOAT_BOTTOM)
) {
draw.getPreviewer().clearResizer()
draw.getImageParticle().dragFloatImage(evt.movementX, evt.movementY)
}
}
host.dragover(evt) host.dragover(evt)
host.isAllowDrop = true host.isAllowDrop = true
return return

@ -1,4 +1,5 @@
import { EDITOR_ELEMENT_STYLE_ATTR } from '../../../dataset/constant/Element' import { EDITOR_ELEMENT_STYLE_ATTR } from '../../../dataset/constant/Element'
import { ImageDisplay } from '../../../dataset/enum/Common'
import { ControlComponent, ControlType } from '../../../dataset/enum/Control' import { ControlComponent, ControlType } from '../../../dataset/enum/Control'
import { ElementType } from '../../../dataset/enum/Element' import { ElementType } from '../../../dataset/enum/Element'
import { IElement } from '../../../interface/Element' import { IElement } from '../../../interface/Element'
@ -18,6 +19,28 @@ function getElementIndexByDragId(dragId: string, elementList: IElement[]) {
return (<IDragElement[]>elementList).findIndex(el => el.dragId === dragId) return (<IDragElement[]>elementList).findIndex(el => el.dragId === dragId)
} }
// 移动悬浮图片位置
function moveImgPosition(
element: IElement,
evt: MouseEvent,
host: CanvasEvent
) {
const draw = host.getDraw()
if (
element.imgDisplay === ImageDisplay.FLOAT_TOP ||
element.imgDisplay === ImageDisplay.FLOAT_BOTTOM
) {
const moveX = evt.offsetX - host.mouseDownStartPosition!.x!
const moveY = evt.offsetY - host.mouseDownStartPosition!.y!
const imgFloatPosition = element.imgFloatPosition!
element.imgFloatPosition = {
x: imgFloatPosition.x + moveX,
y: imgFloatPosition.y + moveY
}
}
draw.getImageParticle().destroyFloatImage()
}
export function mouseup(evt: MouseEvent, host: CanvasEvent) { export function mouseup(evt: MouseEvent, host: CanvasEvent) {
// 判断是否允许拖放 // 判断是否允许拖放
if (host.isAllowDrop) { if (host.isAllowDrop) {
@ -42,13 +65,34 @@ export function mouseup(evt: MouseEvent, host: CanvasEvent) {
range.startIndex >= cacheStartIndex && range.startIndex >= cacheStartIndex &&
range.endIndex <= cacheEndIndex range.endIndex <= cacheEndIndex
) { ) {
// 清除渲染副作用
draw.clearSideEffect()
// 浮动元素拖拽需要提交历史
let isSubmitHistory = false
if (isCacheRangeCollapsed) {
// 图片移动
const dragElement = cacheElementList[cacheEndIndex]
if (dragElement.type === ElementType.IMAGE) {
moveImgPosition(dragElement, evt, host)
if (
dragElement.imgDisplay === ImageDisplay.FLOAT_TOP ||
dragElement.imgDisplay === ImageDisplay.FLOAT_BOTTOM
) {
draw.getPreviewer().drawResizer(dragElement)
isSubmitHistory = true
} else {
const cachePosition = cachePositionList[cacheEndIndex]
draw.getPreviewer().drawResizer(dragElement, cachePosition)
}
}
}
rangeManager.replaceRange({ rangeManager.replaceRange({
...cacheRange ...cacheRange
}) })
draw.render({ draw.render({
isSetCursor: false, isSetCursor: false,
isCompute: false, isCompute: false,
isSubmitHistory: false isSubmitHistory
}) })
return return
} }
@ -207,11 +251,35 @@ export function mouseup(evt: MouseEvent, host: CanvasEvent) {
range.startTrIndex, range.startTrIndex,
range.endTrIndex range.endTrIndex
) )
// 清除渲染副作用
draw.clearSideEffect()
// 移动图片
let imgElement: IElement | null = null
if (isCacheRangeCollapsed) {
const elementList = draw.getElementList()
const dragElement = elementList[rangeEndIndex]
if (dragElement.type === ElementType.IMAGE) {
moveImgPosition(dragElement, evt, host)
imgElement = dragElement
}
}
// 重新渲染 // 重新渲染
draw.render({ draw.render({
isSetCursor: false isSetCursor: false
}) })
draw.clearSideEffect() // 拖拽后渲染图片工具
if (imgElement) {
if (
imgElement.imgDisplay === ImageDisplay.FLOAT_TOP ||
imgElement.imgDisplay === ImageDisplay.FLOAT_BOTTOM
) {
draw.getPreviewer().drawResizer(imgElement)
} else {
const dragPositionList = position.getPositionList()
const dragPosition = dragPositionList[rangeEndIndex]
draw.getPreviewer().drawResizer(imgElement, dragPosition)
}
}
} else if (host.isAllowDrag) { } else if (host.isAllowDrag) {
// 如果是允许拖拽不允许拖放则光标重置 // 如果是允许拖拽不允许拖放则光标重置
host.mousedown(evt) host.mousedown(evt)

@ -21,7 +21,9 @@
"textWrap": "Text wrap", "textWrap": "Text wrap",
"textWrapType": { "textWrapType": {
"embed": "Embed", "embed": "Embed",
"upDown": "Up down" "upDown": "Up down",
"floatTop": "Float above text",
"floatBottom": "Float below text"
} }
}, },
"table": { "table": {

@ -21,7 +21,9 @@
"textWrap": "文字环绕", "textWrap": "文字环绕",
"textWrapType": { "textWrapType": {
"embed": "嵌入型", "embed": "嵌入型",
"upDown": "上下型环绕" "upDown": "上下型环绕",
"floatTop": "浮于文字上方",
"floatBottom": "衬于文字下方"
} }
}, },
"table": { "table": {

@ -1,10 +1,12 @@
import { ElementType, RowFlex, VerticalAlign } from '../..' import { ElementType, RowFlex, VerticalAlign } from '../..'
import { ZERO } from '../../dataset/constant/Common' import { ZERO } from '../../dataset/constant/Common'
import { ControlComponent, ImageDisplay } from '../../dataset/enum/Control' import { ControlComponent } from '../../dataset/enum/Control'
import { import {
IComputePageRowPositionPayload, IComputePageRowPositionPayload,
IComputePageRowPositionResult, IComputePageRowPositionResult,
IComputeRowPositionPayload IComputeRowPositionPayload,
IFloatPosition,
IGetFloatPositionByXYPayload
} from '../../interface/Position' } from '../../interface/Position'
import { IEditorOption } from '../../interface/Editor' import { IEditorOption } from '../../interface/Editor'
import { IElement, IElementPosition } from '../../interface/Element' import { IElement, IElementPosition } from '../../interface/Element'
@ -16,17 +18,20 @@ import {
import { Draw } from '../draw/Draw' import { Draw } from '../draw/Draw'
import { EditorMode, EditorZone } from '../../dataset/enum/Editor' import { EditorMode, EditorZone } from '../../dataset/enum/Editor'
import { deepClone } from '../../utils' import { deepClone } from '../../utils'
import { ImageDisplay } from '../../dataset/enum/Common'
export class Position { export class Position {
private cursorPosition: IElementPosition | null private cursorPosition: IElementPosition | null
private positionContext: IPositionContext private positionContext: IPositionContext
private positionList: IElementPosition[] private positionList: IElementPosition[]
private floatPositionList: IFloatPosition[]
private draw: Draw private draw: Draw
private options: Required<IEditorOption> private options: Required<IEditorOption>
constructor(draw: Draw) { constructor(draw: Draw) {
this.positionList = [] this.positionList = []
this.floatPositionList = []
this.cursorPosition = null this.cursorPosition = null
this.positionContext = { this.positionContext = {
isTable: false, isTable: false,
@ -37,6 +42,10 @@ export class Position {
this.options = draw.getOptions() this.options = draw.getOptions()
} }
public getFloatPositionList(): IFloatPosition[] {
return this.floatPositionList
}
public getTablePositionList( public getTablePositionList(
sourceElementList: IElement[] sourceElementList: IElement[]
): IElementPosition[] { ): IElementPosition[] {
@ -149,6 +158,28 @@ export class Position {
rightBottom: [x + metrics.width, y + curRow.height] rightBottom: [x + metrics.width, y + curRow.height]
} }
} }
// 缓存浮动元素信息
if (
element.imgDisplay === ImageDisplay.FLOAT_TOP ||
element.imgDisplay === ImageDisplay.FLOAT_BOTTOM
) {
// 浮动元素使用上一位置信息
const prePosition = positionList[positionList.length - 1]
if (prePosition) {
positionItem.metrics = prePosition.metrics
positionItem.coordinate = prePosition.coordinate
}
this.floatPositionList.push({
pageNo,
element,
position: positionItem,
isTable: payload.isTable,
index: payload.index,
tdIndex: payload.tdIndex,
trIndex: payload.trIndex,
tdValueIndex: index
})
}
positionList.push(positionItem) positionList.push(positionItem)
index++ index++
x += metrics.width x += metrics.width
@ -170,7 +201,11 @@ export class Position {
startIndex: 0, startIndex: 0,
startX: (td.x! + tdPadding[3]) * scale + tablePreX, startX: (td.x! + tdPadding[3]) * scale + tablePreX,
startY: (td.y! + tdPadding[0]) * scale + tablePreY, startY: (td.y! + tdPadding[0]) * scale + tablePreY,
innerWidth: (td.width! - tdPaddingWidth) * scale innerWidth: (td.width! - tdPaddingWidth) * scale,
isTable: true,
index: index - 1,
tdIndex: d,
trIndex: t
}) })
// 垂直对齐方式 // 垂直对齐方式
if ( if (
@ -217,6 +252,7 @@ export class Position {
public computePositionList() { public computePositionList() {
// 置空原位置信息 // 置空原位置信息
this.positionList = [] this.positionList = []
this.floatPositionList = []
// 按每页行计算 // 按每页行计算
const innerWidth = this.draw.getInnerWidth() const innerWidth = this.draw.getInnerWidth()
const pageRowList = this.draw.getPageRowList() const pageRowList = this.draw.getPageRowList()
@ -291,6 +327,15 @@ export class Position {
const curPageNo = payload.pageNo ?? this.draw.getPageNo() const curPageNo = payload.pageNo ?? this.draw.getPageNo()
const isMainActive = zoneManager.isMainActive() const isMainActive = zoneManager.isMainActive()
const positionNo = isMainActive ? curPageNo : 0 const positionNo = isMainActive ? curPageNo : 0
// 验证浮于文字上方元素
if (!isTable) {
const floatTopPosition = this.getFloatPositionByXY({
...payload,
imgDisplay: ImageDisplay.FLOAT_TOP
})
if (floatTopPosition) return floatTopPosition
}
// 普通元素
for (let j = 0; j < positionList.length; j++) { for (let j = 0; j < positionList.length; j++) {
const { const {
index, index,
@ -388,6 +433,14 @@ export class Position {
} }
} }
} }
// 验证衬于文字下方元素
if (!isTable) {
const floatBottomPosition = this.getFloatPositionByXY({
...payload,
imgDisplay: ImageDisplay.FLOAT_BOTTOM
})
if (floatBottomPosition) return floatBottomPosition
}
// 非命中区域 // 非命中区域
let isLastArea = false let isLastArea = false
let curPositionIndex = -1 let curPositionIndex = -1
@ -493,6 +546,55 @@ export class Position {
} }
} }
public getFloatPositionByXY(
payload: IGetFloatPositionByXYPayload
): ICurrentPosition | void {
const { x, y } = payload
for (let f = 0; f < this.floatPositionList.length; f++) {
const {
position,
element,
isTable,
index,
trIndex,
tdIndex,
tdValueIndex
} = this.floatPositionList[f]
if (
element.type === ElementType.IMAGE &&
element.imgDisplay === payload.imgDisplay
) {
const imgFloatPosition = element.imgFloatPosition!
if (
x >= imgFloatPosition.x &&
x <= imgFloatPosition.x + element.width! &&
y >= imgFloatPosition.y &&
y <= imgFloatPosition.y + element.height!
) {
if (isTable) {
return {
index: index!,
isDirectHit: true,
isImage: true,
isTable,
trIndex,
tdIndex,
tdValueIndex,
tdId: element.tdId,
trId: element.trId,
tableId: element.tableId
}
}
return {
index: position.index,
isDirectHit: true,
isImage: true
}
}
}
}
}
public adjustPositionContext( public adjustPositionContext(
payload: IGetPositionByXYPayload payload: IGetPositionByXYPayload
): ICurrentPosition | null { ): ICurrentPosition | null {

@ -23,7 +23,9 @@ export const INTERNAL_CONTEXT_MENU_KEY = {
SAVE_AS: 'imageSaveAs', SAVE_AS: 'imageSaveAs',
TEXT_WRAP: 'imageTextWrap', TEXT_WRAP: 'imageTextWrap',
TEXT_WRAP_EMBED: 'imageTextWrapEmbed', TEXT_WRAP_EMBED: 'imageTextWrapEmbed',
TEXT_WRAP_UP_DOWN: 'imageTextWrapUpDown' TEXT_WRAP_UP_DOWN: 'imageTextWrapUpDown',
TEXT_WRAP_FLOAT_TOP: 'imageTextWrapFloatTop',
TEXT_WRAP_FLOAT_BOTTOM: 'imageTextWrapFloatBottom'
}, },
TABLE: { TABLE: {
BORDER: 'border', BORDER: 'border',

@ -64,7 +64,9 @@ export const EDITOR_ELEMENT_ZIP_ATTR: Array<keyof IElement> = [
'listStyle', 'listStyle',
'listWrap', 'listWrap',
'groupIds', 'groupIds',
'conceptId' 'conceptId',
'imgDisplay',
'imgFloatPosition'
] ]
export const TABLE_TD_ZIP_ATTR: Array<keyof ITd> = [ export const TABLE_TD_ZIP_ATTR: Array<keyof ITd> = [

@ -8,3 +8,10 @@ export enum NumberType {
ARABIC = 'arabic', ARABIC = 'arabic',
CHINESE = 'chinese' CHINESE = 'chinese'
} }
export enum ImageDisplay {
INLINE = 'inline',
BLOCK = 'block',
FLOAT_TOP = 'float-top',
FLOAT_BOTTOM = 'float-bottom'
}

@ -12,11 +12,6 @@ export enum ControlComponent {
CHECKBOX = 'checkbox' CHECKBOX = 'checkbox'
} }
export enum ImageDisplay {
INLINE = 'inline',
BLOCK = 'block'
}
// 控件内容缩进方式 // 控件内容缩进方式
export enum ControlIndentation { export enum ControlIndentation {
ROW_START = 'rowStart', // 从行起始位置缩进 ROW_START = 'rowStart', // 从行起始位置缩进

@ -6,6 +6,7 @@ import { Command } from './core/command/Command'
import { CommandAdapt } from './core/command/CommandAdapt' import { CommandAdapt } from './core/command/CommandAdapt'
import { Listener } from './core/listener/Listener' import { Listener } from './core/listener/Listener'
import { RowFlex } from './dataset/enum/Row' import { RowFlex } from './dataset/enum/Row'
import { ImageDisplay } from './dataset/enum/Common'
import { ElementType } from './dataset/enum/Element' import { ElementType } from './dataset/enum/Element'
import { formatElementList } from './utils/element' import { formatElementList } from './utils/element'
import { Register } from './core/register/Register' import { Register } from './core/register/Register'
@ -27,11 +28,7 @@ import { IHeader } from './interface/Header'
import { IWatermark } from './interface/Watermark' import { IWatermark } from './interface/Watermark'
import { defaultHeaderOption } from './dataset/constant/Header' import { defaultHeaderOption } from './dataset/constant/Header'
import { defaultWatermarkOption } from './dataset/constant/Watermark' import { defaultWatermarkOption } from './dataset/constant/Watermark'
import { import { ControlIndentation, ControlType } from './dataset/enum/Control'
ControlIndentation,
ControlType,
ImageDisplay
} from './dataset/enum/Control'
import { defaultControlOption } from './dataset/constant/Control' import { defaultControlOption } from './dataset/constant/Control'
import { IControlOption } from './interface/Control' import { IControlOption } from './interface/Control'
import { ICheckboxOption } from './interface/Checkbox' import { ICheckboxOption } from './interface/Checkbox'

@ -1,3 +1,4 @@
import { ImageDisplay } from '../dataset/enum/Common'
import { EditorMode, EditorZone } from '../dataset/enum/Editor' import { EditorMode, EditorZone } from '../dataset/enum/Editor'
import { IElement, IElementPosition } from './Element' import { IElement, IElementPosition } from './Element'
import { IRow } from './Row' import { IRow } from './Row'
@ -32,6 +33,11 @@ export interface IDrawRowPayload {
zone?: EditorZone zone?: EditorZone
} }
export interface IDrawFloatPayload {
pageNo: number
imgDisplay: ImageDisplay
}
export interface IDrawPagePayload { export interface IDrawPagePayload {
elementList: IElement[] elementList: IElement[]
positionList: IElementPosition[] positionList: IElementPosition[]

@ -1,4 +1,5 @@
import { ControlComponent, ImageDisplay } from '../dataset/enum/Control' import { ImageDisplay } from '../dataset/enum/Common'
import { ControlComponent } from '../dataset/enum/Control'
import { ElementType } from '../dataset/enum/Element' import { ElementType } from '../dataset/enum/Element'
import { ListStyle, ListType } from '../dataset/enum/List' import { ListStyle, ListType } from '../dataset/enum/List'
import { RowFlex } from '../dataset/enum/Row' import { RowFlex } from '../dataset/enum/Row'
@ -100,6 +101,10 @@ export interface IDateElement {
export interface IImageElement { export interface IImageElement {
imgDisplay?: ImageDisplay imgDisplay?: ImageDisplay
imgFloatPosition?: {
x: number
y: number
}
} }
export interface IBlockElement { export interface IBlockElement {

@ -1,4 +1,4 @@
import { IElement } from '..' import { IElement, ImageDisplay } from '..'
import { EditorZone } from '../dataset/enum/Editor' import { EditorZone } from '../dataset/enum/Editor'
import { IElementPosition } from './Element' import { IElementPosition } from './Element'
import { IRow } from './Row' import { IRow } from './Row'
@ -6,6 +6,8 @@ import { ITd } from './table/Td'
export interface ICurrentPosition { export interface ICurrentPosition {
index: number index: number
x?: number
y?: number
isCheckbox?: boolean isCheckbox?: boolean
isControl?: boolean isControl?: boolean
isImage?: boolean isImage?: boolean
@ -32,6 +34,10 @@ export interface IGetPositionByXYPayload {
positionList?: IElementPosition[] positionList?: IElementPosition[]
} }
export type IGetFloatPositionByXYPayload = IGetPositionByXYPayload & {
imgDisplay: ImageDisplay
}
export interface IPositionContext { export interface IPositionContext {
isTable: boolean isTable: boolean
isCheckbox?: boolean isCheckbox?: boolean
@ -58,6 +64,11 @@ export interface IComputePageRowPositionPayload {
startX: number startX: number
startY: number startY: number
innerWidth: number innerWidth: number
isTable?: boolean
index?: number
tdIndex?: number
trIndex?: number
tdValueIndex?: number
} }
export interface IComputePageRowPositionResult { export interface IComputePageRowPositionResult {
@ -65,3 +76,14 @@ export interface IComputePageRowPositionResult {
y: number y: number
index: number index: number
} }
export interface IFloatPosition {
pageNo: number
element: IElement
position: IElementPosition
isTable?: boolean
index?: number
tdIndex?: number
trIndex?: number
tdValueIndex?: number
}

Loading…
Cancel
Save