Partial optional content extraction: Add page.GetOptionalContents() and add tests.

This commit is contained in:
BobLd 2021-02-07 12:42:20 +00:00
parent be9fd4b071
commit 78e6e582cd
8 changed files with 212 additions and 0 deletions

View File

@ -0,0 +1,27 @@
using Xunit;
namespace UglyToad.PdfPig.Tests.Integration
{
public class OptionalContentTests
{
[Fact]
public void MarkedOptionalContent()
{
using (var document = PdfDocument.Open(IntegrationHelpers.GetDocumentPath("odwriteex.pdf")))
{
var page = document.GetPage(1);
var oc = page.GetOptionalContents();
Assert.Equal(3, oc.Count);
Assert.Contains("0", oc);
Assert.Contains("Dimentions", oc);
Assert.Contains("Text", oc);
Assert.Equal(1, oc["0"].Count);
Assert.Equal(2, oc["Dimentions"].Count);
Assert.Equal(1, oc["Text"].Count);
}
}
}
}

View File

@ -75,6 +75,7 @@
"UglyToad.PdfPig.Content.Letter",
"UglyToad.PdfPig.Content.MarkedContentElement",
"UglyToad.PdfPig.Content.MediaBox",
"UglyToad.PdfPig.Content.OptionalContentGroupElement",
"UglyToad.PdfPig.Content.Page",
"UglyToad.PdfPig.Content.PageRotationDegrees",
"UglyToad.PdfPig.Content.PageSize",

View File

@ -551,10 +551,12 @@
public static readonly NameToken Unix = new NameToken("Unix");
public static readonly NameToken Uri = new NameToken("URI");
public static readonly NameToken Url = new NameToken("URL");
public static readonly NameToken Usage = new NameToken("Usage");
public static readonly NameToken UserUnit = new NameToken("UserUnit");
// V
public static readonly NameToken V = new NameToken("V");
public static readonly NameToken V2 = new NameToken("V2");
public static readonly NameToken VE = new NameToken("VE");
public static readonly NameToken VerisignPpkvs = new NameToken("VeriSign.PPKVS");
public static readonly NameToken Version = new NameToken("Version");
public static readonly NameToken Vertices = new NameToken("Vertices");

View File

@ -0,0 +1,156 @@
using System;
using System.Collections.Generic;
using UglyToad.PdfPig.Tokens;
namespace UglyToad.PdfPig.Content
{
/// <summary>
/// An optional content group is a dictionary representing a collection of graphics
/// that can be made visible or invisible dynamically by users of viewers applications.
/// </summary>
public class OptionalContentGroupElement
{
/// <summary>
/// The type of PDF object that this dictionary describes.
/// <para>Must be OCG for an optional content group dictionary.</para>
/// </summary>
public string Type { get; }
/// <summary>
/// The name of the optional content group, suitable for presentation in a viewer application's user interface.
/// </summary>
public string Name { get; }
/// <summary>
/// A single name or an array containing any combination of names.
/// <para>Default value is 'View'.</para>
/// </summary>
public IReadOnlyList<string> Intent { get; }
/// <summary>
/// A usage dictionary describing the nature of the content controlled by the group.
/// </summary>
public IDictionary<string, object> Usage { get; }
/// <summary>
/// Underlying <see cref="MarkedContentElement"/>.
/// </summary>
public MarkedContentElement MarkedContent { get; }
internal OptionalContentGroupElement(MarkedContentElement markedContentElement)
{
MarkedContent = markedContentElement;
// Type - Required
if (markedContentElement.Properties.TryGet(NameToken.Type, out NameToken type))
{
Type = type.Data;
}
else if (markedContentElement.Properties.TryGet(NameToken.Type, out StringToken typeStr))
{
Type = typeStr.Data;
}
else
{
throw new ArgumentException($"Cannot parse optional content's {nameof(Type)} from {nameof(markedContentElement.Properties)}. This is a required field.", nameof(markedContentElement.Properties));
}
switch (Type)
{
case "OCG": // Optional content group dictionary
// Name - Required
if (markedContentElement.Properties.TryGet(NameToken.Name, out NameToken name))
{
Name = name.Data;
}
else if (markedContentElement.Properties.TryGet(NameToken.Name, out StringToken nameStr))
{
Name = nameStr.Data;
}
else
{
throw new ArgumentException($"Cannot parse optional content's {nameof(Name)} from {nameof(markedContentElement.Properties)}. This is a required field.", nameof(markedContentElement.Properties));
}
// Intent - Optional
if (markedContentElement.Properties.TryGet(NameToken.Intent, out NameToken intentName))
{
Intent = new string[] { intentName.Data };
}
else if (markedContentElement.Properties.TryGet(NameToken.Intent, out StringToken intentStr))
{
Intent = new string[] { intentStr.Data };
}
else if (markedContentElement.Properties.TryGet(NameToken.Intent, out ArrayToken intentArray))
{
List<string> intentList = new List<string>();
foreach (var token in intentArray.Data)
{
if (token is NameToken nameA)
{
intentList.Add(nameA.Data);
}
else if (token is StringToken strA)
{
intentList.Add(strA.Data);
}
else
{
throw new NotImplementedException();
}
}
Intent = intentList;
}
else
{
// Default value is 'View'.
Intent = new string[] { "View" };
}
// Usage - Optional
if (markedContentElement.Properties.TryGet(NameToken.Usage, out DictionaryToken usage))
{
throw new NotImplementedException();
}
break;
case "OCMD":
// OCGs - Optional
if (markedContentElement.Properties.TryGet(NameToken.Ocgs, out DictionaryToken ocgsD))
{
// dictionary or array
throw new NotImplementedException();
}
else if (markedContentElement.Properties.TryGet(NameToken.Ocgs, out ArrayToken ocgsA))
{
// dictionary or array
throw new NotImplementedException();
}
// P - Optional
if (markedContentElement.Properties.TryGet(NameToken.P, out NameToken p))
{
throw new NotImplementedException();
}
// VE - Optional
if (markedContentElement.Properties.TryGet(NameToken.VE, out ArrayToken ve))
{
throw new NotImplementedException();
}
break;
default:
throw new ArgumentException($"Unknown Optional Content of type '{Type}'.", nameof(Type));
}
}
/// <summary>
/// <inheritdoc/>
/// </summary>
public override string ToString()
{
return $"{Type} - {Name} [{string.Join(",", Intent)}]: {MarkedContent?.ToString()}";
}
}
}

View File

@ -166,6 +166,11 @@
/// </summary>
public IReadOnlyList<MarkedContentElement> GetMarkedContents() => Content.GetMarkedContents();
/// <summary>
/// Gets any optional content on the page.
/// </summary>
public IDictionary<string, IReadOnlyList<OptionalContentGroupElement>> GetOptionalContents() => Content.GetOptionalContents();
/// <summary>
/// Provides access to useful members which will change in future releases.
/// </summary>

View File

@ -2,6 +2,7 @@
{
using System;
using System.Collections.Generic;
using System.Linq;
using Core;
using Filters;
using Graphics;
@ -66,5 +67,24 @@
}
public IReadOnlyList<MarkedContentElement> GetMarkedContents() => markedContents;
public IDictionary<string, IReadOnlyList<OptionalContentGroupElement>> GetOptionalContents()
{
const string ocTag = "OC";
List<OptionalContentGroupElement> optionalContent = new List<OptionalContentGroupElement>();
// 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<OptionalContentGroupElement>);
}
}
}

View File

@ -63,6 +63,7 @@
/// <inheritdoc />
public void Run(IOperationContext operationContext)
{
// need to implement marked content here
}
/// <inheritdoc />