前因
由于python中没有方便快捷生成美观图片的方案,所以我使用了jinja2模板生成html后交给playwright生成网页图片,这其中,图片资源可以使用base64格式解决,而字体加载就难了。
首先尝试了使用 file://xxx.ttf的方式尝试让游览器直接从文件系统中得到字体文件;但很明显,由于跨域问题,游览器是不会加载本地文件的;那能不能解决跨域呢?比如搭一个后端而不是单纯的jinja2模板,或者关闭游览器的csp/CORS拦截。前者虽然可以解决问题,但是增加的不是一两个方法而是一个新后端应用,维护起来相当麻烦;那后者呢?我从playwright的issue页查到的launch args --disable-web-security和 --disable-site-isolation-trials都不能解决问题。但翻到了一个比较巧妙的解决方案:Page Route | Playwright Python
方法
RouteAPI本身是处理网页的请求相关的逻辑,比如拦截和修改请求,在这其中就可以拦截对字体的请求,然后返回字体数据,至于跨域,请求拦截意味着可以随意设置域名,不用考虑跨域问题。
font: Path
await page.route(
f"**/{font.name}",
lambda route: route.fulfill(body=font.read_bytes()),
)
这样当请求中含有字体文件名时,playwright就会拦截并返回字体数据给页面。
封装
为了方便调用,对逻辑进行一个封装来让使用更加方便
async def font_event(font_list: list[str | Path], page: Page, debug: bool = False):
path_list = [Path(font) for font in font_list]
for font in path_list:
await page.route(
f"**/{font.name}",
lambda route: route.fulfill(body=font.read_bytes()),
)
name_list = [Path(font).name for font in font_list]
if not debug:
return
async def print_request(request: Request):
for font in name_list:
if font in request.url:
logger.debug(f"Loading font {font}...")
return
async def print_response(response: Response):
for font in name_list:
if font in response.url:
logger.debug(f"Font {font} loaded successfully.")
return
async def print_request_failed(request: Request):
for font in name_list:
if font in request.url:
logger.error(f"Font {font} loading failed.")
return
page.on("request", print_request)
page.on("response", print_response)
page.on("requestfailed", print_request_failed)
这里还封装了 debug来追踪字体请求状态,至于jinja出来的html也可以再包一层来达到不修改模板改变字体的目的,这里就不演示了。