deno

github.com

作者のRyan Dahlは言わずと知れたnode作者。nodeのしがらみを捨ててV8+GolangでTypeScriptの実行環境を作ると言う。。。いいぞもっとやれ。

たった24日前からのスタートで、まともなリリースも無いのに、すでに13.4kのStarがついているのが驚く。

個人的な期待

個人的な期待はTypeScriptネイティブな実行環境として存在感示しながらnodeへフィードバックされることです。denoがnodeをリプレースするのは正直ちょっと考えづらいけど、へっぽこな自分では到底書けないだろうなーというレベルのチャレンジを早期からウォッチし続けるのは楽しいし、皆もそう思うようですごい勢いで課題とプルリクが集まってますね。。。もしやワンチャン有るのかな?

そして、ちょうど先月にnode-gypやらcmakeやらで拡張を作ってたタイミングでdenoが出てきた偶然に驚くし、とても琴線に響きます。静的に型が付いてるTS拡張オブジェクト(型の無いJS拡張ではなく)をGolangで書けると素敵で、もしちゃんとdenoが世にでるのであればこれが個人的に期待されます。V8のAPI層ではこれまでと同じでも新しくdenoで丁寧にブリッジ構成したら可能かもしれない。しかしGolangじゃなくSwiftだった方がよりインパクト強烈だった気がする。GolangもSwiftもC/C++バインディングが容易なのでどうにでもなるけど、SwiftだとmacOSiOSにネイティブなのでたったそれだけで何か産まれてきそう。まあ、GolangとSwiftをone on oneで比べたらGolangの方が世の多数に好かれてるようにも思いますけど。

ビルドしてみた

v8worker2のビルドでエラー吐いて止まった。馴染みのないビルドプロセスで追うのが辛そうだから、根性なしはすぐ目をつぶって、もうちょい優しくなってからトライする。brewインスコした環境は残しておきます。go getしたものは削っとく。最近Golangで書いてたもの全部TypeScriptで書き直し終えたところだったからGOPATHの中身は空でした。

$ cd ~
$ rm -rf go
$ mkdir go

ccache ?

手仕舞ってから、ふとビルドの際に--use_ccacheとスイッチ渡していたのを思い出して見直すと、確かにccacheはインスコしてなかった!ということでbrew install ccacheでもう一度ビルドやり直し。。。うん、やっぱりエラー。今日はおしまい。

OfficeのAutoUpdateそれ自体の更新が失敗する

f:id:masataka_k:20180601135642p:plain

普段はGoogle G-Suiteばかり使ってるのでMS-Officeを利用することが無いのですが、Office for Macサブスクリプション(Office 365というべきか)を持ってます。たまにExcel開くと画面のように、「Office Update サブ Web」なるもので更新があることを伝えてくるのですが、ここからポチポチ進めても一向に更新できませんでした。それはAutoUpdateそのものが立ち上がってるためにインストールできないという何のジレンマなのか、パラドックスなのか。

解決方法

support.office.com

重要: [ヘルプ] メニューに [更新プログラムの確認] が表示されない場合は、最新バージョンの Microsoft AutoUpdate ツールを https://go.microsoft.com/fwlink/?linkid=830196 からダウンロードします。ツールを実行し、手順 1 からやり直します。これで、[更新プログラムの確認] オプションが [ヘルプ] メニューに表示されるようになります。

  • ヘルプメニューに更新プログラムの確認オプションが表示されていても、ダウンロードしないとだめ。
  • このページで見つけたリンクから、AutoUpdateのバイナリをダウンロードする。
  • ダウンロードしたバイナリをインストールする。
  • 治る。

AutoUpdateを頼みにしていたら永遠に治らない。

CLionが最強かもしれない

WebStorm大好きっ子な私ですが、WebStormはWEB系技術をターゲットとしていてC/C++IDEサポートはありません。Go言語関連がGoLandに製品格上げなされた結果としてプラグイン提供がなくなってしまったのと同様にC/C++プラグインもどうやらWebStormに提供されていない。素のWebStormでもかろうじてカラーリングやカッコ対応などの静的テキストファイルの範囲で頑張れるような機能はあったので、メモ帳よりはマシだと考えてましたがヘッダを見るのにもSpotlight検索かよ、と、気が短くなった中高年には無理無理。C++「も」気持ちよく書ける環境を模索して構築します。前提はTypeScriptで気持ちよく書けることで、さらに同じその上でC++

CLion

www.jetbrains.com

信頼するJetBrainsの製品群を眺めてそれっぽいのをピックアップしました。ReSharper C++なるものはVisualStudioのプラグインだって書いてありますから初見除外すると、CLionとAppCodeというのになります。まずMac向けっぽい体裁のAppCodeをトライアルして見ましたがいきなりXCodeが無くて動かないぞエラーを出すので即アンインストールしました。私はXCodeを入れずにcommandline-toolsで凌いでいるし、今後もフルのXCodeを入れる予定は無い。またAppCodeはメインとしてSwiftとObjective-Cを対象としてMac向けに閉じていました。次いでCLionをトライアル。CLionはプラグイン設定で「NodeJS」と「Vue.js」それぞれのサポートプラグインをインストールできました。なぜか製品デフォルトでTypeScriptとTslintのサポート(当然上位のJavaScriptサポートやHTML5関連なども)は入ってたので私のWebStormの使い方にC++が加わったセットを容易に構成完了。既に動かす前からガッツポーズな気分。node/TypeScript/tslint/Jestは一通り動かして問題ない。

CMake

CLionでC++の新プロジェクトを作る場面でいきなり直面するのは、CMakeを用いた開発への強い推し姿勢です。IDEを一貫してCMake中心に考えてられているように見受けます。これまでCMakeは使ってなかったので手元環境には入れてなかったのですが、/Applications/CLion.app/Contentsを掘って確認すると、clang・cmake・gdb・lldbを含んでいました。CMakeはBrewだともっと新しいバージョンになりますが、CLionがサポートしているバージョンが一個古いものまでなので逆らわずに/usr/local/binへリンクを作って、IDE外部からの利用でも同じものを使うようにします。

$ cd /usr/local/bin
$ sudo ln -s /Applications/CLion.app/Contents/bin/cmake/bin/cmake

CLionもその他のIDEでもnode-gypを綺麗にサポートしているものは無いと断じられる中、同じ効能をCMakeベースで構築し直したというCMake.jsがありました。これのチュートリアルをざっと流してみて(10分程度で完了します)。。。ちゃんとビルドできるし、node-gypで実現されていたみたいに自動コンフィギュアかつ自動ツールチェーンで動きます。よしよし。あくまで短時間での感触ですが、CLion-CMake-CMake.jsの組み合わせは筋が良さそうな気がする。

$ yarn add nan cmake-js bindings

WebStormが初年度$129.00/Yなのに対してCLionが初年度$199.00/Yなので、この値段差に柔軟性の違いがあるのでしょう。年末までWebStormのサブスクリプション残しているけど、CLion欲しいな。30日トライアルし尽くして気持ち変わらなければWebStormの半年分がもったいないけど切り替えよう。

とりあえずの設定

CMake.jsがNodeアドオンを作るために必要な環境の情報をかき集めてCMakeの設定を行ってくれるので、それをIDEにも教えてあげないといけない。

$ yarn cmake-js print-configure --debug

このコマンドで、CMake.jsがどんな情報収拾し、CMAKEに設定しているかがダンプされます。

cmake "/Users/masataka_k/Projects/clpoc" --no-warn-unused-cli -G"Unix Makefiles" -DCMAKE_JS_VERSION="3.7.3" -DCMAKE_BUILD_TYPE="Debug" -DCMAKE_LIBRARY_OUTPUT_DIRECTORY="/Users/masataka_k/Projects/clpoc/build/Debug" -DCMAKE_JS_INC="/Users/masataka_k/.cmake-js/node-x64/v10.1.0/include/node;/Users/masataka_k/Projects/clpoc/node_modules/nan" -DNODE_RUNTIME="node" -DNODE_RUNTIMEVERSION="10.1.0" -DNODE_ARCH="x64" -DCMAKE_CXX_FLAGS="-std=c++11 -D_DARWIN_USE_64_BIT_INODE=1 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -DBUILDING_NODE_EXTENSION -w" -DCMAKE_SHARED_LINKER_FLAGS="-undefined dynamic_lookup"

ながーいコマンド行のうちのオプション部分をコピーしてCLionの設定画面に流し込む。

f:id:masataka_k:20180522060034p:plain

メニューでCLion > Preferences > Build, Execution, Deployment > CMAKE を選び、「CMake Options」にペースト。これでプロジェクトを背後でシンボルの構築が行われてIDEにおよそ期待する機能が動作し始めます。

このオプション群は、チュートリアルでコピペしたCMakeLists.txtに流し込まれてくる。

cmake_minimum_required(VERSION 2.8)

# Name of the project (will be the name of the plugin)
project(addon)

# Build a shared library named after the project from the files in `src/`
file(GLOB SOURCE_FILES "src/*.cc" "src/*.h")
add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES})

# Gives our library file a .node extension without any "lib" prefix
set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "" SUFFIX ".node")

# Essential include files to build a node addon,
# You should add this line in every CMake.js based project
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_JS_INC})

# Essential library files to link to a node addon
# You should add this line in every CMake.js based project
target_link_libraries(${PROJECT_NAME} ${CMAKE_JS_LIB})

これらの環境変数で穴開けてるところに当てはまるのをなぞると、だいたい何が行われているのかが想像できます。ここまでくるとあと必要なのはIDEではなく「そのもの」の知識ですね。チュートリアルはOKでも、CMakeの使い方の詳しいところはこれから習得しないといけない。

OSXの.frameworkを探す

# 〜省略
if(APPLE)
    find_library(CORETEXT CoreText)
    message("CORETEXT= ${CORETEXT}")

    find_library(FOUNDATION Foundation)
    message("FOUNDATION= ${FOUNDATION}")

    set(CMAKE_JS_LIB ${CMAKE_JS_LIB} ${CORETEXT} ${FOUNDATION})
    message("CMAKE_JS_LIB= ${CMAKE_JS_LIB}")
endif(APPLE)

target_link_libraries(${PROJECT_NAME} ${CMAKE_JS_LIB})

練習としてOSXの.frameworkを探す定義を書いてみました。find_libraryの第1引数は変数名で第2引数以降に色々探す方法を書くのだけど、今回の場合はCoreText.frameworkの頭の部分だけ「CoreText」を書けば見つけてくれる。よってFoundation.frameworkは「Foundation」。チュートリアルでコピペしたCMakeLists.txtでは、CMAKE_JS_LIBが書かれてたけど実際は使われていなかった。もしかするとどっかから飛んでくることがあるのかもしれないけど、今のところは何も。見つけたライブラリを跡地利用としてsetを用いてCMAKE_JS_LIBに積んでみた。プラットフォームの切り分けは、if(APPLE) - endif()でできる。endifに引数渡さなくても問題ないと思うけど、引数「APPLE」を渡すこの書き方の方が読みやすいです。

ごちゃごちゃやってる後の対応

ごちゃごちゃ色々やってる後で、突然今までなんでもなかったところでコンパイルエラーをエディタが表示し始めることがあった。でもコマンドラインからcmake rebuildを実行すると何事もなくビルドが通る。これはIDEのキャッシュが疑わしい。実際にググって見つけたヘルプの通りにinvalidしてrestartしたら元に戻った。

C++でCoreText

NodeモジュールでまずはOSのフォント情報を取ろうと思います。Objective-CでやればサンプルもたくさんあるのだけどここはC++で書きたかった。

#include <ApplicationServices/ApplicationServices.h>
#include <nan.h>
using namespace v8;

ApplicationServices/ApplicationServices.hを取り込みます。この中にCoreFoundationやCoreTextやCoreGraphics等のOSXの中で特にC++で書けるライブラリのヘッダだけがまとまってます。他はObjective-CかSwiftじゃないと書かせてもらえないみたい。文字列はNSStringではなくCFString。

char* CFStringToUtf8String(CFStringRef str) {
    if (str == NULL) {
        return NULL;
    }
    CFIndex max = CFStringGetMaximumSizeForEncoding(CFStringGetLength(str), kCFStringEncodingUTF8) + 1;
    char* buffer = (char*)malloc(max);
    if (CFStringGetCString(str, buffer, max, kCFStringEncodingUTF8)) {
        return buffer;
    }
    free(buffer);
    return NULL;
}

char* getStringAttribute(CTFontDescriptorRef ref, CFStringRef attr) {
    return CFStringToUtf8String((CFStringRef)CTFontDescriptorCopyAttribute(ref, attr));
}

char* getLocalizedAttribute(CTFontDescriptorRef ref, CFStringRef attr, CFStringRef *localized) {
    CFStringRef value = (CFStringRef)CTFontDescriptorCopyLocalizedAttribute(ref, attr, localized);
    if (value) {
        return CFStringToUtf8String(value);
    }
    return getStringAttribute(ref, attr);
}

逆算的な説明ですが、とにかくCoreFoundationの文字列であるCFStringから、char*へ変換するのは上記の通り。この用意を踏まえて、フォントDescriptorをいじっていきます。キモはCFStringGetCStringで、CoreFoundationの提供する世界から外界たるC++へ値を取り出すのは他のAPIでもおよそこんな感じ。戻りがBoolで成功失敗で値はバッファをポインタ渡しで受け取ります。APIはだいたい汎用な作りになっていて細かにフラグを設定することが多いです。ここではエンコーディング種別がフラグになってます。

CTFontDescriptorCopyLocalizedAttributeでは可能な場合にログインロケールな文字列を戻します。すなわち日本語をメイン言語として利用している私は「ja」でロケールが探され、得られれば第3引数のポインタにjaを返します。多分このログイン言語環境を動的に変更できるAPIもあるんだろうけど、調べてません。とりあえず今の私は不要。

int getFontWeight(CTFontDescriptorRef ref) {
    CFDictionaryRef traits = (CFDictionaryRef)CTFontDescriptorCopyAttribute(ref, kCTFontTraitsAttribute);
    CFNumberRef value = (CFNumberRef)CFDictionaryGetValue(traits, kCTFontWeightTrait);
    CFRelease(traits);
    float weight = 0.0f;
    if (CFNumberGetValue(value, kCFNumberFloat32Type, &weight)) {
        if (weight <= -0.8f) {
            return 100;
        } else if (weight <= -0.6f) {
            return 200;
        } else if (weight <= -0.4f) {
            return 300;
        } else if (weight <= 0.0f) {
            return 400;
        } else if (weight <= 0.25f) {
            return 500;
        } else if (weight <= 0.35f) {
            return 600;
        } else if (weight <= 0.4f) {
            return 700;
        } else if (weight <= 0.6f) {
            return 800;
        }
        return 900;
    }
    return 0;
}

char* getSrc(CTFontDescriptorRef ref) {
    CFURLRef url = (CFURLRef)CTFontDescriptorCopyAttribute(ref, kCTFontURLAttribute);
    return CFStringToUtf8String((CFStringRef)CFURLCopyPath(url));
}

Local<Object> toJSObject(CTFontDescriptorRef ref) {
    Nan::EscapableHandleScope scope;
    Local<Object> res = Nan::New<Object>();
    CFStringRef localized = NULL;
    res->Set(
        Nan::New<String>("font-family").ToLocalChecked(),
        Nan::New<String>(getLocalizedAttribute(ref, kCTFontFamilyNameAttribute, &localized)).ToLocalChecked()
    );
    res->Set(
        Nan::New<String>("font-style").ToLocalChecked(),
        Nan::New<String>(getStringAttribute(ref, kCTFontStyleNameAttribute)).ToLocalChecked()
    );
    res->Set(
        Nan::New<String>("font-weight").ToLocalChecked(),
        Nan::New<Number>(getFontWeight(ref))
    );
    res->Set(
        Nan::New<String>("src").ToLocalChecked(),
        Nan::New<String>(getSrc(ref)).ToLocalChecked()
    );
    if (localized) {
        res->Set(
            Nan::New<String>("localized").ToLocalChecked(),
            Nan::New<String>(CFStringToUtf8String(localized)).ToLocalChecked()
        );
    }
    return scope.Escape(res);
}

JSの世界へ持って来るために、Local<v8::Object>へ変換するのですが、いちいち面倒ですね。〜Refというのは全てポインタ型ですが、API毎に細かく定義されているのでCoreTextやCoreFoundationのヘッダファイルを見てあたりつけて、AppleのDeveloperサイトを検索するというのを繰り返す。。。同じCTFontDescriptorCopyAttributeでも、引数のフラグに応じて戻るポインタ型が違うので組み合わせの正解探しが続きましたが、ヘッダをよく見たらフラグのコメントに戻りの型がちゃんと書いてあった。

フォントWeightの変換ロジックは、font-managerからもらってきました。このfont-managerがメンテナンス止まってたので今回のコードを書いてます。ちょっと直すだけで使えたのだけど、繰り返しますが自分で書いてみたかったから。font-managerでのOSX版はObjective-Cを用いてます。

NAN_METHOD(getAllLocalFonts) {
    CTFontCollectionRef collection = CTFontCollectionCreateFromAvailableFonts(NULL);
    CFArrayRef results = CTFontCollectionCreateMatchingFontDescriptors(collection);
    CFRelease(collection);
    if (results) {
        CFIndex i, count = CFArrayGetCount(results);
        Local<Array> res = Nan::New<Array>(count);
        for (i = 0; i < count; i++) {
            res->Set(i, toJSObject((CTFontDescriptorRef)CFArrayGetValueAtIndex(results, i)));
        }
        CFRelease(results);
        info.GetReturnValue().Set(res);
    } else {
        info.GetReturnValue().Set(Nan::New<Array>(0));
    }
}

NAN_MODULE_INIT(Init) {
    Nan::Export(target, "getAllLocalFonts", getAllLocalFonts);
}

NODE_MODULE(fontFinder, Init)

JSからアクセスされる関数はこちら。C++というよりボキャブラリを問われる書き方が過ぎて、新種のDSLみたいな。。。先日はAsyncの作りを調べてましたが、結局ローカルのAPIを叩くのは完全にSyncで十分だった。500以上のフォントが入っていてもすぐに値が返って来ます。

node-gypそしてnanへ。おまけでTypeScript

github.com

nodeのアドオンを調べてたら本もなければ情報も少なく、この土日ずっと家族には仕事が忙しくてヤバいという体裁を装い篭ってたので、せめて記録ぐらいは残そうとサンプルを書きました。サンプルは超シンプルになりましたがこれは理解してからシンプルに書き直した結果で、元は何倍ものゴミクズです。

承前

  • nodeだけではできないことをやる。
    • 動画や画像の出力や解析はゼロから書くのは無理ゲーだけど良いライブラリが既にある
    • OSにインストールされているフォントを取得して使う。更に出力サイズはきっちり計る
  • パフォーマンス
    • 極重な処理をハイパーにこなす

既存のコマンドをシェルで叩き標準入出力やファイルシステムでツールをチェーンするのは手軽に実現できます。これは手元環境だけでなく、AWS Lambdaとかのサーバレス環境も要はDockerなのでこの方法は有効です。魔法を見て驚いて中身見たらexecやspawnかよとズッコケることたまにある。

無い物はシェルコマンドをGoやSwiftででも作れば良いのでしょう。そのほうがUNIX的な思考だし再利用もしやすい。でも、なんかズッコケるんだよね。燃えない。TypeScriptで楽に書きたいのに、nodeで無理なところ探してアドオン書こうというのは、やってみたかったという初めから答えあってのことです。手間を考えたらあんまり正解じゃなかった気もするけど、ギリギリ日曜の夜にはできるようになったからOK。

nodeのアドオンを作る

nodeのアドオンはnode-gypを使って作るのが便利なようです。これはマルチプラットフォーム開発環境を提供してくれて、プラットフォーム毎にMakefile等を調整してくれる。しかもnpmと組み合わせて利用者の手元でOSとnodeのバージョンに応じてコンパイルすることができるので配布が柔軟になります。あらかじめよくあるOSとnodeの組み合わせについてバイナリを用意しておくためには、node-pre-gypというのもあります。これはAWSに提供側ビルドをホストしておくことがすぐできる。

nodeのアドオンというのは結局はそのJSインタープリタであるV8の拡張なのですが、こいつが後方互換軽視なアグレッシブな姿勢で作られているらしく、つられてnodeもアグレッシブにならざるを得ない様子なので、過去からの互換性を維持するための抽象層がマクロとC++テンプレートで提供されています。それがNan。nodeアドオンの作りかたでググったら全く違ういろんな書き方が出てきます。これは記事が書かれた時期によるV8のAPI変化であり、書き手それぞれのNanの適用範囲の差であり、中にはNanを使っていない例も多いためでした。よってスマートかつモダンにnode-gypでV8かつフルNanなプログラミングをモノにしましょうという算段です。

ざっくりとした手順

実際はサンプル見てもらうことにして、ざっくりと手順をまとめると以下の感じ。

  • nodeのプロジェクトを作る。フォルダにpackage.jsonがあることが最低。
  • nodo-gyp、nanを用意する。node-gypはグローバルに入れるのが大勢のように思われる。
  • binding.gypを書く。これはnode-gypの唯一設定ファイル。
    • Nanの利用はおきまりのフレーズをここに書く。
    • 驚いたのはnodeのシェル実行でrequireを用いていた。node -e "require('nan')"
  • C++ソースコードを書く。V8とNanを利用。
  • package.scriptsに、"{ install": "node-gyp rebuild }" を追加する。
    • 開発時のビルドはこれをnpmやyarnで実行する
    • 公開後は利用者がnpm installないしyarn addしたと同時にビルドが走る
    • ちなみにnode-gyp rebuildはnode-gyp clean && node-gyp configure && node-gyp build
  • マルチプラットフォームはbinding.gypにうまく書ける仕組みがある。サンプルでは利用してない。
  • 既存ライブラリのリンクはbinding.gypにうまく書ける仕組みがある。サンプルでは利用してない。

Nan::AsyncWorker

サンプルでは、TypeScriptコードから文字列の引数とコールバックをアドオンに送る->アドオンは文字列の引数を保存して作業を行い(サンプルでは何も作業していないけど)、結果がOKだったら非同期にコールバックに返す -> コールバックのTypeScriptコードが実行される、という段取りになります。

ポイントはNan::AsyncWorkerがlibuvをラップしていて別プロセスを立ち上げていることです。ハイパーな処理はメインに迷惑かけずにここでやってくれという。サンプルのような軽い処理だったら本当は同プロセス同期実行でいいですね。その上でさらなるポイントは非同期に入る前に文字列を保存しているところです。V8の環境では常にガベージコレクションが走っていて、メモリの解放はAPIの中で強力にサポートされているので、別プロセス非同期実行だと解放された後にメモリアクセスしてしまいます。ここが私の週末の最後の壁で何度もクラッシュさせてました。今はstrcpyでコピーしているけど多分V8のAPIの中にうまい書き方が隠れていそう。 *1

Nan::New<String>("Hello ").ToLocalChecked()

頻出イディオムとして上記のようなコードを書くことになりますが、Nan::Newでv8::MaybeLocalというクラスを作り、ToLocalCheckedでガベージコレクションの対象となるように登録します。

TypeScript

ここまでの論点でTypeScriptは全く関係ないです。全編C++。利用のコツとしては、bindingsなるライブラリを使うことです。const binding = require('bindings')(<binding.gypのtarget_name>); bindingsはバイナリの位置を探します。

const binding = require('bindings')('helloLib');

export const greetingPromise = (name: string): Promise<string> => {
    return new Promise<string>((resolve) => {
        binding.nativeHello(name, (msg: string) => {
            resolve(msg);
        });
    });
};

こんな感じで、最近流行りのPromise化も可能。

import { greeting, greetingPromise } from '../hello';

test('greetingPromise', async () => {
    console.warn(await greetingPromise('Promise World!'));
});

こんな感じにasync-awaitで書けます。素敵。

気づき

  • V8はガベージコレクションをやるんだなということを思い出せばかなり光が射してくる
  • コールバックの有無と同一プロセス/別プロセスは違うこと
  • Nanは常に何かを楽にしてくれようとしてる。彼の親切心が何処にあるのかを探せば真実が見える

25年ぶりのC++、そして新たな問題

対象言語としてC++ 11が必須となっています。で、25年ぶりだから遠い記憶のそれは当然C++ 11じゃないんですよ。私の経験にあるのは学生の時に16bitのVC++でした。その後すぐにDelphiで更にはJava時代がきちゃう。初見はどこまでがマクロでどこから何のAPIで、言語仕様的なものはどこなのかわかりませんでした。結局Nanマクロが黒魔術であり、私が単にC++を忘れていただけでC++ 11的な何かというのは少なかったのですが。。。

ところでそもそも何でこんなことをしているかというと、まずはC/C++で書かれた魔法的ライブラリ達を取り込むためですが、次いではOSにアクセスするためです。しかしそのOSとは私の場合はOSXなのです。これが問題。OSXは基本的にObjective-Cであり、近年Swiftで代替されてつつあるわけです。C++からSwiftをイージーに呼べればアドオンのベースだけC++で書いてあとはSwiftで良かったのだけど、結構調べましたがC++からSwift(SwiftからC++じゃなく)をうまく使う方法がわからなかったので「おいおい、この年でObjective-Cかよ。俺はもう45才だぞ」と暗い気持ちになりました。Objective-CiPhoneの初期のころにいくらかやりましたが、なんかしっくり来なかったのでちゃんと習得しなかった。老い先短いので、同じく老い先短いObjective-Cは避けたい。その点でC++はいつまでも老いずに良いね。

更に調べると、CoreGraphicsやCoreTextといった一部のフレームワークC++でOKなよう。でもC++版のガイドやサンプルは当然皆無。OSXOSSじゃないからヘッダの先はブラックボックスなので、ヘッダを読んでObjective-C版のドキュメントやサンプルを参考にするしかない。ま、OSXの範囲でやりたいことは幸いにCoreGraphicsやCoreTextに集約されているようなのでボチボチやります。

*1:隠れてた。AsyncWorkerのSaveToPersistentとGetFromPersistentを使う

node v10.1.0は回復

node v10.1.0がリリースされていたので、nで切り替えて一通りテストやビルドを動かしてみました。

(node:26203) [DEP0005] DeprecationWarning: Buffer() is deprecated due to security and usability issues. Please use the Buffer.alloc(), Buffer.allocUnsafe(), or Buffer.from() methods instead.

ほぼ全てと言っていいぐらいに頻繁にBufferコンストラクタのワーニングが出ますけど、私の手元ではv10.0.0の時が嘘のように全てきちんと動きます。10.1.0でいいですね。

node-canvasの型定義備忘

node-canvasの型定義を使うところだけ書いてみたけど目的に敵わなかったので、定義だけ備忘。いつか使うかも。

declare module 'canvas' {
    import * as Stream from 'stream';

    interface PNGOptions {
        palette: Uint8ClampedArray;
        backgroundIndex?: number;
    }

    interface JPEGOptions {
        bufsize?: number;
        quality?: number;
        progressive?: boolean;
        disableChromaSubsampling?: boolean;
    }

    interface Canvas extends HTMLCanvasElement {
        inspect(): string;
        pngStream(options?: PNGOptions): Stream.Readable;
        jpegStream(options?: JPEGOptions): Stream.Readable;
        // pdfStream(): Stream.Readable;
    }

    interface Image extends HTMLImageElement {
        inspect(): string;
    }

    interface FontFace {
        family: string;
        weight?: string;
        style?: string;
    }

    function registerFont(src: string, fontFace: FontFace): any;
    function createCanvas(width: number, height: number, type?: any): Canvas;
    // function loadImage(src: string | Buffer): Promise<Image>;
}

v2.0.0-alpha12でregisterFontしてcreateCanvasで作ったヘッドレスCanvasに描画してpngStream/jpegStreamで書き出す一連の流れは問題ないが、loadImageで爆死。pdfStreamも不調。