PDF分割器
- Unity-PDF分割器
- 前言
- 核心思路
- 解决过程
- 一、Unity安装iTextSharp
- 二、运行时计算将要生成文件的大小
- 三、分割核心代码
- 四、使用StandaloneFileBrowser
- 五、其他的一些脚本
- 六、游戏界面主体的构建
- MainWindow
- WarningPanel & FinishPanel
- By-Round Moon
Unity-PDF分割器
PDFSplitter 资源包下载链接【CSDN】免费
前言
最近有这么一个需求,PDF太大了,需要拆分成多份,要求每份的PDF不大于10MB。刚开始,我认为这应该很简单,但是足足耗费了一天的时间去解决该问题。
核心思路
首先是核心部分当然是PDF如何进行分割了,不仅仅要分割,还要统计分割的大小。然后我还不想伤硬盘,直接写在硬盘上判断文件是否超过了大小,然后回溯。所以我只能在运行的时候检测文件大小。核心部分的难度基本如上所述。下面来讲解一下我是如何解决这个问题的吧。还有一个最难的问题,那就是如何不用UnityEditor库来打开FilePanel
又叫FileDialog
。
解决过程
一、Unity安装iTextSharp
首先在Visual Studio Community中安装.NET 桌面开发,用来下载需要的dll。当然也可以直接在Unity中打开VS后下载,我这其实是脱裤子放屁,但是我想保持Unity的干净,不想事后卸载删除,所以我采用了这个方法。
安装完成后创建一个控制台应用。
将框架选为.NET Framework 2.0。
打开后,在 工具》NuGet包管理器》管理解决方案的NuGet程序包。在浏览中搜索iTextSharp
并安装。安装完成后,在.sln
文件的同路径下有一个packages包,把里面BouncyCastle.1.8.9
和iTextSharp.5.5.13.3
的lib文件夹下的.dll
文件拷贝到Unity项目的Plugins(自己创建)文件夹中。
二、运行时计算将要生成文件的大小
明确已知,没有提供方法来确定生成后的大小。但是我们可以先在MemoryStream
中存储一下,并获取MemoryStream
空间所消耗的大小。
下面给出示例代码。
long GetMemorySize(string inputFilePath)
{
PdfReader reader = new PdfReader(inputFilePath);
Document document = new Document();
MemoryStream memoryStream = new MemoryStream();
PdfCopy copy = new PdfCopy(document, memoryStream);
document.Open();
copy.AddPage(copy.GetImportedPage(reader, 1));//这里页码是从1到最后的,不是常规的从0开始。
long memorySize = memoryStream.Length;
reader.Close();
document.Close();
memoryStream.Close();
return memorySize;
}
三、分割核心代码
这份脚本为PDFSplitter
。将来挂在GameController
空物体上。
using UnityEngine;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;
using UnityEngine.UI;
using System.Collections.Concurrent;
using System.Collections;
public class PDFSplitter : MonoBehaviour
{
private string inputFilePath;
private string outputDirectory;
private long maxFileSizeMB = 10 * 1024 * 1024;
private PdfReader reader;
public InputField from;
public InputField save;
public Slider slider;
private ConcurrentQueue<float> resultQueue = new ConcurrentQueue<float>();
public void Splitter()
{
System.Threading.Thread thread = new System.Threading.Thread(Run);
thread.Start();
StartCoroutine(updateSlider());
}
IEnumerator updateSlider()
{
float res;
while (true)
{
while (!resultQueue.TryDequeue(out res)) ;
if (res < 0)
{
slider.value = 1.0f;
GetComponent<GameController>().showFinishPanel = true;
break;
}
slider.value = res;
yield return null;
}
}
public void Run()
{
int cnt = 1;
int pre = 1;
float rate;
inputFilePath = from.text;
outputDirectory = save.text;
reader = new PdfReader(inputFilePath);
PdfReader.unethicalreading = true;
Document document = null;
MemoryStream memoryStream = null;
PdfCopy copy = null;
int pageCount = reader.NumberOfPages;
for (int i = 1; i <= pageCount; i++)
{
if (document == null)
{
document = new Document();
memoryStream = new MemoryStream();
copy = new PdfCopy(document, memoryStream);
document.Open();
copy.AddPage(copy.GetImportedPage(reader, i));
}
else
{
copy.AddPage(copy.GetImportedPage(reader, i));
if (memoryStream.Length > maxFileSizeMB)
{
PDF_Writer(pre, i - 1, cnt++.ToString("00"));
document.Close();
memoryStream.Close();
document = null;
memoryStream = null;
copy = null;
pre = i--;
}
}
rate = 1.0f * i / pageCount;
resultQueue.Enqueue(rate);
}
document.Close();
PDF_Writer(pre, pageCount, cnt.ToString("00"));
reader.Close();
resultQueue.Enqueue(-1.0f);
}
void PDF_Writer(int start, int end, string name)
{
Document document = new Document();
PdfCopy copy = new PdfCopy(document, new FileStream(outputDirectory + $"//{name}.pdf", FileMode.Create));
document.Open();
for (int i = start; i <= end; i++)
copy.AddPage(copy.GetImportedPage(reader, i));
document.Close();
}
}
四、使用StandaloneFileBrowser
首先在Github链接中下载StandaloneFileBrowser。
之后,就将下载的Packages直接拖进项目中导入。
用这个包之后为了能够打包成功我们需要将Unity的.Net框架改为 .Net 4.x
我们这里只需要使用StandaloneFileBrowser.OpenFilePanel
和StandaloneFileBrowser.OpenFolderPanel
可能导入时候会出一些莫名其妙的错误,不用在意,只要能编译正常用,打包正常打出去,就可以。奇怪的是打包成功之后这些错误莫名其妙的消失掉了。
这份脚本为PathBar
。将来挂在涉及路径选择的自定义物体上。
using UnityEngine;
using UnityEngine.UI;
using SFB;
public class PathBar : MonoBehaviour
{
[HideInInspector]
public string path;
public InputField info;
public void SelectFilePath()
{
var extensions = new[] { new ExtensionFilter("PDF Files", "pdf"), new ExtensionFilter("All Files", "*") };
path = StandaloneFileBrowser.OpenFilePanel("Open PDF File", "", extensions, false)[0];
info.text = path;
}
public void SelectFolderPath()
{
path = StandaloneFileBrowser.OpenFolderPanel("Select save Folder", "", false)[0];
info.text = path;
}
}
五、其他的一些脚本
这份脚本为GameController
。将来挂在GameController
空物体上。
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
using System.IO;
public class GameController : MonoBehaviour
{
public GameObject FinishPanel;
public GameObject WarningPanel;
[HideInInspector]
public bool showFinishPanel;
public InputField from;
public InputField save;
public void Run()
{
if (!File.Exists(from.text) || !Directory.Exists(save.text) || Path.GetExtension(from.text).ToLower() != ".pdf")
{
WarningPanel.GetComponent<PanelController>().Open();
return;
}
showFinishPanel = false;
GetComponent<PDFSplitter>().Splitter();
StartCoroutine(ShowFinishPanel());
}
IEnumerator ShowFinishPanel()
{
while (!showFinishPanel)
yield return null;
FinishPanel.GetComponent<PanelController>().Open();
}
}
这份脚本为PanelController
。将来挂在一些用来提示的物体上。
using UnityEngine;
public class PanelController : MonoBehaviour
{
public void Close()
{
gameObject.SetActive(false);
}
public void Open()
{
gameObject.SetActive(true);
}
}
六、游戏界面主体的构建
MainWindow
界面如下,很好拆分上面的我称之为PathBar,用来选择路径。
中间是Button,用来执行分割操作。
最底下是进度条,可以参考B站视频 BV1iW411D78W
大概在55分钟左右会有讲解。我这里多做了一步操作,把slider的图片全部设置成了None。
下面看一下我的目录结构
WarningPanel & FinishPanel
下面看一下我的目录结构
PDFSplitter 资源包下载链接【CSDN】免费