
前言
本文章旨在介绍该插件对于富文本的支持情况,及二次开发以支持部分富文本元素显示提供思路。对于初次接触的人可以先看文章不限于Vue!vue-plugin-hiprint 打印插件完整使用指南,里面包含了我对这个插件的全部了解,之后再看本文章。
插件对于富文本的支持情况
分析过程
这一步需要查看该插件具体是如何渲染的各个元素,因为此处的分析没有什么太多价值,都是力气活儿,所以只介绍一下思路:从hiprintTemplate.getHtml入手,查找各个元素是什么时候被具体转为了真正的dom。排查后可以总结出这样一条渲染链路
hiprintTemplate.getHtml=>panel.getHtml=>元素的getHtml
需要注意的是,由于每一种类型的元素的真实dom不同,所以他们的getHtml也就不同,只有找寻对应元素的getHtml才能知道它们是如何渲染的。所以我们可以直接打印hiprintTemplate的实例,按照 printPanels.printElements的路径找到对应的元素实例,[[Prototype]]上即可找对应的getHtml。
分析结果
1.内置类型html
js
function(t, e, printData) {
return '<div style="height:50pt;width:50pt;background:red;border-radius: 50%;"></div>';
}
html通过formatter函数可以渲染出dom元素,通过printData获取到富文本数据,在return中返回即可。
-:html内部没有分页的逻辑,虽然可以渲染,但是超过当前页的数据会被截断。如果使用这个元素的话,就需要自己完善html的分页逻辑,目测相当麻烦。
2.longText
在插件的使用过程中发现longText中直接放置富文本,文字的部分是可以渲染出来,并且longText本身支持处理长文本分页的情况,虽然该元素类型的本身的分页方法只能处理普通文本,但是起码分页的部分不用自己从头写。
不足:1.HTML转义符截断,虽然大部分文本都可以正常显示,但是如果分页刚巧在转义符中间(比如:"),那么这个转义符就会被截断 2.标签截断,当分页刚巧在标签上的时候(比如:<img sr="" />的自中间),图片将无法显示,文本部分会出现例如 /> 剩余的残缺字符。
两者相比,我觉得还是longText的改造难度小一些,所以接下来就是二次开发longText。
二次开发longText
思路
问题1:如何获取元素的原型
由于元素的getHTML方法是放在元素的原型上的,我们想要二次开发getHTML方法就需要找到它的原型。
为了找到原型,我们就需要从初始化模板hiprintTemplate.PrintTemplate()开始,一直找到元素创建实例的部分。
最终发现可以通过 getElementType 获取元素类型实例,通过元素类型实例的createPrintElement即可创建对应的元素实例,而 hiprint.PrintElementTypeManager上就有getElementType。
js
hiprint.PrintElementTypeManager.getElementType(tid).createPrintElement()
通过传入longText的tid,最终获取到了元素的实例
问题2:如何处理截断问题
首先分析了longText的getHtml,大概的逻辑是他把整个长文数据拆成单个字符的数组 ,然后通过 IsPaginationIndex 渲染临时容器判断是否超过页面的大小。超过就是使用二分法寻找分页点,因为是这种直接断开的方式所以,会造成截断现象。
js
IsPaginationIndex = function(e3, t3, i4, n3) {
// e3: 字符数组
// t3: 尝试的索引位置
// i4: 最大允许高度(px)
// n3: 临时容器
// 有高度限制模式
// 测试:包含 t3+2 个字符
n3.find(".hiprint-printElement-longText-content")
.html(e3.slice(0, t3 + 2).join(""));
var r2 = n3.height(); // 高度1
// 测试:包含 t3+1 个字符
n3.find(".hiprint-printElement-longText-content")
.html(e3.slice(0, t3 + 1).join(""));
var A3 = n3.height(); // 高度2
// 判断逻辑:
return t3 >= e3.length - 1 && A3 < i4 ? // 情况1:处理完所有字符且未超限
{ IsPagination: true, height: o.a.px.toPt(A3), length: e3.length, target: n3.clone() } :
A3 <= i4 && i4 <= r2 ? // 情况2:当前高度<=限制<=下一字符高度(找到分页点)
{ IsPagination: true, height: A3, length: t3 + 1, target: n3.clone() } :
i4 <= A3 ? // 情况3:限制高度小于等于当前高度(向左搜索)
{ IsPagination: false, move: "l" } :
r2 <= i4 ? // 情况4:限制高度大于等于下一字符高度(向右搜索)
{ IsPagination: false, move: "r" } :
{ IsPagination: true, result: 1 }; // 其他情况
}
我想的解决方法就是,识别分页点所在的位置是否处于不能被截断的字符中间,返回不能被截断的字符的开头索引。
代码
整体代码不变,只需要替换情况2时,重新找到分页点即可,也就是将
js
{ IsPagination: true, height: A3, length: t3 + 1, target: n3.clone() }
替换为
js
{
IsPagination: true,
height: A3,
length: (length = adjustSplitForRichText(e3, t3 + 1), length),
target: (n3.find(".hiprint-printElement-longText-content").html(e3.slice(0, length).join("")), n3.clone()) // 因为已经重新设置分页点了,所以前一页的内容需要按新分页点重新渲染
}
js
// html: 字符数组
// position: 原始分页位置
// 返回值: 调整后的安全分页位置
function adjustSplitForRichText(html, position) {
// 检查并调整HTML实体
const entityAdjusted = getEntityEndOrPosition(html, position);
if (entityAdjusted !== position) return entityAdjusted;
// 检查并调整图片标签
const imgAdjusted = getImgTagStartIfInside(html, position);
if (imgAdjusted !== -1) return imgAdjusted;
return position;
}
我在这里只写了识别HTML实体和图片标签的方法,因为目前项目中只用到了这些,如果需要更多的富文本支持,就需要自己写更多的识别算法,这个就看需求了。
getEntityEndOrPosition与 getImgTagStartIfInside都是用来识别不可拆分字符串的算法,我的算法放在这里仅作参考,如果有性能需求,可以自己重新实现。
js
// 常见HTML实体集合
const HTML_ENTITIES = new Set([
'nbsp', 'lt', 'gt', 'amp', 'quot', 'apos',
'ldquo', 'rdquo', 'lsquo', 'rsquo',
'mdash', 'ndash', 'copy', 'reg', 'trade',
'euro', 'pound', 'cent', 'yen', 'sect',
'deg', 'plusmn', 'times', 'divide',
'middot', 'bull', 'hellip', 'prime', 'Prime',
'oline', 'frasl', 'weierp', 'image', 'real',
'alefsym', 'larr', 'uarr', 'rarr', 'darr',
'harr', 'crarr', 'lArr', 'uArr', 'rArr', 'dArr',
'hArr', 'forall', 'part', 'exist', 'empty',
'nabla', 'isin', 'notin', 'ni', 'prod', 'sum',
'minus', 'lowast', 'radic', 'prop', 'infin',
'ang', 'and', 'or', 'cap', 'cup', 'int',
'there4', 'sim', 'cong', 'asymp', 'ne',
'equiv', 'le', 'ge', 'sub', 'sup', 'nsub',
'sube', 'supe', 'oplus', 'otimes', 'perp',
'sdot', 'lceil', 'rceil', 'lfloor', 'rfloor',
'lang', 'rang', 'loz', 'spades', 'clubs',
'hearts', 'diams', 'ensp', 'emsp', 'thinsp',
'zwnj', 'zwj', 'lrm', 'rlm', 'shy'
]);
function getEntityEndOrPosition(html, position, maxLook = 7) {
// 边界检查
if (position < 0 || position >= html.length) {
return position;
}
// 向前查找&,最多查找maxLook个字符
let ampPos = -1;
for (let i = position; i >= Math.max(0, position - maxLook); i--) {
if (html[i] === '&') {
ampPos = i;
break;
}
}
// 没有找到&,返回原位置
if (ampPos === -1) {
return position;
}
// 向后查找;,最多查找maxLook个字符
let semicolonPos = -1;
for (let i = ampPos + 1; i < Math.min(html.length, ampPos + maxLook + 1); i++) {
if (html[i] === ';') {
semicolonPos = i;
break;
}
}
// 没有找到;,返回原位置
if (semicolonPos === -1) {
return position;
}
// 提取&和;之间的内容
const entityContent = html.slice(ampPos + 1, semicolonPos).join('');
// 检查是否为数字实体({)或十六进制实体( )
if (entityContent.startsWith('#')) {
// 数字实体或十六进制实体,返回&位置
return ampPos;
}
// 检查是否在HTML实体集合中
if (HTML_ENTITIES.has(entityContent.toLowerCase())) {
// 是有效的HTML实体,返回&的位置
return ampPos;
}
// 不是有效的HTML实体,返回原位置
return position;
}
function getImgTagStartIfInside(htmlArray, position, maxLook = 100) {
if (position < 0 || position >= htmlArray.length) return -1;
// 向前查找,最多maxLook个字符
for (let i = position-1; i >= Math.max(0, position - maxLook); i--) {
// 如果先遇到>,说明在标签外部
if (htmlArray[i] === '>') {
return -1;
}
// 如果遇到<,检查是否是<img
if (htmlArray[i] === '<') {
// 检查是否是<img标签
if (i + 3 < htmlArray.length &&
htmlArray[i + 1].toLowerCase() === 'i' &&
htmlArray[i + 2].toLowerCase() === 'm' &&
htmlArray[i + 3].toLowerCase() === 'g') {
// 确保img后面是空格、>或/(避免匹配<imagine>等)
if (i + 4 < htmlArray.length) {
const nextChar = htmlArray[i + 4];
if (nextChar !== ' ' && nextChar !== '>' && nextChar !== '/') {
return -1;
}
}
// 是<img标签,返回开始位置
return i;
}
// 是其他标签,不在img内部
return -1;
}
}
// 没找到<,也不在img内部
return -1;
}
注意事项
- 更新风险:直接修改原型方法可能在插件更新时失效
结语
以这种方式处理的富文本数据,在一定程度上可以解决对应截断问题,但是如果需要更加完善的解决方法,还是得写一套完整的富文本解析,嵌入到longText的方法中,提交给vue-plugin-hiprint的作者。抛砖引玉,希望有哪位大神完善一下吧~~~
如果文章你有所帮助,还请不吝啬您的点赞👍👍👍