打造你專屬的倉頡輸入法

我是倉頡輸入法的使用者,在 windows 10 之前,微軟倉頡輸入法是可以選擇只顯示 big5 字元的。但 windows 10 之後這選項便不復存在。

我想只顯示 big5 是因為 unicode 有非常多的的罕用字,這導致嚴重的重碼問題,而那些罕用字在大部分情況下根本用不著,它們只會降低我的輸入速度和爽快感。

我嘗試在各論壇詢問是否可設定只顯示 big5 字元,截至 windows 10 version 2004,還是沒有辦法設定的。而最接近的答案,是重新安裝舊版的 「微軟新倉頡 」輸入法,詳情可看這篇:

https://irregular.enzan.org/post/3292

但他有一個bug,就是聯想字的部分顯示不出來,加上大五碼本身亦有重碼的問題,例如你打 「夢 」字他會有 「夢甍藅蘮 」四個輸出。而且它是使用 IMM 架構的舊版輸入法,每次更新 windows 便要重裝一次…

既然要安裝另一個輸入法,我在想,有沒有其他的倉頡輸入法可以完全客製化,把那些罕用字刪除,成為我心目中理想的倉頡輸入法呢?

然後我找到一篇關於 dime 輸入法的文章:

https://terryhung.pixnet.net/blog/post/35608897

dime 相當接近我要的東西,其可取之處在於:

  1. 它是基於新的 TSF 輸入法架構,這樣使輸入法的安裝和移除更加方便,未來的支援更有保障,而且不用每次更新 windows 便要重裝。
  2. 它可以自行修改內碼表,這樣我便可以移除一些根本用不到的罕有字,令重碼問題大幅降低。

內碼表應如何製作呢?我先把已做好的內碼表放在這裏給大家參考,是純文字,內容亦相當容易理解:

https://hoishing.github.io/forum/dime_cangjie.txt

我對內碼表有以下的要求:

  1. 不要包含太多罕有字。
  2. 重碼字常用的應在前,例如輸入「人大口」,「知」應在「佑」之前。

對於問題一,我的解決方法是使用中華民國教育部編訂的「常用國字標準字體表」和「次常用國字標準字體表」,其他的字暫時不包括在內,這樣整個字庫加起來只有11000多字。

要解決第二個問題,字庫可依教育部的字頻表排序

http://language.moe.gov.tw/001/Upload/files/SITE_CONTENT/M0001/86NEWS/download/86rest17.TXT

至於每個字對應的倉頡碼,可到政府資料開放平台下載全字庫

https://data.gov.tw/dataset/5961

就這樣,我用 python 把上述的原材料整理一番後,便得出上面的代碼表。然後把它載入 dime 的「自建」輸入法內,一個完全客製化的倉頡便大功告成!

最後對於 dime 的使用,有以下幾點註腳:

  1. windows 的 language settings 內並沒有輸入法的設定。先打開 notepad,然後按 ctrl \ 才可進入設定,這是 dime 最需要改善的地方。進入後,記得把 「組字區最大長度」設為 5,因倉頡最長可有五碼。
  2. 如果安裝後發現沒有聯想字,可先安裝內置的「微軟注音」輸入法,打開「相容性:使用舊版的微軟注音」,然後把 user folder 內的 appdata\roaming\dime 刪除,再重裝 dime 即可。
  3. 安裝 dime 後,它會一拼安裝注音、大易等輸入法,而你要用的只是自建輸入法。要移除不需要的輸入法,你先要在 language setting 中把所有的 dime 輸入法加上去,re-login 後再從 language settings 中移除。

Using Chinese Characters in Matplotlib

When you try to display Chinese or other non-ascii characters in matplotlib, your characters may not be displayed properly, like following figure:

Error displaying Chinese characters in matplotlib

It is because the fonts used by matplotlib couldn’t decode the characters properly. To solve it, we should add the appropriate fonts and update matplotlib font cache.

  1. locate the matplotlib fonts folder:
import matplotlib
print(matplotlib.matplotlib_fname())

this is the location of matplotlib config file, you will get something like …/matplotlib/mpl-data/matplotlibrc

The font folder is …/matplotlib/mpl-data/fonts/ttf , put your ttf file there.

2. Get ttf from ttc file (skip if you have ttf file already)

For macOS, the system Chinese fonts is Heiti, which is embed in a ttc file (ttc is a collection of multiple ttf files). Get the system ttc file in /System/Library/Fonts/STHeiti Medium.ttc Copy this file out and convert it to ttf. Here is an online ttc converter:

https://transfonter.org/ttc-unpack

3. Rebuild the Matplotlib Cache

import matplotlib.font_manager
matplotlib.font_manager._rebuild()

restart your Jupyter / ipython kernel, then test if matplotlib can load your font or not by the ttflist function of the font manager

[f for f in matplotlib.font_manager.fontManager.ttflist if 'Heiti' in f.name]

change ‘Heiti’ to your own font name. If you see your fonts object above. You are ready to use the new fonts.

4. Using Appropriate Fonts in Matplotlib

matplotlib.rcParams['font.family'] = ['Heiti TC']

This will tell matplotlib to use the specific font as default, result as follow~

Using Javascript with WKWebView

Harnessing The Power of Both Languages in Your Apps

WebKit allows us to use javascript along side with the native swift code. On one hand, we could call the javascript statements in swift. On the other hand, javascript from web view could be able to trigger a delegate method defined in swift code. This gives us a two way communication between the native swift code and javascript used by a web view.

Basic Setup

Here we have a window with a WKWebView showing some text. It has 2 buttons inside. One for showing the text and triggering a swift handler, the other for hiding the text. Also, we have a native button that triggers a javascript function to hide the text.

The Demo App

Triggering Javascript Functions from Swift

The IB outlet and action of the app as follow. Note that we simply use evaluateJavaScript(_:completionHandler:) to trigger some javascript in the web view, hideText() is a custom method we defined inside javascript.

Receiving Javascript Messages

To receive message from the javascript, we need to provide a message name for javascript to call upon. We just name it “jsHandler”. After that we load the html and javascript into the web view.

We also need to adopt the WKScriptMessageHandler protocol in our view controller, and implement the userContentController(_:didReceive:) method. For simplicity, we just print out the message received from the javascript.

The HTML and javascript we loaded as follow. Note that in the showText() method, the window.webkit.messageHandlers.jsHandler.postMessage() is the method we use to trigger the delegate method implemented in our view controller.

Download Sample Project

Adding Login Items for macOS

Automatically launch your app after login

To create an app that can be auto launched after login (i.e. adding login item) is far more complicated then expected…

For sandboxed app, the recommended approach is using the Service Management Framework. (Adding login items using a Shared File List is another approach for non-sandboxed app, which will not be discussed here.)

The basic concept is to create an “Helper Application” that registered to the system, which responsible for launching your main app while user login.

The Helper App

In your project, create a new Cocoa Application target.

Project with 2 targets

As the helper app should have no visual element, remove the view controller and window scene from the storyboard, as well as the related swift files.

  • Remove the ViewController.swift
  • Remove the View Controller Scene in the storyboard
  • Remove the Window Controller Scene (if you want visual on the helper app to see if its successfully launched by the main app or not, as well as launched by the system service or not after logging in, you may keep it for a while and delete it afterwards)

As the helper app will have the same default AppDelegate.swift which conflict with the main app. You need to rename it and adjust the class name of App Delegate in the Application Scene accordingly.

Renaming Conflicted Files

Then make the helper app as a background service. In Info.plist, set Application is background only to Yes.

Info Plist Setting of the Helper App

In the app delegate, check if the main app is already running or not. If not, launch it.

The tricky part is to delete the last four path components of the helper app bundle path. It is because the helper app is actually embedded inside the main app bundle, under the subdirectory Contents/Library/LoginItems. So including the helper app name there will be a total of 4 path components to be deleted.

Another tricky part is to set skip install = YES in the build setting.

Build Setting of the Helper App

You then turn on App Sandbox in Capabilities of the helper target, and the helper app part is done.

Main App

You first import ServiceManagement, then check if the helper app is already running or not, launch and register it with SMLoginItemSetEnabled(_:_:) if necessary.

In my sample app I created an checkbox to toggle the helper app, as well as showing if its already launched or not.

We then copy the helper app into the main app bundle. In the Build Phases of the main app, create a new Copy Files phase.

New Copy Files Phase

with the following settings:

  • Destination: Wrapper
  • Subpath: Contents/Library/LoginItems
  • Add the helper app file
Embed the helper app

The last step is to enable App Sandbox and Development Signing of both main and helper app, then your are good to go.

Testing

You can test the auto launch feature as follow:

  1. Build the main app — NOT RUN (if you build and run the main app in Xcode, the helper app may not function after quitting Xcode)
  2. Right click the product of your main app → Show in Finder
  3. Double click the main app in Finder to launch it, and check the auto launch button
  4. Quit everything and log out
  5. Log in again

The main app should be auto launched by the helper, with the auto launch button checked.

Main app

Download Sample Project

Create Menu Bar Apps On macOS

Screen Shot 2017-06-12 at 6.51.22 P

Menu bar apps refer to apps that sit on the menu bar (a.k.a. status bar) of macOS. It provide instant access of the key functionalities, as well as being accessible all time during a login session.

Creating the Status Bar Item

We use NSStatusBar.system().statusItem(withLength: -1) to request a status bar item from the system. Note that it may be failed (return nil) if the status bar is already full packed with many other status bar items.

Upon successfully got an status bar item, you got a NSButton as the UI element to put on the menu bar. You need to configure it by providing an image, as well as setting the target and action of the button.

All these should be done during the app launching period, so in the app delegate, we have:

Note that a strong reference must be used to retain the status bar item object returned by the system. Otherwise it will be released afterwards, and nothing will be shown on the menu bar.

Displaying the Menu

To display the menu, you need to provide a reference point for placing the menu object, in this case, the status bar button. We then create a mouse event, and use popUpContextMenu(_:with:for:) to bring up the menu.

Info.plist Setting

If your app is menu bar ONLY, similar to the Dropbox macOS client, you need to set Application is agent = YES in your Info.plist in order to omit the Dock’s app icon and the app menu on the upper left corner.

Download Sample Project