PDF Layers (OCG) - Add, Show, Hide - C++ Sample Code

Sample code to use Apryse SDK for creating and manipulating PDF layers (also known as Optional Content Groups - OCGs). These samples demonstrate how to create and extract layers, as well as to selectively render them (show, hide) in conforming PDF readers or printers. Sample code provided in Python, C++, C#, Java, Node.js (JavaScript), PHP, Ruby and VB.

Learn more about our Server SDK.

1//---------------------------------------------------------------------------------------
2// Copyright (c) 2001-2024 by Apryse Software Inc. All Rights Reserved.
3// Consult legal.txt regarding legal and license information.
4//---------------------------------------------------------------------------------------
5
6#include <SDF/Obj.h>
7#include <PDF/PDFNet.h>
8#include <PDF/PDFDoc.h>
9#include <PDF/PDFDraw.h>
10#include <PDF/OCG/OCMD.h>
11#include <PDF/ElementBuilder.h>
12#include <PDF/ElementWriter.h>
13#include <PDF/ElementReader.h>
14
15#include <iostream>
16#include "../../LicenseKey/CPP/LicenseKey.h"
17
18using namespace pdftron;
19using namespace Common;
20using namespace SDF;
21using namespace PDF;
22using namespace std;
23
24//-----------------------------------------------------------------------------------
25// This sample demonstrates how to create layers in PDF.
26// The sample also shows how to extract and render PDF layers in documents
27// that contain optional content groups (OCGs)
28//
29// With the introduction of PDF version 1.5 came the concept of Layers.
30// Layers, or as they are more formally known Optional Content Groups (OCGs),
31// refer to sections of content in a PDF document that can be selectively
32// viewed or hidden by document authors or consumers. This capability is useful
33// in CAD drawings, layered artwork, maps, multi-language documents etc.
34//
35// Notes:
36// ---------------------------------------
37// - This sample is using CreateLayer() utility method to create new OCGs.
38// CreateLayer() is relatively basic, however it can be extended to set
39// other optional entries in the 'OCG' and 'OCProperties' dictionary. For
40// a complete listing of possible entries in OC dictionary please refer to
41// section 4.10 'Optional Content' in the PDF Reference Manual.
42// - The sample is grouping all layer content into separate Form XObjects.
43// Although using PDFNet is is also possible to specify Optional Content in
44// Content Streams (Section 4.10.2 in PDF Reference), Optional Content in
45// XObjects results in PDFs that are cleaner, less-error prone, and faster
46// to process.
47//-----------------------------------------------------------------------------------
48
49Obj CreateGroup1(PDFDoc& doc, Obj layer);
50Obj CreateGroup2(PDFDoc& doc, Obj layer);
51Obj CreateGroup3(PDFDoc& doc, Obj layer);
52OCG::Group CreateLayer(PDFDoc& doc, const char* layer_name);
53
54// Relative path to the folder containing test files.
55static const string input_path = "../../TestFiles/";
56static const string output_path = "../../TestFiles/Output/";
57
58int main(int argc, char *argv[])
59{
60 int ret = 0;
61 PDFNet::Initialize(LicenseKey);
62
63 try
64 {
65 PDFDoc doc;
66
67 // Create three layers...
68 OCG::Group image_layer = CreateLayer(doc, "Image Layer");
69 OCG::Group text_layer = CreateLayer(doc, "Text Layer");
70 OCG::Group vector_layer = CreateLayer(doc, "Vector Layer");
71
72 // Start a new page ------------------------------------
73 Page page = doc.PageCreate();
74
75 ElementBuilder builder; // ElementBuilder is used to build new Element objects
76 ElementWriter writer; // ElementWriter is used to write Elements to the page
77 writer.Begin(page); // Begin writing to the page
78
79 // Add new content to the page and associate it with one of the layers.
80 Element element = builder.CreateForm(CreateGroup1(doc, image_layer.GetSDFObj()));
81 writer.WriteElement(element);
82
83 element = builder.CreateForm(CreateGroup2(doc, vector_layer.GetSDFObj()));
84 writer.WriteElement(element);
85
86 // Add the text layer to the page...
87 if (false) // set to true to enable 'ocmd' example.
88 {
89 // A bit more advanced example of how to create an OCMD text layer that
90 // is visible only if text, image and path layers are all 'ON'.
91 // An example of how to set 'Visibility Policy' in OCMD.
92 Obj ocgs = doc.CreateIndirectArray();
93 ocgs.PushBack(image_layer.GetSDFObj());
94 ocgs.PushBack(vector_layer.GetSDFObj());
95 ocgs.PushBack(text_layer.GetSDFObj());
96 OCG::OCMD text_ocmd = OCG::OCMD::Create(doc, ocgs, OCG::OCMD::e_AllOn);
97 element = builder.CreateForm(CreateGroup3(doc, text_ocmd.GetSDFObj()));
98 }
99 else {
100 element = builder.CreateForm(CreateGroup3(doc, text_layer.GetSDFObj()));
101 }
102 writer.WriteElement(element);
103
104 // Add some content to the page that does not belong to any layer...
105 // In this case this is a rectangle representing the page border.
106 element = builder.CreateRect(0, 0, page.GetPageWidth(), page.GetPageHeight());
107 element.SetPathFill(false);
108 element.SetPathStroke(true);
109 element.GetGState().SetLineWidth(40);
110 writer.WriteElement(element);
111
112 writer.End(); // save changes to the current page
113 doc.PagePushBack(page);
114
115 // Set the default viewing preference to display 'Layer' tab.
116 PDFDocViewPrefs prefs = doc.GetViewPrefs();
117 prefs.SetPageMode(PDFDocViewPrefs::e_UseOC);
118
119 doc.Save((output_path + "pdf_layers.pdf").c_str(), SDFDoc::e_linearized, 0);
120 cout << "Done." << endl;
121 }
122 catch(Common::Exception& e)
123 {
124 cout << e << endl;
125 ret = 1;
126 }
127 catch(...)
128 {
129 cout << "Unknown Exception" << endl;
130 ret = 1;
131 }
132
133 // The following is a code snippet shows how to selectively render
134 // and export PDF layers.
135 try
136 {
137 PDFDoc doc((output_path + "pdf_layers.pdf").c_str());
138 doc.InitSecurityHandler();
139
140 if (!doc.HasOC()) {
141 cout << "The document does not contain 'Optional Content'" << endl;
142 }
143 else {
144 OCG::Config init_cfg = doc.GetOCGConfig();
145 OCG::Context ctx(init_cfg);
146
147 PDFDraw pdfdraw;
148 pdfdraw.SetImageSize(1000, 1000);
149 pdfdraw.SetOCGContext(&ctx); // Render the page using the given OCG context.
150
151 Page page = doc.GetPage(1); // Get the first page in the document.
152 pdfdraw.Export(page, (output_path + "pdf_layers_default.png").c_str());
153
154 // Disable drawing of content that is not optional (i.e. is not part of any layer).
155 ctx.SetNonOCDrawing(false);
156
157 // Now render each layer in the input document to a separate image.
158 Obj ocgs = doc.GetOCGs(); // Get the array of all OCGs in the document.
159 if (ocgs != 0) {
160 int i, sz = int(ocgs.Size());
161 for (i=0; i<sz; ++i) {
162 OCG::Group ocg(ocgs.GetAt(i));
163 ctx.ResetStates(false);
164 ctx.SetState(ocg, true);
165 std::string fname("pdf_layers_");
166 fname += ocg.GetName().ConvertToAscii();
167 fname += ".png";
168 cout << fname << endl;
169 pdfdraw.Export(page, (output_path + fname).c_str());
170 }
171 }
172
173 // Now draw content that is not part of any layer...
174 ctx.SetNonOCDrawing(true);
175 ctx.SetOCDrawMode(OCG::Context::e_NoOC);
176 pdfdraw.Export(page, (output_path + "pdf_layers_non_oc.png").c_str());
177 }
178
179 cout << "Done." << endl;
180 }
181 catch(Common::Exception& e)
182 {
183 cout << e << endl;
184 ret = 1;
185 }
186 catch(...)
187 {
188 cout << "Unknown Exception" << endl;
189 ret = 1;
190 }
191
192 PDFNet::Terminate();
193 return ret;
194}
195
196
197// A utility function used to add new Content Groups (Layers) to the document.
198OCG::Group CreateLayer(PDFDoc& doc, const char* layer_name)
199{
200 OCG::Group grp = OCG::Group::Create(doc, layer_name);
201 OCG::Config cfg = doc.GetOCGConfig();
202 if (!cfg.IsValid()) {
203 cfg = OCG::Config::Create(doc, true);
204 cfg.SetName("Default");
205 }
206
207 // Add the new OCG to the list of layers that should appear in PDF viewer GUI.
208 Obj layer_order_array = cfg.GetOrder();
209 if (!layer_order_array) {
210 layer_order_array = doc.CreateIndirectArray();
211 cfg.SetOrder(layer_order_array);
212 }
213 layer_order_array.PushBack(grp.GetSDFObj());
214
215 return grp;
216}
217
218// Creates some content (3 images) and associate them with the image layer
219Obj CreateGroup1(PDFDoc& doc, Obj layer)
220{
221 ElementWriter writer;
222 writer.Begin(doc);
223
224 // Create an Image that can be reused in the document or on the same page.
225 Image img = Image::Create(doc, (input_path + "peppers.jpg").c_str());
226
227 ElementBuilder builder;
228 Element element = builder.CreateImage(img, Common::Matrix2D(img.GetImageWidth()/2, -145, 20, img.GetImageHeight()/2, 200, 150));
229 writer.WritePlacedElement(element);
230
231 GState gstate = element.GetGState(); // use the same image (just change its matrix)
232 gstate.SetTransform(200, 0, 0, 300, 50, 450);
233 writer.WritePlacedElement(element);
234
235 // use the same image again (just change its matrix).
236 writer.WritePlacedElement(builder.CreateImage(img, 300, 600, 200, -150));
237
238 Obj grp_obj = writer.End();
239
240 // Indicate that this form (content group) belongs to the given layer (OCG).
241 grp_obj.PutName("Subtype","Form");
242 grp_obj.Put("OC", layer);
243 grp_obj.PutRect("BBox", 0, 0, 1000, 1000); // Set the clip box for the content.
244
245 return grp_obj;
246}
247
248// Creates some content (a path in the shape of a heart) and associate it with the vector layer
249Obj CreateGroup2(PDFDoc& doc, Obj layer)
250{
251 ElementWriter writer;
252 writer.Begin(doc);
253
254 // Create a path object in the shape of a heart.
255 ElementBuilder builder;
256 builder.PathBegin(); // start constructing the path
257 builder.MoveTo(306, 396);
258 builder.CurveTo(681, 771, 399.75, 864.75, 306, 771);
259 builder.CurveTo(212.25, 864.75, -69, 771, 306, 396);
260 builder.ClosePath();
261 Element element = builder.PathEnd(); // the path geometry is now specified.
262
263 // Set the path FILL color space and color.
264 element.SetPathFill(true);
265 GState gstate = element.GetGState();
266 gstate.SetFillColorSpace(ColorSpace::CreateDeviceCMYK());
267 gstate.SetFillColor(ColorPt(1, 0, 0, 0)); // cyan
268
269 // Set the path STROKE color space and color.
270 element.SetPathStroke(true);
271 gstate.SetStrokeColorSpace(ColorSpace::CreateDeviceRGB());
272 gstate.SetStrokeColor(ColorPt(1, 0, 0)); // red
273 gstate.SetLineWidth(20);
274
275 gstate.SetTransform(0.5, 0, 0, 0.5, 280, 300);
276
277 writer.WriteElement(element);
278
279 Obj grp_obj = writer.End();
280
281 // Indicate that this form (content group) belongs to the given layer (OCG).
282 grp_obj.PutName("Subtype","Form");
283 grp_obj.Put("OC", layer);
284 grp_obj.PutRect("BBox", 0, 0, 1000, 1000); // Set the clip box for the content.
285
286 return grp_obj;
287}
288
289// Creates some text and associate it with the text layer
290Obj CreateGroup3(PDFDoc& doc, Obj layer)
291{
292 ElementWriter writer;
293 writer.Begin(doc);
294
295 // Create a path object in the shape of a heart.
296 ElementBuilder builder;
297
298 // Begin writing a block of text
299 Element element = builder.CreateTextBegin(Font::Create(doc, Font::e_times_roman), 120);
300 writer.WriteElement(element);
301
302 element = builder.CreateTextRun("A text layer!");
303
304 // Rotate text 45 degrees, than translate 180 pts horizontally and 100 pts vertically.
305 Matrix2D transform = Matrix2D::RotationMatrix(-45 * (3.1415/ 180.0));
306 transform *= Matrix2D(1, 0, 0, 1, 180, 100);
307 element.SetTextMatrix(transform);
308
309 writer.WriteElement(element);
310 writer.WriteElement(builder.CreateTextEnd());
311
312 Obj grp_obj = writer.End();
313
314 // Indicate that this form (content group) belongs to the given layer (OCG).
315 grp_obj.PutName("Subtype","Form");
316 grp_obj.Put("OC", layer);
317 grp_obj.PutRect("BBox", 0, 0, 1000, 1000); // Set the clip box for the content.
318
319 return grp_obj;
320}

Did you find this helpful?

Trial setup questions?

Ask experts on Discord

Need other help?

Contact Support

Pricing or product questions?

Contact Sales