- V1 scope: deterministic C# source-to-source migration for simple generated PDFs using the DevExpress PdfDocumentProcessor/PdfGraphics API.
- Roslyn-backed migration connected through
PVAD.WebApivia framework idDevExpress. - Status upgraded from reporting pilot to full converter.
-
new PdfDocumentProcessor()→var document = new PdfDocument(). -
processor.CreateEmptyDocument()→ removed (document created by constructor). -
using var graphics = processor.CreateGraphics()→ removed (PVADE uses PdfPage surface directly). -
graphics.DrawString(text, font, brush, x, y)→ deferred, emitted aspage.DrawTextFromTop(text, x, y, fontSize)after AddPage. -
graphics.DrawLine(pen, x1, y1, x2, y2)→ deferred, emitted aspage.DrawLine(x1, y1, x2, y2). -
graphics.DrawRectangle(pen, x, y, w, h)→ deferred, emitted aspage.DrawRectangle(x, y, w, h). -
processor.RenderNewPage(...)→var page = document.AddPage()+ all deferred draw calls repositioned after it. -
processor.SaveDocument(path)→document.Save(path). - All
DevExpress.*usings removed;using PVAD.Pdf;inserted. - Existing-PDF processing (
LoadDocument,AppendDocument,DeletePage,InsertPage) kept withCANMIGDEVEXP021warning. - Forms, signatures, annotations, bookmarks emit
CANMIGDEVEXP022warning. - Encryption (
PdfEncryptionOptions) emits dedicatedCANMIGDEVEXP024guidance (PVADE now supports it viaPdfSaveOptions.Encryption). - Report export (
XtraReport,ExportToPdf) emitCANMIGDEVEXP020warning. - WebApi conversion response includes migrated code, diagnostics, and summary counts.
- V2 preserves font size from
DXFontwhen the font is passed as an identifier (pre-scan recovery; defaults to 12 only when unknown). - V2 preserves pen colour and brush colour (mapped to PVADE
strokeColor/FillColor). - V2 handles
DrawRectangle(pen, RectangleF)(2-arg form) when the rectangle is constructed inline. - V2 extracts font size from
new DXFont("family", size)constructors and font-variable declarations. - V2 uses hybrid semantic matching (local-symbol tracking) with syntactic vendor-name fallback.
Moves the provider from "deterministic single-page V1" to a more robust V2. Work is isolated to
src/Migrations/PDF/PVAD.Migration.Pdf.Code.DevExpress/DevExpressPdfMigration.cs and its test project.
-
RenderNewPagealways emittedvar page = document.AddPage();, so twoRenderNewPagecalls produced twovar pagedeclarations → duplicate-local compile error. - Fix: add
_pageDeclaredflag — first page emitsvar page = document.AddPage();, subsequent pages emitpage = document.AddPage();. Per-page deferred-draw accumulation already correct. - Test
Migrate_MultiplePages_ReusesPageVariable: exactly onevar page, a secondpage = document.AddPage();, each page's draws follow its ownAddPage().
PVAD.Pdf supports colour: DrawLine(..., strokeColor), DrawRectangle(..., strokeColor, fillColor),
text colour via DrawTextFromTop(text, x, topY, PdfDrawTextOptions { FillColor }).
- New helper
MapDxColor(ExpressionSyntax) -> string?(returnsnullfor default black / unknown):- Named
DXBrushes.X/DXPens.X/DXColor.X→Black→PdfColor.Black(null),White→PdfColor.White,Gray/Grey→PdfColor.Gray,Red→PdfColor.RedColor,Green→PdfColor.GreenColor,Blue→PdfColor.BlueColor. -
DXColor.FromArgb(r,g,b)/FromArgb(a,r,g,b)→PdfColor.FromRgb(r,g,b)(drop alpha). -
new DXPen(color, width)→ recurse colour arg + surface width aslineWidth. -
new DXSolidBrush(color)→ recurse colour arg.
- Named
-
DrawLine: penargs[0]→ strokeColor/width; longer overload only when non-default. -
DrawRectangle(5-arg): penargs[0]→ strokeColor. -
DrawString: brushargs[2]→new PdfDrawTextOptions { FontSize = N, FillColor = color }; keep short 4-arg form when colour is default. - Emit Info
CANMIGDEVEXP009when a colour is applied.
-
DrawRectangle(pen, RectangleF)(2-arg): inlinenew RectangleF(x,y,w,h)→ decompose to normalDrawRectangle(x,y,w,h); variable rect → keep + WarningCANMIGDEVEXP023. - Font size from variable: pre-scan
Dictionary<string,string>of font-local →DXFontctor size;TryExtractDxFontSizefalls back to it when the font arg is an identifier. Default"12"when unknown.
- Build a
CSharpCompilation(tree + core references) and obtain aSemanticModelinMigrate. - Resolve processor / graphics / page locals to
ISymbols; match member-access receivers bySymbolEqualityComparerinstead of by string name. Vendor type/method names stay syntactic. - Fall back to string-name matching when the symbol is unresolved (never worse than V1).
- Test: reassigned processor variable /
this.-qualified access still migrates correctly.
- NuGet packages:
-
DevExpress.Pdf -
DevExpress.Drawing -
DevExpress.XtraReports.UI(report export — manual)
-
- Namespaces removed:
DevExpress.Pdf,DevExpress.Drawing,DevExpress.XtraReports.UI - Classes fully converted:
-
PdfDocumentProcessor→PdfDocument -
CreateEmptyDocument()→ removed -
CreateGraphics()→ removed -
RenderNewPage(...)→AddPage()+ repositioned draw calls -
DrawString(text, font, brush, x, y)→DrawTextFromTop -
DrawLine(pen, x1, y1, x2, y2)→DrawLine -
DrawRectangle(pen, x, y, w, h)→DrawRectangle -
SaveDocument(path)→Save(path)
-
- Classes kept as-is (manual migration):
-
PdfAcroForm/PdfFormField -
PdfSignature/PdfDocumentSigner -
PdfEncryptionOptions -
PdfAnnotation/PdfBookmark -
XtraReport/PdfExportOptions -
LoadDocument/AppendDocument/DeletePage/InsertPage
-
DevExpress uses an unusual API shape where draw calls come before RenderNewPage:
// DevExpress order: draw → render page
graphics.DrawString(...) // draw first
processor.RenderNewPage() // page created after drawingPVAD.Pdf requires draw calls to come after AddPage():
// PVADE order: add page → draw
var page = document.AddPage(); // page first
page.DrawTextFromTop(...) // draw afterThe rewriter handles this by deferring draw calls to a list, then emitting them immediately after the AddPage() statement that replaces RenderNewPage.
-
DevExpressPdfMigrationusesDevExpressRewriter : CSharpSyntaxRewriter. - Pre-scan phase:
FindProcessorVariable,FindGraphicsVariable,FindSaveTarget. -
VisitCompilationUnitoverridden (notVisitGlobalStatement) to allow one-to-many statement expansion whenRenderNewPageis encountered. -
TransformGlobal: dispatches eachGlobalStatementSyntaxto the right transformation. -
TryConvertDrawCall: converts draw calls on_graphicsVarto PVADE calls and defers them in_deferredDrawCalls. -
TryExtractDxFontSize: extracts font size fromnew DXFont("family", size)constructors. -
IsCreationDeclaration: matchesvar x = new TypeName(...)(handlesusing vartoo). -
IsDeclarationWithCall: matchesvar x = obj.Method(...)(used forCreateGraphics). -
IsMethodCallOn: matchesvariable.Method(...)expression statements. -
ScanForUnsupportedIdentifiers: post-rewrite scan for forms/signatures/report-export type names.
| DevExpress PDF API / pattern | PVAD.Pdf replacement | Notes |
|---|---|---|
using DevExpress.Pdf[.*]; |
(removed) + using PVAD.Pdf; |
All DevExpress.* namespaces stripped |
new PdfDocumentProcessor() |
new PdfDocument() |
using var keyword also handled |
processor.CreateEmptyDocument() |
(removed) | Document created by constructor |
using var graphics = processor.CreateGraphics() |
(removed) | PVADE uses PdfPage surface directly |
graphics.DrawString(text, font, brush, x, y) |
page.DrawTextFromTop(text, x, y, fontSize) |
Deferred; font size from DXFont ctor or font variable; non-black brush → PdfDrawTextOptions { FillColor } |
graphics.DrawLine(pen, x1, y1, x2, y2) |
page.DrawLine(x1, y1, x2, y2) |
Deferred; non-default pen → (.., lineWidth, strokeColor) |
graphics.DrawRectangle(pen, x, y, w, h) |
page.DrawRectangle(x, y, w, h) |
Deferred; non-default pen → (.., lineWidth, false, strokeColor) |
graphics.DrawRectangle(pen, RectangleF) |
page.DrawRectangle(x, y, w, h) |
Inline new RectangleF(...) decomposed; variable → CANMIGDEVEXP023 |
processor.RenderNewPage(paperSize, graphics) |
var page = document.AddPage(<size>); + draw calls |
A4→default, A3/Letter→PdfPagePreset, Legal/A5→explicit pts, (w,h,graphics)→AddPage(w,h); unmapped→A4 + CANMIGDEVEXP026 |
processor.SaveDocument(path) |
document.Save(path) |
Path arg preserved |
| ID | Severity | Meaning |
|---|---|---|
CANMIGDEVEXP001 |
Info | new PdfDocumentProcessor() → new PdfDocument() |
CANMIGDEVEXP002 |
Info | CreateEmptyDocument() removed |
CANMIGDEVEXP003 |
Info | CreateGraphics() removed |
CANMIGDEVEXP004 |
Info | RenderNewPage() → document.AddPage() + draw calls repositioned |
CANMIGDEVEXP005 |
Info | DrawString(...) → page.DrawTextFromTop(...) |
CANMIGDEVEXP006 |
Info | DrawLine(...) → page.DrawLine(...) |
CANMIGDEVEXP007 |
Info | DrawRectangle(...) → page.DrawRectangle(...) |
CANMIGDEVEXP008 |
Info | SaveDocument(...) → document.Save(...) |
CANMIGDEVEXP009 |
Info | Pen/brush colour mapped to PVADE colour argument |
CANMIGDEVEXP010 |
Info | DevExpress encryption mapped to PdfSaveOptions.Encryption |
CANMIGDEVEXP020 |
Warning | Report export workflow requires manual migration |
CANMIGDEVEXP021 |
Warning | Existing-PDF processing/page editing APIs are outside v1 |
CANMIGDEVEXP022 |
Warning | Forms, signatures, annotations, or bookmarks are outside v1 |
CANMIGDEVEXP023 |
Warning | DrawRectangle(pen, RectangleF) bounds could not be decomposed |
CANMIGDEVEXP024 |
Warning | Encryption present but not auto-mappable (no 2-arg SaveDocument) — apply PdfSaveOptions.Encryption manually |
CANMIGDEVEXP025 |
Info | DXFont declaration removed — font size inlined into DrawTextFromTop |
CANMIGDEVEXP026 |
Warning | PdfPaperSize.X has no PVADE preset — defaulted to A4 (use AddPage(width, height)) |
- Font family from
DXFont(colour now mapped in V2) - Pen/brush colour and stroke width (V2)
-
DrawRectangle(pen, RectangleF)(2-arg form, inline rectangle — V2) - AcroForms, document merge/split
- Digital signatures
- Encryption — PVAD.Pdf now supports it (
PdfSaveOptions.Encryption); migration emitsCANMIGDEVEXP024guidance - Printing/rendering workflows
- Report export (XtraReport → PVAD generation)
using DevExpress.Pdf;
using DevExpress.Drawing;
using var processor = new PdfDocumentProcessor();
processor.CreateEmptyDocument();
using var graphics = processor.CreateGraphics();
// Draw calls happen before RenderNewPage in DevExpress
graphics.DrawString("Invoice #2024", new DXFont("Arial", 18), DXBrushes.Black, 40, 750);
graphics.DrawLine(DXPens.Black, 40, 720, 555, 720);
graphics.DrawString("Thank you for your order.", new DXFont("Arial", 12), DXBrushes.Black, 40, 690);
graphics.DrawRectangle(DXPens.Black, 40, 620, 200, 60);
processor.RenderNewPage(PdfPaperSize.A4, graphics);
processor.SaveDocument(outputPath);using PVAD.Pdf;
var document = new PdfDocument();
var page = document.AddPage();
page.DrawTextFromTop("Invoice #2024", 40, 750, 18);
page.DrawLine(40, 720, 555, 720);
page.DrawTextFromTop("Thank you for your order.", 40, 690, 12);
page.DrawRectangle(40, 620, 200, 60);
document.Save(outputPath);Note:
DXPens.Blackmaps to the PVADE default stroke colour, so the short-form call is emitted. Non-black pens/brushes (e.g.DXPens.Red,DXColor.FromArgb(...)) are mapped to explicit PVADE colour arguments in V2.
- Remove
DevExpress.*usings, addusing PVAD.Pdf; - Replace
new PdfDocumentProcessor()withnew PdfDocument() - Remove
processor.CreateEmptyDocument() - Remove
using var graphics = processor.CreateGraphics() - Convert
graphics.DrawString(text, font, brush, x, y)→page.DrawTextFromTop(text, x, y, fontSize) - Convert
graphics.DrawLine(pen, x1, y1, x2, y2)→page.DrawLine(x1, y1, x2, y2) - Convert
graphics.DrawRectangle(pen, x, y, w, h)→page.DrawRectangle(x, y, w, h) - Replace
processor.RenderNewPage(...)withvar page = document.AddPage();+ deferred draw calls - Replace
processor.SaveDocument(path)withdocument.Save(path) - Extract font size from
new DXFont("family", size)→fontSizeargument (inline + font-variable pre-scan) - Map pen colour to
strokeColorargument
- Basic generation workflow → PVADE code (PdfDocument, AddPage, DrawTextFromTop, Save)
- Line and rectangle drawing → PVADE DrawLine/DrawRectangle
- Draw calls repositioned after AddPage (ordering assertion)
- Existing-PDF processing → CANMIGDEVEXP021 warning
- Forms/signatures/encryption → CANMIGDEVEXP022 warning
- Report export workflow → CANMIGDEVEXP020 warning
- WebApi migration-service smoke test
- Multi-page sample (multiple RenderNewPage calls)
- Named pen colour → PVADE colour argument + CANMIGDEVEXP009
- Default black colour keeps short-form call (regression guard)
-
DXColor.FromArgb(...)→PdfColor.FromRgb(...) - Brush on
DrawString→PdfDrawTextOptions { FillColor } - Inline
RectangleFdecomposed; variableRectangleF→ CANMIGDEVEXP023 - Font passed as variable recovers font size
- Snapshot before/after migration sample