#127 pdf/a-1a compliance

adds struct tree and markinfo dictionaries to support pdf/a-1a compliance.
This commit is contained in:
Eliot Jones
2020-03-29 17:55:02 +01:00
parent 5f45ee53bd
commit 7f1bf094bc
7 changed files with 104 additions and 7 deletions

View File

@@ -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) private static void WriteFile(string name, byte[] bytes)
{ {
try try

View File

@@ -334,6 +334,7 @@
public static readonly NameToken Mac = new NameToken("Mac"); public static readonly NameToken Mac = new NameToken("Mac");
public static readonly NameToken MacExpertEncoding = new NameToken("MacExpertEncoding"); public static readonly NameToken MacExpertEncoding = new NameToken("MacExpertEncoding");
public static readonly NameToken MacRomanEncoding = new NameToken("MacRomanEncoding"); 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 MarkInfo = new NameToken("MarkInfo");
public static readonly NameToken Mask = new NameToken("Mask"); public static readonly NameToken Mask = new NameToken("Mask");
public static readonly NameToken Matrix = new NameToken("Matrix"); public static readonly NameToken Matrix = new NameToken("Matrix");

View File

@@ -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<NameToken, IToken> catalog)
{
var structTreeRoot = GenerateStructTree();
catalog[NameToken.StructTreeRoot] = structTreeRoot;
var markInfoDictionary = new DictionaryToken(new Dictionary<NameToken, IToken>
{
{NameToken.Marked, BooleanToken.True}
});
catalog[NameToken.MarkInfo] = markInfoDictionary;
}
private static DictionaryToken GenerateStructTree()
{
var rootDictionary = new Dictionary<NameToken, IToken>
{
{NameToken.Type, NameToken.StructTreeRoot}
};
var structTreeRoot = new DictionaryToken(rootDictionary);
return structTreeRoot;
}
}
}

View File

@@ -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<NameToken, IToken> catalog, Func<IToken, ObjectToken> 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);
}
}
}

View File

@@ -12,6 +12,10 @@
/// <summary> /// <summary>
/// Compliance with PDF/A1-B. Level B (basic) conformance are standards necessary for the reliable reproduction of a document's visual appearance. /// Compliance with PDF/A1-B. Level B (basic) conformance are standards necessary for the reliable reproduction of a document's visual appearance.
/// </summary> /// </summary>
A1B = 1 A1B = 1,
/// <summary>
/// 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.
/// </summary>
A1A = 2
} }
} }

View File

@@ -13,8 +13,6 @@ namespace UglyToad.PdfPig.Writer
using PdfPig.Fonts.Standard14Fonts; using PdfPig.Fonts.Standard14Fonts;
using PdfPig.Fonts.TrueType.Parser; using PdfPig.Fonts.TrueType.Parser;
using Tokens; using Tokens;
using Colors;
using Xmp;
using Util.JetBrains.Annotations; using Util.JetBrains.Annotations;
@@ -343,10 +341,16 @@ namespace UglyToad.PdfPig.Writer
if (ArchiveStandard != PdfAStandard.None) if (ArchiveStandard != PdfAStandard.None)
{ {
catalogDictionary[NameToken.OutputIntents] = OutputIntentsFactory.GetOutputIntentsArray(x => context.WriteObject(memory, x)); Func<IToken, ObjectToken> writerFunc = x => context.WriteObject(memory, x);
var xmpStream = XmpWriter.GenerateXmpStream(DocumentInformation, 1.7m, ArchiveStandard);
var xmpObj = context.WriteObject(memory, xmpStream); PdfA1BRuleBuilder.Obey(catalogDictionary, writerFunc, DocumentInformation, ArchiveStandard);
catalogDictionary[NameToken.Metadata] = new IndirectReferenceToken(xmpObj.Number);
switch (ArchiveStandard)
{
case PdfAStandard.A1A:
PdfA1ARuleBuilder.Obey(catalogDictionary);
break;
}
} }
var catalog = new DictionaryToken(catalogDictionary); var catalog = new DictionaryToken(catalogDictionary);

View File

@@ -135,6 +135,10 @@ namespace UglyToad.PdfPig.Writer.Xmp
part = 1; part = 1;
conformance = "B"; conformance = "B";
break; break;
case PdfAStandard.A1A:
part = 1;
conformance = "A";
break;
default: default:
throw new ArgumentOutOfRangeException(nameof(standard), standard, null); throw new ArgumentOutOfRangeException(nameof(standard), standard, null);
} }