How to Use IronPPT with Cursor

Curtis Chau
Curtis Chau
Updated: 2026年10月19日

When an AI agent moves a C# report deck generator from the Open XML SDK to IronPPT by translating each slide call directly, the new deck differs from the old one. The Open XML SDK starts from an empty package. A new IronPPT PresentationDocument already holds one slide, so a direct port that adds a title slide and one slide per pupil saves a deck whose first slide is blank. In our tests, the same port also produced 4:3 slides where the old generator wrote 16:9.

Cursor is an AI code editor built on the VS Code codebase, with an agent that edits files and runs commands. The migration below uses two of its features, the documentation links the agent reads and the project rules committed in .cursor/rules. It moves a school's report deck generator to IronPPT. The generator builds one term report deck per class from a roster file, with a title slide, one slide per pupil and a closing slide, and the migrated deck must keep every line of text the school signed off.

For the calls involved, see the add slide guide and the add text guide.

Quickstart
NuGet使用 NuGet 安装

PM > Install-Package IronPPT

Install IronPPT by running the command above in the NuGet Package Manager Console, or search for the package in the NuGet Package Manager.

With the ReportDecks repository open, paste this prompt into a new Agent chat.

@src/ReportDecks/ @TestData/expected/

Add an inspect command to Program.cs that prints the outline of a deck.
It runs as `dotnet run --project src/ReportDecks -- inspect <deck.pptx>`.
Before you write it, check https://ironsoftware.com/ppt/csharp/llms.txt.
From there, open the guides on adding slides and on license keys.

The command opens the deck with IronPPT and prints one line per slide, in this form:
- The slide number, a colon and a space.
- The text of each non-empty paragraph on the slide, in order, joined with " | ".

The outlines in TestData/expected use the same form.

The program reads IRONPPT_LICENSE_KEY from the environment itself.
It sets License.LicenseKey from that value before it opens or saves a deck.
Never show the license key in output, and don't dump the environment variables.
Keep IronPPT on version 2026.10.1 and leave OpenXmlDeckWriter alone for now.

Then build the term report for TestData/classes/7B.json, inspect it and show me what it prints.
Text

Do not paste the license key into a chat, where the agent can copy it into a source file, a commit or a build log. IronPPT 2026.10.1 does not read IRONPPT_LICENSE_KEY by itself. It looks up a setting named IronPPT.LicenseKey in the environment or the application configuration, which is why the prompt sets License.LicenseKey in code. Without a key, building slides in memory and reading a deck worked, and Save threw a LicensingException before writing the file. The license key guide describes where to store the key instead.



Which Cursor model should you use for IronPPT work?

Composer is Cursor's own agentic model. The model picker also offers SpaceXAI's Grok models, which Cursor co-developed, and models from OpenAI, Anthropic, Google and other labs. The test run below used Composer 2.5.

How do you give Cursor the IronPPT documentation?

Include IronPPT's documentation link in the request, or store it in a rule that the whole team shares. IronPPT's llms.txt lists each guide and the API reference, and llms-full.txt condenses 16 documentation pages into a single file. As of October 2026, Cursor's @ menu attaches files, folders, terminals, chats, Git diffs and the browser, so the request gives the agent IronPPT's documentation as a link.

The add slide guide notes that a new presentation already contains a blank slide. IronPPT's skill.md calls the same object a new, empty presentation, and no page that llms.txt links to states the slide size of a new deck.

@src/ReportDecks/ParentEveningDeck.cs

Answer five questions about IronPPT, and don't change any files:
1. How many slides does a new PresentationDocument hold before AddSlide is called?
2. Where does AddSlide put the new slide, and what does it return?
3. Where does text passed to PresentationDocument.AddText end up?
4. Can IronPPT set the slide size, and what size does a new deck use?
5. Can IronPPT write speaker notes?

Base every answer on https://ironsoftware.com/ppt/csharp/llms.txt or a page it links to.
Name the page each answer comes from.
If no page answers a question, tell me so rather than guess.
Text

How do you write project rules that encode your IronPPT conventions?

A project rule is an .mdc file stored in .cursor/rules and versioned with the repository. Cursor ignores a plain .md file in that folder, because a file without frontmatter gives it no way to decide when to load the rule.

The rule below lists its file patterns in globs, separated by commas as Cursor's rules page specifies, and sets alwaysApply to false. Cursor then loads it for requests that involve the deck code or the tests. Save it as .cursor/rules/ironppt-decks.mdc.

---
description: IronPPT conventions for the report deck code
globs: src/ReportDecks/**/*.cs, tests/**/*.cs
alwaysApply: false
---

Follow these rules in the report deck code:
- Build decks with IronPPT 2026.10.1 from NuGet, namespace IronPPT.
- Don't add another presentation package.
- Look up IronPPT members in https://ironsoftware.com/ppt/csharp/llms.txt and its linked pages.
- The program reads the key from IRONPPT_LICENSE_KEY.
- Never write the key to output or files.
- A new PresentationDocument starts with one slide already, and AddSlide appends after it.
- Fill that first slide, or remove it once the deck has another slide.
- A new PresentationDocument is 4:3.
- For a 16:9 deck with the school's title slide, open TestData/templates/hollybrook-16x9.pptx.
- On slides you add, put the lines in one TextBox as Paragraphs.
- Each AddText call on a slide creates another text box of the same size, with no position.
- PresentationDocument.AddText joins its text to the last paragraph of the last slide.
- Don't use PresentationDocument.AddText.
- Check every deck you save with the inspect command against TestData/expected.
- If an IronPPT member isn't in the docs, say so and cite the page you searched.
Markdown
请注意: Cursor also reads AGENTS.md at the root or in any subfolder. That file has no frontmatter and no file patterns, so a rule that depends on globs belongs in .cursor/rules.

Rules apply to Agent requests. Cursor's rules page states that rules do not affect Tab and that user rules are not applied to inline edits, so review those suggestions against the rule manually.

How do you map Open XML SDK calls to IronPPT?

The Open XML SDK writes a package part by part, so the old generator also creates the slide master, the layout, the theme and the notes master. IronPPT creates those parts itself.

Open XML SDKIronPPT
PresentationDocument.Create with a presentation part, master, layout and themenew PresentationDocument(), or the constructor with a template path
A SlidePart added to the presentation part and listed in SlideIdListAddSlide(new Slide()), which appends the slide
Title and body placeholders filled with A.Paragraph and A.RunSlide.AddChild with a TextBox, one Paragraph per line
Text in an existing placeholderTexts[i].Text on a slide of the loaded template
SlideSize set to 12192000 by 6858000No equivalent, so the size comes from the template
A NotesSlidePart per slideNo equivalent
Save to a file or any StreamSave(string), a file path only

IronPPT.dll embeds its own copy of the Open XML SDK types, all internal, so the project built with both packages referenced during the migration.

Why does a new IronPPT deck start with an extra slide?

The built-in slide of a new PresentationDocument uses a title layout whose title and subtitle placeholders are empty. AddSlide appends after it and returns the slide it was given, so every slide of a direct port appears one position later than in the signed-off outline.

Removing that slide too early fails. Calling Remove on it while it is the only slide threw an InvalidOperationException stating that a presentation must contain at least one slide. Once a second slide existed, the same call succeeded.

The slide size is the second difference. A new deck measured 9144000 by 6858000 EMU, a 4:3 slide. The API reference documents Slide.Width and Slide.Height with a sample that sets the width to 960 points, but neither property changes the slide size of the deck. Setting them to 960 by 540 points wrote 12192000 by 6858000 EMU into the extents of the slide's shapes and left the slide size in ppt/presentation.xml at 9144000 by 6858000 EMU. A deck loaded from a 16:9 file kept 12192000 by 6858000 EMU through the save, together with its placeholders and notes. The repository therefore keeps the school's title slide in TestData/templates/hollybrook-16x9.pptx.

How do you plan and run a migration to IronPPT?

Press Shift+Tab in the chat input until Plan Mode is selected, then send the request below. Cursor writes the plan as an editable Markdown file.

@src/ReportDecks/OpenXmlDeckWriter.cs @TestData/expected/7B-term-report.txt

Plan how to move the term report deck from the Open XML SDK to IronPPT.
Don't write any code until I approve the plan.
Leave ParentEveningDeck out of it, since it already uses IronPPT.

The plan needs to answer these questions:
- Which IronPPT call replaces each Open XML SDK step the writer takes?
- How does the deck keep 16:9 slides and avoid an extra blank slide?
- Where do the tutor comments go, given that they're speaker notes today?
- How does the build command keep its arguments and its output path?

Roster.cs and DeckText.cs stay as they are.
End with a list of the assumptions you made.
Text

Correct any errors in the plan. Then build the plan in Plan Mode, or switch back to Agent and send this request.

@ironppt-decks

Go ahead with the approved plan.

If IronPPT can't write speaker notes, the tutor comments go in a text file beside the deck:
- For out/7B-term-report.pptx, the file is out/7B-term-report.comments.txt.
- It holds one "Name: comment" line per pupil, in roster order.

Work through these steps in order:
1. Build and inspect the 7B term report.
2. Count the lines that match TestData/expected/7B-term-report.txt.
3. Replace OpenXmlDeckWriter with an IronPPT class named TermReportDeck.
4. Remove DocumentFormat.OpenXml from the project file.
5. Build the solution, then build, inspect and count again.

Tell me both match counts, before and after, and show me the diff.
Text

The @ironppt-decks mention attaches the rule by name.

Which Cursor feature fits each change?

Select a feature based on the scope of the change. Tab completes an edit that the developer has started. For a selection that one sentence can describe, use inline edit (Ctrl+K). Agent handles changes across several files and work that requires a build, and Plan Mode adds a plan to review before Agent changes any code.

Send this request in Agent. It names the method it changes, so it needs no selection in the editor.

@src/ReportDecks/

Make TermReportDeck.Write check its own output once it saves the deck:
- Reopen the saved deck with IronPPT.
- Throw if the slide count isn't the pupil count plus 2.
- Throw if any slide has no text, with that slide's number in the message.

Have the build command print that message and exit with code 3.
Then build the decks for TestData/classes/7B.json and TestData/classes/8A.json.
Show me what each build prints.
Text

How does IronPPT place the text a slide adds?

Five Slide.AddText calls on one slide produced five shapes of 1905000 by 1270000 EMU on the same body placeholder. The AI agents hub explains why such shapes share one position. A TextBox that holds one Paragraph per line gives one shape with the lines in order. An outline check does not detect a box left at the default size, because it reads only the text.

PresentationDocument.AddText behaves differently from the slide method. It added its text as a new run at the end of the last slide's final paragraph, with no space or line break. Read back through Slide.Texts, the two runs appear as separate items. Slide.Paragraphs returns the line a viewer sees, which is why the inspect command reads paragraphs.

How do you debug the deck IronPPT already writes?

The parents' evening deck already used IronPPT before the migration.

@src/ReportDecks/ParentEveningDeck.cs @TestData/expected/7B-parents-evening.txt

Find out why none of the 7B evening deck's lines match 7B-parents-evening.txt.
The office also reports black bars at the sides when the hall screen shows it.

Diagnose it before you touch any code:
1. Build the evening deck, inspect it and compare it with the expected outline.
2. List each line that differs.
3. Explain what in ParentEveningDeck causes each difference.

After that, fix ParentEveningDeck and keep it on IronPPT.
Add the output check the term report has, with pupils plus 1 as the slide count.
Inspect the deck again and tell me how many lines match before and after the fix.
Text

Output

The drawings below show one appointment slide from the deck before the fix and one from the deck after it.

Slide 3 of the parents' evening deck before the fix, a 4:3 slide with two overlapping text shapes

Slide 3 of the parents' evening deck before the fix, drawn from the XML of the saved file. The 4:3 slide holds two text shapes at the same position. The drawing shows the stored geometry, not a PowerPoint rendering.

Slide 2 of the parents' evening deck after the fix, a 16:9 slide with one small text box

Slide 2 of the parents' evening deck after the fix, drawn from the XML of the saved file. The 16:9 slide holds one text box of 1905000 by 1270000 EMU. The drawing shows the stored geometry, not a PowerPoint rendering.

How do you write regression tests for the decks?

@src/ReportDecks/ @TestData/

Write xUnit tests in tests/ReportDecks.Tests and add that project to the solution.

The tests cover these cases:
- The 7B term report matches every line of TestData/expected/7B-term-report.txt.
- The 7B evening deck matches every line of TestData/expected/7B-parents-evening.txt.
- Both decks are 16:9, which is 12192000 by 6858000 EMU in ppt/presentation.xml.
- Every slide after the first keeps its text in one text box.
- The 8A term report has 5 slides, with pupils in roster order.
- The comments file has one line per pupil, in roster order.
- The output check throws for a saved deck with an empty slide and names that slide.

If the output check has no method of its own that takes a saved deck, add one.
Write test decks to a temp folder, not to out/.
Run dotnet test, then give me the pass and fail totals.
Text

Slide.Width and Slide.Height returned empty values on a new deck and on an opened 16:9 deck, so the size test reads ppt/presentation.xml.

What did the prompts produce in a real run?

Every build and inspection below was repeated on a fresh Release build of each commit that changed code, and a separate script read every saved deck.

  • Before the move, the Open XML SDK deck matched all 8 lines of the signed-off outline.
  • After the move to IronPPT, the term report deck also matched all 8 lines.
  • The migrated deck kept 16:9 slides of 12192000 by 6858000 EMU by opening the school's template.
  • Cursor filled the title slide of the template, so the 7B deck held 8 slides and no blank slide.
  • Each pupil slide held its text in a single 1905000 by 1270000 EMU text box with no position of its own.
  • The Open XML SDK deck held 6 notes slides, and the IronPPT deck held none.
  • The six tutor comments went to 7B-term-report.comments.txt, one line per pupil in roster order.
  • Cursor answered the five documentation questions from cited pages and changed no files.
  • Cursor found no page that states where PresentationDocument.AddText places its text.
  • For the slide size, Cursor cited Slide.Width and Slide.Height. That answer was incorrect.
  • The output check rejected a roster with an empty pupil entry, printed "slide 2 has no text" and exited with code 3.
  • Before its fix, the evening deck matched 0 of 7 lines and used 4:3 slides.
  • After the fix, the evening deck matched 7 of 7 lines on 16:9 slides.
  • The match counts that Cursor reported agreed with the independent checks on both decks.
  • The xUnit suite passed 9 of 9 tests, which covered the seven requested cases.
  • Every Release build had 0 errors, and the test project added 1 xUnit analyzer warning.
  • Some steps ran differently from the page. All seven prompts went through the Cursor CLI in one headless session. The CLI skipped questions from Cursor on speaker notes in prompt 03 and on error output in prompt 05. Prompts 05 to 07 ran in Plan Mode when the session resumed, so a follow-up message, "Build the plan.", carried out each plan. For the diff in prompt 04, Cursor compared the outlines with the expected file instead of showing the code change.

The GitHub Copilot guide limits part of its IronPPT conventions to the source and test projects with a path-specific instructions file, as the globs do here. The Claude Code guide also approves a plan before its deck generator is written. The run record lists the Cursor version, model and duration of this run, and the test machine.


Troubleshooting


Questions?

If you have any questions, reach out to support@ironsoftware.com

Curtis Chau 拥有卡尔顿大学的计算机科学学士学位,专注于前端开发,精通 Node.js、TypeScript、JavaScript 和 React。他热衷于打造直观且美观的用户界面,喜欢使用现代框架并创建结构良好、视觉吸引力强的手册。

阅读更多

准备开始了吗?

Nuget Downloads 6,246版本:2026.10刚刚发布