From 7f1bf094bc530a3f702572f96b2f5f85d22a76d3 Mon Sep 17 00:00:00 2001 From: Eliot Jones Date: Sun, 29 Mar 2020 17:55:02 +0100 Subject: [PATCH] #127 pdf/a-1a compliance adds struct tree and markinfo dictionaries to support pdf/a-1a compliance. --- .../Writer/PdfDocumentBuilderTests.cs | 28 +++++++++++++++ .../NameToken.Constants.cs | 1 + .../Writer/PdfA1ARuleBuilder.cs | 35 +++++++++++++++++++ .../Writer/PdfA1BRuleBuilder.cs | 21 +++++++++++ src/UglyToad.PdfPig/Writer/PdfAStandard.cs | 6 +++- .../Writer/PdfDocumentBuilder.cs | 16 +++++---- src/UglyToad.PdfPig/Writer/Xmp/XmpWriter.cs | 4 +++ 7 files changed, 104 insertions(+), 7 deletions(-) create mode 100644 src/UglyToad.PdfPig/Writer/PdfA1ARuleBuilder.cs create mode 100644 src/UglyToad.PdfPig/Writer/PdfA1BRuleBuilder.cs diff --git a/src/UglyToad.PdfPig.Tests/Writer/PdfDocumentBuilderTests.cs b/src/UglyToad.PdfPig.Tests/Writer/PdfDocumentBuilderTests.cs index b1d1e6a2..197c7890 100644 --- a/src/UglyToad.PdfPig.Tests/Writer/PdfDocumentBuilderTests.cs +++ b/src/UglyToad.PdfPig.Tests/Writer/PdfDocumentBuilderTests.cs @@ -522,6 +522,34 @@ } } + [Fact] + public void CanGeneratePdfA1AFile() + { + var builder = new PdfDocumentBuilder + { + ArchiveStandard = PdfAStandard.A1A + }; + + var page = builder.AddPage(PageSize.A4); + + var font = builder.AddTrueTypeFont(TrueTypeTestHelper.GetFileBytes("Roboto-Regular.ttf")); + + page.AddText("Howdy PDF/A-1A!", 10, new PdfPoint(25, 700), font); + + var bytes = builder.Build(); + + WriteFile(nameof(CanGeneratePdfA1AFile), bytes); + + using (var pdf = PdfDocument.Open(bytes, ParsingOptions.LenientParsingOff)) + { + Assert.Equal(1, pdf.NumberOfPages); + + Assert.True(pdf.TryGetXmpMetadata(out var xmp)); + + Assert.NotNull(xmp.GetXDocument()); + } + } + private static void WriteFile(string name, byte[] bytes) { try diff --git a/src/UglyToad.PdfPig.Tokens/NameToken.Constants.cs b/src/UglyToad.PdfPig.Tokens/NameToken.Constants.cs index a2c08385..5af9c9fc 100644 --- a/src/UglyToad.PdfPig.Tokens/NameToken.Constants.cs +++ b/src/UglyToad.PdfPig.Tokens/NameToken.Constants.cs @@ -334,6 +334,7 @@ public static readonly NameToken Mac = new NameToken("Mac"); public static readonly NameToken MacExpertEncoding = new NameToken("MacExpertEncoding"); public static readonly NameToken MacRomanEncoding = new NameToken("MacRomanEncoding"); + public static readonly NameToken Marked = new NameToken("Marked"); public static readonly NameToken MarkInfo = new NameToken("MarkInfo"); public static readonly NameToken Mask = new NameToken("Mask"); public static readonly NameToken Matrix = new NameToken("Matrix"); diff --git a/src/UglyToad.PdfPig/Writer/PdfA1ARuleBuilder.cs b/src/UglyToad.PdfPig/Writer/PdfA1ARuleBuilder.cs new file mode 100644 index 00000000..b5e2bf87 --- /dev/null +++ b/src/UglyToad.PdfPig/Writer/PdfA1ARuleBuilder.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using UglyToad.PdfPig.Tokens; + +namespace UglyToad.PdfPig.Writer +{ + internal static class PdfA1ARuleBuilder + { + public static void Obey(Dictionary catalog) + { + var structTreeRoot = GenerateStructTree(); + + catalog[NameToken.StructTreeRoot] = structTreeRoot; + + var markInfoDictionary = new DictionaryToken(new Dictionary + { + {NameToken.Marked, BooleanToken.True} + }); + + catalog[NameToken.MarkInfo] = markInfoDictionary; + } + + private static DictionaryToken GenerateStructTree() + { + var rootDictionary = new Dictionary + { + {NameToken.Type, NameToken.StructTreeRoot} + }; + + var structTreeRoot = new DictionaryToken(rootDictionary); + + return structTreeRoot; + } + } +} diff --git a/src/UglyToad.PdfPig/Writer/PdfA1BRuleBuilder.cs b/src/UglyToad.PdfPig/Writer/PdfA1BRuleBuilder.cs new file mode 100644 index 00000000..1c6f7b7c --- /dev/null +++ b/src/UglyToad.PdfPig/Writer/PdfA1BRuleBuilder.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using UglyToad.PdfPig.Tokens; +using UglyToad.PdfPig.Writer.Colors; +using UglyToad.PdfPig.Writer.Xmp; + +namespace UglyToad.PdfPig.Writer +{ + internal static class PdfA1BRuleBuilder + { + public static void Obey(Dictionary catalog, Func writerFunc, + PdfDocumentBuilder.DocumentInformationBuilder documentInformationBuilder, + PdfAStandard archiveStandard) + { + catalog[NameToken.OutputIntents] = OutputIntentsFactory.GetOutputIntentsArray(writerFunc); + var xmpStream = XmpWriter.GenerateXmpStream(documentInformationBuilder, 1.7m, archiveStandard); + var xmpObj = writerFunc(xmpStream); + catalog[NameToken.Metadata] = new IndirectReferenceToken(xmpObj.Number); + } + } +} \ No newline at end of file diff --git a/src/UglyToad.PdfPig/Writer/PdfAStandard.cs b/src/UglyToad.PdfPig/Writer/PdfAStandard.cs index b22f28ef..056d228b 100644 --- a/src/UglyToad.PdfPig/Writer/PdfAStandard.cs +++ b/src/UglyToad.PdfPig/Writer/PdfAStandard.cs @@ -12,6 +12,10 @@ /// /// Compliance with PDF/A1-B. Level B (basic) conformance are standards necessary for the reliable reproduction of a document's visual appearance. /// - A1B = 1 + A1B = 1, + /// + /// Compliance with PDF/A1-A. Level A (accessible) conformance are PDF/A1-B standards in addition to features intended to improve a document's accessibility. + /// + A1A = 2 } } \ No newline at end of file diff --git a/src/UglyToad.PdfPig/Writer/PdfDocumentBuilder.cs b/src/UglyToad.PdfPig/Writer/PdfDocumentBuilder.cs index c1a79ed4..cd7d4e8a 100644 --- a/src/UglyToad.PdfPig/Writer/PdfDocumentBuilder.cs +++ b/src/UglyToad.PdfPig/Writer/PdfDocumentBuilder.cs @@ -13,8 +13,6 @@ namespace UglyToad.PdfPig.Writer using PdfPig.Fonts.Standard14Fonts; using PdfPig.Fonts.TrueType.Parser; using Tokens; - using Colors; - using Xmp; using Util.JetBrains.Annotations; @@ -343,10 +341,16 @@ namespace UglyToad.PdfPig.Writer if (ArchiveStandard != PdfAStandard.None) { - catalogDictionary[NameToken.OutputIntents] = OutputIntentsFactory.GetOutputIntentsArray(x => context.WriteObject(memory, x)); - var xmpStream = XmpWriter.GenerateXmpStream(DocumentInformation, 1.7m, ArchiveStandard); - var xmpObj = context.WriteObject(memory, xmpStream); - catalogDictionary[NameToken.Metadata] = new IndirectReferenceToken(xmpObj.Number); + Func writerFunc = x => context.WriteObject(memory, x); + + PdfA1BRuleBuilder.Obey(catalogDictionary, writerFunc, DocumentInformation, ArchiveStandard); + + switch (ArchiveStandard) + { + case PdfAStandard.A1A: + PdfA1ARuleBuilder.Obey(catalogDictionary); + break; + } } var catalog = new DictionaryToken(catalogDictionary); diff --git a/src/UglyToad.PdfPig/Writer/Xmp/XmpWriter.cs b/src/UglyToad.PdfPig/Writer/Xmp/XmpWriter.cs index 84fd9d0d..62d4c4d5 100644 --- a/src/UglyToad.PdfPig/Writer/Xmp/XmpWriter.cs +++ b/src/UglyToad.PdfPig/Writer/Xmp/XmpWriter.cs @@ -135,6 +135,10 @@ namespace UglyToad.PdfPig.Writer.Xmp part = 1; conformance = "B"; break; + case PdfAStandard.A1A: + part = 1; + conformance = "A"; + break; default: throw new ArgumentOutOfRangeException(nameof(standard), standard, null); }