> For the complete documentation index, see [llms.txt](https://docs.apryse.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.apryse.com/core/get-started/samples/documentcreation.md).

# Create New Document - DocumentCreation

View code using the Document Layout Engine to easily create reflowable PDF documents programmatically using Apryse SDK. Samples provided in Python, C++, C#, Java, Node.js (JavaScript), PHP, and Ruby

The following sample illustrates how to use the Layout::FlowDocument API to create documents from scratch. Instead of positioning every element on a page, the content is described as a tree of paragraphs, tables, lists, shapes, charts, and floats, which is then paginated into a PDF document. \\

\
Each function below produces a document:

* A**n invoice:** Tables, cell merging, styling, headers/footers, images.
* **A report:** Sections, page setup, lists, charts, page numbers.
* A **newsletter:** Shapes, shape text boxes, floats, hyperlinks, tab stops.
* A **content tree walk:** Post-processing of an existing content tree.

{% tabs %}
{% tab title="C#" %}
{% code lineNumbers="true" %}

```csharp
//
// Copyright (c) 2001-2026 by Apryse Software Inc. All Rights Reserved.
//

using System;
using System.Globalization;
using pdftron;
using pdftron.Common;
using pdftron.SDF;
using pdftron.PDF;
using pdftron.Layout;

namespace DocumentCreationTestCS
{
    class Class1
    {
        private static pdftron.PDFNetLoader pdfNetLoader = pdftron.PDFNetLoader.Instance();
        static Class1() { }
        static readonly string input_path = "../../../../../../../../../Samples/TestFiles/";
        static readonly string output_path = "../../../../../../../../../Samples/TestFiles/Output/";

        static string IntToString(int val) => val.ToString(CultureInfo.InvariantCulture);

        static string MoneyToString(double val) => "$" + val.ToString("F2", CultureInfo.InvariantCulture);

        static Paragraph AddHeading(ContentContainer container, string text, double fontSize, byte r, byte g, byte b)
        {
            Paragraph heading = container.AddParagraph(text);
            TextStyledElement style = heading.GetTextStyledElement();
            style.SetFontSize(fontSize);
            style.SetBold(true);
            style.SetTextColor(r, g, b);
            heading.SetSpaceBefore(12);
            heading.SetSpaceAfter(6);
            return heading;
        }

        static TableRow AddTableRowWithText(Table table, string[] texts, bool bold, byte backR, byte backG, byte backB)
        {
            TableRow row = table.AddTableRow();
            for (int i = 0; i < texts.Length; ++i)
            {
                TableCell cell = row.AddTableCell();
                cell.SetBackgroundColor(backR, backG, backB);
                cell.SetVerticalAlignment(TableCell.CellAlignmentVertical.e_center);

                Paragraph para = cell.AddParagraph(texts[i]);
                para.SetStartIndent(3);
                TextStyledElement style = para.GetTextStyledElement();
                style.SetFontSize(10);
                style.SetBold(bold);
                para.SetJustificationMode(i == 0 ? Paragraph.TextJustification.e_left : Paragraph.TextJustification.e_right);
            }
            return row;
        }

        static void AddPageNumberFooter(Section section, string title)
        {
            Section.PageGroup[] groups = { Section.PageGroup.e_first_page, Section.PageGroup.e_even_pages, Section.PageGroup.e_odd_pages };
            foreach (var group in groups)
            {
                ContentContainer footer = section.GetOrCreateFooter(group);
                Paragraph para = footer.AddParagraph();
                para.SetJustificationMode(Paragraph.TextJustification.e_center);

                TextStyledElement style = para.GetTextStyledElement();
                style.SetFontSize(9);
                style.SetTextColor(120, 120, 120);
                style.SetItalic(true);

                para.AddText(title + "   |   Page ");
                para.AddPageNumber(PageNumber.NumberType.e_current_page);
                para.AddText(" of ");
                para.AddPageNumber(PageNumber.NumberType.e_total_pages);
            }
        }

        static int CreateInvoice()
        {
            try
            {
                using (FlowDocument doc = new FlowDocument())
                {
                    doc.SetDefaultPageSize(612, 792);
                    doc.SetDefaultMargins(54, 54, 54, 54);

                    Section section = doc.GetCurrentSection();

                    ContentContainer header = section.GetOrCreateHeader(Section.PageGroup.e_odd_pages);
                    Paragraph headerPara = header.AddParagraph("SYH Supply Co.");
                    headerPara.SetJustificationMode(Paragraph.TextJustification.e_right);
                    headerPara.GetTextStyledElement().SetFontSize(9);
                    headerPara.GetTextStyledElement().SetTextColor(120, 120, 120);

                    AddPageNumberFooter(section, "Invoice INV-2025-0042");

                    Paragraph title = doc.AddParagraph();
                    Float logoFloat = title.AddFloat();
                    logoFloat.SetHorizontalReferencePoint(Float.FloatReferencePoint.e_reference_page);
                    logoFloat.SetVerticalReferencePoint(Float.FloatReferencePoint.e_reference_paragraph);
                    logoFloat.SetPositionX(400);
                    logoFloat.SetPositionY(0);
                    logoFloat.SetObstructionType(Float.FloatObstructionType.e_square);

                    logoFloat.AddParagraph().AddImage(96, 96, input_path + "logo_red.png");

                    title.AddText("INVOICE");
                    TextStyledElement titleStyle = title.GetTextStyledElement();
                    titleStyle.SetFontSize(28);
                    titleStyle.SetBold(true);
                    titleStyle.SetTextColor(0, 51, 102);

                    Paragraph subtitle = doc.AddParagraph("Invoice #INV-2025-0042\nIssued: March 14, 2025\nDue: April 13, 2025");
                    subtitle.GetTextStyledElement().SetFontSize(10);
                    subtitle.SetSpaceAfter(18);

                    Table addresses = doc.AddTable();
                    addresses.SetDefaultColumnWidth(240);
                    addresses.SetDefaultRowHeight(16);

                    TableRow addressRow = addresses.AddTableRow();
                    string[] addressTitles = { "BILL TO", "SHIP TO" };
                    string[] addressBodies = {
                        "Contoso Ltd.\n123 Maple Street\nVancouver, BC V6B 1A1\nCanada",
                        "Contoso Warehouse #4\n980 Industrial Way\nBurnaby, BC V5J 3J1\nCanada"
                    };

                    for (int i = 0; i < 2; ++i)
                    {
                        TableCell cell = addressRow.AddTableCell();
                        cell.SetBackgroundColor(240, 244, 248);
                        cell.SetBorder(0.5, 200, 210, 220);

                        Paragraph caption = cell.AddParagraph(addressTitles[i]);
                        caption.SetStartIndent(3);
                        TextStyledElement captionStyle = caption.GetTextStyledElement();
                        captionStyle.SetFontSize(9);
                        captionStyle.SetBold(true);
                        captionStyle.SetTextColor(0, 51, 102);

                        Paragraph body = cell.AddParagraph(addressBodies[i]);
                        body.SetStartIndent(3);
                        body.GetTextStyledElement().SetFontSize(10);
                    }

                    doc.AddParagraph(" ");

                    Table items = doc.AddTable();
                    items.SetDefaultColumnWidth(120);
                    items.SetDefaultRowHeight(18);
                    items.SetBorder(0.75, 0, 51, 102);

                    string[] headers = { "Description", "Qty", "Unit price", "Amount" };
                    TableRow headerRow = AddTableRowWithText(items, headers, true, 0, 51, 102);

                    for (ContentNodeIterator cellItr = headerRow.GetContentNodeIterator(); cellItr.HasNext(); cellItr.Next())
                    {
                        TableCell cell = cellItr.Current().AsTableCell();
                        if (cell == null) continue;
                        for (ContentNodeIterator paraItr = cell.GetContentNodeIterator(); paraItr.HasNext(); paraItr.Next())
                        {
                            Paragraph p = paraItr.Current().AsParagraph();
                            if (p != null) p.GetTextStyledElement().SetTextColor(255, 255, 255);
                        }
                    }

                    (string desc, int qty, double price)[] lineItems = {
                        ("Height adjustable desk, oak finish", 2, 4800.00),
                        ("Ergonomic office chair", 5, 950.00),
                        ("Meeting room whiteboard, 180 cm", 1, 1200.00),
                        ("On-site assembly and delivery", 1, 2500.00),
                    };

                    double subtotal = 0;
                    for (int i = 0; i < lineItems.Length; ++i)
                    {
                        double amount = lineItems[i].qty * lineItems[i].price;
                        subtotal += amount;

                        string[] cells = {
                            lineItems[i].desc,
                            IntToString(lineItems[i].qty),
                            MoneyToString(lineItems[i].price),
                            MoneyToString(amount)
                        };

                        bool even = (i % 2) == 0;
                        AddTableRowWithText(items, cells, false, even ? (byte)255 : (byte)240, even ? (byte)255 : (byte)244, even ? (byte)255 : (byte)248);
                    }

                    double tax = subtotal * 0.12;
                    (string label, double value, bool bold)[] totals = {
                        ("Subtotal", subtotal, false),
                        ("GST/PST (12%)", tax, false),
                        ("Total due (CAD)", subtotal + tax, true),
                    };

                    for (int i = 0; i < totals.Length; ++i)
                    {
                        string[] cells = { "", "", totals[i].label, MoneyToString(totals[i].value) };
                        AddTableRowWithText(items, cells, totals[i].bold, 255, 255, 255);
                        items.GetTableCell(0, (uint)(lineItems.Length + 1 + i)).MergeCellsRight(1);
                    }

                    Paragraph notes = doc.AddParagraph("Payment is due within 30 days. Late payments are subject to a 1.5% monthly interest charge. Please reference the invoice number with your payment.");
                    notes.SetSpaceBefore(24);
                    notes.GetTextStyledElement().SetFontSize(9);
                    notes.GetTextStyledElement().SetItalic(true);
                    notes.GetTextStyledElement().SetTextColor(90, 90, 90);

                    Paragraph signature = doc.AddParagraph();
                    signature.SetSpaceBefore(18);
                    signature.AddImage(140, 50, input_path + "signature.jpg");
                    signature.AddText("\nAuthorized signature");

                    using (PDFDoc pdf = doc.PaginateToPDF())
                    {
                        pdf.Save(output_path + "created_invoice.pdf", SDFDoc.SaveOptions.e_linearized);
                    }
                }
                Console.WriteLine("Saved created_invoice.pdf");
                return 0;
            }
            catch (PDFNetException e)
            {
                Console.WriteLine(e.Message);
                return 1;
            }
        }

        static int CreateReportWithChart()
        {
            try
            {
                using (FlowDocument doc = new FlowDocument())
                {
                    doc.SetDefaultPageSize(612, 792);
                    doc.SetDefaultMargins(72, 72, 72, 72);

                    Section dataSection = doc.GetCurrentSection();
                    AddPageNumberFooter(dataSection, "Quarterly Revenue Report");

                    AddHeading(doc, "Quarterly Revenue Report 2025", 22, 0, 51, 102);
                    Paragraph intro = doc.AddParagraph("The table below summarizes the revenue by region for the first three quarters of the fiscal year. All figures are given in thousands of Canadian dollars and exclude intercompany transactions.");
                    intro.GetTextStyledElement().SetFontSize(11);
                    intro.SetSpaceAfter(12);

                    Table table = doc.AddTable();
                    table.SetDefaultColumnWidth(90);
                    table.SetDefaultRowHeight(18);
                    table.SetBorder(0.75, 120, 120, 120);

                    AddTableRowWithText(table, new[] { "Region", "Q1", "Q2", "Q3", "Total" }, true, 220, 230, 240);

                    (string region, int q1, int q2, int q3)[] rows = {
                        ("North America", 1250, 1410, 1580),
                        ("Europe", 910, 980, 1120),
                        ("Asia Pacific", 640, 720, 905),
                        ("Latin America", 310, 355, 390),
                    };

                    int t1 = 0, t2 = 0, t3 = 0;
                    foreach (var r in rows)
                    {
                        t1 += r.q1; t2 += r.q2; t3 += r.q3;
                        AddTableRowWithText(table, new[] { r.region, IntToString(r.q1), IntToString(r.q2), IntToString(r.q3), IntToString(r.q1 + r.q2 + r.q3) }, false, 255, 255, 255);
                    }

                    AddTableRowWithText(table, new[] { "All regions", IntToString(t1), IntToString(t2), IntToString(t3), IntToString(t1 + t2 + t3) }, true, 240, 240, 240);

                    AddHeading(doc, "Key observations", 14, 0, 51, 102);
                    List observations = doc.AddList();
                    observations.SetNumberFormat(List.NumberFormat.e_decimal, ".", true);
                    observations.GetLabelStyle().SetBold(true);
                    observations.AddItem().AddParagraph("Revenue grew in every region, with Asia Pacific showing the strongest quarter over quarter growth.");
                    observations.AddItem().AddParagraph("North America remains the largest market, contributing roughly 40% of the total revenue.");

                    ListItem risks = observations.AddItem();
                    risks.AddParagraph("Risks that may affect the Q4 forecast:");
                    List riskList = risks.AddList();
                    riskList.SetNumberFormat(List.NumberFormat.e_lower_letter, ")", true);
                    riskList.AddItem().AddParagraph("Currency fluctuations in Latin America.");
                    riskList.AddItem().AddParagraph("Longer sales cycles in the enterprise segment.");

                    Section chartSection = doc.AddSection();
                    chartSection.SetPageSize(792, 612);
                    chartSection.SetMargins(54, 54, 54, 54);
                    AddPageNumberFooter(chartSection, "Quarterly Revenue Report");

                    AddHeading(chartSection, "Revenue by region and quarter", 18, 0, 51, 102);

                    string chartDefinition = "{" +
                        "\"chartType\":\"bar\"," +
                        "\"title\":\"Revenue by region (thousands CAD)\"," +
                        "\"theme\":\"vivid\"," +
                        "\"data\":{" +
                        "\"categories\":{ \"data\": [\"Q1\", \"Q2\", \"Q3\"] }," +
                        "\"series\": [" +
                        "{ \"name\": { \"data\": [\"North America\"] }, \"data\": { \"data\": [1250, 1410, 1580] } }," +
                        "{ \"name\": { \"data\": [\"Europe\"] }, \"data\": { \"data\": [910, 980, 1120] } }," +
                        "{ \"name\": { \"data\": [\"Asia Pacific\"] }, \"data\": { \"data\": [640, 720, 905] } }," +
                        "{ \"name\": { \"data\": [\"Latin America\"] }, \"data\": { \"data\": [310, 355, 390] } }" +
                        "]" +
                        "}" +
                        "}";

                    Paragraph chartPara = chartSection.AddParagraph();
                    chartPara.SetJustificationMode(Paragraph.TextJustification.e_center);
                    Chart chart = chartPara.AddChart(560, 340, chartDefinition);
                    chart.SetWidth(600);
                    chart.SetHeight(360);

                    Paragraph caption = chartSection.AddParagraph("Figure 1: revenue distribution across the four sales regions.");
                    caption.SetJustificationMode(Paragraph.TextJustification.e_center);
                    caption.GetTextStyledElement().SetItalic(true);
                    caption.GetTextStyledElement().SetFontSize(10);

                    Console.WriteLine("Report sections: " + doc.GetNumSections() + ", second section page width: " + doc.GetSection(1).GetPageWidth().ToString(CultureInfo.InvariantCulture));

                    using (PDFDoc pdf = doc.PaginateToPDF())
                    {
                        pdf.Save(output_path + "created_report.pdf", SDFDoc.SaveOptions.e_linearized);
                    }
                }
                Console.WriteLine("Saved created_report.pdf");
                return 0;
            }
            catch (PDFNetException e)
            {
                Console.WriteLine(e.Message);
                return 1;
            }
        }

        static int CreateNewsletter()
        {
            try
            {
                string bodyText = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.";

                using (FlowDocument doc = new FlowDocument())
                {
                    doc.SetDefaultPageSize(612, 792);
                    doc.SetDefaultMargins(60, 60, 60, 60);

                    Section section = doc.GetCurrentSection();
                    AddPageNumberFooter(section, "The Monthly Dispatch");

                    Paragraph mastheadPara = doc.AddParagraph();
                    Shape masthead = mastheadPara.AddShape(Shape.ShapeType.e_rectangle_rounded_corners, 492, 70);
                    masthead.SetCornerRadius(10);
                    masthead.SetBackgroundColor(0, 51, 102);
                    masthead.SetOutlineColor(0, 31, 62);
                    masthead.SetOutlineThickness(1.5);

                    ContentContainer mastheadText = masthead.GetTextBox();
                    Paragraph mastheadTitle = mastheadText.AddParagraph("The Monthly Dispatch");
                    mastheadTitle.SetJustificationMode(Paragraph.TextJustification.e_center);
                    TextStyledElement mastheadStyle = mastheadTitle.GetTextStyledElement();
                    mastheadStyle.SetFontSize(24);
                    mastheadStyle.SetBold(true);
                    mastheadStyle.SetTextColor(255, 255, 255);

                    Paragraph mastheadSub = mastheadText.AddParagraph("Issue 42 - March 2025");
                    mastheadSub.SetJustificationMode(Paragraph.TextJustification.e_center);
                    mastheadSub.GetTextStyledElement().SetFontSize(10);
                    mastheadSub.GetTextStyledElement().SetTextColor(200, 215, 230);

                    AddHeading(doc, "Documents, assembled", 16, 0, 51, 102);

                    Paragraph lead = doc.AddParagraph();
                    lead.SetJustificationMode(Paragraph.TextJustification.e_left);
                    lead.SetTextIndent(18);
                    lead.SetSpaceAfter(10);

                    Float pullQuote = lead.AddFloat();
                    pullQuote.SetHorizontalReferencePoint(Float.FloatReferencePoint.e_reference_page);
                    pullQuote.SetVerticalReferencePoint(Float.FloatReferencePoint.e_reference_paragraph);
                    pullQuote.SetPositionX(380);
                    pullQuote.SetPositionY(10);
                    pullQuote.SetObstructionType(Float.FloatObstructionType.e_square);

                    Paragraph quoteHolder = pullQuote.AddParagraph();
                    Shape quoteBox = quoteHolder.AddShape(Shape.ShapeType.e_rectangle_rounded_corners, 190, 70);
                    quoteBox.SetCornerRadius(6);
                    quoteBox.SetBackgroundColor(240, 246, 255);
                    quoteBox.SetOutlineColor(0, 51, 102);
                    quoteBox.SetOutlineThickness(1.0);

                    ContentContainer quoteText = quoteBox.GetTextBox();
                    Paragraph quote = quoteText.AddParagraph("\"The content tree lets you describe a document, not a page.\"");
                    quote.SetStartIndent(6);
                    quote.SetEndIndent(6);
                    TextStyledElement quoteStyle = quote.GetTextStyledElement();
                    quoteStyle.SetFontSize(12);
                    quoteStyle.SetItalic(true);
                    quoteStyle.SetTextColor(0, 51, 102);

                    lead.AddText(bodyText);
                    lead.AddText(" Read more about the API at ");
                    TextRun link = lead.AddHyperlink("apryse.com", "https://www.apryse.com");
                    link.GetTextStyledElement().SetTextColor(0, 102, 204);
                    lead.AddText(".");

                    Paragraph columnLeft = doc.AddParagraph(bodyText);
                    columnLeft.SetStartIndent(0);
                    columnLeft.SetEndIndent(260);
                    columnLeft.GetTextStyledElement().SetFontSize(10);

                    Paragraph columnRight = doc.AddParagraph(bodyText);
                    columnRight.SetStartIndent(260);
                    columnRight.SetEndIndent(0);
                    columnRight.GetTextStyledElement().SetFontSize(10);

                    AddHeading(doc, "Upcoming webinars", 14, 0, 51, 102);

                    (string date, string topic, string host)[] schedule = {
                        ("Apr 02", "Building documents from scratch", "R. Fischer"),
                        ("Apr 16", "Charts and data visualization", "M. Okafor"),
                        ("Apr 30", "Accessible PDF output", "L. Tanaka"),
                    };

                    foreach (var w in schedule)
                    {
                        Paragraph row = doc.AddParagraph();
                        row.AddTabStop(80);
                        row.AddTabStop(360);
                        row.GetTextStyledElement().SetFontSize(10);
                        row.AddText(w.date + "\t" + w.topic + "\t" + w.host);
                    }

                    Paragraph gallery = doc.AddParagraph();
                    gallery.SetSpaceBefore(16);
                    gallery.SetJustificationMode(Paragraph.TextJustification.e_center);

                    Shape arrow = gallery.AddShape(Shape.ShapeType.e_arrow, 90, 40);
                    arrow.SetBackgroundColor(0, 153, 102);
                    arrow.SetOutlineColor(0, 102, 68);
                    arrow.SetOutlineThickness(1);
                    arrow.SetArrowHeadSize(18);

                    gallery.AddText("   ");
                    gallery.AddImage(160, 120, input_path + "butterfly.png");

                    using (PDFDoc pdf = doc.PaginateToPDF())
                    {
                        pdf.Save(output_path + "created_newsletter.pdf", SDFDoc.SaveOptions.e_linearized);
                    }
                }
                Console.WriteLine("Saved created_newsletter.pdf");
                return 0;
            }
            catch (PDFNetException e)
            {
                Console.WriteLine(e.Message);
                return 1;
            }
        }

        static void RestyleContentTree(ContentNode node, ref bool highlight)
        {
            for (ContentNodeIterator itr = node.GetContentNodeIterator(); itr.HasNext(); itr.Next())
            {
                ContentElement el = itr.Current();

                TextRun textRun = el.AsTextRun();
                if (textRun != null)
                {
                    if (highlight)
                    {
                        TextStyledElement style = textRun.GetTextStyledElement();
                        style.SetBold(true);
                        style.SetBackgroundColor(255, 245, 180);
                    }
                    highlight = !highlight;
                    continue;
                }

                TableCell cell = el.AsTableCell();
                if (cell != null) cell.SetBorder(0.5, 150, 150, 150);

                ContentNode child = el.AsContentNode();
                if (child != null) RestyleContentTree(child, ref highlight);
            }
        }

        static int RestyleExistingContent()
        {
            try
            {
                using (FlowDocument doc = new FlowDocument())
                {
                    doc.SetDefaultPageSize(612, 792);
                    doc.SetDefaultMargins(72, 72, 72, 72);

                    AddHeading(doc, "Content tree post-processing", 18, 0, 51, 102);

                    for (int i = 0; i < 4; ++i)
                    {
                        Paragraph para = doc.AddParagraph();
                        para.SetSpaceAfter(8);
                        para.AddText("Sentence " + IntToString(i * 2) + ". ");
                        para.AddText("Sentence " + IntToString(i * 2 + 1) + ". ");
                        para.AddText("The style of these runs is decided after the fact.");
                    }

                    Table table = doc.AddTable();
                    table.SetDefaultColumnWidth(150);
                    table.SetDefaultRowHeight(16);

                    for (int rowIndex = 0; rowIndex < 3; ++rowIndex)
                    {
                        TableRow row = table.AddTableRow();
                        for (int colIndex = 0; colIndex < 3; ++colIndex)
                        {
                            TableCell cell = row.AddTableCell();
                            cell.AddParagraph("R" + IntToString(rowIndex) + " / C" + IntToString(colIndex));
                        }
                    }

                    bool highlight = false;
                    RestyleContentTree(doc, ref highlight);

                    Console.WriteLine("Table has " + table.GetNumRows() + " rows and " + table.GetNumColumns() + " columns.");

                    using (PDFDoc pdf = doc.PaginateToPDF())
                    {
                        pdf.Save(output_path + "created_restyled.pdf", SDFDoc.SaveOptions.e_linearized);
                    }
                }
                Console.WriteLine("Saved created_restyled.pdf");
                return 0;
            }
            catch (PDFNetException e)
            {
                Console.WriteLine(e.Message);
                return 1;
            }
        }

        [STAThread]
        static void Main(string[] args)
        {
            int ret = 0;
            PDFNet.Initialize();
            try
            {
                ret |= CreateInvoice();
                ret |= CreateReportWithChart();
                ret |= CreateNewsletter();
                ret |= RestyleExistingContent();
            }
            catch (PDFNetException e)
            {
                Console.WriteLine(e.Message);
                ret = 1;
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                ret = 1;
            }

            PDFNet.Terminate();
            Console.WriteLine("Done.");
            Environment.ExitCode = ret;
        }
    }
}

```

{% endcode %}
{% endtab %}

{% tab title="C++" %}
{% code lineNumbers="true" %}

```cpp
//---------------------------------------------------------------------------------------
// Copyright (c) 2001-2026 by Apryse Software Inc. All Rights Reserved.
// Consult legal.txt regarding legal and license information.
//---------------------------------------------------------------------------------------

#include <PDF/PDFNet.h>
#include <PDF/PDFDoc.h>
#include <SDF/SDFDoc.h>
#include <Common/UString.h>

#include <Layout/FlowDocument.h>
#include <Layout/ContentTree.h>

#include <iostream>
#include <sstream>
#include "../../LicenseKey/CPP/LicenseKey.h"

//---------------------------------------------------------------------------------------
// The following sample illustrates how to use the Layout::FlowDocument API to create
// documents from scratch. Instead of positioning every element on a page, the content is
// described as a tree of paragraphs, tables, lists, shapes, charts and floats, which is
// then paginated into a PDF document.
//
// Each function below produces one document:
//   * an invoice          - tables, cell merging, styling, headers/footers, images
//   * a report            - sections, page setup, lists, charts, page numbers
//   * a newsletter        - shapes, shape text boxes, floats, hyperlinks, tab stops
//   * a content tree walk - post-processing of an existing content tree
//---------------------------------------------------------------------------------------

using namespace std;
using namespace pdftron;
using namespace PDF;
using namespace Layout;

namespace {

	UString input_path("../../TestFiles/");
	UString output_path("../../TestFiles/Output/");

	std::string IntToString(int val)
	{
		std::stringstream ss;
		ss << val;
		return ss.str();
	}

	std::string MoneyToString(double val)
	{
		std::stringstream ss;
		ss.setf(std::ios::fixed);
		ss.precision(2);
		ss << "$" << val;
		return ss.str();
	}

	//-----------------------------------------------------------------------------------
	// Small helpers shared by the samples below.
	//-----------------------------------------------------------------------------------

	// Adds a paragraph with the given text and applies a simple heading style.
	Paragraph AddHeading(ContentContainer container, const UString& text, double font_size,
			UInt8 red, UInt8 green, UInt8 blue)
	{
		Paragraph heading = container.AddParagraph(text);
		TextStyledElement style = heading.GetTextStyledElement();
		style.SetFontSize(font_size);
		style.SetBold(true);
		style.SetTextColor(red, green, blue);
		heading.SetSpaceBefore(12);
		heading.SetSpaceAfter(6);
		return heading;
	}

	// Adds a single table row filled with the given texts.
	TableRow AddTableRowWithText(Table table, const UString texts[], int count,
			bool bold, UInt8 back_red, UInt8 back_green, UInt8 back_blue)
	{
		TableRow row = table.AddTableRow();
		for (int i = 0; i < count; ++i)
		{
			TableCell cell = row.AddTableCell();
			cell.SetBackgroundColor(back_red, back_green, back_blue);
			cell.SetVerticalAlignment(TableCell::e_alignment_center);

			Paragraph para = cell.AddParagraph(texts[i]);
			para.SetStartIndent(3);

			TextStyledElement style = para.GetTextStyledElement();
			style.SetFontSize(10);
			style.SetBold(bold);

			// Right align everything but the first (description) column.
			para.SetJustificationMode(i == 0 ?
				Paragraph::e_text_justify_left : Paragraph::e_text_justify_right);
		}
		return row;
	}

	// Adds a "Page X of Y" footer to the given section, for all page groups.
	void AddPageNumberFooter(Section section, const UString& title)
	{
		Section::PageGroup const groups[] = {
			Section::e_first_page, Section::e_even_pages, Section::e_odd_pages };

		for (int i = 0; i < 3; ++i)
		{
			ContentContainer footer = section.GetOrCreateFooter(groups[i]);

			Paragraph para = footer.AddParagraph();
			para.SetJustificationMode(Paragraph::e_text_justify_center);

			TextStyledElement style = para.GetTextStyledElement();
			style.SetFontSize(9);
			style.SetTextColor(120, 120, 120);
			style.SetItalic(true);

			para.AddText(title + UString("   |   Page "));
			para.AddPageNumber(PageNumber::e_current_page);
			para.AddText(UString(" of "));
			para.AddPageNumber(PageNumber::e_total_pages);
		}
	}
}

//---------------------------------------------------------------------------------------
// 1. An invoice: a header with a logo float, an addresses table, a line item table with
//    merged cells and a totals block.
//---------------------------------------------------------------------------------------
int CreateInvoice()
{
	try
	{
		FlowDocument doc;
		doc.SetDefaultPageSize(612, 792);           // US Letter, in points.
		doc.SetDefaultMargins(54, 54, 54, 54);

		Section section = doc.GetCurrentSection();

		// --- Header -------------------------------------------------------------------
		ContentContainer header = section.GetOrCreateHeader(Section::e_odd_pages);
		Paragraph header_para = header.AddParagraph(UString("SYH Supply Co."));
		header_para.SetJustificationMode(Paragraph::e_text_justify_right);
		header_para.GetTextStyledElement().SetFontSize(9);
		header_para.GetTextStyledElement().SetTextColor(120, 120, 120);

		AddPageNumberFooter(section, UString("Invoice INV-2025-0042"));

		// --- Title, with the company logo floated to the right ------------------------
		Paragraph title = doc.AddParagraph();

		// A float takes its content out of the main flow and positions it relative to a
		// reference point, with the document content wrapped around it.
		Float logo_float = title.AddFloat();
		logo_float.SetHorizontalReferencePoint(Float::e_reference_page);
		logo_float.SetVerticalReferencePoint(Float::e_reference_paragraph);
		logo_float.SetPositionX(400);
		logo_float.SetPositionY(0);
		logo_float.SetObstructionType(Float::e_square);
		logo_float.AddParagraph().AddImage(96, 96, input_path + "logo_red.png");

		title.AddText(UString("INVOICE"));
		TextStyledElement title_style = title.GetTextStyledElement();
		title_style.SetFontSize(28);
		title_style.SetBold(true);
		title_style.SetTextColor(0, 51, 102);

		Paragraph subtitle = doc.AddParagraph(UString(
			"Invoice #INV-2025-0042\nIssued: March 14, 2025\nDue: April 13, 2025"));
		subtitle.GetTextStyledElement().SetFontSize(10);
		subtitle.SetSpaceAfter(18);

		// --- Bill to / ship to --------------------------------------------------------
		Table addresses = doc.AddTable();
		addresses.SetDefaultColumnWidth(240);
		addresses.SetDefaultRowHeight(16);

		TableRow address_row = addresses.AddTableRow();
		UString const address_titles[] = { UString("BILL TO"), UString("SHIP TO") };
		UString const address_bodies[] = {
			UString("Contoso Ltd.\n123 Maple Street\nVancouver, BC V6B 1A1\nCanada"),
			UString("Contoso Warehouse #4\n980 Industrial Way\nBurnaby, BC V5J 3J1\nCanada") };

		for (int i = 0; i < 2; ++i)
		{
			TableCell cell = address_row.AddTableCell();
			cell.SetBackgroundColor(240, 244, 248);
			cell.SetBorder(0.5, 200, 210, 220);

			Paragraph caption = cell.AddParagraph(address_titles[i]);
			caption.SetStartIndent(3);
			TextStyledElement caption_style = caption.GetTextStyledElement();
			caption_style.SetFontSize(9);
			caption_style.SetBold(true);
			caption_style.SetTextColor(0, 51, 102);

			Paragraph body = cell.AddParagraph(address_bodies[i]);
			body.SetStartIndent(3);
			body.GetTextStyledElement().SetFontSize(10);
		}

		doc.AddParagraph(UString(" "));

		// --- Line items ---------------------------------------------------------------
		Table items = doc.AddTable();
		items.SetDefaultColumnWidth(120);
		items.SetDefaultRowHeight(18);
		items.SetBorder(0.75, 0, 51, 102);

		UString const headers[] = { UString("Description"), UString("Qty"),
			UString("Unit price"), UString("Amount") };
		TableRow header_row = AddTableRowWithText(items, headers, 4, true, 0, 51, 102);

		// Make the header row text white by walking the row we have just created.
		for (ContentNodeIterator cell_itr = header_row.GetContentNodeIterator();
				cell_itr.HasNext(); cell_itr.Next())
		{
			if (ElementRef<TableCell> cell = cell_itr.Current().AsTableCell())
			{
				for (ContentNodeIterator para_itr = cell->GetContentNodeIterator();
						para_itr.HasNext(); para_itr.Next())
				{
					if (ElementRef<Paragraph> para = para_itr.Current().AsParagraph())
					{
						para->GetTextStyledElement().SetTextColor(255, 255, 255);
					}
				}
			}
		}

		struct LineItem { char const* desc; int qty; double price; };
		LineItem const line_items[] = {
			{ "Height adjustable desk, oak finish",  2, 4800.00 },
			{ "Ergonomic office chair",              5,  950.00 },
			{ "Meeting room whiteboard, 180 cm",     1, 1200.00 },
			{ "On-site assembly and delivery",       1, 2500.00 },
		};
		int const num_line_items = (int)(sizeof(line_items) / sizeof(line_items[0]));

		double subtotal = 0;
		for (int i = 0; i < num_line_items; ++i)
		{
			double amount = line_items[i].qty * line_items[i].price;
			subtotal += amount;

			UString const cells[] = {
				UString(line_items[i].desc),
				UString(IntToString(line_items[i].qty)),
				UString(MoneyToString(line_items[i].price)),
				UString(MoneyToString(amount)) };

			// Alternating row background for readability.
			bool even = (i % 2) == 0;
			AddTableRowWithText(items, cells, 4, false,
				even ? 255 : 240, even ? 255 : 244, even ? 255 : 248);
		}

		double const tax = subtotal * 0.12;

		struct TotalRow { char const* label; double value; bool bold; };
		TotalRow const totals[] = {
			{ "Subtotal",        subtotal,       false },
			{ "GST/PST (12%)",   tax,            false },
			{ "Total due (CAD)", subtotal + tax, true  },
		};
		int const num_totals = (int)(sizeof(totals) / sizeof(totals[0]));

		for (int i = 0; i < num_totals; ++i)
		{
			UString const cells[] = { UString(""), UString(""),
				UString(totals[i].label), UString(MoneyToString(totals[i].value)) };
			AddTableRowWithText(items, cells, 4, totals[i].bold, 255, 255, 255);

			// Merge the two empty leading cells of the totals row.
			items.GetTableCell(0, num_line_items + 1 + i).MergeCellsRight(1);
		}

		Paragraph notes = doc.AddParagraph(UString(
			"Payment is due within 30 days. Late payments are subject to a 1.5% monthly "
			"interest charge. Please reference the invoice number with your payment."));
		notes.SetSpaceBefore(24);
		notes.GetTextStyledElement().SetFontSize(9);
		notes.GetTextStyledElement().SetItalic(true);
		notes.GetTextStyledElement().SetTextColor(90, 90, 90);

		Paragraph signature = doc.AddParagraph();
		signature.SetSpaceBefore(18);
		signature.AddImage(140, 50, input_path + "signature.jpg");
		signature.AddText(UString("\nAuthorized signature"));

		PDFDoc pdf = doc.PaginateToPDF();
		pdf.Save(output_path + "created_invoice.pdf", SDF::SDFDoc::e_linearized, 0);
		cout << "Saved created_invoice.pdf" << endl;
		return 0;
	}
	catch (Common::Exception& e)
	{
		cout << e << endl;
		return 1;
	}
}

//---------------------------------------------------------------------------------------
// 2. A two page report. The first page (portrait) holds a data table, the second one is
//    a separate landscape section holding a chart.
//---------------------------------------------------------------------------------------
int CreateReportWithChart()
{
	try
	{
		FlowDocument doc;
		doc.SetDefaultPageSize(612, 792);
		doc.SetDefaultMargins(72, 72, 72, 72);

		//--- Page 1: portrait section with the data table ------------------------------
		Section data_section = doc.GetCurrentSection();
		AddPageNumberFooter(data_section, UString("Quarterly Revenue Report"));

		AddHeading(doc, UString("Quarterly Revenue Report 2025"), 22, 0, 51, 102);

		Paragraph intro = doc.AddParagraph(UString(
			"The table below summarizes the revenue by region for the first three quarters "
			"of the fiscal year. All figures are given in thousands of Canadian dollars and "
			"exclude intercompany transactions."));
		intro.GetTextStyledElement().SetFontSize(11);
		intro.SetSpaceAfter(12);

		Table table = doc.AddTable();
		table.SetDefaultColumnWidth(90);
		table.SetDefaultRowHeight(18);
		table.SetBorder(0.75, 120, 120, 120);

		UString const header_cells[] = { UString("Region"), UString("Q1"),
			UString("Q2"), UString("Q3"), UString("Total") };
		AddTableRowWithText(table, header_cells, 5, true, 220, 230, 240);

		struct RegionRow { char const* region; double q1, q2, q3; };
		RegionRow const rows[] = {
			{ "North America", 1250, 1410, 1580 },
			{ "Europe",         910,  980, 1120 },
			{ "Asia Pacific",   640,  720,  905 },
			{ "Latin America",  310,  355,  390 },
		};
		int const num_rows = (int)(sizeof(rows) / sizeof(rows[0]));

		double totals[3] = { 0, 0, 0 };
		for (int i = 0; i < num_rows; ++i)
		{
			totals[0] += rows[i].q1;
			totals[1] += rows[i].q2;
			totals[2] += rows[i].q3;

			double row_total = rows[i].q1 + rows[i].q2 + rows[i].q3;
			UString const cells[] = {
				UString(rows[i].region),
				UString(IntToString((int)rows[i].q1)),
				UString(IntToString((int)rows[i].q2)),
				UString(IntToString((int)rows[i].q3)),
				UString(IntToString((int)row_total)) };
			AddTableRowWithText(table, cells, 5, false, 255, 255, 255);
		}

		UString const total_cells[] = { UString("All regions"),
			UString(IntToString((int)totals[0])),
			UString(IntToString((int)totals[1])),
			UString(IntToString((int)totals[2])),
			UString(IntToString((int)(totals[0] + totals[1] + totals[2]))) };
		AddTableRowWithText(table, total_cells, 5, true, 240, 240, 240);

		AddHeading(doc, UString("Key observations"), 14, 0, 51, 102);

		List observations = doc.AddList();
		observations.SetNumberFormat(List::e_decimal, UString("."), true);
		//observations.GetLabelStyle().SetFontFace(UString("Comic Sans MS"));
		observations.GetLabelStyle().SetBold(true);
		observations.AddItem().AddParagraph(UString(
			"Revenue grew in every region, with Asia Pacific showing the strongest "
			"quarter over quarter growth."));
		observations.AddItem().AddParagraph(UString(
			"North America remains the largest market, contributing roughly 40% of "
			"the total revenue."));

		ListItem risks = observations.AddItem();
		risks.AddParagraph(UString("Risks that may affect the Q4 forecast:"));
		List risk_list = risks.AddList();
		risk_list.SetNumberFormat(List::e_lower_letter, UString(")"), true);
		risk_list.AddItem().AddParagraph(UString("Currency fluctuations in Latin America."));
		risk_list.AddItem().AddParagraph(UString("Longer sales cycles in the enterprise segment."));

		//--- Page 2: landscape section with the chart ----------------------------------
		Section chart_section = doc.AddSection();
		chart_section.SetPageSize(792, 612);        // landscape
		chart_section.SetMargins(54, 54, 54, 54);
		AddPageNumberFooter(chart_section, UString("Quarterly Revenue Report"));

		AddHeading(chart_section, UString("Revenue by region and quarter"), 18, 0, 51, 102);

		// A chart is described by a JSON definition string.
		UString const chart_definition(
			"{"
			"  \"chartType\": \"bar\","
			"  \"title\": \"Revenue by region (thousands CAD)\","
			"  \"theme\": \"vivid\","
			"  \"data\": {"
			"    \"categories\": { \"data\": [\"Q1\", \"Q2\", \"Q3\"] },"
			"    \"series\": ["
			"      { \"name\": { \"data\": [\"North America\"] }, \"data\": { \"data\": [1250, 1410, 1580] } },"
			"      { \"name\": { \"data\": [\"Europe\"] }, \"data\": { \"data\": [910, 980, 1120] } },"
			"      { \"name\": { \"data\": [\"Asia Pacific\"] }, \"data\": { \"data\": [640, 720, 905] } },"
			"      { \"name\": { \"data\": [\"Latin America\"] }, \"data\": { \"data\": [310, 355, 390] } }"
			"    ]"
			"  }"
			"}");

		Paragraph chart_para = chart_section.AddParagraph();
		chart_para.SetJustificationMode(Paragraph::e_text_justify_center);
		Chart chart = chart_para.AddChart(560, 340, chart_definition);
		chart.SetWidth(600);
		chart.SetHeight(360);

		Paragraph caption = chart_section.AddParagraph(UString(
			"Figure 1: revenue distribution across the four sales regions."));
		caption.SetJustificationMode(Paragraph::e_text_justify_center);
		caption.GetTextStyledElement().SetItalic(true);
		caption.GetTextStyledElement().SetFontSize(10);

		cout << "Report sections: " << doc.GetNumSections()
			<< ", second section page width: "
			<< doc.GetSection(1).GetPageWidth() << endl;

		PDFDoc pdf = doc.PaginateToPDF();
		pdf.Save(output_path + "created_report.pdf", SDF::SDFDoc::e_linearized, 0);
		cout << "Saved created_report.pdf" << endl;
		return 0;
	}
	catch (Common::Exception& e)
	{
		cout << e << endl;
		return 1;
	}
}

//---------------------------------------------------------------------------------------
// 3. A newsletter demonstrating shapes, shape text boxes, floats, hyperlinks and
//    paragraph styling such as indents, tab stops and justification.
//---------------------------------------------------------------------------------------
int CreateNewsletter()
{
	try
	{
		std::string const body_text(
			"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod "
			"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, "
			"quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo "
			"consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse "
			"cillum dolore eu fugiat nulla pariatur.");

		FlowDocument doc;
		doc.SetDefaultPageSize(612, 792);
		doc.SetDefaultMargins(60, 60, 60, 60);

		Section section = doc.GetCurrentSection();
		AddPageNumberFooter(section, UString("The Monthly Dispatch"));

		// Masthead built from a rounded rectangle shape with a text box inside.
		Paragraph masthead_para = doc.AddParagraph();
		Shape masthead = masthead_para.AddShape(Shape::e_rectangle_rounded_corners, 492, 70);
		masthead.SetCornerRadius(10);
		masthead.SetBackgroundColor(0, 51, 102);
		masthead.SetOutlineColor(0, 31, 62);
		masthead.SetOutlineThickness(1.5);

		ContentContainer masthead_text = masthead.GetTextBox();
		Paragraph masthead_title = masthead_text.AddParagraph(UString("The Monthly Dispatch"));
		masthead_title.SetJustificationMode(Paragraph::e_text_justify_center);
		TextStyledElement masthead_style = masthead_title.GetTextStyledElement();
		masthead_style.SetFontSize(24);
		masthead_style.SetBold(true);
		masthead_style.SetTextColor(255, 255, 255);

		Paragraph masthead_sub = masthead_text.AddParagraph(UString("Issue 42 - March 2025"));
		masthead_sub.SetJustificationMode(Paragraph::e_text_justify_center);
		masthead_sub.GetTextStyledElement().SetFontSize(10);
		masthead_sub.GetTextStyledElement().SetTextColor(200, 215, 230);

		// Lead story with a pull quote floated next to it.
		AddHeading(doc, UString("Documents, assembled"), 16, 0, 51, 102);

		Paragraph lead = doc.AddParagraph();
		lead.SetJustificationMode(Paragraph::e_text_justify_left);
		lead.SetTextIndent(18);
		lead.SetSpaceAfter(10);

		Float pull_quote = lead.AddFloat();
		pull_quote.SetHorizontalReferencePoint(Float::e_reference_page);
		pull_quote.SetVerticalReferencePoint(Float::e_reference_paragraph);
		pull_quote.SetPositionX(380);
		pull_quote.SetPositionY(10);
		pull_quote.SetObstructionType(Float::e_square);

		Paragraph quote_holder = pull_quote.AddParagraph();
		Shape quote_box = quote_holder.AddShape(Shape::e_rectangle_rounded_corners, 190, 70);
		quote_box.SetCornerRadius(6);
		quote_box.SetBackgroundColor(240, 246, 255);
		quote_box.SetOutlineColor(0, 51, 102);
		quote_box.SetOutlineThickness(1.0);

		ContentContainer quote_text = quote_box.GetTextBox();
		Paragraph quote = quote_text.AddParagraph(UString(
			"\"The content tree lets you describe a document, not a page.\""));
		quote.SetStartIndent(6);
		quote.SetEndIndent(6);
		TextStyledElement quote_style = quote.GetTextStyledElement();
		quote_style.SetFontSize(12);
		quote_style.SetItalic(true);
		quote_style.SetTextColor(0, 51, 102);

		lead.AddText(UString(body_text));
		lead.AddText(UString(" Read more about the API at "));
		TextRun link = lead.AddHyperlink(UString("apryse.com"), UString("https://www.apryse.com"));
		link.GetTextStyledElement().SetTextColor(0, 102, 204);
		lead.AddText(UString("."));

		// Two "columns" of text, created with complementary indents.
		Paragraph column_left = doc.AddParagraph(UString(body_text));
		column_left.SetStartIndent(0);
		column_left.SetEndIndent(260);
		column_left.GetTextStyledElement().SetFontSize(10);

		Paragraph column_right = doc.AddParagraph(UString(body_text));
		column_right.SetStartIndent(260);
		column_right.SetEndIndent(0);
		column_right.GetTextStyledElement().SetFontSize(10);

		// A schedule built with tab stops instead of a table.
		AddHeading(doc, UString("Upcoming webinars"), 14, 0, 51, 102);

		struct Webinar { char const* date; char const* topic; char const* host; };
		Webinar const schedule[] = {
			{ "Apr 02", "Building documents from scratch", "R. Fischer" },
			{ "Apr 16", "Charts and data visualization",   "M. Okafor" },
			{ "Apr 30", "Accessible PDF output",           "L. Tanaka" },
		};
		int const num_webinars = (int)(sizeof(schedule) / sizeof(schedule[0]));

		for (int i = 0; i < num_webinars; ++i)
		{
			Paragraph row = doc.AddParagraph();
			row.AddTabStop(80);
			row.AddTabStop(360);
			row.GetTextStyledElement().SetFontSize(10);
			row.AddText(UString(schedule[i].date) + UString("\t") +
				UString(schedule[i].topic) + UString("\t") + UString(schedule[i].host));
		}

		// A decorative arrow and a photo, both anchored in a single paragraph.
		Paragraph gallery = doc.AddParagraph();
		gallery.SetSpaceBefore(16);
		gallery.SetJustificationMode(Paragraph::e_text_justify_center);

		Shape arrow = gallery.AddShape(Shape::e_arrow, 90, 40);
		arrow.SetBackgroundColor(0, 153, 102);
		arrow.SetOutlineColor(0, 102, 68);
		arrow.SetOutlineThickness(1);
		arrow.SetArrowHeadSize(18);

		gallery.AddText(UString("   "));
		gallery.AddImage(160, 120, input_path + "butterfly.png");

		PDFDoc pdf = doc.PaginateToPDF();
		pdf.Save(output_path + "created_newsletter.pdf", SDF::SDFDoc::e_linearized, 0);
		cout << "Saved created_newsletter.pdf" << endl;
		return 0;
	}
	catch (Common::Exception& e)
	{
		cout << e << endl;
		return 1;
	}
}

//---------------------------------------------------------------------------------------
// 4. Post-processing: build a simple document and then walk its content tree, restyling
//    the elements that are found along the way.
//---------------------------------------------------------------------------------------
namespace {

	// Recursively walks the content tree, highlighting every second text run and
	// outlining the cells of any table that is encountered.
	void RestyleContentTree(ContentNode node, bool& highlight)
	{
		for (ContentNodeIterator itr = node.GetContentNodeIterator();
				itr.HasNext(); itr.Next())
		{
			ContentElement el = itr.Current();

			if (ElementRef<TextRun> text_run = el.AsTextRun())
			{
				if (highlight)
				{
					TextStyledElement style = text_run->GetTextStyledElement();
					style.SetBold(true);
					style.SetBackgroundColor(255, 245, 180);
				}
				highlight = !highlight;
				continue;
			}

			if (ElementRef<TableCell> cell = el.AsTableCell())
			{
				cell->SetBorder(0.5, 150, 150, 150);
			}

			if (ElementRef<ContentNode> child = el.AsContentNode())
			{
				RestyleContentTree(*child, highlight);
			}
		}
	}
}

int RestyleExistingContent()
{
	try
	{
		FlowDocument doc;
		doc.SetDefaultPageSize(612, 792);
		doc.SetDefaultMargins(72, 72, 72, 72);

		AddHeading(doc, UString("Content tree post-processing"), 18, 0, 51, 102);

		for (int i = 0; i < 4; ++i)
		{
			Paragraph para = doc.AddParagraph();
			para.SetSpaceAfter(8);
			para.AddText(UString("Sentence " + IntToString(i * 2) + ". "));
			para.AddText(UString("Sentence " + IntToString(i * 2 + 1) + ". "));
			para.AddText(UString("The style of these runs is decided after the fact."));
		}

		Table table = doc.AddTable();
		table.SetDefaultColumnWidth(150);
		table.SetDefaultRowHeight(16);

		for (int row_index = 0; row_index < 3; ++row_index)
		{
			TableRow row = table.AddTableRow();
			for (int col_index = 0; col_index < 3; ++col_index)
			{
				TableCell cell = row.AddTableCell();
				cell.AddParagraph(UString("R" + IntToString(row_index) +
					" / C" + IntToString(col_index)));
			}
		}

		bool highlight = false;
		RestyleContentTree(doc, highlight);

		cout << "Table has " << table.GetNumRows() << " rows and "
			<< table.GetNumColumns() << " columns." << endl;

		PDFDoc pdf = doc.PaginateToPDF();
		pdf.Save(output_path + "created_restyled.pdf", SDF::SDFDoc::e_linearized, 0);
		cout << "Saved created_restyled.pdf" << endl;
		return 0;
	}
	catch (Common::Exception& e)
	{
		cout << e << endl;
		return 1;
	}
}

int main(int argc, char* argv[])
{
	int ret = 0;

	PDFNet::Initialize(LicenseKey);

	try
	{
		ret |= CreateInvoice();
		ret |= CreateReportWithChart();
		ret |= CreateNewsletter();
		ret |= RestyleExistingContent();
	}
	catch (Common::Exception& e)
	{
		cout << e << endl;
		ret = 1;
	}
	catch (...)
	{
		cout << "Unknown Exception" << endl;
		ret = 1;
	}

	PDFNet::Terminate();
	cout << "Done." << endl;
	return ret;
}

```

{% endcode %}
{% endtab %}

{% tab title="Java" %}
{% code lineNumbers="true" %}

```java
// Copyright (c) 2001-2026 by Apryse Software Inc. All Rights Reserved.
// Consult legal.txt regarding legal and license information.
//---------------------------------------------------------------------------------------

import com.pdftron.common.PDFNetException;
import com.pdftron.pdf.PDFDoc;
import com.pdftron.pdf.PDFNet;
import com.pdftron.sdf.SDFDoc;
import com.pdftron.layout.Chart;
import com.pdftron.layout.ContentContainer;
import com.pdftron.layout.ContentElement;
import com.pdftron.layout.ContentNode;
import com.pdftron.layout.ContentNodeIterator;
import com.pdftron.layout.FlowDocument;
import com.pdftron.layout.List;
import com.pdftron.layout.ListItem;
import com.pdftron.layout.PageNumber;
import com.pdftron.layout.Paragraph;
import com.pdftron.layout.Section;
import com.pdftron.layout.Shape;
import com.pdftron.layout.Table;
import com.pdftron.layout.TableCell;
import com.pdftron.layout.TableRow;
import com.pdftron.layout.TextRun;
import com.pdftron.layout.TextStyledElement;

//---------------------------------------------------------------------------------------
// The following sample illustrates how to use the com.pdftron.layout.FlowDocument API to
// create documents from scratch. Instead of positioning every element on a page, the
// content is described as a tree of paragraphs, tables, lists, shapes, charts and floats,
// which is then paginated into a PDF document.
//
// Each function below produces one document:
//   * an invoice          - tables, cell merging, styling, headers/footers, images
//   * a report            - sections, page setup, lists, charts, page numbers
//   * a newsletter        - shapes, shape text boxes, floats, hyperlinks, tab stops
//   * a content tree walk - post-processing of an existing content tree
//---------------------------------------------------------------------------------------
public class DocumentCreationTest {

    static final String input_path = "../../TestFiles/";
    static final String output_path = "../../TestFiles/Output/";

    static String moneyToString(double val) {
        return String.format("$%.2f", val);
    }

    //-----------------------------------------------------------------------------------
    // Small helpers shared by the samples below.
    //-----------------------------------------------------------------------------------

    // Adds a paragraph with the given text and applies a simple heading style.
    static Paragraph addHeading(ContentContainer container, String text, double font_size,
            int red, int green, int blue) throws PDFNetException {
        Paragraph heading = container.addParagraph(text);
        TextStyledElement style = heading.getTextStyledElement();
        style.setFontSize(font_size);
        style.setBold(true);
        style.setTextColor(red, green, blue);
        heading.setSpaceBefore(12);
        heading.setSpaceAfter(6);
        return heading;
    }

    // Adds a single table row filled with the given texts.
    static TableRow addTableRowWithText(Table table, String[] texts, boolean bold,
            int back_red, int back_green, int back_blue) throws PDFNetException {
        TableRow row = table.addTableRow();
        for (int i = 0; i < texts.length; ++i) {
            TableCell cell = row.addTableCell();
            cell.setBackgroundColor(back_red, back_green, back_blue);
            cell.setVerticalAlignment(TableCell.CellAlignmentVerticalVals.e_alignment_center);

            Paragraph para = cell.addParagraph(texts[i]);
            para.setStartIndent(3);
            TextStyledElement style = para.getTextStyledElement();
            style.setFontSize(10);
            style.setBold(bold);

            // Right align everything but the first (description) column.
            para.setJustificationMode(i == 0
                ? Paragraph.TextJustification.e_text_justify_left
                : Paragraph.TextJustification.e_text_justify_right);
        }
        return row;
    }

    // Adds a "Page X of Y" footer to the given section, for all page groups.
    static void addPageNumberFooter(Section section, String title) throws PDFNetException {
        Section.PageGroup[] groups = {
            Section.PageGroup.e_first_page,
            Section.PageGroup.e_even_pages,
            Section.PageGroup.e_odd_pages };

        for (Section.PageGroup group : groups) {
            ContentContainer footer = section.getOrCreateFooter(group);

            Paragraph para = footer.addParagraph();
            para.setJustificationMode(Paragraph.TextJustification.e_text_justify_center);

            TextStyledElement style = para.getTextStyledElement();
            style.setFontSize(9);
            style.setTextColor(120, 120, 120);
            style.setItalic(true);

            para.addText(title + "   |   Page ");
            para.addPageNumber(PageNumber.NumberType.e_current_page);
            para.addText(" of ");
            para.addPageNumber(PageNumber.NumberType.e_total_pages);
        }
    }

    //-----------------------------------------------------------------------------------
    // 1. An invoice: a header with a logo float, an addresses table, a line item table
    //    with merged cells and a totals block.
    //-----------------------------------------------------------------------------------
    static int createInvoice() {
        try {
            FlowDocument doc = new FlowDocument();
            doc.setDefaultPageSize(612, 792);          // US Letter, in points.
            doc.setDefaultMargins(54, 54, 54, 54);

            Section section = doc.getCurrentSection();

            // --- Header ---------------------------------------------------------------
            ContentContainer header = section.getOrCreateHeader(Section.PageGroup.e_odd_pages);
            Paragraph header_para = header.addParagraph("SYH Supply Co.");
            header_para.setJustificationMode(Paragraph.TextJustification.e_text_justify_right);
            header_para.getTextStyledElement().setFontSize(9);
            header_para.getTextStyledElement().setTextColor(120, 120, 120);

            addPageNumberFooter(section, "Invoice INV-2025-0042");

            // --- Title, with the company logo floated to the right --------------------
            Paragraph title = doc.addParagraph();

            // A float takes its content out of the main flow and positions it relative to
            // a reference point, with the document content wrapped around it.
            com.pdftron.layout.Float logo_float = title.addFloat();
            logo_float.setHorizontalReferencePoint(
                com.pdftron.layout.Float.ReferencePoint.e_reference_page);
            logo_float.setVerticalReferencePoint(
                com.pdftron.layout.Float.ReferencePoint.e_reference_paragraph);
            logo_float.setPositionX(400);
            logo_float.setPositionY(0);
            logo_float.setObstructionType(
                com.pdftron.layout.Float.ObstructionType.e_square);
            logo_float.addParagraph().addImage(96, 96, input_path + "logo_red.png");

            title.addText("INVOICE");
            TextStyledElement title_style = title.getTextStyledElement();
            title_style.setFontSize(28);
            title_style.setBold(true);
            title_style.setTextColor(0, 51, 102);

            Paragraph subtitle = doc.addParagraph(
                "Invoice #INV-2025-0042\nIssued: March 14, 2025\nDue: April 13, 2025");
            subtitle.getTextStyledElement().setFontSize(10);
            subtitle.setSpaceAfter(18);

            // --- Bill to / ship to ----------------------------------------------------
            Table addresses = doc.addTable();
            addresses.setDefaultColumnWidth(240);
            addresses.setDefaultRowHeight(16);

            TableRow address_row = addresses.addTableRow();
            String[] address_titles = { "BILL TO", "SHIP TO" };
            String[] address_bodies = {
                "Contoso Ltd.\n123 Maple Street\nVancouver, BC V6B 1A1\nCanada",
                "Contoso Warehouse #4\n980 Industrial Way\nBurnaby, BC V5J 3J1\nCanada" };

            for (int i = 0; i < 2; ++i) {
                TableCell cell = address_row.addTableCell();
                cell.setBackgroundColor(240, 244, 248);
                cell.setBorder(0.5, 200, 210, 220);

                Paragraph caption = cell.addParagraph(address_titles[i]);
                caption.setStartIndent(3);
                TextStyledElement caption_style = caption.getTextStyledElement();
                caption_style.setFontSize(9);
                caption_style.setBold(true);
                caption_style.setTextColor(0, 51, 102);

                Paragraph body = cell.addParagraph(address_bodies[i]);
                body.setStartIndent(3);
                body.getTextStyledElement().setFontSize(10);
            }

            doc.addParagraph(" ");

            // --- Line items -----------------------------------------------------------
            Table items = doc.addTable();
            items.setDefaultColumnWidth(120);
            items.setDefaultRowHeight(18);
            items.setBorder(0.75, 0, 51, 102);

            String[] headers = { "Description", "Qty", "Unit price", "Amount" };
            TableRow header_row = addTableRowWithText(items, headers, true, 0, 51, 102);

            // Make the header row text white by walking the row we have just created.
            for (ContentNodeIterator cell_itr = header_row.getContentNodeIterator();
                    cell_itr.hasNext();) {
                TableCell cell = cell_itr.next().asTableCell();
                if (cell != null) {
                    for (ContentNodeIterator para_itr = cell.getContentNodeIterator();
                            para_itr.hasNext();) {
                        Paragraph para = para_itr.next().asParagraph();
                        if (para != null) {
                            para.getTextStyledElement().setTextColor(255, 255, 255);
                        }
                    }
                }
            }

            String[] line_descriptions = {
                "Height adjustable desk, oak finish",
                "Ergonomic office chair",
                "Meeting room whiteboard, 180 cm",
                "On-site assembly and delivery" };
            int[] line_quantities = { 2, 5, 1, 1 };
            double[] line_prices = { 4800.00, 950.00, 1200.00, 2500.00 };
            int num_line_items = line_descriptions.length;

            double subtotal = 0;
            for (int i = 0; i < num_line_items; ++i) {
                double amount = line_quantities[i] * line_prices[i];
                subtotal += amount;

                String[] cells = {
                    line_descriptions[i],
                    Integer.toString(line_quantities[i]),
                    moneyToString(line_prices[i]),
                    moneyToString(amount) };

                // Alternating row background for readability.
                boolean even = (i % 2) == 0;
                addTableRowWithText(items, cells, false,
                    even ? 255 : 240, even ? 255 : 244, even ? 255 : 248);
            }

            double tax = subtotal * 0.12;

            String[] total_labels = { "Subtotal", "GST/PST (12%)", "Total due (CAD)" };
            double[] total_values = { subtotal, tax, subtotal + tax };
            boolean[] total_bold = { false, false, true };

            for (int i = 0; i < total_labels.length; ++i) {
                String[] cells = { "", "", total_labels[i], moneyToString(total_values[i]) };
                addTableRowWithText(items, cells, total_bold[i], 255, 255, 255);

                // Merge the two empty leading cells of the totals row.
                items.getTableCell(0, num_line_items + 1 + i).mergeCellsRight(1);
            }

            Paragraph notes = doc.addParagraph(
                "Payment is due within 30 days. Late payments are subject to a 1.5% monthly "
                + "interest charge. Please reference the invoice number with your payment.");
            notes.setSpaceBefore(24);
            notes.getTextStyledElement().setFontSize(9);
            notes.getTextStyledElement().setItalic(true);
            notes.getTextStyledElement().setTextColor(90, 90, 90);

            Paragraph signature = doc.addParagraph();
            signature.setSpaceBefore(18);
            signature.addImage(140, 50, input_path + "signature.jpg");
            signature.addText("\nAuthorized signature");

            PDFDoc pdf = doc.paginateToPDF();
            pdf.save(output_path + "created_invoice.pdf", SDFDoc.SaveMode.LINEARIZED, null);
            pdf.close();
            doc.destroy();
            System.out.println("Saved created_invoice.pdf");
            return 0;
        } catch (Exception e) {
            System.out.println(e);
            return 1;
        }
    }

    //-----------------------------------------------------------------------------------
    // 2. A two page report. The first page (portrait) holds a data table, the second one
    //    is a separate landscape section holding a chart.
    //-----------------------------------------------------------------------------------
    static int createReportWithChart() {
        try {
            FlowDocument doc = new FlowDocument();
            doc.setDefaultPageSize(612, 792);
            doc.setDefaultMargins(72, 72, 72, 72);

            //--- Page 1: portrait section with the data table --------------------------
            Section data_section = doc.getCurrentSection();
            addPageNumberFooter(data_section, "Quarterly Revenue Report");

            addHeading(doc, "Quarterly Revenue Report 2025", 22, 0, 51, 102);

            Paragraph intro = doc.addParagraph(
                "The table below summarizes the revenue by region for the first three quarters "
                + "of the fiscal year. All figures are given in thousands of Canadian dollars "
                + "and exclude intercompany transactions.");
            intro.getTextStyledElement().setFontSize(11);
            intro.setSpaceAfter(12);

            Table table = doc.addTable();
            table.setDefaultColumnWidth(90);
            table.setDefaultRowHeight(18);
            table.setBorder(0.75, 120, 120, 120);

            String[] header_cells = { "Region", "Q1", "Q2", "Q3", "Total" };
            addTableRowWithText(table, header_cells, true, 220, 230, 240);

            String[] region_names = { "North America", "Europe", "Asia Pacific", "Latin America" };
            double[][] region_values = {
                { 1250, 1410, 1580 },
                {  910,  980, 1120 },
                {  640,  720,  905 },
                {  310,  355,  390 } };

            double[] totals = { 0, 0, 0 };
            for (int i = 0; i < region_names.length; ++i) {
                totals[0] += region_values[i][0];
                totals[1] += region_values[i][1];
                totals[2] += region_values[i][2];

                double row_total = region_values[i][0] + region_values[i][1] + region_values[i][2];
                String[] cells = {
                    region_names[i],
                    Integer.toString((int) region_values[i][0]),
                    Integer.toString((int) region_values[i][1]),
                    Integer.toString((int) region_values[i][2]),
                    Integer.toString((int) row_total) };
                addTableRowWithText(table, cells, false, 255, 255, 255);
            }

            String[] total_cells = { "All regions",
                Integer.toString((int) totals[0]),
                Integer.toString((int) totals[1]),
                Integer.toString((int) totals[2]),
                Integer.toString((int) (totals[0] + totals[1] + totals[2])) };
            addTableRowWithText(table, total_cells, true, 240, 240, 240);

            addHeading(doc, "Key observations", 14, 0, 51, 102);

            List observations = doc.addList();
            observations.setNumberFormat(List.ListItemNumberFormat.e_decimal, ".", true);
            observations.getLabelStyle().setBold(true);
            observations.addItem().addParagraph(
                "Revenue grew in every region, with Asia Pacific showing the strongest "
                + "quarter over quarter growth.");
            observations.addItem().addParagraph(
                "North America remains the largest market, contributing roughly 40% of "
                + "the total revenue.");

            ListItem risks = observations.addItem();
            risks.addParagraph("Risks that may affect the Q4 forecast:");
            List risk_list = risks.addList();
            risk_list.setNumberFormat(List.ListItemNumberFormat.e_lower_letter, ")", true);
            risk_list.addItem().addParagraph("Currency fluctuations in Latin America.");
            risk_list.addItem().addParagraph("Longer sales cycles in the enterprise segment.");

            //--- Page 2: landscape section with the chart ------------------------------
            Section chart_section = doc.addSection();
            chart_section.setPageSize(792, 612);        // landscape
            chart_section.setMargins(54, 54, 54, 54);
            addPageNumberFooter(chart_section, "Quarterly Revenue Report");

            addHeading(doc, "Revenue by region and quarter", 18, 0, 51, 102);

            // A chart is described by a JSON definition string.
            String chart_definition =
                "{"
                + "  \"chartType\": \"bar\","
                + "  \"title\": \"Revenue by region (thousands CAD)\","
                + "  \"theme\": \"vivid\","
                + "  \"data\": {"
                + "    \"categories\": { \"data\": [\"Q1\", \"Q2\", \"Q3\"] },"
                + "    \"series\": ["
                + "      { \"name\": { \"data\": [\"North America\"] }, \"data\": { \"data\": [1250, 1410, 1580] } },"
                + "      { \"name\": { \"data\": [\"Europe\"] }, \"data\": { \"data\": [910, 980, 1120] } },"
                + "      { \"name\": { \"data\": [\"Asia Pacific\"] }, \"data\": { \"data\": [640, 720, 905] } },"
                + "      { \"name\": { \"data\": [\"Latin America\"] }, \"data\": { \"data\": [310, 355, 390] } }"
                + "    ]"
                + "  }"
                + "}";

            Paragraph chart_para = doc.addParagraph();
            chart_para.setJustificationMode(Paragraph.TextJustification.e_text_justify_center);
            Chart chart = chart_para.addChart(560, 340, chart_definition);
            chart.setWidth(600);
            chart.setHeight(360);

            Paragraph caption = doc.addParagraph(
                "Figure 1: revenue distribution across the four sales regions.");
            caption.setJustificationMode(Paragraph.TextJustification.e_text_justify_center);
            caption.getTextStyledElement().setItalic(true);
            caption.getTextStyledElement().setFontSize(10);

            System.out.println("Report sections: " + doc.getNumSections()
                + ", second section page width: " + doc.getSection(1).getPageWidth());

            PDFDoc pdf = doc.paginateToPDF();
            pdf.save(output_path + "created_report.pdf", SDFDoc.SaveMode.LINEARIZED, null);
            pdf.close();
            doc.destroy();
            System.out.println("Saved created_report.pdf");
            return 0;
        } catch (Exception e) {
            System.out.println(e);
            return 1;
        }
    }

    //-----------------------------------------------------------------------------------
    // 3. A newsletter demonstrating shapes, shape text boxes, floats, hyperlinks and
    //    paragraph styling such as indents, tab stops and justification.
    //-----------------------------------------------------------------------------------
    static int createNewsletter() {
        try {
            String body_text =
                "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod "
                + "tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, "
                + "quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo "
                + "consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse "
                + "cillum dolore eu fugiat nulla pariatur.";

            FlowDocument doc = new FlowDocument();
            doc.setDefaultPageSize(612, 792);
            doc.setDefaultMargins(60, 60, 60, 60);

            Section section = doc.getCurrentSection();
            addPageNumberFooter(section, "The Monthly Dispatch");

            // Masthead built from a rounded rectangle shape with a text box inside.
            Paragraph masthead_para = doc.addParagraph();
            Shape masthead = masthead_para.addShape(
                Shape.ShapeType.e_rectangle_rounded_corners, 492, 70);
            masthead.setCornerRadius(10);
            masthead.setBackgroundColor(0, 51, 102);
            masthead.setOutlineColor(0, 31, 62);
            masthead.setOutlineThickness(1.5);

            ContentContainer masthead_text = masthead.getTextBox();
            Paragraph masthead_title = masthead_text.addParagraph("The Monthly Dispatch");
            masthead_title.setJustificationMode(Paragraph.TextJustification.e_text_justify_center);
            TextStyledElement masthead_style = masthead_title.getTextStyledElement();
            masthead_style.setFontSize(24);
            masthead_style.setBold(true);
            masthead_style.setTextColor(255, 255, 255);

            Paragraph masthead_sub = masthead_text.addParagraph("Issue 42 - March 2025");
            masthead_sub.setJustificationMode(Paragraph.TextJustification.e_text_justify_center);
            masthead_sub.getTextStyledElement().setFontSize(10);
            masthead_sub.getTextStyledElement().setTextColor(200, 215, 230);

            // Lead story with a pull quote floated next to it.
            addHeading(doc, "Documents, assembled", 16, 0, 51, 102);

            Paragraph lead = doc.addParagraph();
            lead.setJustificationMode(Paragraph.TextJustification.e_text_justify_left);
            lead.setTextIndent(18);
            lead.setSpaceAfter(10);

            com.pdftron.layout.Float pull_quote = lead.addFloat();
            pull_quote.setHorizontalReferencePoint(
                com.pdftron.layout.Float.ReferencePoint.e_reference_page);
            pull_quote.setVerticalReferencePoint(
                com.pdftron.layout.Float.ReferencePoint.e_reference_paragraph);
            pull_quote.setPositionX(380);
            pull_quote.setPositionY(10);
            pull_quote.setObstructionType(
                com.pdftron.layout.Float.ObstructionType.e_square);

            Paragraph quote_holder = pull_quote.addParagraph();
            Shape quote_box = quote_holder.addShape(
                Shape.ShapeType.e_rectangle_rounded_corners, 190, 70);
            quote_box.setCornerRadius(6);
            quote_box.setBackgroundColor(240, 246, 255);
            quote_box.setOutlineColor(0, 51, 102);
            quote_box.setOutlineThickness(1.0);

            ContentContainer quote_text = quote_box.getTextBox();
            Paragraph quote = quote_text.addParagraph(
                "\"The content tree lets you describe a document, not a page.\"");
            quote.setStartIndent(6);
            quote.setEndIndent(6);
            TextStyledElement quote_style = quote.getTextStyledElement();
            quote_style.setFontSize(12);
            quote_style.setItalic(true);
            quote_style.setTextColor(0, 51, 102);

            lead.addText(body_text);
            lead.addText(" Read more about the API at ");
            TextRun link = lead.addHyperlink("apryse.com", "https://www.apryse.com");
            link.getTextStyledElement().setTextColor(0, 102, 204);
            lead.addText(".");

            // Two "columns" of text, created with complementary indents.
            Paragraph column_left = doc.addParagraph(body_text);
            column_left.setStartIndent(0);
            column_left.setEndIndent(260);
            column_left.getTextStyledElement().setFontSize(10);

            Paragraph column_right = doc.addParagraph(body_text);
            column_right.setStartIndent(260);
            column_right.setEndIndent(0);
            column_right.getTextStyledElement().setFontSize(10);

            // A schedule built with tab stops instead of a table.
            addHeading(doc, "Upcoming webinars", 14, 0, 51, 102);

            String[][] schedule = {
                { "Apr 02", "Building documents from scratch", "R. Fischer" },
                { "Apr 16", "Charts and data visualization",   "M. Okafor" },
                { "Apr 30", "Accessible PDF output",           "L. Tanaka" } };

            for (String[] webinar : schedule) {
                Paragraph row = doc.addParagraph();
                row.addTabStop(80);
                row.addTabStop(360);
                row.getTextStyledElement().setFontSize(10);
                row.addText(webinar[0] + "\t" + webinar[1] + "\t" + webinar[2]);
            }

            // A decorative arrow and a photo, both anchored in a single paragraph.
            Paragraph gallery = doc.addParagraph();
            gallery.setSpaceBefore(16);
            gallery.setJustificationMode(Paragraph.TextJustification.e_text_justify_center);

            Shape arrow = gallery.addShape(Shape.ShapeType.e_arrow, 90, 40);
            arrow.setBackgroundColor(0, 153, 102);
            arrow.setOutlineColor(0, 102, 68);
            arrow.setOutlineThickness(1);
            arrow.setArrowHeadSize(18);

            gallery.addText("   ");
            gallery.addImage(160, 120, input_path + "butterfly.png");

            PDFDoc pdf = doc.paginateToPDF();
            pdf.save(output_path + "created_newsletter.pdf", SDFDoc.SaveMode.LINEARIZED, null);
            pdf.close();
            doc.destroy();
            System.out.println("Saved created_newsletter.pdf");
            return 0;
        } catch (Exception e) {
            System.out.println(e);
            return 1;
        }
    }

    //-----------------------------------------------------------------------------------
    // 4. Post-processing: build a simple document and then walk its content tree,
    //    restyling the elements that are found along the way.
    //-----------------------------------------------------------------------------------

    // Recursively walks the content tree, highlighting every second text run and
    // outlining the cells of any table that is encountered.
    static boolean restyleContentTree(ContentNode node, boolean highlight)
            throws PDFNetException {
        for (ContentNodeIterator itr = node.getContentNodeIterator(); itr.hasNext();) {
            ContentElement el = itr.next();

            TextRun text_run = el.asTextRun();
            if (text_run != null) {
                if (highlight) {
                    TextStyledElement style = text_run.getTextStyledElement();
                    style.setBold(true);
                    style.setBackgroundColor(255, 245, 180);
                }
                highlight = !highlight;
                continue;
            }

            TableCell cell = el.asTableCell();
            if (cell != null) {
                cell.setBorder(0.5, 150, 150, 150);
            }

            ContentNode child = el.asContentNode();
            if (child != null) {
                highlight = restyleContentTree(child, highlight);
            }
        }
        return highlight;
    }

    static int restyleExistingContent() {
        try {
            FlowDocument doc = new FlowDocument();
            doc.setDefaultPageSize(612, 792);
            doc.setDefaultMargins(72, 72, 72, 72);

            addHeading(doc, "Content tree post-processing", 18, 0, 51, 102);

            for (int i = 0; i < 4; ++i) {
                Paragraph para = doc.addParagraph();
                para.setSpaceAfter(8);
                para.addText("Sentence " + (i * 2) + ". ");
                para.addText("Sentence " + (i * 2 + 1) + ". ");
                para.addText("The style of these runs is decided after the fact.");
            }

            Table table = doc.addTable();
            table.setDefaultColumnWidth(150);
            table.setDefaultRowHeight(16);

            for (int row_index = 0; row_index < 3; ++row_index) {
                TableRow row = table.addTableRow();
                for (int col_index = 0; col_index < 3; ++col_index) {
                    TableCell cell = row.addTableCell();
                    cell.addParagraph("R" + row_index + " / C" + col_index);
                }
            }

            restyleContentTree(doc, false);

            System.out.println("Table has " + table.getNumRows() + " rows and "
                + table.getNumColumns() + " columns.");

            PDFDoc pdf = doc.paginateToPDF();
            pdf.save(output_path + "created_restyled.pdf", SDFDoc.SaveMode.LINEARIZED, null);
            pdf.close();
            doc.destroy();
            System.out.println("Saved created_restyled.pdf");
            return 0;
        } catch (Exception e) {
            System.out.println(e);
            return 1;
        }
    }

    public static void main(String[] args) {
        int ret = 0;

        PDFNet.initialize(PDFTronLicense.Key());

        try {
            ret |= createInvoice();
            ret |= createReportWithChart();
            ret |= createNewsletter();
            ret |= restyleExistingContent();
        } catch (Exception e) {
            System.out.println(e);
            ret = 1;
        }

        PDFNet.terminate();
        System.out.println("Done.");
        System.exit(ret);
    }
}

```

{% endcode %}
{% endtab %}

{% tab title="Obj-C" %}

```objective-c
//---------------------------------------------------------------------------------------
// Copyright (c) 2001-2026 by Apryse Software Inc. All Rights Reserved.
// Consult legal.txt regarding legal and license information.
//---------------------------------------------------------------------------------------

#import <OBJC/PDFNetOBJC.h>
#import <Foundation/Foundation.h>

//---------------------------------------------------------------------------------------
// The following sample illustrates how to use the PDFTron Layout (FlowDocument) API
// to programmatically create rich PDF documents.
//
// Instead of placing every element on a page, the content is described as a tree of
// paragraphs, tables, lists, shapes, charts and floats, which is then paginated into
// a PDF document.
//
// Each function below produces one document:
//   * an invoice          - tables, cell merging, styling, headers/footers, images
//   * a report            - sections, page setup, lists, charts, page numbers
//   * a newsletter        - shapes, shape text boxes, floats, hyperlinks, tab stops
//   * a content tree walk - post-processing of an existing content tree
//
// The documents are generated entirely within PDFNet with no external or system
// dependencies, enabling consistent results across platforms.
//
// Please contact us if you have any questions.
//---------------------------------------------------------------------------------------

static NSString* const g_input_path = @"../../TestFiles/";
static NSString* const g_output_path = @"../../TestFiles/Output/";

static NSString* IntToString(int val)
{
    return [NSString stringWithFormat:@"%d", val];
}

static NSString* MoneyToString(double val)
{
    return [NSString stringWithFormat:@"$%.2f", val];
}

static NSString* InputFile(NSString* name)
{
    return [g_input_path stringByAppendingString:name];
}

static NSString* OutputFile(NSString* name)
{
    return [g_output_path stringByAppendingString:name];
}

//---------------------------------------------------------------------------------------
// Small helpers shared by the samples below.
//---------------------------------------------------------------------------------------

// Adds a paragraph with the given text and applies a simple heading style.
static PTParagraph* AddHeading(PTContentContainer* container, NSString* text, double font_size,
                               unsigned char red, unsigned char green, unsigned char blue)
{
    PTParagraph* heading = [container AddParagraphWithText:text];
    PTTextStyledElement* style = [heading GetTextStyledElement];
    [style SetFontSize:font_size];
    [style SetBold:YES];
    [style SetTextColor:red green:green blue:blue];
    [heading SetSpaceBefore:12];
    [heading SetSpaceAfter:6];
    return heading;
}

// Adds a single table row filled with the given texts.
static PTTableRow* AddTableRowWithText(PTTable* table, NSArray<NSString*>* texts, BOOL bold,
                                       unsigned char back_red, unsigned char back_green,
                                       unsigned char back_blue)
{
    PTTableRow* row = [table AddTableRow];
    for (NSUInteger i = 0; i < [texts count]; i++)
    {
        PTTableCell* cell = [row AddTableCell];
        [cell SetBackgroundColor:back_red green:back_green blue:back_blue];
        [cell SetVerticalAlignment:e_ptcell_alignment_center];

        PTParagraph* para = [cell AddParagraphWithText:texts[i]];
        [para SetStartIndent:3];
        PTTextStyledElement* style = [para GetTextStyledElement];
        [style SetFontSize:10];
        [style SetBold:bold];

        // Right align everything but the first (description) column.
        [para SetJustificationMode:(i == 0 ?
            e_ptpara_text_justify_left : e_ptpara_text_justify_right)];
    }
    return row;
}

// Adds a "Page X of Y" footer to the given section, for all page groups.
static void AddPageNumberFooter(PTSection* section, NSString* title)
{
    PTSectionPageGroup const groups[] = {
        e_ptsection_first_page, e_ptsection_even_pages, e_ptsection_odd_pages };

    for (int i = 0; i < 3; i++)
    {
        PTContentContainer* footer = [section GetOrCreateFooter:groups[i]];

        PTParagraph* para = [footer AddParagraph];
        [para SetJustificationMode:e_ptpara_text_justify_center];

        PTTextStyledElement* style = [para GetTextStyledElement];
        [style SetFontSize:9];
        [style SetTextColor:120 green:120 blue:120];
        [style SetItalic:YES];

        [para AddText:[title stringByAppendingString:@"   |   Page "]];
        [para AddPageNumber:e_ptpagenumber_current_page];
        [para AddText:@" of "];
        [para AddPageNumber:e_ptpagenumber_total_pages];
    }
}

//---------------------------------------------------------------------------------------
// 1. An invoice: a header with a logo float, an addresses table, a line item table with
//    merged cells and a totals block.
//---------------------------------------------------------------------------------------
static int CreateInvoice(void)
{
    @try
    {
        PTFlowDocument* doc = [[PTFlowDocument alloc] init];
        [doc SetDefaultPageSize:612 height:792];      // US Letter, in points.
        [doc SetDefaultMargins:54 top:54 right:54 bottom:54];

        PTSection* section = [doc GetCurrentSection];

        // --- Header ---------------------------------------------------------------
        PTContentContainer* header = [section GetOrCreateHeader:e_ptsection_odd_pages];
        PTParagraph* header_para = [header AddParagraphWithText:@"SYH Supply Co."];
        [header_para SetJustificationMode:e_ptpara_text_justify_right];
        [[header_para GetTextStyledElement] SetFontSize:9];
        [[header_para GetTextStyledElement] SetTextColor:120 green:120 blue:120];

        AddPageNumberFooter(section, @"Invoice INV-2025-0042");

        // --- Title, with the company logo floated to the right --------------------
        PTParagraph* title = [doc AddParagraph];

        // A float takes its content out of the main flow and positions it relative to a
        // reference point, with the document content wrapped around it.
        PTFloat* logo_float = [title AddFloat];
        [logo_float SetHorizontalReferencePoint:e_ptfloat_reference_page];
        [logo_float SetVerticalReferencePoint:e_ptfloat_reference_paragraph];
        [logo_float SetPositionX:400];
        [logo_float SetPositionY:0];
        [logo_float SetObstructionType:e_ptfloat_square];
        [[logo_float AddParagraph] AddImageWithPath:96 height:96
                                         image_path:InputFile(@"logo_red.png")];

        [title AddText:@"INVOICE"];
        PTTextStyledElement* title_style = [title GetTextStyledElement];
        [title_style SetFontSize:28];
        [title_style SetBold:YES];
        [title_style SetTextColor:0 green:51 blue:102];

        PTParagraph* subtitle = [doc AddParagraphWithText:
            @"Invoice #INV-2025-0042\nIssued: March 14, 2025\nDue: April 13, 2025"];
        [[subtitle GetTextStyledElement] SetFontSize:10];
        [subtitle SetSpaceAfter:18];

        // --- Bill to / ship to ----------------------------------------------------
        PTTable* addresses = [doc AddTable];
        [addresses SetDefaultColumnWidth:240];
        [addresses SetDefaultRowHeight:16];

        PTTableRow* address_row = [addresses AddTableRow];
        NSArray<NSString*>* address_titles = @[ @"BILL TO", @"SHIP TO" ];
        NSArray<NSString*>* address_bodies = @[
            @"Contoso Ltd.\n123 Maple Street\nVancouver, BC V6B 1A1\nCanada",
            @"Contoso Warehouse #4\n980 Industrial Way\nBurnaby, BC V5J 3J1\nCanada" ];

        for (int i = 0; i < 2; i++)
        {
            PTTableCell* cell = [address_row AddTableCell];
            [cell SetBackgroundColor:240 green:244 blue:248];
            [cell SetBorder:0.5 red:200 green:210 blue:220];

            PTParagraph* caption = [cell AddParagraphWithText:address_titles[i]];
            [caption SetStartIndent:3];
            PTTextStyledElement* caption_style = [caption GetTextStyledElement];
            [caption_style SetFontSize:9];
            [caption_style SetBold:YES];
            [caption_style SetTextColor:0 green:51 blue:102];

            PTParagraph* body = [cell AddParagraphWithText:address_bodies[i]];
            [body SetStartIndent:3];
            [[body GetTextStyledElement] SetFontSize:10];
        }

        [doc AddParagraphWithText:@" "];

        // --- Line items -----------------------------------------------------------
        PTTable* items = [doc AddTable];
        [items SetDefaultColumnWidth:120];
        [items SetDefaultRowHeight:18];
        [items SetBorder:0.75 red:0 green:51 blue:102];

        PTTableRow* header_row = AddTableRowWithText(items,
            @[ @"Description", @"Qty", @"Unit price", @"Amount" ], YES, 0, 51, 102);

        // Make the header row text white by walking the row we have just created.
        PTContentNodeIterator* cell_itr = [header_row GetContentNodeIterator];
        while ([cell_itr HasNext])
        {
            PTTableCell* cell = [[cell_itr Current] AsTableCell];
            if (cell)
            {
                PTContentNodeIterator* para_itr = [cell GetContentNodeIterator];
                while ([para_itr HasNext])
                {
                    PTParagraph* para = [[para_itr Current] AsParagraph];
                    if (para)
                    {
                        [[para GetTextStyledElement] SetTextColor:255 green:255 blue:255];
                    }
                    [para_itr Next];
                }
            }
            [cell_itr Next];
        }

        NSArray<NSString*>* item_descriptions = @[
            @"Height adjustable desk, oak finish",
            @"Ergonomic office chair",
            @"Meeting room whiteboard, 180 cm",
            @"On-site assembly and delivery" ];
        int const item_quantities[] = { 2, 5, 1, 1 };
        double const item_prices[] = { 4800.00, 950.00, 1200.00, 2500.00 };
        int const num_line_items = (int)[item_descriptions count];

        double subtotal = 0;
        for (int i = 0; i < num_line_items; i++)
        {
            double amount = item_quantities[i] * item_prices[i];
            subtotal += amount;

            NSArray<NSString*>* cells = @[
                item_descriptions[i],
                IntToString(item_quantities[i]),
                MoneyToString(item_prices[i]),
                MoneyToString(amount) ];

            // Alternating row background for readability.
            BOOL even = (i % 2) == 0;
            AddTableRowWithText(items, cells, NO,
                even ? 255 : 240, even ? 255 : 244, even ? 255 : 248);
        }

        double const tax = subtotal * 0.12;

        NSArray<NSString*>* total_labels = @[ @"Subtotal", @"GST/PST (12%)", @"Total due (CAD)" ];
        double const total_values[] = { subtotal, tax, subtotal + tax };
        BOOL const total_bold[] = { NO, NO, YES };
        int const num_totals = (int)[total_labels count];

        for (int i = 0; i < num_totals; i++)
        {
            NSArray<NSString*>* cells = @[ @"", @"",
                total_labels[i], MoneyToString(total_values[i]) ];
            AddTableRowWithText(items, cells, total_bold[i], 255, 255, 255);

            // Merge the two empty leading cells of the totals row.
            [[items GetTableCell:0 row:(num_line_items + 1 + i)] MergeCellsRight:1];
        }

        PTParagraph* notes = [doc AddParagraphWithText:
            @"Payment is due within 30 days. Late payments are subject to a 1.5% monthly "
            @"interest charge. Please reference the invoice number with your payment."];
        [notes SetSpaceBefore:24];
        [[notes GetTextStyledElement] SetFontSize:9];
        [[notes GetTextStyledElement] SetItalic:YES];
        [[notes GetTextStyledElement] SetTextColor:90 green:90 blue:90];

        PTParagraph* signature = [doc AddParagraph];
        [signature SetSpaceBefore:18];
        [signature AddImageWithPath:140 height:50 image_path:InputFile(@"signature.jpg")];
        [signature AddText:@"\nAuthorized signature"];

        PTPDFDoc* pdf = [doc PaginateToPDF];
        [pdf SaveToFile:OutputFile(@"created_invoice.pdf") flags:e_ptlinearized];
        NSLog(@"Saved created_invoice.pdf");
        return 0;
    }
    @catch (NSException* e)
    {
        NSLog(@"%@", e);
        return 1;
    }
}

//---------------------------------------------------------------------------------------
// 2. A two page report. The first page (portrait) holds a data table, the second one is
//    a separate landscape section holding a chart.
//---------------------------------------------------------------------------------------
static int CreateReportWithChart(void)
{
    @try
    {
        PTFlowDocument* doc = [[PTFlowDocument alloc] init];
        [doc SetDefaultPageSize:612 height:792];
        [doc SetDefaultMargins:72 top:72 right:72 bottom:72];

        //--- Page 1: portrait section with the data table --------------------------
        PTSection* data_section = [doc GetCurrentSection];
        AddPageNumberFooter(data_section, @"Quarterly Revenue Report");

        AddHeading(doc, @"Quarterly Revenue Report 2025", 22, 0, 51, 102);

        PTParagraph* intro = [doc AddParagraphWithText:
            @"The table below summarizes the revenue by region for the first three quarters "
            @"of the fiscal year. All figures are given in thousands of Canadian dollars and "
            @"exclude intercompany transactions."];
        [[intro GetTextStyledElement] SetFontSize:11];
        [intro SetSpaceAfter:12];

        PTTable* table = [doc AddTable];
        [table SetDefaultColumnWidth:90];
        [table SetDefaultRowHeight:18];
        [table SetBorder:0.75 red:120 green:120 blue:120];

        AddTableRowWithText(table, @[ @"Region", @"Q1", @"Q2", @"Q3", @"Total" ],
            YES, 220, 230, 240);

        NSArray<NSString*>* regions = @[ @"North America", @"Europe",
                                         @"Asia Pacific", @"Latin America" ];
        double const quarters[][3] = {
            { 1250, 1410, 1580 },
            {  910,  980, 1120 },
            {  640,  720,  905 },
            {  310,  355,  390 } };
        int const num_rows = (int)[regions count];

        double totals[3] = { 0, 0, 0 };
        for (int i = 0; i < num_rows; i++)
        {
            totals[0] += quarters[i][0];
            totals[1] += quarters[i][1];
            totals[2] += quarters[i][2];

            double row_total = quarters[i][0] + quarters[i][1] + quarters[i][2];
            NSArray<NSString*>* cells = @[
                regions[i],
                IntToString((int)quarters[i][0]),
                IntToString((int)quarters[i][1]),
                IntToString((int)quarters[i][2]),
                IntToString((int)row_total) ];
            AddTableRowWithText(table, cells, NO, 255, 255, 255);
        }

        NSArray<NSString*>* total_cells = @[ @"All regions",
            IntToString((int)totals[0]),
            IntToString((int)totals[1]),
            IntToString((int)totals[2]),
            IntToString((int)(totals[0] + totals[1] + totals[2])) ];
        AddTableRowWithText(table, total_cells, YES, 240, 240, 240);

        AddHeading(doc, @"Key observations", 14, 0, 51, 102);

        PTList* observations = [doc AddList];
        [observations SetNumberFormatWithSuffix:e_ptlist_format_decimal suffix:@"." cascade:YES];
        [[observations GetLabelStyle] SetBold:YES];
        [[observations AddItem] AddParagraphWithText:
            @"Revenue grew in every region, with Asia Pacific showing the strongest "
            @"quarter over quarter growth."];
        [[observations AddItem] AddParagraphWithText:
            @"North America remains the largest market, contributing roughly 40% of "
            @"the total revenue."];

        PTListItem* risks = [observations AddItem];
        [risks AddParagraphWithText:@"Risks that may affect the Q4 forecast:"];
        PTList* risk_list = [risks AddList];
        [risk_list SetNumberFormatWithSuffix:e_ptlist_format_lower_letter suffix:@")" cascade:YES];
        [[risk_list AddItem] AddParagraphWithText:@"Currency fluctuations in Latin America."];
        [[risk_list AddItem] AddParagraphWithText:@"Longer sales cycles in the enterprise segment."];

        //--- Page 2: landscape section with the chart -------------------------------
        PTSection* chart_section = [doc AddSection];
        [chart_section SetPageSize:792 height:612];        // landscape
        [chart_section SetMargins:54 top:54 right:54 bottom:54];
        AddPageNumberFooter(chart_section, @"Quarterly Revenue Report");

        AddHeading(chart_section, @"Revenue by region and quarter", 18, 0, 51, 102);

        // A chart is described by a JSON definition string.
        NSString* const chart_definition =
            @"{"
            @"  \"chartType\": \"bar\","
            @"  \"title\": \"Revenue by region (thousands CAD)\","
            @"  \"theme\": \"vivid\","
            @"  \"data\": {"
            @"    \"categories\": { \"data\": [\"Q1\", \"Q2\", \"Q3\"] },"
            @"    \"series\": ["
            @"      { \"name\": { \"data\": [\"North America\"] }, \"data\": { \"data\": [1250, 1410, 1580] } },"
            @"      { \"name\": { \"data\": [\"Europe\"] }, \"data\": { \"data\": [910, 980, 1120] } },"
            @"      { \"name\": { \"data\": [\"Asia Pacific\"] }, \"data\": { \"data\": [640, 720, 905] } },"
            @"      { \"name\": { \"data\": [\"Latin America\"] }, \"data\": { \"data\": [310, 355, 390] } }"
            @"    ]"
            @"  }"
            @"}";

        PTParagraph* chart_para = [chart_section AddParagraph];
        [chart_para SetJustificationMode:e_ptpara_text_justify_center];
        PTChart* chart = [chart_para AddChart:560 height:340 definition:chart_definition];
        [chart SetWidth:600];
        [chart SetHeight:360];

        PTParagraph* caption = [chart_section AddParagraphWithText:
            @"Figure 1: revenue distribution across the four sales regions."];
        [caption SetJustificationMode:e_ptpara_text_justify_center];
        [[caption GetTextStyledElement] SetItalic:YES];
        [[caption GetTextStyledElement] SetFontSize:10];

        NSLog(@"Report sections: %u, second section page width: %f",
              [doc GetNumSections], [[doc GetSection:1] GetPageWidth]);

        PTPDFDoc* pdf = [doc PaginateToPDF];
        [pdf SaveToFile:OutputFile(@"created_report.pdf") flags:e_ptlinearized];
        NSLog(@"Saved created_report.pdf");
        return 0;
    }
    @catch (NSException* e)
    {
        NSLog(@"%@", e);
        return 1;
    }
}

//---------------------------------------------------------------------------------------
// 3. A newsletter demonstrating shapes, shape text boxes, floats, hyperlinks and
//    paragraph styling such as indents, tab stops and justification.
//---------------------------------------------------------------------------------------
static int CreateNewsletter(void)
{
    @try
    {
        NSString* const body_text =
            @"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod "
            @"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, "
            @"quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo "
            @"consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse "
            @"cillum dolore eu fugiat nulla pariatur.";

        PTFlowDocument* doc = [[PTFlowDocument alloc] init];
        [doc SetDefaultPageSize:612 height:792];
        [doc SetDefaultMargins:60 top:60 right:60 bottom:60];

        PTSection* section = [doc GetCurrentSection];
        AddPageNumberFooter(section, @"The Monthly Dispatch");

        // Masthead built from a rounded rectangle shape with a text box inside.
        PTParagraph* masthead_para = [doc AddParagraph];
        PTShape* masthead = [masthead_para AddShape:e_ptshape_rectangle_rounded_corners
                                              width:492 height:70];
        [masthead SetCornerRadius:10];
        [masthead SetBackgroundColor:0 g:51 b:102];
        [masthead SetOutlineColor:0 g:31 b:62];
        [masthead SetOutlineThickness:1.5];

        PTContentContainer* masthead_text = [masthead GetTextBox];
        PTParagraph* masthead_title = [masthead_text AddParagraphWithText:@"The Monthly Dispatch"];
        [masthead_title SetJustificationMode:e_ptpara_text_justify_center];
        PTTextStyledElement* masthead_style = [masthead_title GetTextStyledElement];
        [masthead_style SetFontSize:24];
        [masthead_style SetBold:YES];
        [masthead_style SetTextColor:255 green:255 blue:255];

        PTParagraph* masthead_sub = [masthead_text AddParagraphWithText:@"Issue 42 - March 2025"];
        [masthead_sub SetJustificationMode:e_ptpara_text_justify_center];
        [[masthead_sub GetTextStyledElement] SetFontSize:10];
        [[masthead_sub GetTextStyledElement] SetTextColor:200 green:215 blue:230];

        // Lead story with a pull quote floated next to it.
        AddHeading(doc, @"Documents, assembled", 16, 0, 51, 102);

        PTParagraph* lead = [doc AddParagraph];
        [lead SetJustificationMode:e_ptpara_text_justify_left];
        [lead SetTextIndent:18];
        [lead SetSpaceAfter:10];

        PTFloat* pull_quote = [lead AddFloat];
        [pull_quote SetHorizontalReferencePoint:e_ptfloat_reference_page];
        [pull_quote SetVerticalReferencePoint:e_ptfloat_reference_paragraph];
        [pull_quote SetPositionX:380];
        [pull_quote SetPositionY:10];
        [pull_quote SetObstructionType:e_ptfloat_square];

        PTParagraph* quote_holder = [pull_quote AddParagraph];
        PTShape* quote_box = [quote_holder AddShape:e_ptshape_rectangle_rounded_corners
                                              width:190 height:70];
        [quote_box SetCornerRadius:6];
        [quote_box SetBackgroundColor:240 g:246 b:255];
        [quote_box SetOutlineColor:0 g:51 b:102];
        [quote_box SetOutlineThickness:1.0];

        PTContentContainer* quote_text = [quote_box GetTextBox];
        PTParagraph* quote = [quote_text AddParagraphWithText:
            @"\"The content tree lets you describe a document, not a page.\""];
        [quote SetStartIndent:6];
        [quote SetEndIndent:6];
        PTTextStyledElement* quote_style = [quote GetTextStyledElement];
        [quote_style SetFontSize:12];
        [quote_style SetItalic:YES];
        [quote_style SetTextColor:0 green:51 blue:102];

        [lead AddText:body_text];
        [lead AddText:@" Read more about the API at "];
        PTTextRun* link = [lead AddHyperlink:@"apryse.com" target:@"https://www.apryse.com"];
        [[link GetTextStyledElement] SetTextColor:0 green:102 blue:204];
        [lead AddText:@"."];

        // Two "columns" of text, created with complementary indents.
        PTParagraph* column_left = [doc AddParagraphWithText:body_text];
        [column_left SetStartIndent:0];
        [column_left SetEndIndent:260];
        [[column_left GetTextStyledElement] SetFontSize:10];

        PTParagraph* column_right = [doc AddParagraphWithText:body_text];
        [column_right SetStartIndent:260];
        [column_right SetEndIndent:0];
        [[column_right GetTextStyledElement] SetFontSize:10];

        // A schedule built with tab stops instead of a table.
        AddHeading(doc, @"Upcoming webinars", 14, 0, 51, 102);

        NSArray<NSArray<NSString*>*>* schedule = @[
            @[ @"Apr 02", @"Building documents from scratch", @"R. Fischer" ],
            @[ @"Apr 16", @"Charts and data visualization",   @"M. Okafor" ],
            @[ @"Apr 30", @"Accessible PDF output",           @"L. Tanaka" ] ];

        for (NSArray<NSString*>* entry in schedule)
        {
            PTParagraph* row = [doc AddParagraph];
            [row AddTabStop:80];
            [row AddTabStop:360];
            [[row GetTextStyledElement] SetFontSize:10];
            [row AddText:[NSString stringWithFormat:@"%@\t%@\t%@",
                          entry[0], entry[1], entry[2]]];
        }

        // A decorative arrow and a photo, both anchored in a single paragraph.
        PTParagraph* gallery = [doc AddParagraph];
        [gallery SetSpaceBefore:16];
        [gallery SetJustificationMode:e_ptpara_text_justify_center];

        PTShape* arrow = [gallery AddShape:e_ptshape_arrow width:90 height:40];
        [arrow SetBackgroundColor:0 g:153 b:102];
        [arrow SetOutlineColor:0 g:102 b:68];
        [arrow SetOutlineThickness:1];
        [arrow SetArrowHeadSize:18];

        [gallery AddText:@"   "];
        [gallery AddImageWithPath:160 height:120 image_path:InputFile(@"butterfly.png")];

        PTPDFDoc* pdf = [doc PaginateToPDF];
        [pdf SaveToFile:OutputFile(@"created_newsletter.pdf") flags:e_ptlinearized];
        NSLog(@"Saved created_newsletter.pdf");
        return 0;
    }
    @catch (NSException* e)
    {
        NSLog(@"%@", e);
        return 1;
    }
}

//---------------------------------------------------------------------------------------
// 4. Post-processing: build a simple document and then walk its content tree, restyling
//    the elements that are found along the way.
//---------------------------------------------------------------------------------------

// Recursively walks the content tree, highlighting every second text run and
// outlining the cells of any table that is encountered.
static void RestyleContentTree(PTContentNode* node, BOOL* highlight)
{
    PTContentNodeIterator* itr = [node GetContentNodeIterator];
    while ([itr HasNext])
    {
        PTContentElement* el = [itr Current];

        PTTextRun* text_run = [el AsTextRun];
        if (text_run)
        {
            if (*highlight)
            {
                PTTextStyledElement* style = [text_run GetTextStyledElement];
                [style SetBold:YES];
                [style SetBackgroundColor:255 green:245 blue:180];
            }
            *highlight = !*highlight;
            [itr Next];
            continue;
        }

        PTTableCell* cell = [el AsTableCell];
        if (cell)
        {
            [cell SetBorder:0.5 red:150 green:150 blue:150];
        }

        // Recurse into any child node (paragraphs, tables, rows, cells, lists, ...).
        PTContentNode* child = [el AsContentNode];
        if (child)
        {
            RestyleContentTree(child, highlight);
        }

        [itr Next];
    }
}

static int RestyleExistingContent(void)
{
    @try
    {
        PTFlowDocument* doc = [[PTFlowDocument alloc] init];
        [doc SetDefaultPageSize:612 height:792];
        [doc SetDefaultMargins:72 top:72 right:72 bottom:72];

        AddHeading(doc, @"Content tree post-processing", 18, 0, 51, 102);

        for (int i = 0; i < 4; i++)
        {
            PTParagraph* para = [doc AddParagraph];
            [para SetSpaceAfter:8];
            [para AddText:[NSString stringWithFormat:@"Sentence %@. ", IntToString(i * 2)]];
            [para AddText:[NSString stringWithFormat:@"Sentence %@. ", IntToString(i * 2 + 1)]];
            [para AddText:@"The style of these runs is decided after the fact."];
        }

        PTTable* table = [doc AddTable];
        [table SetDefaultColumnWidth:150];
        [table SetDefaultRowHeight:16];

        for (int row_index = 0; row_index < 3; row_index++)
        {
            PTTableRow* row = [table AddTableRow];
            for (int col_index = 0; col_index < 3; col_index++)
            {
                PTTableCell* cell = [row AddTableCell];
                [cell AddParagraphWithText:[NSString stringWithFormat:@"R%@ / C%@",
                                            IntToString(row_index), IntToString(col_index)]];
            }
        }

        BOOL highlight = NO;
        RestyleContentTree(doc, &highlight);

        NSLog(@"Table has %u rows and %u columns.",
              [table GetNumRows], [table GetNumColumns]);

        PTPDFDoc* pdf = [doc PaginateToPDF];
        [pdf SaveToFile:OutputFile(@"created_restyled.pdf") flags:e_ptlinearized];
        NSLog(@"Saved created_restyled.pdf");
        return 0;
    }
    @catch (NSException* e)
    {
        NSLog(@"%@", e);
        return 1;
    }
}

int main(int argc, char *argv[])
{
    @autoreleasepool
    {
        int ret = 0;

        [PTPDFNet Initialize: 0];

        @try
        {
            ret |= CreateInvoice();
            ret |= CreateReportWithChart();
            ret |= CreateNewsletter();
            ret |= RestyleExistingContent();
        }
        @catch (NSException* e)
        {
            NSLog(@"%@", e);
            ret = 1;
        }

        [PTPDFNet Terminate: 0];
        NSLog(@"Done.");
        return ret;
    }
}
```

{% endtab %}

{% tab title="Python" %}
{% code lineNumbers="true" %}

```python
# Copyright (c) 2001-2026 by Apryse Software Inc. All Rights Reserved.
# Consult LICENSE.txt regarding license information.
#---------------------------------------------------------------------------------------

import site
site.addsitedir("../../../PDFNetC/Lib")
import sys
from PDFNetPython import *

sys.path.append("../../LicenseKey/PYTHON")
from LicenseKey import *

#---------------------------------------------------------------------------------------
# The following sample illustrates how to use the FlowDocument API to create documents
# from scratch. Instead of positioning every element on a page, the content is described
# as a tree of paragraphs, tables, lists, shapes, charts and floats, which is then
# paginated into a PDF document.
#
# Each function below produces one document:
#   * an invoice          - tables, cell merging, styling, headers/footers, images
#   * a report            - sections, page setup, lists, charts, page numbers
#   * a newsletter        - shapes, shape text boxes, floats, hyperlinks, tab stops
#   * a content tree walk - post-processing of an existing content tree
#---------------------------------------------------------------------------------------

# Relative path to the folder containing the test files.
input_path = "../../TestFiles/"
output_path = "../../TestFiles/Output/"


def money_to_string(val):
    return "$%.2f" % val


#-----------------------------------------------------------------------------------
# Small helpers shared by the samples below.
#-----------------------------------------------------------------------------------

# Adds a paragraph with the given text and applies a simple heading style.
def add_heading(container, text, font_size, red, green, blue):
    heading = container.AddParagraph(text)
    style = heading.GetTextStyledElement()
    style.SetFontSize(font_size)
    style.SetBold(True)
    style.SetTextColor(red, green, blue)
    heading.SetSpaceBefore(12)
    heading.SetSpaceAfter(6)
    return heading


# Adds a single table row filled with the given texts.
def add_table_row_with_text(table, texts, bold, back_red, back_green, back_blue):
    row = table.AddTableRow()
    for i, text in enumerate(texts):
        cell = row.AddTableCell()
        cell.SetBackgroundColor(back_red, back_green, back_blue)
        cell.SetVerticalAlignment(TableCell.e_alignment_center)

        para = cell.AddParagraph(text)
        para.SetStartIndent(3)
        style = para.GetTextStyledElement()
        style.SetFontSize(10)
        style.SetBold(bold)

        # Right align everything but the first (description) column.
        para.SetJustificationMode(Paragraph.e_text_justify_left if i == 0
                                  else Paragraph.e_text_justify_right)
    return row


# Adds a "Page X of Y" footer to the given section, for all page groups.
def add_page_number_footer(section, title):
    for group in [Section.e_first_page, Section.e_even_pages, Section.e_odd_pages]:
        footer = section.GetOrCreateFooter(group)

        para = footer.AddParagraph()
        para.SetJustificationMode(Paragraph.e_text_justify_center)

        style = para.GetTextStyledElement()
        style.SetFontSize(9)
        style.SetTextColor(120, 120, 120)
        style.SetItalic(True)

        para.AddText(title + "   |   Page ")
        para.AddPageNumber(PageNumber.e_current_page)
        para.AddText(" of ")
        para.AddPageNumber(PageNumber.e_total_pages)


#-----------------------------------------------------------------------------------
# 1. An invoice: a header with a logo float, an addresses table, a line item table
#    with merged cells and a totals block.
#-----------------------------------------------------------------------------------
def create_invoice():
    try:
        doc = FlowDocument()
        doc.SetDefaultPageSize(612, 792)        # US Letter, in points.
        doc.SetDefaultMargins(54, 54, 54, 54)

        section = doc.GetCurrentSection()

        # --- Header ---------------------------------------------------------------
        header = section.GetOrCreateHeader(Section.e_odd_pages)
        header_para = header.AddParagraph("SYH Supply Co.")
        header_para.SetJustificationMode(Paragraph.e_text_justify_right)
        header_para.GetTextStyledElement().SetFontSize(9)
        header_para.GetTextStyledElement().SetTextColor(120, 120, 120)

        add_page_number_footer(section, "Invoice INV-2025-0042")

        # --- Title, with the company logo floated to the right --------------------
        title = doc.AddParagraph()

        # A float takes its content out of the main flow and positions it relative to
        # a reference point, with the document content wrapped around it.
        logo_float = title.AddFloat()
        logo_float.SetHorizontalReferencePoint(Float.e_reference_page)
        logo_float.SetVerticalReferencePoint(Float.e_reference_paragraph)
        logo_float.SetPositionX(400)
        logo_float.SetPositionY(0)
        logo_float.SetObstructionType(Float.e_square)
        logo_float.AddParagraph().AddImage(96, 96, input_path + "logo_red.png")

        title.AddText("INVOICE")
        title_style = title.GetTextStyledElement()
        title_style.SetFontSize(28)
        title_style.SetBold(True)
        title_style.SetTextColor(0, 51, 102)

        subtitle = doc.AddParagraph(
            "Invoice #INV-2025-0042\nIssued: March 14, 2025\nDue: April 13, 2025")
        subtitle.GetTextStyledElement().SetFontSize(10)
        subtitle.SetSpaceAfter(18)

        # --- Bill to / ship to ----------------------------------------------------
        addresses = doc.AddTable()
        addresses.SetDefaultColumnWidth(240)
        addresses.SetDefaultRowHeight(16)

        address_row = addresses.AddTableRow()
        address_titles = ["BILL TO", "SHIP TO"]
        address_bodies = [
            "Contoso Ltd.\n123 Maple Street\nVancouver, BC V6B 1A1\nCanada",
            "Contoso Warehouse #4\n980 Industrial Way\nBurnaby, BC V5J 3J1\nCanada"]

        for i in range(2):
            cell = address_row.AddTableCell()
            cell.SetBackgroundColor(240, 244, 248)
            cell.SetBorder(0.5, 200, 210, 220)

            caption = cell.AddParagraph(address_titles[i])
            caption.SetStartIndent(3)
            caption_style = caption.GetTextStyledElement()
            caption_style.SetFontSize(9)
            caption_style.SetBold(True)
            caption_style.SetTextColor(0, 51, 102)

            body = cell.AddParagraph(address_bodies[i])
            body.SetStartIndent(3)
            body.GetTextStyledElement().SetFontSize(10)

        doc.AddParagraph(" ")

        # --- Line items -----------------------------------------------------------
        items = doc.AddTable()
        items.SetDefaultColumnWidth(120)
        items.SetDefaultRowHeight(18)
        items.SetBorder(0.75, 0, 51, 102)

        headers = ["Description", "Qty", "Unit price", "Amount"]
        header_row = add_table_row_with_text(items, headers, True, 0, 51, 102)

        # Make the header row text white by walking the row we have just created.
        cell_itr = header_row.GetContentNodeIterator()
        while cell_itr.HasNext():
            cell = cell_itr.Current().AsTableCell()
            if cell is not None:
                para_itr = cell.GetContentNodeIterator()
                while para_itr.HasNext():
                    para = para_itr.Current().AsParagraph()
                    if para is not None:
                        para.GetTextStyledElement().SetTextColor(255, 255, 255)
                    para_itr.Next()
            cell_itr.Next()

        line_descriptions = [
            "Height adjustable desk, oak finish",
            "Ergonomic office chair",
            "Meeting room whiteboard, 180 cm",
            "On-site assembly and delivery"]
        line_quantities = [2, 5, 1, 1]
        line_prices = [4800.00, 950.00, 1200.00, 2500.00]
        num_line_items = len(line_descriptions)

        subtotal = 0
        for i in range(num_line_items):
            amount = line_quantities[i] * line_prices[i]
            subtotal += amount

            cells = [
                line_descriptions[i],
                str(line_quantities[i]),
                money_to_string(line_prices[i]),
                money_to_string(amount)]

            # Alternating row background for readability.
            even = (i % 2) == 0
            add_table_row_with_text(items, cells, False,
                                    255 if even else 240,
                                    255 if even else 244,
                                    255 if even else 248)

        tax = subtotal * 0.12

        total_labels = ["Subtotal", "GST/PST (12%)", "Total due (CAD)"]
        total_values = [subtotal, tax, subtotal + tax]
        total_bold = [False, False, True]

        for i in range(len(total_labels)):
            cells = ["", "", total_labels[i], money_to_string(total_values[i])]
            add_table_row_with_text(items, cells, total_bold[i], 255, 255, 255)

            # Merge the two empty leading cells of the totals row.
            items.GetTableCell(0, num_line_items + 1 + i).MergeCellsRight(1)

        notes = doc.AddParagraph(
            "Payment is due within 30 days. Late payments are subject to a 1.5% monthly "
            "interest charge. Please reference the invoice number with your payment.")
        notes.SetSpaceBefore(24)
        notes.GetTextStyledElement().SetFontSize(9)
        notes.GetTextStyledElement().SetItalic(True)
        notes.GetTextStyledElement().SetTextColor(90, 90, 90)

        signature = doc.AddParagraph()
        signature.SetSpaceBefore(18)
        signature.AddImage(140, 50, input_path + "signature.jpg")
        signature.AddText("\nAuthorized signature")

        pdf = doc.PaginateToPDF()
        pdf.Save(output_path + "created_invoice.pdf", SDFDoc.e_linearized)
        pdf.Close()
        print("Saved created_invoice.pdf")
        return True
    except Exception as e:
        print(str(e))
        return False


#-----------------------------------------------------------------------------------
# 2. A two page report. The first page (portrait) holds a data table, the second one
#    is a separate landscape section holding a chart.
#-----------------------------------------------------------------------------------
def create_report_with_chart():
    try:
        doc = FlowDocument()
        doc.SetDefaultPageSize(612, 792)
        doc.SetDefaultMargins(72, 72, 72, 72)

        #--- Page 1: portrait section with the data table --------------------------
        data_section = doc.GetCurrentSection()
        add_page_number_footer(data_section, "Quarterly Revenue Report")

        add_heading(doc, "Quarterly Revenue Report 2025", 22, 0, 51, 102)

        intro = doc.AddParagraph(
            "The table below summarizes the revenue by region for the first three quarters "
            "of the fiscal year. All figures are given in thousands of Canadian dollars "
            "and exclude intercompany transactions.")
        intro.GetTextStyledElement().SetFontSize(11)
        intro.SetSpaceAfter(12)

        table = doc.AddTable()
        table.SetDefaultColumnWidth(90)
        table.SetDefaultRowHeight(18)
        table.SetBorder(0.75, 120, 120, 120)

        header_cells = ["Region", "Q1", "Q2", "Q3", "Total"]
        add_table_row_with_text(table, header_cells, True, 220, 230, 240)

        region_names = ["North America", "Europe", "Asia Pacific", "Latin America"]
        region_values = [
            [1250, 1410, 1580],
            [910, 980, 1120],
            [640, 720, 905],
            [310, 355, 390]]

        totals = [0, 0, 0]
        for i in range(len(region_names)):
            totals[0] += region_values[i][0]
            totals[1] += region_values[i][1]
            totals[2] += region_values[i][2]

            row_total = sum(region_values[i])
            cells = [region_names[i]] + [str(v) for v in region_values[i]] + [str(row_total)]
            add_table_row_with_text(table, cells, False, 255, 255, 255)

        total_cells = ["All regions"] + [str(t) for t in totals] + [str(sum(totals))]
        add_table_row_with_text(table, total_cells, True, 240, 240, 240)

        add_heading(doc, "Key observations", 14, 0, 51, 102)

        observations = doc.AddList()
        observations.SetNumberFormat(List.e_decimal, ".", True)
        observations.GetLabelStyle().SetBold(True)
        observations.AddItem().AddParagraph(
            "Revenue grew in every region, with Asia Pacific showing the strongest "
            "quarter over quarter growth.")
        observations.AddItem().AddParagraph(
            "North America remains the largest market, contributing roughly 40% of "
            "the total revenue.")

        risks = observations.AddItem()
        risks.AddParagraph("Risks that may affect the Q4 forecast:")
        risk_list = risks.AddList()
        risk_list.SetNumberFormat(List.e_lower_letter, ")", True)
        risk_list.AddItem().AddParagraph("Currency fluctuations in Latin America.")
        risk_list.AddItem().AddParagraph("Longer sales cycles in the enterprise segment.")

        #--- Page 2: landscape section with the chart ------------------------------
        chart_section = doc.AddSection()
        chart_section.SetPageSize(792, 612)        # landscape
        chart_section.SetMargins(54, 54, 54, 54)
        add_page_number_footer(chart_section, "Quarterly Revenue Report")

        add_heading(doc, "Revenue by region and quarter", 18, 0, 51, 102)

        # A chart is described by a JSON definition string.
        chart_definition = """{
            "chartType": "bar",
            "title": "Revenue by region (thousands CAD)",
            "theme": "vivid",
            "data": {
                "categories": { "data": ["Q1", "Q2", "Q3"] },
                "series": [
                    { "name": { "data": ["North America"] }, "data": { "data": [1250, 1410, 1580] } },
                    { "name": { "data": ["Europe"] }, "data": { "data": [910, 980, 1120] } },
                    { "name": { "data": ["Asia Pacific"] }, "data": { "data": [640, 720, 905] } },
                    { "name": { "data": ["Latin America"] }, "data": { "data": [310, 355, 390] } }
                ]
            }
        }"""

        chart_para = doc.AddParagraph()
        chart_para.SetJustificationMode(Paragraph.e_text_justify_center)
        chart = chart_para.AddChart(560, 340, chart_definition)
        chart.SetWidth(600)
        chart.SetHeight(360)

        caption = doc.AddParagraph(
            "Figure 1: revenue distribution across the four sales regions.")
        caption.SetJustificationMode(Paragraph.e_text_justify_center)
        caption.GetTextStyledElement().SetItalic(True)
        caption.GetTextStyledElement().SetFontSize(10)

        print("Report sections: " + str(doc.GetNumSections())
              + ", second section page width: " + str(doc.GetSection(1).GetPageWidth()))

        pdf = doc.PaginateToPDF()
        pdf.Save(output_path + "created_report.pdf", SDFDoc.e_linearized)
        pdf.Close()
        print("Saved created_report.pdf")
        return True
    except Exception as e:
        print(str(e))
        return False


#-----------------------------------------------------------------------------------
# 3. A newsletter demonstrating shapes, shape text boxes, floats, hyperlinks and
#    paragraph styling such as indents, tab stops and justification.
#-----------------------------------------------------------------------------------
def create_newsletter():
    try:
        body_text = (
            "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod "
            "tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, "
            "quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo "
            "consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse "
            "cillum dolore eu fugiat nulla pariatur.")

        doc = FlowDocument()
        doc.SetDefaultPageSize(612, 792)
        doc.SetDefaultMargins(60, 60, 60, 60)

        section = doc.GetCurrentSection()
        add_page_number_footer(section, "The Monthly Dispatch")

        # Masthead built from a rounded rectangle shape with a text box inside.
        masthead_para = doc.AddParagraph()
        masthead = masthead_para.AddShape(Shape.e_rectangle_rounded_corners, 492, 70)
        masthead.SetCornerRadius(10)
        masthead.SetBackgroundColor(0, 51, 102)
        masthead.SetOutlineColor(0, 31, 62)
        masthead.SetOutlineThickness(1.5)

        masthead_text = masthead.GetTextBox()
        masthead_title = masthead_text.AddParagraph("The Monthly Dispatch")
        masthead_title.SetJustificationMode(Paragraph.e_text_justify_center)
        masthead_style = masthead_title.GetTextStyledElement()
        masthead_style.SetFontSize(24)
        masthead_style.SetBold(True)
        masthead_style.SetTextColor(255, 255, 255)

        masthead_sub = masthead_text.AddParagraph("Issue 42 - March 2025")
        masthead_sub.SetJustificationMode(Paragraph.e_text_justify_center)
        masthead_sub.GetTextStyledElement().SetFontSize(10)
        masthead_sub.GetTextStyledElement().SetTextColor(200, 215, 230)

        # Lead story with a pull quote floated next to it.
        add_heading(doc, "Documents, assembled", 16, 0, 51, 102)

        lead = doc.AddParagraph()
        lead.SetJustificationMode(Paragraph.e_text_justify_left)
        lead.SetTextIndent(18)
        lead.SetSpaceAfter(10)

        pull_quote = lead.AddFloat()
        pull_quote.SetHorizontalReferencePoint(Float.e_reference_page)
        pull_quote.SetVerticalReferencePoint(Float.e_reference_paragraph)
        pull_quote.SetPositionX(380)
        pull_quote.SetPositionY(10)
        pull_quote.SetObstructionType(Float.e_square)

        quote_holder = pull_quote.AddParagraph()
        quote_box = quote_holder.AddShape(Shape.e_rectangle_rounded_corners, 190, 70)
        quote_box.SetCornerRadius(6)
        quote_box.SetBackgroundColor(240, 246, 255)
        quote_box.SetOutlineColor(0, 51, 102)
        quote_box.SetOutlineThickness(1.0)

        quote_text = quote_box.GetTextBox()
        quote = quote_text.AddParagraph(
            "\"The content tree lets you describe a document, not a page.\"")
        quote.SetStartIndent(6)
        quote.SetEndIndent(6)
        quote_style = quote.GetTextStyledElement()
        quote_style.SetFontSize(12)
        quote_style.SetItalic(True)
        quote_style.SetTextColor(0, 51, 102)

        lead.AddText(body_text)
        lead.AddText(" Read more about the API at ")
        link = lead.AddHyperlink("apryse.com", "https://www.apryse.com")
        link.GetTextStyledElement().SetTextColor(0, 102, 204)
        lead.AddText(".")

        # Two "columns" of text, created with complementary indents.
        column_left = doc.AddParagraph(body_text)
        column_left.SetStartIndent(0)
        column_left.SetEndIndent(260)
        column_left.GetTextStyledElement().SetFontSize(10)

        column_right = doc.AddParagraph(body_text)
        column_right.SetStartIndent(260)
        column_right.SetEndIndent(0)
        column_right.GetTextStyledElement().SetFontSize(10)

        # A schedule built with tab stops instead of a table.
        add_heading(doc, "Upcoming webinars", 14, 0, 51, 102)

        schedule = [
            ["Apr 02", "Building documents from scratch", "R. Fischer"],
            ["Apr 16", "Charts and data visualization", "M. Okafor"],
            ["Apr 30", "Accessible PDF output", "L. Tanaka"]]

        for webinar in schedule:
            row = doc.AddParagraph()
            row.AddTabStop(80)
            row.AddTabStop(360)
            row.GetTextStyledElement().SetFontSize(10)
            row.AddText(webinar[0] + "\t" + webinar[1] + "\t" + webinar[2])

        # A decorative arrow and a photo, both anchored in a single paragraph.
        gallery = doc.AddParagraph()
        gallery.SetSpaceBefore(16)
        gallery.SetJustificationMode(Paragraph.e_text_justify_center)

        arrow = gallery.AddShape(Shape.e_arrow, 90, 40)
        arrow.SetBackgroundColor(0, 153, 102)
        arrow.SetOutlineColor(0, 102, 68)
        arrow.SetOutlineThickness(1)
        arrow.SetArrowHeadSize(18)

        gallery.AddText("   ")
        gallery.AddImage(160, 120, input_path + "butterfly.png")

        pdf = doc.PaginateToPDF()
        pdf.Save(output_path + "created_newsletter.pdf", SDFDoc.e_linearized)
        pdf.Close()
        print("Saved created_newsletter.pdf")
        return True
    except Exception as e:
        print(str(e))
        return False


#-----------------------------------------------------------------------------------
# 4. Post-processing: build a simple document and then walk its content tree,
#    restyling the elements that are found along the way.
#-----------------------------------------------------------------------------------

# Recursively walks the content tree, highlighting every second text run and
# outlining the cells of any table that is encountered.
def restyle_content_tree(node, highlight):
    itr = node.GetContentNodeIterator()
    while itr.HasNext():
        el = itr.Current()
        itr.Next()

        text_run = el.AsTextRun()
        if text_run is not None:
            if highlight:
                style = text_run.GetTextStyledElement()
                style.SetBold(True)
                style.SetBackgroundColor(255, 245, 180)
            highlight = not highlight
            continue

        cell = el.AsTableCell()
        if cell is not None:
            cell.SetBorder(0.5, 150, 150, 150)

        child = el.AsContentNode()
        if child is not None:
            highlight = restyle_content_tree(child, highlight)
    return highlight


def restyle_existing_content():
    try:
        doc = FlowDocument()
        doc.SetDefaultPageSize(612, 792)
        doc.SetDefaultMargins(72, 72, 72, 72)

        add_heading(doc, "Content tree post-processing", 18, 0, 51, 102)

        for i in range(4):
            para = doc.AddParagraph()
            para.SetSpaceAfter(8)
            para.AddText("Sentence " + str(i * 2) + ". ")
            para.AddText("Sentence " + str(i * 2 + 1) + ". ")
            para.AddText("The style of these runs is decided after the fact.")

        table = doc.AddTable()
        table.SetDefaultColumnWidth(150)
        table.SetDefaultRowHeight(16)

        for row_index in range(3):
            row = table.AddTableRow()
            for col_index in range(3):
                cell = row.AddTableCell()
                cell.AddParagraph("R" + str(row_index) + " / C" + str(col_index))

        restyle_content_tree(doc, False)

        print("Table has " + str(table.GetNumRows()) + " rows and "
              + str(table.GetNumColumns()) + " columns.")

        pdf = doc.PaginateToPDF()
        pdf.Save(output_path + "created_restyled.pdf", SDFDoc.e_linearized)
        pdf.Close()
        print("Saved created_restyled.pdf")
        return True
    except Exception as e:
        print(str(e))
        return False


def main():
    # The first step in every application using PDFNet is to initialize the
    # library. The library is usually initialized only once, but calling
    # Initialize() multiple times is also fine.
    PDFNet.Initialize(LicenseKey)

    result = True
    result &= create_invoice()
    result &= create_report_with_chart()
    result &= create_newsletter()
    result &= restyle_existing_content()

    PDFNet.Terminate()

    if not result:
        print("Tests FAILED!!!\n==========")
        return
    print("Tests successful.\n==========")


if __name__ == '__main__':
    main()

```

{% endcode %}
{% endtab %}

{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
//---------------------------------------------------------------------------------------
// Copyright (c) 2001-2026 by Apryse Software Inc. All Rights Reserved.
// Consult legal.txt regarding legal and license information.
//---------------------------------------------------------------------------------------

const { PDFNet } = require('../../lib/pdfnet.js');
const PDFTronLicense = require('../../LicenseKey/NODEJS/LicenseKey');

//---------------------------------------------------------------------------------------
// The following sample illustrates how to use the PDFNet.FlowDocument API to create
// documents from scratch. Instead of positioning every element on a page, the content is
// described as a tree of paragraphs, tables, lists, shapes, charts and floats, which is
// then paginated into a PDF document.
//
// Each function below produces one document:
//   * an invoice          - tables, cell merging, styling, headers/footers, images
//   * a report            - sections, page setup, lists, charts, page numbers
//   * a newsletter        - shapes, shape text boxes, floats, hyperlinks, tab stops
//   * a content tree walk - post-processing of an existing content tree
//---------------------------------------------------------------------------------------

((exports) => {
	'use strict';

	exports.runDocumentCreationTest = async () => {

		const inputPath = '../TestFiles/';
		const outputPath = '../TestFiles/Output/';

		const moneyToString = (val) => '$' + val.toFixed(2);

		//-------------------------------------------------------------------------------
		// Small helpers shared by the samples below.
		//-------------------------------------------------------------------------------

		// Adds a paragraph with the given text and applies a simple heading style.
		const addHeading = async (container, text, font_size, red, green, blue) => {
			const heading = await container.addParagraphWithText(text);
			const style = await heading.getTextStyledElement();
			style.setFontSize(font_size);
			style.setBold(true);
			style.setTextColor(red, green, blue);
			heading.setSpaceBefore(12);
			heading.setSpaceAfter(6);
			return heading;
		};

		// Adds a single table row filled with the given texts.
		const addTableRowWithText = async (table, texts, bold, back_red, back_green, back_blue) => {
			const row = await table.addTableRow();
			for (let i = 0; i < texts.length; i++) {
				const cell = await row.addTableCell();
				cell.setBackgroundColor(back_red, back_green, back_blue);
				cell.setVerticalAlignment(PDFNet.TableCell.AlignmentVertical.e_alignment_center);

				const para = await cell.addParagraphWithText(texts[i]);
				para.setStartIndent(3);
				const style = await para.getTextStyledElement();
				style.setFontSize(10);
				style.setBold(bold);

				// Right align everything but the first (description) column.
				para.setJustificationMode(i === 0
					? PDFNet.Paragraph.TextJustification.e_text_justify_left
					: PDFNet.Paragraph.TextJustification.e_text_justify_right);
			}
			return row;
		};

		// Adds a "Page X of Y" footer to the given section, for all page groups.
		const addPageNumberFooter = async (section, title) => {
			const groups = [
				PDFNet.Section.PageGroup.e_first_page,
				PDFNet.Section.PageGroup.e_even_pages,
				PDFNet.Section.PageGroup.e_odd_pages];

			for (const group of groups) {
				const footer = await section.getOrCreateFooter(group);

				const para = await footer.addParagraph();
				para.setJustificationMode(PDFNet.Paragraph.TextJustification.e_text_justify_center);

				const style = await para.getTextStyledElement();
				style.setFontSize(9);
				style.setTextColor(120, 120, 120);
				style.setItalic(true);

				await para.addText(title + '   |   Page ');
				await para.addPageNumber(PDFNet.PageNumber.NumberType.e_current_page);
				await para.addText(' of ');
				await para.addPageNumber(PDFNet.PageNumber.NumberType.e_total_pages);
			}
		};

		//-------------------------------------------------------------------------------
		// 1. An invoice: a header with a logo float, an addresses table, a line item
		//    table with merged cells and a totals block.
		//-------------------------------------------------------------------------------
		const createInvoice = async () => {
			const doc = await PDFNet.FlowDocument.create();
			doc.setDefaultPageSize(612, 792);          // US Letter, in points.
			doc.setDefaultMargins(54, 54, 54, 54);

			const section = await doc.getCurrentSection();

			// --- Header ----------------------------------------------------------------
			const header = await section.getOrCreateHeader(PDFNet.Section.PageGroup.e_odd_pages);
			const header_para = await header.addParagraphWithText('SYH Supply Co.');
			header_para.setJustificationMode(PDFNet.Paragraph.TextJustification.e_text_justify_right);
			const header_style = await header_para.getTextStyledElement();
			header_style.setFontSize(9);
			header_style.setTextColor(120, 120, 120);

			await addPageNumberFooter(section, 'Invoice INV-2025-0042');

			// --- Title, with the company logo floated to the right ---------------------
			const title = await doc.addParagraph();

			// A float takes its content out of the main flow and positions it relative to
			// a reference point, with the document content wrapped around it.
			const logo_float = await title.addFloat();
			logo_float.setHorizontalReferencePoint(PDFNet.Float.ReferencePoint.e_reference_page);
			logo_float.setVerticalReferencePoint(PDFNet.Float.ReferencePoint.e_reference_paragraph);
			logo_float.setPositionX(400);
			logo_float.setPositionY(0);
			logo_float.setObstructionType(PDFNet.Float.ObstructionType.e_square);
			await (await logo_float.addParagraph()).addImage(96, 96, inputPath + 'logo_red.png');

			await title.addText('INVOICE');
			const title_style = await title.getTextStyledElement();
			title_style.setFontSize(28);
			title_style.setBold(true);
			title_style.setTextColor(0, 51, 102);

			const subtitle = await doc.addParagraphWithText(
				'Invoice #INV-2025-0042\nIssued: March 14, 2025\nDue: April 13, 2025');
			(await subtitle.getTextStyledElement()).setFontSize(10);
			subtitle.setSpaceAfter(18);

			// --- Bill to / ship to -----------------------------------------------------
			const addresses = await doc.addTable();
			addresses.setDefaultColumnWidth(240);
			addresses.setDefaultRowHeight(16);

			const address_row = await addresses.addTableRow();
			const address_titles = ['BILL TO', 'SHIP TO'];
			const address_bodies = [
				'Contoso Ltd.\n123 Maple Street\nVancouver, BC V6B 1A1\nCanada',
				'Contoso Warehouse #4\n980 Industrial Way\nBurnaby, BC V5J 3J1\nCanada'];

			for (let i = 0; i < 2; i++) {
				const cell = await address_row.addTableCell();
				cell.setBackgroundColor(240, 244, 248);
				cell.setBorder(0.5, 200, 210, 220);

				const caption = await cell.addParagraphWithText(address_titles[i]);
				caption.setStartIndent(3);
				const caption_style = await caption.getTextStyledElement();
				caption_style.setFontSize(9);
				caption_style.setBold(true);
				caption_style.setTextColor(0, 51, 102);

				const body = await cell.addParagraphWithText(address_bodies[i]);
				body.setStartIndent(3);
				(await body.getTextStyledElement()).setFontSize(10);
			}

			await doc.addParagraphWithText(' ');

			// --- Line items ------------------------------------------------------------
			const items = await doc.addTable();
			items.setDefaultColumnWidth(120);
			items.setDefaultRowHeight(18);
			items.setBorder(0.75, 0, 51, 102);

			const headers = ['Description', 'Qty', 'Unit price', 'Amount'];
			const header_row = await addTableRowWithText(items, headers, true, 0, 51, 102);

			// Make the header row text white by walking the row we have just created.
			const cell_itr = await header_row.getContentNodeIterator();
			while (await cell_itr.hasNext()) {
				const cell = await (await cell_itr.current()).asTableCell();
				if (cell !== null) {
					const para_itr = await cell.getContentNodeIterator();
					while (await para_itr.hasNext()) {
						const para = await (await para_itr.current()).asParagraph();
						if (para !== null) {
							(await para.getTextStyledElement()).setTextColor(255, 255, 255);
						}
						await para_itr.next();
					}
				}
				await cell_itr.next();
			}

			const line_descriptions = [
				'Height adjustable desk, oak finish',
				'Ergonomic office chair',
				'Meeting room whiteboard, 180 cm',
				'On-site assembly and delivery'];
			const line_quantities = [2, 5, 1, 1];
			const line_prices = [4800.00, 950.00, 1200.00, 2500.00];
			const num_line_items = line_descriptions.length;

			let subtotal = 0;
			for (let i = 0; i < num_line_items; i++) {
				const amount = line_quantities[i] * line_prices[i];
				subtotal += amount;

				const cells = [
					line_descriptions[i],
					line_quantities[i].toString(),
					moneyToString(line_prices[i]),
					moneyToString(amount)];

				// Alternating row background for readability.
				const even = (i % 2) === 0;
				await addTableRowWithText(items, cells, false,
					even ? 255 : 240, even ? 255 : 244, even ? 255 : 248);
			}

			const tax = subtotal * 0.12;

			const total_labels = ['Subtotal', 'GST/PST (12%)', 'Total due (CAD)'];
			const total_values = [subtotal, tax, subtotal + tax];
			const total_bold = [false, false, true];

			for (let i = 0; i < total_labels.length; i++) {
				const cells = ['', '', total_labels[i], moneyToString(total_values[i])];
				await addTableRowWithText(items, cells, total_bold[i], 255, 255, 255);

				// Merge the two empty leading cells of the totals row.
				(await items.getTableCell(0, num_line_items + 1 + i)).mergeCellsRight(1);
			}

			const notes = await doc.addParagraphWithText(
				'Payment is due within 30 days. Late payments are subject to a 1.5% monthly '
				+ 'interest charge. Please reference the invoice number with your payment.');
			notes.setSpaceBefore(24);
			const notes_style = await notes.getTextStyledElement();
			notes_style.setFontSize(9);
			notes_style.setItalic(true);
			notes_style.setTextColor(90, 90, 90);

			const signature = await doc.addParagraph();
			signature.setSpaceBefore(18);
			await signature.addImage(140, 50, inputPath + 'signature.jpg');
			await signature.addText('\nAuthorized signature');

			const pdf = await doc.paginateToPDF();
			await pdf.save(outputPath + 'created_invoice.pdf', PDFNet.SDFDoc.SaveOptions.e_linearized);
			console.log('Saved created_invoice.pdf');
		};

		//-------------------------------------------------------------------------------
		// 2. A two page report. The first page (portrait) holds a data table, the second
		//    one is a separate landscape section holding a chart.
		//-------------------------------------------------------------------------------
		const createReportWithChart = async () => {
			const doc = await PDFNet.FlowDocument.create();
			doc.setDefaultPageSize(612, 792);
			doc.setDefaultMargins(72, 72, 72, 72);

			//--- Page 1: portrait section with the data table ---------------------------
			const data_section = await doc.getCurrentSection();
			await addPageNumberFooter(data_section, 'Quarterly Revenue Report');

			await addHeading(doc, 'Quarterly Revenue Report 2025', 22, 0, 51, 102);

			const intro = await doc.addParagraphWithText(
				'The table below summarizes the revenue by region for the first three quarters '
				+ 'of the fiscal year. All figures are given in thousands of Canadian dollars '
				+ 'and exclude intercompany transactions.');
			(await intro.getTextStyledElement()).setFontSize(11);
			intro.setSpaceAfter(12);

			const table = await doc.addTable();
			table.setDefaultColumnWidth(90);
			table.setDefaultRowHeight(18);
			table.setBorder(0.75, 120, 120, 120);

			const header_cells = ['Region', 'Q1', 'Q2', 'Q3', 'Total'];
			await addTableRowWithText(table, header_cells, true, 220, 230, 240);

			const region_names = ['North America', 'Europe', 'Asia Pacific', 'Latin America'];
			const region_values = [
				[1250, 1410, 1580],
				[910, 980, 1120],
				[640, 720, 905],
				[310, 355, 390]];

			const totals = [0, 0, 0];
			for (let i = 0; i < region_names.length; i++) {
				totals[0] += region_values[i][0];
				totals[1] += region_values[i][1];
				totals[2] += region_values[i][2];

				const row_total = region_values[i][0] + region_values[i][1] + region_values[i][2];
				const cells = [
					region_names[i],
					region_values[i][0].toString(),
					region_values[i][1].toString(),
					region_values[i][2].toString(),
					row_total.toString()];
				await addTableRowWithText(table, cells, false, 255, 255, 255);
			}

			const total_cells = ['All regions',
				totals[0].toString(),
				totals[1].toString(),
				totals[2].toString(),
				(totals[0] + totals[1] + totals[2]).toString()];
			await addTableRowWithText(table, total_cells, true, 240, 240, 240);

			await addHeading(doc, 'Key observations', 14, 0, 51, 102);

			const observations = await doc.addList();
			observations.setNumberFormat(PDFNet.List.NumberFormat.e_decimal, '.', true);
			(await observations.getLabelStyle()).setBold(true);
			await (await observations.addItem()).addParagraphWithText(
				'Revenue grew in every region, with Asia Pacific showing the strongest '
				+ 'quarter over quarter growth.');
			await (await observations.addItem()).addParagraphWithText(
				'North America remains the largest market, contributing roughly 40% of '
				+ 'the total revenue.');

			const risks = await observations.addItem();
			await risks.addParagraphWithText('Risks that may affect the Q4 forecast:');
			const risk_list = await risks.addList();
			risk_list.setNumberFormat(PDFNet.List.NumberFormat.e_lower_letter, ')', true);
			await (await risk_list.addItem()).addParagraphWithText('Currency fluctuations in Latin America.');
			await (await risk_list.addItem()).addParagraphWithText('Longer sales cycles in the enterprise segment.');

			//--- Page 2: landscape section with the chart -------------------------------
			const chart_section = await doc.addSection();
			chart_section.setPageSize(792, 612);        // landscape
			chart_section.setMargins(54, 54, 54, 54);
			await addPageNumberFooter(chart_section, 'Quarterly Revenue Report');

			await addHeading(doc, 'Revenue by region and quarter', 18, 0, 51, 102);

			// A chart is described by a JSON definition string.
			const chart_definition = JSON.stringify({
				chartType: 'bar',
				title: 'Revenue by region (thousands CAD)',
				theme: 'vivid',
				data: {
					categories: { data: ['Q1', 'Q2', 'Q3'] },
					series: region_names.map((name, i) => ({
						name: { data: [name] },
						data: { data: region_values[i] }
					}))
				}
			});

			const chart_para = await doc.addParagraph();
			chart_para.setJustificationMode(PDFNet.Paragraph.TextJustification.e_text_justify_center);
			const chart = await chart_para.addChart(560, 340, chart_definition);
			chart.setWidth(600);
			chart.setHeight(360);

			const caption = await doc.addParagraphWithText(
				'Figure 1: revenue distribution across the four sales regions.');
			caption.setJustificationMode(PDFNet.Paragraph.TextJustification.e_text_justify_center);
			const caption_style = await caption.getTextStyledElement();
			caption_style.setItalic(true);
			caption_style.setFontSize(10);

			console.log('Report sections: ' + (await doc.getNumSections())
				+ ', second section page width: ' + (await (await doc.getSection(1)).getPageWidth()));

			const pdf = await doc.paginateToPDF();
			await pdf.save(outputPath + 'created_report.pdf', PDFNet.SDFDoc.SaveOptions.e_linearized);
			console.log('Saved created_report.pdf');
		};

		//-------------------------------------------------------------------------------
		// 3. A newsletter demonstrating shapes, shape text boxes, floats, hyperlinks and
		//    paragraph styling such as indents, tab stops and justification.
		//-------------------------------------------------------------------------------
		const createNewsletter = async () => {
			const body_text =
				'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod '
				+ 'tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, '
				+ 'quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo '
				+ 'consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse '
				+ 'cillum dolore eu fugiat nulla pariatur.';

			const doc = await PDFNet.FlowDocument.create();
			doc.setDefaultPageSize(612, 792);
			doc.setDefaultMargins(60, 60, 60, 60);

			const section = await doc.getCurrentSection();
			await addPageNumberFooter(section, 'The Monthly Dispatch');

			// Masthead built from a rounded rectangle shape with a text box inside.
			const masthead_para = await doc.addParagraph();
			const masthead = await masthead_para.addShape(
				PDFNet.Shape.Type.e_rectangle_rounded_corners, 492, 70);
			masthead.setCornerRadius(10);
			masthead.setBackgroundColor(0, 51, 102);
			masthead.setOutlineColor(0, 31, 62);
			masthead.setOutlineThickness(1.5);

			const masthead_text = await masthead.getTextBox();
			const masthead_title = await masthead_text.addParagraphWithText('The Monthly Dispatch');
			masthead_title.setJustificationMode(PDFNet.Paragraph.TextJustification.e_text_justify_center);
			const masthead_style = await masthead_title.getTextStyledElement();
			masthead_style.setFontSize(24);
			masthead_style.setBold(true);
			masthead_style.setTextColor(255, 255, 255);

			const masthead_sub = await masthead_text.addParagraphWithText('Issue 42 - March 2025');
			masthead_sub.setJustificationMode(PDFNet.Paragraph.TextJustification.e_text_justify_center);
			const masthead_sub_style = await masthead_sub.getTextStyledElement();
			masthead_sub_style.setFontSize(10);
			masthead_sub_style.setTextColor(200, 215, 230);

			// Lead story with a pull quote floated next to it.
			await addHeading(doc, 'Documents, assembled', 16, 0, 51, 102);

			const lead = await doc.addParagraph();
			lead.setJustificationMode(PDFNet.Paragraph.TextJustification.e_text_justify_left);
			lead.setTextIndent(18);
			lead.setSpaceAfter(10);

			const pull_quote = await lead.addFloat();
			pull_quote.setHorizontalReferencePoint(PDFNet.Float.ReferencePoint.e_reference_page);
			pull_quote.setVerticalReferencePoint(PDFNet.Float.ReferencePoint.e_reference_paragraph);
			pull_quote.setPositionX(380);
			pull_quote.setPositionY(10);
			pull_quote.setObstructionType(PDFNet.Float.ObstructionType.e_square);

			const quote_holder = await pull_quote.addParagraph();
			const quote_box = await quote_holder.addShape(
				PDFNet.Shape.Type.e_rectangle_rounded_corners, 190, 70);
			quote_box.setCornerRadius(6);
			quote_box.setBackgroundColor(240, 246, 255);
			quote_box.setOutlineColor(0, 51, 102);
			quote_box.setOutlineThickness(1.0);

			const quote_text = await quote_box.getTextBox();
			const quote = await quote_text.addParagraphWithText(
				'"The content tree lets you describe a document, not a page."');
			quote.setStartIndent(6);
			quote.setEndIndent(6);
			const quote_style = await quote.getTextStyledElement();
			quote_style.setFontSize(12);
			quote_style.setItalic(true);
			quote_style.setTextColor(0, 51, 102);

			await lead.addText(body_text);
			await lead.addText(' Read more about the API at ');
			const link = await lead.addHyperlink('apryse.com', 'https://www.apryse.com');
			(await link.getTextStyledElement()).setTextColor(0, 102, 204);
			await lead.addText('.');

			// Two "columns" of text, created with complementary indents.
			const column_left = await doc.addParagraphWithText(body_text);
			column_left.setStartIndent(0);
			column_left.setEndIndent(260);
			(await column_left.getTextStyledElement()).setFontSize(10);

			const column_right = await doc.addParagraphWithText(body_text);
			column_right.setStartIndent(260);
			column_right.setEndIndent(0);
			(await column_right.getTextStyledElement()).setFontSize(10);

			// A schedule built with tab stops instead of a table.
			await addHeading(doc, 'Upcoming webinars', 14, 0, 51, 102);

			const schedule = [
				['Apr 02', 'Building documents from scratch', 'R. Fischer'],
				['Apr 16', 'Charts and data visualization', 'M. Okafor'],
				['Apr 30', 'Accessible PDF output', 'L. Tanaka']];

			for (const webinar of schedule) {
				const row = await doc.addParagraph();
				row.addTabStop(80);
				row.addTabStop(360);
				(await row.getTextStyledElement()).setFontSize(10);
				await row.addText(webinar[0] + '\t' + webinar[1] + '\t' + webinar[2]);
			}

			// A decorative arrow and a photo, both anchored in a single paragraph.
			const gallery = await doc.addParagraph();
			gallery.setSpaceBefore(16);
			gallery.setJustificationMode(PDFNet.Paragraph.TextJustification.e_text_justify_center);

			const arrow = await gallery.addShape(PDFNet.Shape.Type.e_arrow, 90, 40);
			arrow.setBackgroundColor(0, 153, 102);
			arrow.setOutlineColor(0, 102, 68);
			arrow.setOutlineThickness(1);
			arrow.setArrowHeadSize(18);

			await gallery.addText('   ');
			await gallery.addImage(160, 120, inputPath + 'butterfly.png');

			const pdf = await doc.paginateToPDF();
			await pdf.save(outputPath + 'created_newsletter.pdf', PDFNet.SDFDoc.SaveOptions.e_linearized);
			console.log('Saved created_newsletter.pdf');
		};

		//-------------------------------------------------------------------------------
		// 4. Post-processing: build a simple document and then walk its content tree,
		//    restyling the elements that are found along the way.
		//-------------------------------------------------------------------------------

		// Recursively walks the content tree, highlighting every second text run and
		// outlining the cells of any table that is encountered.
		const restyleContentTree = async (node, highlight) => {
			const itr = await node.getContentNodeIterator();
			while (await itr.hasNext()) {
				const el = await itr.current();
				await itr.next();

				const text_run = await el.asTextRun();
				if (text_run !== null) {
					if (highlight) {
						const style = await text_run.getTextStyledElement();
						style.setBold(true);
						style.setBackgroundColor(255, 245, 180);
					}
					highlight = !highlight;
					continue;
				}

				const cell = await el.asTableCell();
				if (cell !== null) {
					cell.setBorder(0.5, 150, 150, 150);
				}

				const child = await el.asContentNode();
				if (child !== null) {
					highlight = await restyleContentTree(child, highlight);
				}
			}
			return highlight;
		};

		const restyleExistingContent = async () => {
			const doc = await PDFNet.FlowDocument.create();
			doc.setDefaultPageSize(612, 792);
			doc.setDefaultMargins(72, 72, 72, 72);

			await addHeading(doc, 'Content tree post-processing', 18, 0, 51, 102);

			for (let i = 0; i < 4; i++) {
				const para = await doc.addParagraph();
				para.setSpaceAfter(8);
				await para.addText('Sentence ' + (i * 2) + '. ');
				await para.addText('Sentence ' + (i * 2 + 1) + '. ');
				await para.addText('The style of these runs is decided after the fact.');
			}

			const table = await doc.addTable();
			table.setDefaultColumnWidth(150);
			table.setDefaultRowHeight(16);

			for (let row_index = 0; row_index < 3; row_index++) {
				const row = await table.addTableRow();
				for (let col_index = 0; col_index < 3; col_index++) {
					const cell = await row.addTableCell();
					await cell.addParagraphWithText('R' + row_index + ' / C' + col_index);
				}
			}

			await restyleContentTree(doc, false);

			console.log('Table has ' + (await table.getNumRows()) + ' rows and '
				+ (await table.getNumColumns()) + ' columns.');

			const pdf = await doc.paginateToPDF();
			await pdf.save(outputPath + 'created_restyled.pdf', PDFNet.SDFDoc.SaveOptions.e_linearized);
			console.log('Saved created_restyled.pdf');
		};

		const main = async () => {
			try {
				await createInvoice();
				await createReportWithChart();
				await createNewsletter();
				await restyleExistingContent();
				console.log('Done.');
			} catch (err) {
				console.log(err);
			}
		};

		PDFNet.runWithCleanup(main, PDFTronLicense.Key).catch(function (error) {
			console.log('Error: ' + JSON.stringify(error));
		}).then(function () { return PDFNet.shutdown(); });
	};
	exports.runDocumentCreationTest();
})(exports);
// eslint-disable-next-line spaced-comment
//# sourceURL=DocumentCreationTest.js

```

{% endcode %}
{% endtab %}

{% tab title="PHP" %}
{% code lineNumbers="true" %}

```php
<?php
//---------------------------------------------------------------------------------------
// Copyright (c) 2001-2026 by Apryse Software Inc. All Rights Reserved.
// Consult LICENSE.txt regarding license information.
//---------------------------------------------------------------------------------------
if(file_exists("../../../PDFNetC/Lib/PDFNetPHP.php"))
include("../../../PDFNetC/Lib/PDFNetPHP.php");
include("../../LicenseKey/PHP/LicenseKey.php");

//---------------------------------------------------------------------------------------
// The following sample illustrates how to use the FlowDocument API to create documents
// from scratch. Instead of positioning every element on a page, the content is described
// as a tree of paragraphs, tables, lists, shapes, charts and floats, which is then
// paginated into a PDF document.
//
// Each function below produces one document:
//   * an invoice          - tables, cell merging, styling, headers/footers, images
//   * a report            - sections, page setup, lists, charts, page numbers
//   * a newsletter        - shapes, shape text boxes, floats, hyperlinks, tab stops
//   * a content tree walk - post-processing of an existing content tree
//
// Note: the Layout::List and Layout::Float classes are exposed as ListContainer and
// FloatContainer, because "list" and "float" are reserved names in PHP.
//---------------------------------------------------------------------------------------

// Relative path to the folder containing the test files.
$inputPath = getcwd()."/../../TestFiles/";
$outputPath = $inputPath."Output/";

function money_to_string($val)
{
    return sprintf('$%.2f', $val);
}

//-----------------------------------------------------------------------------------
// Small helpers shared by the samples below.
//-----------------------------------------------------------------------------------

// Adds a paragraph with the given text and applies a simple heading style.
function add_heading($container, $text, $font_size, $red, $green, $blue)
{
    $heading = $container->AddParagraph($text);
    $style = $heading->GetTextStyledElement();
    $style->SetFontSize($font_size);
    $style->SetBold(true);
    $style->SetTextColor($red, $green, $blue);
    $heading->SetSpaceBefore(12);
    $heading->SetSpaceAfter(6);
    return $heading;
}

// Adds a single table row filled with the given texts.
function add_table_row_with_text($table, $texts, $bold, $back_red, $back_green, $back_blue)
{
    $row = $table->AddTableRow();
    for ($i = 0; $i < count($texts); ++$i)
    {
        $cell = $row->AddTableCell();
        $cell->SetBackgroundColor($back_red, $back_green, $back_blue);
        $cell->SetVerticalAlignment(TableCell::e_alignment_center);

        $para = $cell->AddParagraph($texts[$i]);
        $para->SetStartIndent(3);
        $style = $para->GetTextStyledElement();
        $style->SetFontSize(10);
        $style->SetBold($bold);

        // Right align everything but the first (description) column.
        $para->SetJustificationMode($i == 0
            ? Paragraph::e_text_justify_left
            : Paragraph::e_text_justify_right);
    }
    return $row;
}

// Adds a "Page X of Y" footer to the given section, for all page groups.
function add_page_number_footer($section, $title)
{
    $groups = array(Section::e_first_page, Section::e_even_pages, Section::e_odd_pages);

    foreach ($groups as $group)
    {
        $footer = $section->GetOrCreateFooter($group);

        $para = $footer->AddParagraph();
        $para->SetJustificationMode(Paragraph::e_text_justify_center);

        $style = $para->GetTextStyledElement();
        $style->SetFontSize(9);
        $style->SetTextColor(120, 120, 120);
        $style->SetItalic(true);

        $para->AddText($title . "   |   Page ");
        $para->AddPageNumber(PageNumber::e_current_page);
        $para->AddText(" of ");
        $para->AddPageNumber(PageNumber::e_total_pages);
    }
}

//-----------------------------------------------------------------------------------
// 1. An invoice: a header with a logo float, an addresses table, a line item table
//    with merged cells and a totals block.
//-----------------------------------------------------------------------------------
function create_invoice()
{
    global $inputPath, $outputPath;

    try
    {
        $doc = new FlowDocument();
        $doc->SetDefaultPageSize(612, 792);        // US Letter, in points.
        $doc->SetDefaultMargins(54, 54, 54, 54);

        $section = $doc->GetCurrentSection();

        // --- Header ---------------------------------------------------------------
        $header = $section->GetOrCreateHeader(Section::e_odd_pages);
        $header_para = $header->AddParagraph("SYH Supply Co.");
        $header_para->SetJustificationMode(Paragraph::e_text_justify_right);
        $header_para->GetTextStyledElement()->SetFontSize(9);
        $header_para->GetTextStyledElement()->SetTextColor(120, 120, 120);

        add_page_number_footer($section, "Invoice INV-2025-0042");

        // --- Title, with the company logo floated to the right --------------------
        $title = $doc->AddParagraph();

        // A float takes its content out of the main flow and positions it relative to
        // a reference point, with the document content wrapped around it.
        $logo_float = $title->AddFloat();
        $logo_float->SetHorizontalReferencePoint(FloatContainer::e_reference_page);
        $logo_float->SetVerticalReferencePoint(FloatContainer::e_reference_paragraph);
        $logo_float->SetPositionX(400);
        $logo_float->SetPositionY(0);
        $logo_float->SetObstructionType(FloatContainer::e_square);
        $logo_float->AddParagraph()->AddImage(96.0, 96.0, $inputPath . "logo_red.png");

        $title->AddText("INVOICE");
        $title_style = $title->GetTextStyledElement();
        $title_style->SetFontSize(28);
        $title_style->SetBold(true);
        $title_style->SetTextColor(0, 51, 102);

        $subtitle = $doc->AddParagraph(
            "Invoice #INV-2025-0042\nIssued: March 14, 2025\nDue: April 13, 2025");
        $subtitle->GetTextStyledElement()->SetFontSize(10);
        $subtitle->SetSpaceAfter(18);

        // --- Bill to / ship to ----------------------------------------------------
        $addresses = $doc->AddTable();
        $addresses->SetDefaultColumnWidth(240);
        $addresses->SetDefaultRowHeight(16);

        $address_row = $addresses->AddTableRow();
        $address_titles = array("BILL TO", "SHIP TO");
        $address_bodies = array(
            "Contoso Ltd.\n123 Maple Street\nVancouver, BC V6B 1A1\nCanada",
            "Contoso Warehouse #4\n980 Industrial Way\nBurnaby, BC V5J 3J1\nCanada");

        for ($i = 0; $i < 2; ++$i)
        {
            $cell = $address_row->AddTableCell();
            $cell->SetBackgroundColor(240, 244, 248);
            $cell->SetBorder(0.5, 200, 210, 220);

            $caption = $cell->AddParagraph($address_titles[$i]);
            $caption->SetStartIndent(3);
            $caption_style = $caption->GetTextStyledElement();
            $caption_style->SetFontSize(9);
            $caption_style->SetBold(true);
            $caption_style->SetTextColor(0, 51, 102);

            $body = $cell->AddParagraph($address_bodies[$i]);
            $body->SetStartIndent(3);
            $body->GetTextStyledElement()->SetFontSize(10);
        }

        $doc->AddParagraph(" ");

        // --- Line items -----------------------------------------------------------
        $items = $doc->AddTable();
        $items->SetDefaultColumnWidth(120);
        $items->SetDefaultRowHeight(18);
        $items->SetBorder(0.75, 0, 51, 102);

        $headers = array("Description", "Qty", "Unit price", "Amount");
        $header_row = add_table_row_with_text($items, $headers, true, 0, 51, 102);

        // Make the header row text white by walking the row we have just created.
        $cell_itr = $header_row->GetContentNodeIterator();
        while ($cell_itr->HasNext())
        {
            $cell = $cell_itr->Current()->AsTableCell();
            if ($cell !== null)
            {
                $para_itr = $cell->GetContentNodeIterator();
                while ($para_itr->HasNext())
                {
                    $para = $para_itr->Current()->AsParagraph();
                    if ($para !== null)
                    {
                        $para->GetTextStyledElement()->SetTextColor(255, 255, 255);
                    }
                    $para_itr->Next();
                }
            }
            $cell_itr->Next();
        }

        $line_descriptions = array(
            "Height adjustable desk, oak finish",
            "Ergonomic office chair",
            "Meeting room whiteboard, 180 cm",
            "On-site assembly and delivery");
        $line_quantities = array(2, 5, 1, 1);
        $line_prices = array(4800.00, 950.00, 1200.00, 2500.00);
        $num_line_items = count($line_descriptions);

        $subtotal = 0;
        for ($i = 0; $i < $num_line_items; ++$i)
        {
            $amount = $line_quantities[$i] * $line_prices[$i];
            $subtotal += $amount;

            $cells = array(
                $line_descriptions[$i],
                strval($line_quantities[$i]),
                money_to_string($line_prices[$i]),
                money_to_string($amount));

            // Alternating row background for readability.
            $even = ($i % 2) == 0;
            add_table_row_with_text($items, $cells, false,
                $even ? 255 : 240, $even ? 255 : 244, $even ? 255 : 248);
        }

        $tax = $subtotal * 0.12;

        $total_labels = array("Subtotal", "GST/PST (12%)", "Total due (CAD)");
        $total_values = array($subtotal, $tax, $subtotal + $tax);
        $total_bold = array(false, false, true);

        for ($i = 0; $i < count($total_labels); ++$i)
        {
            $cells = array("", "", $total_labels[$i], money_to_string($total_values[$i]));
            add_table_row_with_text($items, $cells, $total_bold[$i], 255, 255, 255);

            // Merge the two empty leading cells of the totals row.
            $items->GetTableCell(0, $num_line_items + 1 + $i)->MergeCellsRight(1);
        }

        $notes = $doc->AddParagraph(
            "Payment is due within 30 days. Late payments are subject to a 1.5% monthly "
            . "interest charge. Please reference the invoice number with your payment.");
        $notes->SetSpaceBefore(24);
        $notes->GetTextStyledElement()->SetFontSize(9);
        $notes->GetTextStyledElement()->SetItalic(true);
        $notes->GetTextStyledElement()->SetTextColor(90, 90, 90);

        $signature = $doc->AddParagraph();
        $signature->SetSpaceBefore(18);
        $signature->AddImage(140.0, 50.0, $inputPath . "signature.jpg");
        $signature->AddText("\nAuthorized signature");

        $pdf = $doc->PaginateToPDF();
        $pdf->Save($outputPath . "created_invoice.pdf", SDFDoc::e_linearized);
        $pdf->Close();
        echo(nl2br("Saved created_invoice.pdf\n"));
        return true;
    }
    catch (Exception $e)
    {
        echo($e->getMessage() . "\n");
        return false;
    }
}

//-----------------------------------------------------------------------------------
// 2. A two page report. The first page (portrait) holds a data table, the second one
//    is a separate landscape section holding a chart.
//-----------------------------------------------------------------------------------
function create_report_with_chart()
{
    global $outputPath;

    try
    {
        $doc = new FlowDocument();
        $doc->SetDefaultPageSize(612, 792);
        $doc->SetDefaultMargins(72, 72, 72, 72);

        //--- Page 1: portrait section with the data table --------------------------
        $data_section = $doc->GetCurrentSection();
        add_page_number_footer($data_section, "Quarterly Revenue Report");

        add_heading($doc, "Quarterly Revenue Report 2025", 22, 0, 51, 102);

        $intro = $doc->AddParagraph(
            "The table below summarizes the revenue by region for the first three quarters "
            . "of the fiscal year. All figures are given in thousands of Canadian dollars "
            . "and exclude intercompany transactions.");
        $intro->GetTextStyledElement()->SetFontSize(11);
        $intro->SetSpaceAfter(12);

        $table = $doc->AddTable();
        $table->SetDefaultColumnWidth(90);
        $table->SetDefaultRowHeight(18);
        $table->SetBorder(0.75, 120, 120, 120);

        $header_cells = array("Region", "Q1", "Q2", "Q3", "Total");
        add_table_row_with_text($table, $header_cells, true, 220, 230, 240);

        $region_names = array("North America", "Europe", "Asia Pacific", "Latin America");
        $region_values = array(
            array(1250, 1410, 1580),
            array(910, 980, 1120),
            array(640, 720, 905),
            array(310, 355, 390));

        $totals = array(0, 0, 0);
        for ($i = 0; $i < count($region_names); ++$i)
        {
            $totals[0] += $region_values[$i][0];
            $totals[1] += $region_values[$i][1];
            $totals[2] += $region_values[$i][2];

            $row_total = array_sum($region_values[$i]);
            $cells = array(
                $region_names[$i],
                strval($region_values[$i][0]),
                strval($region_values[$i][1]),
                strval($region_values[$i][2]),
                strval($row_total));
            add_table_row_with_text($table, $cells, false, 255, 255, 255);
        }

        $total_cells = array("All regions",
            strval($totals[0]),
            strval($totals[1]),
            strval($totals[2]),
            strval(array_sum($totals)));
        add_table_row_with_text($table, $total_cells, true, 240, 240, 240);

        add_heading($doc, "Key observations", 14, 0, 51, 102);

        $observations = $doc->AddList();
        $observations->SetNumberFormat(ListContainer::e_decimal, ".", true);
        $observations->GetLabelStyle()->SetBold(true);
        $observations->AddItem()->AddParagraph(
            "Revenue grew in every region, with Asia Pacific showing the strongest "
            . "quarter over quarter growth.");
        $observations->AddItem()->AddParagraph(
            "North America remains the largest market, contributing roughly 40% of "
            . "the total revenue.");

        $risks = $observations->AddItem();
        $risks->AddParagraph("Risks that may affect the Q4 forecast:");
        $risk_list = $risks->AddList();
        $risk_list->SetNumberFormat(ListContainer::e_lower_letter, ")", true);
        $risk_list->AddItem()->AddParagraph("Currency fluctuations in Latin America.");
        $risk_list->AddItem()->AddParagraph("Longer sales cycles in the enterprise segment.");

        //--- Page 2: landscape section with the chart ------------------------------
        $chart_section = $doc->AddSection();
        $chart_section->SetPageSize(792, 612);        // landscape
        $chart_section->SetMargins(54, 54, 54, 54);
        add_page_number_footer($chart_section, "Quarterly Revenue Report");

        add_heading($doc, "Revenue by region and quarter", 18, 0, 51, 102);

        // A chart is described by a JSON definition string.
        $chart_definition = <<<CHART
{
    "chartType": "bar",
    "title": "Revenue by region (thousands CAD)",
    "theme": "vivid",
    "data": {
        "categories": { "data": ["Q1", "Q2", "Q3"] },
        "series": [
            { "name": { "data": ["North America"] }, "data": { "data": [1250, 1410, 1580] } },
            { "name": { "data": ["Europe"] }, "data": { "data": [910, 980, 1120] } },
            { "name": { "data": ["Asia Pacific"] }, "data": { "data": [640, 720, 905] } },
            { "name": { "data": ["Latin America"] }, "data": { "data": [310, 355, 390] } }
        ]
    }
}
CHART;

        $chart_para = $doc->AddParagraph();
        $chart_para->SetJustificationMode(Paragraph::e_text_justify_center);
        $chart = $chart_para->AddChart(560, 340, $chart_definition);
        $chart->SetWidth(600);
        $chart->SetHeight(360);

        $caption = $doc->AddParagraph(
            "Figure 1: revenue distribution across the four sales regions.");
        $caption->SetJustificationMode(Paragraph::e_text_justify_center);
        $caption->GetTextStyledElement()->SetItalic(true);
        $caption->GetTextStyledElement()->SetFontSize(10);

        echo(nl2br("Report sections: " . $doc->GetNumSections()
            . ", second section page width: " . $doc->GetSection(1)->GetPageWidth() . "\n"));

        $pdf = $doc->PaginateToPDF();
        $pdf->Save($outputPath . "created_report.pdf", SDFDoc::e_linearized);
        $pdf->Close();
        echo(nl2br("Saved created_report.pdf\n"));
        return true;
    }
    catch (Exception $e)
    {
        echo($e->getMessage() . "\n");
        return false;
    }
}

//-----------------------------------------------------------------------------------
// 3. A newsletter demonstrating shapes, shape text boxes, floats, hyperlinks and
//    paragraph styling such as indents, tab stops and justification.
//-----------------------------------------------------------------------------------
function create_newsletter()
{
    global $inputPath, $outputPath;

    try
    {
        $body_text =
            "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod "
            . "tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, "
            . "quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo "
            . "consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse "
            . "cillum dolore eu fugiat nulla pariatur.";

        $doc = new FlowDocument();
        $doc->SetDefaultPageSize(612, 792);
        $doc->SetDefaultMargins(60, 60, 60, 60);

        $section = $doc->GetCurrentSection();
        add_page_number_footer($section, "The Monthly Dispatch");

        // Masthead built from a rounded rectangle shape with a text box inside.
        $masthead_para = $doc->AddParagraph();
        $masthead = $masthead_para->AddShape(Shape::e_rectangle_rounded_corners, 492, 70);
        $masthead->SetCornerRadius(10);
        $masthead->SetBackgroundColor(0, 51, 102);
        $masthead->SetOutlineColor(0, 31, 62);
        $masthead->SetOutlineThickness(1.5);

        $masthead_text = $masthead->GetTextBox();
        $masthead_title = $masthead_text->AddParagraph("The Monthly Dispatch");
        $masthead_title->SetJustificationMode(Paragraph::e_text_justify_center);
        $masthead_style = $masthead_title->GetTextStyledElement();
        $masthead_style->SetFontSize(24);
        $masthead_style->SetBold(true);
        $masthead_style->SetTextColor(255, 255, 255);

        $masthead_sub = $masthead_text->AddParagraph("Issue 42 - March 2025");
        $masthead_sub->SetJustificationMode(Paragraph::e_text_justify_center);
        $masthead_sub->GetTextStyledElement()->SetFontSize(10);
        $masthead_sub->GetTextStyledElement()->SetTextColor(200, 215, 230);

        // Lead story with a pull quote floated next to it.
        add_heading($doc, "Documents, assembled", 16, 0, 51, 102);

        $lead = $doc->AddParagraph();
        $lead->SetJustificationMode(Paragraph::e_text_justify_left);
        $lead->SetTextIndent(18);
        $lead->SetSpaceAfter(10);

        $pull_quote = $lead->AddFloat();
        $pull_quote->SetHorizontalReferencePoint(FloatContainer::e_reference_page);
        $pull_quote->SetVerticalReferencePoint(FloatContainer::e_reference_paragraph);
        $pull_quote->SetPositionX(380);
        $pull_quote->SetPositionY(10);
        $pull_quote->SetObstructionType(FloatContainer::e_square);

        $quote_holder = $pull_quote->AddParagraph();
        $quote_box = $quote_holder->AddShape(Shape::e_rectangle_rounded_corners, 190, 70);
        $quote_box->SetCornerRadius(6);
        $quote_box->SetBackgroundColor(240, 246, 255);
        $quote_box->SetOutlineColor(0, 51, 102);
        $quote_box->SetOutlineThickness(1.0);

        $quote_text = $quote_box->GetTextBox();
        $quote = $quote_text->AddParagraph(
            "\"The content tree lets you describe a document, not a page.\"");
        $quote->SetStartIndent(6);
        $quote->SetEndIndent(6);
        $quote_style = $quote->GetTextStyledElement();
        $quote_style->SetFontSize(12);
        $quote_style->SetItalic(true);
        $quote_style->SetTextColor(0, 51, 102);

        $lead->AddText($body_text);
        $lead->AddText(" Read more about the API at ");
        $link = $lead->AddHyperlink("apryse.com", "https://www.apryse.com");
        $link->GetTextStyledElement()->SetTextColor(0, 102, 204);
        $lead->AddText(".");

        // Two "columns" of text, created with complementary indents.
        $column_left = $doc->AddParagraph($body_text);
        $column_left->SetStartIndent(0);
        $column_left->SetEndIndent(260);
        $column_left->GetTextStyledElement()->SetFontSize(10);

        $column_right = $doc->AddParagraph($body_text);
        $column_right->SetStartIndent(260);
        $column_right->SetEndIndent(0);
        $column_right->GetTextStyledElement()->SetFontSize(10);

        // A schedule built with tab stops instead of a table.
        add_heading($doc, "Upcoming webinars", 14, 0, 51, 102);

        $schedule = array(
            array("Apr 02", "Building documents from scratch", "R. Fischer"),
            array("Apr 16", "Charts and data visualization", "M. Okafor"),
            array("Apr 30", "Accessible PDF output", "L. Tanaka"));

        foreach ($schedule as $webinar)
        {
            $row = $doc->AddParagraph();
            $row->AddTabStop(80);
            $row->AddTabStop(360);
            $row->GetTextStyledElement()->SetFontSize(10);
            $row->AddText($webinar[0] . "\t" . $webinar[1] . "\t" . $webinar[2]);
        }

        // A decorative arrow and a photo, both anchored in a single paragraph.
        $gallery = $doc->AddParagraph();
        $gallery->SetSpaceBefore(16);
        $gallery->SetJustificationMode(Paragraph::e_text_justify_center);

        $arrow = $gallery->AddShape(Shape::e_arrow, 90, 40);
        $arrow->SetBackgroundColor(0, 153, 102);
        $arrow->SetOutlineColor(0, 102, 68);
        $arrow->SetOutlineThickness(1);
        $arrow->SetArrowHeadSize(18);

        $gallery->AddText("   ");
        $gallery->AddImage(160.0, 120.0, $inputPath . "butterfly.png");

        $pdf = $doc->PaginateToPDF();
        $pdf->Save($outputPath . "created_newsletter.pdf", SDFDoc::e_linearized);
        $pdf->Close();
        echo(nl2br("Saved created_newsletter.pdf\n"));
        return true;
    }
    catch (Exception $e)
    {
        echo($e->getMessage() . "\n");
        return false;
    }
}

//-----------------------------------------------------------------------------------
// 4. Post-processing: build a simple document and then walk its content tree,
//    restyling the elements that are found along the way.
//-----------------------------------------------------------------------------------

// Recursively walks the content tree, highlighting every second text run and
// outlining the cells of any table that is encountered.
function restyle_content_tree($node, $highlight)
{
    $itr = $node->GetContentNodeIterator();
    while ($itr->HasNext())
    {
        $el = $itr->Current();
        $itr->Next();

        $text_run = $el->AsTextRun();
        if ($text_run !== null)
        {
            if ($highlight)
            {
                $style = $text_run->GetTextStyledElement();
                $style->SetBold(true);
                $style->SetBackgroundColor(255, 245, 180);
            }
            $highlight = !$highlight;
            continue;
        }

        $cell = $el->AsTableCell();
        if ($cell !== null)
        {
            $cell->SetBorder(0.5, 150, 150, 150);
        }

        $child = $el->AsContentNode();
        if ($child !== null)
        {
            $highlight = restyle_content_tree($child, $highlight);
        }
    }
    return $highlight;
}

function restyle_existing_content()
{
    global $outputPath;

    try
    {
        $doc = new FlowDocument();
        $doc->SetDefaultPageSize(612, 792);
        $doc->SetDefaultMargins(72, 72, 72, 72);

        add_heading($doc, "Content tree post-processing", 18, 0, 51, 102);

        for ($i = 0; $i < 4; ++$i)
        {
            $para = $doc->AddParagraph();
            $para->SetSpaceAfter(8);
            $para->AddText("Sentence " . ($i * 2) . ". ");
            $para->AddText("Sentence " . ($i * 2 + 1) . ". ");
            $para->AddText("The style of these runs is decided after the fact.");
        }

        $table = $doc->AddTable();
        $table->SetDefaultColumnWidth(150);
        $table->SetDefaultRowHeight(16);

        for ($row_index = 0; $row_index < 3; ++$row_index)
        {
            $row = $table->AddTableRow();
            for ($col_index = 0; $col_index < 3; ++$col_index)
            {
                $cell = $row->AddTableCell();
                $cell->AddParagraph("R" . $row_index . " / C" . $col_index);
            }
        }

        restyle_content_tree($doc, false);

        echo(nl2br("Table has " . $table->GetNumRows() . " rows and "
            . $table->GetNumColumns() . " columns.\n"));

        $pdf = $doc->PaginateToPDF();
        $pdf->Save($outputPath . "created_restyled.pdf", SDFDoc::e_linearized);
        $pdf->Close();
        echo(nl2br("Saved created_restyled.pdf\n"));
        return true;
    }
    catch (Exception $e)
    {
        echo($e->getMessage() . "\n");
        return false;
    }
}

function main()
{
    // The first step in every application using PDFNet is to initialize the
    // library. The library is usually initialized only once, but calling
    // Initialize() multiple times is also fine.
    global $LicenseKey;
    PDFNet::Initialize($LicenseKey);
    // 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.
    PDFNet::GetSystemFontList();
    PDFNet::AddResourceSearchPath("../../../PDFNetC/Lib/");

    $result = true;
    if (!create_invoice()) { $result = false; }
    if (!create_report_with_chart()) { $result = false; }
    if (!create_newsletter()) { $result = false; }
    if (!restyle_existing_content()) { $result = false; }

    PDFNet::Terminate();

    if (!$result)
    {
        echo(nl2br("Tests FAILED!!!\n==========\n"));
        return;
    }
    echo(nl2br("Tests successful.\n==========\n"));
}

main();
?>

```

{% endcode %}
{% endtab %}

{% tab title="Ruby" %}
{% code lineNumbers="true" %}

```ruby
#---------------------------------------------------------------------------------------
# Copyright (c) 2001-2026 by Apryse Software Inc. All Rights Reserved.
# Consult LICENSE.txt regarding license information.
#---------------------------------------------------------------------------------------

require '../../../PDFNetC/Lib/PDFNetRuby'
include PDFNetRuby
require '../../LicenseKey/RUBY/LicenseKey'

$stdout.sync = true

#---------------------------------------------------------------------------------------
# The following sample illustrates how to use the FlowDocument API to create documents
# from scratch. Instead of positioning every element on a page, the content is described
# as a tree of paragraphs, tables, lists, shapes, charts and floats, which is then
# paginated into a PDF document.
#
# Each function below produces one document:
#   * an invoice          - tables, cell merging, styling, headers/footers, images
#   * a report            - sections, page setup, lists, charts, page numbers
#   * a newsletter        - shapes, shape text boxes, floats, hyperlinks, tab stops
#   * a content tree walk - post-processing of an existing content tree
#
# Note: the Layout::Float class is exposed as FloatContainer, because Ruby's core Float
# class shadows it once the PDFNetRuby module is included.
#---------------------------------------------------------------------------------------

# Relative path to the folder containing the test files.
$inputPath = "../../TestFiles/"
$outputPath = "../../TestFiles/Output/"

def money_to_string(val)
	return format("$%.2f", val)
end

#-----------------------------------------------------------------------------------
# Small helpers shared by the samples below.
#-----------------------------------------------------------------------------------

# Adds a paragraph with the given text and applies a simple heading style.
def add_heading(container, text, font_size, red, green, blue)
	heading = container.AddParagraph(text)
	style = heading.GetTextStyledElement()
	style.SetFontSize(font_size)
	style.SetBold(true)
	style.SetTextColor(red, green, blue)
	heading.SetSpaceBefore(12)
	heading.SetSpaceAfter(6)
	return heading
end

# Adds a single table row filled with the given texts.
def add_table_row_with_text(table, texts, bold, back_red, back_green, back_blue)
	row = table.AddTableRow()
	texts.each_with_index do |text, i|
		cell = row.AddTableCell()
		cell.SetBackgroundColor(back_red, back_green, back_blue)
		cell.SetVerticalAlignment(TableCell::E_alignment_center)

		para = cell.AddParagraph(text)
		para.SetStartIndent(3)
		style = para.GetTextStyledElement()
		style.SetFontSize(10)
		style.SetBold(bold)

		# Right align everything but the first (description) column.
		if i == 0
			para.SetJustificationMode(Paragraph::E_text_justify_left)
		else
			para.SetJustificationMode(Paragraph::E_text_justify_right)
		end
	end
	return row
end

# Adds a "Page X of Y" footer to the given section, for all page groups.
def add_page_number_footer(section, title)
	[Section::E_first_page, Section::E_even_pages, Section::E_odd_pages].each do |group|
		footer = section.GetOrCreateFooter(group)

		para = footer.AddParagraph()
		para.SetJustificationMode(Paragraph::E_text_justify_center)

		style = para.GetTextStyledElement()
		style.SetFontSize(9)
		style.SetTextColor(120, 120, 120)
		style.SetItalic(true)

		para.AddText(title + "   |   Page ")
		para.AddPageNumber(PageNumber::E_current_page)
		para.AddText(" of ")
		para.AddPageNumber(PageNumber::E_total_pages)
	end
end

#-----------------------------------------------------------------------------------
# 1. An invoice: a header with a logo float, an addresses table, a line item table
#    with merged cells and a totals block.
#-----------------------------------------------------------------------------------
def create_invoice()
	begin
		doc = FlowDocument.new()
		doc.SetDefaultPageSize(612, 792)        # US Letter, in points.
		doc.SetDefaultMargins(54, 54, 54, 54)

		section = doc.GetCurrentSection()

		# --- Header ---------------------------------------------------------------
		header = section.GetOrCreateHeader(Section::E_odd_pages)
		header_para = header.AddParagraph("SYH Supply Co.")
		header_para.SetJustificationMode(Paragraph::E_text_justify_right)
		header_para.GetTextStyledElement().SetFontSize(9)
		header_para.GetTextStyledElement().SetTextColor(120, 120, 120)

		add_page_number_footer(section, "Invoice INV-2025-0042")

		# --- Title, with the company logo floated to the right --------------------
		title = doc.AddParagraph()

		# A float takes its content out of the main flow and positions it relative to
		# a reference point, with the document content wrapped around it.
		logo_float = title.AddFloat()
		logo_float.SetHorizontalReferencePoint(FloatContainer::E_reference_page)
		logo_float.SetVerticalReferencePoint(FloatContainer::E_reference_paragraph)
		logo_float.SetPositionX(400)
		logo_float.SetPositionY(0)
		logo_float.SetObstructionType(FloatContainer::E_square)
		logo_float.AddParagraph().AddImage(96, 96, $inputPath + "logo_red.png")

		title.AddText("INVOICE")
		title_style = title.GetTextStyledElement()
		title_style.SetFontSize(28)
		title_style.SetBold(true)
		title_style.SetTextColor(0, 51, 102)

		subtitle = doc.AddParagraph(
			"Invoice #INV-2025-0042\nIssued: March 14, 2025\nDue: April 13, 2025")
		subtitle.GetTextStyledElement().SetFontSize(10)
		subtitle.SetSpaceAfter(18)

		# --- Bill to / ship to ----------------------------------------------------
		addresses = doc.AddTable()
		addresses.SetDefaultColumnWidth(240)
		addresses.SetDefaultRowHeight(16)

		address_row = addresses.AddTableRow()
		address_titles = ["BILL TO", "SHIP TO"]
		address_bodies = [
			"Contoso Ltd.\n123 Maple Street\nVancouver, BC V6B 1A1\nCanada",
			"Contoso Warehouse #4\n980 Industrial Way\nBurnaby, BC V5J 3J1\nCanada"]

		(0...2).each do |i|
			cell = address_row.AddTableCell()
			cell.SetBackgroundColor(240, 244, 248)
			cell.SetBorder(0.5, 200, 210, 220)

			caption = cell.AddParagraph(address_titles[i])
			caption.SetStartIndent(3)
			caption_style = caption.GetTextStyledElement()
			caption_style.SetFontSize(9)
			caption_style.SetBold(true)
			caption_style.SetTextColor(0, 51, 102)

			body = cell.AddParagraph(address_bodies[i])
			body.SetStartIndent(3)
			body.GetTextStyledElement().SetFontSize(10)
		end

		doc.AddParagraph(" ")

		# --- Line items -----------------------------------------------------------
		items = doc.AddTable()
		items.SetDefaultColumnWidth(120)
		items.SetDefaultRowHeight(18)
		items.SetBorder(0.75, 0, 51, 102)

		headers = ["Description", "Qty", "Unit price", "Amount"]
		header_row = add_table_row_with_text(items, headers, true, 0, 51, 102)

		# Make the header row text white by walking the row we have just created.
		cell_itr = header_row.GetContentNodeIterator()
		while cell_itr.HasNext()
			cell = cell_itr.Current().AsTableCell()
			if !cell.nil?
				para_itr = cell.GetContentNodeIterator()
				while para_itr.HasNext()
					para = para_itr.Current().AsParagraph()
					if !para.nil?
						para.GetTextStyledElement().SetTextColor(255, 255, 255)
					end
					para_itr.Next()
				end
			end
			cell_itr.Next()
		end

		line_descriptions = [
			"Height adjustable desk, oak finish",
			"Ergonomic office chair",
			"Meeting room whiteboard, 180 cm",
			"On-site assembly and delivery"]
		line_quantities = [2, 5, 1, 1]
		line_prices = [4800.00, 950.00, 1200.00, 2500.00]
		num_line_items = line_descriptions.length

		subtotal = 0
		(0...num_line_items).each do |i|
			amount = line_quantities[i] * line_prices[i]
			subtotal += amount

			cells = [
				line_descriptions[i],
				line_quantities[i].to_s,
				money_to_string(line_prices[i]),
				money_to_string(amount)]

			# Alternating row background for readability.
			even = (i % 2) == 0
			add_table_row_with_text(items, cells, false,
				even ? 255 : 240, even ? 255 : 244, even ? 255 : 248)
		end

		tax = subtotal * 0.12

		total_labels = ["Subtotal", "GST/PST (12%)", "Total due (CAD)"]
		total_values = [subtotal, tax, subtotal + tax]
		total_bold = [false, false, true]

		(0...total_labels.length).each do |i|
			cells = ["", "", total_labels[i], money_to_string(total_values[i])]
			add_table_row_with_text(items, cells, total_bold[i], 255, 255, 255)

			# Merge the two empty leading cells of the totals row.
			items.GetTableCell(0, num_line_items + 1 + i).MergeCellsRight(1)
		end

		notes = doc.AddParagraph(
			"Payment is due within 30 days. Late payments are subject to a 1.5% monthly " \
			"interest charge. Please reference the invoice number with your payment.")
		notes.SetSpaceBefore(24)
		notes.GetTextStyledElement().SetFontSize(9)
		notes.GetTextStyledElement().SetItalic(true)
		notes.GetTextStyledElement().SetTextColor(90, 90, 90)

		signature = doc.AddParagraph()
		signature.SetSpaceBefore(18)
		signature.AddImage(140, 50, $inputPath + "signature.jpg")
		signature.AddText("\nAuthorized signature")

		pdf = doc.PaginateToPDF()
		pdf.Save($outputPath + "created_invoice.pdf", SDFDoc::E_linearized)
		pdf.Close()
		puts "Saved created_invoice.pdf"
		return true
	rescue Exception => e
		puts e.to_s
		return false
	end
end

#-----------------------------------------------------------------------------------
# 2. A two page report. The first page (portrait) holds a data table, the second one
#    is a separate landscape section holding a chart.
#-----------------------------------------------------------------------------------
def create_report_with_chart()
	begin
		doc = FlowDocument.new()
		doc.SetDefaultPageSize(612, 792)
		doc.SetDefaultMargins(72, 72, 72, 72)

		#--- Page 1: portrait section with the data table --------------------------
		data_section = doc.GetCurrentSection()
		add_page_number_footer(data_section, "Quarterly Revenue Report")

		add_heading(doc, "Quarterly Revenue Report 2025", 22, 0, 51, 102)

		intro = doc.AddParagraph(
			"The table below summarizes the revenue by region for the first three quarters " \
			"of the fiscal year. All figures are given in thousands of Canadian dollars " \
			"and exclude intercompany transactions.")
		intro.GetTextStyledElement().SetFontSize(11)
		intro.SetSpaceAfter(12)

		table = doc.AddTable()
		table.SetDefaultColumnWidth(90)
		table.SetDefaultRowHeight(18)
		table.SetBorder(0.75, 120, 120, 120)

		header_cells = ["Region", "Q1", "Q2", "Q3", "Total"]
		add_table_row_with_text(table, header_cells, true, 220, 230, 240)

		region_names = ["North America", "Europe", "Asia Pacific", "Latin America"]
		region_values = [
			[1250, 1410, 1580],
			[910, 980, 1120],
			[640, 720, 905],
			[310, 355, 390]]

		totals = [0, 0, 0]
		(0...region_names.length).each do |i|
			totals[0] += region_values[i][0]
			totals[1] += region_values[i][1]
			totals[2] += region_values[i][2]

			row_total = region_values[i].inject(:+)
			cells = [region_names[i]] + region_values[i].map { |v| v.to_s } + [row_total.to_s]
			add_table_row_with_text(table, cells, false, 255, 255, 255)
		end

		total_cells = ["All regions"] + totals.map { |t| t.to_s } + [totals.inject(:+).to_s]
		add_table_row_with_text(table, total_cells, true, 240, 240, 240)

		add_heading(doc, "Key observations", 14, 0, 51, 102)

		observations = doc.AddList()
		observations.SetNumberFormat(List::E_decimal, ".", true)
		observations.GetLabelStyle().SetBold(true)
		observations.AddItem().AddParagraph(
			"Revenue grew in every region, with Asia Pacific showing the strongest " \
			"quarter over quarter growth.")
		observations.AddItem().AddParagraph(
			"North America remains the largest market, contributing roughly 40% of " \
			"the total revenue.")

		risks = observations.AddItem()
		risks.AddParagraph("Risks that may affect the Q4 forecast:")
		risk_list = risks.AddList()
		risk_list.SetNumberFormat(List::E_lower_letter, ")", true)
		risk_list.AddItem().AddParagraph("Currency fluctuations in Latin America.")
		risk_list.AddItem().AddParagraph("Longer sales cycles in the enterprise segment.")

		#--- Page 2: landscape section with the chart ------------------------------
		chart_section = doc.AddSection()
		chart_section.SetPageSize(792, 612)        # landscape
		chart_section.SetMargins(54, 54, 54, 54)
		add_page_number_footer(chart_section, "Quarterly Revenue Report")

		add_heading(doc, "Revenue by region and quarter", 18, 0, 51, 102)

		# A chart is described by a JSON definition string.
		chart_definition = <<~CHART
			{
				"chartType": "bar",
				"title": "Revenue by region (thousands CAD)",
				"theme": "vivid",
				"data": {
					"categories": { "data": ["Q1", "Q2", "Q3"] },
					"series": [
						{ "name": { "data": ["North America"] }, "data": { "data": [1250, 1410, 1580] } },
						{ "name": { "data": ["Europe"] }, "data": { "data": [910, 980, 1120] } },
						{ "name": { "data": ["Asia Pacific"] }, "data": { "data": [640, 720, 905] } },
						{ "name": { "data": ["Latin America"] }, "data": { "data": [310, 355, 390] } }
					]
				}
			}
		CHART

		chart_para = doc.AddParagraph()
		chart_para.SetJustificationMode(Paragraph::E_text_justify_center)
		chart = chart_para.AddChart(560, 340, chart_definition)
		chart.SetWidth(600)
		chart.SetHeight(360)

		caption = doc.AddParagraph(
			"Figure 1: revenue distribution across the four sales regions.")
		caption.SetJustificationMode(Paragraph::E_text_justify_center)
		caption.GetTextStyledElement().SetItalic(true)
		caption.GetTextStyledElement().SetFontSize(10)

		puts "Report sections: " + doc.GetNumSections().to_s +
			", second section page width: " + doc.GetSection(1).GetPageWidth().to_s

		pdf = doc.PaginateToPDF()
		pdf.Save($outputPath + "created_report.pdf", SDFDoc::E_linearized)
		pdf.Close()
		puts "Saved created_report.pdf"
		return true
	rescue Exception => e
		puts e.to_s
		return false
	end
end

#-----------------------------------------------------------------------------------
# 3. A newsletter demonstrating shapes, shape text boxes, floats, hyperlinks and
#    paragraph styling such as indents, tab stops and justification.
#-----------------------------------------------------------------------------------
def create_newsletter()
	begin
		body_text =
			"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod " \
			"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, " \
			"quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo " \
			"consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse " \
			"cillum dolore eu fugiat nulla pariatur."

		doc = FlowDocument.new()
		doc.SetDefaultPageSize(612, 792)
		doc.SetDefaultMargins(60, 60, 60, 60)

		section = doc.GetCurrentSection()
		add_page_number_footer(section, "The Monthly Dispatch")

		# Masthead built from a rounded rectangle shape with a text box inside.
		masthead_para = doc.AddParagraph()
		masthead = masthead_para.AddShape(Shape::E_rectangle_rounded_corners, 492, 70)
		masthead.SetCornerRadius(10)
		masthead.SetBackgroundColor(0, 51, 102)
		masthead.SetOutlineColor(0, 31, 62)
		masthead.SetOutlineThickness(1.5)

		masthead_text = masthead.GetTextBox()
		masthead_title = masthead_text.AddParagraph("The Monthly Dispatch")
		masthead_title.SetJustificationMode(Paragraph::E_text_justify_center)
		masthead_style = masthead_title.GetTextStyledElement()
		masthead_style.SetFontSize(24)
		masthead_style.SetBold(true)
		masthead_style.SetTextColor(255, 255, 255)

		masthead_sub = masthead_text.AddParagraph("Issue 42 - March 2025")
		masthead_sub.SetJustificationMode(Paragraph::E_text_justify_center)
		masthead_sub.GetTextStyledElement().SetFontSize(10)
		masthead_sub.GetTextStyledElement().SetTextColor(200, 215, 230)

		# Lead story with a pull quote floated next to it.
		add_heading(doc, "Documents, assembled", 16, 0, 51, 102)

		lead = doc.AddParagraph()
		lead.SetJustificationMode(Paragraph::E_text_justify_left)
		lead.SetTextIndent(18)
		lead.SetSpaceAfter(10)

		pull_quote = lead.AddFloat()
		pull_quote.SetHorizontalReferencePoint(FloatContainer::E_reference_page)
		pull_quote.SetVerticalReferencePoint(FloatContainer::E_reference_paragraph)
		pull_quote.SetPositionX(380)
		pull_quote.SetPositionY(10)
		pull_quote.SetObstructionType(FloatContainer::E_square)

		quote_holder = pull_quote.AddParagraph()
		quote_box = quote_holder.AddShape(Shape::E_rectangle_rounded_corners, 190, 70)
		quote_box.SetCornerRadius(6)
		quote_box.SetBackgroundColor(240, 246, 255)
		quote_box.SetOutlineColor(0, 51, 102)
		quote_box.SetOutlineThickness(1.0)

		quote_text = quote_box.GetTextBox()
		quote = quote_text.AddParagraph(
			"\"The content tree lets you describe a document, not a page.\"")
		quote.SetStartIndent(6)
		quote.SetEndIndent(6)
		quote_style = quote.GetTextStyledElement()
		quote_style.SetFontSize(12)
		quote_style.SetItalic(true)
		quote_style.SetTextColor(0, 51, 102)

		lead.AddText(body_text)
		lead.AddText(" Read more about the API at ")
		link = lead.AddHyperlink("apryse.com", "https://www.apryse.com")
		link.GetTextStyledElement().SetTextColor(0, 102, 204)
		lead.AddText(".")

		# Two "columns" of text, created with complementary indents.
		column_left = doc.AddParagraph(body_text)
		column_left.SetStartIndent(0)
		column_left.SetEndIndent(260)
		column_left.GetTextStyledElement().SetFontSize(10)

		column_right = doc.AddParagraph(body_text)
		column_right.SetStartIndent(260)
		column_right.SetEndIndent(0)
		column_right.GetTextStyledElement().SetFontSize(10)

		# A schedule built with tab stops instead of a table.
		add_heading(doc, "Upcoming webinars", 14, 0, 51, 102)

		schedule = [
			["Apr 02", "Building documents from scratch", "R. Fischer"],
			["Apr 16", "Charts and data visualization", "M. Okafor"],
			["Apr 30", "Accessible PDF output", "L. Tanaka"]]

		schedule.each do |webinar|
			row = doc.AddParagraph()
			row.AddTabStop(80)
			row.AddTabStop(360)
			row.GetTextStyledElement().SetFontSize(10)
			row.AddText(webinar[0] + "\t" + webinar[1] + "\t" + webinar[2])
		end

		# A decorative arrow and a photo, both anchored in a single paragraph.
		gallery = doc.AddParagraph()
		gallery.SetSpaceBefore(16)
		gallery.SetJustificationMode(Paragraph::E_text_justify_center)

		arrow = gallery.AddShape(Shape::E_arrow, 90, 40)
		arrow.SetBackgroundColor(0, 153, 102)
		arrow.SetOutlineColor(0, 102, 68)
		arrow.SetOutlineThickness(1)
		arrow.SetArrowHeadSize(18)

		gallery.AddText("   ")
		gallery.AddImage(160, 120, $inputPath + "butterfly.png")

		pdf = doc.PaginateToPDF()
		pdf.Save($outputPath + "created_newsletter.pdf", SDFDoc::E_linearized)
		pdf.Close()
		puts "Saved created_newsletter.pdf"
		return true
	rescue Exception => e
		puts e.to_s
		return false
	end
end

#-----------------------------------------------------------------------------------
# 4. Post-processing: build a simple document and then walk its content tree,
#    restyling the elements that are found along the way.
#-----------------------------------------------------------------------------------

# Recursively walks the content tree, highlighting every second text run and
# outlining the cells of any table that is encountered.
def restyle_content_tree(node, highlight)
	itr = node.GetContentNodeIterator()
	while itr.HasNext()
		el = itr.Current()
		itr.Next()

		text_run = el.AsTextRun()
		if !text_run.nil?
			if highlight
				style = text_run.GetTextStyledElement()
				style.SetBold(true)
				style.SetBackgroundColor(255, 245, 180)
			end
			highlight = !highlight
			next
		end

		cell = el.AsTableCell()
		if !cell.nil?
			cell.SetBorder(0.5, 150, 150, 150)
		end

		child = el.AsContentNode()
		if !child.nil?
			highlight = restyle_content_tree(child, highlight)
		end
	end
	return highlight
end

def restyle_existing_content()
	begin
		doc = FlowDocument.new()
		doc.SetDefaultPageSize(612, 792)
		doc.SetDefaultMargins(72, 72, 72, 72)

		add_heading(doc, "Content tree post-processing", 18, 0, 51, 102)

		(0...4).each do |i|
			para = doc.AddParagraph()
			para.SetSpaceAfter(8)
			para.AddText("Sentence " + (i * 2).to_s + ". ")
			para.AddText("Sentence " + (i * 2 + 1).to_s + ". ")
			para.AddText("The style of these runs is decided after the fact.")
		end

		table = doc.AddTable()
		table.SetDefaultColumnWidth(150)
		table.SetDefaultRowHeight(16)

		(0...3).each do |row_index|
			row = table.AddTableRow()
			(0...3).each do |col_index|
				cell = row.AddTableCell()
				cell.AddParagraph("R" + row_index.to_s + " / C" + col_index.to_s)
			end
		end

		restyle_content_tree(doc, false)

		puts "Table has " + table.GetNumRows().to_s + " rows and " +
			table.GetNumColumns().to_s + " columns."

		pdf = doc.PaginateToPDF()
		pdf.Save($outputPath + "created_restyled.pdf", SDFDoc::E_linearized)
		pdf.Close()
		puts "Saved created_restyled.pdf"
		return true
	rescue Exception => e
		puts e.to_s
		return false
	end
end

def main()
	# The first step in every application using PDFNet is to initialize the
	# library. The library is usually initialized only once, but calling
	# Initialize() multiple times is also fine.
	PDFNet.Initialize(PDFTronLicense.Key)
	PDFNet.AddResourceSearchPath("../../../PDFNetC/Lib/")

	result = true
	result &= create_invoice()
	result &= create_report_with_chart()
	result &= create_newsletter()
	result &= restyle_existing_content()

	PDFNet.Terminate()

	if !result
		puts "Tests FAILED!!!\n=========="
		return
	end
	puts "Tests successful.\n=========="
end

main()

```

{% endcode %}
{% endtab %}

{% tab title="Go" %}

```go
//---------------------------------------------------------------------------------------
// Copyright (c) 2001-2026 by Apryse Software Inc. All Rights Reserved.
// Consult LICENSE.txt regarding license information.
//---------------------------------------------------------------------------------------

package main

import (
	"flag"
	"fmt"
	"testing"

	. "github.com/pdftron/pdftron-go/v2"
)

var licenseKey string
var modulePath string

func init() {
	flag.StringVar(&licenseKey, "license", "", "Apryse SDK license")
	flag.StringVar(&modulePath, "modulePath", "", "Module path for Apryse resources")
}

//-----------------------------------------------------------------------------------------
// The following sample illustrates how to use the FlowDocument API to create documents from
// scratch. Instead of positioning every element on a page, the content is described as a
// tree of paragraphs, tables, lists, shapes, charts and floats, which is then paginated
// into a PDF document.
//
// Each function below produces one document:
//   * an invoice          - tables, cell merging, styling, headers/footers, images
//   * a report            - sections, page setup, lists, charts, page numbers
//   * a newsletter        - shapes, shape text boxes, floats, hyperlinks, tab stops
//   * a content tree walk - post-processing of an existing content tree
//-----------------------------------------------------------------------------------------

// Relative path to the folder containing the test files.
var inputPath = "../TestFiles/"
var outputPath = "../TestFiles/Output/"

func moneyToString(val float64) string {
	return fmt.Sprintf("$%.2f", val)
}

//-----------------------------------------------------------------------------------
// Small helpers shared by the samples below.
//-----------------------------------------------------------------------------------

// Adds a paragraph with the given text and applies a simple heading style.
func addHeading(container ContentContainer, text string, fontSize float64,
	red byte, green byte, blue byte) Paragraph {
	heading := container.AddParagraph(text)
	style := heading.GetTextStyledElement()
	style.SetFontSize(fontSize)
	style.SetBold(true)
	style.SetTextColor(red, green, blue)
	heading.SetSpaceBefore(12)
	heading.SetSpaceAfter(6)
	return heading
}

// Adds a single table row filled with the given texts.
func addTableRowWithText(table Table, texts []string, bold bool,
	backRed byte, backGreen byte, backBlue byte) TableRow {
	row := table.AddTableRow()
	for i, text := range texts {
		cell := row.AddTableCell()
		cell.SetBackgroundColor(backRed, backGreen, backBlue)
		cell.SetVerticalAlignment(TableCellE_alignment_center)

		para := cell.AddParagraph(text)
		para.SetStartIndent(3)
		style := para.GetTextStyledElement()
		style.SetFontSize(10)
		style.SetBold(bold)

		// Right align everything but the first (description) column.
		if i == 0 {
			para.SetJustificationMode(ParagraphE_text_justify_left)
		} else {
			para.SetJustificationMode(ParagraphE_text_justify_right)
		}
	}
	return row
}

// Adds a "Page X of Y" footer to the given section, for all page groups.
func addPageNumberFooter(section Section, title string) {
	groups := []PdftronLayoutSectionPageGroup{
		SectionE_first_page, SectionE_even_pages, SectionE_odd_pages}

	for _, group := range groups {
		footer := section.GetOrCreateFooter(group)

		para := footer.AddParagraph()
		para.SetJustificationMode(ParagraphE_text_justify_center)

		style := para.GetTextStyledElement()
		style.SetFontSize(9)
		style.SetTextColor(120, 120, 120)
		style.SetItalic(true)

		para.AddText(title + "   |   Page ")
		para.AddPageNumber(PageNumberE_current_page)
		para.AddText(" of ")
		para.AddPageNumber(PageNumberE_total_pages)
	}
}

//-----------------------------------------------------------------------------------
// (1) An invoice: a header with a logo float, an addresses table, a line item table
// with merged cells and a totals block.
//-----------------------------------------------------------------------------------

func createInvoice() {
	doc := NewFlowDocument()
	doc.SetDefaultPageSize(612, 792) // US Letter, in points.
	doc.SetDefaultMargins(54, 54, 54, 54)

	section := doc.GetCurrentSection()

	// --- Header ---------------------------------------------------------------
	header := section.GetOrCreateHeader(SectionE_odd_pages)
	headerPara := header.AddParagraph("SYH Supply Co.")
	headerPara.SetJustificationMode(ParagraphE_text_justify_right)
	headerPara.GetTextStyledElement().SetFontSize(9)
	headerPara.GetTextStyledElement().SetTextColor(120, 120, 120)

	addPageNumberFooter(section, "Invoice INV-2025-0042")

	// --- Title, with the company logo floated to the right --------------------
	title := doc.AddParagraph()

	// A float takes its content out of the main flow and positions it relative to
	// a reference point, with the document content wrapped around it.
	logoFloat := title.AddFloat()
	logoFloat.SetHorizontalReferencePoint(FloatE_reference_page)
	logoFloat.SetVerticalReferencePoint(FloatE_reference_paragraph)
	logoFloat.SetPositionX(400)
	logoFloat.SetPositionY(0)
	logoFloat.SetObstructionType(FloatE_square)
	// AddImage is overloaded, so the sizes need an explicit float64 type here.
	logoFloat.AddParagraph().AddImage(float64(96), float64(96), inputPath+"logo_red.png")

	title.AddText("INVOICE")
	titleStyle := title.GetTextStyledElement()
	titleStyle.SetFontSize(28)
	titleStyle.SetBold(true)
	titleStyle.SetTextColor(0, 51, 102)

	subtitle := doc.AddParagraph(
		"Invoice #INV-2025-0042\nIssued: March 14, 2025\nDue: April 13, 2025")
	subtitle.GetTextStyledElement().SetFontSize(10)
	subtitle.SetSpaceAfter(18)

	// --- Bill to / ship to ----------------------------------------------------
	addresses := doc.AddTable()
	addresses.SetDefaultColumnWidth(240)
	addresses.SetDefaultRowHeight(16)

	addressRow := addresses.AddTableRow()
	addressTitles := []string{"BILL TO", "SHIP TO"}
	addressBodies := []string{
		"Contoso Ltd.\n123 Maple Street\nVancouver, BC V6B 1A1\nCanada",
		"Contoso Warehouse #4\n980 Industrial Way\nBurnaby, BC V5J 3J1\nCanada"}

	for i := 0; i < 2; i++ {
		cell := addressRow.AddTableCell()
		cell.SetBackgroundColor(240, 244, 248)
		cell.SetBorder(0.5, 200, 210, 220)

		caption := cell.AddParagraph(addressTitles[i])
		caption.SetStartIndent(3)
		captionStyle := caption.GetTextStyledElement()
		captionStyle.SetFontSize(9)
		captionStyle.SetBold(true)
		captionStyle.SetTextColor(0, 51, 102)

		body := cell.AddParagraph(addressBodies[i])
		body.SetStartIndent(3)
		body.GetTextStyledElement().SetFontSize(10)
	}

	doc.AddParagraph(" ")

	// --- Line items -----------------------------------------------------------
	items := doc.AddTable()
	items.SetDefaultColumnWidth(120)
	items.SetDefaultRowHeight(18)
	items.SetBorder(0.75, 0, 51, 102)

	headers := []string{"Description", "Qty", "Unit price", "Amount"}
	headerRow := addTableRowWithText(items, headers, true, 0, 51, 102)

	// Make the header row text white by walking the row we have just created.
	cellItr := headerRow.GetContentNodeIterator()
	for cellItr.HasNext() {
		if cell := cellItr.Current().AsTableCell(); cell != nil {
			paraItr := cell.GetContentNodeIterator()
			for paraItr.HasNext() {
				if para := paraItr.Current().AsParagraph(); para != nil {
					para.GetTextStyledElement().SetTextColor(255, 255, 255)
				}
				paraItr.Next()
			}
		}
		cellItr.Next()
	}

	lineDescriptions := []string{
		"Height adjustable desk, oak finish",
		"Ergonomic office chair",
		"Meeting room whiteboard, 180 cm",
		"On-site assembly and delivery"}
	lineQuantities := []int{2, 5, 1, 1}
	linePrices := []float64{4800.00, 950.00, 1200.00, 2500.00}
	numLineItems := len(lineDescriptions)

	subtotal := 0.0
	for i := 0; i < numLineItems; i++ {
		amount := float64(lineQuantities[i]) * linePrices[i]
		subtotal += amount

		cells := []string{
			lineDescriptions[i],
			fmt.Sprintf("%d", lineQuantities[i]),
			moneyToString(linePrices[i]),
			moneyToString(amount)}

		// Alternating row background for readability.
		if i%2 == 0 {
			addTableRowWithText(items, cells, false, 255, 255, 255)
		} else {
			addTableRowWithText(items, cells, false, 240, 244, 248)
		}
	}

	tax := subtotal * 0.12

	totalLabels := []string{"Subtotal", "GST/PST (12%)", "Total due (CAD)"}
	totalValues := []float64{subtotal, tax, subtotal + tax}
	totalBold := []bool{false, false, true}

	for i := 0; i < len(totalLabels); i++ {
		cells := []string{"", "", totalLabels[i], moneyToString(totalValues[i])}
		addTableRowWithText(items, cells, totalBold[i], 255, 255, 255)

		// Merge the two empty leading cells of the totals row.
		items.GetTableCell(0, uint(numLineItems+1+i)).MergeCellsRight(1)
	}

	notes := doc.AddParagraph(
		"Payment is due within 30 days. Late payments are subject to a 1.5% monthly " +
			"interest charge. Please reference the invoice number with your payment.")
	notes.SetSpaceBefore(24)
	notes.GetTextStyledElement().SetFontSize(9)
	notes.GetTextStyledElement().SetItalic(true)
	notes.GetTextStyledElement().SetTextColor(90, 90, 90)

	signature := doc.AddParagraph()
	signature.SetSpaceBefore(18)
	signature.AddImage(float64(140), float64(50), inputPath+"signature.jpg")
	signature.AddText("\nAuthorized signature")

	pdf := doc.PaginateToPDF()
	pdf.Save(outputPath+"created_invoice.pdf", uint(SDFDocE_linearized))
	pdf.Close()
	fmt.Println("Saved created_invoice.pdf")
}

//-----------------------------------------------------------------------------------
// (2) A two page report. The first page (portrait) holds a data table, the second one
// is a separate landscape section holding a chart.
//-----------------------------------------------------------------------------------

func createReportWithChart() {
	doc := NewFlowDocument()
	doc.SetDefaultPageSize(612, 792)
	doc.SetDefaultMargins(72, 72, 72, 72)

	//--- Page 1: portrait section with the data table --------------------------
	dataSection := doc.GetCurrentSection()
	addPageNumberFooter(dataSection, "Quarterly Revenue Report")

	addHeading(doc, "Quarterly Revenue Report 2025", 22, 0, 51, 102)

	intro := doc.AddParagraph(
		"The table below summarizes the revenue by region for the first three quarters " +
			"of the fiscal year. All figures are given in thousands of Canadian dollars " +
			"and exclude intercompany transactions.")
	intro.GetTextStyledElement().SetFontSize(11)
	intro.SetSpaceAfter(12)

	table := doc.AddTable()
	table.SetDefaultColumnWidth(90)
	table.SetDefaultRowHeight(18)
	table.SetBorder(0.75, 120, 120, 120)

	headerCells := []string{"Region", "Q1", "Q2", "Q3", "Total"}
	addTableRowWithText(table, headerCells, true, 220, 230, 240)

	regionNames := []string{"North America", "Europe", "Asia Pacific", "Latin America"}
	regionValues := [][]int{
		{1250, 1410, 1580},
		{910, 980, 1120},
		{640, 720, 905},
		{310, 355, 390}}

	totals := []int{0, 0, 0}
	for i := 0; i < len(regionNames); i++ {
		totals[0] += regionValues[i][0]
		totals[1] += regionValues[i][1]
		totals[2] += regionValues[i][2]

		rowTotal := regionValues[i][0] + regionValues[i][1] + regionValues[i][2]
		cells := []string{
			regionNames[i],
			fmt.Sprintf("%d", regionValues[i][0]),
			fmt.Sprintf("%d", regionValues[i][1]),
			fmt.Sprintf("%d", regionValues[i][2]),
			fmt.Sprintf("%d", rowTotal)}
		addTableRowWithText(table, cells, false, 255, 255, 255)
	}

	totalCells := []string{"All regions",
		fmt.Sprintf("%d", totals[0]),
		fmt.Sprintf("%d", totals[1]),
		fmt.Sprintf("%d", totals[2]),
		fmt.Sprintf("%d", totals[0]+totals[1]+totals[2])}
	addTableRowWithText(table, totalCells, true, 240, 240, 240)

	addHeading(doc, "Key observations", 14, 0, 51, 102)

	observations := doc.AddList()
	observations.SetNumberFormat(ListE_decimal, ".", true)
	observations.GetLabelStyle().SetBold(true)
	observations.AddItem().AddParagraph(
		"Revenue grew in every region, with Asia Pacific showing the strongest " +
			"quarter over quarter growth.")
	observations.AddItem().AddParagraph(
		"North America remains the largest market, contributing roughly 40% of " +
			"the total revenue.")

	risks := observations.AddItem()
	risks.AddParagraph("Risks that may affect the Q4 forecast:")
	riskList := risks.AddList()
	riskList.SetNumberFormat(ListE_lower_letter, ")", true)
	riskList.AddItem().AddParagraph("Currency fluctuations in Latin America.")
	riskList.AddItem().AddParagraph("Longer sales cycles in the enterprise segment.")

	//--- Page 2: landscape section with the chart ------------------------------
	chartSection := doc.AddSection()
	chartSection.SetPageSize(792, 612) // landscape
	chartSection.SetMargins(54, 54, 54, 54)
	addPageNumberFooter(chartSection, "Quarterly Revenue Report")

	addHeading(doc, "Revenue by region and quarter", 18, 0, 51, 102)

	// A chart is described by a JSON definition string.
	chartDefinition := `{
		"chartType": "bar",
		"title": "Revenue by region (thousands CAD)",
		"theme": "vivid",
		"data": {
			"categories": { "data": ["Q1", "Q2", "Q3"] },
			"series": [
				{ "name": { "data": ["North America"] }, "data": { "data": [1250, 1410, 1580] } },
				{ "name": { "data": ["Europe"] }, "data": { "data": [910, 980, 1120] } },
				{ "name": { "data": ["Asia Pacific"] }, "data": { "data": [640, 720, 905] } },
				{ "name": { "data": ["Latin America"] }, "data": { "data": [310, 355, 390] } }
			]
		}
	}`

	chartPara := doc.AddParagraph()
	chartPara.SetJustificationMode(ParagraphE_text_justify_center)
	chart := chartPara.AddChart(560, 340, chartDefinition)
	chart.SetWidth(600)
	chart.SetHeight(360)

	caption := doc.AddParagraph(
		"Figure 1: revenue distribution across the four sales regions.")
	caption.SetJustificationMode(ParagraphE_text_justify_center)
	caption.GetTextStyledElement().SetItalic(true)
	caption.GetTextStyledElement().SetFontSize(10)

	fmt.Printf("Report sections: %d, second section page width: %g\n",
		doc.GetNumSections(), doc.GetSection(1).GetPageWidth())

	pdf := doc.PaginateToPDF()
	pdf.Save(outputPath+"created_report.pdf", uint(SDFDocE_linearized))
	pdf.Close()
	fmt.Println("Saved created_report.pdf")
}

//-----------------------------------------------------------------------------------
// (3) A newsletter demonstrating shapes, shape text boxes, floats, hyperlinks and
// paragraph styling such as indents, tab stops and justification.
//-----------------------------------------------------------------------------------

func createNewsletter() {
	bodyText := "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod " +
		"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, " +
		"quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo " +
		"consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse " +
		"cillum dolore eu fugiat nulla pariatur."

	doc := NewFlowDocument()
	doc.SetDefaultPageSize(612, 792)
	doc.SetDefaultMargins(60, 60, 60, 60)

	section := doc.GetCurrentSection()
	addPageNumberFooter(section, "The Monthly Dispatch")

	// Masthead built from a rounded rectangle shape with a text box inside.
	mastheadPara := doc.AddParagraph()
	masthead := mastheadPara.AddShape(ShapeE_rectangle_rounded_corners, 492, 70)
	masthead.SetCornerRadius(10)
	masthead.SetBackgroundColor(0, 51, 102)
	masthead.SetOutlineColor(0, 31, 62)
	masthead.SetOutlineThickness(1.5)

	mastheadText := masthead.GetTextBox()
	mastheadTitle := mastheadText.AddParagraph("The Monthly Dispatch")
	mastheadTitle.SetJustificationMode(ParagraphE_text_justify_center)
	mastheadStyle := mastheadTitle.GetTextStyledElement()
	mastheadStyle.SetFontSize(24)
	mastheadStyle.SetBold(true)
	mastheadStyle.SetTextColor(255, 255, 255)

	mastheadSub := mastheadText.AddParagraph("Issue 42 - March 2025")
	mastheadSub.SetJustificationMode(ParagraphE_text_justify_center)
	mastheadSub.GetTextStyledElement().SetFontSize(10)
	mastheadSub.GetTextStyledElement().SetTextColor(200, 215, 230)

	// Lead story with a pull quote floated next to it.
	addHeading(doc, "Documents, assembled", 16, 0, 51, 102)

	lead := doc.AddParagraph()
	lead.SetJustificationMode(ParagraphE_text_justify_left)
	lead.SetTextIndent(18)
	lead.SetSpaceAfter(10)

	pullQuote := lead.AddFloat()
	pullQuote.SetHorizontalReferencePoint(FloatE_reference_page)
	pullQuote.SetVerticalReferencePoint(FloatE_reference_paragraph)
	pullQuote.SetPositionX(380)
	pullQuote.SetPositionY(10)
	pullQuote.SetObstructionType(FloatE_square)

	quoteHolder := pullQuote.AddParagraph()
	quoteBox := quoteHolder.AddShape(ShapeE_rectangle_rounded_corners, 190, 70)
	quoteBox.SetCornerRadius(6)
	quoteBox.SetBackgroundColor(240, 246, 255)
	quoteBox.SetOutlineColor(0, 51, 102)
	quoteBox.SetOutlineThickness(1.0)

	quoteText := quoteBox.GetTextBox()
	quote := quoteText.AddParagraph(
		"\"The content tree lets you describe a document, not a page.\"")
	quote.SetStartIndent(6)
	quote.SetEndIndent(6)
	quoteStyle := quote.GetTextStyledElement()
	quoteStyle.SetFontSize(12)
	quoteStyle.SetItalic(true)
	quoteStyle.SetTextColor(0, 51, 102)

	lead.AddText(bodyText)
	lead.AddText(" Read more about the API at ")
	link := lead.AddHyperlink("apryse.com", "https://www.apryse.com")
	link.GetTextStyledElement().SetTextColor(0, 102, 204)
	lead.AddText(".")

	// Two "columns" of text, created with complementary indents.
	columnLeft := doc.AddParagraph(bodyText)
	columnLeft.SetStartIndent(0)
	columnLeft.SetEndIndent(260)
	columnLeft.GetTextStyledElement().SetFontSize(10)

	columnRight := doc.AddParagraph(bodyText)
	columnRight.SetStartIndent(260)
	columnRight.SetEndIndent(0)
	columnRight.GetTextStyledElement().SetFontSize(10)

	// A schedule built with tab stops instead of a table.
	addHeading(doc, "Upcoming webinars", 14, 0, 51, 102)

	schedule := [][]string{
		{"Apr 02", "Building documents from scratch", "R. Fischer"},
		{"Apr 16", "Charts and data visualization", "M. Okafor"},
		{"Apr 30", "Accessible PDF output", "L. Tanaka"}}

	for _, webinar := range schedule {
		row := doc.AddParagraph()
		row.AddTabStop(80)
		row.AddTabStop(360)
		row.GetTextStyledElement().SetFontSize(10)
		row.AddText(webinar[0] + "\t" + webinar[1] + "\t" + webinar[2])
	}

	// A decorative arrow and a photo, both anchored in a single paragraph.
	gallery := doc.AddParagraph()
	gallery.SetSpaceBefore(16)
	gallery.SetJustificationMode(ParagraphE_text_justify_center)

	arrow := gallery.AddShape(ShapeE_arrow, 90, 40)
	arrow.SetBackgroundColor(0, 153, 102)
	arrow.SetOutlineColor(0, 102, 68)
	arrow.SetOutlineThickness(1)
	arrow.SetArrowHeadSize(18)

	gallery.AddText("   ")
	gallery.AddImage(float64(160), float64(120), inputPath+"butterfly.png")

	pdf := doc.PaginateToPDF()
	pdf.Save(outputPath+"created_newsletter.pdf", uint(SDFDocE_linearized))
	pdf.Close()
	fmt.Println("Saved created_newsletter.pdf")
}

//-----------------------------------------------------------------------------------
// (4) Post-processing: build a simple document and then walk its content tree,
// restyling the elements that are found along the way.
//-----------------------------------------------------------------------------------

// Recursively walks the content tree, highlighting every second text run and
// outlining the cells of any table that is encountered.
func restyleContentTree(node ContentNode, highlight bool) bool {
	itr := node.GetContentNodeIterator()
	for itr.HasNext() {
		el := itr.Current()
		itr.Next()

		if textRun := el.AsTextRun(); textRun != nil {
			if highlight {
				style := textRun.GetTextStyledElement()
				style.SetBold(true)
				style.SetBackgroundColor(255, 245, 180)
			}
			highlight = !highlight
			continue
		}

		if cell := el.AsTableCell(); cell != nil {
			cell.SetBorder(0.5, 150, 150, 150)
		}

		if child := el.AsContentNode(); child != nil {
			highlight = restyleContentTree(child, highlight)
		}
	}
	return highlight
}

func restyleExistingContent() {
	doc := NewFlowDocument()
	doc.SetDefaultPageSize(612, 792)
	doc.SetDefaultMargins(72, 72, 72, 72)

	addHeading(doc, "Content tree post-processing", 18, 0, 51, 102)

	for i := 0; i < 4; i++ {
		para := doc.AddParagraph()
		para.SetSpaceAfter(8)
		para.AddText(fmt.Sprintf("Sentence %d. ", i*2))
		para.AddText(fmt.Sprintf("Sentence %d. ", i*2+1))
		para.AddText("The style of these runs is decided after the fact.")
	}

	table := doc.AddTable()
	table.SetDefaultColumnWidth(150)
	table.SetDefaultRowHeight(16)

	for rowIndex := 0; rowIndex < 3; rowIndex++ {
		row := table.AddTableRow()
		for colIndex := 0; colIndex < 3; colIndex++ {
			cell := row.AddTableCell()
			cell.AddParagraph(fmt.Sprintf("R%d / C%d", rowIndex, colIndex))
		}
	}

	restyleContentTree(doc, false)

	fmt.Printf("Table has %d rows and %d columns.\n",
		table.GetNumRows(), table.GetNumColumns())

	pdf := doc.PaginateToPDF()
	pdf.Save(outputPath+"created_restyled.pdf", uint(SDFDocE_linearized))
	pdf.Close()
	fmt.Println("Saved created_restyled.pdf")
}

func TestDocumentCreation(t *testing.T) {
	PDFNetInitialize(licenseKey)
	PDFNetSetResourcesPath(modulePath)
	defer PDFNetTerminate()

	createInvoice()
	createReportWithChart()
	createNewsletter()
	restyleExistingContent()

	fmt.Println("Done.")
}

```

{% endtab %}
{% endtabs %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.apryse.com/core/get-started/samples/documentcreation.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
