解析html并使⽤canvas进⾏渲染
在学习html5的时候,使⽤canvas实现了对html⽂本的解析和渲染,⽀持的tag有<p>、<i>、<b>、<u>、<ul>、<li>,并参考chrome对不规则html进⾏了解析。代码扔在了我的github上(github/myhonor2013/gadgets,⾥⾯的html-render-with-canvas⽬录⾥⾯)。
程序的主函数是个循环,对html⽂本从左往右进⾏解析,wangy每个循环开始处指向有效的html '<'位置,例如'<p'、'<\'、'<f'、'<\x'等都是有效的,⽽'< p'、'< \'、'< \x'等都是⽆效的,总之'<'后⾯必须紧跟⼀个⾮空字符,这是参考了chrome的解析得出的结论。这也就意味着每个循环的末尾必须到第⼀个类似的位置才能结束循环。在每次循环末尾调⽤渲染函数在canvas上进⾏渲染。在循环的过程中要时刻注意html字符串指针是否越界,如果越界则结束循环进⾏渲染。
⼀、预处理
预处理简单地对html⽂本中连续的空字符(回车、tab、缩进)⽤单个的空格进⾏了替换:
var eplace(/[\r\n\t]/g,WHITESPACE).replace(/\s+/g,WHITESPACE).trim();
然后从html⽂本开始位置寻第⼀个所谓的有效tag位置,并对此位置之前的⽂本进⾏渲染。以下则每次循环都以有效的'<'开始。这⼜分两种情况:有效开标签和有效闭标签。
⼆、有效开标签的处理
有效开标签即'<'后⾯不是'\'的标签,⽤正则表达式就是^<[^\/]+.*。寻和'<'匹配的'>'标签并将标签名称push到tagname中。接下来根据tagname确定其后⾯的⽂本应该采⽤的格式,亦即isbold、isitalic、isicon(<li>标签)、isunderline、nowrap、uloffset等属性,并进⽽根据isbold和isitalic确定绘制canvas需要的font属性值。font和isicon、isunderline、nowrap、uloffset便是canvas渲染真正需要的属性。如果是⽀持的tag同时将标签名称push到tagnames,将font ⼊栈到fontsarr中,后⾯的循环要根据这两个属性来确定其作⽤域的⽂本格式。
1 while(text[index]!=WHITESPACE&&text[index]!=RIGHTSYN){
2                        tagname.push(text[index++]);
3                        if(index==len)break;
4                    }
5                    if(index==len)return;
6                    while(text[index]!=RIGHTSYN){
7                        if(index==len){
8                            break;
9                        }
10                    }
11                    var tag=tagname.join('').toLowerCase();
12                    tagname=[];
13                    if(tag==TAGB){
14                        isbold=true;
15                    }
16                    else if(tag==TAGI){
17                        isitalic=true;
18                    }
19                    else if(tag==TAGLI){
20                        isicon=true;
21                    }
22                    else if(tag==TAGU){
23                        isunderline=true;
24                    }
25                    if(tag==TAGP||tag==TAGLI||tag==TAGUL){
26                        nowrap=false;
27                    }
28                    else{
29                        nowrap=true;
30                    }
31                    if(tag==TAGUL){
32                        uloffset+=ULOFFSET;
33                    }
34
35                    if(isitalic==true&&isbold==true){
36                        font=ITALICBOLD;
37                    }
38                    else if(isitalic==false&&isbold==true){
39                        font=BOLD;
40                    }
41                    else if(isitalic==true&&isbold==false){
42                        font=ITALIC;
43                    }
44                    else{
45                        font=NORMAL;
46                    }
47                    ains(tag)){
48                        tagnames.push(tag);
49                        fontsarr.push(font);
50                    }
后⾯部分就是本次循环的作⽤域⽂本,⽂本被放在texttodraw中并在结束前进⾏canvas渲染。在结束前还要将texttodraw清空,并将isicon置为false。
三、有效闭标签的处理
有效闭标签即'<'后⾯紧跟'\'的标签,⽤正则表达式就是^<\/.*。同样往前出其匹配的闭合'<'。如果闭合标签名和tagnames(其中依次保存了有效开标签处理时的标签名称,还记得吗)中的最后⼀个相同,则将tagnames的最后⼀个元素出栈。如果标签名称是ul则对uloffset往前缩进;如果tagnames中不再包含当前标签名称,则根据标签语义对字体进⾏相应处理,这是考虑了多层嵌套的情况。
1 if(text[index]=="/"){
2                    var arr=[];
3                    while(++index<len&&text[index]!=RIGHTSYN&&text[index]!=LEFTSYN){
4arr.push(text[index]);
5                    }
6                    if(index==len)return;
7if(text[index]==LEFTSYN)break;
8var tag=arr.join('').trim().toLowerCase();
9if(tag==tagnames[tagnames.length-1]){
10font=fontsarr.pop();
11tagnames.pop();
12                        if(tag==TAGUL){
13uloffset -=ULOFFSET;
14uloffset =(uloffset>0)?uloffset:0;
15                        }
16                        if(!ains(tag)){
17                            if(tag==TAGI){
18                                place("italic",'normal');
19                                isitalic=false;
20                            }
21                            else if(tag==TAGB){
22                                place("bold",'normal');
23                                isbold=false;
24                            }
25                            else if(tag==TAGU){
26                                isunderline=false;
27                            }
28                        }
29                    }
30                }
接下来同样是本次循环的作⽤域⽂本,对其进⾏获取并根据前⾯确定的属性值对其进⾏渲染。和开标签的处理⼀致,不再赘述。
四、canvas渲染
两个全局变量xoffset和yoffset⽤以标识上次渲染结束后的位置。在渲染开始时⾸先对这两个属性需要根据uloffset、nowrap等属性进⾏调整。然后如果具有isicon属性,则绘制出<li>标签对应的前⾯的实⼼圆。接着就是对⽂本进⾏渲染了,设定font后逐字符取出并使⽤measureText 测量是否满⾏,如果是则绘制后需要换⾏。在渲染过程中如果需要绘制下划线则⼀并进⾏绘制。如此反复,直到所有字符绘制完毕。完整的渲染函数如下:
1 var  drawtext=function(data){
2                im();
3                var len=data.length;
4                if(len==0){
5                    return;
6                }
7                if(!nowrap&&xoffset>MARGIN){html ul标签
8                    xoffset = MARGIN+uloffset;
9                    yoffset += LINEHEIGHT;
10                }
11
12                if(isicon){
13                    ctx.beginPath();
14                    ctx.arc(MARGIN+uloffset+MARGIN,yoffset-MARGIN,MARGIN,0,Math.PI*2,true);
15                    ctx.closePath();
16                    ctx.fill();
17                    xoffset +=30;
18                }
19
20
21                var index=0;
22                var renderindex=0;
23                ctx.font=font;
24                while(index<len){
25                    while(canvaswidth-xoffset&asureText(data.substring(renderindex,++index)).width){
26                        if(index===len){
27                            break;
28                        }
29                    }
30
31                    if(index==len){
32                        ctx.fillText(data.substring(renderindex,index),xoffset,yoffset);
33                        if(isunderline){
34                            canvas.strokeStyle = "red";
35                            canvas.lineWidth = 5;
36                            ctx.beginPath();
37                            veTo(xoffset, yoffset);
38                            ctx.lineTo(asureText(data.substring(renderindex,index)).width, yoffset);
39                            ctx.closePath();
40                            ctx.stroke();
41                        }
42                        xoffset+=asureText(data.substring(renderindex,index)).width;
43                        break;
44                    }
45                    ctx.fillText(data.substring(renderindex,--index),xoffset,yoffset);
46                    if(isunderline){
47                        canvas.strokeStyle = "red";
48                        canvas.lineWidth = 5;
49                        ctx.beginPath();
50                        veTo(xoffset, yoffset);
51                        ctx.lineTo(canvaswidth, yoffset);
52                        ctx.closePath();
53                        ctx.stroke();
54                    }
55
56
57                    renderindex=index;
58                    xoffset = MARGIN;
59                    yoffset += LINEHEIGHT;
60                }
61                return;
62            };
结束语
使⽤js解析html时切忌使⽤递归,这样处理很容易造成堆栈溢出和性能问题。另代码中出现的Array的contains⽅法是在Array的prototype上添加的⽤以判断是否包含字符串的⽅法:
ains=function(item){
return new RegExp("^" + this.join("|")+ "$","i").String());
}

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。