diff --git a/ReleaseFiles/App.config b/ReleaseFiles/App.config index 7465d52..7123378 100644 --- a/ReleaseFiles/App.config +++ b/ReleaseFiles/App.config @@ -7,7 +7,10 @@ - + + + + @@ -122,7 +125,6 @@ - @@ -148,7 +150,7 @@ - + @@ -161,6 +163,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ReleaseFiles/BouncyCastle.Cryptography.dll b/ReleaseFiles/BouncyCastle.Cryptography.dll new file mode 100644 index 0000000..637242d Binary files /dev/null and b/ReleaseFiles/BouncyCastle.Cryptography.dll differ diff --git a/ReleaseFiles/Microsoft.DotNet.PlatformAbstractions.dll b/ReleaseFiles/Microsoft.DotNet.PlatformAbstractions.dll new file mode 100644 index 0000000..ae549ea Binary files /dev/null and b/ReleaseFiles/Microsoft.DotNet.PlatformAbstractions.dll differ diff --git a/ReleaseFiles/Microsoft.Extensions.DependencyInjection.dll b/ReleaseFiles/Microsoft.Extensions.DependencyInjection.dll index 3ebfa64..1034ee6 100644 Binary files a/ReleaseFiles/Microsoft.Extensions.DependencyInjection.dll and b/ReleaseFiles/Microsoft.Extensions.DependencyInjection.dll differ diff --git a/ReleaseFiles/Microsoft.Extensions.Logging.Abstractions.dll b/ReleaseFiles/Microsoft.Extensions.Logging.Abstractions.dll index 5bc5d39..2c87f79 100644 Binary files a/ReleaseFiles/Microsoft.Extensions.Logging.Abstractions.dll and b/ReleaseFiles/Microsoft.Extensions.Logging.Abstractions.dll differ diff --git a/ReleaseFiles/Microsoft.Extensions.Logging.dll b/ReleaseFiles/Microsoft.Extensions.Logging.dll index bda07c2..6df35e1 100644 Binary files a/ReleaseFiles/Microsoft.Extensions.Logging.dll and b/ReleaseFiles/Microsoft.Extensions.Logging.dll differ diff --git a/ReleaseFiles/Microsoft.Extensions.Options.dll b/ReleaseFiles/Microsoft.Extensions.Options.dll index 088358a..8a2a8c8 100644 Binary files a/ReleaseFiles/Microsoft.Extensions.Options.dll and b/ReleaseFiles/Microsoft.Extensions.Options.dll differ diff --git a/ReleaseFiles/Microsoft.Win32.SystemEvents.dll b/ReleaseFiles/Microsoft.Win32.SystemEvents.dll index 279431a..cd1f3d1 100644 Binary files a/ReleaseFiles/Microsoft.Win32.SystemEvents.dll and b/ReleaseFiles/Microsoft.Win32.SystemEvents.dll differ diff --git a/ReleaseFiles/PlcAddrDfn.xml b/ReleaseFiles/PlcAddrDfn.xml new file mode 100644 index 0000000..ed5dbb3 --- /dev/null +++ b/ReleaseFiles/PlcAddrDfn.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ReleaseFiles/System.Data.OleDb.dll b/ReleaseFiles/System.Data.OleDb.dll index f7840cd..95d3e71 100644 Binary files a/ReleaseFiles/System.Data.OleDb.dll and b/ReleaseFiles/System.Data.OleDb.dll differ diff --git a/ReleaseFiles/System.Data.SqlClient.dll b/ReleaseFiles/System.Data.SqlClient.dll index c9675bc..9d91846 100644 Binary files a/ReleaseFiles/System.Data.SqlClient.dll and b/ReleaseFiles/System.Data.SqlClient.dll differ diff --git a/ReleaseFiles/Templates/Logo.png b/ReleaseFiles/Templates/Logo.png new file mode 100644 index 0000000..5a4dc2c Binary files /dev/null and b/ReleaseFiles/Templates/Logo.png differ diff --git a/ReleaseFiles/Templates/Logo2.png b/ReleaseFiles/Templates/Logo2.png new file mode 100644 index 0000000..9674cea Binary files /dev/null and b/ReleaseFiles/Templates/Logo2.png differ diff --git a/ReleaseFiles/Templates/StandardReportTemplate.json b/ReleaseFiles/Templates/StandardReportTemplate.json new file mode 100644 index 0000000..64fb759 --- /dev/null +++ b/ReleaseFiles/Templates/StandardReportTemplate.json @@ -0,0 +1,222 @@ +{ + "document": { + "pageSize": "A4", + "orientation": "Portrait", + "margins": { "top": 40, "bottom": 20, "left": 20, "right": 20 }, + "header": { + "enabled": true, + "left": [ + "${loc:Report_Title}", + "${loc:Report_Id}:${metadata.reportId} | ${loc:Report_Date}:${formatDate(metadata.inspectionDate)} | ${loc:Report_Sample}:${metadata.sampleName}" + ], + "rightImageKey": "companyLogo", + "fontSize": 7, + "color": "#666666", + "showLine": true + }, + "footer": { + "enabled": true, + "left": ["${CompanyName}"], + "right": ["{currentPage} / {totalPages}"], + "fontSize": 8, + "color": "#666666", + "showLine": true + } + }, + "pages": [ + { + "type": "homepage", + "elements": [ + { + "type": "row", "size": [170, 40], "positioning": "flow", + "children": [ + { + "type": "column", "align": "left", + "children": [ + { "type": "image", "dataKey": "companyLogo", "size": [50, 30], "align": "left" }, + { "type": "text", "content": "${CompanyName}", "style": "companyName", "align": "left" } + ] + }, + { + "type": "column", "align": "right", + "children": [ + { "type": "image", "dataKey": "softwareLogo", "size": [15, 20], "align": "right" }, + { "type": "text", "content": "${SoftwareName}", "style": "companyName", "align": "right" } + ] + } + ] + }, + { "type": "spacer", "size": [170, 15], "positioning": "flow" }, + { "type": "text", "content": "${loc:Report_Title}", "style": "title", "positioning": "flow" }, + { "type": "spacer", "size": [170, 10], "positioning": "flow" }, + { "type": "text", "content": "${loc:Report_Id}:${metadata.reportId}", "style": "homepageInfo", "positioning": "flow" }, + { "type": "text", "content": "${loc:Report_Sample}:${metadata.sampleName}", "style": "homepageInfo", "positioning": "flow" }, + { "type": "text", "content": "${loc:Report_Description}:${metadata.description}", "style": "homepageInfo", "positioning": "flow" }, + { "type": "image", "dataKey": "workpieceImage", "size": [160, 110], "border": true, "align": "center", "style": "imageDefault", "positioning": "flow" }, + { "type": "text", "content": "${loc:Report_Operator}:${metadata.operatorName}", "style": "homepageFooter", "positioning": "flow" }, + { "type": "text", "content": "${loc:Report_Date}:${formatDate(metadata.inspectionDate)}", "style": "homepageFooter", "positioning": "flow" } + ] + }, + { + "type": "summary", + "elements": [ + { "type": "text", "content": "${loc:Report_Summary}", "style": "homepageHeading", "positioning": "flow" }, + { + "type": "table", "dataKey": "summaryTable", "positioning": "flow", "size": [170, 0], "style": "tableDefault", + "columns": [ + { "header": "${loc:Field_InspectionType}", "field": "inspectionType", "width": 50, "align": "left" }, + { "header": "${loc:Field_Result}", "field": "classification", "width": 30, "align": "center", "colorRules": { "Pass": "#008000", "Fail": "#FF0000" } }, + { "header": "${loc:Field_Status}", "field": "status", "width": 30, "align": "center", "colorRules": { "合格": "#008000", "PASS": "#008000", "不合格": "#FF0000", "FAIL": "#FF0000" } } + ] + } + ] + }, + { + "type": "metricData", + "elements": [ + { "type": "text", "content": "${loc:Page_MetricData}", "style": "heading", "positioning": "flow" }, + { + "type": "row", "size": [170, 100], "widths": [6, 4], "positioning": "flow", + "children": [ + { + "type": "column", "align": "left", + "children": [ + { "type": "image", "dataKey": "lineMeasurementImage", "size": [110, 90], "border": true, "align": "left" } + ] + }, + { + "type": "column", "align": "left", + "children": [ + { "type": "text", "content": "${loc:Measurement_Type}:${measurementType}", "style": "body", "align": "left" }, + { "type": "text", "content": "${loc:Field_Point1}:${point1}", "style": "body", "align": "left" }, + { "type": "text", "content": "${loc:Field_Point2}:${point2}", "style": "body", "align": "left" }, + { "type": "text", "content": "${loc:Measurement_Distance}:${actualDistance} ${unit}", "style": "body", "align": "left" }, + { "type": "text", "content": "${loc:Measurement_Angle}:${angle}°", "style": "body", "align": "left" } + ] + } + ] + } + ] + }, + { + "type": "bgaInspection", + "elements": [ + { "type": "text", "content": "${loc:Page_BgaInspection}", "style": "heading", "positioning": "flow" }, + { + "type": "row", "size": [170, 100], "widths": [6, 4], "positioning": "flow", + "children": [ + { + "type": "column", "align": "left", + "children": [ + { "type": "image", "dataKey": "bgaInspectionImage", "size": [110, 90], "border": true, "align": "left" } + ] + }, + { + "type": "column", "align": "left", + "children": [ + { "type": "text", "content": "${loc:Bga_Count}:${bgaCount}", "style": "body", "align": "left" }, + { "type": "text", "content": "${loc:Measurement_VoidRate}:${formatPercent(voidRate)}", "style": "body", "align": "left" }, + { "type": "text", "content": "${loc:Measurement_FillRate}:${formatPercent(fillRate)}", "style": "body", "align": "left" }, + { "type": "text", "content": "${loc:Bga_TotalArea}:${formatNumber(totalBgaArea, 2)}", "style": "body", "align": "left" }, + { "type": "text", "content": "${loc:Bga_TotalVoidArea}:${formatNumber(totalVoidArea, 2)}", "style": "body", "align": "left" }, + { "type": "text", "content": "${loc:Bga_VoidLimit}:${formatPercent(voidLimit)}", "style": "body", "align": "left" }, + { "type": "text", "content": "${loc:Table_Classification}:${classification}", "style": "conclusion", "align": "left", "colorRules": { "Pass": "#008000", "Fail": "#FF0000" } } + ] + } + ] + }, + { + "type": "table", "dataKey": "bgaBallsTable", "positioning": "flow", "size": [170, 0], "style": "tableDefault", + "columns": [ + { "header": "${loc:Bga_BallIndex}", "field": "index", "width": 25, "align": "center" }, + { "header": "${loc:Table_VoidRate}", "field": "voidRate", "width": 40, "align": "center" }, + { "header": "${loc:Table_Area}", "field": "area", "width": 40, "align": "center" }, + { "header": "${loc:Table_Classification}", "field": "classification", "width": 35, "align": "center", "colorRules": { "Pass": "#008000", "Fail": "#FF0000" } } + ] + } + ] + }, + { + "type": "voidInspection", + "elements": [ + { "type": "text", "content": "${loc:Page_VoidInspection}", "style": "heading", "positioning": "flow" }, + { + "type": "row", "size": [170, 100], "widths": [6, 4], "positioning": "flow", + "children": [ + { + "type": "column", "align": "left", + "children": [ + { "type": "image", "dataKey": "voidInspectionImage", "size": [110, 90], "border": true, "align": "left" } + ] + }, + { + "type": "column", "align": "left", + "children": [ + { "type": "text", "content": "${loc:Void_RoiArea}:${formatNumber(roiArea, 2)}", "style": "body", "align": "left" }, + { "type": "text", "content": "${loc:Void_TotalArea}:${formatNumber(totalVoidArea, 2)}", "style": "body", "align": "left" }, + { "type": "text", "content": "${loc:Measurement_VoidRate}:${formatPercent(voidRate)}", "style": "body", "align": "left" }, + { "type": "text", "content": "${loc:Void_Limit}:${formatPercent(voidLimit)}", "style": "body", "align": "left" }, + { "type": "text", "content": "${loc:Void_Count}:${voidCount}", "style": "body", "align": "left" }, + { "type": "text", "content": "${loc:Void_MaxArea}:${formatNumber(maxVoidArea, 2)}", "style": "body", "align": "left" }, + { "type": "text", "content": "${loc:Table_Classification}:${classification}", "style": "conclusion", "align": "left", "colorRules": { "Pass": "#008000", "Fail": "#FF0000" } } + ] + } + ] + }, + { + "type": "table", "dataKey": "voidsTable", "positioning": "flow", "size": [170, 0], "style": "tableDefault", + "columns": [ + { "header": "${loc:Table_Index}", "field": "index", "width": 20, "align": "center" }, + { "header": "${loc:Table_Area}", "field": "area", "width": 35, "align": "center" }, + { "header": "${loc:Table_AreaPercent}", "field": "areaPercent", "width": 35, "align": "center" }, + { "header": "${loc:Table_CenterX}", "field": "centerX", "width": 30, "align": "center" }, + { "header": "${loc:Table_CenterY}", "field": "centerY", "width": 30, "align": "center" } + ] + } + ] + }, + { + "type": "viaFillInspection", + "elements": [ + { "type": "text", "content": "${loc:Page_ViaFillInspection}", "style": "heading", "positioning": "flow" }, + { + "type": "row", "size": [170, 100], "widths": [6, 4], "positioning": "flow", + "children": [ + { + "type": "column", "align": "left", + "children": [ + { "type": "image", "dataKey": "viaFillImage", "size": [110, 90], "border": true, "align": "left" } + ] + }, + { + "type": "column", "align": "left", + "children": [ + { "type": "text", "content": "${loc:Fill_Rate}:${formatPercent(fillRate)}", "style": "body", "align": "left" }, + { "type": "text", "content": "${loc:Measurement_VoidRate}:${formatPercent(voidRate)}", "style": "body", "align": "left" }, + { "type": "text", "content": "${loc:Fill_FullDistance}:${formatNumber(fullDistance, 2)}", "style": "body", "align": "left" }, + { "type": "text", "content": "${loc:Fill_FillDistance}:${formatNumber(fillDistance, 2)}", "style": "body", "align": "left" }, + { "type": "text", "content": "${loc:Fill_THTLimit}:${formatPercent(thtLimit)}", "style": "body", "align": "left" }, + { "type": "text", "content": "${loc:Table_Classification}:${classification}", "style": "conclusion", "align": "left", "colorRules": { "Pass": "#008000", "Fail": "#FF0000" } } + ] + } + ] + } + ] + } + ], + "styles": { + "title": { "font": "auto", "size": 28, "bold": true, "italic": false, "color": "#1a1a1a", "align": "center" }, + "companyName": { "font": "auto", "size": 10, "bold": false, "italic": false, "color": "#333333", "align": "center" }, + "homepageInfo": { "font": "auto", "size": 12, "bold": false, "italic": false, "color": "#333333", "align": "left", "paddingLeft": 10 }, + "homepageFooter": { "font": "auto", "size": 12, "bold": false, "italic": false, "color": "#333333", "align": "center" }, + "homepageHeading": { "font": "auto", "size": 14, "bold": true, "italic": false, "color": "#333333", "align": "left" }, + "heading": { "font": "auto", "size": 16, "bold": true, "italic": false, "color": "#333333", "align": "left", "marginBottom": 3 }, + "body": { "font": "auto", "size": 11, "bold": false, "italic": false, "color": "#333333", "align": "left" }, + "bodyBold": { "font": "auto", "size": 11, "bold": true, "italic": false, "color": "#1a1a1a", "align": "left" }, + "conclusion": { "font": "auto", "size": 12, "bold": true, "italic": false, "color": "#333333", "align": "left", "marginTop": 3 }, + "imageDefault": { "font": "auto", "size": 12, "bold": false, "italic": false, "color": "#333333", "align": "center", "marginTop": 5, "marginBottom": 5 }, + "tableHeader": { "font": "auto", "size": 10, "bold": true, "italic": false, "color": "#ffffff", "align": "center", "backgroundColor": "#4472C4" }, + "tableDefault": { "font": "auto", "size": 10, "bold": false, "italic": false, "color": "#333333", "align": "center", "marginTop": 5 }, + "tableCell": { "font": "auto", "size": 10, "bold": false, "italic": false, "color": "#333333", "align": "center" } + } +} diff --git a/ReleaseFiles/UserManual.pdf b/ReleaseFiles/UserManual.pdf new file mode 100644 index 0000000..2e0b69a Binary files /dev/null and b/ReleaseFiles/UserManual.pdf differ diff --git a/ReleaseFiles/XP.Camera.dll b/ReleaseFiles/XP.Camera.dll index 5a53c26..c927ba4 100644 Binary files a/ReleaseFiles/XP.Camera.dll and b/ReleaseFiles/XP.Camera.dll differ diff --git a/ReleaseFiles/XP.Camera.pdb b/ReleaseFiles/XP.Camera.pdb index 6112b4e..728c2e3 100644 Binary files a/ReleaseFiles/XP.Camera.pdb and b/ReleaseFiles/XP.Camera.pdb differ diff --git a/ReleaseFiles/XP.Common.dll b/ReleaseFiles/XP.Common.dll index 7bc5736..a8833d8 100644 Binary files a/ReleaseFiles/XP.Common.dll and b/ReleaseFiles/XP.Common.dll differ diff --git a/ReleaseFiles/XP.Common.pdb b/ReleaseFiles/XP.Common.pdb index 85e8f2e..c0fc2a9 100644 Binary files a/ReleaseFiles/XP.Common.pdb and b/ReleaseFiles/XP.Common.pdb differ diff --git a/ReleaseFiles/XP.Hardware.Detector.dll b/ReleaseFiles/XP.Hardware.Detector.dll index f02c874..9d8a578 100644 Binary files a/ReleaseFiles/XP.Hardware.Detector.dll and b/ReleaseFiles/XP.Hardware.Detector.dll differ diff --git a/ReleaseFiles/XP.Hardware.Detector.pdb b/ReleaseFiles/XP.Hardware.Detector.pdb index 436b074..6b4aa4e 100644 Binary files a/ReleaseFiles/XP.Hardware.Detector.pdb and b/ReleaseFiles/XP.Hardware.Detector.pdb differ diff --git a/ReleaseFiles/XP.Hardware.MotionControl.dll b/ReleaseFiles/XP.Hardware.MotionControl.dll index 2985310..d44058d 100644 Binary files a/ReleaseFiles/XP.Hardware.MotionControl.dll and b/ReleaseFiles/XP.Hardware.MotionControl.dll differ diff --git a/ReleaseFiles/XP.Hardware.MotionControl.pdb b/ReleaseFiles/XP.Hardware.MotionControl.pdb index 104cb14..8f0de88 100644 Binary files a/ReleaseFiles/XP.Hardware.MotionControl.pdb and b/ReleaseFiles/XP.Hardware.MotionControl.pdb differ diff --git a/ReleaseFiles/XP.Hardware.PLC.dll b/ReleaseFiles/XP.Hardware.PLC.dll index 9d3bdcc..6f3d513 100644 Binary files a/ReleaseFiles/XP.Hardware.PLC.dll and b/ReleaseFiles/XP.Hardware.PLC.dll differ diff --git a/ReleaseFiles/XP.Hardware.PLC.pdb b/ReleaseFiles/XP.Hardware.PLC.pdb index 80e5165..4c2e8ae 100644 Binary files a/ReleaseFiles/XP.Hardware.PLC.pdb and b/ReleaseFiles/XP.Hardware.PLC.pdb differ diff --git a/ReleaseFiles/XP.Hardware.RaySource.Comet.Messages.dll b/ReleaseFiles/XP.Hardware.RaySource.Comet.Messages.dll index e542b84..24b0107 100644 Binary files a/ReleaseFiles/XP.Hardware.RaySource.Comet.Messages.dll and b/ReleaseFiles/XP.Hardware.RaySource.Comet.Messages.dll differ diff --git a/ReleaseFiles/XP.Hardware.RaySource.Comet.Messages.pdb b/ReleaseFiles/XP.Hardware.RaySource.Comet.Messages.pdb index f393a13..7fe6daf 100644 Binary files a/ReleaseFiles/XP.Hardware.RaySource.Comet.Messages.pdb and b/ReleaseFiles/XP.Hardware.RaySource.Comet.Messages.pdb differ diff --git a/ReleaseFiles/XP.Hardware.RaySource.dll b/ReleaseFiles/XP.Hardware.RaySource.dll index 40c575b..21e3295 100644 Binary files a/ReleaseFiles/XP.Hardware.RaySource.dll and b/ReleaseFiles/XP.Hardware.RaySource.dll differ diff --git a/ReleaseFiles/XP.Hardware.RaySource.pdb b/ReleaseFiles/XP.Hardware.RaySource.pdb index d1976de..6176218 100644 Binary files a/ReleaseFiles/XP.Hardware.RaySource.pdb and b/ReleaseFiles/XP.Hardware.RaySource.pdb differ diff --git a/ReleaseFiles/XP.ImageProcessing.CfgControl.dll b/ReleaseFiles/XP.ImageProcessing.CfgControl.dll index 5668ff5..5924248 100644 Binary files a/ReleaseFiles/XP.ImageProcessing.CfgControl.dll and b/ReleaseFiles/XP.ImageProcessing.CfgControl.dll differ diff --git a/ReleaseFiles/XP.ImageProcessing.CfgControl.pdb b/ReleaseFiles/XP.ImageProcessing.CfgControl.pdb index 2a6bfa6..9e87d84 100644 Binary files a/ReleaseFiles/XP.ImageProcessing.CfgControl.pdb and b/ReleaseFiles/XP.ImageProcessing.CfgControl.pdb differ diff --git a/ReleaseFiles/XP.ImageProcessing.Core.dll b/ReleaseFiles/XP.ImageProcessing.Core.dll index 80646aa..0af1c02 100644 Binary files a/ReleaseFiles/XP.ImageProcessing.Core.dll and b/ReleaseFiles/XP.ImageProcessing.Core.dll differ diff --git a/ReleaseFiles/XP.ImageProcessing.Core.pdb b/ReleaseFiles/XP.ImageProcessing.Core.pdb index 190e2af..3690f67 100644 Binary files a/ReleaseFiles/XP.ImageProcessing.Core.pdb and b/ReleaseFiles/XP.ImageProcessing.Core.pdb differ diff --git a/ReleaseFiles/XP.ImageProcessing.Processors.dll b/ReleaseFiles/XP.ImageProcessing.Processors.dll index c776aef..645297e 100644 Binary files a/ReleaseFiles/XP.ImageProcessing.Processors.dll and b/ReleaseFiles/XP.ImageProcessing.Processors.dll differ diff --git a/ReleaseFiles/XP.ImageProcessing.Processors.pdb b/ReleaseFiles/XP.ImageProcessing.Processors.pdb index 12ee5d7..7b55029 100644 Binary files a/ReleaseFiles/XP.ImageProcessing.Processors.pdb and b/ReleaseFiles/XP.ImageProcessing.Processors.pdb differ diff --git a/ReleaseFiles/XP.ImageProcessing.RoiControl.dll b/ReleaseFiles/XP.ImageProcessing.RoiControl.dll index ad057b3..d478d53 100644 Binary files a/ReleaseFiles/XP.ImageProcessing.RoiControl.dll and b/ReleaseFiles/XP.ImageProcessing.RoiControl.dll differ diff --git a/ReleaseFiles/XP.ImageProcessing.RoiControl.pdb b/ReleaseFiles/XP.ImageProcessing.RoiControl.pdb index aaf84d2..5441b82 100644 Binary files a/ReleaseFiles/XP.ImageProcessing.RoiControl.pdb and b/ReleaseFiles/XP.ImageProcessing.RoiControl.pdb differ diff --git a/ReleaseFiles/XP.ReportEngine.dll b/ReleaseFiles/XP.ReportEngine.dll new file mode 100644 index 0000000..c3d733d Binary files /dev/null and b/ReleaseFiles/XP.ReportEngine.dll differ diff --git a/ReleaseFiles/XP.ReportEngine.pdb b/ReleaseFiles/XP.ReportEngine.pdb new file mode 100644 index 0000000..ffd6093 Binary files /dev/null and b/ReleaseFiles/XP.ReportEngine.pdb differ diff --git a/ReleaseFiles/XplorePlane.deps.json b/ReleaseFiles/XplorePlane.deps.json index 6d5820f..7583502 100644 --- a/ReleaseFiles/XplorePlane.deps.json +++ b/ReleaseFiles/XplorePlane.deps.json @@ -1,11 +1,12 @@ { "runtimeTarget": { - "name": ".NETCoreApp,Version=v8.0", + "name": ".NETCoreApp,Version=v8.0/win-x64", "signature": "" }, "compilationOptions": {}, "targets": { - ".NETCoreApp,Version=v8.0": { + ".NETCoreApp,Version=v8.0": {}, + ".NETCoreApp,Version=v8.0/win-x64": { "XplorePlane/1.0.0": { "dependencies": { "Emgu.CV": "4.10.0.5680", @@ -29,12 +30,21 @@ "XP.ImageProcessing.Core": "1.0.0", "XP.ImageProcessing.Processors": "1.0.0", "XP.ImageProcessing.RoiControl": "1.0.0", + "XP.ReportEngine": "1.0.0", "BR.AN.PviServices": "1.1.0.0" }, "runtime": { "XplorePlane.dll": {} } }, + "BouncyCastle.Cryptography/2.2.1": { + "runtime": { + "lib/net6.0/BouncyCastle.Cryptography.dll": { + "assemblyVersion": "2.0.0.0", + "fileVersion": "2.2.1.47552" + } + } + }, "ControlzEx/5.0.1": { "dependencies": { "Microsoft.Xaml.Behaviors.Wpf": "1.1.122", @@ -88,171 +98,48 @@ "Emgu.runtime.windows.msvc.rt.x64": "19.42.34435", "Emgu.runtime.windows.msvc.rt.x86": "19.42.34435" }, - "runtimeTargets": { - "runtimes/win-arm64/native/cvextern.dll": { - "rid": "win-arm64", - "assetType": "native", - "fileVersion": "4.10.0.5680" - }, + "native": { "runtimes/win-x64/native/cvextern.dll": { - "rid": "win-x64", - "assetType": "native", "fileVersion": "4.10.0.5680" }, "runtimes/win-x64/native/libusb-1.0.dll": { - "rid": "win-x64", - "assetType": "native", "fileVersion": "0.0.0.0" }, "runtimes/win-x64/native/opencv_videoio_ffmpeg4100_64.dll": { - "rid": "win-x64", - "assetType": "native", - "fileVersion": "2024.5.0.0" - }, - "runtimes/win-x86/native/cvextern.dll": { - "rid": "win-x86", - "assetType": "native", - "fileVersion": "4.10.0.5680" - }, - "runtimes/win-x86/native/libusb-1.0.dll": { - "rid": "win-x86", - "assetType": "native", - "fileVersion": "0.0.0.0" - }, - "runtimes/win-x86/native/opencv_videoio_ffmpeg4100.dll": { - "rid": "win-x86", - "assetType": "native", "fileVersion": "2024.5.0.0" } } }, - "Emgu.runtime.windows.msvc.rt.arm64/19.42.34435": { - "runtimeTargets": { - "runtimes/win-arm64/native/concrt140.dll": { - "rid": "win-arm64", - "assetType": "native", - "fileVersion": "14.42.34433.0" - }, - "runtimes/win-arm64/native/msvcp140.dll": { - "rid": "win-arm64", - "assetType": "native", - "fileVersion": "14.42.34433.0" - }, - "runtimes/win-arm64/native/msvcp140_1.dll": { - "rid": "win-arm64", - "assetType": "native", - "fileVersion": "14.42.34433.0" - }, - "runtimes/win-arm64/native/msvcp140_2.dll": { - "rid": "win-arm64", - "assetType": "native", - "fileVersion": "14.42.34433.0" - }, - "runtimes/win-arm64/native/msvcp140_atomic_wait.dll": { - "rid": "win-arm64", - "assetType": "native", - "fileVersion": "14.42.34433.0" - }, - "runtimes/win-arm64/native/msvcp140_codecvt_ids.dll": { - "rid": "win-arm64", - "assetType": "native", - "fileVersion": "14.42.34433.0" - }, - "runtimes/win-arm64/native/vcruntime140.dll": { - "rid": "win-arm64", - "assetType": "native", - "fileVersion": "14.42.34433.0" - }, - "runtimes/win-arm64/native/vcruntime140_1.dll": { - "rid": "win-arm64", - "assetType": "native", - "fileVersion": "14.42.34433.0" - } - } - }, + "Emgu.runtime.windows.msvc.rt.arm64/19.42.34435": {}, "Emgu.runtime.windows.msvc.rt.x64/19.42.34435": { - "runtimeTargets": { + "native": { "runtimes/win-x64/native/concrt140.dll": { - "rid": "win-x64", - "assetType": "native", "fileVersion": "14.42.34433.0" }, "runtimes/win-x64/native/msvcp140.dll": { - "rid": "win-x64", - "assetType": "native", "fileVersion": "14.42.34433.0" }, "runtimes/win-x64/native/msvcp140_1.dll": { - "rid": "win-x64", - "assetType": "native", "fileVersion": "14.42.34433.0" }, "runtimes/win-x64/native/msvcp140_2.dll": { - "rid": "win-x64", - "assetType": "native", "fileVersion": "14.42.34433.0" }, "runtimes/win-x64/native/msvcp140_atomic_wait.dll": { - "rid": "win-x64", - "assetType": "native", "fileVersion": "14.42.34433.0" }, "runtimes/win-x64/native/msvcp140_codecvt_ids.dll": { - "rid": "win-x64", - "assetType": "native", "fileVersion": "14.42.34433.0" }, "runtimes/win-x64/native/vcruntime140.dll": { - "rid": "win-x64", - "assetType": "native", "fileVersion": "14.42.34433.0" }, "runtimes/win-x64/native/vcruntime140_1.dll": { - "rid": "win-x64", - "assetType": "native", - "fileVersion": "14.42.34433.0" - } - } - }, - "Emgu.runtime.windows.msvc.rt.x86/19.42.34435": { - "runtimeTargets": { - "runtimes/win-x86/native/concrt140.dll": { - "rid": "win-x86", - "assetType": "native", - "fileVersion": "14.42.34433.0" - }, - "runtimes/win-x86/native/msvcp140.dll": { - "rid": "win-x86", - "assetType": "native", - "fileVersion": "14.42.34433.0" - }, - "runtimes/win-x86/native/msvcp140_1.dll": { - "rid": "win-x86", - "assetType": "native", - "fileVersion": "14.42.34433.0" - }, - "runtimes/win-x86/native/msvcp140_2.dll": { - "rid": "win-x86", - "assetType": "native", - "fileVersion": "14.42.34433.0" - }, - "runtimes/win-x86/native/msvcp140_atomic_wait.dll": { - "rid": "win-x86", - "assetType": "native", - "fileVersion": "14.42.34433.0" - }, - "runtimes/win-x86/native/msvcp140_codecvt_ids.dll": { - "rid": "win-x86", - "assetType": "native", - "fileVersion": "14.42.34433.0" - }, - "runtimes/win-x86/native/vcruntime140.dll": { - "rid": "win-x86", - "assetType": "native", "fileVersion": "14.42.34433.0" } } }, + "Emgu.runtime.windows.msvc.rt.x86/19.42.34435": {}, "EntityFramework/6.4.4": { "dependencies": { "Microsoft.CSharp": "4.7.0", @@ -283,546 +170,103 @@ } } }, - "MahApps.Metro/2.4.11": { + "itext/8.0.5": { "dependencies": { - "ControlzEx": "5.0.1" + "Microsoft.DotNet.PlatformAbstractions": "1.1.0", + "Microsoft.Extensions.DependencyModel": "10.0.0", + "Microsoft.Extensions.Logging": "5.0.0", + "System.Collections.NonGeneric": "4.3.0", + "System.Diagnostics.Process": "4.3.0", + "System.Globalization.Extensions": "4.3.0", + "System.Runtime.Loader": "4.3.0", + "System.Runtime.Serialization.Formatters": "4.3.0", + "System.Security.Cryptography.Csp": "4.3.0", + "System.Text.Encoding.CodePages": "4.3.0", + "System.Text.RegularExpressions": "4.3.1", + "System.Threading.Thread": "4.3.0", + "System.Threading.ThreadPool": "4.3.0", + "System.Xml.XmlDocument": "4.3.0", + "itext.commons": "8.0.5" }, "runtime": { - "lib/netcoreapp3.1/MahApps.Metro.dll": { - "assemblyVersion": "2.0.0.0", - "fileVersion": "2.4.11.0" - } - }, - "resources": { - "lib/netcoreapp3.1/de/MahApps.Metro.resources.dll": { - "locale": "de" + "lib/netstandard2.0/itext.barcodes.dll": { + "assemblyVersion": "8.0.5.0", + "fileVersion": "8.0.5.0" + }, + "lib/netstandard2.0/itext.bouncy-castle-connector.dll": { + "assemblyVersion": "8.0.5.0", + "fileVersion": "8.0.5.0" + }, + "lib/netstandard2.0/itext.forms.dll": { + "assemblyVersion": "8.0.5.0", + "fileVersion": "8.0.5.0" + }, + "lib/netstandard2.0/itext.io.dll": { + "assemblyVersion": "8.0.5.0", + "fileVersion": "8.0.5.0" + }, + "lib/netstandard2.0/itext.kernel.dll": { + "assemblyVersion": "8.0.5.0", + "fileVersion": "8.0.5.0" + }, + "lib/netstandard2.0/itext.layout.dll": { + "assemblyVersion": "8.0.5.0", + "fileVersion": "8.0.5.0" + }, + "lib/netstandard2.0/itext.pdfa.dll": { + "assemblyVersion": "8.0.5.0", + "fileVersion": "8.0.5.0" + }, + "lib/netstandard2.0/itext.pdfua.dll": { + "assemblyVersion": "8.0.5.0", + "fileVersion": "8.0.5.0" + }, + "lib/netstandard2.0/itext.sign.dll": { + "assemblyVersion": "8.0.5.0", + "fileVersion": "8.0.5.0" + }, + "lib/netstandard2.0/itext.styledxmlparser.dll": { + "assemblyVersion": "8.0.5.0", + "fileVersion": "8.0.5.0" + }, + "lib/netstandard2.0/itext.svg.dll": { + "assemblyVersion": "8.0.5.0", + "fileVersion": "8.0.5.0" } } }, - "MahApps.Metro.IconPacks/6.2.1": { + "itext.bouncy-castle-adapter/8.0.5": { "dependencies": { - "MahApps.Metro.IconPacks.BootstrapIcons": "6.2.1", - "MahApps.Metro.IconPacks.BoxIcons": "6.2.1", - "MahApps.Metro.IconPacks.BoxIcons2": "6.2.1", - "MahApps.Metro.IconPacks.CircumIcons": "6.2.1", - "MahApps.Metro.IconPacks.Codicons": "6.2.1", - "MahApps.Metro.IconPacks.Coolicons": "6.2.1", - "MahApps.Metro.IconPacks.Core": "6.2.1", - "MahApps.Metro.IconPacks.Entypo": "6.2.1", - "MahApps.Metro.IconPacks.EvaIcons": "6.2.1", - "MahApps.Metro.IconPacks.FeatherIcons": "6.2.1", - "MahApps.Metro.IconPacks.FileIcons": "6.2.1", - "MahApps.Metro.IconPacks.FontAwesome": "6.2.1", - "MahApps.Metro.IconPacks.FontAwesome5": "6.2.1", - "MahApps.Metro.IconPacks.FontAwesome6": "6.2.1", - "MahApps.Metro.IconPacks.Fontaudio": "6.2.1", - "MahApps.Metro.IconPacks.Fontisto": "6.2.1", - "MahApps.Metro.IconPacks.ForkAwesome": "6.2.1", - "MahApps.Metro.IconPacks.GameIcons": "6.2.1", - "MahApps.Metro.IconPacks.Ionicons": "6.2.1", - "MahApps.Metro.IconPacks.JamIcons": "6.2.1", - "MahApps.Metro.IconPacks.KeyruneIcons": "6.2.1", - "MahApps.Metro.IconPacks.Lucide": "6.2.1", - "MahApps.Metro.IconPacks.Material": "6.2.1", - "MahApps.Metro.IconPacks.MaterialDesign": "6.2.1", - "MahApps.Metro.IconPacks.MaterialLight": "6.2.1", - "MahApps.Metro.IconPacks.MemoryIcons": "6.2.1", - "MahApps.Metro.IconPacks.Microns": "6.2.1", - "MahApps.Metro.IconPacks.MingCuteIcons": "6.2.1", - "MahApps.Metro.IconPacks.Modern": "6.2.1", - "MahApps.Metro.IconPacks.MynaUIIcons": "6.2.1", - "MahApps.Metro.IconPacks.Octicons": "6.2.1", - "MahApps.Metro.IconPacks.PhosphorIcons": "6.2.1", - "MahApps.Metro.IconPacks.PicolIcons": "6.2.1", - "MahApps.Metro.IconPacks.PixelartIcons": "6.2.1", - "MahApps.Metro.IconPacks.RPGAwesome": "6.2.1", - "MahApps.Metro.IconPacks.RadixIcons": "6.2.1", - "MahApps.Metro.IconPacks.RemixIcon": "6.2.1", - "MahApps.Metro.IconPacks.SimpleIcons": "6.2.1", - "MahApps.Metro.IconPacks.Typicons": "6.2.1", - "MahApps.Metro.IconPacks.Unicons": "6.2.1", - "MahApps.Metro.IconPacks.VaadinIcons": "6.2.1", - "MahApps.Metro.IconPacks.WeatherIcons": "6.2.1", - "MahApps.Metro.IconPacks.Zondicons": "6.2.1" + "BouncyCastle.Cryptography": "2.2.1", + "itext.commons": "8.0.5" }, "runtime": { - "lib/net8.0-windows7.0/MahApps.Metro.IconPacks.dll": { - "assemblyVersion": "6.0.0.0", - "fileVersion": "6.2.1.0" + "lib/netstandard2.0/itext.bouncy-castle-adapter.dll": { + "assemblyVersion": "8.0.5.0", + "fileVersion": "8.0.5.0" } } }, - "MahApps.Metro.IconPacks.BootstrapIcons/6.2.1": { + "itext.commons/8.0.5": { "dependencies": { - "MahApps.Metro.IconPacks.Core": "6.2.1" + "Microsoft.Extensions.Logging": "5.0.0", + "Newtonsoft.Json": "13.0.3" }, "runtime": { - "lib/net8.0-windows7.0/MahApps.Metro.IconPacks.BootstrapIcons.dll": { - "assemblyVersion": "6.0.0.0", - "fileVersion": "6.2.1.0" + "lib/netstandard2.0/itext.commons.dll": { + "assemblyVersion": "8.0.5.0", + "fileVersion": "8.0.5.0" } } }, - "MahApps.Metro.IconPacks.BoxIcons/6.2.1": { + "itext7/8.0.5": { "dependencies": { - "MahApps.Metro.IconPacks.Core": "6.2.1" - }, - "runtime": { - "lib/net8.0-windows7.0/MahApps.Metro.IconPacks.BoxIcons.dll": { - "assemblyVersion": "6.0.0.0", - "fileVersion": "6.2.1.0" - } + "itext": "8.0.5" } }, - "MahApps.Metro.IconPacks.BoxIcons2/6.2.1": { + "itext7.bouncy-castle-adapter/8.0.5": { "dependencies": { - "MahApps.Metro.IconPacks.Core": "6.2.1" - }, - "runtime": { - "lib/net8.0-windows7.0/MahApps.Metro.IconPacks.BoxIcons2.dll": { - "assemblyVersion": "6.0.0.0", - "fileVersion": "6.2.1.0" - } - } - }, - "MahApps.Metro.IconPacks.CircumIcons/6.2.1": { - "dependencies": { - "MahApps.Metro.IconPacks.Core": "6.2.1" - }, - "runtime": { - "lib/net8.0-windows7.0/MahApps.Metro.IconPacks.CircumIcons.dll": { - "assemblyVersion": "6.0.0.0", - "fileVersion": "6.2.1.0" - } - } - }, - "MahApps.Metro.IconPacks.Codicons/6.2.1": { - "dependencies": { - "MahApps.Metro.IconPacks.Core": "6.2.1" - }, - "runtime": { - "lib/net8.0-windows7.0/MahApps.Metro.IconPacks.Codicons.dll": { - "assemblyVersion": "6.0.0.0", - "fileVersion": "6.2.1.0" - } - } - }, - "MahApps.Metro.IconPacks.Coolicons/6.2.1": { - "dependencies": { - "MahApps.Metro.IconPacks.Core": "6.2.1" - }, - "runtime": { - "lib/net8.0-windows7.0/MahApps.Metro.IconPacks.Coolicons.dll": { - "assemblyVersion": "6.0.0.0", - "fileVersion": "6.2.1.0" - } - } - }, - "MahApps.Metro.IconPacks.Core/6.2.1": { - "dependencies": { - "System.Text.Json": "10.0.0" - }, - "runtime": { - "lib/net8.0-windows7.0/MahApps.Metro.IconPacks.Core.dll": { - "assemblyVersion": "6.0.0.0", - "fileVersion": "6.2.1.0" - } - } - }, - "MahApps.Metro.IconPacks.Entypo/6.2.1": { - "dependencies": { - "MahApps.Metro.IconPacks.Core": "6.2.1" - }, - "runtime": { - "lib/net8.0-windows7.0/MahApps.Metro.IconPacks.Entypo.dll": { - "assemblyVersion": "6.0.0.0", - "fileVersion": "6.2.1.0" - } - } - }, - "MahApps.Metro.IconPacks.EvaIcons/6.2.1": { - "dependencies": { - "MahApps.Metro.IconPacks.Core": "6.2.1" - }, - "runtime": { - "lib/net8.0-windows7.0/MahApps.Metro.IconPacks.EvaIcons.dll": { - "assemblyVersion": "6.0.0.0", - "fileVersion": "6.2.1.0" - } - } - }, - "MahApps.Metro.IconPacks.FeatherIcons/6.2.1": { - "dependencies": { - "MahApps.Metro.IconPacks.Core": "6.2.1" - }, - "runtime": { - "lib/net8.0-windows7.0/MahApps.Metro.IconPacks.FeatherIcons.dll": { - "assemblyVersion": "6.0.0.0", - "fileVersion": "6.2.1.0" - } - } - }, - "MahApps.Metro.IconPacks.FileIcons/6.2.1": { - "dependencies": { - "MahApps.Metro.IconPacks.Core": "6.2.1" - }, - "runtime": { - "lib/net8.0-windows7.0/MahApps.Metro.IconPacks.FileIcons.dll": { - "assemblyVersion": "6.0.0.0", - "fileVersion": "6.2.1.0" - } - } - }, - "MahApps.Metro.IconPacks.Fontaudio/6.2.1": { - "dependencies": { - "MahApps.Metro.IconPacks.Core": "6.2.1" - }, - "runtime": { - "lib/net8.0-windows7.0/MahApps.Metro.IconPacks.Fontaudio.dll": { - "assemblyVersion": "6.0.0.0", - "fileVersion": "6.2.1.0" - } - } - }, - "MahApps.Metro.IconPacks.FontAwesome/6.2.1": { - "dependencies": { - "MahApps.Metro.IconPacks.Core": "6.2.1" - }, - "runtime": { - "lib/net8.0-windows7.0/MahApps.Metro.IconPacks.FontAwesome.dll": { - "assemblyVersion": "6.0.0.0", - "fileVersion": "6.2.1.0" - } - } - }, - "MahApps.Metro.IconPacks.FontAwesome5/6.2.1": { - "dependencies": { - "MahApps.Metro.IconPacks.Core": "6.2.1" - }, - "runtime": { - "lib/net8.0-windows7.0/MahApps.Metro.IconPacks.FontAwesome5.dll": { - "assemblyVersion": "6.0.0.0", - "fileVersion": "6.2.1.0" - } - } - }, - "MahApps.Metro.IconPacks.FontAwesome6/6.2.1": { - "dependencies": { - "MahApps.Metro.IconPacks.Core": "6.2.1" - }, - "runtime": { - "lib/net8.0-windows7.0/MahApps.Metro.IconPacks.FontAwesome6.dll": { - "assemblyVersion": "6.0.0.0", - "fileVersion": "6.2.1.0" - } - } - }, - "MahApps.Metro.IconPacks.Fontisto/6.2.1": { - "dependencies": { - "MahApps.Metro.IconPacks.Core": "6.2.1" - }, - "runtime": { - "lib/net8.0-windows7.0/MahApps.Metro.IconPacks.Fontisto.dll": { - "assemblyVersion": "6.0.0.0", - "fileVersion": "6.2.1.0" - } - } - }, - "MahApps.Metro.IconPacks.ForkAwesome/6.2.1": { - "dependencies": { - "MahApps.Metro.IconPacks.Core": "6.2.1" - }, - "runtime": { - "lib/net8.0-windows7.0/MahApps.Metro.IconPacks.ForkAwesome.dll": { - "assemblyVersion": "6.0.0.0", - "fileVersion": "6.2.1.0" - } - } - }, - "MahApps.Metro.IconPacks.GameIcons/6.2.1": { - "dependencies": { - "MahApps.Metro.IconPacks.Core": "6.2.1" - }, - "runtime": { - "lib/net8.0-windows7.0/MahApps.Metro.IconPacks.GameIcons.dll": { - "assemblyVersion": "6.0.0.0", - "fileVersion": "6.2.1.0" - } - } - }, - "MahApps.Metro.IconPacks.Ionicons/6.2.1": { - "dependencies": { - "MahApps.Metro.IconPacks.Core": "6.2.1" - }, - "runtime": { - "lib/net8.0-windows7.0/MahApps.Metro.IconPacks.Ionicons.dll": { - "assemblyVersion": "6.0.0.0", - "fileVersion": "6.2.1.0" - } - } - }, - "MahApps.Metro.IconPacks.JamIcons/6.2.1": { - "dependencies": { - "MahApps.Metro.IconPacks.Core": "6.2.1" - }, - "runtime": { - "lib/net8.0-windows7.0/MahApps.Metro.IconPacks.JamIcons.dll": { - "assemblyVersion": "6.0.0.0", - "fileVersion": "6.2.1.0" - } - } - }, - "MahApps.Metro.IconPacks.KeyruneIcons/6.2.1": { - "dependencies": { - "MahApps.Metro.IconPacks.Core": "6.2.1" - }, - "runtime": { - "lib/net8.0-windows7.0/MahApps.Metro.IconPacks.KeyruneIcons.dll": { - "assemblyVersion": "6.0.0.0", - "fileVersion": "6.2.1.0" - } - } - }, - "MahApps.Metro.IconPacks.Lucide/6.2.1": { - "dependencies": { - "MahApps.Metro.IconPacks.Core": "6.2.1" - }, - "runtime": { - "lib/net8.0-windows7.0/MahApps.Metro.IconPacks.Lucide.dll": { - "assemblyVersion": "6.0.0.0", - "fileVersion": "6.2.1.0" - } - } - }, - "MahApps.Metro.IconPacks.Material/6.2.1": { - "dependencies": { - "MahApps.Metro.IconPacks.Core": "6.2.1" - }, - "runtime": { - "lib/net8.0-windows7.0/MahApps.Metro.IconPacks.Material.dll": { - "assemblyVersion": "6.0.0.0", - "fileVersion": "6.2.1.0" - } - } - }, - "MahApps.Metro.IconPacks.MaterialDesign/6.2.1": { - "dependencies": { - "MahApps.Metro.IconPacks.Core": "6.2.1" - }, - "runtime": { - "lib/net8.0-windows7.0/MahApps.Metro.IconPacks.MaterialDesign.dll": { - "assemblyVersion": "6.0.0.0", - "fileVersion": "6.2.1.0" - } - } - }, - "MahApps.Metro.IconPacks.MaterialLight/6.2.1": { - "dependencies": { - "MahApps.Metro.IconPacks.Core": "6.2.1" - }, - "runtime": { - "lib/net8.0-windows7.0/MahApps.Metro.IconPacks.MaterialLight.dll": { - "assemblyVersion": "6.0.0.0", - "fileVersion": "6.2.1.0" - } - } - }, - "MahApps.Metro.IconPacks.MemoryIcons/6.2.1": { - "dependencies": { - "MahApps.Metro.IconPacks.Core": "6.2.1" - }, - "runtime": { - "lib/net8.0-windows7.0/MahApps.Metro.IconPacks.MemoryIcons.dll": { - "assemblyVersion": "6.0.0.0", - "fileVersion": "6.2.1.0" - } - } - }, - "MahApps.Metro.IconPacks.Microns/6.2.1": { - "dependencies": { - "MahApps.Metro.IconPacks.Core": "6.2.1" - }, - "runtime": { - "lib/net8.0-windows7.0/MahApps.Metro.IconPacks.Microns.dll": { - "assemblyVersion": "6.0.0.0", - "fileVersion": "6.2.1.0" - } - } - }, - "MahApps.Metro.IconPacks.MingCuteIcons/6.2.1": { - "dependencies": { - "MahApps.Metro.IconPacks.Core": "6.2.1" - }, - "runtime": { - "lib/net8.0-windows7.0/MahApps.Metro.IconPacks.MingCuteIcons.dll": { - "assemblyVersion": "6.0.0.0", - "fileVersion": "6.2.1.0" - } - } - }, - "MahApps.Metro.IconPacks.Modern/6.2.1": { - "dependencies": { - "MahApps.Metro.IconPacks.Core": "6.2.1" - }, - "runtime": { - "lib/net8.0-windows7.0/MahApps.Metro.IconPacks.Modern.dll": { - "assemblyVersion": "6.0.0.0", - "fileVersion": "6.2.1.0" - } - } - }, - "MahApps.Metro.IconPacks.MynaUIIcons/6.2.1": { - "dependencies": { - "MahApps.Metro.IconPacks.Core": "6.2.1" - }, - "runtime": { - "lib/net8.0-windows7.0/MahApps.Metro.IconPacks.MynaUIIcons.dll": { - "assemblyVersion": "6.0.0.0", - "fileVersion": "6.2.1.0" - } - } - }, - "MahApps.Metro.IconPacks.Octicons/6.2.1": { - "dependencies": { - "MahApps.Metro.IconPacks.Core": "6.2.1" - }, - "runtime": { - "lib/net8.0-windows7.0/MahApps.Metro.IconPacks.Octicons.dll": { - "assemblyVersion": "6.0.0.0", - "fileVersion": "6.2.1.0" - } - } - }, - "MahApps.Metro.IconPacks.PhosphorIcons/6.2.1": { - "dependencies": { - "MahApps.Metro.IconPacks.Core": "6.2.1" - }, - "runtime": { - "lib/net8.0-windows7.0/MahApps.Metro.IconPacks.PhosphorIcons.dll": { - "assemblyVersion": "6.0.0.0", - "fileVersion": "6.2.1.0" - } - } - }, - "MahApps.Metro.IconPacks.PicolIcons/6.2.1": { - "dependencies": { - "MahApps.Metro.IconPacks.Core": "6.2.1" - }, - "runtime": { - "lib/net8.0-windows7.0/MahApps.Metro.IconPacks.PicolIcons.dll": { - "assemblyVersion": "6.0.0.0", - "fileVersion": "6.2.1.0" - } - } - }, - "MahApps.Metro.IconPacks.PixelartIcons/6.2.1": { - "dependencies": { - "MahApps.Metro.IconPacks.Core": "6.2.1" - }, - "runtime": { - "lib/net8.0-windows7.0/MahApps.Metro.IconPacks.PixelartIcons.dll": { - "assemblyVersion": "6.0.0.0", - "fileVersion": "6.2.1.0" - } - } - }, - "MahApps.Metro.IconPacks.RadixIcons/6.2.1": { - "dependencies": { - "MahApps.Metro.IconPacks.Core": "6.2.1" - }, - "runtime": { - "lib/net8.0-windows7.0/MahApps.Metro.IconPacks.RadixIcons.dll": { - "assemblyVersion": "6.0.0.0", - "fileVersion": "6.2.1.0" - } - } - }, - "MahApps.Metro.IconPacks.RemixIcon/6.2.1": { - "dependencies": { - "MahApps.Metro.IconPacks.Core": "6.2.1" - }, - "runtime": { - "lib/net8.0-windows7.0/MahApps.Metro.IconPacks.RemixIcon.dll": { - "assemblyVersion": "6.0.0.0", - "fileVersion": "6.2.1.0" - } - } - }, - "MahApps.Metro.IconPacks.RPGAwesome/6.2.1": { - "dependencies": { - "MahApps.Metro.IconPacks.Core": "6.2.1" - }, - "runtime": { - "lib/net8.0-windows7.0/MahApps.Metro.IconPacks.RPGAwesome.dll": { - "assemblyVersion": "6.0.0.0", - "fileVersion": "6.2.1.0" - } - } - }, - "MahApps.Metro.IconPacks.SimpleIcons/6.2.1": { - "dependencies": { - "MahApps.Metro.IconPacks.Core": "6.2.1" - }, - "runtime": { - "lib/net8.0-windows7.0/MahApps.Metro.IconPacks.SimpleIcons.dll": { - "assemblyVersion": "6.0.0.0", - "fileVersion": "6.2.1.0" - } - } - }, - "MahApps.Metro.IconPacks.Typicons/6.2.1": { - "dependencies": { - "MahApps.Metro.IconPacks.Core": "6.2.1" - }, - "runtime": { - "lib/net8.0-windows7.0/MahApps.Metro.IconPacks.Typicons.dll": { - "assemblyVersion": "6.0.0.0", - "fileVersion": "6.2.1.0" - } - } - }, - "MahApps.Metro.IconPacks.Unicons/6.2.1": { - "dependencies": { - "MahApps.Metro.IconPacks.Core": "6.2.1" - }, - "runtime": { - "lib/net8.0-windows7.0/MahApps.Metro.IconPacks.Unicons.dll": { - "assemblyVersion": "6.0.0.0", - "fileVersion": "6.2.1.0" - } - } - }, - "MahApps.Metro.IconPacks.VaadinIcons/6.2.1": { - "dependencies": { - "MahApps.Metro.IconPacks.Core": "6.2.1" - }, - "runtime": { - "lib/net8.0-windows7.0/MahApps.Metro.IconPacks.VaadinIcons.dll": { - "assemblyVersion": "6.0.0.0", - "fileVersion": "6.2.1.0" - } - } - }, - "MahApps.Metro.IconPacks.WeatherIcons/6.2.1": { - "dependencies": { - "MahApps.Metro.IconPacks.Core": "6.2.1" - }, - "runtime": { - "lib/net8.0-windows7.0/MahApps.Metro.IconPacks.WeatherIcons.dll": { - "assemblyVersion": "6.0.0.0", - "fileVersion": "6.2.1.0" - } - } - }, - "MahApps.Metro.IconPacks.Zondicons/6.2.1": { - "dependencies": { - "MahApps.Metro.IconPacks.Core": "6.2.1" - }, - "runtime": { - "lib/net8.0-windows7.0/MahApps.Metro.IconPacks.Zondicons.dll": { - "assemblyVersion": "6.0.0.0", - "fileVersion": "6.2.1.0" - } + "itext.bouncy-castle-adapter": "8.0.5" } }, "Microsoft.Bcl.AsyncInterfaces/1.1.1": { @@ -860,6 +304,24 @@ } } }, + "Microsoft.DotNet.PlatformAbstractions/1.1.0": { + "dependencies": { + "System.AppContext": "4.3.0", + "System.Collections": "4.3.0", + "System.IO": "4.3.0", + "System.IO.FileSystem": "4.3.0", + "System.Reflection.TypeExtensions": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Runtime.InteropServices.RuntimeInformation": "4.3.0" + }, + "runtime": { + "lib/netstandard1.3/Microsoft.DotNet.PlatformAbstractions.dll": { + "assemblyVersion": "1.1.0.0", + "fileVersion": "1.1.0.0" + } + } + }, "Microsoft.EntityFrameworkCore/3.1.5": { "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "1.1.1", @@ -867,8 +329,8 @@ "Microsoft.EntityFrameworkCore.Abstractions": "3.1.5", "Microsoft.EntityFrameworkCore.Analyzers": "3.1.5", "Microsoft.Extensions.Caching.Memory": "3.1.5", - "Microsoft.Extensions.DependencyInjection": "3.1.5", - "Microsoft.Extensions.Logging": "3.1.5", + "Microsoft.Extensions.DependencyInjection": "5.0.0", + "Microsoft.Extensions.Logging": "5.0.0", "System.Collections.Immutable": "1.7.1", "System.ComponentModel.Annotations": "4.7.0", "System.Diagnostics.DiagnosticSource": "4.7.1" @@ -904,8 +366,8 @@ "dependencies": { "Microsoft.Extensions.Caching.Abstractions": "3.1.5", "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.1", - "Microsoft.Extensions.Logging.Abstractions": "3.1.5", - "Microsoft.Extensions.Options": "3.1.5" + "Microsoft.Extensions.Logging.Abstractions": "5.0.0", + "Microsoft.Extensions.Options": "5.0.0" }, "runtime": { "lib/netcoreapp3.1/Microsoft.Extensions.Caching.Memory.dll": { @@ -949,14 +411,14 @@ } } }, - "Microsoft.Extensions.DependencyInjection/3.1.5": { + "Microsoft.Extensions.DependencyInjection/5.0.0": { "dependencies": { "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.1" }, "runtime": { - "lib/netcoreapp3.1/Microsoft.Extensions.DependencyInjection.dll": { - "assemblyVersion": "3.1.5.0", - "fileVersion": "3.100.520.27009" + "lib/net5.0/Microsoft.Extensions.DependencyInjection.dll": { + "assemblyVersion": "5.0.0.0", + "fileVersion": "5.0.20.51904" } } }, @@ -980,37 +442,37 @@ } } }, - "Microsoft.Extensions.Logging/3.1.5": { + "Microsoft.Extensions.Logging/5.0.0": { "dependencies": { - "Microsoft.Extensions.Configuration.Binder": "10.0.0", - "Microsoft.Extensions.DependencyInjection": "3.1.5", - "Microsoft.Extensions.Logging.Abstractions": "3.1.5", - "Microsoft.Extensions.Options": "3.1.5" + "Microsoft.Extensions.DependencyInjection": "5.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.1", + "Microsoft.Extensions.Logging.Abstractions": "5.0.0", + "Microsoft.Extensions.Options": "5.0.0" }, "runtime": { - "lib/netcoreapp3.1/Microsoft.Extensions.Logging.dll": { - "assemblyVersion": "3.1.5.0", - "fileVersion": "3.100.520.27009" + "lib/netstandard2.1/Microsoft.Extensions.Logging.dll": { + "assemblyVersion": "5.0.0.0", + "fileVersion": "5.0.20.51904" } } }, - "Microsoft.Extensions.Logging.Abstractions/3.1.5": { + "Microsoft.Extensions.Logging.Abstractions/5.0.0": { "runtime": { "lib/netstandard2.0/Microsoft.Extensions.Logging.Abstractions.dll": { - "assemblyVersion": "3.1.5.0", - "fileVersion": "3.100.520.27009" + "assemblyVersion": "5.0.0.0", + "fileVersion": "5.0.20.51904" } } }, - "Microsoft.Extensions.Options/3.1.5": { + "Microsoft.Extensions.Options/5.0.0": { "dependencies": { "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.1", "Microsoft.Extensions.Primitives": "10.0.0" }, "runtime": { - "lib/netcoreapp3.1/Microsoft.Extensions.Options.dll": { - "assemblyVersion": "3.1.5.0", - "fileVersion": "3.100.520.27009" + "lib/net5.0/Microsoft.Extensions.Options.dll": { + "assemblyVersion": "5.0.0.0", + "fileVersion": "5.0.20.51904" } } }, @@ -1032,75 +494,11 @@ "Microsoft.ML.OnnxRuntime.Gpu.Linux/1.20.1": { "dependencies": { "Microsoft.ML.OnnxRuntime.Managed": "1.20.1" - }, - "runtimeTargets": { - "runtimes/linux-x64/native/libonnxruntime.so": { - "rid": "linux-x64", - "assetType": "native", - "fileVersion": "0.0.0.0" - }, - "runtimes/linux-x64/native/libonnxruntime_providers_cuda.so": { - "rid": "linux-x64", - "assetType": "native", - "fileVersion": "0.0.0.0" - }, - "runtimes/linux-x64/native/libonnxruntime_providers_shared.so": { - "rid": "linux-x64", - "assetType": "native", - "fileVersion": "0.0.0.0" - }, - "runtimes/linux-x64/native/libonnxruntime_providers_tensorrt.so": { - "rid": "linux-x64", - "assetType": "native", - "fileVersion": "0.0.0.0" - } } }, "Microsoft.ML.OnnxRuntime.Gpu.Windows/1.20.1": { "dependencies": { "Microsoft.ML.OnnxRuntime.Managed": "1.20.1" - }, - "runtimeTargets": { - "runtimes/win-x64/native/onnxruntime.dll": { - "rid": "win-x64", - "assetType": "native", - "fileVersion": "1.20.24.1119" - }, - "runtimes/win-x64/native/onnxruntime.lib": { - "rid": "win-x64", - "assetType": "native", - "fileVersion": "0.0.0.0" - }, - "runtimes/win-x64/native/onnxruntime_providers_cuda.dll": { - "rid": "win-x64", - "assetType": "native", - "fileVersion": "0.0.0.0" - }, - "runtimes/win-x64/native/onnxruntime_providers_cuda.lib": { - "rid": "win-x64", - "assetType": "native", - "fileVersion": "0.0.0.0" - }, - "runtimes/win-x64/native/onnxruntime_providers_shared.dll": { - "rid": "win-x64", - "assetType": "native", - "fileVersion": "1.20.24.1119" - }, - "runtimes/win-x64/native/onnxruntime_providers_shared.lib": { - "rid": "win-x64", - "assetType": "native", - "fileVersion": "0.0.0.0" - }, - "runtimes/win-x64/native/onnxruntime_providers_tensorrt.dll": { - "rid": "win-x64", - "assetType": "native", - "fileVersion": "0.0.0.0" - }, - "runtimes/win-x64/native/onnxruntime_providers_tensorrt.lib": { - "rid": "win-x64", - "assetType": "native", - "fileVersion": "0.0.0.0" - } } }, "Microsoft.ML.OnnxRuntime.Managed/1.20.1": { @@ -1162,7 +560,8 @@ "dependencies": { "Microsoft.NETCore.Platforms": "3.1.0", "Microsoft.NETCore.Targets": "1.1.3", - "System.Runtime": "4.3.1" + "System.Runtime": "4.3.1", + "runtime.win.Microsoft.Win32.Primitives": "4.3.0" } }, "Microsoft.Win32.Registry/4.7.0": { @@ -1173,15 +572,7 @@ }, "Microsoft.Win32.SystemEvents/9.0.0": { "runtime": { - "lib/net8.0/Microsoft.Win32.SystemEvents.dll": { - "assemblyVersion": "9.0.0.0", - "fileVersion": "9.0.24.52809" - } - }, - "runtimeTargets": { "runtimes/win/lib/net8.0/Microsoft.Win32.SystemEvents.dll": { - "rid": "win", - "assetType": "runtime", "assemblyVersion": "9.0.0.0", "fileVersion": "9.0.24.52809" } @@ -1235,7 +626,7 @@ "System.Security.Cryptography.X509Certificates": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Text.Encoding.Extensions": "4.3.0", - "System.Text.RegularExpressions": "4.3.0", + "System.Text.RegularExpressions": "4.3.1", "System.Threading": "4.3.0", "System.Threading.Tasks": "4.3.0", "System.Threading.Timer": "4.3.0", @@ -1318,6 +709,31 @@ } } }, + "runtime.any.System.Collections/4.3.0": { + "dependencies": { + "System.Runtime": "4.3.1" + } + }, + "runtime.any.System.Diagnostics.Tools/4.3.0": {}, + "runtime.any.System.Diagnostics.Tracing/4.3.0": {}, + "runtime.any.System.Globalization/4.3.0": {}, + "runtime.any.System.Globalization.Calendars/4.3.0": {}, + "runtime.any.System.IO/4.3.0": {}, + "runtime.any.System.Reflection/4.3.0": {}, + "runtime.any.System.Reflection.Extensions/4.3.0": {}, + "runtime.any.System.Reflection.Primitives/4.3.0": {}, + "runtime.any.System.Resources.ResourceManager/4.3.0": {}, + "runtime.any.System.Runtime/4.3.0": { + "dependencies": { + "System.Private.Uri": "4.3.0" + } + }, + "runtime.any.System.Runtime.Handles/4.3.0": {}, + "runtime.any.System.Runtime.InteropServices/4.3.0": {}, + "runtime.any.System.Text.Encoding/4.3.0": {}, + "runtime.any.System.Text.Encoding.Extensions/4.3.0": {}, + "runtime.any.System.Threading.Tasks/4.3.0": {}, + "runtime.any.System.Threading.Timer/4.3.0": {}, "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, @@ -1373,31 +789,93 @@ "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.win-arm64.runtime.native.System.Data.SqlClient.sni/4.4.0": { - "runtimeTargets": { - "runtimes/win-arm64/native/sni.dll": { - "rid": "win-arm64", - "assetType": "native", - "fileVersion": "4.6.25512.1" - } - } - }, + "runtime.win-arm64.runtime.native.System.Data.SqlClient.sni/4.4.0": {}, "runtime.win-x64.runtime.native.System.Data.SqlClient.sni/4.4.0": { - "runtimeTargets": { + "native": { "runtimes/win-x64/native/sni.dll": { - "rid": "win-x64", - "assetType": "native", "fileVersion": "4.6.25512.1" } } }, - "runtime.win-x86.runtime.native.System.Data.SqlClient.sni/4.4.0": { - "runtimeTargets": { - "runtimes/win-x86/native/sni.dll": { - "rid": "win-x86", - "assetType": "native", - "fileVersion": "4.6.25512.1" - } + "runtime.win-x86.runtime.native.System.Data.SqlClient.sni/4.4.0": {}, + "runtime.win.Microsoft.Win32.Primitives/4.3.0": { + "dependencies": { + "System.Runtime": "4.3.1", + "System.Runtime.InteropServices": "4.3.0" + } + }, + "runtime.win.System.Console/4.3.1": { + "dependencies": { + "System.IO": "4.3.0", + "System.IO.FileSystem.Primitives": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.1", + "System.Runtime.InteropServices": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Text.Encoding.Extensions": "4.3.0", + "System.Threading": "4.3.0", + "System.Threading.Tasks": "4.3.0" + } + }, + "runtime.win.System.Diagnostics.Debug/4.3.0": {}, + "runtime.win.System.IO.FileSystem/4.3.0": { + "dependencies": { + "System.Buffers": "4.3.0", + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.IO": "4.3.0", + "System.IO.FileSystem.Primitives": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.1", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Text.Encoding.Extensions": "4.3.0", + "System.Threading": "4.3.0", + "System.Threading.Overlapped": "4.3.0", + "System.Threading.Tasks": "4.3.0" + } + }, + "runtime.win.System.Net.Primitives/4.3.0": { + "dependencies": { + "Microsoft.Win32.Primitives": "4.3.0", + "System.Collections": "4.3.0", + "System.Diagnostics.Tracing": "4.3.0", + "System.Globalization": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.1", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Threading": "4.3.0" + } + }, + "runtime.win.System.Net.Sockets/4.3.0": { + "dependencies": { + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Diagnostics.Tracing": "4.3.0", + "System.Globalization": "4.3.0", + "System.IO": "4.3.0", + "System.IO.FileSystem": "4.3.0", + "System.IO.FileSystem.Primitives": "4.3.0", + "System.Net.NameResolution": "4.3.0", + "System.Net.Primitives": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.1", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Security.Principal.Windows": "4.7.0", + "System.Threading": "4.3.0", + "System.Threading.Overlapped": "4.3.0", + "System.Threading.Tasks": "4.3.0" + } + }, + "runtime.win.System.Runtime.Extensions/4.3.0": { + "dependencies": { + "System.Private.Uri": "4.3.0" } }, "Serilog/4.3.1": { @@ -1564,120 +1042,8 @@ } }, "SQLitePCLRaw.lib.e_sqlite3/2.1.11": { - "runtimeTargets": { - "runtimes/browser-wasm/nativeassets/net8.0/e_sqlite3.a": { - "rid": "browser-wasm", - "assetType": "native", - "fileVersion": "0.0.0.0" - }, - "runtimes/linux-arm/native/libe_sqlite3.so": { - "rid": "linux-arm", - "assetType": "native", - "fileVersion": "0.0.0.0" - }, - "runtimes/linux-arm64/native/libe_sqlite3.so": { - "rid": "linux-arm64", - "assetType": "native", - "fileVersion": "0.0.0.0" - }, - "runtimes/linux-armel/native/libe_sqlite3.so": { - "rid": "linux-armel", - "assetType": "native", - "fileVersion": "0.0.0.0" - }, - "runtimes/linux-mips64/native/libe_sqlite3.so": { - "rid": "linux-mips64", - "assetType": "native", - "fileVersion": "0.0.0.0" - }, - "runtimes/linux-musl-arm/native/libe_sqlite3.so": { - "rid": "linux-musl-arm", - "assetType": "native", - "fileVersion": "0.0.0.0" - }, - "runtimes/linux-musl-arm64/native/libe_sqlite3.so": { - "rid": "linux-musl-arm64", - "assetType": "native", - "fileVersion": "0.0.0.0" - }, - "runtimes/linux-musl-riscv64/native/libe_sqlite3.so": { - "rid": "linux-musl-riscv64", - "assetType": "native", - "fileVersion": "0.0.0.0" - }, - "runtimes/linux-musl-s390x/native/libe_sqlite3.so": { - "rid": "linux-musl-s390x", - "assetType": "native", - "fileVersion": "0.0.0.0" - }, - "runtimes/linux-musl-x64/native/libe_sqlite3.so": { - "rid": "linux-musl-x64", - "assetType": "native", - "fileVersion": "0.0.0.0" - }, - "runtimes/linux-ppc64le/native/libe_sqlite3.so": { - "rid": "linux-ppc64le", - "assetType": "native", - "fileVersion": "0.0.0.0" - }, - "runtimes/linux-riscv64/native/libe_sqlite3.so": { - "rid": "linux-riscv64", - "assetType": "native", - "fileVersion": "0.0.0.0" - }, - "runtimes/linux-s390x/native/libe_sqlite3.so": { - "rid": "linux-s390x", - "assetType": "native", - "fileVersion": "0.0.0.0" - }, - "runtimes/linux-x64/native/libe_sqlite3.so": { - "rid": "linux-x64", - "assetType": "native", - "fileVersion": "0.0.0.0" - }, - "runtimes/linux-x86/native/libe_sqlite3.so": { - "rid": "linux-x86", - "assetType": "native", - "fileVersion": "0.0.0.0" - }, - "runtimes/maccatalyst-arm64/native/libe_sqlite3.dylib": { - "rid": "maccatalyst-arm64", - "assetType": "native", - "fileVersion": "0.0.0.0" - }, - "runtimes/maccatalyst-x64/native/libe_sqlite3.dylib": { - "rid": "maccatalyst-x64", - "assetType": "native", - "fileVersion": "0.0.0.0" - }, - "runtimes/osx-arm64/native/libe_sqlite3.dylib": { - "rid": "osx-arm64", - "assetType": "native", - "fileVersion": "0.0.0.0" - }, - "runtimes/osx-x64/native/libe_sqlite3.dylib": { - "rid": "osx-x64", - "assetType": "native", - "fileVersion": "0.0.0.0" - }, - "runtimes/win-arm/native/e_sqlite3.dll": { - "rid": "win-arm", - "assetType": "native", - "fileVersion": "0.0.0.0" - }, - "runtimes/win-arm64/native/e_sqlite3.dll": { - "rid": "win-arm64", - "assetType": "native", - "fileVersion": "0.0.0.0" - }, + "native": { "runtimes/win-x64/native/e_sqlite3.dll": { - "rid": "win-x64", - "assetType": "native", - "fileVersion": "0.0.0.0" - }, - "runtimes/win-x86/native/e_sqlite3.dll": { - "rid": "win-x86", - "assetType": "native", "fileVersion": "0.0.0.0" } } @@ -1700,25 +1066,8 @@ "fileVersion": "1.0.118.0" } }, - "runtimeTargets": { - "runtimes/linux-x64/native/SQLite.Interop.dll": { - "rid": "linux-x64", - "assetType": "native", - "fileVersion": "0.0.0.0" - }, - "runtimes/osx-x64/native/SQLite.Interop.dll": { - "rid": "osx-x64", - "assetType": "native", - "fileVersion": "0.0.0.0" - }, + "native": { "runtimes/win-x64/native/SQLite.Interop.dll": { - "rid": "win-x64", - "assetType": "native", - "fileVersion": "1.0.118.0" - }, - "runtimes/win-x86/native/SQLite.Interop.dll": { - "rid": "win-x86", - "assetType": "native", "fileVersion": "1.0.118.0" } } @@ -1742,7 +1091,8 @@ "dependencies": { "Microsoft.NETCore.Platforms": "3.1.0", "Microsoft.NETCore.Targets": "1.1.3", - "System.Runtime": "4.3.1" + "System.Runtime": "4.3.1", + "runtime.any.System.Collections": "4.3.0" } }, "System.Collections.Concurrent/4.3.0": { @@ -1760,17 +1110,21 @@ } }, "System.Collections.Immutable/1.7.1": {}, + "System.Collections.NonGeneric/4.3.0": { + "dependencies": { + "System.Diagnostics.Debug": "4.3.0", + "System.Globalization": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.1", + "System.Runtime.Extensions": "4.3.0", + "System.Threading": "4.3.0" + } + }, "System.ComponentModel.Annotations/4.7.0": {}, "System.Configuration.ConfigurationManager/8.0.0": { "dependencies": { "System.Diagnostics.EventLog": "8.0.0", "System.Security.Cryptography.ProtectedData": "8.0.0" - }, - "runtime": { - "lib/net8.0/System.Configuration.ConfigurationManager.dll": { - "assemblyVersion": "8.0.0.0", - "fileVersion": "8.0.23.53103" - } } }, "System.Console/4.3.0": { @@ -1779,7 +1133,8 @@ "Microsoft.NETCore.Targets": "1.1.3", "System.IO": "4.3.0", "System.Runtime": "4.3.1", - "System.Text.Encoding": "4.3.0" + "System.Text.Encoding": "4.3.0", + "runtime.win.System.Console": "4.3.1" } }, "System.Data.OleDb/6.0.0": { @@ -1788,15 +1143,7 @@ "System.Diagnostics.PerformanceCounter": "6.0.0" }, "runtime": { - "lib/net6.0/System.Data.OleDb.dll": { - "assemblyVersion": "6.0.0.0", - "fileVersion": "6.0.21.52210" - } - }, - "runtimeTargets": { "runtimes/win/lib/net6.0/System.Data.OleDb.dll": { - "rid": "win", - "assetType": "runtime", "assemblyVersion": "6.0.0.0", "fileVersion": "6.0.21.52210" } @@ -1809,21 +1156,7 @@ "runtime.native.System.Data.SqlClient.sni": "4.7.0" }, "runtime": { - "lib/netcoreapp2.1/System.Data.SqlClient.dll": { - "assemblyVersion": "4.6.1.1", - "fileVersion": "4.700.20.6702" - } - }, - "runtimeTargets": { - "runtimes/unix/lib/netcoreapp2.1/System.Data.SqlClient.dll": { - "rid": "unix", - "assetType": "runtime", - "assemblyVersion": "4.6.1.1", - "fileVersion": "4.700.20.6702" - }, "runtimes/win/lib/netcoreapp2.1/System.Data.SqlClient.dll": { - "rid": "win", - "assetType": "runtime", "assemblyVersion": "4.6.1.1", "fileVersion": "4.700.20.6702" } @@ -1855,43 +1188,56 @@ "dependencies": { "Microsoft.NETCore.Platforms": "3.1.0", "Microsoft.NETCore.Targets": "1.1.3", - "System.Runtime": "4.3.1" + "System.Runtime": "4.3.1", + "runtime.win.System.Diagnostics.Debug": "4.3.0" } }, "System.Diagnostics.DiagnosticSource/4.7.1": {}, - "System.Diagnostics.EventLog/8.0.0": { - "runtime": { - "lib/net8.0/System.Diagnostics.EventLog.dll": { - "assemblyVersion": "8.0.0.0", - "fileVersion": "8.0.23.53103" - } - }, - "runtimeTargets": { - "runtimes/win/lib/net8.0/System.Diagnostics.EventLog.dll": { - "rid": "win", - "assetType": "runtime", - "assemblyVersion": "8.0.0.0", - "fileVersion": "8.0.23.53103" - } - } - }, + "System.Diagnostics.EventLog/8.0.0": {}, "System.Diagnostics.PerformanceCounter/6.0.0": { "dependencies": { "System.Configuration.ConfigurationManager": "8.0.0" } }, + "System.Diagnostics.Process/4.3.0": { + "dependencies": { + "Microsoft.NETCore.Platforms": "3.1.0", + "Microsoft.Win32.Primitives": "4.3.0", + "Microsoft.Win32.Registry": "4.7.0", + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Globalization": "4.3.0", + "System.IO": "4.3.0", + "System.IO.FileSystem": "4.3.0", + "System.IO.FileSystem.Primitives": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.1", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Text.Encoding.Extensions": "4.3.0", + "System.Threading": "4.3.0", + "System.Threading.Tasks": "4.3.0", + "System.Threading.Thread": "4.3.0", + "System.Threading.ThreadPool": "4.3.0", + "runtime.native.System": "4.3.0" + } + }, "System.Diagnostics.Tools/4.3.0": { "dependencies": { "Microsoft.NETCore.Platforms": "3.1.0", "Microsoft.NETCore.Targets": "1.1.3", - "System.Runtime": "4.3.1" + "System.Runtime": "4.3.1", + "runtime.any.System.Diagnostics.Tools": "4.3.0" } }, "System.Diagnostics.Tracing/4.3.0": { "dependencies": { "Microsoft.NETCore.Platforms": "3.1.0", "Microsoft.NETCore.Targets": "1.1.3", - "System.Runtime": "4.3.1" + "System.Runtime": "4.3.1", + "runtime.any.System.Diagnostics.Tracing": "4.3.0" } }, "System.Drawing.Common/9.0.0": { @@ -1919,7 +1265,8 @@ "dependencies": { "Microsoft.NETCore.Platforms": "3.1.0", "Microsoft.NETCore.Targets": "1.1.3", - "System.Runtime": "4.3.1" + "System.Runtime": "4.3.1", + "runtime.any.System.Globalization": "4.3.0" } }, "System.Globalization.Calendars/4.3.0": { @@ -1927,7 +1274,8 @@ "Microsoft.NETCore.Platforms": "3.1.0", "Microsoft.NETCore.Targets": "1.1.3", "System.Globalization": "4.3.0", - "System.Runtime": "4.3.1" + "System.Runtime": "4.3.1", + "runtime.any.System.Globalization.Calendars": "4.3.0" } }, "System.Globalization.Extensions/4.3.0": { @@ -1946,7 +1294,8 @@ "Microsoft.NETCore.Targets": "1.1.3", "System.Runtime": "4.3.1", "System.Text.Encoding": "4.3.0", - "System.Threading.Tasks": "4.3.0" + "System.Threading.Tasks": "4.3.0", + "runtime.any.System.IO": "4.3.0" } }, "System.IO.Compression/4.3.0": { @@ -1990,7 +1339,8 @@ "System.Runtime": "4.3.1", "System.Runtime.Handles": "4.3.0", "System.Text.Encoding": "4.3.0", - "System.Threading.Tasks": "4.3.0" + "System.Threading.Tasks": "4.3.0", + "runtime.win.System.IO.FileSystem": "4.3.0" } }, "System.IO.FileSystem.Primitives/4.3.0": { @@ -2067,12 +1417,31 @@ "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" } }, + "System.Net.NameResolution/4.3.0": { + "dependencies": { + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Collections": "4.3.0", + "System.Diagnostics.Tracing": "4.3.0", + "System.Globalization": "4.3.0", + "System.Net.Primitives": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.1", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Security.Principal.Windows": "4.7.0", + "System.Threading": "4.3.0", + "System.Threading.Tasks": "4.3.0", + "runtime.native.System": "4.3.0" + } + }, "System.Net.Primitives/4.3.0": { "dependencies": { "Microsoft.NETCore.Platforms": "3.1.0", "Microsoft.NETCore.Targets": "1.1.3", "System.Runtime": "4.3.1", - "System.Runtime.Handles": "4.3.0" + "System.Runtime.Handles": "4.3.0", + "runtime.win.System.Net.Primitives": "4.3.0" } }, "System.Net.Sockets/4.3.0": { @@ -2082,7 +1451,8 @@ "System.IO": "4.3.0", "System.Net.Primitives": "4.3.0", "System.Runtime": "4.3.1", - "System.Threading.Tasks": "4.3.0" + "System.Threading.Tasks": "4.3.0", + "runtime.win.System.Net.Sockets": "4.3.0" } }, "System.ObjectModel/4.3.0": { @@ -2107,13 +1477,20 @@ } } }, + "System.Private.Uri/4.3.0": { + "dependencies": { + "Microsoft.NETCore.Platforms": "3.1.0", + "Microsoft.NETCore.Targets": "1.1.3" + } + }, "System.Reflection/4.3.0": { "dependencies": { "Microsoft.NETCore.Platforms": "3.1.0", "Microsoft.NETCore.Targets": "1.1.3", "System.IO": "4.3.0", "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.1" + "System.Runtime": "4.3.1", + "runtime.any.System.Reflection": "4.3.0" } }, "System.Reflection.DispatchProxy/4.5.0": {}, @@ -2146,14 +1523,16 @@ "Microsoft.NETCore.Platforms": "3.1.0", "Microsoft.NETCore.Targets": "1.1.3", "System.Reflection": "4.3.0", - "System.Runtime": "4.3.1" + "System.Runtime": "4.3.1", + "runtime.any.System.Reflection.Extensions": "4.3.0" } }, "System.Reflection.Primitives/4.3.0": { "dependencies": { "Microsoft.NETCore.Platforms": "3.1.0", "Microsoft.NETCore.Targets": "1.1.3", - "System.Runtime": "4.3.1" + "System.Runtime": "4.3.1", + "runtime.any.System.Reflection.Primitives": "4.3.0" } }, "System.Reflection.TypeExtensions/4.3.0": { @@ -2168,27 +1547,31 @@ "Microsoft.NETCore.Targets": "1.1.3", "System.Globalization": "4.3.0", "System.Reflection": "4.3.0", - "System.Runtime": "4.3.1" + "System.Runtime": "4.3.1", + "runtime.any.System.Resources.ResourceManager": "4.3.0" } }, "System.Runtime/4.3.1": { "dependencies": { "Microsoft.NETCore.Platforms": "3.1.0", - "Microsoft.NETCore.Targets": "1.1.3" + "Microsoft.NETCore.Targets": "1.1.3", + "runtime.any.System.Runtime": "4.3.0" } }, "System.Runtime.Extensions/4.3.0": { "dependencies": { "Microsoft.NETCore.Platforms": "3.1.0", "Microsoft.NETCore.Targets": "1.1.3", - "System.Runtime": "4.3.1" + "System.Runtime": "4.3.1", + "runtime.win.System.Runtime.Extensions": "4.3.0" } }, "System.Runtime.Handles/4.3.0": { "dependencies": { "Microsoft.NETCore.Platforms": "3.1.0", "Microsoft.NETCore.Targets": "1.1.3", - "System.Runtime": "4.3.1" + "System.Runtime": "4.3.1", + "runtime.any.System.Runtime.Handles": "4.3.0" } }, "System.Runtime.InteropServices/4.3.0": { @@ -2198,7 +1581,8 @@ "System.Reflection": "4.3.0", "System.Reflection.Primitives": "4.3.0", "System.Runtime": "4.3.1", - "System.Runtime.Handles": "4.3.0" + "System.Runtime.Handles": "4.3.0", + "runtime.any.System.Runtime.InteropServices": "4.3.0" } }, "System.Runtime.InteropServices.RuntimeInformation/4.3.0": { @@ -2212,6 +1596,13 @@ "runtime.native.System": "4.3.0" } }, + "System.Runtime.Loader/4.3.0": { + "dependencies": { + "System.IO": "4.3.0", + "System.Reflection": "4.3.0", + "System.Runtime": "4.3.1" + } + }, "System.Runtime.Numerics/4.3.0": { "dependencies": { "System.Globalization": "4.3.0", @@ -2220,6 +1611,21 @@ "System.Runtime.Extensions": "4.3.0" } }, + "System.Runtime.Serialization.Formatters/4.3.0": { + "dependencies": { + "System.Collections": "4.3.0", + "System.Reflection": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.1", + "System.Runtime.Serialization.Primitives": "4.3.0" + } + }, + "System.Runtime.Serialization.Primitives/4.3.0": { + "dependencies": { + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.1" + } + }, "System.Security.AccessControl/4.7.0": { "dependencies": { "Microsoft.NETCore.Platforms": "3.1.0", @@ -2311,14 +1717,7 @@ "System.Threading.Tasks": "4.3.0" } }, - "System.Security.Cryptography.ProtectedData/8.0.0": { - "runtime": { - "lib/net8.0/System.Security.Cryptography.ProtectedData.dll": { - "assemblyVersion": "8.0.0.0", - "fileVersion": "8.0.23.53103" - } - } - }, + "System.Security.Cryptography.ProtectedData/8.0.0": {}, "System.Security.Cryptography.X509Certificates/4.3.0": { "dependencies": { "Microsoft.NETCore.Platforms": "3.1.0", @@ -2391,7 +1790,24 @@ "dependencies": { "Microsoft.NETCore.Platforms": "3.1.0", "Microsoft.NETCore.Targets": "1.1.3", - "System.Runtime": "4.3.1" + "System.Runtime": "4.3.1", + "runtime.any.System.Text.Encoding": "4.3.0" + } + }, + "System.Text.Encoding.CodePages/4.3.0": { + "dependencies": { + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Collections": "4.3.0", + "System.Globalization": "4.3.0", + "System.IO": "4.3.0", + "System.Reflection": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.1", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading": "4.3.0" } }, "System.Text.Encoding.Extensions/4.3.0": { @@ -2399,7 +1815,8 @@ "Microsoft.NETCore.Platforms": "3.1.0", "Microsoft.NETCore.Targets": "1.1.3", "System.Runtime": "4.3.1", - "System.Text.Encoding": "4.3.0" + "System.Text.Encoding": "4.3.0", + "runtime.any.System.Text.Encoding.Extensions": "4.3.0" } }, "System.Text.Encodings.Web/10.0.0": { @@ -2408,14 +1825,6 @@ "assemblyVersion": "10.0.0.0", "fileVersion": "10.0.25.52411" } - }, - "runtimeTargets": { - "runtimes/browser/lib/net8.0/System.Text.Encodings.Web.dll": { - "rid": "browser", - "assetType": "runtime", - "assemblyVersion": "10.0.0.0", - "fileVersion": "10.0.25.52411" - } } }, "System.Text.Json/10.0.0": { @@ -2430,7 +1839,7 @@ } } }, - "System.Text.RegularExpressions/4.3.0": { + "System.Text.RegularExpressions/4.3.1": { "dependencies": { "System.Runtime": "4.3.1" } @@ -2441,11 +1850,20 @@ "System.Threading.Tasks": "4.3.0" } }, + "System.Threading.Overlapped/4.3.0": { + "dependencies": { + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.1", + "System.Runtime.Handles": "4.3.0" + } + }, "System.Threading.Tasks/4.3.0": { "dependencies": { "Microsoft.NETCore.Platforms": "3.1.0", "Microsoft.NETCore.Targets": "1.1.3", - "System.Runtime": "4.3.1" + "System.Runtime": "4.3.1", + "runtime.any.System.Threading.Tasks": "4.3.0" } }, "System.Threading.Tasks.Extensions/4.3.0": { @@ -2455,11 +1873,23 @@ "System.Threading.Tasks": "4.3.0" } }, + "System.Threading.Thread/4.3.0": { + "dependencies": { + "System.Runtime": "4.3.1" + } + }, + "System.Threading.ThreadPool/4.3.0": { + "dependencies": { + "System.Runtime": "4.3.1", + "System.Runtime.Handles": "4.3.0" + } + }, "System.Threading.Timer/4.3.0": { "dependencies": { "Microsoft.NETCore.Platforms": "3.1.0", "Microsoft.NETCore.Targets": "1.1.3", - "System.Runtime": "4.3.1" + "System.Runtime": "4.3.1", + "runtime.any.System.Threading.Timer": "4.3.0" } }, "System.Xml.ReaderWriter/4.3.0": { @@ -2476,7 +1906,7 @@ "System.Runtime.InteropServices": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Text.Encoding.Extensions": "4.3.0", - "System.Text.RegularExpressions": "4.3.0", + "System.Text.RegularExpressions": "4.3.1", "System.Threading.Tasks": "4.3.0", "System.Threading.Tasks.Extensions": "4.3.0" } @@ -2497,6 +1927,20 @@ "System.Xml.ReaderWriter": "4.3.0" } }, + "System.Xml.XmlDocument/4.3.0": { + "dependencies": { + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Globalization": "4.3.0", + "System.IO": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.1", + "System.Runtime.Extensions": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading": "4.3.0", + "System.Xml.ReaderWriter": "4.3.0" + } + }, "Telerik.UI.for.Wpf.NetCore.Xaml/2024.1.408": { "dependencies": { "Microsoft.EntityFrameworkCore": "3.1.5", @@ -2786,15 +2230,16 @@ "dependencies": { "Emgu.CV": "4.10.0.5680", "Emgu.CV.Bitmap": "4.10.0.5680", - "Emgu.CV.runtime.windows": "4.10.0.5680", - "MahApps.Metro.IconPacks": "6.2.1", "Prism.DryIoc": "9.0.537", "Prism.Wpf": "9.0.537", "Serilog": "4.3.1", "XP.Common": "1.0.0" }, "runtime": { - "XP.Camera.dll": {} + "XP.Camera.dll": { + "assemblyVersion": "1.0.0.0", + "fileVersion": "1.0.0.0" + } }, "resources": { "en-US/XP.Camera.resources.dll": { @@ -2805,7 +2250,6 @@ "XP.Common/1.0.0": { "dependencies": { "Emgu.CV": "4.10.0.5680", - "Emgu.CV.runtime.windows": "4.10.0.5680", "Microsoft.Data.Sqlite": "10.0.3", "Prism.Wpf": "9.0.537", "Serilog": "4.3.1", @@ -2815,7 +2259,10 @@ "Telerik.UI.for.Wpf.NetCore.Xaml": "2024.1.408" }, "runtime": { - "XP.Common.dll": {} + "XP.Common.dll": { + "assemblyVersion": "1.4.16.1", + "fileVersion": "1.4.16.1" + } }, "resources": { "en-US/XP.Common.resources.dll": { @@ -2836,7 +2283,10 @@ "XP.Common": "1.0.0" }, "runtime": { - "XP.Hardware.Detector.dll": {} + "XP.Hardware.Detector.dll": { + "assemblyVersion": "1.0.0.0", + "fileVersion": "1.0.0.0" + } }, "resources": { "en-US/XP.Hardware.Detector.resources.dll": { @@ -2859,7 +2309,10 @@ "XP.Hardware.PLC": "1.0.0" }, "runtime": { - "XP.Hardware.MotionControl.dll": {} + "XP.Hardware.MotionControl.dll": { + "assemblyVersion": "1.0.0.0", + "fileVersion": "1.0.0.0" + } }, "resources": { "en-US/XP.Hardware.MotionControl.resources.dll": { @@ -2880,7 +2333,10 @@ "XP.Common": "1.0.0" }, "runtime": { - "XP.Hardware.PLC.dll": {} + "XP.Hardware.PLC.dll": { + "assemblyVersion": "1.0.0.0", + "fileVersion": "1.0.0.0" + } }, "resources": { "en-US/XP.Hardware.PLC.resources.dll": { @@ -2903,7 +2359,10 @@ "XP.Hardware.RaySource.Comet.Messages": "1.0.0" }, "runtime": { - "XP.Hardware.RaySource.dll": {} + "XP.Hardware.RaySource.dll": { + "assemblyVersion": "1.0.0.0", + "fileVersion": "1.0.0.0" + } }, "resources": { "en-US/XP.Hardware.RaySource.resources.dll": { @@ -2922,17 +2381,21 @@ "Newtonsoft.Json": "13.0.3" }, "runtime": { - "XP.Hardware.RaySource.Comet.Messages.dll": {} + "XP.Hardware.RaySource.Comet.Messages.dll": { + "assemblyVersion": "1.0.0.0", + "fileVersion": "1.0.0.0" + } } }, "XP.ImageProcessing.CfgControl/1.0.0": { "dependencies": { - "MahApps.Metro": "2.4.11", - "MahApps.Metro.IconPacks": "6.2.1", "XP.ImageProcessing.Core": "1.0.0" }, "runtime": { - "XP.ImageProcessing.CfgControl.dll": {} + "XP.ImageProcessing.CfgControl.dll": { + "assemblyVersion": "1.0.0.0", + "fileVersion": "1.0.0.0" + } }, "resources": { "zh-CN/XP.ImageProcessing.CfgControl.resources.dll": { @@ -2942,18 +2405,19 @@ }, "XP.ImageProcessing.Core/1.0.0": { "dependencies": { - "Emgu.CV": "4.10.0.5680", - "Emgu.CV.runtime.windows": "4.10.0.5680" + "Emgu.CV": "4.10.0.5680" }, "runtime": { - "XP.ImageProcessing.Core.dll": {} + "XP.ImageProcessing.Core.dll": { + "assemblyVersion": "1.0.0.0", + "fileVersion": "1.0.0.0" + } } }, "XP.ImageProcessing.Processors/1.0.0": { "dependencies": { "Emgu.CV": "4.10.0.5680", "Emgu.CV.Bitmap": "4.10.0.5680", - "Emgu.CV.runtime.windows": "4.10.0.5680", "Microsoft.ML.OnnxRuntime.Gpu": "1.20.1", "Serilog": "4.3.1", "Serilog.Sinks.Console": "6.1.1", @@ -2961,7 +2425,10 @@ "XP.ImageProcessing.Core": "1.0.0" }, "runtime": { - "XP.ImageProcessing.Processors.dll": {} + "XP.ImageProcessing.Processors.dll": { + "assemblyVersion": "1.0.0.0", + "fileVersion": "1.0.0.0" + } }, "resources": { "zh-CN/XP.ImageProcessing.Processors.resources.dll": { @@ -2974,7 +2441,37 @@ "Microsoft.Xaml.Behaviors.Wpf": "1.1.122" }, "runtime": { - "XP.ImageProcessing.RoiControl.dll": {} + "XP.ImageProcessing.RoiControl.dll": { + "assemblyVersion": "1.0.0.0", + "fileVersion": "1.0.0.0" + } + } + }, + "XP.ReportEngine/1.0.0": { + "dependencies": { + "Newtonsoft.Json": "13.0.3", + "Prism.Wpf": "9.0.537", + "Telerik.UI.for.Wpf.NetCore.Xaml": "2024.1.408", + "XP.Common": "1.0.0", + "itext7": "8.0.5", + "itext7.bouncy-castle-adapter": "8.0.5" + }, + "runtime": { + "XP.ReportEngine.dll": { + "assemblyVersion": "1.0.0.0", + "fileVersion": "1.0.0.0" + } + }, + "resources": { + "en-US/XP.ReportEngine.resources.dll": { + "locale": "en-US" + }, + "zh-CN/XP.ReportEngine.resources.dll": { + "locale": "zh-CN" + }, + "zh-TW/XP.ReportEngine.resources.dll": { + "locale": "zh-TW" + } } }, "BR.AN.PviServices/1.1.0.0": { @@ -3009,6 +2506,13 @@ "serviceable": false, "sha512": "" }, + "BouncyCastle.Cryptography/2.2.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-A6Zr52zVqJKt18ZBsTnX0qhG0kwIQftVAjLmszmkiR/trSp8H+xj1gUOzk7XHwaKgyREMSV1v9XaKrBUeIOdvQ==", + "path": "bouncycastle.cryptography/2.2.1", + "hashPath": "bouncycastle.cryptography.2.2.1.nupkg.sha512" + }, "ControlzEx/5.0.1": { "type": "package", "serviceable": true, @@ -3079,320 +2583,40 @@ "path": "fluent.ribbon/9.0.0", "hashPath": "fluent.ribbon.9.0.0.nupkg.sha512" }, - "MahApps.Metro/2.4.11": { + "itext/8.0.5": { "type": "package", "serviceable": true, - "sha512": "sha512-pvk757N/ViZy+zlbB56AakryuZkts328M6pX2mMn/Z7r5ifkScDrS300zSBjLd+eGOPeHgUjH4f7cZbRiMyvQg==", - "path": "mahapps.metro/2.4.11", - "hashPath": "mahapps.metro.2.4.11.nupkg.sha512" + "sha512": "sha512-AptrL1Kvm4SrrkZSSfvQF9heRHekr9R91gR/3hEFR0uxJlBdVMmVnPQiTS+AhxJwalNtGNytKeFMWRoEUpWuTg==", + "path": "itext/8.0.5", + "hashPath": "itext.8.0.5.nupkg.sha512" }, - "MahApps.Metro.IconPacks/6.2.1": { + "itext.bouncy-castle-adapter/8.0.5": { "type": "package", "serviceable": true, - "sha512": "sha512-D4iq02tGW42jn0go7M+mBI8uMkfAmUrPwQl9gaV3WqiIssjABwP9vAtU05NTWUx9lB0xl+YlfcnVfFoKki0Ylw==", - "path": "mahapps.metro.iconpacks/6.2.1", - "hashPath": "mahapps.metro.iconpacks.6.2.1.nupkg.sha512" + "sha512": "sha512-mam0CtYkKmVwdX7wU1Zlg9oVW0B9efMASfAEIm+fQGovc9HcplsmP3MylS+VmQHbYlCNSH68DQlVhDz6Cu9WxQ==", + "path": "itext.bouncy-castle-adapter/8.0.5", + "hashPath": "itext.bouncy-castle-adapter.8.0.5.nupkg.sha512" }, - "MahApps.Metro.IconPacks.BootstrapIcons/6.2.1": { + "itext.commons/8.0.5": { "type": "package", "serviceable": true, - "sha512": "sha512-dTd5CaF63Ke2mWKlpagKEoU7sQmAtpoTyIMrZulPtI8N6OEkA6my3ekTPEt0OHu68zjiiXzZA5HLH2dlHUX+9w==", - "path": "mahapps.metro.iconpacks.bootstrapicons/6.2.1", - "hashPath": "mahapps.metro.iconpacks.bootstrapicons.6.2.1.nupkg.sha512" + "sha512": "sha512-exnLqeWOnn0Ee393bHf3zWTmcYuQfYuj7iIbMluG49XFczMWMMXrN9UvMmt5iYOKq0mGZsYb1eRoVATGuDiXGQ==", + "path": "itext.commons/8.0.5", + "hashPath": "itext.commons.8.0.5.nupkg.sha512" }, - "MahApps.Metro.IconPacks.BoxIcons/6.2.1": { + "itext7/8.0.5": { "type": "package", "serviceable": true, - "sha512": "sha512-fvxelxwj1Fr+osvmJnvlSthN34ZpwWMDacCWSGpylw8Pz+bFWcPIA3DxDMbxshvUhZ5dYzigCOxHZKG/BHPEdw==", - "path": "mahapps.metro.iconpacks.boxicons/6.2.1", - "hashPath": "mahapps.metro.iconpacks.boxicons.6.2.1.nupkg.sha512" + "sha512": "sha512-/1btEFYY+ncIR7j/g+pr26PX8Nbly8pbdDWfSHQq9E/rbxhz0ylkOLVpXNEvoaV545q508P8hociHaRiRG7bwg==", + "path": "itext7/8.0.5", + "hashPath": "itext7.8.0.5.nupkg.sha512" }, - "MahApps.Metro.IconPacks.BoxIcons2/6.2.1": { + "itext7.bouncy-castle-adapter/8.0.5": { "type": "package", "serviceable": true, - "sha512": "sha512-TkTvGg1L/w3kCGB7D7Hqd3EhYevijeknuh/VGVsaF47J4eOwf42Y2zXV7rpCXAY13zfrGWCq8RQQdacwFStQow==", - "path": "mahapps.metro.iconpacks.boxicons2/6.2.1", - "hashPath": "mahapps.metro.iconpacks.boxicons2.6.2.1.nupkg.sha512" - }, - "MahApps.Metro.IconPacks.CircumIcons/6.2.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-6L+lBg3Cj8Tq1TK0FHIBvS4t9iDa9V966dGZrzNAryZD/jQ2ncvz4rIDsZpSnsrLIhxMqChLvNogg3sDEuchtA==", - "path": "mahapps.metro.iconpacks.circumicons/6.2.1", - "hashPath": "mahapps.metro.iconpacks.circumicons.6.2.1.nupkg.sha512" - }, - "MahApps.Metro.IconPacks.Codicons/6.2.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-+pQeVS29X7pG7qrJ4y0ZPSRUQVaWidwwM/tdoIABo40MQpC0Izg8t1BXnXprP0C10Od9Zzsi2Qt/GWgfEW/aSw==", - "path": "mahapps.metro.iconpacks.codicons/6.2.1", - "hashPath": "mahapps.metro.iconpacks.codicons.6.2.1.nupkg.sha512" - }, - "MahApps.Metro.IconPacks.Coolicons/6.2.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-YdAqLEsLON2ripNNXkzrVX3iHQgEVXvfcvChx/2UsFyefV63RMVRu1rXCtLrOGRm/NAl7okTu2d6IeUVj+bCFQ==", - "path": "mahapps.metro.iconpacks.coolicons/6.2.1", - "hashPath": "mahapps.metro.iconpacks.coolicons.6.2.1.nupkg.sha512" - }, - "MahApps.Metro.IconPacks.Core/6.2.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-ySAyax37C045Ef1ufN33yQjn3qPM1MuQLgKdxZKYUAl2uZaIrn9wCeBNvfG46QPNRvBFLR6gVKTs6O3o81LNhQ==", - "path": "mahapps.metro.iconpacks.core/6.2.1", - "hashPath": "mahapps.metro.iconpacks.core.6.2.1.nupkg.sha512" - }, - "MahApps.Metro.IconPacks.Entypo/6.2.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-H1xzNFcF1gPgkrMFT3RgVc+whTZ88+g1hFUzaRkZYjH7k4JGGxsb6dqVjHyMQCbSygCUiWNuzy0yzNrv1krQOg==", - "path": "mahapps.metro.iconpacks.entypo/6.2.1", - "hashPath": "mahapps.metro.iconpacks.entypo.6.2.1.nupkg.sha512" - }, - "MahApps.Metro.IconPacks.EvaIcons/6.2.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-bUQfSeAb0uphQrhks3Mhj5kk+WsqL22dPF5b9DSVrRi7bHgWt2+ciMZZp85Q+zFKtHM3PkdGC2nUbl0bBu6AyQ==", - "path": "mahapps.metro.iconpacks.evaicons/6.2.1", - "hashPath": "mahapps.metro.iconpacks.evaicons.6.2.1.nupkg.sha512" - }, - "MahApps.Metro.IconPacks.FeatherIcons/6.2.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-a5sxUHp39ejJu6p2wbbJT71tYY4zYuS+N4Vb9VxcnsQqaeMktuHtiy1ySR43h4dkdzhAohbyxvb9j80AkDXZLA==", - "path": "mahapps.metro.iconpacks.feathericons/6.2.1", - "hashPath": "mahapps.metro.iconpacks.feathericons.6.2.1.nupkg.sha512" - }, - "MahApps.Metro.IconPacks.FileIcons/6.2.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-Rir1rKL8LNhxhWNmVdWDbSRjoJg+y0t4kqDdD5zLH6T7uFm6yjPvIEK4pvA30NIFPgvl4bkN2TKQLv+7P4FNDw==", - "path": "mahapps.metro.iconpacks.fileicons/6.2.1", - "hashPath": "mahapps.metro.iconpacks.fileicons.6.2.1.nupkg.sha512" - }, - "MahApps.Metro.IconPacks.Fontaudio/6.2.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-AsYb9vBKtkYBPV/3OEGTijH+wrlxxPcMFRXVbbRZLz1E9JIoGxkRcwnFryWs2bhDIMiLVzj2DgYClqvJSovmEw==", - "path": "mahapps.metro.iconpacks.fontaudio/6.2.1", - "hashPath": "mahapps.metro.iconpacks.fontaudio.6.2.1.nupkg.sha512" - }, - "MahApps.Metro.IconPacks.FontAwesome/6.2.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-Su6jzajwH8rP7H5OGhXvyC2SlsoyH0pNwNCkDFSP5zyVQlMtmUZGOY8X0Qcj1QFn+t4TmEnMjwGMB6RuzozgEg==", - "path": "mahapps.metro.iconpacks.fontawesome/6.2.1", - "hashPath": "mahapps.metro.iconpacks.fontawesome.6.2.1.nupkg.sha512" - }, - "MahApps.Metro.IconPacks.FontAwesome5/6.2.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-PNTiFAT/+ro1SitUj8k+gGWDq7mC/eYD1cy8QfF84ZyCp+goLh5BApj0IGvv+eisLMw7hFIIzI8vIHQ1GbyExw==", - "path": "mahapps.metro.iconpacks.fontawesome5/6.2.1", - "hashPath": "mahapps.metro.iconpacks.fontawesome5.6.2.1.nupkg.sha512" - }, - "MahApps.Metro.IconPacks.FontAwesome6/6.2.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-IB2AdqnGAdM2RK8Ekmf8U2VBw7cAnRQHQaVJ1Kv/0Ek+VavsmUqlyotmSg28iL4Sq9BBC2SQrRbvuIGfz9seWA==", - "path": "mahapps.metro.iconpacks.fontawesome6/6.2.1", - "hashPath": "mahapps.metro.iconpacks.fontawesome6.6.2.1.nupkg.sha512" - }, - "MahApps.Metro.IconPacks.Fontisto/6.2.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-+JDR5+YpYq/iBdj7DZiT5XA4U5+I2kxqxGK6usqOi077iemIEXKWxhrEiWyruHfRQt4VrD/WGy27PrJzU/h6cA==", - "path": "mahapps.metro.iconpacks.fontisto/6.2.1", - "hashPath": "mahapps.metro.iconpacks.fontisto.6.2.1.nupkg.sha512" - }, - "MahApps.Metro.IconPacks.ForkAwesome/6.2.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-luVbeZF3hH7DQl+QCT6NV9vGJ8a1CtOJd0W2AShOms8YuAU4RnsHMm9jrrONpCBeVqQI8mpFRTzd0bYit8gilg==", - "path": "mahapps.metro.iconpacks.forkawesome/6.2.1", - "hashPath": "mahapps.metro.iconpacks.forkawesome.6.2.1.nupkg.sha512" - }, - "MahApps.Metro.IconPacks.GameIcons/6.2.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-oOVAHye1k+cjfXaGfBP6nu5AtZND65WhsolAqmS8wMtM5wPN5q0k8jUq3lxm5/MRMh+rCT99oAcZKmyDaRlcVA==", - "path": "mahapps.metro.iconpacks.gameicons/6.2.1", - "hashPath": "mahapps.metro.iconpacks.gameicons.6.2.1.nupkg.sha512" - }, - "MahApps.Metro.IconPacks.Ionicons/6.2.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-WBHtfiUfEGUPiLaJkQ2ZXipo01eRbW6PM6qI7S4LLPGs5FM9YIy6PiaeahN8FcYS/HnYx8E2HSJ99R+U+M2nBA==", - "path": "mahapps.metro.iconpacks.ionicons/6.2.1", - "hashPath": "mahapps.metro.iconpacks.ionicons.6.2.1.nupkg.sha512" - }, - "MahApps.Metro.IconPacks.JamIcons/6.2.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-9Jc2IzyBa0vASPCup7woKHTSwG31rtBvZsdpA+Jot++eWkZpvCpgWn8BKdcNcIh3Hjnve9qLzPWb11WobPO2CQ==", - "path": "mahapps.metro.iconpacks.jamicons/6.2.1", - "hashPath": "mahapps.metro.iconpacks.jamicons.6.2.1.nupkg.sha512" - }, - "MahApps.Metro.IconPacks.KeyruneIcons/6.2.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-9Cs7gIl1WCTsZHPgX0Ri32r8vTPi32Zxf/z/3CxLpfksY7qsTodCWWWlIgjEqxriWE/AGoUzdh1o73j5qtQ/RQ==", - "path": "mahapps.metro.iconpacks.keyruneicons/6.2.1", - "hashPath": "mahapps.metro.iconpacks.keyruneicons.6.2.1.nupkg.sha512" - }, - "MahApps.Metro.IconPacks.Lucide/6.2.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-xehG2J/Ljfa6NxlKCqvDxnxUMrApR0xSLGaoaFSVN531jF+WNZNAaUvE9qkxOHKZ+jpH4gUnb2sC3qNcpAGVkA==", - "path": "mahapps.metro.iconpacks.lucide/6.2.1", - "hashPath": "mahapps.metro.iconpacks.lucide.6.2.1.nupkg.sha512" - }, - "MahApps.Metro.IconPacks.Material/6.2.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-DcNQIsJLAmuzlpXdD2hQtMV8IzLRHsxFBn9+nHDLAsYs8duTBTycpNpQitkzuNYH0o+sgIbefCSp3jDUu9hIiQ==", - "path": "mahapps.metro.iconpacks.material/6.2.1", - "hashPath": "mahapps.metro.iconpacks.material.6.2.1.nupkg.sha512" - }, - "MahApps.Metro.IconPacks.MaterialDesign/6.2.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-qMQHCZzHoBZy3bcU9CBf9GusPMpbxy0DPxiJrfbhM7IHh/hK5xWREoP2TOevqAVTEOk3YszWbiJBkYo/oeEliw==", - "path": "mahapps.metro.iconpacks.materialdesign/6.2.1", - "hashPath": "mahapps.metro.iconpacks.materialdesign.6.2.1.nupkg.sha512" - }, - "MahApps.Metro.IconPacks.MaterialLight/6.2.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-8ypOe4HwSGnoQl/JIpC5hb6VnK+xFtSbvhlTmw+cnJVNluiwWdpJv+m9jpU3/9+SRkJRHVgHvEMwEXQx/XmS4A==", - "path": "mahapps.metro.iconpacks.materiallight/6.2.1", - "hashPath": "mahapps.metro.iconpacks.materiallight.6.2.1.nupkg.sha512" - }, - "MahApps.Metro.IconPacks.MemoryIcons/6.2.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-TIU3n0ZXhKgpSaSlqg3EEjPBp+E89UyV6nOhK4DCP9yUbnZ/CuLmkwrrd2rtt9au+VD+TB1lWbA6+3NjpH7G9Q==", - "path": "mahapps.metro.iconpacks.memoryicons/6.2.1", - "hashPath": "mahapps.metro.iconpacks.memoryicons.6.2.1.nupkg.sha512" - }, - "MahApps.Metro.IconPacks.Microns/6.2.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-stnuTZXy+k7BmKGvI4oP0NlspElhernJ4yFXUlEkA3X/38qlqq+YltM5KPPKgQo1BLHj58lOOn+U03grfdPI5A==", - "path": "mahapps.metro.iconpacks.microns/6.2.1", - "hashPath": "mahapps.metro.iconpacks.microns.6.2.1.nupkg.sha512" - }, - "MahApps.Metro.IconPacks.MingCuteIcons/6.2.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-AfMV+SbqWabsuQdS6wt7SpinS5cah+7Pkdj+bfhHBBizdMsL+nY8+tiwmKF1up+wfZm8lCvnrCQWTkJ2mMSjUA==", - "path": "mahapps.metro.iconpacks.mingcuteicons/6.2.1", - "hashPath": "mahapps.metro.iconpacks.mingcuteicons.6.2.1.nupkg.sha512" - }, - "MahApps.Metro.IconPacks.Modern/6.2.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-xCDNREDX37HZE1YIAwQ9YOwwhQLHRby1ec1FwLKRU6MWX0OJuceqk3HY3532xG7KpGGaOIV/dpB5vFq3oo+wug==", - "path": "mahapps.metro.iconpacks.modern/6.2.1", - "hashPath": "mahapps.metro.iconpacks.modern.6.2.1.nupkg.sha512" - }, - "MahApps.Metro.IconPacks.MynaUIIcons/6.2.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-StYsKBuK2kKfUdkKq6ZDa5xtlWk4gyI59ip9mS30niYxn4K0yAZIMEyx1EX4efpGanlLF4oPGZ/YUvWGfl98NA==", - "path": "mahapps.metro.iconpacks.mynauiicons/6.2.1", - "hashPath": "mahapps.metro.iconpacks.mynauiicons.6.2.1.nupkg.sha512" - }, - "MahApps.Metro.IconPacks.Octicons/6.2.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-1WGEPYjdcOMgXNspZHhYXooNJbylZyqLAkzXiCvlu1pW6HinBzaJI+vSkPtoZaF9QaMkSxwT2ZxG4mMkbhUowA==", - "path": "mahapps.metro.iconpacks.octicons/6.2.1", - "hashPath": "mahapps.metro.iconpacks.octicons.6.2.1.nupkg.sha512" - }, - "MahApps.Metro.IconPacks.PhosphorIcons/6.2.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-PTfhQXTe3INWoELJy0dCfm92/faS6hF3F/C4kZyTiVe9Pgf21cd5v7kIBeT2m/eNHyEBFZX2ifmk8Q3xK1dr7g==", - "path": "mahapps.metro.iconpacks.phosphoricons/6.2.1", - "hashPath": "mahapps.metro.iconpacks.phosphoricons.6.2.1.nupkg.sha512" - }, - "MahApps.Metro.IconPacks.PicolIcons/6.2.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-6lEuXdF77v9Ip4QoDEJm7+mrfVRz7EV3LJGxDzVfPsSPwA4VxIsqOU0xHKDQ/5Ekv+gPXW/PQtecfjlQ+UrWGw==", - "path": "mahapps.metro.iconpacks.picolicons/6.2.1", - "hashPath": "mahapps.metro.iconpacks.picolicons.6.2.1.nupkg.sha512" - }, - "MahApps.Metro.IconPacks.PixelartIcons/6.2.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-KJwxtRvqpMHRkghru0H7AZcDgUDpgNq4bQTclivcQyUTChedfZh64REfJ1B5QCfKdCKmvVMR66JpJnGoM6ekdA==", - "path": "mahapps.metro.iconpacks.pixelarticons/6.2.1", - "hashPath": "mahapps.metro.iconpacks.pixelarticons.6.2.1.nupkg.sha512" - }, - "MahApps.Metro.IconPacks.RadixIcons/6.2.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-1COtOqMAtgOydnx8cU/ukwNn4KRAzaFp5+SCucYgjyq0vdm1+1hi8inNDEZvIJqAhCSdCU13afFhsL/1KYLbBQ==", - "path": "mahapps.metro.iconpacks.radixicons/6.2.1", - "hashPath": "mahapps.metro.iconpacks.radixicons.6.2.1.nupkg.sha512" - }, - "MahApps.Metro.IconPacks.RemixIcon/6.2.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-ROcfFFN2S45IV7dqU6Alf8i5gMIG+4iArm8Mzb+KXjiyLOHpTZ/QHGIy9DtqIG4NuCO9pwf+fNTGCsDlXIG3TQ==", - "path": "mahapps.metro.iconpacks.remixicon/6.2.1", - "hashPath": "mahapps.metro.iconpacks.remixicon.6.2.1.nupkg.sha512" - }, - "MahApps.Metro.IconPacks.RPGAwesome/6.2.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-6zHlSIU5z5meUIjDpwX872Z9Zd1ZhBRuohWiy0a+AUevdOpPw9or7rGpiMAdAzEvmab4sy9Un787A4YC5LQr7w==", - "path": "mahapps.metro.iconpacks.rpgawesome/6.2.1", - "hashPath": "mahapps.metro.iconpacks.rpgawesome.6.2.1.nupkg.sha512" - }, - "MahApps.Metro.IconPacks.SimpleIcons/6.2.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-EbK+/s00A72ATU+Qpk72ZxQsLvVGGRD8UA/jsGDqV0Hlym0I//dtQjYwWxzfwmTp2Ay3Sh6RmLu49qqv3Ey2/Q==", - "path": "mahapps.metro.iconpacks.simpleicons/6.2.1", - "hashPath": "mahapps.metro.iconpacks.simpleicons.6.2.1.nupkg.sha512" - }, - "MahApps.Metro.IconPacks.Typicons/6.2.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-o6kNl3wKxsO1MkgRg8V/wY7un1ueNBbqy5Ut3Y46mC2vVzEwxcejss5+BtuO/oFud3GQtet2Ng53z+70dNjG4Q==", - "path": "mahapps.metro.iconpacks.typicons/6.2.1", - "hashPath": "mahapps.metro.iconpacks.typicons.6.2.1.nupkg.sha512" - }, - "MahApps.Metro.IconPacks.Unicons/6.2.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-JdTZD7CtLjcf+Ne0jN6EsyvG/Dl+v4w08OQRSC9XTknuVW/zlJyHcxbJy+NvkBdcO+Jup/TwNslV5g9xVheavQ==", - "path": "mahapps.metro.iconpacks.unicons/6.2.1", - "hashPath": "mahapps.metro.iconpacks.unicons.6.2.1.nupkg.sha512" - }, - "MahApps.Metro.IconPacks.VaadinIcons/6.2.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-6GOuzyvLNojG+NLZDULsWSfQ+RAh2v8W6n2iPWc+c1pDcmgbuKCPiazQXb0YpPuldGEOpK1ur6WJOXXsrTRR6A==", - "path": "mahapps.metro.iconpacks.vaadinicons/6.2.1", - "hashPath": "mahapps.metro.iconpacks.vaadinicons.6.2.1.nupkg.sha512" - }, - "MahApps.Metro.IconPacks.WeatherIcons/6.2.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-ChU6387+6rDPQZRS/L7wT4YFMmSvv0AdeCBAPVZTeW3SdeFt49tOWQvz2Xpb+N55Qn4tGjb+/lE2dg/NAYYU2w==", - "path": "mahapps.metro.iconpacks.weathericons/6.2.1", - "hashPath": "mahapps.metro.iconpacks.weathericons.6.2.1.nupkg.sha512" - }, - "MahApps.Metro.IconPacks.Zondicons/6.2.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-6+IMX7VptiD05ctXsjhEcvBHx0Gbj4DH1j13Ko5gk9yO7m6cCYaWqsKlt/GMH5CJEfa7Obz9+1pRstOzxZ03+A==", - "path": "mahapps.metro.iconpacks.zondicons/6.2.1", - "hashPath": "mahapps.metro.iconpacks.zondicons.6.2.1.nupkg.sha512" + "sha512": "sha512-9jpalaOyxbcuhbl9yJJQGXdseKUFrKlUjjcjIvReb3Qz4Aw0F+WNXKOo0sax87TGGNb66fyHsfJib3JTnfzRYw==", + "path": "itext7.bouncy-castle-adapter/8.0.5", + "hashPath": "itext7.bouncy-castle-adapter.8.0.5.nupkg.sha512" }, "Microsoft.Bcl.AsyncInterfaces/1.1.1": { "type": "package", @@ -3429,6 +2653,13 @@ "path": "microsoft.data.sqlite.core/10.0.3", "hashPath": "microsoft.data.sqlite.core.10.0.3.nupkg.sha512" }, + "Microsoft.DotNet.PlatformAbstractions/1.1.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-Bl6KYfbFSIW3QIRHAp931iR5h01qHjKghdpAtncwbzNUs0+IUZ+XfwkIU0sQsR33ufGvi3u4dZMIYYFysjpHAA==", + "path": "microsoft.dotnet.platformabstractions/1.1.0", + "hashPath": "microsoft.dotnet.platformabstractions.1.1.0.nupkg.sha512" + }, "Microsoft.EntityFrameworkCore/3.1.5": { "type": "package", "serviceable": true, @@ -3485,12 +2716,12 @@ "path": "microsoft.extensions.configuration.binder/10.0.0", "hashPath": "microsoft.extensions.configuration.binder.10.0.0.nupkg.sha512" }, - "Microsoft.Extensions.DependencyInjection/3.1.5": { + "Microsoft.Extensions.DependencyInjection/5.0.0": { "type": "package", "serviceable": true, - "sha512": "sha512-I+RTJQi7TtenIHZqL2zr6523PYXfL88Ruu4UIVmspIxdw14GHd8zZ+2dGLSdwX7fn41Hth4d42S1e1iHWVOJyQ==", - "path": "microsoft.extensions.dependencyinjection/3.1.5", - "hashPath": "microsoft.extensions.dependencyinjection.3.1.5.nupkg.sha512" + "sha512": "sha512-Rc2kb/p3Ze6cP6rhFC3PJRdWGbLvSHZc0ev7YlyeU6FmHciDMLrhoVoTUEzKPhN5ZjFgKF1Cf5fOz8mCMIkvpA==", + "path": "microsoft.extensions.dependencyinjection/5.0.0", + "hashPath": "microsoft.extensions.dependencyinjection.5.0.0.nupkg.sha512" }, "Microsoft.Extensions.DependencyInjection.Abstractions/8.0.1": { "type": "package", @@ -3506,26 +2737,26 @@ "path": "microsoft.extensions.dependencymodel/10.0.0", "hashPath": "microsoft.extensions.dependencymodel.10.0.0.nupkg.sha512" }, - "Microsoft.Extensions.Logging/3.1.5": { + "Microsoft.Extensions.Logging/5.0.0": { "type": "package", "serviceable": true, - "sha512": "sha512-C85NYDym6xy03o70vxX+VQ4ZEjj5Eg5t5QoGW0t100vG5MmPL6+G3XXcQjIIn1WRQrjzGWzQwuKf38fxXEWIWA==", - "path": "microsoft.extensions.logging/3.1.5", - "hashPath": "microsoft.extensions.logging.3.1.5.nupkg.sha512" + "sha512": "sha512-MgOwK6tPzB6YNH21wssJcw/2MKwee8b2gI7SllYfn6rvTpIrVvVS5HAjSU2vqSku1fwqRvWP0MdIi14qjd93Aw==", + "path": "microsoft.extensions.logging/5.0.0", + "hashPath": "microsoft.extensions.logging.5.0.0.nupkg.sha512" }, - "Microsoft.Extensions.Logging.Abstractions/3.1.5": { + "Microsoft.Extensions.Logging.Abstractions/5.0.0": { "type": "package", "serviceable": true, - "sha512": "sha512-ZvwowjRSWXewdPI+whPFXgwF4Qme6Q9KV9SCPEITSGiqHLArct7q5hTBtTzj3GPsVLjTqehvTg6Bd/EQk9JS0A==", - "path": "microsoft.extensions.logging.abstractions/3.1.5", - "hashPath": "microsoft.extensions.logging.abstractions.3.1.5.nupkg.sha512" + "sha512": "sha512-NxP6ahFcBnnSfwNBi2KH2Oz8Xl5Sm2krjId/jRR3I7teFphwiUoUeZPwTNA21EX+5PtjqmyAvKaOeBXcJjcH/w==", + "path": "microsoft.extensions.logging.abstractions/5.0.0", + "hashPath": "microsoft.extensions.logging.abstractions.5.0.0.nupkg.sha512" }, - "Microsoft.Extensions.Options/3.1.5": { + "Microsoft.Extensions.Options/5.0.0": { "type": "package", "serviceable": true, - "sha512": "sha512-f+JT/7lkKBMp/Ak2tVjO+TD7o+UoCfjnExkZNn0PZIso8kIXrqNy6x42Lrxf4Q0pW3JMf9ExmL2EQlvk2XnFAg==", - "path": "microsoft.extensions.options/3.1.5", - "hashPath": "microsoft.extensions.options.3.1.5.nupkg.sha512" + "sha512": "sha512-CBvR92TCJ5uBIdd9/HzDSrxYak+0W/3+yxrNg8Qm6Bmrkh5L+nu6m3WeazQehcZ5q1/6dDA7J5YdQjim0165zg==", + "path": "microsoft.extensions.options/5.0.0", + "hashPath": "microsoft.extensions.options.5.0.0.nupkg.sha512" }, "Microsoft.Extensions.Primitives/10.0.0": { "type": "package", @@ -3688,6 +2919,125 @@ "path": "prism.wpf/9.0.537", "hashPath": "prism.wpf.9.0.537.nupkg.sha512" }, + "runtime.any.System.Collections/4.3.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-23g6rqftKmovn2cLeGsuHUYm0FD7pdutb0uQMJpZ3qTvq+zHkgmt6J65VtRry4WDGYlmkMa4xDACtaQ94alNag==", + "path": "runtime.any.system.collections/4.3.0", + "hashPath": "runtime.any.system.collections.4.3.0.nupkg.sha512" + }, + "runtime.any.System.Diagnostics.Tools/4.3.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-S/GPBmfPBB48ZghLxdDR7kDAJVAqgAuThyDJho3OLP5OS4tWD2ydyL8LKm8lhiBxce10OKe9X2zZ6DUjAqEbPg==", + "path": "runtime.any.system.diagnostics.tools/4.3.0", + "hashPath": "runtime.any.system.diagnostics.tools.4.3.0.nupkg.sha512" + }, + "runtime.any.System.Diagnostics.Tracing/4.3.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-1lpifymjGDzoYIaam6/Hyqf8GhBI3xXYLK2TgEvTtuZMorG3Kb9QnMTIKhLjJYXIiu1JvxjngHvtVFQQlpQ3HQ==", + "path": "runtime.any.system.diagnostics.tracing/4.3.0", + "hashPath": "runtime.any.system.diagnostics.tracing.4.3.0.nupkg.sha512" + }, + "runtime.any.System.Globalization/4.3.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-sMDBnad4rp4t7GY442Jux0MCUuKL4otn5BK6Ni0ARTXTSpRNBzZ7hpMfKSvnVSED5kYJm96YOWsqV0JH0d2uuw==", + "path": "runtime.any.system.globalization/4.3.0", + "hashPath": "runtime.any.system.globalization.4.3.0.nupkg.sha512" + }, + "runtime.any.System.Globalization.Calendars/4.3.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-M1r+760j1CNA6M/ZaW6KX8gOS8nxPRqloqDcJYVidRG566Ykwcs29AweZs2JF+nMOCgWDiMfPSTMfvwOI9F77w==", + "path": "runtime.any.system.globalization.calendars/4.3.0", + "hashPath": "runtime.any.system.globalization.calendars.4.3.0.nupkg.sha512" + }, + "runtime.any.System.IO/4.3.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-SDZ5AD1DtyRoxYtEcqQ3HDlcrorMYXZeCt7ZhG9US9I5Vva+gpIWDGMkcwa5XiKL0ceQKRZIX2x0XEjLX7PDzQ==", + "path": "runtime.any.system.io/4.3.0", + "hashPath": "runtime.any.system.io.4.3.0.nupkg.sha512" + }, + "runtime.any.System.Reflection/4.3.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-hLC3A3rI8jipR5d9k7+f0MgRCW6texsAp0MWkN/ci18FMtQ9KH7E2vDn/DH2LkxsszlpJpOn9qy6Z6/69rH6eQ==", + "path": "runtime.any.system.reflection/4.3.0", + "hashPath": "runtime.any.system.reflection.4.3.0.nupkg.sha512" + }, + "runtime.any.System.Reflection.Extensions/4.3.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-cPhT+Vqu52+cQQrDai/V91gubXUnDKNRvlBnH+hOgtGyHdC17aQIU64EaehwAQymd7kJA5rSrVRNfDYrbhnzyA==", + "path": "runtime.any.system.reflection.extensions/4.3.0", + "hashPath": "runtime.any.system.reflection.extensions.4.3.0.nupkg.sha512" + }, + "runtime.any.System.Reflection.Primitives/4.3.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-Nrm1p3armp6TTf2xuvaa+jGTTmncALWFq22CpmwRvhDf6dE9ZmH40EbOswD4GnFLrMRS0Ki6Kx5aUPmKK/hZBg==", + "path": "runtime.any.system.reflection.primitives/4.3.0", + "hashPath": "runtime.any.system.reflection.primitives.4.3.0.nupkg.sha512" + }, + "runtime.any.System.Resources.ResourceManager/4.3.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-Lxb89SMvf8w9p9+keBLyL6H6x/TEmc6QVsIIA0T36IuyOY3kNvIdyGddA2qt35cRamzxF8K5p0Opq4G4HjNbhQ==", + "path": "runtime.any.system.resources.resourcemanager/4.3.0", + "hashPath": "runtime.any.system.resources.resourcemanager.4.3.0.nupkg.sha512" + }, + "runtime.any.System.Runtime/4.3.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-fRS7zJgaG9NkifaAxGGclDDoRn9HC7hXACl52Or06a/fxdzDajWb5wov3c6a+gVSlekRoexfjwQSK9sh5um5LQ==", + "path": "runtime.any.system.runtime/4.3.0", + "hashPath": "runtime.any.system.runtime.4.3.0.nupkg.sha512" + }, + "runtime.any.System.Runtime.Handles/4.3.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-GG84X6vufoEzqx8PbeBKheE4srOhimv+yLtGb/JkR3Y2FmoqmueLNFU4Xx8Y67plFpltQSdK74x0qlEhIpv/CQ==", + "path": "runtime.any.system.runtime.handles/4.3.0", + "hashPath": "runtime.any.system.runtime.handles.4.3.0.nupkg.sha512" + }, + "runtime.any.System.Runtime.InteropServices/4.3.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-lBoFeQfxe/4eqjPi46E0LU/YaCMdNkQ8B4MZu/mkzdIAZh8RQ1NYZSj0egrQKdgdvlPFtP4STtob40r4o2DBAw==", + "path": "runtime.any.system.runtime.interopservices/4.3.0", + "hashPath": "runtime.any.system.runtime.interopservices.4.3.0.nupkg.sha512" + }, + "runtime.any.System.Text.Encoding/4.3.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-+ihI5VaXFCMVPJNstG4O4eo1CfbrByLxRrQQTqOTp1ttK0kUKDqOdBSTaCB2IBk/QtjDrs6+x4xuezyMXdm0HQ==", + "path": "runtime.any.system.text.encoding/4.3.0", + "hashPath": "runtime.any.system.text.encoding.4.3.0.nupkg.sha512" + }, + "runtime.any.System.Text.Encoding.Extensions/4.3.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-NLrxmLsfRrOuVqPWG+2lrQZnE53MLVeo+w9c54EV+TUo4c8rILpsDXfY8pPiOy9kHpUHHP07ugKmtsU3vVW5Jg==", + "path": "runtime.any.system.text.encoding.extensions/4.3.0", + "hashPath": "runtime.any.system.text.encoding.extensions.4.3.0.nupkg.sha512" + }, + "runtime.any.System.Threading.Tasks/4.3.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-OhBAVBQG5kFj1S+hCEQ3TUHBAEtZ3fbEMgZMRNdN8A0Pj4x+5nTELEqL59DU0TjKVE6II3dqKw4Dklb3szT65w==", + "path": "runtime.any.system.threading.tasks/4.3.0", + "hashPath": "runtime.any.system.threading.tasks.4.3.0.nupkg.sha512" + }, + "runtime.any.System.Threading.Timer/4.3.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-w4ehZJ+AwXYmGwYu+rMvym6RvMaRiUEQR1u6dwcyuKHxz8Heu/mO9AG1MquEgTyucnhv3M43X0iKpDOoN17C0w==", + "path": "runtime.any.system.threading.timer/4.3.0", + "hashPath": "runtime.any.system.threading.timer.4.3.0.nupkg.sha512" + }, "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { "type": "package", "serviceable": true, @@ -3828,6 +3178,55 @@ "path": "runtime.win-x86.runtime.native.system.data.sqlclient.sni/4.4.0", "hashPath": "runtime.win-x86.runtime.native.system.data.sqlclient.sni.4.4.0.nupkg.sha512" }, + "runtime.win.Microsoft.Win32.Primitives/4.3.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-NU51SEt/ZaD2MF48sJ17BIqx7rjeNNLXUevfMOjqQIetdndXwYjZfZsT6jD+rSWp/FYxjesdK4xUSl4OTEI0jw==", + "path": "runtime.win.microsoft.win32.primitives/4.3.0", + "hashPath": "runtime.win.microsoft.win32.primitives.4.3.0.nupkg.sha512" + }, + "runtime.win.System.Console/4.3.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-vHPXC3B18dxhyipVce8xQT1MQv1o5srYZqBlCNu9p9MNjhgGOntdQh/Xh2X4o7M2F839YUcQiGwu8Q498FyDjg==", + "path": "runtime.win.system.console/4.3.1", + "hashPath": "runtime.win.system.console.4.3.1.nupkg.sha512" + }, + "runtime.win.System.Diagnostics.Debug/4.3.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-hHHP0WCStene2jjeYcuDkETozUYF/3sHVRHAEOgS3L15hlip24ssqCTnJC28Z03Wpo078oMcJd0H4egD2aJI8g==", + "path": "runtime.win.system.diagnostics.debug/4.3.0", + "hashPath": "runtime.win.system.diagnostics.debug.4.3.0.nupkg.sha512" + }, + "runtime.win.System.IO.FileSystem/4.3.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-Z37zcSCpXuGCYtFbqYO0TwOVXxS2d+BXgSoDFZmRg8BC4Cuy54edjyIvhhcfCrDQA9nl+EPFTgHN54dRAK7mNA==", + "path": "runtime.win.system.io.filesystem/4.3.0", + "hashPath": "runtime.win.system.io.filesystem.4.3.0.nupkg.sha512" + }, + "runtime.win.System.Net.Primitives/4.3.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-lkXXykakvXUU+Zq2j0pC6EO20lEhijjqMc01XXpp1CJN+DeCwl3nsj4t5Xbpz3kA7yQyTqw6d9SyIzsyLsV3zA==", + "path": "runtime.win.system.net.primitives/4.3.0", + "hashPath": "runtime.win.system.net.primitives.4.3.0.nupkg.sha512" + }, + "runtime.win.System.Net.Sockets/4.3.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-FK/2gX6MmuLIKNCGsV59Fe4IYrLrI5n9pQ1jh477wiivEM/NCXDT2dRetH5FSfY0bQ+VgTLcS3zcmjQ8my3nxQ==", + "path": "runtime.win.system.net.sockets/4.3.0", + "hashPath": "runtime.win.system.net.sockets.4.3.0.nupkg.sha512" + }, + "runtime.win.System.Runtime.Extensions/4.3.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-RkgHVhUPvzZxuUubiZe8yr/6CypRVXj0VBzaR8hsqQ8f+rUo7e4PWrHTLOCjd8fBMGWCrY//fi7Ku3qXD7oHRw==", + "path": "runtime.win.system.runtime.extensions/4.3.0", + "hashPath": "runtime.win.system.runtime.extensions.4.3.0.nupkg.sha512" + }, "Serilog/4.3.1": { "type": "package", "serviceable": true, @@ -3989,6 +3388,13 @@ "path": "system.collections.immutable/1.7.1", "hashPath": "system.collections.immutable.1.7.1.nupkg.sha512" }, + "System.Collections.NonGeneric/4.3.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-prtjIEMhGUnQq6RnPEYLpFt8AtLbp9yq2zxOSrY7KJJZrw25Fi97IzBqY7iqssbM61Ek5b8f3MG/sG1N2sN5KA==", + "path": "system.collections.nongeneric/4.3.0", + "hashPath": "system.collections.nongeneric.4.3.0.nupkg.sha512" + }, "System.ComponentModel.Annotations/4.7.0": { "type": "package", "serviceable": true, @@ -4073,6 +3479,13 @@ "path": "system.diagnostics.performancecounter/6.0.0", "hashPath": "system.diagnostics.performancecounter.6.0.0.nupkg.sha512" }, + "System.Diagnostics.Process/4.3.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-J0wOX07+QASQblsfxmIMFc9Iq7KTXYL3zs2G/Xc704Ylv3NpuVdo6gij6V3PGiptTxqsK0K7CdXenRvKUnkA2g==", + "path": "system.diagnostics.process/4.3.0", + "hashPath": "system.diagnostics.process.4.3.0.nupkg.sha512" + }, "System.Diagnostics.Tools/4.3.0": { "type": "package", "serviceable": true, @@ -4192,6 +3605,13 @@ "path": "system.net.http/4.3.0", "hashPath": "system.net.http.4.3.0.nupkg.sha512" }, + "System.Net.NameResolution/4.3.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-AFYl08R7MrsrEjqpQWTZWBadqXyTzNDaWpMqyxhb0d6sGhV6xMDKueuBXlLL30gz+DIRY6MpdgnHWlCh5wmq9w==", + "path": "system.net.nameresolution/4.3.0", + "hashPath": "system.net.nameresolution.4.3.0.nupkg.sha512" + }, "System.Net.Primitives/4.3.0": { "type": "package", "serviceable": true, @@ -4220,6 +3640,13 @@ "path": "system.private.servicemodel/4.7.0", "hashPath": "system.private.servicemodel.4.7.0.nupkg.sha512" }, + "System.Private.Uri/4.3.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-I4SwANiUGho1esj4V4oSlPllXjzCZDE+5XXso2P03LW2vOda2Enzh8DWOxwN6hnrJyp314c7KuVu31QYhRzOGg==", + "path": "system.private.uri/4.3.0", + "hashPath": "system.private.uri.4.3.0.nupkg.sha512" + }, "System.Reflection/4.3.0": { "type": "package", "serviceable": true, @@ -4318,6 +3745,13 @@ "path": "system.runtime.interopservices.runtimeinformation/4.3.0", "hashPath": "system.runtime.interopservices.runtimeinformation.4.3.0.nupkg.sha512" }, + "System.Runtime.Loader/4.3.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-DHMaRn8D8YCK2GG2pw+UzNxn/OHVfaWx7OTLBD/hPegHZZgcZh3H6seWegrC4BYwsfuGrywIuT+MQs+rPqRLTQ==", + "path": "system.runtime.loader/4.3.0", + "hashPath": "system.runtime.loader.4.3.0.nupkg.sha512" + }, "System.Runtime.Numerics/4.3.0": { "type": "package", "serviceable": true, @@ -4325,6 +3759,20 @@ "path": "system.runtime.numerics/4.3.0", "hashPath": "system.runtime.numerics.4.3.0.nupkg.sha512" }, + "System.Runtime.Serialization.Formatters/4.3.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-KT591AkTNFOTbhZlaeMVvfax3RqhH1EJlcwF50Wm7sfnBLuHiOeZRRKrr1ns3NESkM20KPZ5Ol/ueMq5vg4QoQ==", + "path": "system.runtime.serialization.formatters/4.3.0", + "hashPath": "system.runtime.serialization.formatters.4.3.0.nupkg.sha512" + }, + "System.Runtime.Serialization.Primitives/4.3.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-Wz+0KOukJGAlXjtKr+5Xpuxf8+c8739RI1C+A2BoQZT+wMCCoMDDdO8/4IRHfaVINqL78GO8dW8G2lW/e45Mcw==", + "path": "system.runtime.serialization.primitives/4.3.0", + "hashPath": "system.runtime.serialization.primitives.4.3.0.nupkg.sha512" + }, "System.Security.AccessControl/4.7.0": { "type": "package", "serviceable": true, @@ -4437,6 +3885,13 @@ "path": "system.text.encoding/4.3.0", "hashPath": "system.text.encoding.4.3.0.nupkg.sha512" }, + "System.Text.Encoding.CodePages/4.3.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-IRiEFUa5b/Gs5Egg8oqBVoywhtOeaO2KOx3j0RfcYY/raxqBuEK7NXRDgOwtYM8qbi+7S4RPXUbNt+ZxyY0/NQ==", + "path": "system.text.encoding.codepages/4.3.0", + "hashPath": "system.text.encoding.codepages.4.3.0.nupkg.sha512" + }, "System.Text.Encoding.Extensions/4.3.0": { "type": "package", "serviceable": true, @@ -4458,12 +3913,12 @@ "path": "system.text.json/10.0.0", "hashPath": "system.text.json.10.0.0.nupkg.sha512" }, - "System.Text.RegularExpressions/4.3.0": { + "System.Text.RegularExpressions/4.3.1": { "type": "package", "serviceable": true, - "sha512": "sha512-RpT2DA+L660cBt1FssIE9CAGpLFdFPuheB7pLpKpn6ZXNby7jDERe8Ua/Ne2xGiwLVG2JOqziiaVCGDon5sKFA==", - "path": "system.text.regularexpressions/4.3.0", - "hashPath": "system.text.regularexpressions.4.3.0.nupkg.sha512" + "sha512": "sha512-N0kNRrWe4+nXOWlpLT4LAY5brb8caNFlUuIRpraCVMDLYutKkol1aV079rQjLuSxKMJT2SpBQsYX9xbcTMmzwg==", + "path": "system.text.regularexpressions/4.3.1", + "hashPath": "system.text.regularexpressions.4.3.1.nupkg.sha512" }, "System.Threading/4.3.0": { "type": "package", @@ -4472,6 +3927,13 @@ "path": "system.threading/4.3.0", "hashPath": "system.threading.4.3.0.nupkg.sha512" }, + "System.Threading.Overlapped/4.3.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-m3HQ2dPiX/DSTpf+yJt8B0c+SRvzfqAJKx+QDWi+VLhz8svLT23MVjEOHPF/KiSLeArKU/iHescrbLd3yVgyNg==", + "path": "system.threading.overlapped/4.3.0", + "hashPath": "system.threading.overlapped.4.3.0.nupkg.sha512" + }, "System.Threading.Tasks/4.3.0": { "type": "package", "serviceable": true, @@ -4486,6 +3948,20 @@ "path": "system.threading.tasks.extensions/4.3.0", "hashPath": "system.threading.tasks.extensions.4.3.0.nupkg.sha512" }, + "System.Threading.Thread/4.3.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-OHmbT+Zz065NKII/ZHcH9XO1dEuLGI1L2k7uYss+9C1jLxTC9kTZZuzUOyXHayRk+dft9CiDf3I/QZ0t8JKyBQ==", + "path": "system.threading.thread/4.3.0", + "hashPath": "system.threading.thread.4.3.0.nupkg.sha512" + }, + "System.Threading.ThreadPool/4.3.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-k/+g4b7vjdd4aix83sTgC9VG6oXYKAktSfNIJUNGxPEj7ryEOfzHHhfnmsZvjxawwcD9HyWXKCXmPjX8U4zeSw==", + "path": "system.threading.threadpool/4.3.0", + "hashPath": "system.threading.threadpool.4.3.0.nupkg.sha512" + }, "System.Threading.Timer/4.3.0": { "type": "package", "serviceable": true, @@ -4507,6 +3983,13 @@ "path": "system.xml.xdocument/4.3.0", "hashPath": "system.xml.xdocument.4.3.0.nupkg.sha512" }, + "System.Xml.XmlDocument/4.3.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-lJ8AxvkX7GQxpC6GFCeBj8ThYVyQczx2+f/cWHJU8tjS7YfI6Cv6bon70jVEgs2CiFbmmM8b9j1oZVx0dSI2Ww==", + "path": "system.xml.xmldocument/4.3.0", + "hashPath": "system.xml.xmldocument.4.3.0.nupkg.sha512" + }, "Telerik.UI.for.Wpf.NetCore.Xaml/2024.1.408": { "type": "package", "serviceable": true, @@ -4569,6 +4052,11 @@ "serviceable": false, "sha512": "" }, + "XP.ReportEngine/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + }, "BR.AN.PviServices/1.1.0.0": { "type": "reference", "serviceable": false, diff --git a/ReleaseFiles/XplorePlane.dll b/ReleaseFiles/XplorePlane.dll index 25a23cf..8f80c5a 100644 Binary files a/ReleaseFiles/XplorePlane.dll and b/ReleaseFiles/XplorePlane.dll differ diff --git a/ReleaseFiles/XplorePlane.dll.config b/ReleaseFiles/XplorePlane.dll.config index 7465d52..7123378 100644 --- a/ReleaseFiles/XplorePlane.dll.config +++ b/ReleaseFiles/XplorePlane.dll.config @@ -7,7 +7,10 @@ - + + + + @@ -122,7 +125,6 @@ - @@ -148,7 +150,7 @@ - + @@ -161,6 +163,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ReleaseFiles/XplorePlane.exe b/ReleaseFiles/XplorePlane.exe index c6c35c7..c60cd80 100644 Binary files a/ReleaseFiles/XplorePlane.exe and b/ReleaseFiles/XplorePlane.exe differ diff --git a/ReleaseFiles/XplorePlane.pdb b/ReleaseFiles/XplorePlane.pdb index e5dbb6b..05fa658 100644 Binary files a/ReleaseFiles/XplorePlane.pdb and b/ReleaseFiles/XplorePlane.pdb differ diff --git a/ReleaseFiles/XplorePlane.runtimeconfig.json b/ReleaseFiles/XplorePlane.runtimeconfig.json index 1dc0145..b2dedf3 100644 --- a/ReleaseFiles/XplorePlane.runtimeconfig.json +++ b/ReleaseFiles/XplorePlane.runtimeconfig.json @@ -12,7 +12,8 @@ } ], "configProperties": { - "System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": true + "System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": true, + "CSWINRT_USE_WINDOWS_UI_XAML_PROJECTIONS": false } } } \ No newline at end of file diff --git a/ReleaseFiles/Y.Tracing.Configuration.txt b/ReleaseFiles/Y.Tracing.Configuration.txt new file mode 100644 index 0000000..6b53ce9 --- /dev/null +++ b/ReleaseFiles/Y.Tracing.Configuration.txt @@ -0,0 +1,4 @@ +// Flag, ob die Trace-Meldungen asynchron zu protokolliern sind: 1 fr asynchron, 0 fr synchron. +1 +// Kategorie Konfigurationen: +FXDriver 0 diff --git a/ReleaseFiles/en-US/XP.Camera.resources.dll b/ReleaseFiles/en-US/XP.Camera.resources.dll index 6b54249..0467459 100644 Binary files a/ReleaseFiles/en-US/XP.Camera.resources.dll and b/ReleaseFiles/en-US/XP.Camera.resources.dll differ diff --git a/ReleaseFiles/en-US/XP.Common.resources.dll b/ReleaseFiles/en-US/XP.Common.resources.dll index d7a9c9d..5b2d299 100644 Binary files a/ReleaseFiles/en-US/XP.Common.resources.dll and b/ReleaseFiles/en-US/XP.Common.resources.dll differ diff --git a/ReleaseFiles/en-US/XP.Hardware.Detector.resources.dll b/ReleaseFiles/en-US/XP.Hardware.Detector.resources.dll index bf52a80..27b97b8 100644 Binary files a/ReleaseFiles/en-US/XP.Hardware.Detector.resources.dll and b/ReleaseFiles/en-US/XP.Hardware.Detector.resources.dll differ diff --git a/ReleaseFiles/en-US/XP.Hardware.MotionControl.resources.dll b/ReleaseFiles/en-US/XP.Hardware.MotionControl.resources.dll index 6cb548f..85e1fd4 100644 Binary files a/ReleaseFiles/en-US/XP.Hardware.MotionControl.resources.dll and b/ReleaseFiles/en-US/XP.Hardware.MotionControl.resources.dll differ diff --git a/ReleaseFiles/en-US/XP.Hardware.PLC.resources.dll b/ReleaseFiles/en-US/XP.Hardware.PLC.resources.dll index f84b5a5..facd652 100644 Binary files a/ReleaseFiles/en-US/XP.Hardware.PLC.resources.dll and b/ReleaseFiles/en-US/XP.Hardware.PLC.resources.dll differ diff --git a/ReleaseFiles/en-US/XP.Hardware.RaySource.resources.dll b/ReleaseFiles/en-US/XP.Hardware.RaySource.resources.dll index 8fb508c..78bd8c2 100644 Binary files a/ReleaseFiles/en-US/XP.Hardware.RaySource.resources.dll and b/ReleaseFiles/en-US/XP.Hardware.RaySource.resources.dll differ diff --git a/ReleaseFiles/en-US/XP.ReportEngine.resources.dll b/ReleaseFiles/en-US/XP.ReportEngine.resources.dll new file mode 100644 index 0000000..a525426 Binary files /dev/null and b/ReleaseFiles/en-US/XP.ReportEngine.resources.dll differ diff --git a/ReleaseFiles/itext.barcodes.dll b/ReleaseFiles/itext.barcodes.dll new file mode 100644 index 0000000..00eafd0 Binary files /dev/null and b/ReleaseFiles/itext.barcodes.dll differ diff --git a/ReleaseFiles/itext.bouncy-castle-adapter.dll b/ReleaseFiles/itext.bouncy-castle-adapter.dll new file mode 100644 index 0000000..c665389 Binary files /dev/null and b/ReleaseFiles/itext.bouncy-castle-adapter.dll differ diff --git a/ReleaseFiles/itext.bouncy-castle-connector.dll b/ReleaseFiles/itext.bouncy-castle-connector.dll new file mode 100644 index 0000000..cb4618a Binary files /dev/null and b/ReleaseFiles/itext.bouncy-castle-connector.dll differ diff --git a/ReleaseFiles/itext.commons.dll b/ReleaseFiles/itext.commons.dll new file mode 100644 index 0000000..7e1ac5a Binary files /dev/null and b/ReleaseFiles/itext.commons.dll differ diff --git a/ReleaseFiles/itext.forms.dll b/ReleaseFiles/itext.forms.dll new file mode 100644 index 0000000..6cca33a Binary files /dev/null and b/ReleaseFiles/itext.forms.dll differ diff --git a/ReleaseFiles/itext.io.dll b/ReleaseFiles/itext.io.dll new file mode 100644 index 0000000..816b041 Binary files /dev/null and b/ReleaseFiles/itext.io.dll differ diff --git a/ReleaseFiles/itext.kernel.dll b/ReleaseFiles/itext.kernel.dll new file mode 100644 index 0000000..3cb7dc3 Binary files /dev/null and b/ReleaseFiles/itext.kernel.dll differ diff --git a/ReleaseFiles/itext.layout.dll b/ReleaseFiles/itext.layout.dll new file mode 100644 index 0000000..1e4b6c9 Binary files /dev/null and b/ReleaseFiles/itext.layout.dll differ diff --git a/ReleaseFiles/itext.pdfa.dll b/ReleaseFiles/itext.pdfa.dll new file mode 100644 index 0000000..80d49b2 Binary files /dev/null and b/ReleaseFiles/itext.pdfa.dll differ diff --git a/ReleaseFiles/itext.pdfua.dll b/ReleaseFiles/itext.pdfua.dll new file mode 100644 index 0000000..3fedc4f Binary files /dev/null and b/ReleaseFiles/itext.pdfua.dll differ diff --git a/ReleaseFiles/itext.sign.dll b/ReleaseFiles/itext.sign.dll new file mode 100644 index 0000000..0c7e1cf Binary files /dev/null and b/ReleaseFiles/itext.sign.dll differ diff --git a/ReleaseFiles/itext.styledxmlparser.dll b/ReleaseFiles/itext.styledxmlparser.dll new file mode 100644 index 0000000..7d2a96c Binary files /dev/null and b/ReleaseFiles/itext.styledxmlparser.dll differ diff --git a/ReleaseFiles/itext.svg.dll b/ReleaseFiles/itext.svg.dll new file mode 100644 index 0000000..9ddd289 Binary files /dev/null and b/ReleaseFiles/itext.svg.dll differ diff --git a/ReleaseFiles/zh-CN/XP.Common.resources.dll b/ReleaseFiles/zh-CN/XP.Common.resources.dll index 3a415f1..374d52e 100644 Binary files a/ReleaseFiles/zh-CN/XP.Common.resources.dll and b/ReleaseFiles/zh-CN/XP.Common.resources.dll differ diff --git a/ReleaseFiles/zh-CN/XP.Hardware.Detector.resources.dll b/ReleaseFiles/zh-CN/XP.Hardware.Detector.resources.dll index 773b80e..d714c9c 100644 Binary files a/ReleaseFiles/zh-CN/XP.Hardware.Detector.resources.dll and b/ReleaseFiles/zh-CN/XP.Hardware.Detector.resources.dll differ diff --git a/ReleaseFiles/zh-CN/XP.Hardware.MotionControl.resources.dll b/ReleaseFiles/zh-CN/XP.Hardware.MotionControl.resources.dll index 7219b8b..c18ff74 100644 Binary files a/ReleaseFiles/zh-CN/XP.Hardware.MotionControl.resources.dll and b/ReleaseFiles/zh-CN/XP.Hardware.MotionControl.resources.dll differ diff --git a/ReleaseFiles/zh-CN/XP.Hardware.PLC.resources.dll b/ReleaseFiles/zh-CN/XP.Hardware.PLC.resources.dll index 9c19a09..7f90aa5 100644 Binary files a/ReleaseFiles/zh-CN/XP.Hardware.PLC.resources.dll and b/ReleaseFiles/zh-CN/XP.Hardware.PLC.resources.dll differ diff --git a/ReleaseFiles/zh-CN/XP.Hardware.RaySource.resources.dll b/ReleaseFiles/zh-CN/XP.Hardware.RaySource.resources.dll index 50753d8..648d5a6 100644 Binary files a/ReleaseFiles/zh-CN/XP.Hardware.RaySource.resources.dll and b/ReleaseFiles/zh-CN/XP.Hardware.RaySource.resources.dll differ diff --git a/ReleaseFiles/zh-CN/XP.ImageProcessing.CfgControl.resources.dll b/ReleaseFiles/zh-CN/XP.ImageProcessing.CfgControl.resources.dll index a2bf31a..b7786dd 100644 Binary files a/ReleaseFiles/zh-CN/XP.ImageProcessing.CfgControl.resources.dll and b/ReleaseFiles/zh-CN/XP.ImageProcessing.CfgControl.resources.dll differ diff --git a/ReleaseFiles/zh-CN/XP.ImageProcessing.Processors.resources.dll b/ReleaseFiles/zh-CN/XP.ImageProcessing.Processors.resources.dll index 2321969..6441b5e 100644 Binary files a/ReleaseFiles/zh-CN/XP.ImageProcessing.Processors.resources.dll and b/ReleaseFiles/zh-CN/XP.ImageProcessing.Processors.resources.dll differ diff --git a/ReleaseFiles/zh-CN/XP.ReportEngine.resources.dll b/ReleaseFiles/zh-CN/XP.ReportEngine.resources.dll new file mode 100644 index 0000000..0bfffa6 Binary files /dev/null and b/ReleaseFiles/zh-CN/XP.ReportEngine.resources.dll differ diff --git a/ReleaseFiles/zh-TW/XP.Common.resources.dll b/ReleaseFiles/zh-TW/XP.Common.resources.dll index 3c36a0e..e984f03 100644 Binary files a/ReleaseFiles/zh-TW/XP.Common.resources.dll and b/ReleaseFiles/zh-TW/XP.Common.resources.dll differ diff --git a/ReleaseFiles/zh-TW/XP.Hardware.Detector.resources.dll b/ReleaseFiles/zh-TW/XP.Hardware.Detector.resources.dll index c914527..2e81501 100644 Binary files a/ReleaseFiles/zh-TW/XP.Hardware.Detector.resources.dll and b/ReleaseFiles/zh-TW/XP.Hardware.Detector.resources.dll differ diff --git a/ReleaseFiles/zh-TW/XP.Hardware.MotionControl.resources.dll b/ReleaseFiles/zh-TW/XP.Hardware.MotionControl.resources.dll index 993cae2..a74c4a4 100644 Binary files a/ReleaseFiles/zh-TW/XP.Hardware.MotionControl.resources.dll and b/ReleaseFiles/zh-TW/XP.Hardware.MotionControl.resources.dll differ diff --git a/ReleaseFiles/zh-TW/XP.Hardware.PLC.resources.dll b/ReleaseFiles/zh-TW/XP.Hardware.PLC.resources.dll index 73a659b..1bdde25 100644 Binary files a/ReleaseFiles/zh-TW/XP.Hardware.PLC.resources.dll and b/ReleaseFiles/zh-TW/XP.Hardware.PLC.resources.dll differ diff --git a/ReleaseFiles/zh-TW/XP.Hardware.RaySource.resources.dll b/ReleaseFiles/zh-TW/XP.Hardware.RaySource.resources.dll index ccf0a59..d280028 100644 Binary files a/ReleaseFiles/zh-TW/XP.Hardware.RaySource.resources.dll and b/ReleaseFiles/zh-TW/XP.Hardware.RaySource.resources.dll differ diff --git a/ReleaseFiles/zh-TW/XP.ReportEngine.resources.dll b/ReleaseFiles/zh-TW/XP.ReportEngine.resources.dll new file mode 100644 index 0000000..73fad13 Binary files /dev/null and b/ReleaseFiles/zh-TW/XP.ReportEngine.resources.dll differ diff --git a/XP.ReportEngine/Configs/ConfigLoader.cs b/XP.ReportEngine/Configs/ConfigLoader.cs new file mode 100644 index 0000000..d2516cd --- /dev/null +++ b/XP.ReportEngine/Configs/ConfigLoader.cs @@ -0,0 +1,221 @@ +using System; +using System.Configuration; +using System.IO; +using XP.Common.Logging.Interfaces; + +namespace XP.ReportEngine.Configs +{ + /// + /// 报告引擎配置加载器(读取 App.config)| Report engine configuration loader (reads from App.config) + /// + public class ConfigLoader + { + private readonly ILoggerService _logger; + + /// + /// 构造函数 | Constructor + /// + /// 日志服务 | Logger service + public ConfigLoader(ILoggerService logger) + { + _logger = logger.ForModule(); + } + + /// + /// 从 App.config 加载报告引擎配置 | Load report engine configuration from App.config + /// + /// 配置前缀,默认为 "Report" | Configuration prefix, default is "Report" + /// 报告配置对象 | Report configuration object + public ReportConfig LoadReportConfig(string prefix = "Report") + { + try + { + _logger.Info("开始从 App.config 加载报告引擎配置,前缀: {Prefix} | Loading report config from App.config, prefix: {Prefix}", prefix); + + var config = new ReportConfig(); + + // 读取输出目录 | Read output directory + var outputDirectory = ConfigurationManager.AppSettings[$"{prefix}:OutputDirectory"]; + if (!string.IsNullOrEmpty(outputDirectory)) + { + config.OutputDirectory = outputDirectory; + } + + // 读取模板路径 | Read template path + var templatePath = ConfigurationManager.AppSettings[$"{prefix}:TemplatePath"]; + if (!string.IsNullOrEmpty(templatePath)) + { + config.TemplatePath = templatePath; + } + + // 读取文件名模式 | Read file name pattern + var fileNamePattern = ConfigurationManager.AppSettings[$"{prefix}:FileNamePattern"]; + if (!string.IsNullOrEmpty(fileNamePattern)) + { + config.FileNamePattern = fileNamePattern; + } + + // 读取重复文件名自动累加设置 | Read auto-increment on duplicate setting + var autoIncrement = ConfigurationManager.AppSettings[$"{prefix}:AutoIncrementOnDuplicate"]; + if (bool.TryParse(autoIncrement, out var autoIncrementValue)) + { + config.AutoIncrementOnDuplicate = autoIncrementValue; + } + + // 读取自动打开设置 | Read auto-open setting + var autoOpen = ConfigurationManager.AppSettings[$"{prefix}:AutoOpenAfterGenerate"]; + if (bool.TryParse(autoOpen, out var autoOpenValue)) + { + config.AutoOpenAfterGenerate = autoOpenValue; + } + + // 读取页面尺寸 | Read page size + var pageSize = ConfigurationManager.AppSettings[$"{prefix}:DefaultPageSize"]; + if (!string.IsNullOrEmpty(pageSize)) + { + config.DefaultPageSize = pageSize; + } + + // 读取页面方向 | Read page orientation + var orientation = ConfigurationManager.AppSettings[$"{prefix}:DefaultOrientation"]; + if (!string.IsNullOrEmpty(orientation)) + { + config.DefaultOrientation = orientation; + } + + // 读取边距配置 | Read margin configuration + var marginTop = ConfigurationManager.AppSettings[$"{prefix}:MarginTop"]; + if (float.TryParse(marginTop, out var marginTopValue)) + { + config.MarginTop = marginTopValue; + } + + var marginBottom = ConfigurationManager.AppSettings[$"{prefix}:MarginBottom"]; + if (float.TryParse(marginBottom, out var marginBottomValue)) + { + config.MarginBottom = marginBottomValue; + } + + var marginLeft = ConfigurationManager.AppSettings[$"{prefix}:MarginLeft"]; + if (float.TryParse(marginLeft, out var marginLeftValue)) + { + config.MarginLeft = marginLeftValue; + } + + var marginRight = ConfigurationManager.AppSettings[$"{prefix}:MarginRight"]; + if (float.TryParse(marginRight, out var marginRightValue)) + { + config.MarginRight = marginRightValue; + } + + // 读取公司名称 | Read company name + var companyName = ConfigurationManager.AppSettings[$"{prefix}:CompanyName"]; + if (!string.IsNullOrEmpty(companyName)) + { + config.CompanyName = companyName; + } + + // 读取公司 Logo 路径 | Read company logo path + var companyLogo = ConfigurationManager.AppSettings[$"{prefix}:CompanyLogo"]; + if (!string.IsNullOrEmpty(companyLogo)) + { + config.CompanyLogo = companyLogo; + } + + // 读取软件名称 | Read software name + var softwareName = ConfigurationManager.AppSettings[$"{prefix}:SoftwareName"]; + if (!string.IsNullOrEmpty(softwareName)) + { + config.SoftwareName = softwareName; + } + + // 读取软件 Logo 路径 | Read software logo path + var softwareLogo = ConfigurationManager.AppSettings[$"{prefix}:SoftwareLogo"]; + if (!string.IsNullOrEmpty(softwareLogo)) + { + config.SoftwareLogo = softwareLogo; + } + + // 验证配置 | Validate configuration + ValidateConfig(config); + + _logger.Info("报告引擎配置加载成功:输出目录={OutputDir}, 模板={Template} | Report config loaded: OutputDir={OutputDir}, Template={Template}", + config.OutputDirectory, config.TemplatePath); + + return config; + } + catch (Exception ex) + { + _logger.Warn("加载报告引擎配置失败,使用默认配置 | Failed to load report config, using defaults: {Message}", ex.Message); + return new ReportConfig(); + } + } + + /// + /// 验证配置参数 | Validate configuration parameters + /// + /// 报告配置对象 | Report configuration object + private void ValidateConfig(ReportConfig config) + { + // 验证输出目录(不存在则尝试创建)| Validate output directory (create if not exists) + if (string.IsNullOrWhiteSpace(config.OutputDirectory)) + { + _logger.Warn("OutputDirectory 为空,使用默认值 | OutputDirectory is empty, using default"); + config.OutputDirectory = new ReportConfig().OutputDirectory; + } + + // 验证模板路径 | Validate template path + if (string.IsNullOrWhiteSpace(config.TemplatePath)) + { + _logger.Warn("TemplatePath 为空,使用默认值 | TemplatePath is empty, using default"); + config.TemplatePath = @"Templates\StandardReportTemplate.json"; + } + + // 验证文件名模式 | Validate file name pattern + if (string.IsNullOrWhiteSpace(config.FileNamePattern)) + { + _logger.Warn("FileNamePattern 为空,使用默认值 | FileNamePattern is empty, using default"); + config.FileNamePattern = "{ReportId}"; + } + + // 验证页面方向 | Validate page orientation + if (config.DefaultOrientation != "Portrait" && config.DefaultOrientation != "Landscape") + { + _logger.Warn("DefaultOrientation 无效: {Value},使用默认值 Portrait | DefaultOrientation invalid: {Value}, using default Portrait", config.DefaultOrientation); + config.DefaultOrientation = "Portrait"; + } + + // 验证边距范围(0-100mm)| Validate margin range (0-100mm) + config.MarginTop = ClampMargin(config.MarginTop, "MarginTop"); + config.MarginBottom = ClampMargin(config.MarginBottom, "MarginBottom"); + config.MarginLeft = ClampMargin(config.MarginLeft, "MarginLeft"); + config.MarginRight = ClampMargin(config.MarginRight, "MarginRight"); + + // 验证 Logo 路径(如果配置了则检查文件是否存在)| Validate logo path (check file exists if configured) + if (!string.IsNullOrEmpty(config.CompanyLogo)) + { + var logoPath = Path.IsPathRooted(config.CompanyLogo) + ? config.CompanyLogo + : Path.Combine(AppDomain.CurrentDomain.BaseDirectory, config.CompanyLogo); + + if (!File.Exists(logoPath)) + { + _logger.Warn("公司 Logo 文件不存在: {Path},将不显示 Logo | Company logo file not found: {Path}, logo will not be displayed", logoPath); + } + } + } + + /// + /// 将边距值限制在有效范围内 | Clamp margin value to valid range + /// + private float ClampMargin(float value, string name) + { + if (value < 0 || value > 100) + { + _logger.Warn("{Name} 超出有效范围 [0, 100]: {Value},使用默认值 20 | {Name} out of valid range [0, 100]: {Value}, using default 20", name, value); + return 20f; + } + return value; + } + } +} diff --git a/XP.ReportEngine/Configs/ReportConfig.cs b/XP.ReportEngine/Configs/ReportConfig.cs new file mode 100644 index 0000000..24e5365 --- /dev/null +++ b/XP.ReportEngine/Configs/ReportConfig.cs @@ -0,0 +1,197 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace XP.ReportEngine.Configs +{ + /// + /// 报告引擎配置模型 | Report engine configuration model + /// + public class ReportConfig + { + /// + /// 报告输出文件夹路径 | Report output directory path + /// + public string OutputDirectory { get; set; } = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), + "XplorePlane", "Reports"); + + /// + /// 报告模板文件路径(相对或绝对)| Report template file path (relative or absolute) + /// + public string TemplatePath { get; set; } = @"Templates\StandardReportTemplate.json"; + + /// + /// 输出文件名模式,支持占位符 | Output file name pattern, supports placeholders + /// 支持的占位符 | Supported placeholders: + /// {ReportId} - 报告编号(如 RPT-20250512-001) + /// {CncProgram} - CNC 程序名称 + /// {ProductName} - 产品名称 + /// {ProductCode} - 产品类型码 + /// {WorkpieceSN} - 工件 SN 码 + /// {DeviceId} - 检测设备编号(本机) + /// {MachineId} - 生产机台号 + /// {Date} - 日期(yyyyMMdd) + /// {Time} - 时间(HHmmss) + /// {Result} - 综合检测结论(Pass/Fail) + /// + public string FileNamePattern { get; set; } = "{ReportId}"; + + /// + /// 文件名重复时是否自动累加序号 | Whether to auto-increment suffix when file name duplicates + /// true: 重复时生成 filename(1).pdf, filename(2).pdf ... + /// false: 直接覆盖同名文件 + /// + public bool AutoIncrementOnDuplicate { get; set; } = true; + + /// + /// 生成后是否自动打开 PDF 阅读器 | Whether to auto-open PDF viewer after generation + /// + public bool AutoOpenAfterGenerate { get; set; } = false; + + /// + /// 默认页面尺寸 | Default page size + /// + public string DefaultPageSize { get; set; } = "A4"; + + /// + /// 默认页面方向(Portrait / Landscape)| Default page orientation + /// + public string DefaultOrientation { get; set; } = "Portrait"; + + /// + /// 默认上边距(mm)| Default top margin (mm) + /// + public float MarginTop { get; set; } = 20f; + + /// + /// 默认下边距(mm)| Default bottom margin (mm) + /// + public float MarginBottom { get; set; } = 20f; + + /// + /// 默认左边距(mm)| Default left margin (mm) + /// + public float MarginLeft { get; set; } = 20f; + + /// + /// 默认右边距(mm)| Default right margin (mm) + /// + public float MarginRight { get; set; } = 20f; + + /// + /// 报告中显示的公司名称 | Company name displayed in report + /// + public string CompanyName { get; set; } = "海克斯康制造智能技术(青岛)有限公司"; + + /// + /// 公司 Logo 图片路径(可选,为空则不显示)| Company logo image path (optional, empty means no logo) + /// + public string CompanyLogo { get; set; } = string.Empty; + + /// + /// 报告中显示的软件名称 | Software name displayed in report + /// + public string SoftwareName { get; set; } = "XplorePlane"; + + /// + /// 软件 Logo 图片路径(可选,为空则不显示)| Software logo image path (optional, empty means no logo) + /// + public string SoftwareLogo { get; set; } = string.Empty; + + /// + /// 获取解析后的模板绝对路径 | Get resolved absolute template path + /// 如果 TemplatePath 是相对路径,则基于应用程序目录解析 + /// If TemplatePath is relative, resolves based on application directory + /// + public string GetResolvedTemplatePath() + { + if (Path.IsPathRooted(TemplatePath)) + { + return TemplatePath; + } + return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, TemplatePath); + } + + /// + /// 根据文件名模式和上下文参数生成实际文件名 | Generate actual file name based on pattern and context parameters + /// + /// 占位符参数字典 | Placeholder parameter dictionary + /// 生成的文件名(不含扩展名)| Generated file name (without extension) + public string ResolveFileName(Dictionary parameters) + { + var fileName = FileNamePattern; + + // 替换所有已知占位符 | Replace all known placeholders + if (parameters != null) + { + foreach (var kvp in parameters) + { + fileName = fileName.Replace($"{{{kvp.Key}}}", SanitizeFileName(kvp.Value ?? "")); + } + } + + // 替换日期和时间(始终可用)| Replace date and time (always available) + fileName = fileName.Replace("{Date}", DateTime.Now.ToString("yyyyMMdd")); + fileName = fileName.Replace("{Time}", DateTime.Now.ToString("HHmmss")); + + // 清理未被替换的占位符(替换为空)| Clean up unreplaced placeholders + fileName = System.Text.RegularExpressions.Regex.Replace(fileName, @"\{[^}]+\}", ""); + + // 移除连续的分隔符 | Remove consecutive separators + fileName = System.Text.RegularExpressions.Regex.Replace(fileName, @"[_\-]{2,}", "_"); + fileName = fileName.Trim('_', '-'); + + return string.IsNullOrWhiteSpace(fileName) ? "Report" : fileName; + } + + /// + /// 解析最终输出文件完整路径(含重复累加逻辑)| Resolve final output file full path (with duplicate increment logic) + /// + /// 占位符参数字典 | Placeholder parameter dictionary + /// 文件扩展名(含点号,如 ".pdf")| File extension (with dot, e.g. ".pdf") + /// 最终输出文件完整路径 | Final output file full path + public string ResolveOutputFilePath(Dictionary parameters, string extension = ".pdf") + { + var baseName = ResolveFileName(parameters); + var outputDir = OutputDirectory; + + // 确保输出目录存在 | Ensure output directory exists + if (!Directory.Exists(outputDir)) + { + Directory.CreateDirectory(outputDir); + } + + var filePath = Path.Combine(outputDir, baseName + extension); + + // 重复累加逻辑 | Duplicate increment logic + if (AutoIncrementOnDuplicate && File.Exists(filePath)) + { + int counter = 1; + string newPath; + do + { + newPath = Path.Combine(outputDir, $"{baseName}({counter}){extension}"); + counter++; + } while (File.Exists(newPath)); + + filePath = newPath; + } + + return filePath; + } + + /// + /// 清理文件名中的非法字符 | Sanitize illegal characters in file name + /// + private static string SanitizeFileName(string name) + { + var invalidChars = Path.GetInvalidFileNameChars(); + foreach (var c in invalidChars) + { + name = name.Replace(c, '_'); + } + return name; + } + } +} diff --git a/XP.ReportEngine/Documents/App.config.example b/XP.ReportEngine/Documents/App.config.example new file mode 100644 index 0000000..b48ec6e --- /dev/null +++ b/XP.ReportEngine/Documents/App.config.example @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/XP.ReportEngine/Documents/FontFilesGuidance.md b/XP.ReportEngine/Documents/FontFilesGuidance.md new file mode 100644 index 0000000..63d4ff5 --- /dev/null +++ b/XP.ReportEngine/Documents/FontFilesGuidance.md @@ -0,0 +1,73 @@ +# 字体方案说明 | Font Strategy + +## 当前方案 | Current Approach + +XP.ReportEngine 使用 **Windows 系统自带字体** 生成 PDF,无需额外下载或嵌入字体文件。 + +| 语言 | 字体 | 文件 | 说明 | +|------|------|------|------| +| 简体中文 / 繁体中文 | 微软雅黑 | `C:\Windows\Fonts\msyh.ttc` | Windows 10+ 自带,支持简繁体 | +| 西文(英文等) | Arial | `C:\Windows\Fonts\arial.ttf` | Windows 自带 | + +## 为什么使用系统字体 | Why System Fonts + +1. **Telerik RadPdfViewer 兼容性** — 系统字体生成的 PDF 在 Telerik RadPdfViewer 中显示正常,不会出现中文乱码 +2. **无需额外部署** — 不需要随应用程序分发字体文件,减小安装包体积 +3. **跨阅读器兼容** — 字体以子集嵌入 PDF 中(`PREFER_EMBEDDED`),在任何 PDF 阅读器中都能正确显示 + +## 后备机制 | Fallback Mechanism + +`ITextPdfRenderer` 按以下顺序加载字体: + +``` +微软雅黑 (msyh.ttc) + ↓ 加载失败 +宋体 (simsun.ttc) + ↓ 加载失败 +_cjkFont = null + +Arial (arial.ttf) + ↓ 加载失败 +_westernFont = null + +最终后备: iText 内置 Helvetica(不支持中文字符) +``` + +语言选择逻辑: +- zh-CN / zh-TW → 优先使用微软雅黑 +- en-US → 优先使用 Arial,后备微软雅黑(微软雅黑也支持西文) + +## 字体子集化 | Font Subsetting + +iText7 在 `document.Close()` 时执行字体子集化: +- 分析文档中实际使用的字符 +- 从完整字体文件(微软雅黑约 15MB)中提取用到的字形子集 +- 仅嵌入子集到 PDF 中 + +这使得最终 PDF 文件大小合理(通常 200KB-2MB),但 `Close()` 操作需要约 1-1.5 秒。 + +## 性能说明 | Performance Notes + +| 阶段 | 首次耗时 | 后续耗时 | 说明 | +|------|---------|---------|------| +| 字体加载 | ~5-6s | 0ms | 首次需从磁盘读取 TTC 文件并解析 | +| 字体子集化 | ~1.2s | ~1.2s | 每次生成都需要,与字符数量相关 | + +模块通过 `WarmUpAsync()` 预热机制在应用启动时后台完成首次字体加载,用户首次生成报告时不会感受到延迟。 + +## 系统要求 | System Requirements + +- Windows 10 或更高版本(微软雅黑为系统预装字体) +- 如果在精简版 Windows 上运行,需确保系统已安装微软雅黑字体 + +## 自定义字体扩展 | Custom Font Extension + +如需添加自定义字体: + +1. 将字体文件(.ttf / .ttc / .otf)放入项目 `Fonts/` 目录 +2. 在 `.csproj` 中取消注释嵌入资源配置: + ```xml + + + ``` +3. 在 `ITextPdfRenderer.InitializeFonts()` 中扩展加载逻辑 diff --git a/XP.ReportEngine/Documents/Guidance.md b/XP.ReportEngine/Documents/Guidance.md new file mode 100644 index 0000000..6eea6f2 --- /dev/null +++ b/XP.ReportEngine/Documents/Guidance.md @@ -0,0 +1,269 @@ +# XP.ReportEngine 使用指南 | Usage Guidance + +## 1. 概述 + +本文档说明如何在 XplorePlane 项目中使用 `XP.ReportEngine` 模块生成 PDF 检测报告。 + +模块提供两种调用方式: +- **推荐**:通过 `IReportService` 门面接口(一行调用,自动处理所有细节) +- **高级**:直接使用底层管线接口(`IReportGenerator`、`IReportDataAdapter` 等) + +## 2. 前置条件 + +### 2.1 项目引用 + +在调用方项目的 `.csproj` 中添加项目引用: + +```xml + +``` + +### 2.2 模块注册 + +确保 `ReportEngineModule` 已在 `XP.App` 的模块目录中注册。模块初始化时会自动: +- 注册所有服务到 DI 容器 +- 注册多语言资源到 Fallback Chain +- 后台执行引擎预热(字体加载 + JIT 编译) + +### 2.3 配置文件 + +在 `App.config` 中添加报告引擎配置项,参见 `Documents/App.config.example`。 + +## 3. 通过 IReportService 生成报告(推荐) + +### 3.1 基本用法 + +```csharp +using XP.ReportEngine.Interfaces; +using XP.ReportEngine.Models; + +public class InspectionService +{ + private readonly IReportService _reportService; + + public InspectionService(IReportService reportService) + { + _reportService = reportService; + } + + public async Task GenerateInspectionReport( + List processorOutputs, + string productName, + string operatorName) + { + var request = new ReportRequest + { + // 必填:处理器输出数据 + ProcessorOutputs = processorOutputs, + + // 必填:报告元数据 + Metadata = new ReportMetadata + { + SampleName = productName, + OperatorName = operatorName, + InspectionDate = DateTime.Now, + Description = "BGA 焊球气泡率检测" + }, + + // 可选:文件名占位符参数(用于 FileNamePattern) + FileNameParameters = new Dictionary + { + ["ProductName"] = productName, + ["ProductCode"] = "PCBA-X100", + ["WorkpieceSN"] = "SN20250001", + ["DeviceId"] = "XP-CT-001", + ["MachineId"] = "MC01", + ["Result"] = "Pass" + } + }; + + var result = await _reportService.GenerateAsync(request); + + if (result.IsSuccess) + { + return result.OutputFilePath; // PDF 文件路径 + } + else + { + throw new Exception($"报告生成失败: {result.ErrorMessage}"); + } + } +} +``` + +### 3.2 指定输出路径 + +```csharp +var request = new ReportRequest +{ + ProcessorOutputs = outputs, + Metadata = metadata, + // 指定输出路径后,不再使用 ReportConfig 中的 OutputDirectory 和 FileNamePattern + OutputFilePath = @"D:\CustomPath\MyReport.pdf" +}; +``` + +### 3.3 注入额外图像 + +```csharp +var request = new ReportRequest +{ + ProcessorOutputs = outputs, + Metadata = metadata, + AdditionalImages = new Dictionary + { + // 工件整体图(首页显示) + ["workpieceImage"] = new ImageData + { + SourceType = ImageSourceType.FilePath, + FilePath = @"D:\Images\workpiece.png" + }, + // 自定义图像 + ["customImage"] = new ImageData + { + SourceType = ImageSourceType.Bytes, + Bytes = imageBytes + } + } +}; +``` + +### 3.4 注入自定义属性 + +```csharp +var request = new ReportRequest +{ + ProcessorOutputs = outputs, + Metadata = metadata, + // 自定义属性会合并到 ReportContext.Properties,可在模板中通过 ${key} 绑定 + CustomProperties = new Dictionary + { + ["batchNumber"] = "BATCH-2025-001", + ["inspectionStation"] = "Station-A" + } +}; +``` + +## 4. ReportRequest 完整字段说明 + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `ProcessorOutputs` | `List` | 是 | 处理器输出数据列表 | +| `Metadata` | `ReportMetadata` | 是 | 报告元数据 | +| `OutputFilePath` | `string` | 否 | 输出路径,为空时自动生成 | +| `FileNameParameters` | `Dictionary` | 否 | 文件名占位符参数 | +| `AdditionalImages` | `Dictionary` | 否 | 额外图像数据 | +| `CustomProperties` | `Dictionary` | 否 | 自定义属性 | + +### ReportMetadata 字段 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `ReportId` | `string` | 报告编号(为空时自动生成 RPT-yyyyMMdd-NNN) | +| `InspectionDate` | `DateTime` | 检测日期 | +| `SampleName` | `string` | 样品/产品名称 | +| `OperatorName` | `string` | 操作员 | +| `Description` | `string` | 描述信息 | + +### ProcessorOutput 字段 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `ProcessorType` | `string` | 处理器类型标识 | +| `OutputData` | `Dictionary` | 输出数据字典 | +| `AnnotatedImage` | `ImageData` | 关联的已标注图像 | + +支持的 ProcessorType: +- `LineMeasurementProcessor` — 线测量 +- `BgaVoidRateProcessor` — BGA 气泡率 +- `VoidMeasurementProcessor` — 空隙测量 +- `FillRateProcessor` — 通孔填锡率 + +## 5. ReportServiceResult 结果说明 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `IsSuccess` | `bool` | 是否成功 | +| `OutputFilePath` | `string` | 输出文件路径(成功时有值) | +| `ReportId` | `string` | 报告编号 | +| `ErrorMessage` | `string` | 错误信息(失败时有值) | +| `Exception` | `Exception` | 异常对象(失败时有值) | + +## 6. 在 ViewModel 中使用(带 UI 交互) + +如果需要进度条、文件对话框等 UI 交互,参考 `ReportDemoViewModel.cs` 的实现模式: + +```csharp +// 在后台线程执行,避免阻塞 UI +var genResult = await Task.Run(() => _reportService.GenerateAsync(request)); +``` + +关键点: +- `IReportService.GenerateAsync` 内部是 CPU 密集型操作(PDF 渲染) +- 必须用 `Task.Run` 推到线程池,否则会阻塞 UI 线程 +- 进度条、对话框等 UI 逻辑留在 ViewModel 中 + +## 7. 高级用法:直接使用底层管线 + +适用于需要自定义管线某个阶段的场景: + +```csharp +// 1. 数据适配 +var adapter = container.Resolve(); +var context = adapter.Adapt(processorOutputs, metadata); + +// 2. 手动修改上下文 +context.Properties["customKey"] = "customValue"; +context.Images["myImage"] = new ImageData { ... }; + +// 3. 调用管线生成 +var generator = container.Resolve(); +var options = new ReportGenerationOptions +{ + TemplatePath = "Templates/StandardReportTemplate.json", + OutputFilePath = @"D:\output.pdf", + Format = ReportOutputFormat.Pdf +}; + +var result = await generator.GenerateAsync(context, options); +``` + +## 8. 预热机制 + +模块启动时自动在后台执行预热,无需手动调用。如果需要在特定时机手动预热: + +```csharp +var reportService = container.Resolve(); +await reportService.WarmUpAsync(); +``` + +预热内容: +- iText7 程序集加载 +- BouncyCastle 加密提供程序注册 +- 微软雅黑字体文件读取和解析(约 5-6 秒) +- JSON 模板反序列化 JIT 编译 +- 管线各阶段代码 JIT 编译 + +预热完成后,首次正式生成报告不会有额外延迟。 + +## 9. 线程安全 + +`IReportService` 内部使用 `SemaphoreSlim(1, 1)` 互斥锁,确保: +- 预热与正式生成不会并发执行 +- 多个并发的 `GenerateAsync` 调用会串行执行 + +这是因为 `ITextPdfRenderer` 的字体字段在每次渲染时重置,并发渲染会导致 iText7 的 `"belongs to other PDF document"` 错误。 + +## 10. 常见问题 + +### Q: 首次生成报告很慢? +A: 正常现象。首次需要加载字体(~5s)+ 字体子集化(~1.2s)。预热机制会在应用启动时后台完成字体加载,用户首次操作时只需等待子集化时间。 + +### Q: PDF 中图像显示为"无图像"占位矩形? +A: 检查 `ProcessorOutput.AnnotatedImage` 或 `ReportRequest.AdditionalImages` 中的图像路径是否正确、文件是否存在。 + +### Q: 报告中某些字段为空? +A: 检查 `ProcessorOutput.OutputData` 中的键名是否与 `ProcessorDataAdapter` 中的映射一致(区分大小写)。 + +### Q: 如何自定义报告模板? +A: 修改 `Templates/StandardReportTemplate.json`,参考 `Documents/XP.ReportEngineModelDefine.md` 中的模板结构定义。 diff --git a/XP.ReportEngine/Documents/README.md b/XP.ReportEngine/Documents/README.md new file mode 100644 index 0000000..a53dc39 --- /dev/null +++ b/XP.ReportEngine/Documents/README.md @@ -0,0 +1,217 @@ +# XP.ReportEngine — PDF 报告生成引擎 + +## 概述 + +XP.ReportEngine 是 XplorePlane 平面 CT 检测系统的 PDF 报告生成模块。负责将检测分析结果(距离测量、BGA 气泡率、空隙测量、通孔填锡率)转换为结构化的 PDF 检测报告。 + +模块采用管线式架构(Pipeline),将报告生成过程分解为五个独立阶段: +**模板加载 → 数据适配 → 数据绑定 → 排版计算 → PDF 渲染** + +对外提供 `IReportService` 门面接口,外部模块只需构建 `ReportRequest` 即可一行调用完成报告生成。 + +## 技术栈 + +| 依赖 | 版本 | 用途 | +|------|------|------| +| .NET 8.0 | net8.0-windows7.0 | 运行时 | +| iText7 | 8.0.5 | PDF 文档生成核心库 | +| itext7.bouncy-castle-adapter | 8.0.5 | iText7 加密支持 | +| Newtonsoft.Json | 13.0.3 | JSON 模板反序列化 | +| Prism.Wpf | 9.0.537 | 模块化框架与 DI | +| Telerik UI for WPF | 2024.1.408 | UI 控件(演示窗口) | +| XP.Common | — | 日志、本地化、通用窗体、PDF 阅读器 | + +## 项目结构 + +``` +XP.ReportEngine/ +├── Configs/ # 配置加载 +│ ├── ConfigLoader.cs +│ └── ReportConfig.cs +├── Interfaces/ # 核心接口定义 +│ ├── IReportService.cs ★ 门面接口(外部调用入口) +│ ├── IReportGenerator.cs +│ ├── IReportGeneratorFactory.cs +│ ├── ITemplateEngine.cs +│ ├── IDataBinder.cs +│ ├── ILayoutEngine.cs +│ ├── IPdfRenderer.cs +│ └── IReportDataAdapter.cs +├── Models/ # 数据模型 +│ ├── ReportRequest.cs ★ 报告生成请求 +│ ├── ReportServiceResult.cs ★ 报告服务结果 +│ ├── ReportContext.cs +│ ├── ReportMetadata.cs +│ ├── ImageData.cs +│ ├── ReportTemplate.cs +│ ├── TemplateElement.cs +│ ├── LayoutPage.cs +│ ├── ReportResult.cs +│ ├── ReportGenerationOptions.cs +│ ├── ProcessorOutput.cs +│ └── TemplateValidationResult.cs +├── Services/ # 服务实现 +│ ├── ReportService.cs ★ 门面服务实现 +│ ├── PdfReportGenerator.cs # 管线协调器 +│ ├── ReportGeneratorFactory.cs # 工厂 +│ ├── JsonTemplateEngine.cs # JSON 模板加载与验证 +│ ├── ExpressionDataBinder.cs # ${} 表达式数据绑定 +│ ├── PageLayoutEngine.cs # 分页与排版 +│ ├── ITextPdfRenderer.cs # iText7 PDF 渲染 +│ ├── ProcessorDataAdapter.cs # 处理器数据适配 +│ └── ReportIdGenerator.cs # 报告编号生成(RPT-yyyyMMdd-NNN) +├── Templates/ # JSON 报告模板 +│ └── StandardReportTemplate.json +├── Resources/ # 多语言资源文件 +│ ├── Resources.resx +│ ├── Resources.zh-CN.resx +│ ├── Resources.zh-TW.resx +│ └── Resources.en-US.resx +├── ViewModels/ # 演示窗口 ViewModel +│ └── ReportDemoViewModel.cs +├── Views/ # 演示窗口 View +│ ├── ReportDemoWindow.xaml +│ └── ReportDemoWindow.xaml.cs +├── Documents/ # 项目文档 +│ ├── README.md ← 本文件 +│ ├── Guidance.md # 使用指南 +│ ├── App.config.example # 配置示例 +│ ├── FontFilesGuidance.md # 字体方案说明 +│ ├── XP.ReportEngineDesign.md # 架构设计文档 +│ └── XP.ReportEngineModelDefine.md # 模型定义文档 +├── ReportEngineModule.cs # Prism 模块入口 +└── XP.ReportEngine.csproj +``` + +## 核心接口 + +| 接口 | 实现类 | 职责 | +|------|--------|------| +| **`IReportService`** | **`ReportService`** | **门面接口,外部模块调用入口** | +| `IReportGenerator` | `PdfReportGenerator` | 协调管线各阶段,生成 PDF | +| `IReportGeneratorFactory` | `ReportGeneratorFactory` | 根据格式创建对应生成器 | +| `ITemplateEngine` | `JsonTemplateEngine` | JSON 模板加载、反序列化、验证 | +| `IDataBinder` | `ExpressionDataBinder` | `${}` 表达式解析与数据绑定 | +| `ILayoutEngine` | `PageLayoutEngine` | 分页、元素定位、表格跨页 | +| `IPdfRenderer` | `ITextPdfRenderer` | 使用 iText7 渲染 PDF | +| `IReportDataAdapter` | `ProcessorDataAdapter` | ProcessorOutput → ReportContext 转换 | + +## 架构分层 + +``` +┌─────────────────────────────────────────────────────┐ +│ 外部调用方(XP.App、其他模块) │ +│ 注入 IReportService,传入 ReportRequest │ +└──────────────────────┬──────────────────────────────┘ + │ +┌──────────────────────▼──────────────────────────────┐ +│ ReportService(门面层) │ +│ 生成报告ID → 解析输出路径 → 数据适配 → 注入配置数据 │ +└──────────────────────┬──────────────────────────────┘ + │ +┌──────────────────────▼──────────────────────────────┐ +│ PdfReportGenerator(管线协调层) │ +│ 模板加载 → 数据绑定 → 排版计算 → PDF 渲染 → 保存 │ +└─────────────────────────────────────────────────────┘ +``` + +## 快速开始 + +详细使用指南请参阅 `Documents/Guidance.md`。 + +### 最简调用(通过 IReportService) + +```csharp +public class MyService +{ + private readonly IReportService _reportService; + + public MyService(IReportService reportService) + { + _reportService = reportService; + } + + public async Task GenerateReport(List outputs) + { + var request = new ReportRequest + { + ProcessorOutputs = outputs, + Metadata = new ReportMetadata + { + SampleName = "PCB-001", + OperatorName = "张三", + InspectionDate = DateTime.Now + } + }; + + var result = await _reportService.GenerateAsync(request); + + if (result.IsSuccess) + { + // result.OutputFilePath — 生成的 PDF 路径 + // result.ReportId — 报告编号 + } + } +} +``` + +## 预热机制 + +模块在 Prism 初始化时自动在后台执行一次预热(`WarmUpAsync`),触发 iText7 初始化、字体加载、JIT 编译等一次性开销,避免用户首次生成报告时卡顿。 + +预热与正式生成通过 `SemaphoreSlim` 互斥锁串行执行,不会出现并发冲突。 + +## 数据绑定表达式 + +模板中支持以下绑定语法: + +| 语法 | 说明 | 示例 | +|------|------|------| +| `${propertyName}` | 简单属性绑定 | `${sampleName}` | +| `${object.property}` | 嵌套属性路径 | `${metadata.reportId}` | +| `${list[index]}` | 列表索引访问 | `${bgaBalls[0].voidRate}` | +| `${functionName(param)}` | 格式化函数 | `${formatDate(inspectionDate)}` | +| `${loc:ResourceKey}` | 本地化键解析 | `${loc:Report_Title}` | + +## 多语言支持 + +支持三种语言:简体中文(zh-CN)、繁体中文(zh-TW)、英文(en-US)。 + +通过 `ILocalizationService` 自动解析 `${loc:Key}` 表达式为当前语言文本。模块在初始化时注册资源源到 Fallback Chain。 + +## 模板页面类型 + +标准模板 `StandardReportTemplate.json` 包含以下页面类型: + +| 页面类型 | 说明 | +|---------|------| +| `homepage` | 报告首页(公司信息 + 元数据 + 汇总表格) | +| `summary` | 检测结果汇总页 | +| `metricData` | 距离测量数据页 | +| `bgaInspection` | BGA 焊球检测页(含数据表格) | +| `voidInspection` | 空隙检测页(含数据表格) | +| `viaFillInspection` | 通孔填锡检测页 | + +## 错误处理 + +模块采用结果对象模式(Result Pattern): +- `ReportServiceResult.Success(path, reportId)` — 成功 +- `ReportServiceResult.Failure(message, ex)` — 失败 + +非致命性问题(缺失属性、图像缺失)会记录警告日志并继续执行,不会中断报告生成。 + +## 构建 + +```bash +cd XplorePlane +dotnet build XP.ReportEngine/XP.ReportEngine.csproj +``` + +## 相关文档 + +- `Documents/Guidance.md` — 详细使用指南(IReportService 调用方式) +- `Documents/TemplateDevelopment.md` — 模板开发指南(新增/自定义模板) +- `Documents/App.config.example` — 配置文件示例 +- `Documents/FontFilesGuidance.md` — 字体方案说明 +- `Documents/XP.ReportEngineDesign.md` — 架构设计文档 +- `Documents/XP.ReportEngineModelDefine.md` — 模型定义文档 diff --git a/XP.ReportEngine/Documents/TemplateDevelopment.md b/XP.ReportEngine/Documents/TemplateDevelopment.md new file mode 100644 index 0000000..cdc3b6d --- /dev/null +++ b/XP.ReportEngine/Documents/TemplateDevelopment.md @@ -0,0 +1,403 @@ +# 报告模板开发指南 | Template Development Guide + +## 1. 概述 + +XP.ReportEngine 使用 JSON 格式定义报告模板。模板描述了 PDF 报告的页面结构、元素布局、数据绑定和样式定义。 + +模板文件存放在 `XP.ReportEngine/Templates/` 目录下,通过 `App.config` 中的 `Report:TemplatePath` 配置项指定使用哪个模板。 + +## 2. 新增模板步骤 + +### 2.1 创建模板文件 + +1. 在 `Templates/` 目录下创建新的 JSON 文件,如 `CustomReportTemplate.json` +2. 在 `.csproj` 中确认模板文件会被复制到输出目录(已有通配规则): + ```xml + + PreserveNewest + + ``` +3. 修改 `App.config` 中的 `Report:TemplatePath` 指向新模板: + ```xml + + ``` + +### 2.2 模板验证规则 + +模板加载后会自动验证,必须满足以下条件: +- 包含 `document` 顶层字段(页面设置) +- 包含 `pages` 顶层字段(至少一个页面定义) +- 包含 `styles` 顶层字段(样式字典) + +验证失败时报告生成会返回错误,不会生成 PDF。 + +## 3. 模板 JSON 结构 + +```json +{ + "document": { ... }, // 必需:文档级设置(页面尺寸、边距、页眉页脚) + "pages": [ ... ], // 必需:页面定义数组 + "styles": { ... } // 必需:样式定义字典 +} +``` + +## 4. document 配置 + +```json +{ + "document": { + "pageSize": "A4", + "orientation": "Portrait", + "margins": { "top": 40, "bottom": 20, "left": 20, "right": 20 }, + "header": { + "enabled": true, + "left": ["标题文本", "第二行文本"], + "right": ["右侧文本"], + "rightImageKey": "companyLogo", + "leftImageKey": "softwareLogo", + "fontSize": 7, + "color": "#666666", + "showLine": true + }, + "footer": { + "enabled": true, + "left": ["公司名称"], + "right": ["{currentPage} / {totalPages}"], + "fontSize": 8, + "color": "#666666", + "showLine": true + } + } +} +``` + +| 字段 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `pageSize` | string | `"A4"` | 页面尺寸 | +| `orientation` | string | `"Portrait"` | 方向:Portrait / Landscape | +| `margins` | object | `{top:20, bottom:20, left:20, right:20}` | 边距(mm) | +| `header` | object | null | 页眉配置(首页不显示) | +| `footer` | object | null | 页脚配置(首页不显示) | + +### 页眉/页脚字段 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `enabled` | bool | 是否启用 | +| `left` | string[] | 左侧文本行(支持 `${}` 绑定) | +| `right` | string[] | 右侧文本行(支持 `${}` 绑定) | +| `leftImageKey` | string | 左侧图像的 dataKey | +| `rightImageKey` | string | 右侧图像的 dataKey | +| `fontSize` | float | 字体大小 | +| `color` | string | 字体颜色(十六进制) | +| `showLine` | bool | 是否显示分隔线 | + +页脚特殊占位符: +- `{currentPage}` — 当前页码 +- `{totalPages}` — 总页数 + +## 5. pages 页面定义 + +```json +{ + "pages": [ + { + "type": "homepage", + "elements": [ ... ] + }, + { + "type": "customPage", + "elements": [ ... ] + } + ] +} +``` + +| 字段 | 类型 | 说明 | +|------|------|------| +| `type` | string | 页面类型标识(`homepage` 类型不显示页眉页脚) | +| `elements` | array | 页面内的元素列表 | + +### 内置页面类型 + +| 类型 | 说明 | 特殊行为 | +|------|------|---------| +| `homepage` | 首页 | 不显示页眉页脚 | +| `summary` | 汇总页 | 无 | +| `metricData` | 距离测量页 | 无 | +| `bgaInspection` | BGA 检测页 | 无 | +| `voidInspection` | 空隙检测页 | 无 | +| `viaFillInspection` | 通孔填锡页 | 无 | + +你可以自定义任意 `type` 名称,只有 `homepage` 有特殊行为(不显示页眉页脚)。 + +## 6. 元素类型 + +### 6.1 text — 文本元素 + +```json +{ + "type": "text", + "content": "${loc:Report_Title}", + "style": "heading", + "positioning": "flow", + "align": "center", + "colorRules": { "Pass": "#008000", "Fail": "#FF0000" } +} +``` + +| 字段 | 类型 | 说明 | +|------|------|------| +| `content` | string | 文本内容,支持 `${}` 绑定表达式 | +| `style` | string | 引用 `styles` 中定义的样式名 | +| `align` | string | 对齐:left / center / right | +| `colorRules` | object | 条件颜色规则(内容包含关键词时变色) | + +### 6.2 image — 图像元素 + +```json +{ + "type": "image", + "dataKey": "workpieceImage", + "size": [160, 110], + "border": true, + "align": "center", + "style": "imageDefault", + "positioning": "flow" +} +``` + +| 字段 | 类型 | 说明 | +|------|------|------| +| `dataKey` | string | 图像数据键(对应 `ReportContext.Images` 中的键) | +| `size` | float[2] | [宽, 高](mm),图像会等比缩放适应 | +| `border` | bool | 是否显示边框 | +| `align` | string | 对齐:left / center / right | + +图像缺失时会渲染灰色占位矩形,不会中断报告生成。 + +### 6.3 table — 表格元素 + +```json +{ + "type": "table", + "dataKey": "bgaBallsTable", + "positioning": "flow", + "size": [170, 0], + "style": "tableDefault", + "columns": [ + { "header": "序号", "field": "index", "width": 25, "align": "center" }, + { "header": "气泡率", "field": "voidRate", "width": 40, "align": "center" }, + { "header": "面积", "field": "area", "width": 40, "align": "center" }, + { + "header": "分类", + "field": "classification", + "width": 35, + "align": "center", + "colorRules": { "Pass": "#008000", "Fail": "#FF0000" } + } + ] +} +``` + +| 字段 | 类型 | 说明 | +|------|------|------| +| `dataKey` | string | 表格数据键(对应 `ReportContext.Properties` 中的 `List>` 数据) | +| `size` | float[2] | [宽, 高](mm),高度为 0 表示自动 | +| `columns` | array | 列定义数组 | + +#### 列定义(ColumnDefinition) + +| 字段 | 类型 | 说明 | +|------|------|------| +| `header` | string | 表头文本(支持 `${}` 绑定) | +| `field` | string | 数据字段名(对应行数据字典中的键) | +| `width` | float | 列宽(mm) | +| `align` | string | 对齐:left / center / right | +| `colorRules` | object | 条件颜色规则(单元格值匹配时变色) | + +### 6.4 row — 水平布局容器 + +```json +{ + "type": "row", + "size": [170, 100], + "widths": [6, 4], + "positioning": "flow", + "children": [ + { + "type": "column", "align": "left", + "children": [ + { "type": "image", "dataKey": "myImage", "size": [110, 90] } + ] + }, + { + "type": "column", "align": "left", + "children": [ + { "type": "text", "content": "文本内容", "style": "body" } + ] + } + ] +} +``` + +| 字段 | 类型 | 说明 | +|------|------|------| +| `size` | float[2] | [总宽, 总高](mm) | +| `widths` | float[] | 列宽比例数组(如 `[6, 4]` 表示 60%:40%) | +| `children` | array | 子列元素(`type: "column"`) | + +### 6.5 spacer — 间距元素 + +```json +{ "type": "spacer", "size": [170, 10], "positioning": "flow" } +``` + +用于在元素之间添加垂直间距。`size[1]` 为间距高度(mm)。 + +### 6.6 divider — 分隔线 + +```json +{ "type": "divider", "positioning": "flow" } +``` + +渲染一条水平分隔线。 + +### 6.7 pagebreak — 强制分页 + +```json +{ "type": "pagebreak" } +``` + +在当前位置强制插入分页符。 + +## 7. 样式定义 + +```json +{ + "styles": { + "heading": { + "font": "auto", + "size": 16, + "bold": true, + "italic": false, + "color": "#333333", + "align": "left", + "marginTop": 0, + "marginBottom": 3, + "paddingLeft": 0, + "lineHeight": 0, + "backgroundColor": "" + } + } +} +``` + +| 字段 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `font` | string | `"auto"` | 字体(`auto` 根据语言自动选择) | +| `size` | float | 12 | 字体大小(pt) | +| `bold` | bool | false | 粗体 | +| `italic` | bool | false | 斜体 | +| `color` | string | `"#000000"` | 字体颜色(十六进制) | +| `align` | string | `"left"` | 对齐:left / center / right | +| `backgroundColor` | string | — | 背景色(十六进制) | +| `marginTop` | float | 0 | 上边距(mm) | +| `marginBottom` | float | 0 | 下边距(mm) | +| `paddingLeft` | float | 0 | 左缩进(mm) | +| `lineHeight` | float | 0 | 行高倍数(0 = 默认) | + +元素引用未定义的样式名时,会使用默认样式(12pt、黑色、左对齐),不会报错。 + +## 8. 数据绑定表达式 + +模板中的 `content`、`header` 等文本字段支持 `${}` 绑定表达式: + +| 语法 | 说明 | 示例 | +|------|------|------| +| `${key}` | 从 Properties 中取值 | `${sampleName}` | +| `${metadata.field}` | 从 Metadata 中取值 | `${metadata.reportId}` | +| `${loc:Key}` | 多语言资源键 | `${loc:Report_Title}` | +| `${formatDate(value)}` | 格式化日期 | `${formatDate(metadata.inspectionDate)}` | +| `${formatNumber(value, decimals)}` | 格式化数字 | `${formatNumber(totalArea, 2)}` | +| `${formatPercent(value)}` | 格式化百分比 | `${formatPercent(voidRate)}` | + +### 数据来源 + +绑定表达式从 `ReportContext` 中查找数据: +- `Properties` 字典 — 扁平化的键值对(由 `ProcessorDataAdapter` 从处理器输出转换而来) +- `Metadata` — 报告元数据对象 +- `Images` 字典 — 图像数据(通过 `dataKey` 引用) + +### ProcessorDataAdapter 输出的键名 + +| 处理器类型 | Properties 中的键 | Images 中的键 | 表格 dataKey | +|-----------|------------------|--------------|-------------| +| LineMeasurementProcessor | measurementType, point1, point2, pixelDistance, actualDistance, unit, angle | lineMeasurementImage | — | +| BgaVoidRateProcessor | bgaCount, voidRate, fillRate, totalBgaArea, totalVoidArea, voidLimit, classification | bgaInspectionImage | bgaBallsTable | +| VoidMeasurementProcessor | roiArea, totalVoidArea, voidRate, voidLimit, voidCount, maxVoidArea, classification | voidInspectionImage | voidsTable | +| FillRateProcessor | fillRate, voidRate, fullDistance, fillDistance, thtLimit, classification, e1-e4 | viaFillImage | — | + +### ReportService 自动注入的键 + +| 键 | 来源 | 说明 | +|----|------|------| +| `CompanyName` | ReportConfig | 公司名称 | +| `SoftwareName` | ReportConfig | 软件名称 | +| `companyLogo` | ReportConfig.CompanyLogo | 公司 Logo(Images) | +| `softwareLogo` | ReportConfig.SoftwareLogo | 软件 Logo(Images) | +| `summaryTable` | 自动生成 | 首页汇总表数据 | + +## 9. 定位方式 + +| 值 | 说明 | +|----|------| +| `"flow"` | 流式布局,元素按顺序从上到下排列(推荐) | +| `"absolute"` | 绝对定位,使用 `position` 坐标(不推荐,兼容性差) | + +建议所有元素使用 `"positioning": "flow"`。 + +## 10. 完整模板示例(最小化) + +```json +{ + "document": { + "pageSize": "A4", + "orientation": "Portrait", + "margins": { "top": 20, "bottom": 20, "left": 20, "right": 20 } + }, + "pages": [ + { + "type": "homepage", + "elements": [ + { "type": "text", "content": "检测报告", "style": "title", "positioning": "flow" }, + { "type": "text", "content": "报告编号:${metadata.reportId}", "style": "body", "positioning": "flow" }, + { "type": "text", "content": "检测日期:${formatDate(metadata.inspectionDate)}", "style": "body", "positioning": "flow" } + ] + }, + { + "type": "dataPage", + "elements": [ + { "type": "text", "content": "检测数据", "style": "heading", "positioning": "flow" }, + { "type": "image", "dataKey": "inspectionImage", "size": [150, 100], "positioning": "flow" } + ] + } + ], + "styles": { + "title": { "size": 24, "bold": true, "align": "center" }, + "heading": { "size": 16, "bold": true }, + "body": { "size": 12 } + } +} +``` + +## 11. 注意事项 + +1. **尺寸单位**:所有尺寸(size、margins、width)单位为 **mm**(毫米) +2. **颜色格式**:使用十六进制格式 `#RRGGBB`(如 `#FF0000` 为红色) +3. **表格自动跨页**:当表格数据行超出当前页面剩余空间时,排版引擎会自动分页 +4. **图像缺失容错**:图像 dataKey 对应的数据不存在时,渲染占位矩形,不中断生成 +5. **样式缺失容错**:引用未定义的样式名时使用默认样式,不中断生成 +6. **绑定表达式缺失**:`${}` 表达式对应的数据不存在时,替换为空字符串 +7. **首页特殊处理**:`type: "homepage"` 的页面不显示页眉页脚 +8. **JSON 编码**:模板文件必须使用 **UTF-8** 编码保存 diff --git a/XP.ReportEngine/Documents/XP.ReportEngineDesign.md b/XP.ReportEngine/Documents/XP.ReportEngineDesign.md new file mode 100644 index 0000000..490c1a8 --- /dev/null +++ b/XP.ReportEngine/Documents/XP.ReportEngineDesign.md @@ -0,0 +1,422 @@ +# XP.ReportEngine 项目规划 + +## 一、项目结构优化 + +**1. 完整目录结构** + +``` +XP.ReportEngine/ +├── Interfaces/ +│ ├── IReportGenerator.cs // 报告生成器核心接口 +│ ├── ITemplateEngine.cs // 模板引擎接口 +│ └── IDataBinder.cs // 数据绑定接口 +├── Models/ +│ ├── ReportContext.cs // 报告上下文(包含测量数据、图片列表) +│ ├── ReportTemplate.cs // 报告模板定义 +│ ├── TemplateElement.cs // 模板元素定义 +│ └── LayoutSettings.cs // 布局配置参数 +├── Services/ +│ ├── MeasurementReportBuilder.cs // 测量报告构建器 +│ ├── ImageLayoutService.cs // 图片排版服务(计算坐标、缩放) +│ ├── TemplateEngine.cs // 模板引擎实现 +│ ├── DataBinder.cs // 数据绑定实现 +│ └── PdfGenerationService.cs // PDF生成核心服务 +├── Templates/ +│ └── StandardReportTemplate.json // 标准报告模板配置 +└── Extensions/ + └── ImageExtensions.cs // 图像处理扩展方法 +``` + +## 二、核心模块设计 + +### 1. 报告模板引擎 + +**1.1 模板定义规范** + +- **JSON模板结构**: + + ```json + { + "document": { + "pageSize": "A4", + "margins": { "top": 20, "bottom": 20, "left": 20, "right": 20 } + }, + "pages": [ + { + "type": "title", + "elements": [ + { "type": "text", "content": "工业CT检测报告", "style": "title", "position": [10, 10] }, + { "type": "text", "content": "报告日期: ${reportDate}", "style": "subtitle", "position": [10, 30] } + ] + }, + { + "type": "measurement", + "elements": [ + { "type": "table", "dataKey": "measurements", "position": [10, 10], "size": [190, 100] }, + { "type": "image", "dataKey": "overviewImage", "position": [10, 120], "size": [190, 100] } + ] + }, + { + "type": "defectDetail", + "elements": [ + { "type": "image", "dataKey": "defectImage", "position": [10, 10], "size": [90, 90] }, + { "type": "text", "content": "缺陷类型: ${defectType}", "style": "normal", "position": [10, 100] } + ] + } + ], + "styles": { + "title": { "font": "Arial", "size": 24, "bold": true, "color": "#000000" }, + "subtitle": { "font": "Arial", "size": 16, "italic": true, "color": "#666666" }, + "normal": { "font": "Arial", "size": 12, "color": "#000000" } + } + } + ``` + +**1.2 模板引擎实现** + +- **模板加载与解析**: + + ```csharp + public class TemplateEngine : ITemplateEngine + { + public ReportTemplate LoadTemplate(string templatePath) + { + var json = File.ReadAllText(templatePath); + return JsonConvert.DeserializeObject(json); + } + + public List ParseTemplate(ReportTemplate template, ReportContext context) + { + var elements = new List(); + + foreach (var page in template.Pages) + { + foreach (var element in page.Elements) + { + // 处理数据绑定 + var boundElement = BindData(element, context); + elements.Add(boundElement); + } + } + + return elements; + } + + private PageElement BindData(PageElement element, ReportContext context) + { + // 实现数据绑定逻辑 + if (element.Type == "text" && element.Content.Contains("${")) + { + element.Content = DataBinder.Bind(element.Content, context); + } + return element; + } + } + ``` + +### 2. 数据绑定系统 + +**2.1 数据绑定机制** + +- **支持的绑定语法**: + - `${propertyName}` - 基本属性绑定 + - `${object.property}` - 对象属性绑定 + - `${list[index]}` - 列表索引访问 + - `${function(param)}` - 函数调用(如日期格式化) + +**2.2 数据绑定实现** + +```csharp +public class DataBinder : IDataBinder +{ + public string Bind(string template, ReportContext context) + { + // 使用正则表达式匹配绑定表达式 + var pattern = @"\$\{([^\}]+)\}"; + return Regex.Replace(template, pattern, match => + { + var expression = match.Groups[1].Value; + return EvaluateExpression(expression, context); + }); + } + + private string EvaluateExpression(string expression, ReportContext context) + { + // 解析表达式并获取值 + if (expression.Contains(".")) + { + // 处理对象属性 + var parts = expression.Split('.'); + var obj = context.GetType().GetProperty(parts[0])?.GetValue(context); + return obj?.GetType().GetProperty(parts[1])?.GetValue(obj)?.ToString() ?? string.Empty; + } + else if (expression.Contains("[")) + { + // 处理列表索引 + var index = int.Parse(expression.Split('[')[1].TrimEnd(']')); + return context.Images.Count > index ? "Image" : string.Empty; + } + else + { + // 处理基本属性 + return context.GetType().GetProperty(expression)?.GetValue(context)?.ToString() ?? string.Empty; + } + } +} +``` + +### 3. 自动排版功能 + +**3.1 布局引擎设计** + +- **核心功能**: + - **页面分隔**: 当内容超出一页时自动创建新页 + - **元素定位**: 根据模板定义计算元素在页面上的精确位置 + - **尺寸计算**: 自动调整元素大小以适应内容 + - **响应式布局**: 根据页面尺寸动态调整元素位置 + +**3.2 排版服务实现** + +```csharp +public class ImageLayoutService +{ + public LayoutResult CalculateLayout(ReportTemplate template, ReportContext context) + { + var result = new LayoutResult(); + var currentPage = 0; + var currentY = template.Document.Margins.Top; + + foreach (var page in template.Pages) + { + foreach (var element in page.Elements) + { + // 计算元素位置 + var position = new Point( + template.Document.Margins.Left + element.Position[0], + currentY + element.Position[1] + ); + + // 计算元素尺寸 + var size = new Size(element.Size[0], element.Size[1]); + + // 检查是否需要换页 + if (position.Y + size.Height > PageSize.A4.Height - template.Document.Margins.Bottom) + { + currentPage++; + currentY = template.Document.Margins.Top; + position = new Point( + template.Document.Margins.Left + element.Position[0], + currentY + element.Position[1] + ); + } + + // 添加到布局结果 + result.Elements.Add(new LayoutElement + { + Page = currentPage, + Element = element, + Position = position, + Size = size + }); + + // 更新当前Y位置 + currentY = position.Y + size.Height; + } + } + + return result; + } +} +``` + +### 4. 图表嵌入实现 + +**4.1 WPF图表转PDF图像** + +- **实现方案**: + - 使用`RenderTargetBitmap`将WPF控件渲染为位图 + - 支持高质量图像输出(300 DPI) + - 提供图像压缩选项,平衡质量与文件大小 + +**4.2 图像转换服务** + +```csharp +public static class ImageExtensions +{ + public static byte[] ToPdfImage(this ImageSource image, double width, double height) + { + var renderTarget = new RenderTargetBitmap( + (int)width, (int)height, + 96, 96, PixelFormats.Pbgra32); + + renderTarget.Render(image); + + using (var stream = new MemoryStream()) + { + var encoder = new PngBitmapEncoder(); + encoder.Frames.Add(BitmapFrame.Create(renderTarget)); + encoder.Save(stream); + return stream.ToArray(); + } + } + + public static byte[] ToPdfImage(this FrameworkElement element, double width, double height) + { + var renderTarget = new RenderTargetBitmap( + (int)width, (int)height, + 96, 96, PixelFormats.Pbgra32); + + renderTarget.Render(element); + + using (var stream = new MemoryStream()) + { + var encoder = new PngBitmapEncoder(); + encoder.Frames.Add(BitmapFrame.Create(renderTarget)); + encoder.Save(stream); + return stream.ToArray(); + } + } +} +``` + +## 三、PDF生成核心流程 + +### 1. 生成步骤 + +1. **加载模板**: 从Templates目录加载JSON模板 +2. **解析上下文**: 将MeasurementData和图片列表转换为ReportContext +3. **数据绑定**: 将上下文数据绑定到模板中的占位符 +4. **计算布局**: 确定每个元素在PDF页面上的精确位置 +5. **生成PDF**: 使用iTextSharp绘制内容到PDF文档 +6. **输出结果**: 返回MemoryStream或保存为文件 + +### 2. 核心生成服务 + +```csharp +public class PdfGenerationService +{ + public MemoryStream GeneratePdf(ReportContext context, string templatePath) + { + // 1. 加载模板 + var templateEngine = new TemplateEngine(); + var template = templateEngine.LoadTemplate(templatePath); + + // 2. 解析模板元素 + var elements = templateEngine.ParseTemplate(template, context); + + // 3. 计算布局 + var layoutService = new ImageLayoutService(); + var layout = layoutService.CalculateLayout(template, context); + + // 4. 创建PDF文档 + var document = new Document(PageSize.A4, + template.Document.Margins.Left, + template.Document.Margins.Right, + template.Document.Margins.Top, + template.Document.Margins.Bottom); + + var stream = new MemoryStream(); + var writer = PdfWriter.GetInstance(document, stream); + document.Open(); + + // 5. 绘制内容 + DrawContent(document, layout, context); + + // 6. 关闭文档 + document.Close(); + + return stream; + } + + private void DrawContent(Document document, LayoutResult layout, ReportContext context) + { + // 实现内容绘制逻辑 + foreach (var element in layout.Elements) + { + switch (element.Element.Type) + { + case "text": + DrawText(document, element); + break; + case "image": + DrawImage(document, element, context); + break; + case "table": + DrawTable(document, element, context); + break; + } + } + } + + private void DrawText(Document document, LayoutElement element) + { + var paragraph = new Paragraph(element.Element.Content); + paragraph.SetLocation(element.Position.X, document.PageSize.Height - element.Position.Y); + document.Add(paragraph); + } + + private void DrawImage(Document document, LayoutElement element, ReportContext context) + { + // 获取图像数据 + var imageData = context.Images.FirstOrDefault(); + if (imageData == null) return; + + // 创建iTextSharp图像对象 + var image = Image.GetInstance(imageData); + image.SetAbsolutePosition(element.Position.X, document.PageSize.Height - element.Position.Y - element.Size.Height); + image.ScaleToFit(element.Size.Width, element.Size.Height); + + document.Add(image); + } + + private void DrawTable(Document document, LayoutElement element, ReportContext context) + { + // 创建表格 + var table = new PdfPTable(3); // 假设3列 + table.WidthPercentage = 100; + + // 添加表头 + table.AddCell(new PdfPCell(new Phrase("参数", new Font(Font.FontFamily.HELVETICA, 12, Font.BOLD)))); + table.AddCell(new PdfPCell(new Phrase("测量值", new Font(Font.FontFamily.HELVETICA, 12, Font.BOLD)))); + table.AddCell(new PdfPCell(new Phrase("标准值", new Font(Font.FontFamily.HELVETICA, 12, Font.BOLD)))); + + // 添加数据行 + foreach (var measurement in context.Measurements) + { + table.AddCell(new PdfPCell(new Phrase(measurement.Parameter))); + table.AddCell(new PdfPCell(new Phrase(measurement.Value.ToString()))); + table.AddCell(new PdfPCell(new Phrase(measurement.StandardValue.ToString()))); + } + + document.Add(table); + } +} +``` + +## 四、最佳实践与建议 + +### 1. 性能优化 + +- **图像处理**: 对大尺寸图像进行**压缩和缩放**,避免PDF文件过大 +- **内存管理**: 使用`using`语句确保资源及时释放 +- **批量处理**: 对大量数据采用**分批处理**策略,避免UI冻结 + +### 2. 错误处理 + +- 实现**完善的异常捕获**机制,特别是图像转换和PDF生成环节 +- 提供**友好的错误提示**,便于问题排查 +- 添加**日志记录**功能,跟踪报告生成过程 + +### 3. 样式管理 + +- 创建**样式库**,统一字体、颜色、间距等 +- 支持**主题切换**,适应不同客户的品牌要求 +- 确保**打印友好**,考虑黑白打印时的可读性 + +### 4. 扩展性考虑 + +- **插件架构**: 设计可扩展的插件系统,便于添加新功能 +- **多格式输出**: 基于相同架构,扩展支持Word、Excel等格式输出 +- **云服务集成**: 考虑与云存储集成,自动上传生成的报告 + +通过以上设计,XP.ReportEngine将成为一个**灵活、高效、可维护**的PDF报告生成系统,满足工业CT检测报告的专业需求,同时保持系统的扩展性和易用性。 diff --git a/XP.ReportEngine/Documents/XP.ReportEngineModelDefine.md b/XP.ReportEngine/Documents/XP.ReportEngineModelDefine.md new file mode 100644 index 0000000..a5d96a6 --- /dev/null +++ b/XP.ReportEngine/Documents/XP.ReportEngineModelDefine.md @@ -0,0 +1,328 @@ +# XP.ReportEngine 模板定义规范 + +## **一、模板结构总览** + +模板使用JSON格式定义,分为以下几个核心部分: + +1.  + +`document`:文档基础配置(页面尺寸、边距等)。 + +2.  + +`pages`:页面列表,每个页面包含元素列表。 + +3.  + +`elements`:页面中的具体元素(文本、表格、图片等)。 + +4.  + +`styles`:预定义的样式,可复用。 + +5.  + +`dataKey`:数据绑定字段,关联业务数据。 + +## **二、首页布局规范** + +**模板路径**:`Templates/Homepage.json` + +代码图标/24_new/复制 + +``` +{ + "document": { + "pageSize": "A4", + "orientation": "Portrait", + "margins": { "top": 30, "right": 20, "bottom": 20, "left": 20 } + }, + "pages": [ + { + "type": "homepage", + "elements": [ + { "type": "text", "content": "平面CT检测报告", "style": "title", "position": [10, 10] }, + { "type": "text", "content": "报告编号:${reportId}", "style": "subtitle", "position": [10, 40] }, + { "type": "text", "content": "检测日期:${inspectionDate}", "style": "subtitle", "position": [10, 60] }, + { "type": "text", "content": "样品名称:${sampleName}", "style": "subtitle", "position": [10, 80] }, + { "type": "divider", "position": [10, 100], "width": 190 }, + { "type": "table", "dataKey": "summaryData", "columns": [...], "position": [10, 120], "size": [190, 60] } + ] + } + ], + "styles": { + // ... 样式定义(见下文) + } +} +``` + +**关键要素说明**: + +1.  + +**标题与元数据**:顶部显示报告标题、编号、日期和样品名称。 + +2.  + +**摘要表格**:显示关键统计信息(如缺陷数量、合格率等),通过`summaryData`绑定数据。 + +3.  + +**分隔线**:视觉分隔标题与主体内容。 + +## **三、测量数据布局规范** + +**模板路径**:`Templates/MetricData.json` + +代码图标/24_new/复制 + +``` +{ + "pages": [ + { + "type": "metricData", + "elements": [ + { "type": "text", "content": "测量数据详情", "style": "sectionTitle", "position": [10, 10] }, + { "type": "table", "dataKey": "measurements", "columns": [ + { "header": "参数名称", "field": "parameter" }, + { "header": "测量值", "field": "value" }, + { "header": "单位", "field": "unit" } + ], "position": [10, 30], "size": [190, 100] } + ] + } + ], + "styles": { + // ... 样式定义(见下文) + } +} +``` + +**关键要素说明**: + +●  + +表格绑定`measurements`数据,列通过`field`属性绑定数据字段。 + +●  + +支持动态行数,自动调整表格高度。 + +## **四、缺陷检测布局规范** + +**模板路径**:`Templates/DefectDetails.json` + +代码图标/24_new/复制 + +``` +{ + "pages": [ + { + "type": "defectDetails", + "elements": [ + { "type": "text", "content": "缺陷检测结果", "style": "sectionTitle", "position": [10, 10] }, + { + "type": "grid", "dataKey": "defects", + "columns": [ + { "type": "image", "dataKey": "imagePath", "size": [90, 90], "border": true }, + { "type": "text", "content": "缺陷类型:${type}", "style": "gridText", "width": 100 }, + { "type": "text", "content": "位置:X=${x}, Y=${y}", "style": "gridText", "width": 100 } + ], + "position": [10, 30], "colWidth": 100, "rowHeight": 100 + } + ] + } + ], + "styles": { + "gridText": { "font": "Arial", "size": 10, "align": "left" } + } +} +``` + +**关键要素说明**: + +1.  + +**网格布局**:每行显示缺陷图片、类型和位置信息。 + +2.  + +使用`grid`元素自动排版多缺陷数据,支持水平滚动或分页。 + +3.  + +图片路径通过`imagePath`字段绑定,文本使用数据绑定语法(如`X=${x}`)。 + +## **五、BGA检测布局规范** + +**模板路径**:`Templates/BGAInspection.json` + +代码图标/24_new/复制 + +``` +{ + "pages": [ + { + "type": "bgaDetails", + "elements": [ + { "type": "text", "content": "BGA焊点检测结果", "style": "sectionTitle", "position": [10, 10] }, + { + "type": "image", "dataKey": "bgaTopViewImage", "position": [10, 30], "size": [190, 150], "border": true, + "annotations": [ + { "type": "point", "x": ${defectX}, "y": ${defectY}, "color": "red", "size": 5 } + ] + }, + { + "type": "table", "dataKey": "bgaMetrics", + "columns": [ "引脚编号", "焊锡高度", "偏移量" ], + "position": [10, 190], "size": [190, 80] + } + ] + } + ] +} +``` + +**关键要素说明**: + +1.  + +**图片标注**:在BGA俯视图上标记缺陷位置(通过`annotations`)。 + +2.  + +**表格显示参数**:焊锡高度、偏移量等数据。 + +3.  + +支持动态标注点,绑定缺陷坐标数据。 + +## **六、气泡检测布局规范** + +**模板路径**:`Templates/VoidInspection.json` + +代码图标/24_new/复制 + +``` +{ + "pages": [ + { + "type": "voidDetails", + "elements": [ + { "type": "text", "content": "气泡检测结果", "style": "sectionTitle", "position": [10, 10] }, + { + "type": "image", "dataKey": "voidImage", "position": [10, 30], "size": [190, 150], "border": true, + "annotations": [ + { "type": "circle", "x": ${bubbleX}, "y": ${bubbleY}, "radius": ${bubbleRadius}, "color": "blue" } + ] + }, + { "type": "text", "content": "总气泡数:${voidCount}", "style": "resultText", "position": [10, 190] }, + { "type": "text", "content": "最大气泡体积:${maxVoidVolume} mm³", "style": "resultText", "position": [10, 210] } + ] + } + ], + "styles": { + "resultText": { "font": "Arial", "size": 14, "bold": true } + } +} +``` + +**关键要素说明**: + +1.  + +**气泡可视化**:在图像上用圆形标注气泡位置、半径。 + +2.  + +**统计结果**:显示总数量、最大体积等关键指标。 + +3.  + +支持多气泡标注,数据绑定气泡坐标和尺寸。 + +## **七、通孔填锡率检测布局规范** + +**模板路径**:`Templates/ViaFillInspection.json` + +代码图标/24_new/复制 + +``` +{ + "pages": [ + { + "type": "viaFillDetails", + "elements": [ + { "type": "text", "content": "通孔填锡率检测结果", "style": "sectionTitle", "position": [10, 10] }, + { + "type": "image", "dataKey": "viaImage", "position": [10, 30], "size": [190, 150], "border": true, + "annotations": [ + { "type": "rectangle", "x": ${viaX}, "y": ${viaY}, "width": ${viaWidth}, "height": ${viaHeight}, "color": "green" } + ] + }, + { "type": "text", "content": "平均填锡率:${avgFillRate}%", "style": "resultText", "position": [10, 190] }, + { "type": "progressBar", "value": ${avgFillRate}, "position": [10, 220], "size": [190, 20], "color": "${fillRateColor(avgFillRate)}" } + ] + } + ], + "functions": { + "fillRateColor(value)": "return value >= 90 ? 'green' : value >= 70 ? 'orange' : 'red';" + } +} +``` + +**关键要素说明**: + +1.  + +**填锡率图示**:用矩形标注通孔位置,进度条显示填锡率。 + +2.  + +**颜色条件绑定**:通过函数`fillRateColor`根据数值动态设置颜色。 + +3.  + +支持多通孔标注,绑定位置和尺寸数据。 + +## **八、样式定义示例** + +代码图标/24_new/复制 + +``` +"styles": { + "title": { "font": "Arial", "size": 24, "bold": true, "color": "#000000" }, + "subtitle": { "font": "Arial", "size": 16, "italic": true, "color": "#666666" }, + "gridText": { "font": "Arial", "size": 10, "align": "left" }, + "resultText": { "font": "Arial", "size": 14, "bold": true } +} +``` + +**关键要素说明**: + +●  + +样式可复用,通过`style`属性引用。 + +●  + +支持字体、大小、颜色、对齐等属性配置。 + +**# 总结** + +●  + +**模块化设计**:各检测模块独立为单独模板文件,可灵活组合。 + +●  + +**数据绑定**:通过`${}`语法绑定业务数据(如`measurements`、`defects`等对象)。 + +●  + +**动态布局**:支持表格自适应高度、图片标注、条件样式等高级功能。 + +●  + +**可扩展性**:新增检测类型只需定义对应模板文件即可。 + +**备注**:实际使用时,需根据具体业务数据模型调整`dataKey`和字段名称,确保模板与输入数据匹配。 diff --git a/XP.ReportEngine/Interfaces/IDataBinder.cs b/XP.ReportEngine/Interfaces/IDataBinder.cs new file mode 100644 index 0000000..7063ff8 --- /dev/null +++ b/XP.ReportEngine/Interfaces/IDataBinder.cs @@ -0,0 +1,18 @@ +using XP.ReportEngine.Models; + +namespace XP.ReportEngine.Interfaces +{ + /// + /// 数据绑定器接口 | Data binder interface + /// + public interface IDataBinder + { + /// + /// 将上下文数据绑定到模板 | Bind context data to template + /// + /// 报告模板 | Report template + /// 报告上下文 | Report context + /// 绑定后的模板(元素内容已替换)| Bound template with resolved content + ReportTemplate Bind(ReportTemplate template, ReportContext context); + } +} diff --git a/XP.ReportEngine/Interfaces/ILayoutEngine.cs b/XP.ReportEngine/Interfaces/ILayoutEngine.cs new file mode 100644 index 0000000..732ff3e --- /dev/null +++ b/XP.ReportEngine/Interfaces/ILayoutEngine.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using XP.ReportEngine.Models; + +namespace XP.ReportEngine.Interfaces +{ + /// + /// 排版引擎接口 | Layout engine interface + /// + public interface ILayoutEngine + { + /// + /// 计算页面布局 | Calculate page layout + /// + /// 绑定后的模板 | Bound template + /// 生成选项 | Generation options + /// 排版后的页面列表 | List of laid-out pages + List CalculateLayout(ReportTemplate template, ReportGenerationOptions options); + } +} diff --git a/XP.ReportEngine/Interfaces/IPdfRenderer.cs b/XP.ReportEngine/Interfaces/IPdfRenderer.cs new file mode 100644 index 0000000..3679c0a --- /dev/null +++ b/XP.ReportEngine/Interfaces/IPdfRenderer.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using System.IO; +using XP.ReportEngine.Models; + +namespace XP.ReportEngine.Interfaces +{ + /// + /// PDF 渲染器接口 | PDF renderer interface + /// + public interface IPdfRenderer + { + /// + /// 将排版结果渲染为 PDF | Render layout result to PDF + /// + /// 排版后的页面列表 | Laid-out pages + /// 生成选项 | Generation options + /// 绑定后的模板(用于页眉页脚配置)| Bound template (for header/footer config) + /// PDF 内存流 | PDF memory stream + MemoryStream Render(List pages, ReportGenerationOptions options, ReportTemplate template = null); + } +} diff --git a/XP.ReportEngine/Interfaces/IReportDataAdapter.cs b/XP.ReportEngine/Interfaces/IReportDataAdapter.cs new file mode 100644 index 0000000..264ab7c --- /dev/null +++ b/XP.ReportEngine/Interfaces/IReportDataAdapter.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using XP.ReportEngine.Models; + +namespace XP.ReportEngine.Interfaces +{ + /// + /// 报告数据适配器接口 | Report data adapter interface + /// 将 XP.ImageProcessing 的 OutputData 转换为 ReportContext + /// + public interface IReportDataAdapter + { + /// + /// 将处理器输出数据适配为报告上下文 | Adapt processor output data to report context + /// + /// 处理器输出字典列表 | List of processor output dictionaries + /// 报告元数据 | Report metadata + /// 报告上下文 | Report context + ReportContext Adapt(List processorOutputs, ReportMetadata metadata); + } +} diff --git a/XP.ReportEngine/Interfaces/IReportGenerator.cs b/XP.ReportEngine/Interfaces/IReportGenerator.cs new file mode 100644 index 0000000..4d2e15d --- /dev/null +++ b/XP.ReportEngine/Interfaces/IReportGenerator.cs @@ -0,0 +1,19 @@ +using System.Threading.Tasks; +using XP.ReportEngine.Models; + +namespace XP.ReportEngine.Interfaces +{ + /// + /// 报告生成器接口(格式无关)| Report generator interface (format-agnostic) + /// + public interface IReportGenerator + { + /// + /// 异步生成报告 | Generate report asynchronously + /// + /// 报告上下文数据 | Report context data + /// 生成选项 | Generation options + /// 生成结果 | Generation result + Task GenerateAsync(ReportContext context, ReportGenerationOptions options); + } +} diff --git a/XP.ReportEngine/Interfaces/IReportGeneratorFactory.cs b/XP.ReportEngine/Interfaces/IReportGeneratorFactory.cs new file mode 100644 index 0000000..56f1d72 --- /dev/null +++ b/XP.ReportEngine/Interfaces/IReportGeneratorFactory.cs @@ -0,0 +1,15 @@ +using XP.ReportEngine.Models; + +namespace XP.ReportEngine.Interfaces +{ + /// + /// 报告生成器工厂接口 | Report generator factory interface + /// + public interface IReportGeneratorFactory + { + /// + /// 根据输出格式创建生成器 | Create generator by output format + /// + IReportGenerator Create(ReportOutputFormat format); + } +} diff --git a/XP.ReportEngine/Interfaces/IReportService.cs b/XP.ReportEngine/Interfaces/IReportService.cs new file mode 100644 index 0000000..8b8d63f --- /dev/null +++ b/XP.ReportEngine/Interfaces/IReportService.cs @@ -0,0 +1,45 @@ +using System.Threading.Tasks; +using XP.ReportEngine.Models; + +namespace XP.ReportEngine.Interfaces +{ + /// + /// 报告服务接口(门面)| Report service interface (Facade) + /// 提供完整的报告生成流程,外部模块通过此接口调用报告功能 + /// Provides complete report generation workflow for external modules + /// + /// + /// 使用示例 | Usage example: + /// + /// var request = new ReportRequest + /// { + /// ProcessorOutputs = processorOutputs, + /// Metadata = new ReportMetadata + /// { + /// SampleName = "PCB-001", + /// OperatorName = "Operator", + /// InspectionDate = DateTime.Now + /// } + /// }; + /// var result = await _reportService.GenerateAsync(request); + /// + /// + public interface IReportService + { + /// + /// 生成报告 | Generate report + /// 执行完整流程:生成报告ID → 数据适配 → 上下文组装 → 管线生成 → 文件保存 + /// Executes full workflow: generate report ID → data adaptation → context assembly → pipeline generation → file saving + /// + /// 报告生成请求 | Report generation request + /// 报告生成结果 | Report generation result + Task GenerateAsync(ReportRequest request); + + /// + /// 预热报告引擎(建议在应用启动后台调用)| Warm up report engine (recommended to call in background on app startup) + /// 触发 iText7 初始化、字体加载、JIT 编译等一次性开销,避免首次生成报告时卡顿 + /// Triggers iText7 initialization, font loading, JIT compilation to avoid first-run latency + /// + Task WarmUpAsync(); + } +} diff --git a/XP.ReportEngine/Interfaces/ITemplateEngine.cs b/XP.ReportEngine/Interfaces/ITemplateEngine.cs new file mode 100644 index 0000000..7596ab4 --- /dev/null +++ b/XP.ReportEngine/Interfaces/ITemplateEngine.cs @@ -0,0 +1,22 @@ +using XP.ReportEngine.Models; + +namespace XP.ReportEngine.Interfaces +{ + /// + /// 模板引擎接口 | Template engine interface + /// + public interface ITemplateEngine + { + /// + /// 加载并验证模板 | Load and validate template + /// + /// 模板文件路径 | Template file path + /// 解析后的模板对象 | Parsed template object + ReportTemplate LoadTemplate(string templatePath); + + /// + /// 验证模板结构完整性 | Validate template structure integrity + /// + TemplateValidationResult Validate(ReportTemplate template); + } +} diff --git a/XP.ReportEngine/Models/ImageData.cs b/XP.ReportEngine/Models/ImageData.cs new file mode 100644 index 0000000..b1aa0ed --- /dev/null +++ b/XP.ReportEngine/Models/ImageData.cs @@ -0,0 +1,35 @@ +namespace XP.ReportEngine.Models +{ + /// + /// 图像数据封装 | Image data wrapper + /// + public class ImageData + { + /// + /// 图像来源类型 | Image source type + /// + public ImageSourceType SourceType { get; set; } + + /// + /// 字节数组数据(当 SourceType 为 Bytes 时)| Byte array data + /// + public byte[] Bytes { get; set; } + + /// + /// 文件路径(当 SourceType 为 FilePath 时)| File path + /// + public string FilePath { get; set; } + + /// + /// BitmapSource 引用(当 SourceType 为 BitmapSource 时)| BitmapSource reference + /// + public object BitmapSource { get; set; } + } + + public enum ImageSourceType + { + Bytes, + FilePath, + BitmapSource + } +} diff --git a/XP.ReportEngine/Models/LayoutPage.cs b/XP.ReportEngine/Models/LayoutPage.cs new file mode 100644 index 0000000..9abcac2 --- /dev/null +++ b/XP.ReportEngine/Models/LayoutPage.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; + +namespace XP.ReportEngine.Models +{ + /// + /// 排版后的页面 | Laid-out page + /// + public class LayoutPage + { + public int PageNumber { get; set; } + + /// + /// 页面类型(来自模板定义:homepage / metricData / bgaInspection 等) + /// Page type from template definition + /// + public string PageType { get; set; } + + public List Elements { get; set; } = new(); + } + + /// + /// 排版后的元素(含计算后的绝对坐标)| Laid-out element with computed absolute coordinates + /// + public class LayoutElement + { + public TemplateElement Source { get; set; } + public float X { get; set; } + public float Y { get; set; } + public float Width { get; set; } + public float Height { get; set; } + public StyleDefinition ResolvedStyle { get; set; } + public string ResolvedContent { get; set; } + public List> ResolvedTableData { get; set; } + public ImageData ResolvedImage { get; set; } + } +} diff --git a/XP.ReportEngine/Models/ProcessorOutput.cs b/XP.ReportEngine/Models/ProcessorOutput.cs new file mode 100644 index 0000000..3594ad7 --- /dev/null +++ b/XP.ReportEngine/Models/ProcessorOutput.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; + +namespace XP.ReportEngine.Models +{ + /// + /// 处理器输出数据封装 | Processor output data wrapper + /// + public class ProcessorOutput + { + /// + /// 处理器类型名称 | Processor type name + /// + public string ProcessorType { get; set; } + + /// + /// 输出数据字典 | Output data dictionary + /// + public Dictionary OutputData { get; set; } = new(); + + /// + /// 关联的已标注图像 | Associated annotated image + /// + public ImageData AnnotatedImage { get; set; } + } +} diff --git a/XP.ReportEngine/Models/ReportContext.cs b/XP.ReportEngine/Models/ReportContext.cs new file mode 100644 index 0000000..6b08c73 --- /dev/null +++ b/XP.ReportEngine/Models/ReportContext.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; + +namespace XP.ReportEngine.Models +{ + /// + /// 报告上下文,包含生成报告所需的全部数据 | Report context containing all data needed for report generation + /// + public class ReportContext + { + /// + /// 报告元数据 | Report metadata + /// + public ReportMetadata Metadata { get; set; } + + /// + /// 检测结果数据集合(按处理器类型分组)| Inspection result data grouped by processor type + /// + public List ResultGroups { get; set; } = new(); + + /// + /// 图像数据字典(键为 dataKey,值为图像数据)| Image data dictionary (key=dataKey, value=image data) + /// + public Dictionary Images { get; set; } = new(); + + /// + /// 自定义属性字典(用于数据绑定的扁平化键值对)| Custom properties for data binding + /// + public Dictionary Properties { get; set; } = new(); + } + + /// + /// 报告元数据 | Report metadata + /// + public class ReportMetadata + { + public string ReportId { get; set; } + public DateTime InspectionDate { get; set; } + public string SampleName { get; set; } + public string OperatorName { get; set; } + public string Description { get; set; } + } + + /// + /// 检测结果分组 | Inspection result group + /// + public class InspectionResultGroup + { + /// + /// 处理器类型标识 | Processor type identifier + /// + public string ProcessorType { get; set; } + + /// + /// 数据来源标识 | Data source identifier + /// + public string SourceId { get; set; } + + /// + /// 分类结果(Pass/Fail)| Classification result + /// + public string Classification { get; set; } + + /// + /// 结果键值对 | Result key-value pairs + /// + public Dictionary Data { get; set; } = new(); + + /// + /// 表格数据行(用于表格渲染)| Table data rows for table rendering + /// + public List> TableRows { get; set; } = new(); + } +} diff --git a/XP.ReportEngine/Models/ReportGenerationOptions.cs b/XP.ReportEngine/Models/ReportGenerationOptions.cs new file mode 100644 index 0000000..a111849 --- /dev/null +++ b/XP.ReportEngine/Models/ReportGenerationOptions.cs @@ -0,0 +1,30 @@ +namespace XP.ReportEngine.Models +{ + /// + /// 报告生成选项 | Report generation options + /// + public class ReportGenerationOptions + { + /// + /// 模板文件路径 | Template file path + /// + public string TemplatePath { get; set; } + + /// + /// 输出文件路径(可选,为 null 时仅返回 MemoryStream)| Output file path (optional) + /// + public string OutputFilePath { get; set; } + + /// + /// 输出格式 | Output format + /// + public ReportOutputFormat Format { get; set; } = ReportOutputFormat.Pdf; + } + + public enum ReportOutputFormat + { + Pdf, + Excel, + Csv + } +} diff --git a/XP.ReportEngine/Models/ReportRequest.cs b/XP.ReportEngine/Models/ReportRequest.cs new file mode 100644 index 0000000..0c3f50a --- /dev/null +++ b/XP.ReportEngine/Models/ReportRequest.cs @@ -0,0 +1,50 @@ +using System.Collections.Generic; + +namespace XP.ReportEngine.Models +{ + /// + /// 报告生成请求 | Report generation request + /// 封装外部调用方生成报告所需的全部输入参数 + /// Encapsulates all input parameters needed by external callers to generate a report + /// + public class ReportRequest + { + /// + /// 处理器输出数据列表 | List of processor output data + /// + public List ProcessorOutputs { get; set; } = new(); + + /// + /// 报告元数据(产品名、操作员、描述等)| Report metadata (product name, operator, description, etc.) + /// + public ReportMetadata Metadata { get; set; } + + /// + /// 额外图像数据(如工件整体图、自定义图像)| Additional image data (e.g., workpiece overview, custom images) + /// 键为 dataKey,值为图像数据 + /// Key is dataKey, value is image data + /// + public Dictionary AdditionalImages { get; set; } = new(); + + /// + /// 输出文件路径(可选)| Output file path (optional) + /// 为空时根据 ReportConfig 和 FileNameParameters 自动生成 + /// When empty, auto-generated based on ReportConfig and FileNameParameters + /// + public string OutputFilePath { get; set; } + + /// + /// 文件名占位符参数(可选)| File name placeholder parameters (optional) + /// 用于 ReportConfig.FileNamePattern 中的占位符替换 + /// Used for placeholder replacement in ReportConfig.FileNamePattern + /// + public Dictionary FileNameParameters { get; set; } = new(); + + /// + /// 自定义属性(可选)| Custom properties (optional) + /// 额外的键值对数据,会合并到 ReportContext.Properties 中 + /// Additional key-value data merged into ReportContext.Properties + /// + public Dictionary CustomProperties { get; set; } = new(); + } +} diff --git a/XP.ReportEngine/Models/ReportResult.cs b/XP.ReportEngine/Models/ReportResult.cs new file mode 100644 index 0000000..c95753c --- /dev/null +++ b/XP.ReportEngine/Models/ReportResult.cs @@ -0,0 +1,22 @@ +using System; +using System.IO; + +namespace XP.ReportEngine.Models +{ + /// + /// 报告生成结果 | Report generation result + /// + public class ReportResult + { + public bool IsSuccess { get; set; } + public MemoryStream PdfStream { get; set; } + public string ErrorMessage { get; set; } + public Exception Exception { get; set; } + + public static ReportResult Success(MemoryStream stream) + => new() { IsSuccess = true, PdfStream = stream }; + + public static ReportResult Failure(string message, Exception ex = null) + => new() { IsSuccess = false, ErrorMessage = message, Exception = ex }; + } +} diff --git a/XP.ReportEngine/Models/ReportServiceResult.cs b/XP.ReportEngine/Models/ReportServiceResult.cs new file mode 100644 index 0000000..622a412 --- /dev/null +++ b/XP.ReportEngine/Models/ReportServiceResult.cs @@ -0,0 +1,49 @@ +using System; + +namespace XP.ReportEngine.Models +{ + /// + /// 报告服务结果 | Report service result + /// 封装报告生成的最终结果信息 + /// Encapsulates the final result of report generation + /// + public class ReportServiceResult + { + /// + /// 是否成功 | Whether successful + /// + public bool IsSuccess { get; set; } + + /// + /// 输出文件路径(成功时有值)| Output file path (has value when successful) + /// + public string OutputFilePath { get; set; } + + /// + /// 报告编号 | Report ID + /// + public string ReportId { get; set; } + + /// + /// 错误信息(失败时有值)| Error message (has value when failed) + /// + public string ErrorMessage { get; set; } + + /// + /// 异常对象(失败时有值)| Exception object (has value when failed) + /// + public Exception Exception { get; set; } + + /// + /// 创建成功结果 | Create success result + /// + public static ReportServiceResult Success(string outputFilePath, string reportId) + => new() { IsSuccess = true, OutputFilePath = outputFilePath, ReportId = reportId }; + + /// + /// 创建失败结果 | Create failure result + /// + public static ReportServiceResult Failure(string message, Exception ex = null) + => new() { IsSuccess = false, ErrorMessage = message, Exception = ex }; + } +} diff --git a/XP.ReportEngine/Models/ReportTemplate.cs b/XP.ReportEngine/Models/ReportTemplate.cs new file mode 100644 index 0000000..e2a2218 --- /dev/null +++ b/XP.ReportEngine/Models/ReportTemplate.cs @@ -0,0 +1,94 @@ +using System.Collections.Generic; + +namespace XP.ReportEngine.Models +{ + /// + /// 报告模板定义 | Report template definition + /// + public class ReportTemplate + { + public DocumentSettings Document { get; set; } + public List Pages { get; set; } = new(); + public Dictionary Styles { get; set; } = new(); + } + + public class DocumentSettings + { + public string PageSize { get; set; } = "A4"; + public string Orientation { get; set; } = "Portrait"; + public MarginSettings Margins { get; set; } = new(); + + /// + /// 页眉配置(仅内容页显示,首页不显示)| Header config (content pages only, not homepage) + /// + public HeaderFooterSettings Header { get; set; } + + /// + /// 页脚配置(仅内容页显示,首页不显示)| Footer config (content pages only, not homepage) + /// + public HeaderFooterSettings Footer { get; set; } + } + + public class MarginSettings + { + public float Top { get; set; } = 20f; + public float Bottom { get; set; } = 20f; + public float Left { get; set; } = 20f; + public float Right { get; set; } = 20f; + } + + /// + /// 页眉/页脚配置 | Header/Footer settings + /// + public class HeaderFooterSettings + { + /// + /// 是否启用 | Whether enabled + /// + public bool Enabled { get; set; } = true; + + /// + /// 左侧文本行(支持 ${} 绑定表达式)| Left-side text lines with binding expressions + /// + public List Left { get; set; } = new(); + + /// + /// 右侧文本行(支持 ${} 绑定表达式)| Right-side text lines with binding expressions + /// + public List Right { get; set; } = new(); + + /// + /// 右侧图像 dataKey(如 logo)| Right-side image dataKey (e.g. logo) + /// + public string RightImageKey { get; set; } + + /// + /// 左侧图像 dataKey | Left-side image dataKey + /// + public string LeftImageKey { get; set; } + + /// + /// 字体大小 | Font size + /// + public float FontSize { get; set; } = 8f; + + /// + /// 字体颜色 | Font color + /// + public string Color { get; set; } = "#666666"; + + /// + /// 是否显示分隔线 | Whether to show separator line + /// + public bool ShowLine { get; set; } = true; + } + + public class TemplatePage + { + /// + /// 页面类型:homepage / metricData / defectDetails / bgaInspection / voidInspection / viaFillInspection + /// + public string Type { get; set; } + public List Elements { get; set; } = new(); + } +} diff --git a/XP.ReportEngine/Models/TemplateElement.cs b/XP.ReportEngine/Models/TemplateElement.cs new file mode 100644 index 0000000..9b3dd27 --- /dev/null +++ b/XP.ReportEngine/Models/TemplateElement.cs @@ -0,0 +1,137 @@ +using System.Collections.Generic; + +namespace XP.ReportEngine.Models +{ + public class TemplateElement + { + /// + /// 元素类型:text / image / table / divider / spacer / row / grid + /// + public string Type { get; set; } + + /// + /// 文本内容(可含 ${} 绑定表达式)| Text content with binding expressions + /// + public string Content { get; set; } + + /// + /// 样式名称引用 | Style name reference + /// + public string Style { get; set; } + + /// + /// 数据绑定键 | Data binding key + /// + public string DataKey { get; set; } + + /// + /// 位置坐标 [x, y](mm)| Position coordinates in mm + /// + public float[] Position { get; set; } + + /// + /// 尺寸 [width, height](mm)| Size in mm + /// + public float[] Size { get; set; } + + /// + /// 表格列定义 | Table column definitions + /// + public List Columns { get; set; } + + /// + /// 是否显示边框 | Whether to show border + /// + public bool Border { get; set; } + + /// + /// 定位方式:absolute / flow | Positioning mode + /// + public string Positioning { get; set; } = "absolute"; + + /// + /// 子元素列表(用于 row 类型的水平布局容器) + /// Child elements (for row type horizontal layout container) + /// + public List Children { get; set; } + + /// + /// 水平对齐方式(用于 row 子元素):left / center / right + /// Horizontal alignment for row children + /// + public string Align { get; set; } + + /// + /// 列宽比例数组(用于 row 类型,如 [7, 3] 表示 7:3 比例) + /// Column width ratios for row type (e.g. [7, 3] means 7:3 ratio) + /// + public float[] Widths { get; set; } + + /// + /// 条件颜色规则(用于 text 元素,根据内容中包含的关键词匹配颜色) + /// Conditional color rules for text elements (match keywords in content to color) + /// 格式:{ "Pass": "#008000", "Fail": "#FF0000" } + /// + public Dictionary ColorRules { get; set; } + + /// + /// 表格数据行(数据绑定阶段填充,用于排版计算和渲染) + /// Table data rows (populated during data binding phase, used for layout calculation and rendering) + /// + [Newtonsoft.Json.JsonIgnore] + public List> TableData { get; set; } + + /// + /// 图像数据(数据绑定阶段填充)| Image data (populated during data binding phase) + /// + [Newtonsoft.Json.JsonIgnore] + public ImageData ImageData { get; set; } + } + + public class ColumnDefinition + { + public string Header { get; set; } + public string Field { get; set; } + public float Width { get; set; } + public string Align { get; set; } = "left"; + + /// + /// 条件颜色规则(根据单元格值匹配颜色) + /// Conditional color rules (match cell value to color) + /// 格式:{ "Pass": "#008000", "Fail": "#FF0000" } + /// + public Dictionary ColorRules { get; set; } + } + + public class StyleDefinition + { + public string Font { get; set; } + public float Size { get; set; } = 12f; + public bool Bold { get; set; } + public bool Italic { get; set; } + public string Color { get; set; } = "#000000"; + public string Align { get; set; } = "left"; + public string BackgroundColor { get; set; } + + /// + /// 上边距(mm)| Top margin in mm + /// + public float MarginTop { get; set; } + + /// + /// 下边距(mm)| Bottom margin in mm + /// + public float MarginBottom { get; set; } + + /// + /// 左缩进(mm)| Left indent/padding in mm + /// + public float PaddingLeft { get; set; } + + /// + /// 行高倍数(1.0 = 单倍行距,1.5 = 1.5倍行距,0 表示使用默认) + /// Line height multiplier (1.0 = single spacing, 1.5 = 1.5x spacing, 0 = use default) + /// + public float LineHeight { get; set; } + } +} diff --git a/XP.ReportEngine/Models/TemplateValidationResult.cs b/XP.ReportEngine/Models/TemplateValidationResult.cs new file mode 100644 index 0000000..6e2191a --- /dev/null +++ b/XP.ReportEngine/Models/TemplateValidationResult.cs @@ -0,0 +1,17 @@ +namespace XP.ReportEngine.Models +{ + /// + /// 模板验证结果 | Template validation result + /// + public class TemplateValidationResult + { + public bool IsValid { get; set; } + public string ErrorMessage { get; set; } + + public static TemplateValidationResult Valid() + => new() { IsValid = true }; + + public static TemplateValidationResult Invalid(string message) + => new() { IsValid = false, ErrorMessage = message }; + } +} diff --git a/XP.ReportEngine/ReportEngineModule.cs b/XP.ReportEngine/ReportEngineModule.cs new file mode 100644 index 0000000..a6cd48a --- /dev/null +++ b/XP.ReportEngine/ReportEngineModule.cs @@ -0,0 +1,98 @@ +using Prism.Ioc; +using Prism.Modularity; +using System.Resources; +using XP.Common.Localization; +using XP.Common.Localization.Interfaces; +using XP.Common.Logging.Interfaces; +using XP.ReportEngine.Configs; +using XP.ReportEngine.Interfaces; +using XP.ReportEngine.Services; +using XP.ReportEngine.ViewModels; +using XP.ReportEngine.Views; + +namespace XP.ReportEngine +{ + /// + /// 报告引擎模块 | Report Engine Module + /// Prism 模块入口,注册报告生成相关服务到 DI 容器 + /// Prism module entry, registers report generation services to DI container + /// + [Module(ModuleName = "ReportEngineModule")] + public class ReportEngineModule : IModule + { + /// + /// 模块初始化 | Module initialization + /// 注册模块级多语言资源源 | Register module-level localization resource source + /// + public void OnInitialized(IContainerProvider containerProvider) + { + // 注册模块级多语言资源到 Fallback Chain | Register module-level localization resources to Fallback Chain + var localizationService = containerProvider.Resolve(); + var resourceManager = new ResourceManager( + "XP.ReportEngine.Resources.Resources", + typeof(ReportEngineModule).Assembly); + localizationService.RegisterResourceSource("XP.ReportEngine", resourceManager); + + // 初始化 LocalizationHelper,使其通过 ILocalizationService 获取字符串(支持 Fallback Chain) + // Initialize LocalizationHelper to use ILocalizationService for string lookup (supports Fallback Chain) + LocalizationHelper.Initialize(localizationService); + + // 后台预热报告引擎(触发 iText7 初始化、字体加载、JIT 编译,避免首次生成卡顿) + // Background warm-up report engine (triggers iText7 init, font loading, JIT to avoid first-run latency) + var reportService = containerProvider.Resolve(); + _ = System.Threading.Tasks.Task.Run(() => reportService.WarmUpAsync()); + + System.Console.WriteLine("[ReportEngineModule] 模块已初始化 | Module initialized"); + } + + /// + /// 注册类型到 DI 容器 | Register types to DI container + /// + public void RegisterTypes(IContainerRegistry containerRegistry) + { + // 注册配置加载器(瞬态)| Register config loader (transient) + containerRegistry.Register(); + + // 加载并注册配置为单例 | Load and register config as singleton + containerRegistry.RegisterSingleton(container => + { + var logger = container.Resolve(); + var loader = new Configs.ConfigLoader(logger); + return loader.LoadReportConfig(); + }); + + // 注册报告生成器(瞬态)| Register report generator (transient) + containerRegistry.Register(); + + // 注册报告生成器工厂(单例)| Register report generator factory (singleton) + containerRegistry.RegisterSingleton(); + + // 注册报告服务门面(单例)| Register report service facade (singleton) + containerRegistry.RegisterSingleton(); + + // 注册模板引擎(瞬态)| Register template engine (transient) + containerRegistry.Register(); + + // 注册数据绑定器(瞬态)| Register data binder (transient) + containerRegistry.Register(); + + // 注册排版引擎(瞬态)| Register layout engine (transient) + containerRegistry.Register(); + + // 注册 PDF 渲染器(瞬态)| Register PDF renderer (transient) + containerRegistry.Register(); + + // 注册数据适配器(瞬态)| Register data adapter (transient) + containerRegistry.Register(); + + // 注册报告编号生成器(单例,维护每日计数器状态)| Register report ID generator (singleton, maintains daily counter state) + containerRegistry.RegisterSingleton(); + + // 注册演示窗口 ViewModel 和 View | Register demo window ViewModel and View + containerRegistry.Register(); + containerRegistry.Register(); + + System.Console.WriteLine("[ReportEngineModule] 类型注册完成 | Type registration completed"); + } + } +} diff --git a/XP.ReportEngine/Resources/Resources.en-US.resx b/XP.ReportEngine/Resources/Resources.en-US.resx new file mode 100644 index 0000000..9fe3fc9 --- /dev/null +++ b/XP.ReportEngine/Resources/Resources.en-US.resx @@ -0,0 +1,288 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Inspection Report + + + Inspection Time + + + Product Name + + + Operator + + + Inspection Summary + + + Report ID + + + Description + + + PASS + + + FAIL + + + Line Measurement + + + BGA Void Rate + + + Void Measurement + + + Via Fill Rate + + + No. + + + Void Rate + + + Classification + + + Area + + + Area % + + + Center X + + + Center Y + + + Measurement Type + + + Distance + + + Unit + + + Angle + + + Fill Rate + + + Void Rate + + + Limit + + + Total Defects + + + Pass Count + + + Fail Count + + + Overall Result + + + No Image + + + Inspection Report + + + Measurement Data + + + BGA Solder Ball Inspection + + + Void Inspection + + + Via Fill Rate Inspection + + + Ball Count + + + Total Ball Area + + + Total Void Area + + + Void Rate Limit + + + Ball No. + + + ROI Area + + + Total Void Area + + + Void Count + + + Max Void Area + + + Void Rate Limit + + + Fill Rate + + + Full Distance + + + Fill Distance + + + THT Limit + + + Point 1 + + + Point 2 + + + Result + + + Inspection Type + + + Status + + \ No newline at end of file diff --git a/XP.ReportEngine/Resources/Resources.resx b/XP.ReportEngine/Resources/Resources.resx new file mode 100644 index 0000000..db0c10a --- /dev/null +++ b/XP.ReportEngine/Resources/Resources.resx @@ -0,0 +1,288 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 检测报告 + + + 检测时间 + + + 产品名称 + + + 操作员 + + + 检测摘要 + + + 报告编号 + + + 描述 + + + 通过 + + + 不通过 + + + 距离测量 + + + BGA 气泡率 + + + 空隙测量 + + + 通孔填锡率 + + + 序号 + + + 气泡率 + + + 分类结果 + + + 面积 + + + 面积百分比 + + + 中心 X + + + 中心 Y + + + 测量类型 + + + 距离 + + + 单位 + + + 角度 + + + 填锡率 + + + 气泡率 + + + 限值 + + + 总缺陷数 + + + 通过数量 + + + 不通过数量 + + + 总体结果 + + + 无图像 + + + 检测报告首页 + + + 测量数据 + + + BGA 焊球检测 + + + 空隙检测 + + + 通孔填锡检测 + + + 焊球数量 + + + 焊球总面积 + + + 气泡总面积 + + + 气泡率限值 + + + 焊球序号 + + + ROI 面积 + + + 空隙总面积 + + + 空隙数量 + + + 最大空隙面积 + + + 空隙率限值 + + + 填锡率 + + + 满填距离 + + + 填充距离 + + + THT 限值 + + + 起点 + + + 终点 + + + 结果 + + + 检测类型 + + + 状态 + + \ No newline at end of file diff --git a/XP.ReportEngine/Resources/Resources.zh-CN.resx b/XP.ReportEngine/Resources/Resources.zh-CN.resx new file mode 100644 index 0000000..db0c10a --- /dev/null +++ b/XP.ReportEngine/Resources/Resources.zh-CN.resx @@ -0,0 +1,288 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 检测报告 + + + 检测时间 + + + 产品名称 + + + 操作员 + + + 检测摘要 + + + 报告编号 + + + 描述 + + + 通过 + + + 不通过 + + + 距离测量 + + + BGA 气泡率 + + + 空隙测量 + + + 通孔填锡率 + + + 序号 + + + 气泡率 + + + 分类结果 + + + 面积 + + + 面积百分比 + + + 中心 X + + + 中心 Y + + + 测量类型 + + + 距离 + + + 单位 + + + 角度 + + + 填锡率 + + + 气泡率 + + + 限值 + + + 总缺陷数 + + + 通过数量 + + + 不通过数量 + + + 总体结果 + + + 无图像 + + + 检测报告首页 + + + 测量数据 + + + BGA 焊球检测 + + + 空隙检测 + + + 通孔填锡检测 + + + 焊球数量 + + + 焊球总面积 + + + 气泡总面积 + + + 气泡率限值 + + + 焊球序号 + + + ROI 面积 + + + 空隙总面积 + + + 空隙数量 + + + 最大空隙面积 + + + 空隙率限值 + + + 填锡率 + + + 满填距离 + + + 填充距离 + + + THT 限值 + + + 起点 + + + 终点 + + + 结果 + + + 检测类型 + + + 状态 + + \ No newline at end of file diff --git a/XP.ReportEngine/Resources/Resources.zh-TW.resx b/XP.ReportEngine/Resources/Resources.zh-TW.resx new file mode 100644 index 0000000..9107ec1 --- /dev/null +++ b/XP.ReportEngine/Resources/Resources.zh-TW.resx @@ -0,0 +1,241 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + 檢測報告 + + + 檢測時間 + + + 樣品名稱 + + + 操作員 + + + 檢測摘要 + + + 報告編號 + + + 描述 + + + + 通過 + + + 不通過 + + + + 距離測量 + + + BGA 氣泡率 + + + 空隙測量 + + + 通孔填錫率 + + + + 序號 + + + 氣泡率 + + + 分類結果 + + + 面積 + + + 面積百分比 + + + 中心 X + + + 中心 Y + + + + 測量類型 + + + 距離 + + + 單位 + + + 角度 + + + 填錫率 + + + 氣泡率 + + + 限值 + + + + 總缺陷數 + + + 通過數量 + + + 不通過數量 + + + 總體結果 + + + + 無圖像 + + + + 檢測報告首頁 + + + 測量數據 + + + BGA 焊球檢測 + + + 空隙檢測 + + + 通孔填錫檢測 + + + + 焊球數量 + + + 焊球總面積 + + + 氣泡總面積 + + + 氣泡率限值 + + + 焊球序號 + + + + ROI 面積 + + + 空隙總面積 + + + 空隙數量 + + + 最大空隙面積 + + + 空隙率限值 + + + + 填錫率 + + + 滿填距離 + + + 填充距離 + + + THT 限值 + + + + 起點 + + + 終點 + + + 結果 + + + 檢測類型 + + + 狀態 + + diff --git a/XP.ReportEngine/Services/ExpressionDataBinder.cs b/XP.ReportEngine/Services/ExpressionDataBinder.cs new file mode 100644 index 0000000..a3ce3a8 --- /dev/null +++ b/XP.ReportEngine/Services/ExpressionDataBinder.cs @@ -0,0 +1,460 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.Reflection; +using System.Text.RegularExpressions; +using Newtonsoft.Json; +using XP.Common.Localization.Enums; +using XP.Common.Localization.Interfaces; +using XP.Common.Logging.Interfaces; +using XP.ReportEngine.Interfaces; +using XP.ReportEngine.Models; + +namespace XP.ReportEngine.Services +{ + /// + /// 表达式数据绑定器实现 | Expression data binder implementation + /// 支持 ${} 语法的数据绑定、格式化函数和本地化键解析 + /// Supports ${} syntax data binding, format functions and localization key resolution + /// + public class ExpressionDataBinder : IDataBinder + { + private readonly ILoggerService _logger; + private readonly ILocalizationService _localizationService; + + private static readonly Regex ExpressionPattern = new(@"\$\{([^}]+)\}", RegexOptions.Compiled); + private static readonly Regex LocalizationPattern = new(@"^loc:(.+)$", RegexOptions.Compiled); + private static readonly Regex FunctionPattern = new(@"^(\w+)\((.+)\)$", RegexOptions.Compiled); + private static readonly Regex IndexPattern = new(@"^([^\[]+)\[(\d+)\]$", RegexOptions.Compiled); + + public ExpressionDataBinder(ILoggerService logger, ILocalizationService localizationService) + { + _logger = logger?.ForModule() ?? throw new ArgumentNullException(nameof(logger)); + _localizationService = localizationService ?? throw new ArgumentNullException(nameof(localizationService)); + } + + public ReportTemplate Bind(ReportTemplate template, ReportContext context) + { + if (template == null) throw new ArgumentNullException(nameof(template)); + if (context == null) throw new ArgumentNullException(nameof(context)); + + _logger.Info("开始数据绑定 | Starting data binding"); + var clonedTemplate = DeepClone(template); + + if (clonedTemplate.Pages != null) + { + foreach (var page in clonedTemplate.Pages) + { + if (page.Elements == null) continue; + foreach (var element in page.Elements) + { + BindElement(element, context); + } + } + } + + // 绑定页眉页脚中的表达式 | Bind expressions in header/footer + BindHeaderFooter(clonedTemplate, context); + + _logger.Info("数据绑定完成 | Data binding completed"); + return clonedTemplate; + } + + + private void BindElement(TemplateElement element, ReportContext context) + { + // 文本内容绑定 | Text content binding + if (!string.IsNullOrEmpty(element.Content)) + { + element.Content = ResolveAllExpressions(element.Content, context); + } + + // 图像元素绑定:通过 DataKey 从 ReportContext.Images 获取 ImageData + // Image element binding: get ImageData from ReportContext.Images via DataKey + if (string.Equals(element.Type, "image", StringComparison.OrdinalIgnoreCase) + && !string.IsNullOrEmpty(element.DataKey) + && context.Images != null + && context.Images.TryGetValue(element.DataKey, out var imageData)) + { + element.ImageData = imageData; + } + + // 表格数据绑定:通过 DataKey 从 ReportContext.Properties 获取表格行数据 + // Table data binding: get table row data from ReportContext.Properties via DataKey + if (string.Equals(element.Type, "table", StringComparison.OrdinalIgnoreCase) + && !string.IsNullOrEmpty(element.DataKey) + && context.Properties != null + && context.Properties.TryGetValue(element.DataKey, out var tableValue) + && tableValue is List> tableRows) + { + element.TableData = tableRows; + } + + // 表格列头绑定 | Table column header binding + if (element.Columns != null) + { + foreach (var column in element.Columns) + { + if (!string.IsNullOrEmpty(column.Header)) + { + column.Header = ResolveAllExpressions(column.Header, context); + } + } + } + + // Row 子元素递归绑定 | Recursively bind row child elements + if (string.Equals(element.Type, "row", StringComparison.OrdinalIgnoreCase) + && element.Children != null) + { + foreach (var child in element.Children) + { + BindElement(child, context); + // Column 子元素递归绑定 | Recursively bind column child elements + if (string.Equals(child.Type, "column", StringComparison.OrdinalIgnoreCase) + && child.Children != null) + { + foreach (var subChild in child.Children) + { + BindElement(subChild, context); + } + } + } + } + } + + /// + /// 绑定页眉页脚中的 ${} 表达式 | Bind ${} expressions in header/footer + /// + private void BindHeaderFooter(ReportTemplate template, ReportContext context) + { + if (template?.Document == null) return; + + // 绑定页眉文本 | Bind header text + if (template.Document.Header != null && template.Document.Header.Enabled) + { + BindStringList(template.Document.Header.Left, context); + BindStringList(template.Document.Header.Right, context); + } + + // 绑定页脚文本 | Bind footer text + if (template.Document.Footer != null && template.Document.Footer.Enabled) + { + BindStringList(template.Document.Footer.Left, context); + BindStringList(template.Document.Footer.Right, context); + } + } + + /// + /// 绑定字符串列表中的表达式 | Bind expressions in string list + /// + private void BindStringList(List lines, ReportContext context) + { + if (lines == null) return; + for (int i = 0; i < lines.Count; i++) + { + if (!string.IsNullOrEmpty(lines[i])) + { + lines[i] = ResolveAllExpressions(lines[i], context); + } + } + } + + private string ResolveAllExpressions(string input, ReportContext context) + { + return ExpressionPattern.Replace(input, match => + { + var expression = match.Groups[1].Value.Trim(); + return ResolveExpression(expression, context); + }); + } + + private string ResolveExpression(string expression, ReportContext context) + { + // 1. 本地化键 loc:ResourceKey | Localization key + var locMatch = LocalizationPattern.Match(expression); + if (locMatch.Success) + { + var resourceKey = locMatch.Groups[1].Value.Trim(); + return ResolveLocalizationKey(resourceKey); + } + + // 2. 格式化函数 functionName(params) | Format function + var funcMatch = FunctionPattern.Match(expression); + if (funcMatch.Success) + { + var functionName = funcMatch.Groups[1].Value; + var paramExpression = funcMatch.Groups[2].Value.Trim(); + return ResolveFormatFunction(functionName, paramExpression, context); + } + + // 3. 属性路径 | Property path + return ResolvePropertyPath(expression, context); + } + + private string ResolveLocalizationKey(string resourceKey) + { + try + { + var value = _localizationService.GetString(resourceKey); + if (value == null) + { + _logger.Warn("本地化键未找到: {Key} | Localization key not found: {Key}", resourceKey); + return string.Empty; + } + return value; + } + catch (Exception ex) + { + _logger.Warn("解析本地化键失败: {Key}, 错误: {Message} | Failed to resolve localization key: {Key}, error: {Message}", resourceKey, ex.Message); + return string.Empty; + } + } + + + private string ResolveFormatFunction(string functionName, string paramExpression, ReportContext context) + { + switch (functionName.ToLowerInvariant()) + { + case "formatdate": + { + var value = ResolvePropertyValue(paramExpression, context); + return FormatDate(value); + } + case "formatnumber": + { + var parts = paramExpression.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + var value = ResolvePropertyValue(parts[0].Trim(), context); + var decimals = parts.Length > 1 && int.TryParse(parts[1].Trim(), out var d) ? d : 2; + return FormatNumber(value, decimals); + } + case "formatpercent": + { + var value = ResolvePropertyValue(paramExpression, context); + return FormatPercent(value); + } + default: + { + _logger.Warn("未知的格式化函数: {FunctionName} | Unknown format function: {FunctionName}", functionName); + return string.Empty; + } + } + } + + private string ResolvePropertyPath(string path, ReportContext context) + { + var value = ResolvePropertyValue(path, context); + if (value == null) + { + _logger.Warn("绑定属性未找到: {Path},替换为空字符串 | Binding property not found: {Path}, replacing with empty string", path); + return string.Empty; + } + return ConvertToString(value); + } + + private object ResolvePropertyValue(string path, ReportContext context) + { + if (string.IsNullOrWhiteSpace(path)) return null; + path = path.Trim(); + + // 优先从 Properties 字典查找 | First look up in Properties dictionary + if (context.Properties != null && context.Properties.TryGetValue(path, out var directValue)) + { + return directValue; + } + + // 尝试从 context 对象解析嵌套路径 | Try nested path from context object + var resolved = ResolveNestedPath(path, context); + return resolved; + } + + + private object ResolveNestedPath(string path, object root) + { + if (root == null || string.IsNullOrWhiteSpace(path)) return null; + + var segments = path.Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries); + var current = root; + + foreach (var segment in segments) + { + if (current == null) return null; + + var indexMatch = IndexPattern.Match(segment); + if (indexMatch.Success) + { + var propertyName = indexMatch.Groups[1].Value; + var index = int.Parse(indexMatch.Groups[2].Value); + current = GetPropertyValue(current, propertyName); + if (current == null) return null; + current = GetIndexedValue(current, index); + } + else + { + current = GetPropertyValue(current, segment); + } + } + + return current; + } + + private object GetPropertyValue(object obj, string propertyName) + { + if (obj == null || string.IsNullOrWhiteSpace(propertyName)) return null; + + // 字典访问 | Dictionary access + if (obj is IDictionary dict) + { + if (dict.TryGetValue(propertyName, out var dictValue)) + return dictValue; + foreach (var kvp in dict) + { + if (string.Equals(kvp.Key, propertyName, StringComparison.OrdinalIgnoreCase)) + return kvp.Value; + } + return null; + } + + // 反射获取属性 | Reflection property access + var type = obj.GetType(); + var propInfo = type.GetProperty(propertyName, + BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase); + if (propInfo != null) + { + try { return propInfo.GetValue(obj); } + catch { return null; } + } + + return null; + } + + private object GetIndexedValue(object obj, int index) + { + if (obj == null || index < 0) return null; + + if (obj is IList list) + { + return index < list.Count ? list[index] : null; + } + + if (obj is IEnumerable enumerable) + { + var i = 0; + foreach (var item in enumerable) + { + if (i == index) return item; + i++; + } + } + + return null; + } + + + private string FormatDate(object value) + { + if (value == null) return string.Empty; + + DateTime dateTime; + if (value is DateTime dt) + { + dateTime = dt; + } + else if (DateTime.TryParse(value.ToString(), out var parsed)) + { + dateTime = parsed; + } + else + { + _logger.Warn("无法将值转换为日期: {Value} | Cannot convert value to date: {Value}", value); + return value.ToString(); + } + + var format = _localizationService.CurrentLanguage switch + { + SupportedLanguage.ZhCN => "yyyy年MM月dd日 HH:mm:ss", + SupportedLanguage.ZhTW => "yyyy年MM月dd日 HH:mm:ss", + SupportedLanguage.EnUS => "MM/dd/yyyy HH:mm:ss", + _ => "yyyy-MM-dd HH:mm:ss" + }; + + return dateTime.ToString(format); + } + + private string FormatNumber(object value, int decimals) + { + if (value == null) return string.Empty; + + if (!TryConvertToDouble(value, out var number)) + { + _logger.Warn("无法将值转换为数字: {Value} | Cannot convert value to number: {Value}", value); + return value.ToString(); + } + + var culture = GetCultureInfo(); + return number.ToString($"N{decimals}", culture); + } + + private string FormatPercent(object value) + { + if (value == null) return string.Empty; + + if (!TryConvertToDouble(value, out var number)) + { + _logger.Warn("无法将值转换为百分比: {Value} | Cannot convert value to percentage: {Value}", value); + return value.ToString(); + } + + // 值在 0-1 范围内视为小数百分比 | Values in 0-1 range treated as decimal percentage + if (number >= 0 && number <= 1) + { + number *= 100; + } + + var culture = GetCultureInfo(); + return number.ToString("F2", culture) + "%"; + } + + + private bool TryConvertToDouble(object value, out double result) + { + result = 0; + if (value == null) return false; + + switch (value) + { + case double d: result = d; return true; + case float f: result = f; return true; + case int i: result = i; return true; + case long l: result = l; return true; + case decimal dec: result = (double)dec; return true; + default: + return double.TryParse(value.ToString(), NumberStyles.Any, CultureInfo.InvariantCulture, out result); + } + } + + private CultureInfo GetCultureInfo() + { + return _localizationService.CurrentLanguage switch + { + SupportedLanguage.ZhCN => new CultureInfo("zh-CN"), + SupportedLanguage.ZhTW => new CultureInfo("zh-TW"), + SupportedLanguage.EnUS => new CultureInfo("en-US"), + _ => CultureInfo.InvariantCulture + }; + } + + private string ConvertToString(object value) + { + if (value == null) return string.Empty; + if (value is DateTime dt) return FormatDate(dt); + return value.ToString(); + } + + private ReportTemplate DeepClone(ReportTemplate template) + { + var json = JsonConvert.SerializeObject(template); + return JsonConvert.DeserializeObject(json); + } + } +} diff --git a/XP.ReportEngine/Services/ITextPdfRenderer.cs b/XP.ReportEngine/Services/ITextPdfRenderer.cs new file mode 100644 index 0000000..f97f21a --- /dev/null +++ b/XP.ReportEngine/Services/ITextPdfRenderer.cs @@ -0,0 +1,1481 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Windows.Media.Imaging; +using iText.IO.Font; +using iText.Kernel.Colors; +using iText.Kernel.Font; +using iText.Kernel.Geom; +using iText.Kernel.Pdf; +using iText.Kernel.Pdf.Canvas.Draw; +using iText.Layout; +using iText.Layout.Borders; +using iText.Layout.Element; +using iText.Layout.Properties; +using XP.Common.Localization.Enums; +using XP.Common.Localization.Interfaces; +using XP.Common.Logging.Interfaces; +using XP.ReportEngine.Interfaces; +using XP.ReportEngine.Models; + +namespace XP.ReportEngine.Services +{ + /// + /// iText 7 PDF 渲染器实现 | iText 7 PDF renderer implementation + /// + public class ITextPdfRenderer : IPdfRenderer + { + private readonly ILoggerService _logger; + private readonly ILocalizationService _localizationService; + + /// + /// mm 到 points 的转换系数 | mm to points conversion factor + /// + private const float MmToPoints = 2.83465f; + + /// + /// A4 页面宽度(mm)| A4 page width in mm + /// + private const float A4WidthMm = 210f; + + /// + /// A4 页面高度(mm)| A4 page height in mm + /// + private const float A4HeightMm = 297f; + + /// + /// 表头背景色 | Table header background color + /// + private const string HeaderBackgroundColor = "#E0E0E0"; + + /// + /// 表格奇数行背景色 | Table odd row background color + /// + private const string OddRowBackgroundColor = "#FFFFFF"; + + /// + /// 表格偶数行背景色 | Table even row background color + /// + private const string EvenRowBackgroundColor = "#F5F5F5"; + + private PdfFont _cjkFont; + private PdfFont _westernFont; + private bool _fontsInitialized; + private readonly object _fontLock = new(); + private ReportTemplate _currentTemplate; + + public ITextPdfRenderer(ILoggerService logger, ILocalizationService localizationService) + { + _logger = logger?.ForModule() ?? throw new ArgumentNullException(nameof(logger)); + _localizationService = localizationService ?? throw new ArgumentNullException(nameof(localizationService)); + // 字体延迟加载,不在构造函数中阻塞 | Fonts loaded lazily, not blocking in constructor + } + + #region 7.1 基础 PDF 文档创建 | Basic PDF document creation + + /// + /// 将排版结果渲染为 PDF 内存流 | Render layout result to PDF memory stream + /// + /// 排版后的页面列表 | Laid-out pages + /// 生成选项 | Generation options + /// 绑定后的模板(用于页眉页脚配置)| Bound template (for header/footer config) + /// PDF 内存流 | PDF memory stream + public MemoryStream Render(List pages, ReportGenerationOptions options, ReportTemplate template = null) + { + _logger.Info("开始 PDF 渲染,共 {PageCount} 页 | Starting PDF rendering, {PageCount} pages", pages?.Count ?? 0); + _currentTemplate = template; + + // 每次渲染重置字体,避免跨 PdfDocument 复用导致 "belongs to other PDF document" 错误 + // Reset fonts on each render to avoid cross-PdfDocument reuse error + _fontsInitialized = false; + _cjkFont = null; + _westernFont = null; + + var memoryStream = new MemoryStream(); + + try + { + var writer = new PdfWriter(memoryStream, new WriterProperties().SetFullCompressionMode(true)); + // 防止 PdfWriter 关闭时关闭底层流 | Prevent PdfWriter from closing the underlying stream + writer.SetCloseStream(false); + + var pdfDocument = new PdfDocument(writer); + // 设置 A4 页面尺寸 | Set A4 page size + pdfDocument.SetDefaultPageSize(PageSize.A4); + + var document = new Document(pdfDocument); + + // 设置默认边距(使用默认 20mm)| Set default margins (20mm default) + float marginTop = 20f * MmToPoints; + float marginBottom = 20f * MmToPoints; + float marginLeft = 20f * MmToPoints; + float marginRight = 20f * MmToPoints; + + // 如果有页眉页脚配置,为内容页增加边距空间 | Increase margins for header/footer on content pages + var headerConfig = template?.Document?.Header; + var footerConfig = template?.Document?.Footer; + bool hasHeader = headerConfig != null && headerConfig.Enabled; + bool hasFooter = footerConfig != null && footerConfig.Enabled; + + // 页眉页脚占用的额外空间(mm → points)| Extra space for header/footer + float headerAreaHeight = hasHeader ? 15f * MmToPoints : 0f; + float footerAreaHeight = hasFooter ? 12f * MmToPoints : 0f; + + document.SetMargins(marginTop, marginRight, marginBottom, marginLeft); + + // 注册页眉页脚事件处理器 | Register header/footer event handler + HeaderFooterEventHandler headerFooterHandler = null; + if (hasHeader || hasFooter) + { + headerFooterHandler = new HeaderFooterEventHandler( + this, template, pages, _logger); + pdfDocument.AddEventHandler(iText.Kernel.Events.PdfDocumentEvent.END_PAGE, headerFooterHandler); + } + + if (pages != null && pages.Count > 0) + { + for (int i = 0; i < pages.Count; i++) + { + var pageStopwatch = System.Diagnostics.Stopwatch.StartNew(); + + if (i > 0) + { + // 添加新页面 | Add new page + document.Add(new AreaBreak(AreaBreakType.NEXT_PAGE)); + } + + // 非首页增加页眉页脚边距 | Add header/footer margins for non-homepage + bool isHomepage = string.Equals(pages[i].PageType, "homepage", StringComparison.OrdinalIgnoreCase); + if (!isHomepage) + { + // 通过添加顶部间距为页眉留出空间 | Add top spacing for header area + if (hasHeader) + { + document.Add(new Paragraph("").SetMarginBottom(headerAreaHeight).SetFontSize(1)); + } + } + + RenderPage(document, pages[i]); + + pageStopwatch.Stop(); + _logger.Info("第 {PageIndex}/{TotalPages} 页渲染完成,类型: {PageType},元素数: {ElementCount},耗时: {ElapsedMs}ms | Page {PageIndex}/{TotalPages} rendered, type: {PageType}, elements: {ElementCount}, elapsed: {ElapsedMs}ms", + i + 1, pages.Count, pages[i].PageType ?? "unknown", pages[i].Elements?.Count ?? 0, pageStopwatch.ElapsedMilliseconds); + } + } + + // 文档关闭前回填总页数占位符 | Fill total page count placeholder before closing + if (headerFooterHandler != null) + { + headerFooterHandler.WriteTotal(pdfDocument); + } + + // 关闭文档(触发字体子集化嵌入 + PDF 交叉引用表写入 + 流压缩) + // Close document (triggers font subsetting + PDF cross-reference table writing + stream compression) + _logger.Info("开始关闭文档(字体嵌入 + 压缩)| Starting document close (font embedding + compression)"); + var closeStopwatch = System.Diagnostics.Stopwatch.StartNew(); + document.Close(); + closeStopwatch.Stop(); + _logger.Info("文档关闭完成,耗时: {ElapsedMs}ms | Document close completed, elapsed: {ElapsedMs}ms", closeStopwatch.ElapsedMilliseconds); + + // 重置流位置以便后续读取 | Reset stream position for subsequent reading + memoryStream.Position = 0; + _logger.Info("PDF 渲染完成 | PDF rendering completed"); + } + catch (Exception ex) + { + _logger.Error(ex, "PDF 渲染过程中发生错误 | Error occurred during PDF rendering: {Message}", ex.Message); + throw; + } + + return memoryStream; + } + + /// + /// 渲染单个页面 | Render a single page + /// + private void RenderPage(Document document, LayoutPage page) + { + if (page?.Elements == null) return; + + foreach (var element in page.Elements) + { + try + { + RenderElement(document, element); + } + catch (Exception ex) + { + _logger.Warn("渲染元素失败,跳过该元素 | Failed to render element, skipping: {Message}", ex.Message); + } + } + } + + /// + /// 根据元素类型分发渲染 | Dispatch rendering based on element type + /// + private void RenderElement(Document document, LayoutElement element) + { + if (element?.Source == null) return; + + var elementType = element.Source.Type?.ToLowerInvariant(); + + switch (elementType) + { + case "text": + RenderTextElement(document, element); + break; + case "image": + RenderImageElement(document, element); + break; + case "table": + RenderTableElement(document, element); + break; + case "divider": + RenderDividerElement(document, element); + break; + case "spacer": + RenderSpacerElement(document, element); + break; + case "row": + RenderRowElement(document, element); + break; + case "pagebreak": + RenderPageBreakElement(document); + break; + default: + _logger.Warn("未知的元素类型:{Type},跳过渲染 | Unknown element type: {Type}, skipping", elementType); + break; + } + } + + #endregion + + #region 7.2 文本元素渲染 | Text element rendering + + /// + /// 渲染文本元素 | Render text element + /// + private void RenderTextElement(Document document, LayoutElement element) + { + var content = element.ResolvedContent ?? string.Empty; + var style = element.ResolvedStyle ?? new StyleDefinition(); + + var paragraph = new Paragraph(content); + + // 设置紧凑的默认段落间距 | Set compact default paragraph spacing + paragraph.SetMarginTop(0); + paragraph.SetMarginBottom(2f); + + // 应用字体 | Apply font + var font = GetFontForCurrentLanguage(); + paragraph.SetFont(font); + + // 应用字体大小 | Apply font size + paragraph.SetFontSize(style.Size); + + // 应用粗体 | Apply bold + if (style.Bold) + { + paragraph.SetBold(); + } + + // 应用斜体 | Apply italic + if (style.Italic) + { + paragraph.SetItalic(); + } + + // 应用字体颜色 | Apply font color + var color = ParseColor(style.Color); + if (color != null) + { + paragraph.SetFontColor(color); + } + + // 应用条件颜色规则(根据内容关键词覆盖颜色)| Apply conditional color rules (override color by content keywords) + if (element.Source?.ColorRules != null && !string.IsNullOrEmpty(content)) + { + foreach (var rule in element.Source.ColorRules) + { + if (content.Contains(rule.Key, StringComparison.OrdinalIgnoreCase)) + { + var ruleColor = ParseColor(rule.Value); + if (ruleColor != null) + { + paragraph.SetFontColor(ruleColor); + paragraph.SetBold(); + } + break; + } + } + } + + // 应用对齐方式 | Apply text alignment + paragraph.SetTextAlignment(ParseTextAlignment(style.Align)); + + // 应用背景色 | Apply background color + if (!string.IsNullOrEmpty(style.BackgroundColor)) + { + var bgColor = ParseColor(style.BackgroundColor); + if (bgColor != null) + { + paragraph.SetBackgroundColor(bgColor); + } + } + + // 设置固定位置(如果有坐标信息)| Set fixed position if coordinates available + if (element.Width > 0) + { + paragraph.SetWidth(element.Width * MmToPoints); + } + + // 应用边距和缩进 | Apply margins and indent + if (style.MarginTop > 0) + paragraph.SetMarginTop(style.MarginTop * MmToPoints); + if (style.MarginBottom > 0) + paragraph.SetMarginBottom(style.MarginBottom * MmToPoints); + if (style.PaddingLeft > 0) + paragraph.SetPaddingLeft(style.PaddingLeft * MmToPoints); + if (style.LineHeight > 0) + paragraph.SetMultipliedLeading(style.LineHeight); + + document.Add(paragraph); + } + + #endregion + + #region 7.3 字体管理 | Font management + + /// + /// 确保字体已初始化(线程安全的延迟加载)| Ensure fonts are initialized (thread-safe lazy loading) + /// + private void EnsureFontsInitialized() + { + if (_fontsInitialized) return; + lock (_fontLock) + { + if (_fontsInitialized) return; + InitializeFonts(); + _fontsInitialized = true; + } + } + + /// + /// 初始化字体(从系统字体目录加载)| Initialize fonts (load from system fonts directory) + /// 使用 Windows 系统自带字体,确保 Telerik RadPdfViewer 兼容性 + /// Uses Windows built-in fonts to ensure Telerik RadPdfViewer compatibility + /// + private void InitializeFonts() + { + var fontsDir = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "Fonts"); + + // 加载微软雅黑(支持简体中文、繁体中文)| Load Microsoft YaHei (supports Simplified & Traditional Chinese) + try + { + var msyhPath = System.IO.Path.Combine(fontsDir, "msyh.ttc"); + _cjkFont = PdfFontFactory.CreateFont(msyhPath + ",0", PdfEncodings.IDENTITY_H, PdfFontFactory.EmbeddingStrategy.PREFER_EMBEDDED); + _logger.Info("中文字体加载成功(微软雅黑)| Chinese font loaded successfully (Microsoft YaHei)"); + } + catch (Exception ex) + { + _logger.Warn("微软雅黑加载失败,尝试后备字体 | Microsoft YaHei load failed, trying fallback: {Message}", ex.Message); + try + { + // 后备:宋体 | Fallback: SimSun + var simsunPath = System.IO.Path.Combine(fontsDir, "simsun.ttc"); + _cjkFont = PdfFontFactory.CreateFont(simsunPath + ",0", PdfEncodings.IDENTITY_H, PdfFontFactory.EmbeddingStrategy.PREFER_EMBEDDED); + _logger.Info("中文后备字体加载成功(宋体)| Chinese fallback font loaded successfully (SimSun)"); + } + catch (Exception ex2) + { + _logger.Warn("宋体加载失败 | SimSun load failed: {Message}", ex2.Message); + _cjkFont = null; + } + } + + // 加载 Arial(西文字体)| Load Arial (Western font) + try + { + var arialPath = System.IO.Path.Combine(fontsDir, "arial.ttf"); + _westernFont = PdfFontFactory.CreateFont(arialPath, PdfEncodings.IDENTITY_H, PdfFontFactory.EmbeddingStrategy.PREFER_EMBEDDED); + _logger.Info("西文字体加载成功(Arial)| Western font loaded successfully (Arial)"); + } + catch (Exception ex) + { + _logger.Warn("Arial 加载失败,使用 Helvetica 后备 | Arial load failed, using Helvetica fallback: {Message}", ex.Message); + _westernFont = null; + } + + // 如果系统字体都不可用,使用 iText 内置 Helvetica | If system fonts unavailable, use built-in Helvetica + if (_cjkFont == null && _westernFont == null) + { + _logger.Warn("所有系统字体不可用,使用 Helvetica 后备字体 | All system fonts unavailable, using Helvetica fallback"); + } + } + + /// + /// 根据当前语言获取合适的字体 | Get appropriate font based on current language + /// zh-CN / zh-TW → 微软雅黑(支持简繁体);en-US → Arial + /// + /// PDF 字体 | PDF font + private PdfFont GetFontForCurrentLanguage() + { + // 延迟初始化字体 | Lazy initialize fonts + EnsureFontsInitialized(); + + var language = _localizationService.CurrentLanguage; + + PdfFont selectedFont; + switch (language) + { + case SupportedLanguage.ZhCN: + case SupportedLanguage.ZhTW: + selectedFont = _cjkFont; + break; + case SupportedLanguage.EnUS: + default: + selectedFont = _westernFont ?? _cjkFont; // 西文优先,中文后备(微软雅黑也支持西文)| Western preferred, CJK fallback (YaHei supports Western too) + break; + } + + // 后备字体逻辑 | Fallback font logic + if (selectedFont != null) + { + return selectedFont; + } + + // 尝试使用其他字体 | Try other fonts + if (_cjkFont != null) return _cjkFont; + if (_westernFont != null) return _westernFont; + + // 最终后备:使用 iText 内置 Helvetica | Final fallback: use built-in Helvetica + return PdfFontFactory.CreateFont(iText.IO.Font.Constants.StandardFonts.HELVETICA); + } + + #endregion + + #region 7.4 图像嵌入渲染 | Image embedding rendering + + /// + /// 渲染图像元素 | Render image element + /// + private void RenderImageElement(Document document, LayoutElement element) + { + var imageData = element.ResolvedImage; + + // 如果图像数据缺失,渲染占位矩形 | If image data is missing, render placeholder + if (imageData == null) + { + _logger.Warn("图像数据为空,渲染占位矩形 | Image data is null, rendering placeholder"); + RenderImagePlaceholder(document, element); + return; + } + + try + { + byte[] imageBytes = GetImageBytes(imageData); + + if (imageBytes == null || imageBytes.Length == 0) + { + _logger.Warn("图像字节数据为空,渲染占位矩形 | Image byte data is empty, rendering placeholder"); + RenderImagePlaceholder(document, element); + return; + } + + // 创建 iText 图像对象 | Create iText image object + var iTextImageData = iText.IO.Image.ImageDataFactory.Create(imageBytes); + var image = new Image(iTextImageData); + + // 计算目标区域尺寸(mm → points)| Calculate target area size (mm → points) + float targetWidthPt = element.Width * MmToPoints; + float targetHeightPt = element.Height * MmToPoints; + + // 等比缩放以适应目标区域 | Scale proportionally to fit target area + if (targetWidthPt > 0 && targetHeightPt > 0) + { + float imageWidth = image.GetImageWidth(); + float imageHeight = image.GetImageHeight(); + + float scaleX = targetWidthPt / imageWidth; + float scaleY = targetHeightPt / imageHeight; + float scale = Math.Min(scaleX, scaleY); + + image.SetWidth(imageWidth * scale); + image.SetHeight(imageHeight * scale); + } + else if (targetWidthPt > 0) + { + image.SetWidth(targetWidthPt); + image.ScaleToFit(targetWidthPt, float.MaxValue); + } + + // 应用边框 | Apply border + if (element.Source?.Border == true) + { + image.SetBorder(new SolidBorder(ColorConstants.BLACK, 1f)); + } + + // 应用对齐方式 | Apply alignment + var align = element.Source?.Align?.ToLowerInvariant(); + if (align == "center") + { + image.SetHorizontalAlignment(HorizontalAlignment.CENTER); + } + else if (align == "right") + { + image.SetHorizontalAlignment(HorizontalAlignment.RIGHT); + } + + // 应用样式中的边距 | Apply margins from style + var style = element.ResolvedStyle; + if (style != null) + { + if (style.MarginTop > 0) + image.SetMarginTop(style.MarginTop * MmToPoints); + if (style.MarginBottom > 0) + image.SetMarginBottom(style.MarginBottom * MmToPoints); + } + + document.Add(image); + } + catch (Exception ex) + { + _logger.Warn("图像渲染失败,渲染占位矩形 | Image rendering failed, rendering placeholder: {Message}", ex.Message); + RenderImagePlaceholder(document, element); + } + } + + /// + /// 从 ImageData 获取字节数组 | Get byte array from ImageData + /// + private byte[] GetImageBytes(ImageData imageData) + { + switch (imageData.SourceType) + { + case ImageSourceType.Bytes: + return imageData.Bytes; + + case ImageSourceType.FilePath: + if (!string.IsNullOrEmpty(imageData.FilePath) && File.Exists(imageData.FilePath)) + { + return File.ReadAllBytes(imageData.FilePath); + } + _logger.Warn("图像文件不存在:{Path} | Image file not found: {Path}", imageData.FilePath); + return null; + + case ImageSourceType.BitmapSource: + if (imageData.BitmapSource is BitmapSource bitmapSource) + { + return ConvertBitmapSourceToBytes(bitmapSource); + } + _logger.Warn("BitmapSource 对象无效 | BitmapSource object is invalid"); + return null; + + default: + _logger.Warn("未知的图像来源类型:{Type} | Unknown image source type: {Type}", imageData.SourceType); + return null; + } + } + + #endregion + + #region 7.5 BitmapSource 转 byte[] | BitmapSource to byte[] conversion + + /// + /// 将 BitmapSource 转换为 PNG 编码的字节数组 | Convert BitmapSource to PNG-encoded byte array + /// + /// WPF BitmapSource 对象 | WPF BitmapSource object + /// PNG 编码的字节数组 | PNG-encoded byte array + public static byte[] ConvertBitmapSourceToBytes(BitmapSource bitmapSource) + { + if (bitmapSource == null) + { + return null; + } + + using (var memoryStream = new MemoryStream()) + { + var encoder = new PngBitmapEncoder(); + encoder.Frames.Add(BitmapFrame.Create(bitmapSource)); + encoder.Save(memoryStream); + return memoryStream.ToArray(); + } + } + + #endregion + + #region 7.6 图像占位矩形渲染 | Image placeholder rendering + + /// + /// 渲染图像缺失时的占位矩形(带"无图像 | No Image"文本标签) + /// Render placeholder rectangle when image is missing (with "无图像 | No Image" text label) + /// + private void RenderImagePlaceholder(Document document, LayoutElement element) + { + float widthPt = element.Width > 0 ? element.Width * MmToPoints : 100f * MmToPoints; + float heightPt = element.Height > 0 ? element.Height * MmToPoints : 60f * MmToPoints; + + // 使用表格模拟占位矩形(带边框和居中文本)| Use table to simulate placeholder rectangle + var table = new Table(1); + table.SetWidth(widthPt); + + var cell = new Cell(); + cell.SetHeight(heightPt); + cell.SetBorder(new SolidBorder(ColorConstants.GRAY, 1f)); + cell.SetBackgroundColor(new DeviceRgb(245, 245, 245)); + + // 居中显示"无图像 | No Image"文本 | Center "无图像 | No Image" text + var placeholderText = new Paragraph("无图像 | No Image"); + var font = GetFontForCurrentLanguage(); + placeholderText.SetFont(font); + placeholderText.SetFontSize(10f); + placeholderText.SetFontColor(ColorConstants.GRAY); + placeholderText.SetTextAlignment(TextAlignment.CENTER); + + cell.SetVerticalAlignment(VerticalAlignment.MIDDLE); + cell.Add(placeholderText); + table.AddCell(cell); + + document.Add(table); + } + + #endregion + + #region 7.7 表格渲染 | Table rendering + + /// + /// 渲染表格元素 | Render table element + /// + private void RenderTableElement(Document document, LayoutElement element) + { + var columns = element.Source?.Columns; + var tableData = element.ResolvedTableData; + + if (columns == null || columns.Count == 0) + { + _logger.Warn("表格列定义为空,跳过渲染 | Table column definitions are empty, skipping"); + return; + } + + // 计算列宽(mm → points)| Calculate column widths (mm → points) + var columnWidths = new float[columns.Count]; + for (int i = 0; i < columns.Count; i++) + { + columnWidths[i] = columns[i].Width > 0 ? columns[i].Width * MmToPoints : 30f * MmToPoints; + } + + var table = new Table(columnWidths); + table.SetWidth(UnitValue.CreatePercentValue(100)); + + var font = GetFontForCurrentLanguage(); + + // 渲染表头行 | Render header row + var headerBgColor = ParseColor(HeaderBackgroundColor); + foreach (var column in columns) + { + var headerCell = new Cell(); + var headerParagraph = new Paragraph(column.Header ?? string.Empty); + headerParagraph.SetFont(font); + headerParagraph.SetFontSize(10f); + headerParagraph.SetBold(); + headerParagraph.SetTextAlignment(ParseTextAlignment(column.Align)); + + headerCell.Add(headerParagraph); + headerCell.SetBackgroundColor(headerBgColor); + headerCell.SetBorder(new SolidBorder(ColorConstants.LIGHT_GRAY, 0.5f)); + + table.AddHeaderCell(headerCell); + } + + // 渲染数据行(交替背景色)| Render data rows (alternating background colors) + if (tableData != null) + { + for (int rowIndex = 0; rowIndex < tableData.Count; rowIndex++) + { + var rowData = tableData[rowIndex]; + var rowBgColor = rowIndex % 2 == 0 + ? ParseColor(OddRowBackgroundColor) + : ParseColor(EvenRowBackgroundColor); + + foreach (var column in columns) + { + var dataCell = new Cell(); + + // 从行数据中获取字段值 | Get field value from row data + string cellValue = string.Empty; + if (rowData != null && !string.IsNullOrEmpty(column.Field) && rowData.ContainsKey(column.Field)) + { + cellValue = rowData[column.Field]?.ToString() ?? string.Empty; + } + + var cellParagraph = new Paragraph(cellValue); + cellParagraph.SetFont(font); + cellParagraph.SetFontSize(9f); + cellParagraph.SetTextAlignment(ParseTextAlignment(column.Align)); + + // 应用条件颜色规则 | Apply conditional color rules + if (column.ColorRules != null && !string.IsNullOrEmpty(cellValue)) + { + foreach (var rule in column.ColorRules) + { + if (string.Equals(cellValue, rule.Key, StringComparison.OrdinalIgnoreCase) + || cellValue.Contains(rule.Key, StringComparison.OrdinalIgnoreCase)) + { + var ruleColor = ParseColor(rule.Value); + if (ruleColor != null) + { + cellParagraph.SetFontColor(ruleColor); + cellParagraph.SetBold(); + } + break; + } + } + } + + dataCell.Add(cellParagraph); + dataCell.SetBackgroundColor(rowBgColor); + dataCell.SetBorder(new SolidBorder(ColorConstants.LIGHT_GRAY, 0.5f)); + + table.AddCell(dataCell); + } + } + } + + // 应用样式中的边距 | Apply margins from style + var style = element.ResolvedStyle; + if (style != null) + { + if (style.MarginTop > 0) + table.SetMarginTop(style.MarginTop * MmToPoints); + if (style.MarginBottom > 0) + table.SetMarginBottom(style.MarginBottom * MmToPoints); + } + + document.Add(table); + } + + #endregion + + #region 7.8 分隔线渲染 | Divider rendering + + /// + /// 渲染分隔线元素 | Render divider element + /// + private void RenderDividerElement(Document document, LayoutElement element) + { + var style = element.ResolvedStyle; + + // 确定分隔线颜色(从样式或默认灰色)| Determine divider color (from style or default gray) + Color lineColor = ColorConstants.GRAY; + if (style != null && !string.IsNullOrEmpty(style.Color)) + { + var parsedColor = ParseColor(style.Color); + if (parsedColor != null) + { + lineColor = parsedColor; + } + } + + // 使用 LineSeparator 渲染水平分隔线 | Use LineSeparator to render horizontal divider + var lineSeparator = new LineSeparator(new SolidLine(1f)); + lineSeparator.SetStrokeColor(lineColor); + + // 设置宽度为可用区域全宽 | Set width to full available area + if (element.Width > 0) + { + lineSeparator.SetWidth(element.Width * MmToPoints); + } + + // 添加上下间距 | Add vertical spacing + lineSeparator.SetMarginTop(5f); + lineSeparator.SetMarginBottom(5f); + + document.Add(lineSeparator); + } + + /// + /// 渲染空白间距元素 | Render spacer element + /// 通过 Size[1](高度,mm)控制垂直空白大小 + /// Controls vertical whitespace via Size[1] (height in mm) + /// + private void RenderSpacerElement(Document document, LayoutElement element) + { + // 从 Size[1] 获取高度,默认 10mm | Get height from Size[1], default 10mm + float heightMm = 10f; + if (element.Source?.Size is { Length: >= 2 }) + { + heightMm = element.Source.Size[1]; + } + + // 使用空段落撑出指定高度的空白 | Use empty paragraph to create specified height whitespace + var spacer = new Paragraph("") + .SetFontSize(1) + .SetMarginTop(0) + .SetMarginBottom(heightMm * MmToPoints); + + document.Add(spacer); + } + + /// + /// 渲染强制分页元素 | Render forced page break element + /// + private void RenderPageBreakElement(Document document) + { + document.Add(new AreaBreak(AreaBreakType.NEXT_PAGE)); + } + + /// + /// 渲染行容器元素(水平布局)| Render row container element (horizontal layout) + /// 使用无边框表格实现子元素的水平排列,支持 left/center/right 对齐 + /// Uses borderless table to arrange child elements horizontally, supports left/center/right alignment + /// + private void RenderRowElement(Document document, LayoutElement element) + { + var children = element.Source?.Children; + if (children == null || children.Count == 0) return; + + // 创建表格,支持自定义列宽比例 | Create table with custom column width ratios + var columnCount = children.Count; + Table table; + + if (element.Source.Widths != null && element.Source.Widths.Length == columnCount) + { + // 使用指定的列宽比例 | Use specified column width ratios + var totalRatio = 0f; + foreach (var w in element.Source.Widths) totalRatio += w; + + var columnWidths = new float[columnCount]; + for (int i = 0; i < columnCount; i++) + { + columnWidths[i] = element.Source.Widths[i] / totalRatio; + } + table = new Table(UnitValue.CreatePercentArray(columnWidths)); + } + else + { + // 均分列宽 | Equal column widths + table = new Table(columnCount); + } + + table.UseAllAvailableWidth(); + table.SetBorder(iText.Layout.Borders.Border.NO_BORDER); + + var font = GetFontForCurrentLanguage(); + + foreach (var child in children) + { + var cell = new Cell(); + cell.SetBorder(iText.Layout.Borders.Border.NO_BORDER); + cell.SetPadding(0); + + // 确定子元素对齐方式 | Determine child element alignment + var align = child.Align?.ToLowerInvariant() ?? "left"; + cell.SetTextAlignment(ParseTextAlignment(align)); + + var childType = child.Type?.ToLowerInvariant(); + + if (childType == "column") + { + // 渲染列容器子元素(垂直堆叠多个元素在同一单元格内) + // Render column container child (stack multiple elements vertically in same cell) + if (child.Children != null) + { + foreach (var subChild in child.Children) + { + RenderRowChildIntoCell(cell, subChild, align, font); + } + } + } + else + { + RenderRowChildIntoCell(cell, child, align, font); + } + + table.AddCell(cell); + } + + document.Add(table); + } + + /// + /// 将单个子元素渲染到单元格中 | Render a single child element into a cell + /// + private void RenderRowChildIntoCell(Cell cell, TemplateElement child, string align, PdfFont font) + { + var childType = child.Type?.ToLowerInvariant(); + // 子元素可以覆盖父级对齐 | Child can override parent alignment + var childAlign = child.Align?.ToLowerInvariant() ?? align; + + if (childType == "image") + { + // 渲染图像子元素 | Render image child element + var imageData = child.ImageData; + if (imageData != null) + { + try + { + byte[] imageBytes = GetImageBytes(imageData); + if (imageBytes != null && imageBytes.Length > 0) + { + var iTextImageData = iText.IO.Image.ImageDataFactory.Create(imageBytes); + var image = new Image(iTextImageData); + + // 应用尺寸 | Apply size + float targetWidthPt = child.Size != null && child.Size.Length > 0 ? child.Size[0] * MmToPoints : 0; + float targetHeightPt = child.Size != null && child.Size.Length > 1 ? child.Size[1] * MmToPoints : 0; + + if (targetWidthPt > 0 && targetHeightPt > 0) + { + float imageWidth = image.GetImageWidth(); + float imageHeight = image.GetImageHeight(); + float scaleX = targetWidthPt / imageWidth; + float scaleY = targetHeightPt / imageHeight; + float scale = Math.Min(scaleX, scaleY); + image.SetWidth(imageWidth * scale); + image.SetHeight(imageHeight * scale); + } + + // 设置图像水平对齐 | Set image horizontal alignment + if (childAlign == "right") + image.SetHorizontalAlignment(HorizontalAlignment.RIGHT); + else if (childAlign == "center") + image.SetHorizontalAlignment(HorizontalAlignment.CENTER); + else + image.SetHorizontalAlignment(HorizontalAlignment.LEFT); + + cell.Add(image); + } + } + catch (Exception ex) + { + _logger.Warn("Row 子元素图像渲染失败 | Row child image rendering failed: {Message}", ex.Message); + } + } + } + else if (childType == "text") + { + // 渲染文本子元素 | Render text child element + var content = child.Content ?? string.Empty; + var style = ResolveStyleFromTemplate(child.Style); + + var paragraph = new Paragraph(content); + paragraph.SetFont(font); + paragraph.SetFontSize(style.Size); + if (style.Bold) paragraph.SetBold(); + if (style.Italic) paragraph.SetItalic(); + + var color = ParseColor(style.Color); + if (color != null) paragraph.SetFontColor(color); + + // 应用条件颜色规则 | Apply conditional color rules + if (child.ColorRules != null && !string.IsNullOrEmpty(content)) + { + foreach (var rule in child.ColorRules) + { + if (content.Contains(rule.Key, StringComparison.OrdinalIgnoreCase)) + { + var ruleColor = ParseColor(rule.Value); + if (ruleColor != null) + { + paragraph.SetFontColor(ruleColor); + paragraph.SetBold(); + } + break; + } + } + } + + paragraph.SetTextAlignment(ParseTextAlignment(childAlign)); + paragraph.SetMargin(0); + + cell.Add(paragraph); + } + } + + /// + /// 从当前模板中解析样式定义 | Resolve style definition from current template + /// + private StyleDefinition ResolveStyleFromTemplate(string styleName) + { + if (string.IsNullOrWhiteSpace(styleName)) + return new StyleDefinition(); + + if (_currentTemplate?.Styles != null + && _currentTemplate.Styles.TryGetValue(styleName, out var style)) + { + return style; + } + + return new StyleDefinition(); + } + + #endregion + + #region 7.85 页眉页脚渲染(事件驱动)| Header/Footer rendering (event-driven) + + /// + /// 页眉页脚事件处理器 | Header/Footer event handler + /// 在 END_PAGE 事件中绘制页眉页脚,使用 PdfFormXObject 占位符实现总页数回填 + /// Draws header/footer in END_PAGE event, uses PdfFormXObject placeholder for total page count + /// + private class HeaderFooterEventHandler : iText.Kernel.Events.IEventHandler + { + private readonly ITextPdfRenderer _renderer; + private readonly ReportTemplate _template; + private readonly List _pages; + private readonly ILoggerService _logger; + private readonly HeaderFooterSettings _headerConfig; + private readonly HeaderFooterSettings _footerConfig; + private readonly MarginSettings _margins; + private readonly PdfFont _font; + + // 首页数量 | Homepage count + private readonly int _homepageCount; + + // 总页数占位符模板(用于回填)| Total page count placeholder template (for backfill) + private readonly iText.Kernel.Pdf.Xobject.PdfFormXObject _totalPagePlaceholder; + private readonly List<(iText.Kernel.Pdf.Canvas.PdfCanvas canvas, float x, float y)> _totalPagePositions = new(); + + // 当前页面索引(从 0 开始)| Current page index (0-based) + private int _currentPageIndex = -1; + + public HeaderFooterEventHandler( + ITextPdfRenderer renderer, + ReportTemplate template, + List pages, + ILoggerService logger) + { + _renderer = renderer; + _template = template; + _pages = pages; + _logger = logger; + _headerConfig = template?.Document?.Header; + _footerConfig = template?.Document?.Footer; + _margins = template?.Document?.Margins ?? new MarginSettings(); + _font = renderer.GetFontForCurrentLanguage(); + + // 计算首页数量 | Calculate homepage count + _homepageCount = 0; + if (pages != null) + { + for (int i = 0; i < pages.Count; i++) + { + if (string.Equals(pages[i].PageType, "homepage", StringComparison.OrdinalIgnoreCase)) + _homepageCount++; + else + break; + } + } + + // 创建总页数占位符(固定宽度区域)| Create total page count placeholder (fixed width area) + _totalPagePlaceholder = new iText.Kernel.Pdf.Xobject.PdfFormXObject(new Rectangle(0, 0, 30, 12)); + } + + public void HandleEvent(iText.Kernel.Events.Event @event) + { + if (@event is not iText.Kernel.Events.PdfDocumentEvent docEvent) return; + + _currentPageIndex++; + var pdfDoc = docEvent.GetDocument(); + var pdfPage = docEvent.GetPage(); + var pageSize = pdfPage.GetPageSize(); + + // 跳过首页 | Skip homepage + if (_currentPageIndex < _homepageCount) return; + + int currentContentPageNum = _currentPageIndex - _homepageCount + 1; + + try + { + var canvas = new iText.Kernel.Pdf.Canvas.PdfCanvas(pdfPage.NewContentStreamBefore(), pdfPage.GetResources(), pdfDoc); + + // 绘制页眉 | Draw header + if (_headerConfig != null && _headerConfig.Enabled) + { + DrawHeader(canvas, pageSize); + } + + // 绘制页脚 | Draw footer + if (_footerConfig != null && _footerConfig.Enabled) + { + DrawFooter(canvas, pageSize, pdfDoc, currentContentPageNum); + } + + canvas.Release(); + } + catch (Exception ex) + { + _logger.Warn("页眉页脚绘制异常 | Header/footer drawing exception: {Message}", ex.Message); + } + } + + /// + /// 绘制页眉 | Draw header + /// + private void DrawHeader(iText.Kernel.Pdf.Canvas.PdfCanvas canvas, Rectangle pageSize) + { + float leftX = _margins.Left * MmToPoints; + float rightX = pageSize.GetWidth() - _margins.Right * MmToPoints; + float topY = pageSize.GetHeight() - (_margins.Top * MmToPoints * 0.3f); + + float fontSize = _headerConfig.FontSize > 0 ? _headerConfig.FontSize : 8f; + var fontColor = _renderer.ParseColor(_headerConfig.Color ?? "#666666"); + + // 绘制左侧文本行 | Draw left-side text lines + if (_headerConfig.Left != null && _headerConfig.Left.Count > 0) + { + float lineY = topY; + float lineSpacing = (fontSize + 2f) * 1.2f; + + foreach (var line in _headerConfig.Left) + { + if (string.IsNullOrEmpty(line)) continue; + + canvas.BeginText() + .SetFontAndSize(_font, fontSize) + .MoveText(leftX, lineY) + .ShowText(line) + .EndText(); + + lineY -= lineSpacing; + } + } + + // 绘制右上角 Logo | Draw right-side logo + if (!string.IsNullOrEmpty(_headerConfig.RightImageKey)) + { + try + { + ImageData logoImageData = _renderer.FindBoundImage(_template, _headerConfig.RightImageKey); + if (logoImageData != null) + { + byte[] imageBytes = _renderer.GetImageBytes(logoImageData); + if (imageBytes != null && imageBytes.Length > 0) + { + var iTextImageData = iText.IO.Image.ImageDataFactory.Create(imageBytes); + float logoHeight = 10f * MmToPoints; + float logoWidth = logoHeight * (iTextImageData.GetWidth() / iTextImageData.GetHeight()); + + float logoX = rightX - logoWidth; + float logoY = topY - logoHeight + fontSize; + + canvas.AddImageFittedIntoRectangle(iTextImageData, + new Rectangle(logoX, logoY, logoWidth, logoHeight), false); + } + } + } + catch (Exception ex) + { + _logger.Warn("页眉 Logo 渲染失败 | Header logo rendering failed: {Message}", ex.Message); + } + } + + // 绘制页眉分隔线 | Draw header separator line + if (_headerConfig.ShowLine) + { + float lineY = topY - (_headerConfig.Left?.Count ?? 1) * ((fontSize + 2f) * 1.2f) - 3f; + canvas.SetStrokeColor(fontColor) + .SetLineWidth(0.5f) + .MoveTo(leftX, lineY) + .LineTo(rightX, lineY) + .Stroke(); + } + } + + /// + /// 绘制页脚 | Draw footer + /// + private void DrawFooter(iText.Kernel.Pdf.Canvas.PdfCanvas canvas, Rectangle pageSize, PdfDocument pdfDoc, int currentPage) + { + float leftX = _margins.Left * MmToPoints; + float rightX = pageSize.GetWidth() - _margins.Right * MmToPoints; + float bottomY = _margins.Bottom * MmToPoints * 0.5f; + + float fontSize = _footerConfig.FontSize > 0 ? _footerConfig.FontSize : 8f; + var fontColor = _renderer.ParseColor(_footerConfig.Color ?? "#666666"); + + // 绘制页脚分隔线 | Draw footer separator line + if (_footerConfig.ShowLine) + { + float lineY = bottomY + fontSize + 5f; + canvas.SetStrokeColor(fontColor) + .SetLineWidth(0.5f) + .MoveTo(leftX, lineY) + .LineTo(rightX, lineY) + .Stroke(); + } + + // 绘制左侧文本(公司名称)| Draw left-side text (company name) + if (_footerConfig.Left != null && _footerConfig.Left.Count > 0) + { + var leftText = _footerConfig.Left[0] ?? string.Empty; + canvas.BeginText() + .SetFontAndSize(_font, fontSize) + .MoveText(leftX, bottomY) + .ShowText(leftText) + .EndText(); + } + + // 绘制右侧页码(当前页 / 总页数占位符)| Draw right-side page number (current / total placeholder) + if (_footerConfig.Right != null && _footerConfig.Right.Count > 0) + { + var pageNumTemplate = _footerConfig.Right[0] ?? string.Empty; + // 先写当前页码部分 | Write current page number part + var currentPageText = pageNumTemplate.Replace("{currentPage}", currentPage.ToString()).Replace("{totalPages}", ""); + // 分离出 totalPages 前后的文本 | Separate text around totalPages + var parts = pageNumTemplate.Split(new[] { "{totalPages}" }, StringSplitOptions.None); + + if (parts.Length == 2) + { + // 有总页数占位符:写前缀 + 当前页码 + 占位符 XObject + 后缀 + var prefix = parts[0].Replace("{currentPage}", currentPage.ToString()); + var suffix = parts[1]; + + float prefixWidth = _font.GetWidth(prefix, fontSize); + float suffixWidth = _font.GetWidth(suffix, fontSize); + float placeholderWidth = 15f; // 预留总页数宽度 | Reserve width for total pages + + float totalWidth = prefixWidth + placeholderWidth + suffixWidth; + float startX = rightX - totalWidth; + + // 写前缀文本 | Write prefix text + canvas.BeginText() + .SetFontAndSize(_font, fontSize) + .MoveText(startX, bottomY) + .ShowText(prefix) + .EndText(); + + // 添加总页数占位符 XObject | Add total page count placeholder XObject + float placeholderX = startX + prefixWidth; + canvas.AddXObjectAt(_totalPagePlaceholder, placeholderX, bottomY - 2f); + _totalPagePositions.Add((canvas, placeholderX, bottomY)); + + // 写后缀文本 | Write suffix text + if (!string.IsNullOrEmpty(suffix)) + { + canvas.BeginText() + .SetFontAndSize(_font, fontSize) + .MoveText(placeholderX + placeholderWidth, bottomY) + .ShowText(suffix) + .EndText(); + } + } + else + { + // 无总页数占位符,直接写文本 | No total pages placeholder, write text directly + var text = pageNumTemplate.Replace("{currentPage}", currentPage.ToString()); + float textWidth = _font.GetWidth(text, fontSize); + float textX = rightX - textWidth; + + canvas.BeginText() + .SetFontAndSize(_font, fontSize) + .MoveText(textX, bottomY) + .ShowText(text) + .EndText(); + } + } + } + + /// + /// 文档关闭前回填总页数到所有占位符 | Write total page count to all placeholders before document close + /// + public void WriteTotal(PdfDocument pdfDoc) + { + int totalContentPages = pdfDoc.GetNumberOfPages() - _homepageCount; + var totalText = totalContentPages.ToString(); + + // 在占位符 XObject 上绘制总页数 | Draw total page count on placeholder XObject + var canvas = new iText.Kernel.Pdf.Canvas.PdfCanvas(_totalPagePlaceholder, pdfDoc); + canvas.BeginText() + .SetFontAndSize(_font, _footerConfig?.FontSize > 0 ? _footerConfig.FontSize : 8f) + .MoveText(0, 2f) + .ShowText(totalText) + .EndText(); + canvas.Release(); + } + } + + /// + /// 从模板中查找已绑定的图像数据 | Find bound image data from template + /// + internal ImageData FindBoundImage(ReportTemplate template, string dataKey) + { + if (template?.Pages == null || string.IsNullOrEmpty(dataKey)) return null; + + foreach (var page in template.Pages) + { + if (page.Elements == null) continue; + foreach (var element in page.Elements) + { + var found = FindImageInElement(element, dataKey); + if (found != null) return found; + } + } + return null; + } + + /// + /// 递归搜索元素及其子元素中的图像数据 | Recursively search for image data in element and its children + /// + private ImageData FindImageInElement(TemplateElement element, string dataKey) + { + if (element == null) return null; + + // 检查当前元素 | Check current element + if (string.Equals(element.Type, "image", StringComparison.OrdinalIgnoreCase) + && string.Equals(element.DataKey, dataKey, StringComparison.OrdinalIgnoreCase) + && element.ImageData != null) + { + return element.ImageData; + } + + // 递归搜索子元素 | Recursively search children + if (element.Children != null) + { + foreach (var child in element.Children) + { + var found = FindImageInElement(child, dataKey); + if (found != null) return found; + } + } + + return null; + } + + #endregion + + #region 7.9 PDF 保存到文件 | PDF save to file + + /// + /// 将 PDF 内存流保存到文件 | Save PDF memory stream to file + /// + /// PDF 内存流 | PDF memory stream + /// 输出文件路径 | Output file path + /// 保存结果(成功/失败)| Save result (success/failure) + public ReportResult SaveToFile(MemoryStream pdfStream, string filePath) + { + if (pdfStream == null) + { + return ReportResult.Failure("PDF 流为空,无法保存 | PDF stream is null, cannot save"); + } + + if (string.IsNullOrWhiteSpace(filePath)) + { + return ReportResult.Failure("输出文件路径为空 | Output file path is empty"); + } + + try + { + _logger.Info("开始保存 PDF 到文件:{FilePath} | Saving PDF to file: {FilePath}", filePath); + + // 确保目标目录存在 | Ensure target directory exists + var directory = System.IO.Path.GetDirectoryName(filePath); + if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) + { + Directory.CreateDirectory(directory); + } + + // 保存流位置并重置 | Save stream position and reset + long originalPosition = pdfStream.Position; + pdfStream.Position = 0; + + using (var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None)) + { + pdfStream.CopyTo(fileStream); + } + + // 恢复流位置 | Restore stream position + pdfStream.Position = originalPosition; + + _logger.Info("PDF 文件保存成功:{FilePath} | PDF file saved successfully: {FilePath}", filePath); + return ReportResult.Success(pdfStream); + } + catch (UnauthorizedAccessException ex) + { + _logger.Error(ex, "PDF 保存失败:无写入权限 | PDF save failed: no write permission: {Path}", filePath); + return ReportResult.Failure($"无法写入文件,权限不足:{filePath} | Cannot write file, insufficient permissions: {filePath}", ex); + } + catch (DirectoryNotFoundException ex) + { + _logger.Error(ex, "PDF 保存失败:目录不存在 | PDF save failed: directory not found: {Path}", filePath); + return ReportResult.Failure($"目标目录不存在:{filePath} | Target directory not found: {filePath}", ex); + } + catch (IOException ex) + { + _logger.Error(ex, "PDF 保存失败:IO 错误 | PDF save failed: IO error: {Path}", filePath); + return ReportResult.Failure($"文件保存 IO 错误:{ex.Message} | File save IO error: {ex.Message}", ex); + } + catch (Exception ex) + { + _logger.Error(ex, "PDF 保存失败:未知错误 | PDF save failed: unknown error: {Path}", filePath); + return ReportResult.Failure($"文件保存过程中发生错误:{ex.Message} | Error occurred during file save: {ex.Message}", ex); + } + } + + #endregion + + #region 辅助方法 | Helper methods + + /// + /// 解析十六进制颜色字符串为 iText Color 对象 | Parse hex color string to iText Color object + /// 支持格式:#RRGGBB 或 #RGB | Supports formats: #RRGGBB or #RGB + /// + /// 十六进制颜色字符串 | Hex color string + /// iText Color 对象,解析失败返回黑色 | iText Color object, returns black on failure + private Color ParseColor(string hexColor) + { + if (string.IsNullOrEmpty(hexColor)) + { + return ColorConstants.BLACK; + } + + try + { + var hex = hexColor.TrimStart('#'); + + if (hex.Length == 3) + { + // 扩展 #RGB 为 #RRGGBB | Expand #RGB to #RRGGBB + hex = $"{hex[0]}{hex[0]}{hex[1]}{hex[1]}{hex[2]}{hex[2]}"; + } + + if (hex.Length == 6) + { + int r = Convert.ToInt32(hex.Substring(0, 2), 16); + int g = Convert.ToInt32(hex.Substring(2, 2), 16); + int b = Convert.ToInt32(hex.Substring(4, 2), 16); + return new DeviceRgb(r, g, b); + } + } + catch (Exception ex) + { + _logger.Warn("颜色解析失败:{Color},使用默认黑色 | Color parsing failed: {Color}, using default black: {Message}", hexColor, ex.Message); + } + + return ColorConstants.BLACK; + } + + /// + /// 解析对齐方式字符串为 iText TextAlignment | Parse alignment string to iText TextAlignment + /// + /// 对齐方式字符串(left/center/right)| Alignment string + /// iText TextAlignment 枚举值 | iText TextAlignment enum value + private TextAlignment ParseTextAlignment(string align) + { + switch (align?.ToLowerInvariant()) + { + case "center": + return TextAlignment.CENTER; + case "right": + return TextAlignment.RIGHT; + case "justify": + return TextAlignment.JUSTIFIED; + case "left": + default: + return TextAlignment.LEFT; + } + } + + #endregion + } +} diff --git a/XP.ReportEngine/Services/JsonTemplateEngine.cs b/XP.ReportEngine/Services/JsonTemplateEngine.cs new file mode 100644 index 0000000..d7f9036 --- /dev/null +++ b/XP.ReportEngine/Services/JsonTemplateEngine.cs @@ -0,0 +1,165 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Newtonsoft.Json; +using XP.Common.Logging.Interfaces; +using XP.ReportEngine.Interfaces; +using XP.ReportEngine.Models; + +namespace XP.ReportEngine.Services +{ + /// + /// JSON 模板引擎实现 | JSON template engine implementation + /// 负责加载、反序列化和验证 JSON 格式的报告模板 + /// Responsible for loading, deserializing and validating JSON report templates + /// + public class JsonTemplateEngine : ITemplateEngine + { + private readonly ILoggerService _logger; + + /// + /// 默认样式定义(当模板引用未定义的样式名称时使用) + /// Default style definition (used when template references undefined style name) + /// + public static readonly StyleDefinition DefaultStyle = new() + { + Font = null, + Size = 12f, + Bold = false, + Italic = false, + Color = "#000000", + Align = "left", + BackgroundColor = null + }; + + /// + /// 构造函数 | Constructor + /// + /// 日志服务 | Logger service + public JsonTemplateEngine(ILoggerService logger) + { + _logger = logger?.ForModule() ?? throw new ArgumentNullException(nameof(logger)); + } + + /// + /// 加载并反序列化 JSON 模板文件 | Load and deserialize JSON template file + /// + /// 模板文件路径 | Template file path + /// 解析后的模板对象,文件不存在时返回 null | Parsed template object, null if file not found + public ReportTemplate LoadTemplate(string templatePath) + { + if (string.IsNullOrWhiteSpace(templatePath)) + { + _logger.Warn("模板文件路径为空 | Template file path is null or empty"); + return null; + } + + if (!File.Exists(templatePath)) + { + _logger.Warn("模板文件未找到: {Path} | Template file not found: {Path}", templatePath); + return null; + } + + _logger.Info("开始加载模板文件: {Path} | Loading template file: {Path}", templatePath); + + try + { + var json = File.ReadAllText(templatePath); + var template = JsonConvert.DeserializeObject(json); + + _logger.Info("模板文件加载成功 | Template file loaded successfully"); + return template; + } + catch (JsonReaderException ex) + { + // JSON 语法错误,包含错误位置信息 | JSON syntax error with position info + var message = $"模板 JSON 语法错误,行 {ex.LineNumber},位置 {ex.LinePosition}: {ex.Message} | " + + $"Template JSON syntax error at line {ex.LineNumber}, position {ex.LinePosition}: {ex.Message}"; + _logger.Error(ex, message); + throw new InvalidOperationException(message, ex); + } + catch (JsonSerializationException ex) + { + // JSON 反序列化错误 | JSON deserialization error + var message = $"模板 JSON 反序列化失败: {ex.Message} | Template JSON deserialization failed: {ex.Message}"; + _logger.Error(ex, message); + throw new InvalidOperationException(message, ex); + } + } + + /// + /// 验证模板结构完整性 | Validate template structure integrity + /// 检查 document、pages、styles 必需字段是否存在 + /// Checks if required fields (document, pages, styles) are present + /// + /// 待验证的模板 | Template to validate + /// 验证结果 | Validation result + public TemplateValidationResult Validate(ReportTemplate template) + { + if (template == null) + { + return TemplateValidationResult.Invalid("模板对象为 null | Template object is null"); + } + + var missingFields = new List(); + + if (template.Document == null) + { + missingFields.Add("document"); + } + + if (template.Pages == null || template.Pages.Count == 0) + { + missingFields.Add("pages"); + } + + if (template.Styles == null) + { + missingFields.Add("styles"); + } + + if (missingFields.Count > 0) + { + var fieldList = string.Join(", ", missingFields); + var message = $"模板缺少必需字段: {fieldList} | Template missing required fields: {fieldList}"; + _logger.Warn(message); + return TemplateValidationResult.Invalid(message); + } + + _logger.Info("模板验证通过 | Template validation passed"); + return TemplateValidationResult.Valid(); + } + + /// + /// 解析样式名称,未定义时回退为默认样式 | Resolve style name, fallback to default if undefined + /// + /// 报告模板 | Report template + /// 样式名称 | Style name + /// 解析后的样式定义 | Resolved style definition + public StyleDefinition ResolveStyle(ReportTemplate template, string styleName) + { + // 样式名称为空时直接返回默认样式 | Return default style if style name is empty + if (string.IsNullOrWhiteSpace(styleName)) + { + return DefaultStyle; + } + + // 模板或样式字典为空时返回默认样式 | Return default style if template or styles dictionary is null + if (template?.Styles == null) + { + _logger.Warn("模板样式字典为空,使用默认样式: {StyleName} | Template styles dictionary is null, using default style: {StyleName}", styleName); + return DefaultStyle; + } + + // 查找样式,找到则返回,否则回退为默认样式并记录警告 + // Look up style, return if found, otherwise fallback to default and log warning + if (template.Styles.TryGetValue(styleName, out var style)) + { + return style; + } + + _logger.Warn("未定义的样式名称 '{StyleName}',使用默认样式 | Undefined style name '{StyleName}', using default style", styleName); + return DefaultStyle; + } + } +} diff --git a/XP.ReportEngine/Services/PageLayoutEngine.cs b/XP.ReportEngine/Services/PageLayoutEngine.cs new file mode 100644 index 0000000..5bf2d19 --- /dev/null +++ b/XP.ReportEngine/Services/PageLayoutEngine.cs @@ -0,0 +1,522 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using XP.Common.Logging.Interfaces; +using XP.ReportEngine.Interfaces; +using XP.ReportEngine.Models; + +namespace XP.ReportEngine.Services +{ + /// + /// 页面排版引擎实现 | Page layout engine implementation + /// 负责计算页面元素位置、处理分页和自适应布局 + /// Responsible for calculating element positions, handling pagination and adaptive layout + /// + public class PageLayoutEngine : ILayoutEngine + { + private readonly ILoggerService _logger; + private readonly JsonTemplateEngine _templateEngine; + + // A4 页面尺寸(mm)| A4 page dimensions (mm) + private const float A4Width = 210f; + private const float A4Height = 297f; + + // 默认行高估算(mm)| Default row height estimate (mm) + private const float DefaultRowHeight = 8f; + + // 默认文本元素高度(mm)| Default text element height (mm) + private const float DefaultTextHeight = 10f; + + // 默认分隔线高度(mm)| Default divider height (mm) + private const float DefaultDividerHeight = 2f; + + /// + /// 构造函数 | Constructor + /// + /// 日志服务 | Logger service + /// 模板引擎(用于样式解析)| Template engine (for style resolution) + public PageLayoutEngine(ILoggerService logger, JsonTemplateEngine templateEngine) + { + _logger = logger?.ForModule() ?? throw new ArgumentNullException(nameof(logger)); + _templateEngine = templateEngine ?? throw new ArgumentNullException(nameof(templateEngine)); + } + + /// + /// 计算页面布局 | Calculate page layout + /// 遍历模板中所有页面和元素,根据定位方式计算最终坐标,处理分页和表格跨页 + /// Iterates through all pages and elements in template, calculates final coordinates based on positioning mode, + /// handles pagination and table page-splitting + /// + /// 绑定后的模板 | Bound template + /// 生成选项 | Generation options + /// 排版后的页面列表 | List of laid-out pages + public List CalculateLayout(ReportTemplate template, ReportGenerationOptions options) + { + if (template == null) throw new ArgumentNullException(nameof(template)); + + _logger.Info("开始排版计算 | Starting layout calculation"); + + var margins = template.Document?.Margins ?? new MarginSettings(); + var availableWidth = A4Width - margins.Left - margins.Right; + var availableHeight = A4Height - margins.Top - margins.Bottom; + + var pages = new List(); + var currentPageNumber = 1; + + if (template.Pages == null || template.Pages.Count == 0) + { + _logger.Warn("模板无页面定义 | Template has no page definitions"); + return pages; + } + + foreach (var templatePage in template.Pages) + { + if (templatePage.Elements == null || templatePage.Elements.Count == 0) + { + continue; + } + + // 分离绝对定位和流式定位元素 | Separate absolute and flow positioned elements + var absoluteElements = templatePage.Elements + .Where(e => string.Equals(e.Positioning, "absolute", StringComparison.OrdinalIgnoreCase)) + .ToList(); + var flowElements = templatePage.Elements + .Where(e => string.Equals(e.Positioning, "flow", StringComparison.OrdinalIgnoreCase)) + .ToList(); + + // 创建当前页面 | Create current page + var currentPage = new LayoutPage + { + PageNumber = currentPageNumber, + PageType = templatePage.Type, + Elements = new List() + }; + pages.Add(currentPage); + + // 处理绝对定位元素(不参与分页)| Process absolute positioned elements (no pagination) + foreach (var element in absoluteElements) + { + var layoutElement = ProcessAbsoluteElement(element, template, margins); + currentPage.Elements.Add(layoutElement); + } + + // 处理流式定位元素(参与分页)| Process flow positioned elements (with pagination) + var currentY = margins.Top; + + foreach (var element in flowElements) + { + var elementHeight = CalculateElementHeight(element); + var elementWidth = CalculateElementWidth(element, availableWidth); + + // 强制分页元素 | Forced page break element + if (string.Equals(element.Type, "pagebreak", StringComparison.OrdinalIgnoreCase)) + { + currentPageNumber++; + currentPage = new LayoutPage + { + PageNumber = currentPageNumber, + PageType = templatePage.Type, + Elements = new List() + }; + pages.Add(currentPage); + currentY = margins.Top; + continue; + } + + // 检查是否需要分页 | Check if pagination is needed + if (element.Type == "table" && element.Columns != null) + { + // 表格跨页拆分逻辑 | Table page-split logic + currentY = ProcessTableWithPageSplit( + element, template, margins, availableHeight, availableWidth, + currentY, pages, ref currentPage, ref currentPageNumber, templatePage.Type); + } + else + { + // 普通元素分页检查 | Normal element pagination check + if (currentY + elementHeight > margins.Top + availableHeight) + { + // 创建新页面 | Create new page + currentPageNumber++; + currentPage = new LayoutPage + { + PageNumber = currentPageNumber, + PageType = templatePage.Type, + Elements = new List() + }; + pages.Add(currentPage); + currentY = margins.Top; + } + + var layoutElement = CreateFlowLayoutElement( + element, template, margins, currentY, elementWidth, elementHeight); + currentPage.Elements.Add(layoutElement); + + // 累计 Y 坐标 | Accumulate Y coordinate + currentY += elementHeight; + } + } + + currentPageNumber++; + } + + _logger.Info("排版计算完成,共 {PageCount} 页 | Layout calculation completed, {PageCount} pages total", pages.Count); + return pages; + } + + /// + /// 处理绝对定位元素 | Process absolute positioned element + /// 元素坐标 = Position + Margins 偏移 + /// Element coordinates = Position + Margins offset + /// + private LayoutElement ProcessAbsoluteElement(TemplateElement element, ReportTemplate template, MarginSettings margins) + { + var x = margins.Left + (element.Position != null && element.Position.Length > 0 ? element.Position[0] : 0f); + var y = margins.Top + (element.Position != null && element.Position.Length > 1 ? element.Position[1] : 0f); + var width = element.Size != null && element.Size.Length > 0 ? element.Size[0] : 0f; + var height = element.Size != null && element.Size.Length > 1 ? element.Size[1] : 0f; + + // 图像等比缩放 | Image proportional scaling + if (element.Type == "image" && width > 0 && height > 0) + { + var scaled = CalculateScaledImageDimensions(width, height, width, height); + width = scaled.Width; + height = scaled.Height; + } + + var resolvedStyle = _templateEngine.ResolveStyle(template, element.Style); + + return new LayoutElement + { + Source = element, + X = x, + Y = y, + Width = width, + Height = height, + ResolvedStyle = resolvedStyle, + ResolvedContent = element.Content, + ResolvedTableData = element.TableData, + ResolvedImage = element.ImageData + }; + } + + /// + /// 创建流式定位的布局元素 | Create flow positioned layout element + /// + private LayoutElement CreateFlowLayoutElement( + TemplateElement element, ReportTemplate template, MarginSettings margins, + float currentY, float width, float height) + { + var x = margins.Left; + + // 如果元素有 Position 定义,使用 X 偏移 | If element has Position defined, use X offset + if (element.Position != null && element.Position.Length > 0) + { + x = margins.Left + element.Position[0]; + } + + // 图像等比缩放 | Image proportional scaling + if (element.Type == "image") + { + var targetWidth = width; + var targetHeight = height; + var imageWidth = element.Size != null && element.Size.Length > 0 ? element.Size[0] : width; + var imageHeight = element.Size != null && element.Size.Length > 1 ? element.Size[1] : height; + + if (imageWidth > 0 && imageHeight > 0 && targetWidth > 0 && targetHeight > 0) + { + var scaled = CalculateScaledImageDimensions(imageWidth, imageHeight, targetWidth, targetHeight); + width = scaled.Width; + height = scaled.Height; + } + } + + var resolvedStyle = _templateEngine.ResolveStyle(template, element.Style); + + return new LayoutElement + { + Source = element, + X = x, + Y = currentY, + Width = width, + Height = height, + ResolvedStyle = resolvedStyle, + ResolvedContent = element.Content, + ResolvedTableData = element.TableData, + ResolvedImage = element.ImageData + }; + } + + /// + /// 处理表格跨页拆分 | Process table with page-split + /// 按行高计算剩余空间,超出时拆分到新页面,续页重复表头行 + /// Calculate remaining space by row height, split to new page when exceeded, repeat header on continuation pages + /// + /// 处理后的当前 Y 坐标 | Current Y coordinate after processing + private float ProcessTableWithPageSplit( + TemplateElement element, ReportTemplate template, MarginSettings margins, + float availableHeight, float availableWidth, + float currentY, List pages, + ref LayoutPage currentPage, ref int currentPageNumber, string pageType) + { + var resolvedStyle = _templateEngine.ResolveStyle(template, element.Style); + var tableWidth = element.Size != null && element.Size.Length > 0 ? element.Size[0] : availableWidth; + + // 计算表头高度(1 行)| Calculate header height (1 row) + var headerHeight = DefaultRowHeight; + + // 获取表格数据行数 | Get table data row count + var tableData = GetTableDataFromElement(element); + var totalDataRows = tableData?.Count ?? 0; + + if (totalDataRows == 0) + { + // 空表格,仅渲染表头 | Empty table, render header only + var emptyTableHeight = headerHeight; + if (currentY + emptyTableHeight > margins.Top + availableHeight) + { + currentPageNumber++; + currentPage = new LayoutPage + { + PageNumber = currentPageNumber, + PageType = pageType, + Elements = new List() + }; + pages.Add(currentPage); + currentY = margins.Top; + } + + var emptyTableElement = new LayoutElement + { + Source = element, + X = margins.Left, + Y = currentY, + Width = tableWidth, + Height = emptyTableHeight, + ResolvedStyle = resolvedStyle, + ResolvedContent = element.Content, + ResolvedTableData = tableData + }; + currentPage.Elements.Add(emptyTableElement); + currentY += emptyTableHeight; + return currentY; + } + + // 计算当前页面剩余空间 | Calculate remaining space on current page + var remainingHeight = (margins.Top + availableHeight) - currentY; + var totalTableHeight = headerHeight + (totalDataRows * DefaultRowHeight); + + // 如果整个表格能放下,直接放置 | If entire table fits, place directly + if (totalTableHeight <= remainingHeight) + { + var tableElement = new LayoutElement + { + Source = element, + X = margins.Left, + Y = currentY, + Width = tableWidth, + Height = totalTableHeight, + ResolvedStyle = resolvedStyle, + ResolvedContent = element.Content, + ResolvedTableData = tableData + }; + currentPage.Elements.Add(tableElement); + currentY += totalTableHeight; + return currentY; + } + + // 需要跨页拆分 | Need to split across pages + var currentRowIndex = 0; + + while (currentRowIndex < totalDataRows) + { + // 计算当前页面可容纳的数据行数(需预留表头空间)| Calculate rows that fit on current page (reserve header space) + var currentRemainingHeight = (margins.Top + availableHeight) - currentY; + var rowsOnCurrentPage = (int)Math.Floor((currentRemainingHeight - headerHeight) / DefaultRowHeight); + + if (rowsOnCurrentPage <= 0) + { + // 当前页面空间不足以放置表头+至少一行数据,创建新页面 + // Current page doesn't have space for header + at least one data row, create new page + currentPageNumber++; + currentPage = new LayoutPage + { + PageNumber = currentPageNumber, + PageType = pageType, + Elements = new List() + }; + pages.Add(currentPage); + currentY = margins.Top; + currentRemainingHeight = availableHeight; + rowsOnCurrentPage = (int)Math.Floor((currentRemainingHeight - headerHeight) / DefaultRowHeight); + } + + // 确定本页实际放置的行数 | Determine actual rows to place on this page + var rowsToPlace = Math.Min(rowsOnCurrentPage, totalDataRows - currentRowIndex); + var splitData = tableData.Skip(currentRowIndex).Take(rowsToPlace).ToList(); + var splitHeight = headerHeight + (rowsToPlace * DefaultRowHeight); + + var splitElement = new LayoutElement + { + Source = element, + X = margins.Left, + Y = currentY, + Width = tableWidth, + Height = splitHeight, + ResolvedStyle = resolvedStyle, + ResolvedContent = element.Content, + ResolvedTableData = splitData + }; + currentPage.Elements.Add(splitElement); + currentY += splitHeight; + currentRowIndex += rowsToPlace; + + // 如果还有剩余行,创建新页面继续 | If there are remaining rows, create new page to continue + if (currentRowIndex < totalDataRows) + { + currentPageNumber++; + currentPage = new LayoutPage + { + PageNumber = currentPageNumber, + PageType = pageType, + Elements = new List() + }; + pages.Add(currentPage); + currentY = margins.Top; + } + } + + return currentY; + } + + /// + /// 计算图像等比缩放尺寸 | Calculate proportionally scaled image dimensions + /// 保持宽高比,确保缩放后的宽度和高度均不超过目标区域 + /// Maintain aspect ratio, ensure scaled width and height don't exceed target area + /// + /// 原始图像宽度 | Original image width + /// 原始图像高度 | Original image height + /// 目标区域宽度 | Target area width + /// 目标区域高度 | Target area height + /// 缩放后的尺寸 | Scaled dimensions + public (float Width, float Height) CalculateScaledImageDimensions( + float imageWidth, float imageHeight, float targetWidth, float targetHeight) + { + if (imageWidth <= 0 || imageHeight <= 0 || targetWidth <= 0 || targetHeight <= 0) + { + return (0f, 0f); + } + + // 如果图像已经在目标区域内,无需缩放 | If image already fits, no scaling needed + if (imageWidth <= targetWidth && imageHeight <= targetHeight) + { + return (imageWidth, imageHeight); + } + + // 计算宽度和高度的缩放比例,取较小值以确保两个维度都不超出 + // Calculate scale ratios for width and height, use the smaller one to ensure both dimensions fit + var widthRatio = targetWidth / imageWidth; + var heightRatio = targetHeight / imageHeight; + var scale = Math.Min(widthRatio, heightRatio); + + var scaledWidth = imageWidth * scale; + var scaledHeight = imageHeight * scale; + + return (scaledWidth, scaledHeight); + } + + /// + /// 计算元素高度 | Calculate element height + /// 根据元素类型和 Size 定义确定高度 + /// Determine height based on element type and Size definition + /// + private float CalculateElementHeight(TemplateElement element) + { + // 如果有明确的 Size 定义,使用 Size[1] 作为高度 | If Size is defined, use Size[1] as height + if (element.Size != null && element.Size.Length > 1 && element.Size[1] > 0) + { + return element.Size[1]; + } + + // 根据元素类型使用默认高度 | Use default height based on element type + return element.Type?.ToLowerInvariant() switch + { + "text" => DefaultTextHeight, + "divider" => DefaultDividerHeight, + "spacer" => element.Size is { Length: >= 2 } ? element.Size[1] : DefaultTextHeight, + "row" => CalculateRowHeight(element), + "pagebreak" => 0f, + "image" => DefaultTextHeight, + "table" => CalculateTableHeight(element), + _ => DefaultTextHeight + }; + } + + /// + /// 计算表格高度 | Calculate table height + /// 表头行 + 数据行数 × 默认行高 + /// Header row + data row count × default row height + /// + private float CalculateTableHeight(TemplateElement element) + { + var tableData = GetTableDataFromElement(element); + var dataRowCount = tableData?.Count ?? 0; + // 表头 1 行 + 数据行 | 1 header row + data rows + return DefaultRowHeight + (dataRowCount * DefaultRowHeight); + } + + /// + /// 计算 Row 容器高度 | Calculate row container height + /// 取子元素中最大高度,如果有 Size 定义则优先使用 + /// Uses max child height, or Size definition if available + /// + private float CalculateRowHeight(TemplateElement element) + { + // 如果 row 本身有 Size[1] 定义,直接使用 | If row has Size[1], use it directly + if (element.Size != null && element.Size.Length > 1 && element.Size[1] > 0) + { + return element.Size[1]; + } + + // 否则取子元素中最大高度 | Otherwise use max child height + if (element.Children == null || element.Children.Count == 0) + return DefaultTextHeight; + + float maxHeight = 0; + foreach (var child in element.Children) + { + float childHeight = DefaultTextHeight; + if (child.Size != null && child.Size.Length > 1 && child.Size[1] > 0) + { + childHeight = child.Size[1]; + } + if (childHeight > maxHeight) maxHeight = childHeight; + } + return maxHeight > 0 ? maxHeight : DefaultTextHeight; + } + + /// + /// 计算元素宽度 | Calculate element width + /// + private float CalculateElementWidth(TemplateElement element, float availableWidth) + { + if (element.Size != null && element.Size.Length > 0 && element.Size[0] > 0) + { + return element.Size[0]; + } + return availableWidth; + } + + /// + /// 从元素获取表格数据 | Get table data from element + /// 表格数据在数据绑定阶段通过 TableData 属性填充 + /// Table data is populated during data binding phase via TableData property + /// + private List> GetTableDataFromElement(TemplateElement element) + { + // 表格数据在数据绑定阶段已填充到 TemplateElement.TableData + // Table data is populated during data binding phase into TemplateElement.TableData + return element.TableData; + } + } +} diff --git a/XP.ReportEngine/Services/PdfReportGenerator.cs b/XP.ReportEngine/Services/PdfReportGenerator.cs new file mode 100644 index 0000000..81c31ce --- /dev/null +++ b/XP.ReportEngine/Services/PdfReportGenerator.cs @@ -0,0 +1,134 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using XP.Common.Logging.Interfaces; +using XP.ReportEngine.Interfaces; +using XP.ReportEngine.Models; + +namespace XP.ReportEngine.Services +{ + /// + /// PDF 报告生成器实现 | PDF report generator implementation + /// 协调管线各阶段:模板加载 → 数据绑定 → 排版 → 渲染 → 保存 + /// Orchestrates pipeline phases: template loading → data binding → layout → rendering → saving + /// + public class PdfReportGenerator : IReportGenerator + { + private readonly ILoggerService _logger; + private readonly ITemplateEngine _templateEngine; + private readonly IDataBinder _dataBinder; + private readonly ILayoutEngine _layoutEngine; + private readonly IPdfRenderer _pdfRenderer; + + /// + /// 构造函数 | Constructor + /// + /// 日志服务 | Logger service + /// 模板引擎 | Template engine + /// 数据绑定器 | Data binder + /// 排版引擎 | Layout engine + /// PDF 渲染器 | PDF renderer + public PdfReportGenerator( + ILoggerService logger, + ITemplateEngine templateEngine, + IDataBinder dataBinder, + ILayoutEngine layoutEngine, + IPdfRenderer pdfRenderer) + { + _logger = logger?.ForModule() ?? throw new ArgumentNullException(nameof(logger)); + _templateEngine = templateEngine ?? throw new ArgumentNullException(nameof(templateEngine)); + _dataBinder = dataBinder ?? throw new ArgumentNullException(nameof(dataBinder)); + _layoutEngine = layoutEngine ?? throw new ArgumentNullException(nameof(layoutEngine)); + _pdfRenderer = pdfRenderer ?? throw new ArgumentNullException(nameof(pdfRenderer)); + } + + /// + /// 异步生成 PDF 报告 | Generate PDF report asynchronously + /// 执行完整管线:模板加载 → 验证 → 数据绑定 → 排版计算 → PDF 渲染 → 文件保存(可选) + /// Executes full pipeline: template load → validate → data bind → layout → PDF render → save (optional) + /// + /// 报告上下文数据 | Report context data + /// 生成选项 | Generation options + /// 生成结果 | Generation result + public async Task GenerateAsync(ReportContext context, ReportGenerationOptions options) + { + try + { + _logger.Info("报告生成管线开始 | Report generation pipeline started"); + + // 阶段 1:加载模板 | Phase 1: Load template + _logger.Info("阶段 1:加载模板 | Phase 1: Loading template"); + var template = _templateEngine.LoadTemplate(options.TemplatePath); + if (template == null) + { + var errorMsg = $"模板文件未找到: {options.TemplatePath}"; + _logger.Error(null, "模板加载失败: {Path} | Template loading failed: {Path}", options.TemplatePath); + return ReportResult.Failure(errorMsg); + } + + var validation = _templateEngine.Validate(template); + if (!validation.IsValid) + { + var errorMsg = $"模板验证失败: {validation.ErrorMessage}"; + _logger.Error(null, "模板验证失败: {Message} | Template validation failed: {Message}", validation.ErrorMessage); + return ReportResult.Failure(errorMsg); + } + _logger.Info("阶段 1 完成:模板加载成功 | Phase 1 completed: Template loaded successfully"); + + // 阶段 2:数据绑定 | Phase 2: Data binding + _logger.Info("阶段 2:数据绑定 | Phase 2: Data binding"); + var boundTemplate = _dataBinder.Bind(template, context); + _logger.Info("阶段 2 完成:数据绑定成功 | Phase 2 completed: Data binding successful"); + + // 阶段 3:排版计算 | Phase 3: Layout calculation + _logger.Info("阶段 3:排版计算 | Phase 3: Layout calculation"); + var pages = _layoutEngine.CalculateLayout(boundTemplate, options); + _logger.Info("阶段 3 完成:排版计算成功,共 {PageCount} 页 | Phase 3 completed: Layout calculated, {PageCount} pages", pages.Count); + + // 阶段 4:PDF 渲染 | Phase 4: PDF rendering + _logger.Info("阶段 4:PDF 渲染 | Phase 4: PDF rendering"); + var stream = _pdfRenderer.Render(pages, options, boundTemplate); + _logger.Info("阶段 4 完成:PDF 渲染成功 | Phase 4 completed: PDF rendering successful"); + + // 阶段 5:保存文件(可选)| Phase 5: Save file (optional) + if (!string.IsNullOrEmpty(options.OutputFilePath)) + { + _logger.Info("阶段 5:保存文件 | Phase 5: Saving file"); + await SaveToFileAsync(stream, options.OutputFilePath); + _logger.Info("阶段 5 完成:文件保存成功 {Path} | Phase 5 completed: File saved successfully {Path}", options.OutputFilePath); + } + + _logger.Info("报告生成管线完成 | Report generation pipeline completed"); + return ReportResult.Success(stream); + } + catch (Exception ex) + { + _logger.Error(ex, "报告生成失败 | Report generation failed: {Message}", ex.Message); + return ReportResult.Failure($"报告生成过程中发生错误: {ex.Message}", ex); + } + } + + /// + /// 将 MemoryStream 保存到文件 | Save MemoryStream to file + /// + /// PDF 内存流 | PDF memory stream + /// 输出文件路径 | Output file path + private async Task SaveToFileAsync(MemoryStream stream, string filePath) + { + // 确保输出目录存在 | Ensure output directory exists + var directory = Path.GetDirectoryName(filePath); + if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) + { + Directory.CreateDirectory(directory); + } + + // 重置流位置后写入文件 | Reset stream position before writing to file + stream.Position = 0; + using var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None); + await stream.CopyToAsync(fileStream); + + // 重置流位置以便后续使用 | Reset stream position for subsequent use + stream.Position = 0; + } + } +} diff --git a/XP.ReportEngine/Services/ProcessorDataAdapter.cs b/XP.ReportEngine/Services/ProcessorDataAdapter.cs new file mode 100644 index 0000000..5e794e5 --- /dev/null +++ b/XP.ReportEngine/Services/ProcessorDataAdapter.cs @@ -0,0 +1,533 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using XP.Common.Logging.Interfaces; +using XP.ReportEngine.Interfaces; +using XP.ReportEngine.Models; + +namespace XP.ReportEngine.Services +{ + /// + /// 处理器数据适配器实现 | Processor data adapter implementation + /// 将 XP.ImageProcessing 的 ProcessorOutput 转换为 ReportContext + /// Converts XP.ImageProcessing ProcessorOutput to ReportContext + /// + public class ProcessorDataAdapter : IReportDataAdapter + { + private readonly ILoggerService _logger; + + // 处理器类型常量 | Processor type constants + private const string LineMeasurementProcessor = "LineMeasurementProcessor"; + private const string BgaVoidRateProcessor = "BgaVoidRateProcessor"; + private const string VoidMeasurementProcessor = "VoidMeasurementProcessor"; + private const string FillRateProcessor = "FillRateProcessor"; + + public ProcessorDataAdapter(ILoggerService logger) + { + _logger = logger?.ForModule() ?? throw new ArgumentNullException(nameof(logger)); + } + + /// + /// 将处理器输出数据适配为报告上下文 | Adapt processor output data to report context + /// + public ReportContext Adapt(List processorOutputs, ReportMetadata metadata) + { + if (processorOutputs == null) throw new ArgumentNullException(nameof(processorOutputs)); + if (metadata == null) throw new ArgumentNullException(nameof(metadata)); + + _logger.Info("开始数据适配,处理器数量: {Count} | Starting data adaptation, processor count: {Count}", processorOutputs.Count); + + var context = new ReportContext + { + Metadata = metadata, + ResultGroups = new List(), + Images = new Dictionary(), + Properties = new Dictionary() + }; + + // 多处理器输出聚合逻辑:每个 ProcessorOutput 生成一个 InspectionResultGroup | Aggregation: each ProcessorOutput becomes one InspectionResultGroup + for (var i = 0; i < processorOutputs.Count; i++) + { + var output = processorOutputs[i]; + if (output == null) + { + _logger.Warn("处理器输出为 null,索引: {Index},已跳过 | Processor output is null at index: {Index}, skipped", i); + continue; + } + + var group = AdaptProcessorOutput(output, i); + context.ResultGroups.Add(group); + + // 将结果数据扁平化到 Properties(供模板 ${key} 表达式绑定) + // Flatten result data to Properties (for template ${key} expression binding) + if (group.Data != null) + { + foreach (var kvp in group.Data) + { + context.Properties[kvp.Key] = kvp.Value; + } + } + + // 将 Classification 也放入 Properties | Also put Classification into Properties + if (!string.IsNullOrEmpty(group.Classification)) + { + context.Properties["classification"] = group.Classification; + } + + // 将表格数据以模板期望的 dataKey 存入 Properties + // Store table data with template-expected dataKey into Properties + if (group.TableRows != null && group.TableRows.Count > 0) + { + var tableKey = GetTableDataKey(output.ProcessorType); + if (!string.IsNullOrEmpty(tableKey)) + { + context.Properties[tableKey] = group.TableRows; + } + } + + // 关联标注图像(使用模板期望的 dataKey) + // Associate annotated image (using template-expected dataKey) + if (output.AnnotatedImage != null) + { + var imageKey = GetImageDataKey(output.ProcessorType); + context.Images[imageKey] = output.AnnotatedImage; + + // 同时保留原始键名以兼容其他调用方 | Also keep original key for other callers + var originalKey = $"{output.ProcessorType}_{i}_annotated"; + context.Images[originalKey] = output.AnnotatedImage; + } + } + + _logger.Info("数据适配完成,生成 {Count} 个结果分组 | Data adaptation completed, generated {Count} result groups", context.ResultGroups.Count); + return context; + } + + /// + /// 根据处理器类型分发适配逻辑 | Dispatch adaptation logic by processor type + /// + private InspectionResultGroup AdaptProcessorOutput(ProcessorOutput output, int index) + { + var sourceId = $"{output.ProcessorType}_{index}"; + + return output.ProcessorType switch + { + LineMeasurementProcessor => AdaptLineMeasurement(output, sourceId), + BgaVoidRateProcessor => AdaptBgaVoidRate(output, sourceId), + VoidMeasurementProcessor => AdaptVoidMeasurement(output, sourceId), + FillRateProcessor => AdaptFillRate(output, sourceId), + _ => AdaptGeneric(output, sourceId) + }; + } + + #region LineMeasurementProcessor 适配 | LineMeasurementProcessor Adaptation + + /// + /// 适配线测量处理器输出 | Adapt line measurement processor output + /// 提取 MeasurementType、Point1、Point2、PixelDistance、ActualDistance、Unit、Angle + /// + private InspectionResultGroup AdaptLineMeasurement(ProcessorOutput output, string sourceId) + { + _logger.Debug("适配 LineMeasurementProcessor 输出,SourceId: {SourceId} | Adapting LineMeasurementProcessor output, SourceId: {SourceId}", sourceId); + + var data = output.OutputData ?? new Dictionary(); + var group = new InspectionResultGroup + { + ProcessorType = LineMeasurementProcessor, + SourceId = sourceId, + Classification = string.Empty, + Data = new Dictionary + { + ["measurementType"] = GetValueOrDefault(data, "MeasurementType", string.Empty), + ["point1"] = GetValueOrDefault(data, "Point1", null), + ["point2"] = GetValueOrDefault(data, "Point2", null), + ["pixelDistance"] = GetValueOrDefault(data, "PixelDistance", 0.0), + ["actualDistance"] = GetValueOrDefault(data, "ActualDistance", 0.0), + ["unit"] = GetValueOrDefault(data, "Unit", string.Empty), + ["angle"] = GetValueOrDefault(data, "Angle", 0.0) + }, + TableRows = new List>() + }; + + return group; + } + + #endregion + + #region BgaVoidRateProcessor 适配 | BgaVoidRateProcessor Adaptation + + /// + /// 适配 BGA 气泡率处理器输出 | Adapt BGA void rate processor output + /// 提取 BgaCount、BgaBalls 列表转 TableRows、VoidRate、FillRate、TotalBgaArea、TotalVoidArea、Classification、VoidLimit + /// + private InspectionResultGroup AdaptBgaVoidRate(ProcessorOutput output, string sourceId) + { + _logger.Debug("适配 BgaVoidRateProcessor 输出,SourceId: {SourceId} | Adapting BgaVoidRateProcessor output, SourceId: {SourceId}", sourceId); + + var data = output.OutputData ?? new Dictionary(); + var group = new InspectionResultGroup + { + ProcessorType = BgaVoidRateProcessor, + SourceId = sourceId, + Classification = GetValueOrDefault(data, "Classification", string.Empty), + Data = new Dictionary + { + ["bgaCount"] = GetValueOrDefault(data, "BgaCount", 0), + ["voidRate"] = GetValueOrDefault(data, "VoidRate", 0.0), + ["fillRate"] = GetValueOrDefault(data, "FillRate", 0.0), + ["totalBgaArea"] = GetValueOrDefault(data, "TotalBgaArea", 0.0), + ["totalVoidArea"] = GetValueOrDefault(data, "TotalVoidArea", 0.0), + ["voidLimit"] = GetValueOrDefault(data, "VoidLimit", 0.0) + }, + TableRows = ConvertBgaBallsToTableRows(data) + }; + + return group; + } + + /// + /// 将 BgaBalls 列表转换为表格行 | Convert BgaBalls list to table rows + /// 每个焊球一行,包含 index、voidRate、area、classification + /// + private List> ConvertBgaBallsToTableRows(Dictionary data) + { + var tableRows = new List>(); + var bgaBalls = GetListValue(data, "BgaBalls"); + + if (bgaBalls == null || bgaBalls.Count == 0) + { + return tableRows; + } + + for (var i = 0; i < bgaBalls.Count; i++) + { + var ball = bgaBalls[i]; + var row = new Dictionary + { + ["index"] = i + 1, + ["voidRate"] = GetNestedValue(ball, "VoidRate", 0.0), + ["area"] = GetNestedValue(ball, "Area", 0.0), + ["classification"] = GetNestedValue(ball, "Classification", string.Empty) + }; + tableRows.Add(row); + } + + return tableRows; + } + + #endregion + + #region VoidMeasurementProcessor 适配 | VoidMeasurementProcessor Adaptation + + /// + /// 适配空隙测量处理器输出 | Adapt void measurement processor output + /// 提取 RoiArea、TotalVoidArea、VoidRate、VoidLimit、VoidCount、MaxVoidArea、Classification、Voids 列表转 TableRows + /// + private InspectionResultGroup AdaptVoidMeasurement(ProcessorOutput output, string sourceId) + { + _logger.Debug("适配 VoidMeasurementProcessor 输出,SourceId: {SourceId} | Adapting VoidMeasurementProcessor output, SourceId: {SourceId}", sourceId); + + var data = output.OutputData ?? new Dictionary(); + var group = new InspectionResultGroup + { + ProcessorType = VoidMeasurementProcessor, + SourceId = sourceId, + Classification = GetValueOrDefault(data, "Classification", string.Empty), + Data = new Dictionary + { + ["roiArea"] = GetValueOrDefault(data, "RoiArea", 0.0), + ["totalVoidArea"] = GetValueOrDefault(data, "TotalVoidArea", 0.0), + ["voidRate"] = GetValueOrDefault(data, "VoidRate", 0.0), + ["voidLimit"] = GetValueOrDefault(data, "VoidLimit", 0.0), + ["voidCount"] = GetValueOrDefault(data, "VoidCount", 0), + ["maxVoidArea"] = GetValueOrDefault(data, "MaxVoidArea", 0.0) + }, + TableRows = ConvertVoidsToTableRows(data) + }; + + return group; + } + + /// + /// 将 Voids 列表转换为表格行 | Convert Voids list to table rows + /// 每个空隙一行,包含 index、area、areaPercent、centerX、centerY + /// + private List> ConvertVoidsToTableRows(Dictionary data) + { + var tableRows = new List>(); + var voids = GetListValue(data, "Voids"); + + if (voids == null || voids.Count == 0) + { + return tableRows; + } + + for (var i = 0; i < voids.Count; i++) + { + var voidItem = voids[i]; + var row = new Dictionary + { + ["index"] = i + 1, + ["area"] = GetNestedValue(voidItem, "Area", 0.0), + ["areaPercent"] = GetNestedValue(voidItem, "AreaPercent", 0.0), + ["centerX"] = GetNestedValue(voidItem, "CenterX", 0.0), + ["centerY"] = GetNestedValue(voidItem, "CenterY", 0.0) + }; + tableRows.Add(row); + } + + return tableRows; + } + + #endregion + + #region FillRateProcessor 适配 | FillRateProcessor Adaptation + + /// + /// 适配填锡率处理器输出 | Adapt fill rate processor output + /// 提取 FillRate、VoidRate、FullDistance、FillDistance、THTLimit、Classification、E1-E4 椭圆几何数据 + /// + private InspectionResultGroup AdaptFillRate(ProcessorOutput output, string sourceId) + { + _logger.Debug("适配 FillRateProcessor 输出,SourceId: {SourceId} | Adapting FillRateProcessor output, SourceId: {SourceId}", sourceId); + + var data = output.OutputData ?? new Dictionary(); + var group = new InspectionResultGroup + { + ProcessorType = FillRateProcessor, + SourceId = sourceId, + Classification = GetValueOrDefault(data, "Classification", string.Empty), + Data = new Dictionary + { + ["fillRate"] = GetValueOrDefault(data, "FillRate", 0.0), + ["voidRate"] = GetValueOrDefault(data, "VoidRate", 0.0), + ["fullDistance"] = GetValueOrDefault(data, "FullDistance", 0.0), + ["fillDistance"] = GetValueOrDefault(data, "FillDistance", 0.0), + ["thtLimit"] = GetValueOrDefault(data, "THTLimit", 0.0), + ["e1"] = GetValueOrDefault(data, "E1", null), + ["e2"] = GetValueOrDefault(data, "E2", null), + ["e3"] = GetValueOrDefault(data, "E3", null), + ["e4"] = GetValueOrDefault(data, "E4", null) + }, + TableRows = new List>() + }; + + return group; + } + + #endregion + + #region 通用适配 | Generic Adaptation + + /// + /// 通用处理器适配(未知类型)| Generic processor adaptation (unknown type) + /// 将所有 OutputData 键值对直接映射到 Data 字典 + /// + private InspectionResultGroup AdaptGeneric(ProcessorOutput output, string sourceId) + { + _logger.Warn("未知处理器类型: {ProcessorType},使用通用适配 | Unknown processor type: {ProcessorType}, using generic adaptation", output.ProcessorType); + + var data = output.OutputData ?? new Dictionary(); + var group = new InspectionResultGroup + { + ProcessorType = output.ProcessorType ?? "Unknown", + SourceId = sourceId, + Classification = GetValueOrDefault(data, "Classification", string.Empty), + Data = new Dictionary(), + TableRows = new List>() + }; + + // 将所有键值对转为小驼峰命名映射 | Map all key-value pairs with camelCase naming + foreach (var kvp in data) + { + var camelKey = ToCamelCase(kvp.Key); + group.Data[camelKey] = kvp.Value; + } + + return group; + } + + #endregion + + #region 辅助方法 | Helper Methods + + /// + /// 从字典中获取值,缺失时使用默认值并记录警告 | Get value from dictionary, use default and log warning if missing + /// + private T GetValueOrDefault(Dictionary data, string key, T defaultValue) + { + if (data == null || !data.TryGetValue(key, out var value) || value == null) + { + _logger.Warn("处理器输出缺少键: {Key},使用默认值: {Default} | Processor output missing key: {Key}, using default: {Default}", key, defaultValue); + return defaultValue; + } + + try + { + return ConvertValue(value); + } + catch (Exception ex) + { + _logger.Warn("键 {Key} 的值类型转换失败: {Message},使用默认值 | Value type conversion failed for key {Key}: {Message}, using default", key, ex.Message); + return defaultValue; + } + } + + /// + /// 从字典中获取列表值 | Get list value from dictionary + /// + private IList GetListValue(Dictionary data, string key) + { + if (data == null || !data.TryGetValue(key, out var value) || value == null) + { + _logger.Warn("处理器输出缺少列表键: {Key},返回空列表 | Processor output missing list key: {Key}, returning empty list", key); + return null; + } + + if (value is IList list) + { + return list; + } + + _logger.Warn("键 {Key} 的值不是列表类型 | Value for key {Key} is not a list type", key); + return null; + } + + /// + /// 从嵌套对象中获取属性值 | Get property value from nested object + /// + private T GetNestedValue(object obj, string propertyName, T defaultValue) + { + if (obj == null) return defaultValue; + + // 字典访问 | Dictionary access + if (obj is IDictionary dict) + { + if (dict.TryGetValue(propertyName, out var dictValue) && dictValue != null) + { + try + { + return ConvertValue(dictValue); + } + catch + { + return defaultValue; + } + } + return defaultValue; + } + + // 反射访问 | Reflection access + var type = obj.GetType(); + var propInfo = type.GetProperty(propertyName, + System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.IgnoreCase); + if (propInfo != null) + { + try + { + var value = propInfo.GetValue(obj); + if (value == null) return defaultValue; + return ConvertValue(value); + } + catch + { + return defaultValue; + } + } + + return defaultValue; + } + + /// + /// 类型转换辅助方法 | Type conversion helper + /// + private T ConvertValue(object value) + { + if (value == null) return default; + + var targetType = typeof(T); + + // 直接类型匹配 | Direct type match + if (value is T typedValue) + { + return typedValue; + } + + // 处理 nullable 类型 | Handle nullable types + var underlyingType = Nullable.GetUnderlyingType(targetType) ?? targetType; + + // object 类型直接返回 | Return directly for object type + if (underlyingType == typeof(object)) + { + return (T)value; + } + + // 数值类型转换 | Numeric type conversion + if (underlyingType == typeof(double)) + { + return (T)(object)Convert.ToDouble(value); + } + if (underlyingType == typeof(int)) + { + return (T)(object)Convert.ToInt32(value); + } + if (underlyingType == typeof(float)) + { + return (T)(object)Convert.ToSingle(value); + } + if (underlyingType == typeof(long)) + { + return (T)(object)Convert.ToInt64(value); + } + + // 字符串转换 | String conversion + if (underlyingType == typeof(string)) + { + return (T)(object)value.ToString(); + } + + // 通用转换 | General conversion + return (T)Convert.ChangeType(value, underlyingType); + } + + /// + /// 将 PascalCase 转换为 camelCase | Convert PascalCase to camelCase + /// + private string ToCamelCase(string input) + { + if (string.IsNullOrEmpty(input)) return input; + if (input.Length == 1) return input.ToLowerInvariant(); + return char.ToLowerInvariant(input[0]) + input.Substring(1); + } + + /// + /// 根据处理器类型获取模板中对应的图像 dataKey | Get template image dataKey by processor type + /// + private static string GetImageDataKey(string processorType) + { + return processorType switch + { + LineMeasurementProcessor => "lineMeasurementImage", + BgaVoidRateProcessor => "bgaInspectionImage", + VoidMeasurementProcessor => "voidInspectionImage", + FillRateProcessor => "viaFillImage", + _ => $"{processorType}_image" + }; + } + + /// + /// 根据处理器类型获取模板中对应的表格 dataKey | Get template table dataKey by processor type + /// + private static string GetTableDataKey(string processorType) + { + return processorType switch + { + BgaVoidRateProcessor => "bgaBallsTable", + VoidMeasurementProcessor => "voidsTable", + _ => null + }; + } + + #endregion + } +} diff --git a/XP.ReportEngine/Services/ReportGeneratorFactory.cs b/XP.ReportEngine/Services/ReportGeneratorFactory.cs new file mode 100644 index 0000000..c27623f --- /dev/null +++ b/XP.ReportEngine/Services/ReportGeneratorFactory.cs @@ -0,0 +1,53 @@ +using System; +using XP.Common.Logging.Interfaces; +using XP.ReportEngine.Interfaces; +using XP.ReportEngine.Models; + +namespace XP.ReportEngine.Services +{ + /// + /// 报告生成器工厂实现 | Report generator factory implementation + /// 根据输出格式创建对应的报告生成器实例 + /// Creates report generator instances based on output format + /// + public class ReportGeneratorFactory : IReportGeneratorFactory + { + private readonly ILoggerService _logger; + private readonly IReportGenerator _pdfReportGenerator; + + /// + /// 构造函数 | Constructor + /// + /// 日志服务 | Logger service + /// PDF 报告生成器 | PDF report generator + public ReportGeneratorFactory(ILoggerService logger, IReportGenerator pdfReportGenerator) + { + _logger = logger?.ForModule() ?? throw new ArgumentNullException(nameof(logger)); + _pdfReportGenerator = pdfReportGenerator ?? throw new ArgumentNullException(nameof(pdfReportGenerator)); + } + + /// + /// 根据输出格式创建生成器 | Create generator by output format + /// 当前仅支持 PDF 格式,未来可扩展 Excel/CSV + /// Currently only supports PDF format, extensible for Excel/CSV in the future + /// + /// 输出格式 | Output format + /// 对应格式的报告生成器 | Report generator for the specified format + /// 当请求不支持的格式时抛出 | Thrown when unsupported format is requested + public IReportGenerator Create(ReportOutputFormat format) + { + _logger.Info("创建报告生成器,格式: {Format} | Creating report generator, format: {Format}", format); + + switch (format) + { + case ReportOutputFormat.Pdf: + return _pdfReportGenerator; + + default: + var message = $"不支持的报告输出格式: {format} | Unsupported report output format: {format}"; + _logger.Error(null, message); + throw new NotSupportedException(message); + } + } + } +} diff --git a/XP.ReportEngine/Services/ReportIdGenerator.cs b/XP.ReportEngine/Services/ReportIdGenerator.cs new file mode 100644 index 0000000..4454246 --- /dev/null +++ b/XP.ReportEngine/Services/ReportIdGenerator.cs @@ -0,0 +1,80 @@ +using System; + +namespace XP.ReportEngine.Services +{ + /// + /// 报告编号生成器 | Report ID generator + /// 生成格式为 RPT-yyyyMMdd-NNN 的唯一报告编号 + /// Generates unique report IDs in format: RPT-yyyyMMdd-NNN + /// 线程安全,每日自动重置计数器 + /// Thread-safe with daily counter reset + /// + public class ReportIdGenerator + { + private readonly object _lock = new(); + private int _dailyCounter; + private string _currentDate; + + /// + /// 构造函数 | Constructor + /// + public ReportIdGenerator() + { + _currentDate = DateTime.Now.ToString("yyyyMMdd"); + _dailyCounter = 0; + } + + /// + /// 生成下一个唯一报告编号 | Generate next unique report ID + /// 格式:RPT-yyyyMMdd-NNN(如 RPT-20250101-001) + /// Format: RPT-yyyyMMdd-NNN (e.g., RPT-20250101-001) + /// + /// 唯一报告编号 | Unique report ID + public string GenerateNext() + { + lock (_lock) + { + var today = DateTime.Now.ToString("yyyyMMdd"); + + // 日期变更时重置计数器 | Reset counter when date changes + if (today != _currentDate) + { + _currentDate = today; + _dailyCounter = 0; + } + + _dailyCounter++; + return $"RPT-{_currentDate}-{_dailyCounter:D3}"; + } + } + + /// + /// 根据指定时间戳生成报告编号 | Generate report ID with specified timestamp + /// 用于需要指定日期的场景(如补录报告) + /// Used for scenarios requiring specific dates (e.g., backfilling reports) + /// + /// 指定的时间戳 | Specified timestamp + /// 唯一报告编号 | Unique report ID + public string GenerateForDate(DateTime timestamp) + { + lock (_lock) + { + var dateStr = timestamp.ToString("yyyyMMdd"); + + // 如果指定日期与当前日期相同,使用当前计数器 + // If specified date matches current date, use current counter + if (dateStr == _currentDate) + { + _dailyCounter++; + return $"RPT-{_currentDate}-{_dailyCounter:D3}"; + } + + // 如果指定日期与当前日期不同,更新日期并重置计数器 + // If specified date differs from current date, update date and reset counter + _currentDate = dateStr; + _dailyCounter = 1; + return $"RPT-{_currentDate}-{_dailyCounter:D3}"; + } + } + } +} diff --git a/XP.ReportEngine/Services/ReportService.cs b/XP.ReportEngine/Services/ReportService.cs new file mode 100644 index 0000000..20c68b5 --- /dev/null +++ b/XP.ReportEngine/Services/ReportService.cs @@ -0,0 +1,390 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using XP.Common.Logging.Interfaces; +using XP.ReportEngine.Configs; +using XP.ReportEngine.Interfaces; +using XP.ReportEngine.Models; + +namespace XP.ReportEngine.Services +{ + /// + /// 报告服务实现(门面)| Report service implementation (Facade) + /// 协调报告生成的完整流程,将 UI 无关的业务逻辑封装为可复用服务 + /// Orchestrates the complete report generation workflow, encapsulating UI-independent business logic as a reusable service + /// + public class ReportService : IReportService + { + private readonly IReportGenerator _reportGenerator; + private readonly IReportDataAdapter _dataAdapter; + private readonly ILoggerService _logger; + private readonly ReportIdGenerator _reportIdGenerator; + private readonly ReportConfig _reportConfig; + + /// + /// 生成互斥锁,防止并发渲染导致 iText7 字体对象跨文档引用错误 + /// Generation mutex to prevent concurrent rendering causing iText7 cross-document font reference errors + /// + private readonly SemaphoreSlim _generateLock = new(1, 1); + + /// + /// 构造函数 | Constructor + /// + /// 报告生成器 | Report generator + /// 数据适配器 | Data adapter + /// 日志服务 | Logger service + /// 报告编号生成器 | Report ID generator + /// 报告配置 | Report config + public ReportService( + IReportGenerator reportGenerator, + IReportDataAdapter dataAdapter, + ILoggerService logger, + ReportIdGenerator reportIdGenerator, + ReportConfig reportConfig) + { + _reportGenerator = reportGenerator ?? throw new ArgumentNullException(nameof(reportGenerator)); + _dataAdapter = dataAdapter ?? throw new ArgumentNullException(nameof(dataAdapter)); + _logger = logger?.ForModule() ?? throw new ArgumentNullException(nameof(logger)); + _reportIdGenerator = reportIdGenerator ?? throw new ArgumentNullException(nameof(reportIdGenerator)); + _reportConfig = reportConfig ?? throw new ArgumentNullException(nameof(reportConfig)); + } + + /// + /// 生成报告 | Generate report + /// + public async Task GenerateAsync(ReportRequest request) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + // 获取互斥锁,防止并发渲染 | Acquire mutex to prevent concurrent rendering + await _generateLock.WaitAsync(); + try + { + _logger.Info("报告服务开始生成 | Report service starting generation"); + + // 步骤 1:生成报告编号 | Step 1: Generate report ID + var reportId = GenerateReportId(request); + _logger.Info("报告编号: {ReportId} | Report ID: {ReportId}", reportId); + + // 步骤 2:确定输出路径 | Step 2: Determine output path + var outputPath = ResolveOutputPath(request, reportId); + _logger.Info("输出路径: {Path} | Output path: {Path}", outputPath); + + // 步骤 3:数据适配 | Step 3: Data adaptation + var metadata = request.Metadata ?? new ReportMetadata(); + if (string.IsNullOrEmpty(metadata.ReportId)) + { + metadata.ReportId = reportId; + } + if (metadata.InspectionDate == default) + { + metadata.InspectionDate = DateTime.Now; + } + + var context = _dataAdapter.Adapt(request.ProcessorOutputs ?? new List(), metadata); + _logger.Info("数据适配完成 | Data adaptation completed"); + + // 步骤 4:注入额外图像 | Step 4: Inject additional images + InjectAdditionalImages(context, request); + + // 步骤 5:注入配置中的 Logo 和公司信息 | Step 5: Inject logo and company info from config + InjectConfigData(context); + + // 步骤 6:注入自定义属性 | Step 6: Inject custom properties + InjectCustomProperties(context, request); + + // 步骤 7:生成首页汇总表 | Step 7: Generate homepage summary table + context.Properties["summaryTable"] = CreateSummaryTableData(context); + + // 步骤 8:调用管线生成 PDF | Step 8: Call pipeline to generate PDF + var templatePath = _reportConfig.GetResolvedTemplatePath(); + var options = new ReportGenerationOptions + { + TemplatePath = templatePath, + OutputFilePath = outputPath, + Format = ReportOutputFormat.Pdf + }; + + var genResult = await _reportGenerator.GenerateAsync(context, options); + + // 步骤 9:处理结果 | Step 9: Handle result + if (genResult.IsSuccess) + { + _logger.Info("报告生成成功: {Path} | Report generated successfully: {Path}", outputPath); + return ReportServiceResult.Success(outputPath, reportId); + } + else + { + _logger.Error(null, "报告生成失败: {Message} | Report generation failed: {Message}", genResult.ErrorMessage); + return ReportServiceResult.Failure(genResult.ErrorMessage, genResult.Exception); + } + } + catch (Exception ex) + { + _logger.Error(ex, "报告服务异常: {Message} | Report service exception: {Message}", ex.Message); + return ReportServiceResult.Failure($"报告生成过程中发生异常: {ex.Message}", ex); + } + finally + { + _generateLock.Release(); + } + } + + /// + /// 预热报告引擎 | Warm up report engine + /// 通过生成一个最小化的空白 PDF 来触发所有一次性初始化: + /// iText7 程序集加载、BouncyCastle 注册、字体子系统初始化、JIT 编译 + /// + public async Task WarmUpAsync() + { + // 获取互斥锁,确保预热与正式生成不并发 | Acquire mutex to ensure warm-up doesn't overlap with generation + await _generateLock.WaitAsync(); + try + { + _logger.Info("报告引擎预热开始 | Report engine warm-up started"); + + await Task.Run(() => + { + // 加载模板(触发 JSON 反序列化 JIT)| Load template (triggers JSON deserialization JIT) + var templatePath = _reportConfig.GetResolvedTemplatePath(); + if (!File.Exists(templatePath)) + { + _logger.Warn("预热跳过:模板文件不存在 {Path} | Warm-up skipped: template not found {Path}", templatePath); + return; + } + + // 构建最小上下文 | Build minimal context + var context = new ReportContext + { + Metadata = new ReportMetadata + { + ReportId = "WARMUP", + InspectionDate = DateTime.Now, + SampleName = "WarmUp", + OperatorName = "System" + }, + ResultGroups = new List(), + Images = new Dictionary(), + Properties = new Dictionary + { + ["summaryTable"] = new List>() + } + }; + + var options = new ReportGenerationOptions + { + TemplatePath = templatePath, + OutputFilePath = null, // 不保存文件 | Don't save file + Format = ReportOutputFormat.Pdf + }; + + // 执行完整管线(触发 iText7 初始化 + 字体加载 + JIT) + // Execute full pipeline (triggers iText7 init + font loading + JIT) + var result = _reportGenerator.GenerateAsync(context, options).GetAwaiter().GetResult(); + + // 释放预热产生的流 | Dispose warm-up stream + result.PdfStream?.Dispose(); + }); + + _logger.Info("报告引擎预热完成 | Report engine warm-up completed"); + } + catch (Exception ex) + { + // 预热失败不影响正常功能 | Warm-up failure doesn't affect normal functionality + _logger.Warn("报告引擎预热失败(不影响正常使用)| Report engine warm-up failed (doesn't affect normal usage): {Message}", ex.Message); + } + finally + { + _generateLock.Release(); + } + } + + #region 私有方法 | Private Methods + + /// + /// 生成报告编号 | Generate report ID + /// 优先使用请求中已有的 ReportId,否则自动生成 + /// Prefer existing ReportId from request, otherwise auto-generate + /// + private string GenerateReportId(ReportRequest request) + { + // 如果 Metadata 中已有 ReportId,直接使用 | If Metadata already has ReportId, use it directly + if (request.Metadata != null && !string.IsNullOrEmpty(request.Metadata.ReportId)) + { + return request.Metadata.ReportId; + } + + // 如果 FileNameParameters 中已有 ReportId,直接使用 | If FileNameParameters already has ReportId, use it + if (request.FileNameParameters != null && + request.FileNameParameters.TryGetValue("ReportId", out var existingId) && + !string.IsNullOrEmpty(existingId)) + { + return existingId; + } + + // 自动生成 | Auto-generate + return _reportIdGenerator.GenerateNext(); + } + + /// + /// 解析输出文件路径 | Resolve output file path + /// + private string ResolveOutputPath(ReportRequest request, string reportId) + { + // 如果请求中指定了输出路径,直接使用 | If output path specified in request, use it directly + if (!string.IsNullOrEmpty(request.OutputFilePath)) + { + // 确保目录存在 | Ensure directory exists + var dir = Path.GetDirectoryName(request.OutputFilePath); + if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir)) + { + Directory.CreateDirectory(dir); + } + return request.OutputFilePath; + } + + // 使用配置和参数自动生成路径 | Auto-generate path using config and parameters + var fileNameParams = request.FileNameParameters ?? new Dictionary(); + + // 确保 ReportId 在参数中 | Ensure ReportId is in parameters + if (!fileNameParams.ContainsKey("ReportId")) + { + fileNameParams["ReportId"] = reportId; + } + + return _reportConfig.ResolveOutputFilePath(fileNameParams); + } + + /// + /// 注入额外图像到上下文 | Inject additional images into context + /// + private void InjectAdditionalImages(ReportContext context, ReportRequest request) + { + if (request.AdditionalImages == null || request.AdditionalImages.Count == 0) + return; + + foreach (var kvp in request.AdditionalImages) + { + if (kvp.Value != null) + { + context.Images[kvp.Key] = kvp.Value; + _logger.Debug("注入额外图像: {Key} | Injected additional image: {Key}", kvp.Key); + } + } + } + + /// + /// 注入配置中的 Logo 和公司信息 | Inject logo and company info from config + /// + private void InjectConfigData(ReportContext context) + { + // 注入公司名称 | Inject company name + if (!string.IsNullOrEmpty(_reportConfig.CompanyName)) + { + context.Properties["CompanyName"] = _reportConfig.CompanyName; + } + + // 注入软件名称 | Inject software name + if (!string.IsNullOrEmpty(_reportConfig.SoftwareName)) + { + context.Properties["SoftwareName"] = _reportConfig.SoftwareName; + } + + // 注入公司 Logo | Inject company logo + if (!string.IsNullOrEmpty(_reportConfig.CompanyLogo)) + { + var logoPath = Path.IsPathRooted(_reportConfig.CompanyLogo) + ? _reportConfig.CompanyLogo + : Path.Combine(AppDomain.CurrentDomain.BaseDirectory, _reportConfig.CompanyLogo); + + if (File.Exists(logoPath)) + { + context.Images["companyLogo"] = new ImageData + { + SourceType = ImageSourceType.FilePath, + FilePath = logoPath + }; + _logger.Info("公司 Logo 已加载: {Path} | Company logo loaded: {Path}", logoPath); + } + else + { + _logger.Warn("公司 Logo 文件不存在: {Path} | Company logo file not found: {Path}", logoPath); + } + } + + // 注入软件 Logo | Inject software logo + if (!string.IsNullOrEmpty(_reportConfig.SoftwareLogo)) + { + var softwareLogoPath = Path.IsPathRooted(_reportConfig.SoftwareLogo) + ? _reportConfig.SoftwareLogo + : Path.Combine(AppDomain.CurrentDomain.BaseDirectory, _reportConfig.SoftwareLogo); + + if (File.Exists(softwareLogoPath)) + { + context.Images["softwareLogo"] = new ImageData + { + SourceType = ImageSourceType.FilePath, + FilePath = softwareLogoPath + }; + _logger.Info("软件 Logo 已加载: {Path} | Software logo loaded: {Path}", softwareLogoPath); + } + else + { + _logger.Warn("软件 Logo 文件不存在: {Path} | Software logo file not found: {Path}", softwareLogoPath); + } + } + } + + /// + /// 注入自定义属性 | Inject custom properties + /// + private void InjectCustomProperties(ReportContext context, ReportRequest request) + { + if (request.CustomProperties == null || request.CustomProperties.Count == 0) + return; + + foreach (var kvp in request.CustomProperties) + { + context.Properties[kvp.Key] = kvp.Value; + } + } + + /// + /// 根据 ReportContext 中的结果分组生成首页汇总表数据 + /// Generate homepage summary table data from ReportContext result groups + /// + private List> CreateSummaryTableData(ReportContext context) + { + var rows = new List>(); + + foreach (var group in context.ResultGroups) + { + var inspectionType = group.ProcessorType switch + { + "LineMeasurementProcessor" => "线测量 | Line Measurement", + "BgaVoidRateProcessor" => "BGA 气泡率检测 | BGA Void Rate", + "VoidMeasurementProcessor" => "空隙测量 | Void Measurement", + "FillRateProcessor" => "通孔填锡率 | Via Fill Rate", + _ => group.ProcessorType + }; + + var classification = string.IsNullOrEmpty(group.Classification) ? "N/A" : group.Classification; + var status = classification == "Pass" ? "[PASS] 合格" : classification == "Fail" ? "[FAIL] 不合格" : "—"; + + rows.Add(new Dictionary + { + ["inspectionType"] = inspectionType, + ["classification"] = classification, + ["status"] = status + }); + } + + return rows; + } + + #endregion + } +} diff --git a/XP.ReportEngine/Templates/StandardReportTemplate.json b/XP.ReportEngine/Templates/StandardReportTemplate.json new file mode 100644 index 0000000..64fb759 --- /dev/null +++ b/XP.ReportEngine/Templates/StandardReportTemplate.json @@ -0,0 +1,222 @@ +{ + "document": { + "pageSize": "A4", + "orientation": "Portrait", + "margins": { "top": 40, "bottom": 20, "left": 20, "right": 20 }, + "header": { + "enabled": true, + "left": [ + "${loc:Report_Title}", + "${loc:Report_Id}:${metadata.reportId} | ${loc:Report_Date}:${formatDate(metadata.inspectionDate)} | ${loc:Report_Sample}:${metadata.sampleName}" + ], + "rightImageKey": "companyLogo", + "fontSize": 7, + "color": "#666666", + "showLine": true + }, + "footer": { + "enabled": true, + "left": ["${CompanyName}"], + "right": ["{currentPage} / {totalPages}"], + "fontSize": 8, + "color": "#666666", + "showLine": true + } + }, + "pages": [ + { + "type": "homepage", + "elements": [ + { + "type": "row", "size": [170, 40], "positioning": "flow", + "children": [ + { + "type": "column", "align": "left", + "children": [ + { "type": "image", "dataKey": "companyLogo", "size": [50, 30], "align": "left" }, + { "type": "text", "content": "${CompanyName}", "style": "companyName", "align": "left" } + ] + }, + { + "type": "column", "align": "right", + "children": [ + { "type": "image", "dataKey": "softwareLogo", "size": [15, 20], "align": "right" }, + { "type": "text", "content": "${SoftwareName}", "style": "companyName", "align": "right" } + ] + } + ] + }, + { "type": "spacer", "size": [170, 15], "positioning": "flow" }, + { "type": "text", "content": "${loc:Report_Title}", "style": "title", "positioning": "flow" }, + { "type": "spacer", "size": [170, 10], "positioning": "flow" }, + { "type": "text", "content": "${loc:Report_Id}:${metadata.reportId}", "style": "homepageInfo", "positioning": "flow" }, + { "type": "text", "content": "${loc:Report_Sample}:${metadata.sampleName}", "style": "homepageInfo", "positioning": "flow" }, + { "type": "text", "content": "${loc:Report_Description}:${metadata.description}", "style": "homepageInfo", "positioning": "flow" }, + { "type": "image", "dataKey": "workpieceImage", "size": [160, 110], "border": true, "align": "center", "style": "imageDefault", "positioning": "flow" }, + { "type": "text", "content": "${loc:Report_Operator}:${metadata.operatorName}", "style": "homepageFooter", "positioning": "flow" }, + { "type": "text", "content": "${loc:Report_Date}:${formatDate(metadata.inspectionDate)}", "style": "homepageFooter", "positioning": "flow" } + ] + }, + { + "type": "summary", + "elements": [ + { "type": "text", "content": "${loc:Report_Summary}", "style": "homepageHeading", "positioning": "flow" }, + { + "type": "table", "dataKey": "summaryTable", "positioning": "flow", "size": [170, 0], "style": "tableDefault", + "columns": [ + { "header": "${loc:Field_InspectionType}", "field": "inspectionType", "width": 50, "align": "left" }, + { "header": "${loc:Field_Result}", "field": "classification", "width": 30, "align": "center", "colorRules": { "Pass": "#008000", "Fail": "#FF0000" } }, + { "header": "${loc:Field_Status}", "field": "status", "width": 30, "align": "center", "colorRules": { "合格": "#008000", "PASS": "#008000", "不合格": "#FF0000", "FAIL": "#FF0000" } } + ] + } + ] + }, + { + "type": "metricData", + "elements": [ + { "type": "text", "content": "${loc:Page_MetricData}", "style": "heading", "positioning": "flow" }, + { + "type": "row", "size": [170, 100], "widths": [6, 4], "positioning": "flow", + "children": [ + { + "type": "column", "align": "left", + "children": [ + { "type": "image", "dataKey": "lineMeasurementImage", "size": [110, 90], "border": true, "align": "left" } + ] + }, + { + "type": "column", "align": "left", + "children": [ + { "type": "text", "content": "${loc:Measurement_Type}:${measurementType}", "style": "body", "align": "left" }, + { "type": "text", "content": "${loc:Field_Point1}:${point1}", "style": "body", "align": "left" }, + { "type": "text", "content": "${loc:Field_Point2}:${point2}", "style": "body", "align": "left" }, + { "type": "text", "content": "${loc:Measurement_Distance}:${actualDistance} ${unit}", "style": "body", "align": "left" }, + { "type": "text", "content": "${loc:Measurement_Angle}:${angle}°", "style": "body", "align": "left" } + ] + } + ] + } + ] + }, + { + "type": "bgaInspection", + "elements": [ + { "type": "text", "content": "${loc:Page_BgaInspection}", "style": "heading", "positioning": "flow" }, + { + "type": "row", "size": [170, 100], "widths": [6, 4], "positioning": "flow", + "children": [ + { + "type": "column", "align": "left", + "children": [ + { "type": "image", "dataKey": "bgaInspectionImage", "size": [110, 90], "border": true, "align": "left" } + ] + }, + { + "type": "column", "align": "left", + "children": [ + { "type": "text", "content": "${loc:Bga_Count}:${bgaCount}", "style": "body", "align": "left" }, + { "type": "text", "content": "${loc:Measurement_VoidRate}:${formatPercent(voidRate)}", "style": "body", "align": "left" }, + { "type": "text", "content": "${loc:Measurement_FillRate}:${formatPercent(fillRate)}", "style": "body", "align": "left" }, + { "type": "text", "content": "${loc:Bga_TotalArea}:${formatNumber(totalBgaArea, 2)}", "style": "body", "align": "left" }, + { "type": "text", "content": "${loc:Bga_TotalVoidArea}:${formatNumber(totalVoidArea, 2)}", "style": "body", "align": "left" }, + { "type": "text", "content": "${loc:Bga_VoidLimit}:${formatPercent(voidLimit)}", "style": "body", "align": "left" }, + { "type": "text", "content": "${loc:Table_Classification}:${classification}", "style": "conclusion", "align": "left", "colorRules": { "Pass": "#008000", "Fail": "#FF0000" } } + ] + } + ] + }, + { + "type": "table", "dataKey": "bgaBallsTable", "positioning": "flow", "size": [170, 0], "style": "tableDefault", + "columns": [ + { "header": "${loc:Bga_BallIndex}", "field": "index", "width": 25, "align": "center" }, + { "header": "${loc:Table_VoidRate}", "field": "voidRate", "width": 40, "align": "center" }, + { "header": "${loc:Table_Area}", "field": "area", "width": 40, "align": "center" }, + { "header": "${loc:Table_Classification}", "field": "classification", "width": 35, "align": "center", "colorRules": { "Pass": "#008000", "Fail": "#FF0000" } } + ] + } + ] + }, + { + "type": "voidInspection", + "elements": [ + { "type": "text", "content": "${loc:Page_VoidInspection}", "style": "heading", "positioning": "flow" }, + { + "type": "row", "size": [170, 100], "widths": [6, 4], "positioning": "flow", + "children": [ + { + "type": "column", "align": "left", + "children": [ + { "type": "image", "dataKey": "voidInspectionImage", "size": [110, 90], "border": true, "align": "left" } + ] + }, + { + "type": "column", "align": "left", + "children": [ + { "type": "text", "content": "${loc:Void_RoiArea}:${formatNumber(roiArea, 2)}", "style": "body", "align": "left" }, + { "type": "text", "content": "${loc:Void_TotalArea}:${formatNumber(totalVoidArea, 2)}", "style": "body", "align": "left" }, + { "type": "text", "content": "${loc:Measurement_VoidRate}:${formatPercent(voidRate)}", "style": "body", "align": "left" }, + { "type": "text", "content": "${loc:Void_Limit}:${formatPercent(voidLimit)}", "style": "body", "align": "left" }, + { "type": "text", "content": "${loc:Void_Count}:${voidCount}", "style": "body", "align": "left" }, + { "type": "text", "content": "${loc:Void_MaxArea}:${formatNumber(maxVoidArea, 2)}", "style": "body", "align": "left" }, + { "type": "text", "content": "${loc:Table_Classification}:${classification}", "style": "conclusion", "align": "left", "colorRules": { "Pass": "#008000", "Fail": "#FF0000" } } + ] + } + ] + }, + { + "type": "table", "dataKey": "voidsTable", "positioning": "flow", "size": [170, 0], "style": "tableDefault", + "columns": [ + { "header": "${loc:Table_Index}", "field": "index", "width": 20, "align": "center" }, + { "header": "${loc:Table_Area}", "field": "area", "width": 35, "align": "center" }, + { "header": "${loc:Table_AreaPercent}", "field": "areaPercent", "width": 35, "align": "center" }, + { "header": "${loc:Table_CenterX}", "field": "centerX", "width": 30, "align": "center" }, + { "header": "${loc:Table_CenterY}", "field": "centerY", "width": 30, "align": "center" } + ] + } + ] + }, + { + "type": "viaFillInspection", + "elements": [ + { "type": "text", "content": "${loc:Page_ViaFillInspection}", "style": "heading", "positioning": "flow" }, + { + "type": "row", "size": [170, 100], "widths": [6, 4], "positioning": "flow", + "children": [ + { + "type": "column", "align": "left", + "children": [ + { "type": "image", "dataKey": "viaFillImage", "size": [110, 90], "border": true, "align": "left" } + ] + }, + { + "type": "column", "align": "left", + "children": [ + { "type": "text", "content": "${loc:Fill_Rate}:${formatPercent(fillRate)}", "style": "body", "align": "left" }, + { "type": "text", "content": "${loc:Measurement_VoidRate}:${formatPercent(voidRate)}", "style": "body", "align": "left" }, + { "type": "text", "content": "${loc:Fill_FullDistance}:${formatNumber(fullDistance, 2)}", "style": "body", "align": "left" }, + { "type": "text", "content": "${loc:Fill_FillDistance}:${formatNumber(fillDistance, 2)}", "style": "body", "align": "left" }, + { "type": "text", "content": "${loc:Fill_THTLimit}:${formatPercent(thtLimit)}", "style": "body", "align": "left" }, + { "type": "text", "content": "${loc:Table_Classification}:${classification}", "style": "conclusion", "align": "left", "colorRules": { "Pass": "#008000", "Fail": "#FF0000" } } + ] + } + ] + } + ] + } + ], + "styles": { + "title": { "font": "auto", "size": 28, "bold": true, "italic": false, "color": "#1a1a1a", "align": "center" }, + "companyName": { "font": "auto", "size": 10, "bold": false, "italic": false, "color": "#333333", "align": "center" }, + "homepageInfo": { "font": "auto", "size": 12, "bold": false, "italic": false, "color": "#333333", "align": "left", "paddingLeft": 10 }, + "homepageFooter": { "font": "auto", "size": 12, "bold": false, "italic": false, "color": "#333333", "align": "center" }, + "homepageHeading": { "font": "auto", "size": 14, "bold": true, "italic": false, "color": "#333333", "align": "left" }, + "heading": { "font": "auto", "size": 16, "bold": true, "italic": false, "color": "#333333", "align": "left", "marginBottom": 3 }, + "body": { "font": "auto", "size": 11, "bold": false, "italic": false, "color": "#333333", "align": "left" }, + "bodyBold": { "font": "auto", "size": 11, "bold": true, "italic": false, "color": "#1a1a1a", "align": "left" }, + "conclusion": { "font": "auto", "size": 12, "bold": true, "italic": false, "color": "#333333", "align": "left", "marginTop": 3 }, + "imageDefault": { "font": "auto", "size": 12, "bold": false, "italic": false, "color": "#333333", "align": "center", "marginTop": 5, "marginBottom": 5 }, + "tableHeader": { "font": "auto", "size": 10, "bold": true, "italic": false, "color": "#ffffff", "align": "center", "backgroundColor": "#4472C4" }, + "tableDefault": { "font": "auto", "size": 10, "bold": false, "italic": false, "color": "#333333", "align": "center", "marginTop": 5 }, + "tableCell": { "font": "auto", "size": 10, "bold": false, "italic": false, "color": "#333333", "align": "center" } + } +} diff --git a/XP.ReportEngine/ViewModels/ReportDemoViewModel.cs b/XP.ReportEngine/ViewModels/ReportDemoViewModel.cs new file mode 100644 index 0000000..f7d75b6 --- /dev/null +++ b/XP.ReportEngine/ViewModels/ReportDemoViewModel.cs @@ -0,0 +1,618 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using System.Windows; +using Prism.Commands; +using Prism.Mvvm; +using XP.Common.GeneralForm.Views; +using XP.Common.Logging.Interfaces; +using XP.Common.PdfViewer.Interfaces; +using XP.ReportEngine.Configs; +using XP.ReportEngine.Interfaces; +using XP.ReportEngine.Models; +using XP.ReportEngine.Services; + +namespace XP.ReportEngine.ViewModels +{ + /// + /// 报告生成演示窗口 ViewModel | Report generation demo window ViewModel + /// 演示如何使用 IReportService 生成 PDF 报告 + /// Demonstrates how to use IReportService to generate PDF reports + /// + public class ReportDemoViewModel : BindableBase + { + private readonly IReportService _reportService; + private readonly IPdfViewerService _pdfViewerService; + private readonly IPdfPrintService _pdfPrintService; + private readonly ILoggerService _logger; + private readonly ReportIdGenerator _reportIdGenerator; + private readonly ReportConfig _reportConfig; + + private string _productName = "PCB-TEST-001"; + private string _operatorName = "戚明轩 mingxuan.qi@hexagon.com"; + private string _description = "BGA 焊球气泡率检测"; + private string _cncProgram = "Prog001"; + private string _productCode = "PCBA-X100"; + private string _workpieceSN = "SN20250001"; + private string _deviceId = "XP-CT-001"; + private string _machineId = "MC01"; + private string _statusMessage = "就绪"; + private string _lastOutputPath; + private bool _isGenerating; + + #region 属性 | Properties + + /// + /// 产品名称 | Product name + /// + public string ProductName + { + get => _productName; + set => SetProperty(ref _productName, value); + } + + /// + /// 操作员 | Operator + /// + public string OperatorName + { + get => _operatorName; + set => SetProperty(ref _operatorName, value); + } + + /// + /// 描述 | Description + /// + public string Description + { + get => _description; + set => SetProperty(ref _description, value); + } + + /// + /// CNC 程序名称 | CNC program name + /// + public string CncProgram + { + get => _cncProgram; + set => SetProperty(ref _cncProgram, value); + } + + /// + /// 产品类型码 | Product type code + /// + public string ProductCode + { + get => _productCode; + set => SetProperty(ref _productCode, value); + } + + /// + /// 工件 SN 码 | Workpiece serial number + /// + public string WorkpieceSN + { + get => _workpieceSN; + set => SetProperty(ref _workpieceSN, value); + } + + /// + /// 检测设备编号 | Inspection device ID + /// + public string DeviceId + { + get => _deviceId; + set => SetProperty(ref _deviceId, value); + } + + /// + /// 生产机台号 | Production machine ID + /// + public string MachineId + { + get => _machineId; + set => SetProperty(ref _machineId, value); + } + + /// + /// 状态信息 | Status message + /// + public string StatusMessage + { + get => _statusMessage; + set => SetProperty(ref _statusMessage, value); + } + + /// + /// 是否正在生成 | Whether generating + /// + public bool IsGenerating + { + get => _isGenerating; + set + { + if (SetProperty(ref _isGenerating, value)) + { + GenerateReportCommand.RaiseCanExecuteChanged(); + OpenViewerCommand.RaiseCanExecuteChanged(); + PrintReportCommand.RaiseCanExecuteChanged(); + } + } + } + + #endregion + + #region 命令 | Commands + + /// + /// 生成报告命令 | Generate report command + /// + public DelegateCommand GenerateReportCommand { get; } + + /// + /// 打开 PDF 阅读器命令 | Open PDF viewer command + /// + public DelegateCommand OpenViewerCommand { get; } + + /// + /// 打印报告命令 | Print report command + /// + public DelegateCommand PrintReportCommand { get; } + + #endregion + + /// + /// 构造函数 | Constructor + /// + public ReportDemoViewModel( + IReportService reportService, + IPdfViewerService pdfViewerService, + IPdfPrintService pdfPrintService, + ILoggerService logger, + ReportIdGenerator reportIdGenerator, + ReportConfig reportConfig) + { + _reportService = reportService ?? throw new ArgumentNullException(nameof(reportService)); + _pdfViewerService = pdfViewerService ?? throw new ArgumentNullException(nameof(pdfViewerService)); + _pdfPrintService = pdfPrintService ?? throw new ArgumentNullException(nameof(pdfPrintService)); + _logger = logger?.ForModule() ?? throw new ArgumentNullException(nameof(logger)); + _reportIdGenerator = reportIdGenerator ?? throw new ArgumentNullException(nameof(reportIdGenerator)); + _reportConfig = reportConfig ?? throw new ArgumentNullException(nameof(reportConfig)); + + GenerateReportCommand = new DelegateCommand(async () => await GenerateReportAsync(), () => !IsGenerating); + OpenViewerCommand = new DelegateCommand(OpenViewer, () => !IsGenerating && !string.IsNullOrEmpty(_lastOutputPath)); + PrintReportCommand = new DelegateCommand(PrintReport, () => !IsGenerating && !string.IsNullOrEmpty(_lastOutputPath)); + } + + /// + /// 生成报告(带进度条)| Generate report (with progress window) + /// + private async Task GenerateReportAsync() + { + IsGenerating = true; + StatusMessage = "正在生成报告..."; + + try + { + // 构建文件名占位符参数 | Build file name placeholder parameters + var fileNameParams = new Dictionary + { + ["ReportId"] = _reportIdGenerator.GenerateNext(), + ["ProductName"] = ProductName, + ["CncProgram"] = CncProgram, + ["ProductCode"] = ProductCode, + ["WorkpieceSN"] = WorkpieceSN, + ["DeviceId"] = DeviceId, + ["MachineId"] = MachineId, + ["Result"] = "Pass" + }; + + // 确定输出路径:提示用户是否使用默认位置 | Determine output path: ask user whether to use default location + var defaultOutputPath = _reportConfig.ResolveOutputFilePath(fileNameParams); + var defaultFileName = System.IO.Path.GetFileName(defaultOutputPath); + + var result = MessageBox.Show( + $"是否将报告输出到默认位置?\r\n{defaultOutputPath}", + "输出位置确认", + MessageBoxButton.YesNoCancel, + MessageBoxImage.Question); + + if (result == MessageBoxResult.Cancel) + { + StatusMessage = "已取消生成"; + IsGenerating = false; + return; + } + + string outputPath; + if (result == MessageBoxResult.No) + { + // 用户选择自定义位置 | User chooses custom location + var saveDialog = new Microsoft.Win32.SaveFileDialog + { + Title = "选择报告保存位置", + Filter = "PDF 文件 (*.pdf)|*.pdf", + FileName = defaultFileName, + DefaultExt = ".pdf", + InitialDirectory = System.IO.Path.GetDirectoryName(defaultOutputPath) + }; + + if (saveDialog.ShowDialog() != true) + { + StatusMessage = "已取消生成"; + IsGenerating = false; + return; + } + + outputPath = saveDialog.FileName; + } + else + { + // 使用默认路径 | Use default path + outputPath = defaultOutputPath; + } + + // 创建进度条窗口 | Create progress window + var progressWindow = new ProgressWindow( + title: "报告生成中", + message: "正在准备数据...", + isCancelable: false, + logger: _logger); + + progressWindow.Owner = Application.Current.MainWindow; + progressWindow.Show(); + + try + { + // 步骤 1:准备模拟数据 | Step 1: Prepare mock data + progressWindow.UpdateProgress("正在准备检测数据...", 10); + await Task.Delay(300); // 模拟耗时 | Simulate delay + + var processorOutputs = CreateMockProcessorOutputs(); + + // 步骤 2:构建报告请求 | Step 2: Build report request + progressWindow.UpdateProgress("正在组装报告数据...", 30); + await Task.Delay(200); + + var request = new ReportRequest + { + ProcessorOutputs = processorOutputs, + Metadata = new ReportMetadata + { + ReportId = fileNameParams["ReportId"], + InspectionDate = DateTime.Now, + SampleName = ProductName, + OperatorName = OperatorName, + Description = Description + }, + OutputFilePath = outputPath, + FileNameParameters = fileNameParams + }; + + // 注入工件整体图片 | Inject workpiece overview image + var workpieceImagePath = System.IO.Path.Combine(MockImageDirectory, "OverView.png"); + if (File.Exists(workpieceImagePath)) + { + request.AdditionalImages["workpieceImage"] = new ImageData + { + SourceType = ImageSourceType.FilePath, + FilePath = workpieceImagePath + }; + } + + // 步骤 3:调用报告服务生成(在后台线程执行,避免阻塞 UI) + // Step 3: Call report service (on background thread to avoid blocking UI) + progressWindow.UpdateProgress("正在生成 PDF...", 60); + await Task.Delay(200); + + var genResult = await Task.Run(() => _reportService.GenerateAsync(request)); + + // 步骤 4:处理结果 | Step 4: Handle result + progressWindow.UpdateProgress("正在完成...", 95); + await Task.Delay(200); + + if (genResult.IsSuccess) + { + _lastOutputPath = genResult.OutputFilePath; + StatusMessage = $"报告生成成功:{genResult.OutputFilePath}"; + _logger.Info("报告生成成功:{Path} | Report generated successfully: {Path}", genResult.OutputFilePath); + + progressWindow.UpdateProgress("报告生成完成!", 100); + await Task.Delay(500); + + // 根据配置自动打开 PDF 阅读器 | Auto-open PDF viewer based on config + if (_reportConfig.AutoOpenAfterGenerate) + { + try + { + _pdfViewerService.OpenViewer(genResult.OutputFilePath); + } + catch (Exception viewerEx) + { + _logger.Warn("自动打开 PDF 失败 | Auto-open PDF failed: {Message}", viewerEx.Message); + } + } + } + else + { + StatusMessage = $"报告生成失败:{genResult.ErrorMessage}"; + _logger.Error(null, "报告生成失败:{Message} | Report generation failed: {Message}", genResult.ErrorMessage); + + MessageBox.Show( + $"报告生成失败:\n{genResult.ErrorMessage}", + "错误", + MessageBoxButton.OK, + MessageBoxImage.Error); + } + } + finally + { + progressWindow.Close(); + } + } + catch (Exception ex) + { + StatusMessage = $"报告生成异常:{ex.Message}"; + _logger.Error(ex, "报告生成异常 | Report generation exception: {Message}", ex.Message); + + MessageBox.Show( + $"报告生成过程中发生异常:\n{ex.Message}", + "错误", + MessageBoxButton.OK, + MessageBoxImage.Error); + } + finally + { + IsGenerating = false; + OpenViewerCommand.RaiseCanExecuteChanged(); + PrintReportCommand.RaiseCanExecuteChanged(); + } + } + + /// + /// 打开 PDF 阅读器 | Open PDF viewer + /// + private void OpenViewer() + { + if (string.IsNullOrEmpty(_lastOutputPath) || !File.Exists(_lastOutputPath)) + { + MessageBox.Show("PDF 文件不存在,请先生成报告。", "提示", MessageBoxButton.OK, MessageBoxImage.Warning); + return; + } + + try + { + // 使用 XP.Common PDF 阅读器打开 | Open with XP.Common PDF viewer + _pdfViewerService.OpenViewer(_lastOutputPath); + _logger.Info("使用内置 PDF 阅读器打开:{Path} | Opened with built-in PDF viewer: {Path}", _lastOutputPath); + } + catch (Exception ex) + { + _logger.Error(ex, "打开 PDF 失败 | Failed to open PDF: {Message}", ex.Message); + MessageBox.Show($"打开 PDF 失败:\n{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); + } + } + + /// + /// 打印报告 | Print report + /// + private void PrintReport() + { + if (string.IsNullOrEmpty(_lastOutputPath) || !File.Exists(_lastOutputPath)) + { + MessageBox.Show("PDF 文件不存在,请先生成报告。", "提示", MessageBoxButton.OK, MessageBoxImage.Warning); + return; + } + + try + { + var confirmed = _pdfPrintService.PrintWithDialog(_lastOutputPath); + if (confirmed) + { + _logger.Info("报告已发送到打印机:{Path} | Report sent to printer: {Path}", _lastOutputPath); + StatusMessage = "报告已发送到打印机"; + } + } + catch (Exception ex) + { + _logger.Error(ex, "打印报告失败 | Failed to print report: {Message}", ex.Message); + MessageBox.Show($"打印失败:\n{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); + } + } + + #region 模拟数据 | Mock Data + + /// + /// 模拟图像目录路径 | Mock image directory path + /// + private const string MockImageDirectory = @"D:\XplorePlane\DetectorImages"; + + /// + /// 创建模拟处理器输出数据(演示用,覆盖所有检测类型) + /// Create mock processor outputs (for demo, covers all inspection types) + /// + private List CreateMockProcessorOutputs() + { + return new List + { + CreateLineMeasurementOutput(), + CreateBgaVoidRateOutput(), + CreateVoidMeasurementOutput(), + CreateFillRateOutput() + }; + } + + /// + /// 创建线测量处理器模拟数据 | Create line measurement processor mock data + /// + private ProcessorOutput CreateLineMeasurementOutput() + { + return new ProcessorOutput + { + ProcessorType = "LineMeasurementProcessor", + OutputData = new Dictionary + { + ["MeasurementType"] = "TwoPointDistance", + ["Point1"] = "X=125.32, Y=80.15", + ["Point2"] = "X=340.78, Y=80.15", + ["PixelDistance"] = 215.46, + ["ActualDistance"] = 3.256, + ["Unit"] = "mm", + ["Angle"] = 0.0 + }, + AnnotatedImage = LoadMockImage("BGA.png") + }; + } + + /// + /// 创建 BGA 气泡率处理器模拟数据 | Create BGA void rate processor mock data + /// + private ProcessorOutput CreateBgaVoidRateOutput() + { + // 生成 64 组 BGA 球模拟数据 | Generate 64 BGA ball mock data + var bgaBalls = new List>(); + var random = new Random(42); // 固定种子确保可重复 | Fixed seed for reproducibility + var voidLimit = 0.05; + + for (int i = 1; i <= 64; i++) + { + // 大部分合格,少数不合格 | Most pass, a few fail + double voidRate; + if (i == 12 || i == 27 || i == 41 || i == 58) + { + // 这几个球不合格 | These balls fail + voidRate = Math.Round(0.05 + random.NextDouble() * 0.03, 4); + } + else + { + voidRate = Math.Round(random.NextDouble() * 0.045, 4); + } + + var area = Math.Round(190.0 + random.NextDouble() * 15.0, 1); + var classification = voidRate > voidLimit ? "Fail" : "Pass"; + + bgaBalls.Add(new Dictionary + { + ["Index"] = i, + ["VoidRate"] = voidRate, + ["Area"] = area, + ["Classification"] = classification + }); + } + + return new ProcessorOutput + { + ProcessorType = "BgaVoidRateProcessor", + OutputData = new Dictionary + { + ["BgaCount"] = 64, + ["VoidRate"] = 0.028, + ["FillRate"] = 0.972, + ["TotalBgaArea"] = 12500.5, + ["TotalVoidArea"] = 350.2, + ["Classification"] = "Pass", + ["VoidLimit"] = 0.05, + ["BgaBalls"] = bgaBalls + }, + AnnotatedImage = LoadMockImage("BGA.png") + }; + } + + /// + /// 创建空隙测量处理器模拟数据 | Create void measurement processor mock data + /// + private ProcessorOutput CreateVoidMeasurementOutput() + { + return new ProcessorOutput + { + ProcessorType = "VoidMeasurementProcessor", + OutputData = new Dictionary + { + ["RoiArea"] = 5000.0, + ["TotalVoidArea"] = 125.8, + ["VoidRate"] = 0.025, + ["VoidLimit"] = 0.05, + ["VoidCount"] = 5, + ["MaxVoidArea"] = 65.2, + ["Classification"] = "Pass", + ["Voids"] = new List> + { + new() { ["Index"] = 1, ["Area"] = 65.2, ["AreaPercent"] = 1.30, ["CenterX"] = 120.5, ["CenterY"] = 85.3 }, + new() { ["Index"] = 2, ["Area"] = 38.4, ["AreaPercent"] = 0.77, ["CenterX"] = 200.1, ["CenterY"] = 150.8 }, + new() { ["Index"] = 3, ["Area"] = 22.2, ["AreaPercent"] = 0.44, ["CenterX"] = 80.0, ["CenterY"] = 220.5 }, + new() { ["Index"] = 4, ["Area"] = 15.6, ["AreaPercent"] = 0.31, ["CenterX"] = 310.2, ["CenterY"] = 95.7 }, + new() { ["Index"] = 5, ["Area"] = 8.9, ["AreaPercent"] = 0.18, ["CenterX"] = 155.8, ["CenterY"] = 280.1 } + } + }, + AnnotatedImage = LoadMockImage("Void.png") + }; + } + + /// + /// 创建通孔填锡率处理器模拟数据 | Create via fill rate processor mock data + /// + private ProcessorOutput CreateFillRateOutput() + { + return new ProcessorOutput + { + ProcessorType = "FillRateProcessor", + OutputData = new Dictionary + { + ["FillRate"] = 0.85, + ["VoidRate"] = 0.15, + ["FullDistance"] = 1.60, + ["FillDistance"] = 1.36, + ["THTLimit"] = 0.75, + ["Classification"] = "Pass", + ["E1"] = new Dictionary + { + ["CenterX"] = 256.0, ["CenterY"] = 256.0, + ["SemiAxisA"] = 120.5, ["SemiAxisB"] = 118.2, ["Angle"] = 2.3 + }, + ["E2"] = new Dictionary + { + ["CenterX"] = 256.0, ["CenterY"] = 256.0, + ["SemiAxisA"] = 95.8, ["SemiAxisB"] = 93.1, ["Angle"] = 2.3 + }, + ["E3"] = new Dictionary + { + ["CenterX"] = 256.0, ["CenterY"] = 256.0, + ["SemiAxisA"] = 70.2, ["SemiAxisB"] = 68.5, ["Angle"] = 1.8 + }, + ["E4"] = new Dictionary + { + ["CenterX"] = 256.0, ["CenterY"] = 256.0, + ["SemiAxisA"] = 45.0, ["SemiAxisB"] = 43.7, ["Angle"] = 1.5 + } + }, + AnnotatedImage = LoadMockImage("Void.png") + }; + } + + /// + /// 加载模拟图像文件 | Load mock image file + /// 从指定目录加载图像,文件不存在时返回 null + /// Loads image from specified directory, returns null if file not found + /// + /// 图像文件名 | Image file name + /// 图像数据对象或 null | ImageData object or null + private ImageData LoadMockImage(string fileName) + { + var filePath = System.IO.Path.Combine(MockImageDirectory, fileName); + if (!File.Exists(filePath)) + { + _logger.Warn("模拟图像文件不存在:{Path} | Mock image file not found: {Path}", filePath); + return null; + } + + return new ImageData + { + SourceType = ImageSourceType.FilePath, + FilePath = filePath + }; + } + + #endregion + } +} diff --git a/XP.ReportEngine/Views/ReportDemoWindow.xaml b/XP.ReportEngine/Views/ReportDemoWindow.xaml new file mode 100644 index 0000000..8c28b9e --- /dev/null +++ b/XP.ReportEngine/Views/ReportDemoWindow.xaml @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/XP.ReportEngine/Views/ReportDemoWindow.xaml.cs b/XP.ReportEngine/Views/ReportDemoWindow.xaml.cs new file mode 100644 index 0000000..ed60a62 --- /dev/null +++ b/XP.ReportEngine/Views/ReportDemoWindow.xaml.cs @@ -0,0 +1,21 @@ +using System.Windows; +using XP.ReportEngine.ViewModels; + +namespace XP.ReportEngine.Views +{ + /// + /// 报告生成演示窗口 | Report generation demo window + /// + public partial class ReportDemoWindow : Window + { + /// + /// 构造函数(通过 DI 注入 ViewModel)| Constructor (ViewModel injected via DI) + /// + /// 报告演示 ViewModel | Report demo ViewModel + public ReportDemoWindow(ReportDemoViewModel viewModel) + { + InitializeComponent(); + DataContext = viewModel; + } + } +} diff --git a/XP.ReportEngine/XP.ReportEngine.csproj b/XP.ReportEngine/XP.ReportEngine.csproj new file mode 100644 index 0000000..540ffef --- /dev/null +++ b/XP.ReportEngine/XP.ReportEngine.csproj @@ -0,0 +1,37 @@ + + + net8.0-windows7.0 + true + + + + + + + + + + + + + + + + + + + + + PreserveNewest + + + diff --git a/XplorePlane.sln b/XplorePlane.sln index c43f265..22c932e 100644 --- a/XplorePlane.sln +++ b/XplorePlane.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 -VisualStudioVersion = 17.14.36811.4 +VisualStudioVersion = 17.14.37203.1 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XplorePlane", "XplorePlane\XplorePlane.csproj", "{07978DB9-4B88-4F42-9054-73992742BD6A}" EndProject @@ -66,6 +66,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XP.Calibration", "XP.Calibr EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "XP.Calibration", "XP.Calibration", "{D4E5F6A7-B8C9-0123-4567-89ABCDEF0123}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XP.ReportEngine", "XP.ReportEngine\XP.ReportEngine.csproj", "{809A8588-F64C-4738-8827-CFBC59943DBF}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -268,6 +270,18 @@ Global {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x64.Build.0 = Release|Any CPU {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x86.ActiveCfg = Release|Any CPU {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x86.Build.0 = Release|Any CPU + {809A8588-F64C-4738-8827-CFBC59943DBF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {809A8588-F64C-4738-8827-CFBC59943DBF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {809A8588-F64C-4738-8827-CFBC59943DBF}.Debug|x64.ActiveCfg = Debug|Any CPU + {809A8588-F64C-4738-8827-CFBC59943DBF}.Debug|x64.Build.0 = Debug|Any CPU + {809A8588-F64C-4738-8827-CFBC59943DBF}.Debug|x86.ActiveCfg = Debug|Any CPU + {809A8588-F64C-4738-8827-CFBC59943DBF}.Debug|x86.Build.0 = Debug|Any CPU + {809A8588-F64C-4738-8827-CFBC59943DBF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {809A8588-F64C-4738-8827-CFBC59943DBF}.Release|Any CPU.Build.0 = Release|Any CPU + {809A8588-F64C-4738-8827-CFBC59943DBF}.Release|x64.ActiveCfg = Release|Any CPU + {809A8588-F64C-4738-8827-CFBC59943DBF}.Release|x64.Build.0 = Release|Any CPU + {809A8588-F64C-4738-8827-CFBC59943DBF}.Release|x86.ActiveCfg = Release|Any CPU + {809A8588-F64C-4738-8827-CFBC59943DBF}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/XplorePlane/App.xaml.cs b/XplorePlane/App.xaml.cs index 73eb41c..e31ac2b 100644 --- a/XplorePlane/App.xaml.cs +++ b/XplorePlane/App.xaml.cs @@ -35,6 +35,7 @@ using XP.Hardware.PLC.Abstractions; using XP.Hardware.PLC.Services; using XP.Hardware.RaySource.Module; using XP.Hardware.RaySource.Services; +using XP.ReportEngine; using XplorePlane.Services; using XplorePlane.Services.AppState; using XplorePlane.Services.Camera; @@ -480,6 +481,7 @@ namespace XplorePlane moduleCatalog.AddModule(); moduleCatalog.AddModule(); moduleCatalog.AddModule(); + moduleCatalog.AddModule(); base.ConfigureModuleCatalog(moduleCatalog); } diff --git a/XplorePlane/ViewModels/Main/MainViewModel.cs b/XplorePlane/ViewModels/Main/MainViewModel.cs index f71eeb9..85325e2 100644 --- a/XplorePlane/ViewModels/Main/MainViewModel.cs +++ b/XplorePlane/ViewModels/Main/MainViewModel.cs @@ -110,6 +110,7 @@ namespace XplorePlane.ViewModels public DelegateCommand OpenMotionDebugCommand { get; } public DelegateCommand OpenPlcAddrConfigCommand { get; } public DelegateCommand OpenRaySourceConfigCommand { get; } + public DelegateCommand OpenReportConfigCommand { get; } public DelegateCommand WarmUpCommand { get; } // 测量命令 @@ -223,6 +224,7 @@ namespace XplorePlane.ViewModels private Window _settingsWindow; private Window _toolboxWindow; private Window _raySourceConfigWindow; + private Window _reportConfigWindow; private object _imagePanelContent; private GridLength _viewportPanelWidth = new(1, GridUnitType.Star); private GridLength _imagePanelWidth = new(320); @@ -346,6 +348,7 @@ namespace XplorePlane.ViewModels OpenMotionDebugCommand = new DelegateCommand(ExecuteOpenMotionDebug); OpenPlcAddrConfigCommand = new DelegateCommand(ExecuteOpenPlcAddrConfig); OpenRaySourceConfigCommand = new DelegateCommand(ExecuteOpenRaySourceConfig); + OpenReportConfigCommand = new DelegateCommand(ExecuteOpenReportConfig); WarmUpCommand = new DelegateCommand(ExecuteWarmUp); OpenLanguageSwitcherCommand = new DelegateCommand(ExecuteOpenLanguageSwitcher); @@ -437,6 +440,9 @@ namespace XplorePlane.ViewModels try { var manualPath = ConfigurationManager.AppSettings["UserManual"]; + manualPath = Path.IsPathRooted(manualPath) + ? manualPath + : Path.Combine(AppDomain.CurrentDomain.BaseDirectory, manualPath); if (string.IsNullOrEmpty(manualPath)) { _logger.Warn("User manual path is not configured."); @@ -761,6 +767,16 @@ namespace XplorePlane.ViewModels () => new XP.Hardware.RaySource.Views.RaySourceConfigWindow(), "Ray Source Config"); } + private void ExecuteOpenReportConfig() + { + ShowOrActivate(_reportConfigWindow, w => _reportConfigWindow = w, + () => + { + var viewModel = _containerProvider.Resolve(); + return new XP.ReportEngine.Views.ReportDemoWindow(viewModel); + }, "报告配置"); + } + private void ExecuteLoadImage() { var dialog = new OpenFileDialog diff --git a/XplorePlane/Views/Main/MainWindow.xaml b/XplorePlane/Views/Main/MainWindow.xaml index c16b92a..a9a454f 100644 --- a/XplorePlane/Views/Main/MainWindow.xaml +++ b/XplorePlane/Views/Main/MainWindow.xaml @@ -485,6 +485,13 @@ Size="Medium" SmallImage="/Assets/Icons/tools.png" Text="PLC 地址" /> + +