在Flutter App中实现Markdown解析及数学公式渲染和代码高亮

前言

因为项目需要以纯文本的形式存储简单格式的富文本信息,并能较为便利进行渲染,市面上传统的方式是转为结构化数据或HTML代码的方式进行存储,近几年基本上是Markdown一统天下.基于语法的简洁和易上手因素考量,我们同样选择了Markdown存储,前端进行解析的方案.但是,由于这个项目同时需要在Markdown中展示Latex数学公式和高亮代码,给开发工作带来了一点难度--Web端和小程序自然容易解决,但是我们的客户端是采用Flutter编写,而目前似乎没有找到符合要求的基于Flutter的Markdown解析模块.因此我决定自己找轮子魔改一个.

有人声称Markdown应当为HTML的子集,实际上如果你关注过Pug语言,你会更认同这一观点.

// markdown
# I am Headline. 
### I am Headline3.
// pug
h1 I am Headline.
h3 I am Headline3.

可以简单认为,Markdown是将HTML的部分标签的关键字变为符号的简洁版标记语言.为了拓展功能,它也支持在Markdown中直接书写HTML代码进行渲染.因此,相当一部分Markdown解析器都是将Markdown转换为HTML代码以支持各种稀奇古怪的Inline HTML.在类似RN(例如微信小程序)或浏览器环境下,解析并渲染HTML文档是比较简单的事情,这也是为什么很多前端工程大量使用Markdown进行富文本编辑的原因.但是在客户端上,问题就没有那么简单了.有些解析器采用了取巧的方法,内嵌一个Webview来渲染,性能不用提也会很尴尬,而且与原生API的交互很成问题.优势当然是可以直接复用浏览器环境下的技术栈,比如Highlight.js,Mathjax等.也有解析器自己实现了一套HTML Elements To Widget的方案,将HTML DOM手动转换为原生的控件并渲染,例如Flutter官方提供的flutter_markdown和本项目采用的markdown_widget.虽然由于工程量大,支持的标签有限,但是性能可靠,足够这个项目使用了.Flutter官方提供的Markdown渲染库特别提出Flutter Isn't a HTML Renderer,因此仅支持最基础的GitHub-Flavour Markdown,也不支持Inline-HTML.相比较而言,Markdown_widget支持的功能更完善,而要满足项目的需要,只需要定制解析出Latex语言和代码段并渲染的功能即可.

如何实现代码高亮

Markdown_widget中已经实现了代码高亮,研读下代码可以发现,是利用flutter_highlight

这个库完成的.Markdown_widget先将Markdown转换为HTML,再对HTML进行解析,当需要渲染Code元素时,利用flutter_highlight 库生成带有颜色和格式的TextSpan富文本控件,并渲染到页面上.但是测试中发现:在代码段过长的时候,包裹TextSpanSelectableText会自行进行滚动控制,从而于容器元素进行干扰,将其代码中SelectableText的scrollPhysics属性改为NeverScrollableScrollPhysics()即可.

如何实现Latex解析

  1. 提取Latex标记.
    关于创建自己的自定义标记的Custom Parser,Markdown解析器提供了被称为inlineSyntaxes的自定义功能.在这篇教程里,可以按照示例完成.这里通过正则来匹配,采用了Mathjax中的Inline Latex标记$.但是注意在正则中,$是保留字,需要进行转义.
  2. inlineSyntaxes中,我们可以自定义解析的结果,我解析为一个LatexBlockHTML Tag.实际上取决于后期渲染处理,可以解析为任意HTML DOM.这里由于最后需要通过SVG图像的方式渲染,而Flutter并不官方支持SVG图像的渲染,需要渲染为一个单独的第三方组件,因此解析为img标签可能会干扰正常的图片元素渲染,所以解析为一个自定义元素.
  3. 渲染Latex公式.
    实际上,这是一个很有挑战性的工作.大部分Flutter平台与Latex有关的方案都采用了Webview+Mathjax的方案,但是我找到了catex.就像catex说的那样,采用Webview的方案 is straightforward to implement. You only have to write a wrapper.However, we believe that utilizing web views is an overhead that makes the applications less portable and performant.因此这个项目完全使用Dart编写Parser,并采用原生组件渲染.Respect!

    然鹅...项目最新的版本号为0.01,且在Issue区我少说看到了10+个基础功能的Feature Requests,看上去距离能用还是很远.

一个取巧的办法

在不支持Mathjax的浏览器环境下,一个Fallback方案是通过后端服务器渲染为图片,以透明图片方式展现公式.在这里,我们白嫖了知乎的Latex公式渲染服务.

https://www.zhihu.com/equation?tex={Latex 代码}

比如:Latex

一个尴尬的问题在于,此API返回的SVG图片文件的颜色为currentColor,在Web环境下,这个颜色毫无疑问可以适配当前字体,但是Flutter的SVG插件并不支持,会抛出Error(实际上也有很多issue在讨论这个问题,但是目前仍未改善.)理想的解决方案应该是将这个颜色宏设置为传入插件的color属性,这里我索性Fork了份SVG插件源码自己添加了这个宏,并写死为黑色.不太优雅但是能用了.

至此,我们的Flutter应用支持了Markdown中Latex渲染.

截图:
Markdown Page

tag(s): none
show comments · back · home
Edit with markdown