Compare commits
76
Commits
34d0460130
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
26401e8315 | ||
|
|
ccdd0e5f1a | ||
|
|
55aa35837c | ||
|
|
64c86bcd63 | ||
|
|
dc8b688cb1 | ||
|
|
b8659c2190 | ||
|
|
4a18ccb4ab | ||
|
|
32dec8b9e0 | ||
|
|
531bc91821 | ||
|
|
f0a19c71d4 | ||
|
|
c9924d0737 | ||
|
|
d4e492351b | ||
|
|
ce89ee1b4d | ||
|
|
d3bb1fe952 | ||
|
|
f0e82341b8 | ||
|
|
49944c9d03 | ||
|
|
b5f4217302 | ||
|
|
7bb5b81fbd | ||
|
|
5a3fa6bf20 | ||
|
|
3c346e43e5 | ||
|
|
00b3150d1c | ||
|
|
0ce890f45e | ||
|
|
06e63fbe3e | ||
|
|
df699f1134 | ||
|
|
20458d16ab | ||
|
|
e19b80de47 | ||
|
|
984b03c3d6 | ||
|
|
7b8b560dc0 | ||
|
|
42a869aaaf | ||
|
|
224983c790 | ||
|
|
aeb75c746c | ||
|
|
3f7c9648d2 | ||
|
|
1df8a826c4 | ||
|
|
87f191e27f | ||
|
|
6e9bec5856 | ||
|
|
558f4aba4c | ||
|
|
18b5f63fc7 | ||
|
|
75e0268a35 | ||
|
|
119f406a75 | ||
|
|
dca8a40508 | ||
|
|
f20f256197 | ||
|
|
f7307a0891 | ||
|
|
e1c8b3d94e | ||
|
|
330f4e96ce | ||
|
|
a10969b562 | ||
|
|
f3d60360ac | ||
|
|
5035647c91 | ||
|
|
d4d86eb1da | ||
|
|
8ab3f479a7 | ||
|
|
f3853e31d4 | ||
|
|
0ed04eb3ea | ||
|
|
8f507256ef | ||
|
|
f4adac77c5 | ||
|
|
58ee40f71a | ||
|
|
aaa28f1bd8 | ||
|
|
dd916dcf9e | ||
|
|
0e60f7525b | ||
|
|
a2c7f23107 | ||
|
|
d079a4b4cc | ||
|
|
56daab6b67 | ||
|
|
8802b00bdc | ||
|
|
7735bb75de | ||
|
|
46ecb17956 | ||
|
|
eec8c8c215 | ||
|
|
cff5b12975 | ||
|
|
37839c1e86 | ||
|
|
0d05e9f0a8 | ||
|
|
15575b37d6 | ||
|
|
ec7a44d6fb | ||
|
|
c1a9c2fcda | ||
|
|
17d232dcef | ||
|
|
768ac12740 | ||
|
|
ba8d17ba62 | ||
|
|
355b01b9dc | ||
|
|
0f8a797b7a | ||
|
|
ca1e4dfa72 |
@@ -0,0 +1,451 @@
|
|||||||
|
# PptxGenJS v4.0.1 完整 API 参考
|
||||||
|
|
||||||
|
> 从 TypeScript 类型定义生成
|
||||||
|
|
||||||
|
## 1. 初始化
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const pptxgen = require('pptxgenjs');
|
||||||
|
const pres = new pptxgen();
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. 布局设置
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 自定义布局
|
||||||
|
pres.defineLayout({ name: 'CUSTOM', width: 10, height: 5.625 });
|
||||||
|
pres.layout = 'CUSTOM';
|
||||||
|
|
||||||
|
// 预设布局
|
||||||
|
// 'LAYOUT_16x9' (10x5.63)
|
||||||
|
// 'LAYOUT_4x3' (10x7.5)
|
||||||
|
// 'LAYOUT_16x10' (10x6.25)
|
||||||
|
// 'LAYOUT_WIDE' (13.33x7.5)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Slide 管理
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface PresSlide {
|
||||||
|
addChart()
|
||||||
|
addImage()
|
||||||
|
addMedia()
|
||||||
|
addNotes()
|
||||||
|
addShape()
|
||||||
|
addTable()
|
||||||
|
addText()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. addText — 核心方法
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
slide.addText(text, options);
|
||||||
|
// text: string | number | Array<{text, options}>
|
||||||
|
// options: TextPropsOptions
|
||||||
|
```
|
||||||
|
|
||||||
|
### 位置 (继承自 PositionProps)
|
||||||
|
|
||||||
|
| 属性 | 类型 | 说明 | 默认值 |
|
||||||
|
|------|------|------|--------|
|
||||||
|
|
||||||
|
### 文本样式 (继承自 TextBaseProps)
|
||||||
|
|
||||||
|
| 属性 | 类型 | 说明 | 默认值 |
|
||||||
|
|------|------|------|--------|
|
||||||
|
|
||||||
|
### TextPropsOptions 特有属性
|
||||||
|
|
||||||
|
| 属性 | 类型 | 说明 | 默认值 |
|
||||||
|
|------|------|------|--------|
|
||||||
|
|
||||||
|
## 5. 图片
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
slide.addImage({
|
||||||
|
path: '/path/to/image.png', // 本地路径或URL
|
||||||
|
data: 'image/png;base64,...', // 或 base64
|
||||||
|
x: 1, y: 1, w: 8, h: 4.5
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. 形状
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
type SHAPE_NAME = | 'accentBorderCallout1'
|
||||||
|
| 'accentBorderCallout2'
|
||||||
|
| 'accentBorderCallout3'
|
||||||
|
| 'accentCallout1'
|
||||||
|
| 'accentCallout2'
|
||||||
|
| 'accentCallout3'
|
||||||
|
| 'actionButtonBackPrevious'
|
||||||
|
| 'actionButtonBeginning'
|
||||||
|
| 'actionButtonBlank'
|
||||||
|
| 'actionButtonDocument'
|
||||||
|
| 'actionButtonEnd'
|
||||||
|
| 'actionButtonForwardNext'
|
||||||
|
| 'actionButtonHelp'
|
||||||
|
| 'actionButtonHome'
|
||||||
|
| 'actionButtonInformation'
|
||||||
|
| 'actionButtonMovie'
|
||||||
|
| 'actionButtonReturn'
|
||||||
|
| 'actionButtonSound'
|
||||||
|
| 'arc'
|
||||||
|
| 'bentArrow'
|
||||||
|
| 'bentUpArrow'
|
||||||
|
| 'bevel'
|
||||||
|
| 'blockArc'
|
||||||
|
| 'borderCallout1'
|
||||||
|
| 'borderCallout2'
|
||||||
|
| 'borderCallout3'
|
||||||
|
| 'bracePair'
|
||||||
|
| 'bracketPair'
|
||||||
|
| 'callout1'
|
||||||
|
| 'callout2'
|
||||||
|
| 'callout3'
|
||||||
|
| 'can'
|
||||||
|
| 'chartPlus'
|
||||||
|
| 'chartStar'
|
||||||
|
| 'chartX'
|
||||||
|
| 'chevron'
|
||||||
|
| 'chord'
|
||||||
|
| 'circularArrow'
|
||||||
|
| 'cloud'
|
||||||
|
| 'cloudCallout'
|
||||||
|
| 'corner'
|
||||||
|
| 'cornerTabs'
|
||||||
|
| 'cube'
|
||||||
|
| 'curvedDownArrow'
|
||||||
|
| 'curvedLeftArrow'
|
||||||
|
| 'curvedRightArrow'
|
||||||
|
| 'curvedUpArrow'
|
||||||
|
| 'decagon'
|
||||||
|
| 'diagStripe'
|
||||||
|
| 'diamond'
|
||||||
|
| 'dodecagon'
|
||||||
|
| 'donut'
|
||||||
|
| 'doubleWave'
|
||||||
|
| 'downArrow'
|
||||||
|
| 'downArrowCallout'
|
||||||
|
| 'ellipse'
|
||||||
|
| 'ellipseRibbon'
|
||||||
|
| 'ellipseRibbon2'
|
||||||
|
| 'flowChartAlternateProcess'
|
||||||
|
| 'flowChartCollate'
|
||||||
|
| 'flowChartConnector'
|
||||||
|
| 'flowChartDecision'
|
||||||
|
| 'flowChartDelay'
|
||||||
|
| 'flowChartDisplay'
|
||||||
|
| 'flowChartDocument'
|
||||||
|
| 'flowChartExtract'
|
||||||
|
| 'flowChartInputOutput'
|
||||||
|
| 'flowChartInternalStorage'
|
||||||
|
| 'flowChartMagneticDisk'
|
||||||
|
| 'flowChartMagneticDrum'
|
||||||
|
| 'flowChartMagneticTape'
|
||||||
|
| 'flowChartManualInput'
|
||||||
|
| 'flowChartManualOperation'
|
||||||
|
| 'flowChartMerge'
|
||||||
|
| 'flowChartMultidocument'
|
||||||
|
| 'flowChartOfflineStorage'
|
||||||
|
| 'flowChartOffpageConnector'
|
||||||
|
| 'flowChartOnlineStorage'
|
||||||
|
| 'flowChartOr'
|
||||||
|
| 'flowChartPredefinedProcess'
|
||||||
|
| 'flowChartPreparation'
|
||||||
|
| 'flowChartProcess'
|
||||||
|
| 'flowChartPunchedCard'
|
||||||
|
| 'flowChartPunchedTape'
|
||||||
|
| 'flowChartSort'
|
||||||
|
| 'flowChartSummingJunction'
|
||||||
|
| 'flowChartTerminator'
|
||||||
|
| 'folderCorner'
|
||||||
|
| 'frame'
|
||||||
|
| 'funnel'
|
||||||
|
| 'gear6'
|
||||||
|
| 'gear9'
|
||||||
|
| 'halfFrame'
|
||||||
|
| 'heart'
|
||||||
|
| 'heptagon'
|
||||||
|
| 'hexagon'
|
||||||
|
| 'homePlate'
|
||||||
|
| 'horizontalScroll'
|
||||||
|
| 'irregularSeal1'
|
||||||
|
| 'irregularSeal2'
|
||||||
|
| 'leftArrow'
|
||||||
|
| 'leftArrowCallout'
|
||||||
|
| 'leftBrace'
|
||||||
|
| 'leftBracket'
|
||||||
|
| 'leftCircularArrow'
|
||||||
|
| 'leftRightArrow'
|
||||||
|
| 'leftRightArrowCallout'
|
||||||
|
| 'leftRightCircularArrow'
|
||||||
|
| 'leftRightRibbon'
|
||||||
|
| 'leftRightUpArrow'
|
||||||
|
| 'leftUpArrow'
|
||||||
|
| 'lightningBolt'
|
||||||
|
| 'line'
|
||||||
|
| 'lineInv'
|
||||||
|
| 'mathDivide'
|
||||||
|
| 'mathEqual'
|
||||||
|
| 'mathMinus'
|
||||||
|
| 'mathMultiply'
|
||||||
|
| 'mathNotEqual'
|
||||||
|
| 'mathPlus'
|
||||||
|
| 'moon'
|
||||||
|
| 'noSmoking'
|
||||||
|
| 'nonIsoscelesTrapezoid'
|
||||||
|
| 'notchedRightArrow'
|
||||||
|
| 'octagon'
|
||||||
|
| 'parallelogram'
|
||||||
|
| 'pentagon'
|
||||||
|
| 'pie'
|
||||||
|
| 'pieWedge'
|
||||||
|
| 'plaque'
|
||||||
|
| 'plaqueTabs'
|
||||||
|
| 'plus'
|
||||||
|
| 'quadArrow'
|
||||||
|
| 'quadArrowCallout'
|
||||||
|
| 'rect'
|
||||||
|
| 'ribbon'
|
||||||
|
| 'ribbon2'
|
||||||
|
| 'rightArrow'
|
||||||
|
| 'rightArrowCallout'
|
||||||
|
| 'rightBrace'
|
||||||
|
| 'rightBracket'
|
||||||
|
| 'round1Rect'
|
||||||
|
| 'round2DiagRect'
|
||||||
|
| 'round2SameRect'
|
||||||
|
| 'roundRect'
|
||||||
|
| 'rtTriangle'
|
||||||
|
| 'smileyFace'
|
||||||
|
| 'snip1Rect'
|
||||||
|
| 'snip2DiagRect'
|
||||||
|
| 'snip2SameRect'
|
||||||
|
| 'snipRoundRect'
|
||||||
|
| 'squareTabs'
|
||||||
|
| 'star10'
|
||||||
|
| 'star12'
|
||||||
|
| 'star16'
|
||||||
|
| 'star24'
|
||||||
|
| 'star32'
|
||||||
|
| 'star4'
|
||||||
|
| 'star5'
|
||||||
|
| 'star6'
|
||||||
|
| 'star7'
|
||||||
|
| 'star8'
|
||||||
|
| 'stripedRightArrow'
|
||||||
|
| 'sun'
|
||||||
|
| 'swooshArrow'
|
||||||
|
| 'teardrop'
|
||||||
|
| 'trapezoid'
|
||||||
|
| 'triangle'
|
||||||
|
| 'upArrow'
|
||||||
|
| 'upArrowCallout'
|
||||||
|
| 'upDownArrow'
|
||||||
|
| 'upDownArrowCallout'
|
||||||
|
| 'uturnArrow'
|
||||||
|
| 'verticalScroll'
|
||||||
|
| 'wave'
|
||||||
|
| 'wedgeEllipseCallout'
|
||||||
|
| 'wedgeRectCallout'
|
||||||
|
| 'wedgeRoundRectCallout'
|
||||||
|
|
||||||
|
export enum SLIDE_OBJECT_TYPES {
|
||||||
|
'chart' = 'chart',
|
||||||
|
'hyperlink' = 'hyperlink',
|
||||||
|
'image' = 'image',
|
||||||
|
'media' = 'media',
|
||||||
|
'online' = 'online',
|
||||||
|
'placeholder' = 'placeholder',
|
||||||
|
'table' = 'table',
|
||||||
|
'tablecell' = 'tablecell',
|
||||||
|
'text' = 'text',
|
||||||
|
'notes' = 'notes',
|
||||||
|
}
|
||||||
|
export enum TEXT_HALIGN {
|
||||||
|
'left' = 'left',
|
||||||
|
'center' = 'center',
|
||||||
|
'right' = 'right',
|
||||||
|
'justify' = 'justify',
|
||||||
|
}
|
||||||
|
export enum TEXT_VALIGN {
|
||||||
|
'b' = 'b',
|
||||||
|
'ctr' = 'ctr',
|
||||||
|
't' = 't',
|
||||||
|
}
|
||||||
|
|
||||||
|
// @source `core-interfaces.d.ts` (direct)
|
||||||
|
// Core Types
|
||||||
|
// ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Coordinate number - either:
|
||||||
|
* - Inches (0-n)
|
||||||
|
* - Percentage (0-100)
|
||||||
|
*
|
||||||
|
* @example 10.25 // coordinate in inches
|
||||||
|
* @example '75%' // coordinate as percentage of slide size
|
||||||
|
*/
|
||||||
|
export type Coord = number | `${number}%`
|
||||||
|
export interface PositionProps {
|
||||||
|
/**
|
||||||
|
* Horizontal position
|
||||||
|
* - inches or percentage
|
||||||
|
* @example 10.25 // position in inches
|
||||||
|
* @example '75%' // position as percentage of slide size
|
||||||
|
*/
|
||||||
|
x?: Coord
|
||||||
|
/**
|
||||||
|
* Vertical position
|
||||||
|
* - inches or percentage
|
||||||
|
* @example 10.25 // position in inches
|
||||||
|
* @example '75%' // position as percentage of slide size
|
||||||
|
*/
|
||||||
|
y?: Coord
|
||||||
|
/**
|
||||||
|
* Height
|
||||||
|
* - inches or percentage
|
||||||
|
* @example 10.25 // height in inches
|
||||||
|
* @example '75%' // height as percentage of slide size
|
||||||
|
*/
|
||||||
|
h?: Coord
|
||||||
|
/**
|
||||||
|
* Width
|
||||||
|
* - inches or percentage
|
||||||
|
* @example 10.25 // width in inches
|
||||||
|
* @example '75%' // width as percentage of slide size
|
||||||
|
*/
|
||||||
|
w?: Coord
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Either `data` or `path` is required
|
||||||
|
*/
|
||||||
|
export interface DataOrPathProps {
|
||||||
|
/**
|
||||||
|
* URL or relative path
|
||||||
|
*
|
||||||
|
* @example 'https://onedrives.com/myimg.png` // retrieve image via URL
|
||||||
|
* @example '/home/gitbrent/images/myimg.png` // retrieve image via local path
|
||||||
|
*/
|
||||||
|
path?: string
|
||||||
|
/**
|
||||||
|
* base64-encoded string
|
||||||
|
* - Useful for avoiding potential path/server issues
|
||||||
|
*
|
||||||
|
* @example 'image/png;
|
||||||
|
```
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
slide.addShape(pres.ShapeType.rect, {
|
||||||
|
x: 1, y: 1, w: 8, h: 4,
|
||||||
|
fill: { color: '0088CC' },
|
||||||
|
line: { color: '0055AA', width: 2 }
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 7. 表格
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
slide.addTable([
|
||||||
|
[
|
||||||
|
{ text: 'Header 1', options: { bold: true, color: 'FFFFFF' } },
|
||||||
|
{ text: 'Header 2', options: { bold: true, color: 'FFFFFF' } }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ text: 'Row 1 Col 1' },
|
||||||
|
{ text: 'Row 1 Col 2' }
|
||||||
|
]
|
||||||
|
], {
|
||||||
|
x: 1, y: 1, w: 8,
|
||||||
|
border: { type: 'solid', color: 'CCCCCC', pt: 1 },
|
||||||
|
colW: [4, 4]
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 8. 图表
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
slide.addChart(pres.ChartType.bar, [
|
||||||
|
{
|
||||||
|
name: 'Sales',
|
||||||
|
labels: ['Q1', 'Q2', 'Q3', 'Q4'],
|
||||||
|
values: [100, 200, 150, 300]
|
||||||
|
}
|
||||||
|
], {
|
||||||
|
x: 1, y: 1, w: 8, h: 4
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 9. 输出
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 保存到文件
|
||||||
|
pres.writeFile({ fileName: 'output.pptx' });
|
||||||
|
|
||||||
|
// 浏览器下载
|
||||||
|
pres.write({ outputType: 'blob' });
|
||||||
|
pres.write({ outputType: 'arraybuffer' });
|
||||||
|
pres.write({ outputType: 'dataUrl' });
|
||||||
|
|
||||||
|
// Node stream
|
||||||
|
pres.stream();
|
||||||
|
```
|
||||||
|
|
||||||
|
## 10. 坐标换算
|
||||||
|
|
||||||
|
PptxGenJS 使用英寸作为单位。
|
||||||
|
|
||||||
|
| 单位 | 换算 |
|
||||||
|
|------|------|
|
||||||
|
| 1 英寸 | 1.0 |
|
||||||
|
| 1 厘米 | 0.3937 英寸 |
|
||||||
|
| CSS px (96dpi) | px / 96 英寸 |
|
||||||
|
| PPT pt (72dpi) | pt / 72 英寸 |
|
||||||
|
|
||||||
|
常用:`pxToInches = px / 96`
|
||||||
|
|
||||||
|
## 11. 完整示例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const pptxgen = require('pptxgenjs');
|
||||||
|
const pres = new pptxgen();
|
||||||
|
|
||||||
|
// 16:9 布局
|
||||||
|
pres.defineLayout({ name: 'CUSTOM', width: 10, height: 5.625 });
|
||||||
|
pres.layout = 'CUSTOM';
|
||||||
|
pres.author = 'LiJin';
|
||||||
|
pres.title = 'Presentation';
|
||||||
|
|
||||||
|
// 第一页
|
||||||
|
let slide = pres.addSlide();
|
||||||
|
slide.background = { fill: '1A1A2E' };
|
||||||
|
slide.addText('Title Slide', {
|
||||||
|
x: 1, y: 1.5, w: 8, h: 1,
|
||||||
|
fontSize: 44, color: 'FFFFFF', bold: true,
|
||||||
|
align: 'center', fontFace: 'Arial'
|
||||||
|
});
|
||||||
|
slide.addText('Subtitle', {
|
||||||
|
x: 1, y: 3, w: 8, h: 0.5,
|
||||||
|
fontSize: 18, color: 'AAAAAA',
|
||||||
|
align: 'center'
|
||||||
|
});
|
||||||
|
|
||||||
|
// 第二页
|
||||||
|
slide = pres.addSlide();
|
||||||
|
slide.background = { fill: 'FFFFFF' };
|
||||||
|
slide.addText('Content Page', {
|
||||||
|
x: 0.5, y: 0.3, w: 9, h: 0.6,
|
||||||
|
fontSize: 28, bold: true
|
||||||
|
});
|
||||||
|
slide.addText([
|
||||||
|
{ text: 'Bullet 1\n', options: { fontSize: 18, bullet: true } },
|
||||||
|
{ text: 'Bullet 2\n', options: { fontSize: 18, bullet: true } },
|
||||||
|
{ text: 'Bullet 3', options: { fontSize: 18, bullet: true } }
|
||||||
|
], {
|
||||||
|
x: 0.5, y: 1.5, w: 5, h: 2
|
||||||
|
});
|
||||||
|
|
||||||
|
pres.writeFile({ fileName: 'demo.pptx' });
|
||||||
|
```
|
||||||
@@ -1,37 +1,94 @@
|
|||||||
# html2pptx
|
# html2pptx
|
||||||
|
|
||||||
HTML to editable PPTX export engine.
|
HTML / 截图 → 标准 JSON → 可编辑 PPTX
|
||||||
|
|
||||||
将 HTML 演示文稿导出为**文字可编辑、布局可还原**的 PPTX 文件。
|
将任何格式的演示文稿描述转化为**文字可编辑、布局可还原**的 PPTX 文件。
|
||||||
|
|
||||||
## 原理
|
## 架构
|
||||||
|
|
||||||
```
|
```
|
||||||
HTML 文件 → HTTP 服务 → Chrome headless 渲染 → DOM 遍历提取位置/样式 → pptxgenjs 生成 PPTX
|
输入(任意来源) 中间层 输出
|
||||||
|
━━━━━━━━━━━━━━━ ━━━━━━━━━━━ ━━━━━
|
||||||
|
AI 生成的描述 标准 JSON PPTX 文件
|
||||||
|
HTML 提取的结构 → Schema 校验 → pptxgenjs 渲染
|
||||||
|
多模态识别结果 ↓
|
||||||
|
Markdown / 其他 翻译引擎(机械映射)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 三层结构
|
||||||
|
|
||||||
|
| 层 | 目录 | 说明 |
|
||||||
|
|---|------|------|
|
||||||
|
| **知识库** | `reference/` | pptxgenjs 完整参数表,每个参数的类型/取值范围/默认值。给 AI 用的操作手册 |
|
||||||
|
| **Schema** | `schema/` | JSON Schema 定义 + 示例。AI 输出符合此 Schema 的 JSON 即可驱动 PPT 生成 |
|
||||||
|
| **翻译引擎** | `renderer/` | JSON → pptxgenjs API 调用的机械映射。不做逻辑判断,只做参数透传 |
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### 从 JSON 生成 PPTX
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const { render } = require('./renderer/index');
|
||||||
|
const pptx = render(jsonObject);
|
||||||
|
pptx.writeFile({ fileName: 'output.pptx' });
|
||||||
|
```
|
||||||
|
|
||||||
|
### 从 JSON 文件生成
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const { renderFile } = require('./renderer/index');
|
||||||
|
renderFile('./schema/examples/ui-dashboard.json', 'output.pptx');
|
||||||
|
```
|
||||||
|
|
||||||
|
## 目录结构
|
||||||
|
|
||||||
|
```
|
||||||
|
reference/ ← 知识库:每个元素的完整参数表
|
||||||
|
text.md 文字参数
|
||||||
|
shape.md 形状参数
|
||||||
|
table.md 表格参数
|
||||||
|
image.md 图片参数
|
||||||
|
chart.md 图表参数
|
||||||
|
slide.md 页面参数
|
||||||
|
master.md 母版参数
|
||||||
|
output.md 输出参数
|
||||||
|
coordinate.md 坐标换算
|
||||||
|
|
||||||
|
schema/ ← JSON Schema 定义
|
||||||
|
presentation.schema.json
|
||||||
|
examples/
|
||||||
|
basic.json 基础示例(2页)
|
||||||
|
full.json 全功能示例
|
||||||
|
ui-dashboard.json UI 看板示例
|
||||||
|
ui-advanced.json 精细化 UI 示例
|
||||||
|
|
||||||
|
renderer/ ← 翻译引擎
|
||||||
|
index.js 主入口
|
||||||
|
text-renderer.js 文字渲染
|
||||||
|
shape-renderer.js 形状渲染
|
||||||
|
table-renderer.js 表格渲染
|
||||||
|
image-renderer.js 图片渲染
|
||||||
|
chart-renderer.js 图表渲染
|
||||||
|
slide-renderer.js 页面属性
|
||||||
|
|
||||||
|
PPTXGENJS_API.md ← pptxgenjs v4.0.1 完整 API 参考
|
||||||
|
```
|
||||||
|
|
||||||
|
## 设计原则
|
||||||
|
|
||||||
|
- **翻译引擎是纯机械映射** — 不做样式推断,不做逻辑判断。JSON 里有什么就画什么
|
||||||
|
- **Schema 是 AI 友好** — 字段名直观,不暴露 pptxgenjs 内部命名
|
||||||
|
- **知识库是完整参考** — 每个参数都有类型+取值范围+默认值,不需要 AI 去猜
|
||||||
|
|
||||||
## 依赖
|
## 依赖
|
||||||
|
|
||||||
- Node.js 18+
|
|
||||||
- Chrome / Chromium / Edge(用于渲染页面)
|
|
||||||
|
|
||||||
## 使用
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 安装
|
|
||||||
npm install
|
|
||||||
|
|
||||||
# 导出
|
|
||||||
node bin/html2pptx input.html output.pptx
|
|
||||||
```
|
|
||||||
|
|
||||||
## 核心依赖
|
|
||||||
|
|
||||||
| 包 | 用途 |
|
| 包 | 用途 |
|
||||||
|---|------|
|
|---|------|
|
||||||
| playwright | 控制 Chrome headless 渲染页面 |
|
| pptxgenjs | 生成 PPTX 文件(OOXML) |
|
||||||
| pptxgenjs | 生成 PPTX 文件 |
|
|
||||||
| http-server | 本地静态文件服务(可选) |
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
+46
-99
@@ -1,115 +1,62 @@
|
|||||||
# HTML to PPTX 导出引擎
|
# html2pptx 项目文档
|
||||||
|
|
||||||
## 背景
|
## 项目定位
|
||||||
|
|
||||||
DashiAI PPT Skill 中包含一个 `html-deck-to-pptx` 导出子包,能将 HTML 演示文稿导出为可编辑的 PPTX 文件。该子包目前为专有组件(非开源),但其核心逻辑可复刻。
|
一套通用 PPT 生成基础设施。核心价值在于**翻译引擎**——把标准 JSON 描述翻译成可编辑的 PPTX 文件。
|
||||||
|
|
||||||
我们需要一个独立、可控的导出引擎,集成到现有工作流中。凡是 Hermes/Agogent 生成的 HTML PPT,都能一键导出为**文字可编辑、布局可还原**的 PPTX 文件。
|
## 架构
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 需求
|
|
||||||
|
|
||||||
### 输入
|
|
||||||
|
|
||||||
- 一个 HTML 文件(含完整 CSS 内联或外联),结构为横向翻页的演示文稿
|
|
||||||
- 每页用 `<section>` 或特定容器标识
|
|
||||||
- 样式通过 CSS 定义(font-size, color, font-weight, text-align, background-color 等)
|
|
||||||
|
|
||||||
### 输出
|
|
||||||
|
|
||||||
- 一个标准的 `.pptx` 文件,可用 Microsoft PowerPoint / WPS / Keynote 打开
|
|
||||||
- **文字必须可编辑**(双击修改字体、大小、颜色)
|
|
||||||
- **形状/框线/背景色保持矢量**(可拖动、缩放)
|
|
||||||
- **排版布局按原始位置还原**(x, y, width, height 尽可能一致)
|
|
||||||
|
|
||||||
### 可放弃的特性(第一期不做)
|
|
||||||
|
|
||||||
- JS 图表(ECharts/Chart.js)→ 导出为截图占位或忽略
|
|
||||||
- CSS 渐变/阴影/圆角 → 简化或忽略
|
|
||||||
- 入场/翻页动画 → 忽略
|
|
||||||
- Web 字体 → 映射为 Office 相近字体
|
|
||||||
- 响应式布局 → 固定一宽高比
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 技术方案
|
|
||||||
|
|
||||||
### 架构
|
|
||||||
|
|
||||||
```
|
```
|
||||||
HTML 文件
|
输入(任意来源) 中间层 输出
|
||||||
↓
|
━━━━━━━━━━━━━━━ ━━━━━━━━━━━ ━━━━━
|
||||||
启动本地 HTTP 服务(Node.js http-server 或 express)
|
AI 直接输出 标准 JSON PPTX 文件
|
||||||
↓
|
HTML 提取 → JSON → Schema 校验 → 翻译引擎
|
||||||
Chrome headless(通过 playwright)打开页面
|
截图识别 → JSON (机械映射) pptxgenjs
|
||||||
↓
|
Markdown → JSON
|
||||||
遍历 DOM,提取每页每个元素:
|
|
||||||
- 位置(offsetLeft, offsetTop, offsetWidth, offsetHeight)
|
|
||||||
- 文字内容(innerText)
|
|
||||||
- 样式(font-size, color, font-weight, text-align, background-color 等)
|
|
||||||
↓
|
|
||||||
pptxgenjs 按页创建 slide,按坐标绘制 TextBox / Shape
|
|
||||||
↓
|
|
||||||
输出 .pptx 文件
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 核心依赖
|
### 三层
|
||||||
|
|
||||||
| 依赖 | 用途 | 备注 |
|
| 层 | 内容 | 状态 |
|
||||||
|------|------|------|
|
|---|------|------|
|
||||||
| `playwright` | 控制 Chrome headless 渲染页面、提取 DOM 信息 | 你的环境已有 Chrome |
|
| **知识库** (`reference/`) | pptxgenjs 完整参数表,每个参数的类型/取值范围/默认值 | ✅ 已完成 |
|
||||||
| `pptxgenjs` | 生成 PPTX 文件(OOXML) | 纯 JS,不依赖 Office |
|
| **Schema** (`schema/`) | JSON Schema 定义 + 多种场景的示例文件 | ✅ 已完成 |
|
||||||
| `http-server` 或 express | 本地静态文件服务 | |
|
| **翻译引擎** (`renderer/`) | JSON → pptxgenjs API 调用的机械映射代码 | ✅ 已验收 |
|
||||||
|
|
||||||
### 可选升级
|
### 前端处理器(待开发)
|
||||||
|
|
||||||
- 支持 PNG/JPEG 图片的提取与还原
|
"输入 → 标准 JSON"这层目前没有稳定的通用方案。试验过以下方向:
|
||||||
- 表格结构识别(`<table>` → PPT 表格)
|
|
||||||
- 列表识别(`<ul>/<ol>` → PPT 列表)
|
|
||||||
- CSS 圆角/边框映射
|
|
||||||
- 批量导出(多 HTML 文件)
|
|
||||||
|
|
||||||
---
|
| 方案 | 结论 |
|
||||||
|
|------|------|
|
||||||
|
| 多模态截图识别(Mimo v2.5) | ❌ 坐标和字号不够精确 |
|
||||||
|
| LLM 直接读 HTML+CSS 源码 | ❌ 样式映射不准确 |
|
||||||
|
| playwright 全量 DOM 提取 | ⚠️ 数据精确但分页和元素筛选不稳定 |
|
||||||
|
|
||||||
|
目前推荐的做法:**AI 直接按 Schema 输出 JSON**,不经过中间提取步骤。
|
||||||
|
|
||||||
|
## 翻译引擎使用
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const { render, renderFile } = require('./renderer/index');
|
||||||
|
|
||||||
|
// 从 JSON 对象生成
|
||||||
|
const pptx = render(specObject);
|
||||||
|
pptx.writeFile({ fileName: 'output.pptx' });
|
||||||
|
|
||||||
|
// 从 JSON 文件生成
|
||||||
|
await renderFile('./spec.json', 'output.pptx');
|
||||||
|
```
|
||||||
|
|
||||||
## 验收标准
|
## 验收标准
|
||||||
|
|
||||||
1. **输入一个 10 页的 HTML PPT**,导出 PPTX 后在 PowerPoint 中打开
|
- 输入标准 JSON(符合 Schema)→ 输出可编辑的 PPTX
|
||||||
2. **每一页的标题、正文文字可双击编辑**
|
- 文字可双击编辑(非图片)
|
||||||
3. **文字字体、大小、颜色、粗体与原 HTML 一致**
|
- 支持:文字、形状、表格、图片、图表
|
||||||
4. **布局位置基本还原**(不要求像素级,但段落不应错位到其他区域)
|
- 颜色、字号、位置精确还原
|
||||||
5. **执行时间不超过 30 秒**(10 页)
|
|
||||||
|
|
||||||
---
|
## 已知限制
|
||||||
|
|
||||||
## 工作流集成
|
- 翻译引擎不做样式推断——JSON 里有什么就画什么
|
||||||
|
- 前端处理器(HTML/截图→JSON)尚无稳定方案
|
||||||
```bash
|
- 渐变、阴影等高级 CSS 效果依赖 pptxgenjs 支持程度
|
||||||
# 命令行调用
|
|
||||||
html2pptx input.html output.pptx
|
|
||||||
|
|
||||||
# 或
|
|
||||||
html2pptx input.html -o output.pptx
|
|
||||||
|
|
||||||
# 或指定端口
|
|
||||||
html2pptx input.html -o output.pptx -p 8080
|
|
||||||
```
|
|
||||||
|
|
||||||
输出文件路径直接返回给用户,用户双击或在 PowerPoint 中打开即可。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 第一版范围(MVP)
|
|
||||||
|
|
||||||
- [ ] 基础架构:HTTP 服务 + Chrome headless 渲染 + DOM 提取
|
|
||||||
- [ ] 文字提取:位置、内容、font-size、color、font-weight、text-align
|
|
||||||
- [ ] PPTX 生成:pptxgenjs 建 slide,画 TextBox
|
|
||||||
- [ ] 分页识别:按 `<section>` 或 `[data-slide]` 切割
|
|
||||||
- [ ] 背景色还原:slide 背景色
|
|
||||||
- [ ] 对齐方式:left/center/right
|
|
||||||
|
|
||||||
第一版不做的:
|
|
||||||
- 图片提取
|
|
||||||
- 表格识别
|
|
||||||
- 列表识别
|
|
||||||
- 渐变/阴影/圆角
|
|
||||||
|
|||||||
@@ -1,323 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
/**
|
|
||||||
* html2pptx — HTML to editable PPTX export engine
|
|
||||||
*
|
|
||||||
* Usage:
|
|
||||||
* html2pptx <input.html> [output.pptx]
|
|
||||||
* html2pptx <input.html> -o <output.pptx> -p <port>
|
|
||||||
*
|
|
||||||
* Process:
|
|
||||||
* 1. Start local HTTP server to host the HTML
|
|
||||||
* 2. Chrome headless (Playwright) opens the page
|
|
||||||
* 3. Extract each slide's element positions, text, and styles
|
|
||||||
* 4. Generate PPTX with pptxgenjs
|
|
||||||
*/
|
|
||||||
|
|
||||||
const { chromium } = require('playwright');
|
|
||||||
const http = require('http');
|
|
||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
const PptxGenJS = require('pptxgenjs');
|
|
||||||
|
|
||||||
// ====== CLI argument parsing ======
|
|
||||||
|
|
||||||
function parseArgs() {
|
|
||||||
const args = process.argv.slice(2);
|
|
||||||
if (args.length === 0) {
|
|
||||||
console.error('Usage: html2pptx <input.html> [output.pptx]');
|
|
||||||
console.error(' html2pptx <input.html> -o <output.pptx> -p <port>');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const inputPath = path.resolve(args[0]);
|
|
||||||
const parsed = path.parse(inputPath);
|
|
||||||
let outputPath = path.join(parsed.dir, parsed.name + '.pptx');
|
|
||||||
let port;
|
|
||||||
|
|
||||||
for (let i = 1; i < args.length; i++) {
|
|
||||||
if (args[i] === '-o' && i + 1 < args.length) {
|
|
||||||
outputPath = path.resolve(args[++i]);
|
|
||||||
} else if (args[i] === '-p' && i + 1 < args.length) {
|
|
||||||
port = parseInt(args[++i], 10);
|
|
||||||
} else if (!args[i].startsWith('-')) {
|
|
||||||
outputPath = path.resolve(args[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { inputPath, outputPath, port };
|
|
||||||
}
|
|
||||||
|
|
||||||
// ====== Color parsing (Node.js side) ======
|
|
||||||
|
|
||||||
function parseCSSColor(color, fallback) {
|
|
||||||
if (!color || color === 'transparent' || color === 'rgba(0, 0, 0, 0)') return fallback || '#000000';
|
|
||||||
|
|
||||||
// Already #rrggbb
|
|
||||||
if (/^#[0-9a-f]{6}$/i.test(color)) return color.toLowerCase();
|
|
||||||
// #rgb → #rrggbb
|
|
||||||
if (/^#[0-9a-f]{3}$/i.test(color)) {
|
|
||||||
return '#' + color[1] + color[1] + color[2] + color[2] + color[3] + color[3];
|
|
||||||
}
|
|
||||||
|
|
||||||
// rgb(r, g, b) / rgba(r, g, b, a)
|
|
||||||
const m = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
|
|
||||||
if (m) {
|
|
||||||
return '#' + m.slice(1, 4).map(c => parseInt(c).toString(16).padStart(2, '0')).join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
return fallback || '#000000';
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseFontWeight(w) {
|
|
||||||
if (w === 'bold') return true;
|
|
||||||
const n = parseInt(w, 10);
|
|
||||||
return n >= 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeAlign(align) {
|
|
||||||
switch (align) {
|
|
||||||
case 'left': case 'center': case 'right': case 'justify': return align;
|
|
||||||
case 'start': return 'left';
|
|
||||||
case 'end': return 'right';
|
|
||||||
default: return 'left';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ====== Main ======
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
const { inputPath, outputPath, port: cliPort } = parseArgs();
|
|
||||||
|
|
||||||
if (!fs.existsSync(inputPath)) {
|
|
||||||
console.error('文件不存在:', inputPath);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const TIMEOUT_MS = 30000;
|
|
||||||
const startTime = Date.now();
|
|
||||||
let server, browser;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// ------ 1. HTTP server ------
|
|
||||||
const baseDir = path.dirname(inputPath);
|
|
||||||
const MIME = {
|
|
||||||
'.html': 'text/html; charset=utf-8',
|
|
||||||
'.css': 'text/css; charset=utf-8',
|
|
||||||
'.js': 'application/javascript; charset=utf-8',
|
|
||||||
'.png': 'image/png',
|
|
||||||
'.jpg': 'image/jpeg',
|
|
||||||
'.jpeg': 'image/jpeg',
|
|
||||||
'.svg': 'image/svg+xml',
|
|
||||||
};
|
|
||||||
|
|
||||||
server = http.createServer((req, res) => {
|
|
||||||
const reqPath = req.url === '/' ? '/' + path.basename(inputPath) : req.url;
|
|
||||||
const filePath = path.join(baseDir, reqPath);
|
|
||||||
// Prevent directory traversal
|
|
||||||
if (!filePath.startsWith(baseDir)) {
|
|
||||||
res.writeHead(404);
|
|
||||||
res.end('Not Found');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
|
|
||||||
const ext = path.extname(filePath).toLowerCase();
|
|
||||||
res.writeHead(200, { 'Content-Type': MIME[ext] || 'application/octet-stream' });
|
|
||||||
res.end(fs.readFileSync(filePath));
|
|
||||||
} else {
|
|
||||||
res.writeHead(404);
|
|
||||||
res.end('Not Found');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const port = cliPort || 0;
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
server.listen(port, '127.0.0.1', resolve);
|
|
||||||
server.on('error', reject);
|
|
||||||
});
|
|
||||||
const actualPort = server.address().port;
|
|
||||||
console.log('HTTP 服务已启动: http://127.0.0.1:' + actualPort + '/');
|
|
||||||
|
|
||||||
if (Date.now() - startTime > TIMEOUT_MS) throw new Error('导出超时');
|
|
||||||
|
|
||||||
// ------ 2. Chrome headless ------
|
|
||||||
browser = await chromium.launch({ headless: true });
|
|
||||||
const page = await browser.newPage({ viewport: { width: 1280, height: 720 } });
|
|
||||||
|
|
||||||
if (Date.now() - startTime > TIMEOUT_MS) throw new Error('导出超时');
|
|
||||||
|
|
||||||
await page.goto('http://127.0.0.1:' + actualPort + '/', {
|
|
||||||
waitUntil: 'networkidle',
|
|
||||||
timeout: 15000,
|
|
||||||
});
|
|
||||||
console.log('页面加载完成');
|
|
||||||
|
|
||||||
// ------ 3. DOM extraction (in-browser) ------
|
|
||||||
const slidesData = await page.evaluate(() => {
|
|
||||||
// ----- Slide detection -----
|
|
||||||
var slides;
|
|
||||||
// DashiAI format: .ppt-deck > .ppt-slide
|
|
||||||
var deck = document.querySelector('.ppt-deck');
|
|
||||||
if (deck) {
|
|
||||||
slides = deck.querySelectorAll('.ppt-slide');
|
|
||||||
}
|
|
||||||
if (!slides || slides.length <= 1) {
|
|
||||||
slides = document.querySelectorAll(
|
|
||||||
'section[data-slide], section.slide, section'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (slides.length <= 1) {
|
|
||||||
slides = document.querySelectorAll('[data-slide]');
|
|
||||||
}
|
|
||||||
slides = Array.from(slides).filter(function (s) {
|
|
||||||
var r = s.getBoundingClientRect();
|
|
||||||
return r.width > 0 && r.height > 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Tags whose full text content should be a single text box
|
|
||||||
var TEXT_BLOCK_TAGS = new Set([
|
|
||||||
'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
|
|
||||||
'li', 'td', 'th', 'blockquote', 'figcaption', 'dt', 'dd', 'caption',
|
|
||||||
]);
|
|
||||||
|
|
||||||
return slides.map(function (slide) {
|
|
||||||
var slideRect = slide.getBoundingClientRect();
|
|
||||||
var elements = [];
|
|
||||||
|
|
||||||
// Collect block-level text containers; each becomes one text box
|
|
||||||
var textBlocks = slide.querySelectorAll(
|
|
||||||
'p, h1, h2, h3, h4, h5, h6, li, td, th, blockquote, ' +
|
|
||||||
'figcaption, dt, dd, caption'
|
|
||||||
);
|
|
||||||
|
|
||||||
for (var i = 0; i < textBlocks.length; i++) {
|
|
||||||
var el = textBlocks[i];
|
|
||||||
// Skip if this block is nested inside another text block (e.g. li inside another li)
|
|
||||||
var parent = el.parentElement;
|
|
||||||
var skip = false;
|
|
||||||
while (parent && parent !== slide) {
|
|
||||||
if (TEXT_BLOCK_TAGS.has(parent.tagName.toLowerCase())) {
|
|
||||||
skip = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
parent = parent.parentElement;
|
|
||||||
}
|
|
||||||
if (skip) continue;
|
|
||||||
|
|
||||||
var text = (el.textContent || '').trim();
|
|
||||||
if (!text) continue;
|
|
||||||
|
|
||||||
var rect = el.getBoundingClientRect();
|
|
||||||
if (rect.width === 0 || rect.height === 0) continue;
|
|
||||||
|
|
||||||
var style = window.getComputedStyle(el);
|
|
||||||
|
|
||||||
elements.push({
|
|
||||||
text: text.substring(0, 2000),
|
|
||||||
x: rect.left - slideRect.left,
|
|
||||||
y: rect.top - slideRect.top,
|
|
||||||
width: rect.width,
|
|
||||||
height: rect.height,
|
|
||||||
fontSize: parseFloat(style.fontSize) || 14,
|
|
||||||
fontFamily: style.fontFamily || 'Arial',
|
|
||||||
color: style.color || '#000000',
|
|
||||||
fontWeight: style.fontWeight || 'normal',
|
|
||||||
fontStyle: style.fontStyle || 'normal',
|
|
||||||
textAlign: style.textAlign || 'left',
|
|
||||||
backgroundColor: style.backgroundColor || 'transparent',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Slide background
|
|
||||||
var bg = window.getComputedStyle(slide).backgroundColor;
|
|
||||||
var bgColor =
|
|
||||||
bg &&
|
|
||||||
bg !== 'rgba(0, 0, 0, 0)' &&
|
|
||||||
bg !== 'transparent'
|
|
||||||
? bg
|
|
||||||
: '#FFFFFF';
|
|
||||||
|
|
||||||
return {
|
|
||||||
width: slideRect.width,
|
|
||||||
height: slideRect.height,
|
|
||||||
bgColor: bgColor,
|
|
||||||
elements: elements,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('提取到 ' + slidesData.length + ' 页幻灯片');
|
|
||||||
|
|
||||||
if (Date.now() - startTime > TIMEOUT_MS) throw new Error('导出超时');
|
|
||||||
|
|
||||||
// ------ 4. PPTX generation ------
|
|
||||||
var pptx = new PptxGenJS();
|
|
||||||
pptx.defineLayout({ name: 'CUSTOM', width: 10, height: 5.625 });
|
|
||||||
pptx.layout = 'CUSTOM';
|
|
||||||
|
|
||||||
for (var s = 0; s < slidesData.length; s++) {
|
|
||||||
var slideData = slidesData[s];
|
|
||||||
var slide = pptx.addSlide();
|
|
||||||
|
|
||||||
// Background
|
|
||||||
slide.background = { fill: parseCSSColor(slideData.bgColor, '#FFFFFF') };
|
|
||||||
|
|
||||||
// Scale factors: HTML pixels → PPT inches
|
|
||||||
var scaleX = 10 / slideData.width;
|
|
||||||
var scaleY = 5.625 / slideData.height;
|
|
||||||
|
|
||||||
// Sort top-to-bottom, left-to-right
|
|
||||||
slideData.elements.sort(function (a, b) {
|
|
||||||
return a.y - b.y || a.x - b.x;
|
|
||||||
});
|
|
||||||
|
|
||||||
for (var e = 0; e < slideData.elements.length; e++) {
|
|
||||||
var el = slideData.elements[e];
|
|
||||||
var t = (el.text || '').trim();
|
|
||||||
if (!t) continue;
|
|
||||||
|
|
||||||
try {
|
|
||||||
var x = Math.max(el.x * scaleX, 0);
|
|
||||||
var y = Math.max(el.y * scaleY, 0);
|
|
||||||
var w = Math.max(el.width * scaleX, 0.3);
|
|
||||||
var h = Math.max(el.height * scaleY, 0.2);
|
|
||||||
|
|
||||||
// Font size: CSS px → pt (1pt = 1/72in, CSS 1px = 1/96in, so px × 72/96 = pt)
|
|
||||||
var fontSize = el.fontSize * 72 / 96;
|
|
||||||
fontSize = Math.max(fontSize, 8);
|
|
||||||
|
|
||||||
slide.addText(t, {
|
|
||||||
x: x,
|
|
||||||
y: y,
|
|
||||||
w: w,
|
|
||||||
h: h,
|
|
||||||
fontSize: fontSize,
|
|
||||||
fontFace: (el.fontFamily || 'Arial').split(',')[0].trim().replace(/['"]/g, ''),
|
|
||||||
color: parseCSSColor(el.color, '#000000'),
|
|
||||||
bold: parseFontWeight(el.fontWeight),
|
|
||||||
italic: el.fontStyle === 'italic' || el.fontStyle === 'oblique',
|
|
||||||
align: normalizeAlign(el.textAlign),
|
|
||||||
valign: 'top',
|
|
||||||
wrap: true,
|
|
||||||
margin: 0,
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
// Skip individual element failure
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await pptx.writeFile({ fileName: outputPath });
|
|
||||||
console.log('\nPPTX 已导出: ' + outputPath);
|
|
||||||
} finally {
|
|
||||||
if (browser) await browser.close().catch(function () {});
|
|
||||||
if (server)
|
|
||||||
await new Promise(function (resolve) {
|
|
||||||
server.close(resolve);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
main().catch(function (err) {
|
|
||||||
console.error('导出失败:', err.message);
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
+1118
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,136 @@
|
|||||||
|
/**
|
||||||
|
* web-to-pixso JSON → 翻译引擎 Schema 转换器
|
||||||
|
*
|
||||||
|
* 把 web-to-pixso Chrome 扩展导出的 JSON 转成 renderer/index.js 能吃的标准格式
|
||||||
|
*/
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
function convert(inputPath, outputPath) {
|
||||||
|
const raw = JSON.parse(fs.readFileSync(inputPath, 'utf8'));
|
||||||
|
const nodes = raw.nodes;
|
||||||
|
const canvas = raw.canvas || {};
|
||||||
|
|
||||||
|
// 画布尺寸
|
||||||
|
const canvasW = canvas.width || 1920;
|
||||||
|
const canvasH = canvas.height || 1080;
|
||||||
|
|
||||||
|
// 收集所有页面(node.children 包含多页时按页面分组)
|
||||||
|
const pages = [];
|
||||||
|
|
||||||
|
function extractPage(node, parentY) {
|
||||||
|
if (!node || !node.rect) return null;
|
||||||
|
|
||||||
|
const rect = node.rect;
|
||||||
|
const styles = node.styles || {};
|
||||||
|
const layout = node.layout || {};
|
||||||
|
const children = node.children || [];
|
||||||
|
|
||||||
|
// 跳过不可见节点
|
||||||
|
if (styles.display === 'none' || styles.visibility === 'hidden') return null;
|
||||||
|
|
||||||
|
const elements = [];
|
||||||
|
|
||||||
|
// 如果是文字节点
|
||||||
|
if (node.type === 'text' && node.src) {
|
||||||
|
elements.push({
|
||||||
|
type: 'text',
|
||||||
|
text: node.src,
|
||||||
|
options: {
|
||||||
|
x: rect.x / 96,
|
||||||
|
y: (rect.y - parentY) / 96,
|
||||||
|
w: rect.w / 96,
|
||||||
|
h: rect.h / 96,
|
||||||
|
fontSize: styles.fontSize || 14,
|
||||||
|
fontFace: (styles.fontFamily || 'Arial').split(',')[0].replace(/['"]/g, '').trim(),
|
||||||
|
color: (styles.color || '#000000').replace('#', ''),
|
||||||
|
bold: styles.fontWeight >= 700 || styles.fontWeight === 'bold',
|
||||||
|
italic: styles.fontStyle === 'italic',
|
||||||
|
align: styles.textAlign || 'left'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果有背景色或背景图,生成 shape
|
||||||
|
if (styles.backgroundColor && styles.backgroundColor !== 'rgba(0,0,0,0)' && styles.backgroundColor !== 'transparent') {
|
||||||
|
elements.push({
|
||||||
|
type: 'shape',
|
||||||
|
shapeName: 'rect',
|
||||||
|
options: {
|
||||||
|
x: rect.x / 96,
|
||||||
|
y: (rect.y - parentY) / 96,
|
||||||
|
w: rect.w / 96,
|
||||||
|
h: rect.h / 96,
|
||||||
|
fill: { color: styles.backgroundColor.replace('#', '') }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理子节点
|
||||||
|
for (const childId of children) {
|
||||||
|
const childNode = nodes[childId];
|
||||||
|
if (childNode) {
|
||||||
|
const childElements = extractPage(childNode, parentY || rect.y);
|
||||||
|
if (childElements) elements.push(...childElements);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return elements;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 找顶层容器
|
||||||
|
const root = nodes.children ? findRoot(nodes) : null;
|
||||||
|
if (root) {
|
||||||
|
const pageElements = extractPage(root, 0);
|
||||||
|
if (pageElements && pageElements.length > 0) {
|
||||||
|
pages.push({ objects: pageElements });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有分页,把整个 canvas 当一页
|
||||||
|
if (pages.length === 0) {
|
||||||
|
const allElements = [];
|
||||||
|
for (const [id, node] of Object.entries(nodes)) {
|
||||||
|
if (node.rect && node.type) {
|
||||||
|
const elems = extractPage(node, 0);
|
||||||
|
if (elems) allElements.push(...elems);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pages.push({ objects: allElements });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 输出 Schema
|
||||||
|
const spec = {
|
||||||
|
presentation: { layout: 'LAYOUT_16x9' },
|
||||||
|
slides: pages
|
||||||
|
};
|
||||||
|
|
||||||
|
fs.writeFileSync(outputPath, JSON.stringify(spec, null, 2));
|
||||||
|
console.log(`Converted: ${inputPath} → ${outputPath}`);
|
||||||
|
console.log(`Pages: ${pages.length}, Total objects: ${pages.reduce((s,p) => s + p.objects.length, 0)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function findRoot(nodes) {
|
||||||
|
// 找没有 parent 引用的节点作为根
|
||||||
|
const allIds = new Set(Object.keys(nodes));
|
||||||
|
const childIds = new Set();
|
||||||
|
for (const [id, node] of Object.entries(nodes)) {
|
||||||
|
if (node.children) {
|
||||||
|
for (const cid of node.children) childIds.add(cid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const id of allIds) {
|
||||||
|
if (!childIds.has(id)) return nodes[id];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CLI
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
if (args.length < 1) {
|
||||||
|
console.error('Usage: node convert-w2p.js <input.json> [output.json]');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
const inputPath = args[0];
|
||||||
|
const outputPath = args[1] || inputPath.replace('.json', '_schema.json');
|
||||||
|
convert(inputPath, outputPath);
|
||||||
@@ -0,0 +1,203 @@
|
|||||||
|
# html2pptx 通用转化架构
|
||||||
|
|
||||||
|
## 1. 目标 Schema 定义
|
||||||
|
|
||||||
|
### 顶层结构
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"presentation": {
|
||||||
|
"layout": "CUSTOM",
|
||||||
|
"slideWidth": 24.18, // 英寸
|
||||||
|
"slideHeight": 17.08 // 英寸
|
||||||
|
},
|
||||||
|
"slides": [
|
||||||
|
{
|
||||||
|
"background": { "color": "1A1A1A" }, // 可选
|
||||||
|
"objects": [
|
||||||
|
// text / shape / image / table / chart
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### text 对象
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "文字内容",
|
||||||
|
"options": {
|
||||||
|
"x": 1.463, // 英寸(左上角 x)
|
||||||
|
"y": 1.039, // 英寸(左上角 y)
|
||||||
|
"w": 2.282, // 英寸(宽度)
|
||||||
|
"h": 0.25, // 英寸(高度)
|
||||||
|
"fontSize": 14, // pt
|
||||||
|
"fontFace": "Arial", // 字体名
|
||||||
|
"color": "000000", // 6位hex
|
||||||
|
"bold": false, // 可选
|
||||||
|
"italic": false, // 可选
|
||||||
|
"align": "left", // left/center/right/justify
|
||||||
|
"underline": false, // 可选
|
||||||
|
"charSpacing": 0 // 可选,字符间距
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### shape 对象
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "rect", // rect/roundRect/ellipse
|
||||||
|
"options": {
|
||||||
|
"x": 0.83,
|
||||||
|
"y": 3.07,
|
||||||
|
"w": 17.78,
|
||||||
|
"h": 3.12,
|
||||||
|
"fill": { "color": "ffffff" },
|
||||||
|
"line": { "type": "none" }, // 可选
|
||||||
|
"shadow": { // 可选
|
||||||
|
"type": "outer",
|
||||||
|
"blur": 15,
|
||||||
|
"offset": 5,
|
||||||
|
"color": "999999",
|
||||||
|
"opacity": 0.5
|
||||||
|
},
|
||||||
|
"rectRadius": 0.1, // 可选,英寸
|
||||||
|
"opacity": 1.0 // 可选
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### image 对象
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"options": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"w": 10,
|
||||||
|
"h": 5,
|
||||||
|
"data": "base64..." // 或 "path": "file.png"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. 通用转化逻辑
|
||||||
|
|
||||||
|
### 核心原则
|
||||||
|
- **不靠命名,靠结构**:slide 容器 = 包含内容子节点的 FRAME
|
||||||
|
- **不靠猜测,靠数据**:所有属性从 JSON 字段直接读取
|
||||||
|
- **不硬编码规则**:通过配置或自动推断
|
||||||
|
|
||||||
|
### 2.1 自动识别 Slide 容器
|
||||||
|
|
||||||
|
```
|
||||||
|
输入:web-to-ppt JSON 的 nodes 树
|
||||||
|
输出:slide 容器列表
|
||||||
|
|
||||||
|
策略:
|
||||||
|
1. 遍历 nodes 树,找 FRAME 类型节点
|
||||||
|
2. 判断条件(满足任一):
|
||||||
|
a) name 匹配已知模式:slide-N / page / slide+空格 / slide+横杠
|
||||||
|
b) 结构特征:
|
||||||
|
- children 数量 > 0
|
||||||
|
- 面积接近 canvas 面积(> 50% canvas 面积)
|
||||||
|
- 不是 BODY / HTML / HEAD 等结构性节点
|
||||||
|
3. 排除条件:
|
||||||
|
- layerGroup === 'comparison'(截图底图)
|
||||||
|
- 没有 children
|
||||||
|
- 面积太小(< 100px × 100px)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 收集子节点(递归)
|
||||||
|
|
||||||
|
```
|
||||||
|
collectSlideChildren(node, slideX, slideY, parentW, depth):
|
||||||
|
对于每个子节点:
|
||||||
|
- 计算相对坐标:rect.x - slideX, rect.y - slideY
|
||||||
|
- 记录 parentW(父容器宽度)
|
||||||
|
- 记录 depth(层级深度)
|
||||||
|
- 跳过 RECTANGLE 类型(截图底图)
|
||||||
|
- 跳过 layerGroup === 'comparison'
|
||||||
|
- 递归处理 children
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 收集游离节点
|
||||||
|
|
||||||
|
```
|
||||||
|
collectOrphans(root, slideIds):
|
||||||
|
slideIds = 所有 slide 容器的 id 集合
|
||||||
|
|
||||||
|
遍历 root 树:
|
||||||
|
- 如果节点 id 在 slideIds 中 → 跳过整个子树
|
||||||
|
- 否则 → 收集该节点,递归 children
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.4 映射 TEXT 节点
|
||||||
|
|
||||||
|
```
|
||||||
|
输入:TEXT 类型节点
|
||||||
|
输出:text 对象
|
||||||
|
|
||||||
|
映射规则:
|
||||||
|
fontSize = parseFloat(styles.fontSize) || 14
|
||||||
|
fontFace = styles.fontFamily.split(',')[0].trim()
|
||||||
|
color = rgbaToHex(styles.color) || '000000'
|
||||||
|
bold = parseInt(styles.fontWeight) >= 700
|
||||||
|
italic = styles.fontStyle === 'italic'
|
||||||
|
align = styles.textAlign(非 'start' 时)
|
||||||
|
underline = styles.textDecorationLine 包含 'underline'
|
||||||
|
charSpacing = styles.letterSpacing 转换
|
||||||
|
|
||||||
|
宽度计算:
|
||||||
|
contentW = slide 内容宽度(从子节点 x 范围推算)
|
||||||
|
textW = parentW || rect.w
|
||||||
|
if (depth === 1) textW = contentW
|
||||||
|
if (textW > contentW) textW = contentW
|
||||||
|
if (textW < 108) textW = 108 // 最小 1.5in
|
||||||
|
if (x + textW > contentW) textW = contentW - x
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.5 映射 FRAME 节点 → shape
|
||||||
|
|
||||||
|
```
|
||||||
|
输入:FRAME 类型节点(有背景色)
|
||||||
|
输出:shape 对象
|
||||||
|
|
||||||
|
映射规则:
|
||||||
|
fillColor = rgbaToHex(styles.backgroundColor)
|
||||||
|
渐变背景 = backgroundImage 提取起始色
|
||||||
|
圆角 = borderTopLeftRadius 转换
|
||||||
|
边框 = 四边都有 border 时加 line
|
||||||
|
阴影 = boxShadow 解析
|
||||||
|
单边 border = 独立线条 shape
|
||||||
|
默认 line = { type: 'none' }(去掉 pptxgenjs 默认边框)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.6 背景色推断
|
||||||
|
|
||||||
|
```
|
||||||
|
优先级:
|
||||||
|
1. slide 容器的 styles.backgroundColor(非透明时)
|
||||||
|
2. canvas.backgroundColor(全局兜底)
|
||||||
|
3. 从 slide name 推断(dark → #1A1A1A, light → #FAFAFA)
|
||||||
|
4. 从文字颜色推断(浅色文字 → 深色背景,深色文字 → 浅色背景)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. 当前硬编码 → 通用化改造清单
|
||||||
|
|
||||||
|
| 硬编码 | 当前逻辑 | 通用化方案 |
|
||||||
|
|--------|---------|-----------|
|
||||||
|
| slide 匹配 | `slide-N` / `page` / `slide ` | 结构识别 + 已知模式列表 |
|
||||||
|
| 游离节点跳过 | `startsWith('slide-')` / `startsWith('slide ')` | 用 slide id 集合判断 |
|
||||||
|
| 背景色 | `dark`/`light` 关键词 | 优先取 styles,兜底取 canvas |
|
||||||
|
| TEXT 宽度 | `depth === 1` 判断顶层 | 用 parentW 与 contentW 的关系判断 |
|
||||||
|
| 边框 | 只处理 borderTop | 四边独立处理 |
|
||||||
|
|
||||||
|
## 4. 下一步
|
||||||
|
|
||||||
|
1. 重构 `findSlides()` — 支持结构识别
|
||||||
|
2. 重构 `collectOrphans()` — 用 id 集合判断
|
||||||
|
3. 重构背景色逻辑 — 多级兜底
|
||||||
|
4. 补全 styles 映射 — opacity / lineHeight / letterSpacing / textDecoration
|
||||||
|
5. 测试多个 HTML 样本验证通用性
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
# web-to-ppt → PPT 映射分析
|
||||||
|
|
||||||
|
## 1. JSON 结构总览
|
||||||
|
|
||||||
|
### 顶层字段
|
||||||
|
| 字段 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| `canvas` | 画布尺寸 `{width, height, x, y, backgroundColor}` |
|
||||||
|
| `nodes` | 根节点树(type=FRAME, name=html) |
|
||||||
|
| `assets` | 图片资源 `{key: base64}` |
|
||||||
|
| `fonts` | 字体列表 |
|
||||||
|
| `schemaVersion` | JSON 版本号 |
|
||||||
|
|
||||||
|
### 节点类型
|
||||||
|
| 类型 | 数量 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| FRAME | 471 | HTML 容器(div, section, table, header 等) |
|
||||||
|
| TEXT | 393 | 文字节点 |
|
||||||
|
| RECTANGLE | 10 | 截图底图(comparison layer,用于视觉对比) |
|
||||||
|
|
||||||
|
### 节点属性
|
||||||
|
```
|
||||||
|
type - FRAME/TEXT/RECTANGLE
|
||||||
|
rect - {x, y, width, height} 像素坐标
|
||||||
|
styles - 57个CSS属性(见下表)
|
||||||
|
name - HTML元素名或文字内容
|
||||||
|
tag - HTML标签名(HTML/DIV/TABLE/TR/TD/SPAN/#text等)
|
||||||
|
text / src - 文字内容(TEXT节点用text,收集时映射为src)
|
||||||
|
layerGroup - editable/text/comparison
|
||||||
|
backgroundImages - 图片资源引用数组
|
||||||
|
clipRect - 裁剪区域
|
||||||
|
visibleRect - 可见区域
|
||||||
|
clipped - 是否被裁剪
|
||||||
|
id - 节点ID
|
||||||
|
section - document
|
||||||
|
visualRole - layout-wrapper/text-node/raster-fallback
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. 57个 styles 字段分类
|
||||||
|
|
||||||
|
### 已处理(15个)
|
||||||
|
| 字段 | 用途 | 当前处理方式 |
|
||||||
|
|------|------|-------------|
|
||||||
|
| backgroundColor | 背景色 | → shape fill |
|
||||||
|
| backgroundImage | 渐变背景 | → 提取起始色做纯色 fill |
|
||||||
|
| borderTopWidth/Color/Style | 顶部边框 | → shape line(四边都有时)或单独线条 |
|
||||||
|
| borderBottomWidth/Color/Style | 底部边框 | 同上 |
|
||||||
|
| borderLeftWidth/Color/Style | 左边框 | 同上 |
|
||||||
|
| borderRightWidth/Color/Style | 右边框 | 同上 |
|
||||||
|
| borderTopLeftRadius | 圆角 | → rectRadius |
|
||||||
|
| boxShadow | 阴影 | → shadow |
|
||||||
|
| fontSize | 字号 | → fontSize(pt) |
|
||||||
|
| fontFamily | 字体 | → fontFace |
|
||||||
|
| color | 文字颜色 | → color |
|
||||||
|
| fontWeight | 粗体 | → bold |
|
||||||
|
| fontStyle | 斜体 | → italic |
|
||||||
|
| textAlign | 对齐 | → align |
|
||||||
|
|
||||||
|
### 未处理(42个)
|
||||||
|
| 字段 | 重要性 | 说明 |
|
||||||
|
|------|--------|------|
|
||||||
|
| **opacity** | 高 | 元素透明度,PPT支持 `opacity` |
|
||||||
|
| **lineHeight** | 高 | 行高,影响文字换行和间距 |
|
||||||
|
| **letterSpacing** | 中 | 字间距,PPT支持 `charSpacing` |
|
||||||
|
| **textDecorationLine** | 中 | 下划线/删除线 |
|
||||||
|
| **textTransform** | 低 | 大小写转换 |
|
||||||
|
| **whiteSpace** | 高 | 控制换行行为(nowrap等) |
|
||||||
|
| **wordBreak** | 中 | 断词规则 |
|
||||||
|
| **visibility** | 中 | visible/hidden |
|
||||||
|
| **display** | 中 | none时不渲染 |
|
||||||
|
| **transform** | 中 | 旋转/缩放/位移 |
|
||||||
|
| **transformOrigin** | 低 | 变换原点 |
|
||||||
|
| **zIndex** | 高 | 层级顺序 |
|
||||||
|
| **overflow** | 中 | 溢出处理 |
|
||||||
|
| **clipPath** | 低 | 裁剪路径 |
|
||||||
|
| **filter** | 低 | CSS滤镜 |
|
||||||
|
| **borderTopRightRadius** | 中 | 右上圆角(目前只用左上) |
|
||||||
|
| **borderBottomLeftRadius** | 中 | 左下圆角 |
|
||||||
|
| **borderBottomRightRadius** | 中 | 右下圆角 |
|
||||||
|
| **backgroundPosition** | 低 | 背景定位 |
|
||||||
|
| **backgroundSize** | 低 | 背景尺寸 |
|
||||||
|
| **backgroundRepeat** | 低 | 背景重复 |
|
||||||
|
| **backgroundClip** | 低 | 背景裁剪 |
|
||||||
|
| **backgroundOrigin** | 低 | 背景原点 |
|
||||||
|
| **position** | 低 | 定位方式 |
|
||||||
|
| **objectFit** | 低 | 图片适配 |
|
||||||
|
| **objectPosition** | 低 | 图片定位 |
|
||||||
|
| **maskImage/MaskPosition/MaskSize** | 低 | 遮罩 |
|
||||||
|
| **webkitMaskImage/MaskPosition/MaskSize** | 低 | 遮罩(webkit) |
|
||||||
|
| **backdropFilter** | 低 | 背景滤镜 |
|
||||||
|
|
||||||
|
## 3. 映射优先级
|
||||||
|
|
||||||
|
### P0(必须处理)
|
||||||
|
1. **opacity** → shape/text 的 opacity 属性
|
||||||
|
2. **whiteSpace** → 控制文字是否换行
|
||||||
|
3. **zIndex** → 调整 objects 数组顺序(后渲染在上层)
|
||||||
|
4. **lineHeight** → 文字行高(paraSpaceBefore/after 或 lineSpacingMultiple)
|
||||||
|
|
||||||
|
### P1(应该处理)
|
||||||
|
5. **letterSpacing** → charSpacing(字符间距)
|
||||||
|
6. **textDecorationLine** → underline/strike
|
||||||
|
7. **display:none** → 跳过该节点
|
||||||
|
8. **visibility:hidden** → 跳过该节点
|
||||||
|
9. **四个圆角独立处理** → 当前只用了 borderTopLeftRadius
|
||||||
|
|
||||||
|
### P2(可以处理)
|
||||||
|
10. **transform** → rotate 属性
|
||||||
|
11. **wordBreak** → 辅助判断换行
|
||||||
|
12. **overflow** → 辅助判断裁剪
|
||||||
|
13. **filter** → 特殊效果(大部分PPT不支持)
|
||||||
|
|
||||||
|
## 4. 当前代码架构
|
||||||
|
|
||||||
|
```
|
||||||
|
HTML → web-to-ppt 插件 → JSON
|
||||||
|
↓
|
||||||
|
convert-w2p.js(提取+映射)
|
||||||
|
↓
|
||||||
|
Schema JSON
|
||||||
|
↓
|
||||||
|
pptxgenjs(渲染)
|
||||||
|
↓
|
||||||
|
.pptx 文件
|
||||||
|
```
|
||||||
|
|
||||||
|
### convert-w2p.js 处理流程
|
||||||
|
1. findSlides() — 找 page/slide-N 容器
|
||||||
|
2. collectSlideChildren() — 递归提取子节点(保留层级)
|
||||||
|
3. collectOrphans() — 收集游离节点归入最近 slide
|
||||||
|
4. 排序:FRAME 在前,TEXT 在后
|
||||||
|
5. 映射循环:遍历每个子节点
|
||||||
|
- TEXT → text 对象(含字号/字体/颜色/粗体/对齐)
|
||||||
|
- IMAGE → image 对象
|
||||||
|
- FRAME with bg → shape 对象(含 fill/border/shadow/rectRadius)
|
||||||
|
- 四边 border → 独立线条 shape
|
||||||
|
6. 输出 Schema JSON + 调用 pptxgenjs 生成 PPTX
|
||||||
|
|
||||||
|
## 5. 下一步行动
|
||||||
|
|
||||||
|
### 短期(本次迭代)
|
||||||
|
- [ ] 实现 P0:opacity、whiteSpace、zIndex、lineHeight
|
||||||
|
- [ ] 实现 P1:letterSpacing、textDecoration、display/visibility 过滤
|
||||||
|
|
||||||
|
### 中期
|
||||||
|
- [ ] 建立 web-to-ppt JSON 的完整字段文档
|
||||||
|
- [ ] 验证不同 HTML 结构的 JSON 输出一致性
|
||||||
|
- [ ] 处理 transform(旋转)
|
||||||
|
|
||||||
|
### 长期
|
||||||
|
- [ ] 参考设计稿,实现高度还原的 PPT 输出
|
||||||
|
- [ ] 处理复杂布局(grid/flex 嵌套)
|
||||||
@@ -0,0 +1,781 @@
|
|||||||
|
# 图表参数 (Chart Props)
|
||||||
|
|
||||||
|
图表参数来自 `IChartOpts` 及其继承的所有接口,用于 `slide.addChart()` 方法。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 图表类型 (CHART_NAME)
|
||||||
|
|
||||||
|
- 类型:`'area' | 'bar' | 'bar3D' | 'bubble' | 'doughnut' | 'line' | 'pie' | 'radar' | 'scatter'`
|
||||||
|
|
||||||
|
可通过 `pptxgen.ChartType.XXX` 常量引用:
|
||||||
|
- `ChartType.area`、`ChartType.bar`、`ChartType.bar3d`、`ChartType.bubble`、`ChartType.bubble3d`、
|
||||||
|
`ChartType.doughnut`、`ChartType.line`、`ChartType.pie`、`ChartType.radar`、`ChartType.scatter`
|
||||||
|
|
||||||
|
## 多图类型 (IChartMulti)
|
||||||
|
|
||||||
|
```
|
||||||
|
{ type: CHART_NAME, data: OptsChartData[], options: IChartOpts }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 数据接口 (OptsChartData)
|
||||||
|
|
||||||
|
- **labels**:分类标签
|
||||||
|
- 类型:`string[] | string[][]`
|
||||||
|
- 示例:`['Year 2000', 'Year 2010', 'Year 2020']`
|
||||||
|
- 说明:`string[][]` 用于多级分类轴,始于 v3.11.0
|
||||||
|
|
||||||
|
- **name**:系列名称
|
||||||
|
- 类型:`string`
|
||||||
|
- 示例:`'Locations'`
|
||||||
|
|
||||||
|
- **sizes**:气泡大小
|
||||||
|
- 类型:`number[]`
|
||||||
|
- 说明:仅用于气泡图
|
||||||
|
- 示例:`[5, 1, 5, 1]`
|
||||||
|
|
||||||
|
- **values**:分类数值
|
||||||
|
- 类型:`number[]`
|
||||||
|
- 示例:`[2000, 2010, 2020]`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 位置属性 (PositionProps)
|
||||||
|
|
||||||
|
- **x**:水平位置
|
||||||
|
- 类型:`Coord`(`number | `${number}%``)
|
||||||
|
- 单位:英寸(number)或百分比(`'50%'`)
|
||||||
|
|
||||||
|
- **y**:垂直位置
|
||||||
|
- 类型:`Coord`
|
||||||
|
- 单位:英寸或百分比
|
||||||
|
|
||||||
|
- **w**:宽度
|
||||||
|
- 类型:`Coord`
|
||||||
|
- 单位:英寸或百分比
|
||||||
|
|
||||||
|
- **h**:高度
|
||||||
|
- 类型:`Coord`
|
||||||
|
- 单位:英寸或百分比
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 对象名称属性 (ObjectNameProps)
|
||||||
|
|
||||||
|
- **objectName**:对象名称
|
||||||
|
- 类型:`string`
|
||||||
|
- 说明:替代默认的 "Object N" 名称
|
||||||
|
- 默认值:`'Object 1'`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 基础图表属性 (IChartPropsBase)
|
||||||
|
|
||||||
|
- **axisPos**:坐标轴位置
|
||||||
|
- 类型:`'b' | 'l' | 'r' | 't'`
|
||||||
|
- 取值范围:`b`(下)、`l`(左)、`r`(右)、`t`(上)
|
||||||
|
|
||||||
|
- **chartColors**:图表系列颜色
|
||||||
|
- 类型:`HexColor[]`
|
||||||
|
- 格式:6 位十六进制颜色数组(无 `#`)
|
||||||
|
|
||||||
|
- **chartColorsOpacity**:系列颜色不透明度
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`100`
|
||||||
|
- 示例:`50`(50% 不透明)
|
||||||
|
|
||||||
|
- **dataBorder**:数据点边框
|
||||||
|
- 类型:`BorderProps`
|
||||||
|
- 子参数:
|
||||||
|
- **dataBorder.type**:`'none' | 'dash' | 'solid'`
|
||||||
|
- **dataBorder.color**:`HexColor`
|
||||||
|
- **dataBorder.pt**:边框粗细(pt)
|
||||||
|
|
||||||
|
- **displayBlanksAs**:空值显示方式
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **invertedColors**:反色数组
|
||||||
|
- 类型:`HexColor[]`
|
||||||
|
|
||||||
|
- **lang**:语言
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **layout**:图表在幻灯片中的位置
|
||||||
|
- 类型:`PositionProps`
|
||||||
|
|
||||||
|
- **shadow**:阴影
|
||||||
|
- 类型:`ShadowProps`
|
||||||
|
|
||||||
|
- **showLabel**:显示数据标签
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **showLeaderLines**:显示引导线
|
||||||
|
- 类型:`boolean`
|
||||||
|
|
||||||
|
- **showLegend**:显示图例
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **showPercent**:显示百分比
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **showSerName**:显示系列名称
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **showTitle**:显示图表标题
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **showValue**:显示数值
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **v3DPerspective**:3D 透视
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`120`
|
||||||
|
- 默认值:`30`
|
||||||
|
|
||||||
|
- **v3DRAngAx**:直角坐标轴
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 说明:启用时覆盖 `v3DPerspective`,以第一人称视角显示图表
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **v3DRotX**:X 轴旋转
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`359.9`
|
||||||
|
- 默认值:`30`
|
||||||
|
|
||||||
|
- **v3DRotY**:Y 轴旋转
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`359.9`
|
||||||
|
- 默认值:`30`
|
||||||
|
|
||||||
|
- **chartArea**:图表区域样式
|
||||||
|
- 类型:`IChartAreaProps`(继承 `IChartPropsFillLine`)
|
||||||
|
- 子参数:
|
||||||
|
- **chartArea.border**:边框
|
||||||
|
- **chartArea.fill**:填充
|
||||||
|
- **chartArea.roundedCorners**:圆角
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`true`
|
||||||
|
- 始于:v3.11
|
||||||
|
|
||||||
|
- **plotArea**:绘图区样式
|
||||||
|
- 类型:`IChartPropsFillLine`
|
||||||
|
- 子参数:
|
||||||
|
- **plotArea.border**:绘图区边框
|
||||||
|
- **plotArea.fill**:绘图区填充
|
||||||
|
- 始于:v3.11
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 分类轴属性 (IChartPropsAxisCat)
|
||||||
|
|
||||||
|
- **catAxes**:多图多轴设置
|
||||||
|
- 类型:`IChartPropsAxisCat[]`
|
||||||
|
|
||||||
|
- **catAxisBaseTimeUnit**:分类轴基础时间单位
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **catAxisCrossesAt**:数值轴交叉位置
|
||||||
|
- 类型:`number | 'autoZero'`
|
||||||
|
|
||||||
|
- **catAxisHidden**:隐藏分类轴
|
||||||
|
- 类型:`boolean`
|
||||||
|
|
||||||
|
- **catAxisLabelColor**:轴标签颜色
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **catAxisLabelFontBold**:轴标签粗体
|
||||||
|
- 类型:`boolean`
|
||||||
|
|
||||||
|
- **catAxisLabelFontFace**:轴标签字体
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **catAxisLabelFontItalic**:轴标签斜体
|
||||||
|
- 类型:`boolean`
|
||||||
|
|
||||||
|
- **catAxisLabelFontSize**:轴标签字号
|
||||||
|
- 类型:`number`
|
||||||
|
- 单位:pt
|
||||||
|
- 取值范围:无特殊约束范围
|
||||||
|
- 无默认值
|
||||||
|
|
||||||
|
- **catAxisLabelFrequency**:标签频率
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **catAxisLabelPos**:标签位置
|
||||||
|
- 类型:`'none' | 'low' | 'high' | 'nextTo'`
|
||||||
|
|
||||||
|
- **catAxisLabelRotate**:标签旋转角度
|
||||||
|
- 类型:`number`
|
||||||
|
- 单位:度
|
||||||
|
- 取值范围:无特殊约束范围
|
||||||
|
- 无默认值
|
||||||
|
|
||||||
|
- **catAxisLineColor**:轴线颜色
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **catAxisLineShow**:显示轴线
|
||||||
|
- 类型:`boolean`
|
||||||
|
|
||||||
|
- **catAxisLineSize**:轴线粗细
|
||||||
|
- 类型:`number`
|
||||||
|
- 单位:pt
|
||||||
|
- 取值范围:无特殊约束范围
|
||||||
|
- 无默认值
|
||||||
|
|
||||||
|
- **catAxisLineStyle**:轴线样式
|
||||||
|
- 类型:`'solid' | 'dash' | 'dot'`
|
||||||
|
|
||||||
|
- **catAxisMajorTickMark**:主轴刻度标记
|
||||||
|
- 类型:`ChartAxisTickMark`(`'none' | 'inside' | 'outside' | 'cross'`)
|
||||||
|
|
||||||
|
- **catAxisMajorTimeUnit**:主要时间单位
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **catAxisMajorUnit**:主要刻度单位
|
||||||
|
- 类型:`number`
|
||||||
|
- 单位:无单位(数值刻度)
|
||||||
|
- 取值范围:无特殊约束范围
|
||||||
|
- 无默认值
|
||||||
|
|
||||||
|
- **catAxisMaxVal**:轴最大值
|
||||||
|
- 类型:`number`
|
||||||
|
|
||||||
|
- **catAxisMinorTickMark**:副轴刻度标记
|
||||||
|
- 类型:`ChartAxisTickMark`
|
||||||
|
|
||||||
|
- **catAxisMinorTimeUnit**:次要时间单位
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **catAxisMinorUnit**:次要刻度单位
|
||||||
|
- 类型:`number`
|
||||||
|
- 单位:无单位(数值刻度)
|
||||||
|
- 取值范围:无特殊约束范围
|
||||||
|
- 无默认值
|
||||||
|
|
||||||
|
- **catAxisMinVal**:轴最小值
|
||||||
|
- 类型:`number`
|
||||||
|
|
||||||
|
- **catAxisMultiLevelLabels**:多级标签
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 始于:v3.11.0
|
||||||
|
|
||||||
|
- **catAxisOrientation**:轴方向
|
||||||
|
- 类型:`'minMax'`
|
||||||
|
|
||||||
|
- **catAxisTitle**:轴标题
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **catAxisTitleColor**:轴标题颜色
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **catAxisTitleFontFace**:轴标题字体
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **catAxisTitleFontSize**:轴标题字号
|
||||||
|
- 类型:`number`
|
||||||
|
|
||||||
|
- **catAxisTitleRotate**:轴标题旋转
|
||||||
|
- 类型:`number`
|
||||||
|
- 单位:度
|
||||||
|
- 取值范围:无特殊约束范围
|
||||||
|
- 无默认值
|
||||||
|
|
||||||
|
- **catGridLine**:分类网格线
|
||||||
|
- 类型:`OptsChartGridLine`
|
||||||
|
|
||||||
|
- **catLabelFormatCode**:分类标签格式代码
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **secondaryCatAxis**:使用次要分类轴
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **showCatAxisTitle**:显示分类轴标题
|
||||||
|
- 类型:`boolean`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 系列轴属性 (IChartPropsAxisSer)
|
||||||
|
|
||||||
|
- **serAxisBaseTimeUnit**:系列轴基础时间单位
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **serAxisHidden**:隐藏系列轴
|
||||||
|
- 类型:`boolean`
|
||||||
|
|
||||||
|
- **serAxisLabelColor**:系列轴标签颜色
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **serAxisLabelFontBold**:系列轴标签粗体
|
||||||
|
- 类型:`boolean`
|
||||||
|
|
||||||
|
- **serAxisLabelFontFace**:系列轴标签字体
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **serAxisLabelFontItalic**:系列轴标签斜体
|
||||||
|
- 类型:`boolean`
|
||||||
|
|
||||||
|
- **serAxisLabelFontSize**:系列轴标签字号
|
||||||
|
- 类型:`number`
|
||||||
|
- 单位:pt
|
||||||
|
- 取值范围:无特殊约束范围
|
||||||
|
- 无默认值
|
||||||
|
|
||||||
|
- **serAxisLabelFrequency**:系列轴标签频率
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **serAxisLabelPos**:系列轴标签位置
|
||||||
|
- 类型:`'none' | 'low' | 'high' | 'nextTo'`
|
||||||
|
|
||||||
|
- **serAxisLineColor**:系列轴线颜色
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **serAxisLineShow**:显示系列轴线
|
||||||
|
- 类型:`boolean`
|
||||||
|
|
||||||
|
- **serAxisMajorTimeUnit**:系列轴主要时间单位
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **serAxisMajorUnit**:系列轴主要刻度单位
|
||||||
|
- 类型:`number`
|
||||||
|
|
||||||
|
- **serAxisMinorTimeUnit**:系列轴次要时间单位
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **serAxisMinorUnit**:系列轴次要刻度单位
|
||||||
|
- 类型:`number`
|
||||||
|
|
||||||
|
- **serAxisOrientation**:系列轴方向
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **serAxisTitle**:系列轴标题
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **serAxisTitleColor**:系列轴标题颜色
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **serAxisTitleFontFace**:系列轴标题字体
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **serAxisTitleFontSize**:系列轴标题字号
|
||||||
|
- 类型:`number`
|
||||||
|
|
||||||
|
- **serAxisTitleRotate**:系列轴标题旋转
|
||||||
|
- 类型:`number`
|
||||||
|
|
||||||
|
- **serGridLine**:系列网格线
|
||||||
|
- 类型:`OptsChartGridLine`
|
||||||
|
|
||||||
|
- **serLabelFormatCode**:系列标签格式代码
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **showSerAxisTitle**:显示系列轴标题
|
||||||
|
- 类型:`boolean`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 数值轴属性 (IChartPropsAxisVal)
|
||||||
|
|
||||||
|
- **secondaryValAxis**:使用次要数值轴
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **showValAxisTitle**:显示数值轴标题
|
||||||
|
- 类型:`boolean`
|
||||||
|
|
||||||
|
- **valAxes**:多图多数值轴
|
||||||
|
- 类型:`IChartPropsAxisVal[]`
|
||||||
|
|
||||||
|
- **valAxisCrossesAt**:数值轴交叉位置
|
||||||
|
- 类型:`number | 'autoZero'`
|
||||||
|
|
||||||
|
- **valAxisDisplayUnit**:显示单位
|
||||||
|
- 类型:`'billions' | 'hundredMillions' | 'hundreds' | 'hundredThousands' | 'millions' | 'tenMillions' | 'tenThousands' | 'thousands' | 'trillions'`
|
||||||
|
|
||||||
|
- **valAxisDisplayUnitLabel**:显示单位标签
|
||||||
|
- 类型:`boolean`
|
||||||
|
|
||||||
|
- **valAxisHidden**:隐藏数值轴
|
||||||
|
- 类型:`boolean`
|
||||||
|
|
||||||
|
- **valAxisLabelColor**:数值轴标签颜色
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **valAxisLabelFontBold**:数值轴标签粗体
|
||||||
|
- 类型:`boolean`
|
||||||
|
|
||||||
|
- **valAxisLabelFontFace**:数值轴标签字体
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **valAxisLabelFontItalic**:数值轴标签斜体
|
||||||
|
- 类型:`boolean`
|
||||||
|
|
||||||
|
- **valAxisLabelFontSize**:数值轴标签字号
|
||||||
|
- 类型:`number`
|
||||||
|
- 单位:pt
|
||||||
|
- 取值范围:无特殊约束范围
|
||||||
|
- 无默认值
|
||||||
|
|
||||||
|
- **valAxisLabelFormatCode**:数值轴标签格式代码
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **valAxisLabelPos**:数值轴标签位置
|
||||||
|
- 类型:`'none' | 'low' | 'high' | 'nextTo'`
|
||||||
|
|
||||||
|
- **valAxisLabelRotate**:数值轴标签旋转
|
||||||
|
- 类型:`number`
|
||||||
|
- 单位:度
|
||||||
|
- 取值范围:无特殊约束范围
|
||||||
|
- 无默认值
|
||||||
|
|
||||||
|
- **valAxisLineColor**:数值轴线颜色
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **valAxisLineShow**:显示数值轴线
|
||||||
|
- 类型:`boolean`
|
||||||
|
|
||||||
|
- **valAxisLineSize**:数值轴线粗细
|
||||||
|
- 类型:`number`
|
||||||
|
- 单位:pt
|
||||||
|
- 取值范围:无特殊约束范围
|
||||||
|
- 无默认值
|
||||||
|
|
||||||
|
- **valAxisLineStyle**:数值轴线样式
|
||||||
|
- 类型:`'solid' | 'dash' | 'dot'`
|
||||||
|
|
||||||
|
- **valAxisLogScaleBase**:对数刻度基数
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`2`–`99`
|
||||||
|
- 始于:v3.5.0
|
||||||
|
|
||||||
|
- **valAxisMajorTickMark**:数值轴主刻度标记
|
||||||
|
- 类型:`ChartAxisTickMark`
|
||||||
|
|
||||||
|
- **valAxisMajorUnit**:数值轴主要刻度单位
|
||||||
|
- 类型:`number`
|
||||||
|
- 单位:无单位(数值刻度)
|
||||||
|
- 取值范围:无特殊约束范围
|
||||||
|
- 无默认值
|
||||||
|
|
||||||
|
- **valAxisMaxVal**:数值轴最大值
|
||||||
|
- 类型:`number`
|
||||||
|
- 单位:无单位(数值与数据一致)
|
||||||
|
- 取值范围:无特殊约束范围
|
||||||
|
- 无默认值
|
||||||
|
|
||||||
|
- **valAxisMinorTickMark**:数值轴副刻度标记
|
||||||
|
- 类型:`ChartAxisTickMark`
|
||||||
|
|
||||||
|
- **valAxisMinVal**:数值轴最小值
|
||||||
|
- 类型:`number`
|
||||||
|
- 单位:无单位(数值与数据一致)
|
||||||
|
- 取值范围:无特殊约束范围
|
||||||
|
- 无默认值
|
||||||
|
|
||||||
|
- **valAxisOrientation**:数值轴方向
|
||||||
|
- 类型:`'minMax'`
|
||||||
|
|
||||||
|
- **valAxisTitle**:数值轴标题
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **valAxisTitleColor**:数值轴标题颜色
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **valAxisTitleFontFace**:数值轴标题字体
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **valAxisTitleFontSize**:数值轴标题字号
|
||||||
|
- 类型:`number`
|
||||||
|
|
||||||
|
- **valAxisTitleRotate**:数值轴标题旋转
|
||||||
|
- 类型:`number`
|
||||||
|
|
||||||
|
- **valGridLine**:数值网格线
|
||||||
|
- 类型:`OptsChartGridLine`
|
||||||
|
|
||||||
|
- **valLabelFormatCode**:数值标签格式代码
|
||||||
|
- 类型:`string`
|
||||||
|
- 始于:v3.3.0
|
||||||
|
- 示例:`'#%'`、`'0.00%'`、`'$0.00'`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 柱状图属性 (IChartPropsChartBar)
|
||||||
|
|
||||||
|
- **bar3DShape**:3D 柱形形状
|
||||||
|
- 类型:`string`
|
||||||
|
- 取值范围:`'box' | 'cylinder' | 'cone' | 'pyramid'`(PowerPoint 标准值),类型声明为 string,也可接受其他值
|
||||||
|
- 默认值:`'box'`
|
||||||
|
|
||||||
|
- **barDir**:柱形方向
|
||||||
|
- 类型:`string`
|
||||||
|
- 取值范围:`'bar'`(水平)、`'col'`(垂直)
|
||||||
|
- 默认值:`'col'`
|
||||||
|
|
||||||
|
- **barGapDepthPct**:3D 柱形深度间距百分比
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`500`
|
||||||
|
- 默认值:`150`
|
||||||
|
- 单位:%
|
||||||
|
|
||||||
|
- **barGapWidthPct**:间距宽度百分比
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`500`
|
||||||
|
- 默认值:`150`
|
||||||
|
|
||||||
|
- **barGrouping**:分组方式
|
||||||
|
- 类型:`string`
|
||||||
|
- 取值范围:`'clustered' | 'stacked' | 'standard'`(PowerPoint 标准值),类型声明为 string,也可接受其他值
|
||||||
|
- 默认值:`'clustered'`
|
||||||
|
|
||||||
|
- **barOverlapPct**:系列重叠百分比
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`-100`–`100`
|
||||||
|
- 默认值:`0`
|
||||||
|
- 始于:v3.9.0
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 圆环图属性 (IChartPropsChartDoughnut)
|
||||||
|
|
||||||
|
- **dataNoEffects**:禁用数据点效果
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **holeSize**:中心孔大小
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`90`(百分比)
|
||||||
|
- 默认值:`50`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 折线图属性 (IChartPropsChartLine)
|
||||||
|
|
||||||
|
- **lineCap**:线帽类型
|
||||||
|
- 类型:`ChartLineCap`(`'flat' | 'round' | 'square'`)
|
||||||
|
- 默认值:`'flat'`
|
||||||
|
|
||||||
|
- **lineDash**:线型
|
||||||
|
- 类型:`'dash' | 'dashDot' | 'lgDash' | 'lgDashDot' | 'lgDashDotDot' | 'solid' | 'sysDash' | 'sysDot'`
|
||||||
|
- 默认值:`'solid'`
|
||||||
|
|
||||||
|
- **lineDataSymbol**:数据点标记类型
|
||||||
|
- 类型:`'circle' | 'dash' | 'diamond' | 'dot' | 'none' | 'square' | 'triangle'`
|
||||||
|
- 默认值:`'circle'`
|
||||||
|
|
||||||
|
- **lineDataSymbolLineColor**:标记边框颜色
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **lineDataSymbolLineSize**:标记边框宽度
|
||||||
|
- 类型:`number`
|
||||||
|
- 默认值:`0.75`
|
||||||
|
|
||||||
|
- **lineDataSymbolSize**:标记大小
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`2`–`72`
|
||||||
|
- 默认值:`6`
|
||||||
|
|
||||||
|
- **lineSize**:线条宽度
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`1584`
|
||||||
|
- 默认值:`2`
|
||||||
|
|
||||||
|
- **lineSmooth**:平滑线
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 饼图属性 (IChartPropsChartPie)
|
||||||
|
|
||||||
|
- **dataNoEffects**:禁用数据点效果
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **firstSliceAng**:第一扇区起始角度
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`359`
|
||||||
|
- 默认值:`0`
|
||||||
|
- 始于:v3.4.0
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 雷达图属性 (IChartPropsChartRadar)
|
||||||
|
|
||||||
|
- **radarStyle**:雷达图样式
|
||||||
|
- 类型:`'standard' | 'marker' | 'filled'`
|
||||||
|
- 默认值:`'standard'`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 数据标签属性 (IChartPropsDataLabel)
|
||||||
|
|
||||||
|
- **dataLabelBkgrdColors**:数据标签背景色
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **dataLabelColor**:数据标签颜色
|
||||||
|
- 类型:`string`
|
||||||
|
- 取值范围:无特殊约束范围
|
||||||
|
- 无默认值
|
||||||
|
|
||||||
|
- **dataLabelFontBold**:数据标签粗体
|
||||||
|
- 类型:`boolean`
|
||||||
|
|
||||||
|
- **dataLabelFontFace**:数据标签字体
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **dataLabelFontItalic**:数据标签斜体
|
||||||
|
- 类型:`boolean`
|
||||||
|
|
||||||
|
- **dataLabelFontSize**:数据标签字号
|
||||||
|
- 类型:`number`
|
||||||
|
- 单位:pt
|
||||||
|
- 取值范围:无特殊约束范围
|
||||||
|
- 无默认值
|
||||||
|
|
||||||
|
- **dataLabelFormatCode**:数据标签格式代码
|
||||||
|
- 类型:`string`
|
||||||
|
- 示例:`'#%'`、`'0.00%'`、`'$0.00'`
|
||||||
|
|
||||||
|
- **dataLabelFormatScatter**:散点图标签格式
|
||||||
|
- 类型:`'custom' | 'customXY' | 'XY'`
|
||||||
|
|
||||||
|
- **dataLabelPosition**:数据标签位置
|
||||||
|
- 类型:`'b' | 'bestFit' | 'ctr' | 'l' | 'r' | 't' | 'inEnd' | 'outEnd'`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 数据表属性 (IChartPropsDataTable)
|
||||||
|
|
||||||
|
- **dataTableFontSize**:数据表字号
|
||||||
|
- 类型:`number`
|
||||||
|
- 单位:pt
|
||||||
|
- 取值范围:无特殊约束范围
|
||||||
|
- 无默认值
|
||||||
|
|
||||||
|
- **dataTableFormatCode**:数据表格式代码
|
||||||
|
- 类型:`string`
|
||||||
|
- 始于:v3.3.0
|
||||||
|
|
||||||
|
- **showDataTable**:显示数据表
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **showDataTableHorzBorder**:显示水平边框
|
||||||
|
- 类型:`boolean`
|
||||||
|
|
||||||
|
- **showDataTableKeys**:显示图例标示
|
||||||
|
- 类型:`boolean`
|
||||||
|
|
||||||
|
- **showDataTableOutline**:显示外框
|
||||||
|
- 类型:`boolean`
|
||||||
|
|
||||||
|
- **showDataTableVertBorder**:显示垂直边框
|
||||||
|
- 类型:`boolean`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 图例属性 (IChartPropsLegend)
|
||||||
|
|
||||||
|
- **legendColor**:图例文字颜色
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **legendFontFace**:图例字体
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **legendFontSize**:图例字号
|
||||||
|
- 类型:`number`
|
||||||
|
|
||||||
|
- **legendPos**:图例位置
|
||||||
|
- 类型:`'b' | 'l' | 'r' | 't' | 'tr'`
|
||||||
|
- 取值范围:`b`(底部)、`l`(左侧)、`r`(右侧)、`t`(顶部)、`tr`(右上)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 标题属性 (IChartPropsTitle)
|
||||||
|
|
||||||
|
继承 `TextBaseProps`。
|
||||||
|
|
||||||
|
- **title**:图表标题文字
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **titleAlign**:标题对齐
|
||||||
|
- 类型:`string`
|
||||||
|
- 取值范围:`'l' | 'ctr' | 'r'`
|
||||||
|
- 默认值:`'ctr'`
|
||||||
|
|
||||||
|
- **titleBold**:标题粗体
|
||||||
|
- 类型:`boolean`
|
||||||
|
|
||||||
|
- **titleColor**:标题颜色
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **titleFontFace**:标题字体
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **titleFontSize**:标题字号
|
||||||
|
- 类型:`number`
|
||||||
|
|
||||||
|
- **titlePos**:标题位置
|
||||||
|
- 类型:`{ x: number, y: number }`
|
||||||
|
|
||||||
|
- **titleRotate**:标题旋转
|
||||||
|
- 类型:`number`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 网格线属性 (OptsChartGridLine)
|
||||||
|
|
||||||
|
- **cap**:线帽类型
|
||||||
|
- 类型:`ChartLineCap`(`'flat' | 'round' | 'square'`)
|
||||||
|
- 默认值:`'flat'`
|
||||||
|
|
||||||
|
- **color**:网格线颜色
|
||||||
|
- 类型:`HexColor`
|
||||||
|
|
||||||
|
- **size**:网格线粗细
|
||||||
|
- 类型:`number`
|
||||||
|
- 单位:pt
|
||||||
|
- 取值范围:无特殊约束范围
|
||||||
|
- 无默认值
|
||||||
|
|
||||||
|
- **style**:网格线样式
|
||||||
|
- 类型:`'solid' | 'dash' | 'dot' | 'none'`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## IChartPropsFillLine
|
||||||
|
|
||||||
|
用于 `chartArea`、`plotArea` 的填充与边框基础类型。
|
||||||
|
|
||||||
|
- **border**:边框
|
||||||
|
- 类型:`BorderProps`
|
||||||
|
|
||||||
|
- **fill**:填充
|
||||||
|
- 类型:`ShapeFillProps`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## IChartOpts 附加属性
|
||||||
|
|
||||||
|
- **altText**:替代文字(无障碍)
|
||||||
|
- 类型:`string`
|
||||||
|
- 说明:PowerPoint 中右击图表 > "编辑替代文字"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 已废弃参数
|
||||||
|
|
||||||
|
| 已废弃参数 | 替代参数 | 废弃版本 |
|
||||||
|
|---|---|---|
|
||||||
|
| `border` | `plotArea.border` | v3.11.0 |
|
||||||
|
| `fill` | `plotArea.fill` | v3.11.0 |
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
# 坐标与尺寸参数 (Coordinate Props)
|
||||||
|
|
||||||
|
坐标与尺寸参数用于指定元素在幻灯片上的位置和大小。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Coord 类型
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type Coord = number | `${number}%`
|
||||||
|
```
|
||||||
|
|
||||||
|
- **number**:英寸值
|
||||||
|
- 说明:以英寸为单位的数值
|
||||||
|
- 示例:`1.5`(1.5 英寸)、`10`(10 英寸)
|
||||||
|
|
||||||
|
- **`${number}%`**:百分比
|
||||||
|
- 说明:相对于幻灯片尺寸的百分比
|
||||||
|
- 示例:`'50%'`(一半宽度/高度)、`'100%'`(全宽/全高)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## PositionProps
|
||||||
|
|
||||||
|
所有可放置元素通用的位置属性。
|
||||||
|
|
||||||
|
- **x**:水平位置
|
||||||
|
- 类型:`Coord`
|
||||||
|
- 单位:英寸或百分比
|
||||||
|
- 示例:`10.25`、`'75%'`
|
||||||
|
|
||||||
|
- **y**:垂直位置
|
||||||
|
- 类型:`Coord`
|
||||||
|
- 单位:英寸或百分比
|
||||||
|
- 示例:`10.25`、`'75%'`
|
||||||
|
|
||||||
|
- **w**:宽度
|
||||||
|
- 类型:`Coord`
|
||||||
|
- 单位:英寸或百分比
|
||||||
|
- 示例:`10.25`、`'75%'`
|
||||||
|
|
||||||
|
- **h**:高度
|
||||||
|
- 类型:`Coord`
|
||||||
|
- 单位:英寸或百分比
|
||||||
|
- 示例:`10.25`、`'75%'`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 标准布局尺寸
|
||||||
|
|
||||||
|
| 布局常量名 | 宽度 | 高度 | 宽高比 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `LAYOUT_4x3` | 10" | 7.5" | 4:3 |
|
||||||
|
| `LAYOUT_16x9` | 10" | 5.625" | 16:9 |
|
||||||
|
| `LAYOUT_16x10` | 10" | 6.25" | 16:10 |
|
||||||
|
| `LAYOUT_WIDE` | 13.33" | 7.5" | ~16:9 |
|
||||||
|
|
||||||
|
可通过 `pptx.defineLayout({ name, width, height })` 自定义布局。
|
||||||
|
|
||||||
|
### px 换算参考
|
||||||
|
|
||||||
|
在 96dpi 屏幕上的参考换算关系:
|
||||||
|
- 1 英寸 = 96px
|
||||||
|
- 1pt = 1/72 英寸 ≈ 1.33px
|
||||||
|
- 1cm ≈ 0.394 英寸
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Margin 类型
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type Margin = number | [number, number, number, number]
|
||||||
|
```
|
||||||
|
|
||||||
|
- **number**:统一边距
|
||||||
|
- 说明:四边使用相同值
|
||||||
|
- 单位:pt
|
||||||
|
- 示例:`0`(无边距)、`10`(四边 10pt)
|
||||||
|
|
||||||
|
- **[top, right, bottom, left]**:四边独立边距
|
||||||
|
- 说明:按上右下左(TRBL)顺序分别设置
|
||||||
|
- 单位:pt
|
||||||
|
- 示例:`[10, 5, 10, 5]`(上下 10pt、左右 5pt)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Color 类型
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type HexColor = string // 6 位十六进制,无 # 前缀
|
||||||
|
type ThemeColor = 'tx1' | 'tx2' | 'bg1' | 'bg2'
|
||||||
|
| 'accent1' | 'accent2' | 'accent3'
|
||||||
|
| 'accent4' | 'accent5' | 'accent6'
|
||||||
|
type Color = HexColor | ThemeColor
|
||||||
|
```
|
||||||
|
|
||||||
|
### 十六进制颜色 (HexColor)
|
||||||
|
|
||||||
|
- 格式:6 位十六进制值,不含 `#` 前缀
|
||||||
|
- 示例:`'FF0000'`(红色)、`'0088CC'`(蓝色)、`'FFFFFF'`(白色)
|
||||||
|
|
||||||
|
### 主题色 (ThemeColor)
|
||||||
|
|
||||||
|
| 值 | 说明 | PowerPoint 名称 |
|
||||||
|
|---|---|---|
|
||||||
|
| `'tx1'` | 文字 1 | Theme Color Text 1 |
|
||||||
|
| `'tx2'` | 文字 2 | Theme Color Text 2 |
|
||||||
|
| `'bg1'` | 背景 1 | Theme Color Background 1 |
|
||||||
|
| `'bg2'` | 背景 2 | Theme Color Background 2 |
|
||||||
|
| `'accent1'` | 强调色 1 | Accent 1 |
|
||||||
|
| `'accent2'` | 强调色 2 | Accent 2 |
|
||||||
|
| `'accent3'` | 强调色 3 | Accent 3 |
|
||||||
|
| `'accent4'` | 强调色 4 | Accent 4 |
|
||||||
|
| `'accent5'` | 强调色 5 | Accent 5 |
|
||||||
|
| `'accent6'` | 强调色 6 | Accent 6 |
|
||||||
|
|
||||||
|
可通过 `pptx.SchemeColor.XXX` 常量引用:
|
||||||
|
- `pptx.SchemeColor.text1` → `'tx1'`
|
||||||
|
- `pptx.SchemeColor.background1` → `'bg1'`
|
||||||
|
- `pptx.SchemeColor.accent1` → `'accent1'`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 对齐类型
|
||||||
|
|
||||||
|
### HAlign
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type HAlign = 'left' | 'center' | 'right' | 'justify'
|
||||||
|
```
|
||||||
|
|
||||||
|
| 值 | 说明 |
|
||||||
|
|---|---|
|
||||||
|
| `'left'` | 左对齐 |
|
||||||
|
| `'center'` | 居中对齐 |
|
||||||
|
| `'right'` | 右对齐 |
|
||||||
|
| `'justify'` | 两端对齐 |
|
||||||
|
|
||||||
|
### VAlign
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type VAlign = 'top' | 'middle' | 'bottom'
|
||||||
|
```
|
||||||
|
|
||||||
|
| 值 | 说明 |
|
||||||
|
|---|---|
|
||||||
|
| `'top'` | 顶部对齐 |
|
||||||
|
| `'middle'` | 垂直居中 |
|
||||||
|
| `'bottom'` | 底部对齐 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 其他常用类型
|
||||||
|
|
||||||
|
### ChartAxisTickMark
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type ChartAxisTickMark = 'none' | 'inside' | 'outside' | 'cross'
|
||||||
|
```
|
||||||
|
|
||||||
|
### ChartLineCap
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type ChartLineCap = 'flat' | 'round' | 'square'
|
||||||
|
```
|
||||||
|
|
||||||
|
### SHAPE_NAME
|
||||||
|
|
||||||
|
形状名称类型,包含约 180 种 PowerPoint 形状的字面量联合类型。
|
||||||
|
|
||||||
|
### CHART_NAME
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type CHART_NAME = 'area' | 'bar' | 'bar3D' | 'bubble' | 'doughnut' | 'line' | 'pie' | 'radar' | 'scatter'
|
||||||
|
```
|
||||||
@@ -0,0 +1,197 @@
|
|||||||
|
# 图片参数 (Image Props)
|
||||||
|
|
||||||
|
图片参数来自 `ImageProps`(继承 `PositionProps`、`DataOrPathProps`、`ObjectNameProps`),用于 `slide.addImage()` 方法。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 位置属性 (PositionProps)
|
||||||
|
|
||||||
|
- **x**:水平位置
|
||||||
|
- 类型:`Coord`(`number | `${number}%``)
|
||||||
|
- 单位:英寸(number)或百分比(`'50%'`)
|
||||||
|
|
||||||
|
- **y**:垂直位置
|
||||||
|
- 类型:`Coord`
|
||||||
|
- 单位:英寸或百分比
|
||||||
|
|
||||||
|
- **w**:宽度
|
||||||
|
- 类型:`Coord`
|
||||||
|
- 单位:英寸或百分比
|
||||||
|
|
||||||
|
- **h**:高度
|
||||||
|
- 类型:`Coord`
|
||||||
|
- 单位:英寸或百分比
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 数据/路径属性 (DataOrPathProps)
|
||||||
|
|
||||||
|
- **path**:图片 URL 或本地路径
|
||||||
|
- 类型:`string`
|
||||||
|
- 示例:`'https://onedrives.com/myimg.png'`、`'/home/gitbrent/images/myimg.png'`
|
||||||
|
|
||||||
|
- **data**:base64 编码数据
|
||||||
|
- 类型:`string`
|
||||||
|
- 格式:`'image/png;base64,iVtDafDrBF[...]='`
|
||||||
|
- 说明:base64 编码的图片数据,用时可避免路径/服务器问题
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 对象名称属性 (ObjectNameProps)
|
||||||
|
|
||||||
|
- **objectName**:对象名称
|
||||||
|
- 类型:`string`
|
||||||
|
- 说明:替代默认的 "Object N" 名称,在 PowerPoint 选择窗格中显示
|
||||||
|
- 默认值:`'Object 1'`
|
||||||
|
- 始于:v3.10.0
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 图片专有属性 (ImageProps)
|
||||||
|
|
||||||
|
- **altText**:替代文字(无障碍)
|
||||||
|
- 类型:`string`
|
||||||
|
- 说明:PowerPoint 中右击图片 > "编辑替代文字"
|
||||||
|
|
||||||
|
- **flipH**:水平翻转
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 取值范围:`true` / `false`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **flipV**:垂直翻转
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 取值范围:`true` / `false`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **hyperlink**:超链接
|
||||||
|
- 类型:`HyperlinkProps`
|
||||||
|
- 子参数:
|
||||||
|
- **hyperlink.slide**:链接到页码
|
||||||
|
- 类型:`number`
|
||||||
|
- **hyperlink.url**:链接 URL
|
||||||
|
- 类型:`string`
|
||||||
|
- **hyperlink.tooltip**:超链接提示
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **placeholder**:占位符类型
|
||||||
|
- 类型:`string`
|
||||||
|
- 取值范围:`'body'`、`'header'`、`'footer'`、`'title'` 等
|
||||||
|
- 示例:`'body'`
|
||||||
|
|
||||||
|
- **rotate**:旋转角度
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`-360`–`360`(度)
|
||||||
|
- 默认值:`0`
|
||||||
|
- 示例:`180`(旋转 180 度)
|
||||||
|
|
||||||
|
- **rounding**:启用图片圆角
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **shadow**:阴影
|
||||||
|
- 类型:`ShadowProps`
|
||||||
|
- 子参数:
|
||||||
|
- **shadow.type**:阴影类型
|
||||||
|
- 类型:`'outer' | 'inner' | 'none'`
|
||||||
|
- 默认值:`'none'`
|
||||||
|
- **shadow.opacity**:不透明度
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0.0`–`1.0`
|
||||||
|
- **shadow.blur**:模糊
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`100`(pt)
|
||||||
|
- 默认值:`0`
|
||||||
|
- **shadow.angle**:角度
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`359`(度)
|
||||||
|
- 默认值:`0`
|
||||||
|
- **shadow.offset**:偏移距离
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`200`(pt)
|
||||||
|
- 默认值:`0`
|
||||||
|
- **shadow.color**:阴影颜色
|
||||||
|
- 类型:`HexColor`
|
||||||
|
- 格式:6 位十六进制(无 `#`)
|
||||||
|
- **shadow.rotateWithShape**:阴影是否随形状旋转
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **sizing**:图片大小调整
|
||||||
|
- 类型:`{ type: 'contain' | 'cover' | 'crop', w: Coord, h: Coord, x?: Coord, y?: Coord }`
|
||||||
|
- 子参数:
|
||||||
|
- **sizing.type**:调整类型
|
||||||
|
- 类型:`'contain' | 'cover' | 'crop'`
|
||||||
|
- 取值范围:
|
||||||
|
- `contain` — 包含(保持比例适配)
|
||||||
|
- `cover` — 覆盖(填充区域)
|
||||||
|
- `crop` — 裁剪
|
||||||
|
- **sizing.w**:图片宽度
|
||||||
|
- 类型:`Coord`(英寸或百分比)
|
||||||
|
- **sizing.h**:图片高度
|
||||||
|
- 类型:`Coord`(英寸或百分比)
|
||||||
|
- **sizing.x**:裁剪左偏移
|
||||||
|
- 类型:`Coord`(仅用于 `crop` 模式)
|
||||||
|
- **sizing.y**:裁剪上偏移
|
||||||
|
- 类型:`Coord`(仅用于 `crop` 模式)
|
||||||
|
|
||||||
|
- **transparency**:透明度
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`100`(百分比)
|
||||||
|
- 默认值:`0`
|
||||||
|
- 示例:`25`(25% 透明)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ShadowProps
|
||||||
|
|
||||||
|
用于 `shadow` 属性的类型定义(图表、图片、文字通用)。
|
||||||
|
|
||||||
|
- **type**:阴影类型
|
||||||
|
- 类型:`'outer' | 'inner' | 'none'`
|
||||||
|
- 默认值:`'none'`
|
||||||
|
|
||||||
|
- **opacity**:不透明度
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0.0`–`1.0`
|
||||||
|
- 示例:`0.5`(50% 不透明)
|
||||||
|
|
||||||
|
- **blur**:模糊大小
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`100`(pt)
|
||||||
|
- 默认值:`0`
|
||||||
|
|
||||||
|
- **angle**:阴影角度
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`359`(度)
|
||||||
|
- 默认值:`0`
|
||||||
|
|
||||||
|
- **offset**:阴影偏移距离
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`200`(pt)
|
||||||
|
- 默认值:`0`
|
||||||
|
|
||||||
|
- **color**:阴影颜色
|
||||||
|
- 类型:`HexColor`
|
||||||
|
- 格式:6 位十六进制(无 `#`)
|
||||||
|
|
||||||
|
- **rotateWithShape**:是否随形状旋转
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## HyperlinkProps
|
||||||
|
|
||||||
|
用于 `hyperlink` 属性的类型定义。
|
||||||
|
|
||||||
|
- **slide**:链接到页码
|
||||||
|
- 类型:`number`
|
||||||
|
- 说明:跳转到演示文稿的指定幻灯片
|
||||||
|
|
||||||
|
- **url**:超链接 URL
|
||||||
|
- 类型:`string`
|
||||||
|
- 说明:链接到外部网址
|
||||||
|
|
||||||
|
- **tooltip**:超链接提示
|
||||||
|
- 类型:`string`
|
||||||
|
- 说明:鼠标悬停时显示的文字
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
# 母版参数 (Master Props)
|
||||||
|
|
||||||
|
母版参数来自 `SlideMasterProps`,通过 `pptx.defineSlideMaster()` 定义幻灯片母版(布局)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## SlideMasterProps
|
||||||
|
|
||||||
|
- **title**:母版唯一名称
|
||||||
|
- 类型:`string`
|
||||||
|
- 说明:母版的唯一标识名,`addSlide({ masterName })` 时引用
|
||||||
|
|
||||||
|
- **background**:母版背景
|
||||||
|
- 类型:`BackgroundProps`
|
||||||
|
- 说明:背景颜色或图片,继承 `DataOrPathProps` 和 `ShapeFillProps`
|
||||||
|
- 子参数:
|
||||||
|
- **background.color**:背景颜色
|
||||||
|
- 类型:`Color`
|
||||||
|
- **background.transparency**:透明度
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`100`
|
||||||
|
- **background.path**:背景图片 URL 或本地路径
|
||||||
|
- 类型:`string`
|
||||||
|
- **background.data**:背景图片 base64 数据
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **margin**:母版边距
|
||||||
|
- 类型:`Margin`(`number | [number, number, number, number]`)
|
||||||
|
- 单位:pt
|
||||||
|
|
||||||
|
- **slideNumber**:幻灯片编号
|
||||||
|
- 类型:`SlideNumberProps`
|
||||||
|
- 说明:继承 `PositionProps` 和 `TextBaseProps`
|
||||||
|
- 子参数:
|
||||||
|
- **slideNumber.margin**:编号边距
|
||||||
|
- 类型:`Margin`
|
||||||
|
|
||||||
|
- **objects**:母版对象数组
|
||||||
|
- 类型:`Array<{ chart: IChartOpts } | { image: ImageProps } | { line: ShapeProps } | { rect: ShapeProps } | { text: TextProps } | { placeholder: { options: PlaceholderProps, text?: string } }>`
|
||||||
|
- 说明:母版上的固定对象,包括图表、图片、线条、矩形、文字和占位符
|
||||||
|
- 占位符子参数:
|
||||||
|
- **placeholder.options**:占位符选项
|
||||||
|
- 类型:`PlaceholderProps`
|
||||||
|
- **placeholder.text**:占位符默认文字
|
||||||
|
- 类型:`string`
|
||||||
|
- 说明:留空则 PowerPoint 显示默认提示文字(如"单击此处添加标题")
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## PlaceholderProps
|
||||||
|
|
||||||
|
继承 `PositionProps` 和 `TextBaseProps`。
|
||||||
|
|
||||||
|
- **name**:占位符名称
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **type**:占位符类型
|
||||||
|
- 类型:`PLACEHOLDER_TYPE`
|
||||||
|
- 取值范围:`'title' | 'body' | 'pic' | 'chart' | 'tbl' | 'media'`
|
||||||
|
|
||||||
|
- **margin**:占位符边距
|
||||||
|
- 类型:`Margin`(`number | [number, number, number, number]`)
|
||||||
|
- 单位:pt
|
||||||
|
|
||||||
|
- **x**:水平位置
|
||||||
|
- 类型:`Coord`
|
||||||
|
|
||||||
|
- **y**:垂直位置
|
||||||
|
- 类型:`Coord`
|
||||||
|
|
||||||
|
- **w**:宽度
|
||||||
|
- 类型:`Coord`
|
||||||
|
|
||||||
|
- **h**:高度
|
||||||
|
- 类型:`Coord`
|
||||||
|
|
||||||
|
### PLACEHOLDER_TYPES 枚举
|
||||||
|
|
||||||
|
| 常量 | 值 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `PLACEHOLDER_TYPES.title` | `'title'` | 标题 |
|
||||||
|
| `PLACEHOLDER_TYPES.body` | `'body'` | 正文 |
|
||||||
|
| `PLACEHOLDER_TYPES.image` | `'pic'` | 图片 |
|
||||||
|
| `PLACEHOLDER_TYPES.chart` | `'chart'` | 图表 |
|
||||||
|
| `PLACEHOLDER_TYPES.table` | `'tbl'` | 表格 |
|
||||||
|
| `PLACEHOLDER_TYPES.media` | `'media'` | 媒体 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ThemeProps
|
||||||
|
|
||||||
|
用于 `pptx.theme` 属性。
|
||||||
|
|
||||||
|
- **headFontFace**:标题字体
|
||||||
|
- 类型:`string`
|
||||||
|
- 默认值:`'Calibri Light'`
|
||||||
|
- 示例:`'Arial Narrow'`
|
||||||
|
|
||||||
|
- **bodyFontFace**:正文字体
|
||||||
|
- 类型:`string`
|
||||||
|
- 默认值:`'Calibri'`
|
||||||
|
- 示例:`'Arial'`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 使用示例
|
||||||
|
|
||||||
|
```ts
|
||||||
|
pptx.defineSlideMaster({
|
||||||
|
title: 'MyMaster',
|
||||||
|
background: { color: 'FFFFFF' },
|
||||||
|
margin: 0,
|
||||||
|
objects: [
|
||||||
|
{ rect: { x: 0, y: 0, w: 10, h: 0.5, fill: { color: '003366' } } },
|
||||||
|
{ text: { text: '即稿即用', options: { x: 0.5, y: 0.05, w: 9, h: 0.4, color: 'FFFFFF', fontSize: 14 } } },
|
||||||
|
{ placeholder: { options: { name: 'title', type: 'title', x: 0.5, y: 1, w: 9, h: 1 } } },
|
||||||
|
],
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 已废弃参数
|
||||||
|
|
||||||
|
| 已废弃参数 | 替代参数 | 废弃版本 |
|
||||||
|
|---|---|---|
|
||||||
|
| `bkgd` | `background` | v3.3.0 |
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
# 输出参数 (Output Props)
|
||||||
|
|
||||||
|
输出参数用于导出演示文稿,支持 `writeFile()`(写入文件)、`write()`(返回数据)和 `stream()`(返回流)三种方式。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 输出方法
|
||||||
|
|
||||||
|
### pptx.writeFile()
|
||||||
|
|
||||||
|
```ts
|
||||||
|
pptx.writeFile(props?: WriteFileProps): Promise<string>
|
||||||
|
```
|
||||||
|
|
||||||
|
- **fileName**:导出文件名
|
||||||
|
- 类型:`string`
|
||||||
|
- 默认值:`'Presentation.pptx'`
|
||||||
|
|
||||||
|
- **compression**:是否启用压缩
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
- 说明:可显著减小文件体积,但导出耗时略长
|
||||||
|
- 始于:v3.5.0
|
||||||
|
|
||||||
|
### pptx.write()
|
||||||
|
|
||||||
|
```ts
|
||||||
|
pptx.write(props?: WriteProps): Promise<string | ArrayBuffer | Blob | Uint8Array>
|
||||||
|
```
|
||||||
|
|
||||||
|
- **outputType**:输出类型
|
||||||
|
- 类型:`WRITE_OUTPUT_TYPE`
|
||||||
|
- 取值范围:`'arraybuffer' | 'base64' | 'binarystring' | 'blob' | 'nodebuffer' | 'uint8array' | 'STREAM'`
|
||||||
|
- 默认值:`'blob'`
|
||||||
|
|
||||||
|
- **compression**:是否启用压缩
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
- 始于:v3.5.0
|
||||||
|
|
||||||
|
### pptx.stream()
|
||||||
|
|
||||||
|
```ts
|
||||||
|
pptx.stream(props?: WriteBaseProps): Promise<string | ArrayBuffer | Blob | Uint8Array>
|
||||||
|
```
|
||||||
|
|
||||||
|
- **compression**:是否启用压缩
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
- 始于:v3.5.0
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## WriteBaseProps
|
||||||
|
|
||||||
|
`writeFile()` 和 `stream()` 的输出基础属性。
|
||||||
|
|
||||||
|
- **compression**:压缩选项
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
- 始于:v3.5.0
|
||||||
|
- 说明:启用 Zip 压缩以减小文件体积
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## WriteProps
|
||||||
|
|
||||||
|
继承 `WriteBaseProps`,用于 `write()` 方法。
|
||||||
|
|
||||||
|
- **outputType**:输出类型
|
||||||
|
- 类型:`WRITE_OUTPUT_TYPE`
|
||||||
|
- 取值范围:`'arraybuffer' | 'base64' | 'binarystring' | 'blob' | 'nodebuffer' | 'uint8array' | 'STREAM'`
|
||||||
|
- 默认值:`'blob'`
|
||||||
|
|
||||||
|
- **compression**:压缩选项
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## WriteFileProps
|
||||||
|
|
||||||
|
继承 `WriteBaseProps`,用于 `writeFile()` 方法。
|
||||||
|
|
||||||
|
- **fileName**:导出文件名
|
||||||
|
- 类型:`string`
|
||||||
|
- 默认值:`'Presentation.pptx'`
|
||||||
|
|
||||||
|
- **compression**:压缩选项
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## OutputType 枚举
|
||||||
|
|
||||||
|
| 常量 | 值 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `OutputType.arraybuffer` | `'arraybuffer'` | ArrayBuffer |
|
||||||
|
| `OutputType.base64` | `'base64'` | Base64 字符串 |
|
||||||
|
| `OutputType.binarystring` | `'binarystring'` | 二进制字符串 |
|
||||||
|
| `OutputType.blob` | `'blob'` | Blob 对象(默认) |
|
||||||
|
| `OutputType.nodebuffer` | `'nodebuffer'` | Node.js Buffer |
|
||||||
|
| `OutputType.uint8array` | `'uint8array'` | Uint8Array |
|
||||||
|
|
||||||
|
## JSZIP_OUTPUT_TYPE
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type JSZIP_OUTPUT_TYPE = 'arraybuffer' | 'base64' | 'binarystring' | 'blob' | 'nodebuffer' | 'uint8array'
|
||||||
|
```
|
||||||
|
|
||||||
|
## WRITE_OUTPUT_TYPE
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type WRITE_OUTPUT_TYPE = JSZIP_OUTPUT_TYPE | 'STREAM'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Presentation 属性
|
||||||
|
|
||||||
|
导出前的演示文稿元数据属性。
|
||||||
|
|
||||||
|
- **author**:作者
|
||||||
|
- 类型:`string`
|
||||||
|
- 说明:演示文稿作者名称
|
||||||
|
|
||||||
|
- **company**:公司
|
||||||
|
- 类型:`string`
|
||||||
|
- 说明:公司名称
|
||||||
|
|
||||||
|
- **layout**:布局名称
|
||||||
|
- 类型:`string`
|
||||||
|
- 说明:当前演示文稿布局
|
||||||
|
- 标准值:`'LAYOUT_4x3'`、`'LAYOUT_16x9'`、`'LAYOUT_16x10'`、`'LAYOUT_WIDE'`
|
||||||
|
|
||||||
|
- **rtlMode**:从右向左模式
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **revision**:修订号
|
||||||
|
- 类型:`string`
|
||||||
|
- 说明:必须为整数(不含小数点或逗号),否则 PowerPoint 打开时会报错
|
||||||
|
|
||||||
|
- **subject**:主题
|
||||||
|
- 类型:`string`
|
||||||
|
- 说明:演示文稿主题
|
||||||
|
|
||||||
|
- **theme**:主题字体
|
||||||
|
- 类型:`ThemeProps`
|
||||||
|
- 子参数:
|
||||||
|
- **theme.headFontFace**:标题字体(默认 `'Calibri Light'`)
|
||||||
|
- **theme.bodyFontFace**:正文字体(默认 `'Calibri'`)
|
||||||
|
|
||||||
|
- **title**:演示文稿标题
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **masterSlide**:当前母版
|
||||||
|
- 类型:`PresSlide`
|
||||||
|
- 说明:当前演示文稿的母版幻灯片对象
|
||||||
|
|
||||||
|
- **presLayout**:布局信息
|
||||||
|
- 类型:`PresLayout`
|
||||||
|
- 说明:只读,包含 `name`(布局名称)、`width`(宽度,英寸)、`height`(高度,英寸)
|
||||||
@@ -0,0 +1,440 @@
|
|||||||
|
# 形状参数 (Shape Props)
|
||||||
|
|
||||||
|
形状参数来自 `ShapeProps`(继承 `PositionProps`、`ObjectNameProps`),配合 `ShapeFillProps`、`ShapeLineProps` 使用,用于 `slide.addShape()` 方法。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 位置属性 (PositionProps)
|
||||||
|
|
||||||
|
- **x**:水平位置
|
||||||
|
- 类型:`Coord`(`number | `${number}%``)
|
||||||
|
- 单位:英寸(number)或百分比(`'50%'`)
|
||||||
|
|
||||||
|
- **y**:垂直位置
|
||||||
|
- 类型:`Coord`
|
||||||
|
- 单位:英寸或百分比
|
||||||
|
|
||||||
|
- **w**:宽度
|
||||||
|
- 类型:`Coord`
|
||||||
|
- 单位:英寸或百分比
|
||||||
|
|
||||||
|
- **h**:高度
|
||||||
|
- 类型:`Coord`
|
||||||
|
- 单位:英寸或百分比
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 对象名称属性 (ObjectNameProps)
|
||||||
|
|
||||||
|
- **objectName**:对象名称
|
||||||
|
- 类型:`string`
|
||||||
|
- 说明:替代默认的 "Object N" 名称,在 PowerPoint 选择窗格中显示
|
||||||
|
- 默认值:`'Object 1'`
|
||||||
|
- 始于:v3.10.0
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 形状专有属性 (ShapeProps)
|
||||||
|
|
||||||
|
- **align**:水平对齐方式
|
||||||
|
- 类型:`HAlign`
|
||||||
|
- 取值范围:`'left'`、`'center'`、`'right'`、`'justify'`
|
||||||
|
- 默认值:`'left'`
|
||||||
|
|
||||||
|
- **angleRange**:饼图/弧的角度范围
|
||||||
|
- 类型:`[number, number]`
|
||||||
|
- 说明:仅适用于 `pie`、`arc`、`blockArc` 形状
|
||||||
|
- 取值范围:`[0–359, 0–359]`
|
||||||
|
- 默认值:`[270, 0]`
|
||||||
|
- 始于:v3.4.0
|
||||||
|
|
||||||
|
- **arcThicknessRatio**:弧的厚度比
|
||||||
|
- 类型:`number`
|
||||||
|
- 说明:仅适用于 `blockArc` 形状(需同时设置 `angleRange`)
|
||||||
|
- 取值范围:`0.0`–`1.0`
|
||||||
|
- 默认值:`0.5`
|
||||||
|
- 始于:v3.4.0
|
||||||
|
|
||||||
|
- **fill**:形状填充
|
||||||
|
- 类型:`ShapeFillProps`
|
||||||
|
- 子参数:
|
||||||
|
- **fill.color**:填充颜色
|
||||||
|
- 类型:`Color`(`HexColor | ThemeColor`)
|
||||||
|
- **fill.transparency**:透明度
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`100`
|
||||||
|
- 默认值:`0`
|
||||||
|
- **fill.type**:填充类型
|
||||||
|
- 类型:`'none' | 'solid'`
|
||||||
|
- 默认值:`'solid'`
|
||||||
|
|
||||||
|
- **flipH**:水平翻转
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 取值范围:`true` / `false`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **flipV**:垂直翻转
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 取值范围:`true` / `false`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **hyperlink**:超链接
|
||||||
|
- 类型:`HyperlinkProps`
|
||||||
|
- 子参数:
|
||||||
|
- **hyperlink.slide**:链接到页码
|
||||||
|
- 类型:`number`
|
||||||
|
- **hyperlink.url**:链接 URL
|
||||||
|
- 类型:`string`
|
||||||
|
- **hyperlink.tooltip**:超链接提示
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **line**:线条(边框/轮廓线)
|
||||||
|
- 类型:`ShapeLineProps`
|
||||||
|
- 详见下方 ShapeLineProps 参数
|
||||||
|
|
||||||
|
- **points**:自定义形状顶点
|
||||||
|
- 类型:`Array<{ x: Coord, y: Coord } | { x: Coord, y: Coord, curve: ... } | { close: true }>`
|
||||||
|
- 说明:仅适用于自定义几何形状(CUSTOM_GEOMETRY)
|
||||||
|
- 顶点类型:
|
||||||
|
- `{ x, y }` — 直线线段
|
||||||
|
- `{ x, y, moveTo }` — 移动到新位置
|
||||||
|
- `{ x, y, curve: { type: 'arc', hR, wR, stAng, swAng } }` — 弧线
|
||||||
|
- `{ x, y, curve: { type: 'cubic', x1, y1, x2, y2 } }` — 三次贝塞尔曲线
|
||||||
|
- `{ x, y, curve: { type: 'quadratic', x1, y1 } }` — 二次贝塞尔曲线
|
||||||
|
- `{ close: true }` — 闭合路径
|
||||||
|
|
||||||
|
- **rectRadius**:圆角矩形半径
|
||||||
|
- 类型:`number`
|
||||||
|
- 说明:仅适用于 `roundedRect`(ROUNDED_RECTANGLE)形状
|
||||||
|
- 取值范围:`0.0`–`1.0`
|
||||||
|
- 默认值:`0`
|
||||||
|
|
||||||
|
- **rotate**:旋转角度
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`-360`–`360`(度)
|
||||||
|
- 默认值:`0`
|
||||||
|
|
||||||
|
- **shadow**:阴影
|
||||||
|
- 类型:`ShadowProps`
|
||||||
|
- 子参数:
|
||||||
|
- **shadow.type**:阴影类型
|
||||||
|
- 类型:`'outer' | 'inner' | 'none'`
|
||||||
|
- 默认值:`'none'`
|
||||||
|
- **shadow.opacity**:不透明度
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0.0`–`1.0`
|
||||||
|
- **shadow.blur**:模糊
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`100`(pt)
|
||||||
|
- 默认值:`0`
|
||||||
|
- **shadow.angle**:角度
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`359`(度)
|
||||||
|
- 默认值:`0`
|
||||||
|
- **shadow.offset**:偏移距离
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`200`(pt)
|
||||||
|
- 默认值:`0`
|
||||||
|
- **shadow.color**:阴影颜色
|
||||||
|
- 类型:`HexColor`
|
||||||
|
- 格式:6 位十六进制(无 `#`)
|
||||||
|
- **shadow.rotateWithShape**:阴影是否随形状旋转
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 形状填充 (ShapeFillProps)
|
||||||
|
|
||||||
|
用于 `fill` 属性的类型定义。
|
||||||
|
|
||||||
|
- **color**:填充颜色
|
||||||
|
- 类型:`Color`(`HexColor | ThemeColor`)
|
||||||
|
- 取值范围:6 位十六进制颜色值(不含 `#`)或主题色
|
||||||
|
- 示例:`'FF0000'`、`pptx.SchemeColor.text1`
|
||||||
|
|
||||||
|
- **transparency**:透明度
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`100`(百分比)
|
||||||
|
- 默认值:`0`
|
||||||
|
|
||||||
|
- **type**:填充类型
|
||||||
|
- 类型:`'none' | 'solid'`
|
||||||
|
- 默认值:`'solid'`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 形状线条 (ShapeLineProps)
|
||||||
|
|
||||||
|
继承 `ShapeFillProps`,用于 `line` 属性。
|
||||||
|
|
||||||
|
- **width**:线条宽度
|
||||||
|
- 类型:`number`
|
||||||
|
- 单位:pt
|
||||||
|
- 默认值:`1`
|
||||||
|
|
||||||
|
- **dashType**:虚线类型
|
||||||
|
- 类型:`'solid' | 'dash' | 'dashDot' | 'lgDash' | 'lgDashDot' | 'lgDashDotDot' | 'sysDash' | 'sysDot'`
|
||||||
|
- 默认值:`'solid'`
|
||||||
|
|
||||||
|
- **beginArrowType**:起点箭头类型
|
||||||
|
- 类型:`'none' | 'arrow' | 'diamond' | 'oval' | 'stealth' | 'triangle'`
|
||||||
|
- 始于:v3.3.0
|
||||||
|
|
||||||
|
- **endArrowType**:终点箭头类型
|
||||||
|
- 类型:`'none' | 'arrow' | 'diamond' | 'oval' | 'stealth' | 'triangle'`
|
||||||
|
- 始于:v3.3.0
|
||||||
|
|
||||||
|
- **color**:线条颜色
|
||||||
|
- 类型:`Color`
|
||||||
|
|
||||||
|
- **transparency**:线条透明度
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`100`
|
||||||
|
|
||||||
|
- **type**:填充类型
|
||||||
|
- 类型:`'none' | 'solid'`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 形状类型列表
|
||||||
|
|
||||||
|
`SHAPE_NAME` 类型包含以下所有取值(约180个),可通过 `pptxgen.shapes.XXX` 常量引用。
|
||||||
|
|
||||||
|
### 动作按钮 (Action Buttons)
|
||||||
|
|
||||||
|
| 常量名 | 值 |
|
||||||
|
|---|---|
|
||||||
|
| `ACTION_BUTTON_BACK_OR_PREVIOUS` | `actionButtonBackPrevious` |
|
||||||
|
| `ACTION_BUTTON_BEGINNING` | `actionButtonBeginning` |
|
||||||
|
| `ACTION_BUTTON_CUSTOM` | `actionButtonBlank` |
|
||||||
|
| `ACTION_BUTTON_DOCUMENT` | `actionButtonDocument` |
|
||||||
|
| `ACTION_BUTTON_END` | `actionButtonEnd` |
|
||||||
|
| `ACTION_BUTTON_FORWARD_OR_NEXT` | `actionButtonForwardNext` |
|
||||||
|
| `ACTION_BUTTON_HELP` | `actionButtonHelp` |
|
||||||
|
| `ACTION_BUTTON_HOME` | `actionButtonHome` |
|
||||||
|
| `ACTION_BUTTON_INFORMATION` | `actionButtonInformation` |
|
||||||
|
| `ACTION_BUTTON_MOVIE` | `actionButtonMovie` |
|
||||||
|
| `ACTION_BUTTON_RETURN` | `actionButtonReturn` |
|
||||||
|
| `ACTION_BUTTON_SOUND` | `actionButtonSound` |
|
||||||
|
|
||||||
|
### 箭头 (Arrows)
|
||||||
|
|
||||||
|
| 常量名 | 值 |
|
||||||
|
|---|---|
|
||||||
|
| `BENT_ARROW` | `bentArrow` |
|
||||||
|
| `BENT_UP_ARROW` | `bentUpArrow` |
|
||||||
|
| `CHEVRON` | `chevron` |
|
||||||
|
| `CIRCULAR_ARROW` | `circularArrow` |
|
||||||
|
| `CURVED_DOWN_ARROW` | `curvedDownArrow` |
|
||||||
|
| `CURVED_LEFT_ARROW` | `curvedLeftArrow` |
|
||||||
|
| `CURVED_RIGHT_ARROW` | `curvedRightArrow` |
|
||||||
|
| `CURVED_UP_ARROW` | `curvedUpArrow` |
|
||||||
|
| `DOWN_ARROW` | `downArrow` |
|
||||||
|
| `DOWN_ARROW_CALLOUT` | `downArrowCallout` |
|
||||||
|
| `LEFT_ARROW` | `leftArrow` |
|
||||||
|
| `LEFT_ARROW_CALLOUT` | `leftArrowCallout` |
|
||||||
|
| `LEFT_CIRCULAR_ARROW` | `leftCircularArrow` |
|
||||||
|
| `LEFT_RIGHT_ARROW` | `leftRightArrow` |
|
||||||
|
| `LEFT_RIGHT_ARROW_CALLOUT` | `leftRightArrowCallout` |
|
||||||
|
| `LEFT_RIGHT_CIRCULAR_ARROW` | `leftRightCircularArrow` |
|
||||||
|
| `LEFT_RIGHT_UP_ARROW` | `leftRightUpArrow` |
|
||||||
|
| `LEFT_UP_ARROW` | `leftUpArrow` |
|
||||||
|
| `NOTCHED_RIGHT_ARROW` | `notchedRightArrow` |
|
||||||
|
| `QUAD_ARROW` | `quadArrow` |
|
||||||
|
| `QUAD_ARROW_CALLOUT` | `quadArrowCallout` |
|
||||||
|
| `RIGHT_ARROW` | `rightArrow` |
|
||||||
|
| `RIGHT_ARROW_CALLOUT` | `rightArrowCallout` |
|
||||||
|
| `STRIPED_RIGHT_ARROW` | `stripedRightArrow` |
|
||||||
|
| `SWOOSH_ARROW` | `swooshArrow` |
|
||||||
|
| `UP_ARROW` | `upArrow` |
|
||||||
|
| `UP_ARROW_CALLOUT` | `upArrowCallout` |
|
||||||
|
| `UP_DOWN_ARROW` | `upDownArrow` |
|
||||||
|
| `UP_DOWN_ARROW_CALLOUT` | `upDownArrowCallout` |
|
||||||
|
| `U_TURN_ARROW` | `uturnArrow` |
|
||||||
|
|
||||||
|
### 标注框 (Callouts)
|
||||||
|
|
||||||
|
| 常量名 | 值 |
|
||||||
|
|---|---|
|
||||||
|
| `LINE_CALLOUT_1` | `borderCallout1` |
|
||||||
|
| `LINE_CALLOUT_1_ACCENT_BAR` | `accentCallout1` |
|
||||||
|
| `LINE_CALLOUT_1_BORDER_AND_ACCENT_BAR` | `accentBorderCallout1` |
|
||||||
|
| `LINE_CALLOUT_1_NO_BORDER` | `callout1` |
|
||||||
|
| `LINE_CALLOUT_2` | `borderCallout2` |
|
||||||
|
| `LINE_CALLOUT_2_ACCENT_BAR` | `accentCallout2` |
|
||||||
|
| `LINE_CALLOUT_2_BORDER_AND_ACCENT_BAR` | `accentBorderCallout2` |
|
||||||
|
| `LINE_CALLOUT_2_NO_BORDER` | `callout2` |
|
||||||
|
| `LINE_CALLOUT_3` | `borderCallout3` |
|
||||||
|
| `LINE_CALLOUT_3_ACCENT_BAR` | `accentCallout3` |
|
||||||
|
| `LINE_CALLOUT_3_BORDER_AND_ACCENT_BAR` | `accentBorderCallout3` |
|
||||||
|
| `LINE_CALLOUT_3_NO_BORDER` | `callout3` |
|
||||||
|
| `LINE_CALLOUT_4` | `borderCallout4` |
|
||||||
|
| `LINE_CALLOUT_4_ACCENT_BAR` | `accentCallout4` |
|
||||||
|
| `LINE_CALLOUT_4_BORDER_AND_ACCENT_BAR` | `accentBorderCallout4` |
|
||||||
|
| `LINE_CALLOUT_4_NO_BORDER` | `callout4` |
|
||||||
|
| `CLOUD_CALLOUT` | `cloudCallout` |
|
||||||
|
| `OVAL_CALLOUT` | `wedgeEllipseCallout` |
|
||||||
|
| `RECTANGULAR_CALLOUT` | `wedgeRectCallout` |
|
||||||
|
| `ROUNDED_RECTANGULAR_CALLOUT` | `wedgeRoundRectCallout` |
|
||||||
|
|
||||||
|
### 基本形状 (Basic Shapes)
|
||||||
|
|
||||||
|
| 常量名 | 值 |
|
||||||
|
|---|---|
|
||||||
|
| `ARC` | `arc` |
|
||||||
|
| `BALLOON` | `wedgeRoundRectCallout` |
|
||||||
|
| `BEVEL` | `bevel` |
|
||||||
|
| `BLOCK_ARC` | `blockArc` |
|
||||||
|
| `CAN` | `can` |
|
||||||
|
| `CHORD` | `chord` |
|
||||||
|
| `CLOUD` | `cloud` |
|
||||||
|
| `CORNER` | `corner` |
|
||||||
|
| `CROSS` | `plus` |
|
||||||
|
| `CUBE` | `cube` |
|
||||||
|
| `DIAMOND` | `diamond` |
|
||||||
|
| `DONUT` | `donut` |
|
||||||
|
| `DOUBLE_BRACE` | `bracePair` |
|
||||||
|
| `DOUBLE_BRACKET` | `bracketPair` |
|
||||||
|
| `DOUBLE_WAVE` | `doubleWave` |
|
||||||
|
| `FOLDED_CORNER` | `folderCorner` |
|
||||||
|
| `FRAME` | `frame` |
|
||||||
|
| `FUNNEL` | `funnel` |
|
||||||
|
| `GEAR_6` | `gear6` |
|
||||||
|
| `GEAR_9` | `gear9` |
|
||||||
|
| `HALF_FRAME` | `halfFrame` |
|
||||||
|
| `HEART` | `heart` |
|
||||||
|
| `HORIZONTAL_SCROLL` | `horizontalScroll` |
|
||||||
|
| `ISOSCELES_TRIANGLE` | `triangle` |
|
||||||
|
| `LEFT_BRACE` | `leftBrace` |
|
||||||
|
| `LEFT_BRACKET` | `leftBracket` |
|
||||||
|
| `LIGHTNING_BOLT` | `lightningBolt` |
|
||||||
|
| `LINE` | `line` |
|
||||||
|
| `LINE_INVERSE` | `lineInv` |
|
||||||
|
| `MOON` | `moon` |
|
||||||
|
| `NO_SYMBOL` | `noSmoking` |
|
||||||
|
| `OVAL` | `ellipse` |
|
||||||
|
| `PARALLELOGRAM` | `parallelogram` |
|
||||||
|
| `PENTAGON` | `homePlate` |
|
||||||
|
| `PIE` | `pie` |
|
||||||
|
| `PIE_WEDGE` | `pieWedge` |
|
||||||
|
| `PLAQUE` | `plaque` |
|
||||||
|
| `PLAQUE_TABS` | `plaqueTabs` |
|
||||||
|
| `RECTANGLE` | `rect` |
|
||||||
|
| `REGULAR_PENTAGON` | `pentagon` |
|
||||||
|
| `RIGHT_BRACE` | `rightBrace` |
|
||||||
|
| `RIGHT_BRACKET` | `rightBracket` |
|
||||||
|
| `RIGHT_TRIANGLE` | `rtTriangle` |
|
||||||
|
| `ROUNDED_RECTANGLE` | `roundRect` |
|
||||||
|
| `ROUND_1_RECTANGLE` | `round1Rect` |
|
||||||
|
| `ROUND_2_DIAG_RECTANGLE` | `round2DiagRect` |
|
||||||
|
| `ROUND_2_SAME_RECTANGLE` | `round2SameRect` |
|
||||||
|
| `SMILEY_FACE` | `smileyFace` |
|
||||||
|
| `SNIP_1_RECTANGLE` | `snip1Rect` |
|
||||||
|
| `SNIP_2_DIAG_RECTANGLE` | `snip2DiagRect` |
|
||||||
|
| `SNIP_2_SAME_RECTANGLE` | `snip2SameRect` |
|
||||||
|
| `SNIP_ROUND_RECTANGLE` | `snipRoundRect` |
|
||||||
|
| `SUN` | `sun` |
|
||||||
|
| `TEAR` | `teardrop` |
|
||||||
|
| `TRAPEZOID` | `trapezoid` |
|
||||||
|
| `NON_ISOSCELES_TRAPEZOID` | `nonIsoscelesTrapezoid` |
|
||||||
|
| `VERTICAL_SCROLL` | `verticalScroll` |
|
||||||
|
| `WAVE` | `wave` |
|
||||||
|
|
||||||
|
### 流程图 (Flowchart)
|
||||||
|
|
||||||
|
| 常量名 | 值 |
|
||||||
|
|---|---|
|
||||||
|
| `FLOWCHART_ALTERNATE_PROCESS` | `flowChartAlternateProcess` |
|
||||||
|
| `FLOWCHART_CARD` | `flowChartPunchedCard` |
|
||||||
|
| `FLOWCHART_COLLATE` | `flowChartCollate` |
|
||||||
|
| `FLOWCHART_CONNECTOR` | `flowChartConnector` |
|
||||||
|
| `FLOWCHART_DATA` | `flowChartInputOutput` |
|
||||||
|
| `FLOWCHART_DECISION` | `flowChartDecision` |
|
||||||
|
| `FLOWCHART_DELAY` | `flowChartDelay` |
|
||||||
|
| `FLOWCHART_DIRECT_ACCESS_STORAGE` | `flowChartMagneticDrum` |
|
||||||
|
| `FLOWCHART_DISPLAY` | `flowChartDisplay` |
|
||||||
|
| `FLOWCHART_DOCUMENT` | `flowChartDocument` |
|
||||||
|
| `FLOWCHART_EXTRACT` | `flowChartExtract` |
|
||||||
|
| `FLOWCHART_INTERNAL_STORAGE` | `flowChartInternalStorage` |
|
||||||
|
| `FLOWCHART_MAGNETIC_DISK` | `flowChartMagneticDisk` |
|
||||||
|
| `FLOWCHART_MANUAL_INPUT` | `flowChartManualInput` |
|
||||||
|
| `FLOWCHART_MANUAL_OPERATION` | `flowChartManualOperation` |
|
||||||
|
| `FLOWCHART_MERGE` | `flowChartMerge` |
|
||||||
|
| `FLOWCHART_MULTIDOCUMENT` | `flowChartMultidocument` |
|
||||||
|
| `FLOWCHART_OFFLINE_STORAGE` | `flowChartOfflineStorage` |
|
||||||
|
| `FLOWCHART_OFFPAGE_CONNECTOR` | `flowChartOffpageConnector` |
|
||||||
|
| `FLOWCHART_OR` | `flowChartOr` |
|
||||||
|
| `FLOWCHART_PREDEFINED_PROCESS` | `flowChartPredefinedProcess` |
|
||||||
|
| `FLOWCHART_PREPARATION` | `flowChartPreparation` |
|
||||||
|
| `FLOWCHART_PROCESS` | `flowChartProcess` |
|
||||||
|
| `FLOWCHART_PUNCHED_TAPE` | `flowChartPunchedTape` |
|
||||||
|
| `FLOWCHART_SEQUENTIAL_ACCESS_STORAGE` | `flowChartMagneticTape` |
|
||||||
|
| `FLOWCHART_SORT` | `flowChartSort` |
|
||||||
|
| `FLOWCHART_STORED_DATA` | `flowChartOnlineStorage` |
|
||||||
|
| `FLOWCHART_SUMMING_JUNCTION` | `flowChartSummingJunction` |
|
||||||
|
| `FLOWCHART_TERMINATOR` | `flowChartTerminator` |
|
||||||
|
|
||||||
|
### 星形与旗帜 (Stars & Ribbons)
|
||||||
|
|
||||||
|
| 常量名 | 值 |
|
||||||
|
|---|---|
|
||||||
|
| `STAR_4_POINT` | `star4` |
|
||||||
|
| `STAR_5_POINT` | `star5` |
|
||||||
|
| `STAR_6_POINT` | `star6` |
|
||||||
|
| `STAR_7_POINT` | `star7` |
|
||||||
|
| `STAR_8_POINT` | `star8` |
|
||||||
|
| `STAR_10_POINT` | `star10` |
|
||||||
|
| `STAR_12_POINT` | `star12` |
|
||||||
|
| `STAR_16_POINT` | `star16` |
|
||||||
|
| `STAR_24_POINT` | `star24` |
|
||||||
|
| `STAR_32_POINT` | `star32` |
|
||||||
|
| `CURVED_DOWN_RIBBON` | `ellipseRibbon` |
|
||||||
|
| `CURVED_UP_RIBBON` | `ellipseRibbon2` |
|
||||||
|
| `DOWN_RIBBON` | `ribbon` |
|
||||||
|
| `LEFT_RIGHT_RIBBON` | `leftRightRibbon` |
|
||||||
|
| `UP_RIBBON` | `ribbon2` |
|
||||||
|
| `CHART_PLUS` | `chartPlus` |
|
||||||
|
| `CHART_STAR` | `chartStar` |
|
||||||
|
| `CHART_X` | `chartX` |
|
||||||
|
| `CORNER_TABS` | `cornerTabs` |
|
||||||
|
| `SQUARE_TABS` | `squareTabs` |
|
||||||
|
| `EXPLOSION1` | `irregularSeal1` |
|
||||||
|
| `EXPLOSION2` | `irregularSeal2` |
|
||||||
|
|
||||||
|
### 数学符号 (Math)
|
||||||
|
|
||||||
|
| 常量名 | 值 |
|
||||||
|
|---|---|
|
||||||
|
| `MATH_DIVIDE` | `mathDivide` |
|
||||||
|
| `MATH_EQUAL` | `mathEqual` |
|
||||||
|
| `MATH_MINUS` | `mathMinus` |
|
||||||
|
| `MATH_MULTIPLY` | `mathMultiply` |
|
||||||
|
| `MATH_NOT_EQUAL` | `mathNotEqual` |
|
||||||
|
| `MATH_PLUS` | `mathPlus` |
|
||||||
|
|
||||||
|
### 多边形 (Polygons)
|
||||||
|
|
||||||
|
| 常量名 | 值 |
|
||||||
|
|---|---|
|
||||||
|
| `DECAGON` | `decagon` |
|
||||||
|
| `DODECAGON` | `dodecagon` |
|
||||||
|
| `HEPTAGON` | `heptagon` |
|
||||||
|
| `HEXAGON` | `hexagon` |
|
||||||
|
| `OCTAGON` | `octagon` |
|
||||||
|
| `DIAGONAL_STRIPE` | `diagStripe` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 已废弃参数
|
||||||
|
|
||||||
|
| 已废弃参数 | 替代参数 | 废弃版本 |
|
||||||
|
|---|---|---|
|
||||||
|
| `lineSize` | `line.width` | v3.3.0 |
|
||||||
|
| `lineDash` | `line.dashType` | v3.3.0 |
|
||||||
|
| `lineHead` | `line.beginArrowType` | v3.3.0 |
|
||||||
|
| `lineTail` | `line.endArrowType` | v3.3.0 |
|
||||||
|
| `shapeName` | `objectName` | v3.10.0 |
|
||||||
|
| `pt`(ShapeLineProps) | `width` | v3.3.0 |
|
||||||
|
| `size`(ShapeLineProps) | `width` | v3.3.0 |
|
||||||
|
| `alpha`(ShapeFillProps) | `transparency` | v3.3.0 |
|
||||||
@@ -0,0 +1,223 @@
|
|||||||
|
# 幻灯片参数 (Slide Props)
|
||||||
|
|
||||||
|
幻灯片相关类型包括 `Slide` 类、`PresSlide` 接口和 `BackgroundProps`,通过 `pptx.addSlide()` 创建。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 添加幻灯片 (addSlide)
|
||||||
|
|
||||||
|
```ts
|
||||||
|
pptx.addSlide(props?: AddSlideProps): PptxGenJS.Slide
|
||||||
|
```
|
||||||
|
|
||||||
|
### AddSlideProps
|
||||||
|
|
||||||
|
- **masterName**:母版名称
|
||||||
|
- 类型:`string`
|
||||||
|
- 说明:使用已定义的 Slide Master 名称
|
||||||
|
|
||||||
|
- **sectionTitle**:节标题
|
||||||
|
- 类型:`string`
|
||||||
|
- 说明:将幻灯片添加到指定节
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Slide 类属性
|
||||||
|
|
||||||
|
- **background**:幻灯片背景
|
||||||
|
- 类型:`BackgroundProps`
|
||||||
|
- 说明:背景颜色或图片
|
||||||
|
|
||||||
|
- **color**:默认文字颜色
|
||||||
|
- 类型:`HexColor`
|
||||||
|
- 格式:6 位十六进制(无 `#`)
|
||||||
|
- 默认值:`'000000'`
|
||||||
|
|
||||||
|
- **hidden**:幻灯片是否隐藏
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **slideNumber**:幻灯片编号选项
|
||||||
|
- 类型:`SlideNumberProps`
|
||||||
|
|
||||||
|
- **newAutoPagedSlides**:自动分页创建的新幻灯片
|
||||||
|
- 类型:`PresSlide[]`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Slide 类方法
|
||||||
|
|
||||||
|
- **addChart(type, data, options?)**
|
||||||
|
- 说明:添加图表
|
||||||
|
- 参数:
|
||||||
|
- `type`:`CHART_NAME | IChartMulti[]`
|
||||||
|
- `data`:`any[]`
|
||||||
|
- `options?`:`IChartOpts`
|
||||||
|
- 返回:`Slide`
|
||||||
|
|
||||||
|
- **addImage(options)**
|
||||||
|
- 说明:添加图片
|
||||||
|
- 参数:`options` — `ImageProps`
|
||||||
|
- 返回:`Slide`
|
||||||
|
|
||||||
|
- **addMedia(options)**
|
||||||
|
- 说明:添加媒体(音频/视频)
|
||||||
|
- 参数:`options` — `MediaProps`
|
||||||
|
- 返回:`Slide`
|
||||||
|
|
||||||
|
- **addNotes(notes)**
|
||||||
|
- 说明:添加演讲者备注
|
||||||
|
- 参数:`notes` — `string`
|
||||||
|
- 返回:`Slide`
|
||||||
|
|
||||||
|
- **addShape(shapeName, options?)**
|
||||||
|
- 说明:添加形状
|
||||||
|
- 参数:
|
||||||
|
- `shapeName`:`SHAPE_NAME`
|
||||||
|
- `options?`:`ShapeProps`
|
||||||
|
- 返回:`Slide`
|
||||||
|
|
||||||
|
- **addTable(tableRows, options?)**
|
||||||
|
- 说明:添加表格
|
||||||
|
- 参数:
|
||||||
|
- `tableRows`:`TableRow[]`
|
||||||
|
- `options?`:`TableProps`
|
||||||
|
- 返回:`Slide`
|
||||||
|
|
||||||
|
- **addText(text, options?)**
|
||||||
|
- 说明:添加文字
|
||||||
|
- 参数:
|
||||||
|
- `text`:`string | TextProps[]`
|
||||||
|
- `options?`:`TextPropsOptions`
|
||||||
|
- 返回:`Slide`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## PresSlide 接口
|
||||||
|
|
||||||
|
`PresSlide` 为内部幻灯片表示,包含以下属性和方法:
|
||||||
|
|
||||||
|
- **addChart**:添加图表
|
||||||
|
- 类型:`Function`
|
||||||
|
|
||||||
|
- **addImage**:添加图片
|
||||||
|
- 类型:`Function`
|
||||||
|
|
||||||
|
- **addMedia**:添加媒体
|
||||||
|
- 类型:`Function`
|
||||||
|
|
||||||
|
- **addNotes**:添加备注
|
||||||
|
- 类型:`Function`
|
||||||
|
|
||||||
|
- **addShape**:添加形状
|
||||||
|
- 类型:`Function`
|
||||||
|
|
||||||
|
- **addTable**:添加表格
|
||||||
|
- 类型:`Function`
|
||||||
|
|
||||||
|
- **addText**:添加文字
|
||||||
|
- 类型:`Function`
|
||||||
|
|
||||||
|
- **background**:背景
|
||||||
|
- 类型:`BackgroundProps`
|
||||||
|
|
||||||
|
- **color**:默认文字颜色
|
||||||
|
- 类型:`HexColor`
|
||||||
|
|
||||||
|
- **hidden**:是否隐藏
|
||||||
|
- 类型:`boolean`
|
||||||
|
|
||||||
|
- **slideNumber**:幻灯片编号
|
||||||
|
- 类型:`SlideNumberProps`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## BackgroundProps
|
||||||
|
|
||||||
|
继承 `DataOrPathProps`(`path`、`data`)和 `ShapeFillProps`(`color`、`transparency`、`type`)。
|
||||||
|
|
||||||
|
- **path**:背景图片 URL 或本地路径
|
||||||
|
- 类型:`string`
|
||||||
|
- 示例:`'https://onedrives.com/myimg.png'`
|
||||||
|
|
||||||
|
- **data**:背景图片 base64 数据
|
||||||
|
- 类型:`string`
|
||||||
|
- 示例:`'image/png;base64,iVtDafDrBF[...]='`
|
||||||
|
|
||||||
|
- **color**:背景颜色
|
||||||
|
- 类型:`Color`(来自 ShapeFillProps)
|
||||||
|
- 格式:6 位十六进制(无 `#`)或主题色
|
||||||
|
|
||||||
|
- **transparency**:背景透明度
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`100`
|
||||||
|
- 默认值:`0`
|
||||||
|
|
||||||
|
- **type**:填充类型
|
||||||
|
- 类型:`'none' | 'solid'`
|
||||||
|
- 默认值:`'solid'`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## SlideNumberProps
|
||||||
|
|
||||||
|
继承 `PositionProps` 和 `TextBaseProps`。
|
||||||
|
|
||||||
|
- **margin**:边距
|
||||||
|
- 类型:`Margin`(`number | [number, number, number, number]`)
|
||||||
|
- 单位:pt
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 幻灯片布局 (PresLayout)
|
||||||
|
|
||||||
|
```ts
|
||||||
|
pptx.defineLayout(layout: PresLayout)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **name**:布局名称
|
||||||
|
- 类型:`string`
|
||||||
|
- 示例:`'LAYOUT_WIDE'`
|
||||||
|
|
||||||
|
- **width**:宽度
|
||||||
|
- 类型:`number`
|
||||||
|
- 单位:英寸
|
||||||
|
|
||||||
|
- **height**:高度
|
||||||
|
- 类型:`number`
|
||||||
|
- 单位:英寸
|
||||||
|
|
||||||
|
### 标准布局尺寸
|
||||||
|
|
||||||
|
| 常量名 | 宽度 | 高度 |
|
||||||
|
|---|---|---|
|
||||||
|
| `LAYOUT_4x3` | 10" | 7.5" |
|
||||||
|
| `LAYOUT_16x9` | 10" | 5.625" |
|
||||||
|
| `LAYOUT_16x10` | 10" | 6.25" |
|
||||||
|
| `LAYOUT_WIDE` | 13.33" | 7.5" |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 节属性 (SectionProps)
|
||||||
|
|
||||||
|
```ts
|
||||||
|
pptx.addSection(props: SectionProps)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **title**:节标题
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **order**:节顺序
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`1`–`n`
|
||||||
|
- 说明:在指定索引添加节
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 已废弃参数 (BackgroundProps)
|
||||||
|
|
||||||
|
| 已废弃参数 | 替代参数 | 废弃版本 |
|
||||||
|
|---|---|---|
|
||||||
|
| `fill` | `ShapeFillProps.color` | v3.6.0 |
|
||||||
|
| `src` | `DataOrPathProps.path` | v3.6.0 |
|
||||||
|
| `bkgd` (Slide 类) | `background` | v3.3.0 |
|
||||||
@@ -0,0 +1,203 @@
|
|||||||
|
# 表格参数 (Table Props)
|
||||||
|
|
||||||
|
表格参数来自 `TableProps`(继承 `TextBaseProps`、`PositionProps`)和 `TableCellProps`(继承 `TextBaseProps`),用于 `slide.addTable()` 方法。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 位置属性 (PositionProps)
|
||||||
|
|
||||||
|
- **x**:水平位置
|
||||||
|
- 类型:`Coord`(`number | `${number}%``)
|
||||||
|
- 单位:英寸(number)或百分比(`'50%'`)
|
||||||
|
|
||||||
|
- **y**:垂直位置
|
||||||
|
- 类型:`Coord`
|
||||||
|
- 单位:英寸或百分比
|
||||||
|
|
||||||
|
- **w**:宽度
|
||||||
|
- 类型:`Coord`
|
||||||
|
- 单位:英寸或百分比
|
||||||
|
|
||||||
|
- **h**:高度
|
||||||
|
- 类型:`Coord`
|
||||||
|
- 单位:英寸或百分比
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 表格专有属性 (TableProps)
|
||||||
|
|
||||||
|
- **autoPage**:是否启用自动分页
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 说明:内容溢出时自动创建新幻灯片
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **autoPageCharWeight**:自动分页字符权重
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`-1.0`–`1.0`
|
||||||
|
- 默认值:`0.0`
|
||||||
|
- 说明:调整每行字符数,正值使行更长
|
||||||
|
|
||||||
|
- **autoPageLineWeight**:自动分页行权重
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`-1.0`–`1.0`
|
||||||
|
- 默认值:`0.0`
|
||||||
|
- 说明:调整每页行数,正值使表格更高
|
||||||
|
|
||||||
|
- **autoPageRepeatHeader**:自动分页时重复表头
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
- 始于:v3.3.0
|
||||||
|
|
||||||
|
- **autoPageHeaderRows**:表头行数
|
||||||
|
- 类型:`number`
|
||||||
|
- 说明:`autoPageRepeatHeader` 为 `true` 时需设置
|
||||||
|
- 默认值:`1`
|
||||||
|
- 示例:`2`(在前两行在新页上重复)
|
||||||
|
- 始于:v3.3.0
|
||||||
|
|
||||||
|
- **autoPageSlideStartY**:自动分页新幻灯片的起始 Y 位置
|
||||||
|
- 类型:`number`
|
||||||
|
- 默认值:幻灯片上边距
|
||||||
|
|
||||||
|
- **border**:表格边框
|
||||||
|
- 类型:`BorderProps | [BorderProps, BorderProps, BorderProps, BorderProps]`
|
||||||
|
- 取值范围:单个值应用于所有四边;数组按上右下左(TRBL)顺序应用于各边
|
||||||
|
|
||||||
|
- **colW**:列宽
|
||||||
|
- 类型:`number | number[]`
|
||||||
|
- 单位:英寸
|
||||||
|
- 说明:单个值均匀分配;数组按列依次应用
|
||||||
|
- 默认值:基于 `w` 的等宽列
|
||||||
|
|
||||||
|
- **fill**:单元格填充
|
||||||
|
- 类型:`ShapeFillProps`
|
||||||
|
- 子参数:
|
||||||
|
- **fill.color**:填充颜色
|
||||||
|
- 类型:`Color`
|
||||||
|
- **fill.transparency**:透明度
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`100`
|
||||||
|
- 默认值:`0`
|
||||||
|
- **fill.type**:填充类型
|
||||||
|
- 类型:`'none' | 'solid'`
|
||||||
|
- 默认值:`'solid'`
|
||||||
|
|
||||||
|
- **margin**:单元格边距
|
||||||
|
- 类型:`Margin`(`number | [number, number, number, number]`)
|
||||||
|
- 单位:pt
|
||||||
|
- 说明:影响所有单元格,可被单元格级选项覆盖
|
||||||
|
- 默认值:PowerPoint 默认边距
|
||||||
|
|
||||||
|
- **objectName**:对象名称
|
||||||
|
- 类型:`string`
|
||||||
|
- 说明:替代默认的 "Object N" 名称
|
||||||
|
- 默认值:`'Object 1'`
|
||||||
|
|
||||||
|
- **rowH**:行高
|
||||||
|
- 类型:`number | number[]`
|
||||||
|
- 单位:英寸
|
||||||
|
- 说明:单个值均匀分配;数组按行依次应用
|
||||||
|
- 默认值:基于 `h` 的等高行
|
||||||
|
|
||||||
|
- **verbose**:详细模式(开发工具)
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 说明:在控制台输出自动分页计算的详细信息
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **align**:水平对齐
|
||||||
|
- 类型:`'left' | 'center' | 'right' | 'justify'`
|
||||||
|
- 默认值:`'left'`
|
||||||
|
|
||||||
|
- **valign**:垂直对齐
|
||||||
|
- 类型:`'top' | 'middle' | 'bottom'`
|
||||||
|
- 默认值:`'top'`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 表格行与单元格
|
||||||
|
|
||||||
|
- **TableCell**:单个单元格
|
||||||
|
- 类型:`{ text?: string | TableCell[], options?: TableCellProps }`
|
||||||
|
- 说明:`text` 可为字符串或嵌套表格;`options` 为单元格样式
|
||||||
|
|
||||||
|
- **TableRow**:表格行
|
||||||
|
- 类型:`TableCell[]`
|
||||||
|
- 说明:每行由单元格数组组成
|
||||||
|
|
||||||
|
- **TableRowSlide**:自动分页产出
|
||||||
|
- 类型:`{ rows: TableRow[] }`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 表格单元格属性 (TableCellProps)
|
||||||
|
|
||||||
|
继承 `TextBaseProps`。
|
||||||
|
|
||||||
|
- **autoPageCharWeight**:自动分页字符权重
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`-1.0`–`1.0`
|
||||||
|
- 默认值:`0.0`
|
||||||
|
|
||||||
|
- **autoPageLineWeight**:自动分页行权重
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`-1.0`–`1.0`
|
||||||
|
- 默认值:`0.0`
|
||||||
|
|
||||||
|
- **border**:单元格边框
|
||||||
|
- 类型:`BorderProps | [BorderProps, BorderProps, BorderProps, BorderProps]`
|
||||||
|
- 说明:单个值应用于所有四边;数组按上右下左(TRBL)顺序
|
||||||
|
|
||||||
|
- **colspan**:列合并
|
||||||
|
- 类型:`number`
|
||||||
|
- 说明:跨列数
|
||||||
|
|
||||||
|
- **fill**:单元格填充
|
||||||
|
- 类型:`ShapeFillProps`
|
||||||
|
|
||||||
|
- **hyperlink**:单元格超链接
|
||||||
|
- 类型:`HyperlinkProps`
|
||||||
|
- 子参数:
|
||||||
|
- **hyperlink.slide**:链接到页码
|
||||||
|
- 类型:`number`
|
||||||
|
- **hyperlink.url**:链接 URL
|
||||||
|
- 类型:`string`
|
||||||
|
- **hyperlink.tooltip**:超链接提示
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **margin**:单元格边距
|
||||||
|
- 类型:`Margin`
|
||||||
|
- 单位:pt
|
||||||
|
- 默认值:`0`
|
||||||
|
|
||||||
|
- **rowspan**:行合并
|
||||||
|
- 类型:`number`
|
||||||
|
- 说明:跨行数
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## BorderProps
|
||||||
|
|
||||||
|
用于 `border` 属性的类型定义。
|
||||||
|
|
||||||
|
- **type**:边框类型
|
||||||
|
- 类型:`'none' | 'dash' | 'solid'`
|
||||||
|
- 默认值:`'solid'`
|
||||||
|
|
||||||
|
- **color**:边框颜色
|
||||||
|
- 类型:`HexColor`
|
||||||
|
- 格式:6 位十六进制(无 `#`)
|
||||||
|
- 默认值:`'666666'`
|
||||||
|
|
||||||
|
- **pt**:边框粗细
|
||||||
|
- 类型:`number`
|
||||||
|
- 单位:pt
|
||||||
|
- 默认值:`1`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 已废弃参数 (TableProps)
|
||||||
|
|
||||||
|
| 已废弃参数 | 替代参数 | 废弃版本 |
|
||||||
|
|---|---|---|
|
||||||
|
| `addHeaderToEach` | `autoPageRepeatHeader` | v3.3.0 |
|
||||||
|
| `newSlideStartY` | `autoPageSlideStartY` | v3.3.0 |
|
||||||
@@ -0,0 +1,405 @@
|
|||||||
|
# 文字参数 (Text Props)
|
||||||
|
|
||||||
|
文字参数来自 `TextPropsOptions`(继承 `TextBaseProps`、`PositionProps`、`DataOrPathProps`、`ObjectNameProps`)和 `TextGlowProps`,用于 `slide.addText()` 方法。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 基础文字属性 (TextBaseProps)
|
||||||
|
|
||||||
|
- **align**:水平对齐方式
|
||||||
|
- 类型:`'left' | 'center' | 'right' | 'justify'`
|
||||||
|
- 取值范围:`left`、`center`、`right`、`justify`
|
||||||
|
- 默认值:`'left'`
|
||||||
|
|
||||||
|
- **bold**:粗体样式
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 取值范围:`true` / `false`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **breakLine**:添加换行符
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 取值范围:`true` / `false`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **bullet**:添加标准或自定义项目符号
|
||||||
|
- 类型:`boolean | BulletOptions`
|
||||||
|
- 取值范围:
|
||||||
|
- `true` — 使用标准项目符号
|
||||||
|
- `false` — 无项目符号
|
||||||
|
- 对象 — 自定义项目符号配置
|
||||||
|
- 默认值:`false`
|
||||||
|
- 子参数:
|
||||||
|
- **bullet.type**:项目符号类型
|
||||||
|
- 类型:`'bullet' | 'number'`
|
||||||
|
- 取值范围:`bullet`、`number`
|
||||||
|
- 默认值:`'bullet'`
|
||||||
|
- **bullet.characterCode**:项目符号字符代码(Unicode)
|
||||||
|
- 类型:`string`
|
||||||
|
- 示例:`'25BA'`(U+25BA 黑色右指向指针)
|
||||||
|
- **bullet.indent**:缩进量(项目符号与文字之间的距离,单位 pt)
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:任意正整数
|
||||||
|
- 默认值:`27`
|
||||||
|
- **bullet.numberType**:编号类型
|
||||||
|
- 类型:`string`
|
||||||
|
- 取值范围:
|
||||||
|
`alphaLcParenBoth` | `alphaLcParenR` | `alphaLcPeriod` | `alphaUcParenBoth` | `alphaUcParenR` |
|
||||||
|
`alphaUcPeriod` | `arabicParenBoth` | `arabicParenR` | `arabicPeriod` | `arabicPlain` |
|
||||||
|
`romanLcParenBoth` | `romanLcParenR` | `romanLcPeriod` | `romanUcParenBoth` | `romanUcParenR` |
|
||||||
|
`romanUcPeriod`
|
||||||
|
- **bullet.numberStartAt**:编号起始值
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:任意正整数
|
||||||
|
- 默认值:`1`
|
||||||
|
|
||||||
|
- **color**:文字颜色
|
||||||
|
- 类型:`Color`(`HexColor | ThemeColor`)
|
||||||
|
- 取值范围:6 位十六进制颜色值(不含 `#`)或主题色(`tx1`、`tx2`、`bg1`、`bg2`、`accent1`–`accent6`)
|
||||||
|
- 示例:`'FF0000'`、`pptx.SchemeColor.text1`
|
||||||
|
|
||||||
|
- **fontFace**:字体名称
|
||||||
|
- 类型:`string`
|
||||||
|
- 示例:`'Arial'`、`'微软雅黑'`
|
||||||
|
|
||||||
|
- **fontSize**:字号
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`1`–`400`(pt)
|
||||||
|
- 示例:`12`、`24`
|
||||||
|
|
||||||
|
- **highlight**:文字高亮颜色
|
||||||
|
- 类型:`HexColor`
|
||||||
|
- 格式:6 位十六进制颜色值(不含 `#`)
|
||||||
|
- 示例:`'FFFF00'`(黄色)
|
||||||
|
|
||||||
|
- **italic**:斜体样式
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 取值范围:`true` / `false`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **lang**:语言
|
||||||
|
- 类型:`string`
|
||||||
|
- 取值范围:ISO 639-1 标准语言代码
|
||||||
|
- 默认值:`'en-US'`
|
||||||
|
- 示例:`'fr-CA'`
|
||||||
|
|
||||||
|
- **softBreakBefore**:在文字内容前添加软换行(Shift+Enter)
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 取值范围:`true` / `false`
|
||||||
|
- 默认值:`false`
|
||||||
|
- 始于:v3.5.0
|
||||||
|
|
||||||
|
- **tabStops**:制表位
|
||||||
|
- 类型:`Array<{ position: number, alignment?: 'l' | 'r' | 'ctr' | 'dec' }>`
|
||||||
|
- 说明:PowerPoint 段落 > 制表位 > 制表位位置
|
||||||
|
- 示例:`[{ position: 1 }, { position: 3 }]`
|
||||||
|
- 子参数:
|
||||||
|
- **position**:制表位位置(英寸)
|
||||||
|
- **alignment**:对齐方式(可选),`'l'`(左)、`'r'`(右)、`'ctr'`(居中)、`'dec'`(小数点)
|
||||||
|
|
||||||
|
- **textDirection**:文字方向
|
||||||
|
- 类型:`'horz' | 'vert' | 'vert270' | 'wordArtVert'`
|
||||||
|
- 取值范围:
|
||||||
|
- `horz` — 水平
|
||||||
|
- `vert` — 旋转 90 度
|
||||||
|
- `vert270` — 旋转 270 度
|
||||||
|
- `wordArtVert` — 堆叠
|
||||||
|
- 默认值:`'horz'`
|
||||||
|
|
||||||
|
- **transparency**:透明度
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`100`(百分比)
|
||||||
|
- 默认值:`0`
|
||||||
|
|
||||||
|
- **underline**:下划线属性
|
||||||
|
- 类型:`{ style?: string, color?: Color }`
|
||||||
|
- 子参数:
|
||||||
|
- **underline.style**:下划线样式
|
||||||
|
- 类型:`string`
|
||||||
|
- 取值范围:
|
||||||
|
`'dash'` | `'dashHeavy'` | `'dashLong'` | `'dashLongHeavy'` | `'dbl'` | `'dotDash'` |
|
||||||
|
`'dotDashHeave'` | `'dotDotDash'` | `'dotDotDashHeavy'` | `'dotted'` | `'dottedHeavy'` |
|
||||||
|
`'heavy'` | `'none'` | `'sng'` | `'wavy'` | `'wavyDbl'` | `'wavyHeavy'`
|
||||||
|
- **underline.color**:下划线颜色
|
||||||
|
- 类型:`Color`
|
||||||
|
|
||||||
|
- **valign**:垂直对齐
|
||||||
|
- 类型:`'top' | 'middle' | 'bottom'`
|
||||||
|
- 取值范围:`top`、`middle`、`bottom`
|
||||||
|
- 默认值:`'top'`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 文字专有属性 (TextPropsOptions 特有)
|
||||||
|
|
||||||
|
继承 `DataOrPathProps`,支持以下嵌入图片的属性:
|
||||||
|
|
||||||
|
- **path**:图片路径
|
||||||
|
- 类型:`string`
|
||||||
|
- 说明:URL 或本地路径,可在文本框中嵌入图片
|
||||||
|
|
||||||
|
- **data**:图片 Base64 数据
|
||||||
|
- 类型:`string`
|
||||||
|
- 说明:Base64 编码的图片数据,替代 `path`
|
||||||
|
|
||||||
|
- **baseline**:基线偏移
|
||||||
|
- 类型:`number`
|
||||||
|
|
||||||
|
- **charSpacing**:字符间距
|
||||||
|
- 类型:`number`
|
||||||
|
|
||||||
|
- **fit**:文本适配方式
|
||||||
|
- 类型:`'none' | 'shrink' | 'resize'`
|
||||||
|
- 取值范围:
|
||||||
|
- `none` — 不自动调整
|
||||||
|
- `shrink` — 溢出时缩小文字
|
||||||
|
- `resize` — 调整形状以适配文字
|
||||||
|
- 默认值:`'none'`
|
||||||
|
- 始于:v3.3.0
|
||||||
|
|
||||||
|
- **fill**:形状填充
|
||||||
|
- 类型:`ShapeFillProps`
|
||||||
|
- 子参数:
|
||||||
|
- **fill.color**:填充颜色
|
||||||
|
- 类型:`Color`
|
||||||
|
- 格式:`HexColor`(6 位十六进制无 `#`)或 `ThemeColor`
|
||||||
|
- **fill.transparency**:透明度
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`100`
|
||||||
|
- 默认值:`0`
|
||||||
|
- **fill.type**:填充类型
|
||||||
|
- 类型:`'none' | 'solid'`
|
||||||
|
- 默认值:`'solid'`
|
||||||
|
|
||||||
|
- **flipH**:水平翻转
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 取值范围:`true` / `false`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **flipV**:垂直翻转
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 取值范围:`true` / `false`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **glow**:发光效果
|
||||||
|
- 类型:`TextGlowProps`
|
||||||
|
- 子参数:
|
||||||
|
- **glow.color**:发光颜色
|
||||||
|
- 类型:`HexColor`
|
||||||
|
- 格式:6 位十六进制(无 `#`)
|
||||||
|
- **glow.opacity**:不透明度
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0.0`–`1.0`
|
||||||
|
- **glow.size**:发光大小
|
||||||
|
- 类型:`number`
|
||||||
|
- 单位:pt
|
||||||
|
|
||||||
|
- **hyperlink**:超链接
|
||||||
|
- 类型:`HyperlinkProps`
|
||||||
|
- 子参数:
|
||||||
|
- **hyperlink.slide**:链接到页码
|
||||||
|
- 类型:`number`
|
||||||
|
- **hyperlink.url**:链接 URL
|
||||||
|
- 类型:`string`
|
||||||
|
- **hyperlink.tooltip**:超链接提示
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **indentLevel**:缩进层级
|
||||||
|
- 类型:`number`
|
||||||
|
|
||||||
|
- **isTextBox**:是否为文本框
|
||||||
|
- 类型:`boolean`
|
||||||
|
|
||||||
|
- **line**:线条(边框/轮廓线)
|
||||||
|
- 类型:`ShapeLineProps`
|
||||||
|
- 子参数(继承 `ShapeFillProps`):
|
||||||
|
- **line.color**:线条颜色
|
||||||
|
- 类型:`Color`
|
||||||
|
- **line.transparency**:线条透明度
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`100`
|
||||||
|
- **line.type**:填充类型
|
||||||
|
- 类型:`'none' | 'solid'`
|
||||||
|
- **line.width**:线条宽度
|
||||||
|
- 类型:`number`
|
||||||
|
- 单位:pt
|
||||||
|
- 默认值:`1`
|
||||||
|
- **line.dashType**:虚线类型
|
||||||
|
- 类型:`string`
|
||||||
|
- 取值范围:`'solid' | 'dash' | 'dashDot' | 'lgDash' | 'lgDashDot' | 'lgDashDotDot' | 'sysDash' | 'sysDot'`
|
||||||
|
- 默认值:`'solid'`
|
||||||
|
- **line.beginArrowType**:起点箭头类型
|
||||||
|
- 类型:`'none' | 'arrow' | 'diamond' | 'oval' | 'stealth' | 'triangle'`
|
||||||
|
- **line.endArrowType**:终点箭头类型
|
||||||
|
- 类型:`'none' | 'arrow' | 'diamond' | 'oval' | 'stealth' | 'triangle'`
|
||||||
|
|
||||||
|
- **lineSpacing**:行间距(固定值)
|
||||||
|
- 类型:`number`
|
||||||
|
- 单位:pt
|
||||||
|
- 说明:PowerPoint 段落 > 缩进和间距 > 行距 > "固定值"
|
||||||
|
- 示例:`28`
|
||||||
|
|
||||||
|
- **lineSpacingMultiple**:行间距(倍数)
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0.0`–`9.99`
|
||||||
|
- 单位:倍数(非百分比)
|
||||||
|
- 说明:PowerPoint 段落 > 缩进和间距 > 行距 > "多倍行距"
|
||||||
|
- 示例:`1.5`(1.5 倍行距)
|
||||||
|
- 始于:v3.5.0
|
||||||
|
|
||||||
|
- **margin**:边距
|
||||||
|
- 类型:`Margin`(`number | [number, number, number, number]`)
|
||||||
|
- 单位:pt
|
||||||
|
- 说明:PowerPoint 设置形状格式 > 形状选项 > 大小与属性 > 文本框 > 左右上下边距
|
||||||
|
- 默认值:PowerPoint 默认 "常规" 边距
|
||||||
|
- 示例:`0`(四边统一)、`[10, 5, 10, 5]`(上、右、下、左)
|
||||||
|
|
||||||
|
- **outline**:文字轮廓
|
||||||
|
- 类型:`{ color: Color, size: number }`
|
||||||
|
- 子参数:
|
||||||
|
- **outline.color**:轮廓颜色
|
||||||
|
- 类型:`Color`
|
||||||
|
- **outline.size**:轮廓大小
|
||||||
|
- 类型:`number`
|
||||||
|
- 单位:pt
|
||||||
|
|
||||||
|
- **paraSpaceAfter**:段后间距
|
||||||
|
- 类型:`number`
|
||||||
|
|
||||||
|
- **paraSpaceBefore**:段前间距
|
||||||
|
- 类型:`number`
|
||||||
|
|
||||||
|
- **placeholder**:占位符类型
|
||||||
|
- 类型:`string`
|
||||||
|
- 示例:`'body'`
|
||||||
|
|
||||||
|
- **rectRadius**:圆角矩形半径
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0.0`–`1.0`
|
||||||
|
- 默认值:`0`
|
||||||
|
- 说明:仅适用于 `ROUNDED_RECTANGLE` 形状
|
||||||
|
|
||||||
|
- **rotate**:旋转角度
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`-360`–`360`(度)
|
||||||
|
- 默认值:`0`
|
||||||
|
|
||||||
|
- **rtlMode**:从右向左模式
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 取值范围:`true` / `false`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **shadow**:阴影
|
||||||
|
- 类型:`ShadowProps`
|
||||||
|
- 子参数:
|
||||||
|
- **shadow.type**:阴影类型
|
||||||
|
- 类型:`'outer' | 'inner' | 'none'`
|
||||||
|
- 默认值:`'none'`
|
||||||
|
- **shadow.opacity**:不透明度
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0.0`–`1.0`
|
||||||
|
- **shadow.blur**:模糊
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`100`(pt)
|
||||||
|
- 默认值:`0`
|
||||||
|
- **shadow.angle**:角度
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`359`(度)
|
||||||
|
- 默认值:`0`
|
||||||
|
- **shadow.offset**:偏移距离
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`200`(pt)
|
||||||
|
- 默认值:`0`
|
||||||
|
- **shadow.color**:阴影颜色
|
||||||
|
- 类型:`HexColor`
|
||||||
|
- 格式:6 位十六进制(无 `#`)
|
||||||
|
- **shadow.rotateWithShape**:阴影是否随形状旋转
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **shape**:形状名称
|
||||||
|
- 类型:`SHAPE_NAME`
|
||||||
|
- 说明:将文字转为指定形状
|
||||||
|
|
||||||
|
- **strike**:删除线
|
||||||
|
- 类型:`boolean | 'dblStrike' | 'sngStrike'`
|
||||||
|
- 取值范围:`true`(单删除线)、`false`、`'dblStrike'`(双删除线)、`'sngStrike'`(单删除线)
|
||||||
|
|
||||||
|
- **subscript**:下标
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 取值范围:`true` / `false`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **superscript**:上标
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 取值范围:`true` / `false`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **vert**:垂直文字模式
|
||||||
|
- 类型:`string`
|
||||||
|
- 取值范围:
|
||||||
|
`'eaVert'` | `'horz'` | `'mongolianVert'` | `'vert'` | `'vert270'` | `'wordArtVert'` | `'wordArtVertRtl'`
|
||||||
|
|
||||||
|
- **wrap**:文字自动换行
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 取值范围:`true` / `false`
|
||||||
|
- 默认值:`true`
|
||||||
|
- 始于:v3.3.0
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 位置属性 (PositionProps)
|
||||||
|
|
||||||
|
- **x**:水平位置
|
||||||
|
- 类型:`Coord`(`number | `${number}%``)
|
||||||
|
- 单位:英寸(number)或百分比(`'50%'`)
|
||||||
|
|
||||||
|
- **y**:垂直位置
|
||||||
|
- 类型:`Coord`
|
||||||
|
- 单位:英寸或百分比
|
||||||
|
|
||||||
|
- **w**:宽度
|
||||||
|
- 类型:`Coord`
|
||||||
|
- 单位:英寸或百分比
|
||||||
|
|
||||||
|
- **h**:高度
|
||||||
|
- 类型:`Coord`
|
||||||
|
- 单位:英寸或百分比
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 对象名称属性 (ObjectNameProps)
|
||||||
|
|
||||||
|
- **objectName**:对象名称
|
||||||
|
- 类型:`string`
|
||||||
|
- 说明:替代默认的 "Object N" 名称,在 PowerPoint 选择窗格中显示
|
||||||
|
- 默认值:`'Object 1'`
|
||||||
|
- 始于:v3.10.0
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TextProps
|
||||||
|
|
||||||
|
- **text**:文字内容
|
||||||
|
- 类型:`string`
|
||||||
|
- **options**:文字选项
|
||||||
|
- 类型:`TextPropsOptions`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 已废弃参数
|
||||||
|
|
||||||
|
以下参数已废弃,建议使用替代参数:
|
||||||
|
|
||||||
|
| 已废弃参数 | 替代参数 | 废弃版本 |
|
||||||
|
|---|---|---|
|
||||||
|
| `autoFit` | `fit` | v3.3.0 |
|
||||||
|
| `shrinkText` | `fit` | v3.3.0 |
|
||||||
|
| `inset` | `margin` | v3.10.0 |
|
||||||
|
| `bullet.code` | `bullet.characterCode` | v3.3.0 |
|
||||||
|
| `bullet.marginPt` | `bullet.indent` | v3.3.0 |
|
||||||
|
| `bullet.startAt` | `bullet.numberStartAt` | v3.3.0 |
|
||||||
|
| `bullet.style` | `bullet.numberType` | v3.3.0 |
|
||||||
|
| `lineDash` | `line.dashType` | v3.3.0 |
|
||||||
|
| `lineHead` | `line.beginArrowType` | v3.3.0 |
|
||||||
|
| `lineSize` | `line.width` | v3.3.0 |
|
||||||
|
| `lineTail` | `line.endArrowType` | v3.3.0 |
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
var { normalizePosition, normalizeColor, normalizeColorInOptions, validateEnum } = require('./index');
|
||||||
|
|
||||||
|
function renderChart(slide, chartData) {
|
||||||
|
var opts = {};
|
||||||
|
if (chartData.options) {
|
||||||
|
for (var k in chartData.options) {
|
||||||
|
if (chartData.options.hasOwnProperty(k)) opts[k] = chartData.options[k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
normalizePosition(opts);
|
||||||
|
|
||||||
|
var validChartTypes = ['area', 'bar', 'bar3D', 'bubble', 'doughnut', 'line', 'pie', 'radar', 'scatter'];
|
||||||
|
validateEnum(chartData.chartType, validChartTypes, 'chartType');
|
||||||
|
|
||||||
|
if (chartData.data) {
|
||||||
|
for (var i = 0; i < chartData.data.length; i++) {
|
||||||
|
var series = chartData.data[i];
|
||||||
|
if (series.labels && series.values && series.labels.length !== series.values.length) {
|
||||||
|
console.warn('Chart series "' + (series.name || i) + '" has mismatched labels (' + series.labels.length + ') and values (' + series.values.length + ')');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
validateEnum(opts.barDir, ['bar', 'col'], 'barDir');
|
||||||
|
validateEnum(opts.barGrouping, ['clustered', 'stacked', 'standard'], 'barGrouping');
|
||||||
|
validateEnum(opts.radarStyle, ['standard', 'marker', 'filled'], 'radarStyle');
|
||||||
|
validateEnum(opts.legendPos, ['b', 'l', 'r', 't', 'tr'], 'legendPos');
|
||||||
|
validateEnum(opts.lineCap, ['flat', 'round', 'square'], 'lineCap');
|
||||||
|
validateEnum(opts.lineDataSymbol, ['circle', 'dash', 'diamond', 'dot', 'none', 'square', 'triangle'], 'lineDataSymbol');
|
||||||
|
validateEnum(opts.displayBlanksAs, ['gap', 'span', 'zero'], 'displayBlanksAs');
|
||||||
|
validateEnum(opts.bar3DShape, ['box', 'cylinder', 'cone', 'pyramid'], 'bar3DShape');
|
||||||
|
validateEnum(opts.dataLabelPosition, ['b', 'bestFit', 'ctr', 'l', 'r', 't', 'inEnd', 'outEnd'], 'dataLabelPosition');
|
||||||
|
validateEnum(opts.titleAlign, ['l', 'ctr', 'r'], 'titleAlign');
|
||||||
|
|
||||||
|
if (opts.chartColors) {
|
||||||
|
for (var i = 0; i < opts.chartColors.length; i++) {
|
||||||
|
var nc = normalizeColor(opts.chartColors[i]);
|
||||||
|
if (nc) opts.chartColors[i] = nc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
slide.addChart(chartData.chartType, chartData.data, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { renderChart };
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
var { normalizePosition, normalizeColorInOptions, validateEnum } = require('./index');
|
||||||
|
|
||||||
|
function renderImage(slide, imageData) {
|
||||||
|
var opts = {};
|
||||||
|
if (imageData.options) {
|
||||||
|
for (var k in imageData.options) {
|
||||||
|
if (imageData.options.hasOwnProperty(k)) opts[k] = imageData.options[k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
normalizePosition(opts);
|
||||||
|
|
||||||
|
if (opts.sizing && typeof opts.sizing === 'object') {
|
||||||
|
validateEnum(opts.sizing.type, ['contain', 'cover', 'crop'], 'sizing.type');
|
||||||
|
}
|
||||||
|
if (opts.shadow && typeof opts.shadow === 'object') {
|
||||||
|
normalizeColorInOptions(opts.shadow, 'color');
|
||||||
|
validateEnum(opts.shadow.type, ['outer', 'inner', 'none'], 'shadow.type');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
slide.addImage(opts);
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Failed to load image: ' + (opts.path || 'unknown'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { renderImage };
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
const pptxgen = require('pptxgenjs');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
function normalizePosition(options) {
|
||||||
|
if (!options || typeof options !== 'object') return options;
|
||||||
|
['x', 'y', 'w', 'h'].forEach(function(key) {
|
||||||
|
if (options[key] === undefined) return;
|
||||||
|
var val = options[key];
|
||||||
|
if (typeof val === 'number' && !isNaN(val)) return;
|
||||||
|
if (typeof val === 'string' && /^\d+(\.\d+)?%$/.test(val)) return;
|
||||||
|
console.warn('Invalid position value for "' + key + '": got ' + JSON.stringify(val) + ', expected a number or percentage string (e.g. "50%")');
|
||||||
|
delete options[key];
|
||||||
|
});
|
||||||
|
if ((options.x !== undefined || options.y !== undefined) && options.w === undefined) options.w = 4;
|
||||||
|
if ((options.x !== undefined || options.y !== undefined) && options.h === undefined) options.h = 3;
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeColor(color) {
|
||||||
|
if (color === undefined || color === null) return color;
|
||||||
|
if (typeof color !== 'string') {
|
||||||
|
console.warn('Invalid color value: expected a string, got ' + typeof color);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
var themeColors = ['tx1', 'tx2', 'bg1', 'bg2', 'accent1', 'accent2', 'accent3', 'accent4', 'accent5', 'accent6'];
|
||||||
|
for (var i = 0; i < themeColors.length; i++) {
|
||||||
|
if (color === themeColors[i]) return color;
|
||||||
|
}
|
||||||
|
var clean = color.replace(/^#/, '');
|
||||||
|
if (/^[0-9A-Fa-f]{6}$/.test(clean)) return clean;
|
||||||
|
console.warn('Invalid color value: "' + color + '" is not a valid color format (expected 6-digit hex or theme color)');
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeColorInOptions(options, key) {
|
||||||
|
if (!options || options[key] === undefined) return;
|
||||||
|
var nc = normalizeColor(options[key]);
|
||||||
|
if (nc === undefined) delete options[key];
|
||||||
|
else options[key] = nc;
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateEnum(value, validValues, name) {
|
||||||
|
if (value === undefined || value === null) return value;
|
||||||
|
if (validValues.indexOf(value) === -1) {
|
||||||
|
console.warn('Invalid value for ' + name + ': got "' + value + '", expected one of [' + validValues.join(', ') + ']');
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function withDefaults(options, defaults) {
|
||||||
|
if (!options || typeof options !== 'object') return options;
|
||||||
|
for (var key in defaults) {
|
||||||
|
if (defaults.hasOwnProperty(key) && options[key] === undefined) {
|
||||||
|
options[key] = defaults[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.normalizePosition = normalizePosition;
|
||||||
|
module.exports.normalizeColor = normalizeColor;
|
||||||
|
module.exports.normalizeColorInOptions = normalizeColorInOptions;
|
||||||
|
module.exports.validateEnum = validateEnum;
|
||||||
|
module.exports.withDefaults = withDefaults;
|
||||||
|
|
||||||
|
const { renderSlide } = require('./slide-renderer');
|
||||||
|
|
||||||
|
function render(pptxJson) {
|
||||||
|
const pptx = new pptxgen();
|
||||||
|
const data = typeof pptxJson === 'string' ? JSON.parse(pptxJson) : pptxJson;
|
||||||
|
const pres = data.presentation || {};
|
||||||
|
const slides = data.slides || [];
|
||||||
|
|
||||||
|
if (pres.layout) pptx.layout = pres.layout;
|
||||||
|
if (pres.author) pptx.author = pres.author;
|
||||||
|
if (pres.title) pptx.title = pres.title;
|
||||||
|
if (pres.subject) pptx.subject = pres.subject;
|
||||||
|
if (pres.company) pptx.company = pres.company;
|
||||||
|
if (pres.revision) pptx.revision = pres.revision;
|
||||||
|
if (pres.rtlMode !== undefined) pptx.rtlMode = pres.rtlMode;
|
||||||
|
if (pres.theme) pptx.theme = pres.theme;
|
||||||
|
|
||||||
|
slides.forEach(slideData => renderSlide(pptx, slideData));
|
||||||
|
|
||||||
|
return pptx;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function renderFile(inputPath, outputPath) {
|
||||||
|
const json = fs.readFileSync(inputPath, 'utf8');
|
||||||
|
const pptx = render(json);
|
||||||
|
|
||||||
|
// pptxgenjs v4 uses https.get() to download URL-based images but doesn't
|
||||||
|
// handle connection errors on the request object (only on the response
|
||||||
|
// stream). A failed TLS handshake emits an unhandled 'error' event on the
|
||||||
|
// request that crashes Node.js, and the internal promise never resolves.
|
||||||
|
// Patch to pipe request errors into a fake response that ends cleanly so
|
||||||
|
// writeFile completes with an empty placeholder image.
|
||||||
|
var https = require('https');
|
||||||
|
var _origGet = https.get;
|
||||||
|
var EventEmitter = require('events').EventEmitter;
|
||||||
|
https.get = function() {
|
||||||
|
var args = arguments;
|
||||||
|
var cb = typeof args[args.length - 1] === 'function' ? args[args.length - 1] : null;
|
||||||
|
var urlStr = typeof args[0] === 'string' ? args[0] : '';
|
||||||
|
var req = _origGet.apply(this, args);
|
||||||
|
req.on('error', function(err) {
|
||||||
|
console.warn('Failed to load image: ' + (urlStr || err.hostname || err.host || 'unknown'));
|
||||||
|
if (cb) {
|
||||||
|
var fakeRes = new EventEmitter();
|
||||||
|
fakeRes.setEncoding = function() {};
|
||||||
|
cb(fakeRes);
|
||||||
|
process.nextTick(function() {
|
||||||
|
fakeRes.emit('data', '');
|
||||||
|
fakeRes.emit('end');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return req;
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await pptx.writeFile({ fileName: outputPath });
|
||||||
|
} finally {
|
||||||
|
https.get = _origGet;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.render = render;
|
||||||
|
module.exports.renderFile = renderFile;
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
var { normalizePosition, normalizeColorInOptions, validateEnum } = require('./index');
|
||||||
|
|
||||||
|
function renderShape(slide, shapeData) {
|
||||||
|
var opts = {};
|
||||||
|
if (shapeData.options) {
|
||||||
|
for (var k in shapeData.options) {
|
||||||
|
if (shapeData.options.hasOwnProperty(k)) opts[k] = shapeData.options[k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
normalizePosition(opts);
|
||||||
|
|
||||||
|
if (opts.fill && typeof opts.fill === 'object') {
|
||||||
|
normalizeColorInOptions(opts.fill, 'color');
|
||||||
|
validateEnum(opts.fill.type, ['none', 'solid'], 'fill.type');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.line && typeof opts.line === 'object') {
|
||||||
|
normalizeColorInOptions(opts.line, 'color');
|
||||||
|
validateEnum(opts.line.dashType, ['solid', 'dash', 'dashDot', 'lgDash', 'lgDashDot', 'lgDashDotDot', 'sysDash', 'sysDot'], 'line.dashType');
|
||||||
|
validateEnum(opts.line.beginArrowType, ['none', 'arrow', 'diamond', 'oval', 'stealth', 'triangle'], 'line.beginArrowType');
|
||||||
|
validateEnum(opts.line.endArrowType, ['none', 'arrow', 'diamond', 'oval', 'stealth', 'triangle'], 'line.endArrowType');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.shadow && typeof opts.shadow === 'object') {
|
||||||
|
normalizeColorInOptions(opts.shadow, 'color');
|
||||||
|
validateEnum(opts.shadow.type, ['outer', 'inner', 'none'], 'shadow.type');
|
||||||
|
}
|
||||||
|
|
||||||
|
validateEnum(opts.align, ['left', 'center', 'right', 'justify'], 'align');
|
||||||
|
|
||||||
|
slide.addShape(shapeData.shapeName, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { renderShape };
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
const { normalizeColor } = require('./index');
|
||||||
|
const { renderText } = require('./text-renderer');
|
||||||
|
const { renderShape } = require('./shape-renderer');
|
||||||
|
const { renderTable } = require('./table-renderer');
|
||||||
|
const { renderImage } = require('./image-renderer');
|
||||||
|
const { renderChart } = require('./chart-renderer');
|
||||||
|
|
||||||
|
function renderSlide(pptx, slideData) {
|
||||||
|
const slide = pptx.addSlide();
|
||||||
|
|
||||||
|
if (slideData.background) {
|
||||||
|
if (typeof slideData.background === 'object') {
|
||||||
|
slide.background = {};
|
||||||
|
for (var k in slideData.background) {
|
||||||
|
if (slideData.background.hasOwnProperty(k)) {
|
||||||
|
slide.background[k] = slideData.background[k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (slide.background.color) {
|
||||||
|
var nc = normalizeColor(slide.background.color);
|
||||||
|
if (nc) slide.background.color = nc;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
slide.background = slideData.background;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (slideData.color) {
|
||||||
|
var nc = normalizeColor(slideData.color);
|
||||||
|
if (nc) slide.color = nc;
|
||||||
|
}
|
||||||
|
if (slideData.hidden) slide.hidden = slideData.hidden;
|
||||||
|
|
||||||
|
const objects = slideData.objects || [];
|
||||||
|
objects.forEach(obj => {
|
||||||
|
switch (obj.type) {
|
||||||
|
case 'text':
|
||||||
|
renderText(slide, obj);
|
||||||
|
break;
|
||||||
|
case 'shape':
|
||||||
|
renderShape(slide, obj);
|
||||||
|
break;
|
||||||
|
case 'table':
|
||||||
|
renderTable(slide, obj);
|
||||||
|
break;
|
||||||
|
case 'image':
|
||||||
|
renderImage(slide, obj);
|
||||||
|
break;
|
||||||
|
case 'chart':
|
||||||
|
renderChart(slide, obj);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { renderSlide };
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
var { normalizePosition, normalizeColorInOptions, validateEnum } = require('./index');
|
||||||
|
|
||||||
|
function renderTable(slide, tableData) {
|
||||||
|
var opts = {};
|
||||||
|
if (tableData.options) {
|
||||||
|
for (var k in tableData.options) {
|
||||||
|
if (tableData.options.hasOwnProperty(k)) opts[k] = tableData.options[k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
normalizePosition(opts);
|
||||||
|
|
||||||
|
if (opts.fill && typeof opts.fill === 'object') {
|
||||||
|
normalizeColorInOptions(opts.fill, 'color');
|
||||||
|
validateEnum(opts.fill.type, ['none', 'solid'], 'fill.type');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.border) {
|
||||||
|
if (Array.isArray(opts.border)) {
|
||||||
|
if (opts.border.length !== 4) {
|
||||||
|
console.warn('Invalid table border array: expected 4 sides, got ' + opts.border.length);
|
||||||
|
}
|
||||||
|
} else if (typeof opts.border === 'object') {
|
||||||
|
normalizeColorInOptions(opts.border, 'color');
|
||||||
|
if (opts.border.type !== undefined) {
|
||||||
|
validateEnum(opts.border.type, ['none', 'dash', 'solid'], 'border.type');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var rows = [];
|
||||||
|
if (tableData.rows) {
|
||||||
|
for (var r = 0; r < tableData.rows.length; r++) {
|
||||||
|
var row = tableData.rows[r];
|
||||||
|
var newRow = [];
|
||||||
|
for (var c = 0; c < row.length; c++) {
|
||||||
|
var cell = row[c];
|
||||||
|
var newCell = { text: cell.text };
|
||||||
|
|
||||||
|
if (cell.colspan !== undefined) {
|
||||||
|
if (typeof cell.colspan !== 'number' || !isFinite(cell.colspan) || cell.colspan < 1 || cell.colspan !== Math.floor(cell.colspan)) {
|
||||||
|
console.warn('Invalid colspan: got ' + JSON.stringify(cell.colspan) + ', expected integer >= 1');
|
||||||
|
}
|
||||||
|
newCell.colspan = cell.colspan;
|
||||||
|
}
|
||||||
|
if (cell.rowspan !== undefined) {
|
||||||
|
if (typeof cell.rowspan !== 'number' || !isFinite(cell.rowspan) || cell.rowspan < 1 || cell.rowspan !== Math.floor(cell.rowspan)) {
|
||||||
|
console.warn('Invalid rowspan: got ' + JSON.stringify(cell.rowspan) + ', expected integer >= 1');
|
||||||
|
}
|
||||||
|
newCell.rowspan = cell.rowspan;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cell.options) {
|
||||||
|
newCell.options = {};
|
||||||
|
for (var k in cell.options) {
|
||||||
|
if (cell.options.hasOwnProperty(k)) newCell.options[k] = cell.options[k];
|
||||||
|
}
|
||||||
|
normalizeColorInOptions(newCell.options, 'color');
|
||||||
|
normalizeColorInOptions(newCell.options, 'highlight');
|
||||||
|
if (newCell.options.fill && typeof newCell.options.fill === 'object') {
|
||||||
|
normalizeColorInOptions(newCell.options.fill, 'color');
|
||||||
|
}
|
||||||
|
if (newCell.options.border) {
|
||||||
|
if (Array.isArray(newCell.options.border)) {
|
||||||
|
if (newCell.options.border.length !== 4) {
|
||||||
|
console.warn('Invalid cell border array: expected 4 sides, got ' + newCell.options.border.length);
|
||||||
|
}
|
||||||
|
} else if (typeof newCell.options.border === 'object') {
|
||||||
|
normalizeColorInOptions(newCell.options.border, 'color');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
validateEnum(newCell.options.align, ['left', 'center', 'right', 'justify'], 'cell.align');
|
||||||
|
validateEnum(newCell.options.valign, ['top', 'middle', 'bottom'], 'cell.valign');
|
||||||
|
}
|
||||||
|
|
||||||
|
newRow.push(newCell);
|
||||||
|
}
|
||||||
|
rows.push(newRow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
slide.addTable(rows, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { renderTable };
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
var { normalizePosition, normalizeColor, normalizeColorInOptions, validateEnum } = require('./index');
|
||||||
|
|
||||||
|
function renderText(slide, textData) {
|
||||||
|
var opts = {};
|
||||||
|
if (textData.options) {
|
||||||
|
for (var k in textData.options) {
|
||||||
|
if (textData.options.hasOwnProperty(k)) opts[k] = textData.options[k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
normalizePosition(opts);
|
||||||
|
normalizeColorInOptions(opts, 'color');
|
||||||
|
normalizeColorInOptions(opts, 'highlight');
|
||||||
|
|
||||||
|
if (opts.underline && typeof opts.underline === 'object') {
|
||||||
|
normalizeColorInOptions(opts.underline, 'color');
|
||||||
|
}
|
||||||
|
if (opts.bullet && typeof opts.bullet === 'object') {
|
||||||
|
normalizeColorInOptions(opts.bullet, 'color');
|
||||||
|
}
|
||||||
|
if (opts.glow && typeof opts.glow === 'object') {
|
||||||
|
normalizeColorInOptions(opts.glow, 'color');
|
||||||
|
}
|
||||||
|
if (opts.outline && typeof opts.outline === 'object') {
|
||||||
|
normalizeColorInOptions(opts.outline, 'color');
|
||||||
|
}
|
||||||
|
|
||||||
|
validateEnum(opts.align, ['left', 'center', 'right', 'justify'], 'align');
|
||||||
|
validateEnum(opts.valign, ['top', 'middle', 'bottom'], 'valign');
|
||||||
|
validateEnum(opts.textDirection, ['horz', 'vert', 'vert270', 'wordArtVert'], 'textDirection');
|
||||||
|
validateEnum(opts.vert, ['eaVert', 'horz', 'mongolianVert', 'vert', 'vert270', 'wordArtVert', 'wordArtVertRtl'], 'vert');
|
||||||
|
validateEnum(opts.fit, ['none', 'shrink', 'resize'], 'fit');
|
||||||
|
|
||||||
|
if (typeof opts.strike === 'string') {
|
||||||
|
validateEnum(opts.strike, ['dblStrike', 'sngStrike'], 'strike');
|
||||||
|
}
|
||||||
|
if (opts.underline && opts.underline.style !== undefined) {
|
||||||
|
validateEnum(opts.underline.style, ['dash', 'dashHeavy', 'dashLong', 'dashLongHeavy', 'dbl', 'dotDash', 'dotDashHeave', 'dotDotDash', 'dotDotDashHeavy', 'dotted', 'dottedHeavy', 'heavy', 'none', 'sng', 'wavy', 'wavyDbl', 'wavyHeavy'], 'underline.style');
|
||||||
|
}
|
||||||
|
if (opts.bullet && typeof opts.bullet === 'object' && opts.bullet.type !== undefined) {
|
||||||
|
validateEnum(opts.bullet.type, ['bullet', 'number'], 'bullet.type');
|
||||||
|
}
|
||||||
|
|
||||||
|
slide.addText(textData.text, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { renderText };
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
{
|
||||||
|
"presentation": {
|
||||||
|
"layout": "LAYOUT_16x9",
|
||||||
|
"author": "Demo Author",
|
||||||
|
"title": "Basic Presentation"
|
||||||
|
},
|
||||||
|
"slides": [
|
||||||
|
{
|
||||||
|
"objects": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "Welcome to PPTX Demo",
|
||||||
|
"options": {
|
||||||
|
"x": 1.0,
|
||||||
|
"y": 2.0,
|
||||||
|
"w": 8.0,
|
||||||
|
"h": 1.5,
|
||||||
|
"fontSize": 44,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "2E75B6",
|
||||||
|
"bold": true,
|
||||||
|
"align": "center",
|
||||||
|
"valign": "middle"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"objects": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "Section Title",
|
||||||
|
"options": {
|
||||||
|
"x": 0.5,
|
||||||
|
"y": 0.3,
|
||||||
|
"w": 9.0,
|
||||||
|
"h": 0.8,
|
||||||
|
"fontSize": 28,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "2E75B6",
|
||||||
|
"bold": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "This is the body content of the slide. It demonstrates a typical title and body layout used in presentations.",
|
||||||
|
"options": {
|
||||||
|
"x": 0.5,
|
||||||
|
"y": 1.5,
|
||||||
|
"w": 9.0,
|
||||||
|
"h": 3.0,
|
||||||
|
"fontSize": 18,
|
||||||
|
"fontFace": "Calibri",
|
||||||
|
"color": "333333",
|
||||||
|
"align": "left",
|
||||||
|
"valign": "top",
|
||||||
|
"wrap": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,374 @@
|
|||||||
|
{
|
||||||
|
"presentation": {
|
||||||
|
"layout": "LAYOUT_16x9",
|
||||||
|
"author": "Jane Developer",
|
||||||
|
"title": "Full Feature Demo",
|
||||||
|
"subject": "Demonstrating all PPTX slide object types",
|
||||||
|
"company": "Acme Corp",
|
||||||
|
"revision": "3",
|
||||||
|
"rtlMode": false,
|
||||||
|
"theme": {
|
||||||
|
"headFontFace": "Calibri Light",
|
||||||
|
"bodyFontFace": "Calibri"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"slides": [
|
||||||
|
{
|
||||||
|
"background": {
|
||||||
|
"color": "2E75B6",
|
||||||
|
"transparency": 0
|
||||||
|
},
|
||||||
|
"objects": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "Full Feature Demo",
|
||||||
|
"options": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 1.5,
|
||||||
|
"w": 10,
|
||||||
|
"h": 1.5,
|
||||||
|
"fontSize": 48,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "FFFFFF",
|
||||||
|
"bold": true,
|
||||||
|
"align": "center",
|
||||||
|
"valign": "middle"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "PowerPoint Generation via JSON",
|
||||||
|
"options": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 3.2,
|
||||||
|
"w": 10,
|
||||||
|
"h": 0.8,
|
||||||
|
"fontSize": 20,
|
||||||
|
"fontFace": "Calibri",
|
||||||
|
"color": "D9E2F3",
|
||||||
|
"align": "center",
|
||||||
|
"valign": "top"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"objects": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "Text Formatting",
|
||||||
|
"options": {
|
||||||
|
"x": 0.5,
|
||||||
|
"y": 0.2,
|
||||||
|
"w": 9.0,
|
||||||
|
"h": 0.6,
|
||||||
|
"fontSize": 28,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "2E75B6",
|
||||||
|
"bold": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "Bold and italic text",
|
||||||
|
"options": {
|
||||||
|
"x": 0.5,
|
||||||
|
"y": 1.0,
|
||||||
|
"w": 4.0,
|
||||||
|
"h": 0.5,
|
||||||
|
"fontSize": 16,
|
||||||
|
"fontFace": "Calibri",
|
||||||
|
"bold": true,
|
||||||
|
"italic": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "Left aligned with underline",
|
||||||
|
"options": {
|
||||||
|
"x": 0.5,
|
||||||
|
"y": 1.7,
|
||||||
|
"w": 4.0,
|
||||||
|
"h": 0.5,
|
||||||
|
"fontSize": 14,
|
||||||
|
"fontFace": "Calibri",
|
||||||
|
"color": "0070C0",
|
||||||
|
"underline": { "style": "sng", "color": "0070C0" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "Right aligned text",
|
||||||
|
"options": {
|
||||||
|
"x": 5.5,
|
||||||
|
"y": 1.0,
|
||||||
|
"w": 4.0,
|
||||||
|
"h": 0.5,
|
||||||
|
"fontSize": 14,
|
||||||
|
"fontFace": "Calibri",
|
||||||
|
"color": "C00000",
|
||||||
|
"align": "right"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "Bullet point 1\nBullet point 2\nBullet point 3",
|
||||||
|
"options": {
|
||||||
|
"x": 0.5,
|
||||||
|
"y": 2.5,
|
||||||
|
"w": 9.0,
|
||||||
|
"h": 1.5,
|
||||||
|
"fontSize": 14,
|
||||||
|
"fontFace": "Calibri",
|
||||||
|
"bullet": { "type": "bullet", "characterCode": "25BA" },
|
||||||
|
"valign": "top"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"objects": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "Shapes",
|
||||||
|
"options": {
|
||||||
|
"x": 0.5,
|
||||||
|
"y": 0.2,
|
||||||
|
"w": 9.0,
|
||||||
|
"h": 0.6,
|
||||||
|
"fontSize": 28,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "2E75B6",
|
||||||
|
"bold": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "rect",
|
||||||
|
"options": {
|
||||||
|
"x": 0.5,
|
||||||
|
"y": 1.2,
|
||||||
|
"w": 2.5,
|
||||||
|
"h": 2.0,
|
||||||
|
"fill": { "color": "4472C4", "transparency": 20 },
|
||||||
|
"line": { "color": "000000", "width": 1 },
|
||||||
|
"shadow": { "type": "outer", "blur": 8, "offset": 4, "angle": 45, "color": "999999" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "ellipse",
|
||||||
|
"options": {
|
||||||
|
"x": 3.8,
|
||||||
|
"y": 1.2,
|
||||||
|
"w": 2.5,
|
||||||
|
"h": 2.0,
|
||||||
|
"fill": { "color": "ED7D31" },
|
||||||
|
"line": { "color": "333333", "width": 2 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "roundRect",
|
||||||
|
"options": {
|
||||||
|
"x": 7.0,
|
||||||
|
"y": 1.2,
|
||||||
|
"w": 2.5,
|
||||||
|
"h": 2.0,
|
||||||
|
"fill": { "color": "70AD47" },
|
||||||
|
"line": { "color": "000000", "width": 1.5, "dashType": "dash" },
|
||||||
|
"rectRadius": 0.2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"slideNumber": {
|
||||||
|
"x": 9.0,
|
||||||
|
"y": 5.0,
|
||||||
|
"fontSize": 10,
|
||||||
|
"color": "888888"
|
||||||
|
},
|
||||||
|
"objects": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "Images and Hyperlinks",
|
||||||
|
"options": {
|
||||||
|
"x": 0.5,
|
||||||
|
"y": 0.2,
|
||||||
|
"w": 9.0,
|
||||||
|
"h": 0.6,
|
||||||
|
"fontSize": 28,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "2E75B6",
|
||||||
|
"bold": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"options": {
|
||||||
|
"x": 0.5,
|
||||||
|
"y": 1.2,
|
||||||
|
"w": 4.0,
|
||||||
|
"h": 3.0,
|
||||||
|
"path": "https://via.placeholder.com/800x600.png?text=Sample+Image",
|
||||||
|
"sizing": { "type": "contain", "w": 4.0, "h": 3.0 },
|
||||||
|
"shadow": { "type": "outer", "blur": 6, "offset": 3, "angle": 45, "color": "666666" },
|
||||||
|
"hyperlink": { "url": "https://example.com", "tooltip": "Visit Example" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "Click the image above to visit Example.com",
|
||||||
|
"options": {
|
||||||
|
"x": 0.5,
|
||||||
|
"y": 4.5,
|
||||||
|
"w": 4.0,
|
||||||
|
"h": 0.5,
|
||||||
|
"fontSize": 12,
|
||||||
|
"fontFace": "Calibri",
|
||||||
|
"color": "4472C4",
|
||||||
|
"align": "center"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"objects": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "Table with colspan and rowspan",
|
||||||
|
"options": {
|
||||||
|
"x": 0.5,
|
||||||
|
"y": 0.2,
|
||||||
|
"w": 9.0,
|
||||||
|
"h": 0.6,
|
||||||
|
"fontSize": 28,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "2E75B6",
|
||||||
|
"bold": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "table",
|
||||||
|
"rows": [
|
||||||
|
[
|
||||||
|
{ "text": "Product", "options": { "bold": true, "fill": { "color": "4472C4" }, "color": "FFFFFF", "fontSize": 14 } },
|
||||||
|
{ "text": "Q1 Sales", "options": { "bold": true, "fill": { "color": "4472C4" }, "color": "FFFFFF", "fontSize": 14 } },
|
||||||
|
{ "text": "Q2 Sales", "options": { "bold": true, "fill": { "color": "4472C4" }, "color": "FFFFFF", "fontSize": 14 } },
|
||||||
|
{ "text": "Total", "options": { "bold": true, "fill": { "color": "4472C4" }, "color": "FFFFFF", "fontSize": 14 } }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ "text": "Widget A", "options": { "fill": { "color": "D9E2F3" } } },
|
||||||
|
{ "text": "$12,000", "options": { "fill": { "color": "D9E2F3" }, "align": "right" } },
|
||||||
|
{ "text": "$15,000", "options": { "fill": { "color": "D9E2F3" }, "align": "right" } },
|
||||||
|
{ "text": "$27,000", "options": { "fill": { "color": "D9E2F3" }, "align": "right", "bold": true } }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ "text": "Widget B", "options": { "fill": { "color": "EDEDED" } } },
|
||||||
|
{ "text": "$8,500", "options": { "fill": { "color": "EDEDED" }, "align": "right" } },
|
||||||
|
{ "text": "$9,200", "options": { "fill": { "color": "EDEDED" }, "align": "right" } },
|
||||||
|
{ "text": "$17,700", "options": { "fill": { "color": "EDEDED" }, "align": "right", "bold": true } }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ "text": "Combined Regions", "options": { "bold": true, "fill": { "color": "E2EFDA" } }, "colspan": 3 },
|
||||||
|
{ "text": "$44,700", "options": { "bold": true, "fill": { "color": "E2EFDA" }, "align": "right" } }
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"options": {
|
||||||
|
"x": 0.5,
|
||||||
|
"y": 1.2,
|
||||||
|
"w": 9.0,
|
||||||
|
"h": 3.5,
|
||||||
|
"colW": [3.0, 2.0, 2.0, 2.0],
|
||||||
|
"border": { "type": "solid", "color": "000000", "pt": 1 },
|
||||||
|
"fontSize": 12
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"objects": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "Bar Chart Example",
|
||||||
|
"options": {
|
||||||
|
"x": 0.5,
|
||||||
|
"y": 0.2,
|
||||||
|
"w": 9.0,
|
||||||
|
"h": 0.6,
|
||||||
|
"fontSize": 28,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "2E75B6",
|
||||||
|
"bold": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "chart",
|
||||||
|
"chartType": "bar",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"name": "North Region",
|
||||||
|
"labels": ["Q1", "Q2", "Q3", "Q4"],
|
||||||
|
"values": [45, 52, 38, 61]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "South Region",
|
||||||
|
"labels": ["Q1", "Q2", "Q3", "Q4"],
|
||||||
|
"values": [33, 41, 47, 55]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": {
|
||||||
|
"x": 0.5,
|
||||||
|
"y": 1.2,
|
||||||
|
"w": 9.0,
|
||||||
|
"h": 4.5,
|
||||||
|
"chartColors": ["4472C4", "ED7D31"],
|
||||||
|
"showTitle": true,
|
||||||
|
"showLegend": true,
|
||||||
|
"showValue": false,
|
||||||
|
"catAxisTitle": "Quarter",
|
||||||
|
"valAxisTitle": "Revenue ($K)",
|
||||||
|
"barGapWidthPct": 80
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": false,
|
||||||
|
"objects": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "Thank You!",
|
||||||
|
"options": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 1.8,
|
||||||
|
"w": 10,
|
||||||
|
"h": 1.2,
|
||||||
|
"fontSize": 40,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "2E75B6",
|
||||||
|
"bold": true,
|
||||||
|
"align": "center",
|
||||||
|
"valign": "middle"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "Questions?",
|
||||||
|
"options": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 3.2,
|
||||||
|
"w": 10,
|
||||||
|
"h": 0.8,
|
||||||
|
"fontSize": 20,
|
||||||
|
"fontFace": "Calibri",
|
||||||
|
"color": "666666",
|
||||||
|
"align": "center",
|
||||||
|
"valign": "top"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,984 @@
|
|||||||
|
{
|
||||||
|
"presentation": {
|
||||||
|
"layout": "CUSTOM"
|
||||||
|
},
|
||||||
|
"slides": [
|
||||||
|
{
|
||||||
|
"background": {
|
||||||
|
"color": "F5F6F8"
|
||||||
|
},
|
||||||
|
"objects": [
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "rect",
|
||||||
|
"options": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"w": 24.14,
|
||||||
|
"h": 0.667,
|
||||||
|
"fill": {
|
||||||
|
"color": "1A1A2E"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u817e\u8baf\u4e91",
|
||||||
|
"options": {
|
||||||
|
"x": 0.542,
|
||||||
|
"y": 0.056,
|
||||||
|
"w": 1.499,
|
||||||
|
"h": 0.278,
|
||||||
|
"fontSize": 16,
|
||||||
|
"color": "FFFFFF",
|
||||||
|
"bold": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u63a7\u5236\u53f0",
|
||||||
|
"options": {
|
||||||
|
"x": 2.278,
|
||||||
|
"y": 0.056,
|
||||||
|
"w": 1.201,
|
||||||
|
"h": 0.278,
|
||||||
|
"fontSize": 13,
|
||||||
|
"color": "FFFFFF"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u641c\u7d22\u4e91\u4ea7\u54c1\u3001\u8d44\u6e90\u4e0eAPI",
|
||||||
|
"options": {
|
||||||
|
"x": 6.875,
|
||||||
|
"y": 0.056,
|
||||||
|
"w": 7.0,
|
||||||
|
"h": 0.278,
|
||||||
|
"fontSize": 13,
|
||||||
|
"color": "8E95A1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u96c6\u56e2\u8d26\u53f7",
|
||||||
|
"options": {
|
||||||
|
"x": 8.986,
|
||||||
|
"y": 0.056,
|
||||||
|
"w": 1.049,
|
||||||
|
"h": 0.278,
|
||||||
|
"fontSize": 13,
|
||||||
|
"color": "FFFFFF"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u5907\u6848",
|
||||||
|
"options": {
|
||||||
|
"x": 9.708,
|
||||||
|
"y": 0.056,
|
||||||
|
"w": 0.526,
|
||||||
|
"h": 0.278,
|
||||||
|
"fontSize": 13,
|
||||||
|
"color": "FFFFFF"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u5de5\u5177",
|
||||||
|
"options": {
|
||||||
|
"x": 10.153,
|
||||||
|
"y": 0.056,
|
||||||
|
"w": 0.526,
|
||||||
|
"h": 0.278,
|
||||||
|
"fontSize": 13,
|
||||||
|
"color": "FFFFFF"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u5ba2\u670d\u652f\u6301",
|
||||||
|
"options": {
|
||||||
|
"x": 10.583,
|
||||||
|
"y": 0.056,
|
||||||
|
"w": 1.049,
|
||||||
|
"h": 0.278,
|
||||||
|
"fontSize": 13,
|
||||||
|
"color": "FFFFFF"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u8bd5\u7528",
|
||||||
|
"options": {
|
||||||
|
"x": 11.167,
|
||||||
|
"y": 0.056,
|
||||||
|
"w": 0.526,
|
||||||
|
"h": 0.278,
|
||||||
|
"fontSize": 13,
|
||||||
|
"color": "FFFFFF"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u8d39\u7528",
|
||||||
|
"options": {
|
||||||
|
"x": 11.667,
|
||||||
|
"y": 0.056,
|
||||||
|
"w": 0.526,
|
||||||
|
"h": 0.278,
|
||||||
|
"fontSize": 13,
|
||||||
|
"color": "FFFFFF"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "ellipse",
|
||||||
|
"options": {
|
||||||
|
"x": 12.917,
|
||||||
|
"y": 0.042,
|
||||||
|
"w": 0.25,
|
||||||
|
"h": 0.25,
|
||||||
|
"fill": {
|
||||||
|
"color": "FF4D4F"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "1",
|
||||||
|
"options": {
|
||||||
|
"x": 12.917,
|
||||||
|
"y": 0.042,
|
||||||
|
"w": 0.45,
|
||||||
|
"h": 0.25,
|
||||||
|
"fontSize": 8,
|
||||||
|
"color": "FFFFFF",
|
||||||
|
"bold": true,
|
||||||
|
"align": "center"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "oldi \u4e3b\u8d26\u53f7",
|
||||||
|
"options": {
|
||||||
|
"x": 13.431,
|
||||||
|
"y": 0.056,
|
||||||
|
"w": 1.049,
|
||||||
|
"h": 0.278,
|
||||||
|
"fontSize": 11,
|
||||||
|
"color": "FFFFFF",
|
||||||
|
"align": "right"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "rect",
|
||||||
|
"options": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0.667,
|
||||||
|
"w": 2.403,
|
||||||
|
"h": 13.306,
|
||||||
|
"fill": {
|
||||||
|
"color": "1A1A2E"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "ICP\u5907\u6848",
|
||||||
|
"options": {
|
||||||
|
"x": 0.333,
|
||||||
|
"y": 0.944,
|
||||||
|
"w": 1.201,
|
||||||
|
"h": 0.236,
|
||||||
|
"fontSize": 13,
|
||||||
|
"color": "FFFFFF",
|
||||||
|
"bold": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "rect",
|
||||||
|
"options": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 1.389,
|
||||||
|
"w": 2.403,
|
||||||
|
"h": 0.528,
|
||||||
|
"fill": {
|
||||||
|
"color": "2D2D44"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u6211\u7684\u5907\u6848",
|
||||||
|
"options": {
|
||||||
|
"x": 0.514,
|
||||||
|
"y": 1.528,
|
||||||
|
"w": 1.1,
|
||||||
|
"h": 0.236,
|
||||||
|
"fontSize": 13,
|
||||||
|
"color": "FFFFFF"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u5907\u6848\u77e5\u8bc6\u5e93",
|
||||||
|
"options": {
|
||||||
|
"x": 0.514,
|
||||||
|
"y": 2.056,
|
||||||
|
"w": 1.474,
|
||||||
|
"h": 0.236,
|
||||||
|
"fontSize": 13,
|
||||||
|
"color": "999999"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u5907\u6848\u67e5\u8be2",
|
||||||
|
"options": {
|
||||||
|
"x": 0.514,
|
||||||
|
"y": 2.583,
|
||||||
|
"w": 1.249,
|
||||||
|
"h": 0.236,
|
||||||
|
"fontSize": 13,
|
||||||
|
"color": "999999"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u5907\u6848\u8ba2\u5355",
|
||||||
|
"options": {
|
||||||
|
"x": 0.514,
|
||||||
|
"y": 3.111,
|
||||||
|
"w": 1.249,
|
||||||
|
"h": 0.236,
|
||||||
|
"fontSize": 13,
|
||||||
|
"color": "999999"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u5907\u6848\u6388\u6743\u7801",
|
||||||
|
"options": {
|
||||||
|
"x": 0.514,
|
||||||
|
"y": 3.639,
|
||||||
|
"w": 1.474,
|
||||||
|
"h": 0.236,
|
||||||
|
"fontSize": 13,
|
||||||
|
"color": "999999"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u5907\u6848\u8bb0\u5f55",
|
||||||
|
"options": {
|
||||||
|
"x": 0.514,
|
||||||
|
"y": 4.167,
|
||||||
|
"w": 1.249,
|
||||||
|
"h": 0.236,
|
||||||
|
"fontSize": 13,
|
||||||
|
"color": "999999"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u6536\u8d77",
|
||||||
|
"options": {
|
||||||
|
"x": 0.514,
|
||||||
|
"y": 13.583,
|
||||||
|
"w": 0.526,
|
||||||
|
"h": 0.236,
|
||||||
|
"fontSize": 13,
|
||||||
|
"color": "8E95A1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "WorkBuddy \u4f60\u7684\u529e\u516c\u597d\u642d\u5b50\uff0c\u4e00\u4eba\u6307\u6325\uff0cAI\u4e13\u5bb6\u56e2\u6267\u884c\uff0c\u6587\u6863/\u8bbe\u8ba1/\u5f00\u53d1\u4e00\u53e5\u8bdd\u4ea4\u4ed8\uff0c\u9650\u65f6\u4eab\u53cc\u500d Credits \u67e5\u770b\u8be6\u60c5 \u203a",
|
||||||
|
"options": {
|
||||||
|
"x": 3.028,
|
||||||
|
"y": 0.986,
|
||||||
|
"w": 18.751,
|
||||||
|
"h": 0.208,
|
||||||
|
"fontSize": 10,
|
||||||
|
"color": "333333"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u6211\u7684\u5907\u6848",
|
||||||
|
"options": {
|
||||||
|
"x": 1.75,
|
||||||
|
"y": 1.625,
|
||||||
|
"w": 1.201,
|
||||||
|
"h": 0.278,
|
||||||
|
"fontSize": 14,
|
||||||
|
"color": "333333",
|
||||||
|
"bold": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u63a5\u5165\u5907\u6848 - \u5ba1\u6838\u4e2d",
|
||||||
|
"options": {
|
||||||
|
"x": 2.556,
|
||||||
|
"y": 2.569,
|
||||||
|
"w": 3.499,
|
||||||
|
"h": 0.208,
|
||||||
|
"fontSize": 13,
|
||||||
|
"color": "333333",
|
||||||
|
"bold": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u5907\u6848\u8ba2\u5355\u53f7 30178470045009931 \u8ba2\u5355\u7c7b\u578b \u63a5\u5165\u5907\u6848 \u63d0\u4ea4\u5ba1\u6838\u65f6\u95f4 2026-07-23 06:20:29",
|
||||||
|
"options": {
|
||||||
|
"x": 2.556,
|
||||||
|
"y": 2.986,
|
||||||
|
"w": 17.5,
|
||||||
|
"h": 0.208,
|
||||||
|
"fontSize": 11,
|
||||||
|
"color": "666666"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "ellipse",
|
||||||
|
"options": {
|
||||||
|
"x": 4.306,
|
||||||
|
"y": 3.611,
|
||||||
|
"w": 0.333,
|
||||||
|
"h": 0.333,
|
||||||
|
"fill": {
|
||||||
|
"color": "1677FF"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u2713",
|
||||||
|
"options": {
|
||||||
|
"x": 4.306,
|
||||||
|
"y": 3.611,
|
||||||
|
"w": 0.599,
|
||||||
|
"h": 0.333,
|
||||||
|
"fontSize": 11,
|
||||||
|
"color": "FFFFFF",
|
||||||
|
"bold": true,
|
||||||
|
"align": "center"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u63d0\u4ea4\u521d\u5ba1",
|
||||||
|
"options": {
|
||||||
|
"x": 4.722,
|
||||||
|
"y": 3.611,
|
||||||
|
"w": 1.049,
|
||||||
|
"h": 0.208,
|
||||||
|
"fontSize": 11,
|
||||||
|
"color": "333333"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u5df2\u63d0\u4ea4",
|
||||||
|
"options": {
|
||||||
|
"x": 4.722,
|
||||||
|
"y": 3.819,
|
||||||
|
"w": 1.049,
|
||||||
|
"h": 0.167,
|
||||||
|
"fontSize": 10,
|
||||||
|
"color": "666666"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "ellipse",
|
||||||
|
"options": {
|
||||||
|
"x": 6.667,
|
||||||
|
"y": 3.611,
|
||||||
|
"w": 0.333,
|
||||||
|
"h": 0.333,
|
||||||
|
"fill": {
|
||||||
|
"color": "1677FF"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u2713",
|
||||||
|
"options": {
|
||||||
|
"x": 6.667,
|
||||||
|
"y": 3.611,
|
||||||
|
"w": 0.599,
|
||||||
|
"h": 0.333,
|
||||||
|
"fontSize": 11,
|
||||||
|
"color": "FFFFFF",
|
||||||
|
"bold": true,
|
||||||
|
"align": "center"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u817e\u8baf\u4e91\u5ba1\u6838",
|
||||||
|
"options": {
|
||||||
|
"x": 7.083,
|
||||||
|
"y": 3.611,
|
||||||
|
"w": 1.249,
|
||||||
|
"h": 0.208,
|
||||||
|
"fontSize": 11,
|
||||||
|
"color": "333333"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u5df2\u901a\u8fc7",
|
||||||
|
"options": {
|
||||||
|
"x": 7.083,
|
||||||
|
"y": 3.819,
|
||||||
|
"w": 1.249,
|
||||||
|
"h": 0.167,
|
||||||
|
"fontSize": 10,
|
||||||
|
"color": "666666"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "ellipse",
|
||||||
|
"options": {
|
||||||
|
"x": 9.028,
|
||||||
|
"y": 3.611,
|
||||||
|
"w": 0.333,
|
||||||
|
"h": 0.333,
|
||||||
|
"fill": {
|
||||||
|
"color": "1677FF"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u2713",
|
||||||
|
"options": {
|
||||||
|
"x": 9.028,
|
||||||
|
"y": 3.611,
|
||||||
|
"w": 0.599,
|
||||||
|
"h": 0.333,
|
||||||
|
"fontSize": 11,
|
||||||
|
"color": "FFFFFF",
|
||||||
|
"bold": true,
|
||||||
|
"align": "center"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u5f85\u63d0\u4ea4\u7ba1\u5c40",
|
||||||
|
"options": {
|
||||||
|
"x": 9.444,
|
||||||
|
"y": 3.611,
|
||||||
|
"w": 1.249,
|
||||||
|
"h": 0.208,
|
||||||
|
"fontSize": 11,
|
||||||
|
"color": "333333"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u5df2\u63d0\u4ea4",
|
||||||
|
"options": {
|
||||||
|
"x": 9.444,
|
||||||
|
"y": 3.819,
|
||||||
|
"w": 1.249,
|
||||||
|
"h": 0.167,
|
||||||
|
"fontSize": 10,
|
||||||
|
"color": "666666"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "ellipse",
|
||||||
|
"options": {
|
||||||
|
"x": 11.389,
|
||||||
|
"y": 3.611,
|
||||||
|
"w": 0.333,
|
||||||
|
"h": 0.333,
|
||||||
|
"fill": {
|
||||||
|
"color": "1677FF"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u2713",
|
||||||
|
"options": {
|
||||||
|
"x": 11.389,
|
||||||
|
"y": 3.611,
|
||||||
|
"w": 0.599,
|
||||||
|
"h": 0.333,
|
||||||
|
"fontSize": 11,
|
||||||
|
"color": "FFFFFF",
|
||||||
|
"bold": true,
|
||||||
|
"align": "center"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u5de5\u4fe1\u90e8\u77ed\u4fe1\u6838\u9a8c",
|
||||||
|
"options": {
|
||||||
|
"x": 11.806,
|
||||||
|
"y": 3.611,
|
||||||
|
"w": 1.499,
|
||||||
|
"h": 0.208,
|
||||||
|
"fontSize": 11,
|
||||||
|
"color": "333333"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u5df2\u6838\u9a8c",
|
||||||
|
"options": {
|
||||||
|
"x": 11.806,
|
||||||
|
"y": 3.819,
|
||||||
|
"w": 1.499,
|
||||||
|
"h": 0.167,
|
||||||
|
"fontSize": 10,
|
||||||
|
"color": "666666"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "ellipse",
|
||||||
|
"options": {
|
||||||
|
"x": 13.75,
|
||||||
|
"y": 3.611,
|
||||||
|
"w": 0.333,
|
||||||
|
"h": 0.333,
|
||||||
|
"fill": {
|
||||||
|
"color": "FF9800"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "5",
|
||||||
|
"options": {
|
||||||
|
"x": 13.75,
|
||||||
|
"y": 3.611,
|
||||||
|
"w": 0.599,
|
||||||
|
"h": 0.333,
|
||||||
|
"fontSize": 11,
|
||||||
|
"color": "FFFFFF",
|
||||||
|
"bold": true,
|
||||||
|
"align": "center"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u7ba1\u5c40\u5ba1\u6838",
|
||||||
|
"options": {
|
||||||
|
"x": 14.167,
|
||||||
|
"y": 3.611,
|
||||||
|
"w": 1.049,
|
||||||
|
"h": 0.208,
|
||||||
|
"fontSize": 11,
|
||||||
|
"color": "333333"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u5ba1\u6838\u4e2d | \u8fdb\u5ea6\u54a8\u8be2 \u25be",
|
||||||
|
"options": {
|
||||||
|
"x": 14.167,
|
||||||
|
"y": 3.819,
|
||||||
|
"w": 1.35,
|
||||||
|
"h": 0.167,
|
||||||
|
"fontSize": 10,
|
||||||
|
"color": "1677FF"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u5ba1\u6838\u8bf4\u660e",
|
||||||
|
"options": {
|
||||||
|
"x": 2.556,
|
||||||
|
"y": 5.0,
|
||||||
|
"w": 1.249,
|
||||||
|
"h": 0.208,
|
||||||
|
"fontSize": 13,
|
||||||
|
"color": "333333",
|
||||||
|
"bold": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u6839\u636e\u8ba2\u5355\u91cf\u63a8\u7b97\uff0c\u7ba1\u5c40\u5ba1\u6838\u9884\u8ba18\u4e2a\u5de5\u4f5c\u65e5\u5de6\u53f3\u5b8c\u6210\u5ba1\u6838\uff0c\u6700\u957f\u4e0d\u8d85\u8fc720\u4e2a\u5de5\u4f5c\u65e5\uff0c\u5177\u4f53\u4ee5\u5b9e\u9645\u5ba1\u6838\u65f6\u95f4\u4e3a\u51c6\uff0c\u8bf7\u60a8\u8010\u5fc3\u7b49\u5f85",
|
||||||
|
"options": {
|
||||||
|
"x": 2.556,
|
||||||
|
"y": 5.417,
|
||||||
|
"w": 17.5,
|
||||||
|
"h": 0.25,
|
||||||
|
"fontSize": 10,
|
||||||
|
"color": "666666"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u7531\u4e8e\u7ba1\u5c40\u4e0b\u53d1\u5b58\u5728\u5ef6\u8fdf\uff0c\u5982\u6536\u5230\u5de5\u4fe1\u90e8\u201c\u9a73\u56de\u201d\u3001\u201c\u5df2\u53d8\u66f4\u201d\u6216\u201c\u6210\u529f\u201d\u7684\u77ed\u4fe1\u901a\u77e5\uff0c\u817e\u8baf\u4e91\u4f1a\u57286-24\u5c0f\u65f6\u5185\u6536\u5230\u53cd\u9988\uff08\u9ad8\u5cf0\u671f\u53ef\u80fd\u66f4\u957f\uff09\uff0c\u5e76\u66f4\u65b0\u72b6\u6001\uff0c\u6b64\u8fc7\u7a0b\u65e0\u6cd5\u52a0\u901f\uff0c\u8bf7\u7a0d\u540e\u518d\u67e5\u8be2\uff0c\u611f\u8c22\u60a8\u7684\u7406\u89e3\u3002",
|
||||||
|
"options": {
|
||||||
|
"x": 2.556,
|
||||||
|
"y": 5.833,
|
||||||
|
"w": 17.5,
|
||||||
|
"h": 0.25,
|
||||||
|
"fontSize": 10,
|
||||||
|
"color": "666666"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u5f53\u524d\u8ba2\u5355\u6b63\u5728\u901a\u4fe1\u7ba1\u7406\u5c40\u5ba1\u6838\u4e2d\u3002\u5ba1\u6838\u671f\u95f4\u8ba2\u5355\u5c06\u88ab\u9501\u5b9a\uff0c\u4e0d\u652f\u6301\u64a4\u56de\u6216\u4fee\u6539\u5907\u6848\u4fe1\u606f\uff0c\u8bf7\u7b49\u5f85\u5ba1\u6838\u5b8c\u6210\u540e\u518d\u8fdb\u884c\u540e\u7eed\u64cd\u4f5c\u3002",
|
||||||
|
"options": {
|
||||||
|
"x": 2.556,
|
||||||
|
"y": 6.25,
|
||||||
|
"w": 17.5,
|
||||||
|
"h": 0.25,
|
||||||
|
"fontSize": 10,
|
||||||
|
"color": "666666"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u7ba1\u5c40\u65e0\u6cd5\u50ac\u5ba1",
|
||||||
|
"options": {
|
||||||
|
"x": 2.556,
|
||||||
|
"y": 6.944,
|
||||||
|
"w": 1.499,
|
||||||
|
"h": 0.333,
|
||||||
|
"fontSize": 11,
|
||||||
|
"color": "999999"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u67e5\u770b\u5907\u6848\u4fe1\u606f",
|
||||||
|
"options": {
|
||||||
|
"x": 3.889,
|
||||||
|
"y": 6.944,
|
||||||
|
"w": 1.499,
|
||||||
|
"h": 0.333,
|
||||||
|
"fontSize": 11,
|
||||||
|
"color": "1677FF"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u4e3b\u4f53\u4fe1\u606f",
|
||||||
|
"options": {
|
||||||
|
"x": 2.556,
|
||||||
|
"y": 7.778,
|
||||||
|
"w": 1.249,
|
||||||
|
"h": 0.208,
|
||||||
|
"fontSize": 13,
|
||||||
|
"color": "333333",
|
||||||
|
"bold": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u53d8\u66f4\u5907\u6848",
|
||||||
|
"options": {
|
||||||
|
"x": 8.056,
|
||||||
|
"y": 7.778,
|
||||||
|
"w": 1.001,
|
||||||
|
"h": 0.167,
|
||||||
|
"fontSize": 10,
|
||||||
|
"color": "1677FF"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u6ce8\u9500\u4e3b\u4f53",
|
||||||
|
"options": {
|
||||||
|
"x": 8.889,
|
||||||
|
"y": 7.778,
|
||||||
|
"w": 1.001,
|
||||||
|
"h": 0.167,
|
||||||
|
"fontSize": 10,
|
||||||
|
"color": "1677FF"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u8fc1\u79fb\u5907\u6848\u8d26\u53f7",
|
||||||
|
"options": {
|
||||||
|
"x": 9.722,
|
||||||
|
"y": 7.778,
|
||||||
|
"w": 1.249,
|
||||||
|
"h": 0.167,
|
||||||
|
"fontSize": 10,
|
||||||
|
"color": "1677FF"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u5bfc\u51fa\u57fa\u7840\u4fe1\u606f",
|
||||||
|
"options": {
|
||||||
|
"x": 10.694,
|
||||||
|
"y": 7.778,
|
||||||
|
"w": 1.249,
|
||||||
|
"h": 0.167,
|
||||||
|
"fontSize": 10,
|
||||||
|
"color": "1677FF"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u6682\u65e0\u5907\u6848",
|
||||||
|
"options": {
|
||||||
|
"x": 6.25,
|
||||||
|
"y": 8.611,
|
||||||
|
"w": 1.001,
|
||||||
|
"h": 0.167,
|
||||||
|
"fontSize": 11,
|
||||||
|
"color": "999999"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u4e92\u8054\u7f51\u4fe1\u606f\u670d\u52a1",
|
||||||
|
"options": {
|
||||||
|
"x": 2.556,
|
||||||
|
"y": 9.306,
|
||||||
|
"w": 2.0,
|
||||||
|
"h": 0.208,
|
||||||
|
"fontSize": 13,
|
||||||
|
"color": "333333",
|
||||||
|
"bold": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u65b0\u589e/\u63a5\u5165\u670d\u52a1",
|
||||||
|
"options": {
|
||||||
|
"x": 7.361,
|
||||||
|
"y": 9.306,
|
||||||
|
"w": 1.499,
|
||||||
|
"h": 0.333,
|
||||||
|
"fontSize": 11,
|
||||||
|
"color": "1677FF"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u6ce8\u9500\u670d\u52a1",
|
||||||
|
"options": {
|
||||||
|
"x": 8.472,
|
||||||
|
"y": 9.306,
|
||||||
|
"w": 1.001,
|
||||||
|
"h": 0.333,
|
||||||
|
"fontSize": 11,
|
||||||
|
"color": "999999"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u53d6\u6d88\u817e\u8baf\u4e91\u63a5\u5165",
|
||||||
|
"options": {
|
||||||
|
"x": 9.306,
|
||||||
|
"y": 9.306,
|
||||||
|
"w": 1.75,
|
||||||
|
"h": 0.333,
|
||||||
|
"fontSize": 11,
|
||||||
|
"color": "999999"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u901a\u77e5\u516c\u544a",
|
||||||
|
"options": {
|
||||||
|
"x": 18.056,
|
||||||
|
"y": 1.042,
|
||||||
|
"w": 1.499,
|
||||||
|
"h": 0.208,
|
||||||
|
"fontSize": 13,
|
||||||
|
"color": "333333",
|
||||||
|
"bold": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u5173\u4e8e\u5b58\u91cfAPP\u5907\u6848\u901a\u77e5",
|
||||||
|
"options": {
|
||||||
|
"x": 18.056,
|
||||||
|
"y": 1.458,
|
||||||
|
"w": 3.749,
|
||||||
|
"h": 0.167,
|
||||||
|
"fontSize": 10,
|
||||||
|
"color": "666666"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u817e\u8baf\u4e91\u4e13\u9879\u6838\u67e5\u901a\u77e5",
|
||||||
|
"options": {
|
||||||
|
"x": 18.056,
|
||||||
|
"y": 1.806,
|
||||||
|
"w": 3.749,
|
||||||
|
"h": 0.167,
|
||||||
|
"fontSize": 10,
|
||||||
|
"color": "666666"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u5173\u4e8e\u9884\u9632\u5907\u6848\u8fc7\u7a0b\u4e2d\u906d\u9047\u5feb\u9012\u8bc8\u9a97\u7684\u516c\u544a",
|
||||||
|
"options": {
|
||||||
|
"x": 18.056,
|
||||||
|
"y": 2.153,
|
||||||
|
"w": 3.749,
|
||||||
|
"h": 0.208,
|
||||||
|
"fontSize": 10,
|
||||||
|
"color": "666666"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u8d26\u53f7\u76f8\u5173\u95ee\u9898",
|
||||||
|
"options": {
|
||||||
|
"x": 18.056,
|
||||||
|
"y": 2.778,
|
||||||
|
"w": 1.75,
|
||||||
|
"h": 0.208,
|
||||||
|
"fontSize": 13,
|
||||||
|
"color": "333333",
|
||||||
|
"bold": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u4e00\u4e2a\u8d26\u53f7\u53ef\u4ee5\u7ed9\u591a\u4e2a\u4e0d\u540c\u4e3b\u4f53\u5907\u6848\u5417\uff1f",
|
||||||
|
"options": {
|
||||||
|
"x": 18.056,
|
||||||
|
"y": 3.194,
|
||||||
|
"w": 3.749,
|
||||||
|
"h": 0.167,
|
||||||
|
"fontSize": 10,
|
||||||
|
"color": "666666"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u4e91\u8d44\u6e90\u76f8\u5173\u95ee\u9898",
|
||||||
|
"options": {
|
||||||
|
"x": 18.056,
|
||||||
|
"y": 3.819,
|
||||||
|
"w": 1.75,
|
||||||
|
"h": 0.208,
|
||||||
|
"fontSize": 13,
|
||||||
|
"color": "333333",
|
||||||
|
"bold": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u5982\u4f55\u83b7\u53d6\u548c\u4f7f\u7528\u201c\u5907\u6848\u6388\u6743\u7801\u201d\uff1f",
|
||||||
|
"options": {
|
||||||
|
"x": 18.056,
|
||||||
|
"y": 4.236,
|
||||||
|
"w": 3.749,
|
||||||
|
"h": 0.167,
|
||||||
|
"fontSize": 10,
|
||||||
|
"color": "666666"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u4e00\u53f0\u670d\u52a1\u5668\u53ef\u4ee5\u5907\u6848\u51e0\u4e2a\u7f51\u7ad9\u6216APP\uff1f",
|
||||||
|
"options": {
|
||||||
|
"x": 18.056,
|
||||||
|
"y": 4.514,
|
||||||
|
"w": 3.749,
|
||||||
|
"h": 0.167,
|
||||||
|
"fontSize": 10,
|
||||||
|
"color": "666666"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u57df\u540d\u76f8\u5173\u95ee\u9898",
|
||||||
|
"options": {
|
||||||
|
"x": 18.056,
|
||||||
|
"y": 4.931,
|
||||||
|
"w": 1.499,
|
||||||
|
"h": 0.208,
|
||||||
|
"fontSize": 13,
|
||||||
|
"color": "333333",
|
||||||
|
"bold": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u5907\u6848\u524d\u57df\u540d\u5fc5\u987b\u5b9e\u540d\u8ba4\u8bc1\u5417\uff1f",
|
||||||
|
"options": {
|
||||||
|
"x": 18.056,
|
||||||
|
"y": 5.347,
|
||||||
|
"w": 3.749,
|
||||||
|
"h": 0.167,
|
||||||
|
"fontSize": 10,
|
||||||
|
"color": "666666"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u515a\u653f\u673a\u5173\u6216\u4e8b\u4e1a\u5355\u4f4d\u6027\u8d28\u5907\u6848\uff08\u7f51\u7ad9\u6216APP\uff09\u57df\u540d\u6709\u4ec0\u4e48\u8981\u6c42\uff1f",
|
||||||
|
"options": {
|
||||||
|
"x": 18.056,
|
||||||
|
"y": 5.625,
|
||||||
|
"w": 3.749,
|
||||||
|
"h": 0.25,
|
||||||
|
"fontSize": 10,
|
||||||
|
"color": "666666"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u65b0\u589e\u5907\u6848\u76f8\u5173\u95ee\u9898",
|
||||||
|
"options": {
|
||||||
|
"x": 18.056,
|
||||||
|
"y": 6.181,
|
||||||
|
"w": 2.0,
|
||||||
|
"h": 0.208,
|
||||||
|
"fontSize": 13,
|
||||||
|
"color": "333333",
|
||||||
|
"bold": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u4e00\u4e2a\u8ba2\u5355\u53ef\u4ee5\u65b0\u589e\u51e0\u4e2a\u7f51\u7ad9\u4e0eAPP\uff1f",
|
||||||
|
"options": {
|
||||||
|
"x": 18.056,
|
||||||
|
"y": 6.597,
|
||||||
|
"w": 3.749,
|
||||||
|
"h": 0.167,
|
||||||
|
"fontSize": 10,
|
||||||
|
"color": "666666"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u65b0\u589e\u7f51\u7ad9\u6216 APP \u8fc7\u7a0b\u4e2d\uff0c\u7f51\u7ad9\u6216 APP \u662f\u5426\u80fd\u591f\u8bbf\u95ee\uff1f",
|
||||||
|
"options": {
|
||||||
|
"x": 18.056,
|
||||||
|
"y": 6.875,
|
||||||
|
"w": 3.749,
|
||||||
|
"h": 0.208,
|
||||||
|
"fontSize": 10,
|
||||||
|
"color": "666666"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,367 @@
|
|||||||
|
{
|
||||||
|
"presentation": {
|
||||||
|
"layout": "LAYOUT_16x9",
|
||||||
|
"title": "金融数据看板 - Advanced UI"
|
||||||
|
},
|
||||||
|
"slides": [
|
||||||
|
{
|
||||||
|
"background": {
|
||||||
|
"color": "0D0D2B"
|
||||||
|
},
|
||||||
|
"objects": [
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "rect",
|
||||||
|
"options": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"w": 10,
|
||||||
|
"h": 5.625,
|
||||||
|
"fill": { "color": "0D0D2B" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "rect",
|
||||||
|
"options": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"w": 10,
|
||||||
|
"h": 5.625,
|
||||||
|
"fill": { "color": "1A0A3E", "transparency": 50 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "rect",
|
||||||
|
"options": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"w": 10,
|
||||||
|
"h": 0.65,
|
||||||
|
"fill": { "color": "1A1A3A" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "金融数据看板",
|
||||||
|
"options": {
|
||||||
|
"x": 0.4,
|
||||||
|
"y": 0.12,
|
||||||
|
"w": 5,
|
||||||
|
"h": 0.4,
|
||||||
|
"fontSize": 20,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "FFFFFF",
|
||||||
|
"bold": true,
|
||||||
|
"charSpacing": 3,
|
||||||
|
"glow": { "color": "6669E3", "opacity": 0.6, "size": 4 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "2026-07-23 14:30:00",
|
||||||
|
"options": {
|
||||||
|
"x": 7.2,
|
||||||
|
"y": 0.15,
|
||||||
|
"w": 2.5,
|
||||||
|
"h": 0.35,
|
||||||
|
"fontSize": 11,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "FFFFFF",
|
||||||
|
"transparency": 40,
|
||||||
|
"align": "right"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "roundRect",
|
||||||
|
"options": {
|
||||||
|
"x": 0.4,
|
||||||
|
"y": 0.9,
|
||||||
|
"w": 2.1,
|
||||||
|
"h": 1.3,
|
||||||
|
"fill": { "color": "1E1E3A" },
|
||||||
|
"rectRadius": 0.15,
|
||||||
|
"shadow": { "type": "outer", "blur": 8, "offset": 3, "color": "000000", "opacity": 0.4 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "¥12.8亿",
|
||||||
|
"options": {
|
||||||
|
"x": 0.4,
|
||||||
|
"y": 1.0,
|
||||||
|
"w": 2.1,
|
||||||
|
"h": 0.5,
|
||||||
|
"fontSize": 26,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "FFFFFF",
|
||||||
|
"bold": true,
|
||||||
|
"align": "center"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "总资产",
|
||||||
|
"options": {
|
||||||
|
"x": 0.4,
|
||||||
|
"y": 1.65,
|
||||||
|
"w": 2.1,
|
||||||
|
"h": 0.3,
|
||||||
|
"fontSize": 12,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "AAAAAA",
|
||||||
|
"align": "center"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "roundRect",
|
||||||
|
"options": {
|
||||||
|
"x": 2.75,
|
||||||
|
"y": 0.9,
|
||||||
|
"w": 2.1,
|
||||||
|
"h": 1.3,
|
||||||
|
"fill": { "color": "1E1E3A" },
|
||||||
|
"rectRadius": 0.15,
|
||||||
|
"shadow": { "type": "outer", "blur": 8, "offset": 3, "color": "000000", "opacity": 0.4 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "¥3,240万",
|
||||||
|
"options": {
|
||||||
|
"x": 2.75,
|
||||||
|
"y": 1.0,
|
||||||
|
"w": 2.1,
|
||||||
|
"h": 0.5,
|
||||||
|
"fontSize": 26,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "3BC798",
|
||||||
|
"bold": true,
|
||||||
|
"align": "center"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "今日交易额",
|
||||||
|
"options": {
|
||||||
|
"x": 2.75,
|
||||||
|
"y": 1.65,
|
||||||
|
"w": 2.1,
|
||||||
|
"h": 0.3,
|
||||||
|
"fontSize": 12,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "AAAAAA",
|
||||||
|
"align": "center"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "roundRect",
|
||||||
|
"options": {
|
||||||
|
"x": 5.1,
|
||||||
|
"y": 0.9,
|
||||||
|
"w": 2.1,
|
||||||
|
"h": 1.3,
|
||||||
|
"fill": { "color": "1E1E3A" },
|
||||||
|
"rectRadius": 0.15,
|
||||||
|
"shadow": { "type": "outer", "blur": 8, "offset": 3, "color": "000000", "opacity": 0.4 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "-2.34%",
|
||||||
|
"options": {
|
||||||
|
"x": 5.1,
|
||||||
|
"y": 1.0,
|
||||||
|
"w": 2.1,
|
||||||
|
"h": 0.5,
|
||||||
|
"fontSize": 26,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "FF6752",
|
||||||
|
"bold": true,
|
||||||
|
"align": "center"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "市场指数",
|
||||||
|
"options": {
|
||||||
|
"x": 5.1,
|
||||||
|
"y": 1.65,
|
||||||
|
"w": 2.1,
|
||||||
|
"h": 0.3,
|
||||||
|
"fontSize": 12,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "AAAAAA",
|
||||||
|
"align": "center"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "roundRect",
|
||||||
|
"options": {
|
||||||
|
"x": 7.45,
|
||||||
|
"y": 0.9,
|
||||||
|
"w": 2.1,
|
||||||
|
"h": 1.3,
|
||||||
|
"fill": { "color": "1E1E3A" },
|
||||||
|
"rectRadius": 0.15,
|
||||||
|
"shadow": { "type": "outer", "blur": 8, "offset": 3, "color": "000000", "opacity": 0.4 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "roundRect",
|
||||||
|
"options": {
|
||||||
|
"x": 7.45,
|
||||||
|
"y": 0.9,
|
||||||
|
"w": 2.1,
|
||||||
|
"h": 1.3,
|
||||||
|
"fill": { "color": "6669E3", "transparency": 80 },
|
||||||
|
"rectRadius": 0.15
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "+8.56%",
|
||||||
|
"options": {
|
||||||
|
"x": 7.45,
|
||||||
|
"y": 1.0,
|
||||||
|
"w": 2.1,
|
||||||
|
"h": 0.5,
|
||||||
|
"fontSize": 26,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "3BC798",
|
||||||
|
"bold": true,
|
||||||
|
"align": "center"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "年化收益率",
|
||||||
|
"options": {
|
||||||
|
"x": 7.45,
|
||||||
|
"y": 1.65,
|
||||||
|
"w": 2.1,
|
||||||
|
"h": 0.3,
|
||||||
|
"fontSize": 12,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "AAAAAA",
|
||||||
|
"align": "center"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "chart",
|
||||||
|
"chartType": "line",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"name": "上证指数",
|
||||||
|
"labels": ["09:30", "10:30", "11:30", "13:30", "14:30", "15:00"],
|
||||||
|
"values": [3250, 3280, 3265, 3295, 3310, 3305]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "沪深300",
|
||||||
|
"labels": ["09:30", "10:30", "11:30", "13:30", "14:30", "15:00"],
|
||||||
|
"values": [3980, 4020, 4005, 4045, 4070, 4060]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": {
|
||||||
|
"x": 0.4,
|
||||||
|
"y": 2.5,
|
||||||
|
"w": 9.2,
|
||||||
|
"h": 1.7,
|
||||||
|
"chartColors": ["6669E3", "3BC798"],
|
||||||
|
"lineSize": 2.5,
|
||||||
|
"showLegend": true,
|
||||||
|
"showTitle": true,
|
||||||
|
"title": "市场走势",
|
||||||
|
"titleColor": "FFFFFF",
|
||||||
|
"titleFontSize": 12,
|
||||||
|
"legendColor": "AAAAAA",
|
||||||
|
"legendFontSize": 9,
|
||||||
|
"legendPos": "b",
|
||||||
|
"catAxisLabelColor": "888888",
|
||||||
|
"catAxisLabelFontSize": 8,
|
||||||
|
"valAxisLabelColor": "888888",
|
||||||
|
"valAxisLabelFontSize": 8,
|
||||||
|
"catGridLine": { "color": "2A2A4A", "size": 0.5, "style": "dash" },
|
||||||
|
"valGridLine": { "color": "2A2A4A", "size": 0.5, "style": "solid" },
|
||||||
|
"lineDataSymbol": "circle",
|
||||||
|
"lineDataSymbolSize": 4,
|
||||||
|
"lineDataSymbolLineColor": "FFFFFF",
|
||||||
|
"plotArea": { "fill": { "color": "16162E" } }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "table",
|
||||||
|
"rows": [
|
||||||
|
[
|
||||||
|
{ "text": "股票代码", "options": { "bold": true, "fill": { "color": "262650" }, "color": "FFFFFF", "fontSize": 10, "align": "center" } },
|
||||||
|
{ "text": "名称", "options": { "bold": true, "fill": { "color": "262650" }, "color": "FFFFFF", "fontSize": 10, "align": "center" } },
|
||||||
|
{ "text": "最新价", "options": { "bold": true, "fill": { "color": "262650" }, "color": "FFFFFF", "fontSize": 10, "align": "center" } },
|
||||||
|
{ "text": "涨跌幅", "options": { "bold": true, "fill": { "color": "262650" }, "color": "FFFFFF", "fontSize": 10, "align": "center" } }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ "text": "600001", "options": { "fill": { "color": "1E1E3A" }, "color": "CCCCCC", "fontSize": 9, "align": "center" } },
|
||||||
|
{ "text": "平安银行", "options": { "fill": { "color": "1E1E3A" }, "color": "CCCCCC", "fontSize": 9, "align": "center" } },
|
||||||
|
{ "text": "14.58", "options": { "fill": { "color": "1E1E3A" }, "color": "3BC798", "fontSize": 9, "align": "center" } },
|
||||||
|
{ "text": "+2.15%", "options": { "fill": { "color": "1E1E3A" }, "color": "3BC798", "fontSize": 9, "align": "center" } }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ "text": "600002", "options": { "fill": { "color": "252540" }, "color": "CCCCCC", "fontSize": 9, "align": "center" } },
|
||||||
|
{ "text": "科技创新", "options": { "fill": { "color": "252540" }, "color": "CCCCCC", "fontSize": 9, "align": "center" } },
|
||||||
|
{ "text": "28.90", "options": { "fill": { "color": "252540" }, "color": "FF6752", "fontSize": 9, "align": "center" } },
|
||||||
|
{ "text": "-1.34%", "options": { "fill": { "color": "252540" }, "color": "FF6752", "fontSize": 9, "align": "center" } }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ "text": "600003", "options": { "fill": { "color": "1E1E3A" }, "color": "CCCCCC", "fontSize": 9, "align": "center" } },
|
||||||
|
{ "text": "绿色能源", "options": { "fill": { "color": "1E1E3A" }, "color": "CCCCCC", "fontSize": 9, "align": "center" } },
|
||||||
|
{ "text": "45.20", "options": { "fill": { "color": "1E1E3A" }, "color": "3BC798", "fontSize": 9, "align": "center" } },
|
||||||
|
{ "text": "+3.67%", "options": { "fill": { "color": "1E1E3A" }, "color": "3BC798", "fontSize": 9, "align": "center" } }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ "text": "600004", "options": { "fill": { "color": "252540" }, "color": "CCCCCC", "fontSize": 9, "align": "center" } },
|
||||||
|
{ "text": "医药健康", "options": { "fill": { "color": "252540" }, "color": "CCCCCC", "fontSize": 9, "align": "center" } },
|
||||||
|
{ "text": "62.10", "options": { "fill": { "color": "252540" }, "color": "FF6752", "fontSize": 9, "align": "center" } },
|
||||||
|
{ "text": "-0.52%", "options": { "fill": { "color": "252540" }, "color": "FF6752", "fontSize": 9, "align": "center" } }
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"options": {
|
||||||
|
"x": 0.4,
|
||||||
|
"y": 4.4,
|
||||||
|
"w": 9.2,
|
||||||
|
"h": 1.0,
|
||||||
|
"colW": [1.5, 3.2, 2.0, 2.5],
|
||||||
|
"border": { "type": "solid", "color": "2A2A4A", "pt": 0.5 },
|
||||||
|
"align": "center"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "rect",
|
||||||
|
"options": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0.7,
|
||||||
|
"w": 0.06,
|
||||||
|
"h": 3.5,
|
||||||
|
"fill": { "color": "6669E3", "transparency": 60 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "ellipse",
|
||||||
|
"options": {
|
||||||
|
"x": 9.3,
|
||||||
|
"y": 4.9,
|
||||||
|
"w": 0.4,
|
||||||
|
"h": 0.4,
|
||||||
|
"fill": { "color": "6669E3", "transparency": 50 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,317 @@
|
|||||||
|
{
|
||||||
|
"presentation": {
|
||||||
|
"layout": "LAYOUT_16x9",
|
||||||
|
"title": "数据看板 - UI Dashboard"
|
||||||
|
},
|
||||||
|
"slides": [
|
||||||
|
{
|
||||||
|
"background": {
|
||||||
|
"color": "F5F5F7"
|
||||||
|
},
|
||||||
|
"objects": [
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "rect",
|
||||||
|
"options": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"w": 10,
|
||||||
|
"h": 0.7,
|
||||||
|
"fill": { "color": "1A1A2E" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "数据看板",
|
||||||
|
"options": {
|
||||||
|
"x": 0.4,
|
||||||
|
"y": 0.15,
|
||||||
|
"w": 4,
|
||||||
|
"h": 0.4,
|
||||||
|
"fontSize": 22,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "FFFFFF",
|
||||||
|
"bold": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "2026年7月",
|
||||||
|
"options": {
|
||||||
|
"x": 7.2,
|
||||||
|
"y": 0.15,
|
||||||
|
"w": 2.4,
|
||||||
|
"h": 0.4,
|
||||||
|
"fontSize": 13,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "AAAAAA",
|
||||||
|
"align": "right"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "rect",
|
||||||
|
"options": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0.7,
|
||||||
|
"w": 2.0,
|
||||||
|
"h": 4.925,
|
||||||
|
"fill": { "color": "F0F0F2" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "● 概览",
|
||||||
|
"options": {
|
||||||
|
"x": 0.3,
|
||||||
|
"y": 1.0,
|
||||||
|
"w": 1.5,
|
||||||
|
"h": 0.4,
|
||||||
|
"fontSize": 14,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "6669E3",
|
||||||
|
"bold": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "○ 分析",
|
||||||
|
"options": {
|
||||||
|
"x": 0.3,
|
||||||
|
"y": 1.5,
|
||||||
|
"w": 1.5,
|
||||||
|
"h": 0.4,
|
||||||
|
"fontSize": 14,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "666666"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "○ 设置",
|
||||||
|
"options": {
|
||||||
|
"x": 0.3,
|
||||||
|
"y": 2.0,
|
||||||
|
"w": 1.5,
|
||||||
|
"h": 0.4,
|
||||||
|
"fontSize": 14,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "666666"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "○ 帮助",
|
||||||
|
"options": {
|
||||||
|
"x": 0.3,
|
||||||
|
"y": 2.5,
|
||||||
|
"w": 1.5,
|
||||||
|
"h": 0.4,
|
||||||
|
"fontSize": 14,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "666666"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "roundRect",
|
||||||
|
"options": {
|
||||||
|
"x": 2.3,
|
||||||
|
"y": 0.9,
|
||||||
|
"w": 2.3,
|
||||||
|
"h": 1.3,
|
||||||
|
"fill": { "color": "FFFFFF" },
|
||||||
|
"rectRadius": 0.15,
|
||||||
|
"line": { "color": "E0E0E0", "width": 1 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "1,286",
|
||||||
|
"options": {
|
||||||
|
"x": 2.3,
|
||||||
|
"y": 1.0,
|
||||||
|
"w": 2.3,
|
||||||
|
"h": 0.4,
|
||||||
|
"fontSize": 28,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "1A1A2E",
|
||||||
|
"bold": true,
|
||||||
|
"align": "center"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "总用户",
|
||||||
|
"options": {
|
||||||
|
"x": 2.3,
|
||||||
|
"y": 1.6,
|
||||||
|
"w": 2.3,
|
||||||
|
"h": 0.3,
|
||||||
|
"fontSize": 13,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "666666",
|
||||||
|
"align": "center"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "roundRect",
|
||||||
|
"options": {
|
||||||
|
"x": 4.8,
|
||||||
|
"y": 0.9,
|
||||||
|
"w": 2.3,
|
||||||
|
"h": 1.3,
|
||||||
|
"fill": { "color": "FFFFFF" },
|
||||||
|
"rectRadius": 0.15,
|
||||||
|
"line": { "color": "E0E0E0", "width": 1 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "¥58.2万",
|
||||||
|
"options": {
|
||||||
|
"x": 4.8,
|
||||||
|
"y": 1.0,
|
||||||
|
"w": 2.3,
|
||||||
|
"h": 0.4,
|
||||||
|
"fontSize": 28,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "1A1A2E",
|
||||||
|
"bold": true,
|
||||||
|
"align": "center"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "营收",
|
||||||
|
"options": {
|
||||||
|
"x": 4.8,
|
||||||
|
"y": 1.6,
|
||||||
|
"w": 2.3,
|
||||||
|
"h": 0.3,
|
||||||
|
"fontSize": 13,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "666666",
|
||||||
|
"align": "center"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "roundRect",
|
||||||
|
"options": {
|
||||||
|
"x": 7.3,
|
||||||
|
"y": 0.9,
|
||||||
|
"w": 2.3,
|
||||||
|
"h": 1.3,
|
||||||
|
"fill": { "color": "FFFFFF" },
|
||||||
|
"rectRadius": 0.15,
|
||||||
|
"line": { "color": "E0E0E0", "width": 1 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "96.5%",
|
||||||
|
"options": {
|
||||||
|
"x": 7.3,
|
||||||
|
"y": 1.0,
|
||||||
|
"w": 2.3,
|
||||||
|
"h": 0.4,
|
||||||
|
"fontSize": 28,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "1A1A2E",
|
||||||
|
"bold": true,
|
||||||
|
"align": "center"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "满意度",
|
||||||
|
"options": {
|
||||||
|
"x": 7.3,
|
||||||
|
"y": 1.6,
|
||||||
|
"w": 2.3,
|
||||||
|
"h": 0.3,
|
||||||
|
"fontSize": 13,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "666666",
|
||||||
|
"align": "center"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "table",
|
||||||
|
"rows": [
|
||||||
|
[
|
||||||
|
{ "text": "项目", "options": { "bold": true, "fill": { "color": "6669E3" }, "color": "FFFFFF", "fontSize": 13 } },
|
||||||
|
{ "text": "进度", "options": { "bold": true, "fill": { "color": "6669E3" }, "color": "FFFFFF", "fontSize": 13 } },
|
||||||
|
{ "text": "负责人", "options": { "bold": true, "fill": { "color": "6669E3" }, "color": "FFFFFF", "fontSize": 13 } },
|
||||||
|
{ "text": "状态", "options": { "bold": true, "fill": { "color": "6669E3" }, "color": "FFFFFF", "fontSize": 13 } }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ "text": "首页改版", "options": { "fill": { "color": "D9E2F3" } } },
|
||||||
|
{ "text": "75%", "options": { "fill": { "color": "D9E2F3" } } },
|
||||||
|
{ "text": "张三", "options": { "fill": { "color": "D9E2F3" } } },
|
||||||
|
{ "text": "进行中", "options": { "fill": { "color": "D9E2F3" } } }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ "text": "用户系统", "options": { "fill": { "color": "FFFFFF" } } },
|
||||||
|
{ "text": "90%", "options": { "fill": { "color": "FFFFFF" } } },
|
||||||
|
{ "text": "李四", "options": { "fill": { "color": "FFFFFF" } } },
|
||||||
|
{ "text": "测试中", "options": { "fill": { "color": "FFFFFF" } } }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ "text": "数据迁移", "options": { "fill": { "color": "D9E2F3" } } },
|
||||||
|
{ "text": "60%", "options": { "fill": { "color": "D9E2F3" } } },
|
||||||
|
{ "text": "王五", "options": { "fill": { "color": "D9E2F3" } } },
|
||||||
|
{ "text": "进行中", "options": { "fill": { "color": "D9E2F3" } } }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ "text": "API优化", "options": { "fill": { "color": "FFFFFF" } } },
|
||||||
|
{ "text": "100%", "options": { "fill": { "color": "FFFFFF" } } },
|
||||||
|
{ "text": "赵六", "options": { "fill": { "color": "FFFFFF" } } },
|
||||||
|
{ "text": "已完成", "options": { "fill": { "color": "FFFFFF" } } }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ "text": "安全审计", "options": { "fill": { "color": "D9E2F3" } } },
|
||||||
|
{ "text": "45%", "options": { "fill": { "color": "D9E2F3" } } },
|
||||||
|
{ "text": "陈七", "options": { "fill": { "color": "D9E2F3" } } },
|
||||||
|
{ "text": "进行中", "options": { "fill": { "color": "D9E2F3" } } }
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"options": {
|
||||||
|
"x": 2.3,
|
||||||
|
"y": 2.5,
|
||||||
|
"w": 7.3,
|
||||||
|
"h": 2.0,
|
||||||
|
"colW": [2.3, 1.5, 1.5, 2.0],
|
||||||
|
"border": { "type": "solid", "color": "E0E0E0", "pt": 1 },
|
||||||
|
"fontSize": 12
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "chart",
|
||||||
|
"chartType": "bar",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"name": "营收",
|
||||||
|
"labels": ["1月", "2月", "3月"],
|
||||||
|
"values": [42, 58, 73]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": {
|
||||||
|
"x": 2.3,
|
||||||
|
"y": 4.7,
|
||||||
|
"w": 7.3,
|
||||||
|
"h": 0.9,
|
||||||
|
"barDir": "col",
|
||||||
|
"chartColors": ["6669E3"],
|
||||||
|
"showTitle": false,
|
||||||
|
"showLegend": false,
|
||||||
|
"catAxisLabelColor": "666666",
|
||||||
|
"valAxisLabelColor": "666666"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,845 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"$id": "https://example.com/pptx-presentation.schema.json",
|
||||||
|
"title": "PPTX Presentation Schema",
|
||||||
|
"description": "JSON Schema for PPTX presentation intermediate representation covering all pptxgenjs parameters",
|
||||||
|
"type": "object",
|
||||||
|
"definitions": {
|
||||||
|
"coord": {
|
||||||
|
"oneOf": [
|
||||||
|
{ "type": "number" },
|
||||||
|
{ "type": "string", "pattern": "^\\d+(\\.\\d+)?%$" }
|
||||||
|
],
|
||||||
|
"description": "Coordinate value in inches (number) or percentage (e.g. '50%')"
|
||||||
|
},
|
||||||
|
"hexColor": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^[0-9A-Fa-f]{6}$",
|
||||||
|
"description": "6-digit hex color without # prefix"
|
||||||
|
},
|
||||||
|
"themeColor": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["tx1", "tx2", "bg1", "bg2", "accent1", "accent2", "accent3", "accent4", "accent5", "accent6"],
|
||||||
|
"description": "PowerPoint theme/scheme color"
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"oneOf": [
|
||||||
|
{ "$ref": "#/definitions/hexColor" },
|
||||||
|
{ "$ref": "#/definitions/themeColor" }
|
||||||
|
],
|
||||||
|
"description": "Color value: hex string or theme color"
|
||||||
|
},
|
||||||
|
"margin": {
|
||||||
|
"oneOf": [
|
||||||
|
{ "type": "number" },
|
||||||
|
{ "type": "array", "items": { "type": "number" }, "minItems": 4, "maxItems": 4 }
|
||||||
|
],
|
||||||
|
"description": "Margin in points: single value or [top, right, bottom, left]"
|
||||||
|
},
|
||||||
|
"position": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"x": { "$ref": "#/definitions/coord" },
|
||||||
|
"y": { "$ref": "#/definitions/coord" },
|
||||||
|
"w": { "$ref": "#/definitions/coord" },
|
||||||
|
"h": { "$ref": "#/definitions/coord" }
|
||||||
|
},
|
||||||
|
"description": "Position and size properties"
|
||||||
|
},
|
||||||
|
"dataOrPath": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"path": { "type": "string", "description": "URL or local file path" },
|
||||||
|
"data": { "type": "string", "description": "Base64-encoded data" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"borderProps": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": { "type": "string", "enum": ["none", "dash", "solid"] },
|
||||||
|
"color": { "$ref": "#/definitions/hexColor" },
|
||||||
|
"pt": { "type": "number", "description": "Border width in points" }
|
||||||
|
},
|
||||||
|
"description": "Border properties"
|
||||||
|
},
|
||||||
|
"shapeFill": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"color": { "$ref": "#/definitions/color" },
|
||||||
|
"transparency": { "type": "number", "minimum": 0, "maximum": 100, "description": "Fill transparency percent" },
|
||||||
|
"type": { "type": "string", "enum": ["none", "solid"] }
|
||||||
|
},
|
||||||
|
"description": "Shape fill properties"
|
||||||
|
},
|
||||||
|
"shapeLine": {
|
||||||
|
"allOf": [
|
||||||
|
{ "$ref": "#/definitions/shapeFill" },
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"width": { "type": "number", "description": "Line width in points" },
|
||||||
|
"dashType": { "type": "string", "enum": ["solid", "dash", "dashDot", "lgDash", "lgDashDot", "lgDashDotDot", "sysDash", "sysDot"] },
|
||||||
|
"beginArrowType": { "type": "string", "enum": ["none", "arrow", "diamond", "oval", "stealth", "triangle"] },
|
||||||
|
"endArrowType": { "type": "string", "enum": ["none", "arrow", "diamond", "oval", "stealth", "triangle"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Shape line/border properties"
|
||||||
|
},
|
||||||
|
"shadow": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": { "type": "string", "enum": ["outer", "inner", "none"] },
|
||||||
|
"opacity": { "type": "number", "minimum": 0, "maximum": 1, "description": "Shadow opacity (0.0-1.0)" },
|
||||||
|
"blur": { "type": "number", "minimum": 0, "maximum": 100, "description": "Shadow blur in points" },
|
||||||
|
"angle": { "type": "number", "minimum": 0, "maximum": 359, "description": "Shadow angle in degrees" },
|
||||||
|
"offset": { "type": "number", "minimum": 0, "maximum": 200, "description": "Shadow offset in points" },
|
||||||
|
"color": { "$ref": "#/definitions/hexColor" },
|
||||||
|
"rotateWithShape": { "type": "boolean" }
|
||||||
|
},
|
||||||
|
"required": ["type"],
|
||||||
|
"description": "Shadow properties"
|
||||||
|
},
|
||||||
|
"hyperlink": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"slide": { "type": "integer", "description": "Slide number to link to" },
|
||||||
|
"url": { "type": "string", "description": "URL to link to" },
|
||||||
|
"tooltip": { "type": "string" }
|
||||||
|
},
|
||||||
|
"description": "Hyperlink properties"
|
||||||
|
},
|
||||||
|
"textBaseProps": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"align": { "type": "string", "enum": ["left", "center", "right", "justify"] },
|
||||||
|
"bold": { "type": "boolean" },
|
||||||
|
"breakLine": { "type": "boolean" },
|
||||||
|
"bullet": {
|
||||||
|
"oneOf": [
|
||||||
|
{ "type": "boolean" },
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": { "type": "string", "enum": ["bullet", "number"] },
|
||||||
|
"characterCode": { "type": "string" },
|
||||||
|
"indent": { "type": "number" },
|
||||||
|
"numberType": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["alphaLcParenBoth", "alphaLcParenR", "alphaLcPeriod", "alphaUcParenBoth", "alphaUcParenR", "alphaUcPeriod", "arabicParenBoth", "arabicParenR", "arabicPeriod", "arabicPlain", "romanLcParenBoth", "romanLcParenR", "romanLcPeriod", "romanUcParenBoth", "romanUcParenR", "romanUcPeriod"]
|
||||||
|
},
|
||||||
|
"numberStartAt": { "type": "number" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"color": { "$ref": "#/definitions/color" },
|
||||||
|
"fontFace": { "type": "string" },
|
||||||
|
"fontSize": { "type": "number" },
|
||||||
|
"highlight": { "$ref": "#/definitions/hexColor" },
|
||||||
|
"italic": { "type": "boolean" },
|
||||||
|
"lang": { "type": "string" },
|
||||||
|
"textDirection": { "type": "string", "enum": ["horz", "vert", "vert270", "wordArtVert"] },
|
||||||
|
"transparency": { "type": "number", "minimum": 0, "maximum": 100 },
|
||||||
|
"underline": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"style": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["dash", "dashHeavy", "dashLong", "dashLongHeavy", "dbl", "dotDash", "dotDashHeave", "dotDotDash", "dotDotDashHeavy", "dotted", "dottedHeavy", "heavy", "none", "sng", "wavy", "wavyDbl", "wavyHeavy"]
|
||||||
|
},
|
||||||
|
"color": { "$ref": "#/definitions/color" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"valign": { "type": "string", "enum": ["top", "middle", "bottom"] },
|
||||||
|
"softBreakBefore": { "type": "boolean" },
|
||||||
|
"tabStops": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"position": { "type": "number" },
|
||||||
|
"alignment": { "type": "string", "enum": ["l", "r", "ctr", "dec"] }
|
||||||
|
},
|
||||||
|
"required": ["position"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "Base text properties shared by text objects and table cells"
|
||||||
|
},
|
||||||
|
"textOptions": {
|
||||||
|
"allOf": [
|
||||||
|
{ "$ref": "#/definitions/textBaseProps" },
|
||||||
|
{ "$ref": "#/definitions/position" },
|
||||||
|
{ "$ref": "#/definitions/shapeFill" },
|
||||||
|
{ "$ref": "#/definitions/dataOrPath" },
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"objectName": { "type": "string" },
|
||||||
|
"baseline": { "type": "number" },
|
||||||
|
"charSpacing": { "type": "number" },
|
||||||
|
"fit": { "type": "string", "enum": ["none", "shrink", "resize"] },
|
||||||
|
"flipH": { "type": "boolean" },
|
||||||
|
"flipV": { "type": "boolean" },
|
||||||
|
"glow": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"color": { "$ref": "#/definitions/hexColor" },
|
||||||
|
"opacity": { "type": "number" },
|
||||||
|
"size": { "type": "number" }
|
||||||
|
},
|
||||||
|
"required": ["opacity", "size"]
|
||||||
|
},
|
||||||
|
"hyperlink": { "$ref": "#/definitions/hyperlink" },
|
||||||
|
"indentLevel": { "type": "number" },
|
||||||
|
"isTextBox": { "type": "boolean" },
|
||||||
|
"line": { "$ref": "#/definitions/shapeLine" },
|
||||||
|
"lineSpacing": { "type": "number" },
|
||||||
|
"lineSpacingMultiple": { "type": "number", "minimum": 0, "maximum": 9.99 },
|
||||||
|
"margin": { "$ref": "#/definitions/margin" },
|
||||||
|
"outline": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"color": { "$ref": "#/definitions/color" },
|
||||||
|
"size": { "type": "number" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"paraSpaceAfter": { "type": "number" },
|
||||||
|
"paraSpaceBefore": { "type": "number" },
|
||||||
|
"placeholder": { "type": "string" },
|
||||||
|
"rectRadius": { "type": "number" },
|
||||||
|
"rotate": { "type": "number" },
|
||||||
|
"rtlMode": { "type": "boolean" },
|
||||||
|
"shadow": { "$ref": "#/definitions/shadow" },
|
||||||
|
"shape": { "type": "string", "description": "Valid pptxgenjs shape name" },
|
||||||
|
"strike": { "oneOf": [{ "type": "boolean" }, { "type": "string", "enum": ["dblStrike", "sngStrike"] }] },
|
||||||
|
"subscript": { "type": "boolean" },
|
||||||
|
"superscript": { "type": "boolean" },
|
||||||
|
"vert": { "type": "string", "enum": ["eaVert", "horz", "mongolianVert", "vert", "vert270", "wordArtVert", "wordArtVertRtl"] },
|
||||||
|
"wrap": { "type": "boolean" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Full text object options including all TextPropsOptions parameters"
|
||||||
|
},
|
||||||
|
"textObject": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": { "type": "string", "pattern": "^text$" },
|
||||||
|
"text": { "type": "string", "description": "Text content" },
|
||||||
|
"options": { "$ref": "#/definitions/textOptions" }
|
||||||
|
},
|
||||||
|
"required": ["type", "text"],
|
||||||
|
"description": "Text slide object"
|
||||||
|
},
|
||||||
|
"shapeOptions": {
|
||||||
|
"allOf": [
|
||||||
|
{ "$ref": "#/definitions/position" },
|
||||||
|
{ "$ref": "#/definitions/shapeFill" },
|
||||||
|
{ "$ref": "#/definitions/shapeLine" },
|
||||||
|
{ "$ref": "#/definitions/shadow" },
|
||||||
|
{ "$ref": "#/definitions/hyperlink" },
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"objectName": { "type": "string" },
|
||||||
|
"align": { "type": "string", "enum": ["left", "center", "right", "justify"] },
|
||||||
|
"angleRange": {
|
||||||
|
"type": "array",
|
||||||
|
"items": { "type": "number" },
|
||||||
|
"minItems": 2,
|
||||||
|
"maxItems": 2
|
||||||
|
},
|
||||||
|
"arcThicknessRatio": { "type": "number" },
|
||||||
|
"flipH": { "type": "boolean" },
|
||||||
|
"flipV": { "type": "boolean" },
|
||||||
|
"points": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"x": { "$ref": "#/definitions/coord" },
|
||||||
|
"y": { "$ref": "#/definitions/coord" },
|
||||||
|
"moveTo": { "type": "boolean" }
|
||||||
|
},
|
||||||
|
"required": ["x", "y"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"x": { "$ref": "#/definitions/coord" },
|
||||||
|
"y": { "$ref": "#/definitions/coord" },
|
||||||
|
"curve": { "type": "object" }
|
||||||
|
},
|
||||||
|
"required": ["x", "y", "curve"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"close": { "type": "boolean", "const": true }
|
||||||
|
},
|
||||||
|
"required": ["close"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rectRadius": { "type": "number", "description": "Rounded rectangle radius (0.0-1.0)" },
|
||||||
|
"rotate": { "type": "number", "description": "Rotation in degrees (-360 to 360)" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Shape object options"
|
||||||
|
},
|
||||||
|
"shapeObject": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": { "type": "string", "pattern": "^shape$" },
|
||||||
|
"shapeName": { "type": "string", "description": "Valid pptxgenjs shape type (e.g. 'rect', 'ellipse', 'roundRect')" },
|
||||||
|
"options": { "$ref": "#/definitions/shapeOptions" }
|
||||||
|
},
|
||||||
|
"required": ["type", "shapeName"],
|
||||||
|
"description": "Shape slide object"
|
||||||
|
},
|
||||||
|
"tableCell": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"text": { "type": "string" },
|
||||||
|
"options": {
|
||||||
|
"allOf": [
|
||||||
|
{ "$ref": "#/definitions/textBaseProps" },
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"autoPageCharWeight": { "type": "number", "minimum": -1.0, "maximum": 1.0, "description": "Auto-page character weight adjustment" },
|
||||||
|
"autoPageLineWeight": { "type": "number", "minimum": -1.0, "maximum": 1.0, "description": "Auto-page line weight adjustment" },
|
||||||
|
"border": {
|
||||||
|
"oneOf": [
|
||||||
|
{ "$ref": "#/definitions/borderProps" },
|
||||||
|
{ "type": "array", "items": { "$ref": "#/definitions/borderProps" }, "minItems": 4, "maxItems": 4 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"colspan": { "type": "integer", "minimum": 1 },
|
||||||
|
"fill": { "$ref": "#/definitions/shapeFill" },
|
||||||
|
"hyperlink": { "$ref": "#/definitions/hyperlink" },
|
||||||
|
"margin": { "$ref": "#/definitions/margin" },
|
||||||
|
"rowspan": { "type": "integer", "minimum": 1 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "Table cell with text and styling options"
|
||||||
|
},
|
||||||
|
"tableOptions": {
|
||||||
|
"allOf": [
|
||||||
|
{ "$ref": "#/definitions/position" },
|
||||||
|
{ "$ref": "#/definitions/textBaseProps" },
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"objectName": { "type": "string" },
|
||||||
|
"autoPage": { "type": "boolean" },
|
||||||
|
"autoPageCharWeight": { "type": "number" },
|
||||||
|
"autoPageLineWeight": { "type": "number" },
|
||||||
|
"autoPageRepeatHeader": { "type": "boolean" },
|
||||||
|
"autoPageHeaderRows": { "type": "integer", "minimum": 1 },
|
||||||
|
"autoPageSlideStartY": { "type": "number" },
|
||||||
|
"border": {
|
||||||
|
"oneOf": [
|
||||||
|
{ "$ref": "#/definitions/borderProps" },
|
||||||
|
{ "type": "array", "items": { "$ref": "#/definitions/borderProps" }, "minItems": 4, "maxItems": 4 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"colW": {
|
||||||
|
"oneOf": [
|
||||||
|
{ "type": "number" },
|
||||||
|
{ "type": "array", "items": { "type": "number" } }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"fill": { "$ref": "#/definitions/shapeFill" },
|
||||||
|
"margin": { "$ref": "#/definitions/margin" },
|
||||||
|
"rowH": {
|
||||||
|
"oneOf": [
|
||||||
|
{ "type": "number" },
|
||||||
|
{ "type": "array", "items": { "type": "number" } }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"verbose": { "type": "boolean", "description": "Verbose mode for auto-paging debug output" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Table options including all TableProps parameters"
|
||||||
|
},
|
||||||
|
"tableObject": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": { "type": "string", "pattern": "^table$" },
|
||||||
|
"rows": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "array",
|
||||||
|
"items": { "$ref": "#/definitions/tableCell" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": { "$ref": "#/definitions/tableOptions" }
|
||||||
|
},
|
||||||
|
"required": ["type", "rows"],
|
||||||
|
"description": "Table slide object"
|
||||||
|
},
|
||||||
|
"imageOptions": {
|
||||||
|
"allOf": [
|
||||||
|
{ "$ref": "#/definitions/position" },
|
||||||
|
{ "$ref": "#/definitions/dataOrPath" },
|
||||||
|
{ "$ref": "#/definitions/shadow" },
|
||||||
|
{ "$ref": "#/definitions/hyperlink" },
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"objectName": { "type": "string" },
|
||||||
|
"altText": { "type": "string" },
|
||||||
|
"flipH": { "type": "boolean" },
|
||||||
|
"flipV": { "type": "boolean" },
|
||||||
|
"placeholder": { "type": "string" },
|
||||||
|
"rotate": { "type": "number" },
|
||||||
|
"rounding": { "type": "boolean" },
|
||||||
|
"sizing": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": { "type": "string", "enum": ["contain", "cover", "crop"] },
|
||||||
|
"w": { "$ref": "#/definitions/coord" },
|
||||||
|
"h": { "$ref": "#/definitions/coord" },
|
||||||
|
"x": { "$ref": "#/definitions/coord" },
|
||||||
|
"y": { "$ref": "#/definitions/coord" }
|
||||||
|
},
|
||||||
|
"required": ["type", "w", "h"]
|
||||||
|
},
|
||||||
|
"transparency": { "type": "number", "minimum": 0, "maximum": 100 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Image options including all ImageProps parameters"
|
||||||
|
},
|
||||||
|
"imageObject": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": { "type": "string", "pattern": "^image$" },
|
||||||
|
"options": { "$ref": "#/definitions/imageOptions" }
|
||||||
|
},
|
||||||
|
"required": ["type", "options"],
|
||||||
|
"description": "Image slide object"
|
||||||
|
},
|
||||||
|
"chartData": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": { "type": "string", "description": "Series name" },
|
||||||
|
"labels": {
|
||||||
|
"oneOf": [
|
||||||
|
{ "type": "array", "items": { "type": "string" } },
|
||||||
|
{ "type": "array", "items": { "type": "array", "items": { "type": "string" } } }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"sizes": { "type": "array", "items": { "type": "number" }, "description": "Bubble sizes" },
|
||||||
|
"values": { "type": "array", "items": { "type": "number" }, "description": "Category values" }
|
||||||
|
},
|
||||||
|
"description": "Chart data series"
|
||||||
|
},
|
||||||
|
"optsChartGridLine": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"cap": { "type": "string", "enum": ["flat", "round", "square"] },
|
||||||
|
"color": { "$ref": "#/definitions/hexColor" },
|
||||||
|
"size": { "type": "number" },
|
||||||
|
"style": { "type": "string", "enum": ["solid", "dash", "dot", "none"] }
|
||||||
|
},
|
||||||
|
"description": "Chart grid line options"
|
||||||
|
},
|
||||||
|
"catAxisItem": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"hidden": { "type": "boolean" },
|
||||||
|
"labelColor": { "type": "string" },
|
||||||
|
"labelFontBold": { "type": "boolean" },
|
||||||
|
"labelFontFace": { "type": "string" },
|
||||||
|
"labelFontItalic": { "type": "boolean" },
|
||||||
|
"labelFontSize": { "type": "number" },
|
||||||
|
"labelFrequency": { "type": "string" },
|
||||||
|
"labelPos": { "type": "string", "enum": ["none", "low", "high", "nextTo"] },
|
||||||
|
"labelRotate": { "type": "number" },
|
||||||
|
"lineColor": { "type": "string" },
|
||||||
|
"lineShow": { "type": "boolean" },
|
||||||
|
"lineSize": { "type": "number" },
|
||||||
|
"lineStyle": { "type": "string", "enum": ["solid", "dash", "dot"] },
|
||||||
|
"majorTickMark": { "type": "string", "enum": ["none", "inside", "outside", "cross"] },
|
||||||
|
"majorTimeUnit": { "type": "string" },
|
||||||
|
"majorUnit": { "type": "number" },
|
||||||
|
"maxVal": { "type": "number" },
|
||||||
|
"minorTickMark": { "type": "string", "enum": ["none", "inside", "outside", "cross"] },
|
||||||
|
"minorTimeUnit": { "type": "string" },
|
||||||
|
"minorUnit": { "type": "number" },
|
||||||
|
"minVal": { "type": "number" },
|
||||||
|
"multiLevelLabels": { "type": "boolean" },
|
||||||
|
"orientation": { "type": "string", "enum": ["minMax"] },
|
||||||
|
"title": { "type": "string" },
|
||||||
|
"titleColor": { "type": "string" },
|
||||||
|
"titleFontFace": { "type": "string" },
|
||||||
|
"titleFontSize": { "type": "number" },
|
||||||
|
"titleRotate": { "type": "number" },
|
||||||
|
"gridLine": { "$ref": "#/definitions/optsChartGridLine" },
|
||||||
|
"labelFormatCode": { "type": "string" },
|
||||||
|
"baseTimeUnit": { "type": "string" },
|
||||||
|
"crossesAt": { "oneOf": [{ "type": "number" }, { "type": "string", "enum": ["autoZero"] }] }
|
||||||
|
},
|
||||||
|
"description": "Category axis item for catAxes array"
|
||||||
|
},
|
||||||
|
"valAxisItem": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"crossesAt": { "oneOf": [{ "type": "number" }, { "type": "string", "enum": ["autoZero"] }] },
|
||||||
|
"displayUnit": { "type": "string", "enum": ["billions", "hundredMillions", "hundreds", "hundredThousands", "millions", "tenMillions", "tenThousands", "thousands", "trillions"] },
|
||||||
|
"displayUnitLabel": { "type": "boolean" },
|
||||||
|
"hidden": { "type": "boolean" },
|
||||||
|
"labelColor": { "type": "string" },
|
||||||
|
"labelFontBold": { "type": "boolean" },
|
||||||
|
"labelFontFace": { "type": "string" },
|
||||||
|
"labelFontItalic": { "type": "boolean" },
|
||||||
|
"labelFontSize": { "type": "number" },
|
||||||
|
"labelFormatCode": { "type": "string" },
|
||||||
|
"labelPos": { "type": "string", "enum": ["none", "low", "high", "nextTo"] },
|
||||||
|
"labelRotate": { "type": "number" },
|
||||||
|
"lineColor": { "type": "string" },
|
||||||
|
"lineShow": { "type": "boolean" },
|
||||||
|
"lineSize": { "type": "number" },
|
||||||
|
"lineStyle": { "type": "string", "enum": ["solid", "dash", "dot"] },
|
||||||
|
"logScaleBase": { "type": "number", "minimum": 2, "maximum": 99 },
|
||||||
|
"majorTickMark": { "type": "string", "enum": ["none", "inside", "outside", "cross"] },
|
||||||
|
"majorUnit": { "type": "number" },
|
||||||
|
"maxVal": { "type": "number" },
|
||||||
|
"minorTickMark": { "type": "string", "enum": ["none", "inside", "outside", "cross"] },
|
||||||
|
"minVal": { "type": "number" },
|
||||||
|
"orientation": { "type": "string", "enum": ["minMax"] },
|
||||||
|
"title": { "type": "string" },
|
||||||
|
"titleColor": { "type": "string" },
|
||||||
|
"titleFontFace": { "type": "string" },
|
||||||
|
"titleFontSize": { "type": "number" },
|
||||||
|
"titleRotate": { "type": "number" },
|
||||||
|
"gridLine": { "$ref": "#/definitions/optsChartGridLine" }
|
||||||
|
},
|
||||||
|
"description": "Value axis item for valAxes array"
|
||||||
|
},
|
||||||
|
"chartOptions": {
|
||||||
|
"allOf": [
|
||||||
|
{ "$ref": "#/definitions/position" },
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"objectName": { "type": "string" },
|
||||||
|
"altText": { "type": "string" },
|
||||||
|
|
||||||
|
"axisPos": { "type": "string", "enum": ["b", "l", "r", "t"] },
|
||||||
|
"chartColors": { "type": "array", "items": { "$ref": "#/definitions/hexColor" } },
|
||||||
|
"chartColorsOpacity": { "type": "number" },
|
||||||
|
"dataBorder": { "$ref": "#/definitions/borderProps" },
|
||||||
|
"displayBlanksAs": { "type": "string", "enum": ["gap", "span", "zero"] },
|
||||||
|
"invertedColors": { "type": "array", "items": { "$ref": "#/definitions/hexColor" } },
|
||||||
|
"lang": { "type": "string" },
|
||||||
|
"layout": { "$ref": "#/definitions/position" },
|
||||||
|
"shadow": { "$ref": "#/definitions/shadow" },
|
||||||
|
"showLabel": { "type": "boolean" },
|
||||||
|
"showLeaderLines": { "type": "boolean" },
|
||||||
|
"showLegend": { "type": "boolean" },
|
||||||
|
"showPercent": { "type": "boolean" },
|
||||||
|
"showSerName": { "type": "boolean" },
|
||||||
|
"showTitle": { "type": "boolean" },
|
||||||
|
"showValue": { "type": "boolean" },
|
||||||
|
|
||||||
|
"v3DPerspective": { "type": "number", "minimum": 0, "maximum": 120 },
|
||||||
|
"v3DRAngAx": { "type": "boolean" },
|
||||||
|
"v3DRotX": { "type": "number", "minimum": 0, "maximum": 359.9 },
|
||||||
|
"v3DRotY": { "type": "number", "minimum": 0, "maximum": 359.9 },
|
||||||
|
|
||||||
|
"chartArea": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"border": { "$ref": "#/definitions/borderProps" },
|
||||||
|
"fill": { "$ref": "#/definitions/shapeFill" },
|
||||||
|
"roundedCorners": { "type": "boolean" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plotArea": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"border": { "$ref": "#/definitions/borderProps" },
|
||||||
|
"fill": { "$ref": "#/definitions/shapeFill" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"catAxes": { "type": "array", "items": { "$ref": "#/definitions/catAxisItem" } },
|
||||||
|
"catAxisBaseTimeUnit": { "type": "string" },
|
||||||
|
"catAxisCrossesAt": { "oneOf": [{ "type": "number" }, { "type": "string", "enum": ["autoZero"] }] },
|
||||||
|
"catAxisHidden": { "type": "boolean" },
|
||||||
|
"catAxisLabelColor": { "type": "string" },
|
||||||
|
"catAxisLabelFontBold": { "type": "boolean" },
|
||||||
|
"catAxisLabelFontFace": { "type": "string" },
|
||||||
|
"catAxisLabelFontItalic": { "type": "boolean" },
|
||||||
|
"catAxisLabelFontSize": { "type": "number" },
|
||||||
|
"catAxisLabelFrequency": { "type": "string" },
|
||||||
|
"catAxisLabelPos": { "type": "string", "enum": ["none", "low", "high", "nextTo"] },
|
||||||
|
"catAxisLabelRotate": { "type": "number" },
|
||||||
|
"catAxisLineColor": { "type": "string" },
|
||||||
|
"catAxisLineShow": { "type": "boolean" },
|
||||||
|
"catAxisLineSize": { "type": "number" },
|
||||||
|
"catAxisLineStyle": { "type": "string", "enum": ["solid", "dash", "dot"] },
|
||||||
|
"catAxisMajorTickMark": { "type": "string", "enum": ["none", "inside", "outside", "cross"] },
|
||||||
|
"catAxisMajorTimeUnit": { "type": "string" },
|
||||||
|
"catAxisMajorUnit": { "type": "number" },
|
||||||
|
"catAxisMaxVal": { "type": "number" },
|
||||||
|
"catAxisMinorTickMark": { "type": "string", "enum": ["none", "inside", "outside", "cross"] },
|
||||||
|
"catAxisMinorTimeUnit": { "type": "string" },
|
||||||
|
"catAxisMinorUnit": { "type": "number" },
|
||||||
|
"catAxisMinVal": { "type": "number" },
|
||||||
|
"catAxisMultiLevelLabels": { "type": "boolean" },
|
||||||
|
"catAxisOrientation": { "type": "string", "enum": ["minMax"] },
|
||||||
|
"catAxisTitle": { "type": "string" },
|
||||||
|
"catAxisTitleColor": { "type": "string" },
|
||||||
|
"catAxisTitleFontFace": { "type": "string" },
|
||||||
|
"catAxisTitleFontSize": { "type": "number" },
|
||||||
|
"catAxisTitleRotate": { "type": "number" },
|
||||||
|
"catGridLine": { "$ref": "#/definitions/optsChartGridLine" },
|
||||||
|
"catLabelFormatCode": { "type": "string" },
|
||||||
|
"secondaryCatAxis": { "type": "boolean" },
|
||||||
|
"showCatAxisTitle": { "type": "boolean" },
|
||||||
|
|
||||||
|
"serAxisBaseTimeUnit": { "type": "string" },
|
||||||
|
"serAxisHidden": { "type": "boolean" },
|
||||||
|
"serAxisLabelColor": { "type": "string" },
|
||||||
|
"serAxisLabelFontBold": { "type": "boolean" },
|
||||||
|
"serAxisLabelFontFace": { "type": "string" },
|
||||||
|
"serAxisLabelFontItalic": { "type": "boolean" },
|
||||||
|
"serAxisLabelFontSize": { "type": "number" },
|
||||||
|
"serAxisLabelFrequency": { "type": "string" },
|
||||||
|
"serAxisLabelPos": { "type": "string", "enum": ["none", "low", "high", "nextTo"] },
|
||||||
|
"serAxisLineColor": { "type": "string" },
|
||||||
|
"serAxisLineShow": { "type": "boolean" },
|
||||||
|
"serAxisMajorTimeUnit": { "type": "string" },
|
||||||
|
"serAxisMajorUnit": { "type": "number" },
|
||||||
|
"serAxisMinorTimeUnit": { "type": "string" },
|
||||||
|
"serAxisMinorUnit": { "type": "number" },
|
||||||
|
"serAxisOrientation": { "type": "string", "enum": ["minMax"] },
|
||||||
|
"serAxisTitle": { "type": "string" },
|
||||||
|
"serAxisTitleColor": { "type": "string" },
|
||||||
|
"serAxisTitleFontFace": { "type": "string" },
|
||||||
|
"serAxisTitleFontSize": { "type": "number" },
|
||||||
|
"serAxisTitleRotate": { "type": "number" },
|
||||||
|
"serGridLine": { "$ref": "#/definitions/optsChartGridLine" },
|
||||||
|
"serLabelFormatCode": { "type": "string" },
|
||||||
|
"showSerAxisTitle": { "type": "boolean" },
|
||||||
|
|
||||||
|
"secondaryValAxis": { "type": "boolean" },
|
||||||
|
"showValAxisTitle": { "type": "boolean" },
|
||||||
|
"valAxes": { "type": "array", "items": { "$ref": "#/definitions/valAxisItem" } },
|
||||||
|
"valAxisCrossesAt": { "oneOf": [{ "type": "number" }, { "type": "string", "enum": ["autoZero"] }] },
|
||||||
|
"valAxisDisplayUnit": { "type": "string", "enum": ["billions", "hundredMillions", "hundreds", "hundredThousands", "millions", "tenMillions", "tenThousands", "thousands", "trillions"] },
|
||||||
|
"valAxisDisplayUnitLabel": { "type": "boolean" },
|
||||||
|
"valAxisHidden": { "type": "boolean" },
|
||||||
|
"valAxisLabelColor": { "type": "string" },
|
||||||
|
"valAxisLabelFontBold": { "type": "boolean" },
|
||||||
|
"valAxisLabelFontFace": { "type": "string" },
|
||||||
|
"valAxisLabelFontItalic": { "type": "boolean" },
|
||||||
|
"valAxisLabelFontSize": { "type": "number" },
|
||||||
|
"valAxisLabelFormatCode": { "type": "string" },
|
||||||
|
"valAxisLabelPos": { "type": "string", "enum": ["none", "low", "high", "nextTo"] },
|
||||||
|
"valAxisLabelRotate": { "type": "number" },
|
||||||
|
"valAxisLineColor": { "type": "string" },
|
||||||
|
"valAxisLineShow": { "type": "boolean" },
|
||||||
|
"valAxisLineSize": { "type": "number" },
|
||||||
|
"valAxisLineStyle": { "type": "string", "enum": ["solid", "dash", "dot"] },
|
||||||
|
"valAxisLogScaleBase": { "type": "number", "minimum": 2, "maximum": 99 },
|
||||||
|
"valAxisMajorTickMark": { "type": "string", "enum": ["none", "inside", "outside", "cross"] },
|
||||||
|
"valAxisMajorUnit": { "type": "number" },
|
||||||
|
"valAxisMaxVal": { "type": "number" },
|
||||||
|
"valAxisMinorTickMark": { "type": "string", "enum": ["none", "inside", "outside", "cross"] },
|
||||||
|
"valAxisMinVal": { "type": "number" },
|
||||||
|
"valAxisOrientation": { "type": "string", "enum": ["minMax"] },
|
||||||
|
"valAxisTitle": { "type": "string" },
|
||||||
|
"valAxisTitleColor": { "type": "string" },
|
||||||
|
"valAxisTitleFontFace": { "type": "string" },
|
||||||
|
"valAxisTitleFontSize": { "type": "number" },
|
||||||
|
"valAxisTitleRotate": { "type": "number" },
|
||||||
|
"valGridLine": { "$ref": "#/definitions/optsChartGridLine" },
|
||||||
|
"valLabelFormatCode": { "type": "string" },
|
||||||
|
|
||||||
|
"bar3DShape": { "type": "string", "enum": ["box", "cylinder", "cone", "pyramid"] },
|
||||||
|
"barDir": { "type": "string", "enum": ["bar", "col"] },
|
||||||
|
"barGapDepthPct": { "type": "number" },
|
||||||
|
"barGapWidthPct": { "type": "number" },
|
||||||
|
"barGrouping": { "type": "string", "enum": ["clustered", "stacked", "standard"] },
|
||||||
|
"barOverlapPct": { "type": "number" },
|
||||||
|
|
||||||
|
"dataNoEffects": { "type": "boolean" },
|
||||||
|
"holeSize": { "type": "number" },
|
||||||
|
|
||||||
|
"lineCap": { "type": "string", "enum": ["flat", "round", "square"] },
|
||||||
|
"lineDash": { "type": "string", "enum": ["dash", "dashDot", "lgDash", "lgDashDot", "lgDashDotDot", "solid", "sysDash", "sysDot"] },
|
||||||
|
"lineDataSymbol": { "type": "string", "enum": ["circle", "dash", "diamond", "dot", "none", "square", "triangle"] },
|
||||||
|
"lineDataSymbolLineColor": { "type": "string" },
|
||||||
|
"lineDataSymbolLineSize": { "type": "number" },
|
||||||
|
"lineDataSymbolSize": { "type": "number" },
|
||||||
|
"lineSize": { "type": "number" },
|
||||||
|
"lineSmooth": { "type": "boolean" },
|
||||||
|
|
||||||
|
"firstSliceAng": { "type": "number", "minimum": 0, "maximum": 359 },
|
||||||
|
|
||||||
|
"radarStyle": { "type": "string", "enum": ["standard", "marker", "filled"] },
|
||||||
|
|
||||||
|
"dataLabelBkgrdColors": { "type": "boolean" },
|
||||||
|
"dataLabelColor": { "type": "string" },
|
||||||
|
"dataLabelFontBold": { "type": "boolean" },
|
||||||
|
"dataLabelFontFace": { "type": "string" },
|
||||||
|
"dataLabelFontItalic": { "type": "boolean" },
|
||||||
|
"dataLabelFontSize": { "type": "number" },
|
||||||
|
"dataLabelFormatCode": { "type": "string" },
|
||||||
|
"dataLabelFormatScatter": { "type": "string", "enum": ["custom", "customXY", "XY"] },
|
||||||
|
"dataLabelPosition": { "type": "string", "enum": ["b", "bestFit", "ctr", "l", "r", "t", "inEnd", "outEnd"] },
|
||||||
|
|
||||||
|
"dataTableFontSize": { "type": "number" },
|
||||||
|
"dataTableFormatCode": { "type": "string" },
|
||||||
|
"showDataTable": { "type": "boolean" },
|
||||||
|
"showDataTableHorzBorder": { "type": "boolean" },
|
||||||
|
"showDataTableKeys": { "type": "boolean" },
|
||||||
|
"showDataTableOutline": { "type": "boolean" },
|
||||||
|
"showDataTableVertBorder": { "type": "boolean" },
|
||||||
|
|
||||||
|
"legendColor": { "type": "string" },
|
||||||
|
"legendFontFace": { "type": "string" },
|
||||||
|
"legendFontSize": { "type": "number" },
|
||||||
|
"legendPos": { "type": "string", "enum": ["b", "l", "r", "t", "tr"] },
|
||||||
|
|
||||||
|
"title": { "type": "string" },
|
||||||
|
"titleAlign": { "type": "string", "enum": ["l", "ctr", "r"] },
|
||||||
|
"titleBold": { "type": "boolean" },
|
||||||
|
"titleColor": { "type": "string" },
|
||||||
|
"titleFontFace": { "type": "string" },
|
||||||
|
"titleFontSize": { "type": "number" },
|
||||||
|
"titlePos": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"x": { "type": "number" },
|
||||||
|
"y": { "type": "number" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"titleRotate": { "type": "number" },
|
||||||
|
|
||||||
|
"cap": { "type": "string", "enum": ["flat", "round", "square"] },
|
||||||
|
"color": { "$ref": "#/definitions/hexColor" },
|
||||||
|
"size": { "type": "number" },
|
||||||
|
"style": { "type": "string", "enum": ["solid", "dash", "dot", "none"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Chart options including all IChartOpts parameters"
|
||||||
|
},
|
||||||
|
"chartObject": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": { "type": "string", "pattern": "^chart$" },
|
||||||
|
"chartType": { "type": "string", "enum": ["area", "bar", "bar3D", "bubble", "doughnut", "line", "pie", "radar", "scatter"] },
|
||||||
|
"data": { "type": "array", "items": { "$ref": "#/definitions/chartData" } },
|
||||||
|
"options": { "$ref": "#/definitions/chartOptions" }
|
||||||
|
},
|
||||||
|
"required": ["type", "chartType", "data"],
|
||||||
|
"description": "Chart slide object"
|
||||||
|
},
|
||||||
|
"slideNumberProps": {
|
||||||
|
"allOf": [
|
||||||
|
{ "$ref": "#/definitions/position" },
|
||||||
|
{ "$ref": "#/definitions/textBaseProps" },
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"margin": { "$ref": "#/definitions/margin" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Slide number display properties"
|
||||||
|
},
|
||||||
|
"backgroundProps": {
|
||||||
|
"allOf": [
|
||||||
|
{ "$ref": "#/definitions/dataOrPath" },
|
||||||
|
{ "$ref": "#/definitions/shapeFill" },
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"fill": { "$ref": "#/definitions/hexColor", "description": "Deprecated: use shapeFill.color instead" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Slide background properties (color or image with DataOrPathProps + ShapeFillProps)"
|
||||||
|
},
|
||||||
|
"slideObject": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"background": { "$ref": "#/definitions/backgroundProps" },
|
||||||
|
"color": { "$ref": "#/definitions/hexColor", "description": "Default text color for this slide" },
|
||||||
|
"hidden": { "type": "boolean", "description": "Whether slide is hidden" },
|
||||||
|
"notes": { "type": "string", "description": "Speaker notes for this slide" },
|
||||||
|
"slideNumber": { "$ref": "#/definitions/slideNumberProps" },
|
||||||
|
"objects": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"oneOf": [
|
||||||
|
{ "$ref": "#/definitions/textObject" },
|
||||||
|
{ "$ref": "#/definitions/shapeObject" },
|
||||||
|
{ "$ref": "#/definitions/tableObject" },
|
||||||
|
{ "$ref": "#/definitions/imageObject" },
|
||||||
|
{ "$ref": "#/definitions/chartObject" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "A single slide with background, properties, and content objects"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"presentation": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"layout": { "type": "string", "description": "Presentation layout (e.g. LAYOUT_16x9, LAYOUT_4x3)" },
|
||||||
|
"author": { "type": "string" },
|
||||||
|
"title": { "type": "string" },
|
||||||
|
"subject": { "type": "string" },
|
||||||
|
"company": { "type": "string" },
|
||||||
|
"revision": { "type": "string" },
|
||||||
|
"rtlMode": { "type": "boolean" },
|
||||||
|
"masterSlide": { "type": "object", "description": "Master slide reference object" },
|
||||||
|
"presLayout": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": { "type": "string", "description": "Layout name (e.g. LAYOUT_16x9)" },
|
||||||
|
"width": { "type": "number", "description": "Slide width in inches" },
|
||||||
|
"height": { "type": "number", "description": "Slide height in inches" }
|
||||||
|
},
|
||||||
|
"description": "Presentation layout dimensions"
|
||||||
|
},
|
||||||
|
"theme": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"headFontFace": { "type": "string" },
|
||||||
|
"bodyFontFace": { "type": "string" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "Presentation-level metadata and layout settings"
|
||||||
|
},
|
||||||
|
"slides": {
|
||||||
|
"type": "array",
|
||||||
|
"items": { "$ref": "#/definitions/slideObject" },
|
||||||
|
"minItems": 1,
|
||||||
|
"description": "Array of slides in the presentation"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["slides"],
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>样式测试页</title>
|
||||||
|
<style>
|
||||||
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
body { background: #f5f5f5; font-family: sans-serif; padding: 40px; }
|
||||||
|
h2 { margin: 30px 0 15px; color: #333; }
|
||||||
|
.row { display: flex; gap: 20px; margin: 15px 0; flex-wrap: wrap; align-items: flex-start; }
|
||||||
|
.card {
|
||||||
|
background: white;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||||
|
width: 250px;
|
||||||
|
}
|
||||||
|
.label { font-size: 12px; color: #999; margin-bottom: 5px; }
|
||||||
|
|
||||||
|
/* 四圆角测试 */
|
||||||
|
.rounded-1 { border-radius: 12px; }
|
||||||
|
.rounded-2 { border-radius: 0 20px 0 20px; }
|
||||||
|
.rounded-3 { border-radius: 50px 10px 50px 10px; }
|
||||||
|
.rounded-4 { border-radius: 50%; width: 120px; height: 120px; }
|
||||||
|
|
||||||
|
/* 旋转测试 */
|
||||||
|
.rotate-1 { transform: rotate(5deg); }
|
||||||
|
.rotate-2 { transform: rotate(-8deg); }
|
||||||
|
.rotate-3 { transform: rotate(15deg); }
|
||||||
|
|
||||||
|
/* 组合 */
|
||||||
|
.combo-1 { border-radius: 15px; transform: rotate(3deg); background: #e8f4fd; }
|
||||||
|
.combo-2 { border-radius: 0 0 20px 20px; transform: rotate(-2deg); background: #fde8e8; }
|
||||||
|
|
||||||
|
/* 透明度 */
|
||||||
|
.opacity-1 { opacity: 0.7; background: #d4edda; }
|
||||||
|
.opacity-2 { opacity: 0.4; background: #fff3cd; }
|
||||||
|
|
||||||
|
/* 字间距 */
|
||||||
|
.ls-wide { letter-spacing: 3px; }
|
||||||
|
.ls-narrow { letter-spacing: -0.5px; }
|
||||||
|
|
||||||
|
/* 行高 */
|
||||||
|
.lh-tight { line-height: 1.2; }
|
||||||
|
.lh-loose { line-height: 2.0; }
|
||||||
|
|
||||||
|
/* 文字装饰 */
|
||||||
|
.td-underline { text-decoration: underline; }
|
||||||
|
.td-strike { text-decoration: line-through; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<h1>样式映射测试页</h1>
|
||||||
|
<p>用于验证 html2pptx 的样式转化能力</p>
|
||||||
|
|
||||||
|
<h2>一、四圆角</h2>
|
||||||
|
<div class="row">
|
||||||
|
<div>
|
||||||
|
<div class="label">统一圆角 12px</div>
|
||||||
|
<div class="card rounded-1">border-radius: 12px</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="label">对角圆角</div>
|
||||||
|
<div class="card rounded-2">border-radius: 0 20px 0 20px</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="label">交替圆角</div>
|
||||||
|
<div class="card rounded-3">border-radius: 50px 10px 50px 10px</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="label">正圆 50%</div>
|
||||||
|
<div class="card rounded-4" style="display:flex;align-items:center;justify-content:center;">圆形</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>二、旋转</h2>
|
||||||
|
<div class="row">
|
||||||
|
<div>
|
||||||
|
<div class="label">rotate(5deg)</div>
|
||||||
|
<div class="card rotate-1">轻微右倾</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="label">rotate(-8deg)</div>
|
||||||
|
<div class="card rotate-2">左倾</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="label">rotate(15deg)</div>
|
||||||
|
<div class="card rotate-3">明显右倾</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>三、圆角 + 旋转组合</h2>
|
||||||
|
<div class="row">
|
||||||
|
<div>
|
||||||
|
<div class="label">圆角 + 轻微旋转</div>
|
||||||
|
<div class="card combo-1">border-radius: 15px + rotate(3deg)</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="label">底圆角 + 左倾</div>
|
||||||
|
<div class="card combo-2">底部圆角 + rotate(-2deg)</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>四、透明度</h2>
|
||||||
|
<div class="row">
|
||||||
|
<div>
|
||||||
|
<div class="label">opacity: 0.7</div>
|
||||||
|
<div class="card opacity-1">70% 不透明</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="label">opacity: 0.4</div>
|
||||||
|
<div class="card opacity-2">40% 不透明</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>五、字间距</h2>
|
||||||
|
<div style="background:white;padding:20px;margin:15px 0;">
|
||||||
|
<div class="label">letter-spacing: 3px</div>
|
||||||
|
<p class="ls-wide">这是加宽字间距的文字,每个字之间有明显间隔</p>
|
||||||
|
<div class="label" style="margin-top:15px;">letter-spacing: -0.5px</div>
|
||||||
|
<p class="ls-narrow">这是收紧字间距的文字,字符排列更紧密</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>六、行高</h2>
|
||||||
|
<div style="background:white;padding:20px;margin:15px 0;">
|
||||||
|
<div class="label">line-height: 1.2(紧凑)</div>
|
||||||
|
<p class="lh-tight">这是紧凑行高的段落。文字之间的垂直距离很小,适合标题或短文本展示。行高1.2意味着行间距是字号的1.2倍。</p>
|
||||||
|
<div class="label" style="margin-top:15px;">line-height: 2.0(宽松)</div>
|
||||||
|
<p class="lh-loose">这是宽松行高的段落。文字之间的垂直距离很大,适合长文本阅读。行高2.0意味着行间距是字号的2倍。</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>七、文字装饰</h2>
|
||||||
|
<div style="background:white;padding:20px;margin:15px 0;">
|
||||||
|
<p class="td-underline">这段文字有下划线(text-decoration: underline)</p>
|
||||||
|
<p class="td-strike" style="margin-top:10px;">这段文字有删除线(text-decoration: line-through)</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# Web to PPT
|
||||||
|
|
||||||
|
网页采集 → PPTX 导出工具,包含 Chrome 扩展和转化引擎。
|
||||||
|
|
||||||
|
## 组件
|
||||||
|
|
||||||
|
- `capture.js`:DOM 提取引擎(在页面上下文中运行)
|
||||||
|
- `runner.js`:预处理(冻结动画、滚动加载、等待图片)
|
||||||
|
- `background.js`:Service Worker,编排采集流程
|
||||||
|
- `popup.html/js/css`:扩展弹出窗口 UI
|
||||||
|
- `convert-browser.js`:浏览器版转化逻辑(JSON → PPTX Schema)
|
||||||
|
- `lib-pptxgen.js`:pptxgenjs 浏览器 bundle
|
||||||
|
|
||||||
|
## 使用
|
||||||
|
|
||||||
|
1. 在 Chrome 中加载 `web-to-ppt/` 为未打包扩展
|
||||||
|
2. 打开目标网页
|
||||||
|
3. 点击扩展图标,设置采集宽度
|
||||||
|
4. 点击"导出 PPTX"
|
||||||
|
5. 自动下载生成的 PPTX 文件
|
||||||
|
|
||||||
|
## 文件格式
|
||||||
|
|
||||||
|
扩展导出的 JSON 文件包含:
|
||||||
|
- `source`:页面 URL、标题、视口信息
|
||||||
|
- `canvas`:画布尺寸和背景色
|
||||||
|
- `nodes`:DOM 节点树(FRAME/TEXT/RECTANGLE 类型)
|
||||||
|
- `assets`:图片资源(base64)
|
||||||
|
- `fonts`:字体列表
|
||||||
@@ -0,0 +1,197 @@
|
|||||||
|
const CAPTURE_FILE = "capture.js";
|
||||||
|
const RUNNER_FILE = "runner.js";
|
||||||
|
const SETTINGS_KEY = "webToPixsoSettings";
|
||||||
|
const DEFAULT_SETTINGS = {
|
||||||
|
useProxy: false,
|
||||||
|
concurrency: "8",
|
||||||
|
captureMode: "mixed",
|
||||||
|
captureWidth: null
|
||||||
|
};
|
||||||
|
const MIN_CAPTURE_WIDTH = 320;
|
||||||
|
const MAX_CAPTURE_WIDTH = 3840;
|
||||||
|
|
||||||
|
let lastCaptureData = null;
|
||||||
|
|
||||||
|
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
|
||||||
|
function normalizeSettings(value = {}) {
|
||||||
|
const concurrency = String(value.concurrency || DEFAULT_SETTINGS.concurrency);
|
||||||
|
const captureWidth = normalizeCaptureWidth(value.captureWidth, null);
|
||||||
|
return {
|
||||||
|
useProxy: Boolean(value.useProxy),
|
||||||
|
concurrency: ["4", "6", "8", "10", "12", "16", "20", "infinite"].includes(concurrency)
|
||||||
|
? concurrency
|
||||||
|
: DEFAULT_SETTINGS.concurrency,
|
||||||
|
captureMode: value.captureMode === "editable" ? "editable" : "mixed",
|
||||||
|
captureWidth
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeCaptureWidth(value, fallback = null) {
|
||||||
|
const number = Number.parseInt(String(value || "").replace(/\D+/g, ""), 10);
|
||||||
|
if (!Number.isFinite(number)) return fallback;
|
||||||
|
return Math.max(MIN_CAPTURE_WIDTH, Math.min(MAX_CAPTURE_WIDTH, number));
|
||||||
|
}
|
||||||
|
|
||||||
|
function definedWindowBounds(bounds = {}) {
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(bounds).filter(([, value]) => Number.isFinite(value))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertCaptureableTab(tab) {
|
||||||
|
if (!tab?.id || !tab.url) {
|
||||||
|
throw new Error("没有可采集的当前标签页");
|
||||||
|
}
|
||||||
|
if (/^(chrome|edge|about|devtools|chrome-extension):/i.test(tab.url)) {
|
||||||
|
throw new Error("浏览器内置页面不支持采集,请切换到普通网页");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getActiveTab() {
|
||||||
|
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
||||||
|
assertCaptureableTab(tab);
|
||||||
|
return tab;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runCapture(tabId, options) {
|
||||||
|
await chrome.scripting.executeScript({ target: { tabId }, files: [CAPTURE_FILE] });
|
||||||
|
await chrome.scripting.executeScript({ target: { tabId }, files: [RUNNER_FILE] });
|
||||||
|
const [{ result }] = await chrome.scripting.executeScript({
|
||||||
|
target: { tabId },
|
||||||
|
func: captureOptions => window.__webToPPTRunCapture(captureOptions),
|
||||||
|
args: [options]
|
||||||
|
});
|
||||||
|
if (!result) throw new Error("页面没有返回采集结果");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getTabViewportWidth(tabId) {
|
||||||
|
try {
|
||||||
|
const [{ result }] = await chrome.scripting.executeScript({
|
||||||
|
target: { tabId },
|
||||||
|
func: () => Math.round(window.innerWidth || document.documentElement.clientWidth || 0)
|
||||||
|
});
|
||||||
|
return normalizeCaptureWidth(result, null);
|
||||||
|
} catch { return null; }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function prepareCaptureViewport(tab, requestedWidth) {
|
||||||
|
const targetWidth = normalizeCaptureWidth(requestedWidth, null);
|
||||||
|
const beforeViewportWidth = await getTabViewportWidth(tab.id);
|
||||||
|
const noop = async () => {};
|
||||||
|
if (!targetWidth || !beforeViewportWidth || Math.abs(beforeViewportWidth - targetWidth) <= 2) {
|
||||||
|
return { restore: noop, requestedWidth: targetWidth || beforeViewportWidth, beforeViewportWidth, actualViewportWidth: beforeViewportWidth, resizedWindow: false };
|
||||||
|
}
|
||||||
|
if (!tab.windowId || !chrome.windows?.get || !chrome.windows?.update) {
|
||||||
|
throw new Error("当前浏览器不支持临时调整采集视口宽度");
|
||||||
|
}
|
||||||
|
const originalWindow = await chrome.windows.get(tab.windowId);
|
||||||
|
const originalState = originalWindow.state || "normal";
|
||||||
|
const originalBounds = { left: originalWindow.left, top: originalWindow.top, width: originalWindow.width, height: originalWindow.height };
|
||||||
|
const restore = async () => {
|
||||||
|
try {
|
||||||
|
if (originalState !== "normal") { await chrome.windows.update(tab.windowId, { state: "normal" }); await delay(120); }
|
||||||
|
const restoreBounds = definedWindowBounds(originalBounds);
|
||||||
|
if (Object.keys(restoreBounds).length) await chrome.windows.update(tab.windowId, restoreBounds);
|
||||||
|
if (originalState !== "normal") { await delay(120); await chrome.windows.update(tab.windowId, { state: originalState }); }
|
||||||
|
await delay(250);
|
||||||
|
} catch {}
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
if (originalState !== "normal") { await chrome.windows.update(tab.windowId, { state: "normal" }); await delay(250); }
|
||||||
|
let currentViewportWidth = beforeViewportWidth;
|
||||||
|
let currentWindow = await chrome.windows.get(tab.windowId);
|
||||||
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
||||||
|
const delta = targetWidth - currentViewportWidth;
|
||||||
|
const nextWidth = Math.max(360, Math.round((currentWindow.width || targetWidth) + delta));
|
||||||
|
await chrome.windows.update(tab.windowId, { width: nextWidth });
|
||||||
|
await delay(650);
|
||||||
|
currentViewportWidth = await getTabViewportWidth(tab.id) || currentViewportWidth;
|
||||||
|
if (Math.abs(currentViewportWidth - targetWidth) <= 2) break;
|
||||||
|
currentWindow = await chrome.windows.get(tab.windowId);
|
||||||
|
}
|
||||||
|
if (Math.abs(currentViewportWidth - targetWidth) > 2) throw new Error(`采集视口未生效:目标 ${targetWidth}px,实际 ${currentViewportWidth}px`);
|
||||||
|
return { restore, requestedWidth: targetWidth, beforeViewportWidth, actualViewportWidth: currentViewportWidth, resizedWindow: true };
|
||||||
|
} catch (error) { await restore(); throw error; }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function captureCurrentTab(tab, settings) {
|
||||||
|
const viewport = await prepareCaptureViewport(tab, settings.captureWidth);
|
||||||
|
try {
|
||||||
|
const data = await runCapture(tab.id, { ...settings, captureWidth: viewport.requestedWidth });
|
||||||
|
data.capture = { ...(data.capture || {}), resizedWindow: viewport.resizedWindow, usedTemporaryWindow: viewport.resizedWindow, requestedWidth: viewport.requestedWidth || data.source?.actualViewportWidth || data.canvas?.width, beforeViewportWidth: viewport.beforeViewportWidth, actualViewportWidth: data.source?.actualViewportWidth || viewport.actualViewportWidth };
|
||||||
|
return data;
|
||||||
|
} finally { await viewport.restore(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
function arrayBufferToBase64(buffer) {
|
||||||
|
const bytes = new Uint8Array(buffer);
|
||||||
|
const chunkSize = 0x8000;
|
||||||
|
let binary = "";
|
||||||
|
for (let index = 0; index < bytes.length; index += chunkSize) {
|
||||||
|
binary += String.fromCharCode(...bytes.subarray(index, index + chunkSize));
|
||||||
|
}
|
||||||
|
return btoa(binary);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 消息处理
|
||||||
|
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||||
|
// 采集并缓存数据
|
||||||
|
if (message?.type === "WEB_TO_PPT_CAPTURE_START") {
|
||||||
|
(async () => {
|
||||||
|
const settings = normalizeSettings(message.options);
|
||||||
|
await chrome.storage.local.set({ [SETTINGS_KEY]: settings });
|
||||||
|
const tab = sender?.tab || await getActiveTab();
|
||||||
|
assertCaptureableTab(tab);
|
||||||
|
const data = await captureCurrentTab(tab, settings);
|
||||||
|
lastCaptureData = data;
|
||||||
|
|
||||||
|
return { ok: true, actualViewportWidth: data.source?.actualViewportWidth };
|
||||||
|
})()
|
||||||
|
.then(sendResponse)
|
||||||
|
.catch(error => sendResponse({ ok: false, error: error.message || String(error) }));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取缓存数据
|
||||||
|
if (message?.type === "WEB_TO_PPT_GET_DATA") {
|
||||||
|
if (lastCaptureData) {
|
||||||
|
sendResponse({ ok: true, data: lastCaptureData });
|
||||||
|
} else {
|
||||||
|
(async () => {
|
||||||
|
const settings = normalizeSettings({});
|
||||||
|
const tab = await getActiveTab();
|
||||||
|
const data = await captureCurrentTab(tab, settings);
|
||||||
|
lastCaptureData = data;
|
||||||
|
return { ok: true, data };
|
||||||
|
})()
|
||||||
|
.then(sendResponse)
|
||||||
|
.catch(error => sendResponse({ ok: false, error: error.message || String(error) }));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下载 PPTX(从 storage 读取 base64)
|
||||||
|
if (message?.type === "WEB_TO_PPT_DOWNLOAD") {
|
||||||
|
(async () => {
|
||||||
|
const stored = await chrome.storage.session.get(['pptxBase64', 'pptxFilename']);
|
||||||
|
if (!stored.pptxBase64) throw new Error('没有待下载的数据');
|
||||||
|
const dataUrl = 'data:application/vnd.openxmlformats-officedocument.presentationml.presentation;base64,' + stored.pptxBase64;
|
||||||
|
await chrome.downloads.download({ url: dataUrl, filename: stored.pptxFilename });
|
||||||
|
await chrome.storage.session.remove(['pptxBase64', 'pptxFilename']);
|
||||||
|
return { ok: true };
|
||||||
|
})()
|
||||||
|
.then(sendResponse)
|
||||||
|
.catch(error => sendResponse({ ok: false, error: error.message || String(error) }));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
chrome.runtime.onInstalled.addListener(() => {
|
||||||
|
chrome.storage.local.get({ [SETTINGS_KEY]: DEFAULT_SETTINGS }).then(result => {
|
||||||
|
chrome.storage.local.set({ [SETTINGS_KEY]: normalizeSettings(result[SETTINGS_KEY]) });
|
||||||
|
});
|
||||||
|
});
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,625 @@
|
|||||||
|
/**
|
||||||
|
* convert-browser.js — 浏览器兼容版转化逻辑
|
||||||
|
*
|
||||||
|
* 从 convert-w2p.js 移植,去掉了 fs/path/https/child_process 依赖。
|
||||||
|
* 用 fetch() 替代 https.get(),用 jszip 替代 zip 命令行。
|
||||||
|
*
|
||||||
|
* 导出:convertToPptx(jsonData) → Promise<Blob>
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ===== 颜色工具 =====
|
||||||
|
function rgbaToHex(rgbaStr) {
|
||||||
|
if (typeof rgbaStr !== 'string') return null;
|
||||||
|
rgbaStr = rgbaStr.trim();
|
||||||
|
if (rgbaStr === 'transparent' || rgbaStr === 'rgba(0,0,0,0)' || rgbaStr === 'rgba(0, 0, 0, 0)') return null;
|
||||||
|
const match = rgbaStr.match(/^rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*([\d.]+))?\s*\)$/);
|
||||||
|
if (!match) return null;
|
||||||
|
const r = parseInt(match[1], 10);
|
||||||
|
const g = parseInt(match[2], 10);
|
||||||
|
const b = parseInt(match[3], 10);
|
||||||
|
const a = match[4] !== undefined ? parseFloat(match[4]) : 1;
|
||||||
|
if (a === 0) return null;
|
||||||
|
return ((r << 16) | (g << 8) | b).toString(16).padStart(6, '0');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== 图片下载 =====
|
||||||
|
async function downloadImage(url) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, { signal: AbortSignal.timeout(8000) });
|
||||||
|
if (!response.ok) return null;
|
||||||
|
const buffer = await response.arrayBuffer();
|
||||||
|
const bytes = new Uint8Array(buffer);
|
||||||
|
let binary = '';
|
||||||
|
for (let i = 0; i < bytes.length; i += 0x8000) {
|
||||||
|
binary += String.fromCharCode(...bytes.subarray(i, i + 0x8000));
|
||||||
|
}
|
||||||
|
return 'data:image/png;base64,' + btoa(binary);
|
||||||
|
} catch { return null; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Slide 识别 =====
|
||||||
|
const SLIDE_NAME_RE = /^(slide([- ]\w+)*|page)$/i;
|
||||||
|
const STRUCTURAL_TAGS = new Set(['HTML', 'BODY', 'HEAD', 'CANVAS']);
|
||||||
|
|
||||||
|
function collectIds(node, set) {
|
||||||
|
if (!node) return;
|
||||||
|
if (node.id) set.add(node.id);
|
||||||
|
if (node.children) for (const c of node.children) collectIds(c, set);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSlideCandidate(node, canvasArea) {
|
||||||
|
if (!node || node.type !== 'FRAME') return false;
|
||||||
|
if (!node.rect) return false;
|
||||||
|
if (node.layerGroup === 'comparison') return false;
|
||||||
|
if (STRUCTURAL_TAGS.has(node.tag)) return false;
|
||||||
|
if (!node.children || node.children.length === 0) return false;
|
||||||
|
if (node.name && SLIDE_NAME_RE.test(node.name.toLowerCase())) return true;
|
||||||
|
const rw = node.rect.width ?? node.rect.w ?? 0;
|
||||||
|
const rh = node.rect.height ?? node.rect.h ?? 0;
|
||||||
|
return canvasArea > 0 && (rw * rh) > canvasArea * 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findSlides(node, canvasArea) {
|
||||||
|
if (!node) return [];
|
||||||
|
const results = [];
|
||||||
|
if (node.children) for (const c of node.children) results.push(...findSlides(c, canvasArea));
|
||||||
|
if (results.length > 0) return results;
|
||||||
|
if (isSlideCandidate(node, canvasArea)) results.push(node);
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== 子节点收集 =====
|
||||||
|
function collectSlideChildren(node, slideX, slideY, parentW, depth) {
|
||||||
|
depth = depth || 0;
|
||||||
|
if (!node || !node.rect) return [];
|
||||||
|
const results = [];
|
||||||
|
const nr = node.rect;
|
||||||
|
const rw = nr.width ?? nr.w;
|
||||||
|
const rh = nr.height ?? nr.h;
|
||||||
|
if ((node.type !== 'RECTANGLE' || (node.backgroundImages && node.backgroundImages.length > 0)) && node.layerGroup !== 'comparison') {
|
||||||
|
results.push({
|
||||||
|
id: node.id, type: node.type, name: node.name, tag: node.tag,
|
||||||
|
rect: { x: nr.x - slideX, y: nr.y - slideY, w: rw, h: rh },
|
||||||
|
styles: node.styles || {}, src: node.src || node.text || '',
|
||||||
|
layerGroup: node.layerGroup || '',
|
||||||
|
backgroundImages: node.backgroundImages,
|
||||||
|
parentW: parentW || rw,
|
||||||
|
_depth: depth
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (node.type === 'RECTANGLE' && node.layerGroup === 'comparison' && node.backgroundImages?.length > 0) {
|
||||||
|
results.push({
|
||||||
|
id: node.id, type: 'IMAGE', name: node.name, tag: 'RASTER',
|
||||||
|
rect: { x: nr.x - slideX, y: nr.y - slideY, w: rw, h: rh },
|
||||||
|
styles: node.styles || {}, src: '',
|
||||||
|
layerGroup: node.layerGroup || '',
|
||||||
|
parentW: parentW || rw,
|
||||||
|
backgroundImages: node.backgroundImages,
|
||||||
|
_depth: depth
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (node.children && Array.isArray(node.children)) {
|
||||||
|
for (const child of node.children) {
|
||||||
|
results.push(...collectSlideChildren(child, slideX, slideY, rw, depth + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== 图片下载预处理 =====
|
||||||
|
async function fetchNodeImages(node, pageUrl, assets) {
|
||||||
|
if (!node) return;
|
||||||
|
if (node.tag === 'IMG' && node.attributes?.src && (!node.backgroundImages || node.backgroundImages.length === 0)) {
|
||||||
|
const origin = pageUrl.replace(/^(https?:\/\/[^\/]+).*/, '$1');
|
||||||
|
let src = node.attributes.src;
|
||||||
|
if (src.startsWith('/')) src = origin + src;
|
||||||
|
else if (!src.startsWith('http')) src = pageUrl.replace(/\/[^\/]*$/, '/') + src;
|
||||||
|
const data = await downloadImage(src);
|
||||||
|
if (data) {
|
||||||
|
const key = 'img-' + (node.id || Math.random().toString(36).slice(2));
|
||||||
|
assets[key] = { data };
|
||||||
|
node.backgroundImages = [key];
|
||||||
|
node.type = 'IMAGE';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (node.children) for (const c of node.children) await fetchNodeImages(c, pageUrl, assets);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== 主转化函数 =====
|
||||||
|
async function convertToPptx(input, onProgress) {
|
||||||
|
if (!input || !input.nodes) throw new Error('无效输入:缺少 nodes 字段');
|
||||||
|
|
||||||
|
const inputRoot = input.nodes;
|
||||||
|
const canvas = input.canvas || {};
|
||||||
|
const assets = input.assets || {};
|
||||||
|
const pageUrl = input.source?.url || '';
|
||||||
|
|
||||||
|
// 1. 下载图片
|
||||||
|
if (onProgress) onProgress('下载图片...');
|
||||||
|
await fetchNodeImages(inputRoot, pageUrl, assets);
|
||||||
|
|
||||||
|
// 2. 识别 slide
|
||||||
|
const canvasArea = (canvas.width || 0) * (canvas.height || 0);
|
||||||
|
var slideContainers = findSlides(inputRoot, canvasArea);
|
||||||
|
// fallback:没有匹配的 slide 容器时,用 body 作为 slide
|
||||||
|
if (slideContainers.length === 0) {
|
||||||
|
function findBody(node) {
|
||||||
|
if (!node) return null;
|
||||||
|
if (node.tag === 'BODY' && node.rect) return node;
|
||||||
|
if (node.children) for (const c of node.children) { const r = findBody(c); if (r) return r; }
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const bodyNode = findBody(inputRoot);
|
||||||
|
if (bodyNode) slideContainers = [bodyNode];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 收集子节点
|
||||||
|
const slideGroups = slideContainers.map(s => ({
|
||||||
|
name: s.name,
|
||||||
|
slideRect: { x: s.rect.x, y: s.rect.y, w: s.rect.width ?? s.rect.w, h: s.rect.height ?? s.rect.h },
|
||||||
|
children: collectSlideChildren(s, s.rect.x, s.rect.y, null, 0)
|
||||||
|
}));
|
||||||
|
for (const sg of slideGroups) {
|
||||||
|
if (sg.children.length > 0 && sg.children[0].name === sg.name) sg.children.shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 游离节点
|
||||||
|
const slideSubtreeIds = new Set();
|
||||||
|
for (const sc of slideContainers) collectIds(sc, slideSubtreeIds);
|
||||||
|
|
||||||
|
function collectOrphans(node) {
|
||||||
|
if (!node || !node.rect) return [];
|
||||||
|
const results = [];
|
||||||
|
const nr = node.rect;
|
||||||
|
if (!slideSubtreeIds.has(node.id) && node.type !== 'RECTANGLE' && node.layerGroup !== 'comparison') {
|
||||||
|
results.push({
|
||||||
|
id: node.id, type: node.type, name: node.name, tag: node.tag,
|
||||||
|
rect: { x: nr.x, y: nr.y, w: nr.width ?? nr.w, h: nr.height ?? nr.h },
|
||||||
|
styles: node.styles || {}, src: node.src || node.text || '',
|
||||||
|
layerGroup: node.layerGroup || ''
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (node.children) for (const c of node.children) {
|
||||||
|
if (slideSubtreeIds.has(c.id)) continue;
|
||||||
|
results.push(...collectOrphans(c));
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
const orphans = collectOrphans(inputRoot);
|
||||||
|
|
||||||
|
for (const or of orphans) {
|
||||||
|
if (or.rect.w == null || or.rect.h == null) continue;
|
||||||
|
let best = null, bestDist = Infinity;
|
||||||
|
for (const sg of slideGroups) {
|
||||||
|
const dist = Math.abs(or.rect.y - sg.slideRect.y);
|
||||||
|
if (dist < sg.slideRect.h && dist < bestDist) { bestDist = dist; best = sg; }
|
||||||
|
}
|
||||||
|
if (best) {
|
||||||
|
const relNode = { ...or, rect: { ...or.rect } };
|
||||||
|
relNode.rect.x = or.rect.x - best.slideRect.x;
|
||||||
|
relNode.rect.y = or.rect.y - best.slideRect.y;
|
||||||
|
best.children.push(relNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 映射每个 slide
|
||||||
|
const slides = [];
|
||||||
|
const MAX_H_IN = 55.12;
|
||||||
|
|
||||||
|
for (const sg of slideGroups) {
|
||||||
|
if (onProgress) onProgress('处理 ' + sg.name + '...');
|
||||||
|
let objects = [];
|
||||||
|
let slideBg = null;
|
||||||
|
|
||||||
|
// 背景色
|
||||||
|
const slideNode = slideContainers.find(s => s.name === sg.name);
|
||||||
|
if (slideNode?.styles) {
|
||||||
|
const bg = slideNode.styles.backgroundColor;
|
||||||
|
if (bg && bg !== 'rgba(0, 0, 0, 0)' && bg !== 'transparent') {
|
||||||
|
const hex = rgbaToHex(bg);
|
||||||
|
if (hex) slideBg = { color: hex };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// fallback:canvas 背景色(实际页面背景色)
|
||||||
|
if (!slideBg && canvas.backgroundColor && canvas.backgroundColor !== 'rgba(0, 0, 0, 0)') {
|
||||||
|
const canvasHex = rgbaToHex(canvas.backgroundColor);
|
||||||
|
if (canvasHex) slideBg = { color: canvasHex };
|
||||||
|
}
|
||||||
|
// fallback:从文字颜色推断
|
||||||
|
if (!slideBg && slideNode?.styles) {
|
||||||
|
const textColor = slideNode.styles.color;
|
||||||
|
if (textColor) {
|
||||||
|
const tc = rgbaToHex(textColor);
|
||||||
|
if (tc) {
|
||||||
|
const r = parseInt(tc.slice(0,2), 16);
|
||||||
|
const g = parseInt(tc.slice(2,4), 16);
|
||||||
|
const b = parseInt(tc.slice(4,6), 16);
|
||||||
|
const lum = (r * 299 + g * 587 + b * 114) / 1000;
|
||||||
|
slideBg = lum > 128 ? { color: '1A1A1A' } : { color: 'FAFAFA' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 排序
|
||||||
|
sg.children.sort((a, b) => (parseInt(a.styles?.zIndex) || 0) - (parseInt(b.styles?.zIndex) || 0));
|
||||||
|
|
||||||
|
// 重叠去重
|
||||||
|
const imageRects = sg.children.filter(c => (c.type === 'IMAGE' || (c.type === 'RECTANGLE' && c.backgroundImages?.length > 0)) && c.rect).map(c => ({
|
||||||
|
x: Math.round(c.rect.x), y: Math.round(c.rect.y),
|
||||||
|
w: Math.round(c.rect.w), h: Math.round(c.rect.h),
|
||||||
|
z: parseInt(c.styles?.zIndex) || 0
|
||||||
|
}));
|
||||||
|
const seen = {};
|
||||||
|
const overlapRemove = new Set();
|
||||||
|
for (let i = sg.children.length - 1; i >= 0; i--) {
|
||||||
|
const ci = sg.children[i];
|
||||||
|
if (!ci.rect) continue;
|
||||||
|
if (ci.type === 'TEXT') {
|
||||||
|
const cx = Math.round(ci.rect.x), cy = Math.round(ci.rect.y);
|
||||||
|
const cw = Math.round(ci.rect.w), ch = Math.round(ci.rect.h);
|
||||||
|
const textZ = parseInt(ci.styles?.zIndex) || 0;
|
||||||
|
for (const ir of imageRects) {
|
||||||
|
const exactMatch = Math.abs(cx - ir.x) < 3 && Math.abs(cy - ir.y) < 3 && Math.abs(cw - ir.w) < 3 && Math.abs(ch - ir.h) < 3;
|
||||||
|
const contained = ir.x >= cx - 5 && ir.y >= cy - 5 && ir.x + ir.w <= cx + cw + 5 && ir.y + ir.h <= cy + ch + 5;
|
||||||
|
if ((exactMatch || contained) && ir.z >= textZ) { overlapRemove.add(ci.id); break; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const key = ci.type + ',' + Math.round(ci.rect.x) + ',' + Math.round(ci.rect.y) + ',' + Math.round(ci.rect.w) + ',' + Math.round(ci.rect.h);
|
||||||
|
if (seen[key]) overlapRemove.add(ci.id);
|
||||||
|
else seen[key] = true;
|
||||||
|
}
|
||||||
|
if (overlapRemove.size > 0) sg.children = sg.children.filter(n => !overlapRemove.has(n.id));
|
||||||
|
|
||||||
|
// TEXT 合并
|
||||||
|
const textNodes = sg.children.filter(n => n.type === 'TEXT' && n.src);
|
||||||
|
const merged = new Set();
|
||||||
|
for (let i = 0; i < textNodes.length; i++) {
|
||||||
|
if (merged.has(textNodes[i].id)) continue;
|
||||||
|
const a = textNodes[i];
|
||||||
|
const ra = a.rect;
|
||||||
|
const group = [a];
|
||||||
|
for (let j = i + 1; j < textNodes.length; j++) {
|
||||||
|
if (merged.has(textNodes[j].id)) continue;
|
||||||
|
const b = textNodes[j];
|
||||||
|
const rb = b.rect;
|
||||||
|
if (Math.abs(ra.y - rb.y) > 5) continue;
|
||||||
|
const aRight = ra.x + ra.w;
|
||||||
|
const bRight = rb.x + rb.w;
|
||||||
|
const xAdjacent = Math.abs(aRight - rb.x) < 5 || Math.abs(bRight - ra.x) < 5;
|
||||||
|
const xOverlap = Math.abs(ra.x - rb.x) < 5;
|
||||||
|
if (xAdjacent || xOverlap) { group.push(b); merged.add(b.id); }
|
||||||
|
}
|
||||||
|
if (group.length > 1) {
|
||||||
|
merged.add(a.id);
|
||||||
|
group.sort((m, n) => m.rect.x - n.rect.x);
|
||||||
|
const minX = group[0].rect.x;
|
||||||
|
const maxRight = Math.max(...group.map(g => g.rect.x + g.rect.w));
|
||||||
|
const minTop = Math.min(...group.map(g => g.rect.y));
|
||||||
|
const maxBottom = Math.max(...group.map(g => g.rect.y + g.rect.h));
|
||||||
|
const richText = group.map(g => ({
|
||||||
|
text: g.src,
|
||||||
|
options: {
|
||||||
|
fontSize: Math.round(parseFloat(g.styles.fontSize) || 14),
|
||||||
|
fontFace: (g.styles.fontFamily || 'Arial').split(',')[0].replace(/['"]/g, '').trim(),
|
||||||
|
color: rgbaToHex(g.styles.color) || '000000',
|
||||||
|
bold: parseInt(g.styles.fontWeight) >= 700,
|
||||||
|
italic: g.styles.fontStyle === 'italic'
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
sg.children.push({
|
||||||
|
id: 'merged-' + a.id, type: 'TEXT', _richText: richText,
|
||||||
|
rect: { x: minX, y: minTop, w: maxRight - minX, h: maxBottom - minTop },
|
||||||
|
styles: a.styles || {}, src: group.map(g => g.src).join(''),
|
||||||
|
parentW: a.parentW, _merged: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sg.children = sg.children.filter(n => !merged.has(n.id));
|
||||||
|
|
||||||
|
// contentW
|
||||||
|
var slideW = sg.slideRect.w;
|
||||||
|
var contentW = slideW;
|
||||||
|
var nonOrphans = sg.children.filter(c => c.parentW != null);
|
||||||
|
if (nonOrphans.length > 0) {
|
||||||
|
var minX = Infinity, maxRight = 0;
|
||||||
|
for (const ch of nonOrphans) {
|
||||||
|
if (ch.rect) {
|
||||||
|
if (ch.rect.x < minX) minX = ch.rect.x;
|
||||||
|
var right = ch.rect.x + (ch.rect.w || 0);
|
||||||
|
if (right > maxRight) maxRight = right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (minX < Infinity && maxRight > 0) contentW = maxRight - minX;
|
||||||
|
}
|
||||||
|
|
||||||
|
// clipMap
|
||||||
|
var clipMap = {};
|
||||||
|
function buildClipMap(node, parentClip) {
|
||||||
|
if (!node) return;
|
||||||
|
var myClip = parentClip;
|
||||||
|
if (node.clipped && node.clipRect) {
|
||||||
|
myClip = { x: node.clipRect.x, y: node.clipRect.y, w: node.clipRect.width, h: node.clipRect.height };
|
||||||
|
}
|
||||||
|
clipMap[node.id] = myClip;
|
||||||
|
if (node.children) for (var c of node.children) buildClipMap(c, myClip);
|
||||||
|
}
|
||||||
|
if (slideNode) buildClipMap(slideNode, null);
|
||||||
|
|
||||||
|
// 主循环
|
||||||
|
for (const n of sg.children) {
|
||||||
|
if (n.type === 'RECTANGLE' && !n.backgroundImages?.length) continue;
|
||||||
|
if (n.tag === 'BODY' || n.tag === 'HTML') continue;
|
||||||
|
const r = n.rect;
|
||||||
|
if (r == null || r.w == null || r.h == null) continue;
|
||||||
|
|
||||||
|
var clip = clipMap[n.id];
|
||||||
|
if (clip) {
|
||||||
|
if (r.x > clip.x + clip.w || r.x + r.w < clip.x || r.y > clip.y + clip.h || r.y + r.h < clip.y) continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const S = 1 / 72;
|
||||||
|
const opts = {
|
||||||
|
x: Math.round(r.x * S * 1000) / 1000,
|
||||||
|
y: Math.round(r.y * S * 1000) / 1000,
|
||||||
|
w: Math.round(r.w * S * 1000) / 1000,
|
||||||
|
h: Math.round(r.h * S * 1000) / 1000
|
||||||
|
};
|
||||||
|
|
||||||
|
// TEXT
|
||||||
|
if (n.type === 'TEXT' && n.src) {
|
||||||
|
if (n.styles.display === 'none' || n.styles.visibility === 'hidden') continue;
|
||||||
|
const st = n.styles;
|
||||||
|
const fs = parseFloat(st.fontSize) || 14;
|
||||||
|
opts.fontSize = Math.round(fs);
|
||||||
|
opts.fontFace = (st.fontFamily || 'Arial').split(',')[0].replace(/['"]/g, '').trim();
|
||||||
|
opts.color = rgbaToHex(st.color) || '000000';
|
||||||
|
if (parseInt(st.fontWeight) >= 700 && !n._merged) opts.bold = true;
|
||||||
|
if (st.fontStyle === 'italic') opts.italic = true;
|
||||||
|
if (st.textAlign && st.textAlign !== 'start') opts.align = st.textAlign;
|
||||||
|
if (st.textDecorationLine?.includes('underline')) opts.underline = true;
|
||||||
|
if (st.textDecorationLine?.includes('line-through')) opts.strike = 'sngStrike';
|
||||||
|
if (st.letterSpacing && st.letterSpacing !== 'normal') {
|
||||||
|
var ls = parseFloat(st.letterSpacing);
|
||||||
|
if (!isNaN(ls) && ls !== 0) opts.charSpacing = Math.round(ls * 72 / 96);
|
||||||
|
}
|
||||||
|
if (st.opacity !== undefined && st.opacity !== '' && parseFloat(st.opacity) < 1) {
|
||||||
|
opts.transparency = Math.round((1 - parseFloat(st.opacity)) * 100);
|
||||||
|
}
|
||||||
|
if (st.lineHeight && st.lineHeight !== 'normal') {
|
||||||
|
var lh = parseFloat(st.lineHeight);
|
||||||
|
if (!isNaN(lh) && lh > 0) {
|
||||||
|
var lhRatio = lh / fs;
|
||||||
|
if (lhRatio > 0.5 && lhRatio < 5) opts.lineSpacingMultiple = Math.round(lhRatio * 100) / 100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var textW = n._merged ? r.w : (n.parentW || r.w);
|
||||||
|
if (textW > contentW * 0.8) textW = contentW;
|
||||||
|
if (textW > contentW) textW = contentW;
|
||||||
|
if (textW < 108) textW = 108;
|
||||||
|
var maxW = contentW - r.x;
|
||||||
|
if (maxW < 108) maxW = 108;
|
||||||
|
if (textW > maxW) textW = maxW;
|
||||||
|
opts.w = Math.round(textW / 72 * 1000) / 1000;
|
||||||
|
|
||||||
|
objects.push({ type: 'text', text: n._richText || n.src, options: opts });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// IMAGE
|
||||||
|
if ((n.type === 'IMAGE' || n.type === 'RECTANGLE') && n.backgroundImages?.length > 0) {
|
||||||
|
var imgKey = n.backgroundImages[0];
|
||||||
|
var asset = assets[imgKey];
|
||||||
|
var imgData;
|
||||||
|
if (imgKey.startsWith('data:')) {
|
||||||
|
imgData = imgKey;
|
||||||
|
} else if (asset?.data) {
|
||||||
|
imgData = asset.data;
|
||||||
|
}
|
||||||
|
if (imgData) {
|
||||||
|
objects.push({ type: 'image', options: { x: opts.x, y: opts.y, w: opts.w, h: opts.h, data: imgData } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shape (fill color)
|
||||||
|
if (n.styles.display === 'none' || n.styles.visibility === 'hidden') continue;
|
||||||
|
|
||||||
|
let fillColor = null;
|
||||||
|
let fillTransparency = null;
|
||||||
|
const bg = n.styles.backgroundColor;
|
||||||
|
if (bg && bg !== 'rgba(0, 0, 0, 0)' && bg !== 'transparent') {
|
||||||
|
fillColor = rgbaToHex(bg);
|
||||||
|
const alphaMatch = bg.match(/rgba?\(\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*([\d.]+)\s*\)/);
|
||||||
|
if (alphaMatch) {
|
||||||
|
const alpha = parseFloat(alphaMatch[1]);
|
||||||
|
if (alpha < 1) fillTransparency = Math.round((1 - alpha) * 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!fillColor) {
|
||||||
|
const bgImg = n.styles.backgroundImage;
|
||||||
|
if (bgImg?.startsWith('linear-gradient')) {
|
||||||
|
const m = bgImg.match(/rgb\(\d+,\s*\d+,\s*\d+\)/);
|
||||||
|
if (m) fillColor = rgbaToHex(m[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fillColor) {
|
||||||
|
var shapeOpts = { x: opts.x, y: opts.y, w: opts.w, h: opts.h, fill: { color: fillColor } };
|
||||||
|
if (fillTransparency) shapeOpts.fill.transparency = fillTransparency;
|
||||||
|
if (n.styles.opacity !== undefined && n.styles.opacity !== '' && parseFloat(n.styles.opacity) < 1) {
|
||||||
|
var opTrans = Math.round((1 - parseFloat(n.styles.opacity)) * 100);
|
||||||
|
shapeOpts.fill.transparency = Math.max(shapeOpts.fill.transparency || 0, opTrans);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 圆角(取四角最大值)
|
||||||
|
var brCorners = [
|
||||||
|
n.styles.borderTopLeftRadius,
|
||||||
|
n.styles.borderTopRightRadius,
|
||||||
|
n.styles.borderBottomLeftRadius,
|
||||||
|
n.styles.borderBottomRightRadius
|
||||||
|
].filter(function(v) { return v && v !== '0px'; }).map(function(v) {
|
||||||
|
if (v.includes('%')) return parseFloat(v) / 100;
|
||||||
|
return parseFloat(v) / 72;
|
||||||
|
});
|
||||||
|
if (brCorners.length > 0) {
|
||||||
|
shapeOpts.rectRadius = Math.max.apply(null, brCorners);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 旋转
|
||||||
|
if (n.styles.transform && n.styles.transform !== 'none') {
|
||||||
|
var rotateMatch = n.styles.transform.match(/rotate\(([-\d.]+)deg\)/);
|
||||||
|
if (rotateMatch) {
|
||||||
|
shapeOpts.rotate = parseFloat(rotateMatch[1]);
|
||||||
|
} else {
|
||||||
|
var matrixMatch = n.styles.transform.match(/matrix\(([-\d.]+),\s*([-\d.]+),\s*([-\d.]+),\s*([-\d.]+)/);
|
||||||
|
if (matrixMatch) {
|
||||||
|
var angle = Math.round(Math.atan2(parseFloat(matrixMatch[2]), parseFloat(matrixMatch[1])) * 180 / Math.PI);
|
||||||
|
if (angle !== 0) shapeOpts.rotate = angle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 边框
|
||||||
|
var bw = parseFloat(n.styles.borderTopWidth);
|
||||||
|
var bbw = parseFloat(n.styles.borderBottomWidth);
|
||||||
|
var blw = parseFloat(n.styles.borderLeftWidth);
|
||||||
|
var brw = parseFloat(n.styles.borderRightWidth);
|
||||||
|
var bc = rgbaToHex(n.styles.borderTopColor);
|
||||||
|
var bbc = rgbaToHex(n.styles.borderBottomColor);
|
||||||
|
var blc = rgbaToHex(n.styles.borderLeftColor);
|
||||||
|
var brc = rgbaToHex(n.styles.borderRightColor);
|
||||||
|
var allSameBorder = bw > 0 && bbw > 0 && blw > 0 && brw > 0
|
||||||
|
&& n.styles.borderTopStyle !== 'none'
|
||||||
|
&& bw === bbw && bw === blw && bw === brw
|
||||||
|
&& bc && bc === bbc && bc === blc && bc === brc;
|
||||||
|
if (allSameBorder) {
|
||||||
|
var bc2 = rgbaToHex(n.styles.borderTopColor);
|
||||||
|
if (bc2) shapeOpts.line = { color: bc2, width: bw };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 阴影
|
||||||
|
if (n.styles.boxShadow && n.styles.boxShadow !== 'none') {
|
||||||
|
var shadowMatch = n.styles.boxShadow.match(/rgba?\(([^)]+)\)\s+(\d+)px\s+(\d+)px\s+(\d+)px/);
|
||||||
|
if (shadowMatch) {
|
||||||
|
var shadowParts = shadowMatch[1].split(',').map(function(s) { return s.trim(); });
|
||||||
|
var shadowColor = rgbaToHex('rgb(' + shadowParts.slice(0,3).join(',') + ')') || '999999';
|
||||||
|
var shadowAlpha = shadowParts.length > 3 ? parseFloat(shadowParts[3]) : 0.3;
|
||||||
|
shapeOpts.shadow = { type: 'outer', blur: parseInt(shadowMatch[4]), offset: parseInt(shadowMatch[3]), color: shadowColor, opacity: Math.round(shadowAlpha * 100) / 100 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 形状类型
|
||||||
|
var useShape = 'rect';
|
||||||
|
if (shapeOpts.rectRadius) {
|
||||||
|
var isCircle = shapeOpts.rectRadius >= 0.5 && Math.abs(opts.w - opts.h) < 0.05;
|
||||||
|
useShape = isCircle ? 'ellipse' : 'roundRect';
|
||||||
|
}
|
||||||
|
objects.push({ type: 'shape', shapeName: useShape, options: shapeOpts });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 四周边框线
|
||||||
|
var sides = [
|
||||||
|
{ key: 'borderTopWidth', yOff: 0, hOff: 0 },
|
||||||
|
{ key: 'borderBottomWidth', yOff: 1, hOff: 0 },
|
||||||
|
{ key: 'borderLeftWidth', xOff: 0, wOff: 0 },
|
||||||
|
{ key: 'borderRightWidth', xOff: 1, wOff: 0 }
|
||||||
|
];
|
||||||
|
for (var si = 0; si < sides.length; si++) {
|
||||||
|
var side = sides[si];
|
||||||
|
var sbw = parseFloat(n.styles[side.key]);
|
||||||
|
if (sbw > 0) {
|
||||||
|
var sideKey = side.key.replace('Width', 'Color');
|
||||||
|
var sideStyle = side.key.replace('Width', 'Style');
|
||||||
|
if (n.styles[sideStyle] === 'none') continue;
|
||||||
|
var sbc = rgbaToHex(n.styles[sideKey]);
|
||||||
|
if (!sbc) continue;
|
||||||
|
var lx = side.xOff !== undefined ? opts.x + (side.xOff === 1 ? opts.w : 0) : opts.x;
|
||||||
|
var ly = side.yOff !== undefined ? opts.y + (side.yOff === 1 ? opts.h : 0) : opts.y;
|
||||||
|
var lw = side.wOff !== undefined ? 0 : opts.w;
|
||||||
|
var lh = side.hOff !== undefined ? 0 : opts.h;
|
||||||
|
var lineOpts = { x: lx, y: ly, w: lw || 0.01, h: lh || 0.01, fill: { color: sbc }, line: { type: 'none' } };
|
||||||
|
objects.push({ type: 'shape', shapeName: 'rect', options: lineOpts });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
slides.push({ background: slideBg, objects });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. 画布尺寸:多slide用容器尺寸,单slide用canvas(限制140cm)
|
||||||
|
var sizeSource;
|
||||||
|
if (slideGroups.length > 1) {
|
||||||
|
sizeSource = slideGroups[0].slideRect;
|
||||||
|
} else {
|
||||||
|
sizeSource = { w: canvas.width || 1920, h: Math.min(canvas.height || 1080, MAX_H_IN * 72) };
|
||||||
|
}
|
||||||
|
const sw = sizeSource.w / 72;
|
||||||
|
const sh = Math.min(sizeSource.h / 72, MAX_H_IN);
|
||||||
|
|
||||||
|
return {
|
||||||
|
presentation: { layout: 'CUSTOM', slideWidth: sw, slideHeight: sh },
|
||||||
|
slides
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Schema → PPTX Blob =====
|
||||||
|
async function schemaToPptxBlob(schema) {
|
||||||
|
const pres = new PptxGenJS();
|
||||||
|
pres.defineLayout({ name: 'CUSTOM', width: schema.presentation.slideWidth, height: schema.presentation.slideHeight });
|
||||||
|
pres.layout = 'CUSTOM';
|
||||||
|
|
||||||
|
for (const slideData of schema.slides) {
|
||||||
|
const slide = pres.addSlide();
|
||||||
|
slide.background = { fill: slideData.background?.color || 'FFFFFF' };
|
||||||
|
for (const obj of (slideData.objects || [])) {
|
||||||
|
const o = obj.options || {};
|
||||||
|
if (obj.type === 'text') {
|
||||||
|
const textOpts = {
|
||||||
|
x: o.x, y: o.y, w: o.w, h: o.h,
|
||||||
|
fontSize: o.fontSize || 12, fontFace: o.fontFace || 'Arial',
|
||||||
|
color: o.color || '000000', bold: o.bold || false, align: o.align || 'left'
|
||||||
|
};
|
||||||
|
if (o.underline) textOpts.underline = o.underline;
|
||||||
|
if (o.strike) textOpts.strike = o.strike;
|
||||||
|
if (o.charSpacing) textOpts.charSpacing = o.charSpacing;
|
||||||
|
if (o.transparency !== undefined) textOpts.transparency = o.transparency;
|
||||||
|
if (o.lineSpacingMultiple) textOpts.lineSpacingMultiple = o.lineSpacingMultiple;
|
||||||
|
slide.addText(obj.text || '', textOpts);
|
||||||
|
} else if (obj.type === 'shape') {
|
||||||
|
const st = { rect: 'rect', roundRect: 'roundRect', ellipse: 'ellipse' }[obj.shapeName] || 'rect';
|
||||||
|
const shapeOpts = {
|
||||||
|
x: o.x, y: o.y, w: o.w, h: o.h,
|
||||||
|
fill: o.fill ? { color: o.fill.color, transparency: o.fill.transparency } : undefined
|
||||||
|
};
|
||||||
|
if (o.line) shapeOpts.line = o.line;
|
||||||
|
else shapeOpts.line = { type: 'none' };
|
||||||
|
if (o.shadow) shapeOpts.shadow = o.shadow;
|
||||||
|
if (o.rectRadius) shapeOpts.rectRadius = o.rectRadius;
|
||||||
|
if (o.rotate) shapeOpts.rotate = o.rotate;
|
||||||
|
slide.addShape(pres.ShapeType[st] || pres.ShapeType.rect, shapeOpts);
|
||||||
|
} else if (obj.type === 'image') {
|
||||||
|
slide.addImage({ x: o.x, y: o.y, w: o.w, h: o.h, data: o.data || o.path });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成 PPTX buffer(浏览器用 write,不用 writeFile)
|
||||||
|
const pptxBuffer = await pres.write({ outputType: 'arraybuffer' });
|
||||||
|
|
||||||
|
// 后处理:加 type="custom"
|
||||||
|
try {
|
||||||
|
const zip = await JSZip.loadAsync(pptxBuffer);
|
||||||
|
let presXml = await zip.file('ppt/presentation.xml').async('string');
|
||||||
|
if (presXml.includes('sldSz') && !presXml.includes('type="custom"')) {
|
||||||
|
presXml = presXml.replace(/sldSz cx="([^"]*)" cy="([^"]*)"/, 'sldSz cx="$1" cy="$2" type="custom"');
|
||||||
|
zip.file('ppt/presentation.xml', presXml);
|
||||||
|
return await zip.generateAsync({ type: 'blob', mimeType: 'application/vnd.openxmlformats-officedocument.presentationml.presentation' });
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
return new Blob([pptxBuffer], { type: 'application/vnd.openxmlformats-officedocument.presentationml.presentation' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== 导出 =====
|
||||||
|
// ===== 暴露到全局 =====
|
||||||
|
window.WebToPPT = { convertToPptx, schemaToPptxBlob };
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,27 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" width="128" height="128">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||||
|
<stop offset="0%" style="stop-color:#00D2FF;stop-opacity:1" />
|
||||||
|
<stop offset="100%" style="stop-color:#7C3AED;stop-opacity:1" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<!-- 背景圆 -->
|
||||||
|
<circle cx="64" cy="64" r="60" fill="url(#grad1)"/>
|
||||||
|
|
||||||
|
<!-- 网页图标 -->
|
||||||
|
<rect x="24" y="32" width="80" height="60" rx="4" fill="#fff" opacity="0.95"/>
|
||||||
|
|
||||||
|
<!-- 网页内容线 -->
|
||||||
|
<rect x="32" y="44" width="40" height="4" rx="2" fill="#00D2FF"/>
|
||||||
|
<rect x="32" y="54" width="64" height="3" rx="1.5" fill="#E2E8F0"/>
|
||||||
|
<rect x="32" y="62" width="56" height="3" rx="1.5" fill="#E2E8F0"/>
|
||||||
|
<rect x="32" y="70" width="48" height="3" rx="1.5" fill="#E2E8F0"/>
|
||||||
|
|
||||||
|
<!-- 箭头 -->
|
||||||
|
<path d="M72 80 L88 96 L104 80" stroke="#fff" stroke-width="4" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
|
||||||
|
<!-- Pixso 标志 -->
|
||||||
|
<circle cx="96" cy="56" r="16" fill="#fff"/>
|
||||||
|
<text x="96" y="61" text-anchor="middle" font-size="14" font-weight="bold" fill="url(#grad1)">P</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16">
|
||||||
|
<rect width="16" height="16" rx="2" fill="#00D2FF"/>
|
||||||
|
<text x="8" y="12" text-anchor="middle" font-size="10" font-weight="bold" fill="white">P</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 244 B |
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32">
|
||||||
|
<rect width="32" height="32" rx="4" fill="#00D2FF"/>
|
||||||
|
<text x="16" y="22" text-anchor="middle" font-size="14" font-weight="bold" fill="white">P</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 245 B |
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48" height="48">
|
||||||
|
<rect width="48" height="48" rx="6" fill="#00D2FF"/>
|
||||||
|
<text x="24" y="32" text-anchor="middle" font-size="20" font-weight="bold" fill="white">P</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 245 B |
Binary file not shown.
|
After Width: | Height: | Size: 112 KiB |
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"manifest_version": 3,
|
||||||
|
"name": "Web to PPT",
|
||||||
|
"version": "2.0.0",
|
||||||
|
"description": "Capture a webpage and export it as an editable PPTX presentation.",
|
||||||
|
"permissions": ["activeTab", "scripting", "downloads", "storage"],
|
||||||
|
"host_permissions": ["<all_urls>"],
|
||||||
|
"background": {
|
||||||
|
"service_worker": "background.js"
|
||||||
|
},
|
||||||
|
"action": {
|
||||||
|
"default_title": "Web to PPT",
|
||||||
|
"default_popup": "popup.html",
|
||||||
|
"default_icon": {
|
||||||
|
"16": "logo/plugin-logo.png",
|
||||||
|
"32": "logo/plugin-logo.png",
|
||||||
|
"48": "logo/plugin-logo.png",
|
||||||
|
"128": "logo/plugin-logo.png"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"icons": {
|
||||||
|
"16": "logo/plugin-logo.png",
|
||||||
|
"32": "logo/plugin-logo.png",
|
||||||
|
"48": "logo/plugin-logo.png",
|
||||||
|
"128": "logo/plugin-logo.png"
|
||||||
|
},
|
||||||
|
"web_accessible_resources": [
|
||||||
|
{
|
||||||
|
"resources": ["capture.js", "runner.js", "lib-pptxgen.js", "convert-browser.js"],
|
||||||
|
"matches": ["<all_urls>"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,379 @@
|
|||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
background: transparent !important;
|
||||||
|
border-radius: 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
width: 320px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: transparent !important;
|
||||||
|
color: #1a1a2e;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
|
||||||
|
padding: 0;
|
||||||
|
width: 320px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shell {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid rgba(10, 10, 18, 0.14);
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: none;
|
||||||
|
overflow: hidden;
|
||||||
|
width: 320px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 16px 20px 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-title {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 4px 12px rgba(10, 10, 18, 0.12);
|
||||||
|
display: block;
|
||||||
|
height: 28px;
|
||||||
|
margin-left: -2px;
|
||||||
|
object-fit: cover;
|
||||||
|
width: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
color: #1a1a2e;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-badge {
|
||||||
|
background: #f2f4ff;
|
||||||
|
border: 1px solid #dfe5ff;
|
||||||
|
border-radius: 999px;
|
||||||
|
color: #2450ff;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1;
|
||||||
|
padding: 3px 6px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn {
|
||||||
|
align-items: center;
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 12px;
|
||||||
|
color: #999;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
font-size: 18px;
|
||||||
|
height: 24px;
|
||||||
|
justify-content: center;
|
||||||
|
line-height: 1;
|
||||||
|
transition: background 0.2s, color 0.2s;
|
||||||
|
width: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn:hover {
|
||||||
|
background: #f4f4f6;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-row {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-label {
|
||||||
|
color: #1a1a2e;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-switch {
|
||||||
|
display: inline-block;
|
||||||
|
height: 26px;
|
||||||
|
position: relative;
|
||||||
|
width: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-switch input {
|
||||||
|
height: 0;
|
||||||
|
opacity: 0;
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-slider {
|
||||||
|
background-color: #e4e4e4;
|
||||||
|
border-radius: 999px;
|
||||||
|
bottom: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
left: 0;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
transition: 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-slider::before {
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 50%;
|
||||||
|
bottom: 3px;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
||||||
|
content: "";
|
||||||
|
height: 20px;
|
||||||
|
left: 3px;
|
||||||
|
position: absolute;
|
||||||
|
transition: 0.3s;
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:checked + .toggle-slider {
|
||||||
|
background-color: #1a1a2e;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:checked + .toggle-slider::before {
|
||||||
|
transform: translateX(22px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-select {
|
||||||
|
appearance: none;
|
||||||
|
background: #fff;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23666' d='M6 8L1 3h10z'/%3E%3C/svg%3E");
|
||||||
|
background-position: right 10px center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
border: 1px solid #e4e4e4;
|
||||||
|
border-radius: 9px;
|
||||||
|
color: #1a1a2e;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
min-width: 80px;
|
||||||
|
padding: 7px 30px 7px 13px;
|
||||||
|
transition: border-color 0.18s, box-shadow 0.18s, background 0.18s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-select:focus {
|
||||||
|
border-color: #2450ff;
|
||||||
|
box-shadow: 0 0 0 4px rgba(36, 80, 255, 0.1);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mode-select {
|
||||||
|
min-width: 116px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.width-input-wrap {
|
||||||
|
align-items: center;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #e4e4e4;
|
||||||
|
border-radius: 9px;
|
||||||
|
display: flex;
|
||||||
|
height: 36px;
|
||||||
|
min-width: 116px;
|
||||||
|
padding: 0 10px 0 12px;
|
||||||
|
transition: border-color 0.18s, box-shadow 0.18s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.width-input-wrap:focus-within {
|
||||||
|
border-color: #2450ff;
|
||||||
|
box-shadow: 0 0 0 4px rgba(36, 80, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.width-input {
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
color: #1a1a2e;
|
||||||
|
font: inherit;
|
||||||
|
font-size: 14px;
|
||||||
|
min-width: 0;
|
||||||
|
outline: none;
|
||||||
|
text-align: right;
|
||||||
|
width: 68px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.width-input.invalid {
|
||||||
|
color: #ef4444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.width-unit {
|
||||||
|
color: #999;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
color: #999;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.6;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.capture-btn {
|
||||||
|
background: #1a1a2e;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 9px;
|
||||||
|
color: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
padding: 14px 24px;
|
||||||
|
box-shadow: 0 8px 20px rgba(26, 26, 46, 0.12);
|
||||||
|
transition: background 0.2s, box-shadow 0.2s, transform 0.2s;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.capture-btn:hover {
|
||||||
|
background: #2d2d44;
|
||||||
|
box-shadow: 0 10px 24px rgba(26, 26, 46, 0.16);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.capture-btn:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.capture-btn:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.6;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.capture-btn.loading {
|
||||||
|
background: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.capture-btn.success {
|
||||||
|
background: #10b981;
|
||||||
|
}
|
||||||
|
|
||||||
|
.capture-btn.error {
|
||||||
|
background: #ef4444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-btn {
|
||||||
|
background: #f4f4f6;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 9px;
|
||||||
|
color: #1a1a2e;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-top: 10px;
|
||||||
|
padding: 13px 24px;
|
||||||
|
transition: background 0.2s, box-shadow 0.2s, transform 0.2s;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-btn:hover {
|
||||||
|
background: #ececf1;
|
||||||
|
box-shadow: 0 8px 18px rgba(10, 10, 18, 0.08);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-btn:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-btn:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.6;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-btn.loading {
|
||||||
|
background: #e7e7ee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status {
|
||||||
|
margin-top: 16px;
|
||||||
|
padding: 12px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar {
|
||||||
|
background: #f0f0f0;
|
||||||
|
border-radius: 999px;
|
||||||
|
height: 4px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-fill {
|
||||||
|
background: linear-gradient(90deg, #00d2ff, #7c3aed);
|
||||||
|
border-radius: 999px;
|
||||||
|
height: 100%;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-text {
|
||||||
|
color: #666;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-link {
|
||||||
|
align-items: center;
|
||||||
|
border: 1px solid #e6e8f2;
|
||||||
|
border-radius: 9px;
|
||||||
|
color: #2450ff;
|
||||||
|
display: flex;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: 16px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: background 0.18s, border-color 0.18s, color 0.18s;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-link:hover {
|
||||||
|
background: #f6f8ff;
|
||||||
|
border-color: #dfe5ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
align-items: center;
|
||||||
|
background: #fff;
|
||||||
|
border-top: 1px solid #f0f0f0;
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 12px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.author {
|
||||||
|
color: #666;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.support-email {
|
||||||
|
color: #888;
|
||||||
|
font-size: 10px;
|
||||||
|
line-height: 1.35;
|
||||||
|
min-width: 0;
|
||||||
|
text-align: right;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.support-email:hover {
|
||||||
|
color: #2450ff;
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Web to PPT</title>
|
||||||
|
<link rel="stylesheet" href="popup.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main class="shell">
|
||||||
|
<div class="header">
|
||||||
|
<div class="logo-title">
|
||||||
|
<img src="logo/plugin-logo.png" alt="" class="logo">
|
||||||
|
<span class="title">Web to PPT</span>
|
||||||
|
<span class="version-badge">v2.0</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<div class="setting-row">
|
||||||
|
<span class="setting-label">采集宽度</span>
|
||||||
|
<label class="width-input-wrap">
|
||||||
|
<input class="width-input" id="captureWidth" type="text" inputmode="numeric" placeholder="自动">
|
||||||
|
<span class="width-unit">px</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="capture-btn" id="exportBtn" type="button">
|
||||||
|
<span id="btnText">导出 PPTX</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="status" id="status" hidden>
|
||||||
|
<div class="progress-bar"><div class="progress-fill" id="progressFill"></div></div>
|
||||||
|
<span class="status-text" id="statusText">准备中...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
<span class="author">html2pptx engine</span>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script src="lib-pptxgen.js"></script>
|
||||||
|
<script src="convert-browser.js"></script>
|
||||||
|
<script src="popup.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
const SETTINGS_KEY = 'webToPixsoSettings';
|
||||||
|
const DEFAULT_SETTINGS = { useProxy: false, concurrency: '8', captureMode: 'mixed', captureWidth: null };
|
||||||
|
const MIN_CAPTURE_WIDTH = 320;
|
||||||
|
const MAX_CAPTURE_WIDTH = 3840;
|
||||||
|
|
||||||
|
const captureWidthInput = document.getElementById('captureWidth');
|
||||||
|
const exportBtn = document.getElementById('exportBtn');
|
||||||
|
const btnText = document.getElementById('btnText');
|
||||||
|
const status = document.getElementById('status');
|
||||||
|
const statusText = document.getElementById('statusText');
|
||||||
|
const progressFill = document.getElementById('progressFill');
|
||||||
|
|
||||||
|
function normalizeCaptureWidth(value, fallback = null) {
|
||||||
|
const number = Number.parseInt(String(value || '').replace(/\D+/g, ''), 10);
|
||||||
|
if (!Number.isFinite(number)) return fallback;
|
||||||
|
return Math.max(MIN_CAPTURE_WIDTH, Math.min(MAX_CAPTURE_WIDTH, number));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getCurrentViewportWidth() {
|
||||||
|
try {
|
||||||
|
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
||||||
|
if (!tab?.id) return null;
|
||||||
|
const [{ result }] = await chrome.scripting.executeScript({
|
||||||
|
target: { tabId: tab.id },
|
||||||
|
func: () => Math.round(window.innerWidth || document.documentElement.clientWidth || 0)
|
||||||
|
});
|
||||||
|
return normalizeCaptureWidth(result, null);
|
||||||
|
} catch { return null; }
|
||||||
|
}
|
||||||
|
|
||||||
|
function setProgress(percent, text) {
|
||||||
|
status.hidden = false;
|
||||||
|
progressFill.style.width = `${Math.max(0, Math.min(100, percent))}%`;
|
||||||
|
statusText.textContent = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setBusy(isBusy) {
|
||||||
|
exportBtn.disabled = isBusy;
|
||||||
|
exportBtn.classList.toggle('loading', isBusy);
|
||||||
|
exportBtn.classList.remove('success', 'error');
|
||||||
|
btnText.textContent = isBusy ? '处理中...' : '导出 PPTX';
|
||||||
|
}
|
||||||
|
|
||||||
|
function setResult(kind, text) {
|
||||||
|
exportBtn.classList.remove('loading', 'success', 'error');
|
||||||
|
exportBtn.classList.add(kind);
|
||||||
|
btnText.textContent = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function exportPptx() {
|
||||||
|
setBusy(true);
|
||||||
|
setProgress(5, '准备采集...');
|
||||||
|
|
||||||
|
const captureWidth = normalizeCaptureWidth(captureWidthInput.value, null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. 采集
|
||||||
|
setProgress(15, '正在采集页面...');
|
||||||
|
const captureResult = await chrome.runtime.sendMessage({
|
||||||
|
type: 'WEB_TO_PPT_CAPTURE_START',
|
||||||
|
options: { captureWidth }
|
||||||
|
});
|
||||||
|
if (!captureResult || !captureResult.ok) throw new Error(captureResult?.error || '采集失败');
|
||||||
|
|
||||||
|
// 2. 获取数据
|
||||||
|
setProgress(40, '采集完成,获取数据...');
|
||||||
|
const dataResult = await chrome.runtime.sendMessage({ type: 'WEB_TO_PPT_GET_DATA' });
|
||||||
|
if (!dataResult || !dataResult.ok) throw new Error(dataResult?.error || '获取数据失败');
|
||||||
|
|
||||||
|
// 3. 转化
|
||||||
|
setProgress(50, '正在生成 PPTX...');
|
||||||
|
const schema = await WebToPPT.convertToPptx(dataResult.data, (msg) => setProgress(60, msg));
|
||||||
|
// Debug: check rotation
|
||||||
|
const rotateCount = schema.slides.reduce((acc, s) => acc + s.objects.filter(o => o.options?.rotate).length, 0);
|
||||||
|
console.log('[WebToPPT] schema:', schema.slides.length, 'slides,', rotateCount, 'shapes with rotation');
|
||||||
|
|
||||||
|
// 4. 生成 PPTX
|
||||||
|
setProgress(85, '正在打包...');
|
||||||
|
const blob = await WebToPPT.schemaToPptxBlob(schema);
|
||||||
|
|
||||||
|
// 5. 转 base64
|
||||||
|
const buffer = await blob.arrayBuffer();
|
||||||
|
const bytes = new Uint8Array(buffer);
|
||||||
|
let binary = '';
|
||||||
|
for (let i = 0; i < bytes.length; i += 0x8000) {
|
||||||
|
binary += String.fromCharCode(...bytes.subarray(i, i + 0x8000));
|
||||||
|
}
|
||||||
|
const base64 = btoa(binary);
|
||||||
|
|
||||||
|
// 6. 存到 storage,让 background 下载
|
||||||
|
setProgress(95, '正在下载...');
|
||||||
|
const title = dataResult.data.source?.title || 'webpage';
|
||||||
|
const safeTitle = title.replace(/[\\/:*?"<>|]+/g, '-').replace(/\s+/g, '-').slice(0, 64) || 'webpage';
|
||||||
|
const filename = `web-to-ppt/${safeTitle}-${Date.now()}.pptx`;
|
||||||
|
|
||||||
|
await chrome.storage.session.set({ pptxBase64: base64, pptxFilename: filename });
|
||||||
|
const dlResult = await chrome.runtime.sendMessage({ type: 'WEB_TO_PPT_DOWNLOAD' });
|
||||||
|
if (!dlResult || !dlResult.ok) throw new Error(dlResult?.error || '下载失败');
|
||||||
|
|
||||||
|
setProgress(100, '导出完成');
|
||||||
|
setResult('success', '已下载到 Downloads/web-to-ppt/');
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
setProgress(0, error.message || String(error));
|
||||||
|
setResult('error', '导出失败');
|
||||||
|
setTimeout(() => {
|
||||||
|
setBusy(false);
|
||||||
|
status.hidden = true;
|
||||||
|
progressFill.style.width = '0';
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
|
const currentWidth = await getCurrentViewportWidth();
|
||||||
|
captureWidthInput.value = String(currentWidth || '');
|
||||||
|
});
|
||||||
|
|
||||||
|
exportBtn.addEventListener('click', exportPptx);
|
||||||
|
captureWidthInput.addEventListener('input', () => {
|
||||||
|
captureWidthInput.value = captureWidthInput.value.replace(/\D+/g, '');
|
||||||
|
});
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
(function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
|
||||||
|
function freezeAnimations() {
|
||||||
|
const style = document.createElement("style");
|
||||||
|
style.id = "__web-to-ppt-freeze";
|
||||||
|
style.textContent = `
|
||||||
|
*, *::before, *::after {
|
||||||
|
animation-play-state: paused !important;
|
||||||
|
transition-duration: 0s !important;
|
||||||
|
scroll-behavior: auto !important;
|
||||||
|
}
|
||||||
|
.slick-track,
|
||||||
|
.swiper-wrapper {
|
||||||
|
transition-duration: 0s !important;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
document.documentElement.appendChild(style);
|
||||||
|
|
||||||
|
for (const media of document.querySelectorAll("video, audio")) {
|
||||||
|
try {
|
||||||
|
media.pause();
|
||||||
|
} catch {
|
||||||
|
// Ignore media elements that cannot be controlled by content scripts.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function stabilizeCarousels() {
|
||||||
|
try {
|
||||||
|
if (window.jQuery) {
|
||||||
|
window.jQuery(".slick-slider").each((_, element) => {
|
||||||
|
try {
|
||||||
|
window.jQuery(element).slick("slickPause");
|
||||||
|
window.jQuery(element).slick("slickGoTo", 0, true);
|
||||||
|
} catch {
|
||||||
|
// Some pages expose slick classes without the jQuery plugin instance.
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Best effort only.
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const element of document.querySelectorAll(".swiper, .swiper-container")) {
|
||||||
|
try {
|
||||||
|
if (element.swiper) {
|
||||||
|
element.swiper.autoplay?.stop?.();
|
||||||
|
element.swiper.slideToLoop?.(0, 0, false);
|
||||||
|
element.swiper.slideTo?.(0, 0, false);
|
||||||
|
element.swiper.update?.();
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Keep DOM capture running even if a carousel API rejects.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const wrapper of document.querySelectorAll(".swiper-wrapper, .slick-track")) {
|
||||||
|
wrapper.style.transitionDuration = "0s";
|
||||||
|
wrapper.style.animationPlayState = "paused";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function waitForStableTopLayer(timeout = 2500) {
|
||||||
|
const start = Date.now();
|
||||||
|
const selector = [
|
||||||
|
"header",
|
||||||
|
"nav",
|
||||||
|
"[role='navigation']",
|
||||||
|
"[class*='header' i]",
|
||||||
|
"[class*='nav' i]",
|
||||||
|
"[class*='top' i]"
|
||||||
|
].join(",");
|
||||||
|
|
||||||
|
while (Date.now() - start < timeout) {
|
||||||
|
const candidates = Array.from(document.querySelectorAll(selector));
|
||||||
|
const hasVisibleCandidate = candidates.some(element => {
|
||||||
|
const rect = element.getBoundingClientRect();
|
||||||
|
const style = window.getComputedStyle(element);
|
||||||
|
return rect.width > 20 &&
|
||||||
|
rect.height > 10 &&
|
||||||
|
rect.bottom >= 0 &&
|
||||||
|
rect.top < Math.max(160, window.innerHeight * 0.2) &&
|
||||||
|
style.display !== "none" &&
|
||||||
|
style.visibility !== "hidden" &&
|
||||||
|
Number(style.opacity || 1) > 0;
|
||||||
|
});
|
||||||
|
if (hasVisibleCandidate || document.readyState === "complete") {
|
||||||
|
await delay(300);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await delay(120);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function scrollToLoadLazyContent() {
|
||||||
|
const maxScroll = Math.max(
|
||||||
|
document.documentElement.scrollHeight,
|
||||||
|
document.body.scrollHeight
|
||||||
|
) - window.innerHeight;
|
||||||
|
|
||||||
|
if (maxScroll <= 0) return;
|
||||||
|
|
||||||
|
const step = Math.max(480, Math.floor(window.innerHeight * 0.8));
|
||||||
|
for (let y = 0; y <= maxScroll; y += step) {
|
||||||
|
window.scrollTo(0, Math.min(y, maxScroll));
|
||||||
|
await delay(160);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.scrollTo(0, maxScroll);
|
||||||
|
await delay(220);
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
await delay(220);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function waitForImages(timeout = 3500) {
|
||||||
|
const images = Array.from(document.images || []);
|
||||||
|
await Promise.race([
|
||||||
|
Promise.allSettled(images.map(image => {
|
||||||
|
if (image.complete) return Promise.resolve();
|
||||||
|
return new Promise(resolve => {
|
||||||
|
image.addEventListener("load", resolve, { once: true });
|
||||||
|
image.addEventListener("error", resolve, { once: true });
|
||||||
|
});
|
||||||
|
})),
|
||||||
|
delay(timeout)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function waitForFonts(timeout = 3000) {
|
||||||
|
if (!document.fonts?.ready) return;
|
||||||
|
await Promise.race([document.fonts.ready, delay(timeout)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.__webToPPTRunCapture = async function runCapture(options = {}) {
|
||||||
|
if (!window.__webToPPTCapture) {
|
||||||
|
throw new Error("采集引擎未加载");
|
||||||
|
}
|
||||||
|
|
||||||
|
freezeAnimations();
|
||||||
|
stabilizeCarousels();
|
||||||
|
await waitForStableTopLayer();
|
||||||
|
stabilizeCarousels();
|
||||||
|
await scrollToLoadLazyContent();
|
||||||
|
stabilizeCarousels();
|
||||||
|
await waitForImages();
|
||||||
|
await waitForFonts();
|
||||||
|
stabilizeCarousels();
|
||||||
|
await delay(200);
|
||||||
|
|
||||||
|
return window.__webToPPTCapture({
|
||||||
|
useProxy: Boolean(options.useProxy),
|
||||||
|
concurrency: options.concurrency || "8",
|
||||||
|
captureMode: options.captureMode === "editable" ? "editable" : "mixed",
|
||||||
|
captureWidth: options.captureWidth,
|
||||||
|
selectionId: options.selectionId,
|
||||||
|
selectionWidth: options.selectionWidth
|
||||||
|
});
|
||||||
|
};
|
||||||
|
})();
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
entry: './web-to-ppt/convert-browser.js',
|
||||||
|
output: {
|
||||||
|
filename: 'convert-bundle.js',
|
||||||
|
path: path.resolve(__dirname, 'web-to-ppt'),
|
||||||
|
library: { type: 'module' },
|
||||||
|
module: true
|
||||||
|
},
|
||||||
|
experiments: { outputModule: true },
|
||||||
|
resolve: {
|
||||||
|
fallback: {
|
||||||
|
fs: false,
|
||||||
|
path: false,
|
||||||
|
https: false,
|
||||||
|
http: false,
|
||||||
|
child_process: false,
|
||||||
|
stream: false,
|
||||||
|
buffer: require.resolve('buffer/'),
|
||||||
|
process: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
externals: {
|
||||||
|
// pptxgenjs 和 jszip 通过 import() 动态加载,不打包
|
||||||
|
},
|
||||||
|
mode: 'production',
|
||||||
|
target: 'web'
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user