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

Did you find this helpful?

Trial setup questions?

Ask experts on Discord

Need other help?

Contact Support

Pricing or product questions?

Contact Sales