feat: image element floating #363

pr675
Hufe921 2 years ago
parent 5b52bb8794
commit b357a57dfd

@ -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

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

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

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

@ -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,
@ -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

@ -1,5 +1,5 @@
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,
@ -7,7 +7,15 @@ import {
} 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[] = [
@ -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
)
}
}
]
}

@ -3,6 +3,7 @@ import { ZERO } from '../../dataset/constant/Common'
import { RowFlex } from '../../dataset/enum/Row'
import {
IAppendElementListOption,
IDrawFloatPayload,
IDrawOption,
IDrawPagePayload,
IDrawRowPayload,
@ -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'
@ -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
@ -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) {
@ -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)
@ -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]
@ -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)
// 渲染元素
@ -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)

@ -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'
@ -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>) {
@ -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)

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

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

@ -1,3 +1,5 @@
import { ImageDisplay } from '../../../dataset/enum/Common'
import { ElementType } from '../../../dataset/enum/Element'
import { CanvasEvent } from '../CanvasEvent'
export function mousemove(evt: MouseEvent, host: CanvasEvent) {
@ -22,6 +24,19 @@ export function mousemove(evt: MouseEvent, host: CanvasEvent) {
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.isAllowDrop = true
return

@ -1,4 +1,5 @@
import { EDITOR_ELEMENT_STYLE_ATTR } from '../../../dataset/constant/Element'
import { ImageDisplay } from '../../../dataset/enum/Common'
import { ControlComponent, ControlType } from '../../../dataset/enum/Control'
import { ElementType } from '../../../dataset/enum/Element'
import { IElement } from '../../../interface/Element'
@ -18,6 +19,28 @@ function getElementIndexByDragId(dragId: string, elementList: IElement[]) {
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) {
// 判断是否允许拖放
if (host.isAllowDrop) {
@ -42,13 +65,34 @@ export function mouseup(evt: MouseEvent, host: CanvasEvent) {
range.startIndex >= cacheStartIndex &&
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({
...cacheRange
})
draw.render({
isSetCursor: false,
isCompute: false,
isSubmitHistory: false
isSubmitHistory
})
return
}
@ -207,11 +251,35 @@ export function mouseup(evt: MouseEvent, host: CanvasEvent) {
range.startTrIndex,
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({
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) {
// 如果是允许拖拽不允许拖放则光标重置
host.mousedown(evt)

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

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

@ -1,10 +1,12 @@
import { ElementType, RowFlex, VerticalAlign } from '../..'
import { ZERO } from '../../dataset/constant/Common'
import { ControlComponent, ImageDisplay } from '../../dataset/enum/Control'
import { ControlComponent } from '../../dataset/enum/Control'
import {
IComputePageRowPositionPayload,
IComputePageRowPositionResult,
IComputeRowPositionPayload
IComputeRowPositionPayload,
IFloatPosition,
IGetFloatPositionByXYPayload
} from '../../interface/Position'
import { IEditorOption } from '../../interface/Editor'
import { IElement, IElementPosition } from '../../interface/Element'
@ -16,17 +18,20 @@ import {
import { Draw } from '../draw/Draw'
import { EditorMode, EditorZone } from '../../dataset/enum/Editor'
import { deepClone } from '../../utils'
import { ImageDisplay } from '../../dataset/enum/Common'
export class Position {
private cursorPosition: IElementPosition | null
private positionContext: IPositionContext
private positionList: IElementPosition[]
private floatPositionList: IFloatPosition[]
private draw: Draw
private options: Required<IEditorOption>
constructor(draw: Draw) {
this.positionList = []
this.floatPositionList = []
this.cursorPosition = null
this.positionContext = {
isTable: false,
@ -37,6 +42,10 @@ export class Position {
this.options = draw.getOptions()
}
public getFloatPositionList(): IFloatPosition[] {
return this.floatPositionList
}
public getTablePositionList(
sourceElementList: IElement[]
): IElementPosition[] {
@ -149,6 +158,28 @@ export class Position {
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)
index++
x += metrics.width
@ -170,7 +201,11 @@ export class Position {
startIndex: 0,
startX: (td.x! + tdPadding[3]) * scale + tablePreX,
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 (
@ -217,6 +252,7 @@ export class Position {
public computePositionList() {
// 置空原位置信息
this.positionList = []
this.floatPositionList = []
// 按每页行计算
const innerWidth = this.draw.getInnerWidth()
const pageRowList = this.draw.getPageRowList()
@ -291,6 +327,15 @@ export class Position {
const curPageNo = payload.pageNo ?? this.draw.getPageNo()
const isMainActive = zoneManager.isMainActive()
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++) {
const {
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 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(
payload: IGetPositionByXYPayload
): ICurrentPosition | null {

@ -23,7 +23,9 @@ export const INTERNAL_CONTEXT_MENU_KEY = {
SAVE_AS: 'imageSaveAs',
TEXT_WRAP: 'imageTextWrap',
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: {
BORDER: 'border',

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

@ -8,3 +8,10 @@ export enum NumberType {
ARABIC = 'arabic',
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'
}
export enum ImageDisplay {
INLINE = 'inline',
BLOCK = 'block'
}
// 控件内容缩进方式
export enum ControlIndentation {
ROW_START = 'rowStart', // 从行起始位置缩进

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

@ -1,3 +1,4 @@
import { ImageDisplay } from '../dataset/enum/Common'
import { EditorMode, EditorZone } from '../dataset/enum/Editor'
import { IElement, IElementPosition } from './Element'
import { IRow } from './Row'
@ -32,6 +33,11 @@ export interface IDrawRowPayload {
zone?: EditorZone
}
export interface IDrawFloatPayload {
pageNo: number
imgDisplay: ImageDisplay
}
export interface IDrawPagePayload {
elementList: IElement[]
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 { ListStyle, ListType } from '../dataset/enum/List'
import { RowFlex } from '../dataset/enum/Row'
@ -100,6 +101,10 @@ export interface IDateElement {
export interface IImageElement {
imgDisplay?: ImageDisplay
imgFloatPosition?: {
x: number
y: number
}
}
export interface IBlockElement {

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