用到的库
- express:Express 是一个保持最小规模的灵活的 Node.js Web 应用程序开发框架,为 Web 和移动应用程序提供一组强大的功能。许多流行的开发框架都基于 Express 构建。
- jimp:是一个使用 JavaScript 编写的用于 Node的图像处理库,具有零依赖的特性。
- pdfkit:PDFKit是一个用于Node和浏览器的PDF文档生成库,它可以轻松创建复杂的、多页的PDF文档。该API既包括低层函数,也包括高层功能的抽象,PDFKit API被设计得很简单,生成复杂的文档就像调用几个函数一样简单(浏览器端的PDF生成与展示建议使用JSPDF)。
介绍
公司的远程问诊项目需要做一个PDF处方生成的功能,后端是使用JAVA写的,本身JAVA有很多优秀的PDF生成库例如:PDFBox 、pdfjet、OpenPDF、itext-7-core,但是要么收费,要么功能不全,要么版本太低(OpenPDF是一个基于iText4的开源免费的分支),经过比较发现pdfkit的API和文档比较完整,pdfkit有一个很有吸引力的功能是自动排盘和文字自动换行自动隐藏。
代码片段
Http请求处理
const express = require('express');
const app = express()
app.post('/createPdfByJson', (req, res,next) => {
if (req.method == 'POST') {
let postData = "";
req.on('data', function (chuck) {
postData += chuck;
});
req.on('end', function () {
// 处理参数转换
let _postData = JSON.parse(postData);
let _recipe = _postData.recipe;
_recipe.medicines = _postData.recipeMedicineList;
_recipe.createTime = new Date(_recipe.createTime);
// 生成PDF文件
console.log("生成处方:"+JSON.stringify(_recipe));
pdf.createPdf(_recipe, function (data) {
res.statusCode = 200
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify(data))
});
});
}
});
生成PDF
样式展示

签名图片处理
/**
* 下载文件
* @param {文件地址}} url
*/
function download(url) {
return new Promise(function (resolve, reject) {
https.get(new URL(url).href, (res) => {
let bufferArray = new Array();
res.on('data', (d) => {
bufferArray.push(d);
});
res.on('end', () => {
let imgBuffer = Buffer.concat(bufferArray);
var ab = new ArrayBuffer(imgBuffer.length);
var view = new Uint8Array(ab);
for (var i = 0; i < imgBuffer.length; ++i) {
view[i] = imgBuffer[i];
}
resolve(ab);
});
res.on('error', (error) => {
reject(error);
});
});
});
}
/**
* 下载图像并旋转图像
* @param {地址}} url
*/
function downloadImagRotate(url,rotate) {
return new Promise(function (resolve, reject) {
// URL用来做url的转义,因为传过来的地址不一定是合规的例如带有中文
https.get(new URL(url).href, (res) => {
let bufferArray = new Array();
res.on('data', (d) => {
bufferArray.push(d);
});
res.on('error', (error) => {
reject(error);
});
res.on('end', () => {
let imgBuffer = Buffer.concat(bufferArray);
jimp.read(imgBuffer).then(image=>{
// 图片旋转90度
image.rotate(90);
image.getBase64Async(jimp.MIME_PNG).then(data => {
resolve(data);
});
});
});
});
});
}
PDF排版
function createPdf(recipe,callback) {
new Promise(function(resolve, reject){
try {
// 创建文档
let doc = new PDFDocument({
autoFirstPage: false,
pdfVersion: "1.7ext3",
info: {
Title: "电子处方",
Author: "Node"
},
ownerPassword: '123456',
permissions: {
printing: "highResolution",
modifying: false,
copying: false,
annotating: false,
fillingForms: false,
contentAccessibility: false,
documentAssembly: false
}
});
// 创建页面
doc.addPage({
size: [498.96, 708.48],
margins: {
top: 50,
bottom: 50,
left: 72,
right: 72
}
})
// 处方编码
.fontSize(12)
.font(_LanTingXiHei)
.text('处方单号:'+recipe.recipeId, 20, 30)
// 角标
.roundedRect(432, 26, 40, 20, 0)
.fillAndStroke("#FFFFFF", "#000000")
.font(_LanTingXiHei)
.fillColor('#000000')
.fontSize(12)
.text('普通', 440, 30)
.text('', 72, 50)
// 医生就职机构
.font(_LanTingHei)
.fontSize(18)
.text('', 100, 70)
.text(recipe.clinicName+' 处方笺', {
align: 'center',
bold: 'Courier-Bold'
})
.font(_LanTingXiHei)
// 患者基本信息
.moveDown()
.moveTo(20, 105)
.lineTo(480, 105)
.fillAndStroke("#000000", "#000000")
.stroke()
.moveDown()
.fontSize(12)
.text('姓 名 : '+recipe.patientName, 20, 117, {
width: 285,
height: 50,
ellipsis: true
})
.text('性 别 : '+(recipe.patientSex==0?'男':'女'), 180, 117, {
width: 285,
height: 50,
ellipsis: true
})
.text('年 龄 : '+recipe.patientAge+'岁', 340, 117, {
width: 285,
height: 50,
ellipsis: true
})
// 诊断信息
.moveDown()
.fontSize(12)
.text('科 室 : '+recipe.doctorDepartment, 20, 140, {
width: 285,
ellipsis: true
})
.text('日 期 : '+recipe.createTime.getFullYear()+"年"+(recipe.createTime.getMonth()+1)+"月"+recipe.createTime.getDate()+"日", 180, 140, {
width: 285,
ellipsis: true
})
.moveDown()
.text('过敏史 : '+(recipe.allergyHistory?recipe.allergyHistory:''), 20, 163, {
width: 450,
height: 20,
ellipsis: true
})
.moveDown()
.text('诊 断 : ', 20, 186)
.text(recipe.diagnosis, 72, 186, {
width: 400,
height: 40,
ellipsis: true
})
// 药品列表
.moveDown()
.moveTo(20, 220)
.lineTo(480, 220)
.stroke()
.moveDown()
.font('fonts/兰亭黑 GBK.TTF')
.fontSize(18)
.text('RP', 20, 230)
.text('', 20, 259)
.font(_LanTingXiHei);
for (let i = 0; i < recipe.medicines.length; i++) {
if (i != 0) {
doc.moveDown();
}
let m = recipe.medicines[i];
doc
.fontSize(12)
.text((i + 1) + '.'+m.medicineName, {
continued: true,
align: 'left',
indent: 5
})
.text(m.medicinePec, {
continued: true,
align: 'center'
})
.text(m.medicineNum + m.packageUnit, {
align: 'right'
});
doc
.moveDown()
.fillColor('#333333')
.text('用法用量:'+m.useInfo, {
indent: 20
})
.fillColor('#000000');
}
// 页脚
doc
.moveDown()
.moveTo(20, 620)
.lineTo(480, 620)
.stroke()
.text('', 20, 634)
.fontSize(12)
.text('医师:', 20, 644)
.text('审核/核对:', 180, 644)
.text('调配:', 350, 644);
resolve(doc);
} catch (error) {
reject(error);
}
}).catch(function(erro){
callback({code:500,data:null,error:erro});
}).then(function(doc){
if(!doc){
return;
}
// 处理电子签证
let eSignature = new Array();
if(recipe.autograph){
eSignature.push(download(recipe.autograph));
}else{
eSignature.push(false);
}
if(recipe.pharmacistAutograph){
eSignature.push(downloadImagRotate(recipe.pharmacistAutograph,90));
}else{
eSignature.push(false);
}
if(recipe.dispenserAutograph){
eSignature.push(downloadImagRotate(recipe.dispenserAutograph,90));
}else{
eSignature.push(false);
}
if(recipe.clinicOfficialSeal){
eSignature.push(download(recipe.clinicOfficialSeal));
}else{
eSignature.push(false);
}
Promise.all(eSignature).catch(function(error){
callback({code:500,data:null,error:erro});
}).then(function(imgs){
if(!imgs){
return;
}
if(imgs[0]){
doc.image(imgs[0], 40, 624, { width: 100 });
}
if(imgs[1]){
doc.image(imgs[1], 230, 644, { width: 100 });
}
if(imgs[2]){
doc.image(imgs[2], 370, 644, { width: 100 });
}
if(imgs[3]){
doc.image(imgs[3], 345, 510, { width: 130 });
}
doc.end();
return doc;
}).catch(function(error){
callback({code:500,data:null,error:error.message});
}).then(function(_doc){
if(!_doc){
return;
}
// 上传OSS
getStream.buffer(_doc).then(function(data){
let client = new OSS({
region: '',
accessKeyId: '',
accessKeySecret: '',
bucket: ''
});
client.put(recipe.recipeId+"_"+(new Date().getTime())+'.pdf', data).then(function(ossRes){
callback({code:200,data:ossRes.url,error:null});
});
});
});
});
}
结语
pdf生成是一个常用的功能,实现方式也有很多种,可以结合自己的业务实际来做技术选型,例如加密、自定义图形、报表等。jimp的优势在于零依赖,node还有另一个优秀的图片处理库sharp它依赖于一个高效的图片处理库libvips.
本文介绍了如何使用Node.js的Express框架和PDFKit库来创建一个PDF生成服务。内容涉及了利用jimp进行图像处理,以及pdfkit的自动排版和文字处理功能。通过这个服务,公司远程问诊项目实现了PDF处方的生成。文章还提到了其他JAVA的PDF库,并建议根据具体业务需求选择合适的技术方案。
867

被折叠的 条评论
为什么被折叠?



