【Rust在WASM中实现pdf文件的生成】

Rust在WASM中实现pdf文件的生成

  • 前言
  • 概念和依赖
  • 问题描述
  • 分步实现
  • pdf转Blob生成URL两种方式
  • 利用localstorage传递参数
  • 处理图片Vec<u8>到pdf格式的Vec<u8>
  • 使用rust创建iframe显示pdf的Blob
  • 最后

前言

实现了一个通用的前端jpg转pdf的wasm,因为动态响应框架无法直接打印,格式会变,结合domtoimge,可以实现任意元素转jpg然后,在本地转pdf.
已上传源码,含dist,可直接使用,也可修改源码,变成自己的样子.

dist,放到任何位置, 要用时存储localstorage,并跳转过来就好了., 根据图片高宽比例,自动全屏,自动页面横竖调节…
下载地址:
https://download.csdn.net/download/wjcroom/90078261
https://github.com/wjcroom/hi/blob/master/jpg2pdf_wasm_src.tar.gz

概念和依赖

. WASM
WebAssembly(简称WASM)是一个虚拟指令集体系架构(virtual ISA),旨在为C/C++等语言编写的程序提供一种高效的二进制格式,使其能够在Web平台上以接近原生应用的运行速度运行‌。‌

跨平台‌:WebAssembly兼容所有主流浏览器,如Chrome、Firefox、Safari和Edge。Rust编写的代码可以轻松移植到不同的平台‌。
. Rust
Rust是一种系统编程语言,以其内存安全和高性能著称,是开发WebAssembly应用的理想选择‌‌
. Trunk
vesion 0,21,4
rustup 1.21.7
cargo 1.81
Trunk 是一款专为 Rust 语言设计的 WASM 网页应用打包工具。它能够帮助开发者轻松构建、打包并发布 Rust 编写的 WASM 应用到 Web 平台。Trunk 的设计理念是简单、高效,通过一个源 HTML 文件,Trunk 可以自动处理 WASM、JS 片段以及其他资源(如图片、CSS、SCSS)的打包工作。
本文的大部分依照这个而来:https://trunkrs.dev/guide/getting-started/index.html
项目:https://github.com/trunk-rs/trunk/tree/main
. printpdf
目前使用0.7的版本,
png入文档有色彩空间的问题,转为jpg格式.
https://docs.rs/printpdf/latest/printpdf/index.html
这是一个rust实现的生成pdf的工具,目前documnet-info.rs会在wasm中有两处错误。删除出错行就能在wasm环境使用。

问题描述

https://blog.csdn.net/wjcroom/article/details/143548767
在这个文章中,虽然使用了后端flask生成pdf,但唯一目的是打印,后端完全无必要来回传数据。本文要实现的wasm将图片在本地转pdf并显示。
因为这是一个验证性的工作,不具备很大的实际意义。但此架构可以构造一些功能强大本地wasn应用,比如,批量格式化一批数据到pdf格式,套用模板打印等等。

分步实现

  • 图片到pdf的转换
    先建立空项目
$ cargo new hello_cargo
$ cd hello_cargo

本文所用printpdf,要想开始image功能需要在Cargo.toml文件定义以下内容

[dependencies]
printpdf = { version = "0.7.0", features = [ "embedded_images" ] }

然后在main中测试主逻辑,拷贝printpdf的演示代码

fn main() {println!("Hello, world!");let (doc, page1, layer1) = PdfDocument::new("PDF_Document_title",  Mm(210.0),Mm(297.0), "Layer 1");
let current_layer = doc.get_page(page1).get_layer(layer1);// currently, the only reliable file formats are bmp/jpeg/png
// this is an issue of the image library, not a fault of printpdf
let mut image_file = File::open("tmp.png").unwrap();
let image = Image::try_from( image_crate::codecs::png::PngDecoder::new(&mut image_file).unwrap()).unwrap();
let rotation_center_x = Px((image.image.width.0 as f32 / 2.0) as usize);
let rotation_center_y = Px((image.image.height.0 as f32 / 2.0) as usize);// layer,
image.add_to_layer(current_layer.clone(),ImageTransform {rotate: Some(ImageRotation {angle_ccw_degrees: 0.0,rotation_center_x,rotation_center_y,}),scale_x:  Some( 2.4),scale_y:   Some( 2.4),translate_x: Some(Mm(9.0)),translate_y: Some(Mm(9.0)),..Default::default()},
);doc.save(&mut BufWriter::new(File::create("test_working.pdf").unwrap())).unwrap();
}

最终会生成来自png的一个pdf,
scale_x: Some( 2.4),
scale_y: Some( 2.4), 这两个参数是x,y的放大倍数。以适应页面。translate_x这是距离左下角的,位置。
经过验证,png在0.7下有问题.改为jpg

  • 建立trunk项目

rustup target add wasm32-unknown-unknown
cargo install --locked trunk
cargo new trunk-hello-world
cd trunk-hello-world

从https://github.com/trunk-rs/trunk/tree/main 的example目录,拷贝一个示例的内容,集成了printpdf是这样,
大家可以删除pdf的部分,只体会一下trunk的懒汉能量。

 use printpdf::*;
use std::fs::File;
use std::io::BufWriter;
use web_sys::window;
fn start_app( show:&str) {let document = window().and_then(|win| win.document()).expect("Could not access document");let body = document.body().expect("Could not access document.body");let text_node = document.create_text_node( &format!("Hello, world from Vanilla Rust!{}",show) );body.append_child(text_node.as_ref()).expect("Failed to append text");}fn main() {console_error_panic_hook::set_once();let (doc, page1, layer1) = PdfDocument::new("PDF_Document_title", Mm(247.0), Mm(210.0), "Layer 1");
let (page2, layer1) = doc.add_page(Mm(10.0), Mm(250.0),"Page 2, Layer 1");
let pdf_bytes = doc.save_to_bytes().unwrap();// doc.save(&mut BufWriter::new(File::create("/tmp/test_working.pdf").unwrap())).unwrap();   start_app(&String::from_utf8_lossy(&pdf_bytes)) ;
}

这部分代码会显示生成空白的pdf字节的utf-8编码形式,不能显示的略过.

String::from_utf8_lossy(&pdf_bytes)

在这里插入图片描述

pdf转Blob生成URL两种方式

Blob做为一个对二进制的包装,可以保存pdf的二进制数据,的rust的printpdf中是vec,呈现Blob:url的两个办法,

  1. 从rust-wasm函数传出vec,用js呈现,创建blob和url.在trunk项目的main.rs加入如下定义
#[wasm_bindgen]
pub fn png2pdf(pnginput: &[u8]) -Vec<u8>> {------
let pdf_bytes = doc.save_to_bytes().unwrap();
rturn pdf_bytes
}

然后的js代码中,当 加载和启动wasm模块已经结束.就可以如同js函数一样调用.

const pdfbuf=wasmBindings.png2pdf(bufferArray);const blob = new Blob([pdfbuf], { type: 'application/pdf' }); // 创建一个URL指向Blob对象,必须用数组const blobUrl = URL.createObjectURL(blob);// 打开新的标签页并导航到PDF文件的URLwindow.open(blobUrl,"_self");

其中 const blob = new Blob([pdfbuf], …必须用数组包裹pdfbuf.
接下来的方法,因为没有用数组,导致Blob无法解析成pdf.受这个js Blob的启发,在rust的Blob参数加入Vec.

2… 在rust中使用web_sys,调用window,document等实现显示pdf的blob.主要涉及插入iframe,并生成pdf,blob的URL对象
首先的Cargo.toml定义一些引用.

[dependencies]
console_error_panic_hook = "0.1.7"
wasm-bindgen = "0.2.96"
//js-sys = "0.3.10"
printpdf = { version = "0.7.0", features = [ "embedded_images" ] }
web-sys = { version = "0.3.73", features = ["Window", "Document", "HtmlElement","Blob", "Url", "BlobPropertyBag","Element","Text"] }

定义显示pdf的函数

use printpdf::*;
use std::fs::File;
use std::io::BufWriter;
use web_sys::window;
use web_sys::Element;
use web_sys::Blob;
use web_sys::Url;
use web_sys::BlobPropertyBag;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsValue;fn start_app( show:Vec<u8>) {let document = window().and_then(|win| win.document()).expect("Could not access document");let body = document.body().expect("Could not access document.body");let text_node = document.create_text_node( &format!("Hello, world from Vanilla Rust!{}","show") );let  theiframe :Element = document.create_element_ns(Some(""),"div").unwrap();let jsv=JsValue::from(show);let s:Vec<JsValue>=vec![jsv]; //注意必须包装成数组.JsValue是数组类型.is_array()let jsv1=JsValue::from(s);let tye=BlobPropertyBag::new();tye.set_type("application/pdf");let blob=Blob::new_with_u8_array_sequence_and_options(&jsv1,&tye).unwrap();let curl=Url::create_object_url_with_blob(&blob).unwrap(); theiframe.set_inner_html(&format!("<iframe src='{}' width='1100px' height='600px'></iframe>",curl ));// let ifm= theiframe.dyn_into::<HtmlIFrameElement>().unwrap().clone();//ifm.set_src="d";body.append_child(text_node.as_ref()).expect("Failed to append text");body.append_child(theiframe.as_ref()).expect("Failed to append text");}
fn main() {console_error_panic_hook::set_once();let (doc, page1, layer1) = PdfDocument::new("PDF_Document_title", Mm(247.0), Mm(210.0), "Layer 1");
let (page2, layer1) = doc.add_page(Mm(20.0), Mm(250.0),"Page 2, Layer 1");
let pdf_bytes = doc.save_to_bytes().unwrap();// doc.save(&mut BufWriter::new(File::create("/tmp/test_working.pdf").unwrap())).unwrap();   start_app(pdf_bytes) ;}

这两种方法就是我找到的展现pdf的方式,其中也可接受png的字节数据.
剩下的就是集成了.
在这里插入图片描述

利用localstorage传递参数

放入png图片的dataurl,把这段代码放在trunk目录,index.html的开头,就能在调试过程中,指定一个图片,并且把base64的url放入localstorage.用于后期wasm,功能的调试.

<input type="file" id="imageInput" accept="image/png" /><br><img  id ="smp" src="" style="bgcolor='#ffeedd'"></img>
<script>
document.getElementById('imageInput').addEventListener('change', function(e) {// 获取文件引用const file = e.target.files[0];if (!file) {return;}// 创建FileReader来读取文件const reader = new FileReader();reader.onload = function(event) {// 当文件读取完成后,event.target.result就是DataURLconst dataURL = event.target.result;// 将DataURL存储到localStoragelocalStorage.setItem('imageDataURL', dataURL);document.getElementById("smp").src=  dataURL// 可以在这里添加代码来使用存储的DataURL,例如显示图片等};// 以DataURL的形式读取文件reader.readAsDataURL(file);
});
</script>

利用rust取出放入的值,先要在Cargo.toml中启用 web_sys的"Storage",feature.
然后获取 get_item()

    let winw=   window().unwrap();let iStorage= winw.local_storage().unwrap().unwrap();let geti=iStorage.get_item("imageDataURL").unwrap().unwrap() ;let v= data_url_to_bytes(&geti).unwrap();

转换为vec的data_url_to_bytes函数如下

use data_url::{DataUrl};fn data_url_to_bytes(data_url: &str) -> Result<Vec<u8>, &'static str> {// 尝试从提供的字符串创建一个DataUrl实例let data_url = DataUrl::process(data_url).map_err(|_| "Invalid DataURL format")?;// 确认数据部分的MIME类型是否为PNG图片if data_url.mime_type().type_!= "image" {return Err("Not a PNG image");}let (body, fragment) = data_url.decode_to_vec().unwrap();// 返回Base64解码后的数据Ok( body) }//百度AI给了个大概,一些错误排除了一个.使用data-url = "0.3.1"这个模块

处理图片Vec到pdf格式的Vec

最后的最后,将图片的vec 读入buffreader,并传递给pngdecoder,生成要插入的img,并且对img高和宽,取一个最小的放大倍数.把pdf,结果存成bytes

 let (doc, page1, layer1) = PdfDocument::new("PDF_Document_title",  Mm(210.0),Mm(297.0), "Layer 1");let current_layer = doc.get_page(page1).get_layer(layer1);let reader = BufReader::new(&v[..]);  //v就就上面转换来的png
let img2 = Image::try_from( image_crate::codecs::png::PngDecoder::new(reader).unwrap()).unwrap();
//let image=RawImage::decode_from_bytes(v).unwrap(); let wid= Px((img2.image.width.0 as f32 / 2.0) as usize);//以一张A4大小的纸张为例,满页打印范围约为20×27.5cm ,以300dpi的解析度打印的话,换算成像素约为2362×3248, let scalex:f32= 2302.0 as f32/img2.image.width.0  as f32;let scaley:f32= 3248.0 as f32/img2.image.height.0  as f32;let scale = f32::min(scaley,scalex);alert(&format!("{}",scale));img2.add_to_layer(current_layer.clone(), ImageTransform {scale_x:  Some( scale),scale_y:   Some( scale ),translate_x: Some(Mm(8.0)),translate_y: Some(Mm(8.0)),..Default::default()},
);let pdf_bytes = doc.save_to_bytes().unwrap();

使用rust创建iframe显示pdf的Blob

let jsv=JsValue::from(pdf_bytes);let s:Vec<JsValue>=vec![jsv];let jsv1=JsValue::from(s);let tye=BlobPropertyBag::new();tye.set_type("application/pdf");let blob=Blob::new_with_u8_array_sequence_and_options(&jsv1,&tye).unwrap();
//let blob=Blob::new_with_u8_array_sequence(&jsv).unwrap();let curl=Url::create_object_url_with_blob(&blob).unwrap(); let  theiframe :Element = document.create_element_ns(Some(""),"div").unwrap();theiframe.set_inner_html(&format!("<iframe src='{}' width='1100px' height='600px'></iframe>",curl ));// let ifm= theiframe.dyn_into::<HtmlIFrameElement>().unwrap().clone();//ifm.set_src="d";body.append_child(text_node.as_ref()).expect("Failed to append text");body.append_child(theiframe.as_ref()).expect("Failed to append text");

好至此全部内容结束.
在这里插入图片描述

最后

pdf的blob显示在一个iframe 中 的js代码:

<!DOCTYPE html>
<html>
<head><title>PDF Viewer</title>
</head>
<body><iframe id="pdfViewer" width="100%" height="800px"></iframe><script>// 假设这是你已经有的PDF Blob数据var pdfBlob = yourPDFBlobData; // 这里应该是你实际的Blob数据// 创建一个新的URL,指向这个Blobvar url = URL.createObjectURL(pdfBlob);// 获取iframe元素并设置其src属性为PDF的URLvar viewer = document.getElementById('pdfViewer');viewer.src = url;</script>
</body>
</html>

trunk sever是边改,边自动编译的命令,但是有个别时候,需要手工中止,ctr +c、然后再启动一次。

运行无错,均已实现后可truck build,生成的内容,一个*.wasm , 一个js,一个html、
参考html加入两个库到现有的页面,即可实现本文的要求了。
细节请根据文档调整。
由于时间仓促,且实际已无大的阻碍,当前没有实现最终版。所以本文暂时如此,
12/1后期看情形,将更新为上线运行版的代码。
12/3在一天以后,已经做了最后的更新,目前代码可以,从localstorage获取png的base64. rust获取处理成bytes后,用pdf,加载,然后在页面插入iframe,并显示此pdf.并且定义了,高和宽的自动缩放,然后没有启动压缩,据说发布版本可以压缩,从5M,下降到几百kb.
好了,我先realse一下看看.
12/4几天前我也不知怎么上线运行,既然要搞得有个结果,何况自己的会议签到还等着出替代方案.改服务端为本地转换,4号修复了png显示不全的问题,改为jpg.因为实在想出现一个能用的wasm,pdf生成的东西.所以最后调整了,自动横排,自动扩展.就匆忙上线了.本来放入pan.ezdial.cn.怎耐免费透传总是停机换参数. 比较气人.索性源码和二进制dist放入csdn下载.
效果
在这里插入图片描述

结束了.

特别感谢 【前端柒八九】提供的 Rust赋能前端:纯血前端将table导出excel提供的启发。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/484158.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CanFestival移植到STM32 F4芯片(基于HAL库)

本文讲述如何通过简单操作就可以把CanFestival库移植到STM32 F4芯片上&#xff0c;作为Slave设备。使用启明欣欣的工控板来做实验。 一 硬件连接 观察CAN报文需要专门的设备&#xff0c;本人从某宝上买了一个兼容PCAN的开源小板子&#xff0c;二十几块钱&#xff0c;通过USB接…

Cursor+Devbox AI开发快速入门

1. 前言 今天无意间了解到 Cursor 和 Devbox 两大开发神器,初步尝试以后发现确实能够大幅度提升开发效率,特此想要整理成博客以供大家快速入门. 简单理解 Cursor 就是一款结合AI大模型的代码编辑器,你可以将自己的思路告诉AI,剩下的目录结构的搭建以及项目代码的实现均由AI帮…

Redis常见问题总结

Redis常见问题总结 1.Redis分布式存储方案 分布式存储核心特点主从&#xff08;Master/Slave&#xff09;模式一主多从&#xff0c;故障时手动切换。哨兵&#xff08;Sentinel&#xff09;模式有哨兵的一主多从&#xff0c;主节点故障自动选择新的主节点。集群&#xff08;Cl…

Svn如何切换删除账号

记录Svn清除切换账号 1.首先打开小乌龟的设置如下图 打开设置后单击已保存数据&#xff0c;然后选择清除 接上图选择清除后&#xff0c;就可以打勾选择清除已保存的账号&#xff0c;我们再次检出的就可以切换账号了 &#x1f449;总结 本次记录Svn清除切换账号 如能帮助到你…

电子应用设计方案-38:智能语音系统方案设计

智能语音系统方案设计 一、引言 智能语音系统作为一种便捷、自然的人机交互方式&#xff0c;正逐渐在各个领域得到广泛应用。本方案旨在设计一个高效、准确、功能丰富的智能语音系统。 二、系统概述 1. 系统目标 - 实现高准确率的语音识别和自然流畅的语音合成。 - 支持多种语…

红外跟随避障模块详解

在智能车、机器人和自动化等领域避障技术是确保安全和高效运行的关键。红外避障模块作为一种常见的避障解决方案&#xff0c;因其非接触、响应速度快和抗干扰能力强等优点而备受青睐。本文将详细介绍红外避障模块的特点、工作原理、以及应用案例&#xff0c;帮助您更好地了解这…

数据下载实践教程系列:跨过数据获取障碍---TCIA和TCGA数据下载

1.前言 作为一个医工交叉领域的工科学者&#xff0c;我想你必定听说过TCGA数据库和TCIA数据库&#xff0c;但是身边不少生信学者和医生是会用的&#xff0c;但大都将此作为护城河而讳莫如深&#xff01;有了数据&#xff0c;工科小伙伴也可以摆脱数据依赖而独立进行研究了。作为…

期权懂|场内个股期权开户流程有哪些?

期权小懂每日分享期权知识&#xff0c;帮助期权新手及时有效地掌握即市趋势与新资讯&#xff01; 场内个股期权开户流程有哪些&#xff1f; 场内个股期权开户第一步开户‌&#xff1a; 投资者首先需要在具有期权交易资格的证券公司开立期权账户。 ‌场内个股期权开户第二步选…

Qt复习学习

https://www.bilibili.com/video/BV1Jp4y167R9/?spm_id_from333.999.0.0&vd_sourceb3723521e243814388688d813c9d475f https://subingwen.cn/qt/qt-primer/#1-4-Qt%E6%A1%88%E4%BE%8B https://subingwen.cn/qt/ https://download.qt.io/archive/qt/1.1Qt的特点 1.2QT中的…

Qt开源控件:图像刻度轴绘制器 (附源码)工程项目私信博主

项目简介 图像刻度轴绘制器是一款基于 Qt/C 开发的小型绘图工具&#xff0c;旨在实现带有刻度轴的图像显示功能。该项目主要用于需要精确测量或标注图像坐标的场景。通过左侧和底部的坐标轴以及对应的刻度线&#xff0c;可以直观地了解图像内容在二维空间中的位置。 项目功能 …

【Transformer序列预测】Pytorch中构建Transformer对序列进行预测源代码

Python&#xff0c;Pytorch中构建Transformer进行序列预测源程序。包含所有的源代码和数据&#xff0c;程序能够一键运行。此程序是完整的Transformer&#xff0c;即使用了Encoder、Decoder和Embedding所有模块。源程序是用jupyterLab所写&#xff0c;建议分块运行。也整理了.p…

mac 安装python3和配置环境变量

mac 安装python3和配置环境变量 前言怎样选择python3的版本python3的安装1、去官网下载安装包2、下载完成后直接解压,检查安装是否成功 前言 在学习python的第一步就是安装它和配置他的环境变量&#xff0c;那么选择哪个版本的python你可曾知道&#xff0c;下面就讲解怎样选择…

基于MFC实现的人机对战五子棋游戏

基于MFC实现的人机对战五子棋游戏 1、引言 此报告将详细介绍本次课程设计的动机、设计思路及编写技术的详细过程&#xff0c;展现我所学过的C知识以及我通过本次课程设计所学到例如MFC等知识。在文档最后我也会记录我所编写过程遇到的问题以及解决方案。 1.1 背景 五子棋是…

6.824/6.5840 Lab 4: Fault-tolerant Key/Value Service

We are the champions my friend And well keep on fighting till the end We are the champions ——We Are The Champions 完整代码见&#xff1a; GitHub - SnowLegend-star/6.824: As we advance, the trials grow ever more arduous, and now we stand before an even mig…

ShardingSphere 数据库中间件

数据库中的数据量猛增&#xff0c;访问性能也变慢了&#xff0c;优化迫在眉睫 ? 1. 关系型数据库本身比较容易成为系统瓶颈&#xff1a;单机存储容量、数据库连接数、处理能力都有限。 2. 当单表的数据量达到 1000W 或 100G 以后&#xff0c;由于查询维度较多&#xff0c;即使…

Webpack Tree Shaking 技术原理及应用实战,优化代码,精简产物

前言 在前端开发中&#xff0c;优化代码体积和提升应用性能是至关重要的课题。Webpack 提供了多种优化手段来帮助开发者实现这一目标&#xff0c;Tree Shaking 就是其中一种非常重要的优化技术&#xff0c;它通过在编译阶段移除未被使用的代码模块&#xff0c;从而显著减小最终…

【热门主题】000075 探索嵌入式硬件设计的奥秘

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 【热…

[保姆式教程]使用目标检测模型YOLO11 OBB进行旋转目标检测:训练自己的数据集(基于卫星和无人机的农业大棚数据集)

之前写了一个基于YOLOv8z做旋转目标检测的文章&#xff0c;内容写得不够好&#xff0c;内容也比较杂乱。现如今YOLO已经更新到11了&#xff0c;数据集也集齐了无人机和卫星的农业大棚&#xff0c;所以这次就写一个基于YOLO11 OBB的农业大棚旋转检测。 1. 下载源码配置环境 在h…

Matplotlib 内置的170种颜色映射(colormap)

Matplotlib 提供了许多内置的颜色映射&#xff08;colormap&#xff09;选项&#xff0c;可以将数值数据映射到色彩范围——热力图、温度图、地图等可视化经常会用到。 # colormap 有两种引用形式plt.imshow(data, cmapBlues)plt.imshow(data, cmapcm.Blues) 颜色映射可以分为…

工业—使用Flink处理Kafka中的数据_ProduceRecord1

1 、 使用 Flink 消费 Kafka 中 ProduceRecord 主题的数据,统计在已经检验的产品中,各设备每 5 分钟 生产产品总数,将结果存入Redis 中, key 值为