J-F-Liu / lopdf

A Rust library for PDF document manipulation.
MIT License
1.64k stars 176 forks source link

Is there any way to load your font? #219

Open LegendarySaiyan opened 1 year ago

LegendarySaiyan commented 1 year ago

I tried to add my fonts, but without success. Maybe anyone can share the way?

LegendarySaiyan commented 1 year ago

Here is the code:

let font_data = std::fs::read("src/fonts/Arial-Bold.ttf").unwrap();

    let font_dict_id = template_pdf.add_object(dictionary! {
        "Type" => "Font",
        "Subtype" => "TrueType",
        "BaseFont" => "Arial-Bold",
        "Encoding" => "WinAnsiEncoding",
    });

    let font_stream = Stream::new(dictionary!{}, font_data);
    let font_file_id = template_pdf.add_object(font_stream);

    let font_id = template_pdf.add_object(dictionary! {
        "Type" => "Font",
        "Subtype" => "TrueType",
        "BaseFont" => "Arial-Bold",
        "Encoding" => "WinAnsiEncoding",
        "FontDescriptor" => dictionary! {
            "Type" => "FontDescriptor",
            "FontName" => "Arial-Bold",
            "FontFile2" => font_file_id,
            "Flags" => 32,
            "ItalicAngle" => 0,
            "Ascent" => 905,
            "Descent" => -212,
            "CapHeight" => 728,
            "XHeight" => 519,
            "StemV" => 165,
            "FontBBox" => vec![-628, -376, 2000, 1010].into_iter().map(|n| Object::Integer(n)).collect::<Vec<_>>(),
            "CharSet" => "/WinAnsiEncoding",
            "MaxWidth" => 2000,
        },
    });

    let mut operations = vec![
        Operation::new("BT", vec![]),
    ];

    operations.push(Operation::new("BT", vec![]));
    operations.push(Operation::new("F1", vec![font_size.into()]));
    let color_string = format!("{} {} {} rg", color.0, color.1, color.2);
    operations.push(Operation::new(&color_string, vec![]));
    operations.push(Operation::new("Td", vec![position.0.into(), position.1.into()]));
    operations.push(Operation::new("Tf", vec![font_id.into(), font_size.into()]));
    operations.push(Operation::new("Tj", vec![Object::string_literal(text)]));
    operations.push(Operation::new("ET", vec![]));

    let content = Content {
        operations,
    };

    let text_xobject = xobject::form(
        vec![0.0, 0.0, 595.0, 842.0],
        vec![1.0, 0.0, 0.0, 1.0, 0.0, 0.0],
        content.encode().unwrap(),
    );

    let mut page = template_pdf.get_page(page_id).unwrap();
    let resources = page.resources_mut();
    resources.fonts_mut().insert(Name("F1".to_string()), font_id);
    page.add_content(text_xobject.to_content());

    let a = template_pdf.get_page_fonts(page_id);
    println!("{:?}", a);

    let p_o = existing_page_id.iter().next().unwrap();
    let p_oo = *p_o;

    let pages = dictionary! {
        "Type" => "Pages",
        "Kids" => vec![p_oo.into()],
        "Count" => 1,
        "Resources" => font_dict_id,
        "MediaBox" => vec![0.into(), 0.into(), 595.into(), 842.into()],
    };

    template_pdf.objects.insert(pages_id, Object::Dictionary(pages));

}

@J-F-Liu Would you be so kind to give the example for exporting the fonts? :)

J-F-Liu commented 1 year ago

I haven't tried this before, seems also need to embed the font file, see PDF Reference document for details.

BobrImperator commented 1 year ago

I've done this and it seems to be changing the font:

pub fn example() -> () {
    // Load the font data from a file
    let font_data = std::fs::read("ClimateCrisis-Regular-VariableFont_YEAR.ttf").unwrap();

    // Create a stream object for the font data
    let font_stream = Stream::new(dictionary! {}, font_data.clone());

    // Create a document object and add the font and font descriptor to it
    let mut doc = Document::with_version("1.5");
    // Create a font descriptor dictionary object
    let font_stream_id = doc.add_object(font_stream);
    let font_descriptor_dict = dictionary! {
        "Type" => "FontDescriptor",
        "FontName" => "MyFont",
        "FontFile2" => font_stream_id,
        "Flags" => 4,
        "Ascent" => 728,
        "Descent" => -212,
        "CapHeight" => 728,
        "ItalicAngle" => 0,
        "StemV" => 90,
    };
    let font_descriptor_id = doc.add_object(font_descriptor_dict);

    let encoding = dictionary! {
        "Type" => "Encoding",
        "BaseEncoding" => "WinAnsiEncoding",
    };

    // Create a font dictionary object
    let font_dict = dictionary! {
        "Type" => "Font",
        "Subtype" => "TrueType",
        "BaseFont" => "MyFont",
        "FirstChar" => 32,
        "LastChar" => 255,
        // "Widths" => Object::Array((32..=255).map(|c| (c, 600)).collect()),
        "FontDescriptor" => font_descriptor_id,
        "Encoding" => doc.add_object(encoding),
        "Length1" => font_data.len() as i32
    };

    let font_id = doc.add_object(font_dict);

    let content = Content {
        operations: vec![
            Operation::new("BT", vec![]),
            Operation::new("Tf", vec!["MyFont".into(), 48.into()]),
            Operation::new("Td", vec![100.into(), 600.into()]),
            Operation::new(
                "Tj",
                vec![Object::string_literal("Hello World!ążę Ą Ę Ż Ć")],
            ),
            Operation::new("ET", vec![]),
        ],
    };

    let resources_id = doc.add_object(dictionary! {
        "Font" => dictionary! {
            "MyFont" => font_id,
        },
    });

    let pages_id = doc.new_object_id();

    let content_id = doc.add_object(Stream::new(dictionary! {}, content.encode().unwrap()));
    let page_id = doc.add_object(dictionary! {
        "Type" => "Page",
        "Parent" => pages_id,
        "Contents" => content_id,
    });
    let pages = dictionary! {
        "Type" => "Pages",
        "Kids" => vec![page_id.into()],
        "Count" => 1,
        "Resources" => resources_id,
        "MediaBox" => vec![0.into(), 0.into(), 595.into(), 842.into()],
    };
    doc.objects.insert(pages_id, Object::Dictionary(pages));

    let catalog_id = doc.add_object(dictionary! {
        "Type" => "Catalog",
        "Pages" => pages_id,
    });
    doc.trailer.set("Root", catalog_id);
    doc.compress();
    // Create a resources dictionary object and add the font to it
    doc.save("output.pdf").unwrap();

    ()
}

ClimateCrisis Screenshot from 2023-03-11 15-46-05

UbuntuMono Screenshot from 2023-03-11 15-44-44

It feels to me like there's a problem in the parser somewhere and it doesn't treat multi-byte characters correctly. Notice how on my screenshots the latin-extended characters are not shown correctly.

I'm completely unfamiliar with character encoding though, so I'm just guessing here.

splatrac commented 1 year ago

Thanks for the external font loading code. I have the same problem with encoding regarding greek/cyrillic characters. I have realized that when you create a new pdf from scratch, the non regular characters show normally. However when editing an existing one even if it is written with fonts that support extended characters, the extra text that is added appears obfuscated.

LHRchina commented 2 months ago

Thanks for the external font loading code. I have the same problem with encoding regarding greek/cyrillic characters. I have realized that when you create a new pdf from scratch, the non regular characters show normally. However when editing an existing one even if it is written with fonts that support extended characters, the extra text that is added appears obfuscated.

Can you provide more detail for this issue?