diff --git a/src/UglyToad.PdfPig.Tests/Integration/Documents/Layer pdf - 322_High_Holborn_building_Brochure.pdf b/src/UglyToad.PdfPig.Tests/Integration/Documents/Layer pdf - 322_High_Holborn_building_Brochure.pdf new file mode 100644 index 00000000..5363c286 Binary files /dev/null and b/src/UglyToad.PdfPig.Tests/Integration/Documents/Layer pdf - 322_High_Holborn_building_Brochure.pdf differ diff --git a/src/UglyToad.PdfPig.Tests/Integration/OptionalContentTests.cs b/src/UglyToad.PdfPig.Tests/Integration/OptionalContentTests.cs index 78719aa3..de7dcbf4 100644 --- a/src/UglyToad.PdfPig.Tests/Integration/OptionalContentTests.cs +++ b/src/UglyToad.PdfPig.Tests/Integration/OptionalContentTests.cs @@ -1,4 +1,6 @@ -using Xunit; +using System; +using UglyToad.PdfPig.Tokens; +using Xunit; namespace UglyToad.PdfPig.Tests.Integration { @@ -10,7 +12,7 @@ namespace UglyToad.PdfPig.Tests.Integration using (var document = PdfDocument.Open(IntegrationHelpers.GetDocumentPath("odwriteex.pdf"))) { var page = document.GetPage(1); - var oc = page.GetOptionalContents(); + var oc = page.ExperimentalAccess.GetOptionalContents(); Assert.Equal(3, oc.Count); @@ -23,5 +25,30 @@ namespace UglyToad.PdfPig.Tests.Integration Assert.Equal(1, oc["Text"].Count); } } + + [Fact] + public void MarkedOptionalContentRecursion() + { + using (var document = PdfDocument.Open(IntegrationHelpers.GetDocumentPath("Layer pdf - 322_High_Holborn_building_Brochure.pdf"))) + { + var page1 = document.GetPage(1); + var oc1 = page1.ExperimentalAccess.GetOptionalContents(); + Assert.Equal(16, oc1.Count); + Assert.Contains("NEW ARRANGEMENT", oc1); + + var page2 = document.GetPage(2); + var oc2 = page2.ExperimentalAccess.GetOptionalContents(); + Assert.Equal(15, oc2.Count); + Assert.DoesNotContain("NEW ARRANGEMENT", oc2); + Assert.Contains("WDL Shell text", oc2); + Assert.Equal(2, oc2["WDL Shell text"].Count); + + var page3 = document.GetPage(3); + var oc3 = page3.ExperimentalAccess.GetOptionalContents(); + Assert.Equal(15, oc3.Count); + Assert.Contains("WDL Shell text", oc3); + Assert.Equal(2, oc3["WDL Shell text"].Count); + } + } } } diff --git a/src/UglyToad.PdfPig/Content/OptionalContentGroupElement.cs b/src/UglyToad.PdfPig/Content/OptionalContentGroupElement.cs index 587b7d22..2732bcbd 100644 --- a/src/UglyToad.PdfPig/Content/OptionalContentGroupElement.cs +++ b/src/UglyToad.PdfPig/Content/OptionalContentGroupElement.cs @@ -30,7 +30,7 @@ namespace UglyToad.PdfPig.Content /// /// A usage dictionary describing the nature of the content controlled by the group. /// - public IDictionary Usage { get; } + public IReadOnlyDictionary Usage { get; } /// /// Underlying . @@ -110,7 +110,7 @@ namespace UglyToad.PdfPig.Content // Usage - Optional if (markedContentElement.Properties.TryGet(NameToken.Usage, out DictionaryToken usage)) { - throw new NotImplementedException(); + this.Usage = usage.Data; } break; @@ -119,29 +119,29 @@ namespace UglyToad.PdfPig.Content if (markedContentElement.Properties.TryGet(NameToken.Ocgs, out DictionaryToken ocgsD)) { // dictionary or array - throw new NotImplementedException(); + throw new NotImplementedException($"{NameToken.Ocgs}"); } else if (markedContentElement.Properties.TryGet(NameToken.Ocgs, out ArrayToken ocgsA)) { // dictionary or array - throw new NotImplementedException(); + throw new NotImplementedException($"{NameToken.Ocgs}"); } // P - Optional if (markedContentElement.Properties.TryGet(NameToken.P, out NameToken p)) { - throw new NotImplementedException(); + throw new NotImplementedException($"{NameToken.P}"); } // VE - Optional if (markedContentElement.Properties.TryGet(NameToken.VE, out ArrayToken ve)) { - throw new NotImplementedException(); + throw new NotImplementedException($"{NameToken.VE}"); } break; default: - throw new ArgumentException($"Unknown Optional Content of type '{Type}'.", nameof(Type)); + throw new ArgumentException($"Unknown Optional Content of type '{Type}' not known.", nameof(Type)); } } diff --git a/src/UglyToad.PdfPig/Content/Page.cs b/src/UglyToad.PdfPig/Content/Page.cs index f27bae0d..9ce53278 100644 --- a/src/UglyToad.PdfPig/Content/Page.cs +++ b/src/UglyToad.PdfPig/Content/Page.cs @@ -10,6 +10,7 @@ using Util.JetBrains.Annotations; using Tokenization.Scanner; using Graphics; + using System.Linq; /// /// Contains the content and provides access to methods of a single page in the . @@ -166,11 +167,6 @@ /// public IReadOnlyList GetMarkedContents() => Content.GetMarkedContents(); - /// - /// Gets any optional content on the page. - /// - public IDictionary> GetOptionalContents() => Content.GetOptionalContents(); - /// /// Provides access to useful members which will change in future releases. /// @@ -198,6 +194,42 @@ { return annotationProvider.GetAnnotations(); } + + /// + /// Gets any optional content on the page. + /// Does not handle XObjects and annotations for the time being. + /// + public IDictionary> GetOptionalContents() + { + // 4.10.2 + // Optional content in content stream + var mc = page.Content?.GetMarkedContents(); + + List mcesOptional = new List(); + GetOptionalContentsRecursively(mc, ref mcesOptional); + + // Optional content in XObjects and annotations + // TO DO + //var annots = GetAnnotations().ToList(); + + return mcesOptional.GroupBy(oc => oc.Name).ToDictionary(g => g.Key, g => g.ToList() as IReadOnlyList); + } + + private void GetOptionalContentsRecursively(IReadOnlyList markedContentElements, ref List mcesOptional) + { + foreach (var mce in markedContentElements) + { + if (mce.Tag == "OC") + { + mcesOptional.Add(new OptionalContentGroupElement(mce)); + // we don't recurse + } + else if (mce.Children?.Count > 0) + { + GetOptionalContentsRecursively(mce.Children, ref mcesOptional); + } + } + } } } } diff --git a/src/UglyToad.PdfPig/Content/PageContent.cs b/src/UglyToad.PdfPig/Content/PageContent.cs index 065a764c..0e645df3 100644 --- a/src/UglyToad.PdfPig/Content/PageContent.cs +++ b/src/UglyToad.PdfPig/Content/PageContent.cs @@ -67,24 +67,5 @@ } public IReadOnlyList GetMarkedContents() => markedContents; - - public IDictionary> GetOptionalContents() - { - const string ocTag = "OC"; - - List optionalContent = new List(); - - // 4.10.2 - // Optional content in content stream - foreach (var omc in GetMarkedContents().Where(mc => mc.Tag == ocTag)) - { - optionalContent.Add(new OptionalContentGroupElement(omc)); - } - - // Optional content in XObjects and annotations - // TO DO - - return optionalContent.GroupBy(oc => oc.Name).ToDictionary(g => g.Key, g => g.ToList() as IReadOnlyList); - } } }