397 lines
14 KiB
C#
397 lines
14 KiB
C#
using System;
|
||
using System.Collections;
|
||
using System.Collections.Generic;
|
||
using System.IO;
|
||
using UnityEngine;
|
||
using UnityEngine.UI;
|
||
using SK.Framework;
|
||
using System.Linq;
|
||
|
||
#if UNITY_EDITOR
|
||
using UnityEditor;
|
||
#endif
|
||
|
||
// 注意:使用此功能前需要安装iTextSharp库
|
||
// 可以通过NuGet包管理器安装iTextSharp 5.5.13.3 或更高版本
|
||
using iTextSharp.text;
|
||
using iTextSharp.text.pdf;
|
||
using UnityEngine.Networking;
|
||
|
||
/// <summary>
|
||
/// PDF导出管理器
|
||
/// </summary>
|
||
public class PdfExportManager : MonoSingleton<PdfExportManager>
|
||
{
|
||
[Header("PDF导出设置")]
|
||
public float margin = 50f; // 页面边距
|
||
public float fontSize = 12f; // 正文字体大小
|
||
public float titleFontSize = 18f; // 标题字体大小
|
||
public float headerFooterFontSize = 10f; // 页眉页脚字体大小
|
||
|
||
// 存储已选择的附件文件路径列表
|
||
private List<string> attachedFiles = new List<string>();
|
||
|
||
/// <summary>
|
||
/// 添加附件
|
||
/// </summary>
|
||
/// <param name="filePath">附件文件路径</param>
|
||
public void AddAttachment(string filePath)
|
||
{
|
||
if (!string.IsNullOrEmpty(filePath) && File.Exists(filePath))
|
||
{
|
||
if (!attachedFiles.Contains(filePath))
|
||
{
|
||
attachedFiles.Add(filePath);
|
||
Debug.Log($"附件已添加: {filePath}");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Debug.LogWarning($"文件不存在: {filePath}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 移除附件
|
||
/// </summary>
|
||
/// <param name="filePath">要移除的附件文件路径</param>
|
||
public void RemoveAttachment(string filePath)
|
||
{
|
||
attachedFiles.Remove(filePath);
|
||
Debug.Log($"附件已移除: {filePath}");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清空所有附件
|
||
/// </summary>
|
||
public void ClearAttachments()
|
||
{
|
||
attachedFiles.Clear();
|
||
Debug.Log("所有附件已清空");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取附件列表
|
||
/// </summary>
|
||
/// <returns>附件文件路径列表</returns>
|
||
public List<string> GetAttachments()
|
||
{
|
||
return new List<string>(attachedFiles);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 导出PDF文档
|
||
/// </summary>
|
||
/// <param name="title">PDF标题</param>
|
||
/// <param name="content">PDF内容</param>
|
||
/// <param name="outputPath">输出路径</param>
|
||
/// <param name="onComplete">完成回调</param>
|
||
public void ExportPdf(string title, string content, string outputPath, Action<bool, string> onComplete = null)
|
||
{
|
||
StartCoroutine(ExportPdfCoroutine(title, content, outputPath, onComplete));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 导出PDF协程
|
||
/// </summary>
|
||
private IEnumerator ExportPdfCoroutine(string title, string content, string outputPath, Action<bool, string> onComplete)
|
||
{
|
||
yield return new WaitForEndOfFrame(); // 确保在帧结束时执行
|
||
|
||
try
|
||
{
|
||
bool success = CreatePdfDocument(title, content, outputPath);
|
||
string message = success ? $"PDF已保存至: {outputPath}" : "PDF导出失败";
|
||
|
||
onComplete?.Invoke(success, message);
|
||
}
|
||
catch (System.Exception ex)
|
||
{
|
||
Debug.LogError($"PDF导出过程中发生错误: {ex.Message}\n{ex.StackTrace}");
|
||
onComplete?.Invoke(false, $"PDF导出错误: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 创建PDF文档
|
||
/// </summary>
|
||
/// <param name="title">PDF标题</param>
|
||
/// <param name="content">PDF内容</param>
|
||
/// <param name="outputPath">输出路径</param>
|
||
/// <returns>是否成功创建</returns>
|
||
private bool CreatePdfDocument(string title, string content, string outputPath)
|
||
{
|
||
try
|
||
{
|
||
// 确保输出目录存在
|
||
string directory = Path.GetDirectoryName(outputPath);
|
||
if (!Directory.Exists(directory))
|
||
{
|
||
Directory.CreateDirectory(directory);
|
||
}
|
||
|
||
// 创建文档
|
||
Document document = new Document(PageSize.A4, margin, margin, margin, margin);
|
||
|
||
// 创建PDF writer
|
||
PdfWriter writer = PdfWriter.GetInstance(document, new FileStream(outputPath, FileMode.Create));
|
||
|
||
// 打开文档
|
||
document.Open();
|
||
|
||
// 添加标题
|
||
if (!string.IsNullOrEmpty(title))
|
||
{
|
||
iTextSharp.text.Font titleFont = SetFont(16);
|
||
if (titleFont == null)
|
||
{
|
||
titleFont = new iTextSharp.text.Font(iTextSharp.text.Font.FontFamily.HELVETICA, 16, iTextSharp.text.Font.BOLD);
|
||
}
|
||
Paragraph titleParagraph = new Paragraph(title, titleFont)
|
||
{
|
||
Alignment = Element.ALIGN_CENTER,
|
||
SpacingAfter = 20f
|
||
};
|
||
document.Add(titleParagraph);
|
||
}
|
||
|
||
// 添加当前时间戳
|
||
iTextSharp.text.Font timestampFont = SetFont(10);
|
||
if (timestampFont == null)
|
||
{
|
||
timestampFont = new iTextSharp.text.Font(iTextSharp.text.Font.FontFamily.HELVETICA, 10, iTextSharp.text.Font.NORMAL);
|
||
}
|
||
Paragraph timestamp = new Paragraph($"生成时间: {DateTime.Now:yyyy-MM-dd HH:mm:ss}", timestampFont)
|
||
{
|
||
Alignment = Element.ALIGN_RIGHT,
|
||
SpacingAfter = 20f
|
||
};
|
||
document.Add(timestamp);
|
||
|
||
// 添加内容
|
||
if (!string.IsNullOrEmpty(content))
|
||
{
|
||
iTextSharp.text.Font contentFont = SetFont();
|
||
if (contentFont == null)
|
||
{
|
||
contentFont = new iTextSharp.text.Font(iTextSharp.text.Font.FontFamily.HELVETICA, fontSize, iTextSharp.text.Font.NORMAL);
|
||
}
|
||
// 将换行符转换为段落
|
||
string[] paragraphs = content.Split(new[] { "\n", "\r\n" }, StringSplitOptions.None);
|
||
|
||
foreach (string paragraphText in paragraphs)
|
||
{
|
||
if (!string.IsNullOrEmpty(paragraphText.Trim()))
|
||
{
|
||
Paragraph paragraph = new Paragraph(paragraphText, contentFont)
|
||
{
|
||
SpacingAfter = 10f
|
||
};
|
||
document.Add(paragraph);
|
||
}
|
||
else
|
||
{
|
||
// 添加空行
|
||
Paragraph emptyLine = new Paragraph(" ", contentFont);
|
||
document.Add(emptyLine);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 添加附件信息(核心修改部分)
|
||
if (attachedFiles.Count > 0)
|
||
{
|
||
iTextSharp.text.Font contentFont = SetFont();
|
||
if (contentFont == null)
|
||
{
|
||
contentFont = new iTextSharp.text.Font(iTextSharp.text.Font.FontFamily.HELVETICA, fontSize, iTextSharp.text.Font.NORMAL);
|
||
}
|
||
document.Add(new Paragraph(" ", contentFont)); // 空行分隔
|
||
|
||
iTextSharp.text.Font sectionTitleFont = SetFont(14);
|
||
if (sectionTitleFont == null)
|
||
{
|
||
sectionTitleFont = new iTextSharp.text.Font(iTextSharp.text.Font.FontFamily.HELVETICA, 14, iTextSharp.text.Font.BOLD);
|
||
}
|
||
Paragraph attachmentsTitle = new Paragraph("附件列表:", sectionTitleFont)
|
||
{
|
||
SpacingBefore = 20f,
|
||
SpacingAfter = 10f
|
||
};
|
||
document.Add(attachmentsTitle);
|
||
|
||
iTextSharp.text.Font attachmentFont = SetFont(12);
|
||
if (attachmentFont == null)
|
||
{
|
||
attachmentFont = new iTextSharp.text.Font(iTextSharp.text.Font.FontFamily.HELVETICA, 12, iTextSharp.text.Font.NORMAL);
|
||
}
|
||
|
||
for (int i = 0; i < attachedFiles.Count; i++)
|
||
{
|
||
string fullPath = attachedFiles[i];
|
||
string fileName = Path.GetFileName(fullPath);
|
||
string fileSize = FormatFileSize(new FileInfo(fullPath).Length);
|
||
|
||
// ======================
|
||
// 纯 Unity 中文路径编码
|
||
// ======================
|
||
string unifiedPath = fullPath.Replace('\\', '/');
|
||
string encodedPath = UnityWebRequest.EscapeURL(unifiedPath);
|
||
encodedPath = encodedPath.Replace("%2F", "/").Replace("%3A", ":");
|
||
string fileUrl = "file:///" + encodedPath;
|
||
|
||
// ======================
|
||
// 蓝色下划线链接字体
|
||
// ======================
|
||
iTextSharp.text.Font linkFont = new iTextSharp.text.Font(attachmentFont.BaseFont, 12, iTextSharp.text.Font.UNDERLINE, BaseColor.BLUE);
|
||
Anchor fileLink = new Anchor(fileName, linkFont);
|
||
fileLink.Reference = fileUrl; // 使用编码后的链接
|
||
|
||
Paragraph attachmentItem = new Paragraph($"{i + 1}. ", attachmentFont)
|
||
{
|
||
SpacingAfter = 5f,
|
||
IndentationLeft = 20f
|
||
};
|
||
attachmentItem.Add(fileLink);
|
||
attachmentItem.Add(new Chunk($" ({fileSize})", attachmentFont));
|
||
document.Add(attachmentItem);
|
||
}
|
||
}
|
||
|
||
// 添加页码
|
||
PdfPageEventHelper pageEvent = new PdfPageEventHandler();
|
||
writer.PageEvent = pageEvent;
|
||
|
||
// 关闭文档
|
||
document.Close();
|
||
|
||
Debug.Log($"PDF文档已成功创建: {outputPath}");
|
||
return true;
|
||
}
|
||
catch (System.Exception ex)
|
||
{
|
||
Debug.LogError($"创建PDF文档时发生错误: {ex.Message}\n{ex.StackTrace}");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
public iTextSharp.text.Font SetFont(float _fontsize = 12)
|
||
{
|
||
try
|
||
{
|
||
// 创建 BaseFont(支持中文)
|
||
string fontPath = Path.Combine(Application.streamingAssetsPath, "songTi.ttf");
|
||
if (File.Exists(fontPath))
|
||
{
|
||
BaseFont baseFont = BaseFont.CreateFont(
|
||
fontPath,
|
||
BaseFont.IDENTITY_H,
|
||
BaseFont.EMBEDDED
|
||
);
|
||
|
||
// 创建 iText 字体
|
||
iTextSharp.text.Font chineseFont = new iTextSharp.text.Font(baseFont, _fontsize, iTextSharp.text.Font.NORMAL);
|
||
return chineseFont;
|
||
}
|
||
else
|
||
{
|
||
// 如果指定字体不存在,使用默认字体
|
||
return new iTextSharp.text.Font(iTextSharp.text.Font.FontFamily.HELVETICA, _fontsize, iTextSharp.text.Font.NORMAL);
|
||
}
|
||
}
|
||
catch
|
||
{
|
||
// 如果出现任何错误,返回默认字体
|
||
return new iTextSharp.text.Font(iTextSharp.text.Font.FontFamily.HELVETICA, _fontsize, iTextSharp.text.Font.NORMAL);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 选择并添加附件
|
||
/// </summary>
|
||
/// <param name="onComplete">选择完成回调</param>
|
||
public void SelectAndAddAttachment(Action<bool, string> onComplete = null)
|
||
{
|
||
FileUploadManager.Instance.SelectAndUploadFile((filePath) =>
|
||
{
|
||
if (!string.IsNullOrEmpty(filePath))
|
||
{
|
||
AddAttachment(filePath);
|
||
onComplete?.Invoke(true, $"附件已添加: {Path.GetFileName(filePath)}");
|
||
}
|
||
else
|
||
{
|
||
onComplete?.Invoke(false, "未选择文件");
|
||
}
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 格式化文件大小
|
||
/// </summary>
|
||
/// <param name="bytes">字节数</param>
|
||
/// <returns>格式化的文件大小字符串</returns>
|
||
private string FormatFileSize(long bytes)
|
||
{
|
||
string[] sizes = { "B", "KB", "MB", "GB" };
|
||
int order = 0;
|
||
double len = bytes;
|
||
|
||
while (len >= 1024 && order < sizes.Length - 1)
|
||
{
|
||
order++;
|
||
len = len / 1024;
|
||
}
|
||
|
||
return $"{len:F2} {sizes[order]}";
|
||
}
|
||
|
||
/// <summary>
|
||
/// 选择保存路径并导出PDF
|
||
/// </summary>
|
||
/// <param name="title">PDF标题</param>
|
||
/// <param name="content">PDF内容</param>
|
||
/// <param name="onComplete">完成回调</param>
|
||
public void SelectAndExportPdf(string title, string content, Action<bool, string> onComplete = null)
|
||
{
|
||
#if UNITY_EDITOR || UNITY_STANDALONE_WIN
|
||
string defaultFileName = $"{title}_{DateTime.Now:yyyyMMdd_HHmmss}.pdf";
|
||
string outputPath = EditorUtility.SaveFilePanel("保存PDF文件", "", defaultFileName, "pdf");
|
||
|
||
if (!string.IsNullOrEmpty(outputPath))
|
||
{
|
||
ExportPdf(title, content, outputPath, onComplete);
|
||
}
|
||
else
|
||
{
|
||
onComplete?.Invoke(false, "未选择保存路径");
|
||
}
|
||
#else
|
||
// 对于其他平台,使用默认路径或应用持久化数据路径
|
||
string fileName = $"{title}_{DateTime.Now:yyyyMMdd_HHmmss}.pdf";
|
||
string outputPath = Path.Combine(Application.persistentDataPath, fileName);
|
||
ExportPdf(title, content, outputPath, onComplete);
|
||
#endif
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// PDF页面事件处理器(用于添加页码)
|
||
/// </summary>
|
||
public class PdfPageEventHandler : PdfPageEventHelper
|
||
{
|
||
public override void OnEndPage(PdfWriter writer, Document document)
|
||
{
|
||
base.OnEndPage(writer, document);
|
||
|
||
PdfContentByte canvas = writer.DirectContent;
|
||
ColumnText.ShowTextAligned(
|
||
canvas,
|
||
Element.ALIGN_CENTER,
|
||
new Phrase($"第 {writer.PageNumber} 页", new iTextSharp.text.Font(iTextSharp.text.Font.FontFamily.HELVETICA, 8)),
|
||
(document.PageSize.Left + document.PageSize.Right) / 2,
|
||
document.PageSize.Bottom + 15,
|
||
0
|
||
);
|
||
}
|
||
} |