我有大量带有矢量图形(主要是线条和曲线)的PDF,需要以某种方式进行批量编辑,以修改角落类型和结束类型等基本属性。这也适用于编辑厚度和颜色。
我已经在使用iTextSharp来编辑这些PDF,将图像插入到每个文件的背景中,但是我没有太多关于曲线和线条的文档,我找不到编辑线条的方法。我也对其他图书馆开放,但我没有找到一个清楚地解决如何编辑现有曲线和线条,只绘制新曲线和线条的图书馆。
using iTextSharp.text;
using iTextSharp.text.pdf;
// open the reader
PdfReader reader = new PdfReader(refPath);
Rectangle size = reader.GetPageSizeWithRotation(1);
Document document = new Document(size);
// open the writer
FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write);
PdfWriter writer = PdfWriter.GetInstance(document, fs);
document.Open();
// the pdf content
PdfContentByte cb = writer.DirectContent;
//get an image to be inserted.
var screenshot = System.Drawing.Image.FromFile("somefile.png");
//Create iTextSharp image
Image bg = Image.GetInstance(screenshot, System.Drawing.Imaging.ImageFormat.Png);
bg.SetDpi(dpi, dpi);
bg.ScaleToFit(size);
bg.SetAbsolutePosition(0, 0);
bg.Alignment = Image.UNDERLYING;
cb.AddImage(bg);
/**
Get and edit linework properties in here somewhere???
**/
// create the new page and add it to the pdf
PdfImportedPage page = writer.GetImportedPage(reader, 1);
cb.AddTemplate(page, 0, 0);
// close the streams
document.Close();
fs.Close();
writer.Close();
reader.Close();
理想情况下,所有行的输出看起来像这样:
任何想法都表示赞赏!
通过一种舒适的格式尝试往返这种东西是很诱人的。所以也许转换为SVG,然后进行操作,然后再转换为PDF。
但是我会鼓励你远离这种诱惑,因为这样的往返会不可避免地导致扭曲和失落。
相反,我鼓励您直接使用原始PDF运算符流。一开始看起来有点令人生畏,但实际上一旦掌握了它就很简单了。例如(百分比表示评论),
q % save state
0 0 10 10 re % define rectangle path
s % stroke
Q % restore state
Adobe PDF规范将为您提供所有细节。它很大,但写得很清楚。有关所有运营商的列表以及相关部分的链接,请参见附录A.
https://www.adobe.com/content/dam/acom/en/devnet/pdf/pdfs/PDF32000_2008.pdf
那么问题就变成了如何使用现有的内容流?
解析这些东西并非易事,所以我建议你使用一个工具。例如,ABCpdf将允许您将流分解为原子,修改序列,然后将它们插回到原始文档中。有关代码示例,请参阅
在解析和操作方面,这是一个非常优雅和强大的机制。我确信还有其他工具允许类似的东西,但我知道ABCpdf。 :-)
您的图像显示您要编辑所有路径中的行上限和行连接为圆形。
不幸的是,你没有共享代表性的示例文件,所以我不得不自己构建一个不同的cap和join样式以及一个提醒你的路径表单:
我建议您的任务是使用来自PdfContentStreamEditor
的通用this answer,因为它可以完成所有繁重工作,我们可以专注于手头的任务。
那么,我们的流编辑器实现必须做什么?它必须将cap和join样式设置为“round”并防止覆盖这些设置。查看PDF规范,我们看到cap和join样式是当前图形状态的参数,可以分别使用J和j指令直接设置,也可以通过Graphics State Parameter Dictionary中的LC和LJ条目设置。
因此,我们可以简单地通过首先初始化cap和join样式来实现我们的流编辑器,然后删除所有J和j指令,并在每个图形状态gs指令之后重新初始化cap和join样式。
class PathMakeCapAndJoinRound : PdfContentStreamEditor
{
protected override void Write(PdfContentStreamProcessor processor, PdfLiteral operatorLit, List<PdfObject> operands)
{
if (start)
{
initializeCapAndJoin(processor);
start = false;
}
if (CAP_AND_JOIN_OPERATORS.Contains(operatorLit.ToString()))
{
return;
}
base.Write(processor, operatorLit, operands);
if (GSTATE_OPERATOR == operatorLit.ToString())
{
initializeCapAndJoin(processor);
}
}
void initializeCapAndJoin(PdfContentStreamProcessor processor)
{
PdfLiteral operatorLit = new PdfLiteral("J");
List<PdfObject> operands = new List<PdfObject> { new PdfNumber(PdfContentByte.LINE_CAP_ROUND), operatorLit };
base.Write(processor, operatorLit, operands);
operatorLit = new PdfLiteral("j");
operands = new List<PdfObject> { new PdfNumber(PdfContentByte.LINE_JOIN_ROUND), operatorLit };
base.Write(processor, operatorLit, operands);
}
List<string> CAP_AND_JOIN_OPERATORS = new List<string> { "j", "J" };
string GSTATE_OPERATOR = "gs";
bool start = true;
}
将它像这样应用到上面的PDF中
using (PdfReader pdfReader = new PdfReader(testDocument))
using (PdfStamper pdfStamper = new PdfStamper(pdfReader, new FileStream(@"Paths-Rounded.pdf", FileMode.Create, FileAccess.Write), (char)0, true))
{
pdfStamper.RotateContents = false;
PdfContentStreamEditor editor = new PathMakeCapAndJoinRound();
for (int i = 1; i <= pdfReader.NumberOfPages; i++)
{
editor.EditPage(pdfStamper, i);
}
}
我们得到了结果:
请注意,参考答案的限制仍然存在。特别是此编辑器仅编辑页面内容流。对于完整的解决方案,您还必须编辑所有表单XObject和Pattern流,还要处理注释。
为了允许复制,这就是我创建测试文档的方式:
byte[] createMixedPathsPdf()
{
using (MemoryStream memoryStream = new MemoryStream())
{
using (Document document = new Document())
{
PdfWriter writer = PdfWriter.GetInstance(document, memoryStream);
document.Open();
var canvas = writer.DirectContent;
canvas.SetLineWidth(10);
canvas.MoveTo(100, 700);
canvas.CurveTo(180, 720, 180, 720, 200, 800);
canvas.CurveTo(220, 720, 220, 720, 350, 700);
canvas.MoveTo(350, 700);
canvas.CurveTo(220, 680, 220, 680, 210, 650);
canvas.Stroke();
canvas.SetLineCap(PdfContentByte.LINE_CAP_BUTT);
canvas.SetLineJoin(PdfContentByte.LINE_JOIN_BEVEL);
canvas.SetGState(createGState(PdfContentByte.LINE_CAP_BUTT, PdfContentByte.LINE_JOIN_BEVEL));
canvas.MoveTo(100, 500);
canvas.CurveTo(180, 520, 180, 520, 200, 600);
canvas.CurveTo(220, 520, 220, 520, 350, 500);
canvas.MoveTo(350, 500);
canvas.CurveTo(220, 480, 220, 480, 210, 450);
canvas.Stroke();
canvas.SetLineCap(PdfContentByte.LINE_CAP_PROJECTING_SQUARE);
canvas.SetLineJoin(PdfContentByte.LINE_JOIN_MITER);
canvas.SetGState(createGState(PdfContentByte.LINE_CAP_PROJECTING_SQUARE, PdfContentByte.LINE_JOIN_MITER));
canvas.MoveTo(100, 300);
canvas.CurveTo(180, 320, 180, 320, 200, 400);
canvas.CurveTo(220, 320, 220, 320, 350, 300);
canvas.MoveTo(350, 300);
canvas.CurveTo(220, 280, 220, 280, 210, 250);
canvas.Stroke();
canvas.SetLineCap(PdfContentByte.LINE_CAP_ROUND);
canvas.SetLineJoin(PdfContentByte.LINE_JOIN_ROUND);
canvas.SetGState(createGState(PdfContentByte.LINE_CAP_ROUND, PdfContentByte.LINE_JOIN_ROUND));
canvas.MoveTo(100, 100);
canvas.CurveTo(180, 120, 180, 120, 200, 200);
canvas.CurveTo(220, 120, 220, 120, 350, 100);
canvas.MoveTo(350, 100);
canvas.CurveTo(220, 080, 220, 080, 210, 050);
canvas.Stroke();
}
return memoryStream.ToArray();
}
}
PdfGState createGState(int lineCap, int lineJoin)
{
PdfGState pdfGState = new PdfGState();
pdfGState.Put(new PdfName("LC"), new PdfNumber(lineCap));
pdfGState.Put(new PdfName("LJ"), new PdfNumber(lineJoin));
return pdfGState;
}