diff --git a/apps/mcp-server/src/mcp.ts b/apps/mcp-server/src/mcp.ts index 90d5b28..86cb117 100644 --- a/apps/mcp-server/src/mcp.ts +++ b/apps/mcp-server/src/mcp.ts @@ -140,7 +140,7 @@ function handleInitialize(id: number | string | null): JsonRpcResponse { version: "0.1.0", }, instructions: - "OOXML (ECMA-376 / Office Open XML) reference server. Three tool families: (1) prose search over the spec PDFs (ooxml_search, ooxml_section, ooxml_parts); (2) deterministic schema lookup over the parsed XSDs (ooxml_element, ooxml_type, ooxml_children, ooxml_attributes, ooxml_enum, ooxml_namespace); (3) OPC package metadata curated from Part 1 §11.3.x / §12.3.x / §13.3.x / §15.x (ooxml_package_part). The three corpora can disagree about URIs for the same concept (custom XML data storage is the canonical example); each tool surface notes when it keys on the XSD URI vs the spec-prose URI.", + "OOXML (ECMA-376 / Office Open XML) reference server. Four tool families: (1) prose search over the spec PDFs (ooxml_search, ooxml_section, ooxml_parts); (2) deterministic schema lookup over the parsed XSDs (ooxml_element, ooxml_type, ooxml_children, ooxml_attributes, ooxml_enum, ooxml_namespace); (3) OPC package metadata curated from Part 1 §11.3.x / §12.3.x / §13.3.x / §15.x (ooxml_package_part); (4) preset shape geometry curated from Annex D presetShapeDefinitions.xml — adjust-guide names for every shape (ooxml_preset_shape). The spec PDF search tools cannot answer questions about Annex D content; use ooxml_preset_shape for that. The three schema/prose/package corpora can disagree about URIs for the same concept (custom XML data storage is the canonical example); each tool surface notes when it keys on the XSD URI vs the spec-prose URI.", }, }; } diff --git a/apps/mcp-server/src/ooxml-tools.ts b/apps/mcp-server/src/ooxml-tools.ts index 8541132..26f73f9 100644 --- a/apps/mcp-server/src/ooxml-tools.ts +++ b/apps/mcp-server/src/ooxml-tools.ts @@ -3,7 +3,8 @@ * * Tools: * ooxml_element, ooxml_type, ooxml_children, - * ooxml_attributes, ooxml_enum, ooxml_namespace. + * ooxml_attributes, ooxml_enum, ooxml_namespace, + * ooxml_package_part, ooxml_preset_shape. * * Default profile is `transitional`. Future profiles (e.g. word-compatible-docx) * will compose Transitional with Office extension schemas. @@ -11,6 +12,7 @@ import { neon } from "@neondatabase/serverless"; import type { ToolDef } from "./mcp"; +import { listAdjustableShapes, lookupShapeGuides } from "./preset-shape-geom"; import { type AttrEntry, type ChildEdge, @@ -166,6 +168,26 @@ export const OOXML_TOOL_DEFS: ToolDef[] = [ }, }, }, + { + name: "ooxml_preset_shape", + description: + "Look up the adjust-value guide names for a DrawingML preset shape (`` attribute value). " + + "Returns the ordered `` names that must appear in the `` when emitting adjust values, sourced from " + + "ECMA-376 Part 1 Annex D (`presetShapeDefinitions.xml`). The spec PDF search tools cannot answer this — the guide " + + "names live in a separate electronic addendum, not in the main spec text. " + + "Pass `shape` to look up a single shape (e.g. 'round2SameRect', 'upArrow', 'roundRect'). " + + "Omit `shape` to list every preset shape that takes adjust values.", + inputSchema: { + type: "object" as const, + properties: { + shape: { + type: "string", + description: + "Preset shape name — the @prst attribute value on , e.g. 'round2SameRect', 'upArrow'. Omit to list all adjustable shapes.", + }, + }, + }, + }, ]; export type OoxmlToolName = @@ -175,7 +197,8 @@ export type OoxmlToolName = | "ooxml_attributes" | "ooxml_enum" | "ooxml_namespace" - | "ooxml_package_part"; + | "ooxml_package_part" + | "ooxml_preset_shape"; const OOXML_TOOL_NAMES: ReadonlySet = new Set(OOXML_TOOL_DEFS.map((t) => t.name)); @@ -423,6 +446,19 @@ export async function runOoxmlTool( }); } + case "ooxml_preset_shape": { + const shape = typeof args.shape === "string" ? args.shape.trim() : ""; + if (!shape) { + const shapes = listAdjustableShapes(); + return formatPresetShapeList(shapes); + } + const guides = lookupShapeGuides(shape); + if (guides === null) { + return formatPresetShapeNoGuides(shape); + } + return formatPresetShapeReport(shape, guides); + } + default: { const _exhaustive: never = name; throw new Error(`Unhandled OOXML tool: ${_exhaustive}`); @@ -819,3 +855,126 @@ function formatLocalElementReport( } return lines.join("\n"); } + +// --- Preset shape geometry formatting ----------------------------------- + +function formatPresetShapeReport(shape: string, guides: string[]): string { + const lines: string[] = []; + lines.push(`## Preset shape: ${shape}`); + lines.push(""); + lines.push("**Source**: ECMA-376 Part 1 Annex D (`presetShapeDefinitions.xml`)"); + lines.push(""); + lines.push("**`` guide names** (in order):"); + lines.push(""); + for (const g of guides) { + lines.push(`- \`${g}\``); + } + lines.push(""); + lines.push( + "Emit these as `` children of `` inside ``. " + + "Default values and formula details are in Annex D (`presetShapeDefinitions.xml`); " + + "see also the ONLYOFFICE OOXMLShapes C++ implementations for annotated formulas: " + + "https://github.com/ONLYOFFICE/core/tree/master/MsBinaryFile/Common/Vml/PPTXShape/OOXMLShapes", + ); + lines.push(""); + lines.push("**Scale note**: most `adj*` values use a 0–100000 unit where 100000 represents"); + lines.push( + "the full reference dimension (often `ss = min(w, h)`). Many shapes clamp internally", + ); + lines.push("to 0–50000 via a `pin` formula. `hf`/`vf` guides use a different scale."); + lines.push(""); + lines.push( + "**Spec ref**: ECMA-376 Part 1 §20.1.10.55 (`ST_ShapeType` enumeration), Annex D geometry", + ); + return lines.join("\n"); +} + +function formatPresetShapeNoGuides(shape: string): string { + const lines: string[] = []; + lines.push(`## Preset shape: ${shape}`); + lines.push(""); + + // Distinguish known-no-guides from unknown shape name + const allShapeNames = new Set([ + // A representative sample of shapes that take no guides (complete list + // would be every ST_ShapeType value not in the adjustable set). + "rect", + "ellipse", + "line", + "rtTriangle", + "diamond", + "parallelogram", + "trapezoid", + "pentagon", + "hexagon", + "heptagon", + "octagon", + "decagon", + "dodecagon", + "star4", + "star5", + "star6", + "star7", + "star8", + "star10", + "star12", + "star16", + "star24", + "star32", + "ribbon", + "ribbon2", + "ellipseRibbon", + "ellipseRibbon2", + "irregularSeal1", + "irregularSeal2", + "lightningBolt", + "heart", + "sun", + "moon", + "smileyFace", + "cloud", + "noSmoking", + "can", + "cube", + "bevel", + "donut", + "plus", + ]); + + lines.push( + `\`${shape}\` takes **no adjust values** — emit an empty \`\` inside \`\`.`, + ); + lines.push(""); + if (!allShapeNames.has(shape)) { + lines.push( + `_If you expected adjust values for this shape, double-check the spelling against §20.1.10.55 (\`ST_ShapeType\`). Unknown shape names are also reported here as "no guides"._`, + ); + lines.push(""); + } + lines.push("Call `ooxml_preset_shape` with no args to list every shape that has adjust guides."); + return lines.join("\n"); +} + +function formatPresetShapeList(shapes: string[]): string { + const lines: string[] = []; + lines.push("## Preset shapes with adjust guides"); + lines.push(""); + lines.push( + `${shapes.length} preset shapes declare at least one \`\` in their \`\`. ` + + "Pass a `shape` name for the ordered guide names.", + ); + lines.push(""); + lines.push("| shape | guide count |"); + lines.push("| --- | --- |"); + + for (const s of shapes) { + const count = lookupShapeGuides(s)?.length ?? 0; + lines.push(`| \`${s}\` | ${count} |`); + } + lines.push(""); + lines.push( + "**Source**: ECMA-376 Part 1 Annex D (`presetShapeDefinitions.xml`). " + + "Shapes not listed here take no adjust values (empty `` is correct).", + ); + return lines.join("\n"); +} diff --git a/apps/mcp-server/src/preset-shape-geom.ts b/apps/mcp-server/src/preset-shape-geom.ts new file mode 100644 index 0000000..0471b36 --- /dev/null +++ b/apps/mcp-server/src/preset-shape-geom.ts @@ -0,0 +1,413 @@ +/** + * Curated reference for DrawingML preset shape geometry adjust guides. + * + * The schema graph answers "what elements are legal inside ?". + * The prose corpus answers "what does §20.1.10.55 say?". Neither answers + * "which names must I put in the for a round2SameRect?" + * That data lives in ECMA-376 Part 1 Annex D (OfficeOpenXML-DrawingMLGeometries.zip / + * presetShapeDefinitions.xml), which is shipped as a separate electronic + * addendum and is not part of the spec PDFs. + * + * Static typed data here, no DB. The set is complete for the 5th edition + * and static across editions; PR diff is the right audit primitive. Shapes + * not listed here take no adjust values (empty is correct). + * + * Default values and geometry formulas (gdLst, pathLst): see the Annex D + * XML directly, or the ONLYOFFICE OOXMLShapes C++ implementations at + * https://github.com/ONLYOFFICE/core/tree/master/MsBinaryFile/Common/Vml/PPTXShape/OOXMLShapes + * + * Sources: + * - ECMA-376 Part 1 Annex D presetShapeDefinitions.xml (authoritative) + * - LibreOffice oox-drawingml-adj-names (cross-check) + * - ONLYOFFICE OOXMLShapes CRound2SameRect.cpp etc. (cross-check) + */ + +/** A single adjust guide declared in a shape's . */ +export interface AdjGuide { + /** Guide name used in ``, e.g. "adj", "adj1", "hf". */ + name: string; +} + +// Raw pairs [shapeName, guideName] in avLst order for each shape. +// One row per guide per shape. Sourced from Annex D cross-checked against +// LibreOffice oox-drawingml-adj-names. +const RAW_PAIRS: readonly [string, string][] = [ + ["accentBorderCallout1", "adj1"], + ["accentBorderCallout1", "adj2"], + ["accentBorderCallout1", "adj3"], + ["accentBorderCallout1", "adj4"], + ["accentBorderCallout2", "adj1"], + ["accentBorderCallout2", "adj2"], + ["accentBorderCallout2", "adj3"], + ["accentBorderCallout2", "adj4"], + ["accentBorderCallout2", "adj5"], + ["accentBorderCallout2", "adj6"], + ["accentBorderCallout3", "adj1"], + ["accentBorderCallout3", "adj2"], + ["accentBorderCallout3", "adj3"], + ["accentBorderCallout3", "adj4"], + ["accentBorderCallout3", "adj5"], + ["accentBorderCallout3", "adj6"], + ["accentBorderCallout3", "adj7"], + ["accentBorderCallout3", "adj8"], + ["accentCallout1", "adj1"], + ["accentCallout1", "adj2"], + ["accentCallout1", "adj3"], + ["accentCallout1", "adj4"], + ["accentCallout2", "adj1"], + ["accentCallout2", "adj2"], + ["accentCallout2", "adj3"], + ["accentCallout2", "adj4"], + ["accentCallout2", "adj5"], + ["accentCallout2", "adj6"], + ["accentCallout3", "adj1"], + ["accentCallout3", "adj2"], + ["accentCallout3", "adj3"], + ["accentCallout3", "adj4"], + ["accentCallout3", "adj5"], + ["accentCallout3", "adj6"], + ["accentCallout3", "adj7"], + ["accentCallout3", "adj8"], + ["arc", "adj1"], + ["arc", "adj2"], + ["bentArrow", "adj1"], + ["bentArrow", "adj2"], + ["bentArrow", "adj3"], + ["bentArrow", "adj4"], + ["bentConnector3", "adj1"], + ["bentConnector4", "adj1"], + ["bentConnector4", "adj2"], + ["bentConnector5", "adj1"], + ["bentConnector5", "adj2"], + ["bentConnector5", "adj3"], + ["bentUpArrow", "adj1"], + ["bentUpArrow", "adj2"], + ["bentUpArrow", "adj3"], + ["bevel", "adj"], + ["blockArc", "adj1"], + ["blockArc", "adj2"], + ["blockArc", "adj3"], + ["borderCallout1", "adj1"], + ["borderCallout1", "adj2"], + ["borderCallout1", "adj3"], + ["borderCallout1", "adj4"], + ["borderCallout2", "adj1"], + ["borderCallout2", "adj2"], + ["borderCallout2", "adj3"], + ["borderCallout2", "adj4"], + ["borderCallout2", "adj5"], + ["borderCallout2", "adj6"], + ["borderCallout3", "adj1"], + ["borderCallout3", "adj2"], + ["borderCallout3", "adj3"], + ["borderCallout3", "adj4"], + ["borderCallout3", "adj5"], + ["borderCallout3", "adj6"], + ["borderCallout3", "adj7"], + ["borderCallout3", "adj8"], + ["bracePair", "adj"], + ["bracketPair", "adj"], + ["callout1", "adj1"], + ["callout1", "adj2"], + ["callout1", "adj3"], + ["callout1", "adj4"], + ["callout2", "adj1"], + ["callout2", "adj2"], + ["callout2", "adj3"], + ["callout2", "adj4"], + ["callout2", "adj5"], + ["callout2", "adj6"], + ["callout3", "adj1"], + ["callout3", "adj2"], + ["callout3", "adj3"], + ["callout3", "adj4"], + ["callout3", "adj5"], + ["callout3", "adj6"], + ["callout3", "adj7"], + ["callout3", "adj8"], + ["can", "adj"], + ["chevron", "adj"], + ["chord", "adj1"], + ["chord", "adj2"], + ["circularArrow", "adj1"], + ["circularArrow", "adj2"], + ["circularArrow", "adj3"], + ["circularArrow", "adj4"], + ["circularArrow", "adj5"], + ["cloudCallout", "adj1"], + ["cloudCallout", "adj2"], + ["corner", "adj1"], + ["corner", "adj2"], + ["cube", "adj"], + ["curvedConnector3", "adj1"], + ["curvedConnector4", "adj1"], + ["curvedConnector4", "adj2"], + ["curvedConnector5", "adj1"], + ["curvedConnector5", "adj2"], + ["curvedConnector5", "adj3"], + ["curvedDownArrow", "adj1"], + ["curvedDownArrow", "adj2"], + ["curvedDownArrow", "adj3"], + ["curvedLeftArrow", "adj1"], + ["curvedLeftArrow", "adj2"], + ["curvedLeftArrow", "adj3"], + ["curvedRightArrow", "adj1"], + ["curvedRightArrow", "adj2"], + ["curvedRightArrow", "adj3"], + ["curvedUpArrow", "adj1"], + ["curvedUpArrow", "adj2"], + ["curvedUpArrow", "adj3"], + ["decagon", "vf"], + ["diagStripe", "adj"], + ["donut", "adj"], + ["doubleWave", "adj1"], + ["doubleWave", "adj2"], + ["downArrow", "adj1"], + ["downArrow", "adj2"], + ["downArrowCallout", "adj1"], + ["downArrowCallout", "adj2"], + ["downArrowCallout", "adj3"], + ["downArrowCallout", "adj4"], + ["ellipseRibbon", "adj1"], + ["ellipseRibbon", "adj2"], + ["ellipseRibbon", "adj3"], + ["ellipseRibbon2", "adj1"], + ["ellipseRibbon2", "adj2"], + ["ellipseRibbon2", "adj3"], + ["foldedCorner", "adj"], + ["frame", "adj1"], + ["gear6", "adj1"], + ["gear6", "adj2"], + ["gear9", "adj1"], + ["gear9", "adj2"], + ["halfFrame", "adj1"], + ["halfFrame", "adj2"], + ["heptagon", "hf"], + ["heptagon", "vf"], + ["hexagon", "adj"], + ["hexagon", "vf"], + ["homePlate", "adj"], + ["horizontalScroll", "adj"], + ["leftArrow", "adj1"], + ["leftArrow", "adj2"], + ["leftArrowCallout", "adj1"], + ["leftArrowCallout", "adj2"], + ["leftArrowCallout", "adj3"], + ["leftArrowCallout", "adj4"], + ["leftBrace", "adj1"], + ["leftBrace", "adj2"], + ["leftBracket", "adj"], + ["leftCircularArrow", "adj1"], + ["leftCircularArrow", "adj2"], + ["leftCircularArrow", "adj3"], + ["leftCircularArrow", "adj4"], + ["leftCircularArrow", "adj5"], + ["leftRightArrow", "adj1"], + ["leftRightArrow", "adj2"], + ["leftRightArrowCallout", "adj1"], + ["leftRightArrowCallout", "adj2"], + ["leftRightArrowCallout", "adj3"], + ["leftRightArrowCallout", "adj4"], + ["leftRightCircularArrow", "adj1"], + ["leftRightCircularArrow", "adj2"], + ["leftRightCircularArrow", "adj3"], + ["leftRightCircularArrow", "adj4"], + ["leftRightCircularArrow", "adj5"], + ["leftRightRibbon", "adj1"], + ["leftRightRibbon", "adj2"], + ["leftRightRibbon", "adj3"], + ["leftRightUpArrow", "adj1"], + ["leftRightUpArrow", "adj2"], + ["leftRightUpArrow", "adj3"], + ["leftUpArrow", "adj1"], + ["leftUpArrow", "adj2"], + ["leftUpArrow", "adj3"], + ["mathDivide", "adj1"], + ["mathDivide", "adj2"], + ["mathDivide", "adj3"], + ["mathEqual", "adj1"], + ["mathEqual", "adj2"], + ["mathMinus", "adj1"], + ["mathMultiply", "adj1"], + ["mathNotEqual", "adj1"], + ["mathNotEqual", "adj2"], + ["mathNotEqual", "adj3"], + ["mathPlus", "adj1"], + ["moon", "adj"], + ["noSmoking", "adj"], + ["nonIsoscelesTrapezoid", "adj1"], + ["nonIsoscelesTrapezoid", "adj2"], + ["notchedRightArrow", "adj1"], + ["notchedRightArrow", "adj2"], + ["octagon", "adj"], + ["parallelogram", "adj"], + ["pentagon", "hf"], + ["pentagon", "vf"], + ["pie", "adj1"], + ["pie", "adj2"], + ["plaque", "adj"], + ["plus", "adj"], + ["quadArrow", "adj1"], + ["quadArrow", "adj2"], + ["quadArrow", "adj3"], + ["quadArrowCallout", "adj1"], + ["quadArrowCallout", "adj2"], + ["quadArrowCallout", "adj3"], + ["quadArrowCallout", "adj4"], + ["ribbon", "adj1"], + ["ribbon", "adj2"], + ["ribbon2", "adj1"], + ["ribbon2", "adj2"], + ["rightArrow", "adj1"], + ["rightArrow", "adj2"], + ["rightArrowCallout", "adj1"], + ["rightArrowCallout", "adj2"], + ["rightArrowCallout", "adj3"], + ["rightArrowCallout", "adj4"], + ["rightBrace", "adj1"], + ["rightBrace", "adj2"], + ["rightBracket", "adj"], + ["round1Rect", "adj"], + ["round2DiagRect", "adj1"], + ["round2DiagRect", "adj2"], + ["round2SameRect", "adj1"], + ["round2SameRect", "adj2"], + ["roundRect", "adj"], + ["smileyFace", "adj"], + ["snip1Rect", "adj"], + ["snip2DiagRect", "adj1"], + ["snip2DiagRect", "adj2"], + ["snip2SameRect", "adj1"], + ["snip2SameRect", "adj2"], + ["snipRoundRect", "adj1"], + ["snipRoundRect", "adj2"], + ["star10", "adj"], + ["star10", "hf"], + ["star12", "adj"], + ["star16", "adj"], + ["star24", "adj"], + ["star32", "adj"], + ["star4", "adj"], + ["star5", "adj"], + ["star5", "hf"], + ["star5", "vf"], + ["star6", "adj"], + ["star6", "hf"], + ["star7", "adj"], + ["star7", "hf"], + ["star7", "vf"], + ["star8", "adj"], + ["stripedRightArrow", "adj1"], + ["stripedRightArrow", "adj2"], + ["sun", "adj"], + ["swooshArrow", "adj1"], + ["swooshArrow", "adj2"], + ["teardrop", "adj"], + ["textArchDown", "adj"], + ["textArchDownPour", "adj1"], + ["textArchDownPour", "adj2"], + ["textArchUp", "adj"], + ["textArchUpPour", "adj1"], + ["textArchUpPour", "adj2"], + ["textButton", "adj"], + ["textButtonPour", "adj1"], + ["textButtonPour", "adj2"], + ["textCanDown", "adj"], + ["textCanUp", "adj"], + ["textCascadeDown", "adj"], + ["textCascadeUp", "adj"], + ["textChevron", "adj"], + ["textChevronInverted", "adj"], + ["textCircle", "adj"], + ["textCirclePour", "adj1"], + ["textCirclePour", "adj2"], + ["textCurveDown", "adj"], + ["textCurveUp", "adj"], + ["textDeflate", "adj"], + ["textDeflateBottom", "adj"], + ["textDeflateInflate", "adj"], + ["textDeflateInflateDeflate", "adj"], + ["textDeflateTop", "adj"], + ["textDoubleWave1", "adj1"], + ["textDoubleWave1", "adj2"], + ["textFadeDown", "adj"], + ["textFadeLeft", "adj"], + ["textFadeRight", "adj"], + ["textFadeUp", "adj"], + ["textInflate", "adj"], + ["textInflateBottom", "adj"], + ["textInflateTop", "adj"], + ["textPlain", "adj"], + ["textRingInside", "adj"], + ["textRingOutside", "adj"], + ["textSlantDown", "adj"], + ["textSlantUp", "adj"], + ["textStop", "adj"], + ["textTriangle", "adj"], + ["textTriangleInverted", "adj"], + ["textWave1", "adj1"], + ["textWave1", "adj2"], + ["textWave2", "adj1"], + ["textWave2", "adj2"], + ["textWave4", "adj1"], + ["textWave4", "adj2"], + ["trapezoid", "adj"], + ["triangle", "adj"], + ["upArrow", "adj1"], + ["upArrow", "adj2"], + ["upArrowCallout", "adj1"], + ["upArrowCallout", "adj2"], + ["upArrowCallout", "adj3"], + ["upArrowCallout", "adj4"], + ["upDownArrow", "adj1"], + ["upDownArrow", "adj2"], + ["upDownArrowCallout", "adj1"], + ["upDownArrowCallout", "adj2"], + ["upDownArrowCallout", "adj3"], + ["upDownArrowCallout", "adj4"], + ["uturnArrow", "adj1"], + ["uturnArrow", "adj2"], + ["uturnArrow", "adj3"], + ["uturnArrow", "adj4"], + ["uturnArrow", "adj5"], + ["verticalScroll", "adj"], + ["wave", "adj1"], + ["wave", "adj2"], + ["wedgeEllipseCallout", "adj1"], + ["wedgeEllipseCallout", "adj2"], + ["wedgeRectCallout", "adj1"], + ["wedgeRectCallout", "adj2"], + ["wedgeRoundRectCallout", "adj1"], + ["wedgeRoundRectCallout", "adj2"], + ["wedgeRoundRectCallout", "adj3"], +]; + +// Lazy index: shape name → ordered guide names +let _index: Map | null = null; + +function getIndex(): Map { + if (_index) return _index; + _index = new Map(); + for (const [shape, guide] of RAW_PAIRS) { + const existing = _index.get(shape); + if (existing) { + existing.push(guide); + } else { + _index.set(shape, [guide]); + } + } + return _index; +} + +/** + * Return the ordered avLst guide names for a preset shape, or null if the + * shape takes no adjust values (empty is the correct emission). + */ +export function lookupShapeGuides(shape: string): string[] | null { + return getIndex().get(shape) ?? null; +} + +/** All preset shape names that have at least one adjust guide, sorted. */ +export function listAdjustableShapes(): string[] { + return [...getIndex().keys()].sort(); +} diff --git a/data/sources.json b/data/sources.json index 4503849..6ddeb57 100644 --- a/data/sources.json +++ b/data/sources.json @@ -45,6 +45,15 @@ "url": "https://ecma-international.org/wp-content/uploads/ECMA-376-4_5th_edition_december_2016.zip", "license_note": "Published by Ecma International. See the ECMA-376 publications page for the current download and licensing terms before redistribution.", "sha256": "bd25da1109f73762356596918bf5ff8b74a1331642dba5f1c1d1dfc6bed34ecd" + }, + { + "name": "ecma-376-annex-d-dml-geometries", + "kind": "curated_static", + "edition": "5th", + "version": "2016-12", + "url": "https://ecma-international.org/publications-and-standards/standards/ecma-376/", + "license_note": "Derived from ECMA-376 Annex D electronic addenda (presetShapeDefinitions.xml). Adjust-guide names curated statically in preset-shape-geom.ts; full geometry formulas not included.", + "notes": "Annex D ships as a separate electronic addenda ZIP and is not indexed by the PDF ingestion pipeline. The ooxml_preset_shape tool serves adjust-value guide names (adj, adj1, adj2, hf, vf) from the curated static list. For full geometry path formulas, consult the addenda ZIP or reference implementations such as LibreOffice oox-drawingml-adj-names or ONLYOFFICE OOXMLShapes." } ] }