paper-burner/tests/MODULE_FIX_RECOMMENDATIONS.md

976 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 模块修复建议清单
## 🔴 高优先级 - 必须修复
### 1. TextFittingAdapter - 未定义的常量
**问题位置**: `TextFittingAdapter.js` line 71
**当前代码**:
```javascript
preprocessGlobalFontSizes(contentListJson, translatedContentList) {
// ...
contentListJson.forEach((item, idx) => {
// ...
const height = (bbox[3] - bbox[1]) / BBOX_NORMALIZED_RANGE; // ❌ 未定义!
});
}
```
**修复方案**:
```javascript
preprocessGlobalFontSizes(contentListJson, translatedContentList) {
if (this.hasPreprocessed) return;
console.log('[TextFittingAdapter] 开始预处理全局字号...');
const startTime = performance.now();
const globalFontScale = this.options.globalFontScale;
const BBOX_NORMALIZED_RANGE = 1000; // ✅ 添加这行
contentListJson.forEach((item, idx) => {
if (item.type !== 'text' || !item.bbox) return;
const translatedItem = translatedContentList[idx];
if (!translatedItem || !translatedItem.text) return;
const bbox = item.bbox;
const height = (bbox[3] - bbox[1]) / BBOX_NORMALIZED_RANGE;
const estimatedFontSize = height * globalFontScale;
this.globalFontSizeCache.set(idx, {
estimatedFontSize: estimatedFontSize,
bbox: bbox
});
});
console.log(`[TextFittingAdapter] 预处理完成:全局缩放=${globalFontScale}, 耗时=${(performance.now() - startTime).toFixed(0)}ms`);
this.hasPreprocessed = true;
}
```
**影响**: 🔴 严重 - 会导致运行时NaN错误
---
### 2. PDFExporter - Canvas和PDF文本高度公式不一致
**问题位置**:
- Canvas版本: `history_pdf_compare.js` line 1463-1465
- PDF版本: `PDFExporter.js` line 272-274
**当前代码对比**:
Canvas (错误):
```javascript
const totalHeight = lines.length === 1
? mid * 1.2 // 单行文本额外增加20%
: (lines.length - 1) * lineHeight + mid * 1.2;
```
PDF (错误):
```javascript
const totalHeight = lines.length > 0
? (lines.length - 1) * lineHeight + mid // 单行文本没有增加
: 0;
```
**修复方案** - 统一为一致的公式:
```javascript
// 在 PDFExporter.calculatePdfTextLayout() 中修复 (line 272-274)
// 改为与 Canvas 版本一致:
const totalHeight = lines.length > 0
? (lines.length - 1) * lineHeight + mid * 1.2 // ✅ 与Canvas统一
: 0;
// 同时在 drawPlainTextWithFitting() 中保持一致 (line 1463-1465)
const totalHeight = lines.length > 0
? (lines.length - 1) * lineHeight + mid * 1.2 // ✅ 保持一致
: 0;
```
**说明**:
- 这个 `* 1.2` 是为了给文本留出额外的垂直空间
- Canvas 中确实使用了这个系数
- PDF 版本遗漏了导致文本可能超出bbox
**影响**: 🔴 严重 - PDF导出的文本大小会与Canvas显示不同
---
### 3. SegmentManager - 事件监听器无法清理
**问题位置**: `SegmentManager.js` lines 216-234, 397-413
**当前代码**:
```javascript
initLazyLoadingSegments() {
if (!this._lazyInitialized) {
this.originalScroll.addEventListener('scroll', () => onScroll(this.originalScroll));
this.translationScroll.addEventListener('scroll', () => onScroll(this.translationScroll));
// ❌ 这些匿名箭头函数无法被移除
this._lazyInitialized = true;
}
}
destroy() {
// 注意:由于事件监听使用了箭头函数,无法直接移除
// 这里设置标记位,防止继续渲染
// ❌ 这不能真正清理资源!
this.segments = [];
this.pageInfos = [];
}
```
**修复方案**:
```javascript
constructor(pdfDoc, options = {}) {
// ... 其他初始化 ...
this._destroyed = false; // ✅ 添加销毁标志
this._scrollHandler = null; // ✅ 保存事件处理函数引用
}
initLazyLoadingSegments() {
if (!this.originalScroll || !this.translationScroll) return;
// 初始渲染可见段
this.renderVisibleSegments(this.originalScroll);
const onScroll = (scroller) => {
clearTimeout(this._lazyScrollTimer);
this._lazyScrollTimer = setTimeout(() => {
if (!this._destroyed) { // ✅ 检查销毁标志
this.renderVisibleSegments(scroller);
}
}, this.options.scrollDebounceMs);
};
if (!this._lazyInitialized) {
// ✅ 保存事件处理函数引用以便后续移除
this._scrollHandler = onScroll;
const originalScrollHandler = () => onScroll(this.originalScroll);
const translationScrollHandler = () => onScroll(this.translationScroll);
// ✅ 保存处理函数引用
this._originalScrollHandler = originalScrollHandler;
this._translationScrollHandler = translationScrollHandler;
this.originalScroll.addEventListener('scroll', originalScrollHandler);
this.translationScroll.addEventListener('scroll', translationScrollHandler);
this._lazyInitialized = true;
}
}
destroy() {
this._destroyed = true; // ✅ 设置销毁标志
// ✅ 正确移除事件监听器
if (this._lazyInitialized && this.originalScroll && this.translationScroll) {
if (this._originalScrollHandler) {
this.originalScroll.removeEventListener('scroll', this._originalScrollHandler);
}
if (this._translationScrollHandler) {
this.translationScroll.removeEventListener('scroll', this._translationScrollHandler);
}
}
// ✅ 清除定时器
if (this._lazyScrollTimer) {
clearTimeout(this._lazyScrollTimer);
this._lazyScrollTimer = null;
}
// ✅ 清空 DOM
if (this.originalSegmentsContainer) {
this.originalSegmentsContainer.innerHTML = '';
}
if (this.translationSegmentsContainer) {
this.translationSegmentsContainer.innerHTML = '';
}
this.segments = [];
this.pageInfos = [];
}
```
**影响**: 🔴 严重 - 内存泄漏,事件处理器持续执行
---
## 🟡 中优先级 - 应该改进
### 4. TextFittingAdapter - 缺少错误处理改进
**问题位置**: `TextFittingAdapter.js` line 34-57
**当前代码**:
```javascript
initialize() {
if (typeof TextFittingEngine === 'undefined') {
console.error('[TextFittingAdapter] TextFittingEngine 未加载!...');
return; // ❌ 静默失败
}
// ...
}
```
**修复方案**:
```javascript
initialize() {
// ✅ 改为throw更容易被发现
if (typeof TextFittingEngine === 'undefined') {
throw new Error('[TextFittingAdapter] TextFittingEngine 未加载!请确保 js/utils/text-fitting.js 已正确引入');
}
try {
this.textFittingEngine = new TextFittingEngine({
initialScale: this.options.initialScale,
minScale: this.options.minScale,
scaleStepHigh: this.options.scaleStepHigh,
scaleStepLow: this.options.scaleStepLow,
lineSkipCJK: this.options.lineSkipCJK,
lineSkipWestern: this.options.lineSkipWestern,
minLineHeight: this.options.minLineHeight
});
console.log('[TextFittingAdapter] 文本自适应引擎已启用');
} catch (error) {
console.error('[TextFittingAdapter] 文本自适应引擎初始化失败:', error);
throw error; // ✅ 将错误传播给调用者
}
}
```
**使用方式**:
```javascript
try {
const textFitter = new TextFittingAdapter();
textFitter.initialize();
} catch (error) {
console.error('初始化失败,将使用回退方案');
// 处理回退...
}
```
**影响**: 🟡 中等 - 便于发现问题
---
### 5. TextFittingAdapter - 添加参数验证
**问题位置**: `TextFittingAdapter.js` line 64-93
**当前代码**:
```javascript
preprocessGlobalFontSizes(contentListJson, translatedContentList) {
// ❌ 没有验证参数
if (this.hasPreprocessed) return;
const globalFontScale = this.options.globalFontScale;
contentListJson.forEach((item, idx) => { // ❌ 可能不是数组
// ...
});
}
```
**修复方案**:
```javascript
preprocessGlobalFontSizes(contentListJson, translatedContentList) {
if (this.hasPreprocessed) return;
// ✅ 添加参数验证
if (!contentListJson || !Array.isArray(contentListJson)) {
console.warn('[TextFittingAdapter] 无效的 contentListJson跳过预处理');
return;
}
if (!translatedContentList || !Array.isArray(translatedContentList)) {
console.warn('[TextFittingAdapter] 无效的 translatedContentList跳过预处理');
return;
}
console.log('[TextFittingAdapter] 开始预处理全局字号...');
const startTime = performance.now();
const globalFontScale = this.options.globalFontScale;
const BBOX_NORMALIZED_RANGE = 1000;
contentListJson.forEach((item, idx) => {
if (item.type !== 'text' || !item.bbox) return;
const translatedItem = translatedContentList[idx];
if (!translatedItem || !translatedItem.text) return;
const bbox = item.bbox;
const height = (bbox[3] - bbox[1]) / BBOX_NORMALIZED_RANGE;
const estimatedFontSize = height * globalFontScale;
this.globalFontSizeCache.set(idx, {
estimatedFontSize: estimatedFontSize,
bbox: bbox
});
});
console.log(`[TextFittingAdapter] 预处理完成:全局缩放=${globalFontScale}, 耗时=${(performance.now() - startTime).toFixed(0)}ms`);
this.hasPreprocessed = true;
}
```
**影响**: 🟡 中等 - 防止崩溃
---
### 6. TextFittingAdapter - 添加ctx验证
**问题位置**: `TextFittingAdapter.js` line 309
**当前代码**:
```javascript
wrapText(ctx, text, maxWidth) {
if (!text) return [];
const lines = [];
let currentLine = '';
// ...
const metrics = ctx.measureText(testLine); // ❌ ctx 可能无效
}
```
**修复方案**:
```javascript
wrapText(ctx, text, maxWidth) {
// ✅ 添加验证
if (!text) return [];
if (!ctx || typeof ctx.measureText !== 'function') {
console.warn('[TextFittingAdapter] 无效的 canvas context');
// 返回简单分割
return text.split('\n').length > 0 ? text.split('\n') : [''];
}
if (typeof maxWidth !== 'number' || maxWidth <= 0) {
console.warn('[TextFittingAdapter] 无效的 maxWidth');
return text.split('\n');
}
const lines = [];
let currentLine = '';
const segments = text.split(/([。?!,、;:\n])/);
for (let segment of segments) {
if (!segment) continue;
if (/^[。?!,、;:]$/.test(segment)) {
currentLine += segment;
continue;
}
if (segment === '\n') {
if (currentLine) {
lines.push(currentLine);
currentLine = '';
}
continue;
}
for (let i = 0; i < segment.length; i++) {
const char = segment[i];
const testLine = currentLine + char;
const metrics = ctx.measureText(testLine);
if (metrics.width > maxWidth && currentLine.length > 0) {
lines.push(currentLine);
currentLine = char;
} else {
currentLine = testLine;
}
}
}
if (currentLine) {
lines.push(currentLine);
}
return lines.length > 0 ? lines : [''];
}
```
**影响**: 🟡 中等 - 防止崩溃
---
### 7. PDFExporter - 改进fontkit失败处理
**问题位置**: `PDFExporter.js` line 73-90, 395-410
**当前代码**:
```javascript
let font = null;
try {
if (typeof fontkit === 'undefined') {
throw new Error('fontkit 未加载,无法嵌入中文字体');
}
// ...
font = await pdfDoc.embedFont(fontBytes);
} catch (fontError) {
console.error('[PDFExporter] 中文字体加载失败:', fontError);
if (showNotification) {
showNotification('中文字体加载失败无法导出PDF: ' + fontError.message, 'error');
}
throw fontError; // ❌ 中断流程
}
// 但下面又有:
if (typeof fontkit === 'undefined') {
await new Promise((resolve, reject) => {
// ...
script.onerror = (error) => {
console.warn('[PDFExporter] fontkit 加载失败:', error);
resolve(); // ❌ 失败也继续!
};
});
}
```
**修复方案**:
```javascript
async loadPdfLib() {
// 加载 pdf-lib
if (typeof PDFLib === 'undefined') {
await new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = this.options.pdfLibUrl;
script.onload = () => {
console.log('[PDFExporter] pdf-lib 加载成功');
this.pdfLibLoaded = true;
resolve();
};
script.onerror = (error) => {
console.error('[PDFExporter] pdf-lib 加载失败:', error);
reject(new Error('Failed to load pdf-lib library'));
};
document.head.appendChild(script);
});
}
// 加载 fontkit (可选,失败不中断)
if (typeof fontkit === 'undefined') {
await new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = this.options.fontkitUrl;
script.onload = () => {
console.log('[PDFExporter] fontkit 加载成功');
this.fontkitLoaded = true;
resolve();
};
script.onerror = (error) => {
console.warn('[PDFExporter] fontkit 加载失败,中文字体可能无法正确显示:', error);
this.fontkitLoaded = false;
resolve(); // 不中断流程,但记录失败
};
document.head.appendChild(script);
});
}
}
async exportStructuredTranslation(originalPdfBase64, translatedContentList, showNotification = null) {
try {
// ... 前面的检查 ...
// ✅ 改进字体加载
let font = null;
if (!this.fontkitLoaded) {
console.warn('[PDFExporter] fontkit 未成功加载,中文字体可能无法正确显示');
if (showNotification) {
showNotification('警告:中文字体可能无法正确显示', 'warning');
}
} else {
try {
console.log('[PDFExporter] 正在加载中文字体...');
const fontBytes = await fetch(this.options.fontUrl).then(res => {
if (!res.ok) throw new Error(`HTTP ${res.status}: ${res.statusText}`);
return res.arrayBuffer();
});
font = await pdfDoc.embedFont(fontBytes);
console.log('[PDFExporter] 中文字体加载成功');
} catch (fontError) {
console.error('[PDFExporter] 中文字体加载失败:', fontError);
if (showNotification) {
showNotification('警告:中文字体加载失败,将使用默认字体', 'warning');
}
// ✅ 不中断流程,继续使用默认字体
}
}
// 如果没有字体,使用默认字体
if (!font) {
console.warn('[PDFExporter] 使用PDF默认字体中文可能显示为空');
// 可以选择使用内置字体或继续
}
// ... 后续处理 ...
} catch (error) {
// ...
}
}
```
**影响**: 🟡 中等 - 提高鲁棒性
---
### 8. PDFExporter - 添加showNotification类型检查
**问题位置**: `PDFExporter.js` 多处
**当前代码**:
```javascript
if (showNotification) {
showNotification('没有翻译内容可导出', 'warning'); // ❌ 未检查是否为函数
}
```
**修复方案**:
```javascript
// 在类中添加辅助方法
_notify(message, type = 'info') {
if (typeof this._showNotification === 'function') {
this._showNotification(message, type);
} else if (!this._notificationDisabled) {
console.log(`[${type.toUpperCase()}] ${message}`);
}
}
constructor(options = {}, showNotification = null) {
this.options = Object.assign({
fontUrl: 'https://...',
pdfLibUrl: 'https://...',
fontkitUrl: 'https://...',
bboxNormalizedRange: 1000
}, options);
this._showNotification = typeof showNotification === 'function' ? showNotification : null;
this.pdfLibLoaded = false;
this.fontkitLoaded = false;
}
async exportStructuredTranslation(originalPdfBase64, translatedContentList, showNotification = null) {
// ✅ 更新 showNotification
if (typeof showNotification === 'function') {
this._showNotification = showNotification;
}
try {
if (!translatedContentList || translatedContentList.length === 0) {
this._notify('没有翻译内容可导出', 'warning');
return;
}
if (!originalPdfBase64) {
this._notify('原始PDF数据不可用', 'error');
return;
}
this._notify('正在生成译文PDF请稍候...', 'info');
// ... 后续代码 ...
} catch (error) {
this._notify('导出失败: ' + error.message, 'error');
}
}
```
**影响**: 🟡 中等 - 提高健壮性
---
### 9. SegmentManager - 改进离屏canvas重用
**问题位置**: `SegmentManager.js` line 280-299
**当前代码**:
```javascript
async renderSegment(seg) {
const off = document.createElement('canvas'); // ❌ 每次创建
const offCtx = off.getContext('2d', { willReadFrequently: true, alpha: false });
for (const p of seg.pages) {
if (off.width !== p.width) off.width = p.width; // ❌ 频繁重新分配
if (off.height !== p.height) off.height = p.height;
// ...
}
// ❌ canvas 没有被清理,垃圾回收等待
}
```
**修复方案**:
```javascript
constructor(pdfDoc, options = {}) {
// ... 其他初始化 ...
this._offscreenCanvas = null; // ✅ 缓存离屏canvas
this._offscreenCtx = null; // ✅ 缓存context
this._maxOffscreenSize = { width: 0, height: 0 }; // ✅ 追踪最大尺寸
}
_getOffscreenCanvas(width, height) {
// ✅ 复用或创建离屏canvas
if (!this._offscreenCanvas) {
this._offscreenCanvas = document.createElement('canvas');
this._offscreenCtx = this._offscreenCanvas.getContext('2d', {
willReadFrequently: true,
alpha: false
});
}
// ✅ 只在需要时扩大(不缩小,避免频繁分配)
if (width > this._maxOffscreenSize.width || height > this._maxOffscreenSize.height) {
this._offscreenCanvas.width = Math.max(width, this._maxOffscreenSize.width);
this._offscreenCanvas.height = Math.max(height, this._maxOffscreenSize.height);
this._maxOffscreenSize.width = this._offscreenCanvas.width;
this._maxOffscreenSize.height = this._offscreenCanvas.height;
console.log(`[SegmentManager] 离屏canvas扩展为 ${this._offscreenCanvas.width}x${this._offscreenCanvas.height}`);
}
return { canvas: this._offscreenCanvas, ctx: this._offscreenCtx };
}
async renderSegment(seg) {
for (const p of seg.pages) {
const { canvas: off, ctx: offCtx } = this._getOffscreenCanvas(p.width, p.height);
// ✅ 重新设置尺寸为当前page的尺寸只是清除不重新分配
off.width = p.width;
off.height = p.height;
offCtx.clearRect(0, 0, off.width, off.height);
await p.page.render({ canvasContext: offCtx, viewport: p.viewport }).promise;
// 绘制到左右段画布
seg.left.ctx.drawImage(off, 0, p.yInSegPx);
seg.right.ctx.drawImage(off, 0, p.yInSegPx);
}
// 绘制 overlays
await this.renderSegmentOverlays(seg);
}
destroy() {
// ... 其他清理 ...
// ✅ 清理离屏canvas
this._offscreenCanvas = null;
this._offscreenCtx = null;
this._maxOffscreenSize = { width: 0, height: 0 };
this.segments = [];
this.pageInfos = [];
}
```
**影响**: 🟡 中等 - 性能优化
---
### 10. SegmentManager - 添加容器验证
**问题位置**: `SegmentManager.js` line 209-210
**当前代码**:
```javascript
createSegmentDom(seg, dpr) {
// ...
buildSide(this.originalSegmentsContainer, 'left'); // ❌ 容器可能为null
buildSide(this.translationSegmentsContainer, 'right');
}
const buildSide = (container, side) => {
// ...
container.appendChild(wrapper); // ❌ 如果container为null会崩溃
};
```
**修复方案**:
```javascript
createSegmentDom(seg, dpr) {
// ✅ 验证容器
if (!this.originalSegmentsContainer || !this.translationSegmentsContainer) {
console.error('[SegmentManager] 容器未初始化无法创建段DOM');
return false;
}
const cssWidth = seg.widthPx / dpr;
const cssHeight = seg.heightPx / dpr;
const buildSide = (container, side) => {
if (!container) {
console.error(`[SegmentManager] ${side} 容器为null`);
return;
}
const wrapper = document.createElement('div');
wrapper.className = 'pdf-segment-wrapper';
wrapper.style.position = 'relative';
wrapper.style.display = 'block';
wrapper.style.width = cssWidth + 'px';
wrapper.style.height = cssHeight + 'px';
wrapper.style.margin = '0';
const canvas = document.createElement('canvas');
canvas.width = seg.widthPx;
canvas.height = seg.heightPx;
canvas.style.width = cssWidth + 'px';
canvas.style.height = cssHeight + 'px';
const ctx = canvas.getContext('2d', { willReadFrequently: true, alpha: false });
const overlay = document.createElement('canvas');
overlay.width = seg.widthPx;
overlay.height = seg.heightPx;
overlay.style.width = cssWidth + 'px';
overlay.style.height = cssHeight + 'px';
overlay.style.position = 'absolute';
overlay.style.left = '0';
overlay.style.top = '0';
const overlayCtx = overlay.getContext('2d', { willReadFrequently: true });
wrapper.appendChild(canvas);
wrapper.appendChild(overlay);
container.appendChild(wrapper);
const sideObj = { wrapper, canvas, ctx, overlay, overlayCtx };
if (side === 'left') seg.left = sideObj;
else seg.right = sideObj;
// 绑定点击事件
if (side === 'left' && this.onOverlayClick) {
overlay.addEventListener('click', (e) => this.onOverlayClick(e, seg));
}
};
buildSide(this.originalSegmentsContainer, 'left');
buildSide(this.translationSegmentsContainer, 'right');
return true;
}
```
**影响**: 🟡 中等 - 防止崩溃
---
## 🟢 低优先级 - 可选改进
### 11. SegmentManager - 添加BBOX_NORMALIZED_RANGE验证
**问题位置**: `SegmentManager.js` line 334-365
**当前代码**:
```javascript
const BBOX_NORMALIZED_RANGE = this.options.bboxNormalizedRange;
// ...
const scaleX = p.width / BBOX_NORMALIZED_RANGE; // ❌ 如果为0会导致Infinity
```
**修复方案**:
```javascript
async clearTextInSegment(seg) {
if (!this.contentListJson || !this.clearTextInBbox) {
console.warn('[SegmentManager] 缺少清除文字依赖');
return;
}
const BBOX_NORMALIZED_RANGE = this.options.bboxNormalizedRange;
// ✅ 添加验证
if (!BBOX_NORMALIZED_RANGE || BBOX_NORMALIZED_RANGE <= 0) {
console.error('[SegmentManager] 无效的 bboxNormalizedRange:', BBOX_NORMALIZED_RANGE);
return;
}
// ... 后续代码 ...
}
```
**影响**: 🟢 低 - 防止数值错误
---
### 12. PDFExporter - 改进rgb色值处理
**问题位置**: `PDFExporter.js` line 133
**当前代码**:
```javascript
page.drawRectangle({
x: x,
y: y,
width: width,
height: height,
color: rgb(1, 1, 1), // ⚠️ 这在pdf-lib中是正确的0-1范围
});
```
**说明**: 实际上这是正确的pdf-lib使用0-1范围的RGB值。但建议添加注释
```javascript
page.drawRectangle({
x: x,
y: y,
width: width,
height: height,
color: rgb(1, 1, 1), // ✅ pdf-lib使用0-1范围不是0-255
});
```
**影响**: 🟢 低 - 文档优化
---
## 修复优先级排序
### Phase 1 - 立即修复 (必须在测试前)
1. ✅ TextFittingAdapter - 添加 BBOX_NORMALIZED_RANGE 定义
2. ✅ PDFExporter - 统一Canvas和PDF文本高度公式
3. ✅ SegmentManager - 修复事件监听器清理
### Phase 2 - 尽快修复 (本周内)
4. ✅ TextFittingAdapter - 改进错误处理
5. ✅ TextFittingAdapter - 添加参数验证
6. ✅ TextFittingAdapter - 添加ctx验证
7. ✅ PDFExporter - 改进fontkit失败处理
8. ✅ PDFExporter - 添加showNotification类型检查
### Phase 3 - 后续优化 (下周)
9. ✅ SegmentManager - 改进离屏canvas重用
10. ✅ SegmentManager - 添加容器验证
11. ✅ SegmentManager - 添加BBOX_NORMALIZED_RANGE验证
12. ✅ PDFExporter - 改进rgb色值注释
---
## 测试检查清单
修复完成后,按以下顺序测试:
- [ ] TextFittingAdapter.initialize() 失败时是否正确抛出错误
- [ ] preprocessGlobalFontSizes() 接收无效参数时是否正确处理
- [ ] wrapText() 接收无效ctx时是否降级处理
- [ ] PDFExporter 导出的PDF文本大小是否与Canvas显示一致
- [ ] PDFExporter fontkit加载失败时是否继续导出可能带警告
- [ ] SegmentManager 滚动时是否继续渲染,销毁后是否停止
- [ ] SegmentManager destroy() 调用后内存是否释放
- [ ] SegmentManager setContainers(null, ...) 时是否正确处理
- [ ] 长PDF (100+页) 是否能正确分段和渲染
- [ ] 切换PDF后旧的事件监听器是否被清理
---
## 集成测试示例
```javascript
// 完整的集成测试
async function testModuleIntegration() {
console.log('开始模块集成测试...');
// 1. 测试TextFittingAdapter
console.log('\n1. 测试 TextFittingAdapter');
try {
const textFitter = new TextFittingAdapter({
globalFontScale: 0.9
});
// 应该throw而不是静默失败
try {
textFitter.initialize();
console.warn('⚠️ initialize() 应该检查TextFittingEngine');
} catch (e) {
console.log('✅ initialize() 正确抛出错误');
}
// 测试参数验证
textFitter.preprocessGlobalFontSizes(null, null);
console.log('✅ preprocessGlobalFontSizes() 接受无效参数');
// 测试wrapText验证
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const lines = textFitter.wrapText(null, 'test', 100); // 应该降级
console.log('✅ wrapText() 接受无效ctx返回:', lines);
} catch (error) {
console.error('❌ TextFittingAdapter测试失败:', error);
}
// 2. 测试PDFExporter
console.log('\n2. 测试 PDFExporter');
try {
const exporter = new PDFExporter();
// 测试没有翻译数据
await exporter.exportStructuredTranslation('', [], (msg, type) => {
console.log(`[${type}] ${msg}`);
});
console.log('✅ 空翻译数据处理正确');
} catch (error) {
console.error('❌ PDFExporter测试失败:', error);
}
// 3. 测试SegmentManager
console.log('\n3. 测试 SegmentManager');
try {
// 模拟pdfDoc
const mockPdfDoc = {
numPages: 10,
getPage: async (n) => ({
getViewport: ({ scale }) => ({ width: 612, height: 792, scale }),
render: async ({ canvasContext, viewport }) => ({ promise: Promise.resolve() })
})
};
const manager = new SegmentManager(mockPdfDoc);
// 设置依赖和容器
const origContainer = document.createElement('div');
const transContainer = document.createElement('div');
manager.setContainers(origContainer, transContainer, window, window);
// 测试setContainers(null)
manager.setContainers(null, null, null, null);
console.log('✅ setContainers() 接受nullcreateSegmentDom应该降级');
// 测试destroy
manager.destroy();
console.log('✅ destroy() 执行成功');
} catch (error) {
console.error('❌ SegmentManager测试失败:', error);
}
console.log('\n✅ 模块集成测试完成');
}
// 运行测试
testModuleIntegration();
```