Skip to content

Commit 377eb50

Browse files
EliotJonesBobLd
authored andcommitted
when writing content to an existing page inverse any global transform UglyToad#614
when adding a page to a builder from an existing document using either addpage or copyfrom methods the added page's content stream can contain a global transform matrix change that will subsequently change all the locations of any modifications made by the user. here whenever using an existing stream we apply the inverse of any active transformation matrix there could be a bug here where if you use 'copy from' with a global transform active, we then apply the inverse, and you use 'copy from' again to the same destination page our inverse transform is now active and could potentially affect the second stream, but I don't think it will
1 parent ff4e763 commit 377eb50

File tree

3 files changed

+144
-10
lines changed

3 files changed

+144
-10
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
namespace UglyToad.PdfPig.Writer;
2+
3+
using Core;
4+
using Graphics.Operations;
5+
using Graphics.Operations.SpecialGraphicsState;
6+
using System;
7+
using System.Collections.Generic;
8+
9+
internal static class PdfContentTransformationReader
10+
{
11+
public static TransformationMatrix? GetGlobalTransform(IEnumerable<IGraphicsStateOperation> operations)
12+
{
13+
TransformationMatrix? activeMatrix = null;
14+
var stackDepth = 0;
15+
foreach (var operation in operations)
16+
{
17+
if (operation is ModifyCurrentTransformationMatrix cm)
18+
{
19+
if (stackDepth == 0 && cm.Value.Length == 6)
20+
{
21+
activeMatrix = TransformationMatrix.FromArray(cm.Value);
22+
}
23+
}
24+
else if (operation is Push push)
25+
{
26+
stackDepth++;
27+
}
28+
else if (operation is Pop pop)
29+
{
30+
stackDepth--;
31+
}
32+
}
33+
34+
return activeMatrix;
35+
}
36+
}

src/UglyToad.PdfPig/Writer/PdfDocumentBuilder.cs

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,16 @@ namespace UglyToad.PdfPig.Writer
1111
using Core;
1212
using Fonts;
1313
using Actions;
14+
using Filters;
15+
using Graphics;
16+
using Logging;
1417
using PdfPig.Fonts.TrueType;
1518
using PdfPig.Fonts.Standard14Fonts;
1619
using PdfPig.Fonts.TrueType.Parser;
1720
using Outline;
1821
using Outline.Destinations;
22+
using Parser;
23+
using Parser.Parts;
1924
using Tokenization.Scanner;
2025
using Tokens;
2126

@@ -342,6 +347,7 @@ public PdfPageBuilder AddPage(PdfDocument document, int pageNumber, Func<PdfActi
342347
}
343348

344349
var page = document.GetPage(pageNumber);
350+
var pcp = new PageContentParser(ReflectionGraphicsStateOperationFactory.Instance, true);
345351

346352
// copy content streams
347353
var streams = new List<PdfPageBuilder.CopiedContentStream>();
@@ -352,23 +358,54 @@ public PdfPageBuilder AddPage(PdfDocument document, int pageNumber, Func<PdfActi
352358
var prev = context.AttemptDeduplication;
353359
context.AttemptDeduplication = false;
354360
context.WritingPageContents = true;
361+
362+
var contentReferences = new List<IndirectReferenceToken>();
363+
355364
if (contentsToken is ArrayToken array)
356365
{
357366
foreach (var item in array.Data)
358367
{
359368
if (item is IndirectReferenceToken ir)
360369
{
361-
streams.Add(new PdfPageBuilder.CopiedContentStream(
362-
(IndirectReferenceToken)WriterUtil.CopyToken(context, ir, document.Structure.TokenScanner, refs)));
370+
contentReferences.Add(ir);
363371
}
364372

365373
}
366374
}
367375
else if (contentsToken is IndirectReferenceToken ir)
368376
{
369-
streams.Add(new PdfPageBuilder.CopiedContentStream(
370-
(IndirectReferenceToken)WriterUtil.CopyToken(context, ir, document.Structure.TokenScanner, refs)));
377+
contentReferences.Add(ir);
371378
}
379+
380+
foreach (var indirectReferenceToken in contentReferences)
381+
{
382+
// Detect any globally applied transforms to the graphics state from the content stream.
383+
TransformationMatrix? globalTransform = null;
384+
385+
try
386+
{
387+
// If we don't manage to do this it's not the end of the world.
388+
if (DirectObjectFinder.TryGet<StreamToken>(indirectReferenceToken, document.Structure.TokenScanner, out var contentStream))
389+
{
390+
var contentBytes = contentStream.Decode(DefaultFilterProvider.Instance);
391+
var parsedOperations = pcp.Parse(0, new MemoryInputBytes(contentBytes), new NoOpLog());
392+
globalTransform = PdfContentTransformationReader.GetGlobalTransform(parsedOperations);
393+
}
394+
}
395+
catch
396+
{
397+
// Ignore and continue writing.
398+
}
399+
400+
var updatedIndirect = (IndirectReferenceToken)WriterUtil.CopyToken(
401+
context,
402+
indirectReferenceToken,
403+
document.Structure.TokenScanner,
404+
refs);
405+
406+
streams.Add(new PdfPageBuilder.CopiedContentStream(updatedIndirect, globalTransform));
407+
}
408+
372409
context.AttemptDeduplication = prev;
373410
context.WritingPageContents = false;
374411
}

src/UglyToad.PdfPig/Writer/PdfPageBuilder.cs

Lines changed: 67 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -140,16 +140,42 @@ internal PdfPageBuilder(int number, PdfDocumentBuilder documentBuilder)
140140
contentStreams = new List<IPageContentStream>() { currentStream };
141141
}
142142

143-
internal PdfPageBuilder(int number, PdfDocumentBuilder documentBuilder, IEnumerable<CopiedContentStream> copied,
144-
Dictionary<NameToken, IToken> pageDict, List<(DictionaryToken token, PdfAction action)> links)
143+
internal PdfPageBuilder(
144+
int number,
145+
PdfDocumentBuilder documentBuilder,
146+
IEnumerable<CopiedContentStream> copied,
147+
Dictionary<NameToken, IToken> pageDict,
148+
List<(DictionaryToken token, PdfAction action)> links)
145149
{
146150
this.documentBuilder = documentBuilder ?? throw new ArgumentNullException(nameof(documentBuilder));
147151
this.links = links;
148152
PageNumber = number;
149153
pageDictionary = pageDict;
150-
contentStreams = new List<IPageContentStream>();
151-
contentStreams.AddRange(copied);
152-
currentStream = new DefaultContentStream();
154+
contentStreams = new List<IPageContentStream>(copied);
155+
156+
var writeableContentStream = new DefaultContentStream();
157+
if (contentStreams.Count > 0)
158+
{
159+
var lastGlobalTransform = contentStreams.LastOrDefault(x => x.GlobalTransform.HasValue);
160+
161+
if (lastGlobalTransform?.GlobalTransform != null)
162+
{
163+
var inverse = lastGlobalTransform.GlobalTransform.Value.Inverse();
164+
writeableContentStream.Add(
165+
new ModifyCurrentTransformationMatrix(
166+
[
167+
inverse.A,
168+
inverse.B,
169+
inverse.C,
170+
inverse.D,
171+
inverse.E,
172+
inverse.F
173+
]
174+
));
175+
}
176+
}
177+
178+
currentStream = writeableContentStream;
153179
contentStreams.Add(currentStream);
154180
}
155181

@@ -987,6 +1013,22 @@ public PdfPageBuilder CopyFrom(Page srcPage)
9871013
}
9881014
}
9891015

1016+
// Reset the graphics state to what we'd expect to be able to write our content in the correct locations.
1017+
var globalTransform = PdfContentTransformationReader.GetGlobalTransform(operations);
1018+
if (globalTransform.HasValue)
1019+
{
1020+
var inverse = globalTransform.Value.Inverse();
1021+
operations.Add(new ModifyCurrentTransformationMatrix(
1022+
[
1023+
inverse.A,
1024+
inverse.B,
1025+
inverse.C,
1026+
inverse.D,
1027+
inverse.E,
1028+
inverse.F
1029+
]));
1030+
}
1031+
9901032
destinationStream.Operations.AddRange(operations);
9911033

9921034
return this;
@@ -1090,9 +1132,18 @@ public interface IContentStream
10901132
internal interface IPageContentStream : IContentStream
10911133
{
10921134
bool ReadOnly { get; }
1135+
10931136
bool HasContent { get; }
1137+
10941138
void Add(IGraphicsStateOperation operation);
1139+
10951140
IndirectReferenceToken Write(IPdfStreamWriter writer);
1141+
1142+
/// <summary>
1143+
/// If this content stream applied any global transform to the graphics state this will
1144+
/// tell you which one is currently active at the end of this stream being applied.
1145+
/// </summary>
1146+
TransformationMatrix? GlobalTransform { get; }
10961147
}
10971148

10981149
internal class DefaultContentStream : IPageContentStream
@@ -1109,8 +1160,11 @@ public DefaultContentStream(List<IGraphicsStateOperation> operations)
11091160
}
11101161

11111162
public bool ReadOnly => false;
1163+
11121164
public bool HasContent => operations.Any();
11131165

1166+
public TransformationMatrix? GlobalTransform => null;
1167+
11141168
public void Add(IGraphicsStateOperation operation)
11151169
{
11161170
operations.Add(operation);
@@ -1139,11 +1193,18 @@ public IndirectReferenceToken Write(IPdfStreamWriter writer)
11391193
internal class CopiedContentStream : IPageContentStream
11401194
{
11411195
private readonly IndirectReferenceToken token;
1196+
11421197
public bool ReadOnly => true;
1198+
11431199
public bool HasContent => true;
11441200

1145-
public CopiedContentStream(IndirectReferenceToken indirectReferenceToken)
1201+
public TransformationMatrix? GlobalTransform { get; }
1202+
1203+
public CopiedContentStream(
1204+
IndirectReferenceToken indirectReferenceToken,
1205+
TransformationMatrix? globalTransform)
11461206
{
1207+
GlobalTransform = globalTransform;
11471208
token = indirectReferenceToken;
11481209
}
11491210

0 commit comments

Comments
 (0)