不确定如何在Rust中使用”Printpdf 0.5.3″库格式化我的PDF。

huangapple go评论114阅读模式
英文:

Unsure how to format my pdf with the "Printpdf 0.5.3" crate in Rust

问题

我相对于Rust编程比较新,需要一点关于如何实现我的项目的方向。

目前,我正在尝试构建一个命令行工具,它接收一个人的名字、他们申请的职位、他们申请的公司以及该公司的位置,然后自动地将其求职信内容格式化成PDF。最终,我想将其制作成一个图形用户界面工具,可以发送给我的朋友,让他们可以在他们的Windows/Mac机器上使用它。

问题是,我目前正在使用"Printpdf"库来生成文档本身。正如下面的图片所示,文本在达到行末时并不会自动换行/自动断行/自动格式化。不幸的是,我不能简单地将其分割成不同的字符串变量,因为程序需要能够添加任意用户输入并返回一个完美格式化的PDF文件。

如何去格式化这个文档?我应该使用不是"printpdf"的库吗?还是可以使用其他方法让文本按我想要的方式显示?

以下是Rust代码和PDF输出的图片。

  1. use std::io;
  2. use printpdf::*;
  3. use std::fs::File;
  4. use std::io::BufWriter;
  5. fn main() {
  6. println!("Enter: Position");
  7. let mut position = String::new();
  8. io::stdin()
  9. .read_line(&mut position)
  10. .expect("failed to read");
  11. println!("Enter: Company Name");
  12. let mut coname = String::new();
  13. io::stdin()
  14. .read_line(&mut coname)
  15. .expect("failed to read");
  16. println!("Enter: Company Location");
  17. let mut location = String::new();
  18. io::stdin()
  19. .read_line(&mut location)
  20. .expect("failed to read");
  21. let ntxt = " ";
  22. let sample = "This is some random Sample text, This text should eventually be a user input. Currently, this text is not a user input. this text is supposed to be a text of long string data that will eventually be added to the document via user input";
  23. let (doc, page1, layer1) = PdfDocument::new("PDF_Document_title", Mm(216.0), Mm(279.0), "Layer 1");
  24. let current_layer = doc.get_page(page1).get_layer(layer1);
  25. let font = doc.add_external_font(File::open("./fonts/TNR-Regular.ttf").unwrap()).unwrap();
  26. current_layer.use_text(position.clone(), 14.0, Mm(25.0), Mm(250.0), &font);
  27. current_layer.use_text(coname.clone(), 14.0, Mm(25.0), Mm(240.0), &font);
  28. current_layer.use_text(location.clone(), 14.0, Mm(25.0), Mm(230.0), &font);
  29. current_layer.use_text(ntxt.clone(), 14.0, Mm(25.0), Mm(220.0), &font);
  30. current_layer.begin_text_section();
  31. current_layer.set_font(&font, 14.0);
  32. current_layer.set_text_cursor(Mm(25.0), Mm(210.0));
  33. // write one line, but write text2 in superscript
  34. current_layer.write_text(sample.clone(), &font);
  35. current_layer.end_text_section();
  36. doc.save(&mut BufWriter::new(File::create("test_working.pdf").unwrap())).unwrap();
  37. }

PDF输出图片

我真的不知道从哪里开始解决这个问题。

我尝试查看"Textwrap"库的文档,但它对我来说看起来像一个谜。我还查看了"Printpdf"的文档,但它说它不支持格式化和对齐。

英文:

I am relatively new to programming in rust and need a bit of direction on how to implement my project.

Currently, I am trying to build a CLI tool that takes a persons name, position they are applying too, company they are applying too and the location of that company and auto-magically formats a PDF with the contents of their cover letter. eventually, i want to make this into a GUI tool that I can send to my friends so they can use it on their windows/mac machines.

the problem right now is that I am using the "Printpdf" crate to generate the document itself. as you can see with the picture below, the text doesn't automatically wrap/line-break/format when it reaches the end of the line. unfortunately, I can not simply break this up into different string variables as the program needs to be able to add arbitrary user input and return a perfectly formatted PDF file.

How to I go about formatting this document? should I use a crate that isn't "printpdf"? or can I use something else to get the text to behave the way I want?

here is the rust code and a picture of what the PDF outputs.

  1. use std::io;
  2. use printpdf::*;
  3. use std::fs::File;
  4. use std::io::BufWriter;
  5. fn main() {
  6. println!("Enter: Position");
  7. let mut position = String::new();
  8. io:: stdin()
  9. .read_line(&mut position)
  10. .expect("failed to read");
  11. println!("Enter: Company Name");
  12. let mut coname = String::new();
  13. io:: stdin()
  14. .read_line(&mut coname)
  15. .expect("failed to read");
  16. println!("Enter: Company Location");
  17. let mut location = String::new();
  18. io:: stdin()
  19. .read_line(&mut location)
  20. .expect("failed to read");
  21. let ntxt = " ";
  22. let sample = "This is some random Sample text, This text should eventaully be a user input. Currently, this text is not a user input. this text is supposed to be a text of long string data that will eventually be added to the document via user input";
  23. let (doc, page1, layer1) = PdfDocument::new("PDF_Document_title", Mm(216.0), Mm(279.0), "Layer 1");
  24. let current_layer = doc.get_page(page1).get_layer(layer1);
  25. let font = doc.add_external_font(File::open("./fonts/TNR-Regular.ttf").unwrap()).unwrap();
  26. current_layer.use_text(position.clone(), 14.0, Mm(25.0), Mm(250.0), &font);
  27. current_layer.use_text(coname.clone(), 14.0, Mm(25.0), Mm(240.0), &font);
  28. current_layer.use_text(location.clone(), 14.0, Mm(25.0), Mm(230.0), &font);
  29. current_layer.use_text(ntxt.clone(), 14.0, Mm(25.0), Mm(220.0), &font);
  30. current_layer.begin_text_section();
  31. current_layer.set_font(&font, 14.0);
  32. current_layer.set_text_cursor(Mm(25.0), Mm(210.0));
  33. // write one line, but write text2 in superscript
  34. current_layer.write_text(sample.clone(), &font);
  35. current_layer.end_text_section();
  36. doc.save(&mut BufWriter::new(File::create("test_working.pdf").unwrap())).unwrap();
  37. }

the pdf output

I don't really know where to start with fixing this issue.

I have tried to look at the documentation for the crate "textwrap" but it looks like an utter enigma too me. I also looked at the documentation for "printpdf" but it says it doesn't support formatting and alignment.

答案1

得分: 0

这个代码的主要作用如下:

  1. 加载 font_data 作为字节的向量 Vec<u8>glyph_brush_layoutprintpdf 都会独立地将这个字体作为字节切片 &[u8] 进行处理。
  2. 使用 glyph_brush_layout 来计算字形的位置*(示例是在 README 中适配的),具体是在 160 毫米宽的区域内获取垂直 y 位置。
  3. 将字形根据这些 y 位置进行分组(它们是每行文本基线的垂直位置),以及它们在文本中的索引(其中文本 = sample)。这里使用了 itertools 使这一过程更容易。
  4. 取每个分组中的第一个字形(group.next().unwrap().0),获取其索引(以及垂直位置)。这些索引被收集到一个向量中。
  5. 遍历这个向量,将文本写入 PDF。为了拆分文本,创建了一个可预览的迭代器,这样我们可以为当前索引和下一个(预览的)索引获取文本 sample 的切片。由于 printpdfglyph_brush_layout 处理布局的方式不同,所以需要进行一些垂直偏移和转换。
  • 这里的假设是一个字形等于文本中的一个字符,即 assert_eq!(glyphs.len(), sample.chars().count());。如果不是这种情况,也许你需要考虑一次直接定位一个字形。

此外,代码还包含了与用户输入相关的内容以及 PDF 的创建和保存部分。

英文:

There is probably a more sane way to do this, but this works:

  1. We load the font_data as a Vector of bytes Vec&lt;u8&gt;; both glyph_brush_layout and printpdf then independently work with that font as a slice of bytes &amp;[u8].
  2. We use glyph_brush_layout to calculate the position of the glyphs* (I just adapted the example in the README), specifically getting the vertical y positions in a box 160mm wide.
  3. We group the glyphs on those y positions (they are the vertical positions of the baseline of each line of text), along with their index into the text (where text = sample). I used itertools to make this easier.
  4. We take just the first glyph in each group (group.next().unwrap().0), getting its index (and y position). These indexes are the positions at which we will split the text into individual lines. These are collected into a Vector.
  5. We loop over the Vector, writing the text to the PDF. To split the text we create a peekable iterator, so we can take the slice of the text sample for the current index and the next (peeked) index. Because of the different ways that printpdf and glyph_brush_layout deal with layout, we need to do some vertical offsets and conversions.

*The assumption here is that one glyph equals one character in the text, i.e. assert_eq!(glyphs.len(), sample.chars().count());. If that's not the case, maybe you want to consider positioning one glyph at a time, directly.

不确定如何在Rust中使用”Printpdf 0.5.3″库格式化我的PDF。

main.rs

  1. use printpdf::*;
  2. use std::fs::File;
  3. use std::io::BufWriter;
  4. use std::io::{self, Read};
  5. fn main() {
  6. println!(&quot;Enter: Position&quot;);
  7. let mut position = String::new();
  8. io::stdin()
  9. .read_line(&amp;mut position)
  10. .expect(&quot;failed to read&quot;);
  11. println!(&quot;Enter: Company Name&quot;);
  12. let mut coname = String::new();
  13. io::stdin().read_line(&amp;mut coname).expect(&quot;failed to read&quot;);
  14. println!(&quot;Enter: Company Location&quot;);
  15. let mut location = String::new();
  16. io::stdin()
  17. .read_line(&amp;mut location)
  18. .expect(&quot;failed to read&quot;);
  19. let ntxt = &quot; &quot;;
  20. let sample = &quot;This is some random Sample text, This text should eventaully be a user input. Currently, this text is not a user input. this text is supposed to be a text of long string data that will eventually be added to the document via user input&quot;;
  21. let (doc, page1, layer1) =
  22. PdfDocument::new(&quot;PDF_Document_title&quot;, Mm(216.0), Mm(279.0), &quot;Layer 1&quot;);
  23. let current_layer = doc.get_page(page1).get_layer(layer1);
  24. // load the font data for the font &quot;Times New Roman&quot;
  25. let font_data = {
  26. let mut font_file = File::open(&quot;./times-new-roman.ttf&quot;).unwrap();
  27. let mut font_data = Vec::with_capacity(font_file.metadata().unwrap().len() as usize);
  28. font_file.read_to_end(&amp;mut font_data).unwrap();
  29. font_data
  30. };
  31. // load the font reference for glyph_brush_layout
  32. let gbl_font = glyph_brush_layout::ab_glyph::FontRef::try_from_slice(&amp;font_data).unwrap();
  33. // put it into a slice of glyph_brush_layout font references
  34. let gbl_fonts = &amp;[gbl_font];
  35. // load the font reference for printpdf
  36. let font = doc.add_external_font(font_data.as_slice()).unwrap();
  37. current_layer.use_text(position.clone(), 14.0, Mm(25.0), Mm(250.0), &amp;font);
  38. current_layer.use_text(coname.clone(), 14.0, Mm(25.0), Mm(240.0), &amp;font);
  39. current_layer.use_text(location.clone(), 14.0, Mm(25.0), Mm(230.0), &amp;font);
  40. current_layer.use_text(ntxt, 14.0, Mm(25.0), Mm(220.0), &amp;font);
  41. // calculate the glyph positions using glyph_brush_layout
  42. use glyph_brush_layout::ab_glyph::Font;
  43. use glyph_brush_layout::GlyphPositioner;
  44. let glyphs = glyph_brush_layout::Layout::default().calculate_glyphs(
  45. gbl_fonts,
  46. &amp;glyph_brush_layout::SectionGeometry {
  47. // width 160mm = 210mm - 2 * 25mm margins; height unbounded
  48. bounds: (mm_to_px(160.0), f32::INFINITY),
  49. ..Default::default()
  50. },
  51. &amp;[glyph_brush_layout::SectionText {
  52. text: sample,
  53. scale: gbl_fonts[0].pt_to_px_scale(14.0).unwrap(),
  54. font_id: glyph_brush_layout::FontId(0),
  55. }],
  56. );
  57. // make sure the number of glyphs matches the number of chars in the sample text
  58. assert_eq!(glyphs.len(), sample.chars().count());
  59. // group the glyphs by y position
  60. use itertools::Itertools;
  61. let line_starts = glyphs
  62. .iter()
  63. .enumerate() // enumerate will give us the start index into the sample text of the start of the line
  64. .group_by(|(_, glyph)| glyph.glyph.position.y) // group by &quot;y&quot; which is effectively equivalent to the index of the line
  65. .into_iter()
  66. .map(|(y, mut group)| (y, group.next().unwrap().0))
  67. .collect::&lt;Vec&lt;_&gt;&gt;();
  68. // get the minimum y position
  69. // you could get the max a similar way, if you needed to calculate the vertical size of the text,
  70. // for example if you wanted to lay out text below it
  71. let min = glyphs
  72. .iter()
  73. .map(|glyph| glyph.glyph.position.y)
  74. .fold(f32::INFINITY, |a, b| a.min(b));
  75. // we need a peekable iterator so we can see where the next line starts
  76. let mut iter = line_starts.iter().peekable();
  77. // iterate over the line_starts and draw the text
  78. loop {
  79. // get the next line start, if there is none then we break out of the loop
  80. let Some((y, start)) = iter.next() else {
  81. break;
  82. };
  83. // peek into the line start after that to get the end index,
  84. // if there is none (we&#39;re at the last line in the loop), then we use the length of the sample text
  85. let end = if let Some((_, end)) = iter.peek() {
  86. *end
  87. } else {
  88. sample.chars().count()
  89. };
  90. // slice up the text
  91. // if you know you&#39;re only dealing with ASCII characters you can simplify this as
  92. // `let line = &amp;sample[*start..end];`
  93. // which saves on an allocation to a String;
  94. // or you can use char_indices to get the byte indices and slice that way
  95. let line = sample
  96. .chars()
  97. .skip(*start)
  98. .take(end - start)
  99. .collect::&lt;String&gt;();
  100. // draw the text
  101. current_layer.use_text(
  102. line.trim(),
  103. 14.0,
  104. Mm(25.0),
  105. // printpdf up = y positive, but glyph_brush_layout down = y positive
  106. Mm(210.0 + px_to_mm(min) - px_to_mm(*y)),
  107. &amp;font,
  108. );
  109. }
  110. doc.save(&amp;mut BufWriter::new(
  111. File::create(&quot;test_working.pdf&quot;).unwrap(),
  112. ))
  113. .unwrap();
  114. }
  115. /// glyph_brush_layout deals with f32 pixels, but printpdf deals with f64 mm.
  116. fn px_to_mm(px: f32) -&gt; f64 {
  117. px as f64 * 3175.0 / 12000.0
  118. }
  119. /// printpdf deals with f64 mm, but glyph_brush_layout deals with f32 pixels.
  120. fn mm_to_px(mm: f64) -&gt; f32 {
  121. (mm * 12000.0 / 3175.0) as f32
  122. }

cargo.toml

  1. [package]
  2. name = &quot;generate-pdf&quot;
  3. version = &quot;0.1.0&quot;
  4. edition = &quot;2021&quot;
  5. [dependencies]
  6. glyph_brush_layout = &quot;0.2.3&quot;
  7. itertools = &quot;0.10.5&quot;
  8. printpdf = &quot;0.5.3&quot;

huangapple
  • 本文由 发表于 2023年3月7日 12:59:07
  • 转载请务必保留本文链接:https://go.coder-hub.com/75658182.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定