blog

My blog at www.shimmy1996.com

git clone git://git.shimmy1996.com/blog.git

2019.org (64965B)

    1 #+HUGO_BASE_DIR: ../
    2 #+HUGO_SECTION: ./posts
    3 #+OPTIONS: author:nil
    4 
    5 * DONE 2018 in Review
    6 :PROPERTIES:
    7 :EXPORT_HUGO_CUSTOM_FRONT_MATTER: :date 2019-01-03 :slug 2018-in-review
    8 :END:
    9 
   10 ** DONE en
   11 :PROPERTIES:
   12 :EXPORT_TITLE: 2018 in Review
   13 :EXPORT_FILE_NAME: 2019-01-03-2018-in-review.en.md
   14 :END:
   15 
   16 Before anything, happy New Year!
   17 
   18 It's an interesting feeling when the time span of one year gradually becomes
   19 shorter relative to the time that has already passed in one's life. If only the
   20 actual length of one year also scales with one's age, perhaps we would feel more
   21 of the excitement instead of anxiety during the New Year count down. That being
   22 said, 2018 was a lot of fun for me, even without ray-tracing graphic cards.
   23 
   24 *** The Amazing 2018
   25 To quote my 2017 self:
   26 
   27 #+BEGIN_QUOTE
   28 If I've learned anything from my past failed plans, it would be to always
   29 underestimate my own capabilities when planning...
   30 #+END_QUOTE
   31 
   32 Yeah, it's totally just that my estimates about the amount of free time I would
   33 have was off, as can be seen from the status of my 2018 goals.
   34 
   35 - ☒ Run 1000 miles. [405/1000]
   36 - ☒ Finish a marathon.
   37 - ☒ Write 20 blog posts. [10/20]
   38 - ☒ Get the first signature for my PGP key.
   39 - ☒ Install Gentoo.
   40 
   41 Knowing that I can always change the 'publish date' of blog entries (thanks to
   42 =hugo=), I grew into the bad habit of starting an article and just then shelving
   43 it for months to come. When I finally remember that one unfinished article, I
   44 frequently dismiss the idea as not really worth elaborating. Now that I think
   45 about it, maybe this is exactly what blogs are for, providing a snapshot of
   46 myself that I can look back later, whether my future self find it silly or 'not
   47 really worth elaborating'.
   48 
   49 The number of movie theater visits I had in 2018 probably accounts for 50% of my
   50 lifetime total, and with double doses of disappointment from /Star Wars: The
   51 Last Jedi/ and /Incredibles 2/. By the way, 2018 also saw 90% of my lifetime
   52 popcorn consumption. I've never realized those can be such addicting.
   53 
   54 Although not a marathon, I did ran my first trail half marathon in May. It was
   55 the first time I've ever hit the wall while running, due to bad pacing and
   56 unpreparedness for the weather. The race started mid afternoon on a scorchingly
   57 hot day. After witnessing quite a few people stopped to walk in the first 2
   58 miles, I started off quite a bit faster than my intended pace fueled by a stupid
   59 sense of superiority, and hit the wall right at the mark of 4 miles. Fortunately
   60 the feeling faded away as I walked the next half of the race, gulping ice-cold
   61 Gatorade at every hydration point. However, the ice-cold Gatorade was another
   62 trap—temperature dropped rapidly as sun started to set and my stomach started
   63 to complain about all the chilly liquid. As the finish line appeared within 400
   64 meters of my sight, my legs were hit by the strongest cramps I've ever
   65 had. After barely making it through while being surpassed by 3 people right
   66 before finish line, I could only be happy to learn that I was still not the last
   67 one: actually, I'm even the first one in my age group (+whose size is one+). The
   68 somewhat illegitimate feeling of compliment, mixed with a bit of salt and guilt
   69 made the race a wondrous experience.
   70 
   71 *** The Spectacular 2019
   72 Since Google is deprecating Inbox in the coming March, I've lost my last excuse
   73 for clinging to Gmail. I'll try to gradually fade out my Gmail usage for my own
   74 email server.
   75 
   76 On the front of searching for best solution for blog comments, quite a few
   77 bloggers I follow have started embracing [[https://indieweb.org/][IndieWeb]] and [[https://webmention.net/][Webmention]]. In a lot of ways, Webmention was the exact thing I wanted: federated
   78 blog comments, posts, and more. Yet I'm reluctant to move further away from a
   79 static site, not to mentioning most easy-to-follow Webmention solutions I have
   80 found relies heavily on third-party services. The IndieWeb movement itself though is
   81 fairly intriguing. I've never had much use for [[https://keybase.io/][Keybase]] aside from it being a hub
   82 linking most of my online presences (decryption and encryption does not work
   83 without uploading PGP private keys, and I have no one to securely chat with),
   84 perhaps I should just replace it with =rel=me= links.
   85 
   86 Diving into C++17 was fairly enjoyable during the past year, so I'm looking into
   87 learning other new programming languages. [[https://www.rust-lang.org/][Rust]] and [[https://julialang.org/][Julia]] have been on my radar
   88 for a while, especially Rust. Having a full suite of officially supported tools
   89 makes writing Rust a smooth and deeply satisfying experience. I'll try to dive
   90 deeper into both languages and hopefully put them into some uses.
   91 
   92 As for running and blog posts, I'll try to match 2018's numbers. On top of
   93 those, I'm thinking about keeping a record of the books, music, and shows I've
   94 read/listened/watched on this blog, along with my thoughts. I actually attempted
   95 something similar during this blog's Wordpress days: I once setup a MediaWiki
   96 instance for similar purposes, but lacked the motivation to continue maintaining
   97 the entries. I'll keep it simple this time, and I should come up with a set of
   98 rating system.
   99 
  100 What should I do with the remaining 2018 goals? A separate wishlist is a pretty
  101 good idea—let's go with that. As a stretch goal, I should probably clean my
  102 desktop computer, which is stuffed with four-year-old dirt, cat hair, and dead
  103 skin cells.
  104 
  105 Here's to another spectacular 2.9e+17 radiation periods of Caesium-133!
  106 
  107 ** DONE zh
  108 :PROPERTIES:
  109 :EXPORT_TITLE: 回顾 2018
  110 :EXPORT_FILE_NAME: 2019-01-03-2018-in-review.zh.md
  111 :END:
  112 
  113 新年快乐!
  114 
  115 一年时间的相对长度比起一个人已经度过的时间总是在不停地缩短。如果一年的长度也随着人的年龄增长的话,我们在新年倒计时中的感受大概会有更多的兴奋而非焦虑和不安吧。话说回来,就算没有光追显卡, 2018 年对我来说也很有趣。
  116 
  117 *** The Amazing 2018
  118 引用 2017 年的自己:
  119 
  120 #+BEGIN_QUOTE
  121 如果我有从过去那些我制定后没能执行的计划里得到任何经验的话,那就是定计划时最好稍微低估自己的能力...
  122 #+END_QUOTE
  123 
  124 是的,从我 2018 年目标的完成状态可以看出,一定是对空闲时间的估计上出了问题。嗯,一定是这样。
  125 
  126 - ☒ 跑 1000 英里。[405/1000]
  127 - ☒ 完成一次马拉松。
  128 - ☒ 写 20 篇以上的日志。[10/20]
  129 - ☒ 获得第一个 PGP 密匙签名。
  130 - ☒ 安装 Gentoo。
  131 
  132 由于使用 =hugo= ,我可以随时更改博客条目的“发布日期”,我养成了撰写文章开头后将其搁置好几个月的坏习惯。当我最后记得那篇未完成的文章时,我经常认为这个想法不值得详细写下去。现在想想,也许这正是博客的目的,它提供了我在某个时间点的快照,使得我可以回顾过去的自己,无论将来的我会觉得这是愚蠢的还是“不值得详细写下去”的。
  133 
  134 我在 2018 年的电影院访问次数可能占我一生的总数的 50 %,但平均失望程度却因为 /星球大战:最后的绝地武士/ 和 /超人总动员 2/ 而翻倍。 顺便一提,2018 年的爆米花消费量也达到了我一生总量的 90 %。我从来没有意识到爆米花会如此令人上瘾。
  135 
  136 尽管不是全程马拉松,但我在 5 月参加了第一次山地半程马拉松比赛。这是我第一次在跑步时感到体力不支,原因是配速不佳以及对天气的准备不足。比赛当天极其炎热,且比赛从下午开始。在目睹许多人在头 2 英里内停下来走路后,由于愚蠢的虚荣心作祟,我的初始速度比我预期的速度要快得多,并在 4 英里的时候不得不因为体力不支停下。所幸,在我走完接下来的一半赛程并在每个补给点大量补充佳得乐后,疲劳的感觉消失了。但是,我没想到那些佳得乐会是另一个陷阱,太阳落山的同时带着气温迅速下降,我的胃开始因为摄入太多冰冷的液体而开始抽搐。当终点线出现在距我视线 400 米以内的地方时,我的双腿受到了我有史以来最强烈的抽筋的打击。在距离终点一步之遥的地方连续被 3 个人超越之后,我终于勉强完成了比赛,但我很高兴得知自己还不是最后一名:实际上,我甚至是我这个( +大概只有一个人完赛的+ )年龄组中的第一名。来的名不正言不顺的第一名带来的喜悦,混杂着一点点的不甘心和罪恶感,使那场比赛成为了一次奇妙的经历。
  137 
  138 *** The Spectacular 2019
  139 由于 Google 即将在 3 月淘汰 Inbox ,我失去了继续使用 Gmail 的最后借口。 我将逐渐淡化 Gmail 的使用,转向我自己的电子邮件服务器。
  140 
  141 说到博客评论的最佳解决方案,我关注的许多博客作者已经开始采用 [[https://indieweb.org/][IndieWeb]] 和 [[https://webmention.net/][Webmention]] 。在很多方面, Webmention 正是我想要的东西:它提供了分布式的博客评论、日志等等。 但是,我不愿舍弃静态站点的好处,更不用说我发现大多数易于遵循的 Webmention 解决方案都严重依赖第三方服务。 IndieWeb 运动倒是很吸引人。说起来我的 [[https://keybase.io/][Keybase]] 除了作为一个联系我不同线上身份的枢纽外并没有太大用处(在线解密和加密功能需要上传 PGP 私钥才能使用,安全消息功能对与并没有什么人可以聊天的我更是派不上用场),也许我应该用 =rel=me= 链接来完全取代它。
  142 
  143 去年学习 C++17 的体验非常令人享受,因此我正在考虑学习其他新的编程语言。我已经窥觎 [[https://www.rust-lang.org/][Rust]] 和 [[https://julialang.org/][Julia]] 一段时间了,尤其是 Rust 。拥有一整套官方支持的工具链使写 Rust 变得顺畅而愉快。我会尝试深入了解并实际使用这两种语言。
  144 
  145 至于跑步和博客日志,我将尝试维持 2018 年的数字。除此之外,我考虑在博客上记录看/听/读过的书籍,音乐和节目,以及自己的想法。过去在本站的 Wordpress 时代,我有过类似的尝试:我搭建了一个 MediaWiki 实例来记录这些,但缺乏继续维护条目的动力。这次我会用一些更轻量的解决办法,并且构思一套评分系统。
  146 
  147 我应该如何处理其余的 2018 年目标呢?单独的愿望清单是一个很好的主意。作为一个额外目标,我应该清理一下那台堆积了四年份灰尘、猫毛和死皮细胞的台式机。
  148 
  149 为接下来的 2.9e+17 个铯 133 辐射周期干杯!
  150 
  151 * DONE Installing Gentoo
  152 :PROPERTIES:
  153 :EXPORT_HUGO_CUSTOM_FRONT_MATTER: :date 2019-01-27 :slug installing-gentoo
  154 :END:
  155 
  156 ** DONE en
  157 :PROPERTIES:
  158 :EXPORT_TITLE: Installing Gentoo
  159 :EXPORT_FILE_NAME: 2019-01-27-installing-gentoo.en.md
  160 :END:
  161 
  162 I finally bite the bullet and installed Gentoo on VirtualBox (totally not motivated by the front page wishlist), thereby achieving my ultimate digital @5c3n510n (or descent according to DistroWatch).
  163 
  164 Jokes aside, the installation process is surprisingly pleasant: the [[https://wiki.gentoo.org/wiki/Handbook:Main_Page][Gentoo Handbook]] is wonderfully written, and seems to have a plan for everything that might go wrong. I like the Handbook more than ArchWiki's [[https://wiki.archlinux.org/index.php/Installation_guide][Installation Guide]] as it also details the rationale behind each step I took, which is often a fun read in its own right. I would go as far as saying the Gentoo Handbook is actually more beginner friendly, as it carefully assembles bits of information that are normally scattered all over the place, providing a great starting point for learning how to tame the operating system. Besides, Gentoo Handbook covers more than installation: it also contains other necessary setup processes to set up a usable system. I will be gradually replicating my current desktop setup to decide if a migration is worth the time.
  165 
  166 My very first encounter with GNU/Linux operating systems is Ubuntu 12.04: one of my classmates (vacuuny/A2Clef) was installing it in school's computer labs. There was a time when I would switch between various Ubuntu variants every few days. I dual booted Windows and Ubuntu for a while before switching entirely to Ubuntu in 2014. Much annoyed by the Amazon ads, I tried out Arch Linux as part of my New Year's resolution in 2015. Even with a second computer to look up instructions, it still took me quite a while to adapt to the new system. I ranted "maybe I still haven't gotten the Arch way" in my old blog, but never looked back once I got the knack of it.
  167 
  168 I still try out other distributions from time to time in VirtualBox, but never find them to offer much improvements compared with Arch beyond the setup processes, and even more so when considering the excellent documentation on ArchWiki (well now we have a contender). Once I have my desktop environment set up, the experience between distributions is not that different, but the distinctions kicks in when problems occur and I search online for troubleshooting tips. Having more up-to-date packages is another charm Arch has. More recently, the =systemd= controversy caused me to start shopping around for a new distribution to try out, not so much because of the actual security concerns, but just to see what it is like to use different init system: my time in Ubuntu was spent mostly in GUIs (=apt-get= and =nano= was probably the only command I knew for the longest time) without knowing about init systems and Arch was already using =systemd= when I switched. Aside from Gentoo, the candidates include Void Linux and the BSDs. Void Linux was easy to set up with its installer wizard, yet I didn't feel compelled to move to it. Let's see if Gentoo would change my mind.
  169 
  170 ** DONE zh
  171 :PROPERTIES:
  172 :EXPORT_TITLE: 安装 Gentoo
  173 :EXPORT_FILE_NAME: 2019-01-27-installing-gentoo.zh.md
  174 :END:
  175 
  176 我终于下定决心在 VirtualBox 上尝试了安装 Gentoo(绝对不是因为首页的愿望清单),从而实现了我的终极数字飞升(按照 DistroWatch 排名的话应该是下凡)。
  177 
  178 话说回来,安装过程十分顺畅: [[https://wiki.gentoo.org/wiki/Handbook:Main_Page][Gentoo 手册]] 编写的很出色,似乎预料到了所有可能出错的地方并准备好了后备反感。与 ArchWiki 的 [[https://wiki.archlinux.org/index.php/Installation_guide][安装指南]] 相比,我更喜欢该手册,因为手册还详细介绍了我采取的每一步背后的原因。我甚至觉得,Gentoo 手册实际上是对初学者更友好的,因为它精心汇总了了通常散布在各处的信息,为学习如何驯服你的操作系统提供了一个很好的起点。 此外, Gentoo 手册不仅涉及安装,还包含其他设置一个可用的系统的必要步骤。我将逐步复制我当前的台式机设置,以决定是否值得进行迁移。
  179 
  180 我第一次接触 GNU/Linux 操作系统是 Ubuntu 12.04 :我的一位同学( vacuuny/A2Clef )在学校的计算机实验室中安装了它。曾经有一段时间我每隔几天会在各种 Ubuntu 版本之间进行切换。在同时使用 Windows 和 Ubuntu 一段时间后,我在 2014 年完全切换到 Ubuntu 。由于 Ubuntu 上亚马逊广告的猖獗,我尝试了 Arch Linux ,作为 2015 年新年计划的一部分。即使有第二台计算机来查找说明,我也花了相当长的时间来适应新系统。我在旧博客中还曾写到“大概我还没有 get 到 the Arch way ”。但是完全熟悉 Arch Linux 后,我就再也没有回头。
  181 
  182 我仍然会不时在 VirtualBox 中尝试其他发行版,但是除了设置过程之外,我从未发现它们与 Arch 相比能够提供多少改进,更不用提 ArchWiki 上极为出色的文档(现在我们有一个竞争者了)。设置好桌面环境后,发行版之间的体验并没有太大区别,但是当我遇到问题并在线搜索如何故障排除时,区别就开始出现了。拥有更多、更新的软件包是 Arch 的另一项魅力。最近,关于 =systemd= 的争议使我开始四处寻找新发行版以进行试用。与其说是因为实际的安全问题,不如说我只是想试试使用不同的初始化系统:在 Ubuntu 下我主要使用图形界面( =apt-get= 和 =nano= 可能是我很长一段时间里知道的唯二命令)所以并没有什么直观感受,而在我换用 Arch 时, Arch 已经在使用 =systemd= 了。除了 Gentoo ,候选对象还包括 Void Linux 和 BSD 。 Void Linux 有易于使用的安装向导,但我并不感到它有特别吸引我的地方。看看 Gentoo 是否会改变我的想法。
  183 
  184 * DONE Trackpad and Swollen Batteries
  185 :PROPERTIES:
  186 :EXPORT_HUGO_CUSTOM_FRONT_MATTER: :date 2019-02-28 :slug trackpad-and-swollen-batteries
  187 :END:
  188 
  189 ** DONE en
  190 :PROPERTIES:
  191 :EXPORT_TITLE: Trackpad and Swollen Batteries
  192 :EXPORT_FILE_NAME: 2019-02-28-trackpad-and-swollen-batteries.en.md
  193 :END:
  194 
  195 For the last few weeks, the right click on my Dell XPS 13's trackpad is getting less responsive: the entire right half of the trackpad sunk around 2mm beneath the palm rest, making clicks hard to register. At first I dismissed it as normal wear, but it turned out that the swollen batteries lifted the left half of the trackpad to such a degree that the trackpad warped. I immediately ordered an OEM replacement (Dell JD25G) swapped out the swollen batteries. XPS 13 (9343) was a breeze to service. The screws that hold the bottom panel (a quite hefty hunk of aluminum) in place are all clearly visible and the component layout allows battery to be swapped with minimal disassembly. I also swapped out the WLAN card (Dell DW1560) for an Intel AC9560, whose drivers are in the mainline Linux kernel.
  196 
  197 The trackpad felt normal after the battery swap, of course. However, the fact that average laptop battery starts to degrade around 18 months surprised me quite a bit. Mine lasting nearly four years is probably quite decent. Newer laptops uses prismatic cells (those slab shaped batteries also found in phones) instead of cylindrical ones, as can be found in my first laptop, Dell Vostro 3750. Roughly speaking, prismatic cells trade size for lifespan by emitting external casing and gas vents found on cylindrical cells. The battery swell is caused by gas build up, which might have been avoided in cylindrical cells with vents. It's interesting that (easily) removable batteries have largely disappeared in consumer laptops - even the large desktop replacements (to be fair, those spend most of the time plugged in anyways). The only consumer electronics that still almost always have removable batteries I can think of are cameras.
  198 
  199 After the incident, I started to browse current laptops on the market as the new quad/hex core laptop CPUs are quite tempting an upgrade (my XPS 13 has a i5-5200U). I was not a huge fan of the latest XPS 13 (9380) mostly because of the port selection: I just don't have any USB Type-C devices, so the 1 Type-C plus 2 Type-A combination found on XPS 13 (9360) is superior in my opinion. Besides ports, the onboard WLAN card and removal of full-sized SD card slot also make the latest model less appealing.
  200 
  201 I also came across the Let's Note line of laptops from Panasonic, which are reliable, lightweight business laptops that often comes with removable batteries and a wide spectrum of ports. If only they weren't so prohibitively expansive, doesn't have those ugly "Wheel Pads", and come with US keyboard layout, they are quite the ideal laptops. I like the aesthetics of 2016 CF-MX5 series the most, but that won't make much of an upgrade.
  202 
  203 More realistic choices include HP's EliteBook, Lenovo's ThinkPad T series, and Dell's Latitude/Precision lines. I vetoed EliteBook because all of them had a huge glaring proprietary docking port that I might never use. Latitude 5491 seem to have cooling issues due to the 45W TDP CPUs, while Latitude 7390 and 7490 both seem quite decent, with options to disable Intel ME and official Linux support. ThinkPad T480 pretty much ticks everything on my list, but it seems that the next iteration T490 will no longer have the bridge battery system and only one SODIMM slot, pretty much like T480s.
  204 
  205 Hunting for second-handed machines is also an option, but it defeats the purpose of the upgrade since my primary motivation is the new quad core CPUs. Some may argue our laptops are overpowered already, and indeed my XPS 13 still feels pretty snappy though, so I'm not in urgent need for an upgrade. However, I did come up with a list of what I want in a laptop in case the ideal candidate shows up someday.
  206 - Good Linux driver support.
  207 - Below 15 inch in size and low travel weight. XPS 13 converted me from a DTR enthusiast to an Ultrabook follower: it does feel nice to be able carry a laptop all day without feeling it.
  208 - Non-Nvidia graphics. Both AMD and Intel has better open source driver support and I use my desktop for tasks heavily reliant on GPU.
  209 - Reasonable battery life (6 hours or more) and removable battery.
  210 - Not-too-radical port selections, not until all mouses and flash drives default to USB Type-C at least.
  211 - Standard components and easy to upgrade, i.e. SODIMM slot for memory, PCIe for WLAN card/SSD.
  212 - A nice trackpad. I'm rather insensitive to quality of laptop keyboards so anything marginally decent would do. It would be really cool to have an ErgoDox laptop though.
  213 - Not-super-high-resolution display. I'm not too picky about screens either, but 4K feels like an utter overkill for laptops this size that provides marginal improvements while draining more power. I've always used 16:9 displays, but I'm open to trying out different ones.
  214 
  215 ** DONE zh
  216 :PROPERTIES:
  217 :EXPORT_TITLE: 触摸板和膨胀的电池
  218 :EXPORT_FILE_NAME: 2019-02-28-trackpad-and-swollen-batteries.zh.md
  219 :END:
  220 
  221 在过去的几周中,我的 Dell XPS 13 触控板右键变得越来越难以使用:整个触控板的右半部分沉入了掌托下方约 2mm 的位置,使右击难以被记录。最初我认为是正常磨损,但事实上是膨胀的电池将触控板的左半部分拱起,导致触摸板变形。 我立即订购了 OEM 部件( Dell JD25G ),更换了膨胀的电池。XPS 13 ( 9343 )还算易于维修。固定底板(相当大的一块铝块)的螺钉都清晰可见,并且组件布局允许在打开底板后直接更换电池。 我还将无线网卡( Dell DW1560 )换成了 Intel AC9560,其驱动程序在主线 Linux 内核中,方便不少。
  222 
  223 更换电池后,触控板恢复了正常。但是,笔记本电脑电池平均在 18 个月左右开始性能下降这一事实仍使我感到非常惊讶。 我这块持续了近四年的电池已经算不错了。较新的笔记本电脑大多使用方形电芯(它们也被用在智能手机中的平板状电池里),而非我的第一台笔记本电脑 Dell Vostro 3750 中搭载那种的圆柱形电芯。电池膨胀一般是由气体积聚引起的,这在带有通风孔的圆柱形电芯中可以避免。有趣的是,可拆卸电池在消费类笔记本电脑中已基本消失 - 即使是大型的台式机替代品(虽然这些笔记本电脑大部分时间都插在电源上)。我能想到的唯一仍然几乎总是具有可拆卸电池的消费电子产品是相机。
  224 
  225 这一事件之后,我开始浏览当前市面上的笔记本电脑,因为带有新的四、六核心 CPU 的笔记本电脑是极具诱惑力的升级(我的 XPS 13 配置了 i5-5200U )。我不怎么喜欢最新版本的 XPS 13(9380),主要是因为端口选择:我目前没有任何 USB Type-C 设备,因此我认为 XPS 13 (9360)上的一个 Type-C 加两个 Type-A 的组合更加优越。除了端口之外,板载无线网卡和全尺寸 SD 卡插槽的移除也使最新型号的吸引力降低。
  226 
  227 我还查看了 Panasonic 的 Let's Note 系列笔记本电脑。这些笔记本电脑是可靠而轻便的商务笔记本电脑,并通常配备可拆卸电池和各种端口。如果要是它们没有那么夸张的价格、没有那些丑陋的“ Wheel Pad ”、并配备美式键盘布局,那它们就是理想的笔记本电脑。我最喜欢 2016 年推出的 CF-MX5 系列的外观,但这一系列的性能比起我目前的配置并不会有多大提升。
  228 
  229 更为现实的选择包括惠普的 EliteBook ,联想的 ThinkPad T 系列和戴尔的 Latitude 、 Precision 系列。 我否决了 EliteBook ,因为系列所有机器上都有一个巨大的、我可能永远不会使用的专用坞站端口。由于采用了设计功耗 45W 的 CPU, Latitude 5491 似乎有散热问题,但 Latitude 7390 和 7490 看起来都不错,不仅可以禁用 Intel ME 还带有官方 Linux 支持。 ThinkPad T480 几乎满足了我的所有要求,但下一次代的 T490 似乎将不再具有桥接电池系统并仅保留一个 SODIMM 插槽,与 T480s 差不多。
  230 
  231 寻找二手机器也是一种选择,但是由于我的主要动机是购买新的四核 CPU ,所以这达不到升级的目的。 有的人认为我们的笔记本电脑的处理性能早已超过我们的日常需求,况且我的 XPS 13 使用时确实感觉不慢,因此我并不急需进行升级。不过我还是列了一下我对理想中笔记本电脑的需求,以备万一。
  232 - 良好的 Linux 驱动程序支持。
  233 - 尺寸小于 15 英寸,旅行重量轻。 XPS 13 将我从 DTR 爱好者转变为 Ultrabook 追随者:能够整天携带笔记本电脑而几乎感觉不到重量非常棒。
  234 - 非 Nvidia 显卡。 AMD 和 Intel 都具有更好的开源驱动程序支持,而且高度依赖 GPU 的任务还有台式机可以分担。
  235 - 合理的电池寿命( 6 小时或更长时间)和可拆卸电池。
  236 - 不过于激进的接口选择,至少直到所有鼠标和闪存驱动器默认接口都为 USB C 型的那一天。
  237 - 使用标准组件,易于升级,例如内存使用 SODIMM 插槽、无线网卡和硬盘使用 PCIe 等等。
  238 - 不错的触控板。我对笔记本电脑键盘的质量不太敏感,任何质量尚可的键盘我都能接受。不过要是有搭载 ErgoDox 的笔记本电脑就好了。
  239 - 非超高分辨率的显示屏。我对屏幕也不是很挑剔,但是对于这种大小的笔记本电脑来说,采用 4K 分辨率完全是高射炮打蚊子。我通常使用 16:9 比例的屏幕,但不反对尝试其他分辨率比例。
  240 
  241 * DONE enumerate() with C++
  242 :PROPERTIES:
  243 :EXPORT_HUGO_CUSTOM_FRONT_MATTER: :date 2019-04-27 :slug enumerate-with-c-plus-plus
  244 :END:
  245 
  246 ** DONE en
  247 :PROPERTIES:
  248 :EXPORT_TITLE: enumerate() with C++
  249 :EXPORT_FILE_NAME: 2019-04-27-enumerate-with-c-plus-plus.en.md
  250 :END:
  251 
  252 Quite a few programming languages provide ways to iterate through a container while keeping count of the number of steps taken, such as =enumerate()= in Python:
  253 #+BEGIN_SRC python
  254   for i, elem in enumerate(v):
  255       print(i, elem)
  256 #+END_SRC
  257 and =enumerate()= under =std::iter::Iterator= trait in Rust:
  258 #+BEGIN_SRC rust
  259   for (i, elem) in v.iter().enumerate() {
  260       println!("{}, {}", i, elem);
  261   }
  262 #+END_SRC
  263 This is just a quick note about how to do similar things in C++17 and later without declaring extra variables out of the for loop's scope.
  264 
  265 The first way is to use a mutable lambda:
  266 #+BEGIN_SRC c++
  267   std::for_each(v.begin(), v.end(),
  268                 [i = 0](auto elem) mutable {
  269                     std::cout << i << ", " << elem << std::endl;
  270                     ++i;
  271                 });
  272 #+END_SRC
  273 This could be used with all the algorithms that guarantees in-order application of the lambda, but I don't like the dangling =++i= that could get mixed up with other logic.
  274 
  275 The second way utilizes structured binding in for loops:
  276 #+BEGIN_SRC c++
  277   for (auto [i, elem_it] = std::tuple{0, v.begin()}; elem_it != v.end();
  278        ++i, ++elem_it) {
  279       std::cout << i << ", " << *elem_it << std::endl;
  280   }
  281 #+END_SRC
  282 We have to throw in =std::tuple= as otherwise compiler would try to create a =std::initializer_list=, which does not allow heterogeneous contents.
  283 
  284 The third least fancy method is to just calculate the distance every time:
  285 #+BEGIN_SRC c++
  286   for (auto elem_it = v.begin(); elem_it != v.end(); ++elem_it) {
  287       auto i = std::distance(v.begin(), elem_it);
  288       std::cout << i << ", " << *elem_it << std::endl;
  289   }
  290 #+END_SRC
  291 Since we have to copy paste the starting point twice, I like other counter based approaches better.
  292 
  293 In C++20, we have the ability to add an init-statement in ranged-based for loops, so we can write something like
  294 #+BEGIN_SRC c++
  295   for (auto i = 0; auto elem : v) {
  296       std::cout << i << ", " << elem << std::endl;
  297       i++;
  298   }
  299 #+END_SRC
  300 Meh, not that impressive. The new =<ranges>= library provides a more appealing way to achieve this:
  301 #+BEGIN_SRC c++
  302   for (auto [i, elem] : v | std::views::transform(
  303            [i = 0](auto elem) mutable { return std::tuple{i++, elem}; })) {
  304       std::cout << i << ", " << elem << std::endl;
  305   }
  306 #+END_SRC
  307 
  308 I like the structured binding method and the =<ranges>= based method the most. It would be even better though if we can get a =std::views::enumerate= to solve this problem once and for all.
  309 
  310 ** DONE zh
  311 :PROPERTIES:
  312 :EXPORT_TITLE: 用 C++ 来 enumerate()
  313 :EXPORT_FILE_NAME: 2019-04-27-enumerate-with-c-plus-plus.zh.md
  314 :END:
  315 
  316 不少编程语言都提供了在迭代容器的同时记录步数的方法,例如 Python 的 =enumerate()= :
  317 #+BEGIN_SRC python
  318   for i, elem in enumerate(v):
  319       print(i, elem)
  320 #+END_SRC
  321 以及 Rust 里 =std::iter::Iterator= 特性下的 =enumerate()= :
  322 #+BEGIN_SRC rust
  323   for (i, elem) in v.iter().enumerate() {
  324       println!("{}, {}", i, elem);
  325   }
  326 #+END_SRC
  327 这里记录了如何在 C++17 或更新的标准里尽量简洁地实现类似功能的办法。
  328 
  329 
  330 第一种方法是使用一个可变的 lambda :
  331 #+BEGIN_SRC c++
  332   std::for_each(v.begin(), v.end(),
  333                 [i = 0](auto elem) mutable {
  334                     std::cout << i << ", " << elem << std::endl;
  335                     ++i;
  336                 });
  337 #+END_SRC
  338 这个方法使用于所有能够保证 lambda 有序执行的算法,但是我并不喜欢末尾很可能被混入其他逻辑的 =++i= 。
  339 
  340 第二种方法是在 for 循环中使用结构化绑定:
  341 #+BEGIN_SRC c++
  342   for (auto [i, elem_it] = std::tuple{0, v.begin()}; elem_it != v.end();
  343        ++i, ++elem_it) {
  344       std::cout << i << ", " << *elem_it << std::endl;
  345   }
  346 #+END_SRC
  347 为了不让编译器默认创建只允许同种内容的 =std::initializer_list= ,我们必须加上 =std::tuple= 。
  348 
  349 第三种最朴实无华的办法是在循环的每一步计算指针距离:
  350 #+BEGIN_SRC c++
  351   for (auto elem_it = v.begin(); elem_it != v.end(); ++elem_it) {
  352       auto i = std::distance(v.begin(), elem_it);
  353       std::cout << i << ", " << *elem_it << std::endl;
  354   }
  355 #+END_SRC
  356 由于这种方法需要我们在两个地方指定初始指针,我更喜欢之前提到的基于计数器的方法。
  357 
  358 在 C++20 中,我们可以在基于范围的 for 循环中加入初始化语句:
  359 #+BEGIN_SRC c++
  360   for (auto i = 0; auto elem : v) {
  361       std::cout << i << ", " << elem << std::endl;
  362       i++;
  363   }
  364 #+END_SRC
  365 新加入的 =<ranges>= 库则提供了一种更加吸引人的实现方法:
  366 #+BEGIN_SRC c++
  367   for (auto [i, elem] : v | std::views::transform(
  368            [i = 0](auto elem) mutable { return std::tuple{i++, elem}; })) {
  369       std::cout << i << ", " << elem << std::endl;
  370   }
  371 #+END_SRC
  372 
  373 我最喜欢基于结构化绑定和 =<ranges>= 库的方法。当然如果要是有 =std::views::enumerate= 来一劳永逸地解决这个问题就最好不过了。
  374 
  375 * DONE Hello Darkness, My Old Friend
  376 :PROPERTIES:
  377 :EXPORT_HUGO_CUSTOM_FRONT_MATTER: :date 2019-09-15 :slug hello-darkness-my-old-friend
  378 :END:
  379 
  380 ** DONE en
  381 :PROPERTIES:
  382 :EXPORT_TITLE: Hello Darkness, My Old Friend
  383 :EXPORT_FILE_NAME: 2019-09-15-hello-darkness-my-old-friend.en.md
  384 :END:
  385 
  386 With system wide dark modes becoming commonplace, I took the effort to tweak the color scheme of my blog and added a dark mode specific one using =prefers-color-scheme= in CSS. I also toyed around the idea of adding a user toggle using JavaScript per instructions [[https://flaviocopes.com/dark-mode/][here]], but ultimately decided against it because of my (totally unjustified and groundless) distaste towards the language.
  387 
  388 | Color Usage     | Light Theme | Dark Theme |
  389 | Accent          | =#700000=   | =#8fffff=  |
  390 | Background      | =#f7f3e3=   | =#080c1c=  |
  391 | Text            | =#2e2d2b=   | =#d1d2d4=  |
  392 | Code Background | =#e3dacb=   | =#1c2534=  |
  393 | Border 1        | =#e7e3d3=   | =#181c2c=  |
  394 | Border 2        | =#d7d3c3=   | =#282c3c=  |
  395 
  396 Writing CSS is a such tiring endeavor, but on the bright side, picking colors is a surprisingly relaxing activity. The light mode color scheme now has reduced contrast, and I updated the isso style sheets with matching colors. Yes, I only inverted the colors in dark mode and did not reduce the font weights because of the peculiar way in which human vision work. Part of me already screams heresy when I look at the color codes formed by three numbers that seem to have no connection whatsoever—they are like dissonant chords that cause itches in brain—so I *need* them to at least sum up to a nice number.
  397 
  398 Wissen ist Nacht!
  399 
  400 ** DONE zh
  401 :PROPERTIES:
  402 :EXPORT_TITLE: 你好黑暗,我的老朋友
  403 :EXPORT_FILE_NAME: 2019-09-15-hello-darkness-my-old-friend.zh.md
  404 :END:
  405 
  406 由于越来越多的设备和软件都开始支持暗色模式,我调整了博客的配色并加入了用 CSS 的 =prefers-color-scheme= 实现的暗色主题。我也考虑了加入用户切换的功能(参考 [[https://flaviocopes.com/dark-mode/][这里]] 的教程),但是出于我对 JavaScript (毫无来由)的反感,我最后否定了这个主意。
  407 
  408 | 颜色用途 | 亮色主题  | 暗色主题  |
  409 | 强调     | =#700000= | =#8fffff= |
  410 | 背景     | =#f7f3e3= | =#080c1c= |
  411 | 文字     | =#2e2d2b= | =#d1d2d4= |
  412 | 代码背景 | =#e3dacb= | =#1c2534= |
  413 | 边框1    | =#e7e3d3= | =#181c2c= |
  414 | 边框2    | =#d7d3c3= | =#282c3c= |
  415 
  416 写 CSS 真是累人,不过好在挑选配色是一件挺让人放松的事。新的亮色主题有更低的对比度,我也更新了 isso 的样式表。是的,我的暗色主题只不过是亮色主题的反色版本,并没有降低文字粗细程度以照顾人类视力的某种古怪特性和其他细微的颜色调整。当我看到由三个似乎没有任何联系的数字形成的颜色代码时,我潜意识已经在大呼异端——它们就像不协和和弦一样让人头皮发麻——所以我 *需要* 它们至少加起来是一个不那么差劲的数。
  417 
  418 知识就是黑夜!
  419 
  420 * DONE Fun with Fonts on the Web
  421 :PROPERTIES:
  422 :EXPORT_DATE: 2019-12-01
  423 :EXPORT_HUGO_SLUG: fun-with-fonts-on-the-web
  424 :END:
  425 
  426 ** DONE en
  427 :PROPERTIES:
  428 :EXPORT_FILE_NAME: 2019-12-01-fun-with-fonts-on-the-web.en.md
  429 :EXPORT_TITLE: Fun with Fonts on the Web
  430 :END:
  431 
  432 A more accurate version of the title probably should be "Fun with Fonts in Web Browsers", but oh well, it sounds cooler that way. Text rendering is [[https://gankra.github.io/blah/text-hates-you/][hard]], and it certainly doesn't help that we have a plethora of different writing systems (blame the Tower of Babel for that, I guess) which cannot be elegantly fitted into a uniform system. Running a bilingual blog doubles the trouble in font picking, and here's a compilation of the various problems I encountered.
  433 
  434 *** Space Invaders
  435 Most browsers join consecutive lines of text in HTML to a single one with an added space in between, so
  436 #+BEGIN_SRC html
  437   <html>Line one and
  438   line two.</html>
  439 #+END_SRC
  440 renders to
  441 #+BEGIN_EXAMPLE
  442 Line one and line two.
  443 #+END_EXAMPLE
  444 
  445 Such a simplistic rule doesn't work for CJK languages where no separators is used between words. The solution is to specify the =lang= attribute for the page (or any specific element on the page) like so:
  446 #+BEGIN_SRC html
  447   <html lang="zh">第一行和
  448   第二行。</html>
  449 #+END_SRC
  450 If your browser is smart enough (like Firefox), it will join the lines correctly. All the Blink based browsers, however, still stubbornly shove in the extra space, so it looks like I will be stuck in unwrapped source files like a barbarian for a bit longer. While not a cure-all solution, specifying the =lang= attribute still have the added benefit of enabling language-specific CSS rules, which comes in handy later.
  451 
  452 *** Return of the Quotation Marks
  453 As mentioned in a [[https://www.shimmy1996.com/en/posts/2018-06-24-fun-with-fonts-in-emacs/][previous post]], CJK fonts would render quotation marks as full-width characters, different from Latin fonts. This won't be a problem as long as a web page doesn't try to mix-and-match fonts: just use language specific font-stack.
  454 #+BEGIN_SRC css
  455   body:lang(en) {
  456       font-family: "Oxygen Sans", sans-serif;
  457   }
  458 
  459   body:lang(zh) {
  460       font-family: "Noto Sans SC", sans-serif;
  461   }
  462 #+END_SRC
  463 Coupled with matching =lang= attributes, the story would have ended here. Firefox even allows you to specify default fonts on a per language basis, so you can actually get away with just the fallback values, like =sans-serif= or =serif=, and not even bother writing language specific CSS.
  464 
  465 However, what if I want to use Oxygen Sans for Latin characters, Noto Sans SC for CJK characters? While seemingly an sensible solution, specifying font stack like so,
  466 #+BEGIN_SRC css
  467   body:lang(zh) {
  468       font-family: "Oxygen Sans", "Noto Sans SC", sans-serif;
  469   }
  470 #+END_SRC
  471 would cause the quotation marks to be rendered using Oxygen Sans, which displays them as half-width characters. The solution I found is to declare an override font with a specified =unicode-range= that covers the quotation marks,
  472 #+BEGIN_SRC css
  473   @font-face {
  474       font-family: "Noto Sans SC Override";
  475       unicode-range: U+2018-2019, U+201C-201D;
  476       src: local("NotoSansCJKsc-Regular");
  477   }
  478 #+END_SRC
  479 and revise the font stack as
  480 #+BEGIN_SRC css
  481   body:lang(zh) {
  482       font-family: "Noto Sans SC Override", "Oxygen Sans", "Noto Sans SC", sans-serif;
  483   }
  484 #+END_SRC
  485 Now we can enjoy the quotation marks in their full-width glory!
  486 
  487 *** Font Ninja
  488 Font files are quite significant in size, and even more so for CJK ones: the Noto Sans SC font just mentioned is [[https://github.com/googlefonts/noto-cjk/blob/master/NotoSansSC-Regular.otf][over 8MB]] in size. No matter how determined I am to serve everything from my own server, this seems like an utter overkill considering the average HTML file size on my site is probably closer to 8KB. How does all the web font services handle this then?
  489 
  490 Most web font services work by adding a bunch of [[https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face][=@font-face=]] definitions into a website's style sheet, which pulls font files from dedicated servers. To reduce the size of files been served, Google Fonts slice the font file into smaller chunks, and declare corresponding =unicode-range= for each chunk under =@font-face= blocks (this is exactly how they handle [[https://fonts.googleapis.com/css?family=Noto+Sans+SC][CJK fonts]]). They also compress the font files into WOFF2, further reducing file size. On the other hand, [[https://fonts.adobe.com/][Adobe Fonts]] (previously known as Typekit) seem to have some JavaScript wizardry that dynamically determines which glyphs to load from a font file.
  491 
  492 Combining best of both worlds, and thanks to the fact that this is a static site, it is easy to gather all the used characters and serve a font file containing just that. The tools of choice here would be pyftsubset (available as a component of [[https://pypi.org/project/fonttools/][fonttools]]) and GNU AWK. Compressing font files into WOFF2 also requires Brotli, a compression library. Under Arch Linux, the required packages are [[https://www.archlinux.org/packages/community/any/python-fonttools/][python-fonttools]], [[https://www.archlinux.org/packages/core/x86_64/gawk/][gawk]], [[https://www.archlinux.org/packages/community/x86_64/brotli/][brotli]], and [[https://www.archlinux.org/packages/community/x86_64/python-brotli/][python-brotli]].
  493 
  494 Here's a shell one-liner to collect all the used glyphs from generated HTML files:
  495 #+BEGIN_SRC sh
  496   find . -type f -name "*.html" -printf "%h/%f " | xargs -l awk 'BEGIN{FS="";ORS=""} {for(i=1;i<=NF;i++){chars[$(i)]=$(i);}} END{for(c in chars){print c;} }' > glyphs.txt
  497 #+END_SRC
  498 You may need to =export LANG=en_US.UTF-8= (or any other UTF-8 locale) for certain glyphs to be handled correctly. With the list of glyphs, we can extract the useful part of font files and compress them:
  499 #+BEGIN_SRC sh
  500   pyftsubset NotoSansSC-Regular.otf --text-file=glyphs.txt --flavor=woff2 --output-file=NotoSansSC-Regular.woff2
  501 #+END_SRC
  502 Specifying =--no-hinting= and =--desubroutinize= can further reduce size of generated file at the cost of some aesthetic fine-tuning. A similar technique can be used to shrink down Latin fonts to include only ASCII characters (or keep the extended ASCII range with =U+0000-00FF=):
  503 #+BEGIN_SRC sh
  504   pyftsubset Oxygen-Sans.ttf --unicodes="U+0000-007F" --flavor=woff2 --output-file=Oxygen-Sans.woff2
  505 #+END_SRC
  506 Once this is done, available glyphs can be checked using most font manager software, or this [[http://torinak.com/font/lsfont.html][online checker]] (no support for WOFF2 though, but you can convert into other formats first, such as WOFF).
  507 
  508 I also played around the idea of actually dividing the glyphs into further chunks by popularity, so here's another one liner to get list of glyphs sorted by number of appearances
  509 #+BEGIN_SRC sh
  510   find . -type f -name "*.html" -printf "%h/%f " | xargs -l awk 'BEGIN{FS=""} {for(i=1;i<=NF;i++){chars[$(i)]++;}} END{for(c in chars){printf "%06d %s\n", chars[c], c;}}' | sort -r > glyph-by-freq.txt
  511 #+END_SRC
  512 It turns out my blog has around 1000 different Chinese characters, with roughly 400 of them appearing more than 10 times. Since the file sizes I get from directly a single subsetting is already good enough, I didn't bother proceeding with another split.
  513 
  514 *** For Your Browsers Only
  515 With all the tricks in my bag, I was able to cut down the combined font file size to around 250KB, still magnitudes above that of an HTML file though. While it is nice to see my site appearing the same across all devices and screens, I feel the benefit is out of proportion compared to the 100-fold increase in page size.
  516 
  517 Maybe it is just not worth it to force the choice of fonts. In case you want to see my site as I would like to see it, here are my go-to fonts:
  518 - Proportional Latin font: [[https://github.com/KDE/oxygen-fonts][Oxygen Sans]]. Note that the KDE version has nuanced differences from the [[https://fonts.google.com/specimen/Oxygen][Google Fonts version]], and I like the KDE version much more.
  519 - Proportional CJK font: [[https://www.google.com/get/noto/help/cjk/][Noto Sans CJK]].
  520 - Monospace font: [[https://typeof.net/Iosevka/][Iosevka]], the ss09 variant, to be more exact.
  521 
  522 ** DONE zh
  523 :PROPERTIES:
  524 :EXPORT_FILE_NAME: 2019-12-01-fun-with-fonts-on-the-web.zh.md
  525 :EXPORT_TITLE: 字体配置万维网篇
  526 :END:
  527 
  528 用《字体配置浏览器篇》作为标题或许更为准确,不过现在的标题听起来更吸引人一些。渲染文本 [[https://gankra.github.io/blah/text-hates-you/][不是一件简单的事]] ,如果还要考虑书写系统之间的巨大差异(这大概得怪巴别塔)无异于雪上加霜。运行双语博客会导致字体选择的麻烦加倍,这里是我遇到的一些问题的汇总。
  529 
  530 *** 空格侵略者
  531 大多数浏览器会将 HTML 中的连续文本合并为一行,并在链接处加上空格。所以
  532 #+BEGIN_SRC html
  533   <html>Line one and
  534   line two.</html>
  535 #+END_SRC
  536 会被渲染为
  537 #+BEGIN_EXAMPLE
  538 Line one and line two.
  539 #+END_EXAMPLE
  540 这种一刀切的方法显然不适用与字符之间不带分隔的 CJK 语言。解决方案是为页面(或页面上的任何特定元素)指定 =lang= 属性,如下所示:
  541 #+BEGIN_SRC html
  542   <html lang="zh">第一行和
  543   第二行。</html>
  544 #+END_SRC
  545 如果你的浏览器足够聪明(例如 Firefox),渲染的结果就不会有额外的空格。但是,所有基于 Blink 的浏览器仍然顽固地将多余的空格塞进去,所以我只能像野蛮人那样继续写一段一行的源文件。尽管不是万能的解决方案,但是指定 =lang= 属性仍然具有启用特定于某种语言的CSS规则的额外好处,这稍后会派上用场。
  546 
  547 *** 引号归来
  548 如 [[https://www.shimmy1996.com/zh/posts/2018-06-24-fun-with-fonts-in-emacs/][之前的日志]] 所说, CJK 字体会将引号显示为全角字符,不同于拉丁字体。只要网页不尝试混搭字体,这就不会成为问题:只需使用特定于语言的字体栈就行。
  549 #+BEGIN_SRC css
  550   body:lang(en) {
  551       font-family: "Oxygen Sans", sans-serif;
  552   }
  553 
  554   body:lang(zh) {
  555       font-family: "Noto Sans SC", sans-serif;
  556   }
  557 #+END_SRC
  558 再加上匹配的 =lang= 属性,所有问题就都解决了。 Firefox 甚至允许为每种语言指定默认字体,所以仅使用后备字体(例如 =sans-serif= 或 =serif= )也可行,不一定要费心编写特定于语言的CSS。
  559 
  560 那么,如果我想用 Oxygen Sans 来渲染拉丁字符,并用 Noto Sans SC 来渲染 CJK 字符怎么办?虽然看似没有问题,但像这样指定字体堆栈,
  561 #+BEGIN_SRC css
  562   body:lang(zh) {
  563       font-family: "Oxygen Sans", "Noto Sans SC", sans-serif;
  564   }
  565 #+END_SRC
  566 会导致引号被 Oxygen Sans 渲染、显示为半角字符。我的解决方案是通过 =unicode-range= 定义一个涵盖了引号的替代字体,
  567 #+BEGIN_SRC css
  568   @font-face {
  569       font-family: "Noto Sans SC Override";
  570       unicode-range: U+2018-2019, U+201C-201D;
  571       src: local("NotoSansCJKsc-Regular");
  572   }
  573 #+END_SRC
  574 并修改字体栈为
  575 #+BEGIN_SRC css
  576   body:lang(zh) {
  577       font-family: "Noto Sans SC Override", "Oxygen Sans", "Noto Sans SC", sans-serif;
  578   }
  579 #+END_SRC
  580 这样我们就可以享受全角引号了!
  581 
  582 *** 字体忍者
  583 字体文件通常都不小,对于 CJK 字体来说更是如此:刚才提到的 Noto Sans SC 的大小 [[https://github.com/googlefonts/noto-cjk/blob/master/NotoSansSC-Regular.otf][超过8MB]] 。尽管我已经下定主意要从自己的服务器上提供所有文件,考虑到我网站上的平均 HTML 文件大小更接近 8KB,这显得有些过头了。那么那些网络字体服务如何处理这一问题呢?
  584 
  585 大多数网络字体服务的工作方式是在网站的样式表里添加一堆 [[https://developer.mozilla.org/zh-CN/docs/Web/CSS/@font-face][=@font-face= ]]定义,以从专用服务器上提取字体文件。为了减少所提供的文件大小, Google Fonts 会将字体文件大卸八块,并在 =@font-face= 里声明每一块所对应的 =unicode-range= (这正是它们处理 [[https://fonts.googleapis.com/css?family=Noto+Sans+SC][CJK 字体]] 的方式)。他们还将字体文件压缩为 WOFF2 以进一步缩减文件大小。而 [[https://fonts.adobe.com/][Adobe Fonts]] (以前称为 Typekit )似乎有一些 JavaScript 奇技淫巧,可以动态确定要从字体文件加载的字形。
  586 
  587 博采众家之长,得益于这是一个静态站点,我们可以简单地统计所有用到的字符,并提供一个只包含这些字符的字体文件。所要用到的工具主要是 pyftsubset (属于 [[https://pypi.org/project/fonttools/][fonttools]] 下的一个组件)和 GNU AWK 。将字体压缩为 WOFF2 还需要 Brotli 压缩库。在 Arch Linux 下,获取这些程序需要安装 [[https://www.archlinux.org/packages/community/any/python-fonttools/][python-fonttools]] 、 [[https://www.archlinux.org/packages/core/x86_64/gawk/][gawk]] 、 [[https://www.archlinux.org/packages/community/x86_64/brotli/][brotli]] 和 [[https://www.archlinux.org/packages/community/x86_64/python-brotli/][python-brotli]] 。
  588 
  589 收集生成的HTML文件中的所有使用的字形可以使用这条 shell 命令:
  590 #+BEGIN_SRC sh
  591   find . -type f -name "*.html" -printf "%h/%f " | xargs -l awk 'BEGIN{FS="";ORS=""} {for(i=1;i<=NF;i++){chars[$(i)]=$(i);}} END{for(c in chars){print c;} }' > glyphs.txt
  592 #+END_SRC
  593 你可能需要 =export LANG=en_US.UTF-8= (或者其他 UTF-8 语言环境)以便正确处理某些字形。有了字形清单,我们就可以提取字体文件的有用部分并进行压缩:
  594 #+BEGIN_SRC sh
  595   pyftsubset NotoSansSC-Regular.otf --text-file=glyphs.txt --flavor=woff2 --output-file=NotoSansSC-Regular.woff2
  596 #+END_SRC
  597 指定 =--no-hinting= 和 =--desubroutinize= 可以进一步减小生成文件的大小,但会降低字体的美观程度。拉丁字体也可以使用类似的技术来瘦身,例如只提取包含 ASCII 字符的部分(或将范围设为 =U+0000-00FF= 以涵盖 Extended ASCII 字符):
  598 #+BEGIN_SRC sh
  599   pyftsubset Oxygen-Sans.ttf --unicodes="U+0000-007F" --flavor=woff2 --output-file=Oxygen-Sans.woff2
  600 #+END_SRC
  601 大部分字体管理器都可以用来检查最后生成文件中可用的字形,也可以使用这一 [[http://torinak.com/font/lsfont.html][在线检查器]] (不支持 WOFF2,但是可以先试着转为其他格式后查看,例如 WOFF)。
  602 
  603 我还考虑过将字形按受欢迎程度划分为更多块。获取按出现次数排序的字形列表可以使用以下命令:
  604 #+BEGIN_SRC sh
  605   find . -type f -name "*.html" -printf "%h/%f " | xargs -l awk 'BEGIN{FS=""} {for(i=1;i<=NF;i++){chars[$(i)]++;}} END{for(c in chars){printf "%06d %s\n", chars[c], c;}}' | sort -r > glyph-by-freq.txt
  606 #+END_SRC
  607 结果显示我的博客用到了大约 1000 个不同的汉字,其中大约 400 个出现了10次以上。由于上一步中获得的字体文件大小已经足够好,我没有继续进行拆分。
  608 
  609 *** 孔中窥见真理之貌(好像没有啥不对)
  610 我最终将字体文件的总大小减少到了 250KB 左右,但这仍然比 HTML 文件大好几个数量级。虽然看到我的网站在所有设备和屏幕上都保持一致很让人开心,但是与页面大小增加上百倍的代价相比,我觉得这点好处不成比例。
  611 
  612 费劲心思指定字体或许并不值得。如果你希望看到我眼中本站的样子的话,以下是我的常用字体:
  613 - 比例拉丁字体: [[https://github.com/KDE/oxygen-fonts][Oxygen Sans]] 。注意 KDE 版本与 [[https://fonts.google.com/specimen/Oxygen][Google Fonts 版本]] 有一些微妙的区别,我更喜欢前者。
  614 - 比例 CJK 字体: [[https://www.google.com/get/noto/help/cjk/][Noto Sans CJK]] ,即思源黑体。
  615 - 等宽字体: [[https://typeof.net/Iosevka/][Iosevka]] ,确切地说是 ss09 样式。
  616 
  617 * DONE Review of Star Wars: The Rise of Skywalker
  618 :PROPERTIES:
  619 :EXPORT_DATE: 2019-12-20
  620 :EXPORT_HUGO_SLUG: review-of-star-wars-the-rise-of-skywalker
  621 :END:
  622 
  623 ** DONE en
  624 :PROPERTIES:
  625 :EXPORT_FILE_NAME: 2019-12-20-review-of-star-wars-the-rise-of-skywalker.en.md
  626 :EXPORT_TITLE: Review of Star Wars: The Rise of Skywalker
  627 :END:
  628 
  629 Spoiler alert!
  630 
  631 Just to get it out of the way: I watched the prequels before the original. I thought the prequels were fine - at first viewing, I felt it was as much an Obi-Wan story as it was Anakin's, and I didn't fully realize how good McGregor's performance was until I watched the old trilogy: they felt like the same person to my childhood self. After seeing full picture of the story, I can see how people who grew up with the original trilogy would view the prequels as an utter blasphemy of the original. Watching the prequels first did took out some thrill of the big reveal in /Empire Strikes Back/, but I was no less shocked when Anakin actually turned to the dark side in the prequels.
  632 
  633 My very first encounter with Star Wars, however, were not the prequels, but a version of /Star Wars: The Visual Encyclopedia/ I found in a local book store. It was all those weird weapons (including a lightwhip that I remembered distinctively), spaceships, and costumes that first enticed me to this world. I was more than rejoiced to find out that the prequels depicted exactly such an colorful yet exotic world. The tone of the original trilogy was a lot more bleak, more "spacey" than "alieny", and as a child who just witnessed the downfall of Anakin, the transition felt natural to me.
  634 
  635 Moving on to the sequel trilogy. I watched /The Force Awakens/ on launch date at 19:00 with my college roommate on launch date and we spent half an hour searching for a parking spot, barely making it to the screening by the opening scroll (we still got a ticket though). As for /The Last Jedi/, I watched it at night a month after it launched. I went to see /The Rise of Skywalker/ at 9:00 the day it was released, a surprisingly fitting time for the end to a trilogy. TFA was a decent start, nostalgia mixed with several intriguing leads made the experience quite enjoyable. TLJ left a really bad taste in my mouth in that it not only answered questions TFA raised in the poorest way possible, but also spent too much time trying to teach the old, established characters a lesson while neglecting the growth and development of the new characters. I still have an unpublished blog post full of my rants on TLJ (from 2018), so let's move on to TROS.
  636 
  637 In short, I enjoyed watching TROS, despite it being a over-packed messy hodgepodge.
  638 
  639 The beginning sequence revealing Kylo's encounter with the Emperor and the Falcon crew escaping First Order was succinct and exciting. As Finn, Rey, and Poe reunite though, the pacing dropped considerably, with meaningless arguments breaking out between the trio: I really hope team building is something the last movie of a trilogy shouldn't be worrying about, but the plots of TLJ left J. J. Abrams little choice here I guess.
  640 
  641 Then the movie went nowhere for a good half an hour showing the trio wondering around different planets doing things, also "sacrificing" Chewbacca and C-3PO in the process. Even the revelation of Rey's healing powers seemed so intentional that they are bound to be plot devices. The only good scene out of all these is probably the Rey facing off Kylo. In fact, most of the dual scenes between the two are really enjoyable, and these are the only places I can see the slightest bit of human emotion from Rey (in contrast to Kylo, whose constant struggle and change of heart were expressed amazingly by Adam Driver). Rey being a Palpatine was interesting at first, but adds little to the her overall character: it was Kylo who felt the temptation from the dark side this whole time, and all of a sudden this becomes Rey's thing?
  642 
  643 The dual on Death Star remains was visually stunning, but the way it ended could have been a bit less awkward: more mandatory plot device showoff, and an extra dose of Han Solo that I think was totally unnecessary given how good Adam Driver's portrayal is. Carrie Fisher's passing away was unfortunate, but I think that caused the rather rushed ending for her character. The entire self-exile sequence also felt corny, and uncharacteristic of Rey. Perhaps Leia being the one to give Rey the last guidance and her lightsaber would have worked better (either as she is passing away or as a Force ghost)?
  644 
  645 Subsequent plot again splits Rey away from her supposed "teammates", and sets the stage of the final showdown between the Resistance and the "Final Order" based on Finn and Poe's seemingly crazy idea (which Poe was specifically told not to do in TLJ). Lando appearing early in the plot and doing pretty much nothing feels like a missed opportunity: lack of screen time with the new crew in previous films left him with little ways to interact with them. I would much prefer if he just make a one-off appearance among the thousands of starships coming to the Resistance's aid in the end. I like the do Resistance' side of story here though: characters are shown to be working together with good chemistry, and they accomplished the impossible in a sensible way.
  646 
  647 The fight with the Emperor though was a mixed bag: everything leading up to the final face down was amazing (the lightsaber passing scene was great), until Rey had to face the Emperor alone. There seems to be simply too little emotional connections between Rey and the Emperor for any confrontation between them to have any weight. If anyone, Kylo Ren should be the one allowed to show his resolution at the end of his long journey, not Rey being the same Rey she was in TFA. Having Kylo sacrificing himself in the fight, assuming the role Vader played in /Return of the Jedi/, would have been a much more fitting ending to him than crawling back to heal and kiss Rey (AKA showing off the plot device we spend 15 minutes foreshadowing). The thousand Sith vs. thousand Jedi bit felt forced (pun intended) and doesn't really even tie into the story that much. By the way, the Emperor looked SCARY, and in a entertaining way: the aesthetic resembles 80's horror film, and strangely felt right here (not to mention that the movie opened with "THE DEAD SPEAK!"). It's also funny that star destroyers are finally rightfully so with their shiny new canons.
  648 
  649 Well, looks like I didn't really enjoy the movie now, do I? I'm also surprised that I can still pull out so many things I didn't like despite remembering walking out of the movie theater with a sense of relief and fulfillment. Looking back, the whole trilogy just felt poorly planned, with throw-away characters appearing here and there whose screen time fed to some new droid or alien creature every film presumably just to sell more toys, and broken plot lines that just didn't really make sense. Perhaps /The Rise of Skywalker/ is a valiant attempt at responding to a trick question with no suitable answer and I appreciated the effort. I wonder what the generation growing up with the sequel trilogy would think about them though: would they look back on them fondly the same way I look at the prequels (or Spider-Man 3 for that matter), or is my feeling not entirely clouded by nostalgia after all?
  650 
  651 ** DONE zh
  652 :PROPERTIES:
  653 :EXPORT_FILE_NAME: 2019-12-20-review-of-star-wars-the-rise-of-skywalker.zh.md
  654 :EXPORT_TITLE: 《星球大战:天行者崛起》影评
  655 :END:
  656 
  657 剧透警告!
  658 
  659 话说在前面:我在正传之前看的前传。我觉得前传其实还可以-初看时,我觉得这既可以算是阿纳金的故事,也可以算是欧比旺的。直到我看了老三部曲之后,我才完全意识到麦格雷戈的表现是多么出色:我小时候甚至以为他们是同一个人。看完故事的全部内容后,我可以理解与老三部曲一起长大的人为何会将前传视为对其的亵渎。虽然先看前传确实使得《帝国反击战》中的大转折变得平淡许多,但是当看到安纳金在前传中转向黑暗面时,我的震惊程度也绝对不低。
  660 
  661 我与《星球大战》的第一次接触其实也不是前传,而是我在当地书店中找到的某个版本的《星球大战:视觉图典》。正是那些怪异的武器(包括我印象特别深刻的光鞭)、宇宙飞船和服装使我对这个世界着迷。我很高兴地发现前传恰好描绘了一个如此多彩而充满异界情调的世界。老三部曲的氛围则要黯淡的多,更加“太空”而非“异星”。在目睹了阿纳金堕落之后,这种过渡对儿时的我来说很自然。
  662 
  663 接下来讲讲后传三部曲吧。我和我的大学室友在首映日晚上七点观看了《原力觉醒》( TFA )。在即将开映前我们花了半个小时寻找停车位(虽然最后还是因为超时吃了罚单),好不容易才赶在片头结束前进入放映厅。至于《最后的绝地武士》( TLJ ),我是在首映后一个月的晚上看的。今天早上我赶去看了《天行者崛起》( TROS )的首映,早上九点对于观看三部曲的结尾其实意外地合适。 TFA 是一个不错的开始,怀旧加上几条有趣的线索使观看体验变得非常愉快。 TLJ 则给我留下了非常不好的印象,因为它不仅以最糟糕的方式回答了 TFA 可能提出的问题,还把大部分时间花在试图给已经树立成型的老角色上课,而忽略了新角色的成长和发展。我还有一篇充满了我对 TLJ 牢骚的日志躺在我的( 2018 年的)草稿箱里,所以这里就不多叙述,让我们转移到 TROS 上吧。
  664 
  665 简而言之,虽然 TROS 是一个杂乱无章、臃肿不堪的大杂烩,我看得还是挺开心的。
  666 
  667 电影开头的一连串镜头简洁地展示了凯洛与皇帝的见面和千年隼团队逃离第一秩序的经过,令人兴致高涨。但是电影的节奏在芬恩、蕾伊和波会面后急转直下,充斥这三人之间毫无意义的争论:我不希望三部曲中最后一部电影还花时间在主角团队建设上,但是 TLJ 的剧情大概没有给 J·J·艾布拉姆斯留太多选择的余地。
  668 
  669 这之后的半个小时,三人组和剧情都像无头苍蝇一样在行星间四处乱撞,并在此过程中“牺牲”了丘巴卡和 C-3PO 。蕾伊疗伤能力的展示如此刻意,以至于这几乎必然是情节装置。所有这些中唯一的好场景可能是蕾伊面对凯洛的地方。实际上,两者之间的大多数独处场景都非常有意思,因为只有这些地方,我才能看到蕾伊流露出一点点真实情感(与凯洛形成鲜明对比,凯洛不断的内心挣扎和变化被亚当·德赖弗完美地表现了出来)。蕾伊是一个帕尔帕庭的事实在揭露时挺让人吃惊,但对她的角色整体并没有造成多大影响:一直以来收到阴暗面诱惑的明显是凯洛,而不是蕾伊。
  670 
  671 死星残骸上的对决在视觉上令人惊叹,但结束的方式可能有点尴尬:更多的极为刻意的剧情装置的展示,以及最后有点多余的韩·索罗镜头(考虑到亚当·德赖弗的出色演技,我认为这里体现他的转变完全不需要韩)。嘉莉·费雪的逝世是不幸的,但我觉得这为她的角色带来了过于仓促的结局。蕾伊自我放逐的过程也让人觉得老套,而且没有雷伊的特征。或许莱娅(在她即将去世时或是以原力鬼魂身份)才应该是是给蕾伊提供最后指导并而赠予她的光剑的人。
  672 
  673 随着情节再次将蕾伊与她所谓的“队友”分开,反抗军和“最终秩序”的决战在芬恩和波看似疯狂的想法(尽管 TLJ 花了大量篇幅明确告知波不要这么做)中开幕。过早出现的蓝多在大部分时间里都无所事事,有点浪费这一角色:由于缺少与新主角团共处的时间,蓝多几乎没有与他们互动的方式。我更希望他只在最后前来援助抵抗军的成千上万艘飞船的其中之一里作为彩蛋出现。话说回来,我挺喜欢抵抗军这一条线的故事:角色之间表现出良好的化学反应,并且以巧妙的方式完成了看似不可能的任务。
  674 
  675 与皇帝的战斗则是好坏参半:最终对决之前的一切都非常棒(光剑传递的场面特别赞),直到雷伊不得不独自面对皇帝。雷伊和皇帝之间的情感联系太少了,以至于他们之间的任何对抗都感觉十分空洞。如果有任何角色应该在其漫长旅程的最后得到展示其决心的机会的话,那应该是凯洛,而不是从 TFA 以来都一个样的蕾伊。让凯洛在战斗中牺牲自己、取代维达在《绝地归来》中扮演的角色,会是一个比爬回对决地点、治疗并亲吻雷伊(同时炫耀花了至少十五分钟铺垫的剧情装置)更加契合角色的结尾。一千个西斯对一千个绝地武士的部分显得尴尬且并不怎么贴合故事。顺便一提,皇帝看起来十分吓人(有趣的意义上):观感酷似80年代的恐怖片,但意外地很合适(更不用说这部电影以“THE DEAD SPEAK! ”开头了)。同样有趣的是,皇帝改装的歼星舰终于有了名副其实的歼星级武装。
  676 
  677 好吧,看来我其实也不太喜欢这部电影?我也有点惊讶我能想到这么多负面的评价,即使我记得走出电影院时,我的心情是轻松而满足的。回顾后传,这一三部曲整体的感觉就是缺少规划,充斥着即用即抛型角色、本可以用于发展角色的时间流向了一些新奇的机器人或外星生物(大概是为了卖出更多的玩具),而最关键的剧情方面则支离破碎、不合情理。也许《天行者崛起》是在没有适当答案的情况下尝试解决如何为三部曲收尾这一棘手问题的勇敢尝试,而我欣赏这最后的努力。我不知道在后传三部曲中长大的那代人会怎么想他们:他们会像我看前传(或者是《蜘蛛侠 3 》)一样看待后传吗?还是说我的感受并没有完全被怀旧之情蒙蔽?