Created Date: 11 Sep, 2023 10:24
Last Modifed Date: 11 Sep, 2023 10:24


An output intent describes the final destination device you will use to reproduce the color in the PDF, such as the separations printing device. Output intents override working spaces during viewing and printing, but they do not convert the colors in the PDF. You can include output intents when you create PDF/X (or PDF/A) files.

Mako uses a property of the IPDFOutput class to specify the output intent when writing a PDF. The IOutputIntent class represents a PDF output intent and provides the create() method that requires several items of information, described below. More detailed information regarding the information needed is described in the PDF specification 1.7 (refer to section 14.11.5).

IOutputIntent create()

The create method requires the following information, described here with some examples:

NameOutput intent propertyTypical value / purpose
sRGBSWOPFOGRA39
registryNameProfile registryhttp://www.color.orghttp://www.color.orghttp://www.color.org
outputConditionOutput conditionsRGB IEC61966-2.1CGATS TR 001 (SWOP)FOGRA39
outputConditionIdentifier
sRGB IEC61966-2.1CGATS TR 001FOGRA39
subtypeSubtype

One of: GTS_PDFX, GTS_PDFA1, ISO_PDFE1

infoInfoA human-readable text string containing additional information or comments about the intended target device or production condition. Can be left blank.
profileICC ProfileAn IDOMICCProfile. See code example for creation of this class from a file-based ICC profile.

Worked Example

The code in the following section created a PDF, shown here displayed in Acrobat (or Acrobat Reader).

The ICC profile used for the output intent is deliberately designed to alter colors in a dramatic fashion, so that it is clear it is being used.

To see the effect on the PDF, you can open it in Adobe Acrobat Reader DC and set Edit > Preferences > Page Display > Use Overprint Preview to 'Always':

With the 'Use Overprint Preview' option set to 'Never'.With the 'Use Overprint Preview' option set to 'Always'.

Code

/* -----------------------------------------------------------------------
 * <copyright file="SaveWithOutputIntent.cpp" company="Global Graphics Software Ltd">
 *  Copyright (c) 2022-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 <format>
#include <filesystem>
using namespace std;
namespace fs = std::filesystem;

#include <jawsmako/jawsmako.h>
#include <jawsmako/layout.h>
#include <jawsmako/outputintent.h>
#include <jawsmako/pdfoutput.h>
#include <edl/idomresources.h>

using namespace JawsMako;
using namespace EDL;

#define M2X(value) ((value) / 25.4 * 96.0)
#define P2X(value) ((value) / 72.0 * 96.0)

IDOMImagePtr getImage(const IJawsMakoPtr& mako, const U8String& imageFile, double& width, double& height);

int main()
{
    try
    {
        const auto mako = IJawsMako::create();
        mako->enableAllFeatures(mako);

        // Create assembly, document, page, fixed page
        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, M2X(250), M2X(210));
        page->setContent(fixedPage);

        // The ICC Probe profile
        const auto profile = std::pair<U8String, U8String>("Probev2_ICCv4.icc", "https://www.color.org/probeprofile.xalter");

        // Get a font
        uint32 fontIndex;
        const auto font = edlobj2IDOMFontOpenType(mako->findFont("Arial", fontIndex));

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

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

        // Add frame to hold content
        layout->addFrame(ILayoutFrame::create(FRect(M2X(12), M2X(12), M2X(273), M2X(186))));

        // A vector to point to each paragraph to be added to the frame(s)
        auto paragraphs = CEDLVector<ILayoutParagraphPtr>();
        uint32 paraIndex = 0;

        // Create paragraphs and text runs 
        std::string text = "This PDF has an Output Intent referencing this ICC profile:";
        auto run = ILayoutTextRun::create(text.c_str(), font, fontIndex, P2X(12));
        run->setColor(darkBlue);
        paragraphs.append(ILayoutParagraph::create());
        paragraphs[paraIndex]->setSpacing(P2X(6));
        paragraphs[paraIndex]->addRun(run);
        text = std::format("  ● {} (from {})", profile.first, profile.second);
        run = ILayoutTextRun::create(text.c_str(), font, fontIndex, P2X(10));
        paragraphs.append(ILayoutParagraph::create());
        paragraphs[++paraIndex]->setSpacing(P2X(5));
        paragraphs[paraIndex]->addRun(run);

        // Picture
        double width = 0;
        double height = M2X(169);
        const auto parrot = getImage(mako, R"(..\..\TestFiles\Parrot.png)", width, height);
        paragraphs.append(ILayoutParagraph::create());
        paragraphs[++paraIndex]->addRun(ILayoutImageRun::create(parrot, width, height));

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

        auto output = IPDFOutput::create(mako);
        output->setPreset("PDF/X-4");

        // Load profile from local storage
        const auto iccProfile = IDOMICCProfile::create(mako, IInputStream::createFromFile(mako, R"(..\..\TestFiles\)" + profile.first));

        // Output intent metadata
        const U8String subtype = "GTS_PDFX";
        const U8String registryName = "http://www.color.org";
        const U8String outputCondition = profile.first;
        const U8String outputConditionIdentifier = profile.first;
        const U8String info = "Output Intent test";

        // Create an intent and add to PDF
        const auto outputIntent = IOutputIntent::create(mako, subtype, outputCondition, outputConditionIdentifier, registryName, info, iccProfile);
        output->setOutputIntent(outputIntent);

        // Write PDF
        output->writeAssembly(assembly, "OutputIntentExample.pdf");

        // Done
    }
    catch (IError& e)
    {
        const String errorFormatString = getEDLErrorString(e.getErrorCode());
        std::wcerr << L"Exception thrown: " << 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;
}

// 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;
}

CPP
/* -----------------------------------------------------------------------
 * <copyright file="SaveWithOutputIntent.cs" company="Global Graphics Software Ltd">
 *  Copyright (c) 2022-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 static JawsMako.jawsmakoIF_csharp;

namespace SaveWithOutputIntentCS;

class SaveWithOutputIntent
{
    static void Main(string[] args)
    {
        // The ICC Probe profile
        var profile = Tuple.Create("Probev2_ICCv4.icc", @"https://www.color.org/probeprofile.xalter");

        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, M2X(250), M2X(210));
            page.setContent(fixedPage);

            // Get a font
            var font = IDOMFontOpenType.fromRCObject(mako.findFont("Arial", out var fontIndex).toRCObject());

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

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

            // Add frame to hold content
            layout.addFrame(ILayoutFrame.create(new FRect(M2X(12), M2X(12), M2X(273), M2X(186))));

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

            // Create paragraphs and text runs 
            var text = "This PDF has an Output Intent referencing this ICC profile:";
            var run = ILayoutTextRun.create(text, font, fontIndex, P2X(12));
            run.setColor(darkBlue);
            paragraphs.append(ILayoutParagraph.create());
            paragraphs[paraIndex].setSpacing(P2X(6));
            paragraphs[paraIndex].addRun(ILayoutRun.fromRCObject(run.toRCObject()));
            text = $"  ● {profile.Item1} (from {profile.Item2})";
            run = ILayoutTextRun.create(text, font, fontIndex, P2X(10));
            paragraphs.append(ILayoutParagraph.create());
            paragraphs[++paraIndex].setSpacing(P2X(5));
            paragraphs[paraIndex].addRun(ILayoutRun.fromRCObject(run.toRCObject()));

            // Picture
            double width = 0;
            double height = M2X(169);
            var parrot = GetImage(ref mako, @"..\..\..\..\TestFiles\Parrot.png", ref width, ref height);
            paragraphs.append(ILayoutParagraph.create());
            paragraphs[++paraIndex].addRun(ILayoutImageRun.create(parrot, width, height));

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

            using var output = IPDFOutput.create(mako);
            output.setPreset("PDF/X-4");

            // Load profile from local storage
            var iccProfile = IDOMICCProfile.create(mako, IInputStream.createFromFile(mako, Path.Combine(@"..\..\..\..\TestFiles\", profile.Item1)));

            // Output intent metadata
            var subtype = "GTS_PDFX";
            var registryName = "http://www.color.org";
            var outputCondition = profile.Item1;
            var outputConditionIdentifier = profile.Item1;
            var info = "Output Intent test";

            // Create an intent and add to PDF
            var outputIntent = IOutputIntent.create(mako, subtype, outputCondition, outputConditionIdentifier, registryName, info, iccProfile);
            output.setOutputIntent(outputIntent);

            // Write PDF
            output.writeAssembly(assembly, "OutputIntentExample.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}");
        }
    }

    // Points (1/72") to XPS units (1/96")
    public static double P2X(double value)
    {
        return value / 72.0 * 96.0;
    }

    // Millimetres to XPS units (1/96")
    public static double M2X(double value)
    {
        return value / 25.4 * 96.0;
    }

    // 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;
    }
}
CPP