Initial commit
This commit is contained in:
538
skills/docx/references/decorations.md
Executable file
538
skills/docx/references/decorations.md
Executable file
@@ -0,0 +1,538 @@
|
||||
## Geometric Decoration System — Pure docx-js Decorations
|
||||
|
||||
### Design Philosophy
|
||||
|
||||
Uses only docx-js native capabilities for visual decoration — no external tools (like Playwright screenshots). Suitable for covers, chapter separators, page background enhancement.
|
||||
|
||||
**When to fall back to Playwright?**
|
||||
Only when gradients, complex illustrations, or brand visuals are needed that pure OOXML cannot express. Default: prefer native solutions below.
|
||||
|
||||
### Decoration Element Library
|
||||
|
||||
#### 1. Color Strip — Table Simulation
|
||||
|
||||
Single-row single-column borderless table + background color to create horizontal color strips.
|
||||
|
||||
```js
|
||||
function colorStrip(color, height = 80) {
|
||||
return new Table({
|
||||
width: { size: 100, type: WidthType.PERCENTAGE },
|
||||
borders: { top: NB, bottom: NB, left: NB, right: NB,
|
||||
insideHorizontal: NB, insideVertical: NB },
|
||||
rows: [new TableRow({
|
||||
height: { value: height, rule: "exact" },
|
||||
children: [new TableCell({
|
||||
shading: { type: ShadingType.CLEAR, fill: color.replace("#", "") },
|
||||
borders: { top: NB, bottom: NB, left: NB, right: NB },
|
||||
children: [new Paragraph({ children: [] })],
|
||||
})],
|
||||
})],
|
||||
});
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
// R6 — Editorial Warm (minimal, warm white bg, no decorations)
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
// Suitable for: lesson plans (non-STEM), cultural/creative, newsletters,
|
||||
// event planning, internal reports, light-weight documents
|
||||
// NOT for: formal business, consulting, finance, government, academic
|
||||
// Title constraint: single line only (≤20 chars). Longer titles → route to R1.
|
||||
//
|
||||
// Structure: 2-row wrapper table (no border, warm bg shading)
|
||||
// Row 1 (content): category → title → subtitle → fields
|
||||
// Row 2 (footer): left English title + right label
|
||||
// All spacing via paragraph indent (WPS safe, no cell margins).
|
||||
|
||||
function buildCoverR6(config) {
|
||||
const P = config.palette;
|
||||
const PAD_L = 1300, PAD_R = 1100;
|
||||
const ind = { left: PAD_L, right: PAD_R };
|
||||
const FOOTER_H = 900;
|
||||
const CONTENT_H = 16838 - FOOTER_H;
|
||||
const shading = { fill: P.bg || "F7F7F5", type: ShadingType.CLEAR };
|
||||
|
||||
// ⚠️ R6 uses a simplified title layout: prefer single line, shrink font to fit
|
||||
const availW = 11906 - PAD_L - PAD_R;
|
||||
const { titlePt, titleLines } = calcTitleLayoutR6(config.title, availW, 36, 22);
|
||||
const titleSize = titlePt * 2;
|
||||
const lineH = Math.ceil(titlePt * 23 * 1.3);
|
||||
|
||||
// Dynamic top spacing
|
||||
const titleH = titleLines.length * (titleSize * 10 + 200);
|
||||
const categoryH = 22 * 10 + 900;
|
||||
const subtitleH = config.subtitle ? (28 * 10 + 1200) : 0;
|
||||
const fieldsH = (config.metaLines || []).length * (24 * 10 + 100);
|
||||
const contentH = categoryH + titleH + subtitleH + fieldsH;
|
||||
const remaining = Math.max(CONTENT_H - 1200 - contentH, 400);
|
||||
const topSpacing = Math.floor(remaining * 0.55);
|
||||
|
||||
const children = [];
|
||||
|
||||
// 1. Top spacer (dynamic)
|
||||
children.push(new Paragraph({ indent: ind, spacing: { before: topSpacing } }));
|
||||
|
||||
// 2. Category label (small, wide letter-spacing)
|
||||
if (config.englishLabel) {
|
||||
children.push(new Paragraph({
|
||||
indent: ind, spacing: { after: 900 },
|
||||
children: [new TextRun({
|
||||
text: config.englishLabel, size: 22,
|
||||
color: P.cover.metaColor || "9A9A9A",
|
||||
font: { ascii: "Calibri", eastAsia: "Microsoft YaHei" },
|
||||
characterSpacing: 60,
|
||||
})],
|
||||
}));
|
||||
}
|
||||
|
||||
// 3. Title (single line preferred, dynamic font size)
|
||||
for (let i = 0; i < titleLines.length; i++) {
|
||||
children.push(new Paragraph({
|
||||
indent: ind,
|
||||
spacing: { after: i < titleLines.length - 1 ? 60 : 300, line: lineH, lineRule: "atLeast" },
|
||||
children: [new TextRun({
|
||||
text: titleLines[i], size: titleSize,
|
||||
color: P.cover.titleColor || "2C2C2C",
|
||||
font: { ascii: "Calibri", eastAsia: "Microsoft YaHei" },
|
||||
characterSpacing: 30,
|
||||
})],
|
||||
}));
|
||||
}
|
||||
|
||||
// 4. Subtitle
|
||||
if (config.subtitle) {
|
||||
children.push(new Paragraph({
|
||||
indent: ind, spacing: { after: 1200 },
|
||||
children: [new TextRun({
|
||||
text: config.subtitle, size: 28,
|
||||
color: P.cover.subtitleColor || "6B6B6B",
|
||||
font: { ascii: "Calibri", eastAsia: "Microsoft YaHei" },
|
||||
characterSpacing: 15,
|
||||
})],
|
||||
}));
|
||||
}
|
||||
|
||||
// 5. Meta fields (tab-aligned label + value)
|
||||
for (const line of (config.metaLines || [])) {
|
||||
// Expect "label:value" format or plain text
|
||||
const sep = line.indexOf(":") !== -1 ? ":" : (line.indexOf(":") !== -1 ? ":" : null);
|
||||
const label = sep ? line.split(sep)[0].trim() : line;
|
||||
const value = sep ? line.split(sep).slice(1).join(sep).trim() : "";
|
||||
children.push(new Paragraph({
|
||||
indent: ind, spacing: { after: 100 },
|
||||
tabStops: [{ type: TabStopType.LEFT, position: PAD_L + 1600 }],
|
||||
children: [
|
||||
new TextRun({ text: label, size: 22, color: P.cover.metaColor || "9A9A9A",
|
||||
font: { ascii: "Calibri", eastAsia: "Microsoft YaHei" }, characterSpacing: 20 }),
|
||||
...(value ? [
|
||||
new TextRun({ text: "\t" }),
|
||||
new TextRun({ text: value, size: 24, color: P.cover.subtitleColor || "6B6B6B",
|
||||
font: { ascii: "Calibri", eastAsia: "Microsoft YaHei" }, characterSpacing: 8 }),
|
||||
] : []),
|
||||
],
|
||||
}));
|
||||
}
|
||||
|
||||
// 6. Footer (2-column borderless table)
|
||||
const footerLeft = config.footerLeft || "";
|
||||
const footerRight = config.footerRight || "";
|
||||
// Adaptive font size for long English footer text
|
||||
const flSize = footerLeft.length > 60 ? 14 : (footerLeft.length > 40 ? 16 : 18);
|
||||
const flSpacing = footerLeft.length > 60 ? 5 : (footerLeft.length > 40 ? 10 : 20);
|
||||
|
||||
const footerTable = new Table({
|
||||
width: { size: 100, type: WidthType.PERCENTAGE },
|
||||
layout: TableLayoutType.FIXED, borders: allNoBorders,
|
||||
rows: [new TableRow({
|
||||
children: [
|
||||
new TableCell({
|
||||
width: { size: 70, type: WidthType.PERCENTAGE }, borders: noBorders, shading,
|
||||
children: [new Paragraph({
|
||||
indent: { left: PAD_L },
|
||||
children: [new TextRun({ text: footerLeft, size: flSize,
|
||||
color: P.cover.footerColor || "9A9A9A",
|
||||
font: { ascii: "Calibri" }, characterSpacing: flSpacing })],
|
||||
})],
|
||||
}),
|
||||
new TableCell({
|
||||
width: { size: 30, type: WidthType.PERCENTAGE }, borders: noBorders, shading,
|
||||
children: [new Paragraph({
|
||||
alignment: AlignmentType.RIGHT, indent: { right: PAD_R },
|
||||
children: [new TextRun({ text: footerRight, size: 18,
|
||||
color: P.cover.footerColor || "9A9A9A",
|
||||
font: { ascii: "Calibri" }, characterSpacing: 20 })],
|
||||
})],
|
||||
}),
|
||||
],
|
||||
})],
|
||||
});
|
||||
|
||||
// 7. 2-row wrapper (content + footer)
|
||||
return [new Table({
|
||||
width: { size: 100, type: WidthType.PERCENTAGE },
|
||||
layout: TableLayoutType.FIXED, borders: allNoBorders,
|
||||
rows: [
|
||||
new TableRow({
|
||||
height: { value: CONTENT_H, rule: "exact" },
|
||||
children: [new TableCell({
|
||||
shading, borders: noBorders,
|
||||
margins: { top: 0, bottom: 0, left: 0, right: 0 },
|
||||
verticalAlign: VerticalAlign.TOP,
|
||||
children,
|
||||
})],
|
||||
}),
|
||||
new TableRow({
|
||||
height: { value: FOOTER_H, rule: "exact" },
|
||||
children: [new TableCell({
|
||||
shading, borders: noBorders,
|
||||
margins: { top: 0, bottom: 0, left: 0, right: 0 },
|
||||
verticalAlign: VerticalAlign.CENTER,
|
||||
children: [footerTable],
|
||||
})],
|
||||
}),
|
||||
],
|
||||
})];
|
||||
}
|
||||
|
||||
// R6 title layout: prefer FEWER lines over larger font size (single line best)
|
||||
function calcTitleLayoutR6(title, availableWidthTw, preferredPt, minPt) {
|
||||
const step = 2;
|
||||
// Try to fit in 1 line (shrink font if needed)
|
||||
for (let pt = preferredPt; pt >= minPt; pt -= step) {
|
||||
const charWidthTw = pt * 23 * 0.5; // CJK ~50% em width
|
||||
const charsPerLine = Math.floor(availableWidthTw / charWidthTw);
|
||||
if (title.length <= charsPerLine) return { titlePt: pt, titleLines: [title] };
|
||||
}
|
||||
// Can't fit in 1 line, try 2 lines at largest possible font
|
||||
for (let pt = preferredPt; pt >= minPt; pt -= step) {
|
||||
const charWidthTw = pt * 23 * 0.5;
|
||||
const charsPerLine = Math.floor(availableWidthTw / charWidthTw);
|
||||
const lines = splitTitleLines(title, charsPerLine);
|
||||
if (lines.length <= 2) return { titlePt: pt, titleLines: lines };
|
||||
}
|
||||
// Fallback: minPt, up to 3 lines
|
||||
const charWidthTw = minPt * 23 * 0.5;
|
||||
const charsPerLine = Math.floor(availableWidthTw / charWidthTw);
|
||||
return { titlePt: minPt, titleLines: splitTitleLines(title, charsPerLine) };
|
||||
}
|
||||
|
||||
// Usage: cover top decoration
|
||||
// children: [colorStrip(P.accent, 120), ...]
|
||||
```
|
||||
|
||||
#### 2. Side Ribbon
|
||||
|
||||
Uses left border to create vertical ribbon effect.
|
||||
|
||||
```js
|
||||
function sideRibbon(content, color, width = 14) {
|
||||
return new Paragraph({
|
||||
border: {
|
||||
left: { style: BorderStyle.SINGLE, size: width, color: color.replace("#", ""), space: 12 },
|
||||
},
|
||||
indent: { left: 240 },
|
||||
spacing: { before: 100, after: 100 },
|
||||
children: content,
|
||||
});
|
||||
}
|
||||
|
||||
// Usage: emphasis quotes, chapter tips
|
||||
// sideRibbon([new TextRun({ text: "Key Insight", bold: true })], P.accent)
|
||||
```
|
||||
|
||||
#### 3. Border Compositions
|
||||
|
||||
```js
|
||||
// Top thick line + bottom thin line — title area frame
|
||||
function frameTitle(titleRuns) {
|
||||
return new Paragraph({
|
||||
border: {
|
||||
top: { style: BorderStyle.SINGLE, size: 18, color: c(P.accent) },
|
||||
bottom: { style: BorderStyle.SINGLE, size: 4, color: c(P.accent) },
|
||||
},
|
||||
spacing: { before: 400, after: 200 },
|
||||
alignment: AlignmentType.CENTER,
|
||||
children: titleRuns,
|
||||
});
|
||||
}
|
||||
|
||||
// L-shape border — left + bottom
|
||||
function lShapeBorder(content) {
|
||||
return new Paragraph({
|
||||
border: {
|
||||
left: { style: BorderStyle.SINGLE, size: 12, color: c(P.accent), space: 10 },
|
||||
bottom: { style: BorderStyle.SINGLE, size: 12, color: c(P.accent) },
|
||||
},
|
||||
indent: { left: 300 },
|
||||
spacing: { before: 200, after: 300 },
|
||||
children: content,
|
||||
});
|
||||
}
|
||||
|
||||
// Double-line frame — top and bottom double lines
|
||||
function doubleLine(content) {
|
||||
return new Paragraph({
|
||||
border: {
|
||||
top: { style: BorderStyle.DOUBLE, size: 6, color: c(P.accent) },
|
||||
bottom: { style: BorderStyle.DOUBLE, size: 6, color: c(P.accent) },
|
||||
},
|
||||
spacing: { before: 200, after: 200 },
|
||||
alignment: AlignmentType.CENTER,
|
||||
children: content,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
#### 4. Gradient Simulation
|
||||
|
||||
Multiple narrow color strips to simulate gradient effect.
|
||||
|
||||
```js
|
||||
function gradientStrip(startColor, endColor, steps = 5, totalHeight = 200) {
|
||||
const rows = [];
|
||||
const h = Math.floor(totalHeight / steps);
|
||||
for (let i = 0; i < steps; i++) {
|
||||
const ratio = i / (steps - 1);
|
||||
const blended = blendColors(startColor, endColor, ratio);
|
||||
rows.push(new TableRow({
|
||||
height: { value: h, rule: "exact" },
|
||||
children: [new TableCell({
|
||||
shading: { type: ShadingType.CLEAR, fill: blended },
|
||||
borders: { top: NB, bottom: NB, left: NB, right: NB },
|
||||
children: [new Paragraph({ children: [] })],
|
||||
})],
|
||||
}));
|
||||
}
|
||||
return new Table({
|
||||
width: { size: 100, type: WidthType.PERCENTAGE },
|
||||
borders: { top: NB, bottom: NB, left: NB, right: NB,
|
||||
insideHorizontal: NB, insideVertical: NB },
|
||||
rows,
|
||||
});
|
||||
}
|
||||
|
||||
function blendColors(hex1, hex2, ratio) {
|
||||
const r1 = parseInt(hex1.slice(1, 3), 16), g1 = parseInt(hex1.slice(3, 5), 16), b1 = parseInt(hex1.slice(5, 7), 16);
|
||||
const r2 = parseInt(hex2.slice(1, 3), 16), g2 = parseInt(hex2.slice(3, 5), 16), b2 = parseInt(hex2.slice(5, 7), 16);
|
||||
const r = Math.round(r1 + (r2 - r1) * ratio), g = Math.round(g1 + (g2 - g1) * ratio), b = Math.round(b1 + (b2 - b1) * ratio);
|
||||
return `${r.toString(16).padStart(2,"0")}${g.toString(16).padStart(2,"0")}${b.toString(16).padStart(2,"0")}`;
|
||||
}
|
||||
```
|
||||
|
||||
#### 5. Symbol Ornaments
|
||||
|
||||
```js
|
||||
// Section divider line — for chapter separation
|
||||
function ornamentDivider(symbol = "◆", count = 3) {
|
||||
const ornament = Array(count).fill(symbol).join(" ");
|
||||
return new Paragraph({
|
||||
alignment: AlignmentType.CENTER,
|
||||
spacing: { before: 400, after: 400 },
|
||||
children: [new TextRun({ text: ornament, size: 20, color: c(P.accent) })],
|
||||
});
|
||||
}
|
||||
|
||||
// Common decoration symbols
|
||||
// ◆ ◇ ● ○ ★ ☆ ■ □ ▲ △ ─ ━ ═ ║ ╔ ╗ ╚ ╝
|
||||
// Ornamental: ❧ ❦ ✦ ✧ ✿ ❀ ❁ ※
|
||||
```
|
||||
|
||||
#### 6. Info Card — Table Implementation
|
||||
|
||||
```js
|
||||
function infoCard(title, items, accentColor) {
|
||||
const ac = accentColor.replace("#", "");
|
||||
const headerRow = new TableRow({
|
||||
children: [new TableCell({
|
||||
columnSpan: 2,
|
||||
shading: { type: ShadingType.CLEAR, fill: ac },
|
||||
margins: { top: 80, bottom: 80, left: 160, right: 160 },
|
||||
borders: { top: NB, bottom: NB, left: NB, right: NB },
|
||||
children: [new Paragraph({
|
||||
children: [new TextRun({ text: title, bold: true, size: 24, color: "FFFFFF" })],
|
||||
})],
|
||||
})],
|
||||
});
|
||||
|
||||
const dataRows = items.map(([label, value]) => new TableRow({
|
||||
children: [
|
||||
new TableCell({
|
||||
width: { size: 30, type: WidthType.PERCENTAGE },
|
||||
margins: { top: 60, bottom: 60, left: 160, right: 80 },
|
||||
shading: { type: ShadingType.CLEAR, fill: "F8F9FA" },
|
||||
borders: { bottom: { style: BorderStyle.SINGLE, size: 1, color: "E0E0E0" },
|
||||
top: NB, left: NB, right: NB },
|
||||
children: [new Paragraph({ children: [new TextRun({ text: label, size: 21, color: "666666" })] })],
|
||||
}),
|
||||
new TableCell({
|
||||
margins: { top: 60, bottom: 60, left: 80, right: 160 },
|
||||
borders: { bottom: { style: BorderStyle.SINGLE, size: 1, color: "E0E0E0" },
|
||||
top: NB, left: NB, right: NB },
|
||||
children: [new Paragraph({ children: [new TextRun({ text: value, size: 21 })] })],
|
||||
}),
|
||||
],
|
||||
}));
|
||||
|
||||
return new Table({
|
||||
width: { size: 80, type: WidthType.PERCENTAGE },
|
||||
alignment: AlignmentType.CENTER,
|
||||
borders: { top: NB, bottom: NB, left: NB, right: NB,
|
||||
insideHorizontal: NB, insideVertical: NB },
|
||||
rows: [headerRow, ...dataRows],
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
// R7 — Swiss Tech Minimalist (slate grey bg, Klein blue accent, asymmetric layout)
|
||||
// Suitable for: cultural/creative research, trend reports, brand strategy, design deliverables
|
||||
// Palette: ST-1 (exclusive)
|
||||
// Layout: left-aligned title (upper 20%), right-shifted subtitle with top rule,
|
||||
// right-aligned info block with accent right border, Swiss cross anchor
|
||||
// Key features: ■ square accent dot, open-frame tables, large whitespace
|
||||
//
|
||||
// ⚠️ MANDATORY: All cover non-negotiables apply (margin=0, 16838 exact, allNoBorders)
|
||||
// ⚠️ Title uses calcTitleLayout() with maxPt=36 (not 40 — R7 uses lighter visual weight)
|
||||
|
||||
function buildCoverR7(config) {
|
||||
const P = palettes[config.palette || "ST-1"];
|
||||
const C = P.cover;
|
||||
const padL = 600;
|
||||
|
||||
// Title layout — R7 uses 36pt max (lighter than R1-R4's 40pt)
|
||||
const availW = 11906 - padL - 600;
|
||||
const { titlePt, titleLines } = calcTitleLayout(config.title, availW, 36, 24);
|
||||
const titleSize = titlePt * 2;
|
||||
const lineH = Math.ceil(titlePt * 23);
|
||||
|
||||
// Dynamic spacing based on title lines
|
||||
const topSpacer = titleLines.length <= 2 ? 1200 : 800;
|
||||
const subtitleSpacer = titleLines.length <= 2 ? 1400 : 800;
|
||||
const infoSpacer = titleLines.length <= 2 ? 2200 : 1200;
|
||||
|
||||
const children = [];
|
||||
|
||||
// 1. Swiss cross anchor — top-left decorative element
|
||||
children.push(new Paragraph({
|
||||
spacing: { before: 600 },
|
||||
indent: { left: padL },
|
||||
children: [new TextRun({
|
||||
text: "\uFF0B", // + fullwidth plus
|
||||
size: 40, bold: true, color: C.titleColor,
|
||||
font: { ascii: "Arial", eastAsia: "SimHei" },
|
||||
})],
|
||||
}));
|
||||
|
||||
// 2. Top spacer
|
||||
children.push(new Paragraph({ spacing: { before: topSpacer } }));
|
||||
|
||||
// 3. Title lines — left-aligned, last line has accent ■
|
||||
titleLines.forEach((line, i) => {
|
||||
const isLast = i === titleLines.length - 1;
|
||||
const runs = [new TextRun({
|
||||
text: line, size: titleSize, color: C.titleColor,
|
||||
font: { ascii: "Arial", eastAsia: "Noto Sans SC" },
|
||||
})];
|
||||
if (isLast) {
|
||||
runs.push(new TextRun({
|
||||
text: " \u25A0", // ■ black square
|
||||
size: 24, color: P.accent,
|
||||
font: { ascii: "Arial" },
|
||||
}));
|
||||
}
|
||||
children.push(new Paragraph({
|
||||
indent: { left: padL },
|
||||
spacing: { after: isLast ? 200 : 80, line: lineH, lineRule: "atLeast" },
|
||||
children: runs,
|
||||
}));
|
||||
});
|
||||
|
||||
// 4. Subtitle spacer
|
||||
children.push(new Paragraph({ spacing: { before: subtitleSpacer } }));
|
||||
|
||||
// 5. Subtitle — right-shifted, top border rule, wide character spacing
|
||||
if (config.subtitle) {
|
||||
children.push(new Paragraph({
|
||||
indent: { left: 3800, right: 600 },
|
||||
border: { top: { style: BorderStyle.SINGLE, size: 2, color: C.titleColor, space: 14 } },
|
||||
spacing: { after: 200 },
|
||||
children: [new TextRun({
|
||||
text: config.subtitle, size: 26, color: C.subtitleColor,
|
||||
font: { ascii: "Arial", eastAsia: "Noto Sans SC" },
|
||||
characterSpacing: 40,
|
||||
})],
|
||||
}));
|
||||
}
|
||||
|
||||
// 6. Decorative horizontal line
|
||||
children.push(new Paragraph({
|
||||
spacing: { before: 600 },
|
||||
border: { bottom: { style: BorderStyle.SINGLE, size: 1, color: "C8D0DC", space: 0 } },
|
||||
}));
|
||||
|
||||
// 7. Info spacer
|
||||
children.push(new Paragraph({ spacing: { before: infoSpacer } }));
|
||||
|
||||
// 8. Info footer — right-aligned, 4 label+value pairs, accent right border
|
||||
// Standard fields: ORGANIZATION, RESPONSIBILITY, REPORT NUMBER, DATE & EDITION
|
||||
const metaEntries = config.metaEntries || [
|
||||
{ label: "ORGANIZATION", value: config.organization || "" },
|
||||
{ label: "RESPONSIBILITY", value: config.responsibility || "" },
|
||||
{ label: "REPORT NUMBER", value: config.reportNumber || "" },
|
||||
{ label: "DATE & EDITION", value: config.dateEdition || "" },
|
||||
];
|
||||
|
||||
for (const entry of metaEntries) {
|
||||
// Label — 7pt uppercase English
|
||||
children.push(new Paragraph({
|
||||
alignment: AlignmentType.RIGHT,
|
||||
indent: { right: 800 },
|
||||
border: { right: { style: BorderStyle.SINGLE, size: 12, color: P.accent, space: 16 } },
|
||||
spacing: { after: 20 },
|
||||
children: [new TextRun({
|
||||
text: entry.label, size: 14, color: C.metaColor,
|
||||
font: { ascii: "Arial" },
|
||||
characterSpacing: 20,
|
||||
})],
|
||||
}));
|
||||
// Value — 11pt bold
|
||||
children.push(new Paragraph({
|
||||
alignment: AlignmentType.RIGHT,
|
||||
indent: { right: 800 },
|
||||
border: { right: { style: BorderStyle.SINGLE, size: 12, color: P.accent, space: 16 } },
|
||||
spacing: { after: 280 },
|
||||
children: [new TextRun({
|
||||
text: entry.value, size: 22, bold: true, color: C.titleColor,
|
||||
font: { ascii: "Arial", eastAsia: "Noto Sans SC" },
|
||||
})],
|
||||
}));
|
||||
}
|
||||
|
||||
// Wrap in 16838 exact wrapper table
|
||||
return [new Table({
|
||||
width: { size: 100, type: WidthType.PERCENTAGE },
|
||||
layout: TableLayoutType.FIXED,
|
||||
borders: allNoBorders,
|
||||
rows: [new TableRow({
|
||||
height: { value: 16838, rule: "exact" },
|
||||
children: [new TableCell({
|
||||
shading: { type: ShadingType.CLEAR, fill: P.bg },
|
||||
borders: noBorders,
|
||||
verticalAlign: VerticalAlign.TOP,
|
||||
children,
|
||||
})],
|
||||
})],
|
||||
})];
|
||||
}
|
||||
|
||||
### Decoration Usage Scenarios
|
||||
|
||||
| Scenario | Recommended Decoration | Combination |
|
||||
|------|----------|----------|
|
||||
| Report cover | Color strip + L-frame border | Top strip → Title area → L-frame author info |
|
||||
| Proposal cover | Gradient simulation + double-line frame | Gradient bg → Double-line title |
|
||||
| Chapter separator | Symbol ornament + side ribbon | Symbol divider → New chapter title with ribbon |
|
||||
| Summary card | Info card | Standalone card displaying key metrics |
|
||||
| Academic cover | Color strip + info table | Top strip → School name → Title → Info table |
|
||||
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user