Commit 683c079d authored by zhengke's avatar zhengke

优化字体放大缩小

parent af5295e4
......@@ -26,6 +26,17 @@ export const enum OperateBorderLines {
R = 'right',
}
export const enum OperateTesizeHandlers {
LEFT_TOP = 'left-top',
// TOP = 'top',
RIGHT_TOP = 'right-top',
// LEFT = 'left',
// RIGHT = 'right',
LEFT_BOTTOM = 'left-bottom',
// BOTTOM = 'bottom',
RIGHT_BOTTOM = 'right-bottom',
}
export const enum OperateResizeHandlers {
LEFT_TOP = 'left-top',
TOP = 'top',
......
......@@ -22,6 +22,15 @@
:style="{ left: scaleWidth / 2 + 'px' }"
@mousedown.stop="$event => rotateElement($event, elementInfo)"
/>
<TextsizeHandler
class="operate-resize-handler"
v-for="point in tesizeHandlers"
:key="point.direction"
:type="point.direction"
:rotate="elementInfo.rotate"
:style="point.style"
@mousedown.stop="$event => scaleElement($event, elementInfo, point.direction)"
/>
</template>
</div>
</template>
......@@ -37,11 +46,12 @@ import { computed } from 'vue'
import { storeToRefs } from 'pinia'
import { useMainStore } from '@/store'
import type { PPTTextElement } from '@/types/slides'
import type { OperateResizeHandlers } from '@/types/edit'
import type { OperateResizeHandlers, OperateTesizeHandlers } from '@/types/edit'
import useCommonOperate from '../hooks/useCommonOperate'
import RotateHandler from './RotateHandler.vue'
import ResizeHandler from './ResizeHandler.vue'
import TextsizeHandler from './TextsizeHandler.vue'
import BorderLine from './BorderLine.vue'
const props = defineProps<{
......@@ -49,6 +59,7 @@ const props = defineProps<{
handlerVisible: boolean
rotateElement: (e: MouseEvent, element: PPTTextElement) => void
scaleElement: (e: MouseEvent, element: PPTTextElement, command: OperateResizeHandlers) => void
scaleText: (e: MouseEvent, element: PPTTextElement, command: OperateTesizeHandlers) => void
}>()
const { canvasScale } = storeToRefs(useMainStore())
......@@ -56,6 +67,8 @@ const { canvasScale } = storeToRefs(useMainStore())
const scaleWidth = computed(() => props.elementInfo.width * canvasScale.value)
const scaleHeight = computed(() => props.elementInfo.height * canvasScale.value)
const { textElementResizeHandlers, verticalTextElementResizeHandlers, borderLines } = useCommonOperate(scaleWidth, scaleHeight)
const { textElementResizeHandlers, verticalTextElementResizeHandlers, borderLines, textElementTesizeHandlers } = useCommonOperate(scaleWidth, scaleHeight)
const resizeHandlers = computed(() => props.elementInfo.vertical ? verticalTextElementResizeHandlers.value : textElementResizeHandlers.value)
const tesizeHandlers = computed(() => props.elementInfo.vertical ? textElementTesizeHandlers.value : textElementTesizeHandlers.value)
console.log(textElementTesizeHandlers.value,'---------')
</script>
\ No newline at end of file
......@@ -16,6 +16,7 @@
:handlerVisible="!elementInfo.lock && (isActiveGroupElement || !isMultiSelect)"
:rotateElement="rotateElement"
:scaleElement="scaleElement"
:scaleText="scaleText"
:dragLineElement="dragLineElement"
:moveShapeKeypoint="moveShapeKeypoint"
></component>
......@@ -50,7 +51,7 @@ import {
type PPTShapeElement,
type PPTChartElement,
} from '@/types/slides'
import type { OperateLineHandlers, OperateResizeHandlers } from '@/types/edit'
import type { OperateLineHandlers, OperateResizeHandlers, OperateTesizeHandlers } from '@/types/edit'
import ImageElementOperate from './ImageElementOperate.vue'
import TextElementOperate from './TextElementOperate.vue'
......@@ -68,6 +69,7 @@ const props = defineProps<{
isMultiSelect: boolean
rotateElement: (e: MouseEvent, element: Exclude<PPTElement, PPTChartElement | PPTLineElement | PPTVideoElement | PPTAudioElement>) => void
scaleElement: (e: MouseEvent, element: Exclude<PPTElement, PPTLineElement>, command: OperateResizeHandlers) => void
scaleText: (e: MouseEvent, element: Exclude<PPTElement, PPTLineElement>, command: OperateTesizeHandlers) => void
dragLineElement: (e: MouseEvent, element: PPTLineElement, command: OperateLineHandlers) => void
moveShapeKeypoint: (e: MouseEvent, element: PPTShapeElement) => void
openLinkDialog: () => void
......
......@@ -23,6 +23,18 @@ export default (width: Ref<number>, height: Ref<number>) => {
{ direction: OperateResizeHandlers.RIGHT, style: {left: width.value + 'px', top: height.value / 2 + 'px'} },
]
})
const textElementTesizeHandlers = computed(() => {
return [
{ direction: OperateResizeHandlers.LEFT_TOP, style: {} },
// { direction: OperateResizeHandlers.TOP, style: {left: width.value / 2 + 'px'} },
{ direction: OperateResizeHandlers.RIGHT_TOP, style: {left: width.value + 'px'} },
// { direction: OperateResizeHandlers.LEFT, style: {top: height.value / 2 + 'px'} },
// { direction: OperateResizeHandlers.RIGHT, style: {left: width.value + 'px', top: height.value / 2 + 'px'} },
{ direction: OperateResizeHandlers.LEFT_BOTTOM, style: {top: height.value + 'px'} },
// { direction: OperateResizeHandlers.BOTTOM, style: {left: width.value / 2 + 'px', top: height.value + 'px'} },
{ direction: OperateResizeHandlers.RIGHT_BOTTOM, style: {left: width.value + 'px', top: height.value + 'px'} },
]
})
const verticalTextElementResizeHandlers = computed(() => {
return [
{ direction: OperateResizeHandlers.TOP, style: {left: width.value / 2 + 'px'} },
......@@ -45,5 +57,6 @@ export default (width: Ref<number>, height: Ref<number>) => {
textElementResizeHandlers,
verticalTextElementResizeHandlers,
borderLines,
textElementTesizeHandlers,
}
}
\ No newline at end of file
......@@ -106,6 +106,359 @@ export default (
const { addHistorySnapshot } = useHistorySnapshot()
// 缩放元素
const scaleText = (e: MouseEvent | TouchEvent, element: Exclude<PPTElement, PPTLineElement>, command: OperateResizeHandlers) => {
const isTouchEvent = !(e instanceof MouseEvent)
if (isTouchEvent && (!e.changedTouches || !e.changedTouches[0])) return
let isMouseDown = true
mainStore.setScalingState(true)
const elOriginLeft = element.left
const elOriginTop = element.top
const elOriginWidth = element.width
const elOriginHeight = element.height
const originTableCellMinHeight = element.type === 'table' ? element.cellMinHeight : 0
const elRotate = ('rotate' in element && element.rotate) ? element.rotate : 0
const rotateRadian = Math.PI * elRotate / 180
const fixedRatio = ctrlOrShiftKeyActive.value || ('fixedRatio' in element && element.fixedRatio)
const aspectRatio = elOriginWidth / elOriginHeight
const startPageX = isTouchEvent ? e.changedTouches[0].pageX : e.pageX
const startPageY = isTouchEvent ? e.changedTouches[0].pageY : e.pageY
// 元素最小缩放限制
const minSize = MIN_SIZE[element.type] || 20
const getSizeWithinRange = (size: number) => size < minSize ? minSize : size
let points: ReturnType<typeof getRotateElementPoints>
let baseLeft = 0
let baseTop = 0
let horizontalLines: AlignLine[] = []
let verticalLines: AlignLine[] = []
// 旋转后的元素进行缩放时,引入基点的概念,以当前操作的缩放点相对的点为基点
// 例如拖动右下角缩放时,左上角为基点,需要保持左上角不变然后修改其他的点的位置来达到所放的效果
if ('rotate' in element && element.rotate) {
const { left, top, width, height } = element
points = getRotateElementPoints({ left, top, width, height }, elRotate)
const oppositePoint = getOppositePoint(command, points)
baseLeft = oppositePoint.left
baseTop = oppositePoint.top
}
// 未旋转的元素具有缩放时的对齐吸附功能,在此处收集对齐对齐吸附线
// 包括页面内除目标元素外的其他元素在画布中的各个可吸附对齐位置:上下左右四边
// 其中线条和被旋转过的元素不参与吸附对齐
else {
const edgeWidth = VIEWPORT_SIZE.Value
const edgeHeight = VIEWPORT_SIZE.Value * viewportRatio.value
const isActiveGroupElement = element.id === activeGroupElementId.value
for (const el of elementList.value) {
if ('rotate' in el && el.rotate) continue
if (el.type === 'line') continue
if (isActiveGroupElement && el.id === element.id) continue
if (!isActiveGroupElement && activeElementIdList.value.includes(el.id)) continue
const left = el.left
const top = el.top
const width = el.width
const height = el.height
const right = left + width
const bottom = top + height
const topLine: AlignLine = { value: top, range: [left, right] }
const bottomLine: AlignLine = { value: bottom, range: [left, right] }
const leftLine: AlignLine = { value: left, range: [top, bottom] }
const rightLine: AlignLine = { value: right, range: [top, bottom] }
horizontalLines.push(topLine, bottomLine)
verticalLines.push(leftLine, rightLine)
}
// 画布可视区域的四个边界、水平中心、垂直中心
const edgeTopLine: AlignLine = { value: 0, range: [0, edgeWidth] }
const edgeBottomLine: AlignLine = { value: edgeHeight, range: [0, edgeWidth] }
const edgeHorizontalCenterLine: AlignLine = { value: edgeHeight / 2, range: [0, edgeWidth] }
const edgeLeftLine: AlignLine = { value: 0, range: [0, edgeHeight] }
const edgeRightLine: AlignLine = { value: edgeWidth, range: [0, edgeHeight] }
const edgeVerticalCenterLine: AlignLine = { value: edgeWidth / 2, range: [0, edgeHeight] }
horizontalLines.push(edgeTopLine, edgeBottomLine, edgeHorizontalCenterLine)
verticalLines.push(edgeLeftLine, edgeRightLine, edgeVerticalCenterLine)
horizontalLines = uniqAlignLines(horizontalLines)
verticalLines = uniqAlignLines(verticalLines)
}
// 对齐吸附方法
// 将收集到的对齐吸附线与计算的目标元素当前的位置大小相关数据做对比,差值小于设定的值时执行自动缩放校正
// 水平和垂直两个方向需要分开计算
const alignedAdsorption = (currentX: number | null, currentY: number | null) => {
const sorptionRange = 5
const _alignmentLines: AlignmentLineProps[] = []
let isVerticalAdsorbed = false
let isHorizontalAdsorbed = false
const correctionVal = { offsetX: 0, offsetY: 0 }
if (currentY || currentY === 0) {
for (let i = 0; i < horizontalLines.length; i++) {
const { value, range } = horizontalLines[i]
const min = Math.min(...range, currentX || 0)
const max = Math.max(...range, currentX || 0)
if (Math.abs(currentY - value) < sorptionRange && !isHorizontalAdsorbed) {
correctionVal.offsetY = currentY - value
isHorizontalAdsorbed = true
_alignmentLines.push({ type: 'horizontal', axis: {x: min - 50, y: value}, length: max - min + 100 })
}
}
}
if (currentX || currentX === 0) {
for (let i = 0; i < verticalLines.length; i++) {
const { value, range } = verticalLines[i]
const min = Math.min(...range, (currentY || 0))
const max = Math.max(...range, (currentY || 0))
if (Math.abs(currentX - value) < sorptionRange && !isVerticalAdsorbed) {
correctionVal.offsetX = currentX - value
isVerticalAdsorbed = true
_alignmentLines.push({ type: 'vertical', axis: {x: value, y: min - 50}, length: max - min + 100 })
}
}
}
alignmentLines.value = _alignmentLines
return correctionVal
}
const handleMousemove = (e: MouseEvent | TouchEvent) => {
if (!isMouseDown) return
const currentPageX = e instanceof MouseEvent ? e.pageX : e.changedTouches[0].pageX
const currentPageY = e instanceof MouseEvent ? e.pageY : e.changedTouches[0].pageY
const x = currentPageX - startPageX
const y = currentPageY - startPageY
let width = elOriginWidth
let height = elOriginHeight
let left = elOriginLeft
let top = elOriginTop
// 元素被旋转的情况下,需要根据元素旋转的角度,重新计算需要缩放的距离(鼠标按下后移动的距离)
if (elRotate) {
const revisedX = (Math.cos(rotateRadian) * x + Math.sin(rotateRadian) * y) / canvasScale.value
let revisedY = (Math.cos(rotateRadian) * y - Math.sin(rotateRadian) * x) / canvasScale.value
// 锁定宽高比例(仅四个角可能触发,四条边不会触发)
// 以水平方向上缩放的距离为基础,计算垂直方向上的缩放距离,保持二者具有相同的缩放比例
if (fixedRatio) {
if (command === OperateResizeHandlers.RIGHT_BOTTOM || command === OperateResizeHandlers.LEFT_TOP) revisedY = revisedX / aspectRatio
if (command === OperateResizeHandlers.LEFT_BOTTOM || command === OperateResizeHandlers.RIGHT_TOP) revisedY = -revisedX / aspectRatio
}
// 根据不同的操作点分别计算元素缩放后的大小和位置
// 需要注意:
// 此处计算的位置需要在后面重新进行校正,因为旋转后再缩放事实上会改变元素基点的位置(虽然视觉上基点保持不动,但这是【旋转】+【移动】共同作用的结果)
// 但此处计算的大小不需要重新校正,因为前面已经重新计算需要缩放的距离,相当于大小已经经过了校正
if (command === OperateResizeHandlers.RIGHT_BOTTOM) {
width = getSizeWithinRange(elOriginWidth + revisedX)
height = getSizeWithinRange(elOriginHeight + revisedY)
}
else if (command === OperateResizeHandlers.LEFT_BOTTOM) {
width = getSizeWithinRange(elOriginWidth - revisedX)
height = getSizeWithinRange(elOriginHeight + revisedY)
left = elOriginLeft - (width - elOriginWidth)
}
else if (command === OperateResizeHandlers.LEFT_TOP) {
width = getSizeWithinRange(elOriginWidth - revisedX)
height = getSizeWithinRange(elOriginHeight - revisedY)
left = elOriginLeft - (width - elOriginWidth)
top = elOriginTop - (height - elOriginHeight)
}
else if (command === OperateResizeHandlers.RIGHT_TOP) {
width = getSizeWithinRange(elOriginWidth + revisedX)
height = getSizeWithinRange(elOriginHeight - revisedY)
top = elOriginTop - (height - elOriginHeight)
}
else if (command === OperateResizeHandlers.TOP) {
height = getSizeWithinRange(elOriginHeight - revisedY)
top = elOriginTop - (height - elOriginHeight)
}
else if (command === OperateResizeHandlers.BOTTOM) {
height = getSizeWithinRange(elOriginHeight + revisedY)
}
else if (command === OperateResizeHandlers.LEFT) {
width = getSizeWithinRange(elOriginWidth - revisedX)
left = elOriginLeft - (width - elOriginWidth)
}
else if (command === OperateResizeHandlers.RIGHT) {
width = getSizeWithinRange(elOriginWidth + revisedX)
}
// 获取当前元素的基点坐标,与初始状态时的基点坐标进行对比,并计算差值进行元素位置的校正
const currentPoints = getRotateElementPoints({ width, height, left, top }, elRotate)
const currentOppositePoint = getOppositePoint(command, currentPoints)
const currentBaseLeft = currentOppositePoint.left
const currentBaseTop = currentOppositePoint.top
const offsetX = currentBaseLeft - baseLeft
const offsetY = currentBaseTop - baseTop
left = left - offsetX
top = top - offsetY
}
// 元素未被旋转的情况下,正常计算新的位置大小即可,无需复杂的校正等工作
// 额外需要处理对齐吸附相关的操作
// 锁定宽高比例相关的操作同上,不再赘述
else {
let moveX = x / canvasScale.value
let moveY = y / canvasScale.value
if (fixedRatio) {
if (command === OperateResizeHandlers.RIGHT_BOTTOM || command === OperateResizeHandlers.LEFT_TOP) moveY = moveX / aspectRatio
if (command === OperateResizeHandlers.LEFT_BOTTOM || command === OperateResizeHandlers.RIGHT_TOP) moveY = -moveX / aspectRatio
}
if (command === OperateResizeHandlers.RIGHT_BOTTOM) {
const { offsetX, offsetY } = alignedAdsorption(elOriginLeft + elOriginWidth + moveX, elOriginTop + elOriginHeight + moveY)
moveX = moveX - offsetX
moveY = moveY - offsetY
if (fixedRatio) {
if (offsetY) moveX = moveY * aspectRatio
else moveY = moveX / aspectRatio
}
width = getSizeWithinRange(elOriginWidth + moveX)
height = getSizeWithinRange(elOriginHeight + moveY)
}
else if (command === OperateResizeHandlers.LEFT_BOTTOM) {
const { offsetX, offsetY } = alignedAdsorption(elOriginLeft + moveX, elOriginTop + elOriginHeight + moveY)
moveX = moveX - offsetX
moveY = moveY - offsetY
if (fixedRatio) {
if (offsetY) moveX = -moveY * aspectRatio
else moveY = -moveX / aspectRatio
}
width = getSizeWithinRange(elOriginWidth - moveX)
height = getSizeWithinRange(elOriginHeight + moveY)
left = elOriginLeft - (width - elOriginWidth)
}
else if (command === OperateResizeHandlers.LEFT_TOP) {
const { offsetX, offsetY } = alignedAdsorption(elOriginLeft + moveX, elOriginTop + moveY)
moveX = moveX - offsetX
moveY = moveY - offsetY
if (fixedRatio) {
if (offsetY) moveX = moveY * aspectRatio
else moveY = moveX / aspectRatio
}
width = getSizeWithinRange(elOriginWidth - moveX)
height = getSizeWithinRange(elOriginHeight - moveY)
left = elOriginLeft - (width - elOriginWidth)
top = elOriginTop - (height - elOriginHeight)
}
else if (command === OperateResizeHandlers.RIGHT_TOP) {
const { offsetX, offsetY } = alignedAdsorption(elOriginLeft + elOriginWidth + moveX, elOriginTop + moveY)
moveX = moveX - offsetX
moveY = moveY - offsetY
if (fixedRatio) {
if (offsetY) moveX = -moveY * aspectRatio
else moveY = -moveX / aspectRatio
}
width = getSizeWithinRange(elOriginWidth + moveX)
height = getSizeWithinRange(elOriginHeight - moveY)
top = elOriginTop - (height - elOriginHeight)
}
else if (command === OperateResizeHandlers.LEFT) {
const { offsetX } = alignedAdsorption(elOriginLeft + moveX, null)
moveX = moveX - offsetX
width = getSizeWithinRange(elOriginWidth - moveX)
left = elOriginLeft - (width - elOriginWidth)
}
else if (command === OperateResizeHandlers.RIGHT) {
const { offsetX } = alignedAdsorption(elOriginLeft + elOriginWidth + moveX, null)
moveX = moveX - offsetX
width = getSizeWithinRange(elOriginWidth + moveX)
}
else if (command === OperateResizeHandlers.TOP) {
const { offsetY } = alignedAdsorption(null, elOriginTop + moveY)
moveY = moveY - offsetY
height = getSizeWithinRange(elOriginHeight - moveY)
top = elOriginTop - (height - elOriginHeight)
}
else if (command === OperateResizeHandlers.BOTTOM) {
const { offsetY } = alignedAdsorption(null, elOriginTop + elOriginHeight + moveY)
moveY = moveY - offsetY
height = getSizeWithinRange(elOriginHeight + moveY)
}
}
elementList.value = elementList.value.map(el => {
if (element.id !== el.id) return el
if (el.type === 'shape' && 'pathFormula' in el && el.pathFormula) {
const pathFormula = SHAPE_PATH_FORMULAS[el.pathFormula]
let path = ''
if ('editable' in pathFormula) path = pathFormula.formula(width, height, el.keypoint!)
else path = pathFormula.formula(width, height)
return {
...el, left, top, width, height,
viewBox: [width, height],
path,
}
}
if (el.type === 'table') {
let cellMinHeight = originTableCellMinHeight + (height - elOriginHeight) / el.data.length
cellMinHeight = cellMinHeight < 36 ? 36 : cellMinHeight
if (cellMinHeight === originTableCellMinHeight) return { ...el, left, width }
return {
...el, left, top, width, height,
cellMinHeight: cellMinHeight < 36 ? 36 : cellMinHeight,
}
}
return { ...el, left, top, width, height }
})
}
const handleMouseup = (e: MouseEvent | TouchEvent) => {
isMouseDown = false
document.ontouchmove = null
document.ontouchend = null
document.onmousemove = null
document.onmouseup = null
alignmentLines.value = []
const currentPageX = e instanceof MouseEvent ? e.pageX : e.changedTouches[0].pageX
const currentPageY = e instanceof MouseEvent ? e.pageY : e.changedTouches[0].pageY
if (startPageX === currentPageX && startPageY === currentPageY) return
slidesStore.updateSlide({ elements: elementList.value })
mainStore.setScalingState(false)
addHistorySnapshot()
}
if (isTouchEvent) {
document.ontouchmove = handleMousemove
document.ontouchend = handleMouseup
}
else {
document.onmousemove = handleMousemove
document.onmouseup = handleMouseup
}
}
// 缩放元素
const scaleElement = (e: MouseEvent | TouchEvent, element: Exclude<PPTElement, PPTLineElement>, command: OperateResizeHandlers) => {
const isTouchEvent = !(e instanceof MouseEvent)
......@@ -565,5 +918,6 @@ export default (
return {
scaleElement,
scaleMultiElement,
scaleText,
}
}
\ No newline at end of file
......@@ -51,6 +51,7 @@
:isMultiSelect="activeElementIdList.length > 1"
:rotateElement="rotateElement"
:scaleElement="scaleElement"
:scaleText="scaleText"
:openLinkDialog="openLinkDialog"
:dragLineElement="dragLineElement"
:moveShapeKeypoint="moveShapeKeypoint"
......@@ -200,7 +201,7 @@ const { mouseSelection, mouseSelectionVisible, mouseSelectionQuadrant, updateMou
const { dragElement } = useDragElement(elementList, alignmentLines, canvasScale)
const { dragLineElement } = useDragLineElement(elementList)
const { selectElement } = useSelectElement(elementList, dragElement)
const { scaleElement, scaleMultiElement } = useScaleElement(elementList, alignmentLines, canvasScale)
const { scaleElement, scaleMultiElement, scaleText } = useScaleElement(elementList, alignmentLines, canvasScale)
const { rotateElement } = useRotateElement(elementList, viewportRef, canvasScale)
const { moveShapeKeypoint } = useMoveShapeKeypoint(elementList, canvasScale)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment