This commit is contained in:
BIN
app/assets/fonts/NotoSansCJKsc-Regular.otf
Normal file
BIN
app/assets/fonts/NotoSansCJKsc-Regular.otf
Normal file
Binary file not shown.
@@ -5,6 +5,7 @@ const path = require("path");
|
||||
const moment = require("moment-timezone");
|
||||
const archiver = require("archiver");
|
||||
const { PDFDocument } = require("pdf-lib");
|
||||
const fontkit = require("@pdf-lib/fontkit");
|
||||
const Report = db.report;
|
||||
const RoutePath = db.route_path;
|
||||
const Employee = db.employee;
|
||||
@@ -44,6 +45,26 @@ const findTemplatePathBySite = (site) => {
|
||||
return candidatePaths.find((candidate) => fs.existsSync(candidate)) || "";
|
||||
};
|
||||
|
||||
const findUnicodeFontPath = (site) => {
|
||||
const safeSite = [1, 2, 3].includes(Number(site)) ? Number(site) : 1;
|
||||
const fileName = "NotoSansCJKsc-Regular.otf";
|
||||
const cwd = process.cwd();
|
||||
const candidatePaths = [
|
||||
path.join(ROOT_DIR, "app", "assets", "fonts", fileName),
|
||||
path.join(ROOT_DIR, "app", "views", "upload", fileName),
|
||||
path.join(ROOT_DIR, "client", "build", "upload", fileName),
|
||||
path.join(ROOT_DIR, "client", "public", "upload", fileName),
|
||||
path.join(cwd, "app", "assets", "fonts", fileName),
|
||||
path.join(cwd, "app", "views", "upload", fileName),
|
||||
path.join(cwd, "client", "build", "upload", fileName),
|
||||
path.join(cwd, "client", "public", "upload", fileName),
|
||||
path.join("/www/wwwroot/upload", fileName),
|
||||
path.join(`/www/wwwroot/worldshine${safeSite}`, "app", "assets", "fonts", fileName),
|
||||
path.join(`/www/wwwroot/worldshine${safeSite}-tspt`, "app", "assets", "fonts", fileName)
|
||||
];
|
||||
return candidatePaths.find((candidate) => fs.existsSync(candidate)) || "";
|
||||
};
|
||||
|
||||
const formatUtcToLocalHm = (dateLike) => {
|
||||
if (!dateLike) return "";
|
||||
const parsed = moment.utc(dateLike);
|
||||
@@ -74,8 +95,10 @@ const findOutboundStatusByCustomerId = (outboundCustomerStatuses, customerId) =>
|
||||
return outboundCustomerStatuses.find((item) => toObjectIdText(item?.customer_id) === targetId) || {};
|
||||
};
|
||||
|
||||
const buildRoutePdfBuffer = async (templateBytes, route, seqNum, driversMap, vehiclesMap, outboundCustomerStatuses) => {
|
||||
const buildRoutePdfBuffer = async (templateBytes, route, seqNum, driversMap, vehiclesMap, outboundCustomerStatuses, unicodeFontBytes) => {
|
||||
const pdfDoc = await PDFDocument.load(templateBytes);
|
||||
pdfDoc.registerFontkit(fontkit);
|
||||
const unicodeFont = await pdfDoc.embedFont(unicodeFontBytes, { subset: true });
|
||||
const form = pdfDoc.getForm();
|
||||
const routeName = route?.name || "";
|
||||
const scheduleDate = route?.schedule_date || "";
|
||||
@@ -107,11 +130,11 @@ const buildRoutePdfBuffer = async (templateBytes, route, seqNum, driversMap, veh
|
||||
const enterCenterTime = formatUtcToLocalHm(customer?.customer_enter_center_time);
|
||||
if (enterCenterTime) {
|
||||
safeSetField(form, `arrive_${row}`, enterCenterTime);
|
||||
safeSetField(form, `y_${row}`, "Y");
|
||||
safeSetField(form, `y_${row}`, "✓");
|
||||
} else if (customer?.customer_route_status === "inCenter") {
|
||||
safeSetField(form, `y_${row}`, "Y");
|
||||
safeSetField(form, `y_${row}`, "✓");
|
||||
} else {
|
||||
safeSetField(form, `n_${row}`, "N");
|
||||
safeSetField(form, `n_${row}`, "✕");
|
||||
}
|
||||
|
||||
const outboundStatus = findOutboundStatusByCustomerId(outboundCustomerStatuses, customer?.customer_id);
|
||||
@@ -121,6 +144,8 @@ const buildRoutePdfBuffer = async (templateBytes, route, seqNum, driversMap, veh
|
||||
if (dropoffTime) safeSetField(form, `drop_${row}`, dropoffTime);
|
||||
});
|
||||
|
||||
// Rebuild form appearances with a Unicode font so UTF-8 (e.g., Chinese) renders correctly.
|
||||
form.updateFieldAppearances(unicodeFont);
|
||||
form.flatten();
|
||||
return pdfDoc.save();
|
||||
};
|
||||
@@ -248,9 +273,13 @@ exports.exportRouteReportZip = async (req, res) => {
|
||||
|
||||
const site = splitSite.findSiteNumber(req);
|
||||
const templatePath = findTemplatePathBySite(site);
|
||||
const unicodeFontPath = findUnicodeFontPath(site);
|
||||
if (!templatePath) {
|
||||
return res.status(500).send({ message: `Missing PDF template for site ${site}.` });
|
||||
}
|
||||
if (!unicodeFontPath) {
|
||||
return res.status(500).send({ message: "Missing Unicode font file: NotoSansCJKsc-Regular.otf" });
|
||||
}
|
||||
|
||||
const [routes, drivers, vehicles] = await Promise.all([
|
||||
RoutePath.find(splitSite.splitSiteGet(req, { schedule_date: date, status: { $ne: "disabled" } })),
|
||||
@@ -278,6 +307,7 @@ exports.exportRouteReportZip = async (req, res) => {
|
||||
])
|
||||
);
|
||||
const templateBytes = fs.readFileSync(templatePath);
|
||||
const unicodeFontBytes = fs.readFileSync(unicodeFontPath);
|
||||
|
||||
const filenameDate = (date || "").replace(/\//g, "-");
|
||||
const zipName = `route_report_${filenameDate || "date"}.zip`;
|
||||
@@ -303,7 +333,8 @@ exports.exportRouteReportZip = async (req, res) => {
|
||||
i + 1,
|
||||
driversMap,
|
||||
vehiclesMap,
|
||||
outboundCustomerStatuses
|
||||
outboundCustomerStatuses,
|
||||
unicodeFontBytes
|
||||
);
|
||||
const base = sanitizeFileName(route?.name || `route_${i + 1}`) || `route_${i + 1}`;
|
||||
const existingCount = filenameCounter.get(base) || 0;
|
||||
|
||||
@@ -5,6 +5,8 @@ const rootDir = path.resolve(__dirname, "..");
|
||||
const sourceDir = path.join(rootDir, "public", "upload");
|
||||
const targetDir = path.join(rootDir, "build", "upload");
|
||||
const templates = ["pdf_templete1.pdf", "pdf_templete2.pdf", "pdf_templete3.pdf"];
|
||||
const unicodeFontFile = "NotoSansCJKsc-Regular.otf";
|
||||
const appFontSourcePath = path.join(rootDir, "..", "app", "assets", "fonts", unicodeFontFile);
|
||||
|
||||
if (!fs.existsSync(targetDir)) {
|
||||
fs.mkdirSync(targetDir, { recursive: true });
|
||||
@@ -19,4 +21,8 @@ templates.forEach((templateName) => {
|
||||
fs.copyFileSync(sourcePath, targetPath);
|
||||
});
|
||||
|
||||
console.log("Route report PDF templates copied to build/upload.");
|
||||
if (fs.existsSync(appFontSourcePath)) {
|
||||
fs.copyFileSync(appFontSourcePath, path.join(targetDir, unicodeFontFile));
|
||||
}
|
||||
|
||||
console.log("Route report PDF templates and Unicode font copied to build/upload.");
|
||||
|
||||
16
package-lock.json
generated
16
package-lock.json
generated
@@ -9,6 +9,7 @@
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@pdf-lib/fontkit": "^1.1.1",
|
||||
"archiver": "^7.0.1",
|
||||
"axios": "^0.27.2",
|
||||
"bcryptjs": "^2.4.3",
|
||||
@@ -733,6 +734,21 @@
|
||||
"sparse-bitfield": "^3.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@pdf-lib/fontkit": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@pdf-lib/fontkit/-/fontkit-1.1.1.tgz",
|
||||
"integrity": "sha512-KjMd7grNapIWS/Dm0gvfHEilSyAmeLvrEGVcqLGi0VYebuqqzTbgF29efCx7tvx+IEbG3zQciRSWl3GkUSvjZg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pako": "^1.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@pdf-lib/fontkit/node_modules/pako": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
|
||||
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
|
||||
"license": "(MIT AND Zlib)"
|
||||
},
|
||||
"node_modules/@pdf-lib/standard-fonts": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@pdf-lib/standard-fonts/-/standard-fonts-1.0.0.tgz",
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"author": "yangli",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@pdf-lib/fontkit": "^1.1.1",
|
||||
"archiver": "^7.0.1",
|
||||
"axios": "^0.27.2",
|
||||
"bcryptjs": "^2.4.3",
|
||||
|
||||
Reference in New Issue
Block a user