Files
2026-10-12 16:49:14 +08:00

254 lines
6.8 KiB
C#

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;
using CLR;
namespace PDFPatcher.Processor.ContentParser;
/// <summary>
/// 将 Operation 列表重新构建为 PDF 内容流字节数组
/// </summary>
static class ContentStreamBuilder
{
static readonly Encoding Latin1 = Encoding.GetEncoding("ISO-8859-1");
static readonly byte[] __Null = Latin1.GetBytes("null"),
__True = Latin1.GetBytes("true"),
__False = Latin1.GetBytes("false"),
__BI = Latin1.GetBytes("BI "),
__ID = Latin1.GetBytes("ID"),
__EI = Latin1.GetBytes("EI");
const byte Space = (byte)' ', NewLine = (byte)'\n';
/// <summary>
/// 从操作序列构建 PDF 内容流字节
/// </summary>
public static byte[] Build(IEnumerable<Operation> operations) {
using var ms = new MemoryStream();
foreach (var op in operations) {
// 跳过孤立的 BI,由后续的 EI 处理
switch (op.Kind) {
case RenderCommandKind.BeginInlineImage:
continue;
case RenderCommandKind.EndInlineImage:
// 处理内联图像(必须包含一个 InlineImage 操作数)
if (op.Operands.Length == 1 && op.Operands[0].Type == TokenType.InlineImage) {
WriteInlineImage(ms, (InlineImageContent)op.Operands[0].Value);
continue;
}
// 降级为普通操作符输出(容错)
break;
}
// 普通操作:先输出所有操作数,后跟操作符
foreach (var token in op.Operands) {
WriteToken(ms, token);
ms.WriteByte(Space);
}
WriteOperator(ms, op.Operator);
ms.WriteByte(NewLine);
}
return ms.ToArray();
}
#region 序列化核心
private static void WriteToken(Stream stream, Token token) {
// 如果 Token 持有原始字节且不是复合类型,直接写入,避免重复计算
if (token.Buffer != null
&& !token.Type.CeqAny(TokenType.Array, TokenType.Dictionary, TokenType.InlineImage)) {
stream.Write(token.Buffer, token.Offset, token.Length);
return;
}
switch (token.Type) {
case TokenType.Null:
stream.Write(__Null, 0, 4);
break;
case TokenType.Boolean:
if ((bool)token.Value) {
stream.Write(__True, 0, 4);
}
else {
stream.Write(__False, 0, 5);
}
break;
case TokenType.Integer:
WriteString(stream, ((long)token.Value).ToString(CultureInfo.InvariantCulture));
break;
case TokenType.Real:
WriteString(stream, ((double)token.Value).ToString("G", CultureInfo.InvariantCulture));
break;
case TokenType.String:
WriteLiteralString(stream, (string)token.Value);
break;
case TokenType.HexString:
WriteHexString(stream, (string)token.Value);
break;
case TokenType.Name:
WriteName(stream, (string)token.Value);
break;
case TokenType.Keyword:
WriteString(stream, (string)token.Value);
break;
case TokenType.Array:
WriteArray(stream, (List<Token>)token.Value);
break;
case TokenType.Dictionary:
WriteDictionary(stream, (Dictionary<string, Token>)token.Value);
break;
case TokenType.InlineImage:
// 不应作为常规操作数出现
default:
throw new NotSupportedException($"Unsupported token type: {token.Type}");
}
}
private static void WriteLiteralString(Stream stream, string s) {
byte[] bytes = Latin1.GetBytes(s);
stream.WriteByte((byte)'(');
foreach (byte b in bytes) {
if (b.CeqAny('(', ')', '\\')) {
stream.WriteByte((byte)'\\');
}
stream.WriteByte(b);
}
stream.WriteByte((byte)')');
}
private static void WriteHexString(Stream stream, string hex) {
stream.WriteByte((byte)'<');
WriteString(stream, hex);
stream.WriteByte((byte)'>');
}
private static void WriteName(Stream stream, string name) {
stream.WriteByte((byte)'/');
WriteString(stream, EncodeName(name));
}
private static void WriteArray(Stream stream, List<Token> list) {
stream.WriteByte((byte)'[');
for (int i = 0; i < list.Count; i++) {
if (i > 0) {
stream.WriteByte(Space);
}
WriteToken(stream, list[i]);
}
stream.WriteByte((byte)']');
}
private static void WriteDictionary(Stream stream, Dictionary<string, Token> dict) {
stream.WriteByte((byte)'<');
stream.WriteByte((byte)'<');
bool first = true;
foreach (var kvp in dict) {
if (!first) {
stream.WriteByte(Space);
}
else {
first = false;
}
// 键(名称)
stream.WriteByte((byte)'/');
WriteString(stream, EncodeName(kvp.Key));
stream.WriteByte(Space);
// 值
WriteToken(stream, kvp.Value);
}
stream.WriteByte((byte)'>');
stream.WriteByte((byte)'>');
}
private static void WriteInlineImage(Stream stream, InlineImageContent img) {
// BI
stream.Write(__BI, 0, 3);
// 字典键值对
bool first = true;
foreach (var kvp in img.Dictionary) {
if (!first) {
stream.WriteByte(Space);
}
else {
first = false;
}
// 键(名称)
stream.WriteByte((byte)'/');
WriteString(stream, EncodeName(kvp.Key));
stream.WriteByte(Space);
// 值
WriteToken(stream, kvp.Value);
}
// ID 和图像数据
stream.Write(__ID, 0, 2);
// ID 后通常紧跟图像数据,无空白
stream.Write(img.Data.Buffer, img.Data.Offset, img.Data.Length);
// EI
stream.Write(__EI, 0, 2);
}
private static void WriteOperator(Stream stream, string opName) {
WriteString(stream, opName);
}
private static void WriteString(Stream stream, string s) {
byte[] bytes = Latin1.GetBytes(s);
stream.Write(bytes, 0, bytes.Length);
}
private static readonly char[] HexChars = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
static string EncodeName(string name) {
// 快速路径:检测是否需要转义,同时统计转义次数
int escapeCount = 0;
int firstEscape = -1;
for (int i = 0; i < name.Length; i++) {
char c = name[i];
if (c <= 0x20 || c >= 0x7F || c == '#' || IsDelimiter(c)) {
if (firstEscape == -1) firstEscape = i;
escapeCount++;
}
}
// 无需转义时直接返回原字符串
if (escapeCount == 0)
return name;
// 预分配容量:每个转义字符增加2个字符(#XX 比原字符多2)
var sb = new StringBuilder(name.Length + escapeCount * 2);
int segmentStart = 0;
for (int i = 0; i < name.Length; i++) {
char c = name[i];
if (c <= 0x20 || c >= 0x7F || IsDelimiter(c)) {
// 批量追加之前的不需转义片段
if (segmentStart < i)
sb.Append(name, segmentStart, i - segmentStart);
// 追加转义序列(避免临时字符串)
int val = c;
sb.Append('#');
sb.Append(HexChars[val >> 4]);
sb.Append(HexChars[val & 0xF]);
segmentStart = i + 1;
}
}
// 追加最后剩余的不需转义片段
if (segmentStart < name.Length)
sb.Append(name, segmentStart, name.Length - segmentStart);
return sb.ToString();
}
private static bool IsDelimiter(char ch) {
return ch is '#' or '(' or ')' or '<' or '>' or
'[' or ']' or '{' or '}' or
'/' or '%';
}
#endregion
}