mirror of
https://github.com/UglyToad/PdfPig.git
synced 2025-09-19 19:07:56 +08:00
@@ -91,6 +91,16 @@
|
||||
{
|
||||
}
|
||||
|
||||
public void BeginMarkedContent(NameToken name, NameToken propertyDictionaryName, DictionaryToken properties)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void EndMarkedContent()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private class TestFontFactory : IFontFactory
|
||||
{
|
||||
public IFont Get(DictionaryToken dictionary, bool isLenientParsing)
|
||||
|
@@ -64,6 +64,7 @@
|
||||
"UglyToad.PdfPig.Annotations.AnnotationFlags",
|
||||
"UglyToad.PdfPig.Annotations.AnnotationType",
|
||||
"UglyToad.PdfPig.Annotations.QuadPointsQuadrilateral",
|
||||
"UglyToad.PdfPig.Content.ArtifactMarkedContentElement",
|
||||
"UglyToad.PdfPig.Content.Catalog",
|
||||
"UglyToad.PdfPig.Content.CropBox",
|
||||
"UglyToad.PdfPig.Content.DocumentInformation",
|
||||
@@ -72,6 +73,7 @@
|
||||
"UglyToad.PdfPig.Content.InlineImage",
|
||||
"UglyToad.PdfPig.Content.IPdfImage",
|
||||
"UglyToad.PdfPig.Content.Letter",
|
||||
"UglyToad.PdfPig.Content.MarkedContentElement",
|
||||
"UglyToad.PdfPig.Content.Page",
|
||||
"UglyToad.PdfPig.Content.PageRotationDegrees",
|
||||
"UglyToad.PdfPig.Content.PageSize",
|
||||
|
@@ -65,6 +65,7 @@
|
||||
public static readonly NameToken BleedBox = new NameToken("BleedBox");
|
||||
public static readonly NameToken Bm = new NameToken("BM");
|
||||
public static readonly NameToken Border = new NameToken("Border");
|
||||
public static readonly NameToken Bottom = new NameToken("Bottom");
|
||||
public static readonly NameToken Bounds = new NameToken("Bounds");
|
||||
public static readonly NameToken Bpc = new NameToken("BPC");
|
||||
public static readonly NameToken Bs = new NameToken("BS");
|
||||
@@ -307,6 +308,7 @@
|
||||
public static readonly NameToken Lc = new NameToken("LC");
|
||||
public static readonly NameToken Le = new NameToken("LE");
|
||||
public static readonly NameToken Leading = new NameToken("Leading");
|
||||
public static readonly NameToken Left = new NameToken("Left");
|
||||
public static readonly NameToken LegalAttestation = new NameToken("LegalAttestation");
|
||||
public static readonly NameToken Length = new NameToken("Length");
|
||||
public static readonly NameToken Length1 = new NameToken("Length1");
|
||||
@@ -448,6 +450,7 @@
|
||||
public static readonly NameToken Resources = new NameToken("Resources");
|
||||
public static readonly NameToken Rgb = new NameToken("RGB");
|
||||
public static readonly NameToken Ri = new NameToken("RI");
|
||||
public static readonly NameToken Right = new NameToken("Right");
|
||||
public static readonly NameToken RoleMap = new NameToken("RoleMap");
|
||||
public static readonly NameToken Root = new NameToken("Root");
|
||||
public static readonly NameToken Rotate = new NameToken("Rotate");
|
||||
@@ -513,6 +516,7 @@
|
||||
public static readonly NameToken Title = new NameToken("Title");
|
||||
public static readonly NameToken Tk = new NameToken("TK");
|
||||
public static readonly NameToken Tm = new NameToken("TM");
|
||||
public static readonly NameToken Top = new NameToken("Top");
|
||||
public static readonly NameToken ToUnicode = new NameToken("ToUnicode");
|
||||
public static readonly NameToken Tr = new NameToken("TR");
|
||||
public static readonly NameToken Tr2 = new NameToken("TR2");
|
||||
|
145
src/UglyToad.PdfPig/Content/ArtifactMarkedContentElement.cs
Normal file
145
src/UglyToad.PdfPig/Content/ArtifactMarkedContentElement.cs
Normal file
@@ -0,0 +1,145 @@
|
||||
namespace UglyToad.PdfPig.Content
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using Core;
|
||||
using Tokens;
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Artifacts are graphics objects that are not part of the author's original content but rather are
|
||||
/// generated by the conforming writer in the course of pagination, layout, or other strictly mechanical
|
||||
/// processes.
|
||||
/// <para>Artifacts may also be used to describe areas of the document where the author uses a graphical
|
||||
/// background, with the goal of enhancing the visual experience. In such a case, the background is not
|
||||
/// required for understanding the content. - PDF 32000-1:2008, Section 14.8.2.2</para>
|
||||
/// </summary>
|
||||
public class ArtifactMarkedContentElement : MarkedContentElement
|
||||
{
|
||||
/// <summary>
|
||||
/// The artifact's type: Pagination, Layout, Page, or (PDF 1.7) Background.
|
||||
/// </summary>
|
||||
public ArtifactType Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The artifact's subtype. Standard values are Header, Footer, and Watermark.
|
||||
/// Additional values may be specified for this entry, provided they comply with the naming conventions.
|
||||
/// </summary>
|
||||
public string SubType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The artifact's attribute owners.
|
||||
/// </summary>
|
||||
public string AttributeOwners { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The artifact's bounding box.
|
||||
/// </summary>
|
||||
public PdfRectangle? BoundingBox { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The names of regions this element is attached to.
|
||||
/// </summary>
|
||||
public IReadOnlyList<NameToken> Attached { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Is the artifact attached to the top edge?
|
||||
/// </summary>
|
||||
public bool IsTopAttached => IsAttached(NameToken.Top);
|
||||
|
||||
/// <summary>
|
||||
/// Is the artifact attached to the bottom edge?
|
||||
/// </summary>
|
||||
public bool IsBottomAttached => IsAttached(NameToken.Bottom);
|
||||
|
||||
/// <summary>
|
||||
/// Is the artifact attached to the left edge?
|
||||
/// </summary>
|
||||
public bool IsLeftAttached => IsAttached(NameToken.Left);
|
||||
|
||||
/// <summary>
|
||||
/// Is the artifact attached to the right edge?
|
||||
/// </summary>
|
||||
public bool IsRightAttached => IsAttached(NameToken.Right);
|
||||
|
||||
internal ArtifactMarkedContentElement(int markedContentIdentifier, NameToken tag, DictionaryToken properties,
|
||||
string language,
|
||||
string actualText,
|
||||
string alternateDescription,
|
||||
string expandedForm,
|
||||
ArtifactType artifactType,
|
||||
string subType,
|
||||
string attributeOwners,
|
||||
PdfRectangle? boundingBox,
|
||||
IReadOnlyList<NameToken> attached,
|
||||
IReadOnlyList<MarkedContentElement> children,
|
||||
IReadOnlyList<Letter> letters,
|
||||
IReadOnlyList<PdfPath> paths,
|
||||
IReadOnlyList<IPdfImage> images,
|
||||
int index)
|
||||
: base(markedContentIdentifier, tag, properties, language,
|
||||
actualText,
|
||||
alternateDescription,
|
||||
expandedForm,
|
||||
true,
|
||||
children,
|
||||
letters,
|
||||
paths,
|
||||
images,
|
||||
index)
|
||||
{
|
||||
Type = artifactType;
|
||||
SubType = subType;
|
||||
AttributeOwners = attributeOwners;
|
||||
BoundingBox = boundingBox;
|
||||
Attached = attached ?? EmptyArray<NameToken>.Instance;
|
||||
}
|
||||
|
||||
private bool IsAttached(NameToken edge)
|
||||
{
|
||||
foreach (var name in Attached)
|
||||
{
|
||||
if (name == edge)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If present, shall be one of the names Pagination, Layout, Page, or (PDF 1.7) Background.
|
||||
/// </summary>
|
||||
public enum ArtifactType
|
||||
{
|
||||
/// <summary>
|
||||
/// Unknown artifact type.
|
||||
/// </summary>
|
||||
Unknown,
|
||||
|
||||
/// <summary>
|
||||
/// Ancillary page features such as running heads and folios (page numbers).
|
||||
/// </summary>
|
||||
Pagination,
|
||||
|
||||
/// <summary>
|
||||
/// Purely cosmetic typographical or design elements such as footnote rules or background screens.
|
||||
/// </summary>
|
||||
Layout,
|
||||
|
||||
/// <summary>
|
||||
/// Production aids extraneous to the document itself, such as cut marks and colour bars.
|
||||
/// </summary>
|
||||
Page,
|
||||
|
||||
/// <summary>
|
||||
/// (PDF 1.7) Images, patterns or coloured blocks that either run the entire length and/or
|
||||
/// width of the page or the entire dimensions of a structural element. Background artifacts
|
||||
/// typically serve as a background for content shown either on top of or placed adjacent to
|
||||
/// that background.
|
||||
/// <para>A background artifact can further be classified as visual content that serves to enhance the user experience, that lies under the actual content, and that is not required except to retain visual fidelity.</para>
|
||||
/// </summary>
|
||||
Background
|
||||
}
|
||||
}
|
||||
}
|
@@ -23,5 +23,7 @@
|
||||
IFont GetFontDirectly(IndirectReferenceToken fontReferenceToken, bool isLenientParsing);
|
||||
|
||||
bool TryGetNamedColorSpace(NameToken name, out ResourceColorSpace namedColorSpace);
|
||||
|
||||
DictionaryToken GetMarkedContentPropertiesDictionary(NameToken name);
|
||||
}
|
||||
}
|
115
src/UglyToad.PdfPig/Content/MarkedContentElement.cs
Normal file
115
src/UglyToad.PdfPig/Content/MarkedContentElement.cs
Normal file
@@ -0,0 +1,115 @@
|
||||
namespace UglyToad.PdfPig.Content
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Core;
|
||||
using Tokens;
|
||||
|
||||
/// <summary>
|
||||
/// A marked content element can be used to provide application specific data in the
|
||||
/// page's content stream. Interpretation of the marked content is outside of the PDF specification.
|
||||
/// </summary>
|
||||
public class MarkedContentElement
|
||||
{
|
||||
/// <summary>
|
||||
/// Marked-content identifier.
|
||||
/// </summary>
|
||||
public int MarkedContentIdentifier { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The index of this marked content element in the set of marked content in the page.
|
||||
/// <see cref="Children"/> marked content elements will have the same index as the parent.
|
||||
/// </summary>
|
||||
public int Index { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A name indicating the role or significance of the point.
|
||||
/// </summary>
|
||||
public string Tag { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The properties for this element.
|
||||
/// </summary>
|
||||
public DictionaryToken Properties { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Is the marked content an artifact, see <see cref="ArtifactMarkedContentElement"/>.
|
||||
/// </summary>
|
||||
public bool IsArtifact { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Child contents.
|
||||
/// </summary>
|
||||
public IReadOnlyList<MarkedContentElement> Children { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Letters contained in this marked content.
|
||||
/// </summary>
|
||||
public IReadOnlyList<Letter> Letters { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Paths contained in this marked content.
|
||||
/// </summary>
|
||||
public IReadOnlyList<PdfPath> Paths { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Images contained in this marked content.
|
||||
/// </summary>
|
||||
public IReadOnlyList<IPdfImage> Images { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The natural language specification.
|
||||
/// </summary>
|
||||
public string Language { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The replacement text.
|
||||
/// </summary>
|
||||
public string ActualText { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The alternate description.
|
||||
/// </summary>
|
||||
public string AlternateDescription { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The abbreviation expansion text.
|
||||
/// </summary>
|
||||
public string ExpandedForm { get; }
|
||||
|
||||
public MarkedContentElement(int markedContentIdentifier, NameToken tag, DictionaryToken properties,
|
||||
string language,
|
||||
string actualText,
|
||||
string alternateDescription,
|
||||
string expandedForm,
|
||||
bool isArtifact,
|
||||
IReadOnlyList<MarkedContentElement> children,
|
||||
IReadOnlyList<Letter> letters,
|
||||
IReadOnlyList<PdfPath> paths,
|
||||
IReadOnlyList<IPdfImage> images,
|
||||
int index)
|
||||
{
|
||||
MarkedContentIdentifier = markedContentIdentifier;
|
||||
Tag = tag;
|
||||
Language = language;
|
||||
ActualText = actualText;
|
||||
AlternateDescription = alternateDescription;
|
||||
ExpandedForm = expandedForm;
|
||||
Properties = properties ?? new DictionaryToken(new Dictionary<NameToken, IToken>());
|
||||
IsArtifact = isArtifact;
|
||||
|
||||
Children = children ?? throw new ArgumentNullException(nameof(children));
|
||||
Letters = letters ?? throw new ArgumentNullException(nameof(letters));
|
||||
Paths = paths ?? throw new ArgumentNullException(nameof(paths));
|
||||
Images = images ?? throw new ArgumentNullException(nameof(images));
|
||||
|
||||
Index = index;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Index={Index}, MCID={MarkedContentIdentifier}, Tag={Tag}, Properties={Properties}, Contents={Children.Count}";
|
||||
}
|
||||
}
|
||||
}
|
@@ -153,6 +153,11 @@
|
||||
/// </summary>
|
||||
public IEnumerable<IPdfImage> GetImages() => Content.GetImages();
|
||||
|
||||
/// <summary>
|
||||
/// Gets any marked content on the page.
|
||||
/// </summary>
|
||||
public IReadOnlyList<MarkedContentElement> GetMarkedContents() => Content.GetMarkedContents();
|
||||
|
||||
/// <summary>
|
||||
/// Provides access to useful members which will change in future releases.
|
||||
/// </summary>
|
||||
|
@@ -8,7 +8,6 @@
|
||||
using Graphics.Operations;
|
||||
using Tokenization.Scanner;
|
||||
using XObjects;
|
||||
using Geometry;
|
||||
|
||||
/// <summary>
|
||||
/// Wraps content parsed from a page content stream for access.
|
||||
@@ -20,10 +19,10 @@
|
||||
internal class PageContent
|
||||
{
|
||||
private readonly IReadOnlyList<Union<XObjectContentRecord, InlineImage>> images;
|
||||
private readonly IReadOnlyList<MarkedContentElement> markedContents;
|
||||
private readonly IPdfTokenScanner pdfScanner;
|
||||
private readonly IFilterProvider filterProvider;
|
||||
private readonly IResourceStore resourceStore;
|
||||
private readonly bool isLenientParsing;
|
||||
|
||||
internal IReadOnlyList<IGraphicsStateOperation> GraphicsStateOperations { get; }
|
||||
|
||||
@@ -34,32 +33,32 @@
|
||||
internal PageContent(IReadOnlyList<IGraphicsStateOperation> graphicsStateOperations, IReadOnlyList<Letter> letters,
|
||||
IReadOnlyList<PdfPath> paths,
|
||||
IReadOnlyList<Union<XObjectContentRecord, InlineImage>> images,
|
||||
IReadOnlyList<MarkedContentElement> markedContents,
|
||||
IPdfTokenScanner pdfScanner,
|
||||
IFilterProvider filterProvider,
|
||||
IResourceStore resourceStore,
|
||||
bool isLenientParsing)
|
||||
IResourceStore resourceStore)
|
||||
{
|
||||
GraphicsStateOperations = graphicsStateOperations;
|
||||
Letters = letters;
|
||||
Paths = paths;
|
||||
this.images = images;
|
||||
this.markedContents = markedContents;
|
||||
this.pdfScanner = pdfScanner ?? throw new ArgumentNullException(nameof(pdfScanner));
|
||||
this.filterProvider = filterProvider ?? throw new ArgumentNullException(nameof(filterProvider));
|
||||
this.resourceStore = resourceStore ?? throw new ArgumentNullException(nameof(resourceStore));
|
||||
this.isLenientParsing = isLenientParsing;
|
||||
}
|
||||
|
||||
public IEnumerable<IPdfImage> GetImages()
|
||||
{
|
||||
foreach (var image in images)
|
||||
{
|
||||
|
||||
IPdfImage result = null;
|
||||
image.Match(x => { result = XObjectFactory.ReadImage(x, pdfScanner, filterProvider, resourceStore, isLenientParsing); },
|
||||
x => { result = x; });
|
||||
var result = image.Match<IPdfImage>(x => XObjectFactory.ReadImage(x, pdfScanner, filterProvider, resourceStore),
|
||||
x => x);
|
||||
|
||||
yield return result;
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyList<MarkedContentElement> GetMarkedContents() => markedContents;
|
||||
}
|
||||
}
|
||||
|
@@ -22,6 +22,8 @@
|
||||
|
||||
private readonly Dictionary<NameToken, ResourceColorSpace> namedColorSpaces = new Dictionary<NameToken, ResourceColorSpace>();
|
||||
|
||||
private readonly Dictionary<NameToken, DictionaryToken> markedContentProperties = new Dictionary<NameToken, DictionaryToken>();
|
||||
|
||||
private (NameToken name, IFont font) lastLoadedFont;
|
||||
|
||||
public ResourceStore(IPdfTokenScanner scanner, IFontFactory fontFactory)
|
||||
@@ -101,6 +103,21 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (resourceDictionary.TryGet(NameToken.Properties, scanner, out DictionaryToken markedContentPropertiesList))
|
||||
{
|
||||
foreach (var pair in markedContentPropertiesList.Data)
|
||||
{
|
||||
var key = NameToken.Create(pair.Key);
|
||||
|
||||
if (!DirectObjectFinder.TryGet(pair.Value, scanner, out DictionaryToken namedProperties))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
markedContentProperties[key] = namedProperties;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void UnloadResourceDictionary()
|
||||
@@ -207,5 +224,10 @@
|
||||
{
|
||||
return extendedGraphicsStates[name];
|
||||
}
|
||||
|
||||
public DictionaryToken GetMarkedContentPropertiesDictionary(NameToken name)
|
||||
{
|
||||
return markedContentProperties.TryGetValue(name, out var result) ? result : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -4,6 +4,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Colors;
|
||||
using Content;
|
||||
using Core;
|
||||
using Filters;
|
||||
@@ -34,6 +35,11 @@
|
||||
/// </summary>
|
||||
private readonly List<Union<XObjectContentRecord, InlineImage>> images = new List<Union<XObjectContentRecord, InlineImage>>();
|
||||
|
||||
/// <summary>
|
||||
/// Stores each marked content as it is encountered in the content stream.
|
||||
/// </summary>
|
||||
private readonly List<MarkedContentElement> markedContents = new List<MarkedContentElement>();
|
||||
|
||||
private readonly IResourceStore resourceStore;
|
||||
private readonly UserSpaceUnit userSpaceUnit;
|
||||
private readonly PageRotationDegrees rotation;
|
||||
@@ -42,6 +48,7 @@
|
||||
private readonly IPageContentParser pageContentParser;
|
||||
private readonly IFilterProvider filterProvider;
|
||||
private readonly ILog log;
|
||||
private readonly MarkedContentStack markedContentStack = new MarkedContentStack();
|
||||
|
||||
private Stack<CurrentGraphicsState> graphicsStack = new Stack<CurrentGraphicsState>();
|
||||
private IFont activeExtendedGraphicsStateFont;
|
||||
@@ -100,7 +107,7 @@
|
||||
|
||||
ProcessOperations(operations);
|
||||
|
||||
return new PageContent(operations, letters, paths, images, pdfScanner, filterProvider, resourceStore, isLenientParsing);
|
||||
return new PageContent(operations, letters, paths, images, markedContents, pdfScanner, filterProvider, resourceStore);
|
||||
}
|
||||
|
||||
private void ProcessOperations(IReadOnlyList<IGraphicsStateOperation> operations)
|
||||
@@ -231,6 +238,8 @@
|
||||
|
||||
letters.Add(letter);
|
||||
|
||||
markedContentStack.AddLetter(letter);
|
||||
|
||||
double tx, ty;
|
||||
if (font.IsVertical)
|
||||
{
|
||||
@@ -314,11 +323,19 @@
|
||||
|
||||
if (subType.Equals(NameToken.Ps))
|
||||
{
|
||||
xObjects[XObjectType.PostScript].Add(new XObjectContentRecord(XObjectType.PostScript, xObjectStream, matrix, state.RenderingIntent));
|
||||
var contentRecord = new XObjectContentRecord(XObjectType.PostScript, xObjectStream, matrix, state.RenderingIntent,
|
||||
state.CurrentStrokingColor?.ColorSpace ?? ColorSpace.DeviceRGB);
|
||||
|
||||
xObjects[XObjectType.PostScript].Add(contentRecord);
|
||||
}
|
||||
else if (subType.Equals(NameToken.Image))
|
||||
{
|
||||
images.Add(Union<XObjectContentRecord, InlineImage>.One(new XObjectContentRecord(XObjectType.Image, xObjectStream, matrix, state.RenderingIntent)));
|
||||
var contentRecord = new XObjectContentRecord(XObjectType.Image, xObjectStream, matrix, state.RenderingIntent,
|
||||
state.CurrentStrokingColor?.ColorSpace ?? ColorSpace.DeviceRGB);
|
||||
|
||||
images.Add(Union<XObjectContentRecord, InlineImage>.One(contentRecord));
|
||||
|
||||
markedContentStack.AddXObject(contentRecord, pdfScanner, filterProvider, resourceStore);
|
||||
}
|
||||
else if (subType.Equals(NameToken.Form))
|
||||
{
|
||||
@@ -387,6 +404,7 @@
|
||||
if (CurrentPath != null && CurrentPath.Commands.Count > 0 && !currentPathAdded)
|
||||
{
|
||||
paths.Add(CurrentPath);
|
||||
markedContentStack.AddPath(CurrentPath);
|
||||
}
|
||||
|
||||
CurrentPath = new PdfPath();
|
||||
@@ -402,6 +420,7 @@
|
||||
else
|
||||
{
|
||||
paths.Add(CurrentPath);
|
||||
markedContentStack.AddPath(CurrentPath);
|
||||
currentPathAdded = true;
|
||||
}
|
||||
}
|
||||
@@ -415,6 +434,7 @@
|
||||
else
|
||||
{
|
||||
paths.Add(CurrentPath);
|
||||
markedContentStack.AddPath(CurrentPath);
|
||||
currentPathAdded = true;
|
||||
}
|
||||
}
|
||||
@@ -423,6 +443,7 @@
|
||||
{
|
||||
CurrentPath.ClosePath();
|
||||
paths.Add(CurrentPath);
|
||||
markedContentStack.AddPath(CurrentPath);
|
||||
CurrentPath = null;
|
||||
currentPathAdded = false;
|
||||
}
|
||||
@@ -500,9 +521,31 @@
|
||||
|
||||
images.Add(Union<XObjectContentRecord, InlineImage>.Two(image));
|
||||
|
||||
markedContentStack.AddImage(image);
|
||||
|
||||
inlineImageBuilder = null;
|
||||
}
|
||||
|
||||
public void BeginMarkedContent(NameToken name, NameToken propertyDictionaryName, DictionaryToken properties)
|
||||
{
|
||||
if (propertyDictionaryName != null)
|
||||
{
|
||||
var actual = resourceStore.GetMarkedContentPropertiesDictionary(propertyDictionaryName);
|
||||
|
||||
properties = actual ?? properties;
|
||||
}
|
||||
|
||||
markedContentStack.Push(name, properties);
|
||||
}
|
||||
|
||||
public void EndMarkedContent()
|
||||
{
|
||||
if (markedContentStack.CanPop)
|
||||
{
|
||||
markedContents.Add(markedContentStack.Pop(pdfScanner));
|
||||
}
|
||||
}
|
||||
|
||||
private void AdjustTextMatrix(double tx, double ty)
|
||||
{
|
||||
var matrix = TransformationMatrix.GetTranslationMatrix(tx, ty);
|
||||
|
@@ -1,8 +1,8 @@
|
||||
namespace UglyToad.PdfPig.Graphics
|
||||
{
|
||||
using PdfPig.Core;
|
||||
using System.Collections.Generic;
|
||||
using Tokens;
|
||||
using PdfPig.Core;
|
||||
using Util.JetBrains.Annotations;
|
||||
|
||||
/// <summary>
|
||||
@@ -97,6 +97,16 @@
|
||||
/// </summary>
|
||||
void ClosePath();
|
||||
|
||||
/// <summary>
|
||||
/// Indicate that a marked content region is started.
|
||||
/// </summary>
|
||||
void BeginMarkedContent(NameToken name, NameToken propertyDictionaryName, DictionaryToken properties);
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the current marked content region is complete.
|
||||
/// </summary>
|
||||
void EndMarkedContent();
|
||||
|
||||
/// <summary>
|
||||
/// Update the graphics state to apply the state from the named ExtGState dictionary.
|
||||
/// </summary>
|
||||
|
208
src/UglyToad.PdfPig/Graphics/MarkedContentStack.cs
Normal file
208
src/UglyToad.PdfPig/Graphics/MarkedContentStack.cs
Normal file
@@ -0,0 +1,208 @@
|
||||
namespace UglyToad.PdfPig.Graphics
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Content;
|
||||
using Filters;
|
||||
using PdfPig.Core;
|
||||
using Tokenization.Scanner;
|
||||
using Tokens;
|
||||
using XObjects;
|
||||
|
||||
/// <summary>
|
||||
/// Handles building <see cref="MarkedContentElement"/>s.
|
||||
/// </summary>
|
||||
internal class MarkedContentStack
|
||||
{
|
||||
private readonly Stack<MarkedContentElementActiveBuilder> builderStack = new Stack<MarkedContentElementActiveBuilder>();
|
||||
|
||||
private int number;
|
||||
private MarkedContentElementActiveBuilder top;
|
||||
|
||||
public bool CanPop => top != null;
|
||||
|
||||
public void Push(NameToken name, DictionaryToken properties)
|
||||
{
|
||||
if (builderStack.Count > 0)
|
||||
{
|
||||
number++;
|
||||
}
|
||||
|
||||
top = new MarkedContentElementActiveBuilder(number, name, properties);
|
||||
builderStack.Push(top);
|
||||
}
|
||||
|
||||
public MarkedContentElement Pop(IPdfTokenScanner pdfScanner)
|
||||
{
|
||||
var builder = builderStack.Pop();
|
||||
|
||||
var result = builder.Build(pdfScanner);
|
||||
|
||||
if (builderStack.Count > 0)
|
||||
{
|
||||
top = builderStack.Peek();
|
||||
top.Children.Add(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
top = null;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void AddLetter(Letter letter)
|
||||
{
|
||||
top?.AddLetter(letter);
|
||||
}
|
||||
|
||||
public void AddPath(PdfPath path)
|
||||
{
|
||||
top?.AddPath(path);
|
||||
}
|
||||
|
||||
public void AddImage(IPdfImage image)
|
||||
{
|
||||
top?.AddImage(image);
|
||||
}
|
||||
|
||||
public void AddXObject(XObjectContentRecord xObject,
|
||||
IPdfTokenScanner scanner,
|
||||
IFilterProvider filterProvider,
|
||||
IResourceStore resourceStore)
|
||||
{
|
||||
if (top != null && xObject.Type == XObjectType.Image)
|
||||
{
|
||||
var image = XObjectFactory.ReadImage(xObject, scanner, filterProvider, resourceStore);
|
||||
top?.AddImage(image);
|
||||
}
|
||||
}
|
||||
|
||||
private class MarkedContentElementActiveBuilder
|
||||
{
|
||||
private readonly int number;
|
||||
private readonly NameToken name;
|
||||
private readonly DictionaryToken properties;
|
||||
|
||||
private readonly List<Letter> letters = new List<Letter>();
|
||||
private readonly List<PdfPath> paths = new List<PdfPath>();
|
||||
private readonly List<IPdfImage> images = new List<IPdfImage>();
|
||||
|
||||
public List<MarkedContentElement> Children { get; } = new List<MarkedContentElement>();
|
||||
|
||||
public MarkedContentElementActiveBuilder(int number, NameToken name, DictionaryToken properties)
|
||||
{
|
||||
this.number = number;
|
||||
this.name = name;
|
||||
this.properties = properties ?? new DictionaryToken(new Dictionary<NameToken, IToken>());
|
||||
}
|
||||
|
||||
public void AddLetter(Letter letter)
|
||||
{
|
||||
letters.Add(letter);
|
||||
}
|
||||
|
||||
public void AddImage(IPdfImage image)
|
||||
{
|
||||
images.Add(image);
|
||||
}
|
||||
|
||||
public void AddPath(PdfPath path)
|
||||
{
|
||||
paths.Add(path);
|
||||
}
|
||||
|
||||
public MarkedContentElement Build(IPdfTokenScanner pdfScanner)
|
||||
{
|
||||
var mcid = -1;
|
||||
if (properties.TryGet(NameToken.Mcid, pdfScanner, out NumericToken mcidToken))
|
||||
{
|
||||
mcid = mcidToken.Int;
|
||||
}
|
||||
|
||||
var language = GetOptional(NameToken.Lang, pdfScanner);
|
||||
var actualText = GetOptional(NameToken.ActualText, pdfScanner);
|
||||
var alternateDescription = GetOptional(NameToken.Alternate, pdfScanner);
|
||||
var expandedForm = GetOptional(NameToken.E, pdfScanner);
|
||||
|
||||
if (name != NameToken.Artifact)
|
||||
{
|
||||
return new MarkedContentElement(mcid, name, properties,
|
||||
language,
|
||||
actualText,
|
||||
alternateDescription,
|
||||
expandedForm,
|
||||
false,
|
||||
Children,
|
||||
letters,
|
||||
paths,
|
||||
images,
|
||||
number);
|
||||
}
|
||||
|
||||
var artifactType = ArtifactMarkedContentElement.ArtifactType.Unknown;
|
||||
if (properties.TryGet(NameToken.Type, pdfScanner, out IDataToken<string> typeToken)
|
||||
&& Enum.TryParse(typeToken.Data, true, out ArtifactMarkedContentElement.ArtifactType parsedType))
|
||||
{
|
||||
artifactType = parsedType;
|
||||
}
|
||||
|
||||
var subType = GetOptional(NameToken.Subtype, pdfScanner);
|
||||
var attributeOwners = GetOptional(NameToken.O, pdfScanner);
|
||||
|
||||
var boundingBox = default(PdfRectangle?);
|
||||
if (properties.TryGet(NameToken.Bbox, pdfScanner, out ArrayToken arrayToken)
|
||||
&& arrayToken.Length == 6)
|
||||
{
|
||||
var left = arrayToken[2] as NumericToken;
|
||||
var bottom = arrayToken[3] as NumericToken;
|
||||
var right = arrayToken[4] as NumericToken;
|
||||
var top = arrayToken[5] as NumericToken;
|
||||
|
||||
if (left != null && bottom != null && right != null && top != null)
|
||||
{
|
||||
boundingBox = new PdfRectangle(left.Double, bottom.Double, right.Double, top.Double);
|
||||
}
|
||||
}
|
||||
|
||||
var attached = new List<NameToken>();
|
||||
if (properties.TryGet(NameToken.Attached, out ArrayToken attachedToken))
|
||||
{
|
||||
foreach (var token in attachedToken.Data)
|
||||
{
|
||||
if (token is NameToken aName)
|
||||
{
|
||||
attached.Add(aName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new ArtifactMarkedContentElement(mcid, name, properties, language,
|
||||
actualText,
|
||||
alternateDescription,
|
||||
expandedForm,
|
||||
artifactType,
|
||||
subType,
|
||||
attributeOwners,
|
||||
boundingBox,
|
||||
attached,
|
||||
Children,
|
||||
letters,
|
||||
paths,
|
||||
images,
|
||||
number);
|
||||
}
|
||||
|
||||
private string GetOptional(NameToken optionName, IPdfTokenScanner pdfScanner)
|
||||
{
|
||||
var result = default(string);
|
||||
if (properties.TryGet(optionName, pdfScanner, out IDataToken<string> token))
|
||||
{
|
||||
result = token.Data;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -35,6 +35,7 @@
|
||||
/// <inheritdoc />
|
||||
public void Run(IOperationContext operationContext)
|
||||
{
|
||||
operationContext.BeginMarkedContent(Name, null, null);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@@ -63,6 +63,7 @@
|
||||
/// <inheritdoc />
|
||||
public void Run(IOperationContext operationContext)
|
||||
{
|
||||
operationContext.BeginMarkedContent(Name, PropertyDictionaryName, Properties);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@@ -28,6 +28,7 @@
|
||||
/// <inheritdoc />
|
||||
public void Run(IOperationContext operationContext)
|
||||
{
|
||||
operationContext.EndMarkedContent();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@@ -1,6 +1,7 @@
|
||||
namespace UglyToad.PdfPig.Graphics
|
||||
{
|
||||
using System;
|
||||
using Colors;
|
||||
using Core;
|
||||
using PdfPig.Core;
|
||||
using Tokens;
|
||||
@@ -18,13 +19,17 @@
|
||||
|
||||
public RenderingIntent DefaultRenderingIntent { get; }
|
||||
|
||||
public ColorSpace DefaultColorSpace { get; }
|
||||
|
||||
public XObjectContentRecord(XObjectType type, StreamToken stream, TransformationMatrix appliedTransformation,
|
||||
RenderingIntent defaultRenderingIntent)
|
||||
RenderingIntent defaultRenderingIntent,
|
||||
ColorSpace defaultColorSpace)
|
||||
{
|
||||
Type = type;
|
||||
Stream = stream ?? throw new ArgumentNullException(nameof(stream));
|
||||
AppliedTransformation = appliedTransformation;
|
||||
DefaultRenderingIntent = defaultRenderingIntent;
|
||||
DefaultColorSpace = defaultColorSpace;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -16,8 +16,7 @@
|
||||
{
|
||||
public static XObjectImage ReadImage(XObjectContentRecord xObject, IPdfTokenScanner pdfScanner,
|
||||
IFilterProvider filterProvider,
|
||||
IResourceStore resourceStore,
|
||||
bool isLenientParsing)
|
||||
IResourceStore resourceStore)
|
||||
{
|
||||
if (xObject == null)
|
||||
{
|
||||
@@ -87,25 +86,19 @@
|
||||
{
|
||||
colorSpace = colorSpaceResult;
|
||||
}
|
||||
else if (dictionary.TryGet(NameToken.ColorSpace, pdfScanner, out ArrayToken colorSpaceArrayToken))
|
||||
else if (dictionary.TryGet(NameToken.ColorSpace, pdfScanner, out ArrayToken colorSpaceArrayToken)
|
||||
&& colorSpaceArrayToken.Length > 0)
|
||||
{
|
||||
if (colorSpaceArrayToken.Length == 0)
|
||||
{
|
||||
throw new PdfDocumentFormatException($"Empty ColorSpace array defined for image XObject: {dictionary}.");
|
||||
}
|
||||
|
||||
var first = colorSpaceArrayToken.Data[0];
|
||||
|
||||
if (!(first is NameToken firstColorSpaceName) || !TryMapColorSpace(firstColorSpaceName, resourceStore, out colorSpaceResult))
|
||||
if ((first is NameToken firstColorSpaceName) && TryMapColorSpace(firstColorSpaceName, resourceStore, out colorSpaceResult))
|
||||
{
|
||||
throw new PdfDocumentFormatException($"Invalid ColorSpace array defined for image XObject: {colorSpaceArrayToken}.");
|
||||
colorSpace = colorSpaceResult;
|
||||
}
|
||||
|
||||
colorSpace = colorSpaceResult;
|
||||
}
|
||||
else if (!isJpxDecode)
|
||||
{
|
||||
throw new PdfDocumentFormatException($"No ColorSpace defined for image XObject: {dictionary}.");
|
||||
colorSpace = xObject.DefaultColorSpace;
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user