printpdf icon indicating copy to clipboard operation
printpdf copied to clipboard

Add support for text shaping

Open RagibHasin opened this issue 3 years ago • 1 comments

As I understood the code, printpdf does no text shaping and generates a glyph ID for each code point.

In #73 @fschutt mentioned that, ideally, the shaping engine should be pluggable. Recently, yeslogic/allsorts has emerged as a viable option for text shaping and font substitution in pure Rust, which is already being used in production.

Is that plan still valid? If so, would it be acceptable to switch font subsetting to allsorts along with text shaping?

RagibHasin avatar Jan 15 '23 16:01 RagibHasin

Yeah I know about allsorts, but I never got around to migrating to allsorts.

fschutt avatar Feb 28 '23 16:02 fschutt

ParsedFont now uses allsorts, but there's no text shaping yet. There is, however, an HTML API in progress.

fschutt avatar Mar 08 '25 22:03 fschutt

Shaping was added in 0.8.2, as a pre-step towards HTML content generation.

[dependencies.printpdf]  
version = "0.8.2"
default-features = false
features = ["text_layout"]
fn main() -> Result<(), Box<dyn std::error::Error>> {  
  
    let mut doc = PdfDocument::new("PDF test");  
      
    let font_bytes = std::fs::read("C:\\Windows\\Fonts\\Roboto-Regular.ttf")?;  
    let font = ParsedFont::from_bytes(&font_bytes, 0, &mut Vec::new())?;  
    let font_id = doc.add_font(&font);  
      
    let page_width = Mm(210.0);  
    let page_height = Mm(297.0);  
    let text = "Hello World!";  
  
    let options = TextShapingOptions {  
        font_size: Pt(12.0),  
        // automatically break the text into lines on overflow  
        max_width: Some(page_width.into_pt()),  
        // will automatically center all lines  
        align: TextAlign::Center,  
        ..Default::default()  
    };  
      
    let shaped_text = doc.shape_text(text, &font_id, &options)?;  
      
    // println!("{} x {}", shaped_text.width, shaped_text.height);  
    //   
    // note: only useful if max_width = None to manually center text
  
    // Top left corner of where you want the text to be positioned  
    let text_origin = Point {  
        // X coordinate is already handled by TextAlign::Center  
        x: Pt(0.0),   
        // Y coordinate has to be inversed due to PDF starting the Y at the bottom  
        y: (page_height - Mm(10.0)).into()  
    };  
  
    // Position the text in the page  
    let text_drawing_ops = shaped_text.get_ops(text_origin);  
  
    // Page drawing operations  
    let mut ops = Vec::new();  
  
    // ops.push(Op::SetStrokeColor { .. });  
    // ops.push(Op::SetFillColor { .. });  
    // ops.push(Op::SetTextRenderingMode { .. });  
  
    ops.extend(text_drawing_ops);  
  
    let page = PdfPage::new(page_width, page_height, ops);  
  
    let pdf_bytes = doc  
        .with_pages(vec!\[page\])  
        .save(&PdfSaveOptions::default(), &mut Vec::new());  
  
    std::fs::write("doc.pdf", pdf_bytes)?;  
      
    Ok(())  
}

Proper layouting support will come later, but this should be a start.

fschutt avatar Mar 21 '25 15:03 fschutt