如何在Javascript/Angular中实现PDF文本对比及可视化功能?
在Angular/JavaScript中实现PDF文本对比与可视化方案
一、核心思路拆解
要实现类似Git/Beyond Compare的PDF对比效果,核心分为三步:
- 从两个PDF中提取带行结构的纯文本
- 对提取的文本进行差异计算
- 在Angular中集成可视化差异展示组件
二、步骤1:PDF文本提取(含行号处理)
最成熟的工具是pdf.js(Mozilla官方维护),它能高效解析PDF并提取文本,我们可以基于它来按行拆分文本内容。
Angular中集成pdf.js
- 先安装依赖:
npm install pdfjs-dist @types/pdfjs-dist
- 创建一个PDF文本提取服务(
pdf-text-extractor.service.ts):
import { Injectable } from '@angular/core'; import * as pdfjsLib from 'pdfjs-dist'; import { TextItem } from 'pdfjs-dist/types/src/display/api'; // 配置pdf.js的worker路径 pdfjsLib.GlobalWorkerOptions.workerSrc = '//cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js'; @Injectable({ providedIn: 'root' }) export class PdfTextExtractorService { async extractTextWithLines(pdfFile: File): Promise<string[]> { const arrayBuffer = await pdfFile.arrayBuffer(); const pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise; const allLines: string[] = []; for (let pageNum = 1; pageNum <= pdf.numPages; pageNum++) { const page = await pdf.getPage(pageNum); const textContent = await page.getTextContent(); const pageLines: string[] = []; let currentLine = ''; let lastY = -1; // 按Y坐标分组(PDF中文本行的Y坐标相近,以此判断换行) textContent.items.forEach((item: TextItem) => { // 当Y坐标变化超过阈值,视为新行 if (lastY !== -1 && Math.abs(item.transform[5] - lastY) > 1) { pageLines.push(currentLine.trim()); currentLine = ''; } currentLine += item.str + ' '; lastY = item.transform[5]; }); // 加入当前页最后一行 if (currentLine.trim()) pageLines.push(currentLine.trim()); allLines.push(...pageLines); } return allLines; } }
这个服务会把PDF文本按行拆分,返回字符串数组,每个元素对应一行文本,方便后续对比。
三、步骤2:文本差异计算
推荐使用diff-match-patch(Google开源的差异算法库),它能生成详细的差异数据,完美适配后续可视化需求。
安装与使用
- 安装依赖:
npm install diff-match-patch @types/diff-match-patch
- 在组件中使用它计算差异:
import { Component } from '@angular/core'; import { PdfTextExtractorService } from './pdf-text-extractor.service'; import DiffMatchPatch from 'diff-match-patch'; @Component({ selector: 'app-pdf-compare', templateUrl: './pdf-compare.component.html', styleUrls: ['./pdf-compare.component.css'] }) export class PdfCompareComponent { private dmp = new DiffMatchPatch(); leftPdfLines: string[] = []; rightPdfLines: string[] = []; constructor(private pdfExtractor: PdfTextExtractorService) {} async handleFileUpload(event: any, isLeft: boolean) { const file = event.target.files[0]; if (!file) return; const lines = await this.pdfExtractor.extractTextWithLines(file); isLeft ? this.leftPdfLines = lines : this.rightPdfLines = lines; } calculateDiff() { // 只有两个文件都上传后才计算差异 if (!this.leftPdfLines.length || !this.rightPdfLines.length) return; // 将行数组转为换行分隔的字符串,传入差异算法 const leftText = this.leftPdfLines.join('\n'); const rightText = this.rightPdfLines.join('\n'); // 这里不需要手动处理差异结果,直接传给可视化组件即可 } }
四、步骤3:Angular可视化差异展示
如果不想自己写UI,推荐用ngx-text-diff这个Angular组件,它已经封装了类似Git的差异展示样式(新增行绿色高亮、删除行红色删除线),开箱即用。
集成ngx-text-diff
- 安装依赖:
npm install ngx-text-diff
- 在模块中导入:
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { NgxTextDiffModule } from 'ngx-text-diff'; import { PdfCompareComponent } from './pdf-compare.component'; @NgModule({ imports: [BrowserModule, NgxTextDiffModule], declarations: [PdfCompareComponent], bootstrap: [PdfCompareComponent] }) export class AppModule {}
- 在组件模板中使用:
<div class="pdf-compare-container"> <div class="file-upload"> <h3>原始PDF</h3> <input type="file" accept=".pdf" (change)="handleFileUpload($event, true)"> </div> <div class="file-upload"> <h3>对比PDF</h3> <input type="file" accept=".pdf" (change)="handleFileUpload($event, false)"> </div> <button (click)="calculateDiff()" [disabled]="!leftPdfLines.length || !rightPdfLines.length"> 开始对比 </button> <div *ngIf="leftPdfLines.length && rightPdfLines.length" class="diff-view"> <h3>差异对比结果</h3> <ngx-text-diff [left]="leftPdfLines.join('\n')" [right]="rightPdfLines.join('\n')" [lineNumbers]="true"> </ngx-text-diff> </div> </div>
- 可选:自定义样式
ngx-text-diff默认样式已经很实用,你可以通过CSS调整细节:
.pdf-compare-container { max-width: 1200px; margin: 20px auto; padding: 0 20px; } .file-upload { margin-bottom: 20px; } .diff-view { margin-top: 30px; border: 1px solid #eee; border-radius: 4px; padding: 10px; } .ngx-text-diff-deleted { background-color: #ffebee; } .ngx-text-diff-added { background-color: #e8f5e9; }
五、注意事项
- 行号准确性:PDF的文本布局可能复杂(比如多列、浮动元素),上述按Y坐标分组的方式可能不是100%精确,若需更精准的行号,可能需要结合PDF的页面布局信息做进一步处理。
- 大文件性能:对于超大PDF,建议分页提取和对比,避免内存溢出。
- 特殊字符:部分PDF可能包含特殊编码的文本,pdf.js基本能处理,但遇到异常情况可以尝试调整文本提取的参数。
内容的提问来源于stack exchange,提问作者Hugo
相关产品推荐
相关产品推荐

