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();
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
/// PDF 内存流 | PDF memory stream
public MemoryStream Render(List pages, ReportGenerationOptions options)
{
_logger.Info("开始 PDF 渲染,共 {PageCount} 页 | Starting PDF rendering, {PageCount} pages", pages?.Count ?? 0);
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;
document.SetMargins(marginTop, marginRight, marginBottom, marginLeft);
if (pages != null && pages.Count > 0)
{
for (int i = 0; i < pages.Count; i++)
{
if (i > 0)
{
// 添加新页面 | Add new page
document.Add(new AreaBreak(AreaBreakType.NEXT_PAGE));
}
RenderPage(document, pages[i]);
}
}
// 仅关闭 Document(它会级联关闭 PdfDocument 和 PdfWriter)
// Only close Document (it cascades to PdfDocument and PdfWriter)
document.Close();
// 重置流位置以便后续读取 | 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;
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);
// 应用字体 | 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 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);
}
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));
}
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));
dataCell.Add(cellParagraph);
dataCell.SetBackgroundColor(rowBgColor);
dataCell.SetBorder(new SolidBorder(ColorConstants.LIGHT_GRAY, 0.5f));
table.AddCell(dataCell);
}
}
}
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);
}
#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
}
}