sagadeck 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +305 -0
- package/SKILL.md +148 -0
- package/bin/sagadeck.js +511 -0
- package/docs/ANDAMENTO.md +1255 -0
- package/docs/EXPERIENCIAS.md +39 -0
- package/docs/PLANO-EXPERIENCIAS.md +39 -0
- package/docs/PLANO-TESTE-AULAS-TECNICAS.md +44 -0
- package/docs/PLANO-TESTE-DESINFORMACAO.md +38 -0
- package/docs/REFERENCIA.md +1131 -0
- package/package.json +59 -0
- package/src/adaptive-layouts.js +18 -0
- package/src/ai/art-direction.js +8 -0
- package/src/ai/commands.js +130 -0
- package/src/ai/context.js +245 -0
- package/src/ai/deck-ai.js +1135 -0
- package/src/ai/directions.js +74 -0
- package/src/ai/document-materials.js +49 -0
- package/src/ai/document-visuals.js +215 -0
- package/src/ai/evaluation.js +31 -0
- package/src/ai/ground.js +55 -0
- package/src/ai/llm.js +292 -0
- package/src/ai/quality.js +51 -0
- package/src/ai/relay.js +45 -0
- package/src/ai/staged-generation.js +24 -0
- package/src/ai/transform.js +858 -0
- package/src/ai/usage.js +17 -0
- package/src/ai/variety.js +80 -0
- package/src/ai/video-frames.js +24 -0
- package/src/ai/video-generation.js +126 -0
- package/src/ai/web-operation.js +31 -0
- package/src/algo-catalog.js +153 -0
- package/src/algo-trace.js +139 -0
- package/src/api-client.js +514 -0
- package/src/api-collections.js +14 -0
- package/src/api-demo.js +222 -0
- package/src/aspect.js +40 -0
- package/src/build.js +323 -0
- package/src/carousel.js +56 -0
- package/src/chrome.js +65 -0
- package/src/code-highlight.js +68 -0
- package/src/code-lab-browser.js +22 -0
- package/src/code-lab.js +3 -0
- package/src/code-language.js +52 -0
- package/src/csv.js +74 -0
- package/src/decision-lab.js +20 -0
- package/src/deck-file.js +119 -0
- package/src/diagram/napkin-examples.js +13 -0
- package/src/diagram/napkin.js +539 -0
- package/src/docx.js +128 -0
- package/src/dynamics/git-sim.js +200 -0
- package/src/dynamics/layouts.js +112 -0
- package/src/elements.js +554 -0
- package/src/experiences.js +124 -0
- package/src/exploration.js +31 -0
- package/src/export/browser.js +53 -0
- package/src/export/estudo.js +67 -0
- package/src/export/pptx.js +475 -0
- package/src/export/roteiro.js +40 -0
- package/src/export/shots.js +182 -0
- package/src/figures/charts.js +383 -0
- package/src/figures/diagrams.js +117 -0
- package/src/figures/icons.js +166 -0
- package/src/figures/material-pictos.js +20 -0
- package/src/figures/pictos.js +255 -0
- package/src/figures/points.js +106 -0
- package/src/figures/qr.js +29 -0
- package/src/figures/ufmap.js +48 -0
- package/src/fiscal/autofix.js +377 -0
- package/src/fiscal/normalize.js +115 -0
- package/src/graph-lab.js +24 -0
- package/src/hologram.js +30 -0
- package/src/identity.js +86 -0
- package/src/import/crop.js +59 -0
- package/src/import/index.js +132 -0
- package/src/import/legacy-word.js +42 -0
- package/src/import/merge.js +45 -0
- package/src/import/office.js +123 -0
- package/src/import/omml.js +69 -0
- package/src/import/pdf-render.js +56 -0
- package/src/import/pptx.js +701 -0
- package/src/import/xml.js +42 -0
- package/src/infographic.js +401 -0
- package/src/layouts.js +723 -0
- package/src/lessons.js +171 -0
- package/src/library.js +399 -0
- package/src/markup.js +116 -0
- package/src/master.js +214 -0
- package/src/mcp/server.js +371 -0
- package/src/meeting.js +42 -0
- package/src/motion.js +56 -0
- package/src/package.js +116 -0
- package/src/playground.js +19 -0
- package/src/portal-scene.js +20 -0
- package/src/preferences.js +61 -0
- package/src/protect.js +51 -0
- package/src/purpose.js +24 -0
- package/src/pytrace.js +1092 -0
- package/src/research/duckduckgo.js +73 -0
- package/src/research/research.js +202 -0
- package/src/research/svg-image.js +17 -0
- package/src/research/visuals.js +52 -0
- package/src/runtime/api-collection-ui.js +5 -0
- package/src/runtime/api-collections.js +28 -0
- package/src/runtime/api-core.js +530 -0
- package/src/runtime/api-ui.js +966 -0
- package/src/runtime/base.css +1774 -0
- package/src/runtime/calc.js +139 -0
- package/src/runtime/code-lab.js +371 -0
- package/src/runtime/decision-lab.js +49 -0
- package/src/runtime/diagram.js +341 -0
- package/src/runtime/fit.js +268 -0
- package/src/runtime/fonts/caveat.css +2 -0
- package/src/runtime/fonts/comic-neue.css +3 -0
- package/src/runtime/fonts/fredoka.css +2 -0
- package/src/runtime/fonts/index.json +10 -0
- package/src/runtime/fonts/inter.css +2 -0
- package/src/runtime/fonts/patrick-hand.css +2 -0
- package/src/runtime/fonts/plus-jakarta-sans.css +2 -0
- package/src/runtime/fonts/press-start-2p.css +2 -0
- package/src/runtime/fonts/vt323.css +2 -0
- package/src/runtime/formula.js +148 -0
- package/src/runtime/graph-lab.js +72 -0
- package/src/runtime/meeting.css +7 -0
- package/src/runtime/meeting.js +10 -0
- package/src/runtime/motion.js +15 -0
- package/src/runtime/playground.js +11 -0
- package/src/runtime/points.js +19 -0
- package/src/runtime/portal-scene.css +6 -0
- package/src/runtime/portal-scene.js +22 -0
- package/src/runtime/runtime.js +827 -0
- package/src/runtime/science.js +89 -0
- package/src/runtime/skins/aluminio.css +31 -0
- package/src/runtime/skins/arcade.css +25 -0
- package/src/runtime/skins/aurora.css +17 -0
- package/src/runtime/skins/bauhaus.css +19 -0
- package/src/runtime/skins/editorial.css +19 -0
- package/src/runtime/skins/jornal.css +17 -0
- package/src/runtime/skins/manual-noite.css +14 -0
- package/src/runtime/skins/manual.css +14 -0
- package/src/runtime/skins/noite.css +21 -0
- package/src/runtime/skins/oceano.css +17 -0
- package/src/runtime/skins/pop.css +23 -0
- package/src/runtime/skins/prata.css +18 -0
- package/src/runtime/skins/prisma.css +28 -0
- package/src/runtime/skins/rabisco.css +18 -0
- package/src/runtime/skins/relevo.css +33 -0
- package/src/runtime/skins/sinal.css +16 -0
- package/src/runtime/skins/terminal.css +23 -0
- package/src/runtime/vendor/KATEX-LICENSE.txt +21 -0
- package/src/runtime/vendor/MERMAID-LICENSE.txt +21 -0
- package/src/runtime/vendor/PLOTLY-LICENSE.txt +21 -0
- package/src/runtime/vendor/katex.css +1 -0
- package/src/runtime/vendor/mermaid.min.js +3636 -0
- package/src/runtime/vendor/plotly.min.js +3620 -0
- package/src/runtime/video-player.js +35 -0
- package/src/science.js +162 -0
- package/src/studio/ai-response.js +36 -0
- package/src/studio/api-routes.js +152 -0
- package/src/studio/assets/contexto/leia-me.md +12 -0
- package/src/studio/assets/contexto/vendas.csv +7 -0
- package/src/studio/assets/imagens/colecao-arquitetura.png +0 -0
- package/src/studio/assets/imagens/colecao-cor.png +0 -0
- package/src/studio/assets/imagens/colecao-equipe.png +0 -0
- package/src/studio/assets/imagens/lavanda-cover.jpg +0 -0
- package/src/studio/code-routes.js +32 -0
- package/src/studio/command-approval.js +48 -0
- package/src/studio/demo-decks.js +98 -0
- package/src/studio/direction-routes.js +29 -0
- package/src/studio/errors.js +65 -0
- package/src/studio/exploration-demo.js +17 -0
- package/src/studio/exporting.js +105 -0
- package/src/studio/generation-report.js +10 -0
- package/src/studio/instance.js +48 -0
- package/src/studio/layout-samples.js +152 -0
- package/src/studio/project.js +371 -0
- package/src/studio/public/api-collections.js +28 -0
- package/src/studio/public/app.js +5265 -0
- package/src/studio/public/art-preview.js +10 -0
- package/src/studio/public/calc-fields.js +11 -0
- package/src/studio/public/chat-progress.js +47 -0
- package/src/studio/public/developer-fields.js +22 -0
- package/src/studio/public/explorer.js +443 -0
- package/src/studio/public/history.js +58 -0
- package/src/studio/public/index.html +867 -0
- package/src/studio/public/inspector.js +270 -0
- package/src/studio/public/lab-fields.js +5 -0
- package/src/studio/public/library.css +275 -0
- package/src/studio/public/library.html +55 -0
- package/src/studio/public/library.js +471 -0
- package/src/studio/public/merge-decks.js +155 -0
- package/src/studio/public/review-ui.js +100 -0
- package/src/studio/public/screenshot-editor.js +140 -0
- package/src/studio/public/share-ui.js +72 -0
- package/src/studio/public/slide-clipboard.js +23 -0
- package/src/studio/public/slide-form.js +1263 -0
- package/src/studio/public/studio-awareness.js +154 -0
- package/src/studio/public/studio-next.css +303 -0
- package/src/studio/public/style.css +1262 -0
- package/src/studio/public/ui-icons.js +2 -0
- package/src/studio/public/viewers.js +372 -0
- package/src/studio/public/visual-editor.js +326 -0
- package/src/studio/public/workspace.js +18 -0
- package/src/studio/public-files.js +1 -0
- package/src/studio/request-helpers.js +41 -0
- package/src/studio/server.js +1583 -0
- package/src/studio/share-routes.js +146 -0
- package/src/studio/slide-copy-routes.js +26 -0
- package/src/studio/slide-copy.js +30 -0
- package/src/studio/snapshot.js +160 -0
- package/src/studio/style-routes.js +103 -0
- package/src/studio/template-collections.js +97 -0
- package/src/studio/versions.js +95 -0
- package/src/studio/video-download.js +29 -0
- package/src/studio/visual-keys.js +52 -0
- package/src/studio/workspaces.js +39 -0
- package/src/table.js +93 -0
- package/src/templates/scaffold.js +372 -0
- package/src/themes.js +604 -0
- package/src/trace-view.js +450 -0
- package/src/uid.js +19 -0
- package/src/video-player.js +40 -0
- package/src/visual-edits.js +99 -0
- package/src/ws.js +134 -0
- package/templates/cenario/Texto no cen/303/241rio.yaml" +171 -0
- package/templates/cenario/imagens/cidade.svg +30 -0
- package/templates/cenario/imagens/pessoa.svg +15 -0
- package/templates/cenario-e-ciencia.yaml +47 -0
- package/templates/ensaio-api.yaml +160 -0
- package/templates/exemplo-alegre.yaml +75 -0
- package/templates/exemplo-keynote.yaml +90 -0
- package/templates/exemplo-napkin.yaml +139 -0
- package/templates/exemplo.yaml +228 -0
|
@@ -0,0 +1,701 @@
|
|
|
1
|
+
// Importar .pptx fielmente: cada slide vira um slide `canvas` com os elementos na mesma posição (a tela do sagadeck
|
|
2
|
+
// tem 1920 px de largura; a altura acompanha a proporção do arquivo). Lê o que o PowerPoint lê:
|
|
3
|
+
// - texto: caixas com parágrafos e trechos (fonte, tamanho, negrito, itálico, sublinhado, cor, sobrescrito),
|
|
4
|
+
// marcadores (inclusive os de Wingdings, desenhados), recuo por nível, alinhamento, ancoragem e o auto-ajuste
|
|
5
|
+
// (fontScale); a herança completa: slide > layout > mestre > estilos do mestre > padrão da apresentação
|
|
6
|
+
// - formas: geometria pronta (retângulo, elipse, setas, conectores…) e desenho livre (custGeom), preenchimento
|
|
7
|
+
// sólido/gradiente, contorno, tracejado, pontas de seta; grupos achatados
|
|
8
|
+
// - imagens (com recorte), tabelas (com o estilo de tabela do arquivo), equações (OMML → LaTeX), fundo do slide
|
|
9
|
+
// - o que o mestre e o layout desenham em todo slide (logos, faixas), anotações do apresentador
|
|
10
|
+
// O leitor é puro: devolve o deck e a lista de mídias; quem grava no disco é o chamador (src/import/index.js).
|
|
11
|
+
import JSZip from "jszip";
|
|
12
|
+
import { parseXML, kids, kid, path, all, textOf, num } from "./xml.js";
|
|
13
|
+
import { ommlToLatex } from "./omml.js";
|
|
14
|
+
|
|
15
|
+
const W = 1920;
|
|
16
|
+
const r1 = (v) => Math.round(v * 10) / 10;
|
|
17
|
+
|
|
18
|
+
// ------------------------------------------------------------------------------------------------ cores
|
|
19
|
+
const PRESET = { black: "000000", white: "FFFFFF", red: "FF0000", green: "008000", blue: "0000FF", yellow: "FFFF00", gray: "808080", grey: "808080", darkGray: "A9A9A9", lightGray: "D3D3D3", orange: "FFA500", navy: "000080", darkBlue: "00008B", darkRed: "8B0000", darkGreen: "006400", cyan: "00FFFF", magenta: "FF00FF", purple: "800080", silver: "C0C0C0", maroon: "800000", teal: "008080", olive: "808000", lime: "00FF00", aqua: "00FFFF" };
|
|
20
|
+
const hexToRgb = (h) => [0, 2, 4].map((i) => parseInt(h.slice(i, i + 2), 16));
|
|
21
|
+
const rgbToHex = (rgb) => rgb.map((v) => Math.max(0, Math.min(255, Math.round(v))).toString(16).padStart(2, "0")).join("").toUpperCase();
|
|
22
|
+
function rgbToHsl([r, g, b]) {
|
|
23
|
+
r /= 255; g /= 255; b /= 255;
|
|
24
|
+
const mx = Math.max(r, g, b), mn = Math.min(r, g, b), l = (mx + mn) / 2;
|
|
25
|
+
if (mx === mn) return [0, 0, l];
|
|
26
|
+
const d = mx - mn, s = l > 0.5 ? d / (2 - mx - mn) : d / (mx + mn);
|
|
27
|
+
const h = mx === r ? (g - b) / d + (g < b ? 6 : 0) : mx === g ? (b - r) / d + 2 : (r - g) / d + 4;
|
|
28
|
+
return [h / 6, s, l];
|
|
29
|
+
}
|
|
30
|
+
function hslToRgb([h, s, l]) {
|
|
31
|
+
if (s === 0) return [l * 255, l * 255, l * 255];
|
|
32
|
+
const q = l < 0.5 ? l * (1 + s) : l + s - l * s, p = 2 * l - q;
|
|
33
|
+
const f = (t) => { t = (t + 1) % 1; return t < 1 / 6 ? p + (q - p) * 6 * t : t < 1 / 2 ? q : t < 2 / 3 ? p + (q - p) * (2 / 3 - t) * 6 : p; };
|
|
34
|
+
return [f(h + 1 / 3) * 255, f(h) * 255, f(h - 1 / 3) * 255];
|
|
35
|
+
}
|
|
36
|
+
function applyMods(hex, node) {
|
|
37
|
+
let rgb = hexToRgb(hex), alpha = 1;
|
|
38
|
+
for (const m of kids(node)) {
|
|
39
|
+
const v = num(m.attrs.val) / 100000;
|
|
40
|
+
const n = m.name.split(":").pop();
|
|
41
|
+
if (n === "lumMod" || n === "lumOff") { const hsl = rgbToHsl(rgb); hsl[2] = n === "lumMod" ? hsl[2] * v : hsl[2] + v; hsl[2] = Math.max(0, Math.min(1, hsl[2])); rgb = hslToRgb(hsl); }
|
|
42
|
+
else if (n === "satMod") { const hsl = rgbToHsl(rgb); hsl[1] = Math.max(0, Math.min(1, hsl[1] * v)); rgb = hslToRgb(hsl); }
|
|
43
|
+
else if (n === "tint") rgb = rgb.map((c) => c + (255 - c) * (1 - v));
|
|
44
|
+
else if (n === "shade") rgb = rgb.map((c) => c * v);
|
|
45
|
+
else if (n === "alpha") alpha = v;
|
|
46
|
+
}
|
|
47
|
+
return { hex: rgbToHex(rgb), alpha };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ------------------------------------------------------------------------------------------------ leitura
|
|
51
|
+
export async function readPptx(buf) {
|
|
52
|
+
const zip = await JSZip.loadAsync(buf);
|
|
53
|
+
const xmlCache = new Map();
|
|
54
|
+
const xml = async (p) => {
|
|
55
|
+
if (!xmlCache.has(p)) { const f = zip.file(p); xmlCache.set(p, f ? parseXML(await f.async("string")) : null); }
|
|
56
|
+
return xmlCache.get(p);
|
|
57
|
+
};
|
|
58
|
+
const relsOf = async (part) => {
|
|
59
|
+
const dir = part.slice(0, part.lastIndexOf("/")), base = part.slice(part.lastIndexOf("/") + 1);
|
|
60
|
+
const r = await xml(`${dir}/_rels/${base}.rels`);
|
|
61
|
+
const map = new Map();
|
|
62
|
+
for (const rel of kids(r, "Relationship")) {
|
|
63
|
+
const t = rel.attrs.Target || "";
|
|
64
|
+
const target = rel.attrs.TargetMode === "External" ? t : resolvePath(dir, t);
|
|
65
|
+
map.set(rel.attrs.Id, { type: (rel.attrs.Type || "").split("/").pop(), target, external: rel.attrs.TargetMode === "External" });
|
|
66
|
+
}
|
|
67
|
+
return map;
|
|
68
|
+
};
|
|
69
|
+
const pres = await xml("ppt/presentation.xml");
|
|
70
|
+
if (!pres) throw new Error("Não parece um .pptx (falta ppt/presentation.xml).");
|
|
71
|
+
const sz = kid(pres, "sldSz");
|
|
72
|
+
const cx = num(sz?.attrs.cx, 12192000), cy = num(sz?.attrs.cy, 6858000);
|
|
73
|
+
const presRels = await relsOf("ppt/presentation.xml");
|
|
74
|
+
const slideFiles = kids(kid(pres, "sldIdLst"), "sldId").map((s) => presRels.get(s.attrs["r:id"])?.target).filter(Boolean);
|
|
75
|
+
const themeFile = [...presRels.values()].find((r) => r.type === "theme")?.target;
|
|
76
|
+
return { zip, xml, relsOf, pres, cx, cy, slideFiles, themeFile };
|
|
77
|
+
}
|
|
78
|
+
function resolvePath(dir, t) {
|
|
79
|
+
if (t.startsWith("/")) return t.slice(1);
|
|
80
|
+
const parts = dir.split("/");
|
|
81
|
+
for (const seg of t.split("/")) { if (seg === "..") parts.pop(); else if (seg !== ".") parts.push(seg); }
|
|
82
|
+
return parts.join("/");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ------------------------------------------------------------------------------------------------ importar
|
|
86
|
+
export async function importPptx(buf, { mediaDir = "imagens/original" } = {}) {
|
|
87
|
+
const P = await readPptx(buf);
|
|
88
|
+
const S = W / P.cx, H = Math.round(P.cy * S);
|
|
89
|
+
const PT = S * 12700; // px por ponto
|
|
90
|
+
// tema: cada mestre tem o seu (cores, fontes, estilos de preenchimento e linha)
|
|
91
|
+
const themeCache = new Map();
|
|
92
|
+
async function themeOf(file) {
|
|
93
|
+
if (themeCache.has(file)) return themeCache.get(file);
|
|
94
|
+
const theme = await P.xml(file);
|
|
95
|
+
const scheme = {};
|
|
96
|
+
for (const c of kids(path(theme, "themeElements", "clrScheme"))) {
|
|
97
|
+
const v = kid(c, "srgbClr")?.attrs.val || kid(c, "sysClr")?.attrs.lastClr;
|
|
98
|
+
if (v) scheme[c.name.split(":").pop()] = v.toUpperCase();
|
|
99
|
+
}
|
|
100
|
+
const fontScheme = path(theme, "themeElements", "fontScheme");
|
|
101
|
+
const fmt = path(theme, "themeElements", "fmtScheme");
|
|
102
|
+
const t = { scheme, fonts: { major: path(fontScheme, "majorFont", "latin")?.attrs.typeface || "Calibri Light", minor: path(fontScheme, "minorFont", "latin")?.attrs.typeface || "Calibri" }, bgFills: kids(kid(fmt, "bgFillStyleLst")), lnStyles: kids(kid(fmt, "lnStyleLst")), fillStyles: kids(kid(fmt, "fillStyleLst")) };
|
|
103
|
+
themeCache.set(file, t);
|
|
104
|
+
return t;
|
|
105
|
+
}
|
|
106
|
+
const base = await themeOf(P.themeFile || "ppt/theme/theme1.xml");
|
|
107
|
+
const { scheme, fonts } = base;
|
|
108
|
+
let { bgFills, lnStyles, fillStyles } = base;
|
|
109
|
+
const defaultText = kid(P.pres, "defaultTextStyle");
|
|
110
|
+
const tableStylesXml = await P.xml("ppt/tableStyles.xml");
|
|
111
|
+
const tableStyles = new Map(kids(tableStylesXml, "tblStyle").map((t) => [t.attrs.styleId, t]));
|
|
112
|
+
|
|
113
|
+
const media = new Map(); // caminho no zip -> { name, ext }
|
|
114
|
+
const mediaName = (target) => {
|
|
115
|
+
if (!media.has(target)) {
|
|
116
|
+
const base = target.split("/").pop();
|
|
117
|
+
media.set(target, { zipPath: target, name: `${mediaDir}/${base}`, ext: base.split(".").pop().toLowerCase() });
|
|
118
|
+
}
|
|
119
|
+
return media.get(target).name;
|
|
120
|
+
};
|
|
121
|
+
const warnings = [];
|
|
122
|
+
const dimCache = new Map();
|
|
123
|
+
async function imageDims(zipPath) {
|
|
124
|
+
if (dimCache.has(zipPath)) return dimCache.get(zipPath);
|
|
125
|
+
let d = null;
|
|
126
|
+
try {
|
|
127
|
+
const b = await P.zip.file(zipPath)?.async("uint8array");
|
|
128
|
+
if (b && b[0] === 0x89 && b[1] === 0x50) d = { w: (b[16] << 24) | (b[17] << 16) | (b[18] << 8) | b[19], h: (b[20] << 24) | (b[21] << 16) | (b[22] << 8) | b[23] };
|
|
129
|
+
else if (b && b[0] === 0x47 && b[1] === 0x49) d = { w: b[6] | (b[7] << 8), h: b[8] | (b[9] << 8) };
|
|
130
|
+
else if (b && b[0] === 0xff && b[1] === 0xd8) {
|
|
131
|
+
for (let i = 2; i < b.length - 9;) {
|
|
132
|
+
if (b[i] !== 0xff) { i++; continue; }
|
|
133
|
+
const m = b[i + 1], len = (b[i + 2] << 8) | b[i + 3];
|
|
134
|
+
if (m >= 0xc0 && m <= 0xcf && ![0xc4, 0xc8, 0xcc].includes(m)) { d = { h: (b[i + 5] << 8) | b[i + 6], w: (b[i + 7] << 8) | b[i + 8] }; break; }
|
|
135
|
+
i += 2 + len;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
} catch { d = null; }
|
|
139
|
+
dimCache.set(zipPath, d);
|
|
140
|
+
return d;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// cor de um nó (solidFill, srgbClr…) no contexto de um mapa de cores e de uma cor de "placeholder" (phClr)
|
|
144
|
+
const colorOf = (node, ctx) => {
|
|
145
|
+
if (!node) return null;
|
|
146
|
+
const c = ["srgbClr", "schemeClr", "sysClr", "prstClr", "scrgbClr", "hslClr"].map((n) => kid(node, n)).find(Boolean) || (/(srgbClr|schemeClr|sysClr|prstClr)$/.test(node.name) ? node : null);
|
|
147
|
+
if (!c) return null;
|
|
148
|
+
const n = c.name.split(":").pop();
|
|
149
|
+
let hex = null;
|
|
150
|
+
if (n === "srgbClr") hex = c.attrs.val;
|
|
151
|
+
else if (n === "sysClr") hex = c.attrs.lastClr || (c.attrs.val === "window" ? "FFFFFF" : "000000");
|
|
152
|
+
else if (n === "prstClr") hex = PRESET[c.attrs.val] || "000000";
|
|
153
|
+
else if (n === "scrgbClr") hex = rgbToHex(["r", "g", "b"].map((k) => (num(c.attrs[k]) / 100000) * 255));
|
|
154
|
+
else if (n === "schemeClr") {
|
|
155
|
+
let v = c.attrs.val;
|
|
156
|
+
if (v === "phClr") return ctx?.ph ? { ...ctx.ph, ...applyMods(ctx.ph.hex, c) } : null;
|
|
157
|
+
v = ctx?.clrMap?.[v] || { bg1: "lt1", tx1: "dk1", bg2: "lt2", tx2: "dk2" }[v] || v;
|
|
158
|
+
hex = (ctx?.scheme || scheme)[v];
|
|
159
|
+
}
|
|
160
|
+
if (!hex) return null;
|
|
161
|
+
return applyMods(hex.toUpperCase(), c);
|
|
162
|
+
};
|
|
163
|
+
const css = (c) => (!c ? null : c.alpha < 1 ? `rgba(${hexToRgb(c.hex).join(",")},${+c.alpha.toFixed(3)})` : `#${c.hex}`);
|
|
164
|
+
|
|
165
|
+
// mestre e layout de cada slide (com cache)
|
|
166
|
+
const partCache = new Map();
|
|
167
|
+
async function partInfo(file) {
|
|
168
|
+
if (partCache.has(file)) return partCache.get(file);
|
|
169
|
+
const x = await P.xml(file), rels = await P.relsOf(file);
|
|
170
|
+
const info = { file, x, rels, tree: path(x, "cSld", "spTree"), bg: path(x, "cSld", "bg"), clrMap: kid(x, "clrMap")?.attrs, phs: new Map() };
|
|
171
|
+
for (const sp of [...kids(info.tree, "sp"), ...kids(info.tree, "pic")]) {
|
|
172
|
+
const ph = placeholderOf(sp);
|
|
173
|
+
if (ph) { if (ph.idx != null) info.phs.set(`idx:${ph.idx}`, sp); if (!info.phs.has(`type:${ph.type}`)) info.phs.set(`type:${ph.type}`, sp); }
|
|
174
|
+
}
|
|
175
|
+
partCache.set(file, info);
|
|
176
|
+
return info;
|
|
177
|
+
}
|
|
178
|
+
const placeholderOf = (sp) => { const ph = all(sp, "ph")[0]; return ph ? { type: ph.attrs.type || "body", idx: ph.attrs.idx } : null; };
|
|
179
|
+
const findPh = (info, ph) => {
|
|
180
|
+
if (!info || !ph) return null;
|
|
181
|
+
const alias = { ctrTitle: "title", subTitle: "body", obj: "body" };
|
|
182
|
+
return (ph.idx != null && info.phs.get(`idx:${ph.idx}`)) || info.phs.get(`type:${ph.type}`) || info.phs.get(`type:${alias[ph.type] || ph.type}`) || (ph.type === "body" ? info.phs.get("type:obj") : null);
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const slides = [];
|
|
186
|
+
let masterOut = null;
|
|
187
|
+
for (let si = 0; si < P.slideFiles.length; si++) {
|
|
188
|
+
const file = P.slideFiles[si];
|
|
189
|
+
const slide = await partInfo(file);
|
|
190
|
+
const layoutFile = [...slide.rels.values()].find((r) => r.type === "slideLayout")?.target;
|
|
191
|
+
const layout = layoutFile ? await partInfo(layoutFile) : null;
|
|
192
|
+
const masterFile = layout ? [...layout.rels.values()].find((r) => r.type === "slideMaster")?.target : null;
|
|
193
|
+
const master = masterFile ? await partInfo(masterFile) : null;
|
|
194
|
+
const cmap = { ...(master?.clrMap || { bg1: "lt1", tx1: "dk1", bg2: "lt2", tx2: "dk2" }), ...(path(slide.x, "clrMapOvr", "overrideClrMapping")?.attrs || {}) };
|
|
195
|
+
const txStyles = kid(master?.x, "txStyles");
|
|
196
|
+
const mt = master ? await themeOf([...master.rels.values()].find((r) => r.type === "theme")?.target || P.themeFile || "ppt/theme/theme1.xml") : base;
|
|
197
|
+
({ bgFills, lnStyles, fillStyles } = mt);
|
|
198
|
+
const env = { slide, layout, master, clrMap: cmap, txStyles, scheme: mt.scheme, fonts: mt.fonts };
|
|
199
|
+
const elements = [];
|
|
200
|
+
|
|
201
|
+
// fundo
|
|
202
|
+
const bgNode = slide.bg || layout?.bg || master?.bg;
|
|
203
|
+
const bgPart = slide.bg ? slide : layout?.bg ? layout : master;
|
|
204
|
+
const background = backgroundOf(bgNode, bgPart, env);
|
|
205
|
+
|
|
206
|
+
// o que o mestre e o layout desenham (sem os placeholders), se o slide/layout não desligar
|
|
207
|
+
const showMaster = slide.x?.attrs?.showMasterSp !== "0" && layout?.x?.attrs?.showMasterSp !== "0";
|
|
208
|
+
const deco = [];
|
|
209
|
+
if (showMaster && master) await walk(master.tree, master, env, deco, { deco: true });
|
|
210
|
+
if (layout) await walk(layout.tree, layout, env, deco, { deco: true });
|
|
211
|
+
elements.push(...deco);
|
|
212
|
+
await walk(slide.tree, slide, env, elements, {});
|
|
213
|
+
|
|
214
|
+
// título (para a lista de slides e para a IA) e anotações
|
|
215
|
+
const titleEl = elements.find((e) => e.ph === "title" || e.ph === "ctrTitle");
|
|
216
|
+
const title = titleEl ? plainOf(titleEl) : (elements.find((e) => e.textbox && !e.deco && plainOf(e).trim().length > 2 && !/^\d+$/.test(plainOf(e).trim())) ? plainOf(elements.find((e) => e.textbox && !e.deco && plainOf(e).trim().length > 2 && !/^\d+$/.test(plainOf(e).trim()))) : "");
|
|
217
|
+
const notesFile = [...slide.rels.values()].find((r) => r.type === "notesSlide")?.target;
|
|
218
|
+
let notes = "";
|
|
219
|
+
if (notesFile) {
|
|
220
|
+
const nx = await P.xml(notesFile);
|
|
221
|
+
for (const sp of kids(path(nx, "cSld", "spTree"), "sp")) {
|
|
222
|
+
const ph = placeholderOf(sp);
|
|
223
|
+
if (ph && ph.type !== "body") continue;
|
|
224
|
+
notes += kids(kid(sp, "txBody"), "p").map((p) => all(p, "t").map(textOf).join("")).join("\n");
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
slides.push({ n: si + 1, file, hidden: slide.x?.attrs?.show === "0", layoutName: path(layout?.x, "cSld")?.attrs?.name || "", title: title.replace(/\s+/g, " ").trim().slice(0, 160), background, elements, notes: notes.trim(), decoCount: deco.length });
|
|
228
|
+
if (!masterOut && master) masterOut = { file: master.file, background, deco: deco.map((d) => ({ ...d })) };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function backgroundOf(bg, part, env) {
|
|
232
|
+
if (!bg) return { color: "FFFFFF" };
|
|
233
|
+
const pr = kid(bg, "bgPr");
|
|
234
|
+
if (pr) {
|
|
235
|
+
const blip = path(pr, "blipFill", "blip");
|
|
236
|
+
if (blip) { const t = part.rels.get(blip.attrs["r:embed"])?.target; if (t) return { image: mediaName(t) }; }
|
|
237
|
+
const f = fillOf(pr, env, null);
|
|
238
|
+
return f ? (f.gradient ? { gradient: f.gradient } : { color: f.hex, alpha: f.alpha }) : { color: "FFFFFF" };
|
|
239
|
+
}
|
|
240
|
+
const ref = kid(bg, "bgRef");
|
|
241
|
+
if (ref) {
|
|
242
|
+
const idx = num(ref.attrs.idx);
|
|
243
|
+
const ph = colorOf(ref, env);
|
|
244
|
+
const style = idx >= 1001 ? bgFills[idx - 1001] : fillStyles[idx - 1];
|
|
245
|
+
if (style && style.name.endsWith("solidFill")) return { color: (colorOf(style, { ...env, ph }) || ph)?.hex || "FFFFFF" };
|
|
246
|
+
if (ph) return { color: ph.hex };
|
|
247
|
+
}
|
|
248
|
+
return { color: "FFFFFF" };
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// preenchimento de spPr (ou bgPr): { hex, alpha } | { gradient } | { none: true } | null (não definido)
|
|
252
|
+
function fillOf(pr, env, phClr, part = null) {
|
|
253
|
+
if (!pr) return null;
|
|
254
|
+
const bf = kid(pr, "blipFill");
|
|
255
|
+
if (bf && part) {
|
|
256
|
+
const t = part.rels.get(kid(bf, "blip")?.attrs["r:embed"])?.target;
|
|
257
|
+
if (t) { const tile = kid(bf, "tile"); return { hex: "FFFFFF", alpha: 1, image: mediaName(t), zip: t, tile: tile ? { sx: num(tile.attrs.sx, 100000) / 100000, sy: num(tile.attrs.sy, 100000) / 100000 } : null }; }
|
|
258
|
+
}
|
|
259
|
+
if (kid(pr, "noFill")) return { none: true };
|
|
260
|
+
const sf = kid(pr, "solidFill");
|
|
261
|
+
if (sf) return colorOf(sf, { ...env, ph: phClr });
|
|
262
|
+
const gf = kid(pr, "gradFill");
|
|
263
|
+
if (gf) {
|
|
264
|
+
const stops = kids(kid(gf, "gsLst"), "gs").map((g) => ({ pos: num(g.attrs.pos) / 1000, c: colorOf(g, { ...env, ph: phClr }) })).filter((s) => s.c);
|
|
265
|
+
if (!stops.length) return null;
|
|
266
|
+
const ang = num(kid(gf, "lin")?.attrs.ang) / 60000;
|
|
267
|
+
return { hex: stops[0].c.hex, alpha: stops[0].c.alpha, gradient: { angle: ang, stops: stops.map((s) => ({ pos: s.pos, color: css(s.c) })) } };
|
|
268
|
+
}
|
|
269
|
+
const pf = kid(pr, "pattFill");
|
|
270
|
+
if (pf) {
|
|
271
|
+
const fg = colorOf(kid(pf, "fgClr"), { ...env, ph: phClr }) || { hex: "000000", alpha: 1 }, bg = colorOf(kid(pf, "bgClr"), { ...env, ph: phClr }) || { hex: "FFFFFF", alpha: 1 };
|
|
272
|
+
return { ...bg, pattern: { prst: pf.attrs.prst || "pct50", fg: css(fg), bg: css(bg) } };
|
|
273
|
+
}
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// percorre a árvore de formas (grupos achatados): cada forma vira um elemento do canvas
|
|
278
|
+
async function walk(tree, part, env, out, opts, tf = null) {
|
|
279
|
+
for (const node of kids(tree)) {
|
|
280
|
+
const n = node.name.split(":").pop();
|
|
281
|
+
try {
|
|
282
|
+
if (n === "grpSp") {
|
|
283
|
+
const x = path(node, "grpSpPr", "xfrm");
|
|
284
|
+
const off = kid(x, "off"), ext = kid(x, "ext"), chOff = kid(x, "chOff"), chExt = kid(x, "chExt");
|
|
285
|
+
const g = { ox: num(off?.attrs.x), oy: num(off?.attrs.y), sx: num(ext?.attrs.cx) / (num(chExt?.attrs.cx) || 1) || 1, sy: num(ext?.attrs.cy) / (num(chExt?.attrs.cy) || 1) || 1, cx: num(chOff?.attrs.x), cy: num(chOff?.attrs.y) };
|
|
286
|
+
const composed = (px, py) => { const X = g.ox + (px - g.cx) * g.sx, Y = g.oy + (py - g.cy) * g.sy; return tf ? tf.map(X, Y) : [X, Y]; };
|
|
287
|
+
const scale = { x: g.sx * (tf?.scale.x || 1), y: g.sy * (tf?.scale.y || 1) };
|
|
288
|
+
await walk(node, part, env, out, opts, { map: composed, scale });
|
|
289
|
+
} else if (n === "sp" || n === "cxnSp") await shape(node, part, env, out, opts, tf);
|
|
290
|
+
else if (n === "pic") await picture(node, part, env, out, opts, tf);
|
|
291
|
+
else if (n === "graphicFrame") await frame(node, part, env, out, opts, tf);
|
|
292
|
+
else if (n === "AlternateContent") {
|
|
293
|
+
const choice = kid(node, "Choice"), fb = kid(node, "Fallback");
|
|
294
|
+
const use = choice && all(choice, "oMath").length ? choice : fb || choice;
|
|
295
|
+
if (use) await walk(use, part, env, out, opts, tf);
|
|
296
|
+
}
|
|
297
|
+
} catch (e) { warnings.push(`slide ${slides.length + 1}: ${n} ignorado (${e.message})`); }
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
function boxOf(xfrm, tf) {
|
|
301
|
+
const off = kid(xfrm, "off"), ext = kid(xfrm, "ext");
|
|
302
|
+
if (!off || !ext) return null;
|
|
303
|
+
let x = num(off.attrs.x), y = num(off.attrs.y), w = num(ext.attrs.cx), h = num(ext.attrs.cy);
|
|
304
|
+
if (tf) { const [X, Y] = tf.map(x, y); x = X; y = Y; w *= tf.scale.x; h *= tf.scale.y; }
|
|
305
|
+
return { x: r1(x * S), y: r1(y * S), w: r1(Math.max(0, w * S)), h: r1(Math.max(0, h * S)), rot: num(xfrm.attrs.rot) / 60000, flipH: xfrm.attrs.flipH === "1", flipV: xfrm.attrs.flipV === "1" };
|
|
306
|
+
}
|
|
307
|
+
// xfrm da forma, ou herdado do placeholder do layout/mestre
|
|
308
|
+
function xfrmOf(node, part, env, prName = "spPr") {
|
|
309
|
+
const own = path(node, prName, "xfrm");
|
|
310
|
+
if (own && kid(own, "off")) return own;
|
|
311
|
+
const ph = placeholderOf(node);
|
|
312
|
+
if (!ph) return own;
|
|
313
|
+
for (const info of part === env.slide ? [env.layout, env.master] : part === env.layout ? [env.master] : []) {
|
|
314
|
+
const p = findPh(info, ph);
|
|
315
|
+
const x = p && path(p, "spPr", "xfrm");
|
|
316
|
+
if (x && kid(x, "off")) return x;
|
|
317
|
+
}
|
|
318
|
+
return own;
|
|
319
|
+
}
|
|
320
|
+
function phChain(node, part, env) {
|
|
321
|
+
const ph = placeholderOf(node);
|
|
322
|
+
if (!ph) return [];
|
|
323
|
+
const chain = [];
|
|
324
|
+
for (const info of part === env.slide ? [env.layout, env.master] : part === env.layout ? [env.master] : []) { const p = findPh(info, ph); if (p) chain.push(p); }
|
|
325
|
+
return chain;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
async function shape(node, part, env, out, opts, tf) {
|
|
329
|
+
const ph = placeholderOf(node);
|
|
330
|
+
if (opts.deco && ph) return; // placeholders do mestre/layout só valem pelo estilo
|
|
331
|
+
const box = boxOf(xfrmOf(node, part, env), tf);
|
|
332
|
+
if (!box) return;
|
|
333
|
+
const spPr = kid(node, "spPr"), style = kid(node, "style");
|
|
334
|
+
const chain = phChain(node, part, env);
|
|
335
|
+
// cores da referência de estilo (p:style): o PowerPoint pinta formas "padrão" por aqui
|
|
336
|
+
const fillRef = kid(style, "fillRef"), lnRef = kid(style, "lnRef"), fontRef = kid(style, "fontRef");
|
|
337
|
+
let fill = fillOf(spPr, env, null, part);
|
|
338
|
+
for (const c of chain) if (fill == null) fill = fillOf(kid(c, "spPr"), env, null);
|
|
339
|
+
if (fill == null && fillRef && num(fillRef.attrs.idx) > 0) { const phc = colorOf(fillRef, env); const fs = fillStyles[num(fillRef.attrs.idx) - 1]; fill = (fs && fillOf({ children: [fs] }, env, phc)) || phc; }
|
|
340
|
+
const lnNode = kid(spPr, "ln") || chain.map((c) => path(c, "spPr", "ln")).find(Boolean);
|
|
341
|
+
let line = null;
|
|
342
|
+
const lnRefColor = lnRef && num(lnRef.attrs.idx) > 0 ? colorOf(lnRef, env) : null;
|
|
343
|
+
if (lnNode && kid(lnNode, "noFill")) line = null;
|
|
344
|
+
else {
|
|
345
|
+
const lc = lnNode && kid(lnNode, "solidFill") ? colorOf(kid(lnNode, "solidFill"), env) : lnRefColor;
|
|
346
|
+
if (lc) {
|
|
347
|
+
const refW = lnRef ? num(lnStyles[num(lnRef.attrs.idx) - 1]?.attrs.w, 9525) : 9525;
|
|
348
|
+
line = { color: css(lc), width: Math.max(0.6, num(lnNode?.attrs.w, refW) * S), dash: kid(lnNode, "prstDash")?.attrs.val, head: kid(lnNode, "headEnd")?.attrs.type, tail: kid(lnNode, "tailEnd")?.attrs.type, headLen: kid(lnNode, "headEnd")?.attrs.len, tailLen: kid(lnNode, "tailEnd")?.attrs.len };
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
const geom = kid(spPr, "prstGeom")?.attrs.prst || (kid(spPr, "custGeom") ? "cust" : chain.map((c) => path(c, "spPr", "prstGeom")?.attrs.prst).find(Boolean) || "rect");
|
|
352
|
+
if (fill?.tile && fill.zip) { const dim = await imageDims(fill.zip); if (dim) { fill.tileW = r1(dim.w * 9525 * S * fill.tile.sx); fill.tileH = r1(dim.h * 9525 * S * fill.tile.sy); } }
|
|
353
|
+
const drawn = fill && !fill.none || line;
|
|
354
|
+
if (drawn && box.w + box.h > 0) {
|
|
355
|
+
const svg = shapeSVG(geom, box, fill && !fill.none ? fill : null, line, kid(spPr, "custGeom"), kid(spPr, "prstGeom"));
|
|
356
|
+
if (svg) out.push({ drawing: svg, x: box.x, y: box.y, w: Math.max(box.w, 1), h: Math.max(box.h, 1), ...(box.rot ? { rotate: r1(box.rot) } : {}), ...(opts.deco ? { deco: true } : {}), kind: "shape" });
|
|
357
|
+
}
|
|
358
|
+
// texto
|
|
359
|
+
const tx = kid(node, "txBody");
|
|
360
|
+
if (tx && all(tx, "t").some((t) => textOf(t).length) || tx && all(tx, "oMath").length) {
|
|
361
|
+
const fontColor = fontRef ? colorOf(fontRef, env) : null;
|
|
362
|
+
const tb = textBody(tx, node, part, env, chain, ph, fontColor);
|
|
363
|
+
if (tb.paragraphs.length) out.push({ textbox: tb, x: box.x, y: box.y, w: box.w, h: box.h, ...(box.rot ? { rotate: r1(box.rot) } : {}), ...(ph ? { ph: ph.type } : {}), ...(opts.deco ? { deco: true } : {}) });
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
async function picture(node, part, env, out, opts, tf) {
|
|
368
|
+
if (opts.deco && placeholderOf(node)) return;
|
|
369
|
+
const box = boxOf(xfrmOf(node, part, env), tf);
|
|
370
|
+
const blip = path(node, "blipFill", "blip");
|
|
371
|
+
if (!box || !blip) return;
|
|
372
|
+
const t = part.rels.get(blip.attrs["r:embed"])?.target;
|
|
373
|
+
if (!t) return;
|
|
374
|
+
const src = kid(kid(node, "blipFill"), "srcRect");
|
|
375
|
+
const crop = src ? { l: num(src.attrs.l) / 100000, t: num(src.attrs.t) / 100000, r: num(src.attrs.r) / 100000, b: num(src.attrs.b) / 100000 } : null;
|
|
376
|
+
const alt = kid(path(node, "nvPicPr"), "cNvPr")?.attrs.descr || "";
|
|
377
|
+
const ln = path(node, "spPr", "ln");
|
|
378
|
+
const lc = ln && !kid(ln, "noFill") && kid(ln, "solidFill") ? colorOf(kid(ln, "solidFill"), env) : null;
|
|
379
|
+
const outline = lc ? `outline:${Math.max(0.6, num(ln.attrs.w, 9525) * S).toFixed(1)}px solid ${css(lc)};outline-offset:-${(Math.max(0.6, num(ln.attrs.w, 9525) * S) / 2).toFixed(1)}px;` : "";
|
|
380
|
+
out.push({ image: mediaName(t), x: box.x, y: box.y, w: box.w, h: box.h, fit: "fill", ...(crop && (crop.l || crop.t || crop.r || crop.b) ? { crop: { l: r3(crop.l), t: r3(crop.t), r: r3(crop.r), b: r3(crop.b) } } : {}), ...(box.rot ? { rotate: r1(box.rot) } : {}), ...(box.flipH ? { flipH: true } : {}), ...(alt ? { alt } : {}), ...(outline ? { style: outline } : {}), ...(opts.deco ? { deco: true } : {}) });
|
|
381
|
+
}
|
|
382
|
+
function r3(v) { return Math.round(v * 1000) / 1000; }
|
|
383
|
+
|
|
384
|
+
async function frame(node, part, env, out, opts, tf) {
|
|
385
|
+
const box = boxOf(kid(node, "xfrm"), tf);
|
|
386
|
+
const gd = path(node, "graphic", "graphicData");
|
|
387
|
+
if (!box || !gd) return;
|
|
388
|
+
const uri = gd.attrs.uri || "";
|
|
389
|
+
if (uri.endsWith("/table")) out.push({ ...tableEl(kid(gd, "tbl"), box, env), x: box.x, y: box.y, w: box.w, h: box.h });
|
|
390
|
+
else if (uri.endsWith("/chart")) {
|
|
391
|
+
const rid = all(gd, "chart")[0]?.attrs["r:id"];
|
|
392
|
+
const t = part.rels.get(rid)?.target;
|
|
393
|
+
const ch = t ? await chartEl(t) : null;
|
|
394
|
+
if (ch) out.push({ ...ch, x: box.x, y: box.y, w: box.w, h: box.h });
|
|
395
|
+
} else {
|
|
396
|
+
// objeto OLE (equação do Equation Editor, planilha…): a imagem de prévia que o PowerPoint guarda
|
|
397
|
+
const pic = all(gd, "pic")[0];
|
|
398
|
+
if (pic) { await picture(pic, part, env, out, opts, tf); const last = out[out.length - 1]; if (last?.image) Object.assign(last, { x: box.x, y: box.y, w: box.w, h: box.h, ole: true }); }
|
|
399
|
+
else {
|
|
400
|
+
const ole = all(gd, "oleObj")[0];
|
|
401
|
+
const t = ole && part.rels.get(ole.attrs["r:id"])?.target;
|
|
402
|
+
const img = ole && [...part.rels.values()].find((r) => r.type === "image" && r.target && t);
|
|
403
|
+
if (img) out.push({ image: mediaName(img.target), x: box.x, y: box.y, w: box.w, h: box.h, fit: "fill", ole: true });
|
|
404
|
+
// SmartArt e o que mais não sei desenhar: recortado da foto do original, se houver (src/import/index.js)
|
|
405
|
+
else out.push({ fromSnapshot: uri.split("/").pop() || "objeto", x: box.x, y: box.y, w: box.w, h: box.h });
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// ---------------------------------------------------------------------------------------------- texto
|
|
411
|
+
function lvlProps(list, lvl) { return list ? kid(list, `lvl${lvl + 1}pPr`) : null; }
|
|
412
|
+
function textBody(tx, node, part, env, chain, ph, fontColor) {
|
|
413
|
+
const bodyPr = kid(tx, "bodyPr");
|
|
414
|
+
const bodyChain = [bodyPr, ...chain.map((c) => path(c, "txBody", "bodyPr"))].filter(Boolean);
|
|
415
|
+
const bp = (k, d) => { for (const b of bodyChain) if (b.attrs[k] != null) return b.attrs[k]; return d; };
|
|
416
|
+
const autofit = bodyChain.map((b) => kid(b, "normAutofit")).find(Boolean);
|
|
417
|
+
const fontScale = autofit ? num(autofit.attrs.fontScale, 100000) / 100000 : 1;
|
|
418
|
+
const lnReduce = autofit ? num(autofit.attrs.lnSpcReduction, 0) / 100000 : 0;
|
|
419
|
+
// listas de estilo em ordem de prioridade (primeiro que definir ganha)
|
|
420
|
+
const kind = !ph ? "other" : ["title", "ctrTitle"].includes(ph.type) ? "title" : ["body", "subTitle", "obj"].includes(ph.type) ? "body" : "other";
|
|
421
|
+
// caixa de texto comum (sem placeholder) segue o padrão da apresentação; o otherStyle do mestre é dos
|
|
422
|
+
// placeholders que não são título nem corpo (data, rodapé, número)
|
|
423
|
+
const lists = [kid(tx, "lstStyle"), ...chain.map((c) => path(c, "txBody", "lstStyle")), kind === "title" ? kid(env.txStyles, "titleStyle") : kind === "body" ? kid(env.txStyles, "bodyStyle") : ph ? kid(env.txStyles, "otherStyle") : null, defaultText].filter(Boolean);
|
|
424
|
+
const pick = (lvl, get) => { for (const l of lists) { const p = lvlProps(l, lvl); const v = p && get(p); if (v != null) return v; } return null; };
|
|
425
|
+
const paragraphs = [];
|
|
426
|
+
let autoNum = new Map();
|
|
427
|
+
for (const p of kids(tx, "p")) {
|
|
428
|
+
const pPr = kid(p, "pPr");
|
|
429
|
+
const lvl = num(pPr?.attrs.lvl, 0);
|
|
430
|
+
const get = (fn) => { const v = pPr && fn(pPr); return v != null ? v : pick(lvl, fn); };
|
|
431
|
+
const defR = (k) => get((pp) => kid(pp, "defRPr")?.attrs[k]);
|
|
432
|
+
const size = num(defR("sz"), 1800) / 100;
|
|
433
|
+
const para = { runs: [] };
|
|
434
|
+
const algn = get((pp) => pp.attrs.algn);
|
|
435
|
+
if (algn && algn !== "l") para.align = { ctr: "center", r: "right", just: "justify", dist: "justify" }[algn] || "left";
|
|
436
|
+
const marL = num(get((pp) => pp.attrs.marL), 0) * S, indent = num(get((pp) => pp.attrs.indent), 0) * S;
|
|
437
|
+
if (marL) para.marL = r1(marL);
|
|
438
|
+
if (indent) para.indent = r1(indent);
|
|
439
|
+
const spc = (tag) => { const n = get((pp) => kid(pp, tag)); if (!n) return null; const pts = kid(n, "spcPts"), pct = kid(n, "spcPct"); return pts ? { pt: num(pts.attrs.val) / 100 } : pct ? { pct: num(pct.attrs.val) / 100000 } : null; };
|
|
440
|
+
const lnSpc = spc("lnSpc"), spcBef = spc("spcBef"), spcAft = spc("spcAft");
|
|
441
|
+
if (lnSpc) para.lineHeight = lnSpc.pct != null ? +Math.max(0.6, lnSpc.pct - lnReduce).toFixed(3) : r1(lnSpc.pt * PT * fontScale);
|
|
442
|
+
else if (lnReduce) para.lineHeight = +(1 - lnReduce).toFixed(3);
|
|
443
|
+
const bef = spcBef ? (spcBef.pt != null ? spcBef.pt * PT : spcBef.pct * size * PT) : 0, aft = spcAft ? (spcAft.pt != null ? spcAft.pt * PT : spcAft.pct * size * PT) : 0;
|
|
444
|
+
if (bef) para.spaceBefore = r1(bef * fontScale);
|
|
445
|
+
if (aft) para.spaceAfter = r1(aft * fontScale);
|
|
446
|
+
// marcador
|
|
447
|
+
const buNone = get((pp) => (kid(pp, "buNone") ? true : kid(pp, "buChar") || kid(pp, "buAutoNum") ? false : null));
|
|
448
|
+
if (!buNone) {
|
|
449
|
+
const ch = get((pp) => kid(pp, "buChar")?.attrs.char), an = get((pp) => kid(pp, "buAutoNum")?.attrs.type);
|
|
450
|
+
const buFont = get((pp) => kid(pp, "buFont")?.attrs.typeface);
|
|
451
|
+
const buClr = get((pp) => kid(pp, "buClr") ? colorOf(kid(pp, "buClr"), env) : null);
|
|
452
|
+
const buSz = get((pp) => kid(pp, "buSzPct")?.attrs.val);
|
|
453
|
+
if (an) { const k = `${lvl}`; const nNow = (autoNum.get(k) || num(get((pp) => kid(pp, "buAutoNum")?.attrs.startAt), 1) - 1) + 1; autoNum.set(k, nNow); para.bullet = { num: autoLabel(an, nNow) }; }
|
|
454
|
+
else if (ch) para.bullet = { char: ch, font: buFont || null };
|
|
455
|
+
if (para.bullet) { if (buClr) para.bullet.color = css(buClr); if (buSz) para.bullet.scale = +(num(buSz) / 100000).toFixed(2); }
|
|
456
|
+
} else autoNum = new Map();
|
|
457
|
+
// trechos
|
|
458
|
+
const defRPrNode = (() => { const own = kid(pPr, "defRPr"); return own; })();
|
|
459
|
+
const runDefaults = (rPr) => {
|
|
460
|
+
const pickR = (k) => { if (rPr?.attrs[k] != null) return rPr.attrs[k]; if (defRPrNode?.attrs[k] != null) return defRPrNode.attrs[k]; return defR(k); };
|
|
461
|
+
const fillNode = (rPr && kid(rPr, "solidFill")) || (defRPrNode && kid(defRPrNode, "solidFill")) || get((pp) => (kid(pp, "defRPr") && kid(kid(pp, "defRPr"), "solidFill")) || null);
|
|
462
|
+
const latin = (rPr && kid(rPr, "latin")?.attrs.typeface) || (defRPrNode && kid(defRPrNode, "latin")?.attrs.typeface) || get((pp) => kid(kid(pp, "defRPr"), "latin")?.attrs.typeface) || (kind === "title" ? "+mj-lt" : "+mn-lt");
|
|
463
|
+
return { sz: num(pickR("sz"), 1800) / 100, b: pickR("b"), i: pickR("i"), u: pickR("u"), strike: pickR("strike"), baseline: pickR("baseline"), cap: pickR("cap"), color: fillNode ? colorOf(fillNode, env) : fontColor || colorOf({ name: "schemeClr", attrs: { val: "tx1" }, children: [] }, env), font: latin.startsWith("+mj") ? (env.fonts || fonts).major : latin.startsWith("+mn") ? (env.fonts || fonts).minor : latin };
|
|
464
|
+
};
|
|
465
|
+
for (const r of kids(p)) {
|
|
466
|
+
const rn = r.name.split(":").pop();
|
|
467
|
+
if (rn === "r" || rn === "fld") {
|
|
468
|
+
const t = textOf(kid(r, "t"));
|
|
469
|
+
if (!t) continue;
|
|
470
|
+
const d = runDefaults(kid(r, "rPr"));
|
|
471
|
+
const run = { t: d.cap === "all" ? t.toUpperCase() : t, size: r1(d.sz * PT * fontScale) };
|
|
472
|
+
if (d.b === "1" || d.b === "true") run.b = true;
|
|
473
|
+
if (d.i === "1" || d.i === "true") run.i = true;
|
|
474
|
+
if (d.u && d.u !== "none") run.u = true;
|
|
475
|
+
if (d.strike && d.strike !== "noStrike") run.s = true;
|
|
476
|
+
if (d.baseline && num(d.baseline) !== 0) run[num(d.baseline) > 0 ? "sup" : "sub"] = true;
|
|
477
|
+
if (d.color) run.color = css(d.color);
|
|
478
|
+
if (d.font) run.font = d.font;
|
|
479
|
+
// fonte de s\u00EDmbolo: vale s\u00F3 para os caracteres da faixa de s\u00EDmbolos (F0xx); se a pr\u00F3pria fonte do trecho \u00E9 de
|
|
480
|
+
// s\u00EDmbolo, vale para todos
|
|
481
|
+
const symFont = /^(wingdings|symbol|webdings)/i.test(d.font || "") ? d.font : null;
|
|
482
|
+
const sym = symFont || kid(kid(r, "rPr"), "sym")?.attrs.typeface;
|
|
483
|
+
if (sym && (symFont || /[\uF000-\uF0FF]/.test(run.t))) { run.sym = sym; if (!symFont) run.symPua = true; }
|
|
484
|
+
if (rn === "fld" && /slidenum/i.test(r.attrs.type || "")) run.field = "slidenum";
|
|
485
|
+
const link = kid(kid(r, "rPr"), "hlinkClick");
|
|
486
|
+
if (link) { const rel = part?.rels.get(link.attrs["r:id"]); if (rel?.external) run.link = rel.target; }
|
|
487
|
+
para.runs.push(run);
|
|
488
|
+
} else if (rn === "br") para.runs.push({ br: true });
|
|
489
|
+
else if (rn === "AlternateContent" || rn === "m" || rn === "oMathPara" || rn === "oMath") {
|
|
490
|
+
const maths = all(r, "oMath");
|
|
491
|
+
for (const m of maths) para.runs.push({ latex: ommlToLatex(m), size: r1(runDefaults(null).sz * PT * fontScale) });
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
if (!para.runs.length) { para.empty = true; para.size = r1(num(defR("sz"), 1800) / 100 * PT * fontScale); delete para.bullet; }
|
|
495
|
+
paragraphs.push(para);
|
|
496
|
+
}
|
|
497
|
+
while (paragraphs.length && paragraphs.at(-1).empty) paragraphs.pop();
|
|
498
|
+
const ins = (k, d) => r1(num(bp(k, d)) * S);
|
|
499
|
+
const tb = { paragraphs, pad: [ins("lIns", 91440), ins("tIns", 45720), ins("rIns", 91440), ins("bIns", 45720)] };
|
|
500
|
+
const anchor = bp("anchor", "t");
|
|
501
|
+
if (anchor !== "t") tb.anchor = anchor === "ctr" ? "middle" : anchor === "b" ? "bottom" : "top";
|
|
502
|
+
if (bp("wrap", "square") === "none") tb.nowrap = true;
|
|
503
|
+
const vert = bp("vert", "horz");
|
|
504
|
+
if (vert && vert !== "horz") tb.vertical = vert === "vert270" ? "up" : "down";
|
|
505
|
+
if (num(bp("numCol", 1)) > 1) tb.columns = num(bp("numCol", 1));
|
|
506
|
+
return tb;
|
|
507
|
+
}
|
|
508
|
+
function autoLabel(type, n) {
|
|
509
|
+
const alpha = (k) => { let s = ""; while (k > 0) { s = String.fromCharCode(97 + ((k - 1) % 26)) + s; k = Math.floor((k - 1) / 26); } return s; };
|
|
510
|
+
const roman = (k) => [[1000, "m"], [900, "cm"], [500, "d"], [400, "cd"], [100, "c"], [90, "xc"], [50, "l"], [40, "xl"], [10, "x"], [9, "ix"], [5, "v"], [4, "iv"], [1, "i"]].reduce((s, [v, r]) => { while (k >= v) { s += r; k -= v; } return s; }, "");
|
|
511
|
+
const base = /alphaLc/.test(type) ? alpha(n) : /alphaUc/.test(type) ? alpha(n).toUpperCase() : /romanLc/.test(type) ? roman(n) : /romanUc/.test(type) ? roman(n).toUpperCase() : String(n);
|
|
512
|
+
return /ParenBoth/.test(type) ? `(${base})` : /ParenR/.test(type) ? `${base})` : /Period/.test(type) ? `${base}.` : base;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// ---------------------------------------------------------------------------------------------- tabela
|
|
516
|
+
function tableEl(tbl, box, env) {
|
|
517
|
+
const grid = kids(kid(tbl, "tblGrid"), "gridCol").map((g) => num(g.attrs.w) * S);
|
|
518
|
+
const pr = kid(tbl, "tblPr"), styleId = textOf(kid(pr, "tableStyleId"));
|
|
519
|
+
const ts = tableStyles.get(styleId) || builtinTableStyle(styleId);
|
|
520
|
+
const flags = { firstRow: pr?.attrs.firstRow === "1", bandRow: pr?.attrs.bandRow === "1", firstCol: pr?.attrs.firstCol === "1", lastRow: pr?.attrs.lastRow === "1" };
|
|
521
|
+
const part = (name) => kid(ts, name);
|
|
522
|
+
const partFill = (name) => { const p = part(name); const f = path(p, "tcStyle", "fill"); return f ? fillOf(f, env, null) : null; };
|
|
523
|
+
const partText = (name) => { const p = part(name); const t = kid(p, "tcTxStyle"); return t ? { b: t.attrs.b === "on", color: colorOf(t, env) } : null; };
|
|
524
|
+
const rows = kids(tbl, "tr");
|
|
525
|
+
const data = [];
|
|
526
|
+
const cells = rows.map((tr, ri) => {
|
|
527
|
+
const rowCells = kids(tr, "tc");
|
|
528
|
+
data.push(rowCells.map((tc) => kids(kid(tc, "txBody"), "p").map((p) => all(p, "t").map(textOf).join("")).join("\n")));
|
|
529
|
+
return rowCells.map((tc, ci) => {
|
|
530
|
+
if (tc.attrs.hMerge === "1" || tc.attrs.vMerge === "1") return null;
|
|
531
|
+
const tcPr = kid(tc, "tcPr");
|
|
532
|
+
const isHead = flags.firstRow && ri === 0, isLast = flags.lastRow && ri === rows.length - 1;
|
|
533
|
+
const band = flags.bandRow && !isHead ? ((ri - (flags.firstRow ? 1 : 0)) % 2 === 0 ? "band1H" : "band2H") : null;
|
|
534
|
+
let fill = fillOf(tcPr, env, null);
|
|
535
|
+
if (fill == null) fill = (isHead && partFill("firstRow")) || (isLast && partFill("lastRow")) || (band && partFill(band)) || partFill("wholeTbl");
|
|
536
|
+
const txt = (isHead && partText("firstRow")) || partText("wholeTbl");
|
|
537
|
+
const tb = textBody(kid(tc, "txBody"), tc, null, env, [], null, txt?.color || null);
|
|
538
|
+
if (txt?.b && isHead) tb.paragraphs.forEach((p) => p.runs.forEach((r) => (r.b = true)));
|
|
539
|
+
const mar = (k, d) => r1(num(tcPr?.attrs[k], d) * S);
|
|
540
|
+
const borders = {};
|
|
541
|
+
for (const [k, side] of [["lnL", "left"], ["lnR", "right"], ["lnT", "top"], ["lnB", "bottom"]]) { const ln = kid(tcPr, k); if (ln && !kid(ln, "noFill")) { const c = colorOf(kid(ln, "solidFill"), env); if (c) borders[side] = `${Math.max(0.6, num(ln.attrs.w, 12700) * S).toFixed(1)}px solid ${css(c)}`; } }
|
|
542
|
+
return { tb, fill: fill && !fill.none ? css(fill) : null, span: num(tc.attrs.gridSpan, 1), rowSpan: num(tc.attrs.rowSpan, 1), pad: [mar("marL", 91440), mar("marT", 45720), mar("marR", 91440), mar("marB", 45720)], anchor: tcPr?.attrs.anchor, borders };
|
|
543
|
+
});
|
|
544
|
+
});
|
|
545
|
+
const heights = rows.map((tr) => r1(num(tr.attrs.h) * S));
|
|
546
|
+
return { table: { cols: grid.map(r1), heights, cells, border: ts ? "1px solid rgba(255,255,255,.9)" : "1px solid #999" }, tableData: data };
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// estilos de tabela prontos do PowerPoint mais usados, no mesmo formato do tableStyles.xml
|
|
550
|
+
function builtinTableStyle(id) {
|
|
551
|
+
const X = (xml) => parseXML(`<a:tblStyle xmlns:a="a">${xml}</a:tblStyle>`);
|
|
552
|
+
const solid = (clr, mods = "") => `<a:fill><a:solidFill><a:schemeClr val="${clr}">${mods}</a:schemeClr></a:solidFill></a:fill>`;
|
|
553
|
+
const tx = (clr, b) => `<a:tcTxStyle${b ? ' b="on"' : ""}><a:schemeClr val="${clr}"/></a:tcTxStyle>`;
|
|
554
|
+
const light1 = (acc) => X(`<a:wholeTbl>${tx("tx1")}<a:tcStyle>${solid("bg1", '<a:alpha val="0"/>')}</a:tcStyle></a:wholeTbl><a:band1H><a:tcStyle>${solid(acc, '<a:alpha val="20000"/>')}</a:tcStyle></a:band1H><a:firstRow>${tx("tx1", true)}<a:tcStyle>${solid("bg1", '<a:alpha val="0"/>')}</a:tcStyle></a:firstRow>`);
|
|
555
|
+
const medium2 = (acc) => X(`<a:wholeTbl>${tx("dk1")}<a:tcStyle>${solid(acc, '<a:tint val="20000"/>')}</a:tcStyle></a:wholeTbl><a:band1H><a:tcStyle>${solid(acc, '<a:tint val="40000"/>')}</a:tcStyle></a:band1H><a:firstRow>${tx("lt1", true)}<a:tcStyle>${solid(acc)}</a:tcStyle></a:firstRow>`);
|
|
556
|
+
const map = {
|
|
557
|
+
"{9D7B26C5-4107-4FEC-AEDC-1716B250A1EF}": () => light1("tx1"), "{3B4B98B0-60AC-42C2-AFA5-B58CD77FA1E5}": () => light1("accent1"),
|
|
558
|
+
"{073A0DAA-6AF3-43AB-8588-CEC1D06C72B9}": () => medium2("dk1"), "{5C22544A-7EE6-4342-B048-85BDC9FD1C3A}": () => medium2("accent1"),
|
|
559
|
+
"{21E4AEA4-8DFA-4A89-87EB-49C32662AFE8}": () => medium2("accent2"), "{F5AB1C69-6EDB-4FF4-983F-18BD219EF322}": () => medium2("accent3"),
|
|
560
|
+
};
|
|
561
|
+
return map[id]?.() || null;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// ---------------------------------------------------------------------------------------------- gráfico
|
|
565
|
+
async function chartEl(file) {
|
|
566
|
+
const x = await P.xml(file);
|
|
567
|
+
if (!x) return null;
|
|
568
|
+
const plot = path(x, "chart", "plotArea");
|
|
569
|
+
const kindNode = kids(plot).find((k) => /Chart$/.test(k.name));
|
|
570
|
+
if (!kindNode) return null;
|
|
571
|
+
const type = kindNode.name.split(":").pop().replace("Chart", "").replace(/3D$/, "");
|
|
572
|
+
const dir = kid(kindNode, "barDir")?.attrs.val;
|
|
573
|
+
const chart = { bar: dir === "bar" ? "hbar" : "bar", line: "line", pie: "pie", doughnut: "donut", area: "area", scatter: "scatter" }[type] || "bar";
|
|
574
|
+
const series = kids(kindNode, "ser").map((s) => ({ name: all(kid(s, "tx"), "v").map(textOf)[0] || "", values: all(kid(s, "val") || kid(s, "yVal"), "v").map((v) => num(textOf(v))) }));
|
|
575
|
+
const labels = (() => { const c = kid(kids(kindNode, "ser")[0], "cat") || kid(kids(kindNode, "ser")[0], "xVal"); return c ? all(c, "v").map(textOf) : []; })();
|
|
576
|
+
const title = all(kid(path(x, "chart"), "title"), "t").map(textOf).join("");
|
|
577
|
+
return { chart: { chart, labels, series, ...(title ? { title } : {}) }, kind: "chart" };
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
return { W, H, cx: P.cx, cy: P.cy, fonts, scheme, slides, media: [...media.values()], master: masterOut, warnings };
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// ------------------------------------------------------------------------------------------------ formas em SVG
|
|
584
|
+
const DASH = { dash: "4 3", sysDash: "3 1", dot: "1 2", sysDot: "1 1", lgDash: "8 3", dashDot: "4 3 1 3", lgDashDot: "8 3 1 3", sysDashDot: "3 1 1 1" };
|
|
585
|
+
function shapeSVG(geom, box, fill, line, custGeom, prstGeom) {
|
|
586
|
+
const w = Math.max(box.w, 1), h = Math.max(box.h, 1);
|
|
587
|
+
const sw = line ? Math.max(0.6, line.width) : 0;
|
|
588
|
+
const adj = (name, d) => { const gd = kids(kid(prstGeom, "avLst"), "gd").find((g) => g.attrs.name === name); const m = gd?.attrs.fmla?.match(/val\s+(-?\d+)/); return m ? +m[1] : d; };
|
|
589
|
+
let d = "";
|
|
590
|
+
const isLine = ["leftBrace", "rightBrace", "leftBracket", "rightBracket", "line", "straightConnector1", "bentConnector2", "bentConnector3", "curvedConnector3", "curvedConnector2", "bentConnector4"].includes(geom);
|
|
591
|
+
const [x0, y0, x1, y1] = [box.flipH ? w : 0, box.flipV ? h : 0, box.flipH ? 0 : w, box.flipV ? 0 : h];
|
|
592
|
+
const P = (pts) => "M" + pts.map((p) => p.map((v) => r1(v)).join(" ")).join("L") + "Z";
|
|
593
|
+
const ss = Math.min(w, h);
|
|
594
|
+
switch (geom) {
|
|
595
|
+
case "rect": case "flowChartProcess": case "flowChartAlternateProcess": case "snip1Rect": case "wedgeRectCallout": case "textBox": d = `M0 0H${w}V${h}H0Z`; break;
|
|
596
|
+
case "roundRect": case "round2SameRect": { const r = ss * adj("adj", 16667) / 100000; d = `M${r} 0H${w - r}A${r} ${r} 0 0 1 ${w} ${r}V${h - r}A${r} ${r} 0 0 1 ${w - r} ${h}H${r}A${r} ${r} 0 0 1 0 ${h - r}V${r}A${r} ${r} 0 0 1 ${r} 0Z`; break; }
|
|
597
|
+
case "flowChartTerminator": { const r = h / 2; d = `M${r} 0H${w - r}A${r} ${r} 0 0 1 ${w - r} ${h}H${r}A${r} ${r} 0 0 1 ${r} 0Z`; break; }
|
|
598
|
+
case "ellipse": case "flowChartConnector": case "donut": case "cloud": case "cloudCallout": case "wedgeEllipseCallout": d = `M0 ${h / 2}A${w / 2} ${h / 2} 0 1 0 ${w} ${h / 2}A${w / 2} ${h / 2} 0 1 0 0 ${h / 2}Z`; break;
|
|
599
|
+
case "triangle": d = P([[w * adj("adj", 50000) / 100000, 0], [w, h], [0, h]]); break;
|
|
600
|
+
case "rtTriangle": d = P([[0, 0], [w, h], [0, h]]); break;
|
|
601
|
+
case "diamond": case "flowChartDecision": d = P([[w / 2, 0], [w, h / 2], [w / 2, h], [0, h / 2]]); break;
|
|
602
|
+
case "parallelogram": case "flowChartInputOutput": { const a = ss * adj("adj", 25000) / 100000; d = P([[a, 0], [w, 0], [w - a, h], [0, h]]); break; }
|
|
603
|
+
case "trapezoid": { const a = ss * adj("adj", 25000) / 100000; d = P([[a, 0], [w - a, 0], [w, h], [0, h]]); break; }
|
|
604
|
+
case "pentagon": case "homePlate": { const a = ss * adj("adj", 50000) / 100000; d = P([[0, 0], [w - a, 0], [w, h / 2], [w - a, h], [0, h]]); break; }
|
|
605
|
+
case "chevron": { const a = ss * adj("adj", 50000) / 100000; d = P([[0, 0], [w - a, 0], [w, h / 2], [w - a, h], [0, h], [a, h / 2]]); break; }
|
|
606
|
+
case "hexagon": { const a = ss * adj("adj", 25000) / 100000; d = P([[a, 0], [w - a, 0], [w, h / 2], [w - a, h], [a, h], [0, h / 2]]); break; }
|
|
607
|
+
case "octagon": { const a = ss * adj("adj", 29289) / 100000; d = P([[a, 0], [w - a, 0], [w, a], [w, h - a], [w - a, h], [a, h], [0, h - a], [0, a]]); break; }
|
|
608
|
+
case "plus": { const a = ss * adj("adj", 25000) / 100000; d = P([[a, 0], [w - a, 0], [w - a, a], [w, a], [w, h - a], [w - a, h - a], [w - a, h], [a, h], [a, h - a], [0, h - a], [0, a], [a, a]]); break; }
|
|
609
|
+
case "rightArrow": { const t = h * adj("adj1", 50000) / 100000, hl = ss * adj("adj2", 50000) / 100000; d = P([[0, (h - t) / 2], [w - hl, (h - t) / 2], [w - hl, 0], [w, h / 2], [w - hl, h], [w - hl, (h + t) / 2], [0, (h + t) / 2]]); break; }
|
|
610
|
+
case "leftArrow": { const t = h * adj("adj1", 50000) / 100000, hl = ss * adj("adj2", 50000) / 100000; d = P([[w, (h - t) / 2], [hl, (h - t) / 2], [hl, 0], [0, h / 2], [hl, h], [hl, (h + t) / 2], [w, (h + t) / 2]]); break; }
|
|
611
|
+
case "upArrow": { const t = w * adj("adj1", 50000) / 100000, hl = ss * adj("adj2", 50000) / 100000; d = P([[(w - t) / 2, h], [(w - t) / 2, hl], [0, hl], [w / 2, 0], [w, hl], [(w + t) / 2, hl], [(w + t) / 2, h]]); break; }
|
|
612
|
+
case "downArrow": { const t = w * adj("adj1", 50000) / 100000, hl = ss * adj("adj2", 50000) / 100000; d = P([[(w - t) / 2, 0], [(w - t) / 2, h - hl], [0, h - hl], [w / 2, h], [w, h - hl], [(w + t) / 2, h - hl], [(w + t) / 2, 0]]); break; }
|
|
613
|
+
case "leftRightArrow": { const t = h * adj("adj1", 50000) / 100000, hl = ss * adj("adj2", 50000) / 100000; d = P([[0, h / 2], [hl, 0], [hl, (h - t) / 2], [w - hl, (h - t) / 2], [w - hl, 0], [w, h / 2], [w - hl, h], [w - hl, (h + t) / 2], [hl, (h + t) / 2], [hl, h]]); break; }
|
|
614
|
+
case "star5": { const pts = []; for (let k = 0; k < 10; k++) { const a = -Math.PI / 2 + (k * Math.PI) / 5, rr = k % 2 ? 0.38 : 0.5; pts.push([w / 2 + Math.cos(a) * w * rr, h / 2 + Math.sin(a) * h * rr]); } d = P(pts); break; }
|
|
615
|
+
case "can": case "flowChartMagneticDisk": { const ry = Math.min(h * 0.12, w * 0.25); d = `M0 ${ry}A${w / 2} ${ry} 0 0 1 ${w} ${ry}V${h - ry}A${w / 2} ${ry} 0 0 1 0 ${h - ry}Z M0 ${ry}A${w / 2} ${ry} 0 0 0 ${w} ${ry}`; break; }
|
|
616
|
+
case "line": case "straightConnector1": d = `M${x0} ${y0}L${x1} ${y1}`; break;
|
|
617
|
+
case "bentConnector2": d = `M${x0} ${y0}H${x1}V${y1}`; break;
|
|
618
|
+
case "bentConnector3": case "bentConnector4": { const mx = x0 + (x1 - x0) * (adj("adj1", 50000) / 100000); d = `M${x0} ${y0}H${mx}V${y1}H${x1}`; break; }
|
|
619
|
+
case "curvedConnector2": case "curvedConnector3": d = `M${x0} ${y0}C${(x0 + x1) / 2} ${y0} ${(x0 + x1) / 2} ${y1} ${x1} ${y1}`; break;
|
|
620
|
+
case "arc": { const a1 = (adj("adj1", 16200000) / 60000) * Math.PI / 180, a2 = (adj("adj2", 0) / 60000) * Math.PI / 180; const pt = (a) => [w / 2 + (w / 2) * Math.cos(a), h / 2 + (h / 2) * Math.sin(a)]; const [sx, sy] = pt(a1), [ex, ey] = pt(a2); let sweep = a2 - a1; if (sweep < 0) sweep += 2 * Math.PI; d = `M${r1(sx)} ${r1(sy)}A${w / 2} ${h / 2} 0 ${sweep > Math.PI ? 1 : 0} 1 ${r1(ex)} ${r1(ey)}`; fill = null; break; }
|
|
621
|
+
case "borderCallout1": case "callout1": case "accentCallout1": case "accentBorderCallout1": case "borderCallout2": case "callout2": {
|
|
622
|
+
// balão com a linha que aponta (adj em 1/100000 da altura e da largura; pode sair da caixa)
|
|
623
|
+
const ay1 = h * adj("adj1", 18750) / 100000, ax1 = w * adj("adj2", -8333) / 100000, ay2 = h * adj("adj3", 112500) / 100000, ax2 = w * adj("adj4", -38333) / 100000;
|
|
624
|
+
const two = /2$/.test(geom), ay3 = h * adj("adj5", 112500) / 100000, ax3 = w * adj("adj6", -46667) / 100000;
|
|
625
|
+
d = `M0 0H${w}V${h}H0Z M${r1(ax1)} ${r1(ay1)}L${r1(ax2)} ${r1(ay2)}${two ? `L${r1(ax3)} ${r1(ay3)}` : ""}`;
|
|
626
|
+
break;
|
|
627
|
+
}
|
|
628
|
+
case "leftBrace": { const r = Math.min(w, h / 4); d = `M${w} 0Q${w / 2} 0 ${w / 2} ${r}V${h / 2 - r}Q${w / 2} ${h / 2} 0 ${h / 2}Q${w / 2} ${h / 2} ${w / 2} ${h / 2 + r}V${h - r}Q${w / 2} ${h} ${w} ${h}`; fill = null; break; }
|
|
629
|
+
case "rightBrace": { const r = Math.min(w, h / 4); d = `M0 0Q${w / 2} 0 ${w / 2} ${r}V${h / 2 - r}Q${w / 2} ${h / 2} ${w} ${h / 2}Q${w / 2} ${h / 2} ${w / 2} ${h / 2 + r}V${h - r}Q${w / 2} ${h} 0 ${h}`; fill = null; break; }
|
|
630
|
+
case "leftBracket": d = `M${w} 0H0V${h}H${w}`; fill = null; break;
|
|
631
|
+
case "rightBracket": d = `M0 0H${w}V${h}H0`; fill = null; break;
|
|
632
|
+
case "cust": d = custPath(custGeom, w, h); break;
|
|
633
|
+
default: d = `M0 0H${w}V${h}H0Z`;
|
|
634
|
+
}
|
|
635
|
+
if (!d) return null;
|
|
636
|
+
const open = isLine || geom === "arc" || (geom === "cust" && !/Z\s*$/i.test(d) && !fill);
|
|
637
|
+
let defs = "", fillAttr = "none";
|
|
638
|
+
if (fill && !open) {
|
|
639
|
+
if (fill.image) {
|
|
640
|
+
// textura: a imagem repetida (tile) ou esticada na forma; o endereço "media:" vira a imagem na hora de montar
|
|
641
|
+
const id = `t${Math.random().toString(36).slice(2, 9)}`;
|
|
642
|
+
const tw = fill.tileW || w, th = fill.tileH || h;
|
|
643
|
+
defs = `<defs><pattern id="${id}" patternUnits="userSpaceOnUse" width="${tw}" height="${th}"><image href="media:${fill.image}" width="${tw}" height="${th}" preserveAspectRatio="none"/></pattern></defs>`;
|
|
644
|
+
fillAttr = `url(#${id})`;
|
|
645
|
+
} else if (fill.pattern) {
|
|
646
|
+
const id = `p${Math.random().toString(36).slice(2, 9)}`, k = fill.pattern.prst, fg = fill.pattern.fg;
|
|
647
|
+
const lines = /Cross|Grid|cross/.test(k) ? (/[dD]iag/.test(k) ? "M0 0L8 8M8 0L0 8" : "M4 0V8M0 4H8") : /dnDiag|DnDiag/.test(k) ? "M0 0L8 8M-2 6L2 10M6 -2L10 2" : /upDiag|UpDiag/.test(k) ? "M0 8L8 0M-2 2L2 -2M6 10L10 6" : /horz|Horz/.test(k) ? "M0 4H8" : /vert|Vert/.test(k) ? "M4 0V8" : "";
|
|
648
|
+
const dots = !lines ? `<circle cx="2" cy="2" r="${/pct(5|10|20)$/.test(k) ? 0.7 : 1.3}" fill="${fg}"/><circle cx="6" cy="6" r="${/pct(5|10|20)$/.test(k) ? 0.7 : 1.3}" fill="${fg}"/>` : "";
|
|
649
|
+
defs = `<defs><pattern id="${id}" patternUnits="userSpaceOnUse" width="8" height="8"><rect width="8" height="8" fill="${fill.pattern.bg}"/>${lines ? `<path d="${lines}" stroke="${fg}" stroke-width="${/^(dk|wd)/.test(k) ? 1.6 : 0.9}"/>` : dots}</pattern></defs>`;
|
|
650
|
+
fillAttr = `url(#${id})`;
|
|
651
|
+
} else if (fill.gradient) {
|
|
652
|
+
const id = `g${Math.random().toString(36).slice(2, 9)}`;
|
|
653
|
+
const a = (fill.gradient.angle * Math.PI) / 180;
|
|
654
|
+
defs = `<defs><linearGradient id="${id}" x1="${r1(50 - 50 * Math.cos(a))}%" y1="${r1(50 - 50 * Math.sin(a))}%" x2="${r1(50 + 50 * Math.cos(a))}%" y2="${r1(50 + 50 * Math.sin(a))}%">${fill.gradient.stops.map((s) => `<stop offset="${r1(s.pos)}%" stop-color="${s.color}"/>`).join("")}</linearGradient></defs>`;
|
|
655
|
+
fillAttr = `url(#${id})`;
|
|
656
|
+
} else fillAttr = fill.alpha < 1 ? `rgba(${hexToRgb(fill.hex).join(",")},${+fill.alpha.toFixed(3)})` : `#${fill.hex}`;
|
|
657
|
+
}
|
|
658
|
+
const markers = [];
|
|
659
|
+
let mdefs = "";
|
|
660
|
+
if (line && (line.head || line.tail)) {
|
|
661
|
+
for (const [end, type, len] of [["start", line.head, line.headLen], ["end", line.tail, line.tailLen]]) {
|
|
662
|
+
if (!type || type === "none") continue;
|
|
663
|
+
const id = `m${Math.random().toString(36).slice(2, 9)}`;
|
|
664
|
+
const shape = type === "oval" ? `<circle cx="5" cy="5" r="4" fill="${line.color}"/>` : type === "diamond" ? `<path d="M0 5L5 0L10 5L5 10Z" fill="${line.color}"/>` : type === "arrow" ? `<path d="M0 0L10 5L0 10" fill="none" stroke="${line.color}" stroke-width="1.6"/>` : `<path d="M0 0L10 5L0 10Z" fill="${line.color}"/>`;
|
|
665
|
+
mdefs += `<marker id="${id}" viewBox="0 0 10 10" refX="${type === "oval" ? 5 : 9}" refY="5" markerWidth="${{ sm: 3, med: 4.5, lg: 6 }[len] || 4.5}" markerHeight="${{ sm: 3, med: 4.5, lg: 6 }[len] || 4.5}" orient="auto-start-reverse" markerUnits="strokeWidth">${shape}</marker>`;
|
|
666
|
+
markers.push(` marker-${end}="url(#${id})"`);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
const stroke = line ? ` stroke="${line.color}" stroke-width="${r1(sw)}"${line.dash && DASH[line.dash] ? ` stroke-dasharray="${DASH[line.dash].split(" ").map((v) => r1(+v * sw)).join(" ")}"` : ""} stroke-linejoin="round" stroke-linecap="${isLine ? "round" : "butt"}"` : "";
|
|
670
|
+
const pad = Math.ceil(sw * 3 + 2);
|
|
671
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="${-pad} ${-pad} ${r1(w + 2 * pad)} ${r1(h + 2 * pad)}" style="position:absolute;left:${-pad}px;top:${-pad}px;width:${r1(w + 2 * pad)}px;height:${r1(h + 2 * pad)}px;overflow:visible"${box.flipH && !isLine ? ' transform="scale(-1,1)"' : ""}>${defs}${mdefs ? `<defs>${mdefs}</defs>` : ""}<path d="${d}" fill="${open ? "none" : fillAttr}"${stroke}${markers.join("")}/></svg>`;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// desenho livre (custGeom): moveTo/lnTo/cubicBezTo/quadBezTo/arcTo/close em coordenadas do path, escaladas à caixa
|
|
675
|
+
function custPath(cg, w, h) {
|
|
676
|
+
if (!cg) return "";
|
|
677
|
+
let out = "";
|
|
678
|
+
for (const p of kids(kid(cg, "pathLst"), "path")) {
|
|
679
|
+
const pw = num(p.attrs.w) || w, ph = num(p.attrs.h) || h;
|
|
680
|
+
const sx = w / pw, sy = h / ph;
|
|
681
|
+
let cur = [0, 0];
|
|
682
|
+
const pt = (n) => [num(n.attrs.x) * sx, num(n.attrs.y) * sy];
|
|
683
|
+
for (const c of kids(p)) {
|
|
684
|
+
const n = c.name.split(":").pop();
|
|
685
|
+
if (n === "moveTo") { cur = pt(kid(c, "pt")); out += `M${r1(cur[0])} ${r1(cur[1])}`; }
|
|
686
|
+
else if (n === "lnTo") { cur = pt(kid(c, "pt")); out += `L${r1(cur[0])} ${r1(cur[1])}`; }
|
|
687
|
+
else if (n === "cubicBezTo") { const ps = kids(c, "pt").map(pt); if (ps.length === 3) { out += `C${ps.map((q) => `${r1(q[0])} ${r1(q[1])}`).join(" ")}`; cur = ps[2]; } }
|
|
688
|
+
else if (n === "quadBezTo") { const ps = kids(c, "pt").map(pt); if (ps.length === 2) { out += `Q${ps.map((q) => `${r1(q[0])} ${r1(q[1])}`).join(" ")}`; cur = ps[1]; } }
|
|
689
|
+
else if (n === "arcTo") {
|
|
690
|
+
const wR = num(c.attrs.wR) * sx, hR = num(c.attrs.hR) * sy, st = (num(c.attrs.stAng) / 60000) * Math.PI / 180, sw = (num(c.attrs.swAng) / 60000) * Math.PI / 180;
|
|
691
|
+
const cx = cur[0] - wR * Math.cos(st), cy = cur[1] - hR * Math.sin(st);
|
|
692
|
+
const ex = cx + wR * Math.cos(st + sw), ey = cy + hR * Math.sin(st + sw);
|
|
693
|
+
out += `A${r1(wR)} ${r1(hR)} 0 ${Math.abs(sw) > Math.PI ? 1 : 0} ${sw > 0 ? 1 : 0} ${r1(ex)} ${r1(ey)}`;
|
|
694
|
+
cur = [ex, ey];
|
|
695
|
+
} else if (n === "close") out += "Z";
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
return out;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
export const plainOf = (e) => (e.textbox ? e.textbox.paragraphs.map((p) => p.runs.map((r) => (r.br ? "\n" : r.t ?? (r.latex ? `$${r.latex}$` : ""))).join("")).join("\n") : "");
|