上一篇文章我们提到了 content_shell 可以用来调试 blink 引擎,今天我们就来编译一份 content_shell 并魔改它用来调试网页字体查询。 编译 content_shell 主要参考了 Checking out and building Chromium on Linux Chromium 的源代码是使用它自己的 depot_tools 编译的,我们先下载一份,比如解压到了 ~/Dev/depot_tools 然后这份 depot_tools 我们需要修改一个地方,添加代理,来应对网络环境,编辑 cipd 的这个地方: if hash curl 2> /dev/null ; then curl -x socks5h://localhost:65533 "${URL}" -s --show-error -f --retry 3 --retry-delay 5 -A "${USER_AGENT}" -L -o "${CIPD_CLIENT_TMP}" 然后就可以使用 fetch 去获取代码了。 具体编译我使用了以下脚本: set_environment.sh: #!/bin/sh export PATH=$PATH:~/Dev/depot_tools export http_proxy=http://127.0.0.1:8128 export https_proxy=http://127.0.0.1:8128 export socks_proxy=socks5://127.0.0.1:65533 export all_proxy=http://127.0.0.1:8128 export NO_AUTH_BOTO_CONFIG=~/.boto prepare_build.sh: ...
深入理解 Linux Fontconfig 之四:Chromium 字体查询机制
要谈 Chromium 的字体查询机制,首先要分开 UI 字体和网页字体。UI 字体是渲染 Chromium 菜单栏中文本使用的,网页字体是渲染网页中的文本的。前者的代码主要在 ui/gfx 域下,后者是由 third_party/blink 渲染引擎处理。 UI 字体的渲染 官方文档:RenderText and Chrome UI text drawing Linux 下 Chromium 是用 harfbuzz 做文本渲染的,最重要的函数都在 render_text_harfbuzz.cc 里面,其中 RenderTextHarfbuzz::ShapeRuns 是最重要的: 它会先用 primary configured fonts from font_list() 的字体 Shape 一轮。剩下没匹配上的 Unicode 用 primary font 的 FallbackFont 再 Shape 一轮(比如你配置的字体是 Noto Sans CJK SC 跑了首轮, 第二轮就用它的 fallback font)。第三轮是用 fallback_font_list 跑,fallback_font_list 是用 GetFallbackFonts(primary_font) 生成的,最终是回到 font_fallback_linux.cc。 这里面就是我非常熟悉的 fontconfig 了,核心代码: FallbackFontList fallback_fonts; FcPattern* pattern = FcPatternCreate(); FcPatternAddString(pattern, FC_FAMILY, reinterpret_cast<const FcChar8*>(font_family.c_str())); FcConfig* config = GetGlobalFontConfig(); if (FcConfigSubstitute(config, pattern, FcMatchPattern) == FcTrue) { FcDefaultSubstitute(pattern); FcResult result; FcFontSet* fonts = FcFontSort(config, pattern, FcTrue, nullptr, &result); } 核心是 FcFontSort->FcFontSetSort。本质是基于 score 决定排序,score 主要是由 FcCompare->FcCompareValueList 生成。 ...
openSUSE 下制作 fcitx5-pinyin 的拼音词库
年前换了 am5 平台,把 Windows 7 搞坏了(B650m 主板没有 PS/2,amd 又没有 Win7 下的 USB 驱动),只能在 openSUSE 处理一些工作上的事情。我面临的问题是:输入一些奇奇怪怪的国内公司名字的时候,搜狗输入法NG版本(基于 CPIS),记忆比较快,但是它有几个硬伤解决不了,比如候选词面板上的数字上屏,有时候会跳,输入3上的是第5个字; 比如使用输入法切换到英文输入模式就切换不回来了;比如一些基本的汉字打不出来,比如覆盖的覆字。而 fcitx5 呢,记忆又太慢,我不得不一次次的去敲比如“鑫永俪”。于是我想到了一个办法,就是把我常用的公司名字制作成拼音词库。 fcitx5 的 libime 提供了 libime-pinyindict 工具用来把文本转为词库,要求的文本格式是这样的: 鑫永俪 xin'yong'li 0 于是我需要的就是把 excel 格式的客户名单分词,再标注拼音就可以了(词频默认是 0)。我的客户名单是这样的: 91110000MA0UXXXXXX 北京市鑫永俪金融服务有限责任公司 李华梅 13800138000 所以先需要一段 python 脚本去处理 excel #!/usr/bin/env python3 import xlrd import jieba import sys ''' 输出 Excel 中的中文公司名和法人名 ''' def get_chinese_words_from_excel(f): book = xlrd.open_workbook(f) sheet = book.sheet_by_index(0) res = [] for i in range(sheet.nrows): # 9 开头的是公司 if not sheet.cell_value(i, 0).startswith('9'): continue for j in range(sheet.ncols): if j not in (1, 2): continue if len(sheet.cell_value(i, j).strip()) > 0: print(sheet.cell_value(i, j).strip()) seg_list = jieba.cut(sheet.cell_value(i, j).strip()) for s in seg_list: if s not in res: print(s) res.append(s) return res if len(sys.argv) > 1: get_chinese_words_from_excel(sys.argv[1]) 这样就得到了: ...
深入理解 Linux fontconfig 第三部分
上一篇说到,我们 debug 了 FcConfigSubstitute 和 FcConfigReference,最后找到了 FcFileScanFontConfig,一番魔改后发现 <match target="scan"> 虽然应用了,但是没有影响最终 FontSet 中 “Noto Scan CJK SC” 的 charset。 我们这篇要继续 debug: FcConfigSubstitute (config, font, FcMatchScan) FcFileScanFontConfig 中的这段代码。 无踪无迹的指针 我们知道,上面的 font 是一个指针。要想前后输出有所变化,是一定要修改这个指针指向的数据的。也就是说,我们只需要去 FcConfigSubsitute里找修改指针指向数据的部分就可以了。所以我们只需要关注应用规则集时候的 case FcRuleEdit 里 case FcOpAssign 的部分就好。 通过 if (value[object]) 这个判断我们找到了 test 跟 edit 的联系,原来是用 test 去找到要修改的部分: /* different 'kind' won't be the target of edit */ if (!value[object] && kind == r->u.test->kind) value[object] = vl; 但是剩下的都是 FcConfigAdd 和 FcConfigDel 是修改 config 的,并不直接修改 Font。 ...
深入理解 Linux fontconfig 第二部分
这是深入理解 Linux fontconfig 系列的第二部分,第一部分请移步。 前面说到了 FcConfigSubstitute这个函数,它实际封装了: FcConfigSubstituteWithPat (FcConfig *config, FcPattern *p, FcPattern *p_pat, FcMatchKind kind) { 只不过第二个 p_pat 是 0。 整个 FcConfigSubstituteWithPat 函数比较长,就不全贴出来了。具体在这里。 它先是获取了 config: config = FcConfigReference (config); 然后针对 FcMatchPattern 这个 kind 先往 pattern p 里加入了 lang 和 prgname 元素。接着就是针对从 config 里取到的某个 kind 的 RuleSet 挨个应用到 pattern。 FcConfigReference 实际上只有给它传 0 才会做一大堆初始化,不然就会使用你自己的 config。前面说过 Chromium 的 config 就是它自己做的: FcConfig* config = GetGlobalFontConfig(); 实际上包括 fontconfig 自己在内都是直接传 0 让它做初始化的。初始化最重要的函数是: config = FcInitLoadConfigAndFonts (); 这个函数在 fcinit.c。实际封装了 FcInitLoadOwnConfigAndFonts(NULL) 。这个函数长这样: ...
深入理解 Linux fontconfig 第一部分
这篇文章是之前两篇关于 fontconfig 文章的后续,前文见第一篇 fontconfig 几个常见的坑,第二篇 Color Emoji in openSUSE。 偶然在网上看到 Alynx Zhou 的Fontconfig 和 Noto Color Emoji 和抗锯齿里面说看过我的文,没想到我对 fontconfig 的研究在中文圈还是排在前头的 ⌣。原来大家对后续还是有期待的。 先说一下我 2020 年写完那两篇文章干什么去了。我在 Color Emoji in openSUSE 里面不是提到过几个 .rb 代码文件嘛,有些人说找不到它们,其实它们在我的 fork 里面:marguerite/fonts-config。后来我又重写了 openSUSE 的 fonts-config, 在 marguerite/fonts-config-ng。可以自动生成 Emoji Glyph 的 Blacklist,也就是 81_emoji_blacklist_glyphs.conf。 但是呢,实际上这种方法在现实中不怎么灵。 <match target="scan"> <test name="family"> <string>Noto Sans CJK SC</string> </test> <edit name="charset" mode="assign"> <minus> <name>charset</name> <charset> <int>0x2122</int> </charset> </minus> </edit> </match> 大家可以看到我在 81_emoji_blacklist_glyphs.conf 里面已经有从 Noto Sans CJK SC 字体减除 0x2122(™)这个 charset leaf 的操作了,但是在 Chromium 浏览器中测试是这样的: ...
使用 google 的 fontmake 复活文泉驿项目
最早是 openSUSE 中文论坛上的一个帖子疑似文泉驿字体BUG把我带入字体的坑的,以前我只是停留在玩 fontconfig 的阶段。为了解决这个BUG,我简单的学习了一下 fontforge,发现带草字头和竹字头的字形简直是太多了,绝对不能手改,于是萌生了编程化地解决这个BUG的想法。接着无意中就把文泉驿的 ttc 转成了 ufo3 格式,并且在 README.md 中发愿要使用 googlefonts 的 fontmake 工具来编译文泉驿。 其实当初没有想那么多的,转换成 ufo3 格式并使用 fontmake 来编译文泉驿有什么好处呢?首先文泉驿作为一个中古字体(只能说比什么 UMing 之类的要新一点),它一直以来不是那么开源的。我理解的开源要有源代码,作为普通用户我很难去把 ttc 当成是源代码的(当然对于会 fontforge 的人来说 ttc 就约等于源代码),我理解的源代码就是能作为文本打开的。wenq.org 曾经是有一个在线编辑器的,用户们可以众筹在线编辑字形,FangQ 会在后台把用户编辑的字形搜集起来去做新的 ttc。随着时间,这套机制已经是废弃了的,也就可以说我们无法再获取到它的源代码了。把文泉驿转存成 ufo3 格式,它的每一个字形都是文本化的 glif 格式,相当于复现了源代码。有了源代码一切都好办了,未来我们还可以再制作在线编辑器,让文泉驿原来的模式继续下去。其次使用 fontmake 来编译文泉驿,相当于让文泉驿与所有现代字体比如 Noto Sans CJK 又站在了同一起跑线上。最近几年字体制作发展的很快,前几年我在 github 上是很少能看见字体项目的,但自从 Adobe 和 Google 在 github 上开源了他们的一些字体工具后呢,现在各种拼接字体大行其道了,比如更纱黑体这种,甚至偶尔还会给我推送一些做原创字体的项目。这些曾经都是与文泉驿无缘的,正如我所说的,大多数人不认为 ttc 是源代码,所以没法利用文泉驿。但现在好了,我可以用 fontmake 编译文泉驿,就表示说 fontbakery 我也可以用,ttfautohint 我也可以用,换句话说,如果我的 ufo3 格式通过了 fontbakery 的 adobefonts 或 googlefonts 的系列检查,且不论字形美丑,至少在字体质量上当年手搓出来的文泉驿是可以进化到 Noto Sans CJK 的水平的。 大多数工作是两年前做的,当时我写了一个 ufo3 的解析库,修复了草字头和竹字头的问题,并且使用 fontforge 再次生成了 ttf 字体。后来去打包 fontmake 的时候遇到了 skia-pathops 的编译问题,项目就停滞了,甚至草字头和竹字头的问题最终也没有反馈回发行版中。 ...
为 Discourse 开发一个 Onebox 插件(三)去掉 watir 依赖
我们在前两篇文章中已经基本实现了这个 engine,但是目前有一个非常恼人的依赖问题:由于 build.opensuse.org 的 package build status 是使用 javascript 加载的,而 nokogiri gem 并不支持 javascript,这就造成了我们需要使用 watir gem 去点一下网页上的 refresh 按钮,才能获取正确的 build status: browser = Watir::Browser.new(:chrome, chromeOptions: { args: ['--headless', '--window-size=1200x600', '--no-sandbox', '--disable-dev-shm-usage'] }) browser.goto(link) browser.image(id: reload_id).click 但是随之带来的依赖是非常恐怖的,我们需要一个 chrome-driver 和一个 chromium。要知道 discourse 没有装在本地的,都是装在 VPS 上面,一个 chromium 带来的空间和内存使用是十分恐怖的。于是我们通过这篇文章来教你如何干掉 watir 依赖。 我们测试用的界面是 marketo package,使用 chromium 右键查看网页源代码,我们发现 Refresh 按钮是这样的: <div accesskey='r' class='btn btn-outline-primary build-refresh float-right' onclick='updateBuildResult('')' title='Refresh Build Results'> Refresh <i class='fas fa-sync-alt' id='build-reload'></i> </div> 点击它的时候执行的是“updateBuildResult(’’)”这个 javascript 函数。对于现代网页开发而言,基本上 javascript 都写在一个文件里然后在 html 中引用的,我们翻遍了网页源代码只发现了一个引用: <script src="/assets/webui/application-ab8f02f997c1b9a61cc597f3012f2ad83e4a8e135329131cb4df389335ad4ec1.js"></script> 使用 chromium 的“检查”功能打开调试器,通过 Sources 标签页可以打开这个 js 文件,自动 pretty 一下后是这样的: ...
为 Discourse 开发一个 Onebox 插件(二)从零开始写 custom engine
上一篇为 Discourse 开发一个 Onebox 插件(一)理解 Onebox gem 里,我们知道了 Onebox 是什么样的结构。这篇我们先来写一个 onebox 的自定义引擎。 我们先准备一个脚手架 openbuildservice_onebox.rb module Onebox module Engine class OpenBuildServiceOnebox include Engine include LayoutSupport include HTML always_https matches_regexp(%r{^(https?://)?build.opensuse.org/\w+/show/(.)+$}) private end end end 我们已经知道了这两个 module Onebox和 module Engine 嵌套 class OpenBuildServiceOnebox 是为什么,为了让 Preview.new 的 ordered_engines能够找到我们这个以 Onebox 结尾的类,并调用它通过 matches_regexp 设置的 @@matcher来与真正的 URI 对比来确定唯一一个 engine。include Engine的作用是为了得到 ClassMethods里面定义的与 URI 做相等比较的方法。另外 OpenBuildServiceOnebox 这个 class 没有 initialize 方法是因为 module Engine里面已经统一实现了,我们可以直接用 @options和 @uri 这样的实例变量。 但是有一个问题是不是我忽略了?没有 to_html 这个最终把 URL 转成 html preview 的函数呀! ...
为 Discourse 开发一个 Onebox 插件(一)理解 Onebox gem
为 Discourse 开发一个 Onebox 插件(一)理解 Onebox gem 我们 openSUSE 中文论坛用的是 discourse,有一天给用户贴了一个 OBS 的链接,突然想到是不是可以让它也能像 github 一样有一个漂亮的预览小窗口 🤓 于是说干就干:discourse-openbuildservice-onebox 插件。 以下是教程,由于涉及到目前最大的 Ruby on Rails 程序 discourse,会分成几部分来讲解。第一部分我们来试着理解一下 discourse 出品的 onebox gem。 Ruby 作为一门脚本语言,所有对象的方法都可以被重写。学名叫做 Meta Programming。这是理解 onebox gem 的基础。 我们下面来看 discourse 是怎么使用 onebox gem 的,下面是 app/models/post_analyzer.rb 的 cook 函数,这个函数负责把你输入的文字转为 html 保存在 postgresql 数据库,是最基础的函数之一: def cook(raw, opts = {}) [...] result = Oneboxer.apply(cooked) do |url| @onebox_urls << url if opts[:invalidate_oneboxes] Oneboxer.invalidate(url) InlineOneboxer.invalidate(url) end onebox = Oneboxer.cached_onebox(url) @found_oneboxes = true if onebox.present? onebox end cooked = result.to_html if result.changed? cooked end 可以看到 discourse 自己还有一个 Oneboxer 的 wrapper,这里使用了 Oneboxer.apply 和 Oneboxer.cache_onebox 函数。接下来我们接着看 lib/oneboxer.rb: ...