由于某种原因,当我部署此功能时,我无法正确生成这些 PDF。这似乎是 Puppeteer 的问题,因为即使我在 Chrome 中打开 html 并打印为 pdf,我也可以让它正确打印。请参阅下面的 docker 文件、puppeteer 实现和 HTML(由于模板的构建方式,这有点混乱,但您会明白的):
FROM node:20-alpine
RUN apk add --no-cache \
chromium \
nss \
freetype \
freetype-dev \
harfbuzz \
ca-certificates \
ttf-freefont \
fontconfig
RUN fc-cache -f -v
# Set environment variable to use Puppeteer with Chromium installed in non-standard location
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser
# Create app directory
WORKDIR /usr/src/app
# Install app dependencies
# A wildcard is used to ensure both package.json AND package-lock.json are copied
# where available (npm@5+)
COPY package*.json ./
COPY .npmrc ./
# RUN npm install
# If you are building your code for production
RUN npm install pm2 -g
RUN npm ci --only=production
# Bundle app source
COPY . .
EXPOSE 4042
CMD [ "pm2-runtime", "start", "/usr/src/app/process.yml" ]
import { launch } from 'puppeteer';
class FormatDocsApi {
async formatHtmlString({ htmlString }) {
const browser = await launch({
headless: true,
args: [
'--disable-features=IsolateOrigins',
'--disable-site-isolation-trials',
'--autoplay-policy=user-gesture-required',
'--disable-background-networking',
'--disable-background-timer-throttling',
'--disable-backgrounding-occluded-windows',
'--disable-breakpad',
'--disable-client-side-phishing-detection',
'--disable-component-update',
'--disable-default-apps',
'--disable-dev-shm-usage',
'--disable-domain-reliability',
'--disable-extensions',
'--disable-features=AudioServiceOutOfProcess',
'--disable-hang-monitor',
'--disable-ipc-flooding-protection',
'--disable-notifications',
'--disable-offer-store-unmasked-wallet-cards',
'--disable-popup-blocking',
'--disable-print-preview',
'--disable-prompt-on-repost',
'--disable-renderer-backgrounding',
'--disable-setuid-sandbox',
'--disable-speech-api',
'--disable-sync',
'--hide-scrollbars',
'--ignore-gpu-blacklist',
'--metrics-recording-only',
'--mute-audio',
'--no-default-browser-check',
'--no-first-run',
'--no-pings',
'--no-sandbox',
'--no-zygote',
'--password-store=basic',
'--use-gl=swiftshader',
'--use-mock-keychain',
],
});
console.log('Browser launched:', await browser.version());
const page = await browser.newPage();
console.log('page created');
await page.emulateMediaType('print');
await page.evaluateHandle('document.fonts.ready');
await page.setContent(htmlString, { waitUntil: 'networkidle0' });
const pdfOptions = {
displayHeaderFooter: true,
preferCSSPageSize: true,
format: 'A4',
headerTemplate: '<div></div>',
footerTemplate:
"<div style=\"font-size:8px; text-align:end; width:100%; margin:0px 10px 0px 10px;\"><span class='pageNumber'></span> / <span class='totalPages'></span></div>",
margin: {
top: '40px',
bottom: '40px',
left: '40px',
right: '40px',
},
};
console.log('PDF buffer generating now...');
const pdfBuffer = await page.pdf(pdfOptions);
// Close the browser
await browser.close();
return pdfBuffer; // Return a success message or data
}
}
export { FormatDocsApi };
我运行的是node 20,puppeteer v22,浏览器是Chrome/123.0.6312.122。 我已经为此奋斗了数周,并认为将 Node 升级到 20,将 Puppeteer 升级到 v22 会是解决方案,但部署后仍然无法正常工作。
我尝试将给定的大型 html 示例缩小到更易于管理的大小,以便可以轻松复制:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<style>
#nss-summary-table-header {
background-color: lightgrey;
border-left: 0.1px solid black;
border-right: 0.1px solid black;
}
.nss-summary-table-header {
background-color: lightgrey;
border-left: 0.1px solid black;
border-right: 0.1px solid black;
page-break-inside: avoid;
}
.nss-summary-table {
border-collapse: collapse;
width: 100%;
height: 100%;
table-layout: fixed;
padding: 0 !important;
float: none !important;
}
td {
text-align: center;
margin: 0 !important;
height: 100%;
padding-top: 2px;
padding-bottom: 2px;
}
th {
text-align: center;
margin: 0 !important;
}
tr {
padding: 0 !important;
margin: 0 !important;
}
.nss-summary-table-nested {
border-collapse: collapse;
width: 100%;
height: 100%;
table-layout: fixed;
margin: 0 !important;
padding: 0 !important;
float: none !important;
page-break-inside: avoid;
}
.nested-table {
border-collapse: collapse;
width: 100%;
height: 100%;
table-layout: fixed;
margin: 0 !important;
padding: 0 !important;
float: none !important;
page-break-inside: avoid;
}
.nss-summary-totals {
width: 100%;
display: table;
border-collapse: collapse;
}
.nss-summary-totals td {
padding-top: 4px;
padding-bottom: 4px;
}
#nss-total-row div {
text-align: left;
background-color: white;
margin: 2px;
}
#nss-total-row-wrapper {
display: flex;
justify-content: flex-end;
width: 100%;
margin-top: 10px;
}
#nss-disclaimer {
font-style: italic;
margin-bottom: 10px;
page-break-after: always;
}
.nss-text-align-right {
text-align: right !important;
}
.text-align-justify {
text-align: justify;
}
.nss-border-left-none {
border-left: none !important;
}
.nss-bold {
font-weight: bold;
}
.nss-col-20-percent-w {
width: 20%;
}
.nss-col-25-percent-w {
width: 25%;
}
.parent-td {
width: 80%;
}
.border-left-none {
border-left: none !important;
}
</style>
<style>
html {
-webkit-print-color-adjust: exact;
}
body {
font-family: Arial, sans-serif;
font-size: .7rem;
padding: 40px;
height: 100%;
page-break-inside: avoid;
}
hr {
border-style: solid;
border-bottom: 2px solid black;
}
hr.hr-bold {
border-bottom: 4px solid black;
}
h1 {
text-transform: uppercase;
}
</style>
<title></title>
</head>
<body>
<div style="height: 800px">
</div>
<div id="name-search-summary">
<br>
<table class="nss-summary-table" style="border-top: 0.1px solid black;">
<thead id="nss-summary-table-header" class="nss-summary-table-header">
<tr>
<th class="nss-bold" colspan="5" style="background-color: #70A6AD; color: #ffffff; padding: 4px 1px; border-bottom: 0.1px solid black;">Client, Test </th>
</tr>
<tr>
<th class="nss-bold">Jurisdiction</th>
<th class="nss-bold">Service</th>
<th class="nss-bold">Results</th>
<th class="nss-bold">Through Date</th>
<th class="nss-bold">Fees</th>
</tr>
</thead>
<tbody>
<tr style="">
<td colspan="5" style="padding: 0 !important; margin: 0 !important;">
<table class="nss-summary-table-nested content-block is-first" style="border: 0.1px solid black;">
<tbody>
<tr style="border-bottom: 0.1px solid black;">
<td rowspan="2" class="nss-col-20-percent-w" style="padding: 4px 1px; border-right: 0.1px solid black;">Recorder, Denver, CO</td>
<td colspan="4" class="parent-td" style="padding: 0 !important; margin: 0 !important;">
<table class="nested-table" style="">
<tbody>
<tr style="">
<td class="nss-col-20-percent-w" style="border-bottom: 0.1px solid black; padding: 4px 1px; border-right: 0.1px solid black;">Federal Tax Lien Search</td>
<td class="nss-col-20-percent-w" style="border-bottom: 0.1px solid black; padding: 4px 1px; border-right: 0.1px solid black;">
None Found
</td>
<td class="nss-col-20-percent-w" style="border-bottom: 0.1px solid black; padding: 4px 1px; border-right: 0.1px solid black;">2/12/2024</td>
<td rowspan="2" class="nss-col-20-percent-w" style="padding: 4px 1px;">$80.00</td>
</tr>
<tr style="">
<td class="nss-col-20-percent-w" style=" padding: 4px 1px; border-right: 0.1px solid black;">State Tax Lien Search</td>
<td class="nss-col-20-percent-w" style=" padding: 4px 1px; border-right: 0.1px solid black;">
None Found
</td>
<td class="nss-col-20-percent-w" style=" padding: 4px 1px; border-right: 0.1px solid black;">2/12/2024</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<table class="nss-summary-table-nested content-block " style="border: 0.1px solid black;">
<tbody>
<tr style="border-bottom: 0.1px solid black;">
<td rowspan="2" class="nss-col-20-percent-w" style="padding: 4px 1px; border-right: 0.1px solid black;">District Court, Big Stone, MN</td>
<td colspan="4" class="parent-td" style="padding: 0 !important; margin: 0 !important;">
<table class="nested-table" style="">
<tbody>
<tr style="">
<td class="nss-col-20-percent-w" style="border-bottom: 0.1px solid black; padding: 4px 1px; border-right: 0.1px solid black;">Civil Search - Pending Only</td>
<td class="nss-col-20-percent-w" style="border-bottom: 0.1px solid black; padding: 4px 1px; border-right: 0.1px solid black;">
None Found
</td>
<td class="nss-col-20-percent-w" style="border-bottom: 0.1px solid black; padding: 4px 1px; border-right: 0.1px solid black;">2/12/2024</td>
<td rowspan="2" class="nss-col-20-percent-w" style="padding: 4px 1px;">$40.00</td>
</tr>
<tr style="">
<td class="nss-col-20-percent-w" style=" padding: 4px 1px; border-right: 0.1px solid black;">Judgment Search</td>
<td class="nss-col-20-percent-w" style=" padding: 4px 1px; border-right: 0.1px solid black;">
None Found
</td>
<td class="nss-col-20-percent-w" style=" padding: 4px 1px; border-right: 0.1px solid black;">2/12/2024</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<table class="nss-summary-table-nested content-block " style="border: 0.1px solid black;">
<tbody>
<tr style="border-bottom: 0.1px solid black;">
<td rowspan="2" class="nss-col-20-percent-w" style="padding: 4px 1px; border-right: 0.1px solid black;">U.S. District Court, Minnesota District</td>
<td colspan="4" class="parent-td" style="padding: 0 !important; margin: 0 !important;">
<table class="nested-table" style="">
<tbody>
<tr style="">
<td class="nss-col-20-percent-w" style="border-bottom: 0.1px solid black; padding: 4px 1px; border-right: 0.1px solid black;">Civil Search - Pending Only</td>
<td class="nss-col-20-percent-w" style="border-bottom: 0.1px solid black; padding: 4px 1px; border-right: 0.1px solid black;">
None Found
</td>
<td class="nss-col-20-percent-w" style="border-bottom: 0.1px solid black; padding: 4px 1px; border-right: 0.1px solid black;">2/12/2024</td>
<td rowspan="2" class="nss-col-20-percent-w" style="padding: 4px 1px;">$40.00</td>
</tr>
<tr style="">
<td class="nss-col-20-percent-w" style=" padding: 4px 1px; border-right: 0.1px solid black;">Judgment Search</td>
<td class="nss-col-20-percent-w" style=" padding: 4px 1px; border-right: 0.1px solid black;">
None Found
</td>
<td class="nss-col-20-percent-w" style=" padding: 4px 1px; border-right: 0.1px solid black;">2/12/2024</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<table class="nss-summary-table-nested content-block " style="border: 0.1px solid black;">
<tbody>
<tr style="border-bottom: 0.1px solid black;">
<td rowspan="1" class="nss-col-20-percent-w" style="padding: 4px 1px; border-right: 0.1px solid black;">U.S. Bankruptcy Court, Minnesota Division</td>
<td colspan="4" class="parent-td" style="padding: 0 !important; margin: 0 !important;">
<table class="nested-table" style="">
<tbody>
<tr style="">
<td class="nss-col-20-percent-w" style=" padding: 4px 1px; border-right: 0.1px solid black;">Bankruptcy Search</td>
<td class="nss-col-20-percent-w" style=" padding: 4px 1px; border-right: 0.1px solid black;">
None Found
</td>
<td class="nss-col-20-percent-w" style=" padding: 4px 1px; border-right: 0.1px solid black;">2/12/2024</td>
<td rowspan="1" class="nss-col-20-percent-w" style="padding: 4px 1px;">$20.00</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<table class="nss-summary-table" style="border-top: 0.1px solid black;">
<thead id="nss-summary-table-header" class="nss-summary-table-header">
<tr>
<th class="nss-bold" colspan="5" style="background-color: #70A6AD; color: #ffffff; padding: 4px 1px; border-bottom: 0.1px solid black;">Person, Christofer </th>
</tr>
<tr>
<th class="nss-bold">Jurisdiction</th>
<th class="nss-bold">Service</th>
<th class="nss-bold">Results</th>
<th class="nss-bold">Through Date</th>
<th class="nss-bold">Fees</th>
</tr>
</thead>
<tbody>
<tr style="">
<td colspan="5" style="padding: 0 !important; margin: 0 !important;">
<table class="nss-summary-table-nested content-block is-first" style="border: 0.1px solid black;">
<tbody>
<tr style="border-bottom: 0.1px solid black;">
<td rowspan="2" class="nss-col-20-percent-w" style="padding: 4px 1px; border-right: 0.1px solid black;">Recorder, Denver, CO</td>
<td colspan="4" class="parent-td" style="padding: 0 !important; margin: 0 !important;">
<table class="nested-table" style="">
<tbody>
<tr style="">
<td class="nss-col-20-percent-w" style="border-bottom: 0.1px solid black; padding: 4px 1px; border-right: 0.1px solid black;">Federal Tax Lien Search</td>
<td class="nss-col-20-percent-w" style="border-bottom: 0.1px solid black; padding: 4px 1px; border-right: 0.1px solid black;">
None Found
</td>
<td class="nss-col-20-percent-w" style="border-bottom: 0.1px solid black; padding: 4px 1px; border-right: 0.1px solid black;">2/12/2024</td>
<td rowspan="2" class="nss-col-20-percent-w" style="padding: 4px 1px;">$80.00</td>
</tr>
<tr style="">
<td class="nss-col-20-percent-w" style=" padding: 4px 1px; border-right: 0.1px solid black;">State Tax Lien Search</td>
<td class="nss-col-20-percent-w" style=" padding: 4px 1px; border-right: 0.1px solid black;">
None Found
</td>
<td class="nss-col-20-percent-w" style=" padding: 4px 1px; border-right: 0.1px solid black;">2/12/2024</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</div>
</body>
</html>
我的观察是,pdf 打印仅显示 docker 内部的问题。需要注意的是,docker内部安装了chromium的alpine linux版本。对我来说是:
Chromium 123.0.6312.122 Alpine Linux
在我的 MacOS 上是:
Chrome/123.0.6312.122
我使用 vscode 进入运行 docker 容器,并且能够在没有 puppeteer 的情况下使用命令行复制相同的行为:
/usr/bin/chromium-browser --headless --disable-gpu --no-sandbox --print-to-pdf ./test.html
假设 html 在同一目录中保存为 test.html
。这会生成
output.pdf
,我们可以更轻松地测试行为。更多观察:
<div style="height: 800px"></div>
。如果您将其减少到足以将所有内容放入单页中(例如使用
height: 100px
),则不会出现问题。