using UnityEngine; using System.IO; using iTextSharp.text; using iTextSharp.text.pdf; using System.Collections.Generic; using UnityEngine.UI; using PdfFont = iTextSharp.text.Font; using QFramework; using System.IO; public class SimplePDFReport : MonoBehaviour { UserProxy userProxy = DAL.Instance.Get(); [Header("UI绑定")] public Button Btn_Export; [Header("基础信息")] public string studentName = "Chiva"; private string experimentName = OperateSetting.Instance.m_CourseName; public string date = "2025-10-16"; // 可外部替换 Logo public string logoFileName = "logo.png"; // 放在 StreamingAssets 下 private ScoreInfo scorePanel; // 动态获取成绩信息 private List stepScores = new List(); private float totalScore; private string useTime; [System.Serializable] public class StepScore { public int index; public string stepName; public float score; } private void Start() { studentName = userProxy.userInfo.userName; Btn_Export.onClick.AddListener(ExportPDF); } [ContextMenu("导出成绩单 PDF")] public void ExportPDF() { scorePanel = UIKit.GetPanel().ScoreInfo; if (scorePanel == null) { Debug.LogError("❌ 未找到 ScoreInfo 面板,请确保在当前场景中存在。"); return; } totalScore = float.Parse(scorePanel.Score.text); useTime = scorePanel.UseTime.text; date = System.DateTime.Now.ToString("yyyy-MM-dd"); // 生成步骤表数据 Transform content = scorePanel.Content; stepScores.Clear(); for (int i = 0; i < content.childCount; i++) { var item = content.GetChild(i).GetComponent(); if (item != null) { stepScores.Add(new StepScore() { //index = i + 1, index = int.Parse(item.stepIdText.text), stepName = item.stepDescriptText.text, // 步骤名 score = float.Parse(item.scoreSituationText.text) // 分数 }); } } // ====== PDF导出路径 ====== string tmpSavePath = FolderBrowserHelper.GetPathFromWindowsExplorer("请选择成绩导出位置"); string savePath = Path.Combine(Application.dataPath, $"{studentName}_成绩单.pdf"); if (File.Exists(savePath)) File.Delete(savePath); Document doc = new Document(PageSize.A4, 60, 60, 80, 60); // 上边距略大,留页眉空间 PdfWriter writer = PdfWriter.GetInstance(doc, new FileStream(savePath, FileMode.Create)); // ===== 页脚和页码 ===== writer.PageEvent = new PdfPageEvents(); doc.Open(); // ====== 字体设置 ====== string fontPath = Path.Combine(Application.dataPath, "Fonts/SIMHEI.ttf"); if (!File.Exists(fontPath)) { Debug.LogError("字体文件未找到:" + fontPath); return; } BaseFont bfChinese = BaseFont.CreateFont(fontPath, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); PdfFont smallFont = new PdfFont(bfChinese, 10) { Color = new BaseColor(100, 100, 100) }; // 灰色 PdfFont normalFont = new PdfFont(bfChinese, 12); PdfFont boldFont = new PdfFont(bfChinese, 12, PdfFont.BOLD); PdfFont titleFont = new PdfFont(bfChinese, 30, PdfFont.BOLD); PdfContentByte cb = writer.DirectContent; // ===== 横线 ===== float lineY = doc.Top - 20f; cb.MoveTo(doc.Left, lineY); cb.LineTo(doc.Right, lineY); cb.Stroke(); // ===== 页眉在横线之上 ===== string headerText = "虚拟仿真实验中心"; ColumnText.ShowTextAligned( cb, Element.ALIGN_CENTER, new Phrase(headerText, smallFont), (doc.Left + doc.Right) / 2, lineY + 10, // 横线上方 10pt 0 ); // ===== LOGO 横线上方左侧 ===== string logoPath = Path.Combine(Application.streamingAssetsPath, logoFileName); // 统一去除 URL 前缀(特别是 file://) if (logoPath.StartsWith("file://")) logoPath = logoPath.Replace("file://", ""); if (!File.Exists(logoPath)) { // Unity Editor 下 StreamingAssets 在项目路径内 logoPath = Path.Combine(Application.dataPath, "StreamingAssets", logoFileName); } float logoBottomY = lineY; // 用于计算标题位置 if (File.Exists(logoPath)) { try { byte[] logoBytes = File.ReadAllBytes(logoPath); iTextSharp.text.Image logo = iTextSharp.text.Image.GetInstance(logoBytes); // 👉 只固定高度,让宽度按比例缩放 float targetHeight = 50f; // 设定统一显示宽度 float scaleFactor = targetHeight / logo.Height; // 缩放比例 logo.ScalePercent(scaleFactor * 100); // iTextSharp 用百分比 // 保证顶部距离横线 5pt float logoTopY = lineY + 5f + logo.ScaledHeight; float logoPosY = logoTopY - logo.ScaledHeight; logoBottomY = logoPosY; // 让左下角对齐页边距 logo.SetAbsolutePosition(doc.Left, logoPosY); doc.Add(logo); Debug.Log($"✅ Logo 添加成功(原始大小: {logo.Width}x{logo.Height}, 缩放比例: {scaleFactor:F2})"); } catch (System.Exception e) { Debug.LogError("❌ 读取 Logo 出错:" + e.Message); } } else { Debug.LogWarning("⚠️ 未找到 Logo 文件:" + logoPath); } // ===== 主标题(单独一行居中) ===== Paragraph title = new Paragraph("成 绩 单", titleFont); title.Alignment = Element.ALIGN_CENTER; title.SpacingBefore = 30f; title.SpacingAfter = 20f; doc.Add(title); // ===== 基本信息表格 ===== PdfPTable infoTable = new PdfPTable(4); infoTable.WidthPercentage = 100; infoTable.SpacingBefore = 10f; infoTable.SpacingAfter = 15f; infoTable.SetWidths(new float[] { 1.2f, 2f, 1.2f, 2f }); AddCell(infoTable, "姓名", boldFont, new BaseColor(235, 235, 235)); AddCell(infoTable, studentName, normalFont); AddCell(infoTable, "实验日期", boldFont, new BaseColor(235, 235, 235)); AddCell(infoTable, date, normalFont); AddCell(infoTable, "实验名称", boldFont, new BaseColor(235, 235, 235)); AddCell(infoTable, experimentName, normalFont); AddCell(infoTable, "总成绩", boldFont, new BaseColor(235, 235, 235)); AddCell(infoTable, totalScore.ToString("F1") + " 分", normalFont); AddCell(infoTable, "用时", boldFont, new BaseColor(235, 235, 235)); AddCell(infoTable, useTime, normalFont); AddCell(infoTable, "", normalFont); AddCell(infoTable, "", normalFont); doc.Add(infoTable); // ===== 操作步骤表格 ===== PdfPTable stepTable = new PdfPTable(3); stepTable.WidthPercentage = 100; stepTable.SetWidths(new float[] { 1f, 3f, 1f }); AddHeaderCell(stepTable, "序号", boldFont); AddHeaderCell(stepTable, "操作步骤", boldFont); AddHeaderCell(stepTable, "分数", boldFont); foreach (var s in stepScores) { AddCell(stepTable, s.index.ToString(), normalFont); AddCell(stepTable, s.stepName, normalFont); AddCell(stepTable, s.score.ToString("F1"), normalFont); } doc.Add(stepTable); // ===== 评语 ===== doc.Add(new Paragraph("\n")); string commentText = GenerateComment(totalScore); Paragraph comment = new Paragraph($"评语:{commentText}", normalFont); comment.SpacingBefore = 10; doc.Add(comment); doc.Close(); writer.Close(); Debug.Log($"✅ 成绩单导出成功:{savePath}"); } private string GenerateComment(float score) { if (score >= 90f) return "表现优秀,操作熟练,理解深入。"; else if (score >= 75f) return "完成良好,掌握较为扎实,可继续巩固。"; else if (score >= 60f) return "基本达标,但仍需加强理解与操作细节。"; else return "需改进,建议复习实验步骤并提高熟练度。"; } private void AddHeaderCell(PdfPTable table, string text, PdfFont font) { PdfPCell cell = new PdfPCell(new Phrase(text, font)); cell.BackgroundColor = new BaseColor(220, 220, 220); cell.HorizontalAlignment = Element.ALIGN_CENTER; cell.MinimumHeight = 25; table.AddCell(cell); } private void AddCell(PdfPTable table, string text, PdfFont font, BaseColor bg = null) { PdfPCell cell = new PdfPCell(new Phrase(text, font)); cell.HorizontalAlignment = Element.ALIGN_CENTER; cell.VerticalAlignment = Element.ALIGN_MIDDLE; cell.MinimumHeight = 22; if (bg != null) cell.BackgroundColor = bg; table.AddCell(cell); } // ===== 页脚页码事件类 ===== private class PdfPageEvents : PdfPageEventHelper { BaseFont bf; PdfFont footerFont; public PdfPageEvents() { string fontPath = Path.Combine(Application.dataPath, "Fonts/simhei.ttf"); if (File.Exists(fontPath)) { bf = BaseFont.CreateFont(fontPath, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); footerFont = new PdfFont(bf, 10); } } public override void OnEndPage(PdfWriter writer, Document document) { int pageN = writer.PageNumber; string text = "第 " + pageN + " 页 / 共 " + writer.PageNumber + " 页"; PdfContentByte cb = writer.DirectContent; // 右下页码 ColumnText.ShowTextAligned( cb, Element.ALIGN_RIGHT, new Phrase(text, footerFont), document.Right, document.Bottom - 20, 0 ); // 左下固定文字 ColumnText.ShowTextAligned( cb, Element.ALIGN_LEFT, new Phrase("虚拟仿真实验考核系统 © 2025", footerFont), document.Left, document.Bottom - 20, 0 ); } } }