@nguyenphp/antigravity-marketing 1.0.18 → 1.0.20

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.
Files changed (231) hide show
  1. package/README.md +130 -78
  2. package/package.json +4 -3
  3. package/templates/.agent/skills/marketing-report-expert/SKILL.md +70 -0
  4. package/templates/.agent/skills/minimax-docx/LICENSE +21 -0
  5. package/templates/.agent/skills/minimax-docx/SKILL.md +274 -0
  6. package/templates/.agent/skills/minimax-docx/assets/styles/academic_styles.xml +250 -0
  7. package/templates/.agent/skills/minimax-docx/assets/styles/corporate_styles.xml +284 -0
  8. package/templates/.agent/skills/minimax-docx/assets/styles/default_styles.xml +449 -0
  9. package/templates/.agent/skills/minimax-docx/assets/xsd/aesthetic-rules.xsd +470 -0
  10. package/templates/.agent/skills/minimax-docx/assets/xsd/business-rules.xsd +130 -0
  11. package/templates/.agent/skills/minimax-docx/assets/xsd/common-types.xsd +159 -0
  12. package/templates/.agent/skills/minimax-docx/assets/xsd/wml-subset.xsd +589 -0
  13. package/templates/.agent/skills/minimax-docx/references/cjk_typography.md +357 -0
  14. package/templates/.agent/skills/minimax-docx/references/cjk_university_template_guide.md +184 -0
  15. package/templates/.agent/skills/minimax-docx/references/comments_guide.md +191 -0
  16. package/templates/.agent/skills/minimax-docx/references/design_good_bad_examples.md +829 -0
  17. package/templates/.agent/skills/minimax-docx/references/design_principles.md +819 -0
  18. package/templates/.agent/skills/minimax-docx/references/openxml_element_order.md +308 -0
  19. package/templates/.agent/skills/minimax-docx/references/openxml_encyclopedia_part1.md +4061 -0
  20. package/templates/.agent/skills/minimax-docx/references/openxml_encyclopedia_part2.md +2820 -0
  21. package/templates/.agent/skills/minimax-docx/references/openxml_encyclopedia_part3.md +3381 -0
  22. package/templates/.agent/skills/minimax-docx/references/openxml_namespaces.md +82 -0
  23. package/templates/.agent/skills/minimax-docx/references/openxml_units.md +72 -0
  24. package/templates/.agent/skills/minimax-docx/references/scenario_a_create.md +284 -0
  25. package/templates/.agent/skills/minimax-docx/references/scenario_b_edit_content.md +295 -0
  26. package/templates/.agent/skills/minimax-docx/references/scenario_c_apply_template.md +456 -0
  27. package/templates/.agent/skills/minimax-docx/references/track_changes_guide.md +200 -0
  28. package/templates/.agent/skills/minimax-docx/references/troubleshooting.md +506 -0
  29. package/templates/.agent/skills/minimax-docx/references/typography_guide.md +294 -0
  30. package/templates/.agent/skills/minimax-docx/references/xsd_validation_guide.md +158 -0
  31. package/templates/.agent/skills/minimax-docx/scripts/doc_to_docx.sh +40 -0
  32. package/templates/.agent/skills/minimax-docx/scripts/docx_preview.sh +37 -0
  33. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Cli/MiniMaxAIDocx.Cli.csproj +19 -0
  34. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Cli/Program.cs +18 -0
  35. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Commands/AnalyzeCommand.cs +147 -0
  36. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Commands/ApplyTemplateCommand.cs +322 -0
  37. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Commands/CreateCommand.cs +324 -0
  38. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Commands/DiffCommand.cs +155 -0
  39. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Commands/EditContentCommand.cs +487 -0
  40. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Commands/FixOrderCommand.cs +108 -0
  41. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Commands/MergeRunsCommand.cs +122 -0
  42. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Commands/ValidateCommand.cs +107 -0
  43. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/MiniMaxAIDocx.Core.csproj +15 -0
  44. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/OpenXml/CommentSynchronizer.cs +169 -0
  45. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/OpenXml/ElementOrder.cs +80 -0
  46. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/OpenXml/NamespaceConstants.cs +42 -0
  47. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/OpenXml/RunMerger.cs +81 -0
  48. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/OpenXml/StyleAnalyzer.cs +81 -0
  49. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/OpenXml/TrackChangesHelper.cs +99 -0
  50. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/OpenXml/UnitConverter.cs +23 -0
  51. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/AestheticRecipeSamples.cs +1832 -0
  52. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/AestheticRecipeSamples_Batch1.cs +910 -0
  53. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/AestheticRecipeSamples_Batch2.cs +999 -0
  54. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/AestheticRecipeSamples_Batch3.cs +1048 -0
  55. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/AestheticRecipeSamples_Batch4.cs +1038 -0
  56. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/CharacterFormattingSamples.cs +1020 -0
  57. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/DocumentCreationSamples.cs +1121 -0
  58. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/FieldAndTocSamples.cs +624 -0
  59. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/FootnoteAndCommentSamples.cs +675 -0
  60. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/HeaderFooterSamples.cs +838 -0
  61. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/ImageSamples.cs +917 -0
  62. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/ListAndNumberingSamples.cs +826 -0
  63. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/ParagraphFormattingSamples.cs +1199 -0
  64. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/StyleSystemSamples.cs +1487 -0
  65. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/TableSamples.cs +1163 -0
  66. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/TrackChangesSamples.cs +595 -0
  67. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Typography/CjkHelper.cs +39 -0
  68. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Typography/FontDefaults.cs +24 -0
  69. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Typography/PageSizes.cs +20 -0
  70. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Validation/BusinessRuleValidator.cs +224 -0
  71. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Validation/GateCheckValidator.cs +148 -0
  72. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Validation/ValidationResult.cs +23 -0
  73. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Validation/XsdValidator.cs +69 -0
  74. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.slnx +4 -0
  75. package/templates/.agent/skills/minimax-docx/scripts/env_check.sh +196 -0
  76. package/templates/.agent/skills/minimax-docx/scripts/setup.ps1 +274 -0
  77. package/templates/.agent/skills/minimax-docx/scripts/setup.sh +504 -0
  78. package/templates/.agent/skills/minimax-multimodal-toolkit/SKILL.md +359 -0
  79. package/templates/.agent/skills/minimax-pdf/README.md +222 -0
  80. package/templates/.agent/skills/minimax-pdf/SKILL.md +201 -0
  81. package/templates/.agent/skills/minimax-pdf/design/design.md +381 -0
  82. package/templates/.agent/skills/minimax-pdf/scripts/cover.py +1579 -0
  83. package/templates/.agent/skills/minimax-pdf/scripts/fill_inspect.py +200 -0
  84. package/templates/.agent/skills/minimax-pdf/scripts/fill_write.py +242 -0
  85. package/templates/.agent/skills/minimax-pdf/scripts/make.sh +491 -0
  86. package/templates/.agent/skills/minimax-pdf/scripts/merge.py +112 -0
  87. package/templates/.agent/skills/minimax-pdf/scripts/palette.py +559 -0
  88. package/templates/.agent/skills/minimax-pdf/scripts/reformat_parse.py +374 -0
  89. package/templates/.agent/skills/minimax-pdf/scripts/render_body.py +1055 -0
  90. package/templates/.agent/skills/minimax-pdf/scripts/render_cover.cjs +111 -0
  91. package/templates/.agent/skills/minimax-xlsx/SKILL.md +138 -0
  92. package/templates/.agent/skills/minimax-xlsx/references/create.md +691 -0
  93. package/templates/.agent/skills/minimax-xlsx/references/edit.md +684 -0
  94. package/templates/.agent/skills/minimax-xlsx/references/fix.md +37 -0
  95. package/templates/.agent/skills/minimax-xlsx/references/format.md +768 -0
  96. package/templates/.agent/skills/minimax-xlsx/references/ooxml-cheatsheet.md +231 -0
  97. package/templates/.agent/skills/minimax-xlsx/references/read-analyze.md +97 -0
  98. package/templates/.agent/skills/minimax-xlsx/references/validate.md +772 -0
  99. package/templates/.agent/skills/minimax-xlsx/scripts/formula_check.py +422 -0
  100. package/templates/.agent/skills/minimax-xlsx/scripts/libreoffice_recalc.py +248 -0
  101. package/templates/.agent/skills/minimax-xlsx/scripts/shared_strings_builder.py +163 -0
  102. package/templates/.agent/skills/minimax-xlsx/scripts/style_audit.py +575 -0
  103. package/templates/.agent/skills/minimax-xlsx/scripts/xlsx_add_column.py +395 -0
  104. package/templates/.agent/skills/minimax-xlsx/scripts/xlsx_insert_row.py +274 -0
  105. package/templates/.agent/skills/minimax-xlsx/scripts/xlsx_pack.py +87 -0
  106. package/templates/.agent/skills/minimax-xlsx/scripts/xlsx_reader.py +362 -0
  107. package/templates/.agent/skills/minimax-xlsx/scripts/xlsx_shift_rows.py +396 -0
  108. package/templates/.agent/skills/minimax-xlsx/scripts/xlsx_unpack.py +130 -0
  109. package/templates/.agent/skills/minimax-xlsx/templates/minimal_xlsx/[Content_Types].xml +9 -0
  110. package/templates/.agent/skills/minimax-xlsx/templates/minimal_xlsx/_rels/.rels +6 -0
  111. package/templates/.agent/skills/minimax-xlsx/templates/minimal_xlsx/xl/_rels/workbook.xml.rels +19 -0
  112. package/templates/.agent/skills/minimax-xlsx/templates/minimal_xlsx/xl/sharedStrings.xml +33 -0
  113. package/templates/.agent/skills/minimax-xlsx/templates/minimal_xlsx/xl/styles.xml +160 -0
  114. package/templates/.agent/skills/minimax-xlsx/templates/minimal_xlsx/xl/workbook.xml +30 -0
  115. package/templates/.agent/skills/minimax-xlsx/templates/minimal_xlsx/xl/worksheets/sheet1.xml +70 -0
  116. package/templates/.agent/skills/pptx-generator/SKILL.md +249 -0
  117. package/templates/.agent/skills/pptx-generator/references/design-system.md +392 -0
  118. package/templates/.agent/skills/pptx-generator/references/editing.md +162 -0
  119. package/templates/.agent/skills/pptx-generator/references/pitfalls.md +112 -0
  120. package/templates/.agent/skills/pptx-generator/references/pptxgenjs.md +420 -0
  121. package/templates/.agent/skills/pptx-generator/references/slide-types.md +413 -0
  122. package/templates/.agent/skills/tutorial-video-expert/SKILL.md +88 -0
  123. package/templates/.agent/skills/ui-ux-pro-max/SKILL.md +170 -585
  124. package/templates/.agent/skills/vision-analysis/SKILL.md +174 -0
  125. package/templates/.agent/workflows/analyze.md +3 -0
  126. package/templates/.agent/workflows/brand-report.md +44 -0
  127. package/templates/.agent/workflows/report.md +49 -0
  128. package/templates/.agent/agents/backend-specialist.md +0 -263
  129. package/templates/.agent/agents/database-architect.md +0 -226
  130. package/templates/.agent/agents/debugger.md +0 -225
  131. package/templates/.agent/agents/devops-engineer.md +0 -242
  132. package/templates/.agent/agents/frontend-specialist.md +0 -527
  133. package/templates/.agent/agents/game-developer.md +0 -162
  134. package/templates/.agent/agents/mobile-developer.md +0 -377
  135. package/templates/.agent/agents/penetration-tester.md +0 -188
  136. package/templates/.agent/agents/security-auditor.md +0 -170
  137. package/templates/.agent/agents/test-engineer.md +0 -158
  138. package/templates/.agent/skills/api-patterns/SKILL.md +0 -81
  139. package/templates/.agent/skills/api-patterns/api-style.md +0 -42
  140. package/templates/.agent/skills/api-patterns/auth.md +0 -24
  141. package/templates/.agent/skills/api-patterns/documentation.md +0 -26
  142. package/templates/.agent/skills/api-patterns/graphql.md +0 -41
  143. package/templates/.agent/skills/api-patterns/rate-limiting.md +0 -31
  144. package/templates/.agent/skills/api-patterns/response.md +0 -37
  145. package/templates/.agent/skills/api-patterns/rest.md +0 -40
  146. package/templates/.agent/skills/api-patterns/scripts/api_validator.py +0 -211
  147. package/templates/.agent/skills/api-patterns/security-testing.md +0 -122
  148. package/templates/.agent/skills/api-patterns/trpc.md +0 -41
  149. package/templates/.agent/skills/api-patterns/versioning.md +0 -22
  150. package/templates/.agent/skills/app-builder/SKILL.md +0 -75
  151. package/templates/.agent/skills/app-builder/agent-coordination.md +0 -71
  152. package/templates/.agent/skills/app-builder/feature-building.md +0 -53
  153. package/templates/.agent/skills/app-builder/project-detection.md +0 -34
  154. package/templates/.agent/skills/app-builder/scaffolding.md +0 -118
  155. package/templates/.agent/skills/app-builder/tech-stack.md +0 -40
  156. package/templates/.agent/skills/app-builder/templates/SKILL.md +0 -39
  157. package/templates/.agent/skills/app-builder/templates/astro-static/TEMPLATE.md +0 -76
  158. package/templates/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md +0 -92
  159. package/templates/.agent/skills/app-builder/templates/cli-tool/TEMPLATE.md +0 -88
  160. package/templates/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md +0 -88
  161. package/templates/.agent/skills/app-builder/templates/express-api/TEMPLATE.md +0 -83
  162. package/templates/.agent/skills/app-builder/templates/flutter-app/TEMPLATE.md +0 -90
  163. package/templates/.agent/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +0 -90
  164. package/templates/.agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +0 -82
  165. package/templates/.agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +0 -100
  166. package/templates/.agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md +0 -106
  167. package/templates/.agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md +0 -101
  168. package/templates/.agent/skills/app-builder/templates/python-fastapi/TEMPLATE.md +0 -83
  169. package/templates/.agent/skills/app-builder/templates/react-native-app/TEMPLATE.md +0 -93
  170. package/templates/.agent/skills/architecture/SKILL.md +0 -55
  171. package/templates/.agent/skills/architecture/context-discovery.md +0 -43
  172. package/templates/.agent/skills/architecture/examples.md +0 -94
  173. package/templates/.agent/skills/architecture/pattern-selection.md +0 -68
  174. package/templates/.agent/skills/architecture/patterns-reference.md +0 -50
  175. package/templates/.agent/skills/architecture/trade-off-analysis.md +0 -77
  176. package/templates/.agent/skills/bash-linux/SKILL.md +0 -199
  177. package/templates/.agent/skills/behavioral-modes/SKILL.md +0 -242
  178. package/templates/.agent/skills/clean-code/SKILL.md +0 -201
  179. package/templates/.agent/skills/code-review-checklist/SKILL.md +0 -109
  180. package/templates/.agent/skills/database-design/SKILL.md +0 -52
  181. package/templates/.agent/skills/database-design/database-selection.md +0 -43
  182. package/templates/.agent/skills/database-design/indexing.md +0 -39
  183. package/templates/.agent/skills/database-design/migrations.md +0 -48
  184. package/templates/.agent/skills/database-design/optimization.md +0 -36
  185. package/templates/.agent/skills/database-design/orm-selection.md +0 -30
  186. package/templates/.agent/skills/database-design/schema-design.md +0 -56
  187. package/templates/.agent/skills/database-design/scripts/schema_validator.py +0 -172
  188. package/templates/.agent/skills/deployment-procedures/SKILL.md +0 -241
  189. package/templates/.agent/skills/docker-expert/SKILL.md +0 -409
  190. package/templates/.agent/skills/game-development/2d-games/SKILL.md +0 -119
  191. package/templates/.agent/skills/game-development/3d-games/SKILL.md +0 -135
  192. package/templates/.agent/skills/game-development/SKILL.md +0 -167
  193. package/templates/.agent/skills/game-development/game-art/SKILL.md +0 -185
  194. package/templates/.agent/skills/game-development/game-audio/SKILL.md +0 -190
  195. package/templates/.agent/skills/game-development/game-design/SKILL.md +0 -129
  196. package/templates/.agent/skills/game-development/mobile-games/SKILL.md +0 -108
  197. package/templates/.agent/skills/game-development/multiplayer/SKILL.md +0 -132
  198. package/templates/.agent/skills/game-development/pc-games/SKILL.md +0 -144
  199. package/templates/.agent/skills/game-development/vr-ar/SKILL.md +0 -123
  200. package/templates/.agent/skills/game-development/web-games/SKILL.md +0 -150
  201. package/templates/.agent/skills/lint-and-validate/SKILL.md +0 -45
  202. package/templates/.agent/skills/lint-and-validate/scripts/lint_runner.py +0 -172
  203. package/templates/.agent/skills/lint-and-validate/scripts/type_coverage.py +0 -173
  204. package/templates/.agent/skills/mcp-builder/SKILL.md +0 -176
  205. package/templates/.agent/skills/nestjs-expert/SKILL.md +0 -552
  206. package/templates/.agent/skills/nextjs-best-practices/SKILL.md +0 -203
  207. package/templates/.agent/skills/nodejs-best-practices/SKILL.md +0 -333
  208. package/templates/.agent/skills/parallel-agents/SKILL.md +0 -175
  209. package/templates/.agent/skills/performance-profiling/SKILL.md +0 -143
  210. package/templates/.agent/skills/performance-profiling/scripts/lighthouse_audit.py +0 -76
  211. package/templates/.agent/skills/powershell-windows/SKILL.md +0 -167
  212. package/templates/.agent/skills/prisma-expert/SKILL.md +0 -355
  213. package/templates/.agent/skills/python-patterns/SKILL.md +0 -441
  214. package/templates/.agent/skills/react-patterns/SKILL.md +0 -198
  215. package/templates/.agent/skills/red-team-tactics/SKILL.md +0 -199
  216. package/templates/.agent/skills/server-management/SKILL.md +0 -161
  217. package/templates/.agent/skills/systematic-debugging/SKILL.md +0 -109
  218. package/templates/.agent/skills/tdd-workflow/SKILL.md +0 -149
  219. package/templates/.agent/skills/testing-patterns/SKILL.md +0 -178
  220. package/templates/.agent/skills/testing-patterns/scripts/test_runner.py +0 -219
  221. package/templates/.agent/skills/typescript-expert/SKILL.md +0 -429
  222. package/templates/.agent/skills/vue-expert/SKILL.md +0 -374
  223. package/templates/.agent/skills/vulnerability-scanner/SKILL.md +0 -276
  224. package/templates/.agent/skills/vulnerability-scanner/checklists.md +0 -121
  225. package/templates/.agent/skills/vulnerability-scanner/scripts/security_scan.py +0 -458
  226. package/templates/.agent/skills/webapp-testing/SKILL.md +0 -187
  227. package/templates/.agent/skills/webapp-testing/scripts/playwright_runner.py +0 -173
  228. package/templates/.agent/workflows/debug.md +0 -103
  229. package/templates/.agent/workflows/deploy.md +0 -176
  230. package/templates/.agent/workflows/enhance.md +0 -63
  231. package/templates/.agent/workflows/test.md +0 -144
@@ -0,0 +1,917 @@
1
+ // ============================================================================
2
+ // ImageSamples.cs — Comprehensive OpenXML image handling reference
3
+ // ============================================================================
4
+ // EMU (English Metric Unit) is the universal measurement in DrawingML:
5
+ // 1 inch = 914400 EMU
6
+ // 1 cm = 360000 EMU
7
+ // 1 px@96dpi = 9525 EMU (914400 / 96 = 9525)
8
+ //
9
+ // Image architecture in OpenXML:
10
+ // Paragraph → Run → Drawing → DW.Inline (or DW.Anchor)
11
+ // → A.Graphic → A.GraphicData → PIC.Picture
12
+ // → PIC.BlipFill → A.Blip (references the image part via r:embed)
13
+ // → PIC.ShapeProperties → A.Transform2D → A.Extents (cx, cy)
14
+ //
15
+ // CRITICAL RULES:
16
+ // 1. Extent.Cx/Cy on DW.Inline/DW.Anchor MUST match A.Extents.Cx/Cy
17
+ // on PIC.ShapeProperties. Mismatch causes rendering issues.
18
+ // 2. Each Drawing element needs a unique DocProperties.Id within the document.
19
+ // 3. ImagePart must be added to the PART that references it:
20
+ // - MainDocumentPart for images in body
21
+ // - HeaderPart for images in headers
22
+ // - FooterPart for images in footers
23
+ // 4. Blip.Embed contains the relationship ID (rId) linking to the ImagePart.
24
+ // ============================================================================
25
+
26
+ using DocumentFormat.OpenXml;
27
+ using DocumentFormat.OpenXml.Packaging;
28
+ using DocumentFormat.OpenXml.Wordprocessing;
29
+
30
+ using A = DocumentFormat.OpenXml.Drawing;
31
+ using DW = DocumentFormat.OpenXml.Drawing.Wordprocessing;
32
+ using PIC = DocumentFormat.OpenXml.Drawing.Pictures;
33
+
34
+ namespace MiniMaxAIDocx.Core.Samples;
35
+
36
+ /// <summary>
37
+ /// Reference implementations for every common image operation in OpenXML.
38
+ /// All methods produce valid, Word-renderable markup.
39
+ /// </summary>
40
+ public static class ImageSamples
41
+ {
42
+ // ── Constants ──────────────────────────────────────────────────────
43
+ private const long EmuPerInch = 914400L;
44
+ private const long EmuPerCm = 360000L;
45
+ private const long EmuPerPixel96Dpi = 9525L; // 914400 / 96
46
+
47
+ // GraphicData URI that tells Word "this is a picture"
48
+ private const string PicGraphicDataUri = "http://schemas.openxmlformats.org/drawingml/2006/picture";
49
+
50
+ // ── 1. Inline Image (most common) ──────────────────────────────────
51
+
52
+ /// <summary>
53
+ /// Inserts an inline image into the body. Inline images flow with text
54
+ /// and do not float. This is the most common image insertion pattern.
55
+ /// </summary>
56
+ /// <param name="mainPart">The MainDocumentPart to add the image relationship to.</param>
57
+ /// <param name="body">The Body element to append the paragraph to.</param>
58
+ /// <param name="imagePath">Filesystem path to the image file (png, jpg, etc.).</param>
59
+ /// <param name="widthPx">Desired display width in pixels (at 96 dpi).</param>
60
+ /// <param name="heightPx">Desired display height in pixels (at 96 dpi).</param>
61
+ public static void InsertInlineImage(
62
+ MainDocumentPart mainPart, Body body,
63
+ string imagePath, int widthPx, int heightPx)
64
+ {
65
+ // Step 1: Add the image file as a part. The ImagePartType must match
66
+ // the actual file format. AddImagePart returns the ImagePart; we then
67
+ // feed data into it.
68
+ var imageType = GetImagePartType(imagePath);
69
+ ImagePart imagePart = mainPart.AddImagePart(imageType);
70
+
71
+ using (FileStream stream = new FileStream(imagePath, FileMode.Open))
72
+ {
73
+ imagePart.FeedData(stream);
74
+ }
75
+
76
+ // Step 2: Get the relationship ID that links the Blip to this ImagePart.
77
+ string relId = mainPart.GetIdOfPart(imagePart);
78
+
79
+ // Step 3: Convert pixel dimensions to EMU.
80
+ // Formula: pixels * 9525 = EMU (at 96 dpi, which is Word's assumption)
81
+ long cx = widthPx * EmuPerPixel96Dpi;
82
+ long cy = heightPx * EmuPerPixel96Dpi;
83
+
84
+ // Step 4: Build the Drawing element using the reusable helper.
85
+ // docPropId must be unique across the entire document.
86
+ Drawing drawing = BuildDrawingElement(
87
+ relId, cx, cy,
88
+ docPropId: 1U,
89
+ name: "Image1",
90
+ description: null);
91
+
92
+ // Step 5: Wrap in Paragraph → Run → Drawing
93
+ Paragraph para = new Paragraph(
94
+ new Run(drawing));
95
+
96
+ body.AppendChild(para);
97
+ }
98
+
99
+ // ── 2. Floating Image (Anchor) ─────────────────────────────────────
100
+
101
+ /// <summary>
102
+ /// Inserts a floating image with absolute positioning using DW.Anchor.
103
+ /// Floating images are positioned relative to a reference point (page,
104
+ /// column, paragraph, etc.) and text wraps around them.
105
+ /// </summary>
106
+ public static void InsertFloatingImage(
107
+ MainDocumentPart mainPart, Body body, string imagePath)
108
+ {
109
+ ImagePart imagePart = mainPart.AddImagePart(GetImagePartType(imagePath));
110
+ using (FileStream stream = new FileStream(imagePath, FileMode.Open))
111
+ {
112
+ imagePart.FeedData(stream);
113
+ }
114
+ string relId = mainPart.GetIdOfPart(imagePart);
115
+
116
+ long cx = (long)(3.0 * EmuPerInch); // 3 inches wide
117
+ long cy = (long)(2.0 * EmuPerInch); // 2 inches tall
118
+
119
+ // DW.Anchor is used instead of DW.Inline for floating images.
120
+ // Key differences from Inline:
121
+ // - Has positioning (SimplePos, HorizontalPosition, VerticalPosition)
122
+ // - Has wrapping mode (WrapSquare, WrapTight, WrapNone, etc.)
123
+ // - Has BehindDoc and LayoutInCell flags
124
+ DW.Anchor anchor = new DW.Anchor(
125
+ // SimplePosition: when SimplePos=true, uses SimplePosition x/y directly.
126
+ // Normally false; we use HorizontalPosition/VerticalPosition instead.
127
+ new DW.SimplePosition { X = 0L, Y = 0L },
128
+
129
+ // HorizontalPosition: where the image sits horizontally.
130
+ // RelativeFrom can be: Column, Page, Margin, Character, LeftMargin, etc.
131
+ new DW.HorizontalPosition(
132
+ new DW.PositionOffset("914400") // 1 inch from reference
133
+ )
134
+ { RelativeFrom = DW.HorizontalRelativePositionValues.Column },
135
+
136
+ // VerticalPosition: where the image sits vertically.
137
+ new DW.VerticalPosition(
138
+ new DW.PositionOffset("457200") // 0.5 inch from reference
139
+ )
140
+ { RelativeFrom = DW.VerticalRelativePositionValues.Paragraph },
141
+
142
+ // Extent: overall size of the drawing object
143
+ new DW.Extent { Cx = cx, Cy = cy },
144
+
145
+ // EffectExtent: extra space for shadows, glow, etc. (0 if none)
146
+ new DW.EffectExtent
147
+ {
148
+ LeftEdge = 0L,
149
+ TopEdge = 0L,
150
+ RightEdge = 0L,
151
+ BottomEdge = 0L
152
+ },
153
+
154
+ // WrapSquare: text wraps in a square around the image bounding box.
155
+ new DW.WrapSquare { WrapText = DW.WrapTextValues.BothSides },
156
+
157
+ // DocProperties: unique ID + name for the drawing object
158
+ new DW.DocProperties { Id = 2U, Name = "FloatingImage1" },
159
+
160
+ // Non-visual graphic frame properties (required but usually empty)
161
+ new DW.NonVisualGraphicFrameDrawingProperties(
162
+ new A.GraphicFrameLocks { NoChangeAspect = true }),
163
+
164
+ // The actual graphic content
165
+ new A.Graphic(
166
+ new A.GraphicData(
167
+ new PIC.Picture(
168
+ new PIC.NonVisualPictureProperties(
169
+ new PIC.NonVisualDrawingProperties
170
+ {
171
+ Id = 0U,
172
+ Name = "FloatingImage1.png"
173
+ },
174
+ new PIC.NonVisualPictureDrawingProperties()),
175
+ new PIC.BlipFill(
176
+ new A.Blip { Embed = relId },
177
+ new A.Stretch(new A.FillRectangle())),
178
+ new PIC.ShapeProperties(
179
+ new A.Transform2D(
180
+ new A.Offset { X = 0L, Y = 0L },
181
+ // CRITICAL: These cx/cy MUST match the Extent above
182
+ new A.Extents { Cx = cx, Cy = cy }),
183
+ new A.PresetGeometry(
184
+ new A.AdjustValueList())
185
+ { Preset = A.ShapeTypeValues.Rectangle }))
186
+ )
187
+ { Uri = PicGraphicDataUri })
188
+ )
189
+ {
190
+ // Anchor attributes
191
+ DistanceFromTop = 0U,
192
+ DistanceFromBottom = 0U,
193
+ DistanceFromLeft = 114300U, // ~0.125 inch gap between text and image
194
+ DistanceFromRight = 114300U,
195
+ SimplePos = false,
196
+ RelativeHeight = 251658240U, // z-order; higher = in front
197
+ BehindDoc = false, // true = behind text (like a watermark)
198
+ Locked = false,
199
+ LayoutInCell = true,
200
+ AllowOverlap = true
201
+ };
202
+
203
+ Paragraph para = new Paragraph(new Run(new Drawing(anchor)));
204
+ body.AppendChild(para);
205
+ }
206
+
207
+ // ── 3. Image with Various Text Wrapping ────────────────────────────
208
+
209
+ /// <summary>
210
+ /// Demonstrates the four main text wrapping modes for floating images.
211
+ /// Each wrapping mode controls how body text flows around the image.
212
+ /// </summary>
213
+ public static void InsertImageWithTextWrapping(
214
+ MainDocumentPart mainPart, Body body, string imagePath)
215
+ {
216
+ // All wrapping modes require DW.Anchor (not DW.Inline).
217
+ // The wrapping element is a direct child of the Anchor element.
218
+
219
+ ImagePart imagePart = mainPart.AddImagePart(GetImagePartType(imagePath));
220
+ using (FileStream stream = new FileStream(imagePath, FileMode.Open))
221
+ {
222
+ imagePart.FeedData(stream);
223
+ }
224
+ string relId = mainPart.GetIdOfPart(imagePart);
225
+
226
+ long cx = (long)(2.5 * EmuPerInch);
227
+ long cy = (long)(2.0 * EmuPerInch);
228
+
229
+ // ── WrapSquare ──
230
+ // Text wraps in a rectangular bounding box around the image.
231
+ // WrapText controls which sides text appears on.
232
+ var wrapSquare = new DW.WrapSquare
233
+ {
234
+ WrapText = DW.WrapTextValues.BothSides
235
+ // Other options: Left, Right, Largest
236
+ };
237
+
238
+ // ── WrapTight ──
239
+ // Text wraps tightly around the actual contour of the image.
240
+ // Uses a WrapPolygon to define the outline; Word can auto-generate this.
241
+ // The coordinates are in EMU relative to the image's top-left.
242
+ var wrapTight = new DW.WrapTight(
243
+ new DW.WrapPolygon(
244
+ new DW.StartPoint { X = 0L, Y = 0L },
245
+ new DW.LineTo { X = 0L, Y = 21600L },
246
+ new DW.LineTo { X = 21600L, Y = 21600L },
247
+ new DW.LineTo { X = 21600L, Y = 0L },
248
+ new DW.LineTo { X = 0L, Y = 0L }
249
+ )
250
+ { Edited = false }
251
+ )
252
+ {
253
+ WrapText = DW.WrapTextValues.BothSides
254
+ };
255
+
256
+ // ── WrapTopAndBottom ──
257
+ // No text appears beside the image. Text only above and below.
258
+ // This effectively makes the image act as a block-level element
259
+ // but still floating (not inline).
260
+ var wrapTopAndBottom = new DW.WrapTopBottom
261
+ {
262
+ DistanceFromTop = 0U,
263
+ DistanceFromBottom = 0U
264
+ };
265
+
266
+ // ── WrapNone ──
267
+ // No text wrapping at all. Image floats over or behind text.
268
+ // Combined with BehindDoc=true, this creates a watermark effect.
269
+ var wrapNone = new DW.WrapNone();
270
+
271
+ // Example: build anchor with WrapSquare (swap in any wrapping element above)
272
+ DW.Anchor anchor = BuildAnchorElement(
273
+ relId, cx, cy,
274
+ docPropId: 3U,
275
+ name: "WrappedImage",
276
+ wrapElement: wrapSquare,
277
+ behindDoc: false);
278
+
279
+ body.AppendChild(new Paragraph(new Run(new Drawing(anchor))));
280
+ }
281
+
282
+ // ── 4. Image with Border ───────────────────────────────────────────
283
+
284
+ /// <summary>
285
+ /// Inserts an image with a visible outline/border. The border is applied
286
+ /// via A.Outline on the PIC.ShapeProperties element.
287
+ /// </summary>
288
+ public static void InsertImageWithBorder(
289
+ MainDocumentPart mainPart, Body body, string imagePath)
290
+ {
291
+ ImagePart imagePart = mainPart.AddImagePart(GetImagePartType(imagePath));
292
+ using (FileStream stream = new FileStream(imagePath, FileMode.Open))
293
+ {
294
+ imagePart.FeedData(stream);
295
+ }
296
+ string relId = mainPart.GetIdOfPart(imagePart);
297
+
298
+ long cx = (long)(3.0 * EmuPerInch);
299
+ long cy = (long)(2.0 * EmuPerInch);
300
+
301
+ // Build PIC.ShapeProperties with an Outline element for the border.
302
+ // Outline width is in EMU. 1pt = 12700 EMU.
303
+ var shapeProperties = new PIC.ShapeProperties(
304
+ new A.Transform2D(
305
+ new A.Offset { X = 0L, Y = 0L },
306
+ new A.Extents { Cx = cx, Cy = cy }),
307
+ new A.PresetGeometry(
308
+ new A.AdjustValueList())
309
+ { Preset = A.ShapeTypeValues.Rectangle },
310
+ // The Outline element defines the border
311
+ new A.Outline(
312
+ // SolidFill sets the border color
313
+ new A.SolidFill(
314
+ new A.RgbColorModelHex { Val = "2F5496" }), // Dark blue
315
+ // PresetDash sets the line style (solid, dash, dot, etc.)
316
+ new A.PresetDash { Val = A.PresetLineDashValues.Solid }
317
+ )
318
+ {
319
+ Width = 25400, // 2pt border (12700 EMU per pt)
320
+ CompoundLineType = A.CompoundLineValues.Single
321
+ }
322
+ );
323
+
324
+ var picture = new PIC.Picture(
325
+ new PIC.NonVisualPictureProperties(
326
+ new PIC.NonVisualDrawingProperties { Id = 0U, Name = "BorderedImage.png" },
327
+ new PIC.NonVisualPictureDrawingProperties()),
328
+ new PIC.BlipFill(
329
+ new A.Blip { Embed = relId },
330
+ new A.Stretch(new A.FillRectangle())),
331
+ shapeProperties);
332
+
333
+ var drawing = new Drawing(
334
+ new DW.Inline(
335
+ new DW.Extent { Cx = cx, Cy = cy },
336
+ new DW.EffectExtent
337
+ {
338
+ // Must account for border width in effect extent so it is not clipped
339
+ LeftEdge = 25400L,
340
+ TopEdge = 25400L,
341
+ RightEdge = 25400L,
342
+ BottomEdge = 25400L
343
+ },
344
+ new DW.DocProperties { Id = 4U, Name = "BorderedImage" },
345
+ new DW.NonVisualGraphicFrameDrawingProperties(
346
+ new A.GraphicFrameLocks { NoChangeAspect = true }),
347
+ new A.Graphic(
348
+ new A.GraphicData(picture)
349
+ { Uri = PicGraphicDataUri })
350
+ )
351
+ {
352
+ DistanceFromTop = 0U,
353
+ DistanceFromBottom = 0U,
354
+ DistanceFromLeft = 0U,
355
+ DistanceFromRight = 0U
356
+ });
357
+
358
+ body.AppendChild(new Paragraph(new Run(drawing)));
359
+ }
360
+
361
+ // ── 5. Image with Alt Text ─────────────────────────────────────────
362
+
363
+ /// <summary>
364
+ /// Inserts an image with alt text for accessibility. The alt text is set
365
+ /// on the DocProperties.Description attribute. Screen readers use this.
366
+ /// Word also shows it in the "Alt Text" pane.
367
+ /// </summary>
368
+ public static void InsertImageWithAltText(
369
+ MainDocumentPart mainPart, Body body, string imagePath)
370
+ {
371
+ ImagePart imagePart = mainPart.AddImagePart(GetImagePartType(imagePath));
372
+ using (FileStream stream = new FileStream(imagePath, FileMode.Open))
373
+ {
374
+ imagePart.FeedData(stream);
375
+ }
376
+ string relId = mainPart.GetIdOfPart(imagePart);
377
+
378
+ long cx = (long)(3.0 * EmuPerInch);
379
+ long cy = (long)(2.0 * EmuPerInch);
380
+
381
+ // DocProperties.Description is the standard alt text field.
382
+ // DocProperties.Title is an optional short title shown in some UIs.
383
+ Drawing drawing = BuildDrawingElement(
384
+ relId, cx, cy,
385
+ docPropId: 5U,
386
+ name: "AccessibleImage",
387
+ description: "A chart showing quarterly revenue growth from Q1 to Q4 2025");
388
+
389
+ body.AppendChild(new Paragraph(new Run(drawing)));
390
+ }
391
+
392
+ // ── 6. Image in Header ─────────────────────────────────────────────
393
+
394
+ /// <summary>
395
+ /// Inserts an image into a header part. The image relationship MUST be
396
+ /// added to the HeaderPart, NOT the MainDocumentPart. If you add it
397
+ /// to MainDocumentPart, Word will show a broken image in the header
398
+ /// because relationship IDs are scoped to their containing part.
399
+ /// </summary>
400
+ public static void InsertImageInHeader(HeaderPart headerPart, string imagePath)
401
+ {
402
+ // CRITICAL: AddImagePart to headerPart, not mainDocumentPart!
403
+ // Each OpenXML part has its own relationship namespace.
404
+ // An rId in the header must point to a relationship in the header's .rels file.
405
+ ImagePart imagePart = headerPart.AddImagePart(GetImagePartType(imagePath));
406
+ using (FileStream stream = new FileStream(imagePath, FileMode.Open))
407
+ {
408
+ imagePart.FeedData(stream);
409
+ }
410
+
411
+ // GetIdOfPart must also be called on headerPart
412
+ string relId = headerPart.GetIdOfPart(imagePart);
413
+
414
+ long cx = (long)(1.5 * EmuPerInch); // Company logo, typically small
415
+ long cy = (long)(0.5 * EmuPerInch);
416
+
417
+ Drawing drawing = BuildDrawingElement(
418
+ relId, cx, cy,
419
+ docPropId: 6U,
420
+ name: "HeaderLogo",
421
+ description: "Company logo");
422
+
423
+ // Headers use the Header element with Paragraph children (same as Body)
424
+ Header header = headerPart.Header;
425
+ Paragraph para = new Paragraph(
426
+ new ParagraphProperties(
427
+ new Justification { Val = JustificationValues.Center }),
428
+ new Run(drawing));
429
+
430
+ header.AppendChild(para);
431
+ }
432
+
433
+ // ── 7. Image in Table Cell ─────────────────────────────────────────
434
+
435
+ /// <summary>
436
+ /// Inserts an image into a table cell, sized to fit. Table cells constrain
437
+ /// content width, so we calculate appropriate dimensions to avoid overflow.
438
+ /// The image part is still added to MainDocumentPart (the cell is in the body).
439
+ /// </summary>
440
+ /// <param name="mainPart">MainDocumentPart (owns the relationship).</param>
441
+ /// <param name="cell">The TableCell to insert the image into.</param>
442
+ /// <param name="imagePath">Path to the image file.</param>
443
+ public static void InsertImageInTableCell(
444
+ MainDocumentPart mainPart, TableCell cell, string imagePath)
445
+ {
446
+ ImagePart imagePart = mainPart.AddImagePart(GetImagePartType(imagePath));
447
+ using (FileStream stream = new FileStream(imagePath, FileMode.Open))
448
+ {
449
+ imagePart.FeedData(stream);
450
+ }
451
+ string relId = mainPart.GetIdOfPart(imagePart);
452
+
453
+ // Determine cell width from TableCellWidth if available.
454
+ // TableCellWidth.Width is in DXA (twentieths of a point).
455
+ // If not set, use a reasonable default (e.g., 2 inches).
456
+ long maxWidthEmu = (long)(2.0 * EmuPerInch); // default
457
+
458
+ TableCellProperties? tcPr = cell.GetFirstChild<TableCellProperties>();
459
+ TableCellWidth? tcWidth = tcPr?.GetFirstChild<TableCellWidth>();
460
+ if (tcWidth?.Width is not null && tcWidth.Type?.Value == TableWidthUnitValues.Dxa)
461
+ {
462
+ // Convert DXA to EMU: 1 DXA = 1/20 pt = 1/1440 inch = 914400/1440 EMU
463
+ int dxa = int.Parse(tcWidth.Width);
464
+ maxWidthEmu = (long)(dxa * (EmuPerInch / 1440.0));
465
+ }
466
+
467
+ // Calculate image dimensions to fit within the cell width
468
+ (long cx, long cy) = CalculateImageDimensions(imagePath, maxWidthEmu / (double)EmuPerInch);
469
+
470
+ Drawing drawing = BuildDrawingElement(
471
+ relId, cx, cy,
472
+ docPropId: 7U,
473
+ name: "CellImage",
474
+ description: null);
475
+
476
+ // A TableCell MUST contain at least one Paragraph.
477
+ // We add the image inside that paragraph.
478
+ Paragraph para = cell.GetFirstChild<Paragraph>() ?? cell.AppendChild(new Paragraph());
479
+ para.AppendChild(new Run(drawing));
480
+ }
481
+
482
+ // ── 8. Replace Existing Image ──────────────────────────────────────
483
+
484
+ /// <summary>
485
+ /// Replaces an existing image by updating the ImagePart data behind a
486
+ /// known relationship ID. The Blip.Embed attribute (rId) stays the same;
487
+ /// only the binary content changes. This avoids needing to rebuild the
488
+ /// entire Drawing XML tree.
489
+ /// </summary>
490
+ /// <param name="mainPart">The MainDocumentPart containing the image relationship.</param>
491
+ /// <param name="oldRelId">The existing relationship ID (e.g., "rId5") of the image to replace.</param>
492
+ /// <param name="newImagePath">Path to the replacement image file.</param>
493
+ public static void ReplaceExistingImage(
494
+ MainDocumentPart mainPart, string oldRelId, string newImagePath)
495
+ {
496
+ // Look up the existing ImagePart by its relationship ID
497
+ OpenXmlPart part = mainPart.GetPartById(oldRelId);
498
+ if (part is not ImagePart imagePart)
499
+ {
500
+ throw new InvalidOperationException(
501
+ $"Relationship {oldRelId} does not point to an ImagePart.");
502
+ }
503
+
504
+ // Feed new image data into the existing part.
505
+ // This replaces the binary content while keeping the same rId.
506
+ using (FileStream stream = new FileStream(newImagePath, FileMode.Open))
507
+ {
508
+ imagePart.FeedData(stream);
509
+ }
510
+
511
+ // NOTE: If the new image has different dimensions, you should also
512
+ // update the Extent.Cx/Cy and A.Extents.Cx/Cy in the Drawing element.
513
+ // Find all Blip elements referencing this relId:
514
+ //
515
+ // var blips = mainPart.Document.Descendants<A.Blip>()
516
+ // .Where(b => b.Embed == oldRelId);
517
+ // foreach (var blip in blips)
518
+ // {
519
+ // // Navigate up to find the Extent and A.Extents to update dimensions
520
+ // }
521
+ }
522
+
523
+ // ── 9. SVG with PNG Fallback ───────────────────────────────────────
524
+
525
+ /// <summary>
526
+ /// Inserts an SVG image with a PNG fallback for compatibility.
527
+ /// Word 2019+ supports SVG natively; older versions show the PNG.
528
+ /// The SVG is referenced via an extension element (SvgBlip) inside the Blip,
529
+ /// while the Blip.Embed itself points to the PNG fallback.
530
+ /// </summary>
531
+ public static void InsertSvgWithPngFallback(
532
+ MainDocumentPart mainPart, Body body,
533
+ string svgPath, string pngFallbackPath)
534
+ {
535
+ // Add PNG fallback as the primary image part
536
+ ImagePart pngPart = mainPart.AddImagePart(ImagePartType.Png);
537
+ using (FileStream pngStream = new FileStream(pngFallbackPath, FileMode.Open))
538
+ {
539
+ pngPart.FeedData(pngStream);
540
+ }
541
+ string pngRelId = mainPart.GetIdOfPart(pngPart);
542
+
543
+ // Add SVG as a separate image part
544
+ ImagePart svgPart = mainPart.AddImagePart(ImagePartType.Svg);
545
+ using (FileStream svgStream = new FileStream(svgPath, FileMode.Open))
546
+ {
547
+ svgPart.FeedData(svgStream);
548
+ }
549
+ string svgRelId = mainPart.GetIdOfPart(svgPart);
550
+
551
+ long cx = (long)(3.0 * EmuPerInch);
552
+ long cy = (long)(3.0 * EmuPerInch);
553
+
554
+ // The Blip.Embed points to the PNG fallback.
555
+ // The SVG is added as an extension element (asvg:svgBlip) inside the Blip.
556
+ // Namespace: http://schemas.microsoft.com/office/drawing/2016/SVG/main
557
+ var blip = new A.Blip { Embed = pngRelId };
558
+
559
+ // Add SVG extension to the Blip using BlipExtensionList
560
+ var svgExtension = new A.BlipExtensionList(
561
+ new A.BlipExtension(
562
+ // The SVG blip element references the SVG image part
563
+ new OpenXmlUnknownElement(
564
+ "asvg", "svgBlip",
565
+ "http://schemas.microsoft.com/office/drawing/2016/SVG/main")
566
+ // NOTE: In production, set the r:embed attribute on this element
567
+ // to svgRelId. OpenXmlUnknownElement requires manual attribute setting.
568
+ )
569
+ { Uri = "{96DAC541-7B7A-43D3-8B79-37D633B846F1}" }
570
+ );
571
+ blip.Append(svgExtension);
572
+
573
+ var picture = new PIC.Picture(
574
+ new PIC.NonVisualPictureProperties(
575
+ new PIC.NonVisualDrawingProperties { Id = 0U, Name = "SvgImage.svg" },
576
+ new PIC.NonVisualPictureDrawingProperties()),
577
+ new PIC.BlipFill(
578
+ blip,
579
+ new A.Stretch(new A.FillRectangle())),
580
+ new PIC.ShapeProperties(
581
+ new A.Transform2D(
582
+ new A.Offset { X = 0L, Y = 0L },
583
+ new A.Extents { Cx = cx, Cy = cy }),
584
+ new A.PresetGeometry(new A.AdjustValueList())
585
+ { Preset = A.ShapeTypeValues.Rectangle }));
586
+
587
+ var drawing = new Drawing(
588
+ new DW.Inline(
589
+ new DW.Extent { Cx = cx, Cy = cy },
590
+ new DW.EffectExtent
591
+ {
592
+ LeftEdge = 0L, TopEdge = 0L,
593
+ RightEdge = 0L, BottomEdge = 0L
594
+ },
595
+ new DW.DocProperties { Id = 9U, Name = "SvgImage" },
596
+ new DW.NonVisualGraphicFrameDrawingProperties(
597
+ new A.GraphicFrameLocks { NoChangeAspect = true }),
598
+ new A.Graphic(
599
+ new A.GraphicData(picture)
600
+ { Uri = PicGraphicDataUri })
601
+ )
602
+ {
603
+ DistanceFromTop = 0U,
604
+ DistanceFromBottom = 0U,
605
+ DistanceFromLeft = 0U,
606
+ DistanceFromRight = 0U
607
+ });
608
+
609
+ body.AppendChild(new Paragraph(new Run(drawing)));
610
+ }
611
+
612
+ // ── 10. Calculate Image Dimensions ─────────────────────────────────
613
+
614
+ /// <summary>
615
+ /// Reads the actual pixel dimensions of an image file (PNG or JPEG) and
616
+ /// calculates EMU values that fit within a maximum width while maintaining
617
+ /// the original aspect ratio. Uses raw byte reading to avoid a dependency
618
+ /// on System.Drawing (which is Windows-only on modern .NET).
619
+ /// </summary>
620
+ /// <param name="imagePath">Path to a PNG or JPEG image file.</param>
621
+ /// <param name="maxWidthInches">Maximum allowed width in inches.</param>
622
+ /// <returns>Tuple of (cx, cy) in EMU, scaled to fit maxWidthInches.</returns>
623
+ /// <remarks>
624
+ /// For production use, consider SkiaSharp or SixLabors.ImageSharp for
625
+ /// cross-platform image metadata reading with broader format support.
626
+ /// This implementation handles PNG and JPEG only.
627
+ /// </remarks>
628
+ public static (long cx, long cy) CalculateImageDimensions(
629
+ string imagePath, double maxWidthInches)
630
+ {
631
+ // Read pixel dimensions from the image file header.
632
+ // We parse PNG IHDR or JPEG SOF0 markers directly to avoid
633
+ // pulling in System.Drawing.Common (Windows-only on .NET 6+).
634
+ (int widthPx, int heightPx, double dpiX, double dpiY) = ReadImageMetadata(imagePath);
635
+
636
+ // Calculate actual size in inches based on pixel count and DPI
637
+ double widthInches = widthPx / dpiX;
638
+ double heightInches = heightPx / dpiY;
639
+
640
+ // Scale down if wider than maxWidthInches, preserving aspect ratio
641
+ if (widthInches > maxWidthInches)
642
+ {
643
+ double scale = maxWidthInches / widthInches;
644
+ widthInches = maxWidthInches;
645
+ heightInches *= scale;
646
+ }
647
+
648
+ long cx = (long)(widthInches * EmuPerInch);
649
+ long cy = (long)(heightInches * EmuPerInch);
650
+
651
+ return (cx, cy);
652
+ }
653
+
654
+ /// <summary>
655
+ /// Reads width, height, and DPI from a PNG or JPEG file header.
656
+ /// Returns 96 DPI as default if DPI metadata is not found.
657
+ /// </summary>
658
+ private static (int widthPx, int heightPx, double dpiX, double dpiY) ReadImageMetadata(
659
+ string imagePath)
660
+ {
661
+ const double DefaultDpi = 96.0;
662
+ byte[] header = new byte[32];
663
+
664
+ using var fs = new FileStream(imagePath, FileMode.Open, FileAccess.Read);
665
+ int bytesRead = fs.Read(header, 0, header.Length);
666
+
667
+ // PNG: starts with 0x89 0x50 0x4E 0x47 (‰PNG)
668
+ // IHDR chunk is always first; width and height are at bytes 16-23 (big-endian)
669
+ if (bytesRead >= 24 &&
670
+ header[0] == 0x89 && header[1] == 0x50 &&
671
+ header[2] == 0x4E && header[3] == 0x47)
672
+ {
673
+ int width = (header[16] << 24) | (header[17] << 16) |
674
+ (header[18] << 8) | header[19];
675
+ int height = (header[20] << 24) | (header[21] << 16) |
676
+ (header[22] << 8) | header[23];
677
+ // PNG DPI is in the pHYs chunk (not in IHDR); use default for simplicity
678
+ return (width, height, DefaultDpi, DefaultDpi);
679
+ }
680
+
681
+ // JPEG: starts with 0xFF 0xD8
682
+ // Scan for SOF0 (0xFF 0xC0) marker to find dimensions
683
+ if (bytesRead >= 2 && header[0] == 0xFF && header[1] == 0xD8)
684
+ {
685
+ fs.Position = 2;
686
+ while (fs.Position < fs.Length - 1)
687
+ {
688
+ int b = fs.ReadByte();
689
+ if (b != 0xFF) continue;
690
+
691
+ int marker = fs.ReadByte();
692
+ if (marker == -1) break;
693
+
694
+ // SOF0 (0xC0) or SOF2 (0xC2, progressive)
695
+ if (marker == 0xC0 || marker == 0xC2)
696
+ {
697
+ byte[] sof = new byte[7];
698
+ if (fs.Read(sof, 0, 7) == 7)
699
+ {
700
+ // SOF structure: length(2) + precision(1) + height(2) + width(2)
701
+ int height = (sof[3] << 8) | sof[4];
702
+ int width = (sof[5] << 8) | sof[6];
703
+ return (width, height, DefaultDpi, DefaultDpi);
704
+ }
705
+ break;
706
+ }
707
+
708
+ // Skip other markers: read 2-byte length and advance
709
+ if (marker is not (0xD0 or 0xD1 or 0xD2 or 0xD3 or 0xD4 or
710
+ 0xD5 or 0xD6 or 0xD7 or 0xD8 or 0xD9 or 0x01))
711
+ {
712
+ byte[] lenBytes = new byte[2];
713
+ if (fs.Read(lenBytes, 0, 2) < 2) break;
714
+ int len = (lenBytes[0] << 8) | lenBytes[1];
715
+ if (len < 2) break;
716
+ fs.Position += len - 2;
717
+ }
718
+ }
719
+ }
720
+
721
+ // Fallback: cannot determine dimensions; return a reasonable default
722
+ // Caller should handle this gracefully.
723
+ return (300, 200, DefaultDpi, DefaultDpi);
724
+ }
725
+
726
+ // ── 11. Reusable Drawing Builder (Inline) ──────────────────────────
727
+
728
+ /// <summary>
729
+ /// Builds a complete Drawing element for an inline image. This is the
730
+ /// reusable core that most insertion methods delegate to.
731
+ /// </summary>
732
+ /// <param name="relId">Relationship ID pointing to the ImagePart (e.g., "rId4").</param>
733
+ /// <param name="cx">Image width in EMU. Must be positive.</param>
734
+ /// <param name="cy">Image height in EMU. Must be positive.</param>
735
+ /// <param name="docPropId">Unique ID for DocProperties within the document.
736
+ /// Each Drawing in a document must have a distinct DocProperties.Id.</param>
737
+ /// <param name="name">Name for DocProperties (shows in Word selection pane).</param>
738
+ /// <param name="description">Alt text for accessibility. Null if not needed.</param>
739
+ /// <returns>A fully constructed Drawing element ready to append to a Run.</returns>
740
+ public static Drawing BuildDrawingElement(
741
+ string relId, long cx, long cy,
742
+ uint docPropId, string name, string? description)
743
+ {
744
+ // ── Complete element hierarchy ──
745
+ // Drawing
746
+ // └─ DW.Inline
747
+ // ├─ DW.Extent (cx, cy) ← bounding box size
748
+ // ├─ DW.EffectExtent ← extra space for effects
749
+ // ├─ DW.DocProperties (id, name, descr) ← identity + alt text
750
+ // ├─ DW.NonVisualGraphicFrameDrawingProperties
751
+ // │ └─ A.GraphicFrameLocks ← lock aspect ratio
752
+ // └─ A.Graphic
753
+ // └─ A.GraphicData (uri = picture namespace)
754
+ // └─ PIC.Picture
755
+ // ├─ PIC.NonVisualPictureProperties
756
+ // │ ├─ PIC.NonVisualDrawingProperties
757
+ // │ └─ PIC.NonVisualPictureDrawingProperties
758
+ // ├─ PIC.BlipFill
759
+ // │ ├─ A.Blip (embed = relId)
760
+ // │ └─ A.Stretch → A.FillRectangle
761
+ // └─ PIC.ShapeProperties
762
+ // ├─ A.Transform2D
763
+ // │ ├─ A.Offset (0, 0)
764
+ // │ └─ A.Extents (cx, cy) ← MUST match DW.Extent!
765
+ // └─ A.PresetGeometry (rect)
766
+
767
+ var docProps = new DW.DocProperties
768
+ {
769
+ Id = docPropId,
770
+ Name = name
771
+ };
772
+ if (description is not null)
773
+ {
774
+ docProps.Description = description;
775
+ }
776
+
777
+ var picture = new PIC.Picture(
778
+ new PIC.NonVisualPictureProperties(
779
+ new PIC.NonVisualDrawingProperties
780
+ {
781
+ Id = 0U,
782
+ Name = name
783
+ },
784
+ new PIC.NonVisualPictureDrawingProperties()),
785
+ new PIC.BlipFill(
786
+ new A.Blip
787
+ {
788
+ Embed = relId,
789
+ // CompressionState controls image quality vs file size.
790
+ // Print = high quality, Screen = medium, Email = low, None = original
791
+ CompressionState = A.BlipCompressionValues.Print
792
+ },
793
+ new A.Stretch(new A.FillRectangle())),
794
+ new PIC.ShapeProperties(
795
+ new A.Transform2D(
796
+ new A.Offset { X = 0L, Y = 0L },
797
+ new A.Extents { Cx = cx, Cy = cy }), // MUST match DW.Extent
798
+ new A.PresetGeometry(
799
+ new A.AdjustValueList())
800
+ { Preset = A.ShapeTypeValues.Rectangle }));
801
+
802
+ var inline = new DW.Inline(
803
+ new DW.Extent { Cx = cx, Cy = cy }, // MUST match A.Extents
804
+ new DW.EffectExtent
805
+ {
806
+ LeftEdge = 0L,
807
+ TopEdge = 0L,
808
+ RightEdge = 0L,
809
+ BottomEdge = 0L
810
+ },
811
+ docProps,
812
+ new DW.NonVisualGraphicFrameDrawingProperties(
813
+ new A.GraphicFrameLocks { NoChangeAspect = true }),
814
+ new A.Graphic(
815
+ new A.GraphicData(picture)
816
+ { Uri = PicGraphicDataUri }))
817
+ {
818
+ DistanceFromTop = 0U,
819
+ DistanceFromBottom = 0U,
820
+ DistanceFromLeft = 0U,
821
+ DistanceFromRight = 0U
822
+ };
823
+
824
+ return new Drawing(inline);
825
+ }
826
+
827
+ // ── Private Helpers ────────────────────────────────────────────────
828
+
829
+ /// <summary>
830
+ /// Builds a DW.Anchor element for floating images with configurable wrapping.
831
+ /// </summary>
832
+ private static DW.Anchor BuildAnchorElement(
833
+ string relId, long cx, long cy,
834
+ uint docPropId, string name,
835
+ OpenXmlElement wrapElement,
836
+ bool behindDoc)
837
+ {
838
+ return new DW.Anchor(
839
+ new DW.SimplePosition { X = 0L, Y = 0L },
840
+ new DW.HorizontalPosition(
841
+ new DW.PositionOffset("0"))
842
+ { RelativeFrom = DW.HorizontalRelativePositionValues.Column },
843
+ new DW.VerticalPosition(
844
+ new DW.PositionOffset("0"))
845
+ { RelativeFrom = DW.VerticalRelativePositionValues.Paragraph },
846
+ new DW.Extent { Cx = cx, Cy = cy },
847
+ new DW.EffectExtent
848
+ {
849
+ LeftEdge = 0L,
850
+ TopEdge = 0L,
851
+ RightEdge = 0L,
852
+ BottomEdge = 0L
853
+ },
854
+ wrapElement,
855
+ new DW.DocProperties { Id = docPropId, Name = name },
856
+ new DW.NonVisualGraphicFrameDrawingProperties(
857
+ new A.GraphicFrameLocks { NoChangeAspect = true }),
858
+ new A.Graphic(
859
+ new A.GraphicData(
860
+ new PIC.Picture(
861
+ new PIC.NonVisualPictureProperties(
862
+ new PIC.NonVisualDrawingProperties
863
+ {
864
+ Id = 0U,
865
+ Name = name
866
+ },
867
+ new PIC.NonVisualPictureDrawingProperties()),
868
+ new PIC.BlipFill(
869
+ new A.Blip { Embed = relId },
870
+ new A.Stretch(new A.FillRectangle())),
871
+ new PIC.ShapeProperties(
872
+ new A.Transform2D(
873
+ new A.Offset { X = 0L, Y = 0L },
874
+ new A.Extents { Cx = cx, Cy = cy }),
875
+ new A.PresetGeometry(
876
+ new A.AdjustValueList())
877
+ { Preset = A.ShapeTypeValues.Rectangle }))
878
+ )
879
+ { Uri = PicGraphicDataUri })
880
+ )
881
+ {
882
+ DistanceFromTop = 0U,
883
+ DistanceFromBottom = 0U,
884
+ DistanceFromLeft = 114300U,
885
+ DistanceFromRight = 114300U,
886
+ SimplePos = false,
887
+ RelativeHeight = 251658240U,
888
+ BehindDoc = behindDoc,
889
+ Locked = false,
890
+ LayoutInCell = true,
891
+ AllowOverlap = true
892
+ };
893
+ }
894
+
895
+ /// <summary>
896
+ /// Maps file extensions to OpenXML PartTypeInfo values via ImagePartType.
897
+ /// In SDK 3.x, ImagePartType is a static class whose members return PartTypeInfo.
898
+ /// </summary>
899
+ private static PartTypeInfo GetImagePartType(string imagePath)
900
+ {
901
+ string ext = Path.GetExtension(imagePath).ToLowerInvariant();
902
+ return ext switch
903
+ {
904
+ ".png" => ImagePartType.Png,
905
+ ".jpg" or ".jpeg" => ImagePartType.Jpeg,
906
+ ".gif" => ImagePartType.Gif,
907
+ ".bmp" => ImagePartType.Bmp,
908
+ ".tif" or ".tiff" => ImagePartType.Tiff,
909
+ ".svg" => ImagePartType.Svg,
910
+ ".emf" => ImagePartType.Emf,
911
+ ".wmf" => ImagePartType.Wmf,
912
+ ".ico" => ImagePartType.Icon,
913
+ _ => throw new NotSupportedException(
914
+ $"Image format '{ext}' is not supported by OpenXML.")
915
+ };
916
+ }
917
+ }