我们知道,在一个div里加上contenteditable="true"之后,它就变成了一个可编辑的框,而且是能满足基本需求的富文本编辑框。例如下面的文字:
它的源HTML如下所示:
<div contenteditable="true">这是<font size="1">第一font>行。<div>This is the <u>secondu> line.div><div>这是<font color="#ff0000">第四font><b>行。b>div><div><b>Thisb> is the <font face="微软雅黑">fourthfont> line.div>div>
如果要将上述HTML转成svg文本,而且看起来跟HTML效果一模一样,有两个难点需要解决:
理清HTML源代码的结构。
处理换行的问题。例如This is the second line.这行文字,在HTML里是自动换行了,但SVG没有自动换行的功能,必须计算出原文本在哪个地方换行。
本文的讲述中,文本所使用的样式包括:水平对齐、垂直对齐、字体、字体大小、字体颜色、加粗和下划线。
其中,水平对齐和垂直对齐只能对整个文本进行设置,而其他样式可以对部分文本进行设置。
水平对齐设置的属性是比较简单的,就是text-align,可以赋值left、center和right。
垂直对齐不能通过简单的CSS属性解决,因为文本高度是未知的,随着用户输入而变化,需要通过JS控制。通过getBoundingClientRect方法获取文本实际高度。以绝对定位为例,有以下伪代码:
顶部对齐时,top=文本框.y 居中对齐时,top=(文本框.height-文本.height)/2 底部对齐时,top=文本框.height-文本.height
字体、字体颜色、字体大小,在HTML里使用把文字包住,字体对应face属性,字体颜色对应color属性,字体大小对应size属性。需要注意的是,size属性只有7个值,也就是1-7,其跟实际的像素对应关系是:
size值 | 对应像素值 |
---|---|
1 | 10px |
2 | 12px |
3 | 16px |
4 | 18px |
5 | 24px |
6 | 32px |
7 | 48px |
文字粗体使用包住文字,文字下划线使用
包住文字。
在SVG里面,文字跟HTML有相似的地方,也有特别之处。在此列出几点特别之处:
SVG的文字使用
包住,如果需要分行或者分成几段,在里面使用
。
SVG的文字不会自动换行,设定的宽度也不会。
SVG文字在垂直对齐上,有多种对齐基线,默认对应于HTML的是alphabetic基线。
SVG文字颜色使用fill属性,而不是color。其他样式跟CSS一致。
一个tspan接着一个tspan,如果没有重新设定x、y,它们是横向连在一起的。
调节换行,需要计算tspan的y值。
单倍的行高接近字体大小的1.3倍。
HTML文本是一个树状结构,使用深度优先算法遍历所有结点。算法思想大致如下:
从根DIV结点开始遍历。
用一个数组存储每一行的结点。
如果是#text结点,添加到数组中。
如果是DIV结点,则一行完成,再新建一行。
如果是BR结点,结束上一行,添加新的一行,只有BR结点这个元素,再新建一行。
为了后续构建文本字体样式,可以在结点中同时保存文本的格式。例如默认fontColor是#000000,fontWeight是normal,当遇到font color=#ff0000结点时,fontColor修改为#ff0000。当再遇到b结点时,fontWeight修改为bold。
上文中已经说过,HTML里的文本是会自动换行的,但SVG里的文本不会。那么,原来在HTML里的一行,可能在SVG里需要变成多行。
算法的思想是:一个字一个字的选取,获取选择部分的位置,当选择部分的y值比第一个字的y值大(而且超过了某个阈值),则从这个字符开始换行了。
具体来说,使用document.createRange()创建一个选择区域,使用range.setStart和range.setEnd进行单字符选择。使用range.getBoundingClientRect()获取选择区域的位置。
完成了上面的工作之后,这一步相对就比较简单了。生成的步骤如下:
在text里面,为每一行新建一个tspan。
一行里面也会有很多格式,每种格式都新建一个tspan。
根据遍历时记录的字体样式,设置tspan里的style。
根据单字符选择时记录的y值,设置tspan的y值。
经过上面的步骤,就可以生成跟HTML所见一模一样的SVG文本了。本文的源代码:格式化html文本转svg文本源代码-Typescript文档类资源-CSDN文库