报告ReportEngineBase修改语言资源文件,修改模板和报告输出功能。
This commit is contained in:
@@ -1,5 +1,64 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
@@ -58,15 +117,14 @@
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<!-- Report metadata keys -->
|
||||
<data name="Report_Title" xml:space="preserve">
|
||||
<value>Inspection Report</value>
|
||||
</data>
|
||||
<data name="Report_Date" xml:space="preserve">
|
||||
<value>Inspection Date</value>
|
||||
<value>Inspection Time</value>
|
||||
</data>
|
||||
<data name="Report_Sample" xml:space="preserve">
|
||||
<value>Sample Name</value>
|
||||
<value>Product Name</value>
|
||||
</data>
|
||||
<data name="Report_Operator" xml:space="preserve">
|
||||
<value>Operator</value>
|
||||
@@ -80,14 +138,12 @@
|
||||
<data name="Report_Description" xml:space="preserve">
|
||||
<value>Description</value>
|
||||
</data>
|
||||
<!-- Classification keys -->
|
||||
<data name="Classification_Pass" xml:space="preserve">
|
||||
<value>PASS</value>
|
||||
</data>
|
||||
<data name="Classification_Fail" xml:space="preserve">
|
||||
<value>FAIL</value>
|
||||
</data>
|
||||
<!-- Inspection type keys -->
|
||||
<data name="Inspection_LineMeasurement" xml:space="preserve">
|
||||
<value>Line Measurement</value>
|
||||
</data>
|
||||
@@ -100,7 +156,6 @@
|
||||
<data name="Inspection_FillRate" xml:space="preserve">
|
||||
<value>Via Fill Rate</value>
|
||||
</data>
|
||||
<!-- Table header keys -->
|
||||
<data name="Table_Index" xml:space="preserve">
|
||||
<value>No.</value>
|
||||
</data>
|
||||
@@ -122,7 +177,6 @@
|
||||
<data name="Table_CenterY" xml:space="preserve">
|
||||
<value>Center Y</value>
|
||||
</data>
|
||||
<!-- Measurement keys -->
|
||||
<data name="Measurement_Type" xml:space="preserve">
|
||||
<value>Measurement Type</value>
|
||||
</data>
|
||||
@@ -144,7 +198,6 @@
|
||||
<data name="Measurement_Limit" xml:space="preserve">
|
||||
<value>Limit</value>
|
||||
</data>
|
||||
<!-- Summary keys -->
|
||||
<data name="Summary_TotalDefects" xml:space="preserve">
|
||||
<value>Total Defects</value>
|
||||
</data>
|
||||
@@ -157,11 +210,9 @@
|
||||
<data name="Summary_OverallResult" xml:space="preserve">
|
||||
<value>Overall Result</value>
|
||||
</data>
|
||||
<!-- Image placeholder key -->
|
||||
<data name="Image_NoImage" xml:space="preserve">
|
||||
<value>No Image</value>
|
||||
</data>
|
||||
<!-- Page title keys -->
|
||||
<data name="Page_Homepage" xml:space="preserve">
|
||||
<value>Inspection Report</value>
|
||||
</data>
|
||||
@@ -177,7 +228,6 @@
|
||||
<data name="Page_ViaFillInspection" xml:space="preserve">
|
||||
<value>Via Fill Rate Inspection</value>
|
||||
</data>
|
||||
<!-- BGA inspection keys -->
|
||||
<data name="Bga_Count" xml:space="preserve">
|
||||
<value>Ball Count</value>
|
||||
</data>
|
||||
@@ -193,7 +243,6 @@
|
||||
<data name="Bga_BallIndex" xml:space="preserve">
|
||||
<value>Ball No.</value>
|
||||
</data>
|
||||
<!-- Void inspection keys -->
|
||||
<data name="Void_RoiArea" xml:space="preserve">
|
||||
<value>ROI Area</value>
|
||||
</data>
|
||||
@@ -209,7 +258,6 @@
|
||||
<data name="Void_Limit" xml:space="preserve">
|
||||
<value>Void Rate Limit</value>
|
||||
</data>
|
||||
<!-- Via fill rate keys -->
|
||||
<data name="Fill_Rate" xml:space="preserve">
|
||||
<value>Fill Rate</value>
|
||||
</data>
|
||||
@@ -222,7 +270,6 @@
|
||||
<data name="Fill_THTLimit" xml:space="preserve">
|
||||
<value>THT Limit</value>
|
||||
</data>
|
||||
<!-- Common field keys -->
|
||||
<data name="Field_Point1" xml:space="preserve">
|
||||
<value>Point 1</value>
|
||||
</data>
|
||||
@@ -238,4 +285,4 @@
|
||||
<data name="Field_Status" xml:space="preserve">
|
||||
<value>Status</value>
|
||||
</data>
|
||||
</root>
|
||||
</root>
|
||||
@@ -1,5 +1,64 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
@@ -58,15 +117,14 @@
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<!-- 报告元数据键 | Report metadata keys -->
|
||||
<data name="Report_Title" xml:space="preserve">
|
||||
<value>检测报告</value>
|
||||
</data>
|
||||
<data name="Report_Date" xml:space="preserve">
|
||||
<value>检测日期</value>
|
||||
<value>检测时间</value>
|
||||
</data>
|
||||
<data name="Report_Sample" xml:space="preserve">
|
||||
<value>样品名称</value>
|
||||
<value>产品名称</value>
|
||||
</data>
|
||||
<data name="Report_Operator" xml:space="preserve">
|
||||
<value>操作员</value>
|
||||
@@ -80,14 +138,12 @@
|
||||
<data name="Report_Description" xml:space="preserve">
|
||||
<value>描述</value>
|
||||
</data>
|
||||
<!-- 分类结果键 | Classification keys -->
|
||||
<data name="Classification_Pass" xml:space="preserve">
|
||||
<value>通过</value>
|
||||
</data>
|
||||
<data name="Classification_Fail" xml:space="preserve">
|
||||
<value>不通过</value>
|
||||
</data>
|
||||
<!-- 检测类型键 | Inspection type keys -->
|
||||
<data name="Inspection_LineMeasurement" xml:space="preserve">
|
||||
<value>距离测量</value>
|
||||
</data>
|
||||
@@ -100,7 +156,6 @@
|
||||
<data name="Inspection_FillRate" xml:space="preserve">
|
||||
<value>通孔填锡率</value>
|
||||
</data>
|
||||
<!-- 表格列头键 | Table header keys -->
|
||||
<data name="Table_Index" xml:space="preserve">
|
||||
<value>序号</value>
|
||||
</data>
|
||||
@@ -122,7 +177,6 @@
|
||||
<data name="Table_CenterY" xml:space="preserve">
|
||||
<value>中心 Y</value>
|
||||
</data>
|
||||
<!-- 测量键 | Measurement keys -->
|
||||
<data name="Measurement_Type" xml:space="preserve">
|
||||
<value>测量类型</value>
|
||||
</data>
|
||||
@@ -144,7 +198,6 @@
|
||||
<data name="Measurement_Limit" xml:space="preserve">
|
||||
<value>限值</value>
|
||||
</data>
|
||||
<!-- 摘要键 | Summary keys -->
|
||||
<data name="Summary_TotalDefects" xml:space="preserve">
|
||||
<value>总缺陷数</value>
|
||||
</data>
|
||||
@@ -157,11 +210,9 @@
|
||||
<data name="Summary_OverallResult" xml:space="preserve">
|
||||
<value>总体结果</value>
|
||||
</data>
|
||||
<!-- 图像占位符键 | Image placeholder key -->
|
||||
<data name="Image_NoImage" xml:space="preserve">
|
||||
<value>无图像</value>
|
||||
</data>
|
||||
<!-- 页面标题键 | Page title keys -->
|
||||
<data name="Page_Homepage" xml:space="preserve">
|
||||
<value>检测报告首页</value>
|
||||
</data>
|
||||
@@ -177,7 +228,6 @@
|
||||
<data name="Page_ViaFillInspection" xml:space="preserve">
|
||||
<value>通孔填锡检测</value>
|
||||
</data>
|
||||
<!-- BGA 检测键 | BGA inspection keys -->
|
||||
<data name="Bga_Count" xml:space="preserve">
|
||||
<value>焊球数量</value>
|
||||
</data>
|
||||
@@ -193,7 +243,6 @@
|
||||
<data name="Bga_BallIndex" xml:space="preserve">
|
||||
<value>焊球序号</value>
|
||||
</data>
|
||||
<!-- 空隙检测键 | Void inspection keys -->
|
||||
<data name="Void_RoiArea" xml:space="preserve">
|
||||
<value>ROI 面积</value>
|
||||
</data>
|
||||
@@ -209,7 +258,6 @@
|
||||
<data name="Void_Limit" xml:space="preserve">
|
||||
<value>空隙率限值</value>
|
||||
</data>
|
||||
<!-- 通孔填锡键 | Via fill rate keys -->
|
||||
<data name="Fill_Rate" xml:space="preserve">
|
||||
<value>填锡率</value>
|
||||
</data>
|
||||
@@ -222,7 +270,6 @@
|
||||
<data name="Fill_THTLimit" xml:space="preserve">
|
||||
<value>THT 限值</value>
|
||||
</data>
|
||||
<!-- 通用字段键 | Common field keys -->
|
||||
<data name="Field_Point1" xml:space="preserve">
|
||||
<value>起点</value>
|
||||
</data>
|
||||
@@ -238,4 +285,4 @@
|
||||
<data name="Field_Status" xml:space="preserve">
|
||||
<value>状态</value>
|
||||
</data>
|
||||
</root>
|
||||
</root>
|
||||
@@ -1,5 +1,64 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
@@ -58,15 +117,14 @@
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<!-- 报告元数据键 | Report metadata keys -->
|
||||
<data name="Report_Title" xml:space="preserve">
|
||||
<value>检测报告</value>
|
||||
</data>
|
||||
<data name="Report_Date" xml:space="preserve">
|
||||
<value>检测日期</value>
|
||||
<value>检测时间</value>
|
||||
</data>
|
||||
<data name="Report_Sample" xml:space="preserve">
|
||||
<value>样品名称</value>
|
||||
<value>产品名称</value>
|
||||
</data>
|
||||
<data name="Report_Operator" xml:space="preserve">
|
||||
<value>操作员</value>
|
||||
@@ -80,14 +138,12 @@
|
||||
<data name="Report_Description" xml:space="preserve">
|
||||
<value>描述</value>
|
||||
</data>
|
||||
<!-- 分类结果键 | Classification keys -->
|
||||
<data name="Classification_Pass" xml:space="preserve">
|
||||
<value>通过</value>
|
||||
</data>
|
||||
<data name="Classification_Fail" xml:space="preserve">
|
||||
<value>不通过</value>
|
||||
</data>
|
||||
<!-- 检测类型键 | Inspection type keys -->
|
||||
<data name="Inspection_LineMeasurement" xml:space="preserve">
|
||||
<value>距离测量</value>
|
||||
</data>
|
||||
@@ -100,7 +156,6 @@
|
||||
<data name="Inspection_FillRate" xml:space="preserve">
|
||||
<value>通孔填锡率</value>
|
||||
</data>
|
||||
<!-- 表格列头键 | Table header keys -->
|
||||
<data name="Table_Index" xml:space="preserve">
|
||||
<value>序号</value>
|
||||
</data>
|
||||
@@ -122,7 +177,6 @@
|
||||
<data name="Table_CenterY" xml:space="preserve">
|
||||
<value>中心 Y</value>
|
||||
</data>
|
||||
<!-- 测量键 | Measurement keys -->
|
||||
<data name="Measurement_Type" xml:space="preserve">
|
||||
<value>测量类型</value>
|
||||
</data>
|
||||
@@ -144,7 +198,6 @@
|
||||
<data name="Measurement_Limit" xml:space="preserve">
|
||||
<value>限值</value>
|
||||
</data>
|
||||
<!-- 摘要键 | Summary keys -->
|
||||
<data name="Summary_TotalDefects" xml:space="preserve">
|
||||
<value>总缺陷数</value>
|
||||
</data>
|
||||
@@ -157,11 +210,9 @@
|
||||
<data name="Summary_OverallResult" xml:space="preserve">
|
||||
<value>总体结果</value>
|
||||
</data>
|
||||
<!-- 图像占位符键 | Image placeholder key -->
|
||||
<data name="Image_NoImage" xml:space="preserve">
|
||||
<value>无图像</value>
|
||||
</data>
|
||||
<!-- 页面标题键 | Page title keys -->
|
||||
<data name="Page_Homepage" xml:space="preserve">
|
||||
<value>检测报告首页</value>
|
||||
</data>
|
||||
@@ -177,7 +228,6 @@
|
||||
<data name="Page_ViaFillInspection" xml:space="preserve">
|
||||
<value>通孔填锡检测</value>
|
||||
</data>
|
||||
<!-- BGA 检测键 | BGA inspection keys -->
|
||||
<data name="Bga_Count" xml:space="preserve">
|
||||
<value>焊球数量</value>
|
||||
</data>
|
||||
@@ -193,7 +243,6 @@
|
||||
<data name="Bga_BallIndex" xml:space="preserve">
|
||||
<value>焊球序号</value>
|
||||
</data>
|
||||
<!-- 空隙检测键 | Void inspection keys -->
|
||||
<data name="Void_RoiArea" xml:space="preserve">
|
||||
<value>ROI 面积</value>
|
||||
</data>
|
||||
@@ -209,7 +258,6 @@
|
||||
<data name="Void_Limit" xml:space="preserve">
|
||||
<value>空隙率限值</value>
|
||||
</data>
|
||||
<!-- 通孔填锡键 | Via fill rate keys -->
|
||||
<data name="Fill_Rate" xml:space="preserve">
|
||||
<value>填锡率</value>
|
||||
</data>
|
||||
@@ -222,7 +270,6 @@
|
||||
<data name="Fill_THTLimit" xml:space="preserve">
|
||||
<value>THT 限值</value>
|
||||
</data>
|
||||
<!-- 通用字段键 | Common field keys -->
|
||||
<data name="Field_Point1" xml:space="preserve">
|
||||
<value>起点</value>
|
||||
</data>
|
||||
@@ -238,4 +285,4 @@
|
||||
<data name="Field_Status" xml:space="preserve">
|
||||
<value>状态</value>
|
||||
</data>
|
||||
</root>
|
||||
</root>
|
||||
@@ -63,7 +63,7 @@
|
||||
<value>檢測報告</value>
|
||||
</data>
|
||||
<data name="Report_Date" xml:space="preserve">
|
||||
<value>檢測日期</value>
|
||||
<value>檢測時間</value>
|
||||
</data>
|
||||
<data name="Report_Sample" xml:space="preserve">
|
||||
<value>樣品名稱</value>
|
||||
|
||||
@@ -54,6 +54,9 @@ namespace XP.ReportEngine.Services
|
||||
}
|
||||
}
|
||||
|
||||
// 绑定页眉页脚中的表达式 | Bind expressions in header/footer
|
||||
BindHeaderFooter(clonedTemplate, context);
|
||||
|
||||
_logger.Info("数据绑定完成 | Data binding completed");
|
||||
return clonedTemplate;
|
||||
}
|
||||
@@ -99,6 +102,62 @@ namespace XP.ReportEngine.Services
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 绑定页眉页脚中的 ${} 表达式 | Bind ${} expressions in header/footer
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 绑定字符串列表中的表达式 | Bind expressions in string list
|
||||
/// </summary>
|
||||
private void BindStringList(List<string> 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)
|
||||
@@ -313,10 +372,10 @@ namespace XP.ReportEngine.Services
|
||||
|
||||
var format = _localizationService.CurrentLanguage switch
|
||||
{
|
||||
SupportedLanguage.ZhCN => "yyyy年MM月dd日",
|
||||
SupportedLanguage.ZhTW => "yyyy年MM月dd日",
|
||||
SupportedLanguage.EnUS => "MM/dd/yyyy",
|
||||
_ => "yyyy-MM-dd"
|
||||
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);
|
||||
|
||||
@@ -62,6 +62,7 @@ namespace XP.ReportEngine.Services
|
||||
private PdfFont _westernFont;
|
||||
private bool _fontsInitialized;
|
||||
private readonly object _fontLock = new();
|
||||
private ReportTemplate _currentTemplate;
|
||||
|
||||
public ITextPdfRenderer(ILoggerService logger, ILocalizationService localizationService)
|
||||
{
|
||||
@@ -77,10 +78,12 @@ namespace XP.ReportEngine.Services
|
||||
/// </summary>
|
||||
/// <param name="pages">排版后的页面列表 | Laid-out pages</param>
|
||||
/// <param name="options">生成选项 | Generation options</param>
|
||||
/// <param name="template">绑定后的模板(用于页眉页脚配置)| Bound template (for header/footer config)</param>
|
||||
/// <returns>PDF 内存流 | PDF memory stream</returns>
|
||||
public MemoryStream Render(List<LayoutPage> pages, ReportGenerationOptions options)
|
||||
public MemoryStream Render(List<LayoutPage> pages, ReportGenerationOptions options, ReportTemplate template = null)
|
||||
{
|
||||
_logger.Info("开始 PDF 渲染,共 {PageCount} 页 | Starting PDF rendering, {PageCount} pages", pages?.Count ?? 0);
|
||||
_currentTemplate = template;
|
||||
|
||||
var memoryStream = new MemoryStream();
|
||||
|
||||
@@ -102,8 +105,27 @@ namespace XP.ReportEngine.Services
|
||||
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++)
|
||||
@@ -114,10 +136,27 @@ namespace XP.ReportEngine.Services
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
||||
// 文档关闭前回填总页数占位符 | Fill total page count placeholder before closing
|
||||
if (headerFooterHandler != null)
|
||||
{
|
||||
headerFooterHandler.WriteTotal(pdfDocument);
|
||||
}
|
||||
|
||||
// 仅关闭 Document(它会级联关闭 PdfDocument 和 PdfWriter)
|
||||
// Only close Document (it cascades to PdfDocument and PdfWriter)
|
||||
document.Close();
|
||||
@@ -178,6 +217,15 @@ namespace XP.ReportEngine.Services
|
||||
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;
|
||||
@@ -198,6 +246,10 @@ namespace XP.ReportEngine.Services
|
||||
|
||||
var paragraph = new Paragraph(content);
|
||||
|
||||
// 设置紧凑的默认段落间距 | Set compact default paragraph spacing
|
||||
paragraph.SetMarginTop(0);
|
||||
paragraph.SetMarginBottom(2f);
|
||||
|
||||
// 应用字体 | Apply font
|
||||
var font = GetFontForCurrentLanguage();
|
||||
paragraph.SetFont(font);
|
||||
@@ -224,6 +276,24 @@ namespace XP.ReportEngine.Services
|
||||
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));
|
||||
|
||||
@@ -243,6 +313,16 @@ namespace XP.ReportEngine.Services
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -419,6 +499,27 @@ namespace XP.ReportEngine.Services
|
||||
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)
|
||||
@@ -596,6 +697,25 @@ namespace XP.ReportEngine.Services
|
||||
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));
|
||||
@@ -605,6 +725,16 @@ namespace XP.ReportEngine.Services
|
||||
}
|
||||
}
|
||||
|
||||
// 应用样式中的边距 | 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);
|
||||
}
|
||||
|
||||
@@ -647,6 +777,555 @@ namespace XP.ReportEngine.Services
|
||||
document.Add(lineSeparator);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 渲染空白间距元素 | Render spacer element
|
||||
/// 通过 Size[1](高度,mm)控制垂直空白大小
|
||||
/// Controls vertical whitespace via Size[1] (height in mm)
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 渲染强制分页元素 | Render forced page break element
|
||||
/// </summary>
|
||||
private void RenderPageBreakElement(Document document)
|
||||
{
|
||||
document.Add(new AreaBreak(AreaBreakType.NEXT_PAGE));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 渲染行容器元素(水平布局)| Render row container element (horizontal layout)
|
||||
/// 使用无边框表格实现子元素的水平排列,支持 left/center/right 对齐
|
||||
/// Uses borderless table to arrange child elements horizontally, supports left/center/right alignment
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将单个子元素渲染到单元格中 | Render a single child element into a cell
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从当前模板中解析样式定义 | Resolve style definition from current template
|
||||
/// </summary>
|
||||
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)
|
||||
|
||||
/// <summary>
|
||||
/// 页眉页脚事件处理器 | Header/Footer event handler
|
||||
/// 在 END_PAGE 事件中绘制页眉页脚,使用 PdfFormXObject 占位符实现总页数回填
|
||||
/// Draws header/footer in END_PAGE event, uses PdfFormXObject placeholder for total page count
|
||||
/// </summary>
|
||||
private class HeaderFooterEventHandler : iText.Kernel.Events.IEventHandler
|
||||
{
|
||||
private readonly ITextPdfRenderer _renderer;
|
||||
private readonly ReportTemplate _template;
|
||||
private readonly List<LayoutPage> _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<LayoutPage> 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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 绘制页眉 | Draw header
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 绘制页脚 | Draw footer
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 文档关闭前回填总页数到所有占位符 | Write total page count to all placeholders before document close
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从模板中查找已绑定的图像数据 | Find bound image data from template
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 递归搜索元素及其子元素中的图像数据 | Recursively search for image data in element and its children
|
||||
/// </summary>
|
||||
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
|
||||
|
||||
@@ -2,29 +2,71 @@
|
||||
"document": {
|
||||
"pageSize": "A4",
|
||||
"orientation": "Portrait",
|
||||
"margins": { "top": 20, "bottom": 20, "left": 20, "right": 20 }
|
||||
"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": "image", "dataKey": "companyLogo", "position": [0, 0], "size": [30, 15], "positioning": "absolute" },
|
||||
{ "type": "text", "content": "${CompanyName}", "style": "body", "position": [32, 3], "positioning": "absolute" },
|
||||
{ "type": "text", "content": "${loc:Report_Title}", "style": "title", "position": [60, 40], "positioning": "absolute" },
|
||||
{ "type": "divider", "position": [0, 60], "size": [170, 1], "positioning": "absolute" },
|
||||
{ "type": "text", "content": "${loc:Report_Id}:${metadata.reportId}", "style": "body", "position": [0, 75], "positioning": "absolute" },
|
||||
{ "type": "text", "content": "${loc:Report_Date}:${formatDate(metadata.inspectionDate)}", "style": "body", "position": [0, 85], "positioning": "absolute" },
|
||||
{ "type": "text", "content": "${loc:Report_Sample}:${metadata.sampleName}", "style": "body", "position": [0, 95], "positioning": "absolute" },
|
||||
{ "type": "text", "content": "${loc:Report_Operator}:${metadata.operatorName}", "style": "body", "position": [0, 105], "positioning": "absolute" },
|
||||
{ "type": "text", "content": "${loc:Report_Description}:${metadata.description}", "style": "body", "position": [0, 115], "positioning": "absolute" },
|
||||
{ "type": "divider", "position": [0, 130], "size": [170, 1], "positioning": "absolute" },
|
||||
{ "type": "text", "content": "${loc:Report_Summary}", "style": "heading", "position": [0, 140], "positioning": "absolute" },
|
||||
{
|
||||
"type": "table", "dataKey": "summaryTable", "position": [0, 150], "size": [170, 60], "positioning": "absolute",
|
||||
"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" },
|
||||
{ "header": "${loc:Field_Status}", "field": "status", "width": 30, "align": "center" }
|
||||
{ "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" } }
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -33,35 +75,63 @@
|
||||
"type": "metricData",
|
||||
"elements": [
|
||||
{ "type": "text", "content": "${loc:Page_MetricData}", "style": "heading", "positioning": "flow" },
|
||||
{ "type": "divider", "size": [170, 1], "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Measurement_Type}:${measurementType}", "style": "body", "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Field_Point1}:${point1}", "style": "body", "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Field_Point2}:${point2}", "style": "body", "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Measurement_Distance}:${actualDistance} ${unit}", "style": "body", "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Measurement_Angle}:${angle}°", "style": "body", "positioning": "flow" },
|
||||
{ "type": "image", "dataKey": "lineMeasurementImage", "size": [150, 100], "border": true, "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": "divider", "size": [170, 1], "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Bga_Count}:${bgaCount}", "style": "body", "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Measurement_VoidRate}:${formatPercent(voidRate)}", "style": "body", "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Measurement_FillRate}:${formatPercent(fillRate)}", "style": "body", "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Bga_TotalArea}:${formatNumber(totalBgaArea, 2)}", "style": "body", "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Bga_TotalVoidArea}:${formatNumber(totalVoidArea, 2)}", "style": "body", "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Bga_VoidLimit}:${formatPercent(voidLimit)}", "style": "body", "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Table_Classification}:${classification}", "style": "bodyBold", "positioning": "flow" },
|
||||
{ "type": "image", "dataKey": "bgaInspectionImage", "size": [150, 100], "border": true, "positioning": "flow" },
|
||||
{
|
||||
"type": "table", "dataKey": "bgaBallsTable", "positioning": "flow", "size": [170, 0],
|
||||
"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" }
|
||||
{ "header": "${loc:Table_Classification}", "field": "classification", "width": 35, "align": "center", "colorRules": { "Pass": "#008000", "Fail": "#FF0000" } }
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -70,17 +140,31 @@
|
||||
"type": "voidInspection",
|
||||
"elements": [
|
||||
{ "type": "text", "content": "${loc:Page_VoidInspection}", "style": "heading", "positioning": "flow" },
|
||||
{ "type": "divider", "size": [170, 1], "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Void_RoiArea}:${formatNumber(roiArea, 2)}", "style": "body", "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Void_TotalArea}:${formatNumber(totalVoidArea, 2)}", "style": "body", "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Measurement_VoidRate}:${formatPercent(voidRate)}", "style": "body", "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Void_Limit}:${formatPercent(voidLimit)}", "style": "body", "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Void_Count}:${voidCount}", "style": "body", "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Void_MaxArea}:${formatNumber(maxVoidArea, 2)}", "style": "body", "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Table_Classification}:${classification}", "style": "bodyBold", "positioning": "flow" },
|
||||
{ "type": "image", "dataKey": "voidInspectionImage", "size": [150, 100], "border": true, "positioning": "flow" },
|
||||
{
|
||||
"type": "table", "dataKey": "voidsTable", "positioning": "flow", "size": [170, 0],
|
||||
"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" },
|
||||
@@ -95,23 +179,44 @@
|
||||
"type": "viaFillInspection",
|
||||
"elements": [
|
||||
{ "type": "text", "content": "${loc:Page_ViaFillInspection}", "style": "heading", "positioning": "flow" },
|
||||
{ "type": "divider", "size": [170, 1], "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Fill_Rate}:${formatPercent(fillRate)}", "style": "body", "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Measurement_VoidRate}:${formatPercent(voidRate)}", "style": "body", "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Fill_FullDistance}:${formatNumber(fullDistance, 2)}", "style": "body", "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Fill_FillDistance}:${formatNumber(fillDistance, 2)}", "style": "body", "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Fill_THTLimit}:${formatPercent(thtLimit)}", "style": "body", "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Table_Classification}:${classification}", "style": "bodyBold", "positioning": "flow" },
|
||||
{ "type": "image", "dataKey": "viaFillImage", "size": [150, 100], "border": true, "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": 24, "bold": true, "italic": false, "color": "#1a1a1a", "align": "center" },
|
||||
"heading": { "font": "auto", "size": 16, "bold": true, "italic": false, "color": "#333333", "align": "left" },
|
||||
"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" }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,7 +216,7 @@ namespace XP.ReportEngine.ViewModels
|
||||
var defaultFileName = System.IO.Path.GetFileName(defaultOutputPath);
|
||||
|
||||
var result = MessageBox.Show(
|
||||
$"是否将报告输出到默认位置?\n\n{defaultOutputPath}",
|
||||
$"是否将报告输出到默认位置?\r\n{defaultOutputPath}",
|
||||
"输出位置确认",
|
||||
MessageBoxButton.YesNoCancel,
|
||||
MessageBoxImage.Question);
|
||||
@@ -317,6 +317,50 @@ namespace XP.ReportEngine.ViewModels
|
||||
context.Properties["CompanyName"] = _reportConfig.CompanyName;
|
||||
}
|
||||
|
||||
// 注入软件名称到上下文属性 | Inject software name into context properties
|
||||
if (!string.IsNullOrEmpty(_reportConfig.SoftwareName))
|
||||
{
|
||||
context.Properties["SoftwareName"] = _reportConfig.SoftwareName;
|
||||
}
|
||||
|
||||
// 注入软件 Logo 图像数据 | Inject software logo image data
|
||||
if (!string.IsNullOrEmpty(_reportConfig.SoftwareLogo))
|
||||
{
|
||||
var softwareLogoPath = System.IO.Path.IsPathRooted(_reportConfig.SoftwareLogo)
|
||||
? _reportConfig.SoftwareLogo
|
||||
: System.IO.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 workpiece overview image
|
||||
var workpieceImagePath = System.IO.Path.Combine(MockImageDirectory, "OverView.png");
|
||||
if (File.Exists(workpieceImagePath))
|
||||
{
|
||||
context.Images["workpieceImage"] = new ImageData
|
||||
{
|
||||
SourceType = ImageSourceType.FilePath,
|
||||
FilePath = workpieceImagePath
|
||||
};
|
||||
_logger.Info("工件整体图片已加载:{Path} | Workpiece image loaded: {Path}", workpieceImagePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Warn("工件整体图片不存在:{Path} | Workpiece image not found: {Path}", workpieceImagePath);
|
||||
}
|
||||
|
||||
// 注入首页汇总表数据 | Inject homepage summary table data
|
||||
context.Properties["summaryTable"] = CreateSummaryTableData(context);
|
||||
|
||||
@@ -500,6 +544,37 @@ namespace XP.ReportEngine.ViewModels
|
||||
/// </summary>
|
||||
private ProcessorOutput CreateBgaVoidRateOutput()
|
||||
{
|
||||
// 生成 64 组 BGA 球模拟数据 | Generate 64 BGA ball mock data
|
||||
var bgaBalls = new List<Dictionary<string, object>>();
|
||||
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<string, object>
|
||||
{
|
||||
["Index"] = i,
|
||||
["VoidRate"] = voidRate,
|
||||
["Area"] = area,
|
||||
["Classification"] = classification
|
||||
});
|
||||
}
|
||||
|
||||
return new ProcessorOutput
|
||||
{
|
||||
ProcessorType = "BgaVoidRateProcessor",
|
||||
@@ -512,19 +587,7 @@ namespace XP.ReportEngine.ViewModels
|
||||
["TotalVoidArea"] = 350.2,
|
||||
["Classification"] = "Pass",
|
||||
["VoidLimit"] = 0.05,
|
||||
["BgaBalls"] = new List<Dictionary<string, object>>
|
||||
{
|
||||
new() { ["Index"] = 1, ["VoidRate"] = 0.012, ["Area"] = 195.3, ["Classification"] = "Pass" },
|
||||
new() { ["Index"] = 2, ["VoidRate"] = 0.035, ["Area"] = 198.1, ["Classification"] = "Pass" },
|
||||
new() { ["Index"] = 3, ["VoidRate"] = 0.008, ["Area"] = 192.7, ["Classification"] = "Pass" },
|
||||
new() { ["Index"] = 4, ["VoidRate"] = 0.042, ["Area"] = 201.0, ["Classification"] = "Pass" },
|
||||
new() { ["Index"] = 5, ["VoidRate"] = 0.015, ["Area"] = 196.5, ["Classification"] = "Pass" },
|
||||
new() { ["Index"] = 6, ["VoidRate"] = 0.028, ["Area"] = 199.8, ["Classification"] = "Pass" },
|
||||
new() { ["Index"] = 7, ["VoidRate"] = 0.005, ["Area"] = 194.2, ["Classification"] = "Pass" },
|
||||
new() { ["Index"] = 8, ["VoidRate"] = 0.048, ["Area"] = 200.5, ["Classification"] = "Pass" },
|
||||
new() { ["Index"] = 9, ["VoidRate"] = 0.019, ["Area"] = 197.0, ["Classification"] = "Pass" },
|
||||
new() { ["Index"] = 10, ["VoidRate"] = 0.031, ["Area"] = 196.1, ["Classification"] = "Pass" }
|
||||
}
|
||||
["BgaBalls"] = bgaBalls
|
||||
},
|
||||
AnnotatedImage = LoadMockImage("BGA.png")
|
||||
};
|
||||
@@ -646,7 +709,7 @@ namespace XP.ReportEngine.ViewModels
|
||||
};
|
||||
|
||||
var classification = string.IsNullOrEmpty(group.Classification) ? "N/A" : group.Classification;
|
||||
var status = classification == "Pass" ? "✓ 合格" : classification == "Fail" ? "✗ 不合格" : "—";
|
||||
var status = classification == "Pass" ? "[PASS] 合格" : classification == "Fail" ? "[FAIL] 不合格" : "—";
|
||||
|
||||
rows.Add(new Dictionary<string, object>
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user