在 docker (FastAPI) 中使用 Wea​​syPrint 导出的 PDF 中的字体

Posted

技术标签:

【中文标题】在 docker (FastAPI) 中使用 Wea​​syPrint 导出的 PDF 中的字体【英文标题】:Fonts in PDF exported using WeasyPrint inside docker (FastAPI) 【发布时间】:2021-10-20 06:03:36 【问题描述】:

我有一个在 docker 容器中运行的应用程序。该服务使用 FastAPI 框架编写,用于生成报告。我们首先生成一个 html 报告,然后使用 Wea​​syprint 库将其转换为 PDF。当我在笔记本电脑上执行它时(我使用的是 Manjaro Linux),一切正常。但是,当我在 docker 容器中使用 API 时,我注意到生成的 PDF 中的字体不同。在 CSS 文件中,我们使用 font-family Helvetica, sans-serif。因此,当在容器内生成报告时,无衬线字体不起作用。

此外,我已将我的字体文件夹(从我的笔记本电脑)复制到 docker 容器中,以检查它是否缺少某些字体;但是,sans-serif 仍然不起作用。

这是我的 Dockerfile:

FROM tiangolo/uvicorn-gunicorn-fastapi

RUN apt-get update && apt-get install -y build-essential unzip vim git curl locales orca
RUN apt install -y python3-cffi libcairo2 libcairo2-dev libpango-1.0-0 libpango1.0-dev libpangocairo-1.0-0 libgdk-pixbuf2.0-0 \
                   libgdk-pixbuf2.0-dev libffi-dev shared-mime-info libffi-dev fonts-font-awesome

RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && \
    locale-gen
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8

ARG RELEASE=main
RUN git clone <my_repo>
RUN cd ./<repo> && pip install -r requirements.txt

EXPOSE 80
WORKDIR /app/<repo>/app

CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "80", "--workers", "4"]

以及我安装的 python 库(在 requirements.txt 中):

numpy==1.20.3
pandas==1.2.4
plotly==4.14.3
kaleido==0.2.1
weasyprint==52.5
Jinja2==3.0.1
fastapi==0.65.2
uvicorn==0.14.0
pydantic==1.8.2
psutil==5.8.0
python-multipart==0.0.5
aiofiles==0.7.0
SQLAlchemy==1.4.22
async-exit-stack==1.0.1
async-generator==1.10
mysqlclient==2.0.3

由于我可以在我的笔记本电脑上很好地生成它,我假设缺少某些库(我也尝试安装 fonts-open-sans 和 libglib2.0-dev,但没有任何改变)或者存在某个版本的问题。例如,我查看了 WeasyPrint 文档,pango 应该是 1.44.0 版本或更高版本,而在 Debian 10(这是 docker 中的操作系统)中,软件包 libpango-1.0-0 使用 pango 1.42.0 版本。这可能是为什么 sans-serif 不起作用或对我缺少什么有任何想法的问题吗?

【问题讨论】:

您能否提供一些简单的app.py 应用程序代码示例以便能够重现行为? @rzivmp app.py 没有多大作用。它执行一个后台任务,我们使用 jinja2 和一个 HTML 模板。在模板中,我们只是有一些表格(用 python 和 pandas 生成)和一些 plotly 的图(只是 png,我们不使用 plotly 生成的 div 和 javascript)。由于它是公司的私有代码,我无法共享它,我不知道我提到的复制它需要多少这些代码 - 需要创建一个脚本来复制这些步骤。但我认为你只需要一个带有字体系列 Helvetica、sans-serif 的简单 html,并在 docker 内生成一个带有 weasyprint 的 PDF。 【参考方案1】:

好的,我为你做了一些工作;)

我使用以下方法构建映像并启动 docker 容器:

FROM tiangolo/uvicorn-gunicorn-fastapi

RUN apt-get update && apt-get install -y build-essential unzip vim git curl locales orca
RUN apt install -y python3-cffi libcairo2 libcairo2-dev libpango-1.0-0 libpango1.0-dev libpangocairo-1.0-0 libgdk-pixbuf2.0-0 \
                   libgdk-pixbuf2.0-dev libffi-dev shared-mime-info libffi-dev fonts-font-awesome

RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && \
    locale-gen
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8

COPY app.py .
COPY template .
COPY requirements.txt .
RUN pip install -r requirements.txt

EXPOSE 80

CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "80", "--workers", "4"]

其中requirements.txt和你的一样,template是直接从here复制过来的。

app.py

from fastapi import FastAPI
from fastapi.responses import HTMLResponse, Response
from weasyprint import HTML


app = FastAPI(title="name")

@app.get("/", response_class=HTMLResponse)
async def read_root():
    with open('template', 'r') as f:
       contents = f.read()

    return contents

@app.get("/pdf")
async def read_root_pdf():
    with open('template', 'r') as f:
       contents = f.read()
    
    pdf_content = HTML(string=contents).write_pdf()

    return Response(content=pdf_content, media_type="application/pdf")

if __name__ == "__main__":
    import uvicorn
    uvicorn.run('app:app', host="0.0.0.0", port=80)

正如您在下面看到的(我将 pdf 重命名为 png 以便能够附加)所有字体都是不同的。你的容器没有问题。

【讨论】:

首先,非常感谢您的努力!!我不明白它是如何为您工作的...我检查了 masterpdfeditor 属性中的字体。通过上面的 Dockerfile,生成的字体是 DejaVuSans。因此,似乎无衬线字体有效,但 Helvetica 无效。所以我刚刚下载并复制了 Helvetica ttf 文件。现在,生成的 pdf 确实具有 Helvetica 作为字体。

以上是关于在 docker (FastAPI) 中使用 Wea​​syPrint 导出的 PDF 中的字体的主要内容,如果未能解决你的问题,请参考以下文章

docker中的Nginx,fastapi和streamlit - 反向代理不适用于streamlit

我在将 docker mysql 与 fastapi 连接时遇到问题

何时/何地在 FastAPI 中使用正文/路径/查询/字段?

22.FastAPI开发大型应用

在 FastAPI 中使用 `async def` 与 `def` 并测试阻塞调用

FastAPI - 如何在响应中使用 HTTPException?