From edf99acdf493bcf5124f99be2d2e10a56fb91f00 Mon Sep 17 00:00:00 2001 From: Eliot Jones Date: Sat, 27 May 2023 13:52:28 +0100 Subject: [PATCH] write version from builder in xmp metadata #633 --- .../Writer/PdfABaselineRuleBuilder.cs | 9 +- .../Writer/PdfDedupStreamWriter.cs | 8 +- .../Writer/PdfDocumentBuilder.cs | 18 +- src/UglyToad.PdfPig/Writer/PdfStreamWriter.cs | 213 +++++++++--------- src/UglyToad.PdfPig/Writer/Xmp/XmpWriter.cs | 4 +- 5 files changed, 138 insertions(+), 114 deletions(-) diff --git a/src/UglyToad.PdfPig/Writer/PdfABaselineRuleBuilder.cs b/src/UglyToad.PdfPig/Writer/PdfABaselineRuleBuilder.cs index af810f85..ce67c191 100644 --- a/src/UglyToad.PdfPig/Writer/PdfABaselineRuleBuilder.cs +++ b/src/UglyToad.PdfPig/Writer/PdfABaselineRuleBuilder.cs @@ -8,12 +8,15 @@ namespace UglyToad.PdfPig.Writer { internal static class PdfABaselineRuleBuilder { - public static void Obey(Dictionary catalog, Func writerFunc, + public static void Obey( + Dictionary catalog, + Func writerFunc, PdfDocumentBuilder.DocumentInformationBuilder documentInformationBuilder, - PdfAStandard archiveStandard) + PdfAStandard archiveStandard, + decimal version) { catalog[NameToken.OutputIntents] = OutputIntentsFactory.GetOutputIntentsArray(writerFunc); - var xmpStream = XmpWriter.GenerateXmpStream(documentInformationBuilder, 1.7m, archiveStandard); + var xmpStream = XmpWriter.GenerateXmpStream(documentInformationBuilder, version, archiveStandard); var xmpObj = writerFunc(xmpStream); catalog[NameToken.Metadata] = xmpObj; } diff --git a/src/UglyToad.PdfPig/Writer/PdfDedupStreamWriter.cs b/src/UglyToad.PdfPig/Writer/PdfDedupStreamWriter.cs index bd547b89..72ad6525 100644 --- a/src/UglyToad.PdfPig/Writer/PdfDedupStreamWriter.cs +++ b/src/UglyToad.PdfPig/Writer/PdfDedupStreamWriter.cs @@ -1,5 +1,6 @@ namespace UglyToad.PdfPig.Writer { + using System; using System.Collections.Generic; using System.IO; using Tokens; @@ -8,7 +9,12 @@ { private readonly Dictionary hashes = new Dictionary(new FNVByteComparison()); - public PdfDedupStreamWriter(Stream stream, bool dispose, ITokenWriter tokenWriter = null) : base(stream, dispose, tokenWriter) + public PdfDedupStreamWriter( + Stream stream, + bool dispose, + ITokenWriter tokenWriter = null, + Action recordVersion = null + ) : base(stream, dispose, tokenWriter, recordVersion) { } diff --git a/src/UglyToad.PdfPig/Writer/PdfDocumentBuilder.cs b/src/UglyToad.PdfPig/Writer/PdfDocumentBuilder.cs index e9efa47f..de114a9c 100644 --- a/src/UglyToad.PdfPig/Writer/PdfDocumentBuilder.cs +++ b/src/UglyToad.PdfPig/Writer/PdfDocumentBuilder.cs @@ -9,12 +9,12 @@ namespace UglyToad.PdfPig.Writer using Content; using Core; using Fonts; - using PdfPig.Actions; + using Actions; using PdfPig.Fonts.TrueType; using PdfPig.Fonts.Standard14Fonts; using PdfPig.Fonts.TrueType.Parser; - using PdfPig.Outline; - using PdfPig.Outline.Destinations; + using Outline; + using Outline.Destinations; using Tokenization.Scanner; using Tokens; @@ -30,6 +30,7 @@ namespace UglyToad.PdfPig.Writer private readonly Dictionary fonts = new Dictionary(); private bool completed = false; private int fontId = 0; + private decimal version = 1.7m; private readonly static ArrayToken DefaultProcSet = new ArrayToken(new List { @@ -76,7 +77,7 @@ namespace UglyToad.PdfPig.Writer /// public PdfDocumentBuilder() { - context = new PdfStreamWriter(new MemoryStream(), true); + context = new PdfStreamWriter(new MemoryStream(), true, recordVersion: x => version = x); context.InitializePdf(1.7m); } @@ -86,7 +87,7 @@ namespace UglyToad.PdfPig.Writer /// Pdf version to use in header. public PdfDocumentBuilder(decimal version) { - context = new PdfStreamWriter(new MemoryStream(), true); + context = new PdfStreamWriter(new MemoryStream(), true, recordVersion: x => version = x); context.InitializePdf(version); } @@ -103,12 +104,13 @@ namespace UglyToad.PdfPig.Writer switch (type) { case PdfWriterType.ObjectInMemoryDedup: - context = new PdfDedupStreamWriter(stream, disposeStream, tokenWriter); + context = new PdfDedupStreamWriter(stream, disposeStream, tokenWriter, x => version = x); break; default: - context = new PdfStreamWriter(stream, disposeStream, tokenWriter); + context = new PdfStreamWriter(stream, disposeStream, tokenWriter, x => version = x); break; } + context.InitializePdf(version); } @@ -712,7 +714,7 @@ namespace UglyToad.PdfPig.Writer { Func writerFunc = x => context.WriteToken(x); - PdfABaselineRuleBuilder.Obey(catalogDictionary, writerFunc, DocumentInformation, ArchiveStandard); + PdfABaselineRuleBuilder.Obey(catalogDictionary, writerFunc, DocumentInformation, ArchiveStandard, version); switch (ArchiveStandard) { diff --git a/src/UglyToad.PdfPig/Writer/PdfStreamWriter.cs b/src/UglyToad.PdfPig/Writer/PdfStreamWriter.cs index 0f0e6f7f..078fc2c2 100644 --- a/src/UglyToad.PdfPig/Writer/PdfStreamWriter.cs +++ b/src/UglyToad.PdfPig/Writer/PdfStreamWriter.cs @@ -1,104 +1,115 @@ -namespace UglyToad.PdfPig.Writer -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using Core; - using Graphics.Operations; - using Tokens; - - /// - /// This class would lazily flush all token. Allowing us to make changes to references without need to rewrite the whole stream - /// - internal class PdfStreamWriter : IPdfStreamWriter - { - protected const decimal DefaultVersion = 1.2m; - protected Dictionary offsets = new Dictionary(); - protected bool DisposeStream { get; set; } - protected bool Initialized { get; set; } - protected int CurrentNumber { get; set; } = 1; - protected readonly ITokenWriter TokenWriter; - - internal PdfStreamWriter(Stream baseStream, bool disposeStream = true, ITokenWriter tokenWriter = null) - { - Stream = baseStream ?? throw new ArgumentNullException(nameof(baseStream)); - if (!baseStream.CanWrite) - { - throw new ArgumentException("Output stream must be writable"); - } - DisposeStream = disposeStream; - TokenWriter = tokenWriter ?? new TokenWriter(); - } - +namespace UglyToad.PdfPig.Writer +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using Core; + using Graphics.Operations; + using Tokens; + + /// + /// This class would lazily flush all token. Allowing us to make changes to references without need to rewrite the whole stream + /// + internal class PdfStreamWriter : IPdfStreamWriter + { + private readonly Action recordVersion; + protected const decimal DefaultVersion = 1.2m; + protected Dictionary offsets = new Dictionary(); + protected bool DisposeStream { get; set; } + protected bool Initialized { get; set; } + protected int CurrentNumber { get; set; } = 1; + protected readonly ITokenWriter TokenWriter; + public Stream Stream { get; protected set; } + public bool AttemptDeduplication { get; set; } = true; - public virtual IndirectReferenceToken WriteToken(IToken token) - { - if (!Initialized) - { - InitializePdf(DefaultVersion); - } - - var ir = ReserveObjectNumber(); - offsets.Add(ir.Data, Stream.Position); - var obj = new ObjectToken(Stream.Position, ir.Data, token); - TokenWriter.WriteToken(obj, Stream); - return ir; - } - - public virtual IndirectReferenceToken WriteToken(IToken token, IndirectReferenceToken indirectReference) - { - if (!Initialized) - { - InitializePdf(DefaultVersion); - } - - offsets.Add(indirectReference.Data, Stream.Position); - var obj = new ObjectToken(Stream.Position, indirectReference.Data, token); - TokenWriter.WriteToken(obj, Stream); - return indirectReference; - } - - public IndirectReferenceToken ReserveObjectNumber() - { - return new IndirectReferenceToken(new IndirectReference(CurrentNumber++, 0)); - } - - public void InitializePdf(decimal version) - { - WriteString($"%PDF-{version.ToString("0.0", CultureInfo.InvariantCulture)}", Stream); - - Stream.WriteText("%"); - Stream.WriteByte(169); - Stream.WriteByte(205); - Stream.WriteByte(196); - Stream.WriteByte(210); - Stream.WriteNewLine(); - Initialized = true; - } - - public void CompletePdf(IndirectReferenceToken catalogReference, IndirectReferenceToken documentInformationReference = null) - { - TokenWriter.WriteCrossReferenceTable(offsets, catalogReference.Data, Stream, documentInformationReference?.Data); - } - - private static void WriteString(string text, Stream stream) - { - var bytes = OtherEncodings.StringAsLatin1Bytes(text); - stream.Write(bytes, 0, bytes.Length); - stream.WriteNewLine(); - } - - public void Dispose() - { - if (DisposeStream) - { - Stream?.Dispose(); - } - - Stream = null; - } - } -} + internal PdfStreamWriter( + Stream baseStream, + bool disposeStream = true, + ITokenWriter tokenWriter = null, + Action recordVersion = null) + { + Stream = baseStream ?? throw new ArgumentNullException(nameof(baseStream)); + + if (!baseStream.CanWrite) + { + throw new ArgumentException("Output stream must be writable"); + } + + this.recordVersion = recordVersion; + DisposeStream = disposeStream; + TokenWriter = tokenWriter ?? new TokenWriter(); + } + + public virtual IndirectReferenceToken WriteToken(IToken token) + { + if (!Initialized) + { + InitializePdf(DefaultVersion); + } + + var ir = ReserveObjectNumber(); + offsets.Add(ir.Data, Stream.Position); + var obj = new ObjectToken(Stream.Position, ir.Data, token); + TokenWriter.WriteToken(obj, Stream); + return ir; + } + + public virtual IndirectReferenceToken WriteToken(IToken token, IndirectReferenceToken indirectReference) + { + if (!Initialized) + { + InitializePdf(DefaultVersion); + } + + offsets.Add(indirectReference.Data, Stream.Position); + var obj = new ObjectToken(Stream.Position, indirectReference.Data, token); + TokenWriter.WriteToken(obj, Stream); + return indirectReference; + } + + public IndirectReferenceToken ReserveObjectNumber() + { + return new IndirectReferenceToken(new IndirectReference(CurrentNumber++, 0)); + } + + public void InitializePdf(decimal version) + { + recordVersion?.Invoke(version); + + WriteString($"%PDF-{version.ToString("0.0", CultureInfo.InvariantCulture)}", Stream); + + Stream.WriteText("%"); + Stream.WriteByte(169); + Stream.WriteByte(205); + Stream.WriteByte(196); + Stream.WriteByte(210); + Stream.WriteNewLine(); + Initialized = true; + } + + public void CompletePdf(IndirectReferenceToken catalogReference, IndirectReferenceToken documentInformationReference = null) + { + TokenWriter.WriteCrossReferenceTable(offsets, catalogReference.Data, Stream, documentInformationReference?.Data); + } + + private static void WriteString(string text, Stream stream) + { + var bytes = OtherEncodings.StringAsLatin1Bytes(text); + stream.Write(bytes, 0, bytes.Length); + stream.WriteNewLine(); + } + + public void Dispose() + { + if (DisposeStream) + { + Stream?.Dispose(); + } + + Stream = null; + } + } +} diff --git a/src/UglyToad.PdfPig/Writer/Xmp/XmpWriter.cs b/src/UglyToad.PdfPig/Writer/Xmp/XmpWriter.cs index a61ab83d..ae669284 100644 --- a/src/UglyToad.PdfPig/Writer/Xmp/XmpWriter.cs +++ b/src/UglyToad.PdfPig/Writer/Xmp/XmpWriter.cs @@ -7,6 +7,8 @@ using UglyToad.PdfPig.Tokens; namespace UglyToad.PdfPig.Writer.Xmp { + using System.Globalization; + internal static class XmpWriter { private const string Xmptk = "Adobe XMP Core 5.6-c014 79.156797, 2014/08/20-09:53:02 "; @@ -67,7 +69,7 @@ namespace UglyToad.PdfPig.Writer.Xmp AddElementsForSchema(rdfDescriptionElement, AdobePdfPrefix, AdobePdfNamespace, builder, new List { - new SchemaMapper("PDFVersion", b => "1.7"), + new SchemaMapper("PDFVersion", b => version.ToString("F1", CultureInfo.InvariantCulture)), new SchemaMapper("Producer", b => b.Producer) });