Skip to main content
Skip table of contents

FMatrix and transforms

Introduction

Objects on a PDF page get their size and position from a transformation matrix that is the sum of its own and those of the chain of parent nodes, up to the page root. These transformations are expressed in the form of a matrix. In Mako you can create matrices and apply them to objects to move, resize, or rotate content.

When you need to move, resize, or rotate content, you create an FMatrix and apply it with .setRenderTransform().

Creating an FMatrix

The FMatrix constructor can be used in three different ways:

1.

FMatrix()

Default. Use this to create a default matrix, then use the following methods to change individual values.

2.

FMatrix(double xx, double xy, double yx, double yy, double dx, double dy);

Specifies the full range of values.

3.

FMatrix(FRect sourceRect, FRect destRect);

This is a very useful constructor. It calculates a matrix for you based on source and destination rectangles, each expressed as an FRect (x,y origin and dX, dY dimensions).

The values (xx, xy, yx, yy, dx, and dy) are described below.

Setting values individually

The API documentation refers to a TItem, which is defined (via a typedef) as a double.

void setXX (TItem x)

Sets xx component.

void setXY (TItem x)

Sets xy component.

void setYX (TItem x)

Sets yx component.

void setYY (TItem x)

Sets yy component.

void setDX (TItem x)

Sets dx component.

void setDY (TItem x)

Sets dy component.

Matrix values

Matrix values and their effects are shown below, alternatively you can use the transformation methods to apply these transformations for you.

Rotations will always be performed using the top left corner of the page as the origin, which may result in the object being displaced from its original position.

Transformation methods

The following methods are available for FMatrix.

C#
void FMatrix.translate(double dx, double dy)
void FMatrix.scale(double xscale, double yscale)
void FMatrix.rotate(double radians)

There is no “flip” method however this can be achieved by scaling with a negative scale factor as this is mathematically equivalent. See example below for how this is done.

Using more than one transformation method on the same FMatrix may lead to unexpected results. It is recommended to create a new FMatrix for each transformation you want to perform and then use postMul() or preMul() to multiply them together. See example below of how this is done.

Here is an example of how to use FMatrix objects to transform page content. In this example the content is two ILayout nodes, but the approach will work for any type of node.

First we add the content we want to rotate into an IDOMGroup. Then we create an FMatrix object for each transformation we want to perform. Finally we use matrix multiplication to obtain the combined FMatrix which we can use to transform our group using IDOMGroup::setRenderTransform(). Note that in this case we move the group to the top left corner of the page so that the transformations occur using the middle of the group as the origin.

C++
CPP
// Create a group and add the nodes you want to transform
const auto transformGroup = IDOMGroup::create(mako);

transformGroup->appendChild(layoutNode1);
transformGroup->appendChild(layoutNode2);

// Create matrices for moving the group to and from the origin
auto transformGroupBounds = transformGroup->getBounds();

double center_x = transformGroupBounds.x + transformGroupBounds.dX / 2;
double center_y = transformGroupBounds.y + transformGroupBounds.dY / 2;

FMatrix move_to_origin; 
move_to_origin.translate(-center_x, -center_y);
FMatrix move_back; 
move_back.translate(center_x, center_y);

// Create matrices for rotating and flipping in the x axis (vertically)
FMatrix rotate;
const auto angle = 90 * (PI / 180.0);
rotate.rotate(angle);

FMatrix flip;
flip.scale(1, -1);

// Multiply all matrices in correct order
FMatrix transformChain;
transformChain.postMul(move_to_origin).postMul(rotate).postMul(flip).postMul(move_back);

// Set the result as the transform for the group
transformGroup->setRenderTransform(transformChain);

// Finally add the group to the page
fixedPage->appendChild(transformGroup);
C#
C#
// Create a group and add the nodes you want to transform
IDOMGroup transformGroup = IDOMGroup.create(mako);

transformGroup.appendChild(getLayoutParagraphs(mako));
transformGroup.appendChild(getLayoutParagraphs(mako));

// Create matrices for moving the group to and from the origin
FRect transformGroupBounds = transformGroup.getBounds();

double center_x = (transformGroupBounds.x + transformGroupBounds.dX) / 2.0;
double center_y = (transformGroupBounds.y + transformGroupBounds.dY) / 2.0;

var move_to_origin = new FMatrix(); 
move_to_origin.translate(-center_x, -center_y);
var move_back = new FMatrix(); 
move_back.translate(center_x, center_y);

// Create matrices for rotating and flipping in the x axis (vertically)
var rotate = new FMatrix();
double angle = 90.0 * (Math.PI / 180.0);
rotate.rotate(angle);

var flip = new FMatrix();
flip.scale(1, -1);

// Multiply all matrices in correct order 
var transformChain = new FMatrix();
transformChain.postMul(move_to_origin).postMul(rotate).postMul(flip).postMul(move_back);

// Set the result as the transform for the group
transformGroup.setRenderTransform(transformChain);

// Finally add the group to the page
fixedPage.appendChild(transformGroup);

No transformation

After rotating 90 degrees and flipping vertically

Screenshot 2025-10-23 150308.png

Screenshot 2025-10-23 150358.png

JavaScript errors detected

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

If this problem persists, please contact our support.