jabbany / CommentCoreLibrary

Javascript Live Comment (Danmaku) Engine Implementation. JS弹幕模块核心,提供从基本骨架到高级弹幕的支持。
http://jabbany.github.io/CommentCoreLibrary/demo
MIT License
1.91k stars 304 forks source link

靠谱的估算/测量 textWidth与textHeight #26

Open jabbany opened 10 years ago

jabbany commented 10 years ago

AS3的TextField提供了上述两个属性。可是在Worker里面无法准确计算渲染的参数,沙箱外通讯又困难(二者都是同步性访问的属性,鉴于使用实际情况考虑,可能异步回调结果会没什么用)。

所以目测只能估算值。

欢迎提供建议

ghost commented 10 years ago

既然要投机取巧,或许可以bake一些常见字体的字宽和kerning以便精确估算,查不到的字体再fallback到其他办法。数据不会很多,因为主要问题在西文部分,中文部分都是等宽的。

jabbany commented 10 years ago

有这个打算。还有一个设想是,在加载的时候先在本地浏览器上测一下各种常见字体的常见字符的宽度(好像西文用em法可以测),传到沙箱里面,之后的估算就能稍微准确一些了。唉,Kerning 这东西一直比较奇妙,系统和浏览器给的API都太少。

ghost commented 10 years ago

本地测宽度比起预先测宽度有太多的优势吗?同名字体的不同版本之间应该不会有很大的差异,而似乎客户端获取kerning是一件比较麻烦的事情。或许你可以通过把每一对西文试一遍来猜出kerning,但听起来像overkill。不测的话很可能抹去了前述版本差异产生的优势。

毕竟API不是针对“拿着一堆曲线和kerning自己画”这种情形设计的吧——就是因为不想关心这些才要用API。当然这种抽象到了特殊需求下的确会显得碍事。

jabbany commented 10 years ago

主要是客户端经常容易没字体,然后浏览器就随便fallback到了一个什么乱七八糟的字体上,然后就有可能BUG掉。不过其实这种边角case反正是估值也就随便了。

还有印象里,好像 SimHei 和 SimSum 对西文也都一视同仁的变成等宽?

ghost commented 10 years ago

嗯,是有这问题,之前没有想到。常见的黑体、宋体、楷体、幼圆这几个在Windows上不会缺,但是Mac和各种Linux发布版的版本也是不同的,看来还是客户端测放弃kerning比较好。这时候也只能说“至少比length * pixelheight好”了。不知道把这堆字符测一遍会消耗多少时间?

这两个字体好像的确是这样的。

m13253 commented 10 years ago

中文字体很少有 kerning,因为都是方块字,但是要考虑英文部分。大多数字体都有 kerning。kerning 误差有累积效果,极端情况下会相差几个字宽。 所以必须每一次都测。不能用 length * pixelheight

补充背景资料: 等宽字体有:NSimSun、SimHei、MS Mincho; 非等宽字体有:SimSun、MS PMincho、Heiti SC (OS X)、Hiragino Kaku Gothic ProN (OS X)、Adobe Source Han Sans (Android, Linux)、Droid Sans Fallback (Android)、WenQuanYi Micro Hei (Linux)。

我想知道浏览器在处理 iframe 里的 DOM 的时候,会不会新开一个线程呢?如果是的话,在 iframe 里测尺寸吧。

jabbany commented 10 years ago

所以必须每一次都测。不能用 length * pixelheight 。

所以说主要要看标题,目前的设计是必须估算的,因为不能每次测(架构上不允许,因为不能预判代码)。最多就是在最开始测一遍英文字母(单非等宽字母而已,自然不完整因为没法准确测量kerning),然后对中文采取长宽估算。即使不直接乘法(这个连换行都没法处理→_→),估算只能使用常量数据(虽然这些常量可以在引擎启动的时候传入)。

本issue主要是研究到底怎么估算能最大限度的靠谱,比如即使差一个字宽也完全没问题可以接受。实际上只要误差能保证最大在一个百分比内就可以close掉了。

有关局限性的问题可以参考 /docs/ 有关 scripting 的章节。主要原因来自非阻塞,简单来说参考如下代码:

var c = $.createComment("Test", {});
c.text = "Testing";
trace(c.textWidth);

实际上这段看似同步的代码,进行了两次异步操作:

var c = $.createComment("Test", {}); // 异步告知外界建立对象
c.text = "Testing"; // 异步告知外界建立的 c 对象的对应对象的文字进行变更
trace(c.textWidth); // 同步获取沙箱内 c 对象的宽度值

由于是采取的回调制度,所以没法实时的传递回测量的结果。按照绝对时间轴来看就是这样的效果

var c = $.createComment("Test", {}); // 异步告知外界建立对象
c.text = "Testing"; // 异步告知外界建立的 c 对象的对应对象的文字进行变更
trace(c.textWidth); // 同步获取沙箱内 c 对象的宽度值
// 外部收到信息,对象实际在外部建立
// 外部收到信息,对象实际被更新
// 外部更新完毕,此时可以测算文字宽度,但是读取 textWidth的代码已经运行过去了

后面三条可能会穿插在前面的代码的任何时间被执行,而且没办法让 textWidth 属性变成阻塞(Blocking)的,一是降低效率,二是实际上没办法,采取 while(!updated){/** 等待 **/}来强制阻塞会导致Worker线程占用 100% CPU 而且 还可能导致更新的 message 因为worker卡死了而无法触发worker读取,导致真的永远卡死。

在脚本读取 textWidth 属性的时候能保证的只有:知道文字都是什么,可能可以知道一套估算的字符长宽(也只能是估算的而已,因为不可能对所有出现的 fontSize 都测量(也不知道会出现什么fontSize),只能测一个或多个fontSize剩下的用函数做线性补间)

我想知道浏览器在处理 iframe 里的 DOM 的时候,会不会新开一个线程呢?如果是的话,在 iframe 里测尺寸吧。

大部分浏览器(待证实,但是根据看到的很多文档可以大概感觉出)对iframe不保证开新的进程,相当于在原有的DOM里面插入了一个新的HTML根元素。当然,本身浏览器就可能会采取新的线程来优化局部DOM渲染(Chrome Beta应该是会对不动组件的Compositing做很多优化的)。唯一能确保开新的线程的是Worker之流,但是显然Worker没法涉及任何渲染相关的东西。另外iframe与外部的通讯开销基本抵消了任何可能换来(目测本来也换不来)的效率提升,同时还要解决 Same Origin Policy。

Catofes commented 10 years ago

我也不知道我这里提供的信息到底有用否。还是测试的我以前些的preload分支,直接简单估测字款字长。

大概效果也能看得过去,然后就是一些具体的性能测试。平台是surface pro ,测试内容是demo中的script 6。 用的IE的调试工具。图片如下:

预加载(PreLoad 分支): 49

未预加载 (Master 分支): 51

大概就是加快了脚本的执行速度(也就是获得字长字高这一点被替代了),但是主要的样式计算和绘图部分还是很耗资源。

一个低压i5 大概都要消耗60%CPU,实在是略多的感觉。不知道能否有解决的方法。

jabbany commented 10 years ago

@Catofes 不不这是另一个Issue和之前不一样。textWidth 和 textHeight是 代码弹幕 的两个属性,相较普通的弹幕而言代码弹幕有更多的局限,还需要兼容代码弹幕开发人员的代码(和其中可能存在的各种BUG)。

Catofes commented 10 years ago

如果这个写出来了preload上也可以用啊。性能提升还是很明显的。 On Jul 17, 2014 4:11 PM, "Hao Qiao" 1993422qsh@gmail.com wrote:

233 理解了。不过普通代码的优化怎么处理呢? On Jul 17, 2014 4:06 PM, "Jim Chen" notifications@github.com wrote:

@Catofes https://github.com/Catofes 不不这是另一个Issue和之前不一样。textWidth 和 textHeight是 代码弹幕 的两个属性,相较普通的弹幕而言代码弹幕有更多的局限,还需要兼容代码弹幕开发人员的代码(和其中可能存在的各种BUG)。

— Reply to this email directly or view it on GitHub https://github.com/jabbany/CommentCoreLibrary/issues/26#issuecomment-49270661 .

jabbany commented 10 years ago

如果这个写出来了preload上也可以用啊。性能提升还是很明显的。

以后说不定用纯CSS就能不用计算长宽了(或者长宽计算就不太影响了)。可以玩玩 dev-animations 分支,已经抛弃总定时器了。(虽然那样暂时会导致一些微妙的BUG,不过都是能修的)

Catofes commented 10 years ago

233 理解了。不过普通代码的优化怎么处理呢? On Jul 17, 2014 4:06 PM, "Jim Chen" notifications@github.com wrote:

@Catofes https://github.com/Catofes 不不这是另一个Issue和之前不一样。textWidth 和 textHeight是 代码弹幕 的两个属性,相较普通的弹幕而言代码弹幕有更多的局限,还需要兼容代码弹幕开发人员的代码(和其中可能存在的各种BUG)。

— Reply to this email directly or view it on GitHub https://github.com/jabbany/CommentCoreLibrary/issues/26#issuecomment-49270661 .

jabbany commented 10 years ago

代码吧,非常复杂,有的写法很好,方便优化,有的写法很糟糕,我就得改底层实现让它不是那么的糟糕。

说实话半年前我还认为根本不可能搞出代码弹幕呢。。。事实证明我错了。。。

Catofes commented 10 years ago

赞 去看看 On Jul 17, 2014 4:16 PM, "Jim Chen" notifications@github.com wrote:

如果这个写出来了preload上也可以用啊。性能提升还是很明显的。

以后说不定用纯CSS就能不用计算长宽了(或者长宽计算就不太影响了)。可以玩玩 dev-animation 分支,已经抛弃总定时器了。(虽然那样暂时会导致一些微妙的BUG,不过都是能修的)

— Reply to this email directly or view it on GitHub https://github.com/jabbany/CommentCoreLibrary/issues/26#issuecomment-49271373 .

Catofes commented 10 years ago

http://www.w3schools.com/tags/canvas_measuretext.asp

貌似canvas自带一个测量文字宽度的方法,而且貌似不需要绘制就可以得到。不知道会不会有用处。

jabbany commented 5 years ago

古老的问题在2019年依然是靠hack解决 https://bl.ocks.org/tophtucker/62f93a4658387bb61e4510c37e2e97cf 继续坐等浏览器开放不需要摸DOM的文字测量API