jack 2 днів тому
батько
коміт
ec05cb7fe3

+ 102 - 100
src/hooks/useImport.ts

@@ -1982,119 +1982,121 @@ export default () => {
             // ---------- 形状 ----------
             // ---------- 形状 ----------
             else if (el.type === 'shape') {
             else if (el.type === 'shape') {
               if (el.shapType === 'line' || /Connector/.test(el.shapType)) {
               if (el.shapType === 'line' || /Connector/.test(el.shapType)) {
-                // 线条元素(单独处理)
-                const lineElement = parseLineElement(el, ratio)
-                slide.elements.push(lineElement)
+                //el.isFlipH = el.isFlipV = false;
+                //el.rotate = 0;
+                //   // 线条元素(单独处理)
+                //   const lineElement = parseLineElement(el, ratio)
+                //   slide.elements.push(lineElement)
               }
               }
-              else {
-                const shape = shapeList.find(item => item.pptxShapeType === el.shapType)
+              // else {
+              const shape = shapeList.find(item => item.pptxShapeType === el.shapType)
 
 
-                const vAlignMap: { [key: string]: ShapeTextAlign } = {
-                  mid: 'middle',
-                  down: 'bottom',
-                  up: 'top',
+              const vAlignMap: { [key: string]: ShapeTextAlign } = {
+                mid: 'middle',
+                down: 'bottom',
+                up: 'top',
+              }
+
+              const gradient: Gradient | undefined = el.fill?.type === 'gradient'
+                ? {
+                  type: el.fill.value.path === 'line' ? 'linear' : 'radial',
+                  colors: el.fill.value.colors.map(item => ({
+                    ...item,
+                    pos: parseInt(item.pos),
+                  })),
+                  rotate: el.fill.value.rot,
                 }
                 }
+                : undefined
 
 
-                const gradient: Gradient | undefined = el.fill?.type === 'gradient'
-                  ? {
-                    type: el.fill.value.path === 'line' ? 'linear' : 'radial',
-                    colors: el.fill.value.colors.map(item => ({
-                      ...item,
-                      pos: parseInt(item.pos),
-                    })),
-                    rotate: el.fill.value.rot,
-                  }
-                  : undefined
+              const pattern: string | undefined = el.fill?.type === 'image' ? el.fill.value.picBase64 : undefined
+              const fill = el.fill?.type === 'color' ? el.fill.value : 'none'
+              const style = getStyle(convertFontSizePtToPx(el.content, ratio, el.autoFit)) + (el.pathBBox?.pWidth ? ';width:' + (el.pathBBox?.pWidth * ratio) + 'px;height:' + (el.pathBBox?.pHeight * ratio) + 'px;' : '') // 设置字体的样式等,这里由于不支持的样式在里面会过滤
+              const element: PPTShapeElement = {
+                type: 'shape',
+                id: nanoid(10),
+                width: el.width,
+                height: el.height,
+                left: el.left,
+                top: el.top,
+                viewBox: [200, 200],
+                path: 'M 0 0 L 200 0 L 200 200 L 0 200 Z',
+                fill,
+                gradient,
+                pattern,
+                fixedRatio: false,
+                rotate: el.rotate,
+                pathBBox: el.pathBBox,
+                outline: {
+                  color: el.borderColor,
+                  width: +(el.borderWidth * ratio).toFixed(2),
+                  style: el.borderType,
+                },
+                text: {
+                  content: convertFontSizePtToPx(el.content, ratio, el.autoFit),
+                  style: style,
+                  defaultFontName: theme.value.fontName,
+                  defaultColor: theme.value.fontColor,
+                  align: vAlignMap[el.vAlign] || 'middle',
+                },
+                flipH: el.isFlipH,
+                flipV: el.isFlipV,
+              }
 
 
-                const pattern: string | undefined = el.fill?.type === 'image' ? el.fill.value.picBase64 : undefined
-                const fill = el.fill?.type === 'color' ? el.fill.value : 'none'
-                const style = getStyle(convertFontSizePtToPx(el.content, ratio, el.autoFit)) + (el.pathBBox?.pWidth ? ';width:' + (el.pathBBox?.pWidth * ratio) + 'px;height:' + (el.pathBBox?.pHeight * ratio) + 'px;' : '') // 设置字体的样式等,这里由于不支持的样式在里面会过滤
-                const element: PPTShapeElement = {
-                  type: 'shape',
-                  id: nanoid(10),
-                  width: el.width,
-                  height: el.height,
-                  left: el.left,
-                  top: el.top,
-                  viewBox: [200, 200],
-                  path: 'M 0 0 L 200 0 L 200 200 L 0 200 Z',
-                  fill,
-                  gradient,
-                  pattern,
-                  fixedRatio: false,
-                  rotate: el.rotate,
-                  pathBBox: el.pathBBox,
-                  outline: {
-                    color: el.borderColor,
-                    width: +(el.borderWidth * ratio).toFixed(2),
-                    style: el.borderType,
-                  },
-                  text: {
-                    content: convertFontSizePtToPx(el.content, ratio, el.autoFit),
-                    style: style,
-                    defaultFontName: theme.value.fontName,
-                    defaultColor: theme.value.fontColor,
-                    align: vAlignMap[el.vAlign] || 'middle',
-                  },
-                  flipH: el.isFlipH,
-                  flipV: el.isFlipV,
+              if (el.shadow) {
+                element.shadow = {
+                  h: el.shadow.h * ratio,
+                  v: el.shadow.v * ratio,
+                  blur: el.shadow.blur * ratio,
+                  color: el.shadow.color,
                 }
                 }
+              }
 
 
-                if (el.shadow) {
-                  element.shadow = {
-                    h: el.shadow.h * ratio,
-                    v: el.shadow.v * ratio,
-                    blur: el.shadow.blur * ratio,
-                    color: el.shadow.color,
+              if (shape) {
+                const { maxX, maxY } = getSvgPathRange(el.path)
+                element.path = el.path
+                element.viewBox = poriginWidth ? [maxX, maxY] : [originWidth, originHeight]
+                /*
+                if (shape.pathFormula) {
+                  element.pathFormula = shape.pathFormula
+                  element.viewBox = [el.width, el.height]
+                  // element.viewBox = [poriginWidth || originWidth || maxX, poriginHeight || originHeight || maxY];  
+                  const pathFormula = SHAPE_PATH_FORMULAS[shape.pathFormula]
+                  if ('editable' in pathFormula && pathFormula.editable) {
+                    element.path = pathFormula.formula(el.width, el.height, pathFormula.defaultValue)
+                    element.keypoints = pathFormula.defaultValue
                   }
                   }
-                }
-
-                if (shape) {
-                  const { maxX, maxY } = getSvgPathRange(el.path)
-                  element.path = el.path
-                  element.viewBox = poriginWidth ? [maxX, maxY] : [originWidth, originHeight]
-                  /*
-                  if (shape.pathFormula) {
-                    element.pathFormula = shape.pathFormula
-                    element.viewBox = [el.width, el.height]
-                    // element.viewBox = [poriginWidth || originWidth || maxX, poriginHeight || originHeight || maxY];  
-                    const pathFormula = SHAPE_PATH_FORMULAS[shape.pathFormula]
-                    if ('editable' in pathFormula && pathFormula.editable) {
-                      element.path = pathFormula.formula(el.width, el.height, pathFormula.defaultValue)
-                      element.keypoints = pathFormula.defaultValue
-                    }
-                    else {
-                      element.path = pathFormula.formula(el.width, el.height)
-                    }
+                  else {
+                    element.path = pathFormula.formula(el.width, el.height)
                   }
                   }
-                  */
-                }
-                else if (el.path && el.path.indexOf('NaN') === -1) {
-                  const { maxX, maxY } = getSvgPathRange(el.path)
-                  element.path = el.path
-                  element.viewBox = poriginWidth ? [maxX, maxY] : [originWidth, originHeight]
-                  // element.viewBox = [originWidth || maxX, originHeight || maxY];  
-                  // element.viewBox = originWidth? [(originWidth/(poriginWidth||1)), (originHeight/(poriginHeight||1))] : [maxX, maxY];
-                  // element.viewBox = [poriginWidth || maxX, poriginHeight || maxY];
                 }
                 }
+                */
+              }
+              else if (el.path && el.path.indexOf('NaN') === -1) {
+                const { maxX, maxY } = getSvgPathRange(el.path)
+                element.path = el.path
+                element.viewBox = poriginWidth ? [maxX, maxY] : [originWidth, originHeight]
+                // element.viewBox = [originWidth || maxX, originHeight || maxY];  
+                // element.viewBox = originWidth? [(originWidth/(poriginWidth||1)), (originHeight/(poriginHeight||1))] : [maxX, maxY];
+                // element.viewBox = [poriginWidth || maxX, poriginHeight || maxY];
+              }
 
 
-                if (el.shapType === 'custom') {
-                  if (el.path!.indexOf('NaN') !== -1) {
-                    if (element.width === 0) element.width = 0.1
-                    if (element.height === 0) element.height = 0.1
-                    element.path = el.path!.replace(/NaN/g, '0')
-                  }
-                  const { maxX, maxY } = getSvgPathRange(element.path)
-                  element.viewBox = poriginWidth ? [maxX, maxY] : [originWidth, originHeight]
-                  // element.viewBox = [originWidth || maxX, originHeight || maxY];  
-                  // element.viewBox = [poriginWidth || originWidth || maxX, poriginHeight || originHeight || maxY];  
-                  // element.viewBox = [poriginWidth || originWidth || maxX, poriginHeight || originHeight || maxY];  
-                  // element.viewBox = [Math.max(maxX, originWidth), Math.max(maxY, originHeight)];
-                  // element.viewBox = [originWidth, originHeight];
+              if (el.shapType === 'custom') {
+                if (el.path!.indexOf('NaN') !== -1) {
+                  if (element.width === 0) element.width = 0.1
+                  if (element.height === 0) element.height = 0.1
+                  element.path = el.path!.replace(/NaN/g, '0')
                 }
                 }
-
-                if (element.path) slide.elements.push(element)
+                const { maxX, maxY } = getSvgPathRange(element.path)
+                element.viewBox = poriginWidth ? [maxX, maxY] : [originWidth, originHeight]
+                // element.viewBox = [originWidth || maxX, originHeight || maxY];  
+                // element.viewBox = [poriginWidth || originWidth || maxX, poriginHeight || originHeight || maxY];  
+                // element.viewBox = [poriginWidth || originWidth || maxX, poriginHeight || originHeight || maxY];  
+                // element.viewBox = [Math.max(maxX, originWidth), Math.max(maxY, originHeight)];
+                // element.viewBox = [originWidth, originHeight];
               }
               }
+
+              if (element.path) slide.elements.push(element)
+              //}
             }
             }
 
 
             // ---------- 表格 ----------
             // ---------- 表格 ----------

+ 34 - 11
src/views/components/element/ShapeElement/index.vue

@@ -12,16 +12,13 @@
       height: elementInfo.height + 'px',
       height: elementInfo.height + 'px',
     }"
     }"
   >
   >
-    <div
-      class="rotate-wrapper"
-      :style="{ transform: `rotate(${elementInfo.rotate}deg)` }"
-    >
+    <div class="rotate-wrapper">
       <div
       <div
         class="element-content"
         class="element-content"
         :style="{
         :style="{
           opacity: elementInfo.opacity,
           opacity: elementInfo.opacity,
           filter: shadowStyle ? `drop-shadow(${shadowStyle})` : '',
           filter: shadowStyle ? `drop-shadow(${shadowStyle})` : '',
-          //transform: flipStyle,
+          transform: flipStyle,
           color: text.defaultColor,
           color: text.defaultColor,
           fontFamily: text.defaultFontName,
           fontFamily: text.defaultFontName,
         }"
         }"
@@ -50,11 +47,7 @@
               :rotate="elementInfo.gradient.rotate"
               :rotate="elementInfo.gradient.rotate"
             />
             />
           </defs>
           </defs>
-          <g
-            :transform="`scale(${elementInfo.width / elementInfo.viewBox[0]}, ${
-              elementInfo.height / elementInfo.viewBox[1]
-            }) translate(0,0) matrix(1,0,0,1,0,0)`"
-          >
+          <g :transform="shapeTransform">
             <path
             <path
               class="shape-path"
               class="shape-path"
               vector-effect="non-scaling-stroke"
               vector-effect="non-scaling-stroke"
@@ -148,6 +141,34 @@ const execFormatPainter = () => {
   if (!keep) mainStore.setShapeFormatPainter(null);
   if (!keep) mainStore.setShapeFormatPainter(null);
 };
 };
 
 
+const shapeTransform = computed(() => {
+  {
+    const info = props.elementInfo;
+    const w = info.width;
+    const h = info.height;
+    const vb = info.viewBox;        // 假设为 [vbW, vbH]
+    const vbW = vb[0], vbH = vb[1];
+    
+    const scaleX = w / vbW;
+    const scaleY = h / vbH;
+    
+    const cx = vbW / 2, cy = vbH / 2;  // 旋转中心
+    const rot = info.rotate || 0;
+    
+    // 当前已证实可用的补偿翻转(基于镜像路径)
+    const flipX = info.flipH ? -1 : 1;
+    const flipY = info.flipV ? -1 : 1;  // 补偿后的取值
+    
+    return `
+      scale(${scaleX}, ${scaleY})
+      translate(${cx}, ${cy})
+      rotate(${rot})
+      scale(${flipX}, ${flipY})
+      translate(${-cx}, ${-cy})
+    `;
+  }
+});
+
 const element = computed(() => props.elementInfo);
 const element = computed(() => props.elementInfo);
 const { fill } = useElementFill(element, "editable");
 const { fill } = useElementFill(element, "editable");
 
 
@@ -236,7 +257,9 @@ const startEdit = () => {
   height: 100%;
   height: 100%;
 }
 }
 .element-content {
 .element-content {
-  font-family: Kaiti, "Kaiti SC", "Kaiti TC", Roboto, "Noto Sans SC", "Noto Sans TC", "Noto Sans KR", "Noto Sans JP", "Roboto", Roboto, "Noto Sans SC", "Noto Sans TC", "Noto Sans KR", "Noto Sans JP";
+  font-family: Kaiti, "Kaiti SC", "Kaiti TC", Roboto, "Noto Sans SC",
+    "Noto Sans TC", "Noto Sans KR", "Noto Sans JP", "Roboto", Roboto,
+    "Noto Sans SC", "Noto Sans TC", "Noto Sans KR", "Noto Sans JP";
   width: 100%;
   width: 100%;
   height: 100%;
   height: 100%;
   position: relative;
   position: relative;

+ 9 - 10
src/views/components/element/hooks/useElementFlip.ts

@@ -1,18 +1,17 @@
 import { computed, type Ref } from 'vue'
 import { computed, type Ref } from 'vue'
 
 
-// 计算元素的翻转样式
-export default (flipH: Ref<boolean | undefined>, flipV: Ref<boolean | undefined>) => {
-  const flipStyle = computed(() => {
-    let style = ''
-    
-    if (flipH.value && flipV.value) style = 'rotateX(180deg) rotateY(180deg)'
-    else if (flipV.value) style = 'rotateX(180deg)'
-    else if (flipH.value) style = 'rotateY(180deg)'
+export default (flipH: Ref<boolean | undefined>, flipV: Ref<boolean | undefined>, rotateDeg: Ref<number>) => {
+  const transform = computed(() => {
+    const scaleX = flipH.value ? -1 : 1
+    const scaleY = flipV.value ? -1 : 1
+    const scale = (scaleX !== 1 || scaleY !== 1) ? `scale(${scaleX}, ${scaleY})` : ''
+    const rotate = rotateDeg.value ? `rotate(${rotateDeg.value}deg)` : ''
 
 
-    return style
+    // 变换顺序:先缩放(翻转),再旋转(PPT 惯用顺序)
+    return [scale, rotate].filter(Boolean).join(' ')
   })
   })
 
 
   return {
   return {
-    flipStyle,
+    transform,
   }
   }
 }
 }