本文主要讲述如何将SPL文件转换成EMF文件。
目录
一、什么是SPL文件和EMF文件
(一)SPL文件
如下图把标记处的复选框打钩,这样就可以在打印过程中把打印的文档保留下来。
保留的临时文件存储在⽬录 C:\Windows\System32\spool\PRINTERS下 。
每次打印保留两个⽂件分别以.SHD 和 .SPL 结尾,.SHD 为打印控制⽂件,.SPL 存储打印内容。
我们可以通过解析 SPL⽂件的⽅式来获得打印内容。
(二)EMF文件
EMF文件是一种图片格式,可以用画图软件打开。
EMF(增强元文件)是Windows操作系统用来打印缓存文件格式的术语。打印作业发送到打印机后,如果正在打印另一个文件,计算机将读取新文件并将其存储,通常存储在硬盘或存储器中,以便以后打印。
二、文件解析
(一)SPL格式
简单来说,SPL文件中封装了多个EMF文件,下图是一个SPL文件的二进制格式。
(二)打开SPL文件
可以用WinHex软件打开SPL文件读取其二进制数据(以十六进制显示),读取的数据如下图。
读取的二进制文件有几个要点需要注意:
(1)读取的数据是以双字为一个单位,即四个字节,对于十六进制来说,就是“00 00 01 00”为一个单位。
(2)读取的数据高位存储在低位位置,低位数据存储在高位位置,比如说第一个单位的数据“00 00 01 00”实际上“00 01 00 00”(反过来)。
(三)解析SPL文件
下图中标注部分是SPL头,封装了SPL文件的一些信息,包括EMF文件的位置,可以找到网上其他的一些资料,找到SPL头的具体内容。一些资料中说的SPL头的第二个单位的数据是EMF头的位置,可是对于一些文件来说,确实如此,而也有一些文件并非如此,具体原因不清楚,还需要找到SPL头的具体文档说明,才能知道。如下图中第二个单位是“00 00 00 7C”,指示的是数据第0000007C位置的数据“00 00 00 0C”正是EMF头的开始位置。
EMF头的开始数据是“00 00 00 0C”,下一个单位是EMF文件的大小,即“00 14 0B FC”,即EMF文件正式开始的位置加上EMF文件的大小,就是EMF文件的结尾处,这里要注意的是,EMF文件是不包含EMF头的,即EMF文件正式开始的位置是下一个单位“00 00 00 01”。
EMF文件正式开始的位置“00 00 00 84”加上EMF文件的大小“00 14 0B FC”,再减1,就是EMF文件的结尾位置“00 14 0C 85”,即下图中的“00 00 00 14”处,这样就找到了一个EMF文件,从“00 00 00 01”到“00 00 00 14”,就是一个EMF文件。
三、编程思路
我的想法比较简单,就是以字节形式遍历整个SPL文件,找到“0C 00 00 00 XX XX XX XX 01 00 00 00”,符合这个条件的一般都是EMF文件,然后从“01 00 00 00”开始,按照其中文件大小的数据,读取一整个EMF文件,然后以同样方式继续读取下一个EMF文件。
代码写的不好,比较复杂,如下:
(一)记录EMF文件的位置和大小
用两个全局变量记录EMF文件的位置和大小:
//用一个列表记录一个SPL文件中所有EMF文件的起始位置
private static List<int> _startIndexs;
//用一个列表记录一个SPL文件中所有EMF文件的数据大小
private static List<int> _emfDataCount;
(二)找到EMF文件的开始位置和大小
定义一个方法,用来找到EMF文件的开始位置和大小:
/// <summary>
/// 这个方法可以找到一个SPL文件中所有的EMF文件的开始位置和大小,并分别记录到两个列表中
/// </summary>
/// <param name="srcFilePath">需要提供SPL文件路径(包含文件名)作为参数</param>
private static void FindEmf(string srcFilePath)
{
//初始化两个用来记录EMF文件开始位置和大小的列表
_startIndexs = new List<int>();
_emfDataCount = new List<int>();
//读取SPL文件
using (FileStream fsR = new FileStream(srcFilePath, FileMode.Open, FileAccess.Read))
{
//设置一个20M的字节数组作为缓存区来记录读取的数据
byte[] buffer = new byte[20 * 1024 * 1024];
int i = fsR.Read(buffer, 0, buffer.Length);
//当SPL文件超过20M时,需要读取多遍来记录数据,whileCount用来记录读取的遍数
int whileCount = -1;
bool flag = false;
bool flagB = false;
//当i=0时,说明已经读取完SPL文件
while (i > 0)
{
whileCount++;
//遍历缓存区数组
for (int j = 0; j < i; j++)
{
if (!flag)
{
if (j <= 20 * 1024 * 1024-12)
{
if (j % 4 == 0)
{
//找到“0C 00 00 00 XX XX XX XX 01 00 00 00”
if (buffer[j] == 12 && buffer[j + 1] == 0 && buffer[j + 2] == 0 && buffer[j + 3] == 0 && buffer[j + 8] == 1 && buffer[j + 9] == 0 && buffer[j + 10] == 0 && buffer[j + 11] == 0)
{
//记录emf起始位置
_startIndexs.Add(whileCount *( 20 * 1024 * 1024) + j + 8);
//记录emf大小
_emfDataCount.Add((buffer[j + 7]) * 256 * 256 * 256 + (buffer[j + 6]) * 256 * 256 + (buffer[j + 5]) * 256 + (buffer[j + 4]));
}
}
}
else
{
//当“0C 00 00 00”在数组倒数第二个单位时,需要判断循环读取第二遍时数组第一个单位是否是“01 00 00 00”
if (j == 20 * 1024 * 1024-8)
{
if (buffer[j] == 12 && buffer[j + 1] == 0 && buffer[j + 2] == 0 && buffer[j + 3] == 0)
{
//记录emf起始位置
_startIndexs.Add(whileCount * (20 * 1024 * 1024) + j + 8);
//记录emf大小
_emfDataCount.Add((buffer[j + 7]) * 256 * 256 * 256 + (buffer[j + 6]) * 256 * 256 + (buffer[j + 5]) * 256 + (buffer[j + 4]));
flag = true;
flagB = true;
}
}
//当“0C 00 00 00”在数组倒数第一个单位时,需要判断循环读取第二遍时数组第二个单位是否是“01 00 00 00”
if (j == 20 * 1024 * 1024-4)
{
if (buffer[j] == 12 && buffer[j + 1] == 0 && buffer[j + 2] == 0 && buffer[j + 3] == 0)
{
//记录emf起始位置
_startIndexs.Add(whileCount * (20 * 1024 * 1024) + j + 8);
flag = true;
}
}
}
}
//在上一次循环中,“0C 00 00 00”在数组倒数第二个单位或者倒数第一个单位时
//需要判断此次循环读取数组第一个单位或者第二个单位是否是“01 00 00 00”
else
{
flag = false;
if (_startIndexs.Count == _emfDataCount.Count)
{
if (!(buffer[j] == 1 && buffer[j + 1] == 0 && buffer[j + 2] == 0 && buffer[j + 3] == 0))
{
_startIndexs.RemoveAt(_startIndexs.Count - 1);
_emfDataCount.RemoveAt(_emfDataCount.Count - 1);
}
}
else
{
if (flagB)
{
flagB = false;
_startIndexs.RemoveAt(_startIndexs.Count - 2);
_emfDataCount.RemoveAt(_emfDataCount.Count - 1);
}
if (!(buffer[j + 4] == 1 && buffer[j + 5] == 0 && buffer[j + 6] == 0 && buffer[j + 7] == 0))
{
_startIndexs.RemoveAt(_startIndexs.Count - 1);
}
else
{
_emfDataCount.Add((buffer[j + 3]) * 256 * 256 * 256 + (buffer[j + 2]) * 256 * 256 + (buffer[j + 1]) * 256 + (buffer[j]));
}
}
}
}
//如果SPL文件没读完,则继续读第二遍,直到读完
i = fsR.Read(buffer, 0, buffer.Length);
}
}
}
(三)将SPL文件转换成EMF文件
代码主要部分,将SPL文件转换成EMF文件
/// <summary>
/// 将一个SPL文件转换成EMF文件,一个SPL文件中可能封装多个EMF文件
/// </summary>
/// <param name="srcFilePath">参数一是SPL文件路径(包含SPL文件名)</param>
/// <param name="dstFilePath">参数二是EMF文件路径(不包含EMF文件名)</param>
public static void SplToEmf(string srcFilePath, string dstFilePath)
{
//找到当前SPL文件中所有EMF文件的开始位置和数据大小
FindEmf(srcFilePath);
//遍历记录EMF文件开始位置的列表,从而读取所有EMF文件
for (int j = 0; j < _startIndexs.Count; j++)
{
//读取SPL文件
using (FileStream fsR = new FileStream(srcFilePath, FileMode.Open, FileAccess.Read))
{
//用20M的字节数组作为缓存区读取SPL文件
byte[] buffer = new byte[20 * 1024 * 1024];
//将读取到的EMF文件数据记录下来
using (FileStream fsW = new FileStream(Path.Combine( dstFilePath,Path.GetFileNameWithoutExtension(srcFilePath)+"-"+ j.ToString()+".emf"), FileMode.Create, FileAccess.Write))
{
int i = fsR.Read(buffer, 0, buffer.Length);
int startIndex = _startIndexs[j];
int emfDataCount = _emfDataCount[j];
int X = startIndex;
//remnant记录剩余没读完的EMF数据
int remnant = emfDataCount;
//remnant剩余数据为0时读完一个EMF文件数据
while (remnant > 0)
{
//当EMF文件的起始位置在20M的缓存区内时,开始读取
//否则循环读取SPL文件,在下一个缓存中开始找开始位置
if (startIndex <= 20 * 1024 * 1024-1)
{
if (remnant > i - X)
fsW.Write(buffer, X, i - X);
else
fsW.Write(buffer, X, remnant);
remnant = remnant - (i - X);
i = fsR.Read(buffer, 0, buffer.Length);
X = 0;
}
else
{
i = fsR.Read(buffer, 0, buffer.Length);
startIndex = startIndex - 20 * 1024 * 1024;
X = 0;
}
}
}
}
}
}
代码仅供参考。
本文由本人编写,经验缺乏,尚望各位多多指正,以匡不逮。