TimothyQiu's Blog

keep it simple stupid

凤凰点阵体

做像素风/复古游戏不免要用上点阵字体,之前我用过一阵子 IPix,但它时有缺字,一些英文、数字、以及标点符号的样子和字间距给人感觉也有点奇怪。于是我就自己做了一个像素字体。

做英文字体和中文字体所需要涉及的字形不在一个数量级上,前者撑死了一百个左右,后者则至少要覆盖常用的几千个汉字,非常体力活。IPix 的字模来自于开源项目 BitmapFont,这个项目的作者收集了很多远古系统里的字模数据(感觉从老系统里提取的字模在版权方面界限还是有点模糊),可以直接生成对应的字形。

这些字模数据中,每一个字符都对应一系列的比特位。比如 16 像素宋体的「人」字在数据文件中对应 01000100010001000100010001000280028002800440044008201010200e4004,按照每 16 个比特位换行表示后就成了点阵的「人」字:

       X
       X
       X
       X
       X
       X
       X
      X X
      X X
      X X
     X   X
     X   X
    X     X
   X       X
  X         XXX
 X           X

有了这样的点阵数据,我们就可以很繁琐方便地制作各种格式的字体文件了。

我把制作好的 TrueType 字体放到了 itch.io 上,有需要的童鞋可以自取:

p.s. 其实市面上有一款非常不错的收费点阵字体 丁卯点阵体,支持 7px 和 9px 的大小。

花10分钟用Godot开发平台跳跃游戏

10 分钟的内容,录了两天。一边解说一边写代码,然后还得控制在 10 分钟左右真是太难了,深刻体会到了舌头打结的快感。

BV1Xi4y1M7Eq

p.s. 上一次 B 站投稿居然已经是在八年前了。

夹带私货?

比如前段时间某游戏中出现了不合适的内容,很多人把这件事描述为「制作团队夹带私货」。语文老师看了肯定会很生气:

只要把「私货」换成中立的「自己的观点」、把「夹带」换成中立的「加入」,就能很方便看出语言逻辑上的谬误:

改换以后,你甚至可以发现自己反对的并不是「加入自己的观点」的行为,只是单纯地反感这个观点本身。

情绪化的「夹带私货」并不是在尝试描述问题,而是在逞一时的口舌之快,带来的只能是一滩浑水。无论是「私货」「洗地」「卖惨」还是「带节奏」「泼脏水」「三观不正」「不是笨就是坏」,真的不得不佩服国人对于「骂人不带脏话」的莫名追求。

PostgreSQL 窗口函数

原来除了 Modern C++、Modern CMake,我们还有 Modern SQL,真是佩服这种文艺复兴式的 branding。

窗口函数(Window Function)就是一个例子,它由 SQL:2003 引入,可以用来筛选结果集中与当前行存在指定关联的行。相比子查询,效率更高,用起来也更方便。

例如我们有一张去年全年每日收入的表 revenues,想根据这张表查一张报表,显示每季度总收入及其环比增长,就可以用窗口函数:

created_at revenue
2019-01-01 123.45
2019-01-02 456.78
2019-01-03 420.00
... ...
SELECT
    date_part('quarter', created_at) AS quarter,
    sum(revenue) AS revenue,
    (sum(revenue) /
        lag(sum(revenue)) OVER ()) - 1 AS percentage
FROM revenues
GROUP BY 1
ORDER BY 1

得到的结果类似这样:

quarter revenue percentage
1 4530.50
2 4565.64 0.008
3 4933.01 0.080
4 4731.75 -0.041

SQL 中的 lag(sum(revenue)) OVER () 就是对窗口函数的调用了,其中 lag 函数就表示「上一条记录(季度)」。

窗口函数调用的特征是关键词 OVER

窗口的定义

「窗口定义」中的窗口其实英文叫 Frame,即窗框。「窗口函数」中的窗口则是 Window,即窗户。没什么特别含义,应该就是叫着顺口、想着形象而已。

下面的例子里,我们用 PostgreSQL 的聚合函数 array_agg 列出窗口中有哪几行。

所有记录

括号中留空表示窗口中为结果中的所有行:

SELECT
    i,
    array_agg(i) OVER ()
FROM generate_series(0, 5) AS s(i)
ORDER BY 1
i array_agg
0 0,1,2,3,4,5
1 0,1,2,3,4,5
2 0,1,2,3,4,5
3 0,1,2,3,4,5
4 0,1,2,3,4,5
5 0,1,2,3,4,5

我们可以看到每一行的对应窗口里,都包含了所有其它行。

相同分组

括号中还可以使用 PARTITION BY 指定分组的条件:

SELECT
    i,
    array_agg(i) OVER (
        PARTITION BY i % 2
    )
FROM generate_series(0, 5) AS s(i)
ORDER BY 1;
i array_agg
0 0,2,4
1 1,3,5
2 0,2,4
3 1,3,5
4 0,2,4
5 1,3,5

我们可以看到每一行的对应窗口里,都包含了与它 i % 2 值相同的行。

指定范围

可以用 ROWS BETWEEN A AND B 来指定窗口中包含哪些行,例如:

SELECT
    i,
    array_agg(i) OVER (
        ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING 
    )
FROM generate_series(0, 5) AS s(i)
ORDER BY 1;
i array_agg
0 0,1,2,3,4,5
1 1,2,3,4,5
2 2,3,4,5
3 3,4,5
4 4,5
5 5

这里其实直接读 SQL 就明白了,是要求窗口从当前行开始,一直到最后一条记录结束。

排序

窗口定义里还可以用 ORDER BY 来排序,不过一旦排序,默认的范围就变成了 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRERNT ROW(从开头到当前行),如果不是想要的范围就需要显式指定。

SELECT
    i,
    array_agg(i) OVER (
        ORDER BY i DESC
    )
FROM generate_series(0, 5) AS s(i)
ORDER BY 1;
i array_agg
0 5,4,3,2,1,0
1 5,4,3,2,1
2 5,4,3,2
3 5,4,3
4 5,4
5 5

常见窗口函数

参考

更新了博客的数学公式支持

这个 Typecho 博客搭成以来,就一直在用从这里下载到的 Markdown 插件,是对 PHP Markdown 的封装。

当年还不存在 XX-flavored Markdown 的概念,有些洁癖的我觉得坚持原生 Markdown 是最佳的选择,不够用时直接 HTML 来凑就好了。然而原生 Markdown 是没有数学公式支持的,如果用 $\TeX$ 语法,很难躲避代码被转义的命运。

单占一行的公式还可以手动用 HTML 块元素标签包裹,这样原生 Markdown 就不会对里面的内容转义了。但是行内的公式则不行,原生 Markdown 里没有任何办法禁止某些东西的转义(除了代码块,但是会把内容包裹在 <code> 里)。

所以最后我的选择是,把 $\TeX$ 代码作为代码块渲染以防止 Markdown 转义;然后强制让 MathJax 翻译 <code> 标签中的代码,显示公式。

这样做牺牲了「显示公式代码本身」的可能,不过够用了。

要解决的问题

时间一晃到了八年后的现在,翻翻以前的文章,参考链接里一个个都是 http,非常直观地给人一种「时代变了」的感觉。

如今 XX-flavored Markdown 已深入人心,在 Markdown 中插入数学公式似乎也形成了一些广为认可的写法。

是时候改一改这八年前的解决方案了。

被懒偷了去

首先想到的是 Typecho 早已原生支持 Markdown 文档,不需要再用插件实现了。要不先切换过去再搞?

然而,Typecho 原生的 Markdown 支持我非常不满意。因为它不允许内嵌任意 HTML,需要用 !!! 裹起来才行。放弃放弃。

于是看了看 PHP Markdown。虽然有 PHP Markdown Extra,但也已经是很多年前的东西,不再更新,更没有更新数学公式的可能。

所幸后来搜到了一个 PHP Markdown Extra with support for jsMath 仓库,非常开心,赶紧拿来替换了原先插件中得到 markdown.php。大体上是不错的,然而它有个 bug:行内的 \(C_{ij} = \vec{u}_{rowi} \vec{v}_{colj}\) 还是被转义了,里面的一对下划线依旧被转成了 <em>,代码被破坏,导致公式转换失败。

解决方法

这样折腾了一圈以后非常绝望,甚至还想过换个静态网站生成器把博客重新搞一下的想法。

不过想想这个工程量,不禁还是摇摇头。既然是 bug,要不然我们来 Debug 一下?

不过那代码看着看着,忽然感觉 PHP Markdown 的代码也没有想象的那么复杂。于是干脆换回官方的 PHP Markdown Extra,在那上面依葫芦画瓢改了一通,很快搞定了 $$$ 的功能。

基本上就是模仿 Markdown 中代码块的语法。

行内 $\vec{a}$ 这么写。

$$
\begin{aligned}
S &= 成块的这么写 \\
  &= 就可以了
\end{aligned}
$$

具体补丁见这个 Gist,基于 PHP Markdown Extra 1.2.8。

下载到两者后,使用 patch markdown.php math.patch 就可以打上补丁使用了。

补丁后的版本中有个 MARKDOWN_MATH_CLASS 常量,非空时会为数学公式代码所在的 <span> 或者 <div> 加上对应的 class。这样就可以在 JS 脚本中找到具有这些 class 的节点,用 MathJax / KaTeX 做精准打击,不用怕正文中的普通单词因为夹在 $ 中间而被误认为是公式了。

以上。