import { describe, expect, it } from 'vitest'; import { parseXml } from '../src/parser/XmlParser'; import { parseShapeNode } from '../src/model/nodes/ShapeNode'; import { renderShape } from '../src/serializer/shapeSerializer'; import { minimalCtx } from './helpers'; const NS = 'xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" ' + 'xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"'; function renderShapeXml( spPrInner: string, txBodyInner = '', rot = '', txBox = false, ext = '', ) { const xml = ` ${ext} ${spPrInner} ${txBodyInner} `; return renderShape(parseShapeNode(parseXml(xml)), minimalCtx(), 0); } describe('shapeSerializer · group 局部坐标的亚像素形状', () => { it('保留 noFill + dashed outline 的亚像素 roundRect 面积路径', async () => { const el = await renderShapeXml(` `); if (el.type !== 'shape') { throw new Error(`Expected shape element, got ${el.type}`); } expect(el.height).toBeLessThan(0.75); expect(el.path).toContain(String(el.height)); expect(el.borderWidth).toBeGreaterThan(0); expect(el.borderType).toBe('dashed'); }); it('270 度旋转的单行 spAutoFit 文本不在 HTML 阶段换行', async () => { const el = await renderShapeXml( ` `, ` Copyright © 元知进化Cog Evol. All Rights Reserved `, '16200000', true, ); if (el.type !== 'text') { throw new Error(`Expected text element, got ${el.type}`); } expect(el.content).toContain('white-space: nowrap'); }); it('保留 textArchUp/textArchDown 的弧形文字排布', async () => { const up = await renderShapeXml( ` `, ` 显著降低推理时延 `, '', true, ); const down = await renderShapeXml( ` `, ` 显著降低Token消耗 `, '', true, ); expect(up.content).toContain('data-pptx-text-warp="textArchUp"'); expect(up.content).toContain('transform: translate(-50%, -50%) rotate('); expect(down.content).toContain('data-pptx-text-warp="textArchDown"'); expect(down.content).toContain('显'); }); it('textArchUp/textArchDown 分别贴近文本框上沿和下沿', async () => { const up = await renderShapeXml( ` `, ` 显著降低推理时延 `, '', true, '', ); const down = await renderShapeXml( ` `, ` 显著降低Token消耗 `, '', true, ); const extractTopPercentages = (html: string) => [...html.matchAll(/top: ([\d.]+)%/g)].map((m) => Number(m[1])); const upTops = extractTopPercentages(up.content); const downTops = extractTopPercentages(down.content); expect(up.content).toContain('height: 90.40px'); expect(Math.max(...upTops)).toBeLessThanOrEqual(32); expect(Math.min(...downTops)).toBeGreaterThanOrEqual(68); }); });