关于pdf.js中文本坐标尺寸的使用

一个电子教材项目中有这样一个需求:

用户向网站上传一个PDF书籍后,网站可以对PDF书籍进行解析,并支持用户对PDF书籍的每一页做一些操作,比如:为英语课本的单词和句子添加音频热区。因为热区数量很多,所以,希望网站 "在初始化课本页面的时候,自动初始化热区,然后用户再在此基础上调整",这样可以大大减少工作量。

我使用pdf.js来实现该功能,该库可以获取到pdf中的文本及位置、宽高,但这些位置尺寸使用起来有几处值得注意的细节(稍不注意,可能会被卡很久)。

一、pdf.js提供的文本信息

如图所示,这是一个PDF页面中获取到的文本信息。这里要用到的字段有:height(高)、width(宽)、transform(这是组matrix矩阵数据,其中最末两位分别是水平方向和垂直方向的位置信息)。

二、transform数据对应的坐标系

1)初始坐标数据

通常,我们定位一个元素时,会设置它的left和top,left的数值从左向右递增,top的数值自上而下递增;而transform中的垂直方向数值是从下往上递增的。(下图是坐标系不同导致的错误结果,这个结果是多种原因造成的,后续我们逐一修正)

2)矫正坐标数据(垂直方向翻转)

基于上一步,首先,先来矫正坐标系。我们将垂直方向的数值进行矫正:

新值 = pdf页面高度 - 旧值。

再次渲染后,会发现垂直方向的坐标系已经对了。但仍有两个问题:一个是横纵方向的定位都存在偏差,另一个是热区的宽高比实际文本所占空间大。这主要是因为"绘制pdf的canvas画布的width、height"和"canvas画布在页面布局中被定义的样式style中的width、height"不一致,二者存在比例换算。

三、数据换算

1)矫正比例换算

基于上一步,结合canvas的内外尺寸来矫正热区的宽高和定位:

left = textinfo.transform[4] / canvas.width * canvas.style.width;

top = textinfo.transform[5] / canvas.height * canvas.style.height;

width = textinfo.width / canvas.width * canvas.style.width;

height = textinfo.height / canvas.height * canvas.style.height;

再次渲染后,会发现水平方向的尺寸、定位已经对了。但垂直方向上的定位仍然存在少许偏差。这个问题很细节,我困扰了好几个小时才发觉:我们已经知道初始时的垂直坐标是自下而上的,那么在垂直翻转时,应该把文本所占的高度也减掉才对。

2)再次矫正垂直方向数值

新值 = (pdf页面高度 - 旧值 - 文本自身高度) / canvas.height * canvas.style.height。

修改后再次渲染,可以发现效果已经符合预期了。

四、相关代码片段展示

javascript 复制代码
initHotspots() {
    let pdfDoc = this.loadingTaskDict[this.pageActiveIndex] || this.loadingTask;
    if (!pdfDoc) return;
    pdfDoc.promise.then((pdf) => {
        let pageIndex = this.loadingTaskDict[this.pageActiveIndex] ? 1 : this.pageActiveIndex;
        pdf.getPage(pageIndex).then((page) => {
            let view = page.view || [];
            let pdfPageWidth = view[2] - view[0]; // pdf页面宽度(canvas.width)
            let pdfPageHeight = view[3] - view[1]; // pdf页面高度(canvas.height)
            page.getTextContent().then((textInfo) => {
                textInfo = textInfo || {};
                let textItems = textInfo.items || [];
                // bookPageDom是网页中pdf页面的包裹元素(bookPageInfo.width相当于canvas.style.width)
                let bookPageDom = document.querySelector('.book-page');
                let bookPageInfo = bookPageDom ? bookPageDom.getBoundingClientRect() : null;
                textItems.forEach((v) => {
                    if (/[a-zA-Z]+/i.test(v.str) && v.str.length > 7 && bookPageInfo) {
                        let x = (v.transform[4] / pdfPageWidth) * 100 + '%';
                        let y = ((pdfPageHeight - (v.transform[5] + v.height)) / pdfPageHeight) * 100 + '%';
                        this.addHotpot({
                            top: y,
                            'y%': y,
                            left: x,
                            'x%': x,
                            width: (v.width / pdfPageWidth) * bookPageInfo.width,
                            height: (v.height / pdfPageHeight) * bookPageInfo.height,
                            original: v.str,
                            styles: { left: x, top: y }
                        });
                    }
                });
            });
        });
    });
},

五、最后

上方的截图,因为受制于页面布局,课本页面的尺寸比较小,看不清楚。所以,下面是一张demo效果图:

相关推荐
敲啊敲952717 分钟前
5.npm包
前端·npm·node.js
CodeClimb27 分钟前
【华为OD-E卷-木板 100分(python、java、c++、js、c)】
java·javascript·c++·python·华为od
咸鱼翻面儿31 分钟前
Javascript异步,这次我真弄懂了!!!
javascript
brrdg_sefg32 分钟前
Rust 在前端基建中的使用
前端·rust·状态模式
m0_748230941 小时前
Rust赋能前端: 纯血前端将 Table 导出 Excel
前端·rust·excel
qq_589568101 小时前
Echarts的高级使用,动画,交互api
前端·javascript·echarts
黑客老陈2 小时前
新手小白如何挖掘cnvd通用漏洞之存储xss漏洞(利用xss钓鱼)
运维·服务器·前端·网络·安全·web3·xss
正小安2 小时前
Vite系列课程 | 11. Vite 配置文件中 CSS 配置(Modules 模块化篇)
前端·vite
暴富的Tdy3 小时前
【CryptoJS库AES加密】
前端·javascript·vue.js