namespace UglyToad.PdfPig.XObjects
{
using System;
using System.Linq;
using Content;
using Core;
using Filters;
using Graphics;
using Graphics.Colors;
using Graphics.Core;
using Images;
using Tokenization.Scanner;
using Tokens;
using Util;
///
/// External Object (XObject) factory.
///
public static class XObjectFactory
{
///
/// Read the XObject image.
///
public static XObjectImage ReadImage(XObjectContentRecord xObject,
IPdfTokenScanner pdfScanner,
ILookupFilterProvider filterProvider,
IResourceStore resourceStore)
{
if (xObject is null)
{
throw new ArgumentNullException(nameof(xObject));
}
if (xObject.Type != XObjectType.Image)
{
throw new InvalidOperationException($"Cannot create an image from an XObject with type: {xObject.Type}.");
}
var dictionary = xObject.Stream.StreamDictionary;
var bounds = xObject.AppliedTransformation.Transform(new PdfRectangle(new PdfPoint(0, 0), new PdfPoint(1, 1)));
var width = dictionary.Get(NameToken.Width, pdfScanner).Int;
var height = dictionary.Get(NameToken.Height, pdfScanner).Int;
bool isImageMask = false;
if (dictionary.TryGet(NameToken.ImageMask, pdfScanner, out BooleanToken? isMaskToken))
{
dictionary = dictionary.With(NameToken.ImageMask, isMaskToken);
isImageMask = isMaskToken.Data;
}
var isJpxDecode = dictionary.TryGet(NameToken.Filter, pdfScanner, out NameToken filterName)
&& filterName.Equals(NameToken.JpxDecode);
int bitsPerComponent;
if (isImageMask)
{
bitsPerComponent = 1;
}
else
{
if (isJpxDecode)
{
// Optional for JPX
if (dictionary.TryGet(NameToken.BitsPerComponent, pdfScanner, out NumericToken? bitsPerComponentToken))
{
bitsPerComponent = bitsPerComponentToken.Int;
System.Diagnostics.Debug.Assert(bitsPerComponent == Jpeg2000Helper.GetJp2BitsPerComponent(xObject.Stream.Data.Span));
}
else
{
bitsPerComponent = Jpeg2000Helper.GetJp2BitsPerComponent(xObject.Stream.Data.Span);
System.Diagnostics.Debug.Assert(new int[] { 1, 2, 4, 8, 16 }.Contains(bitsPerComponent));
}
}
else
{
if (!dictionary.TryGet(NameToken.BitsPerComponent, pdfScanner, out NumericToken? bitsPerComponentToken))
{
throw new PdfDocumentFormatException($"No bits per component defined for image: {dictionary}.");
}
bitsPerComponent = bitsPerComponentToken.Int;
}
}
var intent = xObject.DefaultRenderingIntent;
if (dictionary.TryGet(NameToken.Intent, pdfScanner, out NameToken renderingIntentToken))
{
intent = renderingIntentToken.Data.ToRenderingIntent();
}
var interpolate = dictionary.TryGet(NameToken.Interpolate, pdfScanner, out BooleanToken? interpolateToken)
&& interpolateToken.Data;
if (dictionary.TryGet(NameToken.Filter, out var filterToken) && filterToken is IndirectReferenceToken)
{
if (dictionary.TryGet(NameToken.Filter, pdfScanner, out ArrayToken? filterArray))
{
dictionary = dictionary.With(NameToken.Filter, filterArray);
}
else if (dictionary.TryGet(NameToken.Filter, pdfScanner, out NameToken? filterNameToken))
{
dictionary = dictionary.With(NameToken.Filter, filterNameToken);
}
}
var supportsFilters = true;
var filters = filterProvider.GetFilters(dictionary, pdfScanner);
foreach (var filter in filters)
{
if (!filter.IsSupported)
{
supportsFilters = false;
break;
}
}
var decodeParams = dictionary.GetObjectOrDefault(NameToken.DecodeParms, NameToken.Dp);
if (decodeParams is IndirectReferenceToken refToken)
{
dictionary = dictionary.With(NameToken.DecodeParms, pdfScanner.Get(refToken.Data).Data);
}
var jbig2GlobalsParams = dictionary.GetObjectOrDefault(NameToken.Jbig2Globals);
if (jbig2GlobalsParams is IndirectReferenceToken jbig2RefToken)
{
dictionary = dictionary.With(NameToken.Jbig2Globals, pdfScanner.Get(jbig2RefToken.Data).Data);
}
var imParams = dictionary.GetObjectOrDefault(NameToken.Im);
if (imParams is IndirectReferenceToken imRefToken)
{
dictionary = dictionary.With(NameToken.Im, pdfScanner.Get(imRefToken.Data).Data);
}
var streamToken = new StreamToken(dictionary, xObject.Stream.Data);
var decodedBytes = supportsFilters ? new Lazy>(() => streamToken.Decode(filterProvider, pdfScanner))
: null;
var decode = Array.Empty();
if (dictionary.TryGet(NameToken.Decode, pdfScanner, out ArrayToken? decodeArrayToken))
{
decode = decodeArrayToken.Data.OfType()
.Select(x => x.Double)
.ToArray();
}
ColorSpaceDetails? details = null;
if (!isImageMask)
{
if (dictionary.TryGet(NameToken.ColorSpace, pdfScanner, out NameToken? colorSpaceNameToken))
{
details = resourceStore.GetColorSpaceDetails(colorSpaceNameToken, dictionary);
}
else if (dictionary.TryGet(NameToken.ColorSpace, pdfScanner, out ArrayToken? colorSpaceArrayToken)
&& colorSpaceArrayToken.Length > 0 && colorSpaceArrayToken.Data[0] is NameToken firstColorSpaceName)
{
details = resourceStore.GetColorSpaceDetails(firstColorSpaceName, dictionary);
}
else if (!isJpxDecode)
{
details = xObject.DefaultColorSpace;
}
}
else
{
details = resourceStore.GetColorSpaceDetails(null, dictionary);
}
return new XObjectImage(
bounds,
width,
height,
bitsPerComponent,
isJpxDecode,
isImageMask,
intent,
interpolate,
decode,
dictionary,
xObject.Stream.Data,
decodedBytes,
details);
}
}
}