Skip to main content Skip to footer

How to Add PDF Layers Programmatically in C#

PDF Layers formally known as Optional Content Groups(OCG) is a useful feature offered by the PDF file format. As the name suggests, a PDF layer is a partial collection of text and images contained in a PDF document and the display of this content in the document can be made optional.

This feature allows the content of the PDF document to be arranged in layers, which can be made visible or hidden dynamically by the consumers of the PDF viewer applications. This feature lets the users control what content is displayed in the document which is quite unfamiliar with other text formats.

There are many uses for creating multi-layer PDF documents such as show or hide information, view alternative views of product design or plans, etc. A few practical scenarios where this feature is helpful to include CAD drawings, layered artwork, maps, and multi-language documents.

GrapeCity Documents for PDF now provides support for working with layers programmatically in PDF documents. You can add/remove layers and set layer properties. GrapeCity Documents for PDF API lets you implement this feature using C#. This blog discusses how to implement this feature using GrapeCity Documents for PDF and C#.

Download Now!

Creating a Multi-Layer PDF Document in C#

The basic steps to create a multi-layer PDF document includes adding layers and associating appropriate content with each layer. The sections ahead describe the implementation details for these steps using relevant API members from GrapeCity Documents for PDF API.

Add a PDF Layer

The AddLayer method of OptionalContentProperties class is used to add a PDF layer to the PDF document. This method accepts the layer name as a parameter and adds the layer with the same name in a PDF document. Here is how to do this in code:

//Add Layer
doc.OptionalContent.AddLayer("Layer1");

Associate Content with PDF Layers

A PDF layer broadly consists of text and images, which can be added to the layer by associating a content stream, a FormXObject, or an annotation with the PDF layer. You can associate the content with the PDF layer either by invoking the BeginLayer and EndLayer methods of GcPdfGraphics class or setting the Layer property of the specific object being added to the document.

The BeginLayer and EndLayer methods are generally used to associate a content stream with the PDF layer, whereas the Layer property is used to associate a FormXObject or an annotation with a PDF layer. The code snippet below depicts associating different types of content with the PDF layer:

//Associate a content stream with a PDF layer
doc.OptionalContent.AddLayer("Layer1"); 
var g = doc.NewPage().Graphics;
g.BeginLayer("Layer1");
g.DrawString("Layer1", new TextFormat() { Font = StandardFonts.Courier }, new _Point(10, 10));
g.EndLayer();

//Associate a FormXObject with a PDF layer
var l1 = doc.OptionalContent.AddLayer("Layer1");
var g = doc.NewPage().Graphics;
FormXObject fxo1 = new FormXObject(doc, new _Rect(0, 0, 100, 100));
fxo1.Graphics.FillRectangle(new _Rect(0, 0, 100, 100), _Color.Blue);
fxo1.Graphics.DrawString(l1.Name, new TextFormat() { Font = StandardFonts.Courier }, new _Point(10, 10));
fxo1.Layer = l1;

//Associate an annotation with a PDF layer
var watermark = new WatermarkAnnotation();
watermark.Image = Image.FromFile("gclogo.png");
watermark.Rect = new RectangleF(400, 100, 100, 25);
watermark.HorzTranslate = 0.4f;
watermark.VertTranslate = 0.4f;
watermark.Layer = l3;
p.Annotations.Add(watermark);

Now, let's have a quick look at a real-time scenario that creates a multi-layer PDF document:

Use Case: A Multilingual PDF Document

Let's consider a common scenario where the use of a multi-layer PDF document is very beneficial; the creation of a multilingual PDF document like a brochure. Here we create a document where the text is written in two languages i.e. Russian and English. The document also includes a few images used commonly for both versions of the document.

The text for both languages will be added to the document by adding two layers namely "English" and "Russian". Later, the language-specific text will be added to each layer, i.e. the English text will be associated as a content stream with the English layer and Russian text will be associated as a content stream for the Russian layer.

Clicking on the respective language in the Layers panel while hiding the other language layer shows how a single document can be used for multiple audiences.

Here is a quick look at the document programmatically created by this example. Please note that currently, it displays the text in the Russian language:

gcpdf

The steps below will describe how you can create such a document:

  1. Create a new .NET Core Console application.
  2. Install GrapeCity.Documents.Pdf package using Nuget Package Manager.
  3. Define the English and Russian text which will be associated with a layer in the PDF document. The code snippet below depicts how to generate the language-specific text to be further consumed in the sample:
class Texts
{
   public string Caption { get; set; }
   public string SubCap { get; set; }
   public string Bullet1 { get; set; }
   public string Bullet2 { get; set; }
   public string Bullet3 { get; set; }
   public string Bullet4 { get; set; }
   public string Bullet5 { get; set; }
   public string Bullet6 { get; set; }
   public string Pt1Hdr { get; set; }
   public string Pt1Txt { get; set; }
   public string Pt2Hdr { get; set; }
   public string Pt2Txt { get; set; }
   public string Pt3Hdr { get; set; }
   public string Pt3Txt { get; set; }
   public string Pt4Hdr { get; set; }
   public string Pt4Txt { get; set; }
   public string Pt5Hdr { get; set; }
   public string Pt5Txt { get; set; }
   public string Pt6Hdr { get; set; }
   public string Pt6Txt { get; set; }
}

static Dictionary<string, Texts> _texts = new Dictionary<string, Texts>()
{
   { "English", new Texts() {
                Caption = "Fast, Efficient Document APIs for .NET 5 and Java Applications",
                SubCap = "Take total control of your documents with ultra-fast, low-footprint APIs for enterprise apps.",
                Bullet1 = "Generate, load, edit, save XLSX spreadsheets, PDF, Images, and DOCX files using C# .NET, VB.NET, or Java",
                Bullet2 = "View, edit, print, fill and submit documents in JavaScript PDF Viewer and PDF Editor",
                Bullet3 = "Compatible on Windows, macOS, and Linux",
                Bullet4 = "No dependencies on Excel, Word, or Acrobat",
                Bullet5 = "Deploy to a variety of cloud-based services, including Azure, AWS, and AWS Lambda",
                Bullet6 = "Product available individually or as a bundle",
                Pt1Hdr = "High-Speed, Small Footprint, No Dependencies",
                Pt1Txt = "The .NET 5 Document API is designed to generate large, optimized documents fast – while remaining lightweight and extensible, giving you greater flexibility, and creativity in developing your applications.",
                Pt2Hdr = "Full .NET Support for Windows, Linux, and Mac",
                Pt2Txt = "Develop for any .NET platform or major operating system with a single code base. Use in your apps for .NET, C#, VB.NET .NET Framework, Mono, Xamarin.iOS, and Xamarin.Android.",
                Pt3Hdr = "Comprehensive, Highly Programmable",
                Pt3Txt = "Do more with your Excel spreadsheets, Word documents, PDFs, and images with our feature-rich APIs. Create, load, edit, save, and convert your business documents with intuitive tools.",
                Pt4Hdr = "Zero Dependencies",
                Pt4Txt = "Generate and edit digital documents in your applications without any dependencies on Adobe Acrobat, Microsoft Word, or Excel. Build your documents even faster with zero dependencies.",
                Pt5Hdr = "Deploy Apps to the Cloud",
                Pt5Txt = "Be everywhere with cloud-based deployment using NuGet and GrapeCity Documents. You can now deploy to Azure, AWS, and AWS Lambda in a few short steps.",
                Pt6Hdr = "Supports Hundreds of Features",
                Pt6Txt = "Generate full-featured documents and nearly lossless imports! These APIs complete your toolkit through high-performance features, including Excel pivot tables, PDF digital signatures, and Word text comments.",
            }},
   { "Russian", new Texts() {
                Caption = "Document API - для быстрой и эффективной работы с офисными документами под .NET 5 и Java",
                SubCap = "Полный контроль над офисным документооборотом с помощью сверх-быстрых, нетребовательных к памяти библиотек Document API",
                Bullet1 = "Создание, загрузка, редактирование и сохранение файлов XLSX, PDF, DOCX и изображений из кода на C#, VB.NET и Java",
                Bullet2 = "Просмотр, редактирование, печать, заполнение и пересылка PDF файлов с помощью просмотрщика и редактора на чистом JavaScript",
                Bullet3 = "Полная совместимость с операционными системами Windows, macOS и Linux",
                Bullet4 = "Отсутствие зависимостей от Excel, Word и Acrobat",
                Bullet5 = "Развертывание на Azure, AWS и AWS Lambda",
                Bullet6 = "Продукты доступны индивидуально или составе единой студии",
                Pt1Hdr = "Быстрые, маленькие библиотеки без внешних зависимостей",
                Pt1Txt = "Наши API созданы для генерации сложных оптимизированных документов с большой скоростью и малыми требованиями к памяти, для лёгкой и удобной разработки аппликаций.",
                Pt2Hdr = "Полная поддержка .NET под Windows, Linux и macOS",
                Pt2Txt = "Единый код для всех платформ и операционных систем, поддержка разработки на C# и VB.NET под .NET 5, .NET Framework, Mono, Xamarin.iOS и Xamarin.Android.",
                Pt3Hdr = "Функциональность и программируемость",
                Pt3Txt = "Полная поддержка функциональности MS Excel, MS Word и PDF документов и изображений. Создание, загрузка, редактирование, сохранение и конвертация документов.",
                Pt4Hdr = "Отсутствие внешних зависимостей",
                Pt4Txt = "Быстрое и эффективное создание и редактирование электронных документов без зависимостей от Adobe Acrobat, Microsoft Word и Excel.",
                Pt5Hdr = "Разворачивание аппликаций в \"облаке\"",
                Pt5Txt = "Простое и удобное разворачивание аппликаций в Azure, AWS и AWS Lambda. Все зависимости от GrapeCity Documents могут разрешаться через NuGet.",
                Pt6Hdr = "Широкая поддержка возможностей форматов",
                Pt6Txt = "Доступ практически ко всем возможностям, заложенным в спецификациях форматов документов - сводные таблицы Excel, множественные цифровые подписи PDF, комментарии и многое другое.",
   }}
};
  1. Define the methods which will be used to associate the above-defined texts with PDF layers. The code snippet below defines these methods:
static PointF DrawText1(string lang, GcPdfDocument doc, GcPdfGraphics g, PointF ip, TextFormat tf, TextFormat tfCap)
{
   var txts = _texts[lang];
   g.BeginLayer(lang);

   var tl = g.CreateTextLayout();
   tl.MaxWidth = 72 * 5;

   tl.AppendLine(txts.Caption, tfCap);
   tl.AppendLine(tf);
   tl.AppendLine(txts.SubCap, tf);
   tl.AppendLine(tf);
   g.DrawTextLayout(tl, ip);

   // Bullet list:
   ip.Y += tl.ContentHeight;
   tl.Clear();
   const string bullet = "\x2022\x2003";
   tl.FirstLineIndent = -g.MeasureString(bullet, tf).Width;
   tl.ParagraphSpacing += 4;

   tl.Append(bullet, tf);
   tl.AppendLine(txts.Bullet1, tf);
   tl.Append(bullet, tf);
   tl.AppendLine(txts.Bullet2, tf);
   tl.Append(bullet, tf);
   tl.AppendLine(txts.Bullet3, tf);
   tl.Append(bullet, tf);
   tl.AppendLine(txts.Bullet4, tf);
   tl.Append(bullet, tf);
   tl.AppendLine(txts.Bullet5, tf);
   tl.Append(bullet, tf);
   tl.AppendLine(txts.Bullet6, tf);
   g.DrawTextLayout(tl, ip);

   g.EndLayer();

   ip.Y += tl.ContentHeight;
   return ip;
}

static void DrawText2(string lang, GcPdfDocument doc, GcPdfGraphics g, PointF ip, TextFormat tfHdr, TextFormat tfBlack)
{
   var picIcons = Image.FromFile(Path.Combine("Resources", "ImagesBis", "2020-icon-sprites-dx.png"));
   var iconSize = new SizeF(picIcons.Width / 12, picIcons.Height / 2);

   var tl = g.CreateTextLayout();
   tl.FirstLineIndent = 0;
   tl.MaxWidth = (doc.Pages[0].Bounds.Width - ip.X * 2 - 36) / 3;
   var ip1 = ip;
   var txts = _texts[lang];

   drawItem(ip, 0, txts.Pt1Hdr, txts.Pt1Txt);
   ip.X += tl.MaxWidth.Value + 18;
   ip1.Y = Math.Max(ip1.Y, ip.Y + tl.ContentHeight);
   drawItem(ip, 1, txts.Pt2Hdr, txts.Pt2Txt);
   ip.X += tl.MaxWidth.Value + 18;
   ip1.Y = Math.Max(ip1.Y, ip.Y + tl.ContentHeight);
   drawItem(ip, 2, txts.Pt3Hdr, txts.Pt3Txt);

   ip1.Y = Math.Max(ip1.Y, ip.Y + tl.ContentHeight);
   ip1.Y += iconSize.Height + 18;
   ip = ip1;
   drawItem(ip, 3, txts.Pt4Hdr, txts.Pt4Txt);
   ip.X += tl.MaxWidth.Value + 18;
   drawItem(ip, 4, txts.Pt5Hdr, txts.Pt5Txt);
   ip.X += tl.MaxWidth.Value + 18;
   drawItem(ip, 5, txts.Pt6Hdr, txts.Pt6Txt);

   void drawItem(PointF p, int imgIdx, string header, string text)
   {
      if (lang == c_lyrEN)
      {
         // Draw icons just once:
         var xImg = p.X + tl.MaxWidth.Value / 2 - iconSize.Width / 2;
         var xImgAll = xImg - imgIdx * iconSize.Width;
         var imgRc = new RectangleF(xImgAll, p.Y, iconSize.Width * 6, iconSize.Height);
         var rcClip = new RectangleF(new PointF(xImg, p.Y), iconSize);
         rcClip.Width -= 1;
         g.DrawImage(picIcons, imgRc, rcClip, ImageAlign.StretchImage);
      }
      tl.Clear();
      tl.AppendLine(header, tfHdr);
      tl.AppendLine(text, tfBlack);
      g.BeginLayer(lang);
      g.DrawTextLayout(tl, new PointF(p.X, p.Y + iconSize.Height + 8));
      g.EndLayer();
    }
  }
}
  1. Now that all the helper methods are in place, create a PDF document by initializing an object of GcPdfDocument class and add the required images to design the document layout common to both the language versions. Here's how to do that:
// Initialize an instance of GcPdfDocument
var doc = new GcPdfDocument();

var page = doc.Pages.Add();
page.Landscape = true;
var g = page.Graphics;

// Background image:
var picBack = Image.FromFile(Path.Combine("Resources", "ImagesBis", "2020-website-gcdocs-headers_tall.png"));
var rc = page.Bounds;
rc.Height *= 0.5f;
g.DrawImage(picBack, rc, null, ImageAlign.StretchImage);

// Image with some screenshots:
var picScreens = Image.FromFile(Path.Combine("Resources", "ImagesBis", "docs-main-02.png"));
var picScreensSize = new SizeF(picScreens.Width / 2, picScreens.Height / 2);
var rcScreen = new RectangleF(page.Bounds.Width - picScreensSize.Width - 8, 8, picScreensSize.Width, picScreensSize.Height);
g.DrawImage(picScreens, rcScreen, null, ImageAlign.StretchImage);
  1. After completing the basic design and layout of the document, add layers to the document and invoke the above-defined methods to associate the language-specific text with a layer:
// Add layers for different languages
doc.OptionalContent.AddLayer(c_lyrEN);
doc.OptionalContent.AddLayer(c_lyrRU);

// Set default visibility to false for all languages except one:
doc.OptionalContent.SetLayerDefaultState(c_lyrEN, false);

// Text formats:
var fReg = Font.FromFile(Path.Combine("Resources", "Fonts", "OpenSans-Regular.ttf"));
var fBold = Font.FromFile(Path.Combine("Resources", "Fonts", "OpenSans-Bold.ttf"));
var tfNorm = new TextFormat() { Font = fReg, FontSize = 9, ForeColor = Color.White };
var tfCap = new TextFormat(tfNorm) { FontSize = tfNorm.FontSize * 1.6f };
var tfBlack = new TextFormat(tfNorm) { ForeColor = Color.FromArgb(32, 32, 32) };
var tfHdr = new TextFormat(tfBlack) { Font = fBold };

// Draw English texts:
var ip = new PointF(36, 24);
var ip1 = ip;
ip = DrawText1(c_lyrEN, doc, g, ip1, tfNorm, tfCap);
ip.Y += 82;
var ip2 = ip;
DrawText2(c_lyrEN, doc, g, ip2, tfHdr, tfBlack);

// Draw Russian texts:
DrawText1(c_lyrRU, doc, g, ip1, tfNorm, tfCap);
DrawText2(c_lyrRU, doc, g, ip2, tfHdr, tfBlack);
  1. Save the generated multi-layer PDF document using the following code snippet:
// Save the PDF:
doc.Save(stream);

The steps above complete the creation of a multilingual PDF document. You can open this document in a PDF viewer application to see the layers and control their visibility.

The image below depicts how the visibility of layers can be altered, by loading the multilingual document generated by the demo sample in Adobe Reader:

document api

The Language Layers demo sample showcases the above implementation in action.

Edit PDF Layers

GrapeCity Documents for PDF API lets you edit layers in an existing PDF document, by loading the multi-layer PDF document in a GcPdfDocument instance. You can edit the layers by setting or altering the layer properties, or by removing layers. The sections ahead provide you detailed information regarding these two features:

Set PDF Layer Properties

A PDF layer's behavior in terms of its visibility, locked state, default state, and printing can all be customized by setting the layer properties. GrapeCity Documents for PDF API members lets you set the following properties of a PDF layer:

  1. Set Locked State: The SetLayerLocked method of OptionalContentProperties class is used to set the locked state of a PDF layer. If a PDF layer is marked as locked, then the viewer application prevents the user from altering the currently visible set for the layer. This method accepts the layer/layer name and a boolean variable as parameters. Passing TRUE as the second parameter locks the layer, and passing FALSE does not lock the layer.
  2. Set the InitialView State: The SetLayerInitialViewState method of OptionalContentProperties class is used to set the visibility of a PDF layer, which determines whether the layer is visible or not when the document is opened. This method accepts the layer/layer name and an appropriate value from the LayerInitialViewState enum as parameters.
  3. Set the Default State: The SetLayerDefaultState method of OptionalContentProperties class is used to set the initial visibility state of the layer when a document is first opened or when the initial visibility is reset. The eye icons for layers are initially shown or hidden based on this value. This method accepts the layer/layer name and a boolean value as parameters. Passing TRUE as the second parameter displays the eye icon for the layer, whereas passing FALSE hides the eye icon for the layer in the layers panel.
  4. Set Print State: The SetLayerPrintState method of OptionalContentProperties class is used to determine whether the layer will be printed or not. This method accepts the layer/layer name and an appropriate value from the LayerPrintState enum as parameters.
  5. Set Export State: The SetLayerExportState method of OptionalContentProperties class is used to determine whether the layer will be exported or not. This method accepts the layer/layer name and an appropriate value from the LayerExportState enum as parameters.
  6. Set Intent: The Intent property of the OptionalContentGroup class is used to set the Intent property of the PDF layer.

The code snippet below is an example of how to set all the properties described above:

// Loads existing document containing the layers and sets layers properties
GcPdfDocument doc = new GcPdfDocument();
using (FileStream fs = new FileStream("floorplan.pdf", FileMode.Open))
{
   doc.Load(fs);

   doc.OptionalContent.SetLayerLocked("Architecture", true);
   doc.OptionalContent.SetLayerInitialViewState("Equipment", LayerInitialViewState.Always);  
   doc.OptionalContent.SetLayerDefaultState("HVAC", false);
   doc.OptionalContent.SetLayerPrintState("Pipe", LayerPrintState.Never);
   doc.OptionalContent.SetLayerExportState("Architecture", LayerExportState.ExportIfVisible);
   doc.OptionalContent.Groups.FindByName("Electrical").Intent = new string[] { "Design" };  
   doc.Save("SetLayerProperties.pdf");
}

Remove Layers

GrapeCity Documents for PDF API lets you remove layers by invoking the RemoveLayer method of OptionContentProperties class, which accepts either the layer or layer name as a parameter. The code snippet below shows how to remove a layer from the PDF document and save the altered document.

// Loads existing document containing the layers and remove layers
GcPdfDocument doc = new GcPdfDocument();
using (FileStream fs = new FileStream("floorplan.pdf", FileMode.Open))
{
  doc.Load(fs);

  //Remove layer by invoking the RemoveLayer method and passing the layer name as parameter
  doc.OptionalContent.RemoveLayer("Electrical");

  doc.Save("RemoveLayers.pdf");
}

Create Multi-Layer PDF Document from a Set of PDFs

As discussed above, we can add a blank layer to a PDF document and then associate a content stream, FormXObject, or annotation to the layer which acts as the layer's content. Here we discuss another way of associating content with a PDF layer i.e. adding another PDF file content to serve as the layer content. Hence, we create a multi-layer PDF document using an available set of PDFs.

Consider the scenario of an electrical plan for a house, which might have different components such as outlets, wiring, security system wiring, etc. each designed in a separate PDF document. To avoid confusion and have all the information in one place, simply create a PDF document with all the PDF documents content added as separate layers to the new PDF document.

This way, the content stays in one place but is separated. This allows the consumer of the document to control how they would like to view the content, based on which how they set the visibility of each layer and view only the expected content.

The code snippet below shows how to create such a PDF document:

public void CreatePDF(Stream stream)
{
   // The list of PDF names' parts identifying their semantics:
   List<string> fnames = new List<string>()
   {
      "full_electrical_plan.pdf",
      "all_outlets.pdf",
      "data_plan_and_detectors.pdf",
      "HVAC_with_wiring.pdf",
      "lighting_plan.pdf",
      "lighting_plan_with_wiring.pdf",
      "security_system_plan.pdf",
    };
    // The common base name:
    var fbase = "how_to_read_electrical_plans_";
    // The directory containing the PDFs:
    var dir = Path.Combine("Resources", "PDFs");

    GcPdfDocument doc = null;
    Page page = null;
    GcPdfGraphics g = null;
    var disposables = new List<IDisposable>();
    // Combine all PDFs into a single document.
    // The first PDF is used as the base,
    // additional PDFs are added as optional content (layers):
    for (int i = 0; i < fnames.Count; ++i)
    {
       var iname = fnames[i];
       var idoc = new GcPdfDocument();
       var ifs = File.OpenRead(Path.Combine(dir, fbase + iname));
       idoc.Load(ifs);
       disposables.Add(ifs);
       if (i == 0)
       {
          doc = idoc;
          page = idoc.Pages.Last;
          g = page.Graphics;
       }
       else
       {
          doc.OptionalContent.AddLayer(iname);
          doc.OptionalContent.SetLayerDefaultState(iname, false);
          g.BeginLayer(iname);
          g.DrawPdfPage(idoc.Pages[0], page.Bounds);
          g.EndLayer();
       }
    }
    // Save the PDF:
    doc.Save(stream);

    // Dispose file streams:
    disposables.ForEach(d_ => d_.Dispose());
}

You can refer to the demo sample to see the above code snippet in action. Here is an image depicting the multi-layer PDF document created using a set of PDFs: as implemented in the code above:

sample

This blog showcased two common scenarios for PDF layering i.e. a multilingual document and an architectural plan. Please let us know if you would like to see any other scenarios implemented in the demo sample. Refer to the documentation for further details about the feature.

Download Now!


Tags:

Manpreet Kaur - Senior Software Engineer

Manpreet Kaur

Senior Software Engineer
comments powered by Disqus