// ===========================================================================
// ©2013-2026 WebSupergoo. All rights reserved.
//
// This source code is for use exclusively with the ABCpdf product with
// which it is distributed, under the terms of the license for that
// product. Details can be found at
//
// http://www.websupergoo.com/
//
// This copyright notice must not be deleted and must be reproduced alongside
// any sections of code extracted from this module.
// ===========================================================================
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Globalization;
using WebSupergoo.ABCpdf14;
using WebSupergoo.ABCpdf14.Objects;
using WebSupergoo.ABCpdf14.Atoms;
// By default Acrobat is set up to allow you to use internal, but not external, Reference XObjects.
// To see external Reference XObjects you will need to adjust some Acrobat preferences.
//
// The precise settings may vary by Acrobat version and date, but as of January 2021
// these are the Acrobat preferences you need to adjust.
//
// 1) Preferences : Page Display : Reference XObject View Mode
// - allow viewing of all Reference XObjects
// 2) Preferences : Security (Enhanced) : Privileged Locations
// - add the folder/file path of the viewed PDF file (ie External.pdf)
//
// If you do not do this Acrobat error messages are not very explanatory. If you do not do
// any of this the images will likely be black. If you enable XObjects but do not add the
// location as privileged Acrobat may say things like "There is an error on this page.".
//
// For further details see:
// https://www.adobe.com/devnet-docs/acrobatetk/tools/AppSec/trust.html
namespace ReferenceXObject {
class Program {
static void Main(string[] args) {
string baseDir = Directory.GetCurrentDirectory();
string resDir = Path.Combine(Directory.GetParent(baseDir).Parent.Parent.FullName, "PDFTable");
string inputFile = Path.Combine(resDir, "SpaceSample.pdf"); // The PDF file from PDFTable example
string outputDir = Directory.GetParent(baseDir).Parent.FullName;
string outputFile = Path.Combine(outputDir, "External.pdf");
string outputFile2 = Path.Combine(outputDir, "Internal.pdf");
string externalPath = "ImagePDF";
using(Doc doc = new Doc()) {
doc.Read(inputFile);
ConvertImagesToRefXObjects(doc, outputDir, externalPath);
doc.Save(outputFile);
}
using(Doc doc = new Doc()) {
doc.Read(outputFile);
ConvertRefXObjectsToImages(doc, outputDir);
doc.Save(outputFile2);
}
}
private static List<string> ConvertImagesToRefXObjects(Doc doc, string filePath, string externalPath) {
List<string> pdfList = new List<string>();
// Here images are distributed among PDF files according to their color spaces
// Other schemes (e.g. one image per PDF file) can be used, instead
Doc grayDoc = null, rgbDoc = null, cmykDoc = null, otherDoc = null;
string grayRelPath = null, rgbRelPath = null, cmykRelPath = null, otherRelPath = null;
ObjectCopy objCopy = new ObjectCopy(doc);
// restrict the bounds so that the reciprocals (scaleX and scaleY) are accurate in 6 decimal places
XRect maxRect = new XRect();
maxRect.SetRect(0, 0, 100, 100);
int objCount = doc.ObjectSoup.Count;
try {
for(int i = 0; i<objCount; ++i) {
PixMap pixmap = doc.ObjectSoup[i] as PixMap;
if(pixmap==null || pixmap.ImageMask)
continue;
Doc destDoc;
string destRelPath;
switch(GetSimpleColorSpace(doc, pixmap)) {
case ColorSpaceType.DeviceGray:
if(grayDoc==null) {
grayDoc = new Doc();
grayRelPath = Path.Combine(externalPath, "GrayImages.pdf");
}
destDoc = grayDoc;
destRelPath = grayRelPath;
break;
case ColorSpaceType.DeviceRGB:
if(rgbDoc==null) {
rgbDoc = new Doc();
rgbRelPath = Path.Combine(externalPath, "RGBImages.pdf");
}
destDoc = rgbDoc;
destRelPath = rgbRelPath;
break;
case ColorSpaceType.DeviceCMYK:
if(cmykDoc==null) {
cmykDoc = new Doc();
cmykRelPath = Path.Combine(externalPath, "CMYKImages.pdf");
}
destDoc = cmykDoc;
destRelPath = cmykRelPath;
break;
default:
if(otherDoc==null) {
otherDoc = new Doc();
otherRelPath = Path.Combine(externalPath, "OtherImages.pdf");
}
destDoc = otherDoc;
destRelPath = otherRelPath;
break;
}
PixMap destPixMap = objCopy.CopyPixMap(pixmap, destDoc, 0);
destDoc.Rect.Width = destPixMap.Width;
destDoc.Rect.Height = destPixMap.Height;
destDoc.Rect.FitIn(maxRect, ContentAlign.Left | ContentAlign.Bottom,
ContentScaleMode.ShowAll);
destDoc.MediaBox.String = destDoc.Rect.String;
destDoc.Page = destDoc.AddPage();
destDoc.AddImageCopy(destPixMap.ID);
StreamObject streamObj = (StreamObject)IndirectObject.FromString(
"<</Type /XObject /Subtype /Form /Resources<</ProcSet [/PDF]>>>>stream\nendstream\n");
doc.ObjectSoup[i] = streamObj;
streamObj = (StreamObject)doc.ObjectSoup[i];
streamObj.Version = 14;
int id = streamObj.ID;
int localCount = Math.Min(ObjectCopy.XObjectLocalEntryCount,
objCopy.XObjectLocalEntryList.Count);
for(int j = 0; j<localCount; ++j) {
if(objCopy.XObjectLocalEntryList[j]!=null)
doc.SetInfo(id, ObjectCopy.GetXObjectLocalEntryPath(j),
objCopy.XObjectLocalEntryList[j]);
}
// Entries in referenced objects (Mask and SMask)
// that are not copied are stored here for later restoration
// However, these entries rarely exist
for(int j = ObjectCopy.XObjectLocalEntryCount;
j<objCopy.XObjectLocalEntryList.Count; ++j)
{
doc.SetInfo(id, "/MyMaskLocalEntries[]",
objCopy.XObjectLocalEntryList[j]?? "null");
}
string bbox = destDoc.Rect.String;
doc.SetInfo(id, "/BBox:Rect", bbox);
double scaleX = 1 / destDoc.Rect.Width;
double scaleY = 1 / destDoc.Rect.Height;
double translateX = -scaleX * destDoc.Rect.Left;
double translateY = -scaleY * destDoc.Rect.Bottom;
XTransform xform = new XTransform(scaleX, 0, 0, scaleY, translateX, translateY);
doc.SetInfo(id, "/Matrix", "[" + xform.String + "]");
FileSpecification fs = new FileSpecification(doc.ObjectSoup);
fs.Uri = destRelPath.Replace('\\', '/'); // convert into relative URI
doc.SetInfo(id, "/Ref/F:Ref", fs.ID);
doc.SetInfo(id, "/Ref/Page:Num", destDoc.PageNumber-1);
doc.SetInfo(id, "Stream", bbox + " re f");
}
string path, dir;
if(grayDoc!=null) {
path = Path.Combine(filePath, grayRelPath);
dir = Path.GetDirectoryName(path);
if(dir!=null && !Directory.Exists(dir))
Directory.CreateDirectory(dir);
grayDoc.Save(path);
pdfList.Add(path);
}
if(rgbDoc!=null) {
path = Path.Combine(filePath, rgbRelPath);
dir = Path.GetDirectoryName(path);
if(dir!=null && !Directory.Exists(dir))
Directory.CreateDirectory(dir);
rgbDoc.Save(path);
pdfList.Add(path);
}
if(cmykDoc!=null) {
path = Path.Combine(filePath, cmykRelPath);
dir = Path.GetDirectoryName(path);
if(dir!=null && !Directory.Exists(dir))
Directory.CreateDirectory(dir);
cmykDoc.Save(path);
pdfList.Add(path);
}
if(otherDoc!=null) {
path = Path.Combine(filePath, otherRelPath);
dir = Path.GetDirectoryName(path);
if(dir!=null && !Directory.Exists(dir))
Directory.CreateDirectory(dir);
otherDoc.Save(path);
pdfList.Add(path);
}
} finally {
if(grayDoc!=null)
grayDoc.Dispose();
if(rgbDoc!=null)
rgbDoc.Dispose();
if(cmykDoc!=null)
cmykDoc.Dispose();
if(otherDoc!=null)
otherDoc.Dispose();
}
return pdfList;
}
private static ColorSpaceType GetSimpleColorSpace(Doc doc, PixMap pixmap) {
ColorSpaceType colorspace = pixmap.ColorSpaceType;
switch(colorspace) {
case ColorSpaceType.DeviceGray:
case ColorSpaceType.CalGray:
return ColorSpaceType.DeviceGray;
case ColorSpaceType.DeviceRGB:
case ColorSpaceType.CalRGB:
return ColorSpaceType.DeviceRGB;
case ColorSpaceType.DeviceCMYK:
return ColorSpaceType.DeviceCMYK;
case ColorSpaceType.ICCBased:
switch(doc.GetInfo(pixmap.ID, "/ColorSpace*[1]*/Alternate*:Name")) {
case "DeviceGray":
case "CalGray":
return ColorSpaceType.DeviceGray;
case "DeviceRGB":
case "CalRGB":
return ColorSpaceType.DeviceRGB;
case "DeviceCMYK": return ColorSpaceType.DeviceCMYK;
}
goto default;
case ColorSpaceType.Indexed:
switch(doc.GetInfo(pixmap.ID, "/ColorSpace*[1]*:Name")) {
case "DeviceGray":
case "CalGray":
return ColorSpaceType.DeviceGray;
case "DeviceRGB":
case "CalRGB":
return ColorSpaceType.DeviceRGB;
case "DeviceCMYK": return ColorSpaceType.DeviceCMYK;
}
goto default;
case ColorSpaceType.DeviceN:
switch(doc.GetInfo(pixmap.ID, "/ColorSpace*[2]*:Name")) {
case "DeviceGray":
case "CalGray":
return ColorSpaceType.DeviceGray;
case "DeviceRGB":
case "CalRGB":
return ColorSpaceType.DeviceRGB;
case "DeviceCMYK": return ColorSpaceType.DeviceCMYK;
}
goto default;
default: return colorspace;
}
}
private static void ConvertRefXObjectsToImages(Doc doc, string filePath) {
Dictionary<string, Doc> docDict = new Dictionary<string, Doc>();
string[] localEntryList = new string[ObjectCopy.XObjectLocalEntryCount];
int objCount = doc.ObjectSoup.Count;
try {
for(int i = 0; i<objCount; ++i) {
string imageFile = doc.GetInfo(i, "/Ref*/F*/F*:Text");
if(imageFile==string.Empty)
continue;
imageFile = imageFile.Replace('/', '\\'); // convert URI into path
imageFile = Path.Combine(filePath, imageFile);
Doc imageDoc;
if(!docDict.TryGetValue(imageFile, out imageDoc)) {
imageDoc = new Doc();
docDict.Add(imageFile, imageDoc);
imageDoc.Read(imageFile);
}
int pageIndex = doc.GetInfoInt(i, "/Ref*/Page*:Num");
imageDoc.PageNumber = pageIndex+1;
string key = imageDoc.GetInfo(imageDoc.Page, "/Resources*/XObject*:Keys");
int k = key.IndexOf(','); // There should be only one XObject, which is the image
if(k>=0)
key = key.Substring(0, k);
int pixmapID = imageDoc.GetInfoInt(imageDoc.Page, "/Resources*/XObject*/"+key+":Ref");
PixMap pixmap = (PixMap)imageDoc.ObjectSoup[pixmapID];
for(int j = 0; j<ObjectCopy.XObjectLocalEntryCount; ++j) {
localEntryList[j] = doc.GetInfo(i, ObjectCopy.GetXObjectLocalEntryPath(j));
}
ArrayAtom maskLocalEntries = ((DictAtom)pixmap.Atom)["MyMaskLocalEntries"] as ArrayAtom;
ObjectCopy objCopy = new ObjectCopy(imageDoc);
pixmap = objCopy.CopyPixMap(pixmap, doc, i);
for(int j = 0; j<ObjectCopy.XObjectLocalEntryCount; ++j) {
if(localEntryList[j]!=string.Empty)
doc.SetInfo(i, ObjectCopy.GetXObjectLocalEntryPath(j), localEntryList[j]);
}
if(maskLocalEntries!=null) {
int j = 0, count;
int maskID = doc.GetInfoInt(i, "/Mask:Ref");
if(maskID!=0) {
count = Math.Min(ObjectCopy.XObjectLocalEntryCount, maskLocalEntries.Count);
for(; j<count; ++j) {
string v = maskLocalEntries[j].ToString();
if(v!="null")
doc.SetInfo(maskID, ObjectCopy.GetXObjectLocalEntryPath(j), v);
}
}
if(j<maskLocalEntries.Count) {
maskID = doc.GetInfoInt(i, "/SMask:Ref");
if(maskID!=0) {
count = Math.Min(ObjectCopy.XObjectLocalEntryCount, maskLocalEntries.Count-j);
for(k = 0; k<count; ++k) {
string v = maskLocalEntries[j+k].ToString();
if(v!="null")
doc.SetInfo(maskID, ObjectCopy.GetXObjectLocalEntryPath(k), v);
}
}
}
}
}
} finally {
foreach(Doc imageDoc in docDict.Values) {
imageDoc.Dispose();
}
}
}
}
}