PDFLayers

Sample Obj-C 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. Learn more about our iOS 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#import <OBJC/PDFNetOBJC.h>
7#import <Foundation/Foundation.h>
8
9//-----------------------------------------------------------------------------------
10// This sample demonstrates how to create layers in PDF.
11// The sample also shows how to extract and render PDF layers in documents
12// that contain optional content groups (OCGs)
13//
14// With the introduction of PDF version 1.5 came the concept of Layers.
15// Layers, or as they are more formally known Optional Content Groups (OCGs),
16// refer to sections of content in a PDF document that can be selectively
17// viewed or hidden by document authors or consumers. This capability is useful
18// in CAD drawings, layered artwork, maps, multi-language documents etc.
19//
20// Notes:
21// ---------------------------------------
22// - This sample is using CreateLayer() utility method to create new OCGs.
23// CreateLayer() is relatively basic, however it can be extended to set
24// other optional entries in the 'OCG' and 'OCProperties' dictionary. For
25// a complete listing of possible entries in OC dictionary please refer to
26// section 4.10 'Optional Content' in the PDF Reference Manual.
27// - The sample is grouping all layer content into separate Form XObjects.
28// Although using PDFNet is is also possible to specify Optional Content in
29// Content Streams (Section 4.10.2 in PDF Reference), Optional Content in
30// XObjects results in PDFs that are cleaner, less-error prone, and faster
31// to process.
32//-----------------------------------------------------------------------------------
33
34PTObj * CreateGroup1(PTPDFDoc *doc, PTObj * layer);
35PTObj * CreateGroup2(PTPDFDoc *doc, PTObj * layer);
36PTObj * CreateGroup3(PTPDFDoc *doc, PTObj * layer);
37PTGroup *CreateLayer(PTPDFDoc *doc, NSString *layer_name);
38
39int main(int argc, char *argv[])
40{
41 @autoreleasepool {
42 int ret = 0;
43 [PTPDFNet Initialize: 0];
44
45 @try
46 {
47 PTPDFDoc *doc = [[PTPDFDoc alloc] init];
48
49 // Create three layers...
50 PTGroup *image_layer = CreateLayer(doc, @"Image Layer");
51 PTGroup *text_layer = CreateLayer(doc, @"Text Layer");
52 PTGroup *vector_layer = CreateLayer(doc, @"Vector Layer");
53
54 // Start a new page ------------------------------------
55 PTPage *page = [doc PageCreate: [[PTPDFRect alloc] initWithX1: 0 y1: 0 x2: 612 y2:792]];
56
57 PTElementBuilder *builder = [[PTElementBuilder alloc] init]; // ElementBuilder is used to build new Element objects
58 PTElementWriter *writer= [[PTElementWriter alloc] init]; // ElementWriter is used to write Elements to the page
59 [writer WriterBeginWithPage: page placement: e_ptoverlay page_coord_sys: YES compress: YES resources: NULL]; // Begin writing to the page
60
61 // Add new content to the page and associate it with one of the layers.
62 PTElement *element = [builder CreateFormWithObj: CreateGroup1(doc, [image_layer GetSDFObj])];
63 [writer WriteElement: element];
64
65 element = [builder CreateFormWithObj: CreateGroup2(doc, [vector_layer GetSDFObj])];
66 [writer WriteElement: element];
67
68 // Add the text layer to the page...
69 if (NO) // set to true to enable 'ocmd' example.
70 {
71 // A bit more advanced example of how to create an OCMD text layer that
72 // is visible only if text, image and path layers are all 'ON'.
73 // An example of how to set 'Visibility Policy' in OCMD.
74 PTObj * ocgs = [doc CreateIndirectArray];
75 [ocgs PushBack: [image_layer GetSDFObj]];
76 [ocgs PushBack: [vector_layer GetSDFObj]];
77 [ocgs PushBack: [text_layer GetSDFObj]];
78 PTOCMD *text_ocmd = [PTOCMD Create: doc ocgs: ocgs vis_policy: e_ptAllOn];
79 element = [builder CreateFormWithObj: CreateGroup3(doc, [text_ocmd GetSDFObj])];
80 }
81 else {
82 element = [builder CreateFormWithObj: CreateGroup3(doc, [text_layer GetSDFObj])];
83 }
84 [writer WriteElement: element];
85
86 // Add some content to the page that does not belong to any layer...
87 // In this case this is a rectangle representing the page border.
88 element = [builder CreateRect: 0 y: 0 width: [page GetPageWidth: e_ptcrop] height: [page GetPageHeight: e_ptcrop]];
89 [element SetPathFill: NO];
90 [element SetPathStroke: YES];
91 [[element GetGState] SetLineWidth: 40];
92 [writer WriteElement: element];
93
94 [writer End]; // save changes to the current page
95 [doc PagePushBack: page];
96
97 // Set the default viewing preference to display 'Layer' tab.
98 PTPDFDocViewPrefs *prefs = [doc GetViewPrefs];
99 [prefs SetPageMode: e_ptUseOC];
100
101 [doc SaveToFile: @"../../TestFiles/Output/pdf_layers.pdf" flags: e_ptlinearized];
102 NSLog(@"Done.");
103 }
104 @catch(NSException *e)
105 {
106 NSLog(@"%@", e.reason);
107 ret = 1;
108 }
109
110 // The following is a code snippet shows how to selectively render
111 // and export PDF layers.
112 @try
113 {
114 PTPDFDoc *doc = [[PTPDFDoc alloc] initWithFilepath: @"../../TestFiles/Output/pdf_layers.pdf"];
115 [doc InitSecurityHandler];
116
117 if (![doc HasOC]) {
118 NSLog(@"The document does not contain 'Optional Content'");
119 }
120 else {
121 PTConfig *init_cfg = [doc GetOCGConfig];
122 PTContext *ctx = [[PTContext alloc] initWithConfig: init_cfg];
123
124 PTPDFDraw *pdfdraw = [[PTPDFDraw alloc] initWithDpi: 92];
125 [pdfdraw SetImageSize: 1000 height: 1000 preserve_aspect_ratio: YES];
126 [pdfdraw SetOCGContext: ctx]; // Render the page using the given OCG context.
127
128 PTPage *page = [doc GetPage: 1]; // Get the first page in the document.
129 [pdfdraw Export:page filename: @"../../TestFiles/Output/pdf_layers_default.png" format: @"PNG"];
130
131 // Disable drawing of content that is not optional (i.e. is not part of any layer).
132 [ctx SetNonOCDrawing: NO];
133
134 // Now render each layer in the input document to a separate image.
135 PTObj * ocgs = [doc GetOCGs]; // Get the array of all OCGs in the document.
136 if (ocgs != 0) {
137 unsigned long i, sz = [ocgs Size];
138 for (i=0; i<sz; ++i) {
139 PTGroup *ocg = [[PTGroup alloc] initWithOcg: [ocgs GetAt: i]];
140 [ctx ResetStates: NO];
141 [ctx SetState: ocg state: YES];
142 NSLog(@"pdf_layers_%@.png", [ocg GetName]);
143 NSString* fname = [@"../../TestFiles/Output/pdf_layers_" stringByAppendingFormat: @"%@.png", [ocg GetName]];
144 [pdfdraw Export:page filename: fname format: @"PNG"];
145 }
146 }
147
148 // Now draw content that is not part of any layer...
149 [ctx SetNonOCDrawing: YES];
150 [ctx SetOCDrawMode: e_ptNoOC];
151 [pdfdraw Export:page filename: @"../../TestFiles/Output/pdf_layers_non_oc.png" format: @"PNG" ];
152 }
153
154 NSLog(@"Done.");
155 }
156 @catch(NSException *e)
157 {
158 NSLog(@"%@", e.reason);
159 ret = 1;
160 }
161 [PTPDFNet Terminate: 0];
162 return ret;
163 }
164}
165
166
167// A utility function used to add new Content Groups (Layers) to the document.
168PTGroup *CreateLayer(PTPDFDoc *doc, NSString *layer_name)
169{
170 PTGroup *grp = [PTGroup Create: doc name: layer_name];
171 PTConfig *cfg = [doc GetOCGConfig];
172 if (![cfg IsValid]) {
173 cfg = [PTConfig Create: doc default_config: YES];
174 [cfg SetName: @"Default"];
175 }
176
177 // Add the new OCG to the list of layers that should appear in PDF viewer GUI.
178 PTObj * layer_order_array = [cfg GetOrder];
179 if (!layer_order_array) {
180 layer_order_array = [doc CreateIndirectArray];
181 [cfg SetOrder: layer_order_array];
182 }
183 [layer_order_array PushBack: [grp GetSDFObj]];
184
185 return grp;
186}
187
188// Creates some content (3 images) and associate them with the image layer
189PTObj * CreateGroup1(PTPDFDoc *doc, PTObj * layer)
190{
191 PTElementWriter *writer = [[PTElementWriter alloc] init];
192 [writer WriterBeginWithSDFDoc: [doc GetSDFDoc] compress: YES];
193
194 // Create an Image that can be reused in the document or on the same page.
195 PTImage *img = [PTImage Create: [doc GetSDFDoc] filename: @"../../TestFiles/peppers.jpg"];
196
197 PTElementBuilder *builder = [[PTElementBuilder alloc] init];
198 PTElement *element = [builder CreateImageWithMatrix: img mtx: [[PTMatrix2D alloc] initWithA: [img GetImageWidth]/2 b: -145 c: 20 d: [img GetImageHeight]/2 h: 200 v: 150]];
199 [writer WritePlacedElement: element];
200
201 PTGState *gstate = [element GetGState]; // use the same image (just change its matrix)
202 [gstate SetTransform: 200 b: 0 c: 0 d: 300 h: 50 v: 450];
203 [writer WritePlacedElement: element];
204
205 // use the same image again (just change its matrix).
206 [writer WritePlacedElement: [builder CreateImageWithCornerAndScale: img x: 300 y: 600 hscale: 200 vscale: -150]];
207
208 PTObj * grp_obj = [writer End];
209
210 // Indicate that this form (content group) belongs to the given layer (OCG).
211 [grp_obj PutName: @"Subtype" name: @"Form"];
212 [grp_obj Put: @"OC" obj: layer];
213 [grp_obj PutRect: @"BBox" x1: 0 y1: 0 x2: 1000 y2: 1000]; // Set the clip box for the content.
214
215 return grp_obj;
216}
217
218// Creates some content (a path in the shape of a heart) and associate it with the vector layer
219PTObj * CreateGroup2(PTPDFDoc *doc, PTObj * layer)
220{
221 PTElementWriter *writer = [[PTElementWriter alloc] init];
222 [writer WriterBeginWithSDFDoc: [doc GetSDFDoc] compress: YES];
223
224 // Create a path object in the shape of a heart.
225 PTElementBuilder *builder = [[PTElementBuilder alloc] init];
226 [builder PathBegin]; // start constructing the path
227 [builder MoveTo: 306 y: 396];
228 [builder CurveTo: 681 cy1: 771 cx2: 399.75 cy2: 864.75 x2: 306 y2: 771];
229 [builder CurveTo: 212.25 cy1: 864.75 cx2: -69 cy2: 771 x2: 306 y2: 396];
230 [builder ClosePath];
231 PTElement *element = [builder PathEnd]; // the path geometry is now specified.
232
233 // Set the path FILL color space and color.
234 [element SetPathFill: YES];
235 PTGState *gstate = [element GetGState];
236 [gstate SetFillColorSpace: [PTColorSpace CreateDeviceCMYK]];
237 [gstate SetFillColorWithColorPt: [[PTColorPt alloc] initWithX: 1 y: 0 z: 0 w: 0]]; // cyan
238
239 // Set the path STROKE color space and color.
240 [element SetPathStroke: YES];
241 [gstate SetStrokeColorSpace: [PTColorSpace CreateDeviceRGB]];
242 [gstate SetStrokeColorWithColorPt: [[PTColorPt alloc] initWithX: 1 y: 0 z: 0 w: 0]]; // red
243 [gstate SetLineWidth: 20];
244
245 [gstate SetTransform: 0.5 b: 0 c: 0 d: 0.5 h: 280 v: 300];
246
247 [writer WriteElement: element];
248
249 PTObj * grp_obj = [writer End];
250
251 // Indicate that this form (content group) belongs to the given layer (OCG).
252 [grp_obj PutName: @"Subtype" name: @"Form"];
253 [grp_obj Put: @"OC" obj: layer];
254 [grp_obj PutRect: @"BBox" x1: 0 y1: 0 x2: 1000 y2: 1000]; // Set the clip box for the content.
255
256 return grp_obj;
257}
258
259// Creates some text and associate it with the text layer
260PTObj * CreateGroup3(PTPDFDoc *doc, PTObj * layer)
261{
262 PTElementWriter *writer = [[PTElementWriter alloc] init];
263 [writer WriterBeginWithSDFDoc: [doc GetSDFDoc] compress: YES];
264
265 // Create a path object in the shape of a heart.
266 PTElementBuilder *builder = [[PTElementBuilder alloc] init];
267
268 // Begin writing a block of text
269 PTElement *element = [builder CreateTextBeginWithFont: [PTFont Create: [doc GetSDFDoc] type: e_pttimes_roman embed: NO] font_sz: 120];
270 [writer WriteElement: element];
271
272 element = [builder CreateTextRun: @"A text layer!"];
273
274 // Rotate text 45 degrees, than translate 180 pts horizontally and 100 pts vertically.
275 PTMatrix2D *transform = [PTMatrix2D RotationMatrix: -45 * (3.1415/ 180.0)];
276 [transform Concat: 1 b: 0 c: 0 d: 1 h: 180 v: 100];
277 [element SetTextMatrixWithMatrix2D: transform];
278
279 [writer WriteElement: element];
280 [writer WriteElement: [builder CreateTextEnd]];
281
282 PTObj * grp_obj = [writer End];
283
284 // Indicate that this form (content group) belongs to the given layer (OCG).
285 [grp_obj PutName: @"Subtype" name: @"Form"];
286 [grp_obj Put: @"OC" obj: layer];
287 [grp_obj PutRect: @"BBox" x1: 0 y1: 0 x2: 1000 y2: 1000]; // Set the clip box for the content.
288
289 return grp_obj;
290}

Did you find this helpful?

Trial setup questions?

Ask experts on Discord

Need other help?

Contact Support

Pricing or product questions?

Contact Sales