26 KiB
模块修复建议清单
🔴 高优先级 - 必须修复
1. TextFittingAdapter - 未定义的常量
问题位置: TextFittingAdapter.js line 71
当前代码:
preprocessGlobalFontSizes(contentListJson, translatedContentList) {
// ...
contentListJson.forEach((item, idx) => {
// ...
const height = (bbox[3] - bbox[1]) / BBOX_NORMALIZED_RANGE; // ❌ 未定义!
});
}
修复方案:
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.jsline 1463-1465 - PDF版本:
PDFExporter.jsline 272-274
当前代码对比:
Canvas (错误):
const totalHeight = lines.length === 1
? mid * 1.2 // 单行文本额外增加20%
: (lines.length - 1) * lineHeight + mid * 1.2;
PDF (错误):
const totalHeight = lines.length > 0
? (lines.length - 1) * lineHeight + mid // 单行文本没有增加
: 0;
修复方案 - 统一为一致的公式:
// 在 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
当前代码:
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 = [];
}
修复方案:
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
当前代码:
initialize() {
if (typeof TextFittingEngine === 'undefined') {
console.error('[TextFittingAdapter] TextFittingEngine 未加载!...');
return; // ❌ 静默失败
}
// ...
}
修复方案:
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; // ✅ 将错误传播给调用者
}
}
使用方式:
try {
const textFitter = new TextFittingAdapter();
textFitter.initialize();
} catch (error) {
console.error('初始化失败,将使用回退方案');
// 处理回退...
}
影响: 🟡 中等 - 便于发现问题
5. TextFittingAdapter - 添加参数验证
问题位置: TextFittingAdapter.js line 64-93
当前代码:
preprocessGlobalFontSizes(contentListJson, translatedContentList) {
// ❌ 没有验证参数
if (this.hasPreprocessed) return;
const globalFontScale = this.options.globalFontScale;
contentListJson.forEach((item, idx) => { // ❌ 可能不是数组
// ...
});
}
修复方案:
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
当前代码:
wrapText(ctx, text, maxWidth) {
if (!text) return [];
const lines = [];
let currentLine = '';
// ...
const metrics = ctx.measureText(testLine); // ❌ ctx 可能无效
}
修复方案:
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
当前代码:
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(); // ❌ 失败也继续!
};
});
}
修复方案:
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 多处
当前代码:
if (showNotification) {
showNotification('没有翻译内容可导出', 'warning'); // ❌ 未检查是否为函数
}
修复方案:
// 在类中添加辅助方法
_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
当前代码:
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 没有被清理,垃圾回收等待
}
修复方案:
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
当前代码:
createSegmentDom(seg, dpr) {
// ...
buildSide(this.originalSegmentsContainer, 'left'); // ❌ 容器可能为null
buildSide(this.translationSegmentsContainer, 'right');
}
const buildSide = (container, side) => {
// ...
container.appendChild(wrapper); // ❌ 如果container为null会崩溃
};
修复方案:
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
当前代码:
const BBOX_NORMALIZED_RANGE = this.options.bboxNormalizedRange;
// ...
const scaleX = p.width / BBOX_NORMALIZED_RANGE; // ❌ 如果为0会导致Infinity
修复方案:
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
当前代码:
page.drawRectangle({
x: x,
y: y,
width: width,
height: height,
color: rgb(1, 1, 1), // ⚠️ 这在pdf-lib中是正确的(0-1范围)
});
说明: 实际上这是正确的,pdf-lib使用0-1范围的RGB值。但建议添加注释:
page.drawRectangle({
x: x,
y: y,
width: width,
height: height,
color: rgb(1, 1, 1), // ✅ pdf-lib使用0-1范围(不是0-255)
});
影响: 🟢 低 - 文档优化
修复优先级排序
Phase 1 - 立即修复 (必须在测试前)
- ✅ TextFittingAdapter - 添加 BBOX_NORMALIZED_RANGE 定义
- ✅ PDFExporter - 统一Canvas和PDF文本高度公式
- ✅ SegmentManager - 修复事件监听器清理
Phase 2 - 尽快修复 (本周内)
- ✅ TextFittingAdapter - 改进错误处理
- ✅ TextFittingAdapter - 添加参数验证
- ✅ TextFittingAdapter - 添加ctx验证
- ✅ PDFExporter - 改进fontkit失败处理
- ✅ PDFExporter - 添加showNotification类型检查
Phase 3 - 后续优化 (下周)
- ✅ SegmentManager - 改进离屏canvas重用
- ✅ SegmentManager - 添加容器验证
- ✅ SegmentManager - 添加BBOX_NORMALIZED_RANGE验证
- ✅ PDFExporter - 改进rgb色值注释
测试检查清单
修复完成后,按以下顺序测试:
- TextFittingAdapter.initialize() 失败时是否正确抛出错误
- preprocessGlobalFontSizes() 接收无效参数时是否正确处理
- wrapText() 接收无效ctx时是否降级处理
- PDFExporter 导出的PDF文本大小是否与Canvas显示一致
- PDFExporter fontkit加载失败时是否继续导出(可能带警告)
- SegmentManager 滚动时是否继续渲染,销毁后是否停止
- SegmentManager destroy() 调用后内存是否释放
- SegmentManager setContainers(null, ...) 时是否正确处理
- 长PDF (100+页) 是否能正确分段和渲染
- 切换PDF后旧的事件监听器是否被清理
集成测试示例
// 完整的集成测试
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() 接受null,createSegmentDom应该降级');
// 测试destroy
manager.destroy();
console.log('✅ destroy() 执行成功');
} catch (error) {
console.error('❌ SegmentManager测试失败:', error);
}
console.log('\n✅ 模块集成测试完成');
}
// 运行测试
testModuleIntegration();