Can I use PDFNet to group the OCG layers of an existing PDF document?

Q:

I have a document, which I didn’t create, that has several OCG layers on it. This document is attached to this message, “test.pdf”. You can see it has some layers that start with “X” (“X1”, “X2”, etc.), some that begin with “L” (“L1”, “L2”, etc.), and some that start with none. In the document, these layers are all “flat” — they don’t appear inside a hierarchy at all. But I want to group them together. I want the document to have three “top level” layers called “Group X”, “Group L”, and “Rest”. I want all the layers that start with “X” to be grouped underneath “Group X”, and when I switch off the “Group X” layer, all the layers with names starting with “X” switch off too. How can I edit this document to make that happen?

A:

There are three things you must do after adding the new top-level layers. First, you need to change the document’s OCG config so that all layers show up in the hierarchy you want (https://groups.google.com/d/msg/pdfnet-sdk/H0MlFKphIH0/juRbQNIg8lcJ). Then you should add those new groups to any page that references the existing layers. Finally, you should rewrite the content stream to nest the layers, so that when a “top-level” layer is turned off, its children are as well. Since ElementBuilder doesn’t provide ways to create marked content tags, you can simply use ElementWriter.WriteString to add the marked content delimiters. The following code sample (which you can use directly in the PDFLayersTest sample for .NET) shows how:

`

using System;
using pdftron;
using pdftron.Common;
using pdftron.Filters;
using pdftron.SDF;
using pdftron.PDF;
using pdftron.PDF.OCG;

using XSet = System.Collections.Generic.List;

namespace PDFLayersTestCS
{
class Class1
{
private static pdftron.PDFNetLoader pdfNetLoader = pdftron.PDFNetLoader.Instance();

static string input_path = “…/…/…/…/TestFiles/”;
static string output_path = “…/…/…/…/TestFiles/Output/”;

static void Reorganize(PDFDoc doc, ref System.Collections.Generic.Dictionary<String, Group> groups)
{
Obj ocgs = doc.GetOCGs();
Config cfg = doc.GetOCGConfig();
// create new groups
Group Gruppe_L = Group.Create(doc, “Gruppe_L”);
Group Gruppe_X = Group.Create(doc, “Gruppe X”);
Group Rest = Group.Create(doc, “Rest”);

groups.Add(“oc_gruppe_l”, Gruppe_L);
groups.Add(“oc_gruppe_x”, Gruppe_X);
groups.Add(“oc_rest”, Rest);

bool isarr = Gruppe_L.GetSDFObj().IsArray();

Obj old_order = cfg.GetOrder();
Obj new_order = doc.CreateIndirectArray();
new_order.PushBack(Gruppe_L.GetSDFObj());
Obj sub_arr = new_order.PushBackArray();
sub_arr.PushBack(old_order.GetAt(1)); // L1
sub_arr.PushBack(old_order.GetAt(2)); // L2
sub_arr.PushBack(old_order.GetAt(3)); // L3
sub_arr.PushBack(old_order.GetAt(4)); // L5
new_order.PushBack(Gruppe_X.GetSDFObj());
sub_arr = new_order.PushBackArray();
sub_arr.PushBack(old_order.GetAt(6)); // X1
sub_arr.PushBack(old_order.GetAt(7)); // X2
sub_arr.PushBack(old_order.GetAt(8)); // X3;
new_order.PushBack(Rest.GetSDFObj());
sub_arr = new_order.PushBackArray();
sub_arr.PushBack(old_order.GetAt(0)); // 0
sub_arr.PushBack(old_order.GetAt(5)); // Planrand
cfg.SetOrder(new_order);
}

static string GetParentGroup(Element element)
{
string group_name = element.GetMCPropertyDict().Get(“Name”).Value().GetAsPDFText();
if (group_name == “L1” ||
group_name == “L2” ||
group_name == “L3” ||
group_name == “L5”) return “oc_gruppe_l”;
if (group_name == “X1” ||
group_name == “X2” ||
group_name == “X3”) return “oc_gruppe_x”;
if (group_name == “0” ||
group_name == “Planrand”) return “oc_rest”;
return “”;
}

static void ProcessElements(ElementReader reader, ElementWriter writer, XSet visited)
{
Element element;
while ((element = reader.Next()) != null) // Read page contents
{
switch (element.GetType())
{
case Element.Type.e_marked_content_begin:
{
string parent_oc = GetParentGroup(element);
writer.WriteString(" /OC /" + parent_oc + " BDC “);
writer.WriteElement(element);
break;
}
case Element.Type.e_marked_content_end:
{
writer.WriteElement(element);
writer.WriteString(” EMC ");
break;
}
case Element.Type.e_form:
{
writer.WriteElement(element); // write Form XObject reference to current stream

Obj form_obj = element.GetXObject();
if (!visited.Contains(form_obj.GetObjNum())) // if this XObject has not been processed
{
// recursively process the Form XObject
visited.Add(form_obj.GetObjNum());
ElementWriter new_writer = new ElementWriter();

reader.FormBegin();
new_writer.Begin(form_obj, true);
ProcessElements(reader, new_writer, visited);
new_writer.End();
reader.End();
}
break;
}
default:
writer.WriteElement(element);
break;
}
}
}

static void RewriteContent(PDFDoc doc, ref System.Collections.Generic.Dictionary<String, Group> groups)
{
ElementWriter writer = new ElementWriter();
ElementReader reader = new ElementReader();
XSet visited = new XSet();
PageIterator itr = doc.GetPageIterator();

while (itr.HasNext())
{
Page page = itr.Current();
visited.Add(page.GetSDFObj().GetObjNum());

reader.Begin(page);
writer.Begin(page, ElementWriter.WriteMode.e_replacement, false);
ProcessElements(reader, writer, visited);
writer.End();
reader.End();

// add the new “parent” OCGs to the page
Obj properties_dict = page.GetSDFObj().Get(“Resources”).Value().Get(“Properties”).Value();
foreach (var item in groups)
{
properties_dict.Put(item.Key, item.Value.GetSDFObj());
}

itr.Next();
}
}

[STAThread]
static void Main(string[] args)
{
PDFNet.Initialize();

try
{
PDFDoc doc = new PDFDoc(input_path + “test.pdf”);

System.Collections.Generic.Dictionary<String, Group> groups_to_add = new System.Collections.Generic.Dictionary<string,Group>();
Reorganize(doc, ref groups_to_add);
RewriteContent(doc, ref groups_to_add);

doc.Save(output_path + “test_reorganized.pdf”, 0);
}
catch (PDFNetException e)
{
Console.WriteLine(e.Message);
}
}
}
}

`

test.pdf (3.02 KB)