关于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效果图:

相关推荐
阿伟来咯~31 分钟前
记录学习react的一些内容
javascript·学习·react.js
吕彬-前端36 分钟前
使用vite+react+ts+Ant Design开发后台管理项目(五)
前端·javascript·react.js
学前端的小朱39 分钟前
Redux的简介及其在React中的应用
前端·javascript·react.js·redux·store
guai_guai_guai1 小时前
uniapp
前端·javascript·vue.js·uni-app
也无晴也无风雨1 小时前
在JS中, 0 == [0] 吗
开发语言·javascript
bysking2 小时前
【前端-组件】定义行分组的表格表单实现-bysking
前端·react.js
王哲晓2 小时前
第三十章 章节练习商品列表组件封装
前端·javascript·vue.js
fg_4112 小时前
无网络安装ionic和运行
前端·npm
理想不理想v2 小时前
‌Vue 3相比Vue 2的主要改进‌?
前端·javascript·vue.js·面试
酷酷的阿云2 小时前
不用ECharts!从0到1徒手撸一个Vue3柱状图
前端·javascript·vue.js