Rust: PDFs — Cairo and Pango — Image Block Layout
An image block is defined as an image and its caption, treated as a single unit for layout purposes. This article focuses on developing an algorithm capable of ensuring that image blocks can be rendered properly within the available effective height, as mentioned in the closing section of the immediate previous article.
🦀 Index of the Complete Series.
![]() |
|---|
| Rust: PDFs — Cairo and Pango — Image Block Layout |
🚀 The code for this post is in the following GitHub repository: pdf_08_image_layout.
In this article we use another method from the
Context struct:
pub fn rel_move_to(&self, dx: f64, dy: f64)
Its native
CairoGraphics documentation:
💡 Please note: on both Windows and Ubuntu, I’m running Rust version
rustc 1.90.0 (1159e78c4 2025-09-14).
This is once again a one‑off project — I don’t plan to update it in future development. I want to keep a log of progress exactly as it occurred. Future code may copy this and make changes to it. I’ve placed the project under the pdf_08_image_layout directory. The structure is:
.
├── Cargo.toml
├── set_env.bat
├── config
│ └── config.toml
├── img
│ ├── 139015.png
│ ├── KTmCgCBjQXKLsO2JeBMVrA.png
│ ├── Readme.md
│ └── unscalable.png
├── src
│ ├── config.rs
│ ├── document.rs
│ ├── font_utils.rs
│ ├── image_layout.rs
│ ├── main.rs
│ ├── page_geometry.rs
│ └── text_layout.rs
└── .vscode
└── launch.json
We describe some modules in the following subsections. The rest will be covered in the sections that follow.
⓵ The src/page_geometry.rs module is copied unchanged from
the ninth article. 👉 Changing any margin value in the
A4_DEFAULT_MARGINS constant will change the layout of the text in the PDF.
⓶ The src/font_utils.rs and src/document.rs modules
are also copied unchanged from
the ninth article.
⓷ 💡 The code requires the Pango, HarfBuzz, Cairo,
etc. libraries. 🐧 On Ubuntu, all required libraries are globally recognised. 🪟 On Windows,
I haven’t added the paths for the libraries’ DLLs to the PATH environment
variable. In each new Windows terminal session, I run the following once:
set PATH=C:\PF\harfbuzz\dist\bin\;%PATH%
set PATH=C:\PF\vcpkg\installed\x64-windows\bin\;%PATH%
set PATH=C:\PF\pango\dist\bin;C:\PF\cairo-1.18.4\dist\bin;C:\PF\fribidi\dist\bin;%PATH%
Alternatively, you can simply run
set_env.bat.
After that, cargo run works as expected.
⓸ 💡 In the fifth article, we discussed the
PKG_CONFIG_PATH user environment variable. This setting applies to all
later articles. I did not mention it again from the sixth article onward. In the
set_env.bat above, I include setting this variable so that we don’t forget
it and avoid potential surprises.
❸ The Image Block Layout Algorithm
As previously mentioned, we are going to extend the existing
render_image_block() function to support rendering an image and its
caption as a single image block, and give the extended version some essential intelligence,
such as ensuring the image block can be rendered properly within the available effective
height.
The new algorithm introduces several additional parameters:
-
step_scale_factor— a factor used to progressively reduce the image scale when attempting to make the image block fit within the available vertical space. -
min_allowed_scale— the minimum acceptable scale factor. If the scale falls below this value, layout is considered to have failed. -
image_block_spacing— vertical spacing (in points) added below each image caption to create a natural visual gap before the next block. This parameter is not included in the scaling calculation; as long as the image block fits, it does not matter whether there is enough remaining vertical space for this gap — if not, a new page will be created for the next block.
render_image_block() already performs the following:
-
Computes the scale factor required to fit the image within
a4_default_content_width(). -
Applies
reduction_factorto obtain the initial final scale factor. (The image is not scaled yet; this value is used only for layout calculations.)
The extended algorithm then carries out the following steps:
- If the image scaled by the final scale factor, together with its caption, fits in the remaining space on the current page, render the block and return successfully.
-
If the block does not fit, progressively reduce the final scale factor by
multiplying it with
step_scale_factor.- After each reduction, if the block fits on the current page, render it and return successfully.
-
If the scale factor becomes smaller than
min_allowed_scale, proceed to step 3.
-
Attempt to render the image block on a new page:
- Repeat the progressive‑reduction loop described in step 2a (2.1).
- If the block still does not fit even on a fresh page, return an error. It is up to the caller to decide how to handle this failure.
🦀 It should be apparent that the image block is either rendered on the current page
at the current y‑coordinate, or on a new page at
A4_DEFAULT_MARGINS.top.
We discuss the configuration updates required to support the extended algorithm: the
pdf_08_image_layout/config/config.toml file, and the
Rust configuration module
pdf_08_image_layout/src/config.rs.
⓵ The
pdf_08_image_layout/config/config.toml File
We made a few additions:
● Under [fonts], added
caption = { family = "Be Vietnam Pro", size = 12, weight = "normal", style = "italic" },
which is the font used for image captions. 💡 The Be Vietnam Pro font
was installed in the
eighth post.
● Added
[layout] and
[image_block] sections:
🙏️ Under [image_block], the entries reduction_factor and
centre_aligned correspond to the
render_image_block() parameters that have now been externalised.
The entries step_scale_factor and min_allowed_scale are the new
parameters discussed above.
🙏️ Under [layout], the image_block_spacing entry has also been
discussed in the same section.
⓶ The
pdf_08_image_layout/src/config.rs Module
We added new fields to existing structs and introduced new structs to hold the newly added
configuration tables [image_block] and [layout] — these should be
self‑explanatory. A more general
load_config() function replaces the previous
load_font_config().
❺ The
pdf_08_image_layout/src/text_layout.rs
This module is new, but most of the code has already been covered in earlier articles.
⓵ The
LayoutExtJustify trait — this was first
discussed in detail in the sixth post, in the section
On
Pango Justification, and was first illustrated in the accompanying module
pdf_03_pango/src/main_fully_justified.rs. It has been used throughout
later articles ever since.
⓶ The helper functions are short and should be easy to follow. 💡 Please note that the
center_layout_block()</code> function calls
context.rel_move_to(offset_x, 0.0), whose documentation references were
listed earlier.
❻ The
pdf_08_image_layout/src/image_layout.rs
This is the main focus of this article. The
render_image_block() function is no longer relevant and has been removed.
The API is now the
layout_image_block()</code> function. It is fully documented.
The main helper function,
step_scale_image()</code>, performs the progressive‑scaling
calculation — note that it only determines the appropriate scale factor; it does not
actually scale the image. 💡 The image is scaled only once, at the point of rendering.
Together with the extended algorithm discussion and the module documentation, we are not going to cover the code in detail here.
💡 Please pay special attention to the tests — they cover several edge cases. Tests should be read in conjunction with the inline documentation.
❼ The
pdf_08_image_layout/src/main.rs
This module demonstrates the API function
layout_image_block(). We can tweak the configuration
parameters and the top_y value of the first call:
33
34
35
36
37
38
39
40
let caption: &str = "Cassowary, an Australia native, and “the world's most dangerous bird”.\n\
Cassowary, chim bản địa Úc, và là “loài chim nguy hiểm nhất thế giới”.";
// Define the input PNG image file name (ensure this file exists).
let png_file_name = "./img/139015.png";
let image_bottom = layout_image_block(png_file_name, caption,
// A4_DEFAULT_MARGINS.top,
200.00,
&context, &config)?;
to experiment with how the output changes. At a top_y of
200.00, the output appears as shown in the screenshots below:
There is not enough space on the first page, so the second image block is rendered
on the second page at the default y‑coordinate of
A4_DEFAULT_MARGINS.top.
Changing top_y to A4_DEFAULT_MARGINS.top (instead of
200.00, as shown in lines 38–39 above) places both image blocks on a single
page, as illustrated in the screenshot below:
We conclude this article… The next main objective remains the same: enabling
the Markdown parser we
last discussed to support image blocks, where images are specified
using relative paths, similar to how it is done in
LaTeX.
And we are now ready to take this next step. We will add this feature to the parser in the
next article.
Thanks for reading! I hope this post helps others who are looking to deepen their understanding of PDF technology. As always—stay curious, stay safe 🦊
✿✿✿
Feature image sources:
- https://www.omgubuntu.co.uk/2024/03/ubuntu-24-04-wallpaper
- https://in.pinterest.com/pin/337277459600111737/
- https://medium.com/analytics-vidhya/rust-adventures-from-java-class-to-rust-struct-1d63b66890cf/
- https://www.pngitem.com/download/ibmJoR_rust-language-hd-png-download/
- https://en.wikipedia.org/wiki/Cairo_%28graphics%29#/media/File:Cairo_banner_closeup.svg
- https://ur.wikipedia.org/wiki/%D9%81%D8%A7%D8%A6%D9%84:HarfBuzz.svg
- https://en.wikipedia.org/wiki/Pango
