Clear test plugins from public Codex marketplace
Constraint: Public marketplace should only expose production-ready plugins while KeyInfo now publishes internal skills explicitly. Rejected: Keeping eapil-prefixed test plugins | They conflict with future KeyInfo-published skills and confuse plugin identity. Confidence: high Scope-risk: narrow Directive: Keep plugin IDs stable and avoid eapil-prefixed test fixtures in the public market. Tested: python scripts/validate_marketplace.py; CODEX_HOME temp codex plugin marketplace add .; CODEX_HOME temp codex plugin add docx@eapil-skill-market Not-tested: Remote Codex Desktop install after push.
This commit is contained in:
@@ -4,54 +4,6 @@
|
|||||||
"displayName": "EAPIL 技能市场"
|
"displayName": "EAPIL 技能市场"
|
||||||
},
|
},
|
||||||
"plugins": [
|
"plugins": [
|
||||||
{
|
|
||||||
"name": "eapil-pdf",
|
|
||||||
"source": {
|
|
||||||
"source": "local",
|
|
||||||
"path": "./plugins/codex/plugins/eapil-pdf"
|
|
||||||
},
|
|
||||||
"policy": {
|
|
||||||
"installation": "AVAILABLE",
|
|
||||||
"authentication": "ON_INSTALL"
|
|
||||||
},
|
|
||||||
"category": "文档处理"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "eapil-pptx",
|
|
||||||
"source": {
|
|
||||||
"source": "local",
|
|
||||||
"path": "./plugins/codex/plugins/eapil-pptx"
|
|
||||||
},
|
|
||||||
"policy": {
|
|
||||||
"installation": "AVAILABLE",
|
|
||||||
"authentication": "ON_INSTALL"
|
|
||||||
},
|
|
||||||
"category": "文档处理"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "eapil-xlsx",
|
|
||||||
"source": {
|
|
||||||
"source": "local",
|
|
||||||
"path": "./plugins/codex/plugins/eapil-xlsx"
|
|
||||||
},
|
|
||||||
"policy": {
|
|
||||||
"installation": "AVAILABLE",
|
|
||||||
"authentication": "ON_INSTALL"
|
|
||||||
},
|
|
||||||
"category": "数据分析"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "eapil-threejs",
|
|
||||||
"source": {
|
|
||||||
"source": "local",
|
|
||||||
"path": "./plugins/codex/plugins/eapil-threejs"
|
|
||||||
},
|
|
||||||
"policy": {
|
|
||||||
"installation": "AVAILABLE",
|
|
||||||
"authentication": "ON_INSTALL"
|
|
||||||
},
|
|
||||||
"category": "开发工具"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "superpowers",
|
"name": "superpowers",
|
||||||
"source": {
|
"source": {
|
||||||
|
|||||||
@@ -19,14 +19,11 @@
|
|||||||
|
|
||||||
插件目录:
|
插件目录:
|
||||||
|
|
||||||
- `plugins/codex/plugins/eapil-pdf`
|
- `plugins/codex/plugins/docx`
|
||||||
- `plugins/codex/plugins/eapil-pptx`
|
|
||||||
- `plugins/codex/plugins/eapil-xlsx`
|
|
||||||
- `plugins/codex/plugins/eapil-threejs`
|
|
||||||
- `plugins/codex/plugins/superpowers`
|
- `plugins/codex/plugins/superpowers`
|
||||||
- `plugins/codex/plugins/ui-ux-pro-max`
|
- `plugins/codex/plugins/ui-ux-pro-max`
|
||||||
|
|
||||||
Three.js 相关 skill 统一放在 `eapil-threejs` 一个插件内,不再拆成多个 `eapil-threejs-*` 插件。这样用户只需要安装一个 Three.js 技能包,安装后 Codex 仍然可以在包内按具体 skill 选择场景、材质、灯光、交互、动画等工作流。
|
当前市场只保留正式公开插件。历史测试插件不应继续出现在 marketplace 或 `plugins/codex/plugins/` 下,下一次发布或同步时也应移除未被索引的测试目录,避免用户安装到过期能力。
|
||||||
|
|
||||||
## 第三方 skill 引入流程
|
## 第三方 skill 引入流程
|
||||||
|
|
||||||
@@ -64,8 +61,8 @@ powershell -ExecutionPolicy Bypass -File .\scripts\sync_external_plugins.ps1 -On
|
|||||||
|
|
||||||
新增自制 skill 时优先判断分发粒度:
|
新增自制 skill 时优先判断分发粒度:
|
||||||
|
|
||||||
- 如果它是独立领域能力,创建一个新插件,例如 `eapil-pdf`。
|
- 如果它是独立领域能力,创建一个新插件,例如 `docx` 或新的英文 kebab-case 插件名。
|
||||||
- 如果它是某个已有领域的一部分,放进已有插件的 `skills/` 下,例如 Three.js 的新增材质、加载、调试类能力放进 `eapil-threejs`。
|
- 如果它是某个已有领域的一部分,放进已有插件的 `skills/` 下。一个目录下有多个相关 skill 时仍视为一个插件,用户安装和卸载以插件为单位。
|
||||||
|
|
||||||
自制插件最低结构:
|
自制插件最低结构:
|
||||||
|
|
||||||
@@ -105,10 +102,10 @@ python C:\Users\cmx27\.codex\skills\.system\plugin-creator\scripts\validate_plug
|
|||||||
需要验证 Codex 安装行为时:
|
需要验证 Codex 安装行为时:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
codex plugin marketplace add . --json
|
codex plugin marketplace add .
|
||||||
codex plugin list --marketplace eapil-skill-market --available --json
|
codex plugin list --marketplace eapil-skill-market
|
||||||
codex plugin add <plugin-name>@eapil-skill-market --json
|
codex plugin add <plugin-name>@eapil-skill-market
|
||||||
codex plugin remove <plugin-name>@eapil-skill-market --json
|
codex plugin remove <plugin-name>@eapil-skill-market
|
||||||
```
|
```
|
||||||
|
|
||||||
本地测试安装后记得移除测试插件,避免 Codex Desktop 显示为已启用而误判为市场默认安装。
|
本地测试安装后记得移除测试插件,避免 Codex Desktop 显示为已启用而误判为市场默认安装。
|
||||||
|
|||||||
@@ -23,44 +23,41 @@ plugins/codex
|
|||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
codex plugin marketplace add https://git.playones.com/arechen/EapilSkillMarket.git --ref main
|
codex plugin marketplace add https://git.playones.com/arechen/EapilSkillMarket.git --ref main
|
||||||
codex plugin add eapil-pdf@eapil-skill-market
|
codex plugin add docx@eapil-skill-market
|
||||||
```
|
```
|
||||||
|
|
||||||
Sparse CLI form:
|
Sparse CLI form:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
codex plugin marketplace add https://git.playones.com/arechen/EapilSkillMarket.git --ref main --sparse .agents --sparse plugins/codex
|
codex plugin marketplace add https://git.playones.com/arechen/EapilSkillMarket.git --ref main --sparse .agents --sparse plugins/codex
|
||||||
codex plugin add eapil-xlsx@eapil-skill-market
|
codex plugin add superpowers@eapil-skill-market
|
||||||
```
|
```
|
||||||
|
|
||||||
For local testing before pushing:
|
For local testing before pushing:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
codex plugin marketplace add .
|
codex plugin marketplace add .
|
||||||
codex plugin add eapil-pptx@eapil-skill-market
|
codex plugin add ui-ux-pro-max@eapil-skill-market
|
||||||
```
|
```
|
||||||
|
|
||||||
Start a new Codex thread after installing so the new skills are loaded into context.
|
Start a new Codex thread after installing so the new skills are loaded into context.
|
||||||
|
|
||||||
## Plugins
|
## Plugins
|
||||||
|
|
||||||
- `eapil-pdf`
|
- `docx`
|
||||||
- `eapil-pptx`
|
|
||||||
- `eapil-xlsx`
|
|
||||||
- `eapil-threejs`
|
|
||||||
- `superpowers`
|
- `superpowers`
|
||||||
- `ui-ux-pro-max`
|
- `ui-ux-pro-max`
|
||||||
|
|
||||||
List available plugins:
|
List available plugins:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
codex plugin list --marketplace eapil-skill-market --available
|
codex plugin list --marketplace eapil-skill-market
|
||||||
```
|
```
|
||||||
|
|
||||||
Remove an installed plugin:
|
Remove an installed plugin:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
codex plugin remove eapil-pdf@eapil-skill-market
|
codex plugin remove docx@eapil-skill-market
|
||||||
```
|
```
|
||||||
|
|
||||||
## External sync
|
## External sync
|
||||||
@@ -98,17 +95,9 @@ Periodic sync should call the script directly from a maintenance process, cron j
|
|||||||
.agents/plugins/marketplace.json
|
.agents/plugins/marketplace.json
|
||||||
plugins/codex/
|
plugins/codex/
|
||||||
.agents/plugins/marketplace.json
|
.agents/plugins/marketplace.json
|
||||||
plugins/eapil-pdf/
|
plugins/docx/
|
||||||
.codex-plugin/plugin.json
|
.codex-plugin/plugin.json
|
||||||
skills/pdf/
|
skills/docx/
|
||||||
plugins/eapil-xlsx/
|
|
||||||
.codex-plugin/plugin.json
|
|
||||||
skills/xlsx/
|
|
||||||
plugins/eapil-threejs/
|
|
||||||
.codex-plugin/plugin.json
|
|
||||||
skills/threejs-fundamentals/
|
|
||||||
skills/threejs-materials/
|
|
||||||
skills/threejs-shaders/
|
|
||||||
plugins/superpowers/
|
plugins/superpowers/
|
||||||
.codex-plugin/plugin.json
|
.codex-plugin/plugin.json
|
||||||
skills/
|
skills/
|
||||||
|
|||||||
@@ -4,54 +4,6 @@
|
|||||||
"displayName": "EAPIL 技能市场"
|
"displayName": "EAPIL 技能市场"
|
||||||
},
|
},
|
||||||
"plugins": [
|
"plugins": [
|
||||||
{
|
|
||||||
"name": "eapil-pdf",
|
|
||||||
"source": {
|
|
||||||
"source": "local",
|
|
||||||
"path": "./plugins/eapil-pdf"
|
|
||||||
},
|
|
||||||
"policy": {
|
|
||||||
"installation": "AVAILABLE",
|
|
||||||
"authentication": "ON_INSTALL"
|
|
||||||
},
|
|
||||||
"category": "文档处理"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "eapil-pptx",
|
|
||||||
"source": {
|
|
||||||
"source": "local",
|
|
||||||
"path": "./plugins/eapil-pptx"
|
|
||||||
},
|
|
||||||
"policy": {
|
|
||||||
"installation": "AVAILABLE",
|
|
||||||
"authentication": "ON_INSTALL"
|
|
||||||
},
|
|
||||||
"category": "文档处理"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "eapil-xlsx",
|
|
||||||
"source": {
|
|
||||||
"source": "local",
|
|
||||||
"path": "./plugins/eapil-xlsx"
|
|
||||||
},
|
|
||||||
"policy": {
|
|
||||||
"installation": "AVAILABLE",
|
|
||||||
"authentication": "ON_INSTALL"
|
|
||||||
},
|
|
||||||
"category": "数据分析"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "eapil-threejs",
|
|
||||||
"source": {
|
|
||||||
"source": "local",
|
|
||||||
"path": "./plugins/eapil-threejs"
|
|
||||||
},
|
|
||||||
"policy": {
|
|
||||||
"installation": "AVAILABLE",
|
|
||||||
"authentication": "ON_INSTALL"
|
|
||||||
},
|
|
||||||
"category": "开发工具"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "superpowers",
|
"name": "superpowers",
|
||||||
"source": {
|
"source": {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ This directory can be used as a local marketplace root:
|
|||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
codex plugin marketplace add .\plugins\codex
|
codex plugin marketplace add .\plugins\codex
|
||||||
codex plugin add eapil-pdf@eapil-skill-market
|
codex plugin add docx@eapil-skill-market
|
||||||
```
|
```
|
||||||
|
|
||||||
For Git marketplace testing from the repository root, prefer the root `.agents/plugins/marketplace.json` entry and leave sparse checkout empty.
|
For Git marketplace testing from the repository root, prefer the root `.agents/plugins/marketplace.json` entry and leave sparse checkout empty.
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "eapil-pdf",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"description": "EAPIL PDF 提供 PDF 提取、生成、合并、拆分、渲染和表单填写相关的 Codex 工作流。",
|
|
||||||
"author": {
|
|
||||||
"name": "EAPIL",
|
|
||||||
"url": "https://git.playones.com/arechen/EapilSkillMarket"
|
|
||||||
},
|
|
||||||
"homepage": "https://git.playones.com/arechen/EapilSkillMarket",
|
|
||||||
"repository": "https://git.playones.com/arechen/EapilSkillMarket",
|
|
||||||
"license": "Proprietary",
|
|
||||||
"keywords": [
|
|
||||||
"eapil",
|
|
||||||
"pdf",
|
|
||||||
"codex-skill"
|
|
||||||
],
|
|
||||||
"skills": "./skills/",
|
|
||||||
"interface": {
|
|
||||||
"displayName": "EAPIL PDF",
|
|
||||||
"shortDescription": "PDF 处理、内容提取、文档生成和表单填写工作流。",
|
|
||||||
"longDescription": "EAPIL PDF 提供面向 PDF 文档的 Codex 工作流,覆盖文本和表格提取、PDF 生成、合并拆分、页面渲染、可填写表单处理以及中文/CJK 文档场景。",
|
|
||||||
"developerName": "EAPIL",
|
|
||||||
"category": "文档处理",
|
|
||||||
"capabilities": [
|
|
||||||
"Read",
|
|
||||||
"Write"
|
|
||||||
],
|
|
||||||
"defaultPrompt": [
|
|
||||||
"使用 EAPIL PDF 检查这个文档。",
|
|
||||||
"使用 EAPIL PDF 填写这个 PDF 表单。",
|
|
||||||
"使用 EAPIL PDF 提取这个 PDF 的表格。"
|
|
||||||
],
|
|
||||||
"websiteURL": "https://git.playones.com/arechen/EapilSkillMarket",
|
|
||||||
"privacyPolicyURL": "https://git.playones.com/arechen/EapilSkillMarket",
|
|
||||||
"termsOfServiceURL": "https://git.playones.com/arechen/EapilSkillMarket",
|
|
||||||
"brandColor": "#2563EB",
|
|
||||||
"screenshots": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
© 2025 Anthropic, PBC. All rights reserved.
|
|
||||||
|
|
||||||
LICENSE: Use of these materials (including all code, prompts, assets, files,
|
|
||||||
and other components of this Skill) is governed by your agreement with
|
|
||||||
Anthropic regarding use of Anthropic's services. If no separate agreement
|
|
||||||
exists, use is governed by Anthropic's Consumer Terms of Service or
|
|
||||||
Commercial Terms of Service, as applicable:
|
|
||||||
https://www.anthropic.com/legal/consumer-terms
|
|
||||||
https://www.anthropic.com/legal/commercial-terms
|
|
||||||
Your applicable agreement is referred to as the "Agreement." "Services" are
|
|
||||||
as defined in the Agreement.
|
|
||||||
|
|
||||||
ADDITIONAL RESTRICTIONS: Notwithstanding anything in the Agreement to the
|
|
||||||
contrary, users may not:
|
|
||||||
|
|
||||||
- Extract these materials from the Services or retain copies of these
|
|
||||||
materials outside the Services
|
|
||||||
- Reproduce or copy these materials, except for temporary copies created
|
|
||||||
automatically during authorized use of the Services
|
|
||||||
- Create derivative works based on these materials
|
|
||||||
- Distribute, sublicense, or transfer these materials to any third party
|
|
||||||
- Make, offer to sell, sell, or import any inventions embodied in these
|
|
||||||
materials
|
|
||||||
- Reverse engineer, decompile, or disassemble these materials
|
|
||||||
|
|
||||||
The receipt, viewing, or possession of these materials does not convey or
|
|
||||||
imply any license or right beyond those expressly granted above.
|
|
||||||
|
|
||||||
Anthropic retains all right, title, and interest in these materials,
|
|
||||||
including all copyrights, patents, and other intellectual property rights.
|
|
||||||
@@ -1,314 +0,0 @@
|
|||||||
---
|
|
||||||
name: pdf
|
|
||||||
description: "PDF 处理工具包,用于文本和表格提取、PDF 生成、合并拆分、页面渲染、表单填写,以及中文/CJK 文档处理。"
|
|
||||||
license: Proprietary. LICENSE.txt has complete terms
|
|
||||||
---
|
|
||||||
|
|
||||||
# PDF Processing Guide
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
This guide covers essential PDF processing operations using Python libraries and command-line tools. For advanced features, JavaScript libraries, and detailed examples, see reference.md. If you need to fill out a PDF form, read forms.md and follow its instructions.
|
|
||||||
|
|
||||||
## Windows and Chinese-language defaults
|
|
||||||
|
|
||||||
- Prefer quoted Windows paths: `"C:\docs\contract-zh.pdf"`
|
|
||||||
- In Windows PowerShell, set UTF-8 mode before Python PDF scripts: `$env:PYTHONUTF8='1'`
|
|
||||||
- For Chinese text extraction, validate output in UTF-8 files instead of relying only on console rendering
|
|
||||||
- For generated PDFs containing Chinese text, register and use CJK-capable fonts (`Microsoft YaHei`, `SimSun`, `Noto Sans CJK SC`)
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
```python
|
|
||||||
from pypdf import PdfReader, PdfWriter
|
|
||||||
|
|
||||||
# Read a PDF
|
|
||||||
reader = PdfReader("document.pdf")
|
|
||||||
print(f"Pages: {len(reader.pages)}")
|
|
||||||
|
|
||||||
# Extract text
|
|
||||||
text = ""
|
|
||||||
for page in reader.pages:
|
|
||||||
text += page.extract_text()
|
|
||||||
```
|
|
||||||
|
|
||||||
## Python Libraries
|
|
||||||
|
|
||||||
### pypdf - Basic Operations
|
|
||||||
|
|
||||||
#### Merge PDFs
|
|
||||||
```python
|
|
||||||
from pypdf import PdfWriter, PdfReader
|
|
||||||
|
|
||||||
writer = PdfWriter()
|
|
||||||
for pdf_file in ["doc1.pdf", "doc2.pdf", "doc3.pdf"]:
|
|
||||||
reader = PdfReader(pdf_file)
|
|
||||||
for page in reader.pages:
|
|
||||||
writer.add_page(page)
|
|
||||||
|
|
||||||
with open("merged.pdf", "wb") as output:
|
|
||||||
writer.write(output)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Split PDF
|
|
||||||
```python
|
|
||||||
reader = PdfReader("input.pdf")
|
|
||||||
for i, page in enumerate(reader.pages):
|
|
||||||
writer = PdfWriter()
|
|
||||||
writer.add_page(page)
|
|
||||||
with open(f"page_{i+1}.pdf", "wb") as output:
|
|
||||||
writer.write(output)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Extract Metadata
|
|
||||||
```python
|
|
||||||
reader = PdfReader("document.pdf")
|
|
||||||
meta = reader.metadata
|
|
||||||
print(f"Title: {meta.title}")
|
|
||||||
print(f"Author: {meta.author}")
|
|
||||||
print(f"Subject: {meta.subject}")
|
|
||||||
print(f"Creator: {meta.creator}")
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Rotate Pages
|
|
||||||
```python
|
|
||||||
reader = PdfReader("input.pdf")
|
|
||||||
writer = PdfWriter()
|
|
||||||
|
|
||||||
page = reader.pages[0]
|
|
||||||
page.rotate(90) # Rotate 90 degrees clockwise
|
|
||||||
writer.add_page(page)
|
|
||||||
|
|
||||||
with open("rotated.pdf", "wb") as output:
|
|
||||||
writer.write(output)
|
|
||||||
```
|
|
||||||
|
|
||||||
### pdfplumber - Text and Table Extraction
|
|
||||||
|
|
||||||
#### Extract Text with Layout
|
|
||||||
```python
|
|
||||||
import pdfplumber
|
|
||||||
|
|
||||||
with pdfplumber.open("document.pdf") as pdf:
|
|
||||||
for page in pdf.pages:
|
|
||||||
text = page.extract_text()
|
|
||||||
print(text)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Extract Tables
|
|
||||||
```python
|
|
||||||
with pdfplumber.open("document.pdf") as pdf:
|
|
||||||
for i, page in enumerate(pdf.pages):
|
|
||||||
tables = page.extract_tables()
|
|
||||||
for j, table in enumerate(tables):
|
|
||||||
print(f"Table {j+1} on page {i+1}:")
|
|
||||||
for row in table:
|
|
||||||
print(row)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Advanced Table Extraction
|
|
||||||
```python
|
|
||||||
import pandas as pd
|
|
||||||
|
|
||||||
with pdfplumber.open("document.pdf") as pdf:
|
|
||||||
all_tables = []
|
|
||||||
for page in pdf.pages:
|
|
||||||
tables = page.extract_tables()
|
|
||||||
for table in tables:
|
|
||||||
if table: # Check if table is not empty
|
|
||||||
df = pd.DataFrame(table[1:], columns=table[0])
|
|
||||||
all_tables.append(df)
|
|
||||||
|
|
||||||
# Combine all tables
|
|
||||||
if all_tables:
|
|
||||||
combined_df = pd.concat(all_tables, ignore_index=True)
|
|
||||||
combined_df.to_excel("extracted_tables.xlsx", index=False)
|
|
||||||
```
|
|
||||||
|
|
||||||
### reportlab - Create PDFs
|
|
||||||
|
|
||||||
#### Chinese Font Setup (ReportLab)
|
|
||||||
```python
|
|
||||||
from reportlab.pdfbase import pdfmetrics
|
|
||||||
from reportlab.pdfbase.ttfonts import TTFont
|
|
||||||
|
|
||||||
pdfmetrics.registerFont(TTFont('MicrosoftYaHei', r'C:\Windows\Fonts\msyh.ttc'))
|
|
||||||
|
|
||||||
# Then use: canvas.setFont('MicrosoftYaHei', 11)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Basic PDF Creation
|
|
||||||
```python
|
|
||||||
from reportlab.lib.pagesizes import letter
|
|
||||||
from reportlab.pdfgen import canvas
|
|
||||||
|
|
||||||
c = canvas.Canvas("hello.pdf", pagesize=letter)
|
|
||||||
width, height = letter
|
|
||||||
|
|
||||||
# Add text
|
|
||||||
c.drawString(100, height - 100, "Hello World!")
|
|
||||||
c.drawString(100, height - 120, "This is a PDF created with reportlab")
|
|
||||||
|
|
||||||
# Add a line
|
|
||||||
c.line(100, height - 140, 400, height - 140)
|
|
||||||
|
|
||||||
# Save
|
|
||||||
c.save()
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Create PDF with Multiple Pages
|
|
||||||
```python
|
|
||||||
from reportlab.lib.pagesizes import letter
|
|
||||||
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, PageBreak
|
|
||||||
from reportlab.lib.styles import getSampleStyleSheet
|
|
||||||
|
|
||||||
doc = SimpleDocTemplate("report.pdf", pagesize=letter)
|
|
||||||
styles = getSampleStyleSheet()
|
|
||||||
story = []
|
|
||||||
|
|
||||||
# Add content
|
|
||||||
title = Paragraph("Report Title", styles['Title'])
|
|
||||||
story.append(title)
|
|
||||||
story.append(Spacer(1, 12))
|
|
||||||
|
|
||||||
body = Paragraph("This is the body of the report. " * 20, styles['Normal'])
|
|
||||||
story.append(body)
|
|
||||||
story.append(PageBreak())
|
|
||||||
|
|
||||||
# Page 2
|
|
||||||
story.append(Paragraph("Page 2", styles['Heading1']))
|
|
||||||
story.append(Paragraph("Content for page 2", styles['Normal']))
|
|
||||||
|
|
||||||
# Build PDF
|
|
||||||
doc.build(story)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Command-Line Tools
|
|
||||||
|
|
||||||
### pdftotext (poppler-utils)
|
|
||||||
```bash
|
|
||||||
# Extract text
|
|
||||||
pdftotext input.pdf output.txt
|
|
||||||
|
|
||||||
# Extract text preserving layout
|
|
||||||
pdftotext -layout input.pdf output.txt
|
|
||||||
|
|
||||||
# Force UTF-8 output encoding
|
|
||||||
pdftotext -enc UTF-8 input.pdf output.txt
|
|
||||||
|
|
||||||
# Extract specific pages
|
|
||||||
pdftotext -f 1 -l 5 input.pdf output.txt # Pages 1-5
|
|
||||||
```
|
|
||||||
|
|
||||||
### qpdf
|
|
||||||
```bash
|
|
||||||
# Merge PDFs
|
|
||||||
qpdf --empty --pages file1.pdf file2.pdf -- merged.pdf
|
|
||||||
|
|
||||||
# Split pages
|
|
||||||
qpdf input.pdf --pages . 1-5 -- pages1-5.pdf
|
|
||||||
qpdf input.pdf --pages . 6-10 -- pages6-10.pdf
|
|
||||||
|
|
||||||
# Rotate pages
|
|
||||||
qpdf input.pdf output.pdf --rotate=+90:1 # Rotate page 1 by 90 degrees
|
|
||||||
|
|
||||||
# Remove password
|
|
||||||
qpdf --password=mypassword --decrypt encrypted.pdf decrypted.pdf
|
|
||||||
```
|
|
||||||
|
|
||||||
### pdftk (if available)
|
|
||||||
```bash
|
|
||||||
# Merge
|
|
||||||
pdftk file1.pdf file2.pdf cat output merged.pdf
|
|
||||||
|
|
||||||
# Split
|
|
||||||
pdftk input.pdf burst
|
|
||||||
|
|
||||||
# Rotate
|
|
||||||
pdftk input.pdf rotate 1east output rotated.pdf
|
|
||||||
```
|
|
||||||
|
|
||||||
## Common Tasks
|
|
||||||
|
|
||||||
### Extract Text from Scanned PDFs
|
|
||||||
```python
|
|
||||||
# Requires: pip install pytesseract pdf2image
|
|
||||||
import pytesseract
|
|
||||||
from pdf2image import convert_from_path
|
|
||||||
|
|
||||||
# Convert PDF to images
|
|
||||||
images = convert_from_path('scanned.pdf')
|
|
||||||
|
|
||||||
# OCR each page
|
|
||||||
text = ""
|
|
||||||
for i, image in enumerate(images):
|
|
||||||
text += f"Page {i+1}:\n"
|
|
||||||
text += pytesseract.image_to_string(image, lang='chi_sim+eng')
|
|
||||||
text += "\n\n"
|
|
||||||
|
|
||||||
print(text)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Add Watermark
|
|
||||||
```python
|
|
||||||
from pypdf import PdfReader, PdfWriter
|
|
||||||
|
|
||||||
# Create watermark (or load existing)
|
|
||||||
watermark = PdfReader("watermark.pdf").pages[0]
|
|
||||||
|
|
||||||
# Apply to all pages
|
|
||||||
reader = PdfReader("document.pdf")
|
|
||||||
writer = PdfWriter()
|
|
||||||
|
|
||||||
for page in reader.pages:
|
|
||||||
page.merge_page(watermark)
|
|
||||||
writer.add_page(page)
|
|
||||||
|
|
||||||
with open("watermarked.pdf", "wb") as output:
|
|
||||||
writer.write(output)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Extract Images
|
|
||||||
```bash
|
|
||||||
# Using pdfimages (poppler-utils)
|
|
||||||
pdfimages -j input.pdf output_prefix
|
|
||||||
|
|
||||||
# This extracts all images as output_prefix-000.jpg, output_prefix-001.jpg, etc.
|
|
||||||
```
|
|
||||||
|
|
||||||
### Password Protection
|
|
||||||
```python
|
|
||||||
from pypdf import PdfReader, PdfWriter
|
|
||||||
|
|
||||||
reader = PdfReader("input.pdf")
|
|
||||||
writer = PdfWriter()
|
|
||||||
|
|
||||||
for page in reader.pages:
|
|
||||||
writer.add_page(page)
|
|
||||||
|
|
||||||
# Add password
|
|
||||||
writer.encrypt("userpassword", "ownerpassword")
|
|
||||||
|
|
||||||
with open("encrypted.pdf", "wb") as output:
|
|
||||||
writer.write(output)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Quick Reference
|
|
||||||
|
|
||||||
| Task | Best Tool | Command/Code |
|
|
||||||
|------|-----------|--------------|
|
|
||||||
| Merge PDFs | pypdf | `writer.add_page(page)` |
|
|
||||||
| Split PDFs | pypdf | One page per file |
|
|
||||||
| Extract text | pdfplumber | `page.extract_text()` |
|
|
||||||
| Extract tables | pdfplumber | `page.extract_tables()` |
|
|
||||||
| Create PDFs | reportlab | Canvas or Platypus |
|
|
||||||
| Command line merge | qpdf | `qpdf --empty --pages ...` |
|
|
||||||
| OCR scanned PDFs | pytesseract | Convert to image first |
|
|
||||||
| Fill PDF forms | pdf-lib or pypdf (see forms.md) | See forms.md |
|
|
||||||
|
|
||||||
## Next Steps
|
|
||||||
|
|
||||||
- For advanced pypdfium2 usage, see reference.md
|
|
||||||
- For JavaScript libraries (pdf-lib), see reference.md
|
|
||||||
- If you need to fill out a PDF form, follow the instructions in forms.md
|
|
||||||
- For troubleshooting guides, see reference.md
|
|
||||||
@@ -1,211 +0,0 @@
|
|||||||
**CRITICAL: You MUST complete these steps in order. Do not skip ahead to writing code.**
|
|
||||||
|
|
||||||
If you need to fill out a PDF form, first check to see if the PDF has fillable form fields. Run this script from this file's directory:
|
|
||||||
`python scripts/check_fillable_fields <file.pdf>`, and depending on the result go to either the "Fillable fields" or "Non-fillable fields" and follow those instructions.
|
|
||||||
|
|
||||||
## Windows and Chinese-language defaults
|
|
||||||
- Prefer quoted paths on Windows (for example: `"C:\docs\input-form.pdf"`)
|
|
||||||
- In Windows PowerShell, enable UTF-8 mode before running scripts: `$env:PYTHONUTF8='1'`
|
|
||||||
- For Chinese text, visually verify the output PDF after filling (font fallback can affect spacing)
|
|
||||||
- If `python` points to Python 2 on Windows, use `py -3`
|
|
||||||
|
|
||||||
# Fillable fields
|
|
||||||
If the PDF has fillable form fields:
|
|
||||||
- Run this script from this file's directory: `python scripts/extract_form_field_info.py <input.pdf> <field_info.json>`. It will create a JSON file with a list of fields in this format:
|
|
||||||
```
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"field_id": (unique ID for the field),
|
|
||||||
"page": (page number, 1-based),
|
|
||||||
"rect": ([left, bottom, right, top] bounding box in PDF coordinates, y=0 is the bottom of the page),
|
|
||||||
"type": ("text", "checkbox", "radio_group", or "choice"),
|
|
||||||
},
|
|
||||||
// Checkboxes have "checked_value" and "unchecked_value" properties:
|
|
||||||
{
|
|
||||||
"field_id": (unique ID for the field),
|
|
||||||
"page": (page number, 1-based),
|
|
||||||
"type": "checkbox",
|
|
||||||
"checked_value": (Set the field to this value to check the checkbox),
|
|
||||||
"unchecked_value": (Set the field to this value to uncheck the checkbox),
|
|
||||||
},
|
|
||||||
// Radio groups have a "radio_options" list with the possible choices.
|
|
||||||
{
|
|
||||||
"field_id": (unique ID for the field),
|
|
||||||
"page": (page number, 1-based),
|
|
||||||
"type": "radio_group",
|
|
||||||
"radio_options": [
|
|
||||||
{
|
|
||||||
"value": (set the field to this value to select this radio option),
|
|
||||||
"rect": (bounding box for the radio button for this option)
|
|
||||||
},
|
|
||||||
// Other radio options
|
|
||||||
]
|
|
||||||
},
|
|
||||||
// Multiple choice fields have a "choice_options" list with the possible choices:
|
|
||||||
{
|
|
||||||
"field_id": (unique ID for the field),
|
|
||||||
"page": (page number, 1-based),
|
|
||||||
"type": "choice",
|
|
||||||
"choice_options": [
|
|
||||||
{
|
|
||||||
"value": (set the field to this value to select this option),
|
|
||||||
"text": (display text of the option)
|
|
||||||
},
|
|
||||||
// Other choice options
|
|
||||||
],
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
- Convert the PDF to PNGs (one image for each page) with this script (run from this file's directory):
|
|
||||||
`python scripts/convert_pdf_to_images.py <file.pdf> <output_directory>`
|
|
||||||
Then analyze the images to determine the purpose of each form field (make sure to convert the bounding box PDF coordinates to image coordinates).
|
|
||||||
- Create a `field_values.json` file in this format with the values to be entered for each field:
|
|
||||||
```
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"field_id": "last_name", // Must match the field_id from `extract_form_field_info.py`
|
|
||||||
"description": "The user's last name",
|
|
||||||
"page": 1, // Must match the "page" value in field_info.json
|
|
||||||
"value": "Simpson"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"field_id": "Checkbox12",
|
|
||||||
"description": "Checkbox to be checked if the user is 18 or over",
|
|
||||||
"page": 1,
|
|
||||||
"value": "/On" // If this is a checkbox, use its "checked_value" value to check it. If it's a radio button group, use one of the "value" values in "radio_options".
|
|
||||||
},
|
|
||||||
// more fields
|
|
||||||
]
|
|
||||||
```
|
|
||||||
- Run the `fill_fillable_fields.py` script from this file's directory to create a filled-in PDF:
|
|
||||||
`python scripts/fill_fillable_fields.py <input pdf> <field_values.json> <output pdf>`
|
|
||||||
This script will verify that the field IDs and values you provide are valid; if it prints error messages, correct the appropriate fields and try again.
|
|
||||||
|
|
||||||
# Non-fillable fields
|
|
||||||
If the PDF doesn't have fillable form fields, you'll need to visually determine where the data should be added and create text annotations. Follow the below steps *exactly*. You MUST perform all of these steps to ensure that the the form is accurately completed. Details for each step are below.
|
|
||||||
- Convert the PDF to PNG images and determine field bounding boxes.
|
|
||||||
- Create a JSON file with field information and validation images showing the bounding boxes.
|
|
||||||
- Validate the the bounding boxes.
|
|
||||||
- Use the bounding boxes to fill in the form.
|
|
||||||
|
|
||||||
## Step 1: Visual Analysis (REQUIRED)
|
|
||||||
- Convert the PDF to PNG images. Run this script from this file's directory:
|
|
||||||
`python scripts/convert_pdf_to_images.py <file.pdf> <output_directory>`
|
|
||||||
The script will create a PNG image for each page in the PDF.
|
|
||||||
- Carefully examine each PNG image and identify all form fields and areas where the user should enter data. For each form field where the user should enter text, determine bounding boxes for both the form field label, and the area where the user should enter text. The label and entry bounding boxes MUST NOT INTERSECT; the text entry box should only include the area where data should be entered. Usually this area will be immediately to the side, above, or below its label. Entry bounding boxes must be tall and wide enough to contain their text.
|
|
||||||
|
|
||||||
These are some examples of form structures that you might see:
|
|
||||||
|
|
||||||
*Label inside box*
|
|
||||||
```
|
|
||||||
┌────────────────────────┐
|
|
||||||
│ Name: │
|
|
||||||
└────────────────────────┘
|
|
||||||
```
|
|
||||||
The input area should be to the right of the "Name" label and extend to the edge of the box.
|
|
||||||
|
|
||||||
*Label before line*
|
|
||||||
```
|
|
||||||
Email: _______________________
|
|
||||||
```
|
|
||||||
The input area should be above the line and include its entire width.
|
|
||||||
|
|
||||||
*Label under line*
|
|
||||||
```
|
|
||||||
_________________________
|
|
||||||
Name
|
|
||||||
```
|
|
||||||
The input area should be above the line and include the entire width of the line. This is common for signature and date fields.
|
|
||||||
|
|
||||||
*Label above line*
|
|
||||||
```
|
|
||||||
Please enter any special requests:
|
|
||||||
________________________________________________
|
|
||||||
```
|
|
||||||
The input area should extend from the bottom of the label to the line, and should include the entire width of the line.
|
|
||||||
|
|
||||||
*Checkboxes*
|
|
||||||
```
|
|
||||||
Are you a US citizen? Yes □ No □
|
|
||||||
```
|
|
||||||
For checkboxes:
|
|
||||||
- Look for small square boxes (□) - these are the actual checkboxes to target. They may be to the left or right of their labels.
|
|
||||||
- Distinguish between label text ("Yes", "No") and the clickable checkbox squares.
|
|
||||||
- The entry bounding box should cover ONLY the small square, not the text label.
|
|
||||||
|
|
||||||
### Step 2: Create fields.json and validation images (REQUIRED)
|
|
||||||
- Create a file named `fields.json` with information for the form fields and bounding boxes in this format:
|
|
||||||
```
|
|
||||||
{
|
|
||||||
"pages": [
|
|
||||||
{
|
|
||||||
"page_number": 1,
|
|
||||||
"image_width": (first page image width in pixels),
|
|
||||||
"image_height": (first page image height in pixels),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"page_number": 2,
|
|
||||||
"image_width": (second page image width in pixels),
|
|
||||||
"image_height": (second page image height in pixels),
|
|
||||||
}
|
|
||||||
// additional pages
|
|
||||||
],
|
|
||||||
"form_fields": [
|
|
||||||
// Example for a text field.
|
|
||||||
{
|
|
||||||
"page_number": 1,
|
|
||||||
"description": "The user's last name should be entered here",
|
|
||||||
// Bounding boxes are [left, top, right, bottom]. The bounding boxes for the label and text entry should not overlap.
|
|
||||||
"field_label": "Last name",
|
|
||||||
"label_bounding_box": [30, 125, 95, 142],
|
|
||||||
"entry_bounding_box": [100, 125, 280, 142],
|
|
||||||
"entry_text": {
|
|
||||||
"text": "Johnson", // This text will be added as an annotation at the entry_bounding_box location
|
|
||||||
"font_size": 14, // optional, defaults to 14
|
|
||||||
"font_color": "000000", // optional, RRGGBB format, defaults to 000000 (black)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// Example for a checkbox. TARGET THE SQUARE for the entry bounding box, NOT THE TEXT
|
|
||||||
{
|
|
||||||
"page_number": 2,
|
|
||||||
"description": "Checkbox that should be checked if the user is over 18",
|
|
||||||
"entry_bounding_box": [140, 525, 155, 540], // Small box over checkbox square
|
|
||||||
"field_label": "Yes",
|
|
||||||
"label_bounding_box": [100, 525, 132, 540], // Box containing "Yes" text
|
|
||||||
// Use "X" to check a checkbox.
|
|
||||||
"entry_text": {
|
|
||||||
"text": "X",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// additional form field entries
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Create validation images by running this script from this file's directory for each page:
|
|
||||||
`python scripts/create_validation_image.py <page_number> <path_to_fields.json> <input_image_path> <output_image_path>`
|
|
||||||
|
|
||||||
The validation images will have red rectangles where text should be entered, and blue rectangles covering label text.
|
|
||||||
|
|
||||||
### Step 3: Validate Bounding Boxes (REQUIRED)
|
|
||||||
#### Automated intersection check
|
|
||||||
- Verify that none of bounding boxes intersect and that the entry bounding boxes are tall enough by checking the fields.json file with the `check_bounding_boxes.py` script (run from this file's directory):
|
|
||||||
`python scripts/check_bounding_boxes.py <JSON file>`
|
|
||||||
|
|
||||||
If there are errors, reanalyze the relevant fields, adjust the bounding boxes, and iterate until there are no remaining errors. Remember: label (blue) bounding boxes should contain text labels, entry (red) boxes should not.
|
|
||||||
|
|
||||||
#### Manual image inspection
|
|
||||||
**CRITICAL: Do not proceed without visually inspecting validation images**
|
|
||||||
- Red rectangles must ONLY cover input areas
|
|
||||||
- Red rectangles MUST NOT contain any text
|
|
||||||
- Blue rectangles should contain label text
|
|
||||||
- For checkboxes:
|
|
||||||
- Red rectangle MUST be centered on the checkbox square
|
|
||||||
- Blue rectangle should cover the text label for the checkbox
|
|
||||||
|
|
||||||
- If any rectangles look wrong, fix fields.json, regenerate the validation images, and verify again. Repeat this process until the bounding boxes are fully accurate.
|
|
||||||
|
|
||||||
|
|
||||||
### Step 4: Add annotations to the PDF
|
|
||||||
Run this script from this file's directory to create a filled-out PDF using the information in fields.json:
|
|
||||||
`python scripts/fill_pdf_form_with_annotations.py <input_pdf_path> <path_to_fields.json> <output_pdf_path>`
|
|
||||||
@@ -1,644 +0,0 @@
|
|||||||
# PDF Processing Advanced Reference
|
|
||||||
|
|
||||||
This document contains advanced PDF processing features, detailed examples, and additional libraries not covered in the main skill instructions.
|
|
||||||
|
|
||||||
## Windows and Chinese-language defaults
|
|
||||||
- Quote Windows file paths in commands and scripts
|
|
||||||
- Prefer UTF-8 output files when validating extracted Chinese text
|
|
||||||
- For OCR on Chinese scans, use Tesseract language packs with `chi_sim+eng`
|
|
||||||
- For generated Chinese PDFs, use embedded CJK fonts instead of default Helvetica
|
|
||||||
|
|
||||||
## pypdfium2 Library (Apache/BSD License)
|
|
||||||
|
|
||||||
### Overview
|
|
||||||
pypdfium2 is a Python binding for PDFium (Chromium's PDF library). It's excellent for fast PDF rendering, image generation, and serves as a PyMuPDF replacement.
|
|
||||||
|
|
||||||
### Render PDF to Images
|
|
||||||
```python
|
|
||||||
import pypdfium2 as pdfium
|
|
||||||
from PIL import Image
|
|
||||||
|
|
||||||
# Load PDF
|
|
||||||
pdf = pdfium.PdfDocument("document.pdf")
|
|
||||||
|
|
||||||
# Render page to image
|
|
||||||
page = pdf[0] # First page
|
|
||||||
bitmap = page.render(
|
|
||||||
scale=2.0, # Higher resolution
|
|
||||||
rotation=0 # No rotation
|
|
||||||
)
|
|
||||||
|
|
||||||
# Convert to PIL Image
|
|
||||||
img = bitmap.to_pil()
|
|
||||||
img.save("page_1.png", "PNG")
|
|
||||||
|
|
||||||
# Process multiple pages
|
|
||||||
for i, page in enumerate(pdf):
|
|
||||||
bitmap = page.render(scale=1.5)
|
|
||||||
img = bitmap.to_pil()
|
|
||||||
img.save(f"page_{i+1}.jpg", "JPEG", quality=90)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Extract Text with pypdfium2
|
|
||||||
```python
|
|
||||||
import pypdfium2 as pdfium
|
|
||||||
|
|
||||||
pdf = pdfium.PdfDocument("document.pdf")
|
|
||||||
for i, page in enumerate(pdf):
|
|
||||||
text = page.get_text()
|
|
||||||
print(f"Page {i+1} text length: {len(text)} chars")
|
|
||||||
```
|
|
||||||
|
|
||||||
## JavaScript Libraries
|
|
||||||
|
|
||||||
### pdf-lib (MIT License)
|
|
||||||
|
|
||||||
pdf-lib is a powerful JavaScript library for creating and modifying PDF documents in any JavaScript environment.
|
|
||||||
|
|
||||||
#### Load and Manipulate Existing PDF
|
|
||||||
```javascript
|
|
||||||
import { PDFDocument } from 'pdf-lib';
|
|
||||||
import fs from 'fs';
|
|
||||||
|
|
||||||
async function manipulatePDF() {
|
|
||||||
// Load existing PDF
|
|
||||||
const existingPdfBytes = fs.readFileSync('input.pdf');
|
|
||||||
const pdfDoc = await PDFDocument.load(existingPdfBytes);
|
|
||||||
|
|
||||||
// Get page count
|
|
||||||
const pageCount = pdfDoc.getPageCount();
|
|
||||||
console.log(`Document has ${pageCount} pages`);
|
|
||||||
|
|
||||||
// Add new page
|
|
||||||
const newPage = pdfDoc.addPage([600, 400]);
|
|
||||||
newPage.drawText('Added by pdf-lib', {
|
|
||||||
x: 100,
|
|
||||||
y: 300,
|
|
||||||
size: 16
|
|
||||||
});
|
|
||||||
|
|
||||||
// Save modified PDF
|
|
||||||
const pdfBytes = await pdfDoc.save();
|
|
||||||
fs.writeFileSync('modified.pdf', pdfBytes);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Create Complex PDFs from Scratch
|
|
||||||
```javascript
|
|
||||||
import { PDFDocument, rgb, StandardFonts } from 'pdf-lib';
|
|
||||||
import fs from 'fs';
|
|
||||||
|
|
||||||
async function createPDF() {
|
|
||||||
const pdfDoc = await PDFDocument.create();
|
|
||||||
|
|
||||||
// Add fonts
|
|
||||||
const helveticaFont = await pdfDoc.embedFont(StandardFonts.Helvetica);
|
|
||||||
const helveticaBold = await pdfDoc.embedFont(StandardFonts.HelveticaBold);
|
|
||||||
|
|
||||||
// Add page
|
|
||||||
const page = pdfDoc.addPage([595, 842]); // A4 size
|
|
||||||
const { width, height } = page.getSize();
|
|
||||||
|
|
||||||
// Add text with styling
|
|
||||||
page.drawText('Invoice #12345', {
|
|
||||||
x: 50,
|
|
||||||
y: height - 50,
|
|
||||||
size: 18,
|
|
||||||
font: helveticaBold,
|
|
||||||
color: rgb(0.2, 0.2, 0.8)
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add rectangle (header background)
|
|
||||||
page.drawRectangle({
|
|
||||||
x: 40,
|
|
||||||
y: height - 100,
|
|
||||||
width: width - 80,
|
|
||||||
height: 30,
|
|
||||||
color: rgb(0.9, 0.9, 0.9)
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add table-like content
|
|
||||||
const items = [
|
|
||||||
['Item', 'Qty', 'Price', 'Total'],
|
|
||||||
['Widget', '2', '$50', '$100'],
|
|
||||||
['Gadget', '1', '$75', '$75']
|
|
||||||
];
|
|
||||||
|
|
||||||
let yPos = height - 150;
|
|
||||||
items.forEach(row => {
|
|
||||||
let xPos = 50;
|
|
||||||
row.forEach(cell => {
|
|
||||||
page.drawText(cell, {
|
|
||||||
x: xPos,
|
|
||||||
y: yPos,
|
|
||||||
size: 12,
|
|
||||||
font: helveticaFont
|
|
||||||
});
|
|
||||||
xPos += 120;
|
|
||||||
});
|
|
||||||
yPos -= 25;
|
|
||||||
});
|
|
||||||
|
|
||||||
const pdfBytes = await pdfDoc.save();
|
|
||||||
fs.writeFileSync('created.pdf', pdfBytes);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### CJK Font Embedding (for Chinese text)
|
|
||||||
```javascript
|
|
||||||
import { PDFDocument } from 'pdf-lib';
|
|
||||||
import fs from 'fs';
|
|
||||||
|
|
||||||
async function createChinesePDF() {
|
|
||||||
const pdfDoc = await PDFDocument.create();
|
|
||||||
const page = pdfDoc.addPage([595, 842]);
|
|
||||||
|
|
||||||
const fontBytes = fs.readFileSync('NotoSansCJKsc-Regular.otf');
|
|
||||||
const cjkFont = await pdfDoc.embedFont(fontBytes);
|
|
||||||
|
|
||||||
page.drawText('Chinese text sample', {
|
|
||||||
x: 50,
|
|
||||||
y: 760,
|
|
||||||
size: 14,
|
|
||||||
font: cjkFont
|
|
||||||
});
|
|
||||||
|
|
||||||
fs.writeFileSync('cjk-output.pdf', await pdfDoc.save());
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Advanced Merge and Split Operations
|
|
||||||
```javascript
|
|
||||||
import { PDFDocument } from 'pdf-lib';
|
|
||||||
import fs from 'fs';
|
|
||||||
|
|
||||||
async function mergePDFs() {
|
|
||||||
// Create new document
|
|
||||||
const mergedPdf = await PDFDocument.create();
|
|
||||||
|
|
||||||
// Load source PDFs
|
|
||||||
const pdf1Bytes = fs.readFileSync('doc1.pdf');
|
|
||||||
const pdf2Bytes = fs.readFileSync('doc2.pdf');
|
|
||||||
|
|
||||||
const pdf1 = await PDFDocument.load(pdf1Bytes);
|
|
||||||
const pdf2 = await PDFDocument.load(pdf2Bytes);
|
|
||||||
|
|
||||||
// Copy pages from first PDF
|
|
||||||
const pdf1Pages = await mergedPdf.copyPages(pdf1, pdf1.getPageIndices());
|
|
||||||
pdf1Pages.forEach(page => mergedPdf.addPage(page));
|
|
||||||
|
|
||||||
// Copy specific pages from second PDF (pages 0, 2, 4)
|
|
||||||
const pdf2Pages = await mergedPdf.copyPages(pdf2, [0, 2, 4]);
|
|
||||||
pdf2Pages.forEach(page => mergedPdf.addPage(page));
|
|
||||||
|
|
||||||
const mergedPdfBytes = await mergedPdf.save();
|
|
||||||
fs.writeFileSync('merged.pdf', mergedPdfBytes);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### pdfjs-dist (Apache License)
|
|
||||||
|
|
||||||
PDF.js is Mozilla's JavaScript library for rendering PDFs in the browser.
|
|
||||||
|
|
||||||
#### Basic PDF Loading and Rendering
|
|
||||||
```javascript
|
|
||||||
import * as pdfjsLib from 'pdfjs-dist';
|
|
||||||
|
|
||||||
// Configure worker (important for performance)
|
|
||||||
pdfjsLib.GlobalWorkerOptions.workerSrc = './pdf.worker.js';
|
|
||||||
|
|
||||||
async function renderPDF() {
|
|
||||||
// Load PDF
|
|
||||||
const loadingTask = pdfjsLib.getDocument('document.pdf');
|
|
||||||
const pdf = await loadingTask.promise;
|
|
||||||
|
|
||||||
console.log(`Loaded PDF with ${pdf.numPages} pages`);
|
|
||||||
|
|
||||||
// Get first page
|
|
||||||
const page = await pdf.getPage(1);
|
|
||||||
const viewport = page.getViewport({ scale: 1.5 });
|
|
||||||
|
|
||||||
// Render to canvas
|
|
||||||
const canvas = document.createElement('canvas');
|
|
||||||
const context = canvas.getContext('2d');
|
|
||||||
canvas.height = viewport.height;
|
|
||||||
canvas.width = viewport.width;
|
|
||||||
|
|
||||||
const renderContext = {
|
|
||||||
canvasContext: context,
|
|
||||||
viewport: viewport
|
|
||||||
};
|
|
||||||
|
|
||||||
await page.render(renderContext).promise;
|
|
||||||
document.body.appendChild(canvas);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Extract Text with Coordinates
|
|
||||||
```javascript
|
|
||||||
import * as pdfjsLib from 'pdfjs-dist';
|
|
||||||
|
|
||||||
async function extractText() {
|
|
||||||
const loadingTask = pdfjsLib.getDocument('document.pdf');
|
|
||||||
const pdf = await loadingTask.promise;
|
|
||||||
|
|
||||||
let fullText = '';
|
|
||||||
|
|
||||||
// Extract text from all pages
|
|
||||||
for (let i = 1; i <= pdf.numPages; i++) {
|
|
||||||
const page = await pdf.getPage(i);
|
|
||||||
const textContent = await page.getTextContent();
|
|
||||||
|
|
||||||
const pageText = textContent.items
|
|
||||||
.map(item => item.str)
|
|
||||||
.join(' ');
|
|
||||||
|
|
||||||
fullText += `\n--- Page ${i} ---\n${pageText}`;
|
|
||||||
|
|
||||||
// Get text with coordinates for advanced processing
|
|
||||||
const textWithCoords = textContent.items.map(item => ({
|
|
||||||
text: item.str,
|
|
||||||
x: item.transform[4],
|
|
||||||
y: item.transform[5],
|
|
||||||
width: item.width,
|
|
||||||
height: item.height
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(fullText);
|
|
||||||
return fullText;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Extract Annotations and Forms
|
|
||||||
```javascript
|
|
||||||
import * as pdfjsLib from 'pdfjs-dist';
|
|
||||||
|
|
||||||
async function extractAnnotations() {
|
|
||||||
const loadingTask = pdfjsLib.getDocument('annotated.pdf');
|
|
||||||
const pdf = await loadingTask.promise;
|
|
||||||
|
|
||||||
for (let i = 1; i <= pdf.numPages; i++) {
|
|
||||||
const page = await pdf.getPage(i);
|
|
||||||
const annotations = await page.getAnnotations();
|
|
||||||
|
|
||||||
annotations.forEach(annotation => {
|
|
||||||
console.log(`Annotation type: ${annotation.subtype}`);
|
|
||||||
console.log(`Content: ${annotation.contents}`);
|
|
||||||
console.log(`Coordinates: ${JSON.stringify(annotation.rect)}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Advanced Command-Line Operations
|
|
||||||
|
|
||||||
### poppler-utils Advanced Features
|
|
||||||
|
|
||||||
#### Extract Text with Bounding Box Coordinates
|
|
||||||
```bash
|
|
||||||
# Extract text with bounding box coordinates (essential for structured data)
|
|
||||||
pdftotext -bbox-layout document.pdf output.xml
|
|
||||||
|
|
||||||
# Force UTF-8 text output when needed
|
|
||||||
pdftotext -enc UTF-8 document.pdf output.txt
|
|
||||||
|
|
||||||
# The XML output contains precise coordinates for each text element
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Advanced Image Conversion
|
|
||||||
```bash
|
|
||||||
# Convert to PNG images with specific resolution
|
|
||||||
pdftoppm -png -r 300 document.pdf output_prefix
|
|
||||||
|
|
||||||
# Convert specific page range with high resolution
|
|
||||||
pdftoppm -png -r 600 -f 1 -l 3 document.pdf high_res_pages
|
|
||||||
|
|
||||||
# Convert to JPEG with quality setting
|
|
||||||
pdftoppm -jpeg -jpegopt quality=85 -r 200 document.pdf jpeg_output
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Extract Embedded Images
|
|
||||||
```bash
|
|
||||||
# Extract all embedded images with metadata
|
|
||||||
pdfimages -j -p document.pdf page_images
|
|
||||||
|
|
||||||
# List image info without extracting
|
|
||||||
pdfimages -list document.pdf
|
|
||||||
|
|
||||||
# Extract images in their original format
|
|
||||||
pdfimages -all document.pdf images/img
|
|
||||||
```
|
|
||||||
|
|
||||||
### qpdf Advanced Features
|
|
||||||
|
|
||||||
#### Complex Page Manipulation
|
|
||||||
```bash
|
|
||||||
# Split PDF into groups of pages
|
|
||||||
qpdf --split-pages=3 input.pdf output_group_%02d.pdf
|
|
||||||
|
|
||||||
# Extract specific pages with complex ranges
|
|
||||||
qpdf input.pdf --pages input.pdf 1,3-5,8,10-end -- extracted.pdf
|
|
||||||
|
|
||||||
# Merge specific pages from multiple PDFs
|
|
||||||
qpdf --empty --pages doc1.pdf 1-3 doc2.pdf 5-7 doc3.pdf 2,4 -- combined.pdf
|
|
||||||
```
|
|
||||||
|
|
||||||
#### PDF Optimization and Repair
|
|
||||||
```bash
|
|
||||||
# Optimize PDF for web (linearize for streaming)
|
|
||||||
qpdf --linearize input.pdf optimized.pdf
|
|
||||||
|
|
||||||
# Remove unused objects and compress
|
|
||||||
qpdf --optimize-level=all input.pdf compressed.pdf
|
|
||||||
|
|
||||||
# Attempt to repair corrupted PDF structure
|
|
||||||
qpdf --check input.pdf
|
|
||||||
qpdf --fix-qdf damaged.pdf repaired.pdf
|
|
||||||
|
|
||||||
# Show detailed PDF structure for debugging
|
|
||||||
qpdf --show-all-pages input.pdf > structure.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Advanced Encryption
|
|
||||||
```bash
|
|
||||||
# Add password protection with specific permissions
|
|
||||||
qpdf --encrypt user_pass owner_pass 256 --print=none --modify=none -- input.pdf encrypted.pdf
|
|
||||||
|
|
||||||
# Check encryption status
|
|
||||||
qpdf --show-encryption encrypted.pdf
|
|
||||||
|
|
||||||
# Remove password protection (requires password)
|
|
||||||
qpdf --password=secret123 --decrypt encrypted.pdf decrypted.pdf
|
|
||||||
```
|
|
||||||
|
|
||||||
## Advanced Python Techniques
|
|
||||||
|
|
||||||
### pdfplumber Advanced Features
|
|
||||||
|
|
||||||
#### Extract Text with Precise Coordinates
|
|
||||||
```python
|
|
||||||
import pdfplumber
|
|
||||||
|
|
||||||
with pdfplumber.open("document.pdf") as pdf:
|
|
||||||
page = pdf.pages[0]
|
|
||||||
|
|
||||||
# Extract all text with coordinates
|
|
||||||
chars = page.chars
|
|
||||||
for char in chars[:10]: # First 10 characters
|
|
||||||
print(f"Char: '{char['text']}' at x:{char['x0']:.1f} y:{char['y0']:.1f}")
|
|
||||||
|
|
||||||
# Extract text by bounding box (left, top, right, bottom)
|
|
||||||
bbox_text = page.within_bbox((100, 100, 400, 200)).extract_text()
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Advanced Table Extraction with Custom Settings
|
|
||||||
```python
|
|
||||||
import pdfplumber
|
|
||||||
import pandas as pd
|
|
||||||
|
|
||||||
with pdfplumber.open("complex_table.pdf") as pdf:
|
|
||||||
page = pdf.pages[0]
|
|
||||||
|
|
||||||
# Extract tables with custom settings for complex layouts
|
|
||||||
table_settings = {
|
|
||||||
"vertical_strategy": "lines",
|
|
||||||
"horizontal_strategy": "lines",
|
|
||||||
"snap_tolerance": 3,
|
|
||||||
"intersection_tolerance": 15
|
|
||||||
}
|
|
||||||
tables = page.extract_tables(table_settings)
|
|
||||||
|
|
||||||
# Visual debugging for table extraction
|
|
||||||
img = page.to_image(resolution=150)
|
|
||||||
img.save("debug_layout.png")
|
|
||||||
```
|
|
||||||
|
|
||||||
### reportlab Advanced Features
|
|
||||||
|
|
||||||
#### Create Professional Reports with Tables
|
|
||||||
```python
|
|
||||||
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph
|
|
||||||
from reportlab.lib.styles import getSampleStyleSheet
|
|
||||||
from reportlab.lib import colors
|
|
||||||
|
|
||||||
# Sample data
|
|
||||||
data = [
|
|
||||||
['Product', 'Q1', 'Q2', 'Q3', 'Q4'],
|
|
||||||
['Widgets', '120', '135', '142', '158'],
|
|
||||||
['Gadgets', '85', '92', '98', '105']
|
|
||||||
]
|
|
||||||
|
|
||||||
# Create PDF with table
|
|
||||||
doc = SimpleDocTemplate("report.pdf")
|
|
||||||
elements = []
|
|
||||||
|
|
||||||
# Add title
|
|
||||||
styles = getSampleStyleSheet()
|
|
||||||
title = Paragraph("Quarterly Sales Report", styles['Title'])
|
|
||||||
elements.append(title)
|
|
||||||
|
|
||||||
# Add table with advanced styling
|
|
||||||
table = Table(data)
|
|
||||||
table.setStyle(TableStyle([
|
|
||||||
('BACKGROUND', (0, 0), (-1, 0), colors.grey),
|
|
||||||
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
|
|
||||||
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
|
|
||||||
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
|
|
||||||
('FONTSIZE', (0, 0), (-1, 0), 14),
|
|
||||||
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
|
|
||||||
('BACKGROUND', (0, 1), (-1, -1), colors.beige),
|
|
||||||
('GRID', (0, 0), (-1, -1), 1, colors.black)
|
|
||||||
]))
|
|
||||||
elements.append(table)
|
|
||||||
|
|
||||||
doc.build(elements)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Complex Workflows
|
|
||||||
|
|
||||||
### Extract Figures/Images from PDF
|
|
||||||
|
|
||||||
#### Method 1: Using pdfimages (fastest)
|
|
||||||
```bash
|
|
||||||
# Extract all images with original quality
|
|
||||||
pdfimages -all document.pdf images/img
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Method 2: Using pypdfium2 + Image Processing
|
|
||||||
```python
|
|
||||||
import pypdfium2 as pdfium
|
|
||||||
from PIL import Image
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
def extract_figures(pdf_path, output_dir):
|
|
||||||
pdf = pdfium.PdfDocument(pdf_path)
|
|
||||||
|
|
||||||
for page_num, page in enumerate(pdf):
|
|
||||||
# Render high-resolution page
|
|
||||||
bitmap = page.render(scale=3.0)
|
|
||||||
img = bitmap.to_pil()
|
|
||||||
|
|
||||||
# Convert to numpy for processing
|
|
||||||
img_array = np.array(img)
|
|
||||||
|
|
||||||
# Simple figure detection (non-white regions)
|
|
||||||
mask = np.any(img_array != [255, 255, 255], axis=2)
|
|
||||||
|
|
||||||
# Find contours and extract bounding boxes
|
|
||||||
# (This is simplified - real implementation would need more sophisticated detection)
|
|
||||||
|
|
||||||
# Save detected figures
|
|
||||||
# ... implementation depends on specific needs
|
|
||||||
```
|
|
||||||
|
|
||||||
### Batch PDF Processing with Error Handling
|
|
||||||
```python
|
|
||||||
import os
|
|
||||||
import glob
|
|
||||||
from pypdf import PdfReader, PdfWriter
|
|
||||||
import logging
|
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
def batch_process_pdfs(input_dir, operation='merge'):
|
|
||||||
pdf_files = glob.glob(os.path.join(input_dir, "*.pdf"))
|
|
||||||
|
|
||||||
if operation == 'merge':
|
|
||||||
writer = PdfWriter()
|
|
||||||
for pdf_file in pdf_files:
|
|
||||||
try:
|
|
||||||
reader = PdfReader(pdf_file)
|
|
||||||
for page in reader.pages:
|
|
||||||
writer.add_page(page)
|
|
||||||
logger.info(f"Processed: {pdf_file}")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Failed to process {pdf_file}: {e}")
|
|
||||||
continue
|
|
||||||
|
|
||||||
with open("batch_merged.pdf", "wb") as output:
|
|
||||||
writer.write(output)
|
|
||||||
|
|
||||||
elif operation == 'extract_text':
|
|
||||||
for pdf_file in pdf_files:
|
|
||||||
try:
|
|
||||||
reader = PdfReader(pdf_file)
|
|
||||||
text = ""
|
|
||||||
for page in reader.pages:
|
|
||||||
text += page.extract_text()
|
|
||||||
|
|
||||||
output_file = pdf_file.replace('.pdf', '.txt')
|
|
||||||
with open(output_file, 'w', encoding='utf-8') as f:
|
|
||||||
f.write(text)
|
|
||||||
logger.info(f"Extracted text from: {pdf_file}")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Failed to extract text from {pdf_file}: {e}")
|
|
||||||
continue
|
|
||||||
```
|
|
||||||
|
|
||||||
### Advanced PDF Cropping
|
|
||||||
```python
|
|
||||||
from pypdf import PdfWriter, PdfReader
|
|
||||||
|
|
||||||
reader = PdfReader("input.pdf")
|
|
||||||
writer = PdfWriter()
|
|
||||||
|
|
||||||
# Crop page (left, bottom, right, top in points)
|
|
||||||
page = reader.pages[0]
|
|
||||||
page.mediabox.left = 50
|
|
||||||
page.mediabox.bottom = 50
|
|
||||||
page.mediabox.right = 550
|
|
||||||
page.mediabox.top = 750
|
|
||||||
|
|
||||||
writer.add_page(page)
|
|
||||||
with open("cropped.pdf", "wb") as output:
|
|
||||||
writer.write(output)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Performance Optimization Tips
|
|
||||||
|
|
||||||
### 1. For Large PDFs
|
|
||||||
- Use streaming approaches instead of loading entire PDF in memory
|
|
||||||
- Use `qpdf --split-pages` for splitting large files
|
|
||||||
- Process pages individually with pypdfium2
|
|
||||||
|
|
||||||
### 2. For Text Extraction
|
|
||||||
- `pdftotext -bbox-layout` is fastest for plain text extraction
|
|
||||||
- Use pdfplumber for structured data and tables
|
|
||||||
- Avoid `pypdf.extract_text()` for very large documents
|
|
||||||
|
|
||||||
### 3. For Image Extraction
|
|
||||||
- `pdfimages` is much faster than rendering pages
|
|
||||||
- Use low resolution for previews, high resolution for final output
|
|
||||||
|
|
||||||
### 4. For Form Filling
|
|
||||||
- pdf-lib maintains form structure better than most alternatives
|
|
||||||
- Pre-validate form fields before processing
|
|
||||||
|
|
||||||
### 5. Memory Management
|
|
||||||
```python
|
|
||||||
# Process PDFs in chunks
|
|
||||||
def process_large_pdf(pdf_path, chunk_size=10):
|
|
||||||
reader = PdfReader(pdf_path)
|
|
||||||
total_pages = len(reader.pages)
|
|
||||||
|
|
||||||
for start_idx in range(0, total_pages, chunk_size):
|
|
||||||
end_idx = min(start_idx + chunk_size, total_pages)
|
|
||||||
writer = PdfWriter()
|
|
||||||
|
|
||||||
for i in range(start_idx, end_idx):
|
|
||||||
writer.add_page(reader.pages[i])
|
|
||||||
|
|
||||||
# Process chunk
|
|
||||||
with open(f"chunk_{start_idx//chunk_size}.pdf", "wb") as output:
|
|
||||||
writer.write(output)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Troubleshooting Common Issues
|
|
||||||
|
|
||||||
### Encrypted PDFs
|
|
||||||
```python
|
|
||||||
# Handle password-protected PDFs
|
|
||||||
from pypdf import PdfReader
|
|
||||||
|
|
||||||
try:
|
|
||||||
reader = PdfReader("encrypted.pdf")
|
|
||||||
if reader.is_encrypted:
|
|
||||||
reader.decrypt("password")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Failed to decrypt: {e}")
|
|
||||||
```
|
|
||||||
|
|
||||||
### Corrupted PDFs
|
|
||||||
```bash
|
|
||||||
# Use qpdf to repair
|
|
||||||
qpdf --check corrupted.pdf
|
|
||||||
qpdf --replace-input corrupted.pdf
|
|
||||||
```
|
|
||||||
|
|
||||||
### Text Extraction Issues
|
|
||||||
```python
|
|
||||||
# Fallback to OCR for scanned PDFs
|
|
||||||
import pytesseract
|
|
||||||
from pdf2image import convert_from_path
|
|
||||||
|
|
||||||
def extract_text_with_ocr(pdf_path):
|
|
||||||
images = convert_from_path(pdf_path)
|
|
||||||
text = ""
|
|
||||||
for i, image in enumerate(images):
|
|
||||||
text += pytesseract.image_to_string(image, lang='chi_sim+eng')
|
|
||||||
return text
|
|
||||||
```
|
|
||||||
|
|
||||||
## License Information
|
|
||||||
|
|
||||||
- **pypdf**: BSD License
|
|
||||||
- **pdfplumber**: MIT License
|
|
||||||
- **pypdfium2**: Apache/BSD License
|
|
||||||
- **reportlab**: BSD License
|
|
||||||
- **poppler-utils**: GPL-2 License
|
|
||||||
- **qpdf**: Apache License
|
|
||||||
- **pdf-lib**: MIT License
|
|
||||||
- **pdfjs-dist**: Apache License
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
from dataclasses import dataclass
|
|
||||||
import json
|
|
||||||
import sys
|
|
||||||
|
|
||||||
|
|
||||||
# Script to check that the `fields.json` file that Claude creates when analyzing PDFs
|
|
||||||
# does not have overlapping bounding boxes. See forms.md.
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class RectAndField:
|
|
||||||
rect: list[float]
|
|
||||||
rect_type: str
|
|
||||||
field: dict
|
|
||||||
|
|
||||||
|
|
||||||
# Returns a list of messages that are printed to stdout for Claude to read.
|
|
||||||
def get_bounding_box_messages(fields_json_stream) -> list[str]:
|
|
||||||
messages = []
|
|
||||||
fields = json.load(fields_json_stream)
|
|
||||||
messages.append(f"Read {len(fields['form_fields'])} fields")
|
|
||||||
|
|
||||||
def rects_intersect(r1, r2):
|
|
||||||
disjoint_horizontal = r1[0] >= r2[2] or r1[2] <= r2[0]
|
|
||||||
disjoint_vertical = r1[1] >= r2[3] or r1[3] <= r2[1]
|
|
||||||
return not (disjoint_horizontal or disjoint_vertical)
|
|
||||||
|
|
||||||
rects_and_fields = []
|
|
||||||
for f in fields["form_fields"]:
|
|
||||||
rects_and_fields.append(RectAndField(f["label_bounding_box"], "label", f))
|
|
||||||
rects_and_fields.append(RectAndField(f["entry_bounding_box"], "entry", f))
|
|
||||||
|
|
||||||
has_error = False
|
|
||||||
for i, ri in enumerate(rects_and_fields):
|
|
||||||
# This is O(N^2); we can optimize if it becomes a problem.
|
|
||||||
for j in range(i + 1, len(rects_and_fields)):
|
|
||||||
rj = rects_and_fields[j]
|
|
||||||
if ri.field["page_number"] == rj.field["page_number"] and rects_intersect(ri.rect, rj.rect):
|
|
||||||
has_error = True
|
|
||||||
if ri.field is rj.field:
|
|
||||||
messages.append(f"FAILURE: intersection between label and entry bounding boxes for `{ri.field['description']}` ({ri.rect}, {rj.rect})")
|
|
||||||
else:
|
|
||||||
messages.append(f"FAILURE: intersection between {ri.rect_type} bounding box for `{ri.field['description']}` ({ri.rect}) and {rj.rect_type} bounding box for `{rj.field['description']}` ({rj.rect})")
|
|
||||||
if len(messages) >= 20:
|
|
||||||
messages.append("Aborting further checks; fix bounding boxes and try again")
|
|
||||||
return messages
|
|
||||||
if ri.rect_type == "entry":
|
|
||||||
if "entry_text" in ri.field:
|
|
||||||
font_size = ri.field["entry_text"].get("font_size", 14)
|
|
||||||
entry_height = ri.rect[3] - ri.rect[1]
|
|
||||||
if entry_height < font_size:
|
|
||||||
has_error = True
|
|
||||||
messages.append(f"FAILURE: entry bounding box height ({entry_height}) for `{ri.field['description']}` is too short for the text content (font size: {font_size}). Increase the box height or decrease the font size.")
|
|
||||||
if len(messages) >= 20:
|
|
||||||
messages.append("Aborting further checks; fix bounding boxes and try again")
|
|
||||||
return messages
|
|
||||||
|
|
||||||
if not has_error:
|
|
||||||
messages.append("SUCCESS: All bounding boxes are valid")
|
|
||||||
return messages
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
if len(sys.argv) != 2:
|
|
||||||
print("Usage: check_bounding_boxes.py [fields.json]")
|
|
||||||
sys.exit(1)
|
|
||||||
# Input file should be in the `fields.json` format described in forms.md.
|
|
||||||
with open(sys.argv[1], encoding="utf-8") as f:
|
|
||||||
messages = get_bounding_box_messages(f)
|
|
||||||
for msg in messages:
|
|
||||||
print(msg)
|
|
||||||
@@ -1,226 +0,0 @@
|
|||||||
import unittest
|
|
||||||
import json
|
|
||||||
import io
|
|
||||||
from check_bounding_boxes import get_bounding_box_messages
|
|
||||||
|
|
||||||
|
|
||||||
# Currently this is not run automatically in CI; it's just for documentation and manual checking.
|
|
||||||
class TestGetBoundingBoxMessages(unittest.TestCase):
|
|
||||||
|
|
||||||
def create_json_stream(self, data):
|
|
||||||
"""Helper to create a JSON stream from data"""
|
|
||||||
return io.StringIO(json.dumps(data))
|
|
||||||
|
|
||||||
def test_no_intersections(self):
|
|
||||||
"""Test case with no bounding box intersections"""
|
|
||||||
data = {
|
|
||||||
"form_fields": [
|
|
||||||
{
|
|
||||||
"description": "Name",
|
|
||||||
"page_number": 1,
|
|
||||||
"label_bounding_box": [10, 10, 50, 30],
|
|
||||||
"entry_bounding_box": [60, 10, 150, 30]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Email",
|
|
||||||
"page_number": 1,
|
|
||||||
"label_bounding_box": [10, 40, 50, 60],
|
|
||||||
"entry_bounding_box": [60, 40, 150, 60]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
stream = self.create_json_stream(data)
|
|
||||||
messages = get_bounding_box_messages(stream)
|
|
||||||
self.assertTrue(any("SUCCESS" in msg for msg in messages))
|
|
||||||
self.assertFalse(any("FAILURE" in msg for msg in messages))
|
|
||||||
|
|
||||||
def test_label_entry_intersection_same_field(self):
|
|
||||||
"""Test intersection between label and entry of the same field"""
|
|
||||||
data = {
|
|
||||||
"form_fields": [
|
|
||||||
{
|
|
||||||
"description": "Name",
|
|
||||||
"page_number": 1,
|
|
||||||
"label_bounding_box": [10, 10, 60, 30],
|
|
||||||
"entry_bounding_box": [50, 10, 150, 30] # Overlaps with label
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
stream = self.create_json_stream(data)
|
|
||||||
messages = get_bounding_box_messages(stream)
|
|
||||||
self.assertTrue(any("FAILURE" in msg and "intersection" in msg for msg in messages))
|
|
||||||
self.assertFalse(any("SUCCESS" in msg for msg in messages))
|
|
||||||
|
|
||||||
def test_intersection_between_different_fields(self):
|
|
||||||
"""Test intersection between bounding boxes of different fields"""
|
|
||||||
data = {
|
|
||||||
"form_fields": [
|
|
||||||
{
|
|
||||||
"description": "Name",
|
|
||||||
"page_number": 1,
|
|
||||||
"label_bounding_box": [10, 10, 50, 30],
|
|
||||||
"entry_bounding_box": [60, 10, 150, 30]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Email",
|
|
||||||
"page_number": 1,
|
|
||||||
"label_bounding_box": [40, 20, 80, 40], # Overlaps with Name's boxes
|
|
||||||
"entry_bounding_box": [160, 10, 250, 30]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
stream = self.create_json_stream(data)
|
|
||||||
messages = get_bounding_box_messages(stream)
|
|
||||||
self.assertTrue(any("FAILURE" in msg and "intersection" in msg for msg in messages))
|
|
||||||
self.assertFalse(any("SUCCESS" in msg for msg in messages))
|
|
||||||
|
|
||||||
def test_different_pages_no_intersection(self):
|
|
||||||
"""Test that boxes on different pages don't count as intersecting"""
|
|
||||||
data = {
|
|
||||||
"form_fields": [
|
|
||||||
{
|
|
||||||
"description": "Name",
|
|
||||||
"page_number": 1,
|
|
||||||
"label_bounding_box": [10, 10, 50, 30],
|
|
||||||
"entry_bounding_box": [60, 10, 150, 30]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Email",
|
|
||||||
"page_number": 2,
|
|
||||||
"label_bounding_box": [10, 10, 50, 30], # Same coordinates but different page
|
|
||||||
"entry_bounding_box": [60, 10, 150, 30]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
stream = self.create_json_stream(data)
|
|
||||||
messages = get_bounding_box_messages(stream)
|
|
||||||
self.assertTrue(any("SUCCESS" in msg for msg in messages))
|
|
||||||
self.assertFalse(any("FAILURE" in msg for msg in messages))
|
|
||||||
|
|
||||||
def test_entry_height_too_small(self):
|
|
||||||
"""Test that entry box height is checked against font size"""
|
|
||||||
data = {
|
|
||||||
"form_fields": [
|
|
||||||
{
|
|
||||||
"description": "Name",
|
|
||||||
"page_number": 1,
|
|
||||||
"label_bounding_box": [10, 10, 50, 30],
|
|
||||||
"entry_bounding_box": [60, 10, 150, 20], # Height is 10
|
|
||||||
"entry_text": {
|
|
||||||
"font_size": 14 # Font size larger than height
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
stream = self.create_json_stream(data)
|
|
||||||
messages = get_bounding_box_messages(stream)
|
|
||||||
self.assertTrue(any("FAILURE" in msg and "height" in msg for msg in messages))
|
|
||||||
self.assertFalse(any("SUCCESS" in msg for msg in messages))
|
|
||||||
|
|
||||||
def test_entry_height_adequate(self):
|
|
||||||
"""Test that adequate entry box height passes"""
|
|
||||||
data = {
|
|
||||||
"form_fields": [
|
|
||||||
{
|
|
||||||
"description": "Name",
|
|
||||||
"page_number": 1,
|
|
||||||
"label_bounding_box": [10, 10, 50, 30],
|
|
||||||
"entry_bounding_box": [60, 10, 150, 30], # Height is 20
|
|
||||||
"entry_text": {
|
|
||||||
"font_size": 14 # Font size smaller than height
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
stream = self.create_json_stream(data)
|
|
||||||
messages = get_bounding_box_messages(stream)
|
|
||||||
self.assertTrue(any("SUCCESS" in msg for msg in messages))
|
|
||||||
self.assertFalse(any("FAILURE" in msg for msg in messages))
|
|
||||||
|
|
||||||
def test_default_font_size(self):
|
|
||||||
"""Test that default font size is used when not specified"""
|
|
||||||
data = {
|
|
||||||
"form_fields": [
|
|
||||||
{
|
|
||||||
"description": "Name",
|
|
||||||
"page_number": 1,
|
|
||||||
"label_bounding_box": [10, 10, 50, 30],
|
|
||||||
"entry_bounding_box": [60, 10, 150, 20], # Height is 10
|
|
||||||
"entry_text": {} # No font_size specified, should use default 14
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
stream = self.create_json_stream(data)
|
|
||||||
messages = get_bounding_box_messages(stream)
|
|
||||||
self.assertTrue(any("FAILURE" in msg and "height" in msg for msg in messages))
|
|
||||||
self.assertFalse(any("SUCCESS" in msg for msg in messages))
|
|
||||||
|
|
||||||
def test_no_entry_text(self):
|
|
||||||
"""Test that missing entry_text doesn't cause height check"""
|
|
||||||
data = {
|
|
||||||
"form_fields": [
|
|
||||||
{
|
|
||||||
"description": "Name",
|
|
||||||
"page_number": 1,
|
|
||||||
"label_bounding_box": [10, 10, 50, 30],
|
|
||||||
"entry_bounding_box": [60, 10, 150, 20] # Small height but no entry_text
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
stream = self.create_json_stream(data)
|
|
||||||
messages = get_bounding_box_messages(stream)
|
|
||||||
self.assertTrue(any("SUCCESS" in msg for msg in messages))
|
|
||||||
self.assertFalse(any("FAILURE" in msg for msg in messages))
|
|
||||||
|
|
||||||
def test_multiple_errors_limit(self):
|
|
||||||
"""Test that error messages are limited to prevent excessive output"""
|
|
||||||
fields = []
|
|
||||||
# Create many overlapping fields
|
|
||||||
for i in range(25):
|
|
||||||
fields.append({
|
|
||||||
"description": f"Field{i}",
|
|
||||||
"page_number": 1,
|
|
||||||
"label_bounding_box": [10, 10, 50, 30], # All overlap
|
|
||||||
"entry_bounding_box": [20, 15, 60, 35] # All overlap
|
|
||||||
})
|
|
||||||
|
|
||||||
data = {"form_fields": fields}
|
|
||||||
|
|
||||||
stream = self.create_json_stream(data)
|
|
||||||
messages = get_bounding_box_messages(stream)
|
|
||||||
# Should abort after ~20 messages
|
|
||||||
self.assertTrue(any("Aborting" in msg for msg in messages))
|
|
||||||
# Should have some FAILURE messages but not hundreds
|
|
||||||
failure_count = sum(1 for msg in messages if "FAILURE" in msg)
|
|
||||||
self.assertGreater(failure_count, 0)
|
|
||||||
self.assertLess(len(messages), 30) # Should be limited
|
|
||||||
|
|
||||||
def test_edge_touching_boxes(self):
|
|
||||||
"""Test that boxes touching at edges don't count as intersecting"""
|
|
||||||
data = {
|
|
||||||
"form_fields": [
|
|
||||||
{
|
|
||||||
"description": "Name",
|
|
||||||
"page_number": 1,
|
|
||||||
"label_bounding_box": [10, 10, 50, 30],
|
|
||||||
"entry_bounding_box": [50, 10, 150, 30] # Touches at x=50
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
stream = self.create_json_stream(data)
|
|
||||||
messages = get_bounding_box_messages(stream)
|
|
||||||
self.assertTrue(any("SUCCESS" in msg for msg in messages))
|
|
||||||
self.assertFalse(any("FAILURE" in msg for msg in messages))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
unittest.main()
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
import sys
|
|
||||||
|
|
||||||
|
|
||||||
# Script for Claude to run to determine whether a PDF has fillable form fields. See forms.md.
|
|
||||||
|
|
||||||
|
|
||||||
def has_fillable_fields(pdf_path: str) -> bool:
|
|
||||||
try:
|
|
||||||
from pypdf import PdfReader
|
|
||||||
except ModuleNotFoundError as exc:
|
|
||||||
raise RuntimeError("Missing dependency: pypdf") from exc
|
|
||||||
|
|
||||||
reader = PdfReader(pdf_path)
|
|
||||||
return bool(reader.get_fields())
|
|
||||||
|
|
||||||
|
|
||||||
def main(argv: list[str] | None = None) -> int:
|
|
||||||
args = argv if argv is not None else sys.argv[1:]
|
|
||||||
if len(args) != 1:
|
|
||||||
print("Usage: check_fillable_fields.py [input pdf]")
|
|
||||||
return 1
|
|
||||||
|
|
||||||
try:
|
|
||||||
if has_fillable_fields(args[0]):
|
|
||||||
print("This PDF has fillable form fields")
|
|
||||||
else:
|
|
||||||
print("This PDF does not have fillable form fields; you will need to visually determine where to enter data")
|
|
||||||
return 0
|
|
||||||
except RuntimeError as exc:
|
|
||||||
print(str(exc), file=sys.stderr)
|
|
||||||
return 1
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
raise SystemExit(main())
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from pdf2image import convert_from_path
|
|
||||||
from pdf2image.exceptions import PDFInfoNotInstalledError
|
|
||||||
|
|
||||||
|
|
||||||
# Converts each page of a PDF to a PNG image.
|
|
||||||
|
|
||||||
|
|
||||||
def convert(pdf_path, output_dir, max_dim=1000):
|
|
||||||
output_path = Path(output_dir)
|
|
||||||
output_path.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
try:
|
|
||||||
images = convert_from_path(pdf_path, dpi=200)
|
|
||||||
except PDFInfoNotInstalledError:
|
|
||||||
raise RuntimeError(
|
|
||||||
"Poppler is required. Install Poppler and ensure `pdfinfo` is available in PATH."
|
|
||||||
)
|
|
||||||
|
|
||||||
for i, image in enumerate(images):
|
|
||||||
# Scale image if needed to keep width/height under `max_dim`
|
|
||||||
width, height = image.size
|
|
||||||
if width > max_dim or height > max_dim:
|
|
||||||
scale_factor = min(max_dim / width, max_dim / height)
|
|
||||||
new_width = int(width * scale_factor)
|
|
||||||
new_height = int(height * scale_factor)
|
|
||||||
image = image.resize((new_width, new_height))
|
|
||||||
|
|
||||||
image_path = output_path / f"page_{i+1}.png"
|
|
||||||
image.save(str(image_path))
|
|
||||||
print(f"Saved page {i+1} as {image_path} (size: {image.size})")
|
|
||||||
|
|
||||||
print(f"Converted {len(images)} pages to PNG images")
|
|
||||||
|
|
||||||
|
|
||||||
def main(argv: list[str] | None = None) -> int:
|
|
||||||
args = argv if argv is not None else sys.argv[1:]
|
|
||||||
if len(args) != 2:
|
|
||||||
print("Usage: convert_pdf_to_images.py [input pdf] [output directory]")
|
|
||||||
return 1
|
|
||||||
|
|
||||||
pdf_path = args[0]
|
|
||||||
output_directory = args[1]
|
|
||||||
try:
|
|
||||||
convert(pdf_path, output_directory)
|
|
||||||
return 0
|
|
||||||
except RuntimeError as exc:
|
|
||||||
print(str(exc), file=sys.stderr)
|
|
||||||
return 1
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
raise SystemExit(main())
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
import json
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from PIL import Image, ImageDraw
|
|
||||||
|
|
||||||
|
|
||||||
# Creates "validation" images with rectangles for the bounding box information that
|
|
||||||
# Claude creates when determining where to add text annotations in PDFs. See forms.md.
|
|
||||||
|
|
||||||
|
|
||||||
def create_validation_image(page_number, fields_json_path, input_path, output_path):
|
|
||||||
# Input file should be in the `fields.json` format described in forms.md.
|
|
||||||
with open(fields_json_path, "r", encoding="utf-8") as file_obj:
|
|
||||||
data = json.load(file_obj)
|
|
||||||
|
|
||||||
with Image.open(input_path) as image:
|
|
||||||
draw = ImageDraw.Draw(image)
|
|
||||||
num_boxes = 0
|
|
||||||
|
|
||||||
for field in data["form_fields"]:
|
|
||||||
if field["page_number"] != page_number:
|
|
||||||
continue
|
|
||||||
entry_box = field["entry_bounding_box"]
|
|
||||||
label_box = field["label_bounding_box"]
|
|
||||||
# Draw red rectangle over entry bounding box and blue rectangle over the label.
|
|
||||||
draw.rectangle(entry_box, outline="red", width=2)
|
|
||||||
draw.rectangle(label_box, outline="blue", width=2)
|
|
||||||
num_boxes += 2
|
|
||||||
|
|
||||||
image.save(output_path)
|
|
||||||
|
|
||||||
print(f"Created validation image at {output_path} with {num_boxes} bounding boxes")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
if len(sys.argv) != 5:
|
|
||||||
print("Usage: create_validation_image.py [page number] [fields.json file] [input image path] [output image path]")
|
|
||||||
sys.exit(1)
|
|
||||||
page_number = int(sys.argv[1])
|
|
||||||
fields_json_path = sys.argv[2]
|
|
||||||
input_image_path = sys.argv[3]
|
|
||||||
output_image_path = sys.argv[4]
|
|
||||||
create_validation_image(page_number, fields_json_path, input_image_path, output_image_path)
|
|
||||||
@@ -1,152 +0,0 @@
|
|||||||
import json
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from pypdf import PdfReader
|
|
||||||
|
|
||||||
|
|
||||||
# Extracts data for the fillable form fields in a PDF and outputs JSON that
|
|
||||||
# Claude uses to fill the fields. See forms.md.
|
|
||||||
|
|
||||||
|
|
||||||
# This matches the format used by PdfReader `get_fields` and `update_page_form_field_values` methods.
|
|
||||||
def get_full_annotation_field_id(annotation):
|
|
||||||
components = []
|
|
||||||
while annotation:
|
|
||||||
field_name = annotation.get('/T')
|
|
||||||
if field_name:
|
|
||||||
components.append(field_name)
|
|
||||||
annotation = annotation.get('/Parent')
|
|
||||||
return ".".join(reversed(components)) if components else None
|
|
||||||
|
|
||||||
|
|
||||||
def make_field_dict(field, field_id):
|
|
||||||
field_dict = {"field_id": field_id}
|
|
||||||
ft = field.get('/FT')
|
|
||||||
if ft == "/Tx":
|
|
||||||
field_dict["type"] = "text"
|
|
||||||
elif ft == "/Btn":
|
|
||||||
field_dict["type"] = "checkbox" # radio groups handled separately
|
|
||||||
states = field.get("/_States_", [])
|
|
||||||
if len(states) == 2:
|
|
||||||
# "/Off" seems to always be the unchecked value, as suggested by
|
|
||||||
# https://opensource.adobe.com/dc-acrobat-sdk-docs/standards/pdfstandards/pdf/PDF32000_2008.pdf#page=448
|
|
||||||
# It can be either first or second in the "/_States_" list.
|
|
||||||
if "/Off" in states:
|
|
||||||
field_dict["checked_value"] = states[0] if states[0] != "/Off" else states[1]
|
|
||||||
field_dict["unchecked_value"] = "/Off"
|
|
||||||
else:
|
|
||||||
print(f"Unexpected state values for checkbox `${field_id}`. Its checked and unchecked values may not be correct; if you're trying to check it, visually verify the results.")
|
|
||||||
field_dict["checked_value"] = states[0]
|
|
||||||
field_dict["unchecked_value"] = states[1]
|
|
||||||
elif ft == "/Ch":
|
|
||||||
field_dict["type"] = "choice"
|
|
||||||
states = field.get("/_States_", [])
|
|
||||||
field_dict["choice_options"] = [{
|
|
||||||
"value": state[0],
|
|
||||||
"text": state[1],
|
|
||||||
} for state in states]
|
|
||||||
else:
|
|
||||||
field_dict["type"] = f"unknown ({ft})"
|
|
||||||
return field_dict
|
|
||||||
|
|
||||||
|
|
||||||
# Returns a list of fillable PDF fields:
|
|
||||||
# [
|
|
||||||
# {
|
|
||||||
# "field_id": "name",
|
|
||||||
# "page": 1,
|
|
||||||
# "type": ("text", "checkbox", "radio_group", or "choice")
|
|
||||||
# // Per-type additional fields described in forms.md
|
|
||||||
# },
|
|
||||||
# ]
|
|
||||||
def get_field_info(reader: PdfReader):
|
|
||||||
fields = reader.get_fields()
|
|
||||||
|
|
||||||
field_info_by_id = {}
|
|
||||||
possible_radio_names = set()
|
|
||||||
|
|
||||||
for field_id, field in fields.items():
|
|
||||||
# Skip if this is a container field with children, except that it might be
|
|
||||||
# a parent group for radio button options.
|
|
||||||
if field.get("/Kids"):
|
|
||||||
if field.get("/FT") == "/Btn":
|
|
||||||
possible_radio_names.add(field_id)
|
|
||||||
continue
|
|
||||||
field_info_by_id[field_id] = make_field_dict(field, field_id)
|
|
||||||
|
|
||||||
# Bounding rects are stored in annotations in page objects.
|
|
||||||
|
|
||||||
# Radio button options have a separate annotation for each choice;
|
|
||||||
# all choices have the same field name.
|
|
||||||
# See https://westhealth.github.io/exploring-fillable-forms-with-pdfrw.html
|
|
||||||
radio_fields_by_id = {}
|
|
||||||
|
|
||||||
for page_index, page in enumerate(reader.pages):
|
|
||||||
annotations = page.get('/Annots', [])
|
|
||||||
for ann in annotations:
|
|
||||||
field_id = get_full_annotation_field_id(ann)
|
|
||||||
if field_id in field_info_by_id:
|
|
||||||
field_info_by_id[field_id]["page"] = page_index + 1
|
|
||||||
field_info_by_id[field_id]["rect"] = ann.get('/Rect')
|
|
||||||
elif field_id in possible_radio_names:
|
|
||||||
try:
|
|
||||||
# ann['/AP']['/N'] should have two items. One of them is '/Off',
|
|
||||||
# the other is the active value.
|
|
||||||
on_values = [v for v in ann["/AP"]["/N"] if v != "/Off"]
|
|
||||||
except KeyError:
|
|
||||||
continue
|
|
||||||
if len(on_values) == 1:
|
|
||||||
rect = ann.get("/Rect")
|
|
||||||
if field_id not in radio_fields_by_id:
|
|
||||||
radio_fields_by_id[field_id] = {
|
|
||||||
"field_id": field_id,
|
|
||||||
"type": "radio_group",
|
|
||||||
"page": page_index + 1,
|
|
||||||
"radio_options": [],
|
|
||||||
}
|
|
||||||
# Note: at least on macOS 15.7, Preview.app doesn't show selected
|
|
||||||
# radio buttons correctly. (It does if you remove the leading slash
|
|
||||||
# from the value, but that causes them not to appear correctly in
|
|
||||||
# Chrome/Firefox/Acrobat/etc).
|
|
||||||
radio_fields_by_id[field_id]["radio_options"].append({
|
|
||||||
"value": on_values[0],
|
|
||||||
"rect": rect,
|
|
||||||
})
|
|
||||||
|
|
||||||
# Some PDFs have form field definitions without corresponding annotations,
|
|
||||||
# so we can't tell where they are. Ignore these fields for now.
|
|
||||||
fields_with_location = []
|
|
||||||
for field_info in field_info_by_id.values():
|
|
||||||
if "page" in field_info:
|
|
||||||
fields_with_location.append(field_info)
|
|
||||||
else:
|
|
||||||
print(f"Unable to determine location for field id: {field_info.get('field_id')}, ignoring")
|
|
||||||
|
|
||||||
# Sort by page number, then Y position (flipped in PDF coordinate system), then X.
|
|
||||||
def sort_key(f):
|
|
||||||
if "radio_options" in f:
|
|
||||||
rect = f["radio_options"][0]["rect"] or [0, 0, 0, 0]
|
|
||||||
else:
|
|
||||||
rect = f.get("rect") or [0, 0, 0, 0]
|
|
||||||
adjusted_position = [-rect[1], rect[0]]
|
|
||||||
return [f.get("page"), adjusted_position]
|
|
||||||
|
|
||||||
sorted_fields = fields_with_location + list(radio_fields_by_id.values())
|
|
||||||
sorted_fields.sort(key=sort_key)
|
|
||||||
|
|
||||||
return sorted_fields
|
|
||||||
|
|
||||||
|
|
||||||
def write_field_info(pdf_path: str, json_output_path: str):
|
|
||||||
reader = PdfReader(pdf_path)
|
|
||||||
field_info = get_field_info(reader)
|
|
||||||
with open(json_output_path, "w", encoding="utf-8") as f:
|
|
||||||
json.dump(field_info, f, indent=2, ensure_ascii=False)
|
|
||||||
print(f"Wrote {len(field_info)} fields to {json_output_path}")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
if len(sys.argv) != 3:
|
|
||||||
print("Usage: extract_form_field_info.py [input pdf] [output json]")
|
|
||||||
sys.exit(1)
|
|
||||||
write_field_info(sys.argv[1], sys.argv[2])
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
import json
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from pypdf import PdfReader, PdfWriter
|
|
||||||
|
|
||||||
from extract_form_field_info import get_field_info
|
|
||||||
|
|
||||||
|
|
||||||
# Fills fillable form fields in a PDF. See forms.md.
|
|
||||||
|
|
||||||
|
|
||||||
def fill_pdf_fields(input_pdf_path: str, fields_json_path: str, output_pdf_path: str):
|
|
||||||
with open(fields_json_path, encoding="utf-8") as f:
|
|
||||||
fields = json.load(f)
|
|
||||||
# Group by page number.
|
|
||||||
fields_by_page = {}
|
|
||||||
for field in fields:
|
|
||||||
if "value" in field:
|
|
||||||
field_id = field["field_id"]
|
|
||||||
page = field["page"]
|
|
||||||
if page not in fields_by_page:
|
|
||||||
fields_by_page[page] = {}
|
|
||||||
fields_by_page[page][field_id] = field["value"]
|
|
||||||
|
|
||||||
reader = PdfReader(input_pdf_path)
|
|
||||||
|
|
||||||
has_error = False
|
|
||||||
field_info = get_field_info(reader)
|
|
||||||
fields_by_ids = {f["field_id"]: f for f in field_info}
|
|
||||||
for field in fields:
|
|
||||||
existing_field = fields_by_ids.get(field["field_id"])
|
|
||||||
if not existing_field:
|
|
||||||
has_error = True
|
|
||||||
print(f"ERROR: `{field['field_id']}` is not a valid field ID")
|
|
||||||
elif field["page"] != existing_field["page"]:
|
|
||||||
has_error = True
|
|
||||||
print(f"ERROR: Incorrect page number for `{field['field_id']}` (got {field['page']}, expected {existing_field['page']})")
|
|
||||||
else:
|
|
||||||
if "value" in field:
|
|
||||||
err = validation_error_for_field_value(existing_field, field["value"])
|
|
||||||
if err:
|
|
||||||
print(err)
|
|
||||||
has_error = True
|
|
||||||
if has_error:
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
writer = PdfWriter(clone_from=reader)
|
|
||||||
for page, field_values in fields_by_page.items():
|
|
||||||
writer.update_page_form_field_values(writer.pages[page - 1], field_values, auto_regenerate=False)
|
|
||||||
|
|
||||||
# This seems to be necessary for many PDF viewers to format the form values correctly.
|
|
||||||
# It may cause the viewer to show a "save changes" dialog even if the user doesn't make any changes.
|
|
||||||
writer.set_need_appearances_writer(True)
|
|
||||||
|
|
||||||
with open(output_pdf_path, "wb") as f:
|
|
||||||
writer.write(f)
|
|
||||||
|
|
||||||
|
|
||||||
def validation_error_for_field_value(field_info, field_value):
|
|
||||||
field_type = field_info["type"]
|
|
||||||
field_id = field_info["field_id"]
|
|
||||||
if field_type == "checkbox":
|
|
||||||
checked_val = field_info["checked_value"]
|
|
||||||
unchecked_val = field_info["unchecked_value"]
|
|
||||||
if field_value != checked_val and field_value != unchecked_val:
|
|
||||||
return f'ERROR: Invalid value "{field_value}" for checkbox field "{field_id}". The checked value is "{checked_val}" and the unchecked value is "{unchecked_val}"'
|
|
||||||
elif field_type == "radio_group":
|
|
||||||
option_values = [opt["value"] for opt in field_info["radio_options"]]
|
|
||||||
if field_value not in option_values:
|
|
||||||
return f'ERROR: Invalid value "{field_value}" for radio group field "{field_id}". Valid values are: {option_values}'
|
|
||||||
elif field_type == "choice":
|
|
||||||
choice_values = [opt["value"] for opt in field_info["choice_options"]]
|
|
||||||
if field_value not in choice_values:
|
|
||||||
return f'ERROR: Invalid value "{field_value}" for choice field "{field_id}". Valid values are: {choice_values}'
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
# pypdf (at least version 5.7.0) has a bug when setting the value for a selection list field.
|
|
||||||
# In _writer.py around line 966:
|
|
||||||
#
|
|
||||||
# if field.get(FA.FT, "/Tx") == "/Ch" and field_flags & FA.FfBits.Combo == 0:
|
|
||||||
# txt = "\n".join(annotation.get_inherited(FA.Opt, []))
|
|
||||||
#
|
|
||||||
# The problem is that for selection lists, `get_inherited` returns a list of two-element lists like
|
|
||||||
# [["value1", "Text 1"], ["value2", "Text 2"], ...]
|
|
||||||
# This causes `join` to throw a TypeError because it expects an iterable of strings.
|
|
||||||
# The horrible workaround is to patch `get_inherited` to return a list of the value strings.
|
|
||||||
# We call the original method and adjust the return value only if the argument to `get_inherited`
|
|
||||||
# is `FA.Opt` and if the return value is a list of two-element lists.
|
|
||||||
def monkeypatch_pydpf_method():
|
|
||||||
from pypdf.generic import DictionaryObject
|
|
||||||
from pypdf.constants import FieldDictionaryAttributes
|
|
||||||
|
|
||||||
original_get_inherited = DictionaryObject.get_inherited
|
|
||||||
|
|
||||||
def patched_get_inherited(self, key: str, default = None):
|
|
||||||
result = original_get_inherited(self, key, default)
|
|
||||||
if key == FieldDictionaryAttributes.Opt:
|
|
||||||
if isinstance(result, list) and all(isinstance(v, list) and len(v) == 2 for v in result):
|
|
||||||
result = [r[0] for r in result]
|
|
||||||
return result
|
|
||||||
|
|
||||||
DictionaryObject.get_inherited = patched_get_inherited
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
if len(sys.argv) != 4:
|
|
||||||
print("Usage: fill_fillable_fields.py [input pdf] [field_values.json] [output pdf]")
|
|
||||||
sys.exit(1)
|
|
||||||
monkeypatch_pydpf_method()
|
|
||||||
input_pdf = sys.argv[1]
|
|
||||||
fields_json = sys.argv[2]
|
|
||||||
output_pdf = sys.argv[3]
|
|
||||||
fill_pdf_fields(input_pdf, fields_json, output_pdf)
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
import json
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from pypdf import PdfReader, PdfWriter
|
|
||||||
from pypdf.annotations import FreeText
|
|
||||||
|
|
||||||
|
|
||||||
# Fills a PDF by adding text annotations defined in `fields.json`. See forms.md.
|
|
||||||
|
|
||||||
|
|
||||||
def transform_coordinates(bbox, image_width, image_height, pdf_width, pdf_height):
|
|
||||||
"""Transform bounding box from image coordinates to PDF coordinates"""
|
|
||||||
# Image coordinates: origin at top-left, y increases downward
|
|
||||||
# PDF coordinates: origin at bottom-left, y increases upward
|
|
||||||
x_scale = pdf_width / image_width
|
|
||||||
y_scale = pdf_height / image_height
|
|
||||||
|
|
||||||
left = bbox[0] * x_scale
|
|
||||||
right = bbox[2] * x_scale
|
|
||||||
|
|
||||||
# Flip Y coordinates for PDF
|
|
||||||
top = pdf_height - (bbox[1] * y_scale)
|
|
||||||
bottom = pdf_height - (bbox[3] * y_scale)
|
|
||||||
|
|
||||||
return left, bottom, right, top
|
|
||||||
|
|
||||||
|
|
||||||
def fill_pdf_form(input_pdf_path, fields_json_path, output_pdf_path):
|
|
||||||
"""Fill the PDF form with data from fields.json"""
|
|
||||||
|
|
||||||
# `fields.json` format described in forms.md.
|
|
||||||
with open(fields_json_path, "r", encoding="utf-8") as f:
|
|
||||||
fields_data = json.load(f)
|
|
||||||
|
|
||||||
# Open the PDF
|
|
||||||
reader = PdfReader(input_pdf_path)
|
|
||||||
writer = PdfWriter()
|
|
||||||
|
|
||||||
# Copy all pages to writer
|
|
||||||
writer.append(reader)
|
|
||||||
|
|
||||||
# Get PDF dimensions for each page
|
|
||||||
pdf_dimensions = {}
|
|
||||||
for i, page in enumerate(reader.pages):
|
|
||||||
mediabox = page.mediabox
|
|
||||||
pdf_dimensions[i + 1] = [mediabox.width, mediabox.height]
|
|
||||||
|
|
||||||
# Process each form field
|
|
||||||
annotations = []
|
|
||||||
for field in fields_data["form_fields"]:
|
|
||||||
page_num = field["page_number"]
|
|
||||||
|
|
||||||
# Get page dimensions and transform coordinates.
|
|
||||||
page_info = next(p for p in fields_data["pages"] if p["page_number"] == page_num)
|
|
||||||
image_width = page_info["image_width"]
|
|
||||||
image_height = page_info["image_height"]
|
|
||||||
pdf_width, pdf_height = pdf_dimensions[page_num]
|
|
||||||
|
|
||||||
transformed_entry_box = transform_coordinates(
|
|
||||||
field["entry_bounding_box"],
|
|
||||||
image_width, image_height,
|
|
||||||
pdf_width, pdf_height
|
|
||||||
)
|
|
||||||
|
|
||||||
# Skip empty fields
|
|
||||||
if "entry_text" not in field or "text" not in field["entry_text"]:
|
|
||||||
continue
|
|
||||||
entry_text = field["entry_text"]
|
|
||||||
text = entry_text["text"]
|
|
||||||
if not text:
|
|
||||||
continue
|
|
||||||
|
|
||||||
font_name = entry_text.get("font", "Arial")
|
|
||||||
font_size = str(entry_text.get("font_size", 14)) + "pt"
|
|
||||||
font_color = entry_text.get("font_color", "000000")
|
|
||||||
|
|
||||||
# Font size/color seems to not work reliably across viewers:
|
|
||||||
# https://github.com/py-pdf/pypdf/issues/2084
|
|
||||||
annotation = FreeText(
|
|
||||||
text=text,
|
|
||||||
rect=transformed_entry_box,
|
|
||||||
font=font_name,
|
|
||||||
font_size=font_size,
|
|
||||||
font_color=font_color,
|
|
||||||
border_color=None,
|
|
||||||
background_color=None,
|
|
||||||
)
|
|
||||||
annotations.append(annotation)
|
|
||||||
# page_number is 0-based for pypdf
|
|
||||||
writer.add_annotation(page_number=page_num - 1, annotation=annotation)
|
|
||||||
|
|
||||||
# Save the filled PDF
|
|
||||||
with open(output_pdf_path, "wb") as output:
|
|
||||||
writer.write(output)
|
|
||||||
|
|
||||||
print(f"Successfully filled PDF form and saved to {output_pdf_path}")
|
|
||||||
print(f"Added {len(annotations)} text annotations")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
if len(sys.argv) != 4:
|
|
||||||
print("Usage: fill_pdf_form_with_annotations.py [input pdf] [fields.json] [output pdf]")
|
|
||||||
sys.exit(1)
|
|
||||||
input_pdf = sys.argv[1]
|
|
||||||
fields_json = sys.argv[2]
|
|
||||||
output_pdf = sys.argv[3]
|
|
||||||
|
|
||||||
fill_pdf_form(input_pdf, fields_json, output_pdf)
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "eapil-pptx",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"description": "EAPIL PPTX 提供 PowerPoint/PPTX 创建、编辑、检查、验证和 OOXML 转换相关的 Codex 工作流。",
|
|
||||||
"author": {
|
|
||||||
"name": "EAPIL",
|
|
||||||
"url": "https://git.playones.com/arechen/EapilSkillMarket"
|
|
||||||
},
|
|
||||||
"homepage": "https://git.playones.com/arechen/EapilSkillMarket",
|
|
||||||
"repository": "https://git.playones.com/arechen/EapilSkillMarket",
|
|
||||||
"license": "Proprietary",
|
|
||||||
"keywords": [
|
|
||||||
"eapil",
|
|
||||||
"pptx",
|
|
||||||
"codex-skill"
|
|
||||||
],
|
|
||||||
"skills": "./skills/",
|
|
||||||
"interface": {
|
|
||||||
"displayName": "EAPIL PPTX",
|
|
||||||
"shortDescription": "PowerPoint/PPTX 创建、编辑、检查和 OOXML 工作流。",
|
|
||||||
"longDescription": "EAPIL PPTX 提供面向演示文稿的 Codex 工作流,覆盖 PPTX 创建、内容编辑、版式处理、结构检查、OOXML 分析、批注和讲稿等任务。",
|
|
||||||
"developerName": "EAPIL",
|
|
||||||
"category": "文档处理",
|
|
||||||
"capabilities": [
|
|
||||||
"Read",
|
|
||||||
"Write"
|
|
||||||
],
|
|
||||||
"defaultPrompt": [
|
|
||||||
"使用 EAPIL PPTX 编辑这个演示文稿。",
|
|
||||||
"使用 EAPIL PPTX 创建一个演示文稿。",
|
|
||||||
"使用 EAPIL PPTX 检查这个 PPTX 结构。"
|
|
||||||
],
|
|
||||||
"websiteURL": "https://git.playones.com/arechen/EapilSkillMarket",
|
|
||||||
"privacyPolicyURL": "https://git.playones.com/arechen/EapilSkillMarket",
|
|
||||||
"termsOfServiceURL": "https://git.playones.com/arechen/EapilSkillMarket",
|
|
||||||
"brandColor": "#2563EB",
|
|
||||||
"screenshots": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
© 2025 Anthropic, PBC. All rights reserved.
|
|
||||||
|
|
||||||
LICENSE: Use of these materials (including all code, prompts, assets, files,
|
|
||||||
and other components of this Skill) is governed by your agreement with
|
|
||||||
Anthropic regarding use of Anthropic's services. If no separate agreement
|
|
||||||
exists, use is governed by Anthropic's Consumer Terms of Service or
|
|
||||||
Commercial Terms of Service, as applicable:
|
|
||||||
https://www.anthropic.com/legal/consumer-terms
|
|
||||||
https://www.anthropic.com/legal/commercial-terms
|
|
||||||
Your applicable agreement is referred to as the "Agreement." "Services" are
|
|
||||||
as defined in the Agreement.
|
|
||||||
|
|
||||||
ADDITIONAL RESTRICTIONS: Notwithstanding anything in the Agreement to the
|
|
||||||
contrary, users may not:
|
|
||||||
|
|
||||||
- Extract these materials from the Services or retain copies of these
|
|
||||||
materials outside the Services
|
|
||||||
- Reproduce or copy these materials, except for temporary copies created
|
|
||||||
automatically during authorized use of the Services
|
|
||||||
- Create derivative works based on these materials
|
|
||||||
- Distribute, sublicense, or transfer these materials to any third party
|
|
||||||
- Make, offer to sell, sell, or import any inventions embodied in these
|
|
||||||
materials
|
|
||||||
- Reverse engineer, decompile, or disassemble these materials
|
|
||||||
|
|
||||||
The receipt, viewing, or possession of these materials does not convey or
|
|
||||||
imply any license or right beyond those expressly granted above.
|
|
||||||
|
|
||||||
Anthropic retains all right, title, and interest in these materials,
|
|
||||||
including all copyrights, patents, and other intellectual property rights.
|
|
||||||
@@ -1,485 +0,0 @@
|
|||||||
---
|
|
||||||
name: pptx
|
|
||||||
description: "PPTX/PowerPoint 创建、编辑和分析工具,用于新建演示文稿、修改内容、处理版式、添加批注或讲稿,以及检查 OOXML 结构。"
|
|
||||||
---
|
|
||||||
|
|
||||||
# PPTX creation, editing, and analysis
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
A user may ask you to create, edit, or analyze the contents of a .pptx file. A .pptx file is essentially a ZIP archive containing XML files and other resources that you can read or edit. You have different tools and workflows available for different tasks.
|
|
||||||
|
|
||||||
## Reading and analyzing content
|
|
||||||
|
|
||||||
### Text extraction
|
|
||||||
If you just need to read the text contents of a presentation, you should convert the document to markdown:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Convert document to markdown
|
|
||||||
python -m markitdown path-to-file.pptx
|
|
||||||
```
|
|
||||||
|
|
||||||
### Raw XML access
|
|
||||||
You need raw XML access for: comments, speaker notes, slide layouts, animations, design elements, and complex formatting. For any of these features, you'll need to unpack a presentation and read its raw XML contents.
|
|
||||||
|
|
||||||
#### Unpacking a file
|
|
||||||
`python ooxml/scripts/unpack.py <office_file> <output_dir>`
|
|
||||||
|
|
||||||
**Note**: The unpack.py script is located at `skills/pptx/ooxml/scripts/unpack.py` relative to the project root. If the script doesn't exist at this path, use `find . -name "unpack.py"` to locate it.
|
|
||||||
|
|
||||||
#### Key file structures
|
|
||||||
* `ppt/presentation.xml` - Main presentation metadata and slide references
|
|
||||||
* `ppt/slides/slide{N}.xml` - Individual slide contents (slide1.xml, slide2.xml, etc.)
|
|
||||||
* `ppt/notesSlides/notesSlide{N}.xml` - Speaker notes for each slide
|
|
||||||
* `ppt/comments/modernComment_*.xml` - Comments for specific slides
|
|
||||||
* `ppt/slideLayouts/` - Layout templates for slides
|
|
||||||
* `ppt/slideMasters/` - Master slide templates
|
|
||||||
* `ppt/theme/` - Theme and styling information
|
|
||||||
* `ppt/media/` - Images and other media files
|
|
||||||
|
|
||||||
#### Typography and color extraction
|
|
||||||
**When given an example design to emulate**: Always analyze the presentation's typography and colors first using the methods below:
|
|
||||||
1. **Read theme file**: Check `ppt/theme/theme1.xml` for colors (`<a:clrScheme>`) and fonts (`<a:fontScheme>`)
|
|
||||||
2. **Sample slide content**: Examine `ppt/slides/slide1.xml` for actual font usage (`<a:rPr>`) and colors
|
|
||||||
3. **Search for patterns**: Use grep to find color (`<a:solidFill>`, `<a:srgbClr>`) and font references across all XML files
|
|
||||||
|
|
||||||
## Creating a new PowerPoint presentation **without a template**
|
|
||||||
|
|
||||||
When creating a new PowerPoint presentation from scratch, use the **html2pptx** workflow to convert HTML slides to PowerPoint with accurate positioning.
|
|
||||||
|
|
||||||
### Design Principles
|
|
||||||
|
|
||||||
**CRITICAL**: Before creating any presentation, analyze the content and choose appropriate design elements:
|
|
||||||
1. **Consider the subject matter**: What is this presentation about? What tone, industry, or mood does it suggest?
|
|
||||||
2. **Check for branding**: If the user mentions a company/organization, consider their brand colors and identity
|
|
||||||
3. **Match palette to content**: Select colors that reflect the subject
|
|
||||||
4. **State your approach**: Explain your design choices before writing code
|
|
||||||
|
|
||||||
**Requirements**:
|
|
||||||
- ✅ State your content-informed design approach BEFORE writing code
|
|
||||||
- ✅ Use web-safe fonts only: Arial, Helvetica, Times New Roman, Georgia, Courier New, Verdana, Tahoma, Trebuchet MS, Impact
|
|
||||||
- ✅ Create clear visual hierarchy through size, weight, and color
|
|
||||||
- ✅ Ensure readability: strong contrast, appropriately sized text, clean alignment
|
|
||||||
- ✅ Be consistent: repeat patterns, spacing, and visual language across slides
|
|
||||||
|
|
||||||
#### Color Palette Selection
|
|
||||||
|
|
||||||
**Choosing colors creatively**:
|
|
||||||
- **Think beyond defaults**: What colors genuinely match this specific topic? Avoid autopilot choices.
|
|
||||||
- **Consider multiple angles**: Topic, industry, mood, energy level, target audience, brand identity (if mentioned)
|
|
||||||
- **Be adventurous**: Try unexpected combinations - a healthcare presentation doesn't have to be green, finance doesn't have to be navy
|
|
||||||
- **Build your palette**: Pick 3-5 colors that work together (dominant colors + supporting tones + accent)
|
|
||||||
- **Ensure contrast**: Text must be clearly readable on backgrounds
|
|
||||||
|
|
||||||
**Example color palettes** (use these to spark creativity - choose one, adapt it, or create your own):
|
|
||||||
|
|
||||||
1. **Classic Blue**: Deep navy (#1C2833), slate gray (#2E4053), silver (#AAB7B8), off-white (#F4F6F6)
|
|
||||||
2. **Teal & Coral**: Teal (#5EA8A7), deep teal (#277884), coral (#FE4447), white (#FFFFFF)
|
|
||||||
3. **Bold Red**: Red (#C0392B), bright red (#E74C3C), orange (#F39C12), yellow (#F1C40F), green (#2ECC71)
|
|
||||||
4. **Warm Blush**: Mauve (#A49393), blush (#EED6D3), rose (#E8B4B8), cream (#FAF7F2)
|
|
||||||
5. **Burgundy Luxury**: Burgundy (#5D1D2E), crimson (#951233), rust (#C15937), gold (#997929)
|
|
||||||
6. **Deep Purple & Emerald**: Purple (#B165FB), dark blue (#181B24), emerald (#40695B), white (#FFFFFF)
|
|
||||||
7. **Cream & Forest Green**: Cream (#FFE1C7), forest green (#40695B), white (#FCFCFC)
|
|
||||||
8. **Pink & Purple**: Pink (#F8275B), coral (#FF574A), rose (#FF737D), purple (#3D2F68)
|
|
||||||
9. **Lime & Plum**: Lime (#C5DE82), plum (#7C3A5F), coral (#FD8C6E), blue-gray (#98ACB5)
|
|
||||||
10. **Black & Gold**: Gold (#BF9A4A), black (#000000), cream (#F4F6F6)
|
|
||||||
11. **Sage & Terracotta**: Sage (#87A96B), terracotta (#E07A5F), cream (#F4F1DE), charcoal (#2C2C2C)
|
|
||||||
12. **Charcoal & Red**: Charcoal (#292929), red (#E33737), light gray (#CCCBCB)
|
|
||||||
13. **Vibrant Orange**: Orange (#F96D00), light gray (#F2F2F2), charcoal (#222831)
|
|
||||||
14. **Forest Green**: Black (#191A19), green (#4E9F3D), dark green (#1E5128), white (#FFFFFF)
|
|
||||||
15. **Retro Rainbow**: Purple (#722880), pink (#D72D51), orange (#EB5C18), amber (#F08800), gold (#DEB600)
|
|
||||||
16. **Vintage Earthy**: Mustard (#E3B448), sage (#CBD18F), forest green (#3A6B35), cream (#F4F1DE)
|
|
||||||
17. **Coastal Rose**: Old rose (#AD7670), beaver (#B49886), eggshell (#F3ECDC), ash gray (#BFD5BE)
|
|
||||||
18. **Orange & Turquoise**: Light orange (#FC993E), grayish turquoise (#667C6F), white (#FCFCFC)
|
|
||||||
|
|
||||||
#### Visual Details Options
|
|
||||||
|
|
||||||
**Geometric Patterns**:
|
|
||||||
- Diagonal section dividers instead of horizontal
|
|
||||||
- Asymmetric column widths (30/70, 40/60, 25/75)
|
|
||||||
- Rotated text headers at 90° or 270°
|
|
||||||
- Circular/hexagonal frames for images
|
|
||||||
- Triangular accent shapes in corners
|
|
||||||
- Overlapping shapes for depth
|
|
||||||
|
|
||||||
**Border & Frame Treatments**:
|
|
||||||
- Thick single-color borders (10-20pt) on one side only
|
|
||||||
- Double-line borders with contrasting colors
|
|
||||||
- Corner brackets instead of full frames
|
|
||||||
- L-shaped borders (top+left or bottom+right)
|
|
||||||
- Underline accents beneath headers (3-5pt thick)
|
|
||||||
|
|
||||||
**Typography Treatments**:
|
|
||||||
- Extreme size contrast (72pt headlines vs 11pt body)
|
|
||||||
- All-caps headers with wide letter spacing
|
|
||||||
- Numbered sections in oversized display type
|
|
||||||
- Monospace (Courier New) for data/stats/technical content
|
|
||||||
- Condensed fonts (Arial Narrow) for dense information
|
|
||||||
- Outlined text for emphasis
|
|
||||||
|
|
||||||
**Chart & Data Styling**:
|
|
||||||
- Monochrome charts with single accent color for key data
|
|
||||||
- Horizontal bar charts instead of vertical
|
|
||||||
- Dot plots instead of bar charts
|
|
||||||
- Minimal gridlines or none at all
|
|
||||||
- Data labels directly on elements (no legends)
|
|
||||||
- Oversized numbers for key metrics
|
|
||||||
|
|
||||||
**Layout Innovations**:
|
|
||||||
- Full-bleed images with text overlays
|
|
||||||
- Sidebar column (20-30% width) for navigation/context
|
|
||||||
- Modular grid systems (3×3, 4×4 blocks)
|
|
||||||
- Z-pattern or F-pattern content flow
|
|
||||||
- Floating text boxes over colored shapes
|
|
||||||
- Magazine-style multi-column layouts
|
|
||||||
|
|
||||||
**Background Treatments**:
|
|
||||||
- Solid color blocks occupying 40-60% of slide
|
|
||||||
- Gradient fills (vertical or diagonal only)
|
|
||||||
- Split backgrounds (two colors, diagonal or vertical)
|
|
||||||
- Edge-to-edge color bands
|
|
||||||
- Negative space as a design element
|
|
||||||
|
|
||||||
### Layout Tips
|
|
||||||
**When creating slides with charts or tables:**
|
|
||||||
- **Two-column layout (PREFERRED)**: Use a header spanning the full width, then two columns below - text/bullets in one column and the featured content in the other. This provides better balance and makes charts/tables more readable. Use flexbox with unequal column widths (e.g., 40%/60% split) to optimize space for each content type.
|
|
||||||
- **Full-slide layout**: Let the featured content (chart/table) take up the entire slide for maximum impact and readability
|
|
||||||
- **NEVER vertically stack**: Do not place charts/tables below text in a single column - this causes poor readability and layout issues
|
|
||||||
|
|
||||||
### Workflow
|
|
||||||
1. **MANDATORY - READ ENTIRE FILE**: Read [`html2pptx.md`](html2pptx.md) completely from start to finish. **NEVER set any range limits when reading this file.** Read the full file content for detailed syntax, critical formatting rules, and best practices before proceeding with presentation creation.
|
|
||||||
2. (Optional) Scaffold a portable project: `python scripts/create_portable_project.py <target_dir>`
|
|
||||||
3. Create an HTML file for each slide with proper dimensions (e.g., 720pt × 405pt for 16:9)
|
|
||||||
- Use `<p>`, `<h1>`-`<h6>`, `<ul>`, `<ol>` for all text content
|
|
||||||
- Use `class="placeholder"` for areas where charts/tables will be added (render with gray background for visibility)
|
|
||||||
- **CRITICAL**: Rasterize gradients and icons as PNG images FIRST using Sharp, then reference in HTML
|
|
||||||
- **LAYOUT**: For slides with charts/tables/images, use either full-slide layout or two-column layout for better readability
|
|
||||||
- **PORTABILITY**: Prefer relative image paths (e.g., `../images/hero.jpg`). If you must use absolute paths, use `file:///` URLs and avoid Windows-only backslashes in HTML.
|
|
||||||
4. Create and run a JavaScript file using the [`html2pptx.js`](scripts/html2pptx.js) library to convert HTML slides to PowerPoint and save the presentation
|
|
||||||
- Use the `html2pptx()` function to process each HTML file
|
|
||||||
- Add charts and tables to placeholder areas using PptxGenJS API
|
|
||||||
- Save the presentation using `pptx.writeFile()`
|
|
||||||
5. **Visual validation**: Generate thumbnails and inspect for layout issues
|
|
||||||
- Create thumbnail grid: `python scripts/thumbnail.py output.pptx workspace/thumbnails --cols 4`
|
|
||||||
- Read and carefully examine the thumbnail image for:
|
|
||||||
- **Text cutoff**: Text being cut off by header bars, shapes, or slide edges
|
|
||||||
- **Text overlap**: Text overlapping with other text or shapes
|
|
||||||
- **Positioning issues**: Content too close to slide boundaries or other elements
|
|
||||||
- **Contrast issues**: Insufficient contrast between text and backgrounds
|
|
||||||
- If issues found, adjust HTML margins/spacing/colors and regenerate the presentation
|
|
||||||
- Repeat until all slides are visually correct
|
|
||||||
|
|
||||||
## Editing an existing PowerPoint presentation
|
|
||||||
|
|
||||||
When edit slides in an existing PowerPoint presentation, you need to work with the raw Office Open XML (OOXML) format. This involves unpacking the .pptx file, editing the XML content, and repacking it.
|
|
||||||
|
|
||||||
### Workflow
|
|
||||||
1. **MANDATORY - READ ENTIRE FILE**: Read [`ooxml.md`](ooxml.md) (~500 lines) completely from start to finish. **NEVER set any range limits when reading this file.** Read the full file content for detailed guidance on OOXML structure and editing workflows before any presentation editing.
|
|
||||||
2. Unpack the presentation: `python ooxml/scripts/unpack.py <office_file> <output_dir>`
|
|
||||||
3. Edit the XML files (primarily `ppt/slides/slide{N}.xml` and related files)
|
|
||||||
4. **CRITICAL**: Validate immediately after each edit and fix any validation errors before proceeding: `python ooxml/scripts/validate.py <dir> --original <file>`
|
|
||||||
5. Pack the final presentation: `python ooxml/scripts/pack.py <input_directory> <office_file>`
|
|
||||||
|
|
||||||
## Creating a new PowerPoint presentation **using a template**
|
|
||||||
|
|
||||||
When you need to create a presentation that follows an existing template's design, you'll need to duplicate and re-arrange template slides before then replacing placeholder context.
|
|
||||||
|
|
||||||
### Workflow
|
|
||||||
1. **Extract template text AND create visual thumbnail grid**:
|
|
||||||
* Extract text: `python -m markitdown template.pptx > template-content.md`
|
|
||||||
* Read `template-content.md`: Read the entire file to understand the contents of the template presentation. **NEVER set any range limits when reading this file.**
|
|
||||||
* Create thumbnail grids: `python scripts/thumbnail.py template.pptx`
|
|
||||||
* See [Creating Thumbnail Grids](#creating-thumbnail-grids) section for more details
|
|
||||||
|
|
||||||
2. **Analyze template and save inventory to a file**:
|
|
||||||
* **Visual Analysis**: Review thumbnail grid(s) to understand slide layouts, design patterns, and visual structure
|
|
||||||
* Create and save a template inventory file at `template-inventory.md` containing:
|
|
||||||
```markdown
|
|
||||||
# Template Inventory Analysis
|
|
||||||
**Total Slides: [count]**
|
|
||||||
**IMPORTANT: Slides are 0-indexed (first slide = 0, last slide = count-1)**
|
|
||||||
|
|
||||||
## [Category Name]
|
|
||||||
- Slide 0: [Layout code if available] - Description/purpose
|
|
||||||
- Slide 1: [Layout code] - Description/purpose
|
|
||||||
- Slide 2: [Layout code] - Description/purpose
|
|
||||||
[... EVERY slide must be listed individually with its index ...]
|
|
||||||
```
|
|
||||||
* **Using the thumbnail grid**: Reference the visual thumbnails to identify:
|
|
||||||
- Layout patterns (title slides, content layouts, section dividers)
|
|
||||||
- Image placeholder locations and counts
|
|
||||||
- Design consistency across slide groups
|
|
||||||
- Visual hierarchy and structure
|
|
||||||
* This inventory file is REQUIRED for selecting appropriate templates in the next step
|
|
||||||
|
|
||||||
3. **Create presentation outline based on template inventory**:
|
|
||||||
* Review available templates from step 2.
|
|
||||||
* Choose an intro or title template for the first slide. This should be one of the first templates.
|
|
||||||
* Choose safe, text-based layouts for the other slides.
|
|
||||||
* **CRITICAL: Match layout structure to actual content**:
|
|
||||||
- Single-column layouts: Use for unified narrative or single topic
|
|
||||||
- Two-column layouts: Use ONLY when you have exactly 2 distinct items/concepts
|
|
||||||
- Three-column layouts: Use ONLY when you have exactly 3 distinct items/concepts
|
|
||||||
- Image + text layouts: Use ONLY when you have actual images to insert
|
|
||||||
- Quote layouts: Use ONLY for actual quotes from people (with attribution), never for emphasis
|
|
||||||
- Never use layouts with more placeholders than you have content
|
|
||||||
- If you have 2 items, don't force them into a 3-column layout
|
|
||||||
- If you have 4+ items, consider breaking into multiple slides or using a list format
|
|
||||||
* Count your actual content pieces BEFORE selecting the layout
|
|
||||||
* Verify each placeholder in the chosen layout will be filled with meaningful content
|
|
||||||
* Select one option representing the **best** layout for each content section.
|
|
||||||
* Save `outline.md` with content AND template mapping that leverages available designs
|
|
||||||
* Example template mapping:
|
|
||||||
```
|
|
||||||
# Template slides to use (0-based indexing)
|
|
||||||
# WARNING: Verify indices are within range! Template with 73 slides has indices 0-72
|
|
||||||
# Mapping: slide numbers from outline -> template slide indices
|
|
||||||
template_mapping = [
|
|
||||||
0, # Use slide 0 (Title/Cover)
|
|
||||||
34, # Use slide 34 (B1: Title and body)
|
|
||||||
34, # Use slide 34 again (duplicate for second B1)
|
|
||||||
50, # Use slide 50 (E1: Quote)
|
|
||||||
54, # Use slide 54 (F2: Closing + Text)
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **Duplicate, reorder, and delete slides using `rearrange.py`**:
|
|
||||||
* Use the `scripts/rearrange.py` script to create a new presentation with slides in the desired order:
|
|
||||||
```bash
|
|
||||||
python scripts/rearrange.py template.pptx working.pptx 0,34,34,50,52
|
|
||||||
```
|
|
||||||
* The script handles duplicating repeated slides, deleting unused slides, and reordering automatically
|
|
||||||
* Slide indices are 0-based (first slide is 0, second is 1, etc.)
|
|
||||||
* The same slide index can appear multiple times to duplicate that slide
|
|
||||||
|
|
||||||
5. **Extract ALL text using the `inventory.py` script**:
|
|
||||||
* **Run inventory extraction**:
|
|
||||||
```bash
|
|
||||||
python scripts/inventory.py working.pptx text-inventory.json
|
|
||||||
```
|
|
||||||
* **Read text-inventory.json**: Read the entire text-inventory.json file to understand all shapes and their properties. **NEVER set any range limits when reading this file.**
|
|
||||||
|
|
||||||
* The inventory JSON structure:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"slide-0": {
|
|
||||||
"shape-0": {
|
|
||||||
"placeholder_type": "TITLE", // or null for non-placeholders
|
|
||||||
"left": 1.5, // position in inches
|
|
||||||
"top": 2.0,
|
|
||||||
"width": 7.5,
|
|
||||||
"height": 1.2,
|
|
||||||
"paragraphs": [
|
|
||||||
{
|
|
||||||
"text": "Paragraph text",
|
|
||||||
// Optional properties (only included when non-default):
|
|
||||||
"bullet": true, // explicit bullet detected
|
|
||||||
"level": 0, // only included when bullet is true
|
|
||||||
"alignment": "CENTER", // CENTER, RIGHT (not LEFT)
|
|
||||||
"space_before": 10.0, // space before paragraph in points
|
|
||||||
"space_after": 6.0, // space after paragraph in points
|
|
||||||
"line_spacing": 22.4, // line spacing in points
|
|
||||||
"font_name": "Arial", // from first run
|
|
||||||
"font_size": 14.0, // in points
|
|
||||||
"bold": true,
|
|
||||||
"italic": false,
|
|
||||||
"underline": false,
|
|
||||||
"color": "FF0000" // RGB color
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
* Key features:
|
|
||||||
- **Slides**: Named as "slide-0", "slide-1", etc.
|
|
||||||
- **Shapes**: Ordered by visual position (top-to-bottom, left-to-right) as "shape-0", "shape-1", etc.
|
|
||||||
- **Placeholder types**: TITLE, CENTER_TITLE, SUBTITLE, BODY, OBJECT, or null
|
|
||||||
- **Default font size**: `default_font_size` in points extracted from layout placeholders (when available)
|
|
||||||
- **Slide numbers are filtered**: Shapes with SLIDE_NUMBER placeholder type are automatically excluded from inventory
|
|
||||||
- **Bullets**: When `bullet: true`, `level` is always included (even if 0)
|
|
||||||
- **Spacing**: `space_before`, `space_after`, and `line_spacing` in points (only included when set)
|
|
||||||
- **Colors**: `color` for RGB (e.g., "FF0000"), `theme_color` for theme colors (e.g., "DARK_1")
|
|
||||||
- **Properties**: Only non-default values are included in the output
|
|
||||||
|
|
||||||
6. **Generate replacement text and save the data to a JSON file**
|
|
||||||
Based on the text inventory from the previous step:
|
|
||||||
- **CRITICAL**: First verify which shapes exist in the inventory - only reference shapes that are actually present
|
|
||||||
- **VALIDATION**: The replace.py script will validate that all shapes in your replacement JSON exist in the inventory
|
|
||||||
- If you reference a non-existent shape, you'll get an error showing available shapes
|
|
||||||
- If you reference a non-existent slide, you'll get an error indicating the slide doesn't exist
|
|
||||||
- All validation errors are shown at once before the script exits
|
|
||||||
- **IMPORTANT**: The replace.py script uses inventory.py internally to identify ALL text shapes
|
|
||||||
- **AUTOMATIC CLEARING**: ALL text shapes from the inventory will be cleared unless you provide "paragraphs" for them
|
|
||||||
- Add a "paragraphs" field to shapes that need content (not "replacement_paragraphs")
|
|
||||||
- Shapes without "paragraphs" in the replacement JSON will have their text cleared automatically
|
|
||||||
- Paragraphs with bullets will be automatically left aligned. Don't set the `alignment` property on when `"bullet": true`
|
|
||||||
- Generate appropriate replacement content for placeholder text
|
|
||||||
- Use shape size to determine appropriate content length
|
|
||||||
- **CRITICAL**: Include paragraph properties from the original inventory - don't just provide text
|
|
||||||
- **IMPORTANT**: When bullet: true, do NOT include bullet symbols (•, -, *) in text - they're added automatically
|
|
||||||
- **ESSENTIAL FORMATTING RULES**:
|
|
||||||
- Headers/titles should typically have `"bold": true`
|
|
||||||
- List items should have `"bullet": true, "level": 0` (level is required when bullet is true)
|
|
||||||
- Preserve any alignment properties (e.g., `"alignment": "CENTER"` for centered text)
|
|
||||||
- Include font properties when different from default (e.g., `"font_size": 14.0`, `"font_name": "Lora"`)
|
|
||||||
- Colors: Use `"color": "FF0000"` for RGB or `"theme_color": "DARK_1"` for theme colors
|
|
||||||
- The replacement script expects **properly formatted paragraphs**, not just text strings
|
|
||||||
- **Overlapping shapes**: Prefer shapes with larger default_font_size or more appropriate placeholder_type
|
|
||||||
- Save the updated inventory with replacements to `replacement-text.json`
|
|
||||||
- **WARNING**: Different template layouts have different shape counts - always check the actual inventory before creating replacements
|
|
||||||
|
|
||||||
Example paragraphs field showing proper formatting:
|
|
||||||
```json
|
|
||||||
"paragraphs": [
|
|
||||||
{
|
|
||||||
"text": "New presentation title text",
|
|
||||||
"alignment": "CENTER",
|
|
||||||
"bold": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"text": "Section Header",
|
|
||||||
"bold": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"text": "First bullet point without bullet symbol",
|
|
||||||
"bullet": true,
|
|
||||||
"level": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"text": "Red colored text",
|
|
||||||
"color": "FF0000"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"text": "Theme colored text",
|
|
||||||
"theme_color": "DARK_1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"text": "Regular paragraph text without special formatting"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
**Shapes not listed in the replacement JSON are automatically cleared**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"slide-0": {
|
|
||||||
"shape-0": {
|
|
||||||
"paragraphs": [...] // This shape gets new text
|
|
||||||
}
|
|
||||||
// shape-1 and shape-2 from inventory will be cleared automatically
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Common formatting patterns for presentations**:
|
|
||||||
- Title slides: Bold text, sometimes centered
|
|
||||||
- Section headers within slides: Bold text
|
|
||||||
- Bullet lists: Each item needs `"bullet": true, "level": 0`
|
|
||||||
- Body text: Usually no special properties needed
|
|
||||||
- Quotes: May have special alignment or font properties
|
|
||||||
|
|
||||||
7. **Apply replacements using the `replace.py` script**
|
|
||||||
```bash
|
|
||||||
python scripts/replace.py working.pptx replacement-text.json output.pptx
|
|
||||||
```
|
|
||||||
|
|
||||||
The script will:
|
|
||||||
- First extract the inventory of ALL text shapes using functions from inventory.py
|
|
||||||
- Validate that all shapes in the replacement JSON exist in the inventory
|
|
||||||
- Clear text from ALL shapes identified in the inventory
|
|
||||||
- Apply new text only to shapes with "paragraphs" defined in the replacement JSON
|
|
||||||
- Preserve formatting by applying paragraph properties from the JSON
|
|
||||||
- Handle bullets, alignment, font properties, and colors automatically
|
|
||||||
- Save the updated presentation
|
|
||||||
|
|
||||||
Example validation errors:
|
|
||||||
```
|
|
||||||
ERROR: Invalid shapes in replacement JSON:
|
|
||||||
- Shape 'shape-99' not found on 'slide-0'. Available shapes: shape-0, shape-1, shape-4
|
|
||||||
- Slide 'slide-999' not found in inventory
|
|
||||||
```
|
|
||||||
|
|
||||||
```
|
|
||||||
ERROR: Replacement text made overflow worse in these shapes:
|
|
||||||
- slide-0/shape-2: overflow worsened by 1.25" (was 0.00", now 1.25")
|
|
||||||
```
|
|
||||||
|
|
||||||
## Creating Thumbnail Grids
|
|
||||||
|
|
||||||
To create visual thumbnail grids of PowerPoint slides for quick analysis and reference:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python scripts/thumbnail.py template.pptx [output_prefix]
|
|
||||||
```
|
|
||||||
|
|
||||||
**Features**:
|
|
||||||
- Creates: `thumbnails.jpg` (or `thumbnails-1.jpg`, `thumbnails-2.jpg`, etc. for large decks)
|
|
||||||
- Default: 5 columns, max 30 slides per grid (5×6)
|
|
||||||
- Custom prefix: `python scripts/thumbnail.py template.pptx my-grid`
|
|
||||||
- Note: The output prefix should include the path if you want output in a specific directory (e.g., `workspace/my-grid`)
|
|
||||||
- Adjust columns: `--cols 4` (range: 3-6, affects slides per grid)
|
|
||||||
- Grid limits: 3 cols = 12 slides/grid, 4 cols = 20, 5 cols = 30, 6 cols = 42
|
|
||||||
- Slides are zero-indexed (Slide 0, Slide 1, etc.)
|
|
||||||
|
|
||||||
**Use cases**:
|
|
||||||
- Template analysis: Quickly understand slide layouts and design patterns
|
|
||||||
- Content review: Visual overview of entire presentation
|
|
||||||
- Navigation reference: Find specific slides by their visual appearance
|
|
||||||
- Quality check: Verify all slides are properly formatted
|
|
||||||
|
|
||||||
**Examples**:
|
|
||||||
```bash
|
|
||||||
# Basic usage
|
|
||||||
python scripts/thumbnail.py presentation.pptx
|
|
||||||
|
|
||||||
# Combine options: custom name, columns
|
|
||||||
python scripts/thumbnail.py template.pptx analysis --cols 4
|
|
||||||
```
|
|
||||||
|
|
||||||
## Converting Slides to Images
|
|
||||||
|
|
||||||
To visually analyze PowerPoint slides, convert them to images using a two-step process:
|
|
||||||
|
|
||||||
1. **Convert PPTX to PDF**:
|
|
||||||
```bash
|
|
||||||
soffice --headless --convert-to pdf template.pptx
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Convert PDF pages to JPEG images**:
|
|
||||||
```bash
|
|
||||||
pdftoppm -jpeg -r 150 template.pdf slide
|
|
||||||
```
|
|
||||||
This creates files like `slide-1.jpg`, `slide-2.jpg`, etc.
|
|
||||||
|
|
||||||
Options:
|
|
||||||
- `-r 150`: Sets resolution to 150 DPI (adjust for quality/size balance)
|
|
||||||
- `-jpeg`: Output JPEG format (use `-png` for PNG if preferred)
|
|
||||||
- `-f N`: First page to convert (e.g., `-f 2` starts from page 2)
|
|
||||||
- `-l N`: Last page to convert (e.g., `-l 5` stops at page 5)
|
|
||||||
- `slide`: Prefix for output files
|
|
||||||
|
|
||||||
Example for specific range:
|
|
||||||
```bash
|
|
||||||
pdftoppm -jpeg -r 150 -f 2 -l 5 template.pdf slide # Converts only pages 2-5
|
|
||||||
```
|
|
||||||
|
|
||||||
## Code Style Guidelines
|
|
||||||
**IMPORTANT**: When generating code for PPTX operations:
|
|
||||||
- Write concise code
|
|
||||||
- Avoid verbose variable names and redundant operations
|
|
||||||
- Avoid unnecessary print statements
|
|
||||||
|
|
||||||
## Dependencies
|
|
||||||
|
|
||||||
Required dependencies (install per-project when possible):
|
|
||||||
|
|
||||||
- **markitdown**: `pip install "markitdown[pptx]"` (for text extraction from presentations)
|
|
||||||
- **pptxgenjs**: `npm install pptxgenjs` (for creating presentations via html2pptx)
|
|
||||||
- **playwright**: `npm install playwright` (for HTML rendering in html2pptx)
|
|
||||||
- **react-icons**: `npm install react-icons react react-dom` (for icons)
|
|
||||||
- **sharp**: `npm install sharp` (for SVG rasterization and image processing)
|
|
||||||
- **LibreOffice**: `sudo apt-get install libreoffice` (for PDF conversion)
|
|
||||||
- **Poppler**: `sudo apt-get install poppler-utils` (for pdftoppm to convert PDF to images)
|
|
||||||
- **defusedxml**: `pip install defusedxml` (for secure XML parsing)
|
|
||||||
@@ -1,625 +0,0 @@
|
|||||||
# HTML to PowerPoint Guide
|
|
||||||
|
|
||||||
Convert HTML slides to PowerPoint presentations with accurate positioning using the `html2pptx.js` library.
|
|
||||||
|
|
||||||
## Table of Contents
|
|
||||||
|
|
||||||
1. [Creating HTML Slides](#creating-html-slides)
|
|
||||||
2. [Using the html2pptx Library](#using-the-html2pptx-library)
|
|
||||||
3. [Using PptxGenJS](#using-pptxgenjs)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Creating HTML Slides
|
|
||||||
|
|
||||||
Every HTML slide must include proper body dimensions:
|
|
||||||
|
|
||||||
### Layout Dimensions
|
|
||||||
|
|
||||||
- **16:9** (default): `width: 720pt; height: 405pt`
|
|
||||||
- **4:3**: `width: 720pt; height: 540pt`
|
|
||||||
- **16:10**: `width: 720pt; height: 450pt`
|
|
||||||
|
|
||||||
### Supported Elements
|
|
||||||
|
|
||||||
- `<p>`, `<h1>`-`<h6>` - Text with styling
|
|
||||||
- `<ul>`, `<ol>` - Lists (never use manual bullets •, -, *)
|
|
||||||
- `<b>`, `<strong>` - Bold text (inline formatting)
|
|
||||||
- `<i>`, `<em>` - Italic text (inline formatting)
|
|
||||||
- `<u>` - Underlined text (inline formatting)
|
|
||||||
- `<span>` - Inline formatting with CSS styles (bold, italic, underline, color)
|
|
||||||
- `<br>` - Line breaks
|
|
||||||
- `<div>` with bg/border - Becomes shape
|
|
||||||
- `<img>` - Images
|
|
||||||
- `class="placeholder"` - Reserved space for charts (returns `{ id, x, y, w, h }`)
|
|
||||||
|
|
||||||
### Critical Text Rules
|
|
||||||
|
|
||||||
**ALL text MUST be inside `<p>`, `<h1>`-`<h6>`, `<ul>`, or `<ol>` tags:**
|
|
||||||
- ✅ Correct: `<div><p>Text here</p></div>`
|
|
||||||
- ❌ Wrong: `<div>Text here</div>` - **Text will NOT appear in PowerPoint**
|
|
||||||
- ❌ Wrong: `<span>Text</span>` - **Text will NOT appear in PowerPoint**
|
|
||||||
- Text in `<div>` or `<span>` without a text tag will be silently ignored
|
|
||||||
|
|
||||||
**NEVER use manual bullet symbols (•, -, *, etc.)** - Use `<ul>` or `<ol>` lists instead
|
|
||||||
|
|
||||||
**ONLY use web-safe fonts that are universally available:**
|
|
||||||
- ✅ Web-safe fonts: `Arial`, `Helvetica`, `Times New Roman`, `Georgia`, `Courier New`, `Verdana`, `Tahoma`, `Trebuchet MS`, `Impact`, `Comic Sans MS`
|
|
||||||
- ❌ Wrong: `'Segoe UI'`, `'SF Pro'`, `'Roboto'`, custom fonts - **Might cause rendering issues**
|
|
||||||
|
|
||||||
### Styling
|
|
||||||
|
|
||||||
- Use `display: flex` on body to prevent margin collapse from breaking overflow validation
|
|
||||||
- Use `margin` for spacing (padding included in size)
|
|
||||||
- Inline formatting: Use `<b>`, `<i>`, `<u>` tags OR `<span>` with CSS styles
|
|
||||||
- `<span>` supports: `font-weight: bold`, `font-style: italic`, `text-decoration: underline`, `color: #rrggbb`
|
|
||||||
- `<span>` does NOT support: `margin`, `padding` (not supported in PowerPoint text runs)
|
|
||||||
- Example: `<span style="font-weight: bold; color: #667eea;">Bold blue text</span>`
|
|
||||||
- Flexbox works - positions calculated from rendered layout
|
|
||||||
- Use hex colors with `#` prefix in CSS
|
|
||||||
- **Text alignment**: Use CSS `text-align` (`center`, `right`, etc.) when needed as a hint to PptxGenJS for text formatting if text lengths are slightly off
|
|
||||||
|
|
||||||
### Shape Styling (DIV elements only)
|
|
||||||
|
|
||||||
**IMPORTANT: Backgrounds, borders, and shadows only work on `<div>` elements, NOT on text elements (`<p>`, `<h1>`-`<h6>`, `<ul>`, `<ol>`)**
|
|
||||||
|
|
||||||
- **Backgrounds**: CSS `background` or `background-color` on `<div>` elements only
|
|
||||||
- Example: `<div style="background: #f0f0f0;">` - Creates a shape with background
|
|
||||||
- **Borders**: CSS `border` on `<div>` elements converts to PowerPoint shape borders
|
|
||||||
- Supports uniform borders: `border: 2px solid #333333`
|
|
||||||
- Supports partial borders: `border-left`, `border-right`, `border-top`, `border-bottom` (rendered as line shapes)
|
|
||||||
- Example: `<div style="border-left: 8pt solid #E76F51;">`
|
|
||||||
- **Border radius**: CSS `border-radius` on `<div>` elements for rounded corners
|
|
||||||
- `border-radius: 50%` or higher creates circular shape
|
|
||||||
- Percentages <50% calculated relative to shape's smaller dimension
|
|
||||||
- Supports px and pt units (e.g., `border-radius: 8pt;`, `border-radius: 12px;`)
|
|
||||||
- Example: `<div style="border-radius: 25%;">` on 100x200px box = 25% of 100px = 25px radius
|
|
||||||
- **Box shadows**: CSS `box-shadow` on `<div>` elements converts to PowerPoint shadows
|
|
||||||
- Supports outer shadows only (inset shadows are ignored to prevent corruption)
|
|
||||||
- Example: `<div style="box-shadow: 2px 2px 8px rgba(0, 0, 0, 0.3);">`
|
|
||||||
- Note: Inset/inner shadows are not supported by PowerPoint and will be skipped
|
|
||||||
|
|
||||||
### Icons & Gradients
|
|
||||||
|
|
||||||
- **CRITICAL: Never use CSS gradients (`linear-gradient`, `radial-gradient`)** - They don't convert to PowerPoint
|
|
||||||
- **ALWAYS create gradient/icon PNGs FIRST using Sharp, then reference in HTML**
|
|
||||||
- For gradients: Rasterize SVG to PNG background images
|
|
||||||
- For icons: Rasterize react-icons SVG to PNG images
|
|
||||||
- All visual effects must be pre-rendered as raster images before HTML rendering
|
|
||||||
|
|
||||||
**Rasterizing Icons with Sharp:**
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const React = require('react');
|
|
||||||
const ReactDOMServer = require('react-dom/server');
|
|
||||||
const sharp = require('sharp');
|
|
||||||
const { FaHome } = require('react-icons/fa');
|
|
||||||
|
|
||||||
async function rasterizeIconPng(IconComponent, color, size = "256", filename) {
|
|
||||||
const svgString = ReactDOMServer.renderToStaticMarkup(
|
|
||||||
React.createElement(IconComponent, { color: `#${color}`, size: size })
|
|
||||||
);
|
|
||||||
|
|
||||||
// Convert SVG to PNG using Sharp
|
|
||||||
await sharp(Buffer.from(svgString))
|
|
||||||
.png()
|
|
||||||
.toFile(filename);
|
|
||||||
|
|
||||||
return filename;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Usage: Rasterize icon before using in HTML
|
|
||||||
const iconPath = await rasterizeIconPng(FaHome, "4472c4", "256", "home-icon.png");
|
|
||||||
// Then reference in HTML: <img src="home-icon.png" style="width: 40pt; height: 40pt;">
|
|
||||||
```
|
|
||||||
|
|
||||||
**Rasterizing Gradients with Sharp:**
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const sharp = require('sharp');
|
|
||||||
|
|
||||||
async function createGradientBackground(filename) {
|
|
||||||
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="562.5">
|
|
||||||
<defs>
|
|
||||||
<linearGradient id="g" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
||||||
<stop offset="0%" style="stop-color:#COLOR1"/>
|
|
||||||
<stop offset="100%" style="stop-color:#COLOR2"/>
|
|
||||||
</linearGradient>
|
|
||||||
</defs>
|
|
||||||
<rect width="100%" height="100%" fill="url(#g)"/>
|
|
||||||
</svg>`;
|
|
||||||
|
|
||||||
await sharp(Buffer.from(svg))
|
|
||||||
.png()
|
|
||||||
.toFile(filename);
|
|
||||||
|
|
||||||
return filename;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Usage: Create gradient background before HTML
|
|
||||||
const bgPath = await createGradientBackground("gradient-bg.png");
|
|
||||||
// Then in HTML: <body style="background-image: url('gradient-bg.png');">
|
|
||||||
```
|
|
||||||
|
|
||||||
### Example
|
|
||||||
|
|
||||||
```html
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<style>
|
|
||||||
html { background: #ffffff; }
|
|
||||||
body {
|
|
||||||
width: 720pt; height: 405pt; margin: 0; padding: 0;
|
|
||||||
background: #f5f5f5; font-family: Arial, sans-serif;
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
.content { margin: 30pt; padding: 40pt; background: #ffffff; border-radius: 8pt; }
|
|
||||||
h1 { color: #2d3748; font-size: 32pt; }
|
|
||||||
.box {
|
|
||||||
background: #70ad47; padding: 20pt; border: 3px solid #5a8f37;
|
|
||||||
border-radius: 12pt; box-shadow: 3px 3px 10px rgba(0, 0, 0, 0.25);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="content">
|
|
||||||
<h1>Recipe Title</h1>
|
|
||||||
<ul>
|
|
||||||
<li><b>Item:</b> Description</li>
|
|
||||||
</ul>
|
|
||||||
<p>Text with <b>bold</b>, <i>italic</i>, <u>underline</u>.</p>
|
|
||||||
<div id="chart" class="placeholder" style="width: 350pt; height: 200pt;"></div>
|
|
||||||
|
|
||||||
<!-- Text MUST be in <p> tags -->
|
|
||||||
<div class="box">
|
|
||||||
<p>5</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Using the html2pptx Library
|
|
||||||
|
|
||||||
### Dependencies
|
|
||||||
|
|
||||||
These libraries have been globally installed and are available to use:
|
|
||||||
- `pptxgenjs`
|
|
||||||
- `playwright`
|
|
||||||
- `sharp`
|
|
||||||
|
|
||||||
### Basic Usage
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const pptxgen = require('pptxgenjs');
|
|
||||||
const html2pptx = require('./html2pptx');
|
|
||||||
|
|
||||||
const pptx = new pptxgen();
|
|
||||||
pptx.layout = 'LAYOUT_16x9'; // Must match HTML body dimensions
|
|
||||||
|
|
||||||
const { slide, placeholders } = await html2pptx('slide1.html', pptx);
|
|
||||||
|
|
||||||
// Add chart to placeholder area
|
|
||||||
if (placeholders.length > 0) {
|
|
||||||
slide.addChart(pptx.charts.LINE, chartData, placeholders[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
await pptx.writeFile('output.pptx');
|
|
||||||
```
|
|
||||||
|
|
||||||
### API Reference
|
|
||||||
|
|
||||||
#### Function Signature
|
|
||||||
```javascript
|
|
||||||
await html2pptx(htmlFile, pres, options)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Parameters
|
|
||||||
- `htmlFile` (string): Path to HTML file (absolute or relative)
|
|
||||||
- `pres` (pptxgen): PptxGenJS presentation instance with layout already set
|
|
||||||
- `options` (object, optional):
|
|
||||||
- `tmpDir` (string): Temporary directory for generated files (default: `process.env.TMPDIR || '/tmp'`)
|
|
||||||
- `slide` (object): Existing slide to reuse (default: creates new slide)
|
|
||||||
|
|
||||||
#### Returns
|
|
||||||
```javascript
|
|
||||||
{
|
|
||||||
slide: pptxgenSlide, // The created/updated slide
|
|
||||||
placeholders: [ // Array of placeholder positions
|
|
||||||
{ id: string, x: number, y: number, w: number, h: number },
|
|
||||||
...
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Validation
|
|
||||||
|
|
||||||
The library automatically validates and collects all errors before throwing:
|
|
||||||
|
|
||||||
1. **HTML dimensions must match presentation layout** - Reports dimension mismatches
|
|
||||||
2. **Content must not overflow body** - Reports overflow with exact measurements
|
|
||||||
3. **CSS gradients** - Reports unsupported gradient usage
|
|
||||||
4. **Text element styling** - Reports backgrounds/borders/shadows on text elements (only allowed on divs)
|
|
||||||
|
|
||||||
**All validation errors are collected and reported together** in a single error message, allowing you to fix all issues at once instead of one at a time.
|
|
||||||
|
|
||||||
### Working with Placeholders
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const { slide, placeholders } = await html2pptx('slide.html', pptx);
|
|
||||||
|
|
||||||
// Use first placeholder
|
|
||||||
slide.addChart(pptx.charts.BAR, data, placeholders[0]);
|
|
||||||
|
|
||||||
// Find by ID
|
|
||||||
const chartArea = placeholders.find(p => p.id === 'chart-area');
|
|
||||||
slide.addChart(pptx.charts.LINE, data, chartArea);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Complete Example
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const pptxgen = require('pptxgenjs');
|
|
||||||
const html2pptx = require('./html2pptx');
|
|
||||||
|
|
||||||
async function createPresentation() {
|
|
||||||
const pptx = new pptxgen();
|
|
||||||
pptx.layout = 'LAYOUT_16x9';
|
|
||||||
pptx.author = 'Your Name';
|
|
||||||
pptx.title = 'My Presentation';
|
|
||||||
|
|
||||||
// Slide 1: Title
|
|
||||||
const { slide: slide1 } = await html2pptx('slides/title.html', pptx);
|
|
||||||
|
|
||||||
// Slide 2: Content with chart
|
|
||||||
const { slide: slide2, placeholders } = await html2pptx('slides/data.html', pptx);
|
|
||||||
|
|
||||||
const chartData = [{
|
|
||||||
name: 'Sales',
|
|
||||||
labels: ['Q1', 'Q2', 'Q3', 'Q4'],
|
|
||||||
values: [4500, 5500, 6200, 7100]
|
|
||||||
}];
|
|
||||||
|
|
||||||
slide2.addChart(pptx.charts.BAR, chartData, {
|
|
||||||
...placeholders[0],
|
|
||||||
showTitle: true,
|
|
||||||
title: 'Quarterly Sales',
|
|
||||||
showCatAxisTitle: true,
|
|
||||||
catAxisTitle: 'Quarter',
|
|
||||||
showValAxisTitle: true,
|
|
||||||
valAxisTitle: 'Sales ($000s)'
|
|
||||||
});
|
|
||||||
|
|
||||||
// Save
|
|
||||||
await pptx.writeFile({ fileName: 'presentation.pptx' });
|
|
||||||
console.log('Presentation created successfully!');
|
|
||||||
}
|
|
||||||
|
|
||||||
createPresentation().catch(console.error);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Using PptxGenJS
|
|
||||||
|
|
||||||
After converting HTML to slides with `html2pptx`, you'll use PptxGenJS to add dynamic content like charts, images, and additional elements.
|
|
||||||
|
|
||||||
### ⚠️ Critical Rules
|
|
||||||
|
|
||||||
#### Colors
|
|
||||||
- **NEVER use `#` prefix** with hex colors in PptxGenJS - causes file corruption
|
|
||||||
- ✅ Correct: `color: "FF0000"`, `fill: { color: "0066CC" }`
|
|
||||||
- ❌ Wrong: `color: "#FF0000"` (breaks document)
|
|
||||||
|
|
||||||
### Adding Images
|
|
||||||
|
|
||||||
Always calculate aspect ratios from actual image dimensions:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Get image dimensions: identify image.png | grep -o '[0-9]* x [0-9]*'
|
|
||||||
const imgWidth = 1860, imgHeight = 1519; // From actual file
|
|
||||||
const aspectRatio = imgWidth / imgHeight;
|
|
||||||
|
|
||||||
const h = 3; // Max height
|
|
||||||
const w = h * aspectRatio;
|
|
||||||
const x = (10 - w) / 2; // Center on 16:9 slide
|
|
||||||
|
|
||||||
slide.addImage({ path: "chart.png", x, y: 1.5, w, h });
|
|
||||||
```
|
|
||||||
|
|
||||||
### Adding Text
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Rich text with formatting
|
|
||||||
slide.addText([
|
|
||||||
{ text: "Bold ", options: { bold: true } },
|
|
||||||
{ text: "Italic ", options: { italic: true } },
|
|
||||||
{ text: "Normal" }
|
|
||||||
], {
|
|
||||||
x: 1, y: 2, w: 8, h: 1
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Adding Shapes
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Rectangle
|
|
||||||
slide.addShape(pptx.shapes.RECTANGLE, {
|
|
||||||
x: 1, y: 1, w: 3, h: 2,
|
|
||||||
fill: { color: "4472C4" },
|
|
||||||
line: { color: "000000", width: 2 }
|
|
||||||
});
|
|
||||||
|
|
||||||
// Circle
|
|
||||||
slide.addShape(pptx.shapes.OVAL, {
|
|
||||||
x: 5, y: 1, w: 2, h: 2,
|
|
||||||
fill: { color: "ED7D31" }
|
|
||||||
});
|
|
||||||
|
|
||||||
// Rounded rectangle
|
|
||||||
slide.addShape(pptx.shapes.ROUNDED_RECTANGLE, {
|
|
||||||
x: 1, y: 4, w: 3, h: 1.5,
|
|
||||||
fill: { color: "70AD47" },
|
|
||||||
rectRadius: 0.2
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Adding Charts
|
|
||||||
|
|
||||||
**Required for most charts:** Axis labels using `catAxisTitle` (category) and `valAxisTitle` (value).
|
|
||||||
|
|
||||||
**Chart Data Format:**
|
|
||||||
- Use **single series with all labels** for simple bar/line charts
|
|
||||||
- Each series creates a separate legend entry
|
|
||||||
- Labels array defines X-axis values
|
|
||||||
|
|
||||||
**Time Series Data - Choose Correct Granularity:**
|
|
||||||
- **< 30 days**: Use daily grouping (e.g., "10-01", "10-02") - avoid monthly aggregation that creates single-point charts
|
|
||||||
- **30-365 days**: Use monthly grouping (e.g., "2024-01", "2024-02")
|
|
||||||
- **> 365 days**: Use yearly grouping (e.g., "2023", "2024")
|
|
||||||
- **Validate**: Charts with only 1 data point likely indicate incorrect aggregation for the time period
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const { slide, placeholders } = await html2pptx('slide.html', pptx);
|
|
||||||
|
|
||||||
// CORRECT: Single series with all labels
|
|
||||||
slide.addChart(pptx.charts.BAR, [{
|
|
||||||
name: "Sales 2024",
|
|
||||||
labels: ["Q1", "Q2", "Q3", "Q4"],
|
|
||||||
values: [4500, 5500, 6200, 7100]
|
|
||||||
}], {
|
|
||||||
...placeholders[0], // Use placeholder position
|
|
||||||
barDir: 'col', // 'col' = vertical bars, 'bar' = horizontal
|
|
||||||
showTitle: true,
|
|
||||||
title: 'Quarterly Sales',
|
|
||||||
showLegend: false, // No legend needed for single series
|
|
||||||
// Required axis labels
|
|
||||||
showCatAxisTitle: true,
|
|
||||||
catAxisTitle: 'Quarter',
|
|
||||||
showValAxisTitle: true,
|
|
||||||
valAxisTitle: 'Sales ($000s)',
|
|
||||||
// Optional: Control scaling (adjust min based on data range for better visualization)
|
|
||||||
valAxisMaxVal: 8000,
|
|
||||||
valAxisMinVal: 0, // Use 0 for counts/amounts; for clustered data (e.g., 4500-7100), consider starting closer to min value
|
|
||||||
valAxisMajorUnit: 2000, // Control y-axis label spacing to prevent crowding
|
|
||||||
catAxisLabelRotate: 45, // Rotate labels if crowded
|
|
||||||
dataLabelPosition: 'outEnd',
|
|
||||||
dataLabelColor: '000000',
|
|
||||||
// Use single color for single-series charts
|
|
||||||
chartColors: ["4472C4"] // All bars same color
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Scatter Chart
|
|
||||||
|
|
||||||
**IMPORTANT**: Scatter chart data format is unusual - first series contains X-axis values, subsequent series contain Y-values:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Prepare data
|
|
||||||
const data1 = [{ x: 10, y: 20 }, { x: 15, y: 25 }, { x: 20, y: 30 }];
|
|
||||||
const data2 = [{ x: 12, y: 18 }, { x: 18, y: 22 }];
|
|
||||||
|
|
||||||
const allXValues = [...data1.map(d => d.x), ...data2.map(d => d.x)];
|
|
||||||
|
|
||||||
slide.addChart(pptx.charts.SCATTER, [
|
|
||||||
{ name: 'X-Axis', values: allXValues }, // First series = X values
|
|
||||||
{ name: 'Series 1', values: data1.map(d => d.y) }, // Y values only
|
|
||||||
{ name: 'Series 2', values: data2.map(d => d.y) } // Y values only
|
|
||||||
], {
|
|
||||||
x: 1, y: 1, w: 8, h: 4,
|
|
||||||
lineSize: 0, // 0 = no connecting lines
|
|
||||||
lineDataSymbol: 'circle',
|
|
||||||
lineDataSymbolSize: 6,
|
|
||||||
showCatAxisTitle: true,
|
|
||||||
catAxisTitle: 'X Axis',
|
|
||||||
showValAxisTitle: true,
|
|
||||||
valAxisTitle: 'Y Axis',
|
|
||||||
chartColors: ["4472C4", "ED7D31"]
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Line Chart
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
slide.addChart(pptx.charts.LINE, [{
|
|
||||||
name: "Temperature",
|
|
||||||
labels: ["Jan", "Feb", "Mar", "Apr"],
|
|
||||||
values: [32, 35, 42, 55]
|
|
||||||
}], {
|
|
||||||
x: 1, y: 1, w: 8, h: 4,
|
|
||||||
lineSize: 4,
|
|
||||||
lineSmooth: true,
|
|
||||||
// Required axis labels
|
|
||||||
showCatAxisTitle: true,
|
|
||||||
catAxisTitle: 'Month',
|
|
||||||
showValAxisTitle: true,
|
|
||||||
valAxisTitle: 'Temperature (°F)',
|
|
||||||
// Optional: Y-axis range (set min based on data range for better visualization)
|
|
||||||
valAxisMinVal: 0, // For ranges starting at 0 (counts, percentages, etc.)
|
|
||||||
valAxisMaxVal: 60,
|
|
||||||
valAxisMajorUnit: 20, // Control y-axis label spacing to prevent crowding (e.g., 10, 20, 25)
|
|
||||||
// valAxisMinVal: 30, // PREFERRED: For data clustered in a range (e.g., 32-55 or ratings 3-5), start axis closer to min value to show variation
|
|
||||||
// Optional: Chart colors
|
|
||||||
chartColors: ["4472C4", "ED7D31", "A5A5A5"]
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Pie Chart (No Axis Labels Required)
|
|
||||||
|
|
||||||
**CRITICAL**: Pie charts require a **single data series** with all categories in the `labels` array and corresponding values in the `values` array.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
slide.addChart(pptx.charts.PIE, [{
|
|
||||||
name: "Market Share",
|
|
||||||
labels: ["Product A", "Product B", "Other"], // All categories in one array
|
|
||||||
values: [35, 45, 20] // All values in one array
|
|
||||||
}], {
|
|
||||||
x: 2, y: 1, w: 6, h: 4,
|
|
||||||
showPercent: true,
|
|
||||||
showLegend: true,
|
|
||||||
legendPos: 'r', // right
|
|
||||||
chartColors: ["4472C4", "ED7D31", "A5A5A5"]
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Multiple Data Series
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
slide.addChart(pptx.charts.LINE, [
|
|
||||||
{
|
|
||||||
name: "Product A",
|
|
||||||
labels: ["Q1", "Q2", "Q3", "Q4"],
|
|
||||||
values: [10, 20, 30, 40]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Product B",
|
|
||||||
labels: ["Q1", "Q2", "Q3", "Q4"],
|
|
||||||
values: [15, 25, 20, 35]
|
|
||||||
}
|
|
||||||
], {
|
|
||||||
x: 1, y: 1, w: 8, h: 4,
|
|
||||||
showCatAxisTitle: true,
|
|
||||||
catAxisTitle: 'Quarter',
|
|
||||||
showValAxisTitle: true,
|
|
||||||
valAxisTitle: 'Revenue ($M)'
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Chart Colors
|
|
||||||
|
|
||||||
**CRITICAL**: Use hex colors **without** the `#` prefix - including `#` causes file corruption.
|
|
||||||
|
|
||||||
**Align chart colors with your chosen design palette**, ensuring sufficient contrast and distinctiveness for data visualization. Adjust colors for:
|
|
||||||
- Strong contrast between adjacent series
|
|
||||||
- Readability against slide backgrounds
|
|
||||||
- Accessibility (avoid red-green only combinations)
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Example: Ocean palette-inspired chart colors (adjusted for contrast)
|
|
||||||
const chartColors = ["16A085", "FF6B9D", "2C3E50", "F39C12", "9B59B6"];
|
|
||||||
|
|
||||||
// Single-series chart: Use one color for all bars/points
|
|
||||||
slide.addChart(pptx.charts.BAR, [{
|
|
||||||
name: "Sales",
|
|
||||||
labels: ["Q1", "Q2", "Q3", "Q4"],
|
|
||||||
values: [4500, 5500, 6200, 7100]
|
|
||||||
}], {
|
|
||||||
...placeholders[0],
|
|
||||||
chartColors: ["16A085"], // All bars same color
|
|
||||||
showLegend: false
|
|
||||||
});
|
|
||||||
|
|
||||||
// Multi-series chart: Each series gets a different color
|
|
||||||
slide.addChart(pptx.charts.LINE, [
|
|
||||||
{ name: "Product A", labels: ["Q1", "Q2", "Q3"], values: [10, 20, 30] },
|
|
||||||
{ name: "Product B", labels: ["Q1", "Q2", "Q3"], values: [15, 25, 20] }
|
|
||||||
], {
|
|
||||||
...placeholders[0],
|
|
||||||
chartColors: ["16A085", "FF6B9D"] // One color per series
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Adding Tables
|
|
||||||
|
|
||||||
Tables can be added with basic or advanced formatting:
|
|
||||||
|
|
||||||
#### Basic Table
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
slide.addTable([
|
|
||||||
["Header 1", "Header 2", "Header 3"],
|
|
||||||
["Row 1, Col 1", "Row 1, Col 2", "Row 1, Col 3"],
|
|
||||||
["Row 2, Col 1", "Row 2, Col 2", "Row 2, Col 3"]
|
|
||||||
], {
|
|
||||||
x: 0.5,
|
|
||||||
y: 1,
|
|
||||||
w: 9,
|
|
||||||
h: 3,
|
|
||||||
border: { pt: 1, color: "999999" },
|
|
||||||
fill: { color: "F1F1F1" }
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Table with Custom Formatting
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const tableData = [
|
|
||||||
// Header row with custom styling
|
|
||||||
[
|
|
||||||
{ text: "Product", options: { fill: { color: "4472C4" }, color: "FFFFFF", bold: true } },
|
|
||||||
{ text: "Revenue", options: { fill: { color: "4472C4" }, color: "FFFFFF", bold: true } },
|
|
||||||
{ text: "Growth", options: { fill: { color: "4472C4" }, color: "FFFFFF", bold: true } }
|
|
||||||
],
|
|
||||||
// Data rows
|
|
||||||
["Product A", "$50M", "+15%"],
|
|
||||||
["Product B", "$35M", "+22%"],
|
|
||||||
["Product C", "$28M", "+8%"]
|
|
||||||
];
|
|
||||||
|
|
||||||
slide.addTable(tableData, {
|
|
||||||
x: 1,
|
|
||||||
y: 1.5,
|
|
||||||
w: 8,
|
|
||||||
h: 3,
|
|
||||||
colW: [3, 2.5, 2.5], // Column widths
|
|
||||||
rowH: [0.5, 0.6, 0.6, 0.6], // Row heights
|
|
||||||
border: { pt: 1, color: "CCCCCC" },
|
|
||||||
align: "center",
|
|
||||||
valign: "middle",
|
|
||||||
fontSize: 14
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Table with Merged Cells
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const mergedTableData = [
|
|
||||||
[
|
|
||||||
{ text: "Q1 Results", options: { colspan: 3, fill: { color: "4472C4" }, color: "FFFFFF", bold: true } }
|
|
||||||
],
|
|
||||||
["Product", "Sales", "Market Share"],
|
|
||||||
["Product A", "$25M", "35%"],
|
|
||||||
["Product B", "$18M", "25%"]
|
|
||||||
];
|
|
||||||
|
|
||||||
slide.addTable(mergedTableData, {
|
|
||||||
x: 1,
|
|
||||||
y: 1,
|
|
||||||
w: 8,
|
|
||||||
h: 2.5,
|
|
||||||
colW: [3, 2.5, 2.5],
|
|
||||||
border: { pt: 1, color: "DDDDDD" }
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Table Options
|
|
||||||
|
|
||||||
Common table options:
|
|
||||||
- `x, y, w, h` - Position and size
|
|
||||||
- `colW` - Array of column widths (in inches)
|
|
||||||
- `rowH` - Array of row heights (in inches)
|
|
||||||
- `border` - Border style: `{ pt: 1, color: "999999" }`
|
|
||||||
- `fill` - Background color (no # prefix)
|
|
||||||
- `align` - Text alignment: "left", "center", "right"
|
|
||||||
- `valign` - Vertical alignment: "top", "middle", "bottom"
|
|
||||||
- `fontSize` - Text size
|
|
||||||
- `autoPage` - Auto-create new slides if content overflows
|
|
||||||
@@ -1,427 +0,0 @@
|
|||||||
# Office Open XML Technical Reference for PowerPoint
|
|
||||||
|
|
||||||
**Important: Read this entire document before starting.** Critical XML schema rules and formatting requirements are covered throughout. Incorrect implementation can create invalid PPTX files that PowerPoint cannot open.
|
|
||||||
|
|
||||||
## Technical Guidelines
|
|
||||||
|
|
||||||
### Schema Compliance
|
|
||||||
- **Element ordering in `<p:txBody>`**: `<a:bodyPr>`, `<a:lstStyle>`, `<a:p>`
|
|
||||||
- **Whitespace**: Add `xml:space='preserve'` to `<a:t>` elements with leading/trailing spaces
|
|
||||||
- **Unicode**: Escape characters in ASCII content: `"` becomes `“`
|
|
||||||
- **Images**: Add to `ppt/media/`, reference in slide XML, set dimensions to fit slide bounds
|
|
||||||
- **Relationships**: Update `ppt/slides/_rels/slideN.xml.rels` for each slide's resources
|
|
||||||
- **Dirty attribute**: Add `dirty="0"` to `<a:rPr>` and `<a:endParaRPr>` elements to indicate clean state
|
|
||||||
|
|
||||||
## Presentation Structure
|
|
||||||
|
|
||||||
### Basic Slide Structure
|
|
||||||
```xml
|
|
||||||
<!-- ppt/slides/slide1.xml -->
|
|
||||||
<p:sld>
|
|
||||||
<p:cSld>
|
|
||||||
<p:spTree>
|
|
||||||
<p:nvGrpSpPr>...</p:nvGrpSpPr>
|
|
||||||
<p:grpSpPr>...</p:grpSpPr>
|
|
||||||
<!-- Shapes go here -->
|
|
||||||
</p:spTree>
|
|
||||||
</p:cSld>
|
|
||||||
</p:sld>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Text Box / Shape with Text
|
|
||||||
```xml
|
|
||||||
<p:sp>
|
|
||||||
<p:nvSpPr>
|
|
||||||
<p:cNvPr id="2" name="Title"/>
|
|
||||||
<p:cNvSpPr>
|
|
||||||
<a:spLocks noGrp="1"/>
|
|
||||||
</p:cNvSpPr>
|
|
||||||
<p:nvPr>
|
|
||||||
<p:ph type="ctrTitle"/>
|
|
||||||
</p:nvPr>
|
|
||||||
</p:nvSpPr>
|
|
||||||
<p:spPr>
|
|
||||||
<a:xfrm>
|
|
||||||
<a:off x="838200" y="365125"/>
|
|
||||||
<a:ext cx="7772400" cy="1470025"/>
|
|
||||||
</a:xfrm>
|
|
||||||
</p:spPr>
|
|
||||||
<p:txBody>
|
|
||||||
<a:bodyPr/>
|
|
||||||
<a:lstStyle/>
|
|
||||||
<a:p>
|
|
||||||
<a:r>
|
|
||||||
<a:t>Slide Title</a:t>
|
|
||||||
</a:r>
|
|
||||||
</a:p>
|
|
||||||
</p:txBody>
|
|
||||||
</p:sp>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Text Formatting
|
|
||||||
```xml
|
|
||||||
<!-- Bold -->
|
|
||||||
<a:r>
|
|
||||||
<a:rPr b="1"/>
|
|
||||||
<a:t>Bold Text</a:t>
|
|
||||||
</a:r>
|
|
||||||
|
|
||||||
<!-- Italic -->
|
|
||||||
<a:r>
|
|
||||||
<a:rPr i="1"/>
|
|
||||||
<a:t>Italic Text</a:t>
|
|
||||||
</a:r>
|
|
||||||
|
|
||||||
<!-- Underline -->
|
|
||||||
<a:r>
|
|
||||||
<a:rPr u="sng"/>
|
|
||||||
<a:t>Underlined</a:t>
|
|
||||||
</a:r>
|
|
||||||
|
|
||||||
<!-- Highlight -->
|
|
||||||
<a:r>
|
|
||||||
<a:rPr>
|
|
||||||
<a:highlight>
|
|
||||||
<a:srgbClr val="FFFF00"/>
|
|
||||||
</a:highlight>
|
|
||||||
</a:rPr>
|
|
||||||
<a:t>Highlighted Text</a:t>
|
|
||||||
</a:r>
|
|
||||||
|
|
||||||
<!-- Font and Size -->
|
|
||||||
<a:r>
|
|
||||||
<a:rPr sz="2400" typeface="Arial">
|
|
||||||
<a:solidFill>
|
|
||||||
<a:srgbClr val="FF0000"/>
|
|
||||||
</a:solidFill>
|
|
||||||
</a:rPr>
|
|
||||||
<a:t>Colored Arial 24pt</a:t>
|
|
||||||
</a:r>
|
|
||||||
|
|
||||||
<!-- Complete formatting example -->
|
|
||||||
<a:r>
|
|
||||||
<a:rPr lang="en-US" sz="1400" b="1" dirty="0">
|
|
||||||
<a:solidFill>
|
|
||||||
<a:srgbClr val="FAFAFA"/>
|
|
||||||
</a:solidFill>
|
|
||||||
</a:rPr>
|
|
||||||
<a:t>Formatted text</a:t>
|
|
||||||
</a:r>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Lists
|
|
||||||
```xml
|
|
||||||
<!-- Bullet list -->
|
|
||||||
<a:p>
|
|
||||||
<a:pPr lvl="0">
|
|
||||||
<a:buChar char="•"/>
|
|
||||||
</a:pPr>
|
|
||||||
<a:r>
|
|
||||||
<a:t>First bullet point</a:t>
|
|
||||||
</a:r>
|
|
||||||
</a:p>
|
|
||||||
|
|
||||||
<!-- Numbered list -->
|
|
||||||
<a:p>
|
|
||||||
<a:pPr lvl="0">
|
|
||||||
<a:buAutoNum type="arabicPeriod"/>
|
|
||||||
</a:pPr>
|
|
||||||
<a:r>
|
|
||||||
<a:t>First numbered item</a:t>
|
|
||||||
</a:r>
|
|
||||||
</a:p>
|
|
||||||
|
|
||||||
<!-- Second level indent -->
|
|
||||||
<a:p>
|
|
||||||
<a:pPr lvl="1">
|
|
||||||
<a:buChar char="•"/>
|
|
||||||
</a:pPr>
|
|
||||||
<a:r>
|
|
||||||
<a:t>Indented bullet</a:t>
|
|
||||||
</a:r>
|
|
||||||
</a:p>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Shapes
|
|
||||||
```xml
|
|
||||||
<!-- Rectangle -->
|
|
||||||
<p:sp>
|
|
||||||
<p:nvSpPr>
|
|
||||||
<p:cNvPr id="3" name="Rectangle"/>
|
|
||||||
<p:cNvSpPr/>
|
|
||||||
<p:nvPr/>
|
|
||||||
</p:nvSpPr>
|
|
||||||
<p:spPr>
|
|
||||||
<a:xfrm>
|
|
||||||
<a:off x="1000000" y="1000000"/>
|
|
||||||
<a:ext cx="3000000" cy="2000000"/>
|
|
||||||
</a:xfrm>
|
|
||||||
<a:prstGeom prst="rect">
|
|
||||||
<a:avLst/>
|
|
||||||
</a:prstGeom>
|
|
||||||
<a:solidFill>
|
|
||||||
<a:srgbClr val="FF0000"/>
|
|
||||||
</a:solidFill>
|
|
||||||
<a:ln w="25400">
|
|
||||||
<a:solidFill>
|
|
||||||
<a:srgbClr val="000000"/>
|
|
||||||
</a:solidFill>
|
|
||||||
</a:ln>
|
|
||||||
</p:spPr>
|
|
||||||
</p:sp>
|
|
||||||
|
|
||||||
<!-- Rounded Rectangle -->
|
|
||||||
<p:sp>
|
|
||||||
<p:spPr>
|
|
||||||
<a:prstGeom prst="roundRect">
|
|
||||||
<a:avLst/>
|
|
||||||
</a:prstGeom>
|
|
||||||
</p:spPr>
|
|
||||||
</p:sp>
|
|
||||||
|
|
||||||
<!-- Circle/Ellipse -->
|
|
||||||
<p:sp>
|
|
||||||
<p:spPr>
|
|
||||||
<a:prstGeom prst="ellipse">
|
|
||||||
<a:avLst/>
|
|
||||||
</a:prstGeom>
|
|
||||||
</p:spPr>
|
|
||||||
</p:sp>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Images
|
|
||||||
```xml
|
|
||||||
<p:pic>
|
|
||||||
<p:nvPicPr>
|
|
||||||
<p:cNvPr id="4" name="Picture">
|
|
||||||
<a:hlinkClick r:id="" action="ppaction://media"/>
|
|
||||||
</p:cNvPr>
|
|
||||||
<p:cNvPicPr>
|
|
||||||
<a:picLocks noChangeAspect="1"/>
|
|
||||||
</p:cNvPicPr>
|
|
||||||
<p:nvPr/>
|
|
||||||
</p:nvPicPr>
|
|
||||||
<p:blipFill>
|
|
||||||
<a:blip r:embed="rId2"/>
|
|
||||||
<a:stretch>
|
|
||||||
<a:fillRect/>
|
|
||||||
</a:stretch>
|
|
||||||
</p:blipFill>
|
|
||||||
<p:spPr>
|
|
||||||
<a:xfrm>
|
|
||||||
<a:off x="1000000" y="1000000"/>
|
|
||||||
<a:ext cx="3000000" cy="2000000"/>
|
|
||||||
</a:xfrm>
|
|
||||||
<a:prstGeom prst="rect">
|
|
||||||
<a:avLst/>
|
|
||||||
</a:prstGeom>
|
|
||||||
</p:spPr>
|
|
||||||
</p:pic>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Tables
|
|
||||||
```xml
|
|
||||||
<p:graphicFrame>
|
|
||||||
<p:nvGraphicFramePr>
|
|
||||||
<p:cNvPr id="5" name="Table"/>
|
|
||||||
<p:cNvGraphicFramePr>
|
|
||||||
<a:graphicFrameLocks noGrp="1"/>
|
|
||||||
</p:cNvGraphicFramePr>
|
|
||||||
<p:nvPr/>
|
|
||||||
</p:nvGraphicFramePr>
|
|
||||||
<p:xfrm>
|
|
||||||
<a:off x="1000000" y="1000000"/>
|
|
||||||
<a:ext cx="6000000" cy="2000000"/>
|
|
||||||
</p:xfrm>
|
|
||||||
<a:graphic>
|
|
||||||
<a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/table">
|
|
||||||
<a:tbl>
|
|
||||||
<a:tblGrid>
|
|
||||||
<a:gridCol w="3000000"/>
|
|
||||||
<a:gridCol w="3000000"/>
|
|
||||||
</a:tblGrid>
|
|
||||||
<a:tr h="500000">
|
|
||||||
<a:tc>
|
|
||||||
<a:txBody>
|
|
||||||
<a:bodyPr/>
|
|
||||||
<a:lstStyle/>
|
|
||||||
<a:p>
|
|
||||||
<a:r>
|
|
||||||
<a:t>Cell 1</a:t>
|
|
||||||
</a:r>
|
|
||||||
</a:p>
|
|
||||||
</a:txBody>
|
|
||||||
</a:tc>
|
|
||||||
<a:tc>
|
|
||||||
<a:txBody>
|
|
||||||
<a:bodyPr/>
|
|
||||||
<a:lstStyle/>
|
|
||||||
<a:p>
|
|
||||||
<a:r>
|
|
||||||
<a:t>Cell 2</a:t>
|
|
||||||
</a:r>
|
|
||||||
</a:p>
|
|
||||||
</a:txBody>
|
|
||||||
</a:tc>
|
|
||||||
</a:tr>
|
|
||||||
</a:tbl>
|
|
||||||
</a:graphicData>
|
|
||||||
</a:graphic>
|
|
||||||
</p:graphicFrame>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Slide Layouts
|
|
||||||
|
|
||||||
```xml
|
|
||||||
<!-- Title Slide Layout -->
|
|
||||||
<p:sp>
|
|
||||||
<p:nvSpPr>
|
|
||||||
<p:nvPr>
|
|
||||||
<p:ph type="ctrTitle"/>
|
|
||||||
</p:nvPr>
|
|
||||||
</p:nvSpPr>
|
|
||||||
<!-- Title content -->
|
|
||||||
</p:sp>
|
|
||||||
|
|
||||||
<p:sp>
|
|
||||||
<p:nvSpPr>
|
|
||||||
<p:nvPr>
|
|
||||||
<p:ph type="subTitle" idx="1"/>
|
|
||||||
</p:nvPr>
|
|
||||||
</p:nvSpPr>
|
|
||||||
<!-- Subtitle content -->
|
|
||||||
</p:sp>
|
|
||||||
|
|
||||||
<!-- Content Slide Layout -->
|
|
||||||
<p:sp>
|
|
||||||
<p:nvSpPr>
|
|
||||||
<p:nvPr>
|
|
||||||
<p:ph type="title"/>
|
|
||||||
</p:nvPr>
|
|
||||||
</p:nvSpPr>
|
|
||||||
<!-- Slide title -->
|
|
||||||
</p:sp>
|
|
||||||
|
|
||||||
<p:sp>
|
|
||||||
<p:nvSpPr>
|
|
||||||
<p:nvPr>
|
|
||||||
<p:ph type="body" idx="1"/>
|
|
||||||
</p:nvPr>
|
|
||||||
</p:nvSpPr>
|
|
||||||
<!-- Content body -->
|
|
||||||
</p:sp>
|
|
||||||
```
|
|
||||||
|
|
||||||
## File Updates
|
|
||||||
|
|
||||||
When adding content, update these files:
|
|
||||||
|
|
||||||
**`ppt/_rels/presentation.xml.rels`:**
|
|
||||||
```xml
|
|
||||||
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide" Target="slides/slide1.xml"/>
|
|
||||||
<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster" Target="slideMasters/slideMaster1.xml"/>
|
|
||||||
```
|
|
||||||
|
|
||||||
**`ppt/slides/_rels/slide1.xml.rels`:**
|
|
||||||
```xml
|
|
||||||
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout" Target="../slideLayouts/slideLayout1.xml"/>
|
|
||||||
<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="../media/image1.png"/>
|
|
||||||
```
|
|
||||||
|
|
||||||
**`[Content_Types].xml`:**
|
|
||||||
```xml
|
|
||||||
<Default Extension="png" ContentType="image/png"/>
|
|
||||||
<Default Extension="jpg" ContentType="image/jpeg"/>
|
|
||||||
<Override PartName="/ppt/slides/slide1.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slide+xml"/>
|
|
||||||
```
|
|
||||||
|
|
||||||
**`ppt/presentation.xml`:**
|
|
||||||
```xml
|
|
||||||
<p:sldIdLst>
|
|
||||||
<p:sldId id="256" r:id="rId1"/>
|
|
||||||
<p:sldId id="257" r:id="rId2"/>
|
|
||||||
</p:sldIdLst>
|
|
||||||
```
|
|
||||||
|
|
||||||
**`docProps/app.xml`:** Update slide count and statistics
|
|
||||||
```xml
|
|
||||||
<Slides>2</Slides>
|
|
||||||
<Paragraphs>10</Paragraphs>
|
|
||||||
<Words>50</Words>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Slide Operations
|
|
||||||
|
|
||||||
### Adding a New Slide
|
|
||||||
When adding a slide to the end of the presentation:
|
|
||||||
|
|
||||||
1. **Create the slide file** (`ppt/slides/slideN.xml`)
|
|
||||||
2. **Update `[Content_Types].xml`**: Add Override for the new slide
|
|
||||||
3. **Update `ppt/_rels/presentation.xml.rels`**: Add relationship for the new slide
|
|
||||||
4. **Update `ppt/presentation.xml`**: Add slide ID to `<p:sldIdLst>`
|
|
||||||
5. **Create slide relationships** (`ppt/slides/_rels/slideN.xml.rels`) if needed
|
|
||||||
6. **Update `docProps/app.xml`**: Increment slide count and update statistics (if present)
|
|
||||||
|
|
||||||
### Duplicating a Slide
|
|
||||||
1. Copy the source slide XML file with a new name
|
|
||||||
2. Update all IDs in the new slide to be unique
|
|
||||||
3. Follow the "Adding a New Slide" steps above
|
|
||||||
4. **CRITICAL**: Remove or update any notes slide references in `_rels` files
|
|
||||||
5. Remove references to unused media files
|
|
||||||
|
|
||||||
### Reordering Slides
|
|
||||||
1. **Update `ppt/presentation.xml`**: Reorder `<p:sldId>` elements in `<p:sldIdLst>`
|
|
||||||
2. The order of `<p:sldId>` elements determines slide order
|
|
||||||
3. Keep slide IDs and relationship IDs unchanged
|
|
||||||
|
|
||||||
Example:
|
|
||||||
```xml
|
|
||||||
<!-- Original order -->
|
|
||||||
<p:sldIdLst>
|
|
||||||
<p:sldId id="256" r:id="rId2"/>
|
|
||||||
<p:sldId id="257" r:id="rId3"/>
|
|
||||||
<p:sldId id="258" r:id="rId4"/>
|
|
||||||
</p:sldIdLst>
|
|
||||||
|
|
||||||
<!-- After moving slide 3 to position 2 -->
|
|
||||||
<p:sldIdLst>
|
|
||||||
<p:sldId id="256" r:id="rId2"/>
|
|
||||||
<p:sldId id="258" r:id="rId4"/>
|
|
||||||
<p:sldId id="257" r:id="rId3"/>
|
|
||||||
</p:sldIdLst>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Deleting a Slide
|
|
||||||
1. **Remove from `ppt/presentation.xml`**: Delete the `<p:sldId>` entry
|
|
||||||
2. **Remove from `ppt/_rels/presentation.xml.rels`**: Delete the relationship
|
|
||||||
3. **Remove from `[Content_Types].xml`**: Delete the Override entry
|
|
||||||
4. **Delete files**: Remove `ppt/slides/slideN.xml` and `ppt/slides/_rels/slideN.xml.rels`
|
|
||||||
5. **Update `docProps/app.xml`**: Decrement slide count and update statistics
|
|
||||||
6. **Clean up unused media**: Remove orphaned images from `ppt/media/`
|
|
||||||
|
|
||||||
Note: Don't renumber remaining slides - keep their original IDs and filenames.
|
|
||||||
|
|
||||||
|
|
||||||
## Common Errors to Avoid
|
|
||||||
|
|
||||||
- **Encodings**: Escape unicode characters in ASCII content: `"` becomes `“`
|
|
||||||
- **Images**: Add to `ppt/media/` and update relationship files
|
|
||||||
- **Lists**: Omit bullets from list headers
|
|
||||||
- **IDs**: Use valid hexadecimal values for UUIDs
|
|
||||||
- **Themes**: Check all themes in `theme` directory for colors
|
|
||||||
|
|
||||||
## Validation Checklist for Template-Based Presentations
|
|
||||||
|
|
||||||
### Before Packing, Always:
|
|
||||||
- **Clean unused resources**: Remove unreferenced media, fonts, and notes directories
|
|
||||||
- **Fix Content_Types.xml**: Declare ALL slides, layouts, and themes present in the package
|
|
||||||
- **Fix relationship IDs**:
|
|
||||||
- Remove font embed references if not using embedded fonts
|
|
||||||
- **Remove broken references**: Check all `_rels` files for references to deleted resources
|
|
||||||
|
|
||||||
### Common Template Duplication Pitfalls:
|
|
||||||
- Multiple slides referencing the same notes slide after duplication
|
|
||||||
- Image/media references from template slides that no longer exist
|
|
||||||
- Font embedding references when fonts aren't included
|
|
||||||
- Missing slideLayout declarations for layouts 12-25
|
|
||||||
- docProps directory may not unpack - this is optional
|
|
||||||
-1499
File diff suppressed because it is too large
Load Diff
-146
@@ -1,146 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
|
||||||
xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
|
|
||||||
xmlns="http://schemas.openxmlformats.org/drawingml/2006/chartDrawing"
|
|
||||||
targetNamespace="http://schemas.openxmlformats.org/drawingml/2006/chartDrawing"
|
|
||||||
elementFormDefault="qualified">
|
|
||||||
<xsd:import namespace="http://schemas.openxmlformats.org/drawingml/2006/main"
|
|
||||||
schemaLocation="dml-main.xsd"/>
|
|
||||||
<xsd:complexType name="CT_ShapeNonVisual">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
|
|
||||||
<xsd:element name="cNvSpPr" type="a:CT_NonVisualDrawingShapeProps" minOccurs="1" maxOccurs="1"
|
|
||||||
/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_Shape">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="nvSpPr" type="CT_ShapeNonVisual" minOccurs="1" maxOccurs="1"/>
|
|
||||||
<xsd:element name="spPr" type="a:CT_ShapeProperties" minOccurs="1" maxOccurs="1"/>
|
|
||||||
<xsd:element name="style" type="a:CT_ShapeStyle" minOccurs="0" maxOccurs="1"/>
|
|
||||||
<xsd:element name="txBody" type="a:CT_TextBody" minOccurs="0" maxOccurs="1"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="macro" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="textlink" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="fLocksText" type="xsd:boolean" use="optional" default="true"/>
|
|
||||||
<xsd:attribute name="fPublished" type="xsd:boolean" use="optional" default="false"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_ConnectorNonVisual">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
|
|
||||||
<xsd:element name="cNvCxnSpPr" type="a:CT_NonVisualConnectorProperties" minOccurs="1"
|
|
||||||
maxOccurs="1"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_Connector">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="nvCxnSpPr" type="CT_ConnectorNonVisual" minOccurs="1" maxOccurs="1"/>
|
|
||||||
<xsd:element name="spPr" type="a:CT_ShapeProperties" minOccurs="1" maxOccurs="1"/>
|
|
||||||
<xsd:element name="style" type="a:CT_ShapeStyle" minOccurs="0" maxOccurs="1"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="macro" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="fPublished" type="xsd:boolean" use="optional" default="false"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_PictureNonVisual">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
|
|
||||||
<xsd:element name="cNvPicPr" type="a:CT_NonVisualPictureProperties" minOccurs="1"
|
|
||||||
maxOccurs="1"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_Picture">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="nvPicPr" type="CT_PictureNonVisual" minOccurs="1" maxOccurs="1"/>
|
|
||||||
<xsd:element name="blipFill" type="a:CT_BlipFillProperties" minOccurs="1" maxOccurs="1"/>
|
|
||||||
<xsd:element name="spPr" type="a:CT_ShapeProperties" minOccurs="1" maxOccurs="1"/>
|
|
||||||
<xsd:element name="style" type="a:CT_ShapeStyle" minOccurs="0" maxOccurs="1"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="macro" type="xsd:string" use="optional" default=""/>
|
|
||||||
<xsd:attribute name="fPublished" type="xsd:boolean" use="optional" default="false"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_GraphicFrameNonVisual">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
|
|
||||||
<xsd:element name="cNvGraphicFramePr" type="a:CT_NonVisualGraphicFrameProperties"
|
|
||||||
minOccurs="1" maxOccurs="1"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_GraphicFrame">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="nvGraphicFramePr" type="CT_GraphicFrameNonVisual" minOccurs="1"
|
|
||||||
maxOccurs="1"/>
|
|
||||||
<xsd:element name="xfrm" type="a:CT_Transform2D" minOccurs="1" maxOccurs="1"/>
|
|
||||||
<xsd:element ref="a:graphic" minOccurs="1" maxOccurs="1"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="macro" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="fPublished" type="xsd:boolean" use="optional" default="false"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_GroupShapeNonVisual">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
|
|
||||||
<xsd:element name="cNvGrpSpPr" type="a:CT_NonVisualGroupDrawingShapeProps" minOccurs="1"
|
|
||||||
maxOccurs="1"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_GroupShape">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="nvGrpSpPr" type="CT_GroupShapeNonVisual" minOccurs="1" maxOccurs="1"/>
|
|
||||||
<xsd:element name="grpSpPr" type="a:CT_GroupShapeProperties" minOccurs="1" maxOccurs="1"/>
|
|
||||||
<xsd:choice minOccurs="0" maxOccurs="unbounded">
|
|
||||||
<xsd:element name="sp" type="CT_Shape"/>
|
|
||||||
<xsd:element name="grpSp" type="CT_GroupShape"/>
|
|
||||||
<xsd:element name="graphicFrame" type="CT_GraphicFrame"/>
|
|
||||||
<xsd:element name="cxnSp" type="CT_Connector"/>
|
|
||||||
<xsd:element name="pic" type="CT_Picture"/>
|
|
||||||
</xsd:choice>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:group name="EG_ObjectChoices">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:choice minOccurs="1" maxOccurs="1">
|
|
||||||
<xsd:element name="sp" type="CT_Shape"/>
|
|
||||||
<xsd:element name="grpSp" type="CT_GroupShape"/>
|
|
||||||
<xsd:element name="graphicFrame" type="CT_GraphicFrame"/>
|
|
||||||
<xsd:element name="cxnSp" type="CT_Connector"/>
|
|
||||||
<xsd:element name="pic" type="CT_Picture"/>
|
|
||||||
</xsd:choice>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:group>
|
|
||||||
<xsd:simpleType name="ST_MarkerCoordinate">
|
|
||||||
<xsd:restriction base="xsd:double">
|
|
||||||
<xsd:minInclusive value="0.0"/>
|
|
||||||
<xsd:maxInclusive value="1.0"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:complexType name="CT_Marker">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="x" type="ST_MarkerCoordinate" minOccurs="1" maxOccurs="1"/>
|
|
||||||
<xsd:element name="y" type="ST_MarkerCoordinate" minOccurs="1" maxOccurs="1"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_RelSizeAnchor">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="from" type="CT_Marker"/>
|
|
||||||
<xsd:element name="to" type="CT_Marker"/>
|
|
||||||
<xsd:group ref="EG_ObjectChoices"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_AbsSizeAnchor">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="from" type="CT_Marker"/>
|
|
||||||
<xsd:element name="ext" type="a:CT_PositiveSize2D"/>
|
|
||||||
<xsd:group ref="EG_ObjectChoices"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:group name="EG_Anchor">
|
|
||||||
<xsd:choice>
|
|
||||||
<xsd:element name="relSizeAnchor" type="CT_RelSizeAnchor"/>
|
|
||||||
<xsd:element name="absSizeAnchor" type="CT_AbsSizeAnchor"/>
|
|
||||||
</xsd:choice>
|
|
||||||
</xsd:group>
|
|
||||||
<xsd:complexType name="CT_Drawing">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:group ref="EG_Anchor" minOccurs="0" maxOccurs="unbounded"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
</xsd:schema>
|
|
||||||
-1085
File diff suppressed because it is too large
Load Diff
-11
@@ -1,11 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
|
||||||
xmlns="http://schemas.openxmlformats.org/drawingml/2006/lockedCanvas"
|
|
||||||
xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
|
|
||||||
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
|
|
||||||
elementFormDefault="qualified"
|
|
||||||
targetNamespace="http://schemas.openxmlformats.org/drawingml/2006/lockedCanvas">
|
|
||||||
<xsd:import namespace="http://schemas.openxmlformats.org/drawingml/2006/main"
|
|
||||||
schemaLocation="dml-main.xsd"/>
|
|
||||||
<xsd:element name="lockedCanvas" type="a:CT_GvmlGroupShape"/>
|
|
||||||
</xsd:schema>
|
|
||||||
-3081
File diff suppressed because it is too large
Load Diff
-23
@@ -1,23 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
|
||||||
xmlns="http://schemas.openxmlformats.org/drawingml/2006/picture"
|
|
||||||
xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" elementFormDefault="qualified"
|
|
||||||
targetNamespace="http://schemas.openxmlformats.org/drawingml/2006/picture">
|
|
||||||
<xsd:import namespace="http://schemas.openxmlformats.org/drawingml/2006/main"
|
|
||||||
schemaLocation="dml-main.xsd"/>
|
|
||||||
<xsd:complexType name="CT_PictureNonVisual">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
|
|
||||||
<xsd:element name="cNvPicPr" type="a:CT_NonVisualPictureProperties" minOccurs="1"
|
|
||||||
maxOccurs="1"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_Picture">
|
|
||||||
<xsd:sequence minOccurs="1" maxOccurs="1">
|
|
||||||
<xsd:element name="nvPicPr" type="CT_PictureNonVisual" minOccurs="1" maxOccurs="1"/>
|
|
||||||
<xsd:element name="blipFill" type="a:CT_BlipFillProperties" minOccurs="1" maxOccurs="1"/>
|
|
||||||
<xsd:element name="spPr" type="a:CT_ShapeProperties" minOccurs="1" maxOccurs="1"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:element name="pic" type="CT_Picture"/>
|
|
||||||
</xsd:schema>
|
|
||||||
-185
@@ -1,185 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
|
||||||
xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
|
|
||||||
xmlns="http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"
|
|
||||||
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
|
|
||||||
targetNamespace="http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"
|
|
||||||
elementFormDefault="qualified">
|
|
||||||
<xsd:import namespace="http://schemas.openxmlformats.org/drawingml/2006/main"
|
|
||||||
schemaLocation="dml-main.xsd"/>
|
|
||||||
<xsd:import schemaLocation="shared-relationshipReference.xsd"
|
|
||||||
namespace="http://schemas.openxmlformats.org/officeDocument/2006/relationships"/>
|
|
||||||
<xsd:element name="from" type="CT_Marker"/>
|
|
||||||
<xsd:element name="to" type="CT_Marker"/>
|
|
||||||
<xsd:complexType name="CT_AnchorClientData">
|
|
||||||
<xsd:attribute name="fLocksWithSheet" type="xsd:boolean" use="optional" default="true"/>
|
|
||||||
<xsd:attribute name="fPrintsWithSheet" type="xsd:boolean" use="optional" default="true"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_ShapeNonVisual">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
|
|
||||||
<xsd:element name="cNvSpPr" type="a:CT_NonVisualDrawingShapeProps" minOccurs="1" maxOccurs="1"
|
|
||||||
/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_Shape">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="nvSpPr" type="CT_ShapeNonVisual" minOccurs="1" maxOccurs="1"/>
|
|
||||||
<xsd:element name="spPr" type="a:CT_ShapeProperties" minOccurs="1" maxOccurs="1"/>
|
|
||||||
<xsd:element name="style" type="a:CT_ShapeStyle" minOccurs="0" maxOccurs="1"/>
|
|
||||||
<xsd:element name="txBody" type="a:CT_TextBody" minOccurs="0" maxOccurs="1"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="macro" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="textlink" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="fLocksText" type="xsd:boolean" use="optional" default="true"/>
|
|
||||||
<xsd:attribute name="fPublished" type="xsd:boolean" use="optional" default="false"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_ConnectorNonVisual">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
|
|
||||||
<xsd:element name="cNvCxnSpPr" type="a:CT_NonVisualConnectorProperties" minOccurs="1"
|
|
||||||
maxOccurs="1"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_Connector">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="nvCxnSpPr" type="CT_ConnectorNonVisual" minOccurs="1" maxOccurs="1"/>
|
|
||||||
<xsd:element name="spPr" type="a:CT_ShapeProperties" minOccurs="1" maxOccurs="1"/>
|
|
||||||
<xsd:element name="style" type="a:CT_ShapeStyle" minOccurs="0" maxOccurs="1"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="macro" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="fPublished" type="xsd:boolean" use="optional" default="false"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_PictureNonVisual">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
|
|
||||||
<xsd:element name="cNvPicPr" type="a:CT_NonVisualPictureProperties" minOccurs="1"
|
|
||||||
maxOccurs="1"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_Picture">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="nvPicPr" type="CT_PictureNonVisual" minOccurs="1" maxOccurs="1"/>
|
|
||||||
<xsd:element name="blipFill" type="a:CT_BlipFillProperties" minOccurs="1" maxOccurs="1"/>
|
|
||||||
<xsd:element name="spPr" type="a:CT_ShapeProperties" minOccurs="1" maxOccurs="1"/>
|
|
||||||
<xsd:element name="style" type="a:CT_ShapeStyle" minOccurs="0" maxOccurs="1"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="macro" type="xsd:string" use="optional" default=""/>
|
|
||||||
<xsd:attribute name="fPublished" type="xsd:boolean" use="optional" default="false"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_GraphicalObjectFrameNonVisual">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
|
|
||||||
<xsd:element name="cNvGraphicFramePr" type="a:CT_NonVisualGraphicFrameProperties"
|
|
||||||
minOccurs="1" maxOccurs="1"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_GraphicalObjectFrame">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="nvGraphicFramePr" type="CT_GraphicalObjectFrameNonVisual" minOccurs="1"
|
|
||||||
maxOccurs="1"/>
|
|
||||||
<xsd:element name="xfrm" type="a:CT_Transform2D" minOccurs="1" maxOccurs="1"/>
|
|
||||||
<xsd:element ref="a:graphic" minOccurs="1" maxOccurs="1"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="macro" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="fPublished" type="xsd:boolean" use="optional" default="false"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_GroupShapeNonVisual">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
|
|
||||||
<xsd:element name="cNvGrpSpPr" type="a:CT_NonVisualGroupDrawingShapeProps" minOccurs="1"
|
|
||||||
maxOccurs="1"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_GroupShape">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="nvGrpSpPr" type="CT_GroupShapeNonVisual" minOccurs="1" maxOccurs="1"/>
|
|
||||||
<xsd:element name="grpSpPr" type="a:CT_GroupShapeProperties" minOccurs="1" maxOccurs="1"/>
|
|
||||||
<xsd:choice minOccurs="0" maxOccurs="unbounded">
|
|
||||||
<xsd:element name="sp" type="CT_Shape"/>
|
|
||||||
<xsd:element name="grpSp" type="CT_GroupShape"/>
|
|
||||||
<xsd:element name="graphicFrame" type="CT_GraphicalObjectFrame"/>
|
|
||||||
<xsd:element name="cxnSp" type="CT_Connector"/>
|
|
||||||
<xsd:element name="pic" type="CT_Picture"/>
|
|
||||||
</xsd:choice>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:group name="EG_ObjectChoices">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:choice minOccurs="1" maxOccurs="1">
|
|
||||||
<xsd:element name="sp" type="CT_Shape"/>
|
|
||||||
<xsd:element name="grpSp" type="CT_GroupShape"/>
|
|
||||||
<xsd:element name="graphicFrame" type="CT_GraphicalObjectFrame"/>
|
|
||||||
<xsd:element name="cxnSp" type="CT_Connector"/>
|
|
||||||
<xsd:element name="pic" type="CT_Picture"/>
|
|
||||||
<xsd:element name="contentPart" type="CT_Rel"/>
|
|
||||||
</xsd:choice>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:group>
|
|
||||||
<xsd:complexType name="CT_Rel">
|
|
||||||
<xsd:attribute ref="r:id" use="required"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:simpleType name="ST_ColID">
|
|
||||||
<xsd:restriction base="xsd:int">
|
|
||||||
<xsd:minInclusive value="0"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_RowID">
|
|
||||||
<xsd:restriction base="xsd:int">
|
|
||||||
<xsd:minInclusive value="0"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:complexType name="CT_Marker">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="col" type="ST_ColID"/>
|
|
||||||
<xsd:element name="colOff" type="a:ST_Coordinate"/>
|
|
||||||
<xsd:element name="row" type="ST_RowID"/>
|
|
||||||
<xsd:element name="rowOff" type="a:ST_Coordinate"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:simpleType name="ST_EditAs">
|
|
||||||
<xsd:restriction base="xsd:token">
|
|
||||||
<xsd:enumeration value="twoCell"/>
|
|
||||||
<xsd:enumeration value="oneCell"/>
|
|
||||||
<xsd:enumeration value="absolute"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:complexType name="CT_TwoCellAnchor">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="from" type="CT_Marker"/>
|
|
||||||
<xsd:element name="to" type="CT_Marker"/>
|
|
||||||
<xsd:group ref="EG_ObjectChoices"/>
|
|
||||||
<xsd:element name="clientData" type="CT_AnchorClientData" minOccurs="1" maxOccurs="1"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="editAs" type="ST_EditAs" use="optional" default="twoCell"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_OneCellAnchor">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="from" type="CT_Marker"/>
|
|
||||||
<xsd:element name="ext" type="a:CT_PositiveSize2D"/>
|
|
||||||
<xsd:group ref="EG_ObjectChoices"/>
|
|
||||||
<xsd:element name="clientData" type="CT_AnchorClientData" minOccurs="1" maxOccurs="1"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_AbsoluteAnchor">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="pos" type="a:CT_Point2D"/>
|
|
||||||
<xsd:element name="ext" type="a:CT_PositiveSize2D"/>
|
|
||||||
<xsd:group ref="EG_ObjectChoices"/>
|
|
||||||
<xsd:element name="clientData" type="CT_AnchorClientData" minOccurs="1" maxOccurs="1"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:group name="EG_Anchor">
|
|
||||||
<xsd:choice>
|
|
||||||
<xsd:element name="twoCellAnchor" type="CT_TwoCellAnchor"/>
|
|
||||||
<xsd:element name="oneCellAnchor" type="CT_OneCellAnchor"/>
|
|
||||||
<xsd:element name="absoluteAnchor" type="CT_AbsoluteAnchor"/>
|
|
||||||
</xsd:choice>
|
|
||||||
</xsd:group>
|
|
||||||
<xsd:complexType name="CT_Drawing">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:group ref="EG_Anchor" minOccurs="0" maxOccurs="unbounded"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:element name="wsDr" type="CT_Drawing"/>
|
|
||||||
</xsd:schema>
|
|
||||||
-287
@@ -1,287 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
|
||||||
xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
|
|
||||||
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
|
|
||||||
xmlns:dpct="http://schemas.openxmlformats.org/drawingml/2006/picture"
|
|
||||||
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
|
|
||||||
xmlns="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"
|
|
||||||
targetNamespace="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"
|
|
||||||
elementFormDefault="qualified">
|
|
||||||
<xsd:import namespace="http://schemas.openxmlformats.org/drawingml/2006/main"
|
|
||||||
schemaLocation="dml-main.xsd"/>
|
|
||||||
<xsd:import schemaLocation="wml.xsd"
|
|
||||||
namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main"/>
|
|
||||||
<xsd:import namespace="http://schemas.openxmlformats.org/drawingml/2006/picture"
|
|
||||||
schemaLocation="dml-picture.xsd"/>
|
|
||||||
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
|
|
||||||
schemaLocation="shared-relationshipReference.xsd"/>
|
|
||||||
<xsd:complexType name="CT_EffectExtent">
|
|
||||||
<xsd:attribute name="l" type="a:ST_Coordinate" use="required"/>
|
|
||||||
<xsd:attribute name="t" type="a:ST_Coordinate" use="required"/>
|
|
||||||
<xsd:attribute name="r" type="a:ST_Coordinate" use="required"/>
|
|
||||||
<xsd:attribute name="b" type="a:ST_Coordinate" use="required"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:simpleType name="ST_WrapDistance">
|
|
||||||
<xsd:restriction base="xsd:unsignedInt"/>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:complexType name="CT_Inline">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="extent" type="a:CT_PositiveSize2D"/>
|
|
||||||
<xsd:element name="effectExtent" type="CT_EffectExtent" minOccurs="0"/>
|
|
||||||
<xsd:element name="docPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
|
|
||||||
<xsd:element name="cNvGraphicFramePr" type="a:CT_NonVisualGraphicFrameProperties"
|
|
||||||
minOccurs="0" maxOccurs="1"/>
|
|
||||||
<xsd:element ref="a:graphic" minOccurs="1" maxOccurs="1"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="distT" type="ST_WrapDistance" use="optional"/>
|
|
||||||
<xsd:attribute name="distB" type="ST_WrapDistance" use="optional"/>
|
|
||||||
<xsd:attribute name="distL" type="ST_WrapDistance" use="optional"/>
|
|
||||||
<xsd:attribute name="distR" type="ST_WrapDistance" use="optional"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:simpleType name="ST_WrapText">
|
|
||||||
<xsd:restriction base="xsd:token">
|
|
||||||
<xsd:enumeration value="bothSides"/>
|
|
||||||
<xsd:enumeration value="left"/>
|
|
||||||
<xsd:enumeration value="right"/>
|
|
||||||
<xsd:enumeration value="largest"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:complexType name="CT_WrapPath">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="start" type="a:CT_Point2D" minOccurs="1" maxOccurs="1"/>
|
|
||||||
<xsd:element name="lineTo" type="a:CT_Point2D" minOccurs="2" maxOccurs="unbounded"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="edited" type="xsd:boolean" use="optional"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_WrapNone"/>
|
|
||||||
<xsd:complexType name="CT_WrapSquare">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="effectExtent" type="CT_EffectExtent" minOccurs="0"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="wrapText" type="ST_WrapText" use="required"/>
|
|
||||||
<xsd:attribute name="distT" type="ST_WrapDistance" use="optional"/>
|
|
||||||
<xsd:attribute name="distB" type="ST_WrapDistance" use="optional"/>
|
|
||||||
<xsd:attribute name="distL" type="ST_WrapDistance" use="optional"/>
|
|
||||||
<xsd:attribute name="distR" type="ST_WrapDistance" use="optional"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_WrapTight">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="wrapPolygon" type="CT_WrapPath" minOccurs="1" maxOccurs="1"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="wrapText" type="ST_WrapText" use="required"/>
|
|
||||||
<xsd:attribute name="distL" type="ST_WrapDistance" use="optional"/>
|
|
||||||
<xsd:attribute name="distR" type="ST_WrapDistance" use="optional"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_WrapThrough">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="wrapPolygon" type="CT_WrapPath" minOccurs="1" maxOccurs="1"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="wrapText" type="ST_WrapText" use="required"/>
|
|
||||||
<xsd:attribute name="distL" type="ST_WrapDistance" use="optional"/>
|
|
||||||
<xsd:attribute name="distR" type="ST_WrapDistance" use="optional"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_WrapTopBottom">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="effectExtent" type="CT_EffectExtent" minOccurs="0"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="distT" type="ST_WrapDistance" use="optional"/>
|
|
||||||
<xsd:attribute name="distB" type="ST_WrapDistance" use="optional"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:group name="EG_WrapType">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:choice minOccurs="1" maxOccurs="1">
|
|
||||||
<xsd:element name="wrapNone" type="CT_WrapNone" minOccurs="1" maxOccurs="1"/>
|
|
||||||
<xsd:element name="wrapSquare" type="CT_WrapSquare" minOccurs="1" maxOccurs="1"/>
|
|
||||||
<xsd:element name="wrapTight" type="CT_WrapTight" minOccurs="1" maxOccurs="1"/>
|
|
||||||
<xsd:element name="wrapThrough" type="CT_WrapThrough" minOccurs="1" maxOccurs="1"/>
|
|
||||||
<xsd:element name="wrapTopAndBottom" type="CT_WrapTopBottom" minOccurs="1" maxOccurs="1"/>
|
|
||||||
</xsd:choice>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:group>
|
|
||||||
<xsd:simpleType name="ST_PositionOffset">
|
|
||||||
<xsd:restriction base="xsd:int"/>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_AlignH">
|
|
||||||
<xsd:restriction base="xsd:token">
|
|
||||||
<xsd:enumeration value="left"/>
|
|
||||||
<xsd:enumeration value="right"/>
|
|
||||||
<xsd:enumeration value="center"/>
|
|
||||||
<xsd:enumeration value="inside"/>
|
|
||||||
<xsd:enumeration value="outside"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_RelFromH">
|
|
||||||
<xsd:restriction base="xsd:token">
|
|
||||||
<xsd:enumeration value="margin"/>
|
|
||||||
<xsd:enumeration value="page"/>
|
|
||||||
<xsd:enumeration value="column"/>
|
|
||||||
<xsd:enumeration value="character"/>
|
|
||||||
<xsd:enumeration value="leftMargin"/>
|
|
||||||
<xsd:enumeration value="rightMargin"/>
|
|
||||||
<xsd:enumeration value="insideMargin"/>
|
|
||||||
<xsd:enumeration value="outsideMargin"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:complexType name="CT_PosH">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:choice minOccurs="1" maxOccurs="1">
|
|
||||||
<xsd:element name="align" type="ST_AlignH" minOccurs="1" maxOccurs="1"/>
|
|
||||||
<xsd:element name="posOffset" type="ST_PositionOffset" minOccurs="1" maxOccurs="1"/>
|
|
||||||
</xsd:choice>
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="relativeFrom" type="ST_RelFromH" use="required"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:simpleType name="ST_AlignV">
|
|
||||||
<xsd:restriction base="xsd:token">
|
|
||||||
<xsd:enumeration value="top"/>
|
|
||||||
<xsd:enumeration value="bottom"/>
|
|
||||||
<xsd:enumeration value="center"/>
|
|
||||||
<xsd:enumeration value="inside"/>
|
|
||||||
<xsd:enumeration value="outside"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_RelFromV">
|
|
||||||
<xsd:restriction base="xsd:token">
|
|
||||||
<xsd:enumeration value="margin"/>
|
|
||||||
<xsd:enumeration value="page"/>
|
|
||||||
<xsd:enumeration value="paragraph"/>
|
|
||||||
<xsd:enumeration value="line"/>
|
|
||||||
<xsd:enumeration value="topMargin"/>
|
|
||||||
<xsd:enumeration value="bottomMargin"/>
|
|
||||||
<xsd:enumeration value="insideMargin"/>
|
|
||||||
<xsd:enumeration value="outsideMargin"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:complexType name="CT_PosV">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:choice minOccurs="1" maxOccurs="1">
|
|
||||||
<xsd:element name="align" type="ST_AlignV" minOccurs="1" maxOccurs="1"/>
|
|
||||||
<xsd:element name="posOffset" type="ST_PositionOffset" minOccurs="1" maxOccurs="1"/>
|
|
||||||
</xsd:choice>
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="relativeFrom" type="ST_RelFromV" use="required"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_Anchor">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="simplePos" type="a:CT_Point2D"/>
|
|
||||||
<xsd:element name="positionH" type="CT_PosH"/>
|
|
||||||
<xsd:element name="positionV" type="CT_PosV"/>
|
|
||||||
<xsd:element name="extent" type="a:CT_PositiveSize2D"/>
|
|
||||||
<xsd:element name="effectExtent" type="CT_EffectExtent" minOccurs="0"/>
|
|
||||||
<xsd:group ref="EG_WrapType"/>
|
|
||||||
<xsd:element name="docPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
|
|
||||||
<xsd:element name="cNvGraphicFramePr" type="a:CT_NonVisualGraphicFrameProperties"
|
|
||||||
minOccurs="0" maxOccurs="1"/>
|
|
||||||
<xsd:element ref="a:graphic" minOccurs="1" maxOccurs="1"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="distT" type="ST_WrapDistance" use="optional"/>
|
|
||||||
<xsd:attribute name="distB" type="ST_WrapDistance" use="optional"/>
|
|
||||||
<xsd:attribute name="distL" type="ST_WrapDistance" use="optional"/>
|
|
||||||
<xsd:attribute name="distR" type="ST_WrapDistance" use="optional"/>
|
|
||||||
<xsd:attribute name="simplePos" type="xsd:boolean"/>
|
|
||||||
<xsd:attribute name="relativeHeight" type="xsd:unsignedInt" use="required"/>
|
|
||||||
<xsd:attribute name="behindDoc" type="xsd:boolean" use="required"/>
|
|
||||||
<xsd:attribute name="locked" type="xsd:boolean" use="required"/>
|
|
||||||
<xsd:attribute name="layoutInCell" type="xsd:boolean" use="required"/>
|
|
||||||
<xsd:attribute name="hidden" type="xsd:boolean" use="optional"/>
|
|
||||||
<xsd:attribute name="allowOverlap" type="xsd:boolean" use="required"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_TxbxContent">
|
|
||||||
<xsd:group ref="w:EG_BlockLevelElts" minOccurs="1" maxOccurs="unbounded"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_TextboxInfo">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="txbxContent" type="CT_TxbxContent" minOccurs="1" maxOccurs="1"/>
|
|
||||||
<xsd:element name="extLst" type="a:CT_OfficeArtExtensionList" minOccurs="0" maxOccurs="1"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="id" type="xsd:unsignedShort" use="optional" default="0"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_LinkedTextboxInformation">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="extLst" type="a:CT_OfficeArtExtensionList" minOccurs="0" maxOccurs="1"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="id" type="xsd:unsignedShort" use="required"/>
|
|
||||||
<xsd:attribute name="seq" type="xsd:unsignedShort" use="required"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_WordprocessingShape">
|
|
||||||
<xsd:sequence minOccurs="1" maxOccurs="1">
|
|
||||||
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="0" maxOccurs="1"/>
|
|
||||||
<xsd:choice minOccurs="1" maxOccurs="1">
|
|
||||||
<xsd:element name="cNvSpPr" type="a:CT_NonVisualDrawingShapeProps" minOccurs="1"
|
|
||||||
maxOccurs="1"/>
|
|
||||||
<xsd:element name="cNvCnPr" type="a:CT_NonVisualConnectorProperties" minOccurs="1"
|
|
||||||
maxOccurs="1"/>
|
|
||||||
</xsd:choice>
|
|
||||||
<xsd:element name="spPr" type="a:CT_ShapeProperties" minOccurs="1" maxOccurs="1"/>
|
|
||||||
<xsd:element name="style" type="a:CT_ShapeStyle" minOccurs="0" maxOccurs="1"/>
|
|
||||||
<xsd:element name="extLst" type="a:CT_OfficeArtExtensionList" minOccurs="0" maxOccurs="1"/>
|
|
||||||
<xsd:choice minOccurs="0" maxOccurs="1">
|
|
||||||
<xsd:element name="txbx" type="CT_TextboxInfo" minOccurs="1" maxOccurs="1"/>
|
|
||||||
<xsd:element name="linkedTxbx" type="CT_LinkedTextboxInformation" minOccurs="1"
|
|
||||||
maxOccurs="1"/>
|
|
||||||
</xsd:choice>
|
|
||||||
<xsd:element name="bodyPr" type="a:CT_TextBodyProperties" minOccurs="1" maxOccurs="1"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="normalEastAsianFlow" type="xsd:boolean" use="optional" default="false"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_GraphicFrame">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
|
|
||||||
<xsd:element name="cNvFrPr" type="a:CT_NonVisualGraphicFrameProperties" minOccurs="1"
|
|
||||||
maxOccurs="1"/>
|
|
||||||
<xsd:element name="xfrm" type="a:CT_Transform2D" minOccurs="1" maxOccurs="1"/>
|
|
||||||
<xsd:element ref="a:graphic" minOccurs="1" maxOccurs="1"/>
|
|
||||||
<xsd:element name="extLst" type="a:CT_OfficeArtExtensionList" minOccurs="0" maxOccurs="1"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_WordprocessingContentPartNonVisual">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="0" maxOccurs="1"/>
|
|
||||||
<xsd:element name="cNvContentPartPr" type="a:CT_NonVisualContentPartProperties" minOccurs="0" maxOccurs="1"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_WordprocessingContentPart">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="nvContentPartPr" type="CT_WordprocessingContentPartNonVisual" minOccurs="0" maxOccurs="1"/>
|
|
||||||
<xsd:element name="xfrm" type="a:CT_Transform2D" minOccurs="0" maxOccurs="1"/>
|
|
||||||
<xsd:element name="extLst" type="a:CT_OfficeArtExtensionList" minOccurs="0" maxOccurs="1"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="bwMode" type="a:ST_BlackWhiteMode" use="optional"/>
|
|
||||||
<xsd:attribute ref="r:id" use="required"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_WordprocessingGroup">
|
|
||||||
<xsd:sequence minOccurs="1" maxOccurs="1">
|
|
||||||
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="0" maxOccurs="1"/>
|
|
||||||
<xsd:element name="cNvGrpSpPr" type="a:CT_NonVisualGroupDrawingShapeProps" minOccurs="1"
|
|
||||||
maxOccurs="1"/>
|
|
||||||
<xsd:element name="grpSpPr" type="a:CT_GroupShapeProperties" minOccurs="1" maxOccurs="1"/>
|
|
||||||
<xsd:choice minOccurs="0" maxOccurs="unbounded">
|
|
||||||
<xsd:element ref="wsp"/>
|
|
||||||
<xsd:element name="grpSp" type="CT_WordprocessingGroup"/>
|
|
||||||
<xsd:element name="graphicFrame" type="CT_GraphicFrame"/>
|
|
||||||
<xsd:element ref="dpct:pic"/>
|
|
||||||
<xsd:element name="contentPart" type="CT_WordprocessingContentPart"/>
|
|
||||||
</xsd:choice>
|
|
||||||
<xsd:element name="extLst" type="a:CT_OfficeArtExtensionList" minOccurs="0" maxOccurs="1"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_WordprocessingCanvas">
|
|
||||||
<xsd:sequence minOccurs="1" maxOccurs="1">
|
|
||||||
<xsd:element name="bg" type="a:CT_BackgroundFormatting" minOccurs="0" maxOccurs="1"/>
|
|
||||||
<xsd:element name="whole" type="a:CT_WholeE2oFormatting" minOccurs="0" maxOccurs="1"/>
|
|
||||||
<xsd:choice minOccurs="0" maxOccurs="unbounded">
|
|
||||||
<xsd:element ref="wsp"/>
|
|
||||||
<xsd:element ref="dpct:pic"/>
|
|
||||||
<xsd:element name="contentPart" type="CT_WordprocessingContentPart"/>
|
|
||||||
<xsd:element ref="wgp"/>
|
|
||||||
<xsd:element name="graphicFrame" type="CT_GraphicFrame"/>
|
|
||||||
</xsd:choice>
|
|
||||||
<xsd:element name="extLst" type="a:CT_OfficeArtExtensionList" minOccurs="0" maxOccurs="1"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:element name="wpc" type="CT_WordprocessingCanvas"/>
|
|
||||||
<xsd:element name="wgp" type="CT_WordprocessingGroup"/>
|
|
||||||
<xsd:element name="wsp" type="CT_WordprocessingShape"/>
|
|
||||||
<xsd:element name="inline" type="CT_Inline"/>
|
|
||||||
<xsd:element name="anchor" type="CT_Anchor"/>
|
|
||||||
</xsd:schema>
|
|
||||||
-1676
File diff suppressed because it is too large
Load Diff
-28
@@ -1,28 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
|
||||||
xmlns="http://schemas.openxmlformats.org/officeDocument/2006/characteristics"
|
|
||||||
targetNamespace="http://schemas.openxmlformats.org/officeDocument/2006/characteristics"
|
|
||||||
elementFormDefault="qualified">
|
|
||||||
<xsd:complexType name="CT_AdditionalCharacteristics">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="characteristic" type="CT_Characteristic" minOccurs="0"
|
|
||||||
maxOccurs="unbounded"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_Characteristic">
|
|
||||||
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
|
||||||
<xsd:attribute name="relation" type="ST_Relation" use="required"/>
|
|
||||||
<xsd:attribute name="val" type="xsd:string" use="required"/>
|
|
||||||
<xsd:attribute name="vocabulary" type="xsd:anyURI" use="optional"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:simpleType name="ST_Relation">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="ge"/>
|
|
||||||
<xsd:enumeration value="le"/>
|
|
||||||
<xsd:enumeration value="gt"/>
|
|
||||||
<xsd:enumeration value="lt"/>
|
|
||||||
<xsd:enumeration value="eq"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:element name="additionalCharacteristics" type="CT_AdditionalCharacteristics"/>
|
|
||||||
</xsd:schema>
|
|
||||||
-144
@@ -1,144 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
|
||||||
xmlns="http://schemas.openxmlformats.org/officeDocument/2006/bibliography"
|
|
||||||
xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
|
||||||
targetNamespace="http://schemas.openxmlformats.org/officeDocument/2006/bibliography"
|
|
||||||
elementFormDefault="qualified">
|
|
||||||
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
|
||||||
schemaLocation="shared-commonSimpleTypes.xsd"/>
|
|
||||||
<xsd:simpleType name="ST_SourceType">
|
|
||||||
<xsd:restriction base="s:ST_String">
|
|
||||||
<xsd:enumeration value="ArticleInAPeriodical"/>
|
|
||||||
<xsd:enumeration value="Book"/>
|
|
||||||
<xsd:enumeration value="BookSection"/>
|
|
||||||
<xsd:enumeration value="JournalArticle"/>
|
|
||||||
<xsd:enumeration value="ConferenceProceedings"/>
|
|
||||||
<xsd:enumeration value="Report"/>
|
|
||||||
<xsd:enumeration value="SoundRecording"/>
|
|
||||||
<xsd:enumeration value="Performance"/>
|
|
||||||
<xsd:enumeration value="Art"/>
|
|
||||||
<xsd:enumeration value="DocumentFromInternetSite"/>
|
|
||||||
<xsd:enumeration value="InternetSite"/>
|
|
||||||
<xsd:enumeration value="Film"/>
|
|
||||||
<xsd:enumeration value="Interview"/>
|
|
||||||
<xsd:enumeration value="Patent"/>
|
|
||||||
<xsd:enumeration value="ElectronicSource"/>
|
|
||||||
<xsd:enumeration value="Case"/>
|
|
||||||
<xsd:enumeration value="Misc"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:complexType name="CT_NameListType">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="Person" type="CT_PersonType" minOccurs="1" maxOccurs="unbounded"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_PersonType">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="Last" type="s:ST_String" minOccurs="0" maxOccurs="unbounded"/>
|
|
||||||
<xsd:element name="First" type="s:ST_String" minOccurs="0" maxOccurs="unbounded"/>
|
|
||||||
<xsd:element name="Middle" type="s:ST_String" minOccurs="0" maxOccurs="unbounded"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_NameType">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="NameList" type="CT_NameListType" minOccurs="1" maxOccurs="1"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_NameOrCorporateType">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:choice minOccurs="0" maxOccurs="1">
|
|
||||||
<xsd:element name="NameList" type="CT_NameListType" minOccurs="1" maxOccurs="1"/>
|
|
||||||
<xsd:element name="Corporate" minOccurs="1" maxOccurs="1" type="s:ST_String"/>
|
|
||||||
</xsd:choice>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_AuthorType">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:choice minOccurs="0" maxOccurs="unbounded">
|
|
||||||
<xsd:element name="Artist" type="CT_NameType"/>
|
|
||||||
<xsd:element name="Author" type="CT_NameOrCorporateType"/>
|
|
||||||
<xsd:element name="BookAuthor" type="CT_NameType"/>
|
|
||||||
<xsd:element name="Compiler" type="CT_NameType"/>
|
|
||||||
<xsd:element name="Composer" type="CT_NameType"/>
|
|
||||||
<xsd:element name="Conductor" type="CT_NameType"/>
|
|
||||||
<xsd:element name="Counsel" type="CT_NameType"/>
|
|
||||||
<xsd:element name="Director" type="CT_NameType"/>
|
|
||||||
<xsd:element name="Editor" type="CT_NameType"/>
|
|
||||||
<xsd:element name="Interviewee" type="CT_NameType"/>
|
|
||||||
<xsd:element name="Interviewer" type="CT_NameType"/>
|
|
||||||
<xsd:element name="Inventor" type="CT_NameType"/>
|
|
||||||
<xsd:element name="Performer" type="CT_NameOrCorporateType"/>
|
|
||||||
<xsd:element name="ProducerName" type="CT_NameType"/>
|
|
||||||
<xsd:element name="Translator" type="CT_NameType"/>
|
|
||||||
<xsd:element name="Writer" type="CT_NameType"/>
|
|
||||||
</xsd:choice>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_SourceType">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:choice minOccurs="0" maxOccurs="unbounded">
|
|
||||||
<xsd:element name="AbbreviatedCaseNumber" type="s:ST_String"/>
|
|
||||||
<xsd:element name="AlbumTitle" type="s:ST_String"/>
|
|
||||||
<xsd:element name="Author" type="CT_AuthorType"/>
|
|
||||||
<xsd:element name="BookTitle" type="s:ST_String"/>
|
|
||||||
<xsd:element name="Broadcaster" type="s:ST_String"/>
|
|
||||||
<xsd:element name="BroadcastTitle" type="s:ST_String"/>
|
|
||||||
<xsd:element name="CaseNumber" type="s:ST_String"/>
|
|
||||||
<xsd:element name="ChapterNumber" type="s:ST_String"/>
|
|
||||||
<xsd:element name="City" type="s:ST_String"/>
|
|
||||||
<xsd:element name="Comments" type="s:ST_String"/>
|
|
||||||
<xsd:element name="ConferenceName" type="s:ST_String"/>
|
|
||||||
<xsd:element name="CountryRegion" type="s:ST_String"/>
|
|
||||||
<xsd:element name="Court" type="s:ST_String"/>
|
|
||||||
<xsd:element name="Day" type="s:ST_String"/>
|
|
||||||
<xsd:element name="DayAccessed" type="s:ST_String"/>
|
|
||||||
<xsd:element name="Department" type="s:ST_String"/>
|
|
||||||
<xsd:element name="Distributor" type="s:ST_String"/>
|
|
||||||
<xsd:element name="Edition" type="s:ST_String"/>
|
|
||||||
<xsd:element name="Guid" type="s:ST_String"/>
|
|
||||||
<xsd:element name="Institution" type="s:ST_String"/>
|
|
||||||
<xsd:element name="InternetSiteTitle" type="s:ST_String"/>
|
|
||||||
<xsd:element name="Issue" type="s:ST_String"/>
|
|
||||||
<xsd:element name="JournalName" type="s:ST_String"/>
|
|
||||||
<xsd:element name="LCID" type="s:ST_Lang"/>
|
|
||||||
<xsd:element name="Medium" type="s:ST_String"/>
|
|
||||||
<xsd:element name="Month" type="s:ST_String"/>
|
|
||||||
<xsd:element name="MonthAccessed" type="s:ST_String"/>
|
|
||||||
<xsd:element name="NumberVolumes" type="s:ST_String"/>
|
|
||||||
<xsd:element name="Pages" type="s:ST_String"/>
|
|
||||||
<xsd:element name="PatentNumber" type="s:ST_String"/>
|
|
||||||
<xsd:element name="PeriodicalTitle" type="s:ST_String"/>
|
|
||||||
<xsd:element name="ProductionCompany" type="s:ST_String"/>
|
|
||||||
<xsd:element name="PublicationTitle" type="s:ST_String"/>
|
|
||||||
<xsd:element name="Publisher" type="s:ST_String"/>
|
|
||||||
<xsd:element name="RecordingNumber" type="s:ST_String"/>
|
|
||||||
<xsd:element name="RefOrder" type="s:ST_String"/>
|
|
||||||
<xsd:element name="Reporter" type="s:ST_String"/>
|
|
||||||
<xsd:element name="SourceType" type="ST_SourceType"/>
|
|
||||||
<xsd:element name="ShortTitle" type="s:ST_String"/>
|
|
||||||
<xsd:element name="StandardNumber" type="s:ST_String"/>
|
|
||||||
<xsd:element name="StateProvince" type="s:ST_String"/>
|
|
||||||
<xsd:element name="Station" type="s:ST_String"/>
|
|
||||||
<xsd:element name="Tag" type="s:ST_String"/>
|
|
||||||
<xsd:element name="Theater" type="s:ST_String"/>
|
|
||||||
<xsd:element name="ThesisType" type="s:ST_String"/>
|
|
||||||
<xsd:element name="Title" type="s:ST_String"/>
|
|
||||||
<xsd:element name="Type" type="s:ST_String"/>
|
|
||||||
<xsd:element name="URL" type="s:ST_String"/>
|
|
||||||
<xsd:element name="Version" type="s:ST_String"/>
|
|
||||||
<xsd:element name="Volume" type="s:ST_String"/>
|
|
||||||
<xsd:element name="Year" type="s:ST_String"/>
|
|
||||||
<xsd:element name="YearAccessed" type="s:ST_String"/>
|
|
||||||
</xsd:choice>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:element name="Sources" type="CT_Sources"/>
|
|
||||||
<xsd:complexType name="CT_Sources">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="Source" type="CT_SourceType" minOccurs="0" maxOccurs="unbounded"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="SelectedStyle" type="s:ST_String"/>
|
|
||||||
<xsd:attribute name="StyleName" type="s:ST_String"/>
|
|
||||||
<xsd:attribute name="URI" type="s:ST_String"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
</xsd:schema>
|
|
||||||
-174
@@ -1,174 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
|
||||||
xmlns="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
|
||||||
targetNamespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
|
||||||
elementFormDefault="qualified">
|
|
||||||
<xsd:simpleType name="ST_Lang">
|
|
||||||
<xsd:restriction base="xsd:string"/>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_HexColorRGB">
|
|
||||||
<xsd:restriction base="xsd:hexBinary">
|
|
||||||
<xsd:length value="3" fixed="true"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_Panose">
|
|
||||||
<xsd:restriction base="xsd:hexBinary">
|
|
||||||
<xsd:length value="10"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_CalendarType">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="gregorian"/>
|
|
||||||
<xsd:enumeration value="gregorianUs"/>
|
|
||||||
<xsd:enumeration value="gregorianMeFrench"/>
|
|
||||||
<xsd:enumeration value="gregorianArabic"/>
|
|
||||||
<xsd:enumeration value="hijri"/>
|
|
||||||
<xsd:enumeration value="hebrew"/>
|
|
||||||
<xsd:enumeration value="taiwan"/>
|
|
||||||
<xsd:enumeration value="japan"/>
|
|
||||||
<xsd:enumeration value="thai"/>
|
|
||||||
<xsd:enumeration value="korea"/>
|
|
||||||
<xsd:enumeration value="saka"/>
|
|
||||||
<xsd:enumeration value="gregorianXlitEnglish"/>
|
|
||||||
<xsd:enumeration value="gregorianXlitFrench"/>
|
|
||||||
<xsd:enumeration value="none"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_AlgClass">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="hash"/>
|
|
||||||
<xsd:enumeration value="custom"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_CryptProv">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="rsaAES"/>
|
|
||||||
<xsd:enumeration value="rsaFull"/>
|
|
||||||
<xsd:enumeration value="custom"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_AlgType">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="typeAny"/>
|
|
||||||
<xsd:enumeration value="custom"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_ColorType">
|
|
||||||
<xsd:restriction base="xsd:string"/>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_Guid">
|
|
||||||
<xsd:restriction base="xsd:token">
|
|
||||||
<xsd:pattern value="\{[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}\}"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_OnOff">
|
|
||||||
<xsd:union memberTypes="xsd:boolean ST_OnOff1"/>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_OnOff1">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="on"/>
|
|
||||||
<xsd:enumeration value="off"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_String">
|
|
||||||
<xsd:restriction base="xsd:string"/>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_XmlName">
|
|
||||||
<xsd:restriction base="xsd:NCName">
|
|
||||||
<xsd:minLength value="1"/>
|
|
||||||
<xsd:maxLength value="255"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_TrueFalse">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="t"/>
|
|
||||||
<xsd:enumeration value="f"/>
|
|
||||||
<xsd:enumeration value="true"/>
|
|
||||||
<xsd:enumeration value="false"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_TrueFalseBlank">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="t"/>
|
|
||||||
<xsd:enumeration value="f"/>
|
|
||||||
<xsd:enumeration value="true"/>
|
|
||||||
<xsd:enumeration value="false"/>
|
|
||||||
<xsd:enumeration value=""/>
|
|
||||||
<xsd:enumeration value="True"/>
|
|
||||||
<xsd:enumeration value="False"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_UnsignedDecimalNumber">
|
|
||||||
<xsd:restriction base="xsd:decimal">
|
|
||||||
<xsd:minInclusive value="0"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_TwipsMeasure">
|
|
||||||
<xsd:union memberTypes="ST_UnsignedDecimalNumber ST_PositiveUniversalMeasure"/>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_VerticalAlignRun">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="baseline"/>
|
|
||||||
<xsd:enumeration value="superscript"/>
|
|
||||||
<xsd:enumeration value="subscript"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_Xstring">
|
|
||||||
<xsd:restriction base="xsd:string"/>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_XAlign">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="left"/>
|
|
||||||
<xsd:enumeration value="center"/>
|
|
||||||
<xsd:enumeration value="right"/>
|
|
||||||
<xsd:enumeration value="inside"/>
|
|
||||||
<xsd:enumeration value="outside"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_YAlign">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="inline"/>
|
|
||||||
<xsd:enumeration value="top"/>
|
|
||||||
<xsd:enumeration value="center"/>
|
|
||||||
<xsd:enumeration value="bottom"/>
|
|
||||||
<xsd:enumeration value="inside"/>
|
|
||||||
<xsd:enumeration value="outside"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_ConformanceClass">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="strict"/>
|
|
||||||
<xsd:enumeration value="transitional"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_UniversalMeasure">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:pattern value="-?[0-9]+(\.[0-9]+)?(mm|cm|in|pt|pc|pi)"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_PositiveUniversalMeasure">
|
|
||||||
<xsd:restriction base="ST_UniversalMeasure">
|
|
||||||
<xsd:pattern value="[0-9]+(\.[0-9]+)?(mm|cm|in|pt|pc|pi)"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_Percentage">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:pattern value="-?[0-9]+(\.[0-9]+)?%"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_FixedPercentage">
|
|
||||||
<xsd:restriction base="ST_Percentage">
|
|
||||||
<xsd:pattern value="-?((100)|([0-9][0-9]?))(\.[0-9][0-9]?)?%"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_PositivePercentage">
|
|
||||||
<xsd:restriction base="ST_Percentage">
|
|
||||||
<xsd:pattern value="[0-9]+(\.[0-9]+)?%"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_PositiveFixedPercentage">
|
|
||||||
<xsd:restriction base="ST_Percentage">
|
|
||||||
<xsd:pattern value="((100)|([0-9][0-9]?))(\.[0-9][0-9]?)?%"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
</xsd:schema>
|
|
||||||
-25
@@ -1,25 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
|
||||||
xmlns="http://schemas.openxmlformats.org/officeDocument/2006/customXml"
|
|
||||||
xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
|
||||||
targetNamespace="http://schemas.openxmlformats.org/officeDocument/2006/customXml"
|
|
||||||
elementFormDefault="qualified" attributeFormDefault="qualified" blockDefault="#all">
|
|
||||||
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
|
||||||
schemaLocation="shared-commonSimpleTypes.xsd"/>
|
|
||||||
<xsd:complexType name="CT_DatastoreSchemaRef">
|
|
||||||
<xsd:attribute name="uri" type="xsd:string" use="required"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_DatastoreSchemaRefs">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="schemaRef" type="CT_DatastoreSchemaRef" minOccurs="0" maxOccurs="unbounded"
|
|
||||||
/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_DatastoreItem">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="schemaRefs" type="CT_DatastoreSchemaRefs" minOccurs="0"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="itemID" type="s:ST_Guid" use="required"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:element name="datastoreItem" type="CT_DatastoreItem"/>
|
|
||||||
</xsd:schema>
|
|
||||||
-18
@@ -1,18 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
|
||||||
xmlns="http://schemas.openxmlformats.org/schemaLibrary/2006/main"
|
|
||||||
targetNamespace="http://schemas.openxmlformats.org/schemaLibrary/2006/main"
|
|
||||||
attributeFormDefault="qualified" elementFormDefault="qualified">
|
|
||||||
<xsd:complexType name="CT_Schema">
|
|
||||||
<xsd:attribute name="uri" type="xsd:string" default=""/>
|
|
||||||
<xsd:attribute name="manifestLocation" type="xsd:string"/>
|
|
||||||
<xsd:attribute name="schemaLocation" type="xsd:string"/>
|
|
||||||
<xsd:attribute name="schemaLanguage" type="xsd:token"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_SchemaLibrary">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="schema" type="CT_Schema" minOccurs="0" maxOccurs="unbounded"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:element name="schemaLibrary" type="CT_SchemaLibrary"/>
|
|
||||||
</xsd:schema>
|
|
||||||
-59
@@ -1,59 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
|
||||||
xmlns="http://schemas.openxmlformats.org/officeDocument/2006/custom-properties"
|
|
||||||
xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"
|
|
||||||
xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
|
||||||
targetNamespace="http://schemas.openxmlformats.org/officeDocument/2006/custom-properties"
|
|
||||||
blockDefault="#all" elementFormDefault="qualified">
|
|
||||||
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"
|
|
||||||
schemaLocation="shared-documentPropertiesVariantTypes.xsd"/>
|
|
||||||
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
|
||||||
schemaLocation="shared-commonSimpleTypes.xsd"/>
|
|
||||||
<xsd:element name="Properties" type="CT_Properties"/>
|
|
||||||
<xsd:complexType name="CT_Properties">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="property" minOccurs="0" maxOccurs="unbounded" type="CT_Property"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_Property">
|
|
||||||
<xsd:choice minOccurs="1" maxOccurs="1">
|
|
||||||
<xsd:element ref="vt:vector"/>
|
|
||||||
<xsd:element ref="vt:array"/>
|
|
||||||
<xsd:element ref="vt:blob"/>
|
|
||||||
<xsd:element ref="vt:oblob"/>
|
|
||||||
<xsd:element ref="vt:empty"/>
|
|
||||||
<xsd:element ref="vt:null"/>
|
|
||||||
<xsd:element ref="vt:i1"/>
|
|
||||||
<xsd:element ref="vt:i2"/>
|
|
||||||
<xsd:element ref="vt:i4"/>
|
|
||||||
<xsd:element ref="vt:i8"/>
|
|
||||||
<xsd:element ref="vt:int"/>
|
|
||||||
<xsd:element ref="vt:ui1"/>
|
|
||||||
<xsd:element ref="vt:ui2"/>
|
|
||||||
<xsd:element ref="vt:ui4"/>
|
|
||||||
<xsd:element ref="vt:ui8"/>
|
|
||||||
<xsd:element ref="vt:uint"/>
|
|
||||||
<xsd:element ref="vt:r4"/>
|
|
||||||
<xsd:element ref="vt:r8"/>
|
|
||||||
<xsd:element ref="vt:decimal"/>
|
|
||||||
<xsd:element ref="vt:lpstr"/>
|
|
||||||
<xsd:element ref="vt:lpwstr"/>
|
|
||||||
<xsd:element ref="vt:bstr"/>
|
|
||||||
<xsd:element ref="vt:date"/>
|
|
||||||
<xsd:element ref="vt:filetime"/>
|
|
||||||
<xsd:element ref="vt:bool"/>
|
|
||||||
<xsd:element ref="vt:cy"/>
|
|
||||||
<xsd:element ref="vt:error"/>
|
|
||||||
<xsd:element ref="vt:stream"/>
|
|
||||||
<xsd:element ref="vt:ostream"/>
|
|
||||||
<xsd:element ref="vt:storage"/>
|
|
||||||
<xsd:element ref="vt:ostorage"/>
|
|
||||||
<xsd:element ref="vt:vstream"/>
|
|
||||||
<xsd:element ref="vt:clsid"/>
|
|
||||||
</xsd:choice>
|
|
||||||
<xsd:attribute name="fmtid" use="required" type="s:ST_Guid"/>
|
|
||||||
<xsd:attribute name="pid" use="required" type="xsd:int"/>
|
|
||||||
<xsd:attribute name="name" use="optional" type="xsd:string"/>
|
|
||||||
<xsd:attribute name="linkTarget" use="optional" type="xsd:string"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
</xsd:schema>
|
|
||||||
-56
@@ -1,56 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
|
||||||
xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties"
|
|
||||||
xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"
|
|
||||||
targetNamespace="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties"
|
|
||||||
elementFormDefault="qualified" blockDefault="#all">
|
|
||||||
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"
|
|
||||||
schemaLocation="shared-documentPropertiesVariantTypes.xsd"/>
|
|
||||||
<xsd:element name="Properties" type="CT_Properties"/>
|
|
||||||
<xsd:complexType name="CT_Properties">
|
|
||||||
<xsd:all>
|
|
||||||
<xsd:element name="Template" minOccurs="0" maxOccurs="1" type="xsd:string"/>
|
|
||||||
<xsd:element name="Manager" minOccurs="0" maxOccurs="1" type="xsd:string"/>
|
|
||||||
<xsd:element name="Company" minOccurs="0" maxOccurs="1" type="xsd:string"/>
|
|
||||||
<xsd:element name="Pages" minOccurs="0" maxOccurs="1" type="xsd:int"/>
|
|
||||||
<xsd:element name="Words" minOccurs="0" maxOccurs="1" type="xsd:int"/>
|
|
||||||
<xsd:element name="Characters" minOccurs="0" maxOccurs="1" type="xsd:int"/>
|
|
||||||
<xsd:element name="PresentationFormat" minOccurs="0" maxOccurs="1" type="xsd:string"/>
|
|
||||||
<xsd:element name="Lines" minOccurs="0" maxOccurs="1" type="xsd:int"/>
|
|
||||||
<xsd:element name="Paragraphs" minOccurs="0" maxOccurs="1" type="xsd:int"/>
|
|
||||||
<xsd:element name="Slides" minOccurs="0" maxOccurs="1" type="xsd:int"/>
|
|
||||||
<xsd:element name="Notes" minOccurs="0" maxOccurs="1" type="xsd:int"/>
|
|
||||||
<xsd:element name="TotalTime" minOccurs="0" maxOccurs="1" type="xsd:int"/>
|
|
||||||
<xsd:element name="HiddenSlides" minOccurs="0" maxOccurs="1" type="xsd:int"/>
|
|
||||||
<xsd:element name="MMClips" minOccurs="0" maxOccurs="1" type="xsd:int"/>
|
|
||||||
<xsd:element name="ScaleCrop" minOccurs="0" maxOccurs="1" type="xsd:boolean"/>
|
|
||||||
<xsd:element name="HeadingPairs" minOccurs="0" maxOccurs="1" type="CT_VectorVariant"/>
|
|
||||||
<xsd:element name="TitlesOfParts" minOccurs="0" maxOccurs="1" type="CT_VectorLpstr"/>
|
|
||||||
<xsd:element name="LinksUpToDate" minOccurs="0" maxOccurs="1" type="xsd:boolean"/>
|
|
||||||
<xsd:element name="CharactersWithSpaces" minOccurs="0" maxOccurs="1" type="xsd:int"/>
|
|
||||||
<xsd:element name="SharedDoc" minOccurs="0" maxOccurs="1" type="xsd:boolean"/>
|
|
||||||
<xsd:element name="HyperlinkBase" minOccurs="0" maxOccurs="1" type="xsd:string"/>
|
|
||||||
<xsd:element name="HLinks" minOccurs="0" maxOccurs="1" type="CT_VectorVariant"/>
|
|
||||||
<xsd:element name="HyperlinksChanged" minOccurs="0" maxOccurs="1" type="xsd:boolean"/>
|
|
||||||
<xsd:element name="DigSig" minOccurs="0" maxOccurs="1" type="CT_DigSigBlob"/>
|
|
||||||
<xsd:element name="Application" minOccurs="0" maxOccurs="1" type="xsd:string"/>
|
|
||||||
<xsd:element name="AppVersion" minOccurs="0" maxOccurs="1" type="xsd:string"/>
|
|
||||||
<xsd:element name="DocSecurity" minOccurs="0" maxOccurs="1" type="xsd:int"/>
|
|
||||||
</xsd:all>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_VectorVariant">
|
|
||||||
<xsd:sequence minOccurs="1" maxOccurs="1">
|
|
||||||
<xsd:element ref="vt:vector"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_VectorLpstr">
|
|
||||||
<xsd:sequence minOccurs="1" maxOccurs="1">
|
|
||||||
<xsd:element ref="vt:vector"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_DigSigBlob">
|
|
||||||
<xsd:sequence minOccurs="1" maxOccurs="1">
|
|
||||||
<xsd:element ref="vt:blob"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
</xsd:schema>
|
|
||||||
-195
@@ -1,195 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
|
||||||
xmlns="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"
|
|
||||||
xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
|
||||||
targetNamespace="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"
|
|
||||||
blockDefault="#all" elementFormDefault="qualified">
|
|
||||||
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
|
||||||
schemaLocation="shared-commonSimpleTypes.xsd"/>
|
|
||||||
<xsd:simpleType name="ST_VectorBaseType">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="variant"/>
|
|
||||||
<xsd:enumeration value="i1"/>
|
|
||||||
<xsd:enumeration value="i2"/>
|
|
||||||
<xsd:enumeration value="i4"/>
|
|
||||||
<xsd:enumeration value="i8"/>
|
|
||||||
<xsd:enumeration value="ui1"/>
|
|
||||||
<xsd:enumeration value="ui2"/>
|
|
||||||
<xsd:enumeration value="ui4"/>
|
|
||||||
<xsd:enumeration value="ui8"/>
|
|
||||||
<xsd:enumeration value="r4"/>
|
|
||||||
<xsd:enumeration value="r8"/>
|
|
||||||
<xsd:enumeration value="lpstr"/>
|
|
||||||
<xsd:enumeration value="lpwstr"/>
|
|
||||||
<xsd:enumeration value="bstr"/>
|
|
||||||
<xsd:enumeration value="date"/>
|
|
||||||
<xsd:enumeration value="filetime"/>
|
|
||||||
<xsd:enumeration value="bool"/>
|
|
||||||
<xsd:enumeration value="cy"/>
|
|
||||||
<xsd:enumeration value="error"/>
|
|
||||||
<xsd:enumeration value="clsid"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_ArrayBaseType">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="variant"/>
|
|
||||||
<xsd:enumeration value="i1"/>
|
|
||||||
<xsd:enumeration value="i2"/>
|
|
||||||
<xsd:enumeration value="i4"/>
|
|
||||||
<xsd:enumeration value="int"/>
|
|
||||||
<xsd:enumeration value="ui1"/>
|
|
||||||
<xsd:enumeration value="ui2"/>
|
|
||||||
<xsd:enumeration value="ui4"/>
|
|
||||||
<xsd:enumeration value="uint"/>
|
|
||||||
<xsd:enumeration value="r4"/>
|
|
||||||
<xsd:enumeration value="r8"/>
|
|
||||||
<xsd:enumeration value="decimal"/>
|
|
||||||
<xsd:enumeration value="bstr"/>
|
|
||||||
<xsd:enumeration value="date"/>
|
|
||||||
<xsd:enumeration value="bool"/>
|
|
||||||
<xsd:enumeration value="cy"/>
|
|
||||||
<xsd:enumeration value="error"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_Cy">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:pattern value="\s*[0-9]*\.[0-9]{4}\s*"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_Error">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:pattern value="\s*0x[0-9A-Za-z]{8}\s*"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:complexType name="CT_Empty"/>
|
|
||||||
<xsd:complexType name="CT_Null"/>
|
|
||||||
<xsd:complexType name="CT_Vector">
|
|
||||||
<xsd:choice minOccurs="1" maxOccurs="unbounded">
|
|
||||||
<xsd:element ref="variant"/>
|
|
||||||
<xsd:element ref="i1"/>
|
|
||||||
<xsd:element ref="i2"/>
|
|
||||||
<xsd:element ref="i4"/>
|
|
||||||
<xsd:element ref="i8"/>
|
|
||||||
<xsd:element ref="ui1"/>
|
|
||||||
<xsd:element ref="ui2"/>
|
|
||||||
<xsd:element ref="ui4"/>
|
|
||||||
<xsd:element ref="ui8"/>
|
|
||||||
<xsd:element ref="r4"/>
|
|
||||||
<xsd:element ref="r8"/>
|
|
||||||
<xsd:element ref="lpstr"/>
|
|
||||||
<xsd:element ref="lpwstr"/>
|
|
||||||
<xsd:element ref="bstr"/>
|
|
||||||
<xsd:element ref="date"/>
|
|
||||||
<xsd:element ref="filetime"/>
|
|
||||||
<xsd:element ref="bool"/>
|
|
||||||
<xsd:element ref="cy"/>
|
|
||||||
<xsd:element ref="error"/>
|
|
||||||
<xsd:element ref="clsid"/>
|
|
||||||
</xsd:choice>
|
|
||||||
<xsd:attribute name="baseType" type="ST_VectorBaseType" use="required"/>
|
|
||||||
<xsd:attribute name="size" type="xsd:unsignedInt" use="required"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_Array">
|
|
||||||
<xsd:choice minOccurs="1" maxOccurs="unbounded">
|
|
||||||
<xsd:element ref="variant"/>
|
|
||||||
<xsd:element ref="i1"/>
|
|
||||||
<xsd:element ref="i2"/>
|
|
||||||
<xsd:element ref="i4"/>
|
|
||||||
<xsd:element ref="int"/>
|
|
||||||
<xsd:element ref="ui1"/>
|
|
||||||
<xsd:element ref="ui2"/>
|
|
||||||
<xsd:element ref="ui4"/>
|
|
||||||
<xsd:element ref="uint"/>
|
|
||||||
<xsd:element ref="r4"/>
|
|
||||||
<xsd:element ref="r8"/>
|
|
||||||
<xsd:element ref="decimal"/>
|
|
||||||
<xsd:element ref="bstr"/>
|
|
||||||
<xsd:element ref="date"/>
|
|
||||||
<xsd:element ref="bool"/>
|
|
||||||
<xsd:element ref="error"/>
|
|
||||||
<xsd:element ref="cy"/>
|
|
||||||
</xsd:choice>
|
|
||||||
<xsd:attribute name="lBounds" type="xsd:int" use="required"/>
|
|
||||||
<xsd:attribute name="uBounds" type="xsd:int" use="required"/>
|
|
||||||
<xsd:attribute name="baseType" type="ST_ArrayBaseType" use="required"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_Variant">
|
|
||||||
<xsd:choice minOccurs="1" maxOccurs="1">
|
|
||||||
<xsd:element ref="variant"/>
|
|
||||||
<xsd:element ref="vector"/>
|
|
||||||
<xsd:element ref="array"/>
|
|
||||||
<xsd:element ref="blob"/>
|
|
||||||
<xsd:element ref="oblob"/>
|
|
||||||
<xsd:element ref="empty"/>
|
|
||||||
<xsd:element ref="null"/>
|
|
||||||
<xsd:element ref="i1"/>
|
|
||||||
<xsd:element ref="i2"/>
|
|
||||||
<xsd:element ref="i4"/>
|
|
||||||
<xsd:element ref="i8"/>
|
|
||||||
<xsd:element ref="int"/>
|
|
||||||
<xsd:element ref="ui1"/>
|
|
||||||
<xsd:element ref="ui2"/>
|
|
||||||
<xsd:element ref="ui4"/>
|
|
||||||
<xsd:element ref="ui8"/>
|
|
||||||
<xsd:element ref="uint"/>
|
|
||||||
<xsd:element ref="r4"/>
|
|
||||||
<xsd:element ref="r8"/>
|
|
||||||
<xsd:element ref="decimal"/>
|
|
||||||
<xsd:element ref="lpstr"/>
|
|
||||||
<xsd:element ref="lpwstr"/>
|
|
||||||
<xsd:element ref="bstr"/>
|
|
||||||
<xsd:element ref="date"/>
|
|
||||||
<xsd:element ref="filetime"/>
|
|
||||||
<xsd:element ref="bool"/>
|
|
||||||
<xsd:element ref="cy"/>
|
|
||||||
<xsd:element ref="error"/>
|
|
||||||
<xsd:element ref="stream"/>
|
|
||||||
<xsd:element ref="ostream"/>
|
|
||||||
<xsd:element ref="storage"/>
|
|
||||||
<xsd:element ref="ostorage"/>
|
|
||||||
<xsd:element ref="vstream"/>
|
|
||||||
<xsd:element ref="clsid"/>
|
|
||||||
</xsd:choice>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_Vstream">
|
|
||||||
<xsd:simpleContent>
|
|
||||||
<xsd:extension base="xsd:base64Binary">
|
|
||||||
<xsd:attribute name="version" type="s:ST_Guid"/>
|
|
||||||
</xsd:extension>
|
|
||||||
</xsd:simpleContent>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:element name="variant" type="CT_Variant"/>
|
|
||||||
<xsd:element name="vector" type="CT_Vector"/>
|
|
||||||
<xsd:element name="array" type="CT_Array"/>
|
|
||||||
<xsd:element name="blob" type="xsd:base64Binary"/>
|
|
||||||
<xsd:element name="oblob" type="xsd:base64Binary"/>
|
|
||||||
<xsd:element name="empty" type="CT_Empty"/>
|
|
||||||
<xsd:element name="null" type="CT_Null"/>
|
|
||||||
<xsd:element name="i1" type="xsd:byte"/>
|
|
||||||
<xsd:element name="i2" type="xsd:short"/>
|
|
||||||
<xsd:element name="i4" type="xsd:int"/>
|
|
||||||
<xsd:element name="i8" type="xsd:long"/>
|
|
||||||
<xsd:element name="int" type="xsd:int"/>
|
|
||||||
<xsd:element name="ui1" type="xsd:unsignedByte"/>
|
|
||||||
<xsd:element name="ui2" type="xsd:unsignedShort"/>
|
|
||||||
<xsd:element name="ui4" type="xsd:unsignedInt"/>
|
|
||||||
<xsd:element name="ui8" type="xsd:unsignedLong"/>
|
|
||||||
<xsd:element name="uint" type="xsd:unsignedInt"/>
|
|
||||||
<xsd:element name="r4" type="xsd:float"/>
|
|
||||||
<xsd:element name="r8" type="xsd:double"/>
|
|
||||||
<xsd:element name="decimal" type="xsd:decimal"/>
|
|
||||||
<xsd:element name="lpstr" type="xsd:string"/>
|
|
||||||
<xsd:element name="lpwstr" type="xsd:string"/>
|
|
||||||
<xsd:element name="bstr" type="xsd:string"/>
|
|
||||||
<xsd:element name="date" type="xsd:dateTime"/>
|
|
||||||
<xsd:element name="filetime" type="xsd:dateTime"/>
|
|
||||||
<xsd:element name="bool" type="xsd:boolean"/>
|
|
||||||
<xsd:element name="cy" type="ST_Cy"/>
|
|
||||||
<xsd:element name="error" type="ST_Error"/>
|
|
||||||
<xsd:element name="stream" type="xsd:base64Binary"/>
|
|
||||||
<xsd:element name="ostream" type="xsd:base64Binary"/>
|
|
||||||
<xsd:element name="storage" type="xsd:base64Binary"/>
|
|
||||||
<xsd:element name="ostorage" type="xsd:base64Binary"/>
|
|
||||||
<xsd:element name="vstream" type="CT_Vstream"/>
|
|
||||||
<xsd:element name="clsid" type="s:ST_Guid"/>
|
|
||||||
</xsd:schema>
|
|
||||||
-582
@@ -1,582 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
|
||||||
xmlns="http://schemas.openxmlformats.org/officeDocument/2006/math"
|
|
||||||
xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math"
|
|
||||||
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
|
|
||||||
xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
|
||||||
elementFormDefault="qualified" attributeFormDefault="qualified" blockDefault="#all"
|
|
||||||
targetNamespace="http://schemas.openxmlformats.org/officeDocument/2006/math">
|
|
||||||
<xsd:import namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
|
|
||||||
schemaLocation="wml.xsd"/>
|
|
||||||
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
|
||||||
schemaLocation="shared-commonSimpleTypes.xsd"/>
|
|
||||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" schemaLocation="xml.xsd"/>
|
|
||||||
<xsd:simpleType name="ST_Integer255">
|
|
||||||
<xsd:restriction base="xsd:integer">
|
|
||||||
<xsd:minInclusive value="1"/>
|
|
||||||
<xsd:maxInclusive value="255"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:complexType name="CT_Integer255">
|
|
||||||
<xsd:attribute name="val" type="ST_Integer255" use="required"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:simpleType name="ST_Integer2">
|
|
||||||
<xsd:restriction base="xsd:integer">
|
|
||||||
<xsd:minInclusive value="-2"/>
|
|
||||||
<xsd:maxInclusive value="2"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:complexType name="CT_Integer2">
|
|
||||||
<xsd:attribute name="val" type="ST_Integer2" use="required"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:simpleType name="ST_SpacingRule">
|
|
||||||
<xsd:restriction base="xsd:integer">
|
|
||||||
<xsd:minInclusive value="0"/>
|
|
||||||
<xsd:maxInclusive value="4"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:complexType name="CT_SpacingRule">
|
|
||||||
<xsd:attribute name="val" type="ST_SpacingRule" use="required"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:simpleType name="ST_UnSignedInteger">
|
|
||||||
<xsd:restriction base="xsd:unsignedInt"/>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:complexType name="CT_UnSignedInteger">
|
|
||||||
<xsd:attribute name="val" type="ST_UnSignedInteger" use="required"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:simpleType name="ST_Char">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:maxLength value="1"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:complexType name="CT_Char">
|
|
||||||
<xsd:attribute name="val" type="ST_Char" use="required"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_OnOff">
|
|
||||||
<xsd:attribute name="val" type="s:ST_OnOff"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_String">
|
|
||||||
<xsd:attribute name="val" type="s:ST_String"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_XAlign">
|
|
||||||
<xsd:attribute name="val" type="s:ST_XAlign" use="required"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_YAlign">
|
|
||||||
<xsd:attribute name="val" type="s:ST_YAlign" use="required"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:simpleType name="ST_Shp">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="centered"/>
|
|
||||||
<xsd:enumeration value="match"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:complexType name="CT_Shp">
|
|
||||||
<xsd:attribute name="val" type="ST_Shp" use="required"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:simpleType name="ST_FType">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="bar"/>
|
|
||||||
<xsd:enumeration value="skw"/>
|
|
||||||
<xsd:enumeration value="lin"/>
|
|
||||||
<xsd:enumeration value="noBar"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:complexType name="CT_FType">
|
|
||||||
<xsd:attribute name="val" type="ST_FType" use="required"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:simpleType name="ST_LimLoc">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="undOvr"/>
|
|
||||||
<xsd:enumeration value="subSup"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:complexType name="CT_LimLoc">
|
|
||||||
<xsd:attribute name="val" type="ST_LimLoc" use="required"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:simpleType name="ST_TopBot">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="top"/>
|
|
||||||
<xsd:enumeration value="bot"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:complexType name="CT_TopBot">
|
|
||||||
<xsd:attribute name="val" type="ST_TopBot" use="required"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:simpleType name="ST_Script">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="roman"/>
|
|
||||||
<xsd:enumeration value="script"/>
|
|
||||||
<xsd:enumeration value="fraktur"/>
|
|
||||||
<xsd:enumeration value="double-struck"/>
|
|
||||||
<xsd:enumeration value="sans-serif"/>
|
|
||||||
<xsd:enumeration value="monospace"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:complexType name="CT_Script">
|
|
||||||
<xsd:attribute name="val" type="ST_Script"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:simpleType name="ST_Style">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="p"/>
|
|
||||||
<xsd:enumeration value="b"/>
|
|
||||||
<xsd:enumeration value="i"/>
|
|
||||||
<xsd:enumeration value="bi"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:complexType name="CT_Style">
|
|
||||||
<xsd:attribute name="val" type="ST_Style"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_ManualBreak">
|
|
||||||
<xsd:attribute name="alnAt" type="ST_Integer255"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:group name="EG_ScriptStyle">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="scr" minOccurs="0" type="CT_Script"/>
|
|
||||||
<xsd:element name="sty" minOccurs="0" type="CT_Style"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:group>
|
|
||||||
<xsd:complexType name="CT_RPR">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="lit" minOccurs="0" type="CT_OnOff"/>
|
|
||||||
<xsd:choice>
|
|
||||||
<xsd:element name="nor" minOccurs="0" type="CT_OnOff"/>
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:group ref="EG_ScriptStyle"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:choice>
|
|
||||||
<xsd:element name="brk" minOccurs="0" type="CT_ManualBreak"/>
|
|
||||||
<xsd:element name="aln" minOccurs="0" type="CT_OnOff"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_Text">
|
|
||||||
<xsd:simpleContent>
|
|
||||||
<xsd:extension base="s:ST_String">
|
|
||||||
<xsd:attribute ref="xml:space" use="optional"/>
|
|
||||||
</xsd:extension>
|
|
||||||
</xsd:simpleContent>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_R">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="rPr" type="CT_RPR" minOccurs="0"/>
|
|
||||||
<xsd:group ref="w:EG_RPr" minOccurs="0"/>
|
|
||||||
<xsd:choice minOccurs="0" maxOccurs="unbounded">
|
|
||||||
<xsd:group ref="w:EG_RunInnerContent"/>
|
|
||||||
<xsd:element name="t" type="CT_Text" minOccurs="0"/>
|
|
||||||
</xsd:choice>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_CtrlPr">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:group ref="w:EG_RPrMath" minOccurs="0"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_AccPr">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="chr" type="CT_Char" minOccurs="0"/>
|
|
||||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_Acc">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="accPr" type="CT_AccPr" minOccurs="0"/>
|
|
||||||
<xsd:element name="e" type="CT_OMathArg"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_BarPr">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="pos" type="CT_TopBot" minOccurs="0"/>
|
|
||||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_Bar">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="barPr" type="CT_BarPr" minOccurs="0"/>
|
|
||||||
<xsd:element name="e" type="CT_OMathArg"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_BoxPr">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="opEmu" type="CT_OnOff" minOccurs="0"/>
|
|
||||||
<xsd:element name="noBreak" type="CT_OnOff" minOccurs="0"/>
|
|
||||||
<xsd:element name="diff" type="CT_OnOff" minOccurs="0"/>
|
|
||||||
<xsd:element name="brk" type="CT_ManualBreak" minOccurs="0"/>
|
|
||||||
<xsd:element name="aln" type="CT_OnOff" minOccurs="0"/>
|
|
||||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_Box">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="boxPr" type="CT_BoxPr" minOccurs="0"/>
|
|
||||||
<xsd:element name="e" type="CT_OMathArg"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_BorderBoxPr">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="hideTop" type="CT_OnOff" minOccurs="0"/>
|
|
||||||
<xsd:element name="hideBot" type="CT_OnOff" minOccurs="0"/>
|
|
||||||
<xsd:element name="hideLeft" type="CT_OnOff" minOccurs="0"/>
|
|
||||||
<xsd:element name="hideRight" type="CT_OnOff" minOccurs="0"/>
|
|
||||||
<xsd:element name="strikeH" type="CT_OnOff" minOccurs="0"/>
|
|
||||||
<xsd:element name="strikeV" type="CT_OnOff" minOccurs="0"/>
|
|
||||||
<xsd:element name="strikeBLTR" type="CT_OnOff" minOccurs="0"/>
|
|
||||||
<xsd:element name="strikeTLBR" type="CT_OnOff" minOccurs="0"/>
|
|
||||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_BorderBox">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="borderBoxPr" type="CT_BorderBoxPr" minOccurs="0"/>
|
|
||||||
<xsd:element name="e" type="CT_OMathArg"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_DPr">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="begChr" type="CT_Char" minOccurs="0"/>
|
|
||||||
<xsd:element name="sepChr" type="CT_Char" minOccurs="0"/>
|
|
||||||
<xsd:element name="endChr" type="CT_Char" minOccurs="0"/>
|
|
||||||
<xsd:element name="grow" type="CT_OnOff" minOccurs="0"/>
|
|
||||||
<xsd:element name="shp" type="CT_Shp" minOccurs="0"/>
|
|
||||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_D">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="dPr" type="CT_DPr" minOccurs="0"/>
|
|
||||||
<xsd:element name="e" type="CT_OMathArg" maxOccurs="unbounded"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_EqArrPr">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="baseJc" type="CT_YAlign" minOccurs="0"/>
|
|
||||||
<xsd:element name="maxDist" type="CT_OnOff" minOccurs="0"/>
|
|
||||||
<xsd:element name="objDist" type="CT_OnOff" minOccurs="0"/>
|
|
||||||
<xsd:element name="rSpRule" type="CT_SpacingRule" minOccurs="0"/>
|
|
||||||
<xsd:element name="rSp" type="CT_UnSignedInteger" minOccurs="0"/>
|
|
||||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_EqArr">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="eqArrPr" type="CT_EqArrPr" minOccurs="0"/>
|
|
||||||
<xsd:element name="e" type="CT_OMathArg" maxOccurs="unbounded"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_FPr">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="type" type="CT_FType" minOccurs="0"/>
|
|
||||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_F">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="fPr" type="CT_FPr" minOccurs="0"/>
|
|
||||||
<xsd:element name="num" type="CT_OMathArg"/>
|
|
||||||
<xsd:element name="den" type="CT_OMathArg"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_FuncPr">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_Func">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="funcPr" type="CT_FuncPr" minOccurs="0"/>
|
|
||||||
<xsd:element name="fName" type="CT_OMathArg"/>
|
|
||||||
<xsd:element name="e" type="CT_OMathArg"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_GroupChrPr">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="chr" type="CT_Char" minOccurs="0"/>
|
|
||||||
<xsd:element name="pos" type="CT_TopBot" minOccurs="0"/>
|
|
||||||
<xsd:element name="vertJc" type="CT_TopBot" minOccurs="0"/>
|
|
||||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_GroupChr">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="groupChrPr" type="CT_GroupChrPr" minOccurs="0"/>
|
|
||||||
<xsd:element name="e" type="CT_OMathArg"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_LimLowPr">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_LimLow">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="limLowPr" type="CT_LimLowPr" minOccurs="0"/>
|
|
||||||
<xsd:element name="e" type="CT_OMathArg"/>
|
|
||||||
<xsd:element name="lim" type="CT_OMathArg"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_LimUppPr">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_LimUpp">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="limUppPr" type="CT_LimUppPr" minOccurs="0"/>
|
|
||||||
<xsd:element name="e" type="CT_OMathArg"/>
|
|
||||||
<xsd:element name="lim" type="CT_OMathArg"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_MCPr">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="count" type="CT_Integer255" minOccurs="0"/>
|
|
||||||
<xsd:element name="mcJc" type="CT_XAlign" minOccurs="0"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_MC">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="mcPr" type="CT_MCPr" minOccurs="0"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_MCS">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="mc" type="CT_MC" maxOccurs="unbounded"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_MPr">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="baseJc" type="CT_YAlign" minOccurs="0"/>
|
|
||||||
<xsd:element name="plcHide" type="CT_OnOff" minOccurs="0"/>
|
|
||||||
<xsd:element name="rSpRule" type="CT_SpacingRule" minOccurs="0"/>
|
|
||||||
<xsd:element name="cGpRule" type="CT_SpacingRule" minOccurs="0"/>
|
|
||||||
<xsd:element name="rSp" type="CT_UnSignedInteger" minOccurs="0"/>
|
|
||||||
<xsd:element name="cSp" type="CT_UnSignedInteger" minOccurs="0"/>
|
|
||||||
<xsd:element name="cGp" type="CT_UnSignedInteger" minOccurs="0"/>
|
|
||||||
<xsd:element name="mcs" type="CT_MCS" minOccurs="0"/>
|
|
||||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_MR">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="e" type="CT_OMathArg" maxOccurs="unbounded"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_M">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="mPr" type="CT_MPr" minOccurs="0"/>
|
|
||||||
<xsd:element name="mr" type="CT_MR" maxOccurs="unbounded"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_NaryPr">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="chr" type="CT_Char" minOccurs="0"/>
|
|
||||||
<xsd:element name="limLoc" type="CT_LimLoc" minOccurs="0"/>
|
|
||||||
<xsd:element name="grow" type="CT_OnOff" minOccurs="0"/>
|
|
||||||
<xsd:element name="subHide" type="CT_OnOff" minOccurs="0"/>
|
|
||||||
<xsd:element name="supHide" type="CT_OnOff" minOccurs="0"/>
|
|
||||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_Nary">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="naryPr" type="CT_NaryPr" minOccurs="0"/>
|
|
||||||
<xsd:element name="sub" type="CT_OMathArg"/>
|
|
||||||
<xsd:element name="sup" type="CT_OMathArg"/>
|
|
||||||
<xsd:element name="e" type="CT_OMathArg"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_PhantPr">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="show" type="CT_OnOff" minOccurs="0"/>
|
|
||||||
<xsd:element name="zeroWid" type="CT_OnOff" minOccurs="0"/>
|
|
||||||
<xsd:element name="zeroAsc" type="CT_OnOff" minOccurs="0"/>
|
|
||||||
<xsd:element name="zeroDesc" type="CT_OnOff" minOccurs="0"/>
|
|
||||||
<xsd:element name="transp" type="CT_OnOff" minOccurs="0"/>
|
|
||||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_Phant">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="phantPr" type="CT_PhantPr" minOccurs="0"/>
|
|
||||||
<xsd:element name="e" type="CT_OMathArg"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_RadPr">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="degHide" type="CT_OnOff" minOccurs="0"/>
|
|
||||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_Rad">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="radPr" type="CT_RadPr" minOccurs="0"/>
|
|
||||||
<xsd:element name="deg" type="CT_OMathArg"/>
|
|
||||||
<xsd:element name="e" type="CT_OMathArg"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_SPrePr">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_SPre">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="sPrePr" type="CT_SPrePr" minOccurs="0"/>
|
|
||||||
<xsd:element name="sub" type="CT_OMathArg"/>
|
|
||||||
<xsd:element name="sup" type="CT_OMathArg"/>
|
|
||||||
<xsd:element name="e" type="CT_OMathArg"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_SSubPr">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_SSub">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="sSubPr" type="CT_SSubPr" minOccurs="0"/>
|
|
||||||
<xsd:element name="e" type="CT_OMathArg"/>
|
|
||||||
<xsd:element name="sub" type="CT_OMathArg"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_SSubSupPr">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="alnScr" type="CT_OnOff" minOccurs="0"/>
|
|
||||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_SSubSup">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="sSubSupPr" type="CT_SSubSupPr" minOccurs="0"/>
|
|
||||||
<xsd:element name="e" type="CT_OMathArg"/>
|
|
||||||
<xsd:element name="sub" type="CT_OMathArg"/>
|
|
||||||
<xsd:element name="sup" type="CT_OMathArg"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_SSupPr">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_SSup">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="sSupPr" type="CT_SSupPr" minOccurs="0"/>
|
|
||||||
<xsd:element name="e" type="CT_OMathArg"/>
|
|
||||||
<xsd:element name="sup" type="CT_OMathArg"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:group name="EG_OMathMathElements">
|
|
||||||
<xsd:choice>
|
|
||||||
<xsd:element name="acc" type="CT_Acc"/>
|
|
||||||
<xsd:element name="bar" type="CT_Bar"/>
|
|
||||||
<xsd:element name="box" type="CT_Box"/>
|
|
||||||
<xsd:element name="borderBox" type="CT_BorderBox"/>
|
|
||||||
<xsd:element name="d" type="CT_D"/>
|
|
||||||
<xsd:element name="eqArr" type="CT_EqArr"/>
|
|
||||||
<xsd:element name="f" type="CT_F"/>
|
|
||||||
<xsd:element name="func" type="CT_Func"/>
|
|
||||||
<xsd:element name="groupChr" type="CT_GroupChr"/>
|
|
||||||
<xsd:element name="limLow" type="CT_LimLow"/>
|
|
||||||
<xsd:element name="limUpp" type="CT_LimUpp"/>
|
|
||||||
<xsd:element name="m" type="CT_M"/>
|
|
||||||
<xsd:element name="nary" type="CT_Nary"/>
|
|
||||||
<xsd:element name="phant" type="CT_Phant"/>
|
|
||||||
<xsd:element name="rad" type="CT_Rad"/>
|
|
||||||
<xsd:element name="sPre" type="CT_SPre"/>
|
|
||||||
<xsd:element name="sSub" type="CT_SSub"/>
|
|
||||||
<xsd:element name="sSubSup" type="CT_SSubSup"/>
|
|
||||||
<xsd:element name="sSup" type="CT_SSup"/>
|
|
||||||
<xsd:element name="r" type="CT_R"/>
|
|
||||||
</xsd:choice>
|
|
||||||
</xsd:group>
|
|
||||||
<xsd:group name="EG_OMathElements">
|
|
||||||
<xsd:choice>
|
|
||||||
<xsd:group ref="EG_OMathMathElements"/>
|
|
||||||
<xsd:group ref="w:EG_PContentMath"/>
|
|
||||||
</xsd:choice>
|
|
||||||
</xsd:group>
|
|
||||||
<xsd:complexType name="CT_OMathArgPr">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="argSz" type="CT_Integer2" minOccurs="0"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_OMathArg">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="argPr" type="CT_OMathArgPr" minOccurs="0"/>
|
|
||||||
<xsd:group ref="EG_OMathElements" minOccurs="0" maxOccurs="unbounded"/>
|
|
||||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:simpleType name="ST_Jc">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="left"/>
|
|
||||||
<xsd:enumeration value="right"/>
|
|
||||||
<xsd:enumeration value="center"/>
|
|
||||||
<xsd:enumeration value="centerGroup"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:complexType name="CT_OMathJc">
|
|
||||||
<xsd:attribute name="val" type="ST_Jc"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_OMathParaPr">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="jc" type="CT_OMathJc" minOccurs="0"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_TwipsMeasure">
|
|
||||||
<xsd:attribute name="val" type="s:ST_TwipsMeasure" use="required"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:simpleType name="ST_BreakBin">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="before"/>
|
|
||||||
<xsd:enumeration value="after"/>
|
|
||||||
<xsd:enumeration value="repeat"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:complexType name="CT_BreakBin">
|
|
||||||
<xsd:attribute name="val" type="ST_BreakBin"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:simpleType name="ST_BreakBinSub">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="--"/>
|
|
||||||
<xsd:enumeration value="-+"/>
|
|
||||||
<xsd:enumeration value="+-"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:complexType name="CT_BreakBinSub">
|
|
||||||
<xsd:attribute name="val" type="ST_BreakBinSub"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_MathPr">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="mathFont" type="CT_String" minOccurs="0"/>
|
|
||||||
<xsd:element name="brkBin" type="CT_BreakBin" minOccurs="0"/>
|
|
||||||
<xsd:element name="brkBinSub" type="CT_BreakBinSub" minOccurs="0"/>
|
|
||||||
<xsd:element name="smallFrac" type="CT_OnOff" minOccurs="0"/>
|
|
||||||
<xsd:element name="dispDef" type="CT_OnOff" minOccurs="0"/>
|
|
||||||
<xsd:element name="lMargin" type="CT_TwipsMeasure" minOccurs="0"/>
|
|
||||||
<xsd:element name="rMargin" type="CT_TwipsMeasure" minOccurs="0"/>
|
|
||||||
<xsd:element name="defJc" type="CT_OMathJc" minOccurs="0"/>
|
|
||||||
<xsd:element name="preSp" type="CT_TwipsMeasure" minOccurs="0"/>
|
|
||||||
<xsd:element name="postSp" type="CT_TwipsMeasure" minOccurs="0"/>
|
|
||||||
<xsd:element name="interSp" type="CT_TwipsMeasure" minOccurs="0"/>
|
|
||||||
<xsd:element name="intraSp" type="CT_TwipsMeasure" minOccurs="0"/>
|
|
||||||
<xsd:choice minOccurs="0">
|
|
||||||
<xsd:element name="wrapIndent" type="CT_TwipsMeasure"/>
|
|
||||||
<xsd:element name="wrapRight" type="CT_OnOff"/>
|
|
||||||
</xsd:choice>
|
|
||||||
<xsd:element name="intLim" type="CT_LimLoc" minOccurs="0"/>
|
|
||||||
<xsd:element name="naryLim" type="CT_LimLoc" minOccurs="0"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:element name="mathPr" type="CT_MathPr"/>
|
|
||||||
<xsd:complexType name="CT_OMathPara">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="oMathParaPr" type="CT_OMathParaPr" minOccurs="0"/>
|
|
||||||
<xsd:element name="oMath" type="CT_OMath" maxOccurs="unbounded"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_OMath">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:group ref="EG_OMathElements" minOccurs="0" maxOccurs="unbounded"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:element name="oMathPara" type="CT_OMathPara"/>
|
|
||||||
<xsd:element name="oMath" type="CT_OMath"/>
|
|
||||||
</xsd:schema>
|
|
||||||
-25
@@ -1,25 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
|
||||||
xmlns="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
|
|
||||||
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
|
|
||||||
elementFormDefault="qualified"
|
|
||||||
targetNamespace="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
|
|
||||||
blockDefault="#all">
|
|
||||||
<xsd:simpleType name="ST_RelationshipId">
|
|
||||||
<xsd:restriction base="xsd:string"/>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:attribute name="id" type="ST_RelationshipId"/>
|
|
||||||
<xsd:attribute name="embed" type="ST_RelationshipId"/>
|
|
||||||
<xsd:attribute name="link" type="ST_RelationshipId"/>
|
|
||||||
<xsd:attribute name="dm" type="ST_RelationshipId" default=""/>
|
|
||||||
<xsd:attribute name="lo" type="ST_RelationshipId" default=""/>
|
|
||||||
<xsd:attribute name="qs" type="ST_RelationshipId" default=""/>
|
|
||||||
<xsd:attribute name="cs" type="ST_RelationshipId" default=""/>
|
|
||||||
<xsd:attribute name="blip" type="ST_RelationshipId" default=""/>
|
|
||||||
<xsd:attribute name="pict" type="ST_RelationshipId"/>
|
|
||||||
<xsd:attribute name="href" type="ST_RelationshipId"/>
|
|
||||||
<xsd:attribute name="topLeft" type="ST_RelationshipId"/>
|
|
||||||
<xsd:attribute name="topRight" type="ST_RelationshipId"/>
|
|
||||||
<xsd:attribute name="bottomLeft" type="ST_RelationshipId"/>
|
|
||||||
<xsd:attribute name="bottomRight" type="ST_RelationshipId"/>
|
|
||||||
</xsd:schema>
|
|
||||||
-4439
File diff suppressed because it is too large
Load Diff
-570
@@ -1,570 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="urn:schemas-microsoft-com:vml"
|
|
||||||
xmlns:pvml="urn:schemas-microsoft-com:office:powerpoint"
|
|
||||||
xmlns:o="urn:schemas-microsoft-com:office:office"
|
|
||||||
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
|
|
||||||
xmlns:w10="urn:schemas-microsoft-com:office:word"
|
|
||||||
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
|
|
||||||
xmlns:x="urn:schemas-microsoft-com:office:excel"
|
|
||||||
xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
|
||||||
targetNamespace="urn:schemas-microsoft-com:vml" elementFormDefault="qualified"
|
|
||||||
attributeFormDefault="unqualified">
|
|
||||||
<xsd:import namespace="urn:schemas-microsoft-com:office:office"
|
|
||||||
schemaLocation="vml-officeDrawing.xsd"/>
|
|
||||||
<xsd:import namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
|
|
||||||
schemaLocation="wml.xsd"/>
|
|
||||||
<xsd:import namespace="urn:schemas-microsoft-com:office:word"
|
|
||||||
schemaLocation="vml-wordprocessingDrawing.xsd"/>
|
|
||||||
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
|
|
||||||
schemaLocation="shared-relationshipReference.xsd"/>
|
|
||||||
<xsd:import namespace="urn:schemas-microsoft-com:office:excel"
|
|
||||||
schemaLocation="vml-spreadsheetDrawing.xsd"/>
|
|
||||||
<xsd:import namespace="urn:schemas-microsoft-com:office:powerpoint"
|
|
||||||
schemaLocation="vml-presentationDrawing.xsd"/>
|
|
||||||
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
|
||||||
schemaLocation="shared-commonSimpleTypes.xsd"/>
|
|
||||||
<xsd:attributeGroup name="AG_Id">
|
|
||||||
<xsd:attribute name="id" type="xsd:string" use="optional"/>
|
|
||||||
</xsd:attributeGroup>
|
|
||||||
<xsd:attributeGroup name="AG_Style">
|
|
||||||
<xsd:attribute name="style" type="xsd:string" use="optional"/>
|
|
||||||
</xsd:attributeGroup>
|
|
||||||
<xsd:attributeGroup name="AG_Type">
|
|
||||||
<xsd:attribute name="type" type="xsd:string" use="optional"/>
|
|
||||||
</xsd:attributeGroup>
|
|
||||||
<xsd:attributeGroup name="AG_Adj">
|
|
||||||
<xsd:attribute name="adj" type="xsd:string" use="optional"/>
|
|
||||||
</xsd:attributeGroup>
|
|
||||||
<xsd:attributeGroup name="AG_Path">
|
|
||||||
<xsd:attribute name="path" type="xsd:string" use="optional"/>
|
|
||||||
</xsd:attributeGroup>
|
|
||||||
<xsd:attributeGroup name="AG_Fill">
|
|
||||||
<xsd:attribute name="filled" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute name="fillcolor" type="s:ST_ColorType" use="optional"/>
|
|
||||||
</xsd:attributeGroup>
|
|
||||||
<xsd:attributeGroup name="AG_Chromakey">
|
|
||||||
<xsd:attribute name="chromakey" type="s:ST_ColorType" use="optional"/>
|
|
||||||
</xsd:attributeGroup>
|
|
||||||
<xsd:attributeGroup name="AG_Ext">
|
|
||||||
<xsd:attribute name="ext" form="qualified" type="ST_Ext"/>
|
|
||||||
</xsd:attributeGroup>
|
|
||||||
<xsd:attributeGroup name="AG_CoreAttributes">
|
|
||||||
<xsd:attributeGroup ref="AG_Id"/>
|
|
||||||
<xsd:attributeGroup ref="AG_Style"/>
|
|
||||||
<xsd:attribute name="href" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="target" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="class" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="title" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="alt" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="coordsize" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="coordorigin" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="wrapcoords" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="print" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
</xsd:attributeGroup>
|
|
||||||
<xsd:attributeGroup name="AG_ShapeAttributes">
|
|
||||||
<xsd:attributeGroup ref="AG_Chromakey"/>
|
|
||||||
<xsd:attributeGroup ref="AG_Fill"/>
|
|
||||||
<xsd:attribute name="opacity" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="stroked" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute name="strokecolor" type="s:ST_ColorType" use="optional"/>
|
|
||||||
<xsd:attribute name="strokeweight" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="insetpen" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
</xsd:attributeGroup>
|
|
||||||
<xsd:attributeGroup name="AG_OfficeCoreAttributes">
|
|
||||||
<xsd:attribute ref="o:spid"/>
|
|
||||||
<xsd:attribute ref="o:oned"/>
|
|
||||||
<xsd:attribute ref="o:regroupid"/>
|
|
||||||
<xsd:attribute ref="o:doubleclicknotify"/>
|
|
||||||
<xsd:attribute ref="o:button"/>
|
|
||||||
<xsd:attribute ref="o:userhidden"/>
|
|
||||||
<xsd:attribute ref="o:bullet"/>
|
|
||||||
<xsd:attribute ref="o:hr"/>
|
|
||||||
<xsd:attribute ref="o:hrstd"/>
|
|
||||||
<xsd:attribute ref="o:hrnoshade"/>
|
|
||||||
<xsd:attribute ref="o:hrpct"/>
|
|
||||||
<xsd:attribute ref="o:hralign"/>
|
|
||||||
<xsd:attribute ref="o:allowincell"/>
|
|
||||||
<xsd:attribute ref="o:allowoverlap"/>
|
|
||||||
<xsd:attribute ref="o:userdrawn"/>
|
|
||||||
<xsd:attribute ref="o:bordertopcolor"/>
|
|
||||||
<xsd:attribute ref="o:borderleftcolor"/>
|
|
||||||
<xsd:attribute ref="o:borderbottomcolor"/>
|
|
||||||
<xsd:attribute ref="o:borderrightcolor"/>
|
|
||||||
<xsd:attribute ref="o:dgmlayout"/>
|
|
||||||
<xsd:attribute ref="o:dgmnodekind"/>
|
|
||||||
<xsd:attribute ref="o:dgmlayoutmru"/>
|
|
||||||
<xsd:attribute ref="o:insetmode"/>
|
|
||||||
</xsd:attributeGroup>
|
|
||||||
<xsd:attributeGroup name="AG_OfficeShapeAttributes">
|
|
||||||
<xsd:attribute ref="o:spt"/>
|
|
||||||
<xsd:attribute ref="o:connectortype"/>
|
|
||||||
<xsd:attribute ref="o:bwmode"/>
|
|
||||||
<xsd:attribute ref="o:bwpure"/>
|
|
||||||
<xsd:attribute ref="o:bwnormal"/>
|
|
||||||
<xsd:attribute ref="o:forcedash"/>
|
|
||||||
<xsd:attribute ref="o:oleicon"/>
|
|
||||||
<xsd:attribute ref="o:ole"/>
|
|
||||||
<xsd:attribute ref="o:preferrelative"/>
|
|
||||||
<xsd:attribute ref="o:cliptowrap"/>
|
|
||||||
<xsd:attribute ref="o:clip"/>
|
|
||||||
</xsd:attributeGroup>
|
|
||||||
<xsd:attributeGroup name="AG_AllCoreAttributes">
|
|
||||||
<xsd:attributeGroup ref="AG_CoreAttributes"/>
|
|
||||||
<xsd:attributeGroup ref="AG_OfficeCoreAttributes"/>
|
|
||||||
</xsd:attributeGroup>
|
|
||||||
<xsd:attributeGroup name="AG_AllShapeAttributes">
|
|
||||||
<xsd:attributeGroup ref="AG_ShapeAttributes"/>
|
|
||||||
<xsd:attributeGroup ref="AG_OfficeShapeAttributes"/>
|
|
||||||
</xsd:attributeGroup>
|
|
||||||
<xsd:attributeGroup name="AG_ImageAttributes">
|
|
||||||
<xsd:attribute name="src" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="cropleft" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="croptop" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="cropright" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="cropbottom" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="gain" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="blacklevel" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="gamma" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="grayscale" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute name="bilevel" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
</xsd:attributeGroup>
|
|
||||||
<xsd:attributeGroup name="AG_StrokeAttributes">
|
|
||||||
<xsd:attribute name="on" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute name="weight" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="color" type="s:ST_ColorType" use="optional"/>
|
|
||||||
<xsd:attribute name="opacity" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="linestyle" type="ST_StrokeLineStyle" use="optional"/>
|
|
||||||
<xsd:attribute name="miterlimit" type="xsd:decimal" use="optional"/>
|
|
||||||
<xsd:attribute name="joinstyle" type="ST_StrokeJoinStyle" use="optional"/>
|
|
||||||
<xsd:attribute name="endcap" type="ST_StrokeEndCap" use="optional"/>
|
|
||||||
<xsd:attribute name="dashstyle" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="filltype" type="ST_FillType" use="optional"/>
|
|
||||||
<xsd:attribute name="src" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="imageaspect" type="ST_ImageAspect" use="optional"/>
|
|
||||||
<xsd:attribute name="imagesize" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="imagealignshape" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute name="color2" type="s:ST_ColorType" use="optional"/>
|
|
||||||
<xsd:attribute name="startarrow" type="ST_StrokeArrowType" use="optional"/>
|
|
||||||
<xsd:attribute name="startarrowwidth" type="ST_StrokeArrowWidth" use="optional"/>
|
|
||||||
<xsd:attribute name="startarrowlength" type="ST_StrokeArrowLength" use="optional"/>
|
|
||||||
<xsd:attribute name="endarrow" type="ST_StrokeArrowType" use="optional"/>
|
|
||||||
<xsd:attribute name="endarrowwidth" type="ST_StrokeArrowWidth" use="optional"/>
|
|
||||||
<xsd:attribute name="endarrowlength" type="ST_StrokeArrowLength" use="optional"/>
|
|
||||||
<xsd:attribute ref="o:href"/>
|
|
||||||
<xsd:attribute ref="o:althref"/>
|
|
||||||
<xsd:attribute ref="o:title"/>
|
|
||||||
<xsd:attribute ref="o:forcedash"/>
|
|
||||||
<xsd:attribute ref="r:id" use="optional"/>
|
|
||||||
<xsd:attribute name="insetpen" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute ref="o:relid"/>
|
|
||||||
</xsd:attributeGroup>
|
|
||||||
<xsd:group name="EG_ShapeElements">
|
|
||||||
<xsd:choice>
|
|
||||||
<xsd:element ref="path"/>
|
|
||||||
<xsd:element ref="formulas"/>
|
|
||||||
<xsd:element ref="handles"/>
|
|
||||||
<xsd:element ref="fill"/>
|
|
||||||
<xsd:element ref="stroke"/>
|
|
||||||
<xsd:element ref="shadow"/>
|
|
||||||
<xsd:element ref="textbox"/>
|
|
||||||
<xsd:element ref="textpath"/>
|
|
||||||
<xsd:element ref="imagedata"/>
|
|
||||||
<xsd:element ref="o:skew"/>
|
|
||||||
<xsd:element ref="o:extrusion"/>
|
|
||||||
<xsd:element ref="o:callout"/>
|
|
||||||
<xsd:element ref="o:lock"/>
|
|
||||||
<xsd:element ref="o:clippath"/>
|
|
||||||
<xsd:element ref="o:signatureline"/>
|
|
||||||
<xsd:element ref="w10:wrap"/>
|
|
||||||
<xsd:element ref="w10:anchorlock"/>
|
|
||||||
<xsd:element ref="w10:bordertop"/>
|
|
||||||
<xsd:element ref="w10:borderbottom"/>
|
|
||||||
<xsd:element ref="w10:borderleft"/>
|
|
||||||
<xsd:element ref="w10:borderright"/>
|
|
||||||
<xsd:element ref="x:ClientData" minOccurs="0"/>
|
|
||||||
<xsd:element ref="pvml:textdata" minOccurs="0"/>
|
|
||||||
</xsd:choice>
|
|
||||||
</xsd:group>
|
|
||||||
<xsd:element name="shape" type="CT_Shape"/>
|
|
||||||
<xsd:element name="shapetype" type="CT_Shapetype"/>
|
|
||||||
<xsd:element name="group" type="CT_Group"/>
|
|
||||||
<xsd:element name="background" type="CT_Background"/>
|
|
||||||
<xsd:complexType name="CT_Shape">
|
|
||||||
<xsd:choice maxOccurs="unbounded">
|
|
||||||
<xsd:group ref="EG_ShapeElements"/>
|
|
||||||
<xsd:element ref="o:ink"/>
|
|
||||||
<xsd:element ref="pvml:iscomment"/>
|
|
||||||
<xsd:element ref="o:equationxml"/>
|
|
||||||
</xsd:choice>
|
|
||||||
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
|
|
||||||
<xsd:attributeGroup ref="AG_AllShapeAttributes"/>
|
|
||||||
<xsd:attributeGroup ref="AG_Type"/>
|
|
||||||
<xsd:attributeGroup ref="AG_Adj"/>
|
|
||||||
<xsd:attributeGroup ref="AG_Path"/>
|
|
||||||
<xsd:attribute ref="o:gfxdata"/>
|
|
||||||
<xsd:attribute name="equationxml" type="xsd:string" use="optional"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_Shapetype">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:group ref="EG_ShapeElements" minOccurs="0" maxOccurs="unbounded"/>
|
|
||||||
<xsd:element ref="o:complex" minOccurs="0"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
|
|
||||||
<xsd:attributeGroup ref="AG_AllShapeAttributes"/>
|
|
||||||
<xsd:attributeGroup ref="AG_Adj"/>
|
|
||||||
<xsd:attributeGroup ref="AG_Path"/>
|
|
||||||
<xsd:attribute ref="o:master"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_Group">
|
|
||||||
<xsd:choice maxOccurs="unbounded">
|
|
||||||
<xsd:group ref="EG_ShapeElements"/>
|
|
||||||
<xsd:element ref="group"/>
|
|
||||||
<xsd:element ref="shape"/>
|
|
||||||
<xsd:element ref="shapetype"/>
|
|
||||||
<xsd:element ref="arc"/>
|
|
||||||
<xsd:element ref="curve"/>
|
|
||||||
<xsd:element ref="image"/>
|
|
||||||
<xsd:element ref="line"/>
|
|
||||||
<xsd:element ref="oval"/>
|
|
||||||
<xsd:element ref="polyline"/>
|
|
||||||
<xsd:element ref="rect"/>
|
|
||||||
<xsd:element ref="roundrect"/>
|
|
||||||
<xsd:element ref="o:diagram"/>
|
|
||||||
</xsd:choice>
|
|
||||||
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
|
|
||||||
<xsd:attributeGroup ref="AG_Fill"/>
|
|
||||||
<xsd:attribute name="editas" type="ST_EditAs" use="optional"/>
|
|
||||||
<xsd:attribute ref="o:tableproperties"/>
|
|
||||||
<xsd:attribute ref="o:tablelimits"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_Background">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element ref="fill" minOccurs="0"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attributeGroup ref="AG_Id"/>
|
|
||||||
<xsd:attributeGroup ref="AG_Fill"/>
|
|
||||||
<xsd:attribute ref="o:bwmode"/>
|
|
||||||
<xsd:attribute ref="o:bwpure"/>
|
|
||||||
<xsd:attribute ref="o:bwnormal"/>
|
|
||||||
<xsd:attribute ref="o:targetscreensize"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:element name="fill" type="CT_Fill"/>
|
|
||||||
<xsd:element name="formulas" type="CT_Formulas"/>
|
|
||||||
<xsd:element name="handles" type="CT_Handles"/>
|
|
||||||
<xsd:element name="imagedata" type="CT_ImageData"/>
|
|
||||||
<xsd:element name="path" type="CT_Path"/>
|
|
||||||
<xsd:element name="textbox" type="CT_Textbox"/>
|
|
||||||
<xsd:element name="shadow" type="CT_Shadow"/>
|
|
||||||
<xsd:element name="stroke" type="CT_Stroke"/>
|
|
||||||
<xsd:element name="textpath" type="CT_TextPath"/>
|
|
||||||
<xsd:complexType name="CT_Fill">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element ref="o:fill" minOccurs="0"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attributeGroup ref="AG_Id"/>
|
|
||||||
<xsd:attribute name="type" type="ST_FillType" use="optional"/>
|
|
||||||
<xsd:attribute name="on" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute name="color" type="s:ST_ColorType" use="optional"/>
|
|
||||||
<xsd:attribute name="opacity" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="color2" type="s:ST_ColorType" use="optional"/>
|
|
||||||
<xsd:attribute name="src" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute ref="o:href"/>
|
|
||||||
<xsd:attribute ref="o:althref"/>
|
|
||||||
<xsd:attribute name="size" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="origin" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="position" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="aspect" type="ST_ImageAspect" use="optional"/>
|
|
||||||
<xsd:attribute name="colors" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="angle" type="xsd:decimal" use="optional"/>
|
|
||||||
<xsd:attribute name="alignshape" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute name="focus" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="focussize" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="focusposition" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="method" type="ST_FillMethod" use="optional"/>
|
|
||||||
<xsd:attribute ref="o:detectmouseclick"/>
|
|
||||||
<xsd:attribute ref="o:title"/>
|
|
||||||
<xsd:attribute ref="o:opacity2"/>
|
|
||||||
<xsd:attribute name="recolor" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute name="rotate" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute ref="r:id" use="optional"/>
|
|
||||||
<xsd:attribute ref="o:relid" use="optional"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_Formulas">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="f" type="CT_F" minOccurs="0" maxOccurs="unbounded"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_F">
|
|
||||||
<xsd:attribute name="eqn" type="xsd:string"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_Handles">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="h" type="CT_H" minOccurs="0" maxOccurs="unbounded"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_H">
|
|
||||||
<xsd:attribute name="position" type="xsd:string"/>
|
|
||||||
<xsd:attribute name="polar" type="xsd:string"/>
|
|
||||||
<xsd:attribute name="map" type="xsd:string"/>
|
|
||||||
<xsd:attribute name="invx" type="s:ST_TrueFalse"/>
|
|
||||||
<xsd:attribute name="invy" type="s:ST_TrueFalse"/>
|
|
||||||
<xsd:attribute name="switch" type="s:ST_TrueFalseBlank"/>
|
|
||||||
<xsd:attribute name="xrange" type="xsd:string"/>
|
|
||||||
<xsd:attribute name="yrange" type="xsd:string"/>
|
|
||||||
<xsd:attribute name="radiusrange" type="xsd:string"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_ImageData">
|
|
||||||
<xsd:attributeGroup ref="AG_Id"/>
|
|
||||||
<xsd:attributeGroup ref="AG_ImageAttributes"/>
|
|
||||||
<xsd:attributeGroup ref="AG_Chromakey"/>
|
|
||||||
<xsd:attribute name="embosscolor" type="s:ST_ColorType" use="optional"/>
|
|
||||||
<xsd:attribute name="recolortarget" type="s:ST_ColorType"/>
|
|
||||||
<xsd:attribute ref="o:href"/>
|
|
||||||
<xsd:attribute ref="o:althref"/>
|
|
||||||
<xsd:attribute ref="o:title"/>
|
|
||||||
<xsd:attribute ref="o:oleid"/>
|
|
||||||
<xsd:attribute ref="o:detectmouseclick"/>
|
|
||||||
<xsd:attribute ref="o:movie"/>
|
|
||||||
<xsd:attribute ref="o:relid"/>
|
|
||||||
<xsd:attribute ref="r:id"/>
|
|
||||||
<xsd:attribute ref="r:pict"/>
|
|
||||||
<xsd:attribute ref="r:href"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_Path">
|
|
||||||
<xsd:attributeGroup ref="AG_Id"/>
|
|
||||||
<xsd:attribute name="v" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="limo" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="textboxrect" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="fillok" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute name="strokeok" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute name="shadowok" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute name="arrowok" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute name="gradientshapeok" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute name="textpathok" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute name="insetpenok" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute ref="o:connecttype"/>
|
|
||||||
<xsd:attribute ref="o:connectlocs"/>
|
|
||||||
<xsd:attribute ref="o:connectangles"/>
|
|
||||||
<xsd:attribute ref="o:extrusionok"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_Shadow">
|
|
||||||
<xsd:attributeGroup ref="AG_Id"/>
|
|
||||||
<xsd:attribute name="on" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute name="type" type="ST_ShadowType" use="optional"/>
|
|
||||||
<xsd:attribute name="obscured" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute name="color" type="s:ST_ColorType" use="optional"/>
|
|
||||||
<xsd:attribute name="opacity" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="offset" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="color2" type="s:ST_ColorType" use="optional"/>
|
|
||||||
<xsd:attribute name="offset2" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="origin" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="matrix" type="xsd:string" use="optional"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_Stroke">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element ref="o:left" minOccurs="0"/>
|
|
||||||
<xsd:element ref="o:top" minOccurs="0"/>
|
|
||||||
<xsd:element ref="o:right" minOccurs="0"/>
|
|
||||||
<xsd:element ref="o:bottom" minOccurs="0"/>
|
|
||||||
<xsd:element ref="o:column" minOccurs="0"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attributeGroup ref="AG_Id"/>
|
|
||||||
<xsd:attributeGroup ref="AG_StrokeAttributes"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_Textbox">
|
|
||||||
<xsd:choice>
|
|
||||||
<xsd:element ref="w:txbxContent" minOccurs="0"/>
|
|
||||||
<xsd:any namespace="##local" processContents="skip"/>
|
|
||||||
</xsd:choice>
|
|
||||||
<xsd:attributeGroup ref="AG_Id"/>
|
|
||||||
<xsd:attributeGroup ref="AG_Style"/>
|
|
||||||
<xsd:attribute name="inset" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute ref="o:singleclick"/>
|
|
||||||
<xsd:attribute ref="o:insetmode"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_TextPath">
|
|
||||||
<xsd:attributeGroup ref="AG_Id"/>
|
|
||||||
<xsd:attributeGroup ref="AG_Style"/>
|
|
||||||
<xsd:attribute name="on" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute name="fitshape" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute name="fitpath" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute name="trim" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute name="xscale" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute name="string" type="xsd:string" use="optional"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:element name="arc" type="CT_Arc"/>
|
|
||||||
<xsd:element name="curve" type="CT_Curve"/>
|
|
||||||
<xsd:element name="image" type="CT_Image"/>
|
|
||||||
<xsd:element name="line" type="CT_Line"/>
|
|
||||||
<xsd:element name="oval" type="CT_Oval"/>
|
|
||||||
<xsd:element name="polyline" type="CT_PolyLine"/>
|
|
||||||
<xsd:element name="rect" type="CT_Rect"/>
|
|
||||||
<xsd:element name="roundrect" type="CT_RoundRect"/>
|
|
||||||
<xsd:complexType name="CT_Arc">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:group ref="EG_ShapeElements" minOccurs="0" maxOccurs="unbounded"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
|
|
||||||
<xsd:attributeGroup ref="AG_AllShapeAttributes"/>
|
|
||||||
<xsd:attribute name="startAngle" type="xsd:decimal" use="optional"/>
|
|
||||||
<xsd:attribute name="endAngle" type="xsd:decimal" use="optional"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_Curve">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:group ref="EG_ShapeElements" minOccurs="0" maxOccurs="unbounded"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
|
|
||||||
<xsd:attributeGroup ref="AG_AllShapeAttributes"/>
|
|
||||||
<xsd:attribute name="from" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="control1" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="control2" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="to" type="xsd:string" use="optional"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_Image">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:group ref="EG_ShapeElements" minOccurs="0" maxOccurs="unbounded"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
|
|
||||||
<xsd:attributeGroup ref="AG_AllShapeAttributes"/>
|
|
||||||
<xsd:attributeGroup ref="AG_ImageAttributes"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_Line">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:group ref="EG_ShapeElements" minOccurs="0" maxOccurs="unbounded"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
|
|
||||||
<xsd:attributeGroup ref="AG_AllShapeAttributes"/>
|
|
||||||
<xsd:attribute name="from" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="to" type="xsd:string" use="optional"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_Oval">
|
|
||||||
<xsd:choice maxOccurs="unbounded">
|
|
||||||
<xsd:group ref="EG_ShapeElements" minOccurs="0" maxOccurs="unbounded"/>
|
|
||||||
</xsd:choice>
|
|
||||||
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
|
|
||||||
<xsd:attributeGroup ref="AG_AllShapeAttributes"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_PolyLine">
|
|
||||||
<xsd:choice minOccurs="0" maxOccurs="unbounded">
|
|
||||||
<xsd:group ref="EG_ShapeElements"/>
|
|
||||||
<xsd:element ref="o:ink"/>
|
|
||||||
</xsd:choice>
|
|
||||||
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
|
|
||||||
<xsd:attributeGroup ref="AG_AllShapeAttributes"/>
|
|
||||||
<xsd:attribute name="points" type="xsd:string" use="optional"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_Rect">
|
|
||||||
<xsd:choice maxOccurs="unbounded">
|
|
||||||
<xsd:group ref="EG_ShapeElements" minOccurs="0" maxOccurs="unbounded"/>
|
|
||||||
</xsd:choice>
|
|
||||||
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
|
|
||||||
<xsd:attributeGroup ref="AG_AllShapeAttributes"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_RoundRect">
|
|
||||||
<xsd:choice maxOccurs="unbounded">
|
|
||||||
<xsd:group ref="EG_ShapeElements" minOccurs="0" maxOccurs="unbounded"/>
|
|
||||||
</xsd:choice>
|
|
||||||
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
|
|
||||||
<xsd:attributeGroup ref="AG_AllShapeAttributes"/>
|
|
||||||
<xsd:attribute name="arcsize" type="xsd:string" use="optional"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:simpleType name="ST_Ext">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="view"/>
|
|
||||||
<xsd:enumeration value="edit"/>
|
|
||||||
<xsd:enumeration value="backwardCompatible"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_FillType">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="solid"/>
|
|
||||||
<xsd:enumeration value="gradient"/>
|
|
||||||
<xsd:enumeration value="gradientRadial"/>
|
|
||||||
<xsd:enumeration value="tile"/>
|
|
||||||
<xsd:enumeration value="pattern"/>
|
|
||||||
<xsd:enumeration value="frame"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_FillMethod">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="none"/>
|
|
||||||
<xsd:enumeration value="linear"/>
|
|
||||||
<xsd:enumeration value="sigma"/>
|
|
||||||
<xsd:enumeration value="any"/>
|
|
||||||
<xsd:enumeration value="linear sigma"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_ShadowType">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="single"/>
|
|
||||||
<xsd:enumeration value="double"/>
|
|
||||||
<xsd:enumeration value="emboss"/>
|
|
||||||
<xsd:enumeration value="perspective"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_StrokeLineStyle">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="single"/>
|
|
||||||
<xsd:enumeration value="thinThin"/>
|
|
||||||
<xsd:enumeration value="thinThick"/>
|
|
||||||
<xsd:enumeration value="thickThin"/>
|
|
||||||
<xsd:enumeration value="thickBetweenThin"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_StrokeJoinStyle">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="round"/>
|
|
||||||
<xsd:enumeration value="bevel"/>
|
|
||||||
<xsd:enumeration value="miter"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_StrokeEndCap">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="flat"/>
|
|
||||||
<xsd:enumeration value="square"/>
|
|
||||||
<xsd:enumeration value="round"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_StrokeArrowLength">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="short"/>
|
|
||||||
<xsd:enumeration value="medium"/>
|
|
||||||
<xsd:enumeration value="long"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_StrokeArrowWidth">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="narrow"/>
|
|
||||||
<xsd:enumeration value="medium"/>
|
|
||||||
<xsd:enumeration value="wide"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_StrokeArrowType">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="none"/>
|
|
||||||
<xsd:enumeration value="block"/>
|
|
||||||
<xsd:enumeration value="classic"/>
|
|
||||||
<xsd:enumeration value="oval"/>
|
|
||||||
<xsd:enumeration value="diamond"/>
|
|
||||||
<xsd:enumeration value="open"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_ImageAspect">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="ignore"/>
|
|
||||||
<xsd:enumeration value="atMost"/>
|
|
||||||
<xsd:enumeration value="atLeast"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_EditAs">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="canvas"/>
|
|
||||||
<xsd:enumeration value="orgchart"/>
|
|
||||||
<xsd:enumeration value="radial"/>
|
|
||||||
<xsd:enumeration value="cycle"/>
|
|
||||||
<xsd:enumeration value="stacked"/>
|
|
||||||
<xsd:enumeration value="venn"/>
|
|
||||||
<xsd:enumeration value="bullseye"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
</xsd:schema>
|
|
||||||
-509
@@ -1,509 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
|
||||||
xmlns="urn:schemas-microsoft-com:office:office" xmlns:v="urn:schemas-microsoft-com:vml"
|
|
||||||
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
|
|
||||||
xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
|
||||||
targetNamespace="urn:schemas-microsoft-com:office:office" elementFormDefault="qualified"
|
|
||||||
attributeFormDefault="unqualified">
|
|
||||||
<xsd:import namespace="urn:schemas-microsoft-com:vml" schemaLocation="vml-main.xsd"/>
|
|
||||||
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
|
|
||||||
schemaLocation="shared-relationshipReference.xsd"/>
|
|
||||||
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
|
||||||
schemaLocation="shared-commonSimpleTypes.xsd"/>
|
|
||||||
<xsd:attribute name="bwmode" type="ST_BWMode"/>
|
|
||||||
<xsd:attribute name="bwpure" type="ST_BWMode"/>
|
|
||||||
<xsd:attribute name="bwnormal" type="ST_BWMode"/>
|
|
||||||
<xsd:attribute name="targetscreensize" type="ST_ScreenSize"/>
|
|
||||||
<xsd:attribute name="insetmode" type="ST_InsetMode" default="custom"/>
|
|
||||||
<xsd:attribute name="spt" type="xsd:float"/>
|
|
||||||
<xsd:attribute name="wrapcoords" type="xsd:string"/>
|
|
||||||
<xsd:attribute name="oned" type="s:ST_TrueFalse"/>
|
|
||||||
<xsd:attribute name="regroupid" type="xsd:integer"/>
|
|
||||||
<xsd:attribute name="doubleclicknotify" type="s:ST_TrueFalse"/>
|
|
||||||
<xsd:attribute name="connectortype" type="ST_ConnectorType" default="straight"/>
|
|
||||||
<xsd:attribute name="button" type="s:ST_TrueFalse"/>
|
|
||||||
<xsd:attribute name="userhidden" type="s:ST_TrueFalse"/>
|
|
||||||
<xsd:attribute name="forcedash" type="s:ST_TrueFalse"/>
|
|
||||||
<xsd:attribute name="oleicon" type="s:ST_TrueFalse"/>
|
|
||||||
<xsd:attribute name="ole" type="s:ST_TrueFalseBlank"/>
|
|
||||||
<xsd:attribute name="preferrelative" type="s:ST_TrueFalse"/>
|
|
||||||
<xsd:attribute name="cliptowrap" type="s:ST_TrueFalse"/>
|
|
||||||
<xsd:attribute name="clip" type="s:ST_TrueFalse"/>
|
|
||||||
<xsd:attribute name="bullet" type="s:ST_TrueFalse"/>
|
|
||||||
<xsd:attribute name="hr" type="s:ST_TrueFalse"/>
|
|
||||||
<xsd:attribute name="hrstd" type="s:ST_TrueFalse"/>
|
|
||||||
<xsd:attribute name="hrnoshade" type="s:ST_TrueFalse"/>
|
|
||||||
<xsd:attribute name="hrpct" type="xsd:float"/>
|
|
||||||
<xsd:attribute name="hralign" type="ST_HrAlign" default="left"/>
|
|
||||||
<xsd:attribute name="allowincell" type="s:ST_TrueFalse"/>
|
|
||||||
<xsd:attribute name="allowoverlap" type="s:ST_TrueFalse"/>
|
|
||||||
<xsd:attribute name="userdrawn" type="s:ST_TrueFalse"/>
|
|
||||||
<xsd:attribute name="bordertopcolor" type="xsd:string"/>
|
|
||||||
<xsd:attribute name="borderleftcolor" type="xsd:string"/>
|
|
||||||
<xsd:attribute name="borderbottomcolor" type="xsd:string"/>
|
|
||||||
<xsd:attribute name="borderrightcolor" type="xsd:string"/>
|
|
||||||
<xsd:attribute name="connecttype" type="ST_ConnectType"/>
|
|
||||||
<xsd:attribute name="connectlocs" type="xsd:string"/>
|
|
||||||
<xsd:attribute name="connectangles" type="xsd:string"/>
|
|
||||||
<xsd:attribute name="master" type="xsd:string"/>
|
|
||||||
<xsd:attribute name="extrusionok" type="s:ST_TrueFalse"/>
|
|
||||||
<xsd:attribute name="href" type="xsd:string"/>
|
|
||||||
<xsd:attribute name="althref" type="xsd:string"/>
|
|
||||||
<xsd:attribute name="title" type="xsd:string"/>
|
|
||||||
<xsd:attribute name="singleclick" type="s:ST_TrueFalse"/>
|
|
||||||
<xsd:attribute name="oleid" type="xsd:float"/>
|
|
||||||
<xsd:attribute name="detectmouseclick" type="s:ST_TrueFalse"/>
|
|
||||||
<xsd:attribute name="movie" type="xsd:float"/>
|
|
||||||
<xsd:attribute name="spid" type="xsd:string"/>
|
|
||||||
<xsd:attribute name="opacity2" type="xsd:string"/>
|
|
||||||
<xsd:attribute name="relid" type="r:ST_RelationshipId"/>
|
|
||||||
<xsd:attribute name="dgmlayout" type="ST_DiagramLayout"/>
|
|
||||||
<xsd:attribute name="dgmnodekind" type="xsd:integer"/>
|
|
||||||
<xsd:attribute name="dgmlayoutmru" type="ST_DiagramLayout"/>
|
|
||||||
<xsd:attribute name="gfxdata" type="xsd:base64Binary"/>
|
|
||||||
<xsd:attribute name="tableproperties" type="xsd:string"/>
|
|
||||||
<xsd:attribute name="tablelimits" type="xsd:string"/>
|
|
||||||
<xsd:element name="shapedefaults" type="CT_ShapeDefaults"/>
|
|
||||||
<xsd:element name="shapelayout" type="CT_ShapeLayout"/>
|
|
||||||
<xsd:element name="signatureline" type="CT_SignatureLine"/>
|
|
||||||
<xsd:element name="ink" type="CT_Ink"/>
|
|
||||||
<xsd:element name="diagram" type="CT_Diagram"/>
|
|
||||||
<xsd:element name="equationxml" type="CT_EquationXml"/>
|
|
||||||
<xsd:complexType name="CT_ShapeDefaults">
|
|
||||||
<xsd:all minOccurs="0">
|
|
||||||
<xsd:element ref="v:fill" minOccurs="0"/>
|
|
||||||
<xsd:element ref="v:stroke" minOccurs="0"/>
|
|
||||||
<xsd:element ref="v:textbox" minOccurs="0"/>
|
|
||||||
<xsd:element ref="v:shadow" minOccurs="0"/>
|
|
||||||
<xsd:element ref="skew" minOccurs="0"/>
|
|
||||||
<xsd:element ref="extrusion" minOccurs="0"/>
|
|
||||||
<xsd:element ref="callout" minOccurs="0"/>
|
|
||||||
<xsd:element ref="lock" minOccurs="0"/>
|
|
||||||
<xsd:element name="colormru" minOccurs="0" type="CT_ColorMru"/>
|
|
||||||
<xsd:element name="colormenu" minOccurs="0" type="CT_ColorMenu"/>
|
|
||||||
</xsd:all>
|
|
||||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
|
||||||
<xsd:attribute name="spidmax" type="xsd:integer" use="optional"/>
|
|
||||||
<xsd:attribute name="style" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="fill" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute name="fillcolor" type="s:ST_ColorType" use="optional"/>
|
|
||||||
<xsd:attribute name="stroke" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute name="strokecolor" type="s:ST_ColorType"/>
|
|
||||||
<xsd:attribute name="allowincell" form="qualified" type="s:ST_TrueFalse"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_Ink">
|
|
||||||
<xsd:sequence/>
|
|
||||||
<xsd:attribute name="i" type="xsd:string"/>
|
|
||||||
<xsd:attribute name="annotation" type="s:ST_TrueFalse"/>
|
|
||||||
<xsd:attribute name="contentType" type="ST_ContentType" use="optional"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_SignatureLine">
|
|
||||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
|
||||||
<xsd:attribute name="issignatureline" type="s:ST_TrueFalse"/>
|
|
||||||
<xsd:attribute name="id" type="s:ST_Guid"/>
|
|
||||||
<xsd:attribute name="provid" type="s:ST_Guid"/>
|
|
||||||
<xsd:attribute name="signinginstructionsset" type="s:ST_TrueFalse"/>
|
|
||||||
<xsd:attribute name="allowcomments" type="s:ST_TrueFalse"/>
|
|
||||||
<xsd:attribute name="showsigndate" type="s:ST_TrueFalse"/>
|
|
||||||
<xsd:attribute name="suggestedsigner" type="xsd:string" form="qualified"/>
|
|
||||||
<xsd:attribute name="suggestedsigner2" type="xsd:string" form="qualified"/>
|
|
||||||
<xsd:attribute name="suggestedsigneremail" type="xsd:string" form="qualified"/>
|
|
||||||
<xsd:attribute name="signinginstructions" type="xsd:string"/>
|
|
||||||
<xsd:attribute name="addlxml" type="xsd:string"/>
|
|
||||||
<xsd:attribute name="sigprovurl" type="xsd:string"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_ShapeLayout">
|
|
||||||
<xsd:all>
|
|
||||||
<xsd:element name="idmap" type="CT_IdMap" minOccurs="0"/>
|
|
||||||
<xsd:element name="regrouptable" type="CT_RegroupTable" minOccurs="0"/>
|
|
||||||
<xsd:element name="rules" type="CT_Rules" minOccurs="0"/>
|
|
||||||
</xsd:all>
|
|
||||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_IdMap">
|
|
||||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
|
||||||
<xsd:attribute name="data" type="xsd:string" use="optional"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_RegroupTable">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="entry" type="CT_Entry" minOccurs="0" maxOccurs="unbounded"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_Entry">
|
|
||||||
<xsd:attribute name="new" type="xsd:int" use="optional"/>
|
|
||||||
<xsd:attribute name="old" type="xsd:int" use="optional"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_Rules">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="r" type="CT_R" minOccurs="0" maxOccurs="unbounded"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_R">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="proxy" type="CT_Proxy" minOccurs="0" maxOccurs="unbounded"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="id" type="xsd:string" use="required"/>
|
|
||||||
<xsd:attribute name="type" type="ST_RType" use="optional"/>
|
|
||||||
<xsd:attribute name="how" type="ST_How" use="optional"/>
|
|
||||||
<xsd:attribute name="idref" type="xsd:string" use="optional"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_Proxy">
|
|
||||||
<xsd:attribute name="start" type="s:ST_TrueFalseBlank" use="optional" default="false"/>
|
|
||||||
<xsd:attribute name="end" type="s:ST_TrueFalseBlank" use="optional" default="false"/>
|
|
||||||
<xsd:attribute name="idref" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="connectloc" type="xsd:int" use="optional"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_Diagram">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="relationtable" type="CT_RelationTable" minOccurs="0"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
|
||||||
<xsd:attribute name="dgmstyle" type="xsd:integer" use="optional"/>
|
|
||||||
<xsd:attribute name="autoformat" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute name="reverse" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute name="autolayout" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute name="dgmscalex" type="xsd:integer" use="optional"/>
|
|
||||||
<xsd:attribute name="dgmscaley" type="xsd:integer" use="optional"/>
|
|
||||||
<xsd:attribute name="dgmfontsize" type="xsd:integer" use="optional"/>
|
|
||||||
<xsd:attribute name="constrainbounds" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="dgmbasetextscale" type="xsd:integer" use="optional"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_EquationXml">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:any namespace="##any"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="contentType" type="ST_AlternateMathContentType" use="optional"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:simpleType name="ST_AlternateMathContentType">
|
|
||||||
<xsd:restriction base="xsd:string"/>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:complexType name="CT_RelationTable">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="rel" type="CT_Relation" minOccurs="0" maxOccurs="unbounded"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_Relation">
|
|
||||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
|
||||||
<xsd:attribute name="idsrc" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="iddest" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="idcntr" type="xsd:string" use="optional"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_ColorMru">
|
|
||||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
|
||||||
<xsd:attribute name="colors" type="xsd:string"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_ColorMenu">
|
|
||||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
|
||||||
<xsd:attribute name="strokecolor" type="s:ST_ColorType"/>
|
|
||||||
<xsd:attribute name="fillcolor" type="s:ST_ColorType"/>
|
|
||||||
<xsd:attribute name="shadowcolor" type="s:ST_ColorType"/>
|
|
||||||
<xsd:attribute name="extrusioncolor" type="s:ST_ColorType"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:element name="skew" type="CT_Skew"/>
|
|
||||||
<xsd:element name="extrusion" type="CT_Extrusion"/>
|
|
||||||
<xsd:element name="callout" type="CT_Callout"/>
|
|
||||||
<xsd:element name="lock" type="CT_Lock"/>
|
|
||||||
<xsd:element name="OLEObject" type="CT_OLEObject"/>
|
|
||||||
<xsd:element name="complex" type="CT_Complex"/>
|
|
||||||
<xsd:element name="left" type="CT_StrokeChild"/>
|
|
||||||
<xsd:element name="top" type="CT_StrokeChild"/>
|
|
||||||
<xsd:element name="right" type="CT_StrokeChild"/>
|
|
||||||
<xsd:element name="bottom" type="CT_StrokeChild"/>
|
|
||||||
<xsd:element name="column" type="CT_StrokeChild"/>
|
|
||||||
<xsd:element name="clippath" type="CT_ClipPath"/>
|
|
||||||
<xsd:element name="fill" type="CT_Fill"/>
|
|
||||||
<xsd:complexType name="CT_Skew">
|
|
||||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
|
||||||
<xsd:attribute name="id" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="on" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute name="offset" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="origin" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="matrix" type="xsd:string" use="optional"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_Extrusion">
|
|
||||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
|
||||||
<xsd:attribute name="on" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute name="type" type="ST_ExtrusionType" default="parallel" use="optional"/>
|
|
||||||
<xsd:attribute name="render" type="ST_ExtrusionRender" default="solid" use="optional"/>
|
|
||||||
<xsd:attribute name="viewpointorigin" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="viewpoint" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="plane" type="ST_ExtrusionPlane" default="XY" use="optional"/>
|
|
||||||
<xsd:attribute name="skewangle" type="xsd:float" use="optional"/>
|
|
||||||
<xsd:attribute name="skewamt" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="foredepth" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="backdepth" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="orientation" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="orientationangle" type="xsd:float" use="optional"/>
|
|
||||||
<xsd:attribute name="lockrotationcenter" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute name="autorotationcenter" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute name="rotationcenter" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="rotationangle" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="colormode" type="ST_ColorMode" use="optional"/>
|
|
||||||
<xsd:attribute name="color" type="s:ST_ColorType" use="optional"/>
|
|
||||||
<xsd:attribute name="shininess" type="xsd:float" use="optional"/>
|
|
||||||
<xsd:attribute name="specularity" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="diffusity" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="metal" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute name="edge" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="facet" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="lightface" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute name="brightness" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="lightposition" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="lightlevel" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="lightharsh" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute name="lightposition2" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="lightlevel2" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="lightharsh2" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_Callout">
|
|
||||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
|
||||||
<xsd:attribute name="on" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute name="type" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="gap" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="angle" type="ST_Angle" use="optional"/>
|
|
||||||
<xsd:attribute name="dropauto" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute name="drop" type="ST_CalloutDrop" use="optional"/>
|
|
||||||
<xsd:attribute name="distance" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="lengthspecified" type="s:ST_TrueFalse" default="f" use="optional"/>
|
|
||||||
<xsd:attribute name="length" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="accentbar" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute name="textborder" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute name="minusx" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute name="minusy" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_Lock">
|
|
||||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
|
||||||
<xsd:attribute name="position" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute name="selection" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute name="grouping" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute name="ungrouping" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute name="rotation" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute name="cropping" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute name="verticies" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute name="adjusthandles" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute name="text" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute name="aspectratio" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute name="shapetype" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_OLEObject">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="LinkType" type="ST_OLELinkType" minOccurs="0"/>
|
|
||||||
<xsd:element name="LockedField" type="s:ST_TrueFalseBlank" minOccurs="0"/>
|
|
||||||
<xsd:element name="FieldCodes" type="xsd:string" minOccurs="0"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="Type" type="ST_OLEType" use="optional"/>
|
|
||||||
<xsd:attribute name="ProgID" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="ShapeID" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="DrawAspect" type="ST_OLEDrawAspect" use="optional"/>
|
|
||||||
<xsd:attribute name="ObjectID" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute ref="r:id" use="optional"/>
|
|
||||||
<xsd:attribute name="UpdateMode" type="ST_OLEUpdateMode" use="optional"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_Complex">
|
|
||||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_StrokeChild">
|
|
||||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
|
||||||
<xsd:attribute name="on" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute name="weight" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="color" type="s:ST_ColorType" use="optional"/>
|
|
||||||
<xsd:attribute name="color2" type="s:ST_ColorType" use="optional"/>
|
|
||||||
<xsd:attribute name="opacity" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="linestyle" type="v:ST_StrokeLineStyle" use="optional"/>
|
|
||||||
<xsd:attribute name="miterlimit" type="xsd:decimal" use="optional"/>
|
|
||||||
<xsd:attribute name="joinstyle" type="v:ST_StrokeJoinStyle" use="optional"/>
|
|
||||||
<xsd:attribute name="endcap" type="v:ST_StrokeEndCap" use="optional"/>
|
|
||||||
<xsd:attribute name="dashstyle" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="insetpen" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute name="filltype" type="v:ST_FillType" use="optional"/>
|
|
||||||
<xsd:attribute name="src" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="imageaspect" type="v:ST_ImageAspect" use="optional"/>
|
|
||||||
<xsd:attribute name="imagesize" type="xsd:string" use="optional"/>
|
|
||||||
<xsd:attribute name="imagealignshape" type="s:ST_TrueFalse" use="optional"/>
|
|
||||||
<xsd:attribute name="startarrow" type="v:ST_StrokeArrowType" use="optional"/>
|
|
||||||
<xsd:attribute name="startarrowwidth" type="v:ST_StrokeArrowWidth" use="optional"/>
|
|
||||||
<xsd:attribute name="startarrowlength" type="v:ST_StrokeArrowLength" use="optional"/>
|
|
||||||
<xsd:attribute name="endarrow" type="v:ST_StrokeArrowType" use="optional"/>
|
|
||||||
<xsd:attribute name="endarrowwidth" type="v:ST_StrokeArrowWidth" use="optional"/>
|
|
||||||
<xsd:attribute name="endarrowlength" type="v:ST_StrokeArrowLength" use="optional"/>
|
|
||||||
<xsd:attribute ref="href"/>
|
|
||||||
<xsd:attribute ref="althref"/>
|
|
||||||
<xsd:attribute ref="title"/>
|
|
||||||
<xsd:attribute ref="forcedash"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_ClipPath">
|
|
||||||
<xsd:attribute name="v" type="xsd:string" use="required" form="qualified"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_Fill">
|
|
||||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
|
||||||
<xsd:attribute name="type" type="ST_FillType"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:simpleType name="ST_RType">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="arc"/>
|
|
||||||
<xsd:enumeration value="callout"/>
|
|
||||||
<xsd:enumeration value="connector"/>
|
|
||||||
<xsd:enumeration value="align"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_How">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="top"/>
|
|
||||||
<xsd:enumeration value="middle"/>
|
|
||||||
<xsd:enumeration value="bottom"/>
|
|
||||||
<xsd:enumeration value="left"/>
|
|
||||||
<xsd:enumeration value="center"/>
|
|
||||||
<xsd:enumeration value="right"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_BWMode">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="color"/>
|
|
||||||
<xsd:enumeration value="auto"/>
|
|
||||||
<xsd:enumeration value="grayScale"/>
|
|
||||||
<xsd:enumeration value="lightGrayscale"/>
|
|
||||||
<xsd:enumeration value="inverseGray"/>
|
|
||||||
<xsd:enumeration value="grayOutline"/>
|
|
||||||
<xsd:enumeration value="highContrast"/>
|
|
||||||
<xsd:enumeration value="black"/>
|
|
||||||
<xsd:enumeration value="white"/>
|
|
||||||
<xsd:enumeration value="hide"/>
|
|
||||||
<xsd:enumeration value="undrawn"/>
|
|
||||||
<xsd:enumeration value="blackTextAndLines"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_ScreenSize">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="544,376"/>
|
|
||||||
<xsd:enumeration value="640,480"/>
|
|
||||||
<xsd:enumeration value="720,512"/>
|
|
||||||
<xsd:enumeration value="800,600"/>
|
|
||||||
<xsd:enumeration value="1024,768"/>
|
|
||||||
<xsd:enumeration value="1152,862"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_InsetMode">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="auto"/>
|
|
||||||
<xsd:enumeration value="custom"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_ColorMode">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="auto"/>
|
|
||||||
<xsd:enumeration value="custom"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_ContentType">
|
|
||||||
<xsd:restriction base="xsd:string"/>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_DiagramLayout">
|
|
||||||
<xsd:restriction base="xsd:integer">
|
|
||||||
<xsd:enumeration value="0"/>
|
|
||||||
<xsd:enumeration value="1"/>
|
|
||||||
<xsd:enumeration value="2"/>
|
|
||||||
<xsd:enumeration value="3"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_ExtrusionType">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="perspective"/>
|
|
||||||
<xsd:enumeration value="parallel"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_ExtrusionRender">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="solid"/>
|
|
||||||
<xsd:enumeration value="wireFrame"/>
|
|
||||||
<xsd:enumeration value="boundingCube"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_ExtrusionPlane">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="XY"/>
|
|
||||||
<xsd:enumeration value="ZX"/>
|
|
||||||
<xsd:enumeration value="YZ"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_Angle">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="any"/>
|
|
||||||
<xsd:enumeration value="30"/>
|
|
||||||
<xsd:enumeration value="45"/>
|
|
||||||
<xsd:enumeration value="60"/>
|
|
||||||
<xsd:enumeration value="90"/>
|
|
||||||
<xsd:enumeration value="auto"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_CalloutDrop">
|
|
||||||
<xsd:restriction base="xsd:string"/>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_CalloutPlacement">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="top"/>
|
|
||||||
<xsd:enumeration value="center"/>
|
|
||||||
<xsd:enumeration value="bottom"/>
|
|
||||||
<xsd:enumeration value="user"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_ConnectorType">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="none"/>
|
|
||||||
<xsd:enumeration value="straight"/>
|
|
||||||
<xsd:enumeration value="elbow"/>
|
|
||||||
<xsd:enumeration value="curved"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_HrAlign">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="left"/>
|
|
||||||
<xsd:enumeration value="right"/>
|
|
||||||
<xsd:enumeration value="center"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_ConnectType">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="none"/>
|
|
||||||
<xsd:enumeration value="rect"/>
|
|
||||||
<xsd:enumeration value="segments"/>
|
|
||||||
<xsd:enumeration value="custom"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_OLELinkType">
|
|
||||||
<xsd:restriction base="xsd:string"/>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_OLEType">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="Embed"/>
|
|
||||||
<xsd:enumeration value="Link"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_OLEDrawAspect">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="Content"/>
|
|
||||||
<xsd:enumeration value="Icon"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_OLEUpdateMode">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="Always"/>
|
|
||||||
<xsd:enumeration value="OnCall"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_FillType">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="gradientCenter"/>
|
|
||||||
<xsd:enumeration value="solid"/>
|
|
||||||
<xsd:enumeration value="pattern"/>
|
|
||||||
<xsd:enumeration value="tile"/>
|
|
||||||
<xsd:enumeration value="frame"/>
|
|
||||||
<xsd:enumeration value="gradientUnscaled"/>
|
|
||||||
<xsd:enumeration value="gradientRadial"/>
|
|
||||||
<xsd:enumeration value="gradient"/>
|
|
||||||
<xsd:enumeration value="background"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
</xsd:schema>
|
|
||||||
-12
@@ -1,12 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
|
||||||
xmlns="urn:schemas-microsoft-com:office:powerpoint"
|
|
||||||
targetNamespace="urn:schemas-microsoft-com:office:powerpoint" elementFormDefault="qualified"
|
|
||||||
attributeFormDefault="unqualified">
|
|
||||||
<xsd:element name="iscomment" type="CT_Empty"/>
|
|
||||||
<xsd:element name="textdata" type="CT_Rel"/>
|
|
||||||
<xsd:complexType name="CT_Empty"/>
|
|
||||||
<xsd:complexType name="CT_Rel">
|
|
||||||
<xsd:attribute name="id" type="xsd:string"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
</xsd:schema>
|
|
||||||
-108
@@ -1,108 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
|
||||||
xmlns="urn:schemas-microsoft-com:office:excel"
|
|
||||||
xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
|
||||||
targetNamespace="urn:schemas-microsoft-com:office:excel" elementFormDefault="qualified"
|
|
||||||
attributeFormDefault="unqualified">
|
|
||||||
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
|
||||||
schemaLocation="shared-commonSimpleTypes.xsd"/>
|
|
||||||
<xsd:element name="ClientData" type="CT_ClientData"/>
|
|
||||||
<xsd:complexType name="CT_ClientData">
|
|
||||||
<xsd:choice minOccurs="0" maxOccurs="unbounded">
|
|
||||||
<xsd:element name="MoveWithCells" type="s:ST_TrueFalseBlank"/>
|
|
||||||
<xsd:element name="SizeWithCells" type="s:ST_TrueFalseBlank"/>
|
|
||||||
<xsd:element name="Anchor" type="xsd:string"/>
|
|
||||||
<xsd:element name="Locked" type="s:ST_TrueFalseBlank"/>
|
|
||||||
<xsd:element name="DefaultSize" type="s:ST_TrueFalseBlank"/>
|
|
||||||
<xsd:element name="PrintObject" type="s:ST_TrueFalseBlank"/>
|
|
||||||
<xsd:element name="Disabled" type="s:ST_TrueFalseBlank"/>
|
|
||||||
<xsd:element name="AutoFill" type="s:ST_TrueFalseBlank"/>
|
|
||||||
<xsd:element name="AutoLine" type="s:ST_TrueFalseBlank"/>
|
|
||||||
<xsd:element name="AutoPict" type="s:ST_TrueFalseBlank"/>
|
|
||||||
<xsd:element name="FmlaMacro" type="xsd:string"/>
|
|
||||||
<xsd:element name="TextHAlign" type="xsd:string"/>
|
|
||||||
<xsd:element name="TextVAlign" type="xsd:string"/>
|
|
||||||
<xsd:element name="LockText" type="s:ST_TrueFalseBlank"/>
|
|
||||||
<xsd:element name="JustLastX" type="s:ST_TrueFalseBlank"/>
|
|
||||||
<xsd:element name="SecretEdit" type="s:ST_TrueFalseBlank"/>
|
|
||||||
<xsd:element name="Default" type="s:ST_TrueFalseBlank"/>
|
|
||||||
<xsd:element name="Help" type="s:ST_TrueFalseBlank"/>
|
|
||||||
<xsd:element name="Cancel" type="s:ST_TrueFalseBlank"/>
|
|
||||||
<xsd:element name="Dismiss" type="s:ST_TrueFalseBlank"/>
|
|
||||||
<xsd:element name="Accel" type="xsd:integer"/>
|
|
||||||
<xsd:element name="Accel2" type="xsd:integer"/>
|
|
||||||
<xsd:element name="Row" type="xsd:integer"/>
|
|
||||||
<xsd:element name="Column" type="xsd:integer"/>
|
|
||||||
<xsd:element name="Visible" type="s:ST_TrueFalseBlank"/>
|
|
||||||
<xsd:element name="RowHidden" type="s:ST_TrueFalseBlank"/>
|
|
||||||
<xsd:element name="ColHidden" type="s:ST_TrueFalseBlank"/>
|
|
||||||
<xsd:element name="VTEdit" type="xsd:integer"/>
|
|
||||||
<xsd:element name="MultiLine" type="s:ST_TrueFalseBlank"/>
|
|
||||||
<xsd:element name="VScroll" type="s:ST_TrueFalseBlank"/>
|
|
||||||
<xsd:element name="ValidIds" type="s:ST_TrueFalseBlank"/>
|
|
||||||
<xsd:element name="FmlaRange" type="xsd:string"/>
|
|
||||||
<xsd:element name="WidthMin" type="xsd:integer"/>
|
|
||||||
<xsd:element name="Sel" type="xsd:integer"/>
|
|
||||||
<xsd:element name="NoThreeD2" type="s:ST_TrueFalseBlank"/>
|
|
||||||
<xsd:element name="SelType" type="xsd:string"/>
|
|
||||||
<xsd:element name="MultiSel" type="xsd:string"/>
|
|
||||||
<xsd:element name="LCT" type="xsd:string"/>
|
|
||||||
<xsd:element name="ListItem" type="xsd:string"/>
|
|
||||||
<xsd:element name="DropStyle" type="xsd:string"/>
|
|
||||||
<xsd:element name="Colored" type="s:ST_TrueFalseBlank"/>
|
|
||||||
<xsd:element name="DropLines" type="xsd:integer"/>
|
|
||||||
<xsd:element name="Checked" type="xsd:integer"/>
|
|
||||||
<xsd:element name="FmlaLink" type="xsd:string"/>
|
|
||||||
<xsd:element name="FmlaPict" type="xsd:string"/>
|
|
||||||
<xsd:element name="NoThreeD" type="s:ST_TrueFalseBlank"/>
|
|
||||||
<xsd:element name="FirstButton" type="s:ST_TrueFalseBlank"/>
|
|
||||||
<xsd:element name="FmlaGroup" type="xsd:string"/>
|
|
||||||
<xsd:element name="Val" type="xsd:integer"/>
|
|
||||||
<xsd:element name="Min" type="xsd:integer"/>
|
|
||||||
<xsd:element name="Max" type="xsd:integer"/>
|
|
||||||
<xsd:element name="Inc" type="xsd:integer"/>
|
|
||||||
<xsd:element name="Page" type="xsd:integer"/>
|
|
||||||
<xsd:element name="Horiz" type="s:ST_TrueFalseBlank"/>
|
|
||||||
<xsd:element name="Dx" type="xsd:integer"/>
|
|
||||||
<xsd:element name="MapOCX" type="s:ST_TrueFalseBlank"/>
|
|
||||||
<xsd:element name="CF" type="ST_CF"/>
|
|
||||||
<xsd:element name="Camera" type="s:ST_TrueFalseBlank"/>
|
|
||||||
<xsd:element name="RecalcAlways" type="s:ST_TrueFalseBlank"/>
|
|
||||||
<xsd:element name="AutoScale" type="s:ST_TrueFalseBlank"/>
|
|
||||||
<xsd:element name="DDE" type="s:ST_TrueFalseBlank"/>
|
|
||||||
<xsd:element name="UIObj" type="s:ST_TrueFalseBlank"/>
|
|
||||||
<xsd:element name="ScriptText" type="xsd:string"/>
|
|
||||||
<xsd:element name="ScriptExtended" type="xsd:string"/>
|
|
||||||
<xsd:element name="ScriptLanguage" type="xsd:nonNegativeInteger"/>
|
|
||||||
<xsd:element name="ScriptLocation" type="xsd:nonNegativeInteger"/>
|
|
||||||
<xsd:element name="FmlaTxbx" type="xsd:string"/>
|
|
||||||
</xsd:choice>
|
|
||||||
<xsd:attribute name="ObjectType" type="ST_ObjectType" use="required"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:simpleType name="ST_CF">
|
|
||||||
<xsd:restriction base="xsd:string"/>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_ObjectType">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="Button"/>
|
|
||||||
<xsd:enumeration value="Checkbox"/>
|
|
||||||
<xsd:enumeration value="Dialog"/>
|
|
||||||
<xsd:enumeration value="Drop"/>
|
|
||||||
<xsd:enumeration value="Edit"/>
|
|
||||||
<xsd:enumeration value="GBox"/>
|
|
||||||
<xsd:enumeration value="Label"/>
|
|
||||||
<xsd:enumeration value="LineA"/>
|
|
||||||
<xsd:enumeration value="List"/>
|
|
||||||
<xsd:enumeration value="Movie"/>
|
|
||||||
<xsd:enumeration value="Note"/>
|
|
||||||
<xsd:enumeration value="Pict"/>
|
|
||||||
<xsd:enumeration value="Radio"/>
|
|
||||||
<xsd:enumeration value="RectA"/>
|
|
||||||
<xsd:enumeration value="Scroll"/>
|
|
||||||
<xsd:enumeration value="Spin"/>
|
|
||||||
<xsd:enumeration value="Shape"/>
|
|
||||||
<xsd:enumeration value="Group"/>
|
|
||||||
<xsd:enumeration value="Rect"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
</xsd:schema>
|
|
||||||
-96
@@ -1,96 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
|
||||||
xmlns="urn:schemas-microsoft-com:office:word"
|
|
||||||
targetNamespace="urn:schemas-microsoft-com:office:word" elementFormDefault="qualified"
|
|
||||||
attributeFormDefault="unqualified">
|
|
||||||
<xsd:element name="bordertop" type="CT_Border"/>
|
|
||||||
<xsd:element name="borderleft" type="CT_Border"/>
|
|
||||||
<xsd:element name="borderright" type="CT_Border"/>
|
|
||||||
<xsd:element name="borderbottom" type="CT_Border"/>
|
|
||||||
<xsd:complexType name="CT_Border">
|
|
||||||
<xsd:attribute name="type" type="ST_BorderType" use="optional"/>
|
|
||||||
<xsd:attribute name="width" type="xsd:positiveInteger" use="optional"/>
|
|
||||||
<xsd:attribute name="shadow" type="ST_BorderShadow" use="optional"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:element name="wrap" type="CT_Wrap"/>
|
|
||||||
<xsd:complexType name="CT_Wrap">
|
|
||||||
<xsd:attribute name="type" type="ST_WrapType" use="optional"/>
|
|
||||||
<xsd:attribute name="side" type="ST_WrapSide" use="optional"/>
|
|
||||||
<xsd:attribute name="anchorx" type="ST_HorizontalAnchor" use="optional"/>
|
|
||||||
<xsd:attribute name="anchory" type="ST_VerticalAnchor" use="optional"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:element name="anchorlock" type="CT_AnchorLock"/>
|
|
||||||
<xsd:complexType name="CT_AnchorLock"/>
|
|
||||||
<xsd:simpleType name="ST_BorderType">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="none"/>
|
|
||||||
<xsd:enumeration value="single"/>
|
|
||||||
<xsd:enumeration value="thick"/>
|
|
||||||
<xsd:enumeration value="double"/>
|
|
||||||
<xsd:enumeration value="hairline"/>
|
|
||||||
<xsd:enumeration value="dot"/>
|
|
||||||
<xsd:enumeration value="dash"/>
|
|
||||||
<xsd:enumeration value="dotDash"/>
|
|
||||||
<xsd:enumeration value="dashDotDot"/>
|
|
||||||
<xsd:enumeration value="triple"/>
|
|
||||||
<xsd:enumeration value="thinThickSmall"/>
|
|
||||||
<xsd:enumeration value="thickThinSmall"/>
|
|
||||||
<xsd:enumeration value="thickBetweenThinSmall"/>
|
|
||||||
<xsd:enumeration value="thinThick"/>
|
|
||||||
<xsd:enumeration value="thickThin"/>
|
|
||||||
<xsd:enumeration value="thickBetweenThin"/>
|
|
||||||
<xsd:enumeration value="thinThickLarge"/>
|
|
||||||
<xsd:enumeration value="thickThinLarge"/>
|
|
||||||
<xsd:enumeration value="thickBetweenThinLarge"/>
|
|
||||||
<xsd:enumeration value="wave"/>
|
|
||||||
<xsd:enumeration value="doubleWave"/>
|
|
||||||
<xsd:enumeration value="dashedSmall"/>
|
|
||||||
<xsd:enumeration value="dashDotStroked"/>
|
|
||||||
<xsd:enumeration value="threeDEmboss"/>
|
|
||||||
<xsd:enumeration value="threeDEngrave"/>
|
|
||||||
<xsd:enumeration value="HTMLOutset"/>
|
|
||||||
<xsd:enumeration value="HTMLInset"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_BorderShadow">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="t"/>
|
|
||||||
<xsd:enumeration value="true"/>
|
|
||||||
<xsd:enumeration value="f"/>
|
|
||||||
<xsd:enumeration value="false"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_WrapType">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="topAndBottom"/>
|
|
||||||
<xsd:enumeration value="square"/>
|
|
||||||
<xsd:enumeration value="none"/>
|
|
||||||
<xsd:enumeration value="tight"/>
|
|
||||||
<xsd:enumeration value="through"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_WrapSide">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="both"/>
|
|
||||||
<xsd:enumeration value="left"/>
|
|
||||||
<xsd:enumeration value="right"/>
|
|
||||||
<xsd:enumeration value="largest"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_HorizontalAnchor">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="margin"/>
|
|
||||||
<xsd:enumeration value="page"/>
|
|
||||||
<xsd:enumeration value="text"/>
|
|
||||||
<xsd:enumeration value="char"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_VerticalAnchor">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="margin"/>
|
|
||||||
<xsd:enumeration value="page"/>
|
|
||||||
<xsd:enumeration value="text"/>
|
|
||||||
<xsd:enumeration value="line"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
</xsd:schema>
|
|
||||||
-3646
File diff suppressed because it is too large
Load Diff
-116
@@ -1,116 +0,0 @@
|
|||||||
<?xml version='1.0'?>
|
|
||||||
<xs:schema targetNamespace="http://www.w3.org/XML/1998/namespace" xmlns:xs="http://www.w3.org/2001/XMLSchema" xml:lang="en">
|
|
||||||
|
|
||||||
<xs:annotation>
|
|
||||||
<xs:documentation>
|
|
||||||
See http://www.w3.org/XML/1998/namespace.html and
|
|
||||||
http://www.w3.org/TR/REC-xml for information about this namespace.
|
|
||||||
|
|
||||||
This schema document describes the XML namespace, in a form
|
|
||||||
suitable for import by other schema documents.
|
|
||||||
|
|
||||||
Note that local names in this namespace are intended to be defined
|
|
||||||
only by the World Wide Web Consortium or its subgroups. The
|
|
||||||
following names are currently defined in this namespace and should
|
|
||||||
not be used with conflicting semantics by any Working Group,
|
|
||||||
specification, or document instance:
|
|
||||||
|
|
||||||
base (as an attribute name): denotes an attribute whose value
|
|
||||||
provides a URI to be used as the base for interpreting any
|
|
||||||
relative URIs in the scope of the element on which it
|
|
||||||
appears; its value is inherited. This name is reserved
|
|
||||||
by virtue of its definition in the XML Base specification.
|
|
||||||
|
|
||||||
lang (as an attribute name): denotes an attribute whose value
|
|
||||||
is a language code for the natural language of the content of
|
|
||||||
any element; its value is inherited. This name is reserved
|
|
||||||
by virtue of its definition in the XML specification.
|
|
||||||
|
|
||||||
space (as an attribute name): denotes an attribute whose
|
|
||||||
value is a keyword indicating what whitespace processing
|
|
||||||
discipline is intended for the content of the element; its
|
|
||||||
value is inherited. This name is reserved by virtue of its
|
|
||||||
definition in the XML specification.
|
|
||||||
|
|
||||||
Father (in any context at all): denotes Jon Bosak, the chair of
|
|
||||||
the original XML Working Group. This name is reserved by
|
|
||||||
the following decision of the W3C XML Plenary and
|
|
||||||
XML Coordination groups:
|
|
||||||
|
|
||||||
In appreciation for his vision, leadership and dedication
|
|
||||||
the W3C XML Plenary on this 10th day of February, 2000
|
|
||||||
reserves for Jon Bosak in perpetuity the XML name
|
|
||||||
xml:Father
|
|
||||||
</xs:documentation>
|
|
||||||
</xs:annotation>
|
|
||||||
|
|
||||||
<xs:annotation>
|
|
||||||
<xs:documentation>This schema defines attributes and an attribute group
|
|
||||||
suitable for use by
|
|
||||||
schemas wishing to allow xml:base, xml:lang or xml:space attributes
|
|
||||||
on elements they define.
|
|
||||||
|
|
||||||
To enable this, such a schema must import this schema
|
|
||||||
for the XML namespace, e.g. as follows:
|
|
||||||
<schema . . .>
|
|
||||||
. . .
|
|
||||||
<import namespace="http://www.w3.org/XML/1998/namespace"
|
|
||||||
schemaLocation="http://www.w3.org/2001/03/xml.xsd"/>
|
|
||||||
|
|
||||||
Subsequently, qualified reference to any of the attributes
|
|
||||||
or the group defined below will have the desired effect, e.g.
|
|
||||||
|
|
||||||
<type . . .>
|
|
||||||
. . .
|
|
||||||
<attributeGroup ref="xml:specialAttrs"/>
|
|
||||||
|
|
||||||
will define a type which will schema-validate an instance
|
|
||||||
element with any of those attributes</xs:documentation>
|
|
||||||
</xs:annotation>
|
|
||||||
|
|
||||||
<xs:annotation>
|
|
||||||
<xs:documentation>In keeping with the XML Schema WG's standard versioning
|
|
||||||
policy, this schema document will persist at
|
|
||||||
http://www.w3.org/2001/03/xml.xsd.
|
|
||||||
At the date of issue it can also be found at
|
|
||||||
http://www.w3.org/2001/xml.xsd.
|
|
||||||
The schema document at that URI may however change in the future,
|
|
||||||
in order to remain compatible with the latest version of XML Schema
|
|
||||||
itself. In other words, if the XML Schema namespace changes, the version
|
|
||||||
of this document at
|
|
||||||
http://www.w3.org/2001/xml.xsd will change
|
|
||||||
accordingly; the version at
|
|
||||||
http://www.w3.org/2001/03/xml.xsd will not change.
|
|
||||||
</xs:documentation>
|
|
||||||
</xs:annotation>
|
|
||||||
|
|
||||||
<xs:attribute name="lang" type="xs:language">
|
|
||||||
<xs:annotation>
|
|
||||||
<xs:documentation>In due course, we should install the relevant ISO 2- and 3-letter
|
|
||||||
codes as the enumerated possible values . . .</xs:documentation>
|
|
||||||
</xs:annotation>
|
|
||||||
</xs:attribute>
|
|
||||||
|
|
||||||
<xs:attribute name="space" default="preserve">
|
|
||||||
<xs:simpleType>
|
|
||||||
<xs:restriction base="xs:NCName">
|
|
||||||
<xs:enumeration value="default"/>
|
|
||||||
<xs:enumeration value="preserve"/>
|
|
||||||
</xs:restriction>
|
|
||||||
</xs:simpleType>
|
|
||||||
</xs:attribute>
|
|
||||||
|
|
||||||
<xs:attribute name="base" type="xs:anyURI">
|
|
||||||
<xs:annotation>
|
|
||||||
<xs:documentation>See http://www.w3.org/TR/xmlbase/ for
|
|
||||||
information about this attribute.</xs:documentation>
|
|
||||||
</xs:annotation>
|
|
||||||
</xs:attribute>
|
|
||||||
|
|
||||||
<xs:attributeGroup name="specialAttrs">
|
|
||||||
<xs:attribute ref="xml:base"/>
|
|
||||||
<xs:attribute ref="xml:lang"/>
|
|
||||||
<xs:attribute ref="xml:space"/>
|
|
||||||
</xs:attributeGroup>
|
|
||||||
|
|
||||||
</xs:schema>
|
|
||||||
-42
@@ -1,42 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<xs:schema xmlns="http://schemas.openxmlformats.org/package/2006/content-types"
|
|
||||||
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
|
||||||
targetNamespace="http://schemas.openxmlformats.org/package/2006/content-types"
|
|
||||||
elementFormDefault="qualified" attributeFormDefault="unqualified" blockDefault="#all">
|
|
||||||
|
|
||||||
<xs:element name="Types" type="CT_Types"/>
|
|
||||||
<xs:element name="Default" type="CT_Default"/>
|
|
||||||
<xs:element name="Override" type="CT_Override"/>
|
|
||||||
|
|
||||||
<xs:complexType name="CT_Types">
|
|
||||||
<xs:choice minOccurs="0" maxOccurs="unbounded">
|
|
||||||
<xs:element ref="Default"/>
|
|
||||||
<xs:element ref="Override"/>
|
|
||||||
</xs:choice>
|
|
||||||
</xs:complexType>
|
|
||||||
|
|
||||||
<xs:complexType name="CT_Default">
|
|
||||||
<xs:attribute name="Extension" type="ST_Extension" use="required"/>
|
|
||||||
<xs:attribute name="ContentType" type="ST_ContentType" use="required"/>
|
|
||||||
</xs:complexType>
|
|
||||||
|
|
||||||
<xs:complexType name="CT_Override">
|
|
||||||
<xs:attribute name="ContentType" type="ST_ContentType" use="required"/>
|
|
||||||
<xs:attribute name="PartName" type="xs:anyURI" use="required"/>
|
|
||||||
</xs:complexType>
|
|
||||||
|
|
||||||
<xs:simpleType name="ST_ContentType">
|
|
||||||
<xs:restriction base="xs:string">
|
|
||||||
<xs:pattern
|
|
||||||
value="(((([\p{IsBasicLatin}-[\p{Cc}\(\)<>@,;:\\"/\[\]\?=\{\}\s\t]])+))/((([\p{IsBasicLatin}-[\p{Cc}\(\)<>@,;:\\"/\[\]\?=\{\}\s\t]])+))((\s+)*;(\s+)*(((([\p{IsBasicLatin}-[\p{Cc}\(\)<>@,;:\\"/\[\]\?=\{\}\s\t]])+))=((([\p{IsBasicLatin}-[\p{Cc}\(\)<>@,;:\\"/\[\]\?=\{\}\s\t]])+)|("(([\p{IsLatin-1Supplement}\p{IsBasicLatin}-[\p{Cc}"\n\r]]|(\s+))|(\\[\p{IsBasicLatin}]))*"))))*)"
|
|
||||||
/>
|
|
||||||
</xs:restriction>
|
|
||||||
</xs:simpleType>
|
|
||||||
|
|
||||||
<xs:simpleType name="ST_Extension">
|
|
||||||
<xs:restriction base="xs:string">
|
|
||||||
<xs:pattern
|
|
||||||
value="([!$&'\(\)\*\+,:=]|(%[0-9a-fA-F][0-9a-fA-F])|[:@]|[a-zA-Z0-9\-_~])+"/>
|
|
||||||
</xs:restriction>
|
|
||||||
</xs:simpleType>
|
|
||||||
</xs:schema>
|
|
||||||
-50
@@ -1,50 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<xs:schema targetNamespace="http://schemas.openxmlformats.org/package/2006/metadata/core-properties"
|
|
||||||
xmlns="http://schemas.openxmlformats.org/package/2006/metadata/core-properties"
|
|
||||||
xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:dc="http://purl.org/dc/elements/1.1/"
|
|
||||||
xmlns:dcterms="http://purl.org/dc/terms/" elementFormDefault="qualified" blockDefault="#all">
|
|
||||||
|
|
||||||
<xs:import namespace="http://purl.org/dc/elements/1.1/"
|
|
||||||
schemaLocation="http://dublincore.org/schemas/xmls/qdc/2003/04/02/dc.xsd"/>
|
|
||||||
<xs:import namespace="http://purl.org/dc/terms/"
|
|
||||||
schemaLocation="http://dublincore.org/schemas/xmls/qdc/2003/04/02/dcterms.xsd"/>
|
|
||||||
<xs:import id="xml" namespace="http://www.w3.org/XML/1998/namespace"/>
|
|
||||||
|
|
||||||
<xs:element name="coreProperties" type="CT_CoreProperties"/>
|
|
||||||
|
|
||||||
<xs:complexType name="CT_CoreProperties">
|
|
||||||
<xs:all>
|
|
||||||
<xs:element name="category" minOccurs="0" maxOccurs="1" type="xs:string"/>
|
|
||||||
<xs:element name="contentStatus" minOccurs="0" maxOccurs="1" type="xs:string"/>
|
|
||||||
<xs:element ref="dcterms:created" minOccurs="0" maxOccurs="1"/>
|
|
||||||
<xs:element ref="dc:creator" minOccurs="0" maxOccurs="1"/>
|
|
||||||
<xs:element ref="dc:description" minOccurs="0" maxOccurs="1"/>
|
|
||||||
<xs:element ref="dc:identifier" minOccurs="0" maxOccurs="1"/>
|
|
||||||
<xs:element name="keywords" minOccurs="0" maxOccurs="1" type="CT_Keywords"/>
|
|
||||||
<xs:element ref="dc:language" minOccurs="0" maxOccurs="1"/>
|
|
||||||
<xs:element name="lastModifiedBy" minOccurs="0" maxOccurs="1" type="xs:string"/>
|
|
||||||
<xs:element name="lastPrinted" minOccurs="0" maxOccurs="1" type="xs:dateTime"/>
|
|
||||||
<xs:element ref="dcterms:modified" minOccurs="0" maxOccurs="1"/>
|
|
||||||
<xs:element name="revision" minOccurs="0" maxOccurs="1" type="xs:string"/>
|
|
||||||
<xs:element ref="dc:subject" minOccurs="0" maxOccurs="1"/>
|
|
||||||
<xs:element ref="dc:title" minOccurs="0" maxOccurs="1"/>
|
|
||||||
<xs:element name="version" minOccurs="0" maxOccurs="1" type="xs:string"/>
|
|
||||||
</xs:all>
|
|
||||||
</xs:complexType>
|
|
||||||
|
|
||||||
<xs:complexType name="CT_Keywords" mixed="true">
|
|
||||||
<xs:sequence>
|
|
||||||
<xs:element name="value" minOccurs="0" maxOccurs="unbounded" type="CT_Keyword"/>
|
|
||||||
</xs:sequence>
|
|
||||||
<xs:attribute ref="xml:lang" use="optional"/>
|
|
||||||
</xs:complexType>
|
|
||||||
|
|
||||||
<xs:complexType name="CT_Keyword">
|
|
||||||
<xs:simpleContent>
|
|
||||||
<xs:extension base="xs:string">
|
|
||||||
<xs:attribute ref="xml:lang" use="optional"/>
|
|
||||||
</xs:extension>
|
|
||||||
</xs:simpleContent>
|
|
||||||
</xs:complexType>
|
|
||||||
|
|
||||||
</xs:schema>
|
|
||||||
-49
@@ -1,49 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<xsd:schema xmlns="http://schemas.openxmlformats.org/package/2006/digital-signature"
|
|
||||||
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
|
||||||
targetNamespace="http://schemas.openxmlformats.org/package/2006/digital-signature"
|
|
||||||
elementFormDefault="qualified" attributeFormDefault="unqualified" blockDefault="#all">
|
|
||||||
|
|
||||||
<xsd:element name="SignatureTime" type="CT_SignatureTime"/>
|
|
||||||
<xsd:element name="RelationshipReference" type="CT_RelationshipReference"/>
|
|
||||||
<xsd:element name="RelationshipsGroupReference" type="CT_RelationshipsGroupReference"/>
|
|
||||||
|
|
||||||
<xsd:complexType name="CT_SignatureTime">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="Format" type="ST_Format"/>
|
|
||||||
<xsd:element name="Value" type="ST_Value"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
|
|
||||||
<xsd:complexType name="CT_RelationshipReference">
|
|
||||||
<xsd:simpleContent>
|
|
||||||
<xsd:extension base="xsd:string">
|
|
||||||
<xsd:attribute name="SourceId" type="xsd:string" use="required"/>
|
|
||||||
</xsd:extension>
|
|
||||||
</xsd:simpleContent>
|
|
||||||
</xsd:complexType>
|
|
||||||
|
|
||||||
<xsd:complexType name="CT_RelationshipsGroupReference">
|
|
||||||
<xsd:simpleContent>
|
|
||||||
<xsd:extension base="xsd:string">
|
|
||||||
<xsd:attribute name="SourceType" type="xsd:anyURI" use="required"/>
|
|
||||||
</xsd:extension>
|
|
||||||
</xsd:simpleContent>
|
|
||||||
</xsd:complexType>
|
|
||||||
|
|
||||||
<xsd:simpleType name="ST_Format">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:pattern
|
|
||||||
value="(YYYY)|(YYYY-MM)|(YYYY-MM-DD)|(YYYY-MM-DDThh:mmTZD)|(YYYY-MM-DDThh:mm:ssTZD)|(YYYY-MM-DDThh:mm:ss.sTZD)"
|
|
||||||
/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
|
|
||||||
<xsd:simpleType name="ST_Value">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:pattern
|
|
||||||
value="(([0-9][0-9][0-9][0-9]))|(([0-9][0-9][0-9][0-9])-((0[1-9])|(1(0|1|2))))|(([0-9][0-9][0-9][0-9])-((0[1-9])|(1(0|1|2)))-((0[1-9])|(1[0-9])|(2[0-9])|(3(0|1))))|(([0-9][0-9][0-9][0-9])-((0[1-9])|(1(0|1|2)))-((0[1-9])|(1[0-9])|(2[0-9])|(3(0|1)))T((0[0-9])|(1[0-9])|(2(0|1|2|3))):((0[0-9])|(1[0-9])|(2[0-9])|(3[0-9])|(4[0-9])|(5[0-9]))(((\+|-)((0[0-9])|(1[0-9])|(2(0|1|2|3))):((0[0-9])|(1[0-9])|(2[0-9])|(3[0-9])|(4[0-9])|(5[0-9])))|Z))|(([0-9][0-9][0-9][0-9])-((0[1-9])|(1(0|1|2)))-((0[1-9])|(1[0-9])|(2[0-9])|(3(0|1)))T((0[0-9])|(1[0-9])|(2(0|1|2|3))):((0[0-9])|(1[0-9])|(2[0-9])|(3[0-9])|(4[0-9])|(5[0-9])):((0[0-9])|(1[0-9])|(2[0-9])|(3[0-9])|(4[0-9])|(5[0-9]))(((\+|-)((0[0-9])|(1[0-9])|(2(0|1|2|3))):((0[0-9])|(1[0-9])|(2[0-9])|(3[0-9])|(4[0-9])|(5[0-9])))|Z))|(([0-9][0-9][0-9][0-9])-((0[1-9])|(1(0|1|2)))-((0[1-9])|(1[0-9])|(2[0-9])|(3(0|1)))T((0[0-9])|(1[0-9])|(2(0|1|2|3))):((0[0-9])|(1[0-9])|(2[0-9])|(3[0-9])|(4[0-9])|(5[0-9])):(((0[0-9])|(1[0-9])|(2[0-9])|(3[0-9])|(4[0-9])|(5[0-9]))\.[0-9])(((\+|-)((0[0-9])|(1[0-9])|(2(0|1|2|3))):((0[0-9])|(1[0-9])|(2[0-9])|(3[0-9])|(4[0-9])|(5[0-9])))|Z))"
|
|
||||||
/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
</xsd:schema>
|
|
||||||
-33
@@ -1,33 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<xsd:schema xmlns="http://schemas.openxmlformats.org/package/2006/relationships"
|
|
||||||
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
|
||||||
targetNamespace="http://schemas.openxmlformats.org/package/2006/relationships"
|
|
||||||
elementFormDefault="qualified" attributeFormDefault="unqualified" blockDefault="#all">
|
|
||||||
|
|
||||||
<xsd:element name="Relationships" type="CT_Relationships"/>
|
|
||||||
<xsd:element name="Relationship" type="CT_Relationship"/>
|
|
||||||
|
|
||||||
<xsd:complexType name="CT_Relationships">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element ref="Relationship" minOccurs="0" maxOccurs="unbounded"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
|
|
||||||
<xsd:complexType name="CT_Relationship">
|
|
||||||
<xsd:simpleContent>
|
|
||||||
<xsd:extension base="xsd:string">
|
|
||||||
<xsd:attribute name="TargetMode" type="ST_TargetMode" use="optional"/>
|
|
||||||
<xsd:attribute name="Target" type="xsd:anyURI" use="required"/>
|
|
||||||
<xsd:attribute name="Type" type="xsd:anyURI" use="required"/>
|
|
||||||
<xsd:attribute name="Id" type="xsd:ID" use="required"/>
|
|
||||||
</xsd:extension>
|
|
||||||
</xsd:simpleContent>
|
|
||||||
</xsd:complexType>
|
|
||||||
|
|
||||||
<xsd:simpleType name="ST_TargetMode">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="External"/>
|
|
||||||
<xsd:enumeration value="Internal"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
</xsd:schema>
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<xsd:schema xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
|
||||||
attributeFormDefault="unqualified" elementFormDefault="qualified"
|
|
||||||
targetNamespace="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
|
||||||
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
|
||||||
|
|
||||||
<!--
|
|
||||||
This XSD is a modified version of the one found at:
|
|
||||||
https://github.com/plutext/docx4j/blob/master/xsd/mce/markup-compatibility-2006-MINIMAL.xsd
|
|
||||||
|
|
||||||
This XSD has 2 objectives:
|
|
||||||
|
|
||||||
1. round tripping @mc:Ignorable
|
|
||||||
|
|
||||||
<w:document
|
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
|
||||||
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
|
|
||||||
mc:Ignorable="w14 w15 wp14">
|
|
||||||
|
|
||||||
2. enabling AlternateContent to be manipulated in certain elements
|
|
||||||
(in the unusual case where the content model is xsd:any, it doesn't have to be explicitly added)
|
|
||||||
|
|
||||||
See further ECMA-376, 4th Edition, Office Open XML File Formats
|
|
||||||
Part 3 : Markup Compatibility and Extensibility
|
|
||||||
-->
|
|
||||||
|
|
||||||
<!-- Objective 1 -->
|
|
||||||
<xsd:attribute name="Ignorable" type="xsd:string" />
|
|
||||||
|
|
||||||
<!-- Objective 2 -->
|
|
||||||
<xsd:attribute name="MustUnderstand" type="xsd:string" />
|
|
||||||
<xsd:attribute name="ProcessContent" type="xsd:string" />
|
|
||||||
|
|
||||||
<!-- An AlternateContent element shall contain one or more Choice child elements, optionally followed by a
|
|
||||||
Fallback child element. If present, there shall be only one Fallback element, and it shall follow all Choice
|
|
||||||
elements. -->
|
|
||||||
<xsd:element name="AlternateContent">
|
|
||||||
<xsd:complexType>
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="Choice" minOccurs="0" maxOccurs="unbounded">
|
|
||||||
<xsd:complexType>
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:any minOccurs="0" maxOccurs="unbounded"
|
|
||||||
processContents="strict">
|
|
||||||
</xsd:any>
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="Requires" type="xsd:string" use="required" />
|
|
||||||
<xsd:attribute ref="mc:Ignorable" use="optional" />
|
|
||||||
<xsd:attribute ref="mc:MustUnderstand" use="optional" />
|
|
||||||
<xsd:attribute ref="mc:ProcessContent" use="optional" />
|
|
||||||
</xsd:complexType>
|
|
||||||
</xsd:element>
|
|
||||||
<xsd:element name="Fallback" minOccurs="0" maxOccurs="1">
|
|
||||||
<xsd:complexType>
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:any minOccurs="0" maxOccurs="unbounded"
|
|
||||||
processContents="strict">
|
|
||||||
</xsd:any>
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute ref="mc:Ignorable" use="optional" />
|
|
||||||
<xsd:attribute ref="mc:MustUnderstand" use="optional" />
|
|
||||||
<xsd:attribute ref="mc:ProcessContent" use="optional" />
|
|
||||||
</xsd:complexType>
|
|
||||||
</xsd:element>
|
|
||||||
</xsd:sequence>
|
|
||||||
<!-- AlternateContent elements might include the attributes Ignorable,
|
|
||||||
MustUnderstand and ProcessContent described in this Part of ECMA-376. These
|
|
||||||
attributes’ qualified names shall be prefixed when associated with an AlternateContent
|
|
||||||
element. -->
|
|
||||||
<xsd:attribute ref="mc:Ignorable" use="optional" />
|
|
||||||
<xsd:attribute ref="mc:MustUnderstand" use="optional" />
|
|
||||||
<xsd:attribute ref="mc:ProcessContent" use="optional" />
|
|
||||||
</xsd:complexType>
|
|
||||||
</xsd:element>
|
|
||||||
</xsd:schema>
|
|
||||||
@@ -1,560 +0,0 @@
|
|||||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:w12="http://schemas.openxmlformats.org/wordprocessingml/2006/main" elementFormDefault="qualified" attributeFormDefault="qualified" blockDefault="#all" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes" xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns="http://schemas.microsoft.com/office/word/2010/wordml" targetNamespace="http://schemas.microsoft.com/office/word/2010/wordml">
|
|
||||||
<!-- <xsd:import id="rel" namespace="http://schemas.openxmlformats.org/officeDocument/2006/relationships" schemaLocation="orel.xsd"/> -->
|
|
||||||
<xsd:import id="w" namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main" schemaLocation="../ISO-IEC29500-4_2016/wml.xsd"/>
|
|
||||||
<!-- <xsd:import namespace="http://schemas.openxmlformats.org/drawingml/2006/main" schemaLocation="oartbasetypes.xsd"/>
|
|
||||||
<xsd:import namespace="http://schemas.openxmlformats.org/drawingml/2006/main" schemaLocation="oartsplineproperties.xsd"/> -->
|
|
||||||
<xsd:complexType name="CT_LongHexNumber">
|
|
||||||
<xsd:attribute name="val" type="w:ST_LongHexNumber" use="required"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:simpleType name="ST_OnOff">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="true"/>
|
|
||||||
<xsd:enumeration value="false"/>
|
|
||||||
<xsd:enumeration value="0"/>
|
|
||||||
<xsd:enumeration value="1"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:complexType name="CT_OnOff">
|
|
||||||
<xsd:attribute name="val" type="ST_OnOff"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:element name="docId" type="CT_LongHexNumber"/>
|
|
||||||
<xsd:element name="conflictMode" type="CT_OnOff"/>
|
|
||||||
<xsd:attributeGroup name="AG_Parids">
|
|
||||||
<xsd:attribute name="paraId" type="w:ST_LongHexNumber"/>
|
|
||||||
<xsd:attribute name="textId" type="w:ST_LongHexNumber"/>
|
|
||||||
</xsd:attributeGroup>
|
|
||||||
<xsd:attribute name="anchorId" type="w:ST_LongHexNumber"/>
|
|
||||||
<xsd:attribute name="noSpellErr" type="ST_OnOff"/>
|
|
||||||
<xsd:element name="customXmlConflictInsRangeStart" type="w:CT_TrackChange"/>
|
|
||||||
<xsd:element name="customXmlConflictInsRangeEnd" type="w:CT_Markup"/>
|
|
||||||
<xsd:element name="customXmlConflictDelRangeStart" type="w:CT_TrackChange"/>
|
|
||||||
<xsd:element name="customXmlConflictDelRangeEnd" type="w:CT_Markup"/>
|
|
||||||
<xsd:group name="EG_RunLevelConflicts">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="conflictIns" type="w:CT_RunTrackChange" minOccurs="0"/>
|
|
||||||
<xsd:element name="conflictDel" type="w:CT_RunTrackChange" minOccurs="0"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:group>
|
|
||||||
<xsd:group name="EG_Conflicts">
|
|
||||||
<xsd:choice>
|
|
||||||
<xsd:element name="conflictIns" type="w:CT_TrackChange" minOccurs="0"/>
|
|
||||||
<xsd:element name="conflictDel" type="w:CT_TrackChange" minOccurs="0"/>
|
|
||||||
</xsd:choice>
|
|
||||||
</xsd:group>
|
|
||||||
<xsd:complexType name="CT_Percentage">
|
|
||||||
<xsd:attribute name="val" type="a:ST_Percentage" use="required"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_PositiveFixedPercentage">
|
|
||||||
<xsd:attribute name="val" type="a:ST_PositiveFixedPercentage" use="required"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_PositivePercentage">
|
|
||||||
<xsd:attribute name="val" type="a:ST_PositivePercentage" use="required"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:simpleType name="ST_SchemeColorVal">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="bg1"/>
|
|
||||||
<xsd:enumeration value="tx1"/>
|
|
||||||
<xsd:enumeration value="bg2"/>
|
|
||||||
<xsd:enumeration value="tx2"/>
|
|
||||||
<xsd:enumeration value="accent1"/>
|
|
||||||
<xsd:enumeration value="accent2"/>
|
|
||||||
<xsd:enumeration value="accent3"/>
|
|
||||||
<xsd:enumeration value="accent4"/>
|
|
||||||
<xsd:enumeration value="accent5"/>
|
|
||||||
<xsd:enumeration value="accent6"/>
|
|
||||||
<xsd:enumeration value="hlink"/>
|
|
||||||
<xsd:enumeration value="folHlink"/>
|
|
||||||
<xsd:enumeration value="dk1"/>
|
|
||||||
<xsd:enumeration value="lt1"/>
|
|
||||||
<xsd:enumeration value="dk2"/>
|
|
||||||
<xsd:enumeration value="lt2"/>
|
|
||||||
<xsd:enumeration value="phClr"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_RectAlignment">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="none"/>
|
|
||||||
<xsd:enumeration value="tl"/>
|
|
||||||
<xsd:enumeration value="t"/>
|
|
||||||
<xsd:enumeration value="tr"/>
|
|
||||||
<xsd:enumeration value="l"/>
|
|
||||||
<xsd:enumeration value="ctr"/>
|
|
||||||
<xsd:enumeration value="r"/>
|
|
||||||
<xsd:enumeration value="bl"/>
|
|
||||||
<xsd:enumeration value="b"/>
|
|
||||||
<xsd:enumeration value="br"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_PathShadeType">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="shape"/>
|
|
||||||
<xsd:enumeration value="circle"/>
|
|
||||||
<xsd:enumeration value="rect"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_LineCap">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="rnd"/>
|
|
||||||
<xsd:enumeration value="sq"/>
|
|
||||||
<xsd:enumeration value="flat"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_PresetLineDashVal">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="solid"/>
|
|
||||||
<xsd:enumeration value="dot"/>
|
|
||||||
<xsd:enumeration value="sysDot"/>
|
|
||||||
<xsd:enumeration value="dash"/>
|
|
||||||
<xsd:enumeration value="sysDash"/>
|
|
||||||
<xsd:enumeration value="lgDash"/>
|
|
||||||
<xsd:enumeration value="dashDot"/>
|
|
||||||
<xsd:enumeration value="sysDashDot"/>
|
|
||||||
<xsd:enumeration value="lgDashDot"/>
|
|
||||||
<xsd:enumeration value="lgDashDotDot"/>
|
|
||||||
<xsd:enumeration value="sysDashDotDot"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_PenAlignment">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="ctr"/>
|
|
||||||
<xsd:enumeration value="in"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_CompoundLine">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="sng"/>
|
|
||||||
<xsd:enumeration value="dbl"/>
|
|
||||||
<xsd:enumeration value="thickThin"/>
|
|
||||||
<xsd:enumeration value="thinThick"/>
|
|
||||||
<xsd:enumeration value="tri"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:complexType name="CT_RelativeRect">
|
|
||||||
<xsd:attribute name="l" use="optional" type="a:ST_Percentage"/>
|
|
||||||
<xsd:attribute name="t" use="optional" type="a:ST_Percentage"/>
|
|
||||||
<xsd:attribute name="r" use="optional" type="a:ST_Percentage"/>
|
|
||||||
<xsd:attribute name="b" use="optional" type="a:ST_Percentage"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:group name="EG_ColorTransform">
|
|
||||||
<xsd:choice>
|
|
||||||
<xsd:element name="tint" type="CT_PositiveFixedPercentage"/>
|
|
||||||
<xsd:element name="shade" type="CT_PositiveFixedPercentage"/>
|
|
||||||
<xsd:element name="alpha" type="CT_PositiveFixedPercentage"/>
|
|
||||||
<xsd:element name="hueMod" type="CT_PositivePercentage"/>
|
|
||||||
<xsd:element name="sat" type="CT_Percentage"/>
|
|
||||||
<xsd:element name="satOff" type="CT_Percentage"/>
|
|
||||||
<xsd:element name="satMod" type="CT_Percentage"/>
|
|
||||||
<xsd:element name="lum" type="CT_Percentage"/>
|
|
||||||
<xsd:element name="lumOff" type="CT_Percentage"/>
|
|
||||||
<xsd:element name="lumMod" type="CT_Percentage"/>
|
|
||||||
</xsd:choice>
|
|
||||||
</xsd:group>
|
|
||||||
<xsd:complexType name="CT_SRgbColor">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:group ref="EG_ColorTransform" minOccurs="0" maxOccurs="unbounded"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="val" type="s:ST_HexColorRGB" use="required"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_SchemeColor">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:group ref="EG_ColorTransform" minOccurs="0" maxOccurs="unbounded"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="val" type="ST_SchemeColorVal" use="required"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:group name="EG_ColorChoice">
|
|
||||||
<xsd:choice>
|
|
||||||
<xsd:element name="srgbClr" type="CT_SRgbColor"/>
|
|
||||||
<xsd:element name="schemeClr" type="CT_SchemeColor"/>
|
|
||||||
</xsd:choice>
|
|
||||||
</xsd:group>
|
|
||||||
<xsd:complexType name="CT_Color">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:group ref="EG_ColorChoice"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_GradientStop">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:group ref="EG_ColorChoice"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="pos" type="a:ST_PositiveFixedPercentage" use="required"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_GradientStopList">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="gs" type="CT_GradientStop" minOccurs="2" maxOccurs="10"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_LinearShadeProperties">
|
|
||||||
<xsd:attribute name="ang" type="a:ST_PositiveFixedAngle" use="optional"/>
|
|
||||||
<xsd:attribute name="scaled" type="ST_OnOff" use="optional"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_PathShadeProperties">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="fillToRect" type="CT_RelativeRect" minOccurs="0"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="path" type="ST_PathShadeType" use="optional"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:group name="EG_ShadeProperties">
|
|
||||||
<xsd:choice>
|
|
||||||
<xsd:element name="lin" type="CT_LinearShadeProperties"/>
|
|
||||||
<xsd:element name="path" type="CT_PathShadeProperties"/>
|
|
||||||
</xsd:choice>
|
|
||||||
</xsd:group>
|
|
||||||
<xsd:complexType name="CT_SolidColorFillProperties">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:group ref="EG_ColorChoice" minOccurs="0"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_GradientFillProperties">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="gsLst" type="CT_GradientStopList" minOccurs="0"/>
|
|
||||||
<xsd:group ref="EG_ShadeProperties" minOccurs="0"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:group name="EG_FillProperties">
|
|
||||||
<xsd:choice>
|
|
||||||
<xsd:element name="noFill" type="w:CT_Empty"/>
|
|
||||||
<xsd:element name="solidFill" type="CT_SolidColorFillProperties"/>
|
|
||||||
<xsd:element name="gradFill" type="CT_GradientFillProperties"/>
|
|
||||||
</xsd:choice>
|
|
||||||
</xsd:group>
|
|
||||||
<xsd:complexType name="CT_PresetLineDashProperties">
|
|
||||||
<xsd:attribute name="val" type="ST_PresetLineDashVal" use="optional"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:group name="EG_LineDashProperties">
|
|
||||||
<xsd:choice>
|
|
||||||
<xsd:element name="prstDash" type="CT_PresetLineDashProperties"/>
|
|
||||||
</xsd:choice>
|
|
||||||
</xsd:group>
|
|
||||||
<xsd:complexType name="CT_LineJoinMiterProperties">
|
|
||||||
<xsd:attribute name="lim" type="a:ST_PositivePercentage" use="optional"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:group name="EG_LineJoinProperties">
|
|
||||||
<xsd:choice>
|
|
||||||
<xsd:element name="round" type="w:CT_Empty"/>
|
|
||||||
<xsd:element name="bevel" type="w:CT_Empty"/>
|
|
||||||
<xsd:element name="miter" type="CT_LineJoinMiterProperties"/>
|
|
||||||
</xsd:choice>
|
|
||||||
</xsd:group>
|
|
||||||
<xsd:simpleType name="ST_PresetCameraType">
|
|
||||||
<xsd:restriction base="xsd:token">
|
|
||||||
<xsd:enumeration value="legacyObliqueTopLeft"/>
|
|
||||||
<xsd:enumeration value="legacyObliqueTop"/>
|
|
||||||
<xsd:enumeration value="legacyObliqueTopRight"/>
|
|
||||||
<xsd:enumeration value="legacyObliqueLeft"/>
|
|
||||||
<xsd:enumeration value="legacyObliqueFront"/>
|
|
||||||
<xsd:enumeration value="legacyObliqueRight"/>
|
|
||||||
<xsd:enumeration value="legacyObliqueBottomLeft"/>
|
|
||||||
<xsd:enumeration value="legacyObliqueBottom"/>
|
|
||||||
<xsd:enumeration value="legacyObliqueBottomRight"/>
|
|
||||||
<xsd:enumeration value="legacyPerspectiveTopLeft"/>
|
|
||||||
<xsd:enumeration value="legacyPerspectiveTop"/>
|
|
||||||
<xsd:enumeration value="legacyPerspectiveTopRight"/>
|
|
||||||
<xsd:enumeration value="legacyPerspectiveLeft"/>
|
|
||||||
<xsd:enumeration value="legacyPerspectiveFront"/>
|
|
||||||
<xsd:enumeration value="legacyPerspectiveRight"/>
|
|
||||||
<xsd:enumeration value="legacyPerspectiveBottomLeft"/>
|
|
||||||
<xsd:enumeration value="legacyPerspectiveBottom"/>
|
|
||||||
<xsd:enumeration value="legacyPerspectiveBottomRight"/>
|
|
||||||
<xsd:enumeration value="orthographicFront"/>
|
|
||||||
<xsd:enumeration value="isometricTopUp"/>
|
|
||||||
<xsd:enumeration value="isometricTopDown"/>
|
|
||||||
<xsd:enumeration value="isometricBottomUp"/>
|
|
||||||
<xsd:enumeration value="isometricBottomDown"/>
|
|
||||||
<xsd:enumeration value="isometricLeftUp"/>
|
|
||||||
<xsd:enumeration value="isometricLeftDown"/>
|
|
||||||
<xsd:enumeration value="isometricRightUp"/>
|
|
||||||
<xsd:enumeration value="isometricRightDown"/>
|
|
||||||
<xsd:enumeration value="isometricOffAxis1Left"/>
|
|
||||||
<xsd:enumeration value="isometricOffAxis1Right"/>
|
|
||||||
<xsd:enumeration value="isometricOffAxis1Top"/>
|
|
||||||
<xsd:enumeration value="isometricOffAxis2Left"/>
|
|
||||||
<xsd:enumeration value="isometricOffAxis2Right"/>
|
|
||||||
<xsd:enumeration value="isometricOffAxis2Top"/>
|
|
||||||
<xsd:enumeration value="isometricOffAxis3Left"/>
|
|
||||||
<xsd:enumeration value="isometricOffAxis3Right"/>
|
|
||||||
<xsd:enumeration value="isometricOffAxis3Bottom"/>
|
|
||||||
<xsd:enumeration value="isometricOffAxis4Left"/>
|
|
||||||
<xsd:enumeration value="isometricOffAxis4Right"/>
|
|
||||||
<xsd:enumeration value="isometricOffAxis4Bottom"/>
|
|
||||||
<xsd:enumeration value="obliqueTopLeft"/>
|
|
||||||
<xsd:enumeration value="obliqueTop"/>
|
|
||||||
<xsd:enumeration value="obliqueTopRight"/>
|
|
||||||
<xsd:enumeration value="obliqueLeft"/>
|
|
||||||
<xsd:enumeration value="obliqueRight"/>
|
|
||||||
<xsd:enumeration value="obliqueBottomLeft"/>
|
|
||||||
<xsd:enumeration value="obliqueBottom"/>
|
|
||||||
<xsd:enumeration value="obliqueBottomRight"/>
|
|
||||||
<xsd:enumeration value="perspectiveFront"/>
|
|
||||||
<xsd:enumeration value="perspectiveLeft"/>
|
|
||||||
<xsd:enumeration value="perspectiveRight"/>
|
|
||||||
<xsd:enumeration value="perspectiveAbove"/>
|
|
||||||
<xsd:enumeration value="perspectiveBelow"/>
|
|
||||||
<xsd:enumeration value="perspectiveAboveLeftFacing"/>
|
|
||||||
<xsd:enumeration value="perspectiveAboveRightFacing"/>
|
|
||||||
<xsd:enumeration value="perspectiveContrastingLeftFacing"/>
|
|
||||||
<xsd:enumeration value="perspectiveContrastingRightFacing"/>
|
|
||||||
<xsd:enumeration value="perspectiveHeroicLeftFacing"/>
|
|
||||||
<xsd:enumeration value="perspectiveHeroicRightFacing"/>
|
|
||||||
<xsd:enumeration value="perspectiveHeroicExtremeLeftFacing"/>
|
|
||||||
<xsd:enumeration value="perspectiveHeroicExtremeRightFacing"/>
|
|
||||||
<xsd:enumeration value="perspectiveRelaxed"/>
|
|
||||||
<xsd:enumeration value="perspectiveRelaxedModerately"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:complexType name="CT_Camera">
|
|
||||||
<xsd:attribute name="prst" use="required" type="ST_PresetCameraType"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_SphereCoords">
|
|
||||||
<xsd:attribute name="lat" type="a:ST_PositiveFixedAngle" use="required"/>
|
|
||||||
<xsd:attribute name="lon" type="a:ST_PositiveFixedAngle" use="required"/>
|
|
||||||
<xsd:attribute name="rev" type="a:ST_PositiveFixedAngle" use="required"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:simpleType name="ST_LightRigType">
|
|
||||||
<xsd:restriction base="xsd:token">
|
|
||||||
<xsd:enumeration value="legacyFlat1"/>
|
|
||||||
<xsd:enumeration value="legacyFlat2"/>
|
|
||||||
<xsd:enumeration value="legacyFlat3"/>
|
|
||||||
<xsd:enumeration value="legacyFlat4"/>
|
|
||||||
<xsd:enumeration value="legacyNormal1"/>
|
|
||||||
<xsd:enumeration value="legacyNormal2"/>
|
|
||||||
<xsd:enumeration value="legacyNormal3"/>
|
|
||||||
<xsd:enumeration value="legacyNormal4"/>
|
|
||||||
<xsd:enumeration value="legacyHarsh1"/>
|
|
||||||
<xsd:enumeration value="legacyHarsh2"/>
|
|
||||||
<xsd:enumeration value="legacyHarsh3"/>
|
|
||||||
<xsd:enumeration value="legacyHarsh4"/>
|
|
||||||
<xsd:enumeration value="threePt"/>
|
|
||||||
<xsd:enumeration value="balanced"/>
|
|
||||||
<xsd:enumeration value="soft"/>
|
|
||||||
<xsd:enumeration value="harsh"/>
|
|
||||||
<xsd:enumeration value="flood"/>
|
|
||||||
<xsd:enumeration value="contrasting"/>
|
|
||||||
<xsd:enumeration value="morning"/>
|
|
||||||
<xsd:enumeration value="sunrise"/>
|
|
||||||
<xsd:enumeration value="sunset"/>
|
|
||||||
<xsd:enumeration value="chilly"/>
|
|
||||||
<xsd:enumeration value="freezing"/>
|
|
||||||
<xsd:enumeration value="flat"/>
|
|
||||||
<xsd:enumeration value="twoPt"/>
|
|
||||||
<xsd:enumeration value="glow"/>
|
|
||||||
<xsd:enumeration value="brightRoom"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:simpleType name="ST_LightRigDirection">
|
|
||||||
<xsd:restriction base="xsd:token">
|
|
||||||
<xsd:enumeration value="tl"/>
|
|
||||||
<xsd:enumeration value="t"/>
|
|
||||||
<xsd:enumeration value="tr"/>
|
|
||||||
<xsd:enumeration value="l"/>
|
|
||||||
<xsd:enumeration value="r"/>
|
|
||||||
<xsd:enumeration value="bl"/>
|
|
||||||
<xsd:enumeration value="b"/>
|
|
||||||
<xsd:enumeration value="br"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:complexType name="CT_LightRig">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="rot" type="CT_SphereCoords" minOccurs="0"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="rig" type="ST_LightRigType" use="required"/>
|
|
||||||
<xsd:attribute name="dir" type="ST_LightRigDirection" use="required"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:simpleType name="ST_BevelPresetType">
|
|
||||||
<xsd:restriction base="xsd:token">
|
|
||||||
<xsd:enumeration value="relaxedInset"/>
|
|
||||||
<xsd:enumeration value="circle"/>
|
|
||||||
<xsd:enumeration value="slope"/>
|
|
||||||
<xsd:enumeration value="cross"/>
|
|
||||||
<xsd:enumeration value="angle"/>
|
|
||||||
<xsd:enumeration value="softRound"/>
|
|
||||||
<xsd:enumeration value="convex"/>
|
|
||||||
<xsd:enumeration value="coolSlant"/>
|
|
||||||
<xsd:enumeration value="divot"/>
|
|
||||||
<xsd:enumeration value="riblet"/>
|
|
||||||
<xsd:enumeration value="hardEdge"/>
|
|
||||||
<xsd:enumeration value="artDeco"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:complexType name="CT_Bevel">
|
|
||||||
<xsd:attribute name="w" type="a:ST_PositiveCoordinate" use="optional"/>
|
|
||||||
<xsd:attribute name="h" type="a:ST_PositiveCoordinate" use="optional"/>
|
|
||||||
<xsd:attribute name="prst" type="ST_BevelPresetType" use="optional"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:simpleType name="ST_PresetMaterialType">
|
|
||||||
<xsd:restriction base="xsd:token">
|
|
||||||
<xsd:enumeration value="legacyMatte"/>
|
|
||||||
<xsd:enumeration value="legacyPlastic"/>
|
|
||||||
<xsd:enumeration value="legacyMetal"/>
|
|
||||||
<xsd:enumeration value="legacyWireframe"/>
|
|
||||||
<xsd:enumeration value="matte"/>
|
|
||||||
<xsd:enumeration value="plastic"/>
|
|
||||||
<xsd:enumeration value="metal"/>
|
|
||||||
<xsd:enumeration value="warmMatte"/>
|
|
||||||
<xsd:enumeration value="translucentPowder"/>
|
|
||||||
<xsd:enumeration value="powder"/>
|
|
||||||
<xsd:enumeration value="dkEdge"/>
|
|
||||||
<xsd:enumeration value="softEdge"/>
|
|
||||||
<xsd:enumeration value="clear"/>
|
|
||||||
<xsd:enumeration value="flat"/>
|
|
||||||
<xsd:enumeration value="softmetal"/>
|
|
||||||
<xsd:enumeration value="none"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:complexType name="CT_Glow">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:group ref="EG_ColorChoice"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="rad" use="optional" type="a:ST_PositiveCoordinate"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_Shadow">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:group ref="EG_ColorChoice"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="blurRad" use="optional" type="a:ST_PositiveCoordinate"/>
|
|
||||||
<xsd:attribute name="dist" use="optional" type="a:ST_PositiveCoordinate"/>
|
|
||||||
<xsd:attribute name="dir" use="optional" type="a:ST_PositiveFixedAngle"/>
|
|
||||||
<xsd:attribute name="sx" use="optional" type="a:ST_Percentage"/>
|
|
||||||
<xsd:attribute name="sy" use="optional" type="a:ST_Percentage"/>
|
|
||||||
<xsd:attribute name="kx" use="optional" type="a:ST_FixedAngle"/>
|
|
||||||
<xsd:attribute name="ky" use="optional" type="a:ST_FixedAngle"/>
|
|
||||||
<xsd:attribute name="algn" use="optional" type="ST_RectAlignment"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_Reflection">
|
|
||||||
<xsd:attribute name="blurRad" use="optional" type="a:ST_PositiveCoordinate"/>
|
|
||||||
<xsd:attribute name="stA" use="optional" type="a:ST_PositiveFixedPercentage"/>
|
|
||||||
<xsd:attribute name="stPos" use="optional" type="a:ST_PositiveFixedPercentage"/>
|
|
||||||
<xsd:attribute name="endA" use="optional" type="a:ST_PositiveFixedPercentage"/>
|
|
||||||
<xsd:attribute name="endPos" use="optional" type="a:ST_PositiveFixedPercentage"/>
|
|
||||||
<xsd:attribute name="dist" use="optional" type="a:ST_PositiveCoordinate"/>
|
|
||||||
<xsd:attribute name="dir" use="optional" type="a:ST_PositiveFixedAngle"/>
|
|
||||||
<xsd:attribute name="fadeDir" use="optional" type="a:ST_PositiveFixedAngle"/>
|
|
||||||
<xsd:attribute name="sx" use="optional" type="a:ST_Percentage"/>
|
|
||||||
<xsd:attribute name="sy" use="optional" type="a:ST_Percentage"/>
|
|
||||||
<xsd:attribute name="kx" use="optional" type="a:ST_FixedAngle"/>
|
|
||||||
<xsd:attribute name="ky" use="optional" type="a:ST_FixedAngle"/>
|
|
||||||
<xsd:attribute name="algn" use="optional" type="ST_RectAlignment"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_FillTextEffect">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:group ref="EG_FillProperties" minOccurs="0"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_TextOutlineEffect">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:group ref="EG_FillProperties" minOccurs="0"/>
|
|
||||||
<xsd:group ref="EG_LineDashProperties" minOccurs="0"/>
|
|
||||||
<xsd:group ref="EG_LineJoinProperties" minOccurs="0"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="w" use="optional" type="a:ST_LineWidth"/>
|
|
||||||
<xsd:attribute name="cap" use="optional" type="ST_LineCap"/>
|
|
||||||
<xsd:attribute name="cmpd" use="optional" type="ST_CompoundLine"/>
|
|
||||||
<xsd:attribute name="algn" use="optional" type="ST_PenAlignment"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_Scene3D">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="camera" type="CT_Camera"/>
|
|
||||||
<xsd:element name="lightRig" type="CT_LightRig"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_Props3D">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="bevelT" type="CT_Bevel" minOccurs="0"/>
|
|
||||||
<xsd:element name="bevelB" type="CT_Bevel" minOccurs="0"/>
|
|
||||||
<xsd:element name="extrusionClr" type="CT_Color" minOccurs="0"/>
|
|
||||||
<xsd:element name="contourClr" type="CT_Color" minOccurs="0"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="extrusionH" type="a:ST_PositiveCoordinate" use="optional"/>
|
|
||||||
<xsd:attribute name="contourW" type="a:ST_PositiveCoordinate" use="optional"/>
|
|
||||||
<xsd:attribute name="prstMaterial" type="ST_PresetMaterialType" use="optional"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:group name="EG_RPrTextEffects">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="glow" minOccurs="0" type="CT_Glow"/>
|
|
||||||
<xsd:element name="shadow" minOccurs="0" type="CT_Shadow"/>
|
|
||||||
<xsd:element name="reflection" minOccurs="0" type="CT_Reflection"/>
|
|
||||||
<xsd:element name="textOutline" minOccurs="0" type="CT_TextOutlineEffect"/>
|
|
||||||
<xsd:element name="textFill" minOccurs="0" type="CT_FillTextEffect"/>
|
|
||||||
<xsd:element name="scene3d" minOccurs="0" type="CT_Scene3D"/>
|
|
||||||
<xsd:element name="props3d" minOccurs="0" type="CT_Props3D"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:group>
|
|
||||||
<xsd:simpleType name="ST_Ligatures">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="none"/>
|
|
||||||
<xsd:enumeration value="standard"/>
|
|
||||||
<xsd:enumeration value="contextual"/>
|
|
||||||
<xsd:enumeration value="historical"/>
|
|
||||||
<xsd:enumeration value="discretional"/>
|
|
||||||
<xsd:enumeration value="standardContextual"/>
|
|
||||||
<xsd:enumeration value="standardHistorical"/>
|
|
||||||
<xsd:enumeration value="contextualHistorical"/>
|
|
||||||
<xsd:enumeration value="standardDiscretional"/>
|
|
||||||
<xsd:enumeration value="contextualDiscretional"/>
|
|
||||||
<xsd:enumeration value="historicalDiscretional"/>
|
|
||||||
<xsd:enumeration value="standardContextualHistorical"/>
|
|
||||||
<xsd:enumeration value="standardContextualDiscretional"/>
|
|
||||||
<xsd:enumeration value="standardHistoricalDiscretional"/>
|
|
||||||
<xsd:enumeration value="contextualHistoricalDiscretional"/>
|
|
||||||
<xsd:enumeration value="all"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:complexType name="CT_Ligatures">
|
|
||||||
<xsd:attribute name="val" type="ST_Ligatures" use="required"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:simpleType name="ST_NumForm">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="default"/>
|
|
||||||
<xsd:enumeration value="lining"/>
|
|
||||||
<xsd:enumeration value="oldStyle"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:complexType name="CT_NumForm">
|
|
||||||
<xsd:attribute name="val" type="ST_NumForm" use="required"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:simpleType name="ST_NumSpacing">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="default"/>
|
|
||||||
<xsd:enumeration value="proportional"/>
|
|
||||||
<xsd:enumeration value="tabular"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:complexType name="CT_NumSpacing">
|
|
||||||
<xsd:attribute name="val" type="ST_NumSpacing" use="required"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_StyleSet">
|
|
||||||
<xsd:attribute name="id" type="s:ST_UnsignedDecimalNumber" use="required"/>
|
|
||||||
<xsd:attribute name="val" type="ST_OnOff" use="optional"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_StylisticSets">
|
|
||||||
<xsd:sequence minOccurs="0">
|
|
||||||
<xsd:element name="styleSet" minOccurs="0" maxOccurs="unbounded" type="CT_StyleSet"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:group name="EG_RPrOpenType">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="ligatures" minOccurs="0" type="CT_Ligatures"/>
|
|
||||||
<xsd:element name="numForm" minOccurs="0" type="CT_NumForm"/>
|
|
||||||
<xsd:element name="numSpacing" minOccurs="0" type="CT_NumSpacing"/>
|
|
||||||
<xsd:element name="stylisticSets" minOccurs="0" type="CT_StylisticSets"/>
|
|
||||||
<xsd:element name="cntxtAlts" minOccurs="0" type="CT_OnOff"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:group>
|
|
||||||
<xsd:element name="discardImageEditingData" type="CT_OnOff"/>
|
|
||||||
<xsd:element name="defaultImageDpi" type="CT_DefaultImageDpi"/>
|
|
||||||
<xsd:complexType name="CT_DefaultImageDpi">
|
|
||||||
<xsd:attribute name="val" type="w:ST_DecimalNumber" use="required"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:element name="entityPicker" type="w:CT_Empty"/>
|
|
||||||
<xsd:complexType name="CT_SdtCheckboxSymbol">
|
|
||||||
<xsd:attribute name="font" type="s:ST_String"/>
|
|
||||||
<xsd:attribute name="val" type="w:ST_ShortHexNumber"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_SdtCheckbox">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="checked" type="CT_OnOff" minOccurs="0"/>
|
|
||||||
<xsd:element name="checkedState" type="CT_SdtCheckboxSymbol" minOccurs="0"/>
|
|
||||||
<xsd:element name="uncheckedState" type="CT_SdtCheckboxSymbol" minOccurs="0"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:element name="checkbox" type="CT_SdtCheckbox"/>
|
|
||||||
</xsd:schema>
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:w12="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes" elementFormDefault="qualified" attributeFormDefault="qualified" blockDefault="#all" xmlns="http://schemas.microsoft.com/office/word/2012/wordml" targetNamespace="http://schemas.microsoft.com/office/word/2012/wordml">
|
|
||||||
<xsd:import id="w12" namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main" schemaLocation="../ISO-IEC29500-4_2016/wml.xsd"/>
|
|
||||||
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes" schemaLocation="../ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd"/>
|
|
||||||
<xsd:element name="color" type="w12:CT_Color"/>
|
|
||||||
<xsd:simpleType name="ST_SdtAppearance">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="boundingBox"/>
|
|
||||||
<xsd:enumeration value="tags"/>
|
|
||||||
<xsd:enumeration value="hidden"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:element name="dataBinding" type="w12:CT_DataBinding"/>
|
|
||||||
<xsd:complexType name="CT_SdtAppearance">
|
|
||||||
<xsd:attribute name="val" type="ST_SdtAppearance"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:element name="appearance" type="CT_SdtAppearance"/>
|
|
||||||
<xsd:complexType name="CT_CommentsEx">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="commentEx" type="CT_CommentEx" minOccurs="0" maxOccurs="unbounded"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_CommentEx">
|
|
||||||
<xsd:attribute name="paraId" type="w12:ST_LongHexNumber" use="required"/>
|
|
||||||
<xsd:attribute name="paraIdParent" type="w12:ST_LongHexNumber" use="optional"/>
|
|
||||||
<xsd:attribute name="done" type="s:ST_OnOff" use="optional"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:element name="commentsEx" type="CT_CommentsEx"/>
|
|
||||||
<xsd:complexType name="CT_People">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="person" type="CT_Person" minOccurs="0" maxOccurs="unbounded"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_PresenceInfo">
|
|
||||||
<xsd:attribute name="providerId" type="xsd:string" use="required"/>
|
|
||||||
<xsd:attribute name="userId" type="xsd:string" use="required"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_Person">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="presenceInfo" type="CT_PresenceInfo" minOccurs="0" maxOccurs="1"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="author" type="s:ST_String" use="required"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:element name="people" type="CT_People"/>
|
|
||||||
<xsd:complexType name="CT_SdtRepeatedSection">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="sectionTitle" type="w12:CT_String" minOccurs="0"/>
|
|
||||||
<xsd:element name="doNotAllowInsertDeleteSection" type="w12:CT_OnOff" minOccurs="0"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:simpleType name="ST_Guid">
|
|
||||||
<xsd:restriction base="xsd:token">
|
|
||||||
<xsd:pattern value="\{[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}\}"/>
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
<xsd:complexType name="CT_Guid">
|
|
||||||
<xsd:attribute name="val" type="ST_Guid"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:element name="repeatingSection" type="CT_SdtRepeatedSection"/>
|
|
||||||
<xsd:element name="repeatingSectionItem" type="w12:CT_Empty"/>
|
|
||||||
<xsd:element name="chartTrackingRefBased" type="w12:CT_OnOff"/>
|
|
||||||
<xsd:element name="collapsed" type="w12:CT_OnOff"/>
|
|
||||||
<xsd:element name="docId" type="CT_Guid"/>
|
|
||||||
<xsd:element name="footnoteColumns" type="w12:CT_DecimalNumber"/>
|
|
||||||
<xsd:element name="webExtensionLinked" type="w12:CT_OnOff"/>
|
|
||||||
<xsd:element name="webExtensionCreated" type="w12:CT_OnOff"/>
|
|
||||||
<xsd:attribute name="restartNumberingAfterBreak" type="s:ST_OnOff"/>
|
|
||||||
</xsd:schema>
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:w12="http://schemas.openxmlformats.org/wordprocessingml/2006/main" elementFormDefault="qualified" attributeFormDefault="qualified" blockDefault="#all" xmlns="http://schemas.microsoft.com/office/word/2018/wordml" targetNamespace="http://schemas.microsoft.com/office/word/2018/wordml">
|
|
||||||
<xsd:import id="w12" namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main" schemaLocation="../ISO-IEC29500-4_2016/wml.xsd"/>
|
|
||||||
<xsd:complexType name="CT_Extension">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:any processContents="lax"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="uri" type="xsd:token"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_ExtensionList">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="ext" type="CT_Extension" minOccurs="0" maxOccurs="unbounded"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
</xsd:schema>
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes" xmlns:w16="http://schemas.microsoft.com/office/word/2018/wordml" elementFormDefault="qualified" attributeFormDefault="qualified" blockDefault="#all" xmlns="http://schemas.microsoft.com/office/word/2018/wordml/cex" targetNamespace="http://schemas.microsoft.com/office/word/2018/wordml/cex">
|
|
||||||
<xsd:import id="w16" namespace="http://schemas.microsoft.com/office/word/2018/wordml" schemaLocation="wml-2018.xsd"/>
|
|
||||||
<xsd:import id="w" namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main" schemaLocation="../ISO-IEC29500-4_2016/wml.xsd"/>
|
|
||||||
<xsd:import id="s" namespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes" schemaLocation="../ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd"/>
|
|
||||||
<xsd:complexType name="CT_CommentsExtensible">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="commentExtensible" type="CT_CommentExtensible" minOccurs="0" maxOccurs="unbounded"/>
|
|
||||||
<xsd:element name="extLst" type="w16:CT_ExtensionList" minOccurs="0" maxOccurs="1"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_CommentExtensible">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="extLst" type="w16:CT_ExtensionList" minOccurs="0" maxOccurs="1"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="durableId" type="w:ST_LongHexNumber" use="required"/>
|
|
||||||
<xsd:attribute name="dateUtc" type="w:ST_DateTime" use="optional"/>
|
|
||||||
<xsd:attribute name="intelligentPlaceholder" type="s:ST_OnOff" use="optional"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:element name="commentsExtensible" type="CT_CommentsExtensible"/>
|
|
||||||
</xsd:schema>
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:w12="http://schemas.openxmlformats.org/wordprocessingml/2006/main" elementFormDefault="qualified" attributeFormDefault="qualified" blockDefault="#all" xmlns="http://schemas.microsoft.com/office/word/2016/wordml/cid" targetNamespace="http://schemas.microsoft.com/office/word/2016/wordml/cid">
|
|
||||||
<xsd:import id="w12" namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main" schemaLocation="../ISO-IEC29500-4_2016/wml.xsd"/>
|
|
||||||
<xsd:complexType name="CT_CommentsIds">
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="commentId" type="CT_CommentId" minOccurs="0" maxOccurs="unbounded"/>
|
|
||||||
</xsd:sequence>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:complexType name="CT_CommentId">
|
|
||||||
<xsd:attribute name="paraId" type="w12:ST_LongHexNumber" use="required"/>
|
|
||||||
<xsd:attribute name="durableId" type="w12:ST_LongHexNumber" use="required"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:element name="commentsIds" type="CT_CommentsIds"/>
|
|
||||||
</xsd:schema>
|
|
||||||
-4
@@ -1,4 +0,0 @@
|
|||||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:w12="http://schemas.openxmlformats.org/wordprocessingml/2006/main" elementFormDefault="qualified" attributeFormDefault="qualified" blockDefault="#all" xmlns="http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash" targetNamespace="http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash">
|
|
||||||
<xsd:import id="w12" namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main" schemaLocation="../ISO-IEC29500-4_2016/wml.xsd"/>
|
|
||||||
<xsd:attribute name="storeItemChecksum" type="w12:ST_String"/>
|
|
||||||
</xsd:schema>
|
|
||||||
-8
@@ -1,8 +0,0 @@
|
|||||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:w12="http://schemas.openxmlformats.org/wordprocessingml/2006/main" elementFormDefault="qualified" attributeFormDefault="qualified" blockDefault="#all" xmlns="http://schemas.microsoft.com/office/word/2015/wordml/symex" targetNamespace="http://schemas.microsoft.com/office/word/2015/wordml/symex">
|
|
||||||
<xsd:import id="w12" namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main" schemaLocation="../ISO-IEC29500-4_2016/wml.xsd"/>
|
|
||||||
<xsd:complexType name="CT_SymEx">
|
|
||||||
<xsd:attribute name="font" type="w12:ST_String"/>
|
|
||||||
<xsd:attribute name="char" type="w12:ST_LongHexNumber"/>
|
|
||||||
</xsd:complexType>
|
|
||||||
<xsd:element name="symEx" type="CT_SymEx"/>
|
|
||||||
</xsd:schema>
|
|
||||||
@@ -1,159 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Tool to pack a directory into a .docx, .pptx, or .xlsx file with XML formatting undone.
|
|
||||||
|
|
||||||
Example usage:
|
|
||||||
python pack.py <input_directory> <office_file> [--force]
|
|
||||||
"""
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import shutil
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import tempfile
|
|
||||||
import defusedxml.minidom
|
|
||||||
import zipfile
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
parser = argparse.ArgumentParser(description="Pack a directory into an Office file")
|
|
||||||
parser.add_argument("input_directory", help="Unpacked Office document directory")
|
|
||||||
parser.add_argument("output_file", help="Output Office file (.docx/.pptx/.xlsx)")
|
|
||||||
parser.add_argument("--force", action="store_true", help="Skip validation")
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
try:
|
|
||||||
success = pack_document(
|
|
||||||
args.input_directory, args.output_file, validate=not args.force
|
|
||||||
)
|
|
||||||
|
|
||||||
# Show warning if validation was skipped
|
|
||||||
if args.force:
|
|
||||||
print("Warning: Skipped validation, file may be corrupt", file=sys.stderr)
|
|
||||||
# Exit with error if validation failed
|
|
||||||
elif not success:
|
|
||||||
print("Contents would produce a corrupt file.", file=sys.stderr)
|
|
||||||
print("Please validate XML before repacking.", file=sys.stderr)
|
|
||||||
print("Use --force to skip validation and pack anyway.", file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
except ValueError as e:
|
|
||||||
sys.exit(f"Error: {e}")
|
|
||||||
|
|
||||||
|
|
||||||
def pack_document(input_dir, output_file, validate=False):
|
|
||||||
"""Pack a directory into an Office file (.docx/.pptx/.xlsx).
|
|
||||||
|
|
||||||
Args:
|
|
||||||
input_dir: Path to unpacked Office document directory
|
|
||||||
output_file: Path to output Office file
|
|
||||||
validate: If True, validates with soffice (default: False)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
bool: True if successful, False if validation failed
|
|
||||||
"""
|
|
||||||
input_dir = Path(input_dir)
|
|
||||||
output_file = Path(output_file)
|
|
||||||
|
|
||||||
if not input_dir.is_dir():
|
|
||||||
raise ValueError(f"{input_dir} is not a directory")
|
|
||||||
if output_file.suffix.lower() not in {".docx", ".pptx", ".xlsx"}:
|
|
||||||
raise ValueError(f"{output_file} must be a .docx, .pptx, or .xlsx file")
|
|
||||||
|
|
||||||
# Work in temporary directory to avoid modifying original
|
|
||||||
with tempfile.TemporaryDirectory() as temp_dir:
|
|
||||||
temp_content_dir = Path(temp_dir) / "content"
|
|
||||||
shutil.copytree(input_dir, temp_content_dir)
|
|
||||||
|
|
||||||
# Process XML files to remove pretty-printing whitespace
|
|
||||||
for pattern in ["*.xml", "*.rels"]:
|
|
||||||
for xml_file in temp_content_dir.rglob(pattern):
|
|
||||||
condense_xml(xml_file)
|
|
||||||
|
|
||||||
# Create final Office file as zip archive
|
|
||||||
output_file.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
with zipfile.ZipFile(output_file, "w", zipfile.ZIP_DEFLATED) as zf:
|
|
||||||
for f in temp_content_dir.rglob("*"):
|
|
||||||
if f.is_file():
|
|
||||||
zf.write(f, f.relative_to(temp_content_dir))
|
|
||||||
|
|
||||||
# Validate if requested
|
|
||||||
if validate:
|
|
||||||
if not validate_document(output_file):
|
|
||||||
output_file.unlink() # Delete the corrupt file
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def validate_document(doc_path):
|
|
||||||
"""Validate document by converting to HTML with soffice."""
|
|
||||||
# Determine the correct filter based on file extension
|
|
||||||
match doc_path.suffix.lower():
|
|
||||||
case ".docx":
|
|
||||||
filter_name = "html:HTML"
|
|
||||||
case ".pptx":
|
|
||||||
filter_name = "html:impress_html_Export"
|
|
||||||
case ".xlsx":
|
|
||||||
filter_name = "html:HTML (StarCalc)"
|
|
||||||
|
|
||||||
with tempfile.TemporaryDirectory() as temp_dir:
|
|
||||||
try:
|
|
||||||
result = subprocess.run(
|
|
||||||
[
|
|
||||||
"soffice",
|
|
||||||
"--headless",
|
|
||||||
"--convert-to",
|
|
||||||
filter_name,
|
|
||||||
"--outdir",
|
|
||||||
temp_dir,
|
|
||||||
str(doc_path),
|
|
||||||
],
|
|
||||||
capture_output=True,
|
|
||||||
timeout=10,
|
|
||||||
text=True,
|
|
||||||
)
|
|
||||||
if not (Path(temp_dir) / f"{doc_path.stem}.html").exists():
|
|
||||||
error_msg = result.stderr.strip() or "Document validation failed"
|
|
||||||
print(f"Validation error: {error_msg}", file=sys.stderr)
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
except FileNotFoundError:
|
|
||||||
print("Warning: soffice not found. Skipping validation.", file=sys.stderr)
|
|
||||||
return True
|
|
||||||
except subprocess.TimeoutExpired:
|
|
||||||
print("Validation error: Timeout during conversion", file=sys.stderr)
|
|
||||||
return False
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Validation error: {e}", file=sys.stderr)
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def condense_xml(xml_file):
|
|
||||||
"""Strip unnecessary whitespace and remove comments."""
|
|
||||||
with open(xml_file, "r", encoding="utf-8") as f:
|
|
||||||
dom = defusedxml.minidom.parse(f)
|
|
||||||
|
|
||||||
# Process each element to remove whitespace and comments
|
|
||||||
for element in dom.getElementsByTagName("*"):
|
|
||||||
# Skip w:t elements and their processing
|
|
||||||
if element.tagName.endswith(":t"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Remove whitespace-only text nodes and comment nodes
|
|
||||||
for child in list(element.childNodes):
|
|
||||||
if (
|
|
||||||
child.nodeType == child.TEXT_NODE
|
|
||||||
and child.nodeValue
|
|
||||||
and child.nodeValue.strip() == ""
|
|
||||||
) or child.nodeType == child.COMMENT_NODE:
|
|
||||||
element.removeChild(child)
|
|
||||||
|
|
||||||
# Write back the condensed XML
|
|
||||||
with open(xml_file, "wb") as f:
|
|
||||||
f.write(dom.toxml(encoding="UTF-8"))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""Unpack and format XML contents of Office files (.docx, .pptx, .xlsx)"""
|
|
||||||
|
|
||||||
import random
|
|
||||||
import sys
|
|
||||||
import defusedxml.minidom
|
|
||||||
import zipfile
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
# Get command line arguments
|
|
||||||
assert len(sys.argv) == 3, "Usage: python unpack.py <office_file> <output_dir>"
|
|
||||||
input_file, output_dir = sys.argv[1], sys.argv[2]
|
|
||||||
|
|
||||||
# Extract and format
|
|
||||||
output_path = Path(output_dir)
|
|
||||||
output_path.mkdir(parents=True, exist_ok=True)
|
|
||||||
zipfile.ZipFile(input_file).extractall(output_path)
|
|
||||||
|
|
||||||
# Pretty print all XML files
|
|
||||||
xml_files = list(output_path.rglob("*.xml")) + list(output_path.rglob("*.rels"))
|
|
||||||
for xml_file in xml_files:
|
|
||||||
content = xml_file.read_text(encoding="utf-8")
|
|
||||||
dom = defusedxml.minidom.parseString(content)
|
|
||||||
xml_file.write_bytes(dom.toprettyxml(indent=" ", encoding="ascii"))
|
|
||||||
|
|
||||||
# For .docx files, suggest an RSID for tracked changes
|
|
||||||
if input_file.endswith(".docx"):
|
|
||||||
suggested_rsid = "".join(random.choices("0123456789ABCDEF", k=8))
|
|
||||||
print(f"Suggested RSID for edit session: {suggested_rsid}")
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Command line tool to validate Office document XML files against XSD schemas and tracked changes.
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
python validate.py <dir> --original <original_file>
|
|
||||||
"""
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from validation import DOCXSchemaValidator, PPTXSchemaValidator, RedliningValidator
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
parser = argparse.ArgumentParser(description="Validate Office document XML files")
|
|
||||||
parser.add_argument(
|
|
||||||
"unpacked_dir",
|
|
||||||
help="Path to unpacked Office document directory",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--original",
|
|
||||||
required=True,
|
|
||||||
help="Path to original file (.docx/.pptx/.xlsx)",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-v",
|
|
||||||
"--verbose",
|
|
||||||
action="store_true",
|
|
||||||
help="Enable verbose output",
|
|
||||||
)
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
# Validate paths
|
|
||||||
unpacked_dir = Path(args.unpacked_dir)
|
|
||||||
original_file = Path(args.original)
|
|
||||||
file_extension = original_file.suffix.lower()
|
|
||||||
assert unpacked_dir.is_dir(), f"Error: {unpacked_dir} is not a directory"
|
|
||||||
assert original_file.is_file(), f"Error: {original_file} is not a file"
|
|
||||||
assert file_extension in [".docx", ".pptx", ".xlsx"], (
|
|
||||||
f"Error: {original_file} must be a .docx, .pptx, or .xlsx file"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Run validations
|
|
||||||
match file_extension:
|
|
||||||
case ".docx":
|
|
||||||
validators = [DOCXSchemaValidator, RedliningValidator]
|
|
||||||
case ".pptx":
|
|
||||||
validators = [PPTXSchemaValidator]
|
|
||||||
case _:
|
|
||||||
print(f"Error: Validation not supported for file type {file_extension}")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# Run validators
|
|
||||||
success = True
|
|
||||||
for V in validators:
|
|
||||||
validator = V(unpacked_dir, original_file, verbose=args.verbose)
|
|
||||||
if not validator.validate():
|
|
||||||
success = False
|
|
||||||
|
|
||||||
if success:
|
|
||||||
print("All validations PASSED!")
|
|
||||||
|
|
||||||
sys.exit(0 if success else 1)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
"""
|
|
||||||
Validation modules for Word document processing.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from .base import BaseSchemaValidator
|
|
||||||
from .docx import DOCXSchemaValidator
|
|
||||||
from .pptx import PPTXSchemaValidator
|
|
||||||
from .redlining import RedliningValidator
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"BaseSchemaValidator",
|
|
||||||
"DOCXSchemaValidator",
|
|
||||||
"PPTXSchemaValidator",
|
|
||||||
"RedliningValidator",
|
|
||||||
]
|
|
||||||
@@ -1,951 +0,0 @@
|
|||||||
"""
|
|
||||||
Base validator with common validation logic for document files.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import re
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import lxml.etree
|
|
||||||
|
|
||||||
|
|
||||||
class BaseSchemaValidator:
|
|
||||||
"""Base validator with common validation logic for document files."""
|
|
||||||
|
|
||||||
# Elements whose 'id' attributes must be unique within their file
|
|
||||||
# Format: element_name -> (attribute_name, scope)
|
|
||||||
# scope can be 'file' (unique within file) or 'global' (unique across all files)
|
|
||||||
UNIQUE_ID_REQUIREMENTS = {
|
|
||||||
# Word elements
|
|
||||||
"comment": ("id", "file"), # Comment IDs in comments.xml
|
|
||||||
"commentrangestart": ("id", "file"), # Must match comment IDs
|
|
||||||
"commentrangeend": ("id", "file"), # Must match comment IDs
|
|
||||||
"bookmarkstart": ("id", "file"), # Bookmark start IDs
|
|
||||||
"bookmarkend": ("id", "file"), # Bookmark end IDs
|
|
||||||
# Note: ins and del (track changes) can share IDs when part of same revision
|
|
||||||
# PowerPoint elements
|
|
||||||
"sldid": ("id", "file"), # Slide IDs in presentation.xml
|
|
||||||
"sldmasterid": ("id", "global"), # Slide master IDs must be globally unique
|
|
||||||
"sldlayoutid": ("id", "global"), # Slide layout IDs must be globally unique
|
|
||||||
"cm": ("authorid", "file"), # Comment author IDs
|
|
||||||
# Excel elements
|
|
||||||
"sheet": ("sheetid", "file"), # Sheet IDs in workbook.xml
|
|
||||||
"definedname": ("id", "file"), # Named range IDs
|
|
||||||
# Drawing/Shape elements (all formats)
|
|
||||||
"cxnsp": ("id", "file"), # Connection shape IDs
|
|
||||||
"sp": ("id", "file"), # Shape IDs
|
|
||||||
"pic": ("id", "file"), # Picture IDs
|
|
||||||
"grpsp": ("id", "file"), # Group shape IDs
|
|
||||||
}
|
|
||||||
|
|
||||||
# Mapping of element names to expected relationship types
|
|
||||||
# Subclasses should override this with format-specific mappings
|
|
||||||
ELEMENT_RELATIONSHIP_TYPES = {}
|
|
||||||
|
|
||||||
# Unified schema mappings for all Office document types
|
|
||||||
SCHEMA_MAPPINGS = {
|
|
||||||
# Document type specific schemas
|
|
||||||
"word": "ISO-IEC29500-4_2016/wml.xsd", # Word documents
|
|
||||||
"ppt": "ISO-IEC29500-4_2016/pml.xsd", # PowerPoint presentations
|
|
||||||
"xl": "ISO-IEC29500-4_2016/sml.xsd", # Excel spreadsheets
|
|
||||||
# Common file types
|
|
||||||
"[Content_Types].xml": "ecma/fouth-edition/opc-contentTypes.xsd",
|
|
||||||
"app.xml": "ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd",
|
|
||||||
"core.xml": "ecma/fouth-edition/opc-coreProperties.xsd",
|
|
||||||
"custom.xml": "ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd",
|
|
||||||
".rels": "ecma/fouth-edition/opc-relationships.xsd",
|
|
||||||
# Word-specific files
|
|
||||||
"people.xml": "microsoft/wml-2012.xsd",
|
|
||||||
"commentsIds.xml": "microsoft/wml-cid-2016.xsd",
|
|
||||||
"commentsExtensible.xml": "microsoft/wml-cex-2018.xsd",
|
|
||||||
"commentsExtended.xml": "microsoft/wml-2012.xsd",
|
|
||||||
# Chart files (common across document types)
|
|
||||||
"chart": "ISO-IEC29500-4_2016/dml-chart.xsd",
|
|
||||||
# Theme files (common across document types)
|
|
||||||
"theme": "ISO-IEC29500-4_2016/dml-main.xsd",
|
|
||||||
# Drawing and media files
|
|
||||||
"drawing": "ISO-IEC29500-4_2016/dml-main.xsd",
|
|
||||||
}
|
|
||||||
|
|
||||||
# Unified namespace constants
|
|
||||||
MC_NAMESPACE = "http://schemas.openxmlformats.org/markup-compatibility/2006"
|
|
||||||
XML_NAMESPACE = "http://www.w3.org/XML/1998/namespace"
|
|
||||||
|
|
||||||
# Common OOXML namespaces used across validators
|
|
||||||
PACKAGE_RELATIONSHIPS_NAMESPACE = (
|
|
||||||
"http://schemas.openxmlformats.org/package/2006/relationships"
|
|
||||||
)
|
|
||||||
OFFICE_RELATIONSHIPS_NAMESPACE = (
|
|
||||||
"http://schemas.openxmlformats.org/officeDocument/2006/relationships"
|
|
||||||
)
|
|
||||||
CONTENT_TYPES_NAMESPACE = (
|
|
||||||
"http://schemas.openxmlformats.org/package/2006/content-types"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Folders where we should clean ignorable namespaces
|
|
||||||
MAIN_CONTENT_FOLDERS = {"word", "ppt", "xl"}
|
|
||||||
|
|
||||||
# All allowed OOXML namespaces (superset of all document types)
|
|
||||||
OOXML_NAMESPACES = {
|
|
||||||
"http://schemas.openxmlformats.org/officeDocument/2006/math",
|
|
||||||
"http://schemas.openxmlformats.org/officeDocument/2006/relationships",
|
|
||||||
"http://schemas.openxmlformats.org/schemaLibrary/2006/main",
|
|
||||||
"http://schemas.openxmlformats.org/drawingml/2006/main",
|
|
||||||
"http://schemas.openxmlformats.org/drawingml/2006/chart",
|
|
||||||
"http://schemas.openxmlformats.org/drawingml/2006/chartDrawing",
|
|
||||||
"http://schemas.openxmlformats.org/drawingml/2006/diagram",
|
|
||||||
"http://schemas.openxmlformats.org/drawingml/2006/picture",
|
|
||||||
"http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing",
|
|
||||||
"http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing",
|
|
||||||
"http://schemas.openxmlformats.org/wordprocessingml/2006/main",
|
|
||||||
"http://schemas.openxmlformats.org/presentationml/2006/main",
|
|
||||||
"http://schemas.openxmlformats.org/spreadsheetml/2006/main",
|
|
||||||
"http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes",
|
|
||||||
"http://www.w3.org/XML/1998/namespace",
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, unpacked_dir, original_file, verbose=False):
|
|
||||||
self.unpacked_dir = Path(unpacked_dir).resolve()
|
|
||||||
self.original_file = Path(original_file)
|
|
||||||
self.verbose = verbose
|
|
||||||
|
|
||||||
# Set schemas directory
|
|
||||||
self.schemas_dir = Path(__file__).parent.parent.parent / "schemas"
|
|
||||||
|
|
||||||
# Get all XML and .rels files
|
|
||||||
patterns = ["*.xml", "*.rels"]
|
|
||||||
self.xml_files = [
|
|
||||||
f for pattern in patterns for f in self.unpacked_dir.rglob(pattern)
|
|
||||||
]
|
|
||||||
|
|
||||||
if not self.xml_files:
|
|
||||||
print(f"Warning: No XML files found in {self.unpacked_dir}")
|
|
||||||
|
|
||||||
def validate(self):
|
|
||||||
"""Run all validation checks and return True if all pass."""
|
|
||||||
raise NotImplementedError("Subclasses must implement the validate method")
|
|
||||||
|
|
||||||
def validate_xml(self):
|
|
||||||
"""Validate that all XML files are well-formed."""
|
|
||||||
errors = []
|
|
||||||
|
|
||||||
for xml_file in self.xml_files:
|
|
||||||
try:
|
|
||||||
# Try to parse the XML file
|
|
||||||
lxml.etree.parse(str(xml_file))
|
|
||||||
except lxml.etree.XMLSyntaxError as e:
|
|
||||||
errors.append(
|
|
||||||
f" {xml_file.relative_to(self.unpacked_dir)}: "
|
|
||||||
f"Line {e.lineno}: {e.msg}"
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
errors.append(
|
|
||||||
f" {xml_file.relative_to(self.unpacked_dir)}: "
|
|
||||||
f"Unexpected error: {str(e)}"
|
|
||||||
)
|
|
||||||
|
|
||||||
if errors:
|
|
||||||
print(f"FAILED - Found {len(errors)} XML violations:")
|
|
||||||
for error in errors:
|
|
||||||
print(error)
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
if self.verbose:
|
|
||||||
print("PASSED - All XML files are well-formed")
|
|
||||||
return True
|
|
||||||
|
|
||||||
def validate_namespaces(self):
|
|
||||||
"""Validate that namespace prefixes in Ignorable attributes are declared."""
|
|
||||||
errors = []
|
|
||||||
|
|
||||||
for xml_file in self.xml_files:
|
|
||||||
try:
|
|
||||||
root = lxml.etree.parse(str(xml_file)).getroot()
|
|
||||||
declared = set(root.nsmap.keys()) - {None} # Exclude default namespace
|
|
||||||
|
|
||||||
for attr_val in [
|
|
||||||
v for k, v in root.attrib.items() if k.endswith("Ignorable")
|
|
||||||
]:
|
|
||||||
undeclared = set(attr_val.split()) - declared
|
|
||||||
errors.extend(
|
|
||||||
f" {xml_file.relative_to(self.unpacked_dir)}: "
|
|
||||||
f"Namespace '{ns}' in Ignorable but not declared"
|
|
||||||
for ns in undeclared
|
|
||||||
)
|
|
||||||
except lxml.etree.XMLSyntaxError:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if errors:
|
|
||||||
print(f"FAILED - {len(errors)} namespace issues:")
|
|
||||||
for error in errors:
|
|
||||||
print(error)
|
|
||||||
return False
|
|
||||||
if self.verbose:
|
|
||||||
print("PASSED - All namespace prefixes properly declared")
|
|
||||||
return True
|
|
||||||
|
|
||||||
def validate_unique_ids(self):
|
|
||||||
"""Validate that specific IDs are unique according to OOXML requirements."""
|
|
||||||
errors = []
|
|
||||||
global_ids = {} # Track globally unique IDs across all files
|
|
||||||
|
|
||||||
for xml_file in self.xml_files:
|
|
||||||
try:
|
|
||||||
root = lxml.etree.parse(str(xml_file)).getroot()
|
|
||||||
file_ids = {} # Track IDs that must be unique within this file
|
|
||||||
|
|
||||||
# Remove all mc:AlternateContent elements from the tree
|
|
||||||
mc_elements = root.xpath(
|
|
||||||
".//mc:AlternateContent", namespaces={"mc": self.MC_NAMESPACE}
|
|
||||||
)
|
|
||||||
for elem in mc_elements:
|
|
||||||
elem.getparent().remove(elem)
|
|
||||||
|
|
||||||
# Now check IDs in the cleaned tree
|
|
||||||
for elem in root.iter():
|
|
||||||
# Get the element name without namespace
|
|
||||||
tag = (
|
|
||||||
elem.tag.split("}")[-1].lower()
|
|
||||||
if "}" in elem.tag
|
|
||||||
else elem.tag.lower()
|
|
||||||
)
|
|
||||||
|
|
||||||
# Check if this element type has ID uniqueness requirements
|
|
||||||
if tag in self.UNIQUE_ID_REQUIREMENTS:
|
|
||||||
attr_name, scope = self.UNIQUE_ID_REQUIREMENTS[tag]
|
|
||||||
|
|
||||||
# Look for the specified attribute
|
|
||||||
id_value = None
|
|
||||||
for attr, value in elem.attrib.items():
|
|
||||||
attr_local = (
|
|
||||||
attr.split("}")[-1].lower()
|
|
||||||
if "}" in attr
|
|
||||||
else attr.lower()
|
|
||||||
)
|
|
||||||
if attr_local == attr_name:
|
|
||||||
id_value = value
|
|
||||||
break
|
|
||||||
|
|
||||||
if id_value is not None:
|
|
||||||
if scope == "global":
|
|
||||||
# Check global uniqueness
|
|
||||||
if id_value in global_ids:
|
|
||||||
prev_file, prev_line, prev_tag = global_ids[
|
|
||||||
id_value
|
|
||||||
]
|
|
||||||
errors.append(
|
|
||||||
f" {xml_file.relative_to(self.unpacked_dir)}: "
|
|
||||||
f"Line {elem.sourceline}: Global ID '{id_value}' in <{tag}> "
|
|
||||||
f"already used in {prev_file} at line {prev_line} in <{prev_tag}>"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
global_ids[id_value] = (
|
|
||||||
xml_file.relative_to(self.unpacked_dir),
|
|
||||||
elem.sourceline,
|
|
||||||
tag,
|
|
||||||
)
|
|
||||||
elif scope == "file":
|
|
||||||
# Check file-level uniqueness
|
|
||||||
key = (tag, attr_name)
|
|
||||||
if key not in file_ids:
|
|
||||||
file_ids[key] = {}
|
|
||||||
|
|
||||||
if id_value in file_ids[key]:
|
|
||||||
prev_line = file_ids[key][id_value]
|
|
||||||
errors.append(
|
|
||||||
f" {xml_file.relative_to(self.unpacked_dir)}: "
|
|
||||||
f"Line {elem.sourceline}: Duplicate {attr_name}='{id_value}' in <{tag}> "
|
|
||||||
f"(first occurrence at line {prev_line})"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
file_ids[key][id_value] = elem.sourceline
|
|
||||||
|
|
||||||
except (lxml.etree.XMLSyntaxError, Exception) as e:
|
|
||||||
errors.append(
|
|
||||||
f" {xml_file.relative_to(self.unpacked_dir)}: Error: {e}"
|
|
||||||
)
|
|
||||||
|
|
||||||
if errors:
|
|
||||||
print(f"FAILED - Found {len(errors)} ID uniqueness violations:")
|
|
||||||
for error in errors:
|
|
||||||
print(error)
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
if self.verbose:
|
|
||||||
print("PASSED - All required IDs are unique")
|
|
||||||
return True
|
|
||||||
|
|
||||||
def validate_file_references(self):
|
|
||||||
"""
|
|
||||||
Validate that all .rels files properly reference files and that all files are referenced.
|
|
||||||
"""
|
|
||||||
errors = []
|
|
||||||
|
|
||||||
# Find all .rels files
|
|
||||||
rels_files = list(self.unpacked_dir.rglob("*.rels"))
|
|
||||||
|
|
||||||
if not rels_files:
|
|
||||||
if self.verbose:
|
|
||||||
print("PASSED - No .rels files found")
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Get all files in the unpacked directory (excluding reference files)
|
|
||||||
all_files = []
|
|
||||||
for file_path in self.unpacked_dir.rglob("*"):
|
|
||||||
if (
|
|
||||||
file_path.is_file()
|
|
||||||
and file_path.name != "[Content_Types].xml"
|
|
||||||
and not file_path.name.endswith(".rels")
|
|
||||||
): # This file is not referenced by .rels
|
|
||||||
all_files.append(file_path.resolve())
|
|
||||||
|
|
||||||
# Track all files that are referenced by any .rels file
|
|
||||||
all_referenced_files = set()
|
|
||||||
|
|
||||||
if self.verbose:
|
|
||||||
print(
|
|
||||||
f"Found {len(rels_files)} .rels files and {len(all_files)} target files"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Check each .rels file
|
|
||||||
for rels_file in rels_files:
|
|
||||||
try:
|
|
||||||
# Parse relationships file
|
|
||||||
rels_root = lxml.etree.parse(str(rels_file)).getroot()
|
|
||||||
|
|
||||||
# Get the directory where this .rels file is located
|
|
||||||
rels_dir = rels_file.parent
|
|
||||||
|
|
||||||
# Find all relationships and their targets
|
|
||||||
referenced_files = set()
|
|
||||||
broken_refs = []
|
|
||||||
|
|
||||||
for rel in rels_root.findall(
|
|
||||||
".//ns:Relationship",
|
|
||||||
namespaces={"ns": self.PACKAGE_RELATIONSHIPS_NAMESPACE},
|
|
||||||
):
|
|
||||||
target = rel.get("Target")
|
|
||||||
if target and not target.startswith(
|
|
||||||
("http", "mailto:")
|
|
||||||
): # Skip external URLs
|
|
||||||
# Resolve the target path relative to the .rels file location
|
|
||||||
if rels_file.name == ".rels":
|
|
||||||
# Root .rels file - targets are relative to unpacked_dir
|
|
||||||
target_path = self.unpacked_dir / target
|
|
||||||
else:
|
|
||||||
# Other .rels files - targets are relative to their parent's parent
|
|
||||||
# e.g., word/_rels/document.xml.rels -> targets relative to word/
|
|
||||||
base_dir = rels_dir.parent
|
|
||||||
target_path = base_dir / target
|
|
||||||
|
|
||||||
# Normalize the path and check if it exists
|
|
||||||
try:
|
|
||||||
target_path = target_path.resolve()
|
|
||||||
if target_path.exists() and target_path.is_file():
|
|
||||||
referenced_files.add(target_path)
|
|
||||||
all_referenced_files.add(target_path)
|
|
||||||
else:
|
|
||||||
broken_refs.append((target, rel.sourceline))
|
|
||||||
except (OSError, ValueError):
|
|
||||||
broken_refs.append((target, rel.sourceline))
|
|
||||||
|
|
||||||
# Report broken references
|
|
||||||
if broken_refs:
|
|
||||||
rel_path = rels_file.relative_to(self.unpacked_dir)
|
|
||||||
for broken_ref, line_num in broken_refs:
|
|
||||||
errors.append(
|
|
||||||
f" {rel_path}: Line {line_num}: Broken reference to {broken_ref}"
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
rel_path = rels_file.relative_to(self.unpacked_dir)
|
|
||||||
errors.append(f" Error parsing {rel_path}: {e}")
|
|
||||||
|
|
||||||
# Check for unreferenced files (files that exist but are not referenced anywhere)
|
|
||||||
unreferenced_files = set(all_files) - all_referenced_files
|
|
||||||
|
|
||||||
if unreferenced_files:
|
|
||||||
for unref_file in sorted(unreferenced_files):
|
|
||||||
unref_rel_path = unref_file.relative_to(self.unpacked_dir)
|
|
||||||
errors.append(f" Unreferenced file: {unref_rel_path}")
|
|
||||||
|
|
||||||
if errors:
|
|
||||||
print(f"FAILED - Found {len(errors)} relationship validation errors:")
|
|
||||||
for error in errors:
|
|
||||||
print(error)
|
|
||||||
print(
|
|
||||||
"CRITICAL: These errors will cause the document to appear corrupt. "
|
|
||||||
+ "Broken references MUST be fixed, "
|
|
||||||
+ "and unreferenced files MUST be referenced or removed."
|
|
||||||
)
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
if self.verbose:
|
|
||||||
print(
|
|
||||||
"PASSED - All references are valid and all files are properly referenced"
|
|
||||||
)
|
|
||||||
return True
|
|
||||||
|
|
||||||
def validate_all_relationship_ids(self):
|
|
||||||
"""
|
|
||||||
Validate that all r:id attributes in XML files reference existing IDs
|
|
||||||
in their corresponding .rels files, and optionally validate relationship types.
|
|
||||||
"""
|
|
||||||
import lxml.etree
|
|
||||||
|
|
||||||
errors = []
|
|
||||||
|
|
||||||
# Process each XML file that might contain r:id references
|
|
||||||
for xml_file in self.xml_files:
|
|
||||||
# Skip .rels files themselves
|
|
||||||
if xml_file.suffix == ".rels":
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Determine the corresponding .rels file
|
|
||||||
# For dir/file.xml, it's dir/_rels/file.xml.rels
|
|
||||||
rels_dir = xml_file.parent / "_rels"
|
|
||||||
rels_file = rels_dir / f"{xml_file.name}.rels"
|
|
||||||
|
|
||||||
# Skip if there's no corresponding .rels file (that's okay)
|
|
||||||
if not rels_file.exists():
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Parse the .rels file to get valid relationship IDs and their types
|
|
||||||
rels_root = lxml.etree.parse(str(rels_file)).getroot()
|
|
||||||
rid_to_type = {}
|
|
||||||
|
|
||||||
for rel in rels_root.findall(
|
|
||||||
f".//{{{self.PACKAGE_RELATIONSHIPS_NAMESPACE}}}Relationship"
|
|
||||||
):
|
|
||||||
rid = rel.get("Id")
|
|
||||||
rel_type = rel.get("Type", "")
|
|
||||||
if rid:
|
|
||||||
# Check for duplicate rIds
|
|
||||||
if rid in rid_to_type:
|
|
||||||
rels_rel_path = rels_file.relative_to(self.unpacked_dir)
|
|
||||||
errors.append(
|
|
||||||
f" {rels_rel_path}: Line {rel.sourceline}: "
|
|
||||||
f"Duplicate relationship ID '{rid}' (IDs must be unique)"
|
|
||||||
)
|
|
||||||
# Extract just the type name from the full URL
|
|
||||||
type_name = (
|
|
||||||
rel_type.split("/")[-1] if "/" in rel_type else rel_type
|
|
||||||
)
|
|
||||||
rid_to_type[rid] = type_name
|
|
||||||
|
|
||||||
# Parse the XML file to find all r:id references
|
|
||||||
xml_root = lxml.etree.parse(str(xml_file)).getroot()
|
|
||||||
|
|
||||||
# Find all elements with r:id attributes
|
|
||||||
for elem in xml_root.iter():
|
|
||||||
# Check for r:id attribute (relationship ID)
|
|
||||||
rid_attr = elem.get(f"{{{self.OFFICE_RELATIONSHIPS_NAMESPACE}}}id")
|
|
||||||
if rid_attr:
|
|
||||||
xml_rel_path = xml_file.relative_to(self.unpacked_dir)
|
|
||||||
elem_name = (
|
|
||||||
elem.tag.split("}")[-1] if "}" in elem.tag else elem.tag
|
|
||||||
)
|
|
||||||
|
|
||||||
# Check if the ID exists
|
|
||||||
if rid_attr not in rid_to_type:
|
|
||||||
errors.append(
|
|
||||||
f" {xml_rel_path}: Line {elem.sourceline}: "
|
|
||||||
f"<{elem_name}> references non-existent relationship '{rid_attr}' "
|
|
||||||
f"(valid IDs: {', '.join(sorted(rid_to_type.keys())[:5])}{'...' if len(rid_to_type) > 5 else ''})"
|
|
||||||
)
|
|
||||||
# Check if we have type expectations for this element
|
|
||||||
elif self.ELEMENT_RELATIONSHIP_TYPES:
|
|
||||||
expected_type = self._get_expected_relationship_type(
|
|
||||||
elem_name
|
|
||||||
)
|
|
||||||
if expected_type:
|
|
||||||
actual_type = rid_to_type[rid_attr]
|
|
||||||
# Check if the actual type matches or contains the expected type
|
|
||||||
if expected_type not in actual_type.lower():
|
|
||||||
errors.append(
|
|
||||||
f" {xml_rel_path}: Line {elem.sourceline}: "
|
|
||||||
f"<{elem_name}> references '{rid_attr}' which points to '{actual_type}' "
|
|
||||||
f"but should point to a '{expected_type}' relationship"
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
xml_rel_path = xml_file.relative_to(self.unpacked_dir)
|
|
||||||
errors.append(f" Error processing {xml_rel_path}: {e}")
|
|
||||||
|
|
||||||
if errors:
|
|
||||||
print(f"FAILED - Found {len(errors)} relationship ID reference errors:")
|
|
||||||
for error in errors:
|
|
||||||
print(error)
|
|
||||||
print("\nThese ID mismatches will cause the document to appear corrupt!")
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
if self.verbose:
|
|
||||||
print("PASSED - All relationship ID references are valid")
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _get_expected_relationship_type(self, element_name):
|
|
||||||
"""
|
|
||||||
Get the expected relationship type for an element.
|
|
||||||
First checks the explicit mapping, then tries pattern detection.
|
|
||||||
"""
|
|
||||||
# Normalize element name to lowercase
|
|
||||||
elem_lower = element_name.lower()
|
|
||||||
|
|
||||||
# Check explicit mapping first
|
|
||||||
if elem_lower in self.ELEMENT_RELATIONSHIP_TYPES:
|
|
||||||
return self.ELEMENT_RELATIONSHIP_TYPES[elem_lower]
|
|
||||||
|
|
||||||
# Try pattern detection for common patterns
|
|
||||||
# Pattern 1: Elements ending in "Id" often expect a relationship of the prefix type
|
|
||||||
if elem_lower.endswith("id") and len(elem_lower) > 2:
|
|
||||||
# e.g., "sldId" -> "sld", "sldMasterId" -> "sldMaster"
|
|
||||||
prefix = elem_lower[:-2] # Remove "id"
|
|
||||||
# Check if this might be a compound like "sldMasterId"
|
|
||||||
if prefix.endswith("master"):
|
|
||||||
return prefix.lower()
|
|
||||||
elif prefix.endswith("layout"):
|
|
||||||
return prefix.lower()
|
|
||||||
else:
|
|
||||||
# Simple case like "sldId" -> "slide"
|
|
||||||
# Common transformations
|
|
||||||
if prefix == "sld":
|
|
||||||
return "slide"
|
|
||||||
return prefix.lower()
|
|
||||||
|
|
||||||
# Pattern 2: Elements ending in "Reference" expect a relationship of the prefix type
|
|
||||||
if elem_lower.endswith("reference") and len(elem_lower) > 9:
|
|
||||||
prefix = elem_lower[:-9] # Remove "reference"
|
|
||||||
return prefix.lower()
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
def validate_content_types(self):
|
|
||||||
"""Validate that all content files are properly declared in [Content_Types].xml."""
|
|
||||||
errors = []
|
|
||||||
|
|
||||||
# Find [Content_Types].xml file
|
|
||||||
content_types_file = self.unpacked_dir / "[Content_Types].xml"
|
|
||||||
if not content_types_file.exists():
|
|
||||||
print("FAILED - [Content_Types].xml file not found")
|
|
||||||
return False
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Parse and get all declared parts and extensions
|
|
||||||
root = lxml.etree.parse(str(content_types_file)).getroot()
|
|
||||||
declared_parts = set()
|
|
||||||
declared_extensions = set()
|
|
||||||
|
|
||||||
# Get Override declarations (specific files)
|
|
||||||
for override in root.findall(
|
|
||||||
f".//{{{self.CONTENT_TYPES_NAMESPACE}}}Override"
|
|
||||||
):
|
|
||||||
part_name = override.get("PartName")
|
|
||||||
if part_name is not None:
|
|
||||||
declared_parts.add(part_name.lstrip("/"))
|
|
||||||
|
|
||||||
# Get Default declarations (by extension)
|
|
||||||
for default in root.findall(
|
|
||||||
f".//{{{self.CONTENT_TYPES_NAMESPACE}}}Default"
|
|
||||||
):
|
|
||||||
extension = default.get("Extension")
|
|
||||||
if extension is not None:
|
|
||||||
declared_extensions.add(extension.lower())
|
|
||||||
|
|
||||||
# Root elements that require content type declaration
|
|
||||||
declarable_roots = {
|
|
||||||
"sld",
|
|
||||||
"sldLayout",
|
|
||||||
"sldMaster",
|
|
||||||
"presentation", # PowerPoint
|
|
||||||
"document", # Word
|
|
||||||
"workbook",
|
|
||||||
"worksheet", # Excel
|
|
||||||
"theme", # Common
|
|
||||||
}
|
|
||||||
|
|
||||||
# Common media file extensions that should be declared
|
|
||||||
media_extensions = {
|
|
||||||
"png": "image/png",
|
|
||||||
"jpg": "image/jpeg",
|
|
||||||
"jpeg": "image/jpeg",
|
|
||||||
"gif": "image/gif",
|
|
||||||
"bmp": "image/bmp",
|
|
||||||
"tiff": "image/tiff",
|
|
||||||
"wmf": "image/x-wmf",
|
|
||||||
"emf": "image/x-emf",
|
|
||||||
}
|
|
||||||
|
|
||||||
# Get all files in the unpacked directory
|
|
||||||
all_files = list(self.unpacked_dir.rglob("*"))
|
|
||||||
all_files = [f for f in all_files if f.is_file()]
|
|
||||||
|
|
||||||
# Check all XML files for Override declarations
|
|
||||||
for xml_file in self.xml_files:
|
|
||||||
path_str = str(xml_file.relative_to(self.unpacked_dir)).replace(
|
|
||||||
"\\", "/"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Skip non-content files
|
|
||||||
if any(
|
|
||||||
skip in path_str
|
|
||||||
for skip in [".rels", "[Content_Types]", "docProps/", "_rels/"]
|
|
||||||
):
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
|
||||||
root_tag = lxml.etree.parse(str(xml_file)).getroot().tag
|
|
||||||
root_name = root_tag.split("}")[-1] if "}" in root_tag else root_tag
|
|
||||||
|
|
||||||
if root_name in declarable_roots and path_str not in declared_parts:
|
|
||||||
errors.append(
|
|
||||||
f" {path_str}: File with <{root_name}> root not declared in [Content_Types].xml"
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception:
|
|
||||||
continue # Skip unparseable files
|
|
||||||
|
|
||||||
# Check all non-XML files for Default extension declarations
|
|
||||||
for file_path in all_files:
|
|
||||||
# Skip XML files and metadata files (already checked above)
|
|
||||||
if file_path.suffix.lower() in {".xml", ".rels"}:
|
|
||||||
continue
|
|
||||||
if file_path.name == "[Content_Types].xml":
|
|
||||||
continue
|
|
||||||
if "_rels" in file_path.parts or "docProps" in file_path.parts:
|
|
||||||
continue
|
|
||||||
|
|
||||||
extension = file_path.suffix.lstrip(".").lower()
|
|
||||||
if extension and extension not in declared_extensions:
|
|
||||||
# Check if it's a known media extension that should be declared
|
|
||||||
if extension in media_extensions:
|
|
||||||
relative_path = file_path.relative_to(self.unpacked_dir)
|
|
||||||
errors.append(
|
|
||||||
f' {relative_path}: File with extension \'{extension}\' not declared in [Content_Types].xml - should add: <Default Extension="{extension}" ContentType="{media_extensions[extension]}"/>'
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
errors.append(f" Error parsing [Content_Types].xml: {e}")
|
|
||||||
|
|
||||||
if errors:
|
|
||||||
print(f"FAILED - Found {len(errors)} content type declaration errors:")
|
|
||||||
for error in errors:
|
|
||||||
print(error)
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
if self.verbose:
|
|
||||||
print(
|
|
||||||
"PASSED - All content files are properly declared in [Content_Types].xml"
|
|
||||||
)
|
|
||||||
return True
|
|
||||||
|
|
||||||
def validate_file_against_xsd(self, xml_file, verbose=False):
|
|
||||||
"""Validate a single XML file against XSD schema, comparing with original.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
xml_file: Path to XML file to validate
|
|
||||||
verbose: Enable verbose output
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
tuple: (is_valid, new_errors_set) where is_valid is True/False/None (skipped)
|
|
||||||
"""
|
|
||||||
# Resolve both paths to handle symlinks
|
|
||||||
xml_file = Path(xml_file).resolve()
|
|
||||||
unpacked_dir = self.unpacked_dir.resolve()
|
|
||||||
|
|
||||||
# Validate current file
|
|
||||||
is_valid, current_errors = self._validate_single_file_xsd(
|
|
||||||
xml_file, unpacked_dir
|
|
||||||
)
|
|
||||||
|
|
||||||
if is_valid is None:
|
|
||||||
return None, set() # Skipped
|
|
||||||
elif is_valid:
|
|
||||||
return True, set() # Valid, no errors
|
|
||||||
|
|
||||||
# Get errors from original file for this specific file
|
|
||||||
original_errors = self._get_original_file_errors(xml_file)
|
|
||||||
|
|
||||||
# Compare with original (both are guaranteed to be sets here)
|
|
||||||
assert current_errors is not None
|
|
||||||
new_errors = current_errors - original_errors
|
|
||||||
|
|
||||||
if new_errors:
|
|
||||||
if verbose:
|
|
||||||
relative_path = xml_file.relative_to(unpacked_dir)
|
|
||||||
print(f"FAILED - {relative_path}: {len(new_errors)} new error(s)")
|
|
||||||
for error in list(new_errors)[:3]:
|
|
||||||
truncated = error[:250] + "..." if len(error) > 250 else error
|
|
||||||
print(f" - {truncated}")
|
|
||||||
return False, new_errors
|
|
||||||
else:
|
|
||||||
# All errors existed in original
|
|
||||||
if verbose:
|
|
||||||
print(
|
|
||||||
f"PASSED - No new errors (original had {len(current_errors)} errors)"
|
|
||||||
)
|
|
||||||
return True, set()
|
|
||||||
|
|
||||||
def validate_against_xsd(self):
|
|
||||||
"""Validate XML files against XSD schemas, showing only new errors compared to original."""
|
|
||||||
new_errors = []
|
|
||||||
original_error_count = 0
|
|
||||||
valid_count = 0
|
|
||||||
skipped_count = 0
|
|
||||||
|
|
||||||
for xml_file in self.xml_files:
|
|
||||||
relative_path = str(xml_file.relative_to(self.unpacked_dir))
|
|
||||||
is_valid, new_file_errors = self.validate_file_against_xsd(
|
|
||||||
xml_file, verbose=False
|
|
||||||
)
|
|
||||||
|
|
||||||
if is_valid is None:
|
|
||||||
skipped_count += 1
|
|
||||||
continue
|
|
||||||
elif is_valid and not new_file_errors:
|
|
||||||
valid_count += 1
|
|
||||||
continue
|
|
||||||
elif is_valid:
|
|
||||||
# Had errors but all existed in original
|
|
||||||
original_error_count += 1
|
|
||||||
valid_count += 1
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Has new errors
|
|
||||||
new_errors.append(f" {relative_path}: {len(new_file_errors)} new error(s)")
|
|
||||||
for error in list(new_file_errors)[:3]: # Show first 3 errors
|
|
||||||
new_errors.append(
|
|
||||||
f" - {error[:250]}..." if len(error) > 250 else f" - {error}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Print summary
|
|
||||||
if self.verbose:
|
|
||||||
print(f"Validated {len(self.xml_files)} files:")
|
|
||||||
print(f" - Valid: {valid_count}")
|
|
||||||
print(f" - Skipped (no schema): {skipped_count}")
|
|
||||||
if original_error_count:
|
|
||||||
print(f" - With original errors (ignored): {original_error_count}")
|
|
||||||
print(
|
|
||||||
f" - With NEW errors: {len(new_errors) > 0 and len([e for e in new_errors if not e.startswith(' ')]) or 0}"
|
|
||||||
)
|
|
||||||
|
|
||||||
if new_errors:
|
|
||||||
print("\nFAILED - Found NEW validation errors:")
|
|
||||||
for error in new_errors:
|
|
||||||
print(error)
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
if self.verbose:
|
|
||||||
print("\nPASSED - No new XSD validation errors introduced")
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _get_schema_path(self, xml_file):
|
|
||||||
"""Determine the appropriate schema path for an XML file."""
|
|
||||||
# Check exact filename match
|
|
||||||
if xml_file.name in self.SCHEMA_MAPPINGS:
|
|
||||||
return self.schemas_dir / self.SCHEMA_MAPPINGS[xml_file.name]
|
|
||||||
|
|
||||||
# Check .rels files
|
|
||||||
if xml_file.suffix == ".rels":
|
|
||||||
return self.schemas_dir / self.SCHEMA_MAPPINGS[".rels"]
|
|
||||||
|
|
||||||
# Check chart files
|
|
||||||
if "charts/" in str(xml_file) and xml_file.name.startswith("chart"):
|
|
||||||
return self.schemas_dir / self.SCHEMA_MAPPINGS["chart"]
|
|
||||||
|
|
||||||
# Check theme files
|
|
||||||
if "theme/" in str(xml_file) and xml_file.name.startswith("theme"):
|
|
||||||
return self.schemas_dir / self.SCHEMA_MAPPINGS["theme"]
|
|
||||||
|
|
||||||
# Check if file is in a main content folder and use appropriate schema
|
|
||||||
if xml_file.parent.name in self.MAIN_CONTENT_FOLDERS:
|
|
||||||
return self.schemas_dir / self.SCHEMA_MAPPINGS[xml_file.parent.name]
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
def _clean_ignorable_namespaces(self, xml_doc):
|
|
||||||
"""Remove attributes and elements not in allowed namespaces."""
|
|
||||||
# Create a clean copy
|
|
||||||
xml_string = lxml.etree.tostring(xml_doc, encoding="unicode")
|
|
||||||
xml_copy = lxml.etree.fromstring(xml_string)
|
|
||||||
|
|
||||||
# Remove attributes not in allowed namespaces
|
|
||||||
for elem in xml_copy.iter():
|
|
||||||
attrs_to_remove = []
|
|
||||||
|
|
||||||
for attr in elem.attrib:
|
|
||||||
# Check if attribute is from a namespace other than allowed ones
|
|
||||||
if "{" in attr:
|
|
||||||
ns = attr.split("}")[0][1:]
|
|
||||||
if ns not in self.OOXML_NAMESPACES:
|
|
||||||
attrs_to_remove.append(attr)
|
|
||||||
|
|
||||||
# Remove collected attributes
|
|
||||||
for attr in attrs_to_remove:
|
|
||||||
del elem.attrib[attr]
|
|
||||||
|
|
||||||
# Remove elements not in allowed namespaces
|
|
||||||
self._remove_ignorable_elements(xml_copy)
|
|
||||||
|
|
||||||
return lxml.etree.ElementTree(xml_copy)
|
|
||||||
|
|
||||||
def _remove_ignorable_elements(self, root):
|
|
||||||
"""Recursively remove all elements not in allowed namespaces."""
|
|
||||||
elements_to_remove = []
|
|
||||||
|
|
||||||
# Find elements to remove
|
|
||||||
for elem in list(root):
|
|
||||||
# Skip non-element nodes (comments, processing instructions, etc.)
|
|
||||||
if not hasattr(elem, "tag") or callable(elem.tag):
|
|
||||||
continue
|
|
||||||
|
|
||||||
tag_str = str(elem.tag)
|
|
||||||
if tag_str.startswith("{"):
|
|
||||||
ns = tag_str.split("}")[0][1:]
|
|
||||||
if ns not in self.OOXML_NAMESPACES:
|
|
||||||
elements_to_remove.append(elem)
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Recursively clean child elements
|
|
||||||
self._remove_ignorable_elements(elem)
|
|
||||||
|
|
||||||
# Remove collected elements
|
|
||||||
for elem in elements_to_remove:
|
|
||||||
root.remove(elem)
|
|
||||||
|
|
||||||
def _preprocess_for_mc_ignorable(self, xml_doc):
|
|
||||||
"""Preprocess XML to handle mc:Ignorable attribute properly."""
|
|
||||||
# Remove mc:Ignorable attributes before validation
|
|
||||||
root = xml_doc.getroot()
|
|
||||||
|
|
||||||
# Remove mc:Ignorable attribute from root
|
|
||||||
if f"{{{self.MC_NAMESPACE}}}Ignorable" in root.attrib:
|
|
||||||
del root.attrib[f"{{{self.MC_NAMESPACE}}}Ignorable"]
|
|
||||||
|
|
||||||
return xml_doc
|
|
||||||
|
|
||||||
def _validate_single_file_xsd(self, xml_file, base_path):
|
|
||||||
"""Validate a single XML file against XSD schema. Returns (is_valid, errors_set)."""
|
|
||||||
schema_path = self._get_schema_path(xml_file)
|
|
||||||
if not schema_path:
|
|
||||||
return None, None # Skip file
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Load schema
|
|
||||||
with open(schema_path, "rb") as xsd_file:
|
|
||||||
parser = lxml.etree.XMLParser()
|
|
||||||
xsd_doc = lxml.etree.parse(
|
|
||||||
xsd_file, parser=parser, base_url=str(schema_path)
|
|
||||||
)
|
|
||||||
schema = lxml.etree.XMLSchema(xsd_doc)
|
|
||||||
|
|
||||||
# Load and preprocess XML
|
|
||||||
with open(xml_file, "r") as f:
|
|
||||||
xml_doc = lxml.etree.parse(f)
|
|
||||||
|
|
||||||
xml_doc, _ = self._remove_template_tags_from_text_nodes(xml_doc)
|
|
||||||
xml_doc = self._preprocess_for_mc_ignorable(xml_doc)
|
|
||||||
|
|
||||||
# Clean ignorable namespaces if needed
|
|
||||||
relative_path = xml_file.relative_to(base_path)
|
|
||||||
if (
|
|
||||||
relative_path.parts
|
|
||||||
and relative_path.parts[0] in self.MAIN_CONTENT_FOLDERS
|
|
||||||
):
|
|
||||||
xml_doc = self._clean_ignorable_namespaces(xml_doc)
|
|
||||||
|
|
||||||
# Validate
|
|
||||||
if schema.validate(xml_doc):
|
|
||||||
return True, set()
|
|
||||||
else:
|
|
||||||
errors = set()
|
|
||||||
for error in schema.error_log:
|
|
||||||
# Store normalized error message (without line numbers for comparison)
|
|
||||||
errors.add(error.message)
|
|
||||||
return False, errors
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
return False, {str(e)}
|
|
||||||
|
|
||||||
def _get_original_file_errors(self, xml_file):
|
|
||||||
"""Get XSD validation errors from a single file in the original document.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
xml_file: Path to the XML file in unpacked_dir to check
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
set: Set of error messages from the original file
|
|
||||||
"""
|
|
||||||
import tempfile
|
|
||||||
import zipfile
|
|
||||||
|
|
||||||
# Resolve both paths to handle symlinks (e.g., /var vs /private/var on macOS)
|
|
||||||
xml_file = Path(xml_file).resolve()
|
|
||||||
unpacked_dir = self.unpacked_dir.resolve()
|
|
||||||
relative_path = xml_file.relative_to(unpacked_dir)
|
|
||||||
|
|
||||||
with tempfile.TemporaryDirectory() as temp_dir:
|
|
||||||
temp_path = Path(temp_dir)
|
|
||||||
|
|
||||||
# Extract original file
|
|
||||||
with zipfile.ZipFile(self.original_file, "r") as zip_ref:
|
|
||||||
zip_ref.extractall(temp_path)
|
|
||||||
|
|
||||||
# Find corresponding file in original
|
|
||||||
original_xml_file = temp_path / relative_path
|
|
||||||
|
|
||||||
if not original_xml_file.exists():
|
|
||||||
# File didn't exist in original, so no original errors
|
|
||||||
return set()
|
|
||||||
|
|
||||||
# Validate the specific file in original
|
|
||||||
is_valid, errors = self._validate_single_file_xsd(
|
|
||||||
original_xml_file, temp_path
|
|
||||||
)
|
|
||||||
return errors if errors else set()
|
|
||||||
|
|
||||||
def _remove_template_tags_from_text_nodes(self, xml_doc):
|
|
||||||
"""Remove template tags from XML text nodes and collect warnings.
|
|
||||||
|
|
||||||
Template tags follow the pattern {{ ... }} and are used as placeholders
|
|
||||||
for content replacement. They should be removed from text content before
|
|
||||||
XSD validation while preserving XML structure.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
tuple: (cleaned_xml_doc, warnings_list)
|
|
||||||
"""
|
|
||||||
warnings = []
|
|
||||||
template_pattern = re.compile(r"\{\{[^}]*\}\}")
|
|
||||||
|
|
||||||
# Create a copy of the document to avoid modifying the original
|
|
||||||
xml_string = lxml.etree.tostring(xml_doc, encoding="unicode")
|
|
||||||
xml_copy = lxml.etree.fromstring(xml_string)
|
|
||||||
|
|
||||||
def process_text_content(text, content_type):
|
|
||||||
if not text:
|
|
||||||
return text
|
|
||||||
matches = list(template_pattern.finditer(text))
|
|
||||||
if matches:
|
|
||||||
for match in matches:
|
|
||||||
warnings.append(
|
|
||||||
f"Found template tag in {content_type}: {match.group()}"
|
|
||||||
)
|
|
||||||
return template_pattern.sub("", text)
|
|
||||||
return text
|
|
||||||
|
|
||||||
# Process all text nodes in the document
|
|
||||||
for elem in xml_copy.iter():
|
|
||||||
# Skip processing if this is a w:t element
|
|
||||||
if not hasattr(elem, "tag") or callable(elem.tag):
|
|
||||||
continue
|
|
||||||
tag_str = str(elem.tag)
|
|
||||||
if tag_str.endswith("}t") or tag_str == "t":
|
|
||||||
continue
|
|
||||||
|
|
||||||
elem.text = process_text_content(elem.text, "text content")
|
|
||||||
elem.tail = process_text_content(elem.tail, "tail content")
|
|
||||||
|
|
||||||
return lxml.etree.ElementTree(xml_copy), warnings
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
raise RuntimeError("This module should not be run directly.")
|
|
||||||
@@ -1,274 +0,0 @@
|
|||||||
"""
|
|
||||||
Validator for Word document XML files against XSD schemas.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import re
|
|
||||||
import tempfile
|
|
||||||
import zipfile
|
|
||||||
|
|
||||||
import lxml.etree
|
|
||||||
|
|
||||||
from .base import BaseSchemaValidator
|
|
||||||
|
|
||||||
|
|
||||||
class DOCXSchemaValidator(BaseSchemaValidator):
|
|
||||||
"""Validator for Word document XML files against XSD schemas."""
|
|
||||||
|
|
||||||
# Word-specific namespace
|
|
||||||
WORD_2006_NAMESPACE = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
|
|
||||||
|
|
||||||
# Word-specific element to relationship type mappings
|
|
||||||
# Start with empty mapping - add specific cases as we discover them
|
|
||||||
ELEMENT_RELATIONSHIP_TYPES = {}
|
|
||||||
|
|
||||||
def validate(self):
|
|
||||||
"""Run all validation checks and return True if all pass."""
|
|
||||||
# Test 0: XML well-formedness
|
|
||||||
if not self.validate_xml():
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Test 1: Namespace declarations
|
|
||||||
all_valid = True
|
|
||||||
if not self.validate_namespaces():
|
|
||||||
all_valid = False
|
|
||||||
|
|
||||||
# Test 2: Unique IDs
|
|
||||||
if not self.validate_unique_ids():
|
|
||||||
all_valid = False
|
|
||||||
|
|
||||||
# Test 3: Relationship and file reference validation
|
|
||||||
if not self.validate_file_references():
|
|
||||||
all_valid = False
|
|
||||||
|
|
||||||
# Test 4: Content type declarations
|
|
||||||
if not self.validate_content_types():
|
|
||||||
all_valid = False
|
|
||||||
|
|
||||||
# Test 5: XSD schema validation
|
|
||||||
if not self.validate_against_xsd():
|
|
||||||
all_valid = False
|
|
||||||
|
|
||||||
# Test 6: Whitespace preservation
|
|
||||||
if not self.validate_whitespace_preservation():
|
|
||||||
all_valid = False
|
|
||||||
|
|
||||||
# Test 7: Deletion validation
|
|
||||||
if not self.validate_deletions():
|
|
||||||
all_valid = False
|
|
||||||
|
|
||||||
# Test 8: Insertion validation
|
|
||||||
if not self.validate_insertions():
|
|
||||||
all_valid = False
|
|
||||||
|
|
||||||
# Test 9: Relationship ID reference validation
|
|
||||||
if not self.validate_all_relationship_ids():
|
|
||||||
all_valid = False
|
|
||||||
|
|
||||||
# Count and compare paragraphs
|
|
||||||
self.compare_paragraph_counts()
|
|
||||||
|
|
||||||
return all_valid
|
|
||||||
|
|
||||||
def validate_whitespace_preservation(self):
|
|
||||||
"""
|
|
||||||
Validate that w:t elements with whitespace have xml:space='preserve'.
|
|
||||||
"""
|
|
||||||
errors = []
|
|
||||||
|
|
||||||
for xml_file in self.xml_files:
|
|
||||||
# Only check document.xml files
|
|
||||||
if xml_file.name != "document.xml":
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
|
||||||
root = lxml.etree.parse(str(xml_file)).getroot()
|
|
||||||
|
|
||||||
# Find all w:t elements
|
|
||||||
for elem in root.iter(f"{{{self.WORD_2006_NAMESPACE}}}t"):
|
|
||||||
if elem.text:
|
|
||||||
text = elem.text
|
|
||||||
# Check if text starts or ends with whitespace
|
|
||||||
if re.match(r"^\s.*", text) or re.match(r".*\s$", text):
|
|
||||||
# Check if xml:space="preserve" attribute exists
|
|
||||||
xml_space_attr = f"{{{self.XML_NAMESPACE}}}space"
|
|
||||||
if (
|
|
||||||
xml_space_attr not in elem.attrib
|
|
||||||
or elem.attrib[xml_space_attr] != "preserve"
|
|
||||||
):
|
|
||||||
# Show a preview of the text
|
|
||||||
text_preview = (
|
|
||||||
repr(text)[:50] + "..."
|
|
||||||
if len(repr(text)) > 50
|
|
||||||
else repr(text)
|
|
||||||
)
|
|
||||||
errors.append(
|
|
||||||
f" {xml_file.relative_to(self.unpacked_dir)}: "
|
|
||||||
f"Line {elem.sourceline}: w:t element with whitespace missing xml:space='preserve': {text_preview}"
|
|
||||||
)
|
|
||||||
|
|
||||||
except (lxml.etree.XMLSyntaxError, Exception) as e:
|
|
||||||
errors.append(
|
|
||||||
f" {xml_file.relative_to(self.unpacked_dir)}: Error: {e}"
|
|
||||||
)
|
|
||||||
|
|
||||||
if errors:
|
|
||||||
print(f"FAILED - Found {len(errors)} whitespace preservation violations:")
|
|
||||||
for error in errors:
|
|
||||||
print(error)
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
if self.verbose:
|
|
||||||
print("PASSED - All whitespace is properly preserved")
|
|
||||||
return True
|
|
||||||
|
|
||||||
def validate_deletions(self):
|
|
||||||
"""
|
|
||||||
Validate that w:t elements are not within w:del elements.
|
|
||||||
For some reason, XSD validation does not catch this, so we do it manually.
|
|
||||||
"""
|
|
||||||
errors = []
|
|
||||||
|
|
||||||
for xml_file in self.xml_files:
|
|
||||||
# Only check document.xml files
|
|
||||||
if xml_file.name != "document.xml":
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
|
||||||
root = lxml.etree.parse(str(xml_file)).getroot()
|
|
||||||
|
|
||||||
# Find all w:t elements that are descendants of w:del elements
|
|
||||||
namespaces = {"w": self.WORD_2006_NAMESPACE}
|
|
||||||
xpath_expression = ".//w:del//w:t"
|
|
||||||
problematic_t_elements = root.xpath(
|
|
||||||
xpath_expression, namespaces=namespaces
|
|
||||||
)
|
|
||||||
for t_elem in problematic_t_elements:
|
|
||||||
if t_elem.text:
|
|
||||||
# Show a preview of the text
|
|
||||||
text_preview = (
|
|
||||||
repr(t_elem.text)[:50] + "..."
|
|
||||||
if len(repr(t_elem.text)) > 50
|
|
||||||
else repr(t_elem.text)
|
|
||||||
)
|
|
||||||
errors.append(
|
|
||||||
f" {xml_file.relative_to(self.unpacked_dir)}: "
|
|
||||||
f"Line {t_elem.sourceline}: <w:t> found within <w:del>: {text_preview}"
|
|
||||||
)
|
|
||||||
|
|
||||||
except (lxml.etree.XMLSyntaxError, Exception) as e:
|
|
||||||
errors.append(
|
|
||||||
f" {xml_file.relative_to(self.unpacked_dir)}: Error: {e}"
|
|
||||||
)
|
|
||||||
|
|
||||||
if errors:
|
|
||||||
print(f"FAILED - Found {len(errors)} deletion validation violations:")
|
|
||||||
for error in errors:
|
|
||||||
print(error)
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
if self.verbose:
|
|
||||||
print("PASSED - No w:t elements found within w:del elements")
|
|
||||||
return True
|
|
||||||
|
|
||||||
def count_paragraphs_in_unpacked(self):
|
|
||||||
"""Count the number of paragraphs in the unpacked document."""
|
|
||||||
count = 0
|
|
||||||
|
|
||||||
for xml_file in self.xml_files:
|
|
||||||
# Only check document.xml files
|
|
||||||
if xml_file.name != "document.xml":
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
|
||||||
root = lxml.etree.parse(str(xml_file)).getroot()
|
|
||||||
# Count all w:p elements
|
|
||||||
paragraphs = root.findall(f".//{{{self.WORD_2006_NAMESPACE}}}p")
|
|
||||||
count = len(paragraphs)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error counting paragraphs in unpacked document: {e}")
|
|
||||||
|
|
||||||
return count
|
|
||||||
|
|
||||||
def count_paragraphs_in_original(self):
|
|
||||||
"""Count the number of paragraphs in the original docx file."""
|
|
||||||
count = 0
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Create temporary directory to unpack original
|
|
||||||
with tempfile.TemporaryDirectory() as temp_dir:
|
|
||||||
# Unpack original docx
|
|
||||||
with zipfile.ZipFile(self.original_file, "r") as zip_ref:
|
|
||||||
zip_ref.extractall(temp_dir)
|
|
||||||
|
|
||||||
# Parse document.xml
|
|
||||||
doc_xml_path = temp_dir + "/word/document.xml"
|
|
||||||
root = lxml.etree.parse(doc_xml_path).getroot()
|
|
||||||
|
|
||||||
# Count all w:p elements
|
|
||||||
paragraphs = root.findall(f".//{{{self.WORD_2006_NAMESPACE}}}p")
|
|
||||||
count = len(paragraphs)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error counting paragraphs in original document: {e}")
|
|
||||||
|
|
||||||
return count
|
|
||||||
|
|
||||||
def validate_insertions(self):
|
|
||||||
"""
|
|
||||||
Validate that w:delText elements are not within w:ins elements.
|
|
||||||
w:delText is only allowed in w:ins if nested within a w:del.
|
|
||||||
"""
|
|
||||||
errors = []
|
|
||||||
|
|
||||||
for xml_file in self.xml_files:
|
|
||||||
if xml_file.name != "document.xml":
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
|
||||||
root = lxml.etree.parse(str(xml_file)).getroot()
|
|
||||||
namespaces = {"w": self.WORD_2006_NAMESPACE}
|
|
||||||
|
|
||||||
# Find w:delText in w:ins that are NOT within w:del
|
|
||||||
invalid_elements = root.xpath(
|
|
||||||
".//w:ins//w:delText[not(ancestor::w:del)]",
|
|
||||||
namespaces=namespaces
|
|
||||||
)
|
|
||||||
|
|
||||||
for elem in invalid_elements:
|
|
||||||
text_preview = (
|
|
||||||
repr(elem.text or "")[:50] + "..."
|
|
||||||
if len(repr(elem.text or "")) > 50
|
|
||||||
else repr(elem.text or "")
|
|
||||||
)
|
|
||||||
errors.append(
|
|
||||||
f" {xml_file.relative_to(self.unpacked_dir)}: "
|
|
||||||
f"Line {elem.sourceline}: <w:delText> within <w:ins>: {text_preview}"
|
|
||||||
)
|
|
||||||
|
|
||||||
except (lxml.etree.XMLSyntaxError, Exception) as e:
|
|
||||||
errors.append(
|
|
||||||
f" {xml_file.relative_to(self.unpacked_dir)}: Error: {e}"
|
|
||||||
)
|
|
||||||
|
|
||||||
if errors:
|
|
||||||
print(f"FAILED - Found {len(errors)} insertion validation violations:")
|
|
||||||
for error in errors:
|
|
||||||
print(error)
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
if self.verbose:
|
|
||||||
print("PASSED - No w:delText elements within w:ins elements")
|
|
||||||
return True
|
|
||||||
|
|
||||||
def compare_paragraph_counts(self):
|
|
||||||
"""Compare paragraph counts between original and new document."""
|
|
||||||
original_count = self.count_paragraphs_in_original()
|
|
||||||
new_count = self.count_paragraphs_in_unpacked()
|
|
||||||
|
|
||||||
diff = new_count - original_count
|
|
||||||
diff_str = f"+{diff}" if diff > 0 else str(diff)
|
|
||||||
print(f"\nParagraphs: {original_count} → {new_count} ({diff_str})")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
raise RuntimeError("This module should not be run directly.")
|
|
||||||
@@ -1,315 +0,0 @@
|
|||||||
"""
|
|
||||||
Validator for PowerPoint presentation XML files against XSD schemas.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
from .base import BaseSchemaValidator
|
|
||||||
|
|
||||||
|
|
||||||
class PPTXSchemaValidator(BaseSchemaValidator):
|
|
||||||
"""Validator for PowerPoint presentation XML files against XSD schemas."""
|
|
||||||
|
|
||||||
# PowerPoint presentation namespace
|
|
||||||
PRESENTATIONML_NAMESPACE = (
|
|
||||||
"http://schemas.openxmlformats.org/presentationml/2006/main"
|
|
||||||
)
|
|
||||||
|
|
||||||
# PowerPoint-specific element to relationship type mappings
|
|
||||||
ELEMENT_RELATIONSHIP_TYPES = {
|
|
||||||
"sldid": "slide",
|
|
||||||
"sldmasterid": "slidemaster",
|
|
||||||
"notesmasterid": "notesmaster",
|
|
||||||
"sldlayoutid": "slidelayout",
|
|
||||||
"themeid": "theme",
|
|
||||||
"tablestyleid": "tablestyles",
|
|
||||||
}
|
|
||||||
|
|
||||||
def validate(self):
|
|
||||||
"""Run all validation checks and return True if all pass."""
|
|
||||||
# Test 0: XML well-formedness
|
|
||||||
if not self.validate_xml():
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Test 1: Namespace declarations
|
|
||||||
all_valid = True
|
|
||||||
if not self.validate_namespaces():
|
|
||||||
all_valid = False
|
|
||||||
|
|
||||||
# Test 2: Unique IDs
|
|
||||||
if not self.validate_unique_ids():
|
|
||||||
all_valid = False
|
|
||||||
|
|
||||||
# Test 3: UUID ID validation
|
|
||||||
if not self.validate_uuid_ids():
|
|
||||||
all_valid = False
|
|
||||||
|
|
||||||
# Test 4: Relationship and file reference validation
|
|
||||||
if not self.validate_file_references():
|
|
||||||
all_valid = False
|
|
||||||
|
|
||||||
# Test 5: Slide layout ID validation
|
|
||||||
if not self.validate_slide_layout_ids():
|
|
||||||
all_valid = False
|
|
||||||
|
|
||||||
# Test 6: Content type declarations
|
|
||||||
if not self.validate_content_types():
|
|
||||||
all_valid = False
|
|
||||||
|
|
||||||
# Test 7: XSD schema validation
|
|
||||||
if not self.validate_against_xsd():
|
|
||||||
all_valid = False
|
|
||||||
|
|
||||||
# Test 8: Notes slide reference validation
|
|
||||||
if not self.validate_notes_slide_references():
|
|
||||||
all_valid = False
|
|
||||||
|
|
||||||
# Test 9: Relationship ID reference validation
|
|
||||||
if not self.validate_all_relationship_ids():
|
|
||||||
all_valid = False
|
|
||||||
|
|
||||||
# Test 10: Duplicate slide layout references validation
|
|
||||||
if not self.validate_no_duplicate_slide_layouts():
|
|
||||||
all_valid = False
|
|
||||||
|
|
||||||
return all_valid
|
|
||||||
|
|
||||||
def validate_uuid_ids(self):
|
|
||||||
"""Validate that ID attributes that look like UUIDs contain only hex values."""
|
|
||||||
import lxml.etree
|
|
||||||
|
|
||||||
errors = []
|
|
||||||
# UUID pattern: 8-4-4-4-12 hex digits with optional braces/hyphens
|
|
||||||
uuid_pattern = re.compile(
|
|
||||||
r"^[\{\(]?[0-9A-Fa-f]{8}-?[0-9A-Fa-f]{4}-?[0-9A-Fa-f]{4}-?[0-9A-Fa-f]{4}-?[0-9A-Fa-f]{12}[\}\)]?$"
|
|
||||||
)
|
|
||||||
|
|
||||||
for xml_file in self.xml_files:
|
|
||||||
try:
|
|
||||||
root = lxml.etree.parse(str(xml_file)).getroot()
|
|
||||||
|
|
||||||
# Check all elements for ID attributes
|
|
||||||
for elem in root.iter():
|
|
||||||
for attr, value in elem.attrib.items():
|
|
||||||
# Check if this is an ID attribute
|
|
||||||
attr_name = attr.split("}")[-1].lower()
|
|
||||||
if attr_name == "id" or attr_name.endswith("id"):
|
|
||||||
# Check if value looks like a UUID (has the right length and pattern structure)
|
|
||||||
if self._looks_like_uuid(value):
|
|
||||||
# Validate that it contains only hex characters in the right positions
|
|
||||||
if not uuid_pattern.match(value):
|
|
||||||
errors.append(
|
|
||||||
f" {xml_file.relative_to(self.unpacked_dir)}: "
|
|
||||||
f"Line {elem.sourceline}: ID '{value}' appears to be a UUID but contains invalid hex characters"
|
|
||||||
)
|
|
||||||
|
|
||||||
except (lxml.etree.XMLSyntaxError, Exception) as e:
|
|
||||||
errors.append(
|
|
||||||
f" {xml_file.relative_to(self.unpacked_dir)}: Error: {e}"
|
|
||||||
)
|
|
||||||
|
|
||||||
if errors:
|
|
||||||
print(f"FAILED - Found {len(errors)} UUID ID validation errors:")
|
|
||||||
for error in errors:
|
|
||||||
print(error)
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
if self.verbose:
|
|
||||||
print("PASSED - All UUID-like IDs contain valid hex values")
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _looks_like_uuid(self, value):
|
|
||||||
"""Check if a value has the general structure of a UUID."""
|
|
||||||
# Remove common UUID delimiters
|
|
||||||
clean_value = value.strip("{}()").replace("-", "")
|
|
||||||
# Check if it's 32 hex-like characters (could include invalid hex chars)
|
|
||||||
return len(clean_value) == 32 and all(c.isalnum() for c in clean_value)
|
|
||||||
|
|
||||||
def validate_slide_layout_ids(self):
|
|
||||||
"""Validate that sldLayoutId elements in slide masters reference valid slide layouts."""
|
|
||||||
import lxml.etree
|
|
||||||
|
|
||||||
errors = []
|
|
||||||
|
|
||||||
# Find all slide master files
|
|
||||||
slide_masters = list(self.unpacked_dir.glob("ppt/slideMasters/*.xml"))
|
|
||||||
|
|
||||||
if not slide_masters:
|
|
||||||
if self.verbose:
|
|
||||||
print("PASSED - No slide masters found")
|
|
||||||
return True
|
|
||||||
|
|
||||||
for slide_master in slide_masters:
|
|
||||||
try:
|
|
||||||
# Parse the slide master file
|
|
||||||
root = lxml.etree.parse(str(slide_master)).getroot()
|
|
||||||
|
|
||||||
# Find the corresponding _rels file for this slide master
|
|
||||||
rels_file = slide_master.parent / "_rels" / f"{slide_master.name}.rels"
|
|
||||||
|
|
||||||
if not rels_file.exists():
|
|
||||||
errors.append(
|
|
||||||
f" {slide_master.relative_to(self.unpacked_dir)}: "
|
|
||||||
f"Missing relationships file: {rels_file.relative_to(self.unpacked_dir)}"
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Parse the relationships file
|
|
||||||
rels_root = lxml.etree.parse(str(rels_file)).getroot()
|
|
||||||
|
|
||||||
# Build a set of valid relationship IDs that point to slide layouts
|
|
||||||
valid_layout_rids = set()
|
|
||||||
for rel in rels_root.findall(
|
|
||||||
f".//{{{self.PACKAGE_RELATIONSHIPS_NAMESPACE}}}Relationship"
|
|
||||||
):
|
|
||||||
rel_type = rel.get("Type", "")
|
|
||||||
if "slideLayout" in rel_type:
|
|
||||||
valid_layout_rids.add(rel.get("Id"))
|
|
||||||
|
|
||||||
# Find all sldLayoutId elements in the slide master
|
|
||||||
for sld_layout_id in root.findall(
|
|
||||||
f".//{{{self.PRESENTATIONML_NAMESPACE}}}sldLayoutId"
|
|
||||||
):
|
|
||||||
r_id = sld_layout_id.get(
|
|
||||||
f"{{{self.OFFICE_RELATIONSHIPS_NAMESPACE}}}id"
|
|
||||||
)
|
|
||||||
layout_id = sld_layout_id.get("id")
|
|
||||||
|
|
||||||
if r_id and r_id not in valid_layout_rids:
|
|
||||||
errors.append(
|
|
||||||
f" {slide_master.relative_to(self.unpacked_dir)}: "
|
|
||||||
f"Line {sld_layout_id.sourceline}: sldLayoutId with id='{layout_id}' "
|
|
||||||
f"references r:id='{r_id}' which is not found in slide layout relationships"
|
|
||||||
)
|
|
||||||
|
|
||||||
except (lxml.etree.XMLSyntaxError, Exception) as e:
|
|
||||||
errors.append(
|
|
||||||
f" {slide_master.relative_to(self.unpacked_dir)}: Error: {e}"
|
|
||||||
)
|
|
||||||
|
|
||||||
if errors:
|
|
||||||
print(f"FAILED - Found {len(errors)} slide layout ID validation errors:")
|
|
||||||
for error in errors:
|
|
||||||
print(error)
|
|
||||||
print(
|
|
||||||
"Remove invalid references or add missing slide layouts to the relationships file."
|
|
||||||
)
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
if self.verbose:
|
|
||||||
print("PASSED - All slide layout IDs reference valid slide layouts")
|
|
||||||
return True
|
|
||||||
|
|
||||||
def validate_no_duplicate_slide_layouts(self):
|
|
||||||
"""Validate that each slide has exactly one slideLayout reference."""
|
|
||||||
import lxml.etree
|
|
||||||
|
|
||||||
errors = []
|
|
||||||
slide_rels_files = list(self.unpacked_dir.glob("ppt/slides/_rels/*.xml.rels"))
|
|
||||||
|
|
||||||
for rels_file in slide_rels_files:
|
|
||||||
try:
|
|
||||||
root = lxml.etree.parse(str(rels_file)).getroot()
|
|
||||||
|
|
||||||
# Find all slideLayout relationships
|
|
||||||
layout_rels = [
|
|
||||||
rel
|
|
||||||
for rel in root.findall(
|
|
||||||
f".//{{{self.PACKAGE_RELATIONSHIPS_NAMESPACE}}}Relationship"
|
|
||||||
)
|
|
||||||
if "slideLayout" in rel.get("Type", "")
|
|
||||||
]
|
|
||||||
|
|
||||||
if len(layout_rels) > 1:
|
|
||||||
errors.append(
|
|
||||||
f" {rels_file.relative_to(self.unpacked_dir)}: has {len(layout_rels)} slideLayout references"
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
errors.append(
|
|
||||||
f" {rels_file.relative_to(self.unpacked_dir)}: Error: {e}"
|
|
||||||
)
|
|
||||||
|
|
||||||
if errors:
|
|
||||||
print("FAILED - Found slides with duplicate slideLayout references:")
|
|
||||||
for error in errors:
|
|
||||||
print(error)
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
if self.verbose:
|
|
||||||
print("PASSED - All slides have exactly one slideLayout reference")
|
|
||||||
return True
|
|
||||||
|
|
||||||
def validate_notes_slide_references(self):
|
|
||||||
"""Validate that each notesSlide file is referenced by only one slide."""
|
|
||||||
import lxml.etree
|
|
||||||
|
|
||||||
errors = []
|
|
||||||
notes_slide_references = {} # Track which slides reference each notesSlide
|
|
||||||
|
|
||||||
# Find all slide relationship files
|
|
||||||
slide_rels_files = list(self.unpacked_dir.glob("ppt/slides/_rels/*.xml.rels"))
|
|
||||||
|
|
||||||
if not slide_rels_files:
|
|
||||||
if self.verbose:
|
|
||||||
print("PASSED - No slide relationship files found")
|
|
||||||
return True
|
|
||||||
|
|
||||||
for rels_file in slide_rels_files:
|
|
||||||
try:
|
|
||||||
# Parse the relationships file
|
|
||||||
root = lxml.etree.parse(str(rels_file)).getroot()
|
|
||||||
|
|
||||||
# Find all notesSlide relationships
|
|
||||||
for rel in root.findall(
|
|
||||||
f".//{{{self.PACKAGE_RELATIONSHIPS_NAMESPACE}}}Relationship"
|
|
||||||
):
|
|
||||||
rel_type = rel.get("Type", "")
|
|
||||||
if "notesSlide" in rel_type:
|
|
||||||
target = rel.get("Target", "")
|
|
||||||
if target:
|
|
||||||
# Normalize the target path to handle relative paths
|
|
||||||
normalized_target = target.replace("../", "")
|
|
||||||
|
|
||||||
# Track which slide references this notesSlide
|
|
||||||
slide_name = rels_file.stem.replace(
|
|
||||||
".xml", ""
|
|
||||||
) # e.g., "slide1"
|
|
||||||
|
|
||||||
if normalized_target not in notes_slide_references:
|
|
||||||
notes_slide_references[normalized_target] = []
|
|
||||||
notes_slide_references[normalized_target].append(
|
|
||||||
(slide_name, rels_file)
|
|
||||||
)
|
|
||||||
|
|
||||||
except (lxml.etree.XMLSyntaxError, Exception) as e:
|
|
||||||
errors.append(
|
|
||||||
f" {rels_file.relative_to(self.unpacked_dir)}: Error: {e}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Check for duplicate references
|
|
||||||
for target, references in notes_slide_references.items():
|
|
||||||
if len(references) > 1:
|
|
||||||
slide_names = [ref[0] for ref in references]
|
|
||||||
errors.append(
|
|
||||||
f" Notes slide '{target}' is referenced by multiple slides: {', '.join(slide_names)}"
|
|
||||||
)
|
|
||||||
for slide_name, rels_file in references:
|
|
||||||
errors.append(f" - {rels_file.relative_to(self.unpacked_dir)}")
|
|
||||||
|
|
||||||
if errors:
|
|
||||||
print(
|
|
||||||
f"FAILED - Found {len([e for e in errors if not e.startswith(' ')])} notes slide reference validation errors:"
|
|
||||||
)
|
|
||||||
for error in errors:
|
|
||||||
print(error)
|
|
||||||
print("Each slide may optionally have its own slide file.")
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
if self.verbose:
|
|
||||||
print("PASSED - All notes slide references are unique")
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
raise RuntimeError("This module should not be run directly.")
|
|
||||||
@@ -1,279 +0,0 @@
|
|||||||
"""
|
|
||||||
Validator for tracked changes in Word documents.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import subprocess
|
|
||||||
import tempfile
|
|
||||||
import zipfile
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
|
|
||||||
class RedliningValidator:
|
|
||||||
"""Validator for tracked changes in Word documents."""
|
|
||||||
|
|
||||||
def __init__(self, unpacked_dir, original_docx, verbose=False):
|
|
||||||
self.unpacked_dir = Path(unpacked_dir)
|
|
||||||
self.original_docx = Path(original_docx)
|
|
||||||
self.verbose = verbose
|
|
||||||
self.namespaces = {
|
|
||||||
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
|
|
||||||
}
|
|
||||||
|
|
||||||
def validate(self):
|
|
||||||
"""Main validation method that returns True if valid, False otherwise."""
|
|
||||||
# Verify unpacked directory exists and has correct structure
|
|
||||||
modified_file = self.unpacked_dir / "word" / "document.xml"
|
|
||||||
if not modified_file.exists():
|
|
||||||
print(f"FAILED - Modified document.xml not found at {modified_file}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
# First, check if there are any tracked changes by Claude to validate
|
|
||||||
try:
|
|
||||||
import xml.etree.ElementTree as ET
|
|
||||||
|
|
||||||
tree = ET.parse(modified_file)
|
|
||||||
root = tree.getroot()
|
|
||||||
|
|
||||||
# Check for w:del or w:ins tags authored by Claude
|
|
||||||
del_elements = root.findall(".//w:del", self.namespaces)
|
|
||||||
ins_elements = root.findall(".//w:ins", self.namespaces)
|
|
||||||
|
|
||||||
# Filter to only include changes by Claude
|
|
||||||
claude_del_elements = [
|
|
||||||
elem
|
|
||||||
for elem in del_elements
|
|
||||||
if elem.get(f"{{{self.namespaces['w']}}}author") == "Claude"
|
|
||||||
]
|
|
||||||
claude_ins_elements = [
|
|
||||||
elem
|
|
||||||
for elem in ins_elements
|
|
||||||
if elem.get(f"{{{self.namespaces['w']}}}author") == "Claude"
|
|
||||||
]
|
|
||||||
|
|
||||||
# Redlining validation is only needed if tracked changes by Claude have been used.
|
|
||||||
if not claude_del_elements and not claude_ins_elements:
|
|
||||||
if self.verbose:
|
|
||||||
print("PASSED - No tracked changes by Claude found.")
|
|
||||||
return True
|
|
||||||
|
|
||||||
except Exception:
|
|
||||||
# If we can't parse the XML, continue with full validation
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Create temporary directory for unpacking original docx
|
|
||||||
with tempfile.TemporaryDirectory() as temp_dir:
|
|
||||||
temp_path = Path(temp_dir)
|
|
||||||
|
|
||||||
# Unpack original docx
|
|
||||||
try:
|
|
||||||
with zipfile.ZipFile(self.original_docx, "r") as zip_ref:
|
|
||||||
zip_ref.extractall(temp_path)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"FAILED - Error unpacking original docx: {e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
original_file = temp_path / "word" / "document.xml"
|
|
||||||
if not original_file.exists():
|
|
||||||
print(
|
|
||||||
f"FAILED - Original document.xml not found in {self.original_docx}"
|
|
||||||
)
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Parse both XML files using xml.etree.ElementTree for redlining validation
|
|
||||||
try:
|
|
||||||
import xml.etree.ElementTree as ET
|
|
||||||
|
|
||||||
modified_tree = ET.parse(modified_file)
|
|
||||||
modified_root = modified_tree.getroot()
|
|
||||||
original_tree = ET.parse(original_file)
|
|
||||||
original_root = original_tree.getroot()
|
|
||||||
except ET.ParseError as e:
|
|
||||||
print(f"FAILED - Error parsing XML files: {e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Remove Claude's tracked changes from both documents
|
|
||||||
self._remove_claude_tracked_changes(original_root)
|
|
||||||
self._remove_claude_tracked_changes(modified_root)
|
|
||||||
|
|
||||||
# Extract and compare text content
|
|
||||||
modified_text = self._extract_text_content(modified_root)
|
|
||||||
original_text = self._extract_text_content(original_root)
|
|
||||||
|
|
||||||
if modified_text != original_text:
|
|
||||||
# Show detailed character-level differences for each paragraph
|
|
||||||
error_message = self._generate_detailed_diff(
|
|
||||||
original_text, modified_text
|
|
||||||
)
|
|
||||||
print(error_message)
|
|
||||||
return False
|
|
||||||
|
|
||||||
if self.verbose:
|
|
||||||
print("PASSED - All changes by Claude are properly tracked")
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _generate_detailed_diff(self, original_text, modified_text):
|
|
||||||
"""Generate detailed word-level differences using git word diff."""
|
|
||||||
error_parts = [
|
|
||||||
"FAILED - Document text doesn't match after removing Claude's tracked changes",
|
|
||||||
"",
|
|
||||||
"Likely causes:",
|
|
||||||
" 1. Modified text inside another author's <w:ins> or <w:del> tags",
|
|
||||||
" 2. Made edits without proper tracked changes",
|
|
||||||
" 3. Didn't nest <w:del> inside <w:ins> when deleting another's insertion",
|
|
||||||
"",
|
|
||||||
"For pre-redlined documents, use correct patterns:",
|
|
||||||
" - To reject another's INSERTION: Nest <w:del> inside their <w:ins>",
|
|
||||||
" - To restore another's DELETION: Add new <w:ins> AFTER their <w:del>",
|
|
||||||
"",
|
|
||||||
]
|
|
||||||
|
|
||||||
# Show git word diff
|
|
||||||
git_diff = self._get_git_word_diff(original_text, modified_text)
|
|
||||||
if git_diff:
|
|
||||||
error_parts.extend(["Differences:", "============", git_diff])
|
|
||||||
else:
|
|
||||||
error_parts.append("Unable to generate word diff (git not available)")
|
|
||||||
|
|
||||||
return "\n".join(error_parts)
|
|
||||||
|
|
||||||
def _get_git_word_diff(self, original_text, modified_text):
|
|
||||||
"""Generate word diff using git with character-level precision."""
|
|
||||||
try:
|
|
||||||
with tempfile.TemporaryDirectory() as temp_dir:
|
|
||||||
temp_path = Path(temp_dir)
|
|
||||||
|
|
||||||
# Create two files
|
|
||||||
original_file = temp_path / "original.txt"
|
|
||||||
modified_file = temp_path / "modified.txt"
|
|
||||||
|
|
||||||
original_file.write_text(original_text, encoding="utf-8")
|
|
||||||
modified_file.write_text(modified_text, encoding="utf-8")
|
|
||||||
|
|
||||||
# Try character-level diff first for precise differences
|
|
||||||
result = subprocess.run(
|
|
||||||
[
|
|
||||||
"git",
|
|
||||||
"diff",
|
|
||||||
"--word-diff=plain",
|
|
||||||
"--word-diff-regex=.", # Character-by-character diff
|
|
||||||
"-U0", # Zero lines of context - show only changed lines
|
|
||||||
"--no-index",
|
|
||||||
str(original_file),
|
|
||||||
str(modified_file),
|
|
||||||
],
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
if result.stdout.strip():
|
|
||||||
# Clean up the output - remove git diff header lines
|
|
||||||
lines = result.stdout.split("\n")
|
|
||||||
# Skip the header lines (diff --git, index, +++, ---, @@)
|
|
||||||
content_lines = []
|
|
||||||
in_content = False
|
|
||||||
for line in lines:
|
|
||||||
if line.startswith("@@"):
|
|
||||||
in_content = True
|
|
||||||
continue
|
|
||||||
if in_content and line.strip():
|
|
||||||
content_lines.append(line)
|
|
||||||
|
|
||||||
if content_lines:
|
|
||||||
return "\n".join(content_lines)
|
|
||||||
|
|
||||||
# Fallback to word-level diff if character-level is too verbose
|
|
||||||
result = subprocess.run(
|
|
||||||
[
|
|
||||||
"git",
|
|
||||||
"diff",
|
|
||||||
"--word-diff=plain",
|
|
||||||
"-U0", # Zero lines of context
|
|
||||||
"--no-index",
|
|
||||||
str(original_file),
|
|
||||||
str(modified_file),
|
|
||||||
],
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
if result.stdout.strip():
|
|
||||||
lines = result.stdout.split("\n")
|
|
||||||
content_lines = []
|
|
||||||
in_content = False
|
|
||||||
for line in lines:
|
|
||||||
if line.startswith("@@"):
|
|
||||||
in_content = True
|
|
||||||
continue
|
|
||||||
if in_content and line.strip():
|
|
||||||
content_lines.append(line)
|
|
||||||
return "\n".join(content_lines)
|
|
||||||
|
|
||||||
except (subprocess.CalledProcessError, FileNotFoundError, Exception):
|
|
||||||
# Git not available or other error, return None to use fallback
|
|
||||||
pass
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
def _remove_claude_tracked_changes(self, root):
|
|
||||||
"""Remove tracked changes authored by Claude from the XML root."""
|
|
||||||
ins_tag = f"{{{self.namespaces['w']}}}ins"
|
|
||||||
del_tag = f"{{{self.namespaces['w']}}}del"
|
|
||||||
author_attr = f"{{{self.namespaces['w']}}}author"
|
|
||||||
|
|
||||||
# Remove w:ins elements
|
|
||||||
for parent in root.iter():
|
|
||||||
to_remove = []
|
|
||||||
for child in parent:
|
|
||||||
if child.tag == ins_tag and child.get(author_attr) == "Claude":
|
|
||||||
to_remove.append(child)
|
|
||||||
for elem in to_remove:
|
|
||||||
parent.remove(elem)
|
|
||||||
|
|
||||||
# Unwrap content in w:del elements where author is "Claude"
|
|
||||||
deltext_tag = f"{{{self.namespaces['w']}}}delText"
|
|
||||||
t_tag = f"{{{self.namespaces['w']}}}t"
|
|
||||||
|
|
||||||
for parent in root.iter():
|
|
||||||
to_process = []
|
|
||||||
for child in parent:
|
|
||||||
if child.tag == del_tag and child.get(author_attr) == "Claude":
|
|
||||||
to_process.append((child, list(parent).index(child)))
|
|
||||||
|
|
||||||
# Process in reverse order to maintain indices
|
|
||||||
for del_elem, del_index in reversed(to_process):
|
|
||||||
# Convert w:delText to w:t before moving
|
|
||||||
for elem in del_elem.iter():
|
|
||||||
if elem.tag == deltext_tag:
|
|
||||||
elem.tag = t_tag
|
|
||||||
|
|
||||||
# Move all children of w:del to its parent before removing w:del
|
|
||||||
for child in reversed(list(del_elem)):
|
|
||||||
parent.insert(del_index, child)
|
|
||||||
parent.remove(del_elem)
|
|
||||||
|
|
||||||
def _extract_text_content(self, root):
|
|
||||||
"""Extract text content from Word XML, preserving paragraph structure.
|
|
||||||
|
|
||||||
Empty paragraphs are skipped to avoid false positives when tracked
|
|
||||||
insertions add only structural elements without text content.
|
|
||||||
"""
|
|
||||||
p_tag = f"{{{self.namespaces['w']}}}p"
|
|
||||||
t_tag = f"{{{self.namespaces['w']}}}t"
|
|
||||||
|
|
||||||
paragraphs = []
|
|
||||||
for p_elem in root.findall(f".//{p_tag}"):
|
|
||||||
# Get all text elements within this paragraph
|
|
||||||
text_parts = []
|
|
||||||
for t_elem in p_elem.findall(f".//{t_tag}"):
|
|
||||||
if t_elem.text:
|
|
||||||
text_parts.append(t_elem.text)
|
|
||||||
paragraph_text = "".join(text_parts)
|
|
||||||
# Skip empty paragraphs - they don't affect content validation
|
|
||||||
if paragraph_text:
|
|
||||||
paragraphs.append(paragraph_text)
|
|
||||||
|
|
||||||
return "\n".join(paragraphs)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
raise RuntimeError("This module should not be run directly.")
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
import argparse
|
|
||||||
import json
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
|
|
||||||
def write_file(path, content):
|
|
||||||
path.write_text(content, encoding="utf-8")
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
parser = argparse.ArgumentParser()
|
|
||||||
parser.add_argument("target_dir", help="Output directory for the project")
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
target = Path(args.target_dir).resolve()
|
|
||||||
slides_dir = target / "slides"
|
|
||||||
images_dir = target / "images"
|
|
||||||
tools_dir = target / "tools"
|
|
||||||
|
|
||||||
slides_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
images_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
tools_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
skill_root = Path(__file__).resolve().parents[1]
|
|
||||||
html2pptx_src = skill_root / "scripts" / "html2pptx.js"
|
|
||||||
html2pptx_dst = tools_dir / "html2pptx.js"
|
|
||||||
html2pptx_dst.write_text(
|
|
||||||
html2pptx_src.read_text(encoding="utf-8"), encoding="utf-8"
|
|
||||||
)
|
|
||||||
|
|
||||||
package_json = {
|
|
||||||
"name": "pptx_project",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"private": True,
|
|
||||||
"type": "commonjs",
|
|
||||||
"scripts": {"build": "node create_ppt.js"},
|
|
||||||
"dependencies": {
|
|
||||||
"pptxgenjs": "^3.12.0",
|
|
||||||
"playwright": "^1.51.0",
|
|
||||||
"sharp": "^0.34.1",
|
|
||||||
"react": "^18.2.0",
|
|
||||||
"react-dom": "^18.2.0",
|
|
||||||
"react-icons": "^5.3.0",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
write_file(target / "package.json", json.dumps(package_json, indent=2))
|
|
||||||
|
|
||||||
create_ppt = """const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
const pptxgen = require('pptxgenjs');
|
|
||||||
const html2pptx = require('./tools/html2pptx');
|
|
||||||
|
|
||||||
async function build() {
|
|
||||||
const pptx = new pptxgen();
|
|
||||||
pptx.layout = 'LAYOUT_16x9';
|
|
||||||
pptx.author = 'OpenCode';
|
|
||||||
|
|
||||||
const slidesDir = path.join(__dirname, 'slides');
|
|
||||||
const slideFiles = fs.readdirSync(slidesDir)
|
|
||||||
.filter((f) => f.endsWith('.html'))
|
|
||||||
.sort((a, b) => a.localeCompare(b, undefined, { numeric: true }));
|
|
||||||
|
|
||||||
for (const file of slideFiles) {
|
|
||||||
const filePath = path.join(slidesDir, file);
|
|
||||||
await html2pptx(filePath, pptx);
|
|
||||||
}
|
|
||||||
|
|
||||||
const outPath = path.join(__dirname, 'output.pptx');
|
|
||||||
await pptx.writeFile({ fileName: outPath });
|
|
||||||
}
|
|
||||||
|
|
||||||
build().catch((err) => {
|
|
||||||
console.error(err);
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
"""
|
|
||||||
write_file(target / "create_ppt.js", create_ppt)
|
|
||||||
|
|
||||||
slide1 = """<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset=\"utf-8\" />
|
|
||||||
<style>
|
|
||||||
html { background: #f6f4f1; }
|
|
||||||
body {
|
|
||||||
width: 720pt; height: 405pt; margin: 0; padding: 0;
|
|
||||||
display: flex; font-family: Arial, sans-serif;
|
|
||||||
background: #f6f4f1; color: #1b1f2a;
|
|
||||||
}
|
|
||||||
.wrap { display: flex; width: 100%; height: 100%; }
|
|
||||||
.left { width: 60%; padding: 50pt 40pt; box-sizing: border-box; }
|
|
||||||
h1 { font-size: 36pt; margin: 0 0 10pt 0; }
|
|
||||||
p { font-size: 16pt; margin: 0; color: #3b4252; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class=\"wrap\">
|
|
||||||
<div class=\"left\">
|
|
||||||
<h1>Presentation Title</h1>
|
|
||||||
<p>Subtitle or short description.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
"""
|
|
||||||
slide1_path = slides_dir / "slide1.html"
|
|
||||||
if not slide1_path.exists():
|
|
||||||
write_file(slide1_path, slide1)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -1,983 +0,0 @@
|
|||||||
/**
|
|
||||||
* html2pptx - Convert HTML slide to pptxgenjs slide with positioned elements
|
|
||||||
*
|
|
||||||
* USAGE:
|
|
||||||
* const pptx = new pptxgen();
|
|
||||||
* pptx.layout = 'LAYOUT_16x9'; // Must match HTML body dimensions
|
|
||||||
*
|
|
||||||
* const { slide, placeholders } = await html2pptx('slide.html', pptx);
|
|
||||||
* slide.addChart(pptx.charts.LINE, data, placeholders[0]);
|
|
||||||
*
|
|
||||||
* await pptx.writeFile('output.pptx');
|
|
||||||
*
|
|
||||||
* FEATURES:
|
|
||||||
* - Converts HTML to PowerPoint with accurate positioning
|
|
||||||
* - Supports text, images, shapes, and bullet lists
|
|
||||||
* - Extracts placeholder elements (class="placeholder") with positions
|
|
||||||
* - Handles CSS gradients, borders, and margins
|
|
||||||
*
|
|
||||||
* VALIDATION:
|
|
||||||
* - Uses body width/height from HTML for viewport sizing
|
|
||||||
* - Throws error if HTML dimensions don't match presentation layout
|
|
||||||
* - Throws error if content overflows body (with overflow details)
|
|
||||||
*
|
|
||||||
* RETURNS:
|
|
||||||
* { slide, placeholders } where placeholders is an array of { id, x, y, w, h }
|
|
||||||
*/
|
|
||||||
|
|
||||||
const { chromium } = require('playwright');
|
|
||||||
const path = require('path');
|
|
||||||
const sharp = require('sharp');
|
|
||||||
|
|
||||||
const PT_PER_PX = 0.75;
|
|
||||||
const PX_PER_IN = 96;
|
|
||||||
const EMU_PER_IN = 914400;
|
|
||||||
|
|
||||||
function normalizeFilePath(filePath) {
|
|
||||||
let out = filePath.startsWith('file://') ? filePath.replace('file://', '') : filePath;
|
|
||||||
if (/^\/[A-Za-z]:\//.test(out)) {
|
|
||||||
out = out.slice(1);
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper: Get body dimensions and check for overflow
|
|
||||||
async function getBodyDimensions(page) {
|
|
||||||
const bodyDimensions = await page.evaluate(() => {
|
|
||||||
const body = document.body;
|
|
||||||
const style = window.getComputedStyle(body);
|
|
||||||
|
|
||||||
return {
|
|
||||||
width: parseFloat(style.width),
|
|
||||||
height: parseFloat(style.height),
|
|
||||||
scrollWidth: body.scrollWidth,
|
|
||||||
scrollHeight: body.scrollHeight
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const errors = [];
|
|
||||||
const widthOverflowPx = Math.max(0, bodyDimensions.scrollWidth - bodyDimensions.width - 1);
|
|
||||||
const heightOverflowPx = Math.max(0, bodyDimensions.scrollHeight - bodyDimensions.height - 1);
|
|
||||||
|
|
||||||
const widthOverflowPt = widthOverflowPx * PT_PER_PX;
|
|
||||||
const heightOverflowPt = heightOverflowPx * PT_PER_PX;
|
|
||||||
|
|
||||||
if (widthOverflowPt > 0 || heightOverflowPt > 0) {
|
|
||||||
const directions = [];
|
|
||||||
if (widthOverflowPt > 0) directions.push(`${widthOverflowPt.toFixed(1)}pt horizontally`);
|
|
||||||
if (heightOverflowPt > 0) directions.push(`${heightOverflowPt.toFixed(1)}pt vertically`);
|
|
||||||
const reminder = heightOverflowPt > 0 ? ' (Remember: leave 0.5" margin at bottom of slide)' : '';
|
|
||||||
errors.push(`HTML content overflows body by ${directions.join(' and ')}${reminder}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return { ...bodyDimensions, errors };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper: Validate dimensions match presentation layout
|
|
||||||
function validateDimensions(bodyDimensions, pres) {
|
|
||||||
const errors = [];
|
|
||||||
const widthInches = bodyDimensions.width / PX_PER_IN;
|
|
||||||
const heightInches = bodyDimensions.height / PX_PER_IN;
|
|
||||||
|
|
||||||
if (pres.presLayout) {
|
|
||||||
const layoutWidth = pres.presLayout.width / EMU_PER_IN;
|
|
||||||
const layoutHeight = pres.presLayout.height / EMU_PER_IN;
|
|
||||||
|
|
||||||
if (Math.abs(layoutWidth - widthInches) > 0.1 || Math.abs(layoutHeight - heightInches) > 0.1) {
|
|
||||||
errors.push(
|
|
||||||
`HTML dimensions (${widthInches.toFixed(1)}" × ${heightInches.toFixed(1)}") ` +
|
|
||||||
`don't match presentation layout (${layoutWidth.toFixed(1)}" × ${layoutHeight.toFixed(1)}")`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return errors;
|
|
||||||
}
|
|
||||||
|
|
||||||
function validateTextBoxPosition(slideData, bodyDimensions) {
|
|
||||||
const errors = [];
|
|
||||||
const slideHeightInches = bodyDimensions.height / PX_PER_IN;
|
|
||||||
const minBottomMargin = 0.5; // 0.5 inches from bottom
|
|
||||||
|
|
||||||
for (const el of slideData.elements) {
|
|
||||||
// Check text elements (p, h1-h6, list)
|
|
||||||
if (['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'list'].includes(el.type)) {
|
|
||||||
const fontSize = el.style?.fontSize || 0;
|
|
||||||
const bottomEdge = el.position.y + el.position.h;
|
|
||||||
const distanceFromBottom = slideHeightInches - bottomEdge;
|
|
||||||
|
|
||||||
if (fontSize > 12 && distanceFromBottom < minBottomMargin) {
|
|
||||||
const getText = () => {
|
|
||||||
if (typeof el.text === 'string') return el.text;
|
|
||||||
if (Array.isArray(el.text)) return el.text.find(t => t.text)?.text || '';
|
|
||||||
if (Array.isArray(el.items)) return el.items.find(item => item.text)?.text || '';
|
|
||||||
return '';
|
|
||||||
};
|
|
||||||
const textPrefix = getText().substring(0, 50) + (getText().length > 50 ? '...' : '');
|
|
||||||
|
|
||||||
errors.push(
|
|
||||||
`Text box "${textPrefix}" ends too close to bottom edge ` +
|
|
||||||
`(${distanceFromBottom.toFixed(2)}" from bottom, minimum ${minBottomMargin}" required)`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper: Add background to slide
|
|
||||||
async function addBackground(slideData, targetSlide, tmpDir) {
|
|
||||||
if (slideData.background.type === 'image' && slideData.background.path) {
|
|
||||||
targetSlide.background = { path: normalizeFilePath(slideData.background.path) };
|
|
||||||
} else if (slideData.background.type === 'color' && slideData.background.value) {
|
|
||||||
targetSlide.background = { color: slideData.background.value };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper: Add elements to slide
|
|
||||||
function addElements(slideData, targetSlide, pres) {
|
|
||||||
for (const el of slideData.elements) {
|
|
||||||
if (el.type === 'image') {
|
|
||||||
targetSlide.addImage({
|
|
||||||
path: normalizeFilePath(el.src),
|
|
||||||
x: el.position.x,
|
|
||||||
y: el.position.y,
|
|
||||||
w: el.position.w,
|
|
||||||
h: el.position.h
|
|
||||||
});
|
|
||||||
} else if (el.type === 'line') {
|
|
||||||
targetSlide.addShape(pres.ShapeType.line, {
|
|
||||||
x: el.x1,
|
|
||||||
y: el.y1,
|
|
||||||
w: el.x2 - el.x1,
|
|
||||||
h: el.y2 - el.y1,
|
|
||||||
line: { color: el.color, width: el.width }
|
|
||||||
});
|
|
||||||
} else if (el.type === 'shape') {
|
|
||||||
const shapeOptions = {
|
|
||||||
x: el.position.x,
|
|
||||||
y: el.position.y,
|
|
||||||
w: el.position.w,
|
|
||||||
h: el.position.h,
|
|
||||||
shape: el.shape.rectRadius > 0 ? pres.ShapeType.roundRect : pres.ShapeType.rect
|
|
||||||
};
|
|
||||||
|
|
||||||
if (el.shape.fill) {
|
|
||||||
shapeOptions.fill = { color: el.shape.fill };
|
|
||||||
if (el.shape.transparency != null) shapeOptions.fill.transparency = el.shape.transparency;
|
|
||||||
}
|
|
||||||
if (el.shape.line) shapeOptions.line = el.shape.line;
|
|
||||||
if (el.shape.rectRadius > 0) shapeOptions.rectRadius = el.shape.rectRadius;
|
|
||||||
if (el.shape.shadow) shapeOptions.shadow = el.shape.shadow;
|
|
||||||
|
|
||||||
targetSlide.addText(el.text || '', shapeOptions);
|
|
||||||
} else if (el.type === 'list') {
|
|
||||||
const listOptions = {
|
|
||||||
x: el.position.x,
|
|
||||||
y: el.position.y,
|
|
||||||
w: el.position.w,
|
|
||||||
h: el.position.h,
|
|
||||||
fontSize: el.style.fontSize,
|
|
||||||
fontFace: el.style.fontFace,
|
|
||||||
color: el.style.color,
|
|
||||||
align: el.style.align,
|
|
||||||
valign: 'top',
|
|
||||||
lineSpacing: el.style.lineSpacing,
|
|
||||||
paraSpaceBefore: el.style.paraSpaceBefore,
|
|
||||||
paraSpaceAfter: el.style.paraSpaceAfter,
|
|
||||||
margin: el.style.margin
|
|
||||||
};
|
|
||||||
if (el.style.margin) listOptions.margin = el.style.margin;
|
|
||||||
targetSlide.addText(el.items, listOptions);
|
|
||||||
} else {
|
|
||||||
// Check if text is single-line (height suggests one line)
|
|
||||||
const lineHeight = el.style.lineSpacing || el.style.fontSize * 1.2;
|
|
||||||
const isSingleLine = el.position.h <= lineHeight * 1.5;
|
|
||||||
|
|
||||||
let adjustedX = el.position.x;
|
|
||||||
let adjustedW = el.position.w;
|
|
||||||
|
|
||||||
// Make single-line text 2% wider to account for underestimate
|
|
||||||
if (isSingleLine) {
|
|
||||||
const widthIncrease = el.position.w * 0.02;
|
|
||||||
const align = el.style.align;
|
|
||||||
|
|
||||||
if (align === 'center') {
|
|
||||||
// Center: expand both sides
|
|
||||||
adjustedX = el.position.x - (widthIncrease / 2);
|
|
||||||
adjustedW = el.position.w + widthIncrease;
|
|
||||||
} else if (align === 'right') {
|
|
||||||
// Right: expand to the left
|
|
||||||
adjustedX = el.position.x - widthIncrease;
|
|
||||||
adjustedW = el.position.w + widthIncrease;
|
|
||||||
} else {
|
|
||||||
// Left (default): expand to the right
|
|
||||||
adjustedW = el.position.w + widthIncrease;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const textOptions = {
|
|
||||||
x: adjustedX,
|
|
||||||
y: el.position.y,
|
|
||||||
w: adjustedW,
|
|
||||||
h: el.position.h,
|
|
||||||
fontSize: el.style.fontSize,
|
|
||||||
fontFace: el.style.fontFace,
|
|
||||||
color: el.style.color,
|
|
||||||
bold: el.style.bold,
|
|
||||||
italic: el.style.italic,
|
|
||||||
underline: el.style.underline,
|
|
||||||
valign: 'top',
|
|
||||||
lineSpacing: el.style.lineSpacing,
|
|
||||||
paraSpaceBefore: el.style.paraSpaceBefore,
|
|
||||||
paraSpaceAfter: el.style.paraSpaceAfter,
|
|
||||||
inset: 0 // Remove default PowerPoint internal padding
|
|
||||||
};
|
|
||||||
|
|
||||||
if (el.style.align) textOptions.align = el.style.align;
|
|
||||||
if (el.style.margin) textOptions.margin = el.style.margin;
|
|
||||||
if (el.style.rotate !== undefined) textOptions.rotate = el.style.rotate;
|
|
||||||
if (el.style.transparency !== null && el.style.transparency !== undefined) textOptions.transparency = el.style.transparency;
|
|
||||||
|
|
||||||
targetSlide.addText(el.text, textOptions);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper: Extract slide data from HTML page
|
|
||||||
async function extractSlideData(page) {
|
|
||||||
return await page.evaluate(() => {
|
|
||||||
const PT_PER_PX = 0.75;
|
|
||||||
const PX_PER_IN = 96;
|
|
||||||
|
|
||||||
// Fonts that are single-weight and should not have bold applied
|
|
||||||
// (applying bold causes PowerPoint to use faux bold which makes text wider)
|
|
||||||
const SINGLE_WEIGHT_FONTS = ['impact'];
|
|
||||||
|
|
||||||
// Helper: Check if a font should skip bold formatting
|
|
||||||
const shouldSkipBold = (fontFamily) => {
|
|
||||||
if (!fontFamily) return false;
|
|
||||||
const normalizedFont = fontFamily.toLowerCase().replace(/['"]/g, '').split(',')[0].trim();
|
|
||||||
return SINGLE_WEIGHT_FONTS.includes(normalizedFont);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Unit conversion helpers
|
|
||||||
const pxToInch = (px) => px / PX_PER_IN;
|
|
||||||
const pxToPoints = (pxStr) => parseFloat(pxStr) * PT_PER_PX;
|
|
||||||
const rgbToHex = (rgbStr) => {
|
|
||||||
// Handle transparent backgrounds by defaulting to white
|
|
||||||
if (rgbStr === 'rgba(0, 0, 0, 0)' || rgbStr === 'transparent') return 'FFFFFF';
|
|
||||||
|
|
||||||
const match = rgbStr.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
|
|
||||||
if (!match) return 'FFFFFF';
|
|
||||||
return match.slice(1).map(n => parseInt(n).toString(16).padStart(2, '0')).join('');
|
|
||||||
};
|
|
||||||
|
|
||||||
const extractAlpha = (rgbStr) => {
|
|
||||||
const match = rgbStr.match(/rgba\((\d+),\s*(\d+),\s*(\d+),\s*([\d.]+)\)/);
|
|
||||||
if (!match || !match[4]) return null;
|
|
||||||
const alpha = parseFloat(match[4]);
|
|
||||||
return Math.round((1 - alpha) * 100);
|
|
||||||
};
|
|
||||||
|
|
||||||
const applyTextTransform = (text, textTransform) => {
|
|
||||||
if (textTransform === 'uppercase') return text.toUpperCase();
|
|
||||||
if (textTransform === 'lowercase') return text.toLowerCase();
|
|
||||||
if (textTransform === 'capitalize') {
|
|
||||||
return text.replace(/\b\w/g, c => c.toUpperCase());
|
|
||||||
}
|
|
||||||
return text;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Extract rotation angle from CSS transform and writing-mode
|
|
||||||
const getRotation = (transform, writingMode) => {
|
|
||||||
let angle = 0;
|
|
||||||
|
|
||||||
// Handle writing-mode first
|
|
||||||
// PowerPoint: 90° = text rotated 90° clockwise (reads top to bottom, letters upright)
|
|
||||||
// PowerPoint: 270° = text rotated 270° clockwise (reads bottom to top, letters upright)
|
|
||||||
if (writingMode === 'vertical-rl') {
|
|
||||||
// vertical-rl alone = text reads top to bottom = 90° in PowerPoint
|
|
||||||
angle = 90;
|
|
||||||
} else if (writingMode === 'vertical-lr') {
|
|
||||||
// vertical-lr alone = text reads bottom to top = 270° in PowerPoint
|
|
||||||
angle = 270;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then add any transform rotation
|
|
||||||
if (transform && transform !== 'none') {
|
|
||||||
// Try to match rotate() function
|
|
||||||
const rotateMatch = transform.match(/rotate\((-?\d+(?:\.\d+)?)deg\)/);
|
|
||||||
if (rotateMatch) {
|
|
||||||
angle += parseFloat(rotateMatch[1]);
|
|
||||||
} else {
|
|
||||||
// Browser may compute as matrix - extract rotation from matrix
|
|
||||||
const matrixMatch = transform.match(/matrix\(([^)]+)\)/);
|
|
||||||
if (matrixMatch) {
|
|
||||||
const values = matrixMatch[1].split(',').map(parseFloat);
|
|
||||||
// matrix(a, b, c, d, e, f) where rotation = atan2(b, a)
|
|
||||||
const matrixAngle = Math.atan2(values[1], values[0]) * (180 / Math.PI);
|
|
||||||
angle += Math.round(matrixAngle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Normalize to 0-359 range
|
|
||||||
angle = angle % 360;
|
|
||||||
if (angle < 0) angle += 360;
|
|
||||||
|
|
||||||
return angle === 0 ? null : angle;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get position/dimensions accounting for rotation
|
|
||||||
const getPositionAndSize = (el, rect, rotation) => {
|
|
||||||
if (rotation === null) {
|
|
||||||
return { x: rect.left, y: rect.top, w: rect.width, h: rect.height };
|
|
||||||
}
|
|
||||||
|
|
||||||
// For 90° or 270° rotations, swap width and height
|
|
||||||
// because PowerPoint applies rotation to the original (unrotated) box
|
|
||||||
const isVertical = rotation === 90 || rotation === 270;
|
|
||||||
|
|
||||||
if (isVertical) {
|
|
||||||
// The browser shows us the rotated dimensions (tall box for vertical text)
|
|
||||||
// But PowerPoint needs the pre-rotation dimensions (wide box that will be rotated)
|
|
||||||
// So we swap: browser's height becomes PPT's width, browser's width becomes PPT's height
|
|
||||||
const centerX = rect.left + rect.width / 2;
|
|
||||||
const centerY = rect.top + rect.height / 2;
|
|
||||||
|
|
||||||
return {
|
|
||||||
x: centerX - rect.height / 2,
|
|
||||||
y: centerY - rect.width / 2,
|
|
||||||
w: rect.height,
|
|
||||||
h: rect.width
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// For other rotations, use element's offset dimensions
|
|
||||||
const centerX = rect.left + rect.width / 2;
|
|
||||||
const centerY = rect.top + rect.height / 2;
|
|
||||||
return {
|
|
||||||
x: centerX - el.offsetWidth / 2,
|
|
||||||
y: centerY - el.offsetHeight / 2,
|
|
||||||
w: el.offsetWidth,
|
|
||||||
h: el.offsetHeight
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// Parse CSS box-shadow into PptxGenJS shadow properties
|
|
||||||
const parseBoxShadow = (boxShadow) => {
|
|
||||||
if (!boxShadow || boxShadow === 'none') return null;
|
|
||||||
|
|
||||||
// Browser computed style format: "rgba(0, 0, 0, 0.3) 2px 2px 8px 0px [inset]"
|
|
||||||
// CSS format: "[inset] 2px 2px 8px 0px rgba(0, 0, 0, 0.3)"
|
|
||||||
|
|
||||||
const insetMatch = boxShadow.match(/inset/);
|
|
||||||
|
|
||||||
// IMPORTANT: PptxGenJS/PowerPoint doesn't properly support inset shadows
|
|
||||||
// Only process outer shadows to avoid file corruption
|
|
||||||
if (insetMatch) return null;
|
|
||||||
|
|
||||||
// Extract color first (rgba or rgb at start)
|
|
||||||
const colorMatch = boxShadow.match(/rgba?\([^)]+\)/);
|
|
||||||
|
|
||||||
// Extract numeric values (handles both px and pt units)
|
|
||||||
const parts = boxShadow.match(/([-\d.]+)(px|pt)/g);
|
|
||||||
|
|
||||||
if (!parts || parts.length < 2) return null;
|
|
||||||
|
|
||||||
const offsetX = parseFloat(parts[0]);
|
|
||||||
const offsetY = parseFloat(parts[1]);
|
|
||||||
const blur = parts.length > 2 ? parseFloat(parts[2]) : 0;
|
|
||||||
|
|
||||||
// Calculate angle from offsets (in degrees, 0 = right, 90 = down)
|
|
||||||
let angle = 0;
|
|
||||||
if (offsetX !== 0 || offsetY !== 0) {
|
|
||||||
angle = Math.atan2(offsetY, offsetX) * (180 / Math.PI);
|
|
||||||
if (angle < 0) angle += 360;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate offset distance (hypotenuse)
|
|
||||||
const offset = Math.sqrt(offsetX * offsetX + offsetY * offsetY) * PT_PER_PX;
|
|
||||||
|
|
||||||
// Extract opacity from rgba
|
|
||||||
let opacity = 0.5;
|
|
||||||
if (colorMatch) {
|
|
||||||
const opacityMatch = colorMatch[0].match(/[\d.]+\)$/);
|
|
||||||
if (opacityMatch) {
|
|
||||||
opacity = parseFloat(opacityMatch[0].replace(')', ''));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: 'outer',
|
|
||||||
angle: Math.round(angle),
|
|
||||||
blur: blur * 0.75, // Convert to points
|
|
||||||
color: colorMatch ? rgbToHex(colorMatch[0]) : '000000',
|
|
||||||
offset: offset,
|
|
||||||
opacity
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// Parse inline formatting tags (<b>, <i>, <u>, <strong>, <em>, <span>) into text runs
|
|
||||||
const parseInlineFormatting = (element, baseOptions = {}, runs = [], baseTextTransform = (x) => x) => {
|
|
||||||
let prevNodeIsText = false;
|
|
||||||
|
|
||||||
element.childNodes.forEach((node) => {
|
|
||||||
let textTransform = baseTextTransform;
|
|
||||||
|
|
||||||
const isText = node.nodeType === Node.TEXT_NODE || node.tagName === 'BR';
|
|
||||||
if (isText) {
|
|
||||||
const text = node.tagName === 'BR' ? '\n' : textTransform(node.textContent.replace(/\s+/g, ' '));
|
|
||||||
const prevRun = runs[runs.length - 1];
|
|
||||||
if (prevNodeIsText && prevRun) {
|
|
||||||
prevRun.text += text;
|
|
||||||
} else {
|
|
||||||
runs.push({ text, options: { ...baseOptions } });
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (node.nodeType === Node.ELEMENT_NODE && node.textContent.trim()) {
|
|
||||||
const options = { ...baseOptions };
|
|
||||||
const computed = window.getComputedStyle(node);
|
|
||||||
|
|
||||||
// Handle inline elements with computed styles
|
|
||||||
if (node.tagName === 'SPAN' || node.tagName === 'B' || node.tagName === 'STRONG' || node.tagName === 'I' || node.tagName === 'EM' || node.tagName === 'U') {
|
|
||||||
const isBold = computed.fontWeight === 'bold' || parseInt(computed.fontWeight) >= 600;
|
|
||||||
if (isBold && !shouldSkipBold(computed.fontFamily)) options.bold = true;
|
|
||||||
if (computed.fontStyle === 'italic') options.italic = true;
|
|
||||||
if (computed.textDecoration && computed.textDecoration.includes('underline')) options.underline = true;
|
|
||||||
if (computed.color && computed.color !== 'rgb(0, 0, 0)') {
|
|
||||||
options.color = rgbToHex(computed.color);
|
|
||||||
const transparency = extractAlpha(computed.color);
|
|
||||||
if (transparency !== null) options.transparency = transparency;
|
|
||||||
}
|
|
||||||
if (computed.fontSize) options.fontSize = pxToPoints(computed.fontSize);
|
|
||||||
|
|
||||||
// Apply text-transform on the span element itself
|
|
||||||
if (computed.textTransform && computed.textTransform !== 'none') {
|
|
||||||
const transformStr = computed.textTransform;
|
|
||||||
textTransform = (text) => applyTextTransform(text, transformStr);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate: Check for margins on inline elements
|
|
||||||
if (computed.marginLeft && parseFloat(computed.marginLeft) > 0) {
|
|
||||||
errors.push(`Inline element <${node.tagName.toLowerCase()}> has margin-left which is not supported in PowerPoint. Remove margin from inline elements.`);
|
|
||||||
}
|
|
||||||
if (computed.marginRight && parseFloat(computed.marginRight) > 0) {
|
|
||||||
errors.push(`Inline element <${node.tagName.toLowerCase()}> has margin-right which is not supported in PowerPoint. Remove margin from inline elements.`);
|
|
||||||
}
|
|
||||||
if (computed.marginTop && parseFloat(computed.marginTop) > 0) {
|
|
||||||
errors.push(`Inline element <${node.tagName.toLowerCase()}> has margin-top which is not supported in PowerPoint. Remove margin from inline elements.`);
|
|
||||||
}
|
|
||||||
if (computed.marginBottom && parseFloat(computed.marginBottom) > 0) {
|
|
||||||
errors.push(`Inline element <${node.tagName.toLowerCase()}> has margin-bottom which is not supported in PowerPoint. Remove margin from inline elements.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recursively process the child node. This will flatten nested spans into multiple runs.
|
|
||||||
parseInlineFormatting(node, options, runs, textTransform);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
prevNodeIsText = isText;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Trim leading space from first run and trailing space from last run
|
|
||||||
if (runs.length > 0) {
|
|
||||||
runs[0].text = runs[0].text.replace(/^\s+/, '');
|
|
||||||
runs[runs.length - 1].text = runs[runs.length - 1].text.replace(/\s+$/, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
return runs.filter(r => r.text.length > 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Extract background from body (image or color)
|
|
||||||
const body = document.body;
|
|
||||||
const bodyStyle = window.getComputedStyle(body);
|
|
||||||
const bgImage = bodyStyle.backgroundImage;
|
|
||||||
const bgColor = bodyStyle.backgroundColor;
|
|
||||||
|
|
||||||
// Collect validation errors
|
|
||||||
const errors = [];
|
|
||||||
|
|
||||||
// Validate: Check for CSS gradients
|
|
||||||
if (bgImage && (bgImage.includes('linear-gradient') || bgImage.includes('radial-gradient'))) {
|
|
||||||
errors.push(
|
|
||||||
'CSS gradients are not supported. Use Sharp to rasterize gradients as PNG images first, ' +
|
|
||||||
'then reference with background-image: url(\'gradient.png\')'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let background;
|
|
||||||
if (bgImage && bgImage !== 'none') {
|
|
||||||
// Extract URL from url("...") or url(...)
|
|
||||||
const urlMatch = bgImage.match(/url\(["']?([^"')]+)["']?\)/);
|
|
||||||
if (urlMatch) {
|
|
||||||
background = {
|
|
||||||
type: 'image',
|
|
||||||
path: urlMatch[1]
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
background = {
|
|
||||||
type: 'color',
|
|
||||||
value: rgbToHex(bgColor)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
background = {
|
|
||||||
type: 'color',
|
|
||||||
value: rgbToHex(bgColor)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process all elements
|
|
||||||
const elements = [];
|
|
||||||
const placeholders = [];
|
|
||||||
const textTags = ['P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'UL', 'OL', 'LI'];
|
|
||||||
const processed = new Set();
|
|
||||||
|
|
||||||
document.querySelectorAll('*').forEach((el) => {
|
|
||||||
if (processed.has(el)) return;
|
|
||||||
|
|
||||||
// Validate text elements don't have backgrounds, borders, or shadows
|
|
||||||
if (textTags.includes(el.tagName)) {
|
|
||||||
const computed = window.getComputedStyle(el);
|
|
||||||
const hasBg = computed.backgroundColor && computed.backgroundColor !== 'rgba(0, 0, 0, 0)';
|
|
||||||
const hasBorder = (computed.borderWidth && parseFloat(computed.borderWidth) > 0) ||
|
|
||||||
(computed.borderTopWidth && parseFloat(computed.borderTopWidth) > 0) ||
|
|
||||||
(computed.borderRightWidth && parseFloat(computed.borderRightWidth) > 0) ||
|
|
||||||
(computed.borderBottomWidth && parseFloat(computed.borderBottomWidth) > 0) ||
|
|
||||||
(computed.borderLeftWidth && parseFloat(computed.borderLeftWidth) > 0);
|
|
||||||
const hasShadow = computed.boxShadow && computed.boxShadow !== 'none';
|
|
||||||
|
|
||||||
if (hasBg || hasBorder || hasShadow) {
|
|
||||||
errors.push(
|
|
||||||
`Text element <${el.tagName.toLowerCase()}> has ${hasBg ? 'background' : hasBorder ? 'border' : 'shadow'}. ` +
|
|
||||||
'Backgrounds, borders, and shadows are only supported on <div> elements, not text elements.'
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract placeholder elements (for charts, etc.)
|
|
||||||
if (el.className && el.className.includes('placeholder')) {
|
|
||||||
const rect = el.getBoundingClientRect();
|
|
||||||
if (rect.width === 0 || rect.height === 0) {
|
|
||||||
errors.push(
|
|
||||||
`Placeholder "${el.id || 'unnamed'}" has ${rect.width === 0 ? 'width: 0' : 'height: 0'}. Check the layout CSS.`
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
placeholders.push({
|
|
||||||
id: el.id || `placeholder-${placeholders.length}`,
|
|
||||||
x: pxToInch(rect.left),
|
|
||||||
y: pxToInch(rect.top),
|
|
||||||
w: pxToInch(rect.width),
|
|
||||||
h: pxToInch(rect.height)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
processed.add(el);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract images
|
|
||||||
if (el.tagName === 'IMG') {
|
|
||||||
const rect = el.getBoundingClientRect();
|
|
||||||
if (rect.width > 0 && rect.height > 0) {
|
|
||||||
elements.push({
|
|
||||||
type: 'image',
|
|
||||||
src: el.src,
|
|
||||||
position: {
|
|
||||||
x: pxToInch(rect.left),
|
|
||||||
y: pxToInch(rect.top),
|
|
||||||
w: pxToInch(rect.width),
|
|
||||||
h: pxToInch(rect.height)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
processed.add(el);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract DIVs with backgrounds/borders as shapes
|
|
||||||
const isContainer = el.tagName === 'DIV' && !textTags.includes(el.tagName);
|
|
||||||
if (isContainer) {
|
|
||||||
const computed = window.getComputedStyle(el);
|
|
||||||
const hasBg = computed.backgroundColor && computed.backgroundColor !== 'rgba(0, 0, 0, 0)';
|
|
||||||
|
|
||||||
// Validate: Check for unwrapped text content in DIV
|
|
||||||
for (const node of el.childNodes) {
|
|
||||||
if (node.nodeType === Node.TEXT_NODE) {
|
|
||||||
const text = node.textContent.trim();
|
|
||||||
if (text) {
|
|
||||||
errors.push(
|
|
||||||
`DIV element contains unwrapped text "${text.substring(0, 50)}${text.length > 50 ? '...' : ''}". ` +
|
|
||||||
'All text must be wrapped in <p>, <h1>-<h6>, <ul>, or <ol> tags to appear in PowerPoint.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for background images on shapes
|
|
||||||
const bgImage = computed.backgroundImage;
|
|
||||||
if (bgImage && bgImage !== 'none') {
|
|
||||||
errors.push(
|
|
||||||
'Background images on DIV elements are not supported. ' +
|
|
||||||
'Use solid colors or borders for shapes, or use slide.addImage() in PptxGenJS to layer images.'
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for borders - both uniform and partial
|
|
||||||
const borderTop = computed.borderTopWidth;
|
|
||||||
const borderRight = computed.borderRightWidth;
|
|
||||||
const borderBottom = computed.borderBottomWidth;
|
|
||||||
const borderLeft = computed.borderLeftWidth;
|
|
||||||
const borders = [borderTop, borderRight, borderBottom, borderLeft].map(b => parseFloat(b) || 0);
|
|
||||||
const hasBorder = borders.some(b => b > 0);
|
|
||||||
const hasUniformBorder = hasBorder && borders.every(b => b === borders[0]);
|
|
||||||
const borderLines = [];
|
|
||||||
|
|
||||||
if (hasBorder && !hasUniformBorder) {
|
|
||||||
const rect = el.getBoundingClientRect();
|
|
||||||
const x = pxToInch(rect.left);
|
|
||||||
const y = pxToInch(rect.top);
|
|
||||||
const w = pxToInch(rect.width);
|
|
||||||
const h = pxToInch(rect.height);
|
|
||||||
|
|
||||||
// Collect lines to add after shape (inset by half the line width to center on edge)
|
|
||||||
if (parseFloat(borderTop) > 0) {
|
|
||||||
const widthPt = pxToPoints(borderTop);
|
|
||||||
const inset = (widthPt / 72) / 2; // Convert points to inches, then half
|
|
||||||
borderLines.push({
|
|
||||||
type: 'line',
|
|
||||||
x1: x, y1: y + inset, x2: x + w, y2: y + inset,
|
|
||||||
width: widthPt,
|
|
||||||
color: rgbToHex(computed.borderTopColor)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (parseFloat(borderRight) > 0) {
|
|
||||||
const widthPt = pxToPoints(borderRight);
|
|
||||||
const inset = (widthPt / 72) / 2;
|
|
||||||
borderLines.push({
|
|
||||||
type: 'line',
|
|
||||||
x1: x + w - inset, y1: y, x2: x + w - inset, y2: y + h,
|
|
||||||
width: widthPt,
|
|
||||||
color: rgbToHex(computed.borderRightColor)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (parseFloat(borderBottom) > 0) {
|
|
||||||
const widthPt = pxToPoints(borderBottom);
|
|
||||||
const inset = (widthPt / 72) / 2;
|
|
||||||
borderLines.push({
|
|
||||||
type: 'line',
|
|
||||||
x1: x, y1: y + h - inset, x2: x + w, y2: y + h - inset,
|
|
||||||
width: widthPt,
|
|
||||||
color: rgbToHex(computed.borderBottomColor)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (parseFloat(borderLeft) > 0) {
|
|
||||||
const widthPt = pxToPoints(borderLeft);
|
|
||||||
const inset = (widthPt / 72) / 2;
|
|
||||||
borderLines.push({
|
|
||||||
type: 'line',
|
|
||||||
x1: x + inset, y1: y, x2: x + inset, y2: y + h,
|
|
||||||
width: widthPt,
|
|
||||||
color: rgbToHex(computed.borderLeftColor)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasBg || hasBorder) {
|
|
||||||
const rect = el.getBoundingClientRect();
|
|
||||||
if (rect.width > 0 && rect.height > 0) {
|
|
||||||
const shadow = parseBoxShadow(computed.boxShadow);
|
|
||||||
|
|
||||||
// Only add shape if there's background or uniform border
|
|
||||||
if (hasBg || hasUniformBorder) {
|
|
||||||
elements.push({
|
|
||||||
type: 'shape',
|
|
||||||
text: '', // Shape only - child text elements render on top
|
|
||||||
position: {
|
|
||||||
x: pxToInch(rect.left),
|
|
||||||
y: pxToInch(rect.top),
|
|
||||||
w: pxToInch(rect.width),
|
|
||||||
h: pxToInch(rect.height)
|
|
||||||
},
|
|
||||||
shape: {
|
|
||||||
fill: hasBg ? rgbToHex(computed.backgroundColor) : null,
|
|
||||||
transparency: hasBg ? extractAlpha(computed.backgroundColor) : null,
|
|
||||||
line: hasUniformBorder ? {
|
|
||||||
color: rgbToHex(computed.borderColor),
|
|
||||||
width: pxToPoints(computed.borderWidth)
|
|
||||||
} : null,
|
|
||||||
// Convert border-radius to rectRadius (in inches)
|
|
||||||
// % values: 50%+ = circle (1), <50% = percentage of min dimension
|
|
||||||
// pt values: divide by 72 (72pt = 1 inch)
|
|
||||||
// px values: divide by 96 (96px = 1 inch)
|
|
||||||
rectRadius: (() => {
|
|
||||||
const radius = computed.borderRadius;
|
|
||||||
const radiusValue = parseFloat(radius);
|
|
||||||
if (radiusValue === 0) return 0;
|
|
||||||
|
|
||||||
if (radius.includes('%')) {
|
|
||||||
if (radiusValue >= 50) return 1;
|
|
||||||
// Calculate percentage of smaller dimension
|
|
||||||
const minDim = Math.min(rect.width, rect.height);
|
|
||||||
return (radiusValue / 100) * pxToInch(minDim);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (radius.includes('pt')) return radiusValue / 72;
|
|
||||||
return radiusValue / PX_PER_IN;
|
|
||||||
})(),
|
|
||||||
shadow: shadow
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add partial border lines
|
|
||||||
elements.push(...borderLines);
|
|
||||||
|
|
||||||
processed.add(el);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract bullet lists as single text block
|
|
||||||
if (el.tagName === 'UL' || el.tagName === 'OL') {
|
|
||||||
const rect = el.getBoundingClientRect();
|
|
||||||
if (rect.width === 0 || rect.height === 0) return;
|
|
||||||
|
|
||||||
const liElements = Array.from(el.querySelectorAll('li'));
|
|
||||||
const items = [];
|
|
||||||
const ulComputed = window.getComputedStyle(el);
|
|
||||||
const ulPaddingLeftPt = pxToPoints(ulComputed.paddingLeft);
|
|
||||||
|
|
||||||
// Split: margin-left for bullet position, indent for text position
|
|
||||||
// margin-left + indent = ul padding-left
|
|
||||||
const marginLeft = ulPaddingLeftPt * 0.5;
|
|
||||||
const textIndent = ulPaddingLeftPt * 0.5;
|
|
||||||
|
|
||||||
liElements.forEach((li, idx) => {
|
|
||||||
const isLast = idx === liElements.length - 1;
|
|
||||||
const runs = parseInlineFormatting(li, { breakLine: false });
|
|
||||||
// Clean manual bullets from first run
|
|
||||||
if (runs.length > 0) {
|
|
||||||
runs[0].text = runs[0].text.replace(/^[•\-\*▪▸]\s*/, '');
|
|
||||||
runs[0].options.bullet = { indent: textIndent };
|
|
||||||
}
|
|
||||||
// Set breakLine on last run
|
|
||||||
if (runs.length > 0 && !isLast) {
|
|
||||||
runs[runs.length - 1].options.breakLine = true;
|
|
||||||
}
|
|
||||||
items.push(...runs);
|
|
||||||
});
|
|
||||||
|
|
||||||
const computed = window.getComputedStyle(liElements[0] || el);
|
|
||||||
|
|
||||||
elements.push({
|
|
||||||
type: 'list',
|
|
||||||
items: items,
|
|
||||||
position: {
|
|
||||||
x: pxToInch(rect.left),
|
|
||||||
y: pxToInch(rect.top),
|
|
||||||
w: pxToInch(rect.width),
|
|
||||||
h: pxToInch(rect.height)
|
|
||||||
},
|
|
||||||
style: {
|
|
||||||
fontSize: pxToPoints(computed.fontSize),
|
|
||||||
fontFace: computed.fontFamily.split(',')[0].replace(/['"]/g, '').trim(),
|
|
||||||
color: rgbToHex(computed.color),
|
|
||||||
transparency: extractAlpha(computed.color),
|
|
||||||
align: computed.textAlign === 'start' ? 'left' : computed.textAlign,
|
|
||||||
lineSpacing: computed.lineHeight && computed.lineHeight !== 'normal' ? pxToPoints(computed.lineHeight) : null,
|
|
||||||
paraSpaceBefore: 0,
|
|
||||||
paraSpaceAfter: pxToPoints(computed.marginBottom),
|
|
||||||
// PptxGenJS margin array is [left, right, bottom, top]
|
|
||||||
margin: [marginLeft, 0, 0, 0]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
liElements.forEach(li => processed.add(li));
|
|
||||||
processed.add(el);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract text elements (P, H1, H2, etc.)
|
|
||||||
if (!textTags.includes(el.tagName)) return;
|
|
||||||
|
|
||||||
const rect = el.getBoundingClientRect();
|
|
||||||
const text = el.textContent.trim();
|
|
||||||
if (rect.width === 0 || rect.height === 0 || !text) return;
|
|
||||||
|
|
||||||
// Validate: Check for manual bullet symbols in text elements (not in lists)
|
|
||||||
if (el.tagName !== 'LI' && /^[•\-\*▪▸○●◆◇■□]\s/.test(text.trimStart())) {
|
|
||||||
errors.push(
|
|
||||||
`Text element <${el.tagName.toLowerCase()}> starts with bullet symbol "${text.substring(0, 20)}...". ` +
|
|
||||||
'Use <ul> or <ol> lists instead of manual bullet symbols.'
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const computed = window.getComputedStyle(el);
|
|
||||||
const rotation = getRotation(computed.transform, computed.writingMode);
|
|
||||||
const { x, y, w, h } = getPositionAndSize(el, rect, rotation);
|
|
||||||
|
|
||||||
const baseStyle = {
|
|
||||||
fontSize: pxToPoints(computed.fontSize),
|
|
||||||
fontFace: computed.fontFamily.split(',')[0].replace(/['"]/g, '').trim(),
|
|
||||||
color: rgbToHex(computed.color),
|
|
||||||
align: computed.textAlign === 'start' ? 'left' : computed.textAlign,
|
|
||||||
lineSpacing: pxToPoints(computed.lineHeight),
|
|
||||||
paraSpaceBefore: pxToPoints(computed.marginTop),
|
|
||||||
paraSpaceAfter: pxToPoints(computed.marginBottom),
|
|
||||||
// PptxGenJS margin array is [left, right, bottom, top] (not [top, right, bottom, left] as documented)
|
|
||||||
margin: [
|
|
||||||
pxToPoints(computed.paddingLeft),
|
|
||||||
pxToPoints(computed.paddingRight),
|
|
||||||
pxToPoints(computed.paddingBottom),
|
|
||||||
pxToPoints(computed.paddingTop)
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
const transparency = extractAlpha(computed.color);
|
|
||||||
if (transparency !== null) baseStyle.transparency = transparency;
|
|
||||||
|
|
||||||
if (rotation !== null) baseStyle.rotate = rotation;
|
|
||||||
|
|
||||||
const hasFormatting = el.querySelector('b, i, u, strong, em, span, br');
|
|
||||||
|
|
||||||
if (hasFormatting) {
|
|
||||||
// Text with inline formatting
|
|
||||||
const transformStr = computed.textTransform;
|
|
||||||
const runs = parseInlineFormatting(el, {}, [], (str) => applyTextTransform(str, transformStr));
|
|
||||||
|
|
||||||
// Adjust lineSpacing based on largest fontSize in runs
|
|
||||||
const adjustedStyle = { ...baseStyle };
|
|
||||||
if (adjustedStyle.lineSpacing) {
|
|
||||||
const maxFontSize = Math.max(
|
|
||||||
adjustedStyle.fontSize,
|
|
||||||
...runs.map(r => r.options?.fontSize || 0)
|
|
||||||
);
|
|
||||||
if (maxFontSize > adjustedStyle.fontSize) {
|
|
||||||
const lineHeightMultiplier = adjustedStyle.lineSpacing / adjustedStyle.fontSize;
|
|
||||||
adjustedStyle.lineSpacing = maxFontSize * lineHeightMultiplier;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
elements.push({
|
|
||||||
type: el.tagName.toLowerCase(),
|
|
||||||
text: runs,
|
|
||||||
position: { x: pxToInch(x), y: pxToInch(y), w: pxToInch(w), h: pxToInch(h) },
|
|
||||||
style: adjustedStyle
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Plain text - inherit CSS formatting
|
|
||||||
const textTransform = computed.textTransform;
|
|
||||||
const transformedText = applyTextTransform(text, textTransform);
|
|
||||||
|
|
||||||
const isBold = computed.fontWeight === 'bold' || parseInt(computed.fontWeight) >= 600;
|
|
||||||
|
|
||||||
elements.push({
|
|
||||||
type: el.tagName.toLowerCase(),
|
|
||||||
text: transformedText,
|
|
||||||
position: { x: pxToInch(x), y: pxToInch(y), w: pxToInch(w), h: pxToInch(h) },
|
|
||||||
style: {
|
|
||||||
...baseStyle,
|
|
||||||
bold: isBold && !shouldSkipBold(computed.fontFamily),
|
|
||||||
italic: computed.fontStyle === 'italic',
|
|
||||||
underline: computed.textDecoration.includes('underline')
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
processed.add(el);
|
|
||||||
});
|
|
||||||
|
|
||||||
return { background, elements, placeholders, errors };
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function html2pptx(htmlFile, pres, options = {}) {
|
|
||||||
const {
|
|
||||||
tmpDir = process.env.TMPDIR || '/tmp',
|
|
||||||
slide = null
|
|
||||||
} = options;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Use Chrome on macOS, default Chromium on Unix
|
|
||||||
const launchOptions = { env: { TMPDIR: tmpDir } };
|
|
||||||
if (process.platform === 'darwin') {
|
|
||||||
launchOptions.channel = 'chrome';
|
|
||||||
}
|
|
||||||
|
|
||||||
const browser = await chromium.launch(launchOptions);
|
|
||||||
|
|
||||||
let bodyDimensions;
|
|
||||||
let slideData;
|
|
||||||
|
|
||||||
const filePath = path.isAbsolute(htmlFile) ? htmlFile : path.join(process.cwd(), htmlFile);
|
|
||||||
const validationErrors = [];
|
|
||||||
|
|
||||||
try {
|
|
||||||
const page = await browser.newPage();
|
|
||||||
page.on('console', (msg) => {
|
|
||||||
// Log the message text to your test runner's console
|
|
||||||
console.log(`Browser console: ${msg.text()}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
await page.goto(`file://${filePath}`);
|
|
||||||
|
|
||||||
bodyDimensions = await getBodyDimensions(page);
|
|
||||||
|
|
||||||
await page.setViewportSize({
|
|
||||||
width: Math.round(bodyDimensions.width),
|
|
||||||
height: Math.round(bodyDimensions.height)
|
|
||||||
});
|
|
||||||
|
|
||||||
slideData = await extractSlideData(page);
|
|
||||||
} finally {
|
|
||||||
await browser.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collect all validation errors
|
|
||||||
if (bodyDimensions.errors && bodyDimensions.errors.length > 0) {
|
|
||||||
validationErrors.push(...bodyDimensions.errors);
|
|
||||||
}
|
|
||||||
|
|
||||||
const dimensionErrors = validateDimensions(bodyDimensions, pres);
|
|
||||||
if (dimensionErrors.length > 0) {
|
|
||||||
validationErrors.push(...dimensionErrors);
|
|
||||||
}
|
|
||||||
|
|
||||||
const textBoxPositionErrors = validateTextBoxPosition(slideData, bodyDimensions);
|
|
||||||
if (textBoxPositionErrors.length > 0) {
|
|
||||||
validationErrors.push(...textBoxPositionErrors);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (slideData.errors && slideData.errors.length > 0) {
|
|
||||||
validationErrors.push(...slideData.errors);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Throw all errors at once if any exist
|
|
||||||
if (validationErrors.length > 0) {
|
|
||||||
const errorMessage = validationErrors.length === 1
|
|
||||||
? validationErrors[0]
|
|
||||||
: `Multiple validation errors found:\n${validationErrors.map((e, i) => ` ${i + 1}. ${e}`).join('\n')}`;
|
|
||||||
throw new Error(errorMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
const targetSlide = slide || pres.addSlide();
|
|
||||||
|
|
||||||
await addBackground(slideData, targetSlide, tmpDir);
|
|
||||||
addElements(slideData, targetSlide, pres);
|
|
||||||
|
|
||||||
return { slide: targetSlide, placeholders: slideData.placeholders };
|
|
||||||
} catch (error) {
|
|
||||||
if (!error.message.startsWith(htmlFile)) {
|
|
||||||
throw new Error(`${htmlFile}: ${error.message}`);
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = html2pptx;
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,231 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Rearrange PowerPoint slides based on a sequence of indices.
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
python rearrange.py template.pptx output.pptx 0,34,34,50,52
|
|
||||||
|
|
||||||
This will create output.pptx using slides from template.pptx in the specified order.
|
|
||||||
Slides can be repeated (e.g., 34 appears twice).
|
|
||||||
"""
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import shutil
|
|
||||||
import sys
|
|
||||||
from copy import deepcopy
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import six
|
|
||||||
from pptx import Presentation
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description="Rearrange PowerPoint slides based on a sequence of indices.",
|
|
||||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
||||||
epilog="""
|
|
||||||
Examples:
|
|
||||||
python rearrange.py template.pptx output.pptx 0,34,34,50,52
|
|
||||||
Creates output.pptx using slides 0, 34 (twice), 50, and 52 from template.pptx
|
|
||||||
|
|
||||||
python rearrange.py template.pptx output.pptx 5,3,1,2,4
|
|
||||||
Creates output.pptx with slides reordered as specified
|
|
||||||
|
|
||||||
Note: Slide indices are 0-based (first slide is 0, second is 1, etc.)
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument("template", help="Path to template PPTX file")
|
|
||||||
parser.add_argument("output", help="Path for output PPTX file")
|
|
||||||
parser.add_argument(
|
|
||||||
"sequence", help="Comma-separated sequence of slide indices (0-based)"
|
|
||||||
)
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
# Parse the slide sequence
|
|
||||||
try:
|
|
||||||
slide_sequence = [int(x.strip()) for x in args.sequence.split(",")]
|
|
||||||
except ValueError:
|
|
||||||
print(
|
|
||||||
"Error: Invalid sequence format. Use comma-separated integers (e.g., 0,34,34,50,52)"
|
|
||||||
)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# Check template exists
|
|
||||||
template_path = Path(args.template)
|
|
||||||
if not template_path.exists():
|
|
||||||
print(f"Error: Template file not found: {args.template}")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# Create output directory if needed
|
|
||||||
output_path = Path(args.output)
|
|
||||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
try:
|
|
||||||
rearrange_presentation(template_path, output_path, slide_sequence)
|
|
||||||
except ValueError as e:
|
|
||||||
print(f"Error: {e}")
|
|
||||||
sys.exit(1)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error processing presentation: {e}")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
def duplicate_slide(pres, index):
|
|
||||||
"""Duplicate a slide in the presentation."""
|
|
||||||
source = pres.slides[index]
|
|
||||||
|
|
||||||
# Use source's layout to preserve formatting
|
|
||||||
new_slide = pres.slides.add_slide(source.slide_layout)
|
|
||||||
|
|
||||||
# Collect all image and media relationships from the source slide
|
|
||||||
image_rels = {}
|
|
||||||
for rel_id, rel in six.iteritems(source.part.rels):
|
|
||||||
if "image" in rel.reltype or "media" in rel.reltype:
|
|
||||||
image_rels[rel_id] = rel
|
|
||||||
|
|
||||||
# CRITICAL: Clear placeholder shapes to avoid duplicates
|
|
||||||
for shape in new_slide.shapes:
|
|
||||||
sp = shape.element
|
|
||||||
sp.getparent().remove(sp)
|
|
||||||
|
|
||||||
# Copy all shapes from source
|
|
||||||
for shape in source.shapes:
|
|
||||||
el = shape.element
|
|
||||||
new_el = deepcopy(el)
|
|
||||||
new_slide.shapes._spTree.insert_element_before(new_el, "p:extLst")
|
|
||||||
|
|
||||||
# Handle picture shapes - need to update the blip reference
|
|
||||||
# Look for all blip elements (they can be in pic or other contexts)
|
|
||||||
# Using the element's own xpath method without namespaces argument
|
|
||||||
blips = new_el.xpath(".//a:blip[@r:embed]")
|
|
||||||
for blip in blips:
|
|
||||||
old_rId = blip.get(
|
|
||||||
"{http://schemas.openxmlformats.org/officeDocument/2006/relationships}embed"
|
|
||||||
)
|
|
||||||
if old_rId in image_rels:
|
|
||||||
# Create a new relationship in the destination slide for this image
|
|
||||||
old_rel = image_rels[old_rId]
|
|
||||||
# get_or_add returns the rId directly, or adds and returns new rId
|
|
||||||
new_rId = new_slide.part.rels.get_or_add(
|
|
||||||
old_rel.reltype, old_rel._target
|
|
||||||
)
|
|
||||||
# Update the blip's embed reference to use the new relationship ID
|
|
||||||
blip.set(
|
|
||||||
"{http://schemas.openxmlformats.org/officeDocument/2006/relationships}embed",
|
|
||||||
new_rId,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Copy any additional image/media relationships that might be referenced elsewhere
|
|
||||||
for rel_id, rel in image_rels.items():
|
|
||||||
try:
|
|
||||||
new_slide.part.rels.get_or_add(rel.reltype, rel._target)
|
|
||||||
except Exception:
|
|
||||||
pass # Relationship might already exist
|
|
||||||
|
|
||||||
return new_slide
|
|
||||||
|
|
||||||
|
|
||||||
def delete_slide(pres, index):
|
|
||||||
"""Delete a slide from the presentation."""
|
|
||||||
rId = pres.slides._sldIdLst[index].rId
|
|
||||||
pres.part.drop_rel(rId)
|
|
||||||
del pres.slides._sldIdLst[index]
|
|
||||||
|
|
||||||
|
|
||||||
def reorder_slides(pres, slide_index, target_index):
|
|
||||||
"""Move a slide from one position to another."""
|
|
||||||
slides = pres.slides._sldIdLst
|
|
||||||
|
|
||||||
# Remove slide element from current position
|
|
||||||
slide_element = slides[slide_index]
|
|
||||||
slides.remove(slide_element)
|
|
||||||
|
|
||||||
# Insert at target position
|
|
||||||
slides.insert(target_index, slide_element)
|
|
||||||
|
|
||||||
|
|
||||||
def rearrange_presentation(template_path, output_path, slide_sequence):
|
|
||||||
"""
|
|
||||||
Create a new presentation with slides from template in specified order.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
template_path: Path to template PPTX file
|
|
||||||
output_path: Path for output PPTX file
|
|
||||||
slide_sequence: List of slide indices (0-based) to include
|
|
||||||
"""
|
|
||||||
# Copy template to preserve dimensions and theme
|
|
||||||
if template_path != output_path:
|
|
||||||
shutil.copy2(template_path, output_path)
|
|
||||||
prs = Presentation(output_path)
|
|
||||||
else:
|
|
||||||
prs = Presentation(template_path)
|
|
||||||
|
|
||||||
total_slides = len(prs.slides)
|
|
||||||
|
|
||||||
# Validate indices
|
|
||||||
for idx in slide_sequence:
|
|
||||||
if idx < 0 or idx >= total_slides:
|
|
||||||
raise ValueError(f"Slide index {idx} out of range (0-{total_slides - 1})")
|
|
||||||
|
|
||||||
# Track original slides and their duplicates
|
|
||||||
slide_map = [] # List of actual slide indices for final presentation
|
|
||||||
duplicated = {} # Track duplicates: original_idx -> [duplicate_indices]
|
|
||||||
|
|
||||||
# Step 1: DUPLICATE repeated slides
|
|
||||||
print(f"Processing {len(slide_sequence)} slides from template...")
|
|
||||||
for i, template_idx in enumerate(slide_sequence):
|
|
||||||
if template_idx in duplicated and duplicated[template_idx]:
|
|
||||||
# Already duplicated this slide, use the duplicate
|
|
||||||
slide_map.append(duplicated[template_idx].pop(0))
|
|
||||||
print(f" [{i}] Using duplicate of slide {template_idx}")
|
|
||||||
elif slide_sequence.count(template_idx) > 1 and template_idx not in duplicated:
|
|
||||||
# First occurrence of a repeated slide - create duplicates
|
|
||||||
slide_map.append(template_idx)
|
|
||||||
duplicates = []
|
|
||||||
count = slide_sequence.count(template_idx) - 1
|
|
||||||
print(
|
|
||||||
f" [{i}] Using original slide {template_idx}, creating {count} duplicate(s)"
|
|
||||||
)
|
|
||||||
for _ in range(count):
|
|
||||||
duplicate_slide(prs, template_idx)
|
|
||||||
duplicates.append(len(prs.slides) - 1)
|
|
||||||
duplicated[template_idx] = duplicates
|
|
||||||
else:
|
|
||||||
# Unique slide or first occurrence already handled, use original
|
|
||||||
slide_map.append(template_idx)
|
|
||||||
print(f" [{i}] Using original slide {template_idx}")
|
|
||||||
|
|
||||||
# Step 2: DELETE unwanted slides (work backwards)
|
|
||||||
slides_to_keep = set(slide_map)
|
|
||||||
print(f"\nDeleting {len(prs.slides) - len(slides_to_keep)} unused slides...")
|
|
||||||
for i in range(len(prs.slides) - 1, -1, -1):
|
|
||||||
if i not in slides_to_keep:
|
|
||||||
delete_slide(prs, i)
|
|
||||||
# Update slide_map indices after deletion
|
|
||||||
slide_map = [idx - 1 if idx > i else idx for idx in slide_map]
|
|
||||||
|
|
||||||
# Step 3: REORDER to final sequence
|
|
||||||
print(f"Reordering {len(slide_map)} slides to final sequence...")
|
|
||||||
for target_pos in range(len(slide_map)):
|
|
||||||
# Find which slide should be at target_pos
|
|
||||||
current_pos = slide_map[target_pos]
|
|
||||||
if current_pos != target_pos:
|
|
||||||
reorder_slides(prs, current_pos, target_pos)
|
|
||||||
# Update slide_map: the move shifts other slides
|
|
||||||
for i in range(len(slide_map)):
|
|
||||||
if slide_map[i] > current_pos and slide_map[i] <= target_pos:
|
|
||||||
slide_map[i] -= 1
|
|
||||||
elif slide_map[i] < current_pos and slide_map[i] >= target_pos:
|
|
||||||
slide_map[i] += 1
|
|
||||||
slide_map[target_pos] = target_pos
|
|
||||||
|
|
||||||
# Save the presentation
|
|
||||||
prs.save(output_path)
|
|
||||||
print(f"\nSaved rearranged presentation to: {output_path}")
|
|
||||||
print(f"Final presentation has {len(prs.slides)} slides")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -1,385 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""Apply text replacements to PowerPoint presentation.
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
python replace.py <input.pptx> <replacements.json> <output.pptx>
|
|
||||||
|
|
||||||
The replacements JSON should have the structure output by inventory.py.
|
|
||||||
ALL text shapes identified by inventory.py will have their text cleared
|
|
||||||
unless "paragraphs" is specified in the replacements for that shape.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import json
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Any, Dict, List
|
|
||||||
|
|
||||||
from inventory import InventoryData, extract_text_inventory
|
|
||||||
from pptx import Presentation
|
|
||||||
from pptx.dml.color import RGBColor
|
|
||||||
from pptx.enum.dml import MSO_THEME_COLOR
|
|
||||||
from pptx.enum.text import PP_ALIGN
|
|
||||||
from pptx.oxml.xmlchemy import OxmlElement
|
|
||||||
from pptx.util import Pt
|
|
||||||
|
|
||||||
|
|
||||||
def clear_paragraph_bullets(paragraph):
|
|
||||||
"""Clear bullet formatting from a paragraph."""
|
|
||||||
pPr = paragraph._element.get_or_add_pPr()
|
|
||||||
|
|
||||||
# Remove existing bullet elements
|
|
||||||
for child in list(pPr):
|
|
||||||
if (
|
|
||||||
child.tag.endswith("buChar")
|
|
||||||
or child.tag.endswith("buNone")
|
|
||||||
or child.tag.endswith("buAutoNum")
|
|
||||||
or child.tag.endswith("buFont")
|
|
||||||
):
|
|
||||||
pPr.remove(child)
|
|
||||||
|
|
||||||
return pPr
|
|
||||||
|
|
||||||
|
|
||||||
def apply_paragraph_properties(paragraph, para_data: Dict[str, Any]):
|
|
||||||
"""Apply formatting properties to a paragraph."""
|
|
||||||
# Get the text but don't set it on paragraph directly yet
|
|
||||||
text = para_data.get("text", "")
|
|
||||||
|
|
||||||
# Get or create paragraph properties
|
|
||||||
pPr = clear_paragraph_bullets(paragraph)
|
|
||||||
|
|
||||||
# Handle bullet formatting
|
|
||||||
if para_data.get("bullet", False):
|
|
||||||
level = para_data.get("level", 0)
|
|
||||||
paragraph.level = level
|
|
||||||
|
|
||||||
# Calculate font-proportional indentation
|
|
||||||
font_size = para_data.get("font_size", 18.0)
|
|
||||||
level_indent_emu = int((font_size * (1.6 + level * 1.6)) * 12700)
|
|
||||||
hanging_indent_emu = int(-font_size * 0.8 * 12700)
|
|
||||||
|
|
||||||
# Set indentation
|
|
||||||
pPr.attrib["marL"] = str(level_indent_emu)
|
|
||||||
pPr.attrib["indent"] = str(hanging_indent_emu)
|
|
||||||
|
|
||||||
# Add bullet character
|
|
||||||
buChar = OxmlElement("a:buChar")
|
|
||||||
buChar.set("char", "•")
|
|
||||||
pPr.append(buChar)
|
|
||||||
|
|
||||||
# Default to left alignment for bullets if not specified
|
|
||||||
if "alignment" not in para_data:
|
|
||||||
paragraph.alignment = PP_ALIGN.LEFT
|
|
||||||
else:
|
|
||||||
# Remove indentation for non-bullet text
|
|
||||||
pPr.attrib["marL"] = "0"
|
|
||||||
pPr.attrib["indent"] = "0"
|
|
||||||
|
|
||||||
# Add buNone element
|
|
||||||
buNone = OxmlElement("a:buNone")
|
|
||||||
pPr.insert(0, buNone)
|
|
||||||
|
|
||||||
# Apply alignment
|
|
||||||
if "alignment" in para_data:
|
|
||||||
alignment_map = {
|
|
||||||
"LEFT": PP_ALIGN.LEFT,
|
|
||||||
"CENTER": PP_ALIGN.CENTER,
|
|
||||||
"RIGHT": PP_ALIGN.RIGHT,
|
|
||||||
"JUSTIFY": PP_ALIGN.JUSTIFY,
|
|
||||||
}
|
|
||||||
if para_data["alignment"] in alignment_map:
|
|
||||||
paragraph.alignment = alignment_map[para_data["alignment"]]
|
|
||||||
|
|
||||||
# Apply spacing
|
|
||||||
if "space_before" in para_data:
|
|
||||||
paragraph.space_before = Pt(para_data["space_before"])
|
|
||||||
if "space_after" in para_data:
|
|
||||||
paragraph.space_after = Pt(para_data["space_after"])
|
|
||||||
if "line_spacing" in para_data:
|
|
||||||
paragraph.line_spacing = Pt(para_data["line_spacing"])
|
|
||||||
|
|
||||||
# Apply run-level formatting
|
|
||||||
if not paragraph.runs:
|
|
||||||
run = paragraph.add_run()
|
|
||||||
run.text = text
|
|
||||||
else:
|
|
||||||
run = paragraph.runs[0]
|
|
||||||
run.text = text
|
|
||||||
|
|
||||||
# Apply font properties
|
|
||||||
apply_font_properties(run, para_data)
|
|
||||||
|
|
||||||
|
|
||||||
def apply_font_properties(run, para_data: Dict[str, Any]):
|
|
||||||
"""Apply font properties to a text run."""
|
|
||||||
if "bold" in para_data:
|
|
||||||
run.font.bold = para_data["bold"]
|
|
||||||
if "italic" in para_data:
|
|
||||||
run.font.italic = para_data["italic"]
|
|
||||||
if "underline" in para_data:
|
|
||||||
run.font.underline = para_data["underline"]
|
|
||||||
if "font_size" in para_data:
|
|
||||||
run.font.size = Pt(para_data["font_size"])
|
|
||||||
if "font_name" in para_data:
|
|
||||||
run.font.name = para_data["font_name"]
|
|
||||||
|
|
||||||
# Apply color - prefer RGB, fall back to theme_color
|
|
||||||
if "color" in para_data:
|
|
||||||
color_hex = para_data["color"].lstrip("#")
|
|
||||||
if len(color_hex) == 6:
|
|
||||||
r = int(color_hex[0:2], 16)
|
|
||||||
g = int(color_hex[2:4], 16)
|
|
||||||
b = int(color_hex[4:6], 16)
|
|
||||||
run.font.color.rgb = RGBColor(r, g, b)
|
|
||||||
elif "theme_color" in para_data:
|
|
||||||
# Get theme color by name (e.g., "DARK_1", "ACCENT_1")
|
|
||||||
theme_name = para_data["theme_color"]
|
|
||||||
try:
|
|
||||||
run.font.color.theme_color = getattr(MSO_THEME_COLOR, theme_name)
|
|
||||||
except AttributeError:
|
|
||||||
print(f" WARNING: Unknown theme color name '{theme_name}'")
|
|
||||||
|
|
||||||
|
|
||||||
def detect_frame_overflow(inventory: InventoryData) -> Dict[str, Dict[str, float]]:
|
|
||||||
"""Detect text overflow in shapes (text exceeding shape bounds).
|
|
||||||
|
|
||||||
Returns dict of slide_key -> shape_key -> overflow_inches.
|
|
||||||
Only includes shapes that have text overflow.
|
|
||||||
"""
|
|
||||||
overflow_map = {}
|
|
||||||
|
|
||||||
for slide_key, shapes_dict in inventory.items():
|
|
||||||
for shape_key, shape_data in shapes_dict.items():
|
|
||||||
# Check for frame overflow (text exceeding shape bounds)
|
|
||||||
if shape_data.frame_overflow_bottom is not None:
|
|
||||||
if slide_key not in overflow_map:
|
|
||||||
overflow_map[slide_key] = {}
|
|
||||||
overflow_map[slide_key][shape_key] = shape_data.frame_overflow_bottom
|
|
||||||
|
|
||||||
return overflow_map
|
|
||||||
|
|
||||||
|
|
||||||
def validate_replacements(inventory: InventoryData, replacements: Dict) -> List[str]:
|
|
||||||
"""Validate that all shapes in replacements exist in inventory.
|
|
||||||
|
|
||||||
Returns list of error messages.
|
|
||||||
"""
|
|
||||||
errors = []
|
|
||||||
|
|
||||||
for slide_key, shapes_data in replacements.items():
|
|
||||||
if not slide_key.startswith("slide-"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Check if slide exists
|
|
||||||
if slide_key not in inventory:
|
|
||||||
errors.append(f"Slide '{slide_key}' not found in inventory")
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Check each shape
|
|
||||||
for shape_key in shapes_data.keys():
|
|
||||||
if shape_key not in inventory[slide_key]:
|
|
||||||
# Find shapes without replacements defined and show their content
|
|
||||||
unused_with_content = []
|
|
||||||
for k in inventory[slide_key].keys():
|
|
||||||
if k not in shapes_data:
|
|
||||||
shape_data = inventory[slide_key][k]
|
|
||||||
# Get text from paragraphs as preview
|
|
||||||
paragraphs = shape_data.paragraphs
|
|
||||||
if paragraphs and paragraphs[0].text:
|
|
||||||
first_text = paragraphs[0].text[:50]
|
|
||||||
if len(paragraphs[0].text) > 50:
|
|
||||||
first_text += "..."
|
|
||||||
unused_with_content.append(f"{k} ('{first_text}')")
|
|
||||||
else:
|
|
||||||
unused_with_content.append(k)
|
|
||||||
|
|
||||||
errors.append(
|
|
||||||
f"Shape '{shape_key}' not found on '{slide_key}'. "
|
|
||||||
f"Shapes without replacements: {', '.join(sorted(unused_with_content)) if unused_with_content else 'none'}"
|
|
||||||
)
|
|
||||||
|
|
||||||
return errors
|
|
||||||
|
|
||||||
|
|
||||||
def check_duplicate_keys(pairs):
|
|
||||||
"""Check for duplicate keys when loading JSON."""
|
|
||||||
result = {}
|
|
||||||
for key, value in pairs:
|
|
||||||
if key in result:
|
|
||||||
raise ValueError(f"Duplicate key found in JSON: '{key}'")
|
|
||||||
result[key] = value
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def apply_replacements(pptx_file: str, json_file: str, output_file: str):
|
|
||||||
"""Apply text replacements from JSON to PowerPoint presentation."""
|
|
||||||
|
|
||||||
# Load presentation
|
|
||||||
prs = Presentation(pptx_file)
|
|
||||||
|
|
||||||
# Get inventory of all text shapes (returns ShapeData objects)
|
|
||||||
# Pass prs to use same Presentation instance
|
|
||||||
inventory = extract_text_inventory(Path(pptx_file), prs)
|
|
||||||
|
|
||||||
# Detect text overflow in original presentation
|
|
||||||
original_overflow = detect_frame_overflow(inventory)
|
|
||||||
|
|
||||||
# Load replacement data with duplicate key detection
|
|
||||||
with open(json_file, "r") as f:
|
|
||||||
replacements = json.load(f, object_pairs_hook=check_duplicate_keys)
|
|
||||||
|
|
||||||
# Validate replacements
|
|
||||||
errors = validate_replacements(inventory, replacements)
|
|
||||||
if errors:
|
|
||||||
print("ERROR: Invalid shapes in replacement JSON:")
|
|
||||||
for error in errors:
|
|
||||||
print(f" - {error}")
|
|
||||||
print("\nPlease check the inventory and update your replacement JSON.")
|
|
||||||
print(
|
|
||||||
"You can regenerate the inventory with: python inventory.py <input.pptx> <output.json>"
|
|
||||||
)
|
|
||||||
raise ValueError(f"Found {len(errors)} validation error(s)")
|
|
||||||
|
|
||||||
# Track statistics
|
|
||||||
shapes_processed = 0
|
|
||||||
shapes_cleared = 0
|
|
||||||
shapes_replaced = 0
|
|
||||||
|
|
||||||
# Process each slide from inventory
|
|
||||||
for slide_key, shapes_dict in inventory.items():
|
|
||||||
if not slide_key.startswith("slide-"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
slide_index = int(slide_key.split("-")[1])
|
|
||||||
|
|
||||||
if slide_index >= len(prs.slides):
|
|
||||||
print(f"Warning: Slide {slide_index} not found")
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Process each shape from inventory
|
|
||||||
for shape_key, shape_data in shapes_dict.items():
|
|
||||||
shapes_processed += 1
|
|
||||||
|
|
||||||
# Get the shape directly from ShapeData
|
|
||||||
shape = shape_data.shape
|
|
||||||
if not shape:
|
|
||||||
print(f"Warning: {shape_key} has no shape reference")
|
|
||||||
continue
|
|
||||||
|
|
||||||
# ShapeData already validates text_frame in __init__
|
|
||||||
text_frame = shape.text_frame # type: ignore
|
|
||||||
|
|
||||||
text_frame.clear() # type: ignore
|
|
||||||
shapes_cleared += 1
|
|
||||||
|
|
||||||
# Check for replacement paragraphs
|
|
||||||
replacement_shape_data = replacements.get(slide_key, {}).get(shape_key, {})
|
|
||||||
if "paragraphs" not in replacement_shape_data:
|
|
||||||
continue
|
|
||||||
|
|
||||||
shapes_replaced += 1
|
|
||||||
|
|
||||||
# Add replacement paragraphs
|
|
||||||
for i, para_data in enumerate(replacement_shape_data["paragraphs"]):
|
|
||||||
if i == 0:
|
|
||||||
p = text_frame.paragraphs[0] # type: ignore
|
|
||||||
else:
|
|
||||||
p = text_frame.add_paragraph() # type: ignore
|
|
||||||
|
|
||||||
apply_paragraph_properties(p, para_data)
|
|
||||||
|
|
||||||
# Check for issues after replacements
|
|
||||||
# Save to a temporary file and reload to avoid modifying the presentation during inventory
|
|
||||||
# (extract_text_inventory accesses font.color which adds empty <a:solidFill/> elements)
|
|
||||||
import tempfile
|
|
||||||
|
|
||||||
with tempfile.NamedTemporaryFile(suffix=".pptx", delete=False) as tmp:
|
|
||||||
tmp_path = Path(tmp.name)
|
|
||||||
prs.save(str(tmp_path))
|
|
||||||
|
|
||||||
try:
|
|
||||||
updated_inventory = extract_text_inventory(tmp_path)
|
|
||||||
updated_overflow = detect_frame_overflow(updated_inventory)
|
|
||||||
finally:
|
|
||||||
tmp_path.unlink() # Clean up temp file
|
|
||||||
|
|
||||||
# Check if any text overflow got worse
|
|
||||||
overflow_errors = []
|
|
||||||
for slide_key, shape_overflows in updated_overflow.items():
|
|
||||||
for shape_key, new_overflow in shape_overflows.items():
|
|
||||||
# Get original overflow (0 if there was no overflow before)
|
|
||||||
original = original_overflow.get(slide_key, {}).get(shape_key, 0.0)
|
|
||||||
|
|
||||||
# Error if overflow increased
|
|
||||||
if new_overflow > original + 0.01: # Small tolerance for rounding
|
|
||||||
increase = new_overflow - original
|
|
||||||
overflow_errors.append(
|
|
||||||
f'{slide_key}/{shape_key}: overflow worsened by {increase:.2f}" '
|
|
||||||
f'(was {original:.2f}", now {new_overflow:.2f}")'
|
|
||||||
)
|
|
||||||
|
|
||||||
# Collect warnings from updated shapes
|
|
||||||
warnings = []
|
|
||||||
for slide_key, shapes_dict in updated_inventory.items():
|
|
||||||
for shape_key, shape_data in shapes_dict.items():
|
|
||||||
if shape_data.warnings:
|
|
||||||
for warning in shape_data.warnings:
|
|
||||||
warnings.append(f"{slide_key}/{shape_key}: {warning}")
|
|
||||||
|
|
||||||
# Fail if there are any issues
|
|
||||||
if overflow_errors or warnings:
|
|
||||||
print("\nERROR: Issues detected in replacement output:")
|
|
||||||
if overflow_errors:
|
|
||||||
print("\nText overflow worsened:")
|
|
||||||
for error in overflow_errors:
|
|
||||||
print(f" - {error}")
|
|
||||||
if warnings:
|
|
||||||
print("\nFormatting warnings:")
|
|
||||||
for warning in warnings:
|
|
||||||
print(f" - {warning}")
|
|
||||||
print("\nPlease fix these issues before saving.")
|
|
||||||
raise ValueError(
|
|
||||||
f"Found {len(overflow_errors)} overflow error(s) and {len(warnings)} warning(s)"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Save the presentation
|
|
||||||
prs.save(output_file)
|
|
||||||
|
|
||||||
# Report results
|
|
||||||
print(f"Saved updated presentation to: {output_file}")
|
|
||||||
print(f"Processed {len(prs.slides)} slides")
|
|
||||||
print(f" - Shapes processed: {shapes_processed}")
|
|
||||||
print(f" - Shapes cleared: {shapes_cleared}")
|
|
||||||
print(f" - Shapes replaced: {shapes_replaced}")
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""Main entry point for command-line usage."""
|
|
||||||
if len(sys.argv) != 4:
|
|
||||||
print(__doc__)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
input_pptx = Path(sys.argv[1])
|
|
||||||
replacements_json = Path(sys.argv[2])
|
|
||||||
output_pptx = Path(sys.argv[3])
|
|
||||||
|
|
||||||
if not input_pptx.exists():
|
|
||||||
print(f"Error: Input file '{input_pptx}' not found")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
if not replacements_json.exists():
|
|
||||||
print(f"Error: Replacements JSON file '{replacements_json}' not found")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
try:
|
|
||||||
apply_replacements(str(input_pptx), str(replacements_json), str(output_pptx))
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error applying replacements: {e}")
|
|
||||||
import traceback
|
|
||||||
|
|
||||||
traceback.print_exc()
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -1,450 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Create thumbnail grids from PowerPoint presentation slides.
|
|
||||||
|
|
||||||
Creates a grid layout of slide thumbnails with configurable columns (max 6).
|
|
||||||
Each grid contains up to cols×(cols+1) images. For presentations with more
|
|
||||||
slides, multiple numbered grid files are created automatically.
|
|
||||||
|
|
||||||
The program outputs the names of all files created.
|
|
||||||
|
|
||||||
Output:
|
|
||||||
- Single grid: {prefix}.jpg (if slides fit in one grid)
|
|
||||||
- Multiple grids: {prefix}-1.jpg, {prefix}-2.jpg, etc.
|
|
||||||
|
|
||||||
Grid limits by column count:
|
|
||||||
- 3 cols: max 12 slides per grid (3×4)
|
|
||||||
- 4 cols: max 20 slides per grid (4×5)
|
|
||||||
- 5 cols: max 30 slides per grid (5×6) [default]
|
|
||||||
- 6 cols: max 42 slides per grid (6×7)
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
python thumbnail.py input.pptx [output_prefix] [--cols N] [--outline-placeholders]
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
python thumbnail.py presentation.pptx
|
|
||||||
# Creates: thumbnails.jpg (using default prefix)
|
|
||||||
# Outputs:
|
|
||||||
# Created 1 grid(s):
|
|
||||||
# - thumbnails.jpg
|
|
||||||
|
|
||||||
python thumbnail.py large-deck.pptx grid --cols 4
|
|
||||||
# Creates: grid-1.jpg, grid-2.jpg, grid-3.jpg
|
|
||||||
# Outputs:
|
|
||||||
# Created 3 grid(s):
|
|
||||||
# - grid-1.jpg
|
|
||||||
# - grid-2.jpg
|
|
||||||
# - grid-3.jpg
|
|
||||||
|
|
||||||
python thumbnail.py template.pptx analysis --outline-placeholders
|
|
||||||
# Creates thumbnail grids with red outlines around text placeholders
|
|
||||||
"""
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import tempfile
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from inventory import extract_text_inventory
|
|
||||||
from PIL import Image, ImageDraw, ImageFont
|
|
||||||
from pptx import Presentation
|
|
||||||
|
|
||||||
# Constants
|
|
||||||
THUMBNAIL_WIDTH = 300 # Fixed thumbnail width in pixels
|
|
||||||
CONVERSION_DPI = 100 # DPI for PDF to image conversion
|
|
||||||
MAX_COLS = 6 # Maximum number of columns
|
|
||||||
DEFAULT_COLS = 5 # Default number of columns
|
|
||||||
JPEG_QUALITY = 95 # JPEG compression quality
|
|
||||||
|
|
||||||
# Grid layout constants
|
|
||||||
GRID_PADDING = 20 # Padding between thumbnails
|
|
||||||
BORDER_WIDTH = 2 # Border width around thumbnails
|
|
||||||
FONT_SIZE_RATIO = 0.12 # Font size as fraction of thumbnail width
|
|
||||||
LABEL_PADDING_RATIO = 0.4 # Label padding as fraction of font size
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description="Create thumbnail grids from PowerPoint slides."
|
|
||||||
)
|
|
||||||
parser.add_argument("input", help="Input PowerPoint file (.pptx)")
|
|
||||||
parser.add_argument(
|
|
||||||
"output_prefix",
|
|
||||||
nargs="?",
|
|
||||||
default="thumbnails",
|
|
||||||
help="Output prefix for image files (default: thumbnails, will create prefix.jpg or prefix-N.jpg)",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--cols",
|
|
||||||
type=int,
|
|
||||||
default=DEFAULT_COLS,
|
|
||||||
help=f"Number of columns (default: {DEFAULT_COLS}, max: {MAX_COLS})",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--outline-placeholders",
|
|
||||||
action="store_true",
|
|
||||||
help="Outline text placeholders with a colored border",
|
|
||||||
)
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
# Validate columns
|
|
||||||
cols = min(args.cols, MAX_COLS)
|
|
||||||
if args.cols > MAX_COLS:
|
|
||||||
print(f"Warning: Columns limited to {MAX_COLS} (requested {args.cols})")
|
|
||||||
|
|
||||||
# Validate input
|
|
||||||
input_path = Path(args.input)
|
|
||||||
if not input_path.exists() or input_path.suffix.lower() != ".pptx":
|
|
||||||
print(f"Error: Invalid PowerPoint file: {args.input}")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# Construct output path (always JPG)
|
|
||||||
output_path = Path(f"{args.output_prefix}.jpg")
|
|
||||||
|
|
||||||
print(f"Processing: {args.input}")
|
|
||||||
|
|
||||||
try:
|
|
||||||
with tempfile.TemporaryDirectory() as temp_dir:
|
|
||||||
# Get placeholder regions if outlining is enabled
|
|
||||||
placeholder_regions = None
|
|
||||||
slide_dimensions = None
|
|
||||||
if args.outline_placeholders:
|
|
||||||
print("Extracting placeholder regions...")
|
|
||||||
placeholder_regions, slide_dimensions = get_placeholder_regions(
|
|
||||||
input_path
|
|
||||||
)
|
|
||||||
if placeholder_regions:
|
|
||||||
print(f"Found placeholders on {len(placeholder_regions)} slides")
|
|
||||||
|
|
||||||
# Convert slides to images
|
|
||||||
slide_images = convert_to_images(input_path, Path(temp_dir), CONVERSION_DPI)
|
|
||||||
if not slide_images:
|
|
||||||
print("Error: No slides found")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
print(f"Found {len(slide_images)} slides")
|
|
||||||
|
|
||||||
# Create grids (max cols×(cols+1) images per grid)
|
|
||||||
grid_files = create_grids(
|
|
||||||
slide_images,
|
|
||||||
cols,
|
|
||||||
THUMBNAIL_WIDTH,
|
|
||||||
output_path,
|
|
||||||
placeholder_regions,
|
|
||||||
slide_dimensions,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Print saved files
|
|
||||||
print(f"Created {len(grid_files)} grid(s):")
|
|
||||||
for grid_file in grid_files:
|
|
||||||
print(f" - {grid_file}")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error: {e}")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
def create_hidden_slide_placeholder(size):
|
|
||||||
"""Create placeholder image for hidden slides."""
|
|
||||||
img = Image.new("RGB", size, color="#F0F0F0")
|
|
||||||
draw = ImageDraw.Draw(img)
|
|
||||||
line_width = max(5, min(size) // 100)
|
|
||||||
draw.line([(0, 0), size], fill="#CCCCCC", width=line_width)
|
|
||||||
draw.line([(size[0], 0), (0, size[1])], fill="#CCCCCC", width=line_width)
|
|
||||||
return img
|
|
||||||
|
|
||||||
|
|
||||||
def get_placeholder_regions(pptx_path):
|
|
||||||
"""Extract ALL text regions from the presentation.
|
|
||||||
|
|
||||||
Returns a tuple of (placeholder_regions, slide_dimensions).
|
|
||||||
text_regions is a dict mapping slide indices to lists of text regions.
|
|
||||||
Each region is a dict with 'left', 'top', 'width', 'height' in inches.
|
|
||||||
slide_dimensions is a tuple of (width_inches, height_inches).
|
|
||||||
"""
|
|
||||||
prs = Presentation(str(pptx_path))
|
|
||||||
inventory = extract_text_inventory(pptx_path, prs)
|
|
||||||
placeholder_regions = {}
|
|
||||||
|
|
||||||
# Get actual slide dimensions in inches (EMU to inches conversion)
|
|
||||||
slide_width_inches = (prs.slide_width or 9144000) / 914400.0
|
|
||||||
slide_height_inches = (prs.slide_height or 5143500) / 914400.0
|
|
||||||
|
|
||||||
for slide_key, shapes in inventory.items():
|
|
||||||
# Extract slide index from "slide-N" format
|
|
||||||
slide_idx = int(slide_key.split("-")[1])
|
|
||||||
regions = []
|
|
||||||
|
|
||||||
for shape_key, shape_data in shapes.items():
|
|
||||||
# The inventory only contains shapes with text, so all shapes should be highlighted
|
|
||||||
regions.append(
|
|
||||||
{
|
|
||||||
"left": shape_data.left,
|
|
||||||
"top": shape_data.top,
|
|
||||||
"width": shape_data.width,
|
|
||||||
"height": shape_data.height,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if regions:
|
|
||||||
placeholder_regions[slide_idx] = regions
|
|
||||||
|
|
||||||
return placeholder_regions, (slide_width_inches, slide_height_inches)
|
|
||||||
|
|
||||||
|
|
||||||
def convert_to_images(pptx_path, temp_dir, dpi):
|
|
||||||
"""Convert PowerPoint to images via PDF, handling hidden slides."""
|
|
||||||
# Detect hidden slides
|
|
||||||
print("Analyzing presentation...")
|
|
||||||
prs = Presentation(str(pptx_path))
|
|
||||||
total_slides = len(prs.slides)
|
|
||||||
|
|
||||||
# Find hidden slides (1-based indexing for display)
|
|
||||||
hidden_slides = {
|
|
||||||
idx + 1
|
|
||||||
for idx, slide in enumerate(prs.slides)
|
|
||||||
if slide.element.get("show") == "0"
|
|
||||||
}
|
|
||||||
|
|
||||||
print(f"Total slides: {total_slides}")
|
|
||||||
if hidden_slides:
|
|
||||||
print(f"Hidden slides: {sorted(hidden_slides)}")
|
|
||||||
|
|
||||||
pdf_path = temp_dir / f"{pptx_path.stem}.pdf"
|
|
||||||
|
|
||||||
# Convert to PDF
|
|
||||||
print("Converting to PDF...")
|
|
||||||
result = subprocess.run(
|
|
||||||
[
|
|
||||||
"soffice",
|
|
||||||
"--headless",
|
|
||||||
"--convert-to",
|
|
||||||
"pdf",
|
|
||||||
"--outdir",
|
|
||||||
str(temp_dir),
|
|
||||||
str(pptx_path),
|
|
||||||
],
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
)
|
|
||||||
if result.returncode != 0 or not pdf_path.exists():
|
|
||||||
raise RuntimeError("PDF conversion failed")
|
|
||||||
|
|
||||||
# Convert PDF to images
|
|
||||||
print(f"Converting to images at {dpi} DPI...")
|
|
||||||
result = subprocess.run(
|
|
||||||
["pdftoppm", "-jpeg", "-r", str(dpi), str(pdf_path), str(temp_dir / "slide")],
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
)
|
|
||||||
if result.returncode != 0:
|
|
||||||
raise RuntimeError("Image conversion failed")
|
|
||||||
|
|
||||||
visible_images = sorted(temp_dir.glob("slide-*.jpg"))
|
|
||||||
|
|
||||||
# Create full list with placeholders for hidden slides
|
|
||||||
all_images = []
|
|
||||||
visible_idx = 0
|
|
||||||
|
|
||||||
# Get placeholder dimensions from first visible slide
|
|
||||||
if visible_images:
|
|
||||||
with Image.open(visible_images[0]) as img:
|
|
||||||
placeholder_size = img.size
|
|
||||||
else:
|
|
||||||
placeholder_size = (1920, 1080)
|
|
||||||
|
|
||||||
for slide_num in range(1, total_slides + 1):
|
|
||||||
if slide_num in hidden_slides:
|
|
||||||
# Create placeholder image for hidden slide
|
|
||||||
placeholder_path = temp_dir / f"hidden-{slide_num:03d}.jpg"
|
|
||||||
placeholder_img = create_hidden_slide_placeholder(placeholder_size)
|
|
||||||
placeholder_img.save(placeholder_path, "JPEG")
|
|
||||||
all_images.append(placeholder_path)
|
|
||||||
else:
|
|
||||||
# Use the actual visible slide image
|
|
||||||
if visible_idx < len(visible_images):
|
|
||||||
all_images.append(visible_images[visible_idx])
|
|
||||||
visible_idx += 1
|
|
||||||
|
|
||||||
return all_images
|
|
||||||
|
|
||||||
|
|
||||||
def create_grids(
|
|
||||||
image_paths,
|
|
||||||
cols,
|
|
||||||
width,
|
|
||||||
output_path,
|
|
||||||
placeholder_regions=None,
|
|
||||||
slide_dimensions=None,
|
|
||||||
):
|
|
||||||
"""Create multiple thumbnail grids from slide images, max cols×(cols+1) images per grid."""
|
|
||||||
# Maximum images per grid is cols × (cols + 1) for better proportions
|
|
||||||
max_images_per_grid = cols * (cols + 1)
|
|
||||||
grid_files = []
|
|
||||||
|
|
||||||
print(
|
|
||||||
f"Creating grids with {cols} columns (max {max_images_per_grid} images per grid)"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Split images into chunks
|
|
||||||
for chunk_idx, start_idx in enumerate(
|
|
||||||
range(0, len(image_paths), max_images_per_grid)
|
|
||||||
):
|
|
||||||
end_idx = min(start_idx + max_images_per_grid, len(image_paths))
|
|
||||||
chunk_images = image_paths[start_idx:end_idx]
|
|
||||||
|
|
||||||
# Create grid for this chunk
|
|
||||||
grid = create_grid(
|
|
||||||
chunk_images, cols, width, start_idx, placeholder_regions, slide_dimensions
|
|
||||||
)
|
|
||||||
|
|
||||||
# Generate output filename
|
|
||||||
if len(image_paths) <= max_images_per_grid:
|
|
||||||
# Single grid - use base filename without suffix
|
|
||||||
grid_filename = output_path
|
|
||||||
else:
|
|
||||||
# Multiple grids - insert index before extension with dash
|
|
||||||
stem = output_path.stem
|
|
||||||
suffix = output_path.suffix
|
|
||||||
grid_filename = output_path.parent / f"{stem}-{chunk_idx + 1}{suffix}"
|
|
||||||
|
|
||||||
# Save grid
|
|
||||||
grid_filename.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
grid.save(str(grid_filename), quality=JPEG_QUALITY)
|
|
||||||
grid_files.append(str(grid_filename))
|
|
||||||
|
|
||||||
return grid_files
|
|
||||||
|
|
||||||
|
|
||||||
def create_grid(
|
|
||||||
image_paths,
|
|
||||||
cols,
|
|
||||||
width,
|
|
||||||
start_slide_num=0,
|
|
||||||
placeholder_regions=None,
|
|
||||||
slide_dimensions=None,
|
|
||||||
):
|
|
||||||
"""Create thumbnail grid from slide images with optional placeholder outlining."""
|
|
||||||
font_size = int(width * FONT_SIZE_RATIO)
|
|
||||||
label_padding = int(font_size * LABEL_PADDING_RATIO)
|
|
||||||
|
|
||||||
# Get dimensions
|
|
||||||
with Image.open(image_paths[0]) as img:
|
|
||||||
aspect = img.height / img.width
|
|
||||||
height = int(width * aspect)
|
|
||||||
|
|
||||||
# Calculate grid size
|
|
||||||
rows = (len(image_paths) + cols - 1) // cols
|
|
||||||
grid_w = cols * width + (cols + 1) * GRID_PADDING
|
|
||||||
grid_h = rows * (height + font_size + label_padding * 2) + (rows + 1) * GRID_PADDING
|
|
||||||
|
|
||||||
# Create grid
|
|
||||||
grid = Image.new("RGB", (grid_w, grid_h), "white")
|
|
||||||
draw = ImageDraw.Draw(grid)
|
|
||||||
|
|
||||||
# Load font with size based on thumbnail width
|
|
||||||
try:
|
|
||||||
# Use Pillow's default font with size
|
|
||||||
font = ImageFont.load_default(size=font_size)
|
|
||||||
except Exception:
|
|
||||||
# Fall back to basic default font if size parameter not supported
|
|
||||||
font = ImageFont.load_default()
|
|
||||||
|
|
||||||
# Place thumbnails
|
|
||||||
for i, img_path in enumerate(image_paths):
|
|
||||||
row, col = i // cols, i % cols
|
|
||||||
x = col * width + (col + 1) * GRID_PADDING
|
|
||||||
y_base = (
|
|
||||||
row * (height + font_size + label_padding * 2) + (row + 1) * GRID_PADDING
|
|
||||||
)
|
|
||||||
|
|
||||||
# Add label with actual slide number
|
|
||||||
label = f"{start_slide_num + i}"
|
|
||||||
bbox = draw.textbbox((0, 0), label, font=font)
|
|
||||||
text_w = bbox[2] - bbox[0]
|
|
||||||
draw.text(
|
|
||||||
(x + (width - text_w) // 2, y_base + label_padding),
|
|
||||||
label,
|
|
||||||
fill="black",
|
|
||||||
font=font,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Add thumbnail below label with proportional spacing
|
|
||||||
y_thumbnail = y_base + label_padding + font_size + label_padding
|
|
||||||
|
|
||||||
with Image.open(img_path) as img:
|
|
||||||
# Get original dimensions before thumbnail
|
|
||||||
orig_w, orig_h = img.size
|
|
||||||
|
|
||||||
# Apply placeholder outlines if enabled
|
|
||||||
if placeholder_regions and (start_slide_num + i) in placeholder_regions:
|
|
||||||
# Convert to RGBA for transparency support
|
|
||||||
if img.mode != "RGBA":
|
|
||||||
img = img.convert("RGBA")
|
|
||||||
|
|
||||||
# Get the regions for this slide
|
|
||||||
regions = placeholder_regions[start_slide_num + i]
|
|
||||||
|
|
||||||
# Calculate scale factors using actual slide dimensions
|
|
||||||
if slide_dimensions:
|
|
||||||
slide_width_inches, slide_height_inches = slide_dimensions
|
|
||||||
else:
|
|
||||||
# Fallback: estimate from image size at CONVERSION_DPI
|
|
||||||
slide_width_inches = orig_w / CONVERSION_DPI
|
|
||||||
slide_height_inches = orig_h / CONVERSION_DPI
|
|
||||||
|
|
||||||
x_scale = orig_w / slide_width_inches
|
|
||||||
y_scale = orig_h / slide_height_inches
|
|
||||||
|
|
||||||
# Create a highlight overlay
|
|
||||||
overlay = Image.new("RGBA", img.size, (255, 255, 255, 0))
|
|
||||||
overlay_draw = ImageDraw.Draw(overlay)
|
|
||||||
|
|
||||||
# Highlight each placeholder region
|
|
||||||
for region in regions:
|
|
||||||
# Convert from inches to pixels in the original image
|
|
||||||
px_left = int(region["left"] * x_scale)
|
|
||||||
px_top = int(region["top"] * y_scale)
|
|
||||||
px_width = int(region["width"] * x_scale)
|
|
||||||
px_height = int(region["height"] * y_scale)
|
|
||||||
|
|
||||||
# Draw highlight outline with red color and thick stroke
|
|
||||||
# Using a bright red outline instead of fill
|
|
||||||
stroke_width = max(
|
|
||||||
5, min(orig_w, orig_h) // 150
|
|
||||||
) # Thicker proportional stroke width
|
|
||||||
overlay_draw.rectangle(
|
|
||||||
[(px_left, px_top), (px_left + px_width, px_top + px_height)],
|
|
||||||
outline=(255, 0, 0, 255), # Bright red, fully opaque
|
|
||||||
width=stroke_width,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Composite the overlay onto the image using alpha blending
|
|
||||||
img = Image.alpha_composite(img, overlay)
|
|
||||||
# Convert back to RGB for JPEG saving
|
|
||||||
img = img.convert("RGB")
|
|
||||||
|
|
||||||
img.thumbnail((width, height), Image.Resampling.LANCZOS)
|
|
||||||
w, h = img.size
|
|
||||||
tx = x + (width - w) // 2
|
|
||||||
ty = y_thumbnail + (height - h) // 2
|
|
||||||
grid.paste(img, (tx, ty))
|
|
||||||
|
|
||||||
# Add border
|
|
||||||
if BORDER_WIDTH > 0:
|
|
||||||
draw.rectangle(
|
|
||||||
[
|
|
||||||
(tx - BORDER_WIDTH, ty - BORDER_WIDTH),
|
|
||||||
(tx + w + BORDER_WIDTH - 1, ty + h + BORDER_WIDTH - 1),
|
|
||||||
],
|
|
||||||
outline="gray",
|
|
||||||
width=BORDER_WIDTH,
|
|
||||||
)
|
|
||||||
|
|
||||||
return grid
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "eapil-threejs",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"description": "EAPIL Three.js 提供面向 3D 场景、几何体、材质、纹理、灯光、加载器、交互、动画、后处理和着色器的 Codex 技能集合。",
|
|
||||||
"author": {
|
|
||||||
"name": "EAPIL",
|
|
||||||
"url": "https://git.playones.com/arechen/EapilSkillMarket"
|
|
||||||
},
|
|
||||||
"homepage": "https://git.playones.com/arechen/EapilSkillMarket",
|
|
||||||
"repository": "https://git.playones.com/arechen/EapilSkillMarket",
|
|
||||||
"license": "Proprietary",
|
|
||||||
"keywords": [
|
|
||||||
"eapil",
|
|
||||||
"threejs",
|
|
||||||
"3d",
|
|
||||||
"codex-skill"
|
|
||||||
],
|
|
||||||
"skills": "./skills/",
|
|
||||||
"interface": {
|
|
||||||
"displayName": "EAPIL Three.js",
|
|
||||||
"shortDescription": "Three.js 3D 开发技能包,覆盖场景、材质、纹理、灯光、加载、交互、动画、后处理和着色器。",
|
|
||||||
"longDescription": "EAPIL Three.js 提供面向 Three.js 项目的技能集合,帮助 Codex 在 3D 场景搭建、几何体、材质、纹理、灯光、加载器、交互、动画、后处理和自定义着色器任务中选择合适的工作流。",
|
|
||||||
"developerName": "EAPIL",
|
|
||||||
"category": "开发工具",
|
|
||||||
"capabilities": [
|
|
||||||
"Read",
|
|
||||||
"Write"
|
|
||||||
],
|
|
||||||
"defaultPrompt": [
|
|
||||||
"使用 EAPIL Three.js 帮我实现这个 3D 场景。",
|
|
||||||
"使用 EAPIL Three.js 检查这个 Three.js 项目。",
|
|
||||||
"使用 EAPIL Three.js 优化这个 WebGL 效果。"
|
|
||||||
],
|
|
||||||
"websiteURL": "https://git.playones.com/arechen/EapilSkillMarket",
|
|
||||||
"privacyPolicyURL": "https://git.playones.com/arechen/EapilSkillMarket",
|
|
||||||
"termsOfServiceURL": "https://git.playones.com/arechen/EapilSkillMarket",
|
|
||||||
"brandColor": "#2563EB",
|
|
||||||
"screenshots": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,552 +0,0 @@
|
|||||||
---
|
|
||||||
name: threejs-animation
|
|
||||||
description: "Three.js 动画处理,用于关键帧、骨骼动画、morph targets、动画混合、GLTF 动画播放和程序化运动。"
|
|
||||||
---
|
|
||||||
|
|
||||||
# Three.js Animation
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import * as THREE from "three";
|
|
||||||
|
|
||||||
// Simple procedural animation
|
|
||||||
const clock = new THREE.Clock();
|
|
||||||
|
|
||||||
function animate() {
|
|
||||||
const delta = clock.getDelta();
|
|
||||||
const elapsed = clock.getElapsedTime();
|
|
||||||
|
|
||||||
mesh.rotation.y += delta;
|
|
||||||
mesh.position.y = Math.sin(elapsed) * 0.5;
|
|
||||||
|
|
||||||
requestAnimationFrame(animate);
|
|
||||||
renderer.render(scene, camera);
|
|
||||||
}
|
|
||||||
animate();
|
|
||||||
```
|
|
||||||
|
|
||||||
## Animation System Overview
|
|
||||||
|
|
||||||
Three.js animation system has three main components:
|
|
||||||
|
|
||||||
1. **AnimationClip** - Container for keyframe data
|
|
||||||
2. **AnimationMixer** - Plays animations on a root object
|
|
||||||
3. **AnimationAction** - Controls playback of a clip
|
|
||||||
|
|
||||||
## AnimationClip
|
|
||||||
|
|
||||||
Stores keyframe animation data.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Create animation clip
|
|
||||||
const times = [0, 1, 2]; // Keyframe times (seconds)
|
|
||||||
const values = [0, 1, 0]; // Values at each keyframe
|
|
||||||
|
|
||||||
const track = new THREE.NumberKeyframeTrack(
|
|
||||||
".position[y]", // Property path
|
|
||||||
times,
|
|
||||||
values,
|
|
||||||
);
|
|
||||||
|
|
||||||
const clip = new THREE.AnimationClip("bounce", 2, [track]);
|
|
||||||
```
|
|
||||||
|
|
||||||
### KeyframeTrack Types
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Number track (single value)
|
|
||||||
new THREE.NumberKeyframeTrack(".opacity", times, [1, 0]);
|
|
||||||
new THREE.NumberKeyframeTrack(".material.opacity", times, [1, 0]);
|
|
||||||
|
|
||||||
// Vector track (position, scale)
|
|
||||||
new THREE.VectorKeyframeTrack(".position", times, [
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0, // t=0
|
|
||||||
1,
|
|
||||||
2,
|
|
||||||
0, // t=1
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0, // t=2
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Quaternion track (rotation)
|
|
||||||
const q1 = new THREE.Quaternion().setFromEuler(new THREE.Euler(0, 0, 0));
|
|
||||||
const q2 = new THREE.Quaternion().setFromEuler(new THREE.Euler(0, Math.PI, 0));
|
|
||||||
new THREE.QuaternionKeyframeTrack(
|
|
||||||
".quaternion",
|
|
||||||
[0, 1],
|
|
||||||
[q1.x, q1.y, q1.z, q1.w, q2.x, q2.y, q2.z, q2.w],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Color track
|
|
||||||
new THREE.ColorKeyframeTrack(".material.color", times, [
|
|
||||||
1,
|
|
||||||
0,
|
|
||||||
0, // red
|
|
||||||
0,
|
|
||||||
1,
|
|
||||||
0, // green
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
1, // blue
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Boolean track
|
|
||||||
new THREE.BooleanKeyframeTrack(".visible", [0, 0.5, 1], [true, false, true]);
|
|
||||||
|
|
||||||
// String track (for morph targets)
|
|
||||||
new THREE.StringKeyframeTrack(
|
|
||||||
".morphTargetInfluences[smile]",
|
|
||||||
[0, 1],
|
|
||||||
["0", "1"],
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Interpolation Modes
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const track = new THREE.VectorKeyframeTrack(".position", times, values);
|
|
||||||
|
|
||||||
// Interpolation
|
|
||||||
track.setInterpolation(THREE.InterpolateLinear); // Default
|
|
||||||
track.setInterpolation(THREE.InterpolateSmooth); // Cubic spline
|
|
||||||
track.setInterpolation(THREE.InterpolateDiscrete); // Step function
|
|
||||||
```
|
|
||||||
|
|
||||||
## AnimationMixer
|
|
||||||
|
|
||||||
Plays animations on an object and its descendants.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const mixer = new THREE.AnimationMixer(model);
|
|
||||||
|
|
||||||
// Create action from clip
|
|
||||||
const action = mixer.clipAction(clip);
|
|
||||||
action.play();
|
|
||||||
|
|
||||||
// Update in animation loop
|
|
||||||
function animate() {
|
|
||||||
const delta = clock.getDelta();
|
|
||||||
mixer.update(delta); // Required!
|
|
||||||
|
|
||||||
requestAnimationFrame(animate);
|
|
||||||
renderer.render(scene, camera);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Mixer Events
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
mixer.addEventListener("finished", (e) => {
|
|
||||||
console.log("Animation finished:", e.action.getClip().name);
|
|
||||||
});
|
|
||||||
|
|
||||||
mixer.addEventListener("loop", (e) => {
|
|
||||||
console.log("Animation looped:", e.action.getClip().name);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## AnimationAction
|
|
||||||
|
|
||||||
Controls playback of an animation clip.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const action = mixer.clipAction(clip);
|
|
||||||
|
|
||||||
// Playback control
|
|
||||||
action.play();
|
|
||||||
action.stop();
|
|
||||||
action.reset();
|
|
||||||
action.halt(fadeOutDuration);
|
|
||||||
|
|
||||||
// Playback state
|
|
||||||
action.isRunning();
|
|
||||||
action.isScheduled();
|
|
||||||
|
|
||||||
// Time control
|
|
||||||
action.time = 0.5; // Current time
|
|
||||||
action.timeScale = 1; // Playback speed (negative = reverse)
|
|
||||||
action.paused = false;
|
|
||||||
|
|
||||||
// Weight (for blending)
|
|
||||||
action.weight = 1; // 0-1, contribution to final pose
|
|
||||||
action.setEffectiveWeight(1);
|
|
||||||
|
|
||||||
// Loop modes
|
|
||||||
action.loop = THREE.LoopRepeat; // Default: loop forever
|
|
||||||
action.loop = THREE.LoopOnce; // Play once and stop
|
|
||||||
action.loop = THREE.LoopPingPong; // Alternate forward/backward
|
|
||||||
action.repetitions = 3; // Number of loops (Infinity default)
|
|
||||||
|
|
||||||
// Clamping
|
|
||||||
action.clampWhenFinished = true; // Hold last frame when done
|
|
||||||
|
|
||||||
// Blending
|
|
||||||
action.blendMode = THREE.NormalAnimationBlendMode;
|
|
||||||
action.blendMode = THREE.AdditiveAnimationBlendMode;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Fade In/Out
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Fade in
|
|
||||||
action.reset().fadeIn(0.5).play();
|
|
||||||
|
|
||||||
// Fade out
|
|
||||||
action.fadeOut(0.5);
|
|
||||||
|
|
||||||
// Crossfade between animations
|
|
||||||
const action1 = mixer.clipAction(clip1);
|
|
||||||
const action2 = mixer.clipAction(clip2);
|
|
||||||
|
|
||||||
action1.play();
|
|
||||||
|
|
||||||
// Later, crossfade to action2
|
|
||||||
action1.crossFadeTo(action2, 0.5, true);
|
|
||||||
action2.play();
|
|
||||||
```
|
|
||||||
|
|
||||||
## Loading GLTF Animations
|
|
||||||
|
|
||||||
Most common source of skeletal animations.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
|
|
||||||
|
|
||||||
const loader = new GLTFLoader();
|
|
||||||
loader.load("model.glb", (gltf) => {
|
|
||||||
const model = gltf.scene;
|
|
||||||
scene.add(model);
|
|
||||||
|
|
||||||
// Create mixer
|
|
||||||
const mixer = new THREE.AnimationMixer(model);
|
|
||||||
|
|
||||||
// Get all clips
|
|
||||||
const clips = gltf.animations;
|
|
||||||
console.log(
|
|
||||||
"Available animations:",
|
|
||||||
clips.map((c) => c.name),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Play first animation
|
|
||||||
if (clips.length > 0) {
|
|
||||||
const action = mixer.clipAction(clips[0]);
|
|
||||||
action.play();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Play specific animation by name
|
|
||||||
const walkClip = THREE.AnimationClip.findByName(clips, "Walk");
|
|
||||||
if (walkClip) {
|
|
||||||
mixer.clipAction(walkClip).play();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store mixer for update loop
|
|
||||||
window.mixer = mixer;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Animation loop
|
|
||||||
function animate() {
|
|
||||||
const delta = clock.getDelta();
|
|
||||||
if (window.mixer) window.mixer.update(delta);
|
|
||||||
|
|
||||||
requestAnimationFrame(animate);
|
|
||||||
renderer.render(scene, camera);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Skeletal Animation
|
|
||||||
|
|
||||||
### Skeleton and Bones
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Access skeleton from skinned mesh
|
|
||||||
const skinnedMesh = model.getObjectByProperty("type", "SkinnedMesh");
|
|
||||||
const skeleton = skinnedMesh.skeleton;
|
|
||||||
|
|
||||||
// Access bones
|
|
||||||
skeleton.bones.forEach((bone) => {
|
|
||||||
console.log(bone.name, bone.position, bone.rotation);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Find specific bone by name
|
|
||||||
const headBone = skeleton.bones.find((b) => b.name === "Head");
|
|
||||||
if (headBone) headBone.rotation.y = Math.PI / 4; // Turn head
|
|
||||||
|
|
||||||
// Skeleton helper
|
|
||||||
const helper = new THREE.SkeletonHelper(model);
|
|
||||||
scene.add(helper);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Programmatic Bone Animation
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function animate() {
|
|
||||||
const time = clock.getElapsedTime();
|
|
||||||
|
|
||||||
// Animate bone
|
|
||||||
const headBone = skeleton.bones.find((b) => b.name === "Head");
|
|
||||||
if (headBone) {
|
|
||||||
headBone.rotation.y = Math.sin(time) * 0.3;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update mixer if also playing clips
|
|
||||||
mixer.update(clock.getDelta());
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Bone Attachments
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Attach object to bone
|
|
||||||
const weapon = new THREE.Mesh(weaponGeometry, weaponMaterial);
|
|
||||||
const handBone = skeleton.bones.find((b) => b.name === "RightHand");
|
|
||||||
if (handBone) handBone.add(weapon);
|
|
||||||
|
|
||||||
// Offset attachment
|
|
||||||
weapon.position.set(0, 0, 0.5);
|
|
||||||
weapon.rotation.set(0, Math.PI / 2, 0);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Morph Targets
|
|
||||||
|
|
||||||
Blend between different mesh shapes.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Morph targets are stored in geometry
|
|
||||||
const geometry = mesh.geometry;
|
|
||||||
console.log("Morph attributes:", Object.keys(geometry.morphAttributes));
|
|
||||||
|
|
||||||
// Access morph target influences
|
|
||||||
mesh.morphTargetInfluences; // Array of weights
|
|
||||||
mesh.morphTargetDictionary; // Name -> index mapping
|
|
||||||
|
|
||||||
// Set morph target by index
|
|
||||||
mesh.morphTargetInfluences[0] = 0.5;
|
|
||||||
|
|
||||||
// Set by name
|
|
||||||
const smileIndex = mesh.morphTargetDictionary["smile"];
|
|
||||||
mesh.morphTargetInfluences[smileIndex] = 1;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Animating Morph Targets
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Procedural
|
|
||||||
function animate() {
|
|
||||||
const t = clock.getElapsedTime();
|
|
||||||
mesh.morphTargetInfluences[0] = (Math.sin(t) + 1) / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// With keyframe animation
|
|
||||||
const track = new THREE.NumberKeyframeTrack(
|
|
||||||
".morphTargetInfluences[smile]",
|
|
||||||
[0, 0.5, 1],
|
|
||||||
[0, 1, 0],
|
|
||||||
);
|
|
||||||
const clip = new THREE.AnimationClip("smile", 1, [track]);
|
|
||||||
mixer.clipAction(clip).play();
|
|
||||||
```
|
|
||||||
|
|
||||||
## Animation Blending
|
|
||||||
|
|
||||||
Mix multiple animations together.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Setup actions
|
|
||||||
const idleAction = mixer.clipAction(idleClip);
|
|
||||||
const walkAction = mixer.clipAction(walkClip);
|
|
||||||
const runAction = mixer.clipAction(runClip);
|
|
||||||
|
|
||||||
// Play all with different weights
|
|
||||||
idleAction.play();
|
|
||||||
walkAction.play();
|
|
||||||
runAction.play();
|
|
||||||
|
|
||||||
// Set initial weights
|
|
||||||
idleAction.setEffectiveWeight(1);
|
|
||||||
walkAction.setEffectiveWeight(0);
|
|
||||||
runAction.setEffectiveWeight(0);
|
|
||||||
|
|
||||||
// Blend based on speed
|
|
||||||
function updateAnimations(speed) {
|
|
||||||
if (speed < 0.1) {
|
|
||||||
idleAction.setEffectiveWeight(1);
|
|
||||||
walkAction.setEffectiveWeight(0);
|
|
||||||
runAction.setEffectiveWeight(0);
|
|
||||||
} else if (speed < 5) {
|
|
||||||
const t = speed / 5;
|
|
||||||
idleAction.setEffectiveWeight(1 - t);
|
|
||||||
walkAction.setEffectiveWeight(t);
|
|
||||||
runAction.setEffectiveWeight(0);
|
|
||||||
} else {
|
|
||||||
const t = Math.min((speed - 5) / 5, 1);
|
|
||||||
idleAction.setEffectiveWeight(0);
|
|
||||||
walkAction.setEffectiveWeight(1 - t);
|
|
||||||
runAction.setEffectiveWeight(t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Additive Blending
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Base pose
|
|
||||||
const baseAction = mixer.clipAction(baseClip);
|
|
||||||
baseAction.play();
|
|
||||||
|
|
||||||
// Additive layer (e.g., breathing)
|
|
||||||
const additiveAction = mixer.clipAction(additiveClip);
|
|
||||||
additiveAction.blendMode = THREE.AdditiveAnimationBlendMode;
|
|
||||||
additiveAction.play();
|
|
||||||
|
|
||||||
// Convert clip to additive
|
|
||||||
THREE.AnimationUtils.makeClipAdditive(additiveClip);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Animation Utilities
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import * as THREE from "three";
|
|
||||||
|
|
||||||
// Find clip by name
|
|
||||||
const clip = THREE.AnimationClip.findByName(clips, "Walk");
|
|
||||||
|
|
||||||
// Create subclip
|
|
||||||
const subclip = THREE.AnimationUtils.subclip(clip, "subclip", 0, 30, 30);
|
|
||||||
|
|
||||||
// Convert to additive
|
|
||||||
THREE.AnimationUtils.makeClipAdditive(clip);
|
|
||||||
THREE.AnimationUtils.makeClipAdditive(clip, 0, referenceClip);
|
|
||||||
|
|
||||||
// Clone clip
|
|
||||||
const clone = clip.clone();
|
|
||||||
|
|
||||||
// Get clip duration
|
|
||||||
clip.duration;
|
|
||||||
|
|
||||||
// Optimize clip (remove redundant keyframes)
|
|
||||||
clip.optimize();
|
|
||||||
|
|
||||||
// Reset clip to first frame
|
|
||||||
clip.resetDuration();
|
|
||||||
```
|
|
||||||
|
|
||||||
## Procedural Animation Patterns
|
|
||||||
|
|
||||||
### Smooth Damping
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Smooth follow/lerp
|
|
||||||
const target = new THREE.Vector3();
|
|
||||||
const current = new THREE.Vector3();
|
|
||||||
const velocity = new THREE.Vector3();
|
|
||||||
|
|
||||||
function smoothDamp(current, target, velocity, smoothTime, deltaTime) {
|
|
||||||
const omega = 2 / smoothTime;
|
|
||||||
const x = omega * deltaTime;
|
|
||||||
const exp = 1 / (1 + x + 0.48 * x * x + 0.235 * x * x * x);
|
|
||||||
const change = current.clone().sub(target);
|
|
||||||
const temp = velocity
|
|
||||||
.clone()
|
|
||||||
.add(change.clone().multiplyScalar(omega))
|
|
||||||
.multiplyScalar(deltaTime);
|
|
||||||
velocity.sub(temp.clone().multiplyScalar(omega)).multiplyScalar(exp);
|
|
||||||
return target.clone().add(change.add(temp).multiplyScalar(exp));
|
|
||||||
}
|
|
||||||
|
|
||||||
function animate() {
|
|
||||||
current.copy(smoothDamp(current, target, velocity, 0.3, delta));
|
|
||||||
mesh.position.copy(current);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Spring Physics
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
class Spring {
|
|
||||||
constructor(stiffness = 100, damping = 10) {
|
|
||||||
this.stiffness = stiffness;
|
|
||||||
this.damping = damping;
|
|
||||||
this.position = 0;
|
|
||||||
this.velocity = 0;
|
|
||||||
this.target = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
update(dt) {
|
|
||||||
const force = -this.stiffness * (this.position - this.target);
|
|
||||||
const dampingForce = -this.damping * this.velocity;
|
|
||||||
this.velocity += (force + dampingForce) * dt;
|
|
||||||
this.position += this.velocity * dt;
|
|
||||||
return this.position;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const spring = new Spring(100, 10);
|
|
||||||
spring.target = 1;
|
|
||||||
|
|
||||||
function animate() {
|
|
||||||
mesh.position.y = spring.update(delta);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Oscillation
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function animate() {
|
|
||||||
const t = clock.getElapsedTime();
|
|
||||||
|
|
||||||
// Sine wave
|
|
||||||
mesh.position.y = Math.sin(t * 2) * 0.5;
|
|
||||||
|
|
||||||
// Bouncing
|
|
||||||
mesh.position.y = Math.abs(Math.sin(t * 3)) * 2;
|
|
||||||
|
|
||||||
// Circular motion
|
|
||||||
mesh.position.x = Math.cos(t) * 2;
|
|
||||||
mesh.position.z = Math.sin(t) * 2;
|
|
||||||
|
|
||||||
// Figure 8
|
|
||||||
mesh.position.x = Math.sin(t) * 2;
|
|
||||||
mesh.position.z = Math.sin(t * 2) * 1;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Performance Tips
|
|
||||||
|
|
||||||
1. **Share clips**: Same AnimationClip can be used on multiple mixers
|
|
||||||
2. **Optimize clips**: Call `clip.optimize()` to remove redundant keyframes
|
|
||||||
3. **Disable when off-screen**: Stop mixer updates for invisible objects
|
|
||||||
4. **Use LOD for animations**: Simpler rigs for distant characters
|
|
||||||
5. **Limit active mixers**: Each mixer.update() has a cost
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Pause animation when not visible
|
|
||||||
mesh.onBeforeRender = () => {
|
|
||||||
action.paused = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
mesh.onAfterRender = () => {
|
|
||||||
// Check if will be visible next frame
|
|
||||||
if (!isInFrustum(mesh)) {
|
|
||||||
action.paused = true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Cache clips
|
|
||||||
const clipCache = new Map();
|
|
||||||
function getClip(name) {
|
|
||||||
if (!clipCache.has(name)) {
|
|
||||||
clipCache.set(name, loadClip(name));
|
|
||||||
}
|
|
||||||
return clipCache.get(name);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## See Also
|
|
||||||
|
|
||||||
- `threejs-loaders` - Loading animated GLTF models
|
|
||||||
- `threejs-fundamentals` - Clock and animation loop
|
|
||||||
- `threejs-shaders` - Vertex animation in shaders
|
|
||||||
@@ -1,488 +0,0 @@
|
|||||||
---
|
|
||||||
name: threejs-fundamentals
|
|
||||||
description: "Three.js 基础场景搭建,用于 scene、camera、renderer、Object3D 层级、坐标系、变换和渲染循环。"
|
|
||||||
---
|
|
||||||
|
|
||||||
# Three.js Fundamentals
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import * as THREE from "three";
|
|
||||||
|
|
||||||
// Create scene, camera, renderer
|
|
||||||
const scene = new THREE.Scene();
|
|
||||||
const camera = new THREE.PerspectiveCamera(
|
|
||||||
75,
|
|
||||||
window.innerWidth / window.innerHeight,
|
|
||||||
0.1,
|
|
||||||
1000,
|
|
||||||
);
|
|
||||||
const renderer = new THREE.WebGLRenderer({ antialias: true });
|
|
||||||
|
|
||||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
|
||||||
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
|
|
||||||
document.body.appendChild(renderer.domElement);
|
|
||||||
|
|
||||||
// Add a mesh
|
|
||||||
const geometry = new THREE.BoxGeometry(1, 1, 1);
|
|
||||||
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
|
|
||||||
const cube = new THREE.Mesh(geometry, material);
|
|
||||||
scene.add(cube);
|
|
||||||
|
|
||||||
// Add light
|
|
||||||
scene.add(new THREE.AmbientLight(0xffffff, 0.5));
|
|
||||||
const dirLight = new THREE.DirectionalLight(0xffffff, 1);
|
|
||||||
dirLight.position.set(5, 5, 5);
|
|
||||||
scene.add(dirLight);
|
|
||||||
|
|
||||||
camera.position.z = 5;
|
|
||||||
|
|
||||||
// Animation loop
|
|
||||||
function animate() {
|
|
||||||
requestAnimationFrame(animate);
|
|
||||||
cube.rotation.x += 0.01;
|
|
||||||
cube.rotation.y += 0.01;
|
|
||||||
renderer.render(scene, camera);
|
|
||||||
}
|
|
||||||
animate();
|
|
||||||
|
|
||||||
// Handle resize
|
|
||||||
window.addEventListener("resize", () => {
|
|
||||||
camera.aspect = window.innerWidth / window.innerHeight;
|
|
||||||
camera.updateProjectionMatrix();
|
|
||||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Core Classes
|
|
||||||
|
|
||||||
### Scene
|
|
||||||
|
|
||||||
Container for all 3D objects, lights, and cameras.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const scene = new THREE.Scene();
|
|
||||||
scene.background = new THREE.Color(0x000000); // Solid color
|
|
||||||
scene.background = texture; // Skybox texture
|
|
||||||
scene.background = cubeTexture; // Cubemap
|
|
||||||
scene.environment = envMap; // Environment map for PBR
|
|
||||||
scene.fog = new THREE.Fog(0xffffff, 1, 100); // Linear fog
|
|
||||||
scene.fog = new THREE.FogExp2(0xffffff, 0.02); // Exponential fog
|
|
||||||
```
|
|
||||||
|
|
||||||
### Cameras
|
|
||||||
|
|
||||||
**PerspectiveCamera** - Most common, simulates human eye.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// PerspectiveCamera(fov, aspect, near, far)
|
|
||||||
const camera = new THREE.PerspectiveCamera(
|
|
||||||
75, // Field of view (degrees)
|
|
||||||
window.innerWidth / window.innerHeight, // Aspect ratio
|
|
||||||
0.1, // Near clipping plane
|
|
||||||
1000, // Far clipping plane
|
|
||||||
);
|
|
||||||
|
|
||||||
camera.position.set(0, 5, 10);
|
|
||||||
camera.lookAt(0, 0, 0);
|
|
||||||
camera.updateProjectionMatrix(); // Call after changing fov, aspect, near, far
|
|
||||||
```
|
|
||||||
|
|
||||||
**OrthographicCamera** - No perspective distortion, good for 2D/isometric.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// OrthographicCamera(left, right, top, bottom, near, far)
|
|
||||||
const aspect = window.innerWidth / window.innerHeight;
|
|
||||||
const frustumSize = 10;
|
|
||||||
const camera = new THREE.OrthographicCamera(
|
|
||||||
(frustumSize * aspect) / -2,
|
|
||||||
(frustumSize * aspect) / 2,
|
|
||||||
frustumSize / 2,
|
|
||||||
frustumSize / -2,
|
|
||||||
0.1,
|
|
||||||
1000,
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
**ArrayCamera** - Multiple viewports with sub-cameras.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const cameras = [];
|
|
||||||
for (let i = 0; i < 4; i++) {
|
|
||||||
const subcamera = new THREE.PerspectiveCamera(40, 1, 0.1, 100);
|
|
||||||
subcamera.viewport = new THREE.Vector4(
|
|
||||||
Math.floor(i % 2) * 0.5,
|
|
||||||
Math.floor(i / 2) * 0.5,
|
|
||||||
0.5,
|
|
||||||
0.5,
|
|
||||||
);
|
|
||||||
cameras.push(subcamera);
|
|
||||||
}
|
|
||||||
const arrayCamera = new THREE.ArrayCamera(cameras);
|
|
||||||
```
|
|
||||||
|
|
||||||
**CubeCamera** - Renders environment maps for reflections.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const cubeRenderTarget = new THREE.WebGLCubeRenderTarget(256);
|
|
||||||
const cubeCamera = new THREE.CubeCamera(0.1, 1000, cubeRenderTarget);
|
|
||||||
scene.add(cubeCamera);
|
|
||||||
|
|
||||||
// Use for reflections
|
|
||||||
material.envMap = cubeRenderTarget.texture;
|
|
||||||
|
|
||||||
// Update each frame (expensive!)
|
|
||||||
cubeCamera.position.copy(reflectiveMesh.position);
|
|
||||||
cubeCamera.update(renderer, scene);
|
|
||||||
```
|
|
||||||
|
|
||||||
### WebGLRenderer
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const renderer = new THREE.WebGLRenderer({
|
|
||||||
canvas: document.querySelector("#canvas"), // Optional existing canvas
|
|
||||||
antialias: true, // Smooth edges
|
|
||||||
alpha: true, // Transparent background
|
|
||||||
powerPreference: "high-performance", // GPU hint
|
|
||||||
preserveDrawingBuffer: true, // For screenshots
|
|
||||||
});
|
|
||||||
|
|
||||||
renderer.setSize(width, height);
|
|
||||||
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
|
|
||||||
|
|
||||||
// Tone mapping
|
|
||||||
renderer.toneMapping = THREE.ACESFilmicToneMapping;
|
|
||||||
renderer.toneMappingExposure = 1.0;
|
|
||||||
|
|
||||||
// Color space (Three.js r152+)
|
|
||||||
renderer.outputColorSpace = THREE.SRGBColorSpace;
|
|
||||||
|
|
||||||
// Shadows
|
|
||||||
renderer.shadowMap.enabled = true;
|
|
||||||
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
|
||||||
|
|
||||||
// Clear color
|
|
||||||
renderer.setClearColor(0x000000, 1);
|
|
||||||
|
|
||||||
// Render
|
|
||||||
renderer.render(scene, camera);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Object3D
|
|
||||||
|
|
||||||
Base class for all 3D objects. Mesh, Group, Light, Camera all extend Object3D.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const obj = new THREE.Object3D();
|
|
||||||
|
|
||||||
// Transform
|
|
||||||
obj.position.set(x, y, z);
|
|
||||||
obj.rotation.set(x, y, z); // Euler angles (radians)
|
|
||||||
obj.quaternion.set(x, y, z, w); // Quaternion rotation
|
|
||||||
obj.scale.set(x, y, z);
|
|
||||||
|
|
||||||
// Local vs World transforms
|
|
||||||
obj.getWorldPosition(targetVector);
|
|
||||||
obj.getWorldQuaternion(targetQuaternion);
|
|
||||||
obj.getWorldDirection(targetVector);
|
|
||||||
|
|
||||||
// Hierarchy
|
|
||||||
obj.add(child);
|
|
||||||
obj.remove(child);
|
|
||||||
obj.parent;
|
|
||||||
obj.children;
|
|
||||||
|
|
||||||
// Visibility
|
|
||||||
obj.visible = false;
|
|
||||||
|
|
||||||
// Layers (for selective rendering/raycasting)
|
|
||||||
obj.layers.set(1);
|
|
||||||
obj.layers.enable(2);
|
|
||||||
obj.layers.disable(0);
|
|
||||||
|
|
||||||
// Traverse hierarchy
|
|
||||||
obj.traverse((child) => {
|
|
||||||
if (child.isMesh) child.material.color.set(0xff0000);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Matrix updates
|
|
||||||
obj.matrixAutoUpdate = true; // Default: auto-update matrices
|
|
||||||
obj.updateMatrix(); // Manual matrix update
|
|
||||||
obj.updateMatrixWorld(true); // Update world matrix recursively
|
|
||||||
```
|
|
||||||
|
|
||||||
### Group
|
|
||||||
|
|
||||||
Empty container for organizing objects.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const group = new THREE.Group();
|
|
||||||
group.add(mesh1);
|
|
||||||
group.add(mesh2);
|
|
||||||
scene.add(group);
|
|
||||||
|
|
||||||
// Transform entire group
|
|
||||||
group.position.x = 5;
|
|
||||||
group.rotation.y = Math.PI / 4;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Mesh
|
|
||||||
|
|
||||||
Combines geometry and material.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const mesh = new THREE.Mesh(geometry, material);
|
|
||||||
|
|
||||||
// Multiple materials (one per geometry group)
|
|
||||||
const mesh = new THREE.Mesh(geometry, [material1, material2]);
|
|
||||||
|
|
||||||
// Useful properties
|
|
||||||
mesh.geometry;
|
|
||||||
mesh.material;
|
|
||||||
mesh.castShadow = true;
|
|
||||||
mesh.receiveShadow = true;
|
|
||||||
|
|
||||||
// Frustum culling
|
|
||||||
mesh.frustumCulled = true; // Default: skip if outside camera view
|
|
||||||
|
|
||||||
// Render order
|
|
||||||
mesh.renderOrder = 10; // Higher = rendered later
|
|
||||||
```
|
|
||||||
|
|
||||||
## Coordinate System
|
|
||||||
|
|
||||||
Three.js uses a **right-handed coordinate system**:
|
|
||||||
|
|
||||||
- **+X** points right
|
|
||||||
- **+Y** points up
|
|
||||||
- **+Z** points toward viewer (out of screen)
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Axes helper
|
|
||||||
const axesHelper = new THREE.AxesHelper(5);
|
|
||||||
scene.add(axesHelper); // Red=X, Green=Y, Blue=Z
|
|
||||||
```
|
|
||||||
|
|
||||||
## Math Utilities
|
|
||||||
|
|
||||||
### Vector3
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const v = new THREE.Vector3(x, y, z);
|
|
||||||
v.set(x, y, z);
|
|
||||||
v.copy(otherVector);
|
|
||||||
v.clone();
|
|
||||||
|
|
||||||
// Operations (modify in place)
|
|
||||||
v.add(v2);
|
|
||||||
v.sub(v2);
|
|
||||||
v.multiply(v2);
|
|
||||||
v.multiplyScalar(2);
|
|
||||||
v.divideScalar(2);
|
|
||||||
v.normalize();
|
|
||||||
v.negate();
|
|
||||||
v.clamp(min, max);
|
|
||||||
v.lerp(target, alpha);
|
|
||||||
|
|
||||||
// Calculations (return new value)
|
|
||||||
v.length();
|
|
||||||
v.lengthSq(); // Faster than length()
|
|
||||||
v.distanceTo(v2);
|
|
||||||
v.dot(v2);
|
|
||||||
v.cross(v2); // Modifies v
|
|
||||||
v.angleTo(v2);
|
|
||||||
|
|
||||||
// Transform
|
|
||||||
v.applyMatrix4(matrix);
|
|
||||||
v.applyQuaternion(q);
|
|
||||||
v.project(camera); // World to NDC
|
|
||||||
v.unproject(camera); // NDC to world
|
|
||||||
```
|
|
||||||
|
|
||||||
### Matrix4
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const m = new THREE.Matrix4();
|
|
||||||
m.identity();
|
|
||||||
m.copy(other);
|
|
||||||
m.clone();
|
|
||||||
|
|
||||||
// Build transforms
|
|
||||||
m.makeTranslation(x, y, z);
|
|
||||||
m.makeRotationX(theta);
|
|
||||||
m.makeRotationY(theta);
|
|
||||||
m.makeRotationZ(theta);
|
|
||||||
m.makeRotationFromQuaternion(q);
|
|
||||||
m.makeScale(x, y, z);
|
|
||||||
|
|
||||||
// Compose/decompose
|
|
||||||
m.compose(position, quaternion, scale);
|
|
||||||
m.decompose(position, quaternion, scale);
|
|
||||||
|
|
||||||
// Operations
|
|
||||||
m.multiply(m2); // m = m * m2
|
|
||||||
m.premultiply(m2); // m = m2 * m
|
|
||||||
m.invert();
|
|
||||||
m.transpose();
|
|
||||||
|
|
||||||
// Camera matrices
|
|
||||||
m.makePerspective(left, right, top, bottom, near, far);
|
|
||||||
m.makeOrthographic(left, right, top, bottom, near, far);
|
|
||||||
m.lookAt(eye, target, up);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Quaternion
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const q = new THREE.Quaternion();
|
|
||||||
q.setFromEuler(euler);
|
|
||||||
q.setFromAxisAngle(axis, angle);
|
|
||||||
q.setFromRotationMatrix(matrix);
|
|
||||||
|
|
||||||
q.multiply(q2);
|
|
||||||
q.slerp(target, t); // Spherical interpolation
|
|
||||||
q.normalize();
|
|
||||||
q.invert();
|
|
||||||
```
|
|
||||||
|
|
||||||
### Euler
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const euler = new THREE.Euler(x, y, z, "XYZ"); // Order matters!
|
|
||||||
euler.setFromQuaternion(q);
|
|
||||||
euler.setFromRotationMatrix(m);
|
|
||||||
|
|
||||||
// Rotation orders: 'XYZ', 'YXZ', 'ZXY', 'XZY', 'YZX', 'ZYX'
|
|
||||||
```
|
|
||||||
|
|
||||||
### Color
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const color = new THREE.Color(0xff0000);
|
|
||||||
const color = new THREE.Color("red");
|
|
||||||
const color = new THREE.Color("rgb(255, 0, 0)");
|
|
||||||
const color = new THREE.Color("#ff0000");
|
|
||||||
|
|
||||||
color.setHex(0x00ff00);
|
|
||||||
color.setRGB(r, g, b); // 0-1 range
|
|
||||||
color.setHSL(h, s, l); // 0-1 range
|
|
||||||
|
|
||||||
color.lerp(otherColor, alpha);
|
|
||||||
color.multiply(otherColor);
|
|
||||||
color.multiplyScalar(2);
|
|
||||||
```
|
|
||||||
|
|
||||||
### MathUtils
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
THREE.MathUtils.clamp(value, min, max);
|
|
||||||
THREE.MathUtils.lerp(start, end, alpha);
|
|
||||||
THREE.MathUtils.mapLinear(value, inMin, inMax, outMin, outMax);
|
|
||||||
THREE.MathUtils.degToRad(degrees);
|
|
||||||
THREE.MathUtils.radToDeg(radians);
|
|
||||||
THREE.MathUtils.randFloat(min, max);
|
|
||||||
THREE.MathUtils.randInt(min, max);
|
|
||||||
THREE.MathUtils.smoothstep(x, min, max);
|
|
||||||
THREE.MathUtils.smootherstep(x, min, max);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Common Patterns
|
|
||||||
|
|
||||||
### Proper Cleanup
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function dispose() {
|
|
||||||
// Dispose geometries
|
|
||||||
mesh.geometry.dispose();
|
|
||||||
|
|
||||||
// Dispose materials
|
|
||||||
if (Array.isArray(mesh.material)) {
|
|
||||||
mesh.material.forEach((m) => m.dispose());
|
|
||||||
} else {
|
|
||||||
mesh.material.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dispose textures
|
|
||||||
texture.dispose();
|
|
||||||
|
|
||||||
// Remove from scene
|
|
||||||
scene.remove(mesh);
|
|
||||||
|
|
||||||
// Dispose renderer
|
|
||||||
renderer.dispose();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Clock for Animation
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const clock = new THREE.Clock();
|
|
||||||
|
|
||||||
function animate() {
|
|
||||||
const delta = clock.getDelta(); // Time since last frame (seconds)
|
|
||||||
const elapsed = clock.getElapsedTime(); // Total time (seconds)
|
|
||||||
|
|
||||||
mesh.rotation.y += delta * 0.5; // Consistent speed regardless of framerate
|
|
||||||
|
|
||||||
requestAnimationFrame(animate);
|
|
||||||
renderer.render(scene, camera);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Responsive Canvas
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function onWindowResize() {
|
|
||||||
const width = window.innerWidth;
|
|
||||||
const height = window.innerHeight;
|
|
||||||
|
|
||||||
camera.aspect = width / height;
|
|
||||||
camera.updateProjectionMatrix();
|
|
||||||
|
|
||||||
renderer.setSize(width, height);
|
|
||||||
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
|
|
||||||
}
|
|
||||||
window.addEventListener("resize", onWindowResize);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Loading Manager
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const manager = new THREE.LoadingManager();
|
|
||||||
|
|
||||||
manager.onStart = (url, loaded, total) => console.log("Started loading");
|
|
||||||
manager.onLoad = () => console.log("All loaded");
|
|
||||||
manager.onProgress = (url, loaded, total) => console.log(`${loaded}/${total}`);
|
|
||||||
manager.onError = (url) => console.error(`Error loading ${url}`);
|
|
||||||
|
|
||||||
const textureLoader = new THREE.TextureLoader(manager);
|
|
||||||
const gltfLoader = new GLTFLoader(manager);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Performance Tips
|
|
||||||
|
|
||||||
1. **Limit draw calls**: Merge geometries, use instancing, atlas textures
|
|
||||||
2. **Frustum culling**: Enabled by default, ensure bounding boxes are correct
|
|
||||||
3. **LOD (Level of Detail)**: Use `THREE.LOD` for distance-based mesh switching
|
|
||||||
4. **Object pooling**: Reuse objects instead of creating/destroying
|
|
||||||
5. **Avoid `getWorldPosition` in loops**: Cache results
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Merge static geometries
|
|
||||||
import { mergeGeometries } from "three/examples/jsm/utils/BufferGeometryUtils.js";
|
|
||||||
const merged = mergeGeometries([geo1, geo2, geo3]);
|
|
||||||
|
|
||||||
// LOD
|
|
||||||
const lod = new THREE.LOD();
|
|
||||||
lod.addLevel(highDetailMesh, 0);
|
|
||||||
lod.addLevel(medDetailMesh, 50);
|
|
||||||
lod.addLevel(lowDetailMesh, 100);
|
|
||||||
scene.add(lod);
|
|
||||||
```
|
|
||||||
|
|
||||||
## See Also
|
|
||||||
|
|
||||||
- `threejs-geometry` - Geometry creation and manipulation
|
|
||||||
- `threejs-materials` - Material types and properties
|
|
||||||
- `threejs-lighting` - Light types and shadows
|
|
||||||
@@ -1,548 +0,0 @@
|
|||||||
---
|
|
||||||
name: threejs-geometry
|
|
||||||
description: "Three.js 几何体创建,用于内置几何体、BufferGeometry、自定义顶点、网格构建和 instanced rendering 优化。"
|
|
||||||
---
|
|
||||||
|
|
||||||
# Three.js Geometry
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import * as THREE from "three";
|
|
||||||
|
|
||||||
// Built-in geometry
|
|
||||||
const box = new THREE.BoxGeometry(1, 1, 1);
|
|
||||||
const sphere = new THREE.SphereGeometry(0.5, 32, 32);
|
|
||||||
const plane = new THREE.PlaneGeometry(10, 10);
|
|
||||||
|
|
||||||
// Create mesh
|
|
||||||
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
|
|
||||||
const mesh = new THREE.Mesh(box, material);
|
|
||||||
scene.add(mesh);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Built-in Geometries
|
|
||||||
|
|
||||||
### Basic Shapes
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Box - width, height, depth, widthSegments, heightSegments, depthSegments
|
|
||||||
new THREE.BoxGeometry(1, 1, 1, 1, 1, 1);
|
|
||||||
|
|
||||||
// Sphere - radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength
|
|
||||||
new THREE.SphereGeometry(1, 32, 32);
|
|
||||||
new THREE.SphereGeometry(1, 32, 32, 0, Math.PI * 2, 0, Math.PI); // Full sphere
|
|
||||||
new THREE.SphereGeometry(1, 32, 32, 0, Math.PI); // Hemisphere
|
|
||||||
|
|
||||||
// Plane - width, height, widthSegments, heightSegments
|
|
||||||
new THREE.PlaneGeometry(10, 10, 1, 1);
|
|
||||||
|
|
||||||
// Circle - radius, segments, thetaStart, thetaLength
|
|
||||||
new THREE.CircleGeometry(1, 32);
|
|
||||||
new THREE.CircleGeometry(1, 32, 0, Math.PI); // Semicircle
|
|
||||||
|
|
||||||
// Cylinder - radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded
|
|
||||||
new THREE.CylinderGeometry(1, 1, 2, 32, 1, false);
|
|
||||||
new THREE.CylinderGeometry(0, 1, 2, 32); // Cone
|
|
||||||
new THREE.CylinderGeometry(1, 1, 2, 6); // Hexagonal prism
|
|
||||||
|
|
||||||
// Cone - radius, height, radialSegments, heightSegments, openEnded
|
|
||||||
new THREE.ConeGeometry(1, 2, 32, 1, false);
|
|
||||||
|
|
||||||
// Torus - radius, tube, radialSegments, tubularSegments, arc
|
|
||||||
new THREE.TorusGeometry(1, 0.4, 16, 100);
|
|
||||||
|
|
||||||
// TorusKnot - radius, tube, tubularSegments, radialSegments, p, q
|
|
||||||
new THREE.TorusKnotGeometry(1, 0.4, 100, 16, 2, 3);
|
|
||||||
|
|
||||||
// Ring - innerRadius, outerRadius, thetaSegments, phiSegments
|
|
||||||
new THREE.RingGeometry(0.5, 1, 32, 1);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Advanced Shapes
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Capsule - radius, length, capSegments, radialSegments
|
|
||||||
new THREE.CapsuleGeometry(0.5, 1, 4, 8);
|
|
||||||
|
|
||||||
// Dodecahedron - radius, detail
|
|
||||||
new THREE.DodecahedronGeometry(1, 0);
|
|
||||||
|
|
||||||
// Icosahedron - radius, detail (0 = 20 faces, higher = smoother)
|
|
||||||
new THREE.IcosahedronGeometry(1, 0);
|
|
||||||
|
|
||||||
// Octahedron - radius, detail
|
|
||||||
new THREE.OctahedronGeometry(1, 0);
|
|
||||||
|
|
||||||
// Tetrahedron - radius, detail
|
|
||||||
new THREE.TetrahedronGeometry(1, 0);
|
|
||||||
|
|
||||||
// Polyhedron - vertices, indices, radius, detail
|
|
||||||
const vertices = [1, 1, 1, -1, -1, 1, -1, 1, -1, 1, -1, -1];
|
|
||||||
const indices = [2, 1, 0, 0, 3, 2, 1, 3, 0, 2, 3, 1];
|
|
||||||
new THREE.PolyhedronGeometry(vertices, indices, 1, 0);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Path-Based Shapes
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Lathe - points[], segments, phiStart, phiLength
|
|
||||||
const points = [
|
|
||||||
new THREE.Vector2(0, 0),
|
|
||||||
new THREE.Vector2(0.5, 0),
|
|
||||||
new THREE.Vector2(0.5, 1),
|
|
||||||
new THREE.Vector2(0, 1),
|
|
||||||
];
|
|
||||||
new THREE.LatheGeometry(points, 32);
|
|
||||||
|
|
||||||
// Extrude - shape, options
|
|
||||||
const shape = new THREE.Shape();
|
|
||||||
shape.moveTo(0, 0);
|
|
||||||
shape.lineTo(1, 0);
|
|
||||||
shape.lineTo(1, 1);
|
|
||||||
shape.lineTo(0, 1);
|
|
||||||
shape.lineTo(0, 0);
|
|
||||||
|
|
||||||
const extrudeSettings = {
|
|
||||||
steps: 2,
|
|
||||||
depth: 1,
|
|
||||||
bevelEnabled: true,
|
|
||||||
bevelThickness: 0.1,
|
|
||||||
bevelSize: 0.1,
|
|
||||||
bevelSegments: 3,
|
|
||||||
};
|
|
||||||
new THREE.ExtrudeGeometry(shape, extrudeSettings);
|
|
||||||
|
|
||||||
// Tube - path, tubularSegments, radius, radialSegments, closed
|
|
||||||
const curve = new THREE.CatmullRomCurve3([
|
|
||||||
new THREE.Vector3(-1, 0, 0),
|
|
||||||
new THREE.Vector3(0, 1, 0),
|
|
||||||
new THREE.Vector3(1, 0, 0),
|
|
||||||
]);
|
|
||||||
new THREE.TubeGeometry(curve, 64, 0.2, 8, false);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Text Geometry
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { FontLoader } from "three/examples/jsm/loaders/FontLoader.js";
|
|
||||||
import { TextGeometry } from "three/examples/jsm/geometries/TextGeometry.js";
|
|
||||||
|
|
||||||
const loader = new FontLoader();
|
|
||||||
loader.load("fonts/helvetiker_regular.typeface.json", (font) => {
|
|
||||||
const geometry = new TextGeometry("Hello", {
|
|
||||||
font: font,
|
|
||||||
size: 1,
|
|
||||||
depth: 0.2, // Was 'height' in older versions
|
|
||||||
curveSegments: 12,
|
|
||||||
bevelEnabled: true,
|
|
||||||
bevelThickness: 0.03,
|
|
||||||
bevelSize: 0.02,
|
|
||||||
bevelSegments: 5,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Center text
|
|
||||||
geometry.computeBoundingBox();
|
|
||||||
geometry.center();
|
|
||||||
|
|
||||||
const mesh = new THREE.Mesh(geometry, material);
|
|
||||||
scene.add(mesh);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## BufferGeometry
|
|
||||||
|
|
||||||
The base class for all geometries. Stores data as typed arrays for GPU efficiency.
|
|
||||||
|
|
||||||
### Custom BufferGeometry
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const geometry = new THREE.BufferGeometry();
|
|
||||||
|
|
||||||
// Vertices (3 floats per vertex: x, y, z)
|
|
||||||
const vertices = new Float32Array([
|
|
||||||
-1,
|
|
||||||
-1,
|
|
||||||
0, // vertex 0
|
|
||||||
1,
|
|
||||||
-1,
|
|
||||||
0, // vertex 1
|
|
||||||
1,
|
|
||||||
1,
|
|
||||||
0, // vertex 2
|
|
||||||
-1,
|
|
||||||
1,
|
|
||||||
0, // vertex 3
|
|
||||||
]);
|
|
||||||
geometry.setAttribute("position", new THREE.BufferAttribute(vertices, 3));
|
|
||||||
|
|
||||||
// Indices (for indexed geometry - reuse vertices)
|
|
||||||
const indices = new Uint16Array([
|
|
||||||
0,
|
|
||||||
1,
|
|
||||||
2, // triangle 1
|
|
||||||
0,
|
|
||||||
2,
|
|
||||||
3, // triangle 2
|
|
||||||
]);
|
|
||||||
geometry.setIndex(new THREE.BufferAttribute(indices, 1));
|
|
||||||
|
|
||||||
// Normals (required for lighting)
|
|
||||||
const normals = new Float32Array([0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1]);
|
|
||||||
geometry.setAttribute("normal", new THREE.BufferAttribute(normals, 3));
|
|
||||||
|
|
||||||
// UVs (for texturing)
|
|
||||||
const uvs = new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]);
|
|
||||||
geometry.setAttribute("uv", new THREE.BufferAttribute(uvs, 2));
|
|
||||||
|
|
||||||
// Colors (per-vertex colors)
|
|
||||||
const colors = new Float32Array([
|
|
||||||
1,
|
|
||||||
0,
|
|
||||||
0, // red
|
|
||||||
0,
|
|
||||||
1,
|
|
||||||
0, // green
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
1, // blue
|
|
||||||
1,
|
|
||||||
1,
|
|
||||||
0, // yellow
|
|
||||||
]);
|
|
||||||
geometry.setAttribute("color", new THREE.BufferAttribute(colors, 3));
|
|
||||||
// Use with: material.vertexColors = true
|
|
||||||
```
|
|
||||||
|
|
||||||
### BufferAttribute Types
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Common attribute types
|
|
||||||
new THREE.BufferAttribute(array, itemSize);
|
|
||||||
|
|
||||||
// Typed array options
|
|
||||||
new Float32Array(count * itemSize); // Positions, normals, UVs
|
|
||||||
new Uint16Array(count); // Indices (up to 65535 vertices)
|
|
||||||
new Uint32Array(count); // Indices (larger meshes)
|
|
||||||
new Uint8Array(count * itemSize); // Colors (0-255 range)
|
|
||||||
|
|
||||||
// Item sizes
|
|
||||||
// Position: 3 (x, y, z)
|
|
||||||
// Normal: 3 (x, y, z)
|
|
||||||
// UV: 2 (u, v)
|
|
||||||
// Color: 3 (r, g, b) or 4 (r, g, b, a)
|
|
||||||
// Index: 1
|
|
||||||
```
|
|
||||||
|
|
||||||
### Modifying BufferGeometry
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const positions = geometry.attributes.position;
|
|
||||||
|
|
||||||
// Modify vertex
|
|
||||||
positions.setXYZ(index, x, y, z);
|
|
||||||
|
|
||||||
// Access vertex
|
|
||||||
const x = positions.getX(index);
|
|
||||||
const y = positions.getY(index);
|
|
||||||
const z = positions.getZ(index);
|
|
||||||
|
|
||||||
// Flag for GPU update
|
|
||||||
positions.needsUpdate = true;
|
|
||||||
|
|
||||||
// Recompute normals after position changes
|
|
||||||
geometry.computeVertexNormals();
|
|
||||||
|
|
||||||
// Recompute bounding box/sphere after changes
|
|
||||||
geometry.computeBoundingBox();
|
|
||||||
geometry.computeBoundingSphere();
|
|
||||||
```
|
|
||||||
|
|
||||||
### Interleaved Buffers (Advanced)
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// More efficient memory layout for large meshes
|
|
||||||
const interleavedBuffer = new THREE.InterleavedBuffer(
|
|
||||||
new Float32Array([
|
|
||||||
// pos.x, pos.y, pos.z, uv.u, uv.v (repeated per vertex)
|
|
||||||
-1, -1, 0, 0, 0, 1, -1, 0, 1, 0, 1, 1, 0, 1, 1, -1, 1, 0, 0, 1,
|
|
||||||
]),
|
|
||||||
5, // stride (floats per vertex)
|
|
||||||
);
|
|
||||||
|
|
||||||
geometry.setAttribute(
|
|
||||||
"position",
|
|
||||||
new THREE.InterleavedBufferAttribute(interleavedBuffer, 3, 0),
|
|
||||||
); // size 3, offset 0
|
|
||||||
geometry.setAttribute(
|
|
||||||
"uv",
|
|
||||||
new THREE.InterleavedBufferAttribute(interleavedBuffer, 2, 3),
|
|
||||||
); // size 2, offset 3
|
|
||||||
```
|
|
||||||
|
|
||||||
## EdgesGeometry & WireframeGeometry
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Edge lines (only hard edges)
|
|
||||||
const edges = new THREE.EdgesGeometry(boxGeometry, 15); // 15 = threshold angle
|
|
||||||
const edgeMesh = new THREE.LineSegments(
|
|
||||||
edges,
|
|
||||||
new THREE.LineBasicMaterial({ color: 0xffffff }),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Wireframe (all triangles)
|
|
||||||
const wireframe = new THREE.WireframeGeometry(boxGeometry);
|
|
||||||
const wireMesh = new THREE.LineSegments(
|
|
||||||
wireframe,
|
|
||||||
new THREE.LineBasicMaterial({ color: 0xffffff }),
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Points
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Create point cloud
|
|
||||||
const geometry = new THREE.BufferGeometry();
|
|
||||||
const positions = new Float32Array(1000 * 3);
|
|
||||||
|
|
||||||
for (let i = 0; i < 1000; i++) {
|
|
||||||
positions[i * 3] = (Math.random() - 0.5) * 10;
|
|
||||||
positions[i * 3 + 1] = (Math.random() - 0.5) * 10;
|
|
||||||
positions[i * 3 + 2] = (Math.random() - 0.5) * 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
geometry.setAttribute("position", new THREE.BufferAttribute(positions, 3));
|
|
||||||
|
|
||||||
const material = new THREE.PointsMaterial({
|
|
||||||
size: 0.1,
|
|
||||||
sizeAttenuation: true, // Size decreases with distance
|
|
||||||
color: 0xffffff,
|
|
||||||
});
|
|
||||||
|
|
||||||
const points = new THREE.Points(geometry, material);
|
|
||||||
scene.add(points);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Lines
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Line (connected points)
|
|
||||||
const points = [
|
|
||||||
new THREE.Vector3(-1, 0, 0),
|
|
||||||
new THREE.Vector3(0, 1, 0),
|
|
||||||
new THREE.Vector3(1, 0, 0),
|
|
||||||
];
|
|
||||||
const geometry = new THREE.BufferGeometry().setFromPoints(points);
|
|
||||||
const line = new THREE.Line(
|
|
||||||
geometry,
|
|
||||||
new THREE.LineBasicMaterial({ color: 0xff0000 }),
|
|
||||||
);
|
|
||||||
|
|
||||||
// LineLoop (closed loop)
|
|
||||||
const loop = new THREE.LineLoop(geometry, material);
|
|
||||||
|
|
||||||
// LineSegments (pairs of points)
|
|
||||||
const segmentsGeometry = new THREE.BufferGeometry();
|
|
||||||
segmentsGeometry.setAttribute(
|
|
||||||
"position",
|
|
||||||
new THREE.BufferAttribute(
|
|
||||||
new Float32Array([
|
|
||||||
-1,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
1,
|
|
||||||
0, // segment 1
|
|
||||||
0,
|
|
||||||
1,
|
|
||||||
0,
|
|
||||||
1,
|
|
||||||
0,
|
|
||||||
0, // segment 2
|
|
||||||
]),
|
|
||||||
3,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
const segments = new THREE.LineSegments(segmentsGeometry, material);
|
|
||||||
```
|
|
||||||
|
|
||||||
## InstancedMesh
|
|
||||||
|
|
||||||
Efficiently render many copies of the same geometry.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const geometry = new THREE.BoxGeometry(1, 1, 1);
|
|
||||||
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
|
|
||||||
const count = 1000;
|
|
||||||
|
|
||||||
const instancedMesh = new THREE.InstancedMesh(geometry, material, count);
|
|
||||||
|
|
||||||
// Set transforms for each instance
|
|
||||||
const dummy = new THREE.Object3D();
|
|
||||||
const matrix = new THREE.Matrix4();
|
|
||||||
|
|
||||||
for (let i = 0; i < count; i++) {
|
|
||||||
dummy.position.set(
|
|
||||||
(Math.random() - 0.5) * 20,
|
|
||||||
(Math.random() - 0.5) * 20,
|
|
||||||
(Math.random() - 0.5) * 20,
|
|
||||||
);
|
|
||||||
dummy.rotation.set(Math.random() * Math.PI, Math.random() * Math.PI, 0);
|
|
||||||
dummy.scale.setScalar(0.5 + Math.random());
|
|
||||||
dummy.updateMatrix();
|
|
||||||
|
|
||||||
instancedMesh.setMatrixAt(i, dummy.matrix);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flag for GPU update
|
|
||||||
instancedMesh.instanceMatrix.needsUpdate = true;
|
|
||||||
|
|
||||||
// Optional: per-instance colors
|
|
||||||
instancedMesh.instanceColor = new THREE.InstancedBufferAttribute(
|
|
||||||
new Float32Array(count * 3),
|
|
||||||
3,
|
|
||||||
);
|
|
||||||
for (let i = 0; i < count; i++) {
|
|
||||||
instancedMesh.setColorAt(
|
|
||||||
i,
|
|
||||||
new THREE.Color(Math.random(), Math.random(), Math.random()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
instancedMesh.instanceColor.needsUpdate = true;
|
|
||||||
|
|
||||||
scene.add(instancedMesh);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Update Instance at Runtime
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Update single instance
|
|
||||||
const matrix = new THREE.Matrix4();
|
|
||||||
instancedMesh.getMatrixAt(index, matrix);
|
|
||||||
// Modify matrix...
|
|
||||||
instancedMesh.setMatrixAt(index, matrix);
|
|
||||||
instancedMesh.instanceMatrix.needsUpdate = true;
|
|
||||||
|
|
||||||
// Raycasting with instanced mesh
|
|
||||||
const intersects = raycaster.intersectObject(instancedMesh);
|
|
||||||
if (intersects.length > 0) {
|
|
||||||
const instanceId = intersects[0].instanceId;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## InstancedBufferGeometry (Advanced)
|
|
||||||
|
|
||||||
For custom per-instance attributes beyond transform/color.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const geometry = new THREE.InstancedBufferGeometry();
|
|
||||||
geometry.copy(new THREE.BoxGeometry(1, 1, 1));
|
|
||||||
|
|
||||||
// Add per-instance attribute
|
|
||||||
const offsets = new Float32Array(count * 3);
|
|
||||||
for (let i = 0; i < count; i++) {
|
|
||||||
offsets[i * 3] = Math.random() * 10;
|
|
||||||
offsets[i * 3 + 1] = Math.random() * 10;
|
|
||||||
offsets[i * 3 + 2] = Math.random() * 10;
|
|
||||||
}
|
|
||||||
geometry.setAttribute("offset", new THREE.InstancedBufferAttribute(offsets, 3));
|
|
||||||
|
|
||||||
// Use in shader
|
|
||||||
// attribute vec3 offset;
|
|
||||||
// vec3 transformed = position + offset;
|
|
||||||
```
|
|
||||||
|
|
||||||
## Geometry Utilities
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import * as BufferGeometryUtils from "three/examples/jsm/utils/BufferGeometryUtils.js";
|
|
||||||
|
|
||||||
// Merge geometries (must have same attributes)
|
|
||||||
const merged = BufferGeometryUtils.mergeGeometries([geo1, geo2, geo3]);
|
|
||||||
|
|
||||||
// Merge with groups (for multi-material)
|
|
||||||
const merged = BufferGeometryUtils.mergeGeometries([geo1, geo2], true);
|
|
||||||
|
|
||||||
// Compute tangents (required for normal maps)
|
|
||||||
BufferGeometryUtils.computeTangents(geometry);
|
|
||||||
|
|
||||||
// Interleave attributes for better performance
|
|
||||||
const interleaved = BufferGeometryUtils.interleaveAttributes([
|
|
||||||
geometry.attributes.position,
|
|
||||||
geometry.attributes.normal,
|
|
||||||
geometry.attributes.uv,
|
|
||||||
]);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Common Patterns
|
|
||||||
|
|
||||||
### Center Geometry
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
geometry.computeBoundingBox();
|
|
||||||
geometry.center(); // Move vertices so center is at origin
|
|
||||||
```
|
|
||||||
|
|
||||||
### Scale to Fit
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
geometry.computeBoundingBox();
|
|
||||||
const size = new THREE.Vector3();
|
|
||||||
geometry.boundingBox.getSize(size);
|
|
||||||
const maxDim = Math.max(size.x, size.y, size.z);
|
|
||||||
geometry.scale(1 / maxDim, 1 / maxDim, 1 / maxDim);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Clone and Transform
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const clone = geometry.clone();
|
|
||||||
clone.rotateX(Math.PI / 2);
|
|
||||||
clone.translate(0, 1, 0);
|
|
||||||
clone.scale(2, 2, 2);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Morph Targets
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Base geometry
|
|
||||||
const geometry = new THREE.BoxGeometry(1, 1, 1, 4, 4, 4);
|
|
||||||
|
|
||||||
// Create morph target
|
|
||||||
const morphPositions = geometry.attributes.position.array.slice();
|
|
||||||
for (let i = 0; i < morphPositions.length; i += 3) {
|
|
||||||
morphPositions[i] *= 2; // Scale X
|
|
||||||
morphPositions[i + 1] *= 0.5; // Squash Y
|
|
||||||
}
|
|
||||||
|
|
||||||
geometry.morphAttributes.position = [
|
|
||||||
new THREE.BufferAttribute(new Float32Array(morphPositions), 3),
|
|
||||||
];
|
|
||||||
|
|
||||||
const mesh = new THREE.Mesh(geometry, material);
|
|
||||||
mesh.morphTargetInfluences[0] = 0.5; // 50% blend
|
|
||||||
```
|
|
||||||
|
|
||||||
## Performance Tips
|
|
||||||
|
|
||||||
1. **Use indexed geometry**: Reuse vertices with indices
|
|
||||||
2. **Merge static meshes**: Reduce draw calls with `mergeGeometries`
|
|
||||||
3. **Use InstancedMesh**: For many identical objects
|
|
||||||
4. **Choose appropriate segment counts**: More segments = smoother but slower
|
|
||||||
5. **Dispose unused geometry**: `geometry.dispose()`
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Good segment counts for common uses
|
|
||||||
new THREE.SphereGeometry(1, 32, 32); // Good quality
|
|
||||||
new THREE.SphereGeometry(1, 64, 64); // High quality
|
|
||||||
new THREE.SphereGeometry(1, 16, 16); // Performance mode
|
|
||||||
|
|
||||||
// Dispose when done
|
|
||||||
geometry.dispose();
|
|
||||||
```
|
|
||||||
|
|
||||||
## See Also
|
|
||||||
|
|
||||||
- `threejs-fundamentals` - Scene setup and Object3D
|
|
||||||
- `threejs-materials` - Material types for meshes
|
|
||||||
- `threejs-shaders` - Custom vertex manipulation
|
|
||||||
@@ -1,660 +0,0 @@
|
|||||||
---
|
|
||||||
name: threejs-interaction
|
|
||||||
description: "Three.js 交互处理,用于 raycasting、鼠标/触摸输入、对象选择、相机控制和交互式 3D 体验。"
|
|
||||||
---
|
|
||||||
|
|
||||||
# Three.js Interaction
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import * as THREE from "three";
|
|
||||||
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
|
|
||||||
|
|
||||||
// Camera controls
|
|
||||||
const controls = new OrbitControls(camera, renderer.domElement);
|
|
||||||
controls.enableDamping = true;
|
|
||||||
|
|
||||||
// Raycasting for click detection
|
|
||||||
const raycaster = new THREE.Raycaster();
|
|
||||||
const mouse = new THREE.Vector2();
|
|
||||||
|
|
||||||
function onClick(event) {
|
|
||||||
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
|
|
||||||
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
|
|
||||||
|
|
||||||
raycaster.setFromCamera(mouse, camera);
|
|
||||||
const intersects = raycaster.intersectObjects(scene.children);
|
|
||||||
|
|
||||||
if (intersects.length > 0) {
|
|
||||||
console.log("Clicked:", intersects[0].object);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener("click", onClick);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Raycaster
|
|
||||||
|
|
||||||
### Basic Raycasting
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const raycaster = new THREE.Raycaster();
|
|
||||||
|
|
||||||
// From camera (mouse picking)
|
|
||||||
raycaster.setFromCamera(mousePosition, camera);
|
|
||||||
|
|
||||||
// From any origin and direction
|
|
||||||
raycaster.set(origin, direction); // origin: Vector3, direction: normalized Vector3
|
|
||||||
|
|
||||||
// Get intersections
|
|
||||||
const intersects = raycaster.intersectObjects(objects, recursive);
|
|
||||||
|
|
||||||
// intersects array contains:
|
|
||||||
// {
|
|
||||||
// distance: number, // Distance from ray origin
|
|
||||||
// point: Vector3, // Intersection point in world coords
|
|
||||||
// face: Face3, // Intersected face
|
|
||||||
// faceIndex: number, // Face index
|
|
||||||
// object: Object3D, // Intersected object
|
|
||||||
// uv: Vector2, // UV coordinates at intersection
|
|
||||||
// uv1: Vector2, // Second UV channel
|
|
||||||
// normal: Vector3, // Interpolated face normal
|
|
||||||
// instanceId: number // For InstancedMesh
|
|
||||||
// }
|
|
||||||
```
|
|
||||||
|
|
||||||
### Mouse Position Conversion
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const mouse = new THREE.Vector2();
|
|
||||||
|
|
||||||
function updateMouse(event) {
|
|
||||||
// For full window
|
|
||||||
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
|
|
||||||
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// For specific canvas element
|
|
||||||
function updateMouseCanvas(event, canvas) {
|
|
||||||
const rect = canvas.getBoundingClientRect();
|
|
||||||
mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
|
|
||||||
mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Touch Support
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function onTouchStart(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
if (event.touches.length === 1) {
|
|
||||||
const touch = event.touches[0];
|
|
||||||
mouse.x = (touch.clientX / window.innerWidth) * 2 - 1;
|
|
||||||
mouse.y = -(touch.clientY / window.innerHeight) * 2 + 1;
|
|
||||||
|
|
||||||
raycaster.setFromCamera(mouse, camera);
|
|
||||||
const intersects = raycaster.intersectObjects(clickableObjects);
|
|
||||||
|
|
||||||
if (intersects.length > 0) {
|
|
||||||
handleSelection(intersects[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
renderer.domElement.addEventListener("touchstart", onTouchStart);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Raycaster Options
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const raycaster = new THREE.Raycaster();
|
|
||||||
|
|
||||||
// Near/far clipping (default: 0, Infinity)
|
|
||||||
raycaster.near = 0;
|
|
||||||
raycaster.far = 100;
|
|
||||||
|
|
||||||
// Line/Points precision
|
|
||||||
raycaster.params.Line.threshold = 0.1;
|
|
||||||
raycaster.params.Points.threshold = 0.1;
|
|
||||||
|
|
||||||
// Layers (only intersect objects on specific layers)
|
|
||||||
raycaster.layers.set(1);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Efficient Raycasting
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Only check specific objects
|
|
||||||
const clickables = [mesh1, mesh2, mesh3];
|
|
||||||
const intersects = raycaster.intersectObjects(clickables, false);
|
|
||||||
|
|
||||||
// Use layers for filtering
|
|
||||||
mesh1.layers.set(1); // Clickable layer
|
|
||||||
raycaster.layers.set(1);
|
|
||||||
|
|
||||||
// Throttle raycast for hover effects
|
|
||||||
let lastRaycast = 0;
|
|
||||||
function onMouseMove(event) {
|
|
||||||
const now = Date.now();
|
|
||||||
if (now - lastRaycast < 50) return; // 20fps max
|
|
||||||
lastRaycast = now;
|
|
||||||
|
|
||||||
// Raycast here
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Camera Controls
|
|
||||||
|
|
||||||
### OrbitControls
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
|
|
||||||
|
|
||||||
const controls = new OrbitControls(camera, renderer.domElement);
|
|
||||||
|
|
||||||
// Damping (smooth movement)
|
|
||||||
controls.enableDamping = true;
|
|
||||||
controls.dampingFactor = 0.05;
|
|
||||||
|
|
||||||
// Rotation limits
|
|
||||||
controls.minPolarAngle = 0; // Top
|
|
||||||
controls.maxPolarAngle = Math.PI / 2; // Horizon
|
|
||||||
controls.minAzimuthAngle = -Math.PI / 4; // Left
|
|
||||||
controls.maxAzimuthAngle = Math.PI / 4; // Right
|
|
||||||
|
|
||||||
// Zoom limits
|
|
||||||
controls.minDistance = 2;
|
|
||||||
controls.maxDistance = 50;
|
|
||||||
|
|
||||||
// Enable/disable features
|
|
||||||
controls.enableRotate = true;
|
|
||||||
controls.enableZoom = true;
|
|
||||||
controls.enablePan = true;
|
|
||||||
|
|
||||||
// Auto-rotate
|
|
||||||
controls.autoRotate = true;
|
|
||||||
controls.autoRotateSpeed = 2.0;
|
|
||||||
|
|
||||||
// Target (orbit point)
|
|
||||||
controls.target.set(0, 1, 0);
|
|
||||||
|
|
||||||
// Update in animation loop
|
|
||||||
function animate() {
|
|
||||||
controls.update(); // Required for damping and auto-rotate
|
|
||||||
renderer.render(scene, camera);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### FlyControls
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { FlyControls } from "three/addons/controls/FlyControls.js";
|
|
||||||
|
|
||||||
const controls = new FlyControls(camera, renderer.domElement);
|
|
||||||
controls.movementSpeed = 10;
|
|
||||||
controls.rollSpeed = Math.PI / 24;
|
|
||||||
controls.dragToLook = true;
|
|
||||||
|
|
||||||
// Update with delta
|
|
||||||
function animate() {
|
|
||||||
controls.update(clock.getDelta());
|
|
||||||
renderer.render(scene, camera);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### FirstPersonControls
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { FirstPersonControls } from "three/addons/controls/FirstPersonControls.js";
|
|
||||||
|
|
||||||
const controls = new FirstPersonControls(camera, renderer.domElement);
|
|
||||||
controls.movementSpeed = 10;
|
|
||||||
controls.lookSpeed = 0.1;
|
|
||||||
controls.lookVertical = true;
|
|
||||||
controls.constrainVertical = true;
|
|
||||||
controls.verticalMin = Math.PI / 4;
|
|
||||||
controls.verticalMax = (Math.PI * 3) / 4;
|
|
||||||
|
|
||||||
function animate() {
|
|
||||||
controls.update(clock.getDelta());
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### PointerLockControls
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { PointerLockControls } from "three/addons/controls/PointerLockControls.js";
|
|
||||||
|
|
||||||
const controls = new PointerLockControls(camera, document.body);
|
|
||||||
|
|
||||||
// Lock pointer on click
|
|
||||||
document.addEventListener("click", () => {
|
|
||||||
controls.lock();
|
|
||||||
});
|
|
||||||
|
|
||||||
controls.addEventListener("lock", () => {
|
|
||||||
console.log("Pointer locked");
|
|
||||||
});
|
|
||||||
|
|
||||||
controls.addEventListener("unlock", () => {
|
|
||||||
console.log("Pointer unlocked");
|
|
||||||
});
|
|
||||||
|
|
||||||
// Movement
|
|
||||||
const velocity = new THREE.Vector3();
|
|
||||||
const direction = new THREE.Vector3();
|
|
||||||
const moveForward = false;
|
|
||||||
const moveBackward = false;
|
|
||||||
|
|
||||||
document.addEventListener("keydown", (event) => {
|
|
||||||
switch (event.code) {
|
|
||||||
case "KeyW":
|
|
||||||
moveForward = true;
|
|
||||||
break;
|
|
||||||
case "KeyS":
|
|
||||||
moveBackward = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function animate() {
|
|
||||||
if (controls.isLocked) {
|
|
||||||
direction.z = Number(moveForward) - Number(moveBackward);
|
|
||||||
direction.normalize();
|
|
||||||
|
|
||||||
velocity.z -= direction.z * 0.1;
|
|
||||||
velocity.z *= 0.9; // Friction
|
|
||||||
|
|
||||||
controls.moveForward(-velocity.z);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### TrackballControls
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { TrackballControls } from "three/addons/controls/TrackballControls.js";
|
|
||||||
|
|
||||||
const controls = new TrackballControls(camera, renderer.domElement);
|
|
||||||
controls.rotateSpeed = 2.0;
|
|
||||||
controls.zoomSpeed = 1.2;
|
|
||||||
controls.panSpeed = 0.8;
|
|
||||||
controls.staticMoving = true;
|
|
||||||
|
|
||||||
function animate() {
|
|
||||||
controls.update();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### MapControls
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { MapControls } from "three/addons/controls/MapControls.js";
|
|
||||||
|
|
||||||
const controls = new MapControls(camera, renderer.domElement);
|
|
||||||
controls.enableDamping = true;
|
|
||||||
controls.dampingFactor = 0.05;
|
|
||||||
controls.screenSpacePanning = false;
|
|
||||||
controls.maxPolarAngle = Math.PI / 2;
|
|
||||||
```
|
|
||||||
|
|
||||||
## TransformControls
|
|
||||||
|
|
||||||
Gizmo for moving/rotating/scaling objects.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { TransformControls } from "three/addons/controls/TransformControls.js";
|
|
||||||
|
|
||||||
const transformControls = new TransformControls(camera, renderer.domElement);
|
|
||||||
scene.add(transformControls);
|
|
||||||
|
|
||||||
// Attach to object
|
|
||||||
transformControls.attach(selectedMesh);
|
|
||||||
|
|
||||||
// Switch modes
|
|
||||||
transformControls.setMode("translate"); // 'translate', 'rotate', 'scale'
|
|
||||||
|
|
||||||
// Change space
|
|
||||||
transformControls.setSpace("local"); // 'local', 'world'
|
|
||||||
|
|
||||||
// Size
|
|
||||||
transformControls.setSize(1);
|
|
||||||
|
|
||||||
// Events
|
|
||||||
transformControls.addEventListener("dragging-changed", (event) => {
|
|
||||||
// Disable orbit controls while dragging
|
|
||||||
orbitControls.enabled = !event.value;
|
|
||||||
});
|
|
||||||
|
|
||||||
transformControls.addEventListener("change", () => {
|
|
||||||
renderer.render(scene, camera);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Keyboard shortcuts
|
|
||||||
window.addEventListener("keydown", (event) => {
|
|
||||||
switch (event.key) {
|
|
||||||
case "g":
|
|
||||||
transformControls.setMode("translate");
|
|
||||||
break;
|
|
||||||
case "r":
|
|
||||||
transformControls.setMode("rotate");
|
|
||||||
break;
|
|
||||||
case "s":
|
|
||||||
transformControls.setMode("scale");
|
|
||||||
break;
|
|
||||||
case "Escape":
|
|
||||||
transformControls.detach();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## DragControls
|
|
||||||
|
|
||||||
Drag objects directly.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { DragControls } from "three/addons/controls/DragControls.js";
|
|
||||||
|
|
||||||
const draggableObjects = [mesh1, mesh2, mesh3];
|
|
||||||
const dragControls = new DragControls(
|
|
||||||
draggableObjects,
|
|
||||||
camera,
|
|
||||||
renderer.domElement,
|
|
||||||
);
|
|
||||||
|
|
||||||
dragControls.addEventListener("dragstart", (event) => {
|
|
||||||
orbitControls.enabled = false;
|
|
||||||
event.object.material.emissive.set(0xaaaaaa);
|
|
||||||
});
|
|
||||||
|
|
||||||
dragControls.addEventListener("drag", (event) => {
|
|
||||||
// Constrain to ground plane
|
|
||||||
event.object.position.y = 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
dragControls.addEventListener("dragend", (event) => {
|
|
||||||
orbitControls.enabled = true;
|
|
||||||
event.object.material.emissive.set(0x000000);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Selection System
|
|
||||||
|
|
||||||
### Click to Select
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const raycaster = new THREE.Raycaster();
|
|
||||||
const mouse = new THREE.Vector2();
|
|
||||||
let selectedObject = null;
|
|
||||||
|
|
||||||
function onMouseDown(event) {
|
|
||||||
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
|
|
||||||
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
|
|
||||||
|
|
||||||
raycaster.setFromCamera(mouse, camera);
|
|
||||||
const intersects = raycaster.intersectObjects(selectableObjects);
|
|
||||||
|
|
||||||
// Deselect previous
|
|
||||||
if (selectedObject) {
|
|
||||||
selectedObject.material.emissive.set(0x000000);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Select new
|
|
||||||
if (intersects.length > 0) {
|
|
||||||
selectedObject = intersects[0].object;
|
|
||||||
selectedObject.material.emissive.set(0x444444);
|
|
||||||
} else {
|
|
||||||
selectedObject = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Box Selection
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { SelectionBox } from "three/addons/interactive/SelectionBox.js";
|
|
||||||
import { SelectionHelper } from "three/addons/interactive/SelectionHelper.js";
|
|
||||||
|
|
||||||
const selectionBox = new SelectionBox(camera, scene);
|
|
||||||
const selectionHelper = new SelectionHelper(renderer, "selectBox"); // CSS class
|
|
||||||
|
|
||||||
document.addEventListener("pointerdown", (event) => {
|
|
||||||
selectionBox.startPoint.set(
|
|
||||||
(event.clientX / window.innerWidth) * 2 - 1,
|
|
||||||
-(event.clientY / window.innerHeight) * 2 + 1,
|
|
||||||
0.5,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
document.addEventListener("pointermove", (event) => {
|
|
||||||
if (selectionHelper.isDown) {
|
|
||||||
selectionBox.endPoint.set(
|
|
||||||
(event.clientX / window.innerWidth) * 2 - 1,
|
|
||||||
-(event.clientY / window.innerHeight) * 2 + 1,
|
|
||||||
0.5,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
document.addEventListener("pointerup", (event) => {
|
|
||||||
selectionBox.endPoint.set(
|
|
||||||
(event.clientX / window.innerWidth) * 2 - 1,
|
|
||||||
-(event.clientY / window.innerHeight) * 2 + 1,
|
|
||||||
0.5,
|
|
||||||
);
|
|
||||||
|
|
||||||
const selected = selectionBox.select();
|
|
||||||
console.log("Selected objects:", selected);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Hover Effects
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const raycaster = new THREE.Raycaster();
|
|
||||||
const mouse = new THREE.Vector2();
|
|
||||||
let hoveredObject = null;
|
|
||||||
|
|
||||||
function onMouseMove(event) {
|
|
||||||
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
|
|
||||||
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
|
|
||||||
|
|
||||||
raycaster.setFromCamera(mouse, camera);
|
|
||||||
const intersects = raycaster.intersectObjects(hoverableObjects);
|
|
||||||
|
|
||||||
// Reset previous hover
|
|
||||||
if (hoveredObject) {
|
|
||||||
hoveredObject.material.color.set(hoveredObject.userData.originalColor);
|
|
||||||
document.body.style.cursor = "default";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply new hover
|
|
||||||
if (intersects.length > 0) {
|
|
||||||
hoveredObject = intersects[0].object;
|
|
||||||
if (!hoveredObject.userData.originalColor) {
|
|
||||||
hoveredObject.userData.originalColor =
|
|
||||||
hoveredObject.material.color.getHex();
|
|
||||||
}
|
|
||||||
hoveredObject.material.color.set(0xff6600);
|
|
||||||
document.body.style.cursor = "pointer";
|
|
||||||
} else {
|
|
||||||
hoveredObject = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener("mousemove", onMouseMove);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Keyboard Input
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const keys = {};
|
|
||||||
|
|
||||||
document.addEventListener("keydown", (event) => {
|
|
||||||
keys[event.code] = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
document.addEventListener("keyup", (event) => {
|
|
||||||
keys[event.code] = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
function update() {
|
|
||||||
const speed = 0.1;
|
|
||||||
|
|
||||||
if (keys["KeyW"]) player.position.z -= speed;
|
|
||||||
if (keys["KeyS"]) player.position.z += speed;
|
|
||||||
if (keys["KeyA"]) player.position.x -= speed;
|
|
||||||
if (keys["KeyD"]) player.position.x += speed;
|
|
||||||
if (keys["Space"]) player.position.y += speed;
|
|
||||||
if (keys["ShiftLeft"]) player.position.y -= speed;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## World-Screen Coordinate Conversion
|
|
||||||
|
|
||||||
### World to Screen
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function worldToScreen(position, camera) {
|
|
||||||
const vector = position.clone();
|
|
||||||
vector.project(camera);
|
|
||||||
|
|
||||||
return {
|
|
||||||
x: ((vector.x + 1) / 2) * window.innerWidth,
|
|
||||||
y: (-(vector.y - 1) / 2) * window.innerHeight,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Position HTML element over 3D object
|
|
||||||
const screenPos = worldToScreen(mesh.position, camera);
|
|
||||||
element.style.left = screenPos.x + "px";
|
|
||||||
element.style.top = screenPos.y + "px";
|
|
||||||
```
|
|
||||||
|
|
||||||
### Screen to World
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function screenToWorld(screenX, screenY, camera, targetZ = 0) {
|
|
||||||
const vector = new THREE.Vector3(
|
|
||||||
(screenX / window.innerWidth) * 2 - 1,
|
|
||||||
-(screenY / window.innerHeight) * 2 + 1,
|
|
||||||
0.5,
|
|
||||||
);
|
|
||||||
|
|
||||||
vector.unproject(camera);
|
|
||||||
|
|
||||||
const dir = vector.sub(camera.position).normalize();
|
|
||||||
const distance = (targetZ - camera.position.z) / dir.z;
|
|
||||||
|
|
||||||
return camera.position.clone().add(dir.multiplyScalar(distance));
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Ray-Plane Intersection
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function getRayPlaneIntersection(mouse, camera, plane) {
|
|
||||||
const raycaster = new THREE.Raycaster();
|
|
||||||
raycaster.setFromCamera(mouse, camera);
|
|
||||||
|
|
||||||
const intersection = new THREE.Vector3();
|
|
||||||
raycaster.ray.intersectPlane(plane, intersection);
|
|
||||||
|
|
||||||
return intersection;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ground plane
|
|
||||||
const groundPlane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0);
|
|
||||||
const worldPos = getRayPlaneIntersection(mouse, camera, groundPlane);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Event Handling Best Practices
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
class InteractionManager {
|
|
||||||
constructor(camera, renderer, scene) {
|
|
||||||
this.camera = camera;
|
|
||||||
this.renderer = renderer;
|
|
||||||
this.scene = scene;
|
|
||||||
this.raycaster = new THREE.Raycaster();
|
|
||||||
this.mouse = new THREE.Vector2();
|
|
||||||
this.clickables = [];
|
|
||||||
|
|
||||||
this.bindEvents();
|
|
||||||
}
|
|
||||||
|
|
||||||
bindEvents() {
|
|
||||||
const canvas = this.renderer.domElement;
|
|
||||||
|
|
||||||
canvas.addEventListener("click", (e) => this.onClick(e));
|
|
||||||
canvas.addEventListener("mousemove", (e) => this.onMouseMove(e));
|
|
||||||
canvas.addEventListener("touchstart", (e) => this.onTouchStart(e));
|
|
||||||
}
|
|
||||||
|
|
||||||
updateMouse(event) {
|
|
||||||
const rect = this.renderer.domElement.getBoundingClientRect();
|
|
||||||
this.mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
|
|
||||||
this.mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
getIntersects() {
|
|
||||||
this.raycaster.setFromCamera(this.mouse, this.camera);
|
|
||||||
return this.raycaster.intersectObjects(this.clickables, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
onClick(event) {
|
|
||||||
this.updateMouse(event);
|
|
||||||
const intersects = this.getIntersects();
|
|
||||||
|
|
||||||
if (intersects.length > 0) {
|
|
||||||
const object = intersects[0].object;
|
|
||||||
if (object.userData.onClick) {
|
|
||||||
object.userData.onClick(intersects[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addClickable(object, callback) {
|
|
||||||
this.clickables.push(object);
|
|
||||||
object.userData.onClick = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
dispose() {
|
|
||||||
// Remove event listeners
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Usage
|
|
||||||
const interaction = new InteractionManager(camera, renderer, scene);
|
|
||||||
interaction.addClickable(mesh, (intersect) => {
|
|
||||||
console.log("Clicked at:", intersect.point);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Performance Tips
|
|
||||||
|
|
||||||
1. **Limit raycasts**: Throttle mousemove handlers
|
|
||||||
2. **Use layers**: Filter raycast targets
|
|
||||||
3. **Simple collision meshes**: Use invisible simpler geometry for raycasting
|
|
||||||
4. **Disable controls when not needed**: `controls.enabled = false`
|
|
||||||
5. **Batch updates**: Group interaction checks
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Use simpler geometry for raycasting
|
|
||||||
const complexMesh = loadedModel;
|
|
||||||
const collisionMesh = new THREE.Mesh(
|
|
||||||
new THREE.BoxGeometry(1, 1, 1),
|
|
||||||
new THREE.MeshBasicMaterial({ visible: false }),
|
|
||||||
);
|
|
||||||
collisionMesh.userData.target = complexMesh;
|
|
||||||
clickables.push(collisionMesh);
|
|
||||||
```
|
|
||||||
|
|
||||||
## See Also
|
|
||||||
|
|
||||||
- `threejs-fundamentals` - Camera and scene setup
|
|
||||||
- `threejs-animation` - Animating interactions
|
|
||||||
- `threejs-shaders` - Visual feedback effects
|
|
||||||
@@ -1,481 +0,0 @@
|
|||||||
---
|
|
||||||
name: threejs-lighting
|
|
||||||
description: "Three.js 灯光处理,用于光源类型、阴影、环境光、IBL 设置和灯光性能优化。"
|
|
||||||
---
|
|
||||||
|
|
||||||
# Three.js Lighting
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import * as THREE from "three";
|
|
||||||
|
|
||||||
// Basic lighting setup
|
|
||||||
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
|
|
||||||
scene.add(ambientLight);
|
|
||||||
|
|
||||||
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
|
|
||||||
directionalLight.position.set(5, 5, 5);
|
|
||||||
scene.add(directionalLight);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Light Types Overview
|
|
||||||
|
|
||||||
| Light | Description | Shadow Support | Cost |
|
|
||||||
| ---------------- | ---------------------- | -------------- | -------- |
|
|
||||||
| AmbientLight | Uniform everywhere | No | Very Low |
|
|
||||||
| HemisphereLight | Sky/ground gradient | No | Very Low |
|
|
||||||
| DirectionalLight | Parallel rays (sun) | Yes | Low |
|
|
||||||
| PointLight | Omnidirectional (bulb) | Yes | Medium |
|
|
||||||
| SpotLight | Cone-shaped | Yes | Medium |
|
|
||||||
| RectAreaLight | Area light (window) | No\* | High |
|
|
||||||
|
|
||||||
\*RectAreaLight shadows require custom solutions
|
|
||||||
|
|
||||||
## AmbientLight
|
|
||||||
|
|
||||||
Illuminates all objects equally. No direction, no shadows.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// AmbientLight(color, intensity)
|
|
||||||
const ambient = new THREE.AmbientLight(0xffffff, 0.5);
|
|
||||||
scene.add(ambient);
|
|
||||||
|
|
||||||
// Modify at runtime
|
|
||||||
ambient.color.set(0xffffcc);
|
|
||||||
ambient.intensity = 0.3;
|
|
||||||
```
|
|
||||||
|
|
||||||
## HemisphereLight
|
|
||||||
|
|
||||||
Gradient from sky to ground color. Good for outdoor scenes.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// HemisphereLight(skyColor, groundColor, intensity)
|
|
||||||
const hemi = new THREE.HemisphereLight(0x87ceeb, 0x8b4513, 0.6);
|
|
||||||
hemi.position.set(0, 50, 0);
|
|
||||||
scene.add(hemi);
|
|
||||||
|
|
||||||
// Properties
|
|
||||||
hemi.color; // Sky color
|
|
||||||
hemi.groundColor; // Ground color
|
|
||||||
hemi.intensity;
|
|
||||||
```
|
|
||||||
|
|
||||||
## DirectionalLight
|
|
||||||
|
|
||||||
Parallel light rays. Simulates distant light source (sun).
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// DirectionalLight(color, intensity)
|
|
||||||
const dirLight = new THREE.DirectionalLight(0xffffff, 1);
|
|
||||||
dirLight.position.set(5, 10, 5);
|
|
||||||
|
|
||||||
// Light points at target (default: 0, 0, 0)
|
|
||||||
dirLight.target.position.set(0, 0, 0);
|
|
||||||
scene.add(dirLight.target);
|
|
||||||
|
|
||||||
scene.add(dirLight);
|
|
||||||
```
|
|
||||||
|
|
||||||
### DirectionalLight Shadows
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
dirLight.castShadow = true;
|
|
||||||
|
|
||||||
// Shadow map size (higher = sharper, more expensive)
|
|
||||||
dirLight.shadow.mapSize.width = 2048;
|
|
||||||
dirLight.shadow.mapSize.height = 2048;
|
|
||||||
|
|
||||||
// Shadow camera (orthographic)
|
|
||||||
dirLight.shadow.camera.near = 0.5;
|
|
||||||
dirLight.shadow.camera.far = 50;
|
|
||||||
dirLight.shadow.camera.left = -10;
|
|
||||||
dirLight.shadow.camera.right = 10;
|
|
||||||
dirLight.shadow.camera.top = 10;
|
|
||||||
dirLight.shadow.camera.bottom = -10;
|
|
||||||
|
|
||||||
// Shadow softness
|
|
||||||
dirLight.shadow.radius = 4; // Blur radius (PCFSoftShadowMap only)
|
|
||||||
|
|
||||||
// Shadow bias (fixes shadow acne)
|
|
||||||
dirLight.shadow.bias = -0.0001;
|
|
||||||
dirLight.shadow.normalBias = 0.02;
|
|
||||||
|
|
||||||
// Helper to visualize shadow camera
|
|
||||||
const helper = new THREE.CameraHelper(dirLight.shadow.camera);
|
|
||||||
scene.add(helper);
|
|
||||||
```
|
|
||||||
|
|
||||||
## PointLight
|
|
||||||
|
|
||||||
Emits light in all directions from a point. Like a light bulb.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// PointLight(color, intensity, distance, decay)
|
|
||||||
const pointLight = new THREE.PointLight(0xffffff, 1, 100, 2);
|
|
||||||
pointLight.position.set(0, 5, 0);
|
|
||||||
scene.add(pointLight);
|
|
||||||
|
|
||||||
// Properties
|
|
||||||
pointLight.distance; // Maximum range (0 = infinite)
|
|
||||||
pointLight.decay; // Light falloff (physically correct = 2)
|
|
||||||
```
|
|
||||||
|
|
||||||
### PointLight Shadows
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
pointLight.castShadow = true;
|
|
||||||
pointLight.shadow.mapSize.width = 1024;
|
|
||||||
pointLight.shadow.mapSize.height = 1024;
|
|
||||||
|
|
||||||
// Shadow camera (perspective - 6 directions for cube map)
|
|
||||||
pointLight.shadow.camera.near = 0.5;
|
|
||||||
pointLight.shadow.camera.far = 50;
|
|
||||||
|
|
||||||
pointLight.shadow.bias = -0.005;
|
|
||||||
```
|
|
||||||
|
|
||||||
## SpotLight
|
|
||||||
|
|
||||||
Cone-shaped light. Like a flashlight or stage light.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// SpotLight(color, intensity, distance, angle, penumbra, decay)
|
|
||||||
const spotLight = new THREE.SpotLight(0xffffff, 1, 100, Math.PI / 6, 0.5, 2);
|
|
||||||
spotLight.position.set(0, 10, 0);
|
|
||||||
|
|
||||||
// Target (light points at this)
|
|
||||||
spotLight.target.position.set(0, 0, 0);
|
|
||||||
scene.add(spotLight.target);
|
|
||||||
|
|
||||||
scene.add(spotLight);
|
|
||||||
|
|
||||||
// Properties
|
|
||||||
spotLight.angle; // Cone angle (radians, max Math.PI/2)
|
|
||||||
spotLight.penumbra; // Soft edge (0-1)
|
|
||||||
spotLight.distance; // Range
|
|
||||||
spotLight.decay; // Falloff
|
|
||||||
```
|
|
||||||
|
|
||||||
### SpotLight Shadows
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
spotLight.castShadow = true;
|
|
||||||
spotLight.shadow.mapSize.width = 1024;
|
|
||||||
spotLight.shadow.mapSize.height = 1024;
|
|
||||||
|
|
||||||
// Shadow camera (perspective)
|
|
||||||
spotLight.shadow.camera.near = 0.5;
|
|
||||||
spotLight.shadow.camera.far = 50;
|
|
||||||
spotLight.shadow.camera.fov = 30;
|
|
||||||
|
|
||||||
spotLight.shadow.bias = -0.0001;
|
|
||||||
|
|
||||||
// Focus (affects shadow projection)
|
|
||||||
spotLight.shadow.focus = 1;
|
|
||||||
```
|
|
||||||
|
|
||||||
## RectAreaLight
|
|
||||||
|
|
||||||
Rectangular area light. Great for soft, realistic lighting.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { RectAreaLightHelper } from "three/examples/jsm/helpers/RectAreaLightHelper.js";
|
|
||||||
import { RectAreaLightUniformsLib } from "three/examples/jsm/lights/RectAreaLightUniformsLib.js";
|
|
||||||
|
|
||||||
// Must initialize uniforms first
|
|
||||||
RectAreaLightUniformsLib.init();
|
|
||||||
|
|
||||||
// RectAreaLight(color, intensity, width, height)
|
|
||||||
const rectLight = new THREE.RectAreaLight(0xffffff, 5, 4, 2);
|
|
||||||
rectLight.position.set(0, 5, 0);
|
|
||||||
rectLight.lookAt(0, 0, 0);
|
|
||||||
scene.add(rectLight);
|
|
||||||
|
|
||||||
// Helper
|
|
||||||
const helper = new RectAreaLightHelper(rectLight);
|
|
||||||
rectLight.add(helper);
|
|
||||||
|
|
||||||
// Note: Only works with MeshStandardMaterial and MeshPhysicalMaterial
|
|
||||||
// Does not cast shadows natively
|
|
||||||
```
|
|
||||||
|
|
||||||
## Shadow Setup
|
|
||||||
|
|
||||||
### Enable Shadows
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// 1. Enable on renderer
|
|
||||||
renderer.shadowMap.enabled = true;
|
|
||||||
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
|
||||||
|
|
||||||
// Shadow map types:
|
|
||||||
// THREE.BasicShadowMap - fastest, low quality
|
|
||||||
// THREE.PCFShadowMap - default, filtered
|
|
||||||
// THREE.PCFSoftShadowMap - softer edges
|
|
||||||
// THREE.VSMShadowMap - variance shadow map
|
|
||||||
|
|
||||||
// 2. Enable on light
|
|
||||||
light.castShadow = true;
|
|
||||||
|
|
||||||
// 3. Enable on objects
|
|
||||||
mesh.castShadow = true;
|
|
||||||
mesh.receiveShadow = true;
|
|
||||||
|
|
||||||
// Ground plane
|
|
||||||
floor.receiveShadow = true;
|
|
||||||
floor.castShadow = false; // Usually false for floors
|
|
||||||
```
|
|
||||||
|
|
||||||
### Optimizing Shadows
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Tight shadow camera frustum
|
|
||||||
const d = 10;
|
|
||||||
dirLight.shadow.camera.left = -d;
|
|
||||||
dirLight.shadow.camera.right = d;
|
|
||||||
dirLight.shadow.camera.top = d;
|
|
||||||
dirLight.shadow.camera.bottom = -d;
|
|
||||||
dirLight.shadow.camera.near = 0.5;
|
|
||||||
dirLight.shadow.camera.far = 30;
|
|
||||||
|
|
||||||
// Fix shadow acne
|
|
||||||
dirLight.shadow.bias = -0.0001; // Depth bias
|
|
||||||
dirLight.shadow.normalBias = 0.02; // Bias along normal
|
|
||||||
|
|
||||||
// Shadow map size (balance quality vs performance)
|
|
||||||
// 512 - low quality
|
|
||||||
// 1024 - medium quality
|
|
||||||
// 2048 - high quality
|
|
||||||
// 4096 - very high quality (expensive)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Contact Shadows (Fake, Fast)
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { ContactShadows } from "three/examples/jsm/objects/ContactShadows.js";
|
|
||||||
|
|
||||||
const contactShadows = new ContactShadows({
|
|
||||||
resolution: 512,
|
|
||||||
blur: 2,
|
|
||||||
opacity: 0.5,
|
|
||||||
scale: 10,
|
|
||||||
position: [0, 0, 0],
|
|
||||||
});
|
|
||||||
scene.add(contactShadows);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Light Helpers
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { RectAreaLightHelper } from "three/examples/jsm/helpers/RectAreaLightHelper.js";
|
|
||||||
|
|
||||||
// DirectionalLight helper
|
|
||||||
const dirHelper = new THREE.DirectionalLightHelper(dirLight, 5);
|
|
||||||
scene.add(dirHelper);
|
|
||||||
|
|
||||||
// PointLight helper
|
|
||||||
const pointHelper = new THREE.PointLightHelper(pointLight, 1);
|
|
||||||
scene.add(pointHelper);
|
|
||||||
|
|
||||||
// SpotLight helper
|
|
||||||
const spotHelper = new THREE.SpotLightHelper(spotLight);
|
|
||||||
scene.add(spotHelper);
|
|
||||||
|
|
||||||
// Hemisphere helper
|
|
||||||
const hemiHelper = new THREE.HemisphereLightHelper(hemiLight, 5);
|
|
||||||
scene.add(hemiHelper);
|
|
||||||
|
|
||||||
// RectAreaLight helper
|
|
||||||
const rectHelper = new RectAreaLightHelper(rectLight);
|
|
||||||
rectLight.add(rectHelper);
|
|
||||||
|
|
||||||
// Update helpers when light changes
|
|
||||||
dirHelper.update();
|
|
||||||
spotHelper.update();
|
|
||||||
```
|
|
||||||
|
|
||||||
## Environment Lighting (IBL)
|
|
||||||
|
|
||||||
Image-Based Lighting using HDR environment maps.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js";
|
|
||||||
|
|
||||||
const rgbeLoader = new RGBELoader();
|
|
||||||
rgbeLoader.load("environment.hdr", (texture) => {
|
|
||||||
texture.mapping = THREE.EquirectangularReflectionMapping;
|
|
||||||
|
|
||||||
// Set as scene environment (affects all PBR materials)
|
|
||||||
scene.environment = texture;
|
|
||||||
|
|
||||||
// Optional: also use as background
|
|
||||||
scene.background = texture;
|
|
||||||
scene.backgroundBlurriness = 0; // 0-1, blur the background
|
|
||||||
scene.backgroundIntensity = 1;
|
|
||||||
});
|
|
||||||
|
|
||||||
// PMREMGenerator for better reflections
|
|
||||||
const pmremGenerator = new THREE.PMREMGenerator(renderer);
|
|
||||||
pmremGenerator.compileEquirectangularShader();
|
|
||||||
|
|
||||||
rgbeLoader.load("environment.hdr", (texture) => {
|
|
||||||
const envMap = pmremGenerator.fromEquirectangular(texture).texture;
|
|
||||||
scene.environment = envMap;
|
|
||||||
texture.dispose();
|
|
||||||
pmremGenerator.dispose();
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Cube Texture Environment
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const cubeLoader = new THREE.CubeTextureLoader();
|
|
||||||
const envMap = cubeLoader.load([
|
|
||||||
"px.jpg",
|
|
||||||
"nx.jpg",
|
|
||||||
"py.jpg",
|
|
||||||
"ny.jpg",
|
|
||||||
"pz.jpg",
|
|
||||||
"nz.jpg",
|
|
||||||
]);
|
|
||||||
|
|
||||||
scene.environment = envMap;
|
|
||||||
scene.background = envMap;
|
|
||||||
```
|
|
||||||
|
|
||||||
## Light Probes (Advanced)
|
|
||||||
|
|
||||||
Capture lighting from a point in space for ambient lighting.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { LightProbeGenerator } from "three/examples/jsm/lights/LightProbeGenerator.js";
|
|
||||||
|
|
||||||
// Generate from cube texture
|
|
||||||
const lightProbe = new THREE.LightProbe();
|
|
||||||
scene.add(lightProbe);
|
|
||||||
|
|
||||||
lightProbe.copy(LightProbeGenerator.fromCubeTexture(cubeTexture));
|
|
||||||
|
|
||||||
// Or from render target
|
|
||||||
const cubeCamera = new THREE.CubeCamera(
|
|
||||||
0.1,
|
|
||||||
100,
|
|
||||||
new THREE.WebGLCubeRenderTarget(256),
|
|
||||||
);
|
|
||||||
cubeCamera.update(renderer, scene);
|
|
||||||
lightProbe.copy(
|
|
||||||
LightProbeGenerator.fromCubeRenderTarget(renderer, cubeCamera.renderTarget),
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Common Lighting Setups
|
|
||||||
|
|
||||||
### Three-Point Lighting
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Key light (main light)
|
|
||||||
const keyLight = new THREE.DirectionalLight(0xffffff, 1);
|
|
||||||
keyLight.position.set(5, 5, 5);
|
|
||||||
scene.add(keyLight);
|
|
||||||
|
|
||||||
// Fill light (softer, opposite side)
|
|
||||||
const fillLight = new THREE.DirectionalLight(0xffffff, 0.5);
|
|
||||||
fillLight.position.set(-5, 3, 5);
|
|
||||||
scene.add(fillLight);
|
|
||||||
|
|
||||||
// Back light (rim lighting)
|
|
||||||
const backLight = new THREE.DirectionalLight(0xffffff, 0.3);
|
|
||||||
backLight.position.set(0, 5, -5);
|
|
||||||
scene.add(backLight);
|
|
||||||
|
|
||||||
// Ambient fill
|
|
||||||
const ambient = new THREE.AmbientLight(0x404040, 0.3);
|
|
||||||
scene.add(ambient);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Outdoor Daylight
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Sun
|
|
||||||
const sun = new THREE.DirectionalLight(0xffffcc, 1.5);
|
|
||||||
sun.position.set(50, 100, 50);
|
|
||||||
sun.castShadow = true;
|
|
||||||
scene.add(sun);
|
|
||||||
|
|
||||||
// Sky ambient
|
|
||||||
const hemi = new THREE.HemisphereLight(0x87ceeb, 0x8b4513, 0.6);
|
|
||||||
scene.add(hemi);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Indoor Studio
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Multiple area lights
|
|
||||||
RectAreaLightUniformsLib.init();
|
|
||||||
|
|
||||||
const light1 = new THREE.RectAreaLight(0xffffff, 5, 2, 2);
|
|
||||||
light1.position.set(3, 3, 3);
|
|
||||||
light1.lookAt(0, 0, 0);
|
|
||||||
scene.add(light1);
|
|
||||||
|
|
||||||
const light2 = new THREE.RectAreaLight(0xffffff, 3, 2, 2);
|
|
||||||
light2.position.set(-3, 3, 3);
|
|
||||||
light2.lookAt(0, 0, 0);
|
|
||||||
scene.add(light2);
|
|
||||||
|
|
||||||
// Ambient fill
|
|
||||||
const ambient = new THREE.AmbientLight(0x404040, 0.2);
|
|
||||||
scene.add(ambient);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Light Animation
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const clock = new THREE.Clock();
|
|
||||||
|
|
||||||
function animate() {
|
|
||||||
const time = clock.getElapsedTime();
|
|
||||||
|
|
||||||
// Orbit light around scene
|
|
||||||
light.position.x = Math.cos(time) * 5;
|
|
||||||
light.position.z = Math.sin(time) * 5;
|
|
||||||
|
|
||||||
// Pulsing intensity
|
|
||||||
light.intensity = 1 + Math.sin(time * 2) * 0.5;
|
|
||||||
|
|
||||||
// Color cycling
|
|
||||||
light.color.setHSL((time * 0.1) % 1, 1, 0.5);
|
|
||||||
|
|
||||||
// Update helpers if using
|
|
||||||
lightHelper.update();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Performance Tips
|
|
||||||
|
|
||||||
1. **Limit light count**: Each light adds shader complexity
|
|
||||||
2. **Use baked lighting**: For static scenes, bake to textures
|
|
||||||
3. **Smaller shadow maps**: 512-1024 often sufficient
|
|
||||||
4. **Tight shadow frustums**: Only cover needed area
|
|
||||||
5. **Disable unused shadows**: Not all lights need shadows
|
|
||||||
6. **Use light layers**: Exclude objects from certain lights
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Light layers
|
|
||||||
light.layers.set(1); // Light only affects layer 1
|
|
||||||
mesh.layers.enable(1); // Mesh is on layer 1
|
|
||||||
otherMesh.layers.disable(1); // Other mesh not affected
|
|
||||||
|
|
||||||
// Selective shadows
|
|
||||||
mesh.castShadow = true;
|
|
||||||
mesh.receiveShadow = true;
|
|
||||||
decorMesh.castShadow = false; // Small objects often don't need to cast
|
|
||||||
```
|
|
||||||
|
|
||||||
## See Also
|
|
||||||
|
|
||||||
- `threejs-materials` - Material light response
|
|
||||||
- `threejs-textures` - Lightmaps and environment maps
|
|
||||||
- `threejs-postprocessing` - Bloom and other light effects
|
|
||||||
@@ -1,623 +0,0 @@
|
|||||||
---
|
|
||||||
name: threejs-loaders
|
|
||||||
description: "Three.js 资源加载,用于 GLTF、模型、图片、纹理、HDR 环境、异步加载和进度管理。"
|
|
||||||
---
|
|
||||||
|
|
||||||
# Three.js Loaders
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import * as THREE from "three";
|
|
||||||
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
|
|
||||||
|
|
||||||
// Load GLTF model
|
|
||||||
const loader = new GLTFLoader();
|
|
||||||
loader.load("model.glb", (gltf) => {
|
|
||||||
scene.add(gltf.scene);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Load texture
|
|
||||||
const textureLoader = new THREE.TextureLoader();
|
|
||||||
const texture = textureLoader.load("texture.jpg");
|
|
||||||
```
|
|
||||||
|
|
||||||
## LoadingManager
|
|
||||||
|
|
||||||
Coordinate multiple loaders and track progress.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const manager = new THREE.LoadingManager();
|
|
||||||
|
|
||||||
// Callbacks
|
|
||||||
manager.onStart = (url, loaded, total) => {
|
|
||||||
console.log(`Started loading: ${url}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
manager.onLoad = () => {
|
|
||||||
console.log("All assets loaded!");
|
|
||||||
startGame();
|
|
||||||
};
|
|
||||||
|
|
||||||
manager.onProgress = (url, loaded, total) => {
|
|
||||||
const progress = (loaded / total) * 100;
|
|
||||||
console.log(`Loading: ${progress.toFixed(1)}%`);
|
|
||||||
updateProgressBar(progress);
|
|
||||||
};
|
|
||||||
|
|
||||||
manager.onError = (url) => {
|
|
||||||
console.error(`Error loading: ${url}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Use manager with loaders
|
|
||||||
const textureLoader = new THREE.TextureLoader(manager);
|
|
||||||
const gltfLoader = new GLTFLoader(manager);
|
|
||||||
|
|
||||||
// Load assets
|
|
||||||
textureLoader.load("texture1.jpg");
|
|
||||||
textureLoader.load("texture2.jpg");
|
|
||||||
gltfLoader.load("model.glb");
|
|
||||||
// onLoad fires when ALL are complete
|
|
||||||
```
|
|
||||||
|
|
||||||
## Texture Loading
|
|
||||||
|
|
||||||
### TextureLoader
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const loader = new THREE.TextureLoader();
|
|
||||||
|
|
||||||
// Callback style
|
|
||||||
loader.load(
|
|
||||||
"texture.jpg",
|
|
||||||
(texture) => {
|
|
||||||
// onLoad
|
|
||||||
material.map = texture;
|
|
||||||
material.needsUpdate = true;
|
|
||||||
},
|
|
||||||
undefined, // onProgress - not supported for image loading
|
|
||||||
(error) => {
|
|
||||||
// onError
|
|
||||||
console.error("Error loading texture", error);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// Synchronous (returns texture, loads async)
|
|
||||||
const texture = loader.load("texture.jpg");
|
|
||||||
material.map = texture;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Texture Configuration
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const texture = loader.load("texture.jpg", (tex) => {
|
|
||||||
// Color space (important for color accuracy)
|
|
||||||
tex.colorSpace = THREE.SRGBColorSpace; // For color/albedo maps
|
|
||||||
// tex.colorSpace = THREE.LinearSRGBColorSpace; // For data maps (normal, roughness)
|
|
||||||
|
|
||||||
// Wrapping
|
|
||||||
tex.wrapS = THREE.RepeatWrapping;
|
|
||||||
tex.wrapT = THREE.RepeatWrapping;
|
|
||||||
// ClampToEdgeWrapping, RepeatWrapping, MirroredRepeatWrapping
|
|
||||||
|
|
||||||
// Repeat/offset
|
|
||||||
tex.repeat.set(2, 2);
|
|
||||||
tex.offset.set(0.5, 0.5);
|
|
||||||
tex.rotation = Math.PI / 4;
|
|
||||||
tex.center.set(0.5, 0.5);
|
|
||||||
|
|
||||||
// Filtering
|
|
||||||
tex.minFilter = THREE.LinearMipmapLinearFilter; // Default
|
|
||||||
tex.magFilter = THREE.LinearFilter; // Default
|
|
||||||
// NearestFilter - pixelated
|
|
||||||
// LinearFilter - smooth
|
|
||||||
// LinearMipmapLinearFilter - smooth with mipmaps
|
|
||||||
|
|
||||||
// Anisotropic filtering (sharper at angles)
|
|
||||||
tex.anisotropy = renderer.capabilities.getMaxAnisotropy();
|
|
||||||
|
|
||||||
// Flip Y (usually true for standard textures)
|
|
||||||
tex.flipY = true;
|
|
||||||
|
|
||||||
tex.needsUpdate = true;
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### CubeTextureLoader
|
|
||||||
|
|
||||||
For environment maps and skyboxes.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const loader = new THREE.CubeTextureLoader();
|
|
||||||
|
|
||||||
// Load 6 faces
|
|
||||||
const cubeTexture = loader.load([
|
|
||||||
"px.jpg",
|
|
||||||
"nx.jpg", // positive/negative X
|
|
||||||
"py.jpg",
|
|
||||||
"ny.jpg", // positive/negative Y
|
|
||||||
"pz.jpg",
|
|
||||||
"nz.jpg", // positive/negative Z
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Use as background
|
|
||||||
scene.background = cubeTexture;
|
|
||||||
|
|
||||||
// Use as environment map
|
|
||||||
scene.environment = cubeTexture;
|
|
||||||
material.envMap = cubeTexture;
|
|
||||||
```
|
|
||||||
|
|
||||||
### HDR/EXR Loading
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { RGBELoader } from "three/addons/loaders/RGBELoader.js";
|
|
||||||
import { EXRLoader } from "three/addons/loaders/EXRLoader.js";
|
|
||||||
|
|
||||||
// HDR
|
|
||||||
const rgbeLoader = new RGBELoader();
|
|
||||||
rgbeLoader.load("environment.hdr", (texture) => {
|
|
||||||
texture.mapping = THREE.EquirectangularReflectionMapping;
|
|
||||||
scene.environment = texture;
|
|
||||||
scene.background = texture;
|
|
||||||
});
|
|
||||||
|
|
||||||
// EXR
|
|
||||||
const exrLoader = new EXRLoader();
|
|
||||||
exrLoader.load("environment.exr", (texture) => {
|
|
||||||
texture.mapping = THREE.EquirectangularReflectionMapping;
|
|
||||||
scene.environment = texture;
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### PMREMGenerator
|
|
||||||
|
|
||||||
Generate prefiltered environment maps for PBR.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { RGBELoader } from "three/addons/loaders/RGBELoader.js";
|
|
||||||
|
|
||||||
const pmremGenerator = new THREE.PMREMGenerator(renderer);
|
|
||||||
pmremGenerator.compileEquirectangularShader();
|
|
||||||
|
|
||||||
new RGBELoader().load("environment.hdr", (texture) => {
|
|
||||||
const envMap = pmremGenerator.fromEquirectangular(texture).texture;
|
|
||||||
|
|
||||||
scene.environment = envMap;
|
|
||||||
scene.background = envMap;
|
|
||||||
|
|
||||||
texture.dispose();
|
|
||||||
pmremGenerator.dispose();
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## GLTF/GLB Loading
|
|
||||||
|
|
||||||
The most common 3D format for web.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
|
|
||||||
|
|
||||||
const loader = new GLTFLoader();
|
|
||||||
|
|
||||||
loader.load("model.glb", (gltf) => {
|
|
||||||
// The loaded scene
|
|
||||||
const model = gltf.scene;
|
|
||||||
scene.add(model);
|
|
||||||
|
|
||||||
// Animations
|
|
||||||
const animations = gltf.animations;
|
|
||||||
if (animations.length > 0) {
|
|
||||||
const mixer = new THREE.AnimationMixer(model);
|
|
||||||
animations.forEach((clip) => {
|
|
||||||
mixer.clipAction(clip).play();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cameras (if any)
|
|
||||||
const cameras = gltf.cameras;
|
|
||||||
|
|
||||||
// Asset info
|
|
||||||
console.log(gltf.asset); // Version, generator, etc.
|
|
||||||
|
|
||||||
// User data from Blender/etc
|
|
||||||
console.log(gltf.userData);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### GLTF with Draco Compression
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
|
|
||||||
import { DRACOLoader } from "three/addons/loaders/DRACOLoader.js";
|
|
||||||
|
|
||||||
const dracoLoader = new DRACOLoader();
|
|
||||||
dracoLoader.setDecoderPath(
|
|
||||||
"https://www.gstatic.com/draco/versioned/decoders/1.5.6/",
|
|
||||||
);
|
|
||||||
dracoLoader.preload();
|
|
||||||
|
|
||||||
const gltfLoader = new GLTFLoader();
|
|
||||||
gltfLoader.setDRACOLoader(dracoLoader);
|
|
||||||
|
|
||||||
gltfLoader.load("compressed-model.glb", (gltf) => {
|
|
||||||
scene.add(gltf.scene);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### GLTF with KTX2 Textures
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
|
|
||||||
import { KTX2Loader } from "three/addons/loaders/KTX2Loader.js";
|
|
||||||
|
|
||||||
const ktx2Loader = new KTX2Loader();
|
|
||||||
ktx2Loader.setTranscoderPath(
|
|
||||||
"https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/basis/",
|
|
||||||
);
|
|
||||||
ktx2Loader.detectSupport(renderer);
|
|
||||||
|
|
||||||
const gltfLoader = new GLTFLoader();
|
|
||||||
gltfLoader.setKTX2Loader(ktx2Loader);
|
|
||||||
|
|
||||||
gltfLoader.load("model-with-ktx2.glb", (gltf) => {
|
|
||||||
scene.add(gltf.scene);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Process GLTF Content
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
loader.load("model.glb", (gltf) => {
|
|
||||||
const model = gltf.scene;
|
|
||||||
|
|
||||||
// Enable shadows
|
|
||||||
model.traverse((child) => {
|
|
||||||
if (child.isMesh) {
|
|
||||||
child.castShadow = true;
|
|
||||||
child.receiveShadow = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Find specific mesh
|
|
||||||
const head = model.getObjectByName("Head");
|
|
||||||
|
|
||||||
// Adjust materials
|
|
||||||
model.traverse((child) => {
|
|
||||||
if (child.isMesh && child.material) {
|
|
||||||
child.material.envMapIntensity = 0.5;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Center and scale
|
|
||||||
const box = new THREE.Box3().setFromObject(model);
|
|
||||||
const center = box.getCenter(new THREE.Vector3());
|
|
||||||
const size = box.getSize(new THREE.Vector3());
|
|
||||||
|
|
||||||
model.position.sub(center);
|
|
||||||
const maxDim = Math.max(size.x, size.y, size.z);
|
|
||||||
model.scale.setScalar(1 / maxDim);
|
|
||||||
|
|
||||||
scene.add(model);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Other Model Formats
|
|
||||||
|
|
||||||
### OBJ + MTL
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { OBJLoader } from "three/addons/loaders/OBJLoader.js";
|
|
||||||
import { MTLLoader } from "three/addons/loaders/MTLLoader.js";
|
|
||||||
|
|
||||||
const mtlLoader = new MTLLoader();
|
|
||||||
mtlLoader.load("model.mtl", (materials) => {
|
|
||||||
materials.preload();
|
|
||||||
|
|
||||||
const objLoader = new OBJLoader();
|
|
||||||
objLoader.setMaterials(materials);
|
|
||||||
objLoader.load("model.obj", (object) => {
|
|
||||||
scene.add(object);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### FBX
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { FBXLoader } from "three/addons/loaders/FBXLoader.js";
|
|
||||||
|
|
||||||
const loader = new FBXLoader();
|
|
||||||
loader.load("model.fbx", (object) => {
|
|
||||||
// FBX often has large scale
|
|
||||||
object.scale.setScalar(0.01);
|
|
||||||
|
|
||||||
// Animations
|
|
||||||
const mixer = new THREE.AnimationMixer(object);
|
|
||||||
object.animations.forEach((clip) => {
|
|
||||||
mixer.clipAction(clip).play();
|
|
||||||
});
|
|
||||||
|
|
||||||
scene.add(object);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### STL
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { STLLoader } from "three/addons/loaders/STLLoader.js";
|
|
||||||
|
|
||||||
const loader = new STLLoader();
|
|
||||||
loader.load("model.stl", (geometry) => {
|
|
||||||
const material = new THREE.MeshStandardMaterial({ color: 0x888888 });
|
|
||||||
const mesh = new THREE.Mesh(geometry, material);
|
|
||||||
scene.add(mesh);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### PLY
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { PLYLoader } from "three/addons/loaders/PLYLoader.js";
|
|
||||||
|
|
||||||
const loader = new PLYLoader();
|
|
||||||
loader.load("model.ply", (geometry) => {
|
|
||||||
geometry.computeVertexNormals();
|
|
||||||
const material = new THREE.MeshStandardMaterial({ vertexColors: true });
|
|
||||||
const mesh = new THREE.Mesh(geometry, material);
|
|
||||||
scene.add(mesh);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Async/Promise Loading
|
|
||||||
|
|
||||||
### Promisified Loader
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function loadModel(url) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
loader.load(url, resolve, undefined, reject);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Usage
|
|
||||||
async function init() {
|
|
||||||
try {
|
|
||||||
const gltf = await loadModel("model.glb");
|
|
||||||
scene.add(gltf.scene);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Failed to load model:", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Load Multiple Assets
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
async function loadAssets() {
|
|
||||||
const [modelGltf, envTexture, colorTexture] = await Promise.all([
|
|
||||||
loadGLTF("model.glb"),
|
|
||||||
loadRGBE("environment.hdr"),
|
|
||||||
loadTexture("color.jpg"),
|
|
||||||
]);
|
|
||||||
|
|
||||||
scene.add(modelGltf.scene);
|
|
||||||
scene.environment = envTexture;
|
|
||||||
material.map = colorTexture;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper functions
|
|
||||||
function loadGLTF(url) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
new GLTFLoader().load(url, resolve, undefined, reject);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadRGBE(url) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
new RGBELoader().load(
|
|
||||||
url,
|
|
||||||
(texture) => {
|
|
||||||
texture.mapping = THREE.EquirectangularReflectionMapping;
|
|
||||||
resolve(texture);
|
|
||||||
},
|
|
||||||
undefined,
|
|
||||||
reject,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadTexture(url) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
new THREE.TextureLoader().load(url, resolve, undefined, reject);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Caching
|
|
||||||
|
|
||||||
### Built-in Cache
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Enable cache
|
|
||||||
THREE.Cache.enabled = true;
|
|
||||||
|
|
||||||
// Clear cache
|
|
||||||
THREE.Cache.clear();
|
|
||||||
|
|
||||||
// Manual cache management
|
|
||||||
THREE.Cache.add("key", data);
|
|
||||||
THREE.Cache.get("key");
|
|
||||||
THREE.Cache.remove("key");
|
|
||||||
```
|
|
||||||
|
|
||||||
### Custom Asset Manager
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
class AssetManager {
|
|
||||||
constructor() {
|
|
||||||
this.textures = new Map();
|
|
||||||
this.models = new Map();
|
|
||||||
this.gltfLoader = new GLTFLoader();
|
|
||||||
this.textureLoader = new THREE.TextureLoader();
|
|
||||||
}
|
|
||||||
|
|
||||||
async loadTexture(key, url) {
|
|
||||||
if (this.textures.has(key)) {
|
|
||||||
return this.textures.get(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
const texture = await new Promise((resolve, reject) => {
|
|
||||||
this.textureLoader.load(url, resolve, undefined, reject);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.textures.set(key, texture);
|
|
||||||
return texture;
|
|
||||||
}
|
|
||||||
|
|
||||||
async loadModel(key, url) {
|
|
||||||
if (this.models.has(key)) {
|
|
||||||
return this.models.get(key).clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
const gltf = await new Promise((resolve, reject) => {
|
|
||||||
this.gltfLoader.load(url, resolve, undefined, reject);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.models.set(key, gltf.scene);
|
|
||||||
return gltf.scene.clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
dispose() {
|
|
||||||
this.textures.forEach((t) => t.dispose());
|
|
||||||
this.textures.clear();
|
|
||||||
this.models.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Usage
|
|
||||||
const assets = new AssetManager();
|
|
||||||
const texture = await assets.loadTexture("brick", "brick.jpg");
|
|
||||||
const model = await assets.loadModel("tree", "tree.glb");
|
|
||||||
```
|
|
||||||
|
|
||||||
## Loading from Different Sources
|
|
||||||
|
|
||||||
### Data URL / Base64
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const loader = new THREE.TextureLoader();
|
|
||||||
const texture = loader.load("data:image/png;base64,iVBORw0KGgo...");
|
|
||||||
```
|
|
||||||
|
|
||||||
### Blob URL
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
async function loadFromBlob(blob) {
|
|
||||||
const url = URL.createObjectURL(blob);
|
|
||||||
const texture = await loadTexture(url);
|
|
||||||
URL.revokeObjectURL(url);
|
|
||||||
return texture;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### ArrayBuffer
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// From fetch
|
|
||||||
const response = await fetch("model.glb");
|
|
||||||
const buffer = await response.arrayBuffer();
|
|
||||||
|
|
||||||
// Parse with loader
|
|
||||||
const loader = new GLTFLoader();
|
|
||||||
loader.parse(buffer, "", (gltf) => {
|
|
||||||
scene.add(gltf.scene);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Custom Path/URL
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Set base path
|
|
||||||
loader.setPath("assets/models/");
|
|
||||||
loader.load("model.glb"); // Loads from assets/models/model.glb
|
|
||||||
|
|
||||||
// Set resource path (for textures referenced in model)
|
|
||||||
loader.setResourcePath("assets/textures/");
|
|
||||||
|
|
||||||
// Custom URL modifier
|
|
||||||
manager.setURLModifier((url) => {
|
|
||||||
return `https://cdn.example.com/${url}`;
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Graceful fallback
|
|
||||||
async function loadWithFallback(primaryUrl, fallbackUrl) {
|
|
||||||
try {
|
|
||||||
return await loadModel(primaryUrl);
|
|
||||||
} catch (error) {
|
|
||||||
console.warn(`Primary failed, trying fallback: ${error}`);
|
|
||||||
return await loadModel(fallbackUrl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retry logic
|
|
||||||
async function loadWithRetry(url, maxRetries = 3) {
|
|
||||||
for (let i = 0; i < maxRetries; i++) {
|
|
||||||
try {
|
|
||||||
return await loadModel(url);
|
|
||||||
} catch (error) {
|
|
||||||
if (i === maxRetries - 1) throw error;
|
|
||||||
await new Promise((r) => setTimeout(r, 1000 * (i + 1)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Timeout
|
|
||||||
async function loadWithTimeout(url, timeout = 30000) {
|
|
||||||
const controller = new AbortController();
|
|
||||||
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(url, { signal: controller.signal });
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
return response;
|
|
||||||
} catch (error) {
|
|
||||||
if (error.name === "AbortError") {
|
|
||||||
throw new Error("Loading timed out");
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Performance Tips
|
|
||||||
|
|
||||||
1. **Use compressed formats**: DRACO for geometry, KTX2/Basis for textures
|
|
||||||
2. **Load progressively**: Show placeholders while loading
|
|
||||||
3. **Lazy load**: Only load what's needed
|
|
||||||
4. **Use CDN**: Faster asset delivery
|
|
||||||
5. **Enable cache**: `THREE.Cache.enabled = true`
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Progressive loading with placeholder
|
|
||||||
const placeholder = new THREE.Mesh(
|
|
||||||
new THREE.BoxGeometry(1, 1, 1),
|
|
||||||
new THREE.MeshBasicMaterial({ wireframe: true }),
|
|
||||||
);
|
|
||||||
scene.add(placeholder);
|
|
||||||
|
|
||||||
loadModel("model.glb").then((gltf) => {
|
|
||||||
scene.remove(placeholder);
|
|
||||||
scene.add(gltf.scene);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## See Also
|
|
||||||
|
|
||||||
- `threejs-textures` - Texture configuration
|
|
||||||
- `threejs-animation` - Playing loaded animations
|
|
||||||
- `threejs-materials` - Material from loaded models
|
|
||||||
@@ -1,520 +0,0 @@
|
|||||||
---
|
|
||||||
name: threejs-materials
|
|
||||||
description: "Three.js 材质处理,用于 PBR、基础材质、Phong、ShaderMaterial、贴图绑定和材质性能优化。"
|
|
||||||
---
|
|
||||||
|
|
||||||
# Three.js Materials
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import * as THREE from "three";
|
|
||||||
|
|
||||||
// PBR material (recommended for realistic rendering)
|
|
||||||
const material = new THREE.MeshStandardMaterial({
|
|
||||||
color: 0x00ff00,
|
|
||||||
roughness: 0.5,
|
|
||||||
metalness: 0.5,
|
|
||||||
});
|
|
||||||
|
|
||||||
const mesh = new THREE.Mesh(geometry, material);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Material Types Overview
|
|
||||||
|
|
||||||
| Material | Use Case | Lighting |
|
|
||||||
| -------------------- | ------------------------------------- | ------------------ |
|
|
||||||
| MeshBasicMaterial | Unlit, flat colors, wireframes | No |
|
|
||||||
| MeshLambertMaterial | Matte surfaces, performance | Yes (diffuse only) |
|
|
||||||
| MeshPhongMaterial | Shiny surfaces, specular highlights | Yes |
|
|
||||||
| MeshStandardMaterial | PBR, realistic materials | Yes (PBR) |
|
|
||||||
| MeshPhysicalMaterial | Advanced PBR, clearcoat, transmission | Yes (PBR+) |
|
|
||||||
| MeshToonMaterial | Cel-shaded, cartoon look | Yes (toon) |
|
|
||||||
| MeshNormalMaterial | Debug normals | No |
|
|
||||||
| MeshDepthMaterial | Depth visualization | No |
|
|
||||||
| ShaderMaterial | Custom GLSL shaders | Custom |
|
|
||||||
| RawShaderMaterial | Full shader control | Custom |
|
|
||||||
|
|
||||||
## MeshBasicMaterial
|
|
||||||
|
|
||||||
No lighting calculations. Fast, always visible.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const material = new THREE.MeshBasicMaterial({
|
|
||||||
color: 0xff0000,
|
|
||||||
transparent: true,
|
|
||||||
opacity: 0.5,
|
|
||||||
side: THREE.DoubleSide, // FrontSide, BackSide, DoubleSide
|
|
||||||
wireframe: false,
|
|
||||||
map: texture, // Color/diffuse texture
|
|
||||||
alphaMap: alphaTexture, // Transparency texture
|
|
||||||
envMap: envTexture, // Reflection texture
|
|
||||||
reflectivity: 1, // Env map intensity
|
|
||||||
fog: true, // Affected by scene fog
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## MeshLambertMaterial
|
|
||||||
|
|
||||||
Diffuse-only lighting. Fast, no specular highlights.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const material = new THREE.MeshLambertMaterial({
|
|
||||||
color: 0x00ff00,
|
|
||||||
emissive: 0x111111, // Self-illumination color
|
|
||||||
emissiveIntensity: 1,
|
|
||||||
map: texture,
|
|
||||||
emissiveMap: emissiveTexture,
|
|
||||||
envMap: envTexture,
|
|
||||||
reflectivity: 0.5,
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## MeshPhongMaterial
|
|
||||||
|
|
||||||
Specular highlights. Good for shiny, plastic-like surfaces.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const material = new THREE.MeshPhongMaterial({
|
|
||||||
color: 0x0000ff,
|
|
||||||
specular: 0xffffff, // Highlight color
|
|
||||||
shininess: 100, // Highlight sharpness (0-1000)
|
|
||||||
emissive: 0x000000,
|
|
||||||
flatShading: false, // Flat vs smooth shading
|
|
||||||
map: texture,
|
|
||||||
specularMap: specTexture, // Per-pixel shininess
|
|
||||||
normalMap: normalTexture,
|
|
||||||
normalScale: new THREE.Vector2(1, 1),
|
|
||||||
bumpMap: bumpTexture,
|
|
||||||
bumpScale: 1,
|
|
||||||
displacementMap: dispTexture,
|
|
||||||
displacementScale: 1,
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## MeshStandardMaterial (PBR)
|
|
||||||
|
|
||||||
Physically-based rendering. Recommended for realistic results.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const material = new THREE.MeshStandardMaterial({
|
|
||||||
color: 0xffffff,
|
|
||||||
roughness: 0.5, // 0 = mirror, 1 = diffuse
|
|
||||||
metalness: 0.0, // 0 = dielectric, 1 = metal
|
|
||||||
|
|
||||||
// Textures
|
|
||||||
map: colorTexture, // Albedo/base color
|
|
||||||
roughnessMap: roughTexture, // Per-pixel roughness
|
|
||||||
metalnessMap: metalTexture, // Per-pixel metalness
|
|
||||||
normalMap: normalTexture, // Surface detail
|
|
||||||
normalScale: new THREE.Vector2(1, 1),
|
|
||||||
aoMap: aoTexture, // Ambient occlusion (uses uv2!)
|
|
||||||
aoMapIntensity: 1,
|
|
||||||
displacementMap: dispTexture, // Vertex displacement
|
|
||||||
displacementScale: 0.1,
|
|
||||||
displacementBias: 0,
|
|
||||||
|
|
||||||
// Emissive
|
|
||||||
emissive: 0x000000,
|
|
||||||
emissiveIntensity: 1,
|
|
||||||
emissiveMap: emissiveTexture,
|
|
||||||
|
|
||||||
// Environment
|
|
||||||
envMap: envTexture,
|
|
||||||
envMapIntensity: 1,
|
|
||||||
|
|
||||||
// Other
|
|
||||||
flatShading: false,
|
|
||||||
wireframe: false,
|
|
||||||
fog: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Note: aoMap requires second UV channel
|
|
||||||
geometry.setAttribute("uv2", geometry.attributes.uv);
|
|
||||||
```
|
|
||||||
|
|
||||||
## MeshPhysicalMaterial (Advanced PBR)
|
|
||||||
|
|
||||||
Extends MeshStandardMaterial with advanced features.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const material = new THREE.MeshPhysicalMaterial({
|
|
||||||
// All MeshStandardMaterial properties plus:
|
|
||||||
|
|
||||||
// Clearcoat (car paint, lacquer)
|
|
||||||
clearcoat: 1.0, // 0-1 clearcoat layer strength
|
|
||||||
clearcoatRoughness: 0.1,
|
|
||||||
clearcoatMap: ccTexture,
|
|
||||||
clearcoatRoughnessMap: ccrTexture,
|
|
||||||
clearcoatNormalMap: ccnTexture,
|
|
||||||
clearcoatNormalScale: new THREE.Vector2(1, 1),
|
|
||||||
|
|
||||||
// Transmission (glass, water)
|
|
||||||
transmission: 1.0, // 0 = opaque, 1 = fully transparent
|
|
||||||
transmissionMap: transTexture,
|
|
||||||
thickness: 0.5, // Volume thickness for refraction
|
|
||||||
thicknessMap: thickTexture,
|
|
||||||
attenuationDistance: 1, // Absorption distance
|
|
||||||
attenuationColor: new THREE.Color(0xffffff),
|
|
||||||
|
|
||||||
// Refraction
|
|
||||||
ior: 1.5, // Index of refraction (1-2.333)
|
|
||||||
|
|
||||||
// Sheen (fabric, velvet)
|
|
||||||
sheen: 1.0,
|
|
||||||
sheenRoughness: 0.5,
|
|
||||||
sheenColor: new THREE.Color(0xffffff),
|
|
||||||
sheenColorMap: sheenTexture,
|
|
||||||
sheenRoughnessMap: sheenRoughTexture,
|
|
||||||
|
|
||||||
// Iridescence (soap bubbles, oil slicks)
|
|
||||||
iridescence: 1.0,
|
|
||||||
iridescenceIOR: 1.3,
|
|
||||||
iridescenceThicknessRange: [100, 400],
|
|
||||||
iridescenceMap: iridTexture,
|
|
||||||
iridescenceThicknessMap: iridThickTexture,
|
|
||||||
|
|
||||||
// Anisotropy (brushed metal)
|
|
||||||
anisotropy: 1.0,
|
|
||||||
anisotropyRotation: 0,
|
|
||||||
anisotropyMap: anisoTexture,
|
|
||||||
|
|
||||||
// Specular
|
|
||||||
specularIntensity: 1,
|
|
||||||
specularColor: new THREE.Color(0xffffff),
|
|
||||||
specularIntensityMap: specIntTexture,
|
|
||||||
specularColorMap: specColorTexture,
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Glass Material Example
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const glass = new THREE.MeshPhysicalMaterial({
|
|
||||||
color: 0xffffff,
|
|
||||||
metalness: 0,
|
|
||||||
roughness: 0,
|
|
||||||
transmission: 1,
|
|
||||||
thickness: 0.5,
|
|
||||||
ior: 1.5,
|
|
||||||
envMapIntensity: 1,
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Car Paint Example
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const carPaint = new THREE.MeshPhysicalMaterial({
|
|
||||||
color: 0xff0000,
|
|
||||||
metalness: 0.9,
|
|
||||||
roughness: 0.5,
|
|
||||||
clearcoat: 1,
|
|
||||||
clearcoatRoughness: 0.1,
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## MeshToonMaterial
|
|
||||||
|
|
||||||
Cel-shaded cartoon look.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const material = new THREE.MeshToonMaterial({
|
|
||||||
color: 0x00ff00,
|
|
||||||
gradientMap: gradientTexture, // Optional: custom shading gradient
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create step gradient texture
|
|
||||||
const colors = new Uint8Array([0, 128, 255]);
|
|
||||||
const gradientMap = new THREE.DataTexture(colors, 3, 1, THREE.RedFormat);
|
|
||||||
gradientMap.minFilter = THREE.NearestFilter;
|
|
||||||
gradientMap.magFilter = THREE.NearestFilter;
|
|
||||||
gradientMap.needsUpdate = true;
|
|
||||||
```
|
|
||||||
|
|
||||||
## MeshNormalMaterial
|
|
||||||
|
|
||||||
Visualize surface normals. Useful for debugging.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const material = new THREE.MeshNormalMaterial({
|
|
||||||
flatShading: false,
|
|
||||||
wireframe: false,
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## MeshDepthMaterial
|
|
||||||
|
|
||||||
Render depth values. Used for shadow maps, DOF effects.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const material = new THREE.MeshDepthMaterial({
|
|
||||||
depthPacking: THREE.RGBADepthPacking,
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## PointsMaterial
|
|
||||||
|
|
||||||
For point clouds.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const material = new THREE.PointsMaterial({
|
|
||||||
color: 0xffffff,
|
|
||||||
size: 0.1,
|
|
||||||
sizeAttenuation: true, // Scale with distance
|
|
||||||
map: pointTexture,
|
|
||||||
alphaMap: alphaTexture,
|
|
||||||
transparent: true,
|
|
||||||
alphaTest: 0.5, // Discard pixels below threshold
|
|
||||||
vertexColors: true, // Use per-vertex colors
|
|
||||||
});
|
|
||||||
|
|
||||||
const points = new THREE.Points(geometry, material);
|
|
||||||
```
|
|
||||||
|
|
||||||
## LineBasicMaterial & LineDashedMaterial
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Solid lines
|
|
||||||
const lineMaterial = new THREE.LineBasicMaterial({
|
|
||||||
color: 0xffffff,
|
|
||||||
linewidth: 1, // Note: >1 only works on some systems
|
|
||||||
linecap: "round",
|
|
||||||
linejoin: "round",
|
|
||||||
});
|
|
||||||
|
|
||||||
// Dashed lines
|
|
||||||
const dashedMaterial = new THREE.LineDashedMaterial({
|
|
||||||
color: 0xffffff,
|
|
||||||
dashSize: 0.5,
|
|
||||||
gapSize: 0.25,
|
|
||||||
scale: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Required for dashed lines
|
|
||||||
const line = new THREE.Line(geometry, dashedMaterial);
|
|
||||||
line.computeLineDistances();
|
|
||||||
```
|
|
||||||
|
|
||||||
## ShaderMaterial
|
|
||||||
|
|
||||||
Custom GLSL shaders with Three.js uniforms.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const material = new THREE.ShaderMaterial({
|
|
||||||
uniforms: {
|
|
||||||
time: { value: 0 },
|
|
||||||
color: { value: new THREE.Color(0xff0000) },
|
|
||||||
texture1: { value: texture },
|
|
||||||
},
|
|
||||||
vertexShader: `
|
|
||||||
varying vec2 vUv;
|
|
||||||
uniform float time;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vUv = uv;
|
|
||||||
vec3 pos = position;
|
|
||||||
pos.z += sin(pos.x * 10.0 + time) * 0.1;
|
|
||||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
fragmentShader: `
|
|
||||||
varying vec2 vUv;
|
|
||||||
uniform vec3 color;
|
|
||||||
uniform sampler2D texture1;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
// Use texture2D() for GLSL 1.0, texture() for GLSL 3.0 (glslVersion: THREE.GLSL3)
|
|
||||||
vec4 texColor = texture2D(texture1, vUv);
|
|
||||||
gl_FragColor = vec4(color * texColor.rgb, 1.0);
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
transparent: true,
|
|
||||||
side: THREE.DoubleSide,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Update uniform in animation loop
|
|
||||||
material.uniforms.time.value = clock.getElapsedTime();
|
|
||||||
```
|
|
||||||
|
|
||||||
### Built-in Uniforms (auto-provided)
|
|
||||||
|
|
||||||
```glsl
|
|
||||||
// Vertex shader
|
|
||||||
uniform mat4 modelMatrix; // Object to world
|
|
||||||
uniform mat4 modelViewMatrix; // Object to camera
|
|
||||||
uniform mat4 projectionMatrix; // Camera projection
|
|
||||||
uniform mat4 viewMatrix; // World to camera
|
|
||||||
uniform mat3 normalMatrix; // For transforming normals
|
|
||||||
uniform vec3 cameraPosition; // Camera world position
|
|
||||||
|
|
||||||
// Attributes
|
|
||||||
attribute vec3 position;
|
|
||||||
attribute vec3 normal;
|
|
||||||
attribute vec2 uv;
|
|
||||||
```
|
|
||||||
|
|
||||||
## RawShaderMaterial
|
|
||||||
|
|
||||||
Full control - no built-in uniforms/attributes.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const material = new THREE.RawShaderMaterial({
|
|
||||||
uniforms: {
|
|
||||||
projectionMatrix: { value: camera.projectionMatrix },
|
|
||||||
modelViewMatrix: { value: new THREE.Matrix4() },
|
|
||||||
},
|
|
||||||
vertexShader: `
|
|
||||||
precision highp float;
|
|
||||||
attribute vec3 position;
|
|
||||||
uniform mat4 projectionMatrix;
|
|
||||||
uniform mat4 modelViewMatrix;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
fragmentShader: `
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Common Material Properties
|
|
||||||
|
|
||||||
All materials share these base properties:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Visibility
|
|
||||||
material.visible = true;
|
|
||||||
material.transparent = false;
|
|
||||||
material.opacity = 1.0;
|
|
||||||
material.alphaTest = 0; // Discard pixels with alpha < value
|
|
||||||
|
|
||||||
// Rendering
|
|
||||||
material.side = THREE.FrontSide; // FrontSide, BackSide, DoubleSide
|
|
||||||
material.depthTest = true;
|
|
||||||
material.depthWrite = true;
|
|
||||||
material.colorWrite = true;
|
|
||||||
|
|
||||||
// Blending
|
|
||||||
material.blending = THREE.NormalBlending;
|
|
||||||
// NormalBlending, AdditiveBlending, SubtractiveBlending, MultiplyBlending, CustomBlending
|
|
||||||
|
|
||||||
// Stencil
|
|
||||||
material.stencilWrite = false;
|
|
||||||
material.stencilFunc = THREE.AlwaysStencilFunc;
|
|
||||||
material.stencilRef = 0;
|
|
||||||
material.stencilMask = 0xff;
|
|
||||||
|
|
||||||
// Polygon offset (z-fighting fix)
|
|
||||||
material.polygonOffset = false;
|
|
||||||
material.polygonOffsetFactor = 0;
|
|
||||||
material.polygonOffsetUnits = 0;
|
|
||||||
|
|
||||||
// Misc
|
|
||||||
material.dithering = false;
|
|
||||||
material.toneMapped = true;
|
|
||||||
```
|
|
||||||
|
|
||||||
## Multiple Materials
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Assign different materials to geometry groups
|
|
||||||
const geometry = new THREE.BoxGeometry(1, 1, 1);
|
|
||||||
const materials = [
|
|
||||||
new THREE.MeshBasicMaterial({ color: 0xff0000 }), // right
|
|
||||||
new THREE.MeshBasicMaterial({ color: 0x00ff00 }), // left
|
|
||||||
new THREE.MeshBasicMaterial({ color: 0x0000ff }), // top
|
|
||||||
new THREE.MeshBasicMaterial({ color: 0xffff00 }), // bottom
|
|
||||||
new THREE.MeshBasicMaterial({ color: 0xff00ff }), // front
|
|
||||||
new THREE.MeshBasicMaterial({ color: 0x00ffff }), // back
|
|
||||||
];
|
|
||||||
const mesh = new THREE.Mesh(geometry, materials);
|
|
||||||
|
|
||||||
// Custom groups
|
|
||||||
geometry.clearGroups();
|
|
||||||
geometry.addGroup(0, 6, 0); // start, count, materialIndex
|
|
||||||
geometry.addGroup(6, 6, 1);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Environment Maps
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Load cube texture
|
|
||||||
const cubeLoader = new THREE.CubeTextureLoader();
|
|
||||||
const envMap = cubeLoader.load([
|
|
||||||
"px.jpg",
|
|
||||||
"nx.jpg", // positive/negative X
|
|
||||||
"py.jpg",
|
|
||||||
"ny.jpg", // positive/negative Y
|
|
||||||
"pz.jpg",
|
|
||||||
"nz.jpg", // positive/negative Z
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Apply to material
|
|
||||||
material.envMap = envMap;
|
|
||||||
material.envMapIntensity = 1;
|
|
||||||
|
|
||||||
// Or set as scene environment (affects all PBR materials)
|
|
||||||
scene.environment = envMap;
|
|
||||||
|
|
||||||
// HDR environment (recommended)
|
|
||||||
import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js";
|
|
||||||
const rgbeLoader = new RGBELoader();
|
|
||||||
rgbeLoader.load("environment.hdr", (texture) => {
|
|
||||||
texture.mapping = THREE.EquirectangularReflectionMapping;
|
|
||||||
scene.environment = texture;
|
|
||||||
scene.background = texture;
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Material Cloning and Modification
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Clone material
|
|
||||||
const clone = material.clone();
|
|
||||||
clone.color.set(0x00ff00);
|
|
||||||
|
|
||||||
// Modify at runtime
|
|
||||||
material.color.set(0xff0000);
|
|
||||||
material.needsUpdate = true; // Only needed for some changes
|
|
||||||
|
|
||||||
// When needsUpdate is required:
|
|
||||||
// - Changing flat shading
|
|
||||||
// - Changing texture
|
|
||||||
// - Changing transparent
|
|
||||||
// - Custom shader code changes
|
|
||||||
```
|
|
||||||
|
|
||||||
## Performance Tips
|
|
||||||
|
|
||||||
1. **Reuse materials**: Same material = batched draw calls
|
|
||||||
2. **Avoid transparent when possible**: Transparent materials require sorting
|
|
||||||
3. **Use alphaTest instead of transparency**: When applicable, faster
|
|
||||||
4. **Choose simpler materials**: Basic > Lambert > Phong > Standard > Physical
|
|
||||||
5. **Limit active lights**: Each light adds shader complexity
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Material pooling
|
|
||||||
const materialCache = new Map();
|
|
||||||
function getMaterial(color) {
|
|
||||||
const key = color.toString(16);
|
|
||||||
if (!materialCache.has(key)) {
|
|
||||||
materialCache.set(key, new THREE.MeshStandardMaterial({ color }));
|
|
||||||
}
|
|
||||||
return materialCache.get(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dispose when done
|
|
||||||
material.dispose();
|
|
||||||
```
|
|
||||||
|
|
||||||
## See Also
|
|
||||||
|
|
||||||
- `threejs-textures` - Texture loading and configuration
|
|
||||||
- `threejs-shaders` - Custom shader development
|
|
||||||
- `threejs-lighting` - Light interaction with materials
|
|
||||||
@@ -1,602 +0,0 @@
|
|||||||
---
|
|
||||||
name: threejs-postprocessing
|
|
||||||
description: "Three.js 后处理,用于 EffectComposer、bloom、景深、模糊、调色、屏幕空间效果和自定义 pass。"
|
|
||||||
---
|
|
||||||
|
|
||||||
# Three.js Post-Processing
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import * as THREE from "three";
|
|
||||||
import { EffectComposer } from "three/addons/postprocessing/EffectComposer.js";
|
|
||||||
import { RenderPass } from "three/addons/postprocessing/RenderPass.js";
|
|
||||||
import { UnrealBloomPass } from "three/addons/postprocessing/UnrealBloomPass.js";
|
|
||||||
|
|
||||||
// Setup composer
|
|
||||||
const composer = new EffectComposer(renderer);
|
|
||||||
|
|
||||||
// Render scene
|
|
||||||
const renderPass = new RenderPass(scene, camera);
|
|
||||||
composer.addPass(renderPass);
|
|
||||||
|
|
||||||
// Add bloom
|
|
||||||
const bloomPass = new UnrealBloomPass(
|
|
||||||
new THREE.Vector2(window.innerWidth, window.innerHeight),
|
|
||||||
1.5, // strength
|
|
||||||
0.4, // radius
|
|
||||||
0.85, // threshold
|
|
||||||
);
|
|
||||||
composer.addPass(bloomPass);
|
|
||||||
|
|
||||||
// Animation loop - use composer instead of renderer
|
|
||||||
function animate() {
|
|
||||||
requestAnimationFrame(animate);
|
|
||||||
composer.render(); // NOT renderer.render()
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## EffectComposer Setup
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { EffectComposer } from "three/addons/postprocessing/EffectComposer.js";
|
|
||||||
import { RenderPass } from "three/addons/postprocessing/RenderPass.js";
|
|
||||||
|
|
||||||
const composer = new EffectComposer(renderer);
|
|
||||||
|
|
||||||
// First pass: render scene
|
|
||||||
const renderPass = new RenderPass(scene, camera);
|
|
||||||
composer.addPass(renderPass);
|
|
||||||
|
|
||||||
// Add more passes...
|
|
||||||
composer.addPass(effectPass);
|
|
||||||
|
|
||||||
// Last pass should render to screen
|
|
||||||
effectPass.renderToScreen = true; // Default for last pass
|
|
||||||
|
|
||||||
// Handle resize
|
|
||||||
function onResize() {
|
|
||||||
const width = window.innerWidth;
|
|
||||||
const height = window.innerHeight;
|
|
||||||
|
|
||||||
camera.aspect = width / height;
|
|
||||||
camera.updateProjectionMatrix();
|
|
||||||
|
|
||||||
renderer.setSize(width, height);
|
|
||||||
composer.setSize(width, height);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Common Effects
|
|
||||||
|
|
||||||
### Bloom (Glow)
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { UnrealBloomPass } from "three/addons/postprocessing/UnrealBloomPass.js";
|
|
||||||
|
|
||||||
const bloomPass = new UnrealBloomPass(
|
|
||||||
new THREE.Vector2(window.innerWidth, window.innerHeight),
|
|
||||||
1.5, // strength - intensity of glow
|
|
||||||
0.4, // radius - spread of glow
|
|
||||||
0.85, // threshold - brightness threshold
|
|
||||||
);
|
|
||||||
|
|
||||||
composer.addPass(bloomPass);
|
|
||||||
|
|
||||||
// Adjust at runtime
|
|
||||||
bloomPass.strength = 2.0;
|
|
||||||
bloomPass.threshold = 0.5;
|
|
||||||
bloomPass.radius = 0.8;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Selective Bloom
|
|
||||||
|
|
||||||
Apply bloom only to specific objects.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { UnrealBloomPass } from "three/addons/postprocessing/UnrealBloomPass.js";
|
|
||||||
import { ShaderPass } from "three/addons/postprocessing/ShaderPass.js";
|
|
||||||
|
|
||||||
// Layer setup
|
|
||||||
const BLOOM_LAYER = 1;
|
|
||||||
const bloomLayer = new THREE.Layers();
|
|
||||||
bloomLayer.set(BLOOM_LAYER);
|
|
||||||
|
|
||||||
// Mark objects to bloom
|
|
||||||
glowingMesh.layers.enable(BLOOM_LAYER);
|
|
||||||
|
|
||||||
// Dark material for non-blooming objects
|
|
||||||
const darkMaterial = new THREE.MeshBasicMaterial({ color: 0x000000 });
|
|
||||||
const materials = {};
|
|
||||||
|
|
||||||
function darkenNonBloomed(obj) {
|
|
||||||
if (obj.isMesh && !bloomLayer.test(obj.layers)) {
|
|
||||||
materials[obj.uuid] = obj.material;
|
|
||||||
obj.material = darkMaterial;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function restoreMaterial(obj) {
|
|
||||||
if (materials[obj.uuid]) {
|
|
||||||
obj.material = materials[obj.uuid];
|
|
||||||
delete materials[obj.uuid];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Custom render loop
|
|
||||||
function render() {
|
|
||||||
// Render bloom pass
|
|
||||||
scene.traverse(darkenNonBloomed);
|
|
||||||
composer.render();
|
|
||||||
scene.traverse(restoreMaterial);
|
|
||||||
|
|
||||||
// Render final scene over bloom
|
|
||||||
renderer.render(scene, camera);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### FXAA (Anti-Aliasing)
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { ShaderPass } from "three/addons/postprocessing/ShaderPass.js";
|
|
||||||
import { FXAAShader } from "three/addons/shaders/FXAAShader.js";
|
|
||||||
|
|
||||||
const fxaaPass = new ShaderPass(FXAAShader);
|
|
||||||
fxaaPass.material.uniforms["resolution"].value.set(
|
|
||||||
1 / window.innerWidth,
|
|
||||||
1 / window.innerHeight,
|
|
||||||
);
|
|
||||||
|
|
||||||
composer.addPass(fxaaPass);
|
|
||||||
|
|
||||||
// Update on resize
|
|
||||||
function onResize() {
|
|
||||||
fxaaPass.material.uniforms["resolution"].value.set(
|
|
||||||
1 / window.innerWidth,
|
|
||||||
1 / window.innerHeight,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### SMAA (Better Anti-Aliasing)
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { SMAAPass } from "three/addons/postprocessing/SMAAPass.js";
|
|
||||||
|
|
||||||
const smaaPass = new SMAAPass(
|
|
||||||
window.innerWidth * renderer.getPixelRatio(),
|
|
||||||
window.innerHeight * renderer.getPixelRatio(),
|
|
||||||
);
|
|
||||||
|
|
||||||
composer.addPass(smaaPass);
|
|
||||||
```
|
|
||||||
|
|
||||||
### SSAO (Ambient Occlusion)
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { SSAOPass } from "three/addons/postprocessing/SSAOPass.js";
|
|
||||||
|
|
||||||
const ssaoPass = new SSAOPass(
|
|
||||||
scene,
|
|
||||||
camera,
|
|
||||||
window.innerWidth,
|
|
||||||
window.innerHeight,
|
|
||||||
);
|
|
||||||
ssaoPass.kernelRadius = 16;
|
|
||||||
ssaoPass.minDistance = 0.005;
|
|
||||||
ssaoPass.maxDistance = 0.1;
|
|
||||||
|
|
||||||
composer.addPass(ssaoPass);
|
|
||||||
|
|
||||||
// Output modes
|
|
||||||
ssaoPass.output = SSAOPass.OUTPUT.Default;
|
|
||||||
// SSAOPass.OUTPUT.Default - Final composited output
|
|
||||||
// SSAOPass.OUTPUT.SSAO - Just the AO
|
|
||||||
// SSAOPass.OUTPUT.Blur - Blurred AO
|
|
||||||
// SSAOPass.OUTPUT.Depth - Depth buffer
|
|
||||||
// SSAOPass.OUTPUT.Normal - Normal buffer
|
|
||||||
```
|
|
||||||
|
|
||||||
### Depth of Field (DOF)
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { BokehPass } from "three/addons/postprocessing/BokehPass.js";
|
|
||||||
|
|
||||||
const bokehPass = new BokehPass(scene, camera, {
|
|
||||||
focus: 10.0, // Focus distance
|
|
||||||
aperture: 0.025, // Aperture (smaller = more DOF)
|
|
||||||
maxblur: 0.01, // Max blur amount
|
|
||||||
});
|
|
||||||
|
|
||||||
composer.addPass(bokehPass);
|
|
||||||
|
|
||||||
// Update focus dynamically
|
|
||||||
bokehPass.uniforms["focus"].value = distanceToTarget;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Film Grain
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { FilmPass } from "three/addons/postprocessing/FilmPass.js";
|
|
||||||
|
|
||||||
const filmPass = new FilmPass(
|
|
||||||
0.35, // noise intensity
|
|
||||||
0.5, // scanline intensity
|
|
||||||
648, // scanline count
|
|
||||||
false, // grayscale
|
|
||||||
);
|
|
||||||
|
|
||||||
composer.addPass(filmPass);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Vignette
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { ShaderPass } from "three/addons/postprocessing/ShaderPass.js";
|
|
||||||
import { VignetteShader } from "three/addons/shaders/VignetteShader.js";
|
|
||||||
|
|
||||||
const vignettePass = new ShaderPass(VignetteShader);
|
|
||||||
vignettePass.uniforms["offset"].value = 1.0; // Vignette size
|
|
||||||
vignettePass.uniforms["darkness"].value = 1.0; // Vignette intensity
|
|
||||||
|
|
||||||
composer.addPass(vignettePass);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Color Correction
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { ShaderPass } from "three/addons/postprocessing/ShaderPass.js";
|
|
||||||
import { ColorCorrectionShader } from "three/addons/shaders/ColorCorrectionShader.js";
|
|
||||||
|
|
||||||
const colorPass = new ShaderPass(ColorCorrectionShader);
|
|
||||||
colorPass.uniforms["powRGB"].value = new THREE.Vector3(1.2, 1.2, 1.2); // Power
|
|
||||||
colorPass.uniforms["mulRGB"].value = new THREE.Vector3(1.0, 1.0, 1.0); // Multiply
|
|
||||||
|
|
||||||
composer.addPass(colorPass);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Gamma Correction
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { GammaCorrectionShader } from "three/addons/shaders/GammaCorrectionShader.js";
|
|
||||||
|
|
||||||
const gammaPass = new ShaderPass(GammaCorrectionShader);
|
|
||||||
composer.addPass(gammaPass);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Pixelation
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { RenderPixelatedPass } from "three/addons/postprocessing/RenderPixelatedPass.js";
|
|
||||||
|
|
||||||
const pixelPass = new RenderPixelatedPass(6, scene, camera); // 6 = pixel size
|
|
||||||
|
|
||||||
composer.addPass(pixelPass);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Glitch Effect
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { GlitchPass } from "three/addons/postprocessing/GlitchPass.js";
|
|
||||||
|
|
||||||
const glitchPass = new GlitchPass();
|
|
||||||
glitchPass.goWild = false; // Continuous glitching
|
|
||||||
|
|
||||||
composer.addPass(glitchPass);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Halftone
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { HalftonePass } from "three/addons/postprocessing/HalftonePass.js";
|
|
||||||
|
|
||||||
const halftonePass = new HalftonePass(window.innerWidth, window.innerHeight, {
|
|
||||||
shape: 1, // 1 = dot, 2 = ellipse, 3 = line, 4 = square
|
|
||||||
radius: 4, // Dot size
|
|
||||||
rotateR: Math.PI / 12,
|
|
||||||
rotateB: (Math.PI / 12) * 2,
|
|
||||||
rotateG: (Math.PI / 12) * 3,
|
|
||||||
scatter: 0,
|
|
||||||
blending: 1,
|
|
||||||
blendingMode: 1,
|
|
||||||
greyscale: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
composer.addPass(halftonePass);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Outline
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { OutlinePass } from "three/addons/postprocessing/OutlinePass.js";
|
|
||||||
|
|
||||||
const outlinePass = new OutlinePass(
|
|
||||||
new THREE.Vector2(window.innerWidth, window.innerHeight),
|
|
||||||
scene,
|
|
||||||
camera,
|
|
||||||
);
|
|
||||||
|
|
||||||
outlinePass.edgeStrength = 3;
|
|
||||||
outlinePass.edgeGlow = 0;
|
|
||||||
outlinePass.edgeThickness = 1;
|
|
||||||
outlinePass.pulsePeriod = 0;
|
|
||||||
outlinePass.visibleEdgeColor.set(0xffffff);
|
|
||||||
outlinePass.hiddenEdgeColor.set(0x190a05);
|
|
||||||
|
|
||||||
// Select objects to outline
|
|
||||||
outlinePass.selectedObjects = [mesh1, mesh2];
|
|
||||||
|
|
||||||
composer.addPass(outlinePass);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Custom ShaderPass
|
|
||||||
|
|
||||||
Create your own post-processing effects.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { ShaderPass } from "three/addons/postprocessing/ShaderPass.js";
|
|
||||||
|
|
||||||
const CustomShader = {
|
|
||||||
uniforms: {
|
|
||||||
tDiffuse: { value: null }, // Required: input texture
|
|
||||||
time: { value: 0 },
|
|
||||||
intensity: { value: 1.0 },
|
|
||||||
},
|
|
||||||
vertexShader: `
|
|
||||||
varying vec2 vUv;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vUv = uv;
|
|
||||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
fragmentShader: `
|
|
||||||
uniform sampler2D tDiffuse;
|
|
||||||
uniform float time;
|
|
||||||
uniform float intensity;
|
|
||||||
varying vec2 vUv;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec2 uv = vUv;
|
|
||||||
|
|
||||||
// Wave distortion
|
|
||||||
uv.x += sin(uv.y * 10.0 + time) * 0.01 * intensity;
|
|
||||||
|
|
||||||
vec4 color = texture2D(tDiffuse, uv);
|
|
||||||
gl_FragColor = color;
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
};
|
|
||||||
|
|
||||||
const customPass = new ShaderPass(CustomShader);
|
|
||||||
composer.addPass(customPass);
|
|
||||||
|
|
||||||
// Update in animation loop
|
|
||||||
customPass.uniforms.time.value = clock.getElapsedTime();
|
|
||||||
```
|
|
||||||
|
|
||||||
### Invert Colors Shader
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const InvertShader = {
|
|
||||||
uniforms: {
|
|
||||||
tDiffuse: { value: null },
|
|
||||||
},
|
|
||||||
vertexShader: `
|
|
||||||
varying vec2 vUv;
|
|
||||||
void main() {
|
|
||||||
vUv = uv;
|
|
||||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
fragmentShader: `
|
|
||||||
uniform sampler2D tDiffuse;
|
|
||||||
varying vec2 vUv;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec4 color = texture2D(tDiffuse, vUv);
|
|
||||||
gl_FragColor = vec4(1.0 - color.rgb, color.a);
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### Chromatic Aberration
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const ChromaticAberrationShader = {
|
|
||||||
uniforms: {
|
|
||||||
tDiffuse: { value: null },
|
|
||||||
amount: { value: 0.005 },
|
|
||||||
},
|
|
||||||
vertexShader: `
|
|
||||||
varying vec2 vUv;
|
|
||||||
void main() {
|
|
||||||
vUv = uv;
|
|
||||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
fragmentShader: `
|
|
||||||
uniform sampler2D tDiffuse;
|
|
||||||
uniform float amount;
|
|
||||||
varying vec2 vUv;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec2 dir = vUv - 0.5;
|
|
||||||
float dist = length(dir);
|
|
||||||
|
|
||||||
float r = texture2D(tDiffuse, vUv - dir * amount * dist).r;
|
|
||||||
float g = texture2D(tDiffuse, vUv).g;
|
|
||||||
float b = texture2D(tDiffuse, vUv + dir * amount * dist).b;
|
|
||||||
|
|
||||||
gl_FragColor = vec4(r, g, b, 1.0);
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
## Combining Multiple Effects
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { EffectComposer } from "three/addons/postprocessing/EffectComposer.js";
|
|
||||||
import { RenderPass } from "three/addons/postprocessing/RenderPass.js";
|
|
||||||
import { UnrealBloomPass } from "three/addons/postprocessing/UnrealBloomPass.js";
|
|
||||||
import { ShaderPass } from "three/addons/postprocessing/ShaderPass.js";
|
|
||||||
import { FXAAShader } from "three/addons/shaders/FXAAShader.js";
|
|
||||||
import { VignetteShader } from "three/addons/shaders/VignetteShader.js";
|
|
||||||
import { GammaCorrectionShader } from "three/addons/shaders/GammaCorrectionShader.js";
|
|
||||||
|
|
||||||
const composer = new EffectComposer(renderer);
|
|
||||||
|
|
||||||
// 1. Render scene
|
|
||||||
composer.addPass(new RenderPass(scene, camera));
|
|
||||||
|
|
||||||
// 2. Bloom
|
|
||||||
const bloomPass = new UnrealBloomPass(
|
|
||||||
new THREE.Vector2(window.innerWidth, window.innerHeight),
|
|
||||||
0.5,
|
|
||||||
0.4,
|
|
||||||
0.85,
|
|
||||||
);
|
|
||||||
composer.addPass(bloomPass);
|
|
||||||
|
|
||||||
// 3. Vignette
|
|
||||||
const vignettePass = new ShaderPass(VignetteShader);
|
|
||||||
vignettePass.uniforms["offset"].value = 0.95;
|
|
||||||
vignettePass.uniforms["darkness"].value = 1.0;
|
|
||||||
composer.addPass(vignettePass);
|
|
||||||
|
|
||||||
// 4. Gamma correction
|
|
||||||
composer.addPass(new ShaderPass(GammaCorrectionShader));
|
|
||||||
|
|
||||||
// 5. Anti-aliasing (always last before output)
|
|
||||||
const fxaaPass = new ShaderPass(FXAAShader);
|
|
||||||
fxaaPass.uniforms["resolution"].value.set(
|
|
||||||
1 / window.innerWidth,
|
|
||||||
1 / window.innerHeight,
|
|
||||||
);
|
|
||||||
composer.addPass(fxaaPass);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Render to Texture
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Create render target
|
|
||||||
const renderTarget = new THREE.WebGLRenderTarget(512, 512);
|
|
||||||
|
|
||||||
// Render scene to target
|
|
||||||
renderer.setRenderTarget(renderTarget);
|
|
||||||
renderer.render(scene, camera);
|
|
||||||
renderer.setRenderTarget(null);
|
|
||||||
|
|
||||||
// Use texture
|
|
||||||
const texture = renderTarget.texture;
|
|
||||||
otherMaterial.map = texture;
|
|
||||||
```
|
|
||||||
|
|
||||||
## Multi-Pass Rendering
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Multiple composers for different scenes/layers
|
|
||||||
const bgComposer = new EffectComposer(renderer);
|
|
||||||
bgComposer.addPass(new RenderPass(bgScene, camera));
|
|
||||||
|
|
||||||
const fgComposer = new EffectComposer(renderer);
|
|
||||||
fgComposer.addPass(new RenderPass(fgScene, camera));
|
|
||||||
fgComposer.addPass(bloomPass);
|
|
||||||
|
|
||||||
// Combine in render loop
|
|
||||||
function animate() {
|
|
||||||
// Render background without clearing
|
|
||||||
renderer.autoClear = false;
|
|
||||||
renderer.clear();
|
|
||||||
|
|
||||||
bgComposer.render();
|
|
||||||
|
|
||||||
// Render foreground over it
|
|
||||||
renderer.clearDepth();
|
|
||||||
fgComposer.render();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## WebGPU Post-Processing (Three.js r150+)
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { postProcessing } from "three/addons/nodes/Nodes.js";
|
|
||||||
import { pass, bloom, dof } from "three/addons/nodes/Nodes.js";
|
|
||||||
|
|
||||||
// Using node-based system
|
|
||||||
const scenePass = pass(scene, camera);
|
|
||||||
const bloomNode = bloom(scenePass, 0.5, 0.4, 0.85);
|
|
||||||
|
|
||||||
const postProcessing = new THREE.PostProcessing(renderer);
|
|
||||||
postProcessing.outputNode = bloomNode;
|
|
||||||
|
|
||||||
// Render
|
|
||||||
function animate() {
|
|
||||||
postProcessing.render();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Performance Tips
|
|
||||||
|
|
||||||
1. **Limit passes**: Each pass adds a full-screen render
|
|
||||||
2. **Lower resolution**: Use smaller render targets for blur passes
|
|
||||||
3. **Disable unused effects**: Toggle passes on/off
|
|
||||||
4. **Use FXAA over MSAA**: Less expensive anti-aliasing
|
|
||||||
5. **Profile with DevTools**: Check GPU usage
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Disable pass
|
|
||||||
bloomPass.enabled = false;
|
|
||||||
|
|
||||||
// Reduce bloom resolution
|
|
||||||
const bloomPass = new UnrealBloomPass(
|
|
||||||
new THREE.Vector2(window.innerWidth / 2, window.innerHeight / 2),
|
|
||||||
strength,
|
|
||||||
radius,
|
|
||||||
threshold,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Only apply effects in high-performance scenarios
|
|
||||||
const isMobile = /iPhone|iPad|Android/i.test(navigator.userAgent);
|
|
||||||
if (!isMobile) {
|
|
||||||
composer.addPass(expensivePass);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Handle Resize
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function onWindowResize() {
|
|
||||||
const width = window.innerWidth;
|
|
||||||
const height = window.innerHeight;
|
|
||||||
const pixelRatio = renderer.getPixelRatio();
|
|
||||||
|
|
||||||
camera.aspect = width / height;
|
|
||||||
camera.updateProjectionMatrix();
|
|
||||||
|
|
||||||
renderer.setSize(width, height);
|
|
||||||
composer.setSize(width, height);
|
|
||||||
|
|
||||||
// Update pass-specific resolutions
|
|
||||||
if (fxaaPass) {
|
|
||||||
fxaaPass.material.uniforms["resolution"].value.set(
|
|
||||||
1 / (width * pixelRatio),
|
|
||||||
1 / (height * pixelRatio),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bloomPass) {
|
|
||||||
bloomPass.resolution.set(width, height);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener("resize", onWindowResize);
|
|
||||||
```
|
|
||||||
|
|
||||||
## See Also
|
|
||||||
|
|
||||||
- `threejs-shaders` - Custom shader development
|
|
||||||
- `threejs-textures` - Render targets
|
|
||||||
- `threejs-fundamentals` - Renderer setup
|
|
||||||
@@ -1,642 +0,0 @@
|
|||||||
---
|
|
||||||
name: threejs-shaders
|
|
||||||
description: "Three.js 着色器开发,用于 GLSL、ShaderMaterial、uniforms、自定义视觉效果、顶点修改和片元着色。"
|
|
||||||
---
|
|
||||||
|
|
||||||
# Three.js Shaders
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import * as THREE from "three";
|
|
||||||
|
|
||||||
const material = new THREE.ShaderMaterial({
|
|
||||||
uniforms: {
|
|
||||||
time: { value: 0 },
|
|
||||||
color: { value: new THREE.Color(0xff0000) },
|
|
||||||
},
|
|
||||||
vertexShader: `
|
|
||||||
void main() {
|
|
||||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
fragmentShader: `
|
|
||||||
uniform vec3 color;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_FragColor = vec4(color, 1.0);
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Update in animation loop
|
|
||||||
material.uniforms.time.value = clock.getElapsedTime();
|
|
||||||
```
|
|
||||||
|
|
||||||
## ShaderMaterial vs RawShaderMaterial
|
|
||||||
|
|
||||||
### ShaderMaterial
|
|
||||||
|
|
||||||
Three.js provides built-in uniforms and attributes.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const material = new THREE.ShaderMaterial({
|
|
||||||
vertexShader: `
|
|
||||||
// Built-in uniforms available:
|
|
||||||
// uniform mat4 modelMatrix;
|
|
||||||
// uniform mat4 modelViewMatrix;
|
|
||||||
// uniform mat4 projectionMatrix;
|
|
||||||
// uniform mat4 viewMatrix;
|
|
||||||
// uniform mat3 normalMatrix;
|
|
||||||
// uniform vec3 cameraPosition;
|
|
||||||
|
|
||||||
// Built-in attributes available:
|
|
||||||
// attribute vec3 position;
|
|
||||||
// attribute vec3 normal;
|
|
||||||
// attribute vec2 uv;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
fragmentShader: `
|
|
||||||
void main() {
|
|
||||||
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### RawShaderMaterial
|
|
||||||
|
|
||||||
Full control - you define everything.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const material = new THREE.RawShaderMaterial({
|
|
||||||
uniforms: {
|
|
||||||
projectionMatrix: { value: camera.projectionMatrix },
|
|
||||||
modelViewMatrix: { value: new THREE.Matrix4() },
|
|
||||||
},
|
|
||||||
vertexShader: `
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
attribute vec3 position;
|
|
||||||
uniform mat4 projectionMatrix;
|
|
||||||
uniform mat4 modelViewMatrix;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
fragmentShader: `
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Uniforms
|
|
||||||
|
|
||||||
### Uniform Types
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const material = new THREE.ShaderMaterial({
|
|
||||||
uniforms: {
|
|
||||||
// Numbers
|
|
||||||
floatValue: { value: 1.5 },
|
|
||||||
intValue: { value: 1 },
|
|
||||||
|
|
||||||
// Vectors
|
|
||||||
vec2Value: { value: new THREE.Vector2(1, 2) },
|
|
||||||
vec3Value: { value: new THREE.Vector3(1, 2, 3) },
|
|
||||||
vec4Value: { value: new THREE.Vector4(1, 2, 3, 4) },
|
|
||||||
|
|
||||||
// Colors (converted to vec3)
|
|
||||||
colorValue: { value: new THREE.Color(0xff0000) },
|
|
||||||
|
|
||||||
// Matrices
|
|
||||||
mat3Value: { value: new THREE.Matrix3() },
|
|
||||||
mat4Value: { value: new THREE.Matrix4() },
|
|
||||||
|
|
||||||
// Textures
|
|
||||||
textureValue: { value: texture },
|
|
||||||
cubeTextureValue: { value: cubeTexture },
|
|
||||||
|
|
||||||
// Arrays
|
|
||||||
floatArray: { value: [1.0, 2.0, 3.0] },
|
|
||||||
vec3Array: {
|
|
||||||
value: [new THREE.Vector3(1, 0, 0), new THREE.Vector3(0, 1, 0)],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### GLSL Declarations
|
|
||||||
|
|
||||||
```glsl
|
|
||||||
// In shader
|
|
||||||
uniform float floatValue;
|
|
||||||
uniform int intValue;
|
|
||||||
uniform vec2 vec2Value;
|
|
||||||
uniform vec3 vec3Value;
|
|
||||||
uniform vec3 colorValue; // Color becomes vec3
|
|
||||||
uniform vec4 vec4Value;
|
|
||||||
uniform mat3 mat3Value;
|
|
||||||
uniform mat4 mat4Value;
|
|
||||||
uniform sampler2D textureValue;
|
|
||||||
uniform samplerCube cubeTextureValue;
|
|
||||||
uniform float floatArray[3];
|
|
||||||
uniform vec3 vec3Array[2];
|
|
||||||
```
|
|
||||||
|
|
||||||
### Updating Uniforms
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Direct assignment
|
|
||||||
material.uniforms.time.value = clock.getElapsedTime();
|
|
||||||
|
|
||||||
// Vector/Color updates
|
|
||||||
material.uniforms.position.value.set(x, y, z);
|
|
||||||
material.uniforms.color.value.setHSL(hue, 1, 0.5);
|
|
||||||
|
|
||||||
// Matrix updates
|
|
||||||
material.uniforms.matrix.value.copy(mesh.matrixWorld);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Varyings
|
|
||||||
|
|
||||||
Pass data from vertex to fragment shader.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const material = new THREE.ShaderMaterial({
|
|
||||||
vertexShader: `
|
|
||||||
varying vec2 vUv;
|
|
||||||
varying vec3 vNormal;
|
|
||||||
varying vec3 vPosition;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vUv = uv;
|
|
||||||
vNormal = normalize(normalMatrix * normal);
|
|
||||||
vPosition = (modelViewMatrix * vec4(position, 1.0)).xyz;
|
|
||||||
|
|
||||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
fragmentShader: `
|
|
||||||
varying vec2 vUv;
|
|
||||||
varying vec3 vNormal;
|
|
||||||
varying vec3 vPosition;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
// Use interpolated values
|
|
||||||
gl_FragColor = vec4(vNormal * 0.5 + 0.5, 1.0);
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Common Shader Patterns
|
|
||||||
|
|
||||||
### Texture Sampling
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const material = new THREE.ShaderMaterial({
|
|
||||||
uniforms: {
|
|
||||||
map: { value: texture },
|
|
||||||
},
|
|
||||||
vertexShader: `
|
|
||||||
varying vec2 vUv;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vUv = uv;
|
|
||||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
fragmentShader: `
|
|
||||||
uniform sampler2D map;
|
|
||||||
varying vec2 vUv;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec4 texColor = texture2D(map, vUv);
|
|
||||||
gl_FragColor = texColor;
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Vertex Displacement
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const material = new THREE.ShaderMaterial({
|
|
||||||
uniforms: {
|
|
||||||
time: { value: 0 },
|
|
||||||
amplitude: { value: 0.5 },
|
|
||||||
},
|
|
||||||
vertexShader: `
|
|
||||||
uniform float time;
|
|
||||||
uniform float amplitude;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec3 pos = position;
|
|
||||||
|
|
||||||
// Wave displacement
|
|
||||||
pos.z += sin(pos.x * 5.0 + time) * amplitude;
|
|
||||||
pos.z += sin(pos.y * 5.0 + time) * amplitude;
|
|
||||||
|
|
||||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
fragmentShader: `
|
|
||||||
void main() {
|
|
||||||
gl_FragColor = vec4(0.5, 0.8, 1.0, 1.0);
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Fresnel Effect
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const material = new THREE.ShaderMaterial({
|
|
||||||
vertexShader: `
|
|
||||||
varying vec3 vNormal;
|
|
||||||
varying vec3 vWorldPosition;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vNormal = normalize(normalMatrix * normal);
|
|
||||||
vWorldPosition = (modelMatrix * vec4(position, 1.0)).xyz;
|
|
||||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
fragmentShader: `
|
|
||||||
varying vec3 vNormal;
|
|
||||||
varying vec3 vWorldPosition;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
// cameraPosition is auto-provided by ShaderMaterial
|
|
||||||
vec3 viewDirection = normalize(cameraPosition - vWorldPosition);
|
|
||||||
float fresnel = pow(1.0 - dot(viewDirection, vNormal), 3.0);
|
|
||||||
|
|
||||||
vec3 baseColor = vec3(0.0, 0.0, 0.5);
|
|
||||||
vec3 fresnelColor = vec3(0.5, 0.8, 1.0);
|
|
||||||
|
|
||||||
gl_FragColor = vec4(mix(baseColor, fresnelColor, fresnel), 1.0);
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Noise-Based Effects
|
|
||||||
|
|
||||||
```glsl
|
|
||||||
// Simple noise function
|
|
||||||
float random(vec2 st) {
|
|
||||||
return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Value noise
|
|
||||||
float noise(vec2 st) {
|
|
||||||
vec2 i = floor(st);
|
|
||||||
vec2 f = fract(st);
|
|
||||||
|
|
||||||
float a = random(i);
|
|
||||||
float b = random(i + vec2(1.0, 0.0));
|
|
||||||
float c = random(i + vec2(0.0, 1.0));
|
|
||||||
float d = random(i + vec2(1.0, 1.0));
|
|
||||||
|
|
||||||
vec2 u = f * f * (3.0 - 2.0 * f);
|
|
||||||
|
|
||||||
return mix(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Usage
|
|
||||||
float n = noise(vUv * 10.0 + time);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Gradient
|
|
||||||
|
|
||||||
```glsl
|
|
||||||
// Linear gradient
|
|
||||||
vec3 color = mix(colorA, colorB, vUv.y);
|
|
||||||
|
|
||||||
// Radial gradient
|
|
||||||
float dist = distance(vUv, vec2(0.5));
|
|
||||||
vec3 color = mix(centerColor, edgeColor, dist * 2.0);
|
|
||||||
|
|
||||||
// Smooth gradient with custom curve
|
|
||||||
float t = smoothstep(0.0, 1.0, vUv.y);
|
|
||||||
vec3 color = mix(colorA, colorB, t);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Rim Lighting
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const material = new THREE.ShaderMaterial({
|
|
||||||
vertexShader: `
|
|
||||||
varying vec3 vNormal;
|
|
||||||
varying vec3 vViewPosition;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vNormal = normalize(normalMatrix * normal);
|
|
||||||
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
|
|
||||||
vViewPosition = mvPosition.xyz;
|
|
||||||
gl_Position = projectionMatrix * mvPosition;
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
fragmentShader: `
|
|
||||||
varying vec3 vNormal;
|
|
||||||
varying vec3 vViewPosition;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec3 viewDir = normalize(-vViewPosition);
|
|
||||||
float rim = 1.0 - max(0.0, dot(viewDir, vNormal));
|
|
||||||
rim = pow(rim, 4.0);
|
|
||||||
|
|
||||||
vec3 baseColor = vec3(0.2, 0.2, 0.8);
|
|
||||||
vec3 rimColor = vec3(1.0, 0.5, 0.0);
|
|
||||||
|
|
||||||
gl_FragColor = vec4(baseColor + rimColor * rim, 1.0);
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Dissolve Effect
|
|
||||||
|
|
||||||
```glsl
|
|
||||||
uniform float progress;
|
|
||||||
uniform sampler2D noiseMap;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
float noise = texture2D(noiseMap, vUv).r;
|
|
||||||
|
|
||||||
if (noise < progress) {
|
|
||||||
discard;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Edge glow
|
|
||||||
float edge = smoothstep(progress, progress + 0.1, noise);
|
|
||||||
vec3 edgeColor = vec3(1.0, 0.5, 0.0);
|
|
||||||
vec3 baseColor = vec3(0.5);
|
|
||||||
|
|
||||||
gl_FragColor = vec4(mix(edgeColor, baseColor, edge), 1.0);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Extending Built-in Materials
|
|
||||||
|
|
||||||
### onBeforeCompile
|
|
||||||
|
|
||||||
Modify existing material shaders.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
|
|
||||||
|
|
||||||
material.onBeforeCompile = (shader) => {
|
|
||||||
// Add custom uniform
|
|
||||||
shader.uniforms.time = { value: 0 };
|
|
||||||
|
|
||||||
// Store reference for updates
|
|
||||||
material.userData.shader = shader;
|
|
||||||
|
|
||||||
// Modify vertex shader
|
|
||||||
shader.vertexShader = shader.vertexShader.replace(
|
|
||||||
"#include <begin_vertex>",
|
|
||||||
`
|
|
||||||
#include <begin_vertex>
|
|
||||||
transformed.y += sin(position.x * 10.0 + time) * 0.1;
|
|
||||||
`,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Add uniform declaration
|
|
||||||
shader.vertexShader = "uniform float time;\n" + shader.vertexShader;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Update in animation loop
|
|
||||||
if (material.userData.shader) {
|
|
||||||
material.userData.shader.uniforms.time.value = clock.getElapsedTime();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Common Injection Points
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Vertex shader chunks
|
|
||||||
"#include <begin_vertex>"; // After position is calculated
|
|
||||||
"#include <project_vertex>"; // After gl_Position
|
|
||||||
"#include <beginnormal_vertex>"; // Normal calculation start
|
|
||||||
|
|
||||||
// Fragment shader chunks
|
|
||||||
"#include <color_fragment>"; // After diffuse color
|
|
||||||
"#include <output_fragment>"; // Final output
|
|
||||||
"#include <fog_fragment>"; // After fog applied
|
|
||||||
```
|
|
||||||
|
|
||||||
## GLSL Built-in Functions
|
|
||||||
|
|
||||||
### Math Functions
|
|
||||||
|
|
||||||
```glsl
|
|
||||||
// Basic
|
|
||||||
abs(x), sign(x), floor(x), ceil(x), fract(x)
|
|
||||||
mod(x, y), min(x, y), max(x, y), clamp(x, min, max)
|
|
||||||
mix(a, b, t), step(edge, x), smoothstep(edge0, edge1, x)
|
|
||||||
|
|
||||||
// Trigonometry
|
|
||||||
sin(x), cos(x), tan(x)
|
|
||||||
asin(x), acos(x), atan(y, x), atan(x)
|
|
||||||
radians(degrees), degrees(radians)
|
|
||||||
|
|
||||||
// Exponential
|
|
||||||
pow(x, y), exp(x), log(x), exp2(x), log2(x)
|
|
||||||
sqrt(x), inversesqrt(x)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Vector Functions
|
|
||||||
|
|
||||||
```glsl
|
|
||||||
// Length and distance
|
|
||||||
length(v), distance(p0, p1), dot(x, y), cross(x, y)
|
|
||||||
|
|
||||||
// Normalization
|
|
||||||
normalize(v)
|
|
||||||
|
|
||||||
// Reflection and refraction
|
|
||||||
reflect(I, N), refract(I, N, eta)
|
|
||||||
|
|
||||||
// Component-wise
|
|
||||||
lessThan(x, y), lessThanEqual(x, y)
|
|
||||||
greaterThan(x, y), greaterThanEqual(x, y)
|
|
||||||
equal(x, y), notEqual(x, y)
|
|
||||||
any(bvec), all(bvec)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Texture Functions
|
|
||||||
|
|
||||||
```glsl
|
|
||||||
// GLSL 1.0 (default) - use texture2D/textureCube
|
|
||||||
texture2D(sampler, coord)
|
|
||||||
texture2D(sampler, coord, bias)
|
|
||||||
textureCube(sampler, coord)
|
|
||||||
|
|
||||||
// GLSL 3.0 (glslVersion: THREE.GLSL3) - use texture()
|
|
||||||
// texture(sampler, coord) replaces texture2D/textureCube
|
|
||||||
// Also use: out vec4 fragColor instead of gl_FragColor
|
|
||||||
|
|
||||||
// Texture size (GLSL 1.30+)
|
|
||||||
textureSize(sampler, lod)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Common Material Properties
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const material = new THREE.ShaderMaterial({
|
|
||||||
uniforms: {
|
|
||||||
/* ... */
|
|
||||||
},
|
|
||||||
vertexShader: "/* ... */",
|
|
||||||
fragmentShader: "/* ... */",
|
|
||||||
|
|
||||||
// Rendering
|
|
||||||
transparent: true,
|
|
||||||
opacity: 1.0,
|
|
||||||
side: THREE.DoubleSide,
|
|
||||||
depthTest: true,
|
|
||||||
depthWrite: true,
|
|
||||||
|
|
||||||
// Blending
|
|
||||||
blending: THREE.NormalBlending,
|
|
||||||
// AdditiveBlending, SubtractiveBlending, MultiplyBlending
|
|
||||||
|
|
||||||
// Wireframe
|
|
||||||
wireframe: false,
|
|
||||||
wireframeLinewidth: 1, // Note: >1 has no effect on most platforms (WebGL limitation)
|
|
||||||
|
|
||||||
// Extensions
|
|
||||||
extensions: {
|
|
||||||
derivatives: true, // For fwidth, dFdx, dFdy
|
|
||||||
fragDepth: true, // gl_FragDepth
|
|
||||||
drawBuffers: true, // Multiple render targets
|
|
||||||
shaderTextureLOD: true, // texture2DLod
|
|
||||||
},
|
|
||||||
|
|
||||||
// GLSL version
|
|
||||||
glslVersion: THREE.GLSL3, // For WebGL2 features
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Shader Includes
|
|
||||||
|
|
||||||
### Using Three.js Shader Chunks
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { ShaderChunk } from "three";
|
|
||||||
|
|
||||||
const fragmentShader = `
|
|
||||||
${ShaderChunk.common}
|
|
||||||
${ShaderChunk.packing}
|
|
||||||
|
|
||||||
uniform sampler2D depthTexture;
|
|
||||||
varying vec2 vUv;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
float depth = texture2D(depthTexture, vUv).r;
|
|
||||||
float linearDepth = perspectiveDepthToViewZ(depth, 0.1, 1000.0);
|
|
||||||
gl_FragColor = vec4(vec3(-linearDepth / 100.0), 1.0);
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
```
|
|
||||||
|
|
||||||
### External Shader Files
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// With vite/webpack
|
|
||||||
import vertexShader from "./shaders/vertex.glsl";
|
|
||||||
import fragmentShader from "./shaders/fragment.glsl";
|
|
||||||
|
|
||||||
const material = new THREE.ShaderMaterial({
|
|
||||||
vertexShader,
|
|
||||||
fragmentShader,
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Instanced Shaders
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Instanced attribute
|
|
||||||
const offsets = new Float32Array(instanceCount * 3);
|
|
||||||
// Fill offsets...
|
|
||||||
geometry.setAttribute("offset", new THREE.InstancedBufferAttribute(offsets, 3));
|
|
||||||
|
|
||||||
const material = new THREE.ShaderMaterial({
|
|
||||||
vertexShader: `
|
|
||||||
attribute vec3 offset;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec3 pos = position + offset;
|
|
||||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
fragmentShader: `
|
|
||||||
void main() {
|
|
||||||
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Debugging Shaders
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Check for compile errors
|
|
||||||
material.onBeforeCompile = (shader) => {
|
|
||||||
console.log("Vertex Shader:", shader.vertexShader);
|
|
||||||
console.log("Fragment Shader:", shader.fragmentShader);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Visual debugging
|
|
||||||
fragmentShader: `
|
|
||||||
void main() {
|
|
||||||
// Debug UV
|
|
||||||
gl_FragColor = vec4(vUv, 0.0, 1.0);
|
|
||||||
|
|
||||||
// Debug normals
|
|
||||||
gl_FragColor = vec4(vNormal * 0.5 + 0.5, 1.0);
|
|
||||||
|
|
||||||
// Debug position
|
|
||||||
gl_FragColor = vec4(vPosition * 0.1 + 0.5, 1.0);
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
// Check WebGL errors
|
|
||||||
renderer.debug.checkShaderErrors = true;
|
|
||||||
```
|
|
||||||
|
|
||||||
## Performance Tips
|
|
||||||
|
|
||||||
1. **Minimize uniforms**: Group related values into vectors
|
|
||||||
2. **Avoid conditionals**: Use mix/step instead of if/else
|
|
||||||
3. **Precalculate**: Move calculations to JS when possible
|
|
||||||
4. **Use textures**: For complex functions, use lookup tables
|
|
||||||
5. **Limit overdraw**: Avoid transparent objects when possible
|
|
||||||
|
|
||||||
```glsl
|
|
||||||
// Instead of:
|
|
||||||
if (value > 0.5) {
|
|
||||||
color = colorA;
|
|
||||||
} else {
|
|
||||||
color = colorB;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use:
|
|
||||||
color = mix(colorB, colorA, step(0.5, value));
|
|
||||||
```
|
|
||||||
|
|
||||||
## See Also
|
|
||||||
|
|
||||||
- `threejs-materials` - Built-in material types
|
|
||||||
- `threejs-postprocessing` - Full-screen shader effects
|
|
||||||
- `threejs-textures` - Texture sampling in shaders
|
|
||||||
@@ -1,628 +0,0 @@
|
|||||||
---
|
|
||||||
name: threejs-textures
|
|
||||||
description: "Three.js 纹理处理,用于图片纹理、UV、环境贴图、cubemap、HDR 环境和纹理加载优化。"
|
|
||||||
---
|
|
||||||
|
|
||||||
# Three.js Textures
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import * as THREE from "three";
|
|
||||||
|
|
||||||
// Load texture
|
|
||||||
const loader = new THREE.TextureLoader();
|
|
||||||
const texture = loader.load("texture.jpg");
|
|
||||||
|
|
||||||
// Apply to material
|
|
||||||
const material = new THREE.MeshStandardMaterial({
|
|
||||||
map: texture,
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Texture Loading
|
|
||||||
|
|
||||||
### Basic Loading
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const loader = new THREE.TextureLoader();
|
|
||||||
|
|
||||||
// Async with callbacks
|
|
||||||
loader.load(
|
|
||||||
"texture.jpg",
|
|
||||||
(texture) => console.log("Loaded"),
|
|
||||||
(progress) => console.log("Progress"),
|
|
||||||
(error) => console.error("Error"),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Synchronous style (loads async internally)
|
|
||||||
const texture = loader.load("texture.jpg");
|
|
||||||
material.map = texture;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Promise Wrapper
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function loadTexture(url) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
new THREE.TextureLoader().load(url, resolve, undefined, reject);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Usage
|
|
||||||
const [colorMap, normalMap, roughnessMap] = await Promise.all([
|
|
||||||
loadTexture("color.jpg"),
|
|
||||||
loadTexture("normal.jpg"),
|
|
||||||
loadTexture("roughness.jpg"),
|
|
||||||
]);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Texture Configuration
|
|
||||||
|
|
||||||
### Color Space
|
|
||||||
|
|
||||||
Critical for accurate color reproduction.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Color/albedo textures - use sRGB
|
|
||||||
colorTexture.colorSpace = THREE.SRGBColorSpace;
|
|
||||||
|
|
||||||
// Data textures (normal, roughness, metalness, AO) - leave as default
|
|
||||||
// Do NOT set colorSpace for data textures (NoColorSpace is default)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Wrapping Modes
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
texture.wrapS = THREE.RepeatWrapping; // Horizontal
|
|
||||||
texture.wrapT = THREE.RepeatWrapping; // Vertical
|
|
||||||
|
|
||||||
// Options:
|
|
||||||
// THREE.ClampToEdgeWrapping - Stretches edge pixels (default)
|
|
||||||
// THREE.RepeatWrapping - Tiles the texture
|
|
||||||
// THREE.MirroredRepeatWrapping - Tiles with mirror flip
|
|
||||||
```
|
|
||||||
|
|
||||||
### Repeat, Offset, Rotation
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Tile texture 4x4
|
|
||||||
texture.repeat.set(4, 4);
|
|
||||||
texture.wrapS = THREE.RepeatWrapping;
|
|
||||||
texture.wrapT = THREE.RepeatWrapping;
|
|
||||||
|
|
||||||
// Offset (0-1 range)
|
|
||||||
texture.offset.set(0.5, 0.5);
|
|
||||||
|
|
||||||
// Rotation (radians, around center)
|
|
||||||
texture.rotation = Math.PI / 4;
|
|
||||||
texture.center.set(0.5, 0.5); // Rotation pivot
|
|
||||||
```
|
|
||||||
|
|
||||||
### Filtering
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Minification (texture larger than screen pixels)
|
|
||||||
texture.minFilter = THREE.LinearMipmapLinearFilter; // Default, smooth
|
|
||||||
texture.minFilter = THREE.NearestFilter; // Pixelated
|
|
||||||
texture.minFilter = THREE.LinearFilter; // Smooth, no mipmaps
|
|
||||||
|
|
||||||
// Magnification (texture smaller than screen pixels)
|
|
||||||
texture.magFilter = THREE.LinearFilter; // Smooth (default)
|
|
||||||
texture.magFilter = THREE.NearestFilter; // Pixelated (retro games)
|
|
||||||
|
|
||||||
// Anisotropic filtering (sharper at angles)
|
|
||||||
texture.anisotropy = renderer.capabilities.getMaxAnisotropy();
|
|
||||||
```
|
|
||||||
|
|
||||||
### Generate Mipmaps
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Usually true by default
|
|
||||||
texture.generateMipmaps = true;
|
|
||||||
|
|
||||||
// Disable for non-power-of-2 textures or data textures
|
|
||||||
texture.generateMipmaps = false;
|
|
||||||
texture.minFilter = THREE.LinearFilter;
|
|
||||||
```
|
|
||||||
|
|
||||||
## Texture Types
|
|
||||||
|
|
||||||
### Regular Texture
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const texture = new THREE.Texture(image);
|
|
||||||
texture.needsUpdate = true;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Data Texture
|
|
||||||
|
|
||||||
Create texture from raw data.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Create gradient texture
|
|
||||||
const size = 256;
|
|
||||||
const data = new Uint8Array(size * size * 4);
|
|
||||||
|
|
||||||
for (let i = 0; i < size; i++) {
|
|
||||||
for (let j = 0; j < size; j++) {
|
|
||||||
const index = (i * size + j) * 4;
|
|
||||||
data[index] = i; // R
|
|
||||||
data[index + 1] = j; // G
|
|
||||||
data[index + 2] = 128; // B
|
|
||||||
data[index + 3] = 255; // A
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const texture = new THREE.DataTexture(data, size, size);
|
|
||||||
texture.needsUpdate = true;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Canvas Texture
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const canvas = document.createElement("canvas");
|
|
||||||
canvas.width = 256;
|
|
||||||
canvas.height = 256;
|
|
||||||
const ctx = canvas.getContext("2d");
|
|
||||||
|
|
||||||
// Draw on canvas
|
|
||||||
ctx.fillStyle = "red";
|
|
||||||
ctx.fillRect(0, 0, 256, 256);
|
|
||||||
ctx.fillStyle = "white";
|
|
||||||
ctx.font = "48px Arial";
|
|
||||||
ctx.fillText("Hello", 50, 150);
|
|
||||||
|
|
||||||
const texture = new THREE.CanvasTexture(canvas);
|
|
||||||
|
|
||||||
// Update when canvas changes
|
|
||||||
texture.needsUpdate = true;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Video Texture
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const video = document.createElement("video");
|
|
||||||
video.src = "video.mp4";
|
|
||||||
video.loop = true;
|
|
||||||
video.muted = true;
|
|
||||||
video.play();
|
|
||||||
|
|
||||||
const texture = new THREE.VideoTexture(video);
|
|
||||||
texture.colorSpace = THREE.SRGBColorSpace;
|
|
||||||
|
|
||||||
// No need to set needsUpdate - auto-updates
|
|
||||||
```
|
|
||||||
|
|
||||||
### Compressed Textures
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { KTX2Loader } from "three/examples/jsm/loaders/KTX2Loader.js";
|
|
||||||
|
|
||||||
const ktx2Loader = new KTX2Loader();
|
|
||||||
ktx2Loader.setTranscoderPath("path/to/basis/");
|
|
||||||
ktx2Loader.detectSupport(renderer);
|
|
||||||
|
|
||||||
ktx2Loader.load("texture.ktx2", (texture) => {
|
|
||||||
material.map = texture;
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Cube Textures
|
|
||||||
|
|
||||||
For environment maps and skyboxes.
|
|
||||||
|
|
||||||
### CubeTextureLoader
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const loader = new THREE.CubeTextureLoader();
|
|
||||||
const cubeTexture = loader.load([
|
|
||||||
"px.jpg",
|
|
||||||
"nx.jpg", // +X, -X
|
|
||||||
"py.jpg",
|
|
||||||
"ny.jpg", // +Y, -Y
|
|
||||||
"pz.jpg",
|
|
||||||
"nz.jpg", // +Z, -Z
|
|
||||||
]);
|
|
||||||
|
|
||||||
// As background
|
|
||||||
scene.background = cubeTexture;
|
|
||||||
|
|
||||||
// As environment map
|
|
||||||
scene.environment = cubeTexture;
|
|
||||||
material.envMap = cubeTexture;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Equirectangular to Cubemap
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js";
|
|
||||||
|
|
||||||
const pmremGenerator = new THREE.PMREMGenerator(renderer);
|
|
||||||
pmremGenerator.compileEquirectangularShader();
|
|
||||||
|
|
||||||
new RGBELoader().load("environment.hdr", (texture) => {
|
|
||||||
const envMap = pmremGenerator.fromEquirectangular(texture).texture;
|
|
||||||
scene.environment = envMap;
|
|
||||||
scene.background = envMap;
|
|
||||||
|
|
||||||
texture.dispose();
|
|
||||||
pmremGenerator.dispose();
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## HDR Textures
|
|
||||||
|
|
||||||
### RGBELoader
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js";
|
|
||||||
|
|
||||||
const loader = new RGBELoader();
|
|
||||||
loader.load("environment.hdr", (texture) => {
|
|
||||||
texture.mapping = THREE.EquirectangularReflectionMapping;
|
|
||||||
scene.environment = texture;
|
|
||||||
scene.background = texture;
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### EXRLoader
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { EXRLoader } from "three/examples/jsm/loaders/EXRLoader.js";
|
|
||||||
|
|
||||||
const loader = new EXRLoader();
|
|
||||||
loader.load("environment.exr", (texture) => {
|
|
||||||
texture.mapping = THREE.EquirectangularReflectionMapping;
|
|
||||||
scene.environment = texture;
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Background Options
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
scene.background = texture;
|
|
||||||
scene.backgroundBlurriness = 0.5; // 0-1, blur background
|
|
||||||
scene.backgroundIntensity = 1.0; // Brightness
|
|
||||||
scene.backgroundRotation.y = Math.PI; // Rotate background
|
|
||||||
```
|
|
||||||
|
|
||||||
## Render Targets
|
|
||||||
|
|
||||||
Render to texture for effects.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Create render target
|
|
||||||
const renderTarget = new THREE.WebGLRenderTarget(512, 512, {
|
|
||||||
minFilter: THREE.LinearFilter,
|
|
||||||
magFilter: THREE.LinearFilter,
|
|
||||||
format: THREE.RGBAFormat,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Render scene to target
|
|
||||||
renderer.setRenderTarget(renderTarget);
|
|
||||||
renderer.render(scene, camera);
|
|
||||||
renderer.setRenderTarget(null); // Back to screen
|
|
||||||
|
|
||||||
// Use as texture
|
|
||||||
material.map = renderTarget.texture;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Depth Texture
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const renderTarget = new THREE.WebGLRenderTarget(512, 512);
|
|
||||||
renderTarget.depthTexture = new THREE.DepthTexture(
|
|
||||||
512,
|
|
||||||
512,
|
|
||||||
THREE.UnsignedShortType,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Access depth
|
|
||||||
const depthTexture = renderTarget.depthTexture;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Multi-Sample Render Target
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const renderTarget = new THREE.WebGLRenderTarget(512, 512, {
|
|
||||||
samples: 4, // MSAA
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## CubeCamera
|
|
||||||
|
|
||||||
Dynamic environment maps for reflections.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const cubeRenderTarget = new THREE.WebGLCubeRenderTarget(256, {
|
|
||||||
generateMipmaps: true,
|
|
||||||
minFilter: THREE.LinearMipmapLinearFilter,
|
|
||||||
});
|
|
||||||
|
|
||||||
const cubeCamera = new THREE.CubeCamera(0.1, 1000, cubeRenderTarget);
|
|
||||||
scene.add(cubeCamera);
|
|
||||||
|
|
||||||
// Apply to reflective material
|
|
||||||
reflectiveMaterial.envMap = cubeRenderTarget.texture;
|
|
||||||
|
|
||||||
// Update in animation loop (expensive!)
|
|
||||||
function animate() {
|
|
||||||
// Hide reflective object, update env map, show again
|
|
||||||
reflectiveObject.visible = false;
|
|
||||||
cubeCamera.position.copy(reflectiveObject.position);
|
|
||||||
cubeCamera.update(renderer, scene);
|
|
||||||
reflectiveObject.visible = true;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## UV Mapping
|
|
||||||
|
|
||||||
### Accessing UVs
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const uvs = geometry.attributes.uv;
|
|
||||||
|
|
||||||
// Read UV
|
|
||||||
const u = uvs.getX(vertexIndex);
|
|
||||||
const v = uvs.getY(vertexIndex);
|
|
||||||
|
|
||||||
// Modify UV
|
|
||||||
uvs.setXY(vertexIndex, newU, newV);
|
|
||||||
uvs.needsUpdate = true;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Second UV Channel (for AO maps)
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Required for aoMap
|
|
||||||
geometry.setAttribute("uv2", geometry.attributes.uv);
|
|
||||||
|
|
||||||
// Or create custom second UV
|
|
||||||
const uv2 = new Float32Array(vertexCount * 2);
|
|
||||||
// ... fill uv2 data
|
|
||||||
geometry.setAttribute("uv2", new THREE.BufferAttribute(uv2, 2));
|
|
||||||
```
|
|
||||||
|
|
||||||
### UV Transform in Shader
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const material = new THREE.ShaderMaterial({
|
|
||||||
uniforms: {
|
|
||||||
map: { value: texture },
|
|
||||||
uvOffset: { value: new THREE.Vector2(0, 0) },
|
|
||||||
uvScale: { value: new THREE.Vector2(1, 1) },
|
|
||||||
},
|
|
||||||
vertexShader: `
|
|
||||||
varying vec2 vUv;
|
|
||||||
uniform vec2 uvOffset;
|
|
||||||
uniform vec2 uvScale;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vUv = uv * uvScale + uvOffset;
|
|
||||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
fragmentShader: `
|
|
||||||
varying vec2 vUv;
|
|
||||||
uniform sampler2D map;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_FragColor = texture2D(map, vUv);
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Texture Atlas
|
|
||||||
|
|
||||||
Multiple images in one texture.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Atlas with 4 sprites (2x2 grid)
|
|
||||||
const atlas = loader.load("atlas.png");
|
|
||||||
atlas.wrapS = THREE.ClampToEdgeWrapping;
|
|
||||||
atlas.wrapT = THREE.ClampToEdgeWrapping;
|
|
||||||
|
|
||||||
// Select sprite by UV offset/scale
|
|
||||||
function selectSprite(row, col, gridSize = 2) {
|
|
||||||
atlas.offset.set(col / gridSize, 1 - (row + 1) / gridSize);
|
|
||||||
atlas.repeat.set(1 / gridSize, 1 / gridSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Select top-left sprite
|
|
||||||
selectSprite(0, 0);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Material Texture Maps
|
|
||||||
|
|
||||||
### PBR Texture Set
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const material = new THREE.MeshStandardMaterial({
|
|
||||||
// Base color (sRGB)
|
|
||||||
map: colorTexture,
|
|
||||||
|
|
||||||
// Surface detail (Linear)
|
|
||||||
normalMap: normalTexture,
|
|
||||||
normalScale: new THREE.Vector2(1, 1),
|
|
||||||
|
|
||||||
// Roughness (Linear, grayscale)
|
|
||||||
roughnessMap: roughnessTexture,
|
|
||||||
roughness: 1, // Multiplier
|
|
||||||
|
|
||||||
// Metalness (Linear, grayscale)
|
|
||||||
metalnessMap: metalnessTexture,
|
|
||||||
metalness: 1, // Multiplier
|
|
||||||
|
|
||||||
// Ambient occlusion (Linear, uses uv2)
|
|
||||||
aoMap: aoTexture,
|
|
||||||
aoMapIntensity: 1,
|
|
||||||
|
|
||||||
// Self-illumination (sRGB)
|
|
||||||
emissiveMap: emissiveTexture,
|
|
||||||
emissive: 0xffffff,
|
|
||||||
emissiveIntensity: 1,
|
|
||||||
|
|
||||||
// Vertex displacement (Linear)
|
|
||||||
displacementMap: displacementTexture,
|
|
||||||
displacementScale: 0.1,
|
|
||||||
displacementBias: 0,
|
|
||||||
|
|
||||||
// Alpha (Linear)
|
|
||||||
alphaMap: alphaTexture,
|
|
||||||
transparent: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Don't forget UV2 for AO
|
|
||||||
geometry.setAttribute("uv2", geometry.attributes.uv);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Normal Map Types
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// OpenGL style normals (default)
|
|
||||||
material.normalMapType = THREE.TangentSpaceNormalMap;
|
|
||||||
|
|
||||||
// Object space normals
|
|
||||||
material.normalMapType = THREE.ObjectSpaceNormalMap;
|
|
||||||
```
|
|
||||||
|
|
||||||
## Procedural Textures
|
|
||||||
|
|
||||||
### Noise Texture
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function generateNoiseTexture(size = 256) {
|
|
||||||
const data = new Uint8Array(size * size * 4);
|
|
||||||
|
|
||||||
for (let i = 0; i < size * size; i++) {
|
|
||||||
const value = Math.random() * 255;
|
|
||||||
data[i * 4] = value;
|
|
||||||
data[i * 4 + 1] = value;
|
|
||||||
data[i * 4 + 2] = value;
|
|
||||||
data[i * 4 + 3] = 255;
|
|
||||||
}
|
|
||||||
|
|
||||||
const texture = new THREE.DataTexture(data, size, size);
|
|
||||||
texture.needsUpdate = true;
|
|
||||||
return texture;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Gradient Texture
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function generateGradientTexture(color1, color2, size = 256) {
|
|
||||||
const canvas = document.createElement("canvas");
|
|
||||||
canvas.width = size;
|
|
||||||
canvas.height = 1;
|
|
||||||
const ctx = canvas.getContext("2d");
|
|
||||||
|
|
||||||
const gradient = ctx.createLinearGradient(0, 0, size, 0);
|
|
||||||
gradient.addColorStop(0, color1);
|
|
||||||
gradient.addColorStop(1, color2);
|
|
||||||
|
|
||||||
ctx.fillStyle = gradient;
|
|
||||||
ctx.fillRect(0, 0, size, 1);
|
|
||||||
|
|
||||||
return new THREE.CanvasTexture(canvas);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Texture Memory Management
|
|
||||||
|
|
||||||
### Dispose Textures
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Single texture
|
|
||||||
texture.dispose();
|
|
||||||
|
|
||||||
// Material textures
|
|
||||||
function disposeMaterial(material) {
|
|
||||||
const maps = [
|
|
||||||
"map",
|
|
||||||
"normalMap",
|
|
||||||
"roughnessMap",
|
|
||||||
"metalnessMap",
|
|
||||||
"aoMap",
|
|
||||||
"emissiveMap",
|
|
||||||
"displacementMap",
|
|
||||||
"alphaMap",
|
|
||||||
"envMap",
|
|
||||||
"lightMap",
|
|
||||||
"bumpMap",
|
|
||||||
"specularMap",
|
|
||||||
];
|
|
||||||
|
|
||||||
maps.forEach((mapName) => {
|
|
||||||
if (material[mapName]) {
|
|
||||||
material[mapName].dispose();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
material.dispose();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Texture Pooling
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
class TexturePool {
|
|
||||||
constructor() {
|
|
||||||
this.textures = new Map();
|
|
||||||
this.loader = new THREE.TextureLoader();
|
|
||||||
}
|
|
||||||
|
|
||||||
async get(url) {
|
|
||||||
if (this.textures.has(url)) {
|
|
||||||
return this.textures.get(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
const texture = await new Promise((resolve, reject) => {
|
|
||||||
this.loader.load(url, resolve, undefined, reject);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.textures.set(url, texture);
|
|
||||||
return texture;
|
|
||||||
}
|
|
||||||
|
|
||||||
dispose(url) {
|
|
||||||
const texture = this.textures.get(url);
|
|
||||||
if (texture) {
|
|
||||||
texture.dispose();
|
|
||||||
this.textures.delete(url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
disposeAll() {
|
|
||||||
this.textures.forEach((t) => t.dispose());
|
|
||||||
this.textures.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Performance Tips
|
|
||||||
|
|
||||||
1. **Use power-of-2 dimensions**: 256, 512, 1024, 2048
|
|
||||||
2. **Compress textures**: KTX2/Basis for web delivery
|
|
||||||
3. **Use texture atlases**: Reduce texture switches
|
|
||||||
4. **Enable mipmaps**: For distant objects
|
|
||||||
5. **Limit texture size**: 2048 usually sufficient for web
|
|
||||||
6. **Reuse textures**: Same texture = better batching
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Check texture memory
|
|
||||||
console.log(renderer.info.memory.textures);
|
|
||||||
|
|
||||||
// Optimize for mobile
|
|
||||||
const maxSize = renderer.capabilities.maxTextureSize;
|
|
||||||
const isMobile = /iPhone|iPad|Android/i.test(navigator.userAgent);
|
|
||||||
const textureSize = isMobile ? 1024 : 2048;
|
|
||||||
```
|
|
||||||
|
|
||||||
## See Also
|
|
||||||
|
|
||||||
- `threejs-materials` - Applying textures to materials
|
|
||||||
- `threejs-loaders` - Loading texture files
|
|
||||||
- `threejs-shaders` - Custom texture sampling
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "eapil-xlsx",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"description": "EAPIL XLSX 提供电子表格创建、编辑、数据分析、公式校验、CSV 处理和工作簿重算相关的 Codex 工作流。",
|
|
||||||
"author": {
|
|
||||||
"name": "EAPIL",
|
|
||||||
"url": "https://git.playones.com/arechen/EapilSkillMarket"
|
|
||||||
},
|
|
||||||
"homepage": "https://git.playones.com/arechen/EapilSkillMarket",
|
|
||||||
"repository": "https://git.playones.com/arechen/EapilSkillMarket",
|
|
||||||
"license": "Proprietary",
|
|
||||||
"keywords": [
|
|
||||||
"eapil",
|
|
||||||
"xlsx",
|
|
||||||
"codex-skill"
|
|
||||||
],
|
|
||||||
"skills": "./skills/",
|
|
||||||
"interface": {
|
|
||||||
"displayName": "EAPIL XLSX",
|
|
||||||
"shortDescription": "电子表格创建、编辑、分析、公式校验和重算工作流。",
|
|
||||||
"longDescription": "EAPIL XLSX 提供面向 Excel/CSV/TSV 工作簿的 Codex 工作流,覆盖电子表格创建、编辑、数据分析、格式处理、公式校验、财务建模约定和中文数据集场景。",
|
|
||||||
"developerName": "EAPIL",
|
|
||||||
"category": "数据分析",
|
|
||||||
"capabilities": [
|
|
||||||
"Read",
|
|
||||||
"Write"
|
|
||||||
],
|
|
||||||
"defaultPrompt": [
|
|
||||||
"使用 EAPIL XLSX 处理这个工作簿。",
|
|
||||||
"检查这个电子表格里的公式。",
|
|
||||||
"创建一个 Excel 数据模型。"
|
|
||||||
],
|
|
||||||
"websiteURL": "https://git.playones.com/arechen/EapilSkillMarket",
|
|
||||||
"privacyPolicyURL": "https://git.playones.com/arechen/EapilSkillMarket",
|
|
||||||
"termsOfServiceURL": "https://git.playones.com/arechen/EapilSkillMarket",
|
|
||||||
"brandColor": "#2563EB",
|
|
||||||
"screenshots": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
© 2025 Anthropic, PBC. All rights reserved.
|
|
||||||
|
|
||||||
LICENSE: Use of these materials (including all code, prompts, assets, files,
|
|
||||||
and other components of this Skill) is governed by your agreement with
|
|
||||||
Anthropic regarding use of Anthropic's services. If no separate agreement
|
|
||||||
exists, use is governed by Anthropic's Consumer Terms of Service or
|
|
||||||
Commercial Terms of Service, as applicable:
|
|
||||||
https://www.anthropic.com/legal/consumer-terms
|
|
||||||
https://www.anthropic.com/legal/commercial-terms
|
|
||||||
Your applicable agreement is referred to as the "Agreement." "Services" are
|
|
||||||
as defined in the Agreement.
|
|
||||||
|
|
||||||
ADDITIONAL RESTRICTIONS: Notwithstanding anything in the Agreement to the
|
|
||||||
contrary, users may not:
|
|
||||||
|
|
||||||
- Extract these materials from the Services or retain copies of these
|
|
||||||
materials outside the Services
|
|
||||||
- Reproduce or copy these materials, except for temporary copies created
|
|
||||||
automatically during authorized use of the Services
|
|
||||||
- Create derivative works based on these materials
|
|
||||||
- Distribute, sublicense, or transfer these materials to any third party
|
|
||||||
- Make, offer to sell, sell, or import any inventions embodied in these
|
|
||||||
materials
|
|
||||||
- Reverse engineer, decompile, or disassemble these materials
|
|
||||||
|
|
||||||
The receipt, viewing, or possession of these materials does not convey or
|
|
||||||
imply any license or right beyond those expressly granted above.
|
|
||||||
|
|
||||||
Anthropic retains all right, title, and interest in these materials,
|
|
||||||
including all copyrights, patents, and other intellectual property rights.
|
|
||||||
@@ -1,390 +0,0 @@
|
|||||||
---
|
|
||||||
name: xlsx
|
|
||||||
description: "电子表格创建、编辑和分析工具,支持公式、格式、数据分析、可视化、CSV/TSV 处理,以及中文/非英文数据集。"
|
|
||||||
license: Proprietary. LICENSE.txt has complete terms
|
|
||||||
---
|
|
||||||
|
|
||||||
# Requirements for Outputs
|
|
||||||
|
|
||||||
## All Excel files
|
|
||||||
|
|
||||||
### Zero Formula Errors
|
|
||||||
- Every Excel model MUST be delivered with ZERO formula errors (#REF!, #DIV/0!, #VALUE!, #N/A, #NAME?)
|
|
||||||
|
|
||||||
### Preserve Existing Templates (when updating templates)
|
|
||||||
- Study and EXACTLY match existing format, style, and conventions when modifying files
|
|
||||||
- Never impose standardized formatting on files with established patterns
|
|
||||||
- Existing template conventions ALWAYS override these guidelines
|
|
||||||
|
|
||||||
## Financial models
|
|
||||||
|
|
||||||
### Color Coding Standards
|
|
||||||
Unless otherwise stated by the user or existing template
|
|
||||||
|
|
||||||
#### Industry-Standard Color Conventions
|
|
||||||
- **Blue text (RGB: 0,0,255)**: Hardcoded inputs, and numbers users will change for scenarios
|
|
||||||
- **Black text (RGB: 0,0,0)**: ALL formulas and calculations
|
|
||||||
- **Green text (RGB: 0,128,0)**: Links pulling from other worksheets within same workbook
|
|
||||||
- **Red text (RGB: 255,0,0)**: External links to other files
|
|
||||||
- **Yellow background (RGB: 255,255,0)**: Key assumptions needing attention or cells that need to be updated
|
|
||||||
|
|
||||||
### Number Formatting Standards
|
|
||||||
|
|
||||||
#### Required Format Rules
|
|
||||||
- **Years**: Format as text strings (e.g., "2024" not "2,024")
|
|
||||||
- **Currency**: Use $#,##0 format; ALWAYS specify units in headers ("Revenue ($mm)")
|
|
||||||
- **Zeros**: Use number formatting to make all zeros "-", including percentages (e.g., "$#,##0;($#,##0);-")
|
|
||||||
- **Percentages**: Default to 0.0% format (one decimal)
|
|
||||||
- **Multiples**: Format as 0.0x for valuation multiples (EV/EBITDA, P/E)
|
|
||||||
- **Negative numbers**: Use parentheses (123) not minus -123
|
|
||||||
|
|
||||||
### Formula Construction Rules
|
|
||||||
|
|
||||||
#### Assumptions Placement
|
|
||||||
- Place ALL assumptions (growth rates, margins, multiples, etc.) in separate assumption cells
|
|
||||||
- Use cell references instead of hardcoded values in formulas
|
|
||||||
- Example: Use =B5*(1+$B$6) instead of =B5*1.05
|
|
||||||
|
|
||||||
#### Formula Error Prevention
|
|
||||||
- Verify all cell references are correct
|
|
||||||
- Check for off-by-one errors in ranges
|
|
||||||
- Ensure consistent formulas across all projection periods
|
|
||||||
- Test with edge cases (zero values, negative numbers)
|
|
||||||
- Verify no unintended circular references
|
|
||||||
|
|
||||||
#### Documentation Requirements for Hardcodes
|
|
||||||
- Comment or in cells beside (if end of table). Format: "Source: [System/Document], [Date], [Specific Reference], [URL if applicable]"
|
|
||||||
- Examples:
|
|
||||||
- "Source: Company 10-K, FY2024, Page 45, Revenue Note, [SEC EDGAR URL]"
|
|
||||||
- "Source: Company 10-Q, Q2 2025, Exhibit 99.1, [SEC EDGAR URL]"
|
|
||||||
- "Source: Bloomberg Terminal, 8/15/2025, AAPL US Equity"
|
|
||||||
- "Source: FactSet, 8/20/2025, Consensus Estimates Screen"
|
|
||||||
|
|
||||||
# XLSX creation, editing, and analysis
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
A user may ask you to create, edit, or analyze the contents of an .xlsx file. You have different tools and workflows available for different tasks.
|
|
||||||
|
|
||||||
## Important Requirements
|
|
||||||
|
|
||||||
**LibreOffice Required for Formula Recalculation**: You can assume LibreOffice is installed for recalculating formula values using the `recalc.py` script. The script automatically configures LibreOffice on first run.
|
|
||||||
|
|
||||||
## Windows and Chinese-language defaults
|
|
||||||
|
|
||||||
- Prefer `py -3` on Windows if `python` does not resolve to Python 3
|
|
||||||
- Always quote file paths in commands: `"C:\data\Q4 report.xlsx"`
|
|
||||||
- Before running pandas/openpyxl scripts on Windows PowerShell, set UTF-8 mode: `$env:PYTHONUTF8='1'`
|
|
||||||
- For CSV round-trips with Chinese text, prefer UTF-8 with BOM: `encoding='utf-8-sig'`
|
|
||||||
- For output workbooks containing Chinese text, set explicit fonts (`Microsoft YaHei`, `SimSun`) where formatting is required
|
|
||||||
|
|
||||||
## Reading and analyzing data
|
|
||||||
|
|
||||||
### Data analysis with pandas
|
|
||||||
For data analysis, visualization, and basic operations, use **pandas** which provides powerful data manipulation capabilities:
|
|
||||||
|
|
||||||
```python
|
|
||||||
import pandas as pd
|
|
||||||
|
|
||||||
# Read Excel
|
|
||||||
df = pd.read_excel('file.xlsx') # Default: first sheet
|
|
||||||
all_sheets = pd.read_excel('file.xlsx', sheet_name=None) # All sheets as dict
|
|
||||||
|
|
||||||
# Analyze
|
|
||||||
df.head() # Preview data
|
|
||||||
df.info() # Column info
|
|
||||||
df.describe() # Statistics
|
|
||||||
|
|
||||||
# Write Excel
|
|
||||||
df.to_excel('output.xlsx', index=False)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Handling non-English column names
|
|
||||||
When Excel files contain non-ASCII characters (Chinese, Japanese, etc.), column names may display as garbled text in terminal output on Windows due to encoding issues. This does NOT affect data reading - pandas reads the data correctly.
|
|
||||||
|
|
||||||
**Solutions:**
|
|
||||||
|
|
||||||
```python
|
|
||||||
# Option 1: Use openpyxl to read headers directly
|
|
||||||
from openpyxl import load_workbook
|
|
||||||
wb = load_workbook('file.xlsx')
|
|
||||||
sheet = wb.active
|
|
||||||
headers = [cell.value for cell in sheet[1]]
|
|
||||||
|
|
||||||
# Option 2: Rename columns by position (recommended for analysis)
|
|
||||||
df = pd.read_excel('file.xlsx')
|
|
||||||
df.columns = ['user', 'timestamp', 'model', 'latency', 'prompt_tokens', 'completion_tokens']
|
|
||||||
|
|
||||||
# Option 3: Access columns by position
|
|
||||||
df.iloc[:, 0] # First column
|
|
||||||
df.iloc[:, [0, 2, 3]] # Multiple columns by index
|
|
||||||
```
|
|
||||||
|
|
||||||
**Note for Windows users:** Terminal print output (print statements) may show encoding errors with non-ASCII characters. This is usually a console encoding issue, not a data issue. Either avoid printing non-ASCII text, use position-based column renaming, or write verification output to UTF-8 files.
|
|
||||||
|
|
||||||
## Excel File Workflows
|
|
||||||
|
|
||||||
## CRITICAL: Use Formulas, Not Hardcoded Values
|
|
||||||
|
|
||||||
**Always use Excel formulas instead of calculating values in Python and hardcoding them.** This ensures the spreadsheet remains dynamic and updateable.
|
|
||||||
|
|
||||||
### ❌ WRONG - Hardcoding Calculated Values
|
|
||||||
```python
|
|
||||||
# Bad: Calculating in Python and hardcoding result
|
|
||||||
total = df['Sales'].sum()
|
|
||||||
sheet['B10'] = total # Hardcodes 5000
|
|
||||||
|
|
||||||
# Bad: Computing growth rate in Python
|
|
||||||
growth = (df.iloc[-1]['Revenue'] - df.iloc[0]['Revenue']) / df.iloc[0]['Revenue']
|
|
||||||
sheet['C5'] = growth # Hardcodes 0.15
|
|
||||||
|
|
||||||
# Bad: Python calculation for average
|
|
||||||
avg = sum(values) / len(values)
|
|
||||||
sheet['D20'] = avg # Hardcodes 42.5
|
|
||||||
```
|
|
||||||
|
|
||||||
### ✅ CORRECT - Using Excel Formulas
|
|
||||||
```python
|
|
||||||
# Good: Let Excel calculate the sum
|
|
||||||
sheet['B10'] = '=SUM(B2:B9)'
|
|
||||||
|
|
||||||
# Good: Growth rate as Excel formula
|
|
||||||
sheet['C5'] = '=(C4-C2)/C2'
|
|
||||||
|
|
||||||
# Good: Average using Excel function
|
|
||||||
sheet['D20'] = '=AVERAGE(D2:D19)'
|
|
||||||
```
|
|
||||||
|
|
||||||
This applies to ALL calculations - totals, percentages, ratios, differences, etc. The spreadsheet should be able to recalculate when source data changes.
|
|
||||||
|
|
||||||
**Locale-safe formula rule:** When writing formulas with openpyxl, use English function names and comma-separated arguments (e.g., `=SUM(A1:A10)`). Excel may display localized names in UI, but the stored formula should remain in standard Excel syntax.
|
|
||||||
|
|
||||||
## Common Workflow
|
|
||||||
1. **Choose tool**: pandas for data, openpyxl for formulas/formatting
|
|
||||||
2. **Create/Load**: Create new workbook or load existing file
|
|
||||||
3. **Modify**: Add/edit data, formulas, and formatting
|
|
||||||
4. **Save**: Write to file
|
|
||||||
5. **Recalculate formulas (MANDATORY IF USING FORMULAS)**: Use the recalc.py script
|
|
||||||
```bash
|
|
||||||
python recalc.py output.xlsx
|
|
||||||
```
|
|
||||||
Windows PowerShell example:
|
|
||||||
```powershell
|
|
||||||
py -3 .\recalc.py "C:\models\finance-model.xlsx"
|
|
||||||
```
|
|
||||||
6. **Verify and fix any errors**:
|
|
||||||
- The script returns JSON with error details
|
|
||||||
- If `status` is `errors_found`, check `error_summary` for specific error types and locations
|
|
||||||
- Fix the identified errors and recalculate again
|
|
||||||
- Common errors to fix:
|
|
||||||
- `#REF!`: Invalid cell references
|
|
||||||
- `#DIV/0!`: Division by zero
|
|
||||||
- `#VALUE!`: Wrong data type in formula
|
|
||||||
- `#NAME?`: Unrecognized formula name
|
|
||||||
|
|
||||||
### Creating new Excel files
|
|
||||||
|
|
||||||
```python
|
|
||||||
# Using openpyxl for formulas and formatting
|
|
||||||
from openpyxl import Workbook
|
|
||||||
from openpyxl.styles import Font, PatternFill, Alignment
|
|
||||||
|
|
||||||
wb = Workbook()
|
|
||||||
sheet = wb.active
|
|
||||||
|
|
||||||
# Add data
|
|
||||||
sheet['A1'] = 'Hello'
|
|
||||||
sheet['B1'] = 'World'
|
|
||||||
sheet.append(['Row', 'of', 'data'])
|
|
||||||
|
|
||||||
# Add formula
|
|
||||||
sheet['B2'] = '=SUM(A1:A10)'
|
|
||||||
|
|
||||||
# Formatting
|
|
||||||
sheet['A1'].font = Font(name='Microsoft YaHei', bold=True, color='FF0000')
|
|
||||||
sheet['A1'].fill = PatternFill('solid', start_color='FFFF00')
|
|
||||||
sheet['A1'].alignment = Alignment(horizontal='center')
|
|
||||||
|
|
||||||
# Column width
|
|
||||||
sheet.column_dimensions['A'].width = 20
|
|
||||||
|
|
||||||
wb.save('output.xlsx')
|
|
||||||
```
|
|
||||||
|
|
||||||
### Editing existing Excel files
|
|
||||||
|
|
||||||
```python
|
|
||||||
# Using openpyxl to preserve formulas and formatting
|
|
||||||
from openpyxl import load_workbook
|
|
||||||
|
|
||||||
# Load existing file
|
|
||||||
wb = load_workbook('existing.xlsx')
|
|
||||||
sheet = wb.active # or wb['SheetName'] for specific sheet
|
|
||||||
|
|
||||||
# Working with multiple sheets
|
|
||||||
for sheet_name in wb.sheetnames:
|
|
||||||
sheet = wb[sheet_name]
|
|
||||||
print(f"Sheet: {sheet_name}")
|
|
||||||
|
|
||||||
# Modify cells
|
|
||||||
sheet['A1'] = 'New Value'
|
|
||||||
sheet.insert_rows(2) # Insert row at position 2
|
|
||||||
sheet.delete_cols(3) # Delete column 3
|
|
||||||
|
|
||||||
# Add new sheet
|
|
||||||
new_sheet = wb.create_sheet('NewSheet')
|
|
||||||
new_sheet['A1'] = 'Data'
|
|
||||||
|
|
||||||
wb.save('modified.xlsx')
|
|
||||||
```
|
|
||||||
|
|
||||||
### Reading multiple sheets
|
|
||||||
|
|
||||||
```python
|
|
||||||
# Using pandas - all sheets as dict
|
|
||||||
all_sheets = pd.read_excel('file.xlsx', sheet_name=None)
|
|
||||||
df1 = all_sheets['Sheet1']
|
|
||||||
df2 = all_sheets['Sheet2']
|
|
||||||
|
|
||||||
# Using pandas - specific sheets
|
|
||||||
df = pd.read_excel('file.xlsx', sheet_name='Sheet1')
|
|
||||||
multiple = pd.read_excel('file.xlsx', sheet_name=['Sheet1', 'Sheet2'])
|
|
||||||
|
|
||||||
# Using openpyxl - iterate sheets
|
|
||||||
wb = load_workbook('file.xlsx')
|
|
||||||
for sheet_name in wb.sheetnames:
|
|
||||||
sheet = wb[sheet_name]
|
|
||||||
# Process each sheet
|
|
||||||
```
|
|
||||||
|
|
||||||
## Recalculating formulas
|
|
||||||
|
|
||||||
Excel files created or modified by openpyxl contain formulas as strings but not calculated values. Use the provided `recalc.py` script to recalculate formulas:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python recalc.py <excel_file> [timeout_seconds]
|
|
||||||
```
|
|
||||||
|
|
||||||
Example:
|
|
||||||
```bash
|
|
||||||
python recalc.py output.xlsx 30
|
|
||||||
```
|
|
||||||
|
|
||||||
The script:
|
|
||||||
- Automatically sets up LibreOffice macro on first run
|
|
||||||
- Recalculates all formulas in all sheets
|
|
||||||
- Scans ALL cells for Excel errors (#REF!, #DIV/0!, etc.)
|
|
||||||
- Returns JSON with detailed error locations and counts
|
|
||||||
- Works on Windows, Linux, and macOS
|
|
||||||
|
|
||||||
## Formula Verification Checklist
|
|
||||||
|
|
||||||
Quick checks to ensure formulas work correctly:
|
|
||||||
|
|
||||||
### Essential Verification
|
|
||||||
- [ ] **Test 2-3 sample references**: Verify they pull correct values before building full model
|
|
||||||
- [ ] **Column mapping**: Confirm Excel columns match (e.g., column 64 = BL, not BK)
|
|
||||||
- [ ] **Row offset**: Remember Excel rows are 1-indexed (DataFrame row 5 = Excel row 6)
|
|
||||||
|
|
||||||
### Common Pitfalls
|
|
||||||
- [ ] **NaN handling**: Check for null values with `pd.notna()`
|
|
||||||
- [ ] **Far-right columns**: FY data often in columns 50+
|
|
||||||
- [ ] **Multiple matches**: Search all occurrences, not just first
|
|
||||||
- [ ] **Division by zero**: Check denominators before using `/` in formulas (#DIV/0!)
|
|
||||||
- [ ] **Wrong references**: Verify all cell references point to intended cells (#REF!)
|
|
||||||
- [ ] **Cross-sheet references**: Use correct format (Sheet1!A1) for linking sheets
|
|
||||||
|
|
||||||
### Formula Testing Strategy
|
|
||||||
- [ ] **Start small**: Test formulas on 2-3 cells before applying broadly
|
|
||||||
- [ ] **Verify dependencies**: Check all cells referenced in formulas exist
|
|
||||||
- [ ] **Test edge cases**: Include zero, negative, and very large values
|
|
||||||
|
|
||||||
### Interpreting recalc.py Output
|
|
||||||
The script returns JSON with error details:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"status": "success", // or "errors_found"
|
|
||||||
"total_errors": 0, // Total error count
|
|
||||||
"total_formulas": 42, // Number of formulas in file
|
|
||||||
"error_summary": { // Only present if errors found
|
|
||||||
"#REF!": {
|
|
||||||
"count": 2,
|
|
||||||
"locations": ["Sheet1!B5", "Sheet1!C10"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
|
|
||||||
### Library Selection
|
|
||||||
- **pandas**: Best for data analysis, bulk operations, and simple data export
|
|
||||||
- **openpyxl**: Best for complex formatting, formulas, and Excel-specific features
|
|
||||||
|
|
||||||
### Working with openpyxl
|
|
||||||
- Cell indices are 1-based (row=1, column=1 refers to cell A1)
|
|
||||||
- Use `data_only=True` to read calculated values: `load_workbook('file.xlsx', data_only=True)`
|
|
||||||
- **Warning**: If opened with `data_only=True` and saved, formulas are replaced with values and permanently lost
|
|
||||||
- For large files: Use `read_only=True` for reading or `write_only=True` for writing
|
|
||||||
- Formulas are preserved but not evaluated - use recalc.py to update values
|
|
||||||
|
|
||||||
### Working with pandas
|
|
||||||
- Specify data types to avoid inference issues: `pd.read_excel('file.xlsx', dtype={'id': str})`
|
|
||||||
- For large files, read specific columns: `pd.read_excel('file.xlsx', usecols=['A', 'C', 'E'])`
|
|
||||||
- Handle dates properly: `pd.read_excel('file.xlsx', parse_dates=['date_column'])`
|
|
||||||
|
|
||||||
### Data visualization and reporting workflow
|
|
||||||
When analyzing Excel data and generating reports (HTML, dashboards, etc.):
|
|
||||||
|
|
||||||
```python
|
|
||||||
import pandas as pd
|
|
||||||
import json
|
|
||||||
|
|
||||||
# 1. Read and prepare data
|
|
||||||
df = pd.read_excel('data.xlsx')
|
|
||||||
df.columns = ['col1', 'col2', 'col3'] # Rename if needed
|
|
||||||
df['date'] = pd.to_datetime(df['date'])
|
|
||||||
|
|
||||||
# 2. Aggregate and analyze
|
|
||||||
daily_stats = df.groupby('date').agg({'value': ['sum', 'mean']})
|
|
||||||
summary = {
|
|
||||||
'total': len(df),
|
|
||||||
'avg': df['value'].mean(),
|
|
||||||
}
|
|
||||||
|
|
||||||
# 3. Generate output with f-strings (avoids template formatting conflicts)
|
|
||||||
html = f'''<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Report</title>
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Total: {summary['total']:,}</h1>
|
|
||||||
<script>
|
|
||||||
const data = {json.dumps(summary)};
|
|
||||||
// Chart.js code here
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>'''
|
|
||||||
|
|
||||||
with open('report.html', 'w', encoding='utf-8') as f:
|
|
||||||
f.write(html)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Key points:**
|
|
||||||
- Use f-strings for HTML templates to avoid format string conflicts
|
|
||||||
- Double braces `{{` in JavaScript/CSS to escape them in f-strings
|
|
||||||
- Use `encoding='utf-8'` when writing HTML files
|
|
||||||
- Convert data to JSON with `json.dumps()` for embedding in JavaScript
|
|
||||||
|
|
||||||
## Code Style Guidelines
|
|
||||||
**IMPORTANT**: When generating Python code for Excel operations:
|
|
||||||
- Write minimal, concise Python code without unnecessary comments
|
|
||||||
- Avoid verbose variable names and redundant operations
|
|
||||||
- Avoid unnecessary print statements
|
|
||||||
|
|
||||||
**For Excel files themselves**:
|
|
||||||
- Add comments to cells with complex formulas or important assumptions
|
|
||||||
- Document data sources for hardcoded values
|
|
||||||
- Include notes for key calculations and model sections
|
|
||||||
@@ -1,227 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Excel Formula Recalculation Script
|
|
||||||
Recalculates all formulas in an Excel file using LibreOffice
|
|
||||||
"""
|
|
||||||
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import platform
|
|
||||||
import shutil
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from openpyxl import load_workbook
|
|
||||||
|
|
||||||
|
|
||||||
def resolve_soffice_binary():
|
|
||||||
"""Find a working LibreOffice executable."""
|
|
||||||
candidates = ["soffice", "soffice.com", "soffice.exe"]
|
|
||||||
if platform.system() == "Windows":
|
|
||||||
candidates = ["soffice.com", "soffice.exe", "soffice"]
|
|
||||||
|
|
||||||
for name in candidates:
|
|
||||||
binary = shutil.which(name)
|
|
||||||
if binary:
|
|
||||||
return binary
|
|
||||||
|
|
||||||
if platform.system() == "Windows":
|
|
||||||
possible_roots = [
|
|
||||||
os.environ.get("ProgramFiles"),
|
|
||||||
os.environ.get("ProgramFiles(x86)"),
|
|
||||||
]
|
|
||||||
for root in possible_roots:
|
|
||||||
if not root:
|
|
||||||
continue
|
|
||||||
for rel in [
|
|
||||||
"LibreOffice/program/soffice.com",
|
|
||||||
"LibreOffice/program/soffice.exe",
|
|
||||||
]:
|
|
||||||
candidate = Path(root) / rel
|
|
||||||
if candidate.exists():
|
|
||||||
return str(candidate)
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def get_macro_dir():
|
|
||||||
"""Return the LibreOffice macro directory for the current OS."""
|
|
||||||
system = platform.system()
|
|
||||||
if system == "Darwin":
|
|
||||||
return Path.home() / "Library/Application Support/LibreOffice/4/user/basic/Standard"
|
|
||||||
if system == "Windows":
|
|
||||||
appdata = os.environ.get("APPDATA")
|
|
||||||
if appdata:
|
|
||||||
return Path(appdata) / "LibreOffice/4/user/basic/Standard"
|
|
||||||
return Path.home() / "AppData/Roaming/LibreOffice/4/user/basic/Standard"
|
|
||||||
return Path.home() / ".config/libreoffice/4/user/basic/Standard"
|
|
||||||
|
|
||||||
|
|
||||||
def setup_libreoffice_macro(soffice_binary):
|
|
||||||
"""Setup LibreOffice macro for recalculation if not already configured."""
|
|
||||||
macro_dir = get_macro_dir()
|
|
||||||
macro_file = macro_dir / "Module1.xba"
|
|
||||||
|
|
||||||
if macro_file.exists():
|
|
||||||
existing = macro_file.read_text(encoding="utf-8", errors="ignore")
|
|
||||||
if "RecalculateAndSave" in existing:
|
|
||||||
return True
|
|
||||||
|
|
||||||
if not macro_dir.exists():
|
|
||||||
try:
|
|
||||||
subprocess.run(
|
|
||||||
[soffice_binary, "--headless", "--terminate_after_init"],
|
|
||||||
capture_output=True,
|
|
||||||
check=False,
|
|
||||||
timeout=20,
|
|
||||||
text=True,
|
|
||||||
)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
macro_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
macro_content = """<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE script:module PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "module.dtd">
|
|
||||||
<script:module xmlns:script="http://openoffice.org/2000/script" script:name="Module1" script:language="StarBasic">
|
|
||||||
Sub RecalculateAndSave()
|
|
||||||
ThisComponent.calculateAll()
|
|
||||||
ThisComponent.store()
|
|
||||||
ThisComponent.close(True)
|
|
||||||
End Sub
|
|
||||||
</script:module>"""
|
|
||||||
|
|
||||||
try:
|
|
||||||
macro_file.write_text(macro_content, encoding="utf-8")
|
|
||||||
return True
|
|
||||||
except Exception:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def recalc(filename, timeout=30):
|
|
||||||
"""
|
|
||||||
Recalculate formulas in Excel file and report any errors.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
filename: Path to Excel file
|
|
||||||
timeout: Maximum time to wait for recalculation (seconds)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
dict with error locations and counts
|
|
||||||
"""
|
|
||||||
file_path = Path(filename).expanduser()
|
|
||||||
if not file_path.exists():
|
|
||||||
return {"error": f"File {filename} does not exist"}
|
|
||||||
|
|
||||||
soffice_binary = resolve_soffice_binary()
|
|
||||||
if not soffice_binary:
|
|
||||||
return {
|
|
||||||
"error": (
|
|
||||||
"LibreOffice executable not found. Install LibreOffice and ensure "
|
|
||||||
"`soffice` (or `soffice.com` on Windows) is on PATH."
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if not setup_libreoffice_macro(soffice_binary):
|
|
||||||
return {"error": "Failed to setup LibreOffice macro"}
|
|
||||||
|
|
||||||
cmd = [
|
|
||||||
soffice_binary,
|
|
||||||
"--headless",
|
|
||||||
"--norestore",
|
|
||||||
"vnd.sun.star.script:Standard.Module1.RecalculateAndSave?language=Basic&location=application",
|
|
||||||
str(file_path.resolve()),
|
|
||||||
]
|
|
||||||
|
|
||||||
try:
|
|
||||||
run_result = subprocess.run(
|
|
||||||
cmd,
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
check=False,
|
|
||||||
timeout=timeout,
|
|
||||||
)
|
|
||||||
except subprocess.TimeoutExpired:
|
|
||||||
return {"error": f"Recalculation timed out after {timeout} seconds"}
|
|
||||||
|
|
||||||
if run_result.returncode != 0:
|
|
||||||
error_msg = (run_result.stderr or run_result.stdout or "Unknown error during recalculation").strip()
|
|
||||||
if "Module1" in error_msg or "RecalculateAndSave" in error_msg or "Basic" in error_msg:
|
|
||||||
return {
|
|
||||||
"error": "LibreOffice macro not configured properly",
|
|
||||||
"details": error_msg,
|
|
||||||
}
|
|
||||||
return {"error": error_msg}
|
|
||||||
|
|
||||||
try:
|
|
||||||
wb_values = load_workbook(file_path, data_only=True)
|
|
||||||
|
|
||||||
excel_errors = ["#VALUE!", "#DIV/0!", "#REF!", "#NAME?", "#NULL!", "#NUM!", "#N/A"]
|
|
||||||
error_details = {err: [] for err in excel_errors}
|
|
||||||
total_errors = 0
|
|
||||||
|
|
||||||
for sheet_name in wb_values.sheetnames:
|
|
||||||
ws = wb_values[sheet_name]
|
|
||||||
for row in ws.iter_rows():
|
|
||||||
for cell in row:
|
|
||||||
if cell.value is None or not isinstance(cell.value, str):
|
|
||||||
continue
|
|
||||||
for err in excel_errors:
|
|
||||||
if err in cell.value:
|
|
||||||
error_details[err].append(f"{sheet_name}!{cell.coordinate}")
|
|
||||||
total_errors += 1
|
|
||||||
break
|
|
||||||
|
|
||||||
wb_values.close()
|
|
||||||
|
|
||||||
result = {
|
|
||||||
"status": "success" if total_errors == 0 else "errors_found",
|
|
||||||
"total_errors": total_errors,
|
|
||||||
"error_summary": {},
|
|
||||||
}
|
|
||||||
|
|
||||||
for err_type, locations in error_details.items():
|
|
||||||
if locations:
|
|
||||||
result["error_summary"][err_type] = {
|
|
||||||
"count": len(locations),
|
|
||||||
"locations": locations[:20],
|
|
||||||
}
|
|
||||||
|
|
||||||
wb_formulas = load_workbook(file_path, data_only=False)
|
|
||||||
formula_count = 0
|
|
||||||
for sheet_name in wb_formulas.sheetnames:
|
|
||||||
ws = wb_formulas[sheet_name]
|
|
||||||
for row in ws.iter_rows():
|
|
||||||
for cell in row:
|
|
||||||
if isinstance(cell.value, str) and cell.value.startswith("="):
|
|
||||||
formula_count += 1
|
|
||||||
wb_formulas.close()
|
|
||||||
|
|
||||||
result["total_formulas"] = formula_count
|
|
||||||
return result
|
|
||||||
|
|
||||||
except Exception as exc:
|
|
||||||
return {"error": str(exc)}
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
if len(sys.argv) < 2:
|
|
||||||
print("Usage: python recalc.py <excel_file> [timeout_seconds]")
|
|
||||||
print("\nRecalculates all formulas in an Excel file using LibreOffice")
|
|
||||||
print("\nReturns JSON with error details:")
|
|
||||||
print(" - status: 'success' or 'errors_found'")
|
|
||||||
print(" - total_errors: Total number of Excel errors found")
|
|
||||||
print(" - total_formulas: Number of formulas in the file")
|
|
||||||
print(" - error_summary: Breakdown by error type with locations")
|
|
||||||
print(" - #VALUE!, #DIV/0!, #REF!, #NAME?, #NULL!, #NUM!, #N/A")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
filename = sys.argv[1]
|
|
||||||
timeout = int(sys.argv[2]) if len(sys.argv) > 2 else 30
|
|
||||||
|
|
||||||
result = recalc(filename, timeout)
|
|
||||||
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
Reference in New Issue
Block a user