Skip to main content
Skip table of contents

Creating pages with the ILayout class


Introduction

It has always been possible to add text and images to a page with Mako, but it required the use of some fairly low-level APIs to do so. Laying out an entire page could be an onerous task.

Enter the ILayout class. Backed by a layout engine, it is specifically designed to simplify creating new page content, making it far easier to add left, right or fully justified paragraphs of text. Control is provided over fonts, their color, spacing and more. Images can be added too.

Layout process

The process begins by creating an instance of the ILayout class:

CPP
const IJawsMakoPtr mako = IJawsMako::create();
IJawsMako::enableAllFeatures(mako);
...
// Create a layout
ILayoutPtr layout = ILayout::create(mako);
C#
var mako = IJawsMako.create();
IJawsMako.enableAllFeatures(mako);
...
// Create a layout
var layout = ILayout.create(mako);
JAVA
var mako = IJawsMako.create();
IJawsMako.enableAllFeatures(mako);
...
// Create a layout
var layout = ILayout.create(mako);





The process continues by adding frames, defined by FRect() structs that define the top-left corner (x, y) and their width and height (dX, dY). Units are XPS units, (1/96th inch), the default for Mako

gfgfgfg

The final step is to generate Mako DOM (Document Object Model) objects from the layout, which can be added to an IDOMFixedPage().

CPP
// Add to the page
fixedPage->appendChild(layout->layout(paragraphs));
C#
// Add to the page
fixedPage.appendChild(layout.layout(paragraphs));
JAVA
// Add to the page
fixedPage.appendChild(layout.layout(paragraphs));


More stuff to go here

Complete examples

CPP
/* --------------------------------------------------------------------------------
*  <copyright file="Main.cpp" company="Global Graphics Software Ltd">
*    Copyright (c) 2023 Global Graphics Software Ltd. All rights reserved.
*  </copyright>
*  <summary>
*    This example is provided on an "as is" basis and without warranty of any kind.
*    Global Graphics Software Ltd. does not warrant or make any representations
*    regarding the use or results of use of this example.
*  </summary>
* ---------------------------------------------------------------------------------
*/

#include <iostream>
#include <filesystem>
#include <cassert>
#include <jawsmako/jawsmako.h>
#include <jawsmako/layout.h>
#include <jawsmako/pdfoutput.h>
#include <edl/idommetadata.h>

using namespace std;
namespace fs = std::filesystem;
using namespace JawsMako;
using namespace EDL;

typedef std::pair<IDOMFontOpenTypePtr, uint32> otf;

#define MM2XPS(value) ((value) / 25.4 * 96.0)
#define PT2XPS(value) ((value) / 72.0 * 96.0)

// Get font
otf getOpenTypeFont(const IJawsMakoPtr& mako, const std::vector<U8String>& fontsToTry)
{
    // Pick a font
    IDOMFontPtr font;
    uint32 fontIndex = 0; // In case the font is inside a TrueType collection
    for (const auto& fontToTry : fontsToTry)
    {
        try
        {
            font = mako->findFont(fontToTry, fontIndex);
            break;
        }
        catch (IEDLError&)
        {
            // Bad or missing font - default to Arial
            font = mako->findFont("Arial", fontIndex);
        }
    }
    if (font->getFontType() == IDOMFont::eFontTypeOpenType)
        return otf{ edlobj2IDOMFontOpenType(font), fontIndex };

    return otf{ nullptr, 0 };
}

// Draw a path using a solid color brush with a thickness of 1
IDOMPathNodePtr drawFrame(const IJawsMakoPtr& mako, const FRect& frameBounds)
{
    const double margin = 4;
    const auto border = FRect(frameBounds.x - margin, frameBounds.y - margin, frameBounds.dX + margin * 2, frameBounds.dY + margin * 2);
    const auto solidBrush = IDOMSolidColorBrush::createSolidRgb(mako, 0.8f, 0.8f, 0.8f);
    const auto path = IDOMPathNode::createStroked(mako, IDOMPathGeometry::create(mako, border), solidBrush);
    path->setStrokeThickness(1);
    return path;
}

// Add a layout, draw an outline if requested
void addFrame(const IJawsMakoPtr& mako, const ILayoutPtr& layout, const IDOMFixedPagePtr& fixedPage, FRect positionAndSize, bool drawOutline = false)
{
    layout->addFrame(ILayoutFrame::create(positionAndSize));

    // Draw a box where the frame is going to be placed
    if (drawOutline)
        fixedPage->appendChild(drawFrame(mako, positionAndSize));
}

// Create paragraph
ILayoutParagraphPtr createParagraph(double spaceAfter = 0.0, 
                                    ILayoutParagraph::eHorizontalAlignment justification = ILayoutParagraph::eHALeft,
                                    double leading = 1.0,
                                    double spaceBefore = 0.0)
{
    const auto templateParagraph = ILayoutParagraph::create(justification);
    if (spaceAfter > 0.0)
        templateParagraph->setSpacing(spaceAfter);
    if (spaceBefore > 0.0)
        templateParagraph->setSpacing(spaceBefore, true);
    templateParagraph->setLeading(leading);
    return templateParagraph;
}

// Get image and scale proportionately to specified width and/or height
IDOMImagePtr getImage(const IJawsMakoPtr& mako, const U8String& imageFile,
                      double& width, double& height)
{
    IDOMImagePtr image = IDOMImagePtr();
    const auto imagePath = fs::path(imageFile);
    if (!exists(imagePath))
    {
        std::string msg = "Image file ";
        msg += imageFile;
        msg += " not found.";
        throw std::exception(msg.c_str());
    }

    if (imagePath.extension() == ".jpg")
        image = IDOMJPEGImage::create(mako, IInputStream::createFromFile(mako, imageFile));

    if (imagePath.extension() == ".png")
        image = IDOMPNGImage::create(mako, IInputStream::createFromFile(mako, imageFile));

    if (imagePath.extension() == ".tif")
        image = IDOMTIFFImage::create(mako, IInputStream::createFromFile(mako, imageFile));

    if (!image)
    {
        std::string msg = "Image file ";
        msg += imageFile;
        msg += " could not be loaded.";
        throw std::exception(msg.c_str());
    }

    const auto frame = image->getImageFrame(mako);
    const double imageWidth = frame->getWidth();
    const double imageHeight = frame->getHeight();
    const double aspectRatio = imageWidth / imageHeight;

    // If neither dimensions have been set then return the image with its 1:1 pixel dimensions
    if (width == 0.0 && height == 0.0)
    {
        width = imageWidth;
        height = imageHeight;
        return image;
    }

    // If both dimensions have been set then we're done
    if (width > 0.0 && height > 0.0)
        return image;

    // Calculate the missing dimension
    if (height == 0.0)
        height = width / aspectRatio;
    else
        width = height * aspectRatio;

    return image;
}

int main(int argc, char** argv)
{
    U8String lorem = "\nLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\nUt 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. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
    try
    {
        const auto mako = IJawsMako::create();
        IJawsMako::enableAllFeatures(mako);

        // Get a page ready to accept some DOM
        const auto assembly = IDocumentAssembly::create(mako);
        const auto document = IDocument::create(mako);
        assembly->appendDocument(document);
        const auto page = IPage::create(mako);
        document->appendPage(page);
        const auto fixedPage = IDOMFixedPage::create(mako);
        page->setContent(fixedPage);

        // Create a layout
        const auto layout = ILayout::create(mako);

        // Add some frames to hold content
        bool drawBorder = false;
        const uint32 margin = MM2XPS(12);
        const auto widthWithMargins = fixedPage->getWidth() - margin * 2;
        addFrame(mako, layout, fixedPage, FRect(margin, margin, widthWithMargins, MM2XPS(40)), drawBorder);                                                // Banner
        addFrame(mako, layout, fixedPage, FRect(margin, margin + MM2XPS(40), widthWithMargins / 3 - MM2XPS(2), MM2XPS(83)), drawBorder);                   // Sidebar with 2mm right margin
        addFrame(mako, layout, fixedPage, FRect(margin + widthWithMargins / 3, margin + MM2XPS(40), widthWithMargins / 3 * 2, MM2XPS(83)), drawBorder);    // Pic
        addFrame(mako, layout, fixedPage, FRect(margin, margin + MM2XPS(125), widthWithMargins, MM2XPS(155)), drawBorder);                                 // Body

        // Get a font
        const auto titleFont = getOpenTypeFont(mako, { "Arial Black" });
        assert(titleFont.first);
        const auto bodyFont = getOpenTypeFont(mako, { "DejaVu Sans Book", "Gill Sans", "Arial" });
        assert(bodyFont.first);
        const auto bodyBold = getOpenTypeFont(mako, { "DejaVu Sans Book Bold", "Gill Sans Bold", "Arial Bold" });
        assert(bodyBold.first);

        // Create a colour
        const auto darkBlue = IDOMColor::createSolidRgb(mako, 0.0f, 0.0f, 0.5f);
        
        CEDLSysStringVect paragraphCopy;

        paragraphCopy.append("Travel Blog");
        paragraphCopy.append("\xAThe beauty of where I found myself inspired me to write this.");
        paragraphCopy.append("As the sun rose over the horizon, casting a warm golden hue across the landscape, a breathtaking scene unfolded before the onlooker's eyes. Standing at the water's edge, one's gaze extended out over a pristine lake that shimmered like a sheet of glass, reflecting the majestic beauty that surrounded it. The lake seemed to hold its breath, mirroring with utmost precision the awe-inspiring sight that lay just beyond its tranquil surface.");
        paragraphCopy.append("Stretching magnificently into the distance, a range of rocky mountains dominated the backdrop. Each peak soared towards the heavens, their rugged surfaces etched by the passage of time and the forces of nature. The mountains stood resolute, a testament to the immense power and grandeur of the natural world. Their colors shifted subtly, painted with a breathtaking array of earthy tones \xE2\x80\x94 from deep siennas and ochres to soft grays and greens \xE2\x80\x94 all framed by the azure expanse of the sky.");
        paragraphCopy.append("The sky itself seemed to be a canvas of its own, an ever-changing masterpiece of color and light. Towering cumulus clouds danced gracefully, casting dramatic shadows that gently caressed the mountains' slopes. The fluffy white clouds looked as though they were soft pillows, inviting the weary soul to rest upon their tender embrace. The sky stretched boundlessly, seeming to touch the very edges of the earth, a reminder of the vastness of the universe and the limitless possibilities that lay beyond the human imagination.");
        paragraphCopy.append("As a gentle breeze whispered through the air, ripples formed across the surface of the lake, momentarily distorting the mirror-like reflection. The tiny waves moved in rhythmic harmony, lending an animated quality to the otherwise still waters. With each passing gust, the mountains appeared to ripple across the lake's surface, as though a magnificent magic spell had been cast upon the land.");
        paragraphCopy.append("In the distance, a lone boat glided silently across the lake, its presence adding a touch of serenity to the already tranquil scene. The boat's wake created a delicate trail on the water, a fleeting mark of human existence in the midst of the grandeur of nature. It served as a reminder of the delicate balance between mankind and the Earth, and how nature's beauty can be both admired and preserved.");
        paragraphCopy.append("Birds soared overhead, their graceful silhouettes weaving intricate patterns against the sky. They navigated the currents with effortless grace, adding life to the enchanting tableau. The soft cries of the birds mixed harmoniously with the gentle lapping of the lake's water against the shore, creating a soothing symphony that echoed through the air.");
        paragraphCopy.append("");

        // A vector to point to each paragraph to be added to the frame(s)
        CLayoutParagraphVect paragraphs;
        uint32 paraIndex = 0;

        // Title
        paragraphs.append(createParagraph(0, ILayoutParagraph::eHACenter, 1, 0));
        auto run = ILayoutTextRun::create(paragraphCopy[0], titleFont.first, titleFont.second, PT2XPS(60));
        run->setColor(darkBlue);
        paragraphs[paraIndex]->addRun(run);

        // Intro
        paragraphs.append(createParagraph(9, ILayoutParagraph::eHAJustified, 1.3));
        paragraphs[++paraIndex]->addRun(
            ILayoutTextRun::create(paragraphCopy[1], bodyBold.first, bodyBold.second, 14.0));

        // Side bar
        paragraphs.append(createParagraph(7, ILayoutParagraph::eHAJustified, 1.2));
        paragraphs[++paraIndex]->addRun(
            ILayoutTextRun::create(paragraphCopy[2], bodyFont.first, bodyFont.second, 14.0));

        // Break to next frame
        paragraphs.append(ILayoutParagraph::create());
        paragraphs[++paraIndex]->addRun(ILayoutTextRun::create("\xA", bodyFont.first, bodyFont.second, 4));

        // Picture
        double width = widthWithMargins / 3 * 2, height = 0;
        const auto mountain = getImage(mako, R"(..\..\Images\Mountain.jpg)", width, height);
        paragraphs[paraIndex]->addRun(ILayoutImageRun::create(mountain, width, height));

        for (uint32 paraCopyIndex = 3; paraCopyIndex < 8; ++paraCopyIndex)
        {
            paragraphs.append(createParagraph(7, ILayoutParagraph::eHAJustified, 1.2));
            paragraphs[++paraIndex]->addRun(
                ILayoutTextRun::create(paragraphCopy[paraCopyIndex], bodyFont.first, bodyFont.second, 14.0));
        }
        
        const auto david = getOpenTypeFont(mako, { "David Regular" });
        assert(david.first);
        const auto arial = getOpenTypeFont(mako, { "Arial" });
        assert(arial.first);

        // Right-to-left language test
        String david_header = L"סביבות מגורים - מהביוספירה ועד לנישה האקולוגית";
        String david_para = L".מרכזו של כדור-הארץ נמצא כמעט 6400 קילומטר מתחת לפני הקרקע. עד כה הצליח האדם להגיע רק לעומק של שמונה קילומטרים בקירוב. אולם כבר היום ידוע לנו כי קילומטרים ספורים מתחת לכפות רגלינו כבר עולה טמפרטורת הארץ למידה שאיננה מאפשרת קיום של חיים. קילומטרים ספורים מעל לראשנו האוויר נעשה דליל וקר, ולא מתאפשר בו קיום של חיים. בתווך מצויה הביוספירה (ביו=חיים, ספירה=עולם) ¬– שכבה דקה על פני כדור-הארץ שעובייה כקליפת תפוח-העץ ביחס לתפוח השלם, אשר בה מתקיים כל עושר החיים המוכר לנו. הביוספירה כוללת את מכלול החיים בים וביבשה, במחילות ובין הרגבים שמתחת לפני הקרקע ובשכבות האוויר הסמוכות לקרקע. הביוספירה היא סביבת החיים הגדולה ביותר המוכרת לנו";

        paragraphs.append(createParagraph(8, ILayoutParagraph::eHARight));
        paragraphs[++paraIndex]->addRun(ILayoutTextRun::create(StringToU8String(david_header), david.first, david.second, 16.0));
        paragraphs.append(createParagraph(6.5, ILayoutParagraph::eHARight));
        paragraphs[++paraIndex]->addRun(ILayoutTextRun::create(StringToU8String(david_para), arial.first, arial.second, 13.0));

        // Add to the page
        fixedPage->appendChild(layout->layout(paragraphs));

        // Write PDF
        const auto output = IPDFOutput::create(mako);
        output->setParameter("Producer", "Mako Layout Engine");
        output->writeAssembly(assembly, "MyFirstLayout(CPP).pdf");

        // Done
    }
    catch (IError& e)
    {
        const auto errorFormatString = getEDLErrorString(e.getErrorCode());
        std::wcerr << L"Exception: " << e.getErrorDescription(errorFormatString) << std::endl;
        return static_cast<int>(e.getErrorCode());
    }
    catch (std::exception& e)
    {
        std::wcerr << L"std::exception thrown: " << e.what() << std::endl;
        return 1;
    }

    return 0;
}
C#
/* --------------------------------------------------------------------------------
*  <copyright file="Program.cs" company="Global Graphics Software Ltd">
*    Copyright (c) 2023 Global Graphics Software Ltd. All rights reserved.
*  </copyright>
*  <summary>
*    This example is provided on an "as is" basis and without warranty of any kind.
*    Global Graphics Software Ltd. does not warrant or make any representations
*    regarding the use or results of use of this example.
*  </summary>
* ---------------------------------------------------------------------------------
*/

using JawsMako;
using System;
using static JawsMako.jawsmakoIF_csharp;

namespace LayoutExampleCS;

class Program
{
    static void Main(string[] args)
    {
        try
        {
            var mako = IJawsMako.create();
            IJawsMako.enableAllFeatures(mako);

            // Get a page ready to accept some DOM
            using var assembly = IDocumentAssembly.create(mako);
            using var document = IDocument.create(mako);
            assembly.appendDocument(document);
            using var page = IPage.create(mako);
            document.appendPage(page);
            var fixedPage = IDOMFixedPage.create(mako);
            page.setContent(fixedPage);

            // Create a layout
            var layout = ILayout.create(mako);

            // Add some frames to hold content
            bool drawBorder = false;
            var margin = MM2XPS(12);
            var widthWithMargins = fixedPage.getWidth() - margin * 2;
            AddFrame(ref mako, ref layout, ref fixedPage, new FRect(margin, margin, widthWithMargins, MM2XPS(40)), drawBorder);                                                // Banner
            AddFrame(ref mako, ref layout, ref fixedPage, new FRect(margin, margin + MM2XPS(40), widthWithMargins / 3 - MM2XPS(2), MM2XPS(83)), drawBorder);                   // Sidebar with 2mm right margin
            AddFrame(ref mako, ref layout, ref fixedPage, new FRect(margin + widthWithMargins / 3, margin + MM2XPS(40), widthWithMargins / 3 * 2, MM2XPS(83)), drawBorder);    // Pic
            AddFrame(ref mako, ref layout, ref fixedPage, new FRect(margin, margin + MM2XPS(125), widthWithMargins, MM2XPS(155)), drawBorder);                                 // Body

            // Get a font
            var titleFont = GetOpenTypeFont(mako, new List<string>(new[] { "Arial Black" }));
            var bodyFont = GetOpenTypeFont(mako, new List<string>(new[] { "DejaVu Sans Book", "Gill Sans", "Arial" }));
            var bodyBold = GetOpenTypeFont(mako, new List<string>(new[] { "DejaVu Sans Book Bold", "Gill Sans Bold", "Arial Bold" }));

            // Create a colour
            var darkBlue = IDOMColor.createSolidRgb(mako, 0.0f, 0.0f, 0.5f);

            CEDLVectString paragraphCopy = new CEDLVectString();

            paragraphCopy.append("Travel Blog");
            paragraphCopy.append("\nThe beauty of where I found myself inspired me to write this.");
            paragraphCopy.append("As the sun rose over the horizon, casting a warm golden hue across the landscape, a breathtaking scene unfolded before the onlooker's eyes. Standing at the water's edge, one's gaze extended out over a pristine lake that shimmered like a sheet of glass, reflecting the majestic beauty that surrounded it. The lake seemed to hold its breath, mirroring with utmost precision the awe-inspiring sight that lay just beyond its tranquil surface.");
            paragraphCopy.append("Stretching magnificently into the distance, a range of rocky mountains dominated the backdrop. Each peak soared towards the heavens, their rugged surfaces etched by the passage of time and the forces of nature. The mountains stood resolute, a testament to the immense power and grandeur of the natural world. Their colors shifted subtly, painted with a breathtaking array of earthy tones \u2014 from deep siennas and ochres to soft grays and greens \u2014 all framed by the azure expanse of the sky.");
            paragraphCopy.append("The sky itself seemed to be a canvas of its own, an ever-changing masterpiece of color and light. Towering cumulus clouds danced gracefully, casting dramatic shadows that gently caressed the mountains' slopes. The fluffy white clouds looked as though they were soft pillows, inviting the weary soul to rest upon their tender embrace. The sky stretched boundlessly, seeming to touch the very edges of the earth, a reminder of the vastness of the universe and the limitless possibilities that lay beyond the human imagination.");
            paragraphCopy.append("As a gentle breeze whispered through the air, ripples formed across the surface of the lake, momentarily distorting the mirror-like reflection. The tiny waves moved in rhythmic harmony, lending an animated quality to the otherwise still waters. With each passing gust, the mountains appeared to ripple across the lake's surface, as though a magnificent magic spell had been cast upon the land.");
            paragraphCopy.append("In the distance, a lone boat glided silently across the lake, its presence adding a touch of serenity to the already tranquil scene. The boat's wake created a delicate trail on the water, a fleeting mark of human existence in the midst of the grandeur of nature. It served as a reminder of the delicate balance between mankind and the Earth, and how nature's beauty can be both admired and preserved.");
            paragraphCopy.append("Birds soared overhead, their graceful silhouettes weaving intricate patterns against the sky. They navigated the currents with effortless grace, adding life to the enchanting tableau. The soft cries of the birds mixed harmoniously with the gentle lapping of the lake's water against the shore, creating a soothing symphony that echoed through the air.");
            paragraphCopy.append("");

            // A vector to point to each paragraph to be added to the frame(s)
            CEDLVectILayoutParagraph paragraphs = new CEDLVectILayoutParagraph();
            uint paraIndex = 0;

            // Create paragraphs and add text runs to them

            // Title
            paragraphs.append(ILayoutParagraph.create(ILayoutParagraph.eHorizontalAlignment.eHACenter));
            var run = ILayoutTextRun.create(paragraphCopy[0], titleFont.Item1, titleFont.Item2, PT2XPS(60));
            run.setColor(darkBlue);
            paragraphs[paraIndex].addRun(ILayoutRun.fromRCObject(run.toRCObject()));

            // Intro
            paragraphs.append(CreateParagraph(9, ILayoutParagraph.eHorizontalAlignment.eHAJustified, 1.3));
            paragraphs[++paraIndex].addRun(
                ILayoutTextRun.create(paragraphCopy[1], bodyBold.Item1, bodyBold.Item2, 14.0));

            // Side bar
            paragraphs.append(CreateParagraph(7, ILayoutParagraph.eHorizontalAlignment.eHAJustified, 1.2));
            paragraphs[++paraIndex].addRun(
                ILayoutTextRun.create(paragraphCopy[2], bodyFont.Item1, bodyFont.Item2, 14.0));

            // Break to next frame
            paragraphs.append(ILayoutParagraph.create());
            paragraphs[++paraIndex].addRun(ILayoutTextRun.create("\n", bodyFont.Item1, bodyFont.Item2, 4));
            
            // Picture
            double width = widthWithMargins / 3 * 2, height = 0;
            var mountain = GetImage(ref mako, @"..\..\..\..\Images\Mountain.jpg", ref width, ref height);
            paragraphs[paraIndex].addRun(ILayoutImageRun.create(mountain, width, height));

            for (uint paraCopyIndex = 3; paraCopyIndex < 8; paraCopyIndex++)
            {
                paragraphs.append(CreateParagraph(7, ILayoutParagraph.eHorizontalAlignment.eHAJustified, 1.2));
                paragraphs[++paraIndex].addRun(
                    ILayoutTextRun.create(paragraphCopy[paraCopyIndex], bodyFont.Item1, bodyFont.Item2, 14.0));
            }

            var david = GetOpenTypeFont(mako, new List<string>(new[] { "DavidRegular" }));
            var arial = GetOpenTypeFont(mako, new List<string>(new[] { "Arial" }));

            // Right-to-left language test
            string david_header = "סביבות מגורים - מהביוספירה ועד לנישה האקולוגית";
            string david_para =
                "מרכזו של כדור-הארץ נמצא כמעט 6400 קילומטר מתחת לפני הקרקע. עד כה הצליח האדם להגיע רק לעומק של שמונה קילומטרים בקירוב. אולם כבר היום ידוע לנו כי קילומטרים ספורים מתחת לכפות רגלינו כבר עולה טמפרטורת הארץ למידה שאיננה מאפשרת קיום של חיים. קילומטרים ספורים מעל לראשנו האוויר נעשה דליל וקר, ולא מתאפשר בו קיום של חיים. בתווך מצויה הביוספירה (ביו=חיים, ספירה=עולם) ¬– שכבה דקה על פני כדור-הארץ שעובייה כקליפת תפוח-העץ ביחס לתפוח השלם, אשר בה מתקיים כל עושר החיים המוכר לנו. הביוספירה כוללת את מכלול החיים בים וביבשה, במחילות ובין הרגבים שמתחת לפני הקרקע ובשכבות האוויר הסמוכות לקרקע. הביוספירה היא סביבת החיים הגדולה ביותר המוכרת לנו.";

            paragraphs.append(CreateParagraph(8, ILayoutParagraph.eHorizontalAlignment.eHARight));
            paragraphs[++paraIndex].addRun(ILayoutTextRun.create(david_header, david.Item1, david.Item2, 16.0));
            paragraphs.append(CreateParagraph(6.5, ILayoutParagraph.eHorizontalAlignment.eHARight));
            paragraphs[++paraIndex].addRun(ILayoutTextRun.create(david_para, arial.Item1, arial.Item2, 13.0));

            // Add to the page
            fixedPage.appendChild(layout.layout(paragraphs));

            // Write PDF
            using var output = IPDFOutput.create(mako);
            output.setParameter("maxAccumulatedPages", "1");        // MAKOSUP-11222
            output.setParameter("Producer", "Mako Layout Engine");
            output.writeAssembly(assembly, "MyFirstLayout(CS).pdf");

            // Done
        }
        catch (MakoException e)
        {
            var errorFormatString = getEDLErrorString(e.m_errorCode);
            Console.WriteLine("Exception thrown: " + e.m_msg);
        }

        catch (Exception e)
        {
            Console.WriteLine($"Exception thrown: {e}");
        }
    }

    public static double PT2XPS(double value)
    {
        return value / 72.0 * 96.0;
    }

    public static double MM2XPS(double value)
    {
        return value / 25.4 * 96.0;
    }


    // Get font
    static Tuple<IDOMFontOpenType, uint> GetOpenTypeFont(IJawsMako mako, List<string> fontsToTry)
    {
        // Pick a font
        IDOMFont font = null!;
        uint fontIndex = 0; // In case the font is inside a TrueType collection
        foreach (var fontToTry in fontsToTry)
        {
            try
            {
                font = mako.findFont(fontToTry, out fontIndex);
                break;
            }
            catch (MakoException)
            {
                // Bad or missing font - default to Arial
                font = mako.findFont("Arial", out fontIndex);
            }
        }

        if (font!.getFontType() == IDOMFont.eFontType.eFontTypeOpenType)
            return new Tuple<IDOMFontOpenType, uint>(IDOMFontOpenType.fromRCObject(font), fontIndex);

        return new Tuple<IDOMFontOpenType, uint>(null!, 0);
    }

    // Draw a path using a solid color brush with a thickness of 1
    private static IDOMPathNode DrawFrame(IJawsMako mako, FRect frameBounds)
    {
        double margin = 4;
        using var border = new FRect(frameBounds.x - margin, frameBounds.y - margin, frameBounds.dX + margin * 2,
            frameBounds.dY + margin * 2);
        using var solidBrush = IDOMSolidColorBrush.createSolidRgb(mako, 0.8f, 0.8f, 0.8f);
        var path = IDOMPathNode.createStroked(mako, IDOMPathGeometry.create(mako, border), solidBrush);
        path.setStrokeThickness(1);
        return path;
    }

    // Add a layout, draw an outline if requested
    private static void AddFrame(ref IJawsMako mako, ref ILayout layout, ref IDOMFixedPage fixedPage, FRect positionAndSize, bool drawOutline = false)
    {
        layout.addFrame(ILayoutFrame.create(positionAndSize));

        // Draw a box where the frame is going to be placed
        if (drawOutline)
            fixedPage.appendChild(DrawFrame(mako, positionAndSize));
    }

    // Create paragraph
    private static ILayoutParagraph CreateParagraph(
        double spaceAfter = 0.0,
        ILayoutParagraph.eHorizontalAlignment justification = ILayoutParagraph.eHorizontalAlignment.eHALeft,
        double leading = 1.0,
        double spaceBefore = 0.0)
    {
        var templateParagraph = ILayoutParagraph.create(justification);
        if (spaceAfter > 0.0)
            templateParagraph.setSpacing(spaceAfter);
        if (spaceBefore > 0.0)
            templateParagraph.setSpacing(spaceBefore, true);
        templateParagraph.setLeading(leading);
        return templateParagraph;
    }

    // Get image and scale proportionately to specified width and/or height
    private static IDOMImage GetImage(ref IJawsMako mako, string imageFile, ref double width, ref double height)
    {

        var fileInfo = new FileInfo(imageFile);
        if (!fileInfo.Exists)
        {
            throw new Exception($"Image file {imageFile} not found.");
        }

        IDOMImage? image = null;

        if (fileInfo.Extension == ".jpg")
            image = IDOMJPEGImage.create(mako, IInputStream.createFromFile(mako, imageFile));

        if (fileInfo.Extension == ".png")
            image = IDOMPNGImage.create(mako, IInputStream.createFromFile(mako, imageFile));

        if (fileInfo.Extension == ".tif")
            image = IDOMTIFFImage.create(mako, IInputStream.createFromFile(mako, imageFile));

        if (image == null)
        {
            throw new Exception($"Image file {imageFile} could not be loaded.");
        }

        var frame = image.getImageFrame(mako);
        double imageWidth = frame.getWidth();
        double imageHeight = frame.getHeight();
        double aspectRatio = imageWidth / imageHeight;

        // If neither dimensions have been set then return the image with its 1:1 pixel dimensions
        if (width == 0.0 && height == 0.0)
        {
            width = imageWidth;
            height = imageHeight;
            return image;
        }

        // If both dimensions have been set then we're done
        if (width > 0.0 && height > 0.0)
            return image;

        // Calculate the missing dimension
        if (height == 0.0)
            height = width / aspectRatio;
        else
            width = height * aspectRatio;

        return image;
    }
}
JAVA
/* --------------------------------------------------------------------------------
 *  <copyright file="LayoutExample.java" company="Global Graphics Software Ltd">
 *    Copyright (c) 2023 Global Graphics Software Ltd. All rights reserved.
 *  </copyright>
 *  <summary>
 *    This example is provided on an "as is" basis and without warranty of any kind.
 *    Global Graphics Software Ltd. does not warrant or make any representations
 *    regarding the use or results of use of this example.
 *  </summary>
 * ---------------------------------------------------------------------------------
 */

package com.globalgraphics;

import java.util.ArrayList;
import com.globalgraphics.JawsMako.jawsmakoIF.*;

public class LayoutExample
{
    public static void main(String[] args)
    {
        try
        {
            var mako = IJawsMako.create();
            IJawsMako.enableAllFeatures(mako);
            IEDLClassFactory factory = mako.getFactory ();

            // Get a page ready to accept some DOM
            var assembly = IDocumentAssembly.create(mako);
            var document = IDocument.create(mako);
            assembly.appendDocument(document);
            var page = IPage.create(mako);
            document.appendPage(page);
            var fixedPage = IDOMFixedPage.create(factory);
            page.setContent(fixedPage);

            // Create a layout
            var layout = ILayout.create(mako);

            // Add some frames to hold content
            boolean drawBorder = false;
            var margin = MM2XPS(12);
            var widthWithMargins = fixedPage.getWidth() - margin * 2;
            AddFrame(mako, layout, fixedPage, new FRect(margin, margin, widthWithMargins, MM2XPS(40)), drawBorder);                                                // Banner
            AddFrame(mako, layout, fixedPage, new FRect(margin, margin + MM2XPS(40), widthWithMargins / 3 - MM2XPS(2), MM2XPS(83)), drawBorder);                   // Sidebar with 2mm right margin
            AddFrame(mako, layout, fixedPage, new FRect(margin + widthWithMargins / 3, margin + MM2XPS(40), widthWithMargins / 3 * 2, MM2XPS(83)), drawBorder);    // Pic
            AddFrame(mako, layout, fixedPage, new FRect(margin, margin + MM2XPS(125), widthWithMargins, MM2XPS(155)), drawBorder);

            // Get a font
            var titleFont = new GetOpenTypeFont(mako, new ArrayList<>() {{ add("Arial Black"); }});
            var bodyFont = new GetOpenTypeFont(mako, new ArrayList<>() {{ add("DejaVu Sans Book"); add("Gill Sans"); add("Arial"); }});
            var bodyBold = new GetOpenTypeFont(mako, new ArrayList<>() {{ add("DejaVu Sans Book Bold"); add("Gill Sans Bold"); add("Arial Bold"); }});

            // Create a colour
            var darkBlue = IDOMColor.createSolidRgb(mako.getFactory(), 0.0f, 0.0f, 0.5f);

            var paragraphCopy = new ArrayList<String>();
            paragraphCopy.add("Travel Blog");
            paragraphCopy.add("\nThe beauty of where I found myself inspired me to write this.");
            paragraphCopy.add("As the sun rose over the horizon, casting a warm golden hue across the landscape, a breathtaking scene unfolded before the onlooker's eyes. Standing at the water's edge, one's gaze extended out over a pristine lake that shimmered like a sheet of glass, reflecting the majestic beauty that surrounded it. The lake seemed to hold its breath, mirroring with utmost precision the awe-inspiring sight that lay just beyond its tranquil surface.");
            paragraphCopy.add("Stretching magnificently into the distance, a range of rocky mountains dominated the backdrop. Each peak soared towards the heavens, their rugged surfaces etched by the passage of time and the forces of nature. The mountains stood resolute, a testament to the immense power and grandeur of the natural world. Their colors shifted subtly, painted with a breathtaking array of earthy tones \u2014 from deep siennas and ochres to soft grays and greens \u2014 all framed by the azure expanse of the sky.");
            paragraphCopy.add("The sky itself seemed to be a canvas of its own, an ever-changing masterpiece of color and light. Towering cumulus clouds danced gracefully, casting dramatic shadows that gently caressed the mountains' slopes. The fluffy white clouds looked as though they were soft pillows, inviting the weary soul to rest upon their tender embrace. The sky stretched boundlessly, seeming to touch the very edges of the earth, a reminder of the vastness of the universe and the limitless possibilities that lay beyond the human imagination.");
            paragraphCopy.add("As a gentle breeze whispered through the air, ripples formed across the surface of the lake, momentarily distorting the mirror-like reflection. The tiny waves moved in rhythmic harmony, lending an animated quality to the otherwise still waters. With each passing gust, the mountains appeared to ripple across the lake's surface, as though a magnificent magic spell had been cast upon the land.");
            paragraphCopy.add("In the distance, a lone boat glided silently across the lake, its presence adding a touch of serenity to the already tranquil scene. The boat's wake created a delicate trail on the water, a fleeting mark of human existence in the midst of the grandeur of nature. It served as a reminder of the delicate balance between mankind and the Earth, and how nature's beauty can be both admired and preserved.");
            paragraphCopy.add("Birds soared overhead, their graceful silhouettes weaving intricate patterns against the sky. They navigated the currents with effortless grace, adding life to the enchanting tableau. The soft cries of the birds mixed harmoniously with the gentle lapping of the lake's water against the shore, creating a soothing symphony that echoed through the air.");
            paragraphCopy.add("");

            // A vector to point to each paragraph to be added to the frame(s)
            CEDLVectILayoutParagraph paragraphs = new CEDLVectILayoutParagraph();
            long paraIndex = 0;

            // Create paragraphs and add text runs to them
            
            // Title
            paragraphs.append(ILayoutParagraph.create(ILayoutParagraph.eHorizontalAlignment.eHACenter));
            var run = ILayoutTextRun.create(paragraphCopy.get(0), titleFont.font, titleFont.index, PT2XPS(60));
            run.setColor(darkBlue);
            paragraphs.getitem(paraIndex).addRun(run);

            // Intro
            paragraphs.append(CreateParagraph(9, ILayoutParagraph.eHorizontalAlignment.eHAJustified, 1.3, 0.0));
            paragraphs.getitem(++paraIndex).addRun(
                    ILayoutTextRun.create(paragraphCopy.get(1), bodyBold.font, bodyBold.index, 14.0));

            // Sidebar
            paragraphs.append(CreateParagraph(7, ILayoutParagraph.eHorizontalAlignment.eHAJustified, 1.2, 0.0));
            paragraphs.getitem(++paraIndex).addRun(
                    ILayoutTextRun.create(paragraphCopy.get(2), bodyFont.font, bodyFont.index, 14.0));

            // Break to next frame
            paragraphs.append(ILayoutParagraph.create());
            paragraphs.getitem(++paraIndex).addRun(ILayoutTextRun.create("\n", bodyFont.font, bodyFont.index, 4));

            // Picture
            double width = widthWithMargins / 3 * 2, height = 0;
            var mountain = new GetImage(mako, "../Images/Mountain.jpg", width, height);
            paragraphs.getitem(paraIndex).addRun(ILayoutImageRun.create(mountain.image, mountain.width, mountain.height));

            for (int paraCopyIndex = 3; paraCopyIndex < 8; paraCopyIndex++)
            {
                paragraphs.append(CreateParagraph(7.0, ILayoutParagraph.eHorizontalAlignment.eHAJustified, 1.2, 0.0));
                paragraphs.getitem(++paraIndex).addRun(
                        ILayoutTextRun.create(paragraphCopy.get(paraCopyIndex), bodyFont.font, bodyFont.index, 14.0));
            }
            var david = new GetOpenTypeFont(mako, new ArrayList<>() {{ add( "DavidRegular"); }});
            var arial = new GetOpenTypeFont(mako, new ArrayList<>() {{ add( "Arial"); }});

            // Right-to-left language test
            String heb_header = "סביבות מגורים - מהביוספירה ועד לנישה האקולוגית";
            String heb_para =
                "מרכזו של כדור-הארץ נמצא כמעט 6400 קילומטר מתחת לפני הקרקע. עד כה הצליח האדם להגיע רק לעומק של שמונה קילומטרים בקירוב. אולם כבר היום ידוע לנו כי קילומטרים ספורים מתחת לכפות רגלינו כבר עולה טמפרטורת הארץ למידה שאיננה מאפשרת קיום של חיים. קילומטרים ספורים מעל לראשנו האוויר נעשה דליל וקר, ולא מתאפשר בו קיום של חיים. בתווך מצויה הביוספירה (ביו=חיים, ספירה=עולם) ¬– שכבה דקה על פני כדור-הארץ שעובייה כקליפת תפוח-העץ ביחס לתפוח השלם, אשר בה מתקיים כל עושר החיים המוכר לנו. הביוספירה כוללת את מכלול החיים בים וביבשה, במחילות ובין הרגבים שמתחת לפני הקרקע ובשכבות האוויר הסמוכות לקרקע. הביוספירה היא סביבת החיים הגדולה ביותר המוכרת לנו.";

            paragraphs.append(CreateParagraph(8, ILayoutParagraph.eHorizontalAlignment.eHARight, 1.0, 0.0));
            paragraphs.getitem(++paraIndex).addRun(ILayoutTextRun.create(heb_header, david.font, david.index, 16.0));
            paragraphs.append(CreateParagraph(6.5, ILayoutParagraph.eHorizontalAlignment.eHARight, 1.0, 0.0));
            paragraphs.getitem(++paraIndex).addRun(ILayoutTextRun.create(heb_para, arial.font, arial.index, 13.0));

            // Add to the page
            fixedPage.appendChild(layout.layout(paragraphs));

            // Write PDF
            var output = IPDFOutput.create(mako);
            output.setParameter("Producer", "Mako Layout Engine");
            output.writeAssembly(assembly, "MyFirstLayout(Java).pdf");

            // Done
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static double PT2XPS(double value)
    {
        return value / 72.0 * 96.0;
    }

    public static double MM2XPS(double value)
    {
        return value / 25.4 * 96.0;
    }

    // Draw a path using a solid color brush with a thickness of 1
    private static IDOMPathNode DrawFrame(IEDLClassFactory factory, FRect frameBounds)
    {
        double margin = 4;
        var border = new FRect(frameBounds.getX() - margin, frameBounds.getY() - margin,
            frameBounds.getDX() + margin * 2,frameBounds.getDY() + margin * 2);
        var solidBrush = IDOMSolidColorBrush.createSolidRgb(factory, 0.8f, 0.8f, 0.8f);
        var path = IDOMPathNode.createStroked(factory, IDOMPathGeometry.create(factory, border), solidBrush);
        path.setStrokeThickness(1);
        return path;
    }

    // Add a layout, draw an outline if requested
    private static void AddFrame(IJawsMako mako, ILayout layout, IDOMFixedPage fixedPage, FRect positionAndSize, boolean drawOutline)
    {
        layout.addFrame(ILayoutFrame.create(positionAndSize));

        // Draw a box where the frame is going to be placed
        if (drawOutline)
            fixedPage.appendChild(DrawFrame(mako.getFactory(), positionAndSize));
    }

    // Create paragraph
    private static ILayoutParagraph CreateParagraph(
            double spaceAfter,
            ILayoutParagraph.eHorizontalAlignment justification,
            double leading,
            double spaceBefore)
    {
        var templateParagraph = ILayoutParagraph.create(justification);
        if (spaceAfter > 0.0)
            templateParagraph.setSpacing(spaceAfter);
        if (spaceBefore > 0.0)
            templateParagraph.setSpacing(spaceBefore, true);
        templateParagraph.setLeading(leading);
        return templateParagraph;
    }
}




GetOpenTypeFont

JAVA
/* --------------------------------------------------------------------------------
 *  <copyright file="GetImage.java" company="Global Graphics Software Ltd">
 *    Copyright (c) 2023 Global Graphics Software Ltd. All rights reserved.
 *  </copyright>
 *  <summary>
 *    This example is provided on an "as is" basis and without warranty of any kind.
 *    Global Graphics Software Ltd. does not warrant or make any representations
 *    regarding the use or results of use of this example.
 *  </summary>
 * ---------------------------------------------------------------------------------
 */

package com.globalgraphics;

import com.globalgraphics.JawsMako.jawsmakoIF.IDOMFontOpenType;
import com.globalgraphics.JawsMako.jawsmakoIF.IJawsMako;

import java.util.ArrayList;

public class GetOpenTypeFont {
    private IJawsMako mako;
    public IDOMFontOpenType font;
    public long index;

    public GetOpenTypeFont(IJawsMako mako, ArrayList<String> fontsToTry) {
        long[] fontIndex = new long[1]; // In case the font is inside a TrueType collection
        for (String fontToTry : fontsToTry) {
            try {
                font = IDOMFontOpenType.fromRCObject(mako.findFont(fontToTry, fontIndex));
                break;
            } catch (Exception e) {
                // Bad or missing font - default to Arial
                font = IDOMFontOpenType.fromRCObject(mako.findFont("Arial", fontIndex));
            }
        }
        index = fontIndex[0];
    }
}


GetImage

JAVA
/* --------------------------------------------------------------------------------
 *  <copyright file="GetImage.java" company="Global Graphics Software Ltd">
 *    Copyright (c) 2023 Global Graphics Software Ltd. All rights reserved.
 *  </copyright>
 *  <summary>
 *    This example is provided on an "as is" basis and without warranty of any kind.
 *    Global Graphics Software Ltd. does not warrant or make any representations
 *    regarding the use or results of use of this example.
 *  </summary>
 * ---------------------------------------------------------------------------------
 */

package com.globalgraphics;

import com.globalgraphics.JawsMako.jawsmakoIF.*;

import java.io.File;

public class GetImage {
    public IDOMImage image;
    public double width;
    public double height;

    GetImage(IJawsMako mako, String imageFile, double requiredWidth, double requiredHeight) throws Exception {
        File fileInfo = new File(imageFile);
        if (!fileInfo.exists()) {
            throw new Exception("Image file {imageFile} not found.");
        }

        image = null;
        if (fileInfo.getName().toLowerCase().contains(".jpg")) {
            image = IDOMJPEGImage.create(mako.getFactory(), IInputStream.createFromFile(mako.getFactory(), imageFile));
        }

        if (fileInfo.getName().toLowerCase().contains(".png")) {
            image = IDOMPNGImage.create(mako.getFactory(), IInputStream.createFromFile(mako.getFactory(), imageFile));
        }

        if (fileInfo.getName().toLowerCase().contains(".tif")) {
            image = IDOMTIFFImage.create(mako.getFactory(), IInputStream.createFromFile(mako.getFactory(), imageFile));
        }

        if (image == null) {
            throw new Exception("Image file " + imageFile + " could not be loaded.");
        }

        var frame = image.getImageFrame(mako.getFactory());
        double imageWidth = frame.getWidth();
        double imageHeight = frame.getHeight();
        double aspectRatio = imageWidth / imageHeight;

        // If requested dimensions are both zero, return the actual size
        if (requiredWidth == 0.0 && requiredHeight == 0.0) {
            width = imageWidth;
            height = imageHeight;
        }

        // If requested dimensions are both non-zero, return those values (do nothing)
        if (requiredWidth > 0.0 && requiredHeight > 0.0) {
            width = requiredWidth;
            height = requiredHeight;
        }

        // Otherwise, calculate the missing dimension
        if (requiredHeight == 0.0) {
            width = requiredWidth;
            height = requiredWidth / aspectRatio;
        } else {
            width = requiredHeight * aspectRatio;
            height = requiredHeight;
        }
    }
}


JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.