mirror of
https://github.com/UglyToad/PdfPig.git
synced 2025-04-05 20:55:01 +08:00
Partial optional content extraction: Add page.GetOptionalContents() and add tests.
This commit is contained in:
parent
be9fd4b071
commit
78e6e582cd
BIN
src/UglyToad.PdfPig.Tests/Integration/Documents/odwriteex.pdf
Normal file
BIN
src/UglyToad.PdfPig.Tests/Integration/Documents/odwriteex.pdf
Normal file
Binary file not shown.
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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",
|
||||
|
@ -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");
|
||||
|
156
src/UglyToad.PdfPig/Content/OptionalContentGroupElement.cs
Normal file
156
src/UglyToad.PdfPig/Content/OptionalContentGroupElement.cs
Normal 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()}";
|
||||
}
|
||||
}
|
||||
}
|
@ -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>
|
||||
|
@ -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>);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -63,6 +63,7 @@
|
||||
/// <inheritdoc />
|
||||
public void Run(IOperationContext operationContext)
|
||||
{
|
||||
// need to implement marked content here
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
Loading…
Reference in New Issue
Block a user