You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

纯Swift(macOS Cocoa)实现PDF转PPTX生成文件无法打开的问题求助

纯Swift(macOS Cocoa)实现PDF转PPTX生成文件无法打开的问题求助

我现在在macOS上用纯Swift+Cocoa写一个PDF转PPTX的工具,思路是把PDF的每一页转成图片,然后手动构建PPTX的目录结构和配置XML,最后用ZIPFoundation打包成.pptx文件。但折腾了好久,生成的文件始终打不开,系统提示「Sorry, can’t open this file.」,我怀疑是漏了某些必要的配置XML,或者现有的XML内容不符合OOXML规范,想请大家帮忙排查下问题。

我的实现思路

  • 解析PDF文档,将每一页渲染成JPG图片,存入ppt/media目录(命名为imageN.jpg)
  • 为每一页生成对应的幻灯片XML文件slideN.xml,里面用<a:blip r:embed="rId1"/>引用图片
  • 生成幻灯片的关系文件slideN.xml.rels,把rId1映射到对应图片的路径../media/imageN.jpg
  • 构建PPTX要求的全套目录结构(包括ppt/slides/_rels、ppt/_rels、根目录的_rels等)
  • 最后用ZIPFoundation将整个临时目录打包成合法的.pptx文件

当前的代码实现

func convertPDFToPPT(pdfURL: URL) {
    guard let pdfDoc = PDFDocument(url: pdfURL) else {
        print("Cannot open PDF at: \(pdfURL.path)")
        return
    }
    
    let fileManager = FileManager.default
    guard let savePath = fileManager.urls(for: .downloadsDirectory, in: .userDomainMask).first else {
        print("Cannot find Downloads directory")
        return
    }
    
    let pptName = "Presentation_\(UUID().uuidString)"
    let tempFolder = savePath.appendingPathComponent(pptName, isDirectory: true)
    
    // Create folder structure
    let pptFolder = tempFolder.appendingPathComponent("ppt", isDirectory: true)
    let slidesFolder = pptFolder.appendingPathComponent("slides", isDirectory: true)
    let slidesRelsFolder = slidesFolder.appendingPathComponent("_rels", isDirectory: true)
    let mediaFolder = pptFolder.appendingPathComponent("media", isDirectory: true)
    let pptRelsFolder = pptFolder.appendingPathComponent("_rels", isDirectory: true)
    let rootRelsFolder = tempFolder.appendingPathComponent("_rels", isDirectory: true)
    let pptRelsRoot = pptFolder.appendingPathComponent("_rels", isDirectory: true)
    
    do {
        try fileManager.createDirectory(at: slidesRelsFolder, withIntermediateDirectories: true)
        try fileManager.createDirectory(at: mediaFolder, withIntermediateDirectories: true)
        try fileManager.createDirectory(at: rootRelsFolder, withIntermediateDirectories: true)
        try fileManager.createDirectory(at: pptRelsRoot, withIntermediateDirectories: true)
    } catch {
        print("Failed to create PPTX folder structure: \(error)")
        return
    }
    
    // Keep track of slide file names and image names
    var slideFileNames: [String] = []
    var imageFileNames: [String] = []
    
    // Convert PDF pages to images and write slide XML files
    for i in 0..<pdfDoc.pageCount {
        guard let page = pdfDoc.page(at: i) else { continue }
        let pageRect = page.bounds(for: .mediaBox)
        
        // Render page into NSImage
        let image = NSImage(size: pageRect.size)
        image.lockFocus()
        NSColor.white.set()
        pageRect.fill()
        page.draw(with: .mediaBox, to: NSGraphicsContext.current!.cgContext)
        image.unlockFocus()
        
        guard let tiffData = image.tiffRepresentation,
              let bitmap = NSBitmapImageRep(data: tiffData),
              let jpgData = bitmap.representation(using: .jpeg, properties: [.compressionFactor: 0.9]) else {
            print("Failed to create JPEG for page \(i + 1)")
            continue
        }
        
        let imageName = "image\(i + 1).jpg"
        let imageURL = mediaFolder.appendingPathComponent(imageName)
        do {
            try jpgData.write(to: imageURL)
        } catch {
            print("Failed to write image to disk: \(error)")
            continue
        }
        imageFileNames.append(imageName)
        
        // Build slide XML that references rId1 for the image
        let slideXML = """
        <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
        <p:sld xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
            <p:cSld>
                <p:spTree>
                    <p:nvGrpSpPr>
                        <p:cNvPr id="1" name=""/>
                        <p:cNvGrpSpPr/>
                        <p:nvPr/>
                    </p:nvGrpSpPr>
                    <p:grpSpPr/>
                    <p:pic>
                        <p:nvPicPr>
                            <p:cNvPr id="2" name="Picture \(i + 1)"/>
                            <p:cNvPicPr/>
                            <p:nvPr/>
                        </p:nvPicPr>
                        <p:blipFill>
                            <a:blip r:embed="rId1"/>
                            <a:stretch>
                                <a:fillRect/>
                            </a:stretch>
                        </p:blipFill>
                        <p:spPr>
                            <a:xfrm>
                                <a:off x="0" y="0"/>
                                <a:ext cx="9144000" cy="6858000"/>
                            </a:xfrm>
                            <a:prstGeom prst="rect">
                                <a:avLst/>
                            </a:prstGeom>
                        </p:spPr>
                    </p:pic>
                </p:spTree>
            </p:cSld>
            <p:clrMapOvr>
                <a:masterClrMapping/>
            </p:clrMapOvr>
        </p:sld>
        """
        let slideFileName = "slide\(i + 1).xml"
        let slideURL = slidesFolder.appendingPathComponent(slideFileName)
        do {
            try slideXML.write(to: slideURL, atomically: true, encoding: .utf8)
        } catch {
            print("Failed to write slide XML: \(error)")
            continue
        }
        slideFileNames.append(slideFileName)
        
        // Create slide rel that associates rId1
        let slideRelXML = """
        <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
        <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
            <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="../media/image\(i + 1).jpg"/>
        </Relationships>
        """
        let slideRelFileName = "slide\(i + 1).xml.rels"
        let slideRelURL = slidesRelsFolder.appendingPathComponent(slideRelFileName)
        do {
            try slideRelXML.write(to: slideRelURL, atomically: true, encoding: .utf8)
        } catch {
            print("Failed to write slide relationship XML: \(error)")
            continue
        }
    }
    
    // 注:目前缺失以下关键文件的生成逻辑,怀疑是核心问题
    // 1. 根目录的[Content_Types].xml:声明所有文件的类型
    // 2. ppt/presentation.xml:引用所有幻灯片
    // 3. ppt/_rels/presentation.xml.rels:关联presentation到各个slide
    // 4. 根目录的_rels/.rels:关联根到presentation.xml
}

我自己的猜测

  1. 缺失了核心配置文件:比如ppt/presentation.xml、根目录的[Content_Types].xml、ppt/_rels/presentation.xml.rels这些文件我还没正确生成,这大概率是导致文件无效的原因
  2. 现有XML的命名空间或者标签结构不符合OOXML规范,比如幻灯片的尺寸、元素嵌套是否正确?
  3. 打包成ZIP的时候,有没有特殊的压缩格式或者文件顺序要求?

有没有大佬能帮我梳理下,一个合法的PPTX还需要哪些必要的文件?或者我的现有XML哪里有问题?麻烦指点一下,谢谢!

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.07 09:04:36