diff --git a/src/configs/shapes.ts b/src/configs/shapes.ts index e37aa921..cf6277ad 100644 --- a/src/configs/shapes.ts +++ b/src/configs/shapes.ts @@ -1,7 +1,10 @@ +import { ShapePathFormulasKeys } from '@/types/slides' + export interface ShapePoolItem { viewBox: [number, number]; path: string; special?: boolean; + pathFormula?: ShapePathFormulasKeys; } interface ShapeListItem { @@ -9,6 +12,37 @@ interface ShapeListItem { children: ShapePoolItem[]; } +export const SHAPE_PATH_FORMULAS = { + [ShapePathFormulasKeys.ROUND_RECT]: (width: number, height: number) => { + const radius = Math.min(width, height) / 8 + return `M ${radius} 0 L ${width - radius} 0 Q ${width} 0 ${width} ${radius} L ${width} ${height - radius} Q ${width} ${height} ${width - radius} ${height} L ${radius} ${height} Q 0 ${height} 0 ${height - radius} L 0 ${radius} Q 0 0 ${radius} 0 Z` + }, + [ShapePathFormulasKeys.CUT_RECT_DIAGONAL]: (width: number, height: number) => { + const radius = Math.min(width, height) / 5 + return `M 0 ${height - radius} L 0 0 L ${width - radius} 0 L ${width} ${radius} L ${width} ${height} L ${radius} ${height} Z` + }, + [ShapePathFormulasKeys.CUT_RECT_SINGLE]: (width: number, height: number) => { + const radius = Math.min(width, height) / 5 + return `M 0 ${height} L 0 0 L ${width - radius} 0 L ${width} ${radius} L ${width} ${height} Z` + }, + [ShapePathFormulasKeys.CUT_RECT_SAMESIDE]: (width: number, height: number) => { + const radius = Math.min(width, height) / 5 + return `M 0 ${radius} L ${radius} 0 L ${width - radius} 0 L ${width} ${radius} L ${width} ${height} L 0 ${height} Z` + }, + [ShapePathFormulasKeys.ROUND_RECT_DIAGONAL]: (width: number, height: number) => { + const radius = Math.min(width, height) / 8 + return `M 0 0 L ${width - radius} 0 Q ${width} 0 ${width} ${radius} L ${width} ${height} L ${radius} ${height} Q 0 ${height} 0 ${height - radius} L 0 0 Z` + }, + [ShapePathFormulasKeys.ROUND_RECT_SINGLE]: (width: number, height: number) => { + const radius = Math.min(width, height) / 8 + return `M 0 0 L ${width - radius} 0 Q ${width} 0 ${width} ${radius} L ${width} ${height} L 0 ${height} L 0 0 Z` + }, + [ShapePathFormulasKeys.ROUND_RECT_SAMESIDE]: (width: number, height: number) => { + const radius = Math.min(width, height) / 8 + return `M 0 ${radius} Q 0 0 ${radius} 0 L ${width - radius} 0 Q ${width} 0 ${width} ${radius} L ${width} ${height} L 0 ${height} Z` + }, +} + export const SHAPE_LIST: ShapeListItem[] = [ { type: '矩形', @@ -19,31 +53,38 @@ export const SHAPE_LIST: ShapeListItem[] = [ }, { viewBox: [200, 200], - path: 'M 20 0 L 180 0 Q 200 0 200 20 L 200 180 Q 200 200 180 200 L 20 200 Q 0 200 0 180 L 0 20 Q 0 0 20 0 Z' + path: 'M 50 0 L 150 0 Q 200 0 200 50 L 200 150 Q 200 200 150 200 L 50 200 Q 0 200 0 150 L 0 50 Q 0 0 50 0 Z', + pathFormula: ShapePathFormulasKeys.ROUND_RECT, }, { viewBox: [200, 200], - path: 'M 0 150 L 0 0 L 150 0 L 200 50 L 200 200 L 50 200 L 0 150 Z' + path: 'M 0 150 L 0 0 L 150 0 L 200 50 L 200 200 L 50 200 Z', + pathFormula: ShapePathFormulasKeys.CUT_RECT_DIAGONAL, }, { viewBox: [200, 200], - path: 'M 0 200 L 0 0 L 150 0 L 200 50 L 200 200 L 0 200' + path: 'M 0 200 L 0 0 L 150 0 L 200 50 L 200 200 Z', + pathFormula: ShapePathFormulasKeys.CUT_RECT_SINGLE, }, { viewBox: [200, 200], - path: 'M 0 50 L 50 0 L 150 0 L 200 50 L 200 200 L 0 200 L 0 50 Z' + path: 'M 0 50 L 50 0 L 150 0 L 200 50 L 200 200 L 0 200 Z', + pathFormula: ShapePathFormulasKeys.CUT_RECT_SAMESIDE, }, { viewBox: [200, 200], - path: 'M 0 0 L 140 0 Q 200 0 200 60 L 200 200 L 60 200 Q 0 200 0 140 L 0 0 Z' + path: 'M 0 0 L 150 0 Q 200 0 200 50 L 200 200 L 50 200 Q 0 200 0 150 L 0 0 Z', + pathFormula: ShapePathFormulasKeys.ROUND_RECT_DIAGONAL, }, { viewBox: [200, 200], - path: 'M 0 0 L 140 0 Q 200 0 200 60 L 200 200 L 0 200 L 0 0 Z' + path: 'M 0 0 L 150 0 Q 200 0 200 50 L 200 200 L 0 200 L 0 0 Z', + pathFormula: ShapePathFormulasKeys.ROUND_RECT_SINGLE, }, { viewBox: [200, 200], - path: 'M 0 50 Q 0 0 50 0 L 150 0 Q 200 0 200 50 L 200 200 L 0 200 L 0 50 Z' + path: 'M 0 50 Q 0 0 50 0 L 150 0 Q 200 0 200 50 L 200 200 L 0 200 Z', + pathFormula: ShapePathFormulasKeys.ROUND_RECT_SAMESIDE, }, ] }, diff --git a/src/hooks/useCreateElement.ts b/src/hooks/useCreateElement.ts index 1088eaaf..e8f8d553 100644 --- a/src/hooks/useCreateElement.ts +++ b/src/hooks/useCreateElement.ts @@ -4,7 +4,7 @@ import { createRandomCode } from '@/utils/common' import { getImageSize } from '@/utils/image' import { VIEWPORT_SIZE } from '@/configs/canvas' import { PPTLineElement, ChartType, PPTElement, TableCell, TableCellStyle, PPTShapeElement } from '@/types/slides' -import { ShapePoolItem } from '@/configs/shapes' +import { ShapePoolItem, SHAPE_PATH_FORMULAS } from '@/configs/shapes' import { LinePoolItem } from '@/configs/lines' import useHistorySnapshot from '@/hooks/useHistorySnapshot' @@ -195,6 +195,11 @@ export default () => { rotate: 0, } if (data.special) newElement.special = true + if (data.pathFormula) { + newElement.pathFormula = data.pathFormula + newElement.viewBox = [width, height] + newElement.path = SHAPE_PATH_FORMULAS[data.pathFormula](width, height) + } createElement(newElement) } diff --git a/src/types/slides.ts b/src/types/slides.ts index 3e2d9c0e..baf84d6e 100644 --- a/src/types/slides.ts +++ b/src/types/slides.ts @@ -1,5 +1,15 @@ import { IBarChartOptions, ILineChartOptions, IPieChartOptions } from 'chartist' +export const enum ShapePathFormulasKeys { + ROUND_RECT = 'roundRect', + ROUND_RECT_DIAGONAL = 'roundRectDiagonal', + ROUND_RECT_SINGLE = 'roundRectSingle', + ROUND_RECT_SAMESIDE = 'roundRectSameSide', + CUT_RECT_DIAGONAL = 'cutRectDiagonal', + CUT_RECT_SINGLE = 'cutRectSingle', + CUT_RECT_SAMESIDE = 'cutRectSameSide', +} + export const enum ElementTypes { TEXT = 'text', IMAGE = 'image', @@ -277,6 +287,10 @@ export interface ShapeText { * special?: 特殊形状(标记一些难以解析的形状,例如路径使用了 L Q C A 以外的类型,该类形状在导出后将变为图片的形式) * * text?: 形状内文本 + * + * pathFormula?: 形状路径计算公式 + * 一般情况下,形状的大小变化时仅由宽高基于 viewBox 的缩放比例来调整形状,而 viewBox 本身和 path 不会变化, + * 但也有一些形状希望能更精确的控制一些关键点的位置,此时就需要提供路径计算公式,通过在缩放时更新 viewBox 并重新计算 path 来重新绘制形状 */ export interface PPTShapeElement extends PPTBaseElement { type: 'shape'; @@ -292,6 +306,7 @@ export interface PPTShapeElement extends PPTBaseElement { shadow?: PPTElementShadow; special?: boolean; text?: ShapeText; + pathFormula?: ShapePathFormulasKeys; } diff --git a/src/views/Editor/Canvas/hooks/useScaleElement.ts b/src/views/Editor/Canvas/hooks/useScaleElement.ts index d3ff3d27..e6383a35 100644 --- a/src/views/Editor/Canvas/hooks/useScaleElement.ts +++ b/src/views/Editor/Canvas/hooks/useScaleElement.ts @@ -5,6 +5,7 @@ import { PPTElement, PPTImageElement, PPTLineElement, PPTShapeElement } from '@/ import { OperateResizeHandlers, OperateResizeHandler, AlignmentLineProps, MultiSelectRange } from '@/types/edit' import { VIEWPORT_SIZE } from '@/configs/canvas' import { MIN_SIZE } from '@/configs/element' +import { SHAPE_PATH_FORMULAS } from '@/configs/shapes' import { AlignLine, uniqAlignLines } from '@/utils/element' import useHistorySnapshot from '@/hooks/useHistorySnapshot' @@ -393,7 +394,17 @@ export default ( } } - elementList.value = elementList.value.map(el => element.id === el.id ? { ...el, left, top, width, height } : el) + elementList.value = elementList.value.map(el => { + if (element.id !== el.id) return el + if (el.type === 'shape' && 'pathFormula' in el && el.pathFormula) { + return { + ...el, left, top, width, height, + viewBox: [width, height], + path: SHAPE_PATH_FORMULAS[el.pathFormula](width, height), + } + } + return { ...el, left, top, width, height } + }) } document.onmouseup = e => {