Python中使用自定义字体将SVG转换为PNG

6
我正在使用基于Cairo/RSVG的解决方案将SVG转换为PNG。在StackOverflow的"Convert SVG to PNG in Python"中已经有人描述过它。 然而,这种解决方案似乎无法处理自定义字体。
我找到了这个页面,其中描述了嵌入SVG字体
我尝试通过XLink从外部SVG中包含它们,就像示例中所描述的那样。我尝试直接将字体嵌入同一个SVG文件中。在失败之后,我尝试了CSS Web字体语法。在使用Cairo进行渲染时,这3种方法都不起作用(在Ubuntu的默认查看器Eye of GNOME中也不起作用)。
我尝试了ImageMagick,结果与Cairo完全相同。
另一方面,在所有3种字体嵌入方法中,字体在WebKit中呈现良好,可以使用Google Chrome或webkit2png.py。但是,如果可能的话,我想避免在服务器上使用QT WebKit,因为它的设置非常复杂(包括xvfb等),而且我担心可能效率低下且不稳定。
有没有其他的Python SVG转PNG的方法呢?

我使用了适用于PNG的替代方法:https://dev59.com/LWgv5IYBdhLWcg3wSewe#56194265 - Martin Delille
5个回答

4
我花了一周的时间研究这个问题,并得出结论:处理具有自定义字体的SVG的服务器端渲染/光栅化的最佳方法是在服务器上安装这些字体。我尝试过的工具(rsvg、imagemagick、phantomjs、qtwebkit等)无法处理Web字体和SVG字体。
谷歌有数百种字体(并且还在增加),可以在服务器上下载并使用。
  • 下载OTF或TTF字体
  • 将它们安装到服务器上并刷新字体缓存
  • 在SVG文档中,将外部字体的CSS定义替换为字体族名称。
如果您还需要在网页中使用同样的字体,您可以直接链接到Google CDN以获取WOFF文件,以节省您自己的服务器时间和网络带宽。

注意(据我所知),您不能使用在css样式标签中引用但未直接安装在计算机上的字体(即具有引用Web服务器上字体的url的font-face)。它们需要安装在您的计算机上。之后,ImageMagick对我来说就像魔法一样运行良好。 - Shmack

0
你可以尝试使用inkscape,也许这会给你更好的结果:
 inkscape inputfile.svg --export-png=exportfile.png

在Python中运行此命令的方法可以在这里找到:在Python中调用外部命令


不太确定这是我想在服务器上安装的软件。 - vartec
1
@vartec: 至少,您将需要一些第三方产品。不知道为什么inkscape应该比任何其他解决方案更或更少值得信赖。对于Windows,还有一个便携式版本http://portableapps.com/apps/graphics_pictures/inkscape_portable,如果您不想安装这个东西,可以直接运行,不需要任何管理员特权并且不会集成到系统中。 - Doc Brown
刚试了一下,完全不起作用。不仅无法呈现自定义字体,而且还不能处理使用XLink插入的外部图像。 - vartec

0

我在使用Imagemagick时仍然苦于将安装在服务器上并可用于特定操作的字体进行svg栅格化,但在从.svg转换为.png时失败...... 它似乎将每种类型的文本都转换为arial。 我认为这可能是ImageMagick的错误或.svg中需要特定格式。


0

检查 RSVG 时需要注意的几件事情:

  • 字体源文件是否位于您的系统或用户字体路径中
  • 即使字体名称包含空格,也不要在 SVG 中引用字体名称。Librsvg 将假定引号是字体名称的一部分,因此无法找到源文件。

0
这是我如何让 AWS S3 上的自定义字体文件被加载的方法(但是这个例子中我使用的是本地文件系统)- 请注意,它们都是 woff 格式的。
如果你的所有字体都可以被 fontTools 的 TTFont 类加载,你可以像这样做 - 这个库还可以将不同的字体转换为其他字体:
from fontTools.ttLib import TTFont
from pathlib import Path
import xml.etree.ElementTree as ET
from io import BytesIO
from reportlab.graphics import renderPM
import svglib.fonts
from svglib.svglib import svg2rlg


def to_pdf_buffer(d, *args, **kwargs):
    buffer = BytesIO()
    renderPDF.drawToFile(d, buffer, *args, **kwargs)
    return buffer


def to_pil(d, *args, **kwargs):
    pil = renderPM.drawToPIL(d, *args, **kwargs)
    return pil


ET.register_namespace('', "http://www.w3.org/2000/svg")
file_path = Path("my_svg.svg")
root = ET.parse(str(file_path)).getroot()
font_map = svglib.fonts.get_global_font_map()


fonts = []
unloaded_fonts = []
Folder = "./" # folder to where your fonts are
for e in self.root.findall(".//{http://www.w3.org/2000/svg}text"):
    font_family_name = e.get('font-family')
    if font_family_name is not None and font_family_name not in fonts:
        fonts.append(font_family_name)
        path = Path(Folder)
        if path.exists() and path.is_dir():
            file = path.joinpath(Path(f"./{font_family_name}.woff"))
            data = file.read_bytes()
            try:
                TTFont(data)
            except:
                unloaded_fonts.append(font_family_name)
                continue
            else:
                try:
                    font = BytesIO(file.read_bytes())
                    font_map.register_font(font_family_name, font_path=font)
                except:
                    unloaded_fonts.append(font_family_name)



if len(unloaded_fonts) > 0:
    print(f"Could not load fonts: {', '.join(unloaded_fonts)}")

drawing = svg2rlg(BytesIO(ET.tostring(root)), font_map=font_map)
pil = to_pil(drawing, dpi=1200)
pil.save(str(file_path.with_suffix(".png")))

svglib有一个字体映射,可以通过调用svglib.get_global_font_map()函数来获取。您可以使用字体映射的register_fonts方法加载字体,该方法接受字体名称和路径或数据(在他们的文档中有介绍 - 不能同时提供两者或类似的情况)。

这使用ElementTree库来解析文本元素的font-family标签。它获取它们的名称并找到具有该字体族名称的字体文件并加载它,这对我的应用程序有效,但对其他人可能无效,因为通常您使用css @font-face加载字体。为了使@font-face选项起作用,您必须解析它并获取其数据,并将这些字体注册到字体映射中。这并不是非常困难,但我确实懒得做,并且我的注意力此时被转移到了另一个问题上,所以我没有意愿去做,但这对于其他开发者来说已经提供了足够的基础信息。

再次澄清一下:要使此功能正常工作,您的文本必须具有font-family,并且您的字体文件必须是这些字体族的名称;此外,我的所有字体文件都是woff格式的,所以您可能需要更换文件扩展名。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接