Impossible to load font, then use it, into mutable `Document`
stephenjudkins opened this issue · 12 comments
Please forgive me if this question reflects Rust inexperience. But here's my impression:
If we want to load a font and then use it in a document, the borrowing rules make this impossible.
let mut doc = pdfium.create_new_pdf()?;
let font = PdfFont::new_true_type_from_bytes(&doc, include_bytes!("../font.ttf"), true)?;
doc.pages_mut().copy_page_from_document(&template, 0, 0)?; // copy first page #
gives us:
error[E0502]: cannot borrow `doc` as mutable because it is also borrowed as immutable
--> src/main.rs:40:5
|
38 | let font = PdfFont::new_true_type_from_bytes(&doc, include_bytes!("../font.ttf"), true)?;
| ---- immutable borrow occurs here
39 |
40 | doc.pages_mut().copy_page_from_document(&template, 0, 0)?; // copy first page #
| ^^^^^^^^^^^^^^^ mutable borrow occurs here
Likewise, if we pass it &mut doc
:
let mut doc = pdfium.create_new_pdf()?;
let font = PdfFont::new_true_type_from_bytes(&mut doc, include_bytes!("../font.ttf"), true)?;
doc.pages_mut().copy_page_from_document(&template, 0, 0)?; // copy first page #
we see
error[E0499]: cannot borrow `doc` as mutable more than once at a time
--> src/main.rs:40:5
|
38 | let font = PdfFont::new_true_type_from_bytes(&mut doc, include_bytes!("../font.ttf"), true)?;
| -------- first mutable borrow occurs here
39 |
40 | doc.pages_mut().copy_page_from_document(&template, 0, 0)?; // copy first page #
| ^^^^^^^^^^^^^^^ second mutable borrow occurs here
It is possible to load a new font after we call pages_mut()
. But the problem is, we have to do this after every page, and we're loading the font once per page. That is not ideal. I cannot see a way around that.
PEBCAK. I was able to solve this through loading the font at the right place and manual use drop(font)
. Apologies!
Ideally it would be a bit simpler and more intuitive than that, though. I think you've identified a genuine usability problem, if nothing else. Let me come back to you after the Easter break once I've had a chance to examine the PdfFont
constructor code more closely. Perhaps there's a way of avoiding taking that &PdfDocument
reference, or making it cleaner to avoid lifetime hassles.
Yeah, I am actually still struggling with this! It is not ergonomic at all; ideally the font should be usable across the entire lifetime of the document but I've found I've have to allocate a new one much more frequently, which is very slow and presumably inefficient
The Pdfium FPDFText_LoadFont()
and FPDFText_LoadStandardFont()
functions both take FPDF_DOCUMENT
handles. This is why the lifetime of a PdfFont
object is bound to that of a PdfDocument
in pdfium-render
's API.
The need to pass a PdfDocument
reference into the PdfFont
constructor functions could be avoided if fonts were created by PdfDocument
itself (i.e. inversion of control). This could result in a more ergonomic API. Let me think about this over the next day or two.
PS in the meantime, if you are able to share a code sample that demonstrates the difficulty of retaining a PdfFont
across the lifetime of a document, that would be great.
Sure! Here's a simplification of my code:
use pdfium_render::prelude::*;
fn go() -> Result<(), PdfiumError> {
let pdfium = Pdfium::new(
Pdfium::bind_to_library(Pdfium::pdfium_platform_library_name_at_path("./"))
.or_else(|_| Pdfium::bind_to_system_library())?,
);
let mut doc = pdfium.create_new_pdf()?;
let font = PdfFont::new_true_type_from_bytes(&doc, include_bytes!("../font.ttf"), true)?;
let margin = PdfPoints::new(12.0);
let mut page_num = 1;
let mut page = doc.pages_mut().create_page_at_end(PdfPagePaperSize::a4())?;
let mut top = page.height() - margin;
for i in 1..5000 {
if top < margin {
page_num += 1;
page = doc.pages_mut().create_page_at_end(PdfPagePaperSize::a4())?;
top = page.height() - margin;
}
// note that I could load the font here, it'd just be far, far slower and less efficient
let mut object = PdfPageTextObject::new(
&doc,
format!("Item #{}", i),
&font // &PdfFont::helvetica(&doc),
PdfPoints::new(8.0)
)?;
object.translate(margin, top)?;
top = top - margin;
page.objects_mut().add_text_object(object)?;
}
doc.save_to_file("result.pdf")?;
Ok(())
}
fn main() {
go().unwrap();
}
The fundamental problem here is the need to mix mutable and immutable references to PdfDocument
. I believe the only solution is for PdfDocument
to take direct ownership of the PdfFont
object(s) you want to use. I propose adding a new PdfDocument::fonts()
collection to facilitate this. Adding a new PdfFont
to that collection will give you back a token that you can use to refer to the font from that point forward. The token will just be a numeric value that is not bound to a reference of PdfDocument
itself.
Added PdfDocument::fonts()
and PdfDocument::fonts_mut()
accessor functions, providing access to the PdfFonts
collection for a document. Creating a PdfFont
using the PdfFonts
collection returns a PdfFontToken
that can be used to refer to the font later, bypassing issues of lifetime and mutability.
With these changes in place, I can now run your sample like so:
use pdfium_render::prelude::*;
fn main() -> Result<(), PdfiumError> {
let pdfium = Pdfium::new(
Pdfium::bind_to_library(Pdfium::pdfium_platform_library_name_at_path("../pdfium/"))
.or_else(|_| Pdfium::bind_to_system_library())?,
);
let mut doc = pdfium.create_new_pdf()?;
let font = doc
.fonts_mut()
.load_true_type_from_bytes(include_bytes!("../font.ttf"), true)?;
let margin = PdfPoints::new(12.0);
let mut page_num = 1;
doc.pages_mut().create_page_at_end(PdfPagePaperSize::a4())?;
let mut page = doc.pages().last()?;
let mut top = page.height() - margin;
for i in 1..5000 {
if top < margin {
page_num += 1;
page = doc.pages_mut().create_page_at_end(PdfPagePaperSize::a4())?;
top = page.height() - margin;
}
// note that I could load the font here, it'd just be far, far slower and less efficient
let mut object = PdfPageTextObject::new(
&doc,
format!("Item #{} on page #{}", i, page_num),
font, // Note: now passing in a PdfFontToken instead of a &PdfFont
PdfPoints::new(8.0),
)?;
object.translate(margin, top)?;
top = top - margin;
page.objects_mut().add_text_object(object)?;
}
doc.save_to_file("./result.pdf")?;
Ok(())
}
Still to do:
- Pass test suite
- README updates
Updated examples/
to pass full test suite.
Bumped crate version to 0.8.3. Updated README.md
. Made tweaks to internal PdfFont
documentation. Scheduled for release as crate version 0.8.3. Waiting for confirmation from @stephenjudkins that the original problem is resolved.
Original problem is solved! Greatly appreciate the work here.