Flatten Transparencies in PDFs
How to Flatten Transparencies in PDFs
Flattening transparencies allows printers to interpret images more efficiently and reduces the overall file size, making the file easier to digitally send and faster to download.
Get Free Trial
C++
C#
Java
C++
#include
#include "InitializeLibrary.h"
#include "APDFLDoc.h"
#include "PagePDECntCalls.h"
#include "PDFlattenerCalls.h"
#define DIR_LOC "../../../../Resources/Sample_Input/"
#define DEF_INPUT "FlattenTransparency.pdf"
#define DEF_OUTPUT "FlattenTransparency-out.pdf"
// ASBool callback function: A function for PDFlattener which monitors the flattener's progress.
static ASBool flattenerProgMon(ASInt32 pageNum, ASInt32 totalPages, float current, ASInt32 reserved,
void *clientData);
int main(int argc, char **argv) {
ASErrorCode errCode = 0;
APDFLib libInit;
if (libInit.isValid() == false) {
errCode = libInit.getInitError();
std::cout << "Initialization failed with code " << errCode << std::endl;
return libInit.getInitError();
}
std::string csInputFileName(argc > 1 ? argv[1] : DIR_LOC DEF_INPUT);
std::string csOutputFileName(argc > 2 ? argv[2] : DEF_OUTPUT);
std::cout << "Flattening transparencies of " << csInputFileName.c_str() << " and saving to "
<< csOutputFileName.c_str() << std::endl;
PDFlattenerUserParamsRec flattenParams;
memset(&flattenParams, 0, sizeof(PDFlattenerUserParamsRec));
flattenParams.size = sizeof(PDFlattenerUserParamsRec);
DURING
// Initialize the PDFLattener plugin.
// Sets the correct location for the PDFlattener function table.
gPDFlattenerHFT = InitPDFlattenerHFT;
if (!PDFlattenerInitialize()) {
std::cout << "The PDFlattener plugin failed to initialize." << std::endl;
errCode = -1;
}
if (0 == errCode) {
// Step 1) Configure the PDFlattener parameters.
// Appearance options
// A profiled color space to use for transparent objects. For CMYK, use "U.S. Web Coated (SWOP)v2".
flattenParams.profileDesc = ASTextFromUnicode((ASUTF16Val *)"sRGB IEC61966-2.1", kUTF8);
// The ZIP compression scheme (Flate encoding) for images.
flattenParams.colorCompression = kPDFlattenerZipCompression;
// Raster/Vector balance. Use 0.00f for no vectors.
flattenParams.transQuality = 100.0f;
// Callback options.
ASInt32 currentPage = -1;
// Progress monitor callback data. I'm using this data to store the previous page
// the Flattener was working on, using -1 as "hasn't begun yet".
flattenParams.progressClientData = (void *)¤tPage;
// The progress monitor callback function.
flattenParams.flattenProgress = flattenerProgMon;
// Tile flattening options
PDFlattenRec flattener;
memset(&flattener, 0, sizeof(PDFlattenRec));
flattener.size = sizeof(PDFlattenRec);
flattener.tilingMode = kPDNoTiling;
flattener.tileSizePts = 0;
// Resolution for flattening the interior of an atomic region.
flattener.internalDPI = 800.0f;
// Resolution for flattening edges of atomic regions.
flattener.externalDPI = 200.0f;
flattener.clipComplexRegions = false;
// If we convert stroked elements to filled elements.
flattener.strokeToFill = true;
// If we use rastered text instead of native text.
flattener.useTextOutlines = false;
// If we attempt to preserve overprint
flattener.preserveOverprint = true;
flattener.allowShadingOutput = true;
flattener.allowLevel3ShadingOutput = true;
// Maximum image size while flattening. 0 is default.
flattener.maxFltnrImageSize = 0;
// Adaptive flattening threshold. Doesn't matter, since we're not doing adaptive tiling. See tilingMode.
flattener.adaptiveThreshold = 0;
flattenParams.flattenParams = &flattener;
// Step 2) Open the input pdf
APDFLDoc doc(csInputFileName.c_str(), true);
// Step 3) Call the PDFlattener.
ASUns32 numFlattened = 0;
PDDoc pddoc = doc.getPDDoc();
// PDFlattenerConvertEx2 will set this to 0 if flattening failed.
ASInt32 result = 0;
result = PDFlattenerConvertEx2(pddoc, // The document whose pages we wish to flatten.
0, // The first page to flatten.
PDDocGetNumPages(pddoc) - 1, // The last page to flatten.
&numFlattened, // PDFlattener sets this to the number of pages
// it flattened. It will not flatten pages that do
// not contain transparent elements.
&flattenParams); // Flattener options.
if (result) {
std::cout << "Flattened " << numFlattened << " pages." << std::endl;
} else {
std::cout << "Flattening failed." << std::endl;
ASRaise(GenError(genErrGeneral));
}
// Step 4) Save the document, close it, release resources and terminate the plugin.
doc.saveDoc(csOutputFileName.c_str());
ASTextDestroy(flattenParams.profileDesc);
PDFlattenerTerminate();
} // if 0 == errCode
HANDLER
errCode = ERRORCODE;
libInit.displayError(errCode);
if (flattenParams.profileDesc != NULL)
ASTextDestroy(flattenParams.profileDesc);
PDFlattenerTerminate();
END_HANDLER
return errCode; // APDFLib's destructor terminates the library.
}
// ASBool callback function: A function for PDFlattener which monitors the flattener's progress.
//
// Note:
// clientData is an ASInt32* representing the previous page we were working on flattening.
// It must start with a page number that doesn't exist, like -1. With it, we have this function
// print the progress only every time a new page is begun, or when the Flattener is finished.
ASBool flattenerProgMon(ASInt32 pageNum, ASInt32 totalPages, float current, ASInt32 reserved, void *clientData) {
// The previous page we were working on.
ASInt32 *prevPage = (ASInt32 *)clientData;
// If we've begun a new page, or if we've finished.
if (pageNum != (*prevPage) || current == 100.0f) {
// Print the completion percentage.
std::cout << "[" << std::fixed << std::setw(6) << std::setfill('0') << std::setprecision(2)
<< current << "%] ";
// Print the current page.
std::cout << "Flattening page " << pageNum + 1 << " of " << totalPages << ". " << std::endl;
// Update previous page.
*prevPage = pageNum;
}
// Return 1 to cancel Flattening.
return 0;
}
C#
using System;
using Datalogics.PDFL;
namespace FlattenTransparency
{
class FlattenTransparency
{
static void Main(string[] args)
{
Console.WriteLine("FlattenTransparency sample:");
// ReSharper disable once UnusedVariable
using (Library lib = new Library())
{
String sInput1 = Library.ResourceDirectory + "Sample_Input/trans_1page.pdf";
String sOutput1 = "FlattenTransparency-out1.pdf";
String sInput2 = Library.ResourceDirectory + "Sample_Input/trans_multipage.pdf";
String sOutput2 = "FlattenTransparency-out2.pdf";
if (args.Length > 0)
sInput1 = args[0];
if (args.Length > 1)
sInput2 = args[1];
if (args.Length > 2)
sOutput1 = args[2];
if (args.Length > 3)
sOutput2 = args[3];
// Open a document with a single page.
Document doc1 = new Document(sInput1);
// Verify that the page has transparency. The parameter indicates
// whether to include the appearances of annotations or not when
// checking for transparency.
Page pg1 = doc1.GetPage(0);
bool isTransparent = pg1.HasTransparency(true);
// If there is transparency, flatten the document.
if (isTransparent)
{
// Flattening the document will check each page for transparency.
// If a page has transparency, PDFL will create a new, flattened
// version of the page and replace the original page with the
// new one. Because of this, make sure to dispose of outstanding Page objects
// that refer to pages in the Document before calling flattenTransparency.
pg1.Dispose();
doc1.FlattenTransparency();
Console.WriteLine("Flattened single page document " + sInput1 + " as " + sOutput1 + ".");
doc1.Save(SaveFlags.Full, sOutput1);
}
// Open a document with multiple pages.
Document doc2 = new Document(sInput2);
// Iterate over the pages of the document and find the first page that has
// transparency.
isTransparent = false;
int totalPages = doc2.NumPages;
int pageCounter = 0;
while (!isTransparent && pageCounter <= totalPages)
{
Page pg = doc2.GetPage(pageCounter);
if (pg.HasTransparency(true))
{
isTransparent = true;
// Explicitly delete the page here, to ensure the reference is gone before we
// attempt to flatten the document.
pg.Dispose();
break;
}
pageCounter++;
}
if (isTransparent)
{
// Set up some parameters for the flattening.
FlattenTransparencyParams ftParams = new FlattenTransparencyParams();
// The Quality setting indicates the percentage (0%-100%) of vector information
// that is preserved. Lower values result in higher rasterization of vectors.
ftParams.Quality = 50;
// Flatten transparency in the document, starting from the first page
// that has transparency.
doc2.FlattenTransparency(ftParams, pageCounter, Document.LastPage);
Console.WriteLine("Flattened a multi-page document " + sInput2 + " as " + sOutput2 + ".");
doc2.Save(SaveFlags.Full, sOutput2);
}
}
}
}
}
Java
#include
#include
#include
#include
#include
#include "InitializeLibrary.h"
#include "APDFLDoc.h"
#include "PSFCalls.h"
#include "PERCalls.h"
#include "PEWCalls.h"
#include "PagePDECntCalls.h"
#include "DLExtrasCalls.h"
#include "CosCalls.h"
#include "ASExtraVers.h"
#define DIR_LOC "../../../../Resources/Sample_Input/"
#define DEF_INPUT "CreateAnnotations.pdf"
#define DEF_OUTPUT_ANNOT "CreateAnnotations-out.pdf"
#define DEF_OUTPUT_TEXT "CreateAnnotations-out-text.txt"
static void extractPDEElements ( PDEContent c, std::vector& list );
static void SetAnnotationQuads ( PDAnnot annot, ASFixedQuad *quads, ASArraySize numQuads );
static PDColorValue SelectColor ( bool textAnnotation );
int main(int argc, char** argv)
{
APDFLib libInit;
ASErrorCode errCode = 0;
if (libInit.isValid() == false)
{
errCode = libInit.getInitError();
std::cout << "Initialization failed with code " << errCode << std::endl;
return libInit.getInitError();
}
std::string csInputFileName ( argc > 1 ? argv[1] : DIR_LOC DEF_INPUT );
std::string csOutputFileName ( argc > 2 ? argv[2] : DEF_OUTPUT_ANNOT );
std::string csOutputTextFileName ( argc > 3 ? argv[3] : DEF_OUTPUT_TEXT );
std::cout << "Opening " << csInputFileName.c_str() << " and adding annotations to "
<< "all elements on the first page;" << std::endl << "Will save to " << csOutputFileName.c_str()
<< ", then we will reopen that file and extract all annotations to "
<< csOutputTextFileName.c_str() << std::endl;
std::ofstream ofText ( csOutputTextFileName.c_str() );
ofText << "Results from file " << csOutputFileName.c_str() << ":" << std::endl;
DURING
APDFLDoc doc( csInputFileName.c_str(), true); //Open the input document, repairing it if necessary.
PDPage inPage = doc.getPage(0);
PDEContent inPageContent = PDPageAcquirePDEContent(inPage, 0);
// Step 0) Retrieve all the PDEElements on the page.
std::vector pageElements;
extractPDEElements ( inPageContent, pageElements );
// Step 1) Annotate them all
// An annotation with descriptive content will be placed in the same location as each PDEElement.
// For text elements, we will create a red, blue, or green highlight annotation.
// For other elements, we will create a yellow text annotation.
ASAtom atHighlight ( ASAtomFromString ( "Highlight" ) );
ASAtom atText ( ASAtomFromString ( "Text" ) );
std::vector::iterator itElem, itElemEnd = pageElements.end();
for ( itElem = pageElements.begin(); itElem != itElemEnd; ++itElem )
{
// Retrieve the element's location. We will place the annotation where the
// original page element was found.
ASFixedRect elementLoc;
PDEElementGetBBox( *itElem, &elementLoc);
// Fetch the annotation's type
ASInt32 elementType = PDEObjectGetType ( (PDEObject)*itElem );
ASAtom annotationType = ( elementType == kPDEText ) ? atText : atHighlight;
// Create the new annotation. Set the appearance and content.
PDAnnot annot = PDPageAddNewAnnot ( inPage, kPDEAfterLast, annotationType, &elementLoc );
std::wstringstream annotContent;
annotContent << L"This is a ";
switch ( elementType )
{
case kPDEContainer:
annotContent << L"container containing " <<
PDEContentGetNumElems ( PDEContainerGetContent ( (PDEContainer)*itElem ) )
<< L" elements";
break;
case kPDEForm:
annotContent << L"form containing " <<
PDEContentGetNumElems ( PDEFormGetContent ( (PDEForm)*itElem ) )
<< L" elements";
break;
case kPDEGroup:
annotContent << L"group containing " <<
PDEContentGetNumElems ( PDEGroupGetContent ( (PDEGroup)*itElem ) )
<< L" elements";
break;
case kPDEImage:
annotContent << L"image";
break;
case kPDEPath:
annotContent << L"path";
break;
case kPDEPlace:
annotContent << L"place";
break;
case kPDEText:
annotContent << L"text object";
break;
case kPDEXObject:
annotContent << L"XObject";
break;
}
annotContent << L". It is situated at: " << std::endl
<< L"\ttop: " << ASFixedToFloat(elementLoc.top) << std::endl
<< L"\tbottom: " << ASFixedToFloat(elementLoc.bottom) << std::endl
<< L"\tleft: " << ASFixedToFloat(elementLoc.left) << std::endl
<< L"\tright: " << ASFixedToFloat(elementLoc.right);
//The content string is ready. Now make its ASText to add it to the annotation.
ASText annotContentAST = ASTextFromUnicode (
(ASUTF16Val*)annotContent.str().c_str(),
APDFLDoc::GetHostUnicodeFormat() );
//The annotation must be cast to a TextAnnot to set its text content.
PDTextAnnot textAnnot = CastToPDTextAnnot ( annot );
PDTextAnnotSetContentsASText ( textAnnot, annotContentAST );
ASTextDestroy ( annotContentAST );
//The annotation's title.
const char* annotTitleStr = "Page Element";
PDAnnotSetTitle ( annot, annotTitleStr, strlen(annotTitleStr) );
//Set the annotation's quadrilateral values. This will properly
// position a highlight annotation, and have no effect on the text annotation.
ASFixedQuad annotLocQuad = { { elementLoc.left, elementLoc.bottom },
{ elementLoc.right, elementLoc.bottom },
{ elementLoc.left, elementLoc.top },
{ elementLoc.right, elementLoc.top } };
SetAnnotationQuads(annot, &annotLocQuad, 1);
//The annotation will be locked so that it cannot be edited again later.
PDAnnotSetFlags ( annot, PDAnnotGetFlags(annot) | pdAnnotLock | pdAnnotLockContents );
// Set its color
PDAnnotSetColor ( annot, SelectColor ( annotationType == atText ) );
}
// Step 2) Save the document and close it.
doc.saveDoc ( csOutputFileName.c_str() );
PDPageRelease(inPage);
// Close the document and release resources
doc.~APDFLDoc();
// Step 3) Reopen the document we created and Extract the annotations' text
APDFLDoc annotDoc ( csOutputFileName.c_str(), true);
PDPage annotPage = annotDoc.getPage(0);
int numAnnots = PDPageGetNumAnnots(annotPage);
int numTextAnnots = 0;
int numBlankAnnots = 0;
ofText << "The input page has " << numAnnots << " annotations." << std::endl;
//Extract each annotation's text content (if any)
const size_t buffersize = 1000;
static char contentBuffer[buffersize];
for (int i = 0; i < numAnnots; ++i)
{
PDAnnot annotation = PDPageGetAnnot(annotPage, i);
PDTextAnnot nextAsText = CastToPDTextAnnot( annotation );
PDTextAnnotGetContents(nextAsText, contentBuffer, buffersize);
if (contentBuffer[0] != '\0')
{
numTextAnnots++;
ofText << "Annotation no. " << numTextAnnots << ": " << contentBuffer << std::endl;
}
else
{
++numBlankAnnots;
}
}
PDPageRelease ( annotPage );
ofText << numBlankAnnots << " annotations on the page did not have text content." << std::endl;
HANDLER
errCode = ERRORCODE;
libInit.displayError(errCode);
END_HANDLER
return errCode;
}
// Walk the PDEContent tree (recursively) and save a reference to every element found
/* static */ void extractPDEElements ( PDEContent c, std::vector& elements )
{
ASInt32 numElems = PDEContentGetNumElems ( c );
for ( ASInt32 i = 0; i < numElems; ++i )
{
// Track each element found
PDEElement elem = PDEContentGetElem ( c, i );
elements.push_back( elem );
// ...and recurse into the aggregate element types
switch ( PDEObjectGetType ( (PDEObject)elem ) )
{
case kPDEContainer:
extractPDEElements ( PDEContainerGetContent ( (PDEContainer)elem ), elements );
break;
case kPDEForm:
extractPDEElements ( PDEFormGetContent ( (PDEForm)elem ), elements );
break;
case kPDEGroup:
extractPDEElements ( PDEGroupGetContent ( (PDEGroup)elem ), elements );
break;
}
}
}
// Helper function to add quads in BL, BR, TL, TR order to get correct output.
/* static */ void SetAnnotationQuads(PDAnnot annot, ASFixedQuad *quads, ASArraySize numQuads)
{
CosObj coAnnot = PDAnnotGetCosObj(annot); //Acquire the annotation's cos object.
CosDoc coDoc = CosObjGetDoc(coAnnot); //Get the CosDoc containing the annotation.
CosObj coQuads = CosNewArray(coDoc, false, numQuads * 8); //Create a cos array to hold the quadpoints.
for (ASUns32 i = 0, n = 0; i < numQuads; ++i)
{
// Bottom left
CosArrayPut(coQuads, n++, CosNewFixed(coDoc, false, quads[i].bl.h));
CosArrayPut(coQuads, n++, CosNewFixed(coDoc, false, quads[i].bl.v));
// Bottom right
CosArrayPut(coQuads, n++, CosNewFixed(coDoc, false, quads[i].br.h));
CosArrayPut(coQuads, n++, CosNewFixed(coDoc, false, quads[i].br.v));
// Top LEFT
CosArrayPut(coQuads, n++, CosNewFixed(coDoc, false, quads[i].tl.h));
CosArrayPut(coQuads, n++, CosNewFixed(coDoc, false, quads[i].tl.v));
// Top RIGHT
CosArrayPut(coQuads, n++, CosNewFixed(coDoc, false, quads[i].tr.h));
CosArrayPut(coQuads, n++, CosNewFixed(coDoc, false, quads[i].tr.v));
}
CosDictPut(coAnnot, ASAtomFromString("QuadPoints"), coQuads);
}
/* static */ PDColorValue SelectColor ( bool textAnnotation )
{
static PDColorValueRec colorRec;
colorRec.space = PDDeviceRGB;
static int counter ( 0 );
if ( !textAnnotation )
{
//Cycle between R/G/B for highlight annotations.
colorRec.value[0] = 0 == counter ? fixedOne : fixedHalf;
colorRec.value[1] = 1 == counter ? fixedOne : fixedHalf;
colorRec.value[2] = 2 == counter ? fixedOne : fixedHalf;
++counter %= 3;
}
else
{
//Text annotations will be yellow.
colorRec.value[0] = fixedOne;
colorRec.value[1] = fixedOne;
colorRec.value[2] = fixedZero;
}
return &colorRec;
}
#if 0
//Prepare the text object which will hold all the extracted text.
PDEText annotationsText = PDETextCreate(); //This will hold all the extracted text.
ASFixedMatrix textLoc; //Use this value to position the texts.
memset(&textLoc, 0, sizeof(textLoc)); //Ensure there's no garbage in the matrix.
ASFixed fontSize = FloatToASFixed(12.0f); //Set the font to 12 points.
textLoc.a = textLoc.d = fontSize; //Character width and height, respectively.
textLoc.v = textLoc.h = 0; //The vertical and horizontal position of the text, respectively. We will set v as we place texts.
//Load the font we'll use to write the text content.
PDEFontAttrs fontAttrs; //Contains data to retrieve the font.
memset(&fontAttrs, 0, sizeof(fontAttrs)); //Ensure there's no garbage in the struct.
fontAttrs.name = ASAtomFromString("CourierStd"); //The font name.
fontAttrs.type = ASAtomFromString("Type1"); //The font type.
PDSysFont sysFont = PDFindSysFont(&fontAttrs, sizeof(fontAttrs), 0); //Locate the system font that corresponds to the font attributes.
PDEFont font = PDEFontCreateFromSysFont(sysFont, kPDEFontDoNotEmbed); //Create the pdeFont with the sysFont. We won't embed the font.
//A default graphics state with which to draw the text.
PDEGraphicState graphics;
PDEDefaultGState(&graphics, sizeof(PDEGraphicState));
int maxDigits = (int)(log10((double)PDPageGetNumAnnots(annotPage)) + 1); //The maximum number of digits of n that the nth text annotation can have. Used to pad the text string.
#endif
#if 0
//Prepare the string to output.
std::wstringstream extractedString;
extractedString << L"Annotation no. " << numTextAnnots;
//Pad out the spaces so that the extracted strings all line up.
int numDigits = (int)(log10((double)numTextAnnots) + 1); //The number of digits of i that this annotation, the ith one, has.
for (int i = 0; i < (maxDigits - numDigits); ++i)
extractedString << L" ";
extractedString << L" '" << contentBuffer << L"'"; //The content is placed in the output string here.
//Add the text.
ASUnicodeFormat format = (sizeof(wchar_t) == 4) ? kUTF32HostEndian : kUTF16HostEndian;
ASText extractedAST = ASTextFromUnicode((ASUTF16Val*)extractedString.str().c_str(), format);
PDETextAddASText(annotationsText, kPDETextRun, numTextAnnots - 1, extractedAST, font, &graphics, sizeof(PDEGraphicState), NULL, 0, &textLoc);
ASTextDestroy(extractedAST);
//Adjust our page dimension requirements.
ASFixedRect thisBox;
PDETextGetBBox(annotationsText, kPDETextRun, numTextAnnots-1, &thisBox);
ASFixed thisWidth = (thisBox.right - thisBox.left); //The width of this text object.
ASFixed thisHeight = (thisBox.top - thisBox.bottom); //The height of this text object.
if (thisWidth > neededWidth)
neededWidth = thisWidth;
neededHeight += thisHeight + fontSize;
//Position the next text.
textLoc.v -= fontSize*2;
#endif
#if 0
PDERelease((PDEObject)font);
PDERelease((PDEObject)graphics.fillColorSpec.space);
#endif
#if 0
ASFixed neededWidth = fixedZero; //The required width of a page that can hold all the text objects.
ASFixed neededHeight = fixedZero; //The required height.
//The program needs to move each text object up so that it fits on the page. Otherwise it will be added to the bottom-left corner, and be invisible.
for (int i = 0; i < numTextAnnots; ++i)
{
PDETextItem textItem = PDETextGetItem(annotationsText, i);
//The new location will be the old location, adjusted up, and accounting for the font size.
ASFixedMatrix newLoc;
PDETextItemGetTextMatrix(textItem, 0, &newLoc);
newLoc.v += neededHeight - fontSize*2;
PDETextItemSetTextMatrix(textItem, &newLoc);
}
//Create our output document.
APDFLDoc extractDoc;
extractDoc.insertPage(neededWidth,neededHeight, PDBeforeFirstPage);
PDPage extractPage = extractDoc.getPage(0);
//Put the texts onto the output document.
PDEContentAddElem(PDPageAcquirePDEContent(extractPage, 0), 0, (PDEElement)annotationsText);
PDERelease((PDEObject)annotationsText);
std::wcout << L"I extracted " << numTextAnnots << L" text-containing annotations." << std::endl;
std::wcout << L"Saving the extracted text document." << std::endl;
PDPageSetPDEContentCanRaise(extractPage, 0);
PDPageReleasePDEContent(extractPage, 0);
PDPageRelease(extractPage);
extractDoc.saveDoc(L"AnnotationTexts.pdf",PDSaveFull);
#endif