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.

161-feature-image.png
Rust: PDFs — Cairo and Pango — Image Block Layout

🚀 The code for this post is in the following GitHub repository: pdf_08_image_layout.

Reference Documentation

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:

Repository Layout

💡 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:

  1. 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.
  2. min_allowed_scale — the minimum acceptable scale factor. If the scale falls below this value, layout is considered to have failed.
  3. 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_factor to 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:

  1. 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.
  2. If the block does not fit, progressively reduce the final scale factor by multiplying it with step_scale_factor.
    1. After each reduction, if the block fits on the current page, render it and return successfully.
    2. If the scale factor becomes smaller than min_allowed_scale, proceed to step 3.
  3. Attempt to render the image block on a new page:
    1. Repeat the progressive‑reduction loop described in step 2a (2.1).
    2. 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.

Configuration Updates

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:


What’s Next

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:

🦀 Index of the Complete Series.