Files
wehub-resource-sync c48612c494
CI / E2E Tests (push) Has been cancelled
CI / Lint, Typecheck & Unit Tests (push) Has been cancelled
Docs Build / Build docs site (push) Has been cancelled
Publish @openmaic packages / Build, validate & publish (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:03:23 +08:00

148 lines
4.9 KiB
TypeScript

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 = '<a:bodyPr/><a:p/>',
rot = '',
txBox = false,
ext = '<a:ext cx="17870" cy="1871"/>',
) {
const xml = `<p:sp ${NS}>
<p:nvSpPr><p:cNvPr id="15" name="shape"/><p:cNvSpPr${txBox ? ' txBox="1"' : ''}/><p:nvPr/></p:nvSpPr>
<p:spPr>
<a:xfrm${rot ? ` rot="${rot}"` : ''}><a:off x="821" y="1546"/>${ext}</a:xfrm>
${spPrInner}
</p:spPr>
<p:txBody>${txBodyInner}</p:txBody>
</p:sp>`;
return renderShape(parseShapeNode(parseXml(xml)), minimalCtx(), 0);
}
describe('shapeSerializer · group 局部坐标的亚像素形状', () => {
it('保留 noFill + dashed outline 的亚像素 roundRect 面积路径', async () => {
const el = await renderShapeXml(`
<a:prstGeom prst="roundRect"><a:avLst><a:gd name="adj" fmla="val 3697"/></a:avLst></a:prstGeom>
<a:noFill/>
<a:ln w="9525">
<a:solidFill><a:srgbClr val="C3A2F7"/></a:solidFill>
<a:prstDash val="sysDash"/>
</a:ln>
`);
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(
`
<a:prstGeom prst="rect"><a:avLst/></a:prstGeom>
<a:noFill/>
`,
`<a:bodyPr wrap="square" lIns="0" rIns="0"><a:spAutoFit/></a:bodyPr>
<a:lstStyle/>
<a:p>
<a:pPr algn="ctr"/>
<a:r><a:rPr sz="800"/><a:t>Copyright © 元知进化Cog Evol. All Rights Reserved</a:t></a:r>
</a:p>`,
'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(
`
<a:prstGeom prst="rect"><a:avLst/></a:prstGeom>
<a:noFill/>
`,
`<a:bodyPr wrap="square"><a:prstTxWarp prst="textArchUp"><a:avLst/></a:prstTxWarp><a:spAutoFit/></a:bodyPr>
<a:lstStyle/>
<a:p>
<a:pPr algn="ctr"/>
<a:r><a:rPr sz="1400"/><a:t>显著降低推理时延</a:t></a:r>
</a:p>`,
'',
true,
);
const down = await renderShapeXml(
`
<a:prstGeom prst="rect"><a:avLst/></a:prstGeom>
<a:noFill/>
`,
`<a:bodyPr wrap="square"><a:prstTxWarp prst="textArchDown"><a:avLst/></a:prstTxWarp><a:spAutoFit/></a:bodyPr>
<a:lstStyle/>
<a:p>
<a:pPr algn="ctr"/>
<a:r><a:rPr sz="1400"/><a:t>显著降低Token消耗</a:t></a:r>
</a:p>`,
'',
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(
`
<a:prstGeom prst="rect"><a:avLst/></a:prstGeom>
<a:noFill/>
`,
`<a:bodyPr wrap="square"><a:prstTxWarp prst="textArchUp"><a:avLst/></a:prstTxWarp><a:spAutoFit/></a:bodyPr>
<a:lstStyle/>
<a:p>
<a:pPr algn="ctr"/>
<a:r><a:rPr sz="1400"/><a:t>显著降低推理时延</a:t></a:r>
</a:p>`,
'',
true,
'<a:ext cx="952500" cy="952500"/>',
);
const down = await renderShapeXml(
`
<a:prstGeom prst="rect"><a:avLst/></a:prstGeom>
<a:noFill/>
`,
`<a:bodyPr wrap="square"><a:prstTxWarp prst="textArchDown"><a:avLst/></a:prstTxWarp><a:spAutoFit/></a:bodyPr>
<a:lstStyle/>
<a:p>
<a:pPr algn="ctr"/>
<a:r><a:rPr sz="1400"/><a:t>显著降低Token消耗</a:t></a:r>
</a:p>`,
'',
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);
});
});