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

Swift iOS开发:如何用PDFParser提取PDF文本并解决内存与定位问题?

解决方案:无内存崩溃的PDF文本提取与定位

一、解决内存崩溃问题:逐页处理+自动释放

PDFKit遍历全页时的内存崩溃,大多是因为未及时释放单页资源。用autoreleasepool包裹单页处理逻辑,能强制回收每页的临时对象,降低内存峰值。结合你使用的PDFParser,先优化文本提取的准确性(解决复杂PDF文本错乱),再实现内存友好的全页遍历:

1. 优化PDFParser的文本提取(修复复杂PDF错乱)

复杂PDF的文本块可能存在排版顺序混乱,需要按页面位置从上到下、从左到右排序后再拼接:

extension SimpleDocumentIndexer {
    public func extractWholeTextFromPage(pageNumber: Int) -> String {
        guard let pageIndex = pageIndexes[pageNumber] else {
            return ""
        }
        // 按PDF坐标系排序:minY越大位置越靠上,同Y轴按minX从左到右
        let sortedBlocks = pageIndex.textBlocks.sorted { block1, block2 in
            if block1.frame.minY != block2.frame.minY {
                return block1.frame.minY > block2.frame.minY
            } else {
                return block1.frame.minX < block2.frame.minX
            }
        }
        return sortedBlocks.map(\.chars).joined()
    }
}

2. 内存安全的全页遍历与文本匹配

逐页处理目标文本匹配,处理完一页就释放资源,避免全量加载所有页面文本:

/// 遍历PDF所有页面,检查目标文本是否存在,返回页面号与匹配文本的映射
func checkTargetTexts(in document: PDFDocument, indexer: SimpleDocumentIndexer, targets: [String]) -> [Int: [String]] {
    var pageMatches = [Int: [String]]()
    let totalPages = document.pageCount
    
    for pageNum in 0..<totalPages {
        autoreleasepool {
            let pageText = indexer.extractWholeTextFromPage(pageNumber: pageNum)
            let matchedTexts = targets.filter { pageText.contains($0) }
            if !matchedTexts.isEmpty {
                pageMatches[pageNum] = matchedTexts
            }
        }
    }
    return pageMatches
}

二、获取文本坐标/CGRect及前后上下文

针对匹配到的页面,单独处理文本定位,避免全量解析所有页面的坐标数据:

1. 获取目标文本的CGRect坐标(基于PDFKit)

/// 在指定PDF页面查找目标文本的所有位置坐标
func findTextRects(on page: PDFPage, target: String) -> [CGRect] {
    guard let textContent = page.textContent, let pageString = page.string else {
        return []
    }
    var rects = [CGRect]()
    var currentRange = pageString.range(of: target)
    
    while let range = currentRange {
        let nsRange = NSRange(range, in: pageString)
        // 转换为glyph范围,获取对应坐标
        let glyphRange = textContent.glyphRange(for: nsRange)
        var unionRect = CGRect.null
        textContent.enumerateGlyphs(in: glyphRange) { _, _, _, rect in
            unionRect = unionRect.union(rect)
        }
        rects.append(unionRect)
        // 查找下一个匹配项
        let nextStart = range.upperBound
        currentRange = pageString.range(of: target, range: nextStart..<pageString.endIndex)
    }
    return rects
}

2. 获取目标文本的前后上下文

/// 获取目标文本在页面文本中的前后上下文(默认前后各50字符)
func getSurroundingText(for target: String, in pageText: String, length: Int = 50) -> String? {
    guard let range = pageText.range(of: target) else {
        return nil
    }
    let start = pageText.index(range.lowerBound, offsetBy: -length, limitedBy: pageText.startIndex) ?? pageText.startIndex
    let end = pageText.index(range.upperBound, offsetBy: length, limitedBy: pageText.endIndex) ?? pageText.endIndex
    return String(pageText[start..<end])
}

三、整合流程示例

// 假设已初始化PDFDocument和SimpleDocumentIndexer
let pdfDocument = PDFDocument(url: yourPDFURL)!
let indexer = SimpleDocumentIndexer(document: pdfDocument)

// 1. 检查所有页面的目标文本
let targetTexts = ["目标词1", "目标段落"]
let pageMatches = checkTargetTexts(in: pdfDocument, indexer: indexer, targets: targetTexts)

// 2. 对匹配的页面,获取坐标和上下文
for (pageNum, matches) in pageMatches {
    guard let page = pdfDocument.page(at: pageNum) else { continue }
    let pageText = indexer.extractWholeTextFromPage(pageNumber: pageNum)
    
    for match in matches {
        let rects = findTextRects(on: page, target: match)
        let context = getSurroundingText(for: match, in: pageText)
        print("页面\(pageNum+1)找到\(match),坐标:\(rects),上下文:\(context ?? "无")")
    }
}

内容的提问来源于stack exchange,提问作者Aliya Chudhary

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 16:38:24