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 } }