无法使用Python将EMF插入Word

2024-09-29 23:32:06 发布

您现在位置:Python中文网/ 问答频道 /正文

我需要在Word中插入SVG文件。因为,我们不能直接这样做,所以我计划将SVG转换为EMF并插入它。使用inkscape可以很好地将SVG转换为EMF。然而,我无法找到将其插入Word的正确代码。 我按照Alvaro在this post中解释的步骤进行操作。已在所附文件中显示了遵循的步骤-enter image description here

这是我的密码-

enter image description here

但是,当我运行附件中显示的代码时,它仍然抛出docx.image.exceptions.UnrecognizedImageError。github上的库的贡献者声称该库解决了这个问题。如果是这样的话,请让我知道,如果我错过了什么

我能够手动成功插入EMF文件。通过插入EMF来附加文档。此EMF从the internet下载进行测试


Tags: 文件代码svgimage密码附件步骤this
3条回答

SVG可以直接添加到Word中-只需在Word(2016)中手动试用即可。 我已经为您的用例创建了一个example Java project作为POC。 无需调用inkscape,因为回退PNG是通过Batik动态创建的

当然,OP要求提供Python解决方案——但是如果Python openxml缺少一些功能,那么可能需要付出更多的努力,通过Python来运行它,而不是调用java运行时

关于通过EMF的变通解决方案,请注意,在EMF渲染器(我在POI中实现)中,有多种确定边界的方法,默认情况下,我扫描窗口和视口记录,只有在找不到任何其他内容或通过配置选项忽略扫描时,才使用EMF头边界。这通常会给我更好的结果

示例项目的相关代码snipplet如下所示:

public class AddSvgToDocument {
    public static void main(String[] args) throws IOException, InvalidFormatException {
        File tmplDocx = new File(args[0]);
        File svgFile = new File(args[1]);
        File outDocx = new File(args[2]);

        try (FileInputStream fis = new FileInputStream(tmplDocx);
             XWPFDocument doc = new XWPFDocument(fis)) {

            SVGImageRenderer rnd = new SVGImageRenderer();
            try (FileInputStream fis2 = new FileInputStream(svgFile)) {
                rnd.loadImage(fis2, PictureData.PictureType.SVG.contentType);
            }

            Rectangle2D nativeDim = rnd.getNativeBounds();
            double widthPx = 500;
            double heightPx = widthPx * nativeDim.getHeight() / nativeDim.getWidth();

            BufferedImage bi = rnd.getImage(new Dimension2DDouble(widthPx, heightPx));
            ByteArrayOutputStream bos = new ByteArrayOutputStream(100_000);
            ImageIO.write(bi, "PNG", bos);

            XWPFRun run = doc.createParagraph().createRun();

            int widthEmu = Units.pixelToEMU((int)widthPx);
            int heightEmu = Units.pixelToEMU((int)heightPx);
            XWPFPicture pic = run.addPicture(new ByteArrayInputStream(bos.toByteArray()), PictureData.PictureType.PNG.ooxmlId, "image.png", widthEmu, heightEmu);
            CTOfficeArtExtensionList extLst = pic.getCTPicture().getBlipFill().getBlip().addNewExtLst();
            addExt(extLst, "{28A0092B-C50C-407E-A947-70E740481C1C}"
                , "http://schemas.microsoft.com/office/drawing/2010/main", "a14:useLocalDpi"
                , "val", "0");

            addExt(extLst, "{96DAC541-7B7A-43D3-8B79-37D633B846F1}"
                , "http://schemas.microsoft.com/office/drawing/2016/SVG/main", "asvg:svgBlip"
                , "r:embed", addSVG(doc, svgFile));

            try (FileOutputStream fos = new FileOutputStream(outDocx)) {
                doc.write(fos);
            }
        }
    }



    private static void addExt(CTOfficeArtExtensionList extLst, String uri, String namespace, String name, String attribute, String value) {
        CTOfficeArtExtension ext = extLst.addNewExt();
        ext.setUri(uri);
        XmlCursor cur = ext.newCursor();
        cur.toEndToken();
        String[] prefixName = name.split(":");
        cur.beginElement(new QName(namespace, prefixName[1], prefixName[0]));
        cur.insertNamespace(prefixName[0], namespace);
        if (attribute.contains(":")) {
            prefixName = attribute.split(":");
            String prefix = prefixName[0];
            String attrNamespace = DEFAULT_XML_OPTIONS
                .getSaveSuggestedPrefixes().entrySet().stream()
                .filter(me -> prefix.equals(me.getValue()))
                .map(Map.Entry::getKey)
                .findFirst().orElse(null);
            cur.insertAttributeWithValue(new QName(attrNamespace, prefixName[1], prefix), value);
        } else {
            cur.insertAttributeWithValue(attribute, value);
        }
        cur.dispose();
    }

    private static String addSVG(XWPFDocument doc, File svgFile) throws InvalidFormatException, IOException {
        // SVG is not thoroughly supported as of POI 5.0.0, hence we need to go the long way instead of adding a picture
        OPCPackage pkg = doc.getPackage();
        String svgNameTmpl = "/word/media/image#.svg";
        int svgImageIdx = pkg.getUnusedPartIndex(svgNameTmpl);
        PackagePartName svgPPName = PackagingURIHelper.createPartName(svgNameTmpl.replace("#", Integer.toString(svgImageIdx)));
        PackagePart svgPart = pkg.createPart(svgPPName, PictureData.PictureType.SVG.contentType);

        try (FileInputStream fis = new FileInputStream(svgFile);
             OutputStream os = svgPart.getOutputStream()) {
            IOUtils.copy(fis, os);
        }
        PackageRelationship svgRel = doc.getPackagePart().addRelationship(svgPPName, TargetMode.INTERNAL, IMAGE_PART);
        return svgRel.getId();
    }
}

模块docx似乎无法处理EMF文件

我的意思是,围绕这一点开展的工作如下:

import shutil
import zipfile

temp_dir = "_temp"

old_docx = "doc.docx"
new_docx = "doc_new.docx"

old_emf = temp_dir + "/word/media/image1.emf"
new_emf = "new_image.emf"


# unpack content of the docx file into the temp folder

with zipfile.ZipFile(old_docx, "r") as z:
    files = z.namelist()
    for f in files: z.extract(f, temp_dir)


# replace the image

shutil.copyfile(new_emf, old_emf)


# pack all files from temp folder back into the new docx file

with zipfile.ZipFile(new_docx, "a") as z:
    for f in files: z.write(temp_dir + "/" + f, f)


# remove the temp folder

shutil.rmtree(temp_dir)

docx文件的典型结构:

doc.docx
│
├─ [Content_Types].xml
│
├─ _rels
│  └─ .rels
│
├─ docProps
│  ├─ app.xml
│  └─ docProps
│
└─ word
   ├─ document.xml    <  text is here
   ├─ fontTable.xml
   ├─ settings.xml
   ├─ webSettings.xml
   ├─ styles.xml
   │
   ├─ _rels
   │  └─ document.xml.rels
   │
   ├─ theme
   │  └─ theme1.xml
   │
   └─ media
      └─ image1.emf   <  your image is here

它将文档文件doc.docx的内容解压缩到临时文件夹_temp,然后用当前目录中的另一个文件new_image.emf替换临时目录中的文件image1.emf。然后它将临时文件夹的内容打包回doc_new.docx文件并删除临时目录

注意:新图像在new_doc.docx中的大小与旧图像相同

因此,工作流程可以是这样的:创建模板docx文件,手动将模板emf图片放在那里,然后保存docx文件。然后获取新的emf图像,将该图像放在docx文件旁边并运行脚本。通过这种方式,您可以获得一个带有新emf映像的新docx文件

我想您有很多emf图像,所以在这个脚本中添加几行代码是有意义的,这样它就可以拍摄多个图像并生成多个docx文件

如果所有emf图像的大小都相同,那么它就可以正常工作。如果它们的大小不同,则需要更多的编码来处理xml数据

更新

我已经知道了如何获得emf图像的大小。下面是完整的解决方案:

from docx import Document
import shutil
import zipfile

temp_dir = "_temp"
old_docx = "doc.docx"
new_docx = "doc_new.docx"
old_emf  = temp_dir + "/word/media/image1.emf" # don't change this line
new_emf  = "img5.emf"

# unpack content of the docx file into temp folder
with zipfile.ZipFile(old_docx, "r") as z:
    files = z.namelist()
    for f in files: z.extract(f, temp_dir)

# replace the image
shutil.copyfile(new_emf, old_emf)

# pack all files from temp folder back into the new docx file
with zipfile.ZipFile(new_docx, "a") as z:
    for f in files: z.write(temp_dir + "/" + f, f)

# remove temp folder
shutil.rmtree(temp_dir)

# get sizes of the emf image
with open(new_emf, "rb") as f:
    f.read(16)
    w1, w2 = f.read(1).hex(), f.read(1).hex()
    f.read(2)
    h1, h2 = f.read(1).hex(), f.read(1).hex()

width  = int(str(w2) + str(w1), 16) * 762
height = int(str(h2) + str(h1), 16) * 762

# open the new docx file and set the sizes for the image
doc = Document(new_docx)
img = doc.inline_shapes[0]  # suppose the first image is the image
img.width  = width
img.height = height

doc.save(new_docx)

下面是另一个基于win32com模块和MS Word API的解决方案:

from pathlib import Path
import win32com.client

cur_dir  = Path.cwd()                                   # get current folder
pictures = list((cur_dir / "pictures").glob("*.emf"))   # get a list of pictures
word_app = win32com.client.Dispatch("Word.Application") # run Word
doc      = word_app.Documents.Add()                     # create a new docx file

for pict in pictures:                                   # insert all pictures
    doc.InlineShapes.AddPicture(pict)

doc.SaveAs(str(cur_dir / "pictures.docx"))              # save the docx file
doc.Close()                                             # close docx
word_app.Quit()                                         # close Word

将EMF图像放入子文件夹pictures并运行此脚本。之后,您将在当前文件夹中获得包含所有这些EMF图像的文件pictures.docx

相关问题 更多 >

    热门问题