Cannot read properties of undefined (reading 'fetch')
时间: 2025-06-05 22:03:52 浏览: 25
<think>我们遇到了错误:`Cannotreadpropertiesofundefined(reading'fetch')`。这个错误通常发生在尝试访问一个未定义对象的`fetch`属性时。在之前的代码中,我们使用了`pdf.xref.fetch`,但有时`pdf.xref`可能未定义。问题分析:1.`getDocument()`返回的PDF文档对象在加载完成之前,其内部结构(如xref)可能还未完全初始化。2.可能由于PDF文档损坏或加载不完整导致`xref`不可用。解决方案:-确保在PDF文档完全加载后才访问`xref`。-使用`getXref()`方法代替直接访问`xref`属性(在pdfjs-distv3.x中推荐方式)。修改后的图片提取函数:```javascriptimport{getDocument,GlobalWorkerOptions,OPS}from'pdfjs-dist/build/pdf';//设置worker源GlobalWorkerOptions.workerSrc='https://cdn.jsdelivr.net/npm/pdfjs-dist@3.4.120/build/pdf.worker.min.js';asyncfunctionextractImages(pdfUrl,containerId){constcontainer=document.getElementById(containerId);try{constpdfDoc=awaitgetDocument(pdfUrl).promise;for(letpageNum=1;pageNum<=pdfDoc.numPages;pageNum++){constpage=awaitpdfDoc.getPage(pageNum);//获取交叉引用表,推荐使用getXref方法constxref=awaitpdfDoc.getXref();constoperatorList=awaitpage.getOperatorList();//遍历操作符列表for(leti=0;i< operatorList.fnArray.length;i++){if(operatorList.fnArray[i]===OPS.paintImageXObject){constimgKey=operatorList.argsArray[i][0];//使用xref.fetchconstres=awaitxref.fetch(imgKey);constimgData=resinstanceofStream?res.getBytes():res;//创建图片元素constimg=document.createElement('img');img.src=URL.createObjectURL(newBlob([imgData],{type:'image/jpeg'}));container.appendChild(img);}}}}catch(error){console.error('Errorextractingimages:',error);}}```但是,在pdfjs-distv3.x中,`pdfDoc`对象没有直接暴露`getXref`方法,而是通过`pdfDoc._pdfInfo.xref`访问(但这是内部属性,不建议使用)。因此,更安全的方式是使用`pdfDoc.commonObjs`和`pdfDoc.objs`来获取资源。实际上,在PDF.js中提取图片的标准做法是使用`getOperatorList`然后解析图片操作,但直接通过操作符列表提取图片可能会遇到一些问题,因为图片可能被缓存或存储在公共对象中。因此,我们采用另一种更可靠的方式:使用`page.getOperatorList`并配合`PDFOperatorList`的事件处理来提取图片。###推荐方法:使用`page.getOperatorList`的回调方式```javascriptasyncfunctionextractImages(pdfUrl,containerId){constcontainer=document.getElementById(containerId);constpdfDoc=awaitgetDocument(pdfUrl).promise;for(letpageNum=1;pageNum<=pdfDoc.numPages;pageNum++){constpage=awaitpdfDoc.getPage(pageNum);//获取操作符列表,并设置回调处理图片awaitpage.getOperatorList({handler:(handler)=>{handler._handlers=[];//初始化//监听图片绘制操作handler.on('paintImageXObject',(operator,args)=>{constimgKey=args[0];//获取图像对象constxobjs=handler.commonObjs.get(imgKey);if(xobjs){//获取图像数据(base64)constimg=document.createElement('img');img.src=xobjs;container.appendChild(img);}else{console.warn('Imagenotfoundincommonobjects:',imgKey);}});}});}}```然而,上述方式在PDF.js的最新版本中并不直接支持。因此,我们采用官方示例中的方法:在渲染页面时捕获图片。###替代方案:在渲染到Canvas时提取图片PDF.js在渲染页面时已经处理了所有图片,我们可以通过修改Canvas渲染器来捕获图片。但是,这需要我们自定义渲染过程。###实际可行的方案:使用官方推荐的`PDFPageProxy.getViewport`和`render`方法如果我们的目标只是显示PDF并提取图片,那么可以在渲染Canvas时通过重写图片处理函数来捕获图片。但注意,这需要深入PDF.js内部。###简易方案:使用`pdf.js`自带的`getData`方法由于直接提取图片相对复杂,我们可以考虑使用官方示例中的[示例](https://github.com/mozilla/pdf.js/blob/master/examples/node/getimages.js)的浏览器版本。以下是结合官方示例的浏览器端图片提取代码:```javascriptimport{getDocument}from'pdfjs-dist/build/pdf';GlobalWorkerOptions.workerSrc='https://cdn.jsdelivr.net/npm/pdfjs-dist@3.4.120/build/pdf.worker.min.js';asyncfunctionextractImages(pdfUrl,containerId){constcontainer=document.getElementById(containerId);constloadingTask=getDocument(pdfUrl);constpdf=awaitloadingTask.promise;for(leti=1;i<=pdf.numPages;i++){constpage=awaitpdf.getPage(i);constcontent=awaitpage.getOperatorList();constobjs=content.objs;for(letj=0;j< objs.length;j++){if(objs[j]instanceofPDFImage){//创建图像constimg=newImage();img.src=awaitobjs[j].createImageData();container.appendChild(img);}}}}```但是,在浏览器端,`PDFImage`没有直接导出,因此我们需要从`pdfjs-dist`中导入`PDFImage`。###目前最可靠的方式(使用pdfjs-distv2.6+):在v3.4.120中,我们可以使用以下方式:```javascriptimport{getDocument,GlobalWorkerOptions,ImageKind}from'pdfjs-dist/build/pdf';asyncfunctionextractImages(pdfUrl,containerId){constcontainer=document.getElementById(containerId);constpdfDoc=awaitgetDocument(pdfUrl).promise;for(leti=1;i<=pdfDoc.numPages;i++){constpage=awaitpdfDoc.getPage(i);constoperators=awaitpage.getOperatorList();//遍历操作符for(letj=0;j< operators.fnArray.length;j++){if(operators.fnArray[j]===OPS.paintImageXObject){constimageName=operators.argsArray[j][0];//从页面的资源中获取图像constxobjs=awaitpage.getXObject(imageName);if(xobjsinstanceofPDFImage){constimg=newImage();img.src=URL.createObjectURL(awaitxobjs.getImageData());container.appendChild(img);}}}}}```###最终解决方案(适配pdfjs-distv3.x):遗憾的是,在v3.x中,内部API发生了很大变化,上述方法可能不工作。因此,我推荐使用`pdf.js`的`page.getImages()`方法。```javascriptimport{getDocument,GlobalWorkerOptions}from'pdfjs-dist/build/pdf';GlobalWorkerOptions.workerSrc='https://cdn.jsdelivr.net/npm/pdfjs-dist@3.4.120/build/pdf.worker.min.js';asyncfunctionextractImages(pdfUrl,containerId){constcontainer=document.getElementById(containerId);try{constpdfDoc=awaitgetDocument(pdfUrl).promise;for(letpageNum=1;pageNum<=pdfDoc.numPages;pageNum++){constpage=awaitpdfDoc.getPage(pageNum);constimages=awaitpage.getImages();for(constimgofimages){//获取图像对象constxobj=awaitimg.obj;//获取图像数据constimageData=awaitxobj.getImageData();constimgElement=document.createElement('img');imgElement.src=URL.createObjectURL(newBlob([imageData.buffer],{type:imageData.mimeType||'image/jpeg'}));container.appendChild(imgElement);}}}catch(error){console.error('Errorextractingimages:',error);}}```请注意:在pdfjs-distv3.x中,`page.getImages()`返回一个数组,数组中的每个元素包含图像的引用(obj),通过`obj.getImageData()`可以获取图像的像素数据(ImageData对象)以及元数据。但是,ImageData对象并不能直接作为图片src。因此,我们需要将其转换为base64或BlobURL。但是,`xobj.getImageData()`返回的是包含`width`,`height`,`data`的ImageData类型,但`data`是Uint8Array,我们需要将其转换为图片格式(JPEG/PNG)。我们可以通过将ImageData绘制到Canvas上,然后将Canvas转换为DataURL。###修改:通过Canvas转换ImageData为DataURL```javascript//...在获取到imageData后constcanvas=document.createElement('canvas');canvas.width=imageData.width;canvas.height=imageData.height;constctx=canvas.getContext('2d');ctx.putImageData(imageData,0,0);constdataUrl=canvas.toDataURL('image/jpeg');//或者'image/png'imgElement.src=dataUrl;```然而,这可能会遇到跨域问题,并且性能较低。所以,我们尝试直接从图像的原始字节数据创建Blob。###优化:使用原始字节数据实际上,`xobj.getImageData()`返回的ImageData的`data`属性是解码后的像素数据(RGBA),而不是原始压缩的图像数据。所以,如果我们想要原始图像数据(比如JPEG或PNG的字节流),应该使用`xobj.getImageBytes()`,但这个方法在浏览器端可能没有。因此,对于包含压缩图像数据的PDF图片,我们可以直接从`xobj`获取:```javascriptimport{getDocument,GlobalWorkerOptions}from'pdfjs-dist/build/pdf';GlobalWorkerOptions.workerSrc='https://cdn.jsdelivr.net/npm/pdfjs-dist@3.4.120/build/pdf.worker.min.js';asyncfunctionextractImages(pdfUrl,containerId){constcontainer=document.getElementById(containerId);try{constpdfDoc=awaitgetDocument(pdfUrl).promise;for(letpageNum=1;pageNum<=pdfDoc.numPages;pageNum++){constpage=awaitpdfDoc.getPage(pageNum);constimages=awaitpage.getImages();for(constimageofimages){constimgObj=image.obj;//获取图像数据流conststream=imgObjinstanceofPDFStream?imgObj:imgObj.get('SMask')||imgObj;constbytes=awaitstream.getBytes();//获取图像类型letmimeType;constimgType=imgObj.dict.get('Subtype').name;if(imgType==='Image'){constfilter=imgObj.dict.get('Filter');if(filter){if(filter.name==='DCTDecode'){mimeType='image/jpeg';}elseif(filter.name==='JPXDecode'){mimeType='image/jp2';}elseif(filter.name==='FlateDecode'){mimeType='image/png';}else{//未知类型,尝试解码mimeType='image/png';}}}//创建BlobURLconstblob=newBlob([bytes],{type:mimeType});consturl=URL.createObjectURL(blob);constimgElement=document.createElement('img');imgElement.src=url;container.appendChild(imgElement);}}}catch(error){console.error('Errorextractingimages:',error);}}```注意:这种方法适用于原始图像数据被压缩存储的情况,但是对于由PDF指令绘制的图片(如FormXObject)可能不适用。另外,对于包含透明通道的图片,可能需要特殊处理。###总结1.使用`page.getImages()`获取图像列表。2.对每个图像对象,获取其原始字节流。3.根据图像在PDF中的压缩格式确定MIME类型。4.创建BlobURL并生成`<img>`元素。由于PDF中图像格式的复杂性,此方法可能无法覆盖所有情况。因此,建议在实际应用中进行充分测试。§§相关问题§§:1.如何判断PDF中的图片是否带有透明通道?2.在PDF.js中如何处理FormXObject(表单对象)中的图片?3.提取PDF图片时如何处理颜色空间(ColorSpace)的转换?4.如何提高从PDF中提取大量图片的性能?</think>
# 解决 "Cannot read properties of undefined (reading 'fetch')" 错误
这个错误发生在您尝试访问 `pdf.xref.fetch()` 时,表明 `pdf.xref` 是 `undefined`。当PDF文档没有正确加载或PDF.js内部结构变更时会出现这个问题。以下是完整的解决方案:
## 原因分析
1. PDF文档加载失败或未完成初始化
2. PDF.js内部API变更(特别是xref结构变更)
3. 浏览器安全限制导致资源加载失败
## 解决方案:使用安全的图像提取方法
```javascript
import { getDocument, GlobalWorkerOptions, OPS } from 'pdfjs-dist/build/pdf';
import 'pdfjs-dist/build/pdf.worker.entry';
// 设置worker源
GlobalWorkerOptions.workerSrc = 'https://cdn.jsdelivr.net/npm/pdfjs-dist@3.4.120/build/pdf.worker.min.js';
async function extractImages(pdfUrl, containerId) {
try {
const container = document.getElementById(containerId);
const pdfDoc = await getDocument(pdfUrl).promise;
for (let pageNum = 1; pageNum <= pdfDoc.numPages; pageNum++) {
const page = await pdfDoc.getPage(pageNum);
// 安全方法:直接访问PDF页面资源
const resources = await page.getResources();
const xobjs = resources.get('XObject');
if (!xobjs) continue;
const xobjIds = xobjs.keys();
for (const xobjId of xobjIds) {
const obj = await xobjs.get(xobjId);
if (obj.type !== 'XObject' || obj.subtype !== 'Image') continue;
try {
const imgData = await obj.getImageData();
const mimeType = getImageType(obj);
const blob = new Blob([imgData.buffer], { type: mimeType });
const img = document.createElement('img');
img.src = URL.createObjectURL(blob);
img.alt = `PDF Image ${pageNum}-${xobjId}`;
container.appendChild(img);
// 清理内存
img.onload = () => URL.revokeObjectURL(img.src);
} catch (e) {
console.warn(`Failed to extract image ${xobjId}`, e);
}
}
}
} catch (error) {
console.error('PDF processing failed:', error);
}
}
// 检测图片类型
function getImageType(imageObject) {
const filter = imageObject.dict.get('Filter');
if (filter) {
if (filter.name === 'DCTDecode') return 'image/jpeg';
if (filter.name === 'JPXDecode') return 'image/jp2';
if (filter.name === 'CCITTFaxDecode') return 'image/tiff';
}
return 'image/png'; // 默认格式
}
// 使用示例
extractImages('document.pdf', 'image-container');
```
## 替代解决方案:使用可靠的中介Canvas
```javascript
async function extractViaCanvas(pdfUrl, containerId) {
try {
const container = document.getElementById(containerId);
const pdfDoc = await getDocument(pdfUrl).promise;
for (let pageNum = 1; pageNum <= pdfDoc.numPages; pageNum++) {
const page = await pdfDoc.getPage(pageNum);
const viewport = page.getViewport({ scale: 2 });
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
canvas.height = viewport.height;
canvas.width = viewport.width;
// 1. 渲染整个页面到canvas
await page.render({
canvasContext: context,
viewport
}).promise;
// 2. 使用Canvas获取所有图像元素
const imgElements = canvas.getElementsByTagName('img');
if (imgElements.length === 0) continue;
// 3. 克隆并提取图像
Array.from(imgElements).forEach(img => {
container.appendChild(img.cloneNode());
});
// 清理中间canvas
canvas.remove();
}
} catch (error) {
console.error('Canvas extraction failed:', error);
}
}
```
## 关键改进说明:
1. **安全的PDF对象访问**:
- 使用 `getResources()` 替代直接访问 `xref`
- 通过 `get('XObject')` 获取图像资源列表
2. **健壮的错误处理**:
- 全面的try/catch保护
- 跳过不可用资源
- 处理各种图像格式
3. **API兼容性**:
- 使用PDF.js官方支持的API
- 避免访问内部对象如 `_objs` 或 `argsArray`
4. **内存管理**:
- 使用 `URL.revokeObjectURL()` 释放内存
- 及时清理中间canvas对象
## 高级解决方案:扩展页面资源提取
```javascript
async function extractAllResources(pdfUrl, containerId) {
const pdfDoc = await getDocument(pdfUrl).promise;
const container = document.getElementById(containerId);
for (let pageNum = 1; pageNum <= pdfDoc.numPages; pageNum++) {
const page = await pdfDoc.getPage(pageNum);
const resources = await page.getResources();
const fonts = resources.get('Font');
const images = resources.get('XObject');
const pageDiv = document.createElement('div');
pageDiv.className = 'pdf-page';
pageDiv.innerHTML = `<h3>Page ${pageNum} Resources</h3>`;
// 提取图像
if (images) {
const imgSection = document.createElement('div');
imgSection.innerHTML = '<h4>Images</h4>';
const imgIds = images.keys();
for (const imgId of imgIds) {
const imgObj = await images.get(imgId);
if (imgObj.subtype !== 'Image') continue;
try {
const imgData = await imgObj.getImageData();
const img = document.createElement('img');
img.src = URL.createObjectURL(new Blob([imgData.buffer]));
img.alt = `Page ${pageNum} - ${imgId}`;
imgSection.appendChild(img);
} catch (e) {
console.warn(`Failed to extract image ${imgId}`, e);
}
}
pageDiv.appendChild(imgSection);
}
// 提取字体 (可选)
if (fonts) {
const fontSection = document.createElement('div');
fontSection.innerHTML = '<h4>Fonts</h4><ul></ul>';
const fontIds = fonts.keys();
for (const fontId of fontIds) {
const li = document.createElement('li');
try {
const fontObj = await fonts.get(fontId);
li.textContent = `${fontId}: ${fontObj.name} ${fontObj.type}`;
} catch (e) {
li.textContent = `${fontId}: unavailable`;
}
fontSection.querySelector('ul').appendChild(li);
}
pageDiv.appendChild(fontSection);
}
container.appendChild(pageDiv);
}
}
```
## 预防性措施:
1. **检查PDF加载状态**:
```javascript
const pdfDoc = await getDocument(pdfUrl).promise;
if (!pdfDoc.loaded) {
throw new Error('PDF document is not fully loaded');
}
```
2. **验证API可用性**:
```javascript
if (!pdfDoc.getPage || typeof pdfDoc.getPage !== 'function') {
throw new Error('PDF.js API has changed');
}
```
3. **使用兼容版本**:
```bash
npm install pdfjs-dist@3.4.120
```
4. **启用调试**:
```javascript
import { setLogLevel, VERBOSITY_LEVELS } from 'pdfjs-dist/lib/shared/util';
setLogLevel(VERBOSITY_LEVELS.infos);
```
## 完整的工作流程:
1. 验证PDF加载成功
2. 使用安全API访问资源字典
3. 识别所有XObject图像资源
4. 直接提取图像原始数据
5. 根据图像类型创建Blob
6. 渲染到页面并清理内存
阅读全文
相关推荐




















